def process_input(self, user_input): """Process input from the screens. :param user_input: User input string. :type user_input: String. :raises: ExitMainLoop or any other kind of exception from screen processing. """ # process the input, if it wasn't processed (valid) # increment the error counter try: result = self._process_input(user_input) except ExitMainLoop: # pylint: disable=try-except-raise raise except Exception: # pylint: disable=broad-except App.get_event_loop().enqueue_signal(ExceptionSignal(self)) return if result.was_successful(): self._input_error_counter = 0 else: self._input_error_counter += 1 App.get_scheduler().process_input_result( result, self.input_error_threshold_exceeded)
def __init__(self, callback=None, source=None): """Class to handle input from the terminal. This class is designed to be instantiated on place where it should be used. The main method is `get_input()` which is non-blocking asynchronous call. It can be used as synchronous call be calling the `wait_on_input` method. To get result from this class use the `value` property. :param callback: You can specify callback which will be called when user give input. :type callback: Callback function with one argument which will be user input. :param source: Source of this input. It will be helpful in case of debugging an issue. :type source: Class which will process an input from this InputHandler. """ super().__init__() self._input = None self._input_callback = callback self._input_received = False self._input_successful = False self._skip_concurrency_check = False self._source = source App.get_event_loop().register_signal_handler( InputReadySignal, self._input_received_handler)
def redraw(self): """Emit signal to initiate draw. Add RenderScreenSignal to the event loop. """ signal = self.create_signal(RenderScreenSignal) App.get_event_loop().enqueue_signal(signal)
def close(self): """Emit signal to close this screen. Add CloseScreenSignal to the event loop. """ signal = self.create_signal(CloseScreenSignal) App.get_event_loop().enqueue_signal(signal)
def run(self): """Run the `run_input` method and propagate input outside. Do not call this method directly. It will be called by InputThreadManager. """ data = self.get_input() App.get_event_loop().enqueue_signal(InputReceivedSignal(self, data))
def test_emit(self): connect_screen = UIScreen() App.initialize(scheduler=MagicMock()) connect_screen.connect(TestSignal, self._callback) connect_screen.emit(TestSignal(self)) App.get_event_loop().process_signals() self.assertTrue(self.callback_called)
def emit(self, signal): """Emit the signal. This will add `signal` to the event loop. :param signal: signal to emit :type signal: instance of class based on `simpleline.event_loop.AbstractSignal` """ App.get_event_loop().enqueue_signal(signal)
def test_basic_connect(self): connect_screen = UIScreen() App.initialize(scheduler=MagicMock()) connect_screen.connect(SignalMock, self._callback) App.get_event_loop().enqueue_signal(SignalMock(self)) App.get_event_loop().process_signals() self.assertTrue(self.callback_called)
def emit_failed_input_ready_signal(self): """Emit the InputReadySignal with failed state.""" handler_source = self.source signal_source = self._get_request_source() new_signal = InputReadySignal(source=signal_source, input_handler_source=handler_source, data="", success=False) App.get_event_loop().enqueue_signal(new_signal)
def tearDown(self): super().tearDown() self._thread_event_wait_for_outer.set() for t in self._threads: t.join() # process InputReceivedSignal App.get_event_loop().process_signals() # process InputReadySignal App.get_event_loop().process_signals()
def wait_on_input(self): """Blocks execution till the user input is received. Events will works as expected during this blocking. Please check the `input_successful` method to test the input. """ # we already received input from user if self._input_received: return while not self._input_received: App.get_event_loop().process_signals(InputReadySignal)
def connect(self, signal, callback, data=None): """Connect this class method with given signal. :param signal: signal class which you want to connect :type signal: class based on `simpleline.event_loop.AbstractSignal` :param callback: the callback function :type callback: func(event_message, data) :param data: Data you want to pass to the callback :type data: Anything """ App.get_event_loop().register_signal_handler(signal, callback, data)
def setup(self, args): """Do additional setup right before this screen is used. It is mandatory to call this ancestor method in the child class to set ready status. :param args: arguments for the setup :type args: array of values :return: whether this screen should be scheduled or not :rtype: bool """ self._screen_ready = True App.get_event_loop().register_signal_source(self) return True
def emit_input_ready_signal(self, input_data): """Emit the InputReadySignal signal with collected input data. :param input_data: Input data received. :type input_data: str """ handler_source = self.source signal_source = self._get_request_source() new_signal = InputReadySignal(source=signal_source, input_handler_source=handler_source, data=input_data, success=True) App.get_event_loop().enqueue_signal(new_signal)
def exception_msg_handler(signal, data): """ Handler for the ExceptionSignal signal. :param signal: event data :type signal: (event_type, message_data) :param data: additional data :type data: any """ global exception_processed if exception_processed: # get data from the event data structure exception_info = signal.exception_info stack_trace = "\n" + App.get_scheduler().dump_stack() log.error(stack_trace) # exception_info is a list sys.excepthook(*exception_info) else: # show only the first exception do not spam user with others exception_processed = True loop = App.get_event_loop() # start new loop for handling the exception # this will stop processing all the old signals and prevent raising new exceptions loop.execute_new_loop(signal)
def start_rescue_mode_ui(anaconda): """Start the rescue mode UI.""" ksdata_rescue = None if anaconda.ksdata.rescue.seen: ksdata_rescue = anaconda.ksdata.rescue scripts = anaconda.ksdata.scripts storage = anaconda.storage reboot = True if conf.target.is_image: reboot = False if flags.automatedInstall and anaconda.ksdata.reboot.action not in [ KS_REBOOT, KS_SHUTDOWN ]: reboot = False rescue = Rescue(storage, ksdata_rescue, reboot, scripts) rescue.initialize() # We still want to choose from multiple roots, or unlock encrypted devices # if needed, so we run UI even for kickstarts (automated install). App.initialize() loop = App.get_event_loop() loop.set_quit_callback(tui_quit_callback) spoke = RescueModeSpoke(rescue) ScreenHandler.schedule_screen(spoke) App.run()
def start_rescue_mode_ui(anaconda): """Start the rescue mode UI.""" ksdata_rescue = None if anaconda.ksdata.rescue.seen: ksdata_rescue = anaconda.ksdata.rescue scripts = anaconda.ksdata.scripts storage = anaconda.storage reboot = True if conf.target.is_image: reboot = False if flags.automatedInstall and anaconda.ksdata.reboot.action not in [KS_REBOOT, KS_SHUTDOWN]: reboot = False rescue = Rescue(storage, ksdata_rescue, reboot, scripts) rescue.initialize() # We still want to choose from multiple roots, or unlock encrypted devices # if needed, so we run UI even for kickstarts (automated install). App.initialize() loop = App.get_event_loop() loop.set_quit_callback(tui_quit_callback) spoke = RescueModeSpoke(rescue) ScreenHandler.schedule_screen(spoke) App.run()
def test_create_instance_with_custom_everything(self): event_loop = CustomEventLoop() App.initialize(event_loop=event_loop, scheduler=CustomScreenScheduler(event_loop), global_configuration=CustomGlobalConfiguration()) self.assertTrue(isinstance(App.get_event_loop(), CustomEventLoop)) self.assertTrue(isinstance(App.get_scheduler(), CustomScreenScheduler)) self.assertTrue(isinstance(App.get_configuration(), CustomGlobalConfiguration))
def _update_progress(self): """Handle progress updates from install thread.""" from pyanaconda.progress import progressQ import queue q = progressQ.q # Grab all messages may have appeared since last time this method ran. while True: # Attempt to get a message out of the queue for how we should update # the progress bar. If there's no message, don't error out. # Also flush the communication Queue at least once a second and # process it's events so we can react to async evens (like a thread # throwing an exception) while True: try: (code, args) = q.get(timeout=1) break except queue.Empty: pass finally: loop = App.get_event_loop() loop.process_signals() if code == progressQ.PROGRESS_CODE_INIT: # Text mode doesn't have a finite progress bar pass elif code == progressQ.PROGRESS_CODE_STEP: # Instead of updating a progress bar, we just print a pip # but print it without a new line. sys.stdout.write('.') sys.stdout.flush() # Use _stepped as an indication to if we need a newline before # the next message self._stepped = True elif code == progressQ.PROGRESS_CODE_MESSAGE: # This should already be translated if self._stepped: # Get a new line in case we've done a step before self._stepped = False print('') print(args[0]) elif code == progressQ.PROGRESS_CODE_COMPLETE: # There shouldn't be any more progress updates, so return q.task_done() if self._stepped: print('') return True elif code == progressQ.PROGRESS_CODE_QUIT: sys.exit(args[0]) q.task_done() return True
def __init__(self, data, storage=None, payload=None, instclass=None, message=""): super().__init__(data, storage, payload, instclass) self.input_required = True self.initialize_start() self._container = None # The TUI hasn't been initialized with the message handlers yet. Add an # exception message handler so that the TUI exits if anything goes wrong # at this stage. loop = App.get_event_loop() loop.register_signal_handler(ExceptionSignal, exception_msg_handler_and_exit) self._message = message self._usevnc = False self.initialize_done()
def setup(self, data): """Construct all the objects required to implement this interface. This method must be provided by all subclasses. """ # Use GLib event loop for the Simpleline TUI loop = GLibEventLoop() App.initialize(event_loop=loop) loop.set_quit_callback(tui_quit_callback) scheduler = App.get_scheduler() scheduler.quit_screen = YesNoDialog(self.quitMessage) # tell python-meh it should use our raw_input meh_io_handler = meh.ui.text.IOHandler( in_func=self._get_meh_input_func) self._meh_interface.set_io_handler(meh_io_handler) # register handlers for various messages loop = App.get_event_loop() loop.register_signal_handler(ExceptionSignal, exception_msg_handler) loop.register_signal_handler(SendMessageSignal, self._handle_show_message) _hubs = self._list_hubs() # First, grab a list of all the standalone spokes. spokes = self._collectActionClasses(self.paths["spokes"], StandaloneSpoke) actionClasses = self._orderActionClasses(spokes, _hubs) for klass in actionClasses: obj = klass(data, self.storage, self.payload) # If we are doing a kickstart install, some standalone spokes # could already be filled out. In that case, we do not want # to display them. if self._is_standalone(obj) and obj.completed: del (obj) continue if hasattr(obj, "set_path"): obj.set_path("spokes", self.paths["spokes"]) obj.set_path("categories", self.paths["categories"]) should_schedule = obj.setup(self.ENVIRONMENT) if should_schedule: scheduler.schedule_screen(obj)
def _send_show_message(self, msg_fn, args, ret_queue): """ Send message requesting to show some message dialog specified by the message function. :param msg_fn: message dialog function requested to be called :type msg_fn: a function taking the same number of arguments as is the length of the args param :param args: arguments to be passed to the message dialog function :type args: any :param ret_queue: the queue which the return value of the message dialog function should be put :type ret_queue: a queue.Queue instance """ signal = SendMessageSignal(self, msg_fn=msg_fn, args=args, ret_queue=ret_queue) loop = App.get_event_loop() loop.enqueue_signal(signal)
def changeVNCPasswdWindow(self): """ Change the password to a sane parameter. We ask user to input a password that (len(password) > 6 and len(password) <= 8) or password == ''. """ message = _("VNC password must be six to eight characters long.\n" "Please enter a new one, or leave blank for no password.") App.initialize() loop = App.get_event_loop() loop.set_quit_callback(tui_quit_callback) spoke = VNCPassSpoke(self.anaconda.ksdata, None, None, message) ScreenHandler.schedule_screen(spoke) App.run() self.password = self.anaconda.ksdata.vnc.password
def setup(self, data): """Construct all the objects required to implement this interface. This method must be provided by all subclasses. """ # Use GLib event loop for the Simpleline TUI loop = GLibEventLoop() App.initialize(event_loop=loop) loop.set_quit_callback(tui_quit_callback) scheduler = App.get_scheduler() scheduler.quit_screen = YesNoDialog(self.quitMessage) # tell python-meh it should use our raw_input meh_io_handler = meh.ui.text.IOHandler(in_func=self._get_meh_input_func) self._meh_interface.set_io_handler(meh_io_handler) # register handlers for various messages loop = App.get_event_loop() loop.register_signal_handler(ExceptionSignal, exception_msg_handler) loop.register_signal_handler(SendMessageSignal, self._handle_show_message) _hubs = self._list_hubs() # First, grab a list of all the standalone spokes. spokes = self._collectActionClasses(self.paths["spokes"], StandaloneSpoke) actionClasses = self._orderActionClasses(spokes, _hubs) for klass in actionClasses: obj = klass(data, self.storage, self.payload, self.instclass) # If we are doing a kickstart install, some standalone spokes # could already be filled out. In that case, we do not want # to display them. if self._is_standalone(obj) and obj.completed: del(obj) continue if hasattr(obj, "set_path"): obj.set_path("spokes", self.paths["spokes"]) obj.set_path("categories", self.paths["categories"]) should_schedule = obj.setup(self.ENVIRONMENT) if should_schedule: scheduler.schedule_screen(obj)
def changeVNCPasswdWindow(self): """ Change the password to a sane parameter. We ask user to input a password that (len(password) > 6 and len(password) <= 8) or password == ''. """ message = _("VNC password must be six to eight characters long.\n" "Please enter a new one, or leave blank for no password.") App.initialize() loop = App.get_event_loop() loop.set_quit_callback(tui_quit_callback) spoke = VNCPassSpoke(self.anaconda.ksdata, None, None, None, message) ScreenHandler.schedule_screen(spoke) App.run() self.password = self.anaconda.ksdata.vnc.password
def test_reinitialize(self): event_loop1 = CustomEventLoop() event_loop2 = CustomEventLoop() scheduler1 = CustomScreenScheduler(event_loop1) scheduler2 = CustomScreenScheduler(event_loop2) configuration1 = CustomGlobalConfiguration() configuration2 = CustomGlobalConfiguration() App.initialize(event_loop=event_loop1, scheduler=scheduler1, global_configuration=configuration1) self._check_app_settings(event_loop1, scheduler1, configuration1) App.initialize(event_loop=event_loop2, scheduler=scheduler2, global_configuration=configuration2) self._check_app_settings(event_loop2, scheduler2, configuration2) App.initialize() self.assertNotEqual(App.get_event_loop(), event_loop2) self.assertNotEqual(App.get_scheduler(), scheduler2) self.assertNotEqual(App.get_configuration(), configuration2)
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) 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 setup(self, args="anaconda"): environment = args should_schedule = TUIHub.setup(self, environment) if not should_schedule: return False if flags.automatedInstall: sys.stdout.write(_("Starting automated install")) sys.stdout.flush() spokes = self._spokes.values() while not all(spoke.ready for spoke in spokes): # Catch any asynchronous events (like storage crashing) loop = App.get_event_loop() loop.process_signals() sys.stdout.write('.') sys.stdout.flush() time.sleep(1) print('') return True
def setup(self, args="anaconda"): environment = args should_schedule = TUIHub.setup(self, environment) if not should_schedule: return False if flags.automatedInstall: sys.stdout.write(_("Starting automated install")) sys.stdout.flush() spokes = self._spokes.values() while not all(spoke.ready for spoke in spokes): # Catch any asynchronous events (like storage crashing) loop = App.get_event_loop() loop.process_signals() sys.stdout.write('.') sys.stdout.flush() time.sleep(1) print('') for spoke in spokes: if spoke.changed: spoke.execute() return True
def show_all(self): super().show_all() from pyanaconda.installation import RunInstallationTask # Start the installation task. self._task = RunInstallationTask(payload=self.payload, ksdata=self.data) self._task.progress_changed_signal.connect(self._on_progress_changed) self._task.stopped_signal.connect(self._on_installation_done) self._task.start() log.debug("The installation has started.") # This will run until the task is finished. loop = App.get_event_loop() loop.process_signals(return_after=ScreenReadySignal) # kickstart install, continue automatically if reboot or shutdown selected if flags.automatedInstall and self.data.reboot.action in [ KS_REBOOT, KS_SHUTDOWN ]: # Just pretend like we got input, and our input doesn't care # what it gets, it just quits. raise ExitMainLoop()
def _on_installation_done(self): log.debug("The installation has finished.") # Print a new line after the last step. if self._stepped: print('') # Finish the installation task. Re-raise tracebacks if any. self._task.finish() util.ipmi_report(IPMI_FINISHED) if conf.license.eula: # Notify user about the EULA (if any). print(_("Installation complete")) print('') print( _("Use of this product is subject to the license agreement found at:" )) print(conf.license.eula) print('') loop = App.get_event_loop() loop.enqueue_signal(ScreenReadySignal(self))
def force_quit_mock(self, signal, data=None): self._force_quit_called = True loop = App.get_event_loop() loop.force_quit()
def handleException(self, dump_info): """ Our own handleException method doing some additional stuff before calling the original python-meh's one. :type dump_info: an instance of the meh.DumpInfo class :see: python-meh's ExceptionHandler.handleException """ log.debug("running handleException") exception_lines = traceback.format_exception(*dump_info.exc_info) log.critical("\n".join(exception_lines)) ty = dump_info.exc_info.type value = dump_info.exc_info.value try: gi.require_version("Gtk", "3.0") from gi.repository import Gtk # XXX: Gtk stopped raising RuntimeError if it fails to # initialize. Horay! But will it stay like this? Let's be # cautious and raise the exception on our own to work in both # cases initialized = Gtk.init_check(None)[0] if not initialized: raise RuntimeError() # Attempt to grab the GUI initializing lock, do not block if not self._gui_lock.acquire(False): # the graphical interface is running, don't crash it by # running another one potentially from a different thread log.debug( "Gtk running, queuing exception handler to the main loop") run_in_loop(self._main_loop_handleException, dump_info) else: log.debug( "Gtk not running, starting Gtk and running exception handler in it" ) self._main_loop_handleException(dump_info) except (RuntimeError, ImportError, ValueError): log.debug("Gtk cannot be initialized") # X not running (Gtk cannot be initialized) if threadMgr.in_main_thread(): log.debug("In the main thread, running exception handler") if issubclass(ty, NonInteractiveError) or not self._interactive: if issubclass(ty, NonInteractiveError): cmdline_error_msg = _( "\nThe installation was stopped due to an " "error which occurred while running in " "non-interactive cmdline mode. Since there " "cannot be any questions in cmdline mode, edit " "your kickstart file and retry installation. " "\nThe exact error message is: \n\n%s. \n\nThe " "installer will now terminate.") % str(value) else: cmdline_error_msg = _( "\nRunning in cmdline mode, no interactive " "debugging allowed.\nThe exact error message is: " "\n\n%s.\n\nThe installer will now terminate." ) % str(value) # since there is no UI in cmdline mode and it is completely # non-interactive, we can't show a message window asking the user # to acknowledge the error; instead, print the error out and sleep # for a few seconds before exiting the installer print(cmdline_error_msg) time.sleep(180) sys.exit(1) else: print("\nAn unknown error has occured, look at the " "/tmp/anaconda-tb* file(s) for more details") # in the main thread, run exception handler self._main_loop_handleException(dump_info) else: log.debug( "In a non-main thread, sending a message with exception data" ) # not in the main thread, just send message with exception # data and let message handler run the exception handler in # the main thread exc_info = dump_info.exc_info # new Simpleline package is now used in TUI. Look if Simpleline is # initialized or if this is some fallback from GTK or other stuff. if App.is_initialized(): # if Simpleline is initialized enqueue exception there loop = App.get_event_loop() loop.enqueue_signal( ExceptionSignal(App.get_scheduler(), exception_info=exc_info)) else: hubQ.send_exception( (exc_info.type, exc_info.value, exc_info.stack))
def handleException(self, dump_info): """ Our own handleException method doing some additional stuff before calling the original python-meh's one. :type dump_info: an instance of the meh.DumpInfo class :see: python-meh's ExceptionHandler.handleException """ log.debug("running handleException") exception_lines = traceback.format_exception(*dump_info.exc_info) log.critical("\n".join(exception_lines)) ty = dump_info.exc_info.type value = dump_info.exc_info.value try: gi.require_version("Gtk", "3.0") from gi.repository import Gtk # XXX: Gtk stopped raising RuntimeError if it fails to # initialize. Horay! But will it stay like this? Let's be # cautious and raise the exception on our own to work in both # cases initialized = Gtk.init_check(None)[0] if not initialized: raise RuntimeError() # Attempt to grab the GUI initializing lock, do not block if not self._gui_lock.acquire(False): # the graphical interface is running, don't crash it by # running another one potentially from a different thread log.debug("Gtk running, queuing exception handler to the main loop") run_in_loop(self._main_loop_handleException, dump_info) else: log.debug("Gtk not running, starting Gtk and running exception handler in it") self._main_loop_handleException(dump_info) except (RuntimeError, ImportError, ValueError): log.debug("Gtk cannot be initialized") # X not running (Gtk cannot be initialized) if threadMgr.in_main_thread(): log.debug("In the main thread, running exception handler") if issubclass(ty, NonInteractiveError) or not self._interactive: if issubclass(ty, NonInteractiveError): cmdline_error_msg = _("\nThe installation was stopped due to an " "error which occurred while running in " "non-interactive cmdline mode. Since there " "cannot be any questions in cmdline mode, edit " "your kickstart file and retry installation. " "\nThe exact error message is: \n\n%s. \n\nThe " "installer will now terminate.") % str(value) else: cmdline_error_msg = _("\nRunning in cmdline mode, no interactive " "debugging allowed.\nThe exact error message is: " "\n\n%s.\n\nThe installer will now terminate." ) % str(value) # since there is no UI in cmdline mode and it is completely # non-interactive, we can't show a message window asking the user # to acknowledge the error; instead, print the error out and sleep # for a few seconds before exiting the installer print(cmdline_error_msg) time.sleep(180) sys.exit(1) else: print("\nAn unknown error has occured, look at the " "/tmp/anaconda-tb* file(s) for more details") # in the main thread, run exception handler self._main_loop_handleException(dump_info) else: log.debug("In a non-main thread, sending a message with exception data") # not in the main thread, just send message with exception # data and let message handler run the exception handler in # the main thread exc_info = dump_info.exc_info # new Simpleline package is now used in TUI. Look if Simpleline is # initialized or if this is some fallback from GTK or other stuff. if App.is_initialized(): # if Simpleline is initialized enqueue exception there loop = App.get_event_loop() loop.enqueue_signal(ExceptionSignal(App.get_scheduler(), exception_info=exc_info)) else: hubQ.send_exception((exc_info.type, exc_info.value, exc_info.stack))
def _post_init_configuration(self): App.get_event_loop().register_signal_handler( InputReceivedSignal, self.__instance._input_received_handler)