1 /* workspace.c- Workspace management
3 * Window Maker window manager
5 * Copyright (c) 1997, 1998 Alfredo K. Kojima
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
25 #include <X11/Xutil.h>
27 #include <X11/extensions/shape.h>
36 #include "WindowMaker.h"
43 #include "application.h"
46 #include "workspace.h"
58 extern WPreferences wPreferences
;
59 extern XContext wWinContext
;
62 static proplist_t dWorkspaces
=NULL
;
63 static proplist_t dClip
, dName
;
69 if (dWorkspaces
!=NULL
)
72 dWorkspaces
= PLMakeString("Workspaces");
73 dName
= PLMakeString("Name");
74 dClip
= PLMakeString("Clip");
79 wWorkspaceMake(WScreen
*scr
, int count
)
89 wWorkspaceNew(WScreen
*scr
)
91 WWorkspace
*wspace
, **list
;
94 if (scr
->workspace_count
< MAX_WORKSPACES
) {
95 scr
->workspace_count
++;
97 wspace
= wmalloc(sizeof(WWorkspace
));
101 if (scr
->flags
.kwm_syncing_count
) {
102 wspace
->name
= wKWMGetWorkspaceName(scr
, scr
->workspace_count
-1);
106 wspace
->name
= wmalloc(strlen(_("Workspace %i"))+8);
107 sprintf(wspace
->name
, _("Workspace %i"), scr
->workspace_count
);
111 if (!wPreferences
.flags
.noclip
) {
112 wspace
->clip
= wDockCreate(scr
, WM_CLIP
);
116 list
= wmalloc(sizeof(WWorkspace
*)*scr
->workspace_count
);
118 for (i
=0; i
<scr
->workspace_count
-1; i
++) {
119 list
[i
] = scr
->workspaces
[i
];
122 free(scr
->workspaces
);
123 scr
->workspaces
= list
;
125 wWorkspaceMenuUpdate(scr
, scr
->workspace_menu
);
126 wWorkspaceMenuUpdate(scr
, scr
->clip_ws_menu
);
128 wGNOMEUpdateWorkspaceHints(scr
);
131 if (!scr
->flags
.kwm_syncing_count
) {
132 wKWMUpdateWorkspaceCountHint(scr
);
133 wKWMUpdateWorkspaceNameHint(scr
, scr
->workspace_count
-1);
136 wKWMSetUsableAreaHint(scr
, scr
->workspace_count
-1);
141 return scr
->workspace_count
-1;
149 wWorkspaceDelete(WScreen
*scr
, int workspace
)
159 /* verify if workspace is in use by some window */
160 tmp
= scr
->focused_window
;
162 if (!IS_OMNIPRESENT(tmp
) && tmp
->frame
->workspace
==workspace
)
167 if (!wPreferences
.flags
.noclip
) {
168 wDockDestroy(scr
->workspaces
[workspace
]->clip
);
169 scr
->workspaces
[workspace
]->clip
= NULL
;
172 list
= wmalloc(sizeof(WWorkspace
*)*(scr
->workspace_count
-1));
174 for (i
=0; i
<scr
->workspace_count
; i
++) {
176 list
[j
++] = scr
->workspaces
[i
];
178 if (scr
->workspaces
[i
]->name
)
179 free(scr
->workspaces
[i
]->name
);
180 free(scr
->workspaces
[i
]);
183 free(scr
->workspaces
);
184 scr
->workspaces
= list
;
186 scr
->workspace_count
--;
190 wWorkspaceMenuUpdate(scr
, scr
->workspace_menu
);
191 /* clip workspace menu */
192 wWorkspaceMenuUpdate(scr
, scr
->clip_ws_menu
);
194 /* update also window menu */
195 if (scr
->workspace_submenu
) {
196 WMenu
*menu
= scr
->workspace_submenu
;
199 while (i
>scr
->workspace_count
)
200 wMenuRemoveItem(menu
, --i
);
204 if (scr
->clip_submenu
) {
205 WMenu
*menu
= scr
->clip_submenu
;
208 while (i
>scr
->workspace_count
)
209 wMenuRemoveItem(menu
, --i
);
214 wGNOMEUpdateWorkspaceHints(scr
);
217 wKWMUpdateWorkspaceCountHint(scr
);
220 if (scr
->current_workspace
>= scr
->workspace_count
)
221 wWorkspaceChange(scr
, scr
->workspace_count
-1);
227 typedef struct WorkspaceNameData
{
236 hideWorkpaceName(void *data
)
238 WScreen
*scr
= (WScreen
*)data
;
240 if (!scr
->workspace_name_data
|| scr
->workspace_name_data
->count
== 0) {
241 XUnmapWindow(dpy
, scr
->workspace_name
);
243 if (scr
->workspace_name_data
) {
244 RDestroyImage(scr
->workspace_name_data
->back
);
245 RDestroyImage(scr
->workspace_name_data
->text
);
246 free(scr
->workspace_name_data
);
248 scr
->workspace_name_data
= NULL
;
250 scr
->workspace_name_timer
= NULL
;
252 RImage
*img
= RCloneImage(scr
->workspace_name_data
->back
);
255 scr
->workspace_name_timer
= WMAddTimerHandler(30, hideWorkpaceName
,
258 RCombineImagesWithOpaqueness(img
, scr
->workspace_name_data
->text
,
259 scr
->workspace_name_data
->count
*255/10);
261 RConvertImage(scr
->rcontext
, img
, &pix
);
265 XSetWindowBackgroundPixmap(dpy
, scr
->workspace_name
, pix
);
266 XClearWindow(dpy
, scr
->workspace_name
);
267 XFreePixmap(dpy
, pix
);
270 scr
->workspace_name_data
->count
--;
277 showWorkspaceName(WScreen
*scr
, int workspace
)
279 WorkspaceNameData
*data
;
283 char *name
= scr
->workspaces
[workspace
]->name
;
284 int len
= strlen(name
);
287 if (scr
->workspace_name_timer
) {
288 WMDeleteTimerHandler(scr
->workspace_name_timer
);
289 XUnmapWindow(dpy
, scr
->workspace_name
);
293 if (scr
->workspace_name_data
) {
294 RDestroyImage(scr
->workspace_name_data
->back
);
295 RDestroyImage(scr
->workspace_name_data
->text
);
296 free(scr
->workspace_name_data
);
300 XSetFont(dpy
, scr
->mono_gc
, scr
->workspace_name_font
->font
->fid
);
301 XSetFont(dpy
, scr
->draw_gc
, scr
->workspace_name_font
->font
->fid
);
304 data
= wmalloc(sizeof(WorkspaceNameData
));
306 w
= wTextWidth(scr
->workspace_name_font
->font
, name
, len
);
307 h
= scr
->workspace_name_font
->height
;
309 XResizeWindow(dpy
, scr
->workspace_name
, w
+4, h
+4);
310 XMoveWindow(dpy
, scr
->workspace_name
, (scr
->scr_width
- (w
+4))/2,
311 (scr
->scr_height
- (h
+4))/2);
313 text
= XCreatePixmap(dpy
, scr
->w_win
, w
+4, h
+4, scr
->w_depth
);
314 mask
= XCreatePixmap(dpy
, scr
->w_win
, w
+4, h
+4, 1);
316 XSetForeground(dpy
, scr
->draw_gc
, scr
->black_pixel
);
317 XFillRectangle(dpy
, text
, scr
->draw_gc
, 0, 0, w
+4, h
+4);
319 XSetForeground(dpy
, scr
->mono_gc
, 0);
320 XFillRectangle(dpy
, mask
, scr
->mono_gc
, 0, 0, w
+4, h
+4);
322 XSetForeground(dpy
, scr
->mono_gc
, 1);
323 for (x
= 0; x
<= 4; x
++) {
324 for (y
= 0; y
<= 4; y
++) {
325 wDrawString(mask
, scr
->workspace_name_font
, scr
->mono_gc
,
326 x
, scr
->workspace_name_font
->y
+ y
, name
, len
);
330 XSetForeground(dpy
, scr
->draw_gc
, scr
->white_pixel
);
331 wDrawString(text
, scr
->workspace_name_font
, scr
->draw_gc
,
332 2, scr
->workspace_name_font
->y
+ 2,
333 scr
->workspaces
[workspace
]->name
,
334 strlen(scr
->workspaces
[workspace
]->name
));
336 XShapeCombineMask(dpy
, scr
->workspace_name
, ShapeBounding
, 0, 0, mask
,
339 XSetWindowBackgroundPixmap(dpy
, scr
->workspace_name
, text
);
340 XClearWindow(dpy
, scr
->workspace_name
);
342 data
->text
= RCreateImageFromDrawable(scr
->rcontext
, text
, None
);
344 XFreePixmap(dpy
, text
);
345 XFreePixmap(dpy
, mask
);
348 XMapRaised(dpy
, scr
->workspace_name
);
354 ximg
= RGetXImage(scr
->rcontext
, scr
->root_win
,
355 (scr
->scr_width
- data
->text
->width
)/2,
356 (scr
->scr_height
- data
->text
->height
)/2,
357 data
->text
->width
, data
->text
->height
);
363 XMapRaised(dpy
, scr
->workspace_name
);
366 data
->back
= RCreateImageFromXImage(scr
->rcontext
, ximg
->image
, NULL
);
367 RDestroyXImage(scr
->rcontext
, ximg
);
375 scr
->workspace_name_data
= data
;
377 scr
->workspace_name_timer
= WMAddTimerHandler(300, hideWorkpaceName
, scr
);
383 RDestroyImage(data
->text
);
385 RDestroyImage(data
->back
);
388 scr
->workspace_name_data
= NULL
;
390 scr
->workspace_name_timer
= WMAddTimerHandler(600, hideWorkpaceName
, scr
);
395 wWorkspaceChange(WScreen
*scr
, int workspace
)
397 if (scr
->flags
.startup
|| scr
->flags
.startup2
) {
401 if (workspace
!= scr
->current_workspace
) {
402 wWorkspaceForceChange(scr
, workspace
);
404 showWorkspaceName(scr
, workspace
);
410 wWorkspaceRelativeChange(WScreen
*scr
, int amount
)
414 w
= scr
->current_workspace
+ amount
;
419 wWorkspaceChange(scr
, w
);
420 else if (wPreferences
.ws_cycle
)
421 wWorkspaceChange(scr
, scr
->workspace_count
+ w
);
423 } else if (amount
> 0) {
425 if (w
< scr
->workspace_count
)
426 wWorkspaceChange(scr
, w
);
427 else if (wPreferences
.ws_advance
)
428 wWorkspaceChange(scr
, WMIN(w
, MAX_WORKSPACES
-1));
429 else if (wPreferences
.ws_cycle
)
430 wWorkspaceChange(scr
, w
% scr
->workspace_count
);
437 wWorkspaceForceChange(WScreen
*scr
, int workspace
)
439 WWindow
*tmp
, *foc
=NULL
, *foc2
=NULL
;
441 if (workspace
>= MAX_WORKSPACES
|| workspace
< 0)
444 SendHelperMessage(scr
, 'C', workspace
+1, NULL
);
446 if (workspace
> scr
->workspace_count
-1) {
447 wWorkspaceMake(scr
, workspace
- scr
->workspace_count
+ 1);
450 wClipUpdateForWorkspaceChange(scr
, workspace
);
452 scr
->current_workspace
= workspace
;
454 wWorkspaceMenuUpdate(scr
, scr
->workspace_menu
);
456 wWorkspaceMenuUpdate(scr
, scr
->clip_ws_menu
);
458 if ((tmp
= scr
->focused_window
)!= NULL
) {
459 if (IS_OMNIPRESENT(tmp
) || tmp
->flags
.changing_workspace
)
463 if (tmp
->frame
->workspace
!=workspace
&& !tmp
->flags
.selected
) {
464 /* unmap windows not on this workspace */
465 if ((tmp
->flags
.mapped
||tmp
->flags
.shaded
)
466 && !IS_OMNIPRESENT(tmp
)
467 && !tmp
->flags
.changing_workspace
) {
471 /* also unmap miniwindows not on this workspace */
472 if (tmp
->flags
.miniaturized
&& !IS_OMNIPRESENT(tmp
)
474 if (!wPreferences
.sticky_icons
) {
475 XUnmapWindow(dpy
, tmp
->icon
->core
->window
);
476 tmp
->icon
->mapped
= 0;
478 tmp
->icon
->mapped
= 1;
479 /* Why is this here? -Alfredo */
480 XMapWindow(dpy
, tmp
->icon
->core
->window
);
483 /* update current workspace of omnipresent windows */
484 if (IS_OMNIPRESENT(tmp
)) {
485 WApplication
*wapp
= wApplicationOf(tmp
->main_window
);
487 tmp
->frame
->workspace
= workspace
;
490 wapp
->last_workspace
= workspace
;
496 /* change selected windows' workspace */
497 if (tmp
->flags
.selected
) {
498 wWindowChangeWorkspace(tmp
, workspace
);
499 if (!tmp
->flags
.miniaturized
&& !foc
) {
503 if (!tmp
->flags
.hidden
) {
504 if (!(tmp
->flags
.mapped
|| tmp
->flags
.miniaturized
)) {
505 /* remap windows that are on this workspace */
510 /* Also map miniwindow if not omnipresent */
511 if (!wPreferences
.sticky_icons
&&
512 tmp
->flags
.miniaturized
&&
513 !IS_OMNIPRESENT(tmp
) && tmp
->icon
) {
514 tmp
->icon
->mapped
= 1;
515 XMapWindow(dpy
, tmp
->icon
->core
->window
);
526 if (scr
->focused_window
->flags
.mapped
&& !foc
) {
527 foc
= scr
->focused_window
;
529 if (wPreferences
.focus_mode
== WKF_CLICK
) {
530 wSetFocusTo(scr
, foc
);
538 if (XQueryPointer(dpy
, scr
->root_win
, &bar
, &win
,
539 &foo
, &foo
, &foo
, &foo
, &mask
)) {
540 tmp
= wWindowFor(win
);
542 if (!tmp
&& wPreferences
.focus_mode
== WKF_SLOPPY
) {
543 wSetFocusTo(scr
, foc
);
545 wSetFocusTo(scr
, tmp
);
550 /* We need to always arrange icons when changing workspace, even if
551 * no autoarrange icons, because else the icons in different workspaces
553 * This can be avoided if appicons are also workspace specific.
555 if (!wPreferences
.sticky_icons
)
556 wArrangeIcons(scr
, False
);
559 wAppIconPaint(scr
->dock
->icon_array
[0]);
560 if (scr
->clip_icon
) {
561 if (scr
->workspaces
[workspace
]->clip
->auto_collapse
||
562 scr
->workspaces
[workspace
]->clip
->auto_raise_lower
) {
563 /* to handle enter notify. This will also */
564 XUnmapWindow(dpy
, scr
->clip_icon
->icon
->core
->window
);
565 XMapWindow(dpy
, scr
->clip_icon
->icon
->core
->window
);
567 wClipIconPaint(scr
->clip_icon
);
571 showWorkspaceName(scr
, workspace
);
574 wGNOMEUpdateCurrentWorkspaceHint(scr
);
577 wKWMUpdateCurrentWorkspaceHint(scr
);
579 /* XSync(dpy, False); */
584 switchWSCommand(WMenu
*menu
, WMenuEntry
*entry
)
586 wWorkspaceChange(menu
->frame
->screen_ptr
, (long)entry
->clientdata
);
592 deleteWSCommand(WMenu
*menu
, WMenuEntry
*entry
)
594 wWorkspaceDelete(menu
->frame
->screen_ptr
,
595 menu
->frame
->screen_ptr
->workspace_count
-1);
601 newWSCommand(WMenu
*menu
, WMenuEntry
*foo
)
605 ws
= wWorkspaceNew(menu
->frame
->screen_ptr
);
606 /* autochange workspace*/
608 wWorkspaceChange(menu
->frame
->screen_ptr
, ws
);
614 if (wKeyBindings[WKBD_WORKSPACE1+ws]) {
615 kcode = wKeyBindings[WKBD_WORKSPACE1+ws]->keycode;
617 wstrdup(XKeysymToString(XKeycodeToKeysym(dpy, kcode, 0)));
632 end
= &(line
[strlen(line
)])-1;
633 while (isspace(*line
) && *line
!=0) line
++;
634 while (isspace(*end
) && end
!=line
) {
643 wWorkspaceRename(WScreen
*scr
, int workspace
, char *name
)
645 char buf
[MAX_WORKSPACENAME_WIDTH
+1];
648 if (workspace
>= scr
->workspace_count
)
651 /* trim white spaces */
652 tmp
= cropline(name
);
654 if (strlen(tmp
)==0) {
655 sprintf(buf
, _("Workspace %i"), workspace
+1);
657 strncpy(buf
, tmp
, MAX_WORKSPACENAME_WIDTH
);
659 buf
[MAX_WORKSPACENAME_WIDTH
] = 0;
661 /* update workspace */
662 free(scr
->workspaces
[workspace
]->name
);
663 scr
->workspaces
[workspace
]->name
= wstrdup(buf
);
665 if (scr
->clip_ws_menu
) {
666 if (strcmp(scr
->clip_ws_menu
->entries
[workspace
+2]->text
, buf
)!=0) {
667 free(scr
->clip_ws_menu
->entries
[workspace
+2]->text
);
668 scr
->clip_ws_menu
->entries
[workspace
+2]->text
= wstrdup(buf
);
669 wMenuRealize(scr
->clip_ws_menu
);
672 if (scr
->workspace_menu
) {
673 if (strcmp(scr
->workspace_menu
->entries
[workspace
+2]->text
, buf
)!=0) {
674 free(scr
->workspace_menu
->entries
[workspace
+2]->text
);
675 scr
->workspace_menu
->entries
[workspace
+2]->text
= wstrdup(buf
);
676 wMenuRealize(scr
->workspace_menu
);
680 UpdateSwitchMenuWorkspace(scr
, workspace
);
683 wClipIconPaint(scr
->clip_icon
);
686 wGNOMEUpdateWorkspaceNamesHint(scr
);
689 wKWMUpdateWorkspaceNameHint(scr
, workspace
);
696 /* callback for when menu entry is edited */
698 onMenuEntryEdited(WMenu
*menu
, WMenuEntry
*entry
)
703 wWorkspaceRename(menu
->frame
->screen_ptr
, (long)entry
->clientdata
, tmp
);
708 wWorkspaceMenuMake(WScreen
*scr
, Bool titled
)
712 wsmenu
= wMenuCreate(scr
, titled
? _("Workspaces") : NULL
, False
);
714 wwarning(_("could not create Workspace menu"));
718 /* callback to be called when an entry is edited */
719 wsmenu
->on_edit
= onMenuEntryEdited
;
721 wMenuAddCallback(wsmenu
, _("New"), newWSCommand
, NULL
);
722 wMenuAddCallback(wsmenu
, _("Destroy Last"), deleteWSCommand
, NULL
);
730 wWorkspaceMenuUpdate(WScreen
*scr
, WMenu
*menu
)
734 char title
[MAX_WORKSPACENAME_WIDTH
+1];
741 if (menu
->entry_no
< scr
->workspace_count
+2) {
742 /* new workspace(s) added */
743 i
= scr
->workspace_count
-(menu
->entry_no
-2);
744 ws
= menu
->entry_no
- 2;
746 strcpy(title
, scr
->workspaces
[ws
]->name
);
748 entry
= wMenuAddCallback(menu
, title
, switchWSCommand
, (void*)ws
);
749 entry
->flags
.indicator
= 1;
750 entry
->flags
.editable
= 1;
755 } else if (menu
->entry_no
> scr
->workspace_count
+2) {
756 /* removed workspace(s) */
757 for (i
= menu
->entry_no
-1; i
>= scr
->workspace_count
+2; i
--) {
758 wMenuRemoveItem(menu
, i
);
763 for (i
=0; i
<scr
->workspace_count
; i
++) {
764 menu
->entries
[i
+2]->flags
.indicator_on
= 0;
766 menu
->entries
[scr
->current_workspace
+2]->flags
.indicator_on
= 1;
768 /* don't let user destroy current workspace */
769 if (scr
->current_workspace
== scr
->workspace_count
-1) {
770 wMenuSetEnabled(menu
, 1, False
);
772 wMenuSetEnabled(menu
, 1, True
);
775 tmp
= menu
->frame
->top_width
+ 5;
776 /* if menu got unreachable, bring it to a visible place */
777 if (menu
->frame_x
< tmp
- (int)menu
->frame
->core
->width
)
778 wMenuMove(menu
, tmp
- (int)menu
->frame
->core
->width
, menu
->frame_y
, False
);
785 wWorkspaceSaveState(WScreen
*scr
, proplist_t old_state
)
787 proplist_t parr
, pstr
;
788 proplist_t wks_state
, old_wks_state
;
794 old_wks_state
= PLGetDictionaryEntry(old_state
, dWorkspaces
);
795 parr
= PLMakeArrayFromElements(NULL
);
796 for (i
=0; i
< scr
->workspace_count
; i
++) {
797 pstr
= PLMakeString(scr
->workspaces
[i
]->name
);
798 wks_state
= PLMakeDictionaryFromEntries(dName
, pstr
, NULL
);
800 if (!wPreferences
.flags
.noclip
) {
801 pstr
= wClipSaveWorkspaceState(scr
, i
);
802 PLInsertDictionaryEntry(wks_state
, dClip
, pstr
);
804 } else if (old_wks_state
!=NULL
) {
805 if ((foo
= PLGetArrayElement(old_wks_state
, i
))!=NULL
) {
806 if ((bar
= PLGetDictionaryEntry(foo
, dClip
))!=NULL
) {
807 PLInsertDictionaryEntry(wks_state
, dClip
, bar
);
811 PLAppendArrayElement(parr
, wks_state
);
812 PLRelease(wks_state
);
814 PLInsertDictionaryEntry(scr
->session_state
, dWorkspaces
, parr
);
820 wWorkspaceRestoreState(WScreen
*scr
)
822 proplist_t parr
, pstr
, wks_state
;
823 proplist_t clip_state
;
828 parr
= PLGetDictionaryEntry(scr
->session_state
, dWorkspaces
);
833 wscount
= scr
->workspace_count
;
834 for (i
=0; i
< PLGetNumberOfElements(parr
); i
++) {
835 wks_state
= PLGetArrayElement(parr
, i
);
836 if (PLIsDictionary(wks_state
))
837 pstr
= PLGetDictionaryEntry(wks_state
, dName
);
840 if (i
>= scr
->workspace_count
)
842 if (scr
->workspace_menu
) {
843 free(scr
->workspace_menu
->entries
[i
+2]->text
);
844 scr
->workspace_menu
->entries
[i
+2]->text
= wstrdup(PLGetString(pstr
));
845 scr
->workspace_menu
->flags
.realized
= 0;
847 free(scr
->workspaces
[i
]->name
);
848 scr
->workspaces
[i
]->name
= wstrdup(PLGetString(pstr
));
849 if (!wPreferences
.flags
.noclip
) {
850 clip_state
= PLGetDictionaryEntry(wks_state
, dClip
);
851 if (scr
->workspaces
[i
]->clip
)
852 wDockDestroy(scr
->workspaces
[i
]->clip
);
853 scr
->workspaces
[i
]->clip
= wDockRestoreState(scr
, clip_state
,
856 wDockHideIcons(scr
->workspaces
[i
]->clip
);
859 wKWMUpdateWorkspaceNameHint(scr
, i
);
863 wGNOMEUpdateWorkspaceNamesHint(scr
);