diff options
| author | Lennart Poettering <lennart@poettering.net> | 2006-06-02 20:25:06 +0000 | 
|---|---|---|
| committer | Lennart Poettering <lennart@poettering.net> | 2006-06-02 20:25:06 +0000 | 
| commit | f090e48f9f76b3159cf62c8fe9a162f145c4fce1 (patch) | |
| tree | c4c375a0946cdef711a2191cff40bafcbc308277 | |
| parent | f31ac41e0dce32de9a27339de2f089b720af698a (diff) | |
initial checkin
git-svn-id: file:///home/lennart/svn/public/padevchooser/trunk@3 e4aeda27-4315-0410-ac56-b21855d76123
| -rw-r--r-- | src/Makefile | 10 | ||||
| -rw-r--r-- | src/config.h | 1 | ||||
| -rw-r--r-- | src/eggtrayicon.c | 513 | ||||
| -rw-r--r-- | src/eggtrayicon.h | 80 | ||||
| -rw-r--r-- | src/padevchooser.c | 758 | ||||
| -rw-r--r-- | src/padevchooser.glade | 321 | ||||
| -rw-r--r-- | src/padevchooser.gladep | 8 | ||||
| -rw-r--r-- | src/x11prop.c | 70 | ||||
| -rw-r--r-- | src/x11prop.h | 33 | 
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, ¤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("<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", ¬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 @@ +<?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"><b>Notifications</b></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 | 
