Files
jcd3198-t6-driver/bin/fix-monitor-layout
Jacob Nelson 5dbbb34096 Initial commit: JCD3198 second HDMI driver setup for Fedora
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>
2026-05-17 18:36:35 -05:00

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()