/* workspace.c- Workspace management * * Window Maker window manager * * Copyright (c) 1997-2003 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 #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" #include "wmspec.h" #include "xinerama.h" #define MAX_SHORTCUT_LENGTH 32 #define WORKSPACE_NAME_DISPLAY_PADDING 32 extern WPreferences wPreferences; extern XContext wWinContext; extern XContext wVEdgeContext; extern void ProcessPendingEvents(); static WMPropList *dWorkspaces = NULL; static WMPropList *dClip, *dName; static void make_keys() { if (dWorkspaces != NULL) return; dWorkspaces = WMCreatePLString("Workspaces"); dName = WMCreatePLString("Name"); dClip = WMCreatePLString("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; 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; i < scr->workspace_count - 1; i++) { list[i] = scr->workspaces[i]; } list[i] = wspace; if (scr->workspaces) wfree(scr->workspaces); scr->workspaces = list; wWorkspaceMenuUpdate(scr, scr->workspace_menu); wWorkspaceMenuUpdate(scr, scr->clip_ws_menu); #ifdef VIRTUAL_DESKTOP wspace->view_x = wspace->view_y = 0; wspace->height = scr->scr_height; wspace->width = scr->scr_width; #endif wNETWMUpdateDesktop(scr); WMPostNotificationName(WMNWorkspaceCreated, scr, (void *)(uintptr_t) (scr->workspace_count - 1)); 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; i < scr->workspace_count; i++) { if (i != workspace) { list[j++] = scr->workspaces[i]; } else { if (scr->workspaces[i]->name) wfree(scr->workspaces[i]->name); wfree(scr->workspaces[i]); } } wfree(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); } wNETWMUpdateDesktop(scr); WMPostNotificationName(WMNWorkspaceDestroyed, scr, (void *)(uintptr_t) (scr->workspace_count - 1)); 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 hideWorkspaceName(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) { RReleaseImage(scr->workspace_name_data->back); RReleaseImage(scr->workspace_name_data->text); wfree(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, hideWorkspaceName, scr); RCombineImagesWithOpaqueness(img, scr->workspace_name_data->text, scr->workspace_name_data->count * 255 / 10); RConvertImage(scr->rcontext, img, &pix); RReleaseImage(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; #ifdef XINERAMA int head; WMRect rect; int xx, yy; #endif 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, hideWorkspaceName, scr); if (scr->workspace_name_data) { RReleaseImage(scr->workspace_name_data->back); RReleaseImage(scr->workspace_name_data->text); wfree(scr->workspace_name_data); } data = wmalloc(sizeof(WorkspaceNameData)); data->back = NULL; w = WMWidthOfString(scr->workspace_name_font, name, len); h = WMFontHeight(scr->workspace_name_font); #ifdef XINERAMA head = wGetHeadForPointerLocation(scr); rect = wGetRectForHead(scr, head); if (scr->xine_info.count) { xx = rect.pos.x + (scr->xine_info.screens[head].size.width - (w + 4)) / 2; yy = rect.pos.y + (scr->xine_info.screens[head].size.height - (h + 4)) / 2; } else { xx = (scr->scr_width - (w + 4)) / 2; yy = (scr->scr_height - (h + 4)) / 2; } #endif switch (wPreferences.workspace_name_display_position) { case WD_TOP: #ifdef XINERAMA px = xx; #else px = (scr->scr_width - (w + 4)) / 2; #endif py = WORKSPACE_NAME_DISPLAY_PADDING; break; case WD_BOTTOM: #ifdef XINERAMA px = xx; #else px = (scr->scr_width - (w + 4)) / 2; #endif py = scr->scr_height - (h + 4 + WORKSPACE_NAME_DISPLAY_PADDING); break; case WD_TOPLEFT: px = WORKSPACE_NAME_DISPLAY_PADDING; py = WORKSPACE_NAME_DISPLAY_PADDING; break; case WD_TOPRIGHT: px = scr->scr_width - (w + 4 + WORKSPACE_NAME_DISPLAY_PADDING); py = WORKSPACE_NAME_DISPLAY_PADDING; break; case WD_BOTTOMLEFT: px = WORKSPACE_NAME_DISPLAY_PADDING; py = scr->scr_height - (h + 4 + WORKSPACE_NAME_DISPLAY_PADDING); break; case WD_BOTTOMRIGHT: px = scr->scr_width - (w + 4 + WORKSPACE_NAME_DISPLAY_PADDING); py = scr->scr_height - (h + 4 + WORKSPACE_NAME_DISPLAY_PADDING); break; case WD_CENTER: default: #ifdef XINERAMA px = xx; py = yy; #else px = (scr->scr_width - (w + 4)) / 2; py = (scr->scr_height - (h + 4)) / 2; #endif 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->mono_gc, 0); XFillRectangle(dpy, mask, scr->mono_gc, 0, 0, w+4, h+4); */ XFillRectangle(dpy, text, WMColorGC(scr->black), 0, 0, w + 4, h + 4); for (x = 0; x <= 4; x++) { for (y = 0; y <= 4; y++) { WMDrawString(scr->wmscreen, text, scr->white, scr->workspace_name_font, x, y, name, len); } } XSetForeground(dpy, scr->mono_gc, 1); XSetBackground(dpy, scr->mono_gc, 0); XCopyPlane(dpy, text, mask, scr->mono_gc, 0, 0, w + 4, h + 4, 0, 0, 1 << (scr->w_depth - 1)); /*XSetForeground(dpy, scr->mono_gc, 1); */ XSetBackground(dpy, scr->mono_gc, 1); XFillRectangle(dpy, text, WMColorGC(scr->black), 0, 0, w + 4, h + 4); WMDrawString(scr->wmscreen, text, scr->white, scr->workspace_name_font, 2, 2, name, len); #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 || !ximg->image) { 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) RReleaseImage(data->text); if (data->back) RReleaseImage(data->back); wfree(data); scr->workspace_name_data = NULL; scr->workspace_name_timer = WMAddTimerHandler(WORKSPACE_NAME_DELAY + 10 * WORKSPACE_NAME_FADE_DELAY, hideWorkspaceName, scr); } void wWorkspaceChange(WScreen *scr, int workspace) { if (scr->flags.startup || scr->flags.startup2 || scr->flags.ignore_focus_events) return; if (workspace != scr->current_workspace) wWorkspaceForceChange(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.mapped || tmp->flags.shaded) && !WFLAGP(tmp, no_focusable)) || 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 (!wPreferences.sticky_icons && tmp->flags.miniaturized && tmp->icon && !IS_OMNIPRESENT(tmp)) { XUnmapWindow(dpy, tmp->icon->core->window); tmp->icon->mapped = 0; } /* 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 && (tmp->flags.mapped || tmp->flags.shaded)) { 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 && !WFLAGP(tmp, no_focusable)) { 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; } /* Gobble up events unleashed by our mapping & unmapping. * These may trigger various grab-initiated focus & * crossing events. However, we don't care about them, * and ignore their focus implications altogether to avoid * flicker. */ scr->flags.ignore_focus_events = 1; ProcessPendingEvents(); scr->flags.ignore_focus_events = 0; 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 there's a window under the pointer, focus it. * (we ate all other focus events above, so it's * certainly not focused). Otherwise focus last * focused, or the root (depending on sloppiness) */ 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); } } wScreenUpdateUsableArea(scr); wNETWMUpdateDesktop(scr); showWorkspaceName(scr, workspace); WMPostNotificationName(WMNWorkspaceChanged, scr, (void *)(uintptr_t) workspace); /* XSync(dpy, False); */ } #ifdef VIRTUAL_DESKTOP /* TODO: * * 1) Allow border around each window so the scrolling * won't just stop at the border. * 2) Make pager. * */ #define vec_sub(a, b) wmkpoint((a).x-(b).x, (a).y-(b).y) #define vec_add(a, b) wmkpoint((a).x+(b).x, (a).y+(b).y) #define vec_inc(a, b) do { (a).x+=(b).x; (a).y+=(b).y; } while(0) #define vec_dot(a, b) ((a).x*(b).x + (a).y*(b).y) #define vec_scale(a, s) wmkpoint((a).x*s, (a).y*s) #define vec_scale2(a, s, t) wmkpoint((a).x*s, (a).y*t) #ifndef HAS_BORDER #define HAS_BORDER(w) (!(WFLAGP((w), no_border))) #endif #ifndef IS_VSTUCK #define IS_VSTUCK(w) (WFLAGP((w), virtual_stick)) #endif #define updateMinimum(l,p,ml,mp) do { if (cmp(l) && (l)<(ml)) { (ml)=(l); (mp)=(p); }; } while(0) static Bool cmp_gez(int i) { return (i >= 0); } static Bool cmp_gz(int i) { return (i > 0); } static WMPoint getClosestEdge(WScreen * scr, WMPoint direction, Bool(*cmp) (int)) { WMPoint closest = wmkpoint(0, 0); int closest_len = INT_MAX; WWindow *wwin; for (wwin = scr->focused_window; wwin; wwin = wwin->prev) { if (wwin->frame->workspace == scr->current_workspace) { if (!wwin->flags.miniaturized && !IS_VSTUCK(wwin) && !wwin->flags.hidden) { int border = 2 * HAS_BORDER(wwin); int len; int x1, x2, y1, y2; int head = wGetHeadForWindow(wwin); WArea area = wGetUsableAreaForHead(scr, head, NULL, False); WMPoint p; x1 = wwin->frame_x - area.x1; y1 = wwin->frame_y - area.y1; x2 = wwin->frame_x + wwin->frame->core->width + border - area.x2; y2 = wwin->frame_y + wwin->frame->core->height + border - area.y2; p = wmkpoint(x1, y1); len = vec_dot(direction, p); updateMinimum(len, p, closest_len, closest); p = wmkpoint(x1, y2); len = vec_dot(direction, p); updateMinimum(len, p, closest_len, closest); p = wmkpoint(x2, y1); len = vec_dot(direction, p); updateMinimum(len, p, closest_len, closest); p = wmkpoint(x2, y2); len = vec_dot(direction, p); updateMinimum(len, p, closest_len, closest); } } } return closest; } static void getViewPosition(WScreen * scr, int workspace, int *x, int *y) { *x = scr->workspaces[workspace]->view_x; *y = scr->workspaces[workspace]->view_y; } void wWorkspaceKeyboardMoveDesktop(WScreen * scr, WMPoint direction) { int x, y; WMPoint edge = getClosestEdge(scr, direction, cmp_gz); int len = vec_dot(edge, direction); WMPoint step = vec_scale(direction, len); getViewPosition(scr, scr->current_workspace, &x, &y); wWorkspaceSetViewport(scr, scr->current_workspace, x + step.x, y + step.y); } extern Cursor wCursor[WCUR_LAST]; static void vdMouseMoveDesktop(XEvent * event, WMPoint direction) { static int lock = False; if (lock) return; lock = True; Bool done = False; Bool moved = True; WScreen *scr = wScreenForRootWindow(event->xcrossing.root); WMPoint old_pos = wmkpoint(event->xcrossing.x_root, event->xcrossing.y_root); WMPoint step; int x, y; int resisted = 0; if (XGrabPointer(dpy, event->xcrossing.window, False, PointerMotionMask, GrabModeAsync, GrabModeAsync, scr->root_win, wCursor[WCUR_EMPTY], CurrentTime) != GrabSuccess) { /* if the grab fails, do it the old fashioned way */ step = vec_scale2(direction, wPreferences.vedge_hscrollspeed, wPreferences.vedge_vscrollspeed); getViewPosition(scr, scr->current_workspace, &x, &y); if (wWorkspaceSetViewport(scr, scr->current_workspace, x + step.x, y + step.y)) { step = vec_scale(direction, 2); XWarpPointer(dpy, None, scr->root_win, 0, 0, 0, 0, event->xcrossing.x_root - step.x, event->xcrossing.y_root - step.y); } goto exit; } XSync(dpy, True); if (old_pos.x < 0) old_pos.x = 0; if (old_pos.y < 0) old_pos.y = 0; if (old_pos.x > scr->scr_width) old_pos.x = scr->scr_width; if (old_pos.y > scr->scr_height) old_pos.y = scr->scr_height; while (!done) { XEvent ev; if (moved) { XWarpPointer(dpy, None, scr->root_win, 0, 0, 0, 0, scr->scr_width / 2, scr->scr_height / 2); moved = False; } WMMaskEvent(dpy, PointerMotionMask, &ev); switch (ev.type) { case MotionNotify: { int step_len; step = wmkpoint(ev.xmotion.x_root - scr->scr_width / 2, ev.xmotion.y_root - scr->scr_height / 2); step_len = vec_dot(step, direction); if (step_len < 0) { done = True; break; } if (step_len > 0) { Bool do_move = True; int resist = wPreferences.vedge_resistance; WMPoint closest; int closest_len = INT_MAX; if (resist) { closest = getClosestEdge(scr, direction, cmp_gez); closest_len = vec_dot(direction, closest); } if (!closest_len) { resisted += step_len; do_move = resisted >= resist; if (do_move) { closest_len = INT_MAX; step_len = resisted - resist; resisted = 0; } } if (do_move) { if (closest_len <= wPreferences.vedge_attraction) { step = vec_scale(direction, closest_len); } else { step = vec_scale(direction, step_len); } getViewPosition(scr, scr->current_workspace, &x, &y); wWorkspaceSetViewport(scr, scr->current_workspace, x + step.x, y + step.y); moved = True; } } } break; } } step = vec_add(old_pos, vec_scale(direction, -1)); XWarpPointer(dpy, None, scr->root_win, 0, 0, 0, 0, step.x, step.y); XUngrabPointer(dpy, CurrentTime); exit: lock = False; } static void vdHandleEnter_u(XEvent * event) { vdMouseMoveDesktop(event, VEC_UP); } static void vdHandleEnter_d(XEvent * event) { vdMouseMoveDesktop(event, VEC_DOWN); } static void vdHandleEnter_l(XEvent * event) { vdMouseMoveDesktop(event, VEC_LEFT); } static void vdHandleEnter_r(XEvent * event) { vdMouseMoveDesktop(event, VEC_RIGHT); } #define LEFT_EDGE 0x01 #define RIGHT_EDGE 0x02 #define TOP_EDGE 0x04 #define BOTTOM_EDGE 0x08 #define ALL_EDGES 0x0F static void createEdges(WScreen * scr) { if (!scr->virtual_edges) { int i, j, w; int vmask; XSetWindowAttributes attribs; int heads = wXineramaHeads(scr); int *hasEdges = (int *)wmalloc(sizeof(int) * heads); int thickness = 1; int nr_edges = 0; int max_edges = 4 * heads; int head; Window *edges = (Window *) wmalloc(sizeof(Window) * max_edges); for (i = 0; i < heads; ++i) hasEdges[i] = ALL_EDGES; for (i = 0; i < heads; ++i) { WMRect i_rect = wGetRectForHead(scr, i); for (j = i + 1; j < heads; ++j) { WMRect j_rect = wGetRectForHead(scr, j); int vlen = (WMIN(i_rect.pos.y + i_rect.size.height, j_rect.pos.y + j_rect.size.height) - WMAX(i_rect.pos.y, j_rect.pos.y)); int hlen = (WMIN(i_rect.pos.x + i_rect.size.width, j_rect.pos.x + j_rect.size.width) - WMAX(i_rect.pos.x, j_rect.pos.x)); if (vlen > 0 && hlen == 0) { /* horz alignment, vert edges touch */ if (i_rect.pos.x < j_rect.pos.x) { /* i left of j */ hasEdges[i] &= ~RIGHT_EDGE; hasEdges[j] &= ~LEFT_EDGE; } else { /* j left of i */ hasEdges[j] &= ~RIGHT_EDGE; hasEdges[i] &= ~LEFT_EDGE; } } else if (vlen == 0 && hlen > 0) { /* vert alignment, horz edges touch */ if (i_rect.pos.y < j_rect.pos.y) { /* i top of j */ hasEdges[i] &= ~BOTTOM_EDGE; hasEdges[j] &= ~TOP_EDGE; } else { /* j top of i */ hasEdges[j] &= ~BOTTOM_EDGE; hasEdges[i] &= ~TOP_EDGE; } } } } for (w = 0; w < scr->workspace_count; w++) { /* puts("reset workspace"); */ wWorkspaceSetViewport(scr, w, 0, 0); } vmask = CWEventMask | CWOverrideRedirect; attribs.event_mask = (EnterWindowMask | LeaveWindowMask | VisibilityChangeMask); attribs.override_redirect = True; for (head = 0; head < wXineramaHeads(scr); ++head) { WMRect rect = wGetRectForHead(scr, head); if (hasEdges[head] & TOP_EDGE) { edges[nr_edges] = XCreateWindow(dpy, scr->root_win, rect.pos.x, rect.pos.y, rect.size.width, thickness, 0, CopyFromParent, InputOnly, CopyFromParent, vmask, &attribs); XSaveContext(dpy, edges[nr_edges], wVEdgeContext, (XPointer) vdHandleEnter_u); ++nr_edges; } if (hasEdges[head] & BOTTOM_EDGE) { edges[nr_edges] = XCreateWindow(dpy, scr->root_win, rect.pos.x, rect.pos.y + rect.size.height - thickness, rect.size.width, thickness, 0, CopyFromParent, InputOnly, CopyFromParent, vmask, &attribs); XSaveContext(dpy, edges[nr_edges], wVEdgeContext, (XPointer) vdHandleEnter_d); ++nr_edges; } if (hasEdges[head] & LEFT_EDGE) { edges[nr_edges] = XCreateWindow(dpy, scr->root_win, rect.pos.x, rect.pos.y, thickness, rect.pos.y + rect.size.height, 0, CopyFromParent, InputOnly, CopyFromParent, vmask, &attribs); XSaveContext(dpy, edges[nr_edges], wVEdgeContext, (XPointer) vdHandleEnter_l); ++nr_edges; } if (hasEdges[head] & RIGHT_EDGE) { edges[nr_edges] = XCreateWindow(dpy, scr->root_win, rect.pos.x + rect.size.width - thickness, rect.pos.y, thickness, rect.size.height, 0, CopyFromParent, InputOnly, CopyFromParent, vmask, &attribs); XSaveContext(dpy, edges[nr_edges], wVEdgeContext, (XPointer) vdHandleEnter_r); ++nr_edges; } } scr->virtual_nr_edges = nr_edges; scr->virtual_edges = edges; for (i = 0; i < scr->virtual_nr_edges; ++i) { XMapWindow(dpy, scr->virtual_edges[i]); } wWorkspaceRaiseEdge(scr); wfree(hasEdges); } } static void destroyEdges(WScreen * scr) { if (scr->virtual_edges) { int i; for (i = 0; i < scr->virtual_nr_edges; ++i) { XDeleteContext(dpy, scr->virtual_edges[i], wVEdgeContext); XUnmapWindow(dpy, scr->virtual_edges[i]); XDestroyWindow(dpy, scr->virtual_edges[i]); } wfree(scr->virtual_edges); scr->virtual_edges = NULL; scr->virtual_nr_edges = 0; } } void wWorkspaceUpdateEdge(WScreen * scr) { if (wPreferences.vdesk_enable) { createEdges(scr); } else { destroyEdges(scr); } } void wWorkspaceRaiseEdge(WScreen * scr) { static int toggle = 0; int i; if (!scr->virtual_edges) return; if (toggle) { for (i = 0; i < scr->virtual_nr_edges; ++i) { XRaiseWindow(dpy, scr->virtual_edges[i]); } } else { for (i = scr->virtual_nr_edges - 1; i >= 0; --i) { XRaiseWindow(dpy, scr->virtual_edges[i]); } } toggle ^= 1; } void wWorkspaceLowerEdge(WScreen * scr) { int i; for (i = 0; i < scr->virtual_nr_edges; ++i) { XLowerWindow(dpy, scr->virtual_edges[i]); } } void wWorkspaceResizeViewport(WScreen * scr, int workspace) { int x, y; getViewPosition(scr, scr->current_workspace, &x, &y); wWorkspaceSetViewport(scr, scr->current_workspace, x, y); } void updateWorkspaceGeometry(WScreen * scr, int workspace, int *view_x, int *view_y) { int most_left, most_right, most_top, most_bottom; WWindow *wwin; int heads = wXineramaHeads(scr); typedef int strut_t[4]; strut_t *strut = (strut_t *) wmalloc(heads * sizeof(strut_t)); int head, i; for (head = 0; head < heads; ++head) { WMRect rect = wGetRectForHead(scr, head); WArea area = wGetUsableAreaForHead(scr, head, NULL, False); strut[head][0] = area.x1 - rect.pos.x; strut[head][1] = rect.pos.x + rect.size.width - area.x2; strut[head][2] = area.y1 - rect.pos.y; strut[head][3] = rect.pos.y + rect.size.height - area.y2; } /* adjust workspace layout */ wwin = scr->focused_window; most_right = 0; most_bottom = 0; most_left = scr->scr_width; most_top = scr->scr_height; for (; wwin; wwin = wwin->prev) { if (wwin->frame->workspace == workspace) { if (!wwin->flags.miniaturized && !IS_VSTUCK(wwin) && !wwin->flags.hidden) { head = wGetHeadForWindow(wwin); i = wwin->frame_x - strut[head][0]; if (i < most_left) /* record positions, should this be cached? */ most_left = i; i = wwin->frame_x + wwin->frame->core->width + strut[head][1]; if (HAS_BORDER(wwin)) i += 2; if (i > most_right) most_right = i; i = wwin->frame_y - strut[head][2]; if (i < most_top) most_top = i; i = wwin->frame_y + wwin->frame->core->height + strut[head][3]; if (HAS_BORDER(wwin)) i += 2; if (i > most_bottom) { most_bottom = i; } } } } if (most_left > 0) most_left = 0; if (most_top > 0) most_top = 0; scr->workspaces[workspace]->width = WMAX(most_right, scr->scr_width) - WMIN(most_left, 0); scr->workspaces[workspace]->height = WMAX(most_bottom, scr->scr_height) - WMIN(most_top, 0); *view_x += -most_left - scr->workspaces[workspace]->view_x; scr->workspaces[workspace]->view_x = -most_left; *view_y += -most_top - scr->workspaces[workspace]->view_y; scr->workspaces[workspace]->view_y = -most_top; wfree(strut); } typedef struct _delay_configure { WWindow *wwin; int delay_count; } _delay_configure; void sendConfigureNotify(_delay_configure * delay) { WWindow *wwin; delay->delay_count--; if (!delay->delay_count) { for (wwin = delay->wwin; wwin; wwin = wwin->prev) { wWindowSynthConfigureNotify(wwin); } } } Bool wWorkspaceSetViewport(WScreen * scr, int workspace, int view_x, int view_y) { Bool adjust_flag = False; int diff_x, diff_y; static _delay_configure delay_configure = { NULL, 0 }; WWorkspace *wptr; WWindow *wwin; wptr = scr->workspaces[workspace]; /*printf("wWorkspaceSetViewport %d %d\n", view_x, view_y); */ updateWorkspaceGeometry(scr, workspace, &view_x, &view_y); if (view_x + scr->scr_width > wptr->width) { /* puts("right edge of vdesk"); */ view_x = wptr->width - scr->scr_width; } if (view_x < 0) { /* puts("left edge of vdesk"); */ view_x = 0; } if (view_y + scr->scr_height > wptr->height) { /* puts("right edge of vdesk"); */ view_y = wptr->height - scr->scr_height; } if (view_y < 0) { /* puts("left edge of vdesk"); */ view_y = 0; } diff_x = wptr->view_x - view_x; diff_y = wptr->view_y - view_y; if (!diff_x && !diff_y) return False; wptr->view_x = view_x; wptr->view_y = view_y; wNETWMUpdateDesktop(scr); for (wwin = scr->focused_window; wwin; wwin = wwin->prev) { if (wwin->frame->workspace == workspace && !IS_VSTUCK(wwin)) { wWindowMove(wwin, wwin->frame_x + diff_x, wwin->frame_y + diff_y); adjust_flag = True; } } if (1) { /* if delay */ delay_configure.delay_count++; delay_configure.wwin = scr->focused_window; WMAddTimerHandler(200, (WMCallback *) sendConfigureNotify, &delay_configure); } return adjust_flag; } #endif 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) { snprintf(buf, sizeof(buf), _("Workspace %i"), workspace + 1); } else { strncpy(buf, tmp, MAX_WORKSPACENAME_WIDTH); } buf[MAX_WORKSPACENAME_WIDTH] = 0; /* update workspace */ wfree(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) { wfree(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) { wfree(scr->workspace_menu->entries[workspace + 2]->text); scr->workspace_menu->entries[workspace + 2]->text = wstrdup(buf); wMenuRealize(scr->workspace_menu); } } if (scr->clip_icon) wClipIconPaint(scr->clip_icon); WMPostNotificationName(WMNWorkspaceNameChanged, scr, (void *)(uintptr_t) workspace); } /* 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) { strncpy(title, scr->workspaces[ws]->name, MAX_WORKSPACENAME_WIDTH); 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; i < scr->workspace_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, WMPropList * old_state) { WMPropList *parr, *pstr, *wks_state, *old_wks_state, *foo, *bar; int i; make_keys(); old_wks_state = WMGetFromPLDictionary(old_state, dWorkspaces); parr = WMCreatePLArray(NULL); for (i = 0; i < scr->workspace_count; i++) { pstr = WMCreatePLString(scr->workspaces[i]->name); wks_state = WMCreatePLDictionary(dName, pstr, NULL); WMReleasePropList(pstr); if (!wPreferences.flags.noclip) { pstr = wClipSaveWorkspaceState(scr, i); WMPutInPLDictionary(wks_state, dClip, pstr); WMReleasePropList(pstr); } else if (old_wks_state != NULL) { if ((foo = WMGetFromPLArray(old_wks_state, i)) != NULL) { if ((bar = WMGetFromPLDictionary(foo, dClip)) != NULL) { WMPutInPLDictionary(wks_state, dClip, bar); } } } WMAddToPLArray(parr, wks_state); WMReleasePropList(wks_state); } WMPutInPLDictionary(scr->session_state, dWorkspaces, parr); WMReleasePropList(parr); } void wWorkspaceRestoreState(WScreen * scr) { WMPropList *parr, *pstr, *wks_state, *clip_state; int i, j, wscount; make_keys(); if (scr->session_state == NULL) return; parr = WMGetFromPLDictionary(scr->session_state, dWorkspaces); if (!parr) return; wscount = scr->workspace_count; for (i = 0; i < WMIN(WMGetPropListItemCount(parr), MAX_WORKSPACES); i++) { wks_state = WMGetFromPLArray(parr, i); if (WMIsPLDictionary(wks_state)) pstr = WMGetFromPLDictionary(wks_state, dName); else pstr = wks_state; if (i >= scr->workspace_count) wWorkspaceNew(scr); if (scr->workspace_menu) { wfree(scr->workspace_menu->entries[i + 2]->text); scr->workspace_menu->entries[i + 2]->text = wstrdup(WMGetFromPLString(pstr)); scr->workspace_menu->flags.realized = 0; } wfree(scr->workspaces[i]->name); scr->workspaces[i]->name = wstrdup(WMGetFromPLString(pstr)); if (!wPreferences.flags.noclip) { int added_omnipresent_icons = 0; clip_state = WMGetFromPLDictionary(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; j < scr->workspaces[i]->clip->max_icons; j++) { WAppIcon *aicon = scr->workspaces[i]->clip->icon_array[j]; int k; if (!aicon || !aicon->omnipresent) continue; aicon->omnipresent = 0; if (wClipMakeIconOmnipresent(aicon, True) != WO_SUCCESS) continue; if (i == 0) continue; /* Move this appicon from workspace i to workspace 0 */ scr->workspaces[i]->clip->icon_array[j] = NULL; scr->workspaces[i]->clip->icon_count--; added_omnipresent_icons++; /* If there are too many omnipresent appicons, we are in trouble */ assert(scr->workspaces[0]->clip->icon_count + added_omnipresent_icons <= scr->workspaces[0]->clip->max_icons); /* Find first free spot on workspace 0 */ for (k = 0; k < scr->workspaces[0]->clip->max_icons; k++) if (scr->workspaces[0]->clip->icon_array[k] == NULL) break; scr->workspaces[0]->clip->icon_array[k] = aicon; aicon->dock = scr->workspaces[0]->clip; } scr->workspaces[0]->clip->icon_count += added_omnipresent_icons; } WMPostNotificationName(WMNWorkspaceNameChanged, scr, (void *)(uintptr_t) i); } }