1 /* -*- c-basic-offset: 8 -*-
2    rdesktop: A Remote Desktop Protocol client.
3 
4    Support functions for Extended Window Manager Hints,
5    http://www.freedesktop.org/wiki/Standards_2fwm_2dspec
6 
7    Copyright (C) Peter Astrand <astrand@cendio.se> 2005
8 
9    This program is free software; you can redistribute it and/or modify
10    it under the terms of the GNU General Public License as published by
11    the Free Software Foundation; either version 2 of the License, or
12    (at your option) any later version.
13 
14    This program is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17    GNU General Public License for more details.
18 
19    You should have received a copy of the GNU General Public License along
20    with this program; if not, write to the Free Software Foundation, Inc.,
21    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 */
23 
24 #include <X11/Xlib.h>
25 #include <X11/Xatom.h>
26 #include <X11/Xutil.h>
27 #include "rdesktop.h"
28 
29 #define _NET_WM_STATE_REMOVE        0	/* remove/unset property */
30 #define _NET_WM_STATE_ADD           1	/* add/set property */
31 #define _NET_WM_STATE_TOGGLE        2	/* toggle property  */
32 
33 /*
34    Get window property value (32 bit format)
35    Returns zero on success, -1 on error
36 */
37 static int
get_property_value(RDPCLIENT * This,Window wnd,char * propname,long max_length,unsigned long * nitems_return,unsigned char ** prop_return,int nowarn)38 get_property_value(RDPCLIENT * This, Window wnd, char *propname, long max_length,
39 		   unsigned long *nitems_return, unsigned char **prop_return, int nowarn)
40 {
41 	int result;
42 	Atom property;
43 	Atom actual_type_return;
44 	int actual_format_return;
45 	unsigned long bytes_after_return;
46 
47 	property = XInternAtom(This->display, propname, True);
48 	if (property == None)
49 	{
50 		fprintf(stderr, "Atom %s does not exist\n", propname);
51 		return (-1);
52 	}
53 
54 	result = XGetWindowProperty(This->display, wnd, property, 0,	/* long_offset */
55 				    max_length,	/* long_length */
56 				    False,	/* delete */
57 				    AnyPropertyType,	/* req_type */
58 				    &actual_type_return,
59 				    &actual_format_return,
60 				    nitems_return, &bytes_after_return, prop_return);
61 
62 	if (result != Success)
63 	{
64 		fprintf(stderr, "XGetWindowProperty failed\n");
65 		return (-1);
66 	}
67 
68 	if (actual_type_return == None || actual_format_return == 0)
69 	{
70 		if (!nowarn)
71 			fprintf(stderr, "Window is missing property %s\n", propname);
72 		return (-1);
73 	}
74 
75 	if (bytes_after_return)
76 	{
77 		fprintf(stderr, "%s is too big for me\n", propname);
78 		return (-1);
79 	}
80 
81 	if (actual_format_return != 32)
82 	{
83 		fprintf(stderr, "%s has bad format\n", propname);
84 		return (-1);
85 	}
86 
87 	return (0);
88 }
89 
90 /*
91    Get current desktop number
92    Returns -1 on error
93 */
94 static int
get_current_desktop(RDPCLIENT * This)95 get_current_desktop(RDPCLIENT * This)
96 {
97 	unsigned long nitems_return;
98 	unsigned char *prop_return;
99 	int current_desktop;
100 
101 	if (get_property_value
102 	    (This, DefaultRootWindow(This->display), "_NET_CURRENT_DESKTOP", 1, &nitems_return,
103 	     &prop_return, 0) < 0)
104 		return (-1);
105 
106 	if (nitems_return != 1)
107 	{
108 		fprintf(stderr, "_NET_CURRENT_DESKTOP has bad length\n");
109 		return (-1);
110 	}
111 
112 	current_desktop = *prop_return;
113 	XFree(prop_return);
114 	return current_desktop;
115 }
116 
117 /*
118   Get workarea geometry
119   Returns zero on success, -1 on error
120  */
121 
122 int
get_current_workarea(RDPCLIENT * This,uint32 * x,uint32 * y,uint32 * width,uint32 * height)123 get_current_workarea(RDPCLIENT * This, uint32 * x, uint32 * y, uint32 * width, uint32 * height)
124 {
125 	int current_desktop;
126 	unsigned long nitems_return;
127 	unsigned char *prop_return;
128 	uint32 *return_words;
129 	const uint32 net_workarea_x_offset = 0;
130 	const uint32 net_workarea_y_offset = 1;
131 	const uint32 net_workarea_width_offset = 2;
132 	const uint32 net_workarea_height_offset = 3;
133 	const uint32 max_prop_length = 32 * 4;	/* Max 32 desktops */
134 
135 	if (get_property_value
136 	    (This, DefaultRootWindow(This->display), "_NET_WORKAREA", max_prop_length, &nitems_return,
137 	     &prop_return, 0) < 0)
138 		return (-1);
139 
140 	if (nitems_return % 4)
141 	{
142 		fprintf(stderr, "_NET_WORKAREA has odd length\n");
143 		return (-1);
144 	}
145 
146 	current_desktop = get_current_desktop(This);
147 
148 	if (current_desktop < 0)
149 		return -1;
150 
151 	return_words = (uint32 *) prop_return;
152 
153 	*x = return_words[current_desktop * 4 + net_workarea_x_offset];
154 	*y = return_words[current_desktop * 4 + net_workarea_y_offset];
155 	*width = return_words[current_desktop * 4 + net_workarea_width_offset];
156 	*height = return_words[current_desktop * 4 + net_workarea_height_offset];
157 
158 	XFree(prop_return);
159 
160 	return (0);
161 
162 }
163 
164 
165 
166 void
ewmh_init(RDPCLIENT * This)167 ewmh_init(RDPCLIENT * This)
168 {
169 	/* FIXME: Use XInternAtoms */
170 	This->ewmhints.state_maximized_vert_atom =
171 		XInternAtom(This->display, "_NET_WM_STATE_MAXIMIZED_VERT", False);
172 	This->ewmhints.state_maximized_horz_atom =
173 		XInternAtom(This->display, "_NET_WM_STATE_MAXIMIZED_HORZ", False);
174 	This->ewmhints.state_hidden_atom = XInternAtom(This->display, "_NET_WM_STATE_HIDDEN", False);
175 	This->ewmhints.state_skip_taskbar_atom =
176 		XInternAtom(This->display, "_NET_WM_STATE_SKIP_TASKBAR", False);
177 	This->ewmhints.state_skip_pager_atom = XInternAtom(This->display, "_NET_WM_STATE_SKIP_PAGER", False);
178 	This->ewmhints.state_modal_atom = XInternAtom(This->display, "_NET_WM_STATE_MODAL", False);
179 	This->net_wm_state_atom = XInternAtom(This->display, "_NET_WM_STATE", False);
180 	This->net_wm_desktop_atom = XInternAtom(This->display, "_NET_WM_DESKTOP", False);
181 	This->ewmhints.name_atom = XInternAtom(This->display, "_NET_WM_NAME", False);
182 	This->ewmhints.utf8_string_atom = XInternAtom(This->display, "UTF8_STRING", False);
183 }
184 
185 
186 /*
187    Get the window state: normal/minimized/maximized.
188 */
189 #ifndef MAKE_PROTO
190 int
ewmh_get_window_state(RDPCLIENT * This,Window w)191 ewmh_get_window_state(RDPCLIENT * This, Window w)
192 {
193 	unsigned long nitems_return;
194 	unsigned char *prop_return;
195 	uint32 *return_words;
196 	unsigned long item;
197 	BOOL maximized_vert, maximized_horz, hidden;
198 
199 	maximized_vert = maximized_horz = hidden = False;
200 
201 	if (get_property_value(This, w, "_NET_WM_STATE", 64, &nitems_return, &prop_return, 0) < 0)
202 		return SEAMLESSRDP_NORMAL;
203 
204 	return_words = (uint32 *) prop_return;
205 
206 	for (item = 0; item < nitems_return; item++)
207 	{
208 		if (return_words[item] == This->ewmhints.state_maximized_vert_atom)
209 			maximized_vert = True;
210 		if (return_words[item] == This->ewmhints.state_maximized_horz_atom)
211 			maximized_horz = True;
212 		if (return_words[item] == This->ewmhints.state_hidden_atom)
213 			hidden = True;
214 	}
215 
216 	XFree(prop_return);
217 
218 	if (maximized_vert && maximized_horz)
219 		return SEAMLESSRDP_MAXIMIZED;
220 	else if (hidden)
221 		return SEAMLESSRDP_MINIMIZED;
222 	else
223 		return SEAMLESSRDP_NORMAL;
224 }
225 
226 static int
ewmh_modify_state(RDPCLIENT * This,Window wnd,int add,Atom atom1,Atom atom2)227 ewmh_modify_state(RDPCLIENT * This, Window wnd, int add, Atom atom1, Atom atom2)
228 {
229 	Status status;
230 	XEvent xevent;
231 
232 	int result;
233 	unsigned long nitems;
234 	unsigned char *props;
235 	uint32 state = WithdrawnState;
236 
237 	/* The spec states that the window manager must respect any
238 	   _NET_WM_STATE attributes on a withdrawn window. In order words, we
239 	   modify the attributes directly for withdrawn windows and ask the WM
240 	   to do it for active windows. */
241 	result = get_property_value(This, wnd, "WM_STATE", 64, &nitems, &props, 1);
242 	if ((result >= 0) && nitems)
243 	{
244 		state = *(uint32 *) props;
245 		XFree(props);
246 	}
247 
248 	if (state == WithdrawnState)
249 	{
250 		if (add)
251 		{
252 			Atom atoms[2];
253 
254 			atoms[0] = atom1;
255 			nitems = 1;
256 			if (atom2)
257 			{
258 				atoms[1] = atom2;
259 				nitems = 2;
260 			}
261 
262 			XChangeProperty(This->display, wnd, This->net_wm_state_atom, XA_ATOM,
263 					32, PropModeAppend, (unsigned char *) atoms, nitems);
264 		}
265 		else
266 		{
267 			Atom *atoms;
268 			int i;
269 
270 			if (get_property_value(This, wnd, "_NET_WM_STATE", 64, &nitems, &props, 1) < 0)
271 				return 0;
272 
273 			atoms = (Atom *) props;
274 
275 			for (i = 0; i < nitems; i++)
276 			{
277 				if ((atoms[i] == atom1) || (atom2 && (atoms[i] == atom2)))
278 				{
279 					if (i != (nitems - 1))
280 						memmove(&atoms[i], &atoms[i + 1],
281 							sizeof(Atom) * (nitems - i - 1));
282 					nitems--;
283 					i--;
284 				}
285 			}
286 
287 			XChangeProperty(This->display, wnd, This->net_wm_state_atom, XA_ATOM,
288 					32, PropModeReplace, (unsigned char *) atoms, nitems);
289 
290 			XFree(props);
291 		}
292 
293 		return 0;
294 	}
295 
296 	xevent.type = ClientMessage;
297 	xevent.xclient.window = wnd;
298 	xevent.xclient.message_type = This->net_wm_state_atom;
299 	xevent.xclient.format = 32;
300 	if (add)
301 		xevent.xclient.data.l[0] = _NET_WM_STATE_ADD;
302 	else
303 		xevent.xclient.data.l[0] = _NET_WM_STATE_REMOVE;
304 	xevent.xclient.data.l[1] = atom1;
305 	xevent.xclient.data.l[2] = atom2;
306 	xevent.xclient.data.l[3] = 0;
307 	xevent.xclient.data.l[4] = 0;
308 	status = XSendEvent(This->display, DefaultRootWindow(This->display), False,
309 			    SubstructureNotifyMask | SubstructureRedirectMask, &xevent);
310 	if (!status)
311 		return -1;
312 
313 	return 0;
314 }
315 
316 /*
317    Set the window state: normal/minimized/maximized.
318    Returns -1 on failure.
319 */
320 int
ewmh_change_state(RDPCLIENT * This,Window wnd,int state)321 ewmh_change_state(RDPCLIENT * This, Window wnd, int state)
322 {
323 	/*
324 	 * Deal with the max atoms
325 	 */
326 	if (state == SEAMLESSRDP_MAXIMIZED)
327 	{
328 		if (ewmh_modify_state
329 		    (This, wnd, 1, This->ewmhints.state_maximized_vert_atom,
330 		     This->ewmhints.state_maximized_horz_atom) < 0)
331 			return -1;
332 	}
333 	else
334 	{
335 		if (ewmh_modify_state
336 		    (This, wnd, 0, This->ewmhints.state_maximized_vert_atom,
337 		     This->ewmhints.state_maximized_horz_atom) < 0)
338 			return -1;
339 	}
340 
341 	return 0;
342 }
343 
344 
345 int
ewmh_get_window_desktop(RDPCLIENT * This,Window wnd)346 ewmh_get_window_desktop(RDPCLIENT * This, Window wnd)
347 {
348 	unsigned long nitems_return;
349 	unsigned char *prop_return;
350 	int desktop;
351 
352 	if (get_property_value(This, wnd, "_NET_WM_DESKTOP", 1, &nitems_return, &prop_return, 0) < 0)
353 		return (-1);
354 
355 	if (nitems_return != 1)
356 	{
357 		fprintf(stderr, "_NET_WM_DESKTOP has bad length\n");
358 		return (-1);
359 	}
360 
361 	desktop = *prop_return;
362 	XFree(prop_return);
363 	return desktop;
364 }
365 
366 
367 int
ewmh_move_to_desktop(RDPCLIENT * This,Window wnd,unsigned int desktop)368 ewmh_move_to_desktop(RDPCLIENT * This, Window wnd, unsigned int desktop)
369 {
370 	Status status;
371 	XEvent xevent;
372 
373 	xevent.type = ClientMessage;
374 	xevent.xclient.window = wnd;
375 	xevent.xclient.message_type = This->net_wm_desktop_atom;
376 	xevent.xclient.format = 32;
377 	xevent.xclient.data.l[0] = desktop;
378 	xevent.xclient.data.l[1] = 0;
379 	xevent.xclient.data.l[2] = 0;
380 	xevent.xclient.data.l[3] = 0;
381 	xevent.xclient.data.l[4] = 0;
382 	status = XSendEvent(This->display, DefaultRootWindow(This->display), False,
383 			    SubstructureNotifyMask | SubstructureRedirectMask, &xevent);
384 	if (!status)
385 		return -1;
386 
387 	return 0;
388 }
389 
390 void
ewmh_set_wm_name(RDPCLIENT * This,Window wnd,const char * title)391 ewmh_set_wm_name(RDPCLIENT * This, Window wnd, const char *title)
392 {
393 	int len;
394 
395 	len = strlen(title);
396 	XChangeProperty(This->display, wnd, This->ewmhints.name_atom, This->ewmhints.utf8_string_atom,
397 			8, PropModeReplace, (unsigned char *) title, len);
398 }
399 
400 
401 int
ewmh_set_window_popup(RDPCLIENT * This,Window wnd)402 ewmh_set_window_popup(RDPCLIENT * This, Window wnd)
403 {
404 	if (ewmh_modify_state
405 	    (This, wnd, 1, This->ewmhints.state_skip_taskbar_atom, This->ewmhints.state_skip_pager_atom) < 0)
406 		return -1;
407 	return 0;
408 }
409 
410 int
ewmh_set_window_modal(RDPCLIENT * This,Window wnd)411 ewmh_set_window_modal(RDPCLIENT * This, Window wnd)
412 {
413 	if (ewmh_modify_state(This, wnd, 1, This->ewmhints.state_modal_atom, 0) < 0)
414 		return -1;
415 	return 0;
416 }
417 
418 #endif /* MAKE_PROTO */
419 
420 
421 #if 0
422 
423 /* FIXME: _NET_MOVERESIZE_WINDOW is for pagers, not for
424    applications. We should implement _NET_WM_MOVERESIZE instead */
425 
426 int
427 ewmh_net_moveresize_window(RDPCLIENT * This, Window wnd, int x, int y, int width, int height)
428 {
429 	Status status;
430 	XEvent xevent;
431 	Atom moveresize;
432 
433 	moveresize = XInternAtom(This->display, "_NET_MOVERESIZE_WINDOW", False);
434 	if (!moveresize)
435 	{
436 		return -1;
437 	}
438 
439 	xevent.type = ClientMessage;
440 	xevent.xclient.window = wnd;
441 	xevent.xclient.message_type = moveresize;
442 	xevent.xclient.format = 32;
443 	xevent.xclient.data.l[0] = StaticGravity | (1 << 8) | (1 << 9) | (1 << 10) | (1 << 11);
444 	xevent.xclient.data.l[1] = x;
445 	xevent.xclient.data.l[2] = y;
446 	xevent.xclient.data.l[3] = width;
447 	xevent.xclient.data.l[4] = height;
448 
449 	status = XSendEvent(This->display, DefaultRootWindow(This->display), False,
450 			    SubstructureNotifyMask | SubstructureRedirectMask, &xevent);
451 	if (!status)
452 		return -1;
453 	return 0;
454 }
455 
456 #endif
457