Commit 92b428d0 authored by Alberts Muktupāvels's avatar Alberts Muktupāvels
Browse files

sound-applet: create status notifier items

parent 35db78ee
......@@ -81,6 +81,26 @@ dnl **************************************************************************
GLIB_GSETTINGS
dnl **************************************************************************
dnl Build with libstatus-notifier (experimental)
dnl **************************************************************************
AC_ARG_WITH([libstatus-notifier],
[AS_HELP_STRING([--with-libstatus-notifier],
[Build with libstatus-notifier (experimental)])],
[with_libstatus_notifier="$withval"],
[with_libstatus_notifier=no])
LIBSTATUS_NOTIFIER_PKG=""
AS_IF([test "x$with_libstatus_notifier" = "xyes"], [
LIBSTATUS_NOTIFIER_PKG="libstatus-notifier-3.0"
AC_DEFINE([WITH_LIBSTATUS_NOTIFIER], [1],
[Define to 1 if libstatus-notifier is available])
])
AM_CONDITIONAL([WITH_LIBSTATUS_NOTIFIER],
[test "x$with_libstatus_notifier" = "xyes"])
dnl **************************************************************************
dnl Check for required packages
dnl **************************************************************************
......@@ -188,6 +208,15 @@ PKG_CHECK_MODULES([SOUND_APPLET], [
gtk+-3.0 >= $GTK_REQUIRED
libcanberra-gtk3 >= $CANBERRA_REQUIRED
glib-2.0 >= $GLIB_REQUIRED
$LIBSTATUS_NOTIFIER_PKG
])
AS_IF([test "x$with_libstatus_notifier" = "xyes"], [
PKG_CHECK_MODULES([WATCHER], [
glib-2.0 >= $GLIB_REQUIRED
gtk+-3.0 >= $GTK_REQUIRED
$LIBSTATUS_NOTIFIER_PKG
])
])
PKG_CHECK_MODULES([WORKAROUNDS], [
......@@ -196,25 +225,6 @@ PKG_CHECK_MODULES([WORKAROUNDS], [
x11
])
dnl **************************************************************************
dnl Build with libstatus-notifier (experimental)
dnl **************************************************************************
AC_ARG_WITH([libstatus-notifier],
[AS_HELP_STRING([--with-libstatus-notifier],
[Build with libstatus-notifier (experimental)])],
[with_libstatus_notifier="$withval"],
[with_libstatus_notifier=no])
AS_IF([test "x$with_libstatus_notifier" = "xyes"], [
PKG_CHECK_MODULES([WATCHER], [libstatus-notifier-3.0])
AC_DEFINE([WITH_LIBSTATUS_NOTIFIER], [1],
[Define to 1 if libstatus-notifier is available])
])
AM_CONDITIONAL([WITH_LIBSTATUS_NOTIFIER],
[test "x$with_libstatus_notifier" = "xyes"])
dnl **************************************************************************
dnl Define XKB base directory
dnl **************************************************************************
......
......@@ -25,6 +25,13 @@ libsound_applet_la_SOURCES = \
gvc-stream-status-icon.h \
$(NULL)
if WITH_LIBSTATUS_NOTIFIER
libsound_applet_la_SOURCES += \
gf-sound-item.c \
gf-sound-item.h \
$(NULL)
endif
libsound_applet_la_LDFLAGS = \
$(WARN_LDFLAGS) \
$(AM_LDFLAGS) \
......
......@@ -34,6 +34,10 @@
#include "gvc-mixer-control.h"
#include "gvc-stream-status-icon.h"
#ifdef WITH_LIBSTATUS_NOTIFIER
#include "gf-sound-item.h"
#endif
static const gchar *output_icons[] =
{
"audio-volume-muted",
......@@ -59,6 +63,11 @@ struct _GfSoundApplet
GvcStreamStatusIcon *input_status_icon;
GvcStreamStatusIcon *output_status_icon;
GvcMixerControl *control;
#ifdef WITH_LIBSTATUS_NOTIFIER
GfSoundItem *output_item;
GfSoundItem *input_item;
#endif
};
G_DEFINE_TYPE (GfSoundApplet, gf_sound_applet, G_TYPE_OBJECT)
......@@ -81,6 +90,13 @@ maybe_show_status_icons (GfSoundApplet *applet)
gtk_status_icon_set_visible (GTK_STATUS_ICON (applet->output_status_icon), show);
G_GNUC_END_IGNORE_DEPRECATIONS
#ifdef WITH_LIBSTATUS_NOTIFIER
if (show)
sn_item_register (SN_ITEM (applet->output_item));
else
sn_item_unregister (SN_ITEM (applet->output_item));
#endif
show = FALSE;
stream = gvc_mixer_control_get_default_source (applet->control);
source_outputs = gvc_mixer_control_get_source_outputs (applet->control);
......@@ -114,6 +130,13 @@ maybe_show_status_icons (GfSoundApplet *applet)
gtk_status_icon_set_visible (GTK_STATUS_ICON (applet->input_status_icon), show);
G_GNUC_END_IGNORE_DEPRECATIONS
#ifdef WITH_LIBSTATUS_NOTIFIER
if (show)
sn_item_register (SN_ITEM (applet->input_item));
else
sn_item_unregister (SN_ITEM (applet->input_item));
#endif
g_slist_free (source_outputs);
}
......@@ -127,6 +150,9 @@ update_default_sink (GfSoundApplet *applet)
if (stream != NULL)
{
gvc_stream_status_icon_set_mixer_stream (applet->output_status_icon, stream);
#ifdef WITH_LIBSTATUS_NOTIFIER
gf_sound_item_set_mixer_stream (applet->output_item, stream);
#endif
maybe_show_status_icons (applet);
}
else
......@@ -145,6 +171,9 @@ update_default_source (GfSoundApplet *applet)
if (stream != NULL)
{
gvc_stream_status_icon_set_mixer_stream (applet->input_status_icon, stream);
#ifdef WITH_LIBSTATUS_NOTIFIER
gf_sound_item_set_mixer_stream (applet->input_item, stream);
#endif
maybe_show_status_icons (applet);
}
else
......@@ -259,6 +288,11 @@ gf_sound_applet_dispose (GObject *object)
g_clear_object (&applet->input_status_icon);
g_clear_object (&applet->control);
#ifdef WITH_LIBSTATUS_NOTIFIER
g_clear_object (&applet->output_item);
g_clear_object (&applet->input_item);
#endif
G_OBJECT_CLASS (gf_sound_applet_parent_class)->dispose (object);
}
......@@ -277,6 +311,9 @@ static void
gf_sound_applet_init (GfSoundApplet *applet)
{
GvcStreamStatusIcon *icon;
#ifdef WITH_LIBSTATUS_NOTIFIER
SnItemCategory category;
#endif
/* Output icon */
icon = gvc_stream_status_icon_new (NULL, output_icons);
......@@ -297,6 +334,22 @@ gf_sound_applet_init (GfSoundApplet *applet)
G_GNUC_END_IGNORE_DEPRECATIONS
applet->input_status_icon = icon;
#ifdef WITH_LIBSTATUS_NOTIFIER
category = SN_ITEM_CATEGORY_HARDWARE;
applet->output_item = gf_sound_item_new (category,
"gf-sound-output",
_("Sound Output Volume"),
_("Output"),
output_icons);
applet->input_item = gf_sound_item_new (category,
"gf-sound-input",
_("Microphone Volume"),
_("Input"),
input_icons);
#endif
}
GfSoundApplet *
......
/*
* Copyright (C) 2008 William Jon McCann
* Copyright (C) 2015 Alberts Muktupāvels
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include <math.h>
#include <pulse/pulseaudio.h>
#include "gf-sound-item.h"
#include "gvc-channel-bar.h"
struct _GfSoundItem
{
SnItem parent;
gchar *display_name;
gchar **icon_names;
GvcMixerStream *mixer_stream;
gulong notify_volume_id;
gulong notify_is_muted_id;
GtkWidget *dock;
GtkWidget *bar;
};
enum
{
PROP_0,
PROP_DISPLAY_NAME,
PROP_ICON_NAMES,
LAST_PROP
};
static GParamSpec *properties[LAST_PROP] = { NULL };
G_DEFINE_TYPE (GfSoundItem, gf_sound_item, SN_TYPE_ITEM)
static void
ungrab (GfSoundItem *item)
{
GdkDisplay *display;
GdkSeat *seat;
display = gtk_widget_get_display (item->dock);
seat = gdk_display_get_default_seat (display);
gdk_seat_ungrab (seat);
gtk_grab_remove (item->dock);
gtk_widget_hide (item->dock);
}
static gboolean
button_press_event_cb (GtkWidget *widget,
GdkEventButton *event,
gpointer user_data)
{
GfSoundItem *item;
if (event->type != GDK_BUTTON_PRESS)
return GDK_EVENT_PROPAGATE;
item = GF_SOUND_ITEM (user_data);
ungrab (item);
return GDK_EVENT_STOP;
}
static gboolean
key_release_event_cb (GtkWidget *widget,
GdkEventKey *event,
gpointer user_data)
{
GfSoundItem *item;
if (event->keyval != GDK_KEY_Escape)
return GDK_EVENT_PROPAGATE;
item = GF_SOUND_ITEM (user_data);
ungrab (item);
return GDK_EVENT_STOP;
}
static gboolean
scroll_event_cb (GtkWidget *widget,
GdkEventScroll *event,
gpointer user_data)
{
GfSoundItem *item;
item = GF_SOUND_ITEM (user_data);
return gvc_channel_bar_scroll (GVC_CHANNEL_BAR (item->bar), event);
}
static void
update_icon (GfSoundItem *item)
{
guint volume;
gboolean is_muted;
gdouble db;
gboolean can_decibel;
guint n;
gdouble percent;
const gchar *description;
gchar *title;
gchar *text;
if (item->mixer_stream == NULL)
return;
volume = gvc_mixer_stream_get_volume (item->mixer_stream);
is_muted = gvc_mixer_stream_get_is_muted (item->mixer_stream);
db = gvc_mixer_stream_get_decibel (item->mixer_stream);
can_decibel = gvc_mixer_stream_get_can_decibel (item->mixer_stream);
if (volume == 0 || is_muted)
{
n = 0;
}
else
{
n = 3 * volume / PA_VOLUME_NORM + 1;
if (n < 1)
n = 1;
else if (n > 3)
n = 3;
}
sn_item_set_icon_name (SN_ITEM (item), item->icon_names[n]);
percent = 100 * (float) volume / PA_VOLUME_NORM;
description = gvc_mixer_stream_get_description (item->mixer_stream);
if (is_muted)
{
title = g_strdup_printf ("<b>%s: %s</b>", item->display_name, _("Muted"));
text = g_strdup_printf ("<small>%s</small>", description);
}
else if (can_decibel && (db > PA_DECIBEL_MININFTY))
{
title = g_strdup_printf ("<b>%s: %.0f%%</b>", item->display_name, percent);
text = g_strdup_printf ("<small>%0.2f dB\n%s</small>", db, description);
}
else if (can_decibel)
{
title = g_strdup_printf ("<b>%s: %.0f%%</b>", item->display_name, percent);
text = g_strdup_printf ("<small>-&#8734; dB\n%s</small>", description);
}
else
{
title = g_strdup_printf ("<b>%s: %.0f%%</b>", item->display_name, percent);
text = g_strdup_printf ("<small>%s</small>", description);
}
sn_item_set_tooltip (SN_ITEM (item), NULL, NULL, title, text);
g_free (title);
g_free (text);
}
static void
value_changed_cb (GtkAdjustment *adjustment,
gpointer user_data)
{
GfSoundItem *item;
gdouble volume;
pa_volume_t volume_t;
item = GF_SOUND_ITEM (user_data);
volume = gtk_adjustment_get_value (adjustment);
volume_t = (pa_volume_t) round (volume);
if (gvc_mixer_stream_set_volume (item->mixer_stream, volume_t))
gvc_mixer_stream_push_volume (item->mixer_stream);
}
static void
update_dock (GfSoundItem *item)
{
GtkAdjustment *adjustment;
gdouble value;
gboolean is_muted;
adjustment = gvc_channel_bar_get_adjustment (GVC_CHANNEL_BAR (item->bar));
value = gvc_mixer_stream_get_volume (item->mixer_stream);
g_signal_handlers_block_by_func (adjustment, value_changed_cb, item);
gtk_adjustment_set_value (adjustment, value);
g_signal_handlers_unblock_by_func (adjustment, value_changed_cb, item);
is_muted = gvc_mixer_stream_get_is_muted (item->mixer_stream);
gvc_channel_bar_set_is_muted (GVC_CHANNEL_BAR (item->bar), is_muted);
}
static void
bar_notify_is_muted_cb (GObject *object,
GParamSpec *pspec,
gpointer user_data)
{
GfSoundItem *item;
gboolean is_muted;
item = GF_SOUND_ITEM (user_data);
is_muted = gvc_channel_bar_get_is_muted (GVC_CHANNEL_BAR (item->bar));
if (gvc_mixer_stream_get_is_muted (item->mixer_stream) == is_muted)
return;
gvc_mixer_stream_set_is_muted (item->mixer_stream, is_muted);
gvc_mixer_stream_change_is_muted (item->mixer_stream, is_muted);
}
static void
gf_sound_item_constructed (GObject *object)
{
GfSoundItem *item;
GtkWidget *frame;
GtkWidget *box;
GtkAdjustment *adjustment;
G_OBJECT_CLASS (gf_sound_item_parent_class)->constructed (object);
item = GF_SOUND_ITEM (object);
item->dock = gtk_window_new (GTK_WINDOW_POPUP);
gtk_widget_set_name (item->dock, "gvc-stream-status-icon-popup-window");
gtk_window_set_decorated (GTK_WINDOW (item->dock), FALSE);
g_signal_connect (item->dock, "button-press-event",
G_CALLBACK (button_press_event_cb), item);
g_signal_connect (item->dock, "key-release-event",
G_CALLBACK (key_release_event_cb), item);
g_signal_connect (item->dock, "scroll-event",
G_CALLBACK (scroll_event_cb), item);
frame = gtk_frame_new (NULL);
gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT);
gtk_container_add (GTK_CONTAINER (item->dock), frame);
box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
gtk_container_set_border_width (GTK_CONTAINER (box), 2);
gtk_container_add (GTK_CONTAINER (frame), box);
item->bar = gvc_channel_bar_new ();
gtk_box_pack_start (GTK_BOX (box), item->bar, TRUE, FALSE, 0);
gvc_channel_bar_set_orientation (GVC_CHANNEL_BAR (item->bar),
GTK_ORIENTATION_VERTICAL);
g_signal_connect (item->bar, "notify::is-muted",
G_CALLBACK (bar_notify_is_muted_cb), item);
adjustment = gvc_channel_bar_get_adjustment (GVC_CHANNEL_BAR (item->bar));
g_signal_connect (adjustment, "value-changed",
G_CALLBACK (value_changed_cb), item);
}
static void
clear_mixer_stream (GfSoundItem *item)
{
if (item->mixer_stream == NULL)
return;
g_signal_handler_disconnect (item->mixer_stream, item->notify_volume_id);
g_signal_handler_disconnect (item->mixer_stream, item->notify_is_muted_id);
g_clear_object (&item->mixer_stream);
}
static void
gf_sound_item_dispose (GObject *object)
{
GfSoundItem *item;
item = GF_SOUND_ITEM (object);
if (item->dock != NULL)
{
gtk_widget_destroy (item->dock);
item->dock = NULL;
}
clear_mixer_stream (item);
G_OBJECT_CLASS (gf_sound_item_parent_class)->dispose (object);
}
static void
gf_sound_item_finalize (GObject *object)
{
GfSoundItem *item;
item = GF_SOUND_ITEM (object);
g_free (item->display_name);
g_strfreev (item->icon_names);
G_OBJECT_CLASS (gf_sound_item_parent_class)->finalize (object);
}
static void
gf_sound_item_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
GfSoundItem *item;
item = GF_SOUND_ITEM (object);
switch (property_id)
{
case PROP_DISPLAY_NAME:
item->display_name = g_value_dup_string (value);
break;
case PROP_ICON_NAMES:
item->icon_names = (gchar **) g_value_dup_boxed (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
toggled_cb (GtkMenuItem *item,
gpointer user_data)
{
GfSoundItem *sound_item;
gboolean is_muted;
sound_item = GF_SOUND_ITEM (user_data);
is_muted = gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item));
gvc_channel_bar_set_is_muted (GVC_CHANNEL_BAR (sound_item->bar), is_muted);
}
static void
activate_cb (GtkMenuItem *item,
gpointer user_data)
{
GdkDisplay *display;
GdkAppLaunchContext *context;
GAppInfoCreateFlags flags;
GError *error;
GAppInfo *app_info;
display = gdk_display_get_default ();
context = gdk_display_get_app_launch_context (display);
flags = G_APP_INFO_CREATE_NONE;
error = NULL;
app_info = g_app_info_create_from_commandline ("gnome-control-center sound",
"Sound preferences", flags,
&error);
if (app_info)
g_app_info_launch (app_info, NULL, G_APP_LAUNCH_CONTEXT (context), &error);
if (error != NULL)
{
gchar *msg;
GtkWidget *dialog;
msg = g_strdup_printf (_("Failed to start Sound Preferences: %s"),
error->message);
dialog = gtk_message_dialog_new (NULL, 0, GTK_MESSAGE_ERROR,
GTK_BUTTONS_CLOSE, "%s", msg);
g_error_free (error);
g_free (msg);
g_signal_connect (dialog, "response",
G_CALLBACK (gtk_widget_destroy), NULL);
gtk_widget_show (dialog);
}
g_clear_object (&app_info);
g_object_unref (context);
}
typedef struct
{
gint x;
gint y;
} MenuPosition;
static void
context_menu_position (GtkMenu *menu,
gint *x,
gint *