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 */  | 
