示例#1
0
 def __init__(self, nodedir=None, executable=None, reactor=None):
     if reactor is None:
         from twisted.internet import reactor
     self.executable = executable
     self.multi_folder_support = True
     if nodedir:
         self.nodedir = os.path.expanduser(nodedir)
     else:
         self.nodedir = os.path.join(os.path.expanduser('~'), '.tahoe')
     self.rootcap_path = os.path.join(self.nodedir, 'private', 'rootcap')
     self.servers_yaml_path = os.path.join(self.nodedir, 'private',
                                           'servers.yaml')
     self.config = Config(os.path.join(self.nodedir, 'tahoe.cfg'))
     self.pidfile = os.path.join(self.nodedir, 'twistd.pid')
     self.nodeurl = None
     self.shares_happy = None
     self.name = os.path.basename(self.nodedir)
     self.api_token = None
     self.magic_folders_dir = os.path.join(self.nodedir, 'magic-folders')
     self.lock = DeferredLock()
     self.rootcap = None
     self.magic_folders = defaultdict(dict)
     self.remote_magic_folders = defaultdict(dict)
     self.use_tor = False
     self.monitor = Monitor(self)
     streamedlogs_maxlen = None
     debug_settings = global_settings.get('debug')
     if debug_settings:
         log_maxlen = debug_settings.get('log_maxlen')
         if log_maxlen is not None:
             streamedlogs_maxlen = int(log_maxlen)
     self.streamedlogs = StreamedLogs(reactor, streamedlogs_maxlen)
     self.state = Tahoe.STOPPED
     self.newscap = ""
     self.newscap_checker = NewscapChecker(self)
示例#2
0
 def show_message():
     message_settings = settings.get("message")
     if not message_settings:
         return
     if get_preference("message", "suppress") == "true":
         return
     logging.debug("Showing custom message to user...")
     msgbox = QMessageBox()
     icon_type = message_settings.get("type")
     if icon_type:
         icon_type = icon_type.lower()
         if icon_type == "information":
             msgbox.setIcon(QMessageBox.Information)
         elif icon_type == "warning":
             msgbox.setIcon(QMessageBox.Warning)
         elif icon_type == "critical":
             msgbox.setIcon(QMessageBox.Critical)
     if sys.platform == "darwin":
         msgbox.setText(message_settings.get("title"))
         msgbox.setInformativeText(message_settings.get("text"))
     else:
         msgbox.setWindowTitle(message_settings.get("title"))
         msgbox.setText(message_settings.get("text"))
     checkbox = QCheckBox("Do not show this message again")
     checkbox.stateChanged.connect(lambda state: set_preference(
         "message", "suppress", ("true" if state else "false")))
     msgbox.setCheckBox(checkbox)
     msgbox.exec_()
     logging.debug("Custom message closed; proceeding with start...")
示例#3
0
 def show_message():
     message_settings = settings.get('message')
     if not message_settings:
         return
     if get_preference('message', 'suppress') == 'true':
         return
     logging.debug("Showing custom message to user...")
     msgbox = QMessageBox()
     icon_type = message_settings.get('type')
     if icon_type:
         icon_type = icon_type.lower()
         if icon_type == 'information':
             msgbox.setIcon(QMessageBox.Information)
         elif icon_type == 'warning':
             msgbox.setIcon(QMessageBox.Warning)
         elif icon_type == 'critical':
             msgbox.setIcon(QMessageBox.Critical)
     if sys.platform == 'darwin':
         msgbox.setText(message_settings.get('title'))
         msgbox.setInformativeText(message_settings.get('text'))
     else:
         msgbox.setWindowTitle(message_settings.get('title'))
         msgbox.setText(message_settings.get('text'))
     checkbox = QCheckBox("Do not show this message again")
     checkbox.stateChanged.connect(lambda state: set_preference(
         'message', 'suppress', ('true' if state else 'false')))
     msgbox.setCheckBox(checkbox)
     msgbox.exec_()
     logging.debug("Custom message closed; proceeding with start...")
示例#4
0
 def load_newscap(self):
     news_settings = global_settings.get('news:{}'.format(self.name))
     if news_settings:
         newscap = news_settings.get('newscap')
         if newscap:
             self.newscap = newscap
             return
     newscap = self.read_cap_from_file(
         os.path.join(self.nodedir, 'private', 'newscap'))
     if newscap:
         self.newscap = newscap
示例#5
0
 def load_newscap(self):
     news_settings = global_settings.get("news:{}".format(self.name))
     if news_settings:
         newscap = news_settings.get("newscap")
         if newscap:
             self.newscap = newscap
             return
     newscap = self.read_cap_from_file(
         os.path.join(self.nodedir, "private", "newscap"))
     if newscap:
         self.newscap = newscap
示例#6
0
 def __init__(self, args):
     self.args = args
     self.gui = None
     self.gateways = []
     self.executable = None
     self.tahoe_version = None
     self.operations = []
     log_deque_maxlen = 100000  # XXX
     debug_settings = settings.get("debug")
     if debug_settings:
         log_maxlen = debug_settings.get("log_maxlen")
         if log_maxlen is not None:
             log_deque_maxlen = int(log_maxlen)
     self.log_deque = collections.deque(maxlen=log_deque_maxlen)
示例#7
0
    def __init__(self, gui, show_open_action=True):
        super(Menu, self).__init__()
        self.gui = gui

        self.about_msg = QMessageBox()
        self.about_msg.setWindowTitle("{} - About".format(APP_NAME))
        app_icon = QIcon(resource(settings["application"]["tray_icon"]))
        self.about_msg.setIconPixmap(app_icon.pixmap(64, 64))
        self.about_msg.setText("{} {}".format(APP_NAME, __version__))
        self.about_msg.setWindowModality(Qt.WindowModal)

        if show_open_action:
            open_action = QAction(QIcon(""), "Open {}".format(APP_NAME), self)
            open_action.triggered.connect(self.gui.show)
            self.addAction(open_action)
            self.addSeparator()

        preferences_action = QAction(QIcon(""), "Preferences...", self)
        preferences_action.triggered.connect(self.gui.show_preferences_window)
        self.addAction(preferences_action)

        help_menu = QMenu(self)
        help_menu.setTitle("Help")
        help_settings = settings.get("help")
        if help_settings:
            if help_settings.get("docs_url"):
                docs_action = QAction(QIcon(""), "Browse Documentation...",
                                      self)
                docs_action.triggered.connect(
                    lambda: webbrowser.open(settings["help"]["docs_url"]))
                help_menu.addAction(docs_action)
            if help_settings.get("issues_url"):
                issue_action = QAction(QIcon(""), "Report Issue...", self)
                issue_action.triggered.connect(
                    lambda: webbrowser.open(settings["help"]["issues_url"]))
                help_menu.addAction(issue_action)
        export_action = QAction(QIcon(""), "Export Debug Information...", self)
        export_action.triggered.connect(self.gui.show_debug_exporter)
        help_menu.addAction(export_action)
        help_menu.addSeparator()
        about_action = QAction(QIcon(""), "About {}...".format(APP_NAME), self)
        about_action.triggered.connect(self.about_msg.exec_)
        help_menu.addAction(about_action)
        self.addMenu(help_menu)

        self.addSeparator()

        quit_action = QAction(QIcon(""), "&Quit {}".format(APP_NAME), self)
        quit_action.triggered.connect(self.gui.main_window.confirm_quit)
        self.addAction(quit_action)
示例#8
0
def get_tor(reactor):  # TODO: Add launch option?
    tor = None
    features_settings = settings.get("features")
    if features_settings:
        tor_setting = features_settings.get("tor")
        if tor_setting and tor_setting.lower() == "false":
            return tor
    logging.debug("Looking for a running Tor daemon...")
    try:
        tor = yield txtorcon.connect(reactor)
    except RuntimeError:
        logging.debug("Could not connect to a running Tor daemon.")
    if tor:
        logging.debug("Connected to Tor daemon (%s)", tor.version)
    return tor
示例#9
0
    def populate(self):
        self.clear()
        logging.debug("(Re-)populating systray menu...")

        open_action = QAction(QIcon(''), "Open {}".format(APP_NAME), self)
        open_action.triggered.connect(self.gui.show)
        self.addAction(open_action)

        gateways = self.gui.main_window.gateways
        if gateways and len(gateways) > 1:
            self.export_menu = QMenu(self)
            self.export_menu.setTitle("Export Recovery Key")
            for gateway in gateways:
                self._add_export_action(gateway)
            self.addMenu(self.export_menu)
        elif gateways:
            export_action = QAction(QIcon(''), "Export Recovery Key...", self)
            export_action.triggered.connect(
                self.gui.main_window.export_recovery_key)
            self.addAction(export_action)

        help_menu = QMenu(self)
        help_menu.setTitle("Help")
        help_settings = settings.get('help')
        if help_settings:
            if help_settings.get('docs_url'):
                docs_action = QAction(QIcon(''), "Browse Documentation...",
                                      self)
                docs_action.triggered.connect(open_documentation)
                help_menu.addAction(docs_action)
            if help_settings.get('issues_url'):
                issue_action = QAction(QIcon(''), "Report Issue...", self)
                issue_action.triggered.connect(open_issue)
                help_menu.addAction(issue_action)
            help_menu.addSeparator()
        about_action = QAction(QIcon(''), "About {}...".format(APP_NAME), self)
        about_action.triggered.connect(self.about_msg.exec_)
        help_menu.addAction(about_action)
        self.addMenu(help_menu)

        self.addSeparator()

        quit_action = QAction(QIcon(''), "&Quit {}".format(APP_NAME), self)
        quit_action.triggered.connect(self.gui.main_window.confirm_quit)
        self.addAction(quit_action)
示例#10
0
 def __init__(self, gateway):
     super().__init__()
     self.gateway = gateway
     self._started = False
     self.check_delay_min = 30
     self.check_delay_max = 60 * 60 * 24  # 24 hours
     newscap_settings = settings.get("news:{}".format(self.gateway.name))
     if newscap_settings:
         check_delay_min = newscap_settings.get("check_delay_min")
         if check_delay_min:
             self.check_delay_min = int(check_delay_min)
         check_delay_max = newscap_settings.get("check_delay_max")
         if check_delay_max:
             self.check_delay_max = int(check_delay_max)
     if self.check_delay_max < self.check_delay_min:
         self.check_delay_max = self.check_delay_min
     self._last_checked_path = os.path.join(self.gateway.nodedir, "private",
                                            "newscap.last_checked")
示例#11
0
    def populate(self):
        self.clear()
        logging.debug("(Re-)populating systray menu...")

        open_action = QAction(QIcon(''), "Open {}".format(APP_NAME), self)
        open_action.triggered.connect(self.gui.show)
        self.addAction(open_action)

        self.addSeparator()

        preferences_action = QAction(QIcon(''), "Preferences...", self)
        preferences_action.triggered.connect(self.gui.show_preferences_window)
        self.addAction(preferences_action)

        help_menu = QMenu(self)
        help_menu.setTitle("Help")
        help_settings = settings.get('help')
        if help_settings:
            if help_settings.get('docs_url'):
                docs_action = QAction(QIcon(''), "Browse Documentation...",
                                      self)
                docs_action.triggered.connect(open_documentation)
                help_menu.addAction(docs_action)
            if help_settings.get('issues_url'):
                issue_action = QAction(QIcon(''), "Report Issue...", self)
                issue_action.triggered.connect(open_issue)
                help_menu.addAction(issue_action)
            help_menu.addSeparator()
        about_action = QAction(QIcon(''), "About {}...".format(APP_NAME), self)
        about_action.triggered.connect(self.about_msg.exec_)
        help_menu.addAction(about_action)
        self.addMenu(help_menu)

        self.addSeparator()

        quit_action = QAction(QIcon(''), "&Quit {}".format(APP_NAME), self)
        quit_action.triggered.connect(self.gui.main_window.confirm_quit)
        self.addAction(quit_action)
示例#12
0
    def on_right_click(self, position):  # noqa: max-complexity
        if not position:  # From left-click on "Action" button
            position = self.viewport().mapFromGlobal(QCursor().pos())
            self.deselect_remote_folders()
            self.deselect_local_folders()
        cur_item = self.model().itemFromIndex(self.indexAt(position))
        if not cur_item:
            return
        cur_folder = self.model().item(cur_item.row(), 0).text()

        if self.gateway.magic_folders.get(cur_folder):  # is local folder
            selection_is_remote = False
            self.deselect_remote_folders()
        else:
            selection_is_remote = True
            self.deselect_local_folders()

        selected = self.get_selected_folders()
        if not selected:
            selected = [cur_folder]

        menu = QMenu()
        if selection_is_remote:
            download_action = QAction(QIcon(resource("download.png")),
                                      "Download...")
            download_action.triggered.connect(
                lambda: self.select_download_location(selected))
            menu.addAction(download_action)
            menu.addSeparator()
        open_action = QAction(self.model().icon_folder_gray, "Open")
        open_action.triggered.connect(lambda: self.open_folders(selected))

        share_menu = QMenu()
        share_menu.setIcon(QIcon(resource("laptop.png")))
        share_menu.setTitle("Sync with device")  # XXX Rephrase?
        invite_action = QAction(QIcon(resource("invite.png")),
                                "Create Invite Code...")
        invite_action.triggered.connect(
            lambda: self.open_invite_sender_dialog(selected))
        share_menu.addAction(invite_action)

        remove_action = QAction(QIcon(resource("close.png")),
                                "Remove from Recovery Key...")
        menu.addAction(open_action)
        features_settings = settings.get("features")
        if features_settings:
            invites_setting = features_settings.get("invites")
            if invites_setting and invites_setting.lower() != "false":
                menu.addMenu(share_menu)
        else:
            menu.addMenu(share_menu)
        menu.addSeparator()
        menu.addAction(remove_action)
        if selection_is_remote:
            open_action.setEnabled(False)
            share_menu.setEnabled(False)
            remove_action.triggered.connect(
                lambda: self.confirm_unlink(selected))
        else:
            for folder in selected:
                if not self.gateway.magic_folders[folder]["admin_dircap"]:
                    share_menu.setEnabled(False)
                    share_menu.setTitle(
                        "Sync with device (disabled; no admin access)")
            remove_action.setText("Stop syncing...")
            remove_action.triggered.connect(
                lambda: self.confirm_stop_syncing(selected))
        menu.exec_(self.viewport().mapToGlobal(position))
示例#13
0
    def __init__(self, gui):  # noqa: max-complexity
        super().__init__()
        self.gui = gui
        self.gateways = []
        self.welcome_dialog = None
        self.recovery_key_exporter = None

        self.setWindowTitle(APP_NAME)
        self.setMinimumSize(QSize(600, 400))
        self.setUnifiedTitleAndToolBarOnMac(True)
        self.setContextMenuPolicy(Qt.NoContextMenu)

        if sys.platform == "darwin":
            # To disable the broken/buggy "full screen" mode on macOS.
            # See https://github.com/gridsync/gridsync/issues/241
            self.setWindowFlags(Qt.Dialog)

        grid_invites_enabled = True
        invites_enabled = True
        multiple_grids_enabled = True
        features_settings = settings.get("features")
        if features_settings:
            grid_invites = features_settings.get("grid_invites")
            if grid_invites and grid_invites.lower() == "false":
                grid_invites_enabled = False
            invites = features_settings.get("invites")
            if invites and invites.lower() == "false":
                invites_enabled = False
            multiple_grids = features_settings.get("multiple_grids")
            if multiple_grids and multiple_grids.lower() == "false":
                multiple_grids_enabled = False

        if multiple_grids_enabled:
            self.shortcut_new = QShortcut(QKeySequence.New, self)
            self.shortcut_new.activated.connect(self.show_welcome_dialog)

        self.shortcut_open = QShortcut(QKeySequence.Open, self)
        self.shortcut_open.activated.connect(self.select_folder)

        self.shortcut_preferences = QShortcut(QKeySequence.Preferences, self)
        self.shortcut_preferences.activated.connect(
            self.gui.show_preferences_window)

        self.shortcut_close = QShortcut(QKeySequence.Close, self)
        self.shortcut_close.activated.connect(self.close)

        self.shortcut_quit = QShortcut(QKeySequence.Quit, self)
        self.shortcut_quit.activated.connect(self.confirm_quit)

        self.central_widget = CentralWidget(self.gui)
        self.setCentralWidget(self.central_widget)

        font = Font(8)

        folder_icon_default = QFileIconProvider().icon(QFileInfo(config_dir))
        folder_icon_composite = CompositePixmap(
            folder_icon_default.pixmap(256, 256), resource("green-plus.png"))
        folder_icon = QIcon(folder_icon_composite)

        folder_action = QAction(folder_icon, "Add Folder", self)
        folder_action.setToolTip("Add a Folder...")
        folder_action.setFont(font)
        folder_action.triggered.connect(self.select_folder)

        if grid_invites_enabled:
            invites_action = QAction(QIcon(resource("invite.png")), "Invites",
                                     self)
            invites_action.setToolTip("Enter or Create an Invite Code")
            invites_action.setFont(font)

            enter_invite_action = QAction(QIcon(), "Enter Invite Code...",
                                          self)
            enter_invite_action.setToolTip("Enter an Invite Code...")
            enter_invite_action.triggered.connect(self.open_invite_receiver)

            create_invite_action = QAction(QIcon(), "Create Invite Code...",
                                           self)
            create_invite_action.setToolTip("Create on Invite Code...")
            create_invite_action.triggered.connect(
                self.open_invite_sender_dialog)

            invites_menu = QMenu(self)
            invites_menu.addAction(enter_invite_action)
            invites_menu.addAction(create_invite_action)

            invites_button = QToolButton(self)
            invites_button.setDefaultAction(invites_action)
            invites_button.setMenu(invites_menu)
            invites_button.setPopupMode(2)
            invites_button.setStyleSheet(
                "QToolButton::menu-indicator { image: none }")
            invites_button.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)

        elif invites_enabled:
            invite_action = QAction(QIcon(resource("invite.png")),
                                    "Enter Code", self)
            invite_action.setToolTip("Enter an Invite Code...")
            invite_action.setFont(font)
            invite_action.triggered.connect(self.open_invite_receiver)

        spacer_left = QWidget()
        spacer_left.setSizePolicy(QSizePolicy.Expanding, 0)

        self.combo_box = ComboBox()
        self.combo_box.currentIndexChanged.connect(self.on_grid_selected)
        if not multiple_grids_enabled:
            self.combo_box.hide()

        spacer_right = QWidget()
        spacer_right.setSizePolicy(QSizePolicy.Expanding, 0)

        history_action = QAction(QIcon(resource("time.png")), "History", self)
        history_action.setToolTip("Show/Hide History")
        history_action.setFont(font)
        history_action.triggered.connect(self.on_history_button_clicked)

        self.history_button = QToolButton(self)
        self.history_button.setDefaultAction(history_action)
        self.history_button.setCheckable(True)
        self.history_button.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)

        recovery_action = QAction(QIcon(resource("key.png")), "Recovery", self)
        recovery_action.setToolTip("Import or Export a Recovery Key")
        recovery_action.setFont(font)

        import_action = QAction(QIcon(), "Import Recovery Key...", self)
        import_action.setToolTip("Import Recovery Key...")
        import_action.triggered.connect(self.import_recovery_key)

        export_action = QAction(QIcon(), "Export Recovery Key...", self)
        export_action.setToolTip("Export Recovery Key...")
        export_action.setShortcut(QKeySequence.Save)
        export_action.triggered.connect(self.export_recovery_key)

        recovery_menu = QMenu(self)
        recovery_menu.addAction(import_action)
        recovery_menu.addAction(export_action)

        recovery_button = QToolButton(self)
        recovery_button.setDefaultAction(recovery_action)
        recovery_button.setMenu(recovery_menu)
        recovery_button.setPopupMode(2)
        recovery_button.setStyleSheet(
            "QToolButton::menu-indicator { image: none }")
        recovery_button.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)

        self.toolbar = self.addToolBar("")
        p = self.palette()
        dimmer_grey = BlendedColor(p.windowText().color(),
                                   p.window().color(), 0.7).name()
        if sys.platform != "darwin":
            self.toolbar.setStyleSheet("""
                QToolBar {{ border: 0px }}
                QToolButton {{ color: {} }}
            """.format(dimmer_grey))
        else:
            self.toolbar.setStyleSheet(
                "QToolButton {{ color: {} }}".format(dimmer_grey))
        self.toolbar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
        self.toolbar.setIconSize(QSize(24, 24))
        self.toolbar.setMovable(False)
        self.toolbar.addAction(folder_action)
        if grid_invites_enabled:
            self.toolbar.addWidget(invites_button)
        elif invites_enabled:
            self.toolbar.addAction(invite_action)
        self.toolbar.addWidget(spacer_left)
        self.toolbar.addWidget(self.combo_box)
        self.toolbar.addWidget(spacer_right)
        self.toolbar.addWidget(self.history_button)
        self.toolbar.addWidget(recovery_button)

        if sys.platform != "win32":  # Text is getting clipped on Windows 10
            for action in self.toolbar.actions():
                widget = self.toolbar.widgetForAction(action)
                if isinstance(widget, QToolButton):
                    widget.setMaximumWidth(68)

        self.active_invite_sender_dialogs = []
        self.active_invite_receiver_dialogs = []

        self.pending_news_message = ()
示例#14
0
    def __init__(self, gui):
        super(MainWindow, self).__init__()
        self.gui = gui
        self.gateways = []
        self.welcome_dialog = None
        self.recovery_key_exporter = None

        self.setWindowTitle(APP_NAME)
        self.setMinimumSize(QSize(600, 400))
        self.setUnifiedTitleAndToolBarOnMac(True)

        self.shortcut_new = QShortcut(QKeySequence.New, self)
        self.shortcut_new.activated.connect(self.show_welcome_dialog)

        self.shortcut_open = QShortcut(QKeySequence.Open, self)
        self.shortcut_open.activated.connect(self.select_folder)

        self.shortcut_preferences = QShortcut(QKeySequence.Preferences, self)
        self.shortcut_preferences.activated.connect(
            self.gui.show_preferences_window)

        self.shortcut_close = QShortcut(QKeySequence.Close, self)
        self.shortcut_close.activated.connect(self.close)

        self.shortcut_quit = QShortcut(QKeySequence.Quit, self)
        self.shortcut_quit.activated.connect(self.confirm_quit)

        self.central_widget = CentralWidget(self.gui)
        self.setCentralWidget(self.central_widget)

        font = QFont()
        if sys.platform == 'darwin':
            font.setPointSize(11)
        else:
            font.setPointSize(8)

        folder_icon_default = QFileIconProvider().icon(QFileInfo(config_dir))
        folder_icon_composite = CompositePixmap(
            folder_icon_default.pixmap(256, 256), resource('green-plus.png'))
        folder_icon = QIcon(folder_icon_composite)

        folder_action = QAction(folder_icon, "Add Folder", self)
        folder_action.setToolTip("Add a Folder...")
        folder_action.setFont(font)
        folder_action.triggered.connect(self.select_folder)

        grid_invites_enabled = True
        features_settings = settings.get('features')
        if features_settings:
            grid_invites = features_settings.get('grid_invites')
            if grid_invites and grid_invites.lower() == 'false':
                grid_invites_enabled = False

        if grid_invites_enabled:
            invites_action = QAction(QIcon(resource('invite.png')), "Invites",
                                     self)
            invites_action.setToolTip("Enter or Create an Invite Code")
            invites_action.setFont(font)

            enter_invite_action = QAction(QIcon(), "Enter Invite Code...",
                                          self)
            enter_invite_action.setToolTip("Enter an Invite Code...")
            enter_invite_action.triggered.connect(self.open_invite_receiver)

            create_invite_action = QAction(QIcon(), "Create Invite Code...",
                                           self)
            create_invite_action.setToolTip("Create on Invite Code...")
            create_invite_action.triggered.connect(
                self.open_invite_sender_dialog)

            invites_menu = QMenu(self)
            invites_menu.addAction(enter_invite_action)
            invites_menu.addAction(create_invite_action)

            invites_button = QToolButton(self)
            invites_button.setDefaultAction(invites_action)
            invites_button.setMenu(invites_menu)
            invites_button.setPopupMode(2)
            invites_button.setStyleSheet(
                'QToolButton::menu-indicator { image: none }')
            invites_button.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)

        else:
            invite_action = QAction(QIcon(resource('invite.png')),
                                    "Enter Code", self)
            invite_action.setToolTip("Enter an Invite Code...")
            invite_action.setFont(font)
            invite_action.triggered.connect(self.open_invite_receiver)

        spacer_left = QWidget()
        spacer_left.setSizePolicy(QSizePolicy.Expanding, 0)

        self.combo_box = ComboBox()
        self.combo_box.currentIndexChanged.connect(self.on_grid_selected)

        spacer_right = QWidget()
        spacer_right.setSizePolicy(QSizePolicy.Expanding, 0)

        history_action = QAction(QIcon(resource('time.png')), 'History', self)
        history_action.setToolTip("Show/Hide History")
        history_action.setFont(font)
        history_action.triggered.connect(self.on_history_button_clicked)

        self.history_button = QToolButton(self)
        self.history_button.setDefaultAction(history_action)
        self.history_button.setCheckable(True)
        self.history_button.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)

        recovery_action = QAction(QIcon(resource('key.png')), "Recovery", self)
        recovery_action.setToolTip("Import or Export a Recovery Key")
        recovery_action.setFont(font)

        import_action = QAction(QIcon(), "Import Recovery Key...", self)
        import_action.setToolTip("Import Recovery Key...")
        import_action.triggered.connect(self.import_recovery_key)

        export_action = QAction(QIcon(), "Export Recovery Key...", self)
        export_action.setToolTip("Export Recovery Key...")
        export_action.setShortcut(QKeySequence.Save)
        export_action.triggered.connect(self.export_recovery_key)

        recovery_menu = QMenu(self)
        recovery_menu.addAction(import_action)
        recovery_menu.addAction(export_action)

        recovery_button = QToolButton(self)
        recovery_button.setDefaultAction(recovery_action)
        recovery_button.setMenu(recovery_menu)
        recovery_button.setPopupMode(2)
        recovery_button.setStyleSheet(
            'QToolButton::menu-indicator { image: none }')
        recovery_button.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)

        self.toolbar = self.addToolBar('')
        if sys.platform != 'darwin':
            self.toolbar.setStyleSheet("""
                QToolBar { border: 0px }
                QToolButton { color: rgb(50, 50, 50) }
            """)
        else:
            self.toolbar.setStyleSheet(
                "QToolButton { color: rgb(50, 50, 50) }")
        self.toolbar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
        self.toolbar.setIconSize(QSize(24, 24))
        self.toolbar.setMovable(False)
        self.toolbar.addAction(folder_action)
        if grid_invites_enabled:
            self.toolbar.addWidget(invites_button)
        else:
            self.toolbar.addAction(invite_action)
        self.toolbar.addWidget(spacer_left)
        self.toolbar.addWidget(self.combo_box)
        self.toolbar.addWidget(spacer_right)
        self.toolbar.addWidget(self.history_button)
        self.toolbar.addWidget(recovery_button)

        if sys.platform != 'win32':  # Text is getting clipped on Windows 10
            for action in self.toolbar.actions():
                widget = self.toolbar.widgetForAction(action)
                if isinstance(widget, QToolButton):
                    widget.setMaximumWidth(68)

        self.active_invite_sender_dialogs = []
        self.active_invite_receiver_dialogs = []