diff options
author | Stefano Karapetsas <[email protected]> | 2011-12-11 13:11:15 +0100 |
---|---|---|
committer | Stefano Karapetsas <[email protected]> | 2011-12-11 13:11:15 +0100 |
commit | 4ee2559eaaf2a94ac26c265517e9604a72729360 (patch) | |
tree | f24e3e3294c2b75819755289e592bf2e28e668c4 /mate-volume-control/src | |
download | mate-media-4ee2559eaaf2a94ac26c265517e9604a72729360.tar.bz2 mate-media-4ee2559eaaf2a94ac26c265517e9604a72729360.tar.xz |
moved from Mate-Extra
Diffstat (limited to 'mate-volume-control/src')
40 files changed, 13660 insertions, 0 deletions
diff --git a/mate-volume-control/src/Makefile.am b/mate-volume-control/src/Makefile.am new file mode 100644 index 0000000..d750ce7 --- /dev/null +++ b/mate-volume-control/src/Makefile.am @@ -0,0 +1,94 @@ +NULL = + +bin_PROGRAMS = \ + mate-volume-control-applet \ + mate-volume-control \ + $(NULL) + +AM_CPPFLAGS = \ + $(WARN_CFLAGS) \ + -I$(top_srcdir)/sound-theme \ + $(VOLUME_CONTROL_CFLAGS) \ + $(DISABLE_DEPRECATED) \ + $(PULSEAUDIO_CFLAGS) \ + -DLOCALE_DIR=\""$(datadir)/locale"\" \ + -DLIBEXECDIR=\"$(libexecdir)\" \ + -DGLADEDIR=\""$(pkgdatadir)"\" \ + -DICON_DATA_DIR="\"$(pkgdatadir)/icons\"" \ + $(NULL) + +noinst_LTLIBRARIES = libmatevolumecontrol.la +libmatevolumecontrol_la_SOURCES = \ + gvc-mixer-card.h \ + gvc-mixer-card.c \ + gvc-mixer-stream.h \ + gvc-mixer-stream.c \ + gvc-channel-map.h \ + gvc-channel-map.c \ + 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-channel-bar.h \ + gvc-channel-bar.c \ + gvc-log.h \ + gvc-log.c \ + $(NULL) + +mate_volume_control_applet_LDADD = \ + -lm \ + libmatevolumecontrol.la \ + $(VOLUME_CONTROL_LIBS) \ + $(PULSEAUDIO_LIBS) \ + $(NULL) + +mate_volume_control_applet_SOURCES = \ + gvc-stream-status-icon.h \ + gvc-stream-status-icon.c \ + gvc-applet.h \ + gvc-applet.c \ + applet-main.c \ + $(NULL) + +mate_volume_control_LDADD = \ + -lm \ + libmatevolumecontrol.la \ + $(top_builddir)/sound-theme/libsoundtheme.la \ + $(VOLUME_CONTROL_LIBS) \ + $(PULSEAUDIO_LIBS) \ + $(NULL) + +mate_volume_control_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 \ + dialog-main.c \ + $(NULL) + +BUILT_SOURCES = \ + $(NULL) + +CLEANFILES = \ + $(BUILT_SOURCES) \ + $(NULL) + +MAINTAINERCLEANFILES = \ + *~ \ + Makefile.in + +-include $(top_srcdir)/git.mk diff --git a/mate-volume-control/src/Makefile.in b/mate-volume-control/src/Makefile.in new file mode 100644 index 0000000..f45544e --- /dev/null +++ b/mate-volume-control/src/Makefile.in @@ -0,0 +1,757 @@ +# Makefile.in generated by automake 1.11.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, +# Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +bin_PROGRAMS = mate-volume-control-applet$(EXEEXT) \ + mate-volume-control$(EXEEXT) $(am__EXEEXT_1) +subdir = mate-volume-control/src +DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/as-ac-expand.m4 \ + $(top_srcdir)/m4/as-compiler-flag.m4 \ + $(top_srcdir)/m4/as-version.m4 $(top_srcdir)/m4/intltool.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 \ + $(top_srcdir)/m4/mate-doc-utils.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(SHELL) $(top_srcdir)/build-aux/mkinstalldirs +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +LTLIBRARIES = $(noinst_LTLIBRARIES) +libmatevolumecontrol_la_LIBADD = +am__objects_1 = +am_libmatevolumecontrol_la_OBJECTS = gvc-mixer-card.lo \ + gvc-mixer-stream.lo gvc-channel-map.lo gvc-mixer-sink.lo \ + gvc-mixer-source.lo gvc-mixer-sink-input.lo \ + gvc-mixer-source-output.lo gvc-mixer-event-role.lo \ + gvc-mixer-control.lo gvc-channel-bar.lo gvc-log.lo \ + $(am__objects_1) +libmatevolumecontrol_la_OBJECTS = \ + $(am_libmatevolumecontrol_la_OBJECTS) +AM_V_lt = $(am__v_lt_$(V)) +am__v_lt_ = $(am__v_lt_$(AM_DEFAULT_VERBOSITY)) +am__v_lt_0 = --silent +am__EXEEXT_1 = +am__installdirs = "$(DESTDIR)$(bindir)" +PROGRAMS = $(bin_PROGRAMS) +am_mate_volume_control_OBJECTS = gvc-balance-bar.$(OBJEXT) \ + gvc-mixer-dialog.$(OBJEXT) gvc-level-bar.$(OBJEXT) \ + gvc-combo-box.$(OBJEXT) gvc-speaker-test.$(OBJEXT) \ + dialog-main.$(OBJEXT) $(am__objects_1) +mate_volume_control_OBJECTS = $(am_mate_volume_control_OBJECTS) +am__DEPENDENCIES_1 = +mate_volume_control_DEPENDENCIES = libmatevolumecontrol.la \ + $(top_builddir)/sound-theme/libsoundtheme.la \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) +am_mate_volume_control_applet_OBJECTS = \ + gvc-stream-status-icon.$(OBJEXT) gvc-applet.$(OBJEXT) \ + applet-main.$(OBJEXT) $(am__objects_1) +mate_volume_control_applet_OBJECTS = \ + $(am_mate_volume_control_applet_OBJECTS) +mate_volume_control_applet_DEPENDENCIES = libmatevolumecontrol.la \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_$(V)) +am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY)) +am__v_CC_0 = @echo " CC " $@; +AM_V_at = $(am__v_at_$(V)) +am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY)) +am__v_at_0 = @ +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_$(V)) +am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY)) +am__v_CCLD_0 = @echo " CCLD " $@; +AM_V_GEN = $(am__v_GEN_$(V)) +am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY)) +am__v_GEN_0 = @echo " GEN " $@; +SOURCES = $(libmatevolumecontrol_la_SOURCES) \ + $(mate_volume_control_SOURCES) \ + $(mate_volume_control_applet_SOURCES) +DIST_SOURCES = $(libmatevolumecontrol_la_SOURCES) \ + $(mate_volume_control_SOURCES) \ + $(mate_volume_control_applet_SOURCES) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ALL_LINGUAS = @ALL_LINGUAS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CATALOGS = @CATALOGS@ +CATOBJEXT = @CATOBJEXT@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DATADIRNAME = @DATADIRNAME@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DISABLE_DEPRECATED = @DISABLE_DEPRECATED@ +DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@ +DLLTOOL = @DLLTOOL@ +DOC_USER_FORMATS = @DOC_USER_FORMATS@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GETTEXT_PACKAGE = @GETTEXT_PACKAGE@ +GLADEUI_CATALOG_DIR = @GLADEUI_CATALOG_DIR@ +GLADEUI_CFLAGS = @GLADEUI_CFLAGS@ +GLADEUI_LIBS = @GLADEUI_LIBS@ +GLADEUI_MODULE_DIR = @GLADEUI_MODULE_DIR@ +GLIB_GENMARSHAL = @GLIB_GENMARSHAL@ +GMOFILES = @GMOFILES@ +GMP_CFLAGS = @GMP_CFLAGS@ +GMP_LIBS = @GMP_LIBS@ +GMSGFMT = @GMSGFMT@ +GREP = @GREP@ +GSR_CFLAGS = @GSR_CFLAGS@ +GSR_LIBS = @GSR_LIBS@ +GSTMIXER_CFLAGS = @GSTMIXER_CFLAGS@ +GSTMIXER_LIBS = @GSTMIXER_LIBS@ +GSTPROPS_CFLAGS = @GSTPROPS_CFLAGS@ +GSTPROPS_LIBS = @GSTPROPS_LIBS@ +GST_MAJORMINOR = @GST_MAJORMINOR@ +HAVE_PULSEAUDIO = @HAVE_PULSEAUDIO@ +HAVE_SOUND_THEME = @HAVE_SOUND_THEME@ +HELP_DIR = @HELP_DIR@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INSTOBJEXT = @INSTOBJEXT@ +INTLLIBS = @INTLLIBS@ +INTLTOOL_EXTRACT = @INTLTOOL_EXTRACT@ +INTLTOOL_MERGE = @INTLTOOL_MERGE@ +INTLTOOL_PERL = @INTLTOOL_PERL@ +INTLTOOL_UPDATE = @INTLTOOL_UPDATE@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MATECC_DESKTOP_DIR = @MATECC_DESKTOP_DIR@ +MATECONFTOOL = @MATECONFTOOL@ +MATECONF_SCHEMA_CONFIG_SOURCE = @MATECONF_SCHEMA_CONFIG_SOURCE@ +MATECONF_SCHEMA_FILE_DIR = @MATECONF_SCHEMA_FILE_DIR@ +MKDIR_P = @MKDIR_P@ +MKINSTALLDIRS = @MKINSTALLDIRS@ +MSGFMT = @MSGFMT@ +MSGFMT_OPTS = @MSGFMT_OPTS@ +MSGMERGE = @MSGMERGE@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OMF_DIR = @OMF_DIR@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PACKAGE_VERSION_MAJOR = @PACKAGE_VERSION_MAJOR@ +PACKAGE_VERSION_MICRO = @PACKAGE_VERSION_MICRO@ +PACKAGE_VERSION_MINOR = @PACKAGE_VERSION_MINOR@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +POFILES = @POFILES@ +POSUB = @POSUB@ +PO_IN_DATADIR_FALSE = @PO_IN_DATADIR_FALSE@ +PO_IN_DATADIR_TRUE = @PO_IN_DATADIR_TRUE@ +PROGRAMS_GSTPROPS = @PROGRAMS_GSTPROPS@ +PULSEAUDIO_CFLAGS = @PULSEAUDIO_CFLAGS@ +PULSEAUDIO_LIBS = @PULSEAUDIO_LIBS@ +RANLIB = @RANLIB@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SOUNDTHEME_CFLAGS = @SOUNDTHEME_CFLAGS@ +SOUNDTHEME_LIBS = @SOUNDTHEME_LIBS@ +SOUND_THEME_CFLAGS = @SOUND_THEME_CFLAGS@ +SOUND_THEME_LIBS = @SOUND_THEME_LIBS@ +STRIP = @STRIP@ +USE_NLS = @USE_NLS@ +VERSION = @VERSION@ +VOLUME_CONTROL_CFLAGS = @VOLUME_CONTROL_CFLAGS@ +VOLUME_CONTROL_LIBS = @VOLUME_CONTROL_LIBS@ +WARN_CFLAGS = @WARN_CFLAGS@ +WARN_CXXFLAGS = @WARN_CXXFLAGS@ +XGETTEXT = @XGETTEXT@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +NULL = +AM_CPPFLAGS = \ + $(WARN_CFLAGS) \ + -I$(top_srcdir)/sound-theme \ + $(VOLUME_CONTROL_CFLAGS) \ + $(DISABLE_DEPRECATED) \ + $(PULSEAUDIO_CFLAGS) \ + -DLOCALE_DIR=\""$(datadir)/locale"\" \ + -DLIBEXECDIR=\"$(libexecdir)\" \ + -DGLADEDIR=\""$(pkgdatadir)"\" \ + -DICON_DATA_DIR="\"$(pkgdatadir)/icons\"" \ + $(NULL) + +noinst_LTLIBRARIES = libmatevolumecontrol.la +libmatevolumecontrol_la_SOURCES = \ + gvc-mixer-card.h \ + gvc-mixer-card.c \ + gvc-mixer-stream.h \ + gvc-mixer-stream.c \ + gvc-channel-map.h \ + gvc-channel-map.c \ + 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-channel-bar.h \ + gvc-channel-bar.c \ + gvc-log.h \ + gvc-log.c \ + $(NULL) + +mate_volume_control_applet_LDADD = \ + -lm \ + libmatevolumecontrol.la \ + $(VOLUME_CONTROL_LIBS) \ + $(PULSEAUDIO_LIBS) \ + $(NULL) + +mate_volume_control_applet_SOURCES = \ + gvc-stream-status-icon.h \ + gvc-stream-status-icon.c \ + gvc-applet.h \ + gvc-applet.c \ + applet-main.c \ + $(NULL) + +mate_volume_control_LDADD = \ + -lm \ + libmatevolumecontrol.la \ + $(top_builddir)/sound-theme/libsoundtheme.la \ + $(VOLUME_CONTROL_LIBS) \ + $(PULSEAUDIO_LIBS) \ + $(NULL) + +mate_volume_control_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 \ + dialog-main.c \ + $(NULL) + +BUILT_SOURCES = \ + $(NULL) + +CLEANFILES = \ + $(BUILT_SOURCES) \ + $(NULL) + +MAINTAINERCLEANFILES = \ + *~ \ + Makefile.in + +all: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign mate-volume-control/src/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign mate-volume-control/src/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstLTLIBRARIES: + -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES) + @list='$(noinst_LTLIBRARIES)'; for p in $$list; do \ + dir="`echo $$p | sed -e 's|/[^/]*$$||'`"; \ + test "$$dir" != "$$p" || dir=.; \ + echo "rm -f \"$${dir}/so_locations\""; \ + rm -f "$${dir}/so_locations"; \ + done +libmatevolumecontrol.la: $(libmatevolumecontrol_la_OBJECTS) $(libmatevolumecontrol_la_DEPENDENCIES) + $(AM_V_CCLD)$(LINK) $(libmatevolumecontrol_la_OBJECTS) $(libmatevolumecontrol_la_LIBADD) $(LIBS) +install-binPROGRAMS: $(bin_PROGRAMS) + @$(NORMAL_INSTALL) + test -z "$(bindir)" || $(MKDIR_P) "$(DESTDIR)$(bindir)" + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p || test -f $$p1; \ + then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-binPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(bindir)" && rm -f $$files + +clean-binPROGRAMS: + @list='$(bin_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +mate-volume-control$(EXEEXT): $(mate_volume_control_OBJECTS) $(mate_volume_control_DEPENDENCIES) + @rm -f mate-volume-control$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(mate_volume_control_OBJECTS) $(mate_volume_control_LDADD) $(LIBS) +mate-volume-control-applet$(EXEEXT): $(mate_volume_control_applet_OBJECTS) $(mate_volume_control_applet_DEPENDENCIES) + @rm -f mate-volume-control-applet$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(mate_volume_control_applet_OBJECTS) $(mate_volume_control_applet_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/applet-main.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dialog-main.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gvc-applet.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gvc-balance-bar.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gvc-channel-bar.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gvc-channel-map.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gvc-combo-box.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gvc-level-bar.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gvc-log.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gvc-mixer-card.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gvc-mixer-control.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gvc-mixer-dialog.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gvc-mixer-event-role.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gvc-mixer-sink-input.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gvc-mixer-sink.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gvc-mixer-source-output.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gvc-mixer-source.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gvc-mixer-stream.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gvc-speaker-test.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gvc-stream-status-icon.Po@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + set x; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: CTAGS +CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) check-am +all-am: Makefile $(LTLIBRARIES) $(PROGRAMS) +installdirs: + for dir in "$(DESTDIR)$(bindir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES) + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." + -test -z "$(BUILT_SOURCES)" || rm -f $(BUILT_SOURCES) + -test -z "$(MAINTAINERCLEANFILES)" || rm -f $(MAINTAINERCLEANFILES) +clean: clean-am + +clean-am: clean-binPROGRAMS clean-generic clean-libtool \ + clean-noinstLTLIBRARIES mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-binPROGRAMS + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-binPROGRAMS + +.MAKE: all check install install-am install-strip + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-binPROGRAMS \ + clean-generic clean-libtool clean-noinstLTLIBRARIES ctags \ + distclean distclean-compile distclean-generic \ + distclean-libtool distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-binPROGRAMS \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man install-pdf \ + install-pdf-am install-ps install-ps-am install-strip \ + installcheck installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags uninstall uninstall-am uninstall-binPROGRAMS + + +-include $(top_srcdir)/git.mk + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/mate-volume-control/src/applet-main.c b/mate-volume-control/src/applet-main.c new file mode 100644 index 0000000..94d0960 --- /dev/null +++ b/mate-volume-control/src/applet-main.c @@ -0,0 +1,102 @@ +/* -*- 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 <libintl.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> + +#include <glib/gi18n.h> +#include <glib.h> +#include <gtk/gtk.h> +#include <unique/uniqueapp.h> + +#include "gvc-applet.h" +#include "gvc-log.h" + +#define GVCA_DBUS_NAME "org.mate.VolumeControlApplet" + +static gboolean show_version = FALSE; +static gboolean debug = FALSE; + +int +main (int argc, char **argv) +{ + GError *error; + GvcApplet *applet; + UniqueApp *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 *) _(" — MATE 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) { + app = unique_app_new (GVCA_DBUS_NAME, NULL); + if (unique_app_is_running (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/mate-volume-control/src/dialog-main.c b/mate-volume-control/src/dialog-main.c new file mode 100644 index 0000000..21cdd0f --- /dev/null +++ b/mate-volume-control/src/dialog-main.c @@ -0,0 +1,211 @@ +/* -*- 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 <libintl.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> + +#include <glib/gi18n.h> +#include <glib.h> +#include <gtk/gtk.h> +#include <unique/uniqueapp.h> + +#include "gvc-mixer-dialog.h" +#include "gvc-log.h" + +#define GVCA_DBUS_NAME "org.mate.VolumeControl" +#define DIALOG_POPUP_TIMEOUT 3 + +static gboolean show_version = FALSE; +static gboolean debug = FALSE; +static gchar* page = NULL; + +static guint popup_id = 0; +static GtkWidget *dialog = NULL; +static GtkWidget *warning_dialog = NULL; + +static void +on_dialog_response (GtkDialog *dialog, + guint response_id, + gpointer data) +{ + gtk_main_quit (); +} + +static void +on_dialog_close (GtkDialog *dialog, + gpointer data) +{ + gtk_main_quit (); +} + +static UniqueResponse +message_received_cb (UniqueApp *app, + int command, + UniqueMessageData *message_data, + guint time_, + gpointer user_data) +{ + gtk_window_present (GTK_WINDOW (user_data)); + + return UNIQUE_RESPONSE_OK; +} + +static void +on_control_ready (GvcMixerControl *control, + UniqueApp *app) +{ + if (popup_id != 0) { + g_source_remove (popup_id); + popup_id = 0; + } + if (warning_dialog != NULL) { + gtk_widget_destroy (warning_dialog); + warning_dialog = NULL; + } + + if (dialog) + return; + + dialog = GTK_WIDGET (gvc_mixer_dialog_new (control)); + g_signal_connect (dialog, + "response", + G_CALLBACK (on_dialog_response), + NULL); + g_signal_connect (dialog, + "close", + G_CALLBACK (on_dialog_close), + NULL); + + gvc_mixer_dialog_set_page(GVC_MIXER_DIALOG (dialog), page); + + g_signal_connect (app, "message-received", + G_CALLBACK (message_received_cb), dialog); + + gtk_widget_show (dialog); +} + +static void +warning_dialog_answered (GtkDialog *d, + gpointer data) +{ + gtk_widget_destroy (warning_dialog); + gtk_main_quit (); +} + +static gboolean +dialog_popup_timeout (gpointer data) +{ + warning_dialog = gtk_message_dialog_new (GTK_WINDOW(dialog), + 0, + GTK_MESSAGE_INFO, + GTK_BUTTONS_CANCEL, + _("Waiting for sound system to respond")); + g_signal_connect (warning_dialog, "response", + G_CALLBACK (warning_dialog_answered), NULL); + g_signal_connect (warning_dialog, "close", + G_CALLBACK (warning_dialog_answered), NULL); + + gtk_widget_show (warning_dialog); + + return FALSE; +} + +static void +on_control_connecting (GvcMixerControl *control, + UniqueApp *app) +{ + if (popup_id != 0) + return; + + popup_id = g_timeout_add_seconds (DIALOG_POPUP_TIMEOUT, + dialog_popup_timeout, + NULL); +} + +int +main (int argc, char **argv) +{ + GError *error; + GvcMixerControl *control; + UniqueApp *app; + static GOptionEntry entries[] = { + { "page", 'p', 0, G_OPTION_ARG_STRING, &page, N_("Startup page"), "effects|hardware|input|output|applications" }, + { "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 *) _(" — MATE Volume Control"), + 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); + + app = unique_app_new (GVCA_DBUS_NAME, NULL); + if (unique_app_is_running (app)) { + unique_app_send_message (app, UNIQUE_ACTIVATE, NULL); + exit (0); + } + + gtk_icon_theme_append_search_path (gtk_icon_theme_get_default (), + ICON_DATA_DIR); + gtk_window_set_default_icon_name ("multimedia-volume-control"); + + control = gvc_mixer_control_new ("MATE Volume Control Dialog"); + g_signal_connect (control, + "connecting", + G_CALLBACK (on_control_connecting), + app); + g_signal_connect (control, + "ready", + G_CALLBACK (on_control_ready), + app); + gvc_mixer_control_open (control); + + gtk_main (); + + if (control != NULL) { + g_object_unref (control); + } + + return 0; +} diff --git a/mate-volume-control/src/gvc-applet.c b/mate-volume-control/src/gvc-applet.c new file mode 100644 index 0000000..7562fbe --- /dev/null +++ b/mate-volume-control/src/gvc-applet.c @@ -0,0 +1,310 @@ +/* -*- 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 <stdlib.h> +#include <stdio.h> +#include <fcntl.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <gtk/gtk.h> + +#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", + "audio-volume-low", + "audio-volume-medium", + "audio-volume-high", + NULL +}; + +static const char *input_icon_names[] = { + "audio-input-microphone-muted", + "audio-input-microphone-low", + "audio-input-microphone-medium", + "audio-input-microphone-high", + 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.mate.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 ("MATE 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/mate-volume-control/src/gvc-applet.h b/mate-volume-control/src/gvc-applet.h new file mode 100644 index 0000000..d9e0311 --- /dev/null +++ b/mate-volume-control/src/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 <glib-object.h> + +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/mate-volume-control/src/gvc-balance-bar.c b/mate-volume-control/src/gvc-balance-bar.c new file mode 100644 index 0000000..6f5c47f --- /dev/null +++ b/mate-volume-control/src/gvc-balance-bar.c @@ -0,0 +1,548 @@ +/* -*- 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 <stdlib.h> +#include <stdio.h> +#include <unistd.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <gtk/gtk.h> +#include <canberra-gtk.h> + +#include "gvc-balance-bar.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 ("<small>%s</small>", C_("balance", "Left")); + str_upper = g_strdup_printf ("<small>%s</small>", C_("balance", "Right")); + break; + case BALANCE_TYPE_FR: + str_lower = g_strdup_printf ("<small>%s</small>", C_("balance", "Rear")); + str_upper = g_strdup_printf ("<small>%s</small>", C_("balance", "Front")); + break; + case BALANCE_TYPE_LFE: + str_lower = g_strdup_printf ("<small>%s</small>", C_("balance", "Minimum")); + str_upper = g_strdup_printf ("<small>%s</small>", 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/mate-volume-control/src/gvc-balance-bar.h b/mate-volume-control/src/gvc-balance-bar.h new file mode 100644 index 0000000..95e96dc --- /dev/null +++ b/mate-volume-control/src/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 <glib-object.h> + +#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/mate-volume-control/src/gvc-channel-bar.c b/mate-volume-control/src/gvc-channel-bar.c new file mode 100644 index 0000000..d750871 --- /dev/null +++ b/mate-volume-control/src/gvc-channel-bar.c @@ -0,0 +1,930 @@ +/* -*- 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 <stdlib.h> +#include <stdio.h> +#include <unistd.h> + +#include <pulse/pulseaudio.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <gtk/gtk.h> +#include <canberra-gtk.h> + +#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, +}; + +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); + + 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->image, 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); + + 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.mate.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 ("<small>%s</small>", C_("volume", "100%")); + gtk_scale_add_mark (GTK_SCALE (bar->priv->scale), ADJUSTMENT_MAX_NORMAL, + GTK_POS_BOTTOM, str); + } else { + str = g_strdup_printf ("<small>%s</small>", 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 ("<small>%s</small>", 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); + } +} + +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; + 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; + 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_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/mate-volume-control/src/gvc-channel-bar.h b/mate-volume-control/src/gvc-channel-bar.h new file mode 100644 index 0000000..d405038 --- /dev/null +++ b/mate-volume-control/src/gvc-channel-bar.h @@ -0,0 +1,86 @@ +/* -*- 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 <glib-object.h> + +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_scroll (GvcChannelBar *bar, + GdkScrollDirection direction); + +G_END_DECLS + +#endif /* __GVC_CHANNEL_BAR_H */ diff --git a/mate-volume-control/src/gvc-channel-map.c b/mate-volume-control/src/gvc-channel-map.c new file mode 100644 index 0000000..a7a412c --- /dev/null +++ b/mate-volume-control/src/gvc-channel-map.c @@ -0,0 +1,245 @@ +/* -*- 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 <stdlib.h> +#include <stdio.h> +#include <unistd.h> + +#include <glib.h> +#include <glib/gi18n.h> + +#include <pulse/pulseaudio.h> + +#include "gvc-channel-map.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); +} + +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/mate-volume-control/src/gvc-channel-map.h b/mate-volume-control/src/gvc-channel-map.h new file mode 100644 index 0000000..a149ee3 --- /dev/null +++ b/mate-volume-control/src/gvc-channel-map.h @@ -0,0 +1,81 @@ +/* -*- 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 <glib-object.h> +#include <pulse/pulseaudio.h> + +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, +}; + +#define NUM_TYPES LFE + 1 + +GType gvc_channel_map_get_type (void); + +GvcChannelMap * gvc_channel_map_new (void); +GvcChannelMap * gvc_channel_map_new_from_pa_channel_map (const pa_channel_map *map); +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) + +void gvc_channel_map_volume_changed (GvcChannelMap *map, + const pa_cvolume *cv, + gboolean set); +const char * gvc_channel_map_get_mapping (const GvcChannelMap *map); + +/* private */ +const pa_cvolume * gvc_channel_map_get_cvolume (const GvcChannelMap *map); +const pa_channel_map * gvc_channel_map_get_pa_channel_map (const GvcChannelMap *map); +G_END_DECLS + +#endif /* __GVC_CHANNEL_MAP_H */ diff --git a/mate-volume-control/src/gvc-combo-box.c b/mate-volume-control/src/gvc-combo-box.c new file mode 100644 index 0000000..5e0d82c --- /dev/null +++ b/mate-volume-control/src/gvc-combo-box.c @@ -0,0 +1,394 @@ +/* -*- 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 <stdlib.h> +#include <stdio.h> +#include <unistd.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <gtk/gtk.h> +#include <canberra-gtk.h> + +#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/mate-volume-control/src/gvc-combo-box.h b/mate-volume-control/src/gvc-combo-box.h new file mode 100644 index 0000000..f1ffc0d --- /dev/null +++ b/mate-volume-control/src/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 <glib-object.h> + +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/mate-volume-control/src/gvc-level-bar.c b/mate-volume-control/src/gvc-level-bar.c new file mode 100644 index 0000000..12e2f1f --- /dev/null +++ b/mate-volume-control/src/gvc-level-bar.c @@ -0,0 +1,753 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 William Jon McCann <[email protected]> + * + * 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 <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <math.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <gtk/gtk.h> + +#include "gvc-level-bar.h" + +#define NUM_BOXES 15 + +#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; + } + + 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_expose (GtkWidget *widget, + GdkEventExpose *event) +{ + GvcLevelBar *bar; + cairo_t *cr; + GtkAllocation allocation; + + g_return_val_if_fail (GVC_IS_LEVEL_BAR (widget), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + /* event queue compression */ + if (event->count > 0) { + return FALSE; + } + + bar = GVC_LEVEL_BAR (widget); + + cr = gdk_cairo_create (gtk_widget_get_window (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); + } + } + cairo_destroy (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->expose_event = gvc_level_bar_expose; + 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/mate-volume-control/src/gvc-level-bar.h b/mate-volume-control/src/gvc-level-bar.h new file mode 100644 index 0000000..917b415 --- /dev/null +++ b/mate-volume-control/src/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 <[email protected]> + * + * 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 <glib-object.h> +#include <gtk/gtk.h> + +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/mate-volume-control/src/gvc-log.c b/mate-volume-control/src/gvc-log.c new file mode 100644 index 0000000..03a9486 --- /dev/null +++ b/mate-volume-control/src/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 <glib.h> +#include <glib/gstdio.h> + +#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/mate-volume-control/src/gvc-log.h b/mate-volume-control/src/gvc-log.h new file mode 100644 index 0000000..bc1cdd5 --- /dev/null +++ b/mate-volume-control/src/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 <glib.h> + +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/mate-volume-control/src/gvc-mixer-card.c b/mate-volume-control/src/gvc-mixer-card.c new file mode 100644 index 0000000..dc4ccca --- /dev/null +++ b/mate-volume-control/src/gvc-mixer-card.c @@ -0,0 +1,498 @@ +/* -*- 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 <stdlib.h> +#include <stdio.h> +#include <unistd.h> + +#include <glib.h> +#include <glib/gi18n.h> + +#include <pulse/pulseaudio.h> + +#include "gvc-mixer-card.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; +} + +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/mate-volume-control/src/gvc-mixer-card.h b/mate-volume-control/src/gvc-mixer-card.h new file mode 100644 index 0000000..ad24cd6 --- /dev/null +++ b/mate-volume-control/src/gvc-mixer-card.h @@ -0,0 +1,87 @@ +/* -*- 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 <glib-object.h> +#include <pulse/pulseaudio.h> + +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); +GvcMixerCard * gvc_mixer_card_new (pa_context *context, + guint index); + +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); + +pa_context * gvc_mixer_card_get_pa_context (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/mate-volume-control/src/gvc-mixer-control.c b/mate-volume-control/src/gvc-mixer-control.c new file mode 100644 index 0000000..b504427 --- /dev/null +++ b/mate-volume-control/src/gvc-mixer-control.c @@ -0,0 +1,2150 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2006-2008 Lennart Poettering + * Copyright (C) 2008 Sjoerd Simons <[email protected]> + * 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 <stdlib.h> +#include <stdio.h> +#include <unistd.h> + +#include <glib.h> +#include <glib/gi18n.h> + +#include <pulse/pulseaudio.h> +#include <pulse/glib-mainloop.h> +#include <pulse/ext-stream-restore.h> + +#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" + +#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; +} + +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; +} + +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; +} + +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)); +} + +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); +} + +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); +} + +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); +} + +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); +} + +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); +} + +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); +} + +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); +} + +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.mate.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/mate-volume-control/src/gvc-mixer-control.h b/mate-volume-control/src/gvc-mixer-control.h new file mode 100644 index 0000000..d49e5a3 --- /dev/null +++ b/mate-volume-control/src/gvc-mixer-control.h @@ -0,0 +1,98 @@ +/* -*- 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 <glib-object.h> +#include <pulse/pulseaudio.h> +#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); + +pa_context * gvc_mixer_control_get_pa_context (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/mate-volume-control/src/gvc-mixer-dialog.c b/mate-volume-control/src/gvc-mixer-dialog.c new file mode 100644 index 0000000..cf8fcc1 --- /dev/null +++ b/mate-volume-control/src/gvc-mixer-dialog.c @@ -0,0 +1,2134 @@ +/* -*- 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 <stdlib.h> +#include <stdio.h> +#include <unistd.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <gdk/gdkkeysyms.h> +#include <gtk/gtk.h> + +#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" + +#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; + GtkSizeGroup *apps_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_DIALOG) + +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.mate.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, + GtkSizeGroup *size_group, + gboolean symmetric) +{ + GtkWidget *bar; + + bar = gvc_channel_bar_new (); + gtk_widget_set_sensitive (bar, FALSE); + if (size_group != NULL) { + gvc_channel_bar_set_size_group (GVC_CHANNEL_BAR (bar), + 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 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.mate.VolumeControl") != 0 + && g_strcmp0 (id, "org.PulseAudio.pavucontrol") != 0) { + const char *name; + + bar = create_bar (dialog, dialog->priv->apps_size_group, FALSE); + + name = gvc_mixer_stream_get_name (stream); + 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); + } + + gvc_channel_bar_set_icon_name (GVC_CHANNEL_BAR (bar), + 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<i>%s</i>", + 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)); + d = gtk_dialog_new_with_buttons (title, + GTK_WINDOW (dialog), + GTK_DIALOG_MODAL | +#if !GTK_CHECK_VERSION (2, 21, 8) + GTK_DIALOG_NO_SEPARATOR | +#endif + 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<i>%s</i>\n<i>%s</i>", + 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 const guint tab_accel_keys[] = { + GDK_1, GDK_2, GDK_3, GDK_4, GDK_5 +}; + +static void +dialog_accel_cb (GtkAccelGroup *accelgroup, + GObject *object, + guint key, + GdkModifierType mod, + GvcMixerDialog *self) +{ + gint num = -1; + gint i; + + for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) { + if (tab_accel_keys[i] == key) { + num = i; + break; + } + } + + if (num != -1) { + gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook), num); + } +} + +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; + GtkAccelGroup *accel_group; + GClosure *closure; + gint i; + + object = G_OBJECT_CLASS (gvc_mixer_dialog_parent_class)->constructor (type, n_construct_properties, construct_params); + + self = GVC_MIXER_DIALOG (object); + gtk_dialog_add_button (GTK_DIALOG (self), "gtk-close", GTK_RESPONSE_OK); + + main_vbox = gtk_dialog_get_content_area (GTK_DIALOG (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, self->priv->size_group, 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); + + /* Set up accels (borrowed from Empathy) */ + accel_group = gtk_accel_group_new (); + gtk_window_add_accel_group (GTK_WINDOW (self), accel_group); + + for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) { + closure = g_cclosure_new (G_CALLBACK (dialog_accel_cb), + self, + NULL); + gtk_accel_group_connect (accel_group, + tab_accel_keys[i], + GDK_MOD1_MASK, + 0, + closure); + } + + g_object_unref (accel_group); + + /* 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, self->priv->size_group, 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, self->priv->size_group, 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); + dialog->priv->apps_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, + "icon-name", "multimedia-volume-control", + "title", _("Sound Preferences"), + "has-separator", FALSE, + "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/mate-volume-control/src/gvc-mixer-dialog.h b/mate-volume-control/src/gvc-mixer-dialog.h new file mode 100644 index 0000000..64857b3 --- /dev/null +++ b/mate-volume-control/src/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 <glib-object.h> +#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 +{ + GtkDialog parent; + GvcMixerDialogPrivate *priv; +} GvcMixerDialog; + +typedef struct +{ + GtkDialogClass 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/mate-volume-control/src/gvc-mixer-event-role.c b/mate-volume-control/src/gvc-mixer-event-role.c new file mode 100644 index 0000000..6166d1d --- /dev/null +++ b/mate-volume-control/src/gvc-mixer-event-role.c @@ -0,0 +1,239 @@ +/* -*- 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 <stdlib.h> +#include <stdio.h> +#include <unistd.h> + +#include <glib.h> +#include <glib/gi18n.h> + +#include <pulse/pulseaudio.h> +#include <pulse/ext-stream-restore.h> + +#include "gvc-mixer-event-role.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); +} + +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/mate-volume-control/src/gvc-mixer-event-role.h b/mate-volume-control/src/gvc-mixer-event-role.h new file mode 100644 index 0000000..ab4c509 --- /dev/null +++ b/mate-volume-control/src/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 <glib-object.h> +#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/mate-volume-control/src/gvc-mixer-sink-input.c b/mate-volume-control/src/gvc-mixer-sink-input.c new file mode 100644 index 0000000..9d4ad2a --- /dev/null +++ b/mate-volume-control/src/gvc-mixer-sink-input.c @@ -0,0 +1,188 @@ +/* -*- 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 <stdlib.h> +#include <stdio.h> +#include <unistd.h> + +#include <glib.h> +#include <glib/gi18n.h> + +#include <pulse/pulseaudio.h> + +#include "gvc-mixer-sink-input.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); +} + +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/mate-volume-control/src/gvc-mixer-sink-input.h b/mate-volume-control/src/gvc-mixer-sink-input.h new file mode 100644 index 0000000..8a4b714 --- /dev/null +++ b/mate-volume-control/src/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 <glib-object.h> +#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/mate-volume-control/src/gvc-mixer-sink.c b/mate-volume-control/src/gvc-mixer-sink.c new file mode 100644 index 0000000..649b840 --- /dev/null +++ b/mate-volume-control/src/gvc-mixer-sink.c @@ -0,0 +1,220 @@ +/* -*- 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 <stdlib.h> +#include <stdio.h> +#include <unistd.h> + +#include <glib.h> +#include <glib/gi18n.h> + +#include <pulse/pulseaudio.h> + +#include "gvc-mixer-sink.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); +} + +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/mate-volume-control/src/gvc-mixer-sink.h b/mate-volume-control/src/gvc-mixer-sink.h new file mode 100644 index 0000000..2a4a4ba --- /dev/null +++ b/mate-volume-control/src/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 <glib-object.h> +#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/mate-volume-control/src/gvc-mixer-source-output.c b/mate-volume-control/src/gvc-mixer-source-output.c new file mode 100644 index 0000000..b4cc34d --- /dev/null +++ b/mate-volume-control/src/gvc-mixer-source-output.c @@ -0,0 +1,128 @@ +/* -*- 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 <stdlib.h> +#include <stdio.h> +#include <unistd.h> + +#include <glib.h> +#include <glib/gi18n.h> + +#include <pulse/pulseaudio.h> + +#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); +} + +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/mate-volume-control/src/gvc-mixer-source-output.h b/mate-volume-control/src/gvc-mixer-source-output.h new file mode 100644 index 0000000..2283e3b --- /dev/null +++ b/mate-volume-control/src/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 <glib-object.h> +#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/mate-volume-control/src/gvc-mixer-source.c b/mate-volume-control/src/gvc-mixer-source.c new file mode 100644 index 0000000..309bcb9 --- /dev/null +++ b/mate-volume-control/src/gvc-mixer-source.c @@ -0,0 +1,220 @@ +/* -*- 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 <stdlib.h> +#include <stdio.h> +#include <unistd.h> + +#include <glib.h> +#include <glib/gi18n.h> + +#include <pulse/pulseaudio.h> + +#include "gvc-mixer-source.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); +} + +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/mate-volume-control/src/gvc-mixer-source.h b/mate-volume-control/src/gvc-mixer-source.h new file mode 100644 index 0000000..503f1b5 --- /dev/null +++ b/mate-volume-control/src/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 <glib-object.h> +#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/mate-volume-control/src/gvc-mixer-stream.c b/mate-volume-control/src/gvc-mixer-stream.c new file mode 100644 index 0000000..243d773 --- /dev/null +++ b/mate-volume-control/src/gvc-mixer-stream.c @@ -0,0 +1,912 @@ +/* -*- 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 <stdlib.h> +#include <stdio.h> +#include <unistd.h> + +#include <glib.h> +#include <glib/gi18n.h> + +#include <pulse/pulseaudio.h> + +#include "gvc-mixer-stream.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; +} + +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]); +} + +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; +} + +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; +} + +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/mate-volume-control/src/gvc-mixer-stream.h b/mate-volume-control/src/gvc-mixer-stream.h new file mode 100644 index 0000000..bbeb8a3 --- /dev/null +++ b/mate-volume-control/src/gvc-mixer-stream.h @@ -0,0 +1,127 @@ +/* -*- 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 <glib-object.h> +#include <pulse/pulseaudio.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); + +pa_context * gvc_mixer_stream_get_pa_context (GvcMixerStream *stream); +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/mate-volume-control/src/gvc-speaker-test.c b/mate-volume-control/src/gvc-speaker-test.c new file mode 100644 index 0000000..b3bb99c --- /dev/null +++ b/mate-volume-control/src/gvc-speaker-test.c @@ -0,0 +1,499 @@ +/* -*- 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 <stdlib.h> +#include <stdio.h> +#include <unistd.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <gtk/gtk.h> +#include <canberra.h> +#include <canberra-gtk.h> + +#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.mate.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/mate-volume-control/src/gvc-speaker-test.h b/mate-volume-control/src/gvc-speaker-test.h new file mode 100644 index 0000000..912c594 --- /dev/null +++ b/mate-volume-control/src/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 <glib-object.h> +#include <gvc-mixer-card.h> +#include <gvc-mixer-control.h> + +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/mate-volume-control/src/gvc-stream-status-icon.c b/mate-volume-control/src/gvc-stream-status-icon.c new file mode 100644 index 0000000..f89d39e --- /dev/null +++ b/mate-volume-control/src/gvc-stream-status-icon.c @@ -0,0 +1,825 @@ +/* -*- 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 <stdlib.h> +#include <stdio.h> +#include <unistd.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <gtk/gtk.h> +#if GTK_CHECK_VERSION (2, 21, 8) +#include <gdk/gdkkeysyms-compat.h> +#else +#include <gdk/gdkkeysyms.h> +#endif + +#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_size_request (icon->priv->dock, &dock_req); + + 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), + "mate-volume-control", + &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_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 ( + "<b>%s: %s</b>\n<small>%s</small>", + 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 ( + "<b>%s: %.0f%%</b>\n<small>%0.2f dB\n%s</small>", + 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 ( + "<b>%s: %.0f%%</b>\n<small>-∞ dB\n%s</small>", + icon->priv->display_name, + 100 * (float)volume / PA_VOLUME_NORM, + gvc_mixer_stream_get_description (icon->priv->mixer_stream)); + } else { + markup = g_strdup_printf ( + "<b>%s: %.0f%%</b>\n<small>%s</small>", + 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/mate-volume-control/src/gvc-stream-status-icon.h b/mate-volume-control/src/gvc-stream-status-icon.h new file mode 100644 index 0000000..55887f3 --- /dev/null +++ b/mate-volume-control/src/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 <glib-object.h> +#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 */ |