Sort of starting to work with the new data structures

This commit is contained in:
Roberto Alsina 2020-02-05 19:41:05 -03:00
parent 08ce2032ab
commit 1d8769c334
3 changed files with 83 additions and 183 deletions

View File

@ -2,7 +2,6 @@ import os
import shlex import shlex
import subprocess import subprocess
import sys import sys
from copy import deepcopy
from PySide2.QtCore import QFile, QObject from PySide2.QtCore import QFile, QObject
from PySide2.QtUiTools import QUiLoader from PySide2.QtUiTools import QUiLoader
@ -75,20 +74,6 @@ def parse_monitor(line):
) )
def is_replica_of(a, b):
"""Return True if monitor a is a replica of b.
Replica means same resolution and position.
"""
return (
a["pos_x"] == b["pos_x"]
and a["pos_y"] == b["pos_y"]
and a["res_x"] == b["res_x"]
and a["res_y"] == b["res_y"]
and b["enabled"]
)
class Window(QObject): class Window(QObject):
def __init__(self, ui): def __init__(self, ui):
super().__init__() super().__init__()
@ -100,7 +85,6 @@ class Window(QObject):
self.ui.orientationCombo.currentIndexChanged.connect(self.orientation_changed) self.ui.orientationCombo.currentIndexChanged.connect(self.orientation_changed)
self.xrandr_info = {} self.xrandr_info = {}
self.get_xrandr_info() self.get_xrandr_info()
self.orig_xrandr_info = deepcopy(self.xrandr_info)
self.fill_ui() self.fill_ui()
self.ui.horizontalScale.valueChanged.connect(self.scale_changed) self.ui.horizontalScale.valueChanged.connect(self.scale_changed)
self.ui.verticalScale.valueChanged.connect(self.scale_changed) self.ui.verticalScale.valueChanged.connect(self.scale_changed)
@ -132,17 +116,21 @@ class Window(QObject):
self.adjust_view() self.adjust_view()
def primary_changed(self): def primary_changed(self):
mon = self.ui.screenCombo.currentText() mon_name = self.ui.screenCombo.currentText()
primary = self.ui.primary.isChecked() primary = self.ui.primary.isChecked()
if primary:
self.screen.set_primary(mon_name)
else:
self.screen.set_primary("foobar") # no primary
# Update visuals on all monitos # TODO Update visuals on all monitos
for name, monitor in self.xrandr_info.items(): # for name, monitor in self.xrandr_info.items():
if name == mon: # if name == mon:
monitor["primary"] = primary # monitor["primary"] = primary
else: # else:
if primary: # There can only be one primary # if primary: # There can only be one primary
monitor["primary"] = False # monitor["primary"] = False
monitor["item"].update_visuals(monitor) # monitor["item"].update_visuals(monitor)
def scale_mode_changed(self): def scale_mode_changed(self):
mon = self.ui.screenCombo.currentText() mon = self.ui.screenCombo.currentText()
@ -253,57 +241,19 @@ class Window(QObject):
subprocess.check_call(shlex.split(cli)) subprocess.check_call(shlex.split(cli))
def fill_ui(self): def fill_ui(self):
"""Load data from xrandr and setup the whole thing.""" """Configure UI out of our screen data."""
self.scene = QGraphicsScene(self) self.scene = QGraphicsScene(self)
self.ui.sceneView.setScene(self.scene) self.ui.sceneView.setScene(self.scene)
self.ui.screenCombo.clear() self.ui.screenCombo.clear()
for name, monitor in self.xrandr_info.items(): for name, monitor in self.screen.monitors.items():
self.ui.screenCombo.addItem(name) self.ui.screenCombo.addItem(name)
mon_item = MonitorItem(data=monitor, window=self, name=name,) mon_item = MonitorItem(data=monitor, window=self, name=name,)
self.scene.addItem(mon_item) self.scene.addItem(mon_item)
monitor["item"] = mon_item monitor.item = mon_item
self.ui.screenCombo.setCurrentText(self.choose_a_monitor()) self.ui.screenCombo.setCurrentText(self.screen.choose_a_monitor())
self.adjust_view() self.adjust_view()
self.scale_changed() # Trigger scale labels update # self.scale_changed() # Trigger scale labels update
def detect_scale_mode(self, monitor):
"""Given a monitor's data, try to guess what scaling
mode it's using.
TODO: detect "Automatic: physical dimensions"
"""
if not monitor["current_mode"]: # Disabled, whatever
return None
mod_x, mod_y = parse_mode(monitor["current_mode"])
scale_x = monitor["res_x"] / mod_x
scale_y = monitor["res_y"] / mod_y
if 1 == scale_x == scale_y:
print("Scale mode looks like 1x1")
return "Disabled (1x1)"
elif scale_x == scale_y:
print("Looks like Manual, same in both dimensions")
return "Manual, same in both dimensions"
else:
return "Manual"
def choose_a_monitor(self):
"""Choose what monitor to select by default.
* Not disabled
* Primary, if possible
"""
candidate = None
for name, mon in self.xrandr_info.items():
if not mon["enabled"]:
continue
if mon["primary"]:
return name
candidate = name
return candidate
def orientation_changed(self): def orientation_changed(self):
mon = self.ui.screenCombo.currentText() mon = self.ui.screenCombo.currentText()
@ -317,24 +267,20 @@ class Window(QObject):
if not mode: if not mode:
return return
print(f"Changing {mon} to {mode}") print(f"Changing {mon} to {mode}")
self.xrandr_info[mon]["current_mode"] = mode monitor = self.screen.monitors[mon]
mode_x, mode_y = parse_mode(mode) monitor.set_current_mode(mode)
mode_x, mode_y = (
monitor.get_current_mode().res_x,
monitor.get_current_mode().res_y,
)
# use resolution via scaling # use resolution via scaling
if self.xrandr_info[mon]["orientation"] in (0, 2): if monitor.orientation in ("normal", "inverted"):
self.xrandr_info[mon]["res_x"] = int( monitor.res_x = int(mode_x * self.ui.horizontalScale.value() / 1000)
mode_x * self.ui.horizontalScale.value() / 1000 monitor.res_y = int(mode_y * self.ui.verticalScale.value() / 1000)
)
self.xrandr_info[mon]["res_y"] = int(
mode_y * self.ui.verticalScale.value() / 1000
)
else: else:
self.xrandr_info[mon]["res_x"] = int( monitor.res_x = int(mode_y * self.ui.horizontalScale.value() / 1000)
mode_y * self.ui.horizontalScale.value() / 1000 monitor.res_y = int(mode_x * self.ui.verticalScale.value() / 1000)
) # TODO self.xrandr_info[mon]["item"].update_visuals(self.xrandr_info[mon])
self.xrandr_info[mon]["res_y"] = int(
mode_x * self.ui.verticalScale.value() / 1000
)
self.xrandr_info[mon]["item"].update_visuals(self.xrandr_info[mon])
def show_pos(self, x, y): def show_pos(self, x, y):
self.pos_label.setText(f"{x},{y}") self.pos_label.setText(f"{x},{y}")
@ -346,7 +292,7 @@ class Window(QObject):
item = mon["item"] item = mon["item"]
mon["pos_x"] = item.x() mon["pos_x"] = item.x()
mon["pos_y"] = item.y() mon["pos_y"] = item.y()
self.update_replica_of_data() self.screen.update_replica_of()
for _, mon in self.xrandr_info.items(): for _, mon in self.xrandr_info.items():
mon["item"].update_visuals(mon) mon["item"].update_visuals(mon)
self.adjust_view() self.adjust_view()
@ -357,76 +303,36 @@ class Window(QObject):
snaps_x = [] snaps_x = []
snaps_y = [] snaps_y = []
for monitor, data in self.xrandr_info.items(): for output, monitor in self.screen.monitors.items():
if monitor == name: if output == name:
continue continue
else: else:
mod_x, mod_y = parse_mode(data["current_mode"]) mode = monitor.get_current_mode()
snaps_x.append(data["pos_x"]) mod_x, mod_y = mode.res_x, mode.res_y
snaps_x.append(data["pos_x"] + mod_x) snaps_x.append(monitor.pos_x)
snaps_y.append(data["pos_y"]) snaps_x.append(monitor.pos_x + mod_x)
snaps_y.append(data["pos_y"] + mod_y) snaps_y.append(monitor.pos_x)
snaps_y.append(monitor.pos_x + mod_y)
return snaps_x, snaps_y return snaps_x, snaps_y
def adjust_view(self): def adjust_view(self):
self.ui.sceneView.resetTransform() self.ui.sceneView.resetTransform()
self.ui.sceneView.ensureVisible(self.scene.sceneRect(), 100, 100) self.ui.sceneView.ensureVisible(self.scene.sceneRect(), 100, 100)
scale_factor = 0.8 * min( try:
self.ui.sceneView.width() / self.scene.sceneRect().width(), scale_factor = 0.8 * min(
self.ui.sceneView.height() / self.scene.sceneRect().height(), self.ui.sceneView.width() / self.scene.sceneRect().width(),
) self.ui.sceneView.height() / self.scene.sceneRect().height(),
self.ui.sceneView.scale(scale_factor, scale_factor) )
self.ui.sceneView.scale(scale_factor, scale_factor)
except ZeroDivisionError:
# Don't worry
pass
def get_xrandr_info(self): def get_xrandr_info(self):
self.screen = xrandr.parse_data(xrandr.read_data()) _xrandr_data = xrandr.read_data()
self.screen = xrandr.parse_data(_xrandr_data)
self.screen.update_replica_of() self.screen.update_replica_of()
self.reset_screen = xrandr.parse_data(_xrandr_data)
data = subprocess.check_output(["xrandr"]).decode("utf-8").splitlines()
name = None
for line in data:
if (
line and line[0] not in "S \t" and "disconnected" not in line
): # Output line
(
name,
primary,
res_x,
res_y,
w_in_mm,
h_in_mm,
pos_x,
pos_y,
enabled,
orientation,
) = parse_monitor(line)
self.xrandr_info[name] = dict(
primary=primary,
res_x=res_x,
res_y=res_y,
w_in_mm=w_in_mm,
h_in_mm=h_in_mm,
pos_x=pos_x,
pos_y=pos_y,
modes=[],
current_mode=None,
enabled=enabled,
replica_of=[],
orientation=orientation,
)
elif line[0] == " ": # A mode
mode_name = line.strip().split()[0]
self.xrandr_info[name]["modes"].append(mode_name)
if "*" in line:
print(f"Current mode for {name}: {mode_name}")
self.xrandr_info[name]["current_mode"] = mode_name
self.update_replica_of_data()
def update_replica_of_data(self):
for a in self.xrandr_info:
self.xrandr_info[a]["replica_of"] = []
for b in self.xrandr_info:
if a != b and is_replica_of(self.xrandr_info[a], self.xrandr_info[b]):
self.xrandr_info[a]["replica_of"].append(b)
def monitor_selected(self, name): def monitor_selected(self, name):
if not name: if not name:
@ -435,27 +341,24 @@ class Window(QObject):
self.ui.modes.blockSignals(True) self.ui.modes.blockSignals(True)
# Show modes # Show modes
self.ui.modes.clear() self.ui.modes.clear()
for mode in self.xrandr_info[name]["modes"]: monitor = self.screen.monitors[name]
for mode in monitor.modes:
self.ui.modes.addItem(mode) self.ui.modes.addItem(mode)
if (
self.xrandr_info[name]["current_mode"] is None mode = monitor.get_current_mode()
): # Happens with turned off monitors self.ui.modes.setCurrentText(mode.name)
self.xrandr_info[name]["enabled"] = False if monitor.orientation in (0, 2):
h_scale = v_scale = 1 h_scale = monitor.res_x / mode.res_x
v_scale = monitor.res_y / mode.res_y
else: else:
self.ui.modes.setCurrentText(self.xrandr_info[name]["current_mode"]) h_scale = monitor.res_y / mode.res_x
mod_x, mod_y = parse_mode(self.xrandr_info[name]["current_mode"]) v_scale = monitor.res_x / mode.res_y
if self.xrandr_info[name]["orientation"] in (0, 2):
h_scale = self.xrandr_info[name]["res_x"] / mod_x
v_scale = self.xrandr_info[name]["res_y"] / mod_y
else:
h_scale = self.xrandr_info[name]["res_y"] / mod_x
v_scale = self.xrandr_info[name]["res_x"] / mod_y
self.ui.horizontalScale.setValue(h_scale * 1000) self.ui.horizontalScale.setValue(h_scale * 1000)
self.ui.verticalScale.setValue(v_scale * 1000) self.ui.verticalScale.setValue(v_scale * 1000)
self.ui.primary.setChecked(self.xrandr_info[name]["primary"]) self.ui.primary.setChecked(monitor.primary)
self.ui.enabled.setChecked(self.xrandr_info[name]["enabled"]) self.ui.enabled.setChecked(monitor.enabled)
self.ui.orientationCombo.setCurrentIndex(self.xrandr_info[name]["orientation"]) self.ui.orientationCombo.setCurrentText(monitor.orientation)
self.ui.replicaOf.clear() self.ui.replicaOf.clear()
self.ui.replicaOf.addItem("None") self.ui.replicaOf.addItem("None")
@ -466,7 +369,7 @@ class Window(QObject):
self.ui.replicaOf.setCurrentText(mon) self.ui.replicaOf.setCurrentText(mon)
self.ui.modes.blockSignals(False) self.ui.modes.blockSignals(False)
guessed_scale_mode = self.detect_scale_mode(self.xrandr_info[name]) guessed_scale_mode = monitor.guess_scale_mode()
self.ui.scaleModeCombo.setCurrentText(guessed_scale_mode) self.ui.scaleModeCombo.setCurrentText(guessed_scale_mode)
self.scale_mode_changed() self.scale_mode_changed()
@ -486,7 +389,6 @@ def main():
loader = QUiLoader() loader = QUiLoader()
Window(loader.load(ui_file)) Window(loader.load(ui_file))
sys.exit(app.exec_()) sys.exit(app.exec_())

View File

@ -63,6 +63,7 @@ class Monitor:
enabled = False enabled = False
primary = False primary = False
orientation = "normal" orientation = "normal"
item = None
def __init__(self, data): def __init__(self, data):
"""Initialize a monitor object out of data from xrandr --verbose. """Initialize a monitor object out of data from xrandr --verbose.

View File

@ -17,19 +17,19 @@ class MonitorItem(QGraphicsRectItem, QObject):
self.bottom_edge.setBrush(QBrush("red", Qt.SolidPattern)) self.bottom_edge.setBrush(QBrush("red", Qt.SolidPattern))
self.update_visuals(data) self.update_visuals(data)
def update_visuals(self, data): def update_visuals(self, monitor):
self.setRect(0, 0, data["res_x"], data["res_y"]) self.setRect(0, 0, monitor.res_x, monitor.res_y)
self.setPos(data["pos_x"], data["pos_y"]) self.setPos(monitor.pos_x, monitor.pos_y)
if data["orientation"] == 0: if monitor.orientation == "normal":
self.bottom_edge.setRect(0, data["res_y"] - 50, data["res_x"], 50) self.bottom_edge.setRect(0, monitor.res_y - 50, monitor.res_x, 50)
elif data["orientation"] == 1: elif monitor.orientation == 1:
self.bottom_edge.setRect(data["res_x"] - 50, 0, 50, data["res_y"]) self.bottom_edge.setRect(monitor.res_x - 50, 0, 50, monitor.res_y)
elif data["orientation"] == 2: elif monitor.orientation == 2:
self.bottom_edge.setRect(0, 0, data["res_x"], 50) self.bottom_edge.setRect(0, 0, monitor.res_x, 50)
elif data["orientation"] == 3: elif monitor.orientation == 3:
self.bottom_edge.setRect(0, 0, 50, data["res_y"]) self.bottom_edge.setRect(0, 0, 50, monitor.res_y)
if data["replica_of"]: if monitor.replica_of:
label_text = f"{self.name} [{','.join(data['replica_of'])}]" label_text = f"{self.name} [{','.join(monitor.replica_of)}]"
else: else:
label_text = self.name label_text = self.name
self.label.setPlainText(label_text) self.label.setPlainText(label_text)
@ -38,8 +38,8 @@ class MonitorItem(QGraphicsRectItem, QObject):
self.rect().height() / self.label.boundingRect().height(), self.rect().height() / self.label.boundingRect().height(),
) )
self.label.setScale(label_scale) self.label.setScale(label_scale)
if data["enabled"]: if monitor.enabled:
if data["primary"]: if monitor.primary:
color = QColor("#eee8d5") color = QColor("#eee8d5")
color.setAlpha(200) color.setAlpha(200)
self.setBrush(QBrush(color, Qt.SolidPattern)) self.setBrush(QBrush(color, Qt.SolidPattern))
@ -50,16 +50,13 @@ class MonitorItem(QGraphicsRectItem, QObject):
self.setBrush(QBrush(color, Qt.SolidPattern)) self.setBrush(QBrush(color, Qt.SolidPattern))
self.setZValue(self.z) self.setZValue(self.z)
self.z -= 1 self.z -= 1
self.show()
else: else:
color = QColor("#f1f1f1") color = QColor("#f1f1f1")
color.setAlpha(200) color.setAlpha(200)
self.setBrush(QBrush(color, Qt.SolidPattern)) self.setBrush(QBrush(color, Qt.SolidPattern))
self.setZValue(-1000) self.setZValue(-1000)
if not data["current_mode"]: # Disconnected or disabled
self.hide() self.hide()
else:
self.show()
def mousePressEvent(self, event): def mousePressEvent(self, event):
self.window.pos_label.show() self.window.pos_label.show()