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 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 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_input_no_prompt(self, mock_stdin, mock_stdout): screen = InputWithNoPromptMock() App.get_scheduler().schedule_screen(screen) App.run() self.assertTrue(screen.prompt_entered)
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 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 _check_default_password_function(self, password_func=None): if password_func: self.assertEqual(App.get_configuration().password_function, password_func) else: self.assertEqual(App.get_configuration().password_function, DEFAULT_PASSWORD_FUNC)
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 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 setUp(self): super().setUp() self.create_loop() App.initialize(event_loop=self.loop) self._callback_called = False self._callback_input = ""
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_refresh_input(self, mock_stdin, mock_stdout): mock_stdin.return_value = "r" screen = RefreshTestScreenMock() App.get_scheduler().schedule_screen(screen) App.run() self.assertTrue(screen.input_processed)
def test_process_input_and_close(self, input_mock, mock_stdout): input_mock.return_value = "a" screen = InputStateCloseScreenMock() App.get_scheduler().schedule_screen(screen) App.run() self.assertTrue(screen.input_processed)
def test_input_thread_manager_after_initialize(self): App.initialize() thread_mgr = InputThreadManager.get_instance() App.initialize() self.assertNotEqual(thread_mgr, InputThreadManager.get_instance())
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_process_input_and_redraw(self, input_mock, mock_stdout): input_mock.return_value = "a" screen = InputStateRedrawScreen() App.get_scheduler().schedule_screen(screen) App.run() self.assertTrue(screen.refreshed)
def test_basic_input(self, input_mock, mock_stdout): input_mock.return_value = "a" screen = InputScreen() App.get_scheduler().schedule_screen(screen) App.run() self.assertTrue(screen.input_processed)
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 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_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 test_zero_width_no_separator(self, stdout_mock): ui_screen = EmptyScreenMock() width = 0 App.get_configuration().width = width App.get_scheduler().schedule_screen(ui_screen) App.run() self.assertEqual("\n\n", stdout_mock.getvalue())
def test_width(self): self._check_default_width() App.initialize() App.get_configuration().width = 100 self._check_default_width(100) App.initialize() self._check_default_width()
def test_multiple_refresh_on_input_error(self, mock_stdin, mock_stdout): mock_stdin.return_value = "q" threshold = 12 screen = InputErrorTestScreenMock(threshold) App.get_scheduler().schedule_screen(screen) App.run() self.assertEqual(screen.render_counter, 3) self.assertEqual(screen.error_counter, threshold)
def test_create_signal(self): connect_screen = UIScreen() App.initialize(scheduler=MagicMock()) signal = connect_screen.create_signal(TestSignal, priority=20) self.assertEqual(signal.priority, 20) self.assertTrue(isinstance(signal, TestSignal)) # source is set by create_signal self.assertEqual(signal.source, connect_screen)
def test_clear_width(self): self._check_default_width() test_width = 150 App.get_configuration().width = test_width self._check_default_width(test_width) App.get_configuration().clear_width() self._check_default_width()
def test_password_function(self): self._check_default_password_function() test_mock = MagicMock() App.initialize() App.get_configuration().password_function = test_mock self._check_default_password_function(test_mock) App.initialize() self._check_default_password_function()
def test_other_width_separator(self, stdout_mock): ui_screen = EmptyScreenMock() width = 60 App.get_configuration().width = width App.get_scheduler().schedule_screen(ui_screen) App.run() self.assertEqual(self.calculate_separator(width), stdout_mock.getvalue())
def test_custom_getpass(self, mock_stdin, mock_stdout, process_signals): prompt = mock.MagicMock() ret = "test" screen = ScreenWithPassFuncMock(prompt, ret) App.get_scheduler().schedule_screen(screen) App.run() self.assertTrue(screen.pass_called) self.assertEqual(screen.pass_prompt.rstrip(), str(prompt))
def test_clear_password_function(self): self._check_default_password_function() test_func = MagicMock() App.get_configuration().password_function = test_func self._check_default_password_function(test_func) App.get_configuration().clear_password_function() self._check_default_password_function()
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 test_no_refresh_when_prompt_is_none(self, mock_stdin, mock_stdout): mock_stdin.return_value = "q" threshold = 5 screen = InputErrorDynamicPromptTestScreenMock(threshold, not_return_prompt_on=3) App.get_scheduler().schedule_screen(screen) App.run() # first draw and manual redraw when prompt is None self.assertEqual(screen.render_counter, 2) self.assertTrue(screen.input_skipped) self.assertEqual(screen.error_counter, threshold)
def run(self): """Run the interface. This should do little more than just pass through to something else's run method, but is provided here in case more is needed. This method must be provided by all subclasses. """ return App.run()
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 _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 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 __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 _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 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 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))