Browse Source

Merge branch 'monitor-support' into input

input
Ali Hatami Tajik 2 years ago
parent
commit
2a88541eb5
  1. 5
      setup.sh
  2. 1
      src/rules/90-drm.rules
  3. 3
      src/scripts/python/conf/desktop.conf
  4. 180
      src/scripts/python/setupmonitor.py
  5. 1
      src/scripts/python/util/__init__.py
  6. 28
      src/scripts/python/util/common.py
  7. 4
      src/scripts/python/util/egalax.py
  8. 12
      src/scripts/setupmonitor.sh

5
setup.sh

@ -21,6 +21,11 @@ log '.: Setting up sono-os v0.1.0 :.'
sleep 1 sleep 1
draw_progress_bar 5 draw_progress_bar 5
log 'Installing dependancies ...'
# TODO
sleep 1
draw_progress_bar 15
log 'Installing scripts ...' log 'Installing scripts ...'
# TODO # TODO
sleep 1 sleep 1

1
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"

3
src/scripts/python/conf/desktop.conf

@ -0,0 +1,3 @@
[DEFAULT]
MainDisplay=HDMI-3
Policy=Mirror

180
src/scripts/python/setupmonitor.py

@ -29,15 +29,183 @@ Date: 2023 Mar 04
""" """
import Xlib.display 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() display = Xlib.display.Display()
root = display.screen().root root = display.screen().root
for m in root.xrandr_get_monitors(True).monitors: for m in root.xrandr_get_monitors().monitors:
connector = display.get_atom_name(m.name) yield (
print( display.get_atom_name(m.name),
f"{connector}, {m.width_in_pixels}x{m.height_in_pixels}, " m.width_in_pixels,
f"x={m.x}, y={m.y}" 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__": if __name__ == "__main__":
pass conf = read_config()
main, egalax, others = prepare_monitors(conf)
config_xrandr(conf, main, egalax, others)

1
src/scripts/python/util/__init__.py

@ -0,0 +1 @@
from .common import edit_distance, max_match

28
src/scripts/python/util/common.py

@ -33,3 +33,31 @@ def max_match(a: str, b: str) -> str:
i += 1 i += 1
else: else:
return a[0:i] 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
)

4
src/scripts/python/util/egalax.py

@ -5,8 +5,8 @@ touchpannel's drm output and its overal status.
""" """
from pathlib import Path from pathlib import Path
from x import get_edid_dev_path from .x import get_edid_dev_path
from common import max_match from .common import max_match
VENDOR_ID = "0EEF" VENDOR_ID = "0EEF"
DEVICE_ID = "C000" DEVICE_ID = "C000"

12
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
Loading…
Cancel
Save