1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2  *
3  * Copyright (C) 2012 Richard Hughes <richard@hughsie.com>
4  *
5  * Licensed under the GNU General Public License Version 2
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21 
22 #include <config.h>
23 #include <gio/gio.h>
24 #include <cd-plugin.h>
25 #include <gudev/gudev.h>
26 #include <cd-device.h>
27 
28 struct CdPluginPrivate {
29 	GUdevClient		*udev_client;
30 	GHashTable		*devices;
31 };
32 
33 /**
34  * cd_plugin_get_description:
35  */
36 const gchar *
cd_plugin_get_description(void)37 cd_plugin_get_description (void)
38 {
39 	return "Add and remove scanner devices using the SANE udev database";
40 }
41 
42 /**
43  * cd_plugin_config_enabled:
44  */
45 gboolean
cd_plugin_config_enabled(void)46 cd_plugin_config_enabled (void)
47 {
48 #ifdef HAVE_SANE
49 	return FALSE;
50 #else
51 	return TRUE;
52 #endif
53 }
54 
55 /**
56  * cd_plugin_get_scanner_id_for_udev_device:
57  **/
58 static gchar *
cd_plugin_get_scanner_id_for_udev_device(GUdevDevice * udev_device)59 cd_plugin_get_scanner_id_for_udev_device (GUdevDevice *udev_device)
60 {
61 	GString *string;
62 	const gchar *tmp;
63 
64 	/* get id */
65 	string = g_string_new ("sysfs");
66 	tmp = g_udev_device_get_property (udev_device, "ID_VENDOR");
67 	if (tmp != NULL)
68 		g_string_append_printf (string, "-%s", tmp);
69 	tmp = g_udev_device_get_property (udev_device, "ID_MODEL");
70 	if (tmp != NULL)
71 		g_string_append_printf (string, "-%s", tmp);
72 
73 	/* fallback */
74 	if (string->len == 5) {
75 		tmp = g_udev_device_get_device_file (udev_device);
76 		g_string_append_printf (string, "-%s", tmp);
77 	}
78 
79 	return g_string_free (string, FALSE);
80 }
81 
82 /**
83  * cd_plugin_add:
84  **/
85 static void
cd_plugin_add(CdPlugin * plugin,GUdevDevice * udev_device)86 cd_plugin_add (CdPlugin *plugin, GUdevDevice *udev_device)
87 {
88 	const gchar *devclass;
89 	const gchar *seat;
90 	g_autofree gchar *id = NULL;
91 	g_autofree gchar *model = NULL;
92 	g_autofree gchar *vendor = NULL;
93 	g_autoptr(CdDevice) device = NULL;
94 
95 	/* is a scanner? */
96 	if (!g_udev_device_has_property (udev_device, "libsane_matched"))
97 		return;
98 
99 	/* skip hubs */
100 	devclass = g_udev_device_get_sysfs_attr (udev_device, "bDeviceClass");
101 	if (devclass == NULL || g_strcmp0 (devclass, "09") == 0)
102 		return;
103 
104 	/* replace underscores with spaces */
105 	model = g_strdup (g_udev_device_get_property (udev_device,
106 						      "ID_MODEL"));
107 	if (model != NULL) {
108 		g_strdelimit (model, "_\r\n", ' ');
109 		g_strchomp (model);
110 	}
111 	vendor = g_strdup (g_udev_device_get_property (udev_device,
112 						       "ID_VENDOR"));
113 	if (vendor != NULL) {
114 		g_strdelimit (vendor, "_\r\n", ' ');
115 		g_strchomp (vendor);
116 	}
117 
118 	/* generate ID */
119 	id = cd_plugin_get_scanner_id_for_udev_device (udev_device);
120 
121 	/* assume device belongs to "seat0" if not tagged */
122 	seat = g_udev_device_get_property (udev_device, "ID_SEAT");
123 	if (seat == NULL)
124 		seat = "seat0";
125 
126 	/* create new device */
127 	device = cd_device_new ();
128 	cd_device_set_id (device, id);
129 	cd_device_set_property_internal (device,
130 					 "Kind",
131 					 "scanner",
132 					 FALSE,
133 					 NULL);
134 	if (model != NULL) {
135 		cd_device_set_property_internal (device,
136 						 "Model",
137 						 model,
138 						 FALSE,
139 						 NULL);
140 	}
141 	if (vendor != NULL) {
142 		cd_device_set_property_internal (device,
143 						 "Vendor",
144 						 vendor,
145 						 FALSE,
146 						 NULL);
147 	}
148 	cd_device_set_property_internal (device,
149 					 "Colorspace",
150 					 "rgb",
151 					 FALSE,
152 					 NULL);
153 	cd_device_set_property_internal (device,
154 					 "Serial",
155 					 g_udev_device_get_sysfs_path (udev_device),
156 					 FALSE,
157 					 NULL);
158 	cd_device_set_property_internal (device,
159 					 "Seat",
160 					 seat,
161 					 FALSE,
162 					 NULL);
163 
164 	/* keep track so we can remove with the same device */
165 	g_hash_table_insert (plugin->priv->devices,
166 			     g_strdup (g_udev_device_get_sysfs_path (udev_device)),
167 			     g_object_ref (device));
168 
169 	g_debug ("CdPlugin: emit add: %s", id);
170 	cd_plugin_device_added (plugin, device);
171 }
172 
173 /**
174  * cd_plugin_uevent_cb:
175  **/
176 static void
cd_plugin_uevent_cb(GUdevClient * udev_client,const gchar * action,GUdevDevice * udev_device,CdPlugin * plugin)177 cd_plugin_uevent_cb (GUdevClient *udev_client,
178 		     const gchar *action,
179 		     GUdevDevice *udev_device,
180 		     CdPlugin *plugin)
181 {
182 	const gchar *sysfs_path;
183 	CdDevice *device;
184 
185 	/* remove */
186 	if (g_strcmp0 (action, "remove") == 0) {
187 
188 		/* is this a scanner device we added */
189 		sysfs_path = g_udev_device_get_sysfs_path (udev_device);
190 		device = g_hash_table_lookup (plugin->priv->devices, sysfs_path);
191 		if (device == NULL)
192 			return;
193 
194 		g_debug ("CdPlugin: remove %s", sysfs_path);
195 		cd_plugin_device_removed (plugin, device);
196 		g_hash_table_remove (plugin->priv->devices, sysfs_path);
197 		return;
198 	}
199 
200 	/* add */
201 	if (g_strcmp0 (action, "add") == 0) {
202 		cd_plugin_add (plugin, udev_device);
203 		return;
204 	}
205 }
206 
207 /**
208  * cd_plugin_coldplug:
209  */
210 void
cd_plugin_coldplug(CdPlugin * plugin)211 cd_plugin_coldplug (CdPlugin *plugin)
212 {
213 	GList *devices;
214 	GList *l;
215 	GUdevDevice *udev_device;
216 
217 	/* add all USB scanner devices */
218 	devices = g_udev_client_query_by_subsystem (plugin->priv->udev_client,
219 						    "usb");
220 	for (l = devices; l != NULL; l = l->next) {
221 		udev_device = l->data;
222 		cd_plugin_add (plugin, udev_device);
223 	}
224 	g_list_foreach (devices, (GFunc) g_object_unref, NULL);
225 	g_list_free (devices);
226 
227 	/* watch udev for changes */
228 	g_signal_connect (plugin->priv->udev_client, "uevent",
229 			  G_CALLBACK (cd_plugin_uevent_cb), plugin);
230 }
231 
232 /**
233  * cd_plugin_initialize:
234  */
235 void
cd_plugin_initialize(CdPlugin * plugin)236 cd_plugin_initialize (CdPlugin *plugin)
237 {
238 	const gchar *subsystems[] = { "usb", NULL };
239 
240 	/* create private */
241 	plugin->priv = CD_PLUGIN_GET_PRIVATE (CdPluginPrivate);
242 	plugin->priv->devices = g_hash_table_new_full (g_str_hash,
243 						       g_str_equal,
244 						       g_free,
245 						       (GDestroyNotify) g_object_unref);
246 	plugin->priv->udev_client = g_udev_client_new (subsystems);
247 }
248 
249 /**
250  * cd_plugin_destroy:
251  */
252 void
cd_plugin_destroy(CdPlugin * plugin)253 cd_plugin_destroy (CdPlugin *plugin)
254 {
255 	g_object_unref (plugin->priv->udev_client);
256 	g_hash_table_unref (plugin->priv->devices);
257 }
258