1 /* $Id$ */
2 /* Copyright (c) 2011-2016 Pierre Pronchery <khorben@defora.org> */
3 /* This file is part of DeforaOS Desktop Panel */
4 /* This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, version 3 of the License.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program.  If not, see <http://www.gnu.org/licenses/>. */
15 
16 
17 
18 #include <sys/time.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <errno.h>
22 #include <libintl.h>
23 #include <gdk/gdkx.h>
24 #include <X11/Xatom.h>
25 #include <System.h>
26 #include <Desktop.h>
27 #include "Panel/applet.h"
28 #define _(string) gettext(string)
29 #define N_(string) (string)
30 
31 #if !GTK_CHECK_VERSION(3, 0, 0)
32 # define gdk_error_trap_pop_ignored() gdk_error_trap_pop()
33 #endif
34 
35 
36 /* Tasks */
37 /* private */
38 /* types */
39 #define atom(a) TASKS_ATOM_ ## a
40 typedef enum _TasksAtom
41 {
42 #include "tasks.atoms"
43 } TasksAtom;
44 #define TASKS_ATOM_LAST TASKS_ATOM_UTF8_STRING
45 #define TASKS_ATOM_COUNT (TASKS_ATOM_LAST + 1)
46 #undef atom
47 
48 typedef struct _PanelApplet Tasks;
49 
50 typedef struct _Task
51 {
52 	Tasks * tasks;
53 	Window window;
54 	GtkWidget * widget;
55 	GtkWidget * image;
56 	GtkWidget * label;
57 	gboolean delete;
58 	gboolean reorder;
59 } Task;
60 
61 struct _PanelApplet
62 {
63 	PanelAppletHelper * helper;
64 	Task ** tasks;
65 	size_t tasks_cnt;
66 	gboolean label;
67 	gboolean reorder;
68 	gboolean embedded;
69 
70 	GtkWidget * widget;
71 	GtkWidget * hbox;
72 	GtkIconSize iconsize;
73 	int icon_width;
74 	int icon_height;
75 	gulong source;
76 
77 	Atom atom[TASKS_ATOM_COUNT];
78 	GdkDisplay * display;
79 	GdkScreen * screen;
80 	GdkWindow * root;
81 };
82 
83 
84 /* constants */
85 #define atom(a) "" # a
86 static const char * _tasks_atom[TASKS_ATOM_COUNT] =
87 {
88 #include "tasks.atoms"
89 };
90 #undef atom
91 
92 #define _NET_WM_MOVERESIZE_MOVE			 8 /* movement only */
93 #define _NET_WM_MOVERESIZE_SIZE_KEYBOARD	 9 /* size via keyboard */
94 #define _NET_WM_MOVERESIZE_MOVE_KEYBOARD	10 /* move via keyboard */
95 
96 
97 /* prototypes */
98 /* task */
99 static Task * _task_new(Tasks * tasks, gboolean label, gboolean reorder,
100 		Window window, char const * name, GdkPixbuf * pixbuf);
101 static void _task_delete(Task * Task);
102 static void _task_set(Task * task, char const * name, GdkPixbuf * pixbuf);
103 static void _task_toggle_state(Task * task, TasksAtom state);
104 static void _task_toggle_state2(Task * task, TasksAtom state1,
105 		TasksAtom state2);
106 
107 /* tasks */
108 static Tasks * _tasks_init(PanelAppletHelper * helper, GtkWidget ** widget);
109 static void _tasks_destroy(Tasks * tasks);
110 
111 /* accessors */
112 static int _tasks_get_current_desktop(Tasks * tasks);
113 static int _tasks_get_text_property(Tasks * tasks, Window window, Atom property,
114 		char ** ret);
115 static int _tasks_get_window_property(Tasks * tasks, Window window,
116 		TasksAtom property, Atom atom, unsigned long * cnt,
117 		unsigned char ** ret);
118 
119 /* useful */
120 static void _tasks_do(Tasks * tasks);
121 
122 /* callbacks */
123 static gboolean _task_on_button_press(GtkWidget * widget,
124 		GdkEventButton * event, gpointer data);
125 static void _task_on_clicked(gpointer data);
126 static gboolean _task_on_delete_event(gpointer data);
127 static GdkFilterReturn _task_on_filter(GdkXEvent * xevent, GdkEvent * event,
128 		gpointer data);
129 static gboolean _task_on_popup(gpointer data);
130 static void _task_on_popup_change_desktop(gpointer data);
131 static void _task_on_popup_close(gpointer data);
132 static void _task_on_popup_fullscreen(gpointer data);
133 static void _task_on_popup_maximize(gpointer data);
134 static void _task_on_popup_maximize_horz(gpointer data);
135 static void _task_on_popup_maximize_vert(gpointer data);
136 static void _task_on_popup_minimize(gpointer data);
137 static void _task_on_popup_move(gpointer data);
138 static void _task_on_popup_resize(gpointer data);
139 static void _task_on_popup_shade(gpointer data);
140 static void _task_on_popup_stick(gpointer data);
141 static void _task_on_screen_changed(GtkWidget * widget, GdkScreen * previous,
142 		gpointer data);
143 
144 
145 /* public */
146 /* variables */
147 PanelAppletDefinition applet =
148 {
149 	"Tasks",
150 	"application-x-executable",
151 	NULL,
152 	_tasks_init,
153 	_tasks_destroy,
154 	NULL,
155 #ifndef EMBEDDED
156 	TRUE,
157 #else
158 	FALSE,
159 #endif
160 	TRUE
161 };
162 
163 
164 /* private */
165 /* functions */
166 /* Task */
167 /* task_new */
_task_new(Tasks * tasks,gboolean label,gboolean reorder,Window window,char const * name,GdkPixbuf * pixbuf)168 static Task * _task_new(Tasks * tasks, gboolean label, gboolean reorder,
169 		Window window, char const * name, GdkPixbuf * pixbuf)
170 {
171 	Task * task;
172 	GtkWidget * hbox;
173 
174 	if((task = malloc(sizeof(*task))) == NULL)
175 	{
176 		error_set("%s: %s", applet.name, strerror(errno));
177 		return NULL;
178 	}
179 	task->tasks = tasks;
180 	task->window = window;
181 	task->widget = gtk_button_new();
182 	g_signal_connect(task->widget, "button-press-event", G_CALLBACK(
183 				_task_on_button_press), task);
184 	g_signal_connect_swapped(task->widget, "delete-event", G_CALLBACK(
185 				_task_on_delete_event), task);
186 	g_signal_connect_swapped(task->widget, "popup-menu", G_CALLBACK(
187 				_task_on_popup), task);
188 	g_signal_connect_swapped(task->widget, "clicked", G_CALLBACK(
189 				_task_on_clicked), task);
190 	task->image = gtk_image_new();
191 	task->delete = FALSE;
192 	task->reorder = reorder;
193 #if GTK_CHECK_VERSION(3, 0, 0)
194 	hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
195 #else
196 	hbox = gtk_hbox_new(FALSE, 0);
197 #endif
198 	gtk_box_pack_start(GTK_BOX(hbox), task->image, FALSE, TRUE, 0);
199 	if(label)
200 	{
201 		task->label = gtk_label_new(name);
202 # if GTK_CHECK_VERSION(3, 0, 0) /* XXX should work with Gtk+ 2 too */
203 		gtk_label_set_ellipsize(GTK_LABEL(task->label),
204 				PANGO_ELLIPSIZE_END);
205 # endif
206 		if(tasks->iconsize == GTK_ICON_SIZE_LARGE_TOOLBAR)
207 			gtk_label_set_line_wrap(GTK_LABEL(task->label), TRUE);
208 # if GTK_CHECK_VERSION(2, 10, 0)
209 		gtk_label_set_line_wrap_mode(GTK_LABEL(task->label),
210 				PANGO_WRAP_WORD_CHAR);
211 # endif
212 		gtk_box_pack_start(GTK_BOX(hbox), task->label, FALSE, TRUE, 4);
213 		gtk_widget_set_size_request(task->widget, tasks->icon_width,
214 				tasks->icon_height);
215 	}
216 	else
217 		task->label = NULL;
218 	gtk_container_add(GTK_CONTAINER(task->widget), hbox);
219 	_task_set(task, name, pixbuf);
220 	return task;
221 }
222 
223 
224 /* task_delete */
_task_delete(Task * task)225 static void _task_delete(Task * task)
226 {
227 	if(task->widget != NULL)
228 		gtk_widget_destroy(task->widget);
229 	free(task);
230 }
231 
232 
233 /* task_set */
_task_set(Task * task,char const * name,GdkPixbuf * pixbuf)234 static void _task_set(Task * task, char const * name, GdkPixbuf * pixbuf)
235 {
236 	if(task->label != NULL)
237 		gtk_label_set_text(GTK_LABEL(task->label), name);
238 #if GTK_CHECK_VERSION(2, 12, 0)
239 	gtk_widget_set_tooltip_text(task->widget, name);
240 #endif
241 	if(pixbuf != NULL)
242 		gtk_image_set_from_pixbuf(GTK_IMAGE(task->image), pixbuf);
243 	else
244 		gtk_image_set_from_icon_name(GTK_IMAGE(task->image),
245 				"application-x-executable",
246 				task->tasks->iconsize);
247 }
248 
249 
250 /* task_toggle_state */
_task_toggle_state(Task * task,TasksAtom state)251 static void _task_toggle_state(Task * task, TasksAtom state)
252 {
253 	_task_toggle_state2(task, state, 0);
254 }
255 
256 
257 /* task_toggle_state2 */
_task_toggle_state2(Task * task,TasksAtom state1,TasksAtom state2)258 static void _task_toggle_state2(Task * task, TasksAtom state1,
259 		TasksAtom state2)
260 {
261 	Tasks * tasks = task->tasks;
262 	GdkDisplay * display;
263 	XEvent xev;
264 
265 	display = tasks->display;
266 	memset(&xev, 0, sizeof(xev));
267 	xev.xclient.type = ClientMessage;
268 	xev.xclient.window = task->window;
269 	xev.xclient.message_type = tasks->atom[TASKS_ATOM__NET_WM_STATE];
270 	xev.xclient.format = 32;
271 	xev.xclient.data.l[0] = tasks->atom[TASKS_ATOM__NET_WM_STATE_TOGGLE];
272 	xev.xclient.data.l[1] = tasks->atom[state1];
273 	xev.xclient.data.l[2] = (state2 != 0) ? tasks->atom[state2] : 0;
274 	xev.xclient.data.l[3] = 2;
275 	gdk_error_trap_push();
276 	XSendEvent(GDK_DISPLAY_XDISPLAY(display), GDK_WINDOW_XID(tasks->root),
277 			False,
278 			SubstructureNotifyMask | SubstructureRedirectMask,
279 			&xev);
280 	gdk_error_trap_pop_ignored();
281 }
282 
283 
284 /* Tasks */
285 /* tasks_init */
_tasks_init(PanelAppletHelper * helper,GtkWidget ** widget)286 static Tasks * _tasks_init(PanelAppletHelper * helper, GtkWidget ** widget)
287 {
288 	Tasks * tasks;
289 	char const * p;
290 	GtkOrientation orientation;
291 
292 	if((tasks = malloc(sizeof(*tasks))) == NULL)
293 		return NULL;
294 	tasks->helper = helper;
295 	tasks->tasks = NULL;
296 	tasks->tasks_cnt = 0;
297 	/* config: label */
298 	if((p = helper->config_get(helper->panel, "tasks", "label")) != NULL)
299 		tasks->label = strtol(p, NULL, 0) ? TRUE : FALSE;
300 	else
301 #ifdef EMBEDDED
302 		tasks->label = FALSE;
303 #else
304 		tasks->label = TRUE;
305 #endif
306 	/* config: reorder */
307 	if((p = helper->config_get(helper->panel, "tasks", "reorder")) != NULL)
308 		tasks->reorder = strtol(p, NULL, 0) ? TRUE : FALSE;
309 	else
310 #ifdef EMBEDDED
311 		tasks->reorder = TRUE;
312 #else
313 		tasks->reorder = FALSE;
314 #endif
315 #ifdef EMBEDDED
316 	tasks->embedded = TRUE;
317 #else
318 	tasks->embedded = FALSE;
319 #endif
320 	orientation = panel_window_get_orientation(helper->window);
321 #if GTK_CHECK_VERSION(3, 0, 0)
322 	tasks->hbox = gtk_box_new(orientation, 0);
323 #else
324 	tasks->hbox = (orientation == GTK_ORIENTATION_VERTICAL)
325 		? gtk_vbox_new(FALSE, 0) : gtk_hbox_new(FALSE, 0);
326 #endif
327 	gtk_box_set_homogeneous(GTK_BOX(tasks->hbox), TRUE);
328 	tasks->source = g_signal_connect(tasks->hbox, "screen-changed",
329 			G_CALLBACK(_task_on_screen_changed), tasks);
330 	tasks->iconsize = panel_window_get_icon_size(helper->window);
331 	tasks->icon_width = 48;
332 	tasks->icon_height = 48;
333 	gtk_icon_size_lookup(tasks->iconsize, &tasks->icon_width,
334 			&tasks->icon_height);
335 	tasks->icon_width -= 4;
336 	tasks->icon_height -= 4;
337 	tasks->display = NULL;
338 	tasks->screen = NULL;
339 	tasks->root = NULL;
340 #ifndef EMBEDDED
341 	tasks->widget = gtk_scrolled_window_new(NULL, NULL);
342 	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(tasks->widget),
343 			GTK_POLICY_NEVER, GTK_POLICY_NEVER);
344 	gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(tasks->widget),
345 			GTK_SHADOW_NONE);
346 # if GTK_CHECK_VERSION(3, 8, 0)
347 	gtk_container_add(GTK_CONTAINER(tasks->widget), tasks->hbox);
348 # else
349 	gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(
350 				tasks->widget), tasks->hbox);
351 # endif
352 #else
353 	tasks->widget = tasks->hbox;
354 #endif
355 	gtk_widget_show_all(tasks->widget);
356 	*widget = tasks->widget;
357 	return tasks;
358 }
359 
360 
361 /* tasks_destroy */
_tasks_destroy(Tasks * tasks)362 static void _tasks_destroy(Tasks * tasks)
363 {
364 	size_t i;
365 
366 	if(tasks->source != 0)
367 		g_signal_handler_disconnect(tasks->widget, tasks->source);
368 	tasks->source = 0;
369 	if(tasks->root != NULL)
370 		gdk_window_remove_filter(tasks->root, _task_on_filter, tasks);
371 	for(i = 0; i < tasks->tasks_cnt; i++)
372 		_task_delete(tasks->tasks[i]);
373 	free(tasks->tasks);
374 	tasks->tasks = NULL;
375 	tasks->tasks_cnt = 0;
376 	gtk_widget_destroy(tasks->widget);
377 	free(tasks);
378 }
379 
380 
381 /* accessors */
382 /* tasks_get_current_desktop */
_tasks_get_current_desktop(Tasks * tasks)383 static int _tasks_get_current_desktop(Tasks * tasks)
384 {
385 	unsigned long cnt;
386 	unsigned long *p;
387 
388 	if(tasks->embedded)
389 		/* ignore the current desktop */
390 		return -1;
391 	if(_tasks_get_window_property(tasks, GDK_WINDOW_XID(tasks->root),
392 				TASKS_ATOM__NET_CURRENT_DESKTOP, XA_CARDINAL,
393 				&cnt, (void *)&p) != 0)
394 		return -1;
395 	cnt = *p;
396 	XFree(p);
397 	return cnt;
398 }
399 
400 
401 /* tasks_get_text_property */
_tasks_get_text_property(Tasks * tasks,Window window,Atom property,char ** ret)402 static int _tasks_get_text_property(Tasks * tasks, Window window,
403 		Atom property, char ** ret)
404 {
405 	int res;
406 	XTextProperty text;
407 	GdkAtom atom;
408 	int cnt;
409 	char ** list;
410 	int i;
411 
412 #ifdef DEBUG
413 	fprintf(stderr, "DEBUG: %s(tasks, window, %lu)\n", __func__, property);
414 #endif
415 	gdk_error_trap_push();
416 	res = XGetTextProperty(GDK_DISPLAY_XDISPLAY(tasks->display), window,
417 			&text, property);
418 	if(gdk_error_trap_pop() != 0 || res == 0)
419 		return -1;
420 	atom = gdk_x11_xatom_to_atom(text.encoding);
421 #if GTK_CHECK_VERSION(2, 24, 0)
422 	cnt = gdk_x11_display_text_property_to_text_list(tasks->display,
423 			atom, text.format, text.value, text.nitems, &list);
424 #else
425 	cnt = gdk_text_property_to_utf8_list(atom, text.format, text.value,
426 			text.nitems, &list);
427 #endif
428 	if(cnt > 0)
429 	{
430 		*ret = list[0];
431 		for(i = 1; i < cnt; i++)
432 			g_free(list[i]);
433 		g_free(list);
434 	}
435 	else
436 		*ret = NULL;
437 	if(text.value != NULL)
438 		XFree(text.value);
439 	return 0;
440 }
441 
442 
443 /* tasks_get_window_property */
_tasks_get_window_property(Tasks * tasks,Window window,TasksAtom property,Atom atom,unsigned long * cnt,unsigned char ** ret)444 static int _tasks_get_window_property(Tasks * tasks, Window window,
445 		TasksAtom property, Atom atom, unsigned long * cnt,
446 		unsigned char ** ret)
447 {
448 	int res;
449 	Atom type;
450 	int format;
451 	unsigned long bytes;
452 
453 #ifdef DEBUG
454 	fprintf(stderr, "DEBUG: %s(tasks, window, %s, %lu)\n", __func__,
455 			_tasks_atom[property], atom);
456 #endif
457 	gdk_error_trap_push();
458 	res = XGetWindowProperty(GDK_DISPLAY_XDISPLAY(tasks->display), window,
459 			tasks->atom[property], 0, G_MAXLONG, False, atom,
460 			&type, &format, cnt, &bytes, ret);
461 	if(gdk_error_trap_pop() != 0 || res != Success)
462 		return -1;
463 	if(type != atom)
464 	{
465 		if(*ret != NULL)
466 			XFree(*ret);
467 		*ret = NULL;
468 		return 1;
469 	}
470 	return 0;
471 }
472 
473 
474 /* tasks_do */
475 static char * _do_name(Tasks * tasks, Window window);
476 static char * _do_name_text(Tasks * tasks, Window window, Atom property);
477 static char * _do_name_utf8(Tasks * tasks, Window window, Atom property);
478 static GdkPixbuf * _do_pixbuf(Tasks * tasks, Window window);
479 static int _do_tasks_add(Tasks * tasks, int desktop, Window window,
480 		char const * name, GdkPixbuf * pixbuf);
481 static void _do_tasks_clean(Tasks * tasks);
482 static int _do_typehint_normal(Tasks * tasks, Window window);
483 
_tasks_do(Tasks * tasks)484 static void _tasks_do(Tasks * tasks)
485 {
486 	unsigned long cnt = 0;
487 	Window * windows = NULL;
488 	int desktop;
489 	unsigned long i;
490 	char * name;
491 
492 #ifdef DEBUG
493 	fprintf(stderr, "DEBUG: %s()\n", __func__);
494 #endif
495 	if(_tasks_get_window_property(tasks, GDK_WINDOW_XID(tasks->root),
496 				TASKS_ATOM__NET_CLIENT_LIST,
497 				XA_WINDOW, &cnt, (void *)&windows) != 0)
498 		return;
499 	desktop = _tasks_get_current_desktop(tasks);
500 	for(i = 0; i < tasks->tasks_cnt; i++)
501 		tasks->tasks[i]->delete = TRUE;
502 	for(i = 0; i < cnt; i++)
503 	{
504 		if(_do_typehint_normal(tasks, windows[i]) != 0)
505 			continue;
506 		if((name = _do_name(tasks, windows[i])) == NULL)
507 			continue;
508 		_do_tasks_add(tasks, desktop, windows[i], name, _do_pixbuf(
509 					tasks, windows[i]));
510 		g_free(name);
511 	}
512 	_do_tasks_clean(tasks);
513 	XFree(windows);
514 }
515 
_do_name(Tasks * tasks,Window window)516 static char * _do_name(Tasks * tasks, Window window)
517 {
518 	char * ret;
519 
520 	if((ret = _do_name_utf8(tasks, window, TASKS_ATOM__NET_WM_VISIBLE_NAME))
521 			!= NULL)
522 		return ret;
523 	if((ret = _do_name_utf8(tasks, window, TASKS_ATOM__NET_WM_NAME))
524 			!= NULL)
525 		return ret;
526 	if((ret = _do_name_text(tasks, window, XA_WM_NAME)) != NULL)
527 		return ret;
528 	return g_strdup(_("(Untitled)"));
529 }
530 
_do_name_text(Tasks * tasks,Window window,Atom property)531 static char * _do_name_text(Tasks * tasks, Window window, Atom property)
532 {
533 	char * ret = NULL;
534 
535 	if(_tasks_get_text_property(tasks, window, property, (void *)&ret) != 0)
536 		return NULL;
537 	return ret;
538 }
539 
_do_name_utf8(Tasks * tasks,Window window,Atom property)540 static char * _do_name_utf8(Tasks * tasks, Window window, Atom property)
541 {
542 	char * ret = NULL;
543 	char * str = NULL;
544 	unsigned long cnt = 0;
545 
546 	if(_tasks_get_window_property(tasks, window, property,
547 				tasks->atom[TASKS_ATOM_UTF8_STRING], &cnt,
548 				(void *)&str) != 0)
549 		return NULL;
550 	if(g_utf8_validate(str, cnt, NULL))
551 		ret = g_strndup(str, cnt);
552 	XFree(str);
553 	return ret;
554 }
555 
_do_pixbuf(Tasks * tasks,Window window)556 static GdkPixbuf * _do_pixbuf(Tasks * tasks, Window window)
557 {
558 	GdkPixbuf * ret;
559 	unsigned long cnt = 0;
560 	unsigned long * buf = NULL;
561 	unsigned long i;
562 	long width;
563 	long height;
564 	unsigned long size;
565 	unsigned char * pixbuf = NULL;
566 	unsigned long j;
567 	GdkPixbuf * p;
568 	unsigned long * best = NULL;
569 
570 	if(_tasks_get_window_property(tasks, window, TASKS_ATOM__NET_WM_ICON,
571 				XA_CARDINAL, &cnt, (void *)&buf) != 0)
572 		return NULL;
573 	for(i = 0; i < cnt - 3; i += 2 + (width * height))
574 	{
575 		width = buf[i];
576 		height = buf[i + 1];
577 		if(i + 2 + (width * height) > cnt)
578 			break;
579 		if(width <= 0 || height <= 0 || width != height)
580 			continue;
581 		if(tasks->icon_width == width)
582 		{
583 			best = &buf[i];
584 			break;
585 		}
586 		if(best == NULL || abs(best[0] - tasks->icon_width)
587 				> abs(width - tasks->icon_width))
588 			best = &buf[i];
589 	}
590 	if(best != NULL)
591 	{
592 		width = best[0];
593 		height = best[1];
594 		size = width * height * 4;
595 		pixbuf = malloc(size);
596 	}
597 	if(best == NULL || pixbuf == NULL)
598 	{
599 		XFree(buf);
600 		return NULL;
601 	}
602 	for(i = 2, j = 0; j < size; i++)
603 	{
604 		pixbuf[j++] = (best[i] >> 16) & 0xff; /* red */
605 		pixbuf[j++] = (best[i] >> 8) & 0xff; /* green */
606 		pixbuf[j++] = best[i] & 0xff; /* blue */
607 		pixbuf[j++] = best[i] >> 24; /* alpha */
608 	}
609 	p = gdk_pixbuf_new_from_data(pixbuf, GDK_COLORSPACE_RGB, TRUE, 8, width,
610 			height, width * 4, (GdkPixbufDestroyNotify)free, NULL);
611 	XFree(buf);
612 	if(width == tasks->icon_width)
613 		return p;
614 	ret = gdk_pixbuf_scale_simple(p, tasks->icon_width, tasks->icon_height,
615 			GDK_INTERP_BILINEAR);
616 	g_object_unref(p);
617 	return ret;
618 }
619 
_do_tasks_add(Tasks * tasks,int desktop,Window window,char const * name,GdkPixbuf * pixbuf)620 static int _do_tasks_add(Tasks * tasks, int desktop, Window window,
621 		char const * name, GdkPixbuf * pixbuf)
622 {
623 	size_t i;
624 	Task * p = NULL;
625 #ifndef EMBEDDED
626 	unsigned long * l;
627 	unsigned long cnt;
628 	int cur = -1;
629 #endif
630 	Task ** q;
631 
632 #ifndef EMBEDDED
633 	if(_tasks_get_window_property(tasks, window, TASKS_ATOM__NET_WM_DESKTOP,
634 			XA_CARDINAL, &cnt, (void *)&l) == 0)
635 	{
636 		if(cnt == 1)
637 			cur = *l;
638 		XFree(l);
639 	}
640 	if(cur >= 0 && cur != desktop)
641 		return 0;
642 #endif
643 	for(i = 0; i < tasks->tasks_cnt; i++)
644 		if(tasks->tasks[i]->window == window)
645 			break;
646 	if(i < tasks->tasks_cnt) /* found the task */
647 	{
648 		p = tasks->tasks[i];
649 		_task_set(p, name, pixbuf);
650 		p->delete = FALSE;
651 		return 0;
652 	}
653 	if((q = realloc(tasks->tasks, (tasks->tasks_cnt + 1) * sizeof(*q)))
654 			== NULL)
655 		return 1;
656 	tasks->tasks = q;
657 	if((p = _task_new(tasks, tasks->label, tasks->reorder, window, name,
658 					pixbuf)) == NULL)
659 		return 1;
660 	tasks->tasks[tasks->tasks_cnt++] = p;
661 	gtk_widget_show_all(p->widget);
662 	gtk_box_pack_start(GTK_BOX(tasks->hbox), p->widget, FALSE, TRUE, 0);
663 	if(tasks->reorder)
664 		gtk_box_reorder_child(GTK_BOX(tasks->hbox), p->widget, 0);
665 	return 0;
666 }
667 
_do_tasks_clean(Tasks * tasks)668 static void _do_tasks_clean(Tasks * tasks)
669 {
670 	size_t i;
671 	size_t cnt;
672 	size_t j;
673 	Task ** q;
674 
675 	for(i = 0, cnt = tasks->tasks_cnt; i < cnt;)
676 	{
677 		if(tasks->tasks[i]->delete == FALSE)
678 		{
679 			i++;
680 			continue;
681 		}
682 		_task_delete(tasks->tasks[i]);
683 		cnt--;
684 		for(j = i; j < cnt; j++)
685 			tasks->tasks[j] = tasks->tasks[j + 1];
686 		if((q = realloc(tasks->tasks, cnt * sizeof(*q))) != NULL
687 				|| cnt == 0)
688 			tasks->tasks = q;
689 	}
690 	tasks->tasks_cnt = cnt;
691 }
692 
_do_typehint_normal(Tasks * tasks,Window window)693 static int _do_typehint_normal(Tasks * tasks, Window window)
694 {
695 	Atom typehint;
696 	Atom * p;
697 	unsigned long cnt = 0;
698 
699 	if(_tasks_get_window_property(tasks, window,
700 				TASKS_ATOM__NET_WM_WINDOW_TYPE, XA_ATOM, &cnt,
701 				(void *)&p) == 0)
702 	{
703 		typehint = *p;
704 		XFree(p);
705 		return typehint == tasks->atom[
706 			TASKS_ATOM__NET_WM_WINDOW_TYPE_NORMAL] ? 0 : 1;
707 	}
708 	/* FIXME return 1 if WM_TRANSIENT_FOR is set */
709 	return 0;
710 }
711 
712 
713 /* callbacks */
714 /* task_on_button_press */
_task_on_button_press(GtkWidget * widget,GdkEventButton * event,gpointer data)715 static gboolean _task_on_button_press(GtkWidget * widget,
716 		GdkEventButton * event, gpointer data)
717 {
718 	(void) widget;
719 
720 	if(event->button != 3 || event->type != GDK_BUTTON_PRESS)
721 		return FALSE;
722 	_task_on_popup(data);
723 	return TRUE;
724 }
725 
726 
727 /* task_on_clicked */
728 static void _clicked_activate(Task * task);
729 
_task_on_clicked(gpointer data)730 static void _task_on_clicked(gpointer data)
731 {
732 	Task * task = data;
733 
734 	_clicked_activate(task);
735 	if(task->reorder)
736 		gtk_box_reorder_child(GTK_BOX(task->tasks->hbox), task->widget,
737 				0);
738 }
739 
_clicked_activate(Task * task)740 static void _clicked_activate(Task * task)
741 {
742 	GdkDisplay * display;
743 	XEvent xev;
744 #ifdef DEBUG
745 	int res;
746 #endif
747 
748 	display = task->tasks->display;
749 	memset(&xev, 0, sizeof(xev));
750 	xev.xclient.type = ClientMessage;
751 	xev.xclient.window = task->window;
752 	xev.xclient.message_type = task->tasks->atom[
753 		TASKS_ATOM__NET_ACTIVE_WINDOW];
754 	xev.xclient.format = 32;
755 	xev.xclient.data.l[0] = 2;
756 	xev.xclient.data.l[1] = gdk_x11_display_get_user_time(display);
757 	xev.xclient.data.l[2] = 0;
758 	gdk_error_trap_push();
759 #ifdef DEBUG
760 	res =
761 #endif
762 		XSendEvent(GDK_DISPLAY_XDISPLAY(display),
763 			GDK_WINDOW_XID(task->tasks->root), False,
764 			SubstructureNotifyMask | SubstructureRedirectMask,
765 			&xev);
766 #ifdef DEBUG
767 	if(gdk_error_trap_pop() != 0 || res != Success)
768 		fprintf(stderr, "DEBUG: %s() error\n", __func__);
769 #else
770 	gdk_error_trap_pop_ignored();
771 #endif
772 }
773 
774 
775 /* task_on_delete_event */
_task_on_delete_event(gpointer data)776 static gboolean _task_on_delete_event(gpointer data)
777 {
778 	Task * task = data;
779 
780 	task->widget = NULL;
781 	return FALSE;
782 }
783 
784 
785 /* task_on_filter */
_task_on_filter(GdkXEvent * xevent,GdkEvent * event,gpointer data)786 static GdkFilterReturn _task_on_filter(GdkXEvent * xevent, GdkEvent * event,
787 		gpointer data)
788 {
789 	Tasks * tasks = data;
790 	XEvent * xev = xevent;
791 	(void) event;
792 
793 	if(xev->type != PropertyNotify)
794 		return GDK_FILTER_CONTINUE;
795 	if(xev->xproperty.atom != tasks->atom[TASKS_ATOM__NET_CLIENT_LIST]
796 #ifndef EMBEDDED
797 			&& xev->xproperty.atom
798 			!= tasks->atom[TASKS_ATOM__NET_CURRENT_DESKTOP]
799 #endif
800 			)
801 		return GDK_FILTER_CONTINUE;
802 	_tasks_do(tasks);
803 	return GDK_FILTER_CONTINUE;
804 }
805 
806 
807 /* task_on_popup */
_task_on_popup(gpointer data)808 static gboolean _task_on_popup(gpointer data)
809 {
810 	Task * task = data;
811 	unsigned long cnt = 0;
812 	unsigned long * buf = NULL;
813 	unsigned long i;
814 	const struct {
815 		TasksAtom atom;
816 		void (*callback)(gpointer data);
817 		char const * stock;
818 		char const * label;
819 	} items[] = {
820 		{ TASKS_ATOM__NET_WM_ACTION_MOVE, _task_on_popup_move, NULL,
821 			N_("Move") },
822 		{ TASKS_ATOM__NET_WM_ACTION_RESIZE, _task_on_popup_resize, NULL,
823 			N_("Resize") },
824 		{ TASKS_ATOM__NET_WM_ACTION_MINIMIZE, _task_on_popup_minimize,
825 			NULL, N_("Minimize") },
826 		{ TASKS_ATOM__NET_WM_ACTION_SHADE, _task_on_popup_shade, NULL,
827 			N_("Shade") },
828 		{ TASKS_ATOM__NET_WM_ACTION_STICK, _task_on_popup_stick, NULL,
829 			N_("Stick") },
830 		{ TASKS_ATOM__NET_WM_ACTION_MAXIMIZE_HORZ,
831 			_task_on_popup_maximize_horz, NULL,
832 			N_("Maximize horizontally") },
833 		{ TASKS_ATOM__NET_WM_ACTION_MAXIMIZE_VERT,
834 			_task_on_popup_maximize_vert, NULL,
835 			N_("Maximize vertically") },
836 		{ TASKS_ATOM__NET_WM_ACTION_FULLSCREEN, _task_on_popup_fullscreen,
837 			GTK_STOCK_FULLSCREEN, N_("Fullscreen") },
838 		{ TASKS_ATOM__NET_WM_ACTION_CHANGE_DESKTOP,
839 			_task_on_popup_change_desktop, NULL,
840 			N_("Change desktop") },
841 		{ TASKS_ATOM__NET_WM_ACTION_CLOSE, _task_on_popup_close,
842 			GTK_STOCK_CLOSE, N_("Close") }
843 	};
844 	const size_t items_cnt = sizeof(items) / sizeof(*items);
845 	size_t j;
846 	GtkWidget * menu = NULL;
847 	GtkWidget * menuitem;
848 	int max = 0;
849 
850 	if(_tasks_get_window_property(task->tasks, task->window,
851 				TASKS_ATOM__NET_WM_ALLOWED_ACTIONS, XA_ATOM,
852 				&cnt, (void *)&buf) != 0)
853 		return FALSE;
854 	for(i = 0; i < cnt; i++)
855 	{
856 		for(j = 0; j < items_cnt; j++)
857 			if(buf[i] == task->tasks->atom[items[j].atom])
858 				break;
859 		if(j == items_cnt)
860 			continue;
861 		if(items[j].atom == TASKS_ATOM__NET_WM_ACTION_CHANGE_DESKTOP)
862 			continue; /* FIXME implement as a special case */
863 		if(menu == NULL)
864 			menu = gtk_menu_new();
865 		if(items[j].stock != NULL)
866 #if GTK_CHECK_VERSION(3, 10, 0)
867 		{
868 			menuitem = gtk_image_menu_item_new_with_label(
869 					items[j].label);
870 			gtk_image_menu_item_set_image(
871 					GTK_IMAGE_MENU_ITEM(menuitem),
872 					gtk_image_new_from_icon_name(
873 						items[j].stock,
874 						GTK_ICON_SIZE_MENU));
875 		}
876 #else
877 			menuitem = gtk_image_menu_item_new_from_stock(
878 					items[j].stock, NULL);
879 #endif
880 		else
881 			menuitem = gtk_menu_item_new_with_label(
882 					_(items[j].label));
883 		g_signal_connect_swapped(menuitem, "activate", G_CALLBACK(
884 					items[j].callback), task);
885 		gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
886 		/* maximizing horizontally and vertically */
887 		if(items[j].atom != TASKS_ATOM__NET_WM_ACTION_MAXIMIZE_VERT
888 				&& items[j].atom
889 				!= TASKS_ATOM__NET_WM_ACTION_MAXIMIZE_HORZ)
890 			continue;
891 		if(max++ != 1)
892 			continue;
893 		menuitem = gtk_menu_item_new_with_label(_("Maximize"));
894 		g_signal_connect_swapped(menuitem, "activate", G_CALLBACK(
895 					_task_on_popup_maximize), task);
896 		gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
897 	}
898 	XFree(buf);
899 	if(menu == NULL)
900 		return FALSE;
901 	gtk_widget_show_all(menu);
902 	gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, task, 2,
903 			gtk_get_current_event_time());
904 	return TRUE;
905 }
906 
907 
908 /* task_on_popup_change_desktop */
_task_on_popup_change_desktop(gpointer data)909 static void _task_on_popup_change_desktop(gpointer data)
910 {
911 	/* FIXME implement */
912 }
913 
914 
915 /* task_on_popup_close */
_task_on_popup_close(gpointer data)916 static void _task_on_popup_close(gpointer data)
917 {
918 	Task * task = data;
919 	GdkDisplay * display;
920 	XEvent xev;
921 
922 	display = task->tasks->display;
923 	memset(&xev, 0, sizeof(xev));
924 	xev.xclient.type = ClientMessage;
925 	xev.xclient.window = task->window;
926 	xev.xclient.message_type = task->tasks->atom[
927 		TASKS_ATOM__NET_CLOSE_WINDOW];
928 	xev.xclient.format = 32;
929 	xev.xclient.data.l[0] = gdk_x11_display_get_user_time(display);
930 	xev.xclient.data.l[1] = 2;
931 	gdk_error_trap_push();
932 	XSendEvent(GDK_DISPLAY_XDISPLAY(display),
933 			GDK_WINDOW_XID(task->tasks->root), False,
934 			SubstructureNotifyMask | SubstructureRedirectMask,
935 			&xev);
936 	gdk_error_trap_pop_ignored();
937 }
938 
939 
940 /* task_on_popup_fullscreen */
_task_on_popup_fullscreen(gpointer data)941 static void _task_on_popup_fullscreen(gpointer data)
942 {
943 	Task * task = data;
944 
945 	_task_toggle_state(task, TASKS_ATOM__NET_WM_STATE_FULLSCREEN);
946 }
947 
948 
949 /* task_on_popup_maximize */
_task_on_popup_maximize(gpointer data)950 static void _task_on_popup_maximize(gpointer data)
951 {
952 	Task * task = data;
953 
954 	_task_toggle_state2(task, TASKS_ATOM__NET_WM_STATE_MAXIMIZED_HORZ,
955 			TASKS_ATOM__NET_WM_STATE_MAXIMIZED_VERT);
956 }
957 
958 
959 /* task_on_popup_maximize_hort */
_task_on_popup_maximize_horz(gpointer data)960 static void _task_on_popup_maximize_horz(gpointer data)
961 {
962 	Task * task = data;
963 
964 	_task_toggle_state(task, TASKS_ATOM__NET_WM_STATE_MAXIMIZED_HORZ);
965 }
966 
967 
968 /* task_on_popup_maximize_vert */
_task_on_popup_maximize_vert(gpointer data)969 static void _task_on_popup_maximize_vert(gpointer data)
970 {
971 	Task * task = data;
972 
973 	_task_toggle_state(task, TASKS_ATOM__NET_WM_STATE_MAXIMIZED_VERT);
974 }
975 
976 
977 /* task_on_popup_minimize */
_task_on_popup_minimize(gpointer data)978 static void _task_on_popup_minimize(gpointer data)
979 {
980 	Task * task = data;
981 
982 	gdk_error_trap_push();
983 	XIconifyWindow(GDK_DISPLAY_XDISPLAY(task->tasks->display), task->window,
984 			gdk_x11_screen_get_screen_number(task->tasks->screen));
985 	gdk_error_trap_pop_ignored();
986 }
987 
988 
989 /* task_on_popup_move */
_task_on_popup_move(gpointer data)990 static void _task_on_popup_move(gpointer data)
991 {
992 	Task * task = data;
993 	Tasks * tasks = task->tasks;
994 	GdkDisplay * display;
995 	XEvent xev;
996 
997 	display = tasks->display;
998 	memset(&xev, 0, sizeof(xev));
999 	xev.xclient.type = ClientMessage;
1000 	xev.xclient.window = task->window;
1001 	xev.xclient.message_type = tasks->atom[TASKS_ATOM__NET_WM_MOVERESIZE];
1002 	xev.xclient.format = 32;
1003 	memset(&xev.xclient.data, 0, sizeof(xev.xclient.data));
1004 	xev.xclient.data.l[2] = _NET_WM_MOVERESIZE_MOVE_KEYBOARD;
1005 	xev.xclient.data.l[3] = 1; /* XXX may not always be the case */
1006 	xev.xclient.data.l[4] = 2;
1007 	gdk_error_trap_push();
1008 	XSendEvent(GDK_DISPLAY_XDISPLAY(display),
1009 			GDK_WINDOW_XID(tasks->root), False,
1010 			SubstructureNotifyMask | SubstructureRedirectMask,
1011 			&xev);
1012 	gdk_error_trap_pop_ignored();
1013 }
1014 
1015 
1016 /* task_on_popup_resize */
_task_on_popup_resize(gpointer data)1017 static void _task_on_popup_resize(gpointer data)
1018 {
1019 	Task * task = data;
1020 	Tasks * tasks = task->tasks;
1021 	GdkDisplay * display;
1022 	XEvent xev;
1023 
1024 	display = tasks->display;
1025 	memset(&xev, 0, sizeof(xev));
1026 	xev.xclient.type = ClientMessage;
1027 	xev.xclient.window = task->window;
1028 	xev.xclient.message_type = tasks->atom[TASKS_ATOM__NET_WM_MOVERESIZE];
1029 	xev.xclient.format = 32;
1030 	memset(&xev.xclient.data, 0, sizeof(xev.xclient.data));
1031 	xev.xclient.data.l[2] = _NET_WM_MOVERESIZE_SIZE_KEYBOARD;
1032 	xev.xclient.data.l[3] = 1; /* XXX may not always be the case */
1033 	xev.xclient.data.l[4] = 2;
1034 	gdk_error_trap_push();
1035 	XSendEvent(GDK_DISPLAY_XDISPLAY(display),
1036 			GDK_WINDOW_XID(tasks->root), False,
1037 			SubstructureNotifyMask | SubstructureRedirectMask,
1038 			&xev);
1039 	gdk_error_trap_pop_ignored();
1040 }
1041 
1042 
1043 /* task_on_popup_shade */
_task_on_popup_shade(gpointer data)1044 static void _task_on_popup_shade(gpointer data)
1045 {
1046 	Task * task = data;
1047 
1048 	_task_toggle_state(task, TASKS_ATOM__NET_WM_STATE_SHADED);
1049 }
1050 
1051 
1052 /* task_on_popup_stick */
_task_on_popup_stick(gpointer data)1053 static void _task_on_popup_stick(gpointer data)
1054 {
1055 	Task * task = data;
1056 
1057 	_task_toggle_state(task, TASKS_ATOM__NET_WM_STATE_STICKY);
1058 }
1059 
1060 
1061 /* task_on_screen_changed */
_task_on_screen_changed(GtkWidget * widget,GdkScreen * previous,gpointer data)1062 static void _task_on_screen_changed(GtkWidget * widget, GdkScreen * previous,
1063 		gpointer data)
1064 {
1065 	Tasks * tasks = data;
1066 	GdkEventMask events;
1067 	size_t i;
1068 	(void) previous;
1069 
1070 #ifdef DEBUG
1071 	fprintf(stderr, "DEBUG: %s()\n", __func__);
1072 #endif
1073 	if(tasks->root != NULL)
1074 		gdk_window_remove_filter(tasks->root, _task_on_filter, tasks);
1075 	tasks->screen = gtk_widget_get_screen(widget);
1076 	tasks->display = gdk_screen_get_display(tasks->screen);
1077 	tasks->root = gdk_screen_get_root_window(tasks->screen);
1078 	events = gdk_window_get_events(tasks->root);
1079 	gdk_window_set_events(tasks->root, events | GDK_PROPERTY_CHANGE_MASK);
1080 	gdk_window_add_filter(tasks->root, _task_on_filter, tasks);
1081 	/* atoms */
1082 	for(i = 0; i < TASKS_ATOM_COUNT; i++)
1083 		tasks->atom[i] = gdk_x11_get_xatom_by_name_for_display(
1084 				tasks->display, _tasks_atom[i]);
1085 	_tasks_do(tasks);
1086 }
1087