From 27a1e0a6b216ce1cd7b8a49fa75cd3c678a935f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Wed, 10 Jun 2020 19:10:37 +0200 Subject: [PATCH 01/27] add tor browser support --- .docker/build | 4 +- .docker/files/tor-browser/Dockerfile | 28 + .docker/files/tor-browser/openbox.xml | 763 +++++++++++++++++++++ .docker/files/tor-browser/supervisord.conf | 13 + 4 files changed, 807 insertions(+), 1 deletion(-) create mode 100644 .docker/files/tor-browser/Dockerfile create mode 100644 .docker/files/tor-browser/openbox.xml create mode 100644 .docker/files/tor-browser/supervisord.conf diff --git a/.docker/build b/.docker/build index 067b09a1..1c19ff55 100755 --- a/.docker/build +++ b/.docker/build @@ -73,7 +73,8 @@ build() { build_image "xfce4"; \ build_image "jwm"; \ build_image "firefox"; \ - build_image "chromium"; + build_image "chromium"; \ + build_image "tor-browser"; fi sudo docker images nurdism/neko @@ -90,6 +91,7 @@ push() { sudo docker push nurdism/neko:jwm sudo docker push nurdism/neko:firefox sudo docker push nurdism/neko:chromium + sudo docker push nurdism/neko:tor-browser fi } diff --git a/.docker/files/tor-browser/Dockerfile b/.docker/files/tor-browser/Dockerfile new file mode 100644 index 00000000..f8b76d3f --- /dev/null +++ b/.docker/files/tor-browser/Dockerfile @@ -0,0 +1,28 @@ +FROM nurdism/neko:openbox + +# +# install dependencies +RUN set -eux; apt-get update; \ + apt-get install -y --no-install-recommends curl xz-utils file libgtk-3-0 libdbus-glib-1-2; \ + # + # clean up + apt-get clean -y; \ + rm -rf /var/lib/apt/lists/* /var/cache/apt/* + +WORKDIR /home/neko +USER neko + +# +# download TOR browser +RUN DOWNLOAD_URI="$(curl -s -N https://www.torproject.org/download/ | grep -Po -m 1 '(?=(dist/torbrowser)).*(?<=.tar.xz)')"; \ + echo "Downloading $DOWNLOAD_URI"; \ + curl -sSL -o tor.tar.xz "https://www.torproject.org/$DOWNLOAD_URI"; \ + tar -xvJf tor.tar.xz; \ + rm -f tor.tar.xz*; + +USER root + +# +# copy configuation file +COPY .docker/files/tor-browser/supervisord.conf /etc/neko/supervisord/tor-browser.conf +COPY .docker/files/tor-browser/openbox.xml /etc/neko/openbox.xml diff --git a/.docker/files/tor-browser/openbox.xml b/.docker/files/tor-browser/openbox.xml new file mode 100644 index 00000000..f288d364 --- /dev/null +++ b/.docker/files/tor-browser/openbox.xml @@ -0,0 +1,763 @@ + + + + + + + + 10 + 20 + + + + + + no + true + yes + normal + + + + + yes + + no + + yes + + no + + 200 + + no + + + + + Smart + +
yes
+ + Primary + + 1 + +
+ + + Clearlooks + NLIMC + + yes + yes + + sans + 8 + + bold + + normal + + + + sans + 8 + + bold + + normal + + + + sans + 9 + + normal + + normal + + + + sans + 9 + + normal + + normal + + + + sans + 9 + + bold + + normal + + + + sans + 9 + + bold + + normal + + + + + + + 1 + 1 + + + + 875 + + + + + yes + Nonpixel + + Center + + + + + 10 + + 10 + + + + + + + 0 + 0 + 0 + 0 + + + + TopLeft + + 0 + 0 + no + Above + + Vertical + + no + 300 + + 300 + + Middle + + + + + C-g + + + + leftno + + + rightno + + + upno + + + downno + + + leftno + + + rightno + + + upno + + + downno + + + 1 + + + 2 + + + 3 + + + 4 + + + + + + + + + + + + + + + + + + + + scrot -s + + + + + + + + + + + + + + + + + + + + + + + + yesyes + + + + + + + + + + + + right + + + + + left + + + + + up + + + + + down + + + + + + + + true + Konqueror + + kfmclient openProfile filemanagement + + + + + scrot + + + + + 1 + + 500 + + 400 + + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + previous + + + next + + + previous + + + next + + + previous + + + next + + + + + + + + + + + + + + no + + + + + + + + + + + yes + + + + + + + + + + + + + + + + + + + + + + + + + + + top + + + + + + left + + + + + + right + + + + + + bottom + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + vertical + + + horizontal + + + + + + + + + + + + + + + + + previous + + + next + + + + previous + + + next + + + previous + + + next + + + + + + + + + + + + + + + + + + + + previous + + + next + + + previous + + + next + + + + + + + + + + + menu.xml + 200 + + no + + 100 + + 400 + + yes + + yes + + + + + + + +
diff --git a/.docker/files/tor-browser/supervisord.conf b/.docker/files/tor-browser/supervisord.conf new file mode 100644 index 00000000..49cd15b6 --- /dev/null +++ b/.docker/files/tor-browser/supervisord.conf @@ -0,0 +1,13 @@ +[program:tor-browser] +environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s" +command=/home/neko/tor-browser_en-US/Browser/start-tor-browser --display=%(ENV_DISPLAY)s --setDefaultBrowser --window-size 1280,720 +autorestart=true +priority=800 +user=%(ENV_USER)s +stdout_logfile=/var/log/neko/tor-browser.log +stdout_logfile_maxbytes=100MB +stdout_logfile_backups=10 +redirect_stderr=true +stderr_logfile=/var/log/neko/tor-browser.err.log +stderr_logfile_maxbytes=100MB +stderr_logfile_backups=10 From 19c6b8c4ae0590e494b94de7c995bc1efbbd10dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sat, 13 Jun 2020 13:29:16 +0200 Subject: [PATCH 02/27] test if XKeysymToKeycode returns zero --- server/internal/xorg/xorg.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/server/internal/xorg/xorg.c b/server/internal/xorg/xorg.c index a835f7a0..17733c09 100644 --- a/server/internal/xorg/xorg.c +++ b/server/internal/xorg/xorg.c @@ -97,8 +97,11 @@ void XButton(unsigned int button, int down) { void XKey(unsigned long key, int down) { Display *display = getXDisplay(); KeyCode code = XKeysymToKeycode(display, key); - XTestFakeKeyEvent(display, code, down, CurrentTime); - XSync(display, 0); + + if (code != 0) { + XTestFakeKeyEvent(display, code, down, CurrentTime); + XSync(display, 0); + } } void XClipboardSet(char *src) { From aa1fedcb24a85ba9eadd79031bbc25f498a4fdf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sat, 13 Jun 2020 16:21:11 +0200 Subject: [PATCH 03/27] map KeySym at client side --- client/src/components/video.vue | 28 +- client/src/neko/keyboard.ts | 139 +++++ server/internal/remote/manager.go | 8 +- server/internal/types/remote.go | 8 +- server/internal/webrtc/handle.go | 16 +- server/internal/xorg/keycode/button.go | 45 -- server/internal/xorg/keycode/keys.go | 696 ------------------------- server/internal/xorg/xorg.c | 20 +- server/internal/xorg/xorg.go | 189 +------ 9 files changed, 199 insertions(+), 950 deletions(-) create mode 100644 client/src/neko/keyboard.ts delete mode 100644 server/internal/xorg/keycode/button.go delete mode 100644 server/internal/xorg/keycode/keys.go diff --git a/client/src/components/video.vue b/client/src/components/video.vue index 9bb8ba15..85cb689a 100644 --- a/client/src/components/video.vue +++ b/client/src/components/video.vue @@ -142,6 +142,8 @@ import Emote from './emote.vue' import Resolution from './resolution.vue' + import { mapKeyboardEventToKeySym } from '@/neko/keyboard.ts' + @Component({ name: 'neko-video', components: { @@ -449,7 +451,7 @@ return } this.onMousePos(e) - this.$client.sendData('mousedown', { key: e.button }) + this.$client.sendData('mousedown', { key: e.button + 1 }) } onMouseUp(e: MouseEvent) { @@ -457,7 +459,7 @@ return } this.onMousePos(e) - this.$client.sendData('mouseup', { key: e.button }) + this.$client.sendData('mouseup', { key: e.button + 1 }) } onMouseMove(e: MouseEvent) { @@ -477,30 +479,12 @@ this.focused = false } - // frick you firefox - getCode(e: KeyboardEvent): number { - let key = e.keyCode - if (key === 59 && (e.key === ';' || e.key === ':')) { - key = 186 - } - - if (key === 61 && (e.key === '=' || e.key === '+')) { - key = 187 - } - - if (key === 173 && (e.key === '-' || e.key === '_')) { - key = 189 - } - - return key - } - onKeyDown(e: KeyboardEvent) { if (!this.focused || !this.hosting || this.locked) { return } - let key = this.getCode(e) + let key = mapKeyboardEventToKeySym(e) this.$client.sendData('keydown', { key }) this.activeKeys.add(key) } @@ -510,7 +494,7 @@ return } - let key = this.getCode(e) + let key = mapKeyboardEventToKeySym(e) this.$client.sendData('keyup', { key }) this.activeKeys.delete(key) } diff --git a/client/src/neko/keyboard.ts b/client/src/neko/keyboard.ts new file mode 100644 index 00000000..2070f152 --- /dev/null +++ b/client/src/neko/keyboard.ts @@ -0,0 +1,139 @@ +const keyMap: Record = { + 'Backspace': 0xff08, + 'Tab': 0xFF09, + 'Enter': 0xFF0D, + 'ShiftLeft': 0xFFE1, + 'ShiftRight': 0xFFE2, + 'ControlLeft': 0xFFE3, + 'ControlRight': 0xFFE4, + 'AltLeft': 0xFFE9, // 2 keys - one code? + 'AltRight': 0xFFE9, // 2 keys - one code? + 'MetaLeft': 0xFFEB, // Super key, or Meta key? + 'MetaRight': 0xFF67, // Super key, or Meta key? + //'PrintScreen': , + //'ContextMenu': , + 'Pause': 0xFF13, + 'CapsLock': 0xFFE5, + 'Escape': 0xFF1B, + 'Space': 0x0020, + 'PageUp': 0xFF55, + 'PageDown': 0xFF56, + 'End': 0xFF57, + 'Home': 0xFF50, + 'ArrowLeft': 0xFF51, + 'ArrowUp': 0xFF52, + 'ArrowRight': 0xFF53, + 'ArrowDown': 0xFF54, + 'Insert': 0xFF63, + 'Delete': 0xFFFF, + 'Digit0': 0x0030, + 'Digit1': 0x0031, + 'Digit2': 0x0032, + 'Digit3': 0x0033, + 'Digit4': 0x0034, + 'Digit5': 0x0035, + 'Digit6': 0x0036, + 'Digit7': 0x0037, + 'Digit8': 0x0038, + 'Digit9': 0x0039, + 'KeyA': 0x0061, + 'KeyB': 0x0062, + 'KeyC': 0x0063, + 'KeyD': 0x0064, + 'KeyE': 0x0065, + 'KeyF': 0x0066, + 'KeyG': 0x0067, + 'KeyH': 0x0068, + 'KeyI': 0x0069, + 'KeyJ': 0x006a, + 'KeyK': 0x006b, + 'KeyL': 0x006c, + 'KeyM': 0x006d, + 'KeyN': 0x006e, + 'KeyO': 0x006f, + 'KeyP': 0x0070, + 'KeyQ': 0x0071, + 'KeyR': 0x0072, + 'KeyS': 0x0073, + 'KeyT': 0x0074, + 'KeyU': 0x0075, + 'KeyV': 0x0076, + 'KeyW': 0x0077, + 'KeyX': 0x0078, + 'KeyZ': 0x0079, + 'KeyY': 0x007a, + 'NumpadEnter': 0XFF8D, + 'NumpadMultiply': 0xFFAA, + 'NumpadAdd': 0xFFAB, + 'NumpadSubtract': 0xFFAD, + 'NumpadDivide': 0xFFAF, + 'F1': 0xFFBE, + 'F2': 0xFFBF, + 'F3': 0xFFC0, + 'F4': 0xFFC1, + 'F5': 0xFFC2, + 'F6': 0xFFC3, + 'F7': 0xFFC4, + 'F8': 0xFFC5, + 'F9': 0xFFC6, + 'F10': 0xFFC7, + 'F11': 0xFFC8, + 'F12': 0xFFC9, + 'NumLock': 0xFF7F, + 'ScrollLock': 0xFF14, + + // l10s causes many problems + 'Semicolon': 0x003b, + 'Comma': 0x002c, + 'Slash': 0x002f, + 'Period': 0x002e, + 'Minus': 0x002d, + 'Backquote': 0x0060, + 'BracketLeft': 0x005b, + 'Backslash': 0x005c, + 'BracketRight': 0x005d, + 'Quote': 0x0022, + 'Equal': 0x003d, +}; + +// If Num Lock is enabled +const keyMap_numlock_enabled: Record = { + 'Numpad0': 0xFFB0, + 'Numpad1': 0xFFB1, + 'Numpad2': 0xFFB2, + 'Numpad3': 0xFFB3, + 'Numpad4': 0xFFB4, + 'Numpad5': 0xFFB5, + 'Numpad6': 0xFFB6, + 'Numpad7': 0xFFB7, + 'Numpad8': 0xFFB8, + 'Numpad9': 0xFFB9, + 'NumpadDecimal': 0xFFAE, +}; + +// If Num Lock is disabled +const keyMap_numlock_disabled: Record = { + 'Numpad0': 0xFFB0, + 'Numpad1': 0xFFB1, + 'Numpad2': 0xFFB2, + 'Numpad3': 0xFFB3, + 'Numpad4': 0xFFB4, + 'Numpad5': 0xFFB5, + 'Numpad6': 0xFFB6, + 'Numpad7': 0xFFB7, + 'Numpad8': 0xFFB8, + 'Numpad9': 0xFFB9, + 'NumpadDecimal': 0xFF9F, +}; + +export function mapKeyboardEventToKeySym(e: KeyboardEvent): number { + if (e.code in keyMap) { + return keyMap[e.code]; + } + + if (e.getModifierState('NumLock')) { + return keyMap_numlock_enabled[e.code]; + } else { + return keyMap_numlock_disabled[e.code]; + } +} diff --git a/server/internal/remote/manager.go b/server/internal/remote/manager.go index 31c72cb1..b9be8982 100644 --- a/server/internal/remote/manager.go +++ b/server/internal/remote/manager.go @@ -183,19 +183,19 @@ func (manager *RemoteManager) Scroll(x, y int) { xorg.Scroll(x, y) } -func (manager *RemoteManager) ButtonDown(code int) (*types.Button, error) { +func (manager *RemoteManager) ButtonDown(code int) error { return xorg.ButtonDown(code) } -func (manager *RemoteManager) KeyDown(code int) (*types.Key, error) { +func (manager *RemoteManager) KeyDown(code int) error { return xorg.KeyDown(code) } -func (manager *RemoteManager) ButtonUp(code int) (*types.Button, error) { +func (manager *RemoteManager) ButtonUp(code int) error { return xorg.ButtonUp(code) } -func (manager *RemoteManager) KeyUp(code int) (*types.Key, error) { +func (manager *RemoteManager) KeyUp(code int) error { return xorg.KeyUp(code) } diff --git a/server/internal/types/remote.go b/server/internal/types/remote.go index ef6ef0ec..2814d04f 100644 --- a/server/internal/types/remote.go +++ b/server/internal/types/remote.go @@ -15,10 +15,10 @@ type RemoteManager interface { ScreenConfigurations() map[int]ScreenConfiguration Move(x, y int) Scroll(x, y int) - ButtonDown(code int) (*Button, error) - KeyDown(code int) (*Key, error) - ButtonUp(code int) (*Button, error) - KeyUp(code int) (*Key, error) + ButtonDown(code int) error + KeyDown(code int) error + ButtonUp(code int) error + KeyUp(code int) error ReadClipboard() string WriteClipboard(data string) ResetKeys() diff --git a/server/internal/webrtc/handle.go b/server/internal/webrtc/handle.go index 321d54c1..a84b5bc9 100644 --- a/server/internal/webrtc/handle.go +++ b/server/internal/webrtc/handle.go @@ -85,21 +85,21 @@ func (manager *WebRTCManager) handle(id string, msg webrtc.DataChannelMessage) e } if payload.Key < 8 { - button, err := manager.remote.ButtonDown(int(payload.Key)) + err := manager.remote.ButtonDown(int(payload.Key)) if err != nil { manager.logger.Warn().Err(err).Msg("key down failed") return nil } - manager.logger.Debug().Msgf("button down %s(%d)", button.Name, payload.Key) + manager.logger.Debug().Msgf("button down %d", payload.Key) } else { - key, err := manager.remote.KeyDown(int(payload.Key)) + err := manager.remote.KeyDown(int(payload.Key)) if err != nil { manager.logger.Warn().Err(err).Msg("key down failed") return nil } - manager.logger.Debug().Msgf("key down %s(%d)", key.Name, payload.Key) + manager.logger.Debug().Msgf("key down %d", payload.Key) } break @@ -111,21 +111,21 @@ func (manager *WebRTCManager) handle(id string, msg webrtc.DataChannelMessage) e } if payload.Key < 8 { - button, err := manager.remote.ButtonUp(int(payload.Key)) + err := manager.remote.ButtonUp(int(payload.Key)) if err != nil { manager.logger.Warn().Err(err).Msg("button up failed") return nil } - manager.logger.Debug().Msgf("button up %s(%d)", button.Name, payload.Key) + manager.logger.Debug().Msgf("button up %d", payload.Key) } else { - key, err := manager.remote.KeyUp(int(payload.Key)) + err := manager.remote.KeyUp(int(payload.Key)) if err != nil { manager.logger.Warn().Err(err).Msg("keyup failed") return nil } - manager.logger.Debug().Msgf("key up %s(%d)", key.Name, payload.Key) + manager.logger.Debug().Msgf("key up %d", payload.Key) } break case OP_KEY_CLK: diff --git a/server/internal/xorg/keycode/button.go b/server/internal/xorg/keycode/button.go deleted file mode 100644 index 00c35775..00000000 --- a/server/internal/xorg/keycode/button.go +++ /dev/null @@ -1,45 +0,0 @@ -package keycode - -import "n.eko.moe/neko/internal/types" - -var LEFT_BUTTON = types.Button{ - Name: "LEFT", - Code: 0, - Keysym: 1, -} - -var CENTER_BUTTON = types.Button{ - Name: "CENTER", - Code: 1, - Keysym: 2, -} - -var RIGHT_BUTTON = types.Button{ - Name: "RIGHT", - Code: 2, - Keysym: 3, -} - -var SCROLL_UP_BUTTON = types.Button{ - Name: "SCROLL_UP", - Code: 3, - Keysym: 4, -} - -var SCROLL_DOWN_BUTTON = types.Button{ - Name: "SCROLL_DOWN", - Code: 4, - Keysym: 5, -} - -var SCROLL_LEFT_BUTTON = types.Button{ - Name: "SCROLL_LEFT", - Code: 5, - Keysym: 6, -} - -var SCROLL_RIGHT_BUTTON = types.Button{ - Name: "SCROLL_RIGHT", - Code: 6, - Keysym: 7, -} diff --git a/server/internal/xorg/keycode/keys.go b/server/internal/xorg/keycode/keys.go deleted file mode 100644 index 56bae71e..00000000 --- a/server/internal/xorg/keycode/keys.go +++ /dev/null @@ -1,696 +0,0 @@ -package keycode - -import "n.eko.moe/neko/internal/types" - -var BACKSPACE = types.Key{ - Name: "BACKSPACE", - Value: "BackSpace", - Code: 8, - Keysym: int(0xff08), -} - -var TAB = types.Key{ - Name: "TAB", - Value: "Tab", - Code: 9, - Keysym: int(0xFF09), -} - -var CLEAR = types.Key{ - Name: "CLEAR", - Value: "Clear", - Code: 12, - Keysym: int(0xFF0B), -} - -var ENTER = types.Key{ - Name: "ENTER", - Value: "Enter", - Code: 13, - Keysym: int(0xFF0D), -} - -var SHIFT = types.Key{ - Name: "SHIFT", - Value: "Shift", - Code: 16, - Keysym: int(0xFFE1), -} - -var CTRL = types.Key{ - Name: "CTRL", - Value: "Ctrl", - Code: 17, - Keysym: int(0xFFE3), -} - -var ALT = types.Key{ - Name: "ALT", - Value: "Alt", - Code: 18, - Keysym: int(0xFFE9), -} - -var PAUSE = types.Key{ - Name: "PAUSE", - Value: "Pause", - Code: 19, - Keysym: int(0xFF13), -} - -var CAPS_LOCK = types.Key{ - Name: "CAPS_LOCK", - Value: "Caps Lock", - Code: 20, - Keysym: int(0xFFE5), -} - -var ESCAPE = types.Key{ - Name: "ESCAPE", - Value: "Escape", - Code: 27, - Keysym: int(0xFF1B), -} - -var SPACE = types.Key{ - Name: "SPACE", - Value: " ", - Code: 32, - Keysym: int(0x0020), -} - -var PAGE_UP = types.Key{ - Name: "PAGE_UP", - Value: "Page Up", - Code: 33, - Keysym: int(0xFF55), -} - -var PAGE_DOWN = types.Key{ - Name: "PAGE_DOWN", - Value: "Page Down", - Code: 34, - Keysym: int(0xFF56), -} - -var END = types.Key{ - Name: "END", - Value: "End", - Code: 35, - Keysym: int(0xFF57), -} - -var HOME = types.Key{ - Name: "HOME", - Value: "Home", - Code: 36, - Keysym: int(0xFF50), -} - -var LEFT_ARROW = types.Key{ - Name: "LEFT_ARROW", - Value: "Left Arrow", - Code: 37, - Keysym: int(0xFF51), -} - -var UP_ARROW = types.Key{ - Name: "UP_ARROW", - Value: "Up Arrow", - Code: 38, - Keysym: int(0xFF52), -} - -var RIGHT_ARROW = types.Key{ - Name: "RIGHT_ARROW", - Value: "Right Arrow", - Code: 39, - Keysym: int(0xFF53), -} - -var DOWN_ARROW = types.Key{ - Name: "DOWN_ARROW", - Value: "Down Arrow", - Code: 40, - Keysym: int(0xFF54), -} - -var INSERT = types.Key{ - Name: "INSERT", - Value: "Insert", - Code: 45, - Keysym: int(0xFF63), -} - -var DELETE = types.Key{ - Name: "DELETE", - Value: "Delete", - Code: 46, - Keysym: int(0xFFFF), -} - -var KEY_0 = types.Key{ - Name: "KEY_0", - Value: "0", - Code: 48, - Keysym: int(0x0030), -} - -var KEY_1 = types.Key{ - Name: "KEY_1", - Value: "1", - Code: 49, - Keysym: int(0x0031), -} - -var KEY_2 = types.Key{ - Name: "KEY_2", - Value: "2", - Code: 50, - Keysym: int(0x0032), -} - -var KEY_3 = types.Key{ - Name: "KEY_3", - Value: "3", - Code: 51, - Keysym: int(0x0033), -} - -var KEY_4 = types.Key{ - Name: "KEY_4", - Value: "4", - Code: 52, - Keysym: int(0x0034), -} - -var KEY_5 = types.Key{ - Name: "KEY_5", - Value: "5", - Code: 53, - Keysym: int(0x0035), -} - -var KEY_6 = types.Key{ - Name: "KEY_6", - Value: "6", - Code: 54, - Keysym: int(0x0036), -} - -var KEY_7 = types.Key{ - Name: "KEY_7", - Value: "7", - Code: 55, - Keysym: int(0x0037), -} - -var KEY_8 = types.Key{ - Name: "KEY_8", - Value: "8", - Code: 56, - Keysym: int(0x0038), -} - -var KEY_9 = types.Key{ - Name: "KEY_9", - Value: "9", - Code: 57, - Keysym: int(0x0039), -} - -var KEY_A = types.Key{ - Name: "KEY_A", - Value: "a", - Code: 65, - Keysym: int(0x0061), -} - -var KEY_B = types.Key{ - Name: "KEY_B", - Value: "b", - Code: 66, - Keysym: int(0x0062), -} - -var KEY_C = types.Key{ - Name: "KEY_C", - Value: "c", - Code: 67, - Keysym: int(0x0063), -} - -var KEY_D = types.Key{ - Name: "KEY_D", - Value: "d", - Code: 68, - Keysym: int(0x0064), -} - -var KEY_E = types.Key{ - Name: "KEY_E", - Value: "e", - Code: 69, - Keysym: int(0x0065), -} - -var KEY_F = types.Key{ - Name: "KEY_F", - Value: "f", - Code: 70, - Keysym: int(0x0066), -} - -var KEY_G = types.Key{ - Name: "KEY_G", - Value: "g", - Code: 71, - Keysym: int(0x0067), -} - -var KEY_H = types.Key{ - Name: "KEY_H", - Value: "h", - Code: 72, - Keysym: int(0x0068), -} - -var KEY_I = types.Key{ - Name: "KEY_I", - Value: "i", - Code: 73, - Keysym: int(0x0069), -} - -var KEY_J = types.Key{ - Name: "KEY_J", - Value: "j", - Code: 74, - Keysym: int(0x006a), -} - -var KEY_K = types.Key{ - Name: "KEY_K", - Value: "k", - Code: 75, - Keysym: int(0x006b), -} - -var KEY_L = types.Key{ - Name: "KEY_L", - Value: "l", - Code: 76, - Keysym: int(0x006c), -} - -var KEY_M = types.Key{ - Name: "KEY_M", - Value: "m", - Code: 77, - Keysym: int(0x006d), -} - -var KEY_N = types.Key{ - Name: "KEY_N", - Value: "n", - Code: 78, - Keysym: int(0x006e), -} - -var KEY_O = types.Key{ - Name: "KEY_O", - Value: "o", - Code: 79, - Keysym: int(0x006f), -} - -var KEY_P = types.Key{ - Name: "KEY_P", - Value: "p", - Code: 80, - Keysym: int(0x0070), -} - -var KEY_Q = types.Key{ - Name: "KEY_Q", - Value: "q", - Code: 81, - Keysym: int(0x0071), -} - -var KEY_R = types.Key{ - Name: "KEY_R", - Value: "r", - Code: 82, - Keysym: int(0x0072), -} - -var KEY_S = types.Key{ - Name: "KEY_S", - Value: "s", - Code: 83, - Keysym: int(0x0073), -} - -var KEY_T = types.Key{ - Name: "KEY_T", - Value: "t", - Code: 84, - Keysym: int(0x0074), -} - -var KEY_U = types.Key{ - Name: "KEY_U", - Value: "u", - Code: 85, - Keysym: int(0x0075), -} - -var KEY_V = types.Key{ - Name: "KEY_V", - Value: "v", - Code: 86, - Keysym: int(0x0076), -} - -var KEY_W = types.Key{ - Name: "KEY_W", - Value: "w", - Code: 87, - Keysym: int(0x0077), -} - -var KEY_X = types.Key{ - Name: "KEY_X", - Value: "x", - Code: 88, - Keysym: int(0x0078), -} - -var KEY_Y = types.Key{ - Name: "KEY_Y", - Value: "y", - Code: 89, - Keysym: int(0x0079), -} - -var KEY_Z = types.Key{ - Name: "KEY_Z", - Value: "z", - Code: 90, - Keysym: int(0x007a), -} - -var WIN_LEFT = types.Key{ - Name: "WIN_LEFT", - Value: "Win Left", - Code: 91, - Keysym: int(0xFFEB), -} - -var WIN_RIGHT = types.Key{ - Name: "WIN_RIGHT", - Value: "Win Right", - Code: 92, - Keysym: int(0xFF67), -} - -var PAD_0 = types.Key{ - Name: "PAD_0", - Value: "Num Pad 0", - Code: 96, - Keysym: int(0xFFB0), -} - -var PAD_1 = types.Key{ - Name: "PAD_1", - Value: "Num Pad 1", - Code: 97, - Keysym: int(0xFFB1), -} - -var PAD_2 = types.Key{ - Name: "PAD_2", - Value: "Num Pad 2", - Code: 98, - Keysym: int(0xFFB2), -} - -var PAD_3 = types.Key{ - Name: "PAD_3", - Value: "Num Pad 3", - Code: 99, - Keysym: int(0xFFB3), -} - -var PAD_4 = types.Key{ - Name: "PAD_4", - Value: "Num Pad 4", - Code: 100, - Keysym: int(0xFFB4), -} - -var PAD_5 = types.Key{ - Name: "PAD_5", - Value: "Num Pad 5", - Code: 101, - Keysym: int(0xFFB5), -} - -var PAD_6 = types.Key{ - Name: "PAD_6", - Value: "Num Pad 6", - Code: 102, - Keysym: int(0xFFB6), -} - -var PAD_7 = types.Key{ - Name: "PAD_7", - Value: "Num Pad 7", - Code: 103, - Keysym: int(0xFFB7), -} - -var PAD_8 = types.Key{ - Name: "PAD_8", - Value: "Num Pad 8", - Code: 104, - Keysym: int(0xFFB8), -} - -var PAD_9 = types.Key{ - Name: "PAD_9", - Value: "Num Pad 9", - Code: 105, - Keysym: int(0xFFB9), -} - -var MULTIPLY = types.Key{ - Name: "MULTIPLY", - Value: "*", - Code: 106, - Keysym: int(0xFFAA), -} - -var ADD = types.Key{ - Name: "ADD", - Value: "+", - Code: 107, - Keysym: int(0xFFAB), -} - -var SUBTRACT = types.Key{ - Name: "SUBTRACT", - Value: "-", - Code: 109, - Keysym: int(0xFFAD), -} - -var DECIMAL = types.Key{ - Name: "DECIMAL", - Value: ".", - Code: 110, - Keysym: int(0xFFAE), -} - -var DIVIDE = types.Key{ - Name: "DIVIDE", - Value: "/", - Code: 111, - Keysym: int(0xFFAF), -} - -var KEY_F1 = types.Key{ - Name: "KEY_F1", - Value: "f1", - Code: 112, - Keysym: int(0xFFBE), -} - -var KEY_F2 = types.Key{ - Name: "KEY_F2", - Value: "f2", - Code: 113, - Keysym: int(0xFFBF), -} - -var KEY_F3 = types.Key{ - Name: "KEY_F3", - Value: "f3", - Code: 114, - Keysym: int(0xFFC0), -} - -var KEY_F4 = types.Key{ - Name: "KEY_F4", - Value: "f4", - Code: 115, - Keysym: int(0xFFC1), -} - -var KEY_F5 = types.Key{ - Name: "KEY_F5", - Value: "f5", - Code: 116, - Keysym: int(0xFFC2), -} - -var KEY_F6 = types.Key{ - Name: "KEY_F6", - Value: "f6", - Code: 117, - Keysym: int(0xFFC3), -} - -var KEY_F7 = types.Key{ - Name: "KEY_F7", - Value: "f7", - Code: 118, - Keysym: int(0xFFC4), -} - -var KEY_F8 = types.Key{ - Name: "KEY_F8", - Value: "f8", - Code: 119, - Keysym: int(0xFFC5), -} - -var KEY_F9 = types.Key{ - Name: "KEY_F9", - Value: "f9", - Code: 120, - Keysym: int(0xFFC6), -} - -var KEY_F10 = types.Key{ - Name: "KEY_F10", - Value: "f10", - Code: 121, - Keysym: int(0xFFC7), -} - -var KEY_F11 = types.Key{ - Name: "KEY_F11", - Value: "f11", - Code: 122, - Keysym: int(0xFFC8), -} - -var KEY_F12 = types.Key{ - Name: "KEY_F12", - Value: "f12", - Code: 123, - Keysym: int(0xFFC9), -} - -var NUM_LOCK = types.Key{ - Name: "NUM_LOCK", - Value: "Num Lock", - Code: 144, - Keysym: int(0xFF7F), -} - -var SCROLL_LOCK = types.Key{ - Name: "SCROLL_LOCK", - Value: "Scroll Lock", - Code: 145, - Keysym: int(0xFF14), -} - -var SEMI_COLON = types.Key{ - Name: "SEMI_COLON", - Value: ";", - Code: 186, - Keysym: int(0x003b), -} - -var EQUAL = types.Key{ - Name: "EQUAL", - Value: "=", - Code: 187, - Keysym: int(0x003d), -} - -var COMMA = types.Key{ - Name: "COMMA", - Value: ",", - Code: 188, - Keysym: int(0x002c), -} - -var DASH = types.Key{ - Name: "DASH", - Value: "-", - Code: 189, - Keysym: int(0x002d), -} - -var PERIOD = types.Key{ - Name: "PERIOD", - Value: ".", - Code: 190, - Keysym: int(0x002e), -} - -var FORWARD_SLASH = types.Key{ - Name: "FORWARD_SLASH", - Value: "/", - Code: 191, - Keysym: int(0x002f), -} - -var GRAVE = types.Key{ - Name: "GRAVE", - Value: "`", - Code: 192, - Keysym: int(0x0060), -} - -var OPEN_BRACKET = types.Key{ - Name: "OPEN_BRACKET", - Value: "[", - Code: 219, - Keysym: int(0x005b), -} - -var BACK_SLASH = types.Key{ - Name: "BACK_SLASH", - Value: "\\", - Code: 220, - Keysym: int(0x005c), -} - -var CLOSE_BRAKET = types.Key{ - Name: "CLOSE_BRAKET", - Value: "]", - Code: 221, - Keysym: int(0x005d), -} - -var SINGLE_QUOTE = types.Key{ - Name: "SINGLE_QUOTE", - Value: "'", - Code: 222, - Keysym: int(0x0022), -} diff --git a/server/internal/xorg/xorg.c b/server/internal/xorg/xorg.c index 17733c09..81f7d1b9 100644 --- a/server/internal/xorg/xorg.c +++ b/server/internal/xorg/xorg.c @@ -89,18 +89,22 @@ void XScroll(int x, int y) { } void XButton(unsigned int button, int down) { - Display *display = getXDisplay(); - XTestFakeButtonEvent(display, button, down, CurrentTime); - XSync(display, 0); + if (button != 0) { + Display *display = getXDisplay(); + XTestFakeButtonEvent(display, button, down, CurrentTime); + XSync(display, 0); + } } void XKey(unsigned long key, int down) { - Display *display = getXDisplay(); - KeyCode code = XKeysymToKeycode(display, key); + if (key != 0) { + Display *display = getXDisplay(); + KeyCode code = XKeysymToKeycode(display, key); - if (code != 0) { - XTestFakeKeyEvent(display, code, down, CurrentTime); - XSync(display, 0); + if (code != 0) { + XTestFakeKeyEvent(display, code, down, CurrentTime); + XSync(display, 0); + } } } diff --git a/server/internal/xorg/xorg.go b/server/internal/xorg/xorg.go index f6169bff..072c43a8 100644 --- a/server/internal/xorg/xorg.go +++ b/server/internal/xorg/xorg.go @@ -1,9 +1,3 @@ -// NOTE: I have no fucking clue what I'm doing with this, -// it works, but I am positive I'm doing this very wrong... -// should I be freeing these strings? does go gc them? -// pretty sure this *isn't* thread safe either.... /shrug -// if you know a better way to get this done *please* make a pr <3 - package xorg /* @@ -21,125 +15,14 @@ import ( "unsafe" "n.eko.moe/neko/internal/types" - "n.eko.moe/neko/internal/xorg/keycode" ) var ScreenConfigurations = make(map[int]types.ScreenConfiguration) var debounce = make(map[int]time.Time) -var buttons = make(map[int]types.Button) -var keys = make(map[int]types.Key) var mu = sync.Mutex{} func init() { - keys[keycode.BACKSPACE.Code] = keycode.BACKSPACE - keys[keycode.TAB.Code] = keycode.TAB - keys[keycode.CLEAR.Code] = keycode.CLEAR - keys[keycode.ENTER.Code] = keycode.ENTER - keys[keycode.SHIFT.Code] = keycode.SHIFT - keys[keycode.CTRL.Code] = keycode.CTRL - keys[keycode.ALT.Code] = keycode.ALT - keys[keycode.PAUSE.Code] = keycode.PAUSE - keys[keycode.CAPS_LOCK.Code] = keycode.CAPS_LOCK - keys[keycode.ESCAPE.Code] = keycode.ESCAPE - keys[keycode.SPACE.Code] = keycode.SPACE - keys[keycode.PAGE_UP.Code] = keycode.PAGE_UP - keys[keycode.PAGE_DOWN.Code] = keycode.PAGE_DOWN - keys[keycode.END.Code] = keycode.END - keys[keycode.HOME.Code] = keycode.HOME - keys[keycode.LEFT_ARROW.Code] = keycode.LEFT_ARROW - keys[keycode.UP_ARROW.Code] = keycode.UP_ARROW - keys[keycode.RIGHT_ARROW.Code] = keycode.RIGHT_ARROW - keys[keycode.DOWN_ARROW.Code] = keycode.DOWN_ARROW - keys[keycode.INSERT.Code] = keycode.INSERT - keys[keycode.DELETE.Code] = keycode.DELETE - keys[keycode.KEY_0.Code] = keycode.KEY_0 - keys[keycode.KEY_1.Code] = keycode.KEY_1 - keys[keycode.KEY_2.Code] = keycode.KEY_2 - keys[keycode.KEY_3.Code] = keycode.KEY_3 - keys[keycode.KEY_4.Code] = keycode.KEY_4 - keys[keycode.KEY_5.Code] = keycode.KEY_5 - keys[keycode.KEY_6.Code] = keycode.KEY_6 - keys[keycode.KEY_7.Code] = keycode.KEY_7 - keys[keycode.KEY_8.Code] = keycode.KEY_8 - keys[keycode.KEY_9.Code] = keycode.KEY_9 - keys[keycode.KEY_A.Code] = keycode.KEY_A - keys[keycode.KEY_B.Code] = keycode.KEY_B - keys[keycode.KEY_C.Code] = keycode.KEY_C - keys[keycode.KEY_D.Code] = keycode.KEY_D - keys[keycode.KEY_E.Code] = keycode.KEY_E - keys[keycode.KEY_F.Code] = keycode.KEY_F - keys[keycode.KEY_G.Code] = keycode.KEY_G - keys[keycode.KEY_H.Code] = keycode.KEY_H - keys[keycode.KEY_I.Code] = keycode.KEY_I - keys[keycode.KEY_J.Code] = keycode.KEY_J - keys[keycode.KEY_K.Code] = keycode.KEY_K - keys[keycode.KEY_L.Code] = keycode.KEY_L - keys[keycode.KEY_M.Code] = keycode.KEY_M - keys[keycode.KEY_N.Code] = keycode.KEY_N - keys[keycode.KEY_O.Code] = keycode.KEY_O - keys[keycode.KEY_P.Code] = keycode.KEY_P - keys[keycode.KEY_Q.Code] = keycode.KEY_Q - keys[keycode.KEY_R.Code] = keycode.KEY_R - keys[keycode.KEY_S.Code] = keycode.KEY_S - keys[keycode.KEY_T.Code] = keycode.KEY_T - keys[keycode.KEY_U.Code] = keycode.KEY_U - keys[keycode.KEY_V.Code] = keycode.KEY_V - keys[keycode.KEY_W.Code] = keycode.KEY_W - keys[keycode.KEY_X.Code] = keycode.KEY_X - keys[keycode.KEY_Y.Code] = keycode.KEY_Y - keys[keycode.KEY_Z.Code] = keycode.KEY_Z - keys[keycode.WIN_LEFT.Code] = keycode.WIN_LEFT - keys[keycode.WIN_RIGHT.Code] = keycode.WIN_RIGHT - keys[keycode.PAD_0.Code] = keycode.PAD_0 - keys[keycode.PAD_1.Code] = keycode.PAD_1 - keys[keycode.PAD_2.Code] = keycode.PAD_2 - keys[keycode.PAD_3.Code] = keycode.PAD_3 - keys[keycode.PAD_4.Code] = keycode.PAD_4 - keys[keycode.PAD_5.Code] = keycode.PAD_5 - keys[keycode.PAD_6.Code] = keycode.PAD_6 - keys[keycode.PAD_7.Code] = keycode.PAD_7 - keys[keycode.PAD_8.Code] = keycode.PAD_8 - keys[keycode.PAD_9.Code] = keycode.PAD_9 - keys[keycode.MULTIPLY.Code] = keycode.MULTIPLY - keys[keycode.ADD.Code] = keycode.ADD - keys[keycode.SUBTRACT.Code] = keycode.SUBTRACT - keys[keycode.DECIMAL.Code] = keycode.DECIMAL - keys[keycode.DIVIDE.Code] = keycode.DIVIDE - keys[keycode.KEY_F1.Code] = keycode.KEY_F1 - keys[keycode.KEY_F2.Code] = keycode.KEY_F2 - keys[keycode.KEY_F3.Code] = keycode.KEY_F3 - keys[keycode.KEY_F4.Code] = keycode.KEY_F4 - keys[keycode.KEY_F5.Code] = keycode.KEY_F5 - keys[keycode.KEY_F6.Code] = keycode.KEY_F6 - keys[keycode.KEY_F7.Code] = keycode.KEY_F7 - keys[keycode.KEY_F8.Code] = keycode.KEY_F8 - keys[keycode.KEY_F9.Code] = keycode.KEY_F9 - keys[keycode.KEY_F10.Code] = keycode.KEY_F10 - keys[keycode.KEY_F11.Code] = keycode.KEY_F11 - keys[keycode.KEY_F12.Code] = keycode.KEY_F12 - keys[keycode.NUM_LOCK.Code] = keycode.NUM_LOCK - keys[keycode.SCROLL_LOCK.Code] = keycode.SCROLL_LOCK - keys[keycode.SEMI_COLON.Code] = keycode.SEMI_COLON - keys[keycode.EQUAL.Code] = keycode.EQUAL - keys[keycode.COMMA.Code] = keycode.COMMA - keys[keycode.DASH.Code] = keycode.DASH - keys[keycode.PERIOD.Code] = keycode.PERIOD - keys[keycode.FORWARD_SLASH.Code] = keycode.FORWARD_SLASH - keys[keycode.GRAVE.Code] = keycode.GRAVE - keys[keycode.OPEN_BRACKET.Code] = keycode.OPEN_BRACKET - keys[keycode.BACK_SLASH.Code] = keycode.BACK_SLASH - keys[keycode.CLOSE_BRAKET.Code] = keycode.CLOSE_BRAKET - keys[keycode.SINGLE_QUOTE.Code] = keycode.SINGLE_QUOTE - - buttons[keycode.LEFT_BUTTON.Code] = keycode.LEFT_BUTTON - buttons[keycode.CENTER_BUTTON.Code] = keycode.CENTER_BUTTON - buttons[keycode.RIGHT_BUTTON.Code] = keycode.RIGHT_BUTTON - buttons[keycode.SCROLL_UP_BUTTON.Code] = keycode.SCROLL_UP_BUTTON - buttons[keycode.SCROLL_DOWN_BUTTON.Code] = keycode.SCROLL_DOWN_BUTTON - buttons[keycode.SCROLL_LEFT_BUTTON.Code] = keycode.SCROLL_LEFT_BUTTON - buttons[keycode.SCROLL_RIGHT_BUTTON.Code] = keycode.SCROLL_RIGHT_BUTTON - C.XGetScreenConfigurations() } @@ -167,80 +50,60 @@ func Scroll(x, y int) { C.XScroll(C.int(x), C.int(y)) } -func ButtonDown(code int) (*types.Button, error) { +func ButtonDown(code int) error { mu.Lock() defer mu.Unlock() - button, ok := buttons[code] - if !ok { - return nil, fmt.Errorf("invalid button %v", code) - } - if _, ok := debounce[code]; ok { - return nil, fmt.Errorf("debounced button %v(%v)", button.Name, code) + return fmt.Errorf("debounced button %v", code) } debounce[code] = time.Now() - C.XButton(C.uint(button.Keysym), C.int(1)) - return &button, nil + C.XButton(C.uint(code), C.int(1)) + return nil } -func KeyDown(code int) (*types.Key, error) { +func KeyDown(code int) error { mu.Lock() defer mu.Unlock() - key, ok := keys[code] - if !ok { - return nil, fmt.Errorf("invalid key %v", code) - } - if _, ok := debounce[code]; ok { - return nil, fmt.Errorf("debounced key %v(%v)", key.Name, code) + return fmt.Errorf("debounced key %v", code) } debounce[code] = time.Now() - C.XKey(C.ulong(key.Keysym), C.int(1)) - return &key, nil + C.XKey(C.ulong(code), C.int(1)) + return nil } -func ButtonUp(code int) (*types.Button, error) { +func ButtonUp(code int) error { mu.Lock() defer mu.Unlock() - button, ok := buttons[code] - if !ok { - return nil, fmt.Errorf("invalid button %v", code) - } - if _, ok := debounce[code]; !ok { - return nil, fmt.Errorf("debounced button %v(%v)", button.Name, code) + return fmt.Errorf("debounced button %v", code) } delete(debounce, code) - C.XButton(C.uint(button.Keysym), C.int(0)) - return &button, nil + C.XButton(C.uint(code), C.int(0)) + return nil } -func KeyUp(code int) (*types.Key, error) { +func KeyUp(code int) error { mu.Lock() defer mu.Unlock() - key, ok := keys[code] - if !ok { - return nil, fmt.Errorf("invalid key %v", code) - } - if _, ok := debounce[code]; !ok { - return nil, fmt.Errorf("debounced key %v(%v)", key.Name, code) + return fmt.Errorf("debounced key %v", code) } delete(debounce, code) - C.XKey(C.ulong(key.Keysym), C.int(0)) - return &key, nil + C.XKey(C.ulong(code), C.int(0)) + return nil } func ReadClipboard() string { @@ -264,31 +127,31 @@ func WriteClipboard(data string) { } func ResetKeys() { - for key := range debounce { - if key < 8 { - ButtonUp(key) + for code := range debounce { + if code < 8 { + ButtonUp(code) } else { - KeyUp(key) + KeyUp(code) } - delete(debounce, key) + delete(debounce, code) } } func CheckKeys(duration time.Duration) { t := time.Now() - for key, start := range debounce { + for code, start := range debounce { if t.Sub(start) < duration { continue } - if key < 8 { - ButtonUp(key) + if code < 8 { + ButtonUp(code) } else { - KeyUp(key) + KeyUp(code) } - delete(debounce, key) + delete(debounce, code) } } From 0fc847fa593c448a6864caa01cb1847687bced1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sat, 13 Jun 2020 18:03:07 +0200 Subject: [PATCH 04/27] fix key codes --- client/src/neko/keyboard.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/client/src/neko/keyboard.ts b/client/src/neko/keyboard.ts index 2070f152..39b89c4a 100644 --- a/client/src/neko/keyboard.ts +++ b/client/src/neko/keyboard.ts @@ -6,12 +6,12 @@ const keyMap: Record = { 'ShiftRight': 0xFFE2, 'ControlLeft': 0xFFE3, 'ControlRight': 0xFFE4, - 'AltLeft': 0xFFE9, // 2 keys - one code? - 'AltRight': 0xFFE9, // 2 keys - one code? + 'AltLeft': 0xFFE9, + 'AltRight': 0xFFEA, 'MetaLeft': 0xFFEB, // Super key, or Meta key? - 'MetaRight': 0xFF67, // Super key, or Meta key? - //'PrintScreen': , - //'ContextMenu': , + 'MetaRight': 0xFFEC, // Super key, or Meta key? + 'PrintScreen': 0xFF61, + 'ContextMenu': 0xFF67, 'Pause': 0xFF13, 'CapsLock': 0xFFE5, 'Escape': 0xFF1B, From ebec0fef9008324f0e7f4fe7f64a8e68146565a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sun, 14 Jun 2020 13:34:33 +0200 Subject: [PATCH 05/27] use local numlock --- client/src/neko/keyboard.ts | 64 +++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/client/src/neko/keyboard.ts b/client/src/neko/keyboard.ts index 39b89c4a..0d3f604d 100644 --- a/client/src/neko/keyboard.ts +++ b/client/src/neko/keyboard.ts @@ -96,34 +96,34 @@ const keyMap: Record = { 'Equal': 0x003d, }; -// If Num Lock is enabled -const keyMap_numlock_enabled: Record = { - 'Numpad0': 0xFFB0, - 'Numpad1': 0xFFB1, - 'Numpad2': 0xFFB2, - 'Numpad3': 0xFFB3, - 'Numpad4': 0xFFB4, - 'Numpad5': 0xFFB5, - 'Numpad6': 0xFFB6, - 'Numpad7': 0xFFB7, - 'Numpad8': 0xFFB8, - 'Numpad9': 0xFFB9, - 'NumpadDecimal': 0xFFAE, +// If Num Lock is enabled [Original KeySym, Non-NumPad key (on US kbd)] +const keyMap_numlock_enabled: Record> = { + 'Numpad0': [0xFFB0, 0x0030], + 'Numpad1': [0xFFB1, 0x0031], + 'Numpad2': [0xFFB2, 0x0032], + 'Numpad3': [0xFFB3, 0x0033], + 'Numpad4': [0xFFB4, 0x0034], + 'Numpad5': [0xFFB5, 0x0035], + 'Numpad6': [0xFFB6, 0x0036], + 'Numpad7': [0xFFB7, 0x0037], + 'Numpad8': [0xFFB8, 0x0038], + 'Numpad9': [0xFFB9, 0x0039], + 'NumpadDecimal': [0xFFAE, 0x002c] }; -// If Num Lock is disabled -const keyMap_numlock_disabled: Record = { - 'Numpad0': 0xFFB0, - 'Numpad1': 0xFFB1, - 'Numpad2': 0xFFB2, - 'Numpad3': 0xFFB3, - 'Numpad4': 0xFFB4, - 'Numpad5': 0xFFB5, - 'Numpad6': 0xFFB6, - 'Numpad7': 0xFFB7, - 'Numpad8': 0xFFB8, - 'Numpad9': 0xFFB9, - 'NumpadDecimal': 0xFF9F, +// If Num Lock is disabled [Original KeySym, Non-NumPad key (on US kbd)] +const keyMap_numlock_disabled: Record> = { + 'Numpad0': [0xFFB0, 0xff63], + 'Numpad1': [0xFFB1, 0xFF57], + 'Numpad2': [0xFFB2, 0xFF54], + 'Numpad3': [0xFFB3, 0xFF56], + 'Numpad4': [0xFFB4, 0xFF51], + 'Numpad5': [0xFFB5, 0xff0b], + 'Numpad6': [0xFFB6, 0xFF53], + 'Numpad7': [0xFFB7, 0xFF50], + 'Numpad8': [0xFFB8, 0xFF52], + 'Numpad9': [0xFFB9, 0xFF55], + 'NumpadDecimal': [0xFF9F, 0xFFFF], }; export function mapKeyboardEventToKeySym(e: KeyboardEvent): number { @@ -131,9 +131,17 @@ export function mapKeyboardEventToKeySym(e: KeyboardEvent): number { return keyMap[e.code]; } + //// Handle Num Lock at Server + //if (isNumLockActive)) { + // return keyMap_numlock_enabled[e.code][0]; + //} else { + // return keyMap_numlock_disabled[e.code][0]; + //} + + // Handle Num Lock at Client if (e.getModifierState('NumLock')) { - return keyMap_numlock_enabled[e.code]; + return keyMap_numlock_enabled[e.code][1]; } else { - return keyMap_numlock_disabled[e.code]; + return keyMap_numlock_disabled[e.code][1]; } } From fe90a9555fd8426f5751590c9b6da8517dea1fc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sun, 14 Jun 2020 13:37:59 +0200 Subject: [PATCH 06/27] send screen resolution after SessionConnected --- server/internal/websocket/session.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/server/internal/websocket/session.go b/server/internal/websocket/session.go index 2201ce53..23d71cdb 100644 --- a/server/internal/websocket/session.go +++ b/server/internal/websocket/session.go @@ -12,11 +12,6 @@ func (h *MessageHandler) SessionCreated(id string, session types.Session) error return err } - // send screen current resolution - if err := h.screenResolution(id, session); err != nil { - return err - } - if session.Admin() { // send screen configurations if admin if err := h.screenConfigurations(id, session); err != nil { @@ -37,6 +32,11 @@ func (h *MessageHandler) SessionConnected(id string, session types.Session) erro return err } + // send screen current resolution + if err := h.screenResolution(id, session); err != nil { + return err + } + // tell session there is a host host, ok := h.sessions.GetHost() if ok { From 4b378550c282b1220c5903b42fce0e36c8823902 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sun, 14 Jun 2020 13:38:55 +0200 Subject: [PATCH 07/27] xorg.ChangeScreenSize error handling --- server/internal/remote/manager.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/server/internal/remote/manager.go b/server/internal/remote/manager.go index 31c72cb1..f0af9508 100644 --- a/server/internal/remote/manager.go +++ b/server/internal/remote/manager.go @@ -102,10 +102,8 @@ func (manager *RemoteManager) StartStream() { if !xorg.ValidScreenSize(manager.config.ScreenWidth, manager.config.ScreenHeight, manager.config.ScreenRate) { manager.logger.Warn().Msgf("invalid screen option %dx%d@%d", manager.config.ScreenWidth, manager.config.ScreenHeight, manager.config.ScreenRate) - } else { - if err := xorg.ChangeScreenSize(manager.config.ScreenWidth, manager.config.ScreenHeight, manager.config.ScreenRate); err != nil { - manager.logger.Warn().Err(err).Msg("unable to change screen size") - } + } else if err := xorg.ChangeScreenSize(manager.config.ScreenWidth, manager.config.ScreenHeight, manager.config.ScreenRate); err != nil { + manager.logger.Warn().Err(err).Msg("unable to change screen size") } manager.createPipelines() From bce622f31f06631c393ed80a3bf54002884f8a61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Mon, 15 Jun 2020 15:54:40 +0200 Subject: [PATCH 08/27] add Guacamole Keyboard JS module --- client/public/Keyboard.js | 1513 +++++++++++++++++++++++++++++++++++++ client/public/index.html | 1 + 2 files changed, 1514 insertions(+) create mode 100644 client/public/Keyboard.js diff --git a/client/public/Keyboard.js b/client/public/Keyboard.js new file mode 100644 index 00000000..df90f89f --- /dev/null +++ b/client/public/Keyboard.js @@ -0,0 +1,1513 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var Guacamole = Guacamole || {}; + +/** + * Provides cross-browser and cross-keyboard keyboard for a specific element. + * Browser and keyboard layout variation is abstracted away, providing events + * which represent keys as their corresponding X11 keysym. + * + * @constructor + * @param {Element|Document} [element] + * The Element to use to provide keyboard events. If omitted, at least one + * Element must be manually provided through the listenTo() function for + * the Guacamole.Keyboard instance to have any effect. + */ +Guacamole.Keyboard = function Keyboard(element) { + + /** + * Reference to this Guacamole.Keyboard. + * @private + */ + var guac_keyboard = this; + + /** + * An integer value which uniquely identifies this Guacamole.Keyboard + * instance with respect to other Guacamole.Keyboard instances. + * + * @private + * @type {Number} + */ + var guacKeyboardID = Guacamole.Keyboard._nextID++; + + /** + * The name of the property which is added to event objects via markEvent() + * to note that they have already been handled by this Guacamole.Keyboard. + * + * @private + * @constant + * @type {String} + */ + var EVENT_MARKER = '_GUAC_KEYBOARD_HANDLED_BY_' + guacKeyboardID; + + /** + * Fired whenever the user presses a key with the element associated + * with this Guacamole.Keyboard in focus. + * + * @event + * @param {Number} keysym The keysym of the key being pressed. + * @return {Boolean} true if the key event should be allowed through to the + * browser, false otherwise. + */ + this.onkeydown = null; + + /** + * Fired whenever the user releases a key with the element associated + * with this Guacamole.Keyboard in focus. + * + * @event + * @param {Number} keysym The keysym of the key being released. + */ + this.onkeyup = null; + + /** + * Set of known platform-specific or browser-specific quirks which must be + * accounted for to properly interpret key events, even if the only way to + * reliably detect that quirk is to platform/browser-sniff. + * + * @private + * @type {Object.} + */ + var quirks = { + + /** + * Whether keyup events are universally unreliable. + * + * @type {Boolean} + */ + keyupUnreliable: false, + + /** + * Whether the Alt key is actually a modifier for typable keys and is + * thus never used for keyboard shortcuts. + * + * @type {Boolean} + */ + altIsTypableOnly: false, + + /** + * Whether we can rely on receiving a keyup event for the Caps Lock + * key. + * + * @type {Boolean} + */ + capsLockKeyupUnreliable: false + + }; + + // Set quirk flags depending on platform/browser, if such information is + // available + if (navigator && navigator.platform) { + + // All keyup events are unreliable on iOS (sadly) + if (navigator.platform.match(/ipad|iphone|ipod/i)) + quirks.keyupUnreliable = true; + + // The Alt key on Mac is never used for keyboard shortcuts, and the + // Caps Lock key never dispatches keyup events + else if (navigator.platform.match(/^mac/i)) { + quirks.altIsTypableOnly = true; + quirks.capsLockKeyupUnreliable = true; + } + + } + + /** + * A key event having a corresponding timestamp. This event is non-specific. + * Its subclasses should be used instead when recording specific key + * events. + * + * @private + * @constructor + */ + var KeyEvent = function() { + + /** + * Reference to this key event. + */ + var key_event = this; + + /** + * An arbitrary timestamp in milliseconds, indicating this event's + * position in time relative to other events. + * + * @type {Number} + */ + this.timestamp = new Date().getTime(); + + /** + * Whether the default action of this key event should be prevented. + * + * @type {Boolean} + */ + this.defaultPrevented = false; + + /** + * The keysym of the key associated with this key event, as determined + * by a best-effort guess using available event properties and keyboard + * state. + * + * @type {Number} + */ + this.keysym = null; + + /** + * Whether the keysym value of this key event is known to be reliable. + * If false, the keysym may still be valid, but it's only a best guess, + * and future key events may be a better source of information. + * + * @type {Boolean} + */ + this.reliable = false; + + /** + * Returns the number of milliseconds elapsed since this event was + * received. + * + * @return {Number} The number of milliseconds elapsed since this + * event was received. + */ + this.getAge = function() { + return new Date().getTime() - key_event.timestamp; + }; + + }; + + /** + * Information related to the pressing of a key, which need not be a key + * associated with a printable character. The presence or absence of any + * information within this object is browser-dependent. + * + * @private + * @constructor + * @augments Guacamole.Keyboard.KeyEvent + * @param {Number} keyCode The JavaScript key code of the key pressed. + * @param {String} keyIdentifier The legacy DOM3 "keyIdentifier" of the key + * pressed, as defined at: + * http://www.w3.org/TR/2009/WD-DOM-Level-3-Events-20090908/#events-Events-KeyboardEvent + * @param {String} key The standard name of the key pressed, as defined at: + * http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent + * @param {Number} location The location on the keyboard corresponding to + * the key pressed, as defined at: + * http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent + */ + var KeydownEvent = function(keyCode, keyIdentifier, key, location) { + + // We extend KeyEvent + KeyEvent.apply(this); + + /** + * The JavaScript key code of the key pressed. + * + * @type {Number} + */ + this.keyCode = keyCode; + + /** + * The legacy DOM3 "keyIdentifier" of the key pressed, as defined at: + * http://www.w3.org/TR/2009/WD-DOM-Level-3-Events-20090908/#events-Events-KeyboardEvent + * + * @type {String} + */ + this.keyIdentifier = keyIdentifier; + + /** + * The standard name of the key pressed, as defined at: + * http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent + * + * @type {String} + */ + this.key = key; + + /** + * The location on the keyboard corresponding to the key pressed, as + * defined at: + * http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent + * + * @type {Number} + */ + this.location = location; + + // If key is known from keyCode or DOM3 alone, use that + this.keysym = keysym_from_key_identifier(key, location) + || keysym_from_keycode(keyCode, location); + + /** + * Whether the keyup following this keydown event is known to be + * reliable. If false, we cannot rely on the keyup event to occur. + * + * @type {Boolean} + */ + this.keyupReliable = !quirks.keyupUnreliable; + + // DOM3 and keyCode are reliable sources if the corresponding key is + // not a printable key + if (this.keysym && !isPrintable(this.keysym)) + this.reliable = true; + + // Use legacy keyIdentifier as a last resort, if it looks sane + if (!this.keysym && key_identifier_sane(keyCode, keyIdentifier)) + this.keysym = keysym_from_key_identifier(keyIdentifier, location, guac_keyboard.modifiers.shift); + + // If a key is pressed while meta is held down, the keyup will + // never be sent in Chrome (bug #108404) + if (guac_keyboard.modifiers.meta && this.keysym !== 0xFFE7 && this.keysym !== 0xFFE8) + this.keyupReliable = false; + + // We cannot rely on receiving keyup for Caps Lock on certain platforms + else if (this.keysym === 0xFFE5 && quirks.capsLockKeyupUnreliable) + this.keyupReliable = false; + + // Determine whether default action for Alt+combinations must be prevented + var prevent_alt = !guac_keyboard.modifiers.ctrl && !quirks.altIsTypableOnly; + + // Determine whether default action for Ctrl+combinations must be prevented + var prevent_ctrl = !guac_keyboard.modifiers.alt; + + // We must rely on the (potentially buggy) keyIdentifier if preventing + // the default action is important + if ((prevent_ctrl && guac_keyboard.modifiers.ctrl) + || (prevent_alt && guac_keyboard.modifiers.alt) + || guac_keyboard.modifiers.meta + || guac_keyboard.modifiers.hyper) + this.reliable = true; + + // Record most recently known keysym by associated key code + recentKeysym[keyCode] = this.keysym; + + }; + + KeydownEvent.prototype = new KeyEvent(); + + /** + * Information related to the pressing of a key, which MUST be + * associated with a printable character. The presence or absence of any + * information within this object is browser-dependent. + * + * @private + * @constructor + * @augments Guacamole.Keyboard.KeyEvent + * @param {Number} charCode The Unicode codepoint of the character that + * would be typed by the key pressed. + */ + var KeypressEvent = function(charCode) { + + // We extend KeyEvent + KeyEvent.apply(this); + + /** + * The Unicode codepoint of the character that would be typed by the + * key pressed. + * + * @type {Number} + */ + this.charCode = charCode; + + // Pull keysym from char code + this.keysym = keysym_from_charcode(charCode); + + // Keypress is always reliable + this.reliable = true; + + }; + + KeypressEvent.prototype = new KeyEvent(); + + /** + * Information related to the pressing of a key, which need not be a key + * associated with a printable character. The presence or absence of any + * information within this object is browser-dependent. + * + * @private + * @constructor + * @augments Guacamole.Keyboard.KeyEvent + * @param {Number} keyCode The JavaScript key code of the key released. + * @param {String} keyIdentifier The legacy DOM3 "keyIdentifier" of the key + * released, as defined at: + * http://www.w3.org/TR/2009/WD-DOM-Level-3-Events-20090908/#events-Events-KeyboardEvent + * @param {String} key The standard name of the key released, as defined at: + * http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent + * @param {Number} location The location on the keyboard corresponding to + * the key released, as defined at: + * http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent + */ + var KeyupEvent = function(keyCode, keyIdentifier, key, location) { + + // We extend KeyEvent + KeyEvent.apply(this); + + /** + * The JavaScript key code of the key released. + * + * @type {Number} + */ + this.keyCode = keyCode; + + /** + * The legacy DOM3 "keyIdentifier" of the key released, as defined at: + * http://www.w3.org/TR/2009/WD-DOM-Level-3-Events-20090908/#events-Events-KeyboardEvent + * + * @type {String} + */ + this.keyIdentifier = keyIdentifier; + + /** + * The standard name of the key released, as defined at: + * http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent + * + * @type {String} + */ + this.key = key; + + /** + * The location on the keyboard corresponding to the key released, as + * defined at: + * http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent + * + * @type {Number} + */ + this.location = location; + + // If key is known from keyCode or DOM3 alone, use that + this.keysym = keysym_from_keycode(keyCode, location) + || keysym_from_key_identifier(key, location); // keyCode is still more reliable for keyup when dead keys are in use + + // Fall back to the most recently pressed keysym associated with the + // keyCode if the inferred key doesn't seem to actually be pressed + if (!guac_keyboard.pressed[this.keysym]) + this.keysym = recentKeysym[keyCode] || this.keysym; + + // Keyup is as reliable as it will ever be + this.reliable = true; + + }; + + KeyupEvent.prototype = new KeyEvent(); + + /** + * An array of recorded events, which can be instances of the private + * KeydownEvent, KeypressEvent, and KeyupEvent classes. + * + * @private + * @type {KeyEvent[]} + */ + var eventLog = []; + + /** + * Map of known JavaScript keycodes which do not map to typable characters + * to their X11 keysym equivalents. + * @private + */ + var keycodeKeysyms = { + 8: [0xFF08], // backspace + 9: [0xFF09], // tab + 12: [0xFF0B, 0xFF0B, 0xFF0B, 0xFFB5], // clear / KP 5 + 13: [0xFF0D], // enter + 16: [0xFFE1, 0xFFE1, 0xFFE2], // shift + 17: [0xFFE3, 0xFFE3, 0xFFE4], // ctrl + 18: [0xFFE9, 0xFFE9, 0xFE03], // alt + 19: [0xFF13], // pause/break + 20: [0xFFE5], // caps lock + 27: [0xFF1B], // escape + 32: [0x0020], // space + 33: [0xFF55, 0xFF55, 0xFF55, 0xFFB9], // page up / KP 9 + 34: [0xFF56, 0xFF56, 0xFF56, 0xFFB3], // page down / KP 3 + 35: [0xFF57, 0xFF57, 0xFF57, 0xFFB1], // end / KP 1 + 36: [0xFF50, 0xFF50, 0xFF50, 0xFFB7], // home / KP 7 + 37: [0xFF51, 0xFF51, 0xFF51, 0xFFB4], // left arrow / KP 4 + 38: [0xFF52, 0xFF52, 0xFF52, 0xFFB8], // up arrow / KP 8 + 39: [0xFF53, 0xFF53, 0xFF53, 0xFFB6], // right arrow / KP 6 + 40: [0xFF54, 0xFF54, 0xFF54, 0xFFB2], // down arrow / KP 2 + 45: [0xFF63, 0xFF63, 0xFF63, 0xFFB0], // insert / KP 0 + 46: [0xFFFF, 0xFFFF, 0xFFFF, 0xFFAE], // delete / KP decimal + 91: [0xFFEB], // left window key (hyper_l) + 92: [0xFF67], // right window key (menu key?) + 93: null, // select key + 96: [0xFFB0], // KP 0 + 97: [0xFFB1], // KP 1 + 98: [0xFFB2], // KP 2 + 99: [0xFFB3], // KP 3 + 100: [0xFFB4], // KP 4 + 101: [0xFFB5], // KP 5 + 102: [0xFFB6], // KP 6 + 103: [0xFFB7], // KP 7 + 104: [0xFFB8], // KP 8 + 105: [0xFFB9], // KP 9 + 106: [0xFFAA], // KP multiply + 107: [0xFFAB], // KP add + 109: [0xFFAD], // KP subtract + 110: [0xFFAE], // KP decimal + 111: [0xFFAF], // KP divide + 112: [0xFFBE], // f1 + 113: [0xFFBF], // f2 + 114: [0xFFC0], // f3 + 115: [0xFFC1], // f4 + 116: [0xFFC2], // f5 + 117: [0xFFC3], // f6 + 118: [0xFFC4], // f7 + 119: [0xFFC5], // f8 + 120: [0xFFC6], // f9 + 121: [0xFFC7], // f10 + 122: [0xFFC8], // f11 + 123: [0xFFC9], // f12 + 144: [0xFF7F], // num lock + 145: [0xFF14], // scroll lock + 225: [0xFE03] // altgraph (iso_level3_shift) + }; + + /** + * Map of known JavaScript keyidentifiers which do not map to typable + * characters to their unshifted X11 keysym equivalents. + * @private + */ + var keyidentifier_keysym = { + "Again": [0xFF66], + "AllCandidates": [0xFF3D], + "Alphanumeric": [0xFF30], + "Alt": [0xFFE9, 0xFFE9, 0xFE03], + "Attn": [0xFD0E], + "AltGraph": [0xFE03], + "ArrowDown": [0xFF54], + "ArrowLeft": [0xFF51], + "ArrowRight": [0xFF53], + "ArrowUp": [0xFF52], + "Backspace": [0xFF08], + "CapsLock": [0xFFE5], + "Cancel": [0xFF69], + "Clear": [0xFF0B], + "Convert": [0xFF21], + "Copy": [0xFD15], + "Crsel": [0xFD1C], + "CrSel": [0xFD1C], + "CodeInput": [0xFF37], + "Compose": [0xFF20], + "Control": [0xFFE3, 0xFFE3, 0xFFE4], + "ContextMenu": [0xFF67], + "Delete": [0xFFFF], + "Down": [0xFF54], + "End": [0xFF57], + "Enter": [0xFF0D], + "EraseEof": [0xFD06], + "Escape": [0xFF1B], + "Execute": [0xFF62], + "Exsel": [0xFD1D], + "ExSel": [0xFD1D], + "F1": [0xFFBE], + "F2": [0xFFBF], + "F3": [0xFFC0], + "F4": [0xFFC1], + "F5": [0xFFC2], + "F6": [0xFFC3], + "F7": [0xFFC4], + "F8": [0xFFC5], + "F9": [0xFFC6], + "F10": [0xFFC7], + "F11": [0xFFC8], + "F12": [0xFFC9], + "F13": [0xFFCA], + "F14": [0xFFCB], + "F15": [0xFFCC], + "F16": [0xFFCD], + "F17": [0xFFCE], + "F18": [0xFFCF], + "F19": [0xFFD0], + "F20": [0xFFD1], + "F21": [0xFFD2], + "F22": [0xFFD3], + "F23": [0xFFD4], + "F24": [0xFFD5], + "Find": [0xFF68], + "GroupFirst": [0xFE0C], + "GroupLast": [0xFE0E], + "GroupNext": [0xFE08], + "GroupPrevious": [0xFE0A], + "FullWidth": null, + "HalfWidth": null, + "HangulMode": [0xFF31], + "Hankaku": [0xFF29], + "HanjaMode": [0xFF34], + "Help": [0xFF6A], + "Hiragana": [0xFF25], + "HiraganaKatakana": [0xFF27], + "Home": [0xFF50], + "Hyper": [0xFFED, 0xFFED, 0xFFEE], + "Insert": [0xFF63], + "JapaneseHiragana": [0xFF25], + "JapaneseKatakana": [0xFF26], + "JapaneseRomaji": [0xFF24], + "JunjaMode": [0xFF38], + "KanaMode": [0xFF2D], + "KanjiMode": [0xFF21], + "Katakana": [0xFF26], + "Left": [0xFF51], + "Meta": [0xFFE7, 0xFFE7, 0xFFE8], + "ModeChange": [0xFF7E], + "NumLock": [0xFF7F], + "PageDown": [0xFF56], + "PageUp": [0xFF55], + "Pause": [0xFF13], + "Play": [0xFD16], + "PreviousCandidate": [0xFF3E], + "PrintScreen": [0xFF61], + "Redo": [0xFF66], + "Right": [0xFF53], + "RomanCharacters": null, + "Scroll": [0xFF14], + "Select": [0xFF60], + "Separator": [0xFFAC], + "Shift": [0xFFE1, 0xFFE1, 0xFFE2], + "SingleCandidate": [0xFF3C], + "Super": [0xFFEB, 0xFFEB, 0xFFEC], + "Tab": [0xFF09], + "UIKeyInputDownArrow": [0xFF54], + "UIKeyInputEscape": [0xFF1B], + "UIKeyInputLeftArrow": [0xFF51], + "UIKeyInputRightArrow": [0xFF53], + "UIKeyInputUpArrow": [0xFF52], + "Up": [0xFF52], + "Undo": [0xFF65], + "Win": [0xFFEB], + "Zenkaku": [0xFF28], + "ZenkakuHankaku": [0xFF2A] + }; + + /** + * All keysyms which should not repeat when held down. + * @private + */ + var no_repeat = { + 0xFE03: true, // ISO Level 3 Shift (AltGr) + 0xFFE1: true, // Left shift + 0xFFE2: true, // Right shift + 0xFFE3: true, // Left ctrl + 0xFFE4: true, // Right ctrl + 0xFFE5: true, // Caps Lock + 0xFFE7: true, // Left meta + 0xFFE8: true, // Right meta + 0xFFE9: true, // Left alt + 0xFFEA: true, // Right alt + 0xFFEB: true, // Left hyper + 0xFFEC: true // Right hyper + }; + + /** + * All modifiers and their states. + */ + this.modifiers = new Guacamole.Keyboard.ModifierState(); + + /** + * The state of every key, indexed by keysym. If a particular key is + * pressed, the value of pressed for that keysym will be true. If a key + * is not currently pressed, it will not be defined. + */ + this.pressed = {}; + + /** + * The state of every key, indexed by keysym, for strictly those keys whose + * status has been indirectly determined thorugh observation of other key + * events. If a particular key is implicitly pressed, the value of + * implicitlyPressed for that keysym will be true. If a key + * is not currently implicitly pressed (the key is not pressed OR the state + * of the key is explicitly known), it will not be defined. + * + * @private + * @tyle {Object.} + */ + var implicitlyPressed = {}; + + /** + * The last result of calling the onkeydown handler for each key, indexed + * by keysym. This is used to prevent/allow default actions for key events, + * even when the onkeydown handler cannot be called again because the key + * is (theoretically) still pressed. + * + * @private + */ + var last_keydown_result = {}; + + /** + * The keysym most recently associated with a given keycode when keydown + * fired. This object maps keycodes to keysyms. + * + * @private + * @type {Object.} + */ + var recentKeysym = {}; + + /** + * Timeout before key repeat starts. + * @private + */ + var key_repeat_timeout = null; + + /** + * Interval which presses and releases the last key pressed while that + * key is still being held down. + * @private + */ + var key_repeat_interval = null; + + /** + * Given an array of keysyms indexed by location, returns the keysym + * for the given location, or the keysym for the standard location if + * undefined. + * + * @private + * @param {Number[]} keysyms + * An array of keysyms, where the index of the keysym in the array is + * the location value. + * + * @param {Number} location + * The location on the keyboard corresponding to the key pressed, as + * defined at: http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent + */ + var get_keysym = function get_keysym(keysyms, location) { + + if (!keysyms) + return null; + + return keysyms[location] || keysyms[0]; + }; + + /** + * Returns true if the given keysym corresponds to a printable character, + * false otherwise. + * + * @param {Number} keysym + * The keysym to check. + * + * @returns {Boolean} + * true if the given keysym corresponds to a printable character, + * false otherwise. + */ + var isPrintable = function isPrintable(keysym) { + + // Keysyms with Unicode equivalents are printable + return (keysym >= 0x00 && keysym <= 0xFF) + || (keysym & 0xFFFF0000) === 0x01000000; + + }; + + function keysym_from_key_identifier(identifier, location, shifted) { + + if (!identifier) + return null; + + var typedCharacter; + + // If identifier is U+xxxx, decode Unicode character + var unicodePrefixLocation = identifier.indexOf("U+"); + if (unicodePrefixLocation >= 0) { + var hex = identifier.substring(unicodePrefixLocation+2); + typedCharacter = String.fromCharCode(parseInt(hex, 16)); + } + + // If single character and not keypad, use that as typed character + else if (identifier.length === 1 && location !== 3) + typedCharacter = identifier; + + // Otherwise, look up corresponding keysym + else + return get_keysym(keyidentifier_keysym[identifier], location); + + // Alter case if necessary + if (shifted === true) + typedCharacter = typedCharacter.toUpperCase(); + else if (shifted === false) + typedCharacter = typedCharacter.toLowerCase(); + + // Get codepoint + var codepoint = typedCharacter.charCodeAt(0); + return keysym_from_charcode(codepoint); + + } + + function isControlCharacter(codepoint) { + return codepoint <= 0x1F || (codepoint >= 0x7F && codepoint <= 0x9F); + } + + function keysym_from_charcode(codepoint) { + + // Keysyms for control characters + if (isControlCharacter(codepoint)) return 0xFF00 | codepoint; + + // Keysyms for ASCII chars + if (codepoint >= 0x0000 && codepoint <= 0x00FF) + return codepoint; + + // Keysyms for Unicode + if (codepoint >= 0x0100 && codepoint <= 0x10FFFF) + return 0x01000000 | codepoint; + + return null; + + } + + function keysym_from_keycode(keyCode, location) { + return get_keysym(keycodeKeysyms[keyCode], location); + } + + /** + * Heuristically detects if the legacy keyIdentifier property of + * a keydown/keyup event looks incorrectly derived. Chrome, and + * presumably others, will produce the keyIdentifier by assuming + * the keyCode is the Unicode codepoint for that key. This is not + * correct in all cases. + * + * @private + * @param {Number} keyCode + * The keyCode from a browser keydown/keyup event. + * + * @param {String} keyIdentifier + * The legacy keyIdentifier from a browser keydown/keyup event. + * + * @returns {Boolean} + * true if the keyIdentifier looks sane, false if the keyIdentifier + * appears incorrectly derived or is missing entirely. + */ + var key_identifier_sane = function key_identifier_sane(keyCode, keyIdentifier) { + + // Missing identifier is not sane + if (!keyIdentifier) + return false; + + // Assume non-Unicode keyIdentifier values are sane + var unicodePrefixLocation = keyIdentifier.indexOf("U+"); + if (unicodePrefixLocation === -1) + return true; + + // If the Unicode codepoint isn't identical to the keyCode, + // then the identifier is likely correct + var codepoint = parseInt(keyIdentifier.substring(unicodePrefixLocation+2), 16); + if (keyCode !== codepoint) + return true; + + // The keyCodes for A-Z and 0-9 are actually identical to their + // Unicode codepoints + if ((keyCode >= 65 && keyCode <= 90) || (keyCode >= 48 && keyCode <= 57)) + return true; + + // The keyIdentifier does NOT appear sane + return false; + + }; + + /** + * Marks a key as pressed, firing the keydown event if registered. Key + * repeat for the pressed key will start after a delay if that key is + * not a modifier. The return value of this function depends on the + * return value of the keydown event handler, if any. + * + * @param {Number} keysym The keysym of the key to press. + * @return {Boolean} true if event should NOT be canceled, false otherwise. + */ + this.press = function(keysym) { + + // Don't bother with pressing the key if the key is unknown + if (keysym === null) return; + + // Only press if released + if (!guac_keyboard.pressed[keysym]) { + + // Mark key as pressed + guac_keyboard.pressed[keysym] = true; + + // Send key event + if (guac_keyboard.onkeydown) { + var result = guac_keyboard.onkeydown(keysym); + last_keydown_result[keysym] = result; + + // Stop any current repeat + window.clearTimeout(key_repeat_timeout); + window.clearInterval(key_repeat_interval); + + // Repeat after a delay as long as pressed + if (!no_repeat[keysym]) + key_repeat_timeout = window.setTimeout(function() { + key_repeat_interval = window.setInterval(function() { + guac_keyboard.onkeyup(keysym); + guac_keyboard.onkeydown(keysym); + }, 50); + }, 500); + + return result; + } + } + + // Return the last keydown result by default, resort to false if unknown + return last_keydown_result[keysym] || false; + + }; + + /** + * Marks a key as released, firing the keyup event if registered. + * + * @param {Number} keysym The keysym of the key to release. + */ + this.release = function(keysym) { + + // Only release if pressed + if (guac_keyboard.pressed[keysym]) { + + // Mark key as released + delete guac_keyboard.pressed[keysym]; + delete implicitlyPressed[keysym]; + + // Stop repeat + window.clearTimeout(key_repeat_timeout); + window.clearInterval(key_repeat_interval); + + // Send key event + if (keysym !== null && guac_keyboard.onkeyup) + guac_keyboard.onkeyup(keysym); + + } + + }; + + /** + * Presses and releases the keys necessary to type the given string of + * text. + * + * @param {String} str + * The string to type. + */ + this.type = function type(str) { + + // Press/release the key corresponding to each character in the string + for (var i = 0; i < str.length; i++) { + + // Determine keysym of current character + var codepoint = str.codePointAt ? str.codePointAt(i) : str.charCodeAt(i); + var keysym = keysym_from_charcode(codepoint); + + // Press and release key for current character + guac_keyboard.press(keysym); + guac_keyboard.release(keysym); + + } + + }; + + /** + * Resets the state of this keyboard, releasing all keys, and firing keyup + * events for each released key. + */ + this.reset = function() { + + // Release all pressed keys + for (var keysym in guac_keyboard.pressed) + guac_keyboard.release(parseInt(keysym)); + + // Clear event log + eventLog = []; + + }; + + /** + * Given the remote and local state of a particular key, resynchronizes the + * remote state of that key with the local state through pressing or + * releasing keysyms. + * + * @private + * @param {Boolean} remoteState + * Whether the key is currently pressed remotely. + * + * @param {Boolean} localState + * Whether the key is currently pressed remotely locally. If the state + * of the key is not known, this may be undefined. + * + * @param {Number[]} keysyms + * The keysyms which represent the key being updated. + * + * @param {KeyEvent} keyEvent + * Guacamole's current best interpretation of the key event being + * processed. + */ + var updateModifierState = function updateModifierState(remoteState, + localState, keysyms, keyEvent) { + + var i; + + // Do not trust changes in modifier state for events directly involving + // that modifier: (1) the flag may erroneously be cleared despite + // another version of the same key still being held and (2) the change + // in flag may be due to the current event being processed, thus + // updating things here is at best redundant and at worst incorrect + if (keysyms.indexOf(keyEvent.keysym) !== -1) + return; + + // Release all related keys if modifier is implicitly released + if (remoteState && localState === false) { + for (i = 0; i < keysyms.length; i++) { + guac_keyboard.release(keysyms[i]); + } + } + + // Press if modifier is implicitly pressed + else if (!remoteState && localState) { + + // Verify that modifier flag isn't already pressed or already set + // due to another version of the same key being held down + for (i = 0; i < keysyms.length; i++) { + if (guac_keyboard.pressed[keysyms[i]]) + return; + } + + // Mark as implicitly pressed only if there is other information + // within the key event relating to a different key. Some + // platforms, such as iOS, will send essentially empty key events + // for modifier keys, using only the modifier flags to signal the + // identity of the key. + var keysym = keysyms[0]; + if (keyEvent.keysym) + implicitlyPressed[keysym] = true; + + guac_keyboard.press(keysym); + + } + + }; + + /** + * Given a keyboard event, updates the local modifier state and remote + * key state based on the modifier flags within the event. This function + * pays no attention to keycodes. + * + * @private + * @param {KeyboardEvent} e + * The keyboard event containing the flags to update. + * + * @param {KeyEvent} keyEvent + * Guacamole's current best interpretation of the key event being + * processed. + */ + var syncModifierStates = function syncModifierStates(e, keyEvent) { + + // Get state + var state = Guacamole.Keyboard.ModifierState.fromKeyboardEvent(e); + + // Resync state of alt + updateModifierState(guac_keyboard.modifiers.alt, state.alt, [ + 0xFFE9, // Left alt + 0xFFEA, // Right alt + 0xFE03 // AltGr + ], keyEvent); + + // Resync state of shift + updateModifierState(guac_keyboard.modifiers.shift, state.shift, [ + 0xFFE1, // Left shift + 0xFFE2 // Right shift + ], keyEvent); + + // Resync state of ctrl + updateModifierState(guac_keyboard.modifiers.ctrl, state.ctrl, [ + 0xFFE3, // Left ctrl + 0xFFE4 // Right ctrl + ], keyEvent); + + // Resync state of meta + updateModifierState(guac_keyboard.modifiers.meta, state.meta, [ + 0xFFE7, // Left meta + 0xFFE8 // Right meta + ], keyEvent); + + // Resync state of hyper + updateModifierState(guac_keyboard.modifiers.hyper, state.hyper, [ + 0xFFEB, // Left hyper + 0xFFEC // Right hyper + ], keyEvent); + + // Update state + guac_keyboard.modifiers = state; + + }; + + /** + * Returns whether all currently pressed keys were implicitly pressed. A + * key is implicitly pressed if its status was inferred indirectly from + * inspection of other key events. + * + * @private + * @returns {Boolean} + * true if all currently pressed keys were implicitly pressed, false + * otherwise. + */ + var isStateImplicit = function isStateImplicit() { + + for (var keysym in guac_keyboard.pressed) { + if (!implicitlyPressed[keysym]) + return false; + } + + return true; + + }; + + /** + * Reads through the event log, removing events from the head of the log + * when the corresponding true key presses are known (or as known as they + * can be). + * + * @private + * @return {Boolean} Whether the default action of the latest event should + * be prevented. + */ + function interpret_events() { + + // Do not prevent default if no event could be interpreted + var handled_event = interpret_event(); + if (!handled_event) + return false; + + // Interpret as much as possible + var last_event; + do { + last_event = handled_event; + handled_event = interpret_event(); + } while (handled_event !== null); + + // Reset keyboard state if we cannot expect to receive any further + // keyup events + if (isStateImplicit()) + guac_keyboard.reset(); + + return last_event.defaultPrevented; + + } + + /** + * Releases Ctrl+Alt, if both are currently pressed and the given keysym + * looks like a key that may require AltGr. + * + * @private + * @param {Number} keysym The key that was just pressed. + */ + var release_simulated_altgr = function release_simulated_altgr(keysym) { + + // Both Ctrl+Alt must be pressed if simulated AltGr is in use + if (!guac_keyboard.modifiers.ctrl || !guac_keyboard.modifiers.alt) + return; + + // Assume [A-Z] never require AltGr + if (keysym >= 0x0041 && keysym <= 0x005A) + return; + + // Assume [a-z] never require AltGr + if (keysym >= 0x0061 && keysym <= 0x007A) + return; + + // Release Ctrl+Alt if the keysym is printable + if (keysym <= 0xFF || (keysym & 0xFF000000) === 0x01000000) { + guac_keyboard.release(0xFFE3); // Left ctrl + guac_keyboard.release(0xFFE4); // Right ctrl + guac_keyboard.release(0xFFE9); // Left alt + guac_keyboard.release(0xFFEA); // Right alt + } + + }; + + /** + * Reads through the event log, interpreting the first event, if possible, + * and returning that event. If no events can be interpreted, due to a + * total lack of events or the need for more events, null is returned. Any + * interpreted events are automatically removed from the log. + * + * @private + * @return {KeyEvent} + * The first key event in the log, if it can be interpreted, or null + * otherwise. + */ + var interpret_event = function interpret_event() { + + // Peek at first event in log + var first = eventLog[0]; + if (!first) + return null; + + // Keydown event + if (first instanceof KeydownEvent) { + + var keysym = null; + var accepted_events = []; + + // If event itself is reliable, no need to wait for other events + if (first.reliable) { + keysym = first.keysym; + accepted_events = eventLog.splice(0, 1); + } + + // If keydown is immediately followed by a keypress, use the indicated character + else if (eventLog[1] instanceof KeypressEvent) { + keysym = eventLog[1].keysym; + accepted_events = eventLog.splice(0, 2); + } + + // If keydown is immediately followed by anything else, then no + // keypress can possibly occur to clarify this event, and we must + // handle it now + else if (eventLog[1]) { + keysym = first.keysym; + accepted_events = eventLog.splice(0, 1); + } + + // Fire a key press if valid events were found + if (accepted_events.length > 0) { + + if (keysym) { + + // Fire event + release_simulated_altgr(keysym); + var defaultPrevented = !guac_keyboard.press(keysym); + recentKeysym[first.keyCode] = keysym; + + // Release the key now if we cannot rely on the associated + // keyup event + if (!first.keyupReliable) + guac_keyboard.release(keysym); + + // Record whether default was prevented + for (var i=0; iWe're sorry but n.eko doesn't work properly without JavaScript enabled. Please enable it to continue.
+ From d2477a37d0b0c4654a8e96f2e7b3d61e8a09dde4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Mon, 15 Jun 2020 15:55:31 +0200 Subject: [PATCH 09/27] use guacamole kbd --- client/src/components/video.vue | 49 +++++------ client/src/neko/keyboard.ts | 147 -------------------------------- 2 files changed, 25 insertions(+), 171 deletions(-) delete mode 100644 client/src/neko/keyboard.ts diff --git a/client/src/components/video.vue b/client/src/components/video.vue index 85cb689a..929385e3 100644 --- a/client/src/components/video.vue +++ b/client/src/components/video.vue @@ -20,8 +20,6 @@ @mouseup.stop.prevent="onMouseUp" @mouseenter.stop.prevent="onMouseEnter" @mouseleave.stop.prevent="onMouseLeave" - @keydown.stop.prevent="onKeyDown" - @keyup.stop.prevent="onKeyUp" />
@@ -142,8 +140,6 @@ import Emote from './emote.vue' import Resolution from './resolution.vue' - import { mapKeyboardEventToKeySym } from '@/neko/keyboard.ts' - @Component({ name: 'neko-video', components: { @@ -337,6 +333,31 @@ document.addEventListener('focusin', this.onFocus.bind(this)) document.addEventListener('focusout', this.onBlur.bind(this)) + + var Keyboard = {}; + + // @ts-ignore + Guacamole.Keyboard.bind(Keyboard, this._overlay)(); + + // @ts-ignore + Keyboard.onkeydown = (key: number) => { + if (!this.focused || !this.hosting || this.locked) { + return + } + + this.$client.sendData('keydown', { key }) + this.activeKeys.add(key) + }; + + // @ts-ignore + Keyboard.onkeyup = (key: number) => { + if (!this.focused || !this.hosting || this.locked) { + return + } + + this.$client.sendData('keyup', { key }) + this.activeKeys.delete(key) + }; } beforeDestroy() { @@ -479,26 +500,6 @@ this.focused = false } - onKeyDown(e: KeyboardEvent) { - if (!this.focused || !this.hosting || this.locked) { - return - } - - let key = mapKeyboardEventToKeySym(e) - this.$client.sendData('keydown', { key }) - this.activeKeys.add(key) - } - - onKeyUp(e: KeyboardEvent) { - if (!this.focused || !this.hosting || this.locked) { - return - } - - let key = mapKeyboardEventToKeySym(e) - this.$client.sendData('keyup', { key }) - this.activeKeys.delete(key) - } - onResise() { let height = 0 if (!this.fullscreen) { diff --git a/client/src/neko/keyboard.ts b/client/src/neko/keyboard.ts deleted file mode 100644 index 0d3f604d..00000000 --- a/client/src/neko/keyboard.ts +++ /dev/null @@ -1,147 +0,0 @@ -const keyMap: Record = { - 'Backspace': 0xff08, - 'Tab': 0xFF09, - 'Enter': 0xFF0D, - 'ShiftLeft': 0xFFE1, - 'ShiftRight': 0xFFE2, - 'ControlLeft': 0xFFE3, - 'ControlRight': 0xFFE4, - 'AltLeft': 0xFFE9, - 'AltRight': 0xFFEA, - 'MetaLeft': 0xFFEB, // Super key, or Meta key? - 'MetaRight': 0xFFEC, // Super key, or Meta key? - 'PrintScreen': 0xFF61, - 'ContextMenu': 0xFF67, - 'Pause': 0xFF13, - 'CapsLock': 0xFFE5, - 'Escape': 0xFF1B, - 'Space': 0x0020, - 'PageUp': 0xFF55, - 'PageDown': 0xFF56, - 'End': 0xFF57, - 'Home': 0xFF50, - 'ArrowLeft': 0xFF51, - 'ArrowUp': 0xFF52, - 'ArrowRight': 0xFF53, - 'ArrowDown': 0xFF54, - 'Insert': 0xFF63, - 'Delete': 0xFFFF, - 'Digit0': 0x0030, - 'Digit1': 0x0031, - 'Digit2': 0x0032, - 'Digit3': 0x0033, - 'Digit4': 0x0034, - 'Digit5': 0x0035, - 'Digit6': 0x0036, - 'Digit7': 0x0037, - 'Digit8': 0x0038, - 'Digit9': 0x0039, - 'KeyA': 0x0061, - 'KeyB': 0x0062, - 'KeyC': 0x0063, - 'KeyD': 0x0064, - 'KeyE': 0x0065, - 'KeyF': 0x0066, - 'KeyG': 0x0067, - 'KeyH': 0x0068, - 'KeyI': 0x0069, - 'KeyJ': 0x006a, - 'KeyK': 0x006b, - 'KeyL': 0x006c, - 'KeyM': 0x006d, - 'KeyN': 0x006e, - 'KeyO': 0x006f, - 'KeyP': 0x0070, - 'KeyQ': 0x0071, - 'KeyR': 0x0072, - 'KeyS': 0x0073, - 'KeyT': 0x0074, - 'KeyU': 0x0075, - 'KeyV': 0x0076, - 'KeyW': 0x0077, - 'KeyX': 0x0078, - 'KeyZ': 0x0079, - 'KeyY': 0x007a, - 'NumpadEnter': 0XFF8D, - 'NumpadMultiply': 0xFFAA, - 'NumpadAdd': 0xFFAB, - 'NumpadSubtract': 0xFFAD, - 'NumpadDivide': 0xFFAF, - 'F1': 0xFFBE, - 'F2': 0xFFBF, - 'F3': 0xFFC0, - 'F4': 0xFFC1, - 'F5': 0xFFC2, - 'F6': 0xFFC3, - 'F7': 0xFFC4, - 'F8': 0xFFC5, - 'F9': 0xFFC6, - 'F10': 0xFFC7, - 'F11': 0xFFC8, - 'F12': 0xFFC9, - 'NumLock': 0xFF7F, - 'ScrollLock': 0xFF14, - - // l10s causes many problems - 'Semicolon': 0x003b, - 'Comma': 0x002c, - 'Slash': 0x002f, - 'Period': 0x002e, - 'Minus': 0x002d, - 'Backquote': 0x0060, - 'BracketLeft': 0x005b, - 'Backslash': 0x005c, - 'BracketRight': 0x005d, - 'Quote': 0x0022, - 'Equal': 0x003d, -}; - -// If Num Lock is enabled [Original KeySym, Non-NumPad key (on US kbd)] -const keyMap_numlock_enabled: Record> = { - 'Numpad0': [0xFFB0, 0x0030], - 'Numpad1': [0xFFB1, 0x0031], - 'Numpad2': [0xFFB2, 0x0032], - 'Numpad3': [0xFFB3, 0x0033], - 'Numpad4': [0xFFB4, 0x0034], - 'Numpad5': [0xFFB5, 0x0035], - 'Numpad6': [0xFFB6, 0x0036], - 'Numpad7': [0xFFB7, 0x0037], - 'Numpad8': [0xFFB8, 0x0038], - 'Numpad9': [0xFFB9, 0x0039], - 'NumpadDecimal': [0xFFAE, 0x002c] -}; - -// If Num Lock is disabled [Original KeySym, Non-NumPad key (on US kbd)] -const keyMap_numlock_disabled: Record> = { - 'Numpad0': [0xFFB0, 0xff63], - 'Numpad1': [0xFFB1, 0xFF57], - 'Numpad2': [0xFFB2, 0xFF54], - 'Numpad3': [0xFFB3, 0xFF56], - 'Numpad4': [0xFFB4, 0xFF51], - 'Numpad5': [0xFFB5, 0xff0b], - 'Numpad6': [0xFFB6, 0xFF53], - 'Numpad7': [0xFFB7, 0xFF50], - 'Numpad8': [0xFFB8, 0xFF52], - 'Numpad9': [0xFFB9, 0xFF55], - 'NumpadDecimal': [0xFF9F, 0xFFFF], -}; - -export function mapKeyboardEventToKeySym(e: KeyboardEvent): number { - if (e.code in keyMap) { - return keyMap[e.code]; - } - - //// Handle Num Lock at Server - //if (isNumLockActive)) { - // return keyMap_numlock_enabled[e.code][0]; - //} else { - // return keyMap_numlock_disabled[e.code][0]; - //} - - // Handle Num Lock at Client - if (e.getModifierState('NumLock')) { - return keyMap_numlock_enabled[e.code][1]; - } else { - return keyMap_numlock_disabled[e.code][1]; - } -} From 36937a0776a8705d1eb104287ba25a51cde5c6ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Mon, 15 Jun 2020 17:28:05 +0200 Subject: [PATCH 10/27] gruacamole native kbd reset --- client/src/components/video.vue | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/client/src/components/video.vue b/client/src/components/video.vue index 929385e3..75dceb64 100644 --- a/client/src/components/video.vue +++ b/client/src/components/video.vue @@ -159,7 +159,6 @@ private observer = new ResizeObserver(this.onResise.bind(this)) private focused = false private fullscreen = false - private activeKeys: Set = new Set() get admin() { return this.$accessor.user.admin @@ -346,7 +345,6 @@ } this.$client.sendData('keydown', { key }) - this.activeKeys.add(key) }; // @ts-ignore @@ -356,8 +354,16 @@ } this.$client.sendData('keyup', { key }) - this.activeKeys.delete(key) }; + + // @ts-ignore + this.kbdReset = () => { + // @ts-ignore + Keyboard.reset(); + } + + //Keyboard.release(keysym); + //Keyboard.type(str); } beforeDestroy() { @@ -432,10 +438,8 @@ return } - for (let key of this.activeKeys) { - this.$client.sendData('keyup', { key }) - this.activeKeys.delete(key) - } + // @ts-ignore + this.kbdReset(); } onMousePos(e: MouseEvent) { From 9a6ca9b5b01eb2ad1f1c98d691d5bbb31c8b4469 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Mon, 15 Jun 2020 18:57:28 +0200 Subject: [PATCH 11/27] send keysyms in uint64 --- client/src/neko/base.ts | 12 +++---- server/internal/remote/manager.go | 4 +-- server/internal/types/remote.go | 4 +-- server/internal/webrtc/handle.go | 10 +++--- server/internal/xorg/xorg.go | 53 +++++++++++++++++-------------- 5 files changed, 44 insertions(+), 39 deletions(-) diff --git a/client/src/neko/base.ts b/client/src/neko/base.ts index 26fc4f24..d8a40b3f 100644 --- a/client/src/neko/base.ts +++ b/client/src/neko/base.ts @@ -123,19 +123,19 @@ export abstract class BaseClient extends EventEmitter { break case 'keydown': case 'mousedown': - buffer = new ArrayBuffer(5) + buffer = new ArrayBuffer(11) payload = new DataView(buffer) payload.setUint8(0, OPCODE.KEY_DOWN) - payload.setUint16(1, 1, true) - payload.setUint16(3, data.key, true) + payload.setUint16(1, 8, true) + payload.setBigUint64(3, BigInt(data.key), true) break case 'keyup': case 'mouseup': - buffer = new ArrayBuffer(5) + buffer = new ArrayBuffer(11) payload = new DataView(buffer) payload.setUint8(0, OPCODE.KEY_UP) - payload.setUint16(1, 1, true) - payload.setUint16(3, data.key, true) + payload.setUint16(1, 8, true) + payload.setBigUint64(3, BigInt(data.key), true) break default: this.emit('warn', `unknown data event: ${event}`) diff --git a/server/internal/remote/manager.go b/server/internal/remote/manager.go index b9be8982..9a4a4e27 100644 --- a/server/internal/remote/manager.go +++ b/server/internal/remote/manager.go @@ -187,7 +187,7 @@ func (manager *RemoteManager) ButtonDown(code int) error { return xorg.ButtonDown(code) } -func (manager *RemoteManager) KeyDown(code int) error { +func (manager *RemoteManager) KeyDown(code uint64) error { return xorg.KeyDown(code) } @@ -195,7 +195,7 @@ func (manager *RemoteManager) ButtonUp(code int) error { return xorg.ButtonUp(code) } -func (manager *RemoteManager) KeyUp(code int) error { +func (manager *RemoteManager) KeyUp(code uint64) error { return xorg.KeyUp(code) } diff --git a/server/internal/types/remote.go b/server/internal/types/remote.go index 2814d04f..3bdf7fd1 100644 --- a/server/internal/types/remote.go +++ b/server/internal/types/remote.go @@ -16,9 +16,9 @@ type RemoteManager interface { Move(x, y int) Scroll(x, y int) ButtonDown(code int) error - KeyDown(code int) error + KeyDown(code uint64) error ButtonUp(code int) error - KeyUp(code int) error + KeyUp(code uint64) error ReadClipboard() string WriteClipboard(data string) ResetKeys() diff --git a/server/internal/webrtc/handle.go b/server/internal/webrtc/handle.go index a84b5bc9..35c4b235 100644 --- a/server/internal/webrtc/handle.go +++ b/server/internal/webrtc/handle.go @@ -33,7 +33,7 @@ type PayloadScroll struct { type PayloadKey struct { PayloadHeader - Key uint16 + Key uint64 } func (manager *WebRTCManager) handle(id string, msg webrtc.DataChannelMessage) error { @@ -87,13 +87,13 @@ func (manager *WebRTCManager) handle(id string, msg webrtc.DataChannelMessage) e if payload.Key < 8 { err := manager.remote.ButtonDown(int(payload.Key)) if err != nil { - manager.logger.Warn().Err(err).Msg("key down failed") + manager.logger.Warn().Err(err).Msg("button down failed") return nil } manager.logger.Debug().Msgf("button down %d", payload.Key) } else { - err := manager.remote.KeyDown(int(payload.Key)) + err := manager.remote.KeyDown(uint64(payload.Key)) if err != nil { manager.logger.Warn().Err(err).Msg("key down failed") return nil @@ -119,9 +119,9 @@ func (manager *WebRTCManager) handle(id string, msg webrtc.DataChannelMessage) e manager.logger.Debug().Msgf("button up %d", payload.Key) } else { - err := manager.remote.KeyUp(int(payload.Key)) + err := manager.remote.KeyUp(uint64(payload.Key)) if err != nil { - manager.logger.Warn().Err(err).Msg("keyup failed") + manager.logger.Warn().Err(err).Msg("key up failed") return nil } diff --git a/server/internal/xorg/xorg.go b/server/internal/xorg/xorg.go index 072c43a8..852a1551 100644 --- a/server/internal/xorg/xorg.go +++ b/server/internal/xorg/xorg.go @@ -19,7 +19,8 @@ import ( var ScreenConfigurations = make(map[int]types.ScreenConfiguration) -var debounce = make(map[int]time.Time) +var debounce_button = make(map[int]time.Time) +var debounce_key = make(map[uint64]time.Time) var mu = sync.Mutex{} func init() { @@ -54,25 +55,25 @@ func ButtonDown(code int) error { mu.Lock() defer mu.Unlock() - if _, ok := debounce[code]; ok { + if _, ok := debounce_button[code]; ok { return fmt.Errorf("debounced button %v", code) } - debounce[code] = time.Now() + debounce_button[code] = time.Now() C.XButton(C.uint(code), C.int(1)) return nil } -func KeyDown(code int) error { +func KeyDown(code uint64) error { mu.Lock() defer mu.Unlock() - if _, ok := debounce[code]; ok { + if _, ok := debounce_key[code]; ok { return fmt.Errorf("debounced key %v", code) } - debounce[code] = time.Now() + debounce_key[code] = time.Now() C.XKey(C.ulong(code), C.int(1)) return nil @@ -82,25 +83,25 @@ func ButtonUp(code int) error { mu.Lock() defer mu.Unlock() - if _, ok := debounce[code]; !ok { + if _, ok := debounce_button[code]; !ok { return fmt.Errorf("debounced button %v", code) } - delete(debounce, code) + delete(debounce_button, code) C.XButton(C.uint(code), C.int(0)) return nil } -func KeyUp(code int) error { +func KeyUp(code uint64) error { mu.Lock() defer mu.Unlock() - if _, ok := debounce[code]; !ok { + if _, ok := debounce_key[code]; !ok { return fmt.Errorf("debounced key %v", code) } - delete(debounce, code) + delete(debounce_key, code) C.XKey(C.ulong(code), C.int(0)) return nil @@ -127,31 +128,35 @@ func WriteClipboard(data string) { } func ResetKeys() { - for code := range debounce { - if code < 8 { - ButtonUp(code) - } else { - KeyUp(code) - } + for code := range debounce_button { + ButtonUp(code) - delete(debounce, code) + delete(debounce_button, code) + } + for code := range debounce_key { + KeyUp(code) + + delete(debounce_key, code) } } func CheckKeys(duration time.Duration) { t := time.Now() - for code, start := range debounce { + for code, start := range debounce_button { if t.Sub(start) < duration { continue } + ButtonUp(code) - if code < 8 { - ButtonUp(code) - } else { - KeyUp(code) + delete(debounce_button, code) + } + for code, start := range debounce_key { + if t.Sub(start) < duration { + continue } + KeyUp(code) - delete(debounce, code) + delete(debounce_key, code) } } From 8a56f238add4dc4e241704905d743efb52597406 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Mon, 15 Jun 2020 19:09:28 +0200 Subject: [PATCH 12/27] map keysyms to keycodes --- server/internal/xorg/xorg.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/server/internal/xorg/xorg.c b/server/internal/xorg/xorg.c index 81f7d1b9..d1823864 100644 --- a/server/internal/xorg/xorg.c +++ b/server/internal/xorg/xorg.c @@ -101,10 +101,20 @@ void XKey(unsigned long key, int down) { Display *display = getXDisplay(); KeyCode code = XKeysymToKeycode(display, key); - if (code != 0) { - XTestFakeKeyEvent(display, code, down, CurrentTime); - XSync(display, 0); + // Map non-existing keysyms to new keycodes + if(code == 0) { + int min, max, numcodes; + XDisplayKeycodes(display, &min, &max); + XGetKeyboardMapping(display, min, max-min, &numcodes); + + code = (max-min+1)*numcodes; + KeySym keysym_list[numcodes]; + for(int i=0;i Date: Mon, 15 Jun 2020 20:45:32 +0200 Subject: [PATCH 13/27] use guacamole keyboard as TS module --- client/public/index.html | 1 - client/src/components/video.vue | 35 +++------ .../utils/guacamole-keyboard.js} | 4 +- client/src/utils/guacamole-keyboard.ts | 76 +++++++++++++++++++ 4 files changed, 91 insertions(+), 25 deletions(-) rename client/{public/Keyboard.js => src/utils/guacamole-keyboard.js} (99%) create mode 100644 client/src/utils/guacamole-keyboard.ts diff --git a/client/public/index.html b/client/public/index.html index f187c9b0..c4105895 100644 --- a/client/public/index.html +++ b/client/public/index.html @@ -18,6 +18,5 @@ We're sorry but n.eko doesn't work properly without JavaScript enabled. Please enable it to continue.
- diff --git a/client/src/components/video.vue b/client/src/components/video.vue index 75dceb64..9023498b 100644 --- a/client/src/components/video.vue +++ b/client/src/components/video.vue @@ -140,6 +140,8 @@ import Emote from './emote.vue' import Resolution from './resolution.vue' + import GuacamoleKeyboard from '~/utils/guacamole-keyboard.ts' + @Component({ name: 'neko-video', components: { @@ -156,6 +158,7 @@ @Ref('video') readonly _video!: HTMLVideoElement @Ref('resolution') readonly _resolution!: any + private keyboard = GuacamoleKeyboard() private observer = new ResizeObserver(this.onResise.bind(this)) private focused = false private fullscreen = false @@ -333,37 +336,23 @@ document.addEventListener('focusin', this.onFocus.bind(this)) document.addEventListener('focusout', this.onBlur.bind(this)) - var Keyboard = {}; - - // @ts-ignore - Guacamole.Keyboard.bind(Keyboard, this._overlay)(); - - // @ts-ignore - Keyboard.onkeydown = (key: number) => { + /* Initialize Guacamole Keyboard */ + this.keyboard.onkeydown = (key: number) => { if (!this.focused || !this.hosting || this.locked) { - return + return false } this.$client.sendData('keydown', { key }) - }; - - // @ts-ignore - Keyboard.onkeyup = (key: number) => { + return true + } + this.keyboard.onkeyup = (key: number) => { if (!this.focused || !this.hosting || this.locked) { return } this.$client.sendData('keyup', { key }) - }; - - // @ts-ignore - this.kbdReset = () => { - // @ts-ignore - Keyboard.reset(); } - - //Keyboard.release(keysym); - //Keyboard.type(str); + this.keyboard.listenTo(this._overlay) } beforeDestroy() { @@ -371,6 +360,7 @@ this.$accessor.video.setPlayable(false) document.removeEventListener('focusin', this.onFocus.bind(this)) document.removeEventListener('focusout', this.onBlur.bind(this)) + /* Guacamole Keyboard does not provide destroy functions */ } play() { @@ -438,8 +428,7 @@ return } - // @ts-ignore - this.kbdReset(); + this.keyboard.reset() } onMousePos(e: MouseEvent) { diff --git a/client/public/Keyboard.js b/client/src/utils/guacamole-keyboard.js similarity index 99% rename from client/public/Keyboard.js rename to client/src/utils/guacamole-keyboard.js index df90f89f..496dddc0 100644 --- a/client/public/Keyboard.js +++ b/client/src/utils/guacamole-keyboard.js @@ -1510,4 +1510,6 @@ Guacamole.Keyboard.ModifierState.fromKeyboardEvent = function(e) { return state; -}; \ No newline at end of file +}; + +export default Guacamole.Keyboard; diff --git a/client/src/utils/guacamole-keyboard.ts b/client/src/utils/guacamole-keyboard.ts new file mode 100644 index 00000000..4ee1dfbf --- /dev/null +++ b/client/src/utils/guacamole-keyboard.ts @@ -0,0 +1,76 @@ +import GuacamoleKeyboard from './guacamole-keyboard.js' + +export interface GuacamoleKeyboardInterface { + /** + * Fired whenever the user presses a key with the element associated + * with this Guacamole.Keyboard in focus. + * + * @event + * @param {Number} keysym The keysym of the key being pressed. + * @return {Boolean} true if the key event should be allowed through to the + * browser, false otherwise. + */ + onkeydown?: (keysym: number) => boolean; + + /** + * Fired whenever the user releases a key with the element associated + * with this Guacamole.Keyboard in focus. + * + * @event + * @param {Number} keysym The keysym of the key being released. + */ + onkeyup?: (keysym: number) => void; + + /** + * Marks a key as pressed, firing the keydown event if registered. Key + * repeat for the pressed key will start after a delay if that key is + * not a modifier. The return value of this function depends on the + * return value of the keydown event handler, if any. + * + * @param {Number} keysym The keysym of the key to press. + * @return {Boolean} true if event should NOT be canceled, false otherwise. + */ + press: (keysym: number) => boolean; + + /** + * Marks a key as released, firing the keyup event if registered. + * + * @param {Number} keysym The keysym of the key to release. + */ + release: (keysym: number) => void; + + /** + * Presses and releases the keys necessary to type the given string of + * text. + * + * @param {String} str + * The string to type. + */ + type: (str: string) => void; + + /** + * Resets the state of this keyboard, releasing all keys, and firing keyup + * events for each released key. + */ + reset: () => void; + + /** + * Attaches event listeners to the given Element, automatically translating + * received key, input, and composition events into simple keydown/keyup + * events signalled through this Guacamole.Keyboard's onkeydown and + * onkeyup handlers. + * + * @param {Element|Document} element + * The Element to attach event listeners to for the sake of handling + * key or input events. + */ + listenTo: (element: Element | Document) => void; +} + +export default function(element?: Element): GuacamoleKeyboardInterface { + var Keyboard = {}; + + GuacamoleKeyboard.bind(Keyboard, element)(); + + return Keyboard as GuacamoleKeyboardInterface; +} From c176411512a056f3398ba77ab2b7d9dc78375562 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Mon, 15 Jun 2020 22:09:01 +0200 Subject: [PATCH 14/27] add layout select to settings --- client/src/components/settings.vue | 49 ++++++++++++++++++++++++++++++ client/src/locale/en-us.ts | 1 + client/src/store/settings.ts | 6 ++++ 3 files changed, 56 insertions(+) diff --git a/client/src/components/settings.vue b/client/src/components/settings.vue index 16660415..5f0fd127 100644 --- a/client/src/components/settings.vue +++ b/client/src/components/settings.vue @@ -35,6 +35,22 @@ +
  • + {{ $t('setting.keyboard_layout') }} + +
  • @@ -182,6 +198,31 @@ } } } + + .select { + max-width: 120px; + + select { + display: block; + width: 100%; + max-width: 100%; + padding: 4px; + margin: 0; + line-height: 30px; + font-weight: bold; + border: 0; + border-radius: 12px; + + color: black; + background-color: $style-primary; + + option { + font-weight: normal; + color: $text-normal; + background-color: $background-tertiary; + } + } + } } } } @@ -236,6 +277,14 @@ this.$accessor.settings.setSound(value) } + get keyboard_layout() { + return this.$accessor.settings.keyboard_layout + } + + set keyboard_layout(value: string) { + this.$accessor.settings.setKeyboardLayout(value) + } + logout() { this.$accessor.logout() } diff --git a/client/src/locale/en-us.ts b/client/src/locale/en-us.ts index 1dfe6454..2cb49328 100644 --- a/client/src/locale/en-us.ts +++ b/client/src/locale/en-us.ts @@ -60,6 +60,7 @@ export const setting = { autoplay: 'Autoplay Video', ignore_emotes: 'Ignore Emotes', chat_sound: 'Play Chat Sound', + keyboard_layout: 'Change Keyboard Layout', } export const connection = { diff --git a/client/src/store/settings.ts b/client/src/store/settings.ts index 9e1e29a3..ba3d06b7 100644 --- a/client/src/store/settings.ts +++ b/client/src/store/settings.ts @@ -10,6 +10,7 @@ export const state = () => { autoplay: get('autoplay', true), ignore_emotes: get('ignore_emotes', false), chat_sound: get('chat_sound', true), + keyboard_layout: get('keyboard_layout', 'us'), } } @@ -40,4 +41,9 @@ export const mutations = mutationTree(state, { state.chat_sound = value set('chat_sound', value) }, + + setKeyboardLayout(state, value: string) { + state.keyboard_layout = value + set('keyboard_layout', value) + }, }) From 70e8b215cbf5ab806740134ce740e543b02c6e85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Mon, 15 Jun 2020 22:26:47 +0200 Subject: [PATCH 15/27] new WS ControlRequest data type --- client/src/neko/messages.ts | 5 +++++ client/src/store/remote.ts | 2 +- server/internal/types/message/messages.go | 5 +++++ server/internal/websocket/control.go | 5 ++++- server/internal/websocket/handler.go | 6 +++++- 5 files changed, 20 insertions(+), 3 deletions(-) diff --git a/client/src/neko/messages.ts b/client/src/neko/messages.ts index b1eec752..b1b93807 100644 --- a/client/src/neko/messages.ts +++ b/client/src/neko/messages.ts @@ -29,6 +29,7 @@ export type WebSocketPayloads = | MemberListPayload | Member | ControlPayload + | ControlRequestPayload | ControlClipboardPayload | ChatPayload | ChatSendPayload @@ -111,6 +112,10 @@ export interface ControlPayload { id: string } +export interface ControlRequestPayload { + keyboard_layout: string +} + export interface ControlTargetPayload { id: string target: string diff --git a/client/src/store/remote.ts b/client/src/store/remote.ts index 965af5cb..03081cf5 100644 --- a/client/src/store/remote.ts +++ b/client/src/store/remote.ts @@ -64,7 +64,7 @@ export const actions = actionTree( } if (!getters.hosting) { - $client.sendMessage(EVENT.CONTROL.REQUEST) + $client.sendMessage(EVENT.CONTROL.REQUEST, { keyboard_layout: accessor.settings.keyboard_layout }) } else { $client.sendMessage(EVENT.CONTROL.RELEASE) } diff --git a/server/internal/types/message/messages.go b/server/internal/types/message/messages.go index a2f099f1..e0365054 100644 --- a/server/internal/types/message/messages.go +++ b/server/internal/types/message/messages.go @@ -51,6 +51,11 @@ type Control struct { ID string `json:"id"` } +type ControlRequest struct { + Event string `json:"event"` + KeyboardLayout string `json:"keyboard_layout"` +} + type ControlTarget struct { Event string `json:"event"` ID string `json:"id"` diff --git a/server/internal/websocket/control.go b/server/internal/websocket/control.go index bce0aeec..e7dabe59 100644 --- a/server/internal/websocket/control.go +++ b/server/internal/websocket/control.go @@ -31,9 +31,12 @@ func (h *MessageHandler) controlRelease(id string, session types.Session) error return nil } -func (h *MessageHandler) controlRequest(id string, session types.Session) error { +func (h *MessageHandler) controlRequest(id string, session types.Session, payload *message.ControlRequest) error { // check for host if !h.sessions.HasHost() { + // TODO: Keyboard Layout change. + h.logger.Warn().Msgf("should set keyboard to %s", payload.KeyboardLayout) + // set host h.sessions.SetHost(id) diff --git a/server/internal/websocket/handler.go b/server/internal/websocket/handler.go index 7ad21d3e..5753dd7b 100644 --- a/server/internal/websocket/handler.go +++ b/server/internal/websocket/handler.go @@ -76,7 +76,11 @@ func (h *MessageHandler) Message(id string, raw []byte) error { case event.CONTROL_RELEASE: return errors.Wrapf(h.controlRelease(id, session), "%s failed", header.Event) case event.CONTROL_REQUEST: - return errors.Wrapf(h.controlRequest(id, session), "%s failed", header.Event) + payload := &message.ControlRequest{} + return errors.Wrapf( + utils.Unmarshal(payload, raw, func() error { + return h.controlRequest(id, session, payload) + }), "%s failed", header.Event) case event.CONTROL_GIVE: payload := &message.Control{} return errors.Wrapf( From 4a7800c93fdab183a3bacd7c21a7001c02a36dec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Mon, 15 Jun 2020 23:14:23 +0200 Subject: [PATCH 16/27] change kbd layout using setxkbmap --- server/internal/remote/manager.go | 4 ++++ server/internal/types/remote.go | 1 + server/internal/websocket/control.go | 5 +++-- server/internal/xorg/xorg.c | 7 +++++++ server/internal/xorg/xorg.go | 10 ++++++++++ server/internal/xorg/xorg.h | 2 ++ 6 files changed, 27 insertions(+), 2 deletions(-) diff --git a/server/internal/remote/manager.go b/server/internal/remote/manager.go index 9a4a4e27..fe4413fb 100644 --- a/server/internal/remote/manager.go +++ b/server/internal/remote/manager.go @@ -218,3 +218,7 @@ func (manager *RemoteManager) ScreenConfigurations() map[int]types.ScreenConfigu func (manager *RemoteManager) GetScreenSize() *types.ScreenSize { return xorg.GetScreenSize() } + +func (manager *RemoteManager) SetKeyboard(layout string) { + xorg.SetKeyboard(layout) +} \ No newline at end of file diff --git a/server/internal/types/remote.go b/server/internal/types/remote.go index 3bdf7fd1..4b6453be 100644 --- a/server/internal/types/remote.go +++ b/server/internal/types/remote.go @@ -22,4 +22,5 @@ type RemoteManager interface { ReadClipboard() string WriteClipboard(data string) ResetKeys() + SetKeyboard(layout string) } diff --git a/server/internal/websocket/control.go b/server/internal/websocket/control.go index e7dabe59..d3b419fc 100644 --- a/server/internal/websocket/control.go +++ b/server/internal/websocket/control.go @@ -34,8 +34,9 @@ func (h *MessageHandler) controlRelease(id string, session types.Session) error func (h *MessageHandler) controlRequest(id string, session types.Session, payload *message.ControlRequest) error { // check for host if !h.sessions.HasHost() { - // TODO: Keyboard Layout change. - h.logger.Warn().Msgf("should set keyboard to %s", payload.KeyboardLayout) + // keyboard layout change + h.logger.Debug().Msgf("change keyboard to %s", payload.KeyboardLayout) + h.remote.SetKeyboard(payload.KeyboardLayout) // set host h.sessions.SetHost(id) diff --git a/server/internal/xorg/xorg.c b/server/internal/xorg/xorg.c index d1823864..2f129af1 100644 --- a/server/internal/xorg/xorg.c +++ b/server/internal/xorg/xorg.c @@ -165,3 +165,10 @@ short XGetScreenRate() { XRRScreenConfiguration *conf = XRRGetScreenInfo(display, RootWindow(display, 0)); return XRRConfigCurrentRate(conf); } + +void SetKeyboard(char *layout) { + // TOOD: refactor, use native API. + char cmd[12] = "setxkbmap "; + strcat(cmd, layout); + system(cmd); +} diff --git a/server/internal/xorg/xorg.go b/server/internal/xorg/xorg.go index 852a1551..8ff53f1f 100644 --- a/server/internal/xorg/xorg.go +++ b/server/internal/xorg/xorg.go @@ -210,6 +210,16 @@ func GetScreenSize() *types.ScreenSize { return nil } +func SetKeyboard(layout string) { + mu.Lock() + defer mu.Unlock() + + layoutUnsafe := C.CString(layout) + defer C.free(unsafe.Pointer(layoutUnsafe)) + + C.SetKeyboard(layoutUnsafe) +} + //export goCreateScreenSize func goCreateScreenSize(index C.int, width C.int, height C.int, mwidth C.int, mheight C.int) { ScreenConfigurations[int(index)] = types.ScreenConfiguration{ diff --git a/server/internal/xorg/xorg.h b/server/internal/xorg/xorg.h index e29b95d8..24384c48 100644 --- a/server/internal/xorg/xorg.h +++ b/server/internal/xorg/xorg.h @@ -38,5 +38,7 @@ void XDisplayClose(void); void XDisplaySet(char *input); + + void SetKeyboard(char *layout); #endif From 56bd6acf100f37ca12559f8def619e98f919af13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Tue, 16 Jun 2020 00:23:00 +0200 Subject: [PATCH 17/27] security fix: only 2 chars for setxkbmap --- server/internal/xorg/xorg.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/internal/xorg/xorg.c b/server/internal/xorg/xorg.c index 2f129af1..3868f5d2 100644 --- a/server/internal/xorg/xorg.c +++ b/server/internal/xorg/xorg.c @@ -168,7 +168,7 @@ short XGetScreenRate() { void SetKeyboard(char *layout) { // TOOD: refactor, use native API. - char cmd[12] = "setxkbmap "; - strcat(cmd, layout); + char cmd[13] = "setxkbmap "; + strncat(cmd, layout, 2); system(cmd); } From 6206fbbadd5a8578b81d1abc118c279dfd6b0967 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Tue, 16 Jun 2020 00:55:14 +0200 Subject: [PATCH 18/27] add new WS keyboard event --- client/src/neko/events.ts | 2 ++ client/src/neko/messages.ts | 5 +++++ client/src/store/remote.ts | 8 ++++++++ server/internal/types/event/events.go | 1 + server/internal/types/message/messages.go | 5 +++++ server/internal/websocket/control.go | 11 +++++++++++ server/internal/websocket/handler.go | 7 +++++++ 7 files changed, 39 insertions(+) diff --git a/client/src/neko/events.ts b/client/src/neko/events.ts index b61476a9..725e675e 100644 --- a/client/src/neko/events.ts +++ b/client/src/neko/events.ts @@ -27,6 +27,7 @@ export const EVENT = { REQUESTING: 'control/requesting', CLIPBOARD: 'control/clipboard', GIVE: 'control/give', + KEYBOARD: 'control/keyboard' }, CHAT: { MESSAGE: 'chat/message', @@ -67,6 +68,7 @@ export type ControlEvents = | typeof EVENT.CONTROL.REQUEST | typeof EVENT.CONTROL.GIVE | typeof EVENT.CONTROL.CLIPBOARD + | typeof EVENT.CONTROL.KEYBOARD export type SystemEvents = typeof EVENT.SYSTEM.DISCONNECT export type MemberEvents = typeof EVENT.MEMBER.LIST | typeof EVENT.MEMBER.CONNECTED | typeof EVENT.MEMBER.DISCONNECTED diff --git a/client/src/neko/messages.ts b/client/src/neko/messages.ts index b1b93807..67138c53 100644 --- a/client/src/neko/messages.ts +++ b/client/src/neko/messages.ts @@ -31,6 +31,7 @@ export type WebSocketPayloads = | ControlPayload | ControlRequestPayload | ControlClipboardPayload + | ControlKeyboardPayload | ChatPayload | ChatSendPayload | EmojiSendPayload @@ -125,6 +126,10 @@ export interface ControlClipboardPayload { text: string } +export interface ControlKeyboardPayload { + layout: string +} + /* CHAT PAYLOADS */ diff --git a/client/src/store/remote.ts b/client/src/store/remote.ts index 03081cf5..95d5165c 100644 --- a/client/src/store/remote.ts +++ b/client/src/store/remote.ts @@ -133,5 +133,13 @@ export const actions = actionTree( $client.sendMessage(EVENT.ADMIN.GIVE, { id: member.id }) }, + + changeKeyboard({ getters }) { + if (!accessor.connected || !getters.hosting) { + return + } + + $client.sendMessage(EVENT.CONTROL.KEYBOARD, { layout: accessor.settings.keyboard_layout }) + } }, ) diff --git a/server/internal/types/event/events.go b/server/internal/types/event/events.go index 1ee171ea..7b9d31ba 100644 --- a/server/internal/types/event/events.go +++ b/server/internal/types/event/events.go @@ -22,6 +22,7 @@ const ( CONTROL_REQUESTING = "control/requesting" CONTROL_GIVE = "control/give" CONTROL_CLIPBOARD = "control/clipboard" + CONTROL_KEYBOARD = "control/keyboard" ) const ( diff --git a/server/internal/types/message/messages.go b/server/internal/types/message/messages.go index e0365054..d3287b36 100644 --- a/server/internal/types/message/messages.go +++ b/server/internal/types/message/messages.go @@ -46,6 +46,11 @@ type Clipboard struct { Text string `json:"text"` } +type Keyboard struct { + Event string `json:"event"` + Layout string `json:"layout"` +} + type Control struct { Event string `json:"event"` ID string `json:"id"` diff --git a/server/internal/websocket/control.go b/server/internal/websocket/control.go index d3b419fc..8b96bd9d 100644 --- a/server/internal/websocket/control.go +++ b/server/internal/websocket/control.go @@ -119,3 +119,14 @@ func (h *MessageHandler) controlClipboard(id string, session types.Session, payl h.remote.WriteClipboard(payload.Text) return nil } + +func (h *MessageHandler) controlKeyboard(id string, session types.Session, payload *message.Keyboard) error { + // check if session is host + if !h.sessions.IsHost(id) { + h.logger.Debug().Str("id", id).Msg("is not the host") + return nil + } + + h.remote.SetKeyboard(payload.Layout) + return nil +} diff --git a/server/internal/websocket/handler.go b/server/internal/websocket/handler.go index 5753dd7b..25b043e5 100644 --- a/server/internal/websocket/handler.go +++ b/server/internal/websocket/handler.go @@ -93,6 +93,13 @@ func (h *MessageHandler) Message(id string, raw []byte) error { utils.Unmarshal(payload, raw, func() error { return h.controlClipboard(id, session, payload) }), "%s failed", header.Event) + case event.CONTROL_KEYBOARD: + payload := &message.Keyboard{} + return errors.Wrapf( + utils.Unmarshal(payload, raw, func() error { + return h.controlKeyboard(id, session, payload) + }), "%s failed", header.Event) + // Chat Events case event.CHAT_MESSAGE: From e60f9ce8381d7029893bb77ae65d9215fd2aee73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Tue, 16 Jun 2020 00:55:38 +0200 Subject: [PATCH 19/27] changeKeyboard on settings change --- client/src/components/settings.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/client/src/components/settings.vue b/client/src/components/settings.vue index 5f0fd127..921d2762 100644 --- a/client/src/components/settings.vue +++ b/client/src/components/settings.vue @@ -283,6 +283,7 @@ set keyboard_layout(value: string) { this.$accessor.settings.setKeyboardLayout(value) + this.$accessor.remote.changeKeyboard() } logout() { From 8113ce276c4c6a68f5f0676d24ad0a68c88ae4c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Tue, 16 Jun 2020 00:57:12 +0200 Subject: [PATCH 20/27] changeKeyboard on control give --- client/src/neko/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/src/neko/index.ts b/client/src/neko/index.ts index 880e7935..551fbfa4 100644 --- a/client/src/neko/index.ts +++ b/client/src/neko/index.ts @@ -251,6 +251,8 @@ export class NekoClient extends BaseClient implements EventEmitter { } this.$accessor.remote.setHost(member) + this.$accessor.remote.changeKeyboard() + this.$accessor.chat.newMessage({ id, content: this.$vue.$t('notifications.controls_given', { From 01564a57b5cfdc2e0802ac3d6241a6579541f8c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Tue, 16 Jun 2020 01:01:23 +0200 Subject: [PATCH 21/27] Revert "new WS ControlRequest data type" This reverts commit 70e8b215cbf5ab806740134ce740e543b02c6e85. --- client/src/neko/messages.ts | 5 ----- client/src/store/remote.ts | 2 +- server/internal/types/message/messages.go | 5 ----- server/internal/websocket/control.go | 6 +----- server/internal/websocket/handler.go | 6 +----- 5 files changed, 3 insertions(+), 21 deletions(-) diff --git a/client/src/neko/messages.ts b/client/src/neko/messages.ts index 67138c53..5192b549 100644 --- a/client/src/neko/messages.ts +++ b/client/src/neko/messages.ts @@ -29,7 +29,6 @@ export type WebSocketPayloads = | MemberListPayload | Member | ControlPayload - | ControlRequestPayload | ControlClipboardPayload | ControlKeyboardPayload | ChatPayload @@ -113,10 +112,6 @@ export interface ControlPayload { id: string } -export interface ControlRequestPayload { - keyboard_layout: string -} - export interface ControlTargetPayload { id: string target: string diff --git a/client/src/store/remote.ts b/client/src/store/remote.ts index 95d5165c..991ccc63 100644 --- a/client/src/store/remote.ts +++ b/client/src/store/remote.ts @@ -64,7 +64,7 @@ export const actions = actionTree( } if (!getters.hosting) { - $client.sendMessage(EVENT.CONTROL.REQUEST, { keyboard_layout: accessor.settings.keyboard_layout }) + $client.sendMessage(EVENT.CONTROL.REQUEST) } else { $client.sendMessage(EVENT.CONTROL.RELEASE) } diff --git a/server/internal/types/message/messages.go b/server/internal/types/message/messages.go index d3287b36..9d994810 100644 --- a/server/internal/types/message/messages.go +++ b/server/internal/types/message/messages.go @@ -56,11 +56,6 @@ type Control struct { ID string `json:"id"` } -type ControlRequest struct { - Event string `json:"event"` - KeyboardLayout string `json:"keyboard_layout"` -} - type ControlTarget struct { Event string `json:"event"` ID string `json:"id"` diff --git a/server/internal/websocket/control.go b/server/internal/websocket/control.go index 8b96bd9d..234c5d06 100644 --- a/server/internal/websocket/control.go +++ b/server/internal/websocket/control.go @@ -31,13 +31,9 @@ func (h *MessageHandler) controlRelease(id string, session types.Session) error return nil } -func (h *MessageHandler) controlRequest(id string, session types.Session, payload *message.ControlRequest) error { +func (h *MessageHandler) controlRequest(id string, session types.Session) error { // check for host if !h.sessions.HasHost() { - // keyboard layout change - h.logger.Debug().Msgf("change keyboard to %s", payload.KeyboardLayout) - h.remote.SetKeyboard(payload.KeyboardLayout) - // set host h.sessions.SetHost(id) diff --git a/server/internal/websocket/handler.go b/server/internal/websocket/handler.go index 25b043e5..5693c970 100644 --- a/server/internal/websocket/handler.go +++ b/server/internal/websocket/handler.go @@ -76,11 +76,7 @@ func (h *MessageHandler) Message(id string, raw []byte) error { case event.CONTROL_RELEASE: return errors.Wrapf(h.controlRelease(id, session), "%s failed", header.Event) case event.CONTROL_REQUEST: - payload := &message.ControlRequest{} - return errors.Wrapf( - utils.Unmarshal(payload, raw, func() error { - return h.controlRequest(id, session, payload) - }), "%s failed", header.Event) + return errors.Wrapf(h.controlRequest(id, session), "%s failed", header.Event) case event.CONTROL_GIVE: payload := &message.Control{} return errors.Wrapf( From 48bbededc1bd58a7b7c5697868b15e21f424056c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Tue, 16 Jun 2020 01:26:26 +0200 Subject: [PATCH 22/27] try to changeKeyboard everytime host changes --- client/src/neko/index.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client/src/neko/index.ts b/client/src/neko/index.ts index 551fbfa4..e1f5b832 100644 --- a/client/src/neko/index.ts +++ b/client/src/neko/index.ts @@ -165,6 +165,8 @@ export class NekoClient extends BaseClient implements EventEmitter { ///////////////////////////// protected [EVENT.CONTROL.LOCKED]({ id }: ControlPayload) { this.$accessor.remote.setHost(id) + this.$accessor.remote.changeKeyboard() + const member = this.member(id) if (!member) { return @@ -433,6 +435,7 @@ export class NekoClient extends BaseClient implements EventEmitter { protected [EVENT.ADMIN.CONTROL]({ id, target }: AdminTargetPayload) { this.$accessor.remote.setHost(id) + this.$accessor.remote.changeKeyboard() if (!target) { this.$accessor.chat.newMessage({ @@ -497,6 +500,7 @@ export class NekoClient extends BaseClient implements EventEmitter { } this.$accessor.remote.setHost(member) + this.$accessor.remote.changeKeyboard() this.$accessor.chat.newMessage({ id, From e6c7dd012216214ecc5eb24ced0f0ba98c0c7dfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Tue, 16 Jun 2020 02:09:05 +0200 Subject: [PATCH 23/27] keyboard layouts from file --- client/public/keyboard_layouts.json | 1 + client/src/components/settings.vue | 17 +++++++++-------- client/src/store/index.ts | 1 + client/src/store/settings.ts | 28 +++++++++++++++++++++++++++- 4 files changed, 38 insertions(+), 9 deletions(-) create mode 100644 client/public/keyboard_layouts.json diff --git a/client/public/keyboard_layouts.json b/client/public/keyboard_layouts.json new file mode 100644 index 00000000..cebd4635 --- /dev/null +++ b/client/public/keyboard_layouts.json @@ -0,0 +1 @@ +{"af":"Afghani","al":"Albanian","et":"Amharic","ma":"Arabic (Morocco)","sy":"Arabic (Syria)","am":"Armenian","az":"Azerbaijani","ml":"Bambara","bd":"Bangla","by":"Belarusian","be":"Belgian","dz":"Berber (Algeria, Latin characters)","ba":"Bosnian","bg":"Bulgarian","mm":"Burmese","hr":"Croatian","cz":"Czech","dk":"Danish","mv":"Dhivehi","nl":"Dutch","bt":"Dzongkha","au":"English (Australian)","cm":"English (Cameroon)","gh":"English (Ghana)","ng":"English (Nigeria)","za":"English (South Africa)","us":"English (US)","gb":"English (UK)","ee":"Estonian","fo":"Faroese","ph":"Filipino","fi":"Finnish","fr":"French","ca":"French (Canada)","cd":"French (Democratic Republic of the Congo)","gn":"French (Guinea)","tg":"French (Togo)","ge":"Georgian","de":"German","at":"German (Austria)","ch":"German (Switzerland)","gr":"Greek","il":"Hebrew","hu":"Hungarian","cn":"Chinese","is":"Icelandic","in":"Indian","id":"Indonesian (Jawi)","iq":"Iraqi","ie":"Irish","it":"Italian","jp":"Japanese","kz":"Kazakh","kh":"Khmer (Cambodia)","kr":"Korean","kg":"Kyrgyz","la":"Lao","lv":"Latvian","lt":"Lithuanian","mk":"Macedonian","my":"Malay (Jawi)","mt":"Maltese","md":"Moldavian","mn":"Mongolian","me":"Montenegrin","np":"Nepali","no":"Norwegian","ir":"Persian","pl":"Polish","pt":"Portuguese","br":"Portuguese (Brazil)","ro":"Romanian","ru":"Russian","rs":"Serbian","lk":"Sinhala (phonetic)","sk":"Slovak","si":"Slovenian","es":"Spanish","ke":"Swahili (Kenya)","tz":"Swahili (Tanzania)","se":"Swedish","tw":"Taiwanese","tj":"Tajik","th":"Thai","bw":"Tswana","tr":"Turkish","tm":"Turkmen","ua":"Ukrainian","pk":"Urdu (Pakistan)","uz":"Uzbek","vn":"Vietnamese","sn":"Wolof"} \ No newline at end of file diff --git a/client/src/components/settings.vue b/client/src/components/settings.vue index 921d2762..946d9369 100644 --- a/client/src/components/settings.vue +++ b/client/src/components/settings.vue @@ -39,14 +39,11 @@ {{ $t('setting.keyboard_layout') }} @@ -277,6 +274,10 @@ this.$accessor.settings.setSound(value) } + get keyboard_layouts_list() { + return this.$accessor.settings.keyboard_layouts_list + } + get keyboard_layout() { return this.$accessor.settings.keyboard_layout } diff --git a/client/src/store/index.ts b/client/src/store/index.ts index 97ae6f58..ba186175 100644 --- a/client/src/store/index.ts +++ b/client/src/store/index.ts @@ -55,6 +55,7 @@ export const actions = actionTree( { initialise(store) { accessor.emoji.initialise() + accessor.settings.initialise() }, lock() { diff --git a/client/src/store/settings.ts b/client/src/store/settings.ts index ba3d06b7..eb1295db 100644 --- a/client/src/store/settings.ts +++ b/client/src/store/settings.ts @@ -1,8 +1,13 @@ -import { getterTree, mutationTree } from 'typed-vuex' +import { getterTree, mutationTree, actionTree } from 'typed-vuex' import { get, set } from '~/utils/localstorage' +import { accessor } from '~/store' export const namespaced = true +interface KeyboardLayouts { + [code: string]: string +} + export const state = () => { return { scroll: get('scroll', 10), @@ -11,6 +16,8 @@ export const state = () => { ignore_emotes: get('ignore_emotes', false), chat_sound: get('chat_sound', true), keyboard_layout: get('keyboard_layout', 'us'), + + keyboard_layouts_list: {} as KeyboardLayouts, } } @@ -46,4 +53,23 @@ export const mutations = mutationTree(state, { state.keyboard_layout = value set('keyboard_layout', value) }, + + setKeyboardLayoutsList(state, value: KeyboardLayouts) { + state.keyboard_layouts_list = value + }, }) + +export const actions = actionTree( + { state, getters, mutations }, + { + initialise() { + $http + .get('/keyboard_layouts.json') + .then((req) => { + accessor.settings.setKeyboardLayoutsList(req.data) + console.log(req.data) + }) + .catch(console.error) + }, + }, +) From fcca903ae95cf652538ff2ec2c25a37935b2eca0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Tue, 16 Jun 2020 02:39:15 +0200 Subject: [PATCH 24/27] allow only a-zA-Z for keyboard layout --- server/internal/xorg/xorg.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/server/internal/xorg/xorg.go b/server/internal/xorg/xorg.go index 8ff53f1f..200294cc 100644 --- a/server/internal/xorg/xorg.go +++ b/server/internal/xorg/xorg.go @@ -13,6 +13,7 @@ import ( "sync" "time" "unsafe" + "regexp" "n.eko.moe/neko/internal/types" ) @@ -214,6 +215,10 @@ func SetKeyboard(layout string) { mu.Lock() defer mu.Unlock() + if !regexp.MustCompile(`^[a-zA-Z]+$`).MatchString(layout) { + return + } + layoutUnsafe := C.CString(layout) defer C.free(unsafe.Pointer(layoutUnsafe)) From 01d6ea5ad0881503b8edf165608f127aa3a8b28d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Tue, 16 Jun 2020 13:12:36 +0200 Subject: [PATCH 25/27] BUG: prevent events when hosting --- client/src/components/video.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/components/video.vue b/client/src/components/video.vue index 9023498b..6952b42c 100644 --- a/client/src/components/video.vue +++ b/client/src/components/video.vue @@ -339,11 +339,11 @@ /* Initialize Guacamole Keyboard */ this.keyboard.onkeydown = (key: number) => { if (!this.focused || !this.hosting || this.locked) { - return false + return true } this.$client.sendData('keydown', { key }) - return true + return false } this.keyboard.onkeyup = (key: number) => { if (!this.focused || !this.hosting || this.locked) { From add6c35928950ed49a84e2de5ccdffd2677d1b57 Mon Sep 17 00:00:00 2001 From: Laurant Date: Thu, 18 Jun 2020 23:23:56 +0200 Subject: [PATCH 26/27] Change minimum scroll speed value to 1 --- client/src/components/settings.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/settings.vue b/client/src/components/settings.vue index 16660415..91ca3712 100644 --- a/client/src/components/settings.vue +++ b/client/src/components/settings.vue @@ -4,7 +4,7 @@
  • {{ $t('setting.scroll') }}
  • From 832968e8fc0cd12bd086abe91fd92bb3401ec794 Mon Sep 17 00:00:00 2001 From: m1k1o Date: Sun, 21 Jun 2020 03:05:58 +0200 Subject: [PATCH 27/27] fix naming convention --- server/internal/remote/manager.go | 4 ++-- server/internal/types/remote.go | 2 +- server/internal/websocket/control.go | 2 +- server/internal/xorg/xorg.c | 2 +- server/internal/xorg/xorg.go | 4 ++-- server/internal/xorg/xorg.h | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/server/internal/remote/manager.go b/server/internal/remote/manager.go index fe4413fb..c646a39e 100644 --- a/server/internal/remote/manager.go +++ b/server/internal/remote/manager.go @@ -219,6 +219,6 @@ func (manager *RemoteManager) GetScreenSize() *types.ScreenSize { return xorg.GetScreenSize() } -func (manager *RemoteManager) SetKeyboard(layout string) { - xorg.SetKeyboard(layout) +func (manager *RemoteManager) SetKeyboardLayout(layout string) { + xorg.SetKeyboardLayout(layout) } \ No newline at end of file diff --git a/server/internal/types/remote.go b/server/internal/types/remote.go index 4b6453be..d2b0e36e 100644 --- a/server/internal/types/remote.go +++ b/server/internal/types/remote.go @@ -22,5 +22,5 @@ type RemoteManager interface { ReadClipboard() string WriteClipboard(data string) ResetKeys() - SetKeyboard(layout string) + SetKeyboardLayout(layout string) } diff --git a/server/internal/websocket/control.go b/server/internal/websocket/control.go index 234c5d06..7a2ffff9 100644 --- a/server/internal/websocket/control.go +++ b/server/internal/websocket/control.go @@ -123,6 +123,6 @@ func (h *MessageHandler) controlKeyboard(id string, session types.Session, paylo return nil } - h.remote.SetKeyboard(payload.Layout) + h.remote.SetKeyboardLayout(payload.Layout) return nil } diff --git a/server/internal/xorg/xorg.c b/server/internal/xorg/xorg.c index 3868f5d2..d49c3939 100644 --- a/server/internal/xorg/xorg.c +++ b/server/internal/xorg/xorg.c @@ -166,7 +166,7 @@ short XGetScreenRate() { return XRRConfigCurrentRate(conf); } -void SetKeyboard(char *layout) { +void SetKeyboardLayout(char *layout) { // TOOD: refactor, use native API. char cmd[13] = "setxkbmap "; strncat(cmd, layout, 2); diff --git a/server/internal/xorg/xorg.go b/server/internal/xorg/xorg.go index 200294cc..5280a8ef 100644 --- a/server/internal/xorg/xorg.go +++ b/server/internal/xorg/xorg.go @@ -211,7 +211,7 @@ func GetScreenSize() *types.ScreenSize { return nil } -func SetKeyboard(layout string) { +func SetKeyboardLayout(layout string) { mu.Lock() defer mu.Unlock() @@ -222,7 +222,7 @@ func SetKeyboard(layout string) { layoutUnsafe := C.CString(layout) defer C.free(unsafe.Pointer(layoutUnsafe)) - C.SetKeyboard(layoutUnsafe) + C.SetKeyboardLayout(layoutUnsafe) } //export goCreateScreenSize diff --git a/server/internal/xorg/xorg.h b/server/internal/xorg/xorg.h index 24384c48..cf0cd3e8 100644 --- a/server/internal/xorg/xorg.h +++ b/server/internal/xorg/xorg.h @@ -39,6 +39,6 @@ void XDisplayClose(void); void XDisplaySet(char *input); - void SetKeyboard(char *layout); + void SetKeyboardLayout(char *layout); #endif