1 #include <X11/Xlib.h>
2 #include <X11/Xutil.h>
3 #include <X11/Xatom.h>
4 
5 #include "allegro5/allegro.h"
6 #include "allegro5/allegro_x.h"
7 #include "allegro5/internal/aintern_x.h"
8 #include "allegro5/internal/aintern_xdisplay.h"
9 #include "allegro5/internal/aintern_xsystem.h"
10 #include "allegro5/internal/aintern_xwindow.h"
11 
12 #ifdef ALLEGRO_RASPBERRYPI
13 #include "allegro5/internal/aintern_raspberrypi.h"
14 #define ALLEGRO_SYSTEM_XGLX ALLEGRO_SYSTEM_RASPBERRYPI
15 #define ALLEGRO_DISPLAY_XGLX ALLEGRO_DISPLAY_RASPBERRYPI
16 #endif
17 
18 ALLEGRO_DEBUG_CHANNEL("xwindow")
19 
20 #define X11_ATOM(x)  XInternAtom(x11, #x, False);
21 
_al_xwin_set_size_hints(ALLEGRO_DISPLAY * d,int x_off,int y_off)22 void _al_xwin_set_size_hints(ALLEGRO_DISPLAY *d, int x_off, int y_off)
23 {
24    ALLEGRO_SYSTEM_XGLX *system = (void *)al_get_system_driver();
25    ALLEGRO_DISPLAY_XGLX *glx = (void *)d;
26    XSizeHints *sizehints;
27    XWMHints *wmhints;
28    XClassHint *classhints;
29    int w, h;
30 
31    sizehints = XAllocSizeHints();
32    sizehints->flags = 0;
33 
34 #ifdef ALLEGRO_RASPBERRYPI
35    int x, y;
36    _al_raspberrypi_get_screen_info(&x, &y, &w, &h);
37 #else
38    w = d->w;
39    h = d->h;
40 #endif
41 
42    /* Do not force the size of the window on resizeable or fullscreen windows */
43    /* on fullscreen windows, it confuses most X Window Managers */
44    if (!(d->flags & ALLEGRO_RESIZABLE) && !(d->flags & ALLEGRO_FULLSCREEN)) {
45       sizehints->flags |= PMinSize | PMaxSize | PBaseSize;
46       sizehints->min_width  = sizehints->max_width  = sizehints->base_width  = w;
47       sizehints->min_height = sizehints->max_height = sizehints->base_height = h;
48    }
49 
50    /* Constrain the window if needed. */
51    if (d->use_constraints && (d->flags & ALLEGRO_RESIZABLE) &&
52       (d->min_w > 0 || d->min_h > 0 || d->max_w > 0 || d->max_h > 0))
53    {
54       sizehints->flags |= PMinSize | PMaxSize | PBaseSize;
55       sizehints->min_width = (d->min_w > 0) ? d->min_w : 0;
56       sizehints->min_height = (d->min_h > 0) ? d->min_h : 0;
57       sizehints->max_width = (d->max_w > 0) ? d->max_w : INT_MAX;
58       sizehints->max_height = (d->max_h > 0) ? d->max_h : INT_MAX;
59       sizehints->base_width  = w;
60       sizehints->base_height = h;
61    }
62 
63    // Tell WMs to respect our chosen position, otherwise the x_off/y_off
64    // positions passed to XCreateWindow will be ignored by most WMs.
65    if (x_off != INT_MAX && y_off != INT_MAX) {
66       ALLEGRO_DEBUG("Force window position to %d, %d.\n", x_off, y_off);
67       sizehints->flags |= PPosition;
68       sizehints->x = x_off;
69       sizehints->y = y_off;
70    }
71 
72    if (d->flags & ALLEGRO_FULLSCREEN) {
73       /* kwin will improperly layer a panel over our window on a second display without this.
74        * some other Size flags may cause glitches with various WMs, but this seems to be ok
75        * with metacity and kwin. As noted in xdpy_create_display, compiz is just broken.
76        */
77       sizehints->flags |= PBaseSize;
78       sizehints->base_width  = w;
79       sizehints->base_height = h;
80    }
81 
82    /* Setup the input hints so we get keyboard input */
83    wmhints = XAllocWMHints();
84    wmhints->input = True;
85    wmhints->flags = InputHint;
86 
87    ALLEGRO_PATH *exepath = al_get_standard_path(ALLEGRO_EXENAME_PATH);
88 
89    /* Setup the class hints so we can get an icon (AfterStep)
90     * We must use the executable filename here.
91     */
92    classhints = XAllocClassHint();
93    classhints->res_name = strdup(al_get_path_basename(exepath));
94    classhints->res_class = strdup(al_get_path_basename(exepath));
95 
96    /* Set the size, input and class hints, and define WM_CLIENT_MACHINE and WM_LOCALE_NAME */
97    XSetWMProperties(system->x11display, glx->window, NULL, NULL, NULL, 0,
98                     sizehints, wmhints, classhints);
99 
100    free(classhints->res_name);
101    free(classhints->res_class);
102    XFree(sizehints);
103    XFree(wmhints);
104    XFree(classhints);
105 
106    al_destroy_path(exepath);
107 }
108 
109 
_al_xwin_reset_size_hints(ALLEGRO_DISPLAY * d)110 void _al_xwin_reset_size_hints(ALLEGRO_DISPLAY *d)
111 {
112    ALLEGRO_SYSTEM_XGLX *system = (void *)al_get_system_driver();
113    ALLEGRO_DISPLAY_XGLX *glx = (void *)d;
114    XSizeHints *hints;
115 
116    hints  = XAllocSizeHints();
117    hints->flags = PMinSize | PMaxSize;
118    hints->min_width  = 0;
119    hints->min_height = 0;
120    // FIXME: Is there a way to remove/reset max dimensions?
121    hints->max_width  = 32768;
122    hints->max_height = 32768;
123    XSetWMNormalHints(system->x11display, glx->window, hints);
124 
125    XFree(hints);
126 }
127 
128 
129 /* Note: The system mutex must be locked (exactly once) before
130  * calling this as we call _al_display_xglx_await_resize.
131  */
_al_xwin_set_fullscreen_window(ALLEGRO_DISPLAY * display,int value)132 void _al_xwin_set_fullscreen_window(ALLEGRO_DISPLAY *display, int value)
133 {
134    ALLEGRO_SYSTEM_XGLX *system = (void *)al_get_system_driver();
135    ALLEGRO_DISPLAY_XGLX *glx = (ALLEGRO_DISPLAY_XGLX *)display;
136    Display *x11 = system->x11display;
137 #ifndef ALLEGRO_RASPBERRYPI
138    int old_resize_count = glx->resize_count;
139 #endif
140 
141    ALLEGRO_DEBUG("Toggling _NET_WM_STATE_FULLSCREEN hint: %d\n", value);
142 
143    XEvent xev;
144    xev.xclient.type = ClientMessage;
145    xev.xclient.serial = 0;
146    xev.xclient.send_event = True;
147    xev.xclient.message_type = X11_ATOM(_NET_WM_STATE);
148    xev.xclient.window = glx->window;
149    xev.xclient.format = 32;
150 
151    // Note: It seems 0 is not reliable except when mapping a window -
152    // 2 is all we need though.
153    xev.xclient.data.l[0] = value; /* 0 = off, 1 = on, 2 = toggle */
154 
155    xev.xclient.data.l[1] = X11_ATOM(_NET_WM_STATE_FULLSCREEN);
156    xev.xclient.data.l[2] = 0;
157    xev.xclient.data.l[3] = 0;
158    xev.xclient.data.l[4] = 1;
159 
160    XSendEvent(
161       x11,
162 #if !defined ALLEGRO_RASPBERRYPI
163       RootWindowOfScreen(ScreenOfDisplay(x11, glx->xscreen)),
164 #else
165       RootWindowOfScreen(ScreenOfDisplay(x11, DefaultScreen(x11))),
166 #endif
167       False,
168       SubstructureRedirectMask | SubstructureNotifyMask, &xev);
169 
170 #if !defined ALLEGRO_RASPBERRYPI
171    if (value == 2) {
172       /* Only wait for a resize if toggling. */
173       _al_display_xglx_await_resize(display, old_resize_count, true);
174    }
175 #endif
176 }
177 
178 
_al_xwin_set_above(ALLEGRO_DISPLAY * display,int value)179 void _al_xwin_set_above(ALLEGRO_DISPLAY *display, int value)
180 {
181    ALLEGRO_SYSTEM_XGLX *system = (void *)al_get_system_driver();
182    ALLEGRO_DISPLAY_XGLX *glx = (ALLEGRO_DISPLAY_XGLX *)display;
183    Display *x11 = system->x11display;
184 
185    ALLEGRO_DEBUG("Toggling _NET_WM_STATE_ABOVE hint: %d\n", value);
186 
187    XEvent xev;
188    xev.xclient.type = ClientMessage;
189    xev.xclient.serial = 0;
190    xev.xclient.send_event = True;
191    xev.xclient.message_type = X11_ATOM(_NET_WM_STATE);
192    xev.xclient.window = glx->window;
193    xev.xclient.format = 32;
194 
195    // Note: It seems 0 is not reliable except when mapping a window -
196    // 2 is all we need though.
197    xev.xclient.data.l[0] = value; /* 0 = off, 1 = on, 2 = toggle */
198 
199    xev.xclient.data.l[1] = X11_ATOM(_NET_WM_STATE_ABOVE);
200    xev.xclient.data.l[2] = 0;
201    xev.xclient.data.l[3] = 0;
202    xev.xclient.data.l[4] = 1;
203 
204    XSendEvent(x11, DefaultRootWindow(x11), False,
205       SubstructureRedirectMask | SubstructureNotifyMask, &xev);
206 }
207 
208 
_al_xwin_set_frame(ALLEGRO_DISPLAY * display,bool frame_on)209 void _al_xwin_set_frame(ALLEGRO_DISPLAY *display, bool frame_on)
210 {
211    ALLEGRO_SYSTEM_XGLX *system = (void *)al_get_system_driver();
212    ALLEGRO_DISPLAY_XGLX *glx = (ALLEGRO_DISPLAY_XGLX *)display;
213    Display *x11 = system->x11display;
214    Atom hints;
215 
216    _al_mutex_lock(&system->lock);
217 
218 #if 1
219    /* This code is taken from the GDK sources. So it works perfectly in Gnome,
220     * no idea if it will work anywhere else. X11 documentation itself only
221     * describes a way how to make the window completely unmanaged, but that
222     * would also require special care in the event handler.
223     */
224    hints = XInternAtom(x11, "_MOTIF_WM_HINTS", True);
225    if (hints) {
226       struct {
227          unsigned long flags;
228          unsigned long functions;
229          unsigned long decorations;
230          long input_mode;
231          unsigned long status;
232       } motif = {2, 0, frame_on, 0, 0};
233       XChangeProperty(x11, glx->window, hints, hints, 32, PropModeReplace,
234          (void *)&motif, sizeof motif / 4);
235 
236       if (frame_on)
237          display->flags &= ~ALLEGRO_FRAMELESS;
238       else
239          display->flags |= ALLEGRO_FRAMELESS;
240    }
241 #endif
242 
243    _al_mutex_unlock(&system->lock);
244 }
245 
246 
247 /* Helper to set a window icon.  We use the _NET_WM_ICON property which is
248  * supported by modern window managers.
249  *
250  * The old method is XSetWMHints but the (antiquated) ICCCM talks about 1-bit
251  * pixmaps.  For colour icons, perhaps you're supposed use the icon_window,
252  * and draw the window yourself?
253  */
xdpy_set_icon_inner(Display * x11display,Window window,ALLEGRO_BITMAP * bitmap,int prop_mode)254 static bool xdpy_set_icon_inner(Display *x11display, Window window,
255    ALLEGRO_BITMAP *bitmap, int prop_mode)
256 {
257    int w, h;
258    int data_size;
259    unsigned long *data; /* Yes, unsigned long, even on 64-bit platforms! */
260    ALLEGRO_LOCKED_REGION *lr;
261    bool ret;
262 
263    w = al_get_bitmap_width(bitmap);
264    h = al_get_bitmap_height(bitmap);
265    data_size = 2 + w * h;
266    data = al_malloc(data_size * sizeof(data[0]));
267    if (!data)
268       return false;
269 
270    lr = al_lock_bitmap(bitmap, ALLEGRO_PIXEL_FORMAT_ANY_WITH_ALPHA,
271       ALLEGRO_LOCK_READONLY);
272    if (lr) {
273       int x, y;
274       ALLEGRO_COLOR c;
275       unsigned char r, g, b, a;
276       Atom _NET_WM_ICON;
277 
278       data[0] = w;
279       data[1] = h;
280       for (y = 0; y < h; y++) {
281          for (x = 0; x < w; x++) {
282             c = al_get_pixel(bitmap, x, y);
283             al_unmap_rgba(c, &r, &g, &b, &a);
284             data[2 + y*w + x] = ((unsigned long)a << 24) | ((unsigned long)r << 16) |
285                                 ((unsigned long)g << 8) | (unsigned long)b;
286          }
287       }
288 
289       _NET_WM_ICON = XInternAtom(x11display, "_NET_WM_ICON", False);
290       XChangeProperty(x11display, window, _NET_WM_ICON, XA_CARDINAL, 32,
291          prop_mode, (unsigned char *)data, data_size);
292 
293       al_unlock_bitmap(bitmap);
294       ret = true;
295    }
296    else {
297       ret = false;
298    }
299 
300    al_free(data);
301 
302    return ret;
303 }
304 
305 
_al_xwin_set_icons(ALLEGRO_DISPLAY * d,int num_icons,ALLEGRO_BITMAP * bitmaps[])306 void _al_xwin_set_icons(ALLEGRO_DISPLAY *d,
307    int num_icons, ALLEGRO_BITMAP *bitmaps[])
308 {
309    ALLEGRO_SYSTEM_XGLX *system = (ALLEGRO_SYSTEM_XGLX *)al_get_system_driver();
310    ALLEGRO_DISPLAY_XGLX *glx = (ALLEGRO_DISPLAY_XGLX *)d;
311    int prop_mode = PropModeReplace;
312    int i;
313 
314    _al_mutex_lock(&system->lock);
315 
316    for (i = 0; i < num_icons; i++) {
317       if (xdpy_set_icon_inner(system->x11display, glx->window, bitmaps[i],
318             prop_mode)) {
319          prop_mode = PropModeAppend;
320       }
321    }
322 
323    _al_mutex_unlock(&system->lock);
324 }
325 
326 
_al_xwin_maximize(ALLEGRO_DISPLAY * display,bool maximized)327 void _al_xwin_maximize(ALLEGRO_DISPLAY *display, bool maximized)
328 {
329 #ifndef ALLEGRO_RASPBERRYPI
330    if (!!(display->flags & ALLEGRO_MAXIMIZED) == maximized)
331       return;
332    ALLEGRO_SYSTEM_XGLX *system = (void *)al_get_system_driver();
333    ALLEGRO_DISPLAY_XGLX *glx = (ALLEGRO_DISPLAY_XGLX *)display;
334    Display *x11 = system->x11display;
335    int old_resize_count = glx->resize_count;
336 
337    XEvent xev;
338    xev.xclient.type = ClientMessage;
339    xev.xclient.serial = 0;
340    xev.xclient.send_event = True;
341    xev.xclient.message_type = X11_ATOM(_NET_WM_STATE);
342    xev.xclient.window = glx->window;
343    xev.xclient.format = 32;
344 
345    xev.xclient.data.l[0] = maximized ? 1 : 0;
346    xev.xclient.data.l[1] = X11_ATOM(_NET_WM_STATE_MAXIMIZED_HORZ);
347    xev.xclient.data.l[2] = X11_ATOM(_NET_WM_STATE_MAXIMIZED_VERT);
348    xev.xclient.data.l[3] = 0;
349 
350    XSendEvent(
351       x11,
352       RootWindowOfScreen(ScreenOfDisplay(x11, glx->xscreen)),
353       False,
354       SubstructureRedirectMask | SubstructureNotifyMask, &xev);
355 
356    _al_display_xglx_await_resize(display, old_resize_count, true);
357 #endif
358 }
359 
360 
_al_xwin_check_maximized(ALLEGRO_DISPLAY * display)361 void _al_xwin_check_maximized(ALLEGRO_DISPLAY *display)
362 {
363 #ifndef ALLEGRO_RASPBERRYPI
364    ALLEGRO_SYSTEM_XGLX *system = (void *)al_get_system_driver();
365    ALLEGRO_DISPLAY_XGLX *glx = (ALLEGRO_DISPLAY_XGLX *)display;
366    Display *x11 = system->x11display;
367    Atom type;
368    Atom horz = X11_ATOM(_NET_WM_STATE_MAXIMIZED_HORZ);
369    Atom vert = X11_ATOM(_NET_WM_STATE_MAXIMIZED_VERT);
370    Atom property = X11_ATOM(_NET_WM_STATE);
371    int format;
372    int maximized = 0;
373    unsigned long n, remaining, i, *p32;
374    unsigned char *p8 = NULL;
375    if (XGetWindowProperty(x11, glx->window, property, 0, INT_MAX,
376       False, AnyPropertyType, &type, &format, &n, &remaining, &p8)
377          != Success) {
378       return;
379    }
380    p32 = (unsigned long *)p8;
381    for (i = 0; i < n; i++) {
382       if (p32[i] == horz)
383          maximized |= 1;
384       if (p32[i] == vert)
385          maximized |= 2;
386    }
387    XFree(p8);
388    display->flags &= ~ALLEGRO_MAXIMIZED;
389    if (maximized == 3)
390       display->flags |= ALLEGRO_MAXIMIZED;
391 #endif
392 }
393 
394 
395 /* Function: al_get_x_window_id
396  */
al_get_x_window_id(ALLEGRO_DISPLAY * display)397 XID al_get_x_window_id(ALLEGRO_DISPLAY *display)
398 {
399    ASSERT(display != NULL);
400    return ((ALLEGRO_DISPLAY_XGLX*)display)->window;
401 }
402 
403 /* vim: set sts=3 sw=3 et: */
404