A tiling window manager
at master 700 lines 16 kB view raw
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}