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