A tiling window manager
1/*
2 * functions for handling the window list
3 * Copyright (C) 2000, 2001, 2002, 2003, 2004 Shawn Betts <sabetts@vcn.bc.ca>
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the Free
7 * Software Foundation; either version 2 of the License, or (at your option)
8 * any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 * more details.
14 *
15 * You should have received a copy of the GNU General Public License along with
16 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
17 * Place, Suite 330, Boston, MA 02111-1307 USA.
18 */
19
20#include <stdio.h>
21#include <stdlib.h>
22#include <string.h>
23
24#include "sdorfehs.h"
25
26LIST_HEAD(rp_unmapped_window);
27LIST_HEAD(rp_mapped_window);
28
29struct numset *rp_window_numset;
30
31static void set_active_window_body(rp_window * win, int force);
32
33/* Get the mouse position relative to the the specified window */
34static void
35get_mouse_position(rp_window *win, int *mouse_x, int *mouse_y)
36{
37 Window root_win, child_win;
38 int root_x, root_y;
39 unsigned int mask;
40
41 XQueryPointer(dpy, win->vscreen->screen->root, &root_win, &child_win,
42 mouse_x, mouse_y, &root_x, &root_y, &mask);
43}
44
45void
46free_window(rp_window *w)
47{
48 if (w == NULL)
49 return;
50
51 free(w->user_name);
52 free(w->res_name);
53 free(w->res_class);
54 free(w->wm_name);
55
56 XFree(w->hints);
57
58 free(w);
59}
60
61void
62update_window_gravity(rp_window *win)
63{
64 if (win->transient)
65 win->gravity = defaults.trans_gravity;
66 else if (win->hints->flags & PMaxSize || win->hints->flags & PAspect)
67 win->gravity = defaults.maxsize_gravity;
68 else
69 win->gravity = defaults.win_gravity;
70}
71
72char *
73window_name(rp_window *win)
74{
75 if (win == NULL)
76 return NULL;
77
78 if (win->named)
79 return win->user_name;
80
81 switch (defaults.win_name) {
82 case WIN_NAME_RES_NAME:
83 if (win->res_name)
84 return win->res_name;
85 else
86 return win->user_name;
87
88 case WIN_NAME_RES_CLASS:
89 if (win->res_class)
90 return win->res_class;
91 else
92 return win->user_name;
93
94 /*
95 * if we're not looking for the res name or res class, then
96 * we're looking for the window title.
97 */
98 default:
99 if (win->wm_name)
100 return win->wm_name;
101 else
102 return win->user_name;
103 }
104
105 return NULL;
106}
107
108/*
109 * FIXME: we need to verify that the window is running on the same host as
110 * something. otherwise there could be overlapping PIDs.
111 */
112struct rp_child_info *
113get_child_info(Window w, int add)
114{
115 XResClientIdSpec specs;
116 XResClientIdValue *results = NULL;
117 rp_child_info *cur;
118 unsigned long pid = 0;
119 long nresults;
120 int i;
121
122 if (!get_atom(w, _net_wm_pid, XA_CARDINAL, 0, &pid, 1, NULL)) {
123 PRINT_DEBUG(("Couldn't get _NET_WM_PID Property\n"));
124 specs.client = w;
125 specs.mask = XRES_CLIENT_ID_PID_MASK;
126 if (XResQueryClientIds(dpy, 1, &specs, &nresults,
127 &results) != Success)
128 pid = 0;
129 else {
130 for (i = 0; i < nresults; i++) {
131 if (results[i].spec.mask !=
132 XRES_CLIENT_ID_PID_MASK)
133 continue;
134
135 pid = *(CARD32 *)(results[i].value);
136 break;
137 }
138 XFree(results);
139 }
140 }
141
142 PRINT_DEBUG(("NET_WM_PID: %ld\n", pid));
143
144 if (pid) {
145 list_for_each_entry(cur, &rp_children, node)
146 if (pid == cur->pid)
147 return cur;
148 }
149
150 if (!add)
151 return NULL;
152
153 /*
154 * A new process is creating windows that we didn't directly spawn
155 * (otherwise it would be in rp_children via spawn())
156 */
157 cur = xmalloc(sizeof(rp_child_info));
158 cur->cmd = NULL;
159 cur->pid = pid;
160 cur->terminated = 0;
161 cur->frame = current_frame(rp_current_vscreen);
162 cur->vscreen = rp_current_vscreen;
163 cur->screen = rp_current_screen;
164 cur->window_mapped = 0;
165
166 list_add(&cur->node, &rp_children);
167
168 return cur;
169}
170
171/* Allocate a new window and add it to the list of managed windows */
172rp_window *
173add_to_window_list(rp_screen *s, Window w)
174{
175 struct rp_child_info *child_info;
176 rp_window *new_window;
177 rp_vscreen *vscreen = NULL;
178 int frame_num = -1;
179
180 new_window = xmalloc(sizeof(rp_window));
181
182 new_window->w = w;
183 new_window->vscreen = s->current_vscreen;
184 new_window->last_access = 0;
185 new_window->state = WithdrawnState;
186 new_window->number = -1;
187 new_window->sticky_frame = EMPTY;
188 new_window->frame_number = EMPTY;
189 new_window->intended_frame_number = -1;
190 new_window->named = 0;
191 new_window->hints = XAllocSizeHints();
192 new_window->colormap = DefaultColormap(dpy, s->screen_num);
193 new_window->transient = XGetTransientForHint(dpy, new_window->w,
194 &new_window->transient_for);
195 PRINT_DEBUG(("transient %d\n", new_window->transient));
196 new_window->full_screen = 0;
197
198 update_window_gravity(new_window);
199
200 get_mouse_position(new_window, &new_window->mouse_x,
201 &new_window->mouse_y);
202
203 XSelectInput(dpy, new_window->w, WIN_EVENTS);
204
205 new_window->user_name = xstrdup("Unnamed");
206
207 new_window->wm_name = NULL;
208 new_window->res_name = NULL;
209 new_window->res_class = NULL;
210
211 /* Add the window to the end of the unmapped list. */
212 list_add_tail(&new_window->node, &rp_unmapped_window);
213
214 child_info = get_child_info(w, 1);
215 if (child_info) {
216 if (child_info->vscreen != new_window->vscreen &&
217 !defaults.win_add_cur_vscreen)
218 new_window->vscreen = child_info->vscreen;
219
220 if (!child_info->window_mapped) {
221 rp_frame *frame = vscreen_find_frame_by_frame(
222 child_info->vscreen, child_info->frame);
223
224 PRINT_DEBUG(("frame=%p\n", frame));
225 vscreen = child_info->vscreen;
226 if (frame)
227 frame_num = frame->number;
228 /* Only map the first window in the launch frame. */
229 child_info->window_mapped = 1;
230 }
231 }
232
233 /*
234 * Add the window to the vscreen its pid was launched in or the current
235 * one.
236 */
237 if (vscreen)
238 vscreen_add_window(vscreen, new_window);
239 else
240 vscreen_add_window(new_window->vscreen, new_window);
241
242 PRINT_DEBUG(("frame_num: %d\n", frame_num));
243 if (frame_num >= 0)
244 new_window->intended_frame_number = frame_num;
245
246 return new_window;
247}
248
249/* Check to see if the window is in the list of windows. */
250rp_window *
251find_window_in_list(Window w, struct list_head *list)
252{
253 rp_window *cur;
254
255 list_for_each_entry(cur, list, node) {
256 if (cur->w == w)
257 return cur;
258 }
259
260 return NULL;
261}
262
263/* Check to see if the window is in any of the lists of windows. */
264rp_window *
265find_window(Window w)
266{
267 rp_window *win = NULL;
268
269 win = find_window_in_list(w, &rp_mapped_window);
270
271 if (!win) {
272 win = find_window_in_list(w, &rp_unmapped_window);
273 if (win)
274 PRINT_DEBUG(("Window found in unmapped window list\n"));
275 else
276 PRINT_DEBUG(("Window not found.\n"));
277 } else {
278 PRINT_DEBUG(("Window found in mapped window list.\n"));
279 }
280
281 return win;
282}
283
284rp_window *
285find_window_number(int n)
286{
287 rp_window *cur;
288
289 list_for_each_entry(cur, &rp_mapped_window, node) {
290 /* if (cur->state == STATE_UNMAPPED) continue; */
291
292 if (n == cur->number)
293 return cur;
294 }
295
296 return NULL;
297}
298
299rp_window *
300find_window_name(char *name, int exact_match)
301{
302 rp_window_elem *cur;
303
304 if (!exact_match) {
305 list_for_each_entry(cur, &rp_current_vscreen->mapped_windows,
306 node) {
307 if (str_comp(name, window_name(cur->win), strlen(name)))
308 return cur->win;
309 }
310 } else {
311 list_for_each_entry(cur, &rp_current_vscreen->mapped_windows,
312 node) {
313 if (!strcmp(name, window_name(cur->win)))
314 return cur->win;
315 }
316 }
317
318 /* didn't find it */
319 return NULL;
320}
321
322/* TODO: remove this */
323rp_window *
324find_window_other(rp_vscreen *vscreen)
325{
326 return vscreen_last_window(vscreen);
327}
328
329/*
330 * Assumes the list is sorted by increasing number. Inserts win into to Right
331 * place to keep the list sorted.
332 */
333void
334insert_into_list(rp_window *win, struct list_head *list)
335{
336 rp_window *cur;
337
338 list_for_each_entry(cur, list, node) {
339 if (cur->number > win->number) {
340 list_add_tail(&win->node, &cur->node);
341 return;
342 }
343 }
344
345 list_add_tail(&win->node, list);
346}
347
348static void
349save_mouse_position(rp_window *win)
350{
351 Window root_win, child_win;
352 int root_x, root_y;
353 unsigned int mask;
354
355 /*
356 * In the case the XQueryPointer raises a BadWindow error, the window
357 * is not mapped or has been destroyed so it doesn't matter what we
358 * store in mouse_x and mouse_y since they will never be used again.
359 */
360
361 ignore_badwindow++;
362
363 XQueryPointer(dpy, win->w, &root_win, &child_win,
364 &root_x, &root_y, &win->mouse_x, &win->mouse_y, &mask);
365
366 ignore_badwindow--;
367}
368
369/* Takes focus away from last_win and gives focus to win */
370void
371give_window_focus(rp_window *win, rp_window *last_win)
372{
373 /*
374 * counter increments every time this function is called. This way we
375 * can track which window was last accessed.
376 */
377 static int counter = 1;
378
379 /*
380 * Warp the cursor to the window's saved position if last_win and win
381 * are different windows.
382 */
383 if (last_win != NULL && win != last_win) {
384 if (last_win->full_screen)
385 window_full_screen(NULL);
386 save_mouse_position(last_win);
387 XSetWindowBorder(dpy, last_win->w, rp_glob_screen.bwcolor);
388 }
389 if (win == NULL)
390 return;
391
392 counter++;
393 win->last_access = counter;
394 unhide_window(win);
395
396 if (defaults.warp) {
397 PRINT_DEBUG(("Warp pointer\n"));
398 XWarpPointer(dpy, None, win->w,
399 0, 0, 0, 0, win->mouse_x, win->mouse_y);
400 }
401 /* Swap colormaps */
402 if (last_win != NULL)
403 XUninstallColormap(dpy, last_win->colormap);
404 XInstallColormap(dpy, win->colormap);
405
406 XSetWindowBorder(dpy, win->w, rp_glob_screen.fwcolor);
407
408 /* Finally, give the window focus */
409 rp_current_screen = win->vscreen->screen;
410 rp_current_screen->current_vscreen = win->vscreen;
411 set_rp_window_focus(win);
412
413 raise_utility_windows();
414
415 XSync(dpy, False);
416}
417
418/* In the current frame, set the active window to win. win will have focus. */
419void
420set_active_window(rp_window *win)
421{
422 set_active_window_body(win, 0);
423}
424
425void
426set_active_window_force(rp_window *win)
427{
428 set_active_window_body(win, 1);
429}
430
431static rp_frame *
432find_frame_non_dedicated(rp_vscreen *current_vscreen)
433{
434 rp_frame *cur;
435 rp_screen *screen;
436
437 list_for_each_entry(screen, &rp_screens, node) {
438 if (current_vscreen == screen->current_vscreen)
439 continue;
440
441 list_for_each_entry(cur, &screen->current_vscreen->frames,
442 node) {
443 if (!cur->dedicated)
444 return cur;
445 }
446 }
447
448 return NULL;
449}
450
451static void
452set_active_window_body(rp_window *win, int force)
453{
454 rp_window *last_win;
455 rp_frame *frame = NULL, *last_frame = NULL;
456
457 if (win == NULL)
458 return;
459
460 PRINT_DEBUG(("intended_frame_number: %d\n",
461 win->intended_frame_number));
462
463 /* use the intended frame if we can. */
464 if (win->intended_frame_number >= 0) {
465 frame = vscreen_get_frame(win->vscreen,
466 win->intended_frame_number);
467 win->intended_frame_number = -1;
468 if (frame != current_frame(win->vscreen))
469 last_frame = current_frame(win->vscreen);
470 }
471 if (frame == NULL)
472 frame = vscreen_get_frame(win->vscreen,
473 win->vscreen->current_frame);
474
475 if (frame->dedicated && !force) {
476 /* Try to find a non-dedicated frame. */
477 rp_frame *non_dedicated;
478
479 non_dedicated = find_frame_non_dedicated(win->vscreen);
480 if (non_dedicated != NULL) {
481 last_frame = frame;
482 frame = non_dedicated;
483 if (win->vscreen == rp_current_vscreen)
484 set_active_frame(frame, 0);
485 }
486 }
487 last_win = set_frames_window(frame, win);
488
489 if (last_win != NULL)
490 PRINT_DEBUG(("last window: %s\n", window_name(last_win)));
491 PRINT_DEBUG(("new window: %s\n", window_name(win)));
492
493 /* Make sure the window comes up full screen */
494 maximize(win);
495
496 if (win->vscreen == rp_current_vscreen)
497 /* Focus the window. */
498 give_window_focus(win, last_win);
499
500 /*
501 * The other windows in the frame will be hidden if this window doesn't
502 * qualify as a transient window (ie dialog box.
503 */
504 if (!window_is_transient(win))
505 hide_others(win);
506
507 if (win->vscreen == rp_current_vscreen)
508 /* Make sure the program bar is always on the top */
509 update_window_names(win->vscreen->screen, defaults.window_fmt);
510
511 XSync(dpy, False);
512
513 /* If we switched frame, go back to the old one. */
514 if (win->vscreen == rp_current_vscreen) {
515 if (last_frame != NULL)
516 set_active_frame(last_frame, 0);
517
518 /* Call the switch window hook */
519 hook_run(&rp_switch_win_hook);
520 }
521}
522
523/*
524 * Go to the window, switching frames if the window is already in a frame.
525 */
526void
527goto_window(rp_window *win)
528{
529 rp_frame *frame;
530
531 /* There is nothing to do if it is already the current window. */
532 if (current_window() == win)
533 return;
534
535 frame = find_windows_frame(win);
536 if (frame) {
537 set_active_frame(frame, 0);
538 } else {
539 set_active_window(win);
540 }
541}
542
543void
544get_current_window_in_fmt(char *fmt, struct sbuf *buffer)
545{
546 rp_window_elem *we;
547
548 if (buffer == NULL)
549 return;
550
551 sbuf_clear(buffer);
552 find_window_other(rp_current_vscreen);
553
554 list_for_each_entry(we, &rp_current_vscreen->mapped_windows, node) {
555 if (we->win != current_window())
556 continue;
557
558 format_string(fmt, we, buffer);
559 }
560}
561
562/*
563 * get the window list and store it in buffer delimiting each window with
564 * delim. mark_start and mark_end will be filled with the text positions for
565 * the start and end of the current window.
566 */
567void
568get_window_list(char *fmt, char *delim, struct sbuf *buffer,
569 int *mark_start, int *mark_end)
570{
571 rp_window_elem *we;
572
573 if (buffer == NULL)
574 return;
575
576 sbuf_clear(buffer);
577 find_window_other(rp_current_vscreen);
578
579 /* We only loop through the current vscreen to look for windows. */
580 list_for_each_entry(we, &rp_current_vscreen->mapped_windows, node) {
581 PRINT_DEBUG(("%d-%s\n", we->number, window_name(we->win)));
582
583 if (we->win == current_window())
584 *mark_start = strlen(sbuf_get(buffer));
585
586 /*
587 * A hack, pad the window with a space at the beginning and end
588 * if there is no delimiter.
589 */
590 if (!delim)
591 sbuf_concat(buffer, " ");
592
593 format_string(fmt, we, buffer);
594
595 /*
596 * A hack, pad the window with a space at the beginning and end
597 * if there is no delimiter.
598 */
599 if (!delim)
600 sbuf_concat(buffer, " ");
601
602 /*
603 * Only put the delimiter between the windows, and not after
604 * the the last window.
605 */
606 if (delim && we->node.next != &rp_current_vscreen->mapped_windows)
607 sbuf_concat(buffer, delim);
608
609 if (we->win == current_window()) {
610 *mark_end = strlen(sbuf_get(buffer));
611 }
612 }
613
614 if (!strcmp(sbuf_get(buffer), "")) {
615 sbuf_copy(buffer, MESSAGE_NO_MANAGED_WINDOWS);
616 }
617}
618
619void
620init_window_stuff(void)
621{
622 rp_window_numset = numset_new();
623}
624
625void
626free_window_stuff(void)
627{
628 rp_window *cur;
629 struct list_head *tmp, *iter;
630
631 list_for_each_safe_entry(cur, iter, tmp, &rp_unmapped_window, node) {
632 list_del(&cur->node);
633 vscreen_del_window(cur->vscreen, cur);
634 free_window(cur);
635 }
636
637 list_for_each_safe_entry(cur, iter, tmp, &rp_mapped_window, node) {
638 list_del(&cur->node);
639 vscreen_unmap_window(cur->vscreen, cur);
640 vscreen_del_window(cur->vscreen, cur);
641 free_window(cur);
642 }
643
644 numset_free(rp_window_numset);
645}
646
647rp_frame *
648win_get_frame(rp_window *win)
649{
650 if (win->frame_number != EMPTY)
651 return vscreen_get_frame(win->vscreen, win->frame_number);
652
653 return NULL;
654}
655
656void
657change_windows_vscreen(rp_vscreen *old_vscreen, rp_vscreen *new_vscreen)
658{
659 rp_window *win;
660
661 list_for_each_entry(win, &rp_mapped_window, node) {
662 if (win->vscreen == old_vscreen)
663 win->vscreen = new_vscreen;
664 }
665}
666
667void
668window_full_screen(rp_window *win)
669{
670 rp_window *oldfs;
671
672 if ((oldfs = rp_current_screen->full_screen_win)) {
673 if (win == oldfs)
674 return;
675
676 PRINT_DEBUG(("making window 0x%lx no longer "
677 "full-screen\n", oldfs->w));
678
679 oldfs->full_screen = 0;
680 remove_atom(oldfs->w, _net_wm_state, XA_ATOM,
681 _net_wm_state_fullscreen);
682 maximize(oldfs);
683 }
684
685 if (!win) {
686 rp_current_screen->full_screen_win = NULL;
687 if (defaults.bar_timeout != 0)
688 hide_bar(rp_current_screen, 0);
689 return;
690 }
691
692 PRINT_DEBUG(("making window 0x%lx full-screen\n", win->w));
693 hide_bar(rp_current_screen, 1);
694
695 rp_current_screen->full_screen_win = win;
696 win->full_screen = 1;
697 set_atom(win->w, _net_wm_state, XA_ATOM,
698 &_net_wm_state_fullscreen, 1);
699 maximize(win);
700}