Skip to content

Commit

Permalink
Merge pull request #5470 from jexposit/xrandr-removal
Browse files Browse the repository at this point in the history
xrandr and xrdb removal
  • Loading branch information
M4rtinK committed Jun 6, 2024
2 parents 6b4d0ad + 846dbde commit 57bb6f8
Show file tree
Hide file tree
Showing 5 changed files with 193 additions and 23 deletions.
2 changes: 0 additions & 2 deletions anaconda.spec.in
Original file line number Diff line number Diff line change
Expand Up @@ -253,8 +253,6 @@ Requires: createrepo_c
# Display stuff moved from lorax templates
Requires: xorg-x11-drivers
Requires: xorg-x11-server-Xorg
Requires: xrandr
Requires: xrdb
Requires: dbus-x11
Requires: gsettings-desktop-schemas
Requires: nm-connection-editor
Expand Down
3 changes: 3 additions & 0 deletions pyanaconda/core/regexes.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,3 +197,6 @@

# Name of initramfs connection created by NM based on MAC
NM_MAC_INITRAMFS_CONNECTION = re.compile(r'^([0-9A-Fa-f]{2}:){5}([0-9A-Fa-f]{2})$')

# Screen resolution format for the boot option "inst.resolution"
SCREEN_RESOLUTION_CONFIG = re.compile(r'^[0-9]+x[0-9]+$')
35 changes: 14 additions & 21 deletions pyanaconda/display.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import pkgutil
import signal

from pyanaconda.mutter_display import MutterDisplay, MutterConfigError
from pyanaconda.core.configuration.anaconda import conf
from pyanaconda.core.path import join_paths
from pyanaconda.core.process_watchers import WatchProcesses
Expand Down Expand Up @@ -218,31 +219,17 @@ def x11_preexec():
WatchProcesses.watch_process(childproc, "gnome-kiosk")


def set_x_resolution(runres):
"""Set X server screen resolution.
def set_resolution(runres):
"""Set the screen resolution.
:param str runres: a resolution specification string
"""
try:
log.info("Setting the screen resolution to: %s.", runres)
util.execWithRedirect("xrandr", ["-d", ":1", "-s", runres])
except RuntimeError:
log.error("The X resolution was not set")
util.execWithRedirect("xrandr", ["-d", ":1", "-q"])


def do_extra_x11_actions(runres, gui_mode):
"""Perform X11 actions not related to startup.
:param str runres: a resolution specification string
:param gui_mode: an Anaconda display mode
"""
if runres and gui_mode and not flags.usevnc:
set_x_resolution(runres)

# Load the system-wide Xresources
util.execWithRedirect("xrdb", ["-nocpp", "-merge", "/etc/X11/Xresources"])
start_spice_vd_agent()
mutter_display = MutterDisplay()
mutter_display.set_resolution(runres)
except MutterConfigError as error:
log.error("The resolution was not set: %s", error)


def write_xdriver(driver, root=None):
Expand Down Expand Up @@ -390,7 +377,13 @@ def setup_display(anaconda, options):
time.sleep(2)

if not anaconda.gui_startup_failed:
do_extra_x11_actions(options.runres, gui_mode=anaconda.gui_mode)
if options.runres and anaconda.gui_mode and not flags.usevnc:
def on_mutter_ready(observer):
set_resolution(options.runres)
observer.disconnect()

mutter_display = MutterDisplay()
mutter_display.on_service_ready(on_mutter_ready)

if anaconda.tui_mode and anaconda.gui_startup_failed and flags.vncquestion and not anaconda.ksdata.vnc.enabled:
message = _("X was unable to start on your machine. Would you like to start VNC to connect to "
Expand Down
5 changes: 5 additions & 0 deletions pyanaconda/modules/common/constants/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,8 @@
namespace=("org", "gnome", "Kiosk"),
message_bus=SessionBus
)

MUTTER_DISPLAY_CONFIG = DBusServiceIdentifier(
namespace=("org", "gnome", "Mutter", "DisplayConfig"),
message_bus=SessionBus
)
171 changes: 171 additions & 0 deletions pyanaconda/mutter_display.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
#
# 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))

0 comments on commit 57bb6f8

Please sign in to comment.