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
;
241 if (!scr
->workspace_name_data
|| scr
->workspace_name_data
->count
== 0) {
242 XUnmapWindow(dpy
, scr
->workspace_name
);
244 if (scr
->workspace_name_data
) {
245 RDestroyImage(scr
->workspace_name_data
->back
);
246 RDestroyImage(scr
->workspace_name_data
->text
);
247 free(scr
->workspace_name_data
);
249 scr
->workspace_name_data
= NULL
;
251 scr
->workspace_name_timer
= NULL
;
253 RImage
*img
= RCloneImage(scr
->workspace_name_data
->back
);
256 RCombineImagesWithOpaqueness(img
, scr
->workspace_name_data
->text
,
257 scr
->workspace_name_data
->count
*255/10);
259 RConvertImage(scr
->rcontext
, img
, &pix
);
263 XSetWindowBackgroundPixmap(dpy
, scr
->workspace_name
, pix
);
264 XClearWindow(dpy
, scr
->workspace_name
);
265 XFreePixmap(dpy
, pix
);
268 scr
->workspace_name_data
->count
--;
270 scr
->workspace_name_timer
= WMAddTimerHandler(40, hideWorkpaceName
,
278 showWorkspaceName(WScreen
*scr
, int workspace
)
280 WorkspaceNameData
*data
;
284 char *name
= scr
->workspaces
[workspace
]->name
;
285 int len
= strlen(name
);
288 if (scr
->workspace_name_timer
) {
289 WMDeleteTimerHandler(scr
->workspace_name_timer
);
290 XUnmapWindow(dpy
, scr
->workspace_name
);
294 if (scr
->workspace_name_data
) {
295 RDestroyImage(scr
->workspace_name_data
->back
);
296 RDestroyImage(scr
->workspace_name_data
->text
);
297 free(scr
->workspace_name_data
);
301 XSetFont(dpy
, scr
->mono_gc
, scr
->workspace_name_font
->font
->fid
);
302 XSetFont(dpy
, scr
->draw_gc
, scr
->workspace_name_font
->font
->fid
);
305 data
= wmalloc(sizeof(WorkspaceNameData
));
307 w
= wTextWidth(scr
->workspace_name_font
->font
, name
, len
);
308 h
= scr
->workspace_name_font
->height
;
310 XResizeWindow(dpy
, scr
->workspace_name
, w
+4, h
+4);
311 XMoveWindow(dpy
, scr
->workspace_name
, (scr
->scr_width
- (w
+4))/2,
312 (scr
->scr_height
- (h
+4))/2);
314 text
= XCreatePixmap(dpy
, scr
->w_win
, w
+4, h
+4, scr
->w_depth
);
315 mask
= XCreatePixmap(dpy
, scr
->w_win
, w
+4, h
+4, 1);
317 XSetForeground(dpy
, scr
->draw_gc
, scr
->black_pixel
);
318 XFillRectangle(dpy
, text
, scr
->draw_gc
, 0, 0, w
+4, h
+4);
320 XSetForeground(dpy
, scr
->mono_gc
, 0);
321 XFillRectangle(dpy
, mask
, scr
->mono_gc
, 0, 0, w
+4, h
+4);
323 XSetForeground(dpy
, scr
->mono_gc
, 1);
324 for (x
= 0; x
<= 4; x
++) {
325 for (y
= 0; y
<= 4; y
++) {
326 wDrawString(mask
, scr
->workspace_name_font
, scr
->mono_gc
,
327 x
, scr
->workspace_name_font
->y
+ y
, name
, len
);
331 XSetForeground(dpy
, scr
->draw_gc
, scr
->white_pixel
);
332 wDrawString(text
, scr
->workspace_name_font
, scr
->draw_gc
,
333 2, scr
->workspace_name_font
->y
+ 2,
334 scr
->workspaces
[workspace
]->name
,
335 strlen(scr
->workspaces
[workspace
]->name
));
337 XShapeCombineMask(dpy
, scr
->workspace_name
, ShapeBounding
, 0, 0, mask
,
340 XSetWindowBackgroundPixmap(dpy
, scr
->workspace_name
, text
);
341 XClearWindow(dpy
, scr
->workspace_name
);
343 data
->text
= RCreateImageFromDrawable(scr
->rcontext
, text
, None
);
345 XFreePixmap(dpy
, text
);
346 XFreePixmap(dpy
, mask
);
349 XMapRaised(dpy
, scr
->workspace_name
);
355 ximg
= RGetXImage(scr
->rcontext
, scr
->root_win
,
356 (scr
->scr_width
- data
->text
->width
)/2,
357 (scr
->scr_height
- data
->text
->height
)/2,
358 data
->text
->width
, data
->text
->height
);
364 XMapRaised(dpy
, scr
->workspace_name
);
367 data
->back
= RCreateImageFromXImage(scr
->rcontext
, ximg
->image
, NULL
);
368 RDestroyXImage(scr
->rcontext
, ximg
);
376 scr
->workspace_name_data
= data
;
378 scr
->workspace_name_timer
= WMAddTimerHandler(200, hideWorkpaceName
, scr
);
384 RDestroyImage(data
->text
);
386 RDestroyImage(data
->back
);
389 scr
->workspace_name_data
= NULL
;
391 scr
->workspace_name_timer
= WMAddTimerHandler(600, hideWorkpaceName
, scr
);
396 wWorkspaceChange(WScreen
*scr
, int workspace
)
398 if (scr
->flags
.startup
|| scr
->flags
.startup2
) {
402 if (workspace
!= scr
->current_workspace
) {
403 wWorkspaceForceChange(scr
, workspace
);
405 showWorkspaceName(scr
, workspace
);
411 wWorkspaceRelativeChange(WScreen
*scr
, int amount
)
415 w
= scr
->current_workspace
+ amount
;
420 wWorkspaceChange(scr
, w
);
421 else if (wPreferences
.ws_cycle
)
422 wWorkspaceChange(scr
, scr
->workspace_count
+ w
);
424 } else if (amount
> 0) {
426 if (w
< scr
->workspace_count
)
427 wWorkspaceChange(scr
, w
);
428 else if (wPreferences
.ws_advance
)
429 wWorkspaceChange(scr
, WMIN(w
, MAX_WORKSPACES
-1));
430 else if (wPreferences
.ws_cycle
)
431 wWorkspaceChange(scr
, w
% scr
->workspace_count
);
438 wWorkspaceForceChange(WScreen
*scr
, int workspace
)
440 WWindow
*tmp
, *foc
=NULL
, *foc2
=NULL
;
442 if (workspace
>= MAX_WORKSPACES
|| workspace
< 0)
445 SendHelperMessage(scr
, 'C', workspace
+1, NULL
);
447 if (workspace
> scr
->workspace_count
-1) {
448 wWorkspaceMake(scr
, workspace
- scr
->workspace_count
+ 1);
451 wClipUpdateForWorkspaceChange(scr
, workspace
);
453 scr
->current_workspace
= workspace
;
455 wWorkspaceMenuUpdate(scr
, scr
->workspace_menu
);
457 wWorkspaceMenuUpdate(scr
, scr
->clip_ws_menu
);
459 if ((tmp
= scr
->focused_window
)!= NULL
) {
460 if (IS_OMNIPRESENT(tmp
) || tmp
->flags
.changing_workspace
)
464 if (tmp
->frame
->workspace
!=workspace
&& !tmp
->flags
.selected
) {
465 /* unmap windows not on this workspace */
466 if ((tmp
->flags
.mapped
||tmp
->flags
.shaded
)
467 && !IS_OMNIPRESENT(tmp
)
468 && !tmp
->flags
.changing_workspace
) {
472 /* also unmap miniwindows not on this workspace */
473 if (tmp
->flags
.miniaturized
&& !IS_OMNIPRESENT(tmp
)
475 if (!wPreferences
.sticky_icons
) {
476 XUnmapWindow(dpy
, tmp
->icon
->core
->window
);
477 tmp
->icon
->mapped
= 0;
479 tmp
->icon
->mapped
= 1;
480 /* Why is this here? -Alfredo */
481 XMapWindow(dpy
, tmp
->icon
->core
->window
);
484 /* update current workspace of omnipresent windows */
485 if (IS_OMNIPRESENT(tmp
)) {
486 WApplication
*wapp
= wApplicationOf(tmp
->main_window
);
488 tmp
->frame
->workspace
= workspace
;
491 wapp
->last_workspace
= workspace
;
497 /* change selected windows' workspace */
498 if (tmp
->flags
.selected
) {
499 wWindowChangeWorkspace(tmp
, workspace
);
500 if (!tmp
->flags
.miniaturized
&& !foc
) {
504 if (!tmp
->flags
.hidden
) {
505 if (!(tmp
->flags
.mapped
|| tmp
->flags
.miniaturized
)) {
506 /* remap windows that are on this workspace */
511 /* Also map miniwindow if not omnipresent */
512 if (!wPreferences
.sticky_icons
&&
513 tmp
->flags
.miniaturized
&&
514 !IS_OMNIPRESENT(tmp
) && tmp
->icon
) {
515 tmp
->icon
->mapped
= 1;
516 XMapWindow(dpy
, tmp
->icon
->core
->window
);
527 if (scr
->focused_window
->flags
.mapped
&& !foc
) {
528 foc
= scr
->focused_window
;
530 if (wPreferences
.focus_mode
== WKF_CLICK
) {
531 wSetFocusTo(scr
, foc
);
539 if (XQueryPointer(dpy
, scr
->root_win
, &bar
, &win
,
540 &foo
, &foo
, &foo
, &foo
, &mask
)) {
541 tmp
= wWindowFor(win
);
543 if (!tmp
&& wPreferences
.focus_mode
== WKF_SLOPPY
) {
544 wSetFocusTo(scr
, foc
);
546 wSetFocusTo(scr
, tmp
);
551 /* We need to always arrange icons when changing workspace, even if
552 * no autoarrange icons, because else the icons in different workspaces
554 * This can be avoided if appicons are also workspace specific.
556 if (!wPreferences
.sticky_icons
)
557 wArrangeIcons(scr
, False
);
560 wAppIconPaint(scr
->dock
->icon_array
[0]);
561 if (scr
->clip_icon
) {
562 if (scr
->workspaces
[workspace
]->clip
->auto_collapse
||
563 scr
->workspaces
[workspace
]->clip
->auto_raise_lower
) {
564 /* to handle enter notify. This will also */
565 XUnmapWindow(dpy
, scr
->clip_icon
->icon
->core
->window
);
566 XMapWindow(dpy
, scr
->clip_icon
->icon
->core
->window
);
568 wClipIconPaint(scr
->clip_icon
);
572 showWorkspaceName(scr
, workspace
);
575 wGNOMEUpdateCurrentWorkspaceHint(scr
);
578 wKWMUpdateCurrentWorkspaceHint(scr
);
580 /* XSync(dpy, False); */
585 switchWSCommand(WMenu
*menu
, WMenuEntry
*entry
)
587 wWorkspaceChange(menu
->frame
->screen_ptr
, (long)entry
->clientdata
);
593 deleteWSCommand(WMenu
*menu
, WMenuEntry
*entry
)
595 wWorkspaceDelete(menu
->frame
->screen_ptr
,
596 menu
->frame
->screen_ptr
->workspace_count
-1);
602 newWSCommand(WMenu
*menu
, WMenuEntry
*foo
)
606 ws
= wWorkspaceNew(menu
->frame
->screen_ptr
);
607 /* autochange workspace*/
609 wWorkspaceChange(menu
->frame
->screen_ptr
, ws
);
615 if (wKeyBindings[WKBD_WORKSPACE1+ws]) {
616 kcode = wKeyBindings[WKBD_WORKSPACE1+ws]->keycode;
618 wstrdup(XKeysymToString(XKeycodeToKeysym(dpy, kcode, 0)));
633 end
= &(line
[strlen(line
)])-1;
634 while (isspace(*line
) && *line
!=0) line
++;
635 while (isspace(*end
) && end
!=line
) {
644 wWorkspaceRename(WScreen
*scr
, int workspace
, char *name
)
646 char buf
[MAX_WORKSPACENAME_WIDTH
+1];
649 if (workspace
>= scr
->workspace_count
)
652 /* trim white spaces */
653 tmp
= cropline(name
);
655 if (strlen(tmp
)==0) {
656 sprintf(buf
, _("Workspace %i"), workspace
+1);
658 strncpy(buf
, tmp
, MAX_WORKSPACENAME_WIDTH
);
660 buf
[MAX_WORKSPACENAME_WIDTH
] = 0;
662 /* update workspace */
663 free(scr
->workspaces
[workspace
]->name
);
664 scr
->workspaces
[workspace
]->name
= wstrdup(buf
);
666 if (scr
->clip_ws_menu
) {
667 if (strcmp(scr
->clip_ws_menu
->entries
[workspace
+2]->text
, buf
)!=0) {
668 free(scr
->clip_ws_menu
->entries
[workspace
+2]->text
);
669 scr
->clip_ws_menu
->entries
[workspace
+2]->text
= wstrdup(buf
);
670 wMenuRealize(scr
->clip_ws_menu
);
673 if (scr
->workspace_menu
) {
674 if (strcmp(scr
->workspace_menu
->entries
[workspace
+2]->text
, buf
)!=0) {
675 free(scr
->workspace_menu
->entries
[workspace
+2]->text
);
676 scr
->workspace_menu
->entries
[workspace
+2]->text
= wstrdup(buf
);
677 wMenuRealize(scr
->workspace_menu
);
681 UpdateSwitchMenuWorkspace(scr
, workspace
);
684 wClipIconPaint(scr
->clip_icon
);
687 wGNOMEUpdateWorkspaceNamesHint(scr
);
690 wKWMUpdateWorkspaceNameHint(scr
, workspace
);
697 /* callback for when menu entry is edited */
699 onMenuEntryEdited(WMenu
*menu
, WMenuEntry
*entry
)
704 wWorkspaceRename(menu
->frame
->screen_ptr
, (long)entry
->clientdata
, tmp
);
709 wWorkspaceMenuMake(WScreen
*scr
, Bool titled
)
713 wsmenu
= wMenuCreate(scr
, titled
? _("Workspaces") : NULL
, False
);
715 wwarning(_("could not create Workspace menu"));
719 /* callback to be called when an entry is edited */
720 wsmenu
->on_edit
= onMenuEntryEdited
;
722 wMenuAddCallback(wsmenu
, _("New"), newWSCommand
, NULL
);
723 wMenuAddCallback(wsmenu
, _("Destroy Last"), deleteWSCommand
, NULL
);
731 wWorkspaceMenuUpdate(WScreen
*scr
, WMenu
*menu
)
735 char title
[MAX_WORKSPACENAME_WIDTH
+1];
742 if (menu
->entry_no
< scr
->workspace_count
+2) {
743 /* new workspace(s) added */
744 i
= scr
->workspace_count
-(menu
->entry_no
-2);
745 ws
= menu
->entry_no
- 2;
747 strcpy(title
, scr
->workspaces
[ws
]->name
);
749 entry
= wMenuAddCallback(menu
, title
, switchWSCommand
, (void*)ws
);
750 entry
->flags
.indicator
= 1;
751 entry
->flags
.editable
= 1;
756 } else if (menu
->entry_no
> scr
->workspace_count
+2) {
757 /* removed workspace(s) */
758 for (i
= menu
->entry_no
-1; i
>= scr
->workspace_count
+2; i
--) {
759 wMenuRemoveItem(menu
, i
);
764 for (i
=0; i
<scr
->workspace_count
; i
++) {
765 menu
->entries
[i
+2]->flags
.indicator_on
= 0;
767 menu
->entries
[scr
->current_workspace
+2]->flags
.indicator_on
= 1;
769 /* don't let user destroy current workspace */
770 if (scr
->current_workspace
== scr
->workspace_count
-1) {
771 wMenuSetEnabled(menu
, 1, False
);
773 wMenuSetEnabled(menu
, 1, True
);
776 tmp
= menu
->frame
->top_width
+ 5;
777 /* if menu got unreachable, bring it to a visible place */
778 if (menu
->frame_x
< tmp
- (int)menu
->frame
->core
->width
)
779 wMenuMove(menu
, tmp
- (int)menu
->frame
->core
->width
, menu
->frame_y
, False
);
786 wWorkspaceSaveState(WScreen
*scr
, proplist_t old_state
)
788 proplist_t parr
, pstr
;
789 proplist_t wks_state
, old_wks_state
;
795 old_wks_state
= PLGetDictionaryEntry(old_state
, dWorkspaces
);
796 parr
= PLMakeArrayFromElements(NULL
);
797 for (i
=0; i
< scr
->workspace_count
; i
++) {
798 pstr
= PLMakeString(scr
->workspaces
[i
]->name
);
799 wks_state
= PLMakeDictionaryFromEntries(dName
, pstr
, NULL
);
801 if (!wPreferences
.flags
.noclip
) {
802 pstr
= wClipSaveWorkspaceState(scr
, i
);
803 PLInsertDictionaryEntry(wks_state
, dClip
, pstr
);
805 } else if (old_wks_state
!=NULL
) {
806 if ((foo
= PLGetArrayElement(old_wks_state
, i
))!=NULL
) {
807 if ((bar
= PLGetDictionaryEntry(foo
, dClip
))!=NULL
) {
808 PLInsertDictionaryEntry(wks_state
, dClip
, bar
);
812 PLAppendArrayElement(parr
, wks_state
);
813 PLRelease(wks_state
);
815 PLInsertDictionaryEntry(scr
->session_state
, dWorkspaces
, parr
);
821 wWorkspaceRestoreState(WScreen
*scr
)
823 proplist_t parr
, pstr
, wks_state
;
824 proplist_t clip_state
;
829 parr
= PLGetDictionaryEntry(scr
->session_state
, dWorkspaces
);
834 wscount
= scr
->workspace_count
;
835 for (i
=0; i
< PLGetNumberOfElements(parr
); i
++) {
836 wks_state
= PLGetArrayElement(parr
, i
);
837 if (PLIsDictionary(wks_state
))
838 pstr
= PLGetDictionaryEntry(wks_state
, dName
);
841 if (i
>= scr
->workspace_count
)
843 if (scr
->workspace_menu
) {
844 free(scr
->workspace_menu
->entries
[i
+2]->text
);
845 scr
->workspace_menu
->entries
[i
+2]->text
= wstrdup(PLGetString(pstr
));
846 scr
->workspace_menu
->flags
.realized
= 0;
848 free(scr
->workspaces
[i
]->name
);
849 scr
->workspaces
[i
]->name
= wstrdup(PLGetString(pstr
));
850 if (!wPreferences
.flags
.noclip
) {
851 clip_state
= PLGetDictionaryEntry(wks_state
, dClip
);
852 if (scr
->workspaces
[i
]->clip
)
853 wDockDestroy(scr
->workspaces
[i
]->clip
);
854 scr
->workspaces
[i
]->clip
= wDockRestoreState(scr
, clip_state
,
857 wDockHideIcons(scr
->workspaces
[i
]->clip
);
860 wKWMUpdateWorkspaceNameHint(scr
, i
);
864 wGNOMEUpdateWorkspaceNamesHint(scr
);