Skip to content

Commit 58c33b6

Browse files
committed
Fixed keyboard handling in the Kivy example. Created cef key mappings
for about 200 keys (including shift and right alt combinations). Introduced "keyboard_mode" option, can be set to either "local" or "global".
1 parent 1dde33f commit 58c33b6

1 file changed

Lines changed: 224 additions & 45 deletions

File tree

  • cefpython/cef3/linux/binaries_64bit

cefpython/cef3/linux/binaries_64bit/kivy_.py

Lines changed: 224 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@ def __init__(self, **kwargs):
6060

6161

6262
class CefBrowser(Widget):
63+
64+
# Keyboard mode: "global" or "local".
65+
# 1. Global mode forwards keys to CEF all the time.
66+
# 2. Local mode forwards keys to CEF only when an editable
67+
# control is focused (input type=text or textarea).
68+
keyboard_mode = "global"
6369

6470
'''Represent a browser widget for kivy, which can be used like a normal widget.
6571
'''
@@ -164,30 +170,52 @@ def start_cef(self, start_url='http://google.com'):
164170
# The browserWidget instance is required in OnLoadingStateChange().
165171
self.browser.SetUserData("browserWidget", self)
166172

173+
if self.keyboard_mode == "global":
174+
self.request_keyboard()
175+
167176

168177
def request_keyboard(self):
169178
print("request_keyboard()")
170179
self._keyboard = EventLoop.window.request_keyboard(
171180
self.release_keyboard, self)
172181
self._keyboard.bind(on_key_down=self.on_key_down)
173182
self._keyboard.bind(on_key_up=self.on_key_up)
183+
self.is_shift1 = False
184+
self.is_shift2 = False
185+
self.is_ctrl1 = False
186+
self.is_ctrl2 = False
187+
self.is_alt1 = False
188+
self.is_alt2 = False
174189

175190

176191
def release_keyboard(self):
192+
self.is_shift1 = False
193+
self.is_shift2 = False
194+
self.is_ctrl1 = False
195+
self.is_ctrl2 = False
196+
self.is_alt1 = False
197+
self.is_alt2 = False
177198
if not self._keyboard:
178199
return
179200
print("release_keyboard()")
180201
self._keyboard.unbind(on_key_down=self.on_key_down)
181202
self._keyboard.unbind(on_key_up=self.on_key_up)
182203
self._keyboard.release()
183204

184-
205+
# Kivy does not provide modifiers in on_key_up, but these
206+
# must be sent to CEF as well.
207+
is_shift1 = False
208+
is_shift2 = False
209+
is_ctrl1 = False
210+
is_ctrl2 = False
211+
is_alt1 = False
212+
is_alt2 = False
213+
185214
def on_key_down(self, keyboard, keycode, text, modifiers):
186215
# Notes:
187-
# - right alt modifier is not sent by Kivy
188-
# (Polish characters don't work)
189-
print("on_key_down(): keycode = %s modifiers = %s" % (
190-
keycode, modifiers))
216+
# - right alt modifier is not sent by Kivy through modifiers param.
217+
# print("on_key_down(): keycode = %s, text = %s, modifiers = %s" % (
218+
# keycode, text, modifiers))
191219
if keycode[0] == 27:
192220
# On escape release the keyboard, see the injected
193221
# javascript in OnLoadStart().
@@ -204,32 +232,180 @@ def on_key_down(self, keyboard, keycode, text, modifiers):
204232
if "capslock" in modifiers:
205233
cefModifiers |= cefpython.EVENTFLAG_CAPS_LOCK_ON
206234
# print("on_key_down(): cefModifiers = %s" % cefModifiers)
235+
cef_keycode = self.translate_to_cef_keycode(keycode[0])
207236
keyEvent = {
208237
"type": cefpython.KEYEVENT_RAWKEYDOWN,
209-
"native_key_code": keycode[0],
238+
"native_key_code": cef_keycode,
210239
"modifiers": cefModifiers
211240
}
212241
# print("keydown keyEvent: %s" % keyEvent)
213242
self.browser.SendKeyEvent(keyEvent)
243+
if keycode[0] == 304:
244+
self.is_shift1 = True
245+
elif keycode[0] == 303:
246+
self.is_shift2 = True
247+
elif keycode[0] == 306:
248+
self.is_ctrl1 = True
249+
elif keycode[0] == 305:
250+
self.is_ctrl2 = True
251+
elif keycode[0] == 308:
252+
self.is_alt1 = True
253+
elif keycode[0] == 313:
254+
self.is_alt2 = True
214255

215256

216257
def on_key_up(self, keyboard, keycode):
217258
# print("on_key_up(): keycode = %s" % (keycode,))
218259
cefModifiers = cefpython.EVENTFLAG_NONE
260+
if self.is_shift1 or self.is_shift2:
261+
cefModifiers |= cefpython.EVENTFLAG_SHIFT_DOWN
262+
if self.is_ctrl1 or self.is_ctrl2:
263+
cefModifiers |= cefpython.EVENTFLAG_CONTROL_DOWN
264+
if self.is_alt1:
265+
cefModifiers |= cefpython.EVENTFLAG_ALT_DOWN
266+
# Capslock todo.
267+
cef_keycode = self.translate_to_cef_keycode(keycode[0])
219268
keyEvent = {
220269
"type": cefpython.KEYEVENT_KEYUP,
221-
"native_key_code": keycode[0],
270+
"native_key_code": cef_keycode,
222271
"modifiers": cefModifiers
223272
}
224273
# print("keyup keyEvent: %s" % keyEvent)
225274
self.browser.SendKeyEvent(keyEvent)
226275
keyEvent = {
227276
"type": cefpython.KEYEVENT_CHAR,
228-
"native_key_code": keycode[0],
277+
"native_key_code": cef_keycode,
229278
"modifiers": cefModifiers
230279
}
231280
# print("char keyEvent: %s" % keyEvent)
232281
self.browser.SendKeyEvent(keyEvent)
282+
if keycode[0] == 304:
283+
self.is_shift1 = False
284+
elif keycode[0] == 303:
285+
self.is_shift2 = False
286+
elif keycode[0] == 306:
287+
self.is_ctrl1 = False
288+
elif keycode[0] == 305:
289+
self.is_ctrl2 = False
290+
elif keycode[0] == 308:
291+
self.is_alt1 = False
292+
elif keycode[0] == 313:
293+
self.is_alt2 = False
294+
295+
296+
def translate_to_cef_keycode(self, keycode):
297+
# TODO: this works on Linux, but on Windows the key
298+
# mappings will probably be different.
299+
# TODO: what if the Kivy keyboard layout is changed
300+
# from qwerty to azerty? (F1 > options..)
301+
cef_keycode = keycode
302+
if self.is_alt2:
303+
# The key mappings here for right alt were tested
304+
# with the utf-8 charset on a webpage. If the charset
305+
# is different there is a chance they won't work correctly.
306+
alt2_map = {
307+
# tilde
308+
"96":172,
309+
# 0-9 (48..57)
310+
"48":125, "49":185, "50":178, "51":179, "52":188,
311+
"53":189, "54":190, "55":123, "56":91, "57":93,
312+
# minus
313+
"45":92,
314+
# a-z (97..122)
315+
"97":433, "98":2771, "99":486, "100":240, "101":490,
316+
"102":496, "103":959, "104":689, "105":2301, "106":65121,
317+
"107":930, "108":435, "109":181, "110":497, "111":243,
318+
"112":254, "113":64, "114":182, "115":438, "116":956,
319+
"117":2302, "118":2770, "119":435, "120":444, "121":2299,
320+
"122":447,
321+
}
322+
if str(keycode) in alt2_map:
323+
cef_keycode = alt2_map[str(keycode)]
324+
else:
325+
print("Kivy to CEF key mapping not found (right alt), " \
326+
"key code = %s" % keycode)
327+
shift_alt2_map = {
328+
# tilde
329+
"96":172,
330+
# 0-9 (48..57)
331+
"48":176, "49":161, "50":2755, "51":163, "52":36,
332+
"53":2756, "54":2757, "55":2758, "56":2761, "57":177,
333+
# minus
334+
"45":191,
335+
# A-Z (97..122)
336+
"97":417, "98":2769, "99":454, "100":208, "101":458,
337+
"102":170, "103":957, "104":673, "105":697, "106":65122,
338+
"107":38, "108":419, "109":186, "110":465, "111":211,
339+
"112":222, "113":2009, "114":174, "115":422, "116":940,
340+
"117":2300, "118":2768, "119":419, "120":428, "121":165,
341+
"122":431,
342+
# special: <>? :" {}
343+
"44":215, "46":247, "47":65110,
344+
"59":65113, "39":65114,
345+
"91":65112, "93":65108,
346+
}
347+
if self.is_shift1 or self.is_shift2:
348+
if str(keycode) in shift_alt2_map:
349+
cef_keycode = shift_alt2_map[str(keycode)]
350+
else:
351+
print("Kivy to CEF key mapping not found " \
352+
"(shift + right alt), key code = %s" % keycode)
353+
elif self.is_shift1 or self.is_shift2:
354+
shift_map = {
355+
# tilde
356+
"96":126,
357+
# 0-9 (48..57)
358+
"48":41, "49":33, "50":64, "51":35, "52":36, "53":37,
359+
"54":94, "55":38, "56":42, "57":40,
360+
# minus, plus
361+
"45":95, "61":43,
362+
# a-z (97..122)
363+
"97":65, "98":66, "99":67, "100":68, "101":69, "102":70,
364+
"103":71, "104":72, "105":73, "106":74, "107":75, "108":76,
365+
"109":77, "110":78, "111":79, "112":80, "113":81, "114":82,
366+
"115":83, "116":84, "117":85, "118":86, "119":87, "120":88,
367+
"121":89, "122":90,
368+
# special: <>? :" {}
369+
"44":60, "46":62, "47":63,
370+
"59":58, "39":34,
371+
"91":123, "93":125,
372+
}
373+
if str(keycode) in shift_map:
374+
cef_keycode = shift_map[str(keycode)]
375+
# Other keys:
376+
other_keys_map = {
377+
# Escape
378+
"27":65307,
379+
# F1-F12
380+
"282":65470, "283":65471, "284":65472, "285":65473,
381+
"286":65474, "287":65475, "288":65476, "289":65477,
382+
"290":65478, "291":65479, "292":65480, "293":65481,
383+
# Tab
384+
"9":65289,
385+
# Left Shift, Right Shift
386+
"304":65505, "303":65506,
387+
# Left Ctrl, Right Ctrl
388+
"306":65507, "305": 65508,
389+
# Left Alt, Right Alt
390+
"308":65513, "313":65027,
391+
# Backspace
392+
"8":65288,
393+
# Enter
394+
"13":65293,
395+
# PrScr, ScrLck, Pause
396+
"316":65377, "302":65300, "19":65299,
397+
# Insert, Delete,
398+
# Home, End,
399+
# Pgup, Pgdn
400+
"277":65379, "127":65535,
401+
"278":65360, "279":65367,
402+
"280":65365, "281":65366,
403+
# Arrows (left, up, right, down)
404+
"276":65361, "273":65362, "275":65363, "274":65364,
405+
}
406+
if str(keycode) in other_keys_map:
407+
cef_keycode = other_keys_map[str(keycode)]
408+
return cef_keycode
233409

234410

235411
def go_forward(self):
@@ -283,52 +459,55 @@ def __init__(self, texture, parent):
283459

284460
def OnLoadStart(self, browser, frame):
285461
print("OnLoadStart(): injecting focus listeners for text controls")
286-
# The logic is similar to the one found in kivy-berkelium:
287-
# https://github.com/kivy/kivy-berkelium/blob/master/berkelium/__init__.py
288-
jsCode = """
289-
var __kivy__keyboard_requested = false;
290-
function __kivy__keyboard_interval() {
291-
var element = document.activeElement;
292-
if (!element) {
293-
return;
294-
}
295-
var tag = element.tagName;
296-
var type = element.type;
297-
// <input> with an empty type is a text type by default!
298-
if (tag == "INPUT" && (type == "" || type == "text"
299-
|| type == "password") || tag == "TEXTAREA") {
300-
if (!__kivy__keyboard_requested) {
301-
__kivy__request_keyboard();
302-
__kivy__keyboard_requested = true;
462+
browserWidget = browser.GetUserData("browserWidget")
463+
if browserWidget and browserWidget.keyboard_mode == "local":
464+
# The logic is similar to the one found in kivy-berkelium:
465+
# https://github.com/kivy/kivy-berkelium/blob/master/berkelium/__init__.py
466+
jsCode = """
467+
var __kivy__keyboard_requested = false;
468+
function __kivy__keyboard_interval() {
469+
var element = document.activeElement;
470+
if (!element) {
471+
return;
472+
}
473+
var tag = element.tagName;
474+
var type = element.type;
475+
if (tag == "INPUT" && (type == "" || type == "text"
476+
|| type == "password") || tag == "TEXTAREA") {
477+
if (!__kivy__keyboard_requested) {
478+
__kivy__request_keyboard();
479+
__kivy__keyboard_requested = true;
480+
}
481+
return;
482+
}
483+
if (__kivy__keyboard_requested) {
484+
__kivy__release_keyboard();
485+
__kivy__keyboard_requested = false;
303486
}
304-
return;
305-
}
306-
if (__kivy__keyboard_requested) {
307-
__kivy__release_keyboard();
308-
__kivy__keyboard_requested = false;
309-
}
310-
}
311-
function __kivy__on_escape() {
312-
if (document.activeElement) {
313-
document.activeElement.blur();
314487
}
315-
if (__kivy__keyboard_requested) {
316-
__kivy__release_keyboard();
317-
__kivy__keyboard_requested = false;
488+
function __kivy__on_escape() {
489+
if (document.activeElement) {
490+
document.activeElement.blur();
491+
}
492+
if (__kivy__keyboard_requested) {
493+
__kivy__release_keyboard();
494+
__kivy__keyboard_requested = false;
495+
}
318496
}
319-
}
320-
setInterval(__kivy__keyboard_interval, 13);
321-
"""
322-
frame.ExecuteJavascript(jsCode,
323-
"kivy_.py > ClientHandler > OnLoadStart")
497+
setInterval(__kivy__keyboard_interval, 13);
498+
"""
499+
frame.ExecuteJavascript(jsCode,
500+
"kivy_.py > ClientHandler > OnLoadStart")
324501

325502

326503
def OnLoadingStateChange(self, browser, isLoading, canGoBack,
327504
canGoForward):
328505
print("OnLoadingStateChange(): isLoading = %s" % isLoading)
329-
if isLoading and browser.GetUserData("browserWidget"):
506+
browserWidget = browser.GetUserData("browserWidget")
507+
if isLoading and browserWidget \
508+
and browserWidget.keyboard_mode == "local":
330509
# Release keyboard when navigating to a new page.
331-
browser.GetUserData("browserWidget").release_keyboard()
510+
browserWidget.release_keyboard()
332511
pass
333512

334513

0 commit comments

Comments
 (0)