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