From 93a0a73e907b67ca072a3749d3f291c2c47d9c44 Mon Sep 17 00:00:00 2001 From: Ali Hatami Tajik Date: Mon, 13 Mar 2023 15:40:53 +0330 Subject: [PATCH 1/7] Remove multipledispatch module --- src/scripts/python/util/x.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/scripts/python/util/x.py b/src/scripts/python/util/x.py index 8fd0c6f..aef22aa 100644 --- a/src/scripts/python/util/x.py +++ b/src/scripts/python/util/x.py @@ -1,11 +1,9 @@ import subprocess -from multipledispatch import dispatch from pathlib import Path ENCODING = "utf-8" -@dispatch() def get_list_short(): """Returns string output of the `xinput --list --short` command encoded as UTF-8""" @@ -15,7 +13,6 @@ def get_list_short(): return completed.stdout.decode(ENCODING) -@dispatch(int) def get_list_short_with(id): """Short List of the id From 62eebad3a3ebf33bc82fdf0f5ae67f6ced60a41e Mon Sep 17 00:00:00 2001 From: Ali Hatami Tajik Date: Mon, 3 Apr 2023 11:10:57 +0330 Subject: [PATCH 2/7] Add prepare monitor This function will return setup of the monitor systems. --- conf/desktop.conf | 3 + setup.sh | 5 ++ src/scripts/python/setupmonitor.py | 98 ++++++++++++++++++++++++++++- src/scripts/python/util/__init__.py | 1 + src/scripts/python/util/common.py | 28 +++++++++ src/scripts/python/util/egalax.py | 4 +- 6 files changed, 136 insertions(+), 3 deletions(-) create mode 100644 conf/desktop.conf create mode 100644 src/scripts/python/util/__init__.py diff --git a/conf/desktop.conf b/conf/desktop.conf new file mode 100644 index 0000000..11d68f1 --- /dev/null +++ b/conf/desktop.conf @@ -0,0 +1,3 @@ +[DEFAULT] +MainDisplay=HDMI-3 +Policy=Mirror diff --git a/setup.sh b/setup.sh index ac18cdc..1904692 100755 --- a/setup.sh +++ b/setup.sh @@ -21,6 +21,11 @@ log '.: Setting up sono-os v0.1.0 :.' sleep 1 draw_progress_bar 5 +log 'Installing dependancies ...' +# TODO +sleep 1 + +draw_progress_bar 15 log 'Installing scripts ...' # TODO sleep 1 diff --git a/src/scripts/python/setupmonitor.py b/src/scripts/python/setupmonitor.py index aa43d9e..e054156 100644 --- a/src/scripts/python/setupmonitor.py +++ b/src/scripts/python/setupmonitor.py @@ -28,6 +28,102 @@ Date: 2023 Mar 04 """ +import Xlib.display +import Xlib.ext.randr +import configparser +from operator import itemgetter +from util import edit_distance +from util.egalax import get_egalax_drm_pure_name + +CONFIG_NAME = "conf/desktop.conf" + + +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): + """Change eGalax DRM name to atom name""" + return "#TODO" + + +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) + 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 + if __name__ == "__main__": - pass + conf = read_config() + print(prepare_monitors(conf)) diff --git a/src/scripts/python/util/__init__.py b/src/scripts/python/util/__init__.py new file mode 100644 index 0000000..7d9dd68 --- /dev/null +++ b/src/scripts/python/util/__init__.py @@ -0,0 +1 @@ +from .common import edit_distance, max_match diff --git a/src/scripts/python/util/common.py b/src/scripts/python/util/common.py index 6e2bd91..2ee76e4 100644 --- a/src/scripts/python/util/common.py +++ b/src/scripts/python/util/common.py @@ -33,3 +33,31 @@ def max_match(a: str, b: str) -> str: 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 + ) diff --git a/src/scripts/python/util/egalax.py b/src/scripts/python/util/egalax.py index 1cf43aa..3056fb4 100644 --- a/src/scripts/python/util/egalax.py +++ b/src/scripts/python/util/egalax.py @@ -5,8 +5,8 @@ touchpannel's drm output and its overal status. """ from pathlib import Path -from x import get_edid_dev_path -from common import max_match +from .x import get_edid_dev_path +from .common import max_match VENDOR_ID = "0EEF" DEVICE_ID = "C000" From 7120888afe89a62d3cfc26056ee4b0c9235cda8b Mon Sep 17 00:00:00 2001 From: Ali Hatami Tajik Date: Mon, 3 Apr 2023 11:19:22 +0330 Subject: [PATCH 3/7] Implement get edid name --- src/scripts/python/setupmonitor.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/scripts/python/setupmonitor.py b/src/scripts/python/setupmonitor.py index e054156..0be74ca 100644 --- a/src/scripts/python/setupmonitor.py +++ b/src/scripts/python/setupmonitor.py @@ -68,9 +68,11 @@ def all_connected_monitor(): ) -def get_edid_name(drm_name): +def get_edid_name(drm_name: str): """Change eGalax DRM name to atom name""" - return "#TODO" + card_num, name = drm_name[4:].split("-", maxsplit=1) + first, second = name.rsplit("-", maxsplit=1) + return first + "-" + card_num + "-" + second def prepare_monitors(config): From 25a054bd21f5d16f576adb48aabca1c77db8d70c Mon Sep 17 00:00:00 2001 From: Ali Hatami Tajik Date: Fri, 7 Apr 2023 16:27:42 +0330 Subject: [PATCH 4/7] Add xrandr policy --- src/scripts/python/setupmonitor.py | 88 ++++++++++++++++++++++++++++-- 1 file changed, 83 insertions(+), 5 deletions(-) diff --git a/src/scripts/python/setupmonitor.py b/src/scripts/python/setupmonitor.py index 0be74ca..501bb5a 100644 --- a/src/scripts/python/setupmonitor.py +++ b/src/scripts/python/setupmonitor.py @@ -31,12 +31,12 @@ Date: 2023 Mar 04 import Xlib.display import Xlib.ext.randr import configparser -from operator import itemgetter from util import edit_distance from util.egalax import get_egalax_drm_pure_name +import subprocess CONFIG_NAME = "conf/desktop.conf" - +XRANDR = "/usr/bin/xrandr" def read_config(): """Reads config file of desktop setup @@ -69,7 +69,11 @@ def all_connected_monitor(): def get_edid_name(drm_name: str): - """Change eGalax DRM name to atom name""" + """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 @@ -101,7 +105,7 @@ def prepare_monitors(config): main = config["DEFAULT"]["MainDisplay"] all_monitors = list(all_connected_monitor()) egalax_drm = get_egalax_drm_pure_name() - egalax_name = get_edid_name(egalax_drm) + egalax_name = get_edid_name(egalax_drm) if egalax_drm else None egalax_monitor = None main_monitor = None for mon in all_monitors: @@ -126,6 +130,80 @@ def prepare_monitors(config): 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() - print(prepare_monitors(conf)) + main, egalax, others = prepare_monitors(conf) + config_xrandr(conf, main, egalax, others) From 39a481eb565f388b5de4c6865cc3ead9d52da429 Mon Sep 17 00:00:00 2001 From: Ali Hatami Tajik Date: Fri, 7 Apr 2023 17:08:59 +0330 Subject: [PATCH 5/7] Add drm rules for monitor change event --- src/rules/90-drm.rules | 1 + src/scripts/setupmonitor.sh | 12 ++++++++++++ 2 files changed, 13 insertions(+) create mode 100644 src/rules/90-drm.rules create mode 100755 src/scripts/setupmonitor.sh diff --git a/src/rules/90-drm.rules b/src/rules/90-drm.rules new file mode 100644 index 0000000..cbd4186 --- /dev/null +++ b/src/rules/90-drm.rules @@ -0,0 +1 @@ +SUBSYSTEM=="drm", ENV{MONITOR_LOCK}="/tmp/monitorlock", ENV{SONOLOG}="/tmp/sonolog.log", RUN+="/usr/local/bin/setupmonitor.sh" \ No newline at end of file diff --git a/src/scripts/setupmonitor.sh b/src/scripts/setupmonitor.sh new file mode 100755 index 0000000..40552d7 --- /dev/null +++ b/src/scripts/setupmonitor.sh @@ -0,0 +1,12 @@ +#!/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 + xrandr --auto + python3 /usr/bin/local/python/setupmonitor.py + echo $(data) - INFO - Setup Monitor Done >> $SONOLOG +) 100> $MONITOR_LOCK \ No newline at end of file From 2e4b471eaca121d1d68c7b29748880708168a0d2 Mon Sep 17 00:00:00 2001 From: Ali Hatami Tajik Date: Fri, 7 Apr 2023 17:27:06 +0330 Subject: [PATCH 6/7] Move conf folder to src/script/python --- {conf => src/scripts/python/conf}/desktop.conf | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {conf => src/scripts/python/conf}/desktop.conf (100%) diff --git a/conf/desktop.conf b/src/scripts/python/conf/desktop.conf similarity index 100% rename from conf/desktop.conf rename to src/scripts/python/conf/desktop.conf From 6fa9e4f10366026a437315414fbdeaa5d360b032 Mon Sep 17 00:00:00 2001 From: Ali Hatami Tajik Date: Fri, 7 Apr 2023 17:28:17 +0330 Subject: [PATCH 7/7] Change relative config file to absolute --- src/scripts/python/setupmonitor.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/scripts/python/setupmonitor.py b/src/scripts/python/setupmonitor.py index 501bb5a..7f71d69 100644 --- a/src/scripts/python/setupmonitor.py +++ b/src/scripts/python/setupmonitor.py @@ -34,8 +34,10 @@ 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 = "conf/desktop.conf" +CONFIG_NAME = Path(os.path.dirname(os.path.realpath(__file__))) / "conf/desktop.conf" XRANDR = "/usr/bin/xrandr" def read_config():