Qube Manager Dark Mode (built-in method)

Qubes OS - Qubes tools - Dark mode


Preview


Qube Manager. Light mode on left, Dark mode on right.

Introduction


See the first reply for simpler methods.

Description

Add an option in the view menu of Qube Manager to enable/disable dark mode.

Dark mode can also be set in the Qube Manager config file:

[user@dom0 ~]$ cat "$HOME/.config/The Qubes Project/qubes-qube-manager.conf"
[...]
[view]
darkmode=true

This script is a one-file format (single .sh file).

Usage

Copy the script to dom0, make it executable, then launch it.
qubes-os.org/doc/how-to-copy-from-dom0/#copying-to-dom0

:warning: Caution:
The code you run in dom0 MUST be understood.

file_name=qubes_tools_dark_mode.sh
qvm-run --pass-io sys-usb "
    cat /home/user/$file_name" > $HOME/$file_name
chmod +x $HOME/$file_name
$HOME/$file_name

Make sure to have only space characters for python code.
If you have tab characters, Qube Manager & Tools will fail to start.

Theme colors


You can extract the CSS file of your theme with the gresource utility.

If gresource isn’t available, you need to install the package that contain it.

Fedora
[user@disp42 ~]$ sudo dnf install glib2-devel
Debian
[user@disp42 ~]$ sudo apt install libglib2.0-bin

e.g.
List the files of your gtk.gresource theme.

[user@disp42 ~]$ gresource list /usr/share/themes/Arc-Dark/gtk-3.0/gtk.gresource
[...]
/org/gnome/arc-theme/gtk-main.css

Once the file path is known, extract it.

[user@disp42 ~]$ gresource extract /usr/share/themes/Arc-Dark/gtk-3.0/gtk.gresource /org/gnome/arc-theme/gtk-main.css > gtk-arc-dark.css

You may want to use an online CSS formatter to make it easier to read.
You can now find the color values you want to use.

CSS file


Colors are based on the Arc-Dark theme.

CSS content
#!/usr/bin/bash

set -eu -o pipefail

css_content='
QMenu {
    border: 1px solid #252A32;
}

::separator {
    background-color: #475970;
    height: 1px;
    width: 1px;
}

:disabled {
    color: gray;
}

::tab:!selected {
    background-color: #2F343F;
}

::tab:!selected:hover {
    background-color: #505666;
}

* {
    color: #D3DAE3;
    background-color: #383C4A;
    alternate-background-color: #404552;
    selection-background-color: #5294E2;
}'

Save the css file in the ‘Qubes Project’ config directory.
It can be an other directory (e.g. $HOME/Documents).

qubes_cfg_dir="$HOME/.config/The Qubes Project/"
css_file=dark-stylesheet.css
echo "$css_content" > "$qubes_cfg_dir/$css_file"

Qubes Tools


Location of the Qubes Tools files.

python_version=$(python3 --version | grep -Eo '[0-9]+\.[0-9]+')
lib_python_dir=/usr/lib/python$python_version/site-packages/
qm_dir=$lib_python_dir/qubesmanager/

Helper function to backup a file (to be able to reverse the changes).

create_backup_file ()
create_backup_file ()
{
    if [ ! -f $1.bak ]
    then
        sudo cp $1 $1.bak
    fi
}

Helper function to add our code to the Qubes files.

add_data_after_pattern ()
add_data_after_pattern ()
{
    handle_data ()
    {
        if [[ $# -ne 1 ]]
        then
            echo "$3" | sudo tee /tmp/qm_content.tmp > /dev/null
            sudo sed -i -E "/$2/ r /tmp/qm_content.tmp" $1
            $FUNCNAME $1 "${@:4}"
        fi
    }
    local qtool=$qm_dir/$1
    create_backup_file $qtool
    handle_data $qtool "${@:2}"
}

Enable dark mode (or not) according to the setting made in Qube Manager.

add_dark_mode ()
add_dark_mode ()
{
    local qm_darkmode="
        if not parent:
            manager_settings = QtCore.QSettings(
                'The Qubes Project', 'qubes-qube-manager')
        else:
            manager_settings = QtCore.QSettings(self)
        if manager_settings.value('view/darkmode',
                                  defaultValue='false') != 'false':
            qubes_cfg_dir = '$qubes_cfg_dir'
            with open(qubes_cfg_dir + '$css_file') as css_file:
                self.setStyleSheet(css_file.read())"
    for qtool_file in $@
    do
        add_data_after_pattern $qtool_file 'setupUi' "$qm_darkmode"
    done
}

Add our code to the Qubes Tools files.
(template_manager.py is actually the code for Template Switcher).
For 4.2, remove global_settings.py.

add_dark_mode \
    backup.py \
    bootfromdevice.py \
    clone_vm.py \
    create_new_vm.py \
    global_settings.py \
    log_dialog.py \
    restore.py \
    settings.py \
    template_manager.py

sudo sed -i 's/^from PyQt5 import /&QtCore, /' \
         $qm_dir/bootfromdevice.py \
         $qm_dir/log_dialog.py

Template Manager (4.2)

Add our code to the Template Manager tool.

add_dark_mode qvm_template_gui.py
sudo sed -i -E -e '/^import PyQt5\.QtWidgets/ a from PyQt5 import QtCore' \
               -e 's/self, actions/&, parent=None/' \
               -e 's/TemplateInstallConfirmDialog\(actions/&, parent/' \
               -e 's/TemplateInstallProgressDialog\(actions/&, parent/' \
               -e 's/do_install\(self/&, parent=None/' \
         $qm_dir/qvm_template_gui.py

Global Config, Policy Editor, Update & Create New Qube (4.2)

Make the new GUI tools use Arc-Dark theme values.

qui_dark_css=$lib_python_dir/qui/styles/qubes-colors-dark.css
create_backup_file $qui_dark_css
sudo sed -i -E -e '/top-background / s/#[^;]+/#2F343F/' \
               -e '/top-background-2/ s/#[^;]+/#383C4A/' \
               -e '/bottom-background/ s/#[^;]+/#404552/' \
               -e '/background-frame/ s/#[^;]+/#252A32/' \
         $qui_dark_css

Application menu (4.2)

Make the menu use Arc-Dark theme values.
(need logout or kill & restart the new menu to take effect)

qmenu_dark_css=$lib_python_dir/qubes_menu/qubes-menu-dark.css
create_backup_file $qmenu_dark_css
sudo sed -i -E -e '/left-background/ s/#[^;]+/#383C4A/' \
               -e '/right-background/ s/#[^;]+/#404552/' \
               -e '/outer-background/ s/#[^;]+/#2F343F/' \
         $qmenu_dark_css

Qube Manager


ui_qubemanager.py

Widget to be added in the view menu.

qm_darkmode_widget
qm_darkmode_widget="
        self.action_dark_mode = QtWidgets.QAction(VmManagerWindow)
        self.action_dark_mode.setCheckable(True)
        self.action_dark_mode.setChecked(False)
        self.action_dark_mode.setObjectName('action_dark_mode')"

Text displayed in the view menu.

qm_darkmode_text
qm_darkmode_text="
        self.action_dark_mode.setText(_translate('VmManagerWindow', 'Dark mode'))"

Add our code to ui_qubemanager.py.

add_data_after_pattern ui_qubemanager.py \
    '"action_compact_view"' "$qm_darkmode_widget" \
    'Compact view' "$qm_darkmode_text"

qube_manager.py

Add the dark mode widget to the existing view menu.

qm_darkmode_menu
qm_darkmode_menu="
        self.menu_view.addAction(self.action_dark_mode)"

Restore the preference of light or dark mode.

qm_darkmode_restore
qm_darkmode_restore="
        if self.manager_settings.value('view/darkmode',
                                       defaultValue='false') != 'false':
            self.action_dark_mode.setChecked(True)"

Enable or disable dark mode when we click on the menu item.
Save the preference in the config file.

qm_set_darkmode
qm_set_darkmode="
    @pyqtSlot(bool)
    def on_action_dark_mode_toggled(self, checked):
        if checked:
            qubes_cfg_dir = '$qubes_cfg_dir'
            with open(qubes_cfg_dir + '$css_file') as css_file:
                self.setStyleSheet(css_file.read())
        else:
            self.setStyleSheet('')
        if self.settings_loaded:
            self.manager_settings.setValue('view/darkmode', checked)"

Add our code to qube_manager.py.

add_data_after_pattern qube_manager.py \
    'compact_view\.setChecked' "$qm_darkmode_restore" \
    'Action\(self\.action_compact_view' "$qm_darkmode_menu" \
    'self\.close' "$qm_set_darkmode"

Restore backup


A script to restore the backup files.
In case the Dark Mode doesn’t behave as expected.

restore_backup_file ()
#!/bin/bash

python_version=$(python3 --version | grep -Eo '[0-9]+\.[0-9]+')
lib_python_dir=/usr/lib/python$python_version/site-packages/
qm_dir=$lib_python_dir/qubesmanager/

restore_backup_file ()
{
    for qtool in $@
    do
        if [ -f $qtool.bak ]
        then
            sudo cp $qtool.bak $qtool
        else
            echo "${qtool##*/}: no backup file"
        fi
    done
}

restore_backup_file \
    $qm_dir/backup.py \
    $qm_dir/bootfromdevice.py \
    $qm_dir/clone_vm.py \
    $qm_dir/create_new_vm.py \
    $qm_dir/global_settings.py \
    $qm_dir/log_dialog.py \
    $qm_dir/restore.py \
    $qm_dir/settings.py \
    $qm_dir/template_manager.py \
    $qm_dir/qube_manager.py \
    $qm_dir/ui_qubemanager.py

# 4.2
qui_dark_css=$lib_python_dir/qui/styles/qubes-colors-dark.css
qmenu_dark_css=$lib_python_dir/qubes_menu/qubes-menu-dark.css

restore_backup_file \
    $qm_dir/qvm_template_gui.py \
    $qui_dark_css \
    $qmenu_dark_css

Remarks


The dark mode will be erased on Qube Manager update.

When it happens, delete the old backup files before executing this script.
(Otherwise, you will keep the old version as a backup instead of the new one)

2 Likes

Two, more simple solutions.

wiki.archlinux.org/title/qt#Qt_Style_Sheets
Qt application can use Style Sheets, which are CSS files.

These 2 methods add the -stylesheet option to the Qubes applications.

method 1

Add these two lines:

    sys.argv.append('-stylesheet')
    sys.argv.append('/path/to/dark-stylesheet.css')

in /usr/lib/python3.8/site-packages/qubesmanager/utils.py:

  • below the line: def run_asynchronous
  • below the line: def run_synchronous

in /usr/lib/python3.8/site-packages/qubesmanager/create_new_vm.py

  • below the line: def main

The functions are at the bottom of files.

method 2

Modify desktop files for Qube Manager and Qubes Tools.

/usr/share/applications/

Example for Qube Manager:

[Desktop Entry]
...
Exec=qubes-qube-manager -stylesheet '/path/to/dark-stylesheet.css'
...

The desktop files are for the apps menu > Qubes Tools shorcuts.

For the Qubes Tray icon > Open Qube Manager
Add the -stylesheet option in run_manager function at line 350 (need logout).

/usr/lib/python3.8/site-packages/qui/tray/domains.py

def run_manager(_item):
    subprocess.Popen(['qubes-qube-manager', '-stylesheet',
        '/path/to/dark-stylesheet.css'])

note

To disable the dark mode, rename or move the CSS file.

1 Like

Dark mode works out of the box for me, but I run KDE