gdctl: Fix typing and resulting handling of int|None variables

Introduces two new NamedTuples to deal with dimensions and positions.
The position is special in that x and y can be None. This was previously
wrongly declared to be only int. This commit fixes instances mypy found
where None positions were not handled.

Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/4267>
This commit is contained in:
Sebastian Wick 2025-02-12 15:13:44 +01:00 committed by Marge Bot
parent e5f6704a81
commit 674ebecd00

View File

@ -5,7 +5,8 @@ import argcomplete
import sys import sys
from dataclasses import dataclass, field from dataclasses import dataclass, field
from gi.repository import GLib, Gio from typing import NamedTuple, Any
from gi.repository import GLib, Gio # type: ignore
from enum import Enum, Flag from enum import Enum, Flag
from argcomplete.completers import BaseCompleter, SuppressCompleter from argcomplete.completers import BaseCompleter, SuppressCompleter
@ -14,6 +15,16 @@ INTERFACE = "org.gnome.Mutter.DisplayConfig"
OBJECT_PATH = "/org/gnome/Mutter/DisplayConfig" OBJECT_PATH = "/org/gnome/Mutter/DisplayConfig"
class Dimension(NamedTuple):
width: int
height: int
class Position(NamedTuple):
x: int | None
y: int | None
class NamedEnum(Enum): class NamedEnum(Enum):
def __str__(self): def __str__(self):
return next( return next(
@ -125,8 +136,7 @@ def print_data(*, level: int, is_last: bool, lines: list[int], data: str):
if level >= 0: if level >= 0:
indent = level indent = level
buffer = f"{link:{padding}>{indent * 4}}──{data}" buffer = list(f"{link:{padding}>{indent * 4}}──{data}")
buffer = list(buffer)
for line in lines: for line in lines:
if line == level: if line == level:
continue continue
@ -134,11 +144,10 @@ def print_data(*, level: int, is_last: bool, lines: list[int], data: str):
if line > 0: if line > 0:
index -= 1 index -= 1
buffer[index] = "│" buffer[index] = "│"
buffer = "".join(buffer)
else: else:
buffer = data buffer = list(data)
print(buffer) print("".join(buffer))
if is_last and level in lines: if is_last and level in lines:
lines.remove(level) lines.remove(level)
@ -182,7 +191,7 @@ def strip_dbus_error_prefix(message):
return message return message
def transform_size(size, transform) -> tuple[int, int]: def transform_size(size: Dimension, transform) -> Dimension:
match transform: match transform:
case ( case (
Transform.NORMAL Transform.NORMAL
@ -198,14 +207,14 @@ def transform_size(size, transform) -> tuple[int, int]:
| Transform.ROTATE_270_FLIPPED | Transform.ROTATE_270_FLIPPED
): ):
width, height = size width, height = size
return (height, width) return Dimension(height, width)
case _: case _:
raise NotImplementedError raise NotImplementedError
def scale_size(size, scale) -> tuple[int, int]: def scale_size(size: Dimension, scale) -> Dimension:
width, height = size width, height = size
return (round(width / scale), round(height / scale)) return Dimension(round(width / scale), round(height / scale))
class DisplayConfig: class DisplayConfig:
@ -266,11 +275,10 @@ class DisplayConfig:
@dataclass @dataclass
class MonitorMode: class MonitorMode:
name: str name: str
resolution: tuple[int, int] resolution: Dimension
refresh_rate: float refresh_rate: float
preferred_scale: float preferred_scale: float
supported_scales: list[float] supported_scales: list[float]
refresh_rate: float
properties: dict properties: dict
@classmethod @classmethod
@ -341,11 +349,11 @@ class Monitor:
class LogicalMonitor: class LogicalMonitor:
monitors: list[Monitor] monitors: list[Monitor]
scale: float scale: float
position: tuple[int, int] | None = (0, 0) position: Position = Position(0, 0)
transform: Transform = Transform.NORMAL transform: Transform = Transform.NORMAL
is_primary: bool = False is_primary: bool = False
properties: dict = field(default_factory=dict) properties: dict[str, Any] = field(default_factory=dict)
args: dict | None = None args: dict[str, Any] = field(default_factory=dict)
@classmethod @classmethod
def from_variant(cls, monitors_state, variant): def from_variant(cls, monitors_state, variant):
@ -434,7 +442,7 @@ def place_right_of(
else: else:
y = None y = None
logical_monitor.position = (x, y) logical_monitor.position = Position(x, y)
def place_left_of( def place_left_of(
@ -459,7 +467,7 @@ def place_left_of(
else: else:
y = None y = None
logical_monitor.position = (x, y) logical_monitor.position = Position(x, y)
def place_below( def place_below(
@ -479,9 +487,9 @@ def place_below(
if set_x_position: if set_x_position:
x, _ = connector_logical_monitor.position x, _ = connector_logical_monitor.position
else: else:
x = logical_monitor.position[0] x = logical_monitor.position.x
logical_monitor.position = (x, y) logical_monitor.position = Position(x, y)
def place_above( def place_above(
@ -504,9 +512,9 @@ def place_above(
if set_x_position: if set_x_position:
x, _ = connector_logical_monitor.position x, _ = connector_logical_monitor.position
else: else:
x = logical_monitor.position[0] x = logical_monitor.position.x
logical_monitor.position = (x, y) logical_monitor.position = Position(x, y)
class PositionType(Flag): class PositionType(Flag):
@ -536,13 +544,13 @@ def calculate_position(
set_y_position = vertical_args == 0 set_y_position = vertical_args == 0
x = None
y = None
if "x" in logical_monitor.args: if "x" in logical_monitor.args:
x = int(logical_monitor.args["x"]) x = int(logical_monitor.args["x"])
if set_y_position: y = 0 if set_y_position else None
y = 0 logical_monitor.position = Position(x, y)
else:
y = None
logical_monitor.position = (x, y)
position_types |= PositionType.ABSOLUTE_X position_types |= PositionType.ABSOLUTE_X
elif "right_of" in logical_monitor.args: elif "right_of" in logical_monitor.args:
connector = logical_monitor.args["right_of"] connector = logical_monitor.args["right_of"]
@ -573,17 +581,14 @@ def calculate_position(
) )
position_types |= PositionType.RELATIVE_X position_types |= PositionType.RELATIVE_X
else: else:
logical_monitor.position = (0, 0) logical_monitor.position = Position(0, 0)
set_x_position = horizontal_args == 0 set_x_position = horizontal_args == 0
if "y" in logical_monitor.args: if "y" in logical_monitor.args:
y = int(logical_monitor.args["y"]) y = int(logical_monitor.args["y"])
if set_x_position: x = 0 if set_x_position else logical_monitor.position.x
x = 0 logical_monitor.position = Position(x, y)
else:
x = logical_monitor.position[0]
logical_monitor.position = (x, y)
position_types |= PositionType.ABSOLUTE_Y position_types |= PositionType.ABSOLUTE_Y
elif "below" in logical_monitor.args: elif "below" in logical_monitor.args:
connector = logical_monitor.args["below"] connector = logical_monitor.args["below"]
@ -613,17 +618,19 @@ def calculate_position(
x, y = logical_monitor.position x, y = logical_monitor.position
if not y: if not y:
y = 0 y = 0
logical_monitor.position = (x, y) logical_monitor.position = Position(x, y)
assert logical_monitor.position[0] is not None assert logical_monitor.position.x is not None
assert logical_monitor.position[1] is not None assert logical_monitor.position.y is not None
return position_types return position_types
def align_horizontally(logical_monitors: list[LogicalMonitor]): def align_horizontally(logical_monitors: list[LogicalMonitor]):
min_x = min( min_x = min(
logical_monitor.position[0] for logical_monitor in logical_monitors logical_monitor.position.x
for logical_monitor in logical_monitors
if logical_monitor.position.x is not None
) )
dx = min_x dx = min_x
@ -632,12 +639,16 @@ def align_horizontally(logical_monitors: list[LogicalMonitor]):
for logical_monitor in logical_monitors: for logical_monitor in logical_monitors:
x, y = logical_monitor.position x, y = logical_monitor.position
logical_monitor.position = (x - dx, y) logical_monitor.position = Position(
x - dx if x is not None else None, y
)
def align_vertically(logical_monitors: list[LogicalMonitor]): def align_vertically(logical_monitors: list[LogicalMonitor]):
min_y = min( min_y = min(
logical_monitor.position[1] for logical_monitor in logical_monitors logical_monitor.position.y
for logical_monitor in logical_monitors
if logical_monitor.position.y is not None
) )
dy = min_y dy = min_y
@ -646,7 +657,9 @@ def align_vertically(logical_monitors: list[LogicalMonitor]):
for logical_monitor in logical_monitors: for logical_monitor in logical_monitors:
x, y = logical_monitor.position x, y = logical_monitor.position
logical_monitor.position = (x, y - dy) logical_monitor.position = Position(
x, y - dy if y is not None else None
)
def calculate_positions( def calculate_positions(
@ -1324,7 +1337,7 @@ if __name__ == "__main__":
choices=[str(layout_mode) for layout_mode in list(LayoutMode)], choices=[str(layout_mode) for layout_mode in list(LayoutMode)],
type=str, type=str,
action=AppendToGlobal, action=AppendToGlobal,
).completer = LayoutModeCompleter() ).completer = LayoutModeCompleter() # type: ignore[attr-defined]
set_parser.add_argument( set_parser.add_argument(
"-L", "-L",
"--logical-monitor", "--logical-monitor",
@ -1346,7 +1359,7 @@ if __name__ == "__main__":
action=SubGroupAction, action=SubGroupAction,
nargs=1, nargs=1,
help="Configure monitor", help="Configure monitor",
).completer = MonitorCompleter() ).completer = MonitorCompleter() # type: ignore[attr-defined]
monitor_parser = set_parser.add_argument_group( monitor_parser = set_parser.add_argument_group(
"monitor", "monitor",
"Monitor options (pass after --monitor)", "Monitor options (pass after --monitor)",
@ -1358,7 +1371,7 @@ if __name__ == "__main__":
action=AppendToSubGroup, action=AppendToSubGroup,
help="Monitor mode", help="Monitor mode",
type=str, type=str,
).completer = MonitorModeCompleter() ).completer = MonitorModeCompleter() # type: ignore[attr-defined]
monitor_parser.add_argument( monitor_parser.add_argument(
"--color-mode", "--color-mode",
"-c", "-c",
@ -1366,7 +1379,7 @@ if __name__ == "__main__":
help="Color mode", help="Color mode",
choices=[str(color_mode) for color_mode in list(ColorMode)], choices=[str(color_mode) for color_mode in list(ColorMode)],
type=str, type=str,
).completer = ColorModeCompleter() ).completer = ColorModeCompleter() # type: ignore[attr-defined]
logical_monitor_parser.add_argument( logical_monitor_parser.add_argument(
"--primary", "--primary",
"-p", "-p",
@ -1382,7 +1395,7 @@ if __name__ == "__main__":
action=AppendToGroup, action=AppendToGroup,
help="Logical monitor scale", help="Logical monitor scale",
type=float, type=float,
).completer = ScaleCompleter() ).completer = ScaleCompleter() # type: ignore[attr-defined]
logical_monitor_parser.add_argument( logical_monitor_parser.add_argument(
"--transform", "--transform",
"-t", "-t",
@ -1390,7 +1403,7 @@ if __name__ == "__main__":
help="Apply viewport transform", help="Apply viewport transform",
choices=[str(transform) for transform in list(Transform)], choices=[str(transform) for transform in list(Transform)],
type=str, type=str,
).completer = TransformCompleter() ).completer = TransformCompleter() # type: ignore[attr-defined]
logical_monitor_parser.add_argument( logical_monitor_parser.add_argument(
"--x", "--x",
"-x", "-x",
@ -1411,28 +1424,28 @@ if __name__ == "__main__":
metavar="CONNECTOR", metavar="CONNECTOR",
help="Place right of other monitor", help="Place right of other monitor",
type=str, type=str,
).completer = MonitorCompleter() ).completer = MonitorCompleter() # type: ignore[attr-defined]
logical_monitor_parser.add_argument( logical_monitor_parser.add_argument(
"--left-of", "--left-of",
action=AppendToGroup, action=AppendToGroup,
metavar="CONNECTOR", metavar="CONNECTOR",
help="Place left of other monitor", help="Place left of other monitor",
type=str, type=str,
).completer = MonitorCompleter() ).completer = MonitorCompleter() # type: ignore[attr-defined]
logical_monitor_parser.add_argument( logical_monitor_parser.add_argument(
"--above", "--above",
action=AppendToGroup, action=AppendToGroup,
metavar="CONNECTOR", metavar="CONNECTOR",
help="Place above other monitor", help="Place above other monitor",
type=str, type=str,
).completer = MonitorCompleter() ).completer = MonitorCompleter() # type: ignore[attr-defined]
logical_monitor_parser.add_argument( logical_monitor_parser.add_argument(
"--below", "--below",
action=AppendToGroup, action=AppendToGroup,
metavar="CONNECTOR", metavar="CONNECTOR",
help="Place below other monitor", help="Place below other monitor",
type=str, type=str,
).completer = MonitorCompleter() ).completer = MonitorCompleter() # type: ignore[attr-defined]
for action in [ for action in [
GroupAction, GroupAction,
@ -1442,7 +1455,7 @@ if __name__ == "__main__":
AppendToGlobal, AppendToGlobal,
]: ]:
argcomplete.safe_actions.add(action) argcomplete.safe_actions.add(action)
argcomplete.autocomplete(parser, default_completer=SuppressCompleter) argcomplete.autocomplete(parser, default_completer=SuppressCompleter) # type: ignore[arg-type]
args = parser.parse_args() args = parser.parse_args()