#! /bin/bash # Copyright (c) 2012 - 2025 nerdopolis (or n3rdopolis) # # This file is part of RebeccaBlackOS. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . #This script is for configuring multiple seats in Weston. It matches the devices devpath, and the devices serial ID, and sets the ENV{WL_SEAT} and ENV{ID_SEAT} values, or the devices path ID and sets the ENV{ID_SEAT} value in a the udev file /etc/udev/rules.d/72-physical-and-logical-seats.rules #This makes it easier for users to configure multiple pointers and keyboard focuses, as well as multiple sessions. #Require root privlages if [[ $UID != 0 ]] then zenity --info --text="Must be run as root." --title="Multipointer and Seat Configuration" 2>/dev/null exit fi #Allow Exclamation points to be used in commands by turning off the history set +H #Specify the maximum number of seats. This is a more for usability, for specifying how many seats are listed in the zenity dialog. There can be more, but if there are too many, it become a hastle for the user MaxNumberOfSeats=10 #Set the HOME directory, glycin/gtk needs it to be the SUDO_USER's for bwrap to work export HOME=$SUDO_HOME #get the current session ID and seat id export XDG_SESSION_ID=$(busctl get-property org.freedesktop.login1 /org/freedesktop/login1/session/auto org.freedesktop.login1.Session Id | awk -F \" '{print $2}') export XDG_SEAT=$(busctl get-property org.freedesktop.login1 /org/freedesktop/login1/session/auto org.freedesktop.login1.Session Seat | awk -F \" '{print $2}') #Dont run zenity as root if [[ ! -z $SUDO_USER ]] then ZENITYCMD="runuser -u "$SUDO_USER" -m -- zenity" else ZENITYCMD="zenity" fi #Function that prepares the list of seats for the dropdown menus #It takes no arguments function PrepareSeatList { #Generate a list of logical seats for the Zenity dialogs for selection unset LogicalSeatList LogicalSeatList="default" for (( SeatIterator=1; SeatIterator /dev/null } #Configure the seats of a selected device, it takes one argument, the array index element number of the device to configure function ConfigureDevice { CurrentDeviceNumber=$1 unset NewPhysicalSeatNumber unset NewLogicalSeatNumber if [[ ${DeviceInputTypeFlags[$CurrentDeviceNumber]} == 1 ]] then NewSeatSelection=$($ZENITYCMD --forms --add-combo "Physical Seat" --combo-values="$PhysicalSeatList" --add-combo "Logical Seat" --combo-values="$LogicalSeatList" --text="Multipointer and Seat Configuration Select a seat for ${DeviceTypes[$CurrentDeviceNumber]}: ${DeviceDisplayNames[$CurrentDeviceNumber]} Connected to: ${DeviceConnections[$CurrentDeviceNumber]} Current Physical Seat: ${DeviceCurrentPhysicalSeatIDs[$CurrentDeviceNumber]} Current Logical Seat: ${DeviceCurrentLogicalSeatIDs[$CurrentDeviceNumber]}" --separator="@" --title "Multipointer and Seat Configuration" 2>/dev/null) ZenityResult=$? else NewSeatSelection=$($ZENITYCMD --forms --add-combo "Physical Seat" --combo-values="$PhysicalSeatList" --text="Multipointer and Seat Configuration Select a seat for ${DeviceTypes[$CurrentDeviceNumber]}: ${DeviceDisplayNames[$CurrentDeviceNumber]} Connected to: ${DeviceConnections[$CurrentDeviceNumber]} Current Physical Seat: ${DeviceCurrentPhysicalSeatIDs[$CurrentDeviceNumber]}" --separator="@" --title "Multipointer and Seat Configuration" 2>/dev/null) ZenityResult=$? fi if [[ $ZenityResult != 0 ]] then return 1 fi IFS="@" NewSeatSelection=($NewSeatSelection) unset IFS NewPhysicalSeatNumber=(${NewSeatSelection[0]}) NewLogicalSeatNumber=(${NewSeatSelection[1]}) #only change the seat id in udev if the user specified to do so if [[ ! -z ${NewSeatSelection[0]} || ! -z ${NewSeatSelection[1]} ]] then #If only the physical seat is specified if [[ -z $NewLogicalSeatNumber ]] then NewLogicalSeatNumber="${DeviceCurrentLogicalSeatIDs[$CurrentDeviceNumber]}" fi #If only the logical seat is specified if [[ -z $NewPhysicalSeatNumber ]] then NewPhysicalSeatNumber="${DeviceCurrentPhysicalSeatIDs[$CurrentDeviceNumber]}" fi #Don't reconfigure if the configured seats are the same values as the current ones if [[ ${DeviceCurrentPhysicalSeatIDs[$CurrentDeviceNumber]} != $NewPhysicalSeatNumber || ${DeviceNewLogicalSeatIDs[$CurrentDeviceNumber]} != $NewLogicalSeatNumber ]] then DeviceNewPhysicalSeatIDs[$CurrentDeviceNumber]=$NewPhysicalSeatNumber DeviceNewLogicalSeatIDs[$CurrentDeviceNumber]=$NewLogicalSeatNumber DeviceNewSeatConfigChangeds[$CurrentDeviceNumber]=1 else #Account for the device being set to different seats, then the user setting them back DeviceNewPhysicalSeatIDs[$CurrentDeviceNumber]=${DeviceCurrentPhysicalSeatIDs[$CurrentDeviceNumber]} DeviceNewLogicalSeatIDs[$CurrentDeviceNumber]=${DeviceNewLogicalSeatIDs[$CurrentDeviceNumber]} DeviceNewSeatConfigChangeds[$CurrentDeviceNumber]=0 fi fi } #Function that writes the new changes to the config files function CommitConfigFile { #Create the config file touch /etc/udev/rules.d/72-physical-and-logical-seats.rules #Go through each probed input device and prompt for a the seata to add the device to for (( CurrentDeviceNumber=1; CurrentDeviceNumber<=NumberOfDevices; CurrentDeviceNumber++ )) do #only change the seat id in udev if the user specified to do so if [[ ${DeviceNewSeatConfigChangeds[$CurrentDeviceNumber]} == 1 ]] then NewPhysicalSeatNumber=${DeviceNewPhysicalSeatIDs[$CurrentDeviceNumber]} NewLogicalSeatNumber=${DeviceNewLogicalSeatIDs[$CurrentDeviceNumber]} cat /etc/udev/rules.d/72-physical-and-logical-seats.rules > /tmp/72-physical-and-logical-seats.rules.work if [[ ${DeviceInputTypeFlags[$CurrentDeviceNumber]} == 1 ]] then if [[ ! -z ${DeviceNames[$CurrentDeviceNumber]} && ! -z ${DeviceConnections[$CurrentDeviceNumber]} ]] then awk "!/\"${DeviceNames[$CurrentDeviceNumber]//\//\\/}\"/ || !/\"${DeviceConnections[$CurrentDeviceNumber]//\//\\/}\"/" /tmp/72-physical-and-logical-seats.rules.work > /etc/udev/rules.d/72-physical-and-logical-seats.rules else if [[ ! -z ${DeviceNames[$CurrentDeviceNumber]} ]] then awk "!/${DeviceNames[$CurrentDeviceNumber]}//\//\\/}//" /tmp/72-physical-and-logical-seats.rules.work > /etc/udev/rules.d/72-physical-and-logical-seats.rules fi if [[ ! -z ${DeviceConnections[$CurrentDeviceNumber]} ]] then awk "!/${DeviceConnections[$CurrentDeviceNumber]//\//\\/}/" /tmp/72-physical-and-logical-seats.rules.work > /etc/udev/rules.d/72-physical-and-logical-seats.rules fi fi echo "ENV{ID_SERIAL}==\"${DeviceNames[$CurrentDeviceNumber]}\", ENV{ID_PATH}==\"${DeviceConnections[$CurrentDeviceNumber]}\", ENV{ID_SEAT}=\"$NewPhysicalSeatNumber\", ENV{WL_SEAT}=\"$NewLogicalSeatNumber\"" >> /etc/udev/rules.d/72-physical-and-logical-seats.rules else if [[ ! -z ${DeviceConnections[$CurrentDeviceNumber]} ]] then awk "!/\"\*${DeviceConnections[$CurrentDeviceNumber]//\//\\/}\"/" /tmp/72-physical-and-logical-seats.rules.work > /etc/udev/rules.d/72-physical-and-logical-seats.rules fi echo "TAG==\"seat\", ENV{ID_FOR_SEAT}==\"*${DeviceConnections[$CurrentDeviceNumber]}\", ENV{ID_SEAT}=\"$NewPhysicalSeatNumber\"" >> /etc/udev/rules.d/72-physical-and-logical-seats.rules fi rm /tmp/72-physical-and-logical-seats.rules.work fi done } #Function that sends the signal to udev and to the waylandloginmanagers to now detect the config changes that have been made function SignalCompletion { if [[ -e /run/waylandloginmanager ]] then $ZENITYCMD --info --text "Seat Configuration for all devices is now complete. Press OK to apply all the changes. This will switch user to the loginmanager display. This is to signal the display server to detect the new changes, and the waylandloginmanager to detect new seats. Your sessions will NOT be terminated, but they will need to be switched back into with Switch User" --no-wrap --title="Multipointer and Seat Configuration" 2>/dev/null else $ZENITYCMD --info --text "Seat Configuration for all devices is now complete. You may need to restart for the changes to take effect" 2>/dev/null fi #Reload the new configuration into udev udevadm control --reload-rules #Notify each input device about the change with udev, instead of doing a full trigger on all devices on the system for (( Itr=1; Itr<5; Itr++ )) do udevadm trigger --subsystem-match=input udevadm trigger --subsystem-match=drm udevadm trigger --subsystem-match=graphics udevadm trigger --subsystem-match=sound sleep 1 done if [[ ! -e /run/waylandloginmanager ]] then return fi #Force the server to pickup the changes waylandloginmanager --sendcommand DetectSeats sleep 1 RunningSeats=($(loginctl --no-legend list-seats 2>/dev/null | sort)) for RunningSeat in "${RunningSeats[@]}" do if [[ $RunningSeat == $XDG_SEAT ]] then waylandloginmanager --sendcommand Switch $RunningSeat else waylandloginmanager --sendcommand Change $RunningSeat fi done } #Function that shows more detailed help function HelpDialog { echo " INTRODUCTION ============ What is a seat? --------------- A seat is a combination of devices on a computer that belong to one user. Most computers have one seat each. However this wizard allows multiple seats to be configured with proper hardware. What is a Physical Seat? ----------------------- A physical seat is a group of hardware, needing a different video card each to work, along with input devices, such as a keyboard or mouse. These devices belong to different sessions, allowing each user to have independent control over their own session each. If such a configuration was set up in a manor that the cables and computer were well hidden such a setup could appear to the unwitting eye as two (or more) computers, where all seats are really being run on one. A USB graphics card is usually suitable for the multiple graphics card requirement, as long as the driver is supported. The default physical seat is 'seat0' What is a Logical Seat? ----------------------- A logical seat is a group of hardware within a physical seat that are shared within a session, to where, when supported by a given desktop environment, allows there to be multiple pointers on one session, along with their keyboards and mice. This type of configuration allows for better collaboration, with two users sharing a session. It is also possible to have physical seats, with multiple logical seats, to where each independent session also has multiple pointers. For multiple logical seats (assuming one physical seat) there is no need for multiple graphics devices. Weston supports multiple logical seats the best. The default logical seat is 'default' ABOUT THIS UTILITY ================== This utility simplifies the configuration of the seats. Typically one could edit the configuration by editing udev config files manually. This utility assists in creating and editing all seat config, and storing it in the file /etc/udev/rules.d/72-physical-and-logical-seats.rules Devices, and their configured seat values are stored by the name that the device presents itself as (if availible) and the connection. This utility handles the configuration for input devices and for video and sound cards Input Devices can be assignged to physical seats, and logical seats, identified in the config by their ID_SERIAL and ID_PATH properties Output devices (like video and sound cards) can only be assigned to physical seats, as assigning them to a logical seat has no impact on how multipointer works. USING THIS UTILITY ================== The utility will present a list of devices. Select the desired devices, then associate them with the desired seat. The devices are sorted by the order they were connected. Newest devices are on top. The device plugin time is queried the same way the other attriubtes are, and it is relative to the boot time. No actions are needed to ensure that the device plugin order is determined by this utility correctly. Leaving a field blank leaves the device assigned to the current seat info. For example, if a device is on seat0/default and the logical seat is set to seat1 the device's physical seat will remain on seat0 as the logical seat is set to seat1. This utility stores the device seat association with these following attributes: 1. The device's internal name, usually containing the make and model. 2. The port they are plugged into. Devices will only associate with the proper seats if plugged in the same way. "|$ZENITYCMD --title="Multipointer and Seat Configuration" --text-info --font=mono --width=700 --height=600 2>/dev/null } #Function for the main configuration wizard, it takes no arguments function MainConfig { while [ 1 ] do ChangedDeviceCount=0 SelectedDevice=$(DeviceSelection) Result=$? #Exit if selected if [[ $Result != 0 || $SelectedDevice == -2 ]] then $ZENITYCMD --question --no-wrap --title="Multipointer and Seat Configuration" --text "Are you sure you want to quit this utility without saving changes?" 2>/dev/null Result=$? if [[ $Result == 0 ]] then break else continue fi fi if [[ $SelectedDevice == "" ]] then AdditionalPromptText="No device selected, assuming save and close."$'\n' SelectedDevice=-1 else AdditionalPromptText="" fi #Break if user is not selecting any more devices if [[ $SelectedDevice == -1 ]] then DeviceSortArrayString="" for (( DeviceIterator=1; DeviceIterator <= $NumberOfDevices; DeviceIterator++ )) do if [[ $DeviceIterator != 1 ]] then DeviceSortArrayString+=$'\n' fi DeviceSortArrayString+="$DeviceIterator,${DeviceConnectTimestamps[$DeviceIterator]}" done DeviceSortArrayIndex=($(echo "$DeviceSortArrayString" | sort -rnt "," -k 2,2 | awk -F "," '{print $1}')) NewDeviceConfigPromptText="" for DeviceElementNumber in "${DeviceSortArrayIndex[@]}" do #only change the seat id in udev if the user specified to do so if [[ ${DeviceNewSeatConfigChangeds[$DeviceElementNumber]} == 1 ]] then ((ChangedDeviceCount++)) NewDeviceConfigPromptText+="${DeviceTypes[$DeviceElementNumber]}: ${DeviceDisplayNames[$DeviceElementNumber]} on ${DeviceConnections[$DeviceElementNumber]}"$'\n' if [[ ${DeviceInputTypeFlags[$DeviceElementNumber]} == 1 ]] then NewDeviceConfigPromptText+=" From: ${DeviceCurrentPhysicalSeatIDs[$DeviceElementNumber]} / ${DeviceCurrentLogicalSeatIDs[$DeviceElementNumber]}"$'\n' NewDeviceConfigPromptText+=" To: ${DeviceNewPhysicalSeatIDs[$DeviceElementNumber]} / ${DeviceNewLogicalSeatIDs[$DeviceElementNumber]}"$'\n' else NewDeviceConfigPromptText+=" From: ${DeviceCurrentPhysicalSeatIDs[$DeviceElementNumber]}"$'\n' NewDeviceConfigPromptText+=" To: ${DeviceNewPhysicalSeatIDs[$DeviceElementNumber]}"$'\n' fi fi done if [[ $ChangedDeviceCount == 0 ]] then $ZENITYCMD --question --no-wrap --title="Multipointer and Seat Configuration" --text "${AdditionalPromptText}Are you sure you want to quit this utility without making changes?" 2>/dev/null Result=$? if [[ $Result == 0 ]] then break else continue fi fi echo "${AdditionalPromptText}Are you sure you want to quit this utility and save the following changes to the $ChangedDeviceCount Devices?"$'\n'$'\n'"$NewDeviceConfigPromptText" | $ZENITYCMD --text-info --title="Multipointer and Seat Configuration" --width=550 --height=700 --ok-label=Yes --cancel-label=No 2>/dev/null Result=$? if [[ $Result == 0 ]] then break else continue fi fi ConfigureDevice $SelectedDevice done if [[ $ChangedDeviceCount == 0 ]] then return 1 else return 0 fi } function MainDialog { SelectedOption=$(echo "1 Configure Devices 2 Show Detailed Help 3 Quit"| $ZENITYCMD --height 380 --list --hide-column 1 --print-column 1 --column=selectionnumber --column=option --hide-header --title="Multipointer and Seat Configuration" --text "Select device configuration mode. 'Configure All Devices' lists all devices currently attached to be configured. 'Show Detailed Help' shows more details on how to use this wizard 'Quit' Exits this wizard ") Return=$? if [[ $Return != 0 ]] then exit fi } PrepareSeatList function ConfigureSeatsMain { #Main wizard dialog while [ 1 ] do MainDialog if [[ $SelectedOption == 1 ]] then CollectDeviceInfo MainConfig Return=$? #Only act if settings have been changed if [[ $Return == 0 ]] then CommitConfigFile SignalCompletion fi elif [[ $SelectedOption == 2 ]] then HelpDialog elif [[ $SelectedOption == 3 ]] then exit fi done } ConfigureSeatsMain "$@"