From 6638e984ca743a7da842617346425b162bc1bb5d Mon Sep 17 00:00:00 2001 From: William Jon McCann Date: Sat, 30 Oct 2010 12:16:58 -0400 Subject: [PATCH] Initial import of sound panel Copied from gnome-media. See that module for git history. --- configure.ac | 45 +- panels/Makefile.am | 1 + panels/sound/Makefile.am | 120 + panels/sound/applet-main.c | 110 + panels/sound/cc-sound-panel.c | 136 + panels/sound/cc-sound-panel.h | 60 + panels/sound/data/Makefile.am | 44 + .../sound/data/gnome-sound-applet.desktop.in | 16 + .../data/gnome-sound-panel.desktop.in.in | 15 + panels/sound/data/icons/16x16/Makefile.am | 4 + .../sound/data/icons/16x16/apps/Makefile.am | 19 + .../16x16/apps/multimedia-volume-control.png | Bin 0 -> 917 bytes .../16x16/apps/multimedia-volume-control.svg | 585 + .../sound/data/icons/16x16/status/Makefile.am | 21 + .../status/audio-input-microphone-high.png | Bin 0 -> 1015 bytes .../status/audio-input-microphone-low.png | Bin 0 -> 1031 bytes .../status/audio-input-microphone-medium.png | Bin 0 -> 1040 bytes .../status/audio-input-microphone-muted.png | Bin 0 -> 855 bytes panels/sound/data/icons/22x22/Makefile.am | 4 + .../sound/data/icons/22x22/apps/Makefile.am | 19 + .../22x22/apps/multimedia-volume-control.png | Bin 0 -> 1424 bytes .../22x22/apps/multimedia-volume-control.svg | 595 + .../sound/data/icons/22x22/status/Makefile.am | 21 + .../status/audio-input-microphone-high.png | Bin 0 -> 1411 bytes .../status/audio-input-microphone-low.png | Bin 0 -> 1336 bytes .../status/audio-input-microphone-medium.png | Bin 0 -> 1342 bytes .../status/audio-input-microphone-muted.png | Bin 0 -> 1284 bytes panels/sound/data/icons/24x24/Makefile.am | 4 + .../sound/data/icons/24x24/apps/Makefile.am | 18 + .../24x24/apps/multimedia-volume-control.png | Bin 0 -> 1431 bytes .../sound/data/icons/24x24/status/Makefile.am | 21 + .../status/audio-input-microphone-high.png | Bin 0 -> 1441 bytes .../status/audio-input-microphone-low.png | Bin 0 -> 1365 bytes .../status/audio-input-microphone-medium.png | Bin 0 -> 1369 bytes .../status/audio-input-microphone-muted.png | Bin 0 -> 1349 bytes panels/sound/data/icons/32x32/Makefile.am | 4 + .../sound/data/icons/32x32/apps/Makefile.am | 19 + .../32x32/apps/multimedia-volume-control.png | Bin 0 -> 2308 bytes .../32x32/apps/multimedia-volume-control.svg | 633 + .../sound/data/icons/32x32/status/Makefile.am | 21 + .../status/audio-input-microphone-high.png | Bin 0 -> 1906 bytes .../status/audio-input-microphone-low.png | Bin 0 -> 2002 bytes .../status/audio-input-microphone-medium.png | Bin 0 -> 2011 bytes .../status/audio-input-microphone-muted.png | Bin 0 -> 1998 bytes panels/sound/data/icons/48x48/Makefile.am | 4 + .../sound/data/icons/48x48/apps/Makefile.am | 18 + .../48x48/apps/multimedia-volume-control.png | Bin 0 -> 3755 bytes .../status/audio-input-microphone-high.png | Bin 0 -> 3261 bytes .../status/audio-input-microphone-low.png | Bin 0 -> 3388 bytes .../status/audio-input-microphone-medium.png | Bin 0 -> 3406 bytes .../status/audio-input-microphone-muted.png | Bin 0 -> 3135 bytes panels/sound/data/icons/Makefile.am | 12 + panels/sound/data/icons/render-icon-theme.py | 169 + panels/sound/data/icons/scalable/Makefile.am | 4 + .../data/icons/scalable/apps/Makefile.am | 18 + .../apps/multimedia-volume-control.svg | 554 + .../data/icons/scalable/devices/Makefile.am | 35 + .../audio-speaker-center-back-testing.svg | 539 + .../devices/audio-speaker-center-back.svg | 506 + .../devices/audio-speaker-center-testing.svg | 537 + .../scalable/devices/audio-speaker-center.svg | 504 + .../audio-speaker-left-back-testing.svg | 537 + .../devices/audio-speaker-left-back.svg | 504 + .../audio-speaker-left-side-testing.svg | 537 + .../devices/audio-speaker-left-side.svg | 504 + .../devices/audio-speaker-left-testing.svg | 537 + .../scalable/devices/audio-speaker-left.svg | 504 + .../audio-speaker-right-back-testing.svg | 537 + .../devices/audio-speaker-right-back.svg | 504 + .../audio-speaker-right-side-testing.svg | 537 + .../devices/audio-speaker-right-side.svg | 504 + .../devices/audio-speaker-right-testing.svg | 913 ++ .../scalable/devices/audio-speaker-right.svg | 504 + .../devices/audio-speaker-testing.svg | 913 ++ .../devices/audio-subwoofer-testing.svg | 240 + .../scalable/devices/audio-subwoofer.svg | 325 + .../data/icons/src/microphone-levels.svg | 11448 ++++++++++++++++ panels/sound/data/sounds/Makefile.am | 29 + panels/sound/data/sounds/bark.ogg | Bin 0 -> 13322 bytes panels/sound/data/sounds/drip.ogg | Bin 0 -> 8495 bytes panels/sound/data/sounds/glass.ogg | Bin 0 -> 18999 bytes .../sounds/gnome-sounds-default.xml.in.in | 27 + panels/sound/data/sounds/sonar.ogg | Bin 0 -> 20011 bytes panels/sound/data/symbolic-icons/Makefile.am | 10 + panels/sound/data/symbolic-icons/r.rb | 73 + .../data/symbolic-icons/scalable/Makefile.am | 3 + .../scalable/status/Makefile.am | 14 + .../audio-input-microphone-high-symbolic.svg | 37 + .../audio-input-microphone-low-symbolic.svg | 37 + ...audio-input-microphone-medium-symbolic.svg | 37 + .../audio-input-microphone-muted-symbolic.svg | 37 + .../data/symbolic-icons/src/gnome-media.svg | 990 ++ panels/sound/gvc-applet.c | 311 + panels/sound/gvc-applet.h | 55 + panels/sound/gvc-balance-bar.c | 550 + panels/sound/gvc-balance-bar.h | 69 + panels/sound/gvc-channel-bar.c | 963 ++ panels/sound/gvc-channel-bar.h | 89 + panels/sound/gvc-channel-map-private.h | 39 + panels/sound/gvc-channel-map.c | 254 + panels/sound/gvc-channel-map.h | 73 + panels/sound/gvc-combo-box.c | 395 + panels/sound/gvc-combo-box.h | 67 + panels/sound/gvc-level-bar.c | 747 + panels/sound/gvc-level-bar.h | 75 + panels/sound/gvc-log.c | 62 + panels/sound/gvc-log.h | 35 + panels/sound/gvc-mixer-card-private.h | 35 + panels/sound/gvc-mixer-card.c | 506 + panels/sound/gvc-mixer-card.h | 83 + panels/sound/gvc-mixer-control-private.h | 35 + panels/sound/gvc-mixer-control.c | 2232 +++ panels/sound/gvc-mixer-control.h | 96 + panels/sound/gvc-mixer-dialog.c | 2091 +++ panels/sound/gvc-mixer-dialog.h | 56 + panels/sound/gvc-mixer-event-role.c | 250 + panels/sound/gvc-mixer-event-role.h | 57 + panels/sound/gvc-mixer-sink-input.c | 199 + panels/sound/gvc-mixer-sink-input.h | 57 + panels/sound/gvc-mixer-sink.c | 231 + panels/sound/gvc-mixer-sink.h | 57 + panels/sound/gvc-mixer-source-output.c | 137 + panels/sound/gvc-mixer-source-output.h | 57 + panels/sound/gvc-mixer-source.c | 231 + panels/sound/gvc-mixer-source.h | 57 + panels/sound/gvc-mixer-stream-private.h | 34 + panels/sound/gvc-mixer-stream.c | 944 ++ panels/sound/gvc-mixer-stream.h | 125 + panels/sound/gvc-pulseaudio-fake.h | 34 + panels/sound/gvc-sound-theme-chooser.c | 1145 ++ panels/sound/gvc-sound-theme-chooser.h | 54 + panels/sound/gvc-sound-theme-editor.c | 1397 ++ panels/sound/gvc-sound-theme-editor.h | 54 + panels/sound/gvc-speaker-test.c | 500 + panels/sound/gvc-speaker-test.h | 57 + panels/sound/gvc-stream-status-icon.c | 822 ++ panels/sound/gvc-stream-status-icon.h | 63 + panels/sound/sound-theme-file-utils.c | 305 + panels/sound/sound-theme-file-utils.h | 37 + po/POTFILES.in | 16 + po/POTFILES.skip | 2 + 141 files changed, 42114 insertions(+), 3 deletions(-) create mode 100644 panels/sound/Makefile.am create mode 100644 panels/sound/applet-main.c create mode 100644 panels/sound/cc-sound-panel.c create mode 100644 panels/sound/cc-sound-panel.h create mode 100644 panels/sound/data/Makefile.am create mode 100644 panels/sound/data/gnome-sound-applet.desktop.in create mode 100644 panels/sound/data/gnome-sound-panel.desktop.in.in create mode 100644 panels/sound/data/icons/16x16/Makefile.am create mode 100644 panels/sound/data/icons/16x16/apps/Makefile.am create mode 100644 panels/sound/data/icons/16x16/apps/multimedia-volume-control.png create mode 100644 panels/sound/data/icons/16x16/apps/multimedia-volume-control.svg create mode 100644 panels/sound/data/icons/16x16/status/Makefile.am create mode 100644 panels/sound/data/icons/16x16/status/audio-input-microphone-high.png create mode 100644 panels/sound/data/icons/16x16/status/audio-input-microphone-low.png create mode 100644 panels/sound/data/icons/16x16/status/audio-input-microphone-medium.png create mode 100644 panels/sound/data/icons/16x16/status/audio-input-microphone-muted.png create mode 100644 panels/sound/data/icons/22x22/Makefile.am create mode 100644 panels/sound/data/icons/22x22/apps/Makefile.am create mode 100644 panels/sound/data/icons/22x22/apps/multimedia-volume-control.png create mode 100644 panels/sound/data/icons/22x22/apps/multimedia-volume-control.svg create mode 100644 panels/sound/data/icons/22x22/status/Makefile.am create mode 100644 panels/sound/data/icons/22x22/status/audio-input-microphone-high.png create mode 100644 panels/sound/data/icons/22x22/status/audio-input-microphone-low.png create mode 100644 panels/sound/data/icons/22x22/status/audio-input-microphone-medium.png create mode 100644 panels/sound/data/icons/22x22/status/audio-input-microphone-muted.png create mode 100644 panels/sound/data/icons/24x24/Makefile.am create mode 100644 panels/sound/data/icons/24x24/apps/Makefile.am create mode 100644 panels/sound/data/icons/24x24/apps/multimedia-volume-control.png create mode 100644 panels/sound/data/icons/24x24/status/Makefile.am create mode 100644 panels/sound/data/icons/24x24/status/audio-input-microphone-high.png create mode 100644 panels/sound/data/icons/24x24/status/audio-input-microphone-low.png create mode 100644 panels/sound/data/icons/24x24/status/audio-input-microphone-medium.png create mode 100644 panels/sound/data/icons/24x24/status/audio-input-microphone-muted.png create mode 100644 panels/sound/data/icons/32x32/Makefile.am create mode 100644 panels/sound/data/icons/32x32/apps/Makefile.am create mode 100644 panels/sound/data/icons/32x32/apps/multimedia-volume-control.png create mode 100644 panels/sound/data/icons/32x32/apps/multimedia-volume-control.svg create mode 100644 panels/sound/data/icons/32x32/status/Makefile.am create mode 100644 panels/sound/data/icons/32x32/status/audio-input-microphone-high.png create mode 100644 panels/sound/data/icons/32x32/status/audio-input-microphone-low.png create mode 100644 panels/sound/data/icons/32x32/status/audio-input-microphone-medium.png create mode 100644 panels/sound/data/icons/32x32/status/audio-input-microphone-muted.png create mode 100644 panels/sound/data/icons/48x48/Makefile.am create mode 100644 panels/sound/data/icons/48x48/apps/Makefile.am create mode 100644 panels/sound/data/icons/48x48/apps/multimedia-volume-control.png create mode 100644 panels/sound/data/icons/48x48/status/audio-input-microphone-high.png create mode 100644 panels/sound/data/icons/48x48/status/audio-input-microphone-low.png create mode 100644 panels/sound/data/icons/48x48/status/audio-input-microphone-medium.png create mode 100644 panels/sound/data/icons/48x48/status/audio-input-microphone-muted.png create mode 100644 panels/sound/data/icons/Makefile.am create mode 100755 panels/sound/data/icons/render-icon-theme.py create mode 100644 panels/sound/data/icons/scalable/Makefile.am create mode 100644 panels/sound/data/icons/scalable/apps/Makefile.am create mode 100644 panels/sound/data/icons/scalable/apps/multimedia-volume-control.svg create mode 100644 panels/sound/data/icons/scalable/devices/Makefile.am create mode 100644 panels/sound/data/icons/scalable/devices/audio-speaker-center-back-testing.svg create mode 100644 panels/sound/data/icons/scalable/devices/audio-speaker-center-back.svg create mode 100644 panels/sound/data/icons/scalable/devices/audio-speaker-center-testing.svg create mode 100644 panels/sound/data/icons/scalable/devices/audio-speaker-center.svg create mode 100644 panels/sound/data/icons/scalable/devices/audio-speaker-left-back-testing.svg create mode 100644 panels/sound/data/icons/scalable/devices/audio-speaker-left-back.svg create mode 100644 panels/sound/data/icons/scalable/devices/audio-speaker-left-side-testing.svg create mode 100644 panels/sound/data/icons/scalable/devices/audio-speaker-left-side.svg create mode 100644 panels/sound/data/icons/scalable/devices/audio-speaker-left-testing.svg create mode 100644 panels/sound/data/icons/scalable/devices/audio-speaker-left.svg create mode 100644 panels/sound/data/icons/scalable/devices/audio-speaker-right-back-testing.svg create mode 100644 panels/sound/data/icons/scalable/devices/audio-speaker-right-back.svg create mode 100644 panels/sound/data/icons/scalable/devices/audio-speaker-right-side-testing.svg create mode 100644 panels/sound/data/icons/scalable/devices/audio-speaker-right-side.svg create mode 100644 panels/sound/data/icons/scalable/devices/audio-speaker-right-testing.svg create mode 100644 panels/sound/data/icons/scalable/devices/audio-speaker-right.svg create mode 100644 panels/sound/data/icons/scalable/devices/audio-speaker-testing.svg create mode 100644 panels/sound/data/icons/scalable/devices/audio-subwoofer-testing.svg create mode 100644 panels/sound/data/icons/scalable/devices/audio-subwoofer.svg create mode 100644 panels/sound/data/icons/src/microphone-levels.svg create mode 100644 panels/sound/data/sounds/Makefile.am create mode 100644 panels/sound/data/sounds/bark.ogg create mode 100644 panels/sound/data/sounds/drip.ogg create mode 100644 panels/sound/data/sounds/glass.ogg create mode 100644 panels/sound/data/sounds/gnome-sounds-default.xml.in.in create mode 100644 panels/sound/data/sounds/sonar.ogg create mode 100644 panels/sound/data/symbolic-icons/Makefile.am create mode 100755 panels/sound/data/symbolic-icons/r.rb create mode 100644 panels/sound/data/symbolic-icons/scalable/Makefile.am create mode 100644 panels/sound/data/symbolic-icons/scalable/status/Makefile.am create mode 100644 panels/sound/data/symbolic-icons/scalable/status/audio-input-microphone-high-symbolic.svg create mode 100644 panels/sound/data/symbolic-icons/scalable/status/audio-input-microphone-low-symbolic.svg create mode 100644 panels/sound/data/symbolic-icons/scalable/status/audio-input-microphone-medium-symbolic.svg create mode 100644 panels/sound/data/symbolic-icons/scalable/status/audio-input-microphone-muted-symbolic.svg create mode 100644 panels/sound/data/symbolic-icons/src/gnome-media.svg create mode 100644 panels/sound/gvc-applet.c create mode 100644 panels/sound/gvc-applet.h create mode 100644 panels/sound/gvc-balance-bar.c create mode 100644 panels/sound/gvc-balance-bar.h create mode 100644 panels/sound/gvc-channel-bar.c create mode 100644 panels/sound/gvc-channel-bar.h create mode 100644 panels/sound/gvc-channel-map-private.h create mode 100644 panels/sound/gvc-channel-map.c create mode 100644 panels/sound/gvc-channel-map.h create mode 100644 panels/sound/gvc-combo-box.c create mode 100644 panels/sound/gvc-combo-box.h create mode 100644 panels/sound/gvc-level-bar.c create mode 100644 panels/sound/gvc-level-bar.h create mode 100644 panels/sound/gvc-log.c create mode 100644 panels/sound/gvc-log.h create mode 100644 panels/sound/gvc-mixer-card-private.h create mode 100644 panels/sound/gvc-mixer-card.c create mode 100644 panels/sound/gvc-mixer-card.h create mode 100644 panels/sound/gvc-mixer-control-private.h create mode 100644 panels/sound/gvc-mixer-control.c create mode 100644 panels/sound/gvc-mixer-control.h create mode 100644 panels/sound/gvc-mixer-dialog.c create mode 100644 panels/sound/gvc-mixer-dialog.h create mode 100644 panels/sound/gvc-mixer-event-role.c create mode 100644 panels/sound/gvc-mixer-event-role.h create mode 100644 panels/sound/gvc-mixer-sink-input.c create mode 100644 panels/sound/gvc-mixer-sink-input.h create mode 100644 panels/sound/gvc-mixer-sink.c create mode 100644 panels/sound/gvc-mixer-sink.h create mode 100644 panels/sound/gvc-mixer-source-output.c create mode 100644 panels/sound/gvc-mixer-source-output.h create mode 100644 panels/sound/gvc-mixer-source.c create mode 100644 panels/sound/gvc-mixer-source.h create mode 100644 panels/sound/gvc-mixer-stream-private.h create mode 100644 panels/sound/gvc-mixer-stream.c create mode 100644 panels/sound/gvc-mixer-stream.h create mode 100644 panels/sound/gvc-pulseaudio-fake.h create mode 100644 panels/sound/gvc-sound-theme-chooser.c create mode 100644 panels/sound/gvc-sound-theme-chooser.h create mode 100644 panels/sound/gvc-sound-theme-editor.c create mode 100644 panels/sound/gvc-sound-theme-editor.h create mode 100644 panels/sound/gvc-speaker-test.c create mode 100644 panels/sound/gvc-speaker-test.h create mode 100644 panels/sound/gvc-stream-status-icon.c create mode 100644 panels/sound/gvc-stream-status-icon.h create mode 100644 panels/sound/sound-theme-file-utils.c create mode 100644 panels/sound/sound-theme-file-utils.h diff --git a/configure.ac b/configure.ac index 83a30cb3d..72f60a19f 100644 --- a/configure.ac +++ b/configure.ac @@ -75,13 +75,19 @@ dnl ============================================== dnl Check that we meet the dependencies dnl ============================================== -COMMON_MODULES="gtk+-3.0 >= 2.90.0 - glib-2.0 >= 2.25.11 +GLIB_REQUIRED_VERSION=2.25.11 +GTK_REQUIRED_VERSION=2.91.0 +DESKTOP_SCHEMAS_REQUIRED_VERSION=0.0.2 +PA_REQUIRED_VERSION=0.9.16 +CANBERRA_REQUIRED_VERSION=0.13 + +COMMON_MODULES="gtk+-3.0 >= $GTK_REQUIRED_VERSION + glib-2.0 >= $GLIB_REQUIRED_VERSION gthread-2.0 gio-2.0 gconf-2.0 libxml-2.0 - gsettings-desktop-schemas >= 0.0.2" + gsettings-desktop-schemas >= $DESKTOP_SCHEMAS_REQUIRED_VERSION" PKG_CHECK_MODULES(CAPPLET, $COMMON_MODULES) PKG_CHECK_MODULES(GNOMECC_SHELL, $COMMON_MODULES libgnome-menu gio-unix-2.0) PKG_CHECK_MODULES(DBUS, dbus-1 dbus-glib-1) @@ -90,6 +96,14 @@ PKG_CHECK_MODULES(DEFAULT_APPLICATIONS_CAPPLET, libxml-2.0) PKG_CHECK_MODULES(GSD_DBUS, gnome-settings-daemon) PKG_CHECK_MODULES(GIO, gio-2.0) PKG_CHECK_MODULES(XML, libxml-2.0) +PKG_CHECK_MODULES(CANBERRA, libcanberra-gtk3 >= $CANBERRA_REQUIRED_VERSION) +AC_SUBST(CANBERRA_CFLAGS) +AC_SUBST(CANBERRA_LIBS) +PKG_CHECK_MODULES(PULSEAUDIO, + libpulse >= $PA_REQUIRED_VERSION + libpulse-mainloop-glib >= $PA_REQUIRED_VERSION) +AC_SUBST(PULSEAUDIO_CFLAGS) +AC_SUBST(PULSEAUDIO_LIBS) gtk_lib_dir=`$PKG_CONFIG --variable libdir gtk+-3.0` gtk_binary_version=`$PKG_CONFIG --variable gtk_binary_version gtk+-3.0` @@ -320,6 +334,31 @@ panels/mouse/Makefile panels/mouse/gnome-mouse-panel.desktop.in panels/network/Makefile panels/network/gnome-network-panel.desktop.in +panels/sound/Makefile +panels/sound/data/Makefile +panels/sound/data/gnome-sound-panel.desktop.in +panels/sound/data/symbolic-icons/Makefile +panels/sound/data/symbolic-icons/scalable/Makefile +panels/sound/data/symbolic-icons/scalable/status/Makefile +panels/sound/data/icons/Makefile +panels/sound/data/icons/16x16/Makefile +panels/sound/data/icons/16x16/apps/Makefile +panels/sound/data/icons/16x16/status/Makefile +panels/sound/data/icons/22x22/Makefile +panels/sound/data/icons/22x22/apps/Makefile +panels/sound/data/icons/22x22/status/Makefile +panels/sound/data/icons/24x24/Makefile +panels/sound/data/icons/24x24/apps/Makefile +panels/sound/data/icons/24x24/status/Makefile +panels/sound/data/icons/32x32/Makefile +panels/sound/data/icons/32x32/apps/Makefile +panels/sound/data/icons/32x32/status/Makefile +panels/sound/data/icons/48x48/Makefile +panels/sound/data/icons/48x48/apps/Makefile +panels/sound/data/icons/scalable/Makefile +panels/sound/data/icons/scalable/apps/Makefile +panels/sound/data/icons/scalable/devices/Makefile +panels/sound/data/sounds/Makefile panels/universal-access/Makefile panels/universal-access/gnome-universal-access-panel.desktop.in po/Makefile.in diff --git a/panels/Makefile.am b/panels/Makefile.am index 09814eea4..225fcbc2e 100644 --- a/panels/Makefile.am +++ b/panels/Makefile.am @@ -4,6 +4,7 @@ SUBDIRS= \ mouse \ keyboard \ network \ + sound \ default-applications \ keybindings \ universal-access \ diff --git a/panels/sound/Makefile.am b/panels/sound/Makefile.am new file mode 100644 index 000000000..c1ec3a77c --- /dev/null +++ b/panels/sound/Makefile.am @@ -0,0 +1,120 @@ +SUBDIRS = data + +# This is used in GNOMECC_CAPPLETS_CFLAGS +cappletname = sound +NULL = + +ccpanelsdir = $(PANELS_DIR) +ccpanels_LTLIBRARIES = libsound.la + +bin_PROGRAMS = \ + gnome-sound-applet \ + $(NULL) + +AM_CPPFLAGS = \ + $(GNOMECC_CAPPLETS_CFLAGS) \ + $(WARN_CFLAGS) \ + $(CANBERRA_CFLAGS) \ + $(PANEL_CFLAGS) \ + $(DISABLE_DEPRECATED) \ + $(PULSEAUDIO_CFLAGS) \ + -DLOCALE_DIR=\""$(datadir)/locale"\" \ + -DLIBEXECDIR=\"$(libexecdir)\" \ + -DGLADEDIR=\""$(pkgdatadir)"\" \ + -DSOUND_DATA_DIR="\"$(datadir)/sounds\"" \ + -DSOUND_SET_DIR="\"$(pkgdatadir)/sounds\"" \ + -DICON_DATA_DIR="\"$(pkgdatadir)/icons\"" \ + $(NULL) + +noinst_LTLIBRARIES = libgnomevolumecontrol.la +libgnomevolumecontrol_la_SOURCES = \ + gvc-mixer-card.h \ + gvc-mixer-card.c \ + gvc-mixer-card-private.h \ + gvc-mixer-stream.h \ + gvc-mixer-stream.c \ + gvc-mixer-stream-private.h \ + gvc-channel-map.h \ + gvc-channel-map.c \ + gvc-channel-map-private.h \ + gvc-mixer-sink.h \ + gvc-mixer-sink.c \ + gvc-mixer-source.h \ + gvc-mixer-source.c \ + gvc-mixer-sink-input.h \ + gvc-mixer-sink-input.c \ + gvc-mixer-source-output.h \ + gvc-mixer-source-output.c \ + gvc-mixer-event-role.h \ + gvc-mixer-event-role.c \ + gvc-mixer-control.h \ + gvc-mixer-control.c \ + gvc-mixer-control-private.h \ + gvc-channel-bar.h \ + gvc-channel-bar.c \ + gvc-log.h \ + gvc-log.c \ + gvc-pulseaudio-fake.h \ + $(NULL) + +gnome_sound_applet_LDADD = \ + -lm \ + libgnomevolumecontrol.la \ + $(CANBERRA_LIBS) \ + $(PULSEAUDIO_LIBS) \ + $(NULL) + +gnome_sound_applet_SOURCES = \ + gvc-stream-status-icon.h \ + gvc-stream-status-icon.c \ + gvc-applet.h \ + gvc-applet.c \ + applet-main.c \ + $(NULL) + +libsound_la_LIBADD = \ + -lm \ + libgnomevolumecontrol.la \ + $(PANEL_LIBS) \ + $(PULSEAUDIO_LIBS) \ + $(NULL) + +libsound_la_LDFLAGS = \ + $(PANEL_LDFLAGS) \ + $(CANBERRA_LIBS) \ + $(PULSEAUDIO_LIBS) \ + $(NULL) + +libsound_la_SOURCES = \ + gvc-balance-bar.h \ + gvc-balance-bar.c \ + gvc-mixer-dialog.h \ + gvc-mixer-dialog.c \ + gvc-level-bar.h \ + gvc-level-bar.c \ + gvc-combo-box.h \ + gvc-combo-box.c \ + gvc-speaker-test.h \ + gvc-speaker-test.c \ + gvc-sound-theme-chooser.c \ + gvc-sound-theme-chooser.h \ + sound-theme-file-utils.c \ + sound-theme-file-utils.h \ + cc-sound-panel.c \ + cc-sound-panel.h \ + $(NULL) + +EXTRA_DIST = gvc-sound-theme-editor.c gvc-sound-theme-editor.h + +BUILT_SOURCES = \ + $(NULL) + +CLEANFILES = \ + $(BUILT_SOURCES) \ + $(NULL) + +MAINTAINERCLEANFILES = \ + *~ \ + Makefile.in + +-include $(top_srcdir)/git.mk diff --git a/panels/sound/applet-main.c b/panels/sound/applet-main.c new file mode 100644 index 000000000..0335fa143 --- /dev/null +++ b/panels/sound/applet-main.c @@ -0,0 +1,110 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Red Hat, Inc. + * + * 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 + * Lesser 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 +#include + +#include "gvc-applet.h" +#include "gvc-log.h" + +#define GVCA_DBUS_NAME "org.gnome.VolumeControlApplet" + +static gboolean show_version = FALSE; +static gboolean debug = FALSE; + +int +main (int argc, char **argv) +{ + GError *error; + GvcApplet *applet; + GApplication *app = NULL; + static GOptionEntry entries[] = { + { "debug", 0, 0, G_OPTION_ARG_NONE, &debug, N_("Enable debugging code"), NULL }, + { "version", 0, 0, G_OPTION_ARG_NONE, &show_version, N_("Version of this application"), NULL }, + { NULL, 0, 0, 0, NULL, NULL, NULL } + }; + + bindtextdomain (GETTEXT_PACKAGE, LOCALE_DIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + + gvc_log_init (); + + error = NULL; + gtk_init_with_args (&argc, &argv, + (char *) _(" — GNOME Volume Control Applet"), + entries, GETTEXT_PACKAGE, + &error); + + if (error != NULL) { + g_warning ("%s", error->message); + exit (1); + } + + if (show_version) { + g_print ("%s %s\n", argv [0], VERSION); + exit (1); + } + + gvc_log_set_debug (debug); + + if (debug == FALSE) { + GError *error = NULL; + + app = g_application_new (GVCA_DBUS_NAME, + G_APPLICATION_FLAGS_NONE); + if (!g_application_register (app, NULL, &error)) { + g_warning ("%s", error->message); + g_error_free (error); + return 1; + } + if (g_application_get_is_remote (app)) { + g_warning ("Applet is already running, exiting"); + return 0; + } + } + + gtk_icon_theme_append_search_path (gtk_icon_theme_get_default (), + ICON_DATA_DIR); + + applet = gvc_applet_new (); + gvc_applet_start (applet); + + gtk_main (); + + if (applet != NULL) { + g_object_unref (applet); + } + if (app != NULL) { + g_object_unref (app); + } + + return 0; +} diff --git a/panels/sound/cc-sound-panel.c b/panels/sound/cc-sound-panel.c new file mode 100644 index 000000000..1ec93c870 --- /dev/null +++ b/panels/sound/cc-sound-panel.c @@ -0,0 +1,136 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Red Hat, Inc. + * + * 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 + * Lesser 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 +#include + +#include "cc-sound-panel.h" +#include "gvc-mixer-dialog.h" +#include "gvc-log.h" + +G_DEFINE_DYNAMIC_TYPE (CcSoundPanel, cc_sound_panel, CC_TYPE_PANEL) + +static void cc_sound_panel_finalize (GObject *object); + +static void +cc_sound_panel_class_init (CcSoundPanelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = cc_sound_panel_finalize; +} + +static void +cc_sound_panel_class_finalize (CcSoundPanelClass *klass) +{ +} + +static void +cc_sound_panel_finalize (GObject *object) +{ + CcSoundPanel *panel = CC_SOUND_PANEL (object); + + if (panel->dialog != NULL) + panel->dialog = NULL; + if (panel->connecting_label != NULL) + panel->connecting_label = NULL; + if (panel->control != NULL) { + g_object_unref (panel->control); + panel->control = NULL; + } + + G_OBJECT_CLASS (cc_sound_panel_parent_class)->finalize (object); +} + +static void +on_control_ready (GvcMixerControl *control, + CcSoundPanel *panel) +{ + if (panel->dialog != NULL) + return; + + if (panel->connecting_label) { + gtk_widget_destroy (panel->connecting_label); + panel->connecting_label = NULL; + } + + panel->dialog = gvc_mixer_dialog_new (control); + gtk_container_add (GTK_CONTAINER (panel), + GTK_WIDGET (panel->dialog)); + gtk_widget_show (GTK_WIDGET (panel->dialog)); +} + +static void +cc_sound_panel_init (CcSoundPanel *self) +{ + gvc_log_init (); + gvc_log_set_debug (TRUE); + + gtk_icon_theme_append_search_path (gtk_icon_theme_get_default (), + ICON_DATA_DIR); + gtk_window_set_default_icon_name ("multimedia-volume-control"); + + self->control = gvc_mixer_control_new ("GNOME Volume Control Dialog"); + g_signal_connect (self->control, + "ready", + G_CALLBACK (on_control_ready), + self); + gvc_mixer_control_open (self->control); + + self->connecting_label = gtk_label_new (_("Waiting for sound system to respond")); + gtk_container_add (GTK_CONTAINER (self), self->connecting_label); + gtk_widget_show (self->connecting_label); +} + +void +cc_sound_panel_register (GIOModule *module) +{ + cc_sound_panel_register_type (G_TYPE_MODULE (module)); + g_io_extension_point_implement (CC_SHELL_PANEL_EXTENSION_POINT, + CC_TYPE_SOUND_PANEL, + "sound", 0); +} + +/* GIO extension stuff */ +void +g_io_module_load (GIOModule *module) +{ + bindtextdomain (GETTEXT_PACKAGE, LOCALE_DIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + + /* register the panel */ + cc_sound_panel_register (module); +} + +void +g_io_module_unload (GIOModule *module) +{ +} + diff --git a/panels/sound/cc-sound-panel.h b/panels/sound/cc-sound-panel.h new file mode 100644 index 000000000..fda5fb7e7 --- /dev/null +++ b/panels/sound/cc-sound-panel.h @@ -0,0 +1,60 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2010 Red Hat, Inc. + * + * 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 + * Lesser 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. + */ + +#ifndef _CC_SOUND_PANEL_H +#define _CC_SOUND_PANEL_H + +#include +#include "gvc-mixer-control.h" +#include "gvc-mixer-dialog.h" + +G_BEGIN_DECLS + +#define CC_TYPE_SOUND_PANEL cc_sound_panel_get_type() +#define CC_SOUND_PANEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CC_TYPE_SOUND_PANEL, CcSoundPanel)) +#define CC_SOUND_PANEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CC_TYPE_SOUND_PANEL, CcSoundPanelClass)) +#define CC_IS_SOUND_PANEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CC_TYPE_SOUND_PANEL)) +#define CC_IS_SOUND_PANEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CC_TYPE_SOUND_PANEL)) +#define CC_SOUND_PANEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CC_TYPE_SOUND_PANEL, CcSoundPanelClass)) + +typedef struct _CcSoundPanel CcSoundPanel; +typedef struct _CcSoundPanelClass CcSoundPanelClass; +typedef struct _CcSoundPanelPrivate CcSoundPanelPrivate; + +struct _CcSoundPanel { + CcPanel parent; + + GvcMixerControl *control; + GvcMixerDialog *dialog; + GtkWidget *connecting_label; +}; + +struct _CcSoundPanelClass { + CcPanelClass parent_class; +}; + +GType cc_sound_panel_get_type (void) G_GNUC_CONST; + +void cc_sound_panel_register (GIOModule *module); + +G_END_DECLS + +#endif /* _CC_SOUND_PANEL_H */ + diff --git a/panels/sound/data/Makefile.am b/panels/sound/data/Makefile.am new file mode 100644 index 000000000..d5522b2c8 --- /dev/null +++ b/panels/sound/data/Makefile.am @@ -0,0 +1,44 @@ +NULL = + +SUBDIRS = \ + icons \ + symbolic-icons \ + sounds \ + $(NULL) + +@INTLTOOL_DESKTOP_RULE@ +autostartdir = $(sysconfdir)/xdg/autostart +autostart_in_files = gnome-sound-applet.desktop.in +autostart_DATA = $(autostart_in_files:.desktop.in=.desktop) + +appsdir = $(datadir)/applications +apps_in_files = gnome-sound-panel.desktop.in +apps_DATA = $(apps_in_files:.desktop.in=.desktop) + +EXTRA_DIST = \ + $(autostart_in_files) \ + gnome-sound-panel.desktop.in.in \ + $(NULL) + +gtk_update_icon_cache = gtk-update-icon-cache -f -t $(datadir)/icons/hicolor + +install-data-hook: update-icon-cache +uninstall-hook: update-icon-cache +update-icon-cache: + @-if test -z "$(DESTDIR)"; then \ + echo "Updating Gtk icon cache."; \ + $(gtk_update_icon_cache); \ + else \ + echo "*** Icon cache not updated. After (un)install, run this:"; \ + echo "*** $(gtk_update_icon_cache)"; \ + fi + +CLEANFILES = \ + gnome-sound-panel.desktop \ + $(NULL) + +DISTCLEANFILES = \ + gnome-sound-applet.desktop \ + $(NULL) + +-include $(top_srcdir)/git.mk diff --git a/panels/sound/data/gnome-sound-applet.desktop.in b/panels/sound/data/gnome-sound-applet.desktop.in new file mode 100644 index 000000000..29792563f --- /dev/null +++ b/panels/sound/data/gnome-sound-applet.desktop.in @@ -0,0 +1,16 @@ +[Desktop Entry] +_Name=Volume Control +_Comment=Show desktop volume control +Icon=multimedia-volume-control +Exec=gnome-sound-applet +Terminal=false +Type=Application +Categories= +NoDisplay=true +OnlyShowIn=GNOME;XFCE; +X-GNOME-Bugzilla-Bugzilla=GNOME +X-GNOME-Bugzilla-Product=gnome-control-center +X-GNOME-Bugzilla-Component=sound +# See http://bugzilla.gnome.org/show_bug.cgi?id=568320 +#X-GNOME-Autostart-Phase=Panel +X-GNOME-Autostart-Notify=true diff --git a/panels/sound/data/gnome-sound-panel.desktop.in.in b/panels/sound/data/gnome-sound-panel.desktop.in.in new file mode 100644 index 000000000..af1ab074c --- /dev/null +++ b/panels/sound/data/gnome-sound-panel.desktop.in.in @@ -0,0 +1,15 @@ +[Desktop Entry] +_Name=Sound +_Comment=Change sound volume and sound events +Exec=gnome-control-center sound +Icon=multimedia-volume-control +Terminal=false +Type=Application +StartupNotify=true +Categories=GNOME;GTK;Settings;HardwareSettings;X-GNOME-Settings-Panel; +OnlyShowIn=GNOME; +X-GNOME-Bugzilla-Bugzilla=GNOME +X-GNOME-Bugzilla-Product=gnome-control-center +X-GNOME-Bugzilla-Component=sound +X-GNOME-Bugzilla-Version=@VERSION@ +X-GNOME-Settings-Panel=sound diff --git a/panels/sound/data/icons/16x16/Makefile.am b/panels/sound/data/icons/16x16/Makefile.am new file mode 100644 index 000000000..9ed74c7bf --- /dev/null +++ b/panels/sound/data/icons/16x16/Makefile.am @@ -0,0 +1,4 @@ +SUBDIRS = status apps + + +-include $(top_srcdir)/git.mk diff --git a/panels/sound/data/icons/16x16/apps/Makefile.am b/panels/sound/data/icons/16x16/apps/Makefile.am new file mode 100644 index 000000000..613c6b21d --- /dev/null +++ b/panels/sound/data/icons/16x16/apps/Makefile.am @@ -0,0 +1,19 @@ +NULL = + +themedir = $(datadir)/icons/hicolor +size = 16x16 +context = apps + +iconsdir = $(themedir)/$(size)/$(context) + +icons_DATA = \ + multimedia-volume-control.png \ + multimedia-volume-control.svg + $(NULL) + +EXTRA_DIST = \ + $(icons_DATA) \ + $(NULL) + + +-include $(top_srcdir)/git.mk diff --git a/panels/sound/data/icons/16x16/apps/multimedia-volume-control.png b/panels/sound/data/icons/16x16/apps/multimedia-volume-control.png new file mode 100644 index 0000000000000000000000000000000000000000..13b4b7f9adab2a33ac1f8bd941f4e4b05ac93b5e GIT binary patch literal 917 zcmV;G18V$bWdN!D{rO{$$9-a;|1qKK8m3vMSgib$rr@hRZ_;X6T(~%{-i!UZwU3reXGS`7 zedO?9hV{xCrfKq!T;f^BBtn2%U&KaGD9n@cRCp);hY(FrD>HWhQp&dy2llhsXkeNq znotaB<8=2#3EVe8yEF+x5()S?y#6g+U15Up=djE?LSQ>~cXz*=O7uz7ETfc-+w0@D zGlH*jgX(;qr45}+ks+G?7!^kl_v#GQxje;E9S!?64RGjs2d!2Mm&=XI^AeE~lG{1lgRqaSG?s^-e7-!Kf6vY9Is357$9oH&l# z>0~sU<@XC&YPA|qKlLOlrb*eXfDlyxsg(M8W_FHyc13VF9Mm@Ign~hOV?DH5O*CD@ z?Q(K!xrEo_#V~>tZ{EVT?K1%4;GzDw6k;kKzrQn;c$nLb2GiGXuvV>7Sz9L@3b3cQ zm*JO&sMYEe7MJ+q%To=j-99iemYWqyDU#{TQ7ObX(P*TzZ~s1mK?B3^u`3)#NC)-$ zpC~1mpD%Fs+%KD(&F1jLSndaaty4~>Ge@M9-*9hE}#(Z>{b00000NkvXXu0mjfR_Ce8 literal 0 HcmV?d00001 diff --git a/panels/sound/data/icons/16x16/apps/multimedia-volume-control.svg b/panels/sound/data/icons/16x16/apps/multimedia-volume-control.svg new file mode 100644 index 000000000..f8014bfa7 --- /dev/null +++ b/panels/sound/data/icons/16x16/apps/multimedia-volume-control.svg @@ -0,0 +1,585 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Sound CC applet + 17.02.2007 + + + Josef vybíral + + + + + Josef vybíral + + + http://blog.vybiral.info + http://blog.vybiral.info + sound, reproductor, note + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/panels/sound/data/icons/16x16/status/Makefile.am b/panels/sound/data/icons/16x16/status/Makefile.am new file mode 100644 index 000000000..9ca7ba0b4 --- /dev/null +++ b/panels/sound/data/icons/16x16/status/Makefile.am @@ -0,0 +1,21 @@ +NULL = + +themedir = $(pkgdatadir)/icons/hicolor +size = 16x16 +context = status + +iconsdir = $(themedir)/$(size)/$(context) + +icons_DATA = \ + audio-input-microphone-high.png \ + audio-input-microphone-low.png \ + audio-input-microphone-medium.png \ + audio-input-microphone-muted.png \ + $(NULL) + +EXTRA_DIST = \ + $(icons_DATA) \ + $(NULL) + + +-include $(top_srcdir)/git.mk diff --git a/panels/sound/data/icons/16x16/status/audio-input-microphone-high.png b/panels/sound/data/icons/16x16/status/audio-input-microphone-high.png new file mode 100644 index 0000000000000000000000000000000000000000..fc2c114aaee9e385d9e443da565515d4f736cbc6 GIT binary patch literal 1015 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 literal 0 HcmV?d00001 diff --git a/panels/sound/data/icons/22x22/Makefile.am b/panels/sound/data/icons/22x22/Makefile.am new file mode 100644 index 000000000..9ed74c7bf --- /dev/null +++ b/panels/sound/data/icons/22x22/Makefile.am @@ -0,0 +1,4 @@ +SUBDIRS = status apps + + +-include $(top_srcdir)/git.mk diff --git a/panels/sound/data/icons/22x22/apps/Makefile.am b/panels/sound/data/icons/22x22/apps/Makefile.am new file mode 100644 index 000000000..84943a41f --- /dev/null +++ b/panels/sound/data/icons/22x22/apps/Makefile.am @@ -0,0 +1,19 @@ +NULL = + +themedir = $(datadir)/icons/hicolor +size = 22x22 +context = apps + +iconsdir = $(themedir)/$(size)/$(context) + +icons_DATA = \ + multimedia-volume-control.png \ + multimedia-volume-control.svg + $(NULL) + +EXTRA_DIST = \ + $(icons_DATA) \ + $(NULL) + + +-include $(top_srcdir)/git.mk diff --git a/panels/sound/data/icons/22x22/apps/multimedia-volume-control.png b/panels/sound/data/icons/22x22/apps/multimedia-volume-control.png new file mode 100644 index 0000000000000000000000000000000000000000..8d55f04d2c1300d59e0e0f851d8c399b8baf0ad1 GIT binary patch literal 1424 zcmV;B1#kL^P)oeoEf;np%-38s_P&E$io*3sx+DbbLS`2`Y14$%h^rCJYyMhva55y zw70hti$;jWVgShU6{KF`_PGgA<)JVI0>C`T9aB!ifFinpY|*>)ZIo~=T`s-0v|il7FYmk@M0#Dk)`D&s?{n0rfJ~V z4vuYMh_R~_pmJ}XDlU<53x=^*CzKZQsA0~DsxtD}R4@#| zb!=S6!F3%#Sg+f>zS)oO_mWN~iAKX1wh;Dx>P?G3Hcy}p$c>Hh#$VnvuTNfH*eM?3 zl`9o17K>4?l(B6a$FV_#&HH7ZfA$+Z`Quf!!P_9&HZH+TJGRn%@I=_7VSGu`T zJaspppHS6}U9gBG28KpXyN>h2uKisyHZqJDLZwn6@O^yW!*OgPkqFU9l-|BRV$m3X zK0CobuU^%y?d{*qPEVZ%K2+86UT_>pcK7wY7;j7bLIk5H+f7GDhIBGXXGexm$feQn z@O+1M?s($K1>Rx+>}ih+*LQ z!A?gS2C4xf!gwyH7cWmXu1{Y2Do_H}fmK!g?>tu3x`;dgZ1scpYmNF-r+@x4GZKx; zjp-@9aeWHUYm!c<=<4d!bTTb(pL^E}{N_J^O`r@E^r6!KNe~f<$6|@D1B1Q4>FDVA z!b>ka7fL0QcwU31=TRz^SX#cPlUJ|R*VYPm^0#ikyuDps0ajGCxy$)~(Ig@c(B6A+ z@abs0^?Qb491enDAAlIfMiBTn%NzH9fA`y)^DZsSAhz0000 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Sound CC applet + 17.02.2007 + + + Josef vybíral + + + + + Josef vybíral + + + http://blog.vybiral.info + http://blog.vybiral.info + sound, reproductor, note + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/panels/sound/data/icons/22x22/status/Makefile.am b/panels/sound/data/icons/22x22/status/Makefile.am new file mode 100644 index 000000000..d855c3814 --- /dev/null +++ b/panels/sound/data/icons/22x22/status/Makefile.am @@ -0,0 +1,21 @@ +NULL = + +themedir = $(pkgdatadir)/icons/hicolor +size = 22x22 +context = status + +iconsdir = $(themedir)/$(size)/$(context) + +icons_DATA = \ + audio-input-microphone-high.png \ + audio-input-microphone-low.png \ + audio-input-microphone-medium.png \ + audio-input-microphone-muted.png \ + $(NULL) + +EXTRA_DIST = \ + $(icons_DATA) \ + $(NULL) + + +-include $(top_srcdir)/git.mk diff --git a/panels/sound/data/icons/22x22/status/audio-input-microphone-high.png b/panels/sound/data/icons/22x22/status/audio-input-microphone-high.png new file mode 100644 index 0000000000000000000000000000000000000000..d04854be8cff65a7a547a3b9180f1cd62dac3323 GIT binary patch literal 1411 zcmV-}1$_F6P)Jno%)`k^H-j7e9rbCPrRTIVEZ z?L{c1$b_G6T-`w_-3me+00gSguU=YHbu3eF%r&P#VM!BM&1*!7Uu9@&7s({tcD?12 zMx|M;)9dvt%R-?PVk9Ca60!CdS68h|?+?0+=eY9fO}gsStk_zoB>(vC5;N#Sn%PaH?OBO>Ad`P8a*ladkc2>^Hw(G66>6~a>S;w+ImiBxA6>&3STv|Izm?~sn z*`lR7T}}=j_V!}=v(G^ge4rG_vsm;DOLnJo;*#=%uj8&H-noY1-*3wanO^47n<9}& zF~_QK@7_IFEOXKQPdAE-iop-^Sh8#>qGEJ@I){--L%=_Y5#hlGt%_|ntJr;+gt>Sk zjy%g;bai#X?y!OPcu-PO0s?|?IIK?R6df1Gih#{{T#qlCEi6J&Qt0gJgu$eT+wF$U zZbN^6KO`xMNFkSaa2%OHc zN=+7*YC3WV9LJ4q4L4P$`l5{JRj~N2m+S}KQ zisco^H5pGWUg$jh%dKA{9uH3!6ct0wsZm^11i#;p_V)I0zRh}8r!zEFR#c*|zZU>$ zWn|J2^iQ7c{Q1Geqw(msM8a;4D(r=^vC%CZw>xsyzFg(dYO`*08s?kk=9X%Q!>)A{ zICP^vAv!!P#Gff&cB9O>_I_~_5zTUntsPe*|J^SxuvfEU;?RV{e zg@7Js8FrD=>4eQ{gV|(8K|vvgM~3j|(FA95x ztixel&#KtXgkUj13J$ss;ZV~-#3o}yl%k6PP98q|McZ_dOnD4S=~MyW0B8Z^0MPH* zyK6iai&59X1GM&y+W7#~0NCjqN+|*#8O}v6ZIWp!K_fqINli2uzn>aQLJ0F@#2ElA z04|wK0stf_iFh#1PCK80Og^4Uw|BO`y_aE_4+(+xzdcz_fr3JkWXzji&|kN68vtNO z{dNE(N(lMn*ykrced6Oc^8%x)akn5VGQCVFrKqZ^(ilv-P+k4I8of^UwEviaa5#*U zC%#N*)!C*7<3(d2DSsSI%2RnvD5a>buFlqGtE2x*F%9v_WX_cZ<{{^+4heR-t RWM2RP002ovPDHLkV1ipfiyZ&} literal 0 HcmV?d00001 diff --git a/panels/sound/data/icons/22x22/status/audio-input-microphone-low.png b/panels/sound/data/icons/22x22/status/audio-input-microphone-low.png new file mode 100644 index 0000000000000000000000000000000000000000..f0a7f2d58cc3a14890583607816af37a49ea42e9 GIT binary patch literal 1336 zcmV-81;_e{P)dNBACiO~J zcL(&nUC{Mt(WI$gC50*+<>$&Wv#QkRN@-?nR8R+pYFVP$0* zl~v{F)U{(|YzQr_jVm?kDj$1JfkHC(+i)MIW(;^ZHVBy_cWsZbO0Co`uRO!n)+V%F zTFlH&qolkD4@ZXx22tBkt+(gMi}OU9wua5A=|^bmXx>tkLoEDpfV6XN{zYV#%H(l}g_(tx$-nRAqA&%Hk}ABIm>&;Vg05^5WA)Oin#Q zc||F7`c4u{Ff=@1>s~C$G}&_;Vb8){;jQKQg(oN@VofdeD3IrYxow#8y0F4)3_AJ4alaYPj7K=&>sq?jXpF4X$;Re^Qq_ha_4_c{?)ee1}kQgt> z%udJj^b}I>35|3*{o~KS_-x_!-K3p@!aU?kvLP#!;$B(`+}vH~gIGbQg(=AkM&l&J zxtTgGC4yZF;$WK4v`&!6^`Oftg`W95=2 zEaT+ps0j&gULc5j|D|^0-H82)-13H8A%oFqKu=FM3KjVv(^whIpbcIE*GhG-*XwM> zf(N3&Lo zFKokV!D{7Y>tf6?`HQ0ao)eu3juN=ALg+T(sF-kgdU}5Jyy)xp!p5*S7R+T|VrPJb z4W%m#q0Xk=&49owQGxV;^&mz7{=!w5*Uh*V&bEEinJ*%6jll{Er?_Lm&Z6I=T{c&; zKfzdx%hm|%@SpFoySw{A?{i)oZCVZbhxD(6){Z9XZP}pFXotq>N8Zt0IVfuO+rQ9x u`9Ria;Ng=tVGj=vXYyj<|A68;a>XCdVJJhn{!(H90000P)h5ZrG!BA$4UKi|m*q+erMn8`+42H;j=fNkqsq;dyyrKuL04DN+NN9R z>+gZ-Rv(N52DIs$7fGR7fB8kqy!?7y?aJ+86SU2mXNpppYJI28b>DUyIu_@i&S7C; z9(6Zr&};0*-LVmLbha)u=x#*#a!S?mN575U#^jU*cgBXHR2SLT32Sv4!~DWitgI}< z&}YEZ0~;!9%5mr32*EHKTk1`|992cJRNvLIJZZa+uAcT4b!CZsov^w>IqPsZU^Mo@ zGGRtzlNPs!2B6i}!}h>Rim2y&ISPg1?W$U}^oF+jQLUyTU#%|OwN5x+mOVfFWEK;X z_fb<@1*55##7c~e4tcwm$@3h(9Di81a7A))etPCHs)<-zM>9%Q#h@;c5~n7RWL^I6 z!eQaxX;RPfJ<~Ifh#QQ^lVyWgZ7mx5Z$cnS1IaV+`TPz4D183nIg4CT2vNpaL>xMR z*f#04uj{No`+Mje6KJdXB`R!A~0qr9qYEw<{L!C;;83Ok)nEH5v6 z#pn3j^A{@)KZD1O#k%93*_0hBn)pJx$7~)$mQ;dcQAbw8Bf>mw`W6(*Wl$&!K_>FX ztG`}CRgD@)kI_Lt^22JT^eQIEnTs!EELr2kdD7;ClKwL1blBIl6ClQXT8YVgjVU zCkHMs6nuiQl)#m{oUL+Ndd69CUO^6Qwn=1NlUSKd=Et9Z@!8C!D;HKv%ZgDXFMzU4 zfos{B2o4FFW^fpLC7D-etkwz0it>oCAaSBjWfN+H|5`p-nyG65{W(~ zRqa#<7K<7AvfRa(ICf@GaFC6D^rzXRltg!A6#WUAWQrTlDI!g{$H~!A6B6FESe*9$ zOYJ5+m--dC0cCCXw@`y{VFiJ!Fh)qjw%u#GHM z^!G7bR%2`&Yce*D4VTMBgc6Cr*%L8bb`6)y-sdag*T;XWLZiJ#u!Ue7!JF}k+{cvg zFR4Exf*$b!S=&Ug(O1~7k6#eo@SGSxu$>@~6U+1p^HLJ9fB*iEo)>-HSJ)F#o5_9=(RLPxkw9_%`k2}X2?^O8elTpQ%b>^Li0PHk z+0#b7ElYGdeT!coQ^KL4p#kK@!v6uq_2-H|0J8@hRx0v6G5`Po07*qoM6N<$g6wI4 AUjP6A literal 0 HcmV?d00001 diff --git a/panels/sound/data/icons/22x22/status/audio-input-microphone-muted.png b/panels/sound/data/icons/22x22/status/audio-input-microphone-muted.png new file mode 100644 index 0000000000000000000000000000000000000000..6d14cc09194047697f7bb4ff119c55d0ff53c58e GIT binary patch literal 1284 zcmV+f1^fDmP)D zlx<8?R~X0t=ib{(DVKWfu(mfhDg{9l_o7`wvZx_(LvZ_I9Jt})r)?h`ia6c$g9$Oq z4BN6y*w@W0n|t}BZU`YR8sp632x5aT=}PUIda2rUlv~H+_9}allfN!C{$HNG#Z60%Lt8)!RZunFLe+4r0!^I+x{*g zgggyQzWDO9PM^=WD3OTc>eXvdRTca9AHe0ym*8?ap=lZvMS-SiSY2O_Ur(Qg;1E&j zECt5_gyW|Vbba*s6N+uybZ_6*imuLs5Cj3~bQ* zk=nIu&L}D6E`h)5(1P!=f~3TkrK?%tgU4i67Inwz$kJDslgN+n0=xBj!WieC{_ zRS}cP2{VznW37FD&FwX->vuJ7YUD>oN03NpIRfE&(O62_dATqoYnN<=2Ce3PPCsX!oc1q~|E|+58m1k{s+SJw3-!o2 z(7y96{P6vc0Dzr4+n;y_>FwDH824(D#;@!L7 z-@rQ@8vr`DXIry>-!Lpxt*qR$r}F~-yM=dlwc~N?{i+X^Y0j;iTee`}?BJ{*+n!BcFc@@HRxLmL#OP!bwM6=(qLD zH7ch>e?n`T08S3Tfv28DDtAESUYvjit*JLcX3tK18Q`ISTrOudlyTBja;&R!pR~5N zl8DDhBoaGmFTI7-O58Xz0b1XyfCfUqIPlf)g6vQq01S2^OY;4O!iMk&-fOmV23J$So6H4ukyEI=wD`R$ViI5@JZ6lvAyf1FxI5v)LW7`(b z7YF!8?-_o4{5Y*GNz&~uN5;p<4Ig5rFpJ|j zJ7u(NT(^zw?JYFSJ@AS&yt2Bo(wEDPNwHK!1PrA}Bx0PNc$sp!OgiOKtyU<^&M|#6 z&w>8^IF3!ILt>`M;o%Yf{Nib?L;dOl&2Dsbv^_MfceB~7^;kB`)bupAZPC=^U?`If z&!bo@5riR`REnOSPm*bOS--nMtybsgXCG&Fc8=38z3gwSt@h5%&CQF*-fr4IH1wTF zr0K6?hYw3kj}##2pwKIpNs$PrCMX$=Jv+q6E+L2!nti@Ud-+SS&_77N@tbk3>Ad z-%d~P_N7aDYkT`tetPNz@SfJX{4hWgNOkx1J)LZEe4 zef%I`etv;}zIK){48zje>WRf$xBdYXfhDbV?O^~1Z~=E@{4;MI9sjg*@tsSegAh>) zKL~d|p;BlaA|j0Ea{Bzm$@-Pa3r_+i;0~~&wbmlCx0}9C-2^Nhgl|^swJ)9c&99AE zJT6zKr}Wh;Q}}*^bh@3cu1=j!rR9|~ulZrn_!qDNlz}z9%OW3YnIe)*B;2mY2790H z=;-*=Gf#ah(w<7;`*j+=PpMR5ap|_6ymYy?y1I6=aQ()!+uP-3U|DP3*roGmPZkjy zXzkrU_?39F`G-oWgJBr90fI^<3Bf`EuxVX6<`1W002ovPDHLkV1fiIv<3hG literal 0 HcmV?d00001 diff --git a/panels/sound/data/icons/24x24/status/Makefile.am b/panels/sound/data/icons/24x24/status/Makefile.am new file mode 100644 index 000000000..6b9b71cfb --- /dev/null +++ b/panels/sound/data/icons/24x24/status/Makefile.am @@ -0,0 +1,21 @@ +NULL = + +themedir = $(pkgdatadir)/icons/hicolor +size = 24x24 +context = status + +iconsdir = $(themedir)/$(size)/$(context) + +icons_DATA = \ + audio-input-microphone-high.png \ + audio-input-microphone-low.png \ + audio-input-microphone-medium.png \ + audio-input-microphone-muted.png \ + $(NULL) + +EXTRA_DIST = \ + $(icons_DATA) \ + $(NULL) + + +-include $(top_srcdir)/git.mk diff --git a/panels/sound/data/icons/24x24/status/audio-input-microphone-high.png b/panels/sound/data/icons/24x24/status/audio-input-microphone-high.png new file mode 100644 index 0000000000000000000000000000000000000000..0da5af3f0b30094a5c4f4302118d281446f52caa GIT binary patch literal 1441 zcmV;S1z!4zP)sq1QCXVi4IUe zq8CH~gI=M*kU3!T6BLbcfEzdm+q(61+v&D+wCms6p40Pr!8X}g7jAr#Hzzso_k2(C zz3=nAAEA`u{{%N%^77RSTPdX*L5LlIKxO*n3o9#+%(Ql_F27i2Nds8bD@0CSU}$E$ zdUv>&$+mg(m8NqFPPxxyDkv>1Fz4py7rNByoP9rEX=|&5qt&d8ubj zLc}2em{H*JwWgPKdi}~JOUm=+xfVbW0?1?}SglrVZjNSo^OcL$(>d{^yqaYLEbVw7 z3gTsANpV$H0VWjyCI5cM5ED}Xfxt50Z;Y1rf@i{1t>~NO3>5O1A|eIyMNz> zDo2Uo;1EW`kz7ypx|X`Sx~yF|0S*9;96QVxnJrKC_4~oHEJjBokfaQHygeu?GQ-dN zVYA!d=l#gb(;*g%!tE}rP~~u$hC>Iyaoo_xNJB+dA4rm9U$@qScelLFEDiwJvbhE|Tem}!q@r1Uz_1Jvgg$(I@&wc>6^%t>vcI1P z&--CC8R6^o!Duo<7!WWT4j~qc!O#2XH)l>_aBu*eLXn!)2a=fS5CQ?%>~=UyOIL?S zM^86jx|G)GwBUI^3`PTZ-VdA2hW7T3bUdE;%3bDu(`vCn5CV`y@kTZuFaSV6FLriy zqI_vNOa}dl1@qhof4%Wr)aT>rT!#}%P6?;OfnYF*>({SGimaAXTCJ|3qPzl~-S+{I zi;P5S!@Y4|W$%K|$f1<0iD*`G%zjNoFTiM!_JUS2d z)a`kRfF5NTcD~!~hSg$)(O^VzaS8ed`tabv2m*m2+`Hd_jGXyP1~%32t^a*CACTz- z$K&y^cDrQ_t6Pfz(}v7Qt^RvT>C~%&5WIXgv_=ZSD7=?`(svLyLM%%>n_IzHEJ#EG@l4UAo%UtB0yaxhBmO%eMto)s%hPqHSE5 zeKw2v`8kx8m!MVGf`OqvG&a@ESE|dsw)*5MBu{?p@50!G0S|_HAyZ^6ZVOPUmD;)a zXINcbfwoPHiAUooEXl`%!9Jp1RMl4KxBAEnvPGKa+Lf{KhiJavu&OA`k!%Z46v(Dc zCKGhJHW)@nP*tr$S5F62stSxhG7^|#cB_w6Dt)`CR3R!?6+bCe7Njc_nMbw-q>Gd1 zre~%xI`$AHrA5%`TZt`1Uw@A+8?hwK^q;!uw(!Q>Cxf);rq#CJ1D78SvQEq4aCYwqaQ#M{K_blrKlU2D z&YVJE5DSkUPe75M2W4e3l2em5ZA)t-Lc^~hk`n=5Otj5Ma6RU+-8qOA@YXCA3+b9w zoc-|(ynW7LWMmi(jdc(v-a>v+-lnb7)WDx{*# zHEV1|<;HTqadkV95*8k8DI&ekBhjU1+_l;K!aVByEuN=O-HnOkdKVVuRj!T9(XlI{qNbUOXx&%gL=>gMhE_1wH{WJxk0%ah_xaw1&aTqgZl{>O!h z2~$SnD8yN51i%fnI|qC|{}_p4k&4J*7(u!?bs->#ndsu`GEO`H({xy5sKwidHbXL- zUwo#BWj{h<^Vc+msg<^%d!gWw}BIqtSqlj&|fJazL`PGMN6$ycn*P z%0{o(*@BSg%fRDD&+&LyKi%;hgmc2bj^Kp#bFM~`BgsaCrVicRotT}SL3d9l>Kkg1 zBg+Q$o~&yRl8-|kkV2%c`nI&vC^Tbv^ zGw1Np#i+L=i`;ijrs%`+rKm$6z7#;7*NOHK?I(IOIF$XA0{jy7GrVYCACUj{5IJo1 zo8=r#Unn@c2CD@NWf!f}k%wNq56H87V*@9mgGA1(K)MYO9vOm@Cr^H~0e@pFz!-dB z%3S;<)_Yi3R=U6th?N3%)+?OLYMX#0lg``^)B z+AC`Cc9?3txF>zk&++JU0C#tHC-Q~FtBCU5aTieq+^B=&MxhUESf@YFpXjxn-UojG XaD^SJJqXb-00000NkvXXu0mjfAzF|6 literal 0 HcmV?d00001 diff --git a/panels/sound/data/icons/24x24/status/audio-input-microphone-medium.png b/panels/sound/data/icons/24x24/status/audio-input-microphone-medium.png new file mode 100644 index 0000000000000000000000000000000000000000..4d64ee1d5c0ba8eb232b6c099a4d377678dc0dce GIT binary patch literal 1369 zcmV-f1*ZCmP)!buJ-rlystO4d4ibWm0))+IS2*{*q9pIx#w~$K$~sNadMJy8@Idg=T)? zIo8%zp=r}#d}0g*#d&x<&`Z>Vis~}m&Ky~OmQdYTy*fJf1dT0qYx06@@vZ=QzI4WD zG(xLwgMMTf6_rYKc6UIjEW_A@fxzT*J98uw$=ih`a^XW|(PW7tKTR&rII$}rO_Vf0 zGdqKk(I+S_DTG$nN@4+e`?{^wh{UPJ|1?GS1h?m>rk|mRY+74ig&bKHsAf|Tsv<$+ z-B$;=dwj15kK|aUrYFhtXpt&P0vXY*sA+EoH!1>Torc5V?AsII*6momSdxLLm`He@ zI|Kg!7ABsKL!OrlMR^gDQWCdfQ)@kfLarl>6AE5*gf)kMEBdL4bNu8|_*>#7?z=`~ z#Btej_|Xry05N>ty4h?dQ?rKiKb?d3B`*vQ523EU27-jU$Scg-T@N~D-s;iM97C|D- z0IAR#{K_2Wi@FdieMC;YgW6+__RX;aZx*yv_qMd1W}fTM8>5EaAi zZfI0no63#he(UUVIypEb&|FAnpGQuYlK#LN^9pjQ`!{=>J@X(smg`+mn1|-(MydkU zpr51gkI-YQ3+bO=Fznb`Kg(g;2hlN+{M7VhjE#*V@xH)7r_(?F;>*vc@7#;q$jQw@ zrZ^qaTnX+cCBWImY08)7drFWHKW#9KK$MwE09^ke`k{BUR}Kj4yzQr3FYiwq@Nqb~ zT|rdTr{ok%)QR-^VWf#t7X1R42~N&VW3&rD&jg1BnY}O3W=Un^>_Ao~IgELTTq#vC z$(!QyBi{eVG3CXEe@&)hMJAWRV9=wZqaC^OY>?_K45sf2FPdwis?q7R){yo#vYJx>wUz|B55Y&QDvW^{aU|3t6t b^?UFq2~!ZghoUK500000NkvXXu0mjf5SE93 literal 0 HcmV?d00001 diff --git a/panels/sound/data/icons/24x24/status/audio-input-microphone-muted.png b/panels/sound/data/icons/24x24/status/audio-input-microphone-muted.png new file mode 100644 index 0000000000000000000000000000000000000000..85893d1e45da38f756eec44dee06884f5c80f622 GIT binary patch literal 1349 zcmV-L1-kl)P)YSaq{ynPo3?Ij0XOO1RX9^dys$IhXHu z{{M5nkD-*}|7`q1lNT>`R}+vI8ICPvC{z&qxn)b;AO8iw%%`CD*Y3tVUhozc6$&iN zgMeUCRkd^`^H=Sb`m;|9VCNdVbg8GJ$Zq#O_reCDylgcTMFBAkcsw3K%r98c+uK#O zD1b@nrpn57f@l*l5(r@Z^Xs7~F%SYQUA9c%1n#9p0VXEYB|OW+@At#yD#q~5oA7$Q zh=d~$MLSZH8OP!P>I9qBvT(VI;rILDbUF|YhaoSQ!2(c|Np4Yql$r)11VjEI*d2C+ zLP1E98^K@@nxP??QlB>dY{2U~w>Kq|e+vMCEXxRmLU1@7@DC5eY87xlejn<%YH4iT z@eac<%)bE`N+~}5>{N@}?JkN&BN!aK4q29Q@Zcd_y?O;Un-z+pK$0XVih?y&RrvY* zdGHni`PO`}EWohb}I2;ayBN0fF zgc~<*nW@S2K*hSXKS^#0iV}li=#d2hU}!M#Frk(@mb_M4TK@axD_6B}IE=EDUI@GZ z?@BM?@i_YWF6W9p#Y1+R__ELELm)T;5E@+2BBHa`@7){o_4oH%>T0(YTCKKs^F@pQ zm#zyHl1JiYSrTHgF+Cc+Ypi>A?VYu2s`k`uuHgoT1`v%ZX$Jg_`2f?!?&QffFJ<^$ z6{}Wp)z#IA#}fzy1GqCfO7D)15s$~imX?$-Wo6}XI-TerxQ5Zu(T#-A&6v9?8vt`+ zMm0UscBIS#ZY$4m&#Ycu3CS(N;c!5fmm{h~kVqsDQ(_ni+=8y_v4VnvPM>cBZ?)P0 zP#A^*-O!=yIp=r693ij@OZzE^1Yy`;kxM@t~kfs?ZT~)GgU(5Rdfc*!W zK?s3v82I7)A7=o%y1LQW*nnJ4dkko3Xy6!*ZaQ#qKPQUfj19r?2;SV&G|S75&pTo0 z`t*NY{Z@2e=$Sg0X+qO-kDsZ}=d+ZQt~fd?$^uOk#bdR#udsP}c>tz-Cxn8SCeoP< z`hM%1`xRlz|0n%^w1@&Alu`l!Z@s;%P1nt%m?sZ7jyv%WiLn;Jj^rR&00000NkvXX Hu0mjfOuA}E literal 0 HcmV?d00001 diff --git a/panels/sound/data/icons/32x32/Makefile.am b/panels/sound/data/icons/32x32/Makefile.am new file mode 100644 index 000000000..9ed74c7bf --- /dev/null +++ b/panels/sound/data/icons/32x32/Makefile.am @@ -0,0 +1,4 @@ +SUBDIRS = status apps + + +-include $(top_srcdir)/git.mk diff --git a/panels/sound/data/icons/32x32/apps/Makefile.am b/panels/sound/data/icons/32x32/apps/Makefile.am new file mode 100644 index 000000000..e209d40a7 --- /dev/null +++ b/panels/sound/data/icons/32x32/apps/Makefile.am @@ -0,0 +1,19 @@ +NULL = + +themedir = $(datadir)/icons/hicolor +size = 32x32 +context = apps + +iconsdir = $(themedir)/$(size)/$(context) + +icons_DATA = \ + multimedia-volume-control.png \ + multimedia-volume-control.svg + $(NULL) + +EXTRA_DIST = \ + $(icons_DATA) \ + $(NULL) + + +-include $(top_srcdir)/git.mk diff --git a/panels/sound/data/icons/32x32/apps/multimedia-volume-control.png b/panels/sound/data/icons/32x32/apps/multimedia-volume-control.png new file mode 100644 index 0000000000000000000000000000000000000000..3a6c791fa6df5390919dd6204d56177264196e88 GIT binary patch literal 2308 zcmV+f3H$bmP)pkt!;D=oY1&FX($RNyHTV4g-~apm=bRBrDL$#u^Mo*fMxYsJS`(L^+kgt- z*uLl0+FO8v>pWu6-t7$~k>ujdJ|YW>?1*wfYZc_R}2eN$s&V(+d_ zqv*P%(~G218OA3jS+QcSxGoe&Yo|FFOVSi7psmxg zRI1OIBkb2C&7bZPp^8eQn9Gu1%pwHADgqrH9cHv9@$x-)A2i0UUuSaq#%(kIVq%KN z@02um941szNimltpH8C?Xqv?L10;$^6E|qt{ve^;7{SaH%$ltfD_TKFujdKzKJQ4p z=F4?;wGmy@IX^H&5CpdbmQoTibsnz0!G;}sFpD#&%O^04GiV``KuE%-iDj8+Mu^wu z8@WEvixX-$3+D{+xNWkmle=7`NPedHj1g48H5n)vKK<2>pF(6q3b$t zWj9ed*hh26HbQOfIJpc{gMIun*M@0X=z)*#`JBvdhD+ldm^w|1Ss0)i{~k!s`po)F*1$y=yxsF1%g+)N7O8Y65|>B9=ummA)l-$S^R<({)`kMVA*B z61*sCiO1r!G&K>4TbK*;XnrYs*YnV%L`p&6`y^9oB9?^;l(n?}N*4%#qU)_9P?Eir zxe+qR)9E_6pRutqT-QZv5?Qu>5GXR)+$yY9 z1cVeSZ`&eT5h1mhS#CJJTytI5aU6$^wl<#m>5p-W4pL~?wvDdqG}I^f$B8!?8ooqF z+g2v0rdZl{6yOw##40Pu+crXq}2Bl6p7l}QqLHrzR`6J-w&v-OOQ$}0>WNLAP9n?YvU6% zw=^N8Tvk9zSt24Op^!mOUmw|QmOup*Y@1{<$zo=aTrNi{l_nfEF+%z>u%>BfQp0mS znp>I}D>eDhx)LbGvvYIvFYM?PqN%Zwsp)Bi5J({qLLj81x+>1`S6=1J*&bS2ny63I z;y5)8ujjp65|pTT8?WGdp`z`F`-5W!;zj-qF!vMyhHS z!j=`e=TH}C`}!#q@>r&cX@=g9R+w*zo!#rVWH1>3=K z9P)Xax%nhVx({>aTpw4)#tP#j!!4;)D!EF5k&zK+``*3Z1peIHKWOaQaVKZb^_EXS zmQTVU@IfgK+_jggsyLBw1R=n6U6QFJ>2w;;^9h20AW+QRT;R~bgDfN$xjHuH74q39 z)9JLgP5}TRM0ES^JwGu_>w8sli<2W{!RZ_qVg)q$!4Gj$x9fx>4hHcvf zflm;WHXzq^$>jWP-=w>=lh=XeE;;- zt7GcI&=4D1n%KB;BT9kidU(E%>v|L&hvAXS*tWyK#Y+@y2itMT7i@0Mr_l5e-S-@( zP_Q|6{1xSS-kG7n{@<2e>3@&{77(qdu5Q}AdFvmgrft`R3^g{^)4FLR`GSM*y99wk z5+F2*CN+eRQ1*O9Btm;z8`)frH{UwNd~%@}_}=i9(UI>IGMQ;$5tvs>1-Bv~ff!H; z#9G_iACASVexzx7l@NlZi)dwK1+i!xD-uSN5&b4oAX=W4x~^C4IoS1vLWD(WzaR*Ll0~J&_xtsWZ8!L zI`87(z;o9|M~?%!6$BR43ea^|GNqJLRzd>8^E@>>GyQ{&o45YzOOHHkX`1Bn)iE(L zdYSo!1vE{gva*7@L=797n^jA53m1lmi)YXEp1*$W+F#4S`EpFEH5so{V1*J9AVTSX zTzErk>w!c=)30~$+FA9;Ll1<)VG|W7JkKMW%aKl}xi~!Hp6lr&m(9LDJlOw?>$*9h zP*xzRl=9c%-0s>XgfNx}02W|wZtJ)=US0KdP1m=oKqUgDOijv^Ld_KNg@4aXOumpx z-JAi6W$;Cyur~9LMBo-gOiE=l0F82)C|N6@WG@%+fXwZHKNf)%M3#DEB?(am_R7-- ef)4{;8vh54v;!f_`4C(H0000 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Sound CC applet + 17.02.2007 + + + Josef vybíral + + + + + Josef vybíral + + + http://blog.vybiral.info + http://blog.vybiral.info + sound, reproductor, note + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/panels/sound/data/icons/32x32/status/Makefile.am b/panels/sound/data/icons/32x32/status/Makefile.am new file mode 100644 index 000000000..24d4e984f --- /dev/null +++ b/panels/sound/data/icons/32x32/status/Makefile.am @@ -0,0 +1,21 @@ +NULL = + +themedir = $(pkgdatadir)/icons/hicolor +size = 32x32 +context = status + +iconsdir = $(themedir)/$(size)/$(context) + +icons_DATA = \ + audio-input-microphone-high.png \ + audio-input-microphone-low.png \ + audio-input-microphone-medium.png \ + audio-input-microphone-muted.png \ + $(NULL) + +EXTRA_DIST = \ + $(icons_DATA) \ + $(NULL) + + +-include $(top_srcdir)/git.mk diff --git a/panels/sound/data/icons/32x32/status/audio-input-microphone-high.png b/panels/sound/data/icons/32x32/status/audio-input-microphone-high.png new file mode 100644 index 0000000000000000000000000000000000000000..dc72881552c90200ca7c25e1a6889bbbc9d979fd GIT binary patch literal 1906 zcmV-&2aWiNP)Z-#0BkcId1`-2GHXOR>&6xASO1-GB9+9%|HjGUtXMt>t8fORjH)!ZMc8`9+J~U zR<4-4Ab_FZU%u9Qe0f$=z_N#59(zG{H)zG0#|Gf-?ej(!K9MjX_6|Uq!~HbW~QC z&96*WPKBO?AU<}6(P2a+77nMTrY?5~02#2E%~P;iE!cO!7l#h}bOdlT^`5J{q`Cx1~%mYH9@V5dXf1;>Fj&F(mXtkf`M#y)#?6|hueb-dj z*|6zzoHJGrFZZeKJ3s6_8xdBTn|Il6G7Yle7+uE36Y)S?V%%F~Xk1Rw3=Q2S86Xl; zBRxF@r^7;KPM!+3i<1*jS*4-|)d>?3m9ngaaX!xIE(s3^1kta@3HeQAl+sG%^Z8Zu z^mJiz@&`N|dw`zaPF%a*f)b+U_=H%ndr4=>yJ1Xx%wOV!yj($iOc#Nf>FMpJ$jg~% zt;{>-xBrZKNdeIFJjrsBmHfD9Td$ zI4wT`2tE}g-2DFL*VnCE_cHDI#n0JrcpmWa@o{qZazEkY=h@+ZXuti)(SWH)Zp8c% z_#ZxC+y3|M7B_cSiBO-WlJ&-xVnAhaVWrlk{nttWTfFTu_Coxh?0ev z&%ntb@M5?uIdQoEoI83f!0hek)#>8mvi6DQfju6sr%#5QuoTGikXIlD4J639oQaIA zbfVaJnu;1xM6}H1aR^WXL%ZxP5T_)88yzw0w{P#{HkWOGaEJ$1uU@_K<0AoMs)|zN z%cU&m%ZqYRK$1-$qWR~U>IaWj#%XGr7%6E#{xrhrHDM(IDVl%;jNFh+Z zetS&>;KfDX0(xFLc-Y^hqwZIwDQAXFXlg29Or+Jtbtj*>J&piawr$5&4{EKJN_F{MLsLC;1~of)FwT|bB{Y=EX)cgKnwyPF zIT@_eY)KY!^QGYPqiy>S_?k9u-1s|(d4K`2X_NCC{s;H>bIwOtic1P->*@_KUa3VL zbwI674YjrsYE1===Vefo7SWL;S_4mfW^?y+%cDASS`s2+Qn+QymcP30*_9D+ zz)x)d2nn8v;G8v|IU8yT3J!d9?D$dhzWsikJ6(2$Z`iP5ts{*1c$YHN*#H0l07*qoM6N<$f?;)&mH+?% literal 0 HcmV?d00001 diff --git a/panels/sound/data/icons/32x32/status/audio-input-microphone-low.png b/panels/sound/data/icons/32x32/status/audio-input-microphone-low.png new file mode 100644 index 0000000000000000000000000000000000000000..467fff6c0dbc1f48ada9ef5b311d2a63c0198b7e GIT binary patch literal 2002 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 literal 0 HcmV?d00001 diff --git a/panels/sound/data/icons/32x32/status/audio-input-microphone-muted.png b/panels/sound/data/icons/32x32/status/audio-input-microphone-muted.png new file mode 100644 index 0000000000000000000000000000000000000000..fda01ac137c6fa8e67ce90379196f5ec112f00b9 GIT binary patch literal 1998 zcmV;<2Qm1GP)o1hza*T6bi27gp!0R=(CPATA`$WNgb=zFiH zg_gFIQW1ecD5WBUrFLqm1!}DDhZAQZV7 z8yN`+GqcmMxOg8Hm+r&t+zgD2hJ|qWX5yv-4F7HT6s~5 zda%SH?%WyrbkhJrw+D|;&rHJO$B)qKSy*0PhVk(+xH~xk3-{+?VPOt#MTXC88bDyk zUrvVZP2Pj$e=eiPBQU=(5B`7;fu^}CgP{Ro(*UmZUlmCHhmRh@7U2aPWgo0qE#%=K!N)BRuiko<3CLE(C&pn46#F0DbN&qSNWzvMB)eW}9#y zGyef5TyQ7|;}fIc^?BA6c)I)qE_b$yI-PEF0E{L*0xWO^Nd8`T4@}%0!!UU9_Y}{8 z3l|$V2Ou*uIZCZjN%NTLi%W|<@j~GsI9wg;^ZfiA=xeo7aq-DtycGa@Q$zlh-fq!q zvj|yP8BkPm7CoIogG1=~IJ9-NfIsM6pGlQ+RT)?;CW+S2u-UC;{r&y#y%9h|qgj0Z z{5gE{%?pq#vOt-ufXS(QFg=a(_w|6s+Xqc8c0M=bO!rRZ=R#L^rUtaUk~SZv5xX=#xx>B!*|73V87W8qV~(;i%b|}GD!QMl94>Wc=3XE z-+|B7m6esBw0?Q1lGgNLnCKmE41flIX0jM7N%BWn{cy&3im8W^4&|k!CTY}FYN1-H z6pv-3YGj9Hg;m1&beGE^QI{k*T1)TvuePDZVu_*`QPM494WO3?hXT;j(gZ4Xxl~qO zBH5d)tNmbx-}idm+&Ha$D|NtX)orC%TC7GcD{a9!?1s;tJ%guDpWycyn3x#nrP*RN z@wn5Pw_XVi2E#Ud`4N-Fa28=Cf*{Y$^#+ly(Y+t)^$!BT&yR4thvUaaV@<{yfJUd; zh1cF+z4z`qu-UNJWHu}sO*O2>pkq)YF%bk-EmkpgwVJ8a6|7u!?(Ze#C5Qhb0KJ$D zL`O&eG(J9ln@%U?t^t%*6ds5@7`t`*_U#`Km}mrA8_%ZA2N zQWAFLl6+x|0&x@0&)j)nhu7+SM)D_>7w{_-P_S-!% z>|T85RDb#`^I-279o0#Ksm z<>#q=eh)WJHNSUtbRa~nQ46#!zne|a+-!%YW*e$t;nxW$zu@!=)(*6%XK@3rKPQjLwu)%5pyGF#p!Y|3FDoetK^<5SLM$+b(DzLF$(8oYZLC^Qv)wVV zT=wfX+&mnV6BVj5R^MP@sTt`n<2C?zcVEK39e4;4DA7j8A}|yV@Ph{#_o5=C425JZ zG07;Y;p%ZhU0scMG$VcG;-w2r(rMU0(8s#EFSE*O6>INoWx>z@hDO3&QN#Xx^vKJ& zxVSAF?)}d`+x@X3M_wdqMd8}Due6a!7)G!YMz8}8VNbyS3cT^qxzK~9xUsQ8EG#Y* zQ`1wACZ?p!snjZFwbe5`E8=#n!yRGZJHc$JV>&EM#bw1zmYDecb$|-c-o1N2Nlr~p z%>9e9yrA&3NKHtPsx?(rDxtWvP+L%tuaV2M3KEhN{8@n|oCgzuvFaKVKSD*bk gj$gmV_=ukW7Zmv+mI_dEdjJ3c07*qoM6N<$g6=lPz5oCK literal 0 HcmV?d00001 diff --git a/panels/sound/data/icons/48x48/Makefile.am b/panels/sound/data/icons/48x48/Makefile.am new file mode 100644 index 000000000..237e20997 --- /dev/null +++ b/panels/sound/data/icons/48x48/Makefile.am @@ -0,0 +1,4 @@ +SUBDIRS = apps + + +-include $(top_srcdir)/git.mk diff --git a/panels/sound/data/icons/48x48/apps/Makefile.am b/panels/sound/data/icons/48x48/apps/Makefile.am new file mode 100644 index 000000000..8c80e3c7e --- /dev/null +++ b/panels/sound/data/icons/48x48/apps/Makefile.am @@ -0,0 +1,18 @@ +NULL = + +themedir = $(datadir)/icons/hicolor +size = 48x48 +context = apps + +iconsdir = $(themedir)/$(size)/$(context) + +icons_DATA = \ + multimedia-volume-control.png \ + $(NULL) + +EXTRA_DIST = \ + $(icons_DATA) \ + $(NULL) + + +-include $(top_srcdir)/git.mk diff --git a/panels/sound/data/icons/48x48/apps/multimedia-volume-control.png b/panels/sound/data/icons/48x48/apps/multimedia-volume-control.png new file mode 100644 index 0000000000000000000000000000000000000000..61dd43b8c322a01fb242fbde94b609ae0d7aea3b GIT binary patch literal 3755 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 V(az&JcYFW<002ovPDHLkV1m>AL974( literal 0 HcmV?d00001 diff --git a/panels/sound/data/icons/48x48/status/audio-input-microphone-high.png b/panels/sound/data/icons/48x48/status/audio-input-microphone-high.png new file mode 100644 index 0000000000000000000000000000000000000000..e26b9d9016d9cf92df43e0426022e92b0907f587 GIT binary patch literal 3261 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 Z{{a^WSig!3PnZAz002ovPDHLkV1g2X((3>K literal 0 HcmV?d00001 diff --git a/panels/sound/data/icons/Makefile.am b/panels/sound/data/icons/Makefile.am new file mode 100644 index 000000000..35e2de2d6 --- /dev/null +++ b/panels/sound/data/icons/Makefile.am @@ -0,0 +1,12 @@ +NULL = + +SUBDIRS = \ + scalable \ + 16x16 \ + 22x22 \ + 24x24 \ + 32x32 \ + 48x48 \ + $(NULL) + +-include $(top_srcdir)/git.mk diff --git a/panels/sound/data/icons/render-icon-theme.py b/panels/sound/data/icons/render-icon-theme.py new file mode 100755 index 000000000..9e6a476c3 --- /dev/null +++ b/panels/sound/data/icons/render-icon-theme.py @@ -0,0 +1,169 @@ +#!/usr/bin/env python + +import os +import sys +import xml.sax +import subprocess + +INKSCAPE = '/usr/bin/inkscape' +OPTIPNG = '/usr/bin/optipng' +SRC = os.path.join('.', 'src') + +inkscape_process = None + +def optimize_png(png_file): + if os.path.exists(OPTIPNG): + process = subprocess.Popen([OPTIPNG, '-quiet', '-o7', png_file]) + process.wait() + +def wait_for_prompt(process, command=None): + if command is not None: + process.stdin.write(command+'\n') + + # This is kinda ugly ... + # Wait for just a '>', or '\n>' if some other char appearead first + output = process.stdout.read(1) + if output == '>': + return + + output += process.stdout.read(1) + while output != "\n>": + output += process.stdout.read(1) + output = output[1:] + +def start_inkscape(): + process = subprocess.Popen([INKSCAPE, '--shell'], bufsize=0, stdin=subprocess.PIPE, stdout=subprocess.PIPE) + wait_for_prompt(process) + return process + +def inkscape_render_rect(icon_file, rect, output_file): + global inkscape_process + if inkscape_process is None: + inkscape_process = start_inkscape() + wait_for_prompt(inkscape_process, '%s -i %s -e %s' % (icon_file, rect, output_file)) + optimize_png(output_file) + +class ContentHandler(xml.sax.ContentHandler): + ROOT = 0 + SVG = 1 + LAYER = 2 + OTHER = 3 + TEXT = 4 + def __init__(self, path, force=False, filter=None): + self.stack = [self.ROOT] + self.inside = [self.ROOT] + self.path = path + self.rects = [] + self.state = self.ROOT + self.chars = "" + self.force = force + self.filter = filter + + def endDocument(self): + pass + + def startElement(self, name, attrs): + if self.inside[-1] == self.ROOT: + if name == "svg": + self.stack.append(self.SVG) + self.inside.append(self.SVG) + return + elif self.inside[-1] == self.SVG: + if (name == "g" and attrs.has_key('inkscape:groupmode') and attrs.has_key('inkscape:label') + and attrs['inkscape:groupmode'] == 'layer' and attrs['inkscape:label'].startswith('baseplate')): + self.stack.append(self.LAYER) + self.inside.append(self.LAYER) + self.context = None + self.icon_name = None + self.rects = [] + return + elif self.inside[-1] == self.LAYER: + if name == "text" and attrs.has_key('inkscape:label') and attrs['inkscape:label'] == 'context': + self.stack.append(self.TEXT) + self.inside.append(self.TEXT) + self.text='context' + self.chars = "" + return + elif name == "text" and attrs.has_key('inkscape:label') and attrs['inkscape:label'] == 'icon-name': + self.stack.append(self.TEXT) + self.inside.append(self.TEXT) + self.text='icon-name' + self.chars = "" + return + elif name == "rect": + self.rects.append(attrs) + + self.stack.append(self.OTHER) + + + def endElement(self, name): + stacked = self.stack.pop() + if self.inside[-1] == stacked: + self.inside.pop() + + if stacked == self.TEXT and self.text is not None: + assert self.text in ['context', 'icon-name'] + if self.text == 'context': + self.context = self.chars + elif self.text == 'icon-name': + self.icon_name = self.chars + self.text = None + elif stacked == self.LAYER: + assert self.icon_name + assert self.context + + if self.filter is not None and not self.icon_name in self.filter: + return + + print '%s %s' % (self.context, self.icon_name) + for rect in self.rects: + width = rect['width'] + height = rect['height'] + id = rect['id'] + + dir = os.path.join("%sx%s" % (width, height), self.context) + outfile = os.path.join(dir, self.icon_name+'.png') + if not os.path.exists(dir): + os.makedirs(dir) + # Do a time based check! + if self.force or not os.path.exists(outfile): + inkscape_render_rect(self.path, id, outfile) + sys.stdout.write('.') + else: + stat_in = os.stat(self.path) + stat_out = os.stat(outfile) + if stat_in.st_mtime > stat_out.st_mtime: + inkscape_render_rect(self.path, id, outfile) + sys.stdout.write('.') + else: + sys.stdout.write('-') + sys.stdout.flush() + sys.stdout.write('\n') + sys.stdout.flush() + + def characters(self, chars): + self.chars += chars.strip() + +if len(sys.argv) == 1: + if not os.path.exists('gnome'): + os.mkdir('gnome') + print 'Rendering from SVGs in %s' % SRC + for file in os.listdir(SRC): + if file[-4:] == '.svg': + file = os.path.join(SRC, file) + handler = ContentHandler(file) + xml.sax.parse(open(file), handler) +else: + file = os.path.join(SRC, sys.argv[1] + '.svg') + if len(sys.argv) > 2: + icons = sys.argv[2:] + else: + icons = None + if os.path.exists(os.path.join(file)): + handler = ContentHandler(file, True, filter=icons) + xml.sax.parse(open(file), handler) + else: + print "Error: No such file %s" % file + sys.exit(1) + + diff --git a/panels/sound/data/icons/scalable/Makefile.am b/panels/sound/data/icons/scalable/Makefile.am new file mode 100644 index 000000000..eb00566d4 --- /dev/null +++ b/panels/sound/data/icons/scalable/Makefile.am @@ -0,0 +1,4 @@ +SUBDIRS = apps devices + + +-include $(top_srcdir)/git.mk diff --git a/panels/sound/data/icons/scalable/apps/Makefile.am b/panels/sound/data/icons/scalable/apps/Makefile.am new file mode 100644 index 000000000..3c97ab1ba --- /dev/null +++ b/panels/sound/data/icons/scalable/apps/Makefile.am @@ -0,0 +1,18 @@ +NULL = + +themedir = $(datadir)/icons/hicolor +size = scalable +context = apps + +iconsdir = $(themedir)/$(size)/$(context) + +icons_DATA = \ + multimedia-volume-control.svg \ + $(NULL) + +EXTRA_DIST = \ + $(icons_DATA) \ + $(NULL) + + +-include $(top_srcdir)/git.mk diff --git a/panels/sound/data/icons/scalable/apps/multimedia-volume-control.svg b/panels/sound/data/icons/scalable/apps/multimedia-volume-control.svg new file mode 100644 index 000000000..b22a9543d --- /dev/null +++ b/panels/sound/data/icons/scalable/apps/multimedia-volume-control.svg @@ -0,0 +1,554 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Sound CC applet + 17.02.2007 + + + Josef vybíral + + + + + Josef vybíral + + + http://blog.vybiral.info + http://blog.vybiral.info + sound, reproductor, note + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/panels/sound/data/icons/scalable/devices/Makefile.am b/panels/sound/data/icons/scalable/devices/Makefile.am new file mode 100644 index 000000000..404f6e5e4 --- /dev/null +++ b/panels/sound/data/icons/scalable/devices/Makefile.am @@ -0,0 +1,35 @@ +NULL = + +themedir = $(pkgdatadir)/icons/hicolor +size = 48x48 +context = devices + +iconsdir = $(themedir)/$(size)/$(context) + +icons_DATA = \ + audio-speaker-center.svg \ + audio-speaker-center-testing.svg \ + audio-speaker-left-back.svg \ + audio-speaker-left-back-testing.svg \ + audio-speaker-left.svg \ + audio-speaker-left-side.svg \ + audio-speaker-left-side-testing.svg \ + audio-speaker-left-testing.svg \ + audio-speaker-right-back.svg \ + audio-speaker-right-back-testing.svg \ + audio-speaker-right.svg \ + audio-speaker-right-side.svg \ + audio-speaker-right-side-testing.svg \ + audio-speaker-right-testing.svg \ + audio-speaker-center-back-testing.svg \ + audio-speaker-center-back.svg \ + audio-subwoofer.svg \ + audio-subwoofer-testing.svg \ + $(NULL) + +EXTRA_DIST = \ + $(icons_DATA) \ + $(NULL) + + +-include $(top_srcdir)/git.mk diff --git a/panels/sound/data/icons/scalable/devices/audio-speaker-center-back-testing.svg b/panels/sound/data/icons/scalable/devices/audio-speaker-center-back-testing.svg new file mode 100644 index 000000000..6b067d693 --- /dev/null +++ b/panels/sound/data/icons/scalable/devices/audio-speaker-center-back-testing.svg @@ -0,0 +1,539 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + audio + device + speaker + output + center + testing + highlighted + + + audio-speaker-center-testing + + + Evangeline McGlynn + + + + + + + + + + + + + + + + + + + + diff --git a/panels/sound/data/icons/scalable/devices/audio-speaker-center-back.svg b/panels/sound/data/icons/scalable/devices/audio-speaker-center-back.svg new file mode 100644 index 000000000..2d162c45d --- /dev/null +++ b/panels/sound/data/icons/scalable/devices/audio-speaker-center-back.svg @@ -0,0 +1,506 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + audio + device + speaker + output + center + + + audio-speaker-center + + + Evangeline McGlynn + + + + + + + + + + + + + + + + + + + + diff --git a/panels/sound/data/icons/scalable/devices/audio-speaker-center-testing.svg b/panels/sound/data/icons/scalable/devices/audio-speaker-center-testing.svg new file mode 100644 index 000000000..544384a1a --- /dev/null +++ b/panels/sound/data/icons/scalable/devices/audio-speaker-center-testing.svg @@ -0,0 +1,537 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + audio + device + speaker + output + center + testing + highlighted + + + audio-speaker-center-testing + + + Evangeline McGlynn + + + + + + + + + + + + + + + + + + + + diff --git a/panels/sound/data/icons/scalable/devices/audio-speaker-center.svg b/panels/sound/data/icons/scalable/devices/audio-speaker-center.svg new file mode 100644 index 000000000..77426fdb8 --- /dev/null +++ b/panels/sound/data/icons/scalable/devices/audio-speaker-center.svg @@ -0,0 +1,504 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + audio + device + speaker + output + center + + + audio-speaker-center + + + Evangeline McGlynn + + + + + + + + + + + + + + + + + + + + diff --git a/panels/sound/data/icons/scalable/devices/audio-speaker-left-back-testing.svg b/panels/sound/data/icons/scalable/devices/audio-speaker-left-back-testing.svg new file mode 100644 index 000000000..4ab4a3740 --- /dev/null +++ b/panels/sound/data/icons/scalable/devices/audio-speaker-left-back-testing.svg @@ -0,0 +1,537 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + audio + device + speaker + output + left-back + testing + highlighted + + + audio-speaker-left-b-testing + + + Evangeline McGlynn + + + + + + + + + + + + + + + + + + + + diff --git a/panels/sound/data/icons/scalable/devices/audio-speaker-left-back.svg b/panels/sound/data/icons/scalable/devices/audio-speaker-left-back.svg new file mode 100644 index 000000000..f82c536f2 --- /dev/null +++ b/panels/sound/data/icons/scalable/devices/audio-speaker-left-back.svg @@ -0,0 +1,504 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + audio + device + speaker + output + left-back + + + audio-speaker-left-back + + + Evangeline McGlynn + + + + + + + + + + + + + + + + + + + + diff --git a/panels/sound/data/icons/scalable/devices/audio-speaker-left-side-testing.svg b/panels/sound/data/icons/scalable/devices/audio-speaker-left-side-testing.svg new file mode 100644 index 000000000..1d06aa9b4 --- /dev/null +++ b/panels/sound/data/icons/scalable/devices/audio-speaker-left-side-testing.svg @@ -0,0 +1,537 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + audio + device + speaker + output + left-side + testing + highlighted + + + audio-speaker-left-side-testing + + + Evangeline McGlynn + + + + + + + + + + + + + + + + + + + + diff --git a/panels/sound/data/icons/scalable/devices/audio-speaker-left-side.svg b/panels/sound/data/icons/scalable/devices/audio-speaker-left-side.svg new file mode 100644 index 000000000..bfbd3a370 --- /dev/null +++ b/panels/sound/data/icons/scalable/devices/audio-speaker-left-side.svg @@ -0,0 +1,504 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + audio + device + speaker + output + left-side + + + audio-speaker-left-side + + + Evangeline McGlynn + + + + + + + + + + + + + + + + + + + + diff --git a/panels/sound/data/icons/scalable/devices/audio-speaker-left-testing.svg b/panels/sound/data/icons/scalable/devices/audio-speaker-left-testing.svg new file mode 100644 index 000000000..947c2e82a --- /dev/null +++ b/panels/sound/data/icons/scalable/devices/audio-speaker-left-testing.svg @@ -0,0 +1,537 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + audio + device + speaker + output + left + testing + highlighted + + + audio-speaker-left-testing + + + Evangeline McGlynn + + + + + + + + + + + + + + + + + + + + diff --git a/panels/sound/data/icons/scalable/devices/audio-speaker-left.svg b/panels/sound/data/icons/scalable/devices/audio-speaker-left.svg new file mode 100644 index 000000000..a9080445a --- /dev/null +++ b/panels/sound/data/icons/scalable/devices/audio-speaker-left.svg @@ -0,0 +1,504 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + audio + device + speaker + output + left + + + audio-speaker-left + + + Evangeline McGlynn + + + + + + + + + + + + + + + + + + + + diff --git a/panels/sound/data/icons/scalable/devices/audio-speaker-right-back-testing.svg b/panels/sound/data/icons/scalable/devices/audio-speaker-right-back-testing.svg new file mode 100644 index 000000000..a641a4aca --- /dev/null +++ b/panels/sound/data/icons/scalable/devices/audio-speaker-right-back-testing.svg @@ -0,0 +1,537 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + audio + device + speaker + output + right-back + testing + highlighted + + + audio-speaker-right-back-testing + + + Evangeline McGlynn + + + + + + + + + + + + + + + + + + + + diff --git a/panels/sound/data/icons/scalable/devices/audio-speaker-right-back.svg b/panels/sound/data/icons/scalable/devices/audio-speaker-right-back.svg new file mode 100644 index 000000000..051246480 --- /dev/null +++ b/panels/sound/data/icons/scalable/devices/audio-speaker-right-back.svg @@ -0,0 +1,504 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + audio + device + speaker + output + right-back + + + audio-speaker-right-back + + + Evangeline McGlynn + + + + + + + + + + + + + + + + + + + + diff --git a/panels/sound/data/icons/scalable/devices/audio-speaker-right-side-testing.svg b/panels/sound/data/icons/scalable/devices/audio-speaker-right-side-testing.svg new file mode 100644 index 000000000..a15a08f80 --- /dev/null +++ b/panels/sound/data/icons/scalable/devices/audio-speaker-right-side-testing.svg @@ -0,0 +1,537 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + audio + device + speaker + output + right-side + testing + highlighted + + + audio-speaker-right-side-testing + + + Evangeline McGlynn + + + + + + + + + + + + + + + + + + + + diff --git a/panels/sound/data/icons/scalable/devices/audio-speaker-right-side.svg b/panels/sound/data/icons/scalable/devices/audio-speaker-right-side.svg new file mode 100644 index 000000000..1419c0294 --- /dev/null +++ b/panels/sound/data/icons/scalable/devices/audio-speaker-right-side.svg @@ -0,0 +1,504 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + audio + device + speaker + output + right-side + + + audio-speaker-right-side + + + Evangeline McGlynn + + + + + + + + + + + + + + + + + + + + diff --git a/panels/sound/data/icons/scalable/devices/audio-speaker-right-testing.svg b/panels/sound/data/icons/scalable/devices/audio-speaker-right-testing.svg new file mode 100644 index 000000000..9d8482091 --- /dev/null +++ b/panels/sound/data/icons/scalable/devices/audio-speaker-right-testing.svg @@ -0,0 +1,913 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + audio + device + speaker + output + right + testing + highlighted + + + audio-speaker-right-testing + + + Evangeline McGlynn + + + + + + + + + + + + + + + + + + + + diff --git a/panels/sound/data/icons/scalable/devices/audio-speaker-right.svg b/panels/sound/data/icons/scalable/devices/audio-speaker-right.svg new file mode 100644 index 000000000..04b30a05a --- /dev/null +++ b/panels/sound/data/icons/scalable/devices/audio-speaker-right.svg @@ -0,0 +1,504 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + audio + device + speaker + output + right + + + audio-speaker-right + + + Evangeline McGlynn + + + + + + + + + + + + + + + + + + + + diff --git a/panels/sound/data/icons/scalable/devices/audio-speaker-testing.svg b/panels/sound/data/icons/scalable/devices/audio-speaker-testing.svg new file mode 100644 index 000000000..79b1ff274 --- /dev/null +++ b/panels/sound/data/icons/scalable/devices/audio-speaker-testing.svg @@ -0,0 +1,913 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + audio + device + speaker + output + right + testing + highlighted + + + audio-speaker-right-testing + + + Evangeline McGlynn + + + + + + + + + + + + + + + + + + + + diff --git a/panels/sound/data/icons/scalable/devices/audio-subwoofer-testing.svg b/panels/sound/data/icons/scalable/devices/audio-subwoofer-testing.svg new file mode 100644 index 000000000..2e1dd7818 --- /dev/null +++ b/panels/sound/data/icons/scalable/devices/audio-subwoofer-testing.svg @@ -0,0 +1,240 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + audio + device + subwoofer + output + testing + highlighted + + + audio-subwoofer-testing + + + Evangeline McGlynn + + + + + + + + + + + + + + + + + + + diff --git a/panels/sound/data/icons/scalable/devices/audio-subwoofer.svg b/panels/sound/data/icons/scalable/devices/audio-subwoofer.svg new file mode 100644 index 000000000..fb3646800 --- /dev/null +++ b/panels/sound/data/icons/scalable/devices/audio-subwoofer.svg @@ -0,0 +1,325 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + audio + device + subwoofer + output + + + audio-subwoofer + + + Evangeline McGlynn + + + + + + + + + + + + + + + + + + + diff --git a/panels/sound/data/icons/src/microphone-levels.svg b/panels/sound/data/icons/src/microphone-levels.svg new file mode 100644 index 000000000..c6b7222a0 --- /dev/null +++ b/panels/sound/data/icons/src/microphone-levels.svg @@ -0,0 +1,11448 @@ + + + + + + + + Optical Drive + + + + image/svg+xml + + Optical Drive + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/panels/sound/data/sounds/Makefile.am b/panels/sound/data/sounds/Makefile.am new file mode 100644 index 000000000..3277593ff --- /dev/null +++ b/panels/sound/data/sounds/Makefile.am @@ -0,0 +1,29 @@ +NULL = + +sounddir = $(datadir)/sounds/gnome/default/alerts + +sound_DATA = \ + bark.ogg \ + drip.ogg \ + glass.ogg \ + sonar.ogg \ + $(NULL) + +metadata_in_files = gnome-sounds-default.xml.in +metadatadir = $(pkgdatadir)/sounds +metadata_DATA = $(metadata_in_files:.xml.in=.xml) +@INTLTOOL_XML_RULE@ + +noinst_DATA = gnome-sounds-default.xml.in +CLEANFILES = gnome-sounds-default.xml gnome-sounds-default.xml.in + +EXTRA_DIST = $(sound_DATA) gnome-sounds-default.xml.in.in + +gnome-sounds-default.xml.in: gnome-sounds-default.xml.in.in Makefile + $(AM_V_GEN)sed -e 's^\@datadir\@^$(datadir)^g' < $(srcdir)/gnome-sounds-default.xml.in.in > gnome-sounds-default.xml.in.tmp \ + && mv gnome-sounds-default.xml.in.tmp gnome-sounds-default.xml.in + +MAINTAINERCLEANFILES = \ + Makefile.in + +-include $(top_srcdir)/git.mk diff --git a/panels/sound/data/sounds/bark.ogg b/panels/sound/data/sounds/bark.ogg new file mode 100644 index 0000000000000000000000000000000000000000..480950c681b94147299284d425e9ccbbd1fbb4d6 GIT binary patch literal 13322 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 literal 0 HcmV?d00001 diff --git a/panels/sound/data/sounds/drip.ogg b/panels/sound/data/sounds/drip.ogg new file mode 100644 index 0000000000000000000000000000000000000000..144d2b3670e8e294ee7cfe343355bf90123cb687 GIT binary patch literal 8495 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 literal 0 HcmV?d00001 diff --git a/panels/sound/data/sounds/glass.ogg b/panels/sound/data/sounds/glass.ogg new file mode 100644 index 0000000000000000000000000000000000000000..902a3c896a18d1a027eeeba0a23bcb55ca3d5c60 GIT binary patch literal 18999 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 literal 0 HcmV?d00001 diff --git a/panels/sound/data/sounds/gnome-sounds-default.xml.in.in b/panels/sound/data/sounds/gnome-sounds-default.xml.in.in new file mode 100644 index 000000000..bcf29aeb3 --- /dev/null +++ b/panels/sound/data/sounds/gnome-sounds-default.xml.in.in @@ -0,0 +1,27 @@ + + + + + <_name>Bark + @datadir@/sounds/gnome/default/alerts/bark.ogg + + + + <_name>Drip + @datadir@/sounds/gnome/default/alerts/drip.ogg + + + + <_name>Glass + @datadir@/sounds/gnome/default/alerts/glass.ogg + + + + <_name>Sonar + @datadir@/sounds/gnome/default/alerts/sonar.ogg + + diff --git a/panels/sound/data/sounds/sonar.ogg b/panels/sound/data/sounds/sonar.ogg new file mode 100644 index 0000000000000000000000000000000000000000..77aadec27c55e47b116c47a80ae6652bebd0afc5 GIT binary patch literal 20011 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 S(-UuRZ^eCjdg8ym72FD?EI=Os literal 0 HcmV?d00001 diff --git a/panels/sound/data/symbolic-icons/Makefile.am b/panels/sound/data/symbolic-icons/Makefile.am new file mode 100644 index 000000000..9f95f49d5 --- /dev/null +++ b/panels/sound/data/symbolic-icons/Makefile.am @@ -0,0 +1,10 @@ +NULL = + +SUBDIRS = scalable + +EXTRA_DIST = \ + src \ + r.rb \ + $(NULL) + +-include $(top_srcdir)/git.mk diff --git a/panels/sound/data/symbolic-icons/r.rb b/panels/sound/data/symbolic-icons/r.rb new file mode 100755 index 000000000..a15ace233 --- /dev/null +++ b/panels/sound/data/symbolic-icons/r.rb @@ -0,0 +1,73 @@ +#!/usr/bin/env ruby + +require "rexml/document" +require "fileutils" +include REXML + + +#INKSCAPE = '/opt/artlibre/bin/inkscape' +INKSCAPE = '/usr/bin/inkscape' # like this works for me, while using `which` inkscape hangs +SRC = "src/gnome-media.svg" +PREFIX = "scalable" + +def chopSVG(icon) + FileUtils.mkdir_p(icon[:dir]) unless File.exists?(icon[:dir]) + unless (File.exists?(icon[:file]) && !icon[:forcerender]) + FileUtils.cp(SRC,icon[:file]) + puts " >> #{icon[:name]}" + cmd = "#{INKSCAPE} -f #{icon[:file]} --select #{icon[:id]} --verb=FitCanvasToSelection --verb=EditInvertInAllLayers " + cmd += "--verb=EditDelete --verb=EditSelectAll --verb=SelectionUnGroup --verb=StrokeToPath --verb=FileVacuum " + cmd += "--verb=FileSave --verb=FileClose > /dev/null 2>&1" + system(cmd) + #saving as plain SVG gets rid of the classes :/ + #cmd = "#{INKSCAPE} -f #{icon[:file]} -z --vacuum-defs -l #{icon[:file]} > /dev/null 2>&1" + #system(cmd) + svgcrop = Document.new(File.new(icon[:file], 'r')) + svgcrop.root.each_element("//rect") do |rect| + if rect.attributes["width"] == '16' && rect.attributes["height"] == '16' + rect.remove + end + end + icon_f = File.new(icon[:file],'w+') + icon_f.puts svgcrop + icon_f.close + else + puts " -- #{icon[:name]} already exists" + end +end #end of function + + +#main +# Open SVG file. +svg = Document.new(File.new(SRC, 'r')) + +if (ARGV[0].nil?) #render all SVGs + puts "Rendering from icons in #{SRC}" + # Go through every layer. + svg.root.each_element("/svg/g[@inkscape:groupmode='layer']") do |context| + context_name = context.attributes.get_attribute("inkscape:label").value + puts "Going through layer '" + context_name + "'" + context.each_element("g") do |icon| + #puts "DEBUG #{icon.attributes.get_attribute('id')}" + dir = "#{PREFIX}/#{context_name}" + icon_name = icon.attributes.get_attribute("inkscape:label").value + chopSVG({ :name => icon_name, + :id => icon.attributes.get_attribute("id"), + :dir => dir, + :file => "#{dir}/#{icon_name}-symbolic.svg"}) + end + end + puts "\nrendered all SVGs" +else #only render the icons passed + icons = ARGV + ARGV.each do |icon_name| + icon = svg.root.elements["//g[@inkscape:label='#{icon_name}']"] + dir = "#{PREFIX}/#{icon.parent.attributes['inkscape:label']}" + chopSVG({ :name => icon_name, + :id => icon.attributes["id"], + :dir => dir, + :file => "#{dir}/#{icon_name}-symbolic.svg", + :forcerender => true}) + end + puts "\nrendered #{ARGV.length} icons" +end diff --git a/panels/sound/data/symbolic-icons/scalable/Makefile.am b/panels/sound/data/symbolic-icons/scalable/Makefile.am new file mode 100644 index 000000000..b17aad85d --- /dev/null +++ b/panels/sound/data/symbolic-icons/scalable/Makefile.am @@ -0,0 +1,3 @@ +SUBDIRS = status + +-include $(top_srcdir)/git.mk diff --git a/panels/sound/data/symbolic-icons/scalable/status/Makefile.am b/panels/sound/data/symbolic-icons/scalable/status/Makefile.am new file mode 100644 index 000000000..26809918b --- /dev/null +++ b/panels/sound/data/symbolic-icons/scalable/status/Makefile.am @@ -0,0 +1,14 @@ +NULL = + +iconsdir = $(datadir)/icons/hicolor/scalable/status + +icons_DATA = \ + audio-input-microphone-high-symbolic.svg \ + audio-input-microphone-low-symbolic.svg \ + audio-input-microphone-medium-symbolic.svg \ + audio-input-microphone-muted-symbolic.svg \ + $(NULL) + +EXTRA_DIST = $(icons_DATA) + +-include $(top_srcdir)/git.mk diff --git a/panels/sound/data/symbolic-icons/scalable/status/audio-input-microphone-high-symbolic.svg b/panels/sound/data/symbolic-icons/scalable/status/audio-input-microphone-high-symbolic.svg new file mode 100644 index 000000000..6cf30e72f --- /dev/null +++ b/panels/sound/data/symbolic-icons/scalable/status/audio-input-microphone-high-symbolic.svg @@ -0,0 +1,37 @@ + + + + + + + + image/svg+xml + + Gnome Symbolic Icon Theme + + + + + + + Gnome Symbolic Icon Theme + + + + + + + + + + + + + + + + + + + + diff --git a/panels/sound/data/symbolic-icons/scalable/status/audio-input-microphone-low-symbolic.svg b/panels/sound/data/symbolic-icons/scalable/status/audio-input-microphone-low-symbolic.svg new file mode 100644 index 000000000..3119fa4f8 --- /dev/null +++ b/panels/sound/data/symbolic-icons/scalable/status/audio-input-microphone-low-symbolic.svg @@ -0,0 +1,37 @@ + + + + + + + + image/svg+xml + + Gnome Symbolic Icon Theme + + + + + + + Gnome Symbolic Icon Theme + + + + + + + + + + + + + + + + + + + + diff --git a/panels/sound/data/symbolic-icons/scalable/status/audio-input-microphone-medium-symbolic.svg b/panels/sound/data/symbolic-icons/scalable/status/audio-input-microphone-medium-symbolic.svg new file mode 100644 index 000000000..64ec37a61 --- /dev/null +++ b/panels/sound/data/symbolic-icons/scalable/status/audio-input-microphone-medium-symbolic.svg @@ -0,0 +1,37 @@ + + + + + + + + image/svg+xml + + Gnome Symbolic Icon Theme + + + + + + + Gnome Symbolic Icon Theme + + + + + + + + + + + + + + + + + + + + diff --git a/panels/sound/data/symbolic-icons/scalable/status/audio-input-microphone-muted-symbolic.svg b/panels/sound/data/symbolic-icons/scalable/status/audio-input-microphone-muted-symbolic.svg new file mode 100644 index 000000000..d17baa9ea --- /dev/null +++ b/panels/sound/data/symbolic-icons/scalable/status/audio-input-microphone-muted-symbolic.svg @@ -0,0 +1,37 @@ + + + + + + + + image/svg+xml + + Gnome Symbolic Icon Theme + + + + + + + Gnome Symbolic Icon Theme + + + + + + + + + + + + + + + + + + + + diff --git a/panels/sound/data/symbolic-icons/src/gnome-media.svg b/panels/sound/data/symbolic-icons/src/gnome-media.svg new file mode 100644 index 000000000..6fd726860 --- /dev/null +++ b/panels/sound/data/symbolic-icons/src/gnome-media.svg @@ -0,0 +1,990 @@ + + + + + + + + image/svg+xml + + Gnome Symbolic Icon Theme + + + + + + + Gnome Symbolic Icon Theme + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + status + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/panels/sound/gvc-applet.c b/panels/sound/gvc-applet.c new file mode 100644 index 000000000..ef94ac57d --- /dev/null +++ b/panels/sound/gvc-applet.c @@ -0,0 +1,311 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Red Hat, Inc. + * + * 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 +#include +#include + +#include "gvc-applet.h" +#include "gvc-mixer-control.h" +#include "gvc-stream-status-icon.h" + +#define GVC_APPLET_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_APPLET, GvcAppletPrivate)) + +#define SCALE_SIZE 128 + +static const char *output_icon_names[] = { + "audio-volume-muted-symbolic", + "audio-volume-low-symbolic", + "audio-volume-medium-symbolic", + "audio-volume-high-symbolic", + NULL +}; + +static const char *input_icon_names[] = { + "audio-input-microphone-muted-symbolic", + "audio-input-microphone-low-symbolic", + "audio-input-microphone-medium-symbolic", + "audio-input-microphone-high-symbolic", + NULL +}; + +struct GvcAppletPrivate +{ + GvcStreamStatusIcon *input_status_icon; + GvcStreamStatusIcon *output_status_icon; + GvcMixerControl *control; +}; + +static void gvc_applet_class_init (GvcAppletClass *klass); +static void gvc_applet_init (GvcApplet *applet); +static void gvc_applet_finalize (GObject *object); + +G_DEFINE_TYPE (GvcApplet, gvc_applet, G_TYPE_OBJECT) + +static void +maybe_show_status_icons (GvcApplet *applet) +{ + gboolean show; + GvcMixerStream *stream; + GSList *source_outputs, *l; + + show = TRUE; + stream = gvc_mixer_control_get_default_sink (applet->priv->control); + if (stream == NULL) { + show = FALSE; + } + gtk_status_icon_set_visible (GTK_STATUS_ICON (applet->priv->output_status_icon), show); + + + show = FALSE; + stream = gvc_mixer_control_get_default_source (applet->priv->control); + source_outputs = gvc_mixer_control_get_source_outputs (applet->priv->control); + if (stream != NULL && source_outputs != NULL) { + /* Check that we're not trying to add the peak detector + * as an application doing recording */ + for (l = source_outputs ; l ; l = l->next) { + GvcMixerStream *s = l->data; + const char *id; + + id = gvc_mixer_stream_get_application_id (s); + if (id == NULL) { + show = TRUE; + break; + } + + if (!g_str_equal (id, "org.gnome.VolumeControl") && + !g_str_equal (id, "org.PulseAudio.pavucontrol")) { + show = TRUE; + break; + } + } + } + gtk_status_icon_set_visible (GTK_STATUS_ICON (applet->priv->input_status_icon), show); + + g_slist_free (source_outputs); +} + +void +gvc_applet_start (GvcApplet *applet) +{ + g_return_if_fail (GVC_IS_APPLET (applet)); + + maybe_show_status_icons (applet); +} + +static void +gvc_applet_dispose (GObject *object) +{ + GvcApplet *applet = GVC_APPLET (object); + + if (applet->priv->control != NULL) { + g_object_unref (applet->priv->control); + applet->priv->control = NULL; + } + + G_OBJECT_CLASS (gvc_applet_parent_class)->dispose (object); +} + +static void +update_default_source (GvcApplet *applet) +{ + GvcMixerStream *stream; + + stream = gvc_mixer_control_get_default_source (applet->priv->control); + if (stream != NULL) { + gvc_stream_status_icon_set_mixer_stream (applet->priv->input_status_icon, + stream); + maybe_show_status_icons(applet); + } else { + g_debug ("Unable to get default source, or no source available"); + } +} + +static void +update_default_sink (GvcApplet *applet) +{ + GvcMixerStream *stream; + + stream = gvc_mixer_control_get_default_sink (applet->priv->control); + if (stream != NULL) { + gvc_stream_status_icon_set_mixer_stream (applet->priv->output_status_icon, + stream); + maybe_show_status_icons(applet); + } else { + g_warning ("Unable to get default sink"); + } +} + +static void +on_control_ready (GvcMixerControl *control, + GvcApplet *applet) +{ + update_default_sink (applet); + update_default_source (applet); +} + +static void +on_control_connecting (GvcMixerControl *control, + GvcApplet *applet) +{ + g_debug ("Connecting.."); +} + +static void +on_control_default_sink_changed (GvcMixerControl *control, + guint id, + GvcApplet *applet) +{ + update_default_sink (applet); +} + +static void +on_control_default_source_changed (GvcMixerControl *control, + guint id, + GvcApplet *applet) +{ + update_default_source (applet); +} + +static void +on_control_stream_removed (GvcMixerControl *control, + guint id, + GvcApplet *applet) +{ + maybe_show_status_icons (applet); +} + +static void +on_control_stream_added (GvcMixerControl *control, + guint id, + GvcApplet *applet) +{ + maybe_show_status_icons (applet); +} + +static GObject * +gvc_applet_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_params) +{ + GObject *object; + GvcApplet *self; + + object = G_OBJECT_CLASS (gvc_applet_parent_class)->constructor (type, n_construct_properties, construct_params); + + self = GVC_APPLET (object); + + self->priv->control = gvc_mixer_control_new ("GNOME Volume Control Applet"); + g_signal_connect (self->priv->control, + "ready", + G_CALLBACK (on_control_ready), + self); + g_signal_connect (self->priv->control, + "connecting", + G_CALLBACK (on_control_connecting), + self); + g_signal_connect (self->priv->control, + "default-sink-changed", + G_CALLBACK (on_control_default_sink_changed), + self); + g_signal_connect (self->priv->control, + "default-source-changed", + G_CALLBACK (on_control_default_source_changed), + self); + g_signal_connect (self->priv->control, + "stream-added", + G_CALLBACK (on_control_stream_added), + self); + g_signal_connect (self->priv->control, + "stream-removed", + G_CALLBACK (on_control_stream_removed), + self); + + gvc_mixer_control_open (self->priv->control); + + return object; +} + +static void +gvc_applet_class_init (GvcAppletClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gvc_applet_finalize; + object_class->dispose = gvc_applet_dispose; + object_class->constructor = gvc_applet_constructor; + + g_type_class_add_private (klass, sizeof (GvcAppletPrivate)); +} + +static void +gvc_applet_init (GvcApplet *applet) +{ + applet->priv = GVC_APPLET_GET_PRIVATE (applet); + + applet->priv->output_status_icon = gvc_stream_status_icon_new (NULL, + output_icon_names); + gvc_stream_status_icon_set_display_name (applet->priv->output_status_icon, + _("Output")); + gtk_status_icon_set_title (GTK_STATUS_ICON (applet->priv->output_status_icon), + _("Sound Output Volume")); + applet->priv->input_status_icon = gvc_stream_status_icon_new (NULL, + input_icon_names); + gvc_stream_status_icon_set_display_name (applet->priv->input_status_icon, + _("Input")); + gtk_status_icon_set_title (GTK_STATUS_ICON (applet->priv->input_status_icon), + _("Microphone Volume")); +} + +static void +gvc_applet_finalize (GObject *object) +{ + GvcApplet *applet; + + g_return_if_fail (object != NULL); + g_return_if_fail (GVC_IS_APPLET (object)); + + applet = GVC_APPLET (object); + + g_return_if_fail (applet->priv != NULL); + + + G_OBJECT_CLASS (gvc_applet_parent_class)->finalize (object); +} + +GvcApplet * +gvc_applet_new (void) +{ + GObject *applet; + + applet = g_object_new (GVC_TYPE_APPLET, NULL); + + return GVC_APPLET (applet); +} diff --git a/panels/sound/gvc-applet.h b/panels/sound/gvc-applet.h new file mode 100644 index 000000000..d9e031126 --- /dev/null +++ b/panels/sound/gvc-applet.h @@ -0,0 +1,55 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Red Hat, Inc. + * + * 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. + * + */ + +#ifndef __GVC_APPLET_H +#define __GVC_APPLET_H + +#include + +G_BEGIN_DECLS + +#define GVC_TYPE_APPLET (gvc_applet_get_type ()) +#define GVC_APPLET(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GVC_TYPE_APPLET, GvcApplet)) +#define GVC_APPLET_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GVC_TYPE_APPLET, GvcAppletClass)) +#define GVC_IS_APPLET(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVC_TYPE_APPLET)) +#define GVC_IS_APPLET_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GVC_TYPE_APPLET)) +#define GVC_APPLET_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVC_TYPE_APPLET, GvcAppletClass)) + +typedef struct GvcAppletPrivate GvcAppletPrivate; + +typedef struct +{ + GObject parent; + GvcAppletPrivate *priv; +} GvcApplet; + +typedef struct +{ + GObjectClass parent_class; +} GvcAppletClass; + +GType gvc_applet_get_type (void); + +GvcApplet * gvc_applet_new (void); +void gvc_applet_start (GvcApplet *applet); + +G_END_DECLS + +#endif /* __GVC_APPLET_H */ diff --git a/panels/sound/gvc-balance-bar.c b/panels/sound/gvc-balance-bar.c new file mode 100644 index 000000000..78594bf36 --- /dev/null +++ b/panels/sound/gvc-balance-bar.c @@ -0,0 +1,550 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 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 + +#include "gvc-balance-bar.h" +#include "gvc-channel-map-private.h" + +#define SCALE_SIZE 128 +#define ADJUSTMENT_MAX_NORMAL 65536.0 /* PA_VOLUME_NORM */ + +#define GVC_BALANCE_BAR_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_BALANCE_BAR, GvcBalanceBarPrivate)) + +struct GvcBalanceBarPrivate +{ + GvcChannelMap *channel_map; + GvcBalanceType btype; + GtkWidget *scale_box; + GtkWidget *start_box; + GtkWidget *end_box; + GtkWidget *label; + GtkWidget *scale; + GtkAdjustment *adjustment; + GtkSizeGroup *size_group; + gboolean symmetric; + gboolean click_lock; +}; + +enum +{ + PROP_0, + PROP_CHANNEL_MAP, + PROP_BALANCE_TYPE, +}; + +static void gvc_balance_bar_class_init (GvcBalanceBarClass *klass); +static void gvc_balance_bar_init (GvcBalanceBar *balance_bar); +static void gvc_balance_bar_finalize (GObject *object); + +static gboolean on_scale_button_press_event (GtkWidget *widget, + GdkEventButton *event, + GvcBalanceBar *bar); +static gboolean on_scale_button_release_event (GtkWidget *widget, + GdkEventButton *event, + GvcBalanceBar *bar); +static gboolean on_scale_scroll_event (GtkWidget *widget, + GdkEventScroll *event, + GvcBalanceBar *bar); +static void on_adjustment_value_changed (GtkAdjustment *adjustment, + GvcBalanceBar *bar); + +G_DEFINE_TYPE (GvcBalanceBar, gvc_balance_bar, GTK_TYPE_HBOX) + +static GtkWidget * +_scale_box_new (GvcBalanceBar *bar) +{ + GvcBalanceBarPrivate *priv = bar->priv; + GtkWidget *box; + GtkWidget *sbox; + GtkWidget *ebox; + GtkAdjustment *adjustment = bar->priv->adjustment; + char *str_lower, *str_upper; + gdouble lower, upper; + + bar->priv->scale_box = box = gtk_hbox_new (FALSE, 6); + priv->scale = gtk_hscale_new (priv->adjustment); + gtk_widget_set_size_request (priv->scale, SCALE_SIZE, -1); + + gtk_widget_set_name (priv->scale, "balance-bar-scale"); + gtk_rc_parse_string ("style \"balance-bar-scale-style\" {\n" + " GtkScale::trough-side-details = 0\n" + "}\n" + "widget \"*.balance-bar-scale\" style : rc \"balance-bar-scale-style\"\n"); + + bar->priv->start_box = sbox = gtk_hbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX (box), sbox, FALSE, FALSE, 0); + + gtk_box_pack_start (GTK_BOX (sbox), priv->label, FALSE, FALSE, 0); + + gtk_box_pack_start (GTK_BOX (box), priv->scale, TRUE, TRUE, 0); + + switch (bar->priv->btype) { + case BALANCE_TYPE_RL: + str_lower = g_strdup_printf ("%s", C_("balance", "Left")); + str_upper = g_strdup_printf ("%s", C_("balance", "Right")); + break; + case BALANCE_TYPE_FR: + str_lower = g_strdup_printf ("%s", C_("balance", "Rear")); + str_upper = g_strdup_printf ("%s", C_("balance", "Front")); + break; + case BALANCE_TYPE_LFE: + str_lower = g_strdup_printf ("%s", C_("balance", "Minimum")); + str_upper = g_strdup_printf ("%s", C_("balance", "Maximum")); + break; + default: + g_assert_not_reached (); + } + + lower = gtk_adjustment_get_lower (adjustment); + gtk_scale_add_mark (GTK_SCALE (priv->scale), lower, + GTK_POS_BOTTOM, str_lower); + g_free (str_lower); + upper = gtk_adjustment_get_upper (adjustment); + gtk_scale_add_mark (GTK_SCALE (priv->scale), upper, + GTK_POS_BOTTOM, str_upper); + g_free (str_upper); + + if (bar->priv->btype != BALANCE_TYPE_LFE) { + gtk_scale_add_mark (GTK_SCALE (priv->scale), + (upper - lower)/2 + lower, + GTK_POS_BOTTOM, NULL); + } + + bar->priv->end_box = ebox = gtk_hbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX (box), ebox, FALSE, FALSE, 0); + + gtk_range_set_update_policy (GTK_RANGE (priv->scale), GTK_UPDATE_CONTINUOUS); + ca_gtk_widget_disable_sounds (bar->priv->scale, FALSE); + gtk_widget_add_events (bar->priv->scale, GDK_SCROLL_MASK); + + g_signal_connect (G_OBJECT (bar->priv->scale), "button-press-event", + G_CALLBACK (on_scale_button_press_event), bar); + g_signal_connect (G_OBJECT (bar->priv->scale), "button-release-event", + G_CALLBACK (on_scale_button_release_event), bar); + g_signal_connect (G_OBJECT (bar->priv->scale), "scroll-event", + G_CALLBACK (on_scale_scroll_event), bar); + + if (bar->priv->size_group != NULL) { + gtk_size_group_add_widget (bar->priv->size_group, sbox); + + if (bar->priv->symmetric) { + gtk_size_group_add_widget (bar->priv->size_group, ebox); + } + } + + gtk_scale_set_draw_value (GTK_SCALE (priv->scale), FALSE); + + return box; +} + +void +gvc_balance_bar_set_size_group (GvcBalanceBar *bar, + GtkSizeGroup *group, + gboolean symmetric) +{ + g_return_if_fail (GVC_IS_BALANCE_BAR (bar)); + + bar->priv->size_group = group; + bar->priv->symmetric = symmetric; + + if (bar->priv->size_group != NULL) { + gtk_size_group_add_widget (bar->priv->size_group, + bar->priv->start_box); + + if (bar->priv->symmetric) { + gtk_size_group_add_widget (bar->priv->size_group, + bar->priv->end_box); + } + } + gtk_widget_queue_draw (GTK_WIDGET (bar)); +} + +static const char * +btype_to_string (guint btype) +{ + switch (btype) { + case BALANCE_TYPE_RL: + return "Balance"; + case BALANCE_TYPE_FR: + return "Fade"; + break; + case BALANCE_TYPE_LFE: + return "LFE"; + default: + g_assert_not_reached (); + } + return NULL; +} + +static void +update_level_from_map (GvcBalanceBar *bar, + GvcChannelMap *map) +{ + const gdouble *volumes; + gdouble val; + + g_debug ("Volume changed (for %s bar)", btype_to_string (bar->priv->btype)); + + volumes = gvc_channel_map_get_volume (map); + switch (bar->priv->btype) { + case BALANCE_TYPE_RL: + val = volumes[BALANCE]; + break; + case BALANCE_TYPE_FR: + val = volumes[FADE]; + break; + case BALANCE_TYPE_LFE: + val = volumes[LFE]; + break; + default: + g_assert_not_reached (); + } + + gtk_adjustment_set_value (bar->priv->adjustment, val); +} + +static void +on_channel_map_volume_changed (GvcChannelMap *map, + gboolean set, + GvcBalanceBar *bar) +{ + update_level_from_map (bar, map); +} + +static void +gvc_balance_bar_set_channel_map (GvcBalanceBar *bar, + GvcChannelMap *map) +{ + g_return_if_fail (GVC_BALANCE_BAR (bar)); + + if (bar->priv->channel_map != NULL) { + g_signal_handlers_disconnect_by_func (G_OBJECT (bar->priv->channel_map), + on_channel_map_volume_changed, bar); + g_object_unref (bar->priv->channel_map); + } + bar->priv->channel_map = g_object_ref (map); + + update_level_from_map (bar, map); + + g_signal_connect (G_OBJECT (map), "volume-changed", + G_CALLBACK (on_channel_map_volume_changed), bar); + + g_object_notify (G_OBJECT (bar), "channel-map"); +} + +static void +gvc_balance_bar_set_balance_type (GvcBalanceBar *bar, + GvcBalanceType btype) +{ + GtkWidget *frame; + + g_return_if_fail (GVC_BALANCE_BAR (bar)); + + bar->priv->btype = btype; + if (bar->priv->btype != BALANCE_TYPE_LFE) { + bar->priv->adjustment = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, + -1.0, + 1.0, + 0.5, + 0.5, + 0.0)); + } else { + bar->priv->adjustment = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, + 0.0, + ADJUSTMENT_MAX_NORMAL, + ADJUSTMENT_MAX_NORMAL/100.0, + ADJUSTMENT_MAX_NORMAL/10.0, + 0.0)); + } + + g_object_ref_sink (bar->priv->adjustment); + g_signal_connect (bar->priv->adjustment, + "value-changed", + G_CALLBACK (on_adjustment_value_changed), + bar); + + switch (btype) { + case BALANCE_TYPE_RL: + bar->priv->label = gtk_label_new_with_mnemonic (_("_Balance:")); + break; + case BALANCE_TYPE_FR: + bar->priv->label = gtk_label_new_with_mnemonic (_("_Fade:")); + break; + case BALANCE_TYPE_LFE: + bar->priv->label = gtk_label_new_with_mnemonic (_("_Subwoofer:")); + break; + default: + g_assert_not_reached (); + } + gtk_misc_set_alignment (GTK_MISC (bar->priv->label), + 0.0, + 0.0); + /* frame */ + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_NONE); + gtk_container_add (GTK_CONTAINER (bar), frame); + + /* box with scale */ + bar->priv->scale_box = _scale_box_new (bar); + gtk_container_add (GTK_CONTAINER (frame), bar->priv->scale_box); + gtk_widget_show_all (frame); + + gtk_widget_set_direction (bar->priv->scale, GTK_TEXT_DIR_LTR); + gtk_label_set_mnemonic_widget (GTK_LABEL (bar->priv->label), + bar->priv->scale); + + g_object_notify (G_OBJECT (bar), "balance-type"); +} + +static void +gvc_balance_bar_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GvcBalanceBar *self = GVC_BALANCE_BAR (object); + + switch (prop_id) { + case PROP_CHANNEL_MAP: + gvc_balance_bar_set_channel_map (self, g_value_get_object (value)); + break; + case PROP_BALANCE_TYPE: + gvc_balance_bar_set_balance_type (self, g_value_get_int (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gvc_balance_bar_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GvcBalanceBar *self = GVC_BALANCE_BAR (object); + + switch (prop_id) { + case PROP_CHANNEL_MAP: + g_value_set_object (value, self->priv->channel_map); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GObject * +gvc_balance_bar_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_params) +{ + return G_OBJECT_CLASS (gvc_balance_bar_parent_class)->constructor (type, n_construct_properties, construct_params); +} + +static void +gvc_balance_bar_class_init (GvcBalanceBarClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructor = gvc_balance_bar_constructor; + object_class->finalize = gvc_balance_bar_finalize; + object_class->set_property = gvc_balance_bar_set_property; + object_class->get_property = gvc_balance_bar_get_property; + + g_object_class_install_property (object_class, + PROP_CHANNEL_MAP, + g_param_spec_object ("channel-map", + "channel map", + "The channel map", + GVC_TYPE_CHANNEL_MAP, + G_PARAM_READWRITE)); + g_object_class_install_property (object_class, + PROP_BALANCE_TYPE, + g_param_spec_int ("balance-type", + "balance type", + "Whether the balance is right-left or front-rear", + BALANCE_TYPE_RL, NUM_BALANCE_TYPES - 1, BALANCE_TYPE_RL, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY)); + + g_type_class_add_private (klass, sizeof (GvcBalanceBarPrivate)); +} + + +static gboolean +on_scale_button_press_event (GtkWidget *widget, + GdkEventButton *event, + GvcBalanceBar *bar) +{ + bar->priv->click_lock = TRUE; + + return FALSE; +} + +static gboolean +on_scale_button_release_event (GtkWidget *widget, + GdkEventButton *event, + GvcBalanceBar *bar) +{ + bar->priv->click_lock = FALSE; + + return FALSE; +} + +static gboolean +on_scale_scroll_event (GtkWidget *widget, + GdkEventScroll *event, + GvcBalanceBar *bar) +{ + gdouble value; + + value = gtk_adjustment_get_value (bar->priv->adjustment); + + if (bar->priv->btype == BALANCE_TYPE_LFE) { + if (event->direction == GDK_SCROLL_UP) { + if (value + ADJUSTMENT_MAX_NORMAL/100.0 > ADJUSTMENT_MAX_NORMAL) + value = ADJUSTMENT_MAX_NORMAL; + else + value = value + ADJUSTMENT_MAX_NORMAL/100.0; + } else if (event->direction == GDK_SCROLL_DOWN) { + if (value - ADJUSTMENT_MAX_NORMAL/100.0 < 0) + value = 0.0; + else + value = value - ADJUSTMENT_MAX_NORMAL/100.0; + } + } else { + if (event->direction == GDK_SCROLL_UP) { + if (value + 0.01 > 1.0) + value = 1.0; + else + value = value + 0.01; + } else if (event->direction == GDK_SCROLL_DOWN) { + if (value - 0.01 < 0) + value = 0.0; + else + value = value - 0.01; + } + } + gtk_adjustment_set_value (bar->priv->adjustment, value); + + return TRUE; +} + +/* FIXME remove when we depend on a newer PA */ +static pa_cvolume * +gvc_pa_cvolume_set_position (pa_cvolume *cv, const pa_channel_map *map, pa_channel_position_t t, pa_volume_t v) { + unsigned c; + gboolean good = FALSE; + + g_assert(cv); + g_assert(map); + + g_return_val_if_fail(pa_cvolume_compatible_with_channel_map(cv, map), NULL); + g_return_val_if_fail(t < PA_CHANNEL_POSITION_MAX, NULL); + + for (c = 0; c < map->channels; c++) + if (map->map[c] == t) { + cv->values[c] = v; + good = TRUE; + } + + return good ? cv : NULL; +} + +static void +on_adjustment_value_changed (GtkAdjustment *adjustment, + GvcBalanceBar *bar) +{ + gdouble val; + pa_cvolume cv; + const pa_channel_map *pa_map; + + if (bar->priv->channel_map == NULL) + return; + + cv = *gvc_channel_map_get_cvolume (bar->priv->channel_map); + val = gtk_adjustment_get_value (adjustment); + + pa_map = gvc_channel_map_get_pa_channel_map (bar->priv->channel_map); + + switch (bar->priv->btype) { + case BALANCE_TYPE_RL: + pa_cvolume_set_balance (&cv, pa_map, val); + break; + case BALANCE_TYPE_FR: + pa_cvolume_set_fade (&cv, pa_map, val); + break; + case BALANCE_TYPE_LFE: + gvc_pa_cvolume_set_position (&cv, pa_map, PA_CHANNEL_POSITION_LFE, val); + break; + } + + gvc_channel_map_volume_changed (bar->priv->channel_map, &cv, TRUE); +} + +static void +gvc_balance_bar_init (GvcBalanceBar *bar) +{ + bar->priv = GVC_BALANCE_BAR_GET_PRIVATE (bar); +} + +static void +gvc_balance_bar_finalize (GObject *object) +{ + GvcBalanceBar *bar; + + g_return_if_fail (object != NULL); + g_return_if_fail (GVC_IS_BALANCE_BAR (object)); + + bar = GVC_BALANCE_BAR (object); + + g_return_if_fail (bar->priv != NULL); + + if (bar->priv->channel_map != NULL) { + g_signal_handlers_disconnect_by_func (G_OBJECT (bar->priv->channel_map), + on_channel_map_volume_changed, bar); + g_object_unref (bar->priv->channel_map); + } + + G_OBJECT_CLASS (gvc_balance_bar_parent_class)->finalize (object); +} + +GtkWidget * +gvc_balance_bar_new (const GvcChannelMap *channel_map, GvcBalanceType btype) +{ + GObject *bar; + bar = g_object_new (GVC_TYPE_BALANCE_BAR, + "channel-map", channel_map, + "balance-type", btype, + NULL); + return GTK_WIDGET (bar); +} diff --git a/panels/sound/gvc-balance-bar.h b/panels/sound/gvc-balance-bar.h new file mode 100644 index 000000000..95e96dc33 --- /dev/null +++ b/panels/sound/gvc-balance-bar.h @@ -0,0 +1,69 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Red Hat, Inc. + * + * 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. + * + */ + +#ifndef __GVC_BALANCE_BAR_H +#define __GVC_BALANCE_BAR_H + +#include + +#include "gvc-channel-map.h" + +G_BEGIN_DECLS + +#define GVC_TYPE_BALANCE_BAR (gvc_balance_bar_get_type ()) +#define GVC_BALANCE_BAR(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GVC_TYPE_BALANCE_BAR, GvcBalanceBar)) +#define GVC_BALANCE_BAR_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GVC_TYPE_BALANCE_BAR, GvcBalanceBarClass)) +#define GVC_IS_BALANCE_BAR(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVC_TYPE_BALANCE_BAR)) +#define GVC_IS_BALANCE_BAR_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GVC_TYPE_BALANCE_BAR)) +#define GVC_BALANCE_BAR_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVC_TYPE_BALANCE_BAR, GvcBalanceBarClass)) + +typedef enum { + BALANCE_TYPE_RL, + BALANCE_TYPE_FR, + BALANCE_TYPE_LFE, +} GvcBalanceType; + +#define NUM_BALANCE_TYPES BALANCE_TYPE_LFE + 1 + +typedef struct GvcBalanceBarPrivate GvcBalanceBarPrivate; + +typedef struct +{ + GtkHBox parent; + GvcBalanceBarPrivate *priv; +} GvcBalanceBar; + +typedef struct +{ + GtkHBoxClass parent_class; +} GvcBalanceBarClass; + +GType gvc_balance_bar_get_type (void); + +GtkWidget * gvc_balance_bar_new (const GvcChannelMap *map, + GvcBalanceType btype); + +void gvc_balance_bar_set_size_group (GvcBalanceBar *bar, + GtkSizeGroup *group, + gboolean symmetric); + +G_END_DECLS + +#endif /* __GVC_BALANCE_BAR_H */ diff --git a/panels/sound/gvc-channel-bar.c b/panels/sound/gvc-channel-bar.c new file mode 100644 index 000000000..37b87bc2a --- /dev/null +++ b/panels/sound/gvc-channel-bar.c @@ -0,0 +1,963 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 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 + +#include "gvc-channel-bar.h" + +#define SCALE_SIZE 128 +#define ADJUSTMENT_MAX_NORMAL 65536.0 /* PA_VOLUME_NORM */ +#define ADJUSTMENT_MAX_AMPLIFIED 98304.0 /* 1.5 * ADJUSTMENT_MAX_NORMAL */ +#define ADJUSTMENT_MAX (bar->priv->is_amplified ? ADJUSTMENT_MAX_AMPLIFIED : ADJUSTMENT_MAX_NORMAL) +#define SCROLLSTEP (ADJUSTMENT_MAX / 100.0 * 5.0) + +#define GVC_CHANNEL_BAR_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_CHANNEL_BAR, GvcChannelBarPrivate)) + +struct GvcChannelBarPrivate +{ + GtkOrientation orientation; + GtkWidget *scale_box; + GtkWidget *start_box; + GtkWidget *end_box; + GtkWidget *image; + GtkWidget *label; + GtkWidget *low_image; + GtkWidget *scale; + GtkWidget *high_image; + GtkWidget *mute_box; + GtkWidget *mute_button; + GtkAdjustment *adjustment; + GtkAdjustment *zero_adjustment; + gboolean show_mute; + gboolean is_muted; + char *name; + char *icon_name; + char *low_icon_name; + char *high_icon_name; + GtkSizeGroup *size_group; + gboolean symmetric; + gboolean click_lock; + gboolean is_amplified; + guint32 base_volume; +}; + +enum +{ + PROP_0, + PROP_ORIENTATION, + PROP_SHOW_MUTE, + PROP_IS_MUTED, + PROP_ADJUSTMENT, + PROP_NAME, + PROP_ICON_NAME, + PROP_LOW_ICON_NAME, + PROP_HIGH_ICON_NAME, + PROP_IS_AMPLIFIED, + PROP_ELLIPSIZE +}; + +static void gvc_channel_bar_class_init (GvcChannelBarClass *klass); +static void gvc_channel_bar_init (GvcChannelBar *channel_bar); +static void gvc_channel_bar_finalize (GObject *object); + +static gboolean on_scale_button_press_event (GtkWidget *widget, + GdkEventButton *event, + GvcChannelBar *bar); +static gboolean on_scale_button_release_event (GtkWidget *widget, + GdkEventButton *event, + GvcChannelBar *bar); +static gboolean on_scale_scroll_event (GtkWidget *widget, + GdkEventScroll *event, + GvcChannelBar *bar); + +G_DEFINE_TYPE (GvcChannelBar, gvc_channel_bar, GTK_TYPE_HBOX) + +static GtkWidget * +_scale_box_new (GvcChannelBar *bar) +{ + GvcChannelBarPrivate *priv = bar->priv; + GtkWidget *box; + GtkWidget *sbox; + GtkWidget *ebox; + + if (priv->orientation == GTK_ORIENTATION_VERTICAL) { + bar->priv->scale_box = box = gtk_vbox_new (FALSE, 6); + + priv->scale = gtk_vscale_new (priv->adjustment); + + gtk_widget_set_size_request (priv->scale, -1, SCALE_SIZE); + gtk_range_set_inverted (GTK_RANGE (priv->scale), TRUE); + + bar->priv->start_box = sbox = gtk_vbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX (box), sbox, FALSE, FALSE, 0); + + gtk_box_pack_start (GTK_BOX (sbox), priv->image, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (sbox), priv->label, FALSE, FALSE, 0); + + gtk_box_pack_start (GTK_BOX (sbox), priv->high_image, FALSE, FALSE, 0); + gtk_widget_hide (priv->high_image); + gtk_box_pack_start (GTK_BOX (box), priv->scale, TRUE, TRUE, 0); + + bar->priv->end_box = ebox = gtk_vbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX (box), ebox, FALSE, FALSE, 0); + + gtk_box_pack_start (GTK_BOX (ebox), priv->low_image, FALSE, FALSE, 0); + gtk_widget_hide (priv->low_image); + + gtk_box_pack_start (GTK_BOX (ebox), priv->mute_box, FALSE, FALSE, 0); + } else { + bar->priv->scale_box = box = gtk_hbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX (box), priv->image, FALSE, FALSE, 0); + + priv->scale = gtk_hscale_new (priv->adjustment); + + gtk_widget_set_size_request (priv->scale, SCALE_SIZE, -1); + + bar->priv->start_box = sbox = gtk_hbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX (box), sbox, FALSE, FALSE, 0); + + gtk_box_pack_end (GTK_BOX (sbox), priv->low_image, FALSE, FALSE, 0); + gtk_widget_show (priv->low_image); + + gtk_box_pack_start (GTK_BOX (sbox), priv->label, TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (box), priv->scale, TRUE, TRUE, 0); + + bar->priv->end_box = ebox = gtk_hbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX (box), ebox, FALSE, FALSE, 0); + + gtk_box_pack_start (GTK_BOX (ebox), priv->high_image, FALSE, FALSE, 0); + gtk_widget_show (priv->high_image); + gtk_box_pack_start (GTK_BOX (ebox), priv->mute_box, FALSE, FALSE, 0); + } + + gtk_range_set_update_policy (GTK_RANGE (priv->scale), GTK_UPDATE_CONTINUOUS); + ca_gtk_widget_disable_sounds (bar->priv->scale, FALSE); + gtk_widget_add_events (bar->priv->scale, GDK_SCROLL_MASK); + + g_signal_connect (G_OBJECT (bar->priv->scale), "button-press-event", + G_CALLBACK (on_scale_button_press_event), bar); + g_signal_connect (G_OBJECT (bar->priv->scale), "button-release-event", + G_CALLBACK (on_scale_button_release_event), bar); + g_signal_connect (G_OBJECT (bar->priv->scale), "scroll-event", + G_CALLBACK (on_scale_scroll_event), bar); + + if (bar->priv->size_group != NULL) { + gtk_size_group_add_widget (bar->priv->size_group, sbox); + + if (bar->priv->symmetric) { + gtk_size_group_add_widget (bar->priv->size_group, ebox); + } + } + + gtk_scale_set_draw_value (GTK_SCALE (priv->scale), FALSE); + + return box; +} + +static void +update_image (GvcChannelBar *bar) +{ + gtk_image_set_from_icon_name (GTK_IMAGE (bar->priv->image), + bar->priv->icon_name, + GTK_ICON_SIZE_DIALOG); + + if (bar->priv->icon_name != NULL) { + gtk_widget_show (bar->priv->image); + } else { + gtk_widget_hide (bar->priv->image); + } +} + +static void +update_label (GvcChannelBar *bar) +{ + if (bar->priv->name != NULL) { + gtk_label_set_text_with_mnemonic (GTK_LABEL (bar->priv->label), + bar->priv->name); + gtk_label_set_mnemonic_widget (GTK_LABEL (bar->priv->label), + bar->priv->scale); + gtk_widget_show (bar->priv->label); + } else { + gtk_label_set_text (GTK_LABEL (bar->priv->label), NULL); + gtk_widget_hide (bar->priv->label); + } +} + +static void +update_layout (GvcChannelBar *bar) +{ + GtkWidget *box; + GtkWidget *frame; + + if (bar->priv->scale == NULL) { + return; + } + + box = bar->priv->scale_box; + frame = gtk_widget_get_parent (box); + + g_object_ref (bar->priv->image); + g_object_ref (bar->priv->label); + g_object_ref (bar->priv->mute_box); + g_object_ref (bar->priv->low_image); + g_object_ref (bar->priv->high_image); + + gtk_container_remove (GTK_CONTAINER (bar->priv->start_box), bar->priv->image); + gtk_container_remove (GTK_CONTAINER (bar->priv->start_box), bar->priv->label); + gtk_container_remove (GTK_CONTAINER (bar->priv->end_box), bar->priv->mute_box); + + if (bar->priv->orientation == GTK_ORIENTATION_VERTICAL) { + gtk_container_remove (GTK_CONTAINER (bar->priv->start_box), bar->priv->low_image); + gtk_container_remove (GTK_CONTAINER (bar->priv->end_box), bar->priv->high_image); + } else { + gtk_container_remove (GTK_CONTAINER (bar->priv->end_box), bar->priv->low_image); + gtk_container_remove (GTK_CONTAINER (bar->priv->start_box), bar->priv->high_image); + } + + gtk_container_remove (GTK_CONTAINER (box), bar->priv->start_box); + gtk_container_remove (GTK_CONTAINER (box), bar->priv->scale); + gtk_container_remove (GTK_CONTAINER (box), bar->priv->end_box); + gtk_container_remove (GTK_CONTAINER (frame), box); + + bar->priv->scale_box = _scale_box_new (bar); + gtk_container_add (GTK_CONTAINER (frame), bar->priv->scale_box); + + g_object_unref (bar->priv->image); + g_object_unref (bar->priv->label); + g_object_unref (bar->priv->mute_box); + g_object_unref (bar->priv->low_image); + g_object_unref (bar->priv->high_image); + + gtk_widget_show_all (frame); +} + +void +gvc_channel_bar_set_size_group (GvcChannelBar *bar, + GtkSizeGroup *group, + gboolean symmetric) +{ + g_return_if_fail (GVC_IS_CHANNEL_BAR (bar)); + + bar->priv->size_group = group; + bar->priv->symmetric = symmetric; + + if (bar->priv->size_group != NULL) { + gtk_size_group_add_widget (bar->priv->size_group, + bar->priv->start_box); + + if (bar->priv->symmetric) { + gtk_size_group_add_widget (bar->priv->size_group, + bar->priv->end_box); + } + } + gtk_widget_queue_draw (GTK_WIDGET (bar)); +} + +void +gvc_channel_bar_set_name (GvcChannelBar *bar, + const char *name) +{ + g_return_if_fail (GVC_IS_CHANNEL_BAR (bar)); + + g_free (bar->priv->name); + bar->priv->name = g_strdup (name); + update_label (bar); + g_object_notify (G_OBJECT (bar), "name"); +} + +void +gvc_channel_bar_set_icon_name (GvcChannelBar *bar, + const char *name) +{ + g_return_if_fail (GVC_IS_CHANNEL_BAR (bar)); + + g_free (bar->priv->icon_name); + bar->priv->icon_name = g_strdup (name); + update_image (bar); + g_object_notify (G_OBJECT (bar), "icon-name"); +} + +void +gvc_channel_bar_set_low_icon_name (GvcChannelBar *bar, + const char *name) +{ + g_return_if_fail (GVC_IS_CHANNEL_BAR (bar)); + + if (name != NULL && strcmp (bar->priv->low_icon_name, name) != 0) { + g_free (bar->priv->low_icon_name); + bar->priv->low_icon_name = g_strdup (name); + gtk_image_set_from_icon_name (GTK_IMAGE (bar->priv->low_image), + bar->priv->low_icon_name, + GTK_ICON_SIZE_BUTTON); + g_object_notify (G_OBJECT (bar), "low-icon-name"); + } +} + +void +gvc_channel_bar_set_high_icon_name (GvcChannelBar *bar, + const char *name) +{ + g_return_if_fail (GVC_IS_CHANNEL_BAR (bar)); + + if (name != NULL && strcmp (bar->priv->high_icon_name, name) != 0) { + g_free (bar->priv->high_icon_name); + bar->priv->high_icon_name = g_strdup (name); + gtk_image_set_from_icon_name (GTK_IMAGE (bar->priv->high_image), + bar->priv->high_icon_name, + GTK_ICON_SIZE_BUTTON); + g_object_notify (G_OBJECT (bar), "high-icon-name"); + } +} + +void +gvc_channel_bar_set_orientation (GvcChannelBar *bar, + GtkOrientation orientation) +{ + g_return_if_fail (GVC_IS_CHANNEL_BAR (bar)); + + if (orientation != bar->priv->orientation) { + bar->priv->orientation = orientation; + update_layout (bar); + g_object_notify (G_OBJECT (bar), "orientation"); + } +} + +static void +gvc_channel_bar_set_adjustment (GvcChannelBar *bar, + GtkAdjustment *adjustment) +{ + g_return_if_fail (GVC_CHANNEL_BAR (bar)); + g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment)); + + if (bar->priv->adjustment != NULL) { + g_object_unref (bar->priv->adjustment); + } + bar->priv->adjustment = g_object_ref_sink (adjustment); + + if (bar->priv->scale != NULL) { + gtk_range_set_adjustment (GTK_RANGE (bar->priv->scale), adjustment); + } + + g_object_notify (G_OBJECT (bar), "adjustment"); +} + +GtkAdjustment * +gvc_channel_bar_get_adjustment (GvcChannelBar *bar) +{ + g_return_val_if_fail (GVC_IS_CHANNEL_BAR (bar), NULL); + + return bar->priv->adjustment; +} + +static gboolean +on_scale_button_press_event (GtkWidget *widget, + GdkEventButton *event, + GvcChannelBar *bar) +{ + /* HACK: we want the behaviour you get with the middle button, so we + * mangle the event. clicking with other buttons moves the slider in + * step increments, clicking with the middle button moves the slider to + * the location of the click. + */ + if (event->button == 1) + event->button = 2; + + bar->priv->click_lock = TRUE; + + return FALSE; +} + +static gboolean +on_scale_button_release_event (GtkWidget *widget, + GdkEventButton *event, + GvcChannelBar *bar) +{ + GtkAdjustment *adj; + gdouble value; + + /* HACK: see on_scale_button_press_event() */ + if (event->button == 1) + event->button = 2; + + bar->priv->click_lock = FALSE; + + adj = gtk_range_get_adjustment (GTK_RANGE (widget)); + + value = gtk_adjustment_get_value (adj); + + /* this means the adjustment moved away from zero and + * therefore we should unmute and set the volume. */ + gvc_channel_bar_set_is_muted (bar, (value == 0.0)); + + /* Play a sound! */ + ca_gtk_play_for_widget (GTK_WIDGET (bar), 0, + CA_PROP_EVENT_ID, "audio-volume-change", + CA_PROP_EVENT_DESCRIPTION, "foobar event happened", + CA_PROP_APPLICATION_ID, "org.gnome.VolumeControl", + NULL); + + return FALSE; +} + +gboolean +gvc_channel_bar_scroll (GvcChannelBar *bar, GdkScrollDirection direction) +{ + GtkAdjustment *adj; + gdouble value; + + g_return_val_if_fail (bar != NULL, FALSE); + g_return_val_if_fail (GVC_IS_CHANNEL_BAR (bar), FALSE); + + if (bar->priv->orientation == GTK_ORIENTATION_VERTICAL) { + if (direction != GDK_SCROLL_UP && direction != GDK_SCROLL_DOWN) + return FALSE; + } else { + /* Switch direction for RTL */ + if (gtk_widget_get_direction (GTK_WIDGET (bar)) == GTK_TEXT_DIR_RTL) { + if (direction == GDK_SCROLL_RIGHT) + direction = GDK_SCROLL_LEFT; + else if (direction == GDK_SCROLL_LEFT) + direction = GDK_SCROLL_RIGHT; + } + /* Switch side scroll to vertical */ + if (direction == GDK_SCROLL_RIGHT) + direction = GDK_SCROLL_UP; + else if (GDK_SCROLL_LEFT) + direction = GDK_SCROLL_DOWN; + } + + adj = gtk_range_get_adjustment (GTK_RANGE (bar->priv->scale)); + if (adj == bar->priv->zero_adjustment) { + if (direction == GDK_SCROLL_UP) + gvc_channel_bar_set_is_muted (bar, FALSE); + return TRUE; + } + + value = gtk_adjustment_get_value (adj); + + if (direction == GDK_SCROLL_UP) { + if (value + SCROLLSTEP > ADJUSTMENT_MAX) + value = ADJUSTMENT_MAX; + else + value = value + SCROLLSTEP; + } else if (direction == GDK_SCROLL_DOWN) { + if (value - SCROLLSTEP < 0) + value = 0.0; + else + value = value - SCROLLSTEP; + } + + gvc_channel_bar_set_is_muted (bar, (value == 0.0)); + adj = gtk_range_get_adjustment (GTK_RANGE (bar->priv->scale)); + gtk_adjustment_set_value (adj, value); + + return TRUE; +} + +static gboolean +on_scale_scroll_event (GtkWidget *widget, + GdkEventScroll *event, + GvcChannelBar *bar) +{ + return gvc_channel_bar_scroll (bar, event->direction); +} + +static void +on_zero_adjustment_value_changed (GtkAdjustment *adjustment, + GvcChannelBar *bar) +{ + gdouble value; + + if (bar->priv->click_lock != FALSE) { + return; + } + + value = gtk_adjustment_get_value (bar->priv->zero_adjustment); + gtk_adjustment_set_value (bar->priv->adjustment, value); + + + if (bar->priv->show_mute == FALSE) { + /* this means the adjustment moved away from zero and + * therefore we should unmute and set the volume. */ + gvc_channel_bar_set_is_muted (bar, value > 0.0); + } +} + +static void +update_mute_button (GvcChannelBar *bar) +{ + if (bar->priv->show_mute) { + gtk_widget_show (bar->priv->mute_button); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (bar->priv->mute_button), + bar->priv->is_muted); + } else { + gtk_widget_hide (bar->priv->mute_button); + + if (bar->priv->is_muted) { + /* If we aren't showing the mute button then + * move slider to the zero. But we don't want to + * change the adjustment. */ + g_signal_handlers_block_by_func (bar->priv->zero_adjustment, + on_zero_adjustment_value_changed, + bar); + gtk_adjustment_set_value (bar->priv->zero_adjustment, 0); + g_signal_handlers_unblock_by_func (bar->priv->zero_adjustment, + on_zero_adjustment_value_changed, + bar); + gtk_range_set_adjustment (GTK_RANGE (bar->priv->scale), + bar->priv->zero_adjustment); + } else { + /* no longer muted so restore the original adjustment + * and tell the front-end that the value changed */ + gtk_range_set_adjustment (GTK_RANGE (bar->priv->scale), + bar->priv->adjustment); + gtk_adjustment_value_changed (bar->priv->adjustment); + } + } +} + +void +gvc_channel_bar_set_is_muted (GvcChannelBar *bar, + gboolean is_muted) +{ + g_return_if_fail (GVC_IS_CHANNEL_BAR (bar)); + + if (is_muted != bar->priv->is_muted) { + /* Update our internal state before telling the + * front-end about our changes */ + bar->priv->is_muted = is_muted; + update_mute_button (bar); + g_object_notify (G_OBJECT (bar), "is-muted"); + } +} + +gboolean +gvc_channel_bar_get_is_muted (GvcChannelBar *bar) +{ + g_return_val_if_fail (GVC_IS_CHANNEL_BAR (bar), FALSE); + return bar->priv->is_muted; +} + +void +gvc_channel_bar_set_show_mute (GvcChannelBar *bar, + gboolean show_mute) +{ + g_return_if_fail (GVC_IS_CHANNEL_BAR (bar)); + + if (show_mute != bar->priv->show_mute) { + bar->priv->show_mute = show_mute; + g_object_notify (G_OBJECT (bar), "show-mute"); + update_mute_button (bar); + } +} + +gboolean +gvc_channel_bar_get_show_mute (GvcChannelBar *bar) +{ + g_return_val_if_fail (GVC_IS_CHANNEL_BAR (bar), FALSE); + return bar->priv->show_mute; +} + +void +gvc_channel_bar_set_is_amplified (GvcChannelBar *bar, gboolean amplified) +{ + g_return_if_fail (GVC_IS_CHANNEL_BAR (bar)); + + bar->priv->is_amplified = amplified; + gtk_adjustment_set_upper (bar->priv->adjustment, ADJUSTMENT_MAX); + gtk_adjustment_set_upper (bar->priv->zero_adjustment, ADJUSTMENT_MAX); + gtk_scale_clear_marks (GTK_SCALE (bar->priv->scale)); + + if (amplified) { + char *str; + + if (bar->priv->base_volume == ADJUSTMENT_MAX_NORMAL) { + str = g_strdup_printf ("%s", C_("volume", "100%")); + gtk_scale_add_mark (GTK_SCALE (bar->priv->scale), ADJUSTMENT_MAX_NORMAL, + GTK_POS_BOTTOM, str); + } else { + str = g_strdup_printf ("%s", C_("volume", "Unamplified")); + gtk_scale_add_mark (GTK_SCALE (bar->priv->scale), bar->priv->base_volume, + GTK_POS_BOTTOM, str); + /* Only show 100% if it's higher than the base volume */ + if (bar->priv->base_volume < ADJUSTMENT_MAX_NORMAL) { + str = g_strdup_printf ("%s", C_("volume", "100%")); + gtk_scale_add_mark (GTK_SCALE (bar->priv->scale), ADJUSTMENT_MAX_NORMAL, + GTK_POS_BOTTOM, str); + } + } + + g_free (str); + gtk_alignment_set (GTK_ALIGNMENT (bar->priv->mute_box), 0.5, 0, 0, 0); + gtk_misc_set_alignment (GTK_MISC (bar->priv->low_image), 0.5, 0); + gtk_misc_set_alignment (GTK_MISC (bar->priv->high_image), 0.5, 0); + gtk_misc_set_alignment (GTK_MISC (bar->priv->label), 0, 0); + } else { + gtk_alignment_set (GTK_ALIGNMENT (bar->priv->mute_box), 0.5, 0.5, 0, 0); + gtk_misc_set_alignment (GTK_MISC (bar->priv->low_image), 0.5, 0.5); + gtk_misc_set_alignment (GTK_MISC (bar->priv->high_image), 0.5, 0.5); + gtk_misc_set_alignment (GTK_MISC (bar->priv->label), 0, 0.5); + } +} + +gboolean +gvc_channel_bar_get_ellipsize (GvcChannelBar *bar) +{ + g_return_val_if_fail (GVC_IS_CHANNEL_BAR (bar), FALSE); + + return gtk_label_get_ellipsize (GTK_LABEL (bar->priv->label)) != PANGO_ELLIPSIZE_NONE; +} + +void +gvc_channel_bar_set_ellipsize (GvcChannelBar *bar, + gboolean ellipsized) +{ + g_return_if_fail (GVC_IS_CHANNEL_BAR (bar)); + + if (ellipsized) + gtk_label_set_ellipsize (GTK_LABEL (bar->priv->label), PANGO_ELLIPSIZE_END); + else + gtk_label_set_ellipsize (GTK_LABEL (bar->priv->label), PANGO_ELLIPSIZE_NONE); +} + +void +gvc_channel_bar_set_base_volume (GvcChannelBar *bar, + pa_volume_t base_volume) +{ + g_return_if_fail (GVC_IS_CHANNEL_BAR (bar)); + + if (base_volume == 0) { + bar->priv->base_volume = ADJUSTMENT_MAX_NORMAL; + return; + } + + /* Note that you need to call _is_amplified() afterwards to update the marks */ + bar->priv->base_volume = base_volume; +} + +static void +gvc_channel_bar_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GvcChannelBar *self = GVC_CHANNEL_BAR (object); + + switch (prop_id) { + case PROP_ORIENTATION: + gvc_channel_bar_set_orientation (self, g_value_get_enum (value)); + break; + case PROP_IS_MUTED: + gvc_channel_bar_set_is_muted (self, g_value_get_boolean (value)); + break; + case PROP_SHOW_MUTE: + gvc_channel_bar_set_show_mute (self, g_value_get_boolean (value)); + break; + case PROP_NAME: + gvc_channel_bar_set_name (self, g_value_get_string (value)); + break; + case PROP_ICON_NAME: + gvc_channel_bar_set_icon_name (self, g_value_get_string (value)); + break; + case PROP_LOW_ICON_NAME: + gvc_channel_bar_set_low_icon_name (self, g_value_get_string (value)); + break; + case PROP_HIGH_ICON_NAME: + gvc_channel_bar_set_high_icon_name (self, g_value_get_string (value)); + break; + case PROP_ADJUSTMENT: + gvc_channel_bar_set_adjustment (self, g_value_get_object (value)); + break; + case PROP_IS_AMPLIFIED: + gvc_channel_bar_set_is_amplified (self, g_value_get_boolean (value)); + break; + case PROP_ELLIPSIZE: + gvc_channel_bar_set_ellipsize (self, g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gvc_channel_bar_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GvcChannelBar *self = GVC_CHANNEL_BAR (object); + GvcChannelBarPrivate *priv = self->priv; + + switch (prop_id) { + case PROP_ORIENTATION: + g_value_set_enum (value, priv->orientation); + break; + case PROP_IS_MUTED: + g_value_set_boolean (value, priv->is_muted); + break; + case PROP_SHOW_MUTE: + g_value_set_boolean (value, priv->show_mute); + break; + case PROP_NAME: + g_value_set_string (value, priv->name); + break; + case PROP_ICON_NAME: + g_value_set_string (value, priv->icon_name); + break; + case PROP_LOW_ICON_NAME: + g_value_set_string (value, priv->low_icon_name); + break; + case PROP_HIGH_ICON_NAME: + g_value_set_string (value, priv->high_icon_name); + break; + case PROP_ADJUSTMENT: + g_value_set_object (value, gvc_channel_bar_get_adjustment (self)); + break; + case PROP_IS_AMPLIFIED: + g_value_set_boolean (value, priv->is_amplified); + break; + case PROP_ELLIPSIZE: + g_value_set_boolean (value, gvc_channel_bar_get_ellipsize (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GObject * +gvc_channel_bar_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_params) +{ + GObject *object; + GvcChannelBar *self; + + object = G_OBJECT_CLASS (gvc_channel_bar_parent_class)->constructor (type, n_construct_properties, construct_params); + + self = GVC_CHANNEL_BAR (object); + + update_mute_button (self); + + return object; +} + +static void +gvc_channel_bar_class_init (GvcChannelBarClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructor = gvc_channel_bar_constructor; + object_class->finalize = gvc_channel_bar_finalize; + object_class->set_property = gvc_channel_bar_set_property; + object_class->get_property = gvc_channel_bar_get_property; + + g_object_class_install_property (object_class, + PROP_ORIENTATION, + g_param_spec_enum ("orientation", + "Orientation", + "The orientation of the scale", + GTK_TYPE_ORIENTATION, + GTK_ORIENTATION_VERTICAL, + G_PARAM_READWRITE)); + g_object_class_install_property (object_class, + PROP_IS_MUTED, + g_param_spec_boolean ("is-muted", + "is muted", + "Whether stream is muted", + FALSE, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, + PROP_SHOW_MUTE, + g_param_spec_boolean ("show-mute", + "show mute", + "Whether stream is muted", + FALSE, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, + PROP_ADJUSTMENT, + g_param_spec_object ("adjustment", + "Adjustment", + "The GtkAdjustment that contains the current value of this scale button object", + GTK_TYPE_ADJUSTMENT, + G_PARAM_READWRITE)); + g_object_class_install_property (object_class, + PROP_NAME, + g_param_spec_string ("name", + "Name", + "Name to display for this stream", + NULL, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, + PROP_ICON_NAME, + g_param_spec_string ("icon-name", + "Icon Name", + "Name of icon to display for this stream", + NULL, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, + PROP_LOW_ICON_NAME, + g_param_spec_string ("low-icon-name", + "Icon Name", + "Name of icon to display for this stream", + "audio-volume-low", + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, + PROP_HIGH_ICON_NAME, + g_param_spec_string ("high-icon-name", + "Icon Name", + "Name of icon to display for this stream", + "audio-volume-high", + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, + PROP_IS_AMPLIFIED, + g_param_spec_boolean ("is-amplified", + "Is amplified", + "Whether the stream is digitally amplified", + FALSE, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, + PROP_ELLIPSIZE, + g_param_spec_boolean ("ellipsize", + "Label is ellipsized", + "Whether the label is ellipsized", + FALSE, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + g_type_class_add_private (klass, sizeof (GvcChannelBarPrivate)); +} + +static void +on_mute_button_toggled (GtkToggleButton *button, + GvcChannelBar *bar) +{ + gboolean is_muted; + is_muted = gtk_toggle_button_get_active (button); + gvc_channel_bar_set_is_muted (bar, is_muted); +} + +static void +gvc_channel_bar_init (GvcChannelBar *bar) +{ + GtkWidget *frame; + + bar->priv = GVC_CHANNEL_BAR_GET_PRIVATE (bar); + + bar->priv->base_volume = ADJUSTMENT_MAX_NORMAL; + bar->priv->low_icon_name = g_strdup ("audio-volume-low"); + bar->priv->high_icon_name = g_strdup ("audio-volume-high"); + + bar->priv->orientation = GTK_ORIENTATION_VERTICAL; + bar->priv->adjustment = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, + 0.0, + ADJUSTMENT_MAX_NORMAL, + ADJUSTMENT_MAX_NORMAL/100.0, + ADJUSTMENT_MAX_NORMAL/10.0, + 0.0)); + g_object_ref_sink (bar->priv->adjustment); + + bar->priv->zero_adjustment = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, + 0.0, + ADJUSTMENT_MAX_NORMAL, + ADJUSTMENT_MAX_NORMAL/100.0, + ADJUSTMENT_MAX_NORMAL/10.0, + 0.0)); + g_object_ref_sink (bar->priv->zero_adjustment); + + g_signal_connect (bar->priv->zero_adjustment, + "value-changed", + G_CALLBACK (on_zero_adjustment_value_changed), + bar); + + bar->priv->mute_button = gtk_check_button_new_with_label (_("Mute")); + gtk_widget_set_no_show_all (bar->priv->mute_button, TRUE); + g_signal_connect (bar->priv->mute_button, + "toggled", + G_CALLBACK (on_mute_button_toggled), + bar); + bar->priv->mute_box = gtk_alignment_new (0.5, 0.5, 0, 0); + gtk_container_add (GTK_CONTAINER (bar->priv->mute_box), bar->priv->mute_button); + + bar->priv->low_image = gtk_image_new_from_icon_name ("audio-volume-low", + GTK_ICON_SIZE_BUTTON); + gtk_widget_set_no_show_all (bar->priv->low_image, TRUE); + bar->priv->high_image = gtk_image_new_from_icon_name ("audio-volume-high", + GTK_ICON_SIZE_BUTTON); + gtk_widget_set_no_show_all (bar->priv->high_image, TRUE); + + bar->priv->image = gtk_image_new (); + gtk_widget_set_no_show_all (bar->priv->image, TRUE); + + bar->priv->label = gtk_label_new (NULL); + gtk_misc_set_alignment (GTK_MISC (bar->priv->label), 0.0, 0.5); + gtk_widget_set_no_show_all (bar->priv->label, TRUE); + + /* frame */ + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_NONE); + gtk_container_add (GTK_CONTAINER (bar), frame); + gtk_widget_show_all (frame); + + /* box with scale */ + bar->priv->scale_box = _scale_box_new (bar); + + gtk_container_add (GTK_CONTAINER (frame), bar->priv->scale_box); +} + +static void +gvc_channel_bar_finalize (GObject *object) +{ + GvcChannelBar *channel_bar; + + g_return_if_fail (object != NULL); + g_return_if_fail (GVC_IS_CHANNEL_BAR (object)); + + channel_bar = GVC_CHANNEL_BAR (object); + + g_return_if_fail (channel_bar->priv != NULL); + + g_free (channel_bar->priv->name); + g_free (channel_bar->priv->icon_name); + g_free (channel_bar->priv->low_icon_name); + g_free (channel_bar->priv->high_icon_name); + + G_OBJECT_CLASS (gvc_channel_bar_parent_class)->finalize (object); +} + +GtkWidget * +gvc_channel_bar_new (void) +{ + GObject *bar; + bar = g_object_new (GVC_TYPE_CHANNEL_BAR, + NULL); + return GTK_WIDGET (bar); +} diff --git a/panels/sound/gvc-channel-bar.h b/panels/sound/gvc-channel-bar.h new file mode 100644 index 000000000..6d3402d04 --- /dev/null +++ b/panels/sound/gvc-channel-bar.h @@ -0,0 +1,89 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Red Hat, Inc. + * + * 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. + * + */ + +#ifndef __GVC_CHANNEL_BAR_H +#define __GVC_CHANNEL_BAR_H + +#include + +G_BEGIN_DECLS + +#define GVC_TYPE_CHANNEL_BAR (gvc_channel_bar_get_type ()) +#define GVC_CHANNEL_BAR(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GVC_TYPE_CHANNEL_BAR, GvcChannelBar)) +#define GVC_CHANNEL_BAR_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GVC_TYPE_CHANNEL_BAR, GvcChannelBarClass)) +#define GVC_IS_CHANNEL_BAR(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVC_TYPE_CHANNEL_BAR)) +#define GVC_IS_CHANNEL_BAR_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GVC_TYPE_CHANNEL_BAR)) +#define GVC_CHANNEL_BAR_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVC_TYPE_CHANNEL_BAR, GvcChannelBarClass)) + +typedef struct GvcChannelBarPrivate GvcChannelBarPrivate; + +typedef struct +{ + GtkHBox parent; + GvcChannelBarPrivate *priv; +} GvcChannelBar; + +typedef struct +{ + GtkHBoxClass parent_class; +} GvcChannelBarClass; + +GType gvc_channel_bar_get_type (void); + +GtkWidget * gvc_channel_bar_new (void); + +void gvc_channel_bar_set_name (GvcChannelBar *bar, + const char *name); +void gvc_channel_bar_set_icon_name (GvcChannelBar *bar, + const char *icon_name); +void gvc_channel_bar_set_low_icon_name (GvcChannelBar *bar, + const char *icon_name); +void gvc_channel_bar_set_high_icon_name (GvcChannelBar *bar, + const char *icon_name); + +void gvc_channel_bar_set_orientation (GvcChannelBar *bar, + GtkOrientation orientation); +GtkOrientation gvc_channel_bar_get_orientation (GvcChannelBar *bar); + +GtkAdjustment * gvc_channel_bar_get_adjustment (GvcChannelBar *bar); + +gboolean gvc_channel_bar_get_is_muted (GvcChannelBar *bar); +void gvc_channel_bar_set_is_muted (GvcChannelBar *bar, + gboolean is_muted); +gboolean gvc_channel_bar_get_show_mute (GvcChannelBar *bar); +void gvc_channel_bar_set_show_mute (GvcChannelBar *bar, + gboolean show_mute); +void gvc_channel_bar_set_size_group (GvcChannelBar *bar, + GtkSizeGroup *group, + gboolean symmetric); +void gvc_channel_bar_set_is_amplified (GvcChannelBar *bar, + gboolean amplified); +void gvc_channel_bar_set_base_volume (GvcChannelBar *bar, + guint32 base_volume); +gboolean gvc_channel_bar_get_ellipsize (GvcChannelBar *bar); +void gvc_channel_bar_set_ellipsize (GvcChannelBar *bar, + gboolean ellipsized); + +gboolean gvc_channel_bar_scroll (GvcChannelBar *bar, + GdkScrollDirection direction); + +G_END_DECLS + +#endif /* __GVC_CHANNEL_BAR_H */ diff --git a/panels/sound/gvc-channel-map-private.h b/panels/sound/gvc-channel-map-private.h new file mode 100644 index 000000000..3949de3a6 --- /dev/null +++ b/panels/sound/gvc-channel-map-private.h @@ -0,0 +1,39 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Red Hat, Inc. + * + * 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. + * + */ + +#ifndef __GVC_CHANNEL_MAP_PRIVATE_H +#define __GVC_CHANNEL_MAP_PRIVATE_H + +#include +#include + +G_BEGIN_DECLS + +GvcChannelMap * gvc_channel_map_new_from_pa_channel_map (const pa_channel_map *map); +const pa_channel_map * gvc_channel_map_get_pa_channel_map (const GvcChannelMap *map); + +void gvc_channel_map_volume_changed (GvcChannelMap *map, + const pa_cvolume *cv, + gboolean set); +const pa_cvolume * gvc_channel_map_get_cvolume (const GvcChannelMap *map); + +G_END_DECLS + +#endif /* __GVC_CHANNEL_MAP_PRIVATE_H */ diff --git a/panels/sound/gvc-channel-map.c b/panels/sound/gvc-channel-map.c new file mode 100644 index 000000000..a2073fd33 --- /dev/null +++ b/panels/sound/gvc-channel-map.c @@ -0,0 +1,254 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 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 "gvc-channel-map.h" +#include "gvc-channel-map-private.h" + +#define GVC_CHANNEL_MAP_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_CHANNEL_MAP, GvcChannelMapPrivate)) + +struct GvcChannelMapPrivate +{ + pa_channel_map pa_map; + gboolean pa_volume_is_set; + pa_cvolume pa_volume; + gdouble extern_volume[NUM_TYPES]; /* volume, balance, fade, lfe */ + gboolean can_balance; + gboolean can_fade; +}; + +enum { + VOLUME_CHANGED, + LAST_SIGNAL +}; + +static guint signals [LAST_SIGNAL] = { 0, }; + +static void gvc_channel_map_class_init (GvcChannelMapClass *klass); +static void gvc_channel_map_init (GvcChannelMap *channel_map); +static void gvc_channel_map_finalize (GObject *object); + +G_DEFINE_TYPE (GvcChannelMap, gvc_channel_map, G_TYPE_OBJECT) + +guint +gvc_channel_map_get_num_channels (const GvcChannelMap *map) +{ + g_return_val_if_fail (GVC_IS_CHANNEL_MAP (map), 0); + + if (!pa_channel_map_valid(&map->priv->pa_map)) + return 0; + + return map->priv->pa_map.channels; +} + +const gdouble * +gvc_channel_map_get_volume (GvcChannelMap *map) +{ + g_return_val_if_fail (GVC_IS_CHANNEL_MAP (map), NULL); + + if (!pa_channel_map_valid(&map->priv->pa_map)) + return NULL; + + map->priv->extern_volume[VOLUME] = (gdouble) pa_cvolume_max (&map->priv->pa_volume); + if (gvc_channel_map_can_balance (map)) + map->priv->extern_volume[BALANCE] = (gdouble) pa_cvolume_get_balance (&map->priv->pa_volume, &map->priv->pa_map); + else + map->priv->extern_volume[BALANCE] = 0; + if (gvc_channel_map_can_fade (map)) + map->priv->extern_volume[FADE] = (gdouble) pa_cvolume_get_fade (&map->priv->pa_volume, &map->priv->pa_map); + else + map->priv->extern_volume[FADE] = 0; + if (gvc_channel_map_has_lfe (map)) + map->priv->extern_volume[LFE] = (gdouble) pa_cvolume_get_position (&map->priv->pa_volume, &map->priv->pa_map, PA_CHANNEL_POSITION_LFE); + else + map->priv->extern_volume[LFE] = 0; + + return map->priv->extern_volume; +} + +gboolean +gvc_channel_map_can_balance (const GvcChannelMap *map) +{ + g_return_val_if_fail (GVC_IS_CHANNEL_MAP (map), FALSE); + + return map->priv->can_balance; +} + +gboolean +gvc_channel_map_can_fade (const GvcChannelMap *map) +{ + g_return_val_if_fail (GVC_IS_CHANNEL_MAP (map), FALSE); + + return map->priv->can_fade; +} + +const char * +gvc_channel_map_get_mapping (const GvcChannelMap *map) +{ + g_return_val_if_fail (GVC_IS_CHANNEL_MAP (map), NULL); + + if (!pa_channel_map_valid(&map->priv->pa_map)) + return NULL; + + return pa_channel_map_to_pretty_name (&map->priv->pa_map); +} + +/** + * gvc_channel_map_has_position: (skip) + * + * @map: + * @position: + * + * Returns: + */ +gboolean +gvc_channel_map_has_position (const GvcChannelMap *map, + pa_channel_position_t position) +{ + g_return_val_if_fail (GVC_IS_CHANNEL_MAP (map), FALSE); + + return pa_channel_map_has_position (&(map->priv->pa_map), position); +} + +const pa_channel_map * +gvc_channel_map_get_pa_channel_map (const GvcChannelMap *map) +{ + g_return_val_if_fail (GVC_IS_CHANNEL_MAP (map), NULL); + + if (!pa_channel_map_valid(&map->priv->pa_map)) + return NULL; + + return &map->priv->pa_map; +} + +const pa_cvolume * +gvc_channel_map_get_cvolume (const GvcChannelMap *map) +{ + g_return_val_if_fail (GVC_IS_CHANNEL_MAP (map), NULL); + + if (!pa_channel_map_valid(&map->priv->pa_map)) + return NULL; + + return &map->priv->pa_volume; +} + +static void +gvc_channel_map_class_init (GvcChannelMapClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = gvc_channel_map_finalize; + + signals [VOLUME_CHANGED] = + g_signal_new ("volume-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GvcChannelMapClass, volume_changed), + NULL, NULL, + g_cclosure_marshal_VOID__BOOLEAN, + G_TYPE_NONE, 1, G_TYPE_BOOLEAN); + + g_type_class_add_private (klass, sizeof (GvcChannelMapPrivate)); +} + +void +gvc_channel_map_volume_changed (GvcChannelMap *map, + const pa_cvolume *cv, + gboolean set) +{ + g_return_if_fail (GVC_IS_CHANNEL_MAP (map)); + g_return_if_fail (cv != NULL); + g_return_if_fail (pa_cvolume_compatible_with_channel_map(cv, &map->priv->pa_map)); + + if (pa_cvolume_equal(cv, &map->priv->pa_volume)) + return; + + map->priv->pa_volume = *cv; + + if (map->priv->pa_volume_is_set == FALSE) { + map->priv->pa_volume_is_set = TRUE; + return; + } + g_signal_emit (map, signals[VOLUME_CHANGED], 0, set); +} + +static void +gvc_channel_map_init (GvcChannelMap *map) +{ + map->priv = GVC_CHANNEL_MAP_GET_PRIVATE (map); + map->priv->pa_volume_is_set = FALSE; +} + +static void +gvc_channel_map_finalize (GObject *object) +{ + GvcChannelMap *channel_map; + + g_return_if_fail (object != NULL); + g_return_if_fail (GVC_IS_CHANNEL_MAP (object)); + + channel_map = GVC_CHANNEL_MAP (object); + + g_return_if_fail (channel_map->priv != NULL); + + G_OBJECT_CLASS (gvc_channel_map_parent_class)->finalize (object); +} + +GvcChannelMap * +gvc_channel_map_new (void) +{ + GObject *map; + map = g_object_new (GVC_TYPE_CHANNEL_MAP, NULL); + return GVC_CHANNEL_MAP (map); +} + +static void +set_from_pa_map (GvcChannelMap *map, + const pa_channel_map *pa_map) +{ + g_assert (pa_channel_map_valid(pa_map)); + + map->priv->can_balance = pa_channel_map_can_balance (pa_map); + map->priv->can_fade = pa_channel_map_can_fade (pa_map); + + map->priv->pa_map = *pa_map; + pa_cvolume_set(&map->priv->pa_volume, pa_map->channels, PA_VOLUME_NORM); +} + +GvcChannelMap * +gvc_channel_map_new_from_pa_channel_map (const pa_channel_map *pa_map) +{ + GObject *map; + map = g_object_new (GVC_TYPE_CHANNEL_MAP, NULL); + + set_from_pa_map (GVC_CHANNEL_MAP (map), pa_map); + + return GVC_CHANNEL_MAP (map); +} diff --git a/panels/sound/gvc-channel-map.h b/panels/sound/gvc-channel-map.h new file mode 100644 index 000000000..85c577289 --- /dev/null +++ b/panels/sound/gvc-channel-map.h @@ -0,0 +1,73 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Red Hat, Inc. + * + * 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. + * + */ + +#ifndef __GVC_CHANNEL_MAP_H +#define __GVC_CHANNEL_MAP_H + +#include +#include + +G_BEGIN_DECLS + +#define GVC_TYPE_CHANNEL_MAP (gvc_channel_map_get_type ()) +#define GVC_CHANNEL_MAP(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GVC_TYPE_CHANNEL_MAP, GvcChannelMap)) +#define GVC_CHANNEL_MAP_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GVC_TYPE_CHANNEL_MAP, GvcChannelMapClass)) +#define GVC_IS_CHANNEL_MAP(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVC_TYPE_CHANNEL_MAP)) +#define GVC_IS_CHANNEL_MAP_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GVC_TYPE_CHANNEL_MAP)) +#define GVC_CHANNEL_MAP_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVC_TYPE_CHANNEL_MAP, GvcChannelMapClass)) + +typedef struct GvcChannelMapPrivate GvcChannelMapPrivate; + +typedef struct +{ + GObject parent; + GvcChannelMapPrivate *priv; +} GvcChannelMap; + +typedef struct +{ + GObjectClass parent_class; + void (*volume_changed) (GvcChannelMap *channel_map, gboolean set); +} GvcChannelMapClass; + +enum { + VOLUME, + BALANCE, + FADE, + LFE, + NUM_TYPES +}; + +GType gvc_channel_map_get_type (void); + +GvcChannelMap * gvc_channel_map_new (void); +guint gvc_channel_map_get_num_channels (const GvcChannelMap *map); +const gdouble * gvc_channel_map_get_volume (GvcChannelMap *map); +gboolean gvc_channel_map_can_balance (const GvcChannelMap *map); +gboolean gvc_channel_map_can_fade (const GvcChannelMap *map); +gboolean gvc_channel_map_has_position (const GvcChannelMap *map, + pa_channel_position_t position); +#define gvc_channel_map_has_lfe(x) gvc_channel_map_has_position (x, PA_CHANNEL_POSITION_LFE) + +const char * gvc_channel_map_get_mapping (const GvcChannelMap *map); + +G_END_DECLS + +#endif /* __GVC_CHANNEL_MAP_H */ diff --git a/panels/sound/gvc-combo-box.c b/panels/sound/gvc-combo-box.c new file mode 100644 index 000000000..7e14fb27c --- /dev/null +++ b/panels/sound/gvc-combo-box.c @@ -0,0 +1,395 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2009 Bastien Nocera + * + * 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 + +#include "gvc-combo-box.h" +#include "gvc-mixer-stream.h" +#include "gvc-mixer-card.h" + +#define GVC_COMBO_BOX_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_COMBO_BOX, GvcComboBoxPrivate)) + +struct GvcComboBoxPrivate +{ + GtkWidget *drop_box; + GtkWidget *start_box; + GtkWidget *end_box; + GtkWidget *label; + GtkWidget *button; + GtkTreeModel *model; + GtkWidget *combobox; + gboolean set_called; + GtkSizeGroup *size_group; + gboolean symmetric; +}; + +enum { + COL_NAME, + COL_HUMAN_NAME, + NUM_COLS +}; + +enum { + CHANGED, + BUTTON_CLICKED, + LAST_SIGNAL +}; + +enum { + PROP_0, + PROP_LABEL, + PROP_SHOW_BUTTON, + PROP_BUTTON_LABEL +}; + +static guint signals [LAST_SIGNAL] = { 0, }; + +static void gvc_combo_box_class_init (GvcComboBoxClass *klass); +static void gvc_combo_box_init (GvcComboBox *combo_box); +static void gvc_combo_box_finalize (GObject *object); + +G_DEFINE_TYPE (GvcComboBox, gvc_combo_box, GTK_TYPE_HBOX) + +void +gvc_combo_box_set_size_group (GvcComboBox *combo_box, + GtkSizeGroup *group, + gboolean symmetric) +{ + g_return_if_fail (GVC_IS_COMBO_BOX (combo_box)); + + combo_box->priv->size_group = group; + combo_box->priv->symmetric = symmetric; + + if (combo_box->priv->size_group != NULL) { + gtk_size_group_add_widget (combo_box->priv->size_group, + combo_box->priv->start_box); + + if (combo_box->priv->symmetric) { + gtk_size_group_add_widget (combo_box->priv->size_group, + combo_box->priv->end_box); + } + } + gtk_widget_queue_draw (GTK_WIDGET (combo_box)); +} + +static void +gvc_combo_box_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GvcComboBox *self = GVC_COMBO_BOX (object); + + switch (prop_id) { + case PROP_LABEL: + gtk_label_set_text_with_mnemonic (GTK_LABEL (self->priv->label), g_value_get_string (value)); + break; + case PROP_BUTTON_LABEL: + gtk_button_set_label (GTK_BUTTON (self->priv->button), g_value_get_string (value)); + break; + case PROP_SHOW_BUTTON: + gtk_widget_set_visible (self->priv->button, g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gvc_combo_box_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GvcComboBox *self = GVC_COMBO_BOX (object); + + switch (prop_id) { + case PROP_LABEL: + g_value_set_string (value, + gtk_label_get_text (GTK_LABEL (self->priv->label))); + break; + case PROP_BUTTON_LABEL: + g_value_set_string (value, + gtk_button_get_label (GTK_BUTTON (self->priv->button))); + break; + case PROP_SHOW_BUTTON: + g_value_set_boolean (value, + gtk_widget_get_visible (self->priv->button)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gvc_combo_box_class_init (GvcComboBoxClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gvc_combo_box_finalize; + object_class->set_property = gvc_combo_box_set_property; + object_class->get_property = gvc_combo_box_get_property; + + g_object_class_install_property (object_class, + PROP_LABEL, + g_param_spec_string ("label", + "label", + "The combo box label", + _("_Profile:"), + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, + PROP_SHOW_BUTTON, + g_param_spec_boolean ("show-button", + "show-button", + "Whether to show the button", + FALSE, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, + PROP_BUTTON_LABEL, + g_param_spec_string ("button-label", + "button-label", + "The button's label", + "APPLICATION BUG", + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + signals [CHANGED] = + g_signal_new ("changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GvcComboBoxClass, changed), + NULL, NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, 1, G_TYPE_STRING); + signals [BUTTON_CLICKED] = + g_signal_new ("button-clicked", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GvcComboBoxClass, button_clicked), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0, G_TYPE_NONE); + + g_type_class_add_private (klass, sizeof (GvcComboBoxPrivate)); +} + +void +gvc_combo_box_set_profiles (GvcComboBox *combo_box, + const GList *profiles) +{ + const GList *l; + + g_return_if_fail (GVC_IS_COMBO_BOX (combo_box)); + g_return_if_fail (combo_box->priv->set_called == FALSE); + + for (l = profiles; l != NULL; l = l->next) { + GvcMixerCardProfile *p = l->data; + + gtk_list_store_insert_with_values (GTK_LIST_STORE (combo_box->priv->model), + NULL, + G_MAXINT, + COL_NAME, p->profile, + COL_HUMAN_NAME, p->human_profile, + -1); + } + combo_box->priv->set_called = TRUE; +} + +void +gvc_combo_box_set_ports (GvcComboBox *combo_box, + const GList *ports) +{ + const GList *l; + + g_return_if_fail (GVC_IS_COMBO_BOX (combo_box)); + g_return_if_fail (combo_box->priv->set_called == FALSE); + + for (l = ports; l != NULL; l = l->next) { + GvcMixerStreamPort *p = l->data; + + gtk_list_store_insert_with_values (GTK_LIST_STORE (combo_box->priv->model), + NULL, + G_MAXINT, + COL_NAME, p->port, + COL_HUMAN_NAME, p->human_port, + -1); + } + combo_box->priv->set_called = TRUE; +} + +void +gvc_combo_box_set_active (GvcComboBox *combo_box, + const char *id) +{ + GtkTreeIter iter; + gboolean cont; + + cont = gtk_tree_model_get_iter_first (combo_box->priv->model, &iter); + while (cont != FALSE) { + char *name; + + gtk_tree_model_get (combo_box->priv->model, &iter, + COL_NAME, &name, + -1); + if (g_strcmp0 (name, id) == 0) { + gtk_combo_box_set_active_iter (GTK_COMBO_BOX (combo_box->priv->combobox), &iter); + return; + } + gtk_tree_model_iter_next (combo_box->priv->model, &iter); + } + g_warning ("Could not find id '%s' in combo box", id); +} + +static void +on_combo_box_changed (GtkComboBox *widget, + GvcComboBox *combo_box) +{ + GtkTreeIter iter; + char *profile; + + if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (widget), &iter) == FALSE) { + g_warning ("Could not find an active profile or port"); + return; + } + + gtk_tree_model_get (combo_box->priv->model, &iter, + COL_NAME, &profile, + -1); + g_signal_emit (combo_box, signals[CHANGED], 0, profile); + g_free (profile); +} + +static void +on_combo_box_button_clicked (GtkButton *button, + GvcComboBox *combo_box) +{ + g_signal_emit (combo_box, signals[BUTTON_CLICKED], 0); +} + +static void +gvc_combo_box_init (GvcComboBox *combo_box) +{ + GtkWidget *frame; + GtkWidget *box; + GtkWidget *sbox; + GtkWidget *ebox; + GtkCellRenderer *renderer; + + + combo_box->priv = GVC_COMBO_BOX_GET_PRIVATE (combo_box); + + combo_box->priv->model = GTK_TREE_MODEL (gtk_list_store_new (NUM_COLS, + G_TYPE_STRING, + G_TYPE_STRING)); + + combo_box->priv->label = gtk_label_new (NULL); + gtk_misc_set_alignment (GTK_MISC (combo_box->priv->label), + 0.0, + 0.5); + + /* frame */ + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_NONE); + gtk_container_add (GTK_CONTAINER (combo_box), frame); + + combo_box->priv->drop_box = box = gtk_hbox_new (FALSE, 6); + combo_box->priv->combobox = gtk_combo_box_new_with_model (combo_box->priv->model); + renderer = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo_box->priv->combobox), + renderer, FALSE); + gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (combo_box->priv->combobox), + renderer, + "text", COL_HUMAN_NAME); + +/* gtk_widget_set_size_request (combo_box->priv->combobox, 128, -1); */ + + combo_box->priv->start_box = sbox = gtk_hbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX (box), sbox, FALSE, FALSE, 0); + + gtk_box_pack_start (GTK_BOX (sbox), combo_box->priv->label, FALSE, FALSE, 0); + + gtk_box_pack_start (GTK_BOX (box), combo_box->priv->combobox, TRUE, TRUE, 0); + + combo_box->priv->button = gtk_button_new_with_label ("APPLICATION BUG"); + gtk_widget_set_no_show_all (combo_box->priv->button, TRUE); + gtk_box_pack_start (GTK_BOX (box), combo_box->priv->button, FALSE, FALSE, 0); + + + combo_box->priv->end_box = ebox = gtk_hbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX (box), ebox, FALSE, FALSE, 0); + + if (combo_box->priv->size_group != NULL) { + gtk_size_group_add_widget (combo_box->priv->size_group, sbox); + + if (combo_box->priv->symmetric) { + gtk_size_group_add_widget (combo_box->priv->size_group, ebox); + } + } + + gtk_container_add (GTK_CONTAINER (frame), combo_box->priv->drop_box); + gtk_widget_show_all (frame); + + gtk_label_set_mnemonic_widget (GTK_LABEL (combo_box->priv->label), + combo_box->priv->combobox); + + g_signal_connect (G_OBJECT (combo_box->priv->combobox), "changed", + G_CALLBACK (on_combo_box_changed), combo_box); + g_signal_connect (G_OBJECT (combo_box->priv->button), "clicked", + G_CALLBACK (on_combo_box_button_clicked), combo_box); +} + +static void +gvc_combo_box_finalize (GObject *object) +{ + GvcComboBox *combo_box; + + g_return_if_fail (object != NULL); + g_return_if_fail (GVC_IS_COMBO_BOX (object)); + + combo_box = GVC_COMBO_BOX (object); + + g_return_if_fail (combo_box->priv != NULL); + + g_object_unref (combo_box->priv->model); + combo_box->priv->model = NULL; + + G_OBJECT_CLASS (gvc_combo_box_parent_class)->finalize (object); +} + +GtkWidget * +gvc_combo_box_new (const char *label) +{ + GObject *combo_box; + combo_box = g_object_new (GVC_TYPE_COMBO_BOX, + "label", label, + NULL); + return GTK_WIDGET (combo_box); +} + diff --git a/panels/sound/gvc-combo-box.h b/panels/sound/gvc-combo-box.h new file mode 100644 index 000000000..f1ffc0dc0 --- /dev/null +++ b/panels/sound/gvc-combo-box.h @@ -0,0 +1,67 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2009 Red Hat, Inc. + * + * 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. + * + */ + +#ifndef __GVC_COMBO_BOX_H +#define __GVC_COMBO_BOX_H + +#include + +G_BEGIN_DECLS + +#define GVC_TYPE_COMBO_BOX (gvc_combo_box_get_type ()) +#define GVC_COMBO_BOX(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GVC_TYPE_COMBO_BOX, GvcComboBox)) +#define GVC_COMBO_BOX_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GVC_TYPE_COMBO_BOX, GvcComboBoxClass)) +#define GVC_IS_COMBO_BOX(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVC_TYPE_COMBO_BOX)) +#define GVC_IS_COMBO_BOX_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GVC_TYPE_COMBO_BOX)) +#define GVC_COMBO_BOX_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVC_TYPE_COMBO_BOX, GvcComboBoxClass)) + +typedef struct GvcComboBoxPrivate GvcComboBoxPrivate; + +typedef struct +{ + GtkHBox parent; + GvcComboBoxPrivate *priv; +} GvcComboBox; + +typedef struct +{ + GtkHBoxClass parent_class; + void (* changed) (GvcComboBox *combobox, const char *name); + void (* button_clicked) (GvcComboBox *combobox); +} GvcComboBoxClass; + +GType gvc_combo_box_get_type (void); + +GtkWidget * gvc_combo_box_new (const char *label); + +void gvc_combo_box_set_size_group (GvcComboBox *combo_box, + GtkSizeGroup *group, + gboolean symmetric); + +void gvc_combo_box_set_profiles (GvcComboBox *combo_box, + const GList *profiles); +void gvc_combo_box_set_ports (GvcComboBox *combo_box, + const GList *ports); +void gvc_combo_box_set_active (GvcComboBox *combo_box, + const char *id); + +G_END_DECLS + +#endif /* __GVC_COMBO_BOX_H */ diff --git a/panels/sound/gvc-level-bar.c b/panels/sound/gvc-level-bar.c new file mode 100644 index 000000000..2d3d9da22 --- /dev/null +++ b/panels/sound/gvc-level-bar.c @@ -0,0 +1,747 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 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 "gvc-level-bar.h" + +#define NUM_BOXES 30 + +#define GVC_LEVEL_BAR_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_LEVEL_BAR, GvcLevelBarPrivate)) + +#define MIN_HORIZONTAL_BAR_WIDTH 150 +#define HORIZONTAL_BAR_HEIGHT 6 +#define VERTICAL_BAR_WIDTH 6 +#define MIN_VERTICAL_BAR_HEIGHT 400 + +typedef struct { + int peak_num; + int max_peak_num; + + GdkRectangle area; + int delta; + int box_width; + int box_height; + int box_radius; + double bg_r; + double bg_g; + double bg_b; + double bdr_r; + double bdr_g; + double bdr_b; + double fl_r; + double fl_g; + double fl_b; +} LevelBarLayout; + +struct GvcLevelBarPrivate +{ + GtkOrientation orientation; + GtkAdjustment *peak_adjustment; + GtkAdjustment *rms_adjustment; + int scale; + gdouble peak_fraction; + gdouble rms_fraction; + gdouble max_peak; + guint max_peak_id; + LevelBarLayout layout; +}; + +enum +{ + PROP_0, + PROP_PEAK_ADJUSTMENT, + PROP_RMS_ADJUSTMENT, + PROP_SCALE, + PROP_ORIENTATION, +}; + +static void gvc_level_bar_class_init (GvcLevelBarClass *klass); +static void gvc_level_bar_init (GvcLevelBar *level_bar); +static void gvc_level_bar_finalize (GObject *object); + +G_DEFINE_TYPE (GvcLevelBar, gvc_level_bar, GTK_TYPE_HBOX) + +#define check_rectangle(rectangle1, rectangle2) \ + { \ + if (rectangle1.x != rectangle2.x) return TRUE; \ + if (rectangle1.y != rectangle2.y) return TRUE; \ + if (rectangle1.width != rectangle2.width) return TRUE; \ + if (rectangle1.height != rectangle2.height) return TRUE; \ + } + +static gboolean +layout_changed (LevelBarLayout *layout1, + LevelBarLayout *layout2) +{ + check_rectangle (layout1->area, layout2->area); + if (layout1->delta != layout2->delta) return TRUE; + if (layout1->peak_num != layout2->peak_num) return TRUE; + if (layout1->max_peak_num != layout2->max_peak_num) return TRUE; + if (layout1->bg_r != layout2->bg_r + || layout1->bg_g != layout2->bg_g + || layout1->bg_b != layout2->bg_b) + return TRUE; + if (layout1->bdr_r != layout2->bdr_r + || layout1->bdr_g != layout2->bdr_g + || layout1->bdr_b != layout2->bdr_b) + return TRUE; + if (layout1->fl_r != layout2->fl_r + || layout1->fl_g != layout2->fl_g + || layout1->fl_b != layout2->fl_b) + return TRUE; + + return FALSE; +} + +static gdouble +fraction_from_adjustment (GvcLevelBar *bar, + GtkAdjustment *adjustment) +{ + gdouble level; + gdouble fraction; + gdouble min; + gdouble max; + + level = gtk_adjustment_get_value (adjustment); + + min = gtk_adjustment_get_lower (adjustment); + max = gtk_adjustment_get_upper (adjustment); + + switch (bar->priv->scale) { + case GVC_LEVEL_SCALE_LINEAR: + fraction = (level - min) / (max - min); + break; + case GVC_LEVEL_SCALE_LOG: + fraction = log10 ((level - min + 1) / (max - min + 1)); + break; + default: + g_assert_not_reached (); + } + + return fraction; +} + +static gboolean +reset_max_peak (GvcLevelBar *bar) +{ + gdouble min; + + min = gtk_adjustment_get_lower (bar->priv->peak_adjustment); + bar->priv->max_peak = min; + bar->priv->layout.max_peak_num = 0; + gtk_widget_queue_draw (GTK_WIDGET (bar)); + bar->priv->max_peak_id = 0; + return FALSE; +} + +static void +bar_calc_layout (GvcLevelBar *bar) +{ + GdkColor color; + int peak_level; + int max_peak_level; + GtkAllocation allocation; + GtkStyle *style; + + gtk_widget_get_allocation (GTK_WIDGET (bar), &allocation); + bar->priv->layout.area.width = allocation.width - 2; + bar->priv->layout.area.height = allocation.height - 2; + + style = gtk_widget_get_style (GTK_WIDGET (bar)); + color = style->bg [GTK_STATE_NORMAL]; + bar->priv->layout.bg_r = (float)color.red / 65535.0; + bar->priv->layout.bg_g = (float)color.green / 65535.0; + bar->priv->layout.bg_b = (float)color.blue / 65535.0; + color = style->dark [GTK_STATE_NORMAL]; + bar->priv->layout.bdr_r = (float)color.red / 65535.0; + bar->priv->layout.bdr_g = (float)color.green / 65535.0; + bar->priv->layout.bdr_b = (float)color.blue / 65535.0; + color = style->bg [GTK_STATE_SELECTED]; + bar->priv->layout.fl_r = (float)color.red / 65535.0; + bar->priv->layout.fl_g = (float)color.green / 65535.0; + bar->priv->layout.fl_b = (float)color.blue / 65535.0; + + if (bar->priv->orientation == GTK_ORIENTATION_VERTICAL) { + peak_level = bar->priv->peak_fraction * bar->priv->layout.area.height; + max_peak_level = bar->priv->max_peak * bar->priv->layout.area.height; + + bar->priv->layout.delta = bar->priv->layout.area.height / NUM_BOXES; + bar->priv->layout.area.x = 0; + bar->priv->layout.area.y = 0; + bar->priv->layout.box_height = bar->priv->layout.delta / 2; + bar->priv->layout.box_width = bar->priv->layout.area.width; + bar->priv->layout.box_radius = bar->priv->layout.box_width / 2; + } else { + peak_level = bar->priv->peak_fraction * bar->priv->layout.area.width; + max_peak_level = bar->priv->max_peak * bar->priv->layout.area.width; + + bar->priv->layout.delta = bar->priv->layout.area.width / NUM_BOXES; + bar->priv->layout.area.x = 0; + bar->priv->layout.area.y = 0; + bar->priv->layout.box_width = bar->priv->layout.delta / 2; + bar->priv->layout.box_height = bar->priv->layout.area.height; + bar->priv->layout.box_radius = bar->priv->layout.box_height / 2; + } + + /* This can happen if the level bar isn't realized */ + if (bar->priv->layout.delta == 0) + return; + + bar->priv->layout.peak_num = peak_level / bar->priv->layout.delta; + bar->priv->layout.max_peak_num = max_peak_level / bar->priv->layout.delta; +} + +static void +update_peak_value (GvcLevelBar *bar) +{ + gdouble val; + LevelBarLayout layout; + + layout = bar->priv->layout; + + val = fraction_from_adjustment (bar, bar->priv->peak_adjustment); + bar->priv->peak_fraction = val; + + if (val > bar->priv->max_peak) { + if (bar->priv->max_peak_id > 0) { + g_source_remove (bar->priv->max_peak_id); + } + bar->priv->max_peak_id = g_timeout_add_seconds (1, (GSourceFunc)reset_max_peak, bar); + bar->priv->max_peak = val; + } + + bar_calc_layout (bar); + + if (layout_changed (&bar->priv->layout, &layout)) { + gtk_widget_queue_draw (GTK_WIDGET (bar)); + } +} + +static void +update_rms_value (GvcLevelBar *bar) +{ + gdouble val; + + val = fraction_from_adjustment (bar, bar->priv->rms_adjustment); + bar->priv->rms_fraction = val; +} + +GtkOrientation +gvc_level_bar_get_orientation (GvcLevelBar *bar) +{ + g_return_val_if_fail (GVC_IS_LEVEL_BAR (bar), 0); + return bar->priv->orientation; +} + +void +gvc_level_bar_set_orientation (GvcLevelBar *bar, + GtkOrientation orientation) +{ + g_return_if_fail (GVC_IS_LEVEL_BAR (bar)); + + if (orientation != bar->priv->orientation) { + bar->priv->orientation = orientation; + gtk_widget_queue_draw (GTK_WIDGET (bar)); + g_object_notify (G_OBJECT (bar), "orientation"); + } +} + +static void +on_peak_adjustment_value_changed (GtkAdjustment *adjustment, + GvcLevelBar *bar) +{ + update_peak_value (bar); +} + +static void +on_rms_adjustment_value_changed (GtkAdjustment *adjustment, + GvcLevelBar *bar) +{ + update_rms_value (bar); +} + +void +gvc_level_bar_set_peak_adjustment (GvcLevelBar *bar, + GtkAdjustment *adjustment) +{ + g_return_if_fail (GVC_LEVEL_BAR (bar)); + g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment)); + + if (bar->priv->peak_adjustment != NULL) { + g_signal_handlers_disconnect_by_func (bar->priv->peak_adjustment, + G_CALLBACK (on_peak_adjustment_value_changed), + bar); + g_object_unref (bar->priv->peak_adjustment); + } + + bar->priv->peak_adjustment = g_object_ref_sink (adjustment); + + g_signal_connect (bar->priv->peak_adjustment, + "value-changed", + G_CALLBACK (on_peak_adjustment_value_changed), + bar); + + update_peak_value (bar); + + g_object_notify (G_OBJECT (bar), "peak-adjustment"); +} + +void +gvc_level_bar_set_rms_adjustment (GvcLevelBar *bar, + GtkAdjustment *adjustment) +{ + g_return_if_fail (GVC_LEVEL_BAR (bar)); + g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment)); + + if (bar->priv->rms_adjustment != NULL) { + g_signal_handlers_disconnect_by_func (bar->priv->peak_adjustment, + G_CALLBACK (on_rms_adjustment_value_changed), + bar); + g_object_unref (bar->priv->rms_adjustment); + } + + bar->priv->rms_adjustment = g_object_ref_sink (adjustment); + + + g_signal_connect (bar->priv->peak_adjustment, + "value-changed", + G_CALLBACK (on_peak_adjustment_value_changed), + bar); + + update_rms_value (bar); + + g_object_notify (G_OBJECT (bar), "rms-adjustment"); +} + +GtkAdjustment * +gvc_level_bar_get_peak_adjustment (GvcLevelBar *bar) +{ + g_return_val_if_fail (GVC_IS_LEVEL_BAR (bar), NULL); + + return bar->priv->peak_adjustment; +} + +GtkAdjustment * +gvc_level_bar_get_rms_adjustment (GvcLevelBar *bar) +{ + g_return_val_if_fail (GVC_IS_LEVEL_BAR (bar), NULL); + + return bar->priv->rms_adjustment; +} + +void +gvc_level_bar_set_scale (GvcLevelBar *bar, + GvcLevelScale scale) +{ + g_return_if_fail (GVC_IS_LEVEL_BAR (bar)); + + if (scale != bar->priv->scale) { + bar->priv->scale = scale; + + update_peak_value (bar); + update_rms_value (bar); + + g_object_notify (G_OBJECT (bar), "scale"); + } +} + +static void +gvc_level_bar_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GvcLevelBar *self = GVC_LEVEL_BAR (object); + + switch (prop_id) { + case PROP_SCALE: + gvc_level_bar_set_scale (self, g_value_get_int (value)); + break; + case PROP_ORIENTATION: + gvc_level_bar_set_orientation (self, g_value_get_enum (value)); + break; + case PROP_PEAK_ADJUSTMENT: + gvc_level_bar_set_peak_adjustment (self, g_value_get_object (value)); + break; + case PROP_RMS_ADJUSTMENT: + gvc_level_bar_set_rms_adjustment (self, g_value_get_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gvc_level_bar_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GvcLevelBar *self = GVC_LEVEL_BAR (object); + + switch (prop_id) { + case PROP_SCALE: + g_value_set_int (value, self->priv->scale); + break; + case PROP_ORIENTATION: + g_value_set_enum (value, self->priv->orientation); + break; + case PROP_PEAK_ADJUSTMENT: + g_value_set_object (value, self->priv->peak_adjustment); + break; + case PROP_RMS_ADJUSTMENT: + g_value_set_object (value, self->priv->rms_adjustment); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GObject * +gvc_level_bar_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_params) +{ + return G_OBJECT_CLASS (gvc_level_bar_parent_class)->constructor (type, n_construct_properties, construct_params); +} + +static void +gvc_level_bar_size_request (GtkWidget *widget, + GtkRequisition *requisition) +{ + GvcLevelBar *bar; + + g_return_if_fail (GVC_IS_LEVEL_BAR (widget)); + g_return_if_fail (requisition != NULL); + + bar = GVC_LEVEL_BAR (widget); + + switch (bar->priv->orientation) { + case GTK_ORIENTATION_VERTICAL: + requisition->width = VERTICAL_BAR_WIDTH; + requisition->height = MIN_VERTICAL_BAR_HEIGHT; + break; + case GTK_ORIENTATION_HORIZONTAL: + requisition->width = MIN_HORIZONTAL_BAR_WIDTH; + requisition->height = HORIZONTAL_BAR_HEIGHT; + break; + default: + g_assert_not_reached (); + break; + } +} + +static void +gvc_level_bar_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GvcLevelBar *bar; + + g_return_if_fail (GVC_IS_LEVEL_BAR (widget)); + g_return_if_fail (allocation != NULL); + + bar = GVC_LEVEL_BAR (widget); + + /* FIXME: add height property, labels, etc */ + GTK_WIDGET_CLASS (gvc_level_bar_parent_class)->size_allocate (widget, allocation); + + gtk_widget_set_allocation (widget, allocation); + gtk_widget_get_allocation (widget, allocation); + + if (bar->priv->orientation == GTK_ORIENTATION_VERTICAL) { + allocation->height = MIN (allocation->height, MIN_VERTICAL_BAR_HEIGHT); + allocation->width = MAX (allocation->width, VERTICAL_BAR_WIDTH); + } else { + allocation->width = MIN (allocation->width, MIN_HORIZONTAL_BAR_WIDTH); + allocation->height = MAX (allocation->height, HORIZONTAL_BAR_HEIGHT); + } + + bar_calc_layout (bar); +} + +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 int +gvc_level_bar_draw (GtkWidget *widget, + cairo_t *cr) +{ + GvcLevelBar *bar; + GtkAllocation allocation; + + g_return_val_if_fail (GVC_IS_LEVEL_BAR (widget), FALSE); + + bar = GVC_LEVEL_BAR (widget); + + gtk_widget_get_allocation (widget, &allocation); + cairo_translate (cr, + allocation.x, + allocation.y); + + if (bar->priv->orientation == GTK_ORIENTATION_VERTICAL) { + int i; + int by; + + for (i = 0; i < NUM_BOXES; i++) { + by = i * bar->priv->layout.delta; + curved_rectangle (cr, + bar->priv->layout.area.x + 0.5, + by + 0.5, + bar->priv->layout.box_width - 1, + bar->priv->layout.box_height - 1, + bar->priv->layout.box_radius); + if ((bar->priv->layout.max_peak_num - 1) == i) { + /* fill peak foreground */ + cairo_set_source_rgb (cr, bar->priv->layout.fl_r, bar->priv->layout.fl_g, bar->priv->layout.fl_b); + cairo_fill_preserve (cr); + } else if ((bar->priv->layout.peak_num - 1) >= i) { + /* fill background */ + cairo_set_source_rgb (cr, bar->priv->layout.bg_r, bar->priv->layout.bg_g, bar->priv->layout.bg_b); + cairo_fill_preserve (cr); + /* fill foreground */ + cairo_set_source_rgba (cr, bar->priv->layout.fl_r, bar->priv->layout.fl_g, bar->priv->layout.fl_b, 0.5); + cairo_fill_preserve (cr); + } else { + /* fill background */ + cairo_set_source_rgb (cr, bar->priv->layout.bg_r, bar->priv->layout.bg_g, bar->priv->layout.bg_b); + cairo_fill_preserve (cr); + } + + /* stroke border */ + cairo_set_source_rgb (cr, bar->priv->layout.bdr_r, bar->priv->layout.bdr_g, bar->priv->layout.bdr_b); + cairo_set_line_width (cr, 1); + cairo_stroke (cr); + } + + } else { + int i; + int bx; + + for (i = 0; i < NUM_BOXES; i++) { + bx = i * bar->priv->layout.delta; + curved_rectangle (cr, + bx + 0.5, + bar->priv->layout.area.y + 0.5, + bar->priv->layout.box_width - 1, + bar->priv->layout.box_height - 1, + bar->priv->layout.box_radius); + + if ((bar->priv->layout.max_peak_num - 1) == i) { + /* fill peak foreground */ + cairo_set_source_rgb (cr, bar->priv->layout.fl_r, bar->priv->layout.fl_g, bar->priv->layout.fl_b); + cairo_fill_preserve (cr); + } else if ((bar->priv->layout.peak_num - 1) >= i) { + /* fill background */ + cairo_set_source_rgb (cr, bar->priv->layout.bg_r, bar->priv->layout.bg_g, bar->priv->layout.bg_b); + cairo_fill_preserve (cr); + /* fill foreground */ + cairo_set_source_rgba (cr, bar->priv->layout.fl_r, bar->priv->layout.fl_g, bar->priv->layout.fl_b, 0.5); + cairo_fill_preserve (cr); + } else { + /* fill background */ + cairo_set_source_rgb (cr, bar->priv->layout.bg_r, bar->priv->layout.bg_g, bar->priv->layout.bg_b); + cairo_fill_preserve (cr); + } + + /* stroke border */ + cairo_set_source_rgb (cr, bar->priv->layout.bdr_r, bar->priv->layout.bdr_g, bar->priv->layout.bdr_b); + cairo_set_line_width (cr, 1); + cairo_stroke (cr); + } + } + + return FALSE; +} + +static void +gvc_level_bar_class_init (GvcLevelBarClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->constructor = gvc_level_bar_constructor; + object_class->finalize = gvc_level_bar_finalize; + object_class->set_property = gvc_level_bar_set_property; + object_class->get_property = gvc_level_bar_get_property; + + widget_class->draw = gvc_level_bar_draw; + widget_class->size_request = gvc_level_bar_size_request; + widget_class->size_allocate = gvc_level_bar_size_allocate; + + g_object_class_install_property (object_class, + PROP_ORIENTATION, + g_param_spec_enum ("orientation", + "Orientation", + "The orientation of the bar", + GTK_TYPE_ORIENTATION, + GTK_ORIENTATION_HORIZONTAL, + G_PARAM_READWRITE)); + g_object_class_install_property (object_class, + PROP_PEAK_ADJUSTMENT, + g_param_spec_object ("peak-adjustment", + "Peak Adjustment", + "The GtkAdjustment that contains the current peak value", + GTK_TYPE_ADJUSTMENT, + G_PARAM_READWRITE)); + g_object_class_install_property (object_class, + PROP_RMS_ADJUSTMENT, + g_param_spec_object ("rms-adjustment", + "RMS Adjustment", + "The GtkAdjustment that contains the current rms value", + GTK_TYPE_ADJUSTMENT, + G_PARAM_READWRITE)); + g_object_class_install_property (object_class, + PROP_SCALE, + g_param_spec_int ("scale", + "Scale", + "Scale", + 0, + G_MAXINT, + GVC_LEVEL_SCALE_LINEAR, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + + g_type_class_add_private (klass, sizeof (GvcLevelBarPrivate)); +} + +static void +gvc_level_bar_init (GvcLevelBar *bar) +{ + bar->priv = GVC_LEVEL_BAR_GET_PRIVATE (bar); + + bar->priv->peak_adjustment = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, + 0.0, + 1.0, + 0.05, + 0.1, + 0.1)); + g_object_ref_sink (bar->priv->peak_adjustment); + g_signal_connect (bar->priv->peak_adjustment, + "value-changed", + G_CALLBACK (on_peak_adjustment_value_changed), + bar); + + bar->priv->rms_adjustment = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, + 0.0, + 1.0, + 0.05, + 0.1, + 0.1)); + g_object_ref_sink (bar->priv->rms_adjustment); + g_signal_connect (bar->priv->rms_adjustment, + "value-changed", + G_CALLBACK (on_rms_adjustment_value_changed), + bar); + + gtk_widget_set_has_window (GTK_WIDGET (bar), FALSE); +} + +static void +gvc_level_bar_finalize (GObject *object) +{ + GvcLevelBar *bar; + + g_return_if_fail (object != NULL); + g_return_if_fail (GVC_IS_LEVEL_BAR (object)); + + bar = GVC_LEVEL_BAR (object); + + if (bar->priv->max_peak_id > 0) { + g_source_remove (bar->priv->max_peak_id); + } + + g_return_if_fail (bar->priv != NULL); + + G_OBJECT_CLASS (gvc_level_bar_parent_class)->finalize (object); +} + +GtkWidget * +gvc_level_bar_new (void) +{ + GObject *bar; + bar = g_object_new (GVC_TYPE_LEVEL_BAR, + NULL); + return GTK_WIDGET (bar); +} diff --git a/panels/sound/gvc-level-bar.h b/panels/sound/gvc-level-bar.h new file mode 100644 index 000000000..917b415bc --- /dev/null +++ b/panels/sound/gvc-level-bar.h @@ -0,0 +1,75 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 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. + * + */ + +#ifndef __GVC_LEVEL_BAR_H +#define __GVC_LEVEL_BAR_H + +#include +#include + +G_BEGIN_DECLS + +#define GVC_TYPE_LEVEL_BAR (gvc_level_bar_get_type ()) +#define GVC_LEVEL_BAR(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GVC_TYPE_LEVEL_BAR, GvcLevelBar)) +#define GVC_LEVEL_BAR_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GVC_TYPE_LEVEL_BAR, GvcLevelBarClass)) +#define GVC_IS_LEVEL_BAR(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVC_TYPE_LEVEL_BAR)) +#define GVC_IS_LEVEL_BAR_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GVC_TYPE_LEVEL_BAR)) +#define GVC_LEVEL_BAR_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVC_TYPE_LEVEL_BAR, GvcLevelBarClass)) + +typedef struct GvcLevelBarPrivate GvcLevelBarPrivate; + +typedef struct +{ + GtkHBox parent; + GvcLevelBarPrivate *priv; +} GvcLevelBar; + +typedef struct +{ + GtkHBoxClass parent_class; +} GvcLevelBarClass; + +typedef enum +{ + GVC_LEVEL_SCALE_LINEAR, + GVC_LEVEL_SCALE_LOG, + GVC_LEVEL_SCALE_LAST +} GvcLevelScale; + +GType gvc_level_bar_get_type (void); + +GtkWidget * gvc_level_bar_new (void); +void gvc_level_bar_set_orientation (GvcLevelBar *bar, + GtkOrientation orientation); +GtkOrientation gvc_level_bar_get_orientation (GvcLevelBar *bar); + +void gvc_level_bar_set_peak_adjustment (GvcLevelBar *bar, + GtkAdjustment *adjustment); +GtkAdjustment * gvc_level_bar_get_peak_adjustment (GvcLevelBar *bar); +void gvc_level_bar_set_rms_adjustment (GvcLevelBar *bar, + GtkAdjustment *adjustment); +GtkAdjustment * gvc_level_bar_get_rms_adjustment (GvcLevelBar *bar); +void gvc_level_bar_set_scale (GvcLevelBar *bar, + GvcLevelScale scale); + + +G_END_DECLS + +#endif /* __GVC_LEVEL_BAR_H */ diff --git a/panels/sound/gvc-log.c b/panels/sound/gvc-log.c new file mode 100644 index 000000000..03a9486fb --- /dev/null +++ b/panels/sound/gvc-log.c @@ -0,0 +1,62 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2009 Red Hat, Inc. + * + * 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 "gvc-log.h" + + +static int log_levels = G_LOG_LEVEL_CRITICAL | + G_LOG_LEVEL_ERROR | + G_LOG_LEVEL_WARNING | + G_LOG_LEVEL_DEBUG; + +static void +gvc_log_default_handler (const gchar *log_domain, + GLogLevelFlags log_level, + const gchar *message, + gpointer unused_data) +{ + if ((log_level & log_levels) == 0) + return; + + g_log_default_handler (log_domain, log_level, message, unused_data); +} + +void +gvc_log_init (void) +{ + g_log_set_default_handler (gvc_log_default_handler, NULL); +} + +void +gvc_log_set_debug (gboolean debug) +{ + if (debug) { + log_levels |= G_LOG_LEVEL_DEBUG; + g_debug ("Enabling debugging"); + } else { + log_levels &= ~G_LOG_LEVEL_DEBUG; + } +} diff --git a/panels/sound/gvc-log.h b/panels/sound/gvc-log.h new file mode 100644 index 000000000..bc1cdd5ac --- /dev/null +++ b/panels/sound/gvc-log.h @@ -0,0 +1,35 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2009 Red Hat, Inc. + * + * 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. + * + */ + +#ifndef __GVC_LOG_H +#define __GVC_LOG_H + +#include + +G_BEGIN_DECLS + + +void gvc_log_init (void); +void gvc_log_set_debug (gboolean debug); + + +G_END_DECLS + +#endif /* __GVC_LOG_H */ diff --git a/panels/sound/gvc-mixer-card-private.h b/panels/sound/gvc-mixer-card-private.h new file mode 100644 index 000000000..e190f7f4b --- /dev/null +++ b/panels/sound/gvc-mixer-card-private.h @@ -0,0 +1,35 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008-2009 Red Hat, Inc. + * + * 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. + * + */ + +#ifndef __GVC_MIXER_CARD_PRIVATE_H +#define __GVC_MIXER_CARD_PRIVATE_H + +#include +#include "gvc-mixer-card.h" + +G_BEGIN_DECLS + +GvcMixerCard * gvc_mixer_card_new (pa_context *context, + guint index); +pa_context * gvc_mixer_card_get_pa_context (GvcMixerCard *card); + +G_END_DECLS + +#endif /* __GVC_MIXER_CARD_PRIVATE_H */ diff --git a/panels/sound/gvc-mixer-card.c b/panels/sound/gvc-mixer-card.c new file mode 100644 index 000000000..f198f1b1c --- /dev/null +++ b/panels/sound/gvc-mixer-card.c @@ -0,0 +1,506 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 William Jon McCann + * Copyright (C) 2009 Bastien Nocera + * + * 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 "gvc-mixer-card.h" +#include "gvc-mixer-card-private.h" + +#define GVC_MIXER_CARD_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_MIXER_CARD, GvcMixerCardPrivate)) + +static guint32 card_serial = 1; + +struct GvcMixerCardPrivate +{ + pa_context *pa_context; + guint id; + guint index; + char *name; + char *icon_name; + char *profile; + char *target_profile; + char *human_profile; + GList *profiles; + pa_operation *profile_op; +}; + +enum +{ + PROP_0, + PROP_ID, + PROP_PA_CONTEXT, + PROP_INDEX, + PROP_NAME, + PROP_ICON_NAME, + PROP_PROFILE, + PROP_HUMAN_PROFILE, +}; + +static void gvc_mixer_card_class_init (GvcMixerCardClass *klass); +static void gvc_mixer_card_init (GvcMixerCard *mixer_card); +static void gvc_mixer_card_finalize (GObject *object); + +G_DEFINE_TYPE (GvcMixerCard, gvc_mixer_card, G_TYPE_OBJECT) + +static guint32 +get_next_card_serial (void) +{ + guint32 serial; + + serial = card_serial++; + + if ((gint32)card_serial < 0) { + card_serial = 1; + } + + return serial; +} + +pa_context * +gvc_mixer_card_get_pa_context (GvcMixerCard *card) +{ + g_return_val_if_fail (GVC_IS_MIXER_CARD (card), 0); + return card->priv->pa_context; +} + +guint +gvc_mixer_card_get_index (GvcMixerCard *card) +{ + g_return_val_if_fail (GVC_IS_MIXER_CARD (card), 0); + return card->priv->index; +} + +guint +gvc_mixer_card_get_id (GvcMixerCard *card) +{ + g_return_val_if_fail (GVC_IS_MIXER_CARD (card), 0); + return card->priv->id; +} + +const char * +gvc_mixer_card_get_name (GvcMixerCard *card) +{ + g_return_val_if_fail (GVC_IS_MIXER_CARD (card), NULL); + return card->priv->name; +} + +gboolean +gvc_mixer_card_set_name (GvcMixerCard *card, + const char *name) +{ + g_return_val_if_fail (GVC_IS_MIXER_CARD (card), FALSE); + + g_free (card->priv->name); + card->priv->name = g_strdup (name); + g_object_notify (G_OBJECT (card), "name"); + + return TRUE; +} + +const char * +gvc_mixer_card_get_icon_name (GvcMixerCard *card) +{ + g_return_val_if_fail (GVC_IS_MIXER_CARD (card), NULL); + return card->priv->icon_name; +} + +gboolean +gvc_mixer_card_set_icon_name (GvcMixerCard *card, + const char *icon_name) +{ + g_return_val_if_fail (GVC_IS_MIXER_CARD (card), FALSE); + + g_free (card->priv->icon_name); + card->priv->icon_name = g_strdup (icon_name); + g_object_notify (G_OBJECT (card), "icon-name"); + + return TRUE; +} + +/** + * gvc_mixer_card_get_profile: (skip) + * + * @card: + * + * Returns: + */ +GvcMixerCardProfile * +gvc_mixer_card_get_profile (GvcMixerCard *card) +{ + GList *l; + + g_return_val_if_fail (GVC_IS_MIXER_CARD (card), NULL); + g_return_val_if_fail (card->priv->profiles != NULL, FALSE); + + for (l = card->priv->profiles; l != NULL; l = l->next) { + GvcMixerCardProfile *p = l->data; + if (g_str_equal (card->priv->profile, p->profile)) { + return p; + } + } + + g_assert_not_reached (); + + return NULL; +} + +gboolean +gvc_mixer_card_set_profile (GvcMixerCard *card, + const char *profile) +{ + GList *l; + + g_return_val_if_fail (GVC_IS_MIXER_CARD (card), FALSE); + g_return_val_if_fail (card->priv->profiles != NULL, FALSE); + + g_free (card->priv->profile); + card->priv->profile = g_strdup (profile); + + g_free (card->priv->human_profile); + card->priv->human_profile = NULL; + + for (l = card->priv->profiles; l != NULL; l = l->next) { + GvcMixerCardProfile *p = l->data; + if (g_str_equal (card->priv->profile, p->profile)) { + card->priv->human_profile = g_strdup (p->human_profile); + break; + } + } + + g_object_notify (G_OBJECT (card), "profile"); + + return TRUE; +} + +static void +_pa_context_set_card_profile_by_index_cb (pa_context *context, + int success, + void *userdata) +{ + GvcMixerCard *card = GVC_MIXER_CARD (userdata); + + g_assert (card->priv->target_profile); + + if (success > 0) { + gvc_mixer_card_set_profile (card, card->priv->target_profile); + } else { + g_debug ("Failed to switch profile on '%s' from '%s' to '%s'", + card->priv->name, + card->priv->profile, + card->priv->target_profile); + } + g_free (card->priv->target_profile); + card->priv->target_profile = NULL; + + pa_operation_unref (card->priv->profile_op); + card->priv->profile_op = NULL; +} + +gboolean +gvc_mixer_card_change_profile (GvcMixerCard *card, + const char *profile) +{ + g_return_val_if_fail (GVC_IS_MIXER_CARD (card), FALSE); + g_return_val_if_fail (card->priv->profiles != NULL, FALSE); + + /* Same profile, or already requested? */ + if (g_strcmp0 (card->priv->profile, profile) == 0) + return TRUE; + if (g_strcmp0 (profile, card->priv->target_profile) == 0) + return TRUE; + if (card->priv->profile_op != NULL) { + pa_operation_cancel (card->priv->profile_op); + pa_operation_unref (card->priv->profile_op); + card->priv->profile_op = NULL; + } + + if (card->priv->profile != NULL) { + g_free (card->priv->target_profile); + card->priv->target_profile = g_strdup (profile); + + card->priv->profile_op = pa_context_set_card_profile_by_index (card->priv->pa_context, + card->priv->index, + card->priv->target_profile, + _pa_context_set_card_profile_by_index_cb, + card); + + if (card->priv->profile_op == NULL) { + g_warning ("pa_context_set_card_profile_by_index() failed"); + return FALSE; + } + } else { + g_assert (card->priv->human_profile == NULL); + card->priv->profile = g_strdup (profile); + } + + return TRUE; +} + +const GList * +gvc_mixer_card_get_profiles (GvcMixerCard *card) +{ + g_return_val_if_fail (GVC_IS_MIXER_CARD (card), FALSE); + return card->priv->profiles; +} + +static int +sort_profiles (GvcMixerCardProfile *a, + GvcMixerCardProfile *b) +{ + if (a->priority == b->priority) + return 0; + if (a->priority > b->priority) + return 1; + return -1; +} + +gboolean +gvc_mixer_card_set_profiles (GvcMixerCard *card, + GList *profiles) +{ + g_return_val_if_fail (GVC_IS_MIXER_CARD (card), FALSE); + g_return_val_if_fail (card->priv->profiles == NULL, FALSE); + + card->priv->profiles = g_list_sort (profiles, (GCompareFunc) sort_profiles); + + return TRUE; +} + +static void +gvc_mixer_card_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GvcMixerCard *self = GVC_MIXER_CARD (object); + + switch (prop_id) { + case PROP_PA_CONTEXT: + self->priv->pa_context = g_value_get_pointer (value); + break; + case PROP_INDEX: + self->priv->index = g_value_get_ulong (value); + break; + case PROP_ID: + self->priv->id = g_value_get_ulong (value); + break; + case PROP_NAME: + gvc_mixer_card_set_name (self, g_value_get_string (value)); + break; + case PROP_ICON_NAME: + gvc_mixer_card_set_icon_name (self, g_value_get_string (value)); + break; + case PROP_PROFILE: + gvc_mixer_card_set_profile (self, g_value_get_string (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gvc_mixer_card_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GvcMixerCard *self = GVC_MIXER_CARD (object); + + switch (prop_id) { + case PROP_PA_CONTEXT: + g_value_set_pointer (value, self->priv->pa_context); + break; + case PROP_INDEX: + g_value_set_ulong (value, self->priv->index); + break; + case PROP_ID: + g_value_set_ulong (value, self->priv->id); + break; + case PROP_NAME: + g_value_set_string (value, self->priv->name); + break; + case PROP_ICON_NAME: + g_value_set_string (value, self->priv->icon_name); + break; + case PROP_PROFILE: + g_value_set_string (value, self->priv->profile); + break; + case PROP_HUMAN_PROFILE: + g_value_set_string (value, self->priv->human_profile); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GObject * +gvc_mixer_card_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_params) +{ + GObject *object; + GvcMixerCard *self; + + object = G_OBJECT_CLASS (gvc_mixer_card_parent_class)->constructor (type, n_construct_properties, construct_params); + + self = GVC_MIXER_CARD (object); + + self->priv->id = get_next_card_serial (); + + return object; +} + +static void +gvc_mixer_card_class_init (GvcMixerCardClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->constructor = gvc_mixer_card_constructor; + gobject_class->finalize = gvc_mixer_card_finalize; + + gobject_class->set_property = gvc_mixer_card_set_property; + gobject_class->get_property = gvc_mixer_card_get_property; + + g_object_class_install_property (gobject_class, + PROP_INDEX, + g_param_spec_ulong ("index", + "Index", + "The index for this card", + 0, G_MAXULONG, 0, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (gobject_class, + PROP_ID, + g_param_spec_ulong ("id", + "id", + "The id for this card", + 0, G_MAXULONG, 0, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (gobject_class, + PROP_PA_CONTEXT, + g_param_spec_pointer ("pa-context", + "PulseAudio context", + "The PulseAudio context for this card", + G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (gobject_class, + PROP_NAME, + g_param_spec_string ("name", + "Name", + "Name to display for this card", + NULL, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + g_object_class_install_property (gobject_class, + PROP_ICON_NAME, + g_param_spec_string ("icon-name", + "Icon Name", + "Name of icon to display for this card", + NULL, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + g_object_class_install_property (gobject_class, + PROP_PROFILE, + g_param_spec_string ("profile", + "Profile", + "Name of current profile for this card", + NULL, + G_PARAM_READWRITE)); + g_object_class_install_property (gobject_class, + PROP_HUMAN_PROFILE, + g_param_spec_string ("human-profile", + "Profile (Human readable)", + "Name of current profile for this card in human readable form", + NULL, + G_PARAM_READABLE)); + + g_type_class_add_private (klass, sizeof (GvcMixerCardPrivate)); +} + +static void +gvc_mixer_card_init (GvcMixerCard *card) +{ + card->priv = GVC_MIXER_CARD_GET_PRIVATE (card); +} + +GvcMixerCard * +gvc_mixer_card_new (pa_context *context, + guint index) +{ + GObject *object; + + object = g_object_new (GVC_TYPE_MIXER_CARD, + "index", index, + "pa-context", context, + NULL); + return GVC_MIXER_CARD (object); +} + +static void +free_profile (GvcMixerCardProfile *p) +{ + g_free (p->profile); + g_free (p->human_profile); + g_free (p->status); + g_free (p); +} + +static void +gvc_mixer_card_finalize (GObject *object) +{ + GvcMixerCard *mixer_card; + + g_return_if_fail (object != NULL); + g_return_if_fail (GVC_IS_MIXER_CARD (object)); + + mixer_card = GVC_MIXER_CARD (object); + + g_return_if_fail (mixer_card->priv != NULL); + + g_free (mixer_card->priv->name); + mixer_card->priv->name = NULL; + + g_free (mixer_card->priv->icon_name); + mixer_card->priv->icon_name = NULL; + + g_free (mixer_card->priv->target_profile); + mixer_card->priv->target_profile = NULL; + + g_free (mixer_card->priv->profile); + mixer_card->priv->profile = NULL; + + g_free (mixer_card->priv->human_profile); + mixer_card->priv->human_profile = NULL; + + g_list_foreach (mixer_card->priv->profiles, (GFunc) free_profile, NULL); + g_list_free (mixer_card->priv->profiles); + mixer_card->priv->profiles = NULL; + + G_OBJECT_CLASS (gvc_mixer_card_parent_class)->finalize (object); +} + diff --git a/panels/sound/gvc-mixer-card.h b/panels/sound/gvc-mixer-card.h new file mode 100644 index 000000000..5a3a7bc45 --- /dev/null +++ b/panels/sound/gvc-mixer-card.h @@ -0,0 +1,83 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008-2009 Red Hat, Inc. + * + * 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. + * + */ + +#ifndef __GVC_MIXER_CARD_H +#define __GVC_MIXER_CARD_H + +#include + +G_BEGIN_DECLS + +#define GVC_TYPE_MIXER_CARD (gvc_mixer_card_get_type ()) +#define GVC_MIXER_CARD(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GVC_TYPE_MIXER_CARD, GvcMixerCard)) +#define GVC_MIXER_CARD_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GVC_TYPE_MIXER_CARD, GvcMixerCardClass)) +#define GVC_IS_MIXER_CARD(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVC_TYPE_MIXER_CARD)) +#define GVC_IS_MIXER_CARD_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GVC_TYPE_MIXER_CARD)) +#define GVC_MIXER_CARD_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVC_TYPE_MIXER_CARD, GvcMixerCardClass)) + +typedef struct GvcMixerCardPrivate GvcMixerCardPrivate; + +typedef struct +{ + GObject parent; + GvcMixerCardPrivate *priv; +} GvcMixerCard; + +typedef struct +{ + GObjectClass parent_class; + + /* vtable */ +} GvcMixerCardClass; + +typedef struct +{ + char *profile; + char *human_profile; + char *status; + guint priority; + guint n_sinks, n_sources; +} GvcMixerCardProfile; + +GType gvc_mixer_card_get_type (void); + +guint gvc_mixer_card_get_id (GvcMixerCard *card); +guint gvc_mixer_card_get_index (GvcMixerCard *card); +const char * gvc_mixer_card_get_name (GvcMixerCard *card); +const char * gvc_mixer_card_get_icon_name (GvcMixerCard *card); +GvcMixerCardProfile * gvc_mixer_card_get_profile (GvcMixerCard *card); +const GList * gvc_mixer_card_get_profiles (GvcMixerCard *card); + +gboolean gvc_mixer_card_change_profile (GvcMixerCard *card, + const char *profile); + +/* private */ +gboolean gvc_mixer_card_set_name (GvcMixerCard *card, + const char *name); +gboolean gvc_mixer_card_set_icon_name (GvcMixerCard *card, + const char *name); +gboolean gvc_mixer_card_set_profile (GvcMixerCard *card, + const char *profile); +gboolean gvc_mixer_card_set_profiles (GvcMixerCard *card, + GList *profiles); + +G_END_DECLS + +#endif /* __GVC_MIXER_CARD_H */ diff --git a/panels/sound/gvc-mixer-control-private.h b/panels/sound/gvc-mixer-control-private.h new file mode 100644 index 000000000..ac79975aa --- /dev/null +++ b/panels/sound/gvc-mixer-control-private.h @@ -0,0 +1,35 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Red Hat, Inc. + * + * 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. + * + */ + +#ifndef __GVC_MIXER_CONTROL_PRIVATE_H +#define __GVC_MIXER_CONTROL_PRIVATE_H + +#include +#include +#include "gvc-mixer-stream.h" +#include "gvc-mixer-card.h" + +G_BEGIN_DECLS + +pa_context * gvc_mixer_control_get_pa_context (GvcMixerControl *control); + +G_END_DECLS + +#endif /* __GVC_MIXER_CONTROL_PRIVATE_H */ diff --git a/panels/sound/gvc-mixer-control.c b/panels/sound/gvc-mixer-control.c new file mode 100644 index 000000000..d8d80f6af --- /dev/null +++ b/panels/sound/gvc-mixer-control.c @@ -0,0 +1,2232 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2006-2008 Lennart Poettering + * Copyright (C) 2008 Sjoerd Simons + * Copyright (C) 2008 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 + +#include "gvc-mixer-control.h" +#include "gvc-mixer-sink.h" +#include "gvc-mixer-source.h" +#include "gvc-mixer-sink-input.h" +#include "gvc-mixer-source-output.h" +#include "gvc-mixer-event-role.h" +#include "gvc-mixer-card.h" +#include "gvc-mixer-card-private.h" +#include "gvc-channel-map-private.h" +#include "gvc-mixer-control-private.h" + +#define GVC_MIXER_CONTROL_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_MIXER_CONTROL, GvcMixerControlPrivate)) + +#define RECONNECT_DELAY 5 + +enum { + PROP_0, + PROP_NAME +}; + +struct GvcMixerControlPrivate +{ + pa_glib_mainloop *pa_mainloop; + pa_mainloop_api *pa_api; + pa_context *pa_context; + int n_outstanding; + guint reconnect_id; + char *name; + + gboolean default_sink_is_set; + guint default_sink_id; + char *default_sink_name; + gboolean default_source_is_set; + guint default_source_id; + char *default_source_name; + + gboolean event_sink_input_is_set; + guint event_sink_input_id; + + GHashTable *all_streams; + GHashTable *sinks; /* fixed outputs */ + GHashTable *sources; /* fixed inputs */ + GHashTable *sink_inputs; /* routable output streams */ + GHashTable *source_outputs; /* routable input streams */ + GHashTable *clients; + GHashTable *cards; + + GvcMixerStream *new_default_stream; /* new default stream, used in gvc_mixer_control_set_default_sink () */ +}; + +enum { + CONNECTING, + READY, + STREAM_ADDED, + STREAM_REMOVED, + CARD_ADDED, + CARD_REMOVED, + DEFAULT_SINK_CHANGED, + DEFAULT_SOURCE_CHANGED, + LAST_SIGNAL +}; + +static guint signals [LAST_SIGNAL] = { 0, }; + +static void gvc_mixer_control_class_init (GvcMixerControlClass *klass); +static void gvc_mixer_control_init (GvcMixerControl *mixer_control); +static void gvc_mixer_control_finalize (GObject *object); + +G_DEFINE_TYPE (GvcMixerControl, gvc_mixer_control, G_TYPE_OBJECT) + +pa_context * +gvc_mixer_control_get_pa_context (GvcMixerControl *control) +{ + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL); + return control->priv->pa_context; +} + +/** + * gvc_mixer_control_get_event_sink_input: + * + * @control: + * + * Returns: (transfer none): + */ +GvcMixerStream * +gvc_mixer_control_get_event_sink_input (GvcMixerControl *control) +{ + GvcMixerStream *stream; + + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL); + + stream = g_hash_table_lookup (control->priv->all_streams, + GUINT_TO_POINTER (control->priv->event_sink_input_id)); + + return stream; +} + +static void +gvc_mixer_control_stream_restore_cb (pa_context *c, + const pa_ext_stream_restore_info *info, + int eol, + void *userdata) +{ + pa_operation *o; + GvcMixerControl *control = (GvcMixerControl *) userdata; + pa_ext_stream_restore_info new_info; + + if (eol || control->priv->new_default_stream == NULL) + return; + + new_info.name = info->name; + new_info.channel_map = info->channel_map; + new_info.volume = info->volume; + new_info.mute = info->mute; + + new_info.device = gvc_mixer_stream_get_name (control->priv->new_default_stream); + + o = pa_ext_stream_restore_write (control->priv->pa_context, + PA_UPDATE_REPLACE, + &new_info, 1, + TRUE, NULL, NULL); + + if (o == NULL) { + g_warning ("pa_ext_stream_restore_write() failed: %s", + pa_strerror (pa_context_errno (control->priv->pa_context))); + return; + } + + g_debug ("Changed default device for %s to %s", info->name, info->device); + + pa_operation_unref (o); +} + +gboolean +gvc_mixer_control_set_default_sink (GvcMixerControl *control, + GvcMixerStream *stream) +{ + pa_operation *o; + + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE); + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + o = pa_context_set_default_sink (control->priv->pa_context, + gvc_mixer_stream_get_name (stream), + NULL, + NULL); + if (o == NULL) { + g_warning ("pa_context_set_default_sink() failed: %s", + pa_strerror (pa_context_errno (control->priv->pa_context))); + return FALSE; + } + + pa_operation_unref (o); + + control->priv->new_default_stream = stream; + g_object_add_weak_pointer (G_OBJECT (stream), (gpointer *) &control->priv->new_default_stream); + + o = pa_ext_stream_restore_read (control->priv->pa_context, + gvc_mixer_control_stream_restore_cb, + control); + + if (o == NULL) { + g_warning ("pa_ext_stream_restore_read() failed: %s", + pa_strerror (pa_context_errno (control->priv->pa_context))); + return FALSE; + } + + pa_operation_unref (o); + + return TRUE; +} + +gboolean +gvc_mixer_control_set_default_source (GvcMixerControl *control, + GvcMixerStream *stream) +{ + pa_operation *o; + + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE); + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + o = pa_context_set_default_source (control->priv->pa_context, + gvc_mixer_stream_get_name (stream), + NULL, + NULL); + if (o == NULL) { + g_warning ("pa_context_set_default_source() failed"); + return FALSE; + } + + pa_operation_unref (o); + + return TRUE; +} + +/** + * gvc_mixer_control_get_default_sink: + * + * @control: + * + * Returns: (transfer none): + */ +GvcMixerStream * +gvc_mixer_control_get_default_sink (GvcMixerControl *control) +{ + GvcMixerStream *stream; + + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL); + + if (control->priv->default_sink_is_set) { + stream = g_hash_table_lookup (control->priv->all_streams, + GUINT_TO_POINTER (control->priv->default_sink_id)); + } else { + stream = NULL; + } + + return stream; +} + +/** + * gvc_mixer_control_get_default_source: + * + * @control: + * + * Returns: (transfer none): + */ +GvcMixerStream * +gvc_mixer_control_get_default_source (GvcMixerControl *control) +{ + GvcMixerStream *stream; + + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL); + + if (control->priv->default_source_is_set) { + stream = g_hash_table_lookup (control->priv->all_streams, + GUINT_TO_POINTER (control->priv->default_source_id)); + } else { + stream = NULL; + } + + return stream; +} + +static gpointer +gvc_mixer_control_lookup_id (GHashTable *hash_table, + guint id) +{ + return g_hash_table_lookup (hash_table, + GUINT_TO_POINTER (id)); +} + +/** + * gvc_mixer_control_lookup_stream_id: + * + * @control: + * @id: + * + * Returns: (transfer none): + */ +GvcMixerStream * +gvc_mixer_control_lookup_stream_id (GvcMixerControl *control, + guint id) +{ + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL); + + return gvc_mixer_control_lookup_id (control->priv->all_streams, id); +} + +/** + * gvc_mixer_control_lookup_card_id: + * + * @control: + * @id: + * + * Returns: (transfer none): + */ +GvcMixerCard * +gvc_mixer_control_lookup_card_id (GvcMixerControl *control, + guint id) +{ + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL); + + return gvc_mixer_control_lookup_id (control->priv->cards, id); +} + +static void +listify_hash_values_hfunc (gpointer key, + gpointer value, + gpointer user_data) +{ + GSList **list = user_data; + + *list = g_slist_prepend (*list, value); +} + +static int +gvc_name_collate (const char *namea, + const char *nameb) +{ + if (nameb == NULL && namea == NULL) + return 0; + if (nameb == NULL) + return 1; + if (namea == NULL) + return -1; + + return g_utf8_collate (namea, nameb); +} + +static int +gvc_card_collate (GvcMixerCard *a, + GvcMixerCard *b) +{ + const char *namea; + const char *nameb; + + g_return_val_if_fail (a == NULL || GVC_IS_MIXER_CARD (a), 0); + g_return_val_if_fail (b == NULL || GVC_IS_MIXER_CARD (b), 0); + + namea = gvc_mixer_card_get_name (a); + nameb = gvc_mixer_card_get_name (b); + + return gvc_name_collate (namea, nameb); +} + +/** + * gvc_mixer_control_get_cards: + * + * @control: + * + * Returns: (transfer container) (element-type Gvc.MixerCard): + */ +GSList * +gvc_mixer_control_get_cards (GvcMixerControl *control) +{ + GSList *retval; + + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL); + + retval = NULL; + g_hash_table_foreach (control->priv->cards, + listify_hash_values_hfunc, + &retval); + return g_slist_sort (retval, (GCompareFunc) gvc_card_collate); +} + +static int +gvc_stream_collate (GvcMixerStream *a, + GvcMixerStream *b) +{ + const char *namea; + const char *nameb; + + g_return_val_if_fail (a == NULL || GVC_IS_MIXER_STREAM (a), 0); + g_return_val_if_fail (b == NULL || GVC_IS_MIXER_STREAM (b), 0); + + namea = gvc_mixer_stream_get_name (a); + nameb = gvc_mixer_stream_get_name (b); + + return gvc_name_collate (namea, nameb); +} + +/** + * gvc_mixer_control_get_streams: + * + * @control: + * + * Returns: (transfer container) (element-type Gvc.MixerStream): + */ +GSList * +gvc_mixer_control_get_streams (GvcMixerControl *control) +{ + GSList *retval; + + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL); + + retval = NULL; + g_hash_table_foreach (control->priv->all_streams, + listify_hash_values_hfunc, + &retval); + return g_slist_sort (retval, (GCompareFunc) gvc_stream_collate); +} + +/** + * gvc_mixer_control_get_sinks: + * + * @control: + * + * Returns: (transfer container) (element-type Gvc.MixerSink): + */ +GSList * +gvc_mixer_control_get_sinks (GvcMixerControl *control) +{ + GSList *retval; + + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL); + + retval = NULL; + g_hash_table_foreach (control->priv->sinks, + listify_hash_values_hfunc, + &retval); + return g_slist_sort (retval, (GCompareFunc) gvc_stream_collate); +} + +/** + * gvc_mixer_control_get_sources: + * + * @control: + * + * Returns: (transfer container) (element-type Gvc.MixerSource): + */ +GSList * +gvc_mixer_control_get_sources (GvcMixerControl *control) +{ + GSList *retval; + + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL); + + retval = NULL; + g_hash_table_foreach (control->priv->sources, + listify_hash_values_hfunc, + &retval); + return g_slist_sort (retval, (GCompareFunc) gvc_stream_collate); +} + +/** + * gvc_mixer_control_get_sink_inputs: + * + * @control: + * + * Returns: (transfer container) (element-type Gvc.MixerSinkInput): + */ +GSList * +gvc_mixer_control_get_sink_inputs (GvcMixerControl *control) +{ + GSList *retval; + + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL); + + retval = NULL; + g_hash_table_foreach (control->priv->sink_inputs, + listify_hash_values_hfunc, + &retval); + return g_slist_sort (retval, (GCompareFunc) gvc_stream_collate); +} + +/** + * gvc_mixer_control_get_source_outputs: + * + * @control: + * + * Returns: (transfer container) (element-type Gvc.MixerSourceOutput): + */ +GSList * +gvc_mixer_control_get_source_outputs (GvcMixerControl *control) +{ + GSList *retval; + + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL); + + retval = NULL; + g_hash_table_foreach (control->priv->source_outputs, + listify_hash_values_hfunc, + &retval); + return g_slist_sort (retval, (GCompareFunc) gvc_stream_collate); +} + +static void +dec_outstanding (GvcMixerControl *control) +{ + if (control->priv->n_outstanding <= 0) { + return; + } + + if (--control->priv->n_outstanding <= 0) { + g_signal_emit (G_OBJECT (control), signals[READY], 0); + } +} + +gboolean +gvc_mixer_control_is_ready (GvcMixerControl *control) +{ + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE); + + return (control->priv->n_outstanding == 0); +} + + +static void +_set_default_source (GvcMixerControl *control, + GvcMixerStream *stream) +{ + guint new_id; + + if (stream == NULL) { + control->priv->default_source_id = 0; + control->priv->default_source_is_set = FALSE; + g_signal_emit (control, + signals[DEFAULT_SOURCE_CHANGED], + 0, + PA_INVALID_INDEX); + return; + } + + new_id = gvc_mixer_stream_get_id (stream); + + if (control->priv->default_source_id != new_id) { + control->priv->default_source_id = new_id; + control->priv->default_source_is_set = TRUE; + g_signal_emit (control, + signals[DEFAULT_SOURCE_CHANGED], + 0, + new_id); + } +} + +static void +_set_default_sink (GvcMixerControl *control, + GvcMixerStream *stream) +{ + guint new_id; + + if (stream == NULL) { + /* Don't tell front-ends about an unset default + * sink if it's already unset */ + if (control->priv->default_sink_is_set == FALSE) + return; + control->priv->default_sink_id = 0; + control->priv->default_sink_is_set = FALSE; + g_signal_emit (control, + signals[DEFAULT_SINK_CHANGED], + 0, + PA_INVALID_INDEX); + return; + } + + new_id = gvc_mixer_stream_get_id (stream); + + if (control->priv->default_sink_id != new_id) { + control->priv->default_sink_id = new_id; + control->priv->default_sink_is_set = TRUE; + g_signal_emit (control, + signals[DEFAULT_SINK_CHANGED], + 0, + new_id); + } +} + +static gboolean +_stream_has_name (gpointer key, + GvcMixerStream *stream, + const char *name) +{ + const char *t_name; + + t_name = gvc_mixer_stream_get_name (stream); + + if (t_name != NULL + && name != NULL + && strcmp (t_name, name) == 0) { + return TRUE; + } + + return FALSE; +} + +static GvcMixerStream * +find_stream_for_name (GvcMixerControl *control, + const char *name) +{ + GvcMixerStream *stream; + + stream = g_hash_table_find (control->priv->all_streams, + (GHRFunc)_stream_has_name, + (char *)name); + return stream; +} + +static void +update_default_source_from_name (GvcMixerControl *control, + const char *name) +{ + gboolean changed; + + if ((control->priv->default_source_name == NULL + && name != NULL) + || (control->priv->default_source_name != NULL + && name == NULL) + || strcmp (control->priv->default_source_name, name) != 0) { + changed = TRUE; + } + + if (changed) { + GvcMixerStream *stream; + + g_free (control->priv->default_source_name); + control->priv->default_source_name = g_strdup (name); + + stream = find_stream_for_name (control, name); + _set_default_source (control, stream); + } +} + +static void +update_default_sink_from_name (GvcMixerControl *control, + const char *name) +{ + gboolean changed; + + if ((control->priv->default_sink_name == NULL + && name != NULL) + || (control->priv->default_sink_name != NULL + && name == NULL) + || strcmp (control->priv->default_sink_name, name) != 0) { + changed = TRUE; + } + + if (changed) { + GvcMixerStream *stream; + g_free (control->priv->default_sink_name); + control->priv->default_sink_name = g_strdup (name); + + stream = find_stream_for_name (control, name); + _set_default_sink (control, stream); + } +} + +static void +update_server (GvcMixerControl *control, + const pa_server_info *info) +{ + if (info->default_source_name != NULL) { + update_default_source_from_name (control, info->default_source_name); + } + if (info->default_sink_name != NULL) { + update_default_sink_from_name (control, info->default_sink_name); + } +} + +static void +remove_stream (GvcMixerControl *control, + GvcMixerStream *stream) +{ + guint id; + + g_object_ref (stream); + + id = gvc_mixer_stream_get_id (stream); + + if (id == control->priv->default_sink_id) { + _set_default_sink (control, NULL); + } else if (id == control->priv->default_source_id) { + _set_default_source (control, NULL); + } + + g_hash_table_remove (control->priv->all_streams, + GUINT_TO_POINTER (id)); + g_signal_emit (G_OBJECT (control), + signals[STREAM_REMOVED], + 0, + gvc_mixer_stream_get_id (stream)); + g_object_unref (stream); +} + +static void +add_stream (GvcMixerControl *control, + GvcMixerStream *stream) +{ + g_hash_table_insert (control->priv->all_streams, + GUINT_TO_POINTER (gvc_mixer_stream_get_id (stream)), + stream); + g_signal_emit (G_OBJECT (control), + signals[STREAM_ADDED], + 0, + gvc_mixer_stream_get_id (stream)); +} + +static void +update_sink (GvcMixerControl *control, + const pa_sink_info *info) +{ + GvcMixerStream *stream; + gboolean is_new; + pa_volume_t max_volume; + GvcChannelMap *map; + char map_buff[PA_CHANNEL_MAP_SNPRINT_MAX]; + + pa_channel_map_snprint (map_buff, PA_CHANNEL_MAP_SNPRINT_MAX, &info->channel_map); +#if 1 + g_debug ("Updating sink: index=%u name='%s' description='%s' map='%s'", + info->index, + info->name, + info->description, + map_buff); +#endif + + map = NULL; + is_new = FALSE; + stream = g_hash_table_lookup (control->priv->sinks, + GUINT_TO_POINTER (info->index)); + if (stream == NULL) { +#if PA_MICRO > 15 + GList *list = NULL; + guint i; +#endif /* PA_MICRO > 15 */ + + map = gvc_channel_map_new_from_pa_channel_map (&info->channel_map); + stream = gvc_mixer_sink_new (control->priv->pa_context, + info->index, + map); +#if PA_MICRO > 15 + for (i = 0; i < info->n_ports; i++) { + GvcMixerStreamPort *port; + + port = g_new0 (GvcMixerStreamPort, 1); + port->port = g_strdup (info->ports[i]->name); + port->human_port = g_strdup (info->ports[i]->description); + port->priority = info->ports[i]->priority; + list = g_list_prepend (list, port); + } + gvc_mixer_stream_set_ports (stream, list); +#endif /* PA_MICRO > 15 */ + g_object_unref (map); + is_new = TRUE; + } else if (gvc_mixer_stream_is_running (stream)) { + /* Ignore events if volume changes are outstanding */ + g_debug ("Ignoring event, volume changes are outstanding"); + return; + } + + max_volume = pa_cvolume_max (&info->volume); + gvc_mixer_stream_set_name (stream, info->name); + gvc_mixer_stream_set_card_index (stream, info->card); + gvc_mixer_stream_set_description (stream, info->description); + gvc_mixer_stream_set_icon_name (stream, "audio-card"); + gvc_mixer_stream_set_volume (stream, (guint)max_volume); + gvc_mixer_stream_set_is_muted (stream, info->mute); + gvc_mixer_stream_set_can_decibel (stream, !!(info->flags & PA_SINK_DECIBEL_VOLUME)); + gvc_mixer_stream_set_base_volume (stream, (guint32) info->base_volume); +#if PA_MICRO > 15 + if (info->active_port != NULL) + gvc_mixer_stream_set_port (stream, info->active_port->name); +#endif /* PA_MICRO > 15 */ + + if (is_new) { + g_hash_table_insert (control->priv->sinks, + GUINT_TO_POINTER (info->index), + g_object_ref (stream)); + add_stream (control, stream); + } + + if (control->priv->default_sink_name != NULL + && info->name != NULL + && strcmp (control->priv->default_sink_name, info->name) == 0) { + _set_default_sink (control, stream); + } + + if (map == NULL) + map = (GvcChannelMap *) gvc_mixer_stream_get_channel_map (stream); + gvc_channel_map_volume_changed (map, &info->volume, FALSE); +} + +static void +update_source (GvcMixerControl *control, + const pa_source_info *info) +{ + GvcMixerStream *stream; + gboolean is_new; + pa_volume_t max_volume; + +#if 1 + g_debug ("Updating source: index=%u name='%s' description='%s'", + info->index, + info->name, + info->description); +#endif + + /* completely ignore monitors, they're not real sources */ + if (info->monitor_of_sink != PA_INVALID_INDEX) { + return; + } + + is_new = FALSE; + + stream = g_hash_table_lookup (control->priv->sources, + GUINT_TO_POINTER (info->index)); + if (stream == NULL) { +#if PA_MICRO > 15 + GList *list = NULL; + guint i; +#endif /* PA_MICRO > 15 */ + GvcChannelMap *map; + + map = gvc_channel_map_new_from_pa_channel_map (&info->channel_map); + stream = gvc_mixer_source_new (control->priv->pa_context, + info->index, + map); +#if PA_MICRO > 15 + for (i = 0; i < info->n_ports; i++) { + GvcMixerStreamPort *port; + + port = g_new0 (GvcMixerStreamPort, 1); + port->port = g_strdup (info->ports[i]->name); + port->human_port = g_strdup (info->ports[i]->description); + port->priority = info->ports[i]->priority; + list = g_list_prepend (list, port); + } + gvc_mixer_stream_set_ports (stream, list); +#endif /* PA_MICRO > 15 */ + + g_object_unref (map); + is_new = TRUE; + } else if (gvc_mixer_stream_is_running (stream)) { + /* Ignore events if volume changes are outstanding */ + g_debug ("Ignoring event, volume changes are outstanding"); + return; + } + + max_volume = pa_cvolume_max (&info->volume); + + gvc_mixer_stream_set_name (stream, info->name); + gvc_mixer_stream_set_card_index (stream, info->card); + gvc_mixer_stream_set_description (stream, info->description); + gvc_mixer_stream_set_icon_name (stream, "audio-input-microphone"); + gvc_mixer_stream_set_volume (stream, (guint)max_volume); + gvc_mixer_stream_set_is_muted (stream, info->mute); + gvc_mixer_stream_set_can_decibel (stream, !!(info->flags & PA_SOURCE_DECIBEL_VOLUME)); + gvc_mixer_stream_set_base_volume (stream, (guint32) info->base_volume); +#if PA_MICRO > 15 + if (info->active_port != NULL) + gvc_mixer_stream_set_port (stream, info->active_port->name); +#endif /* PA_MICRO > 15 */ + + if (is_new) { + g_hash_table_insert (control->priv->sources, + GUINT_TO_POINTER (info->index), + g_object_ref (stream)); + add_stream (control, stream); + } + + if (control->priv->default_source_name != NULL + && info->name != NULL + && strcmp (control->priv->default_source_name, info->name) == 0) { + _set_default_source (control, stream); + } +} + +static void +set_icon_name_from_proplist (GvcMixerStream *stream, + pa_proplist *l, + const char *default_icon_name) +{ + const char *t; + + if ((t = pa_proplist_gets (l, PA_PROP_MEDIA_ICON_NAME))) { + goto finish; + } + + if ((t = pa_proplist_gets (l, PA_PROP_WINDOW_ICON_NAME))) { + goto finish; + } + + if ((t = pa_proplist_gets (l, PA_PROP_APPLICATION_ICON_NAME))) { + goto finish; + } + + if ((t = pa_proplist_gets (l, PA_PROP_MEDIA_ROLE))) { + + if (strcmp (t, "video") == 0 || + strcmp (t, "phone") == 0) { + goto finish; + } + + if (strcmp (t, "music") == 0) { + t = "audio"; + goto finish; + } + + if (strcmp (t, "game") == 0) { + t = "applications-games"; + goto finish; + } + + if (strcmp (t, "event") == 0) { + t = "dialog-information"; + goto finish; + } + } + + t = default_icon_name; + + finish: + gvc_mixer_stream_set_icon_name (stream, t); +} + +static void +set_is_event_stream_from_proplist (GvcMixerStream *stream, + pa_proplist *l) +{ + const char *t; + gboolean is_event_stream; + + is_event_stream = FALSE; + + if ((t = pa_proplist_gets (l, PA_PROP_MEDIA_ROLE))) { + if (g_str_equal (t, "event")) + is_event_stream = TRUE; + } + + gvc_mixer_stream_set_is_event_stream (stream, is_event_stream); +} + +static void +set_application_id_from_proplist (GvcMixerStream *stream, + pa_proplist *l) +{ + const char *t; + + if ((t = pa_proplist_gets (l, PA_PROP_APPLICATION_ID))) { + gvc_mixer_stream_set_application_id (stream, t); + } +} + +static void +update_sink_input (GvcMixerControl *control, + const pa_sink_input_info *info) +{ + GvcMixerStream *stream; + gboolean is_new; + pa_volume_t max_volume; + const char *name; + +#if 0 + g_debug ("Updating sink input: index=%u name='%s' client=%u sink=%u", + info->index, + info->name, + info->client, + info->sink); +#endif + + is_new = FALSE; + + stream = g_hash_table_lookup (control->priv->sink_inputs, + GUINT_TO_POINTER (info->index)); + if (stream == NULL) { + GvcChannelMap *map; + map = gvc_channel_map_new_from_pa_channel_map (&info->channel_map); + stream = gvc_mixer_sink_input_new (control->priv->pa_context, + info->index, + map); + g_object_unref (map); + is_new = TRUE; + } else if (gvc_mixer_stream_is_running (stream)) { + /* Ignore events if volume changes are outstanding */ + g_debug ("Ignoring event, volume changes are outstanding"); + return; + } + + max_volume = pa_cvolume_max (&info->volume); + + name = (const char *)g_hash_table_lookup (control->priv->clients, + GUINT_TO_POINTER (info->client)); + gvc_mixer_stream_set_name (stream, name); + gvc_mixer_stream_set_description (stream, info->name); + + set_application_id_from_proplist (stream, info->proplist); + set_is_event_stream_from_proplist (stream, info->proplist); + set_icon_name_from_proplist (stream, info->proplist, "applications-multimedia"); + gvc_mixer_stream_set_volume (stream, (guint)max_volume); + gvc_mixer_stream_set_is_muted (stream, info->mute); + gvc_mixer_stream_set_is_virtual (stream, info->client == PA_INVALID_INDEX); + + if (is_new) { + g_hash_table_insert (control->priv->sink_inputs, + GUINT_TO_POINTER (info->index), + g_object_ref (stream)); + add_stream (control, stream); + } +} + +static void +update_source_output (GvcMixerControl *control, + const pa_source_output_info *info) +{ + GvcMixerStream *stream; + gboolean is_new; + const char *name; + +#if 1 + g_debug ("Updating source output: index=%u name='%s' client=%u source=%u", + info->index, + info->name, + info->client, + info->source); +#endif + + is_new = FALSE; + stream = g_hash_table_lookup (control->priv->source_outputs, + GUINT_TO_POINTER (info->index)); + if (stream == NULL) { + GvcChannelMap *map; + map = gvc_channel_map_new_from_pa_channel_map (&info->channel_map); + stream = gvc_mixer_source_output_new (control->priv->pa_context, + info->index, + map); + g_object_unref (map); + is_new = TRUE; + } + + name = (const char *)g_hash_table_lookup (control->priv->clients, + GUINT_TO_POINTER (info->client)); + + gvc_mixer_stream_set_name (stream, name); + gvc_mixer_stream_set_description (stream, info->name); + set_application_id_from_proplist (stream, info->proplist); + set_is_event_stream_from_proplist (stream, info->proplist); + set_icon_name_from_proplist (stream, info->proplist, "audio-input-microphone"); + + if (is_new) { + g_hash_table_insert (control->priv->source_outputs, + GUINT_TO_POINTER (info->index), + g_object_ref (stream)); + add_stream (control, stream); + } +} + +static void +update_client (GvcMixerControl *control, + const pa_client_info *info) +{ +#if 1 + g_debug ("Updating client: index=%u name='%s'", + info->index, + info->name); +#endif + g_hash_table_insert (control->priv->clients, + GUINT_TO_POINTER (info->index), + g_strdup (info->name)); +} + +static char * +card_num_streams_to_status (guint sinks, + guint sources) +{ + char *sinks_str; + char *sources_str; + char *ret; + + if (sinks == 0 && sources == 0) { + /* translators: + * The device has been disabled */ + return g_strdup (_("Disabled")); + } + if (sinks == 0) { + sinks_str = NULL; + } else { + /* translators: + * The number of sound outputs on a particular device */ + sinks_str = g_strdup_printf (ngettext ("%u Output", + "%u Outputs", + sinks), + sinks); + } + if (sources == 0) { + sources_str = NULL; + } else { + /* translators: + * The number of sound inputs on a particular device */ + sources_str = g_strdup_printf (ngettext ("%u Input", + "%u Inputs", + sources), + sources); + } + if (sources_str == NULL) + return sinks_str; + if (sinks_str == NULL) + return sources_str; + ret = g_strdup_printf ("%s / %s", sinks_str, sources_str); + g_free (sinks_str); + g_free (sources_str); + return ret; +} + +static void +update_card (GvcMixerControl *control, + const pa_card_info *info) +{ + GvcMixerCard *card; + gboolean is_new; +#if 1 + guint i; + const char *key; + void *state; + + g_debug ("Udpating card %s (index: %u driver: %s):", + info->name, info->index, info->driver); + + for (i = 0; i < info->n_profiles; i++) { + struct pa_card_profile_info pi = info->profiles[i]; + gboolean is_default; + + is_default = (g_strcmp0 (pi.name, info->active_profile->name) == 0); + g_debug ("\tProfile '%s': %d sources %d sinks%s", + pi.name, pi.n_sources, pi.n_sinks, + is_default ? " (Current)" : ""); + } + state = NULL; + key = pa_proplist_iterate (info->proplist, &state); + while (key != NULL) { + g_debug ("\tProperty: '%s' = '%s'", + key, pa_proplist_gets (info->proplist, key)); + key = pa_proplist_iterate (info->proplist, &state); + } +#endif + card = g_hash_table_lookup (control->priv->cards, + GUINT_TO_POINTER (info->index)); + if (card == NULL) { + GList *list = NULL; + + for (i = 0; i < info->n_profiles; i++) { + struct pa_card_profile_info pi = info->profiles[i]; + GvcMixerCardProfile *profile; + + profile = g_new0 (GvcMixerCardProfile, 1); + profile->profile = g_strdup (pi.name); + profile->human_profile = g_strdup (pi.description); + profile->status = card_num_streams_to_status (pi.n_sinks, pi.n_sources); + profile->n_sinks = pi.n_sinks; + profile->n_sources = pi.n_sources; + profile->priority = pi.priority; + list = g_list_prepend (list, profile); + } + card = gvc_mixer_card_new (control->priv->pa_context, + info->index); + gvc_mixer_card_set_profiles (card, list); + is_new = TRUE; + } + + gvc_mixer_card_set_name (card, pa_proplist_gets (info->proplist, "device.description")); + gvc_mixer_card_set_icon_name (card, pa_proplist_gets (info->proplist, "device.icon_name")); + gvc_mixer_card_set_profile (card, info->active_profile->name); + + if (is_new) { + g_hash_table_insert (control->priv->cards, + GUINT_TO_POINTER (info->index), + g_object_ref (card)); + } + g_signal_emit (G_OBJECT (control), + signals[CARD_ADDED], + 0, + info->index); +} + +static void +_pa_context_get_sink_info_cb (pa_context *context, + const pa_sink_info *i, + int eol, + void *userdata) +{ + GvcMixerControl *control = GVC_MIXER_CONTROL (userdata); + + if (eol < 0) { + if (pa_context_errno (context) == PA_ERR_NOENTITY) { + return; + } + + g_warning ("Sink callback failure"); + return; + } + + if (eol > 0) { + dec_outstanding (control); + return; + } + + update_sink (control, i); +} + +static void +_pa_context_get_source_info_cb (pa_context *context, + const pa_source_info *i, + int eol, + void *userdata) +{ + GvcMixerControl *control = GVC_MIXER_CONTROL (userdata); + + if (eol < 0) { + if (pa_context_errno (context) == PA_ERR_NOENTITY) { + return; + } + + g_warning ("Source callback failure"); + return; + } + + if (eol > 0) { + dec_outstanding (control); + return; + } + + update_source (control, i); +} + +static void +_pa_context_get_sink_input_info_cb (pa_context *context, + const pa_sink_input_info *i, + int eol, + void *userdata) +{ + GvcMixerControl *control = GVC_MIXER_CONTROL (userdata); + + if (eol < 0) { + if (pa_context_errno (context) == PA_ERR_NOENTITY) { + return; + } + + g_warning ("Sink input callback failure"); + return; + } + + if (eol > 0) { + dec_outstanding (control); + return; + } + + update_sink_input (control, i); +} + +static void +_pa_context_get_source_output_info_cb (pa_context *context, + const pa_source_output_info *i, + int eol, + void *userdata) +{ + GvcMixerControl *control = GVC_MIXER_CONTROL (userdata); + + if (eol < 0) { + if (pa_context_errno (context) == PA_ERR_NOENTITY) { + return; + } + + g_warning ("Source output callback failure"); + return; + } + + if (eol > 0) { + dec_outstanding (control); + return; + } + + update_source_output (control, i); +} + +static void +_pa_context_get_client_info_cb (pa_context *context, + const pa_client_info *i, + int eol, + void *userdata) +{ + GvcMixerControl *control = GVC_MIXER_CONTROL (userdata); + + if (eol < 0) { + if (pa_context_errno (context) == PA_ERR_NOENTITY) { + return; + } + + g_warning ("Client callback failure"); + return; + } + + if (eol > 0) { + dec_outstanding (control); + return; + } + + update_client (control, i); +} + +static void +_pa_context_get_card_info_by_index_cb (pa_context *context, + const pa_card_info *i, + int eol, + void *userdata) +{ + GvcMixerControl *control = GVC_MIXER_CONTROL (userdata); + + if (eol < 0) { + if (pa_context_errno (context) == PA_ERR_NOENTITY) + return; + + g_warning ("Card callback failure"); + return; + } + + if (eol > 0) { + dec_outstanding (control); + return; + } + + update_card (control, i); +} + +static void +_pa_context_get_server_info_cb (pa_context *context, + const pa_server_info *i, + void *userdata) +{ + GvcMixerControl *control = GVC_MIXER_CONTROL (userdata); + + if (i == NULL) { + g_warning ("Server info callback failure"); + return; + } + + update_server (control, i); + dec_outstanding (control); +} + +static void +remove_event_role_stream (GvcMixerControl *control) +{ + g_debug ("Removing event role"); +} + +static void +update_event_role_stream (GvcMixerControl *control, + const pa_ext_stream_restore_info *info) +{ + GvcMixerStream *stream; + gboolean is_new; + pa_volume_t max_volume; + + if (strcmp (info->name, "sink-input-by-media-role:event") != 0) { + return; + } + +#if 0 + g_debug ("Updating event role: name='%s' device='%s'", + info->name, + info->device); +#endif + + is_new = FALSE; + + if (!control->priv->event_sink_input_is_set) { + pa_channel_map pa_map; + GvcChannelMap *map; + + pa_map.channels = 1; + pa_map.map[0] = PA_CHANNEL_POSITION_MONO; + map = gvc_channel_map_new_from_pa_channel_map (&pa_map); + + stream = gvc_mixer_event_role_new (control->priv->pa_context, + info->device, + map); + control->priv->event_sink_input_id = gvc_mixer_stream_get_id (stream); + control->priv->event_sink_input_is_set = TRUE; + + is_new = TRUE; + } else { + stream = g_hash_table_lookup (control->priv->all_streams, + GUINT_TO_POINTER (control->priv->event_sink_input_id)); + } + + max_volume = pa_cvolume_max (&info->volume); + + gvc_mixer_stream_set_name (stream, _("System Sounds")); + gvc_mixer_stream_set_icon_name (stream, "multimedia-volume-control"); + gvc_mixer_stream_set_volume (stream, (guint)max_volume); + gvc_mixer_stream_set_is_muted (stream, info->mute); + + if (is_new) { + add_stream (control, stream); + } +} + +static void +_pa_ext_stream_restore_read_cb (pa_context *context, + const pa_ext_stream_restore_info *i, + int eol, + void *userdata) +{ + GvcMixerControl *control = GVC_MIXER_CONTROL (userdata); + + if (eol < 0) { + g_debug ("Failed to initialized stream_restore extension: %s", + pa_strerror (pa_context_errno (context))); + remove_event_role_stream (control); + return; + } + + if (eol > 0) { + dec_outstanding (control); + /* If we don't have an event stream to restore, then + * set one up with a default 100% volume */ + if (!control->priv->event_sink_input_is_set) { + pa_ext_stream_restore_info info; + + memset (&info, 0, sizeof(info)); + info.name = "sink-input-by-media-role:event"; + info.volume.channels = 1; + info.volume.values[0] = PA_VOLUME_NORM; + update_event_role_stream (control, &info); + } + return; + } + + update_event_role_stream (control, i); +} + +static void +_pa_ext_stream_restore_subscribe_cb (pa_context *context, + void *userdata) +{ + GvcMixerControl *control = GVC_MIXER_CONTROL (userdata); + pa_operation *o; + + o = pa_ext_stream_restore_read (context, + _pa_ext_stream_restore_read_cb, + control); + if (o == NULL) { + g_warning ("pa_ext_stream_restore_read() failed"); + return; + } + + pa_operation_unref (o); +} + +static void +req_update_server_info (GvcMixerControl *control, + int index) +{ + pa_operation *o; + + o = pa_context_get_server_info (control->priv->pa_context, + _pa_context_get_server_info_cb, + control); + if (o == NULL) { + g_warning ("pa_context_get_server_info() failed"); + return; + } + pa_operation_unref (o); +} + +static void +req_update_client_info (GvcMixerControl *control, + int index) +{ + pa_operation *o; + + if (index < 0) { + o = pa_context_get_client_info_list (control->priv->pa_context, + _pa_context_get_client_info_cb, + control); + } else { + o = pa_context_get_client_info (control->priv->pa_context, + index, + _pa_context_get_client_info_cb, + control); + } + + if (o == NULL) { + g_warning ("pa_context_client_info_list() failed"); + return; + } + pa_operation_unref (o); +} + +static void +req_update_card (GvcMixerControl *control, + int index) +{ + pa_operation *o; + + if (index < 0) { + o = pa_context_get_card_info_list (control->priv->pa_context, + _pa_context_get_card_info_by_index_cb, + control); + } else { + o = pa_context_get_card_info_by_index (control->priv->pa_context, + index, + _pa_context_get_card_info_by_index_cb, + control); + } + + if (o == NULL) { + g_warning ("pa_context_get_card_info_by_index() failed"); + return; + } + pa_operation_unref (o); +} + +static void +req_update_sink_info (GvcMixerControl *control, + int index) +{ + pa_operation *o; + + if (index < 0) { + o = pa_context_get_sink_info_list (control->priv->pa_context, + _pa_context_get_sink_info_cb, + control); + } else { + o = pa_context_get_sink_info_by_index (control->priv->pa_context, + index, + _pa_context_get_sink_info_cb, + control); + } + + if (o == NULL) { + g_warning ("pa_context_get_sink_info_list() failed"); + return; + } + pa_operation_unref (o); +} + +static void +req_update_source_info (GvcMixerControl *control, + int index) +{ + pa_operation *o; + + if (index < 0) { + o = pa_context_get_source_info_list (control->priv->pa_context, + _pa_context_get_source_info_cb, + control); + } else { + o = pa_context_get_source_info_by_index(control->priv->pa_context, + index, + _pa_context_get_source_info_cb, + control); + } + + if (o == NULL) { + g_warning ("pa_context_get_source_info_list() failed"); + return; + } + pa_operation_unref (o); +} + +static void +req_update_sink_input_info (GvcMixerControl *control, + int index) +{ + pa_operation *o; + + if (index < 0) { + o = pa_context_get_sink_input_info_list (control->priv->pa_context, + _pa_context_get_sink_input_info_cb, + control); + } else { + o = pa_context_get_sink_input_info (control->priv->pa_context, + index, + _pa_context_get_sink_input_info_cb, + control); + } + + if (o == NULL) { + g_warning ("pa_context_get_sink_input_info_list() failed"); + return; + } + pa_operation_unref (o); +} + +static void +req_update_source_output_info (GvcMixerControl *control, + int index) +{ + pa_operation *o; + + if (index < 0) { + o = pa_context_get_source_output_info_list (control->priv->pa_context, + _pa_context_get_source_output_info_cb, + control); + } else { + o = pa_context_get_source_output_info (control->priv->pa_context, + index, + _pa_context_get_source_output_info_cb, + control); + } + + if (o == NULL) { + g_warning ("pa_context_get_source_output_info_list() failed"); + return; + } + pa_operation_unref (o); +} + +static void +remove_client (GvcMixerControl *control, + guint index) +{ + g_hash_table_remove (control->priv->clients, + GUINT_TO_POINTER (index)); +} + +static void +remove_card (GvcMixerControl *control, + guint index) +{ + g_hash_table_remove (control->priv->cards, + GUINT_TO_POINTER (index)); + + g_signal_emit (G_OBJECT (control), + signals[CARD_REMOVED], + 0, + index); +} + +static void +remove_sink (GvcMixerControl *control, + guint index) +{ + GvcMixerStream *stream; + +#if 0 + g_debug ("Removing sink: index=%u", index); +#endif + + stream = g_hash_table_lookup (control->priv->sinks, + GUINT_TO_POINTER (index)); + if (stream == NULL) { + return; + } + g_hash_table_remove (control->priv->sinks, + GUINT_TO_POINTER (index)); + + remove_stream (control, stream); +} + +static void +remove_source (GvcMixerControl *control, + guint index) +{ + GvcMixerStream *stream; + +#if 0 + g_debug ("Removing source: index=%u", index); +#endif + + stream = g_hash_table_lookup (control->priv->sources, + GUINT_TO_POINTER (index)); + if (stream == NULL) { + return; + } + g_hash_table_remove (control->priv->sources, + GUINT_TO_POINTER (index)); + + remove_stream (control, stream); +} + +static void +remove_sink_input (GvcMixerControl *control, + guint index) +{ + GvcMixerStream *stream; + +#if 0 + g_debug ("Removing sink input: index=%u", index); +#endif + stream = g_hash_table_lookup (control->priv->sink_inputs, + GUINT_TO_POINTER (index)); + if (stream == NULL) { + return; + } + g_hash_table_remove (control->priv->sink_inputs, + GUINT_TO_POINTER (index)); + + remove_stream (control, stream); +} + +static void +remove_source_output (GvcMixerControl *control, + guint index) +{ + GvcMixerStream *stream; + +#if 0 + g_debug ("Removing source output: index=%u", index); +#endif + + stream = g_hash_table_lookup (control->priv->source_outputs, + GUINT_TO_POINTER (index)); + if (stream == NULL) { + return; + } + g_hash_table_remove (control->priv->source_outputs, + GUINT_TO_POINTER (index)); + + remove_stream (control, stream); +} + +static void +_pa_context_subscribe_cb (pa_context *context, + pa_subscription_event_type_t t, + uint32_t index, + void *userdata) +{ + GvcMixerControl *control = GVC_MIXER_CONTROL (userdata); + + switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) { + case PA_SUBSCRIPTION_EVENT_SINK: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + remove_sink (control, index); + } else { + req_update_sink_info (control, index); + } + break; + + case PA_SUBSCRIPTION_EVENT_SOURCE: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + remove_source (control, index); + } else { + req_update_source_info (control, index); + } + break; + + case PA_SUBSCRIPTION_EVENT_SINK_INPUT: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + remove_sink_input (control, index); + } else { + req_update_sink_input_info (control, index); + } + break; + + case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + remove_source_output (control, index); + } else { + req_update_source_output_info (control, index); + } + break; + + case PA_SUBSCRIPTION_EVENT_CLIENT: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + remove_client (control, index); + } else { + req_update_client_info (control, index); + } + break; + + case PA_SUBSCRIPTION_EVENT_SERVER: + req_update_server_info (control, index); + break; + + case PA_SUBSCRIPTION_EVENT_CARD: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + remove_card (control, index); + } else { + req_update_card (control, index); + } + break; + } +} + +static void +gvc_mixer_control_ready (GvcMixerControl *control) +{ + pa_operation *o; + + pa_context_set_subscribe_callback (control->priv->pa_context, + _pa_context_subscribe_cb, + control); + o = pa_context_subscribe (control->priv->pa_context, + (pa_subscription_mask_t) + (PA_SUBSCRIPTION_MASK_SINK| + PA_SUBSCRIPTION_MASK_SOURCE| + PA_SUBSCRIPTION_MASK_SINK_INPUT| + PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT| + PA_SUBSCRIPTION_MASK_CLIENT| + PA_SUBSCRIPTION_MASK_SERVER| + PA_SUBSCRIPTION_MASK_CARD), + NULL, + NULL); + + if (o == NULL) { + g_warning ("pa_context_subscribe() failed"); + return; + } + pa_operation_unref (o); + + req_update_server_info (control, -1); + req_update_client_info (control, -1); + req_update_sink_info (control, -1); + req_update_source_info (control, -1); + req_update_sink_input_info (control, -1); + req_update_source_output_info (control, -1); + req_update_card (control, -1); + + control->priv->n_outstanding = 6; + + /* This call is not always supported */ + o = pa_ext_stream_restore_read (control->priv->pa_context, + _pa_ext_stream_restore_read_cb, + control); + if (o != NULL) { + pa_operation_unref (o); + control->priv->n_outstanding++; + + pa_ext_stream_restore_set_subscribe_cb (control->priv->pa_context, + _pa_ext_stream_restore_subscribe_cb, + control); + + o = pa_ext_stream_restore_subscribe (control->priv->pa_context, + 1, + NULL, + NULL); + if (o != NULL) { + pa_operation_unref (o); + } + + } else { + g_debug ("Failed to initialized stream_restore extension: %s", + pa_strerror (pa_context_errno (control->priv->pa_context))); + } +} + +static void +gvc_mixer_new_pa_context (GvcMixerControl *self) +{ + pa_proplist *proplist; + + g_return_if_fail (self); + g_return_if_fail (!self->priv->pa_context); + + proplist = pa_proplist_new (); + pa_proplist_sets (proplist, + PA_PROP_APPLICATION_NAME, + self->priv->name); + pa_proplist_sets (proplist, + PA_PROP_APPLICATION_ID, + "org.gnome.VolumeControl"); + pa_proplist_sets (proplist, + PA_PROP_APPLICATION_ICON_NAME, + "multimedia-volume-control"); + pa_proplist_sets (proplist, + PA_PROP_APPLICATION_VERSION, + PACKAGE_VERSION); + + self->priv->pa_context = pa_context_new_with_proplist (self->priv->pa_api, NULL, proplist); + + pa_proplist_free (proplist); + g_assert (self->priv->pa_context); +} + +static void +remove_all_streams (GvcMixerControl *control, GHashTable *hash_table) +{ + GHashTableIter iter; + gpointer key, value; + + g_hash_table_iter_init (&iter, hash_table); + while (g_hash_table_iter_next (&iter, &key, &value)) { + remove_stream (control, value); + g_hash_table_iter_remove (&iter); + } +} + +static gboolean +idle_reconnect (gpointer data) +{ + GvcMixerControl *control = GVC_MIXER_CONTROL (data); + GHashTableIter iter; + gpointer key, value; + + g_return_val_if_fail (control, FALSE); + + if (control->priv->pa_context) { + pa_context_unref (control->priv->pa_context); + control->priv->pa_context = NULL; + gvc_mixer_new_pa_context (control); + } + + remove_all_streams (control, control->priv->sinks); + remove_all_streams (control, control->priv->sources); + remove_all_streams (control, control->priv->sink_inputs); + remove_all_streams (control, control->priv->source_outputs); + + g_hash_table_iter_init (&iter, control->priv->clients); + while (g_hash_table_iter_next (&iter, &key, &value)) + g_hash_table_iter_remove (&iter); + + gvc_mixer_control_open (control); /* cannot fail */ + + control->priv->reconnect_id = 0; + return FALSE; +} + +static void +_pa_context_state_cb (pa_context *context, + void *userdata) +{ + GvcMixerControl *control = GVC_MIXER_CONTROL (userdata); + + switch (pa_context_get_state (context)) { + case PA_CONTEXT_UNCONNECTED: + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + break; + + case PA_CONTEXT_READY: + gvc_mixer_control_ready (control); + break; + + case PA_CONTEXT_FAILED: + g_warning ("Connection failed, reconnecting..."); + if (control->priv->reconnect_id == 0) + control->priv->reconnect_id = g_timeout_add_seconds (RECONNECT_DELAY, idle_reconnect, control); + break; + + case PA_CONTEXT_TERMINATED: + default: + /* FIXME: */ + break; + } +} + +gboolean +gvc_mixer_control_open (GvcMixerControl *control) +{ + int res; + + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE); + g_return_val_if_fail (control->priv->pa_context != NULL, FALSE); + g_return_val_if_fail (pa_context_get_state (control->priv->pa_context) == PA_CONTEXT_UNCONNECTED, FALSE); + + pa_context_set_state_callback (control->priv->pa_context, + _pa_context_state_cb, + control); + + g_signal_emit (G_OBJECT (control), signals[CONNECTING], 0); + res = pa_context_connect (control->priv->pa_context, NULL, (pa_context_flags_t) PA_CONTEXT_NOFAIL, NULL); + if (res < 0) { + g_warning ("Failed to connect context: %s", + pa_strerror (pa_context_errno (control->priv->pa_context))); + } + + return res; +} + +gboolean +gvc_mixer_control_close (GvcMixerControl *control) +{ + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE); + g_return_val_if_fail (control->priv->pa_context != NULL, FALSE); + + pa_context_disconnect (control->priv->pa_context); + return TRUE; +} + +static void +gvc_mixer_control_dispose (GObject *object) +{ + GvcMixerControl *control = GVC_MIXER_CONTROL (object); + + if (control->priv->pa_context != NULL) { + pa_context_unref (control->priv->pa_context); + control->priv->pa_context = NULL; + } + + if (control->priv->default_source_name != NULL) { + g_free (control->priv->default_source_name); + control->priv->default_source_name = NULL; + } + if (control->priv->default_sink_name != NULL) { + g_free (control->priv->default_sink_name); + control->priv->default_sink_name = NULL; + } + + if (control->priv->pa_mainloop != NULL) { + pa_glib_mainloop_free (control->priv->pa_mainloop); + control->priv->pa_mainloop = NULL; + } + + if (control->priv->all_streams != NULL) { + g_hash_table_destroy (control->priv->all_streams); + control->priv->all_streams = NULL; + } + + if (control->priv->sinks != NULL) { + g_hash_table_destroy (control->priv->sinks); + control->priv->sinks = NULL; + } + if (control->priv->sources != NULL) { + g_hash_table_destroy (control->priv->sources); + control->priv->sources = NULL; + } + if (control->priv->sink_inputs != NULL) { + g_hash_table_destroy (control->priv->sink_inputs); + control->priv->sink_inputs = NULL; + } + if (control->priv->source_outputs != NULL) { + g_hash_table_destroy (control->priv->source_outputs); + control->priv->source_outputs = NULL; + } + if (control->priv->clients != NULL) { + g_hash_table_destroy (control->priv->clients); + control->priv->clients = NULL; + } + if (control->priv->cards != NULL) { + g_hash_table_destroy (control->priv->cards); + control->priv->cards = NULL; + } + + G_OBJECT_CLASS (gvc_mixer_control_parent_class)->dispose (object); +} + +static void +gvc_mixer_control_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GvcMixerControl *self = GVC_MIXER_CONTROL (object); + + switch (prop_id) { + case PROP_NAME: + g_free (self->priv->name); + self->priv->name = g_value_dup_string (value); + g_object_notify (G_OBJECT (self), "name"); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gvc_mixer_control_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GvcMixerControl *self = GVC_MIXER_CONTROL (object); + + switch (prop_id) { + case PROP_NAME: + g_value_set_string (value, self->priv->name); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + + +static GObject * +gvc_mixer_control_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_params) +{ + GObject *object; + GvcMixerControl *self; + + object = G_OBJECT_CLASS (gvc_mixer_control_parent_class)->constructor (type, n_construct_properties, construct_params); + + self = GVC_MIXER_CONTROL (object); + + gvc_mixer_new_pa_context (self); + + return object; +} + +static void +gvc_mixer_control_class_init (GvcMixerControlClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructor = gvc_mixer_control_constructor; + object_class->dispose = gvc_mixer_control_dispose; + object_class->finalize = gvc_mixer_control_finalize; + object_class->set_property = gvc_mixer_control_set_property; + object_class->get_property = gvc_mixer_control_get_property; + + g_object_class_install_property (object_class, + PROP_NAME, + g_param_spec_string ("name", + "Name", + "Name to display for this mixer control", + NULL, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY)); + + signals [CONNECTING] = + g_signal_new ("connecting", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GvcMixerControlClass, connecting), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + signals [READY] = + g_signal_new ("ready", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GvcMixerControlClass, ready), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + signals [STREAM_ADDED] = + g_signal_new ("stream-added", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GvcMixerControlClass, stream_added), + NULL, NULL, + g_cclosure_marshal_VOID__UINT, + G_TYPE_NONE, 1, G_TYPE_UINT); + signals [STREAM_REMOVED] = + g_signal_new ("stream-removed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GvcMixerControlClass, stream_removed), + NULL, NULL, + g_cclosure_marshal_VOID__UINT, + G_TYPE_NONE, 1, G_TYPE_UINT); + signals [CARD_ADDED] = + g_signal_new ("card-added", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GvcMixerControlClass, card_added), + NULL, NULL, + g_cclosure_marshal_VOID__UINT, + G_TYPE_NONE, 1, G_TYPE_UINT); + signals [CARD_REMOVED] = + g_signal_new ("card-removed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GvcMixerControlClass, card_removed), + NULL, NULL, + g_cclosure_marshal_VOID__UINT, + G_TYPE_NONE, 1, G_TYPE_UINT); + signals [DEFAULT_SINK_CHANGED] = + g_signal_new ("default-sink-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GvcMixerControlClass, default_sink_changed), + NULL, NULL, + g_cclosure_marshal_VOID__UINT, + G_TYPE_NONE, 1, G_TYPE_UINT); + signals [DEFAULT_SOURCE_CHANGED] = + g_signal_new ("default-source-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GvcMixerControlClass, default_source_changed), + NULL, NULL, + g_cclosure_marshal_VOID__UINT, + G_TYPE_NONE, 1, G_TYPE_UINT); + + g_type_class_add_private (klass, sizeof (GvcMixerControlPrivate)); +} + +static void +gvc_mixer_control_init (GvcMixerControl *control) +{ + control->priv = GVC_MIXER_CONTROL_GET_PRIVATE (control); + + control->priv->pa_mainloop = pa_glib_mainloop_new (g_main_context_default ()); + g_assert (control->priv->pa_mainloop); + + control->priv->pa_api = pa_glib_mainloop_get_api (control->priv->pa_mainloop); + g_assert (control->priv->pa_api); + + control->priv->all_streams = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref); + control->priv->sinks = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref); + control->priv->sources = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref); + control->priv->sink_inputs = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref); + control->priv->source_outputs = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref); + control->priv->cards = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref); + + control->priv->clients = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_free); +} + +static void +gvc_mixer_control_finalize (GObject *object) +{ + GvcMixerControl *mixer_control; + + g_return_if_fail (object != NULL); + g_return_if_fail (GVC_IS_MIXER_CONTROL (object)); + + mixer_control = GVC_MIXER_CONTROL (object); + g_free (mixer_control->priv->name); + mixer_control->priv->name = NULL; + + g_return_if_fail (mixer_control->priv != NULL); + G_OBJECT_CLASS (gvc_mixer_control_parent_class)->finalize (object); +} + +GvcMixerControl * +gvc_mixer_control_new (const char *name) +{ + GObject *control; + control = g_object_new (GVC_TYPE_MIXER_CONTROL, + "name", name, + NULL); + return GVC_MIXER_CONTROL (control); +} diff --git a/panels/sound/gvc-mixer-control.h b/panels/sound/gvc-mixer-control.h new file mode 100644 index 000000000..d32b20493 --- /dev/null +++ b/panels/sound/gvc-mixer-control.h @@ -0,0 +1,96 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Red Hat, Inc. + * + * 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. + * + */ + +#ifndef __GVC_MIXER_CONTROL_H +#define __GVC_MIXER_CONTROL_H + +#include +#include "gvc-mixer-stream.h" +#include "gvc-mixer-card.h" + +G_BEGIN_DECLS + +#define GVC_TYPE_MIXER_CONTROL (gvc_mixer_control_get_type ()) +#define GVC_MIXER_CONTROL(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GVC_TYPE_MIXER_CONTROL, GvcMixerControl)) +#define GVC_MIXER_CONTROL_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GVC_TYPE_MIXER_CONTROL, GvcMixerControlClass)) +#define GVC_IS_MIXER_CONTROL(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVC_TYPE_MIXER_CONTROL)) +#define GVC_IS_MIXER_CONTROL_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GVC_TYPE_MIXER_CONTROL)) +#define GVC_MIXER_CONTROL_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVC_TYPE_MIXER_CONTROL, GvcMixerControlClass)) + +typedef struct GvcMixerControlPrivate GvcMixerControlPrivate; + +typedef struct +{ + GObject parent; + GvcMixerControlPrivate *priv; +} GvcMixerControl; + +typedef struct +{ + GObjectClass parent_class; + + void (*connecting) (GvcMixerControl *control); + void (*ready) (GvcMixerControl *control); + void (*stream_added) (GvcMixerControl *control, + guint id); + void (*stream_removed) (GvcMixerControl *control, + guint id); + void (*card_added) (GvcMixerControl *control, + guint id); + void (*card_removed) (GvcMixerControl *control, + guint id); + void (*default_sink_changed) (GvcMixerControl *control, + guint id); + void (*default_source_changed) (GvcMixerControl *control, + guint id); +} GvcMixerControlClass; + +GType gvc_mixer_control_get_type (void); + +GvcMixerControl * gvc_mixer_control_new (const char *name); + +gboolean gvc_mixer_control_open (GvcMixerControl *control); +gboolean gvc_mixer_control_close (GvcMixerControl *control); +gboolean gvc_mixer_control_is_ready (GvcMixerControl *control); + +GSList * gvc_mixer_control_get_cards (GvcMixerControl *control); +GSList * gvc_mixer_control_get_streams (GvcMixerControl *control); +GSList * gvc_mixer_control_get_sinks (GvcMixerControl *control); +GSList * gvc_mixer_control_get_sources (GvcMixerControl *control); +GSList * gvc_mixer_control_get_sink_inputs (GvcMixerControl *control); +GSList * gvc_mixer_control_get_source_outputs (GvcMixerControl *control); + +GvcMixerStream * gvc_mixer_control_lookup_stream_id (GvcMixerControl *control, + guint id); +GvcMixerCard * gvc_mixer_control_lookup_card_id (GvcMixerControl *control, + guint id); + +GvcMixerStream * gvc_mixer_control_get_default_sink (GvcMixerControl *control); +GvcMixerStream * gvc_mixer_control_get_default_source (GvcMixerControl *control); +GvcMixerStream * gvc_mixer_control_get_event_sink_input (GvcMixerControl *control); + +gboolean gvc_mixer_control_set_default_sink (GvcMixerControl *control, + GvcMixerStream *stream); +gboolean gvc_mixer_control_set_default_source (GvcMixerControl *control, + GvcMixerStream *stream); + +G_END_DECLS + +#endif /* __GVC_MIXER_CONTROL_H */ diff --git a/panels/sound/gvc-mixer-dialog.c b/panels/sound/gvc-mixer-dialog.c new file mode 100644 index 000000000..fa4ca3b5b --- /dev/null +++ b/panels/sound/gvc-mixer-dialog.c @@ -0,0 +1,2091 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 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 + +#include "gvc-channel-bar.h" +#include "gvc-balance-bar.h" +#include "gvc-combo-box.h" +#include "gvc-mixer-control.h" +#include "gvc-mixer-card.h" +#include "gvc-mixer-sink.h" +#include "gvc-mixer-source.h" +#include "gvc-mixer-source-output.h" +#include "gvc-mixer-dialog.h" +#include "gvc-sound-theme-chooser.h" +#include "gvc-level-bar.h" +#include "gvc-speaker-test.h" +#include "gvc-mixer-control-private.h" + +#define SCALE_SIZE 128 + +#define GVC_MIXER_DIALOG_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_MIXER_DIALOG, GvcMixerDialogPrivate)) + +struct GvcMixerDialogPrivate +{ + GvcMixerControl *mixer_control; + GHashTable *bars; + GtkWidget *notebook; + GtkWidget *output_bar; + GtkWidget *input_bar; + GtkWidget *input_level_bar; + GtkWidget *effects_bar; + GtkWidget *output_stream_box; + GtkWidget *sound_effects_box; + GtkWidget *hw_box; + GtkWidget *hw_treeview; + GtkWidget *hw_settings_box; + GtkWidget *hw_profile_combo; + GtkWidget *input_box; + GtkWidget *output_box; + GtkWidget *applications_box; + GtkWidget *no_apps_label; + GtkWidget *output_treeview; + GtkWidget *output_settings_box; + GtkWidget *output_balance_bar; + GtkWidget *output_fade_bar; + GtkWidget *output_lfe_bar; + GtkWidget *output_port_combo; + GtkWidget *input_treeview; + GtkWidget *input_port_combo; + GtkWidget *input_settings_box; + GtkWidget *sound_theme_chooser; + GtkWidget *click_feedback_button; + GtkWidget *audible_bell_button; + GtkSizeGroup *size_group; + + gdouble last_input_peak; + guint num_apps; +}; + +enum { + NAME_COLUMN, + DEVICE_COLUMN, + ACTIVE_COLUMN, + ID_COLUMN, + SPEAKERS_COLUMN, + NUM_COLUMNS +}; + +enum { + HW_ID_COLUMN, + HW_ICON_COLUMN, + HW_NAME_COLUMN, + HW_STATUS_COLUMN, + HW_PROFILE_COLUMN, + HW_PROFILE_HUMAN_COLUMN, + HW_SENSITIVE_COLUMN, + HW_NUM_COLUMNS +}; + +enum +{ + PROP_0, + PROP_MIXER_CONTROL +}; + +static void gvc_mixer_dialog_class_init (GvcMixerDialogClass *klass); +static void gvc_mixer_dialog_init (GvcMixerDialog *mixer_dialog); +static void gvc_mixer_dialog_finalize (GObject *object); + +static void bar_set_stream (GvcMixerDialog *dialog, + GtkWidget *bar, + GvcMixerStream *stream); + +static void on_adjustment_value_changed (GtkAdjustment *adjustment, + GvcMixerDialog *dialog); + +G_DEFINE_TYPE (GvcMixerDialog, gvc_mixer_dialog, GTK_TYPE_VBOX) + +static void +update_default_input (GvcMixerDialog *dialog) +{ + GtkTreeModel *model; + GtkTreeIter iter; + gboolean ret; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->input_treeview)); + ret = gtk_tree_model_get_iter_first (model, &iter); + if (ret == FALSE) { + g_debug ("No default input selected or available"); + return; + } + do { + gboolean toggled; + gboolean is_default; + guint id; + GvcMixerStream *stream; + + gtk_tree_model_get (model, &iter, + ID_COLUMN, &id, + ACTIVE_COLUMN, &toggled, + -1); + + stream = gvc_mixer_control_lookup_stream_id (dialog->priv->mixer_control, id); + if (stream == NULL) { + g_warning ("Unable to find stream for id: %u", id); + continue; + } + + is_default = FALSE; + if (stream == gvc_mixer_control_get_default_source (dialog->priv->mixer_control)) { + is_default = TRUE; + } + + gtk_list_store_set (GTK_LIST_STORE (model), + &iter, + ACTIVE_COLUMN, is_default, + -1); + } while (gtk_tree_model_iter_next (model, &iter)); +} + +static void +update_description (GvcMixerDialog *dialog, + guint column, + const char *value, + GvcMixerStream *stream) +{ + GtkTreeModel *model; + GtkTreeIter iter; + guint id; + + if (GVC_IS_MIXER_SOURCE (stream)) + model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->input_treeview)); + else if (GVC_IS_MIXER_SINK (stream)) + model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->output_treeview)); + else + g_assert_not_reached (); + gtk_tree_model_get_iter_first (model, &iter); + + id = gvc_mixer_stream_get_id (stream); + do { + guint current_id; + + gtk_tree_model_get (model, &iter, + ID_COLUMN, ¤t_id, + -1); + if (id != current_id) + continue; + + gtk_list_store_set (GTK_LIST_STORE (model), + &iter, + column, value, + -1); + break; + } while (gtk_tree_model_iter_next (model, &iter)); +} + +static void +port_selection_changed (GvcComboBox *combo_box, + const char *port, + GvcMixerDialog *dialog) +{ + GvcMixerStream *stream; + + stream = g_object_get_data (G_OBJECT (combo_box), "stream"); + if (stream == NULL) { + g_warning ("Could not find stream for port combo box"); + return; + } + if (gvc_mixer_stream_change_port (stream, port) == FALSE) { + g_warning ("Could not change port for stream"); + } +} + +static void +update_output_settings (GvcMixerDialog *dialog) +{ + GvcMixerStream *stream; + const GvcChannelMap *map; + const GList *ports; + + g_debug ("Updating output settings"); + if (dialog->priv->output_balance_bar != NULL) { + gtk_container_remove (GTK_CONTAINER (dialog->priv->output_settings_box), + dialog->priv->output_balance_bar); + dialog->priv->output_balance_bar = NULL; + } + if (dialog->priv->output_fade_bar != NULL) { + gtk_container_remove (GTK_CONTAINER (dialog->priv->output_settings_box), + dialog->priv->output_fade_bar); + dialog->priv->output_fade_bar = NULL; + } + if (dialog->priv->output_lfe_bar != NULL) { + gtk_container_remove (GTK_CONTAINER (dialog->priv->output_settings_box), + dialog->priv->output_lfe_bar); + dialog->priv->output_lfe_bar = NULL; + } + if (dialog->priv->output_port_combo != NULL) { + gtk_container_remove (GTK_CONTAINER (dialog->priv->output_settings_box), + dialog->priv->output_port_combo); + dialog->priv->output_port_combo = NULL; + } + + stream = gvc_mixer_control_get_default_sink (dialog->priv->mixer_control); + if (stream == NULL) { + g_warning ("Default sink stream not found"); + return; + } + + gvc_channel_bar_set_base_volume (GVC_CHANNEL_BAR (dialog->priv->output_bar), + gvc_mixer_stream_get_base_volume (stream)); + gvc_channel_bar_set_is_amplified (GVC_CHANNEL_BAR (dialog->priv->output_bar), + gvc_mixer_stream_get_can_decibel (stream)); + + map = gvc_mixer_stream_get_channel_map (stream); + if (map == NULL) { + g_warning ("Default sink stream has no channel map"); + return; + } + + dialog->priv->output_balance_bar = gvc_balance_bar_new (map, BALANCE_TYPE_RL); + if (dialog->priv->size_group != NULL) { + gvc_balance_bar_set_size_group (GVC_BALANCE_BAR (dialog->priv->output_balance_bar), + dialog->priv->size_group, + TRUE); + } + gtk_box_pack_start (GTK_BOX (dialog->priv->output_settings_box), + dialog->priv->output_balance_bar, + FALSE, FALSE, 6); + gtk_widget_show (dialog->priv->output_balance_bar); + + if (gvc_channel_map_can_fade (map)) { + dialog->priv->output_fade_bar = gvc_balance_bar_new (map, BALANCE_TYPE_FR); + if (dialog->priv->size_group != NULL) { + gvc_balance_bar_set_size_group (GVC_BALANCE_BAR (dialog->priv->output_fade_bar), + dialog->priv->size_group, + TRUE); + } + gtk_box_pack_start (GTK_BOX (dialog->priv->output_settings_box), + dialog->priv->output_fade_bar, + FALSE, FALSE, 6); + gtk_widget_show (dialog->priv->output_fade_bar); + } + + if (gvc_channel_map_has_lfe (map)) { + dialog->priv->output_lfe_bar = gvc_balance_bar_new (map, BALANCE_TYPE_LFE); + if (dialog->priv->size_group != NULL) { + gvc_balance_bar_set_size_group (GVC_BALANCE_BAR (dialog->priv->output_lfe_bar), + dialog->priv->size_group, + TRUE); + } + gtk_box_pack_start (GTK_BOX (dialog->priv->output_settings_box), + dialog->priv->output_lfe_bar, + FALSE, FALSE, 6); + gtk_widget_show (dialog->priv->output_lfe_bar); + } + + ports = gvc_mixer_stream_get_ports (stream); + if (ports != NULL) { + const GvcMixerStreamPort *port; + port = gvc_mixer_stream_get_port (stream); + + dialog->priv->output_port_combo = gvc_combo_box_new (_("Co_nnector:")); + gvc_combo_box_set_ports (GVC_COMBO_BOX (dialog->priv->output_port_combo), + ports); + gvc_combo_box_set_active (GVC_COMBO_BOX (dialog->priv->output_port_combo), port->port); + g_object_set_data (G_OBJECT (dialog->priv->output_port_combo), "stream", stream); + g_signal_connect (G_OBJECT (dialog->priv->output_port_combo), "changed", + G_CALLBACK (port_selection_changed), dialog); + + gtk_box_pack_start (GTK_BOX (dialog->priv->output_settings_box), + dialog->priv->output_port_combo, + TRUE, FALSE, 6); + + gvc_combo_box_set_size_group (GVC_COMBO_BOX (dialog->priv->output_port_combo), dialog->priv->size_group, FALSE); + + gtk_widget_show (dialog->priv->output_port_combo); + } + + /* FIXME: We could make this into a "No settings" label instead */ + gtk_widget_set_sensitive (dialog->priv->output_balance_bar, gvc_channel_map_can_balance (map)); +} + +static void +update_default_output (GvcMixerDialog *dialog) +{ + GtkTreeModel *model; + GtkTreeIter iter; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->output_treeview)); + gtk_tree_model_get_iter_first (model, &iter); + do { + gboolean toggled; + gboolean is_default; + guint id; + GvcMixerStream *stream; + + gtk_tree_model_get (model, &iter, + ID_COLUMN, &id, + ACTIVE_COLUMN, &toggled, + -1); + + stream = gvc_mixer_control_lookup_stream_id (dialog->priv->mixer_control, id); + if (stream == NULL) { + g_warning ("Unable to find stream for id: %u", id); + continue; + } + + is_default = FALSE; + if (stream == gvc_mixer_control_get_default_sink (dialog->priv->mixer_control)) { + is_default = TRUE; + } + + gtk_list_store_set (GTK_LIST_STORE (model), + &iter, + ACTIVE_COLUMN, is_default, + -1); + } while (gtk_tree_model_iter_next (model, &iter)); +} + +static void +on_mixer_control_default_sink_changed (GvcMixerControl *control, + guint id, + GvcMixerDialog *dialog) +{ + GvcMixerStream *stream; + + g_debug ("GvcMixerDialog: default sink changed: %u", id); + + if (id == PA_INVALID_INDEX) + stream = NULL; + else + stream = gvc_mixer_control_lookup_stream_id (dialog->priv->mixer_control, + id); + bar_set_stream (dialog, dialog->priv->output_bar, stream); + + update_output_settings (dialog); + + update_default_output (dialog); +} + + +#define DECAY_STEP .15 + +static void +update_input_peak (GvcMixerDialog *dialog, + gdouble v) +{ + GtkAdjustment *adj; + + if (dialog->priv->last_input_peak >= DECAY_STEP) { + if (v < dialog->priv->last_input_peak - DECAY_STEP) { + v = dialog->priv->last_input_peak - DECAY_STEP; + } + } + + dialog->priv->last_input_peak = v; + + adj = gvc_level_bar_get_peak_adjustment (GVC_LEVEL_BAR (dialog->priv->input_level_bar)); + if (v >= 0) { + gtk_adjustment_set_value (adj, v); + } else { + gtk_adjustment_set_value (adj, 0.0); + } +} + +static void +update_input_meter (GvcMixerDialog *dialog, + uint32_t source_index, + uint32_t sink_input_idx, + double v) +{ + update_input_peak (dialog, v); +} + +static void +on_monitor_suspended_callback (pa_stream *s, + void *userdata) +{ + GvcMixerDialog *dialog; + + dialog = userdata; + + if (pa_stream_is_suspended (s)) { + g_debug ("Stream suspended"); + update_input_meter (dialog, + pa_stream_get_device_index (s), + PA_INVALID_INDEX, + -1); + } +} + +static void +on_monitor_read_callback (pa_stream *s, + size_t length, + void *userdata) +{ + GvcMixerDialog *dialog; + const void *data; + double v; + + dialog = userdata; + + if (pa_stream_peek (s, &data, &length) < 0) { + g_warning ("Failed to read data from stream"); + return; + } + + assert (length > 0); + assert (length % sizeof (float) == 0); + + v = ((const float *) data)[length / sizeof (float) -1]; + + pa_stream_drop (s); + + if (v < 0) { + v = 0; + } + if (v > 1) { + v = 1; + } + + update_input_meter (dialog, + pa_stream_get_device_index (s), + pa_stream_get_monitor_stream (s), + v); +} + +static void +create_monitor_stream_for_source (GvcMixerDialog *dialog, + GvcMixerStream *stream) +{ + pa_stream *s; + char t[16]; + pa_buffer_attr attr; + pa_sample_spec ss; + pa_context *context; + int res; + pa_proplist *proplist; + gboolean has_monitor; + + if (stream == NULL) { + return; + } + has_monitor = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (stream), "has-monitor")); + if (has_monitor != FALSE) { + return; + } + + g_debug ("Create monitor for %u", + gvc_mixer_stream_get_index (stream)); + + context = gvc_mixer_control_get_pa_context (dialog->priv->mixer_control); + + if (pa_context_get_server_protocol_version (context) < 13) { + return; + } + + ss.channels = 1; + ss.format = PA_SAMPLE_FLOAT32; + ss.rate = 25; + + memset (&attr, 0, sizeof (attr)); + attr.fragsize = sizeof (float); + attr.maxlength = (uint32_t) -1; + + snprintf (t, sizeof (t), "%u", gvc_mixer_stream_get_index (stream)); + + proplist = pa_proplist_new (); + pa_proplist_sets (proplist, PA_PROP_APPLICATION_ID, "org.gnome.VolumeControl"); + s = pa_stream_new_with_proplist (context, _("Peak detect"), &ss, NULL, proplist); + pa_proplist_free (proplist); + if (s == NULL) { + g_warning ("Failed to create monitoring stream"); + return; + } + + pa_stream_set_read_callback (s, on_monitor_read_callback, dialog); + pa_stream_set_suspended_callback (s, on_monitor_suspended_callback, dialog); + + res = pa_stream_connect_record (s, + t, + &attr, + (pa_stream_flags_t) (PA_STREAM_DONT_MOVE + |PA_STREAM_PEAK_DETECT + |PA_STREAM_ADJUST_LATENCY)); + if (res < 0) { + g_warning ("Failed to connect monitoring stream"); + pa_stream_unref (s); + } else { + g_object_set_data (G_OBJECT (stream), "has-monitor", GINT_TO_POINTER (TRUE)); + g_object_set_data (G_OBJECT (dialog->priv->input_level_bar), "pa_stream", s); + g_object_set_data (G_OBJECT (dialog->priv->input_level_bar), "stream", stream); + } +} + +static void +stop_monitor_stream_for_source (GvcMixerDialog *dialog) +{ + pa_stream *s; + pa_context *context; + int res; + GvcMixerStream *stream; + + s = g_object_get_data (G_OBJECT (dialog->priv->input_level_bar), "pa_stream"); + if (s == NULL) + return; + stream = g_object_get_data (G_OBJECT (dialog->priv->input_level_bar), "stream"); + g_assert (stream != NULL); + + g_debug ("Stopping monitor for %u", pa_stream_get_index (s)); + + context = gvc_mixer_control_get_pa_context (dialog->priv->mixer_control); + + if (pa_context_get_server_protocol_version (context) < 13) { + return; + } + + res = pa_stream_disconnect (s); + if (res == 0) + g_object_set_data (G_OBJECT (stream), "has-monitor", GINT_TO_POINTER (FALSE)); + g_object_set_data (G_OBJECT (dialog->priv->input_level_bar), "pa_stream", NULL); + g_object_set_data (G_OBJECT (dialog->priv->input_level_bar), "stream", NULL); +} + +static void +update_input_settings (GvcMixerDialog *dialog) +{ + const GList *ports; + GvcMixerStream *stream; + + g_debug ("Updating input settings"); + + stop_monitor_stream_for_source (dialog); + + if (dialog->priv->input_port_combo != NULL) { + gtk_container_remove (GTK_CONTAINER (dialog->priv->input_settings_box), + dialog->priv->input_port_combo); + dialog->priv->input_port_combo = NULL; + } + + stream = gvc_mixer_control_get_default_source (dialog->priv->mixer_control); + if (stream == NULL) { + g_debug ("Default source stream not found"); + return; + } + + gvc_channel_bar_set_base_volume (GVC_CHANNEL_BAR (dialog->priv->input_bar), + gvc_mixer_stream_get_base_volume (stream)); + gvc_channel_bar_set_is_amplified (GVC_CHANNEL_BAR (dialog->priv->input_bar), + gvc_mixer_stream_get_can_decibel (stream)); + + ports = gvc_mixer_stream_get_ports (stream); + if (ports != NULL) { + const GvcMixerStreamPort *port; + port = gvc_mixer_stream_get_port (stream); + + dialog->priv->input_port_combo = gvc_combo_box_new (_("Co_nnector:")); + gvc_combo_box_set_ports (GVC_COMBO_BOX (dialog->priv->input_port_combo), + ports); + gvc_combo_box_set_active (GVC_COMBO_BOX (dialog->priv->input_port_combo), port->port); + g_object_set_data (G_OBJECT (dialog->priv->input_port_combo), "stream", stream); + g_signal_connect (G_OBJECT (dialog->priv->input_port_combo), "changed", + G_CALLBACK (port_selection_changed), dialog); + + gvc_combo_box_set_size_group (GVC_COMBO_BOX (dialog->priv->input_port_combo), dialog->priv->size_group, FALSE); + gtk_box_pack_start (GTK_BOX (dialog->priv->input_settings_box), + dialog->priv->input_port_combo, + TRUE, TRUE, 0); + gtk_widget_show (dialog->priv->input_port_combo); + } + + create_monitor_stream_for_source (dialog, stream); +} + +static void +on_mixer_control_default_source_changed (GvcMixerControl *control, + guint id, + GvcMixerDialog *dialog) +{ + GvcMixerStream *stream; + GtkAdjustment *adj; + + g_debug ("GvcMixerDialog: default source changed: %u", id); + + if (id == PA_INVALID_INDEX) + stream = NULL; + else + stream = gvc_mixer_control_lookup_stream_id (dialog->priv->mixer_control, id); + + /* Disconnect the adj, otherwise it might change if is_amplified changes */ + adj = GTK_ADJUSTMENT (gvc_channel_bar_get_adjustment (GVC_CHANNEL_BAR (dialog->priv->input_bar))); + g_signal_handlers_disconnect_by_func(adj, on_adjustment_value_changed, dialog); + + bar_set_stream (dialog, dialog->priv->input_bar, stream); + update_input_settings (dialog); + + g_signal_connect (adj, + "value-changed", + G_CALLBACK (on_adjustment_value_changed), + dialog); + + update_default_input (dialog); +} + +static void +gvc_mixer_dialog_set_mixer_control (GvcMixerDialog *dialog, + GvcMixerControl *control) +{ + g_return_if_fail (GVC_MIXER_DIALOG (dialog)); + g_return_if_fail (GVC_IS_MIXER_CONTROL (control)); + + g_object_ref (control); + + if (dialog->priv->mixer_control != NULL) { + g_signal_handlers_disconnect_by_func (dialog->priv->mixer_control, + G_CALLBACK (on_mixer_control_default_sink_changed), + dialog); + g_signal_handlers_disconnect_by_func (dialog->priv->mixer_control, + G_CALLBACK (on_mixer_control_default_source_changed), + dialog); + g_object_unref (dialog->priv->mixer_control); + } + + dialog->priv->mixer_control = control; + + g_signal_connect (dialog->priv->mixer_control, + "default-sink-changed", + G_CALLBACK (on_mixer_control_default_sink_changed), + dialog); + g_signal_connect (dialog->priv->mixer_control, + "default-source-changed", + G_CALLBACK (on_mixer_control_default_source_changed), + dialog); + + g_object_notify (G_OBJECT (dialog), "mixer-control"); +} + +static GvcMixerControl * +gvc_mixer_dialog_get_mixer_control (GvcMixerDialog *dialog) +{ + g_return_val_if_fail (GVC_IS_MIXER_DIALOG (dialog), NULL); + + return dialog->priv->mixer_control; +} + +static void +gvc_mixer_dialog_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GvcMixerDialog *self = GVC_MIXER_DIALOG (object); + + switch (prop_id) { + case PROP_MIXER_CONTROL: + gvc_mixer_dialog_set_mixer_control (self, g_value_get_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gvc_mixer_dialog_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GvcMixerDialog *self = GVC_MIXER_DIALOG (object); + + switch (prop_id) { + case PROP_MIXER_CONTROL: + g_value_set_object (value, gvc_mixer_dialog_get_mixer_control (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +on_adjustment_value_changed (GtkAdjustment *adjustment, + GvcMixerDialog *dialog) +{ + GvcMixerStream *stream; + + stream = g_object_get_data (G_OBJECT (adjustment), "gvc-mixer-dialog-stream"); + if (stream != NULL) { + GObject *bar; + gdouble volume, rounded; + char *name; + + volume = gtk_adjustment_get_value (adjustment); + rounded = round (volume); + + bar = g_object_get_data (G_OBJECT (adjustment), "gvc-mixer-dialog-bar"); + g_object_get (bar, "name", &name, NULL); + g_debug ("Setting stream volume %lf (rounded: %lf) for bar '%s'", volume, rounded, name); + g_free (name); + + /* FIXME would need to do that in the balance bar really... */ + /* Make sure we do not unmute muted streams, there's a button for that */ + if (volume == 0.0) + gvc_mixer_stream_set_is_muted (stream, TRUE); + /* Only push the volume if it's actually changed */ + if (gvc_mixer_stream_set_volume(stream, (pa_volume_t) rounded) != FALSE) + gvc_mixer_stream_push_volume (stream); + } +} + +static void +on_bar_is_muted_notify (GObject *object, + GParamSpec *pspec, + GvcMixerDialog *dialog) +{ + gboolean is_muted; + GvcMixerStream *stream; + + is_muted = gvc_channel_bar_get_is_muted (GVC_CHANNEL_BAR (object)); + + stream = g_object_get_data (object, "gvc-mixer-dialog-stream"); + if (stream != NULL) { + gvc_mixer_stream_change_is_muted (stream, is_muted); + } else { + char *name; + g_object_get (object, "name", &name, NULL); + g_warning ("Unable to find stream for bar '%s'", name); + g_free (name); + } +} + +static GtkWidget * +lookup_bar_for_stream (GvcMixerDialog *dialog, + GvcMixerStream *stream) +{ + GtkWidget *bar; + + bar = g_hash_table_lookup (dialog->priv->bars, GUINT_TO_POINTER (gvc_mixer_stream_get_id (stream))); + + return bar; +} + +static GtkWidget * +lookup_combo_box_for_stream (GvcMixerDialog *dialog, + GvcMixerStream *stream) +{ + GvcMixerStream *combo_stream; + guint id; + + id = gvc_mixer_stream_get_id (stream); + + if (dialog->priv->output_port_combo != NULL) { + combo_stream = g_object_get_data (G_OBJECT (dialog->priv->output_port_combo), + "stream"); + if (combo_stream != NULL) { + if (id == gvc_mixer_stream_get_id (combo_stream)) + return dialog->priv->output_port_combo; + } + } + + if (dialog->priv->input_port_combo != NULL) { + combo_stream = g_object_get_data (G_OBJECT (dialog->priv->input_port_combo), + "stream"); + if (combo_stream != NULL) { + if (id == gvc_mixer_stream_get_id (combo_stream)) + return dialog->priv->input_port_combo; + } + } + + return NULL; +} + +static void +on_stream_description_notify (GvcMixerStream *stream, + GParamSpec *pspec, + GvcMixerDialog *dialog) +{ + update_description (dialog, NAME_COLUMN, + gvc_mixer_stream_get_description (stream), + stream); +} + +static void +on_stream_port_notify (GObject *object, + GParamSpec *pspec, + GvcMixerDialog *dialog) +{ + GvcComboBox *combo_box; + char *port; + + combo_box = GVC_COMBO_BOX (lookup_combo_box_for_stream (dialog, GVC_MIXER_STREAM (object))); + if (combo_box == NULL) + return; + + g_signal_handlers_block_by_func (G_OBJECT (combo_box), + port_selection_changed, + dialog); + + g_object_get (object, "port", &port, NULL); + gvc_combo_box_set_active (GVC_COMBO_BOX (combo_box), port); + + g_signal_handlers_unblock_by_func (G_OBJECT (combo_box), + port_selection_changed, + dialog); +} + +static void +on_stream_volume_notify (GObject *object, + GParamSpec *pspec, + GvcMixerDialog *dialog) +{ + GvcMixerStream *stream; + GtkWidget *bar; + GtkAdjustment *adj; + + stream = GVC_MIXER_STREAM (object); + + bar = lookup_bar_for_stream (dialog, stream); + + if (bar == NULL) { + g_warning ("Unable to find bar for stream %s in on_stream_volume_notify()", + gvc_mixer_stream_get_name (stream)); + return; + } + + adj = GTK_ADJUSTMENT (gvc_channel_bar_get_adjustment (GVC_CHANNEL_BAR (bar))); + + g_signal_handlers_block_by_func (adj, + on_adjustment_value_changed, + dialog); + + gtk_adjustment_set_value (adj, + gvc_mixer_stream_get_volume (stream)); + + g_signal_handlers_unblock_by_func (adj, + on_adjustment_value_changed, + dialog); +} + +static void +on_stream_is_muted_notify (GObject *object, + GParamSpec *pspec, + GvcMixerDialog *dialog) +{ + GvcMixerStream *stream; + GtkWidget *bar; + gboolean is_muted; + + stream = GVC_MIXER_STREAM (object); + bar = lookup_bar_for_stream (dialog, stream); + + if (bar == NULL) { + g_warning ("Unable to find bar for stream %s in on_stream_is_muted_notify()", + gvc_mixer_stream_get_name (stream)); + return; + } + + is_muted = gvc_mixer_stream_get_is_muted (stream); + gvc_channel_bar_set_is_muted (GVC_CHANNEL_BAR (bar), + is_muted); + + if (stream == gvc_mixer_control_get_default_sink (dialog->priv->mixer_control)) { + gtk_widget_set_sensitive (dialog->priv->applications_box, + !is_muted); + } + +} + +static void +save_bar_for_stream (GvcMixerDialog *dialog, + GvcMixerStream *stream, + GtkWidget *bar) +{ + g_hash_table_insert (dialog->priv->bars, + GUINT_TO_POINTER (gvc_mixer_stream_get_id (stream)), + bar); +} + +static GtkWidget * +create_bar (GvcMixerDialog *dialog, + gboolean symmetric) +{ + GtkWidget *bar; + + bar = gvc_channel_bar_new (); + gtk_widget_set_sensitive (bar, FALSE); + gvc_channel_bar_set_size_group (GVC_CHANNEL_BAR (bar), + dialog->priv->size_group, + symmetric); + gvc_channel_bar_set_orientation (GVC_CHANNEL_BAR (bar), + GTK_ORIENTATION_HORIZONTAL); + gvc_channel_bar_set_show_mute (GVC_CHANNEL_BAR (bar), + TRUE); + g_signal_connect (bar, + "notify::is-muted", + G_CALLBACK (on_bar_is_muted_notify), + dialog); + return bar; +} + +static GtkWidget * +create_app_bar (GvcMixerDialog *dialog, + const char *name, + const char *icon_name) +{ + GtkWidget *bar; + + bar = create_bar (dialog, FALSE); + gvc_channel_bar_set_ellipsize (GVC_CHANNEL_BAR (bar), TRUE); + gvc_channel_bar_set_icon_name (GVC_CHANNEL_BAR (bar), icon_name); + if (name == NULL || strchr (name, '_') == NULL) { + gvc_channel_bar_set_name (GVC_CHANNEL_BAR (bar), name); + } else { + char **tokens, *escaped; + + tokens = g_strsplit (name, "_", -1); + escaped = g_strjoinv ("__", tokens); + g_strfreev (tokens); + gvc_channel_bar_set_name (GVC_CHANNEL_BAR (bar), escaped); + g_free (escaped); + } + + return bar; +} + +static void +bar_set_stream (GvcMixerDialog *dialog, + GtkWidget *bar, + GvcMixerStream *stream) +{ + GtkAdjustment *adj; + GvcMixerStream *old_stream; + + g_assert (bar != NULL); + + old_stream = g_object_get_data (G_OBJECT (bar), "gvc-mixer-dialog-stream"); + if (old_stream != NULL) { + char *name; + + g_object_get (bar, "name", &name, NULL); + g_debug ("Disconnecting old stream '%s' from bar '%s'", + gvc_mixer_stream_get_name (old_stream), name); + g_free (name); + + g_signal_handlers_disconnect_by_func (old_stream, on_stream_is_muted_notify, dialog); + g_signal_handlers_disconnect_by_func (old_stream, on_stream_volume_notify, dialog); + g_signal_handlers_disconnect_by_func (old_stream, on_stream_port_notify, dialog); + g_hash_table_remove (dialog->priv->bars, GUINT_TO_POINTER (gvc_mixer_stream_get_id (old_stream))); + } + + gtk_widget_set_sensitive (bar, (stream != NULL)); + + adj = GTK_ADJUSTMENT (gvc_channel_bar_get_adjustment (GVC_CHANNEL_BAR (bar))); + + g_signal_handlers_disconnect_by_func (adj, on_adjustment_value_changed, dialog); + + g_object_set_data (G_OBJECT (bar), "gvc-mixer-dialog-stream", stream); + g_object_set_data (G_OBJECT (adj), "gvc-mixer-dialog-stream", stream); + g_object_set_data (G_OBJECT (adj), "gvc-mixer-dialog-bar", bar); + + if (stream != NULL) { + gboolean is_muted; + + is_muted = gvc_mixer_stream_get_is_muted (stream); + gvc_channel_bar_set_is_muted (GVC_CHANNEL_BAR (bar), is_muted); + + save_bar_for_stream (dialog, stream, bar); + + gtk_adjustment_set_value (adj, + gvc_mixer_stream_get_volume (stream)); + + g_signal_connect (stream, + "notify::is-muted", + G_CALLBACK (on_stream_is_muted_notify), + dialog); + g_signal_connect (stream, + "notify::volume", + G_CALLBACK (on_stream_volume_notify), + dialog); + g_signal_connect (stream, + "notify::port", + G_CALLBACK (on_stream_port_notify), + dialog); + g_signal_connect (adj, + "value-changed", + G_CALLBACK (on_adjustment_value_changed), + dialog); + } +} + +static void +add_stream (GvcMixerDialog *dialog, + GvcMixerStream *stream) +{ + GtkWidget *bar; + gboolean is_muted; + gboolean is_default; + GtkAdjustment *adj; + const char *id; + + g_assert (stream != NULL); + + if (gvc_mixer_stream_is_event_stream (stream) != FALSE) + return; + + bar = NULL; + is_default = FALSE; + id = gvc_mixer_stream_get_application_id (stream); + + if (stream == gvc_mixer_control_get_default_sink (dialog->priv->mixer_control)) { + bar = dialog->priv->output_bar; + is_muted = gvc_mixer_stream_get_is_muted (stream); + is_default = TRUE; + gtk_widget_set_sensitive (dialog->priv->applications_box, + !is_muted); + adj = GTK_ADJUSTMENT (gvc_channel_bar_get_adjustment (GVC_CHANNEL_BAR (bar))); + g_signal_handlers_disconnect_by_func(adj, on_adjustment_value_changed, dialog); + update_output_settings (dialog); + } else if (stream == gvc_mixer_control_get_default_source (dialog->priv->mixer_control)) { + bar = dialog->priv->input_bar; + adj = GTK_ADJUSTMENT (gvc_channel_bar_get_adjustment (GVC_CHANNEL_BAR (bar))); + g_signal_handlers_disconnect_by_func(adj, on_adjustment_value_changed, dialog); + update_input_settings (dialog); + is_default = TRUE; + } else if (stream == gvc_mixer_control_get_event_sink_input (dialog->priv->mixer_control)) { + bar = dialog->priv->effects_bar; + g_debug ("Adding effects stream"); + } else if (! GVC_IS_MIXER_SOURCE (stream) + && !GVC_IS_MIXER_SINK (stream) + && !gvc_mixer_stream_is_virtual (stream) + && g_strcmp0 (id, "org.gnome.VolumeControl") != 0 + && g_strcmp0 (id, "org.PulseAudio.pavucontrol") != 0) { + const char *name; + + name = gvc_mixer_stream_get_name (stream); + bar = create_app_bar (dialog, name, + gvc_mixer_stream_get_icon_name (stream)); + + gtk_box_pack_start (GTK_BOX (dialog->priv->applications_box), bar, FALSE, FALSE, 12); + dialog->priv->num_apps++; + gtk_widget_hide (dialog->priv->no_apps_label); + } + + if (GVC_IS_MIXER_SOURCE (stream)) { + GtkTreeModel *model; + GtkTreeIter iter; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->input_treeview)); + gtk_list_store_append (GTK_LIST_STORE (model), &iter); + gtk_list_store_set (GTK_LIST_STORE (model), + &iter, + NAME_COLUMN, gvc_mixer_stream_get_description (stream), + DEVICE_COLUMN, "", + ACTIVE_COLUMN, is_default, + ID_COLUMN, gvc_mixer_stream_get_id (stream), + -1); + g_signal_connect (stream, + "notify::description", + G_CALLBACK (on_stream_description_notify), + dialog); + } else if (GVC_IS_MIXER_SINK (stream)) { + GtkTreeModel *model; + GtkTreeIter iter; + const GvcChannelMap *map; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->output_treeview)); + gtk_list_store_append (GTK_LIST_STORE (model), &iter); + map = gvc_mixer_stream_get_channel_map (stream); + gtk_list_store_set (GTK_LIST_STORE (model), + &iter, + NAME_COLUMN, gvc_mixer_stream_get_description (stream), + DEVICE_COLUMN, "", + ACTIVE_COLUMN, is_default, + ID_COLUMN, gvc_mixer_stream_get_id (stream), + SPEAKERS_COLUMN, gvc_channel_map_get_mapping (map), + -1); + g_signal_connect (stream, + "notify::description", + G_CALLBACK (on_stream_description_notify), + dialog); + } + + if (bar != NULL) { + bar_set_stream (dialog, bar, stream); + gtk_widget_show (bar); + } +} + +static void +on_control_stream_added (GvcMixerControl *control, + guint id, + GvcMixerDialog *dialog) +{ + GvcMixerStream *stream; + GtkWidget *bar; + + bar = g_hash_table_lookup (dialog->priv->bars, GUINT_TO_POINTER (id)); + if (bar != NULL) { + g_debug ("GvcMixerDialog: Stream %u already added", id); + return; + } + + stream = gvc_mixer_control_lookup_stream_id (control, id); + if (stream != NULL) { + add_stream (dialog, stream); + } +} + +static gboolean +find_item_by_id (GtkTreeModel *model, + guint id, + guint column, + GtkTreeIter *iter) +{ + gboolean found_item; + + found_item = FALSE; + + if (!gtk_tree_model_get_iter_first (model, iter)) { + return FALSE; + } + + do { + guint t_id; + + gtk_tree_model_get (model, iter, + column, &t_id, -1); + + if (id == t_id) { + found_item = TRUE; + } + } while (!found_item && gtk_tree_model_iter_next (model, iter)); + + return found_item; +} + +static void +remove_stream (GvcMixerDialog *dialog, + guint id) +{ + GtkWidget *bar; + gboolean found; + GtkTreeIter iter; + GtkTreeModel *model; + + /* remove bars for applications and reset fixed bars */ + bar = g_hash_table_lookup (dialog->priv->bars, GUINT_TO_POINTER (id)); + if (bar == dialog->priv->output_bar + || bar == dialog->priv->input_bar + || bar == dialog->priv->effects_bar) { + char *name; + g_object_get (bar, "name", &name, NULL); + g_debug ("Removing stream for bar '%s'", name); + g_free (name); + bar_set_stream (dialog, bar, NULL); + } else if (bar != NULL) { + g_hash_table_remove (dialog->priv->bars, GUINT_TO_POINTER (id)); + gtk_container_remove (GTK_CONTAINER (gtk_widget_get_parent (bar)), + bar); + dialog->priv->num_apps--; + if (dialog->priv->num_apps == 0) { + gtk_widget_show (dialog->priv->no_apps_label); + } + } + + /* remove from any models */ + model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->output_treeview)); + found = find_item_by_id (GTK_TREE_MODEL (model), id, ID_COLUMN, &iter); + if (found) { + gtk_list_store_remove (GTK_LIST_STORE (model), &iter); + } + model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->input_treeview)); + found = find_item_by_id (GTK_TREE_MODEL (model), id, ID_COLUMN, &iter); + if (found) { + gtk_list_store_remove (GTK_LIST_STORE (model), &iter); + } +} + +static void +on_control_stream_removed (GvcMixerControl *control, + guint id, + GvcMixerDialog *dialog) +{ + remove_stream (dialog, id); +} + +static void +add_card (GvcMixerDialog *dialog, + GvcMixerCard *card) +{ + GtkTreeModel *model; + GtkTreeIter iter; + GtkTreeSelection *selection; + GvcMixerCardProfile *profile; + GIcon *icon; + guint index; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->hw_treeview)); + index = gvc_mixer_card_get_index (card); + if (find_item_by_id (GTK_TREE_MODEL (model), index, HW_ID_COLUMN, &iter) == FALSE) + gtk_list_store_append (GTK_LIST_STORE (model), &iter); + profile = gvc_mixer_card_get_profile (card); + g_assert (profile != NULL); + icon = g_themed_icon_new_with_default_fallbacks (gvc_mixer_card_get_icon_name (card)); + //FIXME we need the status (default for a profile?) here + gtk_list_store_set (GTK_LIST_STORE (model), + &iter, + HW_NAME_COLUMN, gvc_mixer_card_get_name (card), + HW_ID_COLUMN, index, + HW_ICON_COLUMN, icon, + HW_PROFILE_COLUMN, profile->profile, + HW_PROFILE_HUMAN_COLUMN, profile->human_profile, + HW_STATUS_COLUMN, profile->status, + HW_SENSITIVE_COLUMN, g_strcmp0 (profile->profile, "off") != 0, + -1); + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dialog->priv->hw_treeview)); + if (gtk_tree_selection_get_selected (selection, NULL, NULL) == FALSE) { + gtk_tree_selection_select_iter (selection, &iter); + } else if (dialog->priv->hw_profile_combo != NULL) { + GvcMixerCard *selected; + + /* Set the current profile if it changed for the selected card */ + selected = g_object_get_data (G_OBJECT (dialog->priv->hw_profile_combo), "card"); + if (gvc_mixer_card_get_index (selected) == gvc_mixer_card_get_index (card)) { + gvc_combo_box_set_active (GVC_COMBO_BOX (dialog->priv->hw_profile_combo), + profile->profile); + g_object_set (G_OBJECT (dialog->priv->hw_profile_combo), + "show-button", profile->n_sinks == 1, + NULL); + } + } +} + +static void +on_control_card_added (GvcMixerControl *control, + guint id, + GvcMixerDialog *dialog) +{ + GvcMixerCard *card; + + card = gvc_mixer_control_lookup_card_id (control, id); + if (card != NULL) { + add_card (dialog, card); + } +} + +static void +remove_card (GvcMixerDialog *dialog, + guint id) +{ + gboolean found; + GtkTreeIter iter; + GtkTreeModel *model; + + /* remove from any models */ + model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->hw_treeview)); + found = find_item_by_id (GTK_TREE_MODEL (model), id, HW_ID_COLUMN, &iter); + if (found) { + gtk_list_store_remove (GTK_LIST_STORE (model), &iter); + } +} +static void +on_control_card_removed (GvcMixerControl *control, + guint id, + GvcMixerDialog *dialog) +{ + remove_card (dialog, id); +} + +static void +_gtk_label_make_bold (GtkLabel *label) +{ + PangoFontDescription *font_desc; + + font_desc = pango_font_description_new (); + + pango_font_description_set_weight (font_desc, + PANGO_WEIGHT_BOLD); + + /* This will only affect the weight of the font, the rest is + * from the current state of the widget, which comes from the + * theme or user prefs, since the font desc only has the + * weight flag turned on. + */ + gtk_widget_modify_font (GTK_WIDGET (label), font_desc); + + pango_font_description_free (font_desc); +} + +static void +on_input_radio_toggled (GtkCellRendererToggle *renderer, + char *path_str, + GvcMixerDialog *dialog) +{ + GtkTreeModel *model; + GtkTreeIter iter; + GtkTreePath *path; + gboolean toggled; + guint id; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->input_treeview)); + + path = gtk_tree_path_new_from_string (path_str); + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_path_free (path); + + gtk_tree_model_get (model, &iter, + ID_COLUMN, &id, + ACTIVE_COLUMN, &toggled, + -1); + + toggled ^= 1; + if (toggled) { + GvcMixerStream *stream; + + g_debug ("Default input selected: %u", id); + stream = gvc_mixer_control_lookup_stream_id (dialog->priv->mixer_control, id); + if (stream == NULL) { + g_warning ("Unable to find stream for id: %u", id); + return; + } + + gvc_mixer_control_set_default_source (dialog->priv->mixer_control, stream); + } +} + +static void +on_output_radio_toggled (GtkCellRendererToggle *renderer, + char *path_str, + GvcMixerDialog *dialog) +{ + GtkTreeModel *model; + GtkTreeIter iter; + GtkTreePath *path; + gboolean toggled; + guint id; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->output_treeview)); + + path = gtk_tree_path_new_from_string (path_str); + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_path_free (path); + + gtk_tree_model_get (model, &iter, + ID_COLUMN, &id, + ACTIVE_COLUMN, &toggled, + -1); + + toggled ^= 1; + if (toggled) { + GvcMixerStream *stream; + + g_debug ("Default output selected: %u", id); + stream = gvc_mixer_control_lookup_stream_id (dialog->priv->mixer_control, id); + if (stream == NULL) { + g_warning ("Unable to find stream for id: %u", id); + return; + } + + gvc_mixer_control_set_default_sink (dialog->priv->mixer_control, stream); + } +} + +static void +name_to_text (GtkTreeViewColumn *column, + GtkCellRenderer *cell, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer user_data) +{ + char *name, *mapping; + + gtk_tree_model_get(model, iter, + NAME_COLUMN, &name, + SPEAKERS_COLUMN, &mapping, + -1); + + if (mapping == NULL) { + g_object_set (cell, "text", name, NULL); + } else { + char *str; + + str = g_strdup_printf ("%s\n%s", + name, mapping); + g_object_set (cell, "markup", str, NULL); + g_free (str); + } + + g_free (name); + g_free (mapping); +} + +static GtkWidget * +create_stream_treeview (GvcMixerDialog *dialog, + GCallback on_toggled) +{ + GtkWidget *treeview; + GtkListStore *store; + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + + treeview = gtk_tree_view_new (); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview), FALSE); + + store = gtk_list_store_new (NUM_COLUMNS, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_BOOLEAN, + G_TYPE_UINT, + G_TYPE_STRING); + gtk_tree_view_set_model (GTK_TREE_VIEW (treeview), + GTK_TREE_MODEL (store)); + + renderer = gtk_cell_renderer_toggle_new (); + gtk_cell_renderer_toggle_set_radio (GTK_CELL_RENDERER_TOGGLE (renderer), + TRUE); + column = gtk_tree_view_column_new_with_attributes (NULL, + renderer, + "active", ACTIVE_COLUMN, + NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column); + g_signal_connect (renderer, + "toggled", + G_CALLBACK (on_toggled), + dialog); + + gtk_tree_view_insert_column_with_data_func (GTK_TREE_VIEW (treeview), -1, + _("Name"), gtk_cell_renderer_text_new (), + name_to_text, NULL, NULL); + +#if 0 + renderer = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes (_("Device"), + renderer, + "text", DEVICE_COLUMN, + NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column); +#endif + return treeview; +} + +static void +on_profile_changed (GvcComboBox *widget, + const char *profile, + gpointer user_data) +{ + GvcMixerCard *card; + + card = g_object_get_data (G_OBJECT (widget), "card"); + if (card == NULL) { + g_warning ("Could not find card for combobox"); + return; + } + + g_debug ("Profile changed to %s for card %s", profile, + gvc_mixer_card_get_name (card)); + + gvc_mixer_card_change_profile (card, profile); +} + +static void +on_test_speakers_clicked (GvcComboBox *widget, + gpointer user_data) +{ + GvcMixerDialog *dialog = GVC_MIXER_DIALOG (user_data); + GvcMixerCard *card; + GvcMixerCardProfile *profile; + GtkWidget *d, *speaker_test, *container; + char *title; + + card = g_object_get_data (G_OBJECT (widget), "card"); + if (card == NULL) { + g_warning ("Could not find card for combobox"); + return; + } + profile = gvc_mixer_card_get_profile (card); + + g_debug ("XXX Start speaker testing for profile '%s', card %s XXX", + profile->profile, gvc_mixer_card_get_name (card)); + + title = g_strdup_printf (_("Speaker Testing for %s"), gvc_mixer_card_get_name (card)); + // FIXME + // set parent dialogue + // https://bugzilla.gnome.org/show_bug.cgi?id=621940 + d = gtk_dialog_new_with_buttons (title, + NULL, + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + NULL); + g_free (title); + speaker_test = gvc_speaker_test_new (dialog->priv->mixer_control, + card); + gtk_widget_show (speaker_test); + container = gtk_dialog_get_content_area (GTK_DIALOG (d)); + gtk_container_add (GTK_CONTAINER (container), speaker_test); + + gtk_dialog_run (GTK_DIALOG (d)); + gtk_widget_destroy (d); +} + +static void +on_card_selection_changed (GtkTreeSelection *selection, + gpointer user_data) +{ + GvcMixerDialog *dialog = GVC_MIXER_DIALOG (user_data); + GtkTreeModel *model; + GtkTreeIter iter; + const GList *profiles; + guint id; + GvcMixerCard *card; + GvcMixerCardProfile *current_profile; + + g_debug ("Card selection changed"); + + if (dialog->priv->hw_profile_combo != NULL) { + gtk_container_remove (GTK_CONTAINER (dialog->priv->hw_settings_box), + dialog->priv->hw_profile_combo); + dialog->priv->hw_profile_combo = NULL; + } + + if (gtk_tree_selection_get_selected (selection, + NULL, + &iter) == FALSE) { + return; + } + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->priv->hw_treeview)); + gtk_tree_model_get (model, &iter, + HW_ID_COLUMN, &id, + -1); + card = gvc_mixer_control_lookup_card_id (dialog->priv->mixer_control, id); + if (card == NULL) { + g_warning ("Unable to find card for id: %u", id); + return; + } + + current_profile = gvc_mixer_card_get_profile (card); + profiles = gvc_mixer_card_get_profiles (card); + dialog->priv->hw_profile_combo = gvc_combo_box_new (_("_Profile:")); + g_object_set (G_OBJECT (dialog->priv->hw_profile_combo), "button-label", _("Test Speakers"), NULL); + gvc_combo_box_set_profiles (GVC_COMBO_BOX (dialog->priv->hw_profile_combo), profiles); + gvc_combo_box_set_active (GVC_COMBO_BOX (dialog->priv->hw_profile_combo), current_profile->profile); + + gtk_box_pack_start (GTK_BOX (dialog->priv->hw_settings_box), + dialog->priv->hw_profile_combo, + TRUE, TRUE, 6); + g_object_set (G_OBJECT (dialog->priv->hw_profile_combo), + "show-button", current_profile->n_sinks == 1, + NULL); + gtk_widget_show (dialog->priv->hw_profile_combo); + + g_object_set_data (G_OBJECT (dialog->priv->hw_profile_combo), "card", card); + g_signal_connect (G_OBJECT (dialog->priv->hw_profile_combo), "changed", + G_CALLBACK (on_profile_changed), dialog); + g_signal_connect (G_OBJECT (dialog->priv->hw_profile_combo), "button-clicked", + G_CALLBACK (on_test_speakers_clicked), dialog); +} + +static void +card_to_text (GtkTreeViewColumn *column, + GtkCellRenderer *cell, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer user_data) +{ + char *name, *status, *profile, *str; + gboolean sensitive; + + gtk_tree_model_get(model, iter, + HW_NAME_COLUMN, &name, + HW_STATUS_COLUMN, &status, + HW_PROFILE_HUMAN_COLUMN, &profile, + HW_SENSITIVE_COLUMN, &sensitive, + -1); + + str = g_strdup_printf ("%s\n%s\n%s", + name, status, profile); + g_object_set (cell, + "markup", str, + "sensitive", sensitive, + NULL); + g_free (str); + + g_free (name); + g_free (status); + g_free (profile); +} + +static GtkWidget * +create_cards_treeview (GvcMixerDialog *dialog, + GCallback on_changed) +{ + GtkWidget *treeview; + GtkListStore *store; + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + GtkTreeSelection *selection; + + treeview = gtk_tree_view_new (); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview), FALSE); + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview)); + g_signal_connect (G_OBJECT (selection), "changed", + on_changed, dialog); + + store = gtk_list_store_new (HW_NUM_COLUMNS, + G_TYPE_UINT, + G_TYPE_ICON, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_BOOLEAN); + gtk_tree_view_set_model (GTK_TREE_VIEW (treeview), + GTK_TREE_MODEL (store)); + + renderer = gtk_cell_renderer_pixbuf_new (); + g_object_set (G_OBJECT (renderer), "stock-size", GTK_ICON_SIZE_DIALOG, NULL); + column = gtk_tree_view_column_new_with_attributes (NULL, + renderer, + "gicon", HW_ICON_COLUMN, + "sensitive", HW_SENSITIVE_COLUMN, + NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column); + + gtk_tree_view_insert_column_with_data_func (GTK_TREE_VIEW (treeview), -1, + _("Name"), gtk_cell_renderer_text_new (), + card_to_text, NULL, NULL); + + return treeview; +} + +static GObject * +gvc_mixer_dialog_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_params) +{ + GObject *object; + GvcMixerDialog *self; + GtkWidget *main_vbox; + GtkWidget *label; + GtkWidget *alignment; + GtkWidget *box; + GtkWidget *sbox; + GtkWidget *ebox; + GSList *streams; + GSList *cards; + GSList *l; + GvcMixerStream *stream; + GvcMixerCard *card; + GtkTreeSelection *selection; + + object = G_OBJECT_CLASS (gvc_mixer_dialog_parent_class)->constructor (type, n_construct_properties, construct_params); + + self = GVC_MIXER_DIALOG (object); + + main_vbox = GTK_WIDGET (self); + gtk_box_set_spacing (GTK_BOX (main_vbox), 2); + + gtk_container_set_border_width (GTK_CONTAINER (self), 6); + + self->priv->output_stream_box = gtk_hbox_new (FALSE, 12); + alignment = gtk_alignment_new (0, 0, 1, 1); + gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 12, 0, 0, 0); + gtk_container_add (GTK_CONTAINER (alignment), self->priv->output_stream_box); + gtk_box_pack_start (GTK_BOX (main_vbox), + alignment, + FALSE, FALSE, 0); + self->priv->output_bar = create_bar (self, TRUE); + gvc_channel_bar_set_name (GVC_CHANNEL_BAR (self->priv->output_bar), + _("_Output volume: ")); + gtk_widget_set_sensitive (self->priv->output_bar, FALSE); + gtk_box_pack_start (GTK_BOX (self->priv->output_stream_box), + self->priv->output_bar, TRUE, TRUE, 12); + + self->priv->notebook = gtk_notebook_new (); + gtk_box_pack_start (GTK_BOX (main_vbox), + self->priv->notebook, + TRUE, TRUE, 0); + gtk_container_set_border_width (GTK_CONTAINER (self->priv->notebook), 5); + + /* Effects page */ + self->priv->sound_effects_box = gtk_vbox_new (FALSE, 6); + gtk_container_set_border_width (GTK_CONTAINER (self->priv->sound_effects_box), 12); + label = gtk_label_new (_("Sound Effects")); + gtk_notebook_append_page (GTK_NOTEBOOK (self->priv->notebook), + self->priv->sound_effects_box, + label); + + self->priv->effects_bar = create_bar (self, TRUE); + gvc_channel_bar_set_name (GVC_CHANNEL_BAR (self->priv->effects_bar), + _("_Alert volume: ")); + gtk_widget_set_sensitive (self->priv->effects_bar, FALSE); + gtk_box_pack_start (GTK_BOX (self->priv->sound_effects_box), + self->priv->effects_bar, FALSE, FALSE, 0); + + self->priv->sound_theme_chooser = gvc_sound_theme_chooser_new (); + gtk_box_pack_start (GTK_BOX (self->priv->sound_effects_box), + self->priv->sound_theme_chooser, + TRUE, TRUE, 6); + + /* Hardware page */ + self->priv->hw_box = gtk_vbox_new (FALSE, 12); + gtk_container_set_border_width (GTK_CONTAINER (self->priv->hw_box), 12); + label = gtk_label_new (_("Hardware")); + gtk_notebook_append_page (GTK_NOTEBOOK (self->priv->notebook), + self->priv->hw_box, + label); + + box = gtk_frame_new (_("C_hoose a device to configure:")); + label = gtk_frame_get_label_widget (GTK_FRAME (box)); + _gtk_label_make_bold (GTK_LABEL (label)); + gtk_label_set_use_underline (GTK_LABEL (label), TRUE); + gtk_frame_set_shadow_type (GTK_FRAME (box), GTK_SHADOW_NONE); + gtk_box_pack_start (GTK_BOX (self->priv->hw_box), box, TRUE, TRUE, 0); + + alignment = gtk_alignment_new (0, 0, 1, 1); + gtk_container_add (GTK_CONTAINER (box), alignment); + gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 6, 0, 0, 0); + + self->priv->hw_treeview = create_cards_treeview (self, + G_CALLBACK (on_card_selection_changed)); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), self->priv->hw_treeview); + + box = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (box), + GTK_POLICY_NEVER, + GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (box), + GTK_SHADOW_IN); + gtk_container_add (GTK_CONTAINER (box), self->priv->hw_treeview); + gtk_container_add (GTK_CONTAINER (alignment), box); + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self->priv->hw_treeview)); + gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE); + + box = gtk_frame_new (_("Settings for the selected device:")); + label = gtk_frame_get_label_widget (GTK_FRAME (box)); + _gtk_label_make_bold (GTK_LABEL (label)); + gtk_frame_set_shadow_type (GTK_FRAME (box), GTK_SHADOW_NONE); + gtk_box_pack_start (GTK_BOX (self->priv->hw_box), box, FALSE, TRUE, 12); + self->priv->hw_settings_box = gtk_vbox_new (FALSE, 12); + gtk_container_add (GTK_CONTAINER (box), self->priv->hw_settings_box); + + /* Input page */ + self->priv->input_box = gtk_vbox_new (FALSE, 12); + gtk_container_set_border_width (GTK_CONTAINER (self->priv->input_box), 12); + label = gtk_label_new (_("Input")); + gtk_notebook_append_page (GTK_NOTEBOOK (self->priv->notebook), + self->priv->input_box, + label); + + self->priv->input_bar = create_bar (self, TRUE); + gvc_channel_bar_set_name (GVC_CHANNEL_BAR (self->priv->input_bar), + _("_Input volume: ")); + gvc_channel_bar_set_low_icon_name (GVC_CHANNEL_BAR (self->priv->input_bar), + "audio-input-microphone-low"); + gvc_channel_bar_set_high_icon_name (GVC_CHANNEL_BAR (self->priv->input_bar), + "audio-input-microphone-high"); + gtk_widget_set_sensitive (self->priv->input_bar, FALSE); + alignment = gtk_alignment_new (0, 0, 1, 1); + gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 6, 0, 0, 0); + gtk_container_add (GTK_CONTAINER (alignment), self->priv->input_bar); + gtk_box_pack_start (GTK_BOX (self->priv->input_box), + alignment, + FALSE, FALSE, 0); + + box = gtk_hbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX (self->priv->input_box), + box, + FALSE, FALSE, 6); + + sbox = gtk_hbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX (box), + sbox, + FALSE, FALSE, 0); + + label = gtk_label_new (_("Input level:")); + gtk_box_pack_start (GTK_BOX (sbox), + label, + FALSE, FALSE, 0); + gtk_size_group_add_widget (self->priv->size_group, sbox); + + self->priv->input_level_bar = gvc_level_bar_new (); + gvc_level_bar_set_orientation (GVC_LEVEL_BAR (self->priv->input_level_bar), + GTK_ORIENTATION_HORIZONTAL); + gvc_level_bar_set_scale (GVC_LEVEL_BAR (self->priv->input_level_bar), + GVC_LEVEL_SCALE_LINEAR); + gtk_box_pack_start (GTK_BOX (box), + self->priv->input_level_bar, + TRUE, TRUE, 6); + + ebox = gtk_hbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX (box), + ebox, + FALSE, FALSE, 0); + gtk_size_group_add_widget (self->priv->size_group, ebox); + + self->priv->input_settings_box = gtk_hbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX (self->priv->input_box), + self->priv->input_settings_box, + FALSE, FALSE, 0); + + box = gtk_frame_new (_("C_hoose a device for sound input:")); + label = gtk_frame_get_label_widget (GTK_FRAME (box)); + _gtk_label_make_bold (GTK_LABEL (label)); + gtk_label_set_use_underline (GTK_LABEL (label), TRUE); + gtk_frame_set_shadow_type (GTK_FRAME (box), GTK_SHADOW_NONE); + gtk_box_pack_start (GTK_BOX (self->priv->input_box), box, TRUE, TRUE, 0); + + alignment = gtk_alignment_new (0, 0, 1, 1); + gtk_container_add (GTK_CONTAINER (box), alignment); + gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 6, 0, 0, 0); + + self->priv->input_treeview = create_stream_treeview (self, + G_CALLBACK (on_input_radio_toggled)); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), self->priv->input_treeview); + + box = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (box), + GTK_POLICY_NEVER, + GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (box), + GTK_SHADOW_IN); + gtk_container_add (GTK_CONTAINER (box), self->priv->input_treeview); + gtk_container_add (GTK_CONTAINER (alignment), box); + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self->priv->input_treeview)); + gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE); + + /* Output page */ + self->priv->output_box = gtk_vbox_new (FALSE, 12); + gtk_container_set_border_width (GTK_CONTAINER (self->priv->output_box), 12); + label = gtk_label_new (_("Output")); + gtk_notebook_append_page (GTK_NOTEBOOK (self->priv->notebook), + self->priv->output_box, + label); + + box = gtk_frame_new (_("C_hoose a device for sound output:")); + label = gtk_frame_get_label_widget (GTK_FRAME (box)); + _gtk_label_make_bold (GTK_LABEL (label)); + gtk_label_set_use_underline (GTK_LABEL (label), TRUE); + gtk_frame_set_shadow_type (GTK_FRAME (box), GTK_SHADOW_NONE); + gtk_box_pack_start (GTK_BOX (self->priv->output_box), box, TRUE, TRUE, 0); + + alignment = gtk_alignment_new (0, 0, 1, 1); + gtk_container_add (GTK_CONTAINER (box), alignment); + gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 6, 0, 0, 0); + + self->priv->output_treeview = create_stream_treeview (self, + G_CALLBACK (on_output_radio_toggled)); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), self->priv->output_treeview); + + box = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (box), + GTK_POLICY_NEVER, + GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (box), + GTK_SHADOW_IN); + gtk_container_add (GTK_CONTAINER (box), self->priv->output_treeview); + gtk_container_add (GTK_CONTAINER (alignment), box); + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self->priv->output_treeview)); + gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE); + + box = gtk_frame_new (_("Settings for the selected device:")); + label = gtk_frame_get_label_widget (GTK_FRAME (box)); + _gtk_label_make_bold (GTK_LABEL (label)); + gtk_frame_set_shadow_type (GTK_FRAME (box), GTK_SHADOW_NONE); + gtk_box_pack_start (GTK_BOX (self->priv->output_box), box, FALSE, FALSE, 12); + self->priv->output_settings_box = gtk_vbox_new (FALSE, 0); + gtk_container_add (GTK_CONTAINER (box), self->priv->output_settings_box); + + /* Applications */ + self->priv->applications_box = gtk_vbox_new (FALSE, 12); + gtk_container_set_border_width (GTK_CONTAINER (self->priv->applications_box), 12); + label = gtk_label_new (_("Applications")); + gtk_notebook_append_page (GTK_NOTEBOOK (self->priv->notebook), + self->priv->applications_box, + label); + self->priv->no_apps_label = gtk_label_new (_("No application is currently playing or recording audio.")); + gtk_box_pack_start (GTK_BOX (self->priv->applications_box), + self->priv->no_apps_label, + TRUE, TRUE, 0); + + g_signal_connect (self->priv->mixer_control, + "stream-added", + G_CALLBACK (on_control_stream_added), + self); + g_signal_connect (self->priv->mixer_control, + "stream-removed", + G_CALLBACK (on_control_stream_removed), + self); + g_signal_connect (self->priv->mixer_control, + "card-added", + G_CALLBACK (on_control_card_added), + self); + g_signal_connect (self->priv->mixer_control, + "card-removed", + G_CALLBACK (on_control_card_removed), + self); + + gtk_widget_show_all (main_vbox); + + streams = gvc_mixer_control_get_streams (self->priv->mixer_control); + for (l = streams; l != NULL; l = l->next) { + stream = l->data; + add_stream (self, stream); + } + g_slist_free (streams); + + cards = gvc_mixer_control_get_cards (self->priv->mixer_control); + for (l = cards; l != NULL; l = l->next) { + card = l->data; + add_card (self, card); + } + g_slist_free (cards); + + return object; +} + +static void +gvc_mixer_dialog_dispose (GObject *object) +{ + GvcMixerDialog *dialog = GVC_MIXER_DIALOG (object); + + if (dialog->priv->mixer_control != NULL) { + g_signal_handlers_disconnect_by_func (dialog->priv->mixer_control, + on_control_stream_added, + dialog); + g_signal_handlers_disconnect_by_func (dialog->priv->mixer_control, + on_control_stream_removed, + dialog); + g_signal_handlers_disconnect_by_func (dialog->priv->mixer_control, + on_control_card_added, + dialog); + g_signal_handlers_disconnect_by_func (dialog->priv->mixer_control, + on_control_card_removed, + dialog); + + g_object_unref (dialog->priv->mixer_control); + dialog->priv->mixer_control = NULL; + } + + if (dialog->priv->bars != NULL) { + g_hash_table_destroy (dialog->priv->bars); + dialog->priv->bars = NULL; + } + + G_OBJECT_CLASS (gvc_mixer_dialog_parent_class)->dispose (object); +} + +static void +gvc_mixer_dialog_class_init (GvcMixerDialogClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructor = gvc_mixer_dialog_constructor; + object_class->dispose = gvc_mixer_dialog_dispose; + object_class->finalize = gvc_mixer_dialog_finalize; + object_class->set_property = gvc_mixer_dialog_set_property; + object_class->get_property = gvc_mixer_dialog_get_property; + + g_object_class_install_property (object_class, + PROP_MIXER_CONTROL, + g_param_spec_object ("mixer-control", + "mixer control", + "mixer control", + GVC_TYPE_MIXER_CONTROL, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + + g_type_class_add_private (klass, sizeof (GvcMixerDialogPrivate)); +} + + +static void +gvc_mixer_dialog_init (GvcMixerDialog *dialog) +{ + dialog->priv = GVC_MIXER_DIALOG_GET_PRIVATE (dialog); + dialog->priv->bars = g_hash_table_new (NULL, NULL); + dialog->priv->size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); +} + +static void +gvc_mixer_dialog_finalize (GObject *object) +{ + GvcMixerDialog *mixer_dialog; + + g_return_if_fail (object != NULL); + g_return_if_fail (GVC_IS_MIXER_DIALOG (object)); + + mixer_dialog = GVC_MIXER_DIALOG (object); + + g_return_if_fail (mixer_dialog->priv != NULL); + G_OBJECT_CLASS (gvc_mixer_dialog_parent_class)->finalize (object); +} + +GvcMixerDialog * +gvc_mixer_dialog_new (GvcMixerControl *control) +{ + GObject *dialog; + dialog = g_object_new (GVC_TYPE_MIXER_DIALOG, + "mixer-control", control, + NULL); + return GVC_MIXER_DIALOG (dialog); +} + +enum { + PAGE_EVENTS, + PAGE_HARDWARE, + PAGE_INPUT, + PAGE_OUTPUT, + PAGE_APPLICATIONS +}; + +gboolean +gvc_mixer_dialog_set_page (GvcMixerDialog *self, + const char *page) +{ + guint num; + + g_return_val_if_fail (self != NULL, FALSE); + + if (page == NULL) + num = 0; + else if (g_str_equal (page, "effects")) + num = PAGE_EVENTS; + else if (g_str_equal (page, "hardware")) + num = PAGE_HARDWARE; + else if (g_str_equal (page, "input")) + num = PAGE_INPUT; + else if (g_str_equal (page, "output")) + num = PAGE_OUTPUT; + else if (g_str_equal (page, "applications")) + num = PAGE_APPLICATIONS; + else + num = 0; + + gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook), num); + + return TRUE; +} diff --git a/panels/sound/gvc-mixer-dialog.h b/panels/sound/gvc-mixer-dialog.h new file mode 100644 index 000000000..e95a7c7b7 --- /dev/null +++ b/panels/sound/gvc-mixer-dialog.h @@ -0,0 +1,56 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Red Hat, Inc. + * + * 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. + * + */ + +#ifndef __GVC_MIXER_DIALOG_H +#define __GVC_MIXER_DIALOG_H + +#include +#include "gvc-mixer-control.h" + +G_BEGIN_DECLS + +#define GVC_TYPE_MIXER_DIALOG (gvc_mixer_dialog_get_type ()) +#define GVC_MIXER_DIALOG(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GVC_TYPE_MIXER_DIALOG, GvcMixerDialog)) +#define GVC_MIXER_DIALOG_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GVC_TYPE_MIXER_DIALOG, GvcMixerDialogClass)) +#define GVC_IS_MIXER_DIALOG(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVC_TYPE_MIXER_DIALOG)) +#define GVC_IS_MIXER_DIALOG_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GVC_TYPE_MIXER_DIALOG)) +#define GVC_MIXER_DIALOG_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVC_TYPE_MIXER_DIALOG, GvcMixerDialogClass)) + +typedef struct GvcMixerDialogPrivate GvcMixerDialogPrivate; + +typedef struct +{ + GtkVBox parent; + GvcMixerDialogPrivate *priv; +} GvcMixerDialog; + +typedef struct +{ + GtkVBoxClass parent_class; +} GvcMixerDialogClass; + +GType gvc_mixer_dialog_get_type (void); + +GvcMixerDialog * gvc_mixer_dialog_new (GvcMixerControl *control); +gboolean gvc_mixer_dialog_set_page (GvcMixerDialog *dialog, const gchar* page); + +G_END_DECLS + +#endif /* __GVC_MIXER_DIALOG_H */ diff --git a/panels/sound/gvc-mixer-event-role.c b/panels/sound/gvc-mixer-event-role.c new file mode 100644 index 000000000..7eb3d00d7 --- /dev/null +++ b/panels/sound/gvc-mixer-event-role.c @@ -0,0 +1,250 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 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 "gvc-mixer-event-role.h" +#include "gvc-mixer-stream-private.h" +#include "gvc-channel-map-private.h" + +#define GVC_MIXER_EVENT_ROLE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_MIXER_EVENT_ROLE, GvcMixerEventRolePrivate)) + +struct GvcMixerEventRolePrivate +{ + char *device; +}; + +enum +{ + PROP_0, + PROP_DEVICE +}; + +static void gvc_mixer_event_role_class_init (GvcMixerEventRoleClass *klass); +static void gvc_mixer_event_role_init (GvcMixerEventRole *mixer_event_role); +static void gvc_mixer_event_role_finalize (GObject *object); + +G_DEFINE_TYPE (GvcMixerEventRole, gvc_mixer_event_role, GVC_TYPE_MIXER_STREAM) + +static gboolean +update_settings (GvcMixerEventRole *role, + gboolean is_muted, + gpointer *op) +{ + pa_operation *o; + guint index; + const GvcChannelMap *map; + pa_context *context; + pa_ext_stream_restore_info info; + + index = gvc_mixer_stream_get_index (GVC_MIXER_STREAM (role)); + + map = gvc_mixer_stream_get_channel_map (GVC_MIXER_STREAM(role)); + + info.volume = *gvc_channel_map_get_cvolume(map); + info.name = "sink-input-by-media-role:event"; + info.channel_map = *gvc_channel_map_get_pa_channel_map(map); + info.device = role->priv->device; + info.mute = is_muted; + + context = gvc_mixer_stream_get_pa_context (GVC_MIXER_STREAM (role)); + + o = pa_ext_stream_restore_write (context, + PA_UPDATE_REPLACE, + &info, + 1, + TRUE, + NULL, + NULL); + + if (o == NULL) { + g_warning ("pa_ext_stream_restore_write() failed"); + return FALSE; + } + + if (op != NULL) + *op = o; + + return TRUE; +} + +static gboolean +gvc_mixer_event_role_push_volume (GvcMixerStream *stream, gpointer *op) +{ + return update_settings (GVC_MIXER_EVENT_ROLE (stream), + gvc_mixer_stream_get_is_muted (stream), op); +} + +static gboolean +gvc_mixer_event_role_change_is_muted (GvcMixerStream *stream, + gboolean is_muted) +{ + return update_settings (GVC_MIXER_EVENT_ROLE (stream), + is_muted, NULL); +} + +static gboolean +gvc_mixer_event_role_set_device (GvcMixerEventRole *role, + const char *device) +{ + g_return_val_if_fail (GVC_IS_MIXER_EVENT_ROLE (role), FALSE); + + g_free (role->priv->device); + role->priv->device = g_strdup (device); + g_object_notify (G_OBJECT (role), "device"); + + return TRUE; +} + +static void +gvc_mixer_event_role_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GvcMixerEventRole *self = GVC_MIXER_EVENT_ROLE (object); + + switch (prop_id) { + case PROP_DEVICE: + gvc_mixer_event_role_set_device (self, g_value_get_string (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gvc_mixer_event_role_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GvcMixerEventRole *self = GVC_MIXER_EVENT_ROLE (object); + + switch (prop_id) { + case PROP_DEVICE: + g_value_set_string (value, self->priv->device); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GObject * +gvc_mixer_event_role_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_params) +{ + GObject *object; + GvcMixerEventRole *self; + + object = G_OBJECT_CLASS (gvc_mixer_event_role_parent_class)->constructor (type, n_construct_properties, construct_params); + + self = GVC_MIXER_EVENT_ROLE (object); + + return object; +} + +static void +gvc_mixer_event_role_class_init (GvcMixerEventRoleClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GvcMixerStreamClass *stream_class = GVC_MIXER_STREAM_CLASS (klass); + + object_class->constructor = gvc_mixer_event_role_constructor; + object_class->finalize = gvc_mixer_event_role_finalize; + object_class->set_property = gvc_mixer_event_role_set_property; + object_class->get_property = gvc_mixer_event_role_get_property; + + stream_class->push_volume = gvc_mixer_event_role_push_volume; + stream_class->change_is_muted = gvc_mixer_event_role_change_is_muted; + + g_object_class_install_property (object_class, + PROP_DEVICE, + g_param_spec_string ("device", + "Device", + "Device", + NULL, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + + g_type_class_add_private (klass, sizeof (GvcMixerEventRolePrivate)); +} + +static void +gvc_mixer_event_role_init (GvcMixerEventRole *event_role) +{ + event_role->priv = GVC_MIXER_EVENT_ROLE_GET_PRIVATE (event_role); + +} + +static void +gvc_mixer_event_role_finalize (GObject *object) +{ + GvcMixerEventRole *mixer_event_role; + + g_return_if_fail (object != NULL); + g_return_if_fail (GVC_IS_MIXER_EVENT_ROLE (object)); + + mixer_event_role = GVC_MIXER_EVENT_ROLE (object); + + g_return_if_fail (mixer_event_role->priv != NULL); + + g_free (mixer_event_role->priv->device); + + G_OBJECT_CLASS (gvc_mixer_event_role_parent_class)->finalize (object); +} + +/** + * gvc_mixer_event_role_new: (skip) + * + * @context: + * @index: + * @channel_map: + * + * Returns: + */ +GvcMixerStream * +gvc_mixer_event_role_new (pa_context *context, + const char *device, + GvcChannelMap *channel_map) +{ + GObject *object; + + object = g_object_new (GVC_TYPE_MIXER_EVENT_ROLE, + "pa-context", context, + "index", 0, + "device", device, + "channel-map", channel_map, + NULL); + + return GVC_MIXER_STREAM (object); +} diff --git a/panels/sound/gvc-mixer-event-role.h b/panels/sound/gvc-mixer-event-role.h new file mode 100644 index 000000000..ab4c509e5 --- /dev/null +++ b/panels/sound/gvc-mixer-event-role.h @@ -0,0 +1,57 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Red Hat, Inc. + * + * 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. + * + */ + +#ifndef __GVC_MIXER_EVENT_ROLE_H +#define __GVC_MIXER_EVENT_ROLE_H + +#include +#include "gvc-mixer-stream.h" + +G_BEGIN_DECLS + +#define GVC_TYPE_MIXER_EVENT_ROLE (gvc_mixer_event_role_get_type ()) +#define GVC_MIXER_EVENT_ROLE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GVC_TYPE_MIXER_EVENT_ROLE, GvcMixerEventRole)) +#define GVC_MIXER_EVENT_ROLE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GVC_TYPE_MIXER_EVENT_ROLE, GvcMixerEventRoleClass)) +#define GVC_IS_MIXER_EVENT_ROLE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVC_TYPE_MIXER_EVENT_ROLE)) +#define GVC_IS_MIXER_EVENT_ROLE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GVC_TYPE_MIXER_EVENT_ROLE)) +#define GVC_MIXER_EVENT_ROLE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVC_TYPE_MIXER_EVENT_ROLE, GvcMixerEventRoleClass)) + +typedef struct GvcMixerEventRolePrivate GvcMixerEventRolePrivate; + +typedef struct +{ + GvcMixerStream parent; + GvcMixerEventRolePrivate *priv; +} GvcMixerEventRole; + +typedef struct +{ + GvcMixerStreamClass parent_class; +} GvcMixerEventRoleClass; + +GType gvc_mixer_event_role_get_type (void); + +GvcMixerStream * gvc_mixer_event_role_new (pa_context *context, + const char *device, + GvcChannelMap *channel_map); + +G_END_DECLS + +#endif /* __GVC_MIXER_EVENT_ROLE_H */ diff --git a/panels/sound/gvc-mixer-sink-input.c b/panels/sound/gvc-mixer-sink-input.c new file mode 100644 index 000000000..9429eca0a --- /dev/null +++ b/panels/sound/gvc-mixer-sink-input.c @@ -0,0 +1,199 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 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 "gvc-mixer-sink-input.h" +#include "gvc-mixer-stream-private.h" +#include "gvc-channel-map-private.h" + +#define GVC_MIXER_SINK_INPUT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_MIXER_SINK_INPUT, GvcMixerSinkInputPrivate)) + +struct GvcMixerSinkInputPrivate +{ + gpointer dummy; +}; + +static void gvc_mixer_sink_input_class_init (GvcMixerSinkInputClass *klass); +static void gvc_mixer_sink_input_init (GvcMixerSinkInput *mixer_sink_input); +static void gvc_mixer_sink_input_finalize (GObject *object); +static void gvc_mixer_sink_input_dispose (GObject *object); + +G_DEFINE_TYPE (GvcMixerSinkInput, gvc_mixer_sink_input, GVC_TYPE_MIXER_STREAM) + +static gboolean +gvc_mixer_sink_input_push_volume (GvcMixerStream *stream, gpointer *op) +{ + pa_operation *o; + guint index; + const GvcChannelMap *map; + pa_context *context; + const pa_cvolume *cv; + guint num_channels; + + index = gvc_mixer_stream_get_index (stream); + + map = gvc_mixer_stream_get_channel_map (stream); + num_channels = gvc_channel_map_get_num_channels (map); + + cv = gvc_channel_map_get_cvolume(map); + + context = gvc_mixer_stream_get_pa_context (stream); + + o = pa_context_set_sink_input_volume (context, + index, + cv, + NULL, + NULL); + + if (o == NULL) { + g_warning ("pa_context_set_sink_input_volume() failed"); + return FALSE; + } + + *op = o; + + return TRUE; +} + +static gboolean +gvc_mixer_sink_input_change_is_muted (GvcMixerStream *stream, + gboolean is_muted) +{ + pa_operation *o; + guint index; + pa_context *context; + + index = gvc_mixer_stream_get_index (stream); + context = gvc_mixer_stream_get_pa_context (stream); + + o = pa_context_set_sink_input_mute (context, + index, + is_muted, + NULL, + NULL); + + if (o == NULL) { + g_warning ("pa_context_set_sink_input_mute_by_index() failed"); + return FALSE; + } + + pa_operation_unref(o); + + return TRUE; +} + +static GObject * +gvc_mixer_sink_input_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_params) +{ + GObject *object; + GvcMixerSinkInput *self; + + object = G_OBJECT_CLASS (gvc_mixer_sink_input_parent_class)->constructor (type, n_construct_properties, construct_params); + + self = GVC_MIXER_SINK_INPUT (object); + + return object; +} + +static void +gvc_mixer_sink_input_class_init (GvcMixerSinkInputClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GvcMixerStreamClass *stream_class = GVC_MIXER_STREAM_CLASS (klass); + + object_class->constructor = gvc_mixer_sink_input_constructor; + object_class->dispose = gvc_mixer_sink_input_dispose; + object_class->finalize = gvc_mixer_sink_input_finalize; + + stream_class->push_volume = gvc_mixer_sink_input_push_volume; + stream_class->change_is_muted = gvc_mixer_sink_input_change_is_muted; + + g_type_class_add_private (klass, sizeof (GvcMixerSinkInputPrivate)); +} + +static void +gvc_mixer_sink_input_init (GvcMixerSinkInput *sink_input) +{ + sink_input->priv = GVC_MIXER_SINK_INPUT_GET_PRIVATE (sink_input); +} + +static void +gvc_mixer_sink_input_dispose (GObject *object) +{ + GvcMixerSinkInput *mixer_sink_input; + + g_return_if_fail (object != NULL); + g_return_if_fail (GVC_IS_MIXER_SINK_INPUT (object)); + + mixer_sink_input = GVC_MIXER_SINK_INPUT (object); + + G_OBJECT_CLASS (gvc_mixer_sink_input_parent_class)->dispose (object); +} + +static void +gvc_mixer_sink_input_finalize (GObject *object) +{ + GvcMixerSinkInput *mixer_sink_input; + + g_return_if_fail (object != NULL); + g_return_if_fail (GVC_IS_MIXER_SINK_INPUT (object)); + + mixer_sink_input = GVC_MIXER_SINK_INPUT (object); + + g_return_if_fail (mixer_sink_input->priv != NULL); + G_OBJECT_CLASS (gvc_mixer_sink_input_parent_class)->finalize (object); +} + +/** + * gvc_mixer_sink_input_new: (skip) + * + * @context: + * @index: + * @channel_map: + * + * Returns: + */ +GvcMixerStream * +gvc_mixer_sink_input_new (pa_context *context, + guint index, + GvcChannelMap *channel_map) +{ + GObject *object; + + object = g_object_new (GVC_TYPE_MIXER_SINK_INPUT, + "pa-context", context, + "index", index, + "channel-map", channel_map, + NULL); + + return GVC_MIXER_STREAM (object); +} diff --git a/panels/sound/gvc-mixer-sink-input.h b/panels/sound/gvc-mixer-sink-input.h new file mode 100644 index 000000000..8a4b71454 --- /dev/null +++ b/panels/sound/gvc-mixer-sink-input.h @@ -0,0 +1,57 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Red Hat, Inc. + * + * 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. + * + */ + +#ifndef __GVC_MIXER_SINK_INPUT_H +#define __GVC_MIXER_SINK_INPUT_H + +#include +#include "gvc-mixer-stream.h" + +G_BEGIN_DECLS + +#define GVC_TYPE_MIXER_SINK_INPUT (gvc_mixer_sink_input_get_type ()) +#define GVC_MIXER_SINK_INPUT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GVC_TYPE_MIXER_SINK_INPUT, GvcMixerSinkInput)) +#define GVC_MIXER_SINK_INPUT_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GVC_TYPE_MIXER_SINK_INPUT, GvcMixerSinkInputClass)) +#define GVC_IS_MIXER_SINK_INPUT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVC_TYPE_MIXER_SINK_INPUT)) +#define GVC_IS_MIXER_SINK_INPUT_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GVC_TYPE_MIXER_SINK_INPUT)) +#define GVC_MIXER_SINK_INPUT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVC_TYPE_MIXER_SINK_INPUT, GvcMixerSinkInputClass)) + +typedef struct GvcMixerSinkInputPrivate GvcMixerSinkInputPrivate; + +typedef struct +{ + GvcMixerStream parent; + GvcMixerSinkInputPrivate *priv; +} GvcMixerSinkInput; + +typedef struct +{ + GvcMixerStreamClass parent_class; +} GvcMixerSinkInputClass; + +GType gvc_mixer_sink_input_get_type (void); + +GvcMixerStream * gvc_mixer_sink_input_new (pa_context *context, + guint index, + GvcChannelMap *map); + +G_END_DECLS + +#endif /* __GVC_MIXER_SINK_INPUT_H */ diff --git a/panels/sound/gvc-mixer-sink.c b/panels/sound/gvc-mixer-sink.c new file mode 100644 index 000000000..30fceac4d --- /dev/null +++ b/panels/sound/gvc-mixer-sink.c @@ -0,0 +1,231 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 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 "gvc-mixer-sink.h" +#include "gvc-mixer-stream-private.h" +#include "gvc-channel-map-private.h" + +#define GVC_MIXER_SINK_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_MIXER_SINK, GvcMixerSinkPrivate)) + +struct GvcMixerSinkPrivate +{ + gpointer dummy; +}; + +static void gvc_mixer_sink_class_init (GvcMixerSinkClass *klass); +static void gvc_mixer_sink_init (GvcMixerSink *mixer_sink); +static void gvc_mixer_sink_finalize (GObject *object); +static void gvc_mixer_sink_dispose (GObject *object); + +G_DEFINE_TYPE (GvcMixerSink, gvc_mixer_sink, GVC_TYPE_MIXER_STREAM) + +static gboolean +gvc_mixer_sink_push_volume (GvcMixerStream *stream, gpointer *op) +{ + pa_operation *o; + guint index; + const GvcChannelMap *map; + pa_context *context; + const pa_cvolume *cv; + + index = gvc_mixer_stream_get_index (stream); + + map = gvc_mixer_stream_get_channel_map (stream); + + /* set the volume */ + cv = gvc_channel_map_get_cvolume(map); + + context = gvc_mixer_stream_get_pa_context (stream); + + o = pa_context_set_sink_volume_by_index (context, + index, + cv, + NULL, + NULL); + + if (o == NULL) { + g_warning ("pa_context_set_sink_volume_by_index() failed: %s", pa_strerror(pa_context_errno(context))); + return FALSE; + } + + *op = o; + + return TRUE; +} + +static gboolean +gvc_mixer_sink_change_is_muted (GvcMixerStream *stream, + gboolean is_muted) +{ + pa_operation *o; + guint index; + pa_context *context; + + index = gvc_mixer_stream_get_index (stream); + context = gvc_mixer_stream_get_pa_context (stream); + + o = pa_context_set_sink_mute_by_index (context, + index, + is_muted, + NULL, + NULL); + + if (o == NULL) { + g_warning ("pa_context_set_sink_mute_by_index() failed: %s", pa_strerror(pa_context_errno(context))); + return FALSE; + } + + pa_operation_unref(o); + + return TRUE; +} + +static gboolean +gvc_mixer_sink_change_port (GvcMixerStream *stream, + const char *port) +{ +#if PA_MICRO > 15 + pa_operation *o; + guint index; + pa_context *context; + + index = gvc_mixer_stream_get_index (stream); + context = gvc_mixer_stream_get_pa_context (stream); + + o = pa_context_set_sink_port_by_index (context, + index, + port, + NULL, + NULL); + + if (o == NULL) { + g_warning ("pa_context_set_sink_port_by_index() failed: %s", pa_strerror(pa_context_errno(context))); + return FALSE; + } + + pa_operation_unref(o); + + return TRUE; +#else + return FALSE; +#endif /* PA_MICRO > 15 */ +} + +static GObject * +gvc_mixer_sink_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_params) +{ + GObject *object; + GvcMixerSink *self; + + object = G_OBJECT_CLASS (gvc_mixer_sink_parent_class)->constructor (type, n_construct_properties, construct_params); + + self = GVC_MIXER_SINK (object); + + return object; +} + +static void +gvc_mixer_sink_class_init (GvcMixerSinkClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GvcMixerStreamClass *stream_class = GVC_MIXER_STREAM_CLASS (klass); + + object_class->constructor = gvc_mixer_sink_constructor; + object_class->dispose = gvc_mixer_sink_dispose; + object_class->finalize = gvc_mixer_sink_finalize; + + stream_class->push_volume = gvc_mixer_sink_push_volume; + stream_class->change_port = gvc_mixer_sink_change_port; + stream_class->change_is_muted = gvc_mixer_sink_change_is_muted; + + g_type_class_add_private (klass, sizeof (GvcMixerSinkPrivate)); +} + +static void +gvc_mixer_sink_init (GvcMixerSink *sink) +{ + sink->priv = GVC_MIXER_SINK_GET_PRIVATE (sink); +} + +static void +gvc_mixer_sink_dispose (GObject *object) +{ + GvcMixerSink *mixer_sink; + + g_return_if_fail (object != NULL); + g_return_if_fail (GVC_IS_MIXER_SINK (object)); + + mixer_sink = GVC_MIXER_SINK (object); + + G_OBJECT_CLASS (gvc_mixer_sink_parent_class)->dispose (object); +} + +static void +gvc_mixer_sink_finalize (GObject *object) +{ + GvcMixerSink *mixer_sink; + + g_return_if_fail (object != NULL); + g_return_if_fail (GVC_IS_MIXER_SINK (object)); + + mixer_sink = GVC_MIXER_SINK (object); + + g_return_if_fail (mixer_sink->priv != NULL); + G_OBJECT_CLASS (gvc_mixer_sink_parent_class)->finalize (object); +} + +/** + * gvc_mixer_sink_new: (skip) + * + * @context: + * @index: + * @channel_map: + * + * Returns: + */ +GvcMixerStream * +gvc_mixer_sink_new (pa_context *context, + guint index, + GvcChannelMap *channel_map) + +{ + GObject *object; + + object = g_object_new (GVC_TYPE_MIXER_SINK, + "pa-context", context, + "index", index, + "channel-map", channel_map, + NULL); + + return GVC_MIXER_STREAM (object); +} diff --git a/panels/sound/gvc-mixer-sink.h b/panels/sound/gvc-mixer-sink.h new file mode 100644 index 000000000..2a4a4badb --- /dev/null +++ b/panels/sound/gvc-mixer-sink.h @@ -0,0 +1,57 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Red Hat, Inc. + * + * 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. + * + */ + +#ifndef __GVC_MIXER_SINK_H +#define __GVC_MIXER_SINK_H + +#include +#include "gvc-mixer-stream.h" + +G_BEGIN_DECLS + +#define GVC_TYPE_MIXER_SINK (gvc_mixer_sink_get_type ()) +#define GVC_MIXER_SINK(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GVC_TYPE_MIXER_SINK, GvcMixerSink)) +#define GVC_MIXER_SINK_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GVC_TYPE_MIXER_SINK, GvcMixerSinkClass)) +#define GVC_IS_MIXER_SINK(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVC_TYPE_MIXER_SINK)) +#define GVC_IS_MIXER_SINK_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GVC_TYPE_MIXER_SINK)) +#define GVC_MIXER_SINK_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVC_TYPE_MIXER_SINK, GvcMixerSinkClass)) + +typedef struct GvcMixerSinkPrivate GvcMixerSinkPrivate; + +typedef struct +{ + GvcMixerStream parent; + GvcMixerSinkPrivate *priv; +} GvcMixerSink; + +typedef struct +{ + GvcMixerStreamClass parent_class; +} GvcMixerSinkClass; + +GType gvc_mixer_sink_get_type (void); + +GvcMixerStream * gvc_mixer_sink_new (pa_context *context, + guint index, + GvcChannelMap *map); + +G_END_DECLS + +#endif /* __GVC_MIXER_SINK_H */ diff --git a/panels/sound/gvc-mixer-source-output.c b/panels/sound/gvc-mixer-source-output.c new file mode 100644 index 000000000..636fc2ea7 --- /dev/null +++ b/panels/sound/gvc-mixer-source-output.c @@ -0,0 +1,137 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 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 "gvc-mixer-source-output.h" + +#define GVC_MIXER_SOURCE_OUTPUT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_MIXER_SOURCE_OUTPUT, GvcMixerSourceOutputPrivate)) + +struct GvcMixerSourceOutputPrivate +{ + gpointer dummy; +}; + +static void gvc_mixer_source_output_class_init (GvcMixerSourceOutputClass *klass); +static void gvc_mixer_source_output_init (GvcMixerSourceOutput *mixer_source_output); +static void gvc_mixer_source_output_finalize (GObject *object); + +G_DEFINE_TYPE (GvcMixerSourceOutput, gvc_mixer_source_output, GVC_TYPE_MIXER_STREAM) + +static gboolean +gvc_mixer_source_output_push_volume (GvcMixerStream *stream, gpointer *op) +{ + /* FIXME: */ + *op = NULL; + return TRUE; +} + +static gboolean +gvc_mixer_source_output_change_is_muted (GvcMixerStream *stream, + gboolean is_muted) +{ + /* FIXME: */ + return TRUE; +} + +static GObject * +gvc_mixer_source_output_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_params) +{ + GObject *object; + GvcMixerSourceOutput *self; + + object = G_OBJECT_CLASS (gvc_mixer_source_output_parent_class)->constructor (type, n_construct_properties, construct_params); + + self = GVC_MIXER_SOURCE_OUTPUT (object); + + return object; +} + +static void +gvc_mixer_source_output_class_init (GvcMixerSourceOutputClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GvcMixerStreamClass *stream_class = GVC_MIXER_STREAM_CLASS (klass); + + object_class->constructor = gvc_mixer_source_output_constructor; + object_class->finalize = gvc_mixer_source_output_finalize; + + stream_class->push_volume = gvc_mixer_source_output_push_volume; + stream_class->change_is_muted = gvc_mixer_source_output_change_is_muted; + + g_type_class_add_private (klass, sizeof (GvcMixerSourceOutputPrivate)); +} + +static void +gvc_mixer_source_output_init (GvcMixerSourceOutput *source_output) +{ + source_output->priv = GVC_MIXER_SOURCE_OUTPUT_GET_PRIVATE (source_output); + +} + +static void +gvc_mixer_source_output_finalize (GObject *object) +{ + GvcMixerSourceOutput *mixer_source_output; + + g_return_if_fail (object != NULL); + g_return_if_fail (GVC_IS_MIXER_SOURCE_OUTPUT (object)); + + mixer_source_output = GVC_MIXER_SOURCE_OUTPUT (object); + + g_return_if_fail (mixer_source_output->priv != NULL); + G_OBJECT_CLASS (gvc_mixer_source_output_parent_class)->finalize (object); +} + +/** + * gvc_mixer_source_output_new: (skip) + * + * @context: + * @index: + * @channel_map: + * + * Returns: + */ +GvcMixerStream * +gvc_mixer_source_output_new (pa_context *context, + guint index, + GvcChannelMap *channel_map) +{ + GObject *object; + + object = g_object_new (GVC_TYPE_MIXER_SOURCE_OUTPUT, + "pa-context", context, + "index", index, + "channel-map", channel_map, + NULL); + + return GVC_MIXER_STREAM (object); +} diff --git a/panels/sound/gvc-mixer-source-output.h b/panels/sound/gvc-mixer-source-output.h new file mode 100644 index 000000000..2283e3b67 --- /dev/null +++ b/panels/sound/gvc-mixer-source-output.h @@ -0,0 +1,57 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Red Hat, Inc. + * + * 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. + * + */ + +#ifndef __GVC_MIXER_SOURCE_OUTPUT_H +#define __GVC_MIXER_SOURCE_OUTPUT_H + +#include +#include "gvc-mixer-stream.h" + +G_BEGIN_DECLS + +#define GVC_TYPE_MIXER_SOURCE_OUTPUT (gvc_mixer_source_output_get_type ()) +#define GVC_MIXER_SOURCE_OUTPUT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GVC_TYPE_MIXER_SOURCE_OUTPUT, GvcMixerSourceOutput)) +#define GVC_MIXER_SOURCE_OUTPUT_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GVC_TYPE_MIXER_SOURCE_OUTPUT, GvcMixerSourceOutputClass)) +#define GVC_IS_MIXER_SOURCE_OUTPUT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVC_TYPE_MIXER_SOURCE_OUTPUT)) +#define GVC_IS_MIXER_SOURCE_OUTPUT_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GVC_TYPE_MIXER_SOURCE_OUTPUT)) +#define GVC_MIXER_SOURCE_OUTPUT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVC_TYPE_MIXER_SOURCE_OUTPUT, GvcMixerSourceOutputClass)) + +typedef struct GvcMixerSourceOutputPrivate GvcMixerSourceOutputPrivate; + +typedef struct +{ + GvcMixerStream parent; + GvcMixerSourceOutputPrivate *priv; +} GvcMixerSourceOutput; + +typedef struct +{ + GvcMixerStreamClass parent_class; +} GvcMixerSourceOutputClass; + +GType gvc_mixer_source_output_get_type (void); + +GvcMixerStream * gvc_mixer_source_output_new (pa_context *context, + guint index, + GvcChannelMap *map); + +G_END_DECLS + +#endif /* __GVC_MIXER_SOURCE_OUTPUT_H */ diff --git a/panels/sound/gvc-mixer-source.c b/panels/sound/gvc-mixer-source.c new file mode 100644 index 000000000..46d640380 --- /dev/null +++ b/panels/sound/gvc-mixer-source.c @@ -0,0 +1,231 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 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 "gvc-mixer-source.h" +#include "gvc-mixer-stream-private.h" +#include "gvc-channel-map-private.h" + +#define GVC_MIXER_SOURCE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_MIXER_SOURCE, GvcMixerSourcePrivate)) + +struct GvcMixerSourcePrivate +{ + gpointer dummy; +}; + +static void gvc_mixer_source_class_init (GvcMixerSourceClass *klass); +static void gvc_mixer_source_init (GvcMixerSource *mixer_source); +static void gvc_mixer_source_finalize (GObject *object); +static void gvc_mixer_source_dispose (GObject *object); + +G_DEFINE_TYPE (GvcMixerSource, gvc_mixer_source, GVC_TYPE_MIXER_STREAM) + +static gboolean +gvc_mixer_source_push_volume (GvcMixerStream *stream, gpointer *op) +{ + pa_operation *o; + guint index; + const GvcChannelMap *map; + pa_context *context; + const pa_cvolume *cv; + + index = gvc_mixer_stream_get_index (stream); + + map = gvc_mixer_stream_get_channel_map (stream); + + /* set the volume */ + cv = gvc_channel_map_get_cvolume (map); + + context = gvc_mixer_stream_get_pa_context (stream); + + o = pa_context_set_source_volume_by_index (context, + index, + cv, + NULL, + NULL); + + if (o == NULL) { + g_warning ("pa_context_set_source_volume_by_index() failed: %s", pa_strerror(pa_context_errno(context))); + return FALSE; + } + + *op = o; + + return TRUE; +} + +static gboolean +gvc_mixer_source_change_is_muted (GvcMixerStream *stream, + gboolean is_muted) +{ + pa_operation *o; + guint index; + pa_context *context; + + index = gvc_mixer_stream_get_index (stream); + context = gvc_mixer_stream_get_pa_context (stream); + + o = pa_context_set_source_mute_by_index (context, + index, + is_muted, + NULL, + NULL); + + if (o == NULL) { + g_warning ("pa_context_set_source_mute_by_index() failed: %s", pa_strerror(pa_context_errno(context))); + return FALSE; + } + + pa_operation_unref(o); + + return TRUE; +} + +static gboolean +gvc_mixer_source_change_port (GvcMixerStream *stream, + const char *port) +{ +#if PA_MICRO > 15 + pa_operation *o; + guint index; + pa_context *context; + + index = gvc_mixer_stream_get_index (stream); + context = gvc_mixer_stream_get_pa_context (stream); + + o = pa_context_set_source_port_by_index (context, + index, + port, + NULL, + NULL); + + if (o == NULL) { + g_warning ("pa_context_set_source_port_by_index() failed: %s", pa_strerror(pa_context_errno(context))); + return FALSE; + } + + pa_operation_unref(o); + + return TRUE; +#else + return FALSE; +#endif /* PA_MICRO > 15 */ +} + +static GObject * +gvc_mixer_source_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_params) +{ + GObject *object; + GvcMixerSource *self; + + object = G_OBJECT_CLASS (gvc_mixer_source_parent_class)->constructor (type, n_construct_properties, construct_params); + + self = GVC_MIXER_SOURCE (object); + + return object; +} + +static void +gvc_mixer_source_class_init (GvcMixerSourceClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GvcMixerStreamClass *stream_class = GVC_MIXER_STREAM_CLASS (klass); + + object_class->constructor = gvc_mixer_source_constructor; + object_class->dispose = gvc_mixer_source_dispose; + object_class->finalize = gvc_mixer_source_finalize; + + stream_class->push_volume = gvc_mixer_source_push_volume; + stream_class->change_is_muted = gvc_mixer_source_change_is_muted; + stream_class->change_port = gvc_mixer_source_change_port; + + g_type_class_add_private (klass, sizeof (GvcMixerSourcePrivate)); +} + +static void +gvc_mixer_source_init (GvcMixerSource *source) +{ + source->priv = GVC_MIXER_SOURCE_GET_PRIVATE (source); +} + +static void +gvc_mixer_source_dispose (GObject *object) +{ + GvcMixerSource *mixer_source; + + g_return_if_fail (object != NULL); + g_return_if_fail (GVC_IS_MIXER_SOURCE (object)); + + mixer_source = GVC_MIXER_SOURCE (object); + + G_OBJECT_CLASS (gvc_mixer_source_parent_class)->dispose (object); +} + +static void +gvc_mixer_source_finalize (GObject *object) +{ + GvcMixerSource *mixer_source; + + g_return_if_fail (object != NULL); + g_return_if_fail (GVC_IS_MIXER_SOURCE (object)); + + mixer_source = GVC_MIXER_SOURCE (object); + + g_return_if_fail (mixer_source->priv != NULL); + G_OBJECT_CLASS (gvc_mixer_source_parent_class)->finalize (object); +} + +/** + * gvc_mixer_source_new: (skip) + * + * @context: + * @index: + * @channel_map: + * + * Returns: + */ +GvcMixerStream * +gvc_mixer_source_new (pa_context *context, + guint index, + GvcChannelMap *channel_map) + +{ + GObject *object; + + object = g_object_new (GVC_TYPE_MIXER_SOURCE, + "pa-context", context, + "index", index, + "channel-map", channel_map, + NULL); + + return GVC_MIXER_STREAM (object); +} diff --git a/panels/sound/gvc-mixer-source.h b/panels/sound/gvc-mixer-source.h new file mode 100644 index 000000000..503f1b54d --- /dev/null +++ b/panels/sound/gvc-mixer-source.h @@ -0,0 +1,57 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Red Hat, Inc. + * + * 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. + * + */ + +#ifndef __GVC_MIXER_SOURCE_H +#define __GVC_MIXER_SOURCE_H + +#include +#include "gvc-mixer-stream.h" + +G_BEGIN_DECLS + +#define GVC_TYPE_MIXER_SOURCE (gvc_mixer_source_get_type ()) +#define GVC_MIXER_SOURCE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GVC_TYPE_MIXER_SOURCE, GvcMixerSource)) +#define GVC_MIXER_SOURCE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GVC_TYPE_MIXER_SOURCE, GvcMixerSourceClass)) +#define GVC_IS_MIXER_SOURCE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVC_TYPE_MIXER_SOURCE)) +#define GVC_IS_MIXER_SOURCE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GVC_TYPE_MIXER_SOURCE)) +#define GVC_MIXER_SOURCE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVC_TYPE_MIXER_SOURCE, GvcMixerSourceClass)) + +typedef struct GvcMixerSourcePrivate GvcMixerSourcePrivate; + +typedef struct +{ + GvcMixerStream parent; + GvcMixerSourcePrivate *priv; +} GvcMixerSource; + +typedef struct +{ + GvcMixerStreamClass parent_class; +} GvcMixerSourceClass; + +GType gvc_mixer_source_get_type (void); + +GvcMixerStream * gvc_mixer_source_new (pa_context *context, + guint index, + GvcChannelMap *map); + +G_END_DECLS + +#endif /* __GVC_MIXER_SOURCE_H */ diff --git a/panels/sound/gvc-mixer-stream-private.h b/panels/sound/gvc-mixer-stream-private.h new file mode 100644 index 000000000..b97ecf5e1 --- /dev/null +++ b/panels/sound/gvc-mixer-stream-private.h @@ -0,0 +1,34 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Red Hat, Inc. + * + * 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. + * + */ + +#ifndef __GVC_MIXER_STREAM_PRIVATE_H +#define __GVC_MIXER_STREAM_PRIVATE_H + +#include + +#include "gvc-channel-map.h" + +G_BEGIN_DECLS + +pa_context * gvc_mixer_stream_get_pa_context (GvcMixerStream *stream); + +G_END_DECLS + +#endif /* __GVC_MIXER_STREAM_PRIVATE_H */ diff --git a/panels/sound/gvc-mixer-stream.c b/panels/sound/gvc-mixer-stream.c new file mode 100644 index 000000000..3b4953aa0 --- /dev/null +++ b/panels/sound/gvc-mixer-stream.c @@ -0,0 +1,944 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 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 "gvc-mixer-stream.h" +#include "gvc-mixer-stream-private.h" +#include "gvc-channel-map-private.h" + +#define GVC_MIXER_STREAM_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_MIXER_STREAM, GvcMixerStreamPrivate)) + +static guint32 stream_serial = 1; + +struct GvcMixerStreamPrivate +{ + pa_context *pa_context; + guint id; + guint index; + gint card_index; + GvcChannelMap *channel_map; + char *name; + char *description; + char *application_id; + char *icon_name; + gboolean is_muted; + gboolean can_decibel; + gboolean is_event_stream; + gboolean is_virtual; + pa_volume_t base_volume; + pa_operation *change_volume_op; + char *port; + char *human_port; + GList *ports; +}; + +enum +{ + PROP_0, + PROP_ID, + PROP_PA_CONTEXT, + PROP_CHANNEL_MAP, + PROP_INDEX, + PROP_NAME, + PROP_DESCRIPTION, + PROP_APPLICATION_ID, + PROP_ICON_NAME, + PROP_VOLUME, + PROP_DECIBEL, + PROP_IS_MUTED, + PROP_CAN_DECIBEL, + PROP_IS_EVENT_STREAM, + PROP_IS_VIRTUAL, + PROP_CARD_INDEX, + PROP_PORT, +}; + +static void gvc_mixer_stream_class_init (GvcMixerStreamClass *klass); +static void gvc_mixer_stream_init (GvcMixerStream *mixer_stream); +static void gvc_mixer_stream_finalize (GObject *object); + +G_DEFINE_ABSTRACT_TYPE (GvcMixerStream, gvc_mixer_stream, G_TYPE_OBJECT) + +static guint32 +get_next_stream_serial (void) +{ + guint32 serial; + + serial = stream_serial++; + + if ((gint32)stream_serial < 0) { + stream_serial = 1; + } + + return serial; +} + +pa_context * +gvc_mixer_stream_get_pa_context (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), 0); + return stream->priv->pa_context; +} + +guint +gvc_mixer_stream_get_index (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), 0); + return stream->priv->index; +} + +guint +gvc_mixer_stream_get_id (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), 0); + return stream->priv->id; +} + +const GvcChannelMap * +gvc_mixer_stream_get_channel_map (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), NULL); + return stream->priv->channel_map; +} + +/** + * gvc_mixer_stream_get_volume: + * + * @stream: + * + * Returns: (type guint32) (transfer none): + */ +pa_volume_t +gvc_mixer_stream_get_volume (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), 0); + + return (pa_volume_t) gvc_channel_map_get_volume(stream->priv->channel_map)[VOLUME]; +} + +gdouble +gvc_mixer_stream_get_decibel (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), 0); + + return pa_sw_volume_to_dB( + (pa_volume_t) gvc_channel_map_get_volume(stream->priv->channel_map)[VOLUME]); +} + +/** + * gvc_mixer_stream_set_volume: + * + * @stream: + * @volume: (type guint32): + * + * Returns: + */ +gboolean +gvc_mixer_stream_set_volume (GvcMixerStream *stream, + pa_volume_t volume) +{ + pa_cvolume cv; + + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + cv = *gvc_channel_map_get_cvolume(stream->priv->channel_map); + pa_cvolume_scale(&cv, volume); + + if (!pa_cvolume_equal(gvc_channel_map_get_cvolume(stream->priv->channel_map), &cv)) { + gvc_channel_map_volume_changed(stream->priv->channel_map, &cv, FALSE); + g_object_notify (G_OBJECT (stream), "volume"); + return TRUE; + } + + return FALSE; +} + +gboolean +gvc_mixer_stream_set_decibel (GvcMixerStream *stream, + gdouble db) +{ + pa_cvolume cv; + + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + cv = *gvc_channel_map_get_cvolume(stream->priv->channel_map); + pa_cvolume_scale(&cv, pa_sw_volume_from_dB(db)); + + if (!pa_cvolume_equal(gvc_channel_map_get_cvolume(stream->priv->channel_map), &cv)) { + gvc_channel_map_volume_changed(stream->priv->channel_map, &cv, FALSE); + g_object_notify (G_OBJECT (stream), "volume"); + } + + return TRUE; +} + +gboolean +gvc_mixer_stream_get_is_muted (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + return stream->priv->is_muted; +} + +gboolean +gvc_mixer_stream_get_can_decibel (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + return stream->priv->can_decibel; +} + +gboolean +gvc_mixer_stream_set_is_muted (GvcMixerStream *stream, + gboolean is_muted) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + if (is_muted != stream->priv->is_muted) { + stream->priv->is_muted = is_muted; + g_object_notify (G_OBJECT (stream), "is-muted"); + } + + return TRUE; +} + +gboolean +gvc_mixer_stream_set_can_decibel (GvcMixerStream *stream, + gboolean can_decibel) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + if (can_decibel != stream->priv->can_decibel) { + stream->priv->can_decibel = can_decibel; + g_object_notify (G_OBJECT (stream), "can-decibel"); + } + + return TRUE; +} + +const char * +gvc_mixer_stream_get_name (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), NULL); + return stream->priv->name; +} + +const char * +gvc_mixer_stream_get_description (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), NULL); + return stream->priv->description; +} + +gboolean +gvc_mixer_stream_set_name (GvcMixerStream *stream, + const char *name) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + g_free (stream->priv->name); + stream->priv->name = g_strdup (name); + g_object_notify (G_OBJECT (stream), "name"); + + return TRUE; +} + +gboolean +gvc_mixer_stream_set_description (GvcMixerStream *stream, + const char *description) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + g_free (stream->priv->description); + stream->priv->description = g_strdup (description); + g_object_notify (G_OBJECT (stream), "description"); + + return TRUE; +} + +gboolean +gvc_mixer_stream_is_event_stream (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + return stream->priv->is_event_stream; +} + +gboolean +gvc_mixer_stream_set_is_event_stream (GvcMixerStream *stream, + gboolean is_event_stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + stream->priv->is_event_stream = is_event_stream; + g_object_notify (G_OBJECT (stream), "is-event-stream"); + + return TRUE; +} + +gboolean +gvc_mixer_stream_is_virtual (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + return stream->priv->is_virtual; +} + +gboolean +gvc_mixer_stream_set_is_virtual (GvcMixerStream *stream, + gboolean is_virtual) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + stream->priv->is_virtual = is_virtual; + g_object_notify (G_OBJECT (stream), "is-virtual"); + + return TRUE; +} + +const char * +gvc_mixer_stream_get_application_id (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), NULL); + return stream->priv->application_id; +} + +gboolean +gvc_mixer_stream_set_application_id (GvcMixerStream *stream, + const char *application_id) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + g_free (stream->priv->application_id); + stream->priv->application_id = g_strdup (application_id); + g_object_notify (G_OBJECT (stream), "application-id"); + + return TRUE; +} + +static void +on_channel_map_volume_changed (GvcChannelMap *channel_map, + gboolean set, + GvcMixerStream *stream) +{ + if (set == TRUE) + gvc_mixer_stream_push_volume (stream); + + g_object_notify (G_OBJECT (stream), "volume"); +} + +static gboolean +gvc_mixer_stream_set_channel_map (GvcMixerStream *stream, + GvcChannelMap *channel_map) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + if (channel_map != NULL) { + g_object_ref (channel_map); + } + + if (stream->priv->channel_map != NULL) { + g_signal_handlers_disconnect_by_func (stream->priv->channel_map, + on_channel_map_volume_changed, + stream); + g_object_unref (stream->priv->channel_map); + } + + stream->priv->channel_map = channel_map; + + if (stream->priv->channel_map != NULL) { + g_signal_connect (stream->priv->channel_map, + "volume-changed", + G_CALLBACK (on_channel_map_volume_changed), + stream); + + g_object_notify (G_OBJECT (stream), "channel-map"); + } + + return TRUE; +} + +const char * +gvc_mixer_stream_get_icon_name (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), NULL); + return stream->priv->icon_name; +} + +gboolean +gvc_mixer_stream_set_icon_name (GvcMixerStream *stream, + const char *icon_name) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + g_free (stream->priv->icon_name); + stream->priv->icon_name = g_strdup (icon_name); + g_object_notify (G_OBJECT (stream), "icon-name"); + + return TRUE; +} + +/** + * gvc_mixer_stream_get_base_volume: + * + * @stream: + * + * Returns: (type guint32) (transfer none): + */ +pa_volume_t +gvc_mixer_stream_get_base_volume (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), 0); + + return stream->priv->base_volume; +} + +/** + * gvc_mixer_stream_set_base_volume: + * + * @stream: + * @base_volume: (type guint32): + * + * Returns: + */ +gboolean +gvc_mixer_stream_set_base_volume (GvcMixerStream *stream, + pa_volume_t base_volume) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + stream->priv->base_volume = base_volume; + + return TRUE; +} + +const GvcMixerStreamPort * +gvc_mixer_stream_get_port (GvcMixerStream *stream) +{ + GList *l; + + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), NULL); + g_return_val_if_fail (stream->priv->ports != NULL, NULL); + + for (l = stream->priv->ports; l != NULL; l = l->next) { + GvcMixerStreamPort *p = l->data; + if (g_strcmp0 (stream->priv->port, p->port) == 0) { + return p; + } + } + + g_assert_not_reached (); + + return NULL; +} + +gboolean +gvc_mixer_stream_set_port (GvcMixerStream *stream, + const char *port) +{ + GList *l; + + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + g_return_val_if_fail (stream->priv->ports != NULL, FALSE); + + g_free (stream->priv->port); + stream->priv->port = g_strdup (port); + + g_free (stream->priv->human_port); + stream->priv->human_port = NULL; + + for (l = stream->priv->ports; l != NULL; l = l->next) { + GvcMixerStreamPort *p = l->data; + if (g_str_equal (stream->priv->port, p->port)) { + stream->priv->human_port = g_strdup (p->human_port); + break; + } + } + + g_object_notify (G_OBJECT (stream), "port"); + + return TRUE; +} + +gboolean +gvc_mixer_stream_change_port (GvcMixerStream *stream, + const char *port) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + return GVC_MIXER_STREAM_GET_CLASS (stream)->change_port (stream, port); +} + +const GList * +gvc_mixer_stream_get_ports (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + return stream->priv->ports; +} + +static int +sort_ports (GvcMixerStreamPort *a, + GvcMixerStreamPort *b) +{ + if (a->priority == b->priority) + return 0; + if (a->priority > b->priority) + return 1; + return -1; +} + +gboolean +gvc_mixer_stream_set_ports (GvcMixerStream *stream, + GList *ports) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + g_return_val_if_fail (stream->priv->ports == NULL, FALSE); + + stream->priv->ports = g_list_sort (ports, (GCompareFunc) sort_ports); + + return TRUE; +} + +gint +gvc_mixer_stream_get_card_index (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), PA_INVALID_INDEX); + return stream->priv->card_index; +} + +gboolean +gvc_mixer_stream_set_card_index (GvcMixerStream *stream, + gint card_index) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + stream->priv->card_index = card_index; + g_object_notify (G_OBJECT (stream), "card-index"); + + return TRUE; +} + +static void +gvc_mixer_stream_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GvcMixerStream *self = GVC_MIXER_STREAM (object); + + switch (prop_id) { + case PROP_PA_CONTEXT: + self->priv->pa_context = g_value_get_pointer (value); + break; + case PROP_INDEX: + self->priv->index = g_value_get_ulong (value); + break; + case PROP_ID: + self->priv->id = g_value_get_ulong (value); + break; + case PROP_CHANNEL_MAP: + gvc_mixer_stream_set_channel_map (self, g_value_get_object (value)); + break; + case PROP_NAME: + gvc_mixer_stream_set_name (self, g_value_get_string (value)); + break; + case PROP_DESCRIPTION: + gvc_mixer_stream_set_description (self, g_value_get_string (value)); + break; + case PROP_APPLICATION_ID: + gvc_mixer_stream_set_application_id (self, g_value_get_string (value)); + break; + case PROP_ICON_NAME: + gvc_mixer_stream_set_icon_name (self, g_value_get_string (value)); + break; + case PROP_VOLUME: + gvc_mixer_stream_set_volume (self, g_value_get_ulong (value)); + break; + case PROP_DECIBEL: + gvc_mixer_stream_set_decibel (self, g_value_get_double (value)); + break; + case PROP_IS_MUTED: + gvc_mixer_stream_set_is_muted (self, g_value_get_boolean (value)); + break; + case PROP_IS_EVENT_STREAM: + gvc_mixer_stream_set_is_event_stream (self, g_value_get_boolean (value)); + break; + case PROP_IS_VIRTUAL: + gvc_mixer_stream_set_is_virtual (self, g_value_get_boolean (value)); + break; + case PROP_CAN_DECIBEL: + gvc_mixer_stream_set_can_decibel (self, g_value_get_boolean (value)); + break; + case PROP_PORT: + gvc_mixer_stream_set_port (self, g_value_get_string (value)); + break; + case PROP_CARD_INDEX: + self->priv->card_index = g_value_get_long (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gvc_mixer_stream_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GvcMixerStream *self = GVC_MIXER_STREAM (object); + + switch (prop_id) { + case PROP_PA_CONTEXT: + g_value_set_pointer (value, self->priv->pa_context); + break; + case PROP_INDEX: + g_value_set_ulong (value, self->priv->index); + break; + case PROP_ID: + g_value_set_ulong (value, self->priv->id); + break; + case PROP_CHANNEL_MAP: + g_value_set_object (value, self->priv->channel_map); + break; + case PROP_NAME: + g_value_set_string (value, self->priv->name); + break; + case PROP_DESCRIPTION: + g_value_set_string (value, self->priv->description); + break; + case PROP_APPLICATION_ID: + g_value_set_string (value, self->priv->application_id); + break; + case PROP_ICON_NAME: + g_value_set_string (value, self->priv->icon_name); + break; + case PROP_VOLUME: + g_value_set_ulong (value, + pa_cvolume_max(gvc_channel_map_get_cvolume(self->priv->channel_map))); + break; + case PROP_DECIBEL: + g_value_set_double (value, + pa_sw_volume_to_dB(pa_cvolume_max(gvc_channel_map_get_cvolume(self->priv->channel_map)))); + break; + case PROP_IS_MUTED: + g_value_set_boolean (value, self->priv->is_muted); + break; + case PROP_IS_EVENT_STREAM: + g_value_set_boolean (value, self->priv->is_event_stream); + break; + case PROP_IS_VIRTUAL: + g_value_set_boolean (value, self->priv->is_virtual); + break; + case PROP_CAN_DECIBEL: + g_value_set_boolean (value, self->priv->can_decibel); + break; + case PROP_PORT: + g_value_set_string (value, self->priv->port); + break; + case PROP_CARD_INDEX: + g_value_set_long (value, self->priv->card_index); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GObject * +gvc_mixer_stream_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_params) +{ + GObject *object; + GvcMixerStream *self; + + object = G_OBJECT_CLASS (gvc_mixer_stream_parent_class)->constructor (type, n_construct_properties, construct_params); + + self = GVC_MIXER_STREAM (object); + + self->priv->id = get_next_stream_serial (); + + return object; +} + +static gboolean +gvc_mixer_stream_real_change_port (GvcMixerStream *stream, + const char *port) +{ + return FALSE; +} + +static gboolean +gvc_mixer_stream_real_push_volume (GvcMixerStream *stream, gpointer *op) +{ + return FALSE; +} + +static gboolean +gvc_mixer_stream_real_change_is_muted (GvcMixerStream *stream, + gboolean is_muted) +{ + return FALSE; +} + +gboolean +gvc_mixer_stream_push_volume (GvcMixerStream *stream) +{ + pa_operation *op; + gboolean ret; + + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + if (stream->priv->is_event_stream != FALSE) + return TRUE; + + g_debug ("Pushing new volume to stream '%s' (%s)", + stream->priv->description, stream->priv->name); + + ret = GVC_MIXER_STREAM_GET_CLASS (stream)->push_volume (stream, (gpointer *) &op); + if (ret) { + if (stream->priv->change_volume_op != NULL) + pa_operation_unref (stream->priv->change_volume_op); + stream->priv->change_volume_op = op; + } + return ret; +} + +gboolean +gvc_mixer_stream_change_is_muted (GvcMixerStream *stream, + gboolean is_muted) +{ + gboolean ret; + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + ret = GVC_MIXER_STREAM_GET_CLASS (stream)->change_is_muted (stream, is_muted); + return ret; +} + +gboolean +gvc_mixer_stream_is_running (GvcMixerStream *stream) +{ + if (stream->priv->change_volume_op == NULL) + return FALSE; + + if ((pa_operation_get_state(stream->priv->change_volume_op) == PA_OPERATION_RUNNING)) + return TRUE; + + pa_operation_unref(stream->priv->change_volume_op); + stream->priv->change_volume_op = NULL; + + return FALSE; +} + +static void +gvc_mixer_stream_class_init (GvcMixerStreamClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->constructor = gvc_mixer_stream_constructor; + gobject_class->finalize = gvc_mixer_stream_finalize; + gobject_class->set_property = gvc_mixer_stream_set_property; + gobject_class->get_property = gvc_mixer_stream_get_property; + + klass->push_volume = gvc_mixer_stream_real_push_volume; + klass->change_port = gvc_mixer_stream_real_change_port; + klass->change_is_muted = gvc_mixer_stream_real_change_is_muted; + + g_object_class_install_property (gobject_class, + PROP_INDEX, + g_param_spec_ulong ("index", + "Index", + "The index for this stream", + 0, G_MAXULONG, 0, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (gobject_class, + PROP_ID, + g_param_spec_ulong ("id", + "id", + "The id for this stream", + 0, G_MAXULONG, 0, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (gobject_class, + PROP_CHANNEL_MAP, + g_param_spec_object ("channel-map", + "channel map", + "The channel map for this stream", + GVC_TYPE_CHANNEL_MAP, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + g_object_class_install_property (gobject_class, + PROP_PA_CONTEXT, + g_param_spec_pointer ("pa-context", + "PulseAudio context", + "The PulseAudio context for this stream", + G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (gobject_class, + PROP_VOLUME, + g_param_spec_ulong ("volume", + "Volume", + "The volume for this stream", + 0, G_MAXULONG, 0, + G_PARAM_READWRITE)); + g_object_class_install_property (gobject_class, + PROP_DECIBEL, + g_param_spec_double ("decibel", + "Decibel", + "The decibel level for this stream", + -G_MAXDOUBLE, G_MAXDOUBLE, 0, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + + g_object_class_install_property (gobject_class, + PROP_NAME, + g_param_spec_string ("name", + "Name", + "Name to display for this stream", + NULL, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + g_object_class_install_property (gobject_class, + PROP_DESCRIPTION, + g_param_spec_string ("description", + "Description", + "Description to display for this stream", + NULL, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + g_object_class_install_property (gobject_class, + PROP_APPLICATION_ID, + g_param_spec_string ("application-id", + "Application identifier", + "Application identifier for this stream", + NULL, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + g_object_class_install_property (gobject_class, + PROP_ICON_NAME, + g_param_spec_string ("icon-name", + "Icon Name", + "Name of icon to display for this stream", + NULL, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + g_object_class_install_property (gobject_class, + PROP_IS_MUTED, + g_param_spec_boolean ("is-muted", + "is muted", + "Whether stream is muted", + FALSE, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + g_object_class_install_property (gobject_class, + PROP_CAN_DECIBEL, + g_param_spec_boolean ("can-decibel", + "can decibel", + "Whether stream volume can be converted to decibel units", + FALSE, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + g_object_class_install_property (gobject_class, + PROP_IS_EVENT_STREAM, + g_param_spec_boolean ("is-event-stream", + "is event stream", + "Whether stream's role is to play an event", + FALSE, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + g_object_class_install_property (gobject_class, + PROP_IS_VIRTUAL, + g_param_spec_boolean ("is-virtual", + "is virtual stream", + "Whether the stream is virtual", + FALSE, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + g_object_class_install_property (gobject_class, + PROP_PORT, + g_param_spec_string ("port", + "Port", + "The name of the current port for this stream", + NULL, + G_PARAM_READWRITE)); + g_object_class_install_property (gobject_class, + PROP_CARD_INDEX, + g_param_spec_long ("card-index", + "Card index", + "The index of the card for this stream", + PA_INVALID_INDEX, G_MAXLONG, PA_INVALID_INDEX, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + g_type_class_add_private (klass, sizeof (GvcMixerStreamPrivate)); +} + +static void +gvc_mixer_stream_init (GvcMixerStream *stream) +{ + stream->priv = GVC_MIXER_STREAM_GET_PRIVATE (stream); +} + +static void +free_port (GvcMixerStreamPort *p) +{ + g_free (p->port); + g_free (p->human_port); + g_free (p); +} + +static void +gvc_mixer_stream_finalize (GObject *object) +{ + GvcMixerStream *mixer_stream; + + g_return_if_fail (object != NULL); + g_return_if_fail (GVC_IS_MIXER_STREAM (object)); + + mixer_stream = GVC_MIXER_STREAM (object); + + g_return_if_fail (mixer_stream->priv != NULL); + + g_object_unref (mixer_stream->priv->channel_map); + mixer_stream->priv->channel_map = NULL; + + g_free (mixer_stream->priv->name); + mixer_stream->priv->name = NULL; + + g_free (mixer_stream->priv->description); + mixer_stream->priv->description = NULL; + + g_free (mixer_stream->priv->application_id); + mixer_stream->priv->application_id = NULL; + + g_free (mixer_stream->priv->icon_name); + mixer_stream->priv->icon_name = NULL; + + g_free (mixer_stream->priv->port); + mixer_stream->priv->port = NULL; + + g_free (mixer_stream->priv->human_port); + mixer_stream->priv->human_port = NULL; + + g_list_foreach (mixer_stream->priv->ports, (GFunc) free_port, NULL); + g_list_free (mixer_stream->priv->ports); + mixer_stream->priv->ports = NULL; + + if (mixer_stream->priv->change_volume_op) { + pa_operation_unref(mixer_stream->priv->change_volume_op); + mixer_stream->priv->change_volume_op = NULL; + } + + G_OBJECT_CLASS (gvc_mixer_stream_parent_class)->finalize (object); +} diff --git a/panels/sound/gvc-mixer-stream.h b/panels/sound/gvc-mixer-stream.h new file mode 100644 index 000000000..53b7eb6d6 --- /dev/null +++ b/panels/sound/gvc-mixer-stream.h @@ -0,0 +1,125 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Red Hat, Inc. + * + * 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. + * + */ + +#ifndef __GVC_MIXER_STREAM_H +#define __GVC_MIXER_STREAM_H + +#include +#include "gvc-pulseaudio-fake.h" +#include "gvc-channel-map.h" + +G_BEGIN_DECLS + +#define GVC_TYPE_MIXER_STREAM (gvc_mixer_stream_get_type ()) +#define GVC_MIXER_STREAM(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GVC_TYPE_MIXER_STREAM, GvcMixerStream)) +#define GVC_MIXER_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GVC_TYPE_MIXER_STREAM, GvcMixerStreamClass)) +#define GVC_IS_MIXER_STREAM(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVC_TYPE_MIXER_STREAM)) +#define GVC_IS_MIXER_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GVC_TYPE_MIXER_STREAM)) +#define GVC_MIXER_STREAM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVC_TYPE_MIXER_STREAM, GvcMixerStreamClass)) + +typedef struct GvcMixerStreamPrivate GvcMixerStreamPrivate; + +typedef struct +{ + GObject parent; + GvcMixerStreamPrivate *priv; +} GvcMixerStream; + +typedef struct +{ + GObjectClass parent_class; + + /* vtable */ + gboolean (*push_volume) (GvcMixerStream *stream, + gpointer *operation); + gboolean (*change_is_muted) (GvcMixerStream *stream, + gboolean is_muted); + gboolean (*change_port) (GvcMixerStream *stream, + const char *port); +} GvcMixerStreamClass; + +typedef struct +{ + char *port; + char *human_port; + guint priority; +} GvcMixerStreamPort; + +GType gvc_mixer_stream_get_type (void); + +guint gvc_mixer_stream_get_index (GvcMixerStream *stream); +guint gvc_mixer_stream_get_id (GvcMixerStream *stream); +const GvcChannelMap *gvc_mixer_stream_get_channel_map(GvcMixerStream *stream); +const GvcMixerStreamPort *gvc_mixer_stream_get_port (GvcMixerStream *stream); +const GList * gvc_mixer_stream_get_ports (GvcMixerStream *stream); +gboolean gvc_mixer_stream_change_port (GvcMixerStream *stream, + const char *port); + +pa_volume_t gvc_mixer_stream_get_volume (GvcMixerStream *stream); +gdouble gvc_mixer_stream_get_decibel (GvcMixerStream *stream); +gboolean gvc_mixer_stream_push_volume (GvcMixerStream *stream); +pa_volume_t gvc_mixer_stream_get_base_volume (GvcMixerStream *stream); + +gboolean gvc_mixer_stream_get_is_muted (GvcMixerStream *stream); +gboolean gvc_mixer_stream_get_can_decibel (GvcMixerStream *stream); +gboolean gvc_mixer_stream_change_is_muted (GvcMixerStream *stream, + gboolean is_muted); +gboolean gvc_mixer_stream_is_running (GvcMixerStream *stream); +const char * gvc_mixer_stream_get_name (GvcMixerStream *stream); +const char * gvc_mixer_stream_get_icon_name (GvcMixerStream *stream); +const char * gvc_mixer_stream_get_description (GvcMixerStream *stream); +const char * gvc_mixer_stream_get_application_id (GvcMixerStream *stream); +gboolean gvc_mixer_stream_is_event_stream (GvcMixerStream *stream); +gboolean gvc_mixer_stream_is_virtual (GvcMixerStream *stream); +gint gvc_mixer_stream_get_card_index (GvcMixerStream *stream); + +/* private */ +gboolean gvc_mixer_stream_set_volume (GvcMixerStream *stream, + pa_volume_t volume); +gboolean gvc_mixer_stream_set_decibel (GvcMixerStream *stream, + gdouble db); +gboolean gvc_mixer_stream_set_is_muted (GvcMixerStream *stream, + gboolean is_muted); +gboolean gvc_mixer_stream_set_can_decibel (GvcMixerStream *stream, + gboolean can_decibel); +gboolean gvc_mixer_stream_set_name (GvcMixerStream *stream, + const char *name); +gboolean gvc_mixer_stream_set_description (GvcMixerStream *stream, + const char *description); +gboolean gvc_mixer_stream_set_icon_name (GvcMixerStream *stream, + const char *name); +gboolean gvc_mixer_stream_set_is_event_stream (GvcMixerStream *stream, + gboolean is_event_stream); +gboolean gvc_mixer_stream_set_is_virtual (GvcMixerStream *stream, + gboolean is_event_stream); +gboolean gvc_mixer_stream_set_application_id (GvcMixerStream *stream, + const char *application_id); +gboolean gvc_mixer_stream_set_base_volume (GvcMixerStream *stream, + pa_volume_t base_volume); +gboolean gvc_mixer_stream_set_port (GvcMixerStream *stream, + const char *port); +gboolean gvc_mixer_stream_set_ports (GvcMixerStream *stream, + GList *ports); +gboolean gvc_mixer_stream_set_card_index (GvcMixerStream *stream, + gint card_index); + +G_END_DECLS + +#endif /* __GVC_MIXER_STREAM_H */ diff --git a/panels/sound/gvc-pulseaudio-fake.h b/panels/sound/gvc-pulseaudio-fake.h new file mode 100644 index 000000000..65293cdef --- /dev/null +++ b/panels/sound/gvc-pulseaudio-fake.h @@ -0,0 +1,34 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Red Hat, Inc. + * + * 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. + * + */ + +#ifndef __GVC_PULSEAUDIO_FAKE_H +#define __GVC_PULSEAUDIO_FAKE_H + +#ifdef WITH_INTROSPECTION + +#ifndef PA_API_VERSION +typedef int pa_channel_position_t; +typedef guint32 pa_volume_t; +typedef gpointer pa_context; +#endif /* PA_API_VERSION */ + +#endif /* WITH_INTROSPECTION */ + +#endif /* __GVC_PULSEAUDIO_FAKE_H */ diff --git a/panels/sound/gvc-sound-theme-chooser.c b/panels/sound/gvc-sound-theme-chooser.c new file mode 100644 index 000000000..676c5107f --- /dev/null +++ b/panels/sound/gvc-sound-theme-chooser.c @@ -0,0 +1,1145 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Bastien Nocera + * Copyright (C) 2008 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 +#include +#include + +#include + +#include "gvc-sound-theme-chooser.h" +#include "sound-theme-file-utils.h" + +#define GVC_SOUND_THEME_CHOOSER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_SOUND_THEME_CHOOSER, GvcSoundThemeChooserPrivate)) + +struct GvcSoundThemeChooserPrivate +{ + GtkWidget *combo_box; + GtkWidget *treeview; + GtkWidget *theme_box; + GtkWidget *selection_box; + GtkWidget *click_feedback_button; + GConfClient *client; + guint sounds_dir_id; + guint metacity_dir_id; +}; + +static void gvc_sound_theme_chooser_class_init (GvcSoundThemeChooserClass *klass); +static void gvc_sound_theme_chooser_init (GvcSoundThemeChooser *sound_theme_chooser); +static void gvc_sound_theme_chooser_finalize (GObject *object); + +G_DEFINE_TYPE (GvcSoundThemeChooser, gvc_sound_theme_chooser, GTK_TYPE_VBOX) + +#define KEY_SOUNDS_DIR "/desktop/gnome/sound" +#define EVENT_SOUNDS_KEY KEY_SOUNDS_DIR "/event_sounds" +#define INPUT_SOUNDS_KEY KEY_SOUNDS_DIR "/input_feedback_sounds" +#define SOUND_THEME_KEY KEY_SOUNDS_DIR "/theme_name" +#define KEY_METACITY_DIR "/apps/metacity/general" +#define AUDIO_BELL_KEY KEY_METACITY_DIR "/audible_bell" + +#define DEFAULT_ALERT_ID "__default" +#define CUSTOM_THEME_NAME "__custom" +#define NO_SOUNDS_THEME_NAME "__no_sounds" + +enum { + THEME_DISPLAY_COL, + THEME_IDENTIFIER_COL, + THEME_PARENT_ID_COL, + THEME_NUM_COLS +}; + +enum { + ALERT_DISPLAY_COL, + ALERT_IDENTIFIER_COL, + ALERT_SOUND_TYPE_COL, + ALERT_NUM_COLS +}; + +enum { + SOUND_TYPE_UNSET, + SOUND_TYPE_OFF, + SOUND_TYPE_DEFAULT_FROM_THEME, + SOUND_TYPE_BUILTIN, + SOUND_TYPE_CUSTOM +}; + +static void +on_combobox_changed (GtkComboBox *widget, + GvcSoundThemeChooser *chooser) +{ + GtkTreeIter iter; + GtkTreeModel *model; + char *theme_name; + + if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (chooser->priv->combo_box), &iter) == FALSE) { + return; + } + + model = gtk_combo_box_get_model (GTK_COMBO_BOX (chooser->priv->combo_box)); + gtk_tree_model_get (model, &iter, THEME_IDENTIFIER_COL, &theme_name, -1); + + g_assert (theme_name != NULL); + + /* special case for no sounds */ + if (strcmp (theme_name, NO_SOUNDS_THEME_NAME) == 0) { + gconf_client_set_bool (chooser->priv->client, EVENT_SOUNDS_KEY, FALSE, NULL); + return; + } else { + gconf_client_set_bool (chooser->priv->client, EVENT_SOUNDS_KEY, TRUE, NULL); + } + + gconf_client_set_string (chooser->priv->client, SOUND_THEME_KEY, theme_name, NULL); + + g_free (theme_name); + + /* FIXME: reset alert model */ +} + +static char * +load_index_theme_name (const char *index, + char **parent) +{ + GKeyFile *file; + char *indexname = NULL; + gboolean hidden; + + file = g_key_file_new (); + if (g_key_file_load_from_file (file, index, G_KEY_FILE_KEEP_TRANSLATIONS, NULL) == FALSE) { + g_key_file_free (file); + return NULL; + } + /* Don't add hidden themes to the list */ + hidden = g_key_file_get_boolean (file, "Sound Theme", "Hidden", NULL); + if (!hidden) { + indexname = g_key_file_get_locale_string (file, + "Sound Theme", + "Name", + NULL, + NULL); + + /* Save the parent theme, if there's one */ + if (parent != NULL) { + *parent = g_key_file_get_string (file, + "Sound Theme", + "Inherits", + NULL); + } + } + + g_key_file_free (file); + return indexname; +} + +static void +sound_theme_in_dir (GHashTable *hash, + const char *dir) +{ + GDir *d; + const char *name; + + d = g_dir_open (dir, 0, NULL); + if (d == NULL) { + return; + } + + while ((name = g_dir_read_name (d)) != NULL) { + char *dirname, *index, *indexname; + + /* Look for directories */ + dirname = g_build_filename (dir, name, NULL); + if (g_file_test (dirname, G_FILE_TEST_IS_DIR) == FALSE) { + g_free (dirname); + continue; + } + + /* Look for index files */ + index = g_build_filename (dirname, "index.theme", NULL); + g_free (dirname); + + /* Check the name of the theme in the index.theme file */ + indexname = load_index_theme_name (index, NULL); + g_free (index); + if (indexname == NULL) { + continue; + } + + g_hash_table_insert (hash, g_strdup (name), indexname); + } + + g_dir_close (d); +} + +static void +add_theme_to_store (const char *key, + const char *value, + GtkListStore *store) +{ + char *parent; + + parent = NULL; + + /* Get the parent, if we're checking the custom theme */ + if (strcmp (key, CUSTOM_THEME_NAME) == 0) { + char *name, *path; + + path = custom_theme_dir_path ("index.theme"); + name = load_index_theme_name (path, &parent); + g_free (name); + g_free (path); + } + gtk_list_store_insert_with_values (store, NULL, G_MAXINT, + THEME_DISPLAY_COL, value, + THEME_IDENTIFIER_COL, key, + THEME_PARENT_ID_COL, parent, + -1); + g_free (parent); +} + +static void +set_combox_for_theme_name (GvcSoundThemeChooser *chooser, + const char *name) +{ + GtkTreeIter iter; + GtkTreeModel *model; + gboolean found; + + /* If the name is empty, use "freedesktop" */ + if (name == NULL || *name == '\0') { + name = "freedesktop"; + } + + model = gtk_combo_box_get_model (GTK_COMBO_BOX (chooser->priv->combo_box)); + + if (gtk_tree_model_get_iter_first (model, &iter) == FALSE) { + return; + } + + do { + char *value; + + gtk_tree_model_get (model, &iter, THEME_IDENTIFIER_COL, &value, -1); + found = (value != NULL && strcmp (value, name) == 0); + g_free (value); + + } while (!found && gtk_tree_model_iter_next (model, &iter)); + + /* When we can't find the theme we need to set, try to set the default + * one "freedesktop" */ + if (found) { + gtk_combo_box_set_active_iter (GTK_COMBO_BOX (chooser->priv->combo_box), &iter); + } else if (strcmp (name, "freedesktop") != 0) { + g_debug ("not found, falling back to fdo"); + set_combox_for_theme_name (chooser, "freedesktop"); + } +} + +static void +set_input_feedback_enabled (GvcSoundThemeChooser *chooser, + gboolean enabled) +{ + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (chooser->priv->click_feedback_button), + enabled); +} + +static void +setup_theme_selector (GvcSoundThemeChooser *chooser) +{ + GHashTable *hash; + GtkListStore *store; + GtkCellRenderer *renderer; + const char * const *data_dirs; + const char *data_dir; + char *dir; + guint i; + + /* Add the theme names and their display name to a hash table, + * makes it easy to avoid duplicate themes */ + hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + + data_dirs = g_get_system_data_dirs (); + for (i = 0; data_dirs[i] != NULL; i++) { + dir = g_build_filename (data_dirs[i], "sounds", NULL); + sound_theme_in_dir (hash, dir); + g_free (dir); + } + + data_dir = g_get_user_data_dir (); + dir = g_build_filename (data_dir, "sounds", NULL); + sound_theme_in_dir (hash, dir); + g_free (dir); + + /* If there isn't at least one theme, make everything + * insensitive, LAME! */ + if (g_hash_table_size (hash) == 0) { + gtk_widget_set_sensitive (GTK_WIDGET (chooser), FALSE); + g_warning ("Bad setup, install the freedesktop sound theme"); + g_hash_table_destroy (hash); + return; + } + + /* Setup the tree model, 3 columns: + * - internal theme name/directory + * - display theme name + * - the internal id for the parent theme, used for the custom theme */ + store = gtk_list_store_new (THEME_NUM_COLS, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING); + + /* Add the themes to a combobox */ + gtk_list_store_insert_with_values (store, + NULL, + G_MAXINT, + THEME_DISPLAY_COL, _("No sounds"), + THEME_IDENTIFIER_COL, "__no_sounds", + THEME_PARENT_ID_COL, NULL, + -1); + g_hash_table_foreach (hash, (GHFunc) add_theme_to_store, store); + g_hash_table_destroy (hash); + + /* Set the display */ + gtk_combo_box_set_model (GTK_COMBO_BOX (chooser->priv->combo_box), + GTK_TREE_MODEL (store)); + + renderer = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (chooser->priv->combo_box), + renderer, + TRUE); + gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (chooser->priv->combo_box), + renderer, + "text", THEME_DISPLAY_COL, + NULL); + + g_signal_connect (chooser->priv->combo_box, + "changed", + G_CALLBACK (on_combobox_changed), + chooser); +} + +#define GVC_SOUND_SOUND (xmlChar *) "sound" +#define GVC_SOUND_NAME (xmlChar *) "name" +#define GVC_SOUND_FILENAME (xmlChar *) "filename" + +/* Adapted from yelp-toc-pager.c */ +static xmlChar * +xml_get_and_trim_names (xmlNodePtr node) +{ + xmlNodePtr cur, keep = NULL; + xmlChar *keep_lang = NULL; + xmlChar *value; + int j, keep_pri = INT_MAX; + + const gchar * const * langs = g_get_language_names (); + + value = NULL; + + for (cur = node->children; cur; cur = cur->next) { + if (! xmlStrcmp (cur->name, GVC_SOUND_NAME)) { + xmlChar *cur_lang = NULL; + int cur_pri = INT_MAX; + + cur_lang = xmlNodeGetLang (cur); + + if (cur_lang) { + for (j = 0; langs[j]; j++) { + if (g_str_equal (cur_lang, langs[j])) { + cur_pri = j; + break; + } + } + } else { + cur_pri = INT_MAX - 1; + } + + if (cur_pri <= keep_pri) { + if (keep_lang) + xmlFree (keep_lang); + if (value) + xmlFree (value); + + value = xmlNodeGetContent (cur); + + keep_lang = cur_lang; + keep_pri = cur_pri; + keep = cur; + } else { + if (cur_lang) + xmlFree (cur_lang); + } + } + } + + /* Delete all GVC_SOUND_NAME nodes */ + cur = node->children; + while (cur) { + xmlNodePtr this = cur; + cur = cur->next; + if (! xmlStrcmp (this->name, GVC_SOUND_NAME)) { + xmlUnlinkNode (this); + xmlFreeNode (this); + } + } + + return value; +} + +static void +populate_model_from_node (GvcSoundThemeChooser *chooser, + GtkTreeModel *model, + xmlNodePtr node) +{ + xmlNodePtr child; + xmlChar *filename; + xmlChar *name; + + filename = NULL; + name = xml_get_and_trim_names (node); + for (child = node->children; child; child = child->next) { + if (xmlNodeIsText (child)) { + continue; + } + + if (xmlStrcmp (child->name, GVC_SOUND_FILENAME) == 0) { + filename = xmlNodeGetContent (child); + } else if (xmlStrcmp (child->name, GVC_SOUND_NAME) == 0) { + /* EH? should have been trimmed */ + } + } + + if (filename != NULL && name != NULL) { + gtk_list_store_insert_with_values (GTK_LIST_STORE (model), + NULL, + G_MAXINT, + ALERT_IDENTIFIER_COL, filename, + ALERT_DISPLAY_COL, name, + ALERT_SOUND_TYPE_COL, _("Built-in"), + -1); + } + + xmlFree (filename); + xmlFree (name); +} + +static void +populate_model_from_file (GvcSoundThemeChooser *chooser, + GtkTreeModel *model, + const char *filename) +{ + xmlDocPtr doc; + xmlNodePtr root; + xmlNodePtr child; + gboolean exists; + + exists = g_file_test (filename, G_FILE_TEST_EXISTS); + if (! exists) { + return; + } + + doc = xmlParseFile (filename); + if (doc == NULL) { + return; + } + + root = xmlDocGetRootElement (doc); + + for (child = root->children; child; child = child->next) { + if (xmlNodeIsText (child)) { + continue; + } + if (xmlStrcmp (child->name, GVC_SOUND_SOUND) != 0) { + continue; + } + + populate_model_from_node (chooser, model, child); + } + + xmlFreeDoc (doc); +} + +static void +populate_model_from_dir (GvcSoundThemeChooser *chooser, + GtkTreeModel *model, + const char *dirname) +{ + GDir *d; + const char *name; + + d = g_dir_open (dirname, 0, NULL); + if (d == NULL) { + return; + } + + while ((name = g_dir_read_name (d)) != NULL) { + char *path; + + if (! g_str_has_suffix (name, ".xml")) { + continue; + } + + path = g_build_filename (dirname, name, NULL); + populate_model_from_file (chooser, model, path); + g_free (path); + } +} + +static gboolean +save_alert_sounds (GvcSoundThemeChooser *chooser, + const char *id) +{ + const char *sounds[3] = { "bell-terminal", "bell-window-system", NULL }; + char *path; + + if (strcmp (id, DEFAULT_ALERT_ID) == 0) { + delete_old_files (sounds); + delete_disabled_files (sounds); + } else { + delete_old_files (sounds); + delete_disabled_files (sounds); + add_custom_file (sounds, id); + } + + /* And poke the directory so the theme gets updated */ + path = custom_theme_dir_path (NULL); + if (utime (path, NULL) != 0) { + g_warning ("Failed to update mtime for directory '%s': %s", + path, g_strerror (errno)); + } + g_free (path); + + return FALSE; +} + + +static void +update_alert_model (GvcSoundThemeChooser *chooser, + const char *id) +{ + GtkTreeModel *model; + GtkTreeIter iter; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (chooser->priv->treeview)); + gtk_tree_model_get_iter_first (model, &iter); + do { + char *this_id; + + gtk_tree_model_get (model, &iter, + ALERT_IDENTIFIER_COL, &this_id, + -1); + + if (strcmp (this_id, id) == 0) { + GtkTreeSelection *selection; + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (chooser->priv->treeview)); + gtk_tree_selection_select_iter (selection, &iter); + } + + g_free (this_id); + } while (gtk_tree_model_iter_next (model, &iter)); +} + +static void +update_alert (GvcSoundThemeChooser *chooser, + const char *alert_id) +{ + GtkTreeModel *theme_model; + GtkTreeIter iter; + char *theme; + char *parent; + gboolean is_custom; + gboolean is_default; + gboolean add_custom; + gboolean remove_custom; + + theme_model = gtk_combo_box_get_model (GTK_COMBO_BOX (chooser->priv->combo_box)); + /* Get the current theme's name, and set the parent */ + if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (chooser->priv->combo_box), &iter) == FALSE) { + return; + } + + gtk_tree_model_get (theme_model, &iter, + THEME_IDENTIFIER_COL, &theme, + THEME_IDENTIFIER_COL, &parent, + -1); + is_custom = strcmp (theme, CUSTOM_THEME_NAME) == 0; + is_default = strcmp (alert_id, DEFAULT_ALERT_ID) == 0; + + /* So a few possibilities: + * 1. Named theme, default alert selected: noop + * 2. Named theme, alternate alert selected: create new custom with sound + * 3. Custom theme, default alert selected: remove sound and possibly custom + * 4. Custom theme, alternate alert selected: update custom sound + */ + add_custom = FALSE; + remove_custom = FALSE; + if (! is_custom && is_default) { + /* remove custom just in case */ + remove_custom = TRUE; + } else if (! is_custom && ! is_default) { + create_custom_theme (parent); + save_alert_sounds (chooser, alert_id); + add_custom = TRUE; + } else if (is_custom && is_default) { + save_alert_sounds (chooser, alert_id); + /* after removing files check if it is empty */ + if (custom_theme_dir_is_empty ()) { + remove_custom = TRUE; + } + } else if (is_custom && ! is_default) { + save_alert_sounds (chooser, alert_id); + } + + if (add_custom) { + gtk_list_store_insert_with_values (GTK_LIST_STORE (theme_model), + NULL, + G_MAXINT, + THEME_DISPLAY_COL, _("Custom"), + THEME_IDENTIFIER_COL, CUSTOM_THEME_NAME, + THEME_PARENT_ID_COL, theme, + -1); + set_combox_for_theme_name (chooser, CUSTOM_THEME_NAME); + } else if (remove_custom) { + gtk_tree_model_get_iter_first (theme_model, &iter); + do { + char *this_parent; + + gtk_tree_model_get (theme_model, &iter, + THEME_PARENT_ID_COL, &this_parent, + -1); + if (this_parent != NULL && strcmp (this_parent, CUSTOM_THEME_NAME) != 0) { + g_free (this_parent); + gtk_list_store_remove (GTK_LIST_STORE (theme_model), &iter); + break; + } + g_free (this_parent); + } while (gtk_tree_model_iter_next (theme_model, &iter)); + + delete_custom_theme_dir (); + + set_combox_for_theme_name (chooser, parent); + } + + update_alert_model (chooser, alert_id); + + g_free (theme); + g_free (parent); +} + +static void +play_preview_for_id (GvcSoundThemeChooser *chooser, + const char *id) +{ + GtkTreeIter theme_iter; + char *parent_theme; + + g_return_if_fail (id != NULL); + + parent_theme = NULL; + if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (chooser->priv->combo_box), &theme_iter)) { + GtkTreeModel *theme_model; + char *theme_id; + char *parent_id; + + theme_model = gtk_combo_box_get_model (GTK_COMBO_BOX (chooser->priv->combo_box)); + theme_id = NULL; + parent_id = NULL; + gtk_tree_model_get (theme_model, &theme_iter, + THEME_IDENTIFIER_COL, &theme_id, + THEME_PARENT_ID_COL, &parent_id, -1); + if (theme_id && strcmp (theme_id, CUSTOM_THEME_NAME) == 0) { + parent_theme = g_strdup (parent_id); + } + g_free (theme_id); + g_free (parent_id); + } + + /* special case: for the default item on custom themes + * play the alert for the parent theme */ + if (strcmp (id, DEFAULT_ALERT_ID) == 0) { + if (parent_theme != NULL) { + ca_gtk_play_for_widget (GTK_WIDGET (chooser), 0, + CA_PROP_APPLICATION_NAME, _("Sound Preferences"), + CA_PROP_EVENT_ID, "bell-window-system", + CA_PROP_CANBERRA_XDG_THEME_NAME, parent_theme, + CA_PROP_EVENT_DESCRIPTION, _("Testing event sound"), + CA_PROP_CANBERRA_CACHE_CONTROL, "never", + CA_PROP_APPLICATION_ID, "org.gnome.VolumeControl", +#ifdef CA_PROP_CANBERRA_ENABLE + CA_PROP_CANBERRA_ENABLE, "1", +#endif + NULL); + } else { + ca_gtk_play_for_widget (GTK_WIDGET (chooser), 0, + CA_PROP_APPLICATION_NAME, _("Sound Preferences"), + CA_PROP_EVENT_ID, "bell-window-system", + CA_PROP_EVENT_DESCRIPTION, _("Testing event sound"), + CA_PROP_CANBERRA_CACHE_CONTROL, "never", + CA_PROP_APPLICATION_ID, "org.gnome.VolumeControl", +#ifdef CA_PROP_CANBERRA_ENABLE + CA_PROP_CANBERRA_ENABLE, "1", +#endif + NULL); + } + } else { + ca_gtk_play_for_widget (GTK_WIDGET (chooser), 0, + CA_PROP_APPLICATION_NAME, _("Sound Preferences"), + CA_PROP_MEDIA_FILENAME, id, + CA_PROP_EVENT_DESCRIPTION, _("Testing event sound"), + CA_PROP_CANBERRA_CACHE_CONTROL, "never", + CA_PROP_APPLICATION_ID, "org.gnome.VolumeControl", +#ifdef CA_PROP_CANBERRA_ENABLE + CA_PROP_CANBERRA_ENABLE, "1", +#endif + NULL); + + } + g_free (parent_theme); +} + +static void +on_treeview_selection_changed (GtkTreeSelection *selection, + GvcSoundThemeChooser *chooser) +{ + GtkTreeModel *model; + GtkTreeIter iter; + char *id; + + if (chooser->priv->treeview == NULL) { + return; + } + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (chooser->priv->treeview)); + + if (gtk_tree_selection_get_selected (selection, &model, &iter) == FALSE) { + return; + } + + id = NULL; + gtk_tree_model_get (model, &iter, + ALERT_IDENTIFIER_COL, &id, + -1); + if (id == NULL) { + return; + } + + play_preview_for_id (chooser, id); + update_alert (chooser, id); + g_free (id); +} + +static gboolean +on_treeview_button_pressed (GtkTreeView *treeview, + GdkEventButton *event, + GvcSoundThemeChooser *chooser) +{ + GtkTreeSelection *selection; + GtkTreePath *path; + + selection = gtk_tree_view_get_selection (treeview); + if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (treeview), + event->x, event->y, &path, NULL, NULL, NULL) == FALSE) { + return FALSE; + } + + if (gtk_tree_selection_path_is_selected (selection, path) == FALSE) { + gtk_tree_path_free (path); + return FALSE; + } + gtk_tree_path_free (path); + + on_treeview_selection_changed (selection, chooser); + + return FALSE; +} + +static GtkWidget * +create_alert_treeview (GvcSoundThemeChooser *chooser) +{ + GtkListStore *store; + GtkWidget *treeview; + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + GtkTreeSelection *selection; + + treeview = gtk_tree_view_new (); + g_signal_connect (treeview, + "button-press-event", + G_CALLBACK (on_treeview_button_pressed), + chooser); + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview)); + gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE); + g_signal_connect (selection, + "changed", + G_CALLBACK (on_treeview_selection_changed), + chooser); + + /* Setup the tree model, 3 columns: + * - display name + * - sound id + * - sound type + */ + store = gtk_list_store_new (ALERT_NUM_COLS, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING); + + gtk_list_store_insert_with_values (store, + NULL, + G_MAXINT, + ALERT_IDENTIFIER_COL, DEFAULT_ALERT_ID, + ALERT_DISPLAY_COL, _("Default"), + ALERT_SOUND_TYPE_COL, _("From theme"), + -1); + + populate_model_from_dir (chooser, GTK_TREE_MODEL (store), SOUND_SET_DIR); + + gtk_tree_view_set_model (GTK_TREE_VIEW (treeview), + GTK_TREE_MODEL (store)); + + renderer = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes (_("Name"), + renderer, + "text", ALERT_DISPLAY_COL, + NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column); + + renderer = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes (_("Type"), + renderer, + "text", ALERT_SOUND_TYPE_COL, + NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column); + + return treeview; +} + +static int +get_file_type (const char *sound_name, + char **linked_name) +{ + char *name, *filename; + + *linked_name = NULL; + + name = g_strdup_printf ("%s.disabled", sound_name); + filename = custom_theme_dir_path (name); + g_free (name); + + if (g_file_test (filename, G_FILE_TEST_IS_REGULAR) != FALSE) { + g_free (filename); + return SOUND_TYPE_OFF; + } + g_free (filename); + + /* We only check for .ogg files because those are the + * only ones we create */ + name = g_strdup_printf ("%s.ogg", sound_name); + filename = custom_theme_dir_path (name); + g_free (name); + + if (g_file_test (filename, G_FILE_TEST_IS_SYMLINK) != FALSE) { + *linked_name = g_file_read_link (filename, NULL); + g_free (filename); + return SOUND_TYPE_CUSTOM; + } + g_free (filename); + + return SOUND_TYPE_BUILTIN; +} + +static void +update_alerts_from_theme_name (GvcSoundThemeChooser *chooser, + const char *name) +{ + if (strcmp (name, CUSTOM_THEME_NAME) != 0) { + /* reset alert to default */ + update_alert (chooser, DEFAULT_ALERT_ID); + } else { + int sound_type; + char *linkname; + + linkname = NULL; + sound_type = get_file_type ("bell-terminal", &linkname); + g_debug ("Found link: %s", linkname); + if (sound_type == SOUND_TYPE_CUSTOM) { + update_alert (chooser, linkname); + } + } +} + +static void +update_theme (GvcSoundThemeChooser *chooser) +{ + char *theme_name; + gboolean events_enabled; + gboolean bell_enabled; + gboolean feedback_enabled; + + bell_enabled = gconf_client_get_bool (chooser->priv->client, AUDIO_BELL_KEY, NULL); + //set_audible_bell_enabled (chooser, bell_enabled); + + feedback_enabled = gconf_client_get_bool (chooser->priv->client, INPUT_SOUNDS_KEY, NULL); + set_input_feedback_enabled (chooser, feedback_enabled); + + events_enabled = gconf_client_get_bool (chooser->priv->client, EVENT_SOUNDS_KEY, NULL); + if (events_enabled) { + theme_name = gconf_client_get_string (chooser->priv->client, SOUND_THEME_KEY, NULL); + } else { + theme_name = g_strdup (NO_SOUNDS_THEME_NAME); + } + + gtk_widget_set_sensitive (chooser->priv->selection_box, events_enabled); + gtk_widget_set_sensitive (chooser->priv->click_feedback_button, events_enabled); + + set_combox_for_theme_name (chooser, theme_name); + + update_alerts_from_theme_name (chooser, theme_name); + + g_free (theme_name); +} + +static GObject * +gvc_sound_theme_chooser_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_params) +{ + GObject *object; + GvcSoundThemeChooser *self; + + object = G_OBJECT_CLASS (gvc_sound_theme_chooser_parent_class)->constructor (type, n_construct_properties, construct_params); + + self = GVC_SOUND_THEME_CHOOSER (object); + + setup_theme_selector (self); + + update_theme (self); + + return object; +} + +static void +gvc_sound_theme_chooser_class_init (GvcSoundThemeChooserClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructor = gvc_sound_theme_chooser_constructor; + object_class->finalize = gvc_sound_theme_chooser_finalize; + + g_type_class_add_private (klass, sizeof (GvcSoundThemeChooserPrivate)); +} + +static void +on_click_feedback_toggled (GtkToggleButton *button, + GvcSoundThemeChooser *chooser) +{ + gboolean enabled; + + enabled = gtk_toggle_button_get_active (button); + + gconf_client_set_bool (chooser->priv->client, INPUT_SOUNDS_KEY, enabled, NULL); +} + +static void +on_key_changed (GConfClient *client, + guint cnxn_id, + GConfEntry *entry, + GvcSoundThemeChooser *chooser) +{ + const char *key; + GConfValue *value; + + key = gconf_entry_get_key (entry); + + if (! g_str_has_prefix (key, KEY_SOUNDS_DIR) + && ! g_str_has_prefix (key, KEY_METACITY_DIR)) { + return; + } + + value = gconf_entry_get_value (entry); + if (strcmp (key, EVENT_SOUNDS_KEY) == 0) { + update_theme (chooser); + } else if (strcmp (key, SOUND_THEME_KEY) == 0) { + update_theme (chooser); + } else if (strcmp (key, INPUT_SOUNDS_KEY) == 0) { + update_theme (chooser); + } else if (strcmp (key, AUDIO_BELL_KEY) == 0) { + update_theme (chooser); + } +} + +static void +constrain_list_size (GtkWidget *widget, + GtkRequisition *requisition, + GtkWidget *to_size) +{ + GtkRequisition req; + int max_height; + + /* constrain height to be the tree height up to a max */ + max_height = (gdk_screen_get_height (gtk_widget_get_screen (widget))) / 4; + + gtk_widget_get_preferred_size (to_size, NULL, &req); + + requisition->height = MIN (req.height, max_height); +} + +static void +setup_list_size_constraint (GtkWidget *widget, + GtkWidget *to_size) +{ + g_signal_connect (widget, + "size-request", + G_CALLBACK (constrain_list_size), + to_size); +} + +static void +gvc_sound_theme_chooser_init (GvcSoundThemeChooser *chooser) +{ + GtkWidget *box; + GtkWidget *label; + GtkWidget *scrolled_window; + GtkWidget *alignment; + char *str; + + chooser->priv = GVC_SOUND_THEME_CHOOSER_GET_PRIVATE (chooser); + + chooser->priv->theme_box = gtk_hbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (chooser), + chooser->priv->theme_box, FALSE, FALSE, 0); + + label = gtk_label_new_with_mnemonic (_("Sound _theme:")); + gtk_box_pack_start (GTK_BOX (chooser->priv->theme_box), label, FALSE, FALSE, 0); + chooser->priv->combo_box = gtk_combo_box_new (); + gtk_box_pack_start (GTK_BOX (chooser->priv->theme_box), chooser->priv->combo_box, FALSE, FALSE, 6); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), chooser->priv->combo_box); + + chooser->priv->client = gconf_client_get_default (); + + str = g_strdup_printf ("%s", _("C_hoose an alert sound:")); + chooser->priv->selection_box = box = gtk_frame_new (str); + g_free (str); + label = gtk_frame_get_label_widget (GTK_FRAME (box)); + gtk_label_set_use_underline (GTK_LABEL (label), TRUE); + gtk_label_set_use_markup (GTK_LABEL (label), TRUE); + gtk_frame_set_shadow_type (GTK_FRAME (box), GTK_SHADOW_NONE); + + alignment = gtk_alignment_new (0, 0, 1, 1); + gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 6, 0, 0, 0); + gtk_container_add (GTK_CONTAINER (alignment), box); + gtk_box_pack_start (GTK_BOX (chooser), alignment, TRUE, TRUE, 6); + + alignment = gtk_alignment_new (0, 0, 1, 1); + gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 6, 0, 0, 0); + gtk_container_add (GTK_CONTAINER (box), alignment); + + chooser->priv->treeview = create_alert_treeview (chooser); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), chooser->priv->treeview); + + scrolled_window = gtk_scrolled_window_new (NULL, NULL); + setup_list_size_constraint (scrolled_window, chooser->priv->treeview); + + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_POLICY_NEVER, + GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_SHADOW_IN); + gtk_container_add (GTK_CONTAINER (scrolled_window), chooser->priv->treeview); + gtk_container_add (GTK_CONTAINER (alignment), scrolled_window); + + chooser->priv->click_feedback_button = gtk_check_button_new_with_mnemonic (_("Enable _window and button sounds")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (chooser->priv->click_feedback_button), + gconf_client_get_bool (chooser->priv->client, INPUT_SOUNDS_KEY, NULL)); + gtk_box_pack_start (GTK_BOX (chooser), + chooser->priv->click_feedback_button, + FALSE, FALSE, 0); + g_signal_connect (chooser->priv->click_feedback_button, + "toggled", + G_CALLBACK (on_click_feedback_toggled), + chooser); + + + gconf_client_add_dir (chooser->priv->client, KEY_SOUNDS_DIR, + GCONF_CLIENT_PRELOAD_ONELEVEL, + NULL); + chooser->priv->sounds_dir_id = gconf_client_notify_add (chooser->priv->client, + KEY_SOUNDS_DIR, + (GConfClientNotifyFunc)on_key_changed, + chooser, NULL, NULL); + gconf_client_add_dir (chooser->priv->client, KEY_METACITY_DIR, + GCONF_CLIENT_PRELOAD_ONELEVEL, + NULL); + chooser->priv->metacity_dir_id = gconf_client_notify_add (chooser->priv->client, + KEY_METACITY_DIR, + (GConfClientNotifyFunc)on_key_changed, + chooser, NULL, NULL); + + /* FIXME: should accept drag and drop themes. should also + add an "Add Theme..." item to the theme combobox */ +} + +static void +gvc_sound_theme_chooser_finalize (GObject *object) +{ + GvcSoundThemeChooser *sound_theme_chooser; + + g_return_if_fail (object != NULL); + g_return_if_fail (GVC_IS_SOUND_THEME_CHOOSER (object)); + + sound_theme_chooser = GVC_SOUND_THEME_CHOOSER (object); + + if (sound_theme_chooser->priv != NULL) { + if (sound_theme_chooser->priv->sounds_dir_id > 0) { + gconf_client_notify_remove (sound_theme_chooser->priv->client, + sound_theme_chooser->priv->sounds_dir_id); + sound_theme_chooser->priv->sounds_dir_id = 0; + } + if (sound_theme_chooser->priv->metacity_dir_id > 0) { + gconf_client_notify_remove (sound_theme_chooser->priv->client, + sound_theme_chooser->priv->metacity_dir_id); + sound_theme_chooser->priv->metacity_dir_id = 0; + } + g_object_unref (sound_theme_chooser->priv->client); + sound_theme_chooser->priv->client = NULL; + } + + G_OBJECT_CLASS (gvc_sound_theme_chooser_parent_class)->finalize (object); +} + +GtkWidget * +gvc_sound_theme_chooser_new (void) +{ + GObject *chooser; + chooser = g_object_new (GVC_TYPE_SOUND_THEME_CHOOSER, + "spacing", 6, + NULL); + return GTK_WIDGET (chooser); +} diff --git a/panels/sound/gvc-sound-theme-chooser.h b/panels/sound/gvc-sound-theme-chooser.h new file mode 100644 index 000000000..6701aad73 --- /dev/null +++ b/panels/sound/gvc-sound-theme-chooser.h @@ -0,0 +1,54 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Red Hat, Inc. + * + * 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. + * + */ + +#ifndef __GVC_SOUND_THEME_CHOOSER_H +#define __GVC_SOUND_THEME_CHOOSER_H + +#include + +G_BEGIN_DECLS + +#define GVC_TYPE_SOUND_THEME_CHOOSER (gvc_sound_theme_chooser_get_type ()) +#define GVC_SOUND_THEME_CHOOSER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GVC_TYPE_SOUND_THEME_CHOOSER, GvcSoundThemeChooser)) +#define GVC_SOUND_THEME_CHOOSER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GVC_TYPE_SOUND_THEME_CHOOSER, GvcSoundThemeChooserClass)) +#define GVC_IS_SOUND_THEME_CHOOSER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVC_TYPE_SOUND_THEME_CHOOSER)) +#define GVC_IS_SOUND_THEME_CHOOSER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GVC_TYPE_SOUND_THEME_CHOOSER)) +#define GVC_SOUND_THEME_CHOOSER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVC_TYPE_SOUND_THEME_CHOOSER, GvcSoundThemeChooserClass)) + +typedef struct GvcSoundThemeChooserPrivate GvcSoundThemeChooserPrivate; + +typedef struct +{ + GtkVBox parent; + GvcSoundThemeChooserPrivate *priv; +} GvcSoundThemeChooser; + +typedef struct +{ + GtkVBoxClass parent_class; +} GvcSoundThemeChooserClass; + +GType gvc_sound_theme_chooser_get_type (void); + +GtkWidget * gvc_sound_theme_chooser_new (void); + +G_END_DECLS + +#endif /* __GVC_SOUND_THEME_CHOOSER_H */ diff --git a/panels/sound/gvc-sound-theme-editor.c b/panels/sound/gvc-sound-theme-editor.c new file mode 100644 index 000000000..cec408e84 --- /dev/null +++ b/panels/sound/gvc-sound-theme-editor.c @@ -0,0 +1,1397 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Bastien Nocera + * Copyright (C) 2008 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 + +#include "gvc-sound-theme-editor.h" +#include "sound-theme-file-utils.h" + +#define GVC_SOUND_THEME_EDITOR_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_SOUND_THEME_EDITOR, GvcSoundThemeEditorPrivate)) + +struct GvcSoundThemeEditorPrivate +{ + GtkWidget *treeview; + GtkWidget *theme_box; + GtkWidget *selection_box; + GtkWidget *click_feedback_button; + GConfClient *client; + guint sounds_dir_id; + guint metacity_dir_id; +}; + +static void gvc_sound_theme_editor_class_init (GvcSoundThemeEditorClass *klass); +static void gvc_sound_theme_editor_init (GvcSoundThemeEditor *sound_theme_editor); +static void gvc_sound_theme_editor_finalize (GObject *object); + +G_DEFINE_TYPE (GvcSoundThemeEditor, gvc_sound_theme_editor, GTK_TYPE_VBOX) + +typedef enum { + CATEGORY_INVALID, + CATEGORY_BELL, + CATEGORY_WINDOWS_BUTTONS, + CATEGORY_DESKTOP, + CATEGORY_ALERTS, + NUM_CATEGORIES +} CategoryType; + +typedef enum { + SOUND_TYPE_NORMAL, + SOUND_TYPE_AUDIO_BELL, + SOUND_TYPE_FEEDBACK +} SoundType; + +static struct { + CategoryType category; + SoundType type; + const char *display_name; + const char *names[6]; +} sounds[20] = { + /* Bell */ + { CATEGORY_BELL, SOUND_TYPE_AUDIO_BELL, NC_("Sound event", "Alert sound"), { "bell-terminal", "bell-window-system", NULL } }, + /* Windows and buttons */ + { CATEGORY_WINDOWS_BUTTONS, -1, NC_("Sound event", "Windows and Buttons"), { NULL } }, + { CATEGORY_WINDOWS_BUTTONS, SOUND_TYPE_FEEDBACK, NC_("Sound event", "Button clicked"), { "button-pressed", "menu-click", "menu-popup", "menu-popdown", "menu-replace", NULL } }, + { CATEGORY_WINDOWS_BUTTONS, SOUND_TYPE_FEEDBACK, NC_("Sound event", "Toggle button clicked"), { "button-toggle-off", "button-toggle-on", NULL } }, + { CATEGORY_WINDOWS_BUTTONS, SOUND_TYPE_FEEDBACK, NC_("Sound event", "Window maximized"), { "window-maximized", NULL } }, + { CATEGORY_WINDOWS_BUTTONS, SOUND_TYPE_FEEDBACK, NC_("Sound event", "Window unmaximized"), { "window-unmaximized", NULL } }, + { CATEGORY_WINDOWS_BUTTONS, SOUND_TYPE_FEEDBACK, NC_("Sound event", "Window minimised"), { "window-minimized", NULL } }, + /* Desktop */ + { CATEGORY_DESKTOP, -1, NC_("Sound event", "Desktop"), { NULL } }, + { CATEGORY_DESKTOP, SOUND_TYPE_NORMAL, NC_("Sound event", "Login"), { "desktop-login", NULL } }, + { CATEGORY_DESKTOP, SOUND_TYPE_NORMAL, NC_("Sound event", "Logout"), { "desktop-logout", NULL } }, + { CATEGORY_DESKTOP, SOUND_TYPE_NORMAL, NC_("Sound event", "New e-mail"), { "message-new-email", NULL } }, + { CATEGORY_DESKTOP, SOUND_TYPE_NORMAL, NC_("Sound event", "Empty trash"), { "trash-empty", NULL } }, + { CATEGORY_DESKTOP, SOUND_TYPE_NORMAL, NC_("Sound event", "Long action completed (download, CD burning, etc.)"), { "complete-copy", "complete-download", "complete-media-burn", "complete-media-rip", "complete-scan", NULL } }, + /* Alerts? */ + { CATEGORY_ALERTS, -1, NC_("Sound event", "Alerts"), { NULL } }, + { CATEGORY_ALERTS, SOUND_TYPE_NORMAL, NC_("Sound event", "Information or question"), { "dialog-information", "dialog-question", NULL } }, + { CATEGORY_ALERTS, SOUND_TYPE_NORMAL, NC_("Sound event", "Warning"), { "dialog-warning", NULL } }, + { CATEGORY_ALERTS, SOUND_TYPE_NORMAL, NC_("Sound event", "Error"), { "dialog-error", NULL } }, + { CATEGORY_ALERTS, SOUND_TYPE_NORMAL, NC_("Sound event", "Battery warning"), { "power-unplug-battery-low", "battery-low", "battery-caution", NULL } }, + /* Finish off */ + { -1, -1, NULL, { NULL } } +}; + +#define KEY_SOUNDS_DIR "/desktop/gnome/sound" +#define EVENT_SOUNDS_KEY KEY_SOUNDS_DIR "/event_sounds" +#define INPUT_SOUNDS_KEY KEY_SOUNDS_DIR "/input_feedback_sounds" +#define SOUND_THEME_KEY KEY_SOUNDS_DIR "/theme_name" +#define KEY_METACITY_DIR "/apps/metacity/general" +#define AUDIO_BELL_KEY KEY_METACITY_DIR "/audible_bell" + +#define CUSTOM_THEME_NAME "__custom" +#define NO_SOUNDS_THEME_NAME "__no_sounds" +#define PREVIEW_BUTTON_XPAD 5 + +enum { + THEME_DISPLAY_COL, + THEME_IDENTIFIER_COL, + THEME_PARENT_ID_COL, + THEME_NUM_COLS +}; + +enum { + SOUND_UNSET, + SOUND_OFF, + SOUND_BUILTIN, + SOUND_CUSTOM, + SOUND_CUSTOM_OLD +}; + +enum { + DISPLAY_COL, + SETTING_COL, + TYPE_COL, + SENSITIVE_COL, + HAS_PREVIEW_COL, + FILENAME_COL, + SOUND_NAMES_COL, + NUM_COLS +}; + +static gboolean +theme_changed_custom_reinit (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + int type; + gboolean sensitive; + + gtk_tree_model_get (model, + iter, + TYPE_COL, &type, + SENSITIVE_COL, &sensitive, -1); + if (type != -1) { + gtk_tree_store_set (GTK_TREE_STORE (model), iter, + SETTING_COL, SOUND_BUILTIN, + HAS_PREVIEW_COL, sensitive, + -1); + } + return FALSE; +} + +static void +on_theme_changed () +{ + /* Don't reinit a custom theme */ + if (strcmp (theme_name, CUSTOM_THEME_NAME) != 0) { + model = gtk_tree_view_get_model (GTK_TREE_VIEW (editor->priv->treeview)); + gtk_tree_model_foreach (model, theme_changed_custom_reinit, NULL); + + /* Delete the custom dir */ + delete_custom_theme_dir (); + + /* And the combo box entry */ + model = gtk_combo_box_get_model (GTK_COMBO_BOX (editor->priv->combo_box)); + gtk_tree_model_get_iter_first (model, &iter); + do { + char *parent; + gtk_tree_model_get (model, &iter, THEME_PARENT_ID_COL, &parent, -1); + if (parent != NULL && strcmp (parent, CUSTOM_THEME_NAME) != 0) { + gtk_list_store_remove (GTK_LIST_STORE (model), &iter); + g_free (parent); + break; + } + g_free (parent); + } while (gtk_tree_model_iter_next (model, &iter)); + } +} + +static char * +load_index_theme_name (const char *index, + char **parent) +{ + GKeyFile *file; + char *indexname = NULL; + gboolean hidden; + + file = g_key_file_new (); + if (g_key_file_load_from_file (file, index, G_KEY_FILE_KEEP_TRANSLATIONS, NULL) == FALSE) { + g_key_file_free (file); + return NULL; + } + /* Don't add hidden themes to the list */ + hidden = g_key_file_get_boolean (file, "Sound Theme", "Hidden", NULL); + if (!hidden) { + indexname = g_key_file_get_locale_string (file, + "Sound Theme", + "Name", + NULL, + NULL); + + /* Save the parent theme, if there's one */ + if (parent != NULL) { + *parent = g_key_file_get_string (file, + "Sound Theme", + "Inherits", + NULL); + } + } + + g_key_file_free (file); + return indexname; +} + +static void +sound_theme_in_dir (GHashTable *hash, + const char *dir) +{ + GDir *d; + const char *name; + + d = g_dir_open (dir, 0, NULL); + if (d == NULL) { + return; + } + + while ((name = g_dir_read_name (d)) != NULL) { + char *dirname, *index, *indexname; + + /* Look for directories */ + dirname = g_build_filename (dir, name, NULL); + if (g_file_test (dirname, G_FILE_TEST_IS_DIR) == FALSE) { + g_free (dirname); + continue; + } + + /* Look for index files */ + index = g_build_filename (dirname, "index.theme", NULL); + g_free (dirname); + + /* Check the name of the theme in the index.theme file */ + indexname = load_index_theme_name (index, NULL); + g_free (index); + if (indexname == NULL) { + continue; + } + + g_hash_table_insert (hash, g_strdup (name), indexname); + } + + g_dir_close (d); +} + +static void +add_theme_to_store (const char *key, + const char *value, + GtkListStore *store) +{ + char *parent; + + parent = NULL; + + /* Get the parent, if we're checking the custom theme */ + if (strcmp (key, CUSTOM_THEME_NAME) == 0) { + char *name, *path; + + path = custom_theme_dir_path ("index.theme"); + name = load_index_theme_name (path, &parent); + g_free (name); + g_free (path); + } + gtk_list_store_insert_with_values (store, NULL, G_MAXINT, + THEME_DISPLAY_COL, value, + THEME_IDENTIFIER_COL, key, + THEME_PARENT_ID_COL, parent, + -1); + g_free (parent); +} + +static void +set_theme_name (GvcSoundThemeEditor *editor, + const char *name) +{ + GConfClient *client; + + g_debug ("setting theme %s", name ? name : "(null)"); + + /* If the name is empty, use "freedesktop" */ + if (name == NULL || *name == '\0') { + name = "freedesktop"; + } + + gconf_client_set_string (editor->priv->client, SOUND_THEME_KEY, theme_name, NULL); +} + +/* Functions to toggle whether the audible bell sound is editable */ +static gboolean +audible_bell_foreach (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + int type; + int setting; + gboolean enabled = GPOINTER_TO_INT (data); + + setting = enabled ? SOUND_BUILTIN : SOUND_OFF; + + gtk_tree_model_get (model, iter, TYPE_COL, &type, -1); + if (type == SOUND_TYPE_AUDIO_BELL) { + gtk_tree_store_set (GTK_TREE_STORE (model), + iter, + SETTING_COL, setting, + HAS_PREVIEW_COL, enabled, + -1); + return TRUE; + } + return FALSE; +} + +static void +set_audible_bell_enabled (GvcSoundThemeEditor *editor, + gboolean enabled) +{ + GtkTreeModel *model; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (editor->priv->treeview)); + gtk_tree_model_foreach (model, audible_bell_foreach, GINT_TO_POINTER (enabled)); +} + +/* Functions to toggle whether the Input feedback sounds are editable */ +static gboolean +input_feedback_foreach (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + int type; + gboolean enabled = GPOINTER_TO_INT (data); + + gtk_tree_model_get (model, iter, TYPE_COL, &type, -1); + if (type == SOUND_TYPE_FEEDBACK) { + gtk_tree_store_set (GTK_TREE_STORE (model), iter, + SENSITIVE_COL, enabled, + HAS_PREVIEW_COL, enabled, + -1); + } + return FALSE; +} + +static void +set_input_feedback_enabled (GvcSoundThemeEditor *editor, + gboolean enabled) +{ + GtkTreeModel *model; + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (editor->priv->click_feedback_button), + enabled); + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (editor->priv->treeview)); + gtk_tree_model_foreach (model, input_feedback_foreach, GINT_TO_POINTER (enabled)); +} + +static int +get_file_type (const char *sound_name, + char **linked_name) +{ + char *name, *filename; + + *linked_name = NULL; + + name = g_strdup_printf ("%s.disabled", sound_name); + filename = custom_theme_dir_path (name); + g_free (name); + + if (g_file_test (filename, G_FILE_TEST_IS_REGULAR) != FALSE) { + g_free (filename); + return SOUND_OFF; + } + g_free (filename); + + /* We only check for .ogg files because those are the + * only ones we create */ + name = g_strdup_printf ("%s.ogg", sound_name); + filename = custom_theme_dir_path (name); + g_free (name); + + if (g_file_test (filename, G_FILE_TEST_IS_SYMLINK) != FALSE) { + *linked_name = g_file_read_link (filename, NULL); + g_free (filename); + return SOUND_CUSTOM; + } + g_free (filename); + + return SOUND_BUILTIN; +} + +static gboolean +theme_changed_custom_init (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + char **sound_names; + + gtk_tree_model_get (model, iter, SOUND_NAMES_COL, &sound_names, -1); + if (sound_names != NULL) { + char *filename; + int type; + + type = get_file_type (sound_names[0], &filename); + + gtk_tree_store_set (GTK_TREE_STORE (model), iter, + SETTING_COL, type, + HAS_PREVIEW_COL, type != SOUND_OFF, + FILENAME_COL, filename, + -1); + g_strfreev (sound_names); + g_free (filename); + } + return FALSE; +} + +static void +update_theme (GvcSoundThemeEditor *editor) +{ + char *theme_name; + gboolean events_enabled; + gboolean bell_enabled; + GConfClient *client; + gboolean feedback_enabled; + + client = editor->priv->client; + + bell_enabled = gconf_client_get_bool (client, AUDIO_BELL_KEY, NULL); + set_audible_bell_enabled (editor, bell_enabled); + + feedback_enabled = gconf_client_get_bool (client, INPUT_SOUNDS_KEY, NULL); + set_input_feedback_enabled (editor, feedback_enabled); + + events_enabled = gconf_client_get_bool (client, EVENT_SOUNDS_KEY, NULL); + if (events_enabled) { + theme_name = gconf_client_get_string (client, SOUND_THEME_KEY, NULL); + } else { + theme_name = g_strdup (NO_SOUNDS_THEME_NAME); + } + + gtk_widget_set_sensitive (editor->priv->selection_box, events_enabled); + + set_theme_name (editor, theme_name); + + /* Setup the default values if we're using the custom theme */ + if (theme_name != NULL && strcmp (theme_name, CUSTOM_THEME_NAME) == 0) { + GtkTreeModel *model; + model = gtk_tree_view_get_model (GTK_TREE_VIEW (editor->priv->treeview)); + gtk_tree_model_foreach (model, + theme_changed_custom_init, + NULL); + } + g_free (theme_name); +} + +static void +setup_theme_selector (GvcSoundThemeEditor *editor) +{ + GHashTable *hash; + GtkListStore *store; + GtkCellRenderer *renderer; + const char * const *data_dirs; + const char *data_dir; + char *dir; + guint i; + + /* Add the theme names and their display name to a hash table, + * makes it easy to avoid duplicate themes */ + hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + + data_dirs = g_get_system_data_dirs (); + for (i = 0; data_dirs[i] != NULL; i++) { + dir = g_build_filename (data_dirs[i], "sounds", NULL); + sound_theme_in_dir (hash, dir); + g_free (dir); + } + + data_dir = g_get_user_data_dir (); + dir = g_build_filename (data_dir, "sounds", NULL); + sound_theme_in_dir (hash, dir); + g_free (dir); + + /* If there isn't at least one theme, make everything + * insensitive, LAME! */ + if (g_hash_table_size (hash) == 0) { + gtk_widget_set_sensitive (GTK_WIDGET (editor), FALSE); + g_warning ("Bad setup, install the freedesktop sound theme"); + g_hash_table_destroy (hash); + return; + } + + /* Setup the tree model, 3 columns: + * - internal theme name/directory + * - display theme name + * - the internal id for the parent theme, used for the custom theme */ + store = gtk_list_store_new (THEME_NUM_COLS, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING); + + /* Add the themes to a combobox */ + gtk_list_store_insert_with_values (store, + NULL, + G_MAXINT, + THEME_DISPLAY_COL, _("No sounds"), + THEME_IDENTIFIER_COL, "__no_sounds", + THEME_PARENT_ID_COL, NULL, + -1); + g_hash_table_foreach (hash, (GHFunc) add_theme_to_store, store); + g_hash_table_destroy (hash); + + /* Set the display */ + gtk_combo_box_set_model (GTK_COMBO_BOX (editor->priv->combo_box), + GTK_TREE_MODEL (store)); + + renderer = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (editor->priv->combo_box), + renderer, + TRUE); + gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (editor->priv->combo_box), + renderer, + "text", THEME_DISPLAY_COL, + NULL); + + g_signal_connect (editor->priv->combo_box, + "changed", + G_CALLBACK (on_combobox_changed), + editor); +} + +static void +play_sound_preview (GtkFileEditor *editor, + gpointer user_data) +{ + char *filename; + + filename = gtk_file_editor_get_preview_filename (GTK_FILE_EDITOR (editor)); + if (filename == NULL) { + return; + } + + ca_gtk_play_for_widget (GTK_WIDGET (editor), 0, + CA_PROP_APPLICATION_NAME, _("Sound Preferences"), + CA_PROP_MEDIA_FILENAME, filename, + CA_PROP_EVENT_DESCRIPTION, _("Testing event sound"), + CA_PROP_CANBERRA_CACHE_CONTROL, "never", + CA_PROP_APPLICATION_ID, "org.gnome.VolumeControl", +#ifdef CA_PROP_CANBERRA_ENABLE + CA_PROP_CANBERRA_ENABLE, "1", +#endif + NULL); + g_free (filename); +} + +static char * +get_sound_filename (GvcSoundThemeEditor *editor) +{ + GtkWidget *file_editor; + GtkWidget *toplevel; + GtkWindow *parent; + int response; + char *filename; + char *path; + const char * const *data_dirs, *data_dir; + GtkFileFilter *filter; + guint i; + + /* Try to get the parent window of the widget */ + toplevel = gtk_widget_get_toplevel (GTK_WIDGET (editor)); + if (gtk_widget_is_toplevel (toplevel) != FALSE) + parent = GTK_WINDOW (toplevel); + else + parent = NULL; + + file_editor = gtk_file_editor_dialog_new (_("Select Sound File"), + parent, + GTK_FILE_EDITOR_ACTION_OPEN, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, + NULL); + + gtk_file_editor_set_local_only (GTK_FILE_EDITOR (file_editor), TRUE); + gtk_file_editor_set_select_multiple (GTK_FILE_EDITOR (file_editor), FALSE); + + filter = gtk_file_filter_new (); + gtk_file_filter_set_name (filter, _("Sound files")); + gtk_file_filter_add_mime_type (filter, "audio/x-vorbis+ogg"); + gtk_file_filter_add_mime_type (filter, "audio/x-wav"); + gtk_file_editor_add_filter (GTK_FILE_EDITOR (file_editor), filter); + gtk_file_editor_set_filter (GTK_FILE_EDITOR (file_editor), filter); + + g_signal_connect (file_editor, "update-preview", + G_CALLBACK (play_sound_preview), NULL); + + data_dirs = g_get_system_data_dirs (); + for (i = 0; data_dirs[i] != NULL; i++) { + path = g_build_filename (data_dirs[i], "sounds", NULL); + gtk_file_editor_add_shortcut_folder (GTK_FILE_EDITOR (file_editor), path, NULL); + g_free (path); + } + data_dir = g_get_user_special_dir (G_USER_DIRECTORY_MUSIC); + if (data_dir != NULL) + gtk_file_editor_add_shortcut_folder (GTK_FILE_EDITOR (file_editor), data_dir, NULL); + + gtk_file_editor_set_current_folder (GTK_FILE_EDITOR (file_editor), SOUND_DATA_DIR); + + response = gtk_dialog_run (GTK_DIALOG (file_editor)); + filename = NULL; + if (response == GTK_RESPONSE_ACCEPT) + filename = gtk_file_editor_get_filename (GTK_FILE_EDITOR (file_editor)); + + gtk_widget_destroy (file_editor); + + return filename; +} + + +static gboolean +count_customised_sounds (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + int *num_custom) +{ + int type; + int setting; + + gtk_tree_model_get (model, iter, TYPE_COL, &type, SETTING_COL, &setting, -1); + if (setting == SOUND_OFF || setting == SOUND_CUSTOM || setting == SOUND_CUSTOM_OLD) { + (*num_custom)++; + } + + return FALSE; +} + +static gboolean +save_sounds (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + int type; + int setting; + char *filename; + char **sounds; + + gtk_tree_model_get (model, iter, + TYPE_COL, &type, + SETTING_COL, &setting, + FILENAME_COL, &filename, + SOUND_NAMES_COL, &sounds, + -1); + + if (setting == SOUND_BUILTIN) { + delete_old_files (sounds); + delete_disabled_files (sounds); + } else if (setting == SOUND_OFF) { + delete_old_files (sounds); + add_disabled_file (sounds); + } else if (setting == SOUND_CUSTOM || setting == SOUND_CUSTOM_OLD) { + delete_old_files (sounds); + delete_disabled_files (sounds); + add_custom_file (sounds, filename); + } + g_free (filename); + g_strfreev (sounds); + + return FALSE; +} + +static void +save_custom_theme (GtkTreeModel *model, + const char *parent) +{ + GKeyFile *keyfile; + char *data; + char *path; + + /* Create the custom directory */ + path = custom_theme_dir_path (NULL); + g_mkdir_with_parents (path, 0755); + g_free (path); + + /* Save the sounds themselves */ + gtk_tree_model_foreach (model, (GtkTreeModelForeachFunc) save_sounds, NULL); + + /* Set the data for index.theme */ + keyfile = g_key_file_new (); + g_key_file_set_string (keyfile, "Sound Theme", "Name", _("Custom")); + g_key_file_set_string (keyfile, "Sound Theme", "Inherits", parent); + g_key_file_set_string (keyfile, "Sound Theme", "Directories", "."); + data = g_key_file_to_data (keyfile, NULL, NULL); + g_key_file_free (keyfile); + + /* Save the index.theme */ + path = custom_theme_dir_path ("index.theme"); + g_file_set_contents (path, data, -1, NULL); + g_free (path); + g_free (data); + + custom_theme_update_time (); +} + +static void +dump_theme (GvcSoundThemeEditor *editor) +{ + int num_custom; + GtkTreeModel *model; + GtkTreeIter iter; + char *parent; + + num_custom = 0; + model = gtk_tree_view_get_model (GTK_TREE_VIEW (editor->priv->treeview)); + gtk_tree_model_foreach (model, (GtkTreeModelForeachFunc) count_customised_sounds, &num_custom); + + g_debug ("%d customised sounds", num_custom); + + model = gtk_combo_box_get_model (GTK_COMBO_BOX (editor->priv->combo_box)); + /* Get the current theme's name, and set the parent */ + if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (editor->priv->combo_box), &iter) == FALSE) + return; + + if (num_custom == 0) { + gtk_tree_model_get (model, &iter, THEME_PARENT_ID_COL, &parent, -1); + if (parent != NULL) { + set_theme_name (editor, parent); + g_free (parent); + } + gtk_tree_model_get_iter_first (model, &iter); + do { + gtk_tree_model_get (model, &iter, THEME_PARENT_ID_COL, &parent, -1); + if (parent != NULL && strcmp (parent, CUSTOM_THEME_NAME) != 0) { + gtk_list_store_remove (GTK_LIST_STORE (model), &iter); + break; + } + } while (gtk_tree_model_iter_next (model, &iter)); + + delete_custom_theme_dir (); + } else { + gtk_tree_model_get (model, &iter, THEME_IDENTIFIER_COL, &parent, -1); + if (strcmp (parent, CUSTOM_THEME_NAME) != 0) { + gtk_list_store_insert_with_values (GTK_LIST_STORE (model), NULL, G_MAXINT, + THEME_DISPLAY_COL, _("Custom"), + THEME_IDENTIFIER_COL, CUSTOM_THEME_NAME, + THEME_PARENT_ID_COL, parent, + -1); + } else { + g_free (parent); + gtk_tree_model_get (model, &iter, THEME_PARENT_ID_COL, &parent, -1); + } + + g_debug ("The parent theme is: %s", parent); + model = gtk_tree_view_get_model (GTK_TREE_VIEW (editor->priv->treeview)); + save_custom_theme (model, parent); + g_free (parent); + + set_theme_name (editor, CUSTOM_THEME_NAME); + } +} + +static void +on_setting_column_edited (GtkCellRendererText *renderer, + char *path, + char *new_text, + GvcSoundThemeEditor *editor) +{ + GtkTreeModel *model; + GtkTreeModel *tree_model; + GtkTreeIter iter; + GtkTreeIter tree_iter; + SoundType type; + char *text; + char *old_filename; + int setting; + + if (new_text == NULL) { + return; + } + + g_object_get (renderer, + "model", &model, + NULL); + + tree_model = gtk_tree_view_get_model (GTK_TREE_VIEW (editor->priv->treeview)); + if (gtk_tree_model_get_iter_from_string (tree_model, &tree_iter, path) == FALSE) + return; + + gtk_tree_model_get (tree_model, &tree_iter, + TYPE_COL, &type, + FILENAME_COL, &old_filename, + -1); + + gtk_tree_model_get_iter_first (model, &iter); + do { + int cmp; + + gtk_tree_model_get (model, &iter, + 0, &text, + 1, &setting, + -1); + cmp = g_utf8_collate (text, new_text); + g_free (text); + + if (cmp != 0) { + continue; + } + + if (type == SOUND_TYPE_NORMAL + || type == SOUND_TYPE_FEEDBACK + || type == SOUND_TYPE_AUDIO_BELL) { + + if (setting == SOUND_CUSTOM + || (setting == SOUND_CUSTOM_OLD + && old_filename == NULL)) { + + char *filename = get_sound_filename (editor); + + if (filename == NULL) { + break; + } + gtk_tree_store_set (GTK_TREE_STORE (tree_model), + &tree_iter, + SETTING_COL, setting, + HAS_PREVIEW_COL, setting != SOUND_OFF, + FILENAME_COL, filename, + -1); + g_free (filename); + } else if (setting == SOUND_CUSTOM_OLD) { + gtk_tree_store_set (GTK_TREE_STORE (tree_model), + &tree_iter, + SETTING_COL, setting, + HAS_PREVIEW_COL, setting != SOUND_OFF, + FILENAME_COL, old_filename, + -1); + } else { + gtk_tree_store_set (GTK_TREE_STORE (tree_model), + &tree_iter, + SETTING_COL, setting, + HAS_PREVIEW_COL, setting != SOUND_OFF, + -1); + } + + g_debug ("Something changed, dump theme"); + dump_theme (editor); + + break; + } + + g_assert_not_reached (); + + } while (gtk_tree_model_iter_next (model, &iter)); + + g_free (old_filename); +} + +static void +fill_custom_model (GtkListStore *store, + const char *prev_filename) +{ + GtkTreeIter iter; + + gtk_list_store_clear (store); + + if (prev_filename != NULL) { + char *display; + display = g_filename_display_basename (prev_filename); + gtk_list_store_insert_with_values (store, &iter, G_MAXINT, + 0, display, + 1, SOUND_CUSTOM_OLD, + -1); + g_free (display); + } + + gtk_list_store_insert_with_values (store, &iter, G_MAXINT, + 0, _("Default"), + 1, SOUND_BUILTIN, + -1); + gtk_list_store_insert_with_values (store, &iter, G_MAXINT, + 0, _("Disabled"), + 1, SOUND_OFF, + -1); + gtk_list_store_insert_with_values (store, &iter, G_MAXINT, + 0, _("Custom…"), + 1, SOUND_CUSTOM, -1); +} + +static void +on_combobox_editing_started (GtkCellRenderer *renderer, + GtkCellEditable *editable, + gchar *path, + GvcSoundThemeEditor *editor) +{ + GtkTreeModel *model; + GtkTreeModel *store; + GtkTreeIter iter; + SoundType type; + char *filename; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (editor->priv->treeview)); + if (gtk_tree_model_get_iter_from_string (model, &iter, path) == FALSE) { + return; + } + + gtk_tree_model_get (model, &iter, TYPE_COL, &type, FILENAME_COL, &filename, -1); + g_object_get (renderer, "model", &store, NULL); + fill_custom_model (GTK_LIST_STORE (store), filename); + g_free (filename); +} + +static gboolean +play_sound_at_path (GtkWidget *tree_view, + GtkTreePath *path) +{ + GtkTreeModel *model; + GtkTreeIter iter; + char **sound_names; + gboolean sensitive; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree_view)); + if (gtk_tree_model_get_iter (model, &iter, path) == FALSE) { + return FALSE; + } + + gtk_tree_model_get (model, &iter, + SOUND_NAMES_COL, &sound_names, + SENSITIVE_COL, &sensitive, + -1); + if (!sensitive || sound_names == NULL) { + return FALSE; + } + + ca_gtk_play_for_widget (GTK_WIDGET (tree_view), 0, + CA_PROP_APPLICATION_NAME, _("Sound Preferences"), + CA_PROP_EVENT_ID, sound_names[0], + CA_PROP_EVENT_DESCRIPTION, _("Testing event sound"), + CA_PROP_CANBERRA_CACHE_CONTROL, "never", + CA_PROP_APPLICATION_ID, "org.gnome.VolumeControl", +#ifdef CA_PROP_CANBERRA_ENABLE + CA_PROP_CANBERRA_ENABLE, "1", +#endif + NULL); + + g_strfreev (sound_names); + + return TRUE; +} + +static void +setting_set_func (GtkTreeViewColumn *tree_column, + GtkCellRenderer *cell, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + int setting; + char *filename; + SoundType type; + + gtk_tree_model_get (model, iter, + SETTING_COL, &setting, + FILENAME_COL, &filename, + TYPE_COL, &type, + -1); + + if (setting == SOUND_UNSET) { + g_object_set (cell, + "visible", FALSE, + NULL); + g_free (filename); + return; + } + + if (setting == SOUND_OFF) { + g_object_set (cell, + "text", _("Disabled"), + NULL); + } else if (setting == SOUND_BUILTIN) { + g_object_set (cell, + "text", _("Default"), + NULL); + } else if (setting == SOUND_CUSTOM || setting == SOUND_CUSTOM_OLD) { + char *display; + + display = g_filename_display_basename (filename); + g_object_set (cell, + "text", display, + NULL); + g_free (display); + } + + g_free (filename); +} + +typedef GtkCellRendererPixbuf ActivatableCellRendererPixbuf; +typedef GtkCellRendererPixbufClass ActivatableCellRendererPixbufClass; + +GType activatable_cell_renderer_pixbuf_get_type (void); +#define ACTIVATABLE_TYPE_CELL_RENDERER_PIXBUF (activatable_cell_renderer_pixbuf_get_type ()) +G_DEFINE_TYPE (ActivatableCellRendererPixbuf, activatable_cell_renderer_pixbuf, GTK_TYPE_CELL_RENDERER_PIXBUF); + +static gboolean +activatable_cell_renderer_pixbuf_activate (GtkCellRenderer *cell, + GdkEvent *event, + GtkWidget *widget, + const gchar *path_string, + GdkRectangle *background_area, + GdkRectangle *cell_area, + GtkCellRendererState flags) +{ + GtkTreePath *path; + gboolean res; + + g_debug ("Activating pixbuf"); + + path = gtk_tree_path_new_from_string (path_string); + res = play_sound_at_path (widget, path); + gtk_tree_path_free (path); + + return res; +} + +static void +activatable_cell_renderer_pixbuf_init (ActivatableCellRendererPixbuf *cell) +{ +} + +static void +activatable_cell_renderer_pixbuf_class_init (ActivatableCellRendererPixbufClass *class) +{ + GtkCellRendererClass *cell_class; + + cell_class = GTK_CELL_RENDERER_CLASS (class); + + cell_class->activate = activatable_cell_renderer_pixbuf_activate; +} + +static void +setup_theme_custom_selector (GvcSoundThemeEditor *editor, + gboolean have_xkb ) +{ + GtkTreeStore *store; + GtkTreeModel *custom_model; + GtkTreeViewColumn *column; + GtkCellRenderer *renderer; + GtkTreeIter iter; + GtkTreeIter parent; + CategoryType type; + guint i; + + /* Set up the model for the custom view */ + store = gtk_tree_store_new (NUM_COLS, + G_TYPE_STRING, + G_TYPE_INT, + G_TYPE_INT, + G_TYPE_BOOLEAN, + G_TYPE_BOOLEAN, + G_TYPE_STRING, + G_TYPE_STRV); + + /* The first column with the categories/sound names */ + renderer = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes ("Display", renderer, + "text", DISPLAY_COL, + "sensitive", SENSITIVE_COL, + "ellipsize", PANGO_ELLIPSIZE_START, + "ellipsize-set", TRUE, + NULL); + g_object_set (G_OBJECT (column), "expand", TRUE, NULL); + + gtk_tree_view_append_column (GTK_TREE_VIEW (editor->priv->treeview), column); + + /* The 2nd column with the sound settings */ + renderer = gtk_cell_renderer_combo_new (); + g_signal_connect (renderer, + "edited", + G_CALLBACK (on_setting_column_edited), + editor); + g_signal_connect (renderer, + "editing-started", + G_CALLBACK (on_combobox_editing_started), + editor); + custom_model = GTK_TREE_MODEL (gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_INT)); + fill_custom_model (GTK_LIST_STORE (custom_model), NULL); + + g_object_set (renderer, + "model", custom_model, + "has-entry", FALSE, + "editable", TRUE, + "text-column", 0, + NULL); + column = gtk_tree_view_column_new_with_attributes ("Setting", renderer, + "editable", SENSITIVE_COL, + "sensitive", SENSITIVE_COL, + "visible", TRUE, + NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (editor->priv->treeview), column); + gtk_tree_view_column_set_cell_data_func (column, renderer, setting_set_func, NULL, NULL); + + /* The 3rd column with the preview pixbuf */ + renderer = g_object_new (ACTIVATABLE_TYPE_CELL_RENDERER_PIXBUF, NULL); + g_object_set (renderer, + "mode", GTK_CELL_RENDERER_MODE_ACTIVATABLE, + "icon-name", "media-playback-start", + "stock-size", GTK_ICON_SIZE_MENU, + NULL); + column = gtk_tree_view_column_new_with_attributes ("Preview", renderer, + "visible", HAS_PREVIEW_COL, + NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (editor->priv->treeview), column); + g_object_set_data (G_OBJECT (editor->priv->treeview), "preview-column", column); + + gtk_tree_view_set_model (GTK_TREE_VIEW (editor->priv->treeview), GTK_TREE_MODEL (store)); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (editor->priv->treeview), FALSE); + + /* Fill in the model */ + type = CATEGORY_INVALID; + + for (i = 0; ; i++) { + GtkTreeIter *_parent; + + if (sounds[i].category == -1) { + break; + } + + /* Is it a new type of sound? */ + if (sounds[i].category == type + && type != CATEGORY_INVALID + && type != CATEGORY_BELL) { + _parent = &parent; + } else { + _parent = NULL; + } + + if (sounds[i].type != -1) { + gtk_tree_store_insert_with_values (store, &iter, _parent, G_MAXINT, + DISPLAY_COL, g_dpgettext2 (NULL, "Sound event", sounds[i].display_name), + SETTING_COL, SOUND_BUILTIN, + TYPE_COL, sounds[i].type, + SOUND_NAMES_COL, sounds[i].names, + HAS_PREVIEW_COL, TRUE, + SENSITIVE_COL, TRUE, + -1); + } else { + /* Category */ + gtk_tree_store_insert_with_values (store, &iter, _parent, G_MAXINT, + DISPLAY_COL, g_dpgettext2 (NULL, "Sound event", sounds[i].display_name), + SETTING_COL, SOUND_UNSET, + TYPE_COL, sounds[i].type, + SENSITIVE_COL, TRUE, + HAS_PREVIEW_COL, FALSE, + -1); + } + + /* If we didn't set a parent already, set one in case we need it later */ + if (_parent == NULL) { + parent = iter; + } + type = sounds[i].category; + } + + gtk_tree_view_expand_all (GTK_TREE_VIEW (editor->priv->treeview)); +} + +static GObject * +gvc_sound_theme_editor_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_params) +{ + GObject *object; + GvcSoundThemeEditor *self; + + object = G_OBJECT_CLASS (gvc_sound_theme_editor_parent_class)->constructor (type, n_construct_properties, construct_params); + + self = GVC_SOUND_THEME_EDITOR (object); + + setup_theme_selector (self); + setup_theme_custom_selector (self, TRUE); + + update_theme (self); + + return object; +} + +static void +gvc_sound_theme_editor_class_init (GvcSoundThemeEditorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructor = gvc_sound_theme_editor_constructor; + object_class->finalize = gvc_sound_theme_editor_finalize; + + g_type_class_add_private (klass, sizeof (GvcSoundThemeEditorPrivate)); +} + +static void +on_click_feedback_toggled (GtkToggleButton *button, + GvcSoundThemeEditor *editor) +{ + GConfClient *client; + gboolean enabled; + + enabled = gtk_toggle_button_get_active (button); + + client = gconf_client_get_default (); + gconf_client_set_bool (client, INPUT_SOUNDS_KEY, enabled, NULL); + g_object_unref (client); +} + +static void +on_key_changed (GConfClient *client, + guint cnxn_id, + GConfEntry *entry, + GvcSoundThemeEditor *editor) +{ + const char *key; + GConfValue *value; + + key = gconf_entry_get_key (entry); + + if (! g_str_has_prefix (key, KEY_SOUNDS_DIR) + && ! g_str_has_prefix (key, KEY_METACITY_DIR)) { + return; + } + + value = gconf_entry_get_value (entry); + if (strcmp (key, EVENT_SOUNDS_KEY) == 0) { + update_theme (editor); + } else if (strcmp (key, SOUND_THEME_KEY) == 0) { + update_theme (editor); + } else if (strcmp (key, INPUT_SOUNDS_KEY) == 0) { + update_theme (editor); + } else if (strcmp (key, AUDIO_BELL_KEY) == 0) { + update_theme (editor); + } +} + +static void +on_treeview_row_activated (GtkTreeView *treeview, + GtkTreePath *path, + GtkTreeViewColumn *column, + GvcSoundThemeEditor *editor) +{ + g_debug ("row activated"); + play_sound_at_path (GTK_WIDGET (treeview), path); +} + +static void +constrain_list_size (GtkWidget *widget, + GtkRequisition *requisition, + GtkWidget *to_size) +{ + GtkRequisition req; + int max_height; + + /* constrain height to be the tree height up to a max */ + max_height = (gdk_screen_get_height (gtk_widget_get_screen (widget))) / 4; + + gtk_widget_get_preferred_size (to_size, NULL, &req); + + requisition->height = MIN (req.height, max_height); +} + +static void +setup_list_size_constraint (GtkWidget *widget, + GtkWidget *to_size) +{ + g_signal_connect (widget, + "size-request", + G_CALLBACK (constrain_list_size), + to_size); +} + +static void +gvc_sound_theme_editor_init (GvcSoundThemeEditor *editor) +{ + GtkWidget *box; + GtkWidget *label; + GtkWidget *scrolled_window; + + editor->priv = GVC_SOUND_THEME_EDITOR_GET_PRIVATE (editor); + + editor->priv->theme_box = gtk_hbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX (editor), + editor->priv->theme_box, FALSE, FALSE, 0); + + label = gtk_label_new (_("Sound Theme:")); + gtk_box_pack_start (GTK_BOX (editor->priv->theme_box), label, FALSE, FALSE, 6); + editor->priv->combo_box = gtk_combo_box_new (); + gtk_box_pack_start (GTK_BOX (editor->priv->theme_box), editor->priv->combo_box, FALSE, FALSE, 0); + + + editor->priv->client = gconf_client_get_default (); + + editor->priv->selection_box = box = gtk_vbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (editor), box, TRUE, TRUE, 0); + + editor->priv->treeview = gtk_tree_view_new (); + g_signal_connect (editor->priv->treeview, + "row-activated", + G_CALLBACK (on_treeview_row_activated), + editor); + + scrolled_window = gtk_scrolled_window_new (NULL, NULL); + setup_list_size_constraint (scrolled_window, editor->priv->treeview); + + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_POLICY_NEVER, + GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_SHADOW_IN); + gtk_container_add (GTK_CONTAINER (scrolled_window), editor->priv->treeview); + gtk_container_add (GTK_CONTAINER (box), scrolled_window); + + editor->priv->click_feedback_button = gtk_check_button_new_with_mnemonic (_("Enable window and button sounds")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (editor->priv->click_feedback_button), + gconf_client_get_bool (editor->priv->client, INPUT_SOUNDS_KEY, NULL)); + gtk_box_pack_start (GTK_BOX (box), + editor->priv->click_feedback_button, + FALSE, FALSE, 0); + g_signal_connect (editor->priv->click_feedback_button, + "toggled", + G_CALLBACK (on_click_feedback_toggled), + editor); + + + gconf_client_add_dir (editor->priv->client, KEY_SOUNDS_DIR, + GCONF_CLIENT_PRELOAD_ONELEVEL, + NULL); + editor->priv->sounds_dir_id = gconf_client_notify_add (editor->priv->client, + KEY_SOUNDS_DIR, + (GConfClientNotifyFunc)on_key_changed, + editor, NULL, NULL); + gconf_client_add_dir (editor->priv->client, KEY_METACITY_DIR, + GCONF_CLIENT_PRELOAD_ONELEVEL, + NULL); + editor->priv->metacity_dir_id = gconf_client_notify_add (editor->priv->client, + KEY_METACITY_DIR, + (GConfClientNotifyFunc)on_key_changed, + editor, NULL, NULL); + + /* FIXME: should accept drag and drop themes. should also + add an "Add Theme..." item to the theme combobox */ +} + +static void +gvc_sound_theme_editor_finalize (GObject *object) +{ + GvcSoundThemeEditor *sound_theme_editor; + + g_return_if_fail (object != NULL); + g_return_if_fail (GVC_IS_SOUND_THEME_EDITOR (object)); + + sound_theme_editor = GVC_SOUND_THEME_EDITOR (object); + + if (sound_theme_editor->priv != NULL) { + if (sound_theme_editor->priv->sounds_dir_id > 0) { + gconf_client_notify_remove (sound_theme_editor->priv->client, + sound_theme_editor->priv->sounds_dir_id); + sound_theme_editor->priv->sounds_dir_id = 0; + } + if (sound_theme_editor->priv->metacity_dir_id > 0) { + gconf_client_notify_remove (sound_theme_editor->priv->client, + sound_theme_editor->priv->metacity_dir_id); + sound_theme_editor->priv->metacity_dir_id = 0; + } + g_object_unref (sound_theme_editor->priv->client); + sound_theme_editor->priv->client = NULL; + } + + G_OBJECT_CLASS (gvc_sound_theme_editor_parent_class)->finalize (object); +} + +GtkWidget * +gvc_sound_theme_editor_new (void) +{ + GObject *editor; + editor = g_object_new (GVC_TYPE_SOUND_THEME_EDITOR, + "spacing", 6, + NULL); + return GTK_WIDGET (editor); +} diff --git a/panels/sound/gvc-sound-theme-editor.h b/panels/sound/gvc-sound-theme-editor.h new file mode 100644 index 000000000..5e4330d70 --- /dev/null +++ b/panels/sound/gvc-sound-theme-editor.h @@ -0,0 +1,54 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Red Hat, Inc. + * + * 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. + * + */ + +#ifndef __GVC_SOUND_THEME_EDITOR_H +#define __GVC_SOUND_THEME_EDITOR_H + +#include + +G_BEGIN_DECLS + +#define GVC_TYPE_SOUND_THEME_EDITOR (gvc_sound_theme_editor_get_type ()) +#define GVC_SOUND_THEME_EDITOR(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GVC_TYPE_SOUND_THEME_EDITOR, GvcSoundThemeEditor)) +#define GVC_SOUND_THEME_EDITOR_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GVC_TYPE_SOUND_THEME_EDITOR, GvcSoundThemeEditorClass)) +#define GVC_IS_SOUND_THEME_EDITOR(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVC_TYPE_SOUND_THEME_EDITOR)) +#define GVC_IS_SOUND_THEME_EDITOR_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GVC_TYPE_SOUND_THEME_EDITOR)) +#define GVC_SOUND_THEME_EDITOR_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVC_TYPE_SOUND_THEME_EDITOR, GvcSoundThemeEditorClass)) + +typedef struct GvcSoundThemeEditorPrivate GvcSoundThemeEditorPrivate; + +typedef struct +{ + GtkVBox parent; + GvcSoundThemeEditorPrivate *priv; +} GvcSoundThemeEditor; + +typedef struct +{ + GtkVBoxClass parent_class; +} GvcSoundThemeEditorClass; + +GType gvc_sound_theme_editor_get_type (void); + +GtkWidget * gvc_sound_theme_editor_new (void); + +G_END_DECLS + +#endif /* __GVC_SOUND_THEME_EDITOR_H */ diff --git a/panels/sound/gvc-speaker-test.c b/panels/sound/gvc-speaker-test.c new file mode 100644 index 000000000..8b56c48a7 --- /dev/null +++ b/panels/sound/gvc-speaker-test.c @@ -0,0 +1,500 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2009 Bastien Nocera + * + * 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 +#include + +#include "gvc-speaker-test.h" +#include "gvc-mixer-stream.h" +#include "gvc-mixer-card.h" + +#define GVC_SPEAKER_TEST_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_SPEAKER_TEST, GvcSpeakerTestPrivate)) + +struct GvcSpeakerTestPrivate +{ + GtkWidget *channel_controls[PA_CHANNEL_POSITION_MAX]; + ca_context *canberra; + GvcMixerCard *card; + GvcMixerControl *control; +}; + +enum { + COL_NAME, + COL_HUMAN_NAME, + NUM_COLS +}; + +enum { + PROP_0, + PROP_CARD, + PROP_CONTROL +}; + +static void gvc_speaker_test_class_init (GvcSpeakerTestClass *klass); +static void gvc_speaker_test_init (GvcSpeakerTest *speaker_test); +static void gvc_speaker_test_finalize (GObject *object); +static void update_channel_map (GvcSpeakerTest *speaker_test); + +G_DEFINE_TYPE (GvcSpeakerTest, gvc_speaker_test, GTK_TYPE_TABLE) + +static const int position_table[] = { + /* Position, X, Y */ + PA_CHANNEL_POSITION_FRONT_LEFT, 0, 0, + PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER, 1, 0, + PA_CHANNEL_POSITION_FRONT_CENTER, 2, 0, + PA_CHANNEL_POSITION_MONO, 2, 0, + PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER, 3, 0, + PA_CHANNEL_POSITION_FRONT_RIGHT, 4, 0, + PA_CHANNEL_POSITION_SIDE_LEFT, 0, 1, + PA_CHANNEL_POSITION_SIDE_RIGHT, 4, 1, + PA_CHANNEL_POSITION_REAR_LEFT, 0, 2, + PA_CHANNEL_POSITION_REAR_CENTER, 2, 2, + PA_CHANNEL_POSITION_REAR_RIGHT, 4, 2, + PA_CHANNEL_POSITION_LFE, 3, 2 +}; + +static void +gvc_speaker_test_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GvcSpeakerTest *self = GVC_SPEAKER_TEST (object); + + switch (prop_id) { + case PROP_CARD: + self->priv->card = g_value_dup_object (value); + if (self->priv->control != NULL) + update_channel_map (self); + break; + case PROP_CONTROL: + self->priv->control = g_value_dup_object (value); + if (self->priv->card != NULL) + update_channel_map (self); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gvc_speaker_test_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GvcSpeakerTest *self = GVC_SPEAKER_TEST (object); + + switch (prop_id) { + case PROP_CARD: + g_value_set_object (value, self->priv->card); + break; + case PROP_CONTROL: + g_value_set_object (value, self->priv->control); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gvc_speaker_test_class_init (GvcSpeakerTestClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gvc_speaker_test_finalize; + object_class->set_property = gvc_speaker_test_set_property; + object_class->get_property = gvc_speaker_test_get_property; + + g_object_class_install_property (object_class, + PROP_CARD, + g_param_spec_object ("card", + "card", + "The card", + GVC_TYPE_MIXER_CARD, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, + PROP_CONTROL, + g_param_spec_object ("control", + "control", + "The mixer controller", + GVC_TYPE_MIXER_CONTROL, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY)); + g_type_class_add_private (klass, sizeof (GvcSpeakerTestPrivate)); +} + +static const char * +sound_name (pa_channel_position_t position) +{ + switch (position) { + case PA_CHANNEL_POSITION_FRONT_LEFT: + return "audio-channel-front-left"; + case PA_CHANNEL_POSITION_FRONT_RIGHT: + return "audio-channel-front-right"; + case PA_CHANNEL_POSITION_FRONT_CENTER: + return "audio-channel-front-center"; + case PA_CHANNEL_POSITION_REAR_LEFT: + return "audio-channel-rear-left"; + case PA_CHANNEL_POSITION_REAR_RIGHT: + return "audio-channel-rear-right"; + case PA_CHANNEL_POSITION_REAR_CENTER: + return "audio-channel-rear-center"; + case PA_CHANNEL_POSITION_LFE: + return "audio-channel-lfe"; + case PA_CHANNEL_POSITION_SIDE_LEFT: + return "audio-channel-side-left"; + case PA_CHANNEL_POSITION_SIDE_RIGHT: + return "audio-channel-side-right"; + default: + return NULL; + } +} + +static const char * +icon_name (pa_channel_position_t position, gboolean playing) +{ + switch (position) { + case PA_CHANNEL_POSITION_FRONT_LEFT: + return playing ? "audio-speaker-left-testing" : "audio-speaker-left"; + case PA_CHANNEL_POSITION_FRONT_RIGHT: + return playing ? "audio-speaker-right-testing" : "audio-speaker-right"; + case PA_CHANNEL_POSITION_FRONT_CENTER: + return playing ? "audio-speaker-center-testing" : "audio-speaker-center"; + case PA_CHANNEL_POSITION_REAR_LEFT: + return playing ? "audio-speaker-left-back-testing" : "audio-speaker-left-back"; + case PA_CHANNEL_POSITION_REAR_RIGHT: + return playing ? "audio-speaker-right-back-testing" : "audio-speaker-right-back"; + case PA_CHANNEL_POSITION_REAR_CENTER: + return playing ? "audio-speaker-center-back-testing" : "audio-speaker-center-back"; + case PA_CHANNEL_POSITION_LFE: + return playing ? "audio-subwoofer-testing" : "audio-subwoofer"; + case PA_CHANNEL_POSITION_SIDE_LEFT: + return playing ? "audio-speaker-left-side-testing" : "audio-speaker-left-side"; + case PA_CHANNEL_POSITION_SIDE_RIGHT: + return playing ? "audio-speaker-right-side-testing" : "audio-speaker-right-side"; + default: + return NULL; + } +} + +static void +update_button (GtkWidget *control) +{ + GtkWidget *button; + GtkWidget *image; + pa_channel_position_t position; + gboolean playing; + + button = g_object_get_data (G_OBJECT (control), "button"); + image = g_object_get_data (G_OBJECT (control), "image"); + position = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (control), "position")); + playing = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (control), "playing")); + gtk_button_set_label (GTK_BUTTON (button), playing ? _("Stop") : _("Test")); + gtk_image_set_from_icon_name (GTK_IMAGE (image), icon_name (position, playing), GTK_ICON_SIZE_DIALOG); +} + +static const char * +pretty_position (pa_channel_position_t position) +{ + if (position == PA_CHANNEL_POSITION_LFE) + return N_("Subwoofer"); + + return pa_channel_position_to_pretty_string (position); +} + +static gboolean +idle_cb (GtkWidget *control) +{ + if (control == NULL) + return FALSE; + + /* This is called in the background thread, hence + * forward to main thread via idle callback */ + g_object_set_data (G_OBJECT (control), "playing", GINT_TO_POINTER(FALSE)); + update_button (control); + + return FALSE; +} + +static void +finish_cb (ca_context *c, uint32_t id, int error_code, void *userdata) +{ + GtkWidget *control = (GtkWidget *) userdata; + + if (error_code == CA_ERROR_DESTROYED || control == NULL) + return; + g_idle_add ((GSourceFunc) idle_cb, control); +} + +static void +on_test_button_clicked (GtkButton *button, + GtkWidget *control) +{ + gboolean playing; + ca_context *canberra; + + canberra = g_object_get_data (G_OBJECT (control), "canberra"); + + ca_context_cancel (canberra, 1); + + playing = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (control), "playing")); + + if (playing) { + g_object_set_data (G_OBJECT (control), "playing", GINT_TO_POINTER(FALSE)); + } else { + pa_channel_position_t position; + const char *name; + ca_proplist *proplist; + + position = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (control), "position")); + + ca_proplist_create (&proplist); + ca_proplist_sets (proplist, CA_PROP_MEDIA_ROLE, "test"); + ca_proplist_sets (proplist, CA_PROP_MEDIA_NAME, pretty_position (position)); + ca_proplist_sets (proplist, CA_PROP_CANBERRA_FORCE_CHANNEL, + pa_channel_position_to_string (position)); + ca_proplist_sets (proplist, CA_PROP_CANBERRA_ENABLE, "1"); + + name = sound_name (position); + if (name != NULL) { + ca_proplist_sets (proplist, CA_PROP_EVENT_ID, name); + playing = ca_context_play_full (canberra, 1, proplist, finish_cb, control) >= 0; + } + + if (!playing) { + ca_proplist_sets (proplist, CA_PROP_EVENT_ID, "audio-test-signal"); + playing = ca_context_play_full (canberra, 1, proplist, finish_cb, control) >= 0; + } + + if (!playing) { + ca_proplist_sets(proplist, CA_PROP_EVENT_ID, "bell-window-system"); + playing = ca_context_play_full (canberra, 1, proplist, finish_cb, control) >= 0; + } + g_object_set_data (G_OBJECT (control), "playing", GINT_TO_POINTER(playing)); + } + + update_button (control); +} + +static GtkWidget * +channel_control_new (ca_context *canberra, pa_channel_position_t position) +{ + GtkWidget *control; + GtkWidget *box; + GtkWidget *label; + GtkWidget *image; + GtkWidget *test_button; + const char *name; + + control = gtk_vbox_new (FALSE, 6); + g_object_set_data (G_OBJECT (control), "playing", GINT_TO_POINTER(FALSE)); + g_object_set_data (G_OBJECT (control), "position", GINT_TO_POINTER(position)); + g_object_set_data (G_OBJECT (control), "canberra", canberra); + + name = icon_name (position, FALSE); + if (name == NULL) + name = "audio-volume-medium"; + image = gtk_image_new_from_icon_name (name, GTK_ICON_SIZE_DIALOG); + g_object_set_data (G_OBJECT (control), "image", image); + gtk_box_pack_start (GTK_BOX (control), image, FALSE, FALSE, 0); + + label = gtk_label_new (pretty_position (position)); + gtk_box_pack_start (GTK_BOX (control), label, FALSE, FALSE, 0); + + test_button = gtk_button_new_with_label (_("Test")); + g_signal_connect (G_OBJECT (test_button), "clicked", + G_CALLBACK (on_test_button_clicked), control); + g_object_set_data (G_OBJECT (control), "button", test_button); + + box = gtk_hbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (box), test_button, TRUE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (control), box, FALSE, FALSE, 0); + + gtk_widget_show_all (control); + + return control; +} + +static void +create_channel_controls (GvcSpeakerTest *speaker_test) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (position_table); i += 3) { + speaker_test->priv->channel_controls[position_table[i]] = channel_control_new (speaker_test->priv->canberra, (pa_channel_position_t) position_table[i]); + gtk_table_attach (GTK_TABLE (speaker_test), + speaker_test->priv->channel_controls[position_table[i]], + position_table[i+1], + position_table[i+1]+1, + position_table[i+2], + position_table[i+2]+1, + GTK_EXPAND, GTK_EXPAND, 0, 0); + } +} + +static const GvcChannelMap * +get_channel_map_for_card (GvcMixerControl *control, + GvcMixerCard *card, + char **output_name) +{ + int card_index; + GSList *sinks, *l; + GvcMixerStream *stream; + const GvcChannelMap *map; + + /* This gets the channel map for the only + * output for the card */ + + card_index = gvc_mixer_card_get_index (card); + if (card_index == PA_INVALID_INDEX) + return NULL; + sinks = gvc_mixer_control_get_sinks (control); + stream = NULL; + for (l = sinks; l != NULL; l = l->next) { + GvcMixerStream *s = l->data; + if (gvc_mixer_stream_get_card_index (s) == card_index) { + stream = g_object_ref (s); + break; + } + } + g_slist_free (sinks); + + g_assert (stream); + + g_debug ("Found stream '%s' for card '%s'", + gvc_mixer_stream_get_name (stream), + gvc_mixer_card_get_name (card)); + + *output_name = g_strdup (gvc_mixer_stream_get_name (stream)); + map = gvc_mixer_stream_get_channel_map (stream); + + g_debug ("Got channel map '%s' for port '%s'", + gvc_channel_map_get_mapping (map), *output_name); + + return map; +} + +static void +update_channel_map (GvcSpeakerTest *speaker_test) +{ + guint i; + const GvcChannelMap *map; + char *output_name; + + g_return_if_fail (speaker_test->priv->control != NULL); + g_return_if_fail (speaker_test->priv->card != NULL); + + g_debug ("XXX update_channel_map called XXX"); + + map = get_channel_map_for_card (speaker_test->priv->control, + speaker_test->priv->card, + &output_name); + + g_return_if_fail (map != NULL); + + ca_context_change_device (speaker_test->priv->canberra, output_name); + g_free (output_name); + + for (i = 0; i < G_N_ELEMENTS (position_table); i += 3) { + gtk_widget_set_visible (speaker_test->priv->channel_controls[position_table[i]], + gvc_channel_map_has_position(map, position_table[i])); + } +} + +static void +gvc_speaker_test_init (GvcSpeakerTest *speaker_test) +{ + GtkWidget *face; + + speaker_test->priv = GVC_SPEAKER_TEST_GET_PRIVATE (speaker_test); + + ca_context_create (&speaker_test->priv->canberra); + ca_context_set_driver (speaker_test->priv->canberra, "pulse"); + ca_context_change_props (speaker_test->priv->canberra, + CA_PROP_APPLICATION_ID, "org.gnome.VolumeControl", + NULL); + + gtk_table_resize (GTK_TABLE (speaker_test), 3, 5); + gtk_container_set_border_width (GTK_CONTAINER (speaker_test), 12); + gtk_table_set_homogeneous (GTK_TABLE (speaker_test), TRUE); + gtk_table_set_row_spacings (GTK_TABLE (speaker_test), 12); + gtk_table_set_col_spacings (GTK_TABLE (speaker_test), 12); + + create_channel_controls (speaker_test); + + face = gtk_image_new_from_icon_name ("face-smile", GTK_ICON_SIZE_DIALOG); + gtk_table_attach (GTK_TABLE (speaker_test), face, + 2, 3, 1, 2, GTK_EXPAND, GTK_EXPAND, 0, 0); + gtk_widget_show (face); +} + +static void +gvc_speaker_test_finalize (GObject *object) +{ + GvcSpeakerTest *speaker_test; + + g_return_if_fail (object != NULL); + g_return_if_fail (GVC_IS_SPEAKER_TEST (object)); + + speaker_test = GVC_SPEAKER_TEST (object); + + g_return_if_fail (speaker_test->priv != NULL); + + g_object_unref (speaker_test->priv->card); + speaker_test->priv->card = NULL; + + g_object_unref (speaker_test->priv->control); + speaker_test->priv->control = NULL; + + ca_context_destroy (speaker_test->priv->canberra); + speaker_test->priv->canberra = NULL; + + G_OBJECT_CLASS (gvc_speaker_test_parent_class)->finalize (object); +} + +GtkWidget * +gvc_speaker_test_new (GvcMixerControl *control, + GvcMixerCard *card) +{ + GObject *speaker_test; + + g_return_val_if_fail (card != NULL, NULL); + g_return_val_if_fail (control != NULL, NULL); + + speaker_test = g_object_new (GVC_TYPE_SPEAKER_TEST, + "card", card, + "control", control, + NULL); + + return GTK_WIDGET (speaker_test); +} + diff --git a/panels/sound/gvc-speaker-test.h b/panels/sound/gvc-speaker-test.h new file mode 100644 index 000000000..912c5948e --- /dev/null +++ b/panels/sound/gvc-speaker-test.h @@ -0,0 +1,57 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2009 Red Hat, Inc. + * + * 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. + * + */ + +#ifndef __GVC_SPEAKER_TEST_H +#define __GVC_SPEAKER_TEST_H + +#include +#include +#include + +G_BEGIN_DECLS + +#define GVC_TYPE_SPEAKER_TEST (gvc_speaker_test_get_type ()) +#define GVC_SPEAKER_TEST(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GVC_TYPE_SPEAKER_TEST, GvcSpeakerTest)) +#define GVC_SPEAKER_TEST_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GVC_TYPE_SPEAKER_TEST, GvcSpeakerTestClass)) +#define GVC_IS_SPEAKER_TEST(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVC_TYPE_SPEAKER_TEST)) +#define GVC_IS_SPEAKER_TEST_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GVC_TYPE_SPEAKER_TEST)) +#define GVC_SPEAKER_TEST_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVC_TYPE_SPEAKER_TEST, GvcSpeakerTestClass)) + +typedef struct GvcSpeakerTestPrivate GvcSpeakerTestPrivate; + +typedef struct +{ + GtkNotebook parent; + GvcSpeakerTestPrivate *priv; +} GvcSpeakerTest; + +typedef struct +{ + GtkNotebookClass parent_class; +} GvcSpeakerTestClass; + +GType gvc_speaker_test_get_type (void); + +GtkWidget * gvc_speaker_test_new (GvcMixerControl *control, + GvcMixerCard *card); + +G_END_DECLS + +#endif /* __GVC_SPEAKER_TEST_H */ diff --git a/panels/sound/gvc-stream-status-icon.c b/panels/sound/gvc-stream-status-icon.c new file mode 100644 index 000000000..02c095fe2 --- /dev/null +++ b/panels/sound/gvc-stream-status-icon.c @@ -0,0 +1,822 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 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 + +#include "gvc-mixer-stream.h" +#include "gvc-channel-bar.h" +#include "gvc-stream-status-icon.h" + +#define GVC_STREAM_STATUS_ICON_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_STREAM_STATUS_ICON, GvcStreamStatusIconPrivate)) + +struct GvcStreamStatusIconPrivate +{ + char **icon_names; + GvcMixerStream *mixer_stream; + GtkWidget *dock; + GtkWidget *bar; + guint current_icon; + char *display_name; + gboolean thaw; +}; + +enum +{ + PROP_0, + PROP_DISPLAY_NAME, + PROP_MIXER_STREAM, + PROP_ICON_NAMES, +}; + +static void gvc_stream_status_icon_class_init (GvcStreamStatusIconClass *klass); +static void gvc_stream_status_icon_init (GvcStreamStatusIcon *stream_status_icon); +static void gvc_stream_status_icon_finalize (GObject *object); + +G_DEFINE_TYPE (GvcStreamStatusIcon, gvc_stream_status_icon, GTK_TYPE_STATUS_ICON) + +static void +on_adjustment_value_changed (GtkAdjustment *adjustment, + GvcStreamStatusIcon *icon) +{ + gdouble volume; + + if (icon->priv->thaw) + return; + + volume = gtk_adjustment_get_value (adjustment); + + /* Only push the volume if it's actually changed */ + if (gvc_mixer_stream_set_volume(icon->priv->mixer_stream, + (pa_volume_t) round (volume)) != FALSE) { + gvc_mixer_stream_push_volume(icon->priv->mixer_stream); + } +} + +static void +update_dock (GvcStreamStatusIcon *icon) +{ + GtkAdjustment *adj; + gboolean is_muted; + + g_return_if_fail (icon); + + adj = GTK_ADJUSTMENT (gvc_channel_bar_get_adjustment (GVC_CHANNEL_BAR (icon->priv->bar))); + + icon->priv->thaw = TRUE; + gtk_adjustment_set_value (adj, + gvc_mixer_stream_get_volume (icon->priv->mixer_stream)); + is_muted = gvc_mixer_stream_get_is_muted (icon->priv->mixer_stream); + gvc_channel_bar_set_is_muted (GVC_CHANNEL_BAR (icon->priv->bar), is_muted); + icon->priv->thaw = FALSE; +} + +static gboolean +popup_dock (GvcStreamStatusIcon *icon, + guint time) +{ + GdkRectangle area; + GtkOrientation orientation; + GdkDisplay *display; + GdkScreen *screen; + gboolean res; + int x; + int y; + int monitor_num; + GdkRectangle monitor; + GtkRequisition dock_req; + + update_dock (icon); + + screen = gtk_status_icon_get_screen (GTK_STATUS_ICON (icon)); + res = gtk_status_icon_get_geometry (GTK_STATUS_ICON (icon), + &screen, + &area, + &orientation); + if (! res) { + g_warning ("Unable to determine geometry of status icon"); + return FALSE; + } + + /* position roughly */ + gtk_window_set_screen (GTK_WINDOW (icon->priv->dock), screen); + gvc_channel_bar_set_orientation (GVC_CHANNEL_BAR (icon->priv->bar), + 1 - orientation); + + monitor_num = gdk_screen_get_monitor_at_point (screen, area.x, area.y); + gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor); + + gtk_container_foreach (GTK_CONTAINER (icon->priv->dock), + (GtkCallback) gtk_widget_show_all, NULL); + gtk_widget_get_preferred_size (icon->priv->dock, &dock_req, NULL); + + if (orientation == GTK_ORIENTATION_VERTICAL) { + if (area.x + area.width + dock_req.width <= monitor.x + monitor.width) { + x = area.x + area.width; + } else { + x = area.x - dock_req.width; + } + if (area.y + dock_req.height <= monitor.y + monitor.height) { + y = area.y; + } else { + y = monitor.y + monitor.height - dock_req.height; + } + } else { + if (area.y + area.height + dock_req.height <= monitor.y + monitor.height) { + y = area.y + area.height; + } else { + y = area.y - dock_req.height; + } + if (area.x + dock_req.width <= monitor.x + monitor.width) { + x = area.x; + } else { + x = monitor.x + monitor.width - dock_req.width; + } + } + + gtk_window_move (GTK_WINDOW (icon->priv->dock), x, y); + + /* FIXME: without this, the popup window appears as a square + * after changing the orientation + */ + gtk_window_resize (GTK_WINDOW (icon->priv->dock), 1, 1); + + gtk_widget_show_all (icon->priv->dock); + + + /* grab focus */ + gtk_grab_add (icon->priv->dock); + + if (gdk_pointer_grab (gtk_widget_get_window (icon->priv->dock), TRUE, + GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | + GDK_POINTER_MOTION_MASK | GDK_SCROLL_MASK, NULL, NULL, + time) + != GDK_GRAB_SUCCESS) { + gtk_grab_remove (icon->priv->dock); + gtk_widget_hide (icon->priv->dock); + return FALSE; + } + + if (gdk_keyboard_grab (gtk_widget_get_window (icon->priv->dock), TRUE, time) != GDK_GRAB_SUCCESS) { + display = gtk_widget_get_display (icon->priv->dock); + gdk_display_pointer_ungrab (display, time); + gtk_grab_remove (icon->priv->dock); + gtk_widget_hide (icon->priv->dock); + return FALSE; + } + + gtk_widget_grab_focus (icon->priv->dock); + + return TRUE; +} + +static void +on_status_icon_activate (GtkStatusIcon *status_icon, + GvcStreamStatusIcon *icon) +{ + popup_dock (icon, GDK_CURRENT_TIME); +} + +static void +on_menu_mute_toggled (GtkMenuItem *item, + GvcStreamStatusIcon *icon) +{ + gboolean is_muted; + is_muted = gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item)); + gvc_channel_bar_set_is_muted (GVC_CHANNEL_BAR (icon->priv->bar), is_muted); +} + +static void +on_menu_activate_open_volume_control (GtkMenuItem *item, + GvcStreamStatusIcon *icon) +{ + GError *error; + + error = NULL; + gdk_spawn_command_line_on_screen (gtk_widget_get_screen (icon->priv->dock), + "gnome-control-center sound", + &error); + + if (error != NULL) { + GtkWidget *dialog; + + dialog = gtk_message_dialog_new (NULL, + 0, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + _("Failed to start Sound Preferences: %s"), + error->message); + g_signal_connect (dialog, + "response", + G_CALLBACK (gtk_widget_destroy), + NULL); + gtk_widget_show (dialog); + g_error_free (error); + } +} + +static void +on_status_icon_popup_menu (GtkStatusIcon *status_icon, + guint button, + guint activate_time, + GvcStreamStatusIcon *icon) +{ + GtkWidget *menu; + GtkWidget *item; + GtkWidget *image; + + menu = gtk_menu_new (); + + item = gtk_check_menu_item_new_with_mnemonic (_("_Mute")); + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), + gvc_mixer_stream_get_is_muted (icon->priv->mixer_stream)); + g_signal_connect (item, + "toggled", + G_CALLBACK (on_menu_mute_toggled), + icon); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + + item = gtk_image_menu_item_new_with_mnemonic (_("_Sound Preferences")); + image = gtk_image_new_from_icon_name ("multimedia-volume-control", + GTK_ICON_SIZE_MENU); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image); + g_signal_connect (item, + "activate", + G_CALLBACK (on_menu_activate_open_volume_control), + icon); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + + gtk_widget_show_all (menu); + gtk_menu_popup (GTK_MENU (menu), + NULL, + NULL, + gtk_status_icon_position_menu, + status_icon, + button, + activate_time); +} + +static gboolean +on_status_icon_scroll_event (GtkStatusIcon *status_icon, + GdkEventScroll *event, + GvcStreamStatusIcon *icon) +{ + return gvc_channel_bar_scroll (GVC_CHANNEL_BAR (icon->priv->bar), event->direction); +} + +static void +gvc_icon_release_grab (GvcStreamStatusIcon *icon, + GdkEventButton *event) +{ + GdkDisplay *display; + + /* ungrab focus */ + display = gtk_widget_get_display (GTK_WIDGET (icon->priv->dock)); + gdk_display_keyboard_ungrab (display, event->time); + gdk_display_pointer_ungrab (display, event->time); + gtk_grab_remove (icon->priv->dock); + + /* hide again */ + gtk_widget_hide (icon->priv->dock); +} + +static gboolean +on_dock_button_press (GtkWidget *widget, + GdkEventButton *event, + GvcStreamStatusIcon *icon) +{ + if (event->type == GDK_BUTTON_PRESS) { + gvc_icon_release_grab (icon, event); + return TRUE; + } + + return FALSE; +} + +static void +popdown_dock (GvcStreamStatusIcon *icon) +{ + GdkDisplay *display; + + /* ungrab focus */ + display = gtk_widget_get_display (icon->priv->dock); + gdk_display_keyboard_ungrab (display, GDK_CURRENT_TIME); + gdk_display_pointer_ungrab (display, GDK_CURRENT_TIME); + gtk_grab_remove (icon->priv->dock); + + /* hide again */ + gtk_widget_hide (icon->priv->dock); +} + +/* This is called when the grab is broken for + * either the dock, or the scale itself */ +static void +gvc_icon_grab_notify (GvcStreamStatusIcon *icon, + gboolean was_grabbed) +{ + if (was_grabbed != FALSE) { + return; + } + + if (!gtk_widget_has_grab (icon->priv->dock)) { + return; + } + + if (gtk_widget_is_ancestor (gtk_grab_get_current (), icon->priv->dock)) { + return; + } + + popdown_dock (icon); +} + +static void +on_dock_grab_notify (GtkWidget *widget, + gboolean was_grabbed, + GvcStreamStatusIcon *icon) +{ + gvc_icon_grab_notify (icon, was_grabbed); +} + +static gboolean +on_dock_grab_broken_event (GtkWidget *widget, + gboolean was_grabbed, + GvcStreamStatusIcon *icon) +{ + gvc_icon_grab_notify (icon, FALSE); + + return FALSE; +} + +static gboolean +on_dock_key_release (GtkWidget *widget, + GdkEventKey *event, + GvcStreamStatusIcon *icon) +{ + if (event->keyval == GDK_KEY_Escape) { + popdown_dock (icon); + return TRUE; + } + +#if 0 + if (!gtk_bindings_activate_event (GTK_OBJECT (widget), event)) { + /* The popup hasn't managed the event, pass onto the button */ + gtk_bindings_activate_event (GTK_OBJECT (user_data), event); + } +#endif + return TRUE; +} + +static gboolean +on_dock_scroll_event (GtkWidget *widget, + GdkEventScroll *event, + GvcStreamStatusIcon *icon) +{ + /* Forward event to the status icon */ + on_status_icon_scroll_event (NULL, event, icon); + return TRUE; +} + +static void +update_icon (GvcStreamStatusIcon *icon) +{ + guint volume; + gboolean is_muted; + guint n; + char *markup; + gboolean can_decibel; + gdouble db; + + if (icon->priv->mixer_stream == NULL) { + return; + } + + volume = gvc_mixer_stream_get_volume (icon->priv->mixer_stream); + is_muted = gvc_mixer_stream_get_is_muted (icon->priv->mixer_stream); + db = gvc_mixer_stream_get_decibel (icon->priv->mixer_stream); + can_decibel = gvc_mixer_stream_get_can_decibel (icon->priv->mixer_stream); + + /* select image */ + 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; + } + } + + /* apparently status icon will reset icon even if + * if doesn't change */ + if (icon->priv->current_icon != n) { + gtk_status_icon_set_from_icon_name (GTK_STATUS_ICON (icon), + icon->priv->icon_names [n]); + icon->priv->current_icon = n; + } + + + if (is_muted) { + markup = g_strdup_printf ( + "%s: %s\n%s", + icon->priv->display_name, + _("Muted"), + gvc_mixer_stream_get_description (icon->priv->mixer_stream)); + } else if (can_decibel && (db > PA_DECIBEL_MININFTY)) { + markup = g_strdup_printf ( + "%s: %.0f%%\n%0.2f dB\n%s", + icon->priv->display_name, + 100 * (float)volume / PA_VOLUME_NORM, + db, + gvc_mixer_stream_get_description (icon->priv->mixer_stream)); + } else if (can_decibel) { + markup = g_strdup_printf ( + "%s: %.0f%%\n-∞ dB\n%s", + icon->priv->display_name, + 100 * (float)volume / PA_VOLUME_NORM, + gvc_mixer_stream_get_description (icon->priv->mixer_stream)); + } else { + markup = g_strdup_printf ( + "%s: %.0f%%\n%s", + icon->priv->display_name, + 100 * (float)volume / PA_VOLUME_NORM, + gvc_mixer_stream_get_description (icon->priv->mixer_stream)); + } + gtk_status_icon_set_tooltip_markup (GTK_STATUS_ICON (icon), markup); + g_free (markup); +} + +void +gvc_stream_status_icon_set_icon_names (GvcStreamStatusIcon *icon, + const char **names) +{ + g_return_if_fail (GVC_IS_STREAM_STATUS_ICON (icon)); + + g_strfreev (icon->priv->icon_names); + icon->priv->icon_names = g_strdupv ((char **)names); + update_icon (icon); + g_object_notify (G_OBJECT (icon), "icon-names"); +} + +static void +on_stream_volume_notify (GObject *object, + GParamSpec *pspec, + GvcStreamStatusIcon *icon) +{ + update_icon (icon); + update_dock (icon); +} + +static void +on_stream_is_muted_notify (GObject *object, + GParamSpec *pspec, + GvcStreamStatusIcon *icon) +{ + update_icon (icon); + update_dock (icon); +} + +void +gvc_stream_status_icon_set_display_name (GvcStreamStatusIcon *icon, + const char *name) +{ + g_return_if_fail (GVC_STREAM_STATUS_ICON (icon)); + + g_free (icon->priv->display_name); + icon->priv->display_name = g_strdup (name); + update_icon (icon); + g_object_notify (G_OBJECT (icon), "display-name"); +} + +void +gvc_stream_status_icon_set_mixer_stream (GvcStreamStatusIcon *icon, + GvcMixerStream *stream) +{ + g_return_if_fail (GVC_STREAM_STATUS_ICON (icon)); + + if (stream != NULL) { + g_object_ref (stream); + } + + if (icon->priv->mixer_stream != NULL) { + g_signal_handlers_disconnect_by_func (icon->priv->mixer_stream, + G_CALLBACK (on_stream_volume_notify), + icon); + g_signal_handlers_disconnect_by_func (icon->priv->mixer_stream, + G_CALLBACK (on_stream_is_muted_notify), + icon); + g_object_unref (icon->priv->mixer_stream); + icon->priv->mixer_stream = NULL; + } + + icon->priv->mixer_stream = stream; + + if (icon->priv->mixer_stream != NULL) { + GtkAdjustment *adj; + + g_object_ref (icon->priv->mixer_stream); + + icon->priv->thaw = TRUE; + adj = GTK_ADJUSTMENT (gvc_channel_bar_get_adjustment (GVC_CHANNEL_BAR (icon->priv->bar))); + gtk_adjustment_set_value (adj, + gvc_mixer_stream_get_volume (icon->priv->mixer_stream)); + icon->priv->thaw = FALSE; + + g_signal_connect (icon->priv->mixer_stream, + "notify::volume", + G_CALLBACK (on_stream_volume_notify), + icon); + g_signal_connect (icon->priv->mixer_stream, + "notify::is-muted", + G_CALLBACK (on_stream_is_muted_notify), + icon); + } + + update_icon (icon); + + g_object_notify (G_OBJECT (icon), "mixer-stream"); +} + +static void +gvc_stream_status_icon_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GvcStreamStatusIcon *self = GVC_STREAM_STATUS_ICON (object); + + switch (prop_id) { + case PROP_MIXER_STREAM: + gvc_stream_status_icon_set_mixer_stream (self, g_value_get_object (value)); + break; + case PROP_DISPLAY_NAME: + gvc_stream_status_icon_set_display_name (self, g_value_get_string (value)); + break; + case PROP_ICON_NAMES: + gvc_stream_status_icon_set_icon_names (self, g_value_get_boxed (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gvc_stream_status_icon_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GvcStreamStatusIcon *self = GVC_STREAM_STATUS_ICON (object); + GvcStreamStatusIconPrivate *priv = self->priv; + + switch (prop_id) { + case PROP_MIXER_STREAM: + g_value_set_object (value, priv->mixer_stream); + break; + case PROP_DISPLAY_NAME: + g_value_set_string (value, priv->display_name); + break; + case PROP_ICON_NAMES: + g_value_set_boxed (value, priv->icon_names); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +on_bar_is_muted_notify (GObject *object, + GParamSpec *pspec, + GvcStreamStatusIcon *icon) +{ + gboolean is_muted; + + is_muted = gvc_channel_bar_get_is_muted (GVC_CHANNEL_BAR (object)); + + if (gvc_mixer_stream_get_is_muted (icon->priv->mixer_stream) != is_muted) { + /* Update the stream before pushing the change */ + gvc_mixer_stream_set_is_muted (icon->priv->mixer_stream, is_muted); + gvc_mixer_stream_change_is_muted (icon->priv->mixer_stream, + is_muted); + } +} + +static GObject * +gvc_stream_status_icon_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_params) +{ + GObject *object; + GvcStreamStatusIcon *icon; + GtkWidget *frame; + GtkWidget *box; + GtkAdjustment *adj; + + object = G_OBJECT_CLASS (gvc_stream_status_icon_parent_class)->constructor (type, n_construct_properties, construct_params); + + icon = GVC_STREAM_STATUS_ICON (object); + + gtk_status_icon_set_from_icon_name (GTK_STATUS_ICON (icon), + icon->priv->icon_names[0]); + + /* window */ + icon->priv->dock = gtk_window_new (GTK_WINDOW_POPUP); + gtk_widget_set_name (icon->priv->dock, "gvc-stream-status-icon-popup-window"); + g_signal_connect (icon->priv->dock, + "button-press-event", + G_CALLBACK (on_dock_button_press), + icon); + g_signal_connect (icon->priv->dock, + "key-release-event", + G_CALLBACK (on_dock_key_release), + icon); + g_signal_connect (icon->priv->dock, + "scroll-event", + G_CALLBACK (on_dock_scroll_event), + icon); + g_signal_connect (icon->priv->dock, + "grab-notify", + G_CALLBACK (on_dock_grab_notify), + icon); + g_signal_connect (icon->priv->dock, + "grab-broken-event", + G_CALLBACK (on_dock_grab_broken_event), + icon); + + gtk_window_set_decorated (GTK_WINDOW (icon->priv->dock), FALSE); + + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT); + gtk_container_add (GTK_CONTAINER (icon->priv->dock), frame); + + box = gtk_vbox_new (FALSE, 6); + gtk_container_set_border_width (GTK_CONTAINER (box), 2); + gtk_container_add (GTK_CONTAINER (frame), box); + + icon->priv->bar = gvc_channel_bar_new (); + gvc_channel_bar_set_orientation (GVC_CHANNEL_BAR (icon->priv->bar), + GTK_ORIENTATION_VERTICAL); + + gtk_box_pack_start (GTK_BOX (box), icon->priv->bar, TRUE, FALSE, 0); + g_signal_connect (icon->priv->bar, + "notify::is-muted", + G_CALLBACK (on_bar_is_muted_notify), + icon); + + adj = GTK_ADJUSTMENT (gvc_channel_bar_get_adjustment (GVC_CHANNEL_BAR (icon->priv->bar))); + g_signal_connect (adj, + "value-changed", + G_CALLBACK (on_adjustment_value_changed), + icon); + + return object; +} + +static void +gvc_stream_status_icon_dispose (GObject *object) +{ + GvcStreamStatusIcon *icon = GVC_STREAM_STATUS_ICON (object); + + if (icon->priv->dock != NULL) { + gtk_widget_destroy (icon->priv->dock); + icon->priv->dock = NULL; + } + + if (icon->priv->mixer_stream != NULL) { + g_object_unref (icon->priv->mixer_stream); + icon->priv->mixer_stream = NULL; + } + + G_OBJECT_CLASS (gvc_stream_status_icon_parent_class)->dispose (object); +} + +static void +gvc_stream_status_icon_class_init (GvcStreamStatusIconClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructor = gvc_stream_status_icon_constructor; + object_class->finalize = gvc_stream_status_icon_finalize; + object_class->dispose = gvc_stream_status_icon_dispose; + object_class->set_property = gvc_stream_status_icon_set_property; + object_class->get_property = gvc_stream_status_icon_get_property; + + g_object_class_install_property (object_class, + PROP_MIXER_STREAM, + g_param_spec_object ("mixer-stream", + "mixer stream", + "mixer stream", + GVC_TYPE_MIXER_STREAM, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, + PROP_DISPLAY_NAME, + g_param_spec_string ("display-name", + "Display Name", + "Name to display for this stream", + NULL, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, + PROP_ICON_NAMES, + g_param_spec_boxed ("icon-names", + "Icon Names", + "Name of icon to display for this stream", + G_TYPE_STRV, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + + g_type_class_add_private (klass, sizeof (GvcStreamStatusIconPrivate)); +} + +static void +on_status_icon_visible_notify (GvcStreamStatusIcon *icon) +{ + gboolean visible; + + g_object_get (icon, "visible", &visible, NULL); + if (! visible) { + if (icon->priv->dock != NULL) { + gtk_widget_hide (icon->priv->dock); + } + } +} + +static void +gvc_stream_status_icon_init (GvcStreamStatusIcon *icon) +{ + icon->priv = GVC_STREAM_STATUS_ICON_GET_PRIVATE (icon); + + g_signal_connect (icon, + "activate", + G_CALLBACK (on_status_icon_activate), + icon); + g_signal_connect (icon, + "popup-menu", + G_CALLBACK (on_status_icon_popup_menu), + icon); + g_signal_connect (icon, + "scroll-event", + G_CALLBACK (on_status_icon_scroll_event), + icon); + g_signal_connect (icon, + "notify::visible", + G_CALLBACK (on_status_icon_visible_notify), + NULL); + + icon->priv->thaw = FALSE; +} + +static void +gvc_stream_status_icon_finalize (GObject *object) +{ + GvcStreamStatusIcon *stream_status_icon; + + g_return_if_fail (object != NULL); + g_return_if_fail (GVC_IS_STREAM_STATUS_ICON (object)); + + stream_status_icon = GVC_STREAM_STATUS_ICON (object); + + g_return_if_fail (stream_status_icon->priv != NULL); + + g_strfreev (stream_status_icon->priv->icon_names); + + G_OBJECT_CLASS (gvc_stream_status_icon_parent_class)->finalize (object); +} + +GvcStreamStatusIcon * +gvc_stream_status_icon_new (GvcMixerStream *stream, + const char **icon_names) +{ + GObject *icon; + icon = g_object_new (GVC_TYPE_STREAM_STATUS_ICON, + "mixer-stream", stream, + "icon-names", icon_names, + NULL); + return GVC_STREAM_STATUS_ICON (icon); +} diff --git a/panels/sound/gvc-stream-status-icon.h b/panels/sound/gvc-stream-status-icon.h new file mode 100644 index 000000000..55887f346 --- /dev/null +++ b/panels/sound/gvc-stream-status-icon.h @@ -0,0 +1,63 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Red Hat, Inc. + * + * 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. + * + */ + +#ifndef __GVC_STREAM_STATUS_ICON_H +#define __GVC_STREAM_STATUS_ICON_H + +#include +#include "gvc-mixer-stream.h" + +G_BEGIN_DECLS + +#define GVC_TYPE_STREAM_STATUS_ICON (gvc_stream_status_icon_get_type ()) +#define GVC_STREAM_STATUS_ICON(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GVC_TYPE_STREAM_STATUS_ICON, GvcStreamStatusIcon)) +#define GVC_STREAM_STATUS_ICON_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GVC_TYPE_STREAM_STATUS_ICON, GvcStreamStatusIconClass)) +#define GVC_IS_STREAM_STATUS_ICON(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVC_TYPE_STREAM_STATUS_ICON)) +#define GVC_IS_STREAM_STATUS_ICON_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GVC_TYPE_STREAM_STATUS_ICON)) +#define GVC_STREAM_STATUS_ICON_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVC_TYPE_STREAM_STATUS_ICON, GvcStreamStatusIconClass)) + +typedef struct GvcStreamStatusIconPrivate GvcStreamStatusIconPrivate; + +typedef struct +{ + GtkStatusIcon parent; + GvcStreamStatusIconPrivate *priv; +} GvcStreamStatusIcon; + +typedef struct +{ + GtkStatusIconClass parent_class; +} GvcStreamStatusIconClass; + +GType gvc_stream_status_icon_get_type (void); + +GvcStreamStatusIcon * gvc_stream_status_icon_new (GvcMixerStream *stream, + const char **icon_names); + +void gvc_stream_status_icon_set_icon_names (GvcStreamStatusIcon *icon, + const char **icon_names); +void gvc_stream_status_icon_set_display_name (GvcStreamStatusIcon *icon, + const char *display_name); +void gvc_stream_status_icon_set_mixer_stream (GvcStreamStatusIcon *icon, + GvcMixerStream *stream); + +G_END_DECLS + +#endif /* __GVC_STREAM_STATUS_ICON_H */ diff --git a/panels/sound/sound-theme-file-utils.c b/panels/sound/sound-theme-file-utils.c new file mode 100644 index 000000000..06877c72d --- /dev/null +++ b/panels/sound/sound-theme-file-utils.c @@ -0,0 +1,305 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * Copyright (C) 2008 Bastien Nocera + * + * 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, 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 +#include +#include +#include +#include +#include + +#include "sound-theme-file-utils.h" + +#define CUSTOM_THEME_NAME "__custom" + +/* This function needs to be called after each individual + * changeset to the theme */ +void +custom_theme_update_time (void) +{ + char *path; + + path = custom_theme_dir_path (NULL); + utime (path, NULL); + g_free (path); +} + +char * +custom_theme_dir_path (const char *child) +{ + static char *dir = NULL; + const char *data_dir; + + if (dir == NULL) { + data_dir = g_get_user_data_dir (); + dir = g_build_filename (data_dir, "sounds", CUSTOM_THEME_NAME, NULL); + } + if (child == NULL) + return g_strdup (dir); + + return g_build_filename (dir, child, NULL); +} + +static gboolean +directory_delete_recursive (GFile *directory, GError **error) +{ + GFileEnumerator *enumerator; + GFileInfo *info; + gboolean success = TRUE; + + enumerator = g_file_enumerate_children (directory, + G_FILE_ATTRIBUTE_STANDARD_NAME "," + G_FILE_ATTRIBUTE_STANDARD_TYPE, + G_FILE_QUERY_INFO_NONE, + NULL, error); + if (enumerator == NULL) + return FALSE; + + while (success && + (info = g_file_enumerator_next_file (enumerator, NULL, NULL))) { + GFile *child; + + child = g_file_get_child (directory, g_file_info_get_name (info)); + + if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) { + success = directory_delete_recursive (child, error); + } + g_object_unref (info); + + if (success) + success = g_file_delete (child, NULL, error); + } + g_file_enumerator_close (enumerator, NULL, NULL); + + if (success) + success = g_file_delete (directory, NULL, error); + + return success; +} + +/** + * capplet_file_delete_recursive : + * @file : + * @error : + * + * A utility routine to delete files and/or directories, + * including non-empty directories. + **/ +static gboolean +capplet_file_delete_recursive (GFile *file, GError **error) +{ + GFileInfo *info; + GFileType type; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + info = g_file_query_info (file, + G_FILE_ATTRIBUTE_STANDARD_TYPE, + G_FILE_QUERY_INFO_NONE, + NULL, error); + if (info == NULL) + return FALSE; + + type = g_file_info_get_file_type (info); + g_object_unref (info); + + if (type == G_FILE_TYPE_DIRECTORY) + return directory_delete_recursive (file, error); + else + return g_file_delete (file, NULL, error); +} + +void +delete_custom_theme_dir (void) +{ + char *dir; + GFile *file; + + dir = custom_theme_dir_path (NULL); + file = g_file_new_for_path (dir); + g_free (dir); + capplet_file_delete_recursive (file, NULL); + g_object_unref (file); + + g_debug ("deleted the custom theme dir"); +} + +gboolean +custom_theme_dir_is_empty (void) +{ + char *dir; + GFile *file; + gboolean is_empty; + GFileEnumerator *enumerator; + GFileInfo *info; + GError *error = NULL; + + dir = custom_theme_dir_path (NULL); + file = g_file_new_for_path (dir); + g_free (dir); + + is_empty = TRUE; + + enumerator = g_file_enumerate_children (file, + G_FILE_ATTRIBUTE_STANDARD_NAME "," + G_FILE_ATTRIBUTE_STANDARD_TYPE, + G_FILE_QUERY_INFO_NONE, + NULL, &error); + if (enumerator == NULL) { + g_warning ("Unable to enumerate files: %s", error->message); + g_error_free (error); + goto out; + } + + while (is_empty && + (info = g_file_enumerator_next_file (enumerator, NULL, NULL))) { + + if (strcmp ("index.theme", g_file_info_get_name (info)) != 0) { + is_empty = FALSE; + } + + g_object_unref (info); + } + g_file_enumerator_close (enumerator, NULL, NULL); + + out: + g_object_unref (file); + + return is_empty; +} + +static void +delete_one_file (const char *sound_name, const char *pattern) +{ + GFile *file; + char *name, *filename; + + name = g_strdup_printf (pattern, sound_name); + filename = custom_theme_dir_path (name); + g_free (name); + file = g_file_new_for_path (filename); + g_free (filename); + capplet_file_delete_recursive (file, NULL); + g_object_unref (file); +} + +void +delete_old_files (const char **sounds) +{ + guint i; + + for (i = 0; sounds[i] != NULL; i++) { + delete_one_file (sounds[i], "%s.ogg"); + } +} + +void +delete_disabled_files (const char **sounds) +{ + guint i; + + for (i = 0; sounds[i] != NULL; i++) + delete_one_file (sounds[i], "%s.disabled"); +} + +static void +create_one_file (GFile *file) +{ + GFileOutputStream* stream; + + stream = g_file_create (file, G_FILE_CREATE_NONE, NULL, NULL); + if (stream != NULL) { + g_output_stream_close (G_OUTPUT_STREAM (stream), NULL, NULL); + g_object_unref (stream); + } +} + +void +add_disabled_file (const char **sounds) +{ + guint i; + + for (i = 0; sounds[i] != NULL; i++) { + GFile *file; + char *name, *filename; + + name = g_strdup_printf ("%s.disabled", sounds[i]); + filename = custom_theme_dir_path (name); + g_free (name); + file = g_file_new_for_path (filename); + g_free (filename); + + create_one_file (file); + g_object_unref (file); + } +} + +void +add_custom_file (const char **sounds, const char *filename) +{ + guint i; + + for (i = 0; sounds[i] != NULL; i++) { + GFile *file; + char *name, *path; + + /* We use *.ogg because it's the first type of file that + * libcanberra looks at */ + name = g_strdup_printf ("%s.ogg", sounds[i]); + path = custom_theme_dir_path (name); + g_free (name); + /* In case there's already a link there, delete it */ + g_unlink (path); + file = g_file_new_for_path (path); + g_free (path); + + /* Create the link */ + g_file_make_symbolic_link (file, filename, NULL, NULL); + g_object_unref (file); + } +} + +void +create_custom_theme (const char *parent) +{ + GKeyFile *keyfile; + char *data; + char *path; + + /* Create the custom directory */ + path = custom_theme_dir_path (NULL); + g_mkdir_with_parents (path, 0755); + g_free (path); + + /* Set the data for index.theme */ + keyfile = g_key_file_new (); + g_key_file_set_string (keyfile, "Sound Theme", "Name", _("Custom")); + g_key_file_set_string (keyfile, "Sound Theme", "Inherits", parent); + g_key_file_set_string (keyfile, "Sound Theme", "Directories", "."); + data = g_key_file_to_data (keyfile, NULL, NULL); + g_key_file_free (keyfile); + + /* Save the index.theme */ + path = custom_theme_dir_path ("index.theme"); + g_file_set_contents (path, data, -1, NULL); + g_free (path); + g_free (data); + + custom_theme_update_time (); +} diff --git a/panels/sound/sound-theme-file-utils.h b/panels/sound/sound-theme-file-utils.h new file mode 100644 index 000000000..7fc3a5880 --- /dev/null +++ b/panels/sound/sound-theme-file-utils.h @@ -0,0 +1,37 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * Copyright (C) 2008 Bastien Nocera + * + * 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, 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. + */ +#ifndef __SOUND_THEME_FILE_UTILS_HH__ +#define __SOUND_THEME_FILE_UTILS_HH__ + +#include + +char *custom_theme_dir_path (const char *child); +gboolean custom_theme_dir_is_empty (void); +void create_custom_theme (const char *parent); + +void delete_custom_theme_dir (void); +void delete_old_files (const char **sounds); +void delete_disabled_files (const char **sounds); + +void add_disabled_file (const char **sounds); +void add_custom_file (const char **sounds, const char *filename); + +void custom_theme_update_time (void); + +#endif /* __SOUND_THEME_FILE_UTILS_HH__ */ diff --git a/po/POTFILES.in b/po/POTFILES.in index f9bc81f83..e3cdea42a 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -46,6 +46,22 @@ panels/mouse/gnome-mouse-panel.desktop.in.in panels/network/gnome-network-properties.c panels/network/gnome-network-panel.desktop.in.in [type: gettext/glade]panels/network/gnome-network-properties.ui +panels/sound/applet-main.c +panels/sound/cc-sound-panel.c +panels/sound/gvc-applet.c +panels/sound/gvc-balance-bar.c +panels/sound/gvc-channel-bar.c +panels/sound/gvc-combo-box.c +panels/sound/gvc-mixer-control.c +panels/sound/gvc-mixer-dialog.c +panels/sound/gvc-speaker-test.c +panels/sound/gvc-stream-status-icon.c +panels/sound/gvc-sound-theme-chooser.c +panels/sound/gvc-sound-theme-editor.c +panels/sound/sound-theme-file-utils.c +panels/sound/data/gnome-sound-applet.desktop.in +panels/sound/data/gnome-sound-panel.desktop.in.in +panels/sound/data/sounds/gnome-sounds-default.xml.in.in panels/universal-access/gnome-universal-access-panel.desktop.in.in [type: gettext/glade]panels/universal-access/uap.ui shell/control-center.c diff --git a/po/POTFILES.skip b/po/POTFILES.skip index e8c5cdc67..cb6a3a1d8 100644 --- a/po/POTFILES.skip +++ b/po/POTFILES.skip @@ -8,6 +8,8 @@ panels/default-applications/gnome-at-session.desktop.in panels/display/gnome-display-panel.desktop.in panels/keybindings/gnome-keybindings-panel.desktop.in panels/keyboard/gnome-keyboard-panel.desktop.in +panels/sound/data/gnome-sound-panel.desktop.in +panels/sound/data/sounds/gnome-sounds-default.xml.in capplets/localization/localization.desktop.in capplets/mouse/gnome-settings-mouse.desktop.in panels/network/gnome-network-panel.desktop.in