Skip to content

Commit

Permalink
Choose monitor with GTK dialog
Browse files Browse the repository at this point in the history
  • Loading branch information
ben-grande committed Jun 27, 2024
1 parent 04aa2d3 commit fc9cf59
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 33 deletions.
2 changes: 1 addition & 1 deletion doc/qubes-video-companion.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ The project emphasizes correctness and security all the while also sporting supe
OPTIONS
=======
resolution
The video resolution to stream and receive video in. The format is [WIDTHxHEIGHTxFPS], meaning resolution is optional. If you set the environment variable "QVC_MONITOR" in the target, that monitor is going to be preferred and if not found, will fallback to the primary monitor. Example: "1920x1080x60"
The video resolution to stream and receive video in. The format is [WIDTHxHEIGHTxFPS], meaning resolution is optional. Example: "1920x1080x60"


video_source
Expand Down
22 changes: 1 addition & 21 deletions qubes-rpc/services/qvc.ScreenShare
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,7 @@

set -eu

## DISPLAY variable used by: xrandr, zenity
export DISPLAY=:0

true "${XDG_RUNTIME_DIR:="/run/user/$(id -u)"}"
true "${DBUS_SESSION_BUS_ADDRESS:="unix:path=${XDG_RUNTIME_DIR}/bus"}"
monitors="$(xrandr --listactivemonitors \
| awk '/^ [0-9]+: \+/ { print "FALSE", $4, $3 }')"
monitor_count="$(echo "${monitors}" | wc -l)"
monitor_longest_line="$(echo "${monitors}" | wc -L)"
dialog_height="$((monitor_count*50+60))"
dialog_width="$((monitor_longest_line*10))"
if test "${monitor_count}" -gt 1; then
# shellcheck disable=SC2086
QVC_MONITOR="$(zenity --list --radiolist \
--height="${dialog_height}" --width="${dialog_width}" \
--column "ID" --column "Name" --column "Resolution" \
--title "Screen share" \
--text "Select monitor to present to qube ${QREXEC_REMOTE_DOMAIN}" \
${monitors})"
fi
true "${QVC_MONITOR:=}"

export XDG_RUNTIME_DIR DBUS_SESSION_BUS_ADDRESS QVC_MONITOR
export DISPLAY=:0 XDG_RUNTIME_DIR DBUS_SESSION_BUS_ADDRESS
exec python3 -- /usr/share/qubes-video-companion/sender/screenshare.py
5 changes: 5 additions & 0 deletions receiver/receiver.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ def read_video_parameters() -> (int, int, int):
raise AssertionError("bug")

untrusted_input = os.read(0, input_size)

if len(untrusted_input) == 0:
print("Operation canceled by sender", file=sys.stderr)
sys.exit(1)

if len(untrusted_input) != input_size:
raise RuntimeError("wrong number of bytes read")
untrusted_width, untrusted_height, untrusted_fps = sstruct.unpack(
Expand Down
99 changes: 88 additions & 11 deletions sender/screenshare.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

# Copyright (C) 2021 Elliot Killick <[email protected]>
# Copyright (C) 2021 Demi Marie Obenour <[email protected]>
# Copyright (C) 2024 Benjamin Grande M. S. <[email protected]>
# Licensed under the MIT License. See LICENSE file for details.

"""Screen sharing video source module"""
Expand All @@ -10,10 +11,9 @@
# pylint: disable=wrong-import-position

import gi
import os

gi.require_version("Gdk", "3.0")
from gi.repository import Gdk
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, Gdk, GdkPixbuf
from service import Service
from typing import List, Tuple

Expand All @@ -22,6 +22,7 @@ class ScreenShare(Service):
"""Screen sharing video souce class"""

def __init__(self) -> None:
self.selected_monitor_index = None
self.main(self)

def video_source(self) -> str:
Expand All @@ -30,16 +31,92 @@ def video_source(self) -> str:
def icon(self) -> str:
return "video-display"

def parameters(self) -> Tuple[int, int, int]:
def monitor_dialog(self) -> None:
display = Gdk.Display().get_default()
monitor_count = display.get_n_monitors()
monitor_wanted = os.environ["QVC_MONITOR"]
## If wanted monitor is not found, use the primary monitor (0).
monitor_index = 0
for m in range(monitor_count):
if display.get_monitor(m).get_model() == monitor_wanted:
monitor_index = m
break

if monitor_count == 1:
self.selected_monitor_index = 0
return

combobox = Gtk.ComboBoxText()
monitor_screenshots = []
for monitor_num in range(monitor_count):
monitor_name = display.get_monitor(monitor_num).get_model()
monitor = display.get_monitor(monitor_num)
monitor_geometry = monitor.get_geometry()
monitor_width = monitor_geometry.width
monitor_height = monitor_geometry.height
monitor_x = monitor_geometry.x
monitor_y = monitor_geometry.y

combobox.append_text(f"{monitor_name}: "
f"{monitor_width}x{monitor_height} "
f"{monitor_x}+{monitor_y}")

pixbuf = Gdk.pixbuf_get_from_window(
display.get_default_screen().get_root_window(),
monitor_x,
monitor_y,
monitor_width,
monitor_height)
pixbuf = pixbuf.scale_simple(
min(960, pixbuf.get_width()),
min(540, pixbuf.get_height()),
GdkPixbuf.InterpType.BILINEAR)
monitor_screenshots.append(pixbuf)

window = Gtk.Window(title="Qubes Screen Share")
window.connect("destroy", Gtk.main_quit)
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
window.add(vbox)

## TODO: add 'remote_domain' to differentiate calls.
label = Gtk.Label(label="Select a monitor:")
vbox.pack_start(label, False, False, 0)
hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6)
vbox.pack_start(hbox, False, False, 0)

## Select first monitor when opening the dialog.
combobox.set_active(0)

hbox.pack_start(combobox, True, True, 0)
ok_button = Gtk.Button(label="OK")
cancel_button = Gtk.Button(label="Cancel")
hbox.pack_end(ok_button, False, False, 0)
hbox.pack_end(cancel_button, False, False, 0)

def on_ok_button_clicked(_):
self.selected_monitor_index = combobox.get_active()
window.close()

def on_cancel_button_clicked(_):
window.close()

ok_button.connect("clicked", on_ok_button_clicked)
cancel_button.connect("clicked", on_cancel_button_clicked)

image = Gtk.Image()
vbox.pack_start(image, True, True, 0)

def on_combobox_changed(combobox):
monitor_index = combobox.get_active()
pixbuf = monitor_screenshots[monitor_index]
image.set_from_pixbuf(pixbuf)

combobox.connect("changed", on_combobox_changed)
## Show the first monitor screenshot when opening the dialog.
on_combobox_changed(combobox)

window.show_all()
Gtk.main()

def parameters(self) -> Tuple[int, int, int]:
display = Gdk.Display().get_default()
self.monitor_dialog()
monitor_index = self.selected_monitor_index
if monitor_index is None:
raise ValueError("Monitor index was not set")
geometry = display.get_monitor(monitor_index).get_geometry()
screen = Gdk.Screen().get_default()
kwargs = {
Expand Down

0 comments on commit fc9cf59

Please sign in to comment.