mh
2 months ago
23 changed files with 11 additions and 1626 deletions
@ -1,98 +1,20 @@ |
|||||
#!/bin/env bash |
#!/bin/env bash |
||||
|
|
||||
source ./src/scripts/utils/source.sh |
sudo cp -r src/rules/* /etc/udev/rules.d |
||||
source ./src/scripts/utils/progressbar.sh |
|
||||
|
|
||||
while getopts 'v' OPTION; do |
|
||||
case "$OPTION" in |
|
||||
v) |
|
||||
_V=1 |
|
||||
;; |
|
||||
?) |
|
||||
echo "usage: ./setup.sh [-v]" >&2 |
|
||||
exit 1 |
|
||||
;; |
|
||||
esac |
|
||||
done |
|
||||
|
|
||||
enable_trapping |
|
||||
draw_progress_bar 0 |
|
||||
log '.: Setting up sono-os v0.1.0 :.' |
|
||||
check_superuser |
|
||||
sleep 1 |
|
||||
draw_progress_bar 5 |
|
||||
log 'Installing dependancies ...' |
|
||||
sudo apt -qq update |
|
||||
draw_progress_bar 10 |
|
||||
sudo apt -qq install -y python3-pip build-essential |
|
||||
draw_progress_bar 15 |
|
||||
|
|
||||
log 'Installing pyxlib and pyudev ...' |
|
||||
pip3 install pyxlib pyudev -q |
|
||||
draw_progress_bar 20 |
|
||||
|
|
||||
log 'Installing X scripts ...' |
|
||||
sudo cp -r src/scripts/xscript/* /usr/local/bin |
|
||||
draw_progress_bar 23 |
|
||||
sudo cp src/scripts/setupmonitor.sh /usr/local/bin |
|
||||
draw_progress_bar 27 |
|
||||
sudo chmod +x /usr/local/bin/setupmonitor.sh |
|
||||
draw_progress_bar 30 |
|
||||
sudo chmod +x /usr/local/bin/changemouse.py |
|
||||
|
|
||||
log 'Installing Sleep scripts ...' |
|
||||
sudo cp src/scripts/utils/socknix.py /usr/local/bin |
|
||||
draw_progress_bar 33 |
|
||||
sudo chmod +x /usr/local/bin/socknix.py |
|
||||
draw_progress_bar 35 |
|
||||
sudo cp src/scripts/sleeputil.sh /lib/systemd/system-sleep/ |
|
||||
draw_progress_bar 36 |
|
||||
sudo chmod +x /lib/systemd/system-sleep/sleeputil.sh |
|
||||
|
|
||||
|
|
||||
draw_progress_bar 45 |
|
||||
log 'Installing config files ...' |
|
||||
# TODO |
|
||||
|
|
||||
draw_progress_bar 65 |
|
||||
log 'Configuring Logger ...' |
|
||||
# TODO |
|
||||
|
|
||||
draw_progress_bar 85 |
|
||||
log 'Copying rules to udev ...' |
|
||||
sudo cp -r src/rules/* /usr/lib/udev/rules.d |
|
||||
sudo cp src/rules/90-usb-storage.rules /etc/udev/rules.d |
|
||||
sudo cp src/rules/90-dvd-storage.rules /etc/udev/rules.d |
|
||||
sudo udevadm control --reload-rules |
sudo udevadm control --reload-rules |
||||
sudo udevadm trigger |
sudo udevadm trigger |
||||
|
|
||||
draw_progress_bar 90 |
|
||||
sudo cp src/scripts/utils/usb/usb-storage-action /usr/local/bin/ |
sudo cp src/scripts/utils/usb/usb-storage-action /usr/local/bin/ |
||||
sudo chmod +x /usr/local/bin/usb-storage-action |
sudo chmod +x /usr/local/bin/usb-storage-action |
||||
sudo cp src/scripts/utils/usb/addUsbScript.sh /usr/local/bin/ |
|
||||
sudo chmod +x /usr/local/bin/addUsbScript.sh |
|
||||
sudo cp src/scripts/utils/usb/usb-socket-sono.socket /etc/systemd/system |
sudo cp src/scripts/utils/usb/usb-socket-sono.socket /etc/systemd/system |
||||
sudo cp src/scripts/utils/usb/usb-socket-sono.service /etc/systemd/system |
sudo cp src/scripts/utils/usb/usb-socket-sono.service /etc/systemd/system |
||||
sudo systemctl enable usb-socket-sono.socket |
sudo systemctl enable usb-socket-sono.socket |
||||
sudo systemctl enable usb-socket-sono.service |
|
||||
sudo systemctl start usb-socket-sono.socket |
sudo systemctl start usb-socket-sono.socket |
||||
sudo systemctl start usb-socket-sono.service |
|
||||
|
|
||||
sudo cp src/scripts/utils/dvd/dvd-storage-action /usr/local/bin/ |
sudo cp src/scripts/utils/dvd/dvd-storage-action /usr/local/bin/ |
||||
sudo chmod +x /usr/local/bin/dvd-storage-action |
sudo chmod +x /usr/local/bin/dvd-storage-action |
||||
sudo cp src/scripts/utils/dvd/addDvdScript.sh /usr/local/bin/ |
|
||||
sudo chmod +x /usr/local/bin/addDvdScript.sh |
|
||||
sudo cp src/scripts/utils/dvd/dvd-socket-sono.socket /etc/systemd/system |
sudo cp src/scripts/utils/dvd/dvd-socket-sono.socket /etc/systemd/system |
||||
sudo cp src/scripts/utils/dvd/dvd-socket-sono.service /etc/systemd/system |
sudo cp src/scripts/utils/dvd/dvd-socket-sono.service /etc/systemd/system |
||||
sudo systemctl enable dvd-socket-sono.socket |
sudo systemctl enable dvd-socket-sono.socket |
||||
sudo systemctl enable dvd-socket-sono.service |
|
||||
sudo systemctl start dvd-socket-sono.socket |
sudo systemctl start dvd-socket-sono.socket |
||||
sudo systemctl start dvd-socket-sono.service |
|
||||
|
|
||||
|
|
||||
draw_progress_bar 95 |
|
||||
log 'Install PCIE Driver' |
|
||||
# TODO |
|
||||
|
|
||||
draw_progress_bar 100 |
|
||||
destroy_scroll_area |
|
@ -1 +0,0 @@ |
|||||
SUBSYSTEM=="drm", ENV{MONITOR_LOCK}="/tmp/monitorlock", ENV{SONOLOG}="/tmp/sonolog.log", RUN+="/usr/local/bin/setupmonitor.sh" |
|
@ -1,39 +0,0 @@ |
|||||
# /bin/sh |
|
||||
# These things are run when an Openbox X Session is started. |
|
||||
# You may place a similar script in $HOME/.config/openbox/autostart |
|
||||
# to run user-specific things. |
|
||||
if true; then |
|
||||
MAX_CRASH=5000 |
|
||||
CRASH_COUNT=0 |
|
||||
while true; do |
|
||||
sudo /home/hasis/Desktop/build-sono-Desktop_Qt_5_13_1_GCC_64bit-Debug/ui/sono & |
|
||||
PID=$! |
|
||||
|
|
||||
wait $PID |
|
||||
|
|
||||
if [ "$(expr 1 = 0)" -eq 1 ]; then |
|
||||
CRASH_COUNT=0 |
|
||||
exit 0 |
|
||||
else |
|
||||
CRASH_COUNT="$(expr $CRASH_COUNT + 1)" |
|
||||
if [ $(expr $CRASH_COUNT ">=" $MAX_CRASH) -eq 1 ]; then |
|
||||
echo "[$(date)] Program has crashed $CRASH_COUNT times, giving up." >> /var/log/crashreport.log |
|
||||
exit 1 |
|
||||
else |
|
||||
echo "[$(date)] Program has crashed $CRASH_COUNT times, restarting." >> /var/log/crashreport.log |
|
||||
fi |
|
||||
fi |
|
||||
done |
|
||||
fi & |
|
||||
|
|
||||
# If you want to use GNOME config tools... |
|
||||
# |
|
||||
#if test -x /usr/lib/x86_64-linux-gnu/gnome-settings-daemon >/dev/null; then |
|
||||
# /usr/lib/x86_64-linux-gnu/gnome-settings-daemon & |
|
||||
#elif which gnome-settings-daemon >/dev/null 2>&1; then |
|
||||
# gnome-settings-daemon & |
|
||||
#fi |
|
||||
|
|
||||
# If you want to use XFCE config tools... |
|
||||
# |
|
||||
#xfce-mcs-manager & |
|
@ -1,11 +0,0 @@ |
|||||
#!/bin/bash |
|
||||
# This script will run when drm change event detected. |
|
||||
# This sctipt should be placed in /usr/local/bin |
|
||||
# SONOLOG file must be set beforehand in the udev rule |
|
||||
# MONITOR_LOCK should be set |
|
||||
( |
|
||||
flock -n 100 || exit 1 |
|
||||
sleep 1 # wait until all changes take place |
|
||||
python3 /usr/bin/local/setupmonitor.py |
|
||||
echo $(data) - INFO - Setup Monitor Done >> $SONOLOG |
|
||||
) 100> $MONITOR_LOCK |
|
@ -1,21 +0,0 @@ |
|||||
#!/bin/sh |
|
||||
|
|
||||
# should be moved to the /lib/systemd/system-sleep/ |
|
||||
|
|
||||
PATH=/sbin:/usr/sbin:/bin:/usr/bin |
|
||||
case "$1" in |
|
||||
pre) |
|
||||
STATUS=$(/usr/local/bin/socknix.py --socket /tmp/sleep.socket -m SUSPEND) |
|
||||
if [ "$STATUS" != "OK" ]; then |
|
||||
exit 1 |
|
||||
fi |
|
||||
;; |
|
||||
post) |
|
||||
STATUS=$(/usr/local/bin/socknix.py --socket /tmp/sleep.socket -m AWAIKEN) |
|
||||
if [ "$STATUS" != "OK" ]; then |
|
||||
exit 1 |
|
||||
fi |
|
||||
;; |
|
||||
esac |
|
||||
|
|
||||
exit 0 |
|
@ -1,3 +0,0 @@ |
|||||
#!/bin/bash |
|
||||
# Your script logic here |
|
||||
echo "Connection received on Unix socket" >> /var/log/exampleDvd.log |
|
@ -1,9 +1,9 @@ |
|||||
[Unit] |
[Unit] |
||||
Description=Example Unix Socket |
Description=Socket for DVD events (Sono) |
||||
|
Requires=dvd-socket-sono.service |
||||
|
|
||||
[Socket] |
[Socket] |
||||
ListenStream=/tmp/dvd-Sono-Socket.socket |
ListenStream=/run/dvd-Sono-Socket.socket |
||||
Accept=false |
|
||||
|
|
||||
[Install] |
[Install] |
||||
WantedBy=sockets.target |
WantedBy=sockets.target |
||||
|
@ -1,195 +0,0 @@ |
|||||
#!/bin/bash |
|
||||
# https://github.com/pollev/bash_progress_bar - See license at end of file |
|
||||
|
|
||||
# Constants |
|
||||
CODE_SAVE_CURSOR="\033[s" |
|
||||
CODE_RESTORE_CURSOR="\033[u" |
|
||||
CODE_CURSOR_IN_SCROLL_AREA="\033[1A" |
|
||||
COLOR_FG="\e[30m" |
|
||||
COLOR_BG="\e[42m" |
|
||||
COLOR_BG_BLOCKED="\e[43m" |
|
||||
RESTORE_FG="\e[39m" |
|
||||
RESTORE_BG="\e[49m" |
|
||||
|
|
||||
# Variables |
|
||||
PROGRESS_BLOCKED="false" |
|
||||
TRAPPING_ENABLED="false" |
|
||||
TRAP_SET="false" |
|
||||
|
|
||||
CURRENT_NR_LINES=0 |
|
||||
|
|
||||
setup_scroll_area() { |
|
||||
# If trapping is enabled, we will want to activate it whenever we setup the scroll area and remove it when we break the scroll area |
|
||||
if [ "$TRAPPING_ENABLED" = "true" ]; then |
|
||||
trap_on_interrupt |
|
||||
fi |
|
||||
|
|
||||
lines=$(tput lines) |
|
||||
CURRENT_NR_LINES=$lines |
|
||||
let lines=$lines-1 |
|
||||
# Scroll down a bit to avoid visual glitch when the screen area shrinks by one row |
|
||||
log -en "\n" |
|
||||
|
|
||||
# Save cursor |
|
||||
log -en "$CODE_SAVE_CURSOR" |
|
||||
# Set scroll region (this will place the cursor in the top left) |
|
||||
log -en "\033[0;${lines}r" |
|
||||
|
|
||||
# Restore cursor but ensure its inside the scrolling area |
|
||||
log -en "$CODE_RESTORE_CURSOR" |
|
||||
log -en "$CODE_CURSOR_IN_SCROLL_AREA" |
|
||||
|
|
||||
# Start empty progress bar |
|
||||
draw_progress_bar 0 |
|
||||
} |
|
||||
|
|
||||
destroy_scroll_area() { |
|
||||
lines=$(tput lines) |
|
||||
# Save cursor |
|
||||
log -en "$CODE_SAVE_CURSOR" |
|
||||
# Set scroll region (this will place the cursor in the top left) |
|
||||
log -en "\033[0;${lines}r" |
|
||||
|
|
||||
# Restore cursor but ensure its inside the scrolling area |
|
||||
log -en "$CODE_RESTORE_CURSOR" |
|
||||
log -en "$CODE_CURSOR_IN_SCROLL_AREA" |
|
||||
|
|
||||
# We are done so clear the scroll bar |
|
||||
clear_progress_bar |
|
||||
|
|
||||
# Scroll down a bit to avoid visual glitch when the screen area grows by one row |
|
||||
log -en "\n\n" |
|
||||
|
|
||||
# Once the scroll area is cleared, we want to remove any trap previously set. Otherwise, ctrl+c will exit our shell |
|
||||
if [ "$TRAP_SET" = "true" ]; then |
|
||||
trap - INT |
|
||||
fi |
|
||||
} |
|
||||
|
|
||||
draw_progress_bar() { |
|
||||
percentage=$1 |
|
||||
lines=$(tput lines) |
|
||||
let lines=$lines |
|
||||
|
|
||||
# Check if the window has been resized. If so, reset the scroll area |
|
||||
if [ "$lines" -ne "$CURRENT_NR_LINES" ]; then |
|
||||
setup_scroll_area |
|
||||
fi |
|
||||
|
|
||||
# Save cursor |
|
||||
log -en "$CODE_SAVE_CURSOR" |
|
||||
|
|
||||
# Move cursor position to last row |
|
||||
log -en "\033[${lines};0f" |
|
||||
|
|
||||
# Clear progress bar |
|
||||
tput el |
|
||||
|
|
||||
# Draw progress bar |
|
||||
PROGRESS_BLOCKED="false" |
|
||||
print_bar_text $percentage |
|
||||
|
|
||||
# Restore cursor position |
|
||||
log -en "$CODE_RESTORE_CURSOR" |
|
||||
} |
|
||||
|
|
||||
block_progress_bar() { |
|
||||
percentage=$1 |
|
||||
lines=$(tput lines) |
|
||||
let lines=$lines |
|
||||
# Save cursor |
|
||||
log -en "$CODE_SAVE_CURSOR" |
|
||||
|
|
||||
# Move cursor position to last row |
|
||||
log -en "\033[${lines};0f" |
|
||||
|
|
||||
# Clear progress bar |
|
||||
tput el |
|
||||
|
|
||||
# Draw progress bar |
|
||||
PROGRESS_BLOCKED="true" |
|
||||
print_bar_text $percentage |
|
||||
|
|
||||
# Restore cursor position |
|
||||
log -en "$CODE_RESTORE_CURSOR" |
|
||||
} |
|
||||
|
|
||||
clear_progress_bar() { |
|
||||
lines=$(tput lines) |
|
||||
let lines=$lines |
|
||||
# Save cursor |
|
||||
log -en "$CODE_SAVE_CURSOR" |
|
||||
|
|
||||
# Move cursor position to last row |
|
||||
log -en "\033[${lines};0f" |
|
||||
|
|
||||
# clear progress bar |
|
||||
tput el |
|
||||
|
|
||||
# Restore cursor position |
|
||||
log -en "$CODE_RESTORE_CURSOR" |
|
||||
} |
|
||||
|
|
||||
print_bar_text() { |
|
||||
local percentage=$1 |
|
||||
local cols=$(tput cols) |
|
||||
let bar_size=$cols-17 |
|
||||
|
|
||||
local color="${COLOR_FG}${COLOR_BG}" |
|
||||
if [ "$PROGRESS_BLOCKED" = "true" ]; then |
|
||||
color="${COLOR_FG}${COLOR_BG_BLOCKED}" |
|
||||
fi |
|
||||
|
|
||||
# Prepare progress bar |
|
||||
let complete_size=($bar_size*$percentage)/100 |
|
||||
let remainder_size=$bar_size-$complete_size |
|
||||
progress_bar=$(log -ne "["; log -en "${color}"; printf_new "#" $complete_size; log -en "${RESTORE_FG}${RESTORE_BG}"; printf_new "." $remainder_size; log -ne "]"); |
|
||||
|
|
||||
# Print progress bar |
|
||||
log -ne " Progress ${percentage}% ${progress_bar}" |
|
||||
} |
|
||||
|
|
||||
enable_trapping() { |
|
||||
TRAPPING_ENABLED="true" |
|
||||
} |
|
||||
|
|
||||
trap_on_interrupt() { |
|
||||
# If this function is called, we setup an interrupt handler to cleanup the progress bar |
|
||||
TRAP_SET="true" |
|
||||
trap cleanup_on_interrupt INT |
|
||||
} |
|
||||
|
|
||||
cleanup_on_interrupt() { |
|
||||
destroy_scroll_area |
|
||||
exit |
|
||||
} |
|
||||
|
|
||||
printf_new() { |
|
||||
str=$1 |
|
||||
num=$2 |
|
||||
v=$(printf "%-${num}s" "$str") |
|
||||
log -ne "${v// /$str}" |
|
||||
} |
|
||||
|
|
||||
|
|
||||
# SPDX-License-Identifier: MIT |
|
||||
# |
|
||||
# Copyright (c) 2018--2020 Polle Vanhoof |
|
||||
# |
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy |
|
||||
# of this software and associated documentation files (the "Software"), to deal |
|
||||
# in the Software without restriction, including without limitation the rights |
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|
||||
# copies of the Software, and to permit persons to whom the Software is |
|
||||
# furnished to do so, subject to the following conditions: |
|
||||
# |
|
||||
# The above copyright notice and this permission notice shall be included in all |
|
||||
# copies or substantial portions of the Software. |
|
||||
# |
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|
||||
# SOFTWARE. |
|
@ -1,25 +0,0 @@ |
|||||
#!/usr/bin/python3 |
|
||||
|
|
||||
# Author: Ali Hatami Tajik (info@alihatamitajik.ir) |
|
||||
# Date: 08 May 2023 |
|
||||
|
|
||||
import socket |
|
||||
import argparse |
|
||||
|
|
||||
parser = argparse.ArgumentParser(prog='socknix.py', |
|
||||
description='Sends specified massage to unix socket specified prints out the answer of the server in the stdout.') |
|
||||
|
|
||||
parser.add_argument('--socket', '-s', required=True, |
|
||||
help='address of the UNIX socket in the filesystem') |
|
||||
parser.add_argument('--message', '-m') |
|
||||
parser.add_argument('--buffer', '-b', type=int, default=1024) |
|
||||
|
|
||||
args = parser.parse_args() |
|
||||
|
|
||||
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) |
|
||||
s.connect(args.socket) |
|
||||
if args.message: |
|
||||
s.send(args.message.encode()) |
|
||||
else: |
|
||||
s.send(input().encode()) |
|
||||
print(s.recv(args.buffer).decode(), end='') |
|
@ -1,400 +0,0 @@ |
|||||
#!/usr/bin/env bash |
|
||||
|
|
||||
# DESC: Handler for unexpected errors |
|
||||
# ARGS: $1 (optional): Exit code (defaults to 1) |
|
||||
# OUTS: None |
|
||||
function script_trap_err() { |
|
||||
local exit_code=1 |
|
||||
|
|
||||
# Disable the error trap handler to prevent potential recursion |
|
||||
trap - ERR |
|
||||
|
|
||||
# Consider any further errors non-fatal to ensure we run to completion |
|
||||
set +o errexit |
|
||||
set +o pipefail |
|
||||
|
|
||||
# Validate any provided exit code |
|
||||
if [[ ${1-} =~ ^[0-9]+$ ]]; then |
|
||||
exit_code="$1" |
|
||||
fi |
|
||||
|
|
||||
# Output debug data if in Cron mode |
|
||||
if [[ -n ${cron-} ]]; then |
|
||||
# Restore original file output descriptors |
|
||||
if [[ -n ${script_output-} ]]; then |
|
||||
exec 1>&3 2>&4 |
|
||||
fi |
|
||||
|
|
||||
# Print basic debugging information |
|
||||
printf '%b\n' "$ta_none" |
|
||||
printf '***** Abnormal termination of script *****\n' |
|
||||
printf 'Script Path: %s\n' "$script_path" |
|
||||
printf 'Script Parameters: %s\n' "$script_params" |
|
||||
printf 'Script Exit Code: %s\n' "$exit_code" |
|
||||
|
|
||||
# Print the script log if we have it. It's possible we may not if we |
|
||||
# failed before we even called cron_init(). This can happen if bad |
|
||||
# parameters were passed to the script so we bailed out very early. |
|
||||
if [[ -n ${script_output-} ]]; then |
|
||||
# shellcheck disable=SC2312 |
|
||||
printf 'Script Output:\n\n%s' "$(cat "$script_output")" |
|
||||
else |
|
||||
printf 'Script Output: None (failed before log init)\n' |
|
||||
fi |
|
||||
fi |
|
||||
|
|
||||
# Exit with failure status |
|
||||
exit "$exit_code" |
|
||||
} |
|
||||
|
|
||||
# DESC: Handler for exiting the script |
|
||||
# ARGS: None |
|
||||
# OUTS: None |
|
||||
function script_trap_exit() { |
|
||||
cd "$orig_cwd" |
|
||||
|
|
||||
# Remove Cron mode script log |
|
||||
if [[ -n ${cron-} && -f ${script_output-} ]]; then |
|
||||
rm "$script_output" |
|
||||
fi |
|
||||
|
|
||||
# Remove script execution lock |
|
||||
if [[ -d ${script_lock-} ]]; then |
|
||||
rmdir "$script_lock" |
|
||||
fi |
|
||||
|
|
||||
# Restore terminal colours |
|
||||
printf '%b' "$ta_none" |
|
||||
} |
|
||||
|
|
||||
# DESC: Exit script with the given message |
|
||||
# ARGS: $1 (required): Message to print on exit |
|
||||
# $2 (optional): Exit code (defaults to 0) |
|
||||
# OUTS: None |
|
||||
# NOTE: The convention used in this script for exit codes is: |
|
||||
# 0: Normal exit |
|
||||
# 1: Abnormal exit due to external error |
|
||||
# 2: Abnormal exit due to script error |
|
||||
function script_exit() { |
|
||||
if [[ $# -eq 1 ]]; then |
|
||||
printf '%s\n' "$1" |
|
||||
exit 0 |
|
||||
fi |
|
||||
|
|
||||
if [[ ${2-} =~ ^[0-9]+$ ]]; then |
|
||||
printf '%b\n' "$1" |
|
||||
# If we've been provided a non-zero exit code run the error trap |
|
||||
if [[ $2 -ne 0 ]]; then |
|
||||
script_trap_err "$2" |
|
||||
else |
|
||||
exit 0 |
|
||||
fi |
|
||||
fi |
|
||||
|
|
||||
script_exit 'Missing required argument to script_exit()!' 2 |
|
||||
} |
|
||||
|
|
||||
# DESC: Generic script initialisation |
|
||||
# ARGS: $@ (optional): Arguments provided to the script |
|
||||
# OUTS: $orig_cwd: The current working directory when the script was run |
|
||||
# $script_path: The full path to the script |
|
||||
# $script_dir: The directory path of the script |
|
||||
# $script_name: The file name of the script |
|
||||
# $script_params: The original parameters provided to the script |
|
||||
# $ta_none: The ANSI control code to reset all text attributes |
|
||||
# NOTE: $script_path only contains the path that was used to call the script |
|
||||
# and will not resolve any symlinks which may be present in the path. |
|
||||
# You can use a tool like realpath to obtain the "true" path. The same |
|
||||
# caveat applies to both the $script_dir and $script_name variables. |
|
||||
# shellcheck disable=SC2034 |
|
||||
function script_init() { |
|
||||
# Useful variables |
|
||||
readonly orig_cwd="$PWD" |
|
||||
readonly script_params="$*" |
|
||||
readonly script_path="${BASH_SOURCE[1]}" |
|
||||
script_dir="$(dirname "$script_path")" |
|
||||
script_name="$(basename "$script_path")" |
|
||||
readonly script_dir script_name |
|
||||
|
|
||||
# Important to always set as we use it in the exit handler |
|
||||
# shellcheck disable=SC2155 |
|
||||
readonly ta_none="$(tput sgr0 2> /dev/null || true)" |
|
||||
} |
|
||||
|
|
||||
# DESC: Initialise colour variables |
|
||||
# ARGS: None |
|
||||
# OUTS: Read-only variables with ANSI control codes |
|
||||
# NOTE: If --no-colour was set the variables will be empty. The output of the |
|
||||
# $ta_none variable after each tput is redundant during normal execution, |
|
||||
# but ensures the terminal output isn't mangled when running with xtrace. |
|
||||
# shellcheck disable=SC2034,SC2155 |
|
||||
function colour_init() { |
|
||||
if [[ -z ${no_colour-} ]]; then |
|
||||
# Text attributes |
|
||||
readonly ta_bold="$(tput bold 2> /dev/null || true)" |
|
||||
printf '%b' "$ta_none" |
|
||||
readonly ta_uscore="$(tput smul 2> /dev/null || true)" |
|
||||
printf '%b' "$ta_none" |
|
||||
readonly ta_blink="$(tput blink 2> /dev/null || true)" |
|
||||
printf '%b' "$ta_none" |
|
||||
readonly ta_reverse="$(tput rev 2> /dev/null || true)" |
|
||||
printf '%b' "$ta_none" |
|
||||
readonly ta_conceal="$(tput invis 2> /dev/null || true)" |
|
||||
printf '%b' "$ta_none" |
|
||||
|
|
||||
# Foreground codes |
|
||||
readonly fg_black="$(tput setaf 0 2> /dev/null || true)" |
|
||||
printf '%b' "$ta_none" |
|
||||
readonly fg_blue="$(tput setaf 4 2> /dev/null || true)" |
|
||||
printf '%b' "$ta_none" |
|
||||
readonly fg_cyan="$(tput setaf 6 2> /dev/null || true)" |
|
||||
printf '%b' "$ta_none" |
|
||||
readonly fg_green="$(tput setaf 2 2> /dev/null || true)" |
|
||||
printf '%b' "$ta_none" |
|
||||
readonly fg_magenta="$(tput setaf 5 2> /dev/null || true)" |
|
||||
printf '%b' "$ta_none" |
|
||||
readonly fg_red="$(tput setaf 1 2> /dev/null || true)" |
|
||||
printf '%b' "$ta_none" |
|
||||
readonly fg_white="$(tput setaf 7 2> /dev/null || true)" |
|
||||
printf '%b' "$ta_none" |
|
||||
readonly fg_yellow="$(tput setaf 3 2> /dev/null || true)" |
|
||||
printf '%b' "$ta_none" |
|
||||
|
|
||||
# Background codes |
|
||||
readonly bg_black="$(tput setab 0 2> /dev/null || true)" |
|
||||
printf '%b' "$ta_none" |
|
||||
readonly bg_blue="$(tput setab 4 2> /dev/null || true)" |
|
||||
printf '%b' "$ta_none" |
|
||||
readonly bg_cyan="$(tput setab 6 2> /dev/null || true)" |
|
||||
printf '%b' "$ta_none" |
|
||||
readonly bg_green="$(tput setab 2 2> /dev/null || true)" |
|
||||
printf '%b' "$ta_none" |
|
||||
readonly bg_magenta="$(tput setab 5 2> /dev/null || true)" |
|
||||
printf '%b' "$ta_none" |
|
||||
readonly bg_red="$(tput setab 1 2> /dev/null || true)" |
|
||||
printf '%b' "$ta_none" |
|
||||
readonly bg_white="$(tput setab 7 2> /dev/null || true)" |
|
||||
printf '%b' "$ta_none" |
|
||||
readonly bg_yellow="$(tput setab 3 2> /dev/null || true)" |
|
||||
printf '%b' "$ta_none" |
|
||||
else |
|
||||
# Text attributes |
|
||||
readonly ta_bold='' |
|
||||
readonly ta_uscore='' |
|
||||
readonly ta_blink='' |
|
||||
readonly ta_reverse='' |
|
||||
readonly ta_conceal='' |
|
||||
|
|
||||
# Foreground codes |
|
||||
readonly fg_black='' |
|
||||
readonly fg_blue='' |
|
||||
readonly fg_cyan='' |
|
||||
readonly fg_green='' |
|
||||
readonly fg_magenta='' |
|
||||
readonly fg_red='' |
|
||||
readonly fg_white='' |
|
||||
readonly fg_yellow='' |
|
||||
|
|
||||
# Background codes |
|
||||
readonly bg_black='' |
|
||||
readonly bg_blue='' |
|
||||
readonly bg_cyan='' |
|
||||
readonly bg_green='' |
|
||||
readonly bg_magenta='' |
|
||||
readonly bg_red='' |
|
||||
readonly bg_white='' |
|
||||
readonly bg_yellow='' |
|
||||
fi |
|
||||
} |
|
||||
|
|
||||
# DESC: Initialise Cron mode |
|
||||
# ARGS: None |
|
||||
# OUTS: $script_output: Path to the file stdout & stderr was redirected to |
|
||||
function cron_init() { |
|
||||
if [[ -n ${cron-} ]]; then |
|
||||
# Redirect all output to a temporary file |
|
||||
script_output="$(mktemp --tmpdir "$script_name".XXXXX)" |
|
||||
readonly script_output |
|
||||
exec 3>&1 4>&2 1> "$script_output" 2>&1 |
|
||||
fi |
|
||||
} |
|
||||
|
|
||||
# DESC: Acquire script lock |
|
||||
# ARGS: $1 (optional): Scope of script execution lock (system or user) |
|
||||
# OUTS: $script_lock: Path to the directory indicating we have the script lock |
|
||||
# NOTE: This lock implementation is extremely simple but should be reliable |
|
||||
# across all platforms. It does *not* support locking a script with |
|
||||
# symlinks or multiple hardlinks as there's no portable way of doing so. |
|
||||
# If the lock was acquired it's automatically released on script exit. |
|
||||
function lock_init() { |
|
||||
local lock_dir |
|
||||
if [[ $1 = 'system' ]]; then |
|
||||
lock_dir="/tmp/$script_name.lock" |
|
||||
elif [[ $1 = 'user' ]]; then |
|
||||
lock_dir="/tmp/$script_name.$UID.lock" |
|
||||
else |
|
||||
script_exit 'Missing or invalid argument to lock_init()!' 2 |
|
||||
fi |
|
||||
|
|
||||
if mkdir "$lock_dir" 2> /dev/null; then |
|
||||
readonly script_lock="$lock_dir" |
|
||||
verbose_print "Acquired script lock: $script_lock" |
|
||||
else |
|
||||
script_exit "Unable to acquire script lock: $lock_dir" 1 |
|
||||
fi |
|
||||
} |
|
||||
|
|
||||
# DESC: Pretty print the provided string |
|
||||
# ARGS: $1 (required): Message to print (defaults to a green foreground) |
|
||||
# $2 (optional): Colour to print the message with. This can be an ANSI |
|
||||
# escape code or one of the prepopulated colour variables. |
|
||||
# $3 (optional): Set to any value to not append a new line to the message |
|
||||
# OUTS: None |
|
||||
function pretty_print() { |
|
||||
if [[ $# -lt 1 ]]; then |
|
||||
script_exit 'Missing required argument to pretty_print()!' 2 |
|
||||
fi |
|
||||
|
|
||||
if [[ -z ${no_colour-} ]]; then |
|
||||
if [[ -n ${2-} ]]; then |
|
||||
printf '%b' "$2" |
|
||||
else |
|
||||
printf '%b' "$fg_green" |
|
||||
fi |
|
||||
fi |
|
||||
|
|
||||
# Print message & reset text attributes |
|
||||
if [[ -n ${3-} ]]; then |
|
||||
printf '%s%b' "$1" "$ta_none" |
|
||||
else |
|
||||
printf '%s%b\n' "$1" "$ta_none" |
|
||||
fi |
|
||||
} |
|
||||
|
|
||||
# DESC: Only pretty_print() the provided string if verbose mode is enabled |
|
||||
# ARGS: $@ (required): Passed through to pretty_print() function |
|
||||
# OUTS: None |
|
||||
function verbose_print() { |
|
||||
if [[ -n ${verbose-} ]]; then |
|
||||
pretty_print "$@" |
|
||||
fi |
|
||||
} |
|
||||
|
|
||||
# DESC: Combines two path variables and removes any duplicates |
|
||||
# ARGS: $1 (required): Path(s) to join with the second argument |
|
||||
# $2 (optional): Path(s) to join with the first argument |
|
||||
# OUTS: $build_path: The constructed path |
|
||||
# NOTE: Heavily inspired by: https://unix.stackexchange.com/a/40973 |
|
||||
function build_path() { |
|
||||
if [[ $# -lt 1 ]]; then |
|
||||
script_exit 'Missing required argument to build_path()!' 2 |
|
||||
fi |
|
||||
|
|
||||
local new_path path_entry temp_path |
|
||||
|
|
||||
temp_path="$1:" |
|
||||
if [[ -n ${2-} ]]; then |
|
||||
temp_path="$temp_path$2:" |
|
||||
fi |
|
||||
|
|
||||
new_path= |
|
||||
while [[ -n $temp_path ]]; do |
|
||||
path_entry="${temp_path%%:*}" |
|
||||
case "$new_path:" in |
|
||||
*:"$path_entry":*) ;; |
|
||||
*) |
|
||||
new_path="$new_path:$path_entry" |
|
||||
;; |
|
||||
esac |
|
||||
temp_path="${temp_path#*:}" |
|
||||
done |
|
||||
|
|
||||
# shellcheck disable=SC2034 |
|
||||
build_path="${new_path#:}" |
|
||||
} |
|
||||
|
|
||||
# DESC: Check a binary exists in the search path |
|
||||
# ARGS: $1 (required): Name of the binary to test for existence |
|
||||
# $2 (optional): Set to any value to treat failure as a fatal error |
|
||||
# OUTS: None |
|
||||
function check_binary() { |
|
||||
if [[ $# -lt 1 ]]; then |
|
||||
script_exit 'Missing required argument to check_binary()!' 2 |
|
||||
fi |
|
||||
|
|
||||
if ! command -v "$1" > /dev/null 2>&1; then |
|
||||
if [[ -n ${2-} ]]; then |
|
||||
script_exit "Missing dependency: Couldn't locate $1." 1 |
|
||||
else |
|
||||
verbose_print "Missing dependency: $1" "${fg_red-}" |
|
||||
return 1 |
|
||||
fi |
|
||||
fi |
|
||||
|
|
||||
verbose_print "Found dependency: $1" |
|
||||
return 0 |
|
||||
} |
|
||||
|
|
||||
# DESC: Validate we have superuser access as root (via sudo if requested) |
|
||||
# ARGS: $1 (optional): Set to any value to not attempt root access via sudo |
|
||||
# OUTS: None |
|
||||
function check_superuser() { |
|
||||
local superuser |
|
||||
if [[ $EUID -eq 0 ]]; then |
|
||||
superuser=true |
|
||||
elif [[ -z ${1-} ]]; then |
|
||||
# shellcheck disable=SC2310 |
|
||||
if check_binary sudo; then |
|
||||
verbose_print 'Sudo: Updating cached credentials ...' |
|
||||
if ! sudo -v; then |
|
||||
verbose_print "Sudo: Couldn't acquire credentials ..." \ |
|
||||
"${fg_red-}" |
|
||||
else |
|
||||
local test_euid |
|
||||
test_euid="$(sudo -H -- "$BASH" -c 'printf "%s" "$EUID"')" |
|
||||
if [[ $test_euid -eq 0 ]]; then |
|
||||
superuser=true |
|
||||
fi |
|
||||
fi |
|
||||
fi |
|
||||
fi |
|
||||
|
|
||||
if [[ -z ${superuser-} ]]; then |
|
||||
verbose_print 'Unable to acquire superuser credentials.' "${fg_red-}" |
|
||||
return 1 |
|
||||
fi |
|
||||
|
|
||||
verbose_print 'Successfully acquired superuser credentials.' |
|
||||
return 0 |
|
||||
} |
|
||||
|
|
||||
# DESC: Run the requested command as root (via sudo if requested) |
|
||||
# ARGS: $1 (optional): Set to zero to not attempt execution via sudo |
|
||||
# $@ (required): Passed through for execution as root user |
|
||||
# OUTS: None |
|
||||
function run_as_root() { |
|
||||
if [[ $# -eq 0 ]]; then |
|
||||
script_exit 'Missing required argument to run_as_root()!' 2 |
|
||||
fi |
|
||||
|
|
||||
if [[ ${1-} =~ ^0$ ]]; then |
|
||||
local skip_sudo=true |
|
||||
shift |
|
||||
fi |
|
||||
|
|
||||
if [[ $EUID -eq 0 ]]; then |
|
||||
"$@" |
|
||||
elif [[ -z ${skip_sudo-} ]]; then |
|
||||
sudo -H -- "$@" |
|
||||
else |
|
||||
script_exit "Unable to run requested command as root: $*" 1 |
|
||||
fi |
|
||||
} |
|
||||
|
|
||||
function log () { |
|
||||
if [[ $_V -eq 1 ]]; then |
|
||||
echo "$@" |
|
||||
fi |
|
||||
} |
|
||||
|
|
||||
# vim: syntax=sh cc=80 tw=79 ts=4 sw=4 sts=4 et sr |
|
@ -1,3 +0,0 @@ |
|||||
#!/bin/bash |
|
||||
# Your script logic here |
|
||||
echo "Connection received on Unix socket" >> /var/log/example.log |
|
@ -1,9 +1,9 @@ |
|||||
[Unit] |
[Unit] |
||||
Description=Example Unix Socket |
Description=Socket for USB events (Sono) |
||||
|
Requires=usb-socket-sono.service |
||||
|
|
||||
[Socket] |
[Socket] |
||||
ListenStream=/tmp/usb-Sono-Socket.socket |
ListenStream=/run/usb-Sono-Socket.socket |
||||
Accept=false |
|
||||
|
|
||||
[Install] |
[Install] |
||||
WantedBy=sockets.target |
WantedBy=sockets.target |
||||
|
@ -1,3 +0,0 @@ |
|||||
[DEFAULT] |
|
||||
MainDisplay=HDMI-3 |
|
||||
Policy=Mirror |
|
@ -1,211 +0,0 @@ |
|||||
"""Setup Monitor Script |
|
||||
|
|
||||
Author: Ali Hatami Tajik [hatam](mailto:a.hatam008@gmail.com) |
|
||||
Date: 2023 Mar 04 |
|
||||
|
|
||||
This script should be used whenever a change happen in the rdm system, I |
|
||||
guess! But as I investigate a rdm change event will happen many time in case |
|
||||
of addition or removal of a monitor auto configuration is done by the |
|
||||
org.mate.SettingsDaemon.plugins.xrandr deamon. I'll searching for a way to |
|
||||
change that event to run our specified script. |
|
||||
|
|
||||
|
|
||||
This script will do the following: |
|
||||
1. List currently available monitors |
|
||||
2. Map those to config file |
|
||||
3. Replace default values for missing configs |
|
||||
4. Handle missing touch-screen/main monitor |
|
||||
NOTE: In general we use at least two monitoers with our system. |
|
||||
One is for touch screen and one is for regular screen. In |
|
||||
case of one missing a callback is run to handle that |
|
||||
occurance (an event may send to the software or a temp log |
|
||||
file may be updated so the software adjust ifself |
|
||||
correspondigly). |
|
||||
|
|
||||
Config files are in JSON format and spesify screen output name and their |
|
||||
mode (xrandr resolution and stuff may be added but for now --auto/--prefered |
|
||||
option is used). The format is: |
|
||||
|
|
||||
""" |
|
||||
|
|
||||
import Xlib.display |
|
||||
import Xlib.ext.randr |
|
||||
import configparser |
|
||||
from util import edit_distance |
|
||||
from util.egalax import get_egalax_drm_pure_name |
|
||||
import subprocess |
|
||||
from pathlib import Path |
|
||||
import os |
|
||||
|
|
||||
CONFIG_NAME = Path(os.path.dirname(os.path.realpath(__file__))) / "conf/desktop.conf" |
|
||||
XRANDR = "/usr/bin/xrandr" |
|
||||
|
|
||||
def read_config(): |
|
||||
"""Reads config file of desktop setup |
|
||||
|
|
||||
This function will reeds the config file of desktop. Config file contins |
|
||||
a default monitor port name which will be the main Monitor display. |
|
||||
|
|
||||
NOTE: eGalax devide will be detected automatically from the /dev mountings. |
|
||||
""" |
|
||||
config = configparser.ConfigParser() |
|
||||
read = config.read(CONFIG_NAME) |
|
||||
if not read: |
|
||||
raise FileNotFoundError("Desktop config file not found") |
|
||||
return config |
|
||||
|
|
||||
|
|
||||
def all_connected_monitor(): |
|
||||
"""Generates all connected monitors |
|
||||
|
|
||||
as a tuple of (atom name: str, width, height) |
|
||||
""" |
|
||||
display = Xlib.display.Display() |
|
||||
root = display.screen().root |
|
||||
for m in root.xrandr_get_monitors().monitors: |
|
||||
yield ( |
|
||||
display.get_atom_name(m.name), |
|
||||
m.width_in_pixels, |
|
||||
m.height_in_pixels, |
|
||||
) |
|
||||
|
|
||||
|
|
||||
def get_edid_name(drm_name: str): |
|
||||
"""Change eGalax DRM name to atom name |
|
||||
|
|
||||
This function is very sensitive to kernel version and might not work |
|
||||
with some kernels. |
|
||||
""" |
|
||||
card_num, name = drm_name[4:].split("-", maxsplit=1) |
|
||||
first, second = name.rsplit("-", maxsplit=1) |
|
||||
return first + "-" + card_num + "-" + second |
|
||||
|
|
||||
|
|
||||
def prepare_monitors(config): |
|
||||
"""Prepare monitor names |
|
||||
|
|
||||
|
|
||||
Rules: |
|
||||
- Use default monitor port as the main monitor if it is connected |
|
||||
- If default monitor is not connected then use a monitor with |
|
||||
minimum edit distance. |
|
||||
|
|
||||
- Use eGalax as touchpanel and if it's not connected return None. |
|
||||
|
|
||||
- other connected monitors will be returned as a list |
|
||||
|
|
||||
each monitor is returen as a |
|
||||
|
|
||||
Returns |
|
||||
tuple: of |
|
||||
- main monitor -> tuple | None if no monitor available other than |
|
||||
touchpanel |
|
||||
- touchpanel -> tuple | None if touchpanel did not exist |
|
||||
- other -> list: this list may be empty if there is no other |
|
||||
monitor connected |
|
||||
""" |
|
||||
main = config["DEFAULT"]["MainDisplay"] |
|
||||
all_monitors = list(all_connected_monitor()) |
|
||||
egalax_drm = get_egalax_drm_pure_name() |
|
||||
egalax_name = get_edid_name(egalax_drm) if egalax_drm else None |
|
||||
egalax_monitor = None |
|
||||
main_monitor = None |
|
||||
for mon in all_monitors: |
|
||||
if egalax_name == mon[0]: |
|
||||
egalax_monitor = mon |
|
||||
if main == mon[0]: |
|
||||
main_monitor = mon |
|
||||
if egalax_monitor: |
|
||||
all_monitors.remove(egalax_monitor) |
|
||||
if not main_monitor: |
|
||||
try: |
|
||||
min_monitor = min( |
|
||||
all_monitors, |
|
||||
key=lambda x: edit_distance(main, x, len(main), len(x)), |
|
||||
) |
|
||||
main_monitor = min_monitor |
|
||||
except: |
|
||||
main_monitor = None |
|
||||
assert len(all_monitors) == 0 |
|
||||
if main_monitor: |
|
||||
all_monitors.remove(main_monitor) |
|
||||
return main_monitor, egalax_monitor, all_monitors |
|
||||
|
|
||||
|
|
||||
def baseline(main, egalax): |
|
||||
"""Base of xrandr arguments |
|
||||
|
|
||||
Both main and egalax are monitor tuples mentioned in prepare_monitors""" |
|
||||
if not main and not egalax: |
|
||||
return [], None |
|
||||
elif not main and egalax: |
|
||||
return ["--output", egalax[0], "--primary", |
|
||||
"--mode", f"{egalax[1]}x{egalax[2]}"], None |
|
||||
elif main and not egalax: |
|
||||
return ["--output", main[0], "--primary", |
|
||||
"--mode", f"{main[1]}x{main[2]}"], main[0] |
|
||||
else: |
|
||||
return ["--output", main[0], "--primary", |
|
||||
"--mode", f"{main[1]}x{main[2]}", |
|
||||
"--output", egalax[0], "--right-of", main[0], |
|
||||
"--mode", f"{egalax[1]}x{egalax[1]}"], egalax[0] |
|
||||
|
|
||||
|
|
||||
def mirror_displays(main, egalax, others: list): |
|
||||
base, should_mirror = baseline(main, egalax) |
|
||||
if should_mirror: |
|
||||
for name, width, height in others: |
|
||||
base.extend(["--output", name, "--mode", f"{width}x{height}", |
|
||||
"--same-as", main[0], |
|
||||
"--scale-from", f"{main[1]}x{main[2]}"]) |
|
||||
return base |
|
||||
|
|
||||
|
|
||||
def off_displays(main, egalax, others: list): |
|
||||
base, should_off = baseline(main, egalax) |
|
||||
if should_off: |
|
||||
for name, width, height in others: |
|
||||
base.extend(["--output", name, "--off"]) |
|
||||
return base |
|
||||
|
|
||||
|
|
||||
def stand_alone_displays(main, egalax, others: list): |
|
||||
base, rightmost = baseline(main, egalax) |
|
||||
if rightmost: |
|
||||
for name, width, height in others: |
|
||||
base.extend(["--output", name, "--mode", f"{width}x{height}", |
|
||||
"--right-of", rightmost]) |
|
||||
rightmost = name |
|
||||
return base |
|
||||
|
|
||||
|
|
||||
POLICY = { |
|
||||
'Off': off_displays, |
|
||||
'Mirror': mirror_displays, |
|
||||
'StandAlone': stand_alone_displays |
|
||||
} |
|
||||
|
|
||||
|
|
||||
def config_xrandr(conf, main, egalax, others: list): |
|
||||
"""Executes xrandr with policy in the conf |
|
||||
|
|
||||
Policies: |
|
||||
Policies are about monitors other than main and touch panel monitors. |
|
||||
There are three supported policies: |
|
||||
- Off: Disables other monitors (default policy if not in config) |
|
||||
- Mirror: Mirror other displays from main monitor. |
|
||||
- StandAlone: Each monitor is mapped to the right of each other |
|
||||
""" |
|
||||
try: |
|
||||
policy = conf['DEFAULT']['Policy'] |
|
||||
except: |
|
||||
policy = 'Off' |
|
||||
args = POLICY[policy](main, egalax, others) |
|
||||
cmd = [XRANDR] + args |
|
||||
subprocess.run(cmd) |
|
||||
|
|
||||
|
|
||||
if __name__ == "__main__": |
|
||||
conf = read_config() |
|
||||
main, egalax, others = prepare_monitors(conf) |
|
||||
config_xrandr(conf, main, egalax, others) |
|
@ -1,64 +0,0 @@ |
|||||
from abc import ABC, abstractmethod |
|
||||
from pyudev import Context, Monitor, MonitorObserver, Device |
|
||||
|
|
||||
|
|
||||
class Handler(ABC): |
|
||||
"""Abstract Handler calss for device monitoring |
|
||||
|
|
||||
NOTE: No checking are done for overlaping filters and callback will be |
|
||||
even by multiple handlers. |
|
||||
|
|
||||
Args: |
|
||||
ABC: Abstract Base Class, provides abstract method functionality and |
|
||||
readability. |
|
||||
""" |
|
||||
|
|
||||
def __init__(self, filter) -> None: |
|
||||
"""Initiate a monitor observer and applies `filter` if any provided |
|
||||
|
|
||||
Args: |
|
||||
filter (_type_): _description_ |
|
||||
""" |
|
||||
monitor = Monitor.from_netlink(Context()) |
|
||||
if filter: |
|
||||
monitor.filter_by(filter) |
|
||||
self.observer = MonitorObserver(monitor, callback=self.handler) |
|
||||
self.observer.start() |
|
||||
|
|
||||
@abstractmethod |
|
||||
def callback(self, device: Device): |
|
||||
"""Callback |
|
||||
|
|
||||
This method must be implemented by child calsses. This method is |
|
||||
responsible for further managments of the devices related to its filter. |
|
||||
|
|
||||
Args: |
|
||||
device (pyudev.Device): device passed by observer through handler |
|
||||
""" |
|
||||
raise NotImplemented("Callback MUST be implemented") |
|
||||
|
|
||||
def handler(self, device): |
|
||||
"""wrapper around callback implemented |
|
||||
|
|
||||
Args: |
|
||||
device (pyudev.Device): modified device passed by self.observer |
|
||||
""" |
|
||||
self.callback(device) |
|
||||
|
|
||||
|
|
||||
class MouseHandler(Handler): |
|
||||
def __init__(self) -> None: |
|
||||
"""Initiate UsbHanlder |
|
||||
|
|
||||
Initialization contains two major steps. First it would do a |
|
||||
configuration for currently available devices and then it would wait for |
|
||||
USB udev events to reconfigure the settings. configurations would be |
|
||||
done by (This part is not decided yet. it could be done by BASH SCRIPTS |
|
||||
or we can invoke xinput binaries via python itself. a bash script |
|
||||
solution would be benefitial since it can used as utility). |
|
||||
""" |
|
||||
# TODO: use somthing that only captures |
|
||||
super().__init__("usb") |
|
||||
|
|
||||
def callback(self, device): |
|
||||
print(device.action) |
|
@ -1 +0,0 @@ |
|||||
from .common import edit_distance, max_match |
|
@ -1,63 +0,0 @@ |
|||||
"""Common Utilities""" |
|
||||
|
|
||||
|
|
||||
def max_match(a: str, b: str) -> str: |
|
||||
"""Maximum matching of intersection of pair of string |
|
||||
|
|
||||
This function will return the intersection of two strings from the start. |
|
||||
|
|
||||
Example: |
|
||||
>>> a = "/sys/devices/folan/bahman" |
|
||||
>>> b = "/sys/devices/fol/bahman" |
|
||||
>>> max_match(a,b) |
|
||||
'/sys/dedices/fol' |
|
||||
|
|
||||
Args: |
|
||||
a (str): firsrt string |
|
||||
b (str): second string |
|
||||
|
|
||||
Returns: |
|
||||
str: intersection of two strings OR None if one or both strings are |
|
||||
empty or None |
|
||||
""" |
|
||||
i = 0 |
|
||||
|
|
||||
if not a or not b: |
|
||||
return None |
|
||||
|
|
||||
if len(b) < len(a): |
|
||||
a, b = b, a |
|
||||
|
|
||||
for c in a: |
|
||||
if b[i] == c: |
|
||||
i += 1 |
|
||||
else: |
|
||||
return a[0:i] |
|
||||
|
|
||||
|
|
||||
def edit_distance(str1, str2, m, n): |
|
||||
# If first string is empty, the only option is to |
|
||||
# insert all characters of second string into first |
|
||||
if m == 0: |
|
||||
return n |
|
||||
|
|
||||
# If second string is empty, the only option is to |
|
||||
# remove all characters of first string |
|
||||
if n == 0: |
|
||||
return m |
|
||||
|
|
||||
# If last characters of two strings are same, nothing |
|
||||
# much to do. Ignore last characters and get count for |
|
||||
# remaining strings. |
|
||||
if str1[m - 1] == str2[n - 1]: |
|
||||
return edit_distance(str1, str2, m - 1, n - 1) |
|
||||
|
|
||||
# If last characters are not same, consider all three |
|
||||
# operations on last character of first string, recursively |
|
||||
# compute minimum cost for all three operations and take |
|
||||
# minimum of three values. |
|
||||
return 1 + min( |
|
||||
edit_distance(str1, str2, m, n - 1), # Insert |
|
||||
edit_distance(str1, str2, m - 1, n), # Remove |
|
||||
edit_distance(str1, str2, m - 1, n - 1), # Replace |
|
||||
) |
|
@ -1,80 +0,0 @@ |
|||||
"""eGalax |
|
||||
|
|
||||
This module is responsible for detecting the touchpanel. It would detect the |
|
||||
touchpannel's drm output and its overal status. |
|
||||
""" |
|
||||
|
|
||||
from pathlib import Path |
|
||||
from .x import get_edid_dev_path |
|
||||
from .common import max_match |
|
||||
|
|
||||
VENDOR_ID = "0EEF" |
|
||||
DEVICE_ID = "C000" |
|
||||
|
|
||||
|
|
||||
def get_egalax_path() -> Path: |
|
||||
"""Get device path |
|
||||
|
|
||||
This function will return the path of the HID device related to the pannel. |
|
||||
NOTE that it is not the path of the EDID but it can be extracted from it. |
|
||||
|
|
||||
Returns: |
|
||||
Path: Path of the eGalax hid device OR None if device is not ceonnected |
|
||||
""" |
|
||||
query = "*" + VENDOR_ID + ":" + DEVICE_ID + "*" |
|
||||
devices = list(Path("/sys/devices").rglob(query)) |
|
||||
if devices: |
|
||||
return devices[0] |
|
||||
else: |
|
||||
return None |
|
||||
|
|
||||
|
|
||||
def is_egalax_connected() -> bool: |
|
||||
"""Checks if device is connected |
|
||||
|
|
||||
avaiability of the device is checked by existing a path of the device in the |
|
||||
/sys/devices directory. |
|
||||
|
|
||||
Returns: |
|
||||
bool: True if device is connected |
|
||||
""" |
|
||||
devpath = get_egalax_path() |
|
||||
return bool(devpath) |
|
||||
|
|
||||
|
|
||||
def get_egalax_edid_path() -> Path: |
|
||||
"""return EDID path of touchpannel rdm |
|
||||
|
|
||||
This function will find intersection of the edid pathes and eGalax hid |
|
||||
device and if this intersection and returns the maximum match. |
|
||||
|
|
||||
Runtime: 160ms on average -> Not efficient |
|
||||
|
|
||||
Returns: |
|
||||
Path: edid path of eGalax OR None if not found or device is'nt connected |
|
||||
""" |
|
||||
egalax_dev = get_egalax_path() |
|
||||
if not egalax_dev: |
|
||||
return None |
|
||||
max_dir = "/sys/devices" |
|
||||
max_path = None |
|
||||
for path in get_edid_dev_path(): |
|
||||
base_dir = max_match(str(path), str(egalax_dev)) |
|
||||
if len(max_dir) < len(base_dir): |
|
||||
max_dir = base_dir |
|
||||
max_path = path |
|
||||
# TODO add sanity check (both edid and VENDOR:DEVICE are in that base) |
|
||||
return max_path |
|
||||
|
|
||||
|
|
||||
def get_egalax_drm_pure_name() -> str: |
|
||||
"""Extract DRM name form edid path |
|
||||
|
|
||||
Returns: |
|
||||
str: pure drm name OR none if device is not found |
|
||||
""" |
|
||||
edid_path = get_egalax_edid_path() |
|
||||
if edid_path: |
|
||||
return str(edid_path.parent.stem) |
|
||||
else: |
|
||||
return None |
|
@ -1,198 +0,0 @@ |
|||||
import util.x as xutil |
|
||||
|
|
||||
# Pointer states |
|
||||
SLAVE, MASTER, FLOATING = range(3) |
|
||||
|
|
||||
|
|
||||
class XInput: |
|
||||
"""Base XInput class |
|
||||
|
|
||||
Attributes: |
|
||||
name (str): name of the input |
|
||||
id (int): id of the input |
|
||||
is_master (bool): True if device is master |
|
||||
""" |
|
||||
|
|
||||
def __init__(self, name, id, state) -> None: |
|
||||
"""Initializes the class with name, id and master status |
|
||||
|
|
||||
Args: |
|
||||
name (str): name of the input. No processing is done on the name |
|
||||
id (int): id of the input |
|
||||
is_master (bool): master status of the input device |
|
||||
""" |
|
||||
self.name = name |
|
||||
self.id = id |
|
||||
self.state = state |
|
||||
|
|
||||
|
|
||||
class Pointer(XInput): |
|
||||
"""Pointer class |
|
||||
|
|
||||
This class is a wrapper around xinput commandline --list output. |
|
||||
|
|
||||
|
|
||||
Attrs: |
|
||||
is_master (bool): True if the pointer is a master pointer else False |
|
||||
""" |
|
||||
|
|
||||
def __init__(self, name, id, state) -> None: |
|
||||
super().__init__(name, id, state) |
|
||||
|
|
||||
def __repr__(self) -> str: |
|
||||
return f"<Pointer: {self.name}-{self.id}-{self.state}>" |
|
||||
|
|
||||
@property |
|
||||
def slave(self): |
|
||||
return self.state == SLAVE |
|
||||
|
|
||||
@property |
|
||||
def master(self): |
|
||||
return self.state == MASTER |
|
||||
|
|
||||
def floating(self): |
|
||||
return self.state == FLOATING |
|
||||
|
|
||||
|
|
||||
def get_short_pointer(id) -> Pointer: |
|
||||
"""Generates Pointer object corresponding to id (short attrs) |
|
||||
|
|
||||
Args: |
|
||||
id (int): pointer id |
|
||||
|
|
||||
Returns: |
|
||||
Pointer: pointer object with name, id and is_master props |
|
||||
|
|
||||
Rises: |
|
||||
ValueError: if id is not reistered with xinput |
|
||||
ValueError: if id is not a pointer id |
|
||||
""" |
|
||||
desc = xutil.get_list_short_with(id) |
|
||||
name, props = desc.rsplit("id=", 1) |
|
||||
if "pointer" in props: |
|
||||
state = FLOATING |
|
||||
if "master" in props: |
|
||||
state = MASTER |
|
||||
elif "slave" in props: |
|
||||
state = SLAVE |
|
||||
return Pointer(name.strip(), props.split(maxsplit=1)[0], state) |
|
||||
else: |
|
||||
raise TypeError(f"id[{id}] is not a pointer id") |
|
||||
|
|
||||
|
|
||||
def get_ids_iter(): |
|
||||
"""xinput id generator |
|
||||
|
|
||||
Yields: |
|
||||
int: id of xinput devices |
|
||||
""" |
|
||||
ids = xutil.get_ids() |
|
||||
for id in ids: |
|
||||
yield id |
|
||||
|
|
||||
|
|
||||
def get_pointer_iter(is_short=True): |
|
||||
"""xinput pointers generator |
|
||||
|
|
||||
Args: |
|
||||
is_short (bool, optional): if True generates short type pointers. |
|
||||
Defaults to True. |
|
||||
|
|
||||
Yields: |
|
||||
Pointer: xinput pointers |
|
||||
""" |
|
||||
for id in get_ids_iter(): |
|
||||
if is_short: |
|
||||
try: |
|
||||
yield get_short_pointer(id) |
|
||||
except TypeError as e: |
|
||||
# ignore if the id is not pointer |
|
||||
pass |
|
||||
except e: |
|
||||
# TODO: logging |
|
||||
pass |
|
||||
else: |
|
||||
pass # TODO |
|
||||
|
|
||||
|
|
||||
def get_pointers(is_short=True): |
|
||||
"""Wraps pointers in `xinput --list` in Pointer class |
|
||||
|
|
||||
Creation of the pointer is done by getting the list description of |
|
||||
each id. if the is_short arg is True, then short list description will be |
|
||||
used which will provide the class `name`, `is_master` and `id` values. |
|
||||
|
|
||||
Getting this pointers is done by first calling `xinput --list --id-only` to |
|
||||
get ids and then execute `xinput --list {id}` to get the description with |
|
||||
less-complicated output compare to iterating over `xinput --list --short` |
|
||||
line by line (--short option has some special characters that cause overhead |
|
||||
to the system for processing them individually and per-case). |
|
||||
""" |
|
||||
pointers = [] |
|
||||
for pointer in get_pointer_iter(is_short): |
|
||||
pointers.append(pointer) |
|
||||
return pointers |
|
||||
|
|
||||
|
|
||||
def create_touch_master(): |
|
||||
"""_summary_ |
|
||||
|
|
||||
Raises: |
|
||||
SystemError: If creation of touch pointer failed |
|
||||
|
|
||||
Returns: |
|
||||
Pointer: pointer object corresponding to `touch` master |
|
||||
""" |
|
||||
touch = None |
|
||||
xutil.create_master("touch") |
|
||||
id = xutil.get_xi_id_by_name("touch pointer") |
|
||||
if id: |
|
||||
try: |
|
||||
touch = get_short_pointer(id) |
|
||||
except: |
|
||||
raise SystemError( |
|
||||
"touch pointer is not available. cannot create touch pointer" |
|
||||
) |
|
||||
else: |
|
||||
raise SystemError( |
|
||||
"touch pointer is not available. cannot create touch pointer" |
|
||||
) |
|
||||
|
|
||||
# TODO configure cursor bitmap |
|
||||
return touch |
|
||||
|
|
||||
|
|
||||
def get_pointers_categorized(): |
|
||||
"""Categorized Pointers |
|
||||
|
|
||||
Categories: |
|
||||
1. VCore: Pointer |
|
||||
2. Touch Master: Pointer |
|
||||
3. eGalax: Pointer | None |
|
||||
4. Other non-masters: List[Pinter] |
|
||||
|
|
||||
Raises: |
|
||||
SystemError: If creation of touch pointer failed |
|
||||
|
|
||||
Returns: |
|
||||
|
|
||||
""" |
|
||||
v_core = None |
|
||||
touch = None |
|
||||
e_galax = None |
|
||||
pointers = [] |
|
||||
# filter pointers |
|
||||
for pointer in get_pointer_iter(): |
|
||||
if pointer.name == "Virtual core pointer": |
|
||||
v_core = pointer |
|
||||
elif "eGalax" in pointer.name: |
|
||||
e_galax = pointer |
|
||||
elif pointer.name == "touch pointer": |
|
||||
touch = pointer |
|
||||
elif not pointer.master: |
|
||||
pointers.append(pointer) |
|
||||
|
|
||||
if not touch: |
|
||||
touch = create_touch_master() |
|
||||
|
|
||||
return v_core, touch, e_galax, pointers |
|
@ -1,113 +0,0 @@ |
|||||
"""RandR |
|
||||
|
|
||||
Author: Ali Hatami Tajik [hatam](mailto:a.hatam008@gmail.com) |
|
||||
Creation Date: 2023 Mar 04 |
|
||||
|
|
||||
--- |
|
||||
This module provides a wrapper utility around xrandr. |
|
||||
|
|
||||
Classes: |
|
||||
Mode |
|
||||
Setting |
|
||||
Screen |
|
||||
Dir |
|
||||
Pos |
|
||||
|
|
||||
|
|
||||
Utilities: |
|
||||
get_screens |
|
||||
|
|
||||
""" |
|
||||
|
|
||||
from enum import Enum |
|
||||
from dataclasses import dataclass |
|
||||
from typing import List |
|
||||
|
|
||||
from Xlib.ext import randr as rnd |
|
||||
|
|
||||
|
|
||||
# TODO: Option class which can be applied by get_args method |
|
||||
# TODO: Screen-related option class ~ |
|
||||
# TODO: abs position |
|
||||
|
|
||||
|
|
||||
class Pos(Enum): |
|
||||
"""Position types in xrandr |
|
||||
|
|
||||
Position the output relative to the position of another output. |
|
||||
""" |
|
||||
|
|
||||
LEFT_OF = (0,) |
|
||||
RIGHT_OF = (1,) |
|
||||
ABOVE = (2,) |
|
||||
BELOW = (3,) |
|
||||
SAME_AS = 4 |
|
||||
|
|
||||
|
|
||||
class RotationDir(Enum): |
|
||||
"""Rotation direction |
|
||||
|
|
||||
This causes the output contents to be rotated in the specified direction. |
|
||||
""" |
|
||||
|
|
||||
NORMAL = (0,) |
|
||||
LEFT = (1,) |
|
||||
RIGHT = (2,) |
|
||||
INVERTED = 3 |
|
||||
|
|
||||
|
|
||||
class ReflectDir(Enum): |
|
||||
"""Reflection direction |
|
||||
|
|
||||
This causes the output contents to be reflected across the specified axes. |
|
||||
""" |
|
||||
|
|
||||
NORMAL = (0,) |
|
||||
X = (1,) |
|
||||
Y = (2,) |
|
||||
XY = 3 |
|
||||
|
|
||||
|
|
||||
@dataclass |
|
||||
class Setting: |
|
||||
"""Settings of a screen |
|
||||
|
|
||||
This data struct will be used as the config of each screen. Note that |
|
||||
default screen cannot be use |
|
||||
""" |
|
||||
|
|
||||
resolution = (None,) |
|
||||
is_primary = (False,) |
|
||||
is_enabeled = (True,) |
|
||||
rotation = None |
|
||||
position = None |
|
||||
reflection = None |
|
||||
|
|
||||
|
|
||||
@dataclass |
|
||||
class Mode: |
|
||||
"""Mode |
|
||||
|
|
||||
Mode of the screen including width, height, refresh rate(s) |
|
||||
""" |
|
||||
|
|
||||
height: int = 0 |
|
||||
width: int = 0 |
|
||||
frequency: List[int] = [] |
|
||||
|
|
||||
|
|
||||
class Screen: |
|
||||
"""Screen class |
|
||||
|
|
||||
This class will hold screen properties and methods related to the screens. |
|
||||
|
|
||||
At the time it will use xrandr (and not the verbose mode) to list the |
|
||||
screens and modes. |
|
||||
""" |
|
||||
|
|
||||
|
|
||||
class Monitor: |
|
||||
"""Monitor Class |
|
||||
|
|
||||
List Monitor Outputs and their states |
|
||||
""" |
|
@ -1,106 +0,0 @@ |
|||||
import subprocess |
|
||||
from pathlib import Path |
|
||||
import os |
|
||||
|
|
||||
ENCODING = "utf-8" |
|
||||
XINPUT = "/ust/bin/xinput" |
|
||||
|
|
||||
|
|
||||
def exec_xinput(args: list): |
|
||||
args.insert(0, XINPUT) |
|
||||
_read, _write = os.pipe() |
|
||||
write_fd = os.fdopen(_write, "w", 0) |
|
||||
os.read() |
|
||||
|
|
||||
|
|
||||
def get_list_short(): |
|
||||
"""Returns string output of the `xinput --list --short` command encoded as |
|
||||
UTF-8""" |
|
||||
completed = subprocess.run( |
|
||||
[XINPUT, "--list", "--short"], capture_output=True |
|
||||
) |
|
||||
return completed.stdout.decode(ENCODING) |
|
||||
|
|
||||
|
|
||||
def get_list_short_with(id): |
|
||||
"""Short List of the id |
|
||||
|
|
||||
Args: |
|
||||
id (int): id registered in xinput |
|
||||
|
|
||||
Rises: |
|
||||
ValueError: in case of id not found in devices |
|
||||
""" |
|
||||
completed = subprocess.run( |
|
||||
[XINPUT, "--list", "--short", str(id)], capture_output=True |
|
||||
) |
|
||||
|
|
||||
if completed.returncode == 0: |
|
||||
return completed.stdout.decode(ENCODING) |
|
||||
else: |
|
||||
ValueError(f"id[{id}] is not registered") |
|
||||
|
|
||||
|
|
||||
def reattach(id, master): |
|
||||
"""Reattach a device to a master |
|
||||
|
|
||||
Args: |
|
||||
id (str|int): name of the slave or id |
|
||||
master (_type_): _description_ |
|
||||
|
|
||||
TODO: Error handling should be done. BUT, if the master is not a master or |
|
||||
id is not valid, xinput will not do anything and nothing bad will happen :) |
|
||||
""" |
|
||||
completed = subprocess.run( |
|
||||
[XINPUT, "--reattach", str(id), str(master)], capture_output=True |
|
||||
) |
|
||||
|
|
||||
return completed.returncode |
|
||||
|
|
||||
|
|
||||
def get_ids(): |
|
||||
"""returns list of ids registered in xinput""" |
|
||||
completed = subprocess.run( |
|
||||
[XINPUT, "--list", "--id-only"], capture_output=True |
|
||||
) |
|
||||
return list(map(int, completed.stdout.decode(ENCODING).split())) |
|
||||
|
|
||||
|
|
||||
def create_master(name: str = "touch"): |
|
||||
"""Creates master with specified name |
|
||||
|
|
||||
Args: |
|
||||
name (str, optional): name of the master. Defaults to 'touch'. |
|
||||
""" |
|
||||
completed = subprocess.run([XINPUT, "create-master", name]) |
|
||||
|
|
||||
return completed.returncode |
|
||||
|
|
||||
|
|
||||
def get_xi_id_by_name(name): |
|
||||
"""find device id from name |
|
||||
|
|
||||
Args: |
|
||||
name (str): name of the device |
|
||||
""" |
|
||||
completed = subprocess.run( |
|
||||
[XINPUT, "list", "--id-only", name], capture_output=True |
|
||||
) |
|
||||
|
|
||||
if completed.returncode == 1: |
|
||||
return None |
|
||||
else: |
|
||||
return int(completed.stdout.decode(ENCODING)) |
|
||||
|
|
||||
|
|
||||
def map_to_output(output, device_id): |
|
||||
# TODO |
|
||||
pass |
|
||||
|
|
||||
|
|
||||
def get_edid_dev_path(): |
|
||||
"""returns iterator of pathes of devices with edid |
|
||||
|
|
||||
devices which has EDID are monitors. |
|
||||
""" |
|
||||
return Path("/sys/devices").rglob("edid") |
|
Loading…
Reference in new issue