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) 2011-2020 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 

6import os 

7 

8#ensure that we use gtk as display source: 

9from xpra.x11.gtk_x11.gdk_display_source import init_gdk_display_source 

10from xpra.util import std, csv, envbool 

11from xpra.os_util import bytestostr 

12from xpra.gtk_common.error import xsync, xlog 

13from xpra.x11.bindings.keyboard_bindings import X11KeyboardBindings #@UnresolvedImport 

14from xpra.keyboard.layouts import parse_xkbmap_query 

15from xpra.log import Logger 

16 

17init_gdk_display_source() 

18X11Keyboard = X11KeyboardBindings() 

19 

20log = Logger("x11", "keyboard") 

21 

22XKB = envbool("XPRA_XKB", True) 

23DEBUG_KEYSYMS = [x for x in os.environ.get("XPRA_DEBUG_KEYSYMS", "").split(",") if len(x)>0] 

24 

25#keys we choose not to map if the free space in the keymap is too limited 

26#this list was generated using: 

27#$ DISPLAY=:1 xmodmap -pke | awk -F= '{print $2}' | xargs -n 1 echo | sort -u | grep XF | xargs 

28OPTIONAL_KEYS = [ 

29 "XF86AudioForward", "XF86AudioLowerVolume", "XF86AudioMedia", "XF86AudioMicMute", "XF86AudioMute", 

30 "XF86AudioNext", "XF86AudioPause", "XF86AudioPlay", "XF86AudioPrev", "XF86AudioRaiseVolume", 

31 "XF86AudioRecord", "XF86AudioRewind", "XF86AudioStop", 

32 "XF86Back", "XF86Battery", "XF86Bluetooth", "XF86Calculator", "XF86ClearGrab", "XF86Close", 

33 "XF86Copy", "XF86Cut", "XF86Display", "XF86Documents", "XF86DOS", "XF86Eject", "XF86Explorer", 

34 "XF86Favorites", "XF86Finance", "XF86Forward", "XF86Game", "XF86Go", "XF86HomePage", "XF86KbdBrightnessDown", 

35 "XF86KbdBrightnessUp", "XF86KbdLightOnOff", 

36 "XF86Launch1", "XF86Launch2", "XF86Launch3", "XF86Launch4", "XF86Launch5", "XF86Launch6", 

37 "XF86Launch7", "XF86Launch8", "XF86Launch9", "XF86LaunchA", "XF86LaunchB", 

38 "XF86Mail", "XF86MailForward", "XF86MenuKB", "XF86Messenger", "XF86MonBrightnessDown", 

39 "XF86MonBrightnessUp", "XF86MyComputer", 

40 "XF86New", "XF86Next_VMode", "XF86Open", "XF86Paste", "XF86Phone", "XF86PowerOff", 

41 "XF86Prev_VMode", "XF86Reload", "XF86Reply", "XF86RotateWindows", "XF86Save", "XF86ScreenSaver", 

42 "XF86ScrollDown", "XF86ScrollUp", "XF86Search", "XF86Send", "XF86Shop", "XF86Sleep", "XF86Suspend", 

43 "XF86Switch_VT_1", "XF86Switch_VT_10", "XF86Switch_VT_11", "XF86Switch_VT_12", "XF86Switch_VT_2", "XF86Switch_VT_3", 

44 "XF86Switch_VT_4", "XF86Switch_VT_5", "XF86Switch_VT_6", "XF86Switch_VT_7", "XF86Switch_VT_8", "XF86Switch_VT_9", 

45 "XF86Tools", "XF86TouchpadOff", "XF86TouchpadOn", "XF86TouchpadToggle", "XF86Ungrab", "XF86WakeUp", "XF86WebCam", 

46 "XF86WLAN", "XF86WWW", "XF86Xfer", 

47 ] 

48 

49 

50def clean_keyboard_state(): 

51 with xlog: 

52 X11Keyboard.ungrab_all_keys() 

53 with xlog: 

54 X11Keyboard.set_layout_group(0) 

55 with xlog: 

56 X11Keyboard.unpress_all_keys() 

57 

58################################################################################ 

59# keyboard layouts 

60 

61def do_set_keymap(xkbmap_layout, xkbmap_variant, xkbmap_options, 

62 xkbmap_query, xkbmap_query_struct): 

63 """ xkbmap_layout is the generic layout name (used on non posix platforms) 

64 xkbmap_variant is the layout variant (may not be set) 

65 xkbmap_print is the output of "setxkbmap -print" on the client 

66 xkbmap_query is the output of "setxkbmap -query" on the client 

67 xkbmap_query_struct is xkbmap_query parsed into a dictionary 

68 Use those to try to setup the correct keyboard map for the client 

69 so that all the keycodes sent will be mapped 

70 """ 

71 #First we try to use data from setxkbmap -query, 

72 #preferably as structured data: 

73 if xkbmap_query and not xkbmap_query_struct: 

74 xkbmap_query_struct = parse_xkbmap_query(xkbmap_query) 

75 if xkbmap_query_struct: 

76 log("do_set_keymap using xkbmap_query struct=%s", xkbmap_query_struct) 

77 #The xkbmap_query_struct data will look something like this: 

78 # { 

79 # b"rules" : b"evdev", 

80 # b"model" : b"pc105", 

81 # b"layout" : b"gb", 

82 # b"options" : b"grp:shift_caps_toggle", 

83 # } 

84 #parse the data into a dict: 

85 rules = xkbmap_query_struct.get(b"rules") 

86 model = xkbmap_query_struct.get(b"model") 

87 layout = xkbmap_query_struct.get(b"layout") 

88 variant = xkbmap_query_struct.get(b"variant") 

89 options = xkbmap_query_struct.get(b"options") 

90 if layout: 

91 log.info("setting keymap: %s", 

92 csv("%s=%s" % (std(k), std(v)) for k,v in xkbmap_query_struct.items() 

93 if k in ("rules", "model", "layout") and v)) 

94 safe_setxkbmap(rules, model, layout, variant, options) 

95 else: 

96 safe_setxkbmap(rules, model, "", "", "") 

97 #fallback for non X11 clients: 

98 layout = xkbmap_layout or "us" 

99 log.info("setting keyboard layout to '%s'", std(layout)) 

100 safe_setxkbmap("evdev", "pc105", layout, xkbmap_variant, xkbmap_options) 

101 

102def safe_setxkbmap(rules, model, layout, variant, options): 

103 #(we execute the options separately in case that fails..) 

104 try: 

105 X11Keyboard.setxkbmap(rules, model, layout, variant, options) 

106 return 

107 except Exception: 

108 log("safe_setxkbmap%s", (rules, model, layout, variant, options), exc_info=True) 

109 log.warn("Warning: failed to set exact keymap,") 

110 log.warn(" rules=%s, model=%s, layout=%s, variant=%s, options=%s", 

111 bytestostr(rules), bytestostr(model), bytestostr(layout), bytestostr(variant), bytestostr(options)) 

112 if options: 

113 #try again with no options: 

114 try: 

115 X11Keyboard.setxkbmap(rules, model, layout, variant, "") 

116 except Exception: 

117 log("setxkbmap", exc_info=True) 

118 log.error("Error: failed to set exact keymap") 

119 log.error(" even without applying any options..") 

120 

121 

122################################################################################ 

123# keycodes 

124 

125def apply_xmodmap(instructions): 

126 try: 

127 with xsync: 

128 unset = X11Keyboard.set_xmodmap(instructions) 

129 except Exception as e: 

130 log("apply_xmodmap(%s)", instructions, exc_info=True) 

131 log.error("Error configuring modifier map: %s", e) 

132 unset = instructions 

133 if unset is None: 

134 #None means an X11 error occurred, re-do all: 

135 unset = instructions 

136 return unset 

137 

138 

139def get_keycode_mappings(): 

140 if XKB and X11Keyboard.hasXkb(): 

141 return X11Keyboard.get_xkb_keycode_mappings() 

142 return X11Keyboard.get_keycode_mappings() 

143 

144def set_keycode_translation(xkbmap_x11_keycodes, xkbmap_keycodes): 

145 """ 

146 Translate the given keycodes into the existing keymap 

147 """ 

148 #get the list of keycodes (either from x11 keycodes or gtk keycodes): 

149 if xkbmap_x11_keycodes: 

150 dump_dict(xkbmap_x11_keycodes) 

151 keycodes = indexed_mappings(xkbmap_x11_keycodes) 

152 else: 

153 keycodes = gtk_keycodes_to_mappings(xkbmap_keycodes) 

154 log("set_keycode_translation(%s, %s)", xkbmap_x11_keycodes, xkbmap_keycodes) 

155 log(" keycodes=%s", keycodes) 

156 #keycodes = { 

157 # 9: set([('', 1), ('Escape', 4), ('', 3), ('Escape', 0), ('Escape', 2)]), 

158 # 10: set([('onesuperior', 4), ('onesuperior', 8), ('exclam', 1), ('1', 6), ('exclam', 3), ('1', 2), ('exclamdown', 9), ('exclamdown', 5), ('1', 0), ('exclam', 7)]), 

159 x11_keycodes = get_keycode_mappings() 

160 log(" x11_keycodes=%s", x11_keycodes) 

161 #x11_keycodes = { 

162 # 8: ['Mode_switch', '', 'Mode_switch', '', 'Mode_switch'], 

163 # 9: ['Escape', '', 'Escape', '', 'Escape'], 

164 #for faster lookups: 

165 x11_keycodes_for_keysym = {} 

166 for keycode, keysyms in x11_keycodes.items(): 

167 for keysym in keysyms: 

168 x11_keycodes_for_keysym.setdefault(keysym, set()).add(keycode) 

169 def find_keycode(kc, keysym, i): 

170 keycodes = tuple(x11_keycodes_for_keysym.get(keysym, set())) 

171 if keysym in DEBUG_KEYSYMS: 

172 log.info("set_keycode_translation: find_keycode%s x11 keycodes=%s", (kc, keysym, i), keycodes) 

173 def rlog(keycode, msg): 

174 if keysym in DEBUG_KEYSYMS: 

175 log.info("set_keycode_translation: find_keycode%s=%s (%s)", (kc, keysym, i), keycode, msg) 

176 if not keycodes: 

177 return None 

178 #no other option, use it: 

179 for keycode in keycodes: 

180 defs = x11_keycodes.get(keycode) 

181 if keysym in DEBUG_KEYSYMS: 

182 log.info("server x11 keycode %i: %s", keycode, defs) 

183 assert defs, "bug: keycode %i not found in %s" % (keycode, x11_keycodes) 

184 if len(defs)>i and defs[i]==keysym: 

185 rlog(keycode, "exact index match") 

186 return keycode, True 

187 #if possible, use the same one: 

188 if kc in keycodes: 

189 rlog(kc, "using same keycode as client") 

190 return kc, False 

191 keycode = keycodes[0] 

192 rlog(keycode, "using first match") 

193 return keycode, False 

194 #generate the translation map: 

195 trans = {} 

196 for keycode, defs in keycodes.items(): 

197 if bool(set(DEBUG_KEYSYMS) & set(bytestostr(d[0]) for d in defs)): 

198 log.info("client keycode=%i, defs=%s", keycode, defs) 

199 for bkeysym, i in tuple(defs): #ie: (b'1', 0) or (b'A', 1), etc 

200 keysym = bytestostr(bkeysym) 

201 m = find_keycode(keycode, keysym, i) 

202 if m: 

203 x11_keycode, index_matched = m 

204 trans[(keycode, keysym)] = x11_keycode 

205 trans[keysym] = x11_keycode 

206 if index_matched: 

207 trans[(keysym, i)] = x11_keycode 

208 if not xkbmap_x11_keycodes: 

209 #now add all the keycodes we may not have mapped yet 

210 #(present in x11_keycodes but not keycodes) 

211 for keycode, keysyms in x11_keycodes.items(): 

212 for i, keysym in enumerate(keysyms): 

213 if keysym not in trans: 

214 if keysym in DEBUG_KEYSYMS: 

215 log.info("x11 keycode %s: %s", keycode, keysym) 

216 trans[keysym] = keycode 

217 key = (keysym, i) 

218 if key not in trans: 

219 if keysym in DEBUG_KEYSYMS: 

220 log.info("x11 keycode %s: %s", keycode, key) 

221 trans[key] = keycode 

222 log("set_keycode_translation(..)=%s", trans) 

223 return trans 

224 

225def set_all_keycodes(xkbmap_x11_keycodes, xkbmap_keycodes, preserve_server_keycodes, modifiers): 

226 """ 

227 Clients that have access to raw x11 keycodes should provide 

228 an xkbmap_x11_keycodes map, we otherwise fallback to using 

229 the xkbmap_keycodes gtk keycode list. 

230 We try to preserve the initial keycodes if asked to do so, 

231 we retrieve them from the current server keymap and combine 

232 them with the given keycodes. 

233 The modifiers dict can be obtained by calling 

234 get_modifiers_from_meanings or get_modifiers_from_keycodes. 

235 We use it to ensure that two modifiers are not 

236 mapped to the same keycode (which is not allowed). 

237 We return a translation map for keycodes after setting them up, 

238 the key is (keycode, keysym) and the value is the server keycode. 

239 """ 

240 log("set_all_keycodes(%s.., %s.., %s.., %s)", 

241 str(xkbmap_x11_keycodes)[:60], str(xkbmap_keycodes)[:60], str(preserve_server_keycodes)[:60], modifiers) 

242 

243 #so we can validate entries: 

244 keysym_to_modifier = {} 

245 for modifier, keysyms in modifiers.items(): 

246 for keysym in keysyms: 

247 existing_mod = keysym_to_modifier.get(keysym) 

248 if existing_mod and existing_mod!=modifier: 

249 log.error("ERROR: keysym %s is mapped to both %s and %s !", keysym, modifier, existing_mod) 

250 else: 

251 keysym_to_modifier[keysym] = modifier 

252 if keysym in DEBUG_KEYSYMS: 

253 log.info("set_all_keycodes() keysym_to_modifier[%s]=%s", keysym, modifier) 

254 log("keysym_to_modifier=%s", keysym_to_modifier) 

255 

256 def modifiers_for(entries): 

257 """ entries can only point to a single modifier - verify """ 

258 modifiers = set() 

259 l = log 

260 for keysym, _ in entries: 

261 modifier = keysym_to_modifier.get(keysym) 

262 if modifier: 

263 modifiers.add(modifier) 

264 if keysym in DEBUG_KEYSYMS: 

265 l = log.info 

266 l("modifiers_for(%s)=%s", entries, modifiers) 

267 return modifiers 

268 

269 def filter_mappings(mappings, drop_extra_keys=False): 

270 filtered = {} 

271 invalid_keysyms = set() 

272 def estr(entries): 

273 try: 

274 return csv(tuple(set(x[0] for x in entries))) 

275 except Exception: 

276 return csv(tuple(entries)) 

277 for keycode, entries in mappings.items(): 

278 mods = modifiers_for(entries) 

279 if len(mods)>1: 

280 log.warn("Warning: keymapping changed:") 

281 log.warn(" keycode %s points to %i modifiers: %s", keycode, len(mods), csv(tuple(mods))) 

282 log.warn(" from definition: %s", estr(entries)) 

283 for mod in mods: 

284 emod = [entry for entry in entries if mod in modifiers_for([entry])] 

285 log.warn(" %s: %s", mod, estr(emod)) 

286 #keep just the first one: 

287 mods = tuple(mods)[:1] 

288 mod = mods[0] 

289 entries = [entry for entry in entries if mod in modifiers_for([entry])] 

290 log.warn(" keeping: %s for %s", estr(entries), mod) 

291 #now remove entries for keysyms we don't have: 

292 f_entries = set((keysym, index) for keysym, index in entries 

293 if keysym and X11Keyboard.parse_keysym(keysym) is not None) 

294 for keysym, _ in entries: 

295 if keysym and X11Keyboard.parse_keysym(keysym) is None: 

296 invalid_keysyms.add(keysym) 

297 if not f_entries: 

298 log("keymapping removed invalid keycode entry %s pointing to only unknown keysyms: %s", 

299 keycode, entries) 

300 continue 

301 if drop_extra_keys: 

302 if not any(keysym for keysym, index in entries if ( 

303 X11Keyboard.parse_keysym(keysym) is not None and keysym not in OPTIONAL_KEYS) 

304 ): 

305 log("keymapping removed keycode entry %s pointing to optional keys: %s", keycode, entries) 

306 continue 

307 filtered[keycode] = f_entries 

308 if invalid_keysyms: 

309 log.warn("Warning: the following keysyms are invalid and have not been mapped") 

310 log.warn(" %s", csv(invalid_keysyms)) 

311 return filtered 

312 

313 #get the list of keycodes (either from x11 keycodes or gtk keycodes): 

314 if xkbmap_x11_keycodes: 

315 log("using x11 keycodes: %s", xkbmap_x11_keycodes) 

316 dump_dict(xkbmap_x11_keycodes) 

317 keycodes = indexed_mappings(xkbmap_x11_keycodes) 

318 else: 

319 log("using gtk keycodes: %s", xkbmap_keycodes) 

320 keycodes = gtk_keycodes_to_mappings(xkbmap_keycodes) 

321 #filter to ensure only valid entries remain: 

322 log("keycodes=%s", keycodes) 

323 keycodes = filter_mappings(keycodes) 

324 

325 #now lookup the current keycodes (if we need to preserve them) 

326 preserve_keycode_entries = {} 

327 if preserve_server_keycodes: 

328 preserve_keycode_entries = X11Keyboard.get_keycode_mappings() 

329 log("preserved mappings:") 

330 dump_dict(preserve_keycode_entries) 

331 

332 kcmin, kcmax = X11Keyboard.get_minmax_keycodes() 

333 for try_harder in (False, True): 

334 filtered_preserve_keycode_entries = filter_mappings(indexed_mappings(preserve_keycode_entries), try_harder) 

335 log("filtered_preserve_keycode_entries=%s", filtered_preserve_keycode_entries) 

336 trans, new_keycodes, missing_keycodes = translate_keycodes(kcmin, kcmax, keycodes, 

337 filtered_preserve_keycode_entries, 

338 keysym_to_modifier, 

339 try_harder) 

340 if not missing_keycodes: 

341 break 

342 instructions = keymap_to_xmodmap(new_keycodes) 

343 unset = apply_xmodmap(instructions) 

344 log("unset=%s", unset) 

345 return trans 

346 

347def dump_dict(d): 

348 for k,v in d.items(): 

349 log("%s\t\t=\t%s", k, v) 

350 

351def group_by_keycode(entries): 

352 keycodes = {} 

353 log_keycodes = [] 

354 for keysym, keycode, index in entries: 

355 keycodes.setdefault(keycode, set()).add((keysym, index)) 

356 if keysym in DEBUG_KEYSYMS: 

357 log_keycodes.append(keycode) 

358 if log_keycodes: 

359 log.info("group_by_keycode: %s", dict((keycode, keycodes.get(keycode)) for keycode in log_keycodes)) 

360 return keycodes 

361 

362def indexed_mappings(raw_mappings): 

363 indexed = {} 

364 for keycode, keysyms in raw_mappings.items(): 

365 pairs = set() 

366 l = log 

367 for i, keysym in enumerate(keysyms): 

368 if keysym in DEBUG_KEYSYMS: 

369 l = log.info 

370 pairs.add((keysym, i)) 

371 indexed[keycode] = pairs 

372 l("indexed_mappings: %s=%s", keycode, pairs) 

373 return indexed 

374 

375 

376def gtk_keycodes_to_mappings(gtk_mappings): 

377 """ 

378 Takes gtk keycodes as obtained by get_gtk_keymap, in the form: 

379 #[(keyval, keyname, keycode, group, level), ..] 

380 And returns a list of entries in the form: 

381 [[keysym, keycode, index], ..] 

382 """ 

383 #use the keycodes supplied by gtk: 

384 mappings = {} 

385 for _, name, keycode, group, level in gtk_mappings: 

386 if keycode<0: 

387 continue #ignore old 'add_if_missing' client side code 

388 index = group*2+level 

389 mappings.setdefault(keycode, set()).add((name, index)) 

390 return mappings 

391 

392def x11_keycodes_to_list(x11_mappings): 

393 """ 

394 Takes x11 keycodes as obtained by get_keycode_mappings(), in the form: 

395 #{keycode : [keysyms], ..} 

396 And returns a list of entries in the form: 

397 [[keysym, keycode, index], ..] 

398 """ 

399 entries = [] 

400 if x11_mappings: 

401 for keycode, keysyms in x11_mappings.items(): 

402 index = 0 

403 for keysym in keysyms: 

404 if keysym: 

405 entries.append([keysym, int(keycode), index]) 

406 if keysym in DEBUG_KEYSYMS: 

407 log.info("x11_keycodes_to_list: (%s, %s) : %s", keycode, keysyms, (keysym, int(keycode), index)) 

408 index += 1 

409 return entries 

410 

411 

412def translate_keycodes(kcmin, kcmax, keycodes, preserve_keycode_entries, keysym_to_modifier, try_harder=False): 

413 """ 

414 The keycodes given may not match the range that the server supports, 

415 or some of those keycodes may not be usable (only one modifier can 

416 be mapped to a single keycode) or we want to preserve a keycode, 

417 or modifiers want to use the same keycode (which is not possible), 

418 so we return a translation map for those keycodes that have been 

419 remapped. 

420 The preserve_keycodes is a dict containing {keycode:[entries]} 

421 for keys we want to preserve the keycode for. 

422 Note: a client_keycode of '0' is valid (osx uses that), 

423 but server_keycode generally starts at 8... 

424 """ 

425 log("translate_keycodes(%s, %s, %s, %s, %s, %s)", 

426 kcmin, kcmax, keycodes, preserve_keycode_entries, keysym_to_modifier, try_harder) 

427 #list of free keycodes we can use: 

428 free_keycodes = [i for i in range(kcmin, kcmax+1) if i not in preserve_keycode_entries] 

429 keycode_trans = {} #translation map from client keycode to our server keycode 

430 server_keycodes = {} #the new keycode definitions 

431 missing_keycodes = [] #the groups of entries we failed to map due to lack of free keycodes 

432 

433 #to do faster lookups: 

434 preserve_keysyms_map = {} 

435 for keycode, entries in preserve_keycode_entries.items(): 

436 for keysym, _ in entries: 

437 preserve_keysyms_map.setdefault(keysym, set()).add(keycode) 

438 for k in DEBUG_KEYSYMS: 

439 log.info("preserve_keysyms_map[%s]=%s", k, preserve_keysyms_map.get(k)) 

440 

441 def do_assign(keycode, server_keycode, entries, override_server_keycode=False): 

442 """ may change the keycode if needed 

443 in which case we update the entries and populate 'keycode_trans' 

444 """ 

445 l = log 

446 for name, _ in entries: 

447 if name in DEBUG_KEYSYMS: 

448 l = log.info 

449 if (server_keycode in server_keycodes) and not override_server_keycode: 

450 l("assign: server keycode %s already in use: %s", server_keycode, server_keycodes.get(server_keycode)) 

451 server_keycode = -1 

452 elif server_keycode>0 and (server_keycode<kcmin or server_keycode>kcmax): 

453 l("assign: keycode %s out of range (%s to %s)", server_keycode, kcmin, kcmax) 

454 server_keycode = -1 

455 if server_keycode<=0: 

456 if free_keycodes: 

457 server_keycode = free_keycodes[0] 

458 l("set_keycodes key %s using free keycode=%s", entries, server_keycode) 

459 else: 

460 msg = "set_keycodes: no free keycodes!, cannot translate %s: %s", server_keycode, tuple(entries) 

461 if try_harder: 

462 log.error(*msg) 

463 else: 

464 l(*msg) 

465 missing_keycodes.append(entries) 

466 server_keycode = -1 

467 if server_keycode>0: 

468 l("set_keycodes key %s (%s) mapped to keycode=%s", keycode, tuple(entries), server_keycode) 

469 #can't use it any more! 

470 if server_keycode in free_keycodes: 

471 free_keycodes.remove(server_keycode) 

472 #record it in trans map: 

473 for name, _ in entries: 

474 if keycode>=0 and server_keycode!=keycode: 

475 keycode_trans[(keycode, name)] = server_keycode 

476 l("keycode_trans[(%s, %s)]=%s", keycode, bytestostr(name), server_keycode) 

477 keycode_trans[name] = server_keycode 

478 l("keycode_trans[%s]=%s", bytestostr(name), server_keycode) 

479 server_keycodes[server_keycode] = entries 

480 return server_keycode 

481 

482 def assign(client_keycode, entries): 

483 if not entries: 

484 return 0 

485 #all the keysyms for this keycode: 

486 keysyms = set(keysym for keysym, _ in entries) 

487 if not keysyms: 

488 return 0 

489 if len(keysyms)==1 and tuple(keysyms)[0]=='0xffffff': 

490 log("skipped invalid keysym: %s / %s", client_keycode, entries) 

491 return 0 

492 l = log 

493 if [k for k in keysyms if k in DEBUG_KEYSYMS]: 

494 l = log.info 

495 l("assign(%s, %s)", client_keycode, entries) 

496 

497 if not preserve_keycode_entries: 

498 return do_assign(client_keycode, client_keycode, entries) 

499 if len(keysyms)==1: 

500 #only one keysym, replace with single entry 

501 entries = set([(tuple(keysyms)[0], 0)]) 

502 

503 #the candidate preserve entries: those that have at least one of the keysyms: 

504 preserve_keycode_matches = {} 

505 for keysym in tuple(keysyms): 

506 keycodes = preserve_keysyms_map.get(keysym, []) 

507 for keycode in keycodes: 

508 v = preserve_keycode_entries.get(keycode) 

509 preserve_keycode_matches[keycode] = v 

510 l("preserve_keycode_matches[%s]=%s", keycode, v) 

511 

512 if not preserve_keycode_matches: 

513 l("no preserve matches for %s", tuple(entries)) 

514 return do_assign(client_keycode, -1, entries) #nothing to preserve 

515 

516 l("preserve matches for %s : %s", entries, preserve_keycode_matches) 

517 #direct superset: 

518 for p_keycode, p_entries in preserve_keycode_matches.items(): 

519 if entries.issubset(p_entries): 

520 l("found direct preserve superset for client keycode %s %s -> server keycode %s %s", 

521 client_keycode, tuple(entries), p_keycode, tuple(p_entries)) 

522 return do_assign(client_keycode, p_keycode, p_entries, override_server_keycode=True) 

523 if p_entries.issubset(entries): 

524 l("found direct superset of preserve for client keycode %s %s -> server keycode %s %s", 

525 client_keycode, tuple(entries), p_keycode, tuple(p_entries)) 

526 return do_assign(client_keycode, p_keycode, entries, override_server_keycode=True) 

527 

528 #ignoring indexes, but requiring at least as many keysyms: 

529 for p_keycode, p_entries in preserve_keycode_matches.items(): 

530 p_keysyms = set(keysym for keysym,_ in p_entries) 

531 if keysyms.issubset(p_keysyms): 

532 if len(p_entries)>len(entries): 

533 l("found keysym preserve superset with more keys for %s : %s", tuple(entries), tuple(p_entries)) 

534 return do_assign(client_keycode, p_keycode, p_entries, override_server_keycode=True) 

535 if p_keysyms.issubset(keysyms): 

536 l("found keysym superset of preserve with more keys for %s : %s", tuple(entries), tuple(p_entries)) 

537 return do_assign(client_keycode, p_keycode, entries, override_server_keycode=True) 

538 

539 if try_harder: 

540 #try to match the main key only: 

541 main_key = set((keysym, index) for keysym, index in entries if index==0) 

542 if len(main_key)==1: 

543 for p_keycode, p_entries in preserve_keycode_matches.items(): 

544 p_keysyms = set(keysym for keysym,_ in p_entries) 

545 if main_key.issubset(p_entries): 

546 l("found main key superset for %s : %s", main_key, tuple(p_entries)) 

547 return do_assign(client_keycode, p_keycode, p_entries, override_server_keycode=True) 

548 

549 l("no matches for %s", tuple(entries)) 

550 return do_assign(client_keycode, -1, entries) 

551 

552 #now try to assign each keycode: 

553 for keycode in sorted(keycodes.keys()): 

554 entries = keycodes.get(keycode) 

555 log("assign(%s, %s)", keycode, entries) 

556 assign(keycode, entries) 

557 

558 #add all the other preserved ones that have not been mapped to any client keycode: 

559 for server_keycode, entries in preserve_keycode_entries.items(): 

560 if server_keycode not in server_keycodes: 

561 do_assign(-1, server_keycode, entries) 

562 

563 #find all keysyms assigned so far: 

564 all_keysyms = set() 

565 for entries in server_keycodes.values(): 

566 for x in [keysym for keysym, _ in entries]: 

567 all_keysyms.add(x) 

568 log("all_keysyms=%s", all_keysyms) 

569 

570 #defined keysyms for modifiers if some are missing: 

571 for keysym, modifier in keysym_to_modifier.items(): 

572 if keysym not in all_keysyms: 

573 l = log 

574 if keysym in DEBUG_KEYSYMS: 

575 l = log.info 

576 l("found missing keysym %s for modifier %s, will add it", keysym, modifier) 

577 new_keycode = set([(keysym, 0)]) 

578 server_keycode = assign(-1, new_keycode) 

579 l("assigned keycode %s for key '%s' of modifier '%s'", server_keycode, keysym, modifier) 

580 

581 log("translated keycodes=%s", keycode_trans) 

582 log("%s free keycodes=%s", len(free_keycodes), free_keycodes) 

583 return keycode_trans, server_keycodes, missing_keycodes 

584 

585 

586def keymap_to_xmodmap(trans_keycodes): 

587 """ 

588 Given a dict with keycodes as keys and lists of keyboard entries as values, 

589 (keysym, keycode, index) 

590 produce a list of xmodmap instructions to set the x11 keyboard to match it, 

591 in the form: 

592 ("keycode", keycode, [keysyms]) 

593 """ 

594 missing_keysyms = [] #the keysyms lookups which failed 

595 instructions = [] 

596 all_entries = [] 

597 for entries in trans_keycodes.values(): 

598 all_entries += entries 

599 keysyms_per_keycode = max([index for _, index in all_entries])+1 

600 for server_keycode, entries in trans_keycodes.items(): 

601 keysyms = [None]*keysyms_per_keycode 

602 names = [""]*keysyms_per_keycode 

603 sentries = sorted(entries, key=lambda x:x[1]) 

604 for name, index in sentries: 

605 assert 0<=index<keysyms_per_keycode 

606 try: 

607 keysym = X11Keyboard.parse_keysym(name) 

608 except Exception: 

609 keysym = None 

610 if keysym is None: 

611 if name!="": 

612 missing_keysyms.append(name) 

613 else: 

614 if keysyms[index] is not None: 

615 #if the client provides multiple keysyms for the same index, 

616 #replace with the new one if the old one exists elsewhere, 

617 #or skip it if we have another entry for it 

618 can_override = any(True for i,v in enumerate(keysyms) if i<index and v==keysyms[index]) 

619 can_skip = any(True for i,v in enumerate(keysyms) if i!=index and v==keysym) 

620 if can_override or can_skip: 

621 l = log.debug 

622 else: 

623 l = log.warn 

624 l("Warning: more than one keysym for keycode %-3i at index %i:", server_keycode, index) 

625 l(" entries=%s", csv(tuple(sentries))) 

626 l(" keysyms=%s", csv(keysyms)) 

627 l(" assigned keysym=%s", names[index]) 

628 l(" wanted keysym=%s", name) 

629 if can_override: 

630 l(" current value also found at another index, overriding it") 

631 elif can_skip: 

632 l(" new value also found elsewhere, skipping it") 

633 else: 

634 continue 

635 names[index] = name 

636 keysyms[index] = keysym 

637 if name in DEBUG_KEYSYMS: 

638 log.info("keymap_to_xmodmap: keysyms[%s]=%s (%s)", index, keysym, name) 

639 #remove empty keysyms: 

640 while keysyms and keysyms[0] is None: 

641 keysyms = keysyms[1:] 

642 l = log 

643 if [k for k in keysyms if k in DEBUG_KEYSYMS]: 

644 l = log.info 

645 l("%s: %s -> %s", server_keycode, names, keysyms) 

646 instructions.append(("keycode", server_keycode, keysyms)) 

647 

648 if missing_keysyms: 

649 log.error("cannot find the X11 keysym for the following key names: %s", set(missing_keysyms)) 

650 log("instructions=%s", instructions) 

651 return instructions 

652 

653 

654################################################################################ 

655# modifiers 

656 

657def clear_modifiers(_modifiers): 

658 instructions = [] 

659 for i in range(0, 8): 

660 instructions.append(("clear", i)) 

661 apply_xmodmap(instructions) 

662 

663def set_modifiers(modifiers): 

664 """ 

665 modifiers is a dict: {modifier : [keynames]} 

666 Note: the same keysym cannot appear in more than one modifier 

667 """ 

668 instructions = [] 

669 for modifier, keynames in modifiers.items(): 

670 mod = X11Keyboard.parse_modifier(bytestostr(modifier)) 

671 if mod>=0: 

672 instructions.append(("add", mod, keynames)) 

673 else: 

674 log.error("Error: unknown modifier %s", modifier) 

675 log("set_modifiers: %s", instructions) 

676 def apply_or_trim(instructions): 

677 err = apply_xmodmap(instructions) 

678 log("set_modifiers: err=%s", err) 

679 if err: 

680 log("set_modifiers %s failed, retrying one more at a time", instructions) 

681 l = len(instructions) 

682 for i in range(1, l): 

683 subset = instructions[:i] 

684 log("set_modifiers testing with [:%s]=%s", i, subset) 

685 err = apply_xmodmap(subset) 

686 log("err=%s", err) 

687 if err: 

688 log.warn("removing problematic modifier mapping: %s", instructions[i-1]) 

689 instructions = instructions[:i-1]+instructions[i:] 

690 return apply_or_trim(instructions) 

691 apply_or_trim(instructions) 

692 return modifiers 

693 

694 

695def get_modifiers_from_meanings(xkbmap_mod_meanings): 

696 """ 

697 xkbmap_mod_meanings maps a keyname to a modifier 

698 returns keynames_for_mod: {modifier : [keynames]} 

699 """ 

700 #first generate a {modifier : [keynames]} dict: 

701 modifiers = {} 

702 for keyname, modifier in xkbmap_mod_meanings.items(): 

703 l = modifiers.setdefault(bytestostr(modifier), []) 

704 kn = bytestostr(keyname) 

705 if kn not in l: 

706 l.append(kn) 

707 log("get_modifiers_from_meanings(%s) modifier dict=%s", xkbmap_mod_meanings, modifiers) 

708 return modifiers 

709 

710def get_modifiers_from_keycodes(xkbmap_keycodes, add_default_modifiers=True): 

711 """ 

712 Some platforms can't tell us about modifier mappings 

713 So we try to find matches from the defaults below: 

714 """ 

715 from xpra.keyboard.mask import DEFAULT_MODIFIER_MEANINGS 

716 pref = DEFAULT_MODIFIER_MEANINGS 

717 #keycodes are: {keycode : (keyval, name, keycode, group, level)} 

718 matches = {} 

719 log("get_modifiers_from_keycodes(%s...)", str(xkbmap_keycodes)[:160]) 

720 all_keynames = set() 

721 for entry in xkbmap_keycodes: 

722 _, keyname, _, _, _ = entry 

723 modifier = pref.get(keyname) 

724 if modifier: 

725 keynames = matches.setdefault(modifier, []) 

726 if keyname not in keynames: 

727 keynames.add(keyname) 

728 all_keynames.add(keyname) 

729 if add_default_modifiers: 

730 #try to add missings ones (magic!) 

731 defaults = {} 

732 for keyname, modifier in DEFAULT_MODIFIER_MEANINGS.items(): 

733 if keyname in all_keynames: 

734 continue #aleady defined 

735 if modifier not in matches: 

736 #define it since it is completely missing 

737 keynames = defaults.setdefault(modifier, []) 

738 if keyname not in keynames: 

739 keynames.append(keyname) 

740 elif modifier in ["shift", "lock", "control", "mod1", "mod2"] or keyname=="ISO_Level3_Shift": 

741 #these ones we always add them, even if a record for this modifier already exists 

742 keynames = matches.setdefault(modifier, []) 

743 if keyname not in keynames: 

744 keynames.append(keyname) 

745 log("get_modifiers_from_keycodes(...) adding defaults: %s", defaults) 

746 matches.update(defaults) 

747 log("get_modifiers_from_keycodes(...)=%s", matches) 

748 return matches 

749 

750def map_missing_modifiers(keynames_for_mod): 

751 x11_keycodes = X11Keyboard.get_keycode_mappings() 

752 min_keycode, max_keycode = X11Keyboard.get_minmax_keycodes() 

753 free_keycodes = [x for x in range(min_keycode, max_keycode) if x not in x11_keycodes] 

754 log("map_missing_modifiers(%s) min_keycode=%i max_keycode=%i, free_keycodes=%s", 

755 keynames_for_mod, min_keycode, max_keycode, free_keycodes) 

756 keysyms_to_keycode = {} 

757 for keycode, keysyms in x11_keycodes.items(): 

758 for keysym in keysyms: 

759 keysyms_to_keycode.setdefault(keysym, []).append(keycode) 

760 xmodmap_changes = [] 

761 for mod, keysyms in keynames_for_mod.items(): 

762 missing = [] 

763 for keysym in keysyms: 

764 if keysym not in keysyms_to_keycode: 

765 missing.append(keysym) 

766 if missing: 

767 log("map_missing_modifiers: no keycode found for modifier keys %s (%s)", csv(missing), mod) 

768 if not free_keycodes: 

769 log.warn("Warning: keymap is full, cannot add '%s' for modifier '%s'", keysym, mod) 

770 else: 

771 keycode = free_keycodes.pop() 

772 xmodmap_changes.append(("keycode", keycode, missing)) 

773 if xmodmap_changes: 

774 log("xmodmap_changes=%s", xmodmap_changes) 

775 X11Keyboard.set_xmodmap(xmodmap_changes)