Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Display monitor hotplugging v2 #199

Merged
merged 17 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 34 additions & 14 deletions application.nix
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,19 @@ rec {
(import ./application/overlays version)
];

module = { config, lib, pkgs, ... }: {
module = { config, lib, pkgs, ... }:
let
selectDisplay = pkgs.writeShellApplication {
name = "select-display";
runtimeInputs = with pkgs; [
gnugrep
gawk
xorg.xrandr
bash
];
text = (builtins.readFile ./application/select-display.sh);
};
in {

imports = [
./application/playos-status.nix
Expand Down Expand Up @@ -67,6 +79,9 @@ rec {
xset s noblank
xset -dpms

# Select best display to output to
${selectDisplay}/bin/select-display || true

# Localization for xsession
if [ -f /var/lib/gui-localization/lang ]; then
export LANG=$(cat /var/lib/gui-localization/lang)
Expand All @@ -75,19 +90,6 @@ rec {
setxkbmap $(cat /var/lib/gui-localization/keymap) || true
fi

# Set preferred screen resolution
scaling_pref=$(cat /var/lib/gui-localization/screen-scaling 2>/dev/null || echo "default")
case "$scaling_pref" in
"default" | "full-hd")
xrandr --size 1920x1080;;
"native")
# Nothing to do, let system decide.
;;
*)
echo "Unknown scaling preference '$scaling_pref'. Ignoring."
;;
esac

# Enable Qt WebEngine Developer Tools (https://doc.qt.io/qt-6/qtwebengine-debugging.html)
export QTWEBENGINE_REMOTE_DEBUGGING="127.0.0.1:3355"

Expand Down Expand Up @@ -145,6 +147,24 @@ rec {
wantedBy = [ "multi-user.target" ];
};

# Monitor hotplugging
services.udev.extraRules = ''
ACTION=="change", SUBSYSTEM=="drm", RUN+="${pkgs.systemd}/bin/systemctl start select-display.service"
'';
systemd.services."select-display" = {
description = "Select best display to output to";
serviceConfig = {
Type = "oneshot";
ExecStart = "${selectDisplay}/bin/select-display";
User = "play";
};
environment = {
XAUTHORITY = "${config.users.users.play.home}/.Xauthority";
DISPLAY = ":0";
};
after = [ "graphical.target" ];
};

# Audio
sound.enable = true;
hardware.pulseaudio = {
Expand Down
48 changes: 48 additions & 0 deletions application/select-display.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
set -euo pipefail

SCALING_PREF=$(cat /var/lib/gui-localization/screen-scaling 2>/dev/null || echo "default")
readonly SCALING_PREF

CONNECTED_OUTPUTS=$(xrandr | grep ' connected' | awk '{ print $1 }')
readonly CONNECTED_OUTPUTS

echo -e "Connected outputs:\n$CONNECTED_OUTPUTS\n"

scaling_pref_params=""

echo "Using scaling preference '$SCALING_PREF'"

case "$SCALING_PREF" in
"default" | "full-hd")
scaling_pref_params=(--mode 1920x1080)
;;
"native")
scaling_pref_params=(--auto)
;;
*)
scaling_pref_params=(--auto)
;;
esac

if [ -z "$CONNECTED_OUTPUTS" ]; then

echo "Error: no connected outputs found. Not applying any changes."

else

first_functional_output=""
for output in $CONNECTED_OUTPUTS; do
if [ -z "$first_functional_output" ]; then
if xrandr --output "$output" --primary "${scaling_pref_params[@]}"; then
first_functional_output=$output
echo "Configured display $output as primary"
else
echo "Failed to configure display $output"
fi
else
xrandr --output "$output" \
--same-as "$first_functional_output" \
"${scaling_pref_params[@]}" || echo "Failed to configure display $output"
fi
done
fi
20 changes: 9 additions & 11 deletions kiosk/kiosk_browser/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,19 @@ def start(kiosk_url, settings_url, toggle_settings_key, fullscreen = True):
mainWidget = main_widget.MainWidget(
kiosk_url = parseUrl(kiosk_url),
settings_url = parseUrl(settings_url),
toggle_settings_key = QKeySequence(toggle_settings_key)
toggle_settings_key = QKeySequence(toggle_settings_key),
fullscreen = fullscreen
)

mainWidget.setContextMenuPolicy(Qt.ContextMenuPolicy.NoContextMenu)

screen_size = app.primaryScreen().size()

if fullscreen:
# Without a Window Manager, showFullScreen does not work under X,
# so set the window size to the primary screen size.
mainWidget.resize(screen_size)
mainWidget.showFullScreen()
else:
mainWidget.resize(QSize(round(screen_size.width() / 2), round(screen_size.height() / 2)))
mainWidget.show()
# Note: Qt primary screen != xrandr primary screen
# Qt will set primary when screen becomes visible, while on
# xrandr it only changes when `--primary` is explicitly specified
app.primaryScreenChanged.connect(mainWidget.handle_screen_change,
type=Qt.ConnectionType.QueuedConnection)
primary = app.primaryScreen()
mainWidget.handle_screen_change(primary)

# Quit application gracefully when receiving SIGINT or SIGTERM
# This is important to trigger flushing of in-memory DOM storage to disk
Expand Down
31 changes: 30 additions & 1 deletion kiosk/kiosk_browser/main_widget.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from PyQt6 import QtWidgets, QtCore, QtGui
import time
import logging

from kiosk_browser import browser_widget, captive_portal, dialogable_widget, proxy as proxy_module

Expand All @@ -11,8 +12,12 @@ class MainWidget(QtWidgets.QWidget):
- Use proxy configured in Connman.
"""

def __init__(self, kiosk_url: str, settings_url: str, toggle_settings_key: str):
def __init__(self, kiosk_url: str, settings_url: str,
toggle_settings_key: str, fullscreen: bool):
super(MainWidget, self).__init__()
# Display
self._primary_screen_con = None
self._fullscreen = fullscreen

# Proxy
proxy = proxy_module.Proxy()
Expand Down Expand Up @@ -102,3 +107,27 @@ def eventFilter(self, source, event):
self._menu_press_since = None

return super(MainWidget, self).eventFilter(source, event)

def handle_screen_change(self, new_primary):
logging.info(f"Primary screen changed to {new_primary.name()}")
if self._primary_screen_con is not None:
QtCore.QObject.disconnect(self._primary_screen_con)

self._primary_screen_con = \
new_primary.geometryChanged.connect(self._resize_to_screen)

# Precautionary sleep to allow Chromium to update screens
time.sleep(1)
self._resize_to_screen(new_primary.geometry())

def _resize_to_screen(self, new_geom):
screen_size = new_geom.size()
logging.info(f"Resizing widget based on new screen size: {screen_size}")
if self._fullscreen:
# Without a Window Manager, showFullScreen does not work under X,
# so set the window size to the primary screen size.
self.resize(screen_size)
self.showFullScreen()
else:
self.resize(QtCore.QSize(round(screen_size.width() / 2), round(screen_size.height() / 2)))
self.show()
Loading
Loading