From f090e48f9f76b3159cf62c8fe9a162f145c4fce1 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 2 Jun 2006 20:25:06 +0000 Subject: initial checkin git-svn-id: file:///home/lennart/svn/public/padevchooser/trunk@3 e4aeda27-4315-0410-ac56-b21855d76123 --- src/Makefile | 10 + src/config.h | 1 + src/eggtrayicon.c | 513 ++++++++++++++++++++++++++++++++ src/eggtrayicon.h | 80 +++++ src/padevchooser.c | 758 ++++++++++++++++++++++++++++++++++++++++++++++++ src/padevchooser.glade | 321 ++++++++++++++++++++ src/padevchooser.gladep | 8 + src/x11prop.c | 70 +++++ src/x11prop.h | 33 +++ 9 files changed, 1794 insertions(+) create mode 100644 src/Makefile create mode 100644 src/config.h create mode 100644 src/eggtrayicon.c create mode 100644 src/eggtrayicon.h create mode 100644 src/padevchooser.c create mode 100644 src/padevchooser.glade create mode 100644 src/padevchooser.gladep create mode 100644 src/x11prop.c create mode 100644 src/x11prop.h 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 + * + * 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 +#include +#include + +#include "eggtrayicon.h" + +#include +#if defined (GDK_WINDOWING_X11) +#include +#include +#elif defined (GDK_WINDOWING_WIN32) +#include +#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 + * + * 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 +#ifdef GDK_WINDOWING_X11 +#include +#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 + +#include +#include +#include +#include + +#include +#include +#include + +#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, ¤t_server_menu_item_info, default_server_menu_item, other_server_menu_item); + look_for_current_menu_item(sink_hash_table, current_sink, TRUE, ¤t_sink_menu_item_info, default_sink_menu_item, other_sink_menu_item); + look_for_current_menu_item(source_hash_table, current_source, TRUE, ¤t_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("%s\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%s\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", ¬ify_on_server_discovery); + g_object_set_data(G_OBJECT(sink_check_button), "ptr", ¬ify_on_sink_discovery); + g_object_set_data(G_OBJECT(source_check_button), "ptr", ¬ify_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 @@ + + + + + + + 6 + Polypaudio Applet Preferences + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_NONE + False + False + False + gtk-preferences + True + False + False + GDK_WINDOW_TYPE_HINT_DIALOG + GDK_GRAVITY_NORTH_WEST + True + False + False + + + + True + False + 0 + + + + True + GTK_BUTTONBOX_END + + + + True + True + True + gtk-close + True + GTK_RELIEF_NORMAL + True + -7 + + + + + 0 + False + True + GTK_PACK_END + + + + + + 6 + True + 0 + 0.5 + GTK_SHADOW_NONE + + + + True + 0.5 + 0.5 + 1 + 1 + 6 + 0 + 12 + 0 + + + + True + True + 6 + + + + True + True + Show notifications for discovered servers + True + GTK_RELIEF_NORMAL + True + False + False + True + + + 0 + False + False + + + + + + True + True + Show notifications for discovered sinks + True + GTK_RELIEF_NORMAL + True + False + False + True + + + 0 + False + False + + + + + + True + True + Show notifications for discovered sources + True + GTK_RELIEF_NORMAL + True + False + False + True + + + 0 + False + False + + + + + + True + True + Don't show notifications on startup + True + GTK_RELIEF_NORMAL + True + False + False + True + + + 0 + False + False + + + + + + + + + + True + <b>Notifications</b> + False + True + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + label_item + + + + + 0 + True + True + + + + + + + + 6 + Other Server + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_NONE + False + 300 + True + False + gtk-edit + True + False + False + GDK_WINDOW_TYPE_HINT_DIALOG + GDK_GRAVITY_NORTH_WEST + True + False + False + + + + True + False + 0 + + + + True + GTK_BUTTONBOX_END + + + + True + True + True + gtk-ok + True + GTK_RELIEF_NORMAL + True + -5 + + + + + 0 + False + True + GTK_PACK_END + + + + + + 6 + True + False + 6 + + + + True + Please enter server name: + False + True + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + + True + 0.5 + 0.5 + 1 + 1 + 0 + 0 + 12 + 0 + + + + True + True + True + True + 0 + + True + * + False + + + + + 0 + False + False + + + + + 0 + True + True + + + + + + + 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 @@ + + + + + paapplet + paapplet + FALSE + 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 +#endif + +#include + +#include +#include + +#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 + +#include + +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 -- cgit