1 /* ghosd -- OSD with fake transparency, cairo, and pango.
2  * Copyright (C) 2006 Evan Martin <martine@danga.com>
3  *
4  * With further development by Giacomo Lozito <james@develia.org>
5  * for the ghosd-based Audacious OSD
6  * - added real transparency with X Composite Extension
7  * - added mouse event handling on OSD window
8  * - added/changed some other stuff
9  */
10 
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <cairo/cairo-xlib-xrender.h>
14 #include <X11/Xlib.h>
15 #include <X11/Xatom.h>
16 
17 #include <X11/extensions/Xcomposite.h>
18 
19 #include <glib.h>
20 
21 #include "ghosd.h"
22 #include "ghosd-internal.h"
23 
24 static Bool
composite_find_manager(Display * dpy,int scr)25 composite_find_manager(Display *dpy, int scr)
26 {
27   Atom comp_manager_atom;
28   char comp_manager_hint[32];
29   Window win;
30 
31   snprintf(comp_manager_hint, 32, "_NET_WM_CM_S%d", scr);
32   comp_manager_atom = XInternAtom(dpy, comp_manager_hint, False);
33   win = XGetSelectionOwner(dpy, comp_manager_atom);
34 
35   if (win != None)
36   {
37     return True;
38   }
39   else
40   {
41     return False;
42   }
43 }
44 
45 static Visual *
composite_find_argb_visual(Display * dpy,int scr)46 composite_find_argb_visual(Display *dpy, int scr)
47 {
48   XVisualInfo	*xvi;
49   XVisualInfo	template;
50   int nvi, i;
51   XRenderPictFormat *format;
52   Visual *visual;
53 
54   template.screen = scr;
55   template.depth = 32;
56   template.class = TrueColor;
57   xvi = XGetVisualInfo (dpy,
58           VisualScreenMask | VisualDepthMask | VisualClassMask,
59           &template, &nvi);
60   if (xvi == NULL)
61     return NULL;
62 
63   visual = NULL;
64   for (i = 0; i < nvi; i++)
65   {
66     format = XRenderFindVisualFormat (dpy, xvi[i].visual);
67     if (format->type == PictTypeDirect && format->direct.alphaMask)
68     {
69       visual = xvi[i].visual;
70       break;
71     }
72   }
73   XFree (xvi);
74 
75   return visual;
76 }
77 
78 static Pixmap
take_snapshot(Ghosd * ghosd)79 take_snapshot(Ghosd *ghosd) {
80   Pixmap pixmap;
81   GC gc;
82 
83   /* create a pixmap to hold the screenshot. */
84   pixmap = XCreatePixmap(ghosd->dpy, ghosd->win,
85                          ghosd->width, ghosd->height,
86                          DefaultDepth(ghosd->dpy, DefaultScreen(ghosd->dpy)));
87 
88   /* then copy the screen into the pixmap. */
89   gc = XCreateGC(ghosd->dpy, pixmap, 0, NULL);
90   XSetSubwindowMode(ghosd->dpy, gc, IncludeInferiors);
91   XCopyArea(ghosd->dpy, DefaultRootWindow(ghosd->dpy), pixmap, gc,
92             ghosd->x, ghosd->y, ghosd->width, ghosd->height,
93             0, 0);
94   XSetSubwindowMode(ghosd->dpy, gc, ClipByChildren);
95   XFreeGC(ghosd->dpy, gc);
96 
97   return pixmap;
98 }
99 
100 void
ghosd_render(Ghosd * ghosd)101 ghosd_render(Ghosd *ghosd) {
102   Pixmap pixmap;
103   GC gc;
104 
105   if (ghosd->composite)
106   {
107     pixmap = XCreatePixmap(ghosd->dpy, ghosd->win, ghosd->width, ghosd->height, 32);
108     gc = XCreateGC(ghosd->dpy, pixmap, 0, NULL);
109     XFillRectangle(ghosd->dpy, pixmap, gc,
110       0, 0, ghosd->width, ghosd->height);
111   }
112   else
113   {
114     pixmap = XCreatePixmap(ghosd->dpy, ghosd->win, ghosd->width, ghosd->height,
115       DefaultDepth(ghosd->dpy, DefaultScreen(ghosd->dpy)));
116     gc = XCreateGC(ghosd->dpy, pixmap, 0, NULL);
117     if (ghosd->transparent) {
118       /* make our own copy of the background pixmap as the initial surface. */
119       XCopyArea(ghosd->dpy, ghosd->background.pixmap, pixmap, gc,
120         0, 0, ghosd->width, ghosd->height, 0, 0);
121     } else {
122       XFillRectangle(ghosd->dpy, pixmap, gc,
123         0, 0, ghosd->width, ghosd->height);
124     }
125   }
126   XFreeGC(ghosd->dpy, gc);
127 
128   /* render with cairo. */
129   if (ghosd->render.func) {
130     /* create cairo surface using the pixmap. */
131     XRenderPictFormat *xrformat;
132     cairo_surface_t *surf;
133     if (ghosd->composite) {
134       xrformat = XRenderFindVisualFormat(ghosd->dpy, ghosd->visual);
135       surf = cairo_xlib_surface_create_with_xrender_format(
136                ghosd->dpy, pixmap,
137                ScreenOfDisplay(ghosd->dpy, ghosd->screen_num),
138                xrformat, ghosd->width, ghosd->height);
139     } else {
140       xrformat = XRenderFindVisualFormat(ghosd->dpy,
141                    DefaultVisual(ghosd->dpy, DefaultScreen(ghosd->dpy)));
142       surf = cairo_xlib_surface_create_with_xrender_format(
143                ghosd->dpy, pixmap,
144                ScreenOfDisplay(ghosd->dpy, DefaultScreen(ghosd->dpy)),
145                xrformat, ghosd->width, ghosd->height);
146     }
147 
148     /* draw some stuff. */
149     cairo_t *cr = cairo_create(surf);
150     ghosd->render.func(ghosd, cr, ghosd->render.data);
151     cairo_destroy(cr);
152     cairo_surface_destroy(surf);
153   }
154 
155   /* point window at its new backing pixmap. */
156   XSetWindowBackgroundPixmap(ghosd->dpy, ghosd->win, pixmap);
157   /* I think it's ok to free it here because XCreatePixmap(3X11) says: "the X
158    * server frees the pixmap storage when there are no references to it".
159    */
160   XFreePixmap(ghosd->dpy, pixmap);
161 
162   /* and tell the window to redraw with this pixmap. */
163   XClearWindow(ghosd->dpy, ghosd->win);
164 }
165 
166 static void
set_hints(Display * dpy,Window win)167 set_hints(Display *dpy, Window win) {
168   XClassHint *classhints;
169   char *res_class = "Audacious";
170   char *res_name = "aosd";
171 
172   /* we're almost a _NET_WM_WINDOW_TYPE_SPLASH, but we don't want
173    * to be centered on the screen.  instead, manually request the
174    * behavior we want. */
175 
176   /* turn off window decorations.
177    * we could pull this in from a motif header, but it's easier to
178    * use this snippet i found on a mailing list.  */
179   Atom mwm_hints = XInternAtom(dpy, "_MOTIF_WM_HINTS", False);
180 #define MWM_HINTS_DECORATIONS (1<<1)
181   struct {
182     long flags, functions, decorations, input_mode;
183   } mwm_hints_setting = {
184     MWM_HINTS_DECORATIONS, 0, 0, 0
185   };
186   XChangeProperty(dpy, win,
187     mwm_hints, mwm_hints, 32, PropModeReplace,
188     (unsigned char *)&mwm_hints_setting, 4);
189 
190   /* always on top, not in taskbar or pager. */
191   Atom win_state = XInternAtom(dpy, "_NET_WM_STATE", False);
192   Atom win_state_setting[] = {
193     XInternAtom(dpy, "_NET_WM_STATE_ABOVE", False),
194     XInternAtom(dpy, "_NET_WM_STATE_SKIP_TASKBAR", False),
195     XInternAtom(dpy, "_NET_WM_STATE_SKIP_PAGER", False)
196   };
197   XChangeProperty(dpy, win, win_state, XA_ATOM, 32,
198                   PropModeReplace, (unsigned char*)&win_state_setting, 3);
199 
200   /* give initial pos/size information to window manager
201      about the window, this prevents flickering */
202   /* NOTE: unneeded if override_redirect is set to True
203   sizehints = XAllocSizeHints();
204   sizehints->flags = USPosition | USSize;
205   sizehints->x = -1;
206   sizehints->y = -1;
207   sizehints->width = 1;
208   sizehints->height = 1;
209   XSetWMNormalHints(dpy, win, sizehints);
210   XFree( sizehints );*/
211 
212   classhints = XAllocClassHint();
213   classhints->res_name = res_name;
214   classhints->res_class = res_class;
215   XSetClassHint(dpy, win, classhints);
216   XFree( classhints );
217 }
218 
219 static Window
make_window(Display * dpy,Window root_win,Visual * visual,Colormap colormap,Bool use_argbvisual)220 make_window(Display *dpy, Window root_win, Visual *visual, Colormap colormap, Bool use_argbvisual) {
221   Window win;
222   XSetWindowAttributes att;
223 
224   att.backing_store = WhenMapped;
225   att.background_pixel = 0x0;
226   att.border_pixel = 0;
227   att.background_pixmap = None;
228   att.save_under = True;
229   att.event_mask = ExposureMask | StructureNotifyMask | ButtonPressMask;
230   att.override_redirect = True;
231 
232   if ( use_argbvisual )
233   {
234     att.colormap = colormap;
235     win = XCreateWindow(dpy, root_win,
236                       -1, -1, 1, 1, 0, 32, InputOutput, visual,
237                       CWBackingStore | CWBackPixel | CWBackPixmap | CWBorderPixel |
238                       CWColormap | CWEventMask | CWSaveUnder | CWOverrideRedirect,
239                       &att);
240   } else {
241     win = XCreateWindow(dpy, root_win,
242                       -1, -1, 1, 1, 0, CopyFromParent, InputOutput, CopyFromParent,
243                       CWBackingStore | CWBackPixel | CWBackPixmap | CWBorderPixel |
244                       CWEventMask | CWSaveUnder | CWOverrideRedirect,
245                       &att);
246   }
247 
248   set_hints(dpy, win);
249 
250   return win;
251 }
252 
253 void
ghosd_show(Ghosd * ghosd)254 ghosd_show(Ghosd *ghosd) {
255   if ((!ghosd->composite) && (ghosd->transparent)) {
256     if (ghosd->background.set)
257     {
258       XFreePixmap(ghosd->dpy, ghosd->background.pixmap);
259       ghosd->background.set = 0;
260     }
261     ghosd->background.pixmap = take_snapshot(ghosd);
262     ghosd->background.set = 1;
263   }
264 
265   ghosd_render(ghosd);
266   XMapRaised(ghosd->dpy, ghosd->win);
267 }
268 
269 void
ghosd_hide(Ghosd * ghosd)270 ghosd_hide(Ghosd *ghosd) {
271   XUnmapWindow(ghosd->dpy, ghosd->win);
272 }
273 
274 void
ghosd_set_transparent(Ghosd * ghosd,int transparent)275 ghosd_set_transparent(Ghosd *ghosd, int transparent) {
276   ghosd->transparent = (transparent != 0);
277 }
278 
279 void
ghosd_set_render(Ghosd * ghosd,GhosdRenderFunc render_func,void * user_data,void (* user_data_d)(void *))280 ghosd_set_render(Ghosd *ghosd, GhosdRenderFunc render_func,
281                  void *user_data, void (*user_data_d)(void*)) {
282   ghosd->render.func = render_func;
283   ghosd->render.data = user_data;
284   ghosd->render.data_destroy = user_data_d;
285 }
286 
287 void
ghosd_set_position(Ghosd * ghosd,int x,int y,int width,int height)288 ghosd_set_position(Ghosd *ghosd, int x, int y, int width, int height) {
289   const int dpy_width  = DisplayWidth(ghosd->dpy,  DefaultScreen(ghosd->dpy));
290   const int dpy_height = DisplayHeight(ghosd->dpy, DefaultScreen(ghosd->dpy));
291 
292   if (x == GHOSD_COORD_CENTER) {
293     x = (dpy_width - width) / 2;
294   } else if (x < 0) {
295     x = dpy_width - width + x;
296   }
297 
298   if (y == GHOSD_COORD_CENTER) {
299     y = (dpy_height - height) / 2;
300   } else if (y < 0) {
301     y = dpy_height - height + y;
302   }
303 
304   ghosd->x      = x;
305   ghosd->y      = y;
306   ghosd->width  = width;
307   ghosd->height = height;
308 
309   XMoveResizeWindow(ghosd->dpy, ghosd->win,
310                     ghosd->x, ghosd->y, ghosd->width, ghosd->height);
311 }
312 
313 void
ghosd_set_event_button_cb(Ghosd * ghosd,GhosdEventButtonCb func,void * user_data)314 ghosd_set_event_button_cb(Ghosd *ghosd, GhosdEventButtonCb func, void *user_data)
315 {
316   ghosd->eventbutton.func = func;
317   ghosd->eventbutton.data = user_data;
318 }
319 
320 Ghosd*
ghosd_new(void)321 ghosd_new(void) {
322   Ghosd *ghosd;
323   Display *dpy;
324   Window win, root_win;
325   int screen_num;
326   Visual *visual;
327   Colormap colormap;
328   Bool use_argbvisual = False;
329 
330   dpy = XOpenDisplay(NULL);
331   if (dpy == NULL) {
332     fprintf(stderr, "Couldn't open display: (XXX FIXME)\n");
333     return NULL;
334   }
335 
336   screen_num = DefaultScreen(dpy);
337   root_win = RootWindow(dpy, screen_num);
338   visual = NULL; /* unused */
339   colormap = None; /* unused */
340 
341   win = make_window(dpy, root_win, visual, colormap, use_argbvisual);
342 
343   ghosd = g_new0(Ghosd, 1);
344   ghosd->dpy = dpy;
345   ghosd->visual = visual;
346   ghosd->colormap = colormap;
347   ghosd->win = win;
348   ghosd->root_win = root_win;
349   ghosd->screen_num = screen_num;
350   ghosd->transparent = 1;
351   ghosd->composite = 0;
352   ghosd->eventbutton.func = NULL;
353   ghosd->background.set = 0;
354 
355   return ghosd;
356 }
357 
358 Ghosd *
ghosd_new_with_argbvisual(void)359 ghosd_new_with_argbvisual(void) {
360   Ghosd *ghosd;
361   Display *dpy;
362   Window win, root_win;
363   int screen_num;
364   Visual *visual;
365   Colormap colormap;
366   Bool use_argbvisual = True;
367 
368   dpy = XOpenDisplay(NULL);
369   if (dpy == NULL) {
370     fprintf(stderr, "Couldn't open display: (XXX FIXME)\n");
371     return NULL;
372   }
373 
374   screen_num = DefaultScreen(dpy);
375   root_win = RootWindow(dpy, screen_num);
376   visual = composite_find_argb_visual(dpy, screen_num);
377   if (visual == NULL)
378     return NULL;
379   colormap = XCreateColormap(dpy, root_win, visual, AllocNone);
380 
381   win = make_window(dpy, root_win, visual, colormap, use_argbvisual);
382 
383   ghosd = g_new0(Ghosd, 1);
384   ghosd->dpy = dpy;
385   ghosd->visual = visual;
386   ghosd->colormap = colormap;
387   ghosd->win = win;
388   ghosd->root_win = root_win;
389   ghosd->screen_num = screen_num;
390   ghosd->transparent = 1;
391   ghosd->composite = 1;
392   ghosd->eventbutton.func = NULL;
393   ghosd->background.set = 0;
394 
395   return ghosd;
396 }
397 
398 int
ghosd_check_composite_ext(void)399 ghosd_check_composite_ext(void)
400 {
401   Display *dpy;
402   int have_composite_x = 0;
403   int composite_event_base = 0, composite_error_base = 0;
404 
405   dpy = XOpenDisplay(NULL);
406   if (dpy == NULL) {
407     fprintf(stderr, "Couldn't open display: (XXX FIXME)\n");
408     return 0;
409   }
410 
411   if (!XCompositeQueryExtension(dpy,
412         &composite_event_base, &composite_error_base))
413     have_composite_x = 0;
414   else
415     have_composite_x = 1;
416 
417   XCloseDisplay(dpy);
418   return have_composite_x;
419 }
420 
421 int
ghosd_check_composite_mgr(void)422 ghosd_check_composite_mgr(void)
423 {
424   Display *dpy;
425   int have_composite_m = 0;
426 
427   dpy = XOpenDisplay(NULL);
428   if (dpy == NULL) {
429     fprintf(stderr, "Couldn't open display: (XXX FIXME)\n");
430     return 0;
431   }
432 
433   if (!composite_find_manager(dpy, DefaultScreen(dpy)))
434     have_composite_m = 0;
435   else
436     have_composite_m = 1;
437 
438   XCloseDisplay(dpy);
439   return have_composite_m;
440 }
441 
442 void
ghosd_destroy(Ghosd * ghosd)443 ghosd_destroy(Ghosd* ghosd) {
444   if (ghosd->background.set)
445   {
446     XFreePixmap(ghosd->dpy, ghosd->background.pixmap);
447     ghosd->background.set = 0;
448   }
449   if (ghosd->composite)
450   {
451     XFreeColormap(ghosd->dpy, ghosd->colormap);
452   }
453   XDestroyWindow(ghosd->dpy, ghosd->win);
454   XCloseDisplay(ghosd->dpy);
455 }
456 
457 int
ghosd_get_socket(Ghosd * ghosd)458 ghosd_get_socket(Ghosd *ghosd) {
459   return ConnectionNumber(ghosd->dpy);
460 }
461 
462 /* vim: set ts=2 sw=2 et cino=(0 : */
463