Initial commit
This commit is contained in:
commit
968d09e362
88 changed files with 2323 additions and 0 deletions
20
contestops/assign-user.sh
Executable file
20
contestops/assign-user.sh
Executable file
|
|
@ -0,0 +1,20 @@
|
|||
#!/bin/bash
|
||||
|
||||
machinename="$1"
|
||||
username="$2"
|
||||
|
||||
machineusername=contestant
|
||||
|
||||
userline=$(grep "^$username;" contestants.csv)
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "User $username not found"
|
||||
exit 1
|
||||
fi
|
||||
fullname=$(echo "$userline" | cut "-d;" -f2)
|
||||
|
||||
# Set real name of machine user
|
||||
ssh -F local.ssh_config "$machinename" chfn --full-name "\"$fullname\"" $machineusername
|
||||
|
||||
# Install client certificate
|
||||
scp -F local.ssh_config "certs/$username.p12" "$machinename:/home/$machineusername/clientcert.p12"
|
||||
ssh -F local.ssh_config "$machinename" install-client-cert $machineusername
|
||||
25
contestops/backup-create.sh
Executable file
25
contestops/backup-create.sh
Executable file
|
|
@ -0,0 +1,25 @@
|
|||
#!/bin/bash
|
||||
|
||||
sleep_secs=120
|
||||
|
||||
do_backup() {
|
||||
for host in $(cat hostlist); do
|
||||
echo $host
|
||||
target=backups/$host/$(date --iso-8601=seconds)
|
||||
mkdir -p $target
|
||||
rsync -e "ssh -F local.ssh_config" --recursive --links --perms --times --verbose --prune-empty-dirs --exclude ".*" --exclude "/snap" --exclude "Screenshot from *" --max-size 200K $host:/home/contestant/ $target
|
||||
done
|
||||
}
|
||||
|
||||
if [ "$1" == timer ]; then
|
||||
while true; do
|
||||
do_backup
|
||||
echo
|
||||
echo "Finished, next backup in $sleep_secs seconds."
|
||||
echo
|
||||
echo
|
||||
sleep $sleep_secs
|
||||
done
|
||||
else
|
||||
do_backup
|
||||
fi
|
||||
9
contestops/config-hosts
Normal file
9
contestops/config-hosts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
127.0.0.1 localhost
|
||||
::1 localhost ip6-localhost ip6-loopback
|
||||
fe00::0 ip6-localnet
|
||||
ff00::0 ip6-mcastprefix
|
||||
ff02::1 ip6-allnodes
|
||||
ff02::2 ip6-allrouters
|
||||
|
||||
89.58.34.6 contest.soi.ch
|
||||
2a03:4000:64:8::1 contest.soi.ch
|
||||
48
contestops/config-nftables.conf
Normal file
48
contestops/config-nftables.conf
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
#!/usr/sbin/nft -f
|
||||
|
||||
flush ruleset
|
||||
|
||||
table inet filter {
|
||||
chain input {
|
||||
type filter hook input priority 0;
|
||||
|
||||
ct state invalid drop
|
||||
ct state { established, related } accept
|
||||
|
||||
# Accept loopback
|
||||
iif lo accept
|
||||
|
||||
# Accept ICMP
|
||||
ip protocol icmp accept
|
||||
ip6 nexthdr icmpv6 accept
|
||||
|
||||
# Accept incoming connections to these ports
|
||||
tcp dport { ssh } accept
|
||||
|
||||
reject
|
||||
}
|
||||
chain forward {
|
||||
type filter hook forward priority 0;
|
||||
reject
|
||||
}
|
||||
chain output {
|
||||
type filter hook output priority 0;
|
||||
|
||||
ct state invalid drop
|
||||
ct state { established, related } accept
|
||||
|
||||
# Accept loopback
|
||||
oif lo accept
|
||||
|
||||
# Accept outgoing connections to these addresses
|
||||
ip daddr { 89.58.34.6 } tcp dport { https } accept
|
||||
ip daddr { 89.58.34.6 } udp dport { ntp } accept
|
||||
ip6 daddr { 2a03:4000:64:8::1 } tcp dport { https } accept
|
||||
ip6 daddr { 2a03:4000:64:8::1 } udp dport { ntp } accept
|
||||
|
||||
# Accept any connections by root user
|
||||
#meta skuid root accept
|
||||
|
||||
reject
|
||||
}
|
||||
}
|
||||
22
contestops/configure-machines.sh
Executable file
22
contestops/configure-machines.sh
Executable file
|
|
@ -0,0 +1,22 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -ex
|
||||
|
||||
# Disable WiFi.
|
||||
parallel-ssh -x "-F local.ssh_config" -h hostlist nmcli radio wifi off
|
||||
|
||||
# Create hosts file so we don't need DNS.
|
||||
parallel-scp -x "-F local.ssh_config" -h hostlist ./config-hosts /etc/hosts
|
||||
|
||||
# Configure firewall.
|
||||
parallel-scp -x "-F local.ssh_config" -h hostlist ./config-nftables.conf /etc/nftables.conf
|
||||
parallel-ssh -x "-F local.ssh_config" -h hostlist systemctl enable nftables.service
|
||||
# For some unknown reason nft gets stuck the first time it is run.
|
||||
parallel-ssh -x "-F local.ssh_config" -h hostlist --par 30 systemctl start nftables.service
|
||||
|
||||
# Uncomment these lines if machines have 4K displays. This scales display to 2x.
|
||||
# parallel-scp -x "-F local.ssh_config" -h hostlist ./set-display-scale.py /usr/local/bin/set-display-scale.py
|
||||
# parallel-ssh -x "-F local.ssh_config" -h hostlist 'DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$(id -u contestant)/bus" runuser -u contestant -- python3 /usr/local/bin/set-display-scale.py'
|
||||
|
||||
# Configure contest lock screen.
|
||||
parallel-scp -x "-F local.ssh_config" -h hostlist ./contest-lock.json /etc/contest-lock.json
|
||||
5
contestops/contest-lock.json
Normal file
5
contestops/contest-lock.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"title": "SOI Finals 2024 · Day 1",
|
||||
"message": "",
|
||||
"startTime": "2024-01-01T10:00:00+01:00"
|
||||
}
|
||||
2
contestops/contestants.csv
Normal file
2
contestops/contestants.csv
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
stofl;Mouse Stofl
|
||||
binna1;Mouse Binna
|
||||
|
60
contestops/create-certs.sh
Executable file
60
contestops/create-certs.sh
Executable file
|
|
@ -0,0 +1,60 @@
|
|||
#!/usr/bin/env bash
|
||||
# install cfssl
|
||||
|
||||
set -e
|
||||
|
||||
usernames=$(cat contestants.csv | cut "-d;" -f1)
|
||||
|
||||
mkdir -p certs
|
||||
cd certs
|
||||
|
||||
cat <<EOF > ca.json
|
||||
{
|
||||
"CN": "SOI Contest Root CA",
|
||||
"key": {
|
||||
"algo": "rsa",
|
||||
"size": 2048
|
||||
}
|
||||
}
|
||||
|
||||
EOF
|
||||
|
||||
if [ ! -f ca.pem ]; then
|
||||
cfssl gencert -initca ca.json | cfssljson -bare ca
|
||||
fi
|
||||
|
||||
cat <<EOF >client-config.json
|
||||
{
|
||||
"signing": {
|
||||
"default": {
|
||||
"expiry": "438000h"
|
||||
},
|
||||
"profiles": {
|
||||
"client": {
|
||||
"usages": ["signing", "key encipherment", "digital signature", "client auth"],
|
||||
"expiry": "438000h"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EOF
|
||||
|
||||
for username in $usernames; do
|
||||
|
||||
cat <<EOF >client-csr-$username.json
|
||||
{
|
||||
"CN": "$username",
|
||||
"key": {
|
||||
"algo": "rsa",
|
||||
"size": 2048
|
||||
}
|
||||
}
|
||||
|
||||
EOF
|
||||
|
||||
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=client-config.json -profile=client client-csr-$username.json | cfssljson --bare $username-cert
|
||||
|
||||
openssl pkcs12 -export -in $username-cert.pem -inkey $username-cert-key.pem -out $username.p12 -passout pass:
|
||||
|
||||
done
|
||||
3
contestops/hostlist
Normal file
3
contestops/hostlist
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
contestant01
|
||||
contestant02
|
||||
contestant03
|
||||
202
contestops/local-epfl.ssh_config
Normal file
202
contestops/local-epfl.ssh_config
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
# ssh config for room INF3 at EPFL
|
||||
|
||||
Host contestant01
|
||||
HostName 128.178.158.101
|
||||
|
||||
Host contestant02
|
||||
HostName 128.178.158.102
|
||||
|
||||
Host contestant03
|
||||
HostName 128.178.158.103
|
||||
|
||||
Host contestant04
|
||||
HostName 128.178.158.104
|
||||
|
||||
Host contestant05
|
||||
HostName 128.178.158.105
|
||||
|
||||
Host contestant06
|
||||
HostName 128.178.158.106
|
||||
|
||||
Host contestant07
|
||||
HostName 128.178.158.107
|
||||
|
||||
Host contestant08
|
||||
HostName 128.178.158.108
|
||||
|
||||
Host contestant09
|
||||
HostName 128.178.158.109
|
||||
|
||||
Host contestant10
|
||||
HostName 128.178.158.110
|
||||
|
||||
Host contestant11
|
||||
HostName 128.178.158.111
|
||||
|
||||
Host contestant12
|
||||
HostName 128.178.158.112
|
||||
|
||||
Host contestant13
|
||||
HostName 128.178.158.113
|
||||
|
||||
Host contestant14
|
||||
HostName 128.178.158.114
|
||||
|
||||
Host contestant15
|
||||
HostName 128.178.158.115
|
||||
|
||||
Host contestant16
|
||||
HostName 128.178.158.116
|
||||
|
||||
Host contestant17
|
||||
HostName 128.178.158.117
|
||||
|
||||
Host contestant18
|
||||
HostName 128.178.158.118
|
||||
|
||||
Host contestant19
|
||||
HostName 128.178.158.119
|
||||
|
||||
Host contestant20
|
||||
HostName 128.178.158.120
|
||||
|
||||
Host contestant21
|
||||
HostName 128.178.158.121
|
||||
|
||||
Host contestant22
|
||||
HostName 128.178.158.122
|
||||
|
||||
Host contestant23
|
||||
HostName 128.178.158.123
|
||||
|
||||
Host contestant24
|
||||
HostName 128.178.158.124
|
||||
|
||||
Host contestant25
|
||||
HostName 128.178.158.125
|
||||
|
||||
Host contestant26
|
||||
HostName 128.178.158.126
|
||||
|
||||
Host contestant27
|
||||
HostName 128.178.158.127
|
||||
|
||||
Host contestant28
|
||||
HostName 128.178.158.128
|
||||
|
||||
Host contestant29
|
||||
HostName 128.178.158.129
|
||||
|
||||
Host contestant30
|
||||
HostName 128.178.158.130
|
||||
|
||||
Host contestant31
|
||||
HostName 128.178.158.131
|
||||
|
||||
Host contestant32
|
||||
HostName 128.178.158.132
|
||||
|
||||
Host contestant33
|
||||
HostName 128.178.158.133
|
||||
|
||||
Host contestant34
|
||||
HostName 128.178.158.134
|
||||
|
||||
Host contestant35
|
||||
HostName 128.178.158.135
|
||||
|
||||
Host contestant36
|
||||
HostName 128.178.158.136
|
||||
|
||||
Host contestant37
|
||||
HostName 128.178.158.137
|
||||
|
||||
Host contestant38
|
||||
HostName 128.178.158.138
|
||||
|
||||
Host contestant39
|
||||
HostName 128.178.158.139
|
||||
|
||||
Host contestant40
|
||||
HostName 128.178.158.140
|
||||
|
||||
Host contestant41
|
||||
HostName 128.178.158.141
|
||||
|
||||
Host contestant42
|
||||
HostName 128.178.158.142
|
||||
|
||||
Host contestant43
|
||||
HostName 128.178.158.143
|
||||
|
||||
Host contestant44
|
||||
HostName 128.178.158.144
|
||||
|
||||
Host contestant45
|
||||
HostName 128.178.158.145
|
||||
|
||||
Host contestant46
|
||||
HostName 128.178.158.146
|
||||
|
||||
Host contestant47
|
||||
HostName 128.178.158.147
|
||||
|
||||
Host contestant48
|
||||
HostName 128.178.158.148
|
||||
|
||||
Host contestant49
|
||||
HostName 128.178.158.149
|
||||
|
||||
Host contestant50
|
||||
HostName 128.178.158.150
|
||||
|
||||
Host contestant51
|
||||
HostName 128.178.158.151
|
||||
|
||||
Host contestant52
|
||||
HostName 128.178.158.152
|
||||
|
||||
Host contestant53
|
||||
HostName 128.178.158.153
|
||||
|
||||
Host contestant54
|
||||
HostName 128.178.158.154
|
||||
|
||||
Host contestant55
|
||||
HostName 128.178.158.155
|
||||
|
||||
Host contestant56
|
||||
HostName 128.178.158.156
|
||||
|
||||
Host contestant57
|
||||
HostName 128.178.158.157
|
||||
|
||||
Host contestant58
|
||||
HostName 128.178.158.158
|
||||
|
||||
Host contestant59
|
||||
HostName 128.178.158.159
|
||||
|
||||
Host contestant60
|
||||
HostName 128.178.158.160
|
||||
|
||||
Host contestant61
|
||||
HostName 128.178.158.161
|
||||
|
||||
Host contestant62
|
||||
HostName 128.178.158.162
|
||||
|
||||
Host contestant63
|
||||
HostName 128.178.158.163
|
||||
|
||||
Host contestant64
|
||||
HostName 128.178.158.164
|
||||
|
||||
Host vm
|
||||
HostName localhost
|
||||
Port 2222
|
||||
|
||||
Host *
|
||||
User root
|
||||
UserKnownHostsFile ./local.known_hosts
|
||||
HashKnownHosts no
|
||||
17
contestops/local.ssh_config
Normal file
17
contestops/local.ssh_config
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
Host contestant01
|
||||
HostName 10.42.0.101
|
||||
|
||||
Host contestant02
|
||||
HostName 10.42.0.102
|
||||
|
||||
Host contestant03
|
||||
HostName 10.42.0.103
|
||||
|
||||
Host vm
|
||||
HostName localhost
|
||||
Port 2222
|
||||
|
||||
Host *
|
||||
User root
|
||||
UserKnownHostsFile ./local.known_hosts
|
||||
HashKnownHosts no
|
||||
184
contestops/readme.md
Normal file
184
contestops/readme.md
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
# Contest ops
|
||||
|
||||
Here are instructions and various scripts and files for running contests.
|
||||
|
||||
The setup consists of a machine for each contestant, a machine running the grader, and an admin machine.
|
||||
All these should be connected through a network, preferably wired.
|
||||
The grader can be a machine accessible over the internet or in the local network.
|
||||
|
||||
## Grader setup
|
||||
|
||||
Install an ntp server on the grader machine.
|
||||
This ensures that the contestant machine clocks are synchronized with the grader clock.
|
||||
If a firewall is enabled, you may need to open the NTP port.
|
||||
|
||||
```bash
|
||||
sudo apt install ntpsec
|
||||
```
|
||||
|
||||
Configure the grader to accept client certificates.
|
||||
The CA certificate (`certs/ca.pem`) is generated as part of the admin setup.
|
||||
|
||||
## Contestant machine setup
|
||||
|
||||
Obtain the contestant ISO, or build it yourself.
|
||||
|
||||
Flash the ISO to an USB stick.
|
||||
All data on the stick will be lost.
|
||||
For example, with the Gnome Disks utility, select the USB stick, open the menu on the right of the title bar, and click "Restore Disk Image...".
|
||||
|
||||
Boot the contestant machine from the USB stick.
|
||||
Insert the stick and power on the machine.
|
||||
Then repeatedly press a key to enter the boot menu (which key depends on the model, e.g. F12).
|
||||
The boot menu may be password protected on machines in computer rooms; in that case you need to know the password.
|
||||
The OS is loaded into RAM during boot, so you can remove the stick once the boot is finished and boot the next machine.
|
||||
|
||||
## Network setup
|
||||
|
||||
If there is not already an existing network, you need to set it up yourself.
|
||||
Connect all contestant machines and the admin machine to a network switch with LAN cables.
|
||||
If you use multiple switches, don't forget to also link the switches together.
|
||||
|
||||
If the grader must be accessed over the internet, you can connect the admin machine to WiFi or USB tethering with a phone.
|
||||
You can then share the internet with the local network.
|
||||
|
||||
If you have Gnome, go to Network settings, click on the gear on the Ethernet connection, go to IPv4 tab, and select "Shared to other computers".
|
||||
|
||||
If you have docker installed, this doesn't work yet, because docker blocks routing.
|
||||
You can fix it by running the following commands.
|
||||
|
||||
```bash
|
||||
sudo iptables -I DOCKER-USER -i en+ -j ACCEPT
|
||||
sudo iptables -I DOCKER-USER -o en+ -j ACCEPT
|
||||
```
|
||||
|
||||
## Admin setup
|
||||
|
||||
This guide assumes that the admin machine is running Debian, Ubuntu or similar.
|
||||
|
||||
Invent a password for root on the machines.
|
||||
Create a password hash for it with the following command.
|
||||
Put the hash in the `contest_root_password` variable in `os/config/config.toml`.
|
||||
This must be done before building the ISO.
|
||||
|
||||
```bash
|
||||
sudo apt install whois
|
||||
mkpasswd
|
||||
```
|
||||
|
||||
Install parallel-ssh.
|
||||
|
||||
```bash
|
||||
sudo apt install pssh
|
||||
```
|
||||
|
||||
Edit `contestants.csv` and fill in the username and real name of each contestant.
|
||||
|
||||
Run the script to create a CA and client certificates.
|
||||
|
||||
```bash
|
||||
sudo apt install golang-cfssl
|
||||
./create-certs.sh
|
||||
```
|
||||
|
||||
Edit `local.ssh_config` and create an entry with hostname and IP address for each contestant machine.
|
||||
You can get the IP address by running `ip addr` in a terminal on the contestant machine.
|
||||
|
||||
Edit `hostlist` and add the hostnames of all contestant machines.
|
||||
|
||||
Get ssh host keys.
|
||||
After rebooting machines, delete `local.known_hosts` and run this command again.
|
||||
|
||||
```bash
|
||||
parallel-ssh -x "-F local.ssh_config" -h hostlist -O StrictHostKeyChecking=accept-new true
|
||||
```
|
||||
|
||||
Test time synchronization.
|
||||
|
||||
```bash
|
||||
parallel-ssh -x "-F local.ssh_config" -h hostlist -i date
|
||||
```
|
||||
|
||||
Edit `config-hosts` and `config-nftables.conf` to fill in the correct IP addresses for the grader.
|
||||
You can look these up with `host contest.soi.ch`.
|
||||
|
||||
Edit `contest-lock.json` to fill in the title and start time of the contest.
|
||||
|
||||
Apply the configuration to machines.
|
||||
If the script gets stuck, press Ctrl+C and run it again.
|
||||
|
||||
```bash
|
||||
./configure-machines.sh
|
||||
```
|
||||
|
||||
Assign users to machines.
|
||||
|
||||
```bash
|
||||
./assign-user.sh contestant01 stofl
|
||||
./assign-user.sh contestant02 binna1
|
||||
```
|
||||
|
||||
Start periodic backup of contestant machines.
|
||||
|
||||
```bash
|
||||
./backup-create.sh timer
|
||||
```
|
||||
|
||||
## Restore machine from backup
|
||||
|
||||
Because machines run from RAM, they will lose all files after rebooting.
|
||||
Therefore, backups are especially important.
|
||||
|
||||
To restore a backup to a spare machine, use the following commands.
|
||||
Prepare in advance by keeping the user to machine assignment nearby for reference, and
|
||||
replacing `contestant03` in the commands below with the spare machine hostname.
|
||||
|
||||
```bash
|
||||
./assign-user.sh contestant03 <username>
|
||||
rsync -e "ssh -F local.ssh_config" -av --chown contestant:contestant backups/contestantxx/xxxx/ contestant03:/home/contestant/
|
||||
```
|
||||
|
||||
## Contest lock screen
|
||||
|
||||
The contest lock screen is a gnome extension which can lock the screen and show a countdown until the contest starts.
|
||||
The screen is unlocked when the contest starts.
|
||||
The lock screen also displays the user name and a title.
|
||||
It is configured in the file `/etc/contest-lock.json`.
|
||||
It watches this file, and when it changes the new configuration is instantly applied.
|
||||
|
||||
If there is an error in the config file, it will continue to use the old config and print a message.
|
||||
To see the logs, run this on a contestant machine:
|
||||
|
||||
```bash
|
||||
journalctl -f -o cat /usr/bin/gnome-shell
|
||||
```
|
||||
|
||||
An additional text can be shown with the `message` field. It can contain newlines (`\n`).
|
||||
|
||||
|
||||
In case there is a problem with the contest lock screen and you can't fix it, the backup solution is to turn off `AutomaticLoginEnable` and set a password instead, that you announce when the contest starts.
|
||||
|
||||
```bash
|
||||
parallel-ssh -x "-F local.ssh_config" -h hostlist 'chpasswd <<< contestant:stofl'
|
||||
```
|
||||
|
||||
**Development notes**
|
||||
|
||||
Links:
|
||||
- https://www.codeproject.com/Articles/5271677/How-to-Create-A-GNOME-Extension
|
||||
- https://gjs.guide/
|
||||
|
||||
Regular lock screen (contest-lock is based on this):
|
||||
- https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/screenShield.js
|
||||
- https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/unlockDialog.js
|
||||
|
||||
Developer commands:
|
||||
- Open the gnome-shell developer tools: Press Alt+F2, enter `lg`.
|
||||
|
||||
## Problems and solutions
|
||||
|
||||
Here are solutions to recurring problems.
|
||||
|
||||
**User indicator does not appear.**
|
||||
Fixed by adding the gnome shell version from `gnome-shell --version` to the list of supported versions: `shell-version` in `os/layers/contestant/includes.chroot/usr/share/gnome-shell/extensions/user-indicator@soi.ch/metadata.json`.
|
||||
The same applies for the contest-lock extension.
|
||||
37
contestops/set-display-scale.py
Normal file
37
contestops/set-display-scale.py
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
# This script sets the display scale factor to 2, for use on 4K displays where everything is too small without scaling.
|
||||
|
||||
import dbus
|
||||
|
||||
new_scale = 2.0
|
||||
|
||||
# https://gitlab.gnome.org/GNOME/mutter/-/blob/main/data/dbus-interfaces/org.gnome.Mutter.DisplayConfig.xml
|
||||
bus_name = "org.gnome.Mutter.DisplayConfig"
|
||||
object_path = "/org/gnome/Mutter/DisplayConfig"
|
||||
|
||||
session_bus = dbus.SessionBus()
|
||||
display_config_object = session_bus.get_object(bus_name, object_path)
|
||||
display_config_intf = dbus.Interface(display_config_object, dbus_interface=bus_name)
|
||||
|
||||
serial, physical_monitors, logical_monitors, properties = display_config_intf.GetCurrentState()
|
||||
|
||||
current_mode_id = {}
|
||||
for ((connector, _, _, _), modes, monitor_properties) in physical_monitors:
|
||||
for (mode_id, width, height, rate, preferred_scale, supported_scales, mode_properties) in modes:
|
||||
if mode_properties.get("is-current", False):
|
||||
current_mode_id[connector] = mode_id
|
||||
|
||||
scaled_logical_monitors = [
|
||||
(layout_x, layout_y, new_scale, transform, primary, [
|
||||
(connector, current_mode_id[connector], {}) for (connector, _, _, _) in monitors
|
||||
])
|
||||
for (layout_x, layout_y, scale, transform, primary, monitors, monitor_properties) in logical_monitors
|
||||
]
|
||||
|
||||
apply_properties = {}
|
||||
if "layout-mode" in properties and properties.get("supports-changing-layout-mode", False):
|
||||
apply_properties["layout-mode"] = properties["layout-mode"]
|
||||
|
||||
method = 1 # temporary
|
||||
display_config_intf.ApplyMonitorsConfig(serial, method, scaled_logical_monitors, apply_properties)
|
||||
Loading…
Add table
Add a link
Reference in a new issue