Hide keyboard shortcuts

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 

6 

7import os 

8 

9from xpra.util import engs, envbool 

10from xpra.os_util import is_Ubuntu, is_Debian 

11from xpra.log import Logger 

12 

13log = Logger("webcam") 

14 

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())) 

19 

20 

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 

31 

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 

43 

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 {} 

50 

51 

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 

88 

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 

117 

118 

119_watch_manager = None 

120_notifier = None 

121 

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") 

125 

126 

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) 

137 

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) 

142 

143 def process_IN_DELETE(self, event): 

144 _fire_video_device_change(False, event.pathname) 

145 

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() 

158 

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