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) 2011 Serviware (Arthur Huillet, <ahuillet@serviware.com>) 

4# Copyright (C) 2010-2019 Antoine Martin <antoine@xpra.org> 

5# Copyright (C) 2008 Nathaniel Smith <njs@pobox.com> 

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

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

8 

9from gi.repository import Gdk 

10 

11from xpra.util import csv, envbool 

12from xpra.os_util import bytestostr 

13from xpra.gtk_common.keymap import get_gtk_keymap 

14from xpra.gtk_common.gtk_util import get_default_root_window 

15from xpra.gtk_common.error import xsync, xlog 

16from xpra.keyboard.mask import DEFAULT_MODIFIER_NUISANCE, DEFAULT_MODIFIER_NUISANCE_KEYNAMES, mask_to_names 

17from xpra.server.keyboard_config_base import KeyboardConfigBase 

18from xpra.x11.gtk_x11.keys import grok_modifier_map 

19from xpra.x11.xkbhelper import ( 

20 do_set_keymap, set_all_keycodes, set_keycode_translation, 

21 get_modifiers_from_meanings, get_modifiers_from_keycodes, 

22 clear_modifiers, set_modifiers, map_missing_modifiers, 

23 clean_keyboard_state, get_keycode_mappings, 

24 DEBUG_KEYSYMS, 

25 ) 

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

27from xpra.log import Logger 

28 

29log = Logger("keyboard") 

30 

31X11Keyboard = X11KeyboardBindings() 

32 

33MAP_MISSING_MODIFIERS = envbool("XPRA_MAP_MISSING_MODIFIERS", True) 

34SHIFT_LOCK = envbool("XPRA_SHIFT_LOCK", False) 

35 

36ALL_X11_MODIFIERS = { 

37 "shift" : 0, 

38 "lock" : 1, 

39 "control" : 2, 

40 "mod1" : 3, 

41 "mod2" : 4, 

42 "mod3" : 5, 

43 "mod4" : 6, 

44 "mod5" : 7 

45 } 

46 

47class KeyboardConfig(KeyboardConfigBase): 

48 def __init__(self): 

49 KeyboardConfigBase.__init__(self) 

50 self.xkbmap_raw = False 

51 self.xkbmap_query = None 

52 self.xkbmap_query_struct = None 

53 self.xkbmap_mod_meanings = {} 

54 self.xkbmap_mod_managed = [] 

55 self.xkbmap_mod_pointermissing = [] 

56 self.xkbmap_mod_nuisance = set(DEFAULT_MODIFIER_NUISANCE) 

57 self.xkbmap_keycodes = () 

58 self.xkbmap_x11_keycodes = [] 

59 self.xkbmap_layout = None 

60 self.xkbmap_variant = None 

61 self.xkbmap_options = None 

62 self.xkbmap_layout_groups = False 

63 

64 #this is shared between clients! 

65 self.keys_pressed = {} 

66 #these are derived by calling set_keymap: 

67 self.keynames_for_mod = {} 

68 self.keycode_translation = {} 

69 self.keycodes_for_modifier_keynames = {} 

70 self.modifier_client_keycodes = {} 

71 self.compute_modifier_map() 

72 self.modifiers_filter = [] 

73 self.keycode_mappings = {} 

74 

75 def __repr__(self): 

76 return "KeyboardConfig(%s / %s / %s)" % (self.xkbmap_layout, self.xkbmap_variant, self.xkbmap_options) 

77 

78 def get_info(self) -> dict: 

79 info = KeyboardConfigBase.get_info(self) 

80 #keycodes: 

81 if self.keycode_translation: 

82 ksinfo = info.setdefault("keysym", {}) 

83 kssinf = info.setdefault("keysyms", {}) 

84 kcinfo = info.setdefault("keycode", {}) 

85 for kc, keycode in self.keycode_translation.items(): 

86 if isinstance(kc, tuple): 

87 a, b = kc 

88 if isinstance(a, int): 

89 client_keycode, keysym = a, b 

90 ksinfo.setdefault(keysym, {})[client_keycode] = keycode 

91 kcinfo.setdefault(client_keycode, {})[keysym] = keycode 

92 elif isinstance(b, int): 

93 keysym, index = a, b 

94 kssinf.setdefault(keycode, []).append((index, keysym)) 

95 else: 

96 kcinfo[kc] = keycode 

97 if self.xkbmap_keycodes: 

98 i = 0 

99 kminfo = info.setdefault("keymap", {}) 

100 for keyval, name, keycode, group, level in self.xkbmap_keycodes: 

101 kminfo[i] = (keyval, name, keycode, group, level) 

102 i += 1 

103 #modifiers: 

104 modinfo = {} 

105 modsinfo = {} 

106 modinfo["filter"] = self.modifiers_filter 

107 if self.modifier_client_keycodes: 

108 for mod, keys in self.modifier_client_keycodes.items(): 

109 modinfo.setdefault(mod, {})["client_keys"] = keys 

110 if self.keynames_for_mod: 

111 for mod, keys in self.keynames_for_mod.items(): 

112 modinfo.setdefault(mod, {})["keys"] = tuple(keys) 

113 if self.keycodes_for_modifier_keynames: 

114 for mod, keys in self.keycodes_for_modifier_keynames.items(): 

115 modinfo.setdefault(mod, {})["keycodes"] = tuple(keys) 

116 if self.xkbmap_mod_meanings: 

117 for mod, mod_name in self.xkbmap_mod_meanings.items(): 

118 modinfo[mod] = mod_name 

119 info["x11_keycode"] = self.xkbmap_x11_keycodes 

120 for x in ("layout", "variant", "mod_managed", "mod_pointermissing", "raw", "layout_groups"): 

121 v = getattr(self, "xkbmap_%s" % x) 

122 if v: 

123 info[x] = v 

124 modsinfo["nuisance"] = tuple(self.xkbmap_mod_nuisance or []) 

125 info["modifier"] = modinfo 

126 info["modifiers"] = modsinfo 

127 #this would need to always run in the UI thread: 

128 #info["state"] = { 

129 # "modifiers" : self.get_current_mask(), 

130 # } 

131 log("keyboard info: %s", info) 

132 return info 

133 

134 

135 def parse_options(self, props): 

136 """ used by both process_hello and process_keymap 

137 to set the keyboard attributes """ 

138 super().parse_options(props) 

139 modded = {} 

140 def parse_option(name, parse_fn, *parse_args): 

141 prop = "xkbmap_%s" % name 

142 cv = getattr(self, prop) 

143 nv = parse_fn(prop, *parse_args) 

144 if cv!=nv: 

145 setattr(self, prop, nv) 

146 modded[prop] = nv 

147 #plain strings: 

148 for x in ("query", ): 

149 parse_option(x, props.strget) 

150 #lists: 

151 parse_option("keycodes", props.tupleget) 

152 #dicts: 

153 for x in ("mod_meanings", "x11_keycodes", "query_struct"): 

154 parse_option(x, props.dictget, {}) 

155 #lists of strings: 

156 for x in ("mod_managed", "mod_pointermissing"): 

157 parse_option(x, props.strtupleget) 

158 parse_option("raw", props.boolget) 

159 #older clients don't specify if they support layout groups safely 

160 #(MS Windows clients used base-1) 

161 #so only enable it by default for X11 clients 

162 parse_option("layout_groups", props.boolget, bool(self.xkbmap_query or self.xkbmap_query_struct)) 

163 log("assign_keymap_options(..) modified %s", modded) 

164 return len(modded)>0 

165 

166 

167 def get_hash(self): 

168 """ 

169 This hash will be different whenever the keyboard configuration changes. 

170 """ 

171 import hashlib 

172 m = hashlib.sha1() 

173 def hashadd(v): 

174 m.update(("/%s" % str(v)).encode("utf8")) 

175 m.update(KeyboardConfigBase.get_hash(self)) 

176 for x in (self.xkbmap_query, self.xkbmap_raw, \ 

177 self.xkbmap_mod_meanings, self.xkbmap_mod_pointermissing, \ 

178 self.xkbmap_keycodes, self.xkbmap_x11_keycodes): 

179 hashadd(x) 

180 if self.xkbmap_query_struct: 

181 #flatten the dict in a predicatable order: 

182 for k in sorted(self.xkbmap_query_struct.keys()): 

183 hashadd(self.xkbmap_query_struct.get(k)) 

184 return "%s/%s/%s/%s" % (self.xkbmap_layout, self.xkbmap_variant, self.xkbmap_options, m.hexdigest()) 

185 

186 def compute_modifiers(self): 

187 if self.xkbmap_raw: 

188 with xsync: 

189 mod_mappings = X11Keyboard.get_modifier_mappings() 

190 self.xkbmap_mod_meanings = {} 

191 self.keycodes_for_modifier_keynames = {} 

192 for mod, mod_defs in mod_mappings.items(): 

193 for mod_def in mod_defs: 

194 for v in mod_def: 

195 if isinstance(v, int): 

196 l = self.keycodes_for_modifier_keynames.setdefault(mod, []) 

197 else: 

198 self.xkbmap_mod_meanings[v] = mod 

199 l = self.keynames_for_mod.setdefault(mod, []) 

200 if v not in l: 

201 l.append(v) 

202 else: 

203 log("compute_modifiers() xkbmap_mod_meanings=%s", self.xkbmap_mod_meanings) 

204 log("compute_modifiers() xkbmap_keycodes=%s", self.xkbmap_keycodes) 

205 if self.xkbmap_mod_meanings: 

206 #Unix-like OS provides modifier meanings: 

207 self.keynames_for_mod = get_modifiers_from_meanings(self.xkbmap_mod_meanings) 

208 elif self.xkbmap_keycodes: 

209 #non-Unix-like OS provides just keycodes for now: 

210 self.keynames_for_mod = get_modifiers_from_keycodes(self.xkbmap_keycodes, MAP_MISSING_MODIFIERS) 

211 if MAP_MISSING_MODIFIERS: 

212 map_missing_modifiers(self.keynames_for_mod) 

213 else: 

214 log.warn("Warning: client did not supply any modifier definitions") 

215 self.keynames_for_mod = {} 

216 log("compute_modifiers() keynames_for_mod=%s", self.keynames_for_mod) 

217 log("compute_modifiers() keycodes_for_modifier_keynames=%s", self.keycodes_for_modifier_keynames) 

218 log("compute_modifiers() mod_meanings=%s", self.xkbmap_mod_meanings) 

219 

220 

221 def compute_modifier_keynames(self): 

222 self.keycodes_for_modifier_keynames = {} 

223 self.xkbmap_mod_nuisance = set(DEFAULT_MODIFIER_NUISANCE) 

224 display = Gdk.Display.get_default() 

225 keymap = Gdk.Keymap.get_for_display(display) 

226 if self.keynames_for_mod: 

227 for modifier, keynames in self.keynames_for_mod.items(): 

228 for keyname in keynames: 

229 if keyname in DEFAULT_MODIFIER_NUISANCE_KEYNAMES: 

230 self.xkbmap_mod_nuisance.add(modifier) 

231 keyval = Gdk.keyval_from_name(bytestostr(keyname)) 

232 if keyval==0: 

233 log.error("Error: no keyval found for keyname '%s' (modifier '%s')", keyname, modifier) 

234 return [] 

235 entries = keymap.get_entries_for_keyval(keyval) 

236 if entries: 

237 keycodes = [] 

238 if entries[0] is True: 

239 keycodes = [entry.keycode for entry in entries[1]] 

240 for keycode in keycodes: 

241 l = self.keycodes_for_modifier_keynames.setdefault(keyname, []) 

242 if keycode not in l: 

243 l.append(keycode) 

244 log("compute_modifier_keynames: keycodes_for_modifier_keynames=%s", self.keycodes_for_modifier_keynames) 

245 

246 def compute_client_modifier_keycodes(self): 

247 """ The keycodes for all modifiers (those are *client* keycodes!) """ 

248 try: 

249 server_mappings = X11Keyboard.get_modifier_mappings() 

250 log("compute_client_modifier_keycodes() server mappings=%s", server_mappings) 

251 #update the mappings to use the keycodes the client knows about: 

252 reverse_trans = {} 

253 for k,v in self.keycode_translation.items(): 

254 reverse_trans[v] = k 

255 self.modifier_client_keycodes = {} 

256 self.xkbmap_mod_nuisance = set(DEFAULT_MODIFIER_NUISANCE) 

257 for modifier, keys in server_mappings.items(): 

258 #ie: modifier=mod3, keys=[(115, 'Super_L'), (116, 'Super_R'), (127, 'Super_L')] 

259 client_keydefs = [] 

260 for keycode,keysym in keys: 

261 #ie: keycode=115, keysym=Super_L 

262 client_def = reverse_trans.get(keycode, (0, keysym)) 

263 #ie: client_def = (99, Super_L) 

264 #ie: client_def = Super_L 

265 #ie: client_def = (0, Super_L) 

266 if isinstance(client_def, (list, tuple)): 

267 #ie: 

268 # keycode, keysym: 

269 # client_def = (99, Super_L) 

270 # or keysym, level: 

271 # client_def = (Super_L, 1) 

272 client_keydefs.append(client_def) 

273 elif client_def==keysym: 

274 #ie: client_def = Super_L 

275 client_keydefs.append((keycode, keysym)) 

276 #record nuisacnde modifiers: 

277 if keysym in DEFAULT_MODIFIER_NUISANCE_KEYNAMES: 

278 self.xkbmap_mod_nuisance.add(modifier) 

279 self.modifier_client_keycodes[modifier] = client_keydefs 

280 log("compute_client_modifier_keycodes() mappings=%s", self.modifier_client_keycodes) 

281 log("compute_client_modifier_keycodes() mod nuisance=%s", self.xkbmap_mod_nuisance) 

282 except Exception as e: 

283 log.error("Error: compute_client_modifier_keycodes: %s" % e, exc_info=True) 

284 

285 def compute_modifier_map(self): 

286 self.modifier_map = grok_modifier_map(Gdk.Display.get_default(), self.xkbmap_mod_meanings) 

287 log("modifier_map(%s)=%s", self.xkbmap_mod_meanings, self.modifier_map) 

288 

289 

290 def is_modifier(self, keycode): 

291 for mod, keys in self.keycodes_for_modifier_keynames.items(): 

292 if keycode in keys: 

293 log("is_modifier(%s) found modifier: %s", keycode, mod) 

294 return True 

295 log("is_modifier(%s) not found", keycode) 

296 return False 

297 

298 

299 def set_layout(self, layout, variant, options): 

300 log("set_layout(%s, %s, %s)", layout, variant, options) 

301 if layout=="en": 

302 log.warn("Warning: invalid keyboard layout name '%s', using 'us' instead", layout) 

303 layout = "us" 

304 if layout!=self.xkbmap_layout or variant!=self.xkbmap_variant or options!=self.xkbmap_options: 

305 self.xkbmap_layout = layout 

306 self.xkbmap_variant = variant 

307 self.xkbmap_options = options 

308 return True 

309 return False 

310 

311 

312 def set_keymap(self, translate_only=False): 

313 if not self.enabled: 

314 return 

315 log("set_keymap(%s) layout=%r, variant=%r, options=%r, query=%r", 

316 translate_only, self.xkbmap_layout, self.xkbmap_variant, self.xkbmap_options, 

317 self.xkbmap_query) 

318 if translate_only: 

319 self.keycode_translation = set_keycode_translation(self.xkbmap_x11_keycodes, self.xkbmap_keycodes) 

320 self.add_gtk_keynames() 

321 self.compute_modifiers() 

322 self.compute_modifier_keynames() 

323 self.compute_client_modifier_keycodes() 

324 self.update_keycode_mappings() 

325 return 

326 

327 with xlog: 

328 clean_keyboard_state() 

329 do_set_keymap(self.xkbmap_layout, self.xkbmap_variant, self.xkbmap_options, 

330 self.xkbmap_query, self.xkbmap_query_struct) 

331 log("set_keymap: xkbmap_query=%r", self.xkbmap_query) 

332 with xlog: 

333 #first clear all existing modifiers: 

334 clean_keyboard_state() 

335 

336 if not self.xkbmap_raw: 

337 has_keycodes = bool(self.xkbmap_x11_keycodes) or bool(self.xkbmap_keycodes) 

338 assert has_keycodes, "client failed to provide any keycodes!" 

339 

340 clear_modifiers(ALL_X11_MODIFIERS.keys()) #just clear all of them (set or not) 

341 clean_keyboard_state() 

342 

343 #now set all the keycodes: 

344 #first compute the modifier maps as this may have an influence 

345 #on the keycode mappings (at least for the from_keycodes case): 

346 self.compute_modifiers() 

347 #key translation: 

348 if bool(self.xkbmap_query): 

349 #native full mapping of all keycodes: 

350 self.keycode_translation = set_all_keycodes(self.xkbmap_x11_keycodes, self.xkbmap_keycodes, False, self.keynames_for_mod) 

351 else: 

352 #if the client does not provide a full native keymap with all the keycodes, 

353 #try to preserve the initial server keycodes and translate the client keycodes instead: 

354 #(used by non X11 clients like osx,win32 or HTML5) 

355 self.keycode_translation = set_keycode_translation(self.xkbmap_x11_keycodes, self.xkbmap_keycodes) 

356 self.add_gtk_keynames() 

357 

358 #now set the new modifier mappings: 

359 clean_keyboard_state() 

360 log("going to set modifiers, xkbmap_mod_meanings=%s, len(xkbmap_keycodes)=%s, keynames_for_mod=%s", self.xkbmap_mod_meanings, len(self.xkbmap_keycodes or []), self.keynames_for_mod) 

361 if self.keynames_for_mod: 

362 set_modifiers(self.keynames_for_mod) 

363 log("keynames_for_mod=%s", self.keynames_for_mod) 

364 self.compute_modifier_keynames() 

365 else: 

366 self.keycode_translation = {} 

367 log("keyboard raw mode, keycode translation left empty") 

368 self.compute_modifiers() 

369 self.compute_client_modifier_keycodes() 

370 log("keyname_for_mod=%s", self.keynames_for_mod) 

371 clean_keyboard_state() 

372 self.update_keycode_mappings() 

373 

374 

375 def add_gtk_keynames(self): 

376 #add the keynames we find via gtk 

377 #since we may rely on finding those keynames from the client 

378 #(used with non native keymaps) 

379 log("add_gtk_keynames() gtk keymap=%s", get_gtk_keymap()) 

380 for _, keyname, keycode, _, _ in get_gtk_keymap(): 

381 if keyname not in self.keycode_translation: 

382 self.keycode_translation[keyname] = keycode 

383 if keyname in DEBUG_KEYSYMS: 

384 log.info("add_gtk_keynames: %s=%s", keyname, keycode) 

385 

386 def set_default_keymap(self): 

387 """ assign a default keymap based on the current X11 server keymap 

388 sets up the translation tables so we can lookup keys without 

389 setting a client keymap. 

390 """ 

391 if not self.enabled: 

392 return 

393 with xsync: 

394 clean_keyboard_state() 

395 #keycodes: 

396 keycode_to_keynames = get_keycode_mappings() 

397 self.keycode_translation = {} 

398 #prefer keycodes that don't use the lowest level+mode: 

399 default_for_keyname = {} 

400 for keycode, keynames in keycode_to_keynames.items(): 

401 for i, keyname in enumerate(keynames): 

402 self.keycode_translation[(keyname, i)] = keycode 

403 if keyname in DEBUG_KEYSYMS: 

404 log.info("set_default_keymap: %s=%s", (keyname, i), keycode) 

405 kd = default_for_keyname.get(keyname) 

406 if kd is None or kd[1]>i: 

407 default_for_keyname[keyname] = (keycode, i) 

408 for keyname, kd in default_for_keyname.items(): 

409 keycode = kd[0] 

410 self.keycode_translation[keyname] = keycode 

411 if keyname in DEBUG_KEYSYMS: 

412 log.info("set_default_keymap: %s=%s", keyname, keycode) 

413 self.add_gtk_keynames() 

414 log("set_default_keymap: keycode_translation=%s", self.keycode_translation) 

415 #modifiers: 

416 self.keynames_for_mod = {} 

417 #ie: {'control': [(37, 'Control_L'), (105, 'Control_R')], ...} 

418 mod_mappings = X11Keyboard.get_modifier_mappings() 

419 log("set_default_keymap: using modifier mappings=%s", mod_mappings) 

420 for modifier, mappings in mod_mappings.items(): 

421 keynames = [] 

422 for m in mappings: #ie: (37, 'Control_L'), (105, 'Control_R') 

423 if len(m)==2: 

424 keynames.append(m[1]) #ie: 'Control_L' 

425 self.keynames_for_mod[modifier] = set(keynames) 

426 self.compute_modifier_keynames() 

427 self.compute_client_modifier_keycodes() 

428 log("set_default_keymap: keynames_for_mod=%s", self.keynames_for_mod) 

429 log("set_default_keymap: keycodes_for_modifier_keynames=%s", self.keycodes_for_modifier_keynames) 

430 log("set_default_keymap: modifier_map=%s", self.modifier_map) 

431 self.update_keycode_mappings() 

432 

433 def update_keycode_mappings(self): 

434 self.keycode_mappings = get_keycode_mappings() 

435 

436 

437 def do_get_keycode(self, client_keycode, keyname, pressed, modifiers, keyval, keystr, group): 

438 if not self.enabled: 

439 log("ignoring keycode since keyboard is turned off") 

440 return -1, group 

441 if keyname=="0xffffff": 

442 return -1, group 

443 if self.xkbmap_raw: 

444 return client_keycode, group 

445 def kmlog(msg, *args): 

446 if keyname in DEBUG_KEYSYMS: 

447 l = log.info 

448 else: 

449 l = log 

450 l(msg, *args) 

451 def klog(msg, *args): 

452 kmlog("do_get_keycode%s"+msg, (client_keycode, keyname, pressed, modifiers, keyval, keystr, group), *args) 

453 keycode = None 

454 rgroup = group 

455 if self.xkbmap_query: 

456 keycode = self.keycode_translation.get((client_keycode, keyname)) or client_keycode 

457 klog("=%s (native keymap)", keycode) 

458 else: 

459 """ 

460 from man xmodmap: 

461 The list of keysyms is assigned to the indicated keycode (which may be specified in decimal, 

462 hex or octal and can be determined by running the xev program). 

463 Up to eight keysyms may be attached to a key, however the last four are not used in any major 

464 X server implementation. 

465 The first keysym is used when no modifier key is pressed in conjunction with this key, 

466 the second with Shift, the third when the Mode_switch key is used with this key and 

467 the fourth when both the Mode_switch and Shift keys are used. 

468 """ 

469 #non-native: try harder to find matching keysym 

470 #first, try to honour shift state: 

471 lock = ("lock" in modifiers) and (SHIFT_LOCK or (bool(keystr) and keystr.isalpha())) 

472 shift = ("shift" in modifiers) ^ lock 

473 mode = 0 

474 numlock = 0 

475 numlock_modifier = None 

476 for mod, keynames in self.keynames_for_mod.items(): 

477 if "Num_Lock" in keynames: 

478 numlock_modifier = mod 

479 break 

480 for mod in modifiers: 

481 names = self.keynames_for_mod.get(mod, []) 

482 if "Num_Lock" in names: 

483 numlock = 1 

484 for name in names: 

485 if name in ("ISO_Level3_Shift", "Mode_switch"): 

486 mode = 1 

487 break 

488 levels = [] 

489 #try to preserve the mode (harder to toggle): 

490 for m in (int(bool(mode)), int(not mode)): 

491 #try to preserve shift state: 

492 for s in (int(bool(shift)), int(not shift)): 

493 #group is comparatively easier to toggle (one function call): 

494 for g in (int(bool(group)), int(not group)): 

495 level = int(g)*4 + int(m)*2 + int(s)*1 

496 levels.append(level) 

497 kmlog("will try levels: %s", levels) 

498 for level in levels: 

499 keycode = self.keycode_translation.get((keyname, level)) 

500 if keycode: 

501 keysyms = self.keycode_mappings.get(keycode) 

502 klog("=%i (level=%i, shift=%s, mode=%i, keysyms=%s)", keycode, level, shift, mode, keysyms) 

503 level0 = levels[0] 

504 uq_keysyms = set(keysyms) 

505 if len(uq_keysyms)<=1 or (len(keysyms)>level0 and keysyms[level0]==""): 

506 #if the keysym we would match for this keycode is 'NoSymbol', 

507 #then we can probably ignore it ('NoSymbol' shows up as "") 

508 #same if there's only one actual keysym for this keycode 

509 kmlog("not toggling any modifiers state for keysyms=%s", keysyms) 

510 break 

511 def toggle_modifier(mod): 

512 keynames = self.keynames_for_mod.get(mod) 

513 if keyname in keynames: 

514 kmlog("not toggling '%s' since '%s' should deal with it", mod, keyname) 

515 #the keycode we're returning is for this modifier, 

516 #assume that this will end up doing what is needed 

517 return 

518 if mod in modifiers: 

519 kmlog("removing '%s' from modifiers", mod) 

520 modifiers.remove(mod) 

521 else: 

522 kmlog("adding '%s' to modifiers", mod) 

523 modifiers.append(mod) 

524 #keypad overrules shift state (see #2702): 

525 if keyname.startswith("KP_"): 

526 if numlock_modifier and not numlock: 

527 toggle_modifier(numlock_modifier) 

528 elif (level & 1) ^ shift: 

529 #shift state does not match 

530 toggle_modifier("shift") 

531 if int(bool(level & 2)) ^ mode: 

532 #try to set / unset mode: 

533 for mod, keynames in self.keynames_for_mod.items(): 

534 if "ISO_Level3_Shift" in keynames or "Mode_switch" in keynames: 

535 #found mode switch modified 

536 toggle_modifier(mod) 

537 break 

538 rgroup = level//4 

539 if rgroup!=group: 

540 kmlog("switching group from %i to %i", group, rgroup) 

541 break 

542 #this should not find anything new?: 

543 if keycode is None: 

544 keycode = self.keycode_translation.get(keyname, -1) 

545 klog("=%i, %i (keyname translation)", keycode, rgroup) 

546 if keycode is None: 

547 #last resort, find using the keyval: 

548 display = Gdk.Display.get_default() 

549 keymap = Gdk.Keymap.get_for_display(display) 

550 b, entries = keymap.get_entries_for_keyval(keyval) 

551 if b and entries: 

552 for entry in entries: 

553 if entry.group==group: 

554 return entry.keycode, entry.group 

555 return keycode, rgroup 

556 

557 

558 def get_current_mask(self): 

559 current_mask = get_default_root_window().get_pointer()[-1] 

560 return mask_to_names(current_mask, self.modifier_map) 

561 

562 def make_keymask_match(self, modifier_list, ignored_modifier_keycode=None, ignored_modifier_keynames=None): 

563 """ 

564 Given a list of modifiers that should be set, try to press the right keys 

565 to make the server's modifier list match it. 

566 Things to take into consideration: 

567 * xkbmap_mod_managed is a list of modifiers which are "server-managed": 

568 these never show up in the client's modifier list as it is not aware of them, 

569 so we just always leave them as they are and rely on some client key event to toggle them. 

570 ie: "num" on win32, which is toggled by the "Num_Lock" key presses. 

571 * when called from '_handle_key', we ignore the modifier key which may be pressed 

572 or released as it should be set by that key press event. 

573 * when called from mouse position/click/focus events we ignore 'xkbmap_mod_pointermissing' 

574 which is set by the client to indicate modifiers which are missing from mouse events. 

575 ie: on win32, "lock" is missing. 

576 (we know this is not a keyboard event because ignored_modifier_keynames is None..) 

577 * if the modifier is a "nuisance" one ("lock", "num", "scroll") then we must 

578 simulate a full keypress (down then up). 

579 * some modifiers can be set by multiple keys ("shift" by both "Shift_L" and "Shift_R" for example) 

580 so we try to find the matching modifier in the currently pressed keys (keys_pressed) 

581 to make sure we unpress the right one. 

582 """ 

583 if not self.keynames_for_mod: 

584 log("make_keymask_match: ignored as keynames_for_mod not assigned yet") 

585 return 

586 if ignored_modifier_keynames is None: 

587 #this is not a keyboard event, ignore modifiers in "mod_pointermissing" 

588 def is_ignored(modifier, _modifier_keynames): 

589 return modifier in (self.xkbmap_mod_pointermissing or []) 

590 else: 

591 #keyboard event: ignore the keynames specified 

592 #(usually the modifier key being pressed/unpressed) 

593 def is_ignored(_modifier, modifier_keynames): 

594 return len(set(modifier_keynames or []) & set(ignored_modifier_keynames or []))>0 

595 

596 def filtered_modifiers_set(modifiers): 

597 m = set() 

598 mm = self.xkbmap_mod_managed or () 

599 for modifier in modifiers: 

600 modifier = bytestostr(modifier) 

601 if modifier in mm: 

602 log("modifier is server managed: %s", modifier) 

603 continue 

604 keynames = self.keynames_for_mod.get(modifier) 

605 if is_ignored(modifier, keynames): 

606 log("modifier '%s' ignored (in ignored keynames=%s)", modifier, keynames) 

607 continue 

608 m.add(modifier) 

609 log("filtered_modifiers_set(%s)=%s", modifiers, m) 

610 return m 

611 

612 def change_mask(modifiers, press, info): 

613 failed = [] 

614 for modifier in modifiers: 

615 modifier = bytestostr(modifier) 

616 keynames = self.keynames_for_mod.get(modifier) 

617 if not keynames: 

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

619 log.error(" known modifiers: %s", csv(self.keynames_for_mod.keys())) 

620 continue 

621 #find the keycodes that match the keynames for this modifier 

622 keycodes = [] 

623 #log.info("keynames(%s)=%s", modifier, keynames) 

624 for keyname in keynames: 

625 if keyname in self.keys_pressed.values(): 

626 #found the key which was pressed to set this modifier 

627 for keycode, name in self.keys_pressed.items(): 

628 if name==keyname: 

629 log("found the key pressed for %s: %s", modifier, name) 

630 keycodes.insert(0, keycode) 

631 keycodes_for_keyname = self.keycodes_for_modifier_keynames.get(keyname) 

632 if keycodes_for_keyname: 

633 for keycode in keycodes_for_keyname: 

634 if keycode not in keycodes: 

635 keycodes.append(keycode) 

636 if ignored_modifier_keycode is not None and ignored_modifier_keycode in keycodes: 

637 log("modifier '%s' ignored (ignored keycode=%s)", modifier, ignored_modifier_keycode) 

638 continue 

639 #nuisance keys (lock, num, scroll) are toggled by a 

640 #full key press + key release (so act accordingly in the loop below) 

641 nuisance = modifier in self.xkbmap_mod_nuisance 

642 log("keynames(%s)=%s, keycodes=%s, nuisance=%s, nuisance keys=%s", modifier, keynames, keycodes, nuisance, self.xkbmap_mod_nuisance) 

643 modkeycode = None 

644 if not press: 

645 #since we want to unpress something, 

646 #let's try the keycodes we know are pressed first: 

647 kdown = X11Keyboard.get_keycodes_down() 

648 pressed = [x for x in keycodes if x in kdown] 

649 others = [x for x in keycodes if x not in kdown] 

650 keycodes = pressed+others 

651 for keycode in keycodes: 

652 if nuisance: 

653 X11Keyboard.xtest_fake_key(keycode, True) 

654 X11Keyboard.xtest_fake_key(keycode, False) 

655 else: 

656 X11Keyboard.xtest_fake_key(keycode, press) 

657 new_mask = self.get_current_mask() 

658 success = (modifier in new_mask)==press 

659 if success: 

660 modkeycode = keycode 

661 log("change_mask(%s) %s modifier '%s' using keycode %s", info, modifier_list, modifier, keycode) 

662 break #we're done for this modifier 

663 log("%s %s with keycode %s did not work", info, modifier, keycode) 

664 if press and not nuisance: 

665 log(" trying to unpress it!") 

666 X11Keyboard.xtest_fake_key(keycode, False) 

667 #maybe doing the full keypress (down+up) worked: 

668 new_mask = self.get_current_mask() 

669 if (modifier in new_mask)==press: 

670 break 

671 log("change_mask(%s) %s modifier '%s' using keycode %s, success: %s", info, modifier_list, modifier, keycode, success) 

672 if not modkeycode: 

673 failed.append(modifier) 

674 log("change_mask(%s, %s, %s) failed=%s", modifiers, press, info, failed) 

675 return failed 

676 

677 with xsync: 

678 current = filtered_modifiers_set(self.get_current_mask()) 

679 wanted = filtered_modifiers_set(modifier_list or []) 

680 if current==wanted: 

681 return 

682 log("make_keymask_match(%s) current mask: %s, wanted: %s, ignoring=%s/%s, keys_pressed=%s", modifier_list, current, wanted, ignored_modifier_keycode, ignored_modifier_keynames, self.keys_pressed) 

683 fr = change_mask(current.difference(wanted), False, "remove") 

684 fa = change_mask(wanted.difference(current), True, "add") 

685 if not fr or fa: 

686 return 

687 if fr: 

688 log.warn("Warning: failed to remove the following modifiers:") 

689 log.warn(" %s", csv(fr)) 

690 if fa: 

691 log.warn("Warning: failed to add the following modifiers:") 

692 log.warn(" %s", csv(fa)) 

693 #this should never happen.. but if it does? 

694 #something didn't work, use the big hammer and start again from scratch: 

695 log.warn(" keys still pressed=%s", X11Keyboard.get_keycodes_down()) 

696 X11Keyboard.unpress_all_keys() 

697 log.warn(" doing a full keyboard reset, keys now pressed=%s", X11Keyboard.get_keycodes_down()) 

698 #and try to set the modifiers one last time: 

699 current = filtered_modifiers_set(self.get_current_mask()) 

700 change_mask(current.difference(wanted), False, "remove") 

701 change_mask(wanted.difference(current), True, "add")