diff --git a/anaconda.py b/anaconda.py
index 7a61f9f66f2..a0ace4b6254 100755
--- a/anaconda.py
+++ b/anaconda.py
@@ -144,10 +144,22 @@ def setup_environment():
if "LD_PRELOAD" in os.environ:
del os.environ["LD_PRELOAD"]
+ # Required for Wayland compositors
+ if os.path.isdir("/tmp/anaconda-xdgrundir"):
+ os.environ["XDG_RUNTIME_DIR"] = "/tmp/anaconda-xdgrundir"
+
+ # Go ahead and set $WAYLAND_DISPLAY whether we're going to use X or not
+ if "WAYLAND_DISPLAY" in os.environ:
+ flags.preexisting_wayland = True
+ else:
+ os.environ["WAYLAND_DISPLAY"] = constants.WAYLAND_SOCKET_NAME
+
# Go ahead and set $DISPLAY whether we're going to use X or not
+ # only if X11 is being used
+ from pyanaconda.core.kernel import kernel_arguments
if "DISPLAY" in os.environ:
flags.preexisting_x11 = True
- else:
+ elif "x11" in kernel_arguments and os.path.isfile("/usr/bin/Xorg"):
os.environ["DISPLAY"] = ":%s" % constants.X_DISPLAY_NUMBER
# We mostly don't run from bash, so it won't load the file for us, and libreport will then
@@ -296,10 +308,11 @@ def setup_environment():
except pid.PidFileError as e:
log.error("Unable to create %s, exiting", pidfile.filename)
- # If we had a $DISPLAY at start and zenity is available, we may be
- # running in a live environment and we can display an error dialog.
+ # If we had a Wayland/X11 display at start and zenity is available,
+ # we may be running in a live environment and we can display an error dialog.
# Otherwise just print an error.
- if flags.preexisting_x11 and os.access("/usr/bin/zenity", os.X_OK):
+ preexisting_graphics = flags.preexisting_wayland or flags.preexisting_x11
+ if preexisting_graphics and os.access("/usr/bin/zenity", os.X_OK):
# The module-level _() calls are ok here because the language may
# be set from the live environment in this case, and anaconda's
# language setup hasn't happened yet.
diff --git a/anaconda.spec.in b/anaconda.spec.in
index 3246bd59b66..458bfd7ecb6 100644
--- a/anaconda.spec.in
+++ b/anaconda.spec.in
@@ -248,15 +248,20 @@ Requires: zram-generator
# needed for proper driver disk support - if RPMs must be installed, a repo is needed
Requires: createrepo_c
# Display stuff moved from lorax templates
+## For Wayland
+Requires: weston
+Requires: xorg-x11-server-Xwayland
+## For X11
Requires: xorg-x11-drivers
Requires: xorg-x11-server-Xorg
Requires: xrandr
+Requires: gnome-kiosk
+## Common stuff
Requires: xrdb
Requires: dbus-x11
Requires: gsettings-desktop-schemas
Requires: nm-connection-editor
Requires: librsvg2
-Requires: gnome-kiosk
Requires: brltty
# dependencies for rpm-ostree payload module
Requires: rpm-ostree >= %{rpmostreever}
@@ -394,6 +399,7 @@ rm -rf \
%{_sbindir}/anaconda
%{_sbindir}/handle-sshpw
%{_datadir}/anaconda
+%{_sysconfdir}/pam.d/anaconda
%{_prefix}/libexec/anaconda
%exclude %{_datadir}/anaconda/gnome
%exclude %{_datadir}/anaconda/pixmaps
diff --git a/configure.ac b/configure.ac
index 070a345329d..e4b0395b517 100644
--- a/configure.ac
+++ b/configure.ac
@@ -111,6 +111,7 @@ AC_CONFIG_FILES([Makefile
data/liveinst/gnome/Makefile
data/systemd/Makefile
data/dbus/Makefile
+ data/pam/Makefile
data/window-manager/Makefile
data/window-manager/config/Makefile
po/Makefile
diff --git a/data/Makefile.am b/data/Makefile.am
index df3974fb760..5406e9d4f95 100644
--- a/data/Makefile.am
+++ b/data/Makefile.am
@@ -15,7 +15,7 @@
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see .
-SUBDIRS = command-stubs liveinst systemd pixmaps window-manager dbus conf.d profile.d
+SUBDIRS = command-stubs liveinst systemd pixmaps window-manager dbus conf.d profile.d pam
CLEANFILES = *~
diff --git a/data/pam/Makefile.am b/data/pam/Makefile.am
new file mode 100644
index 00000000000..97e6657be15
--- /dev/null
+++ b/data/pam/Makefile.am
@@ -0,0 +1,21 @@
+# Copyright (C) 2024 Neal Gompa.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see .
+
+CLEANFILES = *~
+
+pamdir = $(sysconfdir)/pam.d
+dist_pam_DATA = anaconda
+
+MAINTAINERCLEANFILES = Makefile.in
diff --git a/data/pam/anaconda b/data/pam/anaconda
new file mode 100644
index 00000000000..4e6d91adf2c
--- /dev/null
+++ b/data/pam/anaconda
@@ -0,0 +1,8 @@
+#%PAM-1.0
+auth sufficient pam_permit.so
+account sufficient pam_permit.so
+password sufficient pam_permit.so
+session required pam_loginuid.so
+-session optional pam_keyinit.so revoke
+-session optional pam_limits.so
+session required pam_systemd.so
diff --git a/data/systemd/anaconda.service b/data/systemd/anaconda.service
index a80c6bb7075..115045ec9ec 100644
--- a/data/systemd/anaconda.service
+++ b/data/systemd/anaconda.service
@@ -5,6 +5,15 @@ Wants=anaconda-noshell.service
[Service]
Type=forking
-Environment=HOME=/root MALLOC_CHECK_=2 MALLOC_PERTURB_=204 PATH=/usr/bin:/bin:/sbin:/usr/sbin:/mnt/sysimage/bin:/mnt/sysimage/usr/bin:/mnt/sysimage/usr/sbin:/mnt/sysimage/sbin LANG=en_US.UTF-8 GDK_BACKEND=x11 XDG_RUNTIME_DIR=/tmp GIO_USE_VFS=local
+Environment=HOME=/root MALLOC_CHECK_=2 MALLOC_PERTURB_=204 PATH=/usr/bin:/bin:/sbin:/usr/sbin:/mnt/sysimage/bin:/mnt/sysimage/usr/bin:/mnt/sysimage/usr/sbin:/mnt/sysimage/sbin LANG=en_US.UTF-8 GDK_BACKEND=x11 XDG_RUNTIME_DIR=/tmp/anaconda-xdgrundir GIO_USE_VFS=local
WorkingDirectory=/root
+User=root
+Group=root
+ExecStartPre=/usr/bin/mkdir -p /tmp/anaconda-xdgrundir
+ExecStartPre=/usr/bin/chmod 700 /tmp/anaconda-xdgrundir
ExecStart=/usr/bin/tmux -u -f /usr/share/anaconda/tmux.conf start
+PAMName=anaconda
+TTYPath=/dev/tty6
+TTYReset=yes
+TTYVHangup=yes
+TTYVTDisallocate=yes
diff --git a/dockerfile/anaconda-iso-creator/Dockerfile b/dockerfile/anaconda-iso-creator/Dockerfile
index 98e32b9b7a0..27bc24ad800 100644
--- a/dockerfile/anaconda-iso-creator/Dockerfile
+++ b/dockerfile/anaconda-iso-creator/Dockerfile
@@ -43,6 +43,7 @@ RUN set -ex; \
COPY ["lorax-build", "/"]
COPY ["lorax-build-webui", "/"]
COPY ["adjust-templates-for-webui.patch", "/"]
+COPY ["adjust-templates-for-weston.patch", "/"]
RUN mkdir /lorax /anaconda-rpms /images
diff --git a/dockerfile/anaconda-iso-creator/adjust-templates-for-weston.patch b/dockerfile/anaconda-iso-creator/adjust-templates-for-weston.patch
new file mode 100644
index 00000000000..0a0642bb9e6
--- /dev/null
+++ b/dockerfile/anaconda-iso-creator/adjust-templates-for-weston.patch
@@ -0,0 +1,56 @@
+From 3b6fcfc755ee9b2a5b4de7965ee2e3fc41045379 Mon Sep 17 00:00:00 2001
+From: Neal Gompa
+Date: Thu, 18 Jan 2024 11:59:00 -0500
+Subject: [PATCH] templates.d/99-generic/runtime-cleanup: Do not purge sound
+ packages
+
+These cause the build to fail when adding Weston to the boot.iso.
+---
+ share/templates.d/99-generic/runtime-cleanup.tmpl | 10 +---------
+ 1 file changed, 1 insertion(+), 9 deletions(-)
+
+diff --git a/share/templates.d/99-generic/runtime-cleanup.tmpl b/share/templates.d/99-generic/runtime-cleanup.tmpl
+index aad31f77..e4148b7c 100644
+--- a/share/templates.d/99-generic/runtime-cleanup.tmpl
++++ b/share/templates.d/99-generic/runtime-cleanup.tmpl
+@@ -9,8 +9,6 @@ remove usr/share/i18n
+ ## perl needed for powerpc-utils
+ ## perl is needed by /usr/bin/rxe_cfg from libibverbs
+
+-## no sound support, thanks
+-removepkg flac-libs libsndfile pipewire pulseaudio* rtkit sound-theme-freedesktop wireplumber*
+ ## we don't create new initramfs/bootloader conf inside anaconda
+ ## (that happens inside the target system after we install dracut/grubby)
+ removepkg dracut-network grubby anaconda-dracut
+@@ -52,7 +50,6 @@ removepkg mtools glibc-gconv-extra
+
+ ## various other things we remove to save space
+ removepkg diffutils file
+-removepkg libasyncns
+ removepkg lvm2-libs
+ removepkg mobile-broadband-provider-info
+ removepkg rmt rpcbind squashfs-tools
+@@ -196,7 +193,6 @@ removefrom libidn2 /usr/share/locale/*
+ removefrom libnotify /usr/bin/*
+ removefrom libsemanage /etc/selinux/*
+ removefrom libstdc++ /usr/share/*
+-removefrom libvorbis /usr/${libdir}/libvorbisenc.*
+ removefrom libxml2 /usr/bin/*
+ removefrom linux-firmware /usr/lib/firmware/dvb*
+ removefrom linux-firmware /usr/lib/firmware/*_12mhz*
+@@ -365,11 +361,7 @@ removefrom gstreamer1-plugins-base --allbut \
+ removepkg geoclue2
+
+ ## And remove the packages that those extra libraries pulled in
+-removepkg cdparanoia-libs opus libtheora libvisual flac-libs gsm avahi-glib avahi-libs \
+- ModemManager-glib
+-
+-## metacity requires libvorbis and libvorbisfile, but enc/dec are no longer needed
+-removefrom libvorbis --allbut /usr/${libdir}/libvorbisfile.* /usr/${libdir}/libvorbis.*
++removepkg cdparanoia-libs libvisual avahi-glib avahi-libs ModemManager-glib
+
+ ## Remove build-id links, they are used with debuginfo
+ remove /usr/lib/.build-id
+--
+2.43.0
+
diff --git a/dockerfile/anaconda-iso-creator/lorax-build b/dockerfile/anaconda-iso-creator/lorax-build
index 8deb0affb86..2bd251ea274 100755
--- a/dockerfile/anaconda-iso-creator/lorax-build
+++ b/dockerfile/anaconda-iso-creator/lorax-build
@@ -36,12 +36,16 @@ mkdir -p $REPO_DIR
cp -a $INPUT_RPMS/* $REPO_DIR || echo "RPM files can't be copied!" # We could just do the build with official repositories only
createrepo_c $REPO_DIR
+cp -r /usr/share/lorax/templates.d/ /lorax/
+patch -p2 -i /adjust-templates-for-weston.patch
+
# build boot.iso with our rpms
. /etc/os-release
# The download.fedoraproject.org automatic redirector often selects download-ib01.f.o. for GitHub's cloud, which is too unreliable; use a mirror
# The --volid argument can cause different network interface naming: https://github.com/rhinstaller/kickstart-tests/issues/448
lorax -p Fedora -v "$VERSION_ID" -r "$VERSION_ID" \
--volid Fedora-S-dvd-x86_64-rawh \
+ --sharedir ./templates.d/99-generic/ \
-s http://dl.fedoraproject.org/pub/fedora/linux/development/rawhide/Everything/x86_64/os/ \
-s file://$REPO_DIR/ \
"$@" \
diff --git a/dockerfile/anaconda-iso-creator/lorax-build.j2 b/dockerfile/anaconda-iso-creator/lorax-build.j2
index af7714558f9..a93f7adcb4d 100755
--- a/dockerfile/anaconda-iso-creator/lorax-build.j2
+++ b/dockerfile/anaconda-iso-creator/lorax-build.j2
@@ -29,6 +29,9 @@ mkdir -p $REPO_DIR
cp -a $INPUT_RPMS/* $REPO_DIR || echo "RPM files can't be copied!" # We could just do the build with official repositories only
createrepo_c $REPO_DIR
+cp -r /usr/share/lorax/templates.d/ /lorax/
+patch -p2 -i /adjust-templates-for-weston.patch
+
# build boot.iso with our rpms
. /etc/os-release
# The download.fedoraproject.org automatic redirector often selects download-ib01.f.o. for GitHub's cloud, which is too unreliable; use a mirror
@@ -39,6 +42,7 @@ lorax -p Fedora -v "$VERSION_ID" -r "$VERSION_ID" \
{% else %}
--volid Fedora-S-dvd-x86_64-f{$ distro_release $} \
{% endif %}
+ --sharedir ./templates.d/99-generic/ \
-s http://dl.fedoraproject.org/pub/fedora/linux/development/{$ distro_release $}/Everything/x86_64/os/ \
-s file://$REPO_DIR/ \
"$@" \
diff --git a/pyanaconda/core/constants.py b/pyanaconda/core/constants.py
index 40e9adf171b..8a470effc7e 100644
--- a/pyanaconda/core/constants.py
+++ b/pyanaconda/core/constants.py
@@ -261,6 +261,9 @@ class SecretStatus(Enum):
IPMI_ABORTED = 0x9 # installation finished unsuccessfully, due to some non-exn error
IPMI_FAILED = 0xA # installation hit an exception
+# Wayland socket name to use
+WAYLAND_SOCKET_NAME = "wl-sysinstall-0"
+
# X display number to use
X_DISPLAY_NUMBER = 1
@@ -315,6 +318,14 @@ class DisplayModes(Enum):
False: "noninteractive"
}
+# Weston configuration
+WESTON_CONFIG = {
+ "core": {
+ "shell": "kiosk",
+ "xwayland": "true"
+ }
+}
+
# Loggers
LOGGER_ANACONDA_ROOT = "anaconda"
LOGGER_MAIN = "anaconda.main"
@@ -322,6 +333,12 @@ class DisplayModes(Enum):
LOGGER_PROGRAM = "program"
LOGGER_SIMPLELINE = "simpleline"
+# Wayland display vars file
+WAYLAND_DISPLAY_VARS_FILE = "/tmp/anaconda-wayland-display-vars"
+
+# Timeout for starting Wayland
+WAYLAND_TIMEOUT = 60
+
# Timeout for starting X
X_TIMEOUT = 60
diff --git a/pyanaconda/core/util.py b/pyanaconda/core/util.py
index 8005a52eb3c..67c4c8a7031 100644
--- a/pyanaconda/core/util.py
+++ b/pyanaconda/core/util.py
@@ -18,13 +18,17 @@
# along with this program. If not, see .
#
+import configparser
import os
import os.path
+import pathlib
import subprocess
# Used for ascii_lowercase, ascii_uppercase constants
import tempfile
+import time
import re
import signal
+import stat
import sys
import types
import inspect
@@ -39,8 +43,9 @@
from pyanaconda.core.configuration.anaconda import conf
from pyanaconda.core.path import make_directories, open_with_perm, join_paths
from pyanaconda.core.process_watchers import WatchProcesses
-from pyanaconda.core.constants import DRACUT_SHUTDOWN_EJECT, \
- IPMI_ABORTED, X_TIMEOUT, PACKAGES_LIST_FILE
+from pyanaconda.core.constants import DRACUT_SHUTDOWN_EJECT, IPMI_ABORTED, \
+ WAYLAND_SOCKET_NAME, WAYLAND_DISPLAY_VARS_FILE, WAYLAND_TIMEOUT, \
+ WESTON_CONFIG, X_TIMEOUT, PACKAGES_LIST_FILE
from pyanaconda.core.live_user import get_live_user
from pyanaconda.errors import RemovedModuleError
@@ -165,6 +170,92 @@ def preexec():
return partsubp(preexec_fn=preexec)
+class WaylandStatus:
+ """Status of Wayland launch.
+
+ Values of an instance can be modified from the handler functions.
+ """
+ def __init__(self):
+ self.started = False
+ self.timed_out = False
+
+ def needs_waiting(self):
+ return not (self.started or self.timed_out)
+
+
+def startWl(weston_config=WESTON_CONFIG, output_redirect=None, timeout=WAYLAND_TIMEOUT):
+ """ Start Weston for Wayland and return once Weston is ready to accept connections.
+
+ We can identify whether Weston is ready by testing if
+ the Wayland socket is open yet. Once it is, we can return success.
+
+ :param weston_config: The weston.ini(5) configuration to use, as a dictionary
+ :param output_redirect: file or file descriptor to redirect stdout and stderr to
+ :param timeout: Number of seconds to timing out.
+ """
+ wl_status = WaylandStatus()
+
+ # Create the wayland vars getenv script file for Weston
+ wl_getenv_script_file = tempfile.NamedTemporaryFile(mode="w",
+ suffix="-wl-weston-getenv-sh",
+ delete=False)
+ wl_getenv_script = f"""#!/bin/sh
+ rm -f {WAYLAND_DISPLAY_VARS_FILE}
+ echo "[wayland_vars]" >> {WAYLAND_DISPLAY_VARS_FILE}
+ echo "WAYLAND_DISPLAY=$WAYLAND_DISPLAY" >> {WAYLAND_DISPLAY_VARS_FILE}
+ echo "DISPLAY=$DISPLAY" >> {WAYLAND_DISPLAY_VARS_FILE}
+ exit 0
+ """
+ wl_getenv_script_file.write(wl_getenv_script)
+ wl_getenv_script_file.close()
+
+ weston_autolaunch_config = {"autolaunch": {"path": wl_getenv_script_file.name}}
+
+ os.chmod(wl_getenv_script_file.name,
+ os.stat(wl_getenv_script_file.name).st_mode | stat.S_IEXEC)
+
+ # Create the config file for Weston
+ weston_config_file = tempfile.NamedTemporaryFile(mode="w",
+ suffix="-wl-weston-sysinstall-ini",
+ delete=False)
+ weston_config_ini = configparser.ConfigParser()
+ for section, options in (weston_config | weston_autolaunch_config).items():
+ weston_config_ini.add_section(section)
+ for key, value in options.items():
+ weston_config_ini.set(section, key, str(value))
+ weston_config_ini.write(weston_config_file, space_around_delimiters=False)
+ weston_config_file.close()
+
+ # Determine whether to use drm or vnc backend
+ weston_backend = "drm"
+ if "vnc" in weston_config:
+ weston_backend = "vnc"
+
+ log.debug("Starting Weston.")
+ argv = ["weston", f"--backend={weston_backend}",
+ f"--config={weston_config_file.name}", "--log=/tmp/weston.log",
+ f"--socket={WAYLAND_SOCKET_NAME}"]
+
+ childproc = startProgram(argv, stdout=output_redirect, stderr=output_redirect)
+ WatchProcesses.watch_process(childproc, argv[0])
+
+ for _ in range(0, timeout):
+ try:
+ xdg_runtime_dir = os.getenv("XDG_RUNTIME_DIR")
+ pathlib.Path(xdg_runtime_dir, WAYLAND_SOCKET_NAME).resolve(strict=True)
+ wl_status.started = True
+ return wl_status.started
+ except Exception:
+ if wl_status.needs_waiting():
+ time.sleep(1)
+ wl_status.timed_out = True
+ WatchProcesses.unwatch_process(childproc)
+ childproc.terminate()
+ log.debug("Exception handler test suspended to prevent accidental activation by "
+ "delayed Weston start.")
+ raise TimeoutError("Timeout trying to start %s" % argv[0])
+
+
class X11Status:
"""Status of Xorg launch.
diff --git a/pyanaconda/display.py b/pyanaconda/display.py
index 8e5527161e2..3b1e5917251 100644
--- a/pyanaconda/display.py
+++ b/pyanaconda/display.py
@@ -1,8 +1,7 @@
#
# display.py: graphical display setup for the Anaconda GUI
#
-# Copyright (C) 2016
-# Red Hat, Inc. All rights reserved.
+# Copyright (C) 2024 Neal Gompa. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -17,233 +16,15 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-# Author(s): Martin Kolman
+# Author(s): Neal Gompa
#
-import os
-import subprocess
-import time
-import textwrap
-import pkgutil
-import signal
-from pyanaconda.core.configuration.anaconda import conf
-from pyanaconda.core.process_watchers import WatchProcesses
-from pyanaconda import startup_utils
-from pyanaconda.core import util, constants, hw
-from pyanaconda import vnc
-from pyanaconda.core.i18n import _
-from pyanaconda.flags import flags
-from pyanaconda.modules.common.constants.services import NETWORK
-from pyanaconda.ui.tui.spokes.askvnc import AskVNCSpoke
-from pyanaconda.ui.tui import tui_quit_callback
-# needed for checking if the pyanaconda.ui.gui modules are available
-import pyanaconda.ui
-
-import blivet
-
-from pykickstart.constants import DISPLAY_MODE_TEXT
-
-from simpleline import App
-from simpleline.render.screen_handler import ScreenHandler
+from pyanaconda.core.kernel import kernel_arguments
from pyanaconda.anaconda_loggers import get_module_logger, get_stdout_logger
log = get_module_logger(__name__)
stdout_log = get_stdout_logger()
-X_TIMEOUT_ADVICE = \
- "Do not load the stage2 image over a slow network link.\n" \
- "Wait longer for the X server startup with the inst.xtimeout= boot option." \
- "The default is 60 seconds.\n" \
- "Load the stage2 image into memory with the rd.live.ram boot option to decrease access " \
- "time.\n" \
- "Enforce text mode when installing from remote media with the inst.text boot option."
-# on RHEL also: "Use the customer portal download URL in ilo/drac devices for greater speed."
-
-
-def start_user_systemd():
- """Start the user instance of systemd.
-
- The service org.a11y.Bus runs the dbus-broker-launch in
- the user scope that requires the user instance of systemd.
- """
- if not conf.system.can_start_user_systemd:
- log.debug("Don't start the user instance of systemd.")
- return
-
- childproc = util.startProgram(["/usr/lib/systemd/systemd", "--user"])
- WatchProcesses.watch_process(childproc, "systemd")
-
-
-# Spice
-
-def start_spice_vd_agent():
- """Start the spice vdagent.
-
- For certain features to work spice requires that the guest os
- is running the spice vdagent.
- """
- try:
- status = util.execWithRedirect("spice-vdagent", [])
- except OSError as e:
- log.warning("spice-vdagent failed: %s", e)
- return
-
- if status:
- log.info("spice-vdagent exited with status %d", status)
- else:
- log.info("Started spice-vdagent.")
-
-
-# VNC
-
-def ask_vnc_question(anaconda, vnc_server, message):
- """ Ask the user if TUI or GUI-over-VNC should be started.
-
- :param anaconda: instance of the Anaconda class
- :param vnc_server: instance of the VNC server object
- :param str message: a message to show to the user together
- with the question
- """
- App.initialize()
- loop = App.get_event_loop()
- loop.set_quit_callback(tui_quit_callback)
- spoke = AskVNCSpoke(anaconda.ksdata, message=message)
- ScreenHandler.schedule_screen(spoke)
- App.run()
-
- if anaconda.ksdata.vnc.enabled:
- if not anaconda.gui_mode:
- log.info("VNC requested via VNC question, switching Anaconda to GUI mode.")
- anaconda.display_mode = constants.DisplayModes.GUI
- flags.usevnc = True
- vnc_server.password = anaconda.ksdata.vnc.password
-
-
-def check_vnc_can_be_started(anaconda):
- """Check if we can start VNC in the current environment.
-
- :returns: if VNC can be started and list of possible reasons
- why VNC can't be started
- :rtype: (boot, list)
- """
-
- error_messages = []
- vnc_startup_possible = True
-
- # disable VNC over text question when not enough memory is available
- min_gui_ram = hw.minimal_memory_needed(with_gui=True)
- if blivet.util.total_memory() < min_gui_ram:
- error_messages.append("Not asking for VNC because current memory (%d) < MIN_GUI_RAM (%d)" %
- (blivet.util.total_memory(), min_gui_ram))
- vnc_startup_possible = False
-
- # disable VNC question if text mode is requested and this is a ks install
- if anaconda.tui_mode and flags.automatedInstall:
- error_messages.append("Not asking for VNC because of an automated install")
- vnc_startup_possible = False
-
- # disable VNC question if we were explicitly asked for text in kickstart
- if anaconda.ksdata.displaymode.displayMode == DISPLAY_MODE_TEXT:
- error_messages.append("Not asking for VNC because text mode was explicitly asked for in kickstart")
- vnc_startup_possible = False
-
- # disable VNC question if we don't have network
- network_proxy = NETWORK.get_proxy()
- if not network_proxy.IsConnecting() and not network_proxy.Connected:
- error_messages.append("Not asking for VNC because we don't have a network")
- vnc_startup_possible = False
-
- # disable VNC question if we don't have Xvnc
- if not os.access('/usr/bin/Xvnc', os.X_OK):
- error_messages.append("Not asking for VNC because we don't have Xvnc")
- vnc_startup_possible = False
-
- return vnc_startup_possible, error_messages
-
-
-# X11
-
-def start_x11(xtimeout):
- """Start the X server for the Anaconda GUI."""
-
- # Start Xorg and wait for it become ready
- util.startX(["Xorg", "-br", "-logfile", "/tmp/X.log",
- ":%s" % constants.X_DISPLAY_NUMBER, "vt6", "-s", "1440", "-ac",
- "-nolisten", "tcp", "-dpi", "96",
- "-noreset"],
- output_redirect=subprocess.DEVNULL, timeout=xtimeout)
-
-
-# function to handle X startup special issues for anaconda
-
-def do_startup_x11_actions():
- """Start the window manager.
-
- When window manager actually connects to the X server is unknowable, but
- fortunately it doesn't matter. Wm does not need to be the first
- connection to Xorg, and if anaconda starts up before wm, wm
- will just take over and maximize the window and make everything right,
- fingers crossed.
- Add XDG_DATA_DIRS to the environment to pull in our overridden schema
- files.
- """
- datadir = os.environ.get('ANACONDA_DATADIR', '/usr/share/anaconda')
- if 'XDG_DATA_DIRS' in os.environ:
- xdg_data_dirs = datadir + '/window-manager:' + os.environ['XDG_DATA_DIRS']
- else:
- xdg_data_dirs = datadir + '/window-manager:/usr/share'
-
- def x11_preexec():
- # to set GUI subprocess SIGINT handler
- signal.signal(signal.SIGINT, signal.SIG_IGN)
-
- childproc = util.startProgram(["gnome-kiosk", "--display", ":1", "--sm-disable", "--x11"],
- env_add={'XDG_DATA_DIRS': xdg_data_dirs},
- preexec_fn=x11_preexec)
- WatchProcesses.watch_process(childproc, "gnome-kiosk")
-
-
-def set_x_resolution(runres):
- """Set X server 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_user_systemd()
- start_spice_vd_agent()
-
-
-def write_xdriver(driver, root=None):
- """Write the X driver."""
- if root is None:
- root = conf.target.system_root
-
- if not os.path.isdir("%s/etc/X11" % (root,)):
- os.makedirs("%s/etc/X11" % (root,), mode=0o755)
-
- f = open("%s/etc/X11/xorg.conf" % (root,), 'w')
- f.write('Section "Device"\n\tIdentifier "Videocard0"\n\tDriver "%s"\nEndSection\n' % driver)
- f.close()
-
# general display startup
def setup_display(anaconda, options):
@@ -253,138 +34,9 @@ def setup_display(anaconda, options):
:param options: command line/boot options
"""
- try:
- xtimeout = int(options.xtimeout)
- except ValueError:
- log.warning("invalid inst.xtimeout option value: %s", options.xtimeout)
- xtimeout = constants.X_TIMEOUT
-
- vnc_server = vnc.VncServer() # The vnc Server object.
- vnc_server.anaconda = anaconda
- vnc_server.timeout = xtimeout
-
- anaconda.display_mode = options.display_mode
- anaconda.interactive_mode = not options.noninteractive
-
- if options.vnc:
- flags.usevnc = True
- if not anaconda.gui_mode:
- log.info("VNC requested via boot/CLI option, switching Anaconda to GUI mode.")
- anaconda.display_mode = constants.DisplayModes.GUI
- vnc_server.password = options.vncpassword
-
- # Only consider vncconnect when vnc is a param
- if options.vncconnect:
- cargs = options.vncconnect.split(":")
- vnc_server.vncconnecthost = cargs[0]
- if len(cargs) > 1 and len(cargs[1]) > 0:
- if len(cargs[1]) > 0:
- vnc_server.vncconnectport = cargs[1]
-
- if options.xdriver:
- write_xdriver(options.xdriver, root="/")
-
- if flags.rescue_mode:
- return
-
- if anaconda.ksdata.vnc.enabled:
- flags.usevnc = True
- if not anaconda.gui_mode:
- log.info("VNC requested via kickstart, switching Anaconda to GUI mode.")
- anaconda.display_mode = constants.DisplayModes.GUI
-
- if vnc_server.password == "":
- vnc_server.password = anaconda.ksdata.vnc.password
-
- if vnc_server.vncconnecthost == "":
- vnc_server.vncconnecthost = anaconda.ksdata.vnc.host
-
- if vnc_server.vncconnectport == "":
- vnc_server.vncconnectport = anaconda.ksdata.vnc.port
-
- # check if GUI without WebUI
- if anaconda.gui_mode and not anaconda.is_webui_supported:
- mods = (tup[1] for tup in pkgutil.iter_modules(pyanaconda.ui.__path__, "pyanaconda.ui."))
- if "pyanaconda.ui.gui" not in mods:
- stdout_log.warning("Graphical user interface not available, falling back to text mode")
- anaconda.display_mode = constants.DisplayModes.TUI
- flags.usevnc = False
- flags.vncquestion = False
-
- # check if VNC can be started
- vnc_can_be_started, vnc_error_messages = check_vnc_can_be_started(anaconda)
- if not vnc_can_be_started:
- # VNC can't be started - disable the VNC question and log
- # all the errors that prevented VNC from being started
- flags.vncquestion = False
- for error_message in vnc_error_messages:
- stdout_log.warning(error_message)
-
- # Should we try to start Xorg?
- want_x = anaconda.gui_mode and not (flags.preexisting_x11 or flags.usevnc)
-
- # Is Xorg is actually available?
- if want_x and not os.access("/usr/bin/Xorg", os.X_OK):
- stdout_log.warning(_("Graphical installation is not available. "
- "Starting text mode."))
- time.sleep(2)
- anaconda.display_mode = constants.DisplayModes.TUI
- want_x = False
-
- if anaconda.tui_mode and flags.vncquestion:
- # we prefer vnc over text mode, so ask about that
- message = _("Text mode provides a limited set of installation "
- "options. It does not offer custom partitioning for "
- "full control over the disk layout. Would you like "
- "to use VNC mode instead?")
- ask_vnc_question(anaconda, vnc_server, message)
- if not anaconda.ksdata.vnc.enabled:
- # user has explicitly specified text mode
- flags.vncquestion = False
-
- anaconda.log_display_mode()
- startup_utils.check_memory(anaconda, options)
-
- # check_memory may have changed the display mode
- want_x = want_x and (anaconda.gui_mode)
- if want_x:
- try:
- start_x11(xtimeout)
- do_startup_x11_actions()
- except TimeoutError as e:
- log.warning("X startup failed: %s", e)
- print("\nX did not start in the expected time, falling back to text mode. There are "
- "multiple ways to avoid this issue:")
- wrapper = textwrap.TextWrapper(initial_indent=" * ", subsequent_indent=" ",
- width=os.get_terminal_size().columns - 3)
- for line in X_TIMEOUT_ADVICE.split("\n"):
- print(wrapper.fill(line))
- util.vtActivate(1)
- anaconda.display_mode = constants.DisplayModes.TUI
- anaconda.gui_startup_failed = True
- time.sleep(2)
-
- except (OSError, RuntimeError) as e:
- log.warning("X or window manager startup failed: %s", e)
- print("\nX or window manager startup failed, falling back to text mode.")
- util.vtActivate(1)
- anaconda.display_mode = constants.DisplayModes.TUI
- anaconda.gui_startup_failed = True
- time.sleep(2)
-
- if not anaconda.gui_startup_failed:
- do_extra_x11_actions(options.runres, gui_mode=anaconda.gui_mode)
-
- 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 "
- "this computer from another computer and perform a graphical installation or continue "
- "with a text mode installation?")
- ask_vnc_question(anaconda, vnc_server, message)
-
- # if they want us to use VNC do that now
- if anaconda.gui_mode and flags.usevnc:
- vnc_server.startServer()
- do_startup_x11_actions()
-
- # with X running we can initialize the UI interface
- anaconda.initInterface()
+ if "x11" in kernel_arguments:
+ from pyanaconda import display_x11
+ display_x11.setup_display(anaconda, options)
+ else:
+ from pyanaconda import display_wayland
+ display_wayland.setup_display(anaconda, options)
diff --git a/pyanaconda/display_wayland.py b/pyanaconda/display_wayland.py
new file mode 100644
index 00000000000..63450da0ae7
--- /dev/null
+++ b/pyanaconda/display_wayland.py
@@ -0,0 +1,330 @@
+#
+# display_wayland.py: Wayland graphical display setup for the Anaconda GUI
+#
+# Copyright (C) 2024 Neal Gompa.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty 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, see .
+#
+# Author(s): Neal Gompa
+#
+import configparser
+import glob
+import os
+import subprocess
+import time
+import textwrap
+import pkgutil
+import signal
+
+from pyanaconda.core.configuration.anaconda import conf
+from pyanaconda.core.process_watchers import WatchProcesses
+from pyanaconda import startup_utils
+from pyanaconda.core import util, constants, hw
+from pyanaconda.core.i18n import _
+from pyanaconda.flags import flags
+from pyanaconda.modules.common.constants.services import NETWORK
+from pyanaconda.ui.tui.spokes.askvnc import AskVNCSpoke
+from pyanaconda.ui.tui import tui_quit_callback
+# needed for checking if the pyanaconda.ui.gui modules are available
+import pyanaconda.ui
+
+import blivet
+
+from pykickstart.constants import DISPLAY_MODE_TEXT
+
+from simpleline import App
+from simpleline.render.screen_handler import ScreenHandler
+
+from pyanaconda.anaconda_loggers import get_module_logger, get_stdout_logger
+log = get_module_logger(__name__)
+stdout_log = get_stdout_logger()
+
+WAYLAND_TIMEOUT_ADVICE = \
+ "Do not load the stage2 image over a slow network link.\n" \
+ "Wait longer for the compositor startup with the inst.xtimeout= boot option." \
+ "The default is 60 seconds.\n" \
+ "Load the stage2 image into memory with the rd.live.ram boot option to decrease access " \
+ "time.\n" \
+ "Enforce text mode when installing from remote media with the inst.text boot option."
+# on RHEL also: "Use the customer portal download URL in ilo/drac devices for greater speed."
+
+
+def start_user_systemd():
+ """Start the user instance of systemd.
+
+ The service org.a11y.Bus runs the dbus-broker-launch in
+ the user scope that requires the user instance of systemd.
+ """
+ if not conf.system.can_start_user_systemd:
+ log.debug("Don't start the user instance of systemd.")
+ return
+
+ childproc = util.startProgram(["/usr/lib/systemd/systemd", "--user"])
+ WatchProcesses.watch_process(childproc, "systemd")
+
+
+# Spice
+
+def start_spice_vd_agent():
+ """Start the spice vdagent.
+
+ For certain features to work spice requires that the guest os
+ is running the spice vdagent.
+ """
+ try:
+ status = util.execWithRedirect("spice-vdagent", [])
+ except OSError as e:
+ log.warning("spice-vdagent failed: %s", e)
+ return
+
+ if status:
+ log.info("spice-vdagent exited with status %d", status)
+ else:
+ log.info("Started spice-vdagent.")
+
+
+# VNC
+
+def ask_vnc_question(anaconda, message):
+ """ Ask the user if TUI or GUI-over-VNC should be started.
+
+ :param anaconda: instance of the Anaconda class
+ :param vnc_server: instance of the VNC server object
+ :param str message: a message to show to the user together
+ with the question
+ """
+ App.initialize()
+ loop = App.get_event_loop()
+ loop.set_quit_callback(tui_quit_callback)
+ spoke = AskVNCSpoke(anaconda.ksdata, message=message)
+ ScreenHandler.schedule_screen(spoke)
+ App.run()
+
+ if anaconda.ksdata.vnc.enabled:
+ if not anaconda.gui_mode:
+ log.info("VNC requested via VNC question, switching Anaconda to GUI mode.")
+ anaconda.display_mode = constants.DisplayModes.GUI
+ flags.usevnc = True
+
+
+def check_vnc_can_be_started(anaconda):
+ """Check if we can start VNC in the current environment.
+
+ :returns: if VNC can be started and list of possible reasons
+ why VNC can't be started
+ :rtype: (boot, list)
+ """
+
+ error_messages = []
+ vnc_startup_possible = True
+
+ # disable VNC over text question when not enough memory is available
+ min_gui_ram = hw.minimal_memory_needed(with_gui=True)
+ if blivet.util.total_memory() < min_gui_ram:
+ error_messages.append("Not asking for VNC because current memory (%d) < MIN_GUI_RAM (%d)" %
+ (blivet.util.total_memory(), min_gui_ram))
+ vnc_startup_possible = False
+
+ # disable VNC question if text mode is requested and this is a ks install
+ if anaconda.tui_mode and flags.automatedInstall:
+ error_messages.append("Not asking for VNC because of an automated install")
+ vnc_startup_possible = False
+
+ # disable VNC question if we were explicitly asked for text in kickstart
+ if anaconda.ksdata.displaymode.displayMode == DISPLAY_MODE_TEXT:
+ error_messages.append("Not asking for VNC because text mode was explicitly asked for in kickstart")
+ vnc_startup_possible = False
+
+ # disable VNC question if we don't have network
+ network_proxy = NETWORK.get_proxy()
+ if not network_proxy.IsConnecting() and not network_proxy.Connected:
+ error_messages.append("Not asking for VNC because we don't have a network")
+ vnc_startup_possible = False
+
+ # disable VNC question if we don't have Weston's VNC backend
+ if not glob.glob("/usr/lib*/libweston*/vnc-backend.so"):
+ error_messages.append("Not asking for VNC because we don't have weston-vnc")
+ vnc_startup_possible = False
+
+ return vnc_startup_possible, error_messages
+
+
+# Wayland
+
+def start_weston(wconfig, wltimeout):
+ """Start Weston for the Anaconda GUI"""
+
+ # Start Weston and wait for it to become ready
+ # Switch to vt6 only if not using vnc
+ if "vnc" not in wconfig:
+ util.vtActivate(6)
+ started = util.startWl(weston_config=wconfig,
+ output_redirect=subprocess.DEVNULL,
+ timeout=wltimeout)
+ if started:
+ return True
+ else:
+ return False
+
+
+def do_extra_display_actions():
+ """Perform graphics startup actions not related to startup."""
+
+ start_user_systemd()
+ start_spice_vd_agent()
+
+
+# general display startup
+def setup_display(anaconda, options):
+ """Setup the display for the installation environment.
+
+ :param anaconda: instance of the Anaconda class
+ :param options: command line/boot options
+ """
+
+ try:
+ wltimeout = int(options.xtimeout)
+ except ValueError:
+ log.warning("invalid inst.xtimeout option value: %s", options.xtimeout)
+ wltimeout = constants.WAYLAND_TIMEOUT
+
+ # Declare Weston configuration
+ weston_core_config = constants.WESTON_CONFIG
+
+ weston_vnc_config = {
+ "vnc": {
+ "port": "5900",
+ },
+ "output": {
+ "name": "vnc",
+ "resizeable": "true",
+ "mode": "800x600",
+ },
+ }
+
+ anaconda.display_mode = options.display_mode
+ anaconda.interactive_mode = not options.noninteractive
+
+ if options.vnc:
+ flags.usevnc = True
+ if not anaconda.gui_mode:
+ log.info("VNC requested via boot/CLI option, switching Anaconda to GUI mode.")
+ anaconda.display_mode = constants.DisplayModes.GUI
+
+ if flags.rescue_mode:
+ return
+
+ if anaconda.ksdata.vnc.enabled:
+ flags.usevnc = True
+ if not anaconda.gui_mode:
+ log.info("VNC requested via kickstart, switching Anaconda to GUI mode.")
+ anaconda.display_mode = constants.DisplayModes.GUI
+
+ # check if GUI without WebUI
+ if anaconda.gui_mode and not anaconda.is_webui_supported:
+ mods = (tup[1] for tup in pkgutil.iter_modules(pyanaconda.ui.__path__, "pyanaconda.ui."))
+ if "pyanaconda.ui.gui" not in mods:
+ stdout_log.warning("Graphical user interface not available, falling back to text mode")
+ anaconda.display_mode = constants.DisplayModes.TUI
+ flags.usevnc = False
+ flags.vncquestion = False
+
+ # check if VNC can be started
+ vnc_can_be_started, vnc_error_messages = check_vnc_can_be_started(anaconda)
+ if not vnc_can_be_started:
+ # VNC can't be started - disable the VNC question and log
+ # all the errors that prevented VNC from being started
+ flags.vncquestion = False
+ for error_message in vnc_error_messages:
+ stdout_log.warning(error_message)
+
+ # Are Weston and Xwayland actually available?
+ have_gui = os.access("/usr/bin/weston", os.X_OK) and os.access("/usr/bin/Xwayland", os.X_OK)
+ # Should we try to start the graphical environment?
+ want_gui = anaconda.gui_mode and not (flags.preexisting_wayland or flags.usevnc)
+
+ if want_gui and not have_gui:
+ stdout_log.warning(_("Graphical installation is not available. "
+ "Starting text mode."))
+ time.sleep(2)
+ anaconda.display_mode = constants.DisplayModes.TUI
+ want_gui = False
+
+ if anaconda.tui_mode and have_gui and flags.vncquestion:
+ # we prefer vnc over text mode, so ask about that
+ message = _("Text mode provides a limited set of installation "
+ "options. It does not offer custom partitioning for "
+ "full control over the disk layout. Would you like "
+ "to use VNC mode instead?")
+ ask_vnc_question(anaconda, vnc_server, message)
+ if not anaconda.ksdata.vnc.enabled:
+ # user has explicitly specified text mode
+ flags.vncquestion = False
+
+ anaconda.log_display_mode()
+ startup_utils.check_memory(anaconda, options)
+
+ # check_memory may have changed the display mode
+ want_gui = want_gui and (anaconda.gui_mode)
+ if want_gui:
+ try:
+ if flags.usevnc:
+ weston_full_config = weston_core_config | weston_vnc_config
+ else:
+ weston_full_config = weston_core_config
+ start_weston(weston_full_config, wltimeout)
+ except TimeoutError as e:
+ log.warning("Graphics startup failed: %s", e)
+ print("\nGraphics did not start in the expected time, falling back to text mode. There are "
+ "multiple ways to avoid this issue:")
+ wrapper = textwrap.TextWrapper(initial_indent=" * ", subsequent_indent=" ",
+ width=os.get_terminal_size().columns - 3)
+ for line in WAYLAND_TIMEOUT_ADVICE.split("\n"):
+ print(wrapper.fill(line))
+ util.vtActivate(1)
+ anaconda.display_mode = constants.DisplayModes.TUI
+ anaconda.gui_startup_failed = True
+ time.sleep(2)
+
+ except (OSError, RuntimeError) as e:
+ log.warning("Weston startup failed: %s", e)
+ print("\nWeston startup failed, falling back to text mode.")
+ util.vtActivate(1)
+ anaconda.display_mode = constants.DisplayModes.TUI
+ anaconda.gui_startup_failed = True
+ time.sleep(2)
+
+ if not anaconda.gui_startup_failed:
+ do_extra_display_actions()
+
+ if anaconda.tui_mode and anaconda.gui_startup_failed and flags.vncquestion and not anaconda.ksdata.vnc.enabled:
+ message = _("Graphics was unable to start on your machine. Would you like to start VNC to connect to "
+ "this computer from another computer and perform a graphical installation or continue "
+ "with a text mode installation?")
+ ask_vnc_question(anaconda, message)
+
+ # if they want us to use VNC do that now
+ if anaconda.gui_mode and flags.usevnc:
+ weston_full_config = weston_core_config | weston_vnc_config
+ start_weston(weston_full_config, wltimeout)
+
+ if os.path.isfile(constants.WAYLAND_DISPLAY_VARS_FILE):
+ # We need to load the correct variables for the environment
+ wl_envvars_config = configparser.ConfigParser()
+ wl_envvars_config.read(constants.WAYLAND_DISPLAY_VARS_FILE)
+ os.environ["WAYLAND_DISPLAY"] = wl_envvars_config["wayland_vars"]["WAYLAND_DISPLAY"]
+ os.environ["DISPLAY"] = wl_envvars_config["wayland_vars"]["DISPLAY"]
+
+ # with X running we can initialize the UI interface
+ anaconda.initInterface()
diff --git a/pyanaconda/display_x11.py b/pyanaconda/display_x11.py
new file mode 100644
index 00000000000..d46b154915e
--- /dev/null
+++ b/pyanaconda/display_x11.py
@@ -0,0 +1,390 @@
+#
+# display_x11.py: X11 graphical display setup for the Anaconda GUI
+#
+# Copyright (C) 2016
+# Red Hat, Inc. All rights reserved.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty 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, see .
+#
+# Author(s): Martin Kolman
+#
+import os
+import subprocess
+import time
+import textwrap
+import pkgutil
+import signal
+
+from pyanaconda.core.configuration.anaconda import conf
+from pyanaconda.core.process_watchers import WatchProcesses
+from pyanaconda import startup_utils
+from pyanaconda.core import util, constants, hw
+from pyanaconda import vnc
+from pyanaconda.core.i18n import _
+from pyanaconda.flags import flags
+from pyanaconda.modules.common.constants.services import NETWORK
+from pyanaconda.ui.tui.spokes.askvnc import AskVNCSpoke
+from pyanaconda.ui.tui import tui_quit_callback
+# needed for checking if the pyanaconda.ui.gui modules are available
+import pyanaconda.ui
+
+import blivet
+
+from pykickstart.constants import DISPLAY_MODE_TEXT
+
+from simpleline import App
+from simpleline.render.screen_handler import ScreenHandler
+
+from pyanaconda.anaconda_loggers import get_module_logger, get_stdout_logger
+log = get_module_logger(__name__)
+stdout_log = get_stdout_logger()
+
+X_TIMEOUT_ADVICE = \
+ "Do not load the stage2 image over a slow network link.\n" \
+ "Wait longer for the X server startup with the inst.xtimeout= boot option." \
+ "The default is 60 seconds.\n" \
+ "Load the stage2 image into memory with the rd.live.ram boot option to decrease access " \
+ "time.\n" \
+ "Enforce text mode when installing from remote media with the inst.text boot option."
+# on RHEL also: "Use the customer portal download URL in ilo/drac devices for greater speed."
+
+
+def start_user_systemd():
+ """Start the user instance of systemd.
+
+ The service org.a11y.Bus runs the dbus-broker-launch in
+ the user scope that requires the user instance of systemd.
+ """
+ if not conf.system.can_start_user_systemd:
+ log.debug("Don't start the user instance of systemd.")
+ return
+
+ childproc = util.startProgram(["/usr/lib/systemd/systemd", "--user"])
+ WatchProcesses.watch_process(childproc, "systemd")
+
+
+# Spice
+
+def start_spice_vd_agent():
+ """Start the spice vdagent.
+
+ For certain features to work spice requires that the guest os
+ is running the spice vdagent.
+ """
+ try:
+ status = util.execWithRedirect("spice-vdagent", [])
+ except OSError as e:
+ log.warning("spice-vdagent failed: %s", e)
+ return
+
+ if status:
+ log.info("spice-vdagent exited with status %d", status)
+ else:
+ log.info("Started spice-vdagent.")
+
+
+# VNC
+
+def ask_vnc_question(anaconda, vnc_server, message):
+ """ Ask the user if TUI or GUI-over-VNC should be started.
+
+ :param anaconda: instance of the Anaconda class
+ :param vnc_server: instance of the VNC server object
+ :param str message: a message to show to the user together
+ with the question
+ """
+ App.initialize()
+ loop = App.get_event_loop()
+ loop.set_quit_callback(tui_quit_callback)
+ spoke = AskVNCSpoke(anaconda.ksdata, message=message)
+ ScreenHandler.schedule_screen(spoke)
+ App.run()
+
+ if anaconda.ksdata.vnc.enabled:
+ if not anaconda.gui_mode:
+ log.info("VNC requested via VNC question, switching Anaconda to GUI mode.")
+ anaconda.display_mode = constants.DisplayModes.GUI
+ flags.usevnc = True
+ vnc_server.password = anaconda.ksdata.vnc.password
+
+
+def check_vnc_can_be_started(anaconda):
+ """Check if we can start VNC in the current environment.
+
+ :returns: if VNC can be started and list of possible reasons
+ why VNC can't be started
+ :rtype: (boot, list)
+ """
+
+ error_messages = []
+ vnc_startup_possible = True
+
+ # disable VNC over text question when not enough memory is available
+ min_gui_ram = hw.minimal_memory_needed(with_gui=True)
+ if blivet.util.total_memory() < min_gui_ram:
+ error_messages.append("Not asking for VNC because current memory (%d) < MIN_GUI_RAM (%d)" %
+ (blivet.util.total_memory(), min_gui_ram))
+ vnc_startup_possible = False
+
+ # disable VNC question if text mode is requested and this is a ks install
+ if anaconda.tui_mode and flags.automatedInstall:
+ error_messages.append("Not asking for VNC because of an automated install")
+ vnc_startup_possible = False
+
+ # disable VNC question if we were explicitly asked for text in kickstart
+ if anaconda.ksdata.displaymode.displayMode == DISPLAY_MODE_TEXT:
+ error_messages.append("Not asking for VNC because text mode was explicitly asked for in kickstart")
+ vnc_startup_possible = False
+
+ # disable VNC question if we don't have network
+ network_proxy = NETWORK.get_proxy()
+ if not network_proxy.IsConnecting() and not network_proxy.Connected:
+ error_messages.append("Not asking for VNC because we don't have a network")
+ vnc_startup_possible = False
+
+ # disable VNC question if we don't have Xvnc
+ if not os.access('/usr/bin/Xvnc', os.X_OK):
+ error_messages.append("Not asking for VNC because we don't have Xvnc")
+ vnc_startup_possible = False
+
+ return vnc_startup_possible, error_messages
+
+
+# X11
+
+def start_x11(xtimeout):
+ """Start the X server for the Anaconda GUI."""
+
+ # Start Xorg and wait for it become ready
+ util.startX(["Xorg", "-br", "-logfile", "/tmp/X.log",
+ ":%s" % constants.X_DISPLAY_NUMBER, "vt6", "-s", "1440", "-ac",
+ "-nolisten", "tcp", "-dpi", "96",
+ "-noreset"],
+ output_redirect=subprocess.DEVNULL, timeout=xtimeout)
+
+
+# function to handle X startup special issues for anaconda
+
+def do_startup_x11_actions():
+ """Start the window manager.
+
+ When window manager actually connects to the X server is unknowable, but
+ fortunately it doesn't matter. Wm does not need to be the first
+ connection to Xorg, and if anaconda starts up before wm, wm
+ will just take over and maximize the window and make everything right,
+ fingers crossed.
+ Add XDG_DATA_DIRS to the environment to pull in our overridden schema
+ files.
+ """
+ datadir = os.environ.get('ANACONDA_DATADIR', '/usr/share/anaconda')
+ if 'XDG_DATA_DIRS' in os.environ:
+ xdg_data_dirs = datadir + '/window-manager:' + os.environ['XDG_DATA_DIRS']
+ else:
+ xdg_data_dirs = datadir + '/window-manager:/usr/share'
+
+ def x11_preexec():
+ # to set GUI subprocess SIGINT handler
+ signal.signal(signal.SIGINT, signal.SIG_IGN)
+
+ childproc = util.startProgram(["gnome-kiosk", "--display", ":1", "--sm-disable", "--x11"],
+ env_add={'XDG_DATA_DIRS': xdg_data_dirs},
+ preexec_fn=x11_preexec)
+ WatchProcesses.watch_process(childproc, "gnome-kiosk")
+
+
+def set_x_resolution(runres):
+ """Set X server 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_user_systemd()
+ start_spice_vd_agent()
+
+
+def write_xdriver(driver, root=None):
+ """Write the X driver."""
+ if root is None:
+ root = conf.target.system_root
+
+ if not os.path.isdir("%s/etc/X11" % (root,)):
+ os.makedirs("%s/etc/X11" % (root,), mode=0o755)
+
+ f = open("%s/etc/X11/xorg.conf" % (root,), 'w')
+ f.write('Section "Device"\n\tIdentifier "Videocard0"\n\tDriver "%s"\nEndSection\n' % driver)
+ f.close()
+
+
+# general display startup
+def setup_display(anaconda, options):
+ """Setup the display for the installation environment.
+
+ :param anaconda: instance of the Anaconda class
+ :param options: command line/boot options
+ """
+
+ try:
+ xtimeout = int(options.xtimeout)
+ except ValueError:
+ log.warning("invalid inst.xtimeout option value: %s", options.xtimeout)
+ xtimeout = constants.X_TIMEOUT
+
+ vnc_server = vnc.VncServer() # The vnc Server object.
+ vnc_server.anaconda = anaconda
+ vnc_server.timeout = xtimeout
+
+ anaconda.display_mode = options.display_mode
+ anaconda.interactive_mode = not options.noninteractive
+
+ if options.vnc:
+ flags.usevnc = True
+ if not anaconda.gui_mode:
+ log.info("VNC requested via boot/CLI option, switching Anaconda to GUI mode.")
+ anaconda.display_mode = constants.DisplayModes.GUI
+ vnc_server.password = options.vncpassword
+
+ # Only consider vncconnect when vnc is a param
+ if options.vncconnect:
+ cargs = options.vncconnect.split(":")
+ vnc_server.vncconnecthost = cargs[0]
+ if len(cargs) > 1 and len(cargs[1]) > 0:
+ if len(cargs[1]) > 0:
+ vnc_server.vncconnectport = cargs[1]
+
+ if options.xdriver:
+ write_xdriver(options.xdriver, root="/")
+
+ if flags.rescue_mode:
+ return
+
+ if anaconda.ksdata.vnc.enabled:
+ flags.usevnc = True
+ if not anaconda.gui_mode:
+ log.info("VNC requested via kickstart, switching Anaconda to GUI mode.")
+ anaconda.display_mode = constants.DisplayModes.GUI
+
+ if vnc_server.password == "":
+ vnc_server.password = anaconda.ksdata.vnc.password
+
+ if vnc_server.vncconnecthost == "":
+ vnc_server.vncconnecthost = anaconda.ksdata.vnc.host
+
+ if vnc_server.vncconnectport == "":
+ vnc_server.vncconnectport = anaconda.ksdata.vnc.port
+
+ # check if GUI without WebUI
+ if anaconda.gui_mode and not anaconda.is_webui_supported:
+ mods = (tup[1] for tup in pkgutil.iter_modules(pyanaconda.ui.__path__, "pyanaconda.ui."))
+ if "pyanaconda.ui.gui" not in mods:
+ stdout_log.warning("Graphical user interface not available, falling back to text mode")
+ anaconda.display_mode = constants.DisplayModes.TUI
+ flags.usevnc = False
+ flags.vncquestion = False
+
+ # check if VNC can be started
+ vnc_can_be_started, vnc_error_messages = check_vnc_can_be_started(anaconda)
+ if not vnc_can_be_started:
+ # VNC can't be started - disable the VNC question and log
+ # all the errors that prevented VNC from being started
+ flags.vncquestion = False
+ for error_message in vnc_error_messages:
+ stdout_log.warning(error_message)
+
+ # Should we try to start Xorg?
+ want_x = anaconda.gui_mode and not (flags.preexisting_x11 or flags.usevnc)
+
+ # Is Xorg is actually available?
+ if want_x and not os.access("/usr/bin/Xorg", os.X_OK):
+ stdout_log.warning(_("Graphical installation is not available. "
+ "Starting text mode."))
+ time.sleep(2)
+ anaconda.display_mode = constants.DisplayModes.TUI
+ want_x = False
+
+ if anaconda.tui_mode and flags.vncquestion:
+ # we prefer vnc over text mode, so ask about that
+ message = _("Text mode provides a limited set of installation "
+ "options. It does not offer custom partitioning for "
+ "full control over the disk layout. Would you like "
+ "to use VNC mode instead?")
+ ask_vnc_question(anaconda, vnc_server, message)
+ if not anaconda.ksdata.vnc.enabled:
+ # user has explicitly specified text mode
+ flags.vncquestion = False
+
+ anaconda.log_display_mode()
+ startup_utils.check_memory(anaconda, options)
+
+ # check_memory may have changed the display mode
+ want_x = want_x and (anaconda.gui_mode)
+ if want_x:
+ try:
+ start_x11(xtimeout)
+ do_startup_x11_actions()
+ except TimeoutError as e:
+ log.warning("X startup failed: %s", e)
+ print("\nX did not start in the expected time, falling back to text mode. There are "
+ "multiple ways to avoid this issue:")
+ wrapper = textwrap.TextWrapper(initial_indent=" * ", subsequent_indent=" ",
+ width=os.get_terminal_size().columns - 3)
+ for line in X_TIMEOUT_ADVICE.split("\n"):
+ print(wrapper.fill(line))
+ util.vtActivate(1)
+ anaconda.display_mode = constants.DisplayModes.TUI
+ anaconda.gui_startup_failed = True
+ time.sleep(2)
+
+ except (OSError, RuntimeError) as e:
+ log.warning("X or window manager startup failed: %s", e)
+ print("\nX or window manager startup failed, falling back to text mode.")
+ util.vtActivate(1)
+ anaconda.display_mode = constants.DisplayModes.TUI
+ anaconda.gui_startup_failed = True
+ time.sleep(2)
+
+ if not anaconda.gui_startup_failed:
+ do_extra_x11_actions(options.runres, gui_mode=anaconda.gui_mode)
+
+ 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 "
+ "this computer from another computer and perform a graphical installation or continue "
+ "with a text mode installation?")
+ ask_vnc_question(anaconda, vnc_server, message)
+
+ # if they want us to use VNC do that now
+ if anaconda.gui_mode and flags.usevnc:
+ vnc_server.startServer()
+ do_startup_x11_actions()
+
+ # with X running we can initialize the UI interface
+ anaconda.initInterface()
diff --git a/pyanaconda/flags.py b/pyanaconda/flags.py
index 000fd5df876..b70aa7def8e 100644
--- a/pyanaconda/flags.py
+++ b/pyanaconda/flags.py
@@ -35,6 +35,7 @@ def __init__(self):
self.__dict__['_in_init'] = True
self.usevnc = False
self.vncquestion = True
+ self.preexisting_wayland = False
self.preexisting_x11 = False
self.automatedInstall = False
self.eject = True
diff --git a/pyanaconda/ui/gui/__init__.py b/pyanaconda/ui/gui/__init__.py
index 520c9b942e9..b84f6b9730d 100644
--- a/pyanaconda/ui/gui/__init__.py
+++ b/pyanaconda/ui/gui/__init__.py
@@ -672,6 +672,9 @@ def _instantiateAction(self, actionClass):
return obj
def run(self):
+ # Ensure we launch as an "X11" application since we do not support anything else yet
+ util.setenv("GDK_BACKEND", "x11")
+
(success, _args) = Gtk.init_check(None)
if not success:
raise RuntimeError("Failed to initialize Gtk")