/* Copyright 2011 Canonical Ltd. Authors: Conor Curran This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include "udev-mgr.h" #include "sane-rules.h" static void udevice_mgr_device_list_iterator (gpointer data, gpointer userdata); static void udev_mgr_uevent_cb (GUdevClient *client, gchar *action, GUdevDevice *device, gpointer user_data); static void udev_mgr_update_menuitems (UdevMgr* self); static void udev_mgr_check_if_usb_device_is_supported (UdevMgr* self, GUdevDevice *device, UdevMgrDeviceAction action); static void udev_mgr_handle_webcam (UdevMgr* self, GUdevDevice* device, UdevMgrDeviceAction action); static void udev_mgr_handle_scsi_device (UdevMgr* self, GUdevDevice* device, UdevMgrDeviceAction action); static void udev_mgr_cleanup_lists(gpointer data, gpointer self); static void udev_mgr_cleanup_entries(gpointer data, gpointer self); static void debug_device (UdevMgr* self, GUdevDevice* device, UdevMgrDeviceAction action); static gchar* format_device_name (UdevMgr* self, const gchar* brand, const gchar* generic, const gchar* branded) G_GNUC_WARN_UNUSED_RESULT; struct _UdevMgr { GObject parent_instance; DbusmenuMenuitem* scanner_item; DbusmenuMenuitem* webcam_item; GUdevClient* client; GHashTable* supported_usb_scanners; GHashTable* supported_scsi_scanners; GHashTable* scanners_present; GHashTable* webcams_present; }; const char *subsystems[3] = {"usb", "scsi", "video4linux"}; const gchar* usb_subsystem = "usb"; const gchar* scsi_subsystem = "scsi"; const gchar* video4linux_subsystem = "video4linux"; G_DEFINE_TYPE (UdevMgr, udev_mgr, G_TYPE_OBJECT); static void udev_mgr_init (UdevMgr* self) { self->client = NULL; self->supported_usb_scanners = NULL; self->scanners_present = NULL; self->webcams_present = NULL; self->client = g_udev_client_new (subsystems); self->supported_usb_scanners = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)udev_mgr_cleanup_lists); self->supported_scsi_scanners = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)udev_mgr_cleanup_lists); self->scanners_present = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); self->webcams_present = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); // load into memory all supported scanners ... populate_usb_scanners (self->supported_usb_scanners); populate_scsi_scanners (self->supported_scsi_scanners); g_signal_connect (G_OBJECT (self->client), "uevent", G_CALLBACK (udev_mgr_uevent_cb), self); } static void udev_mgr_cleanup_lists(gpointer data, gpointer self) { GList* scanners = (GList*)data; g_list_foreach (scanners, udev_mgr_cleanup_entries, NULL); g_list_free(scanners); } static void udev_mgr_cleanup_entries(gpointer data, gpointer self) { gchar* entry = (gchar*)data; g_free(entry); } static void udev_mgr_finalize (GObject *object) { UdevMgr* self = UDEV_MGR (object); g_hash_table_destroy (self->supported_scsi_scanners); g_hash_table_destroy (self->supported_usb_scanners); g_hash_table_destroy (self->scanners_present); g_hash_table_destroy (self->webcams_present); G_OBJECT_CLASS (udev_mgr_parent_class)->finalize (object); } static void udev_mgr_class_init (UdevMgrClass *klass) { GObjectClass* object_class = G_OBJECT_CLASS (klass); object_class->finalize = udev_mgr_finalize; } static void udevice_mgr_device_list_iterator (gpointer data, gpointer userdata) { g_return_if_fail (G_UDEV_IS_DEVICE (data)); g_return_if_fail (UDEV_IS_MGR (userdata)); UdevMgr* self = UDEV_MGR (userdata); GUdevDevice* device = G_UDEV_DEVICE (data); const gchar* subsystem = NULL; subsystem = g_udev_device_get_subsystem (device); if (g_strcmp0 (subsystem, "usb") == 0){ udev_mgr_check_if_usb_device_is_supported (self, device, ADD); } else if (g_strcmp0 (subsystem, "video4linux") == 0){ udev_mgr_handle_webcam (self, device, ADD); } else if (g_strcmp0 (subsystem, "scsi") == 0){ udev_mgr_handle_scsi_device (self, device, ADD); } g_object_unref (device); } static void udev_mgr_update_menuitems (UdevMgr* self) { dbusmenu_menuitem_property_set_bool (self->scanner_item, DBUSMENU_MENUITEM_PROP_VISIBLE, g_hash_table_size (self->scanners_present) > 0); dbusmenu_menuitem_property_set_bool (self->webcam_item, DBUSMENU_MENUITEM_PROP_VISIBLE, g_hash_table_size (self->webcams_present) > 0); } static void udev_mgr_uevent_cb (GUdevClient *client, gchar *action, GUdevDevice *device, gpointer user_data) { g_return_if_fail (UDEV_IS_MGR (user_data)); UdevMgr* self = UDEV_MGR (user_data); g_return_if_fail (device != NULL); g_debug ("just received a UEVENT with an action : %s", action); UdevMgrDeviceAction udev_mgr_action = ADD; if (g_strcmp0 (action, "remove") == 0){ udev_mgr_action = REMOVE; } const gchar* subsystem = NULL; subsystem = g_udev_device_get_subsystem (device); if (g_strcmp0 (subsystem, "usb") == 0){ udev_mgr_check_if_usb_device_is_supported (self, device, udev_mgr_action); } else if (g_strcmp0 (subsystem, "video4linux") == 0){ udev_mgr_handle_webcam (self, device, udev_mgr_action); } else if (g_strcmp0 (subsystem, "scsi") == 0){ udev_mgr_handle_scsi_device (self, device, udev_mgr_action); } } static void udev_mgr_handle_webcam (UdevMgr* self, GUdevDevice* device, UdevMgrDeviceAction action) { if (FALSE) debug_device (self, device, action); const gchar* vendor; const gchar* product; vendor = g_udev_device_get_property (device, "ID_VENDOR_ID"); product = g_udev_device_get_property (device, "ID_MODEL_ID"); if (!vendor || !product) { return; } if (action == REMOVE){ if (g_hash_table_lookup (self->webcams_present, product) == NULL){ g_warning ("Got a remove event on a webcam device but we don't have that device in our webcam cache"); return; } g_hash_table_remove (self->webcams_present, product); dbusmenu_menuitem_property_set (self->webcam_item, DBUSMENU_MENUITEM_PROP_LABEL, _("Webcam")); } else { if (g_hash_table_lookup (self->webcams_present, product) != NULL){ g_warning ("Got an ADD event on a webcam device but we already have that device in our webcam cache"); return; } const gchar* manufacturer = NULL; manufacturer = g_udev_device_get_property (device, "ID_VENDOR"); if (manufacturer != NULL){ gchar * label = format_device_name(self, manufacturer, _("Webcam"), _("%s Webcam")); dbusmenu_menuitem_property_set (self->webcam_item, DBUSMENU_MENUITEM_PROP_LABEL, label); g_free(label); } g_hash_table_insert (self->webcams_present, g_strdup (product), g_strdup (vendor)); } udev_mgr_update_menuitems (self); } static void debug_device (UdevMgr* self, GUdevDevice* device, UdevMgrDeviceAction action) { const gchar* vendor; const gchar* product; const gchar* number; const gchar* name; const gchar* manufacturer; vendor = g_udev_device_get_property (device, "ID_VENDOR_ID"); manufacturer = g_udev_device_get_property (device, "ID_VENDOR"); product = g_udev_device_get_property (device, "ID_MODEL_ID"); number = g_udev_device_get_number (device); name = g_udev_device_get_name (device); g_debug ("%s device vendor id %s , product id of %s , number of %s and name of %s", g_strdup(manufacturer), g_strdup(vendor), g_strdup(product), g_strdup(number), g_strdup(name)); /*const gchar *const *list; const gchar *const *iter; char propstr[500]; guint32 namelen = 0, i; list = g_udev_device_get_property_keys(device); for (iter = list; iter && *iter; iter++) { if (strlen(*iter) > namelen) namelen = strlen(*iter); } namelen++; for (iter = list; iter && *iter; iter++) { strcpy(propstr, *iter); strcat(propstr, ":"); for (i = 0; i < namelen - strlen(*iter); i++) strcat(propstr, " "); strcat(propstr, g_udev_device_get_property(device, *iter)); g_debug("%s", propstr); }*/ } // TODO SCSI is not dynamic right ? // i.e. just need to handle startup scan. static void udev_mgr_handle_scsi_device (UdevMgr* self, GUdevDevice* device, UdevMgrDeviceAction action) { const gchar* type = NULL; type = g_udev_device_get_property (device, "TYPE"); if (!type) { return; } // apparently anything thats type 6 and SCSI is a Scanner if (g_strcmp0 (type, "6") == 0 && action == ADD){ const gchar* manufacturer = NULL; manufacturer = g_udev_device_get_property (device, "ID_VENDOR"); if (manufacturer != NULL){ gchar * label = format_device_name(self, manufacturer, _("Scanner"), _("%s Scanner")); dbusmenu_menuitem_property_set (self->scanner_item, DBUSMENU_MENUITEM_PROP_LABEL, label); g_free(label); } gchar* random_scanner_name = g_strdup_printf("%p--scanner", self); g_hash_table_insert (self->scanners_present, random_scanner_name, g_strdup("Scanner")); udev_mgr_update_menuitems (self); return; } // We only care about type 3 for the special cases below if (g_strcmp0 (type, "3") != 0){ return; } const gchar* vendor = NULL; vendor = g_udev_device_get_property (device, "VENDOR"); if (vendor == NULL) return; GList* vendor_list = NULL; vendor_list = g_hash_table_lookup (self->supported_scsi_scanners, (gpointer)vendor); if (vendor_list == NULL) return; const gchar* model_id = NULL; model_id = g_udev_device_get_property (device, "MODEL"); if (model_id == NULL) return; GList* model_entry = NULL; model_entry = g_list_find_custom (vendor_list, model_id, (GCompareFunc)g_strcmp0); if (model_entry != NULL){ if (action == REMOVE){ if (g_hash_table_lookup (self->scanners_present, g_strdup(vendor)) == NULL){ g_warning ("Got an REMOVE event on a scanner device but we dont have that device in our scanners cache"); } else{ g_hash_table_remove (self->scanners_present, vendor); dbusmenu_menuitem_property_set (self->scanner_item, DBUSMENU_MENUITEM_PROP_LABEL, _("Scanner")); } } else{ if (g_hash_table_lookup (self->scanners_present, g_strdup(vendor)) != NULL){ g_warning ("Got an ADD event on a scanner device but we already have that device in our scanners cache"); } else{ const gchar* manufacturer = NULL; manufacturer = g_udev_device_get_property (device, "ID_VENDOR"); if (manufacturer != NULL){ gchar * label = format_device_name(self, manufacturer, _("Scanner"), _("%s Scanner")); dbusmenu_menuitem_property_set (self->scanner_item, DBUSMENU_MENUITEM_PROP_LABEL, label); g_free(label); } g_hash_table_insert (self->scanners_present, g_strdup(vendor), g_strdup(model_id)); } } udev_mgr_update_menuitems (self); } } static void udev_mgr_check_if_usb_device_is_supported (UdevMgr* self, GUdevDevice *device, UdevMgrDeviceAction action) { const gchar* vendor = NULL; //debug_device (self, device, action); vendor = g_udev_device_get_property (device, "ID_VENDOR_ID"); if (vendor == NULL) return; //g_debug ("vendor = %s", vendor); GList* vendor_list = NULL; vendor_list = g_hash_table_lookup (self->supported_usb_scanners, (gpointer)vendor); if (vendor_list == NULL) return; const gchar* model_id = NULL; model_id = g_udev_device_get_property (device, "ID_MODEL_ID"); if (model_id == NULL) return; GList* model_entry = NULL; model_entry = g_list_find_custom(vendor_list, model_id, (GCompareFunc)g_strcmp0); if (model_entry != NULL){ if (action == REMOVE){ if (g_hash_table_lookup (self->scanners_present, g_strdup(vendor)) == NULL){ g_warning ("Got an REMOVE event on a scanner device but we dont have that device in our scanners cache"); } else{ g_hash_table_remove (self->scanners_present, vendor); dbusmenu_menuitem_property_set (self->scanner_item, DBUSMENU_MENUITEM_PROP_LABEL, _("Scanner")); } } else{ if (g_hash_table_lookup (self->scanners_present, g_strdup(vendor)) != NULL){ g_warning ("Got an ADD event on a scanner device but we already have that device in our scanners cache"); } else{ const gchar* manufacturer = NULL; manufacturer = g_udev_device_get_property (device, "ID_VENDOR"); if (manufacturer != NULL){ gchar * label = format_device_name(self, manufacturer, _("Scanner"), _("%s Scanner")); dbusmenu_menuitem_property_set (self->scanner_item, DBUSMENU_MENUITEM_PROP_LABEL, label); g_free(label); } g_hash_table_insert (self->scanners_present, g_strdup(vendor), g_strdup(model_id)); } } udev_mgr_update_menuitems (self); } } UdevMgr* udev_mgr_new (DbusmenuMenuitem* scanner, DbusmenuMenuitem* webcam) { UdevMgr* mgr = g_object_new (UDEV_TYPE_MGR, NULL); mgr->scanner_item = scanner; mgr->webcam_item = webcam; // Check for USB devices GList* usb_devices_available = NULL; usb_devices_available = g_udev_client_query_by_subsystem (mgr->client, usb_subsystem); if (usb_devices_available != NULL){ g_list_foreach (usb_devices_available, udevice_mgr_device_list_iterator, mgr); g_list_free (usb_devices_available); } // Check for webcams GList* video_devices_available = NULL; video_devices_available = g_udev_client_query_by_subsystem (mgr->client, video4linux_subsystem); if (video_devices_available != NULL){ g_list_foreach (video_devices_available, udevice_mgr_device_list_iterator, mgr); g_list_free (video_devices_available); } // Check for SCSI devices GList* scsi_devices_available = NULL; scsi_devices_available = g_udev_client_query_by_subsystem (mgr->client, scsi_subsystem); if (scsi_devices_available != NULL){ g_list_foreach (scsi_devices_available, udevice_mgr_device_list_iterator, mgr); g_list_free (scsi_devices_available); } return mgr; } static gchar* format_device_name (UdevMgr* self, const gchar* brand, const gchar* generic, const gchar* branded) { // We don't want to accommodate long names if (strlen(brand) > 7) return g_strdup(generic); gint i = 0; // If it contains something other than an alphabetic entry ignore it. for(i = 0; i < sizeof(brand); i++){ if ( !g_ascii_isalpha (brand[i]) ) return g_strdup(generic); } gchar* lowered = g_ascii_strdown (brand, -1); lowered[0] = g_ascii_toupper (lowered[0]); gchar* label = g_strdup_printf(branded, lowered); g_free (lowered); return label; }