A tiling window manager
1/*
2 * Copyright (C) 2000, 2001, 2002, 2003, 2004 Shawn Betts <sabetts@vcn.bc.ca>
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the Free
6 * Software Foundation; either version 2 of the License, or (at your option)
7 * any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 * more details.
13 *
14 * You should have received a copy of the GNU General Public License along with
15 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
16 * Place, Suite 330, Boston, MA 02111-1307 USA.
17 */
18
19#include "sdorfehs.h"
20
21#include <sys/types.h>
22#include <sys/wait.h>
23
24#include <ctype.h>
25#include <errno.h>
26#include <err.h>
27#include <pwd.h>
28#include <signal.h>
29#include <unistd.h>
30#include <dirent.h>
31
32/*
33 * Several systems seem not to have WAIT_ANY defined, so define it if it isn't.
34 */
35#ifndef WAIT_ANY
36#define WAIT_ANY -1
37#endif
38
39/* Some systems don't define the close-on-exec flag in fcntl.h */
40#ifndef FD_CLOEXEC
41#define FD_CLOEXEC 1
42#endif
43
44int alarm_signalled = 0;
45int kill_signalled = 0;
46int hup_signalled = 0;
47int chld_signalled = 0;
48
49int rp_font_ascent, rp_font_descent, rp_font_width;
50
51Atom wm_name;
52Atom wm_state;
53Atom wm_change_state;
54Atom wm_protocols;
55Atom wm_delete;
56Atom wm_take_focus;
57Atom wm_colormaps;
58
59Atom rp_selection;
60
61/* TEXT atoms */
62Atom xa_string;
63Atom xa_compound_text;
64Atom xa_utf8_string;
65
66/* netwm atoms */
67Atom _net_active_window;
68Atom _net_client_list;
69Atom _net_client_list_stacking;
70Atom _net_current_desktop;
71Atom _net_number_of_desktops;
72Atom _net_supported;
73Atom _net_workarea;
74Atom _net_wm_name;
75Atom _net_wm_pid;
76Atom _net_wm_state;
77Atom _net_wm_state_fullscreen;
78Atom _net_wm_window_type;
79Atom _net_wm_window_type_dialog;
80Atom _net_wm_window_type_dock;
81Atom _net_wm_window_type_splash;
82Atom _net_wm_window_type_tooltip;
83Atom _net_wm_window_type_utility;
84Atom _net_supporting_wm_check;
85
86LIST_HEAD(rp_screens);
87rp_screen *rp_current_screen;
88rp_global_screen rp_glob_screen;
89
90Display *dpy;
91
92int rp_have_xrandr;
93
94LIST_HEAD(rp_children);
95struct rp_defaults defaults;
96
97int ignore_badwindow = 0;
98
99char **myargv;
100
101struct rp_key prefix_key;
102
103struct modifier_info rp_modifier_info;
104
105/* rudeness levels */
106int rp_honour_transient_raise = 1;
107int rp_honour_normal_raise = 1;
108int rp_honour_transient_map = 1;
109int rp_honour_normal_map = 1;
110int rp_honour_vscreen_switch = 0;
111
112char *rp_error_msg = NULL;
113
114/* Global frame numset */
115struct numset *rp_frame_numset;
116
117/* The X11 selection globals */
118rp_xselection selection;
119
120static void
121x_export_selection(void)
122{
123 rp_screen *screen;
124
125 list_first(screen, &rp_screens, node);
126 if (!screen)
127 return;
128
129 /* Hang the selections off screen 0's key window. */
130 XSetSelectionOwner(dpy, XA_PRIMARY, screen->key_window, CurrentTime);
131 if (XGetSelectionOwner(dpy, XA_PRIMARY) != screen->key_window)
132 warnx("can't get primary selection");
133 XChangeProperty(dpy, screen->root, XA_CUT_BUFFER0, xa_string, 8,
134 PropModeReplace, (unsigned char *) selection.text, selection.len);
135}
136
137void
138set_nselection(char *txt, int len)
139{
140 int i;
141
142 /* Update the selection structure */
143 free(selection.text);
144
145 /* Copy the string by hand. */
146 selection.text = xmalloc(len + 1);
147 selection.len = len + 1;
148 for (i = 0; i < len; i++)
149 selection.text[i] = txt[i];
150 selection.text[len] = 0;
151
152 x_export_selection();
153}
154
155void
156set_selection(char *txt)
157{
158 /* Update the selection structure */
159 free(selection.text);
160 selection.text = xstrdup(txt);
161 selection.len = strlen(txt);
162
163 x_export_selection();
164}
165
166static char *
167get_cut_buffer(void)
168{
169 int nbytes;
170 char *data;
171
172 PRINT_DEBUG(("trying the cut buffer\n"));
173
174 data = XFetchBytes(dpy, &nbytes);
175
176 if (data) {
177 struct sbuf *s = sbuf_new(0);
178 sbuf_nconcat(s, data, nbytes);
179 XFree(data);
180 return sbuf_free_struct(s);
181 } else
182 return NULL;
183}
184
185/* Lifted the code from rxvt. */
186static char *
187get_primary_selection(void)
188{
189 long nread;
190 unsigned long bytes_after;
191 XTextProperty ct;
192 struct sbuf *s = sbuf_new(0);
193
194 for (nread = 0, bytes_after = 1; bytes_after > 0; nread += ct.nitems) {
195 if ((XGetWindowProperty(dpy, rp_current_screen->input_window,
196 rp_selection, (nread / 4), 4096, True, AnyPropertyType,
197 &ct.encoding, &ct.format, &ct.nitems, &bytes_after,
198 &ct.value) != Success)) {
199 XFree(ct.value);
200 sbuf_free(s);
201 return NULL;
202 }
203 if (ct.value == NULL)
204 continue;
205 /*
206 * Accumulate the data. FIXME: ct.value may not be NULL
207 * terminated.
208 */
209 sbuf_nconcat(s, (const char *) ct.value, ct.nitems);
210 XFree(ct.value);
211 }
212 return sbuf_free_struct(s);
213}
214
215char *
216get_selection(void)
217{
218 Atom property;
219 XEvent ev;
220 rp_screen *s = rp_current_screen;
221 int loops = 1000;
222
223 /* Just insert our text, if we own the selection. */
224 if (selection.text) {
225 return xstrdup(selection.text);
226 } else {
227 /* be a good icccm citizen */
228 XDeleteProperty(dpy, s->input_window, rp_selection);
229 /*
230 * TODO: we shouldn't use CurrentTime here, use the time of the
231 * XKeyEvent, should we fake it?
232 */
233 XConvertSelection(dpy, XA_PRIMARY, xa_string, rp_selection,
234 s->input_window, CurrentTime);
235
236 /* This seems like a hack. */
237 while (!XCheckTypedWindowEvent(dpy, s->input_window,
238 SelectionNotify, &ev)) {
239 if (loops == 0) {
240 warnx("selection request timed out");
241 return NULL;
242 }
243 usleep(10000);
244 loops--;
245 }
246
247 PRINT_DEBUG(("SelectionNotify event\n"));
248
249 property = ev.xselection.property;
250
251 if (property != None)
252 return get_primary_selection();
253 else
254 return get_cut_buffer();
255 }
256}
257
258/* The hook dictionary globals. */
259
260LIST_HEAD(rp_key_hook);
261LIST_HEAD(rp_switch_win_hook);
262LIST_HEAD(rp_switch_frame_hook);
263LIST_HEAD(rp_switch_screen_hook);
264LIST_HEAD(rp_switch_vscreen_hook);
265LIST_HEAD(rp_quit_hook);
266LIST_HEAD(rp_restart_hook);
267LIST_HEAD(rp_delete_window_hook);
268LIST_HEAD(rp_new_window_hook);
269LIST_HEAD(rp_title_changed_hook);
270
271struct rp_hook_db_entry rp_hook_db[] =
272 {{"key", &rp_key_hook},
273 {"switchwin", &rp_switch_win_hook},
274 {"switchframe", &rp_switch_frame_hook},
275 {"switchscreen", &rp_switch_screen_hook},
276 {"switchvscreen", &rp_switch_vscreen_hook},
277 {"deletewindow", &rp_delete_window_hook},
278 {"quit", &rp_quit_hook},
279 {"restart", &rp_restart_hook},
280 {"newwindow", &rp_new_window_hook},
281 {"titlechanged", &rp_title_changed_hook},
282 {NULL, NULL}
283};
284
285void
286set_rp_window_focus(rp_window *win)
287{
288 PRINT_DEBUG(("Giving focus to '%s'\n", window_name(win)));
289 XSetInputFocus(dpy, win->w,
290 RevertToPointerRoot, CurrentTime);
291 set_atom(win->vscreen->screen->root, _net_active_window, XA_WINDOW,
292 &win->w, 1);
293}
294
295void
296set_window_focus(Window window)
297{
298 PRINT_DEBUG(("Giving focus to %ld\n", window));
299 XSetInputFocus(dpy, window,
300 RevertToPointerRoot, CurrentTime);
301}
302
303XftFont *
304rp_get_font(rp_screen *s, char *font)
305{
306 XftFont *f;
307 int fslots = sizeof(s->xft_font_cache) / sizeof(struct rp_font);
308 int x;
309
310 if (!font || font[0] == '\0')
311 return s->xft_font;
312
313 for (x = 0; x < fslots; x++) {
314 if (!s->xft_font_cache[x].name)
315 break;
316
317 if (strcmp(s->xft_font_cache[x].name, font) == 0)
318 return s->xft_font_cache[x].font;
319 }
320
321 /* not in the cache, make sure we can open it first */
322 f = XftFontOpenName(dpy, DefaultScreen(dpy), font);
323 if (!f) {
324 warnx("failed opening xft font \"%s\"", font);
325 return s->xft_font;
326 }
327
328 PRINT_DEBUG(("font \"%s\" not in font cache\n", font));
329
330 /* free up the last slot if needed */
331 if (x == fslots) {
332 free(s->xft_font_cache[x - 1].name);
333 XftFontClose(dpy, s->xft_font_cache[x - 1].font);
334 }
335
336 /* shift all the cache entries to free up the first slot */
337 for (x = fslots - 1; x >= 1; x--)
338 memcpy(&s->xft_font_cache[x], &s->xft_font_cache[x - 1],
339 sizeof(struct rp_font));
340
341 s->xft_font_cache[0].name = xstrdup(font);
342 s->xft_font_cache[0].font = f;
343
344 return f;
345}
346
347void
348rp_clear_cached_fonts(rp_screen *s)
349{
350 int x;
351
352 for (x = 0; x < (sizeof(s->xft_font_cache) / sizeof(struct rp_font));
353 x++) {
354 if (s->xft_font_cache[x].name) {
355 free(s->xft_font_cache[x - 1].name);
356 XftFontClose(dpy, s->xft_font_cache[x].font);
357 }
358 }
359}
360
361void
362rp_draw_string(rp_screen *s, Drawable d, int style, int x, int y, char *string,
363 int length, char *font, char *color)
364{
365 XftDraw *draw;
366 XftColor xftcolor;
367 XftFont *f = rp_get_font(s, font);
368
369 if (length < 0)
370 length = strlen(string);
371
372 draw = XftDrawCreate(dpy, d, DefaultVisual(dpy, s->screen_num),
373 DefaultColormap(dpy, s->screen_num));
374 if (!draw) {
375 warnx("no Xft font available");
376 return;
377 }
378
379 if (color == NULL) {
380 if (style == STYLE_NORMAL)
381 memcpy(&xftcolor, &s->xft_fgcolor, sizeof(XftColor));
382 else
383 memcpy(&xftcolor, &s->xft_bgcolor, sizeof(XftColor));
384 } else {
385 /*
386 * This won't actually allocate anything if the color is
387 * already allocated.
388 */
389 if (!XftColorAllocName(dpy, DefaultVisual(dpy, s->screen_num),
390 DefaultColormap(dpy, s->screen_num), color, &xftcolor)) {
391 warnx("couldn't XftColorAllocName \"%s\"", color);
392 memcpy(&xftcolor, &s->xft_fgcolor, sizeof(XftColor));
393 }
394 }
395
396 XftDrawStringUtf8(draw, &xftcolor, f, x, y, (FcChar8 *)string, length);
397 XftDrawDestroy(draw);
398}
399
400int
401rp_text_width(rp_screen *s, char *string, int count, char *font)
402{
403 XGlyphInfo extents;
404 XftFont *f = rp_get_font(s, font);
405
406 if (count < 0)
407 count = strlen(string);
408
409 XftTextExtentsUtf8(dpy, f, (FcChar8 *)string, count, &extents);
410
411 return extents.xOff;
412}
413
414/* A case insensitive strncmp. */
415int
416str_comp(char *s1, char *s2, size_t len)
417{
418 size_t i;
419
420 for (i = 0; i < len; i++)
421 if (toupper((unsigned char)s1[i]) != toupper((unsigned char)s2[i]))
422 return 0;
423
424 return 1;
425}
426
427/*
428 * Check for child processes that have quit but haven't been acknowledged yet.
429 * Update their structure.
430 */
431void
432check_child_procs(void)
433{
434 rp_child_info *cur;
435 int pid, status;
436 while (1) {
437 pid = waitpid(WAIT_ANY, &status, WNOHANG);
438 if (pid <= 0)
439 break;
440
441 PRINT_DEBUG(("Child status: %d\n", WEXITSTATUS(status)));
442
443 /* Find the child and update its structure. */
444 list_for_each_entry(cur, &rp_children, node) {
445 if (cur->pid == pid) {
446 cur->terminated = 1;
447 cur->status = WEXITSTATUS(status);
448 break;
449 }
450 }
451
452 chld_signalled = 1;
453 }
454}
455
456void
457chld_handler(int signum)
458{
459 int serrno;
460
461 serrno = errno;
462 check_child_procs();
463 errno = serrno;
464}
465
466void
467set_sig_handler(int sig, void (*action)(int))
468{
469 struct sigaction act;
470
471 memset(&act, 0, sizeof(act));
472 act.sa_handler = action;
473 sigemptyset(&act.sa_mask);
474 if (sigaction(sig, &act, NULL))
475 warnx("error setting signal handler");
476}
477
478void
479set_close_on_exec(int fd)
480{
481 int flags = fcntl(fd, F_GETFD);
482 if (flags >= 0)
483 fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
484}
485
486void
487read_rc_file(FILE *file)
488{
489 char *line;
490 size_t linesize = 256;
491
492 line = xmalloc(linesize);
493
494 while (getline(&line, &linesize, file) != -1) {
495 line[strcspn(line, "\n")] = '\0';
496
497 PRINT_DEBUG(("rcfile line: %s\n", line));
498
499 if (*line != '\0' && *line != '#') {
500 cmdret *result;
501 result = command(0, line);
502
503 /* Gobble the result. */
504 if (result)
505 cmdret_free(result);
506 }
507 }
508
509 free(line);
510}
511
512const char *
513get_homedir(void)
514{
515 char *homedir;
516
517 homedir = getenv("HOME");
518 if (homedir != NULL && homedir[0] == '\0')
519 homedir = NULL;
520
521 if (homedir == NULL) {
522 struct passwd *pw;
523
524 pw = getpwuid(getuid());
525 if (pw != NULL)
526 homedir = pw->pw_dir;
527
528 if (homedir != NULL && homedir[0] == '\0')
529 homedir = NULL;
530 }
531
532 return homedir;
533}
534
535char *
536get_config_dir(void)
537{
538 DIR *d;
539 const char *homedir;
540 char *xdg_config, *home_config;
541 int xdg_alloc = 0;
542
543 homedir = get_homedir();
544 if (!homedir)
545 errx(1, "no home directory");
546
547 xdg_config = getenv("XDG_CONFIG_HOME");
548 if (xdg_config == NULL || !strlen(xdg_config)) {
549 xdg_config = xsprintf("%s/.config", homedir);
550 xdg_alloc = 1;
551 }
552
553 if (!(d = opendir(xdg_config))) {
554 if (mkdir(xdg_config, 0755) == -1)
555 err(1, "failed creating %s", xdg_config);
556
557 if (!(d = opendir(xdg_config)))
558 err(1, "failed opening %s", xdg_config);
559 }
560 closedir(d);
561
562 home_config = xsprintf("%s/sdorfehs", xdg_config);
563 if (!(d = opendir(home_config))) {
564 if (mkdir(home_config, 0755) == -1)
565 err(1, "failed creating %s", home_config);
566
567 if (!(d = opendir(home_config)))
568 err(1, "failed opening %s", home_config);
569 }
570 closedir(d);
571
572 if (xdg_alloc)
573 free(xdg_config);
574
575 return home_config;
576}
577
578void
579clean_up(void)
580{
581 rp_screen *cur;
582 rp_vscreen *vcur;
583 struct list_head *iter, *tmp, *iter2, *tmp2;
584
585 history_save();
586
587 free_keymaps();
588 free_aliases();
589 free_user_commands();
590 free_bar();
591 free_window_stuff();
592
593 list_for_each_safe_entry(cur, iter, tmp, &rp_screens, node) {
594 list_for_each_safe_entry(vcur, iter2, tmp2, &cur->vscreens, node)
595 vscreen_del(vcur);
596
597 list_del(&cur->node);
598 screen_free(cur);
599 free(cur);
600 }
601
602 screen_free_final();
603
604 /* Delete the undo histories */
605 clear_frame_undos();
606
607 /* Free the global frame numset shared by all screens. */
608 numset_free(rp_frame_numset);
609
610 free(defaults.window_fmt);
611
612 XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime);
613 XCloseDisplay(dpy);
614}
615
616void
617register_atom(Atom *a, char *name)
618{
619 *a = XInternAtom(dpy, name, False);
620 PRINT_DEBUG(("Registered Atom %ld = %s\n", (unsigned long)*a, name));
621 append_atom(DefaultRootWindow(dpy), _net_supported, XA_ATOM, a, 1);
622}
623
624int
625set_atom(Window w, Atom a, Atom type, unsigned long *val, unsigned long nitems)
626{
627 return (XChangeProperty(dpy, w, a, type, 32, PropModeReplace,
628 (unsigned char *)val, nitems) == Success);
629}
630
631int
632append_atom(Window w, Atom a, Atom type, unsigned long *val,
633 unsigned long nitems)
634{
635 return (XChangeProperty(dpy, w, a, type, 32, PropModeAppend,
636 (unsigned char *)val, nitems) == Success);
637}
638
639unsigned long
640get_atom(Window w, Atom a, Atom type, unsigned long off, unsigned long *ret,
641 unsigned long nitems, unsigned long *left)
642{
643 Atom real_type;
644 int i, real_format = 0;
645 unsigned long items_read = 0;
646 unsigned long bytes_left = 0;
647 unsigned long *p;
648 unsigned char *data;
649
650 XGetWindowProperty(dpy, w, a, off, nitems, False, type, &real_type,
651 &real_format, &items_read, &bytes_left, &data);
652
653 if (real_format == 32 && items_read) {
654 p = (unsigned long *)data;
655 for (i = 0; i < items_read; i++)
656 *ret++ = *p++;
657 XFree(data);
658 if (left)
659 *left = bytes_left;
660 return items_read;
661 }
662
663 return 0;
664}
665
666void
667remove_atom(Window w, Atom a, Atom type, unsigned long remove)
668{
669 unsigned long tmp, read, left, *new;
670 int i, j = 0;
671
672 read = get_atom(w, a, type, 0, &tmp, 1, &left);
673 if (!read)
674 return;
675
676 new = malloc((read + left) * sizeof(*new));
677 if (read && tmp != remove)
678 new[j++] = tmp;
679
680 for (i = 1, read = left = 1; read && left; i += read) {
681 read = get_atom(w, a, type, i, &tmp, 1, &left);
682 if (!read)
683 break;
684 if (tmp != remove)
685 new[j++] = tmp;
686 }
687
688 if (j)
689 XChangeProperty(dpy, w, a, type, 32, PropModeReplace,
690 (unsigned char *)new, j);
691 else
692 XDeleteProperty(dpy, w, a);
693
694 free(new);
695}