Skip to content

Commit

Permalink
Allow the receiver to request a preferred resolution
Browse files Browse the repository at this point in the history
The sender tries to find a resolution that is close to the preferred one
of the receiver.

Fixes: QubesOS/qubes-issues#8424
  • Loading branch information
DemiMarie committed Mar 29, 2024
1 parent 254bda6 commit 5462c6d
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 18 deletions.
2 changes: 1 addition & 1 deletion qubes-rpc/services/qvc.Webcam
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
# Copyright (C) 2021 Demi Marie Obenour <[email protected]>
# Licensed under the MIT License. See LICENSE file for details.
export DISPLAY=:0
exec python3 -- /usr/share/qubes-video-companion/sender/webcam.py
exec python3 -- /usr/share/qubes-video-companion/sender/webcam.py ${1:+"$1"}
42 changes: 31 additions & 11 deletions receiver/qubes-video-companion
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,39 @@
# Licensed under the MIT License. See LICENSE file for details.

[ "$DEBUG" == 1 ] && set -x
set -u -e

set -E # Enable function inheritance of traps
trap exit ERR
unset GETOPT_COMPATIBLE
name=${0##*/}

usage() {
echo "Usage: qubes-video-companion webcam|screenshare [destination qube]"
} >&2
printf '%s: Usage: qubes-video-companion [--resolution=WIDTHxHEIGHTxFPS] [--] webcam|screenshare [destination qube]\n' "$name"
exit "$1"
}

if [ "$#" -gt 2 ] && [ "$#" -lt 1 ]; then
usage
exit 1
fi
resolution=
opts=$(getopt "--name=$name" --longoptions=resolution:,help -- r: "$@") || exit
eval "set -- $opts"
while :; do
case $1 in
-r|--resolution)
if [[ -z "$2" ]]; then
printf '%s: Empty resolution argument\n' "$name"
usage 0
fi >&2
resolution=${2//@/+}
resolution=${resolution//x/+}
shift 2
;;
--help) usage 0;;
--) shift; break;;
*) exit 1;; # cannot happen
esac
done

if [ "$#" -gt 2 ] || [ "$#" -lt 1 ]; then usage 1 >&2; fi

video_source="$1" qube=${2-'@default'}

Expand All @@ -27,18 +48,17 @@ case "$video_source" in
qvc_service="qvc.ScreenShare"
;;
*)
usage
exit 1
usage 1
;;
esac

exit_clean() {
exit_clean () {
exit_code="$?"

/usr/share/qubes-video-companion/receiver/destroy.sh
sudo rm -f "$qvc_lock_file"

if [ "$video_source" == "webcam" ] && [ "$exit_code" == "141" ]; then
if [ "$video_source" = "webcam" ] && [ "$exit_code" = "141" ]; then
echo "The webcam device is in use! Please stop any instance of Qubes Video Companion running on another qube." >&2
exit 1
fi
Expand All @@ -57,4 +77,4 @@ fi

/usr/share/qubes-video-companion/receiver/setup.sh
# Filter standard error escape characters for safe printing to the terminal from the video sender
qrexec-client-vm --filter-escape-chars-stderr -- "$qube" "$qvc_service" /usr/share/qubes-video-companion/receiver/receiver.py
qrexec-client-vm --filter-escape-chars-stderr -- "$qube" "$qvc_service+$resolution" /usr/share/qubes-video-companion/receiver/receiver.py
3 changes: 0 additions & 3 deletions sender/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,8 @@ def start_transmission(self) -> None:
def main(cls, self) -> NoReturn:
"""Program entry point"""

import argparse
import qubesdb # pylint: disable=import-error

argparse.ArgumentParser().parse_args()

target_domain = qubesdb.QubesDB().read("/name")
if target_domain is None:
# dom0 doesn't have a /name value in its QubesDB
Expand Down
53 changes: 50 additions & 3 deletions sender/webcam.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,43 @@
class Webcam(Service):
"""Webcam video source class"""

def __init__(self):
self.main(self)
untrusted_requested_width: int
untrusted_requested_height: int
untrusted_requested_fps: int

def __init__(self, *, untrusted_arg: str):
if untrusted_arg:
untrusted_arg_bytes = untrusted_arg.encode('ascii', 'strict')
def parse_int(untrusted_decimal: bytes) -> int:
if not ((1 <= len(untrusted_decimal) <= 4) and
untrusted_decimal.isdigit() and
untrusted_decimal[0] != b"0"):
print("Invalid argument " + untrusted_arg + ": bad number",
file=sys.stderr)
sys.exit(1)
return int(untrusted_decimal, 10)
if len(untrusted_arg_bytes) > 14:
# qrexec has already sanitized the argument to some degree,
# so this is safe
print("Invalid argument " + untrusted_arg +
": too long (limit 14 bytes)", file=sys.stderr)
sys.exit(1)
arg_list = untrusted_arg_bytes.split(b"+", 4)
if len(arg_list) != 3:
print("Invalid argument " + untrusted_arg +
": wrong number of integers (expected 3)",
file=sys.stderr)
sys.exit(1)
( self.untrusted_requested_width
, self.untrusted_requested_height
, self.untrusted_requested_fps
) = map(parse_int, arg_list)
else:
self.untrusted_requested_width = 0
self.untrusted_requested_height = 0
self.untrusted_requested_fps = 0

Service.main(self)

def video_source(self) -> str:
return "webcam"
Expand Down Expand Up @@ -60,6 +95,11 @@ def parameters(self):
else:
print("Cannot parse output %r of v4l2ctl" % i, file=sys.stderr)
formats.sort(key=lambda x: x[0] * x[1] * x[2], reverse=True)
if self.untrusted_requested_fps:
formats.sort(key=lambda x:
(x[0] - self.untrusted_requested_width) ** 2 +
(x[1] - self.untrusted_requested_height) ** 2 +
(x[2] - self.untrusted_requested_fps) ** 2)
return formats[0]

def pipeline(self, width: int, height: int, fps: int, **kwargs):
Expand Down Expand Up @@ -108,4 +148,11 @@ def pipeline(self, width: int, height: int, fps: int, **kwargs):


if __name__ == "__main__":
webcam = Webcam()
_untrusted_arg = ""
if len(sys.argv) == 2:
_untrusted_arg = sys.argv[1]
elif len(sys.argv) != 1:
print("Must have 0 or 1 argument, not " + str(len(sys.argv)),
file=sys.stderr)
sys.exit(1)
webcam = Webcam(untrusted_arg=_untrusted_arg)

0 comments on commit 5462c6d

Please sign in to comment.