Fix out of source build for i18n pot files
[wmaker-crm.git] / src / appicon.c
blobf61ae0eefefe825de02caede8dfa97e034bc8b95
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 = wapp->main_window_desc->screen_ptr;
201 WDock *attracting_dock;
202 int x = 0, y = 0;
203 Bool update_icon = False;
205 if (!wapp || !wapp->app_icon)
206 return;
208 icon = wapp->app_icon->icon;
209 wapp->app_icon->main_window = wapp->main_window;
211 /* If the icon is docked, don't continue */
212 if (wapp->app_icon->docked)
213 return;
215 attracting_dock = scr->attracting_drawer != NULL ?
216 scr->attracting_drawer :
217 w_global.workspace.array[w_global.workspace.current]->clip;
218 if (attracting_dock && attracting_dock->attract_icons &&
219 wDockFindFreeSlot(attracting_dock, &x, &y)) {
220 wapp->app_icon->attracted = 1;
221 if (!icon->shadowed) {
222 icon->shadowed = 1;
223 update_icon = True;
225 wDockAttachIcon(attracting_dock, wapp->app_icon, x, y, update_icon);
226 } else {
227 /* We must know if the icon is painted in the screen,
228 * because if painted, then PlaceIcon will return the next
229 * space on the screen, and the icon will move */
230 if (wapp->app_icon->next == NULL && wapp->app_icon->prev == NULL) {
231 PlaceIcon(scr, &x, &y, wGetHeadForWindow(wapp->main_window_desc));
232 wAppIconMove(wapp->app_icon, x, y);
233 wLowerFrame(icon->core);
237 /* If we want appicon (no_appicon is not set) and the icon is not
238 * in the appicon_list, we must add it. Else, we want to avoid
239 * having it on the list */
240 if (!WFLAGP(wapp->main_window_desc, no_appicon) &&
241 wapp->app_icon->next == NULL && wapp->app_icon->prev == NULL)
242 add_to_appicon_list(wapp->app_icon);
244 if (!attracting_dock || !wapp->app_icon->attracted || !attracting_dock->collapsed)
245 XMapWindow(dpy, icon->core->window);
247 if (wPreferences.auto_arrange_icons && !wapp->app_icon->attracted)
248 wArrangeIcons(scr, True);
251 void removeAppIconFor(WApplication *wapp)
253 if (!wapp->app_icon)
254 return;
256 if (wPreferences.highlight_active_app)
257 wIconSetHighlited(wapp->app_icon->icon, False);
258 if (wapp->app_icon->docked && !wapp->app_icon->attracted) {
259 wapp->app_icon->running = 0;
260 /* since we keep it, we don't care if it was attracted or not */
261 wapp->app_icon->attracted = 0;
262 wapp->app_icon->icon->shadowed = 0;
263 wapp->app_icon->main_window = None;
264 wapp->app_icon->pid = 0;
265 wapp->app_icon->icon->owner = NULL;
266 wapp->app_icon->icon->icon_win = None;
268 /* Set the icon image */
269 set_icon_image_from_database(wapp->app_icon->icon, wapp->app_icon->wm_instance,
270 wapp->app_icon->wm_class, wapp->app_icon->command);
272 /* Update the icon, because wapp->app_icon->icon could be NULL */
273 wIconUpdate(wapp->app_icon->icon);
275 /* Paint it */
276 wAppIconPaint(wapp->app_icon);
277 } else if (wapp->app_icon->docked) {
278 wapp->app_icon->running = 0;
279 if (wapp->app_icon->dock->type == WM_DRAWER) {
280 wDrawerFillTheGap(wapp->app_icon->dock, wapp->app_icon, True);
282 wDockDetach(wapp->app_icon->dock, wapp->app_icon);
283 } else {
284 wAppIconDestroy(wapp->app_icon);
287 wapp->app_icon = NULL;
289 if (wPreferences.auto_arrange_icons)
290 wArrangeIcons(wapp->main_window_desc->screen_ptr, True);
293 static WAppIcon *wAppIconCreate(WWindow *leader_win)
295 WAppIcon *aicon;
297 aicon = wmalloc(sizeof(WAppIcon));
298 wretain(aicon);
299 aicon->yindex = -1;
300 aicon->xindex = -1;
301 aicon->prev = NULL;
302 aicon->next = NULL;
304 if (leader_win->wm_class)
305 aicon->wm_class = wstrdup(leader_win->wm_class);
307 if (leader_win->wm_instance)
308 aicon->wm_instance = wstrdup(leader_win->wm_instance);
310 aicon->icon = icon_create_for_wwindow(leader_win);
311 #ifdef XDND
312 wXDNDMakeAwareness(aicon->icon->core->window);
313 #endif
315 /* will be overriden if docked */
316 aicon->icon->core->descriptor.handle_mousedown = appIconMouseDown;
317 aicon->icon->core->descriptor.handle_expose = iconExpose;
318 aicon->icon->core->descriptor.parent_type = WCLASS_APPICON;
319 aicon->icon->core->descriptor.parent = aicon;
320 AddToStackList(aicon->icon->core);
321 aicon->icon->show_title = 0;
323 return aicon;
326 void wAppIconDestroy(WAppIcon *aicon)
328 RemoveFromStackList(aicon->icon->core);
329 wIconDestroy(aicon->icon);
330 if (aicon->command)
331 wfree(aicon->command);
332 #ifdef XDND
333 if (aicon->dnd_command)
334 wfree(aicon->dnd_command);
335 #endif
336 if (aicon->wm_instance)
337 wfree(aicon->wm_instance);
339 if (aicon->wm_class)
340 wfree(aicon->wm_class);
342 remove_from_appicon_list(aicon);
344 aicon->destroyed = 1;
345 wrelease(aicon);
348 static void drawCorner(WIcon * icon)
350 WScreen *scr = icon->core->screen_ptr;
351 XPoint points[3];
353 points[0].x = 1;
354 points[0].y = 1;
355 points[1].x = 12;
356 points[1].y = 1;
357 points[2].x = 1;
358 points[2].y = 12;
359 XFillPolygon(dpy, icon->core->window, scr->icon_title_texture->normal_gc,
360 points, 3, Convex, CoordModeOrigin);
361 XDrawLine(dpy, icon->core->window, scr->icon_title_texture->light_gc, 0, 0, 0, 12);
362 XDrawLine(dpy, icon->core->window, scr->icon_title_texture->light_gc, 0, 0, 12, 0);
365 void wAppIconMove(WAppIcon * aicon, int x, int y)
367 XMoveWindow(dpy, aicon->icon->core->window, x, y);
368 aicon->x_pos = x;
369 aicon->y_pos = y;
372 #ifdef WS_INDICATOR
373 static void updateDockNumbers(WScreen *scr)
375 int length;
376 char *ws_numbers;
377 WAppIcon *dicon = scr->dock->icon_array[0];
379 ws_numbers = wmalloc(20);
380 snprintf(ws_numbers, 20, "%i [ %i ]", w_global.workspace.current + 1, ((w_global.workspace.current / 10) + 1));
381 length = strlen(ws_numbers);
383 XClearArea(dpy, dicon->icon->core->window, 2, 2, 50, WMFontHeight(scr->icon_title_font) + 1, False);
385 WMDrawString(scr->wmscreen, dicon->icon->core->window, scr->black,
386 scr->icon_title_font, 4, 3, ws_numbers, length);
388 WMDrawString(scr->wmscreen, dicon->icon->core->window, scr->white,
389 scr->icon_title_font, 3, 2, ws_numbers, length);
391 wfree(ws_numbers);
393 #endif /* WS_INDICATOR */
395 void wAppIconPaint(WAppIcon *aicon)
397 WApplication *wapp;
398 WScreen *scr = aicon->icon->core->screen_ptr;
400 if (aicon->icon->owner)
401 wapp = wApplicationOf(aicon->icon->owner->main_window);
402 else
403 wapp = NULL;
405 wIconPaint(aicon->icon);
407 # ifdef WS_INDICATOR
408 if (aicon->docked && scr->dock && scr->dock == aicon->dock && aicon->yindex == 0)
409 updateDockNumbers(scr);
410 # endif
411 if (scr->dock_dots && aicon->docked && !aicon->running && aicon->command != NULL) {
412 XSetClipMask(dpy, scr->copy_gc, scr->dock_dots->mask);
413 XSetClipOrigin(dpy, scr->copy_gc, 0, 0);
414 XCopyArea(dpy, scr->dock_dots->image, aicon->icon->core->window,
415 scr->copy_gc, 0, 0, scr->dock_dots->width, scr->dock_dots->height, 0, 0);
417 #ifdef HIDDENDOT
418 if (wapp && wapp->flags.hidden) {
419 XSetClipMask(dpy, scr->copy_gc, scr->dock_dots->mask);
420 XSetClipOrigin(dpy, scr->copy_gc, 0, 0);
421 XCopyArea(dpy, scr->dock_dots->image,
422 aicon->icon->core->window, scr->copy_gc, 0, 0, 7, scr->dock_dots->height, 0, 0);
424 #endif /* HIDDENDOT */
426 if (aicon->omnipresent)
427 drawCorner(aicon->icon);
429 XSetClipMask(dpy, scr->copy_gc, None);
430 if (aicon->launching)
431 XFillRectangle(dpy, aicon->icon->core->window, scr->stipple_gc,
432 0, 0, wPreferences.icon_size, wPreferences.icon_size);
435 /* Save the application icon, if it's a dockapp then use it with dock = True */
436 void save_appicon(WAppIcon *aicon, Bool dock)
438 char *path;
440 if (!aicon)
441 return;
443 if (dock && (!aicon->docked || aicon->attracted))
444 return;
446 path = wIconStore(aicon->icon);
447 if (!path)
448 return;
450 wApplicationSaveIconPathFor(path, aicon->wm_instance, aicon->wm_class);
451 wfree(path);
454 #define canBeDocked(wwin) ((wwin) && ((wwin)->wm_class||(wwin)->wm_instance))
456 /* main_window may not have the full command line; try to find one which does */
457 static void relaunchApplication(WApplication *wapp)
459 WScreen *scr;
460 WWindow *wlist, *next;
462 scr = wapp->main_window_desc->screen_ptr;
463 wlist = scr->focused_window;
464 if (! wlist)
465 return;
467 while (wlist->prev)
468 wlist = wlist->prev;
470 while (wlist) {
471 next = wlist->next;
473 if (wlist->main_window == wapp->main_window) {
474 if (RelaunchWindow(wlist))
475 return;
478 wlist = next;
482 static void relaunchCallback(WMenu * menu, WMenuEntry * entry)
484 WApplication *wapp = (WApplication *) entry->clientdata;
486 /* Parameter not used, but tell the compiler that it is ok */
487 (void) menu;
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 /* Parameter not used, but tell the compiler that it is ok */
509 (void) menu;
511 wUnhideApplication(wapp, False, True);
514 static void setIconCallback(WMenu *menu, WMenuEntry *entry)
516 WAppIcon *icon = ((WApplication *) entry->clientdata)->app_icon;
517 char *file = NULL;
518 WScreen *scr;
519 int result;
521 /* Parameter not used, but tell the compiler that it is ok */
522 (void) menu;
524 assert(icon != NULL);
526 if (icon->editing)
527 return;
529 icon->editing = 1;
530 scr = icon->icon->core->screen_ptr;
532 wretain(icon);
534 result = wIconChooserDialog(scr, &file, icon->wm_instance, icon->wm_class);
536 if (result && !icon->destroyed) {
537 if (file && *file == 0) {
538 wfree(file);
539 file = NULL;
541 if (!wIconChangeImageFile(icon->icon, file)) {
542 wMessageDialog(scr, _("Error"),
543 _("Could not open specified icon file"), _("OK"), NULL, NULL);
544 } else {
545 wDefaultChangeIcon(icon->wm_instance, icon->wm_class, file);
546 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;