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 subprocess
import sys
from copy import deepcopy
from PySide2.QtCore import QFile, QObject
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):
def __init__(self, ui):
super().__init__()
@ -100,7 +85,6 @@ class Window(QObject):
self.ui.orientationCombo.currentIndexChanged.connect(self.orientation_changed)
self.xrandr_info = {}
self.get_xrandr_info()
self.orig_xrandr_info = deepcopy(self.xrandr_info)
self.fill_ui()
self.ui.horizontalScale.valueChanged.connect(self.scale_changed)
self.ui.verticalScale.valueChanged.connect(self.scale_changed)
@ -132,17 +116,21 @@ class Window(QObject):
self.adjust_view()
def primary_changed(self):
mon = self.ui.screenCombo.currentText()
mon_name = self.ui.screenCombo.currentText()
primary = self.ui.primary.isChecked()
# Update visuals on all monitos
for name, monitor in self.xrandr_info.items():
if name == mon:
monitor["primary"] = primary
if primary:
self.screen.set_primary(mon_name)
else:
if primary: # There can only be one primary
monitor["primary"] = False
monitor["item"].update_visuals(monitor)
self.screen.set_primary("foobar") # no primary
# TODO Update visuals on all monitos
# for name, monitor in self.xrandr_info.items():
# if name == mon:
# monitor["primary"] = primary
# else:
# if primary: # There can only be one primary
# monitor["primary"] = False
# monitor["item"].update_visuals(monitor)
def scale_mode_changed(self):
mon = self.ui.screenCombo.currentText()
@ -253,57 +241,19 @@ class Window(QObject):
subprocess.check_call(shlex.split(cli))
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.ui.sceneView.setScene(self.scene)
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)
mon_item = MonitorItem(data=monitor, window=self, name=name,)
self.scene.addItem(mon_item)
monitor["item"] = mon_item
self.ui.screenCombo.setCurrentText(self.choose_a_monitor())
monitor.item = mon_item
self.ui.screenCombo.setCurrentText(self.screen.choose_a_monitor())
self.adjust_view()
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
# self.scale_changed() # Trigger scale labels update
def orientation_changed(self):
mon = self.ui.screenCombo.currentText()
@ -317,24 +267,20 @@ class Window(QObject):
if not mode:
return
print(f"Changing {mon} to {mode}")
self.xrandr_info[mon]["current_mode"] = mode
mode_x, mode_y = parse_mode(mode)
monitor = self.screen.monitors[mon]
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
if self.xrandr_info[mon]["orientation"] in (0, 2):
self.xrandr_info[mon]["res_x"] = int(
mode_x * self.ui.horizontalScale.value() / 1000
)
self.xrandr_info[mon]["res_y"] = int(
mode_y * self.ui.verticalScale.value() / 1000
)
if monitor.orientation in ("normal", "inverted"):
monitor.res_x = int(mode_x * self.ui.horizontalScale.value() / 1000)
monitor.res_y = int(mode_y * self.ui.verticalScale.value() / 1000)
else:
self.xrandr_info[mon]["res_x"] = int(
mode_y * self.ui.horizontalScale.value() / 1000
)
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])
monitor.res_x = int(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])
def show_pos(self, x, y):
self.pos_label.setText(f"{x},{y}")
@ -346,7 +292,7 @@ class Window(QObject):
item = mon["item"]
mon["pos_x"] = item.x()
mon["pos_y"] = item.y()
self.update_replica_of_data()
self.screen.update_replica_of()
for _, mon in self.xrandr_info.items():
mon["item"].update_visuals(mon)
self.adjust_view()
@ -357,76 +303,36 @@ class Window(QObject):
snaps_x = []
snaps_y = []
for monitor, data in self.xrandr_info.items():
if monitor == name:
for output, monitor in self.screen.monitors.items():
if output == name:
continue
else:
mod_x, mod_y = parse_mode(data["current_mode"])
snaps_x.append(data["pos_x"])
snaps_x.append(data["pos_x"] + mod_x)
snaps_y.append(data["pos_y"])
snaps_y.append(data["pos_y"] + mod_y)
mode = monitor.get_current_mode()
mod_x, mod_y = mode.res_x, mode.res_y
snaps_x.append(monitor.pos_x)
snaps_x.append(monitor.pos_x + mod_x)
snaps_y.append(monitor.pos_x)
snaps_y.append(monitor.pos_x + mod_y)
return snaps_x, snaps_y
def adjust_view(self):
self.ui.sceneView.resetTransform()
self.ui.sceneView.ensureVisible(self.scene.sceneRect(), 100, 100)
try:
scale_factor = 0.8 * min(
self.ui.sceneView.width() / self.scene.sceneRect().width(),
self.ui.sceneView.height() / self.scene.sceneRect().height(),
)
self.ui.sceneView.scale(scale_factor, scale_factor)
except ZeroDivisionError:
# Don't worry
pass
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()
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)
self.reset_screen = xrandr.parse_data(_xrandr_data)
def monitor_selected(self, name):
if not name:
@ -435,27 +341,24 @@ class Window(QObject):
self.ui.modes.blockSignals(True)
# Show modes
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)
if (
self.xrandr_info[name]["current_mode"] is None
): # Happens with turned off monitors
self.xrandr_info[name]["enabled"] = False
h_scale = v_scale = 1
mode = monitor.get_current_mode()
self.ui.modes.setCurrentText(mode.name)
if monitor.orientation in (0, 2):
h_scale = monitor.res_x / mode.res_x
v_scale = monitor.res_y / mode.res_y
else:
self.ui.modes.setCurrentText(self.xrandr_info[name]["current_mode"])
mod_x, mod_y = parse_mode(self.xrandr_info[name]["current_mode"])
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
h_scale = monitor.res_y / mode.res_x
v_scale = monitor.res_x / mode.res_y
self.ui.horizontalScale.setValue(h_scale * 1000)
self.ui.verticalScale.setValue(v_scale * 1000)
self.ui.primary.setChecked(self.xrandr_info[name]["primary"])
self.ui.enabled.setChecked(self.xrandr_info[name]["enabled"])
self.ui.orientationCombo.setCurrentIndex(self.xrandr_info[name]["orientation"])
self.ui.primary.setChecked(monitor.primary)
self.ui.enabled.setChecked(monitor.enabled)
self.ui.orientationCombo.setCurrentText(monitor.orientation)
self.ui.replicaOf.clear()
self.ui.replicaOf.addItem("None")
@ -466,7 +369,7 @@ class Window(QObject):
self.ui.replicaOf.setCurrentText(mon)
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.scale_mode_changed()
@ -486,7 +389,6 @@ def main():
loader = QUiLoader()
Window(loader.load(ui_file))
sys.exit(app.exec_())

View File

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