# # display.py: 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 from pyanaconda.core.configuration.anaconda import conf from pyanaconda.core.process_watchers import WatchProcesses from pyanaconda import isys from pyanaconda import startup_utils from pyanaconda.core import util, constants 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.\n" \ "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 if blivet.util.total_memory() < isys.MIN_GUI_RAM: error_messages.append("Not asking for VNC because current memory (%d) < MIN_GUI_RAM (%d)" % (blivet.util.total_memory(), isys.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' childproc = util.startProgram(["gnome-kiosk", "--display", ":1", "--sm-disable", "--x11"], env_add={'XDG_DATA_DIRS': xdg_data_dirs}) 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 if anaconda.gui_mode: 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()