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