Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/server/window/content_guesser.py : 41%
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.
7import re
8import os.path
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
15log = Logger("window", "util")
17DEFAULT_CONTENT_TYPE = os.environ.get("XPRA_DEFAULT_CONTENT_TYPE", "")
18CONTENT_TYPE_DEFS = os.environ.get("XPRA_CONTENT_TYPE_DEFS","")
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
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)
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
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)
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
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
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()
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
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
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
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
246def guess_content_type(window):
247 return guess_content_type_from_defs(window) or guess_content_type_from_command(window) or DEFAULT_CONTENT_TYPE