Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/platform/xposix/webcam.py : 33%
Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# This file is part of Xpra.
2# Copyright (C) 2016-2019 Antoine Martin <antoine@xpra.org>
3# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
4# later version. See the file COPYING for details.
5#pylint: disable-msg=E1101
7import os
9from xpra.util import engs, envbool
10from xpra.os_util import is_Ubuntu, is_Debian
11from xpra.log import Logger
13log = Logger("webcam")
15#on Debian and Ubuntu, the v4l2loopback device is created with exclusive_caps=1,
16#so we cannot check the devices caps for the "VIDEO_CAPTURE" flag.
17#see https://xpra.org/trac/ticket/1596
18CHECK_VIRTUAL_CAPTURE = envbool("XPRA_CHECK_VIRTUAL_CAPTURE", not (is_Ubuntu() or is_Debian()))
21def _can_capture_video(dev_file, dev_info):
22 if not dev_info:
23 return False
24 caps = dev_info.get("capabilities", [])
25 if "DEVICE_CAPS" in caps:
26 caps = dev_info.get("device_caps", [])
27 if not "VIDEO_CAPTURE" in caps:
28 log("device %s does not support video capture, capabilities=%s", dev_file, caps)
29 return False
30 return True
32v4l2_virtual_dir = "/sys/devices/virtual/video4linux"
33def check_virtual_dir(warn=True):
34 global v4l2_virtual_dir
35 if not os.path.exists(v4l2_virtual_dir) or not os.path.isdir(v4l2_virtual_dir):
36 if warn:
37 log.warn("Warning: webcam forwarding is disabled")
38 log.warn(" the virtual video directory '%s' was not found", v4l2_virtual_dir)
39 log.warn(" make sure that the 'v4l2loopback' kernel module is installed and loaded")
40 log.warn(" or use the 'webcam=no' option")
41 return False
42 return True
44def query_video_device(device):
45 try:
46 from xpra.codecs.v4l2.pusher import query_video_device as v4l_query_video_device
47 return v4l_query_video_device(device)
48 except ImportError:
49 return {}
52def get_virtual_video_devices(capture_only=True) -> dict:
53 log("get_virtual_video_devices(%s) CHECK_VIRTUAL_CAPTURE=%s", capture_only, CHECK_VIRTUAL_CAPTURE)
54 if not check_virtual_dir(False):
55 return {}
56 contents = os.listdir(v4l2_virtual_dir)
57 devices = {}
58 for f in sorted(contents):
59 if not f.startswith("video"):
60 continue
61 try:
62 no_str = f[len("video"):]
63 no = int(no_str)
64 assert no>=0
65 except (TypeError, ValueError, AssertionError):
66 continue
67 dev_file = "/dev/%s" % f
68 dev_info = query_video_device(dev_file)
69 if CHECK_VIRTUAL_CAPTURE and capture_only and not _can_capture_video(dev_file, dev_info):
70 continue
71 info = {"device" : dev_file}
72 info.update(dev_info)
73 if "card" not in dev_info:
74 #look up the name from the v4l2 virtual dir:
75 dev_dir = os.path.join(v4l2_virtual_dir, f)
76 if not os.path.isdir(dev_dir):
77 continue
78 dev_name = os.path.join(dev_dir, "name")
79 try:
80 name = open(dev_name).read().replace("\n", "")
81 info["card"] = name
82 except OSError:
83 pass
84 devices[no] = info
85 log("devices: %s", devices)
86 log("found %i virtual video device%s", len(devices), engs(devices))
87 return devices
89def get_all_video_devices(capture_only=True):
90 contents = os.listdir("/dev")
91 devices = {}
92 device_paths = set()
93 for f in contents:
94 if not f.startswith("video"):
95 continue
96 dev_file = "/dev/%s" % f
97 try:
98 dev_file = os.readlink(dev_file)
99 except OSError:
100 pass
101 if dev_file in device_paths:
102 continue
103 device_paths.add(dev_file)
104 try:
105 no_str = f[len("video"):]
106 no = int(no_str)
107 assert no>=0
108 except (TypeError, ValueError, AssertionError):
109 continue
110 dev_info = query_video_device(dev_file)
111 if capture_only and not _can_capture_video(dev_file, dev_info):
112 continue
113 info = {"device" : dev_file}
114 info.update(dev_info)
115 devices[no] = info
116 return devices
119_watch_manager = None
120_notifier = None
122def _video_device_file_filter(event):
123 # return True to stop processing of event (to "stop chaining")
124 return not event.pathname.startswith("/dev/video")
127def add_video_device_change_callback(callback):
128 from xpra.platform.webcam import _video_device_change_callbacks, _fire_video_device_change
129 global _watch_manager, _notifier
130 try:
131 import pyinotify
132 except ImportError as e:
133 log.error("Error: cannot watch for video device changes without pyinotify:")
134 log.error(" %s", e)
135 return
136 log("add_video_device_change_callback(%s) pyinotify=%s", callback, pyinotify)
138 if not _watch_manager:
139 class EventHandler(pyinotify.ProcessEvent):
140 def process_IN_CREATE(self, event):
141 _fire_video_device_change(True, event.pathname)
143 def process_IN_DELETE(self, event):
144 _fire_video_device_change(False, event.pathname)
146 _watch_manager = pyinotify.WatchManager()
147 mask = pyinotify.IN_DELETE | pyinotify.IN_CREATE #@UndefinedVariable
148 handler = EventHandler(pevent=_video_device_file_filter)
149 _notifier = pyinotify.ThreadedNotifier(_watch_manager, handler)
150 _notifier.setDaemon(True)
151 wdd = _watch_manager.add_watch('/dev', mask)
152 log("watching for video device changes in /dev")
153 log("notifier=%s, watch=%s", _notifier, wdd)
154 _notifier.start()
155 _video_device_change_callbacks.append(callback)
156 #for running standalone:
157 #notifier.loop()
159def remove_video_device_change_callback(callback):
160 from xpra.platform.webcam import _video_device_change_callbacks
161 global _watch_manager, _notifier
162 if not _watch_manager:
163 log.error("Error: cannot remove video device change callback, no watch manager!")
164 return
165 if callback not in _video_device_change_callbacks:
166 log.error("Error: video device change callback not found, cannot remove it!")
167 return
168 log("remove_video_device_change_callback(%s)", callback)
169 _video_device_change_callbacks.remove(callback)
170 if not _video_device_change_callbacks:
171 log("last video device change callback removed, closing the watch manager")
172 #we can close it:
173 try:
174 _notifier.stop()
175 except Exception:
176 pass
177 _notifier = None
178 try:
179 _watch_manager.close()
180 except Exception:
181 pass
182 _watch_manager = None