]> git.0d.be Git - empathy.git/blob - libempathy/cheese-camera-device-monitor.c
contact-dialog: stop using EmpathyContactManager
[empathy.git] / libempathy / cheese-camera-device-monitor.c
1 /*
2  * Copyright © 2007,2008 Jaap Haitsma <jaap@haitsma.org>
3  * Copyright © 2007-2009 daniel g. siegel <dgsiegel@gnome.org>
4  * Copyright © 2008 Ryan Zeigler <zeiglerr@gmail.com>
5  * Copyright © 2010 Filippo Argiolas <filippo.argiolas@gmail.com>
6  *
7  * Licensed under the GNU General Public License Version 2
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
21  */
22 #ifdef HAVE_CONFIG_H
23   #include <config.h>
24 #endif
25
26 #include <glib-object.h>
27 #include <string.h>
28
29 #ifdef HAVE_UDEV
30   #define G_UDEV_API_IS_SUBJECT_TO_CHANGE 1
31   #include <gudev/gudev.h>
32 #else
33   #include <fcntl.h>
34   #include <unistd.h>
35   #include <sys/ioctl.h>
36   #if USE_SYS_VIDEOIO_H > 0
37     #include <sys/types.h>
38     #include <sys/videoio.h>
39   #elif defined (__sun)
40     #include <sys/types.h>
41     #include <sys/videodev2.h>
42   #endif /* USE_SYS_VIDEOIO_H */
43 #endif
44
45 #include "cheese-camera-device-monitor.h"
46
47 /**
48  * SECTION:cheese-camera-device-monitor
49  * @short_description: Simple object to enumerate v4l devices
50  * @include: cheese/cheese-camera-device-monitor.h
51  *
52  * #CheeseCameraDeviceMonitor provides a basic interface for
53  * video4linux device enumeration and hotplugging.
54  *
55  * It uses either GUdev or some platform specific code to list video
56  * devices.  It is also capable (right now in linux only, with the
57  * udev backend) to monitor device plugging and emit a
58  * CheeseCameraDeviceMonitor::added or
59  * CheeseCameraDeviceMonitor::removed signal when an event happens.
60  */
61
62 G_DEFINE_TYPE (CheeseCameraDeviceMonitor, cheese_camera_device_monitor, G_TYPE_OBJECT)
63
64 #define CHEESE_CAMERA_DEVICE_MONITOR_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o),                               \
65                                                                                   CHEESE_TYPE_CAMERA_DEVICE_MONITOR, \
66                                                                                   CheeseCameraDeviceMonitorPrivate))
67
68 #define CHEESE_CAMERA_DEVICE_MONITOR_ERROR cheese_camera_device_monitor_error_quark ()
69
70 GST_DEBUG_CATEGORY (cheese_device_monitor_cat);
71 #define GST_CAT_DEFAULT cheese_device_monitor_cat
72
73 enum CheeseCameraDeviceMonitorError
74 {
75   CHEESE_CAMERA_DEVICE_MONITOR_ERROR_UNKNOWN,
76   CHEESE_CAMERA_DEVICE_MONITOR_ERROR_ELEMENT_NOT_FOUND
77 };
78
79 typedef struct
80 {
81 #ifdef HAVE_UDEV
82   GUdevClient *client;
83 #else
84   guint filler;
85 #endif /* HAVE_UDEV */
86 } CheeseCameraDeviceMonitorPrivate;
87
88 enum
89 {
90   ADDED,
91   REMOVED,
92   LAST_SIGNAL
93 };
94
95 static guint monitor_signals[LAST_SIGNAL];
96
97 #if 0
98 GQuark
99 cheese_camera_device_monitor_error_quark (void)
100 {
101   return g_quark_from_static_string ("cheese-camera-error-quark");
102 }
103 #endif
104
105 #ifdef HAVE_UDEV
106 static void
107 cheese_camera_device_monitor_added (CheeseCameraDeviceMonitor *monitor,
108                                     GUdevDevice               *udevice)
109 {
110   const char *device_file;
111   const char *product_name;
112   const char *vendor;
113   const char *product;
114   const char *bus;
115   gint        vendor_id   = 0;
116   gint        product_id  = 0;
117   gint        v4l_version = 0;
118
119   const gchar *devpath = g_udev_device_get_property (udevice, "DEVPATH");
120
121   GST_INFO ("Checking udev device '%s'", devpath);
122
123   bus = g_udev_device_get_property (udevice, "ID_BUS");
124   if (g_strcmp0 (bus, "usb") == 0)
125   {
126     vendor = g_udev_device_get_property (udevice, "ID_VENDOR_ID");
127     if (vendor != NULL)
128       vendor_id = g_ascii_strtoll (vendor, NULL, 16);
129     product = g_udev_device_get_property (udevice, "ID_MODEL_ID");
130     if (product != NULL)
131       product_id = g_ascii_strtoll (product, NULL, 16);
132     if (vendor_id == 0 || product_id == 0)
133     {
134       GST_WARNING ("Error getting vendor and product id");
135     }
136     else
137     {
138       GST_INFO ("Found device %04x:%04x, getting capabilities...", vendor_id, product_id);
139     }
140   }
141   else
142   {
143     GST_INFO ("Not an usb device, skipping vendor and model id retrieval");
144   }
145
146   device_file = g_udev_device_get_device_file (udevice);
147   if (device_file == NULL)
148   {
149     GST_WARNING ("Error getting V4L device");
150     return;
151   }
152
153   /* vbi devices support capture capability too, but cannot be used,
154    * so detect them by device name */
155   if (strstr (device_file, "vbi"))
156   {
157     GST_INFO ("Skipping vbi device: %s", device_file);
158     return;
159   }
160
161   v4l_version = g_udev_device_get_property_as_int (udevice, "ID_V4L_VERSION");
162   if (v4l_version == 2 || v4l_version == 1)
163   {
164     const char *caps;
165
166     caps = g_udev_device_get_property (udevice, "ID_V4L_CAPABILITIES");
167     if (caps == NULL || strstr (caps, ":capture:") == NULL)
168     {
169       GST_WARNING ("Device %s seems to not have the capture capability, (radio tuner?)"
170                    "Removing it from device list.", device_file);
171       return;
172     }
173     product_name = g_udev_device_get_property (udevice, "ID_V4L_PRODUCT");
174   }
175   else if (v4l_version == 0)
176   {
177     GST_ERROR ("Fix your udev installation to include v4l_id, ignoring %s", device_file);
178     return;
179   }
180   else
181   {
182     g_assert_not_reached ();
183   }
184
185   g_signal_emit (monitor, monitor_signals[ADDED], 0,
186                  devpath,
187                  device_file,
188                  product_name,
189                  v4l_version);
190 }
191
192 static void
193 cheese_camera_device_monitor_removed (CheeseCameraDeviceMonitor *monitor,
194                                       GUdevDevice               *udevice)
195 {
196   g_signal_emit (monitor, monitor_signals[REMOVED], 0,
197                  g_udev_device_get_property (udevice, "DEVPATH"));
198 }
199
200 static void
201 cheese_camera_device_monitor_uevent_cb (GUdevClient               *client,
202                                         const gchar               *action,
203                                         GUdevDevice               *udevice,
204                                         CheeseCameraDeviceMonitor *monitor)
205 {
206   if (g_str_equal (action, "remove"))
207     cheese_camera_device_monitor_removed (monitor, udevice);
208   else if (g_str_equal (action, "add"))
209     cheese_camera_device_monitor_added (monitor, udevice);
210 }
211
212 /**
213  * cheese_camera_device_monitor_coldplug:
214  * @monitor: a #CheeseCameraDeviceMonitor object.
215  *
216  * Will actively look for plugged in cameras and emit
217  * ::added for those new cameras.
218  * This is only required when your program starts, so as to connect
219  * to those signals before they are emitted.
220  */
221 void
222 cheese_camera_device_monitor_coldplug (CheeseCameraDeviceMonitor *monitor)
223 {
224   CheeseCameraDeviceMonitorPrivate *priv = CHEESE_CAMERA_DEVICE_MONITOR_GET_PRIVATE (monitor);
225   GList                            *devices, *l;
226   gint                              i = 0;
227
228   if (priv->client == NULL)
229     return;
230
231   GST_INFO ("Probing devices with udev...");
232
233   devices = g_udev_client_query_by_subsystem (priv->client, "video4linux");
234
235   /* Initialize camera structures */
236   for (l = devices; l != NULL; l = l->next)
237   {
238     cheese_camera_device_monitor_added (monitor, l->data);
239     g_object_unref (l->data);
240     i++;
241   }
242   g_list_free (devices);
243
244   if (i == 0) GST_WARNING ("No device found");
245 }
246
247 #else /* HAVE_UDEV */
248 void
249 cheese_camera_device_monitor_coldplug (CheeseCameraDeviceMonitor *monitor)
250 {
251   #if 0
252   CheeseCameraDeviceMonitorPrivate *priv = CHEESE_CAMERA_DEVICE_MONITOR_GET_PRIVATE (monitor);
253   struct v4l2_capability            v2cap;
254   struct video_capability           v1cap;
255   int                               fd, ok;
256
257   if ((fd = open (device_path, O_RDONLY | O_NONBLOCK)) < 0)
258   {
259     g_warning ("Failed to open %s: %s", device_path, strerror (errno));
260     return;
261   }
262   ok = ioctl (fd, VIDIOC_QUERYCAP, &v2cap);
263   if (ok < 0)
264   {
265     ok = ioctl (fd, VIDIOCGCAP, &v1cap);
266     if (ok < 0)
267     {
268       g_warning ("Error while probing v4l capabilities for %s: %s",
269                  device_path, strerror (errno));
270       close (fd);
271       return;
272     }
273     g_print ("Detected v4l device: %s\n", v1cap.name);
274     g_print ("Device type: %d\n", v1cap.type);
275     gstreamer_src = "v4lsrc";
276     product_name  = v1cap.name;
277   }
278   else
279   {
280     guint cap = v2cap.capabilities;
281     g_print ("Detected v4l2 device: %s\n", v2cap.card);
282     g_print ("Driver: %s, version: %d\n", v2cap.driver, v2cap.version);
283
284     /* g_print ("Bus info: %s\n", v2cap.bus_info); */ /* Doesn't seem anything useful */
285     g_print ("Capabilities: 0x%08X\n", v2cap.capabilities);
286     if (!(cap & V4L2_CAP_VIDEO_CAPTURE))
287     {
288       g_print ("Device %s seems to not have the capture capability, (radio tuner?)\n"
289                "Removing it from device list.\n", device_path);
290       close (fd);
291       return;
292     }
293     gstreamer_src = "v4l2src";
294     product_name  = (char *) v2cap.card;
295   }
296   close (fd);
297
298   GList *devices, *l;
299
300   g_print ("Probing devices with udev...\n");
301
302   if (priv->client == NULL)
303     return;
304
305   devices = g_udev_client_query_by_subsystem (priv->client, "video4linux");
306
307   /* Initialize camera structures */
308   for (l = devices; l != NULL; l = l->next)
309   {
310     cheese_camera_device_monitor_added (monitor, l->data);
311     g_object_unref (l->data);
312   }
313   g_list_free (devices);
314   #endif
315 }
316
317 #endif /* HAVE_UDEV */
318
319 static void
320 cheese_camera_device_monitor_finalize (GObject *object)
321 {
322 #ifdef HAVE_UDEV
323   CheeseCameraDeviceMonitor *monitor = CHEESE_CAMERA_DEVICE_MONITOR (object);
324   CheeseCameraDeviceMonitorPrivate *priv = CHEESE_CAMERA_DEVICE_MONITOR_GET_PRIVATE (monitor);
325
326   if (priv->client != NULL)
327   {
328     g_object_unref (priv->client);
329     priv->client = NULL;
330   }
331 #endif /* HAVE_UDEV */
332   G_OBJECT_CLASS (cheese_camera_device_monitor_parent_class)->finalize (object);
333 }
334
335 static void
336 cheese_camera_device_monitor_class_init (CheeseCameraDeviceMonitorClass *klass)
337 {
338   GObjectClass *object_class = G_OBJECT_CLASS (klass);
339
340   if (cheese_device_monitor_cat == NULL)
341     GST_DEBUG_CATEGORY_INIT (cheese_device_monitor_cat,
342                              "cheese-device-monitor",
343                              0, "Cheese Camera Device Monitor");
344
345   object_class->finalize = cheese_camera_device_monitor_finalize;
346
347   /**
348    * CheeseCameraDeviceMonitor::added:
349    * @device: A private object representing the newly added camera.
350    * @id: Device unique identifier.
351    * @device: Device file name  (e.g. /dev/video2).
352    * @product_name: Device product name (human readable, intended to be displayed in a UI).
353    * @api_version: Supported video4linux API: 1 for v4l, 2 for v4l2.
354    *
355    * The ::added signal is emitted when a camera is added, or on start-up
356    * after #cheese_camera_device_monitor_colplug is called.
357    **/
358   monitor_signals[ADDED] = g_signal_new ("added", G_OBJECT_CLASS_TYPE (klass),
359                                          G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
360                                          G_STRUCT_OFFSET (CheeseCameraDeviceMonitorClass, added),
361                                          NULL, NULL,
362                                          g_cclosure_marshal_generic,
363                                          G_TYPE_NONE, 4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT);
364
365   /**
366    * CheeseCameraDeviceMonitor::removed:
367    * @device: A private object representing the newly added camera
368    * @id: Device unique identifier.
369    *
370    * The ::removed signal is emitted when a camera is un-plugged, or
371    * disabled on the system.
372    **/
373   monitor_signals[REMOVED] = g_signal_new ("removed", G_OBJECT_CLASS_TYPE (klass),
374                                            G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
375                                            G_STRUCT_OFFSET (CheeseCameraDeviceMonitorClass, removed),
376                                            NULL, NULL,
377                                            g_cclosure_marshal_generic,
378                                            G_TYPE_NONE, 1, G_TYPE_STRING);
379
380   g_type_class_add_private (klass, sizeof (CheeseCameraDeviceMonitorPrivate));
381 }
382
383 static void
384 cheese_camera_device_monitor_init (CheeseCameraDeviceMonitor *monitor)
385 {
386 #ifdef HAVE_UDEV
387   CheeseCameraDeviceMonitorPrivate *priv         = CHEESE_CAMERA_DEVICE_MONITOR_GET_PRIVATE (monitor);
388   const gchar *const                subsystems[] = {"video4linux", NULL};
389
390   priv->client = g_udev_client_new (subsystems);
391   g_signal_connect (G_OBJECT (priv->client), "uevent",
392                     G_CALLBACK (cheese_camera_device_monitor_uevent_cb), monitor);
393 #endif /* HAVE_UDEV */
394 }
395
396 /**
397  * cheese_camera_device_monitor_new:
398  *
399  * Returns a new #CheeseCameraDeviceMonitor object.
400  *
401  * Return value: a new #CheeseCameraDeviceMonitor object.
402  **/
403 CheeseCameraDeviceMonitor *
404 cheese_camera_device_monitor_new (void)
405 {
406   return g_object_new (CHEESE_TYPE_CAMERA_DEVICE_MONITOR, NULL);
407 }
408
409 /*
410  * vim: sw=2 ts=8 cindent noai bs=2
411  */