workspace_utils.c revision 0bbfda8a
1/* 2 * Various workspace handling and utilities. 3 */ 4 5#include "ctwm.h" 6 7#include <stdio.h> 8#include <string.h> 9#include <stdlib.h> 10 11#include <X11/Xatom.h> 12 13#include "animate.h" 14#include "clicktofocus.h" 15#include "ctwm_atoms.h" 16#include "drawing.h" 17#include "functions.h" 18#include "iconmgr.h" 19#include "image.h" 20#include "otp.h" 21#include "screen.h" 22#include "vscreen.h" 23#include "win_ops.h" 24#include "win_utils.h" 25#include "workspace_manager.h" 26#include "workspace_utils.h" 27 28#ifdef EWMH 29# include "ewmh_atoms.h" 30#endif 31 32 33/* 34 * XXX I'm not sure this should be here; maybe it's more of a per-screen 35 * thing, and so should be in the Screen struct? 36 */ 37bool useBackgroundInfo = false; 38 39 40/* 41 * Move the display (of a given vs) over to a new workspace. 42 */ 43void 44GotoWorkSpace(VirtualScreen *vs, WorkSpace *ws) 45{ 46 TwmWindow *twmWin; 47 WorkSpace *oldws, *newws; 48 WList *wl, *wl1; 49 WinList *winl; 50 XSetWindowAttributes attr; 51 long eventMask; 52 IconMgr *iconmgr; 53 Window oldw; 54 Window neww; 55 TwmWindow *focuswindow; 56 VirtualScreen *tmpvs; 57 58 if(! Scr->workSpaceManagerActive) { 59 return; 60 } 61 for(tmpvs = Scr->vScreenList; tmpvs != NULL; tmpvs = tmpvs->next) { 62 if(ws == tmpvs->wsw->currentwspc) { 63 XBell(dpy, 0); 64 return; 65 } 66 } 67 oldws = vs->wsw->currentwspc; 68 newws = ws; 69 if(oldws == newws) { 70 return; 71 } 72 73 /* XXX X-ref CTAG_BGDRAW in CreateWorkSpaceManager() and below */ 74 if(useBackgroundInfo && ! Scr->DontPaintRootWindow) { 75 if(newws->image == NULL) { 76 XSetWindowBackground(dpy, vs->window, newws->backcp.back); 77 } 78 else { 79 XSetWindowBackgroundPixmap(dpy, vs->window, newws->image->pixmap); 80 } 81 XClearWindow(dpy, vs->window); 82 } 83 84 /* If SaveWorkspaceFocus is on, save the focus of the last window. */ 85 if(Scr->SaveWorkspaceFocus) { 86 oldws->save_focus = Scr->Focus; 87 } 88 89 focuswindow = NULL; 90 /* For better visual effect, the order or map/unmap is important: 91 - map from top to bottom. 92 - unmap from bottom to top. 93 - unmap after mapping. 94 The guiding factor: at any point during the transition, something 95 should be visible only if it was visible before the transition or if 96 it will be visible at the end. */ 97 OtpCheckConsistency(); 98 99 for(twmWin = OtpTopWin(); twmWin != NULL; 100 twmWin = OtpNextWinDown(twmWin)) { 101 102 if(OCCUPY(twmWin, newws)) { 103 if(!twmWin->vs) { 104 DisplayWin(vs, twmWin); 105 } 106#ifdef EWMH 107 if(OCCUPY(twmWin, oldws)) { 108 /* 109 * If the window remains visible, re-order the workspace 110 * numbers in NET_WM_DESKTOP. 111 */ 112 EwmhSet_NET_WM_DESKTOP_ws(twmWin, newws); 113 } 114#endif 115 } 116 } 117 118 for(twmWin = OtpBottomWin(); twmWin != NULL; 119 twmWin = OtpNextWinUp(twmWin)) { 120 if(twmWin->vs == vs) { 121 if(!OCCUPY(twmWin, newws)) { 122 VirtualScreen *tvs; 123 124 Vanish(vs, twmWin); 125 /* 126 * Now that the window has Vanished from one virtual screen, 127 * check to see if it is wanted on another one. 128 * This is relatively rare, so don't bother with the 129 * top-to-bottom order here. 130 */ 131 if(Scr->numVscreens > 1) { 132 for(tvs = Scr->vScreenList; tvs != NULL; tvs = tvs->next) { 133 if(tvs == vs) { /* no, not back on the old one */ 134 continue; 135 } 136 if(OCCUPY(twmWin, tvs->wsw->currentwspc)) { 137 DisplayWin(tvs, twmWin); 138 break; 139 } 140 } 141 } 142 } 143 else if(twmWin->hasfocusvisible) { 144 focuswindow = twmWin; 145 SetFocusVisualAttributes(focuswindow, false); 146 } 147 } 148 } 149 OtpCheckConsistency(); 150 151 /* 152 Reorganize icon manager window lists 153 */ 154 for(twmWin = Scr->FirstWindow; twmWin != NULL; twmWin = twmWin->next) { 155 wl = twmWin->iconmanagerlist; 156 if(wl == NULL) { 157 continue; 158 } 159 if(OCCUPY(wl->iconmgr->twm_win, newws)) { 160 continue; 161 } 162 wl1 = wl; 163 wl = wl->nextv; 164 while(wl != NULL) { 165 if(OCCUPY(wl->iconmgr->twm_win, newws)) { 166 break; 167 } 168 wl1 = wl; 169 wl = wl->nextv; 170 } 171 if(wl != NULL) { 172 wl1->nextv = wl->nextv; 173 wl->nextv = twmWin->iconmanagerlist; 174 twmWin->iconmanagerlist = wl; 175 } 176 } 177 wl = NULL; 178 for(iconmgr = newws->iconmgr; iconmgr; iconmgr = iconmgr->next) { 179 if(iconmgr->first) { 180 wl = iconmgr->first; 181 break; 182 } 183 } 184 CurrentIconManagerEntry(wl); 185 if(focuswindow) { 186 SetFocusVisualAttributes(focuswindow, true); 187 } 188 vs->wsw->currentwspc = newws; 189 if(Scr->ReverseCurrentWorkspace && vs->wsw->state == WMS_map) { 190 MapSubwindow *msw = vs->wsw->mswl [oldws->number]; 191 for(winl = msw->wl; winl != NULL; winl = winl->next) { 192 WMapRedrawName(vs, winl); 193 } 194 msw = vs->wsw->mswl [newws->number]; 195 for(winl = msw->wl; winl != NULL; winl = winl->next) { 196 WMapRedrawName(vs, winl); 197 } 198 } 199 else if(vs->wsw->state == WMS_buttons) { 200 ButtonSubwindow *bsw = vs->wsw->bswl [oldws->number]; 201 PaintWsButton(WSPCWINDOW, vs, bsw->w, oldws->label, oldws->cp, off); 202 bsw = vs->wsw->bswl [newws->number]; 203 PaintWsButton(WSPCWINDOW, vs, bsw->w, newws->label, newws->cp, on); 204 } 205 oldws->iconmgr = Scr->iconmgr; 206 Scr->iconmgr = newws->iconmgr; 207 208 /* XXX X-ref CTAG_BGDRAW in CreateWorkSpaceManager() and above */ 209 oldw = vs->wsw->mswl [oldws->number]->w; 210 neww = vs->wsw->mswl [newws->number]->w; 211 if(useBackgroundInfo) { 212 if(oldws->image == NULL || Scr->NoImagesInWorkSpaceManager) { 213 XSetWindowBackground(dpy, oldw, oldws->backcp.back); 214 } 215 else { 216 XSetWindowBackgroundPixmap(dpy, oldw, oldws->image->pixmap); 217 } 218 } 219 else { 220 if(Scr->workSpaceMgr.defImage == NULL || Scr->NoImagesInWorkSpaceManager) { 221 XSetWindowBackground(dpy, oldw, Scr->workSpaceMgr.defColors.back); 222 } 223 else { 224 XSetWindowBackgroundPixmap(dpy, oldw, Scr->workSpaceMgr.defImage->pixmap); 225 } 226 } 227 attr.border_pixel = Scr->workSpaceMgr.defBorderColor; 228 XChangeWindowAttributes(dpy, oldw, CWBorderPixel, &attr); 229 230 if(Scr->workSpaceMgr.curImage == NULL) { 231 if(Scr->workSpaceMgr.curPaint) { 232 XSetWindowBackground(dpy, neww, Scr->workSpaceMgr.curColors.back); 233 } 234 } 235 else { 236 XSetWindowBackgroundPixmap(dpy, neww, Scr->workSpaceMgr.curImage->pixmap); 237 } 238 attr.border_pixel = Scr->workSpaceMgr.curBorderColor; 239 XChangeWindowAttributes(dpy, neww, CWBorderPixel, &attr); 240 241 XClearWindow(dpy, oldw); 242 XClearWindow(dpy, neww); 243 244 eventMask = mask_out_event(Scr->Root, PropertyChangeMask); 245 246 XChangeProperty(dpy, Scr->Root, XA_WM_CURRENTWORKSPACE, XA_STRING, 8, 247 PropModeReplace, (unsigned char *) newws->name, strlen(newws->name)); 248#ifdef EWMH 249 { 250 long number = newws->number; 251 /* 252 * TODO: this should probably not use Scr->Root but ->XineramaRoot. 253 * That is the real root window if we're using virtual screens. 254 * Also, on the real root it would need values for each of the 255 * virtual roots, but that doesn't fit in the EWMH ideas. 256 */ 257 XChangeProperty(dpy, Scr->Root, XA__NET_CURRENT_DESKTOP, 258 XA_CARDINAL, 32, 259 PropModeReplace, (unsigned char *) &number, 1); 260 } 261#endif /* EWMH */ 262 263 restore_mask(Scr->Root, eventMask); 264 265 /* XDestroyWindow (dpy, cachew);*/ 266 if(Scr->ChangeWorkspaceFunction.func != 0) { 267 char *action; 268 XEvent event; 269 270 action = Scr->ChangeWorkspaceFunction.item ? 271 Scr->ChangeWorkspaceFunction.item->action : NULL; 272 ExecuteFunction(Scr->ChangeWorkspaceFunction.func, action, 273 (Window) 0, NULL, &event, C_ROOT, false); 274 } 275 276 /* If SaveWorkspaceFocus is on, try to restore the focus to the last 277 window which was focused when we left this workspace. */ 278 if(Scr->SaveWorkspaceFocus && newws->save_focus) { 279 twmWin = newws->save_focus; 280 if(OCCUPY(twmWin, newws)) { /* check should not even be needed anymore */ 281 WarpToWindow(twmWin, false); 282 } 283 else { 284 newws->save_focus = NULL; 285 } 286 } 287 288 /* keep track of the order of the workspaces across restarts */ 289 CtwmSetVScreenMap(dpy, Scr->Root, Scr->vScreenList); 290 291 XSync(dpy, 0); 292 if(Scr->ClickToFocus || Scr->SloppyFocus) { 293 set_last_window(newws); 294 } 295 MaybeAnimate = true; 296} 297 298 299 300/* 301 * Various frontends to GotoWorkSpace() 302 */ 303 304/* 305 * Simplify redundant checks. If no multiple workspaces, or no vs given 306 * to the func, there's nothing to do. 307 */ 308#define GWS_CHECK do { \ 309 if(! Scr->workSpaceManagerActive) { \ 310 return; \ 311 } \ 312 if(!vs) { \ 313 return; \ 314 } \ 315 } while(0) 316 317void 318GotoWorkSpaceByName(VirtualScreen *vs, const char *wname) 319{ 320 WorkSpace *ws; 321 322 GWS_CHECK; 323 324 ws = GetWorkspace(wname); 325 if(ws == NULL) { 326 return; 327 } 328 GotoWorkSpace(vs, ws); 329} 330 331 332void 333GotoWorkSpaceByNumber(VirtualScreen *vs, int workspacenum) 334{ 335 WorkSpace *ws; 336 337 GWS_CHECK; 338 339 for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) { 340 if(ws->number == workspacenum) { 341 break; 342 } 343 } 344 if(ws == NULL) { 345 return; 346 } 347 GotoWorkSpace(vs, ws); 348} 349 350 351void 352GotoPrevWorkSpace(VirtualScreen *vs) 353{ 354 WorkSpace *ws1, *ws2; 355 356 GWS_CHECK; 357 358 ws1 = Scr->workSpaceMgr.workSpaceList; 359 if(ws1 == NULL) { 360 return; 361 } 362 ws2 = ws1->next; 363 364 while((ws2 != vs->wsw->currentwspc) && (ws2 != NULL)) { 365 ws1 = ws2; 366 ws2 = ws2->next; 367 } 368 GotoWorkSpace(vs, ws1); 369} 370 371 372void 373GotoNextWorkSpace(VirtualScreen *vs) 374{ 375 WorkSpace *ws; 376 377 GWS_CHECK; 378 379 ws = vs->wsw->currentwspc; 380 ws = (ws->next != NULL) ? ws->next : Scr->workSpaceMgr.workSpaceList; 381 GotoWorkSpace(vs, ws); 382} 383 384 385void 386GotoRightWorkSpace(VirtualScreen *vs) 387{ 388 WorkSpace *ws; 389 int number, columns, count; 390 391 GWS_CHECK; 392 393 ws = vs->wsw->currentwspc; 394 number = ws->number; 395 columns = Scr->workSpaceMgr.columns; 396 count = Scr->workSpaceMgr.count; 397 number++; 398 if((number % columns) == 0) { 399 number -= columns; 400 } 401 else if(number >= count) { 402 number = (number / columns) * columns; 403 } 404 405 GotoWorkSpaceByNumber(vs, number); 406} 407 408 409void 410GotoLeftWorkSpace(VirtualScreen *vs) 411{ 412 WorkSpace *ws; 413 int number, columns, count; 414 415 GWS_CHECK; 416 417 ws = vs->wsw->currentwspc; 418 number = ws->number; 419 columns = Scr->workSpaceMgr.columns; 420 count = Scr->workSpaceMgr.count; 421 number += (number % columns) ? -1 : (columns - 1); 422 if(number >= count) { 423 number = count - 1; 424 } 425 GotoWorkSpaceByNumber(vs, number); 426} 427 428 429void 430GotoUpWorkSpace(VirtualScreen *vs) 431{ 432 WorkSpace *ws; 433 int number, lines, columns, count; 434 435 GWS_CHECK; 436 437 ws = vs->wsw->currentwspc; 438 number = ws->number; 439 lines = Scr->workSpaceMgr.lines; 440 columns = Scr->workSpaceMgr.columns; 441 count = Scr->workSpaceMgr.count; 442 number -= columns; 443 if(number < 0) { 444 number += lines * columns; 445 /* If the number of workspaces is not a multiple of nr of columns */ 446 if(number >= count) { 447 number -= columns; 448 } 449 } 450 GotoWorkSpaceByNumber(vs, number); 451} 452 453 454void 455GotoDownWorkSpace(VirtualScreen *vs) 456{ 457 WorkSpace *ws; 458 int number, columns, count; 459 460 GWS_CHECK; 461 462 ws = vs->wsw->currentwspc; 463 number = ws->number; 464 columns = Scr->workSpaceMgr.columns; 465 count = Scr->workSpaceMgr.count; 466 number += columns; 467 if(number >= count) { 468 number %= columns; 469 } 470 GotoWorkSpaceByNumber(vs, number); 471} 472 473#undef GWS_CHECK 474 475 476 477/* 478 * Show the background (by hiding all windows) or undo it. 479 * f.showbackground, also can be called via EWMH bits. 480 * 481 * newstate is the desired showing state. 482 * Pass -1 to toggle, 1 to show the background, 483 * or 0 to re-show the windows. 484 * 485 * XXX Doesn't really belong here; more of a functions.c-ish thing 486 * probably. But left here for the moment. 487 */ 488void 489ShowBackground(VirtualScreen *vs, int newstate) 490{ 491 static int state = 0; 492 TwmWindow *twmWin; 493 494 if(newstate == state) { 495 return; 496 } 497 498 if(state) { 499 for(twmWin = Scr->FirstWindow; twmWin != NULL; twmWin = twmWin->next) { 500 if(twmWin->savevs == vs) { 501 DisplayWin(vs, twmWin); 502 } 503 twmWin->savevs = NULL; 504 } 505 state = 0; 506 } 507 else { 508 for(twmWin = Scr->FirstWindow; twmWin != NULL; twmWin = twmWin->next) { 509 if(twmWin->vs == vs 510#ifdef EWMH 511 /* leave wt_Desktop and wt_Dock visible */ 512 && twmWin->ewmhWindowType == wt_Normal 513#endif /* EWMH */ 514 ) { 515 twmWin->savevs = twmWin->vs; 516 Vanish(vs, twmWin); 517 } 518 } 519 state = 1; 520 } 521#ifdef EWMH 522 EwmhSet_NET_SHOWING_DESKTOP(state); 523#endif /* EWMH */ 524} 525 526 527/* 528 * Get the name of the currently active WS. Used in Execute() for 529 * sub'ing in $currentworkspace in executing commands. 530 */ 531char * 532GetCurrentWorkSpaceName(VirtualScreen *vs) 533{ 534 if(! Scr->workSpaceManagerActive) { 535 return (NULL); 536 } 537 if(!vs) { 538 vs = Scr->vScreenList; 539 } 540 return vs->wsw->currentwspc->name; 541} 542 543 544/* 545 * Find workspace by name 546 */ 547WorkSpace * 548GetWorkspace(const char *wname) 549{ 550 WorkSpace *ws; 551 552 /* Guard */ 553 if(!wname) { 554 return (NULL); 555 } 556 557 /* Check by label */ 558 for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) { 559 if(strcmp(ws->label, wname) == 0) { 560 return ws; 561 } 562 } 563 564 /* Check by name */ 565 for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) { 566 if(strcmp(ws->name, wname) == 0) { 567 return ws; 568 } 569 } 570 571 /* Nope */ 572 return NULL; 573} 574