]> Dogcows Code - chaz/tint2/blob - src/tint.c
7b6e637594b65db025e2e6c9dc9398d5a6bffe6e
[chaz/tint2] / src / tint.c
1 /**************************************************************************
2 *
3 * Tint2 panel
4 *
5 * Copyright (C) 2007 Pål Staurland (staura@gmail.com)
6 * Modified (C) 2008 thierry lorthiois (lorthiois@bbsoft.fr) from Omega distribution
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License version 2
10 * as published by the Free Software Foundation.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 **************************************************************************/
20
21 #include <sys/stat.h>
22 #include <unistd.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <stdint.h>
26 #include <string.h>
27 #include <X11/Xutil.h>
28 #include <X11/Xatom.h>
29 #include <X11/Xlocale.h>
30 #include <X11/extensions/Xdamage.h>
31 #include <Imlib2.h>
32 #include <signal.h>
33
34 #ifdef HAVE_SN
35 #include <libsn/sn.h>
36 #include <sys/wait.h>
37 #endif
38
39 #include <version.h>
40 #include "server.h"
41 #include "window.h"
42 #include "config.h"
43 #include "task.h"
44 #include "taskbar.h"
45 #include "systraybar.h"
46 #include "launcher.h"
47 #include "panel.h"
48 #include "tooltip.h"
49 #include "timer.h"
50 #include "xsettings-client.h"
51
52 // Drag and Drop state variables
53 Window dnd_source_window;
54 Window dnd_target_window;
55 int dnd_version;
56 Atom dnd_selection;
57 Atom dnd_atom;
58 int dnd_sent_request;
59 char *dnd_launcher_exec;
60
61 void signal_handler(int sig)
62 {
63 // signal handler is light as it should be
64 signal_pending = sig;
65 }
66
67
68 void init (int argc, char *argv[])
69 {
70 int i;
71
72 // set global data
73 default_config();
74 default_timeout();
75 default_systray();
76 memset(&server, 0, sizeof(Server_global));
77 #ifdef ENABLE_BATTERY
78 default_battery();
79 #endif
80 default_clock();
81 default_launcher();
82 default_taskbar();
83 default_tooltip();
84 default_panel();
85
86 // read options
87 for (i = 1; i < argc; ++i) {
88 if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
89 printf("Usage: tint2 [-c] <config_file>\n");
90 exit(0);
91 }
92 if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--version")) {
93 printf("tint2 version %s\n", VERSION_STRING);
94 exit(0);
95 }
96 if (!strcmp(argv[i], "-c")) {
97 i++;
98 if (i < argc)
99 config_path = strdup(argv[i]);
100 }
101 if (!strcmp(argv[i], "-s")) {
102 i++;
103 if (i < argc)
104 snapshot_path = strdup(argv[i]);
105 }
106 }
107 // Set signal handler
108 signal_pending = 0;
109 struct sigaction sa = { .sa_handler = signal_handler };
110 struct sigaction sa_chld = { .sa_handler = SIG_DFL, .sa_flags = SA_NOCLDWAIT };
111 sigaction(SIGUSR1, &sa, 0);
112 sigaction(SIGINT, &sa, 0);
113 sigaction(SIGTERM, &sa, 0);
114 sigaction(SIGHUP, &sa, 0);
115 sigaction(SIGCHLD, &sa_chld, 0);
116
117 // BSD does not support pselect(), therefore we have to use select and hope that we do not
118 // end up in a race condition there (see 'man select()' on a linux machine for more information)
119 // block all signals, such that no race conditions occur before pselect in our main loop
120 // sigset_t block_mask;
121 // sigaddset(&block_mask, SIGINT);
122 // sigaddset(&block_mask, SIGTERM);
123 // sigaddset(&block_mask, SIGHUP);
124 // sigaddset(&block_mask, SIGUSR1);
125 // sigprocmask(SIG_BLOCK, &block_mask, 0);
126 }
127
128 #ifdef HAVE_SN
129 static int error_trap_depth = 0;
130
131 static void
132 error_trap_push (SnDisplay *display,
133 Display *xdisplay)
134 {
135 ++error_trap_depth;
136 }
137
138 static void
139 error_trap_pop (SnDisplay *display,
140 Display *xdisplay)
141 {
142 if (error_trap_depth == 0)
143 {
144 fprintf(stderr, "Error trap underflow!\n");
145 return;
146 }
147
148 XSync(xdisplay, False); /* get all errors out of the queue */
149 --error_trap_depth;
150 }
151
152 static void sigchld_handler(int sig) {
153 // Wait for all dead processes
154 pid_t pid;
155 while ((pid = waitpid(-1, NULL, WNOHANG)) > 0) {
156 SnLauncherContext *ctx;
157 ctx = (SnLauncherContext *) g_tree_lookup (server.pids, GINT_TO_POINTER (pid));
158 if (ctx == NULL) {
159 fprintf(stderr, "Unknown child %d terminated!\n", pid);
160 }
161 else {
162 g_tree_remove (server.pids, GINT_TO_POINTER (pid));
163 sn_launcher_context_complete (ctx);
164 sn_launcher_context_unref (ctx);
165 }
166 }
167 }
168
169 static gint cmp_ptr(gconstpointer a, gconstpointer b) {
170 if (a < b)
171 return -1;
172 else if (a == b)
173 return 0;
174 else
175 return 1;
176 }
177
178 #endif // HAVE_SN
179
180 void init_X11()
181 {
182 server.dsp = XOpenDisplay (NULL);
183 if (!server.dsp) {
184 fprintf(stderr, "tint2 exit : could not open display.\n");
185 exit(0);
186 }
187 server_init_atoms ();
188 server.screen = DefaultScreen (server.dsp);
189 server.root_win = RootWindow(server.dsp, server.screen);
190 server.desktop = server_get_current_desktop ();
191 server_init_visual();
192 XSetErrorHandler ((XErrorHandler) server_catch_error);
193
194 #ifdef HAVE_SN
195 // Initialize startup-notification
196 server.sn_dsp = sn_display_new (server.dsp, error_trap_push, error_trap_pop);
197 server.pids = g_tree_new (cmp_ptr);
198 // Setup a handler for child termination
199 struct sigaction act;
200 memset (&act, 0, sizeof (struct sigaction));
201 act.sa_handler = sigchld_handler;
202 if (sigaction(SIGCHLD, &act, 0)) {
203 perror("sigaction");
204 }
205 #endif // HAVE_SN
206
207 imlib_context_set_display (server.dsp);
208 imlib_context_set_visual (server.visual);
209 imlib_context_set_colormap (server.colormap);
210
211 /* Catch events */
212 XSelectInput (server.dsp, server.root_win, PropertyChangeMask|StructureNotifyMask);
213
214 setlocale (LC_ALL, "");
215 // config file use '.' as decimal separator
216 setlocale(LC_NUMERIC, "POSIX");
217
218 // load default icon
219 gchar *path;
220 const gchar * const *data_dirs;
221 data_dirs = g_get_system_data_dirs ();
222 int i;
223 for (i = 0; data_dirs[i] != NULL; i++) {
224 path = g_build_filename(data_dirs[i], "tint2", "default_icon.png", NULL);
225 if (g_file_test (path, G_FILE_TEST_EXISTS))
226 default_icon = imlib_load_image(path);
227 g_free(path);
228 }
229
230 // get monitor and desktop config
231 get_monitors();
232 get_desktops();
233 }
234
235
236 void cleanup()
237 {
238 cleanup_systray();
239 cleanup_tooltip();
240 cleanup_clock();
241 cleanup_launcher();
242 #ifdef ENABLE_BATTERY
243 cleanup_battery();
244 #endif
245 cleanup_panel();
246 cleanup_config();
247
248 if (default_icon) {
249 imlib_context_set_image(default_icon);
250 imlib_free_image();
251 }
252 imlib_context_disconnect_display();
253
254 cleanup_server();
255 cleanup_timeout();
256 if (server.dsp) XCloseDisplay(server.dsp);
257 }
258
259
260 void get_snapshot(const char *path)
261 {
262 Panel *panel = &panel1[0];
263
264 if (panel->area.width > server.monitor[0].width)
265 panel->area.width = server.monitor[0].width;
266
267 panel->temp_pmap = XCreatePixmap(server.dsp, server.root_win, panel->area.width, panel->area.height, server.depth);
268 rendering(panel);
269
270 Imlib_Image img = NULL;
271 imlib_context_set_drawable(panel->temp_pmap);
272 img = imlib_create_image_from_drawable(0, 0, 0, panel->area.width, panel->area.height, 0);
273
274 imlib_context_set_image(img);
275 if (!panel_horizontal) {
276 // rotate 90° vertical panel
277 imlib_image_flip_horizontal();
278 imlib_image_flip_diagonal();
279 }
280 imlib_save_image(path);
281 imlib_free_image();
282 }
283
284
285 void window_action (Task *tsk, int action)
286 {
287 if (!tsk) return;
288 int desk;
289 switch (action) {
290 case CLOSE:
291 set_close (tsk->win);
292 break;
293 case TOGGLE:
294 set_active(tsk->win);
295 break;
296 case ICONIFY:
297 XIconifyWindow (server.dsp, tsk->win, server.screen);
298 break;
299 case TOGGLE_ICONIFY:
300 if (task_active && tsk->win == task_active->win)
301 XIconifyWindow (server.dsp, tsk->win, server.screen);
302 else
303 set_active (tsk->win);
304 break;
305 case SHADE:
306 window_toggle_shade (tsk->win);
307 break;
308 case MAXIMIZE_RESTORE:
309 window_maximize_restore (tsk->win);
310 break;
311 case MAXIMIZE:
312 window_maximize_restore (tsk->win);
313 break;
314 case RESTORE:
315 window_maximize_restore (tsk->win);
316 break;
317 case DESKTOP_LEFT:
318 if ( tsk->desktop == 0 ) break;
319 desk = tsk->desktop - 1;
320 windows_set_desktop(tsk->win, desk);
321 if (desk == server.desktop)
322 set_active(tsk->win);
323 break;
324 case DESKTOP_RIGHT:
325 if (tsk->desktop == server.nb_desktop ) break;
326 desk = tsk->desktop + 1;
327 windows_set_desktop(tsk->win, desk);
328 if (desk == server.desktop)
329 set_active(tsk->win);
330 break;
331 case NEXT_TASK:
332 if (task_active) {
333 Task *tsk1;
334 tsk1 = next_task(task_active);
335 set_active(tsk1->win);
336 }
337 break;
338 case PREV_TASK:
339 if (task_active) {
340 Task *tsk1;
341 tsk1 = prev_task(task_active);
342 set_active(tsk1->win);
343 }
344 }
345 }
346
347
348 int tint2_handles_click(Panel* panel, XButtonEvent* e)
349 {
350 Task* task = click_task(panel, e->x, e->y);
351 if (task) {
352 if( (e->button == 1)
353 || (e->button == 2 && mouse_middle != 0)
354 || (e->button == 3 && mouse_right != 0)
355 || (e->button == 4 && mouse_scroll_up != 0)
356 || (e->button == 5 && mouse_scroll_down !=0) )
357 {
358 return 1;
359 }
360 else
361 return 0;
362 }
363 LauncherIcon *icon = click_launcher_icon(panel, e->x, e->y);
364 if (icon) {
365 if (e->button == 1) {
366 return 1;
367 } else {
368 return 0;
369 }
370 }
371 // no launcher/task clicked --> check if taskbar clicked
372 Taskbar *tskbar = click_taskbar(panel, e->x, e->y);
373 if (tskbar && e->button == 1 && panel_mode == MULTI_DESKTOP)
374 return 1;
375 if (click_clock(panel, e->x, e->y)) {
376 if ( (e->button == 1 && clock_lclick_command) || (e->button == 3 && clock_rclick_command) )
377 return 1;
378 else
379 return 0;
380 }
381 return 0;
382 }
383
384
385 void forward_click(XEvent* e)
386 {
387 // forward the click to the desktop window (thanks conky)
388 XUngrabPointer(server.dsp, e->xbutton.time);
389 e->xbutton.window = server.root_win;
390 // icewm doesn't open under the mouse.
391 // and xfce doesn't open at all.
392 e->xbutton.x = e->xbutton.x_root;
393 e->xbutton.y = e->xbutton.y_root;
394 //printf("**** %d, %d\n", e->xbutton.x, e->xbutton.y);
395 //XSetInputFocus(server.dsp, e->xbutton.window, RevertToParent, e->xbutton.time);
396 XSendEvent(server.dsp, e->xbutton.window, False, ButtonPressMask, e);
397 }
398
399
400 void event_button_press (XEvent *e)
401 {
402 Panel *panel = get_panel(e->xany.window);
403 if (!panel) return;
404
405
406 if (wm_menu && !tint2_handles_click(panel, &e->xbutton) ) {
407 forward_click(e);
408 return;
409 }
410 task_drag = click_task(panel, e->xbutton.x, e->xbutton.y);
411
412 if (panel_layer == BOTTOM_LAYER)
413 XLowerWindow (server.dsp, panel->main_win);
414 }
415
416 void event_button_motion_notify (XEvent *e)
417 {
418 Panel * panel = get_panel(e->xany.window);
419 if(!panel || !task_drag)
420 return;
421
422 // Find the taskbar on the event's location
423 Taskbar * event_taskbar = click_taskbar(panel, e->xbutton.x, e->xbutton.y);
424 if(event_taskbar == NULL)
425 return;
426
427 // Find the task on the event's location
428 Task * event_task = click_task(panel, e->xbutton.x, e->xbutton.y);
429
430 // If the event takes place on the same taskbar as the task being dragged
431 if(event_taskbar == task_drag->area.parent) {
432 // Swap the task_drag with the task on the event's location (if they differ)
433 if(event_task && event_task != task_drag) {
434 GSList * drag_iter = g_slist_find(event_taskbar->area.list, task_drag);
435 GSList * task_iter = g_slist_find(event_taskbar->area.list, event_task);
436 if(drag_iter && task_iter) {
437 gpointer temp = task_iter->data;
438 task_iter->data = drag_iter->data;
439 drag_iter->data = temp;
440 event_taskbar->area.resize = 1;
441 panel_refresh = 1;
442 task_dragged = 1;
443 }
444 }
445 }
446 else { // The event is on another taskbar than the task being dragged
447 if(task_drag->desktop == ALLDESKTOP || panel_mode != MULTI_DESKTOP)
448 return;
449
450 Taskbar * drag_taskbar = (Taskbar*)task_drag->area.parent;
451 drag_taskbar->area.list = g_slist_remove(drag_taskbar->area.list, task_drag);
452
453 if(event_taskbar->area.posx > drag_taskbar->area.posx || event_taskbar->area.posy > drag_taskbar->area.posy) {
454 int i = (taskbarname_enabled) ? 1 : 0;
455 event_taskbar->area.list = g_slist_insert(event_taskbar->area.list, task_drag, i);
456 }
457 else
458 event_taskbar->area.list = g_slist_append(event_taskbar->area.list, task_drag);
459
460 // Move task to other desktop (but avoid the 'Window desktop changed' code in 'event_property_notify')
461 task_drag->area.parent = event_taskbar;
462 task_drag->desktop = event_taskbar->desktop;
463
464 windows_set_desktop(task_drag->win, event_taskbar->desktop);
465
466 event_taskbar->area.resize = 1;
467 drag_taskbar->area.resize = 1;
468 task_dragged = 1;
469 panel_refresh = 1;
470 }
471 }
472
473 void event_button_release (XEvent *e)
474 {
475 Panel *panel = get_panel(e->xany.window);
476 if (!panel) return;
477
478 if (wm_menu && !tint2_handles_click(panel, &e->xbutton)) {
479 forward_click(e);
480 if (panel_layer == BOTTOM_LAYER)
481 XLowerWindow (server.dsp, panel->main_win);
482 task_drag = 0;
483 return;
484 }
485
486 int action = TOGGLE_ICONIFY;
487 switch (e->xbutton.button) {
488 case 2:
489 action = mouse_middle;
490 break;
491 case 3:
492 action = mouse_right;
493 break;
494 case 4:
495 action = mouse_scroll_up;
496 break;
497 case 5:
498 action = mouse_scroll_down;
499 break;
500 case 6:
501 action = mouse_tilt_left;
502 break;
503 case 7:
504 action = mouse_tilt_right;
505 break;
506 }
507
508 if ( click_clock(panel, e->xbutton.x, e->xbutton.y)) {
509 clock_action(e->xbutton.button);
510 if (panel_layer == BOTTOM_LAYER)
511 XLowerWindow (server.dsp, panel->main_win);
512 task_drag = 0;
513 return;
514 }
515
516 if ( click_launcher(panel, e->xbutton.x, e->xbutton.y)) {
517 LauncherIcon *icon = click_launcher_icon(panel, e->xbutton.x, e->xbutton.y);
518 if (icon) {
519 launcher_action(icon, e);
520 }
521 task_drag = 0;
522 return;
523 }
524
525 Taskbar *tskbar;
526 if ( !(tskbar = click_taskbar(panel, e->xbutton.x, e->xbutton.y)) ) {
527 // TODO: check better solution to keep window below
528 if (panel_layer == BOTTOM_LAYER)
529 XLowerWindow (server.dsp, panel->main_win);
530 task_drag = 0;
531 return;
532 }
533
534 // drag and drop task
535 if (task_dragged) {
536 task_drag = 0;
537 task_dragged = 0;
538 return;
539 }
540
541 // switch desktop
542 if (panel_mode == MULTI_DESKTOP) {
543 if (tskbar->desktop != server.desktop && action != CLOSE && action != DESKTOP_LEFT && action != DESKTOP_RIGHT)
544 set_desktop (tskbar->desktop);
545 }
546
547 // action on task
548 window_action( click_task(panel, e->xbutton.x, e->xbutton.y), action);
549
550 // to keep window below
551 if (panel_layer == BOTTOM_LAYER)
552 XLowerWindow (server.dsp, panel->main_win);
553 }
554
555
556 void event_property_notify (XEvent *e)
557 {
558 int i;
559 Task *tsk;
560 Window win = e->xproperty.window;
561 Atom at = e->xproperty.atom;
562
563 if (xsettings_client)
564 xsettings_client_process_event(xsettings_client, e);
565 if (win == server.root_win) {
566 if (!server.got_root_win) {
567 XSelectInput (server.dsp, server.root_win, PropertyChangeMask|StructureNotifyMask);
568 server.got_root_win = 1;
569 }
570
571 // Change name of desktops
572 else if (at == server.atom._NET_DESKTOP_NAMES) {
573 if (!taskbarname_enabled) return;
574 GSList *l, *list = server_get_name_of_desktop();
575 int j;
576 gchar *name;
577 Taskbar *tskbar;
578 for (i=0 ; i < nb_panel ; i++) {
579 for (j=0, l=list ; j < panel1[i].nb_desktop ; j++) {
580 if (l) {
581 name = g_strdup(l->data);
582 l = l->next;
583 }
584 else
585 name = g_strdup_printf("%d", j+1);
586 tskbar = &panel1[i].taskbar[j];
587 if (strcmp(name, tskbar->bar_name.name) != 0) {
588 g_free(tskbar->bar_name.name);
589 tskbar->bar_name.name = name;
590 tskbar->bar_name.area.resize = 1;
591 }
592 else
593 g_free(name);
594 }
595 }
596 for (l=list ; l ; l = l->next)
597 g_free(l->data);
598 g_slist_free(list);
599 panel_refresh = 1;
600 }
601 // Change number of desktops
602 else if (at == server.atom._NET_NUMBER_OF_DESKTOPS) {
603 if (!taskbar_enabled) return;
604 server.nb_desktop = server_get_number_of_desktop ();
605 if (server.nb_desktop <= server.desktop) {
606 server.desktop = server.nb_desktop-1;
607 }
608 cleanup_taskbar();
609 init_taskbar();
610 for (i=0 ; i < nb_panel ; i++) {
611 init_taskbar_panel(&panel1[i]);
612 set_panel_items_order(&panel1[i]);
613 visible_taskbar(&panel1[i]);
614 panel1[i].area.resize = 1;
615 }
616 task_refresh_tasklist();
617 active_task();
618 panel_refresh = 1;
619 }
620 // Change desktop
621 else if (at == server.atom._NET_CURRENT_DESKTOP) {
622 if (!taskbar_enabled) return;
623 int old_desktop = server.desktop;
624 server.desktop = server_get_current_desktop ();
625 for (i=0 ; i < nb_panel ; i++) {
626 Panel *panel = &panel1[i];
627 set_taskbar_state(&panel->taskbar[old_desktop], TASKBAR_NORMAL);
628 set_taskbar_state(&panel->taskbar[server.desktop], TASKBAR_ACTIVE);
629 // check ALLDESKTOP task => resize taskbar
630 Taskbar *tskbar;
631 Task *tsk;
632 GSList *l;
633 if (server.nb_desktop > old_desktop) {
634 tskbar = &panel->taskbar[old_desktop];
635 l = tskbar->area.list;
636 if (taskbarname_enabled) l = l->next;
637 for (; l ; l = l->next) {
638 tsk = l->data;
639 if (tsk->desktop == ALLDESKTOP) {
640 tsk->area.on_screen = 0;
641 tskbar->area.resize = 1;
642 panel_refresh = 1;
643 }
644 }
645 }
646 tskbar = &panel->taskbar[server.desktop];
647 l = tskbar->area.list;
648 if (taskbarname_enabled) l = l->next;
649 for (; l ; l = l->next) {
650 tsk = l->data;
651 if (tsk->desktop == ALLDESKTOP) {
652 tsk->area.on_screen = 1;
653 tskbar->area.resize = 1;
654 }
655 }
656 }
657 }
658 // Window list
659 else if (at == server.atom._NET_CLIENT_LIST) {
660 task_refresh_tasklist();
661 panel_refresh = 1;
662 }
663 // Change active
664 else if (at == server.atom._NET_ACTIVE_WINDOW) {
665 active_task();
666 panel_refresh = 1;
667 }
668 else if (at == server.atom._XROOTPMAP_ID || at == server.atom._XROOTMAP_ID) {
669 // change Wallpaper
670 for (i=0 ; i < nb_panel ; i++) {
671 set_panel_background(&panel1[i]);
672 }
673 panel_refresh = 1;
674 }
675 }
676 else {
677 tsk = task_get_task (win);
678 if (!tsk) {
679 if (at != server.atom._NET_WM_STATE)
680 return;
681 else {
682 // xfce4 sends _NET_WM_STATE after minimized to tray, so we need to check if window is mapped
683 // if it is mapped and not set as skip_taskbar, we must add it to our task list
684 XWindowAttributes wa;
685 XGetWindowAttributes(server.dsp, win, &wa);
686 if (wa.map_state == IsViewable && !window_is_skip_taskbar(win)) {
687 if ( (tsk = add_task(win)) )
688 panel_refresh = 1;
689 else
690 return;
691 }
692 else
693 return;
694 }
695 }
696 //printf("atom root_win = %s, %s\n", XGetAtomName(server.dsp, at), tsk->title);
697
698 // Window title changed
699 if (at == server.atom._NET_WM_VISIBLE_NAME || at == server.atom._NET_WM_NAME || at == server.atom.WM_NAME) {
700 if (get_title(tsk)) {
701 if (g_tooltip.mapped && (g_tooltip.area == (Area*)tsk)) {
702 tooltip_copy_text((Area*)tsk);
703 tooltip_update();
704 }
705 panel_refresh = 1;
706 }
707 }
708 // Demand attention
709 else if (at == server.atom._NET_WM_STATE) {
710 if (window_is_urgent (win)) {
711 add_urgent(tsk);
712 }
713 if (window_is_skip_taskbar(win)) {
714 remove_task( tsk );
715 panel_refresh = 1;
716 }
717 }
718 else if (at == server.atom.WM_STATE) {
719 // Iconic state
720 int state = (task_active && tsk->win == task_active->win ? TASK_ACTIVE : TASK_NORMAL);
721 if (window_is_iconified(win))
722 state = TASK_ICONIFIED;
723 set_task_state(tsk, state);
724 panel_refresh = 1;
725 }
726 // Window icon changed
727 else if (at == server.atom._NET_WM_ICON) {
728 get_icon(tsk);
729 panel_refresh = 1;
730 }
731 // Window desktop changed
732 else if (at == server.atom._NET_WM_DESKTOP) {
733 int desktop = window_get_desktop (win);
734 //printf(" Window desktop changed %d, %d\n", tsk->desktop, desktop);
735 // bug in windowmaker : send unecessary 'desktop changed' when focus changed
736 if (desktop != tsk->desktop) {
737 remove_task (tsk);
738 tsk = add_task (win);
739 active_task();
740 panel_refresh = 1;
741 }
742 }
743 else if (at == server.atom.WM_HINTS) {
744 XWMHints* wmhints = XGetWMHints(server.dsp, win);
745 if (wmhints && wmhints->flags & XUrgencyHint) {
746 add_urgent(tsk);
747 }
748 XFree(wmhints);
749 }
750
751 if (!server.got_root_win) server.root_win = RootWindow (server.dsp, server.screen);
752 }
753 }
754
755
756 void event_expose (XEvent *e)
757 {
758 Panel *panel;
759 panel = get_panel(e->xany.window);
760 if (!panel) return;
761 // TODO : one panel_refresh per panel ?
762 panel_refresh = 1;
763 }
764
765
766 void event_configure_notify (Window win)
767 {
768 // change in root window (xrandr)
769 if (win == server.root_win) {
770 signal_pending = SIGUSR1;
771 return;
772 }
773
774 // 'win' is a trayer icon
775 TrayWindow *traywin;
776 GSList *l;
777 for (l = systray.list_icons; l ; l = l->next) {
778 traywin = (TrayWindow*)l->data;
779 if (traywin->tray_id == win) {
780 //printf("move tray %d\n", traywin->x);
781 XMoveResizeWindow(server.dsp, traywin->id, traywin->x, traywin->y, traywin->width, traywin->height);
782 XResizeWindow(server.dsp, traywin->tray_id, traywin->width, traywin->height);
783 panel_refresh = 1;
784 return;
785 }
786 }
787
788 // 'win' move in another monitor
789 if (nb_panel == 1) return;
790 Task *tsk = task_get_task (win);
791 if (!tsk) return;
792
793 Panel *p = tsk->area.panel;
794 if (p->monitor != window_get_monitor (win)) {
795 remove_task (tsk);
796 tsk = add_task (win);
797 if (win == window_get_active ()) {
798 set_task_state(tsk, TASK_ACTIVE);
799 task_active = tsk;
800 }
801 panel_refresh = 1;
802 }
803 }
804
805 char *GetAtomName(Display* disp, Atom a)
806 {
807 if (a == None)
808 return "None";
809 else
810 return XGetAtomName(disp, a);
811 }
812
813 typedef struct Property
814 {
815 unsigned char *data;
816 int format, nitems;
817 Atom type;
818 } Property;
819
820 //This fetches all the data from a property
821 struct Property read_property(Display* disp, Window w, Atom property)
822 {
823 Atom actual_type;
824 int actual_format;
825 unsigned long nitems;
826 unsigned long bytes_after;
827 unsigned char *ret=0;
828
829 int read_bytes = 1024;
830
831 //Keep trying to read the property until there are no
832 //bytes unread.
833 do {
834 if (ret != 0)
835 XFree(ret);
836 XGetWindowProperty(disp, w, property, 0, read_bytes, False, AnyPropertyType,
837 &actual_type, &actual_format, &nitems, &bytes_after,
838 &ret);
839 read_bytes *= 2;
840 } while (bytes_after != 0);
841
842 fprintf(stderr, "DnD %s:%d: Property:\n", __FILE__, __LINE__);
843 fprintf(stderr, "DnD %s:%d: Actual type: %s\n", __FILE__, __LINE__, GetAtomName(disp, actual_type));
844 fprintf(stderr, "DnD %s:%d: Actual format: %d\n", __FILE__, __LINE__, actual_format);
845 fprintf(stderr, "DnD %s:%d: Number of items: %lu\n", __FILE__, __LINE__, nitems);
846
847 Property p;
848 p.data = ret;
849 p.format = actual_format;
850 p.nitems = nitems;
851 p.type = actual_type;
852
853 return p;
854 }
855
856 // This function takes a list of targets which can be converted to (atom_list, nitems)
857 // and a list of acceptable targets with prioritees (datatypes). It returns the highest
858 // entry in datatypes which is also in atom_list: ie it finds the best match.
859 Atom pick_target_from_list(Display* disp, Atom* atom_list, int nitems)
860 {
861 Atom to_be_requested = None;
862 int i;
863 for (i = 0; i < nitems; i++) {
864 char *atom_name = GetAtomName(disp, atom_list[i]);
865 fprintf(stderr, "DnD %s:%d: Type %d = %s\n", __FILE__, __LINE__, i, atom_name);
866
867 //See if this data type is allowed and of higher priority (closer to zero)
868 //than the present one.
869 if (strcmp(atom_name, "STRING") == 0) {
870 to_be_requested = atom_list[i];
871 }
872 }
873
874 return to_be_requested;
875 }
876
877 // Finds the best target given up to three atoms provided (any can be None).
878 // Useful for part of the Xdnd protocol.
879 Atom pick_target_from_atoms(Display* disp, Atom t1, Atom t2, Atom t3)
880 {
881 Atom atoms[3];
882 int n = 0;
883
884 if (t1 != None)
885 atoms[n++] = t1;
886
887 if (t2 != None)
888 atoms[n++] = t2;
889
890 if (t3 != None)
891 atoms[n++] = t3;
892
893 return pick_target_from_list(disp, atoms, n);
894 }
895
896 // Finds the best target given a local copy of a property.
897 Atom pick_target_from_targets(Display* disp, Property p)
898 {
899 //The list of targets is a list of atoms, so it should have type XA_ATOM
900 //but it may have the type TARGETS instead.
901
902 if ((p.type != XA_ATOM && p.type != server.atom.TARGETS) || p.format != 32) {
903 //This would be really broken. Targets have to be an atom list
904 //and applications should support this. Nevertheless, some
905 //seem broken (MATLAB 7, for instance), so ask for STRING
906 //next instead as the lowest common denominator
907 return XA_STRING;
908 } else {
909 Atom *atom_list = (Atom*)p.data;
910
911 return pick_target_from_list(disp, atom_list, p.nitems);
912 }
913 }
914
915 void dnd_enter(XClientMessageEvent *e)
916 {
917 dnd_atom = None;
918 int more_than_3 = e->data.l[1] & 1;
919 dnd_source_window = e->data.l[0];
920 dnd_version = (e->data.l[1] >> 24);
921
922 fprintf(stderr, "DnD %s:%d: DnDEnter\n", __FILE__, __LINE__);
923 fprintf(stderr, "DnD %s:%d: DnDEnter. Supports > 3 types = %s\n", __FILE__, __LINE__, more_than_3 ? "yes" : "no");
924 fprintf(stderr, "DnD %s:%d: Protocol version = %d\n", __FILE__, __LINE__, dnd_version);
925 fprintf(stderr, "DnD %s:%d: Type 1 = %s\n", __FILE__, __LINE__, GetAtomName(server.dsp, e->data.l[2]));
926 fprintf(stderr, "DnD %s:%d: Type 2 = %s\n", __FILE__, __LINE__, GetAtomName(server.dsp, e->data.l[3]));
927 fprintf(stderr, "DnD %s:%d: Type 3 = %s\n", __FILE__, __LINE__, GetAtomName(server.dsp, e->data.l[4]));
928
929 //Query which conversions are available and pick the best
930
931 if (more_than_3) {
932 //Fetch the list of possible conversions
933 //Notice the similarity to TARGETS with paste.
934 Property p = read_property(server.dsp, dnd_source_window, server.atom.XdndTypeList);
935 dnd_atom = pick_target_from_targets(server.dsp, p);
936 XFree(p.data);
937 } else {
938 //Use the available list
939 dnd_atom = pick_target_from_atoms(server.dsp, e->data.l[2], e->data.l[3], e->data.l[4]);
940 }
941
942 fprintf(stderr, "DnD %s:%d: Requested type = %s\n", __FILE__, __LINE__, GetAtomName(server.dsp, dnd_atom));
943 }
944
945 void dnd_position(XClientMessageEvent *e)
946 {
947 dnd_target_window = e->window;
948 int accept = 0;
949 Panel *panel = get_panel(e->window);
950 int x, y, mapX, mapY;
951 Window child;
952 x = (e->data.l[2] >> 16) & 0xFFFF;
953 y = e->data.l[2] & 0xFFFF;
954 XTranslateCoordinates(server.dsp, server.root_win, e->window, x, y, &mapX, &mapY, &child);
955 Task* task = click_task(panel, mapX, mapY);
956 if (task) {
957 if (task->desktop != server.desktop )
958 set_desktop (task->desktop);
959 window_action(task, TOGGLE);
960 } else {
961 LauncherIcon *icon = click_launcher_icon(panel, mapX, mapY);
962 if (icon) {
963 accept = 1;
964 dnd_launcher_exec = icon->cmd;
965 } else {
966 dnd_launcher_exec = 0;
967 }
968 }
969
970 // send XdndStatus event to get more XdndPosition events
971 XClientMessageEvent se;
972 se.type = ClientMessage;
973 se.window = e->data.l[0];
974 se.message_type = server.atom.XdndStatus;
975 se.format = 32;
976 se.data.l[0] = e->window; // XID of the target window
977 se.data.l[1] = accept ? 1 : 0; // bit 0: accept drop bit 1: send XdndPosition events if inside rectangle
978 se.data.l[2] = 0; // Rectangle x,y for which no more XdndPosition events
979 se.data.l[3] = (1 << 16) | 1; // Rectangle w,h for which no more XdndPosition events
980 if (accept) {
981 se.data.l[4] = dnd_version >= 2 ? e->data.l[4] : server.atom.XdndActionCopy;
982 } else {
983 se.data.l[4] = None; // None = drop will not be accepted
984 }
985
986 XSendEvent(server.dsp, e->data.l[0], False, NoEventMask, (XEvent*)&se);
987 }
988
989 void dnd_drop(XClientMessageEvent *e)
990 {
991 if (dnd_target_window && dnd_launcher_exec) {
992 if (dnd_version >= 1) {
993 XConvertSelection(server.dsp, server.atom.XdndSelection, XA_STRING, dnd_selection, dnd_target_window, e->data.l[2]);
994 } else {
995 XConvertSelection(server.dsp, server.atom.XdndSelection, XA_STRING, dnd_selection, dnd_target_window, CurrentTime);
996 }
997 } else {
998 //The source is sending anyway, despite instructions to the contrary.
999 //So reply that we're not interested.
1000 XClientMessageEvent m;
1001 memset(&m, sizeof(m), 0);
1002 m.type = ClientMessage;
1003 m.display = e->display;
1004 m.window = e->data.l[0];
1005 m.message_type = server.atom.XdndFinished;
1006 m.format = 32;
1007 m.data.l[0] = dnd_target_window;
1008 m.data.l[1] = 0;
1009 m.data.l[2] = None; //Failed.
1010 XSendEvent(server.dsp, e->data.l[0], False, NoEventMask, (XEvent*)&m);
1011 }
1012 }
1013
1014 int main (int argc, char *argv[])
1015 {
1016 XEvent e;
1017 XClientMessageEvent *ev;
1018 fd_set fdset;
1019 int x11_fd, i;
1020 Panel *panel;
1021 GSList *it;
1022 struct timeval* timeout;
1023 int hidden_dnd = 0;
1024
1025 start:
1026 init (argc, argv);
1027 init_X11();
1028
1029 i = 0;
1030 if (config_path)
1031 i = config_read_file (config_path);
1032 else
1033 i = config_read ();
1034 if (!i) {
1035 fprintf(stderr, "usage: tint2 [-c] <config_file>\n");
1036 cleanup();
1037 exit(1);
1038 }
1039
1040 init_panel();
1041 if (snapshot_path) {
1042 get_snapshot(snapshot_path);
1043 cleanup();
1044 exit(0);
1045 }
1046
1047 int damage_event, damage_error;
1048 XDamageQueryExtension(server.dsp, &damage_event, &damage_error);
1049 x11_fd = ConnectionNumber(server.dsp);
1050 XSync(server.dsp, False);
1051
1052 // XDND initialization
1053 dnd_source_window = 0;
1054 dnd_target_window = 0;
1055 dnd_version = 0;
1056 dnd_selection = XInternAtom(server.dsp, "PRIMARY", 0);
1057 dnd_atom = None;
1058 dnd_sent_request = 0;
1059 dnd_launcher_exec = 0;
1060
1061 // sigset_t empty_mask;
1062 // sigemptyset(&empty_mask);
1063
1064 while (1) {
1065 if (panel_refresh) {
1066 panel_refresh = 0;
1067
1068 for (i=0 ; i < nb_panel ; i++) {
1069 panel = &panel1[i];
1070
1071 if (panel->is_hidden) {
1072 XCopyArea(server.dsp, panel->hidden_pixmap, panel->main_win, server.gc, 0, 0, panel->hidden_width, panel->hidden_height, 0, 0);
1073 XSetWindowBackgroundPixmap(server.dsp, panel->main_win, panel->hidden_pixmap);
1074 }
1075 else {
1076 if (panel->temp_pmap) XFreePixmap(server.dsp, panel->temp_pmap);
1077 panel->temp_pmap = XCreatePixmap(server.dsp, server.root_win, panel->area.width, panel->area.height, server.depth);
1078 rendering(panel);
1079 XCopyArea(server.dsp, panel->temp_pmap, panel->main_win, server.gc, 0, 0, panel->area.width, panel->area.height, 0, 0);
1080 }
1081 }
1082 XFlush (server.dsp);
1083
1084 panel = (Panel*)systray.area.panel;
1085 if (refresh_systray && panel && !panel->is_hidden) {
1086 refresh_systray = 0;
1087 // tint2 doen't draw systray icons. it just redraw background.
1088 XSetWindowBackgroundPixmap (server.dsp, panel->main_win, panel->temp_pmap);
1089 // force icon's refresh
1090 refresh_systray_icon();
1091 }
1092 }
1093
1094 // thanks to AngryLlama for the timer
1095 // Create a File Description Set containing x11_fd
1096 FD_ZERO (&fdset);
1097 FD_SET (x11_fd, &fdset);
1098 update_next_timeout();
1099 if (next_timeout.tv_sec >= 0 && next_timeout.tv_usec >= 0)
1100 timeout = &next_timeout;
1101 else
1102 timeout = 0;
1103
1104 // Wait for X Event or a Timer
1105 if (select(x11_fd+1, &fdset, 0, 0, timeout) > 0) {
1106 while (XPending (server.dsp)) {
1107 XNextEvent(server.dsp, &e);
1108 #if HAVE_SN
1109 sn_display_process_event (server.sn_dsp, &e);
1110 #endif // HAVE_SN
1111
1112 panel = get_panel(e.xany.window);
1113 if (panel && panel_autohide) {
1114 if (e.type == EnterNotify)
1115 autohide_trigger_show(panel);
1116 else if (e.type == LeaveNotify)
1117 autohide_trigger_hide(panel);
1118 if (panel->is_hidden) {
1119 if (e.type == ClientMessage && e.xclient.message_type == server.atom.XdndPosition) {
1120 hidden_dnd = 1;
1121 autohide_show(panel);
1122 }
1123 else
1124 continue; // discard further processing of this event because the panel is not visible yet
1125 }
1126 else if (hidden_dnd && e.type == ClientMessage && e.xclient.message_type == server.atom.XdndLeave) {
1127 hidden_dnd = 0;
1128 autohide_hide(panel);
1129 }
1130 }
1131
1132 switch (e.type) {
1133 case ButtonPress:
1134 tooltip_hide(0);
1135 event_button_press (&e);
1136 break;
1137
1138 case ButtonRelease:
1139 event_button_release(&e);
1140 break;
1141
1142 case MotionNotify: {
1143 unsigned int button_mask = Button1Mask | Button2Mask | Button3Mask | Button4Mask | Button5Mask;
1144 if (e.xmotion.state & button_mask)
1145 event_button_motion_notify (&e);
1146
1147 Panel* panel = get_panel(e.xmotion.window);
1148 Area* area = click_area(panel, e.xmotion.x, e.xmotion.y);
1149 if (area->_get_tooltip_text)
1150 tooltip_trigger_show(area, panel, &e);
1151 else
1152 tooltip_trigger_hide();
1153 break;
1154 }
1155
1156 case LeaveNotify:
1157 tooltip_trigger_hide();
1158 break;
1159
1160 case Expose:
1161 event_expose(&e);
1162 break;
1163
1164 case PropertyNotify:
1165 event_property_notify(&e);
1166 break;
1167
1168 case ConfigureNotify:
1169 event_configure_notify (e.xconfigure.window);
1170 break;
1171
1172 case ReparentNotify:
1173 if (!systray_enabled)
1174 break;
1175 panel = (Panel*)systray.area.panel;
1176 if (e.xany.window == panel->main_win) // reparented to us
1177 break;
1178 // FIXME: 'reparent to us' badly detected => disabled
1179 break;
1180 case UnmapNotify:
1181 case DestroyNotify:
1182 if (e.xany.window == server.composite_manager) {
1183 // Stop real_transparency
1184 signal_pending = SIGUSR1;
1185 break;
1186 }
1187 if (e.xany.window == g_tooltip.window || !systray_enabled)
1188 break;
1189 for (it = systray.list_icons; it; it = g_slist_next(it)) {
1190 if (((TrayWindow*)it->data)->tray_id == e.xany.window) {
1191 remove_icon((TrayWindow*)it->data);
1192 break;
1193 }
1194 }
1195 break;
1196
1197 case ClientMessage:
1198 ev = &e.xclient;
1199 if (ev->data.l[1] == server.atom._NET_WM_CM_S0) {
1200 if (ev->data.l[2] == None)
1201 // Stop real_transparency
1202 signal_pending = SIGUSR1;
1203 else
1204 // Start real_transparency
1205 signal_pending = SIGUSR1;
1206 }
1207 if (systray_enabled && e.xclient.message_type == server.atom._NET_SYSTEM_TRAY_OPCODE && e.xclient.format == 32 && e.xclient.window == net_sel_win) {
1208 net_message(&e.xclient);
1209 }
1210 else if (e.xclient.message_type == server.atom.XdndEnter) {
1211 dnd_enter(&e.xclient);
1212 }
1213 else if (e.xclient.message_type == server.atom.XdndPosition) {
1214 dnd_position(&e.xclient);
1215 }
1216 else if (e.xclient.message_type == server.atom.XdndDrop) {
1217 dnd_drop(&e.xclient);
1218 }
1219 break;
1220
1221 case SelectionNotify:
1222 {
1223 Atom target = e.xselection.target;
1224
1225 fprintf(stderr, "DnD %s:%d: A selection notify has arrived!\n", __FILE__, __LINE__);
1226 fprintf(stderr, "DnD %s:%d: Requestor = %lu\n", __FILE__, __LINE__, e.xselectionrequest.requestor);
1227 fprintf(stderr, "DnD %s:%d: Selection atom = %s\n", __FILE__, __LINE__, GetAtomName(server.dsp, e.xselection.selection));
1228 fprintf(stderr, "DnD %s:%d: Target atom = %s\n", __FILE__, __LINE__, GetAtomName(server.dsp, target));
1229 fprintf(stderr, "DnD %s:%d: Property atom = %s\n", __FILE__, __LINE__, GetAtomName(server.dsp, e.xselection.property));
1230
1231 if (e.xselection.property != None && dnd_launcher_exec) {
1232 Property prop = read_property(server.dsp, dnd_target_window, dnd_selection);
1233
1234 //If we're being given a list of targets (possible conversions)
1235 if (target == server.atom.TARGETS && !dnd_sent_request) {
1236 dnd_sent_request = 1;
1237 dnd_atom = pick_target_from_targets(server.dsp, prop);
1238
1239 if (dnd_atom == None) {
1240 fprintf(stderr, "No matching datatypes.\n");
1241 } else {
1242 //Request the data type we are able to select
1243 fprintf(stderr, "Now requsting type %s", GetAtomName(server.dsp, dnd_atom));
1244 XConvertSelection(server.dsp, dnd_selection, dnd_atom, dnd_selection, dnd_target_window, CurrentTime);
1245 }
1246 } else if (target == dnd_atom) {
1247 //Dump the binary data
1248 fprintf(stderr, "DnD %s:%d: Data begins:\n", __FILE__, __LINE__);
1249 fprintf(stderr, "--------\n");
1250 int i;
1251 for (i = 0; i < prop.nitems * prop.format/8; i++)
1252 fprintf(stderr, "%c", ((char*)prop.data)[i]);
1253 fprintf(stderr, "--------\n");
1254
1255 int cmd_length = 0;
1256 cmd_length += 1; // (
1257 cmd_length += strlen(dnd_launcher_exec) + 1; // exec + space
1258 cmd_length += 1; // open double quotes
1259 for (i = 0; i < prop.nitems * prop.format/8; i++) {
1260 char c = ((char*)prop.data)[i];
1261 if (c == '\n') {
1262 if (i < prop.nitems * prop.format/8 - 1) {
1263 cmd_length += 3; // close double quotes, space, open double quotes
1264 }
1265 } else if (c == '\r') {
1266 } else {
1267 cmd_length += 1; // 1 character
1268 if (c == '`' || c == '$' || c == '\\') {
1269 cmd_length += 1; // escape with one backslash
1270 }
1271 }
1272 }
1273 cmd_length += 1; // close double quotes
1274 cmd_length += 2; // &)
1275 cmd_length += 1; // terminator
1276
1277 char *cmd = malloc(cmd_length);
1278 cmd[0] = '\0';
1279 strcat(cmd, "(");
1280 strcat(cmd, dnd_launcher_exec);
1281 strcat(cmd, " \"");
1282 for (i = 0; i < prop.nitems * prop.format/8; i++) {
1283 char c = ((char*)prop.data)[i];
1284 if (c == '\n') {
1285 if (i < prop.nitems * prop.format/8 - 1) {
1286 strcat(cmd, "\" \"");
1287 }
1288 } else if (c == '\r') {
1289 } else {
1290 if (c == '`' || c == '$' || c == '\\') {
1291 strcat(cmd, "\\");
1292 }
1293 char sc[2];
1294 sc[0] = c;
1295 sc[1] = '\0';
1296 strcat(cmd, sc);
1297 }
1298 }
1299 strcat(cmd, "\"");
1300 strcat(cmd, "&)");
1301 fprintf(stderr, "DnD %s:%d: Running command: %s\n", __FILE__, __LINE__, cmd);
1302 tint_exec(cmd);
1303 free(cmd);
1304
1305 // Reply OK.
1306 XClientMessageEvent m;
1307 memset(&m, sizeof(m), 0);
1308 m.type = ClientMessage;
1309 m.display = server.dsp;
1310 m.window = dnd_source_window;
1311 m.message_type = server.atom.XdndFinished;
1312 m.format = 32;
1313 m.data.l[0] = dnd_target_window;
1314 m.data.l[1] = 1;
1315 m.data.l[2] = server.atom.XdndActionCopy; //We only ever copy.
1316 XSendEvent(server.dsp, dnd_source_window, False, NoEventMask, (XEvent*)&m);
1317 XSync(server.dsp, False);
1318 }
1319
1320 XFree(prop.data);
1321 }
1322
1323 break;
1324 }
1325
1326 default:
1327 if (e.type == XDamageNotify+damage_event) {
1328 // union needed to avoid strict-aliasing warnings by gcc
1329 union { XEvent e; XDamageNotifyEvent de; } event_union = {.e=e};
1330 TrayWindow *traywin;
1331 GSList *l;
1332 XDamageNotifyEvent* de = &event_union.de;
1333 for (l = systray.list_icons; l ; l = l->next) {
1334 traywin = (TrayWindow*)l->data;
1335 if ( traywin->id == de->drawable ) {
1336 systray_render_icon(traywin);
1337 break;
1338 }
1339 }
1340 }
1341 }
1342 }
1343 }
1344
1345 callback_timeout_expired();
1346
1347 if (signal_pending) {
1348 cleanup();
1349 if (signal_pending == SIGUSR1) {
1350 // restart tint2
1351 // SIGUSR1 used when : user's signal, composite manager stop/start or xrandr
1352 FD_CLR (x11_fd, &fdset); // not sure if needed
1353 goto start;
1354 }
1355 else {
1356 // SIGINT, SIGTERM, SIGHUP
1357 return 0;
1358 }
1359 }
1360 }
1361 }
1362
1363
This page took 0.089245 seconds and 3 git commands to generate.