[gvfs] port gphoto2 backend and monitor to gudev



commit 12c9c9ae561f9c572ec137d78c6da3bf9ba520bf
Author: Martin Pitt <martin pitt ubuntu com>
Date:   Thu Jul 16 18:28:38 2009 +0200

    port gphoto2 backend and monitor to gudev
    
    This works with the ID_GPHOTO2 attribute, which was recently added to
    libgphoto2:
    http://sourceforge.net/tracker/?func=detail&aid=2801117&group_id=8874&atid=308874
    
    That initial libgphoto2 patch does not yet pass the actual name, and just sets
    ID_GPHOTO2="1". However, in this patch we check if it's not "1", and use that
    as volume name (since it's the "best" one). Otherwise fall back to usb_id and
    sysfs vendor/model/product names.
    
    This also supports ID_MEDIA_PLAYER* attributes for media players with gphoto
    support. Please see here for the discussion and development progress:
    http://lists.freedesktop.org/archives/devkit-devel/2009-June/000226.html
    http://cgit.freedesktop.org/~teuf/media-player-id/
    
    http://bugzilla.gnome.org/show_bug.cgi?id=586410
    
    Signed-off-by: David Zeuthen <davidz redhat com>

 configure.ac                            |   11 ++-
 daemon/Makefile.am                      |   11 ++-
 daemon/gvfsbackendgphoto2.c             |  150 ++++++++++++++++++++++--
 monitor/gphoto2/Makefile.am             |   25 ++++-
 monitor/gphoto2/ggphoto2volume.c        |  172 +++++++++++++++++++++++++++-
 monitor/gphoto2/ggphoto2volume.h        |   18 +++-
 monitor/gphoto2/ggphoto2volumemonitor.c |  191 ++++++++++++++++++++++++++++++-
 7 files changed, 558 insertions(+), 20 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 7f1ec16..446b98c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -367,7 +367,7 @@ msg_gphoto2=no
 GPHOTO2_LIBS=
 GPHOTO2_CFLAGS=
 
-if test "x$enable_gphoto2" != "xno" -a "x$msg_hal" = "xyes" ; then
+if test "x$enable_gphoto2" != "xno" -a \( "x$msg_hal" = "xyes" -o "x$msg_gudev" = "xyes" \); then
   PKG_CHECK_EXISTS(libgphoto2, msg_gphoto2=yes)
 
   # Need OS tweaks in hal volume monitor backend
@@ -720,3 +720,12 @@ echo "
 	GNOME Keyring support:        $msg_keyring
 	Bash-completion support:      $msg_bash_completion
 "
+
+# The gudev gphoto monitor needs a recent libgphoto; point to the required patch if the version is too old
+if test "x$msg_gudev" = "xyes"; then
+  PKG_CHECK_EXISTS(libgphoto2 >= 2.4.7,, msg_gphoto_patch=yes)
+  if test "x$msg_gphoto_patch" = "xyes"; then
+    AC_MSG_WARN([You are using a libgphoto2 version earlier than 2.4.7. To work with gudev, you must apply the patch in http://sourceforge.net/tracker/?func=detail&aid=2801117&group_id=8874&atid=308874])
+  fi
+fi
+
diff --git a/daemon/Makefile.am b/daemon/Makefile.am
index 733fa41..a03eba1 100644
--- a/daemon/Makefile.am
+++ b/daemon/Makefile.am
@@ -387,10 +387,19 @@ gvfsd_gphoto2_CPPFLAGS = \
 	-DBACKEND_HEADER=gvfsbackendgphoto2.h \
 	-DDEFAULT_BACKEND_TYPE=gphoto2 \
 	-DMAX_JOB_THREADS=1 \
-	$(GPHOTO2_CFLAGS) $(HAL_CFLAGS) \
+	$(GPHOTO2_CFLAGS) \
 	-DBACKEND_TYPES='"gphoto2", G_VFS_TYPE_BACKEND_GPHOTO2,'
+if USE_GUDEV
+gvfsd_gphoto2_CPPFLAGS += $(GUDEV_CFLAGS)
+else
+gvfsd_gphoto2_CPPFLAGS += $(HAL_CFLAGS)
+endif
 
+if USE_GUDEV
+gvfsd_gphoto2_LDADD = $(libraries) $(GPHOTO2_LIBS) $(GUDEV_LIBS)
+else
 gvfsd_gphoto2_LDADD = $(libraries) $(GPHOTO2_LIBS) $(HAL_LIBS)
+endif
 
 gvfsd_http_SOURCES = \
 	soup-input-stream.c soup-input-stream.h \
diff --git a/daemon/gvfsbackendgphoto2.c b/daemon/gvfsbackendgphoto2.c
index 9ef15a6..c8177f3 100644
--- a/daemon/gvfsbackendgphoto2.c
+++ b/daemon/gvfsbackendgphoto2.c
@@ -35,8 +35,14 @@
 #include <glib/gi18n.h>
 #include <gio/gio.h>
 #include <gphoto2.h>
-#include <libhal.h>
-#include <dbus/dbus.h>
+#ifdef HAVE_GUDEV
+  #include <gudev/gudev.h>
+#elif defined(HAVE_HAL)
+  #include <libhal.h>
+  #include <dbus/dbus.h>
+#else
+  #error Needs hal or gudev
+#endif
 #include <sys/time.h>
 
 #include "gvfsbackendgphoto2.h"
@@ -176,11 +182,16 @@ struct _GVfsBackendGphoto2
   /* see comment in ensure_ignore_prefix() */
   char *ignore_prefix;
 
+#ifdef HAVE_GUDEV
+  GUdevClient *gudev_client;
+  GUdevDevice *udev_device;
+#elif defined(HAVE_HAL)
   DBusConnection *dbus_connection;
   LibHalContext *hal_ctx;
   char *hal_udi;
   char *hal_name;
-  char *hal_icon_name;
+#endif
+  char *icon_name;
 
   /* whether we can write to the device */
   gboolean can_write;
@@ -542,6 +553,13 @@ release_device (GVfsBackendGphoto2 *gphoto2_backend)
       gphoto2_backend->camera = NULL;
     }
 
+#ifdef HAVE_GUDEV
+  if (gphoto2_backend->gudev_client != NULL)
+    g_object_unref (gphoto2_backend->gudev_client);
+  if (gphoto2_backend->udev_device != NULL)
+    g_object_unref (gphoto2_backend->udev_device);
+
+#elif defined(HAVE_HAL)
   if (gphoto2_backend->dbus_connection != NULL)
     {
       dbus_connection_close (gphoto2_backend->dbus_connection);
@@ -559,8 +577,9 @@ release_device (GVfsBackendGphoto2 *gphoto2_backend)
   gphoto2_backend->hal_udi = NULL;
   g_free (gphoto2_backend->hal_name);
   gphoto2_backend->hal_name = NULL;
-  g_free (gphoto2_backend->hal_icon_name);
-  gphoto2_backend->hal_icon_name = NULL;
+#endif
+  g_free (gphoto2_backend->icon_name);
+  gphoto2_backend->icon_name = NULL;
 
   g_free (gphoto2_backend->ignore_prefix);
   gphoto2_backend->ignore_prefix = NULL;
@@ -660,13 +679,13 @@ compute_icon_name (GVfsBackendGphoto2 *gphoto2_backend)
 {
   char *result;
 
-  if (gphoto2_backend->hal_icon_name == NULL)
+  if (gphoto2_backend->icon_name == NULL)
     {
       result = g_strdup_printf ("camera-photo");
     }
   else
     {
-      result = g_strdup (gphoto2_backend->hal_icon_name);
+      result = g_strdup (gphoto2_backend->icon_name);
     }
 
   return result;
@@ -677,8 +696,28 @@ compute_icon_name (GVfsBackendGphoto2 *gphoto2_backend)
 static char *
 compute_display_name (GVfsBackendGphoto2 *gphoto2_backend)
 {
-  char *result;
+  char *result = NULL;
+
+#ifdef HAVE_GUDEV
+  const char *s;
+
+  /* the real "nice" and user-visible name is computed in the monitor; just try
+   * using the product name here */
+  if (gphoto2_backend->udev_device != NULL)
+    {
+      s = g_udev_device_get_sysfs_attr (gphoto2_backend->udev_device, "product");
+      if (s == NULL)
+        s = g_udev_device_get_property (gphoto2_backend->udev_device, "ID_MODEL");
 
+      if (s != NULL)
+        result = g_strdup (s);
+    }
+  if (result == NULL )
+    {
+      /* Translator: %s represents the device, e.g. usb:001,042  */
+      result = g_strdup_printf (_("Digital Camera (%s)"), gphoto2_backend->gphoto2_port);
+    }
+#elif defined(HAVE_HAL)
   if (gphoto2_backend->hal_name == NULL)
     {
       /* Translator: %s represents the device, e.g. usb:001,042  */
@@ -688,12 +727,76 @@ compute_display_name (GVfsBackendGphoto2 *gphoto2_backend)
     {
       result = g_strdup (gphoto2_backend->hal_name);
     }
+#endif
 
   return result;
 }
 
 /* ------------------------------------------------------------------------------------------------- */
 
+#ifdef HAVE_GUDEV
+static void
+setup_for_device (GVfsBackendGphoto2 *gphoto2_backend)
+{
+  gchar *devname;
+  char *comma;
+  char *camera_x_content_types[] = {"x-content/image-dcf", NULL};
+
+  /* turn usb:001,041 string into an udev device name */
+  if (!g_str_has_prefix (gphoto2_backend->gphoto2_port, "usb:"))
+    return;
+  devname = g_strconcat ("/dev/bus/usb/", gphoto2_backend->gphoto2_port+4, NULL);
+  if ((comma = strchr (devname, ',')) == NULL)
+    {
+      g_free (devname);
+      return;
+    }
+  *comma = '/';
+  DEBUG ("Parsed '%s' into device name %s", gphoto2_backend->gphoto2_port, devname);
+
+  /* find corresponding GUdevDevice */
+  gphoto2_backend->udev_device = g_udev_client_query_by_device_file (gphoto2_backend->gudev_client, devname);
+  g_free (devname);
+  if (gphoto2_backend->udev_device)
+    {
+      DEBUG ("-> sysfs path %s, subsys %s, name %s", g_udev_device_get_sysfs_path (gphoto2_backend->udev_device), g_udev_device_get_subsystem (gphoto2_backend->udev_device), g_udev_device_get_name (gphoto2_backend->udev_device));
+
+      /* determine icon name */
+      if (g_udev_device_has_property (gphoto2_backend->udev_device, "ID_MEDIA_PLAYER_ICON_NAME"))
+          gphoto2_backend->icon_name = g_strdup (g_udev_device_get_property (gphoto2_backend->udev_device, "ID_MEDIA_PLAYER_ICON_NAME"));
+      else if (g_udev_device_has_property (gphoto2_backend->udev_device, "ID_MEDIA_PLAYER"))
+          gphoto2_backend->icon_name = g_strdup ("multimedia-player");
+      else
+          gphoto2_backend->icon_name = g_strdup ("camera-photo");
+    }
+  else
+      DEBUG ("-> did not find matching udev device");
+
+  g_vfs_backend_set_x_content_types (G_VFS_BACKEND (gphoto2_backend), camera_x_content_types);
+}
+
+static void
+on_uevent (GUdevClient *client, gchar *action, GUdevDevice *device, gpointer user_data)
+{
+  GVfsBackendGphoto2 *gphoto2_backend = G_VFS_BACKEND_GPHOTO2 (user_data);
+
+  DEBUG ("on_uevent action %s, device %s", action, g_udev_device_get_device_file (device));
+
+  if (gphoto2_backend->udev_device != NULL && 
+      g_strcmp0 (g_udev_device_get_device_file (gphoto2_backend->udev_device), g_udev_device_get_device_file (device)) == 0 &&
+      strcmp (action, "remove") == 0)
+    {
+      DEBUG ("we have been removed!");
+
+      /* nuke all caches so we're a bit more valgrind friendly */
+      caches_invalidate_all (gphoto2_backend);
+
+      /* TODO: need a cleaner way to force unmount ourselves */
+      exit (1);
+    }
+}
+
+#elif defined(HAVE_HAL)
 static void
 find_udi_for_device (GVfsBackendGphoto2 *gphoto2_backend)
 {
@@ -851,17 +954,17 @@ find_udi_for_device (GVfsBackendGphoto2 *gphoto2_backend)
                           gphoto2_backend->hal_name = name;
                           if (icon_from_hal != NULL)
                             {
-                              gphoto2_backend->hal_icon_name = g_strdup (icon_from_hal);
+                              gphoto2_backend->icon_name = g_strdup (icon_from_hal);
                             }
                           else
                             {
                               if (m == 1)
                                 {
-                                  gphoto2_backend->hal_icon_name = g_strdup ("multimedia-player");
+                                  gphoto2_backend->icon_name = g_strdup ("multimedia-player");
                                 }
                               else
                                 {
-                                  gphoto2_backend->hal_icon_name = g_strdup ("camera-photo");
+                                  gphoto2_backend->icon_name = g_strdup ("camera-photo");
                                 }
                             }
 
@@ -909,6 +1012,7 @@ _hal_device_removed (LibHalContext *hal_ctx, const char *udi)
       exit (1);
     }
 }
+#endif
 
 /* ------------------------------------------------------------------------------------------------- */
 
@@ -1376,13 +1480,30 @@ do_mount (GVfsBackend *backend,
   GPPortInfo info;
   GPPortInfoList *il = NULL;
   int n;
-  DBusError dbus_error;
   CameraStorageInformation *storage_info;
   int num_storage_info;
 
   DEBUG ("do_mount %p", gphoto2_backend);
 
+#ifdef HAVE_GUDEV
+  /* setup gudev */
+  const char *subsystems[] = {"usb", NULL};
+
+  gphoto2_backend->gudev_client = g_udev_client_new (subsystems);
+  if (gphoto2_backend->gudev_client == NULL)
+    {
+      release_device (gphoto2_backend);
+      g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Cannot create gudev client"));
+      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+      g_error_free (error);
+      return;
+    }
+
+  g_signal_connect (gphoto2_backend->gudev_client, "uevent", G_CALLBACK (on_uevent), gphoto2_backend);
+
+#elif defined(HAVE_HAL)
   /* setup libhal */
+  DBusError dbus_error;
 
   dbus_error_init (&dbus_error);
   gphoto2_backend->dbus_connection = dbus_bus_get_private (DBUS_BUS_SYSTEM, &dbus_error);
@@ -1423,6 +1544,7 @@ do_mount (GVfsBackend *backend,
 
   libhal_ctx_set_device_removed (gphoto2_backend->hal_ctx, _hal_device_removed);
   libhal_ctx_set_user_data (gphoto2_backend->hal_ctx, gphoto2_backend);
+#endif
 
   /* setup gphoto2 */
 
@@ -1442,7 +1564,11 @@ do_mount (GVfsBackend *backend,
 
   DEBUG ("  decoded host='%s'", gphoto2_backend->gphoto2_port);
 
+#ifdef HAVE_GUDEV
+  setup_for_device (gphoto2_backend);
+#elif defined(HAVE_HAL)
   find_udi_for_device (gphoto2_backend);
+#endif
 
   gphoto2_backend->context = gp_context_new ();
   if (gphoto2_backend->context == NULL)
diff --git a/monitor/gphoto2/Makefile.am b/monitor/gphoto2/Makefile.am
index bbb54bc..18ab680 100644
--- a/monitor/gphoto2/Makefile.am
+++ b/monitor/gphoto2/Makefile.am
@@ -3,6 +3,10 @@ NULL =
 
 libexec_PROGRAMS = gvfs-gphoto2-volume-monitor
 
+if USE_GUDEV
+gvfs_gphoto2_volume_monitor_SOURCES =
+
+else
 BUILT_SOURCES =                                         \
 	hal-marshal.h           hal-marshal.c
 
@@ -12,12 +16,15 @@ hal-marshal.h: hal-marshal.list
 hal-marshal.c: hal-marshal.list
 	echo "#include \"hal-marshal.h\"" > $@ && glib-genmarshal $< --prefix=hal_marshal --body >> $@
 
-
 gvfs_gphoto2_volume_monitor_SOURCES =			\
 	hal-utils.c 		hal-utils.h 		\
 	hal-marshal.c		hal-marshal.h		\
 	hal-device.c		hal-device.h		\
 	hal-pool.c		hal-pool.h		\
+	$(NULL)
+endif
+
+gvfs_gphoto2_volume_monitor_SOURCES +=			\
 	gphoto2-volume-monitor-daemon.c			\
 	ggphoto2volume.c	ggphoto2volume.h	\
 	ggphoto2volumemonitor.c	ggphoto2volumemonitor.h	\
@@ -28,24 +35,36 @@ gvfs_gphoto2_volume_monitor_CFLAGS =		\
 	-I$(top_srcdir)/common                  \
 	-I$(top_srcdir)/monitor/proxy           \
 	$(GLIB_CFLAGS)                          \
-	$(HAL_CFLAGS)                           \
 	$(GPHOTO2_CFLAGS)			\
 	-DGIO_MODULE_DIR=\"$(GIO_MODULE_DIR)\"	\
 	-DGVFS_LOCALEDIR=\""$(localedir)"\"	\
 	-DG_DISABLE_DEPRECATED			\
+	-DG_UDEV_API_IS_SUBJECT_TO_CHANGE	\
 	$(NULL)
 
+if USE_GUDEV
+gvfs_gphoto2_volume_monitor_CFLAGS += $(GUDEV_CFLAGS)
+else
+gvfs_gphoto2_volume_monitor_CFLAGS += $(HAL_CFLAGS)
+endif
+
 gvfs_gphoto2_volume_monitor_LDFLAGS =	\
 	$(NULL)
 
 gvfs_gphoto2_volume_monitor_LDADD  =		     			      \
 	$(GLIB_LIBS)                                 			      \
-	$(HAL_LIBS)                                  			      \
 	$(GPHOTO2_LIBS)                              			      \
 	$(top_builddir)/common/libgvfscommon.la 			      \
 	$(top_builddir)/monitor/proxy/libgvfsproxyvolumemonitordaemon-noin.la \
 	$(NULL)
 
+if USE_GUDEV
+gvfs_gphoto2_volume_monitor_LDADD += $(GUDEV_LIBS)
+else
+gvfs_gphoto2_volume_monitor_LDADD += $(HAL_LIBS)
+endif
+
+
 remote_volume_monitorsdir = $(datadir)/gvfs/remote-volume-monitors
 remote_volume_monitors_DATA = gphoto2.monitor
 
diff --git a/monitor/gphoto2/ggphoto2volume.c b/monitor/gphoto2/ggphoto2volume.c
index 1ddbee1..b7c0438 100644
--- a/monitor/gphoto2/ggphoto2volume.c
+++ b/monitor/gphoto2/ggphoto2volume.c
@@ -33,7 +33,9 @@
 
 #include "ggphoto2volume.h"
 
+#ifndef HAVE_GUDEV
 #include "hal-utils.h"
+#endif
 
 /* Protects all fields of GHalDrive that can change */
 G_LOCK_DEFINE_STATIC(gphoto2_volume);
@@ -44,8 +46,12 @@ struct _GGPhoto2Volume {
   GVolumeMonitor *volume_monitor; /* owned by volume monitor */
 
   char *device_path;
+#ifdef HAVE_GUDEV
+  GUdevDevice *device;
+#else
   HalDevice *device;
   HalDevice *drive_device;
+#endif
 
   GFile *activation_root;
 
@@ -138,6 +144,120 @@ dupv_and_uniqify (char **str_array)
   return result;
 }
 
+#ifdef HAVE_GUDEV
+static int hexdigit(char c)
+{
+  if (c >= 'a')
+      return c - 'a' + 10;
+  if (c >= 'A')
+     return c - 'A' + 10;
+  g_return_val_if_fail (c >= '0' && c <= '9', 0);
+  return c - '0';
+}
+
+/* Do not free result, it's a static buffer */
+static const char*
+udev_decode_string (const char* encoded)
+{
+  static char decoded[4096];
+  int len;
+  const char* s;
+
+  if (encoded == NULL)
+      return NULL;
+
+  for (len = 0, s = encoded; *s && len < sizeof(decoded)-1; ++len, ++s)
+    {
+      /* need to check for NUL terminator in advance */
+      if (s[0] == '\\' && s[1] == 'x' && s[2] >= '0' && s[3] >= '0')
+	{
+	  decoded[len] = (hexdigit(s[2]) << 4) | hexdigit(s[3]);
+	  s += 3;
+	}
+      else
+	  decoded[len] = *s;
+    }
+  decoded[len] = '\0';
+  return decoded;
+}
+
+static void
+set_volume_name (GGPhoto2Volume *v)
+{
+  const char *gphoto_name;
+  const char *product = NULL;
+  const char *vendor;
+  const char *model;
+
+  /* our preference: ID_GPHOTO2 > ID_MEDIA_PLAYER_{VENDOR,PRODUCT} > product >
+   * ID_{VENDOR,MODEL} */
+
+  gphoto_name = g_udev_device_get_property (v->device, "ID_GPHOTO2");
+  if (gphoto_name != NULL && strcmp (gphoto_name, "1") != 0)
+    {
+      v->name = g_strdup (gphoto_name);
+      return;
+    }
+
+  vendor = g_udev_device_get_property (v->device, "ID_MEDIA_PLAYER_VENDOR");
+  if (vendor == NULL)
+      vendor = g_udev_device_get_property (v->device, "ID_VENDOR_ENC");
+  model = g_udev_device_get_property (v->device, "ID_MEDIA_PLAYER_MODEL");
+  if (model == NULL)
+    {
+      model = g_udev_device_get_property (v->device, "ID_MODEL_ENC");
+      product = g_udev_device_get_sysfs_attr (v->device, "product");
+    }
+
+  v->name = NULL;
+  if (product != NULL && strlen (product) > 0)
+    v->name = g_strdup (product);
+  else if (vendor == NULL)
+    {
+      if (model != NULL)
+        v->name = g_strdup (udev_decode_string (model));
+    }
+  else
+    {
+      if (model != NULL)
+	{
+	  /* we can't call udev_decode_string() twice in one g_strdup_printf(),
+	   * it returns a static buffer */
+	  gchar *temp = g_strdup_printf ("%s %s", vendor, model);
+          v->name = g_strdup (udev_decode_string (temp));
+	  g_free (temp);
+        }
+      else
+        {
+          if (g_udev_device_has_property (v->device, "ID_MEDIA_PLAYER"))
+            {
+              /* Translators: %s is the device vendor */
+              v->name = g_strdup_printf (_("%s Audio Player"), udev_decode_string (vendor));
+	    }
+	  else
+	    {
+	      /* Translators: %s is the device vendor */
+	      v->name = g_strdup_printf (_("%s Camera"), udev_decode_string (vendor));
+	    }
+        }
+    }
+
+  if (v->name == NULL)
+      v->name = g_strdup (_("Camera"));
+}
+
+static void
+set_volume_icon (GGPhoto2Volume *volume)
+{
+  if (g_udev_device_has_property (volume->device, "ID_MEDIA_PLAYER_ICON_NAME"))
+      volume->icon = g_strdup (g_udev_device_get_property (volume->device, "ID_MEDIA_PLAYER_ICON_NAME"));
+  else if (g_udev_device_has_property (volume->device, "ID_MEDIA_PLAYER"))
+      volume->icon = g_strdup ("multimedia-player");
+  else
+      volume->icon = g_strdup ("camera-photo");
+}
+
+#else
 static void
 do_update_from_hal_for_camera (GGPhoto2Volume *v)
 {
@@ -239,23 +359,40 @@ hal_changed (HalDevice *device, const char *key, gpointer user_data)
   /*g_warning ("hal modifying %s (property %s changed)", gphoto2_volume->device_path, key);*/
   update_from_hal (gphoto2_volume, TRUE);
 }
+#endif
 
 GGPhoto2Volume *
 g_gphoto2_volume_new (GVolumeMonitor   *volume_monitor,
+#ifdef HAVE_GUDEV
+                      GUdevDevice      *device,
+                      GUdevClient      *gudev_client,
+#else
                       HalDevice        *device,
                       HalPool          *pool,
+#endif
                       GFile            *activation_root)
 {
   GGPhoto2Volume *volume;
+#ifndef HAVE_GUDEV
   HalDevice *drive_device;
   const char *storage_udi;
+#endif
   const char *device_path;
 
   g_return_val_if_fail (volume_monitor != NULL, NULL);
   g_return_val_if_fail (device != NULL, NULL);
+#ifdef HAVE_GUDEV
+  g_return_val_if_fail (gudev_client != NULL, NULL);
+#else
   g_return_val_if_fail (pool != NULL, NULL);
+#endif
   g_return_val_if_fail (activation_root != NULL, NULL);
 
+#ifdef HAVE_GUDEV
+  if (!g_udev_device_has_property (device, "ID_GPHOTO2"))
+      return NULL;
+  device_path = g_udev_device_get_device_file (device);
+#else
   if (!(hal_device_has_capability (device, "camera") ||
         (hal_device_has_capability (device, "portable_audio_player") &&
          hal_device_get_property_bool (device, "camera.libgphoto2.support"))))
@@ -276,19 +413,28 @@ g_gphoto2_volume_new (GVolumeMonitor   *volume_monitor,
   device_path = hal_device_get_property_string (drive_device, "linux.device_file");
   if (strlen (device_path) == 0)
     device_path = NULL;
+#endif
 
   volume = g_object_new (G_TYPE_GPHOTO2_VOLUME, NULL);
   volume->volume_monitor = volume_monitor;
   g_object_add_weak_pointer (G_OBJECT (volume_monitor), (gpointer) &(volume->volume_monitor));
   volume->device_path = g_strdup (device_path);
   volume->device = g_object_ref (device);
+#ifndef HAVE_GUDEV
   volume->drive_device = g_object_ref (drive_device);
+#endif
   volume->activation_root = g_object_ref (activation_root);
 
+#ifdef HAVE_GUDEV
+  set_volume_name (volume);
+  set_volume_icon (volume);
+  /* we do not really need to listen for changes */
+#else
   g_signal_connect_object (device, "hal_property_changed", (GCallback) hal_changed, volume, 0);
   g_signal_connect_object (drive_device, "hal_property_changed", (GCallback) hal_changed, volume, 0);
 
   update_from_hal (volume, FALSE);
+#endif
 
   return volume;
 }
@@ -360,6 +506,24 @@ g_gphoto2_volume_get_mount (GVolume *volume)
   return NULL;
 }
 
+#ifdef HAVE_GUDEV
+gboolean
+g_gphoto2_volume_has_path (GGPhoto2Volume  *volume,
+                      const char  *sysfs_path)
+{
+  GGPhoto2Volume *gphoto2_volume = G_GPHOTO2_VOLUME (volume);
+  gboolean res;
+
+  G_LOCK (gphoto2_volume);
+  res = FALSE;
+  if (gphoto2_volume->device != NULL)
+    res = strcmp (g_udev_device_get_sysfs_path   (gphoto2_volume->device), sysfs_path) == 0;
+  G_UNLOCK (gphoto2_volume);
+  return res;
+}
+
+#else
+
 gboolean
 g_gphoto2_volume_has_udi (GGPhoto2Volume  *volume,
                       const char  *udi)
@@ -374,6 +538,7 @@ g_gphoto2_volume_has_udi (GGPhoto2Volume  *volume,
   G_UNLOCK (gphoto2_volume);
   return res;
 }
+#endif
 
 typedef struct
 {
@@ -449,9 +614,12 @@ g_gphoto2_volume_get_identifier (GVolume              *volume,
 
   G_LOCK (gphoto2_volume);
   id = NULL;
+#ifndef HAVE_GUDEV
   if (strcmp (kind, G_VOLUME_IDENTIFIER_KIND_HAL_UDI) == 0)
     id = g_strdup (hal_device_get_udi (gphoto2_volume->device));
-  else if (strcmp (kind, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE) == 0)
+  else 
+#endif
+    if (strcmp (kind, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE) == 0)
     id = g_strdup (gphoto2_volume->device_path);
   G_UNLOCK (gphoto2_volume);
 
@@ -468,8 +636,10 @@ g_gphoto2_volume_enumerate_identifiers (GVolume *volume)
 
   res = g_ptr_array_new ();
 
+#ifndef HAVE_GUDEV
   g_ptr_array_add (res,
                    g_strdup (G_VOLUME_IDENTIFIER_KIND_HAL_UDI));
+#endif
 
   if (gphoto2_volume->device_path && *gphoto2_volume->device_path != 0)
     g_ptr_array_add (res,
diff --git a/monitor/gphoto2/ggphoto2volume.h b/monitor/gphoto2/ggphoto2volume.h
index a7a4131..08d9b49 100644
--- a/monitor/gphoto2/ggphoto2volume.h
+++ b/monitor/gphoto2/ggphoto2volume.h
@@ -26,7 +26,13 @@
 #include <glib-object.h>
 #include <gio/gio.h>
 
-#include "hal-pool.h"
+#ifdef HAVE_GUDEV
+ #include <gudev/gudev.h>
+#elif defined(HAVE_HAL)
+ #include "hal-pool.h"
+#else
+ #error Needs gudev or hal
+#endif
 #include "ggphoto2volumemonitor.h"
 
 G_BEGIN_DECLS
@@ -46,12 +52,22 @@ struct _GGPhoto2VolumeClass {
 GType g_gphoto2_volume_get_type (void) G_GNUC_CONST;
 
 GGPhoto2Volume *g_gphoto2_volume_new            (GVolumeMonitor   *volume_monitor,
+#ifdef HAVE_GUDEV
+                                                 GUdevDevice      *device,
+                                                 GUdevClient      *gudev_client,
+#else
                                                  HalDevice        *device,
                                                  HalPool          *pool,
+#endif
                                                  GFile            *activation_root);
 
+#ifdef HAVE_GUDEV
+gboolean    g_gphoto2_volume_has_path       (GGPhoto2Volume       *volume,
+                                             const char       *path);
+#else
 gboolean    g_gphoto2_volume_has_udi        (GGPhoto2Volume       *volume,
                                              const char       *udi);
+#endif
 
 void        g_gphoto2_volume_removed        (GGPhoto2Volume       *volume);
 
diff --git a/monitor/gphoto2/ggphoto2volumemonitor.c b/monitor/gphoto2/ggphoto2volumemonitor.c
index b5ec0ec..a51674c 100644
--- a/monitor/gphoto2/ggphoto2volumemonitor.c
+++ b/monitor/gphoto2/ggphoto2volumemonitor.c
@@ -35,25 +35,42 @@
 #include "ggphoto2volumemonitor.h"
 #include "ggphoto2volume.h"
 
+#ifdef HAVE_GUDEV
+#include <gio/gio.h>
+#include <gio/gunixmounts.h>
+#else
 #include "hal-pool.h"
+#endif
 
 G_LOCK_DEFINE_STATIC(hal_vm);
 
 static GGPhoto2VolumeMonitor *the_volume_monitor = NULL;
+#ifndef HAVE_GUDEV
 static HalPool *pool = NULL;
+#endif
 
 struct _GGPhoto2VolumeMonitor {
   GNativeVolumeMonitor parent;
 
   GUnixMountMonitor *mount_monitor;
 
+#ifdef HAVE_GUDEV
+  GUdevClient *gudev_client;
+#else
   HalPool *pool;
+#endif
 
   GList *last_camera_devices;
 
   GList *camera_volumes;
 };
 
+#ifdef HAVE_GUDEV
+static void on_uevent                (GUdevClient *client, 
+                                      gchar *action,
+                                      GUdevDevice *device,
+                                      gpointer user_data);
+#else
 static void hal_changed              (HalPool    *pool,
                                       HalDevice  *device,
                                       gpointer    user_data);
@@ -64,7 +81,9 @@ static void update_all (GGPhoto2VolumeMonitor *monitor,
 static void update_cameras           (GGPhoto2VolumeMonitor *monitor,
                                       GList **added_volumes,
                                       GList **removed_volumes);
+#endif
 
+static GList* get_stores_for_camera (int bus_num, int device_num);
 
 G_DEFINE_TYPE (GGPhoto2VolumeMonitor, g_gphoto2_volume_monitor, G_TYPE_VOLUME_MONITOR)
 
@@ -75,6 +94,7 @@ list_free (GList *objects)
   g_list_free (objects);
 }
 
+#ifndef HAVE_GUDEV
 static HalPool *
 get_hal_pool (void)
 {
@@ -85,6 +105,7 @@ get_hal_pool (void)
 
   return pool;
 }
+#endif
 
 static void
 g_gphoto2_volume_monitor_dispose (GObject *object)
@@ -108,9 +129,14 @@ g_gphoto2_volume_monitor_finalize (GObject *object)
 
   monitor = G_GPHOTO2_VOLUME_MONITOR (object);
 
-  g_signal_handlers_disconnect_by_func (monitor->pool, hal_changed, monitor);
+#ifdef HAVE_GUDEV
+  g_signal_handlers_disconnect_by_func (monitor->gudev_client, on_uevent, monitor);
 
+  g_object_unref (monitor->gudev_client);
+#else
+  g_signal_handlers_disconnect_by_func (monitor->pool, hal_changed, monitor);
   g_object_unref (monitor->pool);
+#endif
 
   list_free (monitor->last_camera_devices);
   list_free (monitor->camera_volumes);
@@ -161,6 +187,145 @@ get_mount_for_uuid (GVolumeMonitor *volume_monitor, const char *uuid)
   return NULL;
 }
 
+#ifdef HAVE_GUDEV
+
+static void
+gudev_add_camera (GGPhoto2VolumeMonitor *monitor, GUdevDevice *device, gboolean do_emit)
+{
+    GGPhoto2Volume *volume;
+    GList *store_heads, *l;
+    guint num_store_heads;
+    const char *property;
+    int usb_bus_num;
+    int usb_device_num;
+
+    property = g_udev_device_get_property (device, "BUSNUM");
+    if (property == NULL) {
+	g_warning("device %s has no BUSNUM property, ignoring", g_udev_device_get_device_file (device));
+	return;
+    }
+    usb_bus_num = atoi (property);
+
+    property = g_udev_device_get_property (device, "DEVNUM");
+    if (property == NULL) {
+	g_warning("device %s has no DEVNUM property, ignoring", g_udev_device_get_device_file (device));
+	return;
+    }
+    usb_device_num = atoi (property);
+
+    /* g_debug ("gudev_add_camera: camera device %s (bus: %i, device: %i)", 
+             g_udev_device_get_device_file (device),
+             usb_bus_num, usb_device_num); */
+
+    store_heads = get_stores_for_camera (usb_bus_num, usb_device_num);
+    num_store_heads = g_list_length (store_heads);
+    for (l = store_heads ; l != NULL; l = l->next)
+      {
+        char *store_path = (char *) l->data;
+        GFile *activation_mount_root;
+        gchar *uri;
+
+        /* If we only have a single store, don't use the store name at all. The backend automatically
+         * prepend the storename; this is to work around bugs with devices (like the iPhone) for which
+         * the store name changes every time the camera is initialized (e.g. mounted).
+         */
+        if (num_store_heads == 1)
+          {
+            uri = g_strdup_printf ("gphoto2://[usb:%03d,%03d]", usb_bus_num, usb_device_num);
+          }
+        else
+          {
+            uri = g_strdup_printf ("gphoto2://[usb:%03d,%03d]/%s", usb_bus_num, usb_device_num,
+                                   store_path[0] == '/' ? store_path + 1 : store_path);
+          }
+        /* g_debug ("gudev_add_camera: ... adding URI for storage head: %s", uri); */
+        activation_mount_root = g_file_new_for_uri (uri);
+        g_free (uri);
+
+        volume = g_gphoto2_volume_new (G_VOLUME_MONITOR (monitor),
+                                       device, 
+                                       monitor->gudev_client,
+                                       activation_mount_root);
+        if (volume != NULL)
+          {
+            monitor->camera_volumes = g_list_prepend (monitor->camera_volumes, volume);
+            if (do_emit)
+                g_signal_emit_by_name (monitor, "volume_added", volume);
+          }
+
+        if (activation_mount_root != NULL)
+          g_object_unref (activation_mount_root);
+      }
+
+    g_list_foreach (store_heads, (GFunc) g_free, NULL);
+    g_list_free (store_heads);
+}
+
+static void
+gudev_remove_camera (GGPhoto2VolumeMonitor *monitor, GUdevDevice *device)
+{
+    /* g_debug ("gudev_remove_camera: %s", g_udev_device_get_device_file (device)); */
+
+    GList *l;
+    const gchar* sysfs_path = g_udev_device_get_sysfs_path (device);
+  
+    for (l = monitor->camera_volumes; l != NULL; l = l->next)
+      {
+        GGPhoto2Volume *volume = G_GPHOTO2_VOLUME (l->data);
+
+        if (g_gphoto2_volume_has_path (volume, sysfs_path))
+          {
+            /* g_debug ("gudev_remove_camera: found volume %s, deleting", sysfs_path); */
+            g_signal_emit_by_name (monitor, "volume_removed", volume);
+            g_signal_emit_by_name (volume, "removed");
+            g_gphoto2_volume_removed (volume);
+            monitor->camera_volumes = g_list_remove (monitor->camera_volumes, volume);
+            g_object_unref (volume);
+          }
+      }
+}
+
+static void
+on_uevent (GUdevClient *client, 
+           gchar *action,
+           GUdevDevice *device,
+           gpointer user_data)
+{
+  GGPhoto2VolumeMonitor *monitor = G_GPHOTO2_VOLUME_MONITOR (user_data);
+
+  /* g_debug ("on_uevent: action=%s, device=%s", action, g_udev_device_get_device_file(device)); */
+
+  /* filter out uninteresting events */
+  if (!g_udev_device_has_property (device, "ID_GPHOTO2"))
+    {
+      /* g_debug ("on_uevent: discarding, not ID_GPHOTO2"); */
+      return;
+    }
+
+  if (strcmp (action, "add") == 0)
+     gudev_add_camera (monitor, device, TRUE); 
+  else if (strcmp (action, "remove") == 0)
+     gudev_remove_camera (monitor, device); 
+}
+
+/* Find all attached gphoto supported cameras; this is called on startup
+ * (coldplugging). */
+static void
+gudev_coldplug_cameras (GGPhoto2VolumeMonitor *monitor)
+{
+    GList *usb_devices, *l;
+
+    usb_devices = g_udev_client_query_by_subsystem (monitor->gudev_client, "usb");
+    for (l = usb_devices; l != NULL; l = l->next)
+    {
+        GUdevDevice *d = l->data;
+        if (g_udev_device_has_property (d, "ID_GPHOTO2"))
+            gudev_add_camera (monitor, d, FALSE);
+    }
+}
+
+#else
+
 static void
 hal_changed (HalPool    *pool,
              HalDevice  *device,
@@ -172,6 +337,7 @@ hal_changed (HalPool    *pool,
 
   update_all (monitor, TRUE);
 }
+#endif
 
 static GObject *
 g_gphoto2_volume_monitor_constructor (GType                  type,
@@ -204,6 +370,18 @@ g_gphoto2_volume_monitor_constructor (GType                  type,
                                       construct_properties);
 
   monitor = G_GPHOTO2_VOLUME_MONITOR (object);
+
+#ifdef HAVE_GUDEV
+  const char *subsystems[] = {"usb", NULL};
+  monitor->gudev_client = g_udev_client_new (subsystems);
+
+  g_signal_connect (monitor->gudev_client, 
+                    "uevent", G_CALLBACK (on_uevent), 
+                    monitor);
+
+  gudev_coldplug_cameras (monitor);
+
+#else
   monitor->pool = g_object_ref (get_hal_pool ());
 
   g_signal_connect (monitor->pool,
@@ -215,6 +393,7 @@ g_gphoto2_volume_monitor_constructor (GType                  type,
                     monitor);
 
   update_all (monitor, FALSE);
+#endif
 
   G_LOCK (hal_vm);
   the_volume_monitor = monitor;
@@ -231,7 +410,13 @@ g_gphoto2_volume_monitor_init (GGPhoto2VolumeMonitor *monitor)
 static gboolean
 is_supported (void)
 {
+#ifdef HAVE_GUDEV
+  /* Today's Linux desktops pretty much need udev to have anything working, so
+   * assume it's there */
+  return TRUE;
+#else
   return get_hal_pool() != NULL;
+#endif
 }
 
 static void
@@ -267,6 +452,7 @@ g_gphoto2_volume_monitor_new (void)
   return G_VOLUME_MONITOR (monitor);
 }
 
+#ifndef HAVE_GUDEV
 static void
 diff_sorted_lists (GList         *list1,
                    GList         *list2,
@@ -405,6 +591,7 @@ update_all (GGPhoto2VolumeMonitor *monitor,
       list_free (added_volumes);
     }
 }
+#endif
 
 static GList *
 get_stores_for_camera (int bus_num, int device_num)
@@ -482,6 +669,7 @@ out:
   return l;
 }
 
+#ifndef HAVE_GUDEV
 static void
 update_cameras (GGPhoto2VolumeMonitor *monitor,
                 GList **added_volumes,
@@ -618,3 +806,4 @@ update_cameras (GGPhoto2VolumeMonitor *monitor,
   list_free (monitor->last_camera_devices);
   monitor->last_camera_devices = new_camera_devices;
 }
+#endif



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]