1 /* -*- tab-width: 4; c-basic-offset: 4; -*- */
2 /*
3 * xxkb.c
4 *
5 * Main module of the xxkb program.
6 *
7 * Copyright (c) 1999-2003, by Ivan Pascal <pascal@tsu.ru>
8 */
9
10 #include <stdio.h>
11 #include <err.h>
12
13 #include <X11/Xlib.h>
14 #include <X11/Xutil.h>
15 #include <X11/XKBlib.h>
16 #include <X11/Xlibint.h>
17 #include <X11/Xatom.h>
18
19 #include "xxkb.h"
20 #include "wlist.h"
21
22 #ifdef XT_RESOURCE_SEARCH
23 #include <X11/IntrinsicP.h>
24 static XtAppContext app_cont;
25 #endif
26
27 #ifdef SHAPE_EXT
28 #include <X11/extensions/shape.h>
29 #endif
30
31 #define BASE(w) (w & base_mask)
32 #define APPNAME "XXkb"
33
34 #define button_update(win, elem, gc, grp) win_update((win), (elem), (gc), (grp), 0, 0)
35
36 /* Global variables */
37 Display *dpy;
38 #ifdef SHAPE_EXT
39 Bool shape_ext = False;
40 #endif
41 /* Local variables */
42 static int win_x = 0, win_y = 0, revert, base_mask;
43 static GC gc;
44 static XXkbConfig conf;
45 static Window root_win, main_win, icon, focused_win;
46 static Atom systray_selection_atom, take_focus_atom, wm_del_win_atom, wm_manager_atom, xembed_atom, xembed_info_atom, utf8_string_atom, net_wm_name_atom, net_window_type_atom;
47 static WInfo def_info, *info;
48 static kbdState def_state;
49 static XErrorHandler DefErrHandler;
50
51 /* Forward function declarations */
52 static ListAction GetWindowAction(Window w);
53 static MatchType GetTypeFromState(unsigned int state);
54 static Window GetSystray(Display *dpy);
55 static Window GetGrandParent(Window w);
56 static char* GetWindowIdent(Window appwin, MatchType type);
57 static void MakeButton(WInfo *info, Window parent);
58 static void IgnoreWindow(WInfo *info, MatchType type);
59 static void DockWindow(Display *dpy, Window systray, Window w);
60 static void MoveOrigin(Display *dpy, Window w, int *w_x, int *w_y);
61 static void SendDockMessage(Display *dpy, Window w, long message, long data1, long data2, long data3);
62 static void Reset(void);
63 static void Terminate(void);
64 static void DestroyPixmaps(XXkbElement *elem);
65
66 static void GetAppWindow(Window w, Window *app);
67 static WInfo* AddWindow(Window w, Window parent);
68 static void AdjustWindowPos(Display *dpy, Window win, Window parent, Bool set_gravity);
69 static Bool ExpectInput(Window win);
70
71 int
main(int argc,char ** argv)72 main(int argc, char ** argv)
73 {
74 int xkbEventType, xkbError, reason_rtrn, mjr, mnr, scr;
75 #ifdef SHAPE_EXT
76 int shape_event_base, shape_error_base;
77 #endif
78 Bool fout_flag;
79 Window systray = None;
80 Geometry geom;
81 XkbEvent ev;
82 XWMHints *wm_hints;
83 XSizeHints *size_hints;
84 XClassHint *class_hints;
85 XSetWindowAttributes win_attr;
86 char *display_name, buf[64];
87 unsigned long valuemask, xembed_info[2] = { 0, 1 };
88 XGCValues values;
89
90 /* Lets begin */
91 display_name = NULL;
92 mjr = XkbMajorVersion;
93 mnr = XkbMinorVersion;
94 dpy = XkbOpenDisplay(display_name, &xkbEventType, &xkbError, &mjr, &mnr, &reason_rtrn);
95 if (dpy == NULL) {
96 warnx("Can't open display named %s", XDisplayName(display_name));
97 switch (reason_rtrn) {
98 case XkbOD_BadLibraryVersion :
99 case XkbOD_BadServerVersion :
100 warnx("xxkb was compiled with XKB version %d.%02d",
101 XkbMajorVersion, XkbMinorVersion);
102 warnx("But %s uses incompatible version %d.%02d",
103 reason_rtrn == XkbOD_BadLibraryVersion ? "Xlib" : "Xserver",
104 mjr, mnr);
105 break;
106
107 case XkbOD_ConnectionRefused :
108 warnx("Connection refused");
109 break;
110
111 case XkbOD_NonXkbServer:
112 warnx("XKB extension not present");
113 break;
114
115 default:
116 warnx("Unknown error %d from XkbOpenDisplay", reason_rtrn);
117 break;
118 }
119 exit(1);
120 }
121
122 scr = DefaultScreen(dpy);
123 root_win = RootWindow(dpy, scr);
124 base_mask = ~(dpy->resource_mask);
125 sprintf(buf, "_NET_SYSTEM_TRAY_S%d", scr);
126 systray_selection_atom = XInternAtom(dpy, buf, False);
127 take_focus_atom = XInternAtom(dpy, "WM_TAKE_FOCUS", False);
128 wm_del_win_atom = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
129 wm_manager_atom = XInternAtom(dpy, "MANAGER", False);
130 xembed_atom = XInternAtom(dpy, "_XEMBED", False);
131 xembed_info_atom = XInternAtom(dpy, "_XEMBED_INFO", False);
132 net_wm_name_atom = XInternAtom(dpy, "_NET_WM_NAME", False);
133 net_window_type_atom = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False);
134 utf8_string_atom = XInternAtom(dpy, "UTF8_STRING", False);
135
136 DefErrHandler = XSetErrorHandler((XErrorHandler) ErrHandler);
137
138 #ifdef XT_RESOURCE_SEARCH
139 app_cont = XtCreateApplicationContext();
140 XtDisplayInitialize(app_cont, dpy, APPNAME, APPNAME, NULL, 0, &argc, argv);
141 #endif
142 /* Do we have SHAPE extension */
143 #ifdef SHAPE_EXT
144 shape_ext = XShapeQueryExtension(dpy, &shape_event_base, &shape_error_base);
145 #endif
146 /* My configuration*/
147 memset(&conf, 0, sizeof(conf));
148 if (GetConfig(dpy, &conf) != 0) {
149 errx(2, "Unable to initialize");
150 }
151 /* My MAIN window */
152 geom = conf.mainwindow.geometry;
153 if (conf.controls & Main_enable) {
154 if (geom.mask & (XNegative | YNegative)) {
155 int x, y;
156 unsigned int width, height, border, dep;
157 Window rwin;
158 XGetGeometry(dpy, root_win, &rwin, &x, &y, &width, &height, &border, &dep);
159 if (geom.mask & XNegative) {
160 geom.x = width + geom.x - geom.width;
161 }
162 if (geom.mask & YNegative) {
163 geom.y = height + geom.y - geom.height;
164 }
165 }
166 }
167 else {
168 geom.x = geom.y = 0;
169 geom.width = geom.height = 1;
170 conf.mainwindow.border_width = 0;
171 }
172
173 valuemask = 0;
174 memset(&win_attr, 0, sizeof(win_attr));
175 win_attr.background_pixmap = ParentRelative;
176 win_attr.border_pixel = conf.mainwindow.border_color;
177 valuemask = CWBackPixmap | CWBorderPixel;
178 if (conf.controls & Main_ontop) {
179 win_attr.override_redirect = True;
180 valuemask |= CWOverrideRedirect;
181 }
182
183 main_win = XCreateWindow(dpy, root_win,
184 geom.x, geom.y, geom.width, geom.height,
185 conf.mainwindow.border_width,
186 CopyFromParent, InputOutput,
187 CopyFromParent, valuemask,
188 &win_attr);
189
190 XStoreName(dpy, main_win, APPNAME);
191 XSetCommand(dpy, main_win, argv, argc);
192
193 /* WMHints */
194 wm_hints = XAllocWMHints();
195 if (wm_hints == NULL) {
196 errx(1, "Unable to allocate WM hints");
197 }
198 wm_hints->window_group = main_win;
199 wm_hints->input = False;
200 wm_hints->flags = InputHint | WindowGroupHint;
201 XSetWMHints(dpy, main_win, wm_hints);
202 XFree(wm_hints);
203
204 /* ClassHints */
205 class_hints = XAllocClassHint();
206 if (class_hints == NULL) {
207 errx(1, "Unable to allocate class hints");
208 }
209 class_hints->res_name = APPNAME;
210 class_hints->res_class = APPNAME;
211 XSetClassHint(dpy, main_win, class_hints);
212 XFree(class_hints);
213
214 /* SizeHints */
215 size_hints = XAllocSizeHints();
216 if (size_hints == NULL) {
217 errx(1, "Unable to allocate size hints");
218 }
219 if (geom.mask & (XValue | YValue)) {
220 size_hints->x = geom.x;
221 size_hints->y = geom.y;
222 size_hints->flags = USPosition;
223 }
224 size_hints->base_width = size_hints->min_width =
225 size_hints->max_width = geom.width;
226 size_hints->base_height = size_hints->min_height =
227 size_hints->max_height = geom.height;
228 size_hints->flags |= PBaseSize | PMinSize | PMaxSize;
229 XSetNormalHints(dpy, main_win, size_hints);
230 XFree(size_hints);
231
232 /* to fix: fails if mainwindow geometry was not read */
233 XSetWMProtocols(dpy, main_win, &wm_del_win_atom, 1);
234 XChangeProperty(dpy, main_win, net_wm_name_atom, utf8_string_atom,
235 8, PropModeReplace,
236 (unsigned char*) APPNAME, strlen(APPNAME));
237 XChangeProperty(dpy, main_win, xembed_info_atom, xembed_info_atom, 32, 0,
238 (unsigned char *) &xembed_info, 2);
239
240 /* Show window ? */
241 if (conf.controls & WMaker) {
242 icon = XCreateSimpleWindow(dpy, main_win, geom.x, geom.y,
243 geom.width, geom.height, 0,
244 BlackPixel(dpy, scr), WhitePixel(dpy, scr));
245
246 wm_hints = XGetWMHints(dpy, main_win);
247 if (wm_hints != NULL) {
248 wm_hints->icon_window = icon;
249 wm_hints->initial_state = WithdrawnState;
250 wm_hints->flags |= StateHint | IconWindowHint;
251 XSetWMHints(dpy, main_win, wm_hints);
252 XFree(wm_hints);
253 }
254 }
255 else {
256 icon = None;
257 }
258
259 if (conf.controls & Main_tray) {
260 Atom atom;
261 int data = 1;
262
263 atom = XInternAtom(dpy, "KWM_DOCKWINDOW", False);
264 if (atom != None) {
265 XChangeProperty(dpy, main_win, atom, atom, 32, 0,
266 (unsigned char*) &data, 1);
267 }
268
269 atom = XInternAtom(dpy, "_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR", False);
270 if (atom != None) {
271 XChangeProperty(dpy, main_win, atom, XA_WINDOW, 32, 0,
272 (unsigned char*) &data, 1);
273 }
274
275 systray = GetSystray(dpy);
276 if (systray != None) {
277 DockWindow(dpy, systray, main_win);
278 }
279 /* Don't show main window */
280 conf.controls &= ~Main_enable;
281 }
282
283 /* What events do we want */
284 XkbSelectEventDetails(dpy, XkbUseCoreKbd, XkbStateNotify,
285 XkbAllStateComponentsMask, XkbGroupStateMask);
286 if (conf.controls & When_create) {
287 XSelectInput(dpy, root_win, StructureNotifyMask | SubstructureNotifyMask);
288 }
289
290 XSelectInput(dpy, main_win, ExposureMask | ButtonPressMask | ButtonMotionMask | ButtonReleaseMask);
291 if (icon) {
292 XSelectInput(dpy, icon, ExposureMask | ButtonPressMask);
293 }
294
295 valuemask = 0;
296 memset(&values, 0, sizeof(XGCValues));
297 gc = XCreateGC(dpy, main_win, valuemask, &values);
298
299 /* Set current defaults */
300 def_state.group = conf.Base_group;
301 def_state.alt = conf.Alt_group;
302
303 def_info.win = icon ? icon : main_win;
304 def_info.button = None;
305 def_info.state = def_state;
306
307 Reset();
308
309 if (conf.controls & When_start) {
310 Window rwin, parent, *children, *child, app;
311 int num;
312
313 XQueryTree(dpy, root_win, &rwin, &parent, &children, &num);
314 child = children;
315 while (num > 0) {
316 app = None;
317 GetAppWindow(*child, &app);
318 if (app != None && app != main_win && app != icon) {
319 AddWindow(app, *child);
320 }
321 child++;
322 num--;
323 }
324
325 if (children != None)
326 XFree(children);
327
328 XGetInputFocus(dpy, &focused_win, &revert);
329 info = win_find(focused_win);
330 if (info == NULL) {
331 info = &def_info;
332 }
333 }
334 /* Show main window */
335 if (conf.controls & Main_enable) {
336 XMapWindow(dpy, main_win);
337 }
338
339 /* Main Loop */
340 fout_flag = False;
341 while (1) {
342 int grp;
343 static int move_window = 0;
344 static int add_x = 0, add_y = 0;
345
346 XNextEvent(dpy, &ev.core);
347
348 if (ev.type == xkbEventType) {
349 switch (ev.any.xkb_type) {
350 case XkbStateNotify :
351 grp = ev.state.locked_group;
352
353 if ((conf.controls & When_change) && !fout_flag) {
354 XGetInputFocus(dpy, &focused_win, &revert);
355 if (focused_win == None || focused_win == PointerRoot)
356 break;
357 if (focused_win != info->win) {
358 WInfo *temp_info;
359 temp_info = AddWindow(focused_win, focused_win);
360 if (temp_info != NULL) {
361 info = temp_info;
362 info->state.group = grp;
363 }
364 }
365 }
366 fout_flag = False;
367
368 if ((conf.controls & Two_state) && ev.state.keycode) {
369 int g_min, g_max;
370 if (conf.Base_group < info->state.alt) {
371 g_min = conf.Base_group;
372 g_max = info->state.alt;
373 }
374 else {
375 g_max = conf.Base_group;
376 g_min = info->state.alt;
377 }
378
379 if ((grp > g_min) && (grp < g_max)) {
380 XkbLockGroup(dpy, XkbUseCoreKbd, g_max);
381 break;
382 }
383
384 if ((grp < g_min) || (grp > g_max)) {
385 XkbLockGroup(dpy, XkbUseCoreKbd, g_min);
386 break;
387 }
388 }
389
390 info->state.group = grp;
391 if ((conf.controls & Two_state) &&
392 (grp != conf.Base_group) && (grp != info->state.alt)) {
393 info->state.alt = grp;
394 }
395
396 if (info->button != None) {
397 button_update(info->button, &conf.button, gc, grp);
398 }
399 win_update(main_win, &conf.mainwindow, gc, grp, win_x, win_y);
400 if (icon != None) {
401 win_update(icon, &conf.mainwindow, gc, grp, win_x, win_y);
402 }
403 if (conf.controls & Bell_enable) {
404 XBell(dpy, conf.Bell_percent);
405 }
406 break;
407
408 default:
409 break;
410 }
411 } /* xkb events */
412 else {
413 Window temp_win;
414 WInfo *temp_info;
415 XClientMessageEvent *cmsg_evt;
416 XReparentEvent *repar_evt;
417 XButtonEvent *btn_evt;
418 XMotionEvent *mov_evt;
419
420 switch (ev.type) { /* core events */
421 case Expose: /* Update our window or button */
422 if (ev.core.xexpose.count != 0)
423 break;
424
425 temp_win = ev.core.xexpose.window;
426 if (temp_win == main_win) {
427 MoveOrigin(dpy, main_win, &win_x, &win_y);
428 }
429 if (temp_win == main_win || (icon && (temp_win == icon))) {
430 win_update(temp_win, &conf.mainwindow, gc, info->state.group, win_x, win_y);
431 }
432 else {
433 temp_info = button_find(temp_win);
434 if (temp_info != NULL) {
435 button_update(temp_win, &conf.button, gc, temp_info->state.group);
436 }
437 }
438 break;
439
440 case ButtonPress:
441 btn_evt = &ev.core.xbutton;
442 temp_win = btn_evt->window;
443 switch (btn_evt->button) {
444 case Button1:
445 if (btn_evt->state & ControlMask) {
446 int root_x, root_y, mask;
447 Window root, child;
448
449 move_window = 1;
450 XQueryPointer(dpy, temp_win, &root, &child, &root_x, &root_y, &add_x, &add_y, &mask);
451 break;
452 }
453
454 if (temp_win == info->button || temp_win == main_win ||
455 (icon != None && temp_win == icon)) {
456 if (conf.controls & Two_state) {
457 if (info->state.group == conf.Base_group) {
458 XkbLockGroup(dpy, XkbUseCoreKbd, info->state.alt);
459 }
460 else {
461 XkbLockGroup(dpy, XkbUseCoreKbd, conf.Base_group);
462 }
463 }
464 else {
465 if (conf.controls & But1_reverse) {
466 XkbLockGroup(dpy, XkbUseCoreKbd, info->state.group - 1);
467 }
468 else {
469 XkbLockGroup(dpy, XkbUseCoreKbd, info->state.group + 1);
470 }
471 }
472 }
473 break;
474
475 case Button3:
476 if (temp_win == info->button || temp_win == main_win ||
477 (icon != None && temp_win == icon)) {
478 if (conf.controls & But3_reverse) {
479 XkbLockGroup(dpy, XkbUseCoreKbd, info->state.group - 1);
480 }
481 else {
482 XkbLockGroup(dpy, XkbUseCoreKbd, info->state.group + 1);
483 }
484 }
485 break;
486
487 case Button2:
488 if (temp_win != main_win && temp_win != icon) {
489 if (conf.controls & Button_delete) {
490 XDestroyWindow(dpy, temp_win);
491 if (conf.controls & Forget_window) {
492 MatchType type;
493
494 type = GetTypeFromState(btn_evt->state);
495 if (type == -1)
496 break;
497
498 if (temp_win == info->button) {
499 IgnoreWindow(info, type);
500 Reset();
501 } else {
502 temp_info = button_find(temp_win);
503 if (temp_info == NULL)
504 break;
505
506 IgnoreWindow(temp_info, type);
507 }
508 }
509 }
510 break;
511 }
512
513 if (conf.controls & Main_delete) {
514 Terminate();
515 }
516 break;
517
518 case Button4:
519 if (temp_win == info->button || temp_win == main_win ||
520 (icon != None && temp_win == icon)) {
521 XkbLockGroup(dpy, XkbUseCoreKbd, info->state.group - 1);
522 }
523 break;
524
525 case Button5:
526 if (temp_win == info->button || temp_win == main_win ||
527 (icon != None && temp_win == icon)) {
528 XkbLockGroup(dpy, XkbUseCoreKbd, info->state.group + 1);
529 }
530 break;
531 }
532 break;
533
534 case MotionNotify:
535 if (move_window != 0) {
536 int x, y;
537 Window child;
538
539 mov_evt = &ev.core.xmotion;
540 temp_win = mov_evt->window;
541
542 /* Don't move window in systray */
543 if (temp_win == main_win && (conf.controls & Main_tray)) {
544 break;
545 }
546
547 temp_info = button_find(temp_win);
548 if (temp_info != NULL) {
549 XTranslateCoordinates(dpy, mov_evt->root, temp_info->parent,
550 mov_evt->x_root, mov_evt->y_root,
551 &x, &y, &child);
552 }
553 else {
554 x = mov_evt->x_root;
555 y = mov_evt->y_root;
556 }
557 XMoveWindow(dpy, temp_win, x - add_x, y - add_y);
558 }
559 break;
560
561 case ButtonRelease:
562 if (move_window != 0) {
563 btn_evt = &ev.core.xbutton;
564 temp_win = btn_evt->window;
565
566 temp_info = button_find(temp_win);
567 if (temp_info != NULL) {
568 AdjustWindowPos(dpy, temp_win, temp_info->parent, True);
569 }
570 }
571 move_window = 0;
572 break;
573
574 case FocusIn:
575 info = win_find(ev.core.xfocus.window);
576 if (info == NULL) {
577 warnx("Oops. FocusEvent from unknown window");
578 info = &def_info;
579 }
580
581 if (info->ignore == 0) {
582 XkbLockGroup(dpy, XkbUseCoreKbd, info->state.group);
583 }
584 else {
585 def_info.state.group = info->state.group;
586 info = &def_info;
587 }
588 break;
589
590 case FocusOut:
591 if (conf.controls & Focus_out) {
592 temp_info = info;
593 info = &def_info;
594 info->state.group = conf.Base_group; /*???*/
595 if (temp_info->state.group != conf.Base_group) {
596 fout_flag = True;
597 XkbLockGroup(dpy, XkbUseCoreKbd, info->state.group);
598 }
599 }
600 break;
601
602 case ReparentNotify:
603 repar_evt = &ev.core.xreparent;
604 temp_win = repar_evt->window;
605 if (temp_win != main_win && temp_win != icon &&
606 repar_evt->parent != root_win &&
607 BASE(repar_evt->parent) != BASE(temp_win) &&
608 repar_evt->override_redirect != True) {
609 AddWindow(temp_win, repar_evt->parent);
610 }
611 break;
612
613 case DestroyNotify:
614 if (ev.core.xdestroywindow.event == root_win)
615 break;
616
617 temp_win = ev.core.xdestroywindow.window;
618 temp_info = win_find(temp_win);
619 if (temp_info != NULL) {
620 win_free(temp_win);
621 if (temp_info == info) {
622 Reset();
623 }
624 break;
625 }
626
627 temp_info = button_find(temp_win);
628 if (temp_info != NULL) {
629 temp_info->button = None;
630 }
631 break;
632
633 case ConfigureNotify:
634 /* Buttons are not enabled */
635 if (!(conf.controls & Button_enable)) {
636 break;
637 }
638
639 /* Adjust position of the button, if necessary */
640 temp_win = ev.core.xconfigure.window;
641 temp_info = win_find(temp_win);
642 if (temp_info != NULL) {
643 AdjustWindowPos(dpy, temp_info->button, temp_info->parent, False);
644 }
645
646 /* Raise window, if necessary */
647 temp_win = ev.core.xconfigure.above;
648
649 if (temp_win == None) {
650 break;
651 }
652
653 temp_info = button_find(temp_win);
654 if (temp_info != NULL) {
655 XRaiseWindow(dpy, temp_win);
656 }
657 break;
658
659 case PropertyNotify:
660 temp_win = ev.core.xproperty.window;
661 if (temp_win == main_win || temp_win == icon) {
662 break;
663 }
664
665 temp_info = win_find(temp_win);
666 if (temp_info == NULL) {
667 Window rwin, parent, *children;
668 int num;
669
670 XQueryTree(dpy, temp_win, &rwin, &parent, &children, &num);
671 AddWindow(temp_win, parent);
672
673 if (children != None) {
674 XFree(children);
675 }
676 }
677 break;
678
679 case ClientMessage:
680 cmsg_evt = &ev.core.xclient;
681 temp_win = cmsg_evt->window;
682 if (cmsg_evt->message_type != None && cmsg_evt->format == 32) {
683 if (cmsg_evt->message_type == wm_manager_atom
684 && cmsg_evt->data.l[1] == systray_selection_atom
685 && systray == None) {
686 systray = cmsg_evt->data.l[2];
687 DockWindow(dpy, systray, main_win);
688 }
689 else if (cmsg_evt->message_type == xembed_atom &&
690 temp_win == main_win && cmsg_evt->data.l[1] == 0) {
691 /* XEMBED_EMBEDDED_NOTIFY */
692 MoveOrigin(dpy, main_win, &win_x, &win_y);
693 win_update(main_win, &conf.mainwindow, gc, info->state.group, win_x, win_y);
694 }
695 else if ((temp_win == main_win || temp_win == icon)
696 && cmsg_evt->data.l[0] == wm_del_win_atom) {
697 Terminate();
698 }
699 }
700 break;
701
702 case CreateNotify:
703 case NoExpose:
704 case UnmapNotify:
705 case MapNotify:
706 case GravityNotify:
707 case MappingNotify:
708 case GraphicsExpose:
709 /* Ignore these events */
710 break;
711
712 default:
713 warnx("Unknown event %d", ev.type);
714 break;
715 }
716 }
717 }
718
719 return(0);
720 }
721
722 static void
AdjustWindowPos(Display * dpy,Window win,Window parent,Bool set_gravity)723 AdjustWindowPos(Display *dpy, Window win, Window parent, Bool set_gravity)
724 {
725 Window rwin;
726 int x, y, x1, y1;
727 unsigned int w, h, w1, h1, bd, dep;
728 XSetWindowAttributes attr;
729
730 XGetGeometry(dpy, parent, &rwin, &x1, &y1, &w1, &h1, &bd, &dep);
731 XGetGeometry(dpy, win, &rwin, &x, &y, &w, &h, &bd, &dep);
732 /* Normalize coordinates */
733 x1 = (x < 0) ? 0 : ((x+w+2*bd) > w1) ? w1-w-2*bd : x;
734 y1 = (y < 0) ? 0 : ((y+h+2*bd) > h1) ? h1-h-2*bd : y;
735
736 /* Adjust window position, if necessary */
737 if (x != x1 || y != y1) {
738 XMoveWindow(dpy, win, x1, y1);
739 }
740
741 if (set_gravity == False) {
742 return;
743 }
744
745 /* Adjus gravity of the window */
746 memset(&attr, 0, sizeof(attr));
747
748 switch(3*((3*x1)/w1) + (3*y1)/h1) {
749 case 0:
750 attr.win_gravity = NorthWestGravity;
751 break;
752 case 1:
753 attr.win_gravity = WestGravity;
754 break;
755 case 2:
756 attr.win_gravity = SouthWestGravity;
757 break;
758 case 3:
759 attr.win_gravity = NorthGravity;
760 break;
761 case 4:
762 attr.win_gravity = CenterGravity;
763 break;
764 case 5:
765 attr.win_gravity = SouthGravity;
766 break;
767 case 6:
768 attr.win_gravity = NorthEastGravity;
769 break;
770 case 7:
771 attr.win_gravity = EastGravity;
772 break;
773 case 8:
774 attr.win_gravity = SouthEastGravity;
775 break;
776 default:
777 /* warnx("No gravity set"); */
778 return;
779 }
780 XChangeWindowAttributes(dpy, win, CWWinGravity, &attr);
781 }
782
783 static void
Reset()784 Reset()
785 {
786 info = &def_info;
787 info->state = def_state;
788 XkbLockGroup(dpy, XkbUseCoreKbd, conf.Base_group);
789 }
790
791 static void
Terminate()792 Terminate()
793 {
794 win_free_list();
795 XFreeGC(dpy, gc);
796
797 DestroyPixmaps(&conf.mainwindow);
798 DestroyPixmaps(&conf.button);
799
800 if (icon != None) {
801 XDestroyWindow(dpy, icon);
802 }
803 if (main_win != None) {
804 XDestroyWindow(dpy, main_win);
805 }
806
807 #ifdef XT_RESOURCE_SEARCH
808 XtCloseDisplay(dpy);
809 #else
810 XCloseDisplay(dpy);
811 #endif
812
813 exit(0);
814 }
815
816 static void
DestroyPixmaps(XXkbElement * elem)817 DestroyPixmaps(XXkbElement *elem)
818 {
819 int i = 0;
820 for (i = 0; i < MAX_GROUP; i++) {
821 if (elem->pictures[i] != None) {
822 XFreePixmap(dpy, elem->pictures[i]);
823 }
824 if (elem->shapemask[i] != None) {
825 XFreePixmap(dpy, elem->shapemask[i]);
826 }
827 #ifdef SHAPE_EXT
828 if (elem->boundmask[i] != None) {
829 XFreePixmap(dpy, elem->boundmask[i]);
830 }
831 #endif
832 }
833 }
834
835 static WInfo*
AddWindow(Window win,Window parent)836 AddWindow(Window win, Window parent)
837 {
838 int ignore = 0;
839 WInfo *info;
840 ListAction action;
841 XWindowAttributes attr;
842
843 /* properties can be unsuitable at this moment so we need to have
844 a posibility to reconsider when they will be changed */
845 XSelectInput(dpy, win, PropertyChangeMask);
846
847 /* don't deal with windows that never get a focus */
848 if (!ExpectInput(win)) {
849 return NULL;
850 }
851
852 action = GetWindowAction(win);
853 if (((action & Ignore) && !(conf.controls & Ignore_reverse)) ||
854 (!(action & Ignore) && (conf.controls & Ignore_reverse))) {
855 ignore = 1;
856 }
857
858 info = win_find(win);
859 if (info == NULL) {
860 info = win_add(win, &def_state);
861 if (info == NULL) {
862 return NULL;
863 }
864 if (action & AltGrp) {
865 info->state.alt = action & GrpMask;
866 }
867 if (action & InitAltGrp) {
868 info->state.group = info->state.alt;
869 }
870 }
871
872 XGetInputFocus(dpy, &focused_win, &revert);
873 XSelectInput(dpy, win, FocusChangeMask | StructureNotifyMask | PropertyChangeMask);
874 if (focused_win == win) {
875 XFocusChangeEvent focused_evt;
876 focused_evt.type = FocusIn;
877 focused_evt.display = dpy;
878 focused_evt.window = win;
879 XSendEvent(dpy, main_win, 0, 0, (XEvent *) &focused_evt);
880 }
881
882 info->ignore = ignore;
883 if ((conf.controls & Button_enable) && (!info->button) && !ignore) {
884 MakeButton(info, parent);
885 }
886
887 /* make sure that window still exists */
888 if (XGetWindowAttributes(dpy, win, &attr) == 0) {
889 /* failed */
890 win_free(win);
891 return NULL;
892 }
893 return info;
894 }
895
896 static void
MakeButton(WInfo * info,Window parent)897 MakeButton(WInfo *info, Window parent)
898 {
899 Window button, rwin;
900 int x, y;
901 unsigned int width, height, border, dep;
902 XSetWindowAttributes butt_attr;
903 Geometry geom = conf.button.geometry;
904
905 parent = GetGrandParent(parent);
906 if (parent == None) {
907 return;
908 }
909
910 XGetGeometry(dpy, parent, &rwin, &x, &y, &width, &height, &border, &dep);
911 x = (geom.mask & XNegative) ? width + geom.x - geom.width : geom.x;
912 y = (geom.mask & YNegative) ? height + geom.y - geom.height : geom.y;
913
914 if ((geom.width > width) || (geom.height > height)) {
915 return;
916 }
917
918 memset(&butt_attr, 0, sizeof(butt_attr));
919 butt_attr.background_pixmap = ParentRelative;
920 butt_attr.border_pixel = conf.button.border_color;
921 butt_attr.override_redirect = True;
922 butt_attr.win_gravity = geom.gravity;
923
924 button = XCreateWindow(dpy, parent,
925 x, y,
926 geom.width, geom.height, conf.button.border_width,
927 CopyFromParent, InputOutput, CopyFromParent,
928 CWWinGravity | CWOverrideRedirect | CWBackPixmap | CWBorderPixel,
929 &butt_attr);
930
931 XSelectInput(dpy, parent, SubstructureNotifyMask);
932 XSelectInput(dpy, button, ExposureMask | ButtonPressMask | ButtonMotionMask | ButtonReleaseMask);
933 XMapRaised(dpy, button);
934
935 info->parent = parent;
936 info->button = button;
937 }
938
939
940 /*
941 * GetGrandParent
942 * Returns
943 * highest-level parent of the window. The one which is a child of the
944 * root window.
945 */
946
947 static Window
GetGrandParent(Window w)948 GetGrandParent(Window w)
949 {
950 Window rwin, parent, *children;
951 int num;
952
953 while (1) {
954 if (!XQueryTree(dpy, w, &rwin, &parent, &children, &num)) {
955 return None;
956 }
957
958 if (children != None)
959 XFree(children);
960
961 if (parent == rwin) {
962 return w;
963 }
964 w = parent;
965 }
966 }
967
968 static void
GetAppWindow(Window win,Window * core)969 GetAppWindow(Window win, Window *core)
970 {
971 Window rwin, parent, *children, *child;
972 int num;
973
974 if (!XQueryTree(dpy, win, &rwin, &parent, &children, &num)) {
975 return;
976 }
977 child = children;
978
979 while (num != 0) {
980 if (BASE(*child) != BASE(win)) {
981 *core = *child;
982 break;
983 }
984 GetAppWindow(*child, core);
985 if (*core)
986 break;
987 child++;
988 num--;
989 }
990
991 if (children != None)
992 XFree(children);
993 }
994
995 static Bool
Compare(char * pattern,char * str)996 Compare(char *pattern, char *str)
997 {
998 char *i = pattern, *j = str, *sub = i, *lpos = j;
999 Bool aster = False;
1000
1001 do {
1002 if (*i == '*') {
1003 i++;
1004 if (*i == '\0') {
1005 return True;
1006 }
1007 aster = True; sub = i; lpos = j;
1008 continue;
1009 }
1010 if (*i == *j) {
1011 i++; j++;
1012 continue;
1013 }
1014 if (*j == '\0') {
1015 return False;
1016 }
1017
1018 if (aster) {
1019 j = ++lpos;
1020 i = sub;
1021 } else {
1022 return False;
1023 }
1024 } while (*j || *i);
1025
1026 return ((*i == *j) ? True : False);
1027 }
1028
1029 static ListAction
searchInList(SearchList * list,char * ident)1030 searchInList(SearchList *list, char *ident)
1031 {
1032 ListAction ret = 0;
1033 int i;
1034
1035 while (list != NULL) {
1036 for (i = 0; i < list->num; i++) {
1037 if (Compare(list->idx[i], ident)) {
1038 ret |= list->action; /*???*/
1039 break;
1040 }
1041 }
1042 list = list->next;
1043 }
1044
1045 return ret;
1046 }
1047
1048 static ListAction
searchInPropList(SearchList * list,Window win)1049 searchInPropList(SearchList *list, Window win)
1050 {
1051 ListAction ret = 0;
1052 int i, j, prop_num;
1053 Atom *props, *atoms;
1054
1055 while (list != NULL) {
1056 atoms = (Atom *) calloc(list->num, sizeof(Atom));
1057 if (atoms == NULL) {
1058 return 0;
1059 }
1060
1061 XInternAtoms(dpy, list->idx, list->num, False, atoms);
1062 for (i = 0, j = 0; i < list->num; i++) {
1063 if (atoms[i] != None) {
1064 j = 1;
1065 break;
1066 }
1067 }
1068 if (j != 0) {
1069 props = XListProperties(dpy, win, &prop_num);
1070 if (props != NULL) {
1071 for (i = 0; i < list->num; i++) {
1072 if (atoms[i] != None) {
1073 for (j = 0; j < prop_num; j++) {
1074 if (atoms[i] == props[j]) {
1075 ret |= list->action;
1076 }
1077 }
1078 }
1079 }
1080 XFree(props);
1081 }
1082 }
1083 XFree(atoms);
1084 list = list->next;
1085 }
1086
1087 return ret;
1088 }
1089
1090
1091 /*
1092 * GetWindowAction
1093 * Returns
1094 * an action associated with a window.
1095 */
1096
1097 static ListAction
GetWindowAction(Window w)1098 GetWindowAction(Window w)
1099 {
1100 ListAction ret = 0;
1101 Status stat;
1102 XClassHint wm_class;
1103 char *name;
1104
1105 stat = XGetClassHint(dpy, w, &wm_class);
1106 if (stat != 0) {
1107 /* success */
1108 ret |= searchInList(conf.app_lists[0], wm_class.res_class);
1109 ret |= searchInList(conf.app_lists[1], wm_class.res_name);
1110 XFree(wm_class.res_name);
1111 XFree(wm_class.res_class);
1112 }
1113
1114 stat = XFetchName(dpy, w, &name);
1115 if (stat != 0 && name != NULL) {
1116 /* success */
1117 ret |= searchInList(conf.app_lists[2], name);
1118 XFree(name);
1119 }
1120
1121 ret |= searchInPropList(conf.app_lists[3], w);
1122
1123 return ret;
1124 }
1125
1126 static Bool
ExpectInput(Window w)1127 ExpectInput(Window w)
1128 {
1129 Bool ok = False;
1130 XWMHints *hints;
1131
1132 hints = XGetWMHints(dpy, w);
1133 if (hints != NULL) {
1134 if ((hints->flags & InputHint) && hints->input) {
1135 ok = True;
1136 }
1137 XFree(hints);
1138 }
1139
1140 if (!ok) {
1141 Atom *protocols;
1142 Status stat;
1143 int n, i;
1144
1145 stat = XGetWMProtocols(dpy, w, &protocols, &n);
1146 if (stat != 0) {
1147 /* success */
1148 for (i = 0; i < n; i++) {
1149 if (protocols[i] == take_focus_atom) {
1150 ok = True;
1151 break;
1152 }
1153 }
1154 XFree(protocols);
1155 }
1156 }
1157
1158 return ok;
1159 }
1160
1161
1162 /*
1163 * GetWindowIdent
1164 * Returns
1165 * the window identifier, which should be freed by the caller.
1166 */
1167
1168 static char*
GetWindowIdent(Window appwin,MatchType type)1169 GetWindowIdent(Window appwin, MatchType type)
1170 {
1171 Status stat;
1172 XClassHint wm_class;
1173 char *ident = NULL;
1174
1175 switch (type) {
1176 case WMName:
1177 XFetchName(dpy, appwin, &ident);
1178 break;
1179
1180 default:
1181 stat = XGetClassHint(dpy, appwin, &wm_class);
1182 if (stat != 0) {
1183 /* success */
1184 if (type == WMClassClass) {
1185 XFree(wm_class.res_name);
1186 ident = wm_class.res_class;
1187 } else if (type == WMClassName) {
1188 XFree(wm_class.res_class);
1189 ident = wm_class.res_name;
1190 }
1191 }
1192 break;
1193 }
1194
1195 return ident;
1196 }
1197
1198
1199 /*
1200 * IgnoreWindow
1201 * Appends a window to the ignore list.
1202 */
1203
1204 static void
IgnoreWindow(WInfo * info,MatchType type)1205 IgnoreWindow(WInfo *info, MatchType type)
1206 {
1207 char *ident;
1208
1209 ident = GetWindowIdent(info->win, type);
1210 if (ident == NULL) {
1211 XBell(dpy, conf.Bell_percent);
1212 return;
1213 }
1214
1215 AddAppToIgnoreList(&conf, ident, type);
1216 info->ignore = 1;
1217
1218 XFree(ident);
1219 }
1220
1221 static MatchType
GetTypeFromState(unsigned int state)1222 GetTypeFromState(unsigned int state)
1223 {
1224 MatchType type = -1;
1225
1226 switch (state & (ControlMask | ShiftMask)) {
1227 case 0:
1228 type = -1;
1229 break;
1230
1231 case ControlMask:
1232 type = WMClassClass;
1233 break;
1234
1235 case ShiftMask:
1236 type = WMName;
1237 break;
1238
1239 case ControlMask | ShiftMask:
1240 type = WMClassName;
1241 break;
1242 }
1243
1244 return type;
1245 }
1246
1247 void
ErrHandler(Display * dpy,XErrorEvent * err)1248 ErrHandler(Display *dpy, XErrorEvent *err)
1249 {
1250 switch (err->error_code) {
1251 case BadWindow:
1252 case BadDrawable:
1253 /* Ignore these errors */
1254 break;
1255
1256 default:
1257 (*DefErrHandler)(dpy, err);
1258 break;
1259 }
1260 }
1261
1262 static Window
GetSystray(Display * dpy)1263 GetSystray(Display *dpy)
1264 {
1265 Window systray = None;
1266
1267 XGrabServer(dpy);
1268
1269 systray = XGetSelectionOwner(dpy, systray_selection_atom);
1270 if (systray != None) {
1271 XSelectInput(dpy, systray, StructureNotifyMask | PropertyChangeMask);
1272 }
1273
1274 XUngrabServer(dpy);
1275
1276 XFlush(dpy);
1277
1278 return systray;
1279 }
1280
1281 static void
SendDockMessage(Display * dpy,Window w,long message,long data1,long data2,long data3)1282 SendDockMessage(Display* dpy, Window w, long message, long data1, long data2, long data3)
1283 {
1284 XEvent ev;
1285
1286 memset(&ev, 0, sizeof(ev));
1287 ev.xclient.type = ClientMessage;
1288 ev.xclient.window = w;
1289 ev.xclient.message_type = XInternAtom(dpy, "_NET_SYSTEM_TRAY_OPCODE", False);
1290 ev.xclient.format = 32;
1291 ev.xclient.data.l[0] = CurrentTime;
1292 ev.xclient.data.l[1] = message;
1293 ev.xclient.data.l[2] = data1;
1294 ev.xclient.data.l[3] = data2;
1295 ev.xclient.data.l[4] = data3;
1296
1297 XSendEvent(dpy, w, False, NoEventMask, &ev);
1298 XFlush(dpy);
1299 }
1300
1301 static void
DockWindow(Display * dpy,Window systray,Window w)1302 DockWindow(Display *dpy, Window systray, Window w)
1303 {
1304 if (systray != None) {
1305 SendDockMessage(dpy, systray, SYSTEM_TRAY_REQUEST_DOCK, w, 0, 0);
1306 }
1307 }
1308
1309 static void
MoveOrigin(Display * dpy,Window w,int * w_x,int * w_y)1310 MoveOrigin(Display *dpy, Window w, int *w_x, int *w_y)
1311 {
1312 Window rwin;
1313 Geometry geom;
1314 int x, y;
1315 unsigned int width, height, border, dep;
1316
1317 geom = conf.mainwindow.geometry;
1318 XGetGeometry(dpy, w, &rwin, &x, &y, &width, &height, &border, &dep);
1319
1320 /* X axis */
1321 if (width > geom.width + border) {
1322 *w_x = (width - geom.width - border) / 2;
1323 }
1324 /* Y axis */
1325 if (height > geom.height + border) {
1326 *w_y = (height - geom.height - border) / 2;
1327 }
1328 }
1329
1330
1331 /*
1332 * PrependProgramName
1333 * Prepends a program name to the string.
1334 *
1335 * Returns
1336 * a new string, which must be freed by the caller.
1337 * Exits the process if fails, which should never happen.
1338 */
1339
1340 char*
PrependProgramName(char * string)1341 PrependProgramName(char *string)
1342 {
1343 size_t len;
1344 char *result;
1345
1346 len = strlen(APPNAME) + 1 + strlen(string);
1347
1348 result = malloc(len + 1);
1349 if (result == NULL) {
1350 err(1, NULL);
1351 }
1352
1353 strcpy(result, APPNAME);
1354 strcat(result, ".");
1355 strcat(result, string);
1356
1357 return result;
1358 }
1359