Skip to content

Commit

Permalink
Merge pull request #199 from yfyf/hdmi-hotplug
Browse files Browse the repository at this point in the history
Display monitor hotplugging v2
  • Loading branch information
guyonvarch authored Nov 19, 2024
2 parents 0526bad + a8770c3 commit 338bf1e
Show file tree
Hide file tree
Showing 6 changed files with 474 additions and 26 deletions.
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

0 comments on commit 338bf1e

Please sign in to comment.