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