1 /* GStreamer
2  * Copyright (C) 2015 Руслан Ижбулатов <lrn1986@gmail.com>
3  *
4  * ksdeviceprovider.c: Kernel Streaming device probing and monitoring
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, USA.
20  */
21 
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25 
26 #include "gstksvideosrc.h"
27 #include "ksdeviceprovider.h"
28 
29 #include <string.h>
30 
31 #include <dbt.h>                /* for DBT_* consts and [_]DEV_* structs */
32 #include <devguid.h>            /* for GUID_DEVCLASS_WCEUSBS */
33 #include <setupapi.h>           /* for DIGCF_ALLCLASSES */
34 
35 #include <gst/gst.h>
36 
37 #include "kshelpers.h"
38 #include "ksvideohelpers.h"
39 
40 
41 GST_DEBUG_CATEGORY_EXTERN (gst_ks_debug);
42 #define GST_CAT_DEFAULT gst_ks_debug
43 
44 
45 static GstDevice *gst_ks_device_new (guint id,
46     const gchar * device_name, GstCaps * caps, const gchar * device_path,
47     GstKsDeviceType type);
48 
49 G_DEFINE_TYPE (GstKsDeviceProvider, gst_ks_device_provider,
50     GST_TYPE_DEVICE_PROVIDER);
51 
52 static GList *gst_ks_device_provider_probe (GstDeviceProvider * provider);
53 static gboolean gst_ks_device_provider_start (GstDeviceProvider * provider);
54 static void gst_ks_device_provider_stop (GstDeviceProvider * provider);
55 
56 static void
gst_ks_device_provider_class_init(GstKsDeviceProviderClass * klass)57 gst_ks_device_provider_class_init (GstKsDeviceProviderClass * klass)
58 {
59   GstDeviceProviderClass *dm_class = GST_DEVICE_PROVIDER_CLASS (klass);
60 
61   dm_class->probe = gst_ks_device_provider_probe;
62   dm_class->start = gst_ks_device_provider_start;
63   dm_class->stop = gst_ks_device_provider_stop;
64 
65   gst_device_provider_class_set_static_metadata (dm_class,
66       "KernelStreaming Device Provider", "Sink/Source/Audio/Video",
67       "List and provide KernelStreaming source and sink devices",
68       "Руслан Ижбулатов <lrn1986@gmail.com>");
69 }
70 
71 static void
gst_ks_device_provider_init(GstKsDeviceProvider * self)72 gst_ks_device_provider_init (GstKsDeviceProvider * self)
73 {
74 }
75 
76 static GstDevice *
new_video_source(const KsDeviceEntry * info)77 new_video_source (const KsDeviceEntry * info)
78 {
79   GstCaps *caps;
80   HANDLE filter_handle;
81   GList *media_types;
82   GList *cur;
83 
84   g_assert (info->path != NULL);
85 
86   caps = gst_caps_new_empty ();
87 
88   filter_handle = CreateFile (info->path,
89       GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
90       FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
91   if (!ks_is_valid_handle (filter_handle))
92     goto error;
93 
94   media_types = ks_video_probe_filter_for_caps (filter_handle);
95 
96   for (cur = media_types; cur != NULL; cur = cur->next) {
97     KsVideoMediaType *media_type = cur->data;
98 
99     gst_caps_append (caps, gst_caps_copy (media_type->translated_caps));
100 
101     ks_video_media_type_free (media_type);
102   }
103 
104   CloseHandle (filter_handle);
105   g_list_free (media_types);
106 
107   return gst_ks_device_new (info->index, info->name,
108       caps, info->path, GST_KS_DEVICE_TYPE_VIDEO_SOURCE);
109 error:
110   gst_caps_unref (caps);
111   return NULL;
112 }
113 
114 static GList *
gst_ks_device_provider_probe(GstDeviceProvider * provider)115 gst_ks_device_provider_probe (GstDeviceProvider * provider)
116 {
117   /*GstKsDeviceProvider *self = GST_KS_DEVICE_PROVIDER (provider); */
118   GList *devices, *cur;
119   GList *result;
120 
121   result = NULL;
122 
123   devices = ks_enumerate_devices (&KSCATEGORY_VIDEO, &KSCATEGORY_CAPTURE);
124   if (devices == NULL)
125     return result;
126 
127   devices = ks_video_device_list_sort_cameras_first (devices);
128 
129   for (cur = devices; cur != NULL; cur = cur->next) {
130     GstDevice *source;
131     KsDeviceEntry *entry = cur->data;
132 
133     source = new_video_source (entry);
134     if (source)
135       result = g_list_prepend (result, gst_object_ref_sink (source));
136 
137     ks_device_entry_free (entry);
138   }
139 
140   result = g_list_reverse (result);
141 
142   g_list_free (devices);
143 
144   return result;
145 }
146 
147 static const gchar *
get_dev_type(DEV_BROADCAST_HDR * dev_msg_header)148 get_dev_type (DEV_BROADCAST_HDR * dev_msg_header)
149 {
150   switch (dev_msg_header->dbch_devicetype) {
151     case DBT_DEVTYP_DEVICEINTERFACE:
152       return "Device interface class";
153     case DBT_DEVTYP_HANDLE:
154       return "Filesystem handle";
155     case DBT_DEVTYP_OEM:
156       return "OEM or IHV device type";
157     case DBT_DEVTYP_PORT:
158       return "Port device";
159     case DBT_DEVTYP_VOLUME:
160       return "Logical volume";
161     default:
162       return "Unknown device type";
163   }
164 }
165 
166 #define KS_MSG_WINDOW_CLASS "gst_winks_device_msg_window"
167 #define WM_QUITTHREAD (WM_USER + 0)
168 
169 static void unreg_msg_window_class (ATOM class_id, const char *class_name,
170     HINSTANCE inst);
171 
172 static HDEVNOTIFY
register_device_interface(GstKsDeviceProvider * self,GUID interface_class_guid,HWND window_handle)173 register_device_interface (GstKsDeviceProvider * self,
174     GUID interface_class_guid, HWND window_handle)
175 {
176   DEV_BROADCAST_DEVICEINTERFACE notification_filter;
177   HDEVNOTIFY notification_handle;
178   DWORD error;
179 
180   memset (&notification_filter, 0, sizeof (notification_filter));
181   notification_filter.dbcc_size = sizeof (notification_filter);
182   notification_filter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
183   notification_filter.dbcc_classguid = interface_class_guid;
184 
185   notification_handle = RegisterDeviceNotificationW (window_handle,
186       &notification_filter,
187       DEVICE_NOTIFY_WINDOW_HANDLE | DEVICE_NOTIFY_ALL_INTERFACE_CLASSES);
188   error = GetLastError ();
189 
190   if (notification_handle == NULL)
191     GST_ERROR_OBJECT (self,
192         "Could not register for a device notification: %lu", error);
193 
194   return notification_handle;
195 }
196 
197 static INT_PTR WINAPI
msg_window_message_proc(HWND window_handle,UINT message,WPARAM wparam,LPARAM lparam)198 msg_window_message_proc (HWND window_handle, UINT message,
199     WPARAM wparam, LPARAM lparam)
200 {
201   LRESULT result;
202   LONG_PTR user_data;
203   GstKsDeviceProvider *self;
204   PDEV_BROADCAST_DEVICEINTERFACE bcdi;
205   DEV_BROADCAST_HDR *dev_msg_header;
206   struct _DEV_BROADCAST_USERDEFINED *user_dev_msg_header;
207   CREATESTRUCT *create_data;
208   DWORD error;
209   HINSTANCE inst;
210   GstKsDevice *dev;
211   GstDevice *source;
212   GList *item;
213   GstDeviceProvider *provider;
214   GList *devices;
215   gchar *guid_str;
216 
217   result = TRUE;
218 
219   switch (message) {
220     case WM_CREATE:
221       create_data = (CREATESTRUCT *) lparam;
222 
223       if (create_data->lpCreateParams == NULL) {
224         /* DO SOMETHING!! */
225       }
226 
227       self = GST_KS_DEVICE_PROVIDER (create_data->lpCreateParams);
228 
229       SetLastError (0);
230       SetWindowLongPtr (window_handle, GWLP_USERDATA, (LONG_PTR) self);
231       error = GetLastError ();
232       if (error != NO_ERROR) {
233         GST_ERROR_OBJECT (self,
234             "Could not attach user data to the message window: %lu", error);
235         DestroyWindow (window_handle);
236         inst = (HINSTANCE) GetModuleHandle (NULL);
237         GST_OBJECT_LOCK (self);
238         unreg_msg_window_class (self->message_window_class, KS_MSG_WINDOW_CLASS,
239             inst);
240         self->message_window_class = 0;
241         GST_OBJECT_UNLOCK (self);
242       }
243       result = FALSE;
244       break;
245     case WM_DEVICECHANGE:
246       GST_DEBUG ("WM_DEVICECHANGE for %x %x", (unsigned int) wparam,
247           (unsigned int) lparam);
248 
249       user_data = GetWindowLongPtr (window_handle, GWLP_USERDATA);
250       if (user_data == 0)
251         break;
252 
253       self = GST_KS_DEVICE_PROVIDER (user_data);
254       provider = GST_DEVICE_PROVIDER (self);
255 
256       dev_msg_header = (DEV_BROADCAST_HDR *) lparam;
257 
258       switch (wparam) {
259         case DBT_CONFIGCHANGECANCELED:
260           GST_DEBUG_OBJECT (self, "DBT_CONFIGCHANGECANCELED for %s",
261               get_dev_type (dev_msg_header));
262           break;
263         case DBT_CONFIGCHANGED:
264           GST_DEBUG_OBJECT (self, "DBT_CONFIGCHANGED for %s",
265               get_dev_type (dev_msg_header));
266           break;
267         case DBT_CUSTOMEVENT:
268           GST_DEBUG_OBJECT (self, "DBT_CUSTOMEVENT for %s",
269               get_dev_type (dev_msg_header));
270           break;
271         case DBT_DEVICEARRIVAL:
272           GST_DEBUG_OBJECT (self, "DBT_DEVICEARRIVAL for %s",
273               get_dev_type (dev_msg_header));
274 
275           if (dev_msg_header->dbch_devicetype != DBT_DEVTYP_DEVICEINTERFACE)
276             break;
277 
278           bcdi = (PDEV_BROADCAST_DEVICEINTERFACE) lparam;
279           guid_str = ks_guid_to_string (&bcdi->dbcc_classguid);
280           GST_INFO_OBJECT (self, "New device, class interface GUID %s, path %s",
281               guid_str, bcdi->dbcc_name);
282           g_free (guid_str);
283           break;
284         case DBT_DEVICEQUERYREMOVE:
285           GST_DEBUG_OBJECT (self, "DBT_DEVICEQUERYREMOVE for %s",
286               get_dev_type (dev_msg_header));
287           break;
288         case DBT_DEVICEQUERYREMOVEFAILED:
289           GST_DEBUG_OBJECT (self, "DBT_DEVICEQUERYREMOVEFAILED for %s",
290               get_dev_type (dev_msg_header));
291           break;
292         case DBT_DEVICEREMOVECOMPLETE:
293           GST_DEBUG_OBJECT (self, "DBT_DEVICEREMOVECOMPLETE for %s",
294               get_dev_type (dev_msg_header));
295 
296           if (dev_msg_header->dbch_devicetype != DBT_DEVTYP_DEVICEINTERFACE)
297             break;
298 
299           bcdi = (PDEV_BROADCAST_DEVICEINTERFACE) lparam;
300 
301           guid_str = ks_guid_to_string (&bcdi->dbcc_classguid);
302           GST_INFO_OBJECT (self,
303               "Removed device, class interface GUID %s, path %s", guid_str,
304               bcdi->dbcc_name);
305           g_free (guid_str);
306           break;
307         case DBT_DEVICEREMOVEPENDING:
308           GST_DEBUG_OBJECT (self, "DBT_DEVICEREMOVEPENDING for %s",
309               get_dev_type (dev_msg_header));
310           break;
311         case DBT_DEVICETYPESPECIFIC:
312           GST_DEBUG_OBJECT (self, "DBT_DEVICETYPESPECIFIC for %s",
313               get_dev_type (dev_msg_header));
314           break;
315         case DBT_DEVNODES_CHANGED:
316           GST_DEBUG_OBJECT (self, "DBT_DEVNODES_CHANGED for %s",
317               get_dev_type (dev_msg_header));
318           break;
319         case DBT_QUERYCHANGECONFIG:
320           GST_DEBUG_OBJECT (self, "DBT_QUERYCHANGECONFIG for %s",
321               get_dev_type (dev_msg_header));
322           break;
323         case DBT_USERDEFINED:
324           user_dev_msg_header = (struct _DEV_BROADCAST_USERDEFINED *) lparam;
325           dev_msg_header =
326               (DEV_BROADCAST_HDR *) & user_dev_msg_header->dbud_dbh;
327           GST_DEBUG_OBJECT (self, "DBT_USERDEFINED for %s: %s",
328               get_dev_type (dev_msg_header), user_dev_msg_header->dbud_szName);
329           break;
330         default:
331           break;
332       }
333 
334       switch (wparam) {
335         case DBT_DEVICEARRIVAL:
336           if (dev_msg_header->dbch_devicetype != DBT_DEVTYP_DEVICEINTERFACE)
337             break;
338 
339           bcdi = (PDEV_BROADCAST_DEVICEINTERFACE) lparam;
340 
341           /* Since both video and audio capture device declare KSCATEGORY_CAPTURE, we filter on
342              KSCATEGORY_VIDEO here. To add audio support we should accept also KSCATEGORY_AUDIO. */
343           if (!IsEqualGUID (&bcdi->dbcc_classguid, &KSCATEGORY_VIDEO))
344             break;
345 
346           devices =
347               ks_enumerate_devices (&bcdi->dbcc_classguid, &KSCATEGORY_CAPTURE);
348           if (devices == NULL)
349             break;
350 
351           source = NULL;
352           for (item = devices; item != NULL; item = item->next) {
353             KsDeviceEntry *entry = item->data;
354             GST_DEBUG_OBJECT (self, "Listed device %s = %s", entry->name,
355                 entry->path);
356 
357             if ((source == NULL) &&
358                 (g_ascii_strcasecmp (entry->path, bcdi->dbcc_name) == 0))
359               source = new_video_source (entry);        /* Or audio source, not implemented yet */
360 
361             ks_device_entry_free (entry);
362           }
363 
364           if (source)
365             gst_device_provider_device_add (GST_DEVICE_PROVIDER (self), source);
366 
367           g_list_free (devices);
368           break;
369         case DBT_DEVICEREMOVECOMPLETE:
370           if (dev_msg_header->dbch_devicetype != DBT_DEVTYP_DEVICEINTERFACE)
371             break;
372 
373           bcdi = (PDEV_BROADCAST_DEVICEINTERFACE) lparam;
374           dev = NULL;
375 
376           GST_OBJECT_LOCK (self);
377           for (item = provider->devices; item; item = item->next) {
378             dev = item->data;
379 
380             if (g_ascii_strcasecmp (dev->path, bcdi->dbcc_name) == 0) {
381               guid_str = gst_device_get_display_name (GST_DEVICE (dev));
382               GST_INFO_OBJECT (self, "Device matches to %s", guid_str);
383               g_free (guid_str);
384               gst_object_ref (dev);
385               break;
386             }
387             dev = NULL;
388           }
389           GST_OBJECT_UNLOCK (self);
390 
391           if (dev) {
392             gst_device_provider_device_remove (GST_DEVICE_PROVIDER (self),
393                 GST_DEVICE (dev));
394             gst_object_unref (dev);
395           }
396           break;
397         default:
398           break;
399       }
400       result = FALSE;
401       break;
402     case WM_DESTROY:
403       PostQuitMessage (0);
404       result = FALSE;
405       break;
406     case WM_QUITTHREAD:
407       DestroyWindow (window_handle);
408       result = FALSE;
409       break;
410     default:
411       result = DefWindowProc (window_handle, message, wparam, lparam);
412       break;
413   }
414 
415   return result;
416 }
417 
418 static ATOM
reg_msg_window_class(const char * class_name,HINSTANCE inst)419 reg_msg_window_class (const char *class_name, HINSTANCE inst)
420 {
421   WNDCLASSEXA classex;
422 
423   memset (&classex, 0, sizeof (classex));
424   classex.cbSize = sizeof (classex);
425   classex.hInstance = inst;
426   classex.lpfnWndProc = (WNDPROC) msg_window_message_proc;
427   classex.lpszClassName = class_name;
428 
429   return RegisterClassExA (&classex);
430 }
431 
432 static void
unreg_msg_window_class(ATOM class_id,const char * class_name,HINSTANCE inst)433 unreg_msg_window_class (ATOM class_id, const char *class_name, HINSTANCE inst)
434 {
435   if (class_id != 0)
436     UnregisterClassA ((LPCSTR) MAKELPARAM (class_id, 0), inst);
437   else
438     UnregisterClassA (class_name, inst);
439 }
440 
441 static gpointer
ks_provider_msg_window_thread(gpointer dat)442 ks_provider_msg_window_thread (gpointer dat)
443 {
444   GstKsDeviceProvider *self;
445   MSG msg;
446   ATOM wnd_class;
447   BOOL message_status;
448   HINSTANCE inst;
449   HANDLE msg_window = NULL;
450   DWORD error;
451   HDEVNOTIFY devnotify = NULL;
452 
453   g_return_val_if_fail (dat != NULL, NULL);
454 
455   self = GST_KS_DEVICE_PROVIDER (dat);
456 
457   GST_DEBUG_OBJECT (self, "Entering message window thread: %p",
458       g_thread_self ());
459 
460   GST_OBJECT_LOCK (self);
461   wnd_class = self->message_window_class;
462   GST_OBJECT_UNLOCK (self);
463 
464   inst = (HINSTANCE) GetModuleHandle (NULL);
465 
466   msg_window = CreateWindowExA (0,
467       wnd_class != 0 ? (LPCSTR) MAKELPARAM (wnd_class, 0) : KS_MSG_WINDOW_CLASS,
468       "", 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, inst, self);
469   error = GetLastError ();
470 
471   if (msg_window == NULL) {
472     GST_ERROR_OBJECT (self, "Could not create a message window: %lu", error);
473     GST_OBJECT_LOCK (self);
474     unreg_msg_window_class (wnd_class, KS_MSG_WINDOW_CLASS, inst);
475     self->message_window_class = 0;
476     SetEvent (self->wakeup_event);
477     GST_OBJECT_UNLOCK (self);
478     return NULL;
479   }
480 
481   GST_OBJECT_LOCK (self);
482   self->message_window = msg_window;
483 
484   devnotify =
485       register_device_interface (self, GUID_DEVCLASS_WCEUSBS, msg_window);
486   if (devnotify == NULL) {
487     DestroyWindow (msg_window);
488     unreg_msg_window_class (wnd_class, KS_MSG_WINDOW_CLASS, inst);
489     self->message_window_class = 0;
490     self->message_window = NULL;
491     SetEvent (self->wakeup_event);
492     GST_OBJECT_UNLOCK (self);
493     return NULL;
494   }
495 
496   self->device_notify_handle = devnotify;
497   SetEvent (self->wakeup_event);
498   GST_OBJECT_UNLOCK (self);
499 
500   while ((message_status = GetMessage (&msg, NULL, 0, 0)) != 0) {
501     if (message_status < 0 || msg.message == WM_QUIT)
502       break;
503     TranslateMessage (&msg);
504     DispatchMessage (&msg);
505   }
506 
507   GST_DEBUG_OBJECT (self, "Exiting internal window thread: %p",
508       g_thread_self ());
509 
510   return NULL;
511 }
512 
513 static gboolean
gst_ks_device_provider_start(GstDeviceProvider * provider)514 gst_ks_device_provider_start (GstDeviceProvider * provider)
515 {
516   ATOM wnd_class = 0;
517   HINSTANCE inst;
518   HANDLE wakeup_event;
519   HWND message_window;
520   DWORD error;
521   GList *devs;
522   GList *dev;
523   GstKsDeviceProvider *self = GST_KS_DEVICE_PROVIDER (provider);
524 
525   GST_OBJECT_LOCK (self);
526   g_assert (self->message_window == NULL);
527   GST_OBJECT_UNLOCK (self);
528 
529   /* We get notifications on *change*, so before we get to that,
530    * we need to obtain a complete list of devices, which we will
531    * watch for changes.
532    */
533   devs = gst_ks_device_provider_probe (provider);
534   for (dev = devs; dev; dev = dev->next) {
535     if (dev->data)
536       gst_device_provider_device_add (provider, (GstDevice *) dev->data);
537   }
538   g_list_free (devs);
539 
540   inst = (HINSTANCE) GetModuleHandle (NULL);
541 
542   wakeup_event = CreateEvent (NULL, TRUE, FALSE, NULL);
543   error = GetLastError ();
544   if (wakeup_event == NULL) {
545     GST_OBJECT_LOCK (self);
546     GST_ERROR_OBJECT (self, "Could not create a wakeup event: %lu", error);
547     GST_OBJECT_UNLOCK (self);
548     return FALSE;
549   }
550 
551   wnd_class = reg_msg_window_class (KS_MSG_WINDOW_CLASS, inst);
552   error = GetLastError ();
553 
554   if ((wnd_class == 0) && (error != ERROR_CLASS_ALREADY_EXISTS)) {
555     GST_ERROR_OBJECT (self,
556         "Could not register message window class: %lu", error);
557     CloseHandle (wakeup_event);
558     return FALSE;
559   }
560 
561   GST_OBJECT_LOCK (self);
562   self->message_window_class = wnd_class;
563   self->wakeup_event = wakeup_event;
564 
565   self->message_thread =
566       g_thread_new ("ks-device-provider-message-window-thread",
567       (GThreadFunc) ks_provider_msg_window_thread, self);
568   if (self->message_thread == NULL) {
569     GST_ERROR_OBJECT (self, "Could not create message window thread");
570     unreg_msg_window_class (wnd_class, KS_MSG_WINDOW_CLASS, inst);
571     self->message_window_class = 0;
572     CloseHandle (self->wakeup_event);
573     GST_OBJECT_UNLOCK (self);
574     return FALSE;
575   }
576   GST_OBJECT_UNLOCK (self);
577 
578   if (WaitForSingleObject (wakeup_event, INFINITE) != WAIT_OBJECT_0) {
579     GST_ERROR_OBJECT (self,
580         "Failed to wait for the message thread to initialize");
581   }
582 
583   GST_OBJECT_LOCK (self);
584   CloseHandle (self->wakeup_event);
585   self->wakeup_event = NULL;
586   message_window = self->message_window;
587   GST_OBJECT_UNLOCK (self);
588 
589   if (message_window == NULL)
590     return FALSE;
591 
592   return TRUE;
593 }
594 
595 static void
gst_ks_device_provider_stop(GstDeviceProvider * provider)596 gst_ks_device_provider_stop (GstDeviceProvider * provider)
597 {
598   HINSTANCE inst;
599   GThread *message_thread;
600   GstKsDeviceProvider *self = GST_KS_DEVICE_PROVIDER (provider);
601 
602   GST_OBJECT_LOCK (self);
603 
604   g_assert (self->message_window != NULL);
605 
606   UnregisterDeviceNotification (self->device_notify_handle);
607   self->device_notify_handle = NULL;
608   PostMessage (self->message_window, WM_QUITTHREAD, 0, 0);
609   message_thread = self->message_thread;
610   GST_OBJECT_UNLOCK (self);
611 
612   g_thread_join (message_thread);
613 
614   GST_OBJECT_LOCK (self);
615   self->message_window = NULL;
616   self->message_thread = NULL;
617 
618   inst = (HINSTANCE) GetModuleHandle (NULL);
619 
620   unreg_msg_window_class (self->message_window_class, KS_MSG_WINDOW_CLASS,
621       inst);
622 
623   self->message_window_class = 0;
624   GST_OBJECT_UNLOCK (self);
625 }
626 
627 enum
628 {
629   PROP_PATH = 1
630 };
631 
632 G_DEFINE_TYPE (GstKsDevice, gst_ks_device, GST_TYPE_DEVICE);
633 
634 static void gst_ks_device_get_property (GObject * object, guint prop_id,
635     GValue * value, GParamSpec * pspec);
636 static void gst_ks_device_set_property (GObject * object, guint prop_id,
637     const GValue * value, GParamSpec * pspec);
638 static void gst_ks_device_finalize (GObject * object);
639 static GstElement *gst_ks_device_create_element (GstDevice * device,
640     const gchar * name);
641 static gboolean gst_ks_device_reconfigure_element (GstDevice * device,
642     GstElement * element);
643 
644 static void
gst_ks_device_class_init(GstKsDeviceClass * klass)645 gst_ks_device_class_init (GstKsDeviceClass * klass)
646 {
647   GstDeviceClass *dev_class = GST_DEVICE_CLASS (klass);
648   GObjectClass *object_class = G_OBJECT_CLASS (klass);
649 
650   dev_class->create_element = gst_ks_device_create_element;
651   dev_class->reconfigure_element = gst_ks_device_reconfigure_element;
652 
653   object_class->get_property = gst_ks_device_get_property;
654   object_class->set_property = gst_ks_device_set_property;
655   object_class->finalize = gst_ks_device_finalize;
656 
657   g_object_class_install_property (object_class, PROP_PATH,
658       g_param_spec_string ("path", "System device path",
659           "The system path to the device", "",
660           G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
661 }
662 
663 static void
gst_ks_device_init(GstKsDevice * device)664 gst_ks_device_init (GstKsDevice * device)
665 {
666 }
667 
668 static void
gst_ks_device_finalize(GObject * object)669 gst_ks_device_finalize (GObject * object)
670 {
671   GstKsDevice *device = GST_KS_DEVICE (object);
672 
673   g_free (device->path);
674 
675   G_OBJECT_CLASS (gst_ks_device_parent_class)->finalize (object);
676 }
677 
678 static GstElement *
gst_ks_device_create_element(GstDevice * device,const gchar * name)679 gst_ks_device_create_element (GstDevice * device, const gchar * name)
680 {
681   GstKsDevice *ks_dev = GST_KS_DEVICE (device);
682   GstElement *elem;
683 
684   elem = gst_element_factory_make (ks_dev->element, name);
685   g_object_set (elem, "device-path", ks_dev->path, NULL);
686 
687   return elem;
688 }
689 
690 static gboolean
gst_ks_device_reconfigure_element(GstDevice * device,GstElement * element)691 gst_ks_device_reconfigure_element (GstDevice * device, GstElement * element)
692 {
693   GstKsDevice *ks_dev = GST_KS_DEVICE (device);
694 
695   if (!strcmp (ks_dev->element, "ksvideosrc")) {
696     if (!GST_IS_KS_VIDEO_SRC (element))
697       return FALSE;
698 /*
699   } else if (!strcmp (ks_dev->element, "ksaudiosrc")) {
700     if (!GST_IS_KS_AUDIO_SRC (element))
701       return FALSE;
702   } else if (!strcmp (ks_dev->element, "ksaudiosink")) {
703     if (!GST_IS_KS_AUDIO_SINK (element))
704       return FALSE;
705 */
706   } else {
707     g_assert_not_reached ();
708   }
709 
710   g_object_set (element, "path", ks_dev->path, NULL);
711 
712   return TRUE;
713 }
714 
715 static GstDevice *
gst_ks_device_new(guint device_index,const gchar * device_name,GstCaps * caps,const gchar * device_path,GstKsDeviceType type)716 gst_ks_device_new (guint device_index, const gchar * device_name,
717     GstCaps * caps, const gchar * device_path, GstKsDeviceType type)
718 {
719   GstKsDevice *gstdev;
720   const gchar *element = NULL;
721   const gchar *klass = NULL;
722 
723   g_return_val_if_fail (device_name, NULL);
724   g_return_val_if_fail (device_path, NULL);
725   g_return_val_if_fail (caps, NULL);
726 
727 
728   switch (type) {
729     case GST_KS_DEVICE_TYPE_VIDEO_SOURCE:
730       element = "ksvideosrc";
731       klass = "Video/Source";
732       break;
733     case GST_KS_DEVICE_TYPE_AUDIO_SOURCE:
734       element = "ksaudiosrc";
735       klass = "Audio/Source";
736       break;
737     case GST_KS_DEVICE_TYPE_AUDIO_SINK:
738       element = "ksaudiosink";
739       klass = "Audio/Sink";
740       break;
741     default:
742       g_assert_not_reached ();
743       break;
744   }
745 
746 
747   gstdev = g_object_new (GST_TYPE_KS_DEVICE,
748       "display-name", device_name, "caps", caps, "device-class", klass,
749       "path", device_path, NULL);
750 
751   gstdev->type = type;
752   gstdev->device_index = device_index;
753   gstdev->path = g_strdup (device_path);
754   gstdev->element = element;
755 
756   return GST_DEVICE (gstdev);
757 }
758 
759 
760 static void
gst_ks_device_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)761 gst_ks_device_get_property (GObject * object, guint prop_id,
762     GValue * value, GParamSpec * pspec)
763 {
764   GstKsDevice *device;
765 
766   device = GST_KS_DEVICE_CAST (object);
767 
768   switch (prop_id) {
769     case PROP_PATH:
770       g_value_set_string (value, device->path);
771       break;
772     default:
773       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
774       break;
775   }
776 }
777 
778 
779 static void
gst_ks_device_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)780 gst_ks_device_set_property (GObject * object, guint prop_id,
781     const GValue * value, GParamSpec * pspec)
782 {
783   GstKsDevice *device;
784 
785   device = GST_KS_DEVICE_CAST (object);
786 
787   switch (prop_id) {
788     case PROP_PATH:
789       device->path = g_value_dup_string (value);
790       break;
791     default:
792       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
793       break;
794   }
795 }
796