+
diff --git a/web/kvm/window-keyboard.pug b/web/kvm/window-keyboard.pug
index e134e628..5c7fcc3e 100644
--- a/web/kvm/window-keyboard.pug
+++ b/web/kvm/window-keyboard.pug
@@ -13,7 +13,7 @@ mixin key(sp, code, classes="", width=0)
mixin modifier(sp, code, classes="", width=0)
- div(data-code=code class=`modifier ${classes}` style=(width ? `width: ${width}px` : ""))
+ div(data-code=code, data-allow-autohold, class=`key ${classes}`, style=(width ? `width: ${width}px` : ""))
.label
| #[b •]#[br]
block
@@ -21,7 +21,7 @@ mixin modifier(sp, code, classes="", width=0)
mixin empty(sp, classes="", width=0)
- div(class=`empty ${classes}` style=(width ? `width:${width}px` : ""))
+ div(class=`empty ${classes}`, style=(width ? `width:${width}px` : ""))
.label
+spacer(sp)
diff --git a/web/kvm/window-stream.pug b/web/kvm/window-stream.pug
index 51bbc32f..8fc7d199 100644
--- a/web/kvm/window-stream.pug
+++ b/web/kvm/window-stream.pug
@@ -24,28 +24,16 @@
.keypad#stream-mouse-buttons(align="center")
.keypad-block
.keypad-row
- .key.wide-3.left.rounded-left(data-code="left")
- .label Left
- .modifier.left.small.rounded-right(data-code="left")
- .label #[b •]#[br]Hold
-
- .empty(style="width: 15px")
-
- .key.wide-1.left.rounded-left(data-code="middle")
- .label Mid
- .modifier.left.small.rounded-right(data-code="middle")
- .label #[b •]#[br]Hold
-
- .empty(style="width: 15px")
-
- .modifier.right.small.rounded-left(data-code="right")
- .label #[b •]#[br]Hold
- .key.wide-3.right.rounded-right(data-code="right")
- .label Right
+ .key.wide-3.left.rounded-left(data-code="left" data-allow-autohold)
+ .label #[b •]#[br]Left
+ .key.wide-1.rounded-none(data-code="middle" data-allow-autohold)
+ .label #[b •]#[br]Mid
+ .key.wide-3.right.rounded-right(data-code="right" data-allow-autohold)
+ .label #[b •]#[br]Right
.empty(style="width: 30px")
- .key.small.rounded-left(data-code="up")
- .label Up
- .key.small.rounded-right(data-code="down")
- .label Down
+ .key.small.rounded-left(data-code="up" data-allow-autohold)
+ .label #[b •]#[br]Up
+ .key.small.rounded-right(data-code="down" data-allow-autohold)
+ .label #[b •]#[br]Down
diff --git a/web/share/css/keypad.css b/web/share/css/keypad.css
index 141ecf18..7456b8c5 100644
--- a/web/share/css/keypad.css
+++ b/web/share/css/keypad.css
@@ -52,7 +52,6 @@ div.keypad div.keypad-row div.spacer-fixed {
}
div.keypad div.key,
-div.keypad div.modifier,
div.keypad div.empty {
vertical-align: top;
font-size: 0.9em;
@@ -65,8 +64,7 @@ div.keypad div.empty {
div.keypad div.empty {
border: thin solid transparent;
}
-div.keypad div.key,
-div.keypad div.modifier {
+div.keypad div.key {
box-shadow: var(--shadow-micro);
border: var(--border-key-thin);
border-radius: 6px;
@@ -74,33 +72,57 @@ div.keypad div.modifier {
background-color: var(--cs-key-default-bg);
cursor: pointer;
}
-div.keypad div.key:hover,
-div.keypad div.modifier:hover {
- color: var(--cs-key-hovered-fg);
- background-color: var(--cs-key-hovered-bg);
+@media (hover: hover) {
+ div.keypad div.key:not(div.holded):not(div.locked):hover {
+ color: var(--cs-key-hovered-fg);
+ background-color: var(--cs-key-hovered-bg);
+ }
}
+
div.keypad div.rounded-left {
- border-radius: 6px 0px 0px 6px !important;
+ border-radius: 6px 0px 0px 6px;
}
div.keypad div.rounded-right {
- border-radius: 0px 6px 6px 0px !important;
+ border-radius: 0px 6px 6px 0px;
}
div.keypad div.rounded-none {
- border-radius: 0px !important;
+ border-radius: 0px;
}
+
div.keypad div.pressed {
box-shadow: none;
- color: var(--cs-key-pressed-fg) !important;
- background-color: var(--cs-key-pressed-bg) !important;
+ color: var(--cs-key-pressed-fg);
+ background-color: var(--cs-key-pressed-bg);
}
+
+div.keypad div.pressed:not(div.holded):not(div.locked):hover[data-allow-autohold] {
+ /* :active is not working on Firefox and iOS */
+ background: linear-gradient(to top, var(--cs-key-holded-bg) 50%, var(--cs-key-pressed-bg) 0);
+ background-size: 100% 200%;
+ background-position: top;
+ animation: keypad-animate-holding 0.2s 0.3s forwards;
+}
+@keyframes keypad-animate-holding {
+ 100% {
+ background-position: bottom;
+ }
+}
+
div.keypad div.holded {
- box-shadow: none;
- color: var(--cs-key-default-fg) !important;
+ /* Override animation end on iOS with !important */
+ box-shadow: none !important;
+ color: var(--cs-key-holded-fg) !important;
background-color: var(--cs-key-holded-bg) !important;
}
+
+div.keypad div.locked {
+ box-shadow: none;
+ color: var(--cs-key-locked-fg);
+ background-color: var(--cs-key-locked-bg);
+}
+
div.keypad div.key:last-child,
-div.keypad div.empty:last-child,
-div.keypad div.modifier:last-child {
+div.keypad div.empty:last-child {
margin-right: 0;
}
div.keypad div.wide-0 {
@@ -120,12 +142,12 @@ div.keypad div.wide-4 {
width: 288px;
}
div.keypad div.left {
- text-align: left !important;
- padding-left: 6px !important;
+ text-align: left;
+ padding-left: 6px;
}
div.keypad div.right {
- text-align: right !important;
- padding-right: 6px !important;
+ text-align: right;
+ padding-right: 6px;
}
div.keypad div.small {
font-size: 0.7em;
@@ -142,3 +164,6 @@ div.keypad div.label {
div.keypad b {
color: var(--cs-key-holded-bg);
}
+div.keypad div.locked b {
+ color: var(--cs-key-locked-bg);
+}
diff --git a/web/share/css/vars.css b/web/share/css/vars.css
index 289c9dd2..cfa5a0b1 100644
--- a/web/share/css/vars.css
+++ b/web/share/css/vars.css
@@ -66,6 +66,9 @@
--cs-key-pressed-bg: #17191d;
--cs-key-pressed-fg: #6c7481;
--cs-key-holded-bg: #436a8a;
+ --cs-key-holded-fg: white;
+ --cs-key-locked-bg: #a80000;
+ --cs-key-locked-fg: white;
--cs-marker-fg: #5b90bb;
--cs-corner-bg: #5b90bb;
diff --git a/web/share/css/x-mobile.css b/web/share/css/x-mobile.css
index 46d0b5ea..7db2bfd1 100644
--- a/web/share/css/x-mobile.css
+++ b/web/share/css/x-mobile.css
@@ -113,9 +113,3 @@ ul#navbar li a.menu-button:hover:not(.active) {
div.keypad {
zoom: 1.28 !important;
}
-
-div.keypad div.key:hover,
-div.keypad div.modifier:hover {
- color: var(--cs-key-default-fg);
- background-color: var(--cs-key-default-bg);
-}
diff --git a/web/share/js/keypad.js b/web/share/js/keypad.js
index 454bc405..a432e92d 100644
--- a/web/share/js/keypad.js
+++ b/web/share/js/keypad.js
@@ -23,73 +23,67 @@
"use strict";
-import {tools, $$$} from "./tools.js";
+import {tools} from "./tools.js";
-export function Keypad(__keys_parent, __sendKey, __apply_fixes) {
+export function Keypad(__el_keypad, __sendKey, __apply_fixes) {
var self = this;
/************************************************************************/
- var __merged = {};
var __keys = {};
- var __modifiers = {};
+ var __hold_timers = {};
var __fix_mac_cmd = false;
var __fix_win_altgr = false;
var __altgr_ctrl_timer = null;
var __init__ = function() {
+ __el_keypad.addEventListener("contextmenu", (ev) => ev.preventDefault());
+
if (__apply_fixes) {
__fix_mac_cmd = tools.browser.is_mac;
if (__fix_mac_cmd) {
- tools.info(`Keymap at ${__keys_parent}: enabled Fix-Mac-CMD`);
+ tools.info(`Keymap at ${__el_keypad.id}: enabled Fix-Mac-CMD`);
}
__fix_win_altgr = tools.browser.is_win;
if (__fix_win_altgr) {
- tools.info(`Keymap at ${__keys_parent}: enabled Fix-Win-AltGr`);
+ tools.info(`Keymap at ${__el_keypad.id}: enabled Fix-Win-AltGr`);
}
}
- for (let el_key of $$$(`${__keys_parent} div.key`)) {
+ for (let el_key of [].slice.call(__el_keypad.getElementsByClassName("key"))) {
+ if (el_key.hasAttribute("data-allow-autohold")) {
+ el_key.title = "Long left click or short right click for hold, middle for lock";
+ } else {
+ el_key.title = "Right click for hold, middle for lock";
+ }
+
let code = el_key.getAttribute("data-code");
tools.setDefault(__keys, code, []);
__keys[code].push(el_key);
- tools.setDefault(__merged, code, []);
- __merged[code].push(el_key);
-
- tools.el.setOnDown(el_key, () => __clickHandler(el_key, true));
- tools.el.setOnUp(el_key, () => __clickHandler(el_key, false));
+ tools.el.setOnDown(el_key, (ev) => __clickHandler(el_key, ev));
+ tools.el.setOnUp(el_key, () => __clickHandler(el_key, null));
el_key.onmouseout = function() {
- if (__isPressed(el_key)) {
- __clickHandler(el_key, false);
+ if (
+ __isActive(el_key, "pressed")
+ && !__isActive(el_key, "holded")
+ && !__isActive(el_key, "locked")
+ ) {
+ __clickHandler(el_key, null);
}
};
}
-
- for (let el_key of $$$(`${__keys_parent} div.modifier`)) {
- let code = el_key.getAttribute("data-code");
-
- tools.setDefault(__modifiers, code, []);
- __modifiers[code].push(el_key);
-
- tools.setDefault(__merged, code, []);
- __merged[code].push(el_key);
-
- tools.el.setOnDown(el_key, () => __toggleModifierHandler(el_key));
- }
};
/************************************************************************/
self.releaseAll = function() {
- for (let dict of [__keys, __modifiers]) {
- for (let code in dict) {
- if (__isActive(dict[code][0])) {
- self.emitByCode(code, false);
- }
+ for (let code in __keys) {
+ if (__isActive(__keys[code][0])) {
+ self.emitByCode(code, false);
}
}
};
@@ -113,7 +107,10 @@ export function Keypad(__keys_parent, __sendKey, __apply_fixes) {
};
self.emitByCode = function(code, state, apply_fixes=true) {
- if (code in __merged) {
+ if (code in __keys) {
+ let el_key = __keys[code][0];
+ __stopHoldTimer(el_key);
+
if (__fix_win_altgr && apply_fixes) {
if (!__fixWinAltgr(code, state)) {
return;
@@ -122,9 +119,17 @@ export function Keypad(__keys_parent, __sendKey, __apply_fixes) {
if (__fix_mac_cmd && apply_fixes) {
__fixMacCmd(code, state);
}
- __commonHandler(__merged[code][0], state, false);
- __unholdModifiers();
- }
+
+ if (state && !__isActive(el_key)) {
+ __deactivate(el_key);
+ __activate(el_key, "pressed");
+ __process(el_key, true);
+ } else {
+ __deactivate(el_key);
+ __process(el_key, false);
+ }
+ __unholdAll();
+ };
};
var __fixMacCmd = function(code, state) {
@@ -148,7 +153,7 @@ export function Keypad(__keys_parent, __sendKey, __apply_fixes) {
self.emitByCode("ControlLeft", true, false);
}
}
- if (code === "ControlLeft" && !__isActive(__modifiers["ControlLeft"][0])) {
+ if (code === "ControlLeft" && !__isActive(__keys["ControlLeft"][0])) {
__altgr_ctrl_timer = setTimeout(function() {
__altgr_ctrl_timer = null;
self.emitByCode("ControlLeft", true, false);
@@ -165,61 +170,93 @@ export function Keypad(__keys_parent, __sendKey, __apply_fixes) {
return true; // Continue handling
};
- var __clickHandler = function(el_key, state) {
- __commonHandler(el_key, state, false);
- __unholdModifiers();
- };
+ var __clickHandler = function(el_key, ev) {
+ let state = false;
+ let act = "pressed";
+ if (ev) {
+ state = (ev.type === "mousedown" || ev.type === "touchstart");
+ if (ev.type === "mousedown") {
+ if (ev.button === 1) {
+ act = "locked";
+ } else if (ev.button === 2) {
+ act = "holded";
+ }
+ }
+ }
- var __toggleModifierHandler = function(el_key) {
- __commonHandler(el_key, !__isActive(el_key), true);
- };
-
- var __commonHandler = function(el_key, state, hold) {
if (state && !__isActive(el_key)) {
+ __stopHoldTimer(el_key);
__deactivate(el_key);
- __activate(el_key, (hold ? "holded" : "pressed"));
+ __activate(el_key, act);
__process(el_key, true);
+ __startHoldTimer(el_key);
} else {
- __deactivate(el_key);
- __process(el_key, false);
+ let fixed = (__isActive(el_key, "holded") || __isActive(el_key, "locked"));
+ if (!state && fixed && __stopHoldTimer(el_key)) {
+ return; // Игнорировать первое отжатие сразу после нажатия
+ }
+ if (!state) {
+ __stopHoldTimer(el_key);
+ __deactivate(el_key);
+ __process(el_key, false);
+ if (!fixed) {
+ __unholdAll();
+ }
+ }
}
};
- var __unholdModifiers = function() {
- for (let code in __modifiers) {
- let el_key = __modifiers[code][0];
- if (__isHolded(el_key)) {
+ var __startHoldTimer = function(el_key) {
+ __stopHoldTimer(el_key);
+ let code = el_key.getAttribute("data-code");
+ __hold_timers[code] = setTimeout(function() {
+ // Помимо прямой функции, hold timer используется для детектирования факта
+ // нажатия в рамках одной сессии press/release, чтобы не отпустить сразу же
+ // зажатую или заблокированную клавишу. Поэтому таймер инициализируется всегда,
+ // но основную функцию выполняет только если у него есть атрибут data-allow-autohold.
+ if (el_key.hasAttribute("data-allow-autohold")) {
+ __deactivate(el_key);
+ __activate(el_key, "holded");
+ }
+ }, 500); // Check keypad.css for the animation
+ };
+
+ var __stopHoldTimer = function(el_key) {
+ let code = el_key.getAttribute("data-code");
+ if (!__hold_timers[code]) {
+ return false;
+ }
+ clearTimeout(__hold_timers[code]);
+ __hold_timers[code] = null;
+ return true;
+ };
+
+ var __unholdAll = function() {
+ for (let el_key of [].slice.call(__el_keypad.getElementsByClassName("key"))) {
+ __stopHoldTimer(el_key);
+ if (__isActive(el_key, "holded") && !__isActive(el_key, "locked")) { // Skip duplicating keys
__deactivate(el_key);
__process(el_key, false);
}
}
};
- var __isPressed = function(el_key) {
- let is_pressed = false;
+ var __isActive = function(el_key, cls=null) {
let el_keys = __resolveKeys(el_key);
for (el_key of el_keys) {
- is_pressed = (is_pressed || el_key.classList.contains("pressed"));
+ if (cls) {
+ if (el_key.classList.contains(cls)) {
+ return true;
+ }
+ } else if (
+ el_key.classList.contains("pressed")
+ || el_key.classList.contains("holded")
+ || el_key.classList.contains("locked")
+ ) {
+ return true;
+ }
}
- return is_pressed;
- };
-
- var __isHolded = function(el_key) {
- let is_holded = false;
- let el_keys = __resolveKeys(el_key);
- for (el_key of el_keys) {
- is_holded = (is_holded || el_key.classList.contains("holded"));
- }
- return is_holded;
- };
-
- var __isActive = function(el_key) {
- let is_active = false;
- let el_keys = __resolveKeys(el_key);
- for (el_key of el_keys) {
- is_active = (is_active || el_key.classList.contains("pressed") || el_key.classList.contains("holded"));
- }
- return is_active;
+ return false;
};
var __activate = function(el_key, cls) {
@@ -234,12 +271,13 @@ export function Keypad(__keys_parent, __sendKey, __apply_fixes) {
for (el_key of el_keys) {
el_key.classList.remove("pressed");
el_key.classList.remove("holded");
+ el_key.classList.remove("locked");
}
};
var __resolveKeys = function(el_key) {
let code = el_key.getAttribute("data-code");
- return __merged[code];
+ return __keys[code];
};
var __process = function(el_key, state) {
diff --git a/web/share/js/kvm/keyboard.js b/web/share/js/kvm/keyboard.js
index 92218f09..3c30a900 100644
--- a/web/share/js/kvm/keyboard.js
+++ b/web/share/js/kvm/keyboard.js
@@ -35,7 +35,7 @@ export function Keyboard(__recordWsEvent) {
var __keypad = null;
var __init__ = function() {
- __keypad = new Keypad("div#keyboard-window", __sendKey, true);
+ __keypad = new Keypad($("keyboard-window"), __sendKey, true);
$("hid-keyboard-led").title = "Keyboard free";
diff --git a/web/share/js/kvm/mouse.js b/web/share/js/kvm/mouse.js
index 73d59314..fd3c0c7f 100644
--- a/web/share/js/kvm/mouse.js
+++ b/web/share/js/kvm/mouse.js
@@ -55,7 +55,7 @@ export function Mouse(__getGeometry, __recordWsEvent) {
var __stream_hovered = false;
var __init__ = function() {
- __keypad = new Keypad("div#stream-mouse-buttons", __sendButton, false);
+ __keypad = new Keypad($("stream-mouse-buttons"), __sendButton, false);
$("hid-mouse-led").title = "Mouse free";
diff --git a/web/share/js/tools.js b/web/share/js/tools.js
index 3436aba1..5c68fdb2 100644
--- a/web/share/js/tools.js
+++ b/web/share/js/tools.js
@@ -164,7 +164,7 @@ export var tools = new function() {
if (prevent_default) {
ev.preventDefault();
}
- callback();
+ callback(ev);
};
},
"setOnUp": function(el, callback, prevent_default=true) {