Allow undocking appicon when Alt is pressed
[wmaker-crm.git] / src / appicon.c
blob4ccd3a42141b10ac75224219a045a6fa862e79a1
1 /* appicon.c- icon for applications (not mini-window)
3 * Window Maker window manager
5 * Copyright (c) 1997-2003 Alfredo K. Kojima
6 * Copyright (c) 1998-2003 Dan Pascu
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License along
19 * with this program; if not, write to the Free Software Foundation, Inc.,
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 #include "wconfig.h"
25 #include <X11/Xlib.h>
26 #include <X11/Xutil.h>
27 #include <stdlib.h>
28 #include <libgen.h>
29 #include <string.h>
30 #include <unistd.h>
31 #include <sys/stat.h>
32 #include <errno.h>
34 #include "WindowMaker.h"
35 #include "window.h"
36 #include "icon.h"
37 #include "application.h"
38 #include "appicon.h"
39 #include "actions.h"
40 #include "stacking.h"
41 #include "dock.h"
42 #include "main.h"
43 #include "defaults.h"
44 #include "workspace.h"
45 #include "superfluous.h"
46 #include "menu.h"
47 #include "framewin.h"
48 #include "dialog.h"
49 #include "xinerama.h"
50 #include "client.h"
51 #include "placement.h"
52 #include "misc.h"
53 #include "event.h"
54 #ifdef XDND
55 #include "xdnd.h"
56 #endif
59 * icon_file for the dock is got from the preferences file by
60 * using the classname/instancename
63 /**** Global variables ****/
64 extern WDDomain *WDWindowAttributes;
66 #define MOD_MASK wPreferences.modifier_mask
67 #define ICON_SIZE wPreferences.icon_size
69 static void iconDblClick(WObjDescriptor * desc, XEvent * event);
70 static void iconExpose(WObjDescriptor * desc, XEvent * event);
71 static void wApplicationSaveIconPathFor(const char *iconPath, const char *wm_instance, const char *wm_class);
72 static WAppIcon *wAppIconCreate(WWindow * leader_win);
73 static void add_to_appicon_list(WAppIcon *appicon);
74 static void remove_from_appicon_list(WAppIcon *appicon);
75 static void create_appicon_from_dock(WWindow *wwin, WApplication *wapp, Window main_window);
77 /* This function is used if the application is a .app. It checks if it has an icon in it
78 * like for example /usr/local/GNUstep/Applications/WPrefs.app/WPrefs.tiff
80 void wApplicationExtractDirPackIcon(const char *path, const char *wm_instance, const char *wm_class)
82 char *iconPath = NULL;
83 char *tmp = NULL;
85 if (strstr(path, ".app")) {
86 tmp = wmalloc(strlen(path) + 16);
88 if (wPreferences.supports_tiff) {
89 strcpy(tmp, path);
90 strcat(tmp, ".tiff");
91 if (access(tmp, R_OK) == 0)
92 iconPath = tmp;
95 if (!iconPath) {
96 strcpy(tmp, path);
97 strcat(tmp, ".xpm");
98 if (access(tmp, R_OK) == 0)
99 iconPath = tmp;
102 if (!iconPath)
103 wfree(tmp);
105 if (iconPath) {
106 wApplicationSaveIconPathFor(iconPath, wm_instance, wm_class);
107 wfree(iconPath);
112 WAppIcon *wAppIconCreateForDock(WScreen *scr, const char *command, const char *wm_instance, const char *wm_class, int tile)
114 WAppIcon *aicon;
116 aicon = wmalloc(sizeof(WAppIcon));
117 wretain(aicon);
118 aicon->yindex = -1;
119 aicon->xindex = -1;
121 add_to_appicon_list(aicon);
123 if (command)
124 aicon->command = wstrdup(command);
126 if (wm_class)
127 aicon->wm_class = wstrdup(wm_class);
129 if (wm_instance)
130 aicon->wm_instance = wstrdup(wm_instance);
132 if (strcmp(wm_class, "WMDock") == 0 && wPreferences.flags.clip_merged_in_dock)
133 tile = TILE_CLIP;
134 aicon->icon = icon_create_for_dock(scr, command, wm_instance, wm_class, tile);
136 #ifdef XDND
137 wXDNDMakeAwareness(aicon->icon->core->window);
138 #endif
140 /* will be overriden by dock */
141 aicon->icon->core->descriptor.handle_mousedown = appIconMouseDown;
142 aicon->icon->core->descriptor.handle_expose = iconExpose;
143 aicon->icon->core->descriptor.parent_type = WCLASS_APPICON;
144 aicon->icon->core->descriptor.parent = aicon;
145 AddToStackList(aicon->icon->core);
147 return aicon;
150 void create_appicon_for_application(WApplication *wapp, WWindow *wwin)
152 /* Try to create an icon from the dock or clip */
153 create_appicon_from_dock(wwin, wapp, wapp->main_window);
155 /* If app_icon was not found, create it */
156 if (!wapp->app_icon) {
157 /* Create the icon */
158 wapp->app_icon = wAppIconCreate(wapp->main_window_desc);
159 wIconUpdate(wapp->app_icon->icon);
161 /* Now, paint the icon */
162 if (!WFLAGP(wapp->main_window_desc, no_appicon))
163 paint_app_icon(wapp);
166 /* Save the app_icon in a file */
167 save_appicon(wapp->app_icon, False);
170 void unpaint_app_icon(WApplication *wapp)
172 WAppIcon *aicon;
173 WScreen *scr;
174 WDock *clip;
176 if (!wapp || !wapp->app_icon)
177 return;
179 aicon = wapp->app_icon;
181 /* If the icon is docked, don't continue */
182 if (aicon->docked)
183 return;
185 scr = wapp->main_window_desc->screen_ptr;
186 clip = w_global.workspace.array[w_global.workspace.current]->clip;
188 if (!clip || !aicon->attracted || !clip->collapsed)
189 XUnmapWindow(dpy, aicon->icon->core->window);
191 /* We want to avoid having it on the list because otherwise
192 * there will be a hole when the icons are arranged with
193 * wArrangeIcons() */
194 remove_from_appicon_list(aicon);
196 if (wPreferences.auto_arrange_icons && !aicon->attracted)
197 wArrangeIcons(scr, True);
200 void paint_app_icon(WApplication *wapp)
202 WIcon *icon;
203 WScreen *scr = wapp->main_window_desc->screen_ptr;
204 WDock *attracting_dock;
205 int x = 0, y = 0;
206 Bool update_icon = False;
208 if (!wapp || !wapp->app_icon)
209 return;
211 icon = wapp->app_icon->icon;
212 wapp->app_icon->main_window = wapp->main_window;
214 /* If the icon is docked, don't continue */
215 if (wapp->app_icon->docked)
216 return;
218 attracting_dock = scr->attracting_drawer != NULL ?
219 scr->attracting_drawer :
220 w_global.workspace.array[w_global.workspace.current]->clip;
221 if (attracting_dock && attracting_dock->attract_icons &&
222 wDockFindFreeSlot(attracting_dock, &x, &y)) {
223 wapp->app_icon->attracted = 1;
224 if (!icon->shadowed) {
225 icon->shadowed = 1;
226 update_icon = True;
228 wDockAttachIcon(attracting_dock, wapp->app_icon, x, y, update_icon);
229 } else {
230 /* We must know if the icon is painted in the screen,
231 * because if painted, then PlaceIcon will return the next
232 * space on the screen, and the icon will move */
233 if (wapp->app_icon->next == NULL && wapp->app_icon->prev == NULL) {
234 PlaceIcon(scr, &x, &y, wGetHeadForWindow(wapp->main_window_desc));
235 wAppIconMove(wapp->app_icon, x, y);
236 wLowerFrame(icon->core);
240 /* If we want appicon (no_appicon is not set) and the icon is not
241 * in the appicon_list, we must add it. Else, we want to avoid
242 * having it on the list */
243 if (!WFLAGP(wapp->main_window_desc, no_appicon) &&
244 wapp->app_icon->next == NULL && wapp->app_icon->prev == NULL)
245 add_to_appicon_list(wapp->app_icon);
247 if (!attracting_dock || !wapp->app_icon->attracted || !attracting_dock->collapsed)
248 XMapWindow(dpy, icon->core->window);
250 if (wPreferences.auto_arrange_icons && !wapp->app_icon->attracted)
251 wArrangeIcons(scr, True);
254 void removeAppIconFor(WApplication *wapp)
256 if (!wapp->app_icon)
257 return;
259 if (wPreferences.highlight_active_app)
260 wIconSetHighlited(wapp->app_icon->icon, False);
261 if (wapp->app_icon->docked && !wapp->app_icon->attracted) {
262 wapp->app_icon->running = 0;
263 /* since we keep it, we don't care if it was attracted or not */
264 wapp->app_icon->attracted = 0;
265 wapp->app_icon->icon->shadowed = 0;
266 wapp->app_icon->main_window = None;
267 wapp->app_icon->pid = 0;
268 wapp->app_icon->icon->owner = NULL;
269 wapp->app_icon->icon->icon_win = None;
271 /* Set the icon image */
272 set_icon_image_from_database(wapp->app_icon->icon, wapp->app_icon->wm_instance,
273 wapp->app_icon->wm_class, wapp->app_icon->command);
275 /* Update the icon, because wapp->app_icon->icon could be NULL */
276 wIconUpdate(wapp->app_icon->icon);
278 /* Paint it */
279 wAppIconPaint(wapp->app_icon);
280 } else if (wapp->app_icon->docked) {
281 wapp->app_icon->running = 0;
282 if (wapp->app_icon->dock->type == WM_DRAWER) {
283 wDrawerFillTheGap(wapp->app_icon->dock, wapp->app_icon, True);
285 wDockDetach(wapp->app_icon->dock, wapp->app_icon);
286 } else {
287 wAppIconDestroy(wapp->app_icon);
290 wapp->app_icon = NULL;
292 if (wPreferences.auto_arrange_icons)
293 wArrangeIcons(wapp->main_window_desc->screen_ptr, True);
296 static WAppIcon *wAppIconCreate(WWindow *leader_win)
298 WAppIcon *aicon;
300 aicon = wmalloc(sizeof(WAppIcon));
301 wretain(aicon);
302 aicon->yindex = -1;
303 aicon->xindex = -1;
304 aicon->prev = NULL;
305 aicon->next = NULL;
307 if (leader_win->wm_class)
308 aicon->wm_class = wstrdup(leader_win->wm_class);
310 if (leader_win->wm_instance)
311 aicon->wm_instance = wstrdup(leader_win->wm_instance);
313 aicon->icon = icon_create_for_wwindow(leader_win);
314 #ifdef XDND
315 wXDNDMakeAwareness(aicon->icon->core->window);
316 #endif
318 /* will be overriden if docked */
319 aicon->icon->core->descriptor.handle_mousedown = appIconMouseDown;
320 aicon->icon->core->descriptor.handle_expose = iconExpose;
321 aicon->icon->core->descriptor.parent_type = WCLASS_APPICON;
322 aicon->icon->core->descriptor.parent = aicon;
323 AddToStackList(aicon->icon->core);
324 aicon->icon->show_title = 0;
326 return aicon;
329 void wAppIconDestroy(WAppIcon *aicon)
331 RemoveFromStackList(aicon->icon->core);
332 wIconDestroy(aicon->icon);
333 if (aicon->command)
334 wfree(aicon->command);
335 #ifdef XDND
336 if (aicon->dnd_command)
337 wfree(aicon->dnd_command);
338 #endif
339 if (aicon->wm_instance)
340 wfree(aicon->wm_instance);
342 if (aicon->wm_class)
343 wfree(aicon->wm_class);
345 remove_from_appicon_list(aicon);
347 aicon->destroyed = 1;
348 wrelease(aicon);
351 static void drawCorner(WIcon * icon)
353 WScreen *scr = icon->core->screen_ptr;
354 XPoint points[3];
356 points[0].x = 1;
357 points[0].y = 1;
358 points[1].x = 12;
359 points[1].y = 1;
360 points[2].x = 1;
361 points[2].y = 12;
362 XFillPolygon(dpy, icon->core->window, scr->icon_title_texture->normal_gc,
363 points, 3, Convex, CoordModeOrigin);
364 XDrawLine(dpy, icon->core->window, scr->icon_title_texture->light_gc, 0, 0, 0, 12);
365 XDrawLine(dpy, icon->core->window, scr->icon_title_texture->light_gc, 0, 0, 12, 0);
368 void wAppIconMove(WAppIcon * aicon, int x, int y)
370 XMoveWindow(dpy, aicon->icon->core->window, x, y);
371 aicon->x_pos = x;
372 aicon->y_pos = y;
375 #ifdef WS_INDICATOR
376 static void updateDockNumbers(WScreen *scr)
378 int length;
379 char *ws_numbers;
380 WAppIcon *dicon = scr->dock->icon_array[0];
382 ws_numbers = wmalloc(20);
383 snprintf(ws_numbers, 20, "%i [ %i ]", w_global.workspace.current + 1, ((w_global.workspace.current / 10) + 1));
384 length = strlen(ws_numbers);
386 XClearArea(dpy, dicon->icon->core->window, 2, 2, 50, WMFontHeight(scr->icon_title_font) + 1, False);
388 WMDrawString(scr->wmscreen, dicon->icon->core->window, scr->black,
389 scr->icon_title_font, 4, 3, ws_numbers, length);
391 WMDrawString(scr->wmscreen, dicon->icon->core->window, scr->white,
392 scr->icon_title_font, 3, 2, ws_numbers, length);
394 wfree(ws_numbers);
396 #endif /* WS_INDICATOR */
398 void wAppIconPaint(WAppIcon *aicon)
400 WApplication *wapp;
401 WScreen *scr = aicon->icon->core->screen_ptr;
403 if (aicon->icon->owner)
404 wapp = wApplicationOf(aicon->icon->owner->main_window);
405 else
406 wapp = NULL;
408 wIconPaint(aicon->icon);
410 # ifdef WS_INDICATOR
411 if (aicon->docked && scr->dock && scr->dock == aicon->dock && aicon->yindex == 0)
412 updateDockNumbers(scr);
413 # endif
414 if (scr->dock_dots && aicon->docked && !aicon->running && aicon->command != NULL) {
415 XSetClipMask(dpy, scr->copy_gc, scr->dock_dots->mask);
416 XSetClipOrigin(dpy, scr->copy_gc, 0, 0);
417 XCopyArea(dpy, scr->dock_dots->image, aicon->icon->core->window,
418 scr->copy_gc, 0, 0, scr->dock_dots->width, scr->dock_dots->height, 0, 0);
420 #ifdef HIDDENDOT
421 if (wapp && wapp->flags.hidden) {
422 XSetClipMask(dpy, scr->copy_gc, scr->dock_dots->mask);
423 XSetClipOrigin(dpy, scr->copy_gc, 0, 0);
424 XCopyArea(dpy, scr->dock_dots->image,
425 aicon->icon->core->window, scr->copy_gc, 0, 0, 7, scr->dock_dots->height, 0, 0);
427 #endif /* HIDDENDOT */
429 if (aicon->omnipresent)
430 drawCorner(aicon->icon);
432 XSetClipMask(dpy, scr->copy_gc, None);
433 if (aicon->launching)
434 XFillRectangle(dpy, aicon->icon->core->window, scr->stipple_gc,
435 0, 0, wPreferences.icon_size, wPreferences.icon_size);
438 /* Save the application icon, if it's a dockapp then use it with dock = True */
439 void save_appicon(WAppIcon *aicon, Bool dock)
441 char *path;
443 if (!aicon)
444 return;
446 if (dock && (!aicon->docked || aicon->attracted))
447 return;
449 path = wIconStore(aicon->icon);
450 if (!path)
451 return;
453 wApplicationSaveIconPathFor(path, aicon->wm_instance, aicon->wm_class);
454 wfree(path);
457 #define canBeDocked(wwin) ((wwin) && ((wwin)->wm_class||(wwin)->wm_instance))
459 /* main_window may not have the full command line; try to find one which does */
460 static void relaunchApplication(WApplication *wapp)
462 WScreen *scr;
463 WWindow *wlist, *next;
465 scr = wapp->main_window_desc->screen_ptr;
466 wlist = scr->focused_window;
467 if (! wlist)
468 return;
470 while (wlist->prev)
471 wlist = wlist->prev;
473 while (wlist) {
474 next = wlist->next;
476 if (wlist->main_window == wapp->main_window) {
477 if (RelaunchWindow(wlist))
478 return;
481 wlist = next;
485 static void relaunchCallback(WMenu * menu, WMenuEntry * entry)
487 WApplication *wapp = (WApplication *) entry->clientdata;
489 relaunchApplication(wapp);
492 static void hideCallback(WMenu * menu, WMenuEntry * entry)
494 WApplication *wapp = (WApplication *) entry->clientdata;
496 if (wapp->flags.hidden) {
497 wWorkspaceChange(menu->menu->screen_ptr, wapp->last_workspace);
498 wUnhideApplication(wapp, False, False);
499 } else {
500 wHideApplication(wapp);
504 static void unhideHereCallback(WMenu * menu, WMenuEntry * entry)
506 WApplication *wapp = (WApplication *) entry->clientdata;
508 wUnhideApplication(wapp, False, True);
511 static void setIconCallback(WMenu *menu, WMenuEntry *entry)
513 WAppIcon *icon = ((WApplication *) entry->clientdata)->app_icon;
514 char *file = NULL;
515 WScreen *scr;
516 int result;
518 assert(icon != NULL);
520 if (icon->editing)
521 return;
523 icon->editing = 1;
524 scr = icon->icon->core->screen_ptr;
526 wretain(icon);
528 result = wIconChooserDialog(scr, &file, icon->wm_instance, icon->wm_class);
530 if (result && !icon->destroyed) {
531 if (file && *file == 0) {
532 wfree(file);
533 file = NULL;
535 if (!wIconChangeImageFile(icon->icon, file)) {
536 wMessageDialog(scr, _("Error"),
537 _("Could not open specified icon file"), _("OK"), NULL, NULL);
538 } else {
539 wDefaultChangeIcon(scr, icon->wm_instance, icon->wm_class, file);
540 wAppIconPaint(icon);
542 if (file)
543 wfree(file);
545 icon->editing = 0;
546 wrelease(icon);
549 static void killCallback(WMenu * menu, WMenuEntry * entry)
551 WApplication *wapp = (WApplication *) entry->clientdata;
552 WFakeGroupLeader *fPtr;
553 char *buffer;
554 char *shortname;
556 if (!WCHECK_STATE(WSTATE_NORMAL))
557 return;
559 WCHANGE_STATE(WSTATE_MODAL);
561 assert(entry->clientdata != NULL);
563 shortname = basename(wapp->app_icon->wm_instance);
565 buffer = wstrconcat(wapp->app_icon ? shortname : NULL,
566 _(" will be forcibly closed.\n"
567 "Any unsaved changes will be lost.\n" "Please confirm."));
569 fPtr = wapp->main_window_desc->fake_group;
571 wretain(wapp->main_window_desc);
572 if (wPreferences.dont_confirm_kill
573 || wMessageDialog(menu->frame->screen_ptr, _("Kill Application"),
574 buffer, _("Yes"), _("No"), NULL) == WAPRDefault) {
575 if (fPtr != NULL) {
576 WWindow *wwin, *twin;
578 wwin = wapp->main_window_desc->screen_ptr->focused_window;
579 while (wwin) {
580 twin = wwin->prev;
581 if (wwin->fake_group == fPtr)
582 wClientKill(wwin);
583 wwin = twin;
585 } else if (!wapp->main_window_desc->flags.destroyed) {
586 wClientKill(wapp->main_window_desc);
589 wrelease(wapp->main_window_desc);
590 wfree(buffer);
591 WCHANGE_STATE(WSTATE_NORMAL);
594 static WMenu *createApplicationMenu(WScreen *scr)
596 WMenu *menu;
598 menu = wMenuCreate(scr, NULL, False);
599 wMenuAddCallback(menu, _("Unhide Here"), unhideHereCallback, NULL);
600 wMenuAddCallback(menu, _("Hide"), hideCallback, NULL);
601 wMenuAddCallback(menu, _("Launch"), relaunchCallback, NULL);
602 wMenuAddCallback(menu, _("Set Icon..."), setIconCallback, NULL);
603 wMenuAddCallback(menu, _("Kill"), killCallback, NULL);
605 return menu;
608 static void openApplicationMenu(WApplication * wapp, int x, int y)
610 WMenu *menu;
611 WScreen *scr = wapp->main_window_desc->screen_ptr;
612 int i;
614 if (!scr->icon_menu) {
615 scr->icon_menu = createApplicationMenu(scr);
616 wfree(scr->icon_menu->entries[1]->text);
619 menu = scr->icon_menu;
621 if (wapp->flags.hidden)
622 menu->entries[1]->text = _("Unhide");
623 else
624 menu->entries[1]->text = _("Hide");
626 menu->flags.realized = 0;
627 wMenuRealize(menu);
629 x -= menu->frame->core->width / 2;
630 if (x + menu->frame->core->width > scr->scr_width)
631 x = scr->scr_width - menu->frame->core->width;
633 if (x < 0)
634 x = 0;
636 /* set client data */
637 for (i = 0; i < menu->entry_no; i++)
638 menu->entries[i]->clientdata = wapp;
640 wMenuMapAt(menu, x, y, False);
643 /******************************************************************/
645 static void iconExpose(WObjDescriptor *desc, XEvent *event)
647 wAppIconPaint(desc->parent);
650 static void iconDblClick(WObjDescriptor *desc, XEvent *event)
652 WAppIcon *aicon = desc->parent;
653 WApplication *wapp;
654 WScreen *scr = aicon->icon->core->screen_ptr;
655 int unhideHere;
657 assert(aicon->icon->owner != NULL);
659 wapp = wApplicationOf(aicon->icon->owner->main_window);
661 if (event->xbutton.state & ControlMask) {
662 relaunchApplication(wapp);
663 return;
666 unhideHere = (event->xbutton.state & ShiftMask);
667 /* go to the last workspace that the user worked on the app */
668 if (!unhideHere && wapp->last_workspace != w_global.workspace.current)
669 wWorkspaceChange(scr, wapp->last_workspace);
671 wUnhideApplication(wapp, event->xbutton.button == Button2, unhideHere);
673 if (event->xbutton.state & MOD_MASK)
674 wHideOtherApplications(aicon->icon->owner);
677 void appIconMouseDown(WObjDescriptor * desc, XEvent * event)
679 WAppIcon *aicon = desc->parent;
680 WScreen *scr = aicon->icon->core->screen_ptr;
681 Bool hasMoved;
683 if (aicon->editing || WCHECK_STATE(WSTATE_MODAL))
684 return;
686 if (IsDoubleClick(scr, event)) {
687 /* Middle or right mouse actions were handled on first click */
688 if (event->xbutton.button == Button1)
689 iconDblClick(desc, event);
690 return;
693 if (event->xbutton.button == Button2) {
694 WApplication *wapp = wApplicationOf(aicon->icon->owner->main_window);
696 if (wapp)
697 relaunchApplication(wapp);
699 return;
702 if (event->xbutton.button == Button3) {
703 WObjDescriptor *desc;
704 WApplication *wapp = wApplicationOf(aicon->icon->owner->main_window);
706 if (!wapp)
707 return;
709 if (event->xbutton.send_event &&
710 XGrabPointer(dpy, aicon->icon->core->window, True, ButtonMotionMask
711 | ButtonReleaseMask | ButtonPressMask, GrabModeAsync,
712 GrabModeAsync, None, None, CurrentTime) != GrabSuccess) {
713 wwarning("pointer grab failed for appicon menu");
714 return;
717 openApplicationMenu(wapp, event->xbutton.x_root, event->xbutton.y_root);
719 /* allow drag select of menu */
720 desc = &scr->icon_menu->menu->descriptor;
721 event->xbutton.send_event = True;
722 (*desc->handle_mousedown) (desc, event);
723 return;
726 hasMoved = wHandleAppIconMove(aicon, event);
727 if (wPreferences.single_click && !hasMoved && aicon->dock != NULL)
729 iconDblClick(desc, event);
733 Bool wHandleAppIconMove(WAppIcon *aicon, XEvent *event)
735 WIcon *icon = aicon->icon;
736 WScreen *scr = icon->core->screen_ptr;
737 WDock *originalDock = aicon->dock; /* can be NULL */
738 WDock *lastDock = originalDock;
739 WDock *allDocks[scr->drawer_count + 2]; /* clip, dock and drawers (order determined at runtime) */
740 WDrawerChain *dc;
741 Bool done = False, dockable, ondock;
742 Bool grabbed = False;
743 Bool collapsed = False; /* Stores the collapsed state of lastDock, before the moving appicon entered it */
744 int superfluous = wPreferences.superfluous; /* we cache it to avoid problems */
745 int omnipresent = aicon->omnipresent; /* this must be cached */
746 Bool showed_all_clips = False;
748 int clickButton = event->xbutton.button;
749 Pixmap ghost = None;
750 Window wins[2]; /* Managing shadow window */
751 XEvent ev;
753 int x = aicon->x_pos, y = aicon->y_pos;
754 int ofs_x = event->xbutton.x, ofs_y = event->xbutton.y;
755 int shad_x = x, shad_y = y;
756 int ix = aicon->xindex, iy = aicon->yindex;
757 int i;
758 int oldX = x;
759 int oldY = y;
760 Bool hasMoved = False;
762 if (wPreferences.flags.noupdates && originalDock != NULL)
763 return False;
765 if (!(event->xbutton.state & MOD_MASK))
766 wRaiseFrame(icon->core);
767 else {
768 /* If Mod is pressed for an docked appicon, assume it is to undock it,
769 * so don't lower it */
770 if (originalDock == NULL)
771 wLowerFrame(icon->core);
774 if (XGrabPointer(dpy, icon->core->window, True, ButtonMotionMask
775 | ButtonReleaseMask | ButtonPressMask, GrabModeAsync,
776 GrabModeAsync, None, None, CurrentTime) != GrabSuccess) {
777 wwarning("Pointer grab failed in wHandleAppIconMove");
780 if (originalDock != NULL) {
781 dockable = True;
782 ondock = True;
784 else {
785 ondock = False;
786 if (wPreferences.flags.nodock && wPreferences.flags.noclip && wPreferences.flags.nodrawer)
787 dockable = 0;
788 else
789 dockable = canBeDocked(icon->owner);
792 /* We try the various docks in that order:
793 * - First, the dock the appicon comes from, if any
794 * - Then, the drawers
795 * - Then, the "dock" (WM_DOCK)
796 * - Finally, the clip
798 i = 0;
799 if (originalDock != NULL)
800 allDocks[ i++ ] = originalDock;
801 /* Testing scr->drawers is enough, no need to test wPreferences.flags.nodrawer */
802 for (dc = scr->drawers; dc != NULL; dc = dc->next) {
803 if (dc->adrawer != originalDock)
804 allDocks[ i++ ] = dc->adrawer;
806 if (!wPreferences.flags.nodock && scr->dock != originalDock)
807 allDocks[ i++ ] = scr->dock;
809 if (!wPreferences.flags.noclip &&
810 originalDock != w_global.workspace.array[w_global.workspace.current]->clip)
811 allDocks[i++] = w_global.workspace.array[w_global.workspace.current]->clip;
813 for ( ; i < scr->drawer_count + 2; i++) /* In case the clip, the dock, or both, are disabled */
814 allDocks[ i ] = NULL;
816 wins[0] = icon->core->window;
817 wins[1] = scr->dock_shadow;
818 XRestackWindows(dpy, wins, 2);
819 XMoveResizeWindow(dpy, scr->dock_shadow, aicon->x_pos, aicon->y_pos, ICON_SIZE, ICON_SIZE);
820 if (superfluous) {
821 if (icon->pixmap != None)
822 ghost = MakeGhostIcon(scr, icon->pixmap);
823 else
824 ghost = MakeGhostIcon(scr, icon->core->window);
825 XSetWindowBackgroundPixmap(dpy, scr->dock_shadow, ghost);
826 XClearWindow(dpy, scr->dock_shadow);
828 if (ondock)
829 XMapWindow(dpy, scr->dock_shadow);
831 while (!done) {
832 WMMaskEvent(dpy, PointerMotionMask | ButtonReleaseMask | ButtonPressMask
833 | ButtonMotionMask | ExposureMask | EnterWindowMask, &ev);
834 switch (ev.type) {
835 case Expose:
836 WMHandleEvent(&ev);
837 break;
839 case EnterNotify:
840 /* It means the cursor moved so fast that it entered
841 * something else (if moving slowly, it would have
842 * stayed in the appIcon that is being moved. Ignore
843 * such "spurious" EnterNotifiy's */
844 break;
846 case MotionNotify:
847 hasMoved = True;
848 if (!grabbed) {
849 if (abs(ofs_x - ev.xmotion.x) >= MOVE_THRESHOLD
850 || abs(ofs_y - ev.xmotion.y) >= MOVE_THRESHOLD) {
851 XChangeActivePointerGrab(dpy, ButtonMotionMask
852 | ButtonReleaseMask | ButtonPressMask,
853 wPreferences.cursor[WCUR_MOVE], CurrentTime);
854 grabbed = 1;
855 } else {
856 break;
860 if (omnipresent && !showed_all_clips) {
861 int i;
862 for (i = 0; i < w_global.workspace.count; i++) {
863 if (i == w_global.workspace.current)
864 continue;
866 wDockShowIcons(w_global.workspace.array[i]->clip);
867 /* Note: if dock is collapsed (for instance, because it
868 auto-collapses), its icons still won't show up */
870 showed_all_clips = True; /* To prevent flickering */
873 x = ev.xmotion.x_root - ofs_x;
874 y = ev.xmotion.y_root - ofs_y;
875 wAppIconMove(aicon, x, y);
877 WDock *theNewDock = NULL;
878 if (!(ev.xmotion.state & MOD_MASK) || aicon->launching || aicon->lock || originalDock == NULL) {
879 for (i = 0; dockable && i < scr->drawer_count + 2; i++) {
880 WDock *theDock = allDocks[i];
881 if (theDock == NULL)
882 break;
883 if (wDockSnapIcon(theDock, aicon, x, y, &ix, &iy, (theDock == originalDock))) {
884 theNewDock = theDock;
885 break;
888 if (originalDock != NULL && theNewDock == NULL &&
889 (aicon->launching || aicon->lock || aicon->running)) {
890 /* In those cases, stay in lastDock if no dock really wants us */
891 theNewDock = lastDock;
894 if (lastDock != NULL && lastDock != theNewDock) {
895 /* Leave lastDock in the state we found it */
896 if (lastDock->type == WM_DRAWER) {
897 wDrawerFillTheGap(lastDock, aicon, (lastDock == originalDock));
899 if (collapsed) {
900 lastDock->collapsed = 1;
901 wDockHideIcons(lastDock);
902 collapsed = False;
904 if (lastDock->auto_raise_lower) {
905 wDockLower(lastDock);
908 if (theNewDock != NULL) {
909 if (lastDock != theNewDock) {
910 collapsed = theNewDock->collapsed;
911 if (collapsed) {
912 theNewDock->collapsed = 0;
913 wDockShowIcons(theNewDock);
915 if (theNewDock->auto_raise_lower) {
916 wDockRaise(theNewDock);
917 /* And raise the moving tile above it */
918 wRaiseFrame(aicon->icon->core);
920 lastDock = theNewDock;
923 shad_x = lastDock->x_pos + ix*wPreferences.icon_size;
924 shad_y = lastDock->y_pos + iy*wPreferences.icon_size;
926 XMoveWindow(dpy, scr->dock_shadow, shad_x, shad_y);
928 if (!ondock) {
929 XMapWindow(dpy, scr->dock_shadow);
931 ondock = 1;
932 } else {
933 lastDock = theNewDock; // i.e., NULL
934 if (ondock) {
935 XUnmapWindow(dpy, scr->dock_shadow);
937 * Leaving that weird comment for now.
938 * But if we see no gap, there is no need to fill one!
939 * We could test ondock first and the lastDock to NULL afterwards
940 if (lastDock_before_it_was_null->type == WM_DRAWER) {
941 wDrawerFillTheGap(lastDock, aicon, (lastDock == originalDock));
942 } */
945 ondock = 0;
947 break;
949 case ButtonPress:
950 break;
952 case ButtonRelease:
953 if (ev.xbutton.button != clickButton)
954 break;
955 XUngrabPointer(dpy, CurrentTime);
957 Bool docked = False;
958 if (ondock) {
959 SlideWindow(icon->core->window, x, y, shad_x, shad_y);
960 XUnmapWindow(dpy, scr->dock_shadow);
961 if (originalDock == NULL) { // docking an undocked appicon
962 docked = wDockAttachIcon(lastDock, aicon, ix, iy, False);
963 if (!docked) {
964 /* AppIcon got rejected (happens only when we can't get the
965 command for that appicon, and the user cancels the
966 wInputDialog asking for one). Make the rejection obvious by
967 sliding the icon to its old position */
968 if (lastDock->type == WM_DRAWER) {
969 // Also fill the gap left in the drawer
970 wDrawerFillTheGap(lastDock, aicon, False);
972 SlideWindow(icon->core->window, x, y, oldX, oldY);
975 else { // moving a docked appicon to a dock
976 if (originalDock == lastDock) {
977 docked = True;
978 wDockReattachIcon(originalDock, aicon, ix, iy);
980 else {
981 docked = wDockMoveIconBetweenDocks(originalDock, lastDock, aicon, ix, iy);
982 if (!docked) {
983 /* Possible scenario: user moved an auto-attracted appicon
984 from the clip to the dock, and cancelled the wInputDialog
985 asking for a command */
986 if (lastDock->type == WM_DRAWER) {
987 wDrawerFillTheGap(lastDock, aicon, False);
989 /* If aicon comes from a drawer, make some room to reattach it */
990 if (originalDock->type == WM_DRAWER) {
991 WAppIcon *aiconsToShift[ originalDock->icon_count ];
992 int j = 0;
994 for (i = 0; i < originalDock->max_icons; i++) {
995 WAppIcon *ai = originalDock->icon_array[ i ];
996 if (ai && ai != aicon &&
997 abs(ai->xindex) >= abs(aicon->xindex))
998 aiconsToShift[j++] = ai;
1000 if (j != originalDock->icon_count - abs(aicon->xindex) - 1)
1001 // Trust this never happens?
1002 wwarning("Shifting j=%d appicons (instead of %d!) to reinsert aicon at index %d.",
1003 j, originalDock->icon_count - abs(aicon->xindex) - 1, aicon->xindex);
1004 wSlideAppicons(aiconsToShift, j, originalDock->on_right_side);
1005 // Trust the appicon is inserted at exactly the same place, so its oldX/oldY are consistent with its "new" location?
1008 SlideWindow(icon->core->window, x, y, oldX, oldY);
1009 wDockReattachIcon(originalDock, aicon, aicon->xindex, aicon->yindex);
1011 else {
1012 if (originalDock->auto_collapse && !originalDock->collapsed) {
1013 originalDock->collapsed = 1;
1014 wDockHideIcons(originalDock);
1016 if (originalDock->auto_raise_lower)
1017 wDockLower(originalDock);
1021 // No matter what happened above, check to lower lastDock
1022 // Don't see why I commented out the following 2 lines
1023 /* if (lastDock->auto_raise_lower)
1024 wDockLower(lastDock); */
1025 /* If docked (or tried to dock) to a auto_collapsing dock, unset
1026 * collapsed, so that wHandleAppIconMove doesn't collapse it
1027 * right away (the timer will take care of it) */
1028 if (lastDock->auto_collapse)
1029 collapsed = 0;
1031 else {
1032 if (originalDock != NULL) { /* Detaching a docked appicon */
1033 if (superfluous) {
1034 if (!aicon->running && !wPreferences.no_animations) {
1035 /* We need to deselect it, even if is deselected in
1036 * wDockDetach(), because else DoKaboom() will fail.
1038 if (aicon->icon->selected)
1039 wIconSelect(aicon->icon);
1040 DoKaboom(scr, aicon->icon->core->window, x, y);
1043 wDockDetach(originalDock, aicon);
1044 if (originalDock->auto_collapse && !originalDock->collapsed) {
1045 originalDock->collapsed = 1;
1046 wDockHideIcons(originalDock);
1048 if (originalDock->auto_raise_lower)
1049 wDockLower(originalDock);
1052 // Can't remember why the icon hiding is better done above than below (commented out)
1053 // Also, lastDock is quite different from originalDock
1055 if (collapsed) {
1056 lastDock->collapsed = 1;
1057 wDockHideIcons(lastDock);
1058 collapsed = 0;
1061 if (superfluous) {
1062 if (ghost != None)
1063 XFreePixmap(dpy, ghost);
1064 XSetWindowBackground(dpy, scr->dock_shadow, scr->white_pixel);
1066 if (showed_all_clips) {
1067 int i;
1068 for (i = 0; i < w_global.workspace.count; i++) {
1069 if (i == w_global.workspace.current)
1070 continue;
1072 wDockHideIcons(w_global.workspace.array[i]->clip);
1075 if (wPreferences.auto_arrange_icons && !(originalDock != NULL && docked))
1076 /* Need to rearrange unless moving from dock to dock */
1077 wArrangeIcons(scr, True);
1078 return hasMoved;
1081 return False; /* Never reached */
1084 /* This function save the application icon and store the path in the Dictionary */
1085 static void wApplicationSaveIconPathFor(const char *iconPath, const char *wm_instance, const char *wm_class)
1087 WMPropList *dict = WDWindowAttributes->dictionary;
1088 WMPropList *adict, *key, *iconk;
1089 WMPropList *val;
1090 char *tmp;
1092 tmp = get_name_for_instance_class(wm_instance, wm_class);
1093 key = WMCreatePLString(tmp);
1094 wfree(tmp);
1096 adict = WMGetFromPLDictionary(dict, key);
1097 iconk = WMCreatePLString("Icon");
1099 if (adict) {
1100 val = WMGetFromPLDictionary(adict, iconk);
1101 } else {
1102 /* no dictionary for app, so create one */
1103 adict = WMCreatePLDictionary(NULL, NULL);
1104 WMPutInPLDictionary(dict, key, adict);
1105 WMReleasePropList(adict);
1106 val = NULL;
1109 if (!val) {
1110 val = WMCreatePLString(iconPath);
1111 WMPutInPLDictionary(adict, iconk, val);
1112 WMReleasePropList(val);
1113 } else {
1114 val = NULL;
1117 WMReleasePropList(key);
1118 WMReleasePropList(iconk);
1120 if (val && !wPreferences.flags.noupdates)
1121 UpdateDomainFile(WDWindowAttributes);
1124 static WAppIcon *findDockIconFor(WDock *dock, Window main_window)
1126 WAppIcon *aicon = NULL;
1128 aicon = wDockFindIconForWindow(dock, main_window);
1129 if (!aicon) {
1130 wDockTrackWindowLaunch(dock, main_window);
1131 aicon = wDockFindIconForWindow(dock, main_window);
1133 return aicon;
1136 static void create_appicon_from_dock(WWindow *wwin, WApplication *wapp, Window main_window)
1138 WScreen *scr = wwin->screen_ptr;
1139 wapp->app_icon = NULL;
1141 if (scr->last_dock)
1142 wapp->app_icon = findDockIconFor(scr->last_dock, main_window);
1144 /* check main dock if we did not find it in last dock */
1145 if (!wapp->app_icon && scr->dock)
1146 wapp->app_icon = findDockIconFor(scr->dock, main_window);
1148 /* check clips */
1149 if (!wapp->app_icon) {
1150 int i;
1151 for (i = 0; i < w_global.workspace.count; i++) {
1152 WDock *dock = w_global.workspace.array[i]->clip;
1154 if (dock)
1155 wapp->app_icon = findDockIconFor(dock, main_window);
1157 if (wapp->app_icon)
1158 break;
1162 /* Finally check drawers */
1163 if (!wapp->app_icon) {
1164 WDrawerChain *dc;
1165 for (dc = scr->drawers; dc != NULL; dc = dc->next) {
1166 wapp->app_icon = findDockIconFor(dc->adrawer, main_window);
1167 if (wapp->app_icon)
1168 break;
1172 /* If created, then set some flags */
1173 if (wapp->app_icon) {
1174 WWindow *mainw = wapp->main_window_desc;
1176 wapp->app_icon->running = 1;
1177 wapp->app_icon->icon->owner = mainw;
1178 if (mainw->wm_hints && (mainw->wm_hints->flags & IconWindowHint))
1179 wapp->app_icon->icon->icon_win = mainw->wm_hints->icon_window;
1181 /* Update the icon images */
1182 wIconUpdate(wapp->app_icon->icon);
1184 /* Paint it */
1185 wAppIconPaint(wapp->app_icon);
1186 save_appicon(wapp->app_icon, True);
1190 /* Add the appicon to the appiconlist */
1191 static void add_to_appicon_list(WAppIcon *appicon)
1193 appicon->prev = NULL;
1194 appicon->next = w_global.app_icon_list;
1195 if (w_global.app_icon_list)
1196 w_global.app_icon_list->prev = appicon;
1198 w_global.app_icon_list = appicon;
1201 /* Remove the appicon from the appiconlist */
1202 static void remove_from_appicon_list(WAppIcon *appicon)
1204 if (appicon == w_global.app_icon_list) {
1205 if (appicon->next)
1206 appicon->next->prev = NULL;
1207 w_global.app_icon_list = appicon->next;
1208 } else {
1209 if (appicon->next)
1210 appicon->next->prev = appicon->prev;
1211 if (appicon->prev)
1212 appicon->prev->next = appicon->next;
1215 appicon->prev = NULL;
1216 appicon->next = NULL;
1219 /* Return the AppIcon associated with a given (Xlib) Window. */
1220 WAppIcon *wAppIconFor(Window window)
1222 WObjDescriptor *desc;
1224 if (window == None)
1225 return NULL;
1227 if (XFindContext(dpy, window, w_global.context.client_win, (XPointer *) & desc) == XCNOENT)
1228 return NULL;
1230 if (desc->parent_type == WCLASS_APPICON || desc->parent_type == WCLASS_DOCK_ICON)
1231 return desc->parent;
1233 return NULL;