/* workspace.c- Workspace management * * Window Maker window manager * * Copyright (c) 1997, 1998 Alfredo K. Kojima * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, * USA. */ #include "wconfig.h" #include #include #ifdef SHAPE #include #endif #include #include #include #include #include #include #include "WindowMaker.h" #include "wcore.h" #include "framewin.h" #include "window.h" #include "icon.h" #include "funcs.h" #include "menu.h" #include "application.h" #include "dock.h" #include "actions.h" #include "workspace.h" #include "appicon.h" #ifdef GNOME_STUFF #include "gnome.h" #endif #ifdef KWM_HINTS #include "kwm.h" #endif #include extern WPreferences wPreferences; extern XContext wWinContext; static proplist_t dWorkspaces=NULL; static proplist_t dClip, dName; static void make_keys() { if (dWorkspaces!=NULL) return; dWorkspaces = PLMakeString("Workspaces"); dName = PLMakeString("Name"); dClip = PLMakeString("Clip"); } void wWorkspaceMake(WScreen *scr, int count) { while (count>0) { wWorkspaceNew(scr); count--; } } int wWorkspaceNew(WScreen *scr) { WWorkspace *wspace, **list; int i; if (scr->workspace_count < MAX_WORKSPACES) { scr->workspace_count++; wspace = wmalloc(sizeof(WWorkspace)); wspace->name = NULL; #ifdef KWM_HINTS if (scr->flags.kwm_syncing_count) { wspace->name = wKWMGetWorkspaceName(scr, scr->workspace_count-1); } #endif if (!wspace->name) { wspace->name = wmalloc(strlen(_("Workspace %i"))+8); sprintf(wspace->name, _("Workspace %i"), scr->workspace_count); } if (!wPreferences.flags.noclip) { wspace->clip = wDockCreate(scr, WM_CLIP); } else wspace->clip = NULL; list = wmalloc(sizeof(WWorkspace*)*scr->workspace_count); for (i=0; iworkspace_count-1; i++) { list[i] = scr->workspaces[i]; } list[i] = wspace; free(scr->workspaces); scr->workspaces = list; wWorkspaceMenuUpdate(scr, scr->workspace_menu); wWorkspaceMenuUpdate(scr, scr->clip_ws_menu); #ifdef GNOME_STUFF wGNOMEUpdateWorkspaceHints(scr); #endif #ifdef KWM_HINTS if (!scr->flags.kwm_syncing_count) { wKWMUpdateWorkspaceCountHint(scr); wKWMUpdateWorkspaceNameHint(scr, scr->workspace_count-1); } #ifdef not_used wKWMSetUsableAreaHint(scr, scr->workspace_count-1); #endif #endif XFlush(dpy); return scr->workspace_count-1; } return -1; } Bool wWorkspaceDelete(WScreen *scr, int workspace) { WWindow *tmp; WWorkspace **list; int i, j; if (workspace<=0) return False; /* verify if workspace is in use by some window */ tmp = scr->focused_window; while (tmp) { if (!IS_OMNIPRESENT(tmp) && tmp->frame->workspace==workspace) return False; tmp = tmp->prev; } if (!wPreferences.flags.noclip) { wDockDestroy(scr->workspaces[workspace]->clip); scr->workspaces[workspace]->clip = NULL; } list = wmalloc(sizeof(WWorkspace*)*(scr->workspace_count-1)); j = 0; for (i=0; iworkspace_count; i++) { if (i!=workspace) list[j++] = scr->workspaces[i]; else { if (scr->workspaces[i]->name) free(scr->workspaces[i]->name); free(scr->workspaces[i]); } } free(scr->workspaces); scr->workspaces = list; scr->workspace_count--; /* update menu */ wWorkspaceMenuUpdate(scr, scr->workspace_menu); /* clip workspace menu */ wWorkspaceMenuUpdate(scr, scr->clip_ws_menu); /* update also window menu */ if (scr->workspace_submenu) { WMenu *menu = scr->workspace_submenu; i = menu->entry_no; while (i>scr->workspace_count) wMenuRemoveItem(menu, --i); wMenuRealize(menu); } /* and clip menu */ if (scr->clip_submenu) { WMenu *menu = scr->clip_submenu; i = menu->entry_no; while (i>scr->workspace_count) wMenuRemoveItem(menu, --i); wMenuRealize(menu); } #ifdef GNOME_STUFF wGNOMEUpdateWorkspaceHints(scr); #endif #ifdef KWM_HINTS wKWMUpdateWorkspaceCountHint(scr); #endif if (scr->current_workspace >= scr->workspace_count) wWorkspaceChange(scr, scr->workspace_count-1); return True; } typedef struct WorkspaceNameData { int count; RImage *back; RImage *text; time_t timeout; } WorkspaceNameData; static void hideWorkpaceName(void *data) { WScreen *scr = (WScreen*)data; if (!scr->workspace_name_data || scr->workspace_name_data->count == 0 || time(NULL) > scr->workspace_name_data->timeout) { XUnmapWindow(dpy, scr->workspace_name); if (scr->workspace_name_data) { RDestroyImage(scr->workspace_name_data->back); RDestroyImage(scr->workspace_name_data->text); free(scr->workspace_name_data); scr->workspace_name_data = NULL; } scr->workspace_name_timer = NULL; } else { RImage *img = RCloneImage(scr->workspace_name_data->back); Pixmap pix; scr->workspace_name_timer = WMAddTimerHandler(WORKSPACE_NAME_FADE_DELAY, hideWorkpaceName, scr); RCombineImagesWithOpaqueness(img, scr->workspace_name_data->text, scr->workspace_name_data->count*255/10); RConvertImage(scr->rcontext, img, &pix); RDestroyImage(img); XSetWindowBackgroundPixmap(dpy, scr->workspace_name, pix); XClearWindow(dpy, scr->workspace_name); XFreePixmap(dpy, pix); XFlush(dpy); scr->workspace_name_data->count--; } } static void showWorkspaceName(WScreen *scr, int workspace) { WorkspaceNameData *data; RXImage *ximg; Pixmap text, mask; int w, h; int px, py; char *name = scr->workspaces[workspace]->name; int len = strlen(name); int x, y; if (wPreferences.workspace_name_display_position == WD_NONE || scr->workspace_count < 2) return; if (scr->workspace_name_timer) { WMDeleteTimerHandler(scr->workspace_name_timer); XUnmapWindow(dpy, scr->workspace_name); XFlush(dpy); } scr->workspace_name_timer = WMAddTimerHandler(WORKSPACE_NAME_DELAY, hideWorkpaceName, scr); if (scr->workspace_name_data) { RDestroyImage(scr->workspace_name_data->back); RDestroyImage(scr->workspace_name_data->text); free(scr->workspace_name_data); } data = wmalloc(sizeof(WorkspaceNameData)); w = WMWidthOfString(scr->workspace_name_font, name, len); h = WMFontHeight(scr->workspace_name_font); switch (wPreferences.workspace_name_display_position) { case WD_CENTER: px = (scr->scr_width - (w+4))/2; py = (scr->scr_height - (h+4))/2; break; case WD_TOP: px = (scr->scr_width - (w+4))/2; py = 0; break; case WD_BOTTOM: px = (scr->scr_width - (w+4))/2; py = scr->scr_height - h; break; case WD_TOPLEFT: px = 0; py = 0; break; case WD_TOPRIGHT: px = scr->scr_width - w; py = 0; break; case WD_BOTTOMLEFT: px = 0; py = scr->scr_height - h; break; case WD_BOTTOMRIGHT: px = scr->scr_width - w; py = scr->scr_height - h; break; } XResizeWindow(dpy, scr->workspace_name, w+4, h+4); XMoveWindow(dpy, scr->workspace_name, px, py); text = XCreatePixmap(dpy, scr->w_win, w+4, h+4, scr->w_depth); mask = XCreatePixmap(dpy, scr->w_win, w+4, h+4, 1); XSetForeground(dpy, scr->draw_gc, scr->black_pixel); XFillRectangle(dpy, text, scr->draw_gc, 0, 0, w+4, h+4); XSetForeground(dpy, scr->mono_gc, 0); XFillRectangle(dpy, mask, scr->mono_gc, 0, 0, w+4, h+4); XSetForeground(dpy, scr->mono_gc, 1); for (x = 0; x <= 4; x++) { for (y = 0; y <= 4; y++) { WMDrawString(scr->wmscreen, mask, scr->mono_gc, scr->workspace_name_font, x, y, name, len); } } XSetForeground(dpy, scr->draw_gc, scr->white_pixel); WMDrawString(scr->wmscreen, text, scr->draw_gc, scr->workspace_name_font, 2, 2, scr->workspaces[workspace]->name, strlen(scr->workspaces[workspace]->name)); #ifdef SHAPE XShapeCombineMask(dpy, scr->workspace_name, ShapeBounding, 0, 0, mask, ShapeSet); #endif XSetWindowBackgroundPixmap(dpy, scr->workspace_name, text); XClearWindow(dpy, scr->workspace_name); data->text = RCreateImageFromDrawable(scr->rcontext, text, None); XFreePixmap(dpy, text); XFreePixmap(dpy, mask); if (!data->text) { XMapRaised(dpy, scr->workspace_name); XFlush(dpy); goto erro; } ximg = RGetXImage(scr->rcontext, scr->root_win, px, py, data->text->width, data->text->height); if (!ximg) { goto erro; } XMapRaised(dpy, scr->workspace_name); XFlush(dpy); data->back = RCreateImageFromXImage(scr->rcontext, ximg->image, NULL); RDestroyXImage(scr->rcontext, ximg); if (!data->back) { goto erro; } data->count = 10; /* set a timeout for the effect */ data->timeout = time(NULL) + 2 + (WORKSPACE_NAME_DELAY + WORKSPACE_NAME_FADE_DELAY*data->count)/1000; scr->workspace_name_data = data; return; erro: if (scr->workspace_name_timer) WMDeleteTimerHandler(scr->workspace_name_timer); if (data->text) RDestroyImage(data->text); if (data->back) RDestroyImage(data->back); free(data); scr->workspace_name_data = NULL; scr->workspace_name_timer = WMAddTimerHandler(WORKSPACE_NAME_DELAY + 10*WORKSPACE_NAME_FADE_DELAY, hideWorkpaceName, scr); } void wWorkspaceChange(WScreen *scr, int workspace) { if (scr->flags.startup || scr->flags.startup2) { return; } if (workspace != scr->current_workspace) { wWorkspaceForceChange(scr, workspace); } /*else { showWorkspaceName(scr, workspace); }*/ } void wWorkspaceRelativeChange(WScreen *scr, int amount) { int w; w = scr->current_workspace + amount; if (amount < 0) { if (w >= 0) wWorkspaceChange(scr, w); else if (wPreferences.ws_cycle) wWorkspaceChange(scr, scr->workspace_count + w); } else if (amount > 0) { if (w < scr->workspace_count) wWorkspaceChange(scr, w); else if (wPreferences.ws_advance) wWorkspaceChange(scr, WMIN(w, MAX_WORKSPACES-1)); else if (wPreferences.ws_cycle) wWorkspaceChange(scr, w % scr->workspace_count); } } void wWorkspaceForceChange(WScreen *scr, int workspace) { WWindow *tmp, *foc=NULL, *foc2=NULL; if (workspace >= MAX_WORKSPACES || workspace < 0) return; SendHelperMessage(scr, 'C', workspace+1, NULL); if (workspace > scr->workspace_count-1) { wWorkspaceMake(scr, workspace - scr->workspace_count + 1); } wClipUpdateForWorkspaceChange(scr, workspace); scr->current_workspace = workspace; wWorkspaceMenuUpdate(scr, scr->workspace_menu); wWorkspaceMenuUpdate(scr, scr->clip_ws_menu); if ((tmp = scr->focused_window)!= NULL) { if (IS_OMNIPRESENT(tmp) || tmp->flags.changing_workspace) foc = tmp; /* foc2 = tmp; will fix annoyance with gnome panel * but will create annoyance for every other application */ while (tmp) { if (tmp->frame->workspace!=workspace && !tmp->flags.selected) { /* unmap windows not on this workspace */ if ((tmp->flags.mapped||tmp->flags.shaded) && !IS_OMNIPRESENT(tmp) && !tmp->flags.changing_workspace) { wWindowUnmap(tmp); } /* also unmap miniwindows not on this workspace */ if (tmp->flags.miniaturized && !IS_OMNIPRESENT(tmp) && tmp->icon) { if (!wPreferences.sticky_icons) { XUnmapWindow(dpy, tmp->icon->core->window); tmp->icon->mapped = 0; } else { tmp->icon->mapped = 1; /* Why is this here? -Alfredo */ XMapWindow(dpy, tmp->icon->core->window); } } /* update current workspace of omnipresent windows */ if (IS_OMNIPRESENT(tmp)) { WApplication *wapp = wApplicationOf(tmp->main_window); tmp->frame->workspace = workspace; if (wapp) { wapp->last_workspace = workspace; } if (!foc2) foc2 = tmp; } } else { /* change selected windows' workspace */ if (tmp->flags.selected) { wWindowChangeWorkspace(tmp, workspace); if (!tmp->flags.miniaturized && !foc) { foc = tmp; } } else { if (!tmp->flags.hidden) { if (!(tmp->flags.mapped || tmp->flags.miniaturized)) { /* remap windows that are on this workspace */ wWindowMap(tmp); if (!foc) foc = tmp; } /* Also map miniwindow if not omnipresent */ if (!wPreferences.sticky_icons && tmp->flags.miniaturized && !IS_OMNIPRESENT(tmp) && tmp->icon) { tmp->icon->mapped = 1; XMapWindow(dpy, tmp->icon->core->window); } } } } tmp = tmp->prev; } if (!foc) foc = foc2; if (scr->focused_window->flags.mapped && !foc) { foc = scr->focused_window; } if (wPreferences.focus_mode == WKF_CLICK) { wSetFocusTo(scr, foc); } else { unsigned int mask; int foo; Window bar, win; WWindow *tmp; tmp = NULL; if (XQueryPointer(dpy, scr->root_win, &bar, &win, &foo, &foo, &foo, &foo, &mask)) { tmp = wWindowFor(win); } if (!tmp && wPreferences.focus_mode == WKF_SLOPPY) { wSetFocusTo(scr, foc); } else { wSetFocusTo(scr, tmp); } } } /* We need to always arrange icons when changing workspace, even if * no autoarrange icons, because else the icons in different workspaces * can be superposed. * This can be avoided if appicons are also workspace specific. */ if (!wPreferences.sticky_icons) wArrangeIcons(scr, False); if (scr->dock) wAppIconPaint(scr->dock->icon_array[0]); if (scr->clip_icon) { if (scr->workspaces[workspace]->clip->auto_collapse || scr->workspaces[workspace]->clip->auto_raise_lower) { /* to handle enter notify. This will also */ XUnmapWindow(dpy, scr->clip_icon->icon->core->window); XMapWindow(dpy, scr->clip_icon->icon->core->window); } else { wClipIconPaint(scr->clip_icon); } } if (!scr->flags.startup2) showWorkspaceName(scr, workspace); #ifdef GNOME_STUFF wGNOMEUpdateCurrentWorkspaceHint(scr); #endif #ifdef KWM_HINTS wKWMUpdateCurrentWorkspaceHint(scr); #endif /* XSync(dpy, False); */ } static void switchWSCommand(WMenu *menu, WMenuEntry *entry) { wWorkspaceChange(menu->frame->screen_ptr, (long)entry->clientdata); } static void deleteWSCommand(WMenu *menu, WMenuEntry *entry) { wWorkspaceDelete(menu->frame->screen_ptr, menu->frame->screen_ptr->workspace_count-1); } static void newWSCommand(WMenu *menu, WMenuEntry *foo) { int ws; ws = wWorkspaceNew(menu->frame->screen_ptr); /* autochange workspace*/ if (ws>=0) wWorkspaceChange(menu->frame->screen_ptr, ws); /* if (ws<9) { int kcode; if (wKeyBindings[WKBD_WORKSPACE1+ws]) { kcode = wKeyBindings[WKBD_WORKSPACE1+ws]->keycode; entry->rtext = wstrdup(XKeysymToString(XKeycodeToKeysym(dpy, kcode, 0))); } }*/ } static char* cropline(char *line) { char *start, *end; if (strlen(line)==0) return line; start = line; end = &(line[strlen(line)])-1; while (isspace(*line) && *line!=0) line++; while (isspace(*end) && end!=line) { *end=0; end--; } return line; } void wWorkspaceRename(WScreen *scr, int workspace, char *name) { char buf[MAX_WORKSPACENAME_WIDTH+1]; char *tmp; if (workspace >= scr->workspace_count) return; /* trim white spaces */ tmp = cropline(name); if (strlen(tmp)==0) { sprintf(buf, _("Workspace %i"), workspace+1); } else { strncpy(buf, tmp, MAX_WORKSPACENAME_WIDTH); } buf[MAX_WORKSPACENAME_WIDTH] = 0; /* update workspace */ free(scr->workspaces[workspace]->name); scr->workspaces[workspace]->name = wstrdup(buf); if (scr->clip_ws_menu) { if (strcmp(scr->clip_ws_menu->entries[workspace+2]->text, buf)!=0) { free(scr->clip_ws_menu->entries[workspace+2]->text); scr->clip_ws_menu->entries[workspace+2]->text = wstrdup(buf); wMenuRealize(scr->clip_ws_menu); } } if (scr->workspace_menu) { if (strcmp(scr->workspace_menu->entries[workspace+2]->text, buf)!=0) { free(scr->workspace_menu->entries[workspace+2]->text); scr->workspace_menu->entries[workspace+2]->text = wstrdup(buf); wMenuRealize(scr->workspace_menu); } } UpdateSwitchMenuWorkspace(scr, workspace); if (scr->clip_icon) wClipIconPaint(scr->clip_icon); #ifdef GNOME_STUFF wGNOMEUpdateWorkspaceNamesHint(scr); #endif #ifdef KWM_HINTS wKWMUpdateWorkspaceNameHint(scr, workspace); #endif } /* callback for when menu entry is edited */ static void onMenuEntryEdited(WMenu *menu, WMenuEntry *entry) { char *tmp; tmp = entry->text; wWorkspaceRename(menu->frame->screen_ptr, (long)entry->clientdata, tmp); } WMenu* wWorkspaceMenuMake(WScreen *scr, Bool titled) { WMenu *wsmenu; wsmenu = wMenuCreate(scr, titled ? _("Workspaces") : NULL, False); if (!wsmenu) { wwarning(_("could not create Workspace menu")); return NULL; } /* callback to be called when an entry is edited */ wsmenu->on_edit = onMenuEntryEdited; wMenuAddCallback(wsmenu, _("New"), newWSCommand, NULL); wMenuAddCallback(wsmenu, _("Destroy Last"), deleteWSCommand, NULL); return wsmenu; } void wWorkspaceMenuUpdate(WScreen *scr, WMenu *menu) { int i; long ws; char title[MAX_WORKSPACENAME_WIDTH+1]; WMenuEntry *entry; int tmp; if (!menu) return; if (menu->entry_no < scr->workspace_count+2) { /* new workspace(s) added */ i = scr->workspace_count-(menu->entry_no-2); ws = menu->entry_no - 2; while (i>0) { strcpy(title, scr->workspaces[ws]->name); entry = wMenuAddCallback(menu, title, switchWSCommand, (void*)ws); entry->flags.indicator = 1; entry->flags.editable = 1; i--; ws++; } } else if (menu->entry_no > scr->workspace_count+2) { /* removed workspace(s) */ for (i = menu->entry_no-1; i >= scr->workspace_count+2; i--) { wMenuRemoveItem(menu, i); } } wMenuRealize(menu); for (i=0; iworkspace_count; i++) { menu->entries[i+2]->flags.indicator_on = 0; } menu->entries[scr->current_workspace+2]->flags.indicator_on = 1; /* don't let user destroy current workspace */ if (scr->current_workspace == scr->workspace_count-1) { wMenuSetEnabled(menu, 1, False); } else { wMenuSetEnabled(menu, 1, True); } tmp = menu->frame->top_width + 5; /* if menu got unreachable, bring it to a visible place */ if (menu->frame_x < tmp - (int)menu->frame->core->width) wMenuMove(menu, tmp - (int)menu->frame->core->width, menu->frame_y, False); wMenuPaint(menu); } void wWorkspaceSaveState(WScreen *scr, proplist_t old_state) { proplist_t parr, pstr; proplist_t wks_state, old_wks_state; proplist_t foo, bar; int i; make_keys(); old_wks_state = PLGetDictionaryEntry(old_state, dWorkspaces); parr = PLMakeArrayFromElements(NULL); for (i=0; i < scr->workspace_count; i++) { pstr = PLMakeString(scr->workspaces[i]->name); wks_state = PLMakeDictionaryFromEntries(dName, pstr, NULL); PLRelease(pstr); if (!wPreferences.flags.noclip) { pstr = wClipSaveWorkspaceState(scr, i); PLInsertDictionaryEntry(wks_state, dClip, pstr); PLRelease(pstr); } else if (old_wks_state!=NULL) { if ((foo = PLGetArrayElement(old_wks_state, i))!=NULL) { if ((bar = PLGetDictionaryEntry(foo, dClip))!=NULL) { PLInsertDictionaryEntry(wks_state, dClip, bar); } } } PLAppendArrayElement(parr, wks_state); PLRelease(wks_state); } PLInsertDictionaryEntry(scr->session_state, dWorkspaces, parr); PLRelease(parr); } void wWorkspaceRestoreState(WScreen *scr) { proplist_t parr, pstr, wks_state; proplist_t clip_state; int i, j, wscount; make_keys(); parr = PLGetDictionaryEntry(scr->session_state, dWorkspaces); if (!parr) return; wscount = scr->workspace_count; for (i=0; i < PLGetNumberOfElements(parr); i++) { wks_state = PLGetArrayElement(parr, i); if (PLIsDictionary(wks_state)) pstr = PLGetDictionaryEntry(wks_state, dName); else pstr = wks_state; if (i >= scr->workspace_count) wWorkspaceNew(scr); if (scr->workspace_menu) { free(scr->workspace_menu->entries[i+2]->text); scr->workspace_menu->entries[i+2]->text = wstrdup(PLGetString(pstr)); scr->workspace_menu->flags.realized = 0; } free(scr->workspaces[i]->name); scr->workspaces[i]->name = wstrdup(PLGetString(pstr)); if (!wPreferences.flags.noclip) { clip_state = PLGetDictionaryEntry(wks_state, dClip); if (scr->workspaces[i]->clip) wDockDestroy(scr->workspaces[i]->clip); scr->workspaces[i]->clip = wDockRestoreState(scr, clip_state, WM_CLIP); if (i>0) wDockHideIcons(scr->workspaces[i]->clip); /* We set the global icons here, because scr->workspaces[i]->clip * was not valid in wDockRestoreState(). * There we only set icon->omnipresent to know which icons we * need to set here. */ for (j=0; jworkspaces[i]->clip->max_icons; j++) { WAppIcon *aicon = scr->workspaces[i]->clip->icon_array[j]; if (aicon && aicon->omnipresent) { aicon->omnipresent = 0; wClipMakeIconOmnipresent(aicon, True); XMapWindow(dpy, aicon->icon->core->window); } } } #ifdef KWM_HINTS wKWMUpdateWorkspaceNameHint(scr, i); #endif } #ifdef GNOME_STUFF wGNOMEUpdateWorkspaceNamesHint(scr); #endif }