Enables the MCT T6 USB Station (0711:5601) chip on the J5 Create JCD3198 dock, which drives the 4K30 HDMI port with no native Linux kernel driver. Stack: EVDI 1.14.16 (kernel module + libevdi) → t6evdi userspace daemon (mobilepixels-linux-driver, patched for EVDI 1.14 API) → MCT T6 over USB. Includes DKMS registration for evdi.ko (auto-rebuilds on kernel updates) and a Mutter D-Bus layout script that restores monitor positions by serial number after each daemon restart, working around the incrementing DVI-I-N connector name assigned by EVDI on every reconnect. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
86 lines
2.4 KiB
Python
Executable File
86 lines
2.4 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Re-apply dual-monitor layout after t6evdi restarts.
|
|
|
|
Matches monitors by serial number (not connector name), so it works
|
|
regardless of what DRM connector name t6evdi assigns on each restart.
|
|
|
|
LG FHD (GSM, serial 0x01010101) → left (x=0)
|
|
Acer SA270 (ACR, serial 0x13102591) → right (x=1920), primary
|
|
"""
|
|
|
|
import sys
|
|
import dbus
|
|
|
|
BUS = 'org.gnome.Mutter.DisplayConfig'
|
|
PATH = '/org/gnome/Mutter/DisplayConfig'
|
|
|
|
LG_SERIAL = '0x01010101'
|
|
ACER_SERIAL = '0x13102591'
|
|
|
|
|
|
def find_mode(modes, width=1920, height=1080, rate=74.973):
|
|
"""Return mode ID matching resolution+rate, falling back to resolution only."""
|
|
for mode_id, mw, mh, mr, *_ in modes:
|
|
if int(mw) == width and int(mh) == height and abs(float(mr) - rate) < 0.5:
|
|
return str(mode_id)
|
|
for mode_id, mw, mh, *_ in modes:
|
|
if int(mw) == width and int(mh) == height:
|
|
return str(mode_id)
|
|
return None
|
|
|
|
|
|
def main():
|
|
try:
|
|
bus = dbus.SessionBus()
|
|
except dbus.exceptions.DBusException as e:
|
|
print(f"ERROR: cannot connect to session bus: {e}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
proxy = bus.get_object(BUS, PATH)
|
|
iface = dbus.Interface(proxy, BUS)
|
|
|
|
serial, monitors, _, _ = iface.GetCurrentState()
|
|
|
|
connectors = {}
|
|
mode_ids = {}
|
|
|
|
for (connector, vendor, product, mon_serial), modes, props in monitors:
|
|
s = str(mon_serial)
|
|
if s in (LG_SERIAL, ACER_SERIAL):
|
|
connectors[s] = str(connector)
|
|
mode_ids[s] = find_mode(modes)
|
|
|
|
errors = []
|
|
for label, key in [('LG FHD', LG_SERIAL), ('Acer SA270', ACER_SERIAL)]:
|
|
if key not in connectors:
|
|
errors.append(f"{label} not detected")
|
|
elif mode_ids[key] is None:
|
|
errors.append(f"no 1920x1080 mode on {label}")
|
|
if errors:
|
|
print(f"ERROR: {'; '.join(errors)}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
logical = [
|
|
(0, 0, 1.0, 0, False,
|
|
[(connectors[LG_SERIAL], mode_ids[LG_SERIAL], {})]),
|
|
(1920, 0, 1.0, 0, True,
|
|
[(connectors[ACER_SERIAL], mode_ids[ACER_SERIAL], {})]),
|
|
]
|
|
|
|
iface.ApplyMonitorsConfig(
|
|
dbus.UInt32(serial),
|
|
dbus.UInt32(2), # 2 = persistent
|
|
logical,
|
|
{},
|
|
signature='uua(iiduba(ssa{sv}))a{sv}'
|
|
)
|
|
|
|
print(
|
|
f"Applied: {connectors[LG_SERIAL]} (LG FHD) at x=0, "
|
|
f"{connectors[ACER_SERIAL]} (Acer SA270) at x=1920 [primary]"
|
|
)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|