/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- * * Copyright (C) 2006-2007 William Jon McCann * * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * */ #include "config.h" #include #include #include #include #include #include #include #include "gsd-media-keys-window.h" #define DIALOG_TIMEOUT 2000 /* dialog timeout in ms */ #define BG_ALPHA 0.50 #define FG_ALPHA 1.00 static void gsd_media_keys_window_class_init (GsdMediaKeysWindowClass *klass); static void gsd_media_keys_window_init (GsdMediaKeysWindow *fade); static void gsd_media_keys_window_finalize (GObject *object); #define GSD_MEDIA_KEYS_WINDOW_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GSD_TYPE_MEDIA_KEYS_WINDOW, GsdMediaKeysWindowPrivate)) struct GsdMediaKeysWindowPrivate { guint is_composited : 1; guint hide_timeout_id; GsdMediaKeysWindowAction action; guint volume_muted : 1; int volume_level; GladeXML *xml; }; G_DEFINE_TYPE (GsdMediaKeysWindow, gsd_media_keys_window, GTK_TYPE_WINDOW) static gboolean hide_timeout (GsdMediaKeysWindow *window) { gtk_widget_hide (GTK_WIDGET (window)); return FALSE; } static void remove_hide_timeout (GsdMediaKeysWindow *window) { if (window->priv->hide_timeout_id != 0) { g_source_remove (window->priv->hide_timeout_id); window->priv->hide_timeout_id = 0; } } static void add_hide_timeout (GsdMediaKeysWindow *window) { window->priv->hide_timeout_id = g_timeout_add (DIALOG_TIMEOUT, (GSourceFunc)hide_timeout, window); } static void update_window (GsdMediaKeysWindow *window) { remove_hide_timeout (window); add_hide_timeout (window); if (window->priv->is_composited) { gtk_widget_queue_draw (GTK_WIDGET (window)); } } static void volume_controls_set_visible (GsdMediaKeysWindow *window, gboolean visible) { GtkWidget *progress; if (window->priv->xml == NULL) { return; } progress = glade_xml_get_widget (window->priv->xml, "acme_volume_progressbar"); if (progress == NULL) { return; } if (visible) { gtk_widget_show (progress); } else { gtk_widget_hide (progress); } } static void window_set_icon_name (GsdMediaKeysWindow *window, const char *name) { GtkWidget *image; if (window->priv->xml == NULL) { return; } image = glade_xml_get_widget (window->priv->xml, "acme_image"); if (image == NULL) { return; } gtk_image_set_from_icon_name (GTK_IMAGE (image), name, GTK_ICON_SIZE_DIALOG); } static void window_set_icon_file (GsdMediaKeysWindow *window, const char *path) { GtkWidget *image; if (window->priv->xml == NULL) { return; } image = glade_xml_get_widget (window->priv->xml, "acme_image"); if (image == NULL) { return; } gtk_image_set_from_file (GTK_IMAGE (image), path); } static void action_changed (GsdMediaKeysWindow *window) { if (! window->priv->is_composited) { switch (window->priv->action) { case GSD_MEDIA_KEYS_WINDOW_ACTION_VOLUME: volume_controls_set_visible (window, TRUE); if (window->priv->volume_muted) { window_set_icon_name (window, "audio-volume-muted"); } else { window_set_icon_name (window, "audio-volume-high"); } break; case GSD_MEDIA_KEYS_WINDOW_ACTION_EJECT: volume_controls_set_visible (window, FALSE); window_set_icon_file (window, GNOMECC_PIXMAPS_DIR "/acme-eject.png"); break; default: break; } } update_window (window); } static void volume_level_changed (GsdMediaKeysWindow *window) { update_window (window); if (! window->priv->is_composited) { GtkWidget *progress; double fraction; if (window->priv->xml != NULL) { fraction = (double)window->priv->volume_level / 100.0; progress = glade_xml_get_widget (window->priv->xml, "acme_volume_progressbar"); gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (progress), fraction); } } } static void volume_muted_changed (GsdMediaKeysWindow *window) { update_window (window); if (! window->priv->is_composited) { if (window->priv->volume_muted) { window_set_icon_name (window, "audio-volume-muted"); } else { window_set_icon_name (window, "audio-volume-high"); } } } void gsd_media_keys_window_set_action (GsdMediaKeysWindow *window, GsdMediaKeysWindowAction action) { g_return_if_fail (GSD_IS_MEDIA_KEYS_WINDOW (window)); if (window->priv->action != action) { window->priv->action = action; action_changed (window); } } void gsd_media_keys_window_set_volume_muted (GsdMediaKeysWindow *window, gboolean muted) { g_return_if_fail (GSD_IS_MEDIA_KEYS_WINDOW (window)); if (window->priv->volume_muted != muted) { window->priv->volume_muted = muted; volume_muted_changed (window); } } void gsd_media_keys_window_set_volume_level (GsdMediaKeysWindow *window, int level) { g_return_if_fail (GSD_IS_MEDIA_KEYS_WINDOW (window)); if (window->priv->volume_level != level) { window->priv->volume_level = level; volume_level_changed (window); } } static void curved_rectangle (cairo_t *cr, double x0, double y0, double width, double height, double radius) { double x1; double y1; x1 = x0 + width; y1 = y0 + height; if (!width || !height) { return; } if (width / 2 < radius) { if (height / 2 < radius) { cairo_move_to (cr, x0, (y0 + y1) / 2); cairo_curve_to (cr, x0 ,y0, x0, y0, (x0 + x1) / 2, y0); cairo_curve_to (cr, x1, y0, x1, y0, x1, (y0 + y1) / 2); cairo_curve_to (cr, x1, y1, x1, y1, (x1 + x0) / 2, y1); cairo_curve_to (cr, x0, y1, x0, y1, x0, (y0 + y1) / 2); } else { cairo_move_to (cr, x0, y0 + radius); cairo_curve_to (cr, x0, y0, x0, y0, (x0 + x1) / 2, y0); cairo_curve_to (cr, x1, y0, x1, y0, x1, y0 + radius); cairo_line_to (cr, x1, y1 - radius); cairo_curve_to (cr, x1, y1, x1, y1, (x1 + x0) / 2, y1); cairo_curve_to (cr, x0, y1, x0, y1, x0, y1 - radius); } } else { if (height / 2 < radius) { cairo_move_to (cr, x0, (y0 + y1) / 2); cairo_curve_to (cr, x0, y0, x0 , y0, x0 + radius, y0); cairo_line_to (cr, x1 - radius, y0); cairo_curve_to (cr, x1, y0, x1, y0, x1, (y0 + y1) / 2); cairo_curve_to (cr, x1, y1, x1, y1, x1 - radius, y1); cairo_line_to (cr, x0 + radius, y1); cairo_curve_to (cr, x0, y1, x0, y1, x0, (y0 + y1) / 2); } else { cairo_move_to (cr, x0, y0 + radius); cairo_curve_to (cr, x0 , y0, x0 , y0, x0 + radius, y0); cairo_line_to (cr, x1 - radius, y0); cairo_curve_to (cr, x1, y0, x1, y0, x1, y0 + radius); cairo_line_to (cr, x1, y1 - radius); cairo_curve_to (cr, x1, y1, x1, y1, x1 - radius, y1); cairo_line_to (cr, x0 + radius, y1); cairo_curve_to (cr, x0, y1, x0, y1, x0, y1 - radius); } } cairo_close_path (cr); } static GdkPixbuf * load_pixbuf (GsdMediaKeysWindow *window, const char *name, int icon_size) { GtkIconTheme *theme; GdkPixbuf *pixbuf; if (window != NULL && gtk_widget_has_screen (GTK_WIDGET (window))) { theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (window))); } else { theme = gtk_icon_theme_get_default (); } pixbuf = gtk_icon_theme_load_icon (theme, name, icon_size, GTK_ICON_LOOKUP_FORCE_SVG, NULL); /* make sure the pixbuf is close to the requested size * this is necessary because GTK_ICON_LOOKUP_FORCE_SVG * seems to be broken */ if (pixbuf != NULL) { int width; width = gdk_pixbuf_get_width (pixbuf); if (width < (float)icon_size * 0.75) { g_object_unref (pixbuf); pixbuf = NULL; } } return pixbuf; } static gboolean render_eject (GsdMediaKeysWindow *window, cairo_t *cr, double x0, double y0, double width, double height) { GdkPixbuf *pixbuf; int icon_size; const char *icon_name; icon_name = "media-eject"; icon_size = (int)width; pixbuf = load_pixbuf (window, icon_name, icon_size); if (pixbuf == NULL) { return FALSE; } gdk_cairo_set_source_pixbuf (cr, pixbuf, x0, y0); cairo_paint_with_alpha (cr, FG_ALPHA); g_object_unref (pixbuf); return TRUE; } static void draw_eject (cairo_t *cr, double x0, double y0, double width, double height) { int box_height; int tri_height; int separation; box_height = height * 0.2; separation = box_height / 3; tri_height = height - box_height - separation; cairo_rectangle (cr, x0, y0 + height - box_height, width, box_height); cairo_move_to (cr, x0, y0 + tri_height); cairo_rel_line_to (cr, width, 0); cairo_rel_line_to (cr, -width / 2, -tri_height); cairo_rel_line_to (cr, -width / 2, tri_height); cairo_close_path (cr); cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, FG_ALPHA); cairo_fill_preserve (cr); cairo_set_source_rgba (cr, 0.6, 0.6, 0.6, FG_ALPHA / 2); cairo_set_line_width (cr, 2); cairo_stroke (cr); } static void draw_action_eject (GsdMediaKeysWindow *window, cairo_t *cr) { int window_width; int window_height; double width; double height; double x0; double y0; gboolean res; gtk_window_get_size (GTK_WINDOW (window), &window_width, &window_height); width = window_width * 0.65; height = window_height * 0.65; x0 = (window_width - width) / 2; y0 = (window_height - height) / 2; #if 0 g_message ("eject box: w=%f h=%f x0=%f y0=%f", width, height, x0, y0); #endif res = render_eject (window, cr, x0, y0, width, height); if (! res) { /* draw eject symbol */ draw_eject (cr, x0, y0, width, height); } } static void draw_waves (cairo_t *cr, double cx, double cy, double max_radius) { int n_waves; int i; n_waves = 3; for (i = 0; i < n_waves; i++) { double angle1; double angle2; double radius; angle1 = -M_PI / 4; angle2 = M_PI / 4; radius = (i + 1) * (max_radius / n_waves); cairo_arc (cr, cx, cy, radius, angle1, angle2); cairo_set_source_rgba (cr, 0.6, 0.6, 0.6, FG_ALPHA / 2); cairo_set_line_width (cr, 14); cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND); cairo_stroke_preserve (cr); cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, FG_ALPHA); cairo_set_line_width (cr, 10); cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND); cairo_stroke (cr); } } static void draw_speaker (cairo_t *cr, double cx, double cy, double width, double height) { double box_width; double box_height; double x0; double y0; box_width = width / 3; box_height = height / 3; x0 = cx - (width / 2) + box_width; y0 = cy - box_height / 2; cairo_move_to (cr, x0, y0); cairo_rel_line_to (cr, - box_width, 0); cairo_rel_line_to (cr, 0, box_height); cairo_rel_line_to (cr, box_width, 0); cairo_line_to (cr, cx + box_width, cy + height / 2); cairo_rel_line_to (cr, 0, -height); cairo_line_to (cr, x0, y0); cairo_close_path (cr); cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, FG_ALPHA); cairo_fill_preserve (cr); cairo_set_source_rgba (cr, 0.6, 0.6, 0.6, FG_ALPHA / 2); cairo_set_line_width (cr, 2); cairo_stroke (cr); } static gboolean render_speaker (GsdMediaKeysWindow *window, cairo_t *cr, double x0, double y0, double width, double height) { GdkPixbuf *pixbuf; int icon_size; int n; static const char *icon_names[] = { "audio-volume-muted", "audio-volume-low", "audio-volume-medium", "audio-volume-high", NULL }; if (window->priv->volume_muted) { n = 0; } else { /* select image */ n = 3 * window->priv->volume_level / 100 + 1; if (n < 1) { n = 1; } else if (n > 3) { n = 3; } } icon_size = (int)width; pixbuf = load_pixbuf (window, icon_names[n], icon_size); if (pixbuf == NULL) { return FALSE; } gdk_cairo_set_source_pixbuf (cr, pixbuf, x0, y0); cairo_paint_with_alpha (cr, FG_ALPHA); g_object_unref (pixbuf); return TRUE; } static void draw_volume_boxes (GsdMediaKeysWindow *window, cairo_t *cr, double percentage, double x0, double y0, double width, double height) { gdouble x1; GdkColor color; double r, g, b; x1 = width * percentage; /* bar background */ color = GTK_WIDGET (window)->style->dark [GTK_STATE_NORMAL]; r = (float)color.red / 65535.0; g = (float)color.green / 65535.0; b = (float)color.blue / 65535.0; cairo_rectangle (cr, x0, y0, width, height); cairo_set_source_rgba (cr, r, g, b, FG_ALPHA); cairo_fill (cr); /* bar border */ color = GTK_WIDGET (window)->style->dark [GTK_STATE_SELECTED]; r = (float)color.red / 65535.0; g = (float)color.green / 65535.0; b = (float)color.blue / 65535.0; cairo_rectangle (cr, x0, y0, width, height); cairo_set_source_rgba (cr, r, g, b, FG_ALPHA); cairo_set_line_width (cr, 1); cairo_stroke (cr); /* bar progress */ color = GTK_WIDGET (window)->style->bg [GTK_STATE_SELECTED]; r = (float)color.red / 65535.0; g = (float)color.green / 65535.0; b = (float)color.blue / 65535.0; cairo_rectangle (cr, x0, y0, x1, height); cairo_set_source_rgba (cr, r, g, b, FG_ALPHA); cairo_fill (cr); } static void draw_action_volume (GsdMediaKeysWindow *window, cairo_t *cr) { int window_width; int window_height; double icon_box_width; double icon_box_height; double icon_box_x0; double icon_box_y0; double volume_box_x0; double volume_box_y0; double volume_box_width; double volume_box_height; gboolean res; gtk_window_get_size (GTK_WINDOW (window), &window_width, &window_height); icon_box_width = window_width * 0.65; icon_box_height = window_height * 0.65; volume_box_width = icon_box_width; volume_box_height = window_height * 0.05; icon_box_x0 = (window_width - icon_box_width) / 2; icon_box_y0 = (window_height - icon_box_height - volume_box_height) / 2; volume_box_x0 = icon_box_x0; volume_box_y0 = icon_box_height + icon_box_y0; #if 0 g_message ("icon box: w=%f h=%f x0=%f y0=%f", icon_box_width, icon_box_height, icon_box_x0, icon_box_y0); g_message ("volume box: w=%f h=%f x0=%f y0=%f", volume_box_width, volume_box_height, volume_box_x0, volume_box_y0); #endif res = render_speaker (window, cr, icon_box_x0, icon_box_y0, icon_box_width, icon_box_height); if (! res) { double speaker_width; double speaker_height; double speaker_cx; double speaker_cy; double wave_x0; double wave_y0; double wave_radius; speaker_width = icon_box_width * 0.5; speaker_height = icon_box_height * 0.75; speaker_cx = icon_box_x0 + speaker_width / 2; speaker_cy = icon_box_y0 + speaker_height / 2; wave_x0 = window_width / 2; wave_y0 = speaker_cy; wave_radius = icon_box_width / 2; #if 0 g_message ("speaker box: w=%f h=%f cx=%f cy=%f", speaker_width, speaker_height, speaker_cx, speaker_cy); #endif /* draw speaker symbol */ draw_speaker (cr, speaker_cx, speaker_cy, speaker_width, speaker_height); /* draw sound waves */ if (! window->priv->volume_muted) { draw_waves (cr, wave_x0, wave_y0, wave_radius); } } /* draw volume meter */ draw_volume_boxes (window, cr, (double)window->priv->volume_level / 100.0, volume_box_x0, volume_box_y0, volume_box_width, volume_box_height); } static void draw_action (GsdMediaKeysWindow *window, cairo_t *cr) { switch (window->priv->action) { case GSD_MEDIA_KEYS_WINDOW_ACTION_VOLUME: draw_action_volume (window, cr); break; case GSD_MEDIA_KEYS_WINDOW_ACTION_EJECT: draw_action_eject (window, cr); break; default: break; } } static gboolean on_expose_event (GtkWidget *widget, GdkEventExpose *event, GsdMediaKeysWindow *window) { cairo_t *context; cairo_t *cr; cairo_surface_t *surface; int width; int height; GdkColor color; double r, g, b; context = gdk_cairo_create (GTK_WIDGET (window)->window); cairo_set_operator (context, CAIRO_OPERATOR_SOURCE); gtk_window_get_size (GTK_WINDOW (widget), &width, &height); surface = cairo_surface_create_similar (cairo_get_target (context), CAIRO_CONTENT_COLOR_ALPHA, width, height); if (cairo_surface_status (surface) != CAIRO_STATUS_SUCCESS) { goto done; } cr = cairo_create (surface); if (cairo_status (cr) != CAIRO_STATUS_SUCCESS) { goto done; } cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.0); cairo_set_operator (cr, CAIRO_OPERATOR_OVER); cairo_paint (cr); /* draw a box */ curved_rectangle (cr, 0, 0, width, height, height / 10); color = GTK_WIDGET (window)->style->bg [GTK_STATE_NORMAL]; r = (float)color.red / 65535.0; g = (float)color.green / 65535.0; b = (float)color.blue / 65535.0; cairo_set_source_rgba (cr, r, g, b, BG_ALPHA); cairo_fill_preserve (cr); color = GTK_WIDGET (window)->style->fg [GTK_STATE_NORMAL]; r = (float)color.red / 65535.0; g = (float)color.green / 65535.0; b = (float)color.blue / 65535.0; cairo_set_source_rgba (cr, r, g, b, BG_ALPHA); cairo_set_line_width (cr, 1); cairo_stroke (cr); /* draw action */ draw_action (window, cr); cairo_destroy (cr); cairo_set_source_surface (context, surface, 0, 0); cairo_paint (context); done: if (surface != NULL) { cairo_surface_destroy (surface); } cairo_destroy (context); return FALSE; } static void gsd_media_keys_window_real_show (GtkWidget *widget) { GsdMediaKeysWindow *window; if (GTK_WIDGET_CLASS (gsd_media_keys_window_parent_class)->show) { GTK_WIDGET_CLASS (gsd_media_keys_window_parent_class)->show (widget); } window = GSD_MEDIA_KEYS_WINDOW (widget); remove_hide_timeout (window); add_hide_timeout (window); } static void gsd_media_keys_window_real_hide (GtkWidget *widget) { GsdMediaKeysWindow *window; if (GTK_WIDGET_CLASS (gsd_media_keys_window_parent_class)->hide) { GTK_WIDGET_CLASS (gsd_media_keys_window_parent_class)->hide (widget); } window = GSD_MEDIA_KEYS_WINDOW (widget); remove_hide_timeout (window); } static void gsd_media_keys_window_class_init (GsdMediaKeysWindowClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class; widget_class = GTK_WIDGET_CLASS (klass); object_class->finalize = gsd_media_keys_window_finalize; widget_class->show = gsd_media_keys_window_real_show; widget_class->hide = gsd_media_keys_window_real_hide; g_type_class_add_private (klass, sizeof (GsdMediaKeysWindowPrivate)); } gboolean gsd_media_keys_window_is_valid (GsdMediaKeysWindow *window) { GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (window)); return gdk_screen_is_composited (screen) == window->priv->is_composited; } static void initialize_alpha_mode (GsdMediaKeysWindow *window) { GdkScreen *screen; GdkColormap *colormap = NULL; screen = gtk_widget_get_screen (GTK_WIDGET (window)); if (gdk_screen_is_composited (screen)) { colormap = gdk_screen_get_rgba_colormap (screen); } if (colormap != NULL) { gtk_widget_set_colormap (GTK_WIDGET (window), colormap); window->priv->is_composited = TRUE; } else { window->priv->is_composited = FALSE; } } static void gsd_media_keys_window_init (GsdMediaKeysWindow *window) { window->priv = GSD_MEDIA_KEYS_WINDOW_GET_PRIVATE (window); initialize_alpha_mode (window); if (window->priv->is_composited) { gtk_window_set_decorated (GTK_WINDOW (window), FALSE); gtk_widget_set_app_paintable (GTK_WIDGET (window), TRUE); gtk_window_set_default_size (GTK_WINDOW (window), 300, 300); g_signal_connect (window, "expose-event", G_CALLBACK (on_expose_event), window); } else { GtkWidget *frame; window->priv->xml = glade_xml_new (GNOMECC_GLADE_DIR "/acme.glade", "acme_frame", NULL); frame = glade_xml_get_widget (window->priv->xml, "acme_frame"); if (frame != NULL) { gtk_container_add (GTK_CONTAINER (window), frame); gtk_widget_show_all (frame); } } } static void gsd_media_keys_window_finalize (GObject *object) { GsdMediaKeysWindow *window; g_return_if_fail (object != NULL); g_return_if_fail (GSD_IS_MEDIA_KEYS_WINDOW (object)); window = GSD_MEDIA_KEYS_WINDOW (object); g_return_if_fail (window->priv != NULL); if (window->priv->xml != NULL) { g_object_unref (window->priv->xml); } G_OBJECT_CLASS (gsd_media_keys_window_parent_class)->finalize (object); } GtkWidget * gsd_media_keys_window_new (void) { GObject *object; object = g_object_new (GSD_TYPE_MEDIA_KEYS_WINDOW, "type", GTK_WINDOW_POPUP, "skip-taskbar-hint", TRUE, "skip-pager-hint", TRUE, "focus-on-map", FALSE, NULL); return GTK_WIDGET (object); }