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# -*- coding: utf-8 -*- 

2# This file is part of Xpra. 

3# Copyright (C) 2017-2021 Antoine Martin <antoine@xpra.org> 

4# Xpra is released under the terms of the GNU GPL v2, or, at your option, any 

5# later version. See the file COPYING for details. 

6 

7import re 

8import os.path 

9 

10from xpra.util import ellipsizer 

11from xpra.os_util import load_binary_file, getuid, OSX, POSIX, LINUX 

12from xpra.platform.paths import get_user_conf_dirs, get_system_conf_dirs 

13from xpra.log import Logger 

14 

15log = Logger("window", "util") 

16 

17DEFAULT_CONTENT_TYPE = os.environ.get("XPRA_DEFAULT_CONTENT_TYPE", "") 

18CONTENT_TYPE_DEFS = os.environ.get("XPRA_CONTENT_TYPE_DEFS","") 

19 

20 

21def get_proc_cmdline(pid): 

22 if pid and LINUX: 

23 #try to find the command via /proc: 

24 proc_cmd_line = os.path.join("/proc", "%s" % pid, "cmdline") 

25 if os.path.exists(proc_cmd_line): 

26 return load_binary_file(proc_cmd_line).rstrip(b"\0") 

27 return None 

28 

29def getprop(window, prop): 

30 try: 

31 if prop not in window.get_property_names(): 

32 log("no '%s' property on window %s", prop, window) 

33 return None 

34 return window.get_property(prop) 

35 except TypeError: 

36 log.error("Error querying %s on %s", prop, window, exc_info=True) 

37 

38 

39content_type_defs = None 

40def load_content_type_defs() -> dict: 

41 global content_type_defs 

42 if content_type_defs is None: 

43 content_type_defs = {} 

44 for d in get_system_conf_dirs(): 

45 load_content_type_dir(os.path.join(d, "content-type")) 

46 if not POSIX or getuid()>0: 

47 for d in get_user_conf_dirs(): 

48 load_content_type_dir(os.path.join(d, "content-type")) 

49 for e in CONTENT_TYPE_DEFS.split(","): 

50 if not process_content_type_entry(e): 

51 log.warn(" invalid entry in environment variable") 

52 return content_type_defs 

53 

54def load_content_type_dir(d): 

55 log("load_content_type_dir(%s)", d) 

56 if not os.path.exists(d) or not os.path.isdir(d): 

57 return 

58 for f in sorted(os.listdir(d)): 

59 if f.endswith(".conf"): 

60 ct_file = os.path.join(d, f) 

61 if os.path.isfile(ct_file): 

62 try: 

63 load_content_type_file(ct_file) 

64 except Exception as e: 

65 log("load_content_type_file(%s)", ct_file, exc_info=True) 

66 log.error("Error loading content-type data from '%s'", ct_file) 

67 log.error(" %s", e) 

68 

69def load_content_type_file(ct_file): 

70 with open(ct_file, "r") as f: 

71 l = 0 

72 for line in f: 

73 if not process_content_type_entry(line): 

74 log.warn(" line %i of file '%s'", l, ct_file) 

75 l += 1 

76 

77def process_content_type_entry(entry): 

78 global content_type_defs 

79 entry = entry.rstrip("\n\r") 

80 if entry.startswith("#") or not entry.strip(): 

81 return True 

82 parts = entry.rsplit("=", 1) 

83 #ie: "title:helloworld=text #some comments here" -> "title:helloworld", "text #some comments here" 

84 if len(parts)!=2: 

85 log.warn("Warning: invalid content-type definition") 

86 log.warn(" found in '%s'", entry) 

87 log.warn(" '%s' is missing a '='", entry) 

88 return False 

89 match, content_type = parts 

90 parts = match.split(":", 1) 

91 #ie: "title:helloworld" -> "title", "helloworld" 

92 if len(parts)!=2: 

93 log.warn("Warning: invalid content-type definition") 

94 log.warn(" match string '%s' is missing a ':'", match) 

95 return False 

96 #ignore comments: 

97 #"text #some comments here" > "text" 

98 content_type = content_type.split(":")[0].strip() 

99 prop_name, regex = parts 

100 try: 

101 c = re.compile(regex) 

102 content_type_defs.setdefault(prop_name, {})[c]=(regex, content_type) 

103 log("%16s matching '%s' is %s", prop_name, regex, content_type) 

104 except Exception as e: 

105 log.warn("Warning: invalid regular expression") 

106 log.warn(" match string '%s':", regex) 

107 log.warn(" %s", e) 

108 return False 

109 return True 

110 

111 

112def get_content_type_properties(): 

113 """ returns the list of window properties which can be used 

114 to guess the content-type. 

115 """ 

116 load_content_type_defs() 

117 return content_type_defs.keys() 

118 

119 

120def guess_content_type_from_defs(window) -> str: 

121 global content_type_defs 

122 load_content_type_defs() 

123 for prop_name, defs in content_type_defs.items(): 

124 if prop_name not in window.get_property_names(): 

125 continue 

126 prop_value = window.get_property(prop_name) 

127 #special case for "command": 

128 #we can look it up using proc on Linux 

129 if not prop_value and prop_name=="command": 

130 pid = getprop(window, "pid") 

131 prop_value = get_proc_cmdline(pid) 

132 #some properties return lists of values, 

133 #in which case we try to match any of them: 

134 log("guess_content_type_from_defs(%s) prop(%s)=%s", window, prop_name, prop_value) 

135 if isinstance(prop_value, (list,tuple)): 

136 values = prop_value 

137 else: 

138 values = [prop_value] 

139 for value in values: 

140 for regex, match_data in defs.items(): 

141 if regex.search(str(value)): 

142 regex_str, content_type = match_data 

143 log("guess_content_type(%s) found match: property=%s, regex=%s, content-type=%s", 

144 window, prop_name, regex_str, content_type) 

145 return content_type 

146 return None 

147 

148def load_categories_to_type(): 

149 categories_to_type = {} 

150 for d in get_system_conf_dirs(): 

151 v = load_content_categories_dir(os.path.join(d, "content-categories")) 

152 categories_to_type.update(v) 

153 if not POSIX or getuid()>0: 

154 for d in get_user_conf_dirs(): 

155 load_content_categories_dir(os.path.join(d, "content-categories")) 

156 categories_to_type.update(v) 

157 return categories_to_type 

158def load_content_categories_dir(d): 

159 if not os.path.exists(d) or not os.path.isdir(d): 

160 log("load_content_categories_dir(%s) directory not found", d) 

161 return {} 

162 categories_to_type = {} 

163 for f in sorted(os.listdir(d)): 

164 if f.endswith(".conf"): 

165 cc_file = os.path.join(d, f) 

166 if os.path.isfile(cc_file): 

167 try: 

168 categories_to_type.update(load_content_categories_file(cc_file)) 

169 except Exception as e: 

170 log("load_content_type_file(%s)", cc_file, exc_info=True) 

171 log.error("Error loading content-type data from '%s'", cc_file) 

172 log.error(" %s", e) 

173 log("load_categories_to_type(%s)=%s", d, categories_to_type) 

174 return categories_to_type 

175def load_content_categories_file(cc_file): 

176 d = {} 

177 with open(cc_file, "r") as f: 

178 l = 0 

179 for line in f: 

180 l += 1 

181 line = line.rstrip("\n\r") 

182 if line.startswith("#") or not line.strip(): 

183 continue 

184 parts = line.rsplit(":", 1) 

185 #ie: "title:helloworld=text #some comments here" -> "title:helloworld", "text #some comments here" 

186 if len(parts)!=2: 

187 log.warn("Warning: invalid content-type definition") 

188 log.warn(" found in '%s' at line %i", line, l) 

189 log.warn(" '%s' is missing a '='", line) 

190 continue 

191 category, content_type = parts 

192 d[category.strip("\t ").lower()] = content_type.strip("\t ") 

193 log("load_content_categories_file(%s)=%s", cc_file, d) 

194 return d 

195 

196command_to_type = None 

197def load_command_to_type(): 

198 global command_to_type 

199 if command_to_type is None: 

200 command_to_type = {} 

201 from xpra.platform.xposix.xdg_helper import load_xdg_menu_data 

202 xdg_menu = load_xdg_menu_data() 

203 categories_to_type = load_categories_to_type() 

204 log("load_command_to_type() xdg_menu=%s, categories_to_type=%s", xdg_menu, categories_to_type) 

205 if xdg_menu and categories_to_type: 

206 for category, category_props in xdg_menu.items(): 

207 log("category %s: %s", category, ellipsizer(category_props)) 

208 entries = category_props.get("Entries", {}) 

209 for name, props in entries.items(): 

210 command = props.get("TryExec") or props.get("Exec") 

211 categories = props.get("Categories") 

212 log("Entry '%s': command=%s, categories=%s", name, command, categories) 

213 if command and categories: 

214 for c in categories: 

215 ctype = categories_to_type.get(c.lower()) 

216 if not ctype: 

217 #try a more fuzzy match: 

218 for category_name,ct in categories_to_type.items(): 

219 if c.lower().find(category_name)>=0: 

220 ctype = ct 

221 break 

222 if ctype: 

223 cmd = os.path.basename(command.split(" ")[0]).encode() 

224 if cmd: 

225 command_to_type[cmd] = ctype 

226 break 

227 log("load_command_to_type()=%s", command_to_type) 

228 return command_to_type 

229 

230def guess_content_type_from_command(window): 

231 if POSIX and not OSX: 

232 command = getprop(window, "command") 

233 if not command and LINUX: 

234 pid = getprop(window, "pid") 

235 command = get_proc_cmdline(pid) 

236 log("guess_content_type_from_command(%s) command=%s", window, command) 

237 if command: 

238 ctt = load_command_to_type() 

239 cmd = os.path.basename(command) 

240 ctype = ctt.get(cmd) 

241 log("content-type(%s)=%s", cmd, ctype) 

242 return ctype 

243 return None 

244 

245 

246def guess_content_type(window): 

247 return guess_content_type_from_defs(window) or guess_content_type_from_command(window) or DEFAULT_CONTENT_TYPE