Esempio n. 1
0
async def test_login_no_available_devices(aqtbot, gui_factory,
                                          autoclose_dialog, core_config,
                                          alice):
    password = "******"
    save_device_with_password_in_config(core_config.config_dir, alice,
                                        password)

    device = list_available_devices(core_config.config_dir)[0]

    gui = await gui_factory()

    ParsecApp.add_connected_device(device.organization_id, device.device_id)

    lw = gui.test_get_login_widget()

    lw.reload_devices()

    def _devices_listed():
        assert lw.widget.layout().count() > 0

    await aqtbot.wait_until(_devices_listed)

    no_device_w = lw.widget.layout().itemAt(0).widget()
    assert isinstance(no_device_w, LoginNoDevicesWidget)
    # 0 is spacer, 1 is label
    assert no_device_w.layout().itemAt(
        2).widget().text() == "Create an organization"
    assert no_device_w.layout().itemAt(
        3).widget().text() == "Join an organization"
    assert no_device_w.layout().itemAt(4).widget().text() == "Recover a device"
Esempio n. 2
0
 def on_core_run_done(self):
     assert self.running_core_job.is_finished()
     if self.core:
         ParsecApp.remove_connected_device(
             self.core.device.organization_addr.organization_id,
             self.core.device.device_id)
         self.core.event_bus.disconnect(CoreEvent.GUI_CONFIG_CHANGED,
                                        self.on_core_config_updated)
     self.running_core_job = None
     self.logged_out.emit()
Esempio n. 3
0
 def on_run_core_ready(self, core, core_jobs_ctx):
     self.core = core
     self.core_jobs_ctx = core_jobs_ctx
     self.core.event_bus.connect(CoreEvent.GUI_CONFIG_CHANGED, self.on_core_config_updated)
     self.event_bus.send(
         CoreEvent.GUI_CONFIG_CHANGED, gui_last_device=self.core.device.device_id.str
     )
     ParsecApp.add_connected_device(
         self.core.device.organization_addr.organization_id, self.core.device.device_id
     )
     self.logged_in.emit()
Esempio n. 4
0
 def on_core_run_done(self):
     assert self.running_core_job.is_finished()
     if self.core:
         ParsecApp.remove_connected_device(
             self.core.device.organization_addr.organization_id,
             self.core.device.device_id)
         self.core.event_bus.disconnect("gui.config.changed",
                                        self.on_core_config_updated)
     self.running_core_job = None
     self.core_jobs_ctx = None
     self.core = None
     self.logged_out.emit()
Esempio n. 5
0
 def on_run_core_ready(self, core, core_jobs_ctx):
     self.core = core
     self.core_jobs_ctx = core_jobs_ctx
     self.core.event_bus.connect("gui.config.changed",
                                 self.on_core_config_updated)
     self.event_bus.send(
         "gui.config.changed",
         gui_last_device="{}:{}".format(
             self.core.device.organization_addr.organization_id,
             self.core.device.device_id),
     )
     ParsecApp.add_connected_device(
         self.core.device.organization_addr.organization_id,
         self.core.device.device_id)
     self.logged_in.emit()
Esempio n. 6
0
 def __init__(self, center_widget, title, parent, hide_close=False):
     super().__init__(None)
     self.setupUi(self)
     self.setModal(True)
     self.setObjectName("GreyedDialog")
     self.setWindowModality(Qt.ApplicationModal)
     self.button_close.apply_style()
     if platform.system() == "Windows":
         # SplashScreen on Windows freezes the Window
         self.setWindowFlags(Qt.FramelessWindowHint)
     else:
         # FramelessWindowHint on Linux (at least xfce) is less pretty
         self.setWindowFlags(Qt.SplashScreen)
     self.setAttribute(Qt.WA_TranslucentBackground)
     self.center_widget = center_widget
     self.main_layout.addWidget(center_widget)
     if not title and hide_close:
         self.widget_title.hide()
     if title:
         self.label_title.setText(title)
     if hide_close:
         self.button_close.hide()
     main_win = ParsecApp.get_main_window()
     if main_win:
         if main_win.isVisible():
             self.setParent(main_win)
             self.resize(main_win.size())
         else:
             self.showMaximized()
         self.move(0, 0)
     else:
         logger.error("GreyedDialog did not find the main window, this is probably a bug")
     self.setFocus()
     self.accepted.connect(self.on_finished)
     self.rejected.connect(self.on_finished)
Esempio n. 7
0
 def reload_devices(self):
     while self.combo_username.count():
         self.combo_username.removeItem(0)
     # Display devices in `<organization>:<user>@<device>` format
     self.devices = {}
     for available_device in list_available_devices(self.config.config_dir):
         if not ParsecApp.is_device_connected(
                 available_device.organization_id,
                 available_device.device_id):
             name = f"{available_device.organization_id}: {available_device.user_display} @ {available_device.device_display}"
             self.combo_username.addItem(name)
             self.devices[name] = available_device
     last_device = self.config.gui_last_device
     if last_device and last_device in self.devices:
         self.combo_username.setCurrentText(last_device)
     if len(self.devices):
         self.widget_no_device.hide()
         self.widget_login.show()
     else:
         self.widget_no_device.show()
         self.widget_login.hide()
         if ParsecApp.connected_devices:
             self.label_no_device.setText(
                 _("TEXT_LOGIN_NO_AVAILABLE_DEVICE"))
         else:
             self.label_no_device.setText(
                 _("TEXT_LOGIN_NO_DEVICE_ON_MACHINE"))
Esempio n. 8
0
 async def login_with_smartcard(self, key_file):
     message = None
     exception = None
     try:
         device = await load_device_with_smartcard(key_file)
         if ParsecApp.is_device_connected(
             device.organization_addr.organization_id, device.device_id
         ):
             message = _("TEXT_LOGIN_ERROR_ALREADY_CONNECTED")
         else:
             self.start_core(device)
     except LocalDeviceCertificatePinCodeUnavailableError:
         # User cancelled the prompt
         self.login_failed.emit()
     except LocalDeviceError as exc:
         message = _("TEXT_LOGIN_ERROR_AUTHENTICATION_FAILED")
         exception = exc
     except ModuleNotFoundError as exc:
         message = _("TEXT_LOGIN_SMARTCARD_NOT_AVAILABLE")
         exception = exc
     except (RuntimeError, MountpointConfigurationError, MountpointDriverCrash) as exc:
         message = _("TEXT_LOGIN_MOUNTPOINT_ERROR")
         exception = exc
     except Exception as exc:
         message = _("TEXT_LOGIN_UNKNOWN_ERROR")
         exception = exc
         logger.exception("Unhandled error during login")
     finally:
         if message:
             show_error(self, message, exception=exception)
             self.login_failed.emit()
Esempio n. 9
0
    def login_with_password(self, key_file, password):
        message = None
        exception = None
        try:
            device = load_device_with_password(key_file, password)
            if ParsecApp.is_device_connected(
                device.organization_addr.organization_id, device.device_id
            ):
                message = _("TEXT_LOGIN_ERROR_ALREADY_CONNECTED")
            else:
                self.start_core(device)
        except LocalDeviceError as exc:
            message = _("TEXT_LOGIN_ERROR_AUTHENTICATION_FAILED")
            exception = exc

        except (RuntimeError, MountpointConfigurationError, MountpointDriverCrash) as exc:
            message = _("TEXT_LOGIN_MOUNTPOINT_ERROR")
            exception = exc

        except Exception as exc:
            message = _("TEXT_LOGIN_UNKNOWN_ERROR")
            exception = exc
            logger.exception("Unhandled error during login")
        finally:
            if message:
                show_error(self, message, exception=exception)
                self.login_failed.emit()
Esempio n. 10
0
async def test_login_no_available_devices(aqtbot, gui_factory,
                                          autoclose_dialog, core_config, alice,
                                          qt_thread_gateway):
    password = "******"
    save_device_with_password(core_config.config_dir, alice, password)

    device = list_available_devices(core_config.config_dir)[0]

    gui = await gui_factory()

    ParsecApp.add_connected_device(device.organization_id, device.device_id)

    lw = gui.test_get_login_widget()

    def _reload_devices():
        lw.reload_devices()

    await qt_thread_gateway.send_action(_reload_devices)

    no_device_w = lw.widget.layout().itemAt(0).widget()
    assert isinstance(no_device_w, LoginNoDevicesWidget)
Esempio n. 11
0
    def start_core(self, device):
        assert not self.running_core_job
        assert not self.core
        assert not self.core_jobs_ctx

        self.config = ParsecApp.get_main_window().config

        self.running_core_job = self.jobs_ctx.submit_job(
            self.run_core_success,
            self.run_core_error,
            _do_run_core,
            self.config,
            device,
            self.run_core_ready,
        )
Esempio n. 12
0
    def start_core(self, device):
        assert not self.running_core_job
        assert not self.core
        assert not self.core_jobs_ctx

        self.config = ParsecApp.get_main_window().config

        self.running_core_job = self.jobs_ctx.submit_job(
            ThreadSafeQtSignal(self, "run_core_success"),
            ThreadSafeQtSignal(self, "run_core_error"),
            _do_run_core,
            self.config,
            device,
            ThreadSafeQtSignal(self, "run_core_ready", object, object),
        )
Esempio n. 13
0
    def move_popup(self):
        main_window = ParsecApp.get_main_window()
        if not main_window:
            return
        offset = 10
        height = 101 if platform.system() == "Windows" else 75
        width = min(500, main_window.size().width() - 40)
        self.resize(QSize(width, height))

        x = main_window.size().width() - width - 20
        y = main_window.size().height() - ((height + offset) * (self.index + 1))
        # Hide the snackbar if the main window does not have enough space to show it
        self.set_visible(y > 30)
        pos = main_window.mapToGlobal(QPoint(x, y))
        self.setGeometry(pos.x(), pos.y(), width, height)
Esempio n. 14
0
def run_gui(config: CoreConfig,
            start_arg: Optional[str] = None,
            diagnose: bool = False):
    logger.info("Starting UI")

    # Needed for High DPI usage of QIcons, otherwise only QImages are well scaled
    QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
    QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps)
    QApplication.setHighDpiScaleFactorRoundingPolicy(
        Qt.HighDpiScaleFactorRoundingPolicy.PassThrough)

    # The parsec app needs to be instanciated before qtrio runs in order
    # to be the default QApplication instance
    app = ParsecApp()
    assert QApplication.instance() is app
    return qtrio.run(_run_gui, app, config, start_arg, diagnose)
Esempio n. 15
0
 def __init__(
     self, center_widget, title, parent, hide_close=False, width=None, close_on_click=False
 ):
     super().__init__(None)
     self.setupUi(self)
     self.setModal(True)
     self.setObjectName("GreyedDialog")
     self.setWindowModality(Qt.ApplicationModal)
     self.button_close.apply_style()
     self.close_on_click = close_on_click
     if sys.platform == "win32":
         # SplashScreen on Windows freezes the Window
         self.setWindowFlags(Qt.FramelessWindowHint)
     else:
         # FramelessWindowHint on Linux (at least xfce) is less pretty
         self.setWindowFlags(Qt.SplashScreen)
     self.setAttribute(Qt.WA_TranslucentBackground)
     self.center_widget = center_widget
     self.main_layout.addWidget(center_widget)
     if not title and hide_close:
         self.widget_title.hide()
     if title:
         self.label_title.setText(title)
     if hide_close:
         self.button_close.hide()
     main_win = ParsecApp.get_main_window()
     if width:
         if width < main_win.size().width():
             spacing = int((main_win.size().width() - width) / 2)
             self._get_spacer_right().changeSize(
                 spacing, 0, QSizePolicy.Preferred, QSizePolicy.Preferred
             )
             self._get_spacer_left().changeSize(
                 spacing, 0, QSizePolicy.Preferred, QSizePolicy.Preferred
             )
     if main_win:
         if main_win.isVisible():
             self.setParent(main_win)
             self.resize(main_win.size())
         else:
             self.showMaximized()
         self.move(0, 0)
     elif parent is not None:
         logger.error("GreyedDialog did not find the main window, this is probably a bug")
     self.setFocus()
     self.accepted.connect(self.on_finished)
     self.rejected.connect(self.on_finished)
Esempio n. 16
0
 def list_devices_and_enrollments(self):
     pendings = PkiEnrollmentSubmitterSubmittedCtx.list_from_disk(
         config_dir=self.config.config_dir)
     devices = [
         device for device in list_available_devices(self.config.config_dir)
         if not ParsecApp.is_device_connected(device.organization_id,
                                              device.device_id)
     ]
     if not len(devices) and not len(pendings):
         no_device_widget = LoginNoDevicesWidget()
         no_device_widget.create_organization_clicked.connect(
             self.create_organization_clicked.emit)
         no_device_widget.join_organization_clicked.connect(
             self.join_organization_clicked.emit)
         no_device_widget.recover_device_clicked.connect(
             self.recover_device_clicked.emit)
         self.widget.layout().addWidget(no_device_widget)
         no_device_widget.setFocus()
     elif len(devices) == 1 and not len(pendings):
         self._on_account_clicked(devices[0], hide_back=True)
     else:
         # If the GUI has a last used device, we look for it in our devices list
         # and insert it to the front, so it will be shown first
         if self.config.gui_last_device:
             last_used = next(
                 (d for d in devices
                  if d.device_id.str == self.config.gui_last_device), None)
             if last_used:
                 devices.remove(last_used)
                 devices.insert(0, last_used)
         accounts_widget = LoginAccountsWidget(self.config, self.jobs_ctx,
                                               devices, pendings)
         accounts_widget.account_clicked.connect(self._on_account_clicked)
         accounts_widget.pending_finalize_clicked.connect(
             self._on_pending_finalize_clicked)
         accounts_widget.pending_clear_clicked.connect(
             self._on_pending_clear_clicked)
         self.widget.layout().addWidget(accounts_widget)
         accounts_widget.setFocus()
Esempio n. 17
0
 def reload_devices(self):
     self._clear_widget()
     devices = [
         device for device in list_available_devices(self.config.config_dir)
         if not ParsecApp.is_device_connected(device.organization_id,
                                              device.device_id)
     ]
     if not len(devices):
         no_device_widget = LoginNoDevicesWidget()
         no_device_widget.create_organization_clicked.connect(
             self.create_organization_clicked.emit)
         no_device_widget.join_organization_clicked.connect(
             self.join_organization_clicked.emit)
         self.widget.layout().addWidget(no_device_widget)
         no_device_widget.setFocus()
     elif len(devices) == 1:
         self._on_account_clicked(devices[0], hide_back=True)
     else:
         accounts_widget = LoginAccountsWidget(devices)
         accounts_widget.account_clicked.connect(self._on_account_clicked)
         self.widget.layout().addWidget(accounts_widget)
         accounts_widget.setFocus()
Esempio n. 18
0
 def reload_devices(self):
     while self.combo_username.count():
         self.combo_username.removeItem(0)
     devices = list_available_devices(self.config.config_dir)
     # Display devices in `<organization>:<device_id>` format
     self.devices = {}
     for o, d, t, kf in devices:
         if not ParsecApp.is_device_connected(o, d):
             self.combo_username.addItem(f"{o}:{d}")
             self.devices[f"{o}:{d}"] = (o, d, t, kf)
     last_device = self.config.gui_last_device
     if last_device and last_device in self.devices:
         self.combo_username.setCurrentText(last_device)
     if len(self.devices):
         self.widget_no_device.hide()
         self.widget_login.show()
     else:
         self.widget_no_device.show()
         self.widget_login.hide()
         if ParsecApp.connected_devices:
             self.label_no_device.setText(_("TEXT_LOGIN_NO_AVAILABLE_DEVICE"))
         else:
             self.label_no_device.setText(_("TEXT_LOGIN_NO_DEVICE_ON_MACHINE"))
Esempio n. 19
0
 def switch_to_tab(self, idx):
     if not ParsecApp.has_active_modal():
         self.tab_center.setCurrentIndex(idx)
Esempio n. 20
0
 def _inner_proxy():
     if ParsecApp.has_active_modal():
         return
     funct()
Esempio n. 21
0
 def right_main_window():
     assert ParsecApp.get_main_window() is main_w
Esempio n. 22
0
async def _run_gui(app: ParsecApp,
                   config: CoreConfig,
                   start_arg: str = None,
                   diagnose: bool = False):
    app.load_stylesheet()
    app.load_font()

    lang_key = lang.switch_language(config)

    event_bus = EventBus()
    async with run_trio_job_scheduler() as jobs_ctx:
        win = MainWindow(
            jobs_ctx=jobs_ctx,
            quit_callback=jobs_ctx.close,
            event_bus=event_bus,
            config=config,
            minimize_on_close=config.gui_tray_enabled and systray_available(),
        )

        # Attempt to run an IPC server if Parsec is not already started
        try:
            await jobs_ctx.nursery.start(_run_ipc_server, config, win,
                                         start_arg)
        # Another instance of Parsec already started, nothing more to do
        except IPCServerAlreadyRunning:
            return

        # If we are here, it's either the IPC server has successfully started
        # or it has crashed without being able to communicate with an existing
        # IPC server. Such case is of course not supposed to happen but if it
        # does we nevertheless keep the application running as a kind of
        # failsafe mode (and the crash reason is logged and sent to telemetry).

        # Systray is not displayed on MacOS, having natively a menu with similar functions.
        if systray_available() and sys.platform != "darwin":
            systray = Systray(parent=win)
            win.systray_notification.connect(systray.on_systray_notification)
            systray.on_close.connect(win.close_app)
            systray.on_show.connect(win.show_top)
            app.aboutToQuit.connect(before_quit(systray))
            if config.gui_tray_enabled:
                app.setQuitOnLastWindowClosed(False)

        if config.gui_check_version_at_startup and not diagnose:
            CheckNewVersion(jobs_ctx=jobs_ctx,
                            event_bus=event_bus,
                            config=config,
                            parent=win)

        win.show_window(skip_dialogs=diagnose)
        win.show_top()
        win.new_instance_needed.emit(start_arg)

        if sys.platform == "darwin":
            # macFUSE is not bundled with Parsec and must be manually installed by the user
            # so we detect early such need to provide a help dialogue ;-)
            # TODO: provide a similar mechanism on Windows&Linux to handle mountpoint runner not available
            from parsec.core.gui.instance_widget import ensure_macfuse_available_or_show_dialogue

            ensure_macfuse_available_or_show_dialogue(win)

        def kill_window(*args):
            win.close_app(force=True)

        signal.signal(signal.SIGINT, kill_window)

        # QTimer wakes up the event loop periodically which allows us to close
        # the window even when it is in background.
        timer = QTimer()
        timer.start(400)
        timer.timeout.connect(lambda: None)

        if diagnose:
            diagnose_timer = QTimer()
            diagnose_timer.start(1000)
            diagnose_timer.timeout.connect(kill_window)

        if lang_key:
            event_bus.send(CoreEvent.GUI_CONFIG_CHANGED, gui_language=lang_key)

        with QDialogInProcess.manage_pools():
            if diagnose:
                with fail_on_first_exception(kill_window):
                    await trio.sleep_forever()
            else:
                with log_pyqt_exceptions():
                    await trio.sleep_forever()
Esempio n. 23
0
def run_gui(config: CoreConfig, start_arg: str = None, diagnose: bool = False):
    logger.info("Starting UI")

    # Needed for High DPI usage of QIcons, otherwise only QImages are well scaled
    QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
    QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps)
    QApplication.setHighDpiScaleFactorRoundingPolicy(
        Qt.HighDpiScaleFactorRoundingPolicy.PassThrough)

    app = ParsecApp()

    app.load_stylesheet()
    app.load_font()

    lang_key = lang.switch_language(config)

    event_bus = EventBus()
    with run_trio_thread() as jobs_ctx:
        win = MainWindow(
            jobs_ctx=jobs_ctx,
            event_bus=event_bus,
            config=config,
            minimize_on_close=config.gui_tray_enabled and systray_available(),
        )

        result_queue = Queue(maxsize=1)

        class ThreadSafeNoQtSignal(ThreadSafeQtSignal):
            def __init__(self):
                self.qobj = None
                self.signal_name = ""
                self.args_types = ()

            def emit(self, *args):
                pass

        jobs_ctx.submit_job(
            ThreadSafeNoQtSignal(),
            ThreadSafeNoQtSignal(),
            _start_ipc_server,
            config,
            win,
            start_arg,
            result_queue,
        )
        if result_queue.get() == "already_running":
            # Another instance of Parsec already started, nothing more to do
            return

        if systray_available():
            systray = Systray(parent=win)
            win.systray_notification.connect(systray.on_systray_notification)
            systray.on_close.connect(win.close_app)
            systray.on_show.connect(win.show_top)
            app.aboutToQuit.connect(before_quit(systray))
            if config.gui_tray_enabled:
                app.setQuitOnLastWindowClosed(False)

        if config.gui_check_version_at_startup and not diagnose:
            CheckNewVersion(jobs_ctx=jobs_ctx,
                            event_bus=event_bus,
                            config=config,
                            parent=win)

        win.show_window(skip_dialogs=diagnose, invitation_link=start_arg)
        win.show_top()
        win.new_instance_needed.emit(start_arg)

        def kill_window(*args):
            win.close_app(force=True)
            QApplication.quit()

        signal.signal(signal.SIGINT, kill_window)

        # QTimer wakes up the event loop periodically which allows us to close
        # the window even when it is in background.
        timer = QTimer()
        timer.start(1000 if diagnose else 400)
        timer.timeout.connect(kill_window if diagnose else lambda: None)

        if diagnose:
            diagnose_timer = QTimer()
            diagnose_timer.start(1000)
            diagnose_timer.timeout.connect(kill_window)

        if lang_key:
            event_bus.send(CoreEvent.GUI_CONFIG_CHANGED, gui_language=lang_key)

        if diagnose:
            with fail_on_first_exception(kill_window):
                return app.exec_()
        else:
            with log_pyqt_exceptions():
                return app.exec_()
Esempio n. 24
0
 def __init__(self):
     super().__init__()
     self.snackbars = []
     ParsecApp.get_main_window().installEventFilter(self)
     self.setParent(ParsecApp.get_main_window())
     self.destroyed.connect(self._on_destroyed)