summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2006-06-02 20:25:06 +0000
committerLennart Poettering <lennart@poettering.net>2006-06-02 20:25:06 +0000
commitf090e48f9f76b3159cf62c8fe9a162f145c4fce1 (patch)
treec4c375a0946cdef711a2191cff40bafcbc308277 /src
parentf31ac41e0dce32de9a27339de2f089b720af698a (diff)
initial checkin
git-svn-id: file:///home/lennart/svn/public/padevchooser/trunk@3 e4aeda27-4315-0410-ac56-b21855d76123
Diffstat (limited to 'src')
-rw-r--r--src/Makefile10
-rw-r--r--src/config.h1
-rw-r--r--src/eggtrayicon.c513
-rw-r--r--src/eggtrayicon.h80
-rw-r--r--src/padevchooser.c758
-rw-r--r--src/padevchooser.glade321
-rw-r--r--src/padevchooser.gladep8
-rw-r--r--src/x11prop.c70
-rw-r--r--src/x11prop.h33
9 files changed, 1794 insertions, 0 deletions
diff --git a/src/Makefile b/src/Makefile
new file mode 100644
index 0000000..82d6ead
--- /dev/null
+++ b/src/Makefile
@@ -0,0 +1,10 @@
+CFLAGS=-Wextra -Wall -g -O0 -pipe `pkg-config --cflags gtk+-2.0 polyplib polyplib-browse polyplib-glib-mainloop libnotify libglade-2.0 gconf-2.0` -I. -DEGG_COMPILATION
+LIBS=`pkg-config --libs gtk+-2.0 polyplib polyplib-browse polyplib-glib-mainloop libnotify libglade-2.0 gconf-2.0`
+
+padevchooser: eggtrayicon.o padevchooser.o x11prop.o
+ $(CC) -o $@ $^ $(LIBS)
+
+clean:
+ rm -f *.o padevchooser
+
+.PHONY: clean
diff --git a/src/config.h b/src/config.h
new file mode 100644
index 0000000..c498b27
--- /dev/null
+++ b/src/config.h
@@ -0,0 +1 @@
+#define GETTEX_PACKAGE foo
diff --git a/src/eggtrayicon.c b/src/eggtrayicon.c
new file mode 100644
index 0000000..07ce5df
--- /dev/null
+++ b/src/eggtrayicon.c
@@ -0,0 +1,513 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* eggtrayicon.c
+ * Copyright (C) 2002 Anders Carlsson <andersca@gnu.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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 Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <string.h>
+#include <libintl.h>
+
+#include "eggtrayicon.h"
+
+#include <gdkconfig.h>
+#if defined (GDK_WINDOWING_X11)
+#include <gdk/gdkx.h>
+#include <X11/Xatom.h>
+#elif defined (GDK_WINDOWING_WIN32)
+#include <gdk/gdkwin32.h>
+#endif
+
+#ifndef EGG_COMPILATION
+#ifndef _
+#define _(x) dgettext (GETTEXT_PACKAGE, x)
+#define N_(x) x
+#endif
+#else
+#define _(x) x
+#define N_(x) x
+#endif
+
+#define SYSTEM_TRAY_REQUEST_DOCK 0
+#define SYSTEM_TRAY_BEGIN_MESSAGE 1
+#define SYSTEM_TRAY_CANCEL_MESSAGE 2
+
+#define SYSTEM_TRAY_ORIENTATION_HORZ 0
+#define SYSTEM_TRAY_ORIENTATION_VERT 1
+
+enum {
+ PROP_0,
+ PROP_ORIENTATION
+};
+
+static GtkPlugClass *parent_class = NULL;
+
+static void egg_tray_icon_init (EggTrayIcon *icon);
+static void egg_tray_icon_class_init (EggTrayIconClass *klass);
+
+static void egg_tray_icon_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void egg_tray_icon_realize (GtkWidget *widget);
+static void egg_tray_icon_unrealize (GtkWidget *widget);
+
+#ifdef GDK_WINDOWING_X11
+static void egg_tray_icon_update_manager_window (EggTrayIcon *icon,
+ gboolean dock_if_realized);
+static void egg_tray_icon_manager_window_destroyed (EggTrayIcon *icon);
+#endif
+
+GType
+egg_tray_icon_get_type (void)
+{
+ static GType our_type = 0;
+
+ if (our_type == 0)
+ {
+ static const GTypeInfo our_info =
+ {
+ sizeof (EggTrayIconClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) egg_tray_icon_class_init,
+ NULL, /* class_finalize */
+ NULL, /* class_data */
+ sizeof (EggTrayIcon),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) egg_tray_icon_init
+ };
+
+ our_type = g_type_register_static (GTK_TYPE_PLUG, "EggTrayIcon", &our_info, 0);
+ }
+
+ return our_type;
+}
+
+static void
+egg_tray_icon_init (EggTrayIcon *icon)
+{
+ icon->stamp = 1;
+ icon->orientation = GTK_ORIENTATION_HORIZONTAL;
+
+ gtk_widget_add_events (GTK_WIDGET (icon), GDK_PROPERTY_CHANGE_MASK);
+}
+
+static void
+egg_tray_icon_class_init (EggTrayIconClass *klass)
+{
+ GObjectClass *gobject_class = (GObjectClass *)klass;
+ GtkWidgetClass *widget_class = (GtkWidgetClass *)klass;
+
+ parent_class = g_type_class_peek_parent (klass);
+
+ gobject_class->get_property = egg_tray_icon_get_property;
+
+ widget_class->realize = egg_tray_icon_realize;
+ widget_class->unrealize = egg_tray_icon_unrealize;
+
+ g_object_class_install_property (gobject_class,
+ PROP_ORIENTATION,
+ g_param_spec_enum ("orientation",
+ _("Orientation"),
+ _("The orientation of the tray."),
+ GTK_TYPE_ORIENTATION,
+ GTK_ORIENTATION_HORIZONTAL,
+ G_PARAM_READABLE));
+
+#if defined (GDK_WINDOWING_X11)
+ /* Nothing */
+#elif defined (GDK_WINDOWING_WIN32)
+ g_warning ("Port eggtrayicon to Win32");
+#else
+ g_warning ("Port eggtrayicon to this GTK+ backend");
+#endif
+}
+
+static void
+egg_tray_icon_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ EggTrayIcon *icon = EGG_TRAY_ICON (object);
+
+ switch (prop_id)
+ {
+ case PROP_ORIENTATION:
+ g_value_set_enum (value, icon->orientation);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+#ifdef GDK_WINDOWING_X11
+
+static void
+egg_tray_icon_get_orientation_property (EggTrayIcon *icon)
+{
+ Display *xdisplay;
+ Atom type;
+ int format;
+ union {
+ gulong *prop;
+ guchar *prop_ch;
+ } prop = { NULL };
+ gulong nitems;
+ gulong bytes_after;
+ int error, result;
+
+ g_assert (icon->manager_window != None);
+
+ xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
+
+ gdk_error_trap_push ();
+ type = None;
+ result = XGetWindowProperty (xdisplay,
+ icon->manager_window,
+ icon->orientation_atom,
+ 0, G_MAXLONG, FALSE,
+ XA_CARDINAL,
+ &type, &format, &nitems,
+ &bytes_after, &(prop.prop_ch));
+ error = gdk_error_trap_pop ();
+
+ if (error || result != Success)
+ return;
+
+ if (type == XA_CARDINAL)
+ {
+ GtkOrientation orientation;
+
+ orientation = (prop.prop [0] == SYSTEM_TRAY_ORIENTATION_HORZ) ?
+ GTK_ORIENTATION_HORIZONTAL :
+ GTK_ORIENTATION_VERTICAL;
+
+ if (icon->orientation != orientation)
+ {
+ icon->orientation = orientation;
+
+ g_object_notify (G_OBJECT (icon), "orientation");
+ }
+ }
+
+ if (prop.prop)
+ XFree (prop.prop);
+}
+
+static GdkFilterReturn
+egg_tray_icon_manager_filter (GdkXEvent *xevent, GdkEvent *event, gpointer user_data)
+{
+ EggTrayIcon *icon = user_data;
+ XEvent *xev = (XEvent *)xevent;
+
+ if (xev->xany.type == ClientMessage &&
+ xev->xclient.message_type == icon->manager_atom &&
+ xev->xclient.data.l[1] == icon->selection_atom)
+ {
+ egg_tray_icon_update_manager_window (icon, TRUE);
+ }
+ else if (xev->xany.window == icon->manager_window)
+ {
+ if (xev->xany.type == PropertyNotify &&
+ xev->xproperty.atom == icon->orientation_atom)
+ {
+ egg_tray_icon_get_orientation_property (icon);
+ }
+ if (xev->xany.type == DestroyNotify)
+ {
+ egg_tray_icon_manager_window_destroyed (icon);
+ }
+ }
+ return GDK_FILTER_CONTINUE;
+}
+
+#endif
+
+static void
+egg_tray_icon_unrealize (GtkWidget *widget)
+{
+#ifdef GDK_WINDOWING_X11
+ EggTrayIcon *icon = EGG_TRAY_ICON (widget);
+ GdkWindow *root_window;
+
+ if (icon->manager_window != None)
+ {
+ GdkWindow *gdkwin;
+
+ gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (widget),
+ icon->manager_window);
+
+ gdk_window_remove_filter (gdkwin, egg_tray_icon_manager_filter, icon);
+ }
+
+ root_window = gdk_screen_get_root_window (gtk_widget_get_screen (widget));
+
+ gdk_window_remove_filter (root_window, egg_tray_icon_manager_filter, icon);
+
+ if (GTK_WIDGET_CLASS (parent_class)->unrealize)
+ (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
+#endif
+}
+
+#ifdef GDK_WINDOWING_X11
+
+static void
+egg_tray_icon_send_manager_message (EggTrayIcon *icon,
+ long message,
+ Window window,
+ long data1,
+ long data2,
+ long data3)
+{
+ XClientMessageEvent ev;
+ Display *display;
+
+ ev.type = ClientMessage;
+ ev.window = window;
+ ev.message_type = icon->system_tray_opcode_atom;
+ ev.format = 32;
+ ev.data.l[0] = gdk_x11_get_server_time (GTK_WIDGET (icon)->window);
+ ev.data.l[1] = message;
+ ev.data.l[2] = data1;
+ ev.data.l[3] = data2;
+ ev.data.l[4] = data3;
+
+ display = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
+
+ gdk_error_trap_push ();
+ XSendEvent (display,
+ icon->manager_window, False, NoEventMask, (XEvent *)&ev);
+ XSync (display, False);
+ gdk_error_trap_pop ();
+}
+
+static void
+egg_tray_icon_send_dock_request (EggTrayIcon *icon)
+{
+ egg_tray_icon_send_manager_message (icon,
+ SYSTEM_TRAY_REQUEST_DOCK,
+ icon->manager_window,
+ gtk_plug_get_id (GTK_PLUG (icon)),
+ 0, 0);
+}
+
+static void
+egg_tray_icon_update_manager_window (EggTrayIcon *icon,
+ gboolean dock_if_realized)
+{
+ Display *xdisplay;
+
+ if (icon->manager_window != None)
+ return;
+
+ xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
+
+ XGrabServer (xdisplay);
+
+ icon->manager_window = XGetSelectionOwner (xdisplay,
+ icon->selection_atom);
+
+ if (icon->manager_window != None)
+ XSelectInput (xdisplay,
+ icon->manager_window, StructureNotifyMask|PropertyChangeMask);
+
+ XUngrabServer (xdisplay);
+ XFlush (xdisplay);
+
+ if (icon->manager_window != None)
+ {
+ GdkWindow *gdkwin;
+
+ gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (GTK_WIDGET (icon)),
+ icon->manager_window);
+
+ gdk_window_add_filter (gdkwin, egg_tray_icon_manager_filter, icon);
+
+ if (dock_if_realized && GTK_WIDGET_REALIZED (icon))
+ egg_tray_icon_send_dock_request (icon);
+
+ egg_tray_icon_get_orientation_property (icon);
+ }
+}
+
+static void
+egg_tray_icon_manager_window_destroyed (EggTrayIcon *icon)
+{
+ GdkWindow *gdkwin;
+
+ g_return_if_fail (icon->manager_window != None);
+
+ gdkwin = gdk_window_lookup_for_display (gtk_widget_get_display (GTK_WIDGET (icon)),
+ icon->manager_window);
+
+ gdk_window_remove_filter (gdkwin, egg_tray_icon_manager_filter, icon);
+
+ icon->manager_window = None;
+
+ egg_tray_icon_update_manager_window (icon, TRUE);
+}
+
+#endif
+
+static void
+egg_tray_icon_realize (GtkWidget *widget)
+{
+#ifdef GDK_WINDOWING_X11
+ EggTrayIcon *icon = EGG_TRAY_ICON (widget);
+ GdkScreen *screen;
+ GdkDisplay *display;
+ Display *xdisplay;
+ char buffer[256];
+ GdkWindow *root_window;
+
+ if (GTK_WIDGET_CLASS (parent_class)->realize)
+ GTK_WIDGET_CLASS (parent_class)->realize (widget);
+
+ screen = gtk_widget_get_screen (widget);
+ display = gdk_screen_get_display (screen);
+ xdisplay = gdk_x11_display_get_xdisplay (display);
+
+ /* Now see if there's a manager window around */
+ g_snprintf (buffer, sizeof (buffer),
+ "_NET_SYSTEM_TRAY_S%d",
+ gdk_screen_get_number (screen));
+
+ icon->selection_atom = XInternAtom (xdisplay, buffer, False);
+
+ icon->manager_atom = XInternAtom (xdisplay, "MANAGER", False);
+
+ icon->system_tray_opcode_atom = XInternAtom (xdisplay,
+ "_NET_SYSTEM_TRAY_OPCODE",
+ False);
+
+ icon->orientation_atom = XInternAtom (xdisplay,
+ "_NET_SYSTEM_TRAY_ORIENTATION",
+ False);
+
+ egg_tray_icon_update_manager_window (icon, FALSE);
+ egg_tray_icon_send_dock_request (icon);
+
+ root_window = gdk_screen_get_root_window (screen);
+
+ /* Add a root window filter so that we get changes on MANAGER */
+ gdk_window_add_filter (root_window,
+ egg_tray_icon_manager_filter, icon);
+#endif
+}
+
+EggTrayIcon *
+egg_tray_icon_new_for_screen (GdkScreen *screen, const char *name)
+{
+ g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL);
+
+ return g_object_new (EGG_TYPE_TRAY_ICON, "screen", screen, "title", name, NULL);
+}
+
+EggTrayIcon*
+egg_tray_icon_new (const gchar *name)
+{
+ return g_object_new (EGG_TYPE_TRAY_ICON, "title", name, NULL);
+}
+
+guint
+egg_tray_icon_send_message (EggTrayIcon *icon,
+ gint timeout,
+ const gchar *message,
+ gint len)
+{
+ guint stamp;
+
+ g_return_val_if_fail (EGG_IS_TRAY_ICON (icon), 0);
+ g_return_val_if_fail (timeout >= 0, 0);
+ g_return_val_if_fail (message != NULL, 0);
+
+#ifdef GDK_WINDOWING_X11
+ if (icon->manager_window == None)
+ return 0;
+#endif
+
+ if (len < 0)
+ len = strlen (message);
+
+ stamp = icon->stamp++;
+
+#ifdef GDK_WINDOWING_X11
+ /* Get ready to send the message */
+ egg_tray_icon_send_manager_message (icon, SYSTEM_TRAY_BEGIN_MESSAGE,
+ (Window)gtk_plug_get_id (GTK_PLUG (icon)),
+ timeout, len, stamp);
+
+ /* Now to send the actual message */
+ gdk_error_trap_push ();
+ while (len > 0)
+ {
+ XClientMessageEvent ev;
+ Display *xdisplay;
+
+ xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon)));
+
+ ev.type = ClientMessage;
+ ev.window = (Window)gtk_plug_get_id (GTK_PLUG (icon));
+ ev.format = 8;
+ ev.message_type = XInternAtom (xdisplay,
+ "_NET_SYSTEM_TRAY_MESSAGE_DATA", False);
+ if (len > 20)
+ {
+ memcpy (&ev.data, message, 20);
+ len -= 20;
+ message += 20;
+ }
+ else
+ {
+ memcpy (&ev.data, message, len);
+ len = 0;
+ }
+
+ XSendEvent (xdisplay,
+ icon->manager_window, False, StructureNotifyMask, (XEvent *)&ev);
+ XSync (xdisplay, False);
+ }
+ gdk_error_trap_pop ();
+#endif
+
+ return stamp;
+}
+
+void
+egg_tray_icon_cancel_message (EggTrayIcon *icon,
+ guint id)
+{
+ g_return_if_fail (EGG_IS_TRAY_ICON (icon));
+ g_return_if_fail (id > 0);
+#ifdef GDK_WINDOWING_X11
+ egg_tray_icon_send_manager_message (icon, SYSTEM_TRAY_CANCEL_MESSAGE,
+ (Window)gtk_plug_get_id (GTK_PLUG (icon)),
+ id, 0, 0);
+#endif
+}
+
+GtkOrientation
+egg_tray_icon_get_orientation (EggTrayIcon *icon)
+{
+ g_return_val_if_fail (EGG_IS_TRAY_ICON (icon), GTK_ORIENTATION_HORIZONTAL);
+
+ return icon->orientation;
+}
diff --git a/src/eggtrayicon.h b/src/eggtrayicon.h
new file mode 100644
index 0000000..e6664fc
--- /dev/null
+++ b/src/eggtrayicon.h
@@ -0,0 +1,80 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* eggtrayicon.h
+ * Copyright (C) 2002 Anders Carlsson <andersca@gnu.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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 Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __EGG_TRAY_ICON_H__
+#define __EGG_TRAY_ICON_H__
+
+#include <gtk/gtkplug.h>
+#ifdef GDK_WINDOWING_X11
+#include <gdk/gdkx.h>
+#endif
+
+G_BEGIN_DECLS
+
+#define EGG_TYPE_TRAY_ICON (egg_tray_icon_get_type ())
+#define EGG_TRAY_ICON(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_TRAY_ICON, EggTrayIcon))
+#define EGG_TRAY_ICON_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_TRAY_ICON, EggTrayIconClass))
+#define EGG_IS_TRAY_ICON(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_TRAY_ICON))
+#define EGG_IS_TRAY_ICON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_TRAY_ICON))
+#define EGG_TRAY_ICON_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_TRAY_ICON, EggTrayIconClass))
+
+typedef struct _EggTrayIcon EggTrayIcon;
+typedef struct _EggTrayIconClass EggTrayIconClass;
+
+struct _EggTrayIcon
+{
+ GtkPlug parent_instance;
+
+ guint stamp;
+
+#ifdef GDK_WINDOWING_X11
+ Atom selection_atom;
+ Atom manager_atom;
+ Atom system_tray_opcode_atom;
+ Atom orientation_atom;
+ Window manager_window;
+#endif
+ GtkOrientation orientation;
+};
+
+struct _EggTrayIconClass
+{
+ GtkPlugClass parent_class;
+};
+
+GType egg_tray_icon_get_type (void);
+
+EggTrayIcon *egg_tray_icon_new_for_screen (GdkScreen *screen,
+ const gchar *name);
+
+EggTrayIcon *egg_tray_icon_new (const gchar *name);
+
+guint egg_tray_icon_send_message (EggTrayIcon *icon,
+ gint timeout,
+ const char *message,
+ gint len);
+void egg_tray_icon_cancel_message (EggTrayIcon *icon,
+ guint id);
+
+GtkOrientation egg_tray_icon_get_orientation (EggTrayIcon *icon);
+
+G_END_DECLS
+
+#endif /* __EGG_TRAY_ICON_H__ */
diff --git a/src/padevchooser.c b/src/padevchooser.c
new file mode 100644
index 0000000..09f7c97
--- /dev/null
+++ b/src/padevchooser.c
@@ -0,0 +1,758 @@
+#include <string.h>
+
+#include <gtk/gtk.h>
+#include <glade/glade.h>
+#include <gconf/gconf-client.h>
+#include <libnotify/notify.h>
+
+#include <polyp/polypaudio.h>
+#include <polyp/browser.h>
+#include <polyp/glib-mainloop.h>
+
+#include "eggtrayicon.h"
+#include "x11prop.h"
+
+#define GCONF_PREFIX "/apps/padevchooser"
+
+struct menu_item_info {
+ GtkWidget *menu_item;
+ char *name, *server, *device, *description;
+ pa_sample_spec sample_spec;
+ int sample_spec_valid;
+};
+
+static NotifyNotification *notification = NULL;
+static gchar *last_events = NULL;
+
+static EggTrayIcon *tray_icon = NULL;
+static gchar *current_server = NULL, *current_sink = NULL, *current_source = NULL;
+static struct menu_item_info *current_source_menu_item_info = NULL, *current_sink_menu_item_info = NULL, *current_server_menu_item_info = NULL;
+static GtkMenu *menu = NULL, *sink_submenu = NULL, *source_submenu = NULL, *server_submenu = NULL;
+static GHashTable *server_hash_table = NULL, *sink_hash_table = NULL, *source_hash_table = NULL;
+static GtkWidget *no_servers_menu_item = NULL, *no_sinks_menu_item = NULL, *no_sources_menu_item = NULL;
+static GtkWidget *default_server_menu_item = NULL, *default_sink_menu_item = NULL, *default_source_menu_item = NULL;
+static GtkWidget *other_server_menu_item = NULL, *other_sink_menu_item = NULL, *other_source_menu_item = NULL;
+static GtkTooltips *menu_tooltips = NULL;
+static int updating = 0;
+static time_t startup_time = 0;
+static GConfClient *gconf = NULL;
+static GladeXML *glade_xml = NULL;
+static gboolean notify_on_server_discovery = FALSE, notify_on_sink_discovery = FALSE, notify_on_source_discovery = FALSE, no_notify_on_startup = FALSE;
+
+static void set_sink(const char *server, const char *device);
+static void set_source(const char *server, const char *device);
+static void set_server(const char *server);
+
+static void set_x11_props(void);
+
+static gboolean find_predicate(const gchar* name, const struct menu_item_info *m, gpointer userdata) {
+
+ return
+ strcmp(m->server, current_server) == 0 &&
+ (!m->device || strcmp(m->device, userdata) == 0);
+}
+
+static void look_for_current_menu_item(
+ GHashTable *h,
+ const char *device,
+ int look_for_device,
+ struct menu_item_info **current_menu_item_info,
+ GtkWidget *default_menu_item,
+ GtkWidget *other_menu_item) {
+
+ struct menu_item_info *m;
+
+ if (!current_server || (look_for_device && !device))
+ m = NULL;
+ else if (*current_menu_item_info &&
+ (strcmp(current_server, (*current_menu_item_info)->server) == 0 &&
+ (!look_for_device || strcmp(device, (*current_menu_item_info)->device) == 0)))
+ m = *current_menu_item_info;
+ else
+ /* Look for the right entry */
+ m = g_hash_table_find(h, (GHRFunc) find_predicate, (gpointer) device);
+
+ /* Deactivate the old item */
+ if (*current_menu_item_info)
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM((*current_menu_item_info)->menu_item), FALSE);
+
+ /* Update item */
+ *current_menu_item_info = m;
+
+ /* Activate the new item */
+ if (*current_menu_item_info)
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM((*current_menu_item_info)->menu_item), TRUE);
+
+ /* Enable/Disable the "Default" menu item */
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(default_menu_item), !*current_menu_item_info && (look_for_device ? !device : !current_server));
+
+ /* Enable/Disable the "Other..." menu item and set the tooltip appriately */
+ if (!*current_menu_item_info && (look_for_device ? device : current_server)) {
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(other_menu_item), TRUE);
+ gtk_tooltips_set_tip(GTK_TOOLTIPS(menu_tooltips), other_menu_item, look_for_device ? device : current_server, NULL);
+ } else {
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(other_menu_item), FALSE);
+ gtk_tooltips_set_tip(GTK_TOOLTIPS(menu_tooltips), other_menu_item, NULL, NULL);
+ }
+}
+
+static void look_for_current_menu_items(void) {
+ updating = 1;
+ look_for_current_menu_item(server_hash_table, NULL, FALSE, &current_server_menu_item_info, default_server_menu_item, other_server_menu_item);
+ look_for_current_menu_item(sink_hash_table, current_sink, TRUE, &current_sink_menu_item_info, default_sink_menu_item, other_sink_menu_item);
+ look_for_current_menu_item(source_hash_table, current_source, TRUE, &current_source_menu_item_info, default_source_menu_item, other_source_menu_item);
+ updating = 0;
+}
+
+static void menu_item_info_free(struct menu_item_info *i) {
+ if (i->menu_item)
+ gtk_widget_destroy(i->menu_item);
+
+ g_free(i->name);
+ g_free(i->server);
+ g_free(i->device);
+ g_free(i->description);
+ g_free(i);
+}
+
+static void notification_closed(void) {
+ if (notification) {
+ g_object_unref(G_OBJECT(notification));
+ notification = NULL;
+ }
+}
+
+static void notify_event(const char *title, const char*text) {
+ char *s;
+
+ if (no_notify_on_startup && time(NULL)-startup_time <= 5)
+ return;
+
+ if (!notify_is_initted())
+ return;
+
+ if (!notification) {
+ s = g_strdup_printf("<i>%s</i>\n%s", title, text);
+ notification = notify_notification_new(title, s, "audio-card", GTK_WIDGET(tray_icon));
+ notify_notification_set_category(notification, "device.added");
+ notify_notification_set_urgency(notification, NOTIFY_URGENCY_LOW);
+ g_signal_connect_swapped(G_OBJECT(notification), "closed", G_CALLBACK(notification_closed), NULL);
+ } else {
+ s = g_strdup_printf("%s\n\n<i>%s</i>\n%s", last_events, title, text);
+ notify_notification_update(notification, title, s, "audio-card");
+ }
+
+ g_free(last_events);
+ last_events = s;
+
+ notify_notification_show(notification, NULL);
+}
+
+static GtkWidget *append_radio_menu_item(GtkMenu *menu, const gchar *label, gboolean mnemonic, gboolean prepend) {
+ GtkWidget *item;
+
+ if (mnemonic)
+ item = gtk_check_menu_item_new_with_mnemonic(label);
+ else
+ item = gtk_check_menu_item_new_with_label(label);
+
+ gtk_check_menu_item_set_draw_as_radio(GTK_CHECK_MENU_ITEM(item), TRUE);
+ gtk_widget_show_all(item);
+
+ if (prepend)
+ gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), item);
+ else
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+
+ return item;
+}
+
+static void sink_change_cb(struct menu_item_info *m) {
+ set_sink(m->server, m->device);
+}
+
+static void source_change_cb(struct menu_item_info *m) {
+ set_source(m->server, m->device);
+}
+
+static void server_change_cb(struct menu_item_info *m) {
+ set_server(m->server);
+}
+
+static struct menu_item_info* add_menu_item_info(GHashTable *h, GtkMenu *menu, const pa_browse_info *i, GCallback callback) {
+ struct menu_item_info *m;
+ gchar *c;
+ const gchar *title;
+ gboolean b;
+
+ m = g_new(struct menu_item_info, 1);
+
+ m->name = g_strdup(i->name);
+ m->server = g_strdup(i->server);
+ m->device = g_strdup(i->device);
+ m->description = g_strdup(i->description);
+ if ((m->sample_spec_valid = !!i->sample_spec))
+ m->sample_spec = *i->sample_spec;
+
+ m->menu_item = append_radio_menu_item(menu, m->name, FALSE, TRUE);
+ g_signal_connect_swapped(G_OBJECT(m->menu_item), "activate", callback, m);
+
+ if (!m->device)
+ c = g_strdup_printf(
+ "Name: %s\n"
+ "Server: %s",
+ i->name,
+ i->server);
+ else {
+ char t[PA_SAMPLE_SPEC_SNPRINT_MAX];
+ c = g_strdup_printf(
+ "Name: %s\n"
+ "Server: %s\n"
+ "Device: %s\n"
+ "Description: %s\n"
+ "Sample Specification: %s",
+ i->name,
+ i->server,
+ i->device,
+ i->description ? i->description : "n/a",
+ m->sample_spec_valid ? pa_sample_spec_snprint(t, sizeof(t), &m->sample_spec) : "n/a");
+ }
+
+ gtk_tooltips_set_tip(GTK_TOOLTIPS(menu_tooltips), m->menu_item, c, NULL);
+
+ if (menu == sink_submenu) {
+ title = "Networked Audio Sink Discovered";
+ b = notify_on_sink_discovery;
+ } else if (menu == source_submenu) {
+ title = "Networked Audio Source Discovered";
+ b = notify_on_source_discovery;
+ } else {
+ title = "Networked Audio Server Discovered";
+ b = notify_on_server_discovery;
+ }
+
+ if (b)
+ notify_event(title, c);
+
+ g_free(c);
+ g_hash_table_insert(h, m->name, m);
+
+ return m;
+}
+
+static void remove_menu_item_info(GHashTable *h, const pa_browse_info *i) {
+ struct menu_item_info *m;
+ const gchar *title;
+ gchar *c;
+ gboolean b;
+
+ if (!(m = g_hash_table_lookup(h, i->name)))
+ return;
+
+ if (h == sink_hash_table) {
+ title = "Networked Audio Sink Disappeared";
+ b = notify_on_sink_discovery;
+ } else if (h == source_hash_table) {
+ title = "Networked Audio Source Disappeared";
+ b = notify_on_source_discovery;
+ } else {
+ title = "Networked Audio Server Disappeared";
+ b = notify_on_server_discovery;
+ }
+
+ c = g_strdup_printf("Name: %s", i->name);
+
+ if (b)
+ notify_event(title, c);
+ g_free(c);
+
+ g_hash_table_remove(h, i->name);
+}
+
+static void update_no_devices_menu_items(void) {
+ if (g_hash_table_size(server_hash_table) == 0)
+ gtk_widget_show_all(no_servers_menu_item);
+ else
+ gtk_widget_hide(no_servers_menu_item);
+
+ if (g_hash_table_size(source_hash_table) == 0)
+ gtk_widget_show_all(no_sources_menu_item);
+ else
+ gtk_widget_hide(no_sources_menu_item);
+
+ if (g_hash_table_size(sink_hash_table) == 0)
+ gtk_widget_show_all(no_sinks_menu_item);
+ else
+ gtk_widget_hide(no_sinks_menu_item);
+}
+
+static void browse_cb(pa_browser *z, pa_browse_opcode_t c, const pa_browse_info *i, void *userdata) {
+ switch (c) {
+ case PA_BROWSE_NEW_SERVER:
+ add_menu_item_info(server_hash_table, server_submenu, i, (GCallback) server_change_cb);
+ break;
+
+ case PA_BROWSE_NEW_SINK:
+ add_menu_item_info(sink_hash_table, sink_submenu, i, (GCallback) sink_change_cb);
+ break;
+
+ case PA_BROWSE_NEW_SOURCE:
+ add_menu_item_info(source_hash_table, source_submenu, i, (GCallback) source_change_cb);
+ break;
+
+ case PA_BROWSE_REMOVE_SERVER:
+ remove_menu_item_info(server_hash_table, i);
+ break;
+
+ case PA_BROWSE_REMOVE_SINK:
+ remove_menu_item_info(sink_hash_table, i);
+ break;
+
+ case PA_BROWSE_REMOVE_SOURCE:
+ remove_menu_item_info(source_hash_table, i);
+ break;
+ }
+
+ update_no_devices_menu_items();
+ look_for_current_menu_items();
+}
+
+static void tray_icon_on_click(GtkWidget *widget, GdkEventButton* event) {
+ if (event->type == GDK_BUTTON_PRESS)
+ gtk_menu_popup(menu, NULL, NULL, NULL, NULL, event->button, event->time);
+}
+
+static void start_manager_cb(void) {
+ g_spawn_command_line_async("paman", NULL);
+}
+
+static void start_vucontrol_cb(void) {
+ g_spawn_command_line_async("pavucontrol", NULL);
+}
+
+static void start_vumeter_playback_cb(void) {
+ g_spawn_command_line_async("pavumeter --sink", NULL);
+}
+
+static void start_vumeter_record_cb(void) {
+ g_spawn_command_line_async("pavumeter --source", NULL);
+}
+
+static void show_preferences(void) {
+ GtkWidget *w;
+
+ w = glade_xml_get_widget(glade_xml, "preferencesDialog");
+ gtk_widget_show_all(w);
+ gtk_window_present(GTK_WINDOW(w));
+ gtk_dialog_run(GTK_DIALOG(w));
+ gtk_widget_hide(w);
+}
+
+static void set_props(const char *server, const char *sink, const char *source) {
+ if (server != current_server) {
+ g_free(current_server);
+ current_server = g_strdup(server);
+ }
+
+ if (sink != current_sink) {
+ g_free(current_sink);
+ current_sink = g_strdup(sink);
+ }
+
+ if (source != current_source) {
+ g_free(current_source);
+ current_source = g_strdup(source);
+ }
+
+ set_x11_props();
+}
+
+static gboolean pstrequal(const char *a, const char *b) {
+ if (!a && !b)
+ return TRUE;
+
+ if (!a || !b)
+ return FALSE;
+
+ return strcmp(a, b) == 0;
+}
+
+static void set_sink(const char *server, const char *sink) {
+ if (updating)
+ return;
+
+ if (server) {
+ if (!pstrequal(server, current_server) || !pstrequal(sink, current_sink))
+ set_props(server, sink, pstrequal(server, current_server) ? current_source : NULL);
+ } else {
+ if (!pstrequal(sink, current_sink))
+ set_props(current_server, sink, current_source);
+ }
+
+ look_for_current_menu_items();
+}
+
+static void set_source(const char *server, const char *source) {
+ if (updating)
+ return;
+
+ if (server) {
+ if (!pstrequal(server, current_server) || !pstrequal(source, current_source))
+ set_props(server, pstrequal(server, current_server) ? current_sink : NULL, source);
+ } else {
+ if (!pstrequal(source, current_source))
+ set_props(current_server, current_sink, source);
+ }
+
+ look_for_current_menu_items();
+}
+
+static void set_server(const char *server) {
+ if (updating)
+ return;
+
+ if (!pstrequal(server, current_server))
+ set_props(server, NULL, NULL);
+
+ look_for_current_menu_items();
+}
+
+static void sink_default_cb(void) {
+ set_sink(NULL, NULL);
+}
+
+static void source_default_cb(void) {
+ set_source(NULL, NULL);
+}
+
+static void server_default_cb(void) {
+ set_server(NULL);
+}
+
+static const gchar *input_dialog(const gchar *title, const gchar *text, const gchar *value) {
+ GtkWidget *w, *entry, *label;
+ gint response;
+
+ w = glade_xml_get_widget(glade_xml, "inputDialog");
+
+ if (GTK_WIDGET_VISIBLE(w)) {
+ gtk_window_present(GTK_WINDOW(w));
+ return value;
+ }
+
+ gtk_window_set_title(GTK_WINDOW(w), title);
+
+ entry = glade_xml_get_widget(glade_xml, "inputEntry");
+ gtk_entry_set_text(GTK_ENTRY(entry), value ? value : "");
+
+ label = glade_xml_get_widget(glade_xml, "inputLabel");
+ gtk_label_set_markup(GTK_LABEL(label), text);
+
+ gtk_widget_show_all(w);
+ gtk_window_present(GTK_WINDOW(w));
+ response = gtk_dialog_run(GTK_DIALOG(w));
+ gtk_widget_hide(w);
+
+ if (response != GTK_RESPONSE_OK)
+ return value;
+
+ return gtk_entry_get_text(GTK_ENTRY(entry));
+}
+
+static void sink_other_cb(void) {
+
+ if (updating)
+ return;
+
+ set_sink(NULL, input_dialog("Other Sink", "Please enter sink name:", current_sink));
+}
+
+static void source_other_cb(void) {
+
+ if (updating)
+ return;
+
+ set_source(NULL, input_dialog("Other Source", "Please enter source name:", current_source));
+}
+
+static void server_other_cb(void) {
+ if (updating)
+ return;
+
+ set_server(input_dialog("Other Server", "Please enter server name:", current_server));
+}
+
+static EggTrayIcon *create_tray_icon(void) {
+ GtkTooltips *tips;
+ EggTrayIcon *tray_icon;
+ GtkWidget *event_box, *icon;
+
+ tray_icon = egg_tray_icon_new("Polypaudio Device Chooser");
+
+ event_box = gtk_event_box_new();
+ g_signal_connect_swapped(G_OBJECT(event_box), "button-press-event", G_CALLBACK(tray_icon_on_click), NULL);
+
+ gtk_container_add(GTK_CONTAINER(tray_icon), event_box);
+ icon = gtk_image_new_from_icon_name("audio-card", GTK_ICON_SIZE_SMALL_TOOLBAR);
+ gtk_container_add(GTK_CONTAINER(event_box), icon);
+
+ gtk_widget_show_all(GTK_WIDGET(tray_icon));
+
+ tips = gtk_tooltips_new();
+ gtk_tooltips_set_tip(GTK_TOOLTIPS(tips), event_box, "Polypaudio Applet", "I don't know what this is.");
+
+ return tray_icon;
+}
+
+static GtkWidget *append_menuitem(GtkMenu *m, const char *text, const char *icon_name) {
+ GtkWidget *item;
+
+ item = gtk_image_menu_item_new_with_mnemonic(text);
+ gtk_menu_shell_append(GTK_MENU_SHELL(m), item);
+
+ if (icon_name) {
+ GtkWidget *i;
+ i = gtk_image_new_from_icon_name(icon_name, GTK_ICON_SIZE_MENU);
+ gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), i);
+ }
+
+ return item;
+}
+
+static GtkWidget* append_submenu(GtkMenu *m, const char *text, GtkMenu *sub, const char *icon_name) {
+ GtkWidget *item;
+
+ item = append_menuitem(m, text, icon_name);
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), GTK_WIDGET(sub));
+ return item;
+}
+
+static void append_default_device_menu_items(GtkMenu *m, GtkWidget **empty_menu_item, GtkWidget **default_menu_item, GtkWidget**other_menu_item, GCallback default_callback, GCallback other_callback) {
+
+ *empty_menu_item = append_menuitem(m, "No Network Devices Found", NULL);
+ gtk_widget_set_sensitive(*empty_menu_item, FALSE);
+
+ gtk_menu_shell_append(GTK_MENU_SHELL(m), gtk_separator_menu_item_new());
+
+ *default_menu_item = append_radio_menu_item(m, "_Default", TRUE, FALSE);
+ g_signal_connect_swapped(G_OBJECT(*default_menu_item), "activate", default_callback, NULL);
+ *other_menu_item = append_radio_menu_item(m, "_Other...", TRUE, FALSE);
+ g_signal_connect_swapped(G_OBJECT(*other_menu_item), "activate", other_callback, NULL);
+}
+
+static GtkMenu *create_menu(void) {
+ GtkWidget *item;
+ gchar *c;
+
+ menu = GTK_MENU(gtk_menu_new());
+ menu_tooltips = gtk_tooltips_new();
+
+ sink_submenu = GTK_MENU(gtk_menu_new());
+ source_submenu = GTK_MENU(gtk_menu_new());
+ server_submenu = GTK_MENU(gtk_menu_new());
+
+ append_default_device_menu_items(sink_submenu, &no_sinks_menu_item, &default_sink_menu_item, &other_sink_menu_item, sink_default_cb, sink_other_cb);
+ append_default_device_menu_items(source_submenu, &no_sources_menu_item, &default_source_menu_item, &other_source_menu_item, source_default_cb, source_other_cb);
+ append_default_device_menu_items(server_submenu, &no_servers_menu_item, &default_server_menu_item, &other_server_menu_item, server_default_cb, server_other_cb);
+
+ append_submenu(menu, "Default S_erver", server_submenu, "network-wired");
+ append_submenu(menu, "Default S_ink", sink_submenu, "audio-card");
+ append_submenu(menu, "Default S_ource", source_submenu, "audio-input-microphone");
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
+
+ item = append_menuitem(menu, "_Manager...", NULL);
+ gtk_widget_set_sensitive(item, !!(c = g_find_program_in_path("paman")));
+ g_free(c);
+ g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(start_manager_cb), NULL);
+
+ item = append_menuitem(menu, "_Volume Control...", "multimedia-volume-control");
+ gtk_widget_set_sensitive(item, !!(c = g_find_program_in_path("pavucontrol")));
+ g_free(c);
+ g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(start_vucontrol_cb), NULL);
+
+ item = append_menuitem(menu, "_Volume Meter (Playback)...", NULL);
+ gtk_widget_set_sensitive(item, !!(c = g_find_program_in_path("pavumeter")));
+ g_free(c);
+ g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(start_vumeter_playback_cb), NULL);
+
+ item = append_menuitem(menu, "_Volume Meter (Recording)...", NULL);
+ gtk_widget_set_sensitive(item, !!c);
+ g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(start_vumeter_record_cb), NULL);
+
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
+ item = append_menuitem(menu, "_Preferences...", "gtk-preferences");
+ g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(show_preferences), NULL);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
+
+ item = gtk_image_menu_item_new_from_stock(GTK_STOCK_QUIT, NULL);
+ g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(gtk_main_quit), NULL);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+
+ gtk_widget_show_all(GTK_WIDGET(menu));
+
+ return menu;
+}
+
+static void get_x11_props(void) {
+ char t[256];
+
+ g_free(current_server);
+ g_free(current_sink);
+ g_free(current_source);
+
+ current_server = g_strdup(x11_get_prop(GDK_DISPLAY(), "POLYP_SERVER", t, sizeof(t)));
+ current_sink = g_strdup(x11_get_prop(GDK_DISPLAY(), "POLYP_SINK", t, sizeof(t)));
+ current_source = g_strdup(x11_get_prop(GDK_DISPLAY(), "POLYP_SOURCE", t, sizeof(t)));
+}
+
+static void set_x11_props(void) {
+
+ if (current_server)
+ x11_set_prop(GDK_DISPLAY(), "POLYP_SERVER", current_server);
+ else
+ x11_del_prop(GDK_DISPLAY(), "POLYP_SERVER");
+
+ if (current_sink)
+ x11_set_prop(GDK_DISPLAY(), "POLYP_SINK", current_sink);
+ else
+ x11_del_prop(GDK_DISPLAY(), "POLYP_SINK");
+
+ if (current_source)
+ x11_set_prop(GDK_DISPLAY(), "POLYP_SOURCE", current_source);
+ else
+ x11_del_prop(GDK_DISPLAY(), "POLYP_SOURCE");
+
+ /* This is used by module-x11-publish to detect whether the
+ * properties have been altered. We delete this property here to
+ * make sure that the module notices that it is no longer in
+ * control */
+ x11_del_prop(GDK_DISPLAY(), "POLYP_ID");
+}
+
+static void check_button_cb(GtkCheckButton *w, const gchar *key) {
+ gboolean b, *ptr;
+
+ ptr = g_object_get_data(G_OBJECT(w), "ptr");
+ b = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w));
+
+ if (*ptr == b)
+ return;
+
+ *ptr = b;
+ gconf_client_set_bool(gconf, key, b, NULL);
+
+ gtk_widget_set_sensitive(glade_xml_get_widget(glade_xml, "startupCheckButton"), notify_on_server_discovery||notify_on_sink_discovery||notify_on_source_discovery);
+}
+
+static void gconf_notify_cb(GConfClient *client, guint cnxn_id, GConfEntry *entry, gpointer userdata) {
+ gboolean b;
+
+ b = gconf_value_get_bool(gconf_entry_get_value(entry));
+
+ if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(userdata)) != b)
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(userdata), b);
+
+ gtk_widget_set_sensitive(glade_xml_get_widget(glade_xml, "startupCheckButton"), notify_on_server_discovery||notify_on_sink_discovery||notify_on_source_discovery);
+}
+
+static void setup_gconf(void) {
+ GtkWidget *server_check_button, *sink_check_button, *source_check_button, *startup_check_button;
+
+ gconf = gconf_client_get_default();
+ g_assert(gconf);
+
+ gconf_client_add_dir(gconf, GCONF_PREFIX, GCONF_CLIENT_PRELOAD_NONE, NULL);
+
+ server_check_button = glade_xml_get_widget(glade_xml, "serverCheckButton");
+ sink_check_button = glade_xml_get_widget(glade_xml, "sinkCheckButton");
+ source_check_button = glade_xml_get_widget(glade_xml, "sourceCheckButton");
+ startup_check_button = glade_xml_get_widget(glade_xml, "startupCheckButton");
+
+ g_object_set_data(G_OBJECT(server_check_button), "ptr", &notify_on_server_discovery);
+ g_object_set_data(G_OBJECT(sink_check_button), "ptr", &notify_on_sink_discovery);
+ g_object_set_data(G_OBJECT(source_check_button), "ptr", &notify_on_source_discovery);
+ g_object_set_data(G_OBJECT(startup_check_button), "ptr", &no_notify_on_startup);
+
+ notify_on_server_discovery = gconf_client_get_bool(gconf, GCONF_PREFIX"/notify_on_server_discovery", NULL);
+ notify_on_sink_discovery = gconf_client_get_bool(gconf, GCONF_PREFIX"/notify_on_sink_discovery", NULL);
+ notify_on_source_discovery = gconf_client_get_bool(gconf, GCONF_PREFIX"/notify_on_source_discovery", NULL);
+ no_notify_on_startup = gconf_client_get_bool(gconf, GCONF_PREFIX"/no_notify_on_startup", NULL);
+
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(server_check_button), notify_on_server_discovery);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(sink_check_button), notify_on_sink_discovery);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(source_check_button), notify_on_source_discovery);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(startup_check_button), no_notify_on_startup);
+
+ gtk_widget_set_sensitive(startup_check_button, notify_on_server_discovery||notify_on_sink_discovery||notify_on_source_discovery);
+
+ gconf_client_notify_add(gconf, GCONF_PREFIX"/notify_on_server_discovery", gconf_notify_cb, server_check_button, NULL, NULL);
+ gconf_client_notify_add(gconf, GCONF_PREFIX"/notify_on_sink_discovery", gconf_notify_cb, sink_check_button, NULL, NULL);
+ gconf_client_notify_add(gconf, GCONF_PREFIX"/notify_on_source_discovery", gconf_notify_cb, source_check_button, NULL, NULL);
+ gconf_client_notify_add(gconf, GCONF_PREFIX"/no_notify_on_startup", gconf_notify_cb, startup_check_button, NULL, NULL);
+
+ g_signal_connect(G_OBJECT(server_check_button), "toggled", G_CALLBACK(check_button_cb), GCONF_PREFIX"/notify_on_server_discovery");
+ g_signal_connect(G_OBJECT(sink_check_button), "toggled", G_CALLBACK(check_button_cb), GCONF_PREFIX"/notify_on_sink_discovery");
+ g_signal_connect(G_OBJECT(source_check_button), "toggled", G_CALLBACK(check_button_cb), GCONF_PREFIX"/notify_on_source_discovery");
+ g_signal_connect(G_OBJECT(startup_check_button), "toggled", G_CALLBACK(check_button_cb), GCONF_PREFIX"/no_notify_on_startup");
+}
+
+int main(int argc, char *argv[]) {
+ pa_browser *b = NULL;
+ pa_glib_mainloop *m = NULL;
+
+ startup_time = time(NULL);
+
+ gtk_init(&argc, &argv);
+
+
+ glade_xml = glade_xml_new("padevchooser.glade", NULL, NULL);
+ g_assert(glade_xml);
+
+ m = pa_glib_mainloop_new(NULL);
+ g_assert(m);
+
+ server_hash_table = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify) menu_item_info_free);
+ sink_hash_table = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify) menu_item_info_free);
+ source_hash_table = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify) menu_item_info_free);
+
+ create_menu();
+ update_no_devices_menu_items();
+
+ setup_gconf();
+
+ notify_init("Polypaudio Applet");
+
+ get_x11_props();
+
+ if (!(b = pa_browser_new(pa_glib_mainloop_get_api(m)))) {
+ g_warning("pa_browser_new() failed.");
+ goto fail;
+ }
+
+ pa_browser_set_callback(b, browse_cb, NULL);
+
+ tray_icon = create_tray_icon();
+
+ gtk_main();
+
+fail:
+ if (b)
+ pa_browser_unref(b);
+
+ if (m)
+ pa_glib_mainloop_free(m);
+
+ if (server_hash_table)
+ g_hash_table_destroy(server_hash_table);
+ if (sink_hash_table)
+ g_hash_table_destroy(sink_hash_table);
+ if (source_hash_table)
+ g_hash_table_destroy(source_hash_table);
+
+ if (notification)
+ g_object_unref(G_OBJECT(notification));
+
+ g_free(last_events);
+
+ return 0;
+}
diff --git a/src/padevchooser.glade b/src/padevchooser.glade
new file mode 100644
index 0000000..3ce2f35
--- /dev/null
+++ b/src/padevchooser.glade
@@ -0,0 +1,321 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
+
+<glade-interface>
+
+<widget class="GtkDialog" id="preferencesDialog">
+ <property name="border_width">6</property>
+ <property name="title" translatable="yes">Polypaudio Applet Preferences</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_NONE</property>
+ <property name="modal">False</property>
+ <property name="resizable">False</property>
+ <property name="destroy_with_parent">False</property>
+ <property name="icon_name">gtk-preferences</property>
+ <property name="decorated">True</property>
+ <property name="skip_taskbar_hint">False</property>
+ <property name="skip_pager_hint">False</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+ <property name="focus_on_map">True</property>
+ <property name="urgency_hint">False</property>
+ <property name="has_separator">False</property>
+
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+
+ <child internal-child="action_area">
+ <widget class="GtkHButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+
+ <child>
+ <widget class="GtkButton" id="closebutton1">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-close</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="response_id">-7</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkFrame" id="frame1">
+ <property name="border_width">6</property>
+ <property name="visible">True</property>
+ <property name="label_xalign">0</property>
+ <property name="label_yalign">0.5</property>
+ <property name="shadow_type">GTK_SHADOW_NONE</property>
+
+ <child>
+ <widget class="GtkAlignment" id="alignment1">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">1</property>
+ <property name="yscale">1</property>
+ <property name="top_padding">6</property>
+ <property name="bottom_padding">0</property>
+ <property name="left_padding">12</property>
+ <property name="right_padding">0</property>
+
+ <child>
+ <widget class="GtkVBox" id="vbox2">
+ <property name="visible">True</property>
+ <property name="homogeneous">True</property>
+ <property name="spacing">6</property>
+
+ <child>
+ <widget class="GtkCheckButton" id="serverCheckButton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Show notifications for discovered servers</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkCheckButton" id="sinkCheckButton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Show notifications for discovered sinks</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkCheckButton" id="sourceCheckButton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Show notifications for discovered sources</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkCheckButton" id="startupCheckButton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Don't show notifications on startup</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&lt;b&gt;Notifications&lt;/b&gt;</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="type">label_item</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+</widget>
+
+<widget class="GtkDialog" id="inputDialog">
+ <property name="border_width">6</property>
+ <property name="title" translatable="yes">Other Server</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_NONE</property>
+ <property name="modal">False</property>
+ <property name="default_width">300</property>
+ <property name="resizable">True</property>
+ <property name="destroy_with_parent">False</property>
+ <property name="icon_name">gtk-edit</property>
+ <property name="decorated">True</property>
+ <property name="skip_taskbar_hint">False</property>
+ <property name="skip_pager_hint">False</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+ <property name="focus_on_map">True</property>
+ <property name="urgency_hint">False</property>
+ <property name="has_separator">False</property>
+
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="dialog-vbox2">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+
+ <child internal-child="action_area">
+ <widget class="GtkHButtonBox" id="dialog-action_area2">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+
+ <child>
+ <widget class="GtkButton" id="okbutton1">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-ok</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="response_id">-5</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkVBox" id="vbox3">
+ <property name="border_width">6</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+
+ <child>
+ <widget class="GtkLabel" id="inputLabel">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Please enter server name:</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkAlignment" id="alignment2">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">1</property>
+ <property name="yscale">1</property>
+ <property name="top_padding">0</property>
+ <property name="bottom_padding">0</property>
+ <property name="left_padding">12</property>
+ <property name="right_padding">0</property>
+
+ <child>
+ <widget class="GtkEntry" id="inputEntry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes"></property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">*</property>
+ <property name="activates_default">False</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+</widget>
+
+</glade-interface>
diff --git a/src/padevchooser.gladep b/src/padevchooser.gladep
new file mode 100644
index 0000000..35f1d2c
--- /dev/null
+++ b/src/padevchooser.gladep
@@ -0,0 +1,8 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-project SYSTEM "http://glade.gnome.org/glade-project-2.0.dtd">
+
+<glade-project>
+ <name>paapplet</name>
+ <program_name>paapplet</program_name>
+ <gnome_support>FALSE</gnome_support>
+</glade-project>
diff --git a/src/x11prop.c b/src/x11prop.c
new file mode 100644
index 0000000..e8a6dc9
--- /dev/null
+++ b/src/x11prop.c
@@ -0,0 +1,70 @@
+/* $Id$ */
+
+/***
+ This file is part of polypaudio.
+
+ polypaudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ polypaudio 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 Lesser General Public License
+ along with polypaudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+
+#include "x11prop.h"
+
+
+void x11_set_prop(Display *d, const char *name, const char *data) {
+ Atom a = XInternAtom(d, name, False);
+ XChangeProperty(d, RootWindow(d, 0), a, XA_STRING, 8, PropModeReplace, (const unsigned char*) data, strlen(data)+1);
+}
+
+void x11_del_prop(Display *d, const char *name) {
+ Atom a = XInternAtom(d, name, False);
+ XDeleteProperty(d, RootWindow(d, 0), a);
+}
+
+char* x11_get_prop(Display *d, const char *name, char *p, size_t l) {
+ Atom actual_type;
+ int actual_format;
+ unsigned long nitems;
+ unsigned long nbytes_after;
+ unsigned char *prop = NULL;
+ char *ret = NULL;
+
+ Atom a = XInternAtom(d, name, False);
+ if (XGetWindowProperty(d, RootWindow(d, 0), a, 0, (l+2)/4, False, XA_STRING, &actual_type, &actual_format, &nitems, &nbytes_after, &prop) != Success)
+ goto finish;
+
+ if (actual_type != XA_STRING)
+ goto finish;
+
+ memcpy(p, prop, nitems);
+ p[nitems] = 0;
+
+ ret = p;
+
+finish:
+
+ if (prop)
+ XFree(prop);
+
+ return ret;
+}
diff --git a/src/x11prop.h b/src/x11prop.h
new file mode 100644
index 0000000..c908324
--- /dev/null
+++ b/src/x11prop.h
@@ -0,0 +1,33 @@
+#ifndef foox11prophfoo
+#define foox11prophfoo
+
+/* $Id$ */
+
+/***
+ This file is part of polypaudio.
+
+ polypaudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2 of the License,
+ or (at your option) any later version.
+
+ polypaudio 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 Lesser General Public License
+ along with polypaudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include <sys/types.h>
+
+#include <X11/Xlib.h>
+
+void x11_set_prop(Display *d, const char *name, const char *data);
+void x11_del_prop(Display *d, const char *name);
+char* x11_get_prop(Display *d, const char *name, char *p, size_t l);
+
+#endif