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 123 Vanish(vs, twmWin); 124#ifdef VSCREEN 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 VirtualScreen *tvs; 133 for(tvs = Scr->vScreenList; tvs != NULL; tvs = tvs->next) { 134 if(tvs == vs) { /* no, not back on the old one */ 135 continue; 136 } 137 if(OCCUPY(twmWin, tvs->wsw->currentwspc)) { 138 DisplayWin(tvs, twmWin); 139 break; 140 } 141 } 142 } 143#endif 144 } 145 else if(twmWin->hasfocusvisible) { 146 focuswindow = twmWin; 147 SetFocusVisualAttributes(focuswindow, false); 148 } 149 } 150 } 151 OtpCheckConsistency(); 152 153 /* 154 Reorganize icon manager window lists 155 */ 156 for(twmWin = Scr->FirstWindow; twmWin != NULL; twmWin = twmWin->next) { 157 wl = twmWin->iconmanagerlist; 158 if(wl == NULL) { 159 continue; 160 } 161 if(OCCUPY(wl->iconmgr->twm_win, newws)) { 162 continue; 163 } 164 wl1 = wl; 165 wl = wl->nextv; 166 while(wl != NULL) { 167 if(OCCUPY(wl->iconmgr->twm_win, newws)) { 168 break; 169 } 170 wl1 = wl; 171 wl = wl->nextv; 172 } 173 if(wl != NULL) { 174 wl1->nextv = wl->nextv; 175 wl->nextv = twmWin->iconmanagerlist; 176 twmWin->iconmanagerlist = wl; 177 } 178 } 179 wl = NULL; 180 for(iconmgr = newws->iconmgr; iconmgr; iconmgr = iconmgr->next) { 181 if(iconmgr->first) { 182 wl = iconmgr->first; 183 break; 184 } 185 } 186 CurrentIconManagerEntry(wl); 187 if(focuswindow) { 188 SetFocusVisualAttributes(focuswindow, true); 189 } 190 vs->wsw->currentwspc = newws; 191 if(Scr->ReverseCurrentWorkspace && vs->wsw->state == WMS_map) { 192 MapSubwindow *msw = vs->wsw->mswl [oldws->number]; 193 for(winl = msw->wl; winl != NULL; winl = winl->next) { 194 WMapRedrawName(vs, winl); 195 } 196 msw = vs->wsw->mswl [newws->number]; 197 for(winl = msw->wl; winl != NULL; winl = winl->next) { 198 WMapRedrawName(vs, winl); 199 } 200 } 201 else if(vs->wsw->state == WMS_buttons) { 202 ButtonSubwindow *bsw = vs->wsw->bswl [oldws->number]; 203 PaintWsButton(WSPCWINDOW, vs, bsw->w, oldws->label, oldws->cp, off); 204 bsw = vs->wsw->bswl [newws->number]; 205 PaintWsButton(WSPCWINDOW, vs, bsw->w, newws->label, newws->cp, on); 206 } 207 oldws->iconmgr = Scr->iconmgr; 208 Scr->iconmgr = newws->iconmgr; 209 210 /* XXX X-ref CTAG_BGDRAW in CreateWorkSpaceManager() and above */ 211 oldw = vs->wsw->mswl [oldws->number]->w; 212 neww = vs->wsw->mswl [newws->number]->w; 213 if(useBackgroundInfo) { 214 if(oldws->image == NULL || Scr->NoImagesInWorkSpaceManager) { 215 XSetWindowBackground(dpy, oldw, oldws->backcp.back); 216 } 217 else { 218 XSetWindowBackgroundPixmap(dpy, oldw, oldws->image->pixmap); 219 } 220 } 221 else { 222 if(Scr->workSpaceMgr.defImage == NULL || Scr->NoImagesInWorkSpaceManager) { 223 XSetWindowBackground(dpy, oldw, Scr->workSpaceMgr.defColors.back); 224 } 225 else { 226 XSetWindowBackgroundPixmap(dpy, oldw, Scr->workSpaceMgr.defImage->pixmap); 227 } 228 } 229 attr.border_pixel = Scr->workSpaceMgr.defBorderColor; 230 XChangeWindowAttributes(dpy, oldw, CWBorderPixel, &attr); 231 232 if(Scr->workSpaceMgr.curImage == NULL) { 233 if(Scr->workSpaceMgr.curPaint) { 234 XSetWindowBackground(dpy, neww, Scr->workSpaceMgr.curColors.back); 235 } 236 } 237 else { 238 XSetWindowBackgroundPixmap(dpy, neww, Scr->workSpaceMgr.curImage->pixmap); 239 } 240 attr.border_pixel = Scr->workSpaceMgr.curBorderColor; 241 XChangeWindowAttributes(dpy, neww, CWBorderPixel, &attr); 242 243 XClearWindow(dpy, oldw); 244 XClearWindow(dpy, neww); 245 246 eventMask = mask_out_event(Scr->Root, PropertyChangeMask); 247 248 XChangeProperty(dpy, Scr->Root, XA_WM_CURRENTWORKSPACE, XA_STRING, 8, 249 PropModeReplace, (unsigned char *) newws->name, strlen(newws->name)); 250#ifdef EWMH 251 { 252 long number = newws->number; 253 /* 254 * TODO: this should probably not use Scr->Root but ->XineramaRoot. 255 * That is the real root window if we're using virtual screens. 256 * Also, on the real root it would need values for each of the 257 * virtual roots, but that doesn't fit in the EWMH ideas. 258 */ 259 XChangeProperty(dpy, Scr->Root, XA__NET_CURRENT_DESKTOP, 260 XA_CARDINAL, 32, 261 PropModeReplace, (unsigned char *) &number, 1); 262 } 263#endif /* EWMH */ 264 265 restore_mask(Scr->Root, eventMask); 266 267 /* XDestroyWindow (dpy, cachew);*/ 268 if(Scr->ChangeWorkspaceFunction.func != 0) { 269 char *action; 270 XEvent event; 271 272 action = Scr->ChangeWorkspaceFunction.item ? 273 Scr->ChangeWorkspaceFunction.item->action : NULL; 274 ExecuteFunction(Scr->ChangeWorkspaceFunction.func, action, 275 (Window) 0, NULL, &event, C_ROOT, false); 276 } 277 278 /* If SaveWorkspaceFocus is on, try to restore the focus to the last 279 window which was focused when we left this workspace. */ 280 if(Scr->SaveWorkspaceFocus && newws->save_focus) { 281 twmWin = newws->save_focus; 282 if(OCCUPY(twmWin, newws)) { /* check should not even be needed anymore */ 283 WarpToWindow(twmWin, false); 284 } 285 else { 286 newws->save_focus = NULL; 287 } 288 } 289 290 /* keep track of the order of the workspaces across restarts */ 291 CtwmSetVScreenMap(dpy, Scr->Root, Scr->vScreenList); 292 293 XSync(dpy, 0); 294 if(Scr->ClickToFocus || Scr->SloppyFocus) { 295 set_last_window(newws); 296 } 297 MaybeAnimate = true; 298} 299 300 301 302/* 303 * Various frontends to GotoWorkSpace() 304 */ 305 306/* 307 * Simplify redundant checks. If no multiple workspaces, or no vs given 308 * to the func, there's nothing to do. 309 */ 310#define GWS_CHECK do { \ 311 if(! Scr->workSpaceManagerActive) { \ 312 return; \ 313 } \ 314 if(!vs) { \ 315 return; \ 316 } \ 317 } while(0) 318 319void 320GotoWorkSpaceByName(VirtualScreen *vs, const char *wname) 321{ 322 WorkSpace *ws; 323 324 GWS_CHECK; 325 326 ws = GetWorkspace(wname); 327 if(ws == NULL) { 328 return; 329 } 330 GotoWorkSpace(vs, ws); 331} 332 333 334void 335GotoWorkSpaceByNumber(VirtualScreen *vs, int workspacenum) 336{ 337 WorkSpace *ws; 338 339 GWS_CHECK; 340 341 for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) { 342 if(ws->number == workspacenum) { 343 break; 344 } 345 } 346 if(ws == NULL) { 347 return; 348 } 349 GotoWorkSpace(vs, ws); 350} 351 352 353void 354GotoPrevWorkSpace(VirtualScreen *vs) 355{ 356 WorkSpace *ws1, *ws2; 357 358 GWS_CHECK; 359 360 ws1 = Scr->workSpaceMgr.workSpaceList; 361 if(ws1 == NULL) { 362 return; 363 } 364 ws2 = ws1->next; 365 366 while((ws2 != vs->wsw->currentwspc) && (ws2 != NULL)) { 367 ws1 = ws2; 368 ws2 = ws2->next; 369 } 370 GotoWorkSpace(vs, ws1); 371} 372 373 374void 375GotoNextWorkSpace(VirtualScreen *vs) 376{ 377 WorkSpace *ws; 378 379 GWS_CHECK; 380 381 ws = vs->wsw->currentwspc; 382 ws = (ws->next != NULL) ? ws->next : Scr->workSpaceMgr.workSpaceList; 383 GotoWorkSpace(vs, ws); 384} 385 386 387void 388GotoRightWorkSpace(VirtualScreen *vs) 389{ 390 WorkSpace *ws; 391 int number, columns, count; 392 393 GWS_CHECK; 394 395 ws = vs->wsw->currentwspc; 396 number = ws->number; 397 columns = Scr->workSpaceMgr.columns; 398 count = Scr->workSpaceMgr.count; 399 number++; 400 if((number % columns) == 0) { 401 number -= columns; 402 } 403 else if(number >= count) { 404 number = (number / columns) * columns; 405 } 406 407 GotoWorkSpaceByNumber(vs, number); 408} 409 410 411void 412GotoLeftWorkSpace(VirtualScreen *vs) 413{ 414 WorkSpace *ws; 415 int number, columns, count; 416 417 GWS_CHECK; 418 419 ws = vs->wsw->currentwspc; 420 number = ws->number; 421 columns = Scr->workSpaceMgr.columns; 422 count = Scr->workSpaceMgr.count; 423 number += (number % columns) ? -1 : (columns - 1); 424 if(number >= count) { 425 number = count - 1; 426 } 427 GotoWorkSpaceByNumber(vs, number); 428} 429 430 431void 432GotoUpWorkSpace(VirtualScreen *vs) 433{ 434 WorkSpace *ws; 435 int number, lines, columns, count; 436 437 GWS_CHECK; 438 439 ws = vs->wsw->currentwspc; 440 number = ws->number; 441 lines = Scr->workSpaceMgr.lines; 442 columns = Scr->workSpaceMgr.columns; 443 count = Scr->workSpaceMgr.count; 444 number -= columns; 445 if(number < 0) { 446 number += lines * columns; 447 /* If the number of workspaces is not a multiple of nr of columns */ 448 if(number >= count) { 449 number -= columns; 450 } 451 } 452 GotoWorkSpaceByNumber(vs, number); 453} 454 455 456void 457GotoDownWorkSpace(VirtualScreen *vs) 458{ 459 WorkSpace *ws; 460 int number, columns, count; 461 462 GWS_CHECK; 463 464 ws = vs->wsw->currentwspc; 465 number = ws->number; 466 columns = Scr->workSpaceMgr.columns; 467 count = Scr->workSpaceMgr.count; 468 number += columns; 469 if(number >= count) { 470 number %= columns; 471 } 472 GotoWorkSpaceByNumber(vs, number); 473} 474 475#undef GWS_CHECK 476 477 478 479/* 480 * Show the background (by hiding all windows) or undo it. 481 * f.showbackground, also can be called via EWMH bits. 482 * 483 * newstate is the desired showing state. 484 * Pass -1 to toggle, 1 to show the background, 485 * or 0 to re-show the windows. 486 * 487 * XXX Doesn't really belong here; more of a functions.c-ish thing 488 * probably. But left here for the moment. 489 */ 490void 491ShowBackground(VirtualScreen *vs, int newstate) 492{ 493 static int state = 0; 494 TwmWindow *twmWin; 495 496 if(newstate == state) { 497 return; 498 } 499 500 if(state) { 501 for(twmWin = Scr->FirstWindow; twmWin != NULL; twmWin = twmWin->next) { 502 if(twmWin->savevs == vs) { 503 DisplayWin(vs, twmWin); 504 } 505 twmWin->savevs = NULL; 506 } 507 state = 0; 508 } 509 else { 510 for(twmWin = Scr->FirstWindow; twmWin != NULL; twmWin = twmWin->next) { 511 if(twmWin->vs == vs 512#ifdef EWMH 513 /* leave wt_Desktop and wt_Dock visible */ 514 && twmWin->ewmhWindowType == wt_Normal 515#endif /* EWMH */ 516 ) { 517 twmWin->savevs = twmWin->vs; 518 Vanish(vs, twmWin); 519 } 520 } 521 state = 1; 522 } 523#ifdef EWMH 524 EwmhSet_NET_SHOWING_DESKTOP(state); 525#endif /* EWMH */ 526} 527 528 529/* 530 * Get the name of the currently active WS. Used in Execute() for 531 * sub'ing in $currentworkspace in executing commands. 532 */ 533char * 534GetCurrentWorkSpaceName(VirtualScreen *vs) 535{ 536 if(! Scr->workSpaceManagerActive) { 537 return (NULL); 538 } 539 if(!vs) { 540 vs = Scr->vScreenList; 541 } 542 return vs->wsw->currentwspc->name; 543} 544 545 546/* 547 * Find workspace by name 548 */ 549WorkSpace * 550GetWorkspace(const char *wname) 551{ 552 WorkSpace *ws; 553 554 /* Guard */ 555 if(!wname) { 556 return (NULL); 557 } 558 559 /* Check by label */ 560 for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) { 561 if(strcmp(ws->label, wname) == 0) { 562 return ws; 563 } 564 } 565 566 /* Check by name */ 567 for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) { 568 if(strcmp(ws->name, wname) == 0) { 569 return ws; 570 } 571 } 572 573 /* Nope */ 574 return NULL; 575} 576