summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--kvmd/apps/kvmd/server.py18
-rw-r--r--kvmd/apps/kvmd/ugpio.py60
-rw-r--r--testenv/v2-hdmi-rpi4.override.yaml4
-rw-r--r--web/kvm/index.html7
-rw-r--r--web/kvm/navbar-macro.pug2
-rw-r--r--web/kvm/window-about.pug5
-rw-r--r--web/share/js/kvm/gpio.js40
-rw-r--r--web/share/js/wm.js10
9 files changed, 107 insertions, 41 deletions
diff --git a/Makefile b/Makefile
index e8467361..04ab76ad 100644
--- a/Makefile
+++ b/Makefile
@@ -173,7 +173,7 @@ release:
make tox
make clean
make push
- make bump
+ make bump V=$(V)
make push
make clean
diff --git a/kvmd/apps/kvmd/server.py b/kvmd/apps/kvmd/server.py
index 4003bf2b..1b3e2363 100644
--- a/kvmd/apps/kvmd/server.py
+++ b/kvmd/apps/kvmd/server.py
@@ -250,12 +250,13 @@ class KvmdServer(HttpServer): # pylint: disable=too-many-arguments,too-many-ins
await client.ws.prepare(request)
await self.__register_ws_client(client)
try:
- await self.__broadcast_event("gpio_model_state", await self.__user_gpio.get_model())
+ await self.__send_event(client.ws, "gpio_model_state", await self.__user_gpio.get_model())
await asyncio.gather(*[
- self.__broadcast_event(component.event_type, await component.get_state())
+ self.__send_event(client.ws, component.event_type, await component.get_state())
for component in self.__components
if component.get_state
])
+ await self.__send_event(client.ws, "loop", {})
async for msg in client.ws:
if msg.type == aiohttp.web.WSMsgType.TEXT:
try:
@@ -278,7 +279,7 @@ class KvmdServer(HttpServer): # pylint: disable=too-many-arguments,too-many-ins
@exposed_ws("ping")
async def __ws_ping_handler(self, ws: aiohttp.web.WebSocketResponse, _: Dict) -> None:
- await ws.send_str(json.dumps({"event_type": "pong", "event": {}}))
+ await self.__send_event(ws, "pong", {})
# ===== SYSTEM STUFF
@@ -363,13 +364,16 @@ class KvmdServer(HttpServer): # pylint: disable=too-many-arguments,too-many-ins
except Exception:
logger.exception("Cleanup error on %s", component.name)
+ async def __send_event(self, ws: aiohttp.web.WebSocketResponse, event_type: str, event: Optional[Dict]) -> None:
+ await ws.send_str(json.dumps({
+ "event_type": event_type,
+ "event": event,
+ }))
+
async def __broadcast_event(self, event_type: str, event: Optional[Dict]) -> None:
if self.__ws_clients:
await asyncio.gather(*[
- client.ws.send_str(json.dumps({
- "event_type": event_type,
- "event": event,
- }))
+ self.__send_event(client.ws, event_type, event)
for client in list(self.__ws_clients)
if (
not client.ws.closed
diff --git a/kvmd/apps/kvmd/ugpio.py b/kvmd/apps/kvmd/ugpio.py
index cb328115..d4e9d0af 100644
--- a/kvmd/apps/kvmd/ugpio.py
+++ b/kvmd/apps/kvmd/ugpio.py
@@ -322,29 +322,51 @@ class UserGpio:
items: List[Dict] = []
for item in map(str.strip, row):
if item.startswith("#") or len(item) == 0:
- items.append({
- "type": "label",
- "text": item[1:].strip(),
- })
+ items.append(self.__make_view_label(item))
else:
- parts = list(map(str.strip, item.split("|", 1)))
+ parts = list(map(str.strip, item.split("|", 2)))
if parts:
- channel: str = parts[0]
- param: Optional[str] = (parts[1] if len(parts) > 1 else None)
- if channel in self.__inputs:
- items.append({
- "type": UserGpioModes.INPUT,
- "channel": channel,
- "color": (param if param in ["green", "yellow", "red"] else "green"),
- })
- elif channel in self.__outputs:
- items.append({
- "type": UserGpioModes.OUTPUT,
- "channel": parts[0],
- "text": (param if param is not None else "Click"),
- })
+ if parts[0] in self.__inputs:
+ items.append(self.__make_view_input(parts))
+ elif parts[0] in self.__outputs:
+ items.append(self.__make_view_output(parts))
table.append(items)
+
return {
"header": self.__view["header"],
"table": table,
}
+
+ def __make_view_label(self, item: str) -> Dict:
+ assert item.startswith("#")
+ return {
+ "type": "label",
+ "text": item[1:].strip(),
+ }
+
+ def __make_view_input(self, parts: List[str]) -> Dict:
+ assert len(parts) >= 1
+ color = (parts[1] if len(parts) > 1 else None)
+ if color not in ["green", "yellow", "red"]:
+ color = "green"
+ return {
+ "type": UserGpioModes.INPUT,
+ "channel": parts[0],
+ "color": color,
+ }
+
+ def __make_view_output(self, parts: List[str]) -> Dict:
+ assert len(parts) >= 1
+ confirm = False
+ text = "Click"
+ if len(parts) == 2:
+ text = parts[1]
+ elif len(parts) == 3:
+ confirm = (parts[1] == "confirm")
+ text = parts[2]
+ return {
+ "type": UserGpioModes.OUTPUT,
+ "channel": parts[0],
+ "confirm": confirm,
+ "text": text,
+ }
diff --git a/testenv/v2-hdmi-rpi4.override.yaml b/testenv/v2-hdmi-rpi4.override.yaml
index a8f361b3..2d2bf678 100644
--- a/testenv/v2-hdmi-rpi4.override.yaml
+++ b/testenv/v2-hdmi-rpi4.override.yaml
@@ -84,11 +84,11 @@ kvmd:
- ["#Generic GPIO leds"]
- []
- ["#Test 1:", led1, button1]
- - ["#Test 2:", led2, button2]
+ - ["#Test 2:", led2, button2|confirm|Click]
- []
- ["#HID Relays /dev/hidraw0"]
- []
- - ["#Relay #1:", "relay1|Boop 0.1"]
+ - ["#Relay #1:", "relay1|confirm|Boop 0.1"]
- ["#Relay #2:", "relay2|Boop 2.0"]
vnc:
diff --git a/web/kvm/index.html b/web/kvm/index.html
index 97777402..39e1cfb8 100644
--- a/web/kvm/index.html
+++ b/web/kvm/index.html
@@ -333,7 +333,7 @@
</li>
<li class="right"><a class="menu-button" href="#"><img class="led-gray" data-dont-hide-menu id="hid-recorder-led" src="/share/svg/led-gear.svg">Macro &#8628;</a>
<div class="menu" data-dont-hide-menu>
- <div class="text"><b>Record and play keyboard &amp; mouse actions<br></b><sub>For security reasons, the record will not saved on Pi-KVM</sub></div>
+ <div class="text"><b>Record and play keyboard &amp; mouse actions<br></b><sub>For security reasons, the record will not be saved on the Pi-KVM</sub></div>
<hr>
<div class="buttons buttons-row">
<button class="row25" disabled data-force-hide-menu id="hid-recorder-record">&bull; Rec</button>
@@ -1344,6 +1344,7 @@
<ul>
<li>Aleksei Brusianskii</li>
<li>Alucard</li>
+ <li>Andrzej V</li>
<li>Anton Kovalenko</li>
<li>Aron Perelman</li>
<li>Arthur Woimbée</li>
@@ -1362,6 +1363,7 @@
<li>David Howell</li>
<li>Denis Yatsenko</li>
<li>Dmitry Shilov</li>
+ <li>Eric Phenix</li>
<li>Fergus McKay</li>
<li>Foamy</li>
<li>Ge Men</li>
@@ -1386,6 +1388,7 @@
<li>Mark Robinson</li>
<li>Martin Gasser</li>
<li>Mauricio Allende</li>
+ <li>Mehmet Aydoğdu</li>
<li>Michael Kovacs</li>
<li>Michael Lynch</li>
<li>Morgan Helton</li>
@@ -1401,10 +1404,12 @@
<li>Sergey Lukjanov</li>
<li>Steve Ovens</li>
<li>Steven Richter</li>
+ <li>Tejun Heo</li>
<li>Truman Kilen</li>
<li>Walter_Ego</li>
<li>YURI LEE</li>
<li>zgen</li>
+ <li>Zsombor Vari</li>
</ul>
</div>
</div>
diff --git a/web/kvm/navbar-macro.pug b/web/kvm/navbar-macro.pug
index 9df95e4f..2a17f4c4 100644
--- a/web/kvm/navbar-macro.pug
+++ b/web/kvm/navbar-macro.pug
@@ -5,7 +5,7 @@ li(class="right")
div(data-dont-hide-menu class="menu")
div(class="text")
b Record and play keyboard &amp; mouse actions#[br]
- sub For security reasons, the record will not saved on Pi-KVM
+ sub For security reasons, the record will not be saved on the Pi-KVM
hr
div(class="buttons buttons-row")
button(disabled data-force-hide-menu id="hid-recorder-record" class="row25") &bull; Rec
diff --git a/web/kvm/window-about.pug b/web/kvm/window-about.pug
index df8437f1..c3836791 100644
--- a/web/kvm/window-about.pug
+++ b/web/kvm/window-about.pug
@@ -40,6 +40,7 @@ mixin about_tab(name, title, checked=false)
ul
li Aleksei Brusianskii
li Alucard
+ li Andrzej V
li Anton Kovalenko
li Aron Perelman
li Arthur Woimbée
@@ -58,6 +59,7 @@ mixin about_tab(name, title, checked=false)
li David Howell
li Denis Yatsenko
li Dmitry Shilov
+ li Eric Phenix
li Fergus McKay
li Foamy
li Ge Men
@@ -82,6 +84,7 @@ mixin about_tab(name, title, checked=false)
li Mark Robinson
li Martin Gasser
li Mauricio Allende
+ li Mehmet Aydoğdu
li Michael Kovacs
li Michael Lynch
li Morgan Helton
@@ -97,10 +100,12 @@ mixin about_tab(name, title, checked=false)
li Sergey Lukjanov
li Steve Ovens
li Steven Richter
+ li Tejun Heo
li Truman Kilen
li Walter_Ego
li YURI LEE
li zgen
+ li Zsombor Vari
br
p(class="text")
| Full documentation, source code, hardware schematics and legal information
diff --git a/web/share/js/kvm/gpio.js b/web/share/js/kvm/gpio.js
index e7b3c9e1..a67224b7 100644
--- a/web/share/js/kvm/gpio.js
+++ b/web/share/js/kvm/gpio.js
@@ -96,11 +96,11 @@ export function Gpio() {
for (let channel in model.scheme.outputs) {
let el = $(`gpio-switch-${channel}`);
if (el) {
- tools.setOnClick(el, () => __switchChannel(channel));
+ tools.setOnClick(el, () => __switchChannel(el));
}
el = $(`gpio-button-${channel}`);
if (el) {
- tools.setOnClick(el, () => __pulseChannel(channel));
+ tools.setOnClick(el, () => __pulseChannel(el));
}
}
@@ -120,7 +120,8 @@ export function Gpio() {
if (item.scheme["switch"]) {
controls.push(`
<td><div class="switch-box">
- <input disabled type="checkbox" id="gpio-switch-${item.channel}" class="gpio-switch" />
+ <input disabled type="checkbox" id="gpio-switch-${item.channel}" class="gpio-switch"
+ data-channel="${item.channel}" data-confirm="${Number(item.confirm)}" />
<label for="gpio-switch-${item.channel}">
<span class="switch-inner"></span>
<span class="switch"></span>
@@ -129,7 +130,10 @@ export function Gpio() {
`);
}
if (item.scheme.pulse.delay) {
- controls.push(`<td><button disabled id="gpio-button-${item.channel}" class="gpio-button">${item.text}</button></td>`);
+ controls.push(`
+ <td><button disabled id="gpio-button-${item.channel}" class="gpio-button"
+ data-channel="${item.channel}" data-confirm="${Number(item.confirm)}">${item.text}</button></td>
+ `);
}
return `<table><tr>${controls.join("<td>&nbsp;&nbsp;&nbsp;</td>")}</tr></table>`;
} else {
@@ -148,13 +152,33 @@ export function Gpio() {
}
};
- var __switchChannel = function(channel) {
+ var __switchChannel = function(el) {
+ let channel = el.getAttribute("data-channel");
+ let confirm = parseInt(el.getAttribute("data-confirm"));
let to = ($(`gpio-switch-${channel}`).checked ? "1" : "0");
- __sendPost(`/api/gpio/switch?channel=${channel}&state=${to}`);
+ let act = () => __sendPost(`/api/gpio/switch?channel=${channel}&state=${to}`);
+ if (confirm) {
+ wm.confirm("Are you sure to act this switch?").then(function(ok) {
+ if (ok) {
+ act();
+ } else {
+ self.setState(__state); // Switch back
+ }
+ });
+ } else {
+ act();
+ }
};
- var __pulseChannel = function(channel) {
- __sendPost(`/api/gpio/pulse?channel=${channel}`);
+ var __pulseChannel = function(el) {
+ let channel = el.getAttribute("data-channel");
+ let confirm = parseInt(el.getAttribute("data-confirm"));
+ let act = () => __sendPost(`/api/gpio/pulse?channel=${channel}`);
+ if (confirm) {
+ wm.confirm("Are you sure to click this button?").then(function(ok) { if (ok) act(); });
+ } else {
+ act();
+ }
};
var __sendPost = function(url) {
diff --git a/web/share/js/wm.js b/web/share/js/wm.js
index f4a0dbee..fb4b30a1 100644
--- a/web/share/js/wm.js
+++ b/web/share/js/wm.js
@@ -84,6 +84,8 @@ function __WindowManager() {
self.confirm = (...args) => __modalDialog("Question", args.join(" "), true, true);
var __modalDialog = function(header, text, ok, cancel) {
+ let el_active_menu = (document.activeElement && document.activeElement.closest(".menu"));
+
let el_modal = document.createElement("div");
el_modal.className = "modal";
el_modal.style.visibility = "visible";
@@ -117,7 +119,11 @@ function __WindowManager() {
if (index !== -1) {
__windows.splice(index, 1);
}
- __activateLastWindow(el_modal);
+ if (el_active_menu && el_active_menu.style.visibility === "visible") {
+ el_active_menu.focus();
+ } else {
+ __activateLastWindow(el_modal);
+ }
resolve(retval);
}
@@ -288,7 +294,7 @@ function __WindowManager() {
};
var __globalMouseButtonHandler = function(event) {
- if (!event.target.matches(".menu-button")) {
+ if (!event.target.matches(".menu-button") && !event.target.closest(".modal")) {
for (let el_item = event.target; el_item && el_item !== document; el_item = el_item.parentNode) {
if (el_item.hasAttribute("data-force-hide-menu")) {
break;