view src/aosd/aosd_osd.c @ 906:16e51fb5908e trunk

[svn] - aosd: beta4, ghosd source was rewritten to support argb visual (x composite extension) as an option, this allows to have real transparency in the OSD
author giacomo
date Sat, 31 Mar 2007 17:44:23 -0700
parents e6d51d079a46
children f20ca9b8cd7d
line wrap: on
line source

/*
*
* Author: Giacomo Lozito <james@develia.org>, (C) 2005-2007
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
*
*/

#include "aosd_osd.h"
#include "aosd_style.h"
#include "aosd_style_private.h"
#include "aosd_cfg.h"
#include <glib.h>
#include <X11/Xlib.h>
#include <cairo/cairo.h>
#include <pango/pangocairo.h>
#include <gdk/gdk.h>
#include <stdlib.h>
#include <sys/time.h>
#include "ghosd.h"


#define AOSD_STATUS_HIDDEN       0
#define AOSD_STATUS_FADEIN       1
#define AOSD_STATUS_SHOW         2
#define AOSD_STATUS_FADEOUT      3
#define AOSD_STATUS_DESTROY      4
/* updating OSD every 50 msec */
#define AOSD_TIMING              50


typedef struct
{
  cairo_surface_t * surface;
  gfloat alpha;
  gpointer user_data;
  gint width;
  gint height;
  gint deco_code;
}
GhosdFadeData;


typedef struct
{
  gchar * markup_message;
  gboolean cfg_is_copied;
  gfloat dalpha_in, dalpha_out, ddisplay_stay;

  PangoContext *pango_context;
  PangoLayout *pango_layout;

  aosd_cfg_osd_t * cfg_osd;

  GhosdFadeData fade_data;
}
GhosdData;


static gint osd_source_id = 0;
static gint osd_status = AOSD_STATUS_HIDDEN;
static Ghosd *osd;
static GhosdData *osd_data;


static void
aosd_osd_data_alloc ( gchar * markup_string , aosd_cfg_osd_t * cfg_osd , gboolean copy_cfg )
{
  osd_data = g_malloc0(sizeof(GhosdData));
  osd_data->markup_message = g_strdup( markup_string );
  if ( copy_cfg == TRUE )
  {
    osd_data->cfg_osd = aosd_cfg_osd_copy( cfg_osd );
    osd_data->cfg_is_copied = TRUE;
  }
  else
  {
    osd_data->cfg_osd = cfg_osd;
    osd_data->cfg_is_copied = FALSE;
  }
  return;
}


static void
aosd_osd_data_free ( void )
{
  if ( osd_data->fade_data.surface != NULL )
  {
    cairo_surface_destroy( osd_data->fade_data.surface );
    osd_data->fade_data.surface = NULL;
  }

  if ( osd_data->markup_message != NULL )
  {
    g_free( osd_data->markup_message );
    osd_data->markup_message = NULL;
  }

  if ( osd_data->cfg_is_copied == TRUE )
  {
    aosd_cfg_osd_delete( osd_data->cfg_osd );
    osd_data->cfg_osd = NULL;
  }

  if ( osd_data->pango_layout != NULL )
  {
    g_object_unref( osd_data->pango_layout );
    osd_data->pango_layout = NULL;
  }

  if ( osd_data->pango_context != NULL )
  {
    g_object_unref( osd_data->pango_context );
    osd_data->pango_context = NULL;
  }

  g_free( osd_data );
}


static void
aosd_osd_hide ( void )
{
  if ( osd != NULL )
  {
    ghosd_hide( osd );
    ghosd_main_iterations( osd );
  }
  return;
}


static void
aosd_fade_func ( Ghosd * gosd , cairo_t * cr , void * user_data )
{
  GhosdFadeData *fade_data = user_data;

  if ( fade_data->surface == NULL )
  {
    cairo_t *rendered_cr;
    fade_data->surface = cairo_surface_create_similar( cairo_get_target( cr ) ,
                           CAIRO_CONTENT_COLOR_ALPHA , fade_data->width , fade_data->height );
    rendered_cr = cairo_create( fade_data->surface );
    aosd_deco_style_render( fade_data->deco_code , gosd , rendered_cr , fade_data->user_data );
    cairo_destroy( rendered_cr );
  }

  cairo_set_source_surface( cr , fade_data->surface , 0 , 0 );
  cairo_paint_with_alpha( cr , fade_data->alpha );
}


static void
aosd_button_func ( Ghosd * gosd , GhosdEventButton * ev , void * user_data )
{
  if ( ev->button == 1 )
  {
    osd_status = AOSD_STATUS_DESTROY; /* move to status destroy */
  }
  return;
}


static void
aosd_osd_create ( void )
{
  gint max_width, layout_width, layout_height;
  GdkScreen *screen = gdk_screen_get_default();
  gint pos_x = 0, pos_y = 0;
  gint pad_left = 0 , pad_right = 0 , pad_top = 0 , pad_bottom = 0;
  gint screen_width, screen_height;
  aosd_deco_style_data_t style_data;

  /* calculate screen_width and screen_height */
  if ( osd_data->cfg_osd->position.multimon_id > -1 )
  {
    /* adjust coordinates and size according to selected monitor */
    GdkRectangle rect;
    gdk_screen_get_monitor_geometry( screen , osd_data->cfg_osd->position.multimon_id , &rect );
    pos_x = rect.x;
    pos_y = rect.y;
    screen_width = rect.width;
    screen_height = rect.height;
  }
  else
  {
    /* use total space available, even when composed by multiple monitor */
    screen_width = gdk_screen_get_width( screen );
    screen_height = gdk_screen_get_height( screen );
    pos_x = 0;
    pos_y = 0;
  }

  /* pick padding from selected decoration style */
  aosd_deco_style_get_padding( osd_data->cfg_osd->decoration.code ,
    &pad_top , &pad_bottom , &pad_left , &pad_right );

  if ( osd_data->cfg_osd->position.maxsize_width > 0 )
  {
    gint max_width_default = screen_width - pad_left - pad_right - abs(osd_data->cfg_osd->position.offset_x);
    max_width = osd_data->cfg_osd->position.maxsize_width - pad_left - pad_right;
    /* ignore user-defined max_width if it is too small or too large */
    if (( max_width < 1 ) || ( max_width > max_width_default ))
      max_width = max_width_default;
  }
  else
  {
    max_width = screen_width - pad_left - pad_right - abs(osd_data->cfg_osd->position.offset_x);
  }
  osd_data->pango_context = pango_cairo_font_map_create_context(
                              PANGO_CAIRO_FONT_MAP(pango_cairo_font_map_get_default()));
  osd_data->pango_layout = pango_layout_new(osd_data->pango_context);
  pango_layout_set_markup( osd_data->pango_layout, osd_data->markup_message , -1 );
  pango_layout_set_ellipsize( osd_data->pango_layout , PANGO_ELLIPSIZE_NONE );
  pango_layout_set_justify( osd_data->pango_layout , FALSE );
  pango_layout_set_width( osd_data->pango_layout , PANGO_SCALE * max_width );
  pango_layout_get_pixel_size( osd_data->pango_layout , &layout_width , &layout_height );

  /* osd position */
  switch ( osd_data->cfg_osd->position.placement )
  {
    case AOSD_POSITION_PLACEMENT_TOP:
      pos_x += (screen_width - (layout_width + pad_left + pad_right)) / 2;
      pos_y += 0;
      break;
    case AOSD_POSITION_PLACEMENT_TOPRIGHT:
      pos_x += screen_width - (layout_width + pad_left + pad_right);
      pos_y += 0;
      break;
    case AOSD_POSITION_PLACEMENT_MIDDLELEFT:
      pos_x += 0;
      pos_y += (screen_height - (layout_height + pad_top + pad_bottom)) / 2;
      break;
    case AOSD_POSITION_PLACEMENT_MIDDLE:
      pos_x += (screen_width - (layout_width + pad_left + pad_right)) / 2;
      pos_y += (screen_height - (layout_height + pad_top + pad_bottom)) / 2;
      break;
    case AOSD_POSITION_PLACEMENT_MIDDLERIGHT:
      pos_x += screen_width - (layout_width + pad_left + pad_right);
      pos_y += (screen_height - (layout_height + pad_top + pad_bottom)) / 2;
      break;
    case AOSD_POSITION_PLACEMENT_BOTTOMLEFT:
      pos_x += 0;
      pos_y += screen_height - (layout_height + pad_top + pad_bottom);
      break;
    case AOSD_POSITION_PLACEMENT_BOTTOM:
      pos_x += (screen_width - (layout_width + pad_left + pad_right)) / 2;
      pos_y += screen_height - (layout_height + pad_top + pad_bottom);
      break;
    case AOSD_POSITION_PLACEMENT_BOTTOMRIGHT:
      pos_x += screen_width - (layout_width + pad_left + pad_right);
      pos_y += screen_height - (layout_height + pad_top + pad_bottom);
      break;
    case AOSD_POSITION_PLACEMENT_TOPLEFT:
    default:
      pos_x += 0;
      pos_y += 0;
      break;
  }

  /* add offset to position */
  pos_x += osd_data->cfg_osd->position.offset_x;
  pos_y += osd_data->cfg_osd->position.offset_y;

  ghosd_set_position( osd , pos_x , pos_y ,
    layout_width + pad_left + pad_right ,
    layout_height + pad_top + pad_bottom );

  ghosd_set_event_button_cb( osd , aosd_button_func , NULL );

  style_data.layout = osd_data->pango_layout;
  style_data.text = &(osd_data->cfg_osd->text);
  style_data.decoration = &(osd_data->cfg_osd->decoration);
  osd_data->fade_data.surface = NULL;
  osd_data->fade_data.user_data = &style_data;
  osd_data->fade_data.width = layout_width + pad_left + pad_right;
  osd_data->fade_data.height = layout_height + pad_top + pad_bottom;
  osd_data->fade_data.alpha = 0;
  osd_data->fade_data.deco_code = osd_data->cfg_osd->decoration.code;
  osd_data->dalpha_in = 1.0 / ( osd_data->cfg_osd->animation.timing_fadein / (gfloat)AOSD_TIMING );
  osd_data->dalpha_out = 1.0 / ( osd_data->cfg_osd->animation.timing_fadeout / (gfloat)AOSD_TIMING );
  osd_data->ddisplay_stay = 1.0 / ( osd_data->cfg_osd->animation.timing_display / (gfloat)AOSD_TIMING );
  ghosd_set_render( osd , (GhosdRenderFunc)aosd_fade_func , &(osd_data->fade_data) , NULL );

  /* show the osd (with alpha 0, invisible) */
  ghosd_show( osd );
  return;
}


static gboolean
aosd_timer_func ( gpointer none )
{
  static gfloat display_time = 0;

  switch ( osd_status )
  {
    case AOSD_STATUS_FADEIN:
    {
      /* fade in */
      osd_data->fade_data.alpha += osd_data->dalpha_in;
      if ( osd_data->fade_data.alpha < 1.0 )
      {
        ghosd_render( osd );
        ghosd_main_iterations( osd );
      }
      else
      {
        osd_data->fade_data.alpha = 1.0;
        display_time = 0;
        osd_status = AOSD_STATUS_SHOW; /* move to next phase */
        ghosd_render( osd );
        ghosd_main_iterations( osd );
      }
      return TRUE;
    }

    case AOSD_STATUS_SHOW:
    {
      display_time += osd_data->ddisplay_stay;
      if ( display_time >= 1.0 )
      {
        osd_status = AOSD_STATUS_FADEOUT; /* move to next phase */
        ghosd_main_iterations( osd );
      }
      else
      {
        ghosd_main_iterations( osd );
      }
      return TRUE;
    }

    case AOSD_STATUS_FADEOUT:
    {
      /* fade out */
      osd_data->fade_data.alpha -= osd_data->dalpha_out;
      if ( osd_data->fade_data.alpha > 0.0 )
      {
        ghosd_render( osd );
        ghosd_main_iterations( osd );
      }
      else
      {
        osd_data->fade_data.alpha = 0.0;
        osd_status = AOSD_STATUS_DESTROY; /* move to next phase */
        ghosd_render( osd );
        ghosd_main_iterations( osd );
      }
      return TRUE;
    }

    case AOSD_STATUS_DESTROY:
    {
      aosd_osd_hide();
      aosd_osd_data_free();

      osd_status = AOSD_STATUS_HIDDEN; /* reset status */
      osd_source_id = 0;
      return FALSE;
    }
  }

  return TRUE;
}


gint
aosd_osd_display ( gchar * markup_string , aosd_cfg_osd_t * cfg_osd , gboolean copy_cfg )
{
  if ( osd_status == AOSD_STATUS_HIDDEN )
  {
    aosd_osd_data_alloc( markup_string , cfg_osd , copy_cfg );
    aosd_osd_create();
    osd_status = AOSD_STATUS_FADEIN;
    osd_source_id = g_timeout_add_full( G_PRIORITY_DEFAULT_IDLE , AOSD_TIMING ,
                                        aosd_timer_func , NULL , NULL );
  }
  else
  {
    g_source_remove( osd_source_id ); /* remove timer */
    osd_source_id = 0;
    aosd_osd_hide();
    aosd_osd_data_free();
    osd_status = AOSD_STATUS_HIDDEN;
    /* now display new OSD */
    aosd_osd_data_alloc( markup_string , cfg_osd , copy_cfg );
    aosd_osd_create();
    osd_status = AOSD_STATUS_FADEIN;
    osd_source_id = g_timeout_add_full( G_PRIORITY_DEFAULT_IDLE , AOSD_TIMING ,
                                        aosd_timer_func , NULL , NULL );
  }
  return 0;
}


void
aosd_osd_shutdown ( void )
{
  if (( osd != NULL ) && ( osd_status != AOSD_STATUS_HIDDEN )) /* osd is being displayed */
  {
    g_source_remove( osd_source_id ); /* remove timer */
    osd_source_id = 0;
    aosd_osd_hide();
    aosd_osd_data_free();
    osd_status = AOSD_STATUS_HIDDEN;
  }
  return;
}


void
aosd_osd_init ( gint transparency_mode )
{
  if ( osd == NULL )
  {
    /* create Ghosd object */
    if ( transparency_mode == AOSD_MISC_TRANSPARENCY_FAKE )
      osd = ghosd_new();
    else
      osd = ghosd_new_with_argbvisual();
  }
  return;
}


void 
aosd_osd_cleanup ( void )
{
  if ( osd != NULL )
  {
    /* destroy Ghosd object */
    ghosd_destroy( osd );
    osd = NULL;
  }
  return;
}