1 /*
2 *
3 * Author: Giacomo Lozito <james@develia.org>, (C) 2005-2007
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 *
19 */
20
21 #include "aosd_osd.h"
22 #include "aosd_style.h"
23 #include "aosd_style_private.h"
24 #include "aosd_cfg.h"
25
26 #include <X11/Xlib.h>
27 #include <cairo/cairo.h>
28 #include <pango/pangocairo.h>
29 #include <gdk/gdk.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <sys/time.h>
33
34 #include <libaudcore/runtime.h>
35
36 #include "ghosd.h"
37
38
39 #define AOSD_STATUS_HIDDEN 0
40 #define AOSD_STATUS_FADEIN 1
41 #define AOSD_STATUS_SHOW 2
42 #define AOSD_STATUS_FADEOUT 3
43 #define AOSD_STATUS_DESTROY 4
44 /* updating OSD every 50 msec */
45 #define AOSD_TIMING 50
46
47
48 struct GhosdFadeData
49 {
50 cairo_surface_t * surface = nullptr;
51 float alpha = 0.0;
52 void * user_data = nullptr;
53 int width = 0;
54 int height = 0;
55 int deco_code = 0;
56
~GhosdFadeDataGhosdFadeData57 ~GhosdFadeData ()
58 {
59 if (surface)
60 cairo_surface_destroy (surface);
61 }
62 };
63
64
65 struct GhosdData
66 {
67 String markup_message;
68 bool cfg_is_copied = false;
69 float dalpha_in = 0.0, dalpha_out = 0.0, ddisplay_stay = 0.0;
70
71 PangoContext *pango_context = nullptr;
72 PangoLayout *pango_layout = nullptr;
73
74 aosd_cfg_t *cfg_osd = nullptr;
75
76 GhosdFadeData fade_data;
77
GhosdDataGhosdData78 GhosdData (const char * markup_string, aosd_cfg_t * cfg_osd, bool copy_cfg) :
79 markup_message (markup_string),
80 cfg_is_copied (copy_cfg),
81 cfg_osd (copy_cfg ? new aosd_cfg_t (* cfg_osd) : cfg_osd) {}
82
~GhosdDataGhosdData83 ~GhosdData ()
84 {
85 if (pango_layout)
86 g_object_unref (pango_layout);
87 if (pango_context)
88 g_object_unref (pango_context);
89 if (cfg_is_copied)
90 delete cfg_osd;
91 }
92 };
93
94
95 static int osd_source_id = 0;
96 static int osd_status = AOSD_STATUS_HIDDEN;
97 static Ghosd *osd;
98 static SmartPtr<GhosdData> osd_data;
99
100
101 static void
aosd_osd_hide(void)102 aosd_osd_hide ( void )
103 {
104 if ( osd != nullptr )
105 {
106 ghosd_hide( osd );
107 ghosd_main_iterations( osd );
108 }
109 return;
110 }
111
112
113 static void
aosd_fade_func(Ghosd * gosd,cairo_t * cr,void * user_data)114 aosd_fade_func ( Ghosd * gosd , cairo_t * cr , void * user_data )
115 {
116 GhosdFadeData *fade_data = (GhosdFadeData *) user_data;
117
118 if ( fade_data->surface == nullptr )
119 {
120 cairo_t *rendered_cr;
121 fade_data->surface = cairo_surface_create_similar( cairo_get_target( cr ) ,
122 CAIRO_CONTENT_COLOR_ALPHA , fade_data->width , fade_data->height );
123 rendered_cr = cairo_create( fade_data->surface );
124 aosd_deco_style_render( fade_data->deco_code , gosd , rendered_cr , fade_data->user_data );
125 cairo_destroy( rendered_cr );
126 }
127
128 cairo_set_source_surface( cr , fade_data->surface , 0 , 0 );
129 cairo_paint_with_alpha( cr , fade_data->alpha );
130 }
131
132
133 static void
aosd_button_func(Ghosd * gosd,GhosdEventButton * ev,void * user_data)134 aosd_button_func ( Ghosd * gosd , GhosdEventButton * ev , void * user_data )
135 {
136 if ( ev->button == 1 )
137 {
138 osd_status = AOSD_STATUS_DESTROY; /* move to status destroy */
139 }
140 return;
141 }
142
143
144 static void
aosd_osd_create(void)145 aosd_osd_create ( void )
146 {
147 int max_width, layout_width, layout_height;
148 PangoRectangle ink, log;
149 GdkScreen *screen = gdk_screen_get_default();
150 int pos_x = 0, pos_y = 0;
151 int pad_left = 0 , pad_right = 0 , pad_top = 0 , pad_bottom = 0;
152 int screen_width, screen_height;
153 aosd_deco_style_data_t style_data;
154
155 /* calculate screen_width and screen_height */
156 if ( osd_data->cfg_osd->position.multimon_id > -1 )
157 {
158 /* adjust coordinates and size according to selected monitor */
159 GdkRectangle rect;
160 gdk_screen_get_monitor_geometry( screen , osd_data->cfg_osd->position.multimon_id , &rect );
161 pos_x = rect.x;
162 pos_y = rect.y;
163 screen_width = rect.width;
164 screen_height = rect.height;
165 }
166 else
167 {
168 /* use total space available, even when composed by multiple monitor */
169 screen_width = gdk_screen_get_width( screen );
170 screen_height = gdk_screen_get_height( screen );
171 pos_x = 0;
172 pos_y = 0;
173 }
174
175 /* pick padding from selected decoration style */
176 aosd_deco_style_get_padding( osd_data->cfg_osd->decoration.code ,
177 &pad_top , &pad_bottom , &pad_left , &pad_right );
178
179 if ( osd_data->cfg_osd->position.maxsize_width > 0 )
180 {
181 int max_width_default = screen_width - pad_left - pad_right - abs(osd_data->cfg_osd->position.offset_x);
182 max_width = osd_data->cfg_osd->position.maxsize_width - pad_left - pad_right;
183 /* ignore user-defined max_width if it is too small or too large */
184 if (( max_width < 1 ) || ( max_width > max_width_default ))
185 max_width = max_width_default;
186 }
187 else
188 {
189 max_width = screen_width - pad_left - pad_right - abs(osd_data->cfg_osd->position.offset_x);
190 }
191
192 osd_data->pango_context = pango_font_map_create_context
193 (pango_cairo_font_map_get_default ());
194 osd_data->pango_layout = pango_layout_new(osd_data->pango_context);
195 pango_layout_set_markup( osd_data->pango_layout, osd_data->markup_message , -1 );
196 pango_layout_set_ellipsize( osd_data->pango_layout , PANGO_ELLIPSIZE_NONE );
197 pango_layout_set_justify( osd_data->pango_layout , false );
198 pango_layout_set_width( osd_data->pango_layout , PANGO_SCALE * max_width );
199 pango_layout_get_pixel_extents( osd_data->pango_layout , &ink , &log );
200 layout_width = ink.width;
201 layout_height = log.height;
202
203 /* osd position */
204 switch ( osd_data->cfg_osd->position.placement )
205 {
206 case AOSD_POSITION_PLACEMENT_TOP:
207 pos_x += (screen_width - (layout_width + pad_left + pad_right)) / 2;
208 pos_y += 0;
209 break;
210 case AOSD_POSITION_PLACEMENT_TOPRIGHT:
211 pos_x += screen_width - (layout_width + pad_left + pad_right);
212 pos_y += 0;
213 break;
214 case AOSD_POSITION_PLACEMENT_MIDDLELEFT:
215 pos_x += 0;
216 pos_y += (screen_height - (layout_height + pad_top + pad_bottom)) / 2;
217 break;
218 case AOSD_POSITION_PLACEMENT_MIDDLE:
219 pos_x += (screen_width - (layout_width + pad_left + pad_right)) / 2;
220 pos_y += (screen_height - (layout_height + pad_top + pad_bottom)) / 2;
221 break;
222 case AOSD_POSITION_PLACEMENT_MIDDLERIGHT:
223 pos_x += screen_width - (layout_width + pad_left + pad_right);
224 pos_y += (screen_height - (layout_height + pad_top + pad_bottom)) / 2;
225 break;
226 case AOSD_POSITION_PLACEMENT_BOTTOMLEFT:
227 pos_x += 0;
228 pos_y += screen_height - (layout_height + pad_top + pad_bottom);
229 break;
230 case AOSD_POSITION_PLACEMENT_BOTTOM:
231 pos_x += (screen_width - (layout_width + pad_left + pad_right)) / 2;
232 pos_y += screen_height - (layout_height + pad_top + pad_bottom);
233 break;
234 case AOSD_POSITION_PLACEMENT_BOTTOMRIGHT:
235 pos_x += screen_width - (layout_width + pad_left + pad_right);
236 pos_y += screen_height - (layout_height + pad_top + pad_bottom);
237 break;
238 case AOSD_POSITION_PLACEMENT_TOPLEFT:
239 default:
240 pos_x += 0;
241 pos_y += 0;
242 break;
243 }
244
245 /* add offset to position */
246 pos_x += osd_data->cfg_osd->position.offset_x;
247 pos_y += osd_data->cfg_osd->position.offset_y;
248
249 ghosd_set_position( osd , pos_x , pos_y ,
250 layout_width + pad_left + pad_right ,
251 layout_height + pad_top + pad_bottom );
252
253 ghosd_set_event_button_cb( osd , aosd_button_func , nullptr );
254
255 style_data.layout = osd_data->pango_layout;
256 style_data.text = &(osd_data->cfg_osd->text);
257 style_data.decoration = &(osd_data->cfg_osd->decoration);
258 osd_data->fade_data.surface = nullptr;
259 osd_data->fade_data.user_data = &style_data;
260 osd_data->fade_data.width = layout_width + pad_left + pad_right;
261 osd_data->fade_data.height = layout_height + pad_top + pad_bottom;
262 osd_data->fade_data.alpha = 0;
263 osd_data->fade_data.deco_code = osd_data->cfg_osd->decoration.code;
264 osd_data->dalpha_in = 1.0 / ( osd_data->cfg_osd->animation.timing_fadein / (float)AOSD_TIMING );
265 osd_data->dalpha_out = 1.0 / ( osd_data->cfg_osd->animation.timing_fadeout / (float)AOSD_TIMING );
266 osd_data->ddisplay_stay = 1.0 / ( osd_data->cfg_osd->animation.timing_display / (float)AOSD_TIMING );
267 ghosd_set_render( osd , (GhosdRenderFunc)aosd_fade_func , &(osd_data->fade_data) , nullptr );
268
269 /* show the osd (with alpha 0, invisible) */
270 ghosd_show( osd );
271 return;
272 }
273
274
275 static gboolean
aosd_timer_func(void * none)276 aosd_timer_func ( void * none )
277 {
278 static float display_time = 0;
279
280 switch ( osd_status )
281 {
282 case AOSD_STATUS_FADEIN:
283 {
284 /* fade in */
285 osd_data->fade_data.alpha += osd_data->dalpha_in;
286 if ( osd_data->fade_data.alpha < 1.0 )
287 {
288 ghosd_render( osd );
289 ghosd_main_iterations( osd );
290 }
291 else
292 {
293 osd_data->fade_data.alpha = 1.0;
294 display_time = 0;
295 osd_status = AOSD_STATUS_SHOW; /* move to next phase */
296 ghosd_render( osd );
297 ghosd_main_iterations( osd );
298 }
299 return true;
300 }
301
302 case AOSD_STATUS_SHOW:
303 {
304 display_time += osd_data->ddisplay_stay;
305 if ( display_time >= 1.0 )
306 {
307 osd_status = AOSD_STATUS_FADEOUT; /* move to next phase */
308 ghosd_main_iterations( osd );
309 }
310 else
311 {
312 ghosd_main_iterations( osd );
313 }
314 return true;
315 }
316
317 case AOSD_STATUS_FADEOUT:
318 {
319 /* fade out */
320 osd_data->fade_data.alpha -= osd_data->dalpha_out;
321 if ( osd_data->fade_data.alpha > 0.0 )
322 {
323 ghosd_render( osd );
324 ghosd_main_iterations( osd );
325 }
326 else
327 {
328 osd_data->fade_data.alpha = 0.0;
329 osd_status = AOSD_STATUS_DESTROY; /* move to next phase */
330 ghosd_render( osd );
331 ghosd_main_iterations( osd );
332 }
333 return true;
334 }
335
336 case AOSD_STATUS_DESTROY:
337 {
338 aosd_osd_hide();
339 osd_data.clear();
340
341 osd_status = AOSD_STATUS_HIDDEN; /* reset status */
342 osd_source_id = 0;
343 return false;
344 }
345 }
346
347 return true;
348 }
349
350
351 int
aosd_osd_display(char * markup_string,aosd_cfg_t * cfg_osd,bool copy_cfg)352 aosd_osd_display ( char * markup_string , aosd_cfg_t * cfg_osd , bool copy_cfg )
353 {
354 if ( osd != nullptr )
355 {
356 if ( osd_status == AOSD_STATUS_HIDDEN )
357 {
358 osd_data.capture( new GhosdData( markup_string , cfg_osd , copy_cfg ) );
359 aosd_osd_create();
360 osd_status = AOSD_STATUS_FADEIN;
361 osd_source_id = g_timeout_add_full( G_PRIORITY_DEFAULT_IDLE , AOSD_TIMING ,
362 aosd_timer_func , nullptr , nullptr );
363 }
364 else
365 {
366 g_source_remove( osd_source_id ); /* remove timer */
367 osd_source_id = 0;
368 aosd_osd_hide();
369 osd_data.clear();
370 osd_status = AOSD_STATUS_HIDDEN;
371 /* now display new OSD */
372 osd_data.capture( new GhosdData( markup_string , cfg_osd , copy_cfg ) );
373 aosd_osd_create();
374 osd_status = AOSD_STATUS_FADEIN;
375 osd_source_id = g_timeout_add_full( G_PRIORITY_DEFAULT_IDLE , AOSD_TIMING ,
376 aosd_timer_func , nullptr , nullptr );
377 }
378 return 0;
379 }
380 else
381 {
382 g_warning( "OSD display requested, but no osd object is loaded!\n" );
383 }
384 return 1;
385 }
386
387
388 void
aosd_osd_shutdown(void)389 aosd_osd_shutdown ( void )
390 {
391 if ( osd != nullptr )
392 {
393 if ( osd_status != AOSD_STATUS_HIDDEN ) /* osd is being displayed */
394 {
395 g_source_remove( osd_source_id ); /* remove timer */
396 osd_source_id = 0;
397 aosd_osd_hide();
398 osd_data.clear();
399 osd_status = AOSD_STATUS_HIDDEN;
400 }
401 }
402 else
403 {
404 g_warning( "OSD shutdown requested, but no osd object is loaded!\n" );
405 }
406 return;
407 }
408
409
410 void
aosd_osd_init(int transparency_mode)411 aosd_osd_init ( int transparency_mode )
412 {
413 if ( osd == nullptr )
414 {
415 /* create Ghosd object */
416 if ( transparency_mode == AOSD_MISC_TRANSPARENCY_FAKE )
417 osd = ghosd_new();
418 else
419 {
420 /* check if the composite module is actually loaded */
421 if ( aosd_osd_check_composite_ext() )
422 osd = ghosd_new_with_argbvisual(); /* ok */
423 else
424 {
425 g_warning( "X Composite module not loaded; falling back to fake transparency.\n");
426 osd = ghosd_new(); /* fall back to fake transparency */
427 }
428 }
429
430 if ( osd == nullptr )
431 g_warning( "Unable to load osd object; OSD will not work properly!\n" );
432 }
433 return;
434 }
435
436
437 void
aosd_osd_cleanup(void)438 aosd_osd_cleanup ( void )
439 {
440 if ( osd != nullptr )
441 {
442 /* destroy Ghosd object */
443 ghosd_destroy( osd );
444 osd = nullptr;
445 }
446 return;
447 }
448
449 int
aosd_osd_check_composite_ext(void)450 aosd_osd_check_composite_ext ( void )
451 {
452 return ghosd_check_composite_ext();
453 }
454
455 int
aosd_osd_check_composite_mgr(void)456 aosd_osd_check_composite_mgr ( void )
457 {
458 /* ghosd_check_composite_mgr() only checks for composite managers that
459 adhere to the Extended Window Manager hint specification ver.1.4 from
460 freedesktop ( where composite manager are identified with the hint
461 _NET_WM_CM_Sn ); unfortunately, non-standard comp.managers and older
462 ones (xcompmgr) do not use this hint; so let's also check if xcompmgr
463 is running before reporting a likely absence of running comp.managers */
464 int have_comp_mgr = ghosd_check_composite_mgr();
465
466 if ( have_comp_mgr != 0 )
467 {
468 AUDDBG("running composite manager found\n");
469 return have_comp_mgr;
470 }
471 else
472 {
473 /* check if xcompmgr is running; assumes there's a working 'ps'
474 utility in the system; not the most elegant of the checking
475 systems, but this is more than enough for its purpose */
476 char *soutput = nullptr, *serror = nullptr;
477 int exit_status;
478
479 if ( g_spawn_command_line_sync( "ps -eo comm" ,
480 &soutput , &serror , &exit_status , nullptr ) == true )
481 {
482 if (( soutput != nullptr ) && ( strstr( soutput , "\nxcompmgr\n" ) != nullptr ))
483 {
484 AUDDBG("running xcompmgr found\n");
485 have_comp_mgr = 1;
486 }
487 else
488 {
489 AUDDBG("running xcompmgr not found\n");
490 have_comp_mgr = 0;
491 }
492 }
493 else
494 {
495 g_warning("command 'ps -eo comm' failed, unable to check if xcompgr is running\n");
496 have_comp_mgr = 0;
497 }
498
499 g_free( soutput );
500 g_free( serror );
501 return have_comp_mgr;
502 }
503 }
504