Esempio n. 1
0
class Application(QApplication):

    shutdown_signal_received = pyqtSignal()

    def __init__(self, args, force_calibre_style=False, override_program_name=None, headless=False, color_prefs=gprefs):
        self.file_event_hook = None
        if override_program_name:
            args = [override_program_name] + args[1:]
        if headless:
            if not args:
                args = sys.argv[:1]
            args.extend(['-platformpluginpath', sys.extensions_location, '-platform', 'headless'])
        self.headless = headless
        qargs = [i.encode('utf-8') if isinstance(i, unicode) else i for i in args]
        self.pi = plugins['progress_indicator'][0]
        if not isosx and not headless:
            # On OS X high dpi scaling is turned on automatically by the OS, so we dont need to set it explicitly
            setup_hidpi()
        QApplication.setOrganizationName('calibre-ebook.com')
        QApplication.setOrganizationDomain(QApplication.organizationName())
        QApplication.setApplicationVersion(__version__)
        QApplication.setApplicationName(APP_UID)
        QApplication.__init__(self, qargs)
        self.setAttribute(Qt.AA_UseHighDpiPixmaps)
        self.setAttribute(Qt.AA_SynthesizeTouchForUnhandledMouseEvents, False)
        try:
            base_dir()
        except EnvironmentError as err:
            if not headless:
                show_temp_dir_error(err)
            raise SystemExit('Failed to create temporary directory')
        if DEBUG and not headless:
            prints('devicePixelRatio:', self.devicePixelRatio())
            s = self.primaryScreen()
            if s:
                prints('logicalDpi:', s.logicalDotsPerInchX(), 'x', s.logicalDotsPerInchY())
                prints('physicalDpi:', s.physicalDotsPerInchX(), 'x', s.physicalDotsPerInchY())
        if not iswindows:
            self.setup_unix_signals()
        if islinux or isbsd:
            self.setAttribute(Qt.AA_DontUseNativeMenuBar, 'CALIBRE_NO_NATIVE_MENUBAR' in os.environ)
        self.setup_styles(force_calibre_style)
        self.setup_ui_font()
        if not self.using_calibre_style and self.style().objectName() == 'fusion':
            # Since Qt is using the fusion style anyway, specialize it
            self.load_calibre_style()
        fi = gprefs['font']
        if fi is not None:
            font = QFont(*(fi[:4]))
            s = gprefs.get('font_stretch', None)
            if s is not None:
                font.setStretch(s)
            QApplication.setFont(font)
        self.line_height = max(12, QFontMetrics(self.font()).lineSpacing())

        dl = QLocale(get_lang())
        if unicode(dl.bcp47Name()) != u'C':
            QLocale.setDefault(dl)
        global gui_thread, qt_app
        gui_thread = QThread.currentThread()
        self._translator = None
        self.load_translations()
        qt_app = self
        self._file_open_paths = []
        self._file_open_lock = RLock()

        if not isosx:
            # OS X uses a native color dialog that does not support custom
            # colors
            self.color_prefs = color_prefs
            self.read_custom_colors()
            self.lastWindowClosed.connect(self.save_custom_colors)

        if isxp:
            error_dialog(None, _('Windows XP not supported'), '<p>' + _(
                'calibre versions newer than 2.0 do not run on Windows XP. This is'
                ' because the graphics toolkit calibre uses (Qt 5) crashes a lot'
                ' on Windows XP. We suggest you stay with <a href="%s">calibre 1.48</a>'
                ' which works well on Windows XP.') % 'http://download.calibre-ebook.com/1.48.0/', show=True)
            raise SystemExit(1)

        if iswindows:
            # On windows the highlighted colors for inactive widgets are the
            # same as non highlighted colors. This is a regression from Qt 4.
            # https://bugreports.qt-project.org/browse/QTBUG-41060
            p = self.palette()
            for role in (p.Highlight, p.HighlightedText, p.Base, p.AlternateBase):
                p.setColor(p.Inactive, role, p.color(p.Active, role))
            self.setPalette(p)

            # Prevent text copied to the clipboard from being lost on quit due to
            # Qt 5 bug: https://bugreports.qt-project.org/browse/QTBUG-41125
            self.aboutToQuit.connect(self.flush_clipboard)

    def setup_ui_font(self):
        f = QFont(QApplication.font())
        q = (f.family(), f.pointSize())
        if iswindows:
            if q == ('MS Shell Dlg 2', 8):  # Qt default setting
                # Microsoft recommends the default font be Segoe UI at 9 pt
                # https://msdn.microsoft.com/en-us/library/windows/desktop/dn742483(v=vs.85).aspx
                f.setFamily('Segoe UI')
                f.setPointSize(9)
                QApplication.setFont(f)
        else:
            if q == ('Sans Serif', 9):  # Hard coded Qt settings, no user preference detected
                f.setPointSize(10)
                QApplication.setFont(f)
        f = QFontInfo(f)
        self.original_font = (f.family(), f.pointSize(), f.weight(), f.italic(), 100)

    def flush_clipboard(self):
        try:
            if self.clipboard().ownsClipboard():
                import ctypes
                ctypes.WinDLL(b'ole32.dll').OleFlushClipboard()
        except Exception:
            import traceback
            traceback.print_exc()

    def load_builtin_fonts(self, scan_for_fonts=False):
        if scan_for_fonts:
            from calibre.utils.fonts.scanner import font_scanner
            # Start scanning the users computer for fonts
            font_scanner

        load_builtin_fonts()

    def setup_styles(self, force_calibre_style):
        if iswindows or isosx:
            using_calibre_style = gprefs['ui_style'] != 'system'
        else:
            using_calibre_style = 'CALIBRE_USE_SYSTEM_THEME' not in os.environ
        if force_calibre_style:
            using_calibre_style = True
        self.using_calibre_style = using_calibre_style
        if DEBUG:
            prints('Using calibre Qt style:', self.using_calibre_style)
        if self.using_calibre_style:
            self.load_calibre_style()

    def load_calibre_style(self):
        icon_map = self.__icon_map_memory_ = {}
        pcache = {}
        for k, v in {
            'DialogYesButton': u'ok.png',
            'DialogNoButton': u'window-close.png',
            'DialogCloseButton': u'window-close.png',
            'DialogOkButton': u'ok.png',
            'DialogCancelButton': u'window-close.png',
            'DialogHelpButton': u'help.png',
            'DialogOpenButton': u'document_open.png',
            'DialogSaveButton': u'save.png',
            'DialogApplyButton': u'ok.png',
            'DialogDiscardButton': u'trash.png',
            'MessageBoxInformation': u'dialog_information.png',
            'MessageBoxWarning': u'dialog_warning.png',
            'MessageBoxCritical': u'dialog_error.png',
            'MessageBoxQuestion': u'dialog_question.png',
            'BrowserReload': u'view-refresh.png',
        }.iteritems():
            if v not in pcache:
                p = I(v)
                if isinstance(p, bytes):
                    p = p.decode(filesystem_encoding)
                # if not os.path.exists(p): raise ValueError(p)
                pcache[v] = p
            v = pcache[v]
            icon_map[getattr(QStyle, 'SP_'+k)] = v
        self.pi.load_style(icon_map)

    def _send_file_open_events(self):
        with self._file_open_lock:
            if self._file_open_paths:
                self.file_event_hook(self._file_open_paths)
                self._file_open_paths = []

    def load_translations(self):
        if self._translator is not None:
            self.removeTranslator(self._translator)
        self._translator = Translator(self)
        self.installTranslator(self._translator)

    def event(self, e):
        if callable(self.file_event_hook) and e.type() == QEvent.FileOpen:
            path = unicode(e.file())
            if os.access(path, os.R_OK):
                with self._file_open_lock:
                    self._file_open_paths.append(path)
                QTimer.singleShot(1000, self._send_file_open_events)
            return True
        else:
            return QApplication.event(self, e)

    @dynamic_property
    def current_custom_colors(self):
        from PyQt5.Qt import QColorDialog, QColor

        def fget(self):
            return [col.getRgb() for col in
                    (QColorDialog.customColor(i) for i in xrange(QColorDialog.customCount()))]

        def fset(self, colors):
            num = min(len(colors), QColorDialog.customCount())
            for i in xrange(num):
                QColorDialog.setCustomColor(i, QColor(*colors[i]))
        return property(fget=fget, fset=fset)

    def read_custom_colors(self):
        colors = self.color_prefs.get('custom_colors_for_color_dialog', None)
        if colors is not None:
            self.current_custom_colors = colors

    def save_custom_colors(self):
        # Qt 5 regression, it no longer saves custom colors
        colors = self.current_custom_colors
        if colors != self.color_prefs.get('custom_colors_for_color_dialog', None):
            self.color_prefs.set('custom_colors_for_color_dialog', colors)

    def __enter__(self):
        self.setQuitOnLastWindowClosed(False)

    def __exit__(self, *args):
        self.setQuitOnLastWindowClosed(True)

    def setup_unix_signals(self):
        import fcntl
        read_fd, write_fd = os.pipe()
        cloexec_flag = getattr(fcntl, 'FD_CLOEXEC', 1)
        for fd in (read_fd, write_fd):
            flags = fcntl.fcntl(fd, fcntl.F_GETFD)
            fcntl.fcntl(fd, fcntl.F_SETFD, flags | cloexec_flag | os.O_NONBLOCK)
        for sig in (signal.SIGINT, signal.SIGTERM):
            signal.signal(sig, lambda x, y: None)
            signal.siginterrupt(sig, False)
        signal.set_wakeup_fd(write_fd)
        self.signal_notifier = QSocketNotifier(read_fd, QSocketNotifier.Read, self)
        self.signal_notifier.setEnabled(True)
        self.signal_notifier.activated.connect(self.signal_received, type=Qt.QueuedConnection)

    def signal_received(self, read_fd):
        try:
            os.read(read_fd, 1024)
        except EnvironmentError:
            return
        self.shutdown_signal_received.emit()
Esempio n. 2
0
class Application(QApplication):

    password_loaded = pyqtSignal(object, object)

    def __init__(self,
                 master_password=None,
                 urls=(),
                 new_instance=False,
                 shutdown=False,
                 restart_state=None,
                 no_session=False,
                 run_local_server=True):
        if not isosx:  # OS X turns this on automatically
            for v in ('QT_AUTO_SCREEN_SCALE_FACTOR', 'QT_SCALE_FACTOR',
                      'QT_SCREEN_SCALE_FACTORS', 'QT_DEVICE_PIXEL_RATIO'):
                if os.environ.get(v):
                    break
            else:
                QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
        QApplication.__init__(self, [appname, '-name', appname])
        self.setAttribute(Qt.AA_UseHighDpiPixmaps)
        self.setOrganizationName('kovidgoyal')
        self.setApplicationName(appname)
        self.setApplicationVersion(str_version)
        self.no_session = no_session
        self.handle_unix_signals()
        if not QSslSocket.supportsSsl():
            raise SystemExit('Qt has been compiled without SSL support!')
        from .config import font_families, font_sizes
        ff = font_families().get('sans-serif') or 'default'
        if ff == 'default':
            ff = font_families().get('default') or 'default'
        f = self.font()
        if ff != 'default':
            f.setFamily(ff)
            fs = font_sizes().get('default-size')
            try:
                f.setPixelSize(int(fs))
            except Exception:
                pass
        self.setFont(f)
        self.password_loaded.connect(self.on_password_load,
                                     type=Qt.QueuedConnection)
        if master_password is not None:
            password_db.start_load(master_password, self.password_loaded.emit)
        elif restart_state and 'key' in restart_state:
            password_db.start_load(restart_state.pop('key'),
                                   self.password_loaded.emit,
                                   pw_is_key=True)

        self.lastWindowClosed.connect(self.shutdown)
        if run_local_server:
            self.run_local_server(urls, new_instance, shutdown)
        sys.excepthook = self.on_unhandled_error
        self.windows = []
        f = self.font()
        if (f.family(), f.pointSize()) == (
                'Sans Serif',
                9):  # Hard coded Qt settings, no user preference detected
            f.setPointSize(10)
            if 'Ubuntu' in QFontDatabase().families():
                f.setFamily('Ubuntu')
            self.setFont(f)
        self.downloads = Downloads(self)
        self.disk_cache = create_favicon_cache()
        self.key_filter = KeyFilter(self)
        self.installEventFilter(self.key_filter)

    def handle_unix_signals(self):
        if iswindows:
            # TODO: test this on windows
            self.signal_read_socket, self.signal_write_socket = socket.socketpair(
            )
            self.signal_read_socket.setblocking(False)
            self.signal_write_socket.setblocking(False)
            read_fd, write_fd = self.signal_read_socket.fileno(
            ), self.signal_write_socket.fileno()
        else:
            read_fd, write_fd = os.pipe2(os.O_NONBLOCK | os.O_CLOEXEC)
        for sig in (signal.SIGINT, signal.SIGTERM):
            signal.signal(sig, lambda x, y: None)
            signal.siginterrupt(sig, False)
        signal.set_wakeup_fd(write_fd)
        self.signal_notifier = QSocketNotifier(read_fd, QSocketNotifier.Read,
                                               self)
        self.signal_notifier.setEnabled(True)
        self.signal_notifier.activated.connect(self.signal_received,
                                               type=Qt.QueuedConnection)

    def signal_received(self, read_fd):
        try:
            data = self.signal_read_socket.recv(
                1024) if iswindows else os.read(read_fd, 1024)
        except BlockingIOError:
            return
        if data:
            signals = struct.unpack('%uB' % len(data), data)
            if signal.SIGINT in signals or signal.SIGTERM in signals:
                self.shutdown()

    def show_password_load_error(self, error, tb, parent=None):
        error_dialog(
            parent or (self.windows[0] if self.windows else None),
            _('Failed to load password database'),
            _('There was an error processing the password database:') +
            '<br>' + str(error),
            det_msg=tb,
            show=True)

    def on_password_load(self, error, tb):
        if error:
            self.show_password_load_error(error, tb)

    def ask_for_master_password(self, parent=None):
        from .passwd.gui import AskForPassword
        with BusyCursor():
            pw_loaded = password_db.join()
        if not pw_loaded:
            while True:
                d = AskForPassword(
                    parent=parent,
                    create_password=not password_db.has_password())
                if d.exec_() != AskForPassword.Accepted:
                    return
                with BusyCursor():
                    password_db.start_load(d.password)
                    password_db.join()
                if password_db.error[0] is None:
                    break
                self.show_password_load_error(password_db.error[0],
                                              password_db.error[1],
                                              parent=parent)
        return password_db.is_loaded

    def store_password(self, url, username, password):
        key = key_from_url(url)
        password_db.add_account(key, username, password)

    def run_local_server(self, urls, new_instance, shutdown):
        if shutdown:
            new_instance = False
        server_name = local_socket_address()
        s = QLocalSocket()
        s.connectToServer(server_name)
        if s.waitForConnected(500):
            if new_instance:
                return
            stream = QTextStream(s)
            stream.setCodec('UTF-8')
            if shutdown:
                cargs = json.dumps({'action': 'shutdown'})
            else:
                cargs = json.dumps({'open': urls}, ensure_ascii=False)
            stream << cargs
            stream.flush()
            s.waitForBytesWritten()
            raise SystemExit(0)
        if shutdown:
            raise SystemExit('No running vise instance found')
        self.local_server = ls = QLocalServer(self)
        ls.newConnection.connect(self.another_instance_wants_to_talk)
        if not ls.listen(server_name):
            if ls.serverError() == QAbstractSocket.AddressInUseError:
                try:
                    os.remove(server_name)
                except FileNotFoundError:
                    pass
            if not ls.listen(server_name):
                raise SystemExit(
                    'Failed to establish local listening socket at: %s with error: %s'
                    % (server_name, ls.errorString()))

    def another_instance_wants_to_talk(self):
        s = self.local_server.nextPendingConnection()
        if s is None:
            return
        s.waitForReadyRead(1000)
        stream = QTextStream(s)
        stream.setCodec('UTF-8')
        raw = stream.readAll()
        try:
            command = json.loads(raw)
        except Exception as e:
            self.error('Invalid data from other instance: %s' % e)
            return
        finally:
            s.close()
            del s
        if not isinstance(command, dict):
            self.error('Invalid data from other instance: %r' % command)
            return
        ac = command.get('action')
        if ac == 'shutdown':
            return self.shutdown()
        urls = command.get('open', [])
        if not isinstance(urls, list):
            self.error('Invalid data from other instance: %r' % command)
            return
        urls = [x for x in urls if isinstance(x, str)]
        if urls:
            self.open_urls(urls, in_current_tab='dynamic', switch_to_tab=True)

    def save_favicon_in_cache(self, icon, qurl):
        md = QNetworkCacheMetaData()
        md.setUrl(qurl)
        md.setSaveToDisk(True)
        dio = self.disk_cache.prepare(md)
        if dio:
            ic = icon_to_data(icon, w=None)
            if ic:
                while len(ic) > 0:
                    written = dio.write(ic)
                    if written < 0:
                        print('Failed to save favicon with error:',
                              dio.errorString())
                        return  # error occurred
                    ic = ic[written:]
                self.disk_cache.insert(dio)

    def shutdown(self):
        self.lastWindowClosed.disconnect()
        if not self.no_session:
            state = self.serialize_state()
            state = pickle.dumps(state, pickle.HIGHEST_PROTOCOL)
            f = tempfile.NamedTemporaryFile(dir=cache_dir)
            try:
                f.write(state)
                os.replace(f.name,
                           os.path.join(cache_dir, 'last-session.pickle'))
            finally:
                try:
                    f.close()
                except FileNotFoundError:
                    pass
        for w in self.windows:
            w.close()

    def new_window(self, is_private=False, restart_state=None):
        w = MainWindow(is_private=is_private, restart_state=restart_state)
        w.window_closed.connect(self.remove_window, type=Qt.QueuedConnection)
        self.windows.append(w)
        return w

    def remove_window(self, window):
        window.break_cycles()
        try:
            self.windows.remove(window)
        except ValueError:
            pass

    def open_urls(self, urls, in_current_tab=True, switch_to_tab=False):
        if not self.windows:
            self.new_window().show()
        w = self.activeWindow() or self.windows[0]
        if in_current_tab == 'dynamic':
            in_current_tab = w.current_tab is not None and w.current_tab.url(
            ) == WELCOME_URL
        for i, url in enumerate(urls):
            w.open_url(parse_url(url),
                       in_current_tab=in_current_tab and i == 0,
                       switch_to_tab=switch_to_tab and i == 0)

    def error(self, *args, **kwargs):
        kwargs['file'] = sys.stderr
        prefix = '[%s %s]' % (appname, datetime.now().isoformat(' '))
        try:
            print(prefix, *args, **kwargs)
        except OSError:
            pass

    def on_unhandled_error(self, etype, value, tb):
        if etype == KeyboardInterrupt:
            return
        sys.__excepthook__(etype, value, tb)
        try:
            msg = str(value)
        except Exception:
            msg = repr(value)
        msg = '<p>' + msg + '<br>' + _(
            'Click "Show details" for more information')
        det_msg = '%s: %s\n%s' % (appname, str_version, ''.join(
            traceback.format_exception(etype, value, tb)))
        parent = self.activeWindow()
        d = error_dialog(parent,
                         _('Unhandled exception'),
                         msg,
                         det_msg=det_msg,
                         show=False)
        b = d.shutdown_button = d.bb.addButton(_('Shutdown'), d.bb.ActionRole)
        b.clicked.connect(lambda: self.exit(1))
        d.exec_()

    def break_cycles(self):
        # Make sure the application object has no references in python and the
        # other global objects can also be garbage collected

        # Reset excepthook otherwise we get a segfault on exit, since the application object is deleted
        # before we exit
        sys.excepthook = sys.__excepthook__
        if hasattr(self, 'local_server'):
            self.local_server.close()
            del self.local_server
        self.downloads.break_cycles()
        for w in self.windows:
            w.break_cycles()
            w.deleteLater()
        for s in (self.password_loaded, ):
            s.disconnect()
        del self.windows

    def serialize_state(self, include_favicons=False, include_key=False):
        ans = {
            'windows':
            [w.serialize_state(include_favicons) for w in self.windows]
        }
        w = self.activeWindow()
        if getattr(w, 'window_id', None) is not None:
            ans['active_window'] = w.window_id
        if include_key and password_db.is_loaded and password_db.key and not password_db.key_error:
            ans['key'] = password_db.key
        return ans

    def unserialize_state(self, state):
        active_window = state.get('active_window')
        for wstate in state['windows']:
            w = self.new_window(restart_state=wstate,
                                is_private=wstate['is_private'])
            w.show()
            if wstate['window_id'] == active_window:
                active_window = w
        if hasattr(active_window,
                   'raise_') and self.activeWindow() != active_window and len(
                       self.windows) > 1:
            w.raise_()

    def restart_app(self):
        password_db.join()
        state = self.serialize_state(include_key=True)
        self.restart_state = pickle.dumps(state, pickle.HIGHEST_PROTOCOL)
        self.no_session = True
        self.shutdown()
Esempio n. 3
0
class Application(QApplication):

    password_loaded = pyqtSignal(object, object)

    def __init__(self, master_password=None, urls=(), new_instance=False, shutdown=False, restart_state=None, no_session=False, run_local_server=True):
        if not isosx:  # OS X turns this on automatically
            for v in ('QT_AUTO_SCREEN_SCALE_FACTOR', 'QT_SCALE_FACTOR', 'QT_SCREEN_SCALE_FACTORS', 'QT_DEVICE_PIXEL_RATIO'):
                if os.environ.get(v):
                    break
            else:
                QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
        QApplication.__init__(self, [appname, '-name', appname])
        self.setAttribute(Qt.AA_UseHighDpiPixmaps)
        self.setOrganizationName('kovidgoyal')
        self.setApplicationName(appname)
        self.setApplicationVersion(str_version)
        self.no_session = no_session
        self.handle_unix_signals()
        if not QSslSocket.supportsSsl():
            raise SystemExit('Qt has been compiled without SSL support!')
        from .config import font_families, font_sizes
        ff = font_families().get('sans-serif') or 'default'
        if ff == 'default':
            ff = font_families().get('default') or 'default'
        f = self.font()
        if ff != 'default':
            f.setFamily(ff)
            fs = font_sizes().get('default-size')
            try:
                f.setPixelSize(int(fs))
            except Exception:
                pass
        self.setFont(f)
        self.password_loaded.connect(self.on_password_load, type=Qt.QueuedConnection)
        if master_password is not None:
            password_db.start_load(master_password, self.password_loaded.emit)
        elif restart_state and 'key' in restart_state:
            password_db.start_load(restart_state.pop('key'), self.password_loaded.emit, pw_is_key=True)

        self.lastWindowClosed.connect(self.shutdown)
        if run_local_server:
            self.run_local_server(urls, new_instance, shutdown)
        sys.excepthook = self.on_unhandled_error
        self.windows = []
        f = self.font()
        if (f.family(), f.pointSize()) == ('Sans Serif', 9):  # Hard coded Qt settings, no user preference detected
            f.setPointSize(10)
            if 'Ubuntu' in QFontDatabase().families():
                f.setFamily('Ubuntu')
            self.setFont(f)
        self.downloads = Downloads(self)
        self.disk_cache = create_favicon_cache()
        self.key_filter = KeyFilter(self)
        self.installEventFilter(self.key_filter)

    def handle_unix_signals(self):
        if iswindows:
            # TODO: test this on windows
            self.signal_read_socket, self.signal_write_socket = socket.socketpair()
            self.signal_read_socket.setblocking(False)
            self.signal_write_socket.setblocking(False)
            read_fd, write_fd = self.signal_read_socket.fileno(), self.signal_write_socket.fileno()
        else:
            read_fd, write_fd = os.pipe2(os.O_NONBLOCK | os.O_CLOEXEC)
        for sig in (signal.SIGINT, signal.SIGTERM):
            signal.signal(sig, lambda x, y: None)
            signal.siginterrupt(sig, False)
        signal.set_wakeup_fd(write_fd)
        self.signal_notifier = QSocketNotifier(read_fd, QSocketNotifier.Read, self)
        self.signal_notifier.setEnabled(True)
        self.signal_notifier.activated.connect(self.signal_received, type=Qt.QueuedConnection)

    def signal_received(self, read_fd):
        try:
            data = self.signal_read_socket.recv(1024) if iswindows else os.read(read_fd, 1024)
        except BlockingIOError:
            return
        if data:
            signals = struct.unpack('%uB' % len(data), data)
            if signal.SIGINT in signals or signal.SIGTERM in signals:
                self.shutdown()

    def show_password_load_error(self, error, tb, parent=None):
        error_dialog(parent or (self.windows[0] if self.windows else None), _('Failed to load password database'), _(
            'There was an error processing the password database:') + '<br>' + str(error), det_msg=tb, show=True)

    def on_password_load(self, error, tb):
        if error:
            self.show_password_load_error(error, tb)

    def ask_for_master_password(self, parent=None):
        from .passwd.gui import AskForPassword
        with BusyCursor():
            pw_loaded = password_db.join()
        if not pw_loaded:
            while True:
                d = AskForPassword(parent=parent, create_password=not password_db.has_password())
                if d.exec_() != AskForPassword.Accepted:
                    return
                with BusyCursor():
                    password_db.start_load(d.password)
                    password_db.join()
                if password_db.error[0] is None:
                    break
                self.show_password_load_error(password_db.error[0], password_db.error[1], parent=parent)
        return password_db.is_loaded

    def store_password(self, url, username, password):
        key = key_from_url(url)
        password_db.add_account(key, username, password)

    def run_local_server(self, urls, new_instance, shutdown):
        if shutdown:
            new_instance = False
        server_name = local_socket_address()
        s = QLocalSocket()
        s.connectToServer(server_name)
        if s.waitForConnected(500):
            if new_instance:
                return
            stream = QTextStream(s)
            stream.setCodec('UTF-8')
            if shutdown:
                cargs = json.dumps({'action': 'shutdown'})
            else:
                cargs = json.dumps({'open': urls}, ensure_ascii=False)
            stream << cargs
            stream.flush()
            s.waitForBytesWritten()
            raise SystemExit(0)
        if shutdown:
            raise SystemExit('No running vise instance found')
        self.local_server = ls = QLocalServer(self)
        ls.newConnection.connect(self.another_instance_wants_to_talk)
        if not ls.listen(server_name):
            if ls.serverError() == QAbstractSocket.AddressInUseError:
                try:
                    os.remove(server_name)
                except FileNotFoundError:
                    pass
            if not ls.listen(server_name):
                raise SystemExit('Failed to establish local listening socket at: %s with error: %s' % (
                    server_name, ls.errorString()))

    def another_instance_wants_to_talk(self):
        s = self.local_server.nextPendingConnection()
        if s is None:
            return
        s.waitForReadyRead(1000)
        stream = QTextStream(s)
        stream.setCodec('UTF-8')
        raw = stream.readAll()
        try:
            command = json.loads(raw)
        except Exception as e:
            self.error('Invalid data from other instance: %s' % e)
            return
        finally:
            s.close()
            del s
        if not isinstance(command, dict):
            self.error('Invalid data from other instance: %r' % command)
            return
        ac = command.get('action')
        if ac == 'shutdown':
            return self.shutdown()
        urls = command.get('open', [])
        if not isinstance(urls, list):
            self.error('Invalid data from other instance: %r' % command)
            return
        urls = [x for x in urls if isinstance(x, str)]
        if urls:
            self.open_urls(urls, in_current_tab='dynamic', switch_to_tab=True)

    def save_favicon_in_cache(self, icon, qurl):
        md = QNetworkCacheMetaData()
        md.setUrl(qurl)
        md.setSaveToDisk(True)
        dio = self.disk_cache.prepare(md)
        if dio:
            ic = icon_to_data(icon, w=None)
            if ic:
                while len(ic) > 0:
                    written = dio.write(ic)
                    if written < 0:
                        print('Failed to save favicon with error:', dio.errorString())
                        return  # error occurred
                    ic = ic[written:]
                self.disk_cache.insert(dio)

    def shutdown(self):
        self.lastWindowClosed.disconnect()
        if not self.no_session:
            state = self.serialize_state()
            state = pickle.dumps(state, pickle.HIGHEST_PROTOCOL)
            f = tempfile.NamedTemporaryFile(dir=cache_dir)
            try:
                f.write(state)
                os.replace(f.name, os.path.join(cache_dir, 'last-session.pickle'))
            finally:
                try:
                    f.close()
                except FileNotFoundError:
                    pass
        for w in self.windows:
            w.close()

    def new_window(self, is_private=False, restart_state=None):
        w = MainWindow(is_private=is_private, restart_state=restart_state)
        w.window_closed.connect(self.remove_window, type=Qt.QueuedConnection)
        self.windows.append(w)
        return w

    def remove_window(self, window):
        window.break_cycles()
        try:
            self.windows.remove(window)
        except ValueError:
            pass

    def open_urls(self, urls, in_current_tab=True, switch_to_tab=False):
        if not self.windows:
            self.new_window().show()
        w = self.activeWindow() or self.windows[0]
        if in_current_tab == 'dynamic':
            in_current_tab = w.current_tab is not None and w.current_tab.url() == WELCOME_URL
        for i, url in enumerate(urls):
            w.open_url(parse_url(url), in_current_tab=in_current_tab and i == 0, switch_to_tab=switch_to_tab and i == 0)

    def error(self, *args, **kwargs):
        kwargs['file'] = sys.stderr
        prefix = '[%s %s]' % (appname, datetime.now().isoformat(' '))
        try:
            print(prefix, *args, **kwargs)
        except OSError:
            pass

    def on_unhandled_error(self, etype, value, tb):
        if etype == KeyboardInterrupt:
            return
        sys.__excepthook__(etype, value, tb)
        try:
            msg = str(value)
        except Exception:
            msg = repr(value)
        msg = '<p>' + msg + '<br>' + _('Click "Show details" for more information')
        det_msg = '%s: %s\n%s' % (appname, str_version, ''.join(traceback.format_exception(etype, value, tb)))
        parent = self.activeWindow()
        d = error_dialog(parent, _('Unhandled exception'), msg, det_msg=det_msg, show=False)
        b = d.shutdown_button = d.bb.addButton(_('Shutdown'), d.bb.ActionRole)
        b.clicked.connect(lambda: self.exit(1))
        d.exec_()

    def break_cycles(self):
        # Make sure the application object has no references in python and the
        # other global objects can also be garbage collected

        # Reset excepthook otherwise we get a segfault on exit, since the application object is deleted
        # before we exit
        sys.excepthook = sys.__excepthook__
        if hasattr(self, 'local_server'):
            self.local_server.close()
            del self.local_server
        self.downloads.break_cycles()
        for w in self.windows:
            w.break_cycles()
            w.deleteLater()
        for s in (self.password_loaded,):
            s.disconnect()
        del self.windows

    def serialize_state(self, include_favicons=False, include_key=False):
        ans = {'windows': [w.serialize_state(include_favicons) for w in self.windows]}
        w = self.activeWindow()
        if getattr(w, 'window_id', None) is not None:
            ans['active_window'] = w.window_id
        if include_key and password_db.is_loaded and password_db.key and not password_db.key_error:
            ans['key'] = password_db.key
        return ans

    def unserialize_state(self, state):
        active_window = state.get('active_window')
        for wstate in state['windows']:
            w = self.new_window(restart_state=wstate, is_private=wstate['is_private'])
            w.show()
            if wstate['window_id'] == active_window:
                active_window = w
        if hasattr(active_window, 'raise_') and self.activeWindow() != active_window and len(self.windows) > 1:
            w.raise_()

    def restart_app(self):
        password_db.join()
        state = self.serialize_state(include_key=True)
        self.restart_state = pickle.dumps(state, pickle.HIGHEST_PROTOCOL)
        self.no_session = True
        self.shutdown()