2 * Window Maker window manager
4 * Copyright (c) 1998-2003 Alfredo K. Kojima
5 * Copyright (c) 2014 Window Maker Team
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
28 #include <X11/Xutil.h>
30 #include <X11/extensions/shape.h>
38 #include "WindowMaker.h"
44 #include "workspace.h"
49 typedef struct _WBalloon
{
75 static int countLines(const char *text
)
81 if (*p
== '\n' && p
[1] != 0)
88 static int getMaxStringWidth(WMFont
*font
, const char *text
)
97 wt
= WMWidthOfString(font
, pb
, pos
);
108 wt
= WMWidthOfString(font
, pb
, pos
);
115 static void drawMultiLineString(WMScreen
*scr
, Pixmap pixmap
, WMColor
*color
,
116 WMFont
*font
, int x
, int y
, const char *text
, int len
)
118 const char *p
= text
;
121 int height
= WMFontHeight(font
);
123 while (*p
&& p
- text
< len
) {
125 WMDrawString(scr
, pixmap
, color
, font
, x
, y
+ l
* height
, pb
, pos
);
135 WMDrawString(scr
, pixmap
, color
, font
, x
, y
+ l
* height
, pb
, pos
);
138 #ifdef SHAPED_BALLOON
142 static void drawBalloon(WScreen
*scr
, Pixmap bitmap
, Pixmap pix
, int x
, int y
, int w
, int h
, int side
)
144 GC bgc
= scr
->balloon
->monoGC
;
145 GC gc
= scr
->draw_gc
;
146 int rad
= h
* 3 / 10;
147 XPoint pt
[3], ipt
[3];
151 XSetForeground(dpy
, bgc
, 1);
153 XFillArc(dpy
, bitmap
, bgc
, x
, y
, rad
, rad
, 90 * 64, 90 * 64);
154 XFillArc(dpy
, bitmap
, bgc
, x
, y
+ h
- 1 - rad
, rad
, rad
, 180 * 64, 90 * 64);
156 XFillArc(dpy
, bitmap
, bgc
, x
+ w
- 1 - rad
, y
, rad
, rad
, 0 * 64, 90 * 64);
157 XFillArc(dpy
, bitmap
, bgc
, x
+ w
- 1 - rad
, y
+ h
- 1 - rad
, rad
, rad
, 270 * 64, 90 * 64);
159 XFillRectangle(dpy
, bitmap
, bgc
, x
, y
+ rad
/ 2, w
, h
- rad
);
160 XFillRectangle(dpy
, bitmap
, bgc
, x
+ rad
/ 2, y
, w
- rad
, h
);
163 XSetForeground(dpy
, gc
, scr
->white_pixel
);
165 XFillArc(dpy
, pix
, gc
, x
+ 1, y
+ 1, rad
, rad
, 90 * 64, 90 * 64);
166 XFillArc(dpy
, pix
, gc
, x
+ 1, y
+ h
- 2 - rad
, rad
, rad
, 180 * 64, 90 * 64);
168 XFillArc(dpy
, pix
, gc
, x
+ w
- 2 - rad
, y
+ 1, rad
, rad
, 0 * 64, 90 * 64);
169 XFillArc(dpy
, pix
, gc
, x
+ w
- 2 - rad
, y
+ h
- 2 - rad
, rad
, rad
, 270 * 64, 90 * 64);
171 XFillRectangle(dpy
, pix
, gc
, x
+ 1, y
+ 1 + rad
/ 2, w
- 2, h
- 2 - rad
);
172 XFillRectangle(dpy
, pix
, gc
, x
+ 1 + rad
/ 2, y
+ 1, w
- 2 - rad
, h
- 2);
176 pt
[1].y
= y
+ h
- 1 + SPACE
;
178 ipt
[0].y
= pt
[0].y
- 1;
179 ipt
[1].y
= pt
[1].y
- 1;
180 ipt
[2].y
= pt
[2].y
- 1;
185 ipt
[0].y
= pt
[0].y
+ 1;
186 ipt
[1].y
= pt
[1].y
+ 1;
187 ipt
[2].y
= pt
[2].y
+ 1;
190 /*w1 = WMAX(h, 24); */
194 pt
[0].x
= x
+ w
- w1
+ 2 * w1
/ 16;
195 pt
[1].x
= x
+ w
- w1
+ 11 * w1
/ 16;
196 pt
[2].x
= x
+ w
- w1
+ 7 * w1
/ 16;
197 ipt
[0].x
= x
+ 1 + w
- w1
+ 2 * (w1
- 1) / 16;
198 ipt
[1].x
= x
+ 1 + w
- w1
+ 11 * (w1
- 1) / 16;
199 ipt
[2].x
= x
+ 1 + w
- w1
+ 7 * (w1
- 1) / 16;
200 /*ipt[0].x = pt[0].x+1;
202 ipt[2].x = pt[2].x; */
204 pt
[0].x
= x
+ w1
- 2 * w1
/ 16;
205 pt
[1].x
= x
+ w1
- 11 * w1
/ 16;
206 pt
[2].x
= x
+ w1
- 7 * w1
/ 16;
207 ipt
[0].x
= x
- 1 + w1
- 2 * (w1
- 1) / 16;
208 ipt
[1].x
= x
- 1 + w1
- 11 * (w1
- 1) / 16;
209 ipt
[2].x
= x
- 1 + w1
- 7 * (w1
- 1) / 16;
210 /*ipt[0].x = pt[0].x-1;
212 ipt[2].x = pt[2].x; */
215 XFillPolygon(dpy
, bitmap
, bgc
, pt
, 3, Convex
, CoordModeOrigin
);
216 XFillPolygon(dpy
, pix
, gc
, ipt
, 3, Convex
, CoordModeOrigin
);
219 XSetForeground(dpy
, gc
, scr
->black_pixel
);
221 XDrawLines(dpy
, pix
, gc
, pt
, 3, CoordModeOrigin
);
229 XDrawLines(dpy
, pix
, gc
, pt
, 3, CoordModeOrigin
);
232 static Pixmap
makePixmap(WScreen
*scr
, int width
, int height
, int side
, Pixmap
*mask
)
234 WBalloon
*bal
= scr
->balloon
;
239 bitmap
= XCreatePixmap(dpy
, scr
->root_win
, width
+ SPACE
, height
+ SPACE
, 1);
242 bal
->monoGC
= XCreateGC(dpy
, bitmap
, 0, NULL
);
244 XSetForeground(dpy
, bal
->monoGC
, 0);
245 XFillRectangle(dpy
, bitmap
, bal
->monoGC
, 0, 0, width
+ SPACE
, height
+ SPACE
);
247 pixmap
= XCreatePixmap(dpy
, scr
->root_win
, width
+ SPACE
, height
+ SPACE
, scr
->w_depth
);
248 XSetForeground(dpy
, scr
->draw_gc
, scr
->black_pixel
);
249 XFillRectangle(dpy
, pixmap
, scr
->draw_gc
, 0, 0, width
+ SPACE
, height
+ SPACE
);
257 drawBalloon(scr
, bitmap
, pixmap
, x
, y
, width
, height
, side
);
264 static void showText(WScreen
*scr
, int x
, int y
, int h
, int w
, const char *text
)
270 WMFont
*font
= scr
->info_text_font
;
275 if (scr
->balloon
->contents
)
276 XFreePixmap(dpy
, scr
->balloon
->contents
);
278 width
= getMaxStringWidth(font
, text
) + 16;
279 height
= countLines(text
) * WMFontHeight(font
) + 4;
286 if (x
+ width
> scr
->scr_width
) {
288 bx
= x
- width
+ w
/ 2;
295 if (bx
+ width
> scr
->scr_width
)
296 bx
= scr
->scr_width
- width
;
298 if (y
- (height
+ SPACE
) < 0) {
304 by
= y
- (height
+ SPACE
);
307 pixmap
= makePixmap(scr
, width
, height
, side
, &mask
);
309 drawMultiLineString(scr
->wmscreen
, pixmap
, scr
->black
, font
, 8, ty
+ 2, text
, strlen(text
));
311 XSetWindowBackgroundPixmap(dpy
, scr
->balloon
->window
, pixmap
);
312 scr
->balloon
->contents
= pixmap
;
314 XResizeWindow(dpy
, scr
->balloon
->window
, width
, height
+ SPACE
);
315 XShapeCombineMask(dpy
, scr
->balloon
->window
, ShapeBounding
, 0, 0, mask
, ShapeSet
);
316 XFreePixmap(dpy
, mask
);
317 XMoveWindow(dpy
, scr
->balloon
->window
, bx
, by
);
318 XMapRaised(dpy
, scr
->balloon
->window
);
320 scr
->balloon
->mapped
= 1;
322 #else /* !SHAPED_BALLOON */
324 static void showText(WScreen
*scr
, int x
, int y
, int h
, int w
, const char *text
)
329 WMFont
*font
= scr
->info_text_font
;
331 if (scr
->balloon
->contents
)
332 XFreePixmap(dpy
, scr
->balloon
->contents
);
334 width
= getMaxStringWidth(font
, text
) + 8;
335 /*width = WMWidthOfString(font, text, strlen(text))+8; */
336 height
= countLines(text
) * WMFontHeight(font
) + 4;
340 else if (x
+ width
> scr
->scr_width
- 1)
341 x
= scr
->scr_width
- width
;
343 if (y
- height
- 2 < 0) {
351 if (scr
->window_title_texture
[0])
352 XSetForeground(dpy
, scr
->draw_gc
, scr
->window_title_texture
[0]->any
.color
.pixel
);
354 XSetForeground(dpy
, scr
->draw_gc
, scr
->light_pixel
);
356 pixmap
= XCreatePixmap(dpy
, scr
->root_win
, width
, height
, scr
->w_depth
);
357 XFillRectangle(dpy
, pixmap
, scr
->draw_gc
, 0, 0, width
, height
);
359 drawMultiLineString(scr
->wmscreen
, pixmap
, scr
->window_title_color
[0], font
, 4, 2, text
, strlen(text
));
361 XResizeWindow(dpy
, scr
->balloon
->window
, width
, height
);
362 XMoveWindow(dpy
, scr
->balloon
->window
, x
, y
);
364 XSetWindowBackgroundPixmap(dpy
, scr
->balloon
->window
, pixmap
);
365 XClearWindow(dpy
, scr
->balloon
->window
);
366 XMapRaised(dpy
, scr
->balloon
->window
);
368 scr
->balloon
->contents
= pixmap
;
370 scr
->balloon
->mapped
= 1;
372 #endif /* !SHAPED_BALLOON */
374 static void show_minipreview(WScreen
*scr
, int x
, int y
, const char *title
, Pixmap mini_preview
)
377 WMFont
*font
= scr
->info_text_font
;
382 if (scr
->balloon
->contents
)
383 XFreePixmap(dpy
, scr
->balloon
->contents
);
385 width
= wPreferences
.minipreview_size
;
386 height
= wPreferences
.minipreview_size
;
388 if (wPreferences
.miniwin_title_balloon
) {
389 shortenTitle
= ShrinkString(font
, title
, width
- MINIPREVIEW_BORDER
* 2);
390 titleHeight
= countLines(shortenTitle
) * WMFontHeight(font
) + 4;
391 height
+= titleHeight
;
399 else if (x
+ width
> scr
->scr_width
- 1)
400 x
= scr
->scr_width
- width
- 1;
402 if (y
- height
- 2 < 0) {
403 y
+= wPreferences
.icon_size
;
410 if (scr
->window_title_texture
[0])
411 XSetForeground(dpy
, scr
->draw_gc
, scr
->window_title_texture
[0]->any
.color
.pixel
);
413 XSetForeground(dpy
, scr
->draw_gc
, scr
->light_pixel
);
415 pixmap
= XCreatePixmap(dpy
, scr
->root_win
, width
, height
, scr
->w_depth
);
416 XFillRectangle(dpy
, pixmap
, scr
->draw_gc
, 0, 0, width
, height
);
418 if (shortenTitle
!= NULL
) {
419 drawMultiLineString(scr
->wmscreen
, pixmap
, scr
->window_title_color
[0], font
,
420 MINIPREVIEW_BORDER
, MINIPREVIEW_BORDER
, shortenTitle
, strlen(shortenTitle
));
424 XCopyArea(dpy
, mini_preview
, pixmap
, scr
->draw_gc
,
425 0, 0, (wPreferences
.minipreview_size
- 1 - MINIPREVIEW_BORDER
* 2),
426 (wPreferences
.minipreview_size
- 1 - MINIPREVIEW_BORDER
* 2),
427 MINIPREVIEW_BORDER
, MINIPREVIEW_BORDER
+ titleHeight
);
429 #ifdef SHAPED_BALLOON
430 XShapeCombineMask(dpy
, scr
->balloon
->window
, ShapeBounding
, 0, 0, None
, ShapeSet
);
432 XResizeWindow(dpy
, scr
->balloon
->window
, width
, height
);
433 XMoveWindow(dpy
, scr
->balloon
->window
, x
, y
);
435 XSetWindowBackgroundPixmap(dpy
, scr
->balloon
->window
, pixmap
);
437 XClearWindow(dpy
, scr
->balloon
->window
);
438 XMapRaised(dpy
, scr
->balloon
->window
);
441 scr
->balloon
->contents
= pixmap
;
443 scr
->balloon
->mapped
= 1;
446 static void showBalloon(WScreen
*scr
)
452 scr
->balloon
->timer
= NULL
;
453 scr
->balloon
->ignoreTimer
= 1;
455 if (!XGetGeometry(dpy
, scr
->balloon
->objectWindow
, &foow
, &x
, &y
, &w
, &foo
, &foo
, &foo
)) {
456 scr
->balloon
->prevType
= 0;
460 if (wPreferences
.miniwin_preview_balloon
&& scr
->balloon
->mini_preview
!= None
)
461 /* used to display either the mini-preview alone or the mini-preview with the title */
462 show_minipreview(scr
, x
, y
, scr
->balloon
->text
, scr
->balloon
->mini_preview
);
464 showText(scr
, x
, y
, scr
->balloon
->h
, w
, scr
->balloon
->text
);
467 static void frameBalloon(WObjDescriptor
*object
)
469 WFrameWindow
*fwin
= (WFrameWindow
*) object
->parent
;
470 WScreen
*scr
= fwin
->core
->screen_ptr
;
472 if (fwin
->titlebar
!= object
->self
|| !fwin
->flags
.is_client_window_frame
) {
476 if (fwin
->title
&& fwin
->flags
.incomplete_title
) {
477 scr
->balloon
->h
= (fwin
->titlebar
? fwin
->titlebar
->height
: 0);
478 scr
->balloon
->text
= wstrdup(fwin
->title
);
479 scr
->balloon
->objectWindow
= fwin
->core
->window
;
480 scr
->balloon
->timer
= WMAddTimerHandler(BALLOON_DELAY
, (WMCallback
*) showBalloon
, scr
);
484 static void miniwindowBalloon(WObjDescriptor
*object
)
486 WIcon
*icon
= (WIcon
*) object
->parent
;
487 WScreen
*scr
= icon
->core
->screen_ptr
;
489 if (!icon
->icon_name
) {
493 scr
->balloon
->h
= icon
->core
->height
;
494 scr
->balloon
->text
= wstrdup(icon
->icon_name
);
495 scr
->balloon
->mini_preview
= icon
->mini_preview
;
496 scr
->balloon
->objectWindow
= icon
->core
->window
;
498 if ((scr
->balloon
->prevType
== object
->parent_type
|| scr
->balloon
->prevType
== WCLASS_APPICON
)
499 && scr
->balloon
->ignoreTimer
) {
500 XUnmapWindow(dpy
, scr
->balloon
->window
);
503 scr
->balloon
->timer
= WMAddTimerHandler(BALLOON_DELAY
, (WMCallback
*) showBalloon
, scr
);
507 static void appiconBalloon(WObjDescriptor
*object
)
509 WAppIcon
*aicon
= (WAppIcon
*) object
->parent
;
510 WScreen
*scr
= aicon
->icon
->core
->screen_ptr
;
513 /* Show balloon if it is the Clip and the workspace name is > 5 chars */
514 if (object
->parent
== scr
->clip_icon
) {
515 if (strlen(scr
->workspaces
[scr
->current_workspace
]->name
) > 5) {
516 scr
->balloon
->text
= wstrdup(scr
->workspaces
[scr
->current_workspace
]->name
);
521 } else if (aicon
->command
&& aicon
->wm_class
) {
524 unsigned int app_win_cnt
= 0;
525 const char *display_name
;
527 if (object
->parent_type
== WCLASS_DOCK_ICON
) {
528 if (aicon
->main_window
) {
529 app
= wApplicationOf(aicon
->main_window
);
530 if (app
&& app
->main_window_desc
&& app
->main_window_desc
->fake_group
)
531 app_win_cnt
= app
->main_window_desc
->fake_group
->retainCount
- 1;
536 * Check to see if it is a GNUstep app, because in this case we use the instance
537 * instead of the class, otherwise the user will not be able to distinguish what
540 if (strcmp(aicon
->wm_class
, "GNUstep") == 0)
541 display_name
= aicon
->wm_instance
;
543 display_name
= aicon
->wm_class
;
545 len
= strlen(aicon
->command
) + strlen(display_name
) + 8;
548 len
+= 1 + snprintf(NULL
, 0, "%u", app_win_cnt
);
552 snprintf(tmp
, len
, "%u %s\n(%s)", app_win_cnt
, display_name
, aicon
->command
);
554 snprintf(tmp
, len
, "%s\n(%s)", aicon
->wm_instance
, aicon
->command
);
555 scr
->balloon
->text
= tmp
;
557 } else if (aicon
->command
) {
558 scr
->balloon
->text
= wstrdup(aicon
->command
);
559 } else if (aicon
->wm_class
) {
560 /* Check to see if it is a GNUstep App */
561 if (strcmp(aicon
->wm_class
, "GNUstep") == 0)
562 scr
->balloon
->text
= wstrdup(aicon
->wm_instance
);
564 scr
->balloon
->text
= wstrdup(aicon
->wm_class
);
569 scr
->balloon
->h
= aicon
->icon
->core
->height
- 2;
571 scr
->balloon
->objectWindow
= aicon
->icon
->core
->window
;
572 if ((scr
->balloon
->prevType
== object
->parent_type
|| scr
->balloon
->prevType
== WCLASS_MINIWINDOW
)
573 && scr
->balloon
->ignoreTimer
) {
574 XUnmapWindow(dpy
, scr
->balloon
->window
);
577 scr
->balloon
->timer
= WMAddTimerHandler(BALLOON_DELAY
, (WMCallback
*) showBalloon
, scr
);
581 void wBalloonInitialize(WScreen
*scr
)
584 XSetWindowAttributes attribs
;
587 bal
= wmalloc(sizeof(WBalloon
));
591 vmask
= CWSaveUnder
| CWOverrideRedirect
| CWColormap
| CWBackPixel
| CWBorderPixel
;
592 attribs
.save_under
= True
;
593 attribs
.override_redirect
= True
;
594 attribs
.colormap
= scr
->w_colormap
;
595 attribs
.background_pixel
= scr
->icon_back_texture
->normal
.pixel
;
596 attribs
.border_pixel
= 0; /* do not care */
598 bal
->window
= XCreateWindow(dpy
, scr
->root_win
, 1, 1, 10, 10, 1,
599 scr
->w_depth
, CopyFromParent
, scr
->w_visual
, vmask
, &attribs
);
601 /* select EnterNotify to so that the balloon will be unmapped
602 * when the pointer is moved over it */
603 XSelectInput(dpy
, bal
->window
, EnterWindowMask
);
607 void wBalloonEnteredObject(WScreen
*scr
, WObjDescriptor
*object
)
609 WBalloon
*balloon
= scr
->balloon
;
611 if (balloon
->timer
) {
612 WMDeleteTimerHandler(balloon
->timer
);
613 balloon
->timer
= NULL
;
614 balloon
->ignoreTimer
= 0;
617 if (scr
->balloon
->text
)
618 wfree(scr
->balloon
->text
);
619 scr
->balloon
->text
= NULL
;
621 scr
->balloon
->mini_preview
= None
;
625 balloon
->ignoreTimer
= 0;
629 switch (object
->parent_type
) {
631 if (wPreferences
.window_balloon
)
632 frameBalloon(object
);
634 case WCLASS_DOCK_ICON
:
635 if (wPreferences
.appicon_balloon
)
636 appiconBalloon(object
);
638 case WCLASS_MINIWINDOW
:
639 if (wPreferences
.miniwin_title_balloon
|| wPreferences
.miniwin_preview_balloon
)
640 miniwindowBalloon(object
);
643 if (wPreferences
.appicon_balloon
)
644 appiconBalloon(object
);
650 scr
->balloon
->prevType
= object
->parent_type
;
653 void wBalloonHide(WScreen
*scr
)
656 if (scr
->balloon
->mapped
) {
657 XUnmapWindow(dpy
, scr
->balloon
->window
);
658 scr
->balloon
->mapped
= 0;
659 } else if (scr
->balloon
->timer
) {
660 WMDeleteTimerHandler(scr
->balloon
->timer
);
661 scr
->balloon
->timer
= NULL
;
663 scr
->balloon
->prevType
= 0;