@@ -60,6 +60,12 @@ def __init__(self, **kwargs):
6060
6161
6262class 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