1 #include <X11/Xatom.h>
2 #include <gdk/gdkx.h>
3 #include <gdk/gdk.h>
4 #include <string.h>
5 #include <glib.h>
6 #include <glib-object.h>
7 #include "wm-common.h"
8 
9 typedef struct _WMCallbackData
10 {
11   GFunc func;
12   gpointer data;
13 } WMCallbackData;
14 
15 /* Our WM Window */
16 static Window wm_window = None;
17 
18 /*
19  * These push/pop implementations are based on the GDK versions, except that they
20  * use only non-deprecated API.
21  */
22 
23 static GPtrArray*
push_error_traps(void)24 push_error_traps (void)
25 {
26   GdkDisplayManager *manager;
27   g_autoptr(GPtrArray) trapped_displays = NULL;
28   g_autoptr(GSList) displays = NULL;
29   GSList *l;
30 
31   manager = gdk_display_manager_get ();
32   displays = gdk_display_manager_list_displays (manager);
33   trapped_displays = g_ptr_array_new ();
34 
35   for (l = displays; l != NULL; l = l->next)
36     {
37       GdkDisplay *display = l->data;
38 
39 #ifdef GDK_WINDOWING_X11
40       if (GDK_IS_X11_DISPLAY (display))
41         {
42           gdk_x11_display_error_trap_push (display);
43           g_ptr_array_add (trapped_displays, display);
44         }
45 #endif
46     }
47 
48   return g_steal_pointer (&trapped_displays);
49 }
50 
51 static gint
pop_error_traps(GPtrArray * displays)52 pop_error_traps (GPtrArray *displays)
53 {
54   guint i;
55   gint result;
56 
57   result = 0;
58 
59   for (i = 0; displays && i < displays->len; i++)
60     {
61       GdkDisplay *display;
62       gint code = 0;
63 
64       display = g_ptr_array_index (displays, i);
65 
66 #ifdef GDK_WINDOWING_X11
67       code = gdk_x11_display_error_trap_pop (display);
68 #endif
69 
70       if (code != 0)
71         result = code;
72     }
73 
74   return result;
75 }
76 
77 static char *
wm_common_get_window_manager_property(Atom atom)78 wm_common_get_window_manager_property (Atom atom)
79 {
80   g_autoptr(GPtrArray) trapped_displays = NULL;
81   Atom utf8_string, type;
82   int result;
83   char *retval;
84   int format;
85   gulong nitems;
86   gulong bytes_after;
87   gchar *val;
88 
89   if (wm_window == None)
90     return NULL;
91 
92   utf8_string = XInternAtom (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), "UTF8_STRING", False);
93 
94   trapped_displays = push_error_traps ();
95 
96   val = NULL;
97   result = XGetWindowProperty (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
98                                wm_window,
99                                atom,
100                                0, G_MAXLONG,
101                                False, utf8_string,
102                                &type, &format, &nitems,
103                                &bytes_after, (guchar **) &val);
104 
105   if (pop_error_traps (trapped_displays) ||
106       result != Success ||
107       type != utf8_string ||
108       format != 8 ||
109       nitems == 0 ||
110       !g_utf8_validate (val, nitems, NULL))
111     {
112       retval = NULL;
113     }
114   else
115     {
116       retval = g_strndup (val, nitems);
117     }
118 
119   g_clear_pointer (&val, XFree);
120 
121   return retval;
122 }
123 static gchar*
wm_common_get_current_window_manager(void)124 wm_common_get_current_window_manager (void)
125 {
126   Atom atom = XInternAtom (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), "_NET_WM_NAME", False);
127   char *result;
128 
129   result = wm_common_get_window_manager_property (atom);
130   if (result)
131     return result;
132   else
133     return g_strdup (WM_COMMON_UNKNOWN);
134 }
135 
136 GStrv
wm_common_get_current_keybindings(void)137 wm_common_get_current_keybindings (void)
138 {
139   g_autofree gchar* keybindings = NULL;
140   g_auto(GStrv) results = NULL;
141   Atom keybindings_atom;
142 
143   keybindings_atom = XInternAtom (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), "_GNOME_WM_KEYBINDINGS", False);
144   keybindings = wm_common_get_window_manager_property (keybindings_atom);
145 
146   if (keybindings)
147     {
148       GStrv p;
149 
150       results = g_strsplit (keybindings, ",", -1);
151 
152       for (p = results; p && *p; p++)
153         g_strstrip (*p);
154     }
155   else
156     {
157       g_autofree gchar *wm_name = NULL;
158       Atom wm_atom;
159       gchar *to_copy[2] = { NULL, NULL };
160 
161       wm_atom = XInternAtom (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), "_NET_WM_NAME", False);
162       wm_name = wm_common_get_window_manager_property (wm_atom);
163 
164       to_copy[0] = wm_name ? wm_name : WM_COMMON_UNKNOWN;
165 
166       results = g_strdupv (to_copy);
167     }
168 
169   return g_steal_pointer (&results);
170 }
171 
172 static void
update_wm_window(void)173 update_wm_window (void)
174 {
175   g_autoptr(GPtrArray) trapped_displays = NULL;
176   Window *xwindow;
177   Atom type;
178   gint format;
179   gulong nitems;
180   gulong bytes_after;
181 
182   XGetWindowProperty (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), GDK_ROOT_WINDOW (),
183                       XInternAtom (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), "_NET_SUPPORTING_WM_CHECK", False),
184                       0, G_MAXLONG, False, XA_WINDOW, &type, &format,
185                       &nitems, &bytes_after, (guchar **) &xwindow);
186 
187   if (type != XA_WINDOW)
188     {
189       wm_window = None;
190      return;
191     }
192 
193   trapped_displays = push_error_traps ();
194 
195   XSelectInput (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), *xwindow, StructureNotifyMask | PropertyChangeMask);
196   XSync (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), False);
197 
198   if (pop_error_traps (trapped_displays))
199     {
200        XFree (xwindow);
201        wm_window = None;
202        return;
203     }
204 
205   wm_window = *xwindow;
206   XFree (xwindow);
207 }
208 
209 static GdkFilterReturn
wm_window_event_filter(GdkXEvent * xev,GdkEvent * event,gpointer data)210 wm_window_event_filter (GdkXEvent *xev,
211                         GdkEvent  *event,
212                         gpointer   data)
213 {
214   WMCallbackData *ncb_data = (WMCallbackData*) data;
215   XEvent *xevent = (XEvent *)xev;
216 
217   if ((xevent->type == DestroyNotify &&
218        wm_window != None && xevent->xany.window == wm_window) ||
219       (xevent->type == PropertyNotify &&
220        xevent->xany.window == GDK_ROOT_WINDOW () &&
221        xevent->xproperty.atom == (XInternAtom (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),  "_NET_SUPPORTING_WM_CHECK", False))) ||
222       (xevent->type == PropertyNotify &&
223        wm_window != None && xevent->xany.window == wm_window &&
224        xevent->xproperty.atom == (XInternAtom (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), "_NET_WM_NAME", False))))
225     {
226       update_wm_window ();
227       (* ncb_data->func) ((gpointer) wm_common_get_current_window_manager (), ncb_data->data);
228     }
229 
230   return GDK_FILTER_CONTINUE;
231 }
232 
233 gpointer
wm_common_register_window_manager_change(GFunc func,gpointer data)234 wm_common_register_window_manager_change (GFunc    func,
235                                           gpointer data)
236 {
237   WMCallbackData *ncb_data;
238 
239   ncb_data = g_new0 (WMCallbackData, 1);
240 
241   ncb_data->func = func;
242   ncb_data->data = data;
243 
244   gdk_window_add_filter (NULL, wm_window_event_filter, ncb_data);
245 
246   update_wm_window ();
247 
248   XSelectInput (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), GDK_ROOT_WINDOW (), PropertyChangeMask);
249   XSync (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), False);
250 
251   return ncb_data;
252 }
253 
254 void
wm_common_unregister_window_manager_change(gpointer id)255 wm_common_unregister_window_manager_change (gpointer id)
256 {
257   g_return_if_fail (id != NULL);
258 
259   gdk_window_remove_filter (NULL, wm_window_event_filter, id);
260   g_free (id);
261 }
262