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