…aaand add PREFIX to the freebsd makefile too
[voxelands-alt.git] / src / graphics / wm_x11.c
blobb31d1ffe6a8c4e4b3ad5e45c6773f8421a1a4485
1 /************************************************************************
2 * wm_x11.c
3 * voxelands - 3d voxel world sandbox game
4 * Copyright (C) Lisa 'darkrose' Milne 2016 <lisa@ltmnet.com>
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14 * See the GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>
18 ************************************************************************/
20 #include "common.h"
21 #define _WM_EXPOSE_ALL
22 #include "wm.h"
23 #include "graphics.h"
25 #ifndef WIN32
27 #include <X11/Xatom.h>
29 #define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091
30 #define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092
31 typedef GLXContext (*glXCreateContextAttribsARBProc)(Display*, GLXFBConfig, GLXContext, Bool, const int*);
33 /* attributes for a double buffered visual in RGBA format with at least
34 * 4 bits per color and a 16 bit depth buffer */
35 static int attr_list[] = {
36 GLX_RENDER_TYPE, GLX_RGBA_BIT,
37 GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
38 GLX_DOUBLEBUFFER, True,
39 GLX_X_RENDERABLE, True,
40 GLX_RED_SIZE, 4,
41 GLX_GREEN_SIZE, 4,
42 GLX_BLUE_SIZE, 4,
43 GLX_ALPHA_SIZE, 4,
44 GLX_DEPTH_SIZE, 16,
45 None
48 static XIMStyle style_shoose_best(XIMStyle style1, XIMStyle style2)
50 XIMStyle s,t;
51 XIMStyle preedit = XIMPreeditArea | XIMPreeditCallbacks |
52 XIMPreeditPosition | XIMPreeditNothing | XIMPreeditNone;
53 XIMStyle status = XIMStatusArea | XIMStatusCallbacks |
54 XIMStatusNothing | XIMStatusNone;
56 if (style1 == 0)
57 return style2;
58 if (style2 == 0)
59 return style1;
60 if ((style1 & (preedit | status)) == (style2 & (preedit | status)))
61 return style1;
63 s = style1 & preedit;
64 t = style2 & preedit;
65 if (s != t) {
66 if (s | t | XIMPreeditCallbacks) {
67 return (s == XIMPreeditCallbacks)?style1:style2;
68 }else if (s | t | XIMPreeditPosition) {
69 return (s == XIMPreeditPosition)?style1:style2;
70 }else if (s | t | XIMPreeditArea) {
71 return (s == XIMPreeditArea)?style1:style2;
72 }else if (s | t | XIMPreeditNothing) {
73 return (s == XIMPreeditNothing)?style1:style2;
75 }else{ /* if preedit flags are the same, compare status flags */
76 s = style1 & status;
77 t = style2 & status;
78 if (s | t | XIMStatusCallbacks) {
79 return (s == XIMStatusCallbacks)?style1:style2;
80 }else if (s | t | XIMStatusArea) {
81 return (s == XIMStatusArea)?style1:style2;
82 }else if (s | t | XIMStatusNothing) {
83 return (s == XIMStatusNothing)?style1:style2;
86 return style1;
89 /* initialise the game window */
90 int wm_init()
92 int min;
93 int max;
94 char* glXv;
96 wm_data.isinit = 1;
98 /* get a connection */
99 wm_data.dpy = XOpenDisplay(0);
100 wm_data.screen = DefaultScreen(wm_data.dpy);
101 wm_data.fb_cfg = NULL;
102 wm_data.cursor.mat = NULL;
104 if (XSetLocaleModifiers("") == NULL)
105 return 1;
107 /* get a connection */
108 XF86VidModeQueryVersion(wm_data.dpy, &max, &min);
109 vlprintf(CN_INFO "XF86VidModeExtension version: %d.%d", max, min);
110 XF86VidModeGetAllModeLines(wm_data.dpy, wm_data.screen, &wm_data.mode_count, &wm_data.modes);
112 /* save desktop resolution before switching modes */
113 wm_data.deskMode = *wm_data.modes[0];
115 glXv = (char*)glXGetClientString(wm_data.dpy, GLX_VERSION);
116 vlprintf(CN_INFO "glX version: %s",glXv,max,min);
118 return wm_create();
121 /* exit the game window */
122 void wm_exit()
124 wm_destroy();
125 XFree(wm_data.modes);
126 XCloseDisplay(wm_data.dpy);
129 /* create a window */
130 int wm_create()
132 Colormap cmap;
133 Atom wmDelete;
134 Window winDummy;
135 unsigned int borderDummy;
136 int x;
137 int y;
138 unsigned int w;
139 unsigned int h;
140 unsigned int d;
141 int i;
142 glXCreateContextAttribsARBProc glXCreateContextAttribsARB = 0;
143 XIMStyles *im_supported_styles;
144 XIMStyle app_supported_styles;
145 XIMStyle style;
146 XVaNestedList list;
147 int im_event_mask;
149 if (!wm_data.fb_cfg) {
150 GLXFBConfig *fb_configs = NULL;
151 int num_fb_configs = 0;
153 fb_configs = glXChooseFBConfig(wm_data.dpy, wm_data.screen, attr_list, &num_fb_configs);
154 if (!fb_configs || !num_fb_configs) {
155 fb_configs = glXGetFBConfigs(wm_data.dpy, wm_data.screen, &num_fb_configs);
156 if (!fb_configs || !num_fb_configs)
157 return 1;
159 wm_data.fb_cfg = fb_configs[0];
160 glXGetFBConfigAttrib(wm_data.dpy, wm_data.fb_cfg, GLX_DOUBLEBUFFER, &wm_data.dblbuff);
161 wm_data.vi = glXGetVisualFromFBConfig(wm_data.dpy, wm_data.fb_cfg);
163 glXCreateContextAttribsARB = (glXCreateContextAttribsARBProc)glXGetProcAddressARB( (const GLubyte *) "glXCreateContextAttribsARB" );
165 if (!glXCreateContextAttribsARB) {
166 wm_data.ctx = glXCreateNewContext(wm_data.dpy, wm_data.fb_cfg, GLX_RGBA_TYPE, 0, True);
167 }else{
168 int context_attribs[] = {
169 GLX_CONTEXT_MAJOR_VERSION_ARB, 3,
170 GLX_CONTEXT_MINOR_VERSION_ARB, 3,
171 GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
172 GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB,
173 None
175 wm_data.ctx = glXCreateContextAttribsARB(wm_data.dpy, wm_data.fb_cfg, 0, True, context_attribs);
177 if (!wm_data.ctx)
178 return 1;
181 /* set best mode to current */
182 wm_data.mode = 0;
183 /* look for mode with requested resolution */
184 for (i=0; i<wm_data.mode_count; i++) {
185 if ((wm_data.modes[i]->hdisplay == wm_data.size.width) && (wm_data.modes[i]->vdisplay == wm_data.size.height)) {
186 wm_data.mode = i;
187 break;
191 /* create a color map */
192 cmap = XCreateColormap(
193 wm_data.dpy,
194 RootWindow(wm_data.dpy, wm_data.vi->screen),
195 wm_data.vi->visual,
196 AllocNone
199 wm_data.attr.colormap = cmap;
200 wm_data.attr.border_pixel = 0;
201 wm_data.attr.event_mask = ExposureMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | StructureNotifyMask;
203 if (wm_data.fullscreen) {
204 XF86VidModeSwitchToMode(wm_data.dpy, wm_data.screen, wm_data.modes[wm_data.mode]);
205 XF86VidModeSetViewPort(wm_data.dpy, wm_data.screen, 0, 0);
207 /* create a fullscreen window */
208 wm_data.attr.override_redirect = True;
209 wm_data.win = XCreateWindow(
210 wm_data.dpy,
211 RootWindow(wm_data.dpy, wm_data.vi->screen),
214 wm_data.modes[wm_data.mode]->hdisplay,
215 wm_data.modes[wm_data.mode]->vdisplay,
217 wm_data.vi->depth,
218 InputOutput,
219 wm_data.vi->visual,
220 CWBorderPixel | CWColormap | CWEventMask | CWOverrideRedirect,
221 &wm_data.attr
223 }else{
224 /* create a window */
225 wm_data.win = XCreateWindow(
226 wm_data.dpy,
227 RootWindow(wm_data.dpy, wm_data.vi->screen),
230 wm_data.size.width,
231 wm_data.size.height,
233 wm_data.vi->depth,
234 InputOutput,
235 wm_data.vi->visual,
236 CWBorderPixel | CWColormap | CWEventMask,
237 &wm_data.attr
240 /* handle wm_delete_events */
241 wmDelete = XInternAtom(wm_data.dpy, "WM_DELETE_WINDOW", True);
242 XSetWMProtocols(wm_data.dpy, wm_data.win, &wmDelete, 1);
245 wm_data.im = XOpenIM(wm_data.dpy, NULL, NULL, NULL);
246 if (wm_data.im == NULL)
247 return 1;
249 app_supported_styles = XIMPreeditNone | XIMPreeditNothing | XIMPreeditArea;
250 app_supported_styles |= XIMStatusNone | XIMStatusNothing | XIMStatusArea;
252 /* figure out which styles the IM can support */
253 XGetIMValues(wm_data.im, XNQueryInputStyle, &im_supported_styles, NULL);
255 /* find the best IM */
256 wm_data.style = 0;
257 for(i=0; i < im_supported_styles->count_styles; i++) {
258 style = im_supported_styles->supported_styles[i];
259 if ((style & app_supported_styles) == style) /* if we can handle it */
260 wm_data.style = style_shoose_best(style, wm_data.style);
264 /* ... or not */
265 if (wm_data.style == 0)
266 return 1;
268 /* create an IC from the IM, this is needed for unicode support */
269 list = XVaCreateNestedList(0,NULL);
270 wm_data.ic = XCreateIC(
271 wm_data.im,
272 XNInputStyle, wm_data.style,
273 XNClientWindow, wm_data.win,
274 NULL
276 XFree(list);
278 if (wm_data.ic == NULL)
279 return 1;
281 XGetICValues(wm_data.ic, XNFilterEvents, &im_event_mask, NULL);
282 XSelectInput(wm_data.dpy, wm_data.win, ExposureMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | StructureNotifyMask | im_event_mask);
284 XSetICFocus(wm_data.ic);
286 if (wm_data.fullscreen) {
287 XWarpPointer(wm_data.dpy, None, wm_data.win, 0, 0, 0, 0, 0, 0);
288 XMapRaised(wm_data.dpy, wm_data.win);
289 XGrabKeyboard(wm_data.dpy, wm_data.win, True, GrabModeAsync, GrabModeAsync, CurrentTime);
290 XGrabPointer(wm_data.dpy, wm_data.win, True, ButtonPressMask, GrabModeAsync, GrabModeAsync, wm_data.win, None, CurrentTime);
291 }else{
292 XSizeHints *hints;
293 XMapRaised(wm_data.dpy, wm_data.win);
295 /* set window title */
296 XStoreName(wm_data.dpy, wm_data.win, wm_data.title);
298 /* prevent window resizing */
299 hints = XAllocSizeHints();
300 hints->min_width = hints->max_width = wm_data.size.width;
301 hints->min_height = hints->max_height = wm_data.size.height;
302 hints->flags = PMinSize | PMaxSize;
303 XSetWMNormalHints(wm_data.dpy, wm_data.win, hints);
304 XFree(hints);
307 wm_data.glxwin = glXCreateWindow(wm_data.dpy, wm_data.fb_cfg, wm_data.win, NULL);
309 /* make OpenGL context current */
310 if (!glXMakeContextCurrent(wm_data.dpy, wm_data.glxwin, wm_data.glxwin, wm_data.ctx)) {
311 glXDestroyContext(wm_data.dpy, wm_data.ctx);
312 return 1;
315 XGetGeometry(
316 wm_data.dpy,
317 wm_data.win,
318 &winDummy,
323 &borderDummy,
327 wm_data.size.width = (int)w;
328 wm_data.size.height = (int)h;
330 if (glXIsDirect(wm_data.dpy, wm_data.ctx)) {
331 int major;
332 int minor;
333 char* db = "";
334 glGetIntegerv(GL_MAJOR_VERSION, &major);
335 glGetIntegerv(GL_MINOR_VERSION, &minor);
337 if (wm_data.dblbuff)
338 db = "Double Buffered ";
340 vlprintf(CN_INFO "%sDirect Rendering: OpenGL %d.%d",db,major,minor);
343 /* MUST be done, else rendering gets messed up */
344 render3d_set_projection_matrix();
346 return 0;
349 /* resize the screen, fullscreen or windowed */
350 int wm_resize()
352 if (!wm_data.isinit)
353 return 0;
355 XResizeWindow(wm_data.dpy, wm_data.win, wm_data.size.width, wm_data.size.height);
357 /* MUST be done after a resize, else rendering gets messed up */
358 render3d_set_projection_matrix();
360 return 0;
363 /* flush graphics through and flip buffers */
364 int wm_update()
366 glFlush();
367 /* update the screen */
368 if (wm_data.dblbuff)
369 glXSwapBuffers(wm_data.dpy, wm_data.glxwin);
370 return 0;
373 /* destroy the current window */
374 void wm_destroy()
376 if (wm_data.ctx) {
377 glXMakeContextCurrent(wm_data.dpy, None, None, NULL);
379 glXDestroyContext(wm_data.dpy, wm_data.ctx);
380 wm_data.ctx = NULL;
381 glXDestroyWindow(wm_data.dpy,wm_data.glxwin);
383 /* switch back to original desktop resolution if we were in fs */
384 if (wm_data.fullscreen) {
385 XF86VidModeSwitchToMode(wm_data.dpy, wm_data.screen, &wm_data.deskMode);
386 XF86VidModeSetViewPort(wm_data.dpy, wm_data.screen, 0, 0);
391 /* set fullscreen on/off */
392 void wm_toggle_fullscreen(int fs)
394 if (fs == wm_data.fullscreen)
395 return;
397 if (!wm_data.isinit) {
398 wm_data.fullscreen = fs;
399 return;
401 if (wm_data.ctx) {
402 glXMakeContextCurrent(wm_data.dpy, None, None, NULL);
403 glXDestroyWindow(wm_data.dpy,wm_data.glxwin);
405 /* switch back to original desktop resolution if we were in fs */
406 if (wm_data.fullscreen) {
407 XF86VidModeSwitchToMode(wm_data.dpy, wm_data.screen, &wm_data.deskMode);
408 XF86VidModeSetViewPort(wm_data.dpy, wm_data.screen, 0, 0);
410 wm_data.fullscreen = fs;
411 /* TODO: this isn't working in glX */
412 wm_create();
415 /* use file as a cursor texture */
416 void wm_cursor(char* file, int width, int height, int offset_x, int offset_y)
418 Cursor invisibleCursor;
419 Pixmap bitmapNoData;
420 XColor black;
421 static char noData[] = { 0,0,0,0,0,0,0,0 };
423 if (!file) {
424 if (!wm_data.cursor.mat)
425 return;
427 wm_data.cursor.mat = NULL;
428 XUndefineCursor(wm_data.dpy, wm_data.win);
430 return;
433 wm_data.cursor.mat = mat_from_image("texture",file);
434 wm_data.cursor.w = width;
435 wm_data.cursor.h = height;
436 wm_data.cursor.x = offset_x;
437 wm_data.cursor.y = offset_y;
439 if (!wm_data.cursor.mat)
440 return;
442 black.red = 0;
443 black.green = 0;
444 black.blue = 0;
446 bitmapNoData = XCreateBitmapFromData(wm_data.dpy, wm_data.win, noData, 8, 8);
447 invisibleCursor = XCreatePixmapCursor(wm_data.dpy, bitmapNoData, bitmapNoData, &black, &black, 0, 0);
448 XDefineCursor(wm_data.dpy, wm_data.win, invisibleCursor);
449 XFreeCursor(wm_data.dpy, invisibleCursor);
452 /* grab the mouse */
453 void wm_grab()
455 Cursor invisibleCursor;
456 Pixmap bitmapNoData;
457 XColor black;
458 static char noData[] = { 0,0,0,0,0,0,0,0 };
460 if (events_get_mousegrab())
461 return;
463 black.red = 0;
464 black.green = 0;
465 black.blue = 0;
467 bitmapNoData = XCreateBitmapFromData(wm_data.dpy, wm_data.win, noData, 8, 8);
468 invisibleCursor = XCreatePixmapCursor(wm_data.dpy, bitmapNoData, bitmapNoData, &black, &black, 0, 0);
469 XDefineCursor(wm_data.dpy, wm_data.win, invisibleCursor);
470 XFreeCursor(wm_data.dpy, invisibleCursor);
472 if (!wm_data.fullscreen)
473 XGrabPointer(wm_data.dpy, wm_data.win, True, ButtonPressMask, GrabModeAsync, GrabModeAsync, wm_data.win, None, CurrentTime);
475 events_set_mousegrab(1);
478 /* stop grabbing the mouse */
479 void wm_ungrab()
481 if (!events_get_mousegrab())
482 return;
484 XUndefineCursor(wm_data.dpy, wm_data.win);
485 if (!wm_data.fullscreen)
486 XUngrabPointer(wm_data.dpy, CurrentTime);
488 events_set_mousegrab(0);
491 /* set the window title */
492 void wm_title(char* title)
494 if (title) {
495 if (wm_data.title)
496 free(wm_data.title);
497 wm_data.title = strdup(title);
499 if (!wm_data.isinit)
500 return;
502 XStoreName(wm_data.dpy, wm_data.win, wm_data.title);
505 #endif