172 lines
5.9 KiB
Python
172 lines
5.9 KiB
Python
|
#
|
||
|
# Copyright (C) 2024 Red Hat, Inc.
|
||
|
#
|
||
|
# This copyrighted material is made available to anyone wishing to use,
|
||
|
# modify, copy, or redistribute it subject to the terms and conditions of
|
||
|
# the GNU General Public License v.2, or (at your option) any later version.
|
||
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||
|
# ANY WARRANTY expressed or implied, including the implied warranties of
|
||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||
|
# Public License for more details. You should have received a copy of the
|
||
|
# GNU General Public License along with this program; if not, write to the
|
||
|
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||
|
# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
|
||
|
# source code or documentation are not subject to the GNU General Public
|
||
|
# License and may only be used or replicated with the express permission of
|
||
|
# Red Hat, Inc.
|
||
|
#
|
||
|
|
||
|
from dasbus.client.observer import DBusObserver
|
||
|
from pyanaconda.core.dbus import SessionBus
|
||
|
from pyanaconda.modules.common.constants.services import MUTTER_DISPLAY_CONFIG
|
||
|
from pyanaconda.core.regexes import SCREEN_RESOLUTION_CONFIG
|
||
|
|
||
|
|
||
|
__all__ = ['MutterDisplay', 'MutterConfigError']
|
||
|
|
||
|
|
||
|
class MutterConfigError(Exception):
|
||
|
"""Exception class for mutter configuration related problems"""
|
||
|
pass
|
||
|
|
||
|
|
||
|
class MonitorId(object):
|
||
|
"""Collection of properties that identify a unique monitor."""
|
||
|
|
||
|
def __init__(self, props):
|
||
|
self.connector = props[0]
|
||
|
self.vendor = props[1]
|
||
|
self.product = props[2]
|
||
|
self.serial = props[3]
|
||
|
|
||
|
def __eq__(self, other):
|
||
|
return self.connector == other.connector and \
|
||
|
self.vendor == other.vendor and \
|
||
|
self.product == other.product and \
|
||
|
self.serial == other.serial
|
||
|
|
||
|
|
||
|
class MonitorMode(object):
|
||
|
"""Available modes for a monitor."""
|
||
|
|
||
|
def __init__(self, props):
|
||
|
self.id = props[0]
|
||
|
self.width = props[1]
|
||
|
self.height = props[2]
|
||
|
self.refresh_rate = props[3]
|
||
|
self.preferred_scale = props[4]
|
||
|
self.supported_scales = props[5]
|
||
|
self.properties = props[6]
|
||
|
|
||
|
|
||
|
class Monitor(object):
|
||
|
"""Represent a connected physical monitor."""
|
||
|
|
||
|
def __init__(self, props):
|
||
|
self.id = MonitorId(props[0])
|
||
|
self.modes = list(map(MonitorMode, props[1]))
|
||
|
self.properties = props[2]
|
||
|
|
||
|
|
||
|
class LogicalMonitor(object):
|
||
|
"""Represent the current logical monitor configuration"""
|
||
|
|
||
|
def __init__(self, props):
|
||
|
self.x = props[0]
|
||
|
self.y = props[1]
|
||
|
self.scale = props[2]
|
||
|
self.transform = props[3]
|
||
|
self.primary = props[4]
|
||
|
self.monitor_ids = list(map(MonitorId, props[5]))
|
||
|
self.properties = props[6]
|
||
|
|
||
|
|
||
|
class LogicalMonitorConfig(object):
|
||
|
"""Logical monitor configuration object"""
|
||
|
|
||
|
def __init__(self, logical_monitor, monitors, x, y, width, height):
|
||
|
"""Creates a LogicalMonitorConfig setting the given resolution if available."""
|
||
|
self._logical_monitor = logical_monitor
|
||
|
self._monitors = monitors
|
||
|
|
||
|
self.x = x
|
||
|
self.y = y
|
||
|
self.scale = logical_monitor.scale
|
||
|
self.transform = logical_monitor.transform
|
||
|
self.primary = logical_monitor.primary
|
||
|
|
||
|
self.monitors = list()
|
||
|
for monitor_id in logical_monitor.monitor_ids:
|
||
|
connector = monitor_id.connector
|
||
|
mode_id = self._get_matching_monitor_mode_id(monitors, monitor_id, width, height)
|
||
|
self.monitors.append((connector, mode_id, {}))
|
||
|
|
||
|
def _get_matching_monitor_mode_id(self, monitors, monitor_id, width, height):
|
||
|
monitor = next(filter(lambda m: m.id == monitor_id, monitors))
|
||
|
for mode in monitor.modes:
|
||
|
if mode.width == width and mode.height == height:
|
||
|
return mode.id
|
||
|
|
||
|
raise MutterConfigError('Monitor mode with selected resolution not found')
|
||
|
|
||
|
def to_dbus(self):
|
||
|
return (
|
||
|
self.x,
|
||
|
self.y,
|
||
|
self.scale,
|
||
|
self.transform,
|
||
|
self.primary,
|
||
|
self.monitors,
|
||
|
)
|
||
|
|
||
|
|
||
|
class MutterDisplay(object):
|
||
|
"""Class wrapping Mutter's display configuration API."""
|
||
|
|
||
|
def __init__(self):
|
||
|
self._proxy = MUTTER_DISPLAY_CONFIG.get_proxy()
|
||
|
|
||
|
def on_service_ready(self, callback):
|
||
|
observer = DBusObserver(SessionBus, 'org.gnome.Kiosk')
|
||
|
observer.service_available.connect(callback)
|
||
|
observer.connect_once_available()
|
||
|
|
||
|
def set_resolution(self, res_str):
|
||
|
"""Changes the screen resolution.
|
||
|
|
||
|
:param res_str: Screen resolution configuration with format "800x600".
|
||
|
:raises MutterConfigError on failure.
|
||
|
"""
|
||
|
if not self._proxy.ApplyMonitorsConfigAllowed:
|
||
|
raise MutterConfigError('Monitor configuration is not allowed')
|
||
|
|
||
|
(width, height) = self._parse_resolution_str(res_str)
|
||
|
(serial, monitor_props, logical_monitor_props, _) = self._proxy.GetCurrentState()
|
||
|
|
||
|
# Configuration method as described in org.gnome.Mutter.DisplayConfig.xml:
|
||
|
# 0: verify
|
||
|
# 1: temporary
|
||
|
# 2: persistent
|
||
|
persistent_config = 2
|
||
|
|
||
|
monitors = list(map(Monitor, monitor_props))
|
||
|
logical_monitors = list(map(LogicalMonitor, logical_monitor_props))
|
||
|
|
||
|
# Align the monitors in a row starting at X coordinate 0
|
||
|
x = 0
|
||
|
|
||
|
configs = list()
|
||
|
for logical_monitor in logical_monitors:
|
||
|
config = LogicalMonitorConfig(logical_monitor, monitors, x, 0, width, height)
|
||
|
x += width
|
||
|
configs.append(config.to_dbus())
|
||
|
|
||
|
self._proxy.ApplyMonitorsConfig(serial, persistent_config, configs, {})
|
||
|
|
||
|
def _parse_resolution_str(self, res_str):
|
||
|
if not SCREEN_RESOLUTION_CONFIG.match(res_str):
|
||
|
raise MutterConfigError('Invalid configuration resolution')
|
||
|
|
||
|
[width, height] = res_str.split('x')
|
||
|
return (int(width, 10), int(height, 10))
|