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 <sys/time.h>
14 #include <sys/poll.h>
15 #include <string.h>
16 #include <time.h>
17 #include <unistd.h>
18 #include <errno.h>
19 #include <gdk/gdk.h>
20 
21 #include "ghosd.h"
22 #include "ghosd-internal.h"
23 
24 static void
ghosd_main_iteration(Ghosd * ghosd)25 ghosd_main_iteration(Ghosd *ghosd) {
26   XEvent ev, pev;
27   XNextEvent(ghosd->dpy, &ev);
28 
29   /* smash multiple configure/exposes into one. */
30   if (ev.type == ConfigureNotify) {
31     while (XPending(ghosd->dpy)) {
32       XPeekEvent(ghosd->dpy, &pev);
33       if (pev.type != ConfigureNotify && pev.type != Expose)
34         break;
35       XNextEvent(ghosd->dpy, &ev);
36     }
37   }
38 
39   switch (ev.type) {
40   case Expose:
41     break;
42   case ConfigureNotify:
43     if (ghosd->width > 0) {
44       /* XXX if the window manager disagrees with our positioning here,
45        * we loop. */
46       if (ghosd->x != ev.xconfigure.x ||
47           ghosd->y != ev.xconfigure.y) {
48         /*width = ev.xconfigure.width;
49         height = ev.xconfigure.height;*/
50         XMoveResizeWindow(ghosd->dpy, ghosd->win,
51                           ghosd->x, ghosd->y, ghosd->width, ghosd->height);
52       }
53     }
54     break;
55   case ButtonPress:
56     /* create a GhosdEventButton event and pass it to callback function */
57     if ( ghosd->eventbutton.func != NULL )
58     {
59       GhosdEventButton gevb;
60       gevb.x = ev.xbutton.x;
61       gevb.y = ev.xbutton.y;
62       gevb.x_root = ev.xbutton.x_root;
63       gevb.y_root = ev.xbutton.y_root;
64       gevb.button = ev.xbutton.button;
65       gevb.send_event = ev.xbutton.send_event;
66       gevb.time = ev.xbutton.time;
67       ghosd->eventbutton.func( ghosd , &gevb , ghosd->eventbutton.data );
68     }
69     break;
70   }
71 }
72 
73 void
ghosd_main_iterations(Ghosd * ghosd)74 ghosd_main_iterations(Ghosd *ghosd) {
75   while (XPending(ghosd->dpy))
76     ghosd_main_iteration(ghosd);
77 }
78 
79 void
ghosd_main_until(Ghosd * ghosd,struct timeval * until)80 ghosd_main_until(Ghosd *ghosd, struct timeval *until) {
81   struct timeval tv_now;
82 
83   ghosd_main_iterations(ghosd);
84 
85   for (;;) {
86     gettimeofday(&tv_now, NULL);
87     int dt = (until->tv_sec  - tv_now.tv_sec )*1000 +
88              (until->tv_usec - tv_now.tv_usec)/1000;
89     if (dt <= 0) break;
90 
91     struct pollfd pollfd = { ghosd_get_socket(ghosd), POLLIN, 0 };
92     int ret = poll(&pollfd, 1, dt);
93     if (ret < 0) {
94       if ( errno != EINTR )
95       {
96         perror("poll");
97         exit(1);
98       }
99       /* else go on, ignore EINTR */
100     } else if (ret > 0) {
101       ghosd_main_iterations(ghosd);
102     } else {
103       /* timer expired. */
104       break;
105     }
106   }
107 }
108 
109 typedef struct {
110   cairo_surface_t* surface;
111   float alpha;
112   RenderCallback user_render;
113 } GhosdFlashData;
114 
115 static void
flash_render(Ghosd * ghosd,cairo_t * cr,void * data)116 flash_render(Ghosd *ghosd, cairo_t *cr, void* data) {
117   GhosdFlashData *flash = data;
118 
119   /* the first time we render, let the client render into their own surface. */
120   if (flash->surface == NULL) {
121     cairo_t *rendered_cr;
122     flash->surface = cairo_surface_create_similar(cairo_get_target(cr),
123                                                   CAIRO_CONTENT_COLOR_ALPHA,
124                                                   ghosd->width, ghosd->height);
125     rendered_cr = cairo_create(flash->surface);
126     flash->user_render.func(ghosd, rendered_cr, flash->user_render.data);
127     cairo_destroy(rendered_cr);
128   }
129 
130   /* now that we have a rendered surface, all we normally do is copy that to
131    * the screen. */
132   cairo_set_source_surface(cr, flash->surface, 0, 0);
133   cairo_paint_with_alpha(cr, flash->alpha);
134 }
135 
136 /* we don't need to free the flashdata object, because we stack-allocate that.
137  * but we do need to let the old user data free itself... */
138 static void
flash_destroy(void * data)139 flash_destroy(void *data) {
140   GhosdFlashData *flash = data;
141   if (flash->user_render.data_destroy)
142     flash->user_render.data_destroy(flash->user_render.data);
143 }
144 
145 void
ghosd_flash(Ghosd * ghosd,int fade_ms,int total_display_ms)146 ghosd_flash(Ghosd *ghosd, int fade_ms, int total_display_ms) {
147   GhosdFlashData flash = {0};
148   memcpy(&flash.user_render, &ghosd->render, sizeof(RenderCallback));
149   ghosd_set_render(ghosd, flash_render, &flash, flash_destroy);
150 
151   ghosd_show(ghosd);
152 
153   const int STEP_MS = 50;
154   const float dalpha = 1.0 / (fade_ms / (float)STEP_MS);
155   struct timeval tv_nextupdate;
156 
157   /* fade in. */
158   for (flash.alpha = 0; flash.alpha < 1.0; flash.alpha += dalpha) {
159     if (flash.alpha > 1.0) flash.alpha = 1.0;
160     ghosd_render(ghosd);
161 
162     gettimeofday(&tv_nextupdate, NULL);
163     tv_nextupdate.tv_usec += STEP_MS*1000;
164     ghosd_main_until(ghosd, &tv_nextupdate);
165   }
166 
167   /* full display. */
168   flash.alpha = 1.0;
169   ghosd_render(ghosd);
170 
171   gettimeofday(&tv_nextupdate, NULL);
172   tv_nextupdate.tv_usec += (total_display_ms - (2*fade_ms))*1000;
173   ghosd_main_until(ghosd, &tv_nextupdate);
174 
175   /* fade out. */
176   for (flash.alpha = 1.0; flash.alpha > 0.0; flash.alpha -= dalpha) {
177     ghosd_render(ghosd);
178 
179     gettimeofday(&tv_nextupdate, NULL);
180     tv_nextupdate.tv_usec += STEP_MS*1000;
181     ghosd_main_until(ghosd, &tv_nextupdate);
182   }
183 
184   flash.alpha = 0;
185   ghosd_render(ghosd);
186 
187   /* display for another half-second,
188    * because otherwise the fade out attracts your eye
189    * and then you'll see a flash while it repaints where the ghosd was.
190    */
191   gettimeofday(&tv_nextupdate, NULL);
192   tv_nextupdate.tv_usec += 500*1000;
193   ghosd_main_until(ghosd, &tv_nextupdate);
194 }
195 
196 /* vim: set ts=2 sw=2 et cino=(0 : */
197