summaryrefslogtreecommitdiff
path: root/kvmd
diff options
context:
space:
mode:
Diffstat (limited to 'kvmd')
-rw-r--r--kvmd/apps/__init__.py6
-rw-r--r--kvmd/apps/kvmd/server.py2
-rw-r--r--kvmd/apps/kvmd/streamer.py98
-rw-r--r--kvmd/validators/kvm.py13
4 files changed, 95 insertions, 24 deletions
diff --git a/kvmd/apps/__init__.py b/kvmd/apps/__init__.py
index b3bc98fc..a4264faf 100644
--- a/kvmd/apps/__init__.py
+++ b/kvmd/apps/__init__.py
@@ -56,6 +56,7 @@ from ..validators.basic import valid_number
from ..validators.basic import valid_int_f1
from ..validators.basic import valid_float_f0
from ..validators.basic import valid_float_f01
+from ..validators.basic import valid_string_list
from ..validators.auth import valid_user
from ..validators.auth import valid_users_list
@@ -74,6 +75,7 @@ from ..validators.net import valid_ssl_ciphers
from ..validators.kvm import valid_stream_quality
from ..validators.kvm import valid_stream_fps
+from ..validators.kvm import valid_stream_resolution
from ..validators.kvm import valid_hid_key
from ..validators.kvm import valid_hid_mouse_move
@@ -259,9 +261,11 @@ def _get_config_scheme() -> Dict:
"shutdown_delay": Option(10.0, type=valid_float_f01),
"state_poll": Option(1.0, type=valid_float_f01),
- "quality": Option(80, type=valid_stream_quality),
+ "quality": Option(80, type=(lambda arg: (valid_stream_quality(arg) if arg else 0))), # 0 for disabled feature
"desired_fps": Option(30, type=valid_stream_fps),
"max_fps": Option(60, type=valid_stream_fps),
+ "resolution": Option("", type=(lambda arg: (valid_stream_resolution(arg) if arg else ""))),
+ "available_resolutions": Option([], type=(lambda arg: valid_string_list(arg, subval=valid_stream_resolution))),
"host": Option("localhost", type=valid_ip_or_host),
"port": Option(0, type=valid_port),
diff --git a/kvmd/apps/kvmd/server.py b/kvmd/apps/kvmd/server.py
index 9e4c1122..51589a16 100644
--- a/kvmd/apps/kvmd/server.py
+++ b/kvmd/apps/kvmd/server.py
@@ -56,6 +56,7 @@ from ...validators.basic import valid_bool
from ...validators.kvm import valid_stream_quality
from ...validators.kvm import valid_stream_fps
+from ...validators.kvm import valid_stream_resolution
from ... import aiotools
from ... import aioproc
@@ -193,6 +194,7 @@ class KvmdServer(HttpServer): # pylint: disable=too-many-arguments,too-many-ins
for (name, validator) in [
("quality", valid_stream_quality),
("desired_fps", valid_stream_fps),
+ ("resolution", valid_stream_resolution),
]:
if (value := request.query.get(name)):
value = validator(value)
diff --git a/kvmd/apps/kvmd/streamer.py b/kvmd/apps/kvmd/streamer.py
index 5b239572..59277db8 100644
--- a/kvmd/apps/kvmd/streamer.py
+++ b/kvmd/apps/kvmd/streamer.py
@@ -54,6 +54,69 @@ class StreamerSnapshot:
data: bytes
+class _StreamerParams:
+ __DESIRED_FPS = "desired_fps"
+ __MAX_FPS = "max_fps"
+
+ __QUALITY = "quality"
+
+ __RESOLUTION = "resolution"
+ __AVAILABLE_RESOLUTIONS = "available_resolutions"
+
+ def __init__(
+ self,
+ desired_fps: int,
+ max_fps: int,
+ quality: int,
+ resolution: str,
+ available_resolutions: List[str],
+ ) -> None:
+
+ self.__has_quality = bool(quality)
+ self.__has_resolution = bool(resolution)
+
+ self.__params: Dict = {
+ self.__DESIRED_FPS: desired_fps,
+ **({self.__QUALITY: quality} if self.__has_quality else {}),
+ **({self.__RESOLUTION: resolution} if self.__has_resolution else {}),
+ }
+
+ self.__limits: Dict = {
+ self.__MAX_FPS: max_fps,
+ **({self.__AVAILABLE_RESOLUTIONS: available_resolutions} if self.__has_resolution else {}),
+ }
+
+ def get_features(self) -> Dict:
+ return {
+ self.__QUALITY: self.__has_quality,
+ self.__RESOLUTION: self.__has_resolution,
+ }
+
+ def get_limits(self) -> Dict:
+ limits = dict(self.__limits)
+ if self.__has_resolution:
+ limits[self.__AVAILABLE_RESOLUTIONS] = list(limits[self.__AVAILABLE_RESOLUTIONS])
+ return limits
+
+ def get_params(self) -> Dict:
+ return dict(self.__params)
+
+ def set_params(self, params: Dict) -> None:
+ new_params = dict(self.__params)
+
+ if self.__DESIRED_FPS in params:
+ new_params[self.__DESIRED_FPS] = min(max(params[self.__DESIRED_FPS], 0), self.__limits[self.__MAX_FPS])
+
+ if self.__QUALITY in params and self.__has_quality:
+ new_params[self.__QUALITY] = min(max(params[self.__QUALITY], 1), 100)
+
+ if self.__RESOLUTION in params and self.__has_resolution:
+ if params[self.__RESOLUTION] in self.__limits[self.__AVAILABLE_RESOLUTIONS]:
+ new_params[self.__RESOLUTION] = params[self.__RESOLUTION]
+
+ self.__params = new_params
+
+
class Streamer: # pylint: disable=too-many-instance-attributes
def __init__( # pylint: disable=too-many-arguments,too-many-locals
self,
@@ -66,10 +129,6 @@ class Streamer: # pylint: disable=too-many-instance-attributes
shutdown_delay: float,
state_poll: float,
- quality: int,
- desired_fps: int,
- max_fps: int,
-
host: str,
port: int,
unix_path: str,
@@ -78,6 +137,8 @@ class Streamer: # pylint: disable=too-many-instance-attributes
process_name_prefix: str,
cmd: List[str],
+
+ **params_kwargs: Any,
) -> None:
self.__cap_pin = (gpio.set_output(cap_pin) if cap_pin >= 0 else -1)
@@ -89,12 +150,6 @@ class Streamer: # pylint: disable=too-many-instance-attributes
self.__shutdown_delay = shutdown_delay
self.__state_poll = state_poll
- self.__params = {
- "quality": quality,
- "desired_fps": desired_fps,
- }
- self.__max_fps = max_fps
-
assert port or unix_path
self.__host = host
self.__port = port
@@ -105,6 +160,8 @@ class Streamer: # pylint: disable=too-many-instance-attributes
self.__cmd = cmd
+ self.__params = _StreamerParams(**params_kwargs)
+
self.__stop_task: Optional[asyncio.Task] = None
self.__stop_wip = False
@@ -183,16 +240,10 @@ class Streamer: # pylint: disable=too-many-instance-attributes
def set_params(self, params: Dict) -> None:
assert not self.__streamer_task
- self.__params = {
- key: min(max(params.get(key, self.__params[key]), a), b)
- for (key, a, b) in [
- ("quality", 0, 100),
- ("desired_fps", 0, self.__max_fps),
- ]
- }
+ return self.__params.set_params(params)
def get_params(self) -> Dict:
- return dict(self.__params)
+ return self.__params.get_params()
# =====
@@ -216,10 +267,11 @@ class Streamer: # pylint: disable=too-many-instance-attributes
del snapshot["data"]
return {
- "limits": {"max_fps": self.__max_fps},
- "params": self.__params,
+ "limits": self.__params.get_limits(),
+ "params": self.__params.get_params(),
"snapshot": {"saved": snapshot},
"state": state,
+ "features": self.__params.get_features(),
}
async def poll_state(self) -> AsyncGenerator[Dict, None]:
@@ -370,11 +422,11 @@ class Streamer: # pylint: disable=too-many-instance-attributes
except asyncio.CancelledError:
break
- except Exception as err:
+ except Exception:
if self.__streamer_proc:
logger.exception("Unexpected streamer error: pid=%d", self.__streamer_proc.pid)
else:
- logger.exception("Can't start streamer: %s", err)
+ logger.exception("Can't start streamer")
await self.__kill_streamer_proc()
await asyncio.sleep(1)
@@ -386,7 +438,7 @@ class Streamer: # pylint: disable=too-many-instance-attributes
port=self.__port,
unix=self.__unix_path,
process_name_prefix=self.__process_name_prefix,
- **self.__params,
+ **self.__params.get_params(),
)
for part in self.__cmd
]
diff --git a/kvmd/validators/kvm.py b/kvmd/validators/kvm.py
index cf63b97e..b32a785e 100644
--- a/kvmd/validators/kvm.py
+++ b/kvmd/validators/kvm.py
@@ -24,8 +24,10 @@ from typing import Any
from ..keyboard.mappings import KEYMAP
+from . import raise_error
from . import check_string_in_list
+from .basic import valid_stripped_string_not_empty
from .basic import valid_number
from .os import valid_printable_filename
@@ -56,6 +58,17 @@ def valid_stream_fps(arg: Any) -> int:
return int(valid_number(arg, min=0, max=120, name="stream FPS"))
+def valid_stream_resolution(arg: Any) -> str:
+ name = "stream resolution"
+ arg = valid_stripped_string_not_empty(arg, name)
+ parts = arg.split("x")
+ if len(parts) != 2:
+ raise_error(arg, name)
+ width = int(valid_number(parts[0], min=1, name=f"{name} (width)"))
+ height = int(valid_number(parts[1], min=1, name=f"{name} (height)"))
+ return f"{width}x{height}"
+
+
# =====
def valid_hid_key(arg: Any) -> str:
return check_string_in_list(arg, "HID key", KEYMAP, lower=False)