wmaker: removed unused constant SCROLL_STEPS in the switchpanel code
[wmaker-crm.git] / src / appicon.c
blobed03ffc1d0ea356d41bef7f834800e060c746ded
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 #define MOD_MASK wPreferences.modifier_mask
64 #define ICON_SIZE wPreferences.icon_size
66 static void iconDblClick(WObjDescriptor * desc, XEvent * event);
67 static void iconExpose(WObjDescriptor * desc, XEvent * event);
68 static void wApplicationSaveIconPathFor(const char *iconPath, const char *wm_instance, const char *wm_class);
69 static WAppIcon *wAppIconCreate(WWindow * leader_win);
70 static void add_to_appicon_list(WScreen *scr, WAppIcon *appicon);
71 static void remove_from_appicon_list(WScreen *scr, WAppIcon *appicon);
72 static void create_appicon_from_dock(WWindow *wwin, WApplication *wapp, Window main_window);
74 /* This function is used if the application is a .app. It checks if it has an icon in it
75 * like for example /usr/local/GNUstep/Applications/WPrefs.app/WPrefs.tiff
77 void wApplicationExtractDirPackIcon(const char *path, const char *wm_instance, const char *wm_class)
79 char *iconPath = NULL;
80 char *tmp = NULL;
82 if (strstr(path, ".app")) {
83 tmp = wmalloc(strlen(path) + 16);
85 if (wPreferences.supports_tiff) {
86 strcpy(tmp, path);
87 strcat(tmp, ".tiff");
88 if (access(tmp, R_OK) == 0)
89 iconPath = tmp;
92 if (!iconPath) {
93 strcpy(tmp, path);
94 strcat(tmp, ".xpm");
95 if (access(tmp, R_OK) == 0)
96 iconPath = tmp;
99 if (!iconPath)
100 wfree(tmp);
102 if (iconPath) {
103 wApplicationSaveIconPathFor(iconPath, wm_instance, wm_class);
104 wfree(iconPath);
109 WAppIcon *wAppIconCreateForDock(WScreen *scr, const char *command, const char *wm_instance, const char *wm_class, int tile)
111 WAppIcon *aicon;
113 aicon = wmalloc(sizeof(WAppIcon));
114 wretain(aicon);
115 aicon->yindex = -1;
116 aicon->xindex = -1;
118 add_to_appicon_list(scr, aicon);
120 if (command)
121 aicon->command = wstrdup(command);
123 if (wm_class)
124 aicon->wm_class = wstrdup(wm_class);
126 if (wm_instance)
127 aicon->wm_instance = wstrdup(wm_instance);
129 if (wPreferences.flags.clip_merged_in_dock && wm_class != NULL && strcmp(wm_class, "WMDock") == 0)
130 tile = TILE_CLIP;
131 aicon->icon = icon_create_for_dock(scr, command, wm_instance, wm_class, tile);
133 #ifdef XDND
134 wXDNDMakeAwareness(aicon->icon->core->window);
135 #endif
137 /* will be overriden by dock */
138 aicon->icon->core->descriptor.handle_mousedown = appIconMouseDown;
139 aicon->icon->core->descriptor.handle_expose = iconExpose;
140 aicon->icon->core->descriptor.parent_type = WCLASS_APPICON;
141 aicon->icon->core->descriptor.parent = aicon;
142 AddToStackList(aicon->icon->core);
144 return aicon;
147 void create_appicon_for_application(WApplication *wapp, WWindow *wwin)
149 /* Try to create an icon from the dock or clip */
150 create_appicon_from_dock(wwin, wapp, wapp->main_window);
152 /* If app_icon was not found, create it */
153 if (!wapp->app_icon) {
154 /* Create the icon */
155 wapp->app_icon = wAppIconCreate(wapp->main_window_desc);
156 wIconUpdate(wapp->app_icon->icon);
158 /* Now, paint the icon */
159 if (!WFLAGP(wapp->main_window_desc, no_appicon))
160 paint_app_icon(wapp);
163 /* Save the app_icon in a file */
164 save_appicon(wapp->app_icon, False);
167 void unpaint_app_icon(WApplication *wapp)
169 WAppIcon *aicon;
170 WScreen *scr;
171 WDock *clip;
173 if (!wapp || !wapp->app_icon)
174 return;
176 aicon = wapp->app_icon;
178 /* If the icon is docked, don't continue */
179 if (aicon->docked)
180 return;
182 scr = wapp->main_window_desc->screen_ptr;
183 clip = scr->workspaces[scr->current_workspace]->clip;
185 if (!clip || !aicon->attracted || !clip->collapsed)
186 XUnmapWindow(dpy, aicon->icon->core->window);
188 /* We want to avoid having it on the list because otherwise
189 * there will be a hole when the icons are arranged with
190 * wArrangeIcons() */
191 remove_from_appicon_list(scr, aicon);
193 if (wPreferences.auto_arrange_icons && !aicon->attracted)
194 wArrangeIcons(scr, True);
197 void paint_app_icon(WApplication *wapp)
199 WIcon *icon;
200 WScreen *scr;
201 WDock *attracting_dock;
202 int x = 0, y = 0;
203 Bool update_icon = False;
205 if (!wapp || !wapp->app_icon || !wapp->main_window_desc)
206 return;
208 icon = wapp->app_icon->icon;
209 scr = wapp->main_window_desc->screen_ptr;
210 wapp->app_icon->main_window = wapp->main_window;
212 /* If the icon is docked, don't continue */
213 if (wapp->app_icon->docked)
214 return;
216 attracting_dock = scr->attracting_drawer != NULL ?
217 scr->attracting_drawer :
218 scr->workspaces[scr->current_workspace]->clip;
219 if (attracting_dock && attracting_dock->attract_icons &&
220 wDockFindFreeSlot(attracting_dock, &x, &y)) {
221 wapp->app_icon->attracted = 1;
222 if (!icon->shadowed) {
223 icon->shadowed = 1;
224 update_icon = True;
226 wDockAttachIcon(attracting_dock, wapp->app_icon, x, y, update_icon);
227 } else {
228 /* We must know if the icon is painted in the screen,
229 * because if painted, then PlaceIcon will return the next
230 * space on the screen, and the icon will move */
231 if (wapp->app_icon->next == NULL && wapp->app_icon->prev == NULL) {
232 PlaceIcon(scr, &x, &y, wGetHeadForWindow(wapp->main_window_desc));
233 wAppIconMove(wapp->app_icon, x, y);
234 wLowerFrame(icon->core);
238 /* If we want appicon (no_appicon is not set) and the icon is not
239 * in the appicon_list, we must add it. Else, we want to avoid
240 * having it on the list */
241 if (!WFLAGP(wapp->main_window_desc, no_appicon) &&
242 wapp->app_icon->next == NULL && wapp->app_icon->prev == NULL)
243 add_to_appicon_list(scr, wapp->app_icon);
245 if (!attracting_dock || !wapp->app_icon->attracted || !attracting_dock->collapsed)
246 XMapWindow(dpy, icon->core->window);
248 if (wPreferences.auto_arrange_icons && !wapp->app_icon->attracted)
249 wArrangeIcons(scr, True);
252 void removeAppIconFor(WApplication *wapp)
254 if (!wapp->app_icon)
255 return;
257 if (wPreferences.highlight_active_app)
258 wIconSetHighlited(wapp->app_icon->icon, False);
259 if (wapp->app_icon->docked && !wapp->app_icon->attracted) {
260 wapp->app_icon->running = 0;
261 /* since we keep it, we don't care if it was attracted or not */
262 wapp->app_icon->attracted = 0;
263 wapp->app_icon->icon->shadowed = 0;
264 wapp->app_icon->main_window = None;
265 wapp->app_icon->pid = 0;
266 wapp->app_icon->icon->owner = NULL;
267 wapp->app_icon->icon->icon_win = None;
269 /* Set the icon image */
270 set_icon_image_from_database(wapp->app_icon->icon, wapp->app_icon->wm_instance,
271 wapp->app_icon->wm_class, wapp->app_icon->command);
273 /* Update the icon, because wapp->app_icon->icon could be NULL */
274 wIconUpdate(wapp->app_icon->icon);
276 /* Paint it */
277 wAppIconPaint(wapp->app_icon);
278 } else if (wapp->app_icon->docked) {
279 wapp->app_icon->running = 0;
280 if (wapp->app_icon->dock->type == WM_DRAWER) {
281 wDrawerFillTheGap(wapp->app_icon->dock, wapp->app_icon, True);
283 wDockDetach(wapp->app_icon->dock, wapp->app_icon);
284 } else {
285 wAppIconDestroy(wapp->app_icon);
288 wapp->app_icon = NULL;
290 if (wPreferences.auto_arrange_icons)
291 wArrangeIcons(wapp->main_window_desc->screen_ptr, True);
294 static WAppIcon *wAppIconCreate(WWindow *leader_win)
296 WAppIcon *aicon;
298 aicon = wmalloc(sizeof(WAppIcon));
299 wretain(aicon);
300 aicon->yindex = -1;
301 aicon->xindex = -1;
302 aicon->prev = NULL;
303 aicon->next = NULL;
305 if (leader_win->wm_class)
306 aicon->wm_class = wstrdup(leader_win->wm_class);
308 if (leader_win->wm_instance)
309 aicon->wm_instance = wstrdup(leader_win->wm_instance);
311 aicon->icon = icon_create_for_wwindow(leader_win);
312 #ifdef XDND
313 wXDNDMakeAwareness(aicon->icon->core->window);
314 #endif
316 /* will be overriden if docked */
317 aicon->icon->core->descriptor.handle_mousedown = appIconMouseDown;
318 aicon->icon->core->descriptor.handle_expose = iconExpose;
319 aicon->icon->core->descriptor.parent_type = WCLASS_APPICON;
320 aicon->icon->core->descriptor.parent = aicon;
321 AddToStackList(aicon->icon->core);
322 aicon->icon->show_title = 0;
324 return aicon;
327 void wAppIconDestroy(WAppIcon * aicon)
329 WScreen *scr = aicon->icon->core->screen_ptr;
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(scr, 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 ]", scr->current_workspace + 1, ((scr->current_workspace / 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 (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 /* Parameter not used, but tell the compiler that it is ok */
490 (void) menu;
492 relaunchApplication(wapp);
495 static void hideCallback(WMenu * menu, WMenuEntry * entry)
497 WApplication *wapp = (WApplication *) entry->clientdata;
499 if (wapp->flags.hidden) {
500 wWorkspaceChange(menu->menu->screen_ptr, wapp->last_workspace);
501 wUnhideApplication(wapp, False, False);
502 } else {
503 wHideApplication(wapp);
507 static void unhideHereCallback(WMenu * menu, WMenuEntry * entry)
509 WApplication *wapp = (WApplication *) entry->clientdata;
511 /* Parameter not used, but tell the compiler that it is ok */
512 (void) menu;
514 wUnhideApplication(wapp, False, True);
517 static void setIconCallback(WMenu *menu, WMenuEntry *entry)
519 WAppIcon *icon = ((WApplication *) entry->clientdata)->app_icon;
520 char *file = NULL;
521 WScreen *scr;
522 int result;
524 /* Parameter not used, but tell the compiler that it is ok */
525 (void) menu;
527 assert(icon != NULL);
529 if (icon->editing)
530 return;
532 icon->editing = 1;
533 scr = icon->icon->core->screen_ptr;
535 wretain(icon);
537 result = wIconChooserDialog(scr, &file, icon->wm_instance, icon->wm_class);
539 if (result) {
540 if (!icon->destroyed) {
541 if (!wIconChangeImageFile(icon->icon, file)) {
542 wMessageDialog(scr, _("Error"),
543 _("Could not open specified icon file"),
544 _("OK"), NULL, NULL);
545 } else {
546 wDefaultChangeIcon(icon->wm_instance, icon->wm_class, file);
547 wAppIconPaint(icon);
550 if (file)
551 wfree(file);
553 icon->editing = 0;
554 wrelease(icon);
557 static void killCallback(WMenu * menu, WMenuEntry * entry)
559 WApplication *wapp = (WApplication *) entry->clientdata;
560 WFakeGroupLeader *fPtr;
561 char *buffer;
562 char *shortname;
564 if (!WCHECK_STATE(WSTATE_NORMAL))
565 return;
567 WCHANGE_STATE(WSTATE_MODAL);
569 assert(entry->clientdata != NULL);
571 shortname = basename(wapp->app_icon->wm_instance);
573 buffer = wstrconcat(wapp->app_icon ? shortname : NULL,
574 _(" will be forcibly closed.\n"
575 "Any unsaved changes will be lost.\n" "Please confirm."));
577 fPtr = wapp->main_window_desc->fake_group;
579 wretain(wapp->main_window_desc);
580 if (wPreferences.dont_confirm_kill
581 || wMessageDialog(menu->frame->screen_ptr, _("Kill Application"),
582 buffer, _("Yes"), _("No"), NULL) == WAPRDefault) {
583 if (fPtr != NULL) {
584 WWindow *wwin, *twin;
586 wwin = wapp->main_window_desc->screen_ptr->focused_window;
587 while (wwin) {
588 twin = wwin->prev;
589 if (wwin->fake_group == fPtr)
590 wClientKill(wwin);
591 wwin = twin;
593 } else if (!wapp->main_window_desc->flags.destroyed) {
594 wClientKill(wapp->main_window_desc);
597 wrelease(wapp->main_window_desc);
598 wfree(buffer);
599 WCHANGE_STATE(WSTATE_NORMAL);
602 static WMenu *createApplicationMenu(WScreen *scr)
604 WMenu *menu;
606 menu = wMenuCreate(scr, NULL, False);
607 wMenuAddCallback(menu, _("Unhide Here"), unhideHereCallback, NULL);
608 wMenuAddCallback(menu, _("Hide"), hideCallback, NULL);
609 wMenuAddCallback(menu, _("Launch"), relaunchCallback, NULL);
610 wMenuAddCallback(menu, _("Set Icon..."), setIconCallback, NULL);
611 wMenuAddCallback(menu, _("Kill"), killCallback, NULL);
613 return menu;
616 static void openApplicationMenu(WApplication * wapp, int x, int y)
618 WMenu *menu;
619 WScreen *scr = wapp->main_window_desc->screen_ptr;
620 int i;
622 if (!scr->icon_menu) {
623 scr->icon_menu = createApplicationMenu(scr);
624 wfree(scr->icon_menu->entries[1]->text);
627 menu = scr->icon_menu;
629 if (wapp->flags.hidden)
630 menu->entries[1]->text = _("Unhide");
631 else
632 menu->entries[1]->text = _("Hide");
634 menu->flags.realized = 0;
635 wMenuRealize(menu);
637 x -= menu->frame->core->width / 2;
638 if (x + menu->frame->core->width > scr->scr_width)
639 x = scr->scr_width - menu->frame->core->width;
641 if (x < 0)
642 x = 0;
644 /* set client data */
645 for (i = 0; i < menu->entry_no; i++)
646 menu->entries[i]->clientdata = wapp;
648 wMenuMapAt(menu, x, y, False);
651 /******************************************************************/
653 static void iconExpose(WObjDescriptor *desc, XEvent *event)
655 /* Parameter not used, but tell the compiler that it is ok */
656 (void) event;
658 wAppIconPaint(desc->parent);
661 static void iconDblClick(WObjDescriptor *desc, XEvent *event)
663 WAppIcon *aicon = desc->parent;
664 WApplication *wapp;
665 WScreen *scr = aicon->icon->core->screen_ptr;
666 int unhideHere;
668 assert(aicon->icon->owner != NULL);
670 wapp = wApplicationOf(aicon->icon->owner->main_window);
672 if (event->xbutton.state & ControlMask) {
673 relaunchApplication(wapp);
674 return;
677 unhideHere = (event->xbutton.state & ShiftMask);
678 /* go to the last workspace that the user worked on the app */
679 if (!unhideHere && wapp->last_workspace != scr->current_workspace)
680 wWorkspaceChange(scr, wapp->last_workspace);
682 wUnhideApplication(wapp, event->xbutton.button == Button2, unhideHere);
684 if (event->xbutton.state & MOD_MASK)
685 wHideOtherApplications(aicon->icon->owner);
688 void appIconMouseDown(WObjDescriptor * desc, XEvent * event)
690 WAppIcon *aicon = desc->parent;
691 WScreen *scr = aicon->icon->core->screen_ptr;
692 Bool hasMoved;
694 if (aicon->editing || WCHECK_STATE(WSTATE_MODAL))
695 return;
697 if (IsDoubleClick(scr, event)) {
698 /* Middle or right mouse actions were handled on first click */
699 if (event->xbutton.button == Button1)
700 iconDblClick(desc, event);
701 return;
704 if (event->xbutton.button == Button2) {
705 WApplication *wapp = wApplicationOf(aicon->icon->owner->main_window);
707 if (wapp)
708 relaunchApplication(wapp);
710 return;
713 if (event->xbutton.button == Button3) {
714 WObjDescriptor *desc;
715 WApplication *wapp = wApplicationOf(aicon->icon->owner->main_window);
717 if (!wapp)
718 return;
720 if (event->xbutton.send_event &&
721 XGrabPointer(dpy, aicon->icon->core->window, True, ButtonMotionMask
722 | ButtonReleaseMask | ButtonPressMask, GrabModeAsync,
723 GrabModeAsync, None, None, CurrentTime) != GrabSuccess) {
724 wwarning("pointer grab failed for appicon menu");
725 return;
728 openApplicationMenu(wapp, event->xbutton.x_root, event->xbutton.y_root);
730 /* allow drag select of menu */
731 desc = &scr->icon_menu->menu->descriptor;
732 event->xbutton.send_event = True;
733 (*desc->handle_mousedown) (desc, event);
734 return;
737 hasMoved = wHandleAppIconMove(aicon, event);
738 if (wPreferences.single_click && !hasMoved && aicon->dock != NULL)
740 iconDblClick(desc, event);
744 Bool wHandleAppIconMove(WAppIcon *aicon, XEvent *event)
746 WIcon *icon = aicon->icon;
747 WScreen *scr = icon->core->screen_ptr;
748 WDock *originalDock = aicon->dock; /* can be NULL */
749 WDock *lastDock = originalDock;
750 WDock *allDocks[scr->drawer_count + 2]; /* clip, dock and drawers (order determined at runtime) */
751 WDrawerChain *dc;
752 Bool dockable, ondock;
753 Bool grabbed = False;
754 Bool collapsed = False; /* Stores the collapsed state of lastDock, before the moving appicon entered it */
755 int superfluous = wPreferences.superfluous; /* we cache it to avoid problems */
756 int omnipresent = aicon->omnipresent; /* this must be cached */
757 Bool showed_all_clips = False;
759 int clickButton = event->xbutton.button;
760 Pixmap ghost = None;
761 Window wins[2]; /* Managing shadow window */
762 XEvent ev;
764 int x = aicon->x_pos, y = aicon->y_pos;
765 int ofs_x = event->xbutton.x, ofs_y = event->xbutton.y;
766 int shad_x = x, shad_y = y;
767 int ix = aicon->xindex, iy = aicon->yindex;
768 int i;
769 int oldX = x;
770 int oldY = y;
771 Bool hasMoved = False;
773 if (wPreferences.flags.noupdates && originalDock != NULL)
774 return False;
776 if (!(event->xbutton.state & MOD_MASK))
777 wRaiseFrame(icon->core);
778 else {
779 /* If Mod is pressed for an docked appicon, assume it is to undock it,
780 * so don't lower it */
781 if (originalDock == NULL)
782 wLowerFrame(icon->core);
785 if (XGrabPointer(dpy, icon->core->window, True, ButtonMotionMask
786 | ButtonReleaseMask | ButtonPressMask, GrabModeAsync,
787 GrabModeAsync, None, None, CurrentTime) != GrabSuccess) {
788 wwarning("Pointer grab failed in wHandleAppIconMove");
791 if (originalDock != NULL) {
792 dockable = True;
793 ondock = True;
795 else {
796 ondock = False;
797 if (wPreferences.flags.nodock && wPreferences.flags.noclip && wPreferences.flags.nodrawer)
798 dockable = 0;
799 else
800 dockable = canBeDocked(icon->owner);
803 /* We try the various docks in that order:
804 * - First, the dock the appicon comes from, if any
805 * - Then, the drawers
806 * - Then, the "dock" (WM_DOCK)
807 * - Finally, the clip
809 i = 0;
810 if (originalDock != NULL)
811 allDocks[ i++ ] = originalDock;
812 /* Testing scr->drawers is enough, no need to test wPreferences.flags.nodrawer */
813 for (dc = scr->drawers; dc != NULL; dc = dc->next) {
814 if (dc->adrawer != originalDock)
815 allDocks[ i++ ] = dc->adrawer;
817 if (!wPreferences.flags.nodock && scr->dock != originalDock)
818 allDocks[ i++ ] = scr->dock;
820 if (!wPreferences.flags.noclip &&
821 originalDock != scr->workspaces[scr->current_workspace]->clip)
822 allDocks[ i++ ] = scr->workspaces[scr->current_workspace]->clip;
824 for ( ; i < scr->drawer_count + 2; i++) /* In case the clip, the dock, or both, are disabled */
825 allDocks[ i ] = NULL;
827 wins[0] = icon->core->window;
828 wins[1] = scr->dock_shadow;
829 XRestackWindows(dpy, wins, 2);
830 XMoveResizeWindow(dpy, scr->dock_shadow, aicon->x_pos, aicon->y_pos, ICON_SIZE, ICON_SIZE);
831 if (superfluous) {
832 if (icon->pixmap != None)
833 ghost = MakeGhostIcon(scr, icon->pixmap);
834 else
835 ghost = MakeGhostIcon(scr, icon->core->window);
836 XSetWindowBackgroundPixmap(dpy, scr->dock_shadow, ghost);
837 XClearWindow(dpy, scr->dock_shadow);
839 if (ondock)
840 XMapWindow(dpy, scr->dock_shadow);
842 while (1) {
843 WMMaskEvent(dpy, PointerMotionMask | ButtonReleaseMask | ButtonPressMask
844 | ButtonMotionMask | ExposureMask | EnterWindowMask, &ev);
845 switch (ev.type) {
846 case Expose:
847 WMHandleEvent(&ev);
848 break;
850 case EnterNotify:
851 /* It means the cursor moved so fast that it entered
852 * something else (if moving slowly, it would have
853 * stayed in the appIcon that is being moved. Ignore
854 * such "spurious" EnterNotifiy's */
855 break;
857 case MotionNotify:
858 hasMoved = True;
859 if (!grabbed) {
860 if (abs(ofs_x - ev.xmotion.x) >= MOVE_THRESHOLD
861 || abs(ofs_y - ev.xmotion.y) >= MOVE_THRESHOLD) {
862 XChangeActivePointerGrab(dpy, ButtonMotionMask
863 | ButtonReleaseMask | ButtonPressMask,
864 wPreferences.cursor[WCUR_MOVE], CurrentTime);
865 grabbed = 1;
866 } else {
867 break;
871 if (omnipresent && !showed_all_clips) {
872 int i;
873 for (i = 0; i < scr->workspace_count; i++) {
874 if (i == scr->current_workspace)
875 continue;
877 wDockShowIcons(scr->workspaces[i]->clip);
878 /* Note: if dock is collapsed (for instance, because it
879 auto-collapses), its icons still won't show up */
881 showed_all_clips = True; /* To prevent flickering */
884 x = ev.xmotion.x_root - ofs_x;
885 y = ev.xmotion.y_root - ofs_y;
886 wAppIconMove(aicon, x, y);
888 WDock *theNewDock = NULL;
889 if (!(ev.xmotion.state & MOD_MASK) || aicon->launching || aicon->lock || originalDock == NULL) {
890 for (i = 0; dockable && i < scr->drawer_count + 2; i++) {
891 WDock *theDock = allDocks[i];
892 if (theDock == NULL)
893 break;
894 if (wDockSnapIcon(theDock, aicon, x, y, &ix, &iy, (theDock == originalDock))) {
895 theNewDock = theDock;
896 break;
899 if (originalDock != NULL && theNewDock == NULL &&
900 (aicon->launching || aicon->lock || aicon->running)) {
901 /* In those cases, stay in lastDock if no dock really wants us */
902 theNewDock = lastDock;
905 if (lastDock != NULL && lastDock != theNewDock) {
906 /* Leave lastDock in the state we found it */
907 if (lastDock->type == WM_DRAWER) {
908 wDrawerFillTheGap(lastDock, aicon, (lastDock == originalDock));
910 if (collapsed) {
911 lastDock->collapsed = 1;
912 wDockHideIcons(lastDock);
913 collapsed = False;
915 if (lastDock->auto_raise_lower) {
916 wDockLower(lastDock);
919 if (theNewDock != NULL) {
920 if (lastDock != theNewDock) {
921 collapsed = theNewDock->collapsed;
922 if (collapsed) {
923 theNewDock->collapsed = 0;
924 wDockShowIcons(theNewDock);
926 if (theNewDock->auto_raise_lower) {
927 wDockRaise(theNewDock);
928 /* And raise the moving tile above it */
929 wRaiseFrame(aicon->icon->core);
931 lastDock = theNewDock;
934 shad_x = lastDock->x_pos + ix*wPreferences.icon_size;
935 shad_y = lastDock->y_pos + iy*wPreferences.icon_size;
937 XMoveWindow(dpy, scr->dock_shadow, shad_x, shad_y);
939 if (!ondock) {
940 XMapWindow(dpy, scr->dock_shadow);
942 ondock = 1;
943 } else {
944 lastDock = theNewDock; // i.e., NULL
945 if (ondock) {
946 XUnmapWindow(dpy, scr->dock_shadow);
948 * Leaving that weird comment for now.
949 * But if we see no gap, there is no need to fill one!
950 * We could test ondock first and the lastDock to NULL afterwards
951 if (lastDock_before_it_was_null->type == WM_DRAWER) {
952 wDrawerFillTheGap(lastDock, aicon, (lastDock == originalDock));
953 } */
956 ondock = 0;
958 break;
960 case ButtonPress:
961 break;
963 case ButtonRelease:
964 if (ev.xbutton.button != clickButton)
965 break;
966 XUngrabPointer(dpy, CurrentTime);
968 Bool docked = False;
969 if (ondock) {
970 SlideWindow(icon->core->window, x, y, shad_x, shad_y);
971 XUnmapWindow(dpy, scr->dock_shadow);
972 if (originalDock == NULL) { // docking an undocked appicon
973 docked = wDockAttachIcon(lastDock, aicon, ix, iy, False);
974 if (!docked) {
975 /* AppIcon got rejected (happens only when we can't get the
976 command for that appicon, and the user cancels the
977 wInputDialog asking for one). Make the rejection obvious by
978 sliding the icon to its old position */
979 if (lastDock->type == WM_DRAWER) {
980 // Also fill the gap left in the drawer
981 wDrawerFillTheGap(lastDock, aicon, False);
983 SlideWindow(icon->core->window, x, y, oldX, oldY);
986 else { // moving a docked appicon to a dock
987 if (originalDock == lastDock) {
988 docked = True;
989 wDockReattachIcon(originalDock, aicon, ix, iy);
991 else {
992 docked = wDockMoveIconBetweenDocks(originalDock, lastDock, aicon, ix, iy);
993 if (!docked) {
994 /* Possible scenario: user moved an auto-attracted appicon
995 from the clip to the dock, and cancelled the wInputDialog
996 asking for a command */
997 if (lastDock->type == WM_DRAWER) {
998 wDrawerFillTheGap(lastDock, aicon, False);
1000 /* If aicon comes from a drawer, make some room to reattach it */
1001 if (originalDock->type == WM_DRAWER) {
1002 WAppIcon *aiconsToShift[ originalDock->icon_count ];
1003 int j = 0;
1005 for (i = 0; i < originalDock->max_icons; i++) {
1006 WAppIcon *ai = originalDock->icon_array[ i ];
1007 if (ai && ai != aicon &&
1008 abs(ai->xindex) >= abs(aicon->xindex))
1009 aiconsToShift[j++] = ai;
1011 if (j != originalDock->icon_count - abs(aicon->xindex) - 1)
1012 // Trust this never happens?
1013 wwarning("Shifting j=%d appicons (instead of %d!) to reinsert aicon at index %d.",
1014 j, originalDock->icon_count - abs(aicon->xindex) - 1, aicon->xindex);
1015 wSlideAppicons(aiconsToShift, j, originalDock->on_right_side);
1016 // Trust the appicon is inserted at exactly the same place, so its oldX/oldY are consistent with its "new" location?
1019 SlideWindow(icon->core->window, x, y, oldX, oldY);
1020 wDockReattachIcon(originalDock, aicon, aicon->xindex, aicon->yindex);
1022 else {
1023 if (originalDock->auto_collapse && !originalDock->collapsed) {
1024 originalDock->collapsed = 1;
1025 wDockHideIcons(originalDock);
1027 if (originalDock->auto_raise_lower)
1028 wDockLower(originalDock);
1032 // No matter what happened above, check to lower lastDock
1033 // Don't see why I commented out the following 2 lines
1034 /* if (lastDock->auto_raise_lower)
1035 wDockLower(lastDock); */
1036 /* If docked (or tried to dock) to a auto_collapsing dock, unset
1037 * collapsed, so that wHandleAppIconMove doesn't collapse it
1038 * right away (the timer will take care of it) */
1039 if (lastDock->auto_collapse)
1040 collapsed = 0;
1042 else {
1043 if (originalDock != NULL) { /* Detaching a docked appicon */
1044 if (superfluous) {
1045 if (!aicon->running && !wPreferences.no_animations) {
1046 /* We need to deselect it, even if is deselected in
1047 * wDockDetach(), because else DoKaboom() will fail.
1049 if (aicon->icon->selected)
1050 wIconSelect(aicon->icon);
1051 DoKaboom(scr, aicon->icon->core->window, x, y);
1054 wDockDetach(originalDock, aicon);
1055 if (originalDock->auto_collapse && !originalDock->collapsed) {
1056 originalDock->collapsed = 1;
1057 wDockHideIcons(originalDock);
1059 if (originalDock->auto_raise_lower)
1060 wDockLower(originalDock);
1063 // Can't remember why the icon hiding is better done above than below (commented out)
1064 // Also, lastDock is quite different from originalDock
1066 if (collapsed) {
1067 lastDock->collapsed = 1;
1068 wDockHideIcons(lastDock);
1069 collapsed = 0;
1072 if (superfluous) {
1073 if (ghost != None)
1074 XFreePixmap(dpy, ghost);
1075 XSetWindowBackground(dpy, scr->dock_shadow, scr->white_pixel);
1077 if (showed_all_clips) {
1078 int i;
1079 for (i = 0; i < scr->workspace_count; i++) {
1080 if (i == scr->current_workspace)
1081 continue;
1083 wDockHideIcons(scr->workspaces[i]->clip);
1086 if (wPreferences.auto_arrange_icons && !(originalDock != NULL && docked))
1087 /* Need to rearrange unless moving from dock to dock */
1088 wArrangeIcons(scr, True);
1089 return hasMoved;
1094 /* This function save the application icon and store the path in the Dictionary */
1095 static void wApplicationSaveIconPathFor(const char *iconPath, const char *wm_instance, const char *wm_class)
1097 WMPropList *dict = w_global.domain.window_attr->dictionary;
1098 WMPropList *adict, *key, *iconk;
1099 WMPropList *val;
1100 char *tmp;
1102 tmp = get_name_for_instance_class(wm_instance, wm_class);
1103 key = WMCreatePLString(tmp);
1104 wfree(tmp);
1106 adict = WMGetFromPLDictionary(dict, key);
1107 iconk = WMCreatePLString("Icon");
1109 if (adict) {
1110 val = WMGetFromPLDictionary(adict, iconk);
1111 } else {
1112 /* no dictionary for app, so create one */
1113 adict = WMCreatePLDictionary(NULL, NULL);
1114 WMPutInPLDictionary(dict, key, adict);
1115 WMReleasePropList(adict);
1116 val = NULL;
1119 if (!val) {
1120 val = WMCreatePLString(iconPath);
1121 WMPutInPLDictionary(adict, iconk, val);
1122 WMReleasePropList(val);
1123 } else {
1124 val = NULL;
1127 WMReleasePropList(key);
1128 WMReleasePropList(iconk);
1130 if (val && !wPreferences.flags.noupdates)
1131 UpdateDomainFile(w_global.domain.window_attr);
1134 static WAppIcon *findDockIconFor(WDock *dock, Window main_window)
1136 WAppIcon *aicon = NULL;
1138 aicon = wDockFindIconForWindow(dock, main_window);
1139 if (!aicon) {
1140 wDockTrackWindowLaunch(dock, main_window);
1141 aicon = wDockFindIconForWindow(dock, main_window);
1143 return aicon;
1146 static void create_appicon_from_dock(WWindow *wwin, WApplication *wapp, Window main_window)
1148 WScreen *scr = wwin->screen_ptr;
1149 wapp->app_icon = NULL;
1151 if (scr->last_dock)
1152 wapp->app_icon = findDockIconFor(scr->last_dock, main_window);
1154 /* check main dock if we did not find it in last dock */
1155 if (!wapp->app_icon && scr->dock)
1156 wapp->app_icon = findDockIconFor(scr->dock, main_window);
1158 /* check clips */
1159 if (!wapp->app_icon) {
1160 int i;
1161 for (i = 0; i < scr->workspace_count; i++) {
1162 WDock *dock = scr->workspaces[i]->clip;
1164 if (dock)
1165 wapp->app_icon = findDockIconFor(dock, main_window);
1167 if (wapp->app_icon)
1168 break;
1172 /* Finally check drawers */
1173 if (!wapp->app_icon) {
1174 WDrawerChain *dc;
1175 for (dc = scr->drawers; dc != NULL; dc = dc->next) {
1176 wapp->app_icon = findDockIconFor(dc->adrawer, main_window);
1177 if (wapp->app_icon)
1178 break;
1182 /* If created, then set some flags */
1183 if (wapp->app_icon) {
1184 WWindow *mainw = wapp->main_window_desc;
1186 wapp->app_icon->running = 1;
1187 wapp->app_icon->icon->owner = mainw;
1188 if (mainw->wm_hints && (mainw->wm_hints->flags & IconWindowHint))
1189 wapp->app_icon->icon->icon_win = mainw->wm_hints->icon_window;
1191 /* Update the icon images */
1192 wIconUpdate(wapp->app_icon->icon);
1194 /* Paint it */
1195 wAppIconPaint(wapp->app_icon);
1196 save_appicon(wapp->app_icon, True);
1200 /* Add the appicon to the appiconlist */
1201 static void add_to_appicon_list(WScreen *scr, WAppIcon *appicon)
1203 appicon->prev = NULL;
1204 appicon->next = scr->app_icon_list;
1205 if (scr->app_icon_list)
1206 scr->app_icon_list->prev = appicon;
1208 scr->app_icon_list = appicon;
1211 /* Remove the appicon from the appiconlist */
1212 static void remove_from_appicon_list(WScreen *scr, WAppIcon *appicon)
1214 if (appicon == scr->app_icon_list) {
1215 if (appicon->next)
1216 appicon->next->prev = NULL;
1217 scr->app_icon_list = appicon->next;
1218 } else {
1219 if (appicon->next)
1220 appicon->next->prev = appicon->prev;
1221 if (appicon->prev)
1222 appicon->prev->next = appicon->next;
1225 appicon->prev = NULL;
1226 appicon->next = NULL;
1229 /* Return the AppIcon associated with a given (Xlib) Window. */
1230 WAppIcon *wAppIconFor(Window window)
1232 WObjDescriptor *desc;
1234 if (window == None)
1235 return NULL;
1237 if (XFindContext(dpy, window, w_global.context.client_win, (XPointer *) & desc) == XCNOENT)
1238 return NULL;
1240 if (desc->parent_type == WCLASS_APPICON || desc->parent_type == WCLASS_DOCK_ICON)
1241 return desc->parent;
1243 return NULL;