Example #1
0
    def __init__(self, auto_exit, *args, **kwargs):
        super(GUI, self).__init__(*args, **kwargs)

        self.auto_exit = auto_exit

        self.set_wmclass(APP_NAME, APP_NAME)
        self.populate_window()

        # Redirect logging to the GUI.
        bb_logger = logging.getLogger('bleachbit')
        from bleachbit.Log import GtkLoggerHandler
        self.gtklog = GtkLoggerHandler(self.append_text)
        bb_logger.addHandler(self.gtklog)

        # process any delayed logs
        from bleachbit.Log import DelayLog
        if isinstance(sys.stderr, DelayLog):
            for msg in sys.stderr.read():
                self.append_text(msg)
            # if stderr was redirected - keep redirecting it
            sys.stderr = self.gtklog

        self.set_windows10_theme()
        Gtk.Settings.get_default().set_property(
            'gtk-application-prefer-dark-theme', options.get('dark_mode'))

        if options.is_corrupt():
            logger.error(
                _('Resetting the configuration file because it is corrupt: %s') % bleachbit.options_file)
            bleachbit.Options.init_configuration()

        GLib.idle_add(self.cb_refresh_operations)
Example #2
0
class GUI(Gtk.ApplicationWindow):
    """The main application GUI"""
    _style_provider = None
    _style_provider_regular = None
    _style_provider_dark = None

    def __init__(self, auto_exit, *args, **kwargs):
        super(GUI, self).__init__(*args, **kwargs)

        self.auto_exit = auto_exit

        self.set_wmclass(APP_NAME, APP_NAME)
        self.populate_window()

        # Redirect logging to the GUI.
        bb_logger = logging.getLogger('bleachbit')
        from bleachbit.Log import GtkLoggerHandler
        self.gtklog = GtkLoggerHandler(self.append_text)
        bb_logger.addHandler(self.gtklog)

        # process any delayed logs
        from bleachbit.Log import DelayLog
        if isinstance(sys.stderr, DelayLog):
            for msg in sys.stderr.read():
                self.append_text(msg)
            # if stderr was redirected - keep redirecting it
            sys.stderr = self.gtklog

        self.set_windows10_theme()
        Gtk.Settings.get_default().set_property(
            'gtk-application-prefer-dark-theme', options.get('dark_mode'))

        if options.is_corrupt():
            logger.error(
                _('Resetting the configuration file because it is corrupt: %s') % bleachbit.options_file)
            bleachbit.Options.init_configuration()

        GLib.idle_add(self.cb_refresh_operations)

    def get_preferences_dialog(self):
        return PreferencesDialog(
            self,
            self.cb_refresh_operations,
            self.set_windows10_theme)

    def shred_paths(self, paths, shred_settings=False):
        """Shred file or folders

        When shredding_settings=True:
        If user confirms to delete, then returns True.  If user aborts, returns
        False.

        When quit_when_done=True:
        Always returns False to remove function from the idle queue.
        """
        # create a temporary cleaner object
        backends['_gui'] = Cleaner.create_simple_cleaner(paths)

        # preview and confirm
        operations = {'_gui': ['files']}
        self.preview_or_run_operations(False, operations)

        if GuiBasic.delete_confirmation_dialog(self, mention_preview=False, shred_settings=shred_settings):
            # delete
            self.preview_or_run_operations(True, operations)
            return True

        # user aborted
        return False

    def append_text(self, text, tag=None, __iter=None, scroll=True):
        """Add some text to the main log"""
        if not __iter:
            __iter = self.textbuffer.get_end_iter()
        if tag:
            self.textbuffer.insert_with_tags_by_name(__iter, text, tag)
        else:
            self.textbuffer.insert(__iter, text)
        # Scroll to end.  If the command is run directly instead of
        # through the idle loop, it may only scroll most of the way
        # as seen on Ubuntu 9.04 with Italian and Spanish.
        if scroll:
            GLib.idle_add(lambda:
                          self.textview.scroll_mark_onscreen(
                              self.textbuffer.get_insert()))

    def update_log_level(self):
        """This gets called when the log level might have changed via the preferences."""
        self.gtklog.update_log_level()

    def on_selection_changed(self, selection):
        """When the tree view selection changed"""
        model = self.view.get_model()
        selected_rows = selection.get_selected_rows()
        if not selected_rows[1]:  # empty
            # happens when searching in the tree view
            return
        paths = selected_rows[1][0]
        row = paths[0]
        name = model[row][0]
        cleaner_id = model[row][2]
        self.progressbar.hide()
        description = backends[cleaner_id].get_description()
        self.textbuffer.set_text("")
        self.append_text(name + "\n", 'operation', scroll=False)
        if not description:
            description = ""
        self.append_text(description + "\n\n\n", 'description', scroll=False)
        for (label, description) in backends[cleaner_id].get_option_descriptions():
            self.append_text(label, 'option_label', scroll=False)
            if description:
                self.append_text(': ', 'option_label', scroll=False)
                self.append_text(description, scroll=False)
            self.append_text("\n\n", scroll=False)

    def get_selected_operations(self):
        """Return a list of the IDs of the selected operations in the tree view"""
        ret = []
        model = self.tree_store.get_model()
        path = Gtk.TreePath(0)
        __iter = model.get_iter(path)
        while __iter:
            if model[__iter][1]:
                ret.append(model[__iter][2])
            __iter = model.iter_next(__iter)
        return ret

    def get_operation_options(self, operation):
        """For the given operation ID, return a list of the selected option IDs."""
        ret = []
        model = self.tree_store.get_model()
        path = Gtk.TreePath(0)
        __iter = model.get_iter(path)
        while __iter:
            if operation == model[__iter][2]:
                iterc = model.iter_children(__iter)
                if not iterc:
                    return None
                while iterc:
                    if model[iterc][1]:
                        # option is enabled
                        ret.append(model[iterc][2])
                    iterc = model.iter_next(iterc)
                return ret
            __iter = model.iter_next(__iter)
        return None

    def set_sensitive(self, is_sensitive):
        """Disable commands while an operation is running"""
        self.view.set_sensitive(is_sensitive)
        self.preview_button.set_sensitive(is_sensitive)
        self.run_button.set_sensitive(is_sensitive)
        self.stop_button.set_sensitive(not is_sensitive)

    def run_operations(self, __widget):
        """Event when the 'delete' toolbar button is clicked."""
        # fixme: should present this dialog after finding operations

        # Disable delete confirmation message.
        # if the option is selected under preference.

        if options.get("delete_confirmation"):
            if not GuiBasic.delete_confirmation_dialog(self, True):
                return
        self.preview_or_run_operations(True)

    def preview_or_run_operations(self, really_delete, operations=None):
        """Preview operations or run operations (delete files)"""

        assert isinstance(really_delete, bool)
        from bleachbit import Worker
        self.start_time = None
        if not operations:
            operations = {}
            for operation in self.get_selected_operations():
                operations[operation] = self.get_operation_options(operation)
        assert isinstance(operations, dict)
        if not operations:  # empty
            GuiBasic.message_dialog(self,
                                    _("You must select an operation"),
                                    Gtk.MessageType.WARNING, Gtk.ButtonsType.OK)
            return
        try:
            self.set_sensitive(False)
            self.textbuffer.set_text("")
            self.progressbar.show()
            self.worker = Worker.Worker(self, really_delete, operations)
        except Exception:
            logger.exception('Error in Worker()')
        else:
            self.start_time = time.time()
            worker = self.worker.run()
            GLib.idle_add(worker.__next__)

    def worker_done(self, worker, really_delete):
        """Callback for when Worker is done"""
        self.progressbar.set_text("")
        self.progressbar.set_fraction(1)
        self.progressbar.set_text(_("Done."))
        self.textview.scroll_mark_onscreen(self.textbuffer.get_insert())
        self.set_sensitive(True)

        # Close the program after cleaning is completed.
        # if the option is selected under preference.

        if really_delete:
            if options.get("exit_done"):
                sys.exit()

        # notification for long-running process
        elapsed = (time.time() - self.start_time)
        logger.debug('elapsed time: %d seconds', elapsed)
        if elapsed < 10 or self.is_active():
            return
        notify(_("Done."))

    def create_operations_box(self):
        """Create and return the operations box (which holds a tree view)"""
        scrolled_window = Gtk.ScrolledWindow()
        scrolled_window.set_policy(
            Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
        self.tree_store = TreeInfoModel()
        display = TreeDisplayModel()
        mdl = self.tree_store.get_model()
        self.view = display.make_view(
            mdl, self, self.context_menu_event)
        self.view.get_selection().connect("changed", self.on_selection_changed)
        scrolled_window.add(self.view)
        return scrolled_window

    def cb_refresh_operations(self):
        """Callback to refresh the list of cleaners"""
        # Is this the first time in this session?
        if not hasattr(self, 'recognized_cleanerml') and not self.auto_exit:
            from bleachbit import RecognizeCleanerML
            RecognizeCleanerML.RecognizeCleanerML()
            self.recognized_cleanerml = True
        # reload cleaners from disk
        self.view.expand_all()
        self.progressbar.show()
        rc = register_cleaners(self.update_progress_bar,
                               self.cb_register_cleaners_done)
        GLib.idle_add(rc.__next__)
        return False

    def cb_register_cleaners_done(self):
        """Called from register_cleaners()"""
        self.progressbar.hide()
        # update tree view
        self.tree_store.refresh_rows()
        # expand tree view
        self.view.expand_all()

        # Check for online updates.
        if not self.auto_exit and \
            bleachbit.online_update_notification_enabled and \
            options.get("check_online_updates") and \
                not hasattr(self, 'checked_for_updates'):
            self.checked_for_updates = True
            self.check_online_updates()

        # Show information for first start.
        # (The first start flag is set also for each new version.)
        if options.get("first_start") and not self.auto_exit:
            if os.name == 'posix':
                self.append_text(
                    _('Access the application menu by clicking the hamburger icon on the title bar.'))
                pref = self.get_preferences_dialog()
                pref.run()
            if os.name == 'nt':
                self.append_text(
                    _('Access the application menu by clicking the logo on the title bar.'))
            options.set('first_start', False)

        if os.name == 'nt':
            # BitDefender false positive.  BitDefender didn't mark BleachBit as infected or show
            # anything in its log, but sqlite would fail to import unless BitDefender was in "game mode."
            # http://bleachbit.sourceforge.net/forum/074-fails-errors
            try:
                import sqlite3
            except ImportError as e:
                self.append_text(
                    _("Error loading the SQLite module: the antivirus software may be blocking it."), 'error')

        # Show notice about admin privileges.
        if os.name == 'posix' and os.path.expanduser('~') == '/root':
            self.append_text(
                _('You are running BleachBit with administrative privileges for cleaning shared parts of the system, and references to the user profile folder will clean only the root account.')+'\n')
        if os.name == 'nt' and options.get('shred'):
            from win32com.shell.shell import IsUserAnAdmin
            if not IsUserAnAdmin():
                self.append_text(
                    _('Run BleachBit with administrator privileges to improve the accuracy of overwriting the contents of files.'))
                self.append_text('\n')

        # remove from idle loop (see GObject.idle_add)
        return False

    def cb_run_option(self, widget, really_delete, cleaner_id, option_id):
        """Callback from context menu to delete/preview a single option"""
        operations = {cleaner_id: [option_id]}

        # preview
        if not really_delete:
            self.preview_or_run_operations(False, operations)
            return

        # delete
        if GuiBasic.delete_confirmation_dialog(self, mention_preview=False):
            self.preview_or_run_operations(True, operations)
            return

    def cb_stop_operations(self, __widget):
        """Callback to stop the preview/cleaning process"""
        self.worker.abort()

    def context_menu_event(self, treeview, event):
        """When user right clicks on the tree view"""
        if event.button != 3:
            return False
        pathinfo = treeview.get_path_at_pos(int(event.x), int(event.y))
        if not pathinfo:
            return False
        path, col, _cellx, _celly = pathinfo
        treeview.grab_focus()
        treeview.set_cursor(path, col, 0)
        # context menu applies only to children, not parents
        if len(path) != 2:
            return False
        # find the selected option
        model = treeview.get_model()
        option_id = model[path][2]
        cleaner_id = model[path[0]][2]
        # make a menu
        menu = Gtk.Menu()
        menu.connect('hide', lambda widget: widget.detach())
        # TRANSLATORS: this is the context menu
        preview_item = Gtk.MenuItem(label=_("Preview"))
        preview_item.connect('activate', self.cb_run_option,
                             False, cleaner_id, option_id)
        menu.append(preview_item)
        # TRANSLATORS: this is the context menu
        clean_item = Gtk.MenuItem(label=_("Clean"))
        clean_item.connect('activate', self.cb_run_option,
                           True, cleaner_id, option_id)
        menu.append(clean_item)

        # show the context menu
        menu.attach_to_widget(treeview)
        menu.show_all()
        menu.popup(None, None, None, None, event.button, event.time)
        return True

    def setup_drag_n_drop(self):
        def cb_drag_data_received(widget, _context, _x, _y, data, info, _time):
            if info == 80:
                uris = data.get_uris()
                paths = FileUtilities.uris_to_paths(uris)
                self.shred_paths(paths)

        def setup_widget(widget):
            widget.drag_dest_set(Gtk.DestDefaults.MOTION | Gtk.DestDefaults.HIGHLIGHT | Gtk.DestDefaults.DROP,
                                 [Gtk.TargetEntry.new("text/uri-list", 0, 80)], Gdk.DragAction.COPY)
            widget.connect('drag_data_received', cb_drag_data_received)

        setup_widget(self)
        setup_widget(self.textview)
        self.textview.connect('drag_motion', lambda widget,
                              context, x, y, time: True)

    def update_progress_bar(self, status):
        """Callback to update the progress bar with number or text"""
        if isinstance(status, float):
            self.progressbar.set_fraction(status)
        elif isinstance(status, str):
            self.progressbar.set_show_text(True)
            self.progressbar.set_text(status)
        else:
            raise RuntimeError('unexpected type: ' + str(type(status)))

    def update_item_size(self, option, option_id, bytes_removed):
        """Update size in tree control"""
        model = self.view.get_model()

        text = FileUtilities.bytes_to_human(bytes_removed)
        if bytes_removed == 0:
            text = ""

        treepath = Gtk.TreePath(0)
        try:
            __iter = model.get_iter(treepath)
        except ValueError as e:
            logger.warning(
                'ValueError in get_iter() when updating file size for tree path=%s' % treepath)
            return
        while __iter:
            if model[__iter][2] == option:
                if option_id == -1:
                    model[__iter][3] = text
                else:
                    child = model.iter_children(__iter)
                    while child:
                        if model[child][2] == option_id:
                            model[child][3] = text
                        child = model.iter_next(child)
            __iter = model.iter_next(__iter)

    def update_total_size(self, bytes_removed):
        """Callback to update the total size cleaned"""
        context_id = self.status_bar.get_context_id('size')
        text = FileUtilities.bytes_to_human(bytes_removed)
        if bytes_removed == 0:
            text = ""
        self.status_bar.push(context_id, text)

    def create_headerbar(self):
        """Create the headerbar"""
        hbar = Gtk.HeaderBar()
        hbar.props.show_close_button = True
        hbar.props.title = APP_NAME

        box = Gtk.Box()
        Gtk.StyleContext.add_class(box.get_style_context(), "linked")

        if os.name == 'nt':
            icon_size = Gtk.IconSize.BUTTON
        else:
            icon_size = Gtk.IconSize.LARGE_TOOLBAR

        # create the preview button
        self.preview_button = Gtk.Button.new_from_icon_name(
            'edit-find', icon_size)
        self.preview_button.set_always_show_image(True)
        self.preview_button.connect(
            'clicked', lambda *dummy: self.preview_or_run_operations(False))
        self.preview_button.set_tooltip_text(
            _("Preview files in the selected operations (without deleting any files)"))
        # TRANSLATORS: This is the preview button on the main window.  It
        # previews changes.
        self.preview_button.set_label(_('Preview'))
        box.add(self.preview_button)

        # create the delete button
        self.run_button = Gtk.Button.new_from_icon_name(
            'edit-clear-all', icon_size)
        self.run_button.set_always_show_image(True)
        # TRANSLATORS: This is the clean button on the main window.
        # It makes permanent changes: usually deleting files, sometimes
        # altering them.
        self.run_button.set_label(_('Clean'))
        self.run_button.set_tooltip_text(
            _("Clean files in the selected operations"))
        self.run_button.connect("clicked", self.run_operations)
        box.add(self.run_button)

        # stop cleaning
        self.stop_button = Gtk.Button.new_from_icon_name(
            'process-stop', icon_size)
        self.stop_button.set_always_show_image(True)
        self.stop_button.set_label(_('Abort'))
        self.stop_button.set_tooltip_text(
            _('Abort the preview or cleaning process'))
        self.stop_button.set_sensitive(False)
        self.stop_button.connect('clicked', self.cb_stop_operations)
        box.add(self.stop_button)

        hbar.pack_start(box)

        # Add hamburger menu on the right.
        # This is not needed for Microsoft Windows because other code places its
        # menu on the left side.
        if os.name == 'nt':
            return hbar
        menu_button = Gtk.MenuButton()
        icon = Gio.ThemedIcon(name="open-menu-symbolic")
        image = Gtk.Image.new_from_gicon(icon, Gtk.IconSize.BUTTON)
        builder = Gtk.Builder()
        builder.add_from_file(bleachbit.app_menu_filename)
        menu_button.set_menu_model(builder.get_object('app-menu'))
        menu_button.add(image)
        hbar.pack_end(menu_button)

        return hbar

    def on_configure_event(self, widget, event):
        (x, y) = self.get_position()
        (width, height) = self.get_size()

        # fixup maximized window position:
        # on Windows if a window is maximized on a secondary monitor it is moved off the screen
        if 'nt' == os.name:
            window = self.get_window()
            if window.get_state() & Gdk.WindowState.MAXIMIZED != 0:
                screen = self.get_screen()
                monitor_num = screen.get_monitor_at_window(window)
                g = screen.get_monitor_geometry(monitor_num)
                if x < g.x or x >= g.x + g.width or y < g.y or y >= g.y + g.height:
                    logger.debug("Maximized window {}+{}: monitor ({}) geometry = {}+{}".format(
                        (x, y), (width, height), monitor_num, (g.x, g.y), (g.width, g.height)))
                    self.move(g.x, g.y)
                    return True

        # save window position and size
        options.set("window_x", x, commit=False)
        options.set("window_y", y, commit=False)
        options.set("window_width", width, commit=False)
        options.set("window_height", height, commit=False)
        return False

    def on_window_state_event(self, widget, event):
        # save window state
        fullscreen = event.new_window_state & Gdk.WindowState.FULLSCREEN != 0
        options.set("window_fullscreen", fullscreen, commit=False)
        maximized = event.new_window_state & Gdk.WindowState.MAXIMIZED != 0
        options.set("window_maximized", maximized, commit=False)
        return False

    def on_delete_event(self, widget, event):
        # commit options to disk
        options.commit()
        return False

    def on_show(self, widget):
        # restore window position, size and state
        if options.has_option("window_x") and options.has_option("window_y") and \
           options.has_option("window_width") and options.has_option("window_height"):
            r = Gdk.Rectangle()
            (r.x, r.y) = (options.get("window_x"), options.get("window_y"))
            (r.width, r.height) = (options.get(
                "window_width"), options.get("window_height"))

            screen = self.get_screen()
            monitor_num = screen.get_monitor_at_point(r.x, r.y)
            g = screen.get_monitor_geometry(monitor_num)

            # only restore position and size if window left corner
            # is within the closest monitor
            if r.x >= g.x and r.x < g.x + g.width and \
               r.y >= g.y and r.y < g.y + g.height:
                logger.debug("closest monitor ({}) geometry = {}+{}, window geometry = {}+{}".format(
                    monitor_num, (g.x, g.y), (g.width, g.height), (r.x, r.y), (r.width, r.height)))
                self.move(r.x, r.y)
                self.resize(r.width, r.height)
        if options.get("window_fullscreen"):
            self.fullscreen()
        elif options.get("window_maximized"):
            self.maximize()

    def set_windows10_theme(self):
        """Toggle the Windows 10 theme"""
        if not 'nt' == os.name:
            return

        if not self._style_provider_regular:
            self._style_provider_regular = Gtk.CssProvider()
            self._style_provider_regular.load_from_path(
                os.path.join(windows10_theme_path, 'gtk.css'))
        if not self._style_provider_dark:
            self._style_provider_dark = Gtk.CssProvider()
            self._style_provider_dark.load_from_path(
                os.path.join(windows10_theme_path, 'gtk-dark.css'))

        screen = Gdk.Display.get_default_screen(Gdk.Display.get_default())
        if self._style_provider is not None:
            Gtk.StyleContext.remove_provider_for_screen(
                screen, self._style_provider)
        if options.get("win10_theme"):
            if options.get("dark_mode"):
                self._style_provider = self._style_provider_dark
            else:
                self._style_provider = self._style_provider_regular
            Gtk.StyleContext.add_provider_for_screen(
                screen, self._style_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
        else:
            self._style_provider = None

    def populate_window(self):
        """Create the main application window"""
        screen = self.get_screen()
        self.set_default_size(min(screen.width(), 800),
                              min(screen.height(), 600))
        self.set_position(Gtk.WindowPosition.CENTER)
        self.connect("configure-event", self.on_configure_event)
        self.connect("window-state-event", self.on_window_state_event)
        self.connect("delete-event", self.on_delete_event)
        self.connect("show", self.on_show)

        if appicon_path and os.path.exists(appicon_path):
            self.set_icon_from_file(appicon_path)

        # add headerbar
        self.headerbar = self.create_headerbar()
        self.set_titlebar(self.headerbar)

        # split main window twice
        hbox = Gtk.Box(homogeneous=False)
        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, homogeneous=False)
        self.add(vbox)
        vbox.add(hbox)

        # add operations to left
        operations = self.create_operations_box()
        hbox.pack_start(operations, False, True, 0)

        # create the right side of the window
        right_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        self.progressbar = Gtk.ProgressBar()
        right_box.pack_start(self.progressbar, False, True, 0)

        # add output display on right
        self.textbuffer = Gtk.TextBuffer()
        swindow = Gtk.ScrolledWindow()
        swindow.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
        swindow.set_property('expand', True)
        self.textview = Gtk.TextView.new_with_buffer(self.textbuffer)
        self.textview.set_editable(False)
        self.textview.set_wrap_mode(Gtk.WrapMode.WORD)
        swindow.add(self.textview)
        right_box.add(swindow)
        hbox.add(right_box)

        # add markup tags
        tt = self.textbuffer.get_tag_table()

        style_operation = Gtk.TextTag.new('operation')
        style_operation.set_property('size-points', 14)
        style_operation.set_property('weight', 700)
        style_operation.set_property('pixels-above-lines', 10)
        style_operation.set_property('justification', Gtk.Justification.CENTER)
        tt.add(style_operation)

        style_description = Gtk.TextTag.new('description')
        style_description.set_property(
            'justification', Gtk.Justification.CENTER)
        tt.add(style_description)

        style_option_label = Gtk.TextTag.new('option_label')
        style_option_label.set_property('weight', 700)
        style_option_label.set_property('left-margin', 20)
        tt.add(style_option_label)

        style_operation = Gtk.TextTag.new('error')
        style_operation.set_property('foreground', '#b00000')
        tt.add(style_operation)

        self.status_bar = Gtk.Statusbar()
        vbox.add(self.status_bar)
        # setup drag&drop
        self.setup_drag_n_drop()
        # done
        self.show_all()
        self.progressbar.hide()

    @threaded
    def check_online_updates(self):
        """Check for software updates in background"""
        from bleachbit import Update
        try:
            updates = Update.check_updates(options.get('check_beta'),
                                           options.get('update_winapp2'),
                                           self.append_text,
                                           lambda: GLib.idle_add(self.cb_refresh_operations))
            if updates:
                GLib.idle_add(
                    lambda: Update.update_dialog(self, updates))
        except Exception:
            logger.exception(_("Error when checking for updates: "))
Example #3
0
    def __init__(self, auto_exit, *args, **kwargs):
        super(GUI, self).__init__(*args, **kwargs)

        self.auto_exit = auto_exit

        self.set_wmclass(APP_NAME, APP_NAME)
        self.populate_window()

        # Redirect logging to the GUI.
        bb_logger = logging.getLogger('bleachbit')
        from bleachbit.Log import GtkLoggerHandler
        self.gtklog = GtkLoggerHandler(self.append_text)
        bb_logger.addHandler(self.gtklog)

        # process any delayed logs
        from bleachbit.Log import DelayLog
        if isinstance(sys.stderr, DelayLog):
            for msg in sys.stderr.read():
                self.append_text(msg)
            # if stderr was redirected - keep redirecting it
            sys.stderr = self.gtklog

        Gtk.Settings.get_default().set_property(
            'gtk-application-prefer-dark-theme', options.get('dark_mode'))

        if options.is_corrupt():
            logger.error(
                _('Resetting the configuration file because it is corrupt: %s')
                % bleachbit.options_file)
            bleachbit.Options.init_configuration()

        if options.get("first_start") and not auto_exit:
            if os.name == 'posix':
                self.append_text(
                    _('Access the application menu by clicking the hamburger icon on the title bar.'
                      ))
                pref = PreferencesDialog(self, self.cb_refresh_operations)
                pref.run()
            if os.name == 'nt':
                self.append_text(
                    _('Access the application menu by clicking the logo on the title bar.'
                      ))
            options.set('first_start', False)
        if os.name == 'nt':
            # BitDefender false positive.  BitDefender didn't mark BleachBit as infected or show
            # anything in its log, but sqlite would fail to import unless BitDefender was in "game mode."
            # http://bleachbit.sourceforge.net/forum/074-fails-errors
            try:
                import sqlite3
            except ImportError as e:
                self.append_text(
                    _("Error loading the SQLite module: the antivirus software may be blocking it."
                      ), 'error')

        if os.name == 'posix' and bleachbit.expanduser('~') == '/root':
            self.append_text(
                _('You are running BleachBit with administrative privileges for cleaning shared parts of the system, and references to the user profile folder will clean only the root account.'
                  ))
        if os.name == 'nt' and options.get('shred'):
            from win32com.shell.shell import IsUserAnAdmin
            if not IsUserAnAdmin():
                self.append_text(
                    _('Run BleachBit with administrator privileges to improve the accuracy of overwriting the contents of files.'
                      ))
                self.append_text('\n')

        GLib.idle_add(self.cb_refresh_operations)