util/wmiv: add image auto orientation detection
[wmaker-crm.git] / src / appicon.c
blob43a0f32ab2cebeaa3ead42b72c608c2ca0e06b23
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(WAppIcon *appicon);
71 static void remove_from_appicon_list(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(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 (strcmp(wm_class, "WMDock") == 0 && wPreferences.flags.clip_merged_in_dock)
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 = w_global.workspace.array[w_global.workspace.current]->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(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 w_global.workspace.array[w_global.workspace.current]->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(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 RemoveFromStackList(aicon->icon->core);
330 wIconDestroy(aicon->icon);
331 if (aicon->command)
332 wfree(aicon->command);
333 #ifdef XDND
334 if (aicon->dnd_command)
335 wfree(aicon->dnd_command);
336 #endif
337 if (aicon->wm_instance)
338 wfree(aicon->wm_instance);
340 if (aicon->wm_class)
341 wfree(aicon->wm_class);
343 remove_from_appicon_list(aicon);
345 aicon->destroyed = 1;
346 wrelease(aicon);
349 static void drawCorner(WIcon * icon)
351 WScreen *scr = icon->core->screen_ptr;
352 XPoint points[3];
354 points[0].x = 1;
355 points[0].y = 1;
356 points[1].x = 12;
357 points[1].y = 1;
358 points[2].x = 1;
359 points[2].y = 12;
360 XFillPolygon(dpy, icon->core->window, scr->icon_title_texture->normal_gc,
361 points, 3, Convex, CoordModeOrigin);
362 XDrawLine(dpy, icon->core->window, scr->icon_title_texture->light_gc, 0, 0, 0, 12);
363 XDrawLine(dpy, icon->core->window, scr->icon_title_texture->light_gc, 0, 0, 12, 0);
366 void wAppIconMove(WAppIcon * aicon, int x, int y)
368 XMoveWindow(dpy, aicon->icon->core->window, x, y);
369 aicon->x_pos = x;
370 aicon->y_pos = y;
373 #ifdef WS_INDICATOR
374 static void updateDockNumbers(WScreen *scr)
376 int length;
377 char *ws_numbers;
378 WAppIcon *dicon = scr->dock->icon_array[0];
380 ws_numbers = wmalloc(20);
381 snprintf(ws_numbers, 20, "%i [ %i ]", w_global.workspace.current + 1, ((w_global.workspace.current / 10) + 1));
382 length = strlen(ws_numbers);
384 XClearArea(dpy, dicon->icon->core->window, 2, 2, 50, WMFontHeight(scr->icon_title_font) + 1, False);
386 WMDrawString(scr->wmscreen, dicon->icon->core->window, scr->black,
387 scr->icon_title_font, 4, 3, ws_numbers, length);
389 WMDrawString(scr->wmscreen, dicon->icon->core->window, scr->white,
390 scr->icon_title_font, 3, 2, ws_numbers, length);
392 wfree(ws_numbers);
394 #endif /* WS_INDICATOR */
396 void wAppIconPaint(WAppIcon *aicon)
398 WApplication *wapp;
399 WScreen *scr = aicon->icon->core->screen_ptr;
401 if (aicon->icon->owner)
402 wapp = wApplicationOf(aicon->icon->owner->main_window);
403 else
404 wapp = NULL;
406 wIconPaint(aicon->icon);
408 # ifdef WS_INDICATOR
409 if (aicon->docked && scr->dock && scr->dock == aicon->dock && aicon->yindex == 0)
410 updateDockNumbers(scr);
411 # endif
412 if (scr->dock_dots && aicon->docked && !aicon->running && aicon->command != NULL) {
413 XSetClipMask(dpy, scr->copy_gc, scr->dock_dots->mask);
414 XSetClipOrigin(dpy, scr->copy_gc, 0, 0);
415 XCopyArea(dpy, scr->dock_dots->image, aicon->icon->core->window,
416 scr->copy_gc, 0, 0, scr->dock_dots->width, scr->dock_dots->height, 0, 0);
418 #ifdef HIDDENDOT
419 if (wapp && wapp->flags.hidden) {
420 XSetClipMask(dpy, scr->copy_gc, scr->dock_dots->mask);
421 XSetClipOrigin(dpy, scr->copy_gc, 0, 0);
422 XCopyArea(dpy, scr->dock_dots->image,
423 aicon->icon->core->window, scr->copy_gc, 0, 0, 7, scr->dock_dots->height, 0, 0);
425 #endif /* HIDDENDOT */
427 if (aicon->omnipresent)
428 drawCorner(aicon->icon);
430 XSetClipMask(dpy, scr->copy_gc, None);
431 if (aicon->launching)
432 XFillRectangle(dpy, aicon->icon->core->window, scr->stipple_gc,
433 0, 0, wPreferences.icon_size, wPreferences.icon_size);
436 /* Save the application icon, if it's a dockapp then use it with dock = True */
437 void save_appicon(WAppIcon *aicon, Bool dock)
439 char *path;
441 if (!aicon)
442 return;
444 if (dock && (!aicon->docked || aicon->attracted))
445 return;
447 path = wIconStore(aicon->icon);
448 if (!path)
449 return;
451 wApplicationSaveIconPathFor(path, aicon->wm_instance, aicon->wm_class);
452 wfree(path);
455 #define canBeDocked(wwin) ((wwin) && ((wwin)->wm_class||(wwin)->wm_instance))
457 /* main_window may not have the full command line; try to find one which does */
458 static void relaunchApplication(WApplication *wapp)
460 WScreen *scr;
461 WWindow *wlist, *next;
463 scr = wapp->main_window_desc->screen_ptr;
464 wlist = scr->focused_window;
465 if (! wlist)
466 return;
468 while (wlist->prev)
469 wlist = wlist->prev;
471 while (wlist) {
472 next = wlist->next;
474 if (wlist->main_window == wapp->main_window) {
475 if (RelaunchWindow(wlist))
476 return;
479 wlist = next;
483 static void relaunchCallback(WMenu * menu, WMenuEntry * entry)
485 WApplication *wapp = (WApplication *) entry->clientdata;
487 /* Parameter not used, but tell the compiler that it is ok */
488 (void) menu;
490 relaunchApplication(wapp);
493 static void hideCallback(WMenu * menu, WMenuEntry * entry)
495 WApplication *wapp = (WApplication *) entry->clientdata;
497 if (wapp->flags.hidden) {
498 wWorkspaceChange(menu->menu->screen_ptr, wapp->last_workspace);
499 wUnhideApplication(wapp, False, False);
500 } else {
501 wHideApplication(wapp);
505 static void unhideHereCallback(WMenu * menu, WMenuEntry * entry)
507 WApplication *wapp = (WApplication *) entry->clientdata;
509 /* Parameter not used, but tell the compiler that it is ok */
510 (void) menu;
512 wUnhideApplication(wapp, False, True);
515 static void setIconCallback(WMenu *menu, WMenuEntry *entry)
517 WAppIcon *icon = ((WApplication *) entry->clientdata)->app_icon;
518 char *file = NULL;
519 WScreen *scr;
520 int result;
522 /* Parameter not used, but tell the compiler that it is ok */
523 (void) menu;
525 assert(icon != NULL);
527 if (icon->editing)
528 return;
530 icon->editing = 1;
531 scr = icon->icon->core->screen_ptr;
533 wretain(icon);
535 result = wIconChooserDialog(scr, &file, icon->wm_instance, icon->wm_class);
537 if (result) {
538 if (!icon->destroyed) {
539 if (!wIconChangeImageFile(icon->icon, file)) {
540 wMessageDialog(scr, _("Error"),
541 _("Could not open specified icon file"),
542 _("OK"), NULL, NULL);
543 } else {
544 wDefaultChangeIcon(icon->wm_instance, icon->wm_class, file);
545 wAppIconPaint(icon);
548 if (file)
549 wfree(file);
551 icon->editing = 0;
552 wrelease(icon);
555 static void killCallback(WMenu * menu, WMenuEntry * entry)
557 WApplication *wapp = (WApplication *) entry->clientdata;
558 WFakeGroupLeader *fPtr;
559 char *buffer;
560 char *shortname;
562 if (!WCHECK_STATE(WSTATE_NORMAL))
563 return;
565 WCHANGE_STATE(WSTATE_MODAL);
567 assert(entry->clientdata != NULL);
569 shortname = basename(wapp->app_icon->wm_instance);
571 buffer = wstrconcat(wapp->app_icon ? shortname : NULL,
572 _(" will be forcibly closed.\n"
573 "Any unsaved changes will be lost.\n" "Please confirm."));
575 fPtr = wapp->main_window_desc->fake_group;
577 wretain(wapp->main_window_desc);
578 if (wPreferences.dont_confirm_kill
579 || wMessageDialog(menu->frame->screen_ptr, _("Kill Application"),
580 buffer, _("Yes"), _("No"), NULL) == WAPRDefault) {
581 if (fPtr != NULL) {
582 WWindow *wwin, *twin;
584 wwin = wapp->main_window_desc->screen_ptr->focused_window;
585 while (wwin) {
586 twin = wwin->prev;
587 if (wwin->fake_group == fPtr)
588 wClientKill(wwin);
589 wwin = twin;
591 } else if (!wapp->main_window_desc->flags.destroyed) {
592 wClientKill(wapp->main_window_desc);
595 wrelease(wapp->main_window_desc);
596 wfree(buffer);
597 WCHANGE_STATE(WSTATE_NORMAL);
600 static WMenu *createApplicationMenu(WScreen *scr)
602 WMenu *menu;
604 menu = wMenuCreate(scr, NULL, False);
605 wMenuAddCallback(menu, _("Unhide Here"), unhideHereCallback, NULL);
606 wMenuAddCallback(menu, _("Hide"), hideCallback, NULL);
607 wMenuAddCallback(menu, _("Launch"), relaunchCallback, NULL);
608 wMenuAddCallback(menu, _("Set Icon..."), setIconCallback, NULL);
609 wMenuAddCallback(menu, _("Kill"), killCallback, NULL);
611 return menu;
614 static void openApplicationMenu(WApplication * wapp, int x, int y)
616 WMenu *menu;
617 WScreen *scr = wapp->main_window_desc->screen_ptr;
618 int i;
620 if (!scr->icon_menu) {
621 scr->icon_menu = createApplicationMenu(scr);
622 wfree(scr->icon_menu->entries[1]->text);
625 menu = scr->icon_menu;
627 if (wapp->flags.hidden)
628 menu->entries[1]->text = _("Unhide");
629 else
630 menu->entries[1]->text = _("Hide");
632 menu->flags.realized = 0;
633 wMenuRealize(menu);
635 x -= menu->frame->core->width / 2;
636 if (x + menu->frame->core->width > scr->scr_width)
637 x = scr->scr_width - menu->frame->core->width;
639 if (x < 0)
640 x = 0;
642 /* set client data */
643 for (i = 0; i < menu->entry_no; i++)
644 menu->entries[i]->clientdata = wapp;
646 wMenuMapAt(menu, x, y, False);
649 /******************************************************************/
651 static void iconExpose(WObjDescriptor *desc, XEvent *event)
653 /* Parameter not used, but tell the compiler that it is ok */
654 (void) event;
656 wAppIconPaint(desc->parent);
659 static void iconDblClick(WObjDescriptor *desc, XEvent *event)
661 WAppIcon *aicon = desc->parent;
662 WApplication *wapp;
663 WScreen *scr = aicon->icon->core->screen_ptr;
664 int unhideHere;
666 assert(aicon->icon->owner != NULL);
668 wapp = wApplicationOf(aicon->icon->owner->main_window);
670 if (event->xbutton.state & ControlMask) {
671 relaunchApplication(wapp);
672 return;
675 unhideHere = (event->xbutton.state & ShiftMask);
676 /* go to the last workspace that the user worked on the app */
677 if (!unhideHere && wapp->last_workspace != w_global.workspace.current)
678 wWorkspaceChange(scr, wapp->last_workspace);
680 wUnhideApplication(wapp, event->xbutton.button == Button2, unhideHere);
682 if (event->xbutton.state & MOD_MASK)
683 wHideOtherApplications(aicon->icon->owner);
686 void appIconMouseDown(WObjDescriptor * desc, XEvent * event)
688 WAppIcon *aicon = desc->parent;
689 WScreen *scr = aicon->icon->core->screen_ptr;
690 Bool hasMoved;
692 if (aicon->editing || WCHECK_STATE(WSTATE_MODAL))
693 return;
695 if (IsDoubleClick(scr, event)) {
696 /* Middle or right mouse actions were handled on first click */
697 if (event->xbutton.button == Button1)
698 iconDblClick(desc, event);
699 return;
702 if (event->xbutton.button == Button2) {
703 WApplication *wapp = wApplicationOf(aicon->icon->owner->main_window);
705 if (wapp)
706 relaunchApplication(wapp);
708 return;
711 if (event->xbutton.button == Button3) {
712 WObjDescriptor *desc;
713 WApplication *wapp = wApplicationOf(aicon->icon->owner->main_window);
715 if (!wapp)
716 return;
718 if (event->xbutton.send_event &&
719 XGrabPointer(dpy, aicon->icon->core->window, True, ButtonMotionMask
720 | ButtonReleaseMask | ButtonPressMask, GrabModeAsync,
721 GrabModeAsync, None, None, CurrentTime) != GrabSuccess) {
722 wwarning("pointer grab failed for appicon menu");
723 return;
726 openApplicationMenu(wapp, event->xbutton.x_root, event->xbutton.y_root);
728 /* allow drag select of menu */
729 desc = &scr->icon_menu->menu->descriptor;
730 event->xbutton.send_event = True;
731 (*desc->handle_mousedown) (desc, event);
732 return;
735 hasMoved = wHandleAppIconMove(aicon, event);
736 if (wPreferences.single_click && !hasMoved && aicon->dock != NULL)
738 iconDblClick(desc, event);
742 Bool wHandleAppIconMove(WAppIcon *aicon, XEvent *event)
744 WIcon *icon = aicon->icon;
745 WScreen *scr = icon->core->screen_ptr;
746 WDock *originalDock = aicon->dock; /* can be NULL */
747 WDock *lastDock = originalDock;
748 WDock *allDocks[scr->drawer_count + 2]; /* clip, dock and drawers (order determined at runtime) */
749 WDrawerChain *dc;
750 Bool done = False, dockable, ondock;
751 Bool grabbed = False;
752 Bool collapsed = False; /* Stores the collapsed state of lastDock, before the moving appicon entered it */
753 int superfluous = wPreferences.superfluous; /* we cache it to avoid problems */
754 int omnipresent = aicon->omnipresent; /* this must be cached */
755 Bool showed_all_clips = False;
757 int clickButton = event->xbutton.button;
758 Pixmap ghost = None;
759 Window wins[2]; /* Managing shadow window */
760 XEvent ev;
762 int x = aicon->x_pos, y = aicon->y_pos;
763 int ofs_x = event->xbutton.x, ofs_y = event->xbutton.y;
764 int shad_x = x, shad_y = y;
765 int ix = aicon->xindex, iy = aicon->yindex;
766 int i;
767 int oldX = x;
768 int oldY = y;
769 Bool hasMoved = False;
771 if (wPreferences.flags.noupdates && originalDock != NULL)
772 return False;
774 if (!(event->xbutton.state & MOD_MASK))
775 wRaiseFrame(icon->core);
776 else {
777 /* If Mod is pressed for an docked appicon, assume it is to undock it,
778 * so don't lower it */
779 if (originalDock == NULL)
780 wLowerFrame(icon->core);
783 if (XGrabPointer(dpy, icon->core->window, True, ButtonMotionMask
784 | ButtonReleaseMask | ButtonPressMask, GrabModeAsync,
785 GrabModeAsync, None, None, CurrentTime) != GrabSuccess) {
786 wwarning("Pointer grab failed in wHandleAppIconMove");
789 if (originalDock != NULL) {
790 dockable = True;
791 ondock = True;
793 else {
794 ondock = False;
795 if (wPreferences.flags.nodock && wPreferences.flags.noclip && wPreferences.flags.nodrawer)
796 dockable = 0;
797 else
798 dockable = canBeDocked(icon->owner);
801 /* We try the various docks in that order:
802 * - First, the dock the appicon comes from, if any
803 * - Then, the drawers
804 * - Then, the "dock" (WM_DOCK)
805 * - Finally, the clip
807 i = 0;
808 if (originalDock != NULL)
809 allDocks[ i++ ] = originalDock;
810 /* Testing scr->drawers is enough, no need to test wPreferences.flags.nodrawer */
811 for (dc = scr->drawers; dc != NULL; dc = dc->next) {
812 if (dc->adrawer != originalDock)
813 allDocks[ i++ ] = dc->adrawer;
815 if (!wPreferences.flags.nodock && scr->dock != originalDock)
816 allDocks[ i++ ] = scr->dock;
818 if (!wPreferences.flags.noclip &&
819 originalDock != w_global.workspace.array[w_global.workspace.current]->clip)
820 allDocks[i++] = w_global.workspace.array[w_global.workspace.current]->clip;
822 for ( ; i < scr->drawer_count + 2; i++) /* In case the clip, the dock, or both, are disabled */
823 allDocks[ i ] = NULL;
825 wins[0] = icon->core->window;
826 wins[1] = scr->dock_shadow;
827 XRestackWindows(dpy, wins, 2);
828 XMoveResizeWindow(dpy, scr->dock_shadow, aicon->x_pos, aicon->y_pos, ICON_SIZE, ICON_SIZE);
829 if (superfluous) {
830 if (icon->pixmap != None)
831 ghost = MakeGhostIcon(scr, icon->pixmap);
832 else
833 ghost = MakeGhostIcon(scr, icon->core->window);
834 XSetWindowBackgroundPixmap(dpy, scr->dock_shadow, ghost);
835 XClearWindow(dpy, scr->dock_shadow);
837 if (ondock)
838 XMapWindow(dpy, scr->dock_shadow);
840 while (!done) {
841 WMMaskEvent(dpy, PointerMotionMask | ButtonReleaseMask | ButtonPressMask
842 | ButtonMotionMask | ExposureMask | EnterWindowMask, &ev);
843 switch (ev.type) {
844 case Expose:
845 WMHandleEvent(&ev);
846 break;
848 case EnterNotify:
849 /* It means the cursor moved so fast that it entered
850 * something else (if moving slowly, it would have
851 * stayed in the appIcon that is being moved. Ignore
852 * such "spurious" EnterNotifiy's */
853 break;
855 case MotionNotify:
856 hasMoved = True;
857 if (!grabbed) {
858 if (abs(ofs_x - ev.xmotion.x) >= MOVE_THRESHOLD
859 || abs(ofs_y - ev.xmotion.y) >= MOVE_THRESHOLD) {
860 XChangeActivePointerGrab(dpy, ButtonMotionMask
861 | ButtonReleaseMask | ButtonPressMask,
862 wPreferences.cursor[WCUR_MOVE], CurrentTime);
863 grabbed = 1;
864 } else {
865 break;
869 if (omnipresent && !showed_all_clips) {
870 int i;
871 for (i = 0; i < w_global.workspace.count; i++) {
872 if (i == w_global.workspace.current)
873 continue;
875 wDockShowIcons(w_global.workspace.array[i]->clip);
876 /* Note: if dock is collapsed (for instance, because it
877 auto-collapses), its icons still won't show up */
879 showed_all_clips = True; /* To prevent flickering */
882 x = ev.xmotion.x_root - ofs_x;
883 y = ev.xmotion.y_root - ofs_y;
884 wAppIconMove(aicon, x, y);
886 WDock *theNewDock = NULL;
887 if (!(ev.xmotion.state & MOD_MASK) || aicon->launching || aicon->lock || originalDock == NULL) {
888 for (i = 0; dockable && i < scr->drawer_count + 2; i++) {
889 WDock *theDock = allDocks[i];
890 if (theDock == NULL)
891 break;
892 if (wDockSnapIcon(theDock, aicon, x, y, &ix, &iy, (theDock == originalDock))) {
893 theNewDock = theDock;
894 break;
897 if (originalDock != NULL && theNewDock == NULL &&
898 (aicon->launching || aicon->lock || aicon->running)) {
899 /* In those cases, stay in lastDock if no dock really wants us */
900 theNewDock = lastDock;
903 if (lastDock != NULL && lastDock != theNewDock) {
904 /* Leave lastDock in the state we found it */
905 if (lastDock->type == WM_DRAWER) {
906 wDrawerFillTheGap(lastDock, aicon, (lastDock == originalDock));
908 if (collapsed) {
909 lastDock->collapsed = 1;
910 wDockHideIcons(lastDock);
911 collapsed = False;
913 if (lastDock->auto_raise_lower) {
914 wDockLower(lastDock);
917 if (theNewDock != NULL) {
918 if (lastDock != theNewDock) {
919 collapsed = theNewDock->collapsed;
920 if (collapsed) {
921 theNewDock->collapsed = 0;
922 wDockShowIcons(theNewDock);
924 if (theNewDock->auto_raise_lower) {
925 wDockRaise(theNewDock);
926 /* And raise the moving tile above it */
927 wRaiseFrame(aicon->icon->core);
929 lastDock = theNewDock;
932 shad_x = lastDock->x_pos + ix*wPreferences.icon_size;
933 shad_y = lastDock->y_pos + iy*wPreferences.icon_size;
935 XMoveWindow(dpy, scr->dock_shadow, shad_x, shad_y);
937 if (!ondock) {
938 XMapWindow(dpy, scr->dock_shadow);
940 ondock = 1;
941 } else {
942 lastDock = theNewDock; // i.e., NULL
943 if (ondock) {
944 XUnmapWindow(dpy, scr->dock_shadow);
946 * Leaving that weird comment for now.
947 * But if we see no gap, there is no need to fill one!
948 * We could test ondock first and the lastDock to NULL afterwards
949 if (lastDock_before_it_was_null->type == WM_DRAWER) {
950 wDrawerFillTheGap(lastDock, aicon, (lastDock == originalDock));
951 } */
954 ondock = 0;
956 break;
958 case ButtonPress:
959 break;
961 case ButtonRelease:
962 if (ev.xbutton.button != clickButton)
963 break;
964 XUngrabPointer(dpy, CurrentTime);
966 Bool docked = False;
967 if (ondock) {
968 SlideWindow(icon->core->window, x, y, shad_x, shad_y);
969 XUnmapWindow(dpy, scr->dock_shadow);
970 if (originalDock == NULL) { // docking an undocked appicon
971 docked = wDockAttachIcon(lastDock, aicon, ix, iy, False);
972 if (!docked) {
973 /* AppIcon got rejected (happens only when we can't get the
974 command for that appicon, and the user cancels the
975 wInputDialog asking for one). Make the rejection obvious by
976 sliding the icon to its old position */
977 if (lastDock->type == WM_DRAWER) {
978 // Also fill the gap left in the drawer
979 wDrawerFillTheGap(lastDock, aicon, False);
981 SlideWindow(icon->core->window, x, y, oldX, oldY);
984 else { // moving a docked appicon to a dock
985 if (originalDock == lastDock) {
986 docked = True;
987 wDockReattachIcon(originalDock, aicon, ix, iy);
989 else {
990 docked = wDockMoveIconBetweenDocks(originalDock, lastDock, aicon, ix, iy);
991 if (!docked) {
992 /* Possible scenario: user moved an auto-attracted appicon
993 from the clip to the dock, and cancelled the wInputDialog
994 asking for a command */
995 if (lastDock->type == WM_DRAWER) {
996 wDrawerFillTheGap(lastDock, aicon, False);
998 /* If aicon comes from a drawer, make some room to reattach it */
999 if (originalDock->type == WM_DRAWER) {
1000 WAppIcon *aiconsToShift[ originalDock->icon_count ];
1001 int j = 0;
1003 for (i = 0; i < originalDock->max_icons; i++) {
1004 WAppIcon *ai = originalDock->icon_array[ i ];
1005 if (ai && ai != aicon &&
1006 abs(ai->xindex) >= abs(aicon->xindex))
1007 aiconsToShift[j++] = ai;
1009 if (j != originalDock->icon_count - abs(aicon->xindex) - 1)
1010 // Trust this never happens?
1011 wwarning("Shifting j=%d appicons (instead of %d!) to reinsert aicon at index %d.",
1012 j, originalDock->icon_count - abs(aicon->xindex) - 1, aicon->xindex);
1013 wSlideAppicons(aiconsToShift, j, originalDock->on_right_side);
1014 // Trust the appicon is inserted at exactly the same place, so its oldX/oldY are consistent with its "new" location?
1017 SlideWindow(icon->core->window, x, y, oldX, oldY);
1018 wDockReattachIcon(originalDock, aicon, aicon->xindex, aicon->yindex);
1020 else {
1021 if (originalDock->auto_collapse && !originalDock->collapsed) {
1022 originalDock->collapsed = 1;
1023 wDockHideIcons(originalDock);
1025 if (originalDock->auto_raise_lower)
1026 wDockLower(originalDock);
1030 // No matter what happened above, check to lower lastDock
1031 // Don't see why I commented out the following 2 lines
1032 /* if (lastDock->auto_raise_lower)
1033 wDockLower(lastDock); */
1034 /* If docked (or tried to dock) to a auto_collapsing dock, unset
1035 * collapsed, so that wHandleAppIconMove doesn't collapse it
1036 * right away (the timer will take care of it) */
1037 if (lastDock->auto_collapse)
1038 collapsed = 0;
1040 else {
1041 if (originalDock != NULL) { /* Detaching a docked appicon */
1042 if (superfluous) {
1043 if (!aicon->running && !wPreferences.no_animations) {
1044 /* We need to deselect it, even if is deselected in
1045 * wDockDetach(), because else DoKaboom() will fail.
1047 if (aicon->icon->selected)
1048 wIconSelect(aicon->icon);
1049 DoKaboom(scr, aicon->icon->core->window, x, y);
1052 wDockDetach(originalDock, aicon);
1053 if (originalDock->auto_collapse && !originalDock->collapsed) {
1054 originalDock->collapsed = 1;
1055 wDockHideIcons(originalDock);
1057 if (originalDock->auto_raise_lower)
1058 wDockLower(originalDock);
1061 // Can't remember why the icon hiding is better done above than below (commented out)
1062 // Also, lastDock is quite different from originalDock
1064 if (collapsed) {
1065 lastDock->collapsed = 1;
1066 wDockHideIcons(lastDock);
1067 collapsed = 0;
1070 if (superfluous) {
1071 if (ghost != None)
1072 XFreePixmap(dpy, ghost);
1073 XSetWindowBackground(dpy, scr->dock_shadow, scr->white_pixel);
1075 if (showed_all_clips) {
1076 int i;
1077 for (i = 0; i < w_global.workspace.count; i++) {
1078 if (i == w_global.workspace.current)
1079 continue;
1081 wDockHideIcons(w_global.workspace.array[i]->clip);
1084 if (wPreferences.auto_arrange_icons && !(originalDock != NULL && docked))
1085 /* Need to rearrange unless moving from dock to dock */
1086 wArrangeIcons(scr, True);
1087 return hasMoved;
1090 return False; /* Never reached */
1093 /* This function save the application icon and store the path in the Dictionary */
1094 static void wApplicationSaveIconPathFor(const char *iconPath, const char *wm_instance, const char *wm_class)
1096 WMPropList *dict = w_global.domain.window_attr->dictionary;
1097 WMPropList *adict, *key, *iconk;
1098 WMPropList *val;
1099 char *tmp;
1101 tmp = get_name_for_instance_class(wm_instance, wm_class);
1102 key = WMCreatePLString(tmp);
1103 wfree(tmp);
1105 adict = WMGetFromPLDictionary(dict, key);
1106 iconk = WMCreatePLString("Icon");
1108 if (adict) {
1109 val = WMGetFromPLDictionary(adict, iconk);
1110 } else {
1111 /* no dictionary for app, so create one */
1112 adict = WMCreatePLDictionary(NULL, NULL);
1113 WMPutInPLDictionary(dict, key, adict);
1114 WMReleasePropList(adict);
1115 val = NULL;
1118 if (!val) {
1119 val = WMCreatePLString(iconPath);
1120 WMPutInPLDictionary(adict, iconk, val);
1121 WMReleasePropList(val);
1122 } else {
1123 val = NULL;
1126 WMReleasePropList(key);
1127 WMReleasePropList(iconk);
1129 if (val && !wPreferences.flags.noupdates)
1130 UpdateDomainFile(w_global.domain.window_attr);
1133 static WAppIcon *findDockIconFor(WDock *dock, Window main_window)
1135 WAppIcon *aicon = NULL;
1137 aicon = wDockFindIconForWindow(dock, main_window);
1138 if (!aicon) {
1139 wDockTrackWindowLaunch(dock, main_window);
1140 aicon = wDockFindIconForWindow(dock, main_window);
1142 return aicon;
1145 static void create_appicon_from_dock(WWindow *wwin, WApplication *wapp, Window main_window)
1147 WScreen *scr = wwin->screen_ptr;
1148 wapp->app_icon = NULL;
1150 if (scr->last_dock)
1151 wapp->app_icon = findDockIconFor(scr->last_dock, main_window);
1153 /* check main dock if we did not find it in last dock */
1154 if (!wapp->app_icon && scr->dock)
1155 wapp->app_icon = findDockIconFor(scr->dock, main_window);
1157 /* check clips */
1158 if (!wapp->app_icon) {
1159 int i;
1160 for (i = 0; i < w_global.workspace.count; i++) {
1161 WDock *dock = w_global.workspace.array[i]->clip;
1163 if (dock)
1164 wapp->app_icon = findDockIconFor(dock, main_window);
1166 if (wapp->app_icon)
1167 break;
1171 /* Finally check drawers */
1172 if (!wapp->app_icon) {
1173 WDrawerChain *dc;
1174 for (dc = scr->drawers; dc != NULL; dc = dc->next) {
1175 wapp->app_icon = findDockIconFor(dc->adrawer, main_window);
1176 if (wapp->app_icon)
1177 break;
1181 /* If created, then set some flags */
1182 if (wapp->app_icon) {
1183 WWindow *mainw = wapp->main_window_desc;
1185 wapp->app_icon->running = 1;
1186 wapp->app_icon->icon->owner = mainw;
1187 if (mainw->wm_hints && (mainw->wm_hints->flags & IconWindowHint))
1188 wapp->app_icon->icon->icon_win = mainw->wm_hints->icon_window;
1190 /* Update the icon images */
1191 wIconUpdate(wapp->app_icon->icon);
1193 /* Paint it */
1194 wAppIconPaint(wapp->app_icon);
1195 save_appicon(wapp->app_icon, True);
1199 /* Add the appicon to the appiconlist */
1200 static void add_to_appicon_list(WAppIcon *appicon)
1202 appicon->prev = NULL;
1203 appicon->next = w_global.app_icon_list;
1204 if (w_global.app_icon_list)
1205 w_global.app_icon_list->prev = appicon;
1207 w_global.app_icon_list = appicon;
1210 /* Remove the appicon from the appiconlist */
1211 static void remove_from_appicon_list(WAppIcon *appicon)
1213 if (appicon == w_global.app_icon_list) {
1214 if (appicon->next)
1215 appicon->next->prev = NULL;
1216 w_global.app_icon_list = appicon->next;
1217 } else {
1218 if (appicon->next)
1219 appicon->next->prev = appicon->prev;
1220 if (appicon->prev)
1221 appicon->prev->next = appicon->next;
1224 appicon->prev = NULL;
1225 appicon->next = NULL;
1228 /* Return the AppIcon associated with a given (Xlib) Window. */
1229 WAppIcon *wAppIconFor(Window window)
1231 WObjDescriptor *desc;
1233 if (window == None)
1234 return NULL;
1236 if (XFindContext(dpy, window, w_global.context.client_win, (XPointer *) & desc) == XCNOENT)
1237 return NULL;
1239 if (desc->parent_type == WCLASS_APPICON || desc->parent_type == WCLASS_DOCK_ICON)
1240 return desc->parent;
1242 return NULL;