wmaker: Fix compiler warnings about pointer <--> integer conversion
[wmaker-crm.git] / src / workspace.c
blob3c72710fc304944650be82f40415536e92ba495b
1 /* workspace.c- Workspace management
3 * Window Maker window manager
5 * Copyright (c) 1997-2003 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,
20 * USA.
22 #include "wconfig.h"
24 #include <X11/Xlib.h>
25 #include <X11/Xutil.h>
26 #ifdef SHAPE
27 #include <X11/extensions/shape.h>
28 #endif
30 #include <stdlib.h>
31 #include <stdio.h>
32 #include <stdint.h>
33 #include <unistd.h>
34 #include <ctype.h>
35 #include <string.h>
36 #include <time.h>
37 #include <sys/time.h>
39 #include "WindowMaker.h"
40 #include "wcore.h"
41 #include "framewin.h"
42 #include "window.h"
43 #include "icon.h"
44 #include "funcs.h"
45 #include "menu.h"
46 #include "application.h"
47 #include "dock.h"
48 #include "actions.h"
49 #include "workspace.h"
50 #include "appicon.h"
51 #ifdef NETWM_HINTS
52 #include "wmspec.h"
53 #endif
55 #include "xinerama.h"
58 extern WPreferences wPreferences;
59 extern XContext wWinContext;
60 extern XContext wVEdgeContext;
62 extern void ProcessPendingEvents();
64 static WMPropList *dWorkspaces=NULL;
65 static WMPropList *dClip, *dName;
69 static void
70 make_keys()
72 if (dWorkspaces!=NULL)
73 return;
75 dWorkspaces = WMCreatePLString("Workspaces");
76 dName = WMCreatePLString("Name");
77 dClip = WMCreatePLString("Clip");
81 void
82 wWorkspaceMake(WScreen *scr, int count)
84 while (count>0) {
85 wWorkspaceNew(scr);
86 count--;
91 int
92 wWorkspaceNew(WScreen *scr)
94 WWorkspace *wspace, **list;
95 int i;
97 if (scr->workspace_count < MAX_WORKSPACES) {
98 scr->workspace_count++;
100 wspace = wmalloc(sizeof(WWorkspace));
101 wspace->name = NULL;
103 if (!wspace->name) {
104 wspace->name = wmalloc(strlen(_("Workspace %i"))+8);
105 sprintf(wspace->name, _("Workspace %i"), scr->workspace_count);
109 if (!wPreferences.flags.noclip) {
110 wspace->clip = wDockCreate(scr, WM_CLIP);
111 } else
112 wspace->clip = NULL;
114 list = wmalloc(sizeof(WWorkspace*)*scr->workspace_count);
116 for (i=0; i<scr->workspace_count-1; i++) {
117 list[i] = scr->workspaces[i];
119 list[i] = wspace;
120 if (scr->workspaces)
121 wfree(scr->workspaces);
122 scr->workspaces = list;
124 wWorkspaceMenuUpdate(scr, scr->workspace_menu);
125 wWorkspaceMenuUpdate(scr, scr->clip_ws_menu);
126 #ifdef VIRTUAL_DESKTOP
127 wspace->view_x = wspace->view_y = 0;
128 wspace->height = scr->scr_height;
129 wspace->width = scr->scr_width;
130 #endif
131 #ifdef NETWM_HINTS
132 wNETWMUpdateDesktop(scr);
133 #endif
135 WMPostNotificationName(WMNWorkspaceCreated, scr,
136 (void*)(uintptr_t)(scr->workspace_count-1));
137 XFlush(dpy);
139 return scr->workspace_count-1;
142 return -1;
146 Bool
147 wWorkspaceDelete(WScreen *scr, int workspace)
149 WWindow *tmp;
150 WWorkspace **list;
151 int i, j;
153 if (workspace<=0)
154 return False;
156 /* verify if workspace is in use by some window */
157 tmp = scr->focused_window;
158 while (tmp) {
159 if (!IS_OMNIPRESENT(tmp) && tmp->frame->workspace==workspace)
160 return False;
161 tmp = tmp->prev;
164 if (!wPreferences.flags.noclip) {
165 wDockDestroy(scr->workspaces[workspace]->clip);
166 scr->workspaces[workspace]->clip = NULL;
169 list = wmalloc(sizeof(WWorkspace*)*(scr->workspace_count-1));
170 j = 0;
171 for (i=0; i<scr->workspace_count; i++) {
172 if (i!=workspace) {
173 list[j++] = scr->workspaces[i];
174 } else {
175 if (scr->workspaces[i]->name)
176 wfree(scr->workspaces[i]->name);
177 wfree(scr->workspaces[i]);
180 wfree(scr->workspaces);
181 scr->workspaces = list;
183 scr->workspace_count--;
186 /* update menu */
187 wWorkspaceMenuUpdate(scr, scr->workspace_menu);
188 /* clip workspace menu */
189 wWorkspaceMenuUpdate(scr, scr->clip_ws_menu);
191 /* update also window menu */
192 if (scr->workspace_submenu) {
193 WMenu *menu = scr->workspace_submenu;
195 i = menu->entry_no;
196 while (i>scr->workspace_count)
197 wMenuRemoveItem(menu, --i);
198 wMenuRealize(menu);
200 /* and clip menu */
201 if (scr->clip_submenu) {
202 WMenu *menu = scr->clip_submenu;
204 i = menu->entry_no;
205 while (i>scr->workspace_count)
206 wMenuRemoveItem(menu, --i);
207 wMenuRealize(menu);
210 #ifdef NETWM_HINTS
211 wNETWMUpdateDesktop(scr);
212 #endif
214 WMPostNotificationName(WMNWorkspaceDestroyed, scr,
215 (void*)(uintptr_t)(scr->workspace_count-1));
217 if (scr->current_workspace >= scr->workspace_count)
218 wWorkspaceChange(scr, scr->workspace_count-1);
220 return True;
224 typedef struct WorkspaceNameData {
225 int count;
226 RImage *back;
227 RImage *text;
228 time_t timeout;
229 } WorkspaceNameData;
232 static void
233 hideWorkspaceName(void *data)
235 WScreen *scr = (WScreen*)data;
237 if (!scr->workspace_name_data || scr->workspace_name_data->count == 0
238 || time(NULL) > scr->workspace_name_data->timeout) {
239 XUnmapWindow(dpy, scr->workspace_name);
241 if (scr->workspace_name_data) {
242 RReleaseImage(scr->workspace_name_data->back);
243 RReleaseImage(scr->workspace_name_data->text);
244 wfree(scr->workspace_name_data);
246 scr->workspace_name_data = NULL;
248 scr->workspace_name_timer = NULL;
249 } else {
250 RImage *img = RCloneImage(scr->workspace_name_data->back);
251 Pixmap pix;
253 scr->workspace_name_timer =
254 WMAddTimerHandler(WORKSPACE_NAME_FADE_DELAY, hideWorkspaceName, scr);
256 RCombineImagesWithOpaqueness(img, scr->workspace_name_data->text,
257 scr->workspace_name_data->count*255/10);
259 RConvertImage(scr->rcontext, img, &pix);
261 RReleaseImage(img);
263 XSetWindowBackgroundPixmap(dpy, scr->workspace_name, pix);
264 XClearWindow(dpy, scr->workspace_name);
265 XFreePixmap(dpy, pix);
266 XFlush(dpy);
268 scr->workspace_name_data->count--;
273 static void
274 showWorkspaceName(WScreen *scr, int workspace)
276 WorkspaceNameData *data;
277 RXImage *ximg;
278 Pixmap text, mask;
279 int w, h;
280 int px, py;
281 char *name = scr->workspaces[workspace]->name;
282 int len = strlen(name);
283 int x, y;
285 if (wPreferences.workspace_name_display_position == WD_NONE ||
286 scr->workspace_count < 2) {
287 return;
290 if (scr->workspace_name_timer) {
291 WMDeleteTimerHandler(scr->workspace_name_timer);
292 XUnmapWindow(dpy, scr->workspace_name);
293 XFlush(dpy);
295 scr->workspace_name_timer = WMAddTimerHandler(WORKSPACE_NAME_DELAY,
296 hideWorkspaceName, scr);
298 if (scr->workspace_name_data) {
299 RReleaseImage(scr->workspace_name_data->back);
300 RReleaseImage(scr->workspace_name_data->text);
301 wfree(scr->workspace_name_data);
304 data = wmalloc(sizeof(WorkspaceNameData));
305 data->back = NULL;
307 w = WMWidthOfString(scr->workspace_name_font, name, len);
308 h = WMFontHeight(scr->workspace_name_font);
310 switch (wPreferences.workspace_name_display_position) {
311 case WD_TOP:
312 px = (scr->scr_width - (w+4))/2;
313 py = 0;
314 break;
315 case WD_BOTTOM:
316 px = (scr->scr_width - (w+4))/2;
317 py = scr->scr_height - (h+4);
318 break;
319 case WD_TOPLEFT:
320 px = 0;
321 py = 0;
322 break;
323 case WD_TOPRIGHT:
324 px = scr->scr_width - (w+4);
325 py = 0;
326 break;
327 case WD_BOTTOMLEFT:
328 px = 0;
329 py = scr->scr_height - (h+4);
330 break;
331 case WD_BOTTOMRIGHT:
332 px = scr->scr_width - (w+4);
333 py = scr->scr_height - (h+4);
334 break;
335 case WD_CENTER:
336 default:
337 px = (scr->scr_width - (w+4))/2;
338 py = (scr->scr_height - (h+4))/2;
339 break;
341 XResizeWindow(dpy, scr->workspace_name, w+4, h+4);
342 XMoveWindow(dpy, scr->workspace_name, px, py);
344 text = XCreatePixmap(dpy, scr->w_win, w+4, h+4, scr->w_depth);
345 mask = XCreatePixmap(dpy, scr->w_win, w+4, h+4, 1);
347 /*XSetForeground(dpy, scr->mono_gc, 0);
348 XFillRectangle(dpy, mask, scr->mono_gc, 0, 0, w+4, h+4);*/
350 XFillRectangle(dpy, text, WMColorGC(scr->black), 0, 0, w+4, h+4);
352 for (x = 0; x <= 4; x++) {
353 for (y = 0; y <= 4; y++) {
354 WMDrawString(scr->wmscreen, text, scr->white,
355 scr->workspace_name_font, x, y, name, len);
359 XSetForeground(dpy, scr->mono_gc, 1);
360 XSetBackground(dpy, scr->mono_gc, 0);
362 XCopyPlane(dpy, text, mask, scr->mono_gc, 0, 0, w+4, h+4, 0, 0, 1<<(scr->w_depth-1));
364 /*XSetForeground(dpy, scr->mono_gc, 1);*/
365 XSetBackground(dpy, scr->mono_gc, 1);
367 XFillRectangle(dpy, text, WMColorGC(scr->black), 0, 0, w+4, h+4);
369 WMDrawString(scr->wmscreen, text, scr->white, scr->workspace_name_font,
370 2, 2, name, len);
372 #ifdef SHAPE
373 XShapeCombineMask(dpy, scr->workspace_name, ShapeBounding, 0, 0, mask,
374 ShapeSet);
375 #endif
376 XSetWindowBackgroundPixmap(dpy, scr->workspace_name, text);
377 XClearWindow(dpy, scr->workspace_name);
379 data->text = RCreateImageFromDrawable(scr->rcontext, text, None);
381 XFreePixmap(dpy, text);
382 XFreePixmap(dpy, mask);
384 if (!data->text) {
385 XMapRaised(dpy, scr->workspace_name);
386 XFlush(dpy);
388 goto erro;
391 ximg = RGetXImage(scr->rcontext, scr->root_win, px, py,
392 data->text->width, data->text->height);
394 if (!ximg || !ximg->image) {
395 goto erro;
398 XMapRaised(dpy, scr->workspace_name);
399 XFlush(dpy);
401 data->back = RCreateImageFromXImage(scr->rcontext, ximg->image, NULL);
402 RDestroyXImage(scr->rcontext, ximg);
404 if (!data->back) {
405 goto erro;
408 data->count = 10;
410 /* set a timeout for the effect */
411 data->timeout = time(NULL) + 2 +
412 (WORKSPACE_NAME_DELAY + WORKSPACE_NAME_FADE_DELAY*data->count)/1000;
414 scr->workspace_name_data = data;
417 return;
419 erro:
420 if (scr->workspace_name_timer)
421 WMDeleteTimerHandler(scr->workspace_name_timer);
423 if (data->text)
424 RReleaseImage(data->text);
425 if (data->back)
426 RReleaseImage(data->back);
427 wfree(data);
429 scr->workspace_name_data = NULL;
431 scr->workspace_name_timer = WMAddTimerHandler(WORKSPACE_NAME_DELAY +
432 10*WORKSPACE_NAME_FADE_DELAY,
433 hideWorkspaceName, scr);
437 void
438 wWorkspaceChange(WScreen *scr, int workspace)
440 if (scr->flags.startup || scr->flags.startup2) {
441 return;
444 if (workspace != scr->current_workspace) {
445 wWorkspaceForceChange(scr, workspace);
446 } /*else {
447 showWorkspaceName(scr, workspace);
452 void
453 wWorkspaceRelativeChange(WScreen *scr, int amount)
455 int w;
457 w = scr->current_workspace + amount;
459 if (amount < 0) {
460 if (w >= 0) {
461 wWorkspaceChange(scr, w);
462 } else if (wPreferences.ws_cycle) {
463 wWorkspaceChange(scr, scr->workspace_count + w);
465 } else if (amount > 0) {
466 if (w < scr->workspace_count) {
467 wWorkspaceChange(scr, w);
468 } else if (wPreferences.ws_advance) {
469 wWorkspaceChange(scr, WMIN(w, MAX_WORKSPACES-1));
470 } else if (wPreferences.ws_cycle) {
471 wWorkspaceChange(scr, w % scr->workspace_count);
477 void
478 wWorkspaceForceChange(WScreen *scr, int workspace)
480 WWindow *tmp, *foc=NULL, *foc2=NULL;
482 if (workspace >= MAX_WORKSPACES || workspace < 0)
483 return;
485 SendHelperMessage(scr, 'C', workspace+1, NULL);
487 if (workspace > scr->workspace_count-1) {
488 wWorkspaceMake(scr, workspace - scr->workspace_count + 1);
491 wClipUpdateForWorkspaceChange(scr, workspace);
493 scr->current_workspace = workspace;
495 wWorkspaceMenuUpdate(scr, scr->workspace_menu);
497 wWorkspaceMenuUpdate(scr, scr->clip_ws_menu);
499 if ((tmp = scr->focused_window)!= NULL) {
500 if ((IS_OMNIPRESENT(tmp) && (tmp->flags.mapped || tmp->flags.shaded) &&
501 !WFLAGP(tmp, no_focusable)) || tmp->flags.changing_workspace) {
502 foc = tmp;
505 /* foc2 = tmp; will fix annoyance with gnome panel
506 * but will create annoyance for every other application
508 while (tmp) {
509 if (tmp->frame->workspace!=workspace && !tmp->flags.selected) {
510 /* unmap windows not on this workspace */
511 if ((tmp->flags.mapped||tmp->flags.shaded) &&
512 !IS_OMNIPRESENT(tmp) && !tmp->flags.changing_workspace) {
513 wWindowUnmap(tmp);
515 /* also unmap miniwindows not on this workspace */
516 if (!wPreferences.sticky_icons && tmp->flags.miniaturized &&
517 tmp->icon && !IS_OMNIPRESENT(tmp)) {
518 XUnmapWindow(dpy, tmp->icon->core->window);
519 tmp->icon->mapped = 0;
521 /* update current workspace of omnipresent windows */
522 if (IS_OMNIPRESENT(tmp)) {
523 WApplication *wapp = wApplicationOf(tmp->main_window);
525 tmp->frame->workspace = workspace;
527 if (wapp) {
528 wapp->last_workspace = workspace;
530 if (!foc2 && (tmp->flags.mapped || tmp->flags.shaded)) {
531 foc2 = tmp;
534 } else {
535 /* change selected windows' workspace */
536 if (tmp->flags.selected) {
537 wWindowChangeWorkspace(tmp, workspace);
538 if (!tmp->flags.miniaturized && !foc) {
539 foc = tmp;
541 } else {
542 if (!tmp->flags.hidden) {
543 if (!(tmp->flags.mapped || tmp->flags.miniaturized)) {
544 /* remap windows that are on this workspace */
545 wWindowMap(tmp);
546 if (!foc && !WFLAGP(tmp, no_focusable)) {
547 foc = tmp;
550 /* Also map miniwindow if not omnipresent */
551 if (!wPreferences.sticky_icons &&
552 tmp->flags.miniaturized &&
553 !IS_OMNIPRESENT(tmp) && tmp->icon) {
554 tmp->icon->mapped = 1;
555 XMapWindow(dpy, tmp->icon->core->window);
560 tmp = tmp->prev;
563 /* Gobble up events unleashed by our mapping & unmapping.
564 * These may trigger various grab-initiated focus &
565 * crossing events. However, we don't care about them,
566 * and ignore their focus implications altogether to avoid
567 * flicker.
569 scr->flags.ignore_focus_events = 1;
570 ProcessPendingEvents();
571 scr->flags.ignore_focus_events = 0;
573 if (!foc)
574 foc = foc2;
576 if (scr->focused_window->flags.mapped && !foc) {
577 foc = scr->focused_window;
579 if (wPreferences.focus_mode == WKF_CLICK) {
580 wSetFocusTo(scr, foc);
581 } else {
582 unsigned int mask;
583 int foo;
584 Window bar, win;
585 WWindow *tmp;
587 tmp = NULL;
588 if (XQueryPointer(dpy, scr->root_win, &bar, &win,
589 &foo, &foo, &foo, &foo, &mask)) {
590 tmp = wWindowFor(win);
593 /* If there's a window under the pointer, focus it.
594 * (we ate all other focus events above, so it's
595 * certainly not focused). Otherwise focus last
596 * focused, or the root (depending on sloppiness)
598 if (!tmp && wPreferences.focus_mode == WKF_SLOPPY) {
599 wSetFocusTo(scr, foc);
600 } else {
601 wSetFocusTo(scr, tmp);
606 /* We need to always arrange icons when changing workspace, even if
607 * no autoarrange icons, because else the icons in different workspaces
608 * can be superposed.
609 * This can be avoided if appicons are also workspace specific.
611 if (!wPreferences.sticky_icons)
612 wArrangeIcons(scr, False);
614 if (scr->dock)
615 wAppIconPaint(scr->dock->icon_array[0]);
617 if (scr->clip_icon) {
618 if (scr->workspaces[workspace]->clip->auto_collapse ||
619 scr->workspaces[workspace]->clip->auto_raise_lower) {
620 /* to handle enter notify. This will also */
621 XUnmapWindow(dpy, scr->clip_icon->icon->core->window);
622 XMapWindow(dpy, scr->clip_icon->icon->core->window);
623 } else {
624 wClipIconPaint(scr->clip_icon);
628 #ifdef NETWM_HINTS
629 wScreenUpdateUsableArea(scr);
630 wNETWMUpdateDesktop(scr);
631 #endif
633 showWorkspaceName(scr, workspace);
635 WMPostNotificationName(WMNWorkspaceChanged, scr, (void*)(uintptr_t)workspace);
637 /* XSync(dpy, False); */
641 #ifdef VIRTUAL_DESKTOP
643 /* TODO:
645 * 1) Allow border around each window so the scrolling
646 * won't just stop at the border.
647 * 2) Make pager.
651 #define vec_sub(a, b) wmkpoint((a).x-(b).x, (a).y-(b).y)
652 #define vec_add(a, b) wmkpoint((a).x+(b).x, (a).y+(b).y)
653 #define vec_inc(a, b) do { (a).x+=(b).x; (a).y+=(b).y; } while(0)
654 #define vec_dot(a, b) ((a).x*(b).x + (a).y*(b).y)
655 #define vec_scale(a, s) wmkpoint((a).x*s, (a).y*s)
656 #define vec_scale2(a, s, t) wmkpoint((a).x*s, (a).y*t)
658 #ifndef HAS_BORDER
659 #define HAS_BORDER(w) (!(WFLAGP((w), no_border)))
660 #endif
662 #ifndef IS_VSTUCK
663 #define IS_VSTUCK(w) (WFLAGP((w), virtual_stick))
664 #endif
666 #define updateMinimum(l,p,ml,mp) do { if (cmp(l) && (l)<(ml)) { (ml)=(l); (mp)=(p); }; } while(0)
668 static Bool cmp_gez(int i) { return (i >= 0); }
670 static Bool cmp_gz(int i) { return (i > 0); }
673 static WMPoint
674 getClosestEdge(WScreen * scr, WMPoint direction, Bool (*cmp)(int))
676 WMPoint closest = wmkpoint(0, 0);
677 int closest_len = INT_MAX;
678 WWindow * wwin;
680 for (wwin=scr->focused_window; wwin; wwin=wwin->prev) {
681 if (wwin->frame->workspace == scr->current_workspace) {
682 if (!wwin->flags.miniaturized &&
683 !IS_VSTUCK(wwin) &&
684 !wwin->flags.hidden) {
685 int border = 2*HAS_BORDER(wwin);
686 int len;
687 int x1,x2,y1,y2;
688 int head = wGetHeadForWindow(wwin);
689 WArea area = wGetUsableAreaForHead(scr, head, NULL, False);
690 WMPoint p;
692 x1 = wwin->frame_x - area.x1;
693 y1 = wwin->frame_y - area.y1;
694 x2 = wwin->frame_x + wwin->frame->core->width + border - area.x2;
695 y2 = wwin->frame_y + wwin->frame->core->height + border - area.y2;
697 p = wmkpoint(x1,y1);
698 len = vec_dot(direction, p);
699 updateMinimum(len, p, closest_len, closest);
701 p = wmkpoint(x1,y2);
702 len = vec_dot(direction, p);
703 updateMinimum(len, p, closest_len, closest);
705 p = wmkpoint(x2,y1);
706 len = vec_dot(direction, p);
707 updateMinimum(len, p, closest_len, closest);
709 p = wmkpoint(x2,y2);
710 len = vec_dot(direction, p);
711 updateMinimum(len, p, closest_len, closest);
716 return closest;
720 static void
721 getViewPosition(WScreen *scr, int workspace, int *x, int *y)
723 *x = scr->workspaces[workspace]->view_x;
724 *y = scr->workspaces[workspace]->view_y;
728 void
729 wWorkspaceKeyboardMoveDesktop(WScreen *scr, WMPoint direction)
731 int x, y;
732 WMPoint edge = getClosestEdge(scr, direction, cmp_gz);
733 int len = vec_dot(edge, direction);
734 WMPoint step = vec_scale(direction, len);
735 getViewPosition(scr, scr->current_workspace, &x, &y);
736 wWorkspaceSetViewport(scr, scr->current_workspace, x+step.x, y+step.y);
740 extern Cursor wCursor[WCUR_LAST];
743 static void
744 vdMouseMoveDesktop(XEvent *event, WMPoint direction)
746 static int lock = False;
747 if (lock) return;
748 lock = True;
750 Bool done = False;
751 Bool moved = True;
752 WScreen *scr = wScreenForRootWindow(event->xcrossing.root);
753 WMPoint old_pos = wmkpoint(event->xcrossing.x_root, event->xcrossing.y_root);
754 WMPoint step;
755 int x, y;
756 int resisted = 0;
758 if (XGrabPointer(dpy, event->xcrossing.window, False,
759 PointerMotionMask, GrabModeAsync, GrabModeAsync,
760 scr->root_win, wCursor[WCUR_EMPTY],
761 CurrentTime) != GrabSuccess) {
763 /* if the grab fails, do it the old fashioned way */
764 step = vec_scale2(direction, wPreferences.vedge_hscrollspeed,
765 wPreferences.vedge_vscrollspeed);
766 getViewPosition(scr, scr->current_workspace, &x, &y);
767 if (wWorkspaceSetViewport(scr, scr->current_workspace,
768 x+step.x, y+step.y)) {
769 step = vec_scale(direction, 2);
770 XWarpPointer(dpy, None, scr->root_win, 0,0,0,0,
771 event->xcrossing.x_root - step.x,
772 event->xcrossing.y_root - step.y);
774 goto exit;
776 XSync(dpy, True);
778 if (old_pos.x < 0)
779 old_pos.x = 0;
780 if (old_pos.y < 0)
781 old_pos.y = 0;
782 if (old_pos.x > scr->scr_width)
783 old_pos.x = scr->scr_width;
784 if (old_pos.y > scr->scr_height)
785 old_pos.y = scr->scr_height;
787 while (!done) {
788 XEvent ev;
789 if (moved) {
790 XWarpPointer(dpy, None, scr->root_win, 0, 0, 0, 0,
791 scr->scr_width/2, scr->scr_height/2);
792 moved = False;
794 WMMaskEvent(dpy, PointerMotionMask, &ev);
796 switch (ev.type) {
797 case MotionNotify:
799 int step_len;
800 step = wmkpoint(ev.xmotion.x_root-scr->scr_width/2,
801 ev.xmotion.y_root-scr->scr_height/2);
802 step_len = vec_dot(step, direction);
803 if (step_len < 0) {
804 done = True;
805 break;
808 if (step_len > 0) {
809 Bool do_move = True;
810 int resist = wPreferences.vedge_resistance;
811 WMPoint closest;
812 int closest_len = INT_MAX;
813 if (resist) {
814 closest = getClosestEdge(scr, direction, cmp_gez);
815 closest_len = vec_dot(direction, closest);
818 if (!closest_len) {
819 resisted += step_len;
820 do_move = resisted >= resist;
821 if (do_move) {
822 closest_len = INT_MAX;
823 step_len = resisted - resist;
824 resisted = 0;
827 if (do_move) {
828 if (closest_len <= wPreferences.vedge_attraction) {
829 step = vec_scale(direction, closest_len);
830 } else {
831 step = vec_scale(direction, step_len);
834 getViewPosition(scr, scr->current_workspace, &x, &y);
835 wWorkspaceSetViewport(scr, scr->current_workspace,
836 x+step.x, y+step.y);
837 moved = True;
841 break;
845 step = vec_add(old_pos, vec_scale(direction, -1));
846 XWarpPointer(dpy, None, scr->root_win, 0,0,0,0, step.x, step.y);
847 XUngrabPointer(dpy, CurrentTime);
849 exit:
850 lock = False;
854 static void
855 vdHandleEnter_u(XEvent *event) {
856 vdMouseMoveDesktop(event, VEC_UP);
860 static void
861 vdHandleEnter_d(XEvent *event) {
862 vdMouseMoveDesktop(event, VEC_DOWN);
866 static void
867 vdHandleEnter_l(XEvent *event) {
868 vdMouseMoveDesktop(event, VEC_LEFT);
872 static void
873 vdHandleEnter_r(XEvent *event) {
874 vdMouseMoveDesktop(event, VEC_RIGHT);
878 #define LEFT_EDGE 0x01
879 #define RIGHT_EDGE 0x02
880 #define TOP_EDGE 0x04
881 #define BOTTOM_EDGE 0x08
882 #define ALL_EDGES 0x0F
884 static void
885 createEdges(WScreen *scr)
887 if (!scr->virtual_edges) {
888 int i, j, w;
889 int vmask;
890 XSetWindowAttributes attribs;
892 int heads = wXineramaHeads(scr);
893 int *hasEdges = (int*)wmalloc(sizeof(int)*heads);
895 int thickness = 1;
896 int nr_edges = 0;
897 int max_edges = 4*heads;
898 int head;
899 Window *edges = (Window *)wmalloc(sizeof(Window)*max_edges);
901 for (i=0; i<heads; ++i)
902 hasEdges[i] = ALL_EDGES;
903 for (i=0; i<heads; ++i) {
904 WMRect i_rect = wGetRectForHead(scr, i);
905 for (j=i+1; j<heads; ++j) {
906 WMRect j_rect = wGetRectForHead(scr, j);
908 int vlen = (WMIN(i_rect.pos.y + i_rect.size.height,
909 j_rect.pos.y + j_rect.size.height) -
910 WMAX(i_rect.pos.y, j_rect.pos.y));
912 int hlen = (WMIN(i_rect.pos.x + i_rect.size.width,
913 j_rect.pos.x + j_rect.size.width) -
914 WMAX( i_rect.pos.x, j_rect.pos.x));
916 if (vlen > 0 && hlen == 0) { /* horz alignment, vert edges touch */
917 if (i_rect.pos.x < j_rect.pos.x) { /* i left of j */
918 hasEdges[i] &= ~RIGHT_EDGE;
919 hasEdges[j] &= ~LEFT_EDGE;
920 } else { /* j left of i */
921 hasEdges[j] &= ~RIGHT_EDGE;
922 hasEdges[i] &= ~LEFT_EDGE;
924 } else if (vlen == 0 && hlen > 0) { /* vert alignment, horz edges touch */
925 if (i_rect.pos.y < j_rect.pos.y) { /* i top of j */
926 hasEdges[i] &= ~BOTTOM_EDGE;
927 hasEdges[j] &= ~TOP_EDGE;
928 } else { /* j top of i */
929 hasEdges[j] &= ~BOTTOM_EDGE;
930 hasEdges[i] &= ~TOP_EDGE;
936 for (w = 0; w < scr->workspace_count; w++) {
937 /* puts("reset workspace"); */
938 wWorkspaceSetViewport(scr, w, 0, 0);
941 vmask = CWEventMask|CWOverrideRedirect;
942 attribs.event_mask = (EnterWindowMask | LeaveWindowMask | VisibilityChangeMask);
943 attribs.override_redirect = True;
945 for (head=0; head<wXineramaHeads(scr); ++head) {
946 WMRect rect = wGetRectForHead(scr, head);
948 if (hasEdges[head] & TOP_EDGE) {
949 edges[nr_edges] =
950 XCreateWindow(dpy, scr->root_win, rect.pos.x, rect.pos.y,
951 rect.size.width, thickness, 0,
952 CopyFromParent, InputOnly, CopyFromParent,
953 vmask, &attribs);
954 XSaveContext(dpy, edges[nr_edges], wVEdgeContext,
955 (XPointer)vdHandleEnter_u);
956 ++nr_edges;
959 if (hasEdges[head] & BOTTOM_EDGE) {
960 edges[nr_edges] =
961 XCreateWindow(dpy, scr->root_win, rect.pos.x,
962 rect.pos.y+rect.size.height-thickness,
963 rect.size.width, thickness, 0,
964 CopyFromParent, InputOnly, CopyFromParent,
965 vmask, &attribs);
966 XSaveContext(dpy, edges[nr_edges], wVEdgeContext,
967 (XPointer)vdHandleEnter_d);
968 ++nr_edges;
971 if (hasEdges[head] & LEFT_EDGE) {
972 edges[nr_edges] =
973 XCreateWindow(dpy, scr->root_win, rect.pos.x, rect.pos.y,
974 thickness, rect.pos.y+rect.size.height, 0,
975 CopyFromParent, InputOnly, CopyFromParent,
976 vmask, &attribs);
977 XSaveContext(dpy, edges[nr_edges], wVEdgeContext,
978 (XPointer)vdHandleEnter_l);
979 ++nr_edges;
982 if (hasEdges[head] & RIGHT_EDGE) {
983 edges[nr_edges] =
984 XCreateWindow(dpy, scr->root_win,
985 rect.pos.x + rect.size.width - thickness, rect.pos.y,
986 thickness, rect.size.height, 0,
987 CopyFromParent, InputOnly, CopyFromParent, vmask,
988 &attribs);
989 XSaveContext(dpy, edges[nr_edges], wVEdgeContext,
990 (XPointer)vdHandleEnter_r);
991 ++nr_edges;
995 scr->virtual_nr_edges = nr_edges;
996 scr->virtual_edges = edges;
998 for (i=0; i<scr->virtual_nr_edges; ++i) {
999 XMapWindow(dpy, scr->virtual_edges[i]);
1001 wWorkspaceRaiseEdge(scr);
1003 wfree(hasEdges);
1008 static void
1009 destroyEdges(WScreen *scr)
1011 if (scr->virtual_edges) {
1012 int i;
1014 for (i=0; i<scr->virtual_nr_edges; ++i) {
1015 XDeleteContext(dpy, scr->virtual_edges[i], wVEdgeContext);
1016 XUnmapWindow(dpy, scr->virtual_edges[i]);
1017 XDestroyWindow(dpy, scr->virtual_edges[i]);
1020 wfree(scr->virtual_edges);
1021 scr->virtual_edges = NULL;
1022 scr->virtual_nr_edges = 0;
1027 void
1028 wWorkspaceUpdateEdge(WScreen *scr)
1030 if (wPreferences.vdesk_enable) {
1031 createEdges(scr);
1032 } else {
1033 destroyEdges(scr);
1038 void
1039 wWorkspaceRaiseEdge(WScreen *scr)
1041 static int toggle = 0;
1042 int i;
1044 if (!scr->virtual_edges)
1045 return;
1047 if (toggle) {
1048 for (i=0; i<scr->virtual_nr_edges; ++i) {
1049 XRaiseWindow(dpy, scr->virtual_edges[i]);
1051 } else {
1052 for (i=scr->virtual_nr_edges-1; i>=0; --i) {
1053 XRaiseWindow(dpy, scr->virtual_edges[i]);
1057 toggle ^= 1;
1061 void
1062 wWorkspaceLowerEdge(WScreen *scr)
1064 int i;
1065 for (i=0; i<scr->virtual_nr_edges; ++i) {
1066 XLowerWindow(dpy, scr->virtual_edges[i]);
1071 void
1072 wWorkspaceResizeViewport(WScreen *scr, int workspace)
1074 int x, y;
1075 getViewPosition(scr, scr->current_workspace, &x, &y);
1076 wWorkspaceSetViewport(scr, scr->current_workspace, x, y);
1080 void
1081 updateWorkspaceGeometry(WScreen *scr, int workspace, int *view_x, int *view_y)
1083 int most_left, most_right, most_top, most_bottom;
1084 WWindow *wwin;
1086 int heads = wXineramaHeads(scr);
1087 typedef int strut_t[4];
1088 strut_t * strut = (strut_t*)wmalloc(heads*sizeof(strut_t));
1089 int head, i;
1091 for (head=0; head<heads; ++head) {
1092 WMRect rect = wGetRectForHead(scr, head);
1093 WArea area = wGetUsableAreaForHead(scr, head, NULL, False);
1094 strut[head][0] = area.x1 - rect.pos.x;
1095 strut[head][1] = rect.pos.x + rect.size.width - area.x2;
1096 strut[head][2] = area.y1 - rect.pos.y;
1097 strut[head][3] = rect.pos.y + rect.size.height - area.y2;
1100 /* adjust workspace layout */
1101 wwin = scr->focused_window;
1102 most_right = 0;
1103 most_bottom = 0;
1104 most_left = scr->scr_width;
1105 most_top = scr->scr_height;
1106 for(;wwin; wwin = wwin->prev) {
1107 if (wwin->frame->workspace == workspace) {
1108 if (!wwin->flags.miniaturized && !IS_VSTUCK(wwin) &&
1109 !wwin->flags.hidden) {
1111 head = wGetHeadForWindow(wwin);
1113 i = wwin->frame_x - strut[head][0];
1114 if (i < most_left) /* record positions, should this be cached? */
1115 most_left = i;
1116 i = wwin->frame_x + wwin->frame->core->width + strut[head][1];
1117 if (HAS_BORDER(wwin))
1118 i+=2;
1119 if (i > most_right)
1120 most_right = i;
1121 i = wwin->frame_y - strut[head][2];
1122 if (i < most_top)
1123 most_top = i;
1124 i = wwin->frame_y + wwin->frame->core->height + strut[head][3];
1125 if (HAS_BORDER(wwin))
1126 i+=2;
1127 if (i > most_bottom) {
1128 most_bottom = i;
1134 if (most_left > 0) most_left = 0;
1135 if (most_top > 0) most_top = 0;
1137 scr->workspaces[workspace]->width = WMAX(most_right, scr->scr_width) - WMIN(most_left, 0);
1138 scr->workspaces[workspace]->height = WMAX(most_bottom, scr->scr_height) - WMIN(most_top, 0);
1140 *view_x += -most_left - scr->workspaces[workspace]->view_x;
1141 scr->workspaces[workspace]->view_x = -most_left;
1143 *view_y += -most_top - scr->workspaces[workspace]->view_y;
1144 scr->workspaces[workspace]->view_y = -most_top;
1146 wfree(strut);
1150 typedef struct _delay_configure {
1151 WWindow *wwin;
1152 int delay_count;
1153 } _delay_configure;
1156 void
1157 sendConfigureNotify (_delay_configure *delay)
1159 WWindow *wwin;
1161 delay->delay_count--;
1162 if (!delay->delay_count) {
1163 for (wwin = delay->wwin; wwin; wwin = wwin->prev) {
1164 wWindowSynthConfigureNotify(wwin);
1170 Bool
1171 wWorkspaceSetViewport(WScreen *scr, int workspace, int view_x, int view_y)
1173 Bool adjust_flag = False;
1174 int diff_x, diff_y;
1175 static _delay_configure delay_configure = {NULL, 0};
1176 WWorkspace *wptr;
1177 WWindow *wwin;
1179 wptr = scr->workspaces[workspace];
1181 /*printf("wWorkspaceSetViewport %d %d\n", view_x, view_y);*/
1183 updateWorkspaceGeometry(scr, workspace, &view_x, &view_y);
1185 if (view_x + scr->scr_width > wptr->width) {
1186 /* puts("right edge of vdesk"); */
1187 view_x = wptr->width - scr->scr_width;
1189 if (view_x < 0) {
1190 /* puts("left edge of vdesk"); */
1191 view_x = 0;
1193 if (view_y + scr->scr_height > wptr->height) {
1194 /* puts("right edge of vdesk"); */
1195 view_y = wptr->height - scr->scr_height;
1197 if (view_y < 0) {
1198 /* puts("left edge of vdesk"); */
1199 view_y = 0;
1202 diff_x = wptr->view_x - view_x;
1203 diff_y = wptr->view_y - view_y;
1204 if (!diff_x && !diff_y)
1205 return False;
1207 wptr->view_x = view_x;
1208 wptr->view_y = view_y;
1210 #ifdef NETWM_HINTS
1211 wNETWMUpdateDesktop(scr);
1212 #endif
1214 for (wwin = scr->focused_window; wwin; wwin = wwin->prev) {
1215 if (wwin->frame->workspace == workspace && !IS_VSTUCK(wwin)) {
1216 wWindowMove(wwin, wwin->frame_x + diff_x, wwin->frame_y + diff_y);
1217 adjust_flag = True;
1220 if (1) { /* if delay*/
1221 delay_configure.delay_count++;
1222 delay_configure.wwin = scr->focused_window;
1223 WMAddTimerHandler(200, (WMCallback *)sendConfigureNotify, &delay_configure);
1226 return adjust_flag;
1230 #endif
1233 static void
1234 switchWSCommand(WMenu *menu, WMenuEntry *entry)
1236 wWorkspaceChange(menu->frame->screen_ptr, (long)entry->clientdata);
1240 static void
1241 deleteWSCommand(WMenu *menu, WMenuEntry *entry)
1243 wWorkspaceDelete(menu->frame->screen_ptr,
1244 menu->frame->screen_ptr->workspace_count-1);
1248 static void
1249 newWSCommand(WMenu *menu, WMenuEntry *foo)
1251 int ws;
1253 ws = wWorkspaceNew(menu->frame->screen_ptr);
1254 /* autochange workspace*/
1255 if (ws>=0)
1256 wWorkspaceChange(menu->frame->screen_ptr, ws);
1260 if (ws<9) {
1261 int kcode;
1262 if (wKeyBindings[WKBD_WORKSPACE1+ws]) {
1263 kcode = wKeyBindings[WKBD_WORKSPACE1+ws]->keycode;
1264 entry->rtext =
1265 wstrdup(XKeysymToString(XKeycodeToKeysym(dpy, kcode, 0)));
1271 static char*
1272 cropline(char *line)
1274 char *start, *end;
1276 if (strlen(line)==0)
1277 return line;
1279 start = line;
1280 end = &(line[strlen(line)])-1;
1281 while (isspace(*line) && *line!=0) line++;
1282 while (isspace(*end) && end!=line) {
1283 *end=0;
1284 end--;
1286 return line;
1290 void
1291 wWorkspaceRename(WScreen *scr, int workspace, char *name)
1293 char buf[MAX_WORKSPACENAME_WIDTH+1];
1294 char *tmp;
1296 if (workspace >= scr->workspace_count)
1297 return;
1299 /* trim white spaces */
1300 tmp = cropline(name);
1302 if (strlen(tmp)==0) {
1303 snprintf(buf, sizeof(buf), _("Workspace %i"), workspace+1);
1304 } else {
1305 strncpy(buf, tmp, MAX_WORKSPACENAME_WIDTH);
1307 buf[MAX_WORKSPACENAME_WIDTH] = 0;
1309 /* update workspace */
1310 wfree(scr->workspaces[workspace]->name);
1311 scr->workspaces[workspace]->name = wstrdup(buf);
1313 if (scr->clip_ws_menu) {
1314 if (strcmp(scr->clip_ws_menu->entries[workspace+2]->text, buf)!=0) {
1315 wfree(scr->clip_ws_menu->entries[workspace+2]->text);
1316 scr->clip_ws_menu->entries[workspace+2]->text = wstrdup(buf);
1317 wMenuRealize(scr->clip_ws_menu);
1320 if (scr->workspace_menu) {
1321 if (strcmp(scr->workspace_menu->entries[workspace+2]->text, buf)!=0) {
1322 wfree(scr->workspace_menu->entries[workspace+2]->text);
1323 scr->workspace_menu->entries[workspace+2]->text = wstrdup(buf);
1324 wMenuRealize(scr->workspace_menu);
1328 if (scr->clip_icon)
1329 wClipIconPaint(scr->clip_icon);
1331 WMPostNotificationName(WMNWorkspaceNameChanged, scr, (void*)(uintptr_t)workspace);
1337 /* callback for when menu entry is edited */
1338 static void
1339 onMenuEntryEdited(WMenu *menu, WMenuEntry *entry)
1341 char *tmp;
1343 tmp = entry->text;
1344 wWorkspaceRename(menu->frame->screen_ptr, (long)entry->clientdata, tmp);
1348 WMenu*
1349 wWorkspaceMenuMake(WScreen *scr, Bool titled)
1351 WMenu *wsmenu;
1353 wsmenu = wMenuCreate(scr, titled ? _("Workspaces") : NULL, False);
1354 if (!wsmenu) {
1355 wwarning(_("could not create Workspace menu"));
1356 return NULL;
1359 /* callback to be called when an entry is edited */
1360 wsmenu->on_edit = onMenuEntryEdited;
1362 wMenuAddCallback(wsmenu, _("New"), newWSCommand, NULL);
1363 wMenuAddCallback(wsmenu, _("Destroy Last"), deleteWSCommand, NULL);
1365 return wsmenu;
1370 void
1371 wWorkspaceMenuUpdate(WScreen *scr, WMenu *menu)
1373 int i;
1374 long ws;
1375 char title[MAX_WORKSPACENAME_WIDTH+1];
1376 WMenuEntry *entry;
1377 int tmp;
1379 if (!menu)
1380 return;
1382 if (menu->entry_no < scr->workspace_count+2) {
1383 /* new workspace(s) added */
1384 i = scr->workspace_count-(menu->entry_no-2);
1385 ws = menu->entry_no - 2;
1386 while (i>0) {
1387 strcpy(title, scr->workspaces[ws]->name);
1389 entry = wMenuAddCallback(menu, title, switchWSCommand, (void*)ws);
1390 entry->flags.indicator = 1;
1391 entry->flags.editable = 1;
1393 i--;
1394 ws++;
1396 } else if (menu->entry_no > scr->workspace_count+2) {
1397 /* removed workspace(s) */
1398 for (i = menu->entry_no-1; i >= scr->workspace_count+2; i--) {
1399 wMenuRemoveItem(menu, i);
1402 wMenuRealize(menu);
1404 for (i=0; i<scr->workspace_count; i++) {
1405 menu->entries[i+2]->flags.indicator_on = 0;
1407 menu->entries[scr->current_workspace+2]->flags.indicator_on = 1;
1409 /* don't let user destroy current workspace */
1410 if (scr->current_workspace == scr->workspace_count-1) {
1411 wMenuSetEnabled(menu, 1, False);
1412 } else {
1413 wMenuSetEnabled(menu, 1, True);
1416 tmp = menu->frame->top_width + 5;
1417 /* if menu got unreachable, bring it to a visible place */
1418 if (menu->frame_x < tmp - (int)menu->frame->core->width)
1419 wMenuMove(menu, tmp - (int)menu->frame->core->width, menu->frame_y, False);
1421 wMenuPaint(menu);
1425 void
1426 wWorkspaceSaveState(WScreen *scr, WMPropList *old_state)
1428 WMPropList *parr, *pstr, *wks_state, *old_wks_state, *foo, *bar;
1429 int i;
1431 make_keys();
1433 old_wks_state = WMGetFromPLDictionary(old_state, dWorkspaces);
1434 parr = WMCreatePLArray(NULL);
1435 for (i=0; i < scr->workspace_count; i++) {
1436 pstr = WMCreatePLString(scr->workspaces[i]->name);
1437 wks_state = WMCreatePLDictionary(dName, pstr, NULL);
1438 WMReleasePropList(pstr);
1439 if (!wPreferences.flags.noclip) {
1440 pstr = wClipSaveWorkspaceState(scr, i);
1441 WMPutInPLDictionary(wks_state, dClip, pstr);
1442 WMReleasePropList(pstr);
1443 } else if (old_wks_state!=NULL) {
1444 if ((foo = WMGetFromPLArray(old_wks_state, i))!=NULL) {
1445 if ((bar = WMGetFromPLDictionary(foo, dClip))!=NULL) {
1446 WMPutInPLDictionary(wks_state, dClip, bar);
1450 WMAddToPLArray(parr, wks_state);
1451 WMReleasePropList(wks_state);
1453 WMPutInPLDictionary(scr->session_state, dWorkspaces, parr);
1454 WMReleasePropList(parr);
1458 void
1459 wWorkspaceRestoreState(WScreen *scr)
1461 WMPropList *parr, *pstr, *wks_state, *clip_state;
1462 int i, j, wscount;
1464 make_keys();
1466 if (scr->session_state == NULL)
1467 return;
1469 parr = WMGetFromPLDictionary(scr->session_state, dWorkspaces);
1471 if (!parr)
1472 return;
1474 wscount = scr->workspace_count;
1475 for (i=0; i < WMIN(WMGetPropListItemCount(parr), MAX_WORKSPACES); i++) {
1476 wks_state = WMGetFromPLArray(parr, i);
1477 if (WMIsPLDictionary(wks_state))
1478 pstr = WMGetFromPLDictionary(wks_state, dName);
1479 else
1480 pstr = wks_state;
1481 if (i >= scr->workspace_count)
1482 wWorkspaceNew(scr);
1483 if (scr->workspace_menu) {
1484 wfree(scr->workspace_menu->entries[i+2]->text);
1485 scr->workspace_menu->entries[i+2]->text = wstrdup(WMGetFromPLString(pstr));
1486 scr->workspace_menu->flags.realized = 0;
1488 wfree(scr->workspaces[i]->name);
1489 scr->workspaces[i]->name = wstrdup(WMGetFromPLString(pstr));
1490 if (!wPreferences.flags.noclip) {
1491 clip_state = WMGetFromPLDictionary(wks_state, dClip);
1492 if (scr->workspaces[i]->clip)
1493 wDockDestroy(scr->workspaces[i]->clip);
1494 scr->workspaces[i]->clip = wDockRestoreState(scr, clip_state,
1495 WM_CLIP);
1496 if (i>0)
1497 wDockHideIcons(scr->workspaces[i]->clip);
1499 /* We set the global icons here, because scr->workspaces[i]->clip
1500 * was not valid in wDockRestoreState().
1501 * There we only set icon->omnipresent to know which icons we
1502 * need to set here.
1504 for (j=0; j<scr->workspaces[i]->clip->max_icons; j++) {
1505 WAppIcon *aicon = scr->workspaces[i]->clip->icon_array[j];
1507 if (aicon && aicon->omnipresent) {
1508 aicon->omnipresent = 0;
1509 wClipMakeIconOmnipresent(aicon, True);
1510 XMapWindow(dpy, aicon->icon->core->window);
1515 WMPostNotificationName(WMNWorkspaceNameChanged, scr, (void*)(uintptr_t)i);