mirror of
https://github.com/GradienceTeam/Gradience.git
synced 2024-05-02 19:27:22 +00:00
GNOME Shell theming support (#679)
Signed-off-by: tfuxu <73042332+tfuxu@users.noreply.github.com> Co-authored-by: 0xMRTT <0xMRTT@evta.fr> Co-authored-by: 0xMRTT <0xMRTT@proton.me> Co-authored-by: daudix-UFO <ddaudix@gmail.com>
This commit is contained in:
parent
905b314b6a
commit
e412a2c537
3
.github/workflows/build.yml
vendored
3
.github/workflows/build.yml
vendored
|
@ -38,6 +38,9 @@ jobs:
|
|||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
dnf -y install docker
|
||||
|
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
[submodule "data/submodules"]
|
||||
path = data/submodules
|
||||
url = https://github.com/GradienceTeam/Submodules
|
|
@ -46,6 +46,7 @@ flatpak install org.gnome.Sdk//44 org.gnome.Platform//44
|
|||
```shell
|
||||
git clone https://github.com/GradienceTeam/Gradience.git
|
||||
cd Gradience
|
||||
git submodule update --init --recursive
|
||||
flatpak-builder --install --user --force-clean repo/ build-aux/flatpak/com.github.GradienceTeam.Gradience.json
|
||||
```
|
||||
|
||||
|
@ -53,6 +54,7 @@ flatpak-builder --install --user --force-clean repo/ build-aux/flatpak/com.githu
|
|||
```shell
|
||||
git clone https://github.com/GradienceTeam/Gradience.git
|
||||
cd Gradience
|
||||
git submodule update --init --recursive
|
||||
flatpak-builder --install --system --force-clean repo/ build-aux/flatpak/com.github.GradienceTeam.Gradience.json
|
||||
```
|
||||
|
||||
|
@ -84,6 +86,7 @@ pip install -r requirements.txt
|
|||
```shell
|
||||
git clone https://github.com/GradienceTeam/Gradience.git
|
||||
cd Gradience
|
||||
git submodule update --init --recursive
|
||||
meson setup builddir
|
||||
meson configure builddir -Dprefix=/usr/local
|
||||
sudo ninja -C builddir install
|
||||
|
@ -94,6 +97,7 @@ sudo ninja -C builddir install
|
|||
```shell
|
||||
git clone https://github.com/GradienceTeam/Gradience.git
|
||||
cd Gradience
|
||||
git submodule update --init --recursive
|
||||
meson setup builddir
|
||||
meson configure builddir -Dprefix="$(pwd)/builddir"
|
||||
ninja -C builddir install
|
||||
|
|
11
README.md
11
README.md
|
@ -112,10 +112,15 @@ Use [this guide](https://github.com/lassekongo83/adw-gtk3/blob/main/gtk4.md) to
|
|||
|
||||
|
||||
## 🔄 Revert Theming
|
||||
1. Open Preferences window
|
||||
|
||||
> **Note**
|
||||
> You can press on the menu button in the headerbar and press `Reset Applied Color Scheme`
|
||||
> ![Main Gradience menu](https://raw.githubusercontent.com/GradienceTeam/Design/main/Screenshots/hamburger_menu.png)
|
||||
![Main Gradience Menu](https://i.imgur.com/bJMNX6d.png)
|
||||
|
||||
2. Go to Theming tab
|
||||
|
||||
3. In _Reset & Restore Presets_ group, click Reset button for either GTK 3 or Libadwaita applications
|
||||
|
||||
![Reset & Restore Presets Group](https://i.imgur.com/SynxTJT.png)
|
||||
|
||||
<details>
|
||||
<summary>🪛️ Manual revert</summary>
|
||||
|
|
|
@ -10,7 +10,11 @@
|
|||
"--device=dri",
|
||||
"--socket=fallback-x11",
|
||||
"--socket=wayland",
|
||||
"--talk-name=org.freedesktop.Flatpak",
|
||||
"--filesystem=~/.local/share/gnome-shell/extensions",
|
||||
"--filesystem=xdg-data/flatpak/overrides:create",
|
||||
"--filesystem=xdg-cache/gradience:create",
|
||||
"--filesystem=xdg-data/themes:create",
|
||||
"--filesystem=xdg-config/gtk-3.0",
|
||||
"--filesystem=xdg-config/gtk-4.0",
|
||||
"--filesystem=xdg-run/gvfsd",
|
||||
|
|
|
@ -10,7 +10,11 @@
|
|||
"--device=dri",
|
||||
"--socket=fallback-x11",
|
||||
"--socket=wayland",
|
||||
"--talk-name=org.freedesktop.Flatpak",
|
||||
"--filesystem=~/.local/share/gnome-shell/extensions",
|
||||
"--filesystem=xdg-data/flatpak/overrides:create",
|
||||
"--filesystem=xdg-cache/gradience:create",
|
||||
"--filesystem=xdg-data/themes:create",
|
||||
"--filesystem=xdg-config/gtk-3.0",
|
||||
"--filesystem=xdg-config/gtk-4.0",
|
||||
"--filesystem=xdg-run/gvfsd",
|
||||
|
|
|
@ -12,8 +12,8 @@
|
|||
"sources": [
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/39/f6/7c1e3a2a54f18b67c5bd092c25ac7327083d4b3b15731b98a9c193df2db9/anyascii-0.3.1-py3-none-any.whl",
|
||||
"sha256": "8707d3185017435933360462a65e2c70a4818490745804f38a5ca55e59eb56a0"
|
||||
"url": "https://files.pythonhosted.org/packages/4f/7b/a9a747e0632271d855da379532b05a62c58e979813814a57fa3b3afeb3a4/anyascii-0.3.2-py3-none-any.whl",
|
||||
"sha256": "3b3beef6fc43d9036d3b0529050b0c48bfad8bc960e9e562d7223cfb94fe45d4"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -24,6 +24,11 @@
|
|||
"pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"material-color-utilities-python\" --no-build-isolation"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/d8/29/bd8de07107bc952e0e2783243024e1c125e787fd685725a622e4ac7aeb3c/regex-2023.3.23.tar.gz",
|
||||
"sha256": "dc80df325b43ffea5cdea2e3eaa97a44f3dd298262b1c7fe9dbb2a9522b956a7"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/bc/07/830784e061fb94d67649f3e438ff63cfb902dec6d48ac75aeaaac7c7c30e/Pillow-9.4.0.tar.gz",
|
||||
|
@ -33,11 +38,6 @@
|
|||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/31/65/a8e0f3e2bad0d4eabeb1931b22cdae08344a955f28022dc83420a128683c/material_color_utilities_python-0.1.5-py2.py3-none-any.whl",
|
||||
"sha256": "48abd8695a1355ab3ad43fe314ca8664c66282a86fbf94a717571273bf422bdf"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/27/b5/92d404279fd5f4f0a17235211bb0f5ae7a0d9afb7f439086ec247441ed28/regex-2022.10.31.tar.gz",
|
||||
"sha256": "a3a98921da9a1bf8457aeee6a551948a83601689e5ecdd736894ea9bbec77e83"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -48,21 +48,16 @@
|
|||
"pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"svglib\" --no-build-isolation"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl",
|
||||
"sha256": "a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/bc/07/830784e061fb94d67649f3e438ff63cfb902dec6d48ac75aeaaac7c7c30e/Pillow-9.4.0.tar.gz",
|
||||
"sha256": "a1c2d7780448eb93fbcc3789bf3916aa5720d942e37945f4056680317f1cd23e"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/9d/3a/e39436efe51894243ff145a37c4f9a030839b97779ebcc4f13b3ba21c54e/cssselect2-0.7.0-py3-none-any.whl",
|
||||
"sha256": "fd23a65bfd444595913f02fc71f6b286c29261e354c41d722ca7a261a49b5969"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/06/5a/e11cad7b79f2cf3dd2ff8f81fa8ca667e7591d3d8451768589996b65dec1/lxml-4.9.2.tar.gz",
|
||||
"sha256": "2455cfaeb7ac70338b3257f41e21f0724f4b5b0c0e7702da67ee6c3640835b67"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/b8/ac/10d68a650b321bd8c4d8cbefd9994e7727d57b381c9bdb0a013273011e62/reportlab-3.6.12.tar.gz",
|
||||
|
@ -70,8 +65,8 @@
|
|||
},
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/ee/cd/8b19f6299541862deea19b26a9efe269ff843fa15a9fd833ff0fec2ac22c/svglib-1.4.1.tar.gz",
|
||||
"sha256": "48c24706c23bb4262173b6fa49eabb10afa15b8412f14283120549517ccfa314"
|
||||
"url": "https://files.pythonhosted.org/packages/06/5a/e11cad7b79f2cf3dd2ff8f81fa8ca667e7591d3d8451768589996b65dec1/lxml-4.9.2.tar.gz",
|
||||
"sha256": "2455cfaeb7ac70338b3257f41e21f0724f4b5b0c0e7702da67ee6c3640835b67"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
|
@ -80,8 +75,13 @@
|
|||
},
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl",
|
||||
"sha256": "a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"
|
||||
"url": "https://files.pythonhosted.org/packages/9d/3a/e39436efe51894243ff145a37c4f9a030839b97779ebcc4f13b3ba21c54e/cssselect2-0.7.0-py3-none-any.whl",
|
||||
"sha256": "fd23a65bfd444595913f02fc71f6b286c29261e354c41d722ca7a261a49b5969"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/56/5b/53ca0fd447f73423c7dc59d34e523530ef434481a3d18808ff7537ad33ec/svglib-1.5.1.tar.gz",
|
||||
"sha256": "3ae765d3a9409ee60c0fb4d24c2deb6a80617aa927054f5bcd7fc98f0695e587"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -108,13 +108,27 @@
|
|||
"sources": [
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/bc/c3/f068337a370801f372f2f8f6bad74a5c140f6fda3d9de154052708dd3c65/Jinja2-3.1.2-py3-none-any.whl",
|
||||
"sha256": "6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"
|
||||
"url": "https://files.pythonhosted.org/packages/95/7e/68018b70268fb4a2a605e2be44ab7b4dd7ce7808adae6c5ef32e34f4b55a/MarkupSafe-2.1.2.tar.gz",
|
||||
"sha256": "abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/1d/97/2288fe498044284f39ab8950703e88abbac2abbdf65524d576157af70556/MarkupSafe-2.1.1.tar.gz",
|
||||
"sha256": "7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"
|
||||
"url": "https://files.pythonhosted.org/packages/bc/c3/f068337a370801f372f2f8f6bad74a5c140f6fda3d9de154052708dd3c65/Jinja2-3.1.2-py3-none-any.whl",
|
||||
"sha256": "6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "python3-libsass",
|
||||
"buildsystem": "simple",
|
||||
"build-commands": [
|
||||
"pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"libsass\" --no-build-isolation"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://files.pythonhosted.org/packages/55/14/f1d9578dce39f890ae3c0f93db8a23e89d2a1403da81d307ffb429df7c3b/libsass-0.22.0.tar.gz",
|
||||
"sha256": "3ab5ad18e47db560f4f0c09e3d28cf3bb1a44711257488ac2adad69f4f7f8425"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -22,6 +22,9 @@
|
|||
<key name="enabled-plugins" type="as">
|
||||
<default>[]</default>
|
||||
</key>
|
||||
<key name="enabled-theme-engines" type="as">
|
||||
<default>['shell', 'monet']</default>
|
||||
</key>
|
||||
<key name="user-flatpak-theming-gtk4" type="b">
|
||||
<default>false</default>
|
||||
</key>
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
<file preprocess="xml-stripblanks">ui/error_list_row.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/explore_preset_row.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/log_out_dialog.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/monet_theming_group.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/no_plugin_window.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/option_row.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/palette_shades.ui</file>
|
||||
|
@ -18,8 +19,12 @@
|
|||
<file preprocess="xml-stripblanks">ui/preset_row.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/presets_manager_window.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/repo_row.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/reset_preset_group.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/save_dialog.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/share_window.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/shell_prefs_window.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/shell_theming_group.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/theming_empty_group.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/welcome_window.ui</file>
|
||||
<file preprocess="xml-stripblanks">ui/window.ui</file>
|
||||
<file>images/welcome-dark.svg</file>
|
||||
|
|
|
@ -73,3 +73,6 @@ endif
|
|||
|
||||
subdir('icons')
|
||||
subdir('plugins')
|
||||
|
||||
subdir('shell')
|
||||
subdir('submodules/gnome-shell')
|
||||
|
|
|
@ -33,7 +33,9 @@
|
|||
"popover_bg_color": "#383838",
|
||||
"popover_fg_color": "#ffffff",
|
||||
"shade_color": "rgba(0, 0, 0, 0.36)",
|
||||
"scrollbar_outline_color": "rgba(0, 0, 0, 0.5)"
|
||||
"scrollbar_outline_color": "rgba(0, 0, 0, 0.5)",
|
||||
"thumbnail_bg_color": "#383838",
|
||||
"thumbnail_fg_color": "#ffffff"
|
||||
},
|
||||
"palette": {
|
||||
"blue_": {
|
||||
|
@ -100,4 +102,4 @@
|
|||
"5": "#000000"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,7 +33,9 @@
|
|||
"popover_bg_color": "#ffffff",
|
||||
"popover_fg_color": "rgba(0, 0, 0, 0.8)",
|
||||
"shade_color": "rgba(0, 0, 0, 0.07)",
|
||||
"scrollbar_outline_color": "#ffffff"
|
||||
"scrollbar_outline_color": "#ffffff",
|
||||
"thumbnail_bg_color": "#ffffff",
|
||||
"thumbnail_fg_color": "rgba(0, 0, 0, 0.8)"
|
||||
},
|
||||
"palette": {
|
||||
"blue_": {
|
||||
|
@ -100,4 +102,4 @@
|
|||
"5": "#000000"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,7 +33,9 @@
|
|||
"popover_bg_color": "#241f31",
|
||||
"popover_fg_color": "#ffffff",
|
||||
"shade_color": "rgba(0, 0, 0, 0.36)",
|
||||
"scrollbar_outline_color": "rgba(0, 0, 0, 0.5)"
|
||||
"scrollbar_outline_color": "rgba(0, 0, 0, 0.5)",
|
||||
"thumbnail_bg_color": "#241f31",
|
||||
"thumbnail_fg_color": "#ffffff"
|
||||
},
|
||||
"palette": {
|
||||
"blue_": {
|
||||
|
@ -100,4 +102,4 @@
|
|||
"5": "#000000"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
5
data/shell/meson.build
Normal file
5
data/shell/meson.build
Normal file
|
@ -0,0 +1,5 @@
|
|||
install_subdir('templates',
|
||||
install_dir: join_paths(get_option('datadir'), 'gradience', 'shell'),
|
||||
exclude_files: 'meson.build',
|
||||
strip_directory : false
|
||||
)
|
18
data/shell/templates/42/check-box.template
Normal file
18
data/shell/templates/42/check-box.template
Normal file
|
@ -0,0 +1,18 @@
|
|||
/* Check Boxes */
|
||||
|
||||
// these are equal to the size of the SVG assets
|
||||
$check_height: 24px;
|
||||
$check_width: 24px;
|
||||
|
||||
|
||||
.check-box {
|
||||
StBoxLayout { spacing: .8em; }
|
||||
StBin {
|
||||
width: $check_width;
|
||||
height: $check_height;
|
||||
background-image: if($variant == 'light', url("resource:///org/gnome/shell/theme/checkbox-off-light.svg"), url("resource:///org/gnome/shell/theme/checkbox-off.svg"));
|
||||
}
|
||||
&:focus StBin { background-image: if($variant == 'light', url("resource:///org/gnome/shell/theme/checkbox-off-focused-light.svg"), url("resource:///org/gnome/shell/theme/checkbox-off-focused.svg"));; }
|
||||
&:checked StBin { background-image: url("resource:///org/gnome/shell/theme/checkbox.svg"); }
|
||||
&:focus:checked StBin { background-image: url("resource:///org/gnome/shell/theme/checkbox-focused.svg"); }
|
||||
}
|
71
data/shell/templates/42/colors.template
Normal file
71
data/shell/templates/42/colors.template
Normal file
|
@ -0,0 +1,71 @@
|
|||
// When color definition differs for dark and light variant,
|
||||
// it gets @if-ed depending on $variant
|
||||
|
||||
@import '_palette.scss';
|
||||
|
||||
$_dark_base_color: darken(desaturate({{bg_color}}, 2%), 2%);
|
||||
|
||||
$base_color: $_dark_base_color;
|
||||
$bg_color: if($variant == 'light', darken($base_color, 5%), lighten($base_color, 5%));
|
||||
$fg_color: if($variant == 'light', transparentize(black, .2), {{fg_color}});
|
||||
|
||||
$accent_fg_color: {{accent_fg_color}};
|
||||
$selected_fg_color: if($variant == 'light', $accent_fg_color, {{selected_fg_color}});
|
||||
$selected_bg_color: {{selected_bg_color}};
|
||||
$selected_borders_color: if($variant == 'light', darken($selected_bg_color, 15%), darken($selected_bg_color, 30%)); // NOTE: Unused in GNOME Shell 42
|
||||
|
||||
$borders_color: if($variant == 'light', transparentize($fg_color, .5), transparentize($fg_color, .9));
|
||||
$borders_edge: if($variant == 'light', rgba(255,255,255,0.8), lighten($bg_color, 5%));
|
||||
|
||||
$link_color: if($variant == 'light', darken($selected_bg_color, 10%), lighten($selected_bg_color, 20%));
|
||||
$link_visited_color: if($variant == 'light', darken($selected_bg_color, 20%), lighten($selected_bg_color, 10%)); // NOTE: Unused in GNOME Shell 42
|
||||
|
||||
$warning_color: {{warning_bg_color}};
|
||||
$error_color: {{error_bg_color}};
|
||||
$success_color: {{success_bg_color}}; // NOTE: Unused in GNOME Shell 42
|
||||
$destructive_color: {{destructive_bg_color}};
|
||||
|
||||
// NOTE: Used also in overview for folder colors, in search results, partially in text and for indicators below app icons
|
||||
$osd_fg_color: {{osd_fg_color}};
|
||||
$osd_bg_color: $_dark_base_color; // hardcoded for both light & dark
|
||||
$osd_insensitive_bg_color: transparentize(mix($osd_fg_color, opacify($osd_bg_color, 1), 10%), 0.5); // NOTE: Unused in GNOME Shell 42
|
||||
$osd_insensitive_fg_color: if($variant == 'light', mix($osd_fg_color, $osd_bg_color, 80%), mix($osd_fg_color, $osd_bg_color, 70%));
|
||||
$osd_borders_color: transparentize(black, 0.3);
|
||||
$osd_outer_borders_color: transparentize($osd_fg_color, 0.98);
|
||||
|
||||
$shadow_color: if($variant == 'light', rgba(0,0,0,0.1), rgba(0,0,0,0.2));
|
||||
|
||||
// cards
|
||||
$card_bg_color: if($variant == 'light', darken($bg_color, 5%), lighten($bg_color, 2%)); // TODO: Allow to modify this value
|
||||
|
||||
// notifications
|
||||
$bubble_buttons_color: if($variant == 'light', darken($bg_color, 12%), lighten($bg_color, 10%));
|
||||
|
||||
// overview background color
|
||||
$system_bg_color: darken(desaturate({{system_bg_color}}, 2%), 2%);
|
||||
|
||||
//insensitive state derived colors
|
||||
$insensitive_fg_color: mix($fg_color, $bg_color, 50%);
|
||||
$insensitive_bg_color: mix($bg_color, $base_color, 60%);
|
||||
$insensitive_borders_color: mix($borders_color, $base_color, 60%); // NOTE: Unused in GNOME Shell 42
|
||||
|
||||
//colors for the backdrop state, derived from the main colors.
|
||||
// NOTE: This entire section doesn't seem to be used anywhere in GNOME Shell 42
|
||||
$backdrop_base_color: if($variant =='light', darken($base_color,1%), lighten($base_color,1%));
|
||||
$backdrop_bg_color: $bg_color;
|
||||
$backdrop_fg_color: mix($fg_color, $backdrop_bg_color, 80%);
|
||||
$backdrop_insensitive_color: if($variant =='light', darken($backdrop_bg_color,15%), lighten($backdrop_bg_color,15%));
|
||||
$backdrop_borders_color: mix($borders_color, $bg_color, 90%);
|
||||
$backdrop_dark_fill: mix($backdrop_borders_color,$backdrop_bg_color, 35%);
|
||||
|
||||
// derived checked colors
|
||||
$checked_bg_color: if($variant=='light', darken($bg_color, 7%), lighten($bg_color, 7%));
|
||||
$checked_fg_color: if($variant=='light', darken($fg_color, 7%), lighten($fg_color, 7%)); // NOTE: Unused in GNOME Shell 42
|
||||
|
||||
// derived hover colors
|
||||
$hover_bg_color: if($variant=='light', darken($bg_color, 3%), lighten($bg_color, 10%));
|
||||
$hover_fg_color: if($variant=='light', darken($fg_color, 5%), lighten($fg_color, 10%));
|
||||
|
||||
// derived active colors
|
||||
$active_bg_color: if($variant=='light', darken($bg_color, 5%), lighten($bg_color, 12%));
|
||||
$active_fg_color: if($variant=='light', darken($fg_color, 5%), lighten($fg_color, 12%));
|
15
data/shell/templates/42/gnome-shell.template
Normal file
15
data/shell/templates/42/gnome-shell.template
Normal file
|
@ -0,0 +1,15 @@
|
|||
$variant: {{theme_variant}};
|
||||
|
||||
/* Generated with Gradience
|
||||
*
|
||||
* Issues caused by theming should be reported to Gradience repository, and not upstream
|
||||
*
|
||||
* https://github.com/GradienceTeam/Gradience
|
||||
*/
|
||||
|
||||
@import "gnome-shell-sass/_colors"; //use gtk colors
|
||||
@import "gnome-shell-sass/_drawing";
|
||||
@import "gnome-shell-sass/_common";
|
||||
@import "gnome-shell-sass/_widgets";
|
||||
|
||||
{{custom_css}}
|
46
data/shell/templates/42/palette.template
Normal file
46
data/shell/templates/42/palette.template
Normal file
|
@ -0,0 +1,46 @@
|
|||
//GNOME Color Palette
|
||||
$blue_1: {{blue_1}};
|
||||
$blue_2: {{blue_2}};
|
||||
$blue_3: {{blue_3}};
|
||||
$blue_4: {{blue_4}};
|
||||
$blue_5: {{blue_5}};
|
||||
$green_1: {{green_1}};
|
||||
$green_2: {{green_2}};
|
||||
$green_3: {{green_3}};
|
||||
$green_4: {{green_4}};
|
||||
$green_5: {{green_5}};
|
||||
$yellow_1: {{yellow_1}};
|
||||
$yellow_2: {{yellow_2}};
|
||||
$yellow_3: {{yellow_3}};
|
||||
$yellow_4: {{yellow_4}};
|
||||
$yellow_5: {{yellow_5}};
|
||||
$orange_1: {{orange_1}};
|
||||
$orange_2: {{orange_2}};
|
||||
$orange_3: {{orange_3}};
|
||||
$orange_4: {{orange_4}};
|
||||
$orange_5: {{orange_5}};
|
||||
$red_1: {{red_1}};
|
||||
$red_2: {{red_2}};
|
||||
$red_3: {{red_3}};
|
||||
$red_4: {{red_4}};
|
||||
$red_5: {{red_5}};
|
||||
$purple_1: {{purple_1}};
|
||||
$purple_2: {{purple_2}};
|
||||
$purple_3: {{purple_3}};
|
||||
$purple_4: {{purple_4}};
|
||||
$purple_5: {{purple_5}};
|
||||
$brown_1: {{brown_1}};
|
||||
$brown_2: {{brown_2}};
|
||||
$brown_3: {{brown_3}};
|
||||
$brown_4: {{brown_4}};
|
||||
$brown_5: {{brown_5}};
|
||||
$light_1: {{light_1}};
|
||||
$light_2: {{light_2}};
|
||||
$light_3: {{light_3}};
|
||||
$light_4: {{light_4}};
|
||||
$light_5: {{light_5}};
|
||||
$dark_1: {{dark_1}};
|
||||
$dark_2: {{dark_2}};
|
||||
$dark_3: {{dark_3}};
|
||||
$dark_4: {{dark_4}};
|
||||
$dark_5: {{dark_5}};
|
208
data/shell/templates/42/panel.template
Normal file
208
data/shell/templates/42/panel.template
Normal file
|
@ -0,0 +1,208 @@
|
|||
/* Top Bar */
|
||||
// a.k.a. the panel
|
||||
|
||||
$panel_bg_color: #000; // TODO: Allow to modify this value
|
||||
$panel_fg_color: if($variant == 'light', lighten($bg_color, 10%), darken($fg_color, 5%)); // TODO: Allow to modify this value
|
||||
$panel_height: 2.2em; // TODO: Allow to modify this value
|
||||
$panel_transition_duration: 250ms; // same as the overview transition duration
|
||||
|
||||
#panel {
|
||||
background-color: $panel_bg_color;
|
||||
font-weight: bold;
|
||||
height: $panel_height;
|
||||
@extend %numeric;
|
||||
transition-duration: $panel_transition_duration;
|
||||
|
||||
// transparent panel on lock & login screens
|
||||
&.unlock-screen,
|
||||
&.login-screen,
|
||||
&:overview {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
// panel menus
|
||||
.panel-button {
|
||||
font-weight: bold;
|
||||
color: $panel_fg_color;
|
||||
-natural-hpadding: $base_padding * 2;
|
||||
-minimum-hpadding: $base_padding;
|
||||
transition-duration: 150ms;
|
||||
border: 3px solid transparent;
|
||||
border-radius: 99px;
|
||||
|
||||
&.clock-display {
|
||||
.clock {
|
||||
transition-duration: 150ms;
|
||||
border: 3px solid transparent;
|
||||
border-radius: 99px;
|
||||
}
|
||||
}
|
||||
|
||||
&.screen-recording-indicator {
|
||||
box-shadow: inset 0 0 0 100px $screenshot_ui_button_red;
|
||||
|
||||
StBoxLayout {
|
||||
spacing: $base_padding;
|
||||
}
|
||||
|
||||
StIcon {
|
||||
icon-size: $base_icon_size;
|
||||
}
|
||||
}
|
||||
|
||||
&:active, &:overview, &:focus, &:checked {
|
||||
box-shadow: inset 0 0 0 100px transparentize($panel_fg_color, 0.8);
|
||||
|
||||
// The clock display needs to have the background on .clock because
|
||||
// we want to exclude the do-not-disturb indicator from the background
|
||||
&.clock-display {
|
||||
box-shadow: none;
|
||||
|
||||
.clock {
|
||||
box-shadow: inset 0 0 0 100px transparentize($panel_fg_color, 0.8);
|
||||
}
|
||||
}
|
||||
|
||||
&.screen-recording-indicator {
|
||||
box-shadow: inset 0 0 0 100px transparentize($screenshot_ui_button_red, 0.15);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
box-shadow: inset 0 0 0 100px transparentize($panel_fg_color, 0.85);
|
||||
&.clock-display {
|
||||
box-shadow: none;
|
||||
.clock {
|
||||
box-shadow: inset 0 0 0 100px transparentize($panel_fg_color, 0.85);
|
||||
}
|
||||
}
|
||||
|
||||
&.screen-recording-indicator {
|
||||
box-shadow: inset 0 0 0 100px transparentize($screenshot_ui_button_red, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
&:active:hover, &:overview:hover, &:focus:hover, &:checked:hover {
|
||||
box-shadow: inset 0 0 0 100px transparentize($panel_fg_color, 0.75);
|
||||
&.clock-display {
|
||||
box-shadow: none;
|
||||
.clock {
|
||||
box-shadow: inset 0 0 0 100px transparentize($panel_fg_color, 0.75);
|
||||
}
|
||||
}
|
||||
|
||||
&.screen-recording-indicator {
|
||||
box-shadow: inset 0 0 0 100px transparentize($screenshot_ui_button_red, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
// status area icons
|
||||
.system-status-icon {
|
||||
icon-size: $base_icon_size;
|
||||
padding: $base_padding - 1px;
|
||||
margin: 0 $base_margin;
|
||||
}
|
||||
|
||||
.panel-status-indicators-box .system-status-icon,
|
||||
.panel-status-menu-box .system-status-icon {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
// app menu icon
|
||||
.app-menu-icon {
|
||||
-st-icon-style: symbolic;
|
||||
// dimensions of the icon are hardcoded
|
||||
}
|
||||
|
||||
&#panelActivities {
|
||||
-natural-hpadding: $base_padding * 3;
|
||||
}
|
||||
}
|
||||
|
||||
&.unlock-screen,
|
||||
&.login-screen,
|
||||
&:overview {
|
||||
.panel-button {
|
||||
&:active, &:overview, &:focus, &:checked {
|
||||
box-shadow: inset 0 0 0 100px rgba(255,255,255, 0.15);
|
||||
|
||||
&.clock-display {
|
||||
box-shadow: none;
|
||||
|
||||
.clock {
|
||||
box-shadow: inset 0 0 0 100px rgba(255,255,255, 0.15);
|
||||
}
|
||||
}
|
||||
|
||||
&.screen-recording-indicator {
|
||||
box-shadow: inset 0 0 0 100px transparentize($screenshot_ui_button_red, 0.15);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
box-shadow: inset 0 0 0 100px rgba(255,255,255, 0.10);
|
||||
&.clock-display {
|
||||
box-shadow: none;
|
||||
.clock {
|
||||
box-shadow: inset 0 0 0 100px rgba(255,255,255, 0.10);
|
||||
}
|
||||
}
|
||||
|
||||
&.screen-recording-indicator {
|
||||
box-shadow: inset 0 0 0 100px transparentize($screenshot_ui_button_red, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
&:active:hover, &:overview:hover, &:focus:hover, &:checked:hover {
|
||||
box-shadow: inset 0 0 0 100px rgba(255,255,255, 0.2);
|
||||
&.clock-display {
|
||||
box-shadow: none;
|
||||
.clock {
|
||||
box-shadow: inset 0 0 0 100px rgba(255,255,255, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
&.screen-recording-indicator {
|
||||
box-shadow: inset 0 0 0 100px transparentize($screenshot_ui_button_red, 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.panel-status-indicators-box,
|
||||
.panel-status-menu-box {
|
||||
spacing: 2px;
|
||||
}
|
||||
|
||||
// spacing between power icon and (optional) percentage label
|
||||
.power-status.panel-status-indicators-box {
|
||||
spacing: 0;
|
||||
}
|
||||
|
||||
// indicator for active
|
||||
.screencast-indicator,
|
||||
.remote-access-indicator { color: $warning_color; }
|
||||
}
|
||||
|
||||
// App Menu
|
||||
#appMenu {
|
||||
spacing: $base_padding;
|
||||
.label-shadow { color: transparent; }
|
||||
}
|
||||
|
||||
#appMenu .panel-status-menu-box {
|
||||
padding: 0 $base_padding;
|
||||
spacing: $base_padding;
|
||||
}
|
||||
|
||||
|
||||
// Clock
|
||||
|
||||
.clock-display-box {
|
||||
spacing: 2px;
|
||||
|
||||
.clock {
|
||||
padding-left: $base_padding * 2;
|
||||
padding-right: $base_padding * 2;
|
||||
}
|
||||
}
|
16
data/shell/templates/42/switches.template
Normal file
16
data/shell/templates/42/switches.template
Normal file
|
@ -0,0 +1,16 @@
|
|||
/* Switches */
|
||||
|
||||
// these are equal to the size of the SVG assets
|
||||
$switch_height: 26px;
|
||||
$switch_width: 48px;
|
||||
|
||||
.toggle-switch {
|
||||
color: $fg_color;
|
||||
height: $switch_height;
|
||||
width: $switch_width;
|
||||
background-size: contain;
|
||||
background-image: if($variant == 'light', url("resource:///org/gnome/shell/theme/toggle-off-light.svg"), url("resource:///org/gnome/shell/theme/toggle-off.svg"));
|
||||
&:checked {
|
||||
background-image: if($variant == 'light', url("resource:///org/gnome/shell/theme/toggle-on-light.svg"), url("asstets/toggle-on.svg"));
|
||||
}
|
||||
}
|
18
data/shell/templates/43/check-box.template
Normal file
18
data/shell/templates/43/check-box.template
Normal file
|
@ -0,0 +1,18 @@
|
|||
/* Check Boxes */
|
||||
|
||||
// these are equal to the size of the SVG assets
|
||||
$check_height: 24px;
|
||||
$check_width: 24px;
|
||||
|
||||
|
||||
.check-box {
|
||||
StBoxLayout { spacing: .8em; }
|
||||
StBin {
|
||||
width: $check_width;
|
||||
height: $check_height;
|
||||
background-image: if($variant == 'light', url("resource:///org/gnome/shell/theme/checkbox-off-light.svg"), url("resource:///org/gnome/shell/theme/checkbox-off.svg"));
|
||||
}
|
||||
&:focus StBin { background-image: if($variant == 'light', url("resource:///org/gnome/shell/theme/checkbox-off-focused-light.svg"), url("resource:///org/gnome/shell/theme/checkbox-off-focused.svg"));; }
|
||||
&:checked StBin { background-image: url("resource:///org/gnome/shell/theme/checkbox.svg"); }
|
||||
&:focus:checked StBin { background-image: url("resource:///org/gnome/shell/theme/checkbox-focused.svg"); }
|
||||
}
|
77
data/shell/templates/43/colors.template
Normal file
77
data/shell/templates/43/colors.template
Normal file
|
@ -0,0 +1,77 @@
|
|||
// When color definition differs for dark and light variant,
|
||||
// it gets @if-ed depending on $variant
|
||||
|
||||
@import '_palette.scss';
|
||||
|
||||
$is_highcontrast: "false";
|
||||
|
||||
$_dark_base_color: darken(desaturate({{bg_color}}, 2%), 2%);
|
||||
|
||||
$base_color: $_dark_base_color;
|
||||
$bg_color: if($variant == 'light', darken($base_color, 5%), lighten($base_color, 5%));
|
||||
$fg_color: if($variant == 'light', transparentize(black, .2), {{fg_color}});
|
||||
|
||||
$accent_fg_color: {{accent_fg_color}};
|
||||
$selected_fg_color: if($variant == 'light', $accent_fg_color, {{selected_fg_color}});
|
||||
$selected_bg_color: {{selected_bg_color}};
|
||||
$selected_borders_color: if($variant == 'light', darken($selected_bg_color, 15%), darken($selected_bg_color, 30%)); // NOTE: Unused in GNOME Shell 43
|
||||
|
||||
$borders_color: if($variant == 'light', transparentize($fg_color, .5), transparentize($fg_color, .9));
|
||||
$borders_edge: if($variant == 'light', rgba(255,255,255,0.8), lighten($bg_color, 5%));
|
||||
|
||||
$link_color: if($variant == 'light', darken($selected_bg_color, 10%), lighten($selected_bg_color, 20%));
|
||||
$link_visited_color: if($variant == 'light', darken($selected_bg_color, 20%), lighten($selected_bg_color, 10%)); // NOTE: Unused in GNOME Shell 43
|
||||
|
||||
$warning_color: {{warning_bg_color}};
|
||||
$error_color: {{error_bg_color}};
|
||||
$success_color: {{success_bg_color}}; // NOTE: Unused in GNOME Shell 43
|
||||
$destructive_color: {{destructive_bg_color}};
|
||||
|
||||
// NOTE: Used also in overview for folder colors, in search results, partially in text and for indicators below app icons
|
||||
$osd_fg_color: {{osd_fg_color}};
|
||||
$osd_bg_color: $_dark_base_color; // hardcoded for both light & dark
|
||||
$osd_insensitive_bg_color: transparentize(mix($osd_fg_color, opacify($osd_bg_color, 1), 10%), 0.5); // NOTE: Unused in GNOME Shell 43
|
||||
$osd_insensitive_fg_color: if($variant == 'light', mix($osd_fg_color, $osd_bg_color, 80%), mix($osd_fg_color, $osd_bg_color, 70%));
|
||||
$osd_borders_color: transparentize(black, 0.3);
|
||||
$osd_outer_borders_color: transparentize($osd_fg_color, 0.98);
|
||||
|
||||
$shadow_color: if($variant == 'light', rgba(0,0,0,0.1), rgba(0,0,0,0.2));
|
||||
|
||||
// button
|
||||
$button_mix_factor: 5%;
|
||||
|
||||
// cards
|
||||
$card_bg_color: if($variant == 'light', darken($bg_color, 5%), lighten($bg_color, 2%)); // TODO: Allow to modify this value
|
||||
$card_outer_borders_color: transparentize($fg_color, 0.98);
|
||||
|
||||
// notifications
|
||||
$bubble_buttons_color: if($variant == 'light', darken($bg_color, 12%), lighten($bg_color, 10%));
|
||||
|
||||
// overview background color
|
||||
$system_bg_color: darken(desaturate({{system_bg_color}}, 2%), 2%);
|
||||
|
||||
//insensitive state derived colors
|
||||
$insensitive_fg_color: mix($fg_color, $bg_color, 50%);
|
||||
$insensitive_bg_color: mix($bg_color, $base_color, 60%);
|
||||
$insensitive_borders_color: mix($borders_color, $base_color, 60%); // NOTE: Unused in GNOME Shell 43
|
||||
|
||||
//colors for the backdrop state, derived from the main colors.
|
||||
// NOTE: This entire section doesn't seem to be used anywhere in GNOME Shell 43
|
||||
$backdrop_base_color: if($variant =='light', darken($base_color,1%), lighten($base_color,1%));
|
||||
$backdrop_bg_color: $bg_color;
|
||||
$backdrop_fg_color: mix($fg_color, $backdrop_bg_color, 80%);
|
||||
$backdrop_insensitive_color: if($variant =='light', darken($backdrop_bg_color,15%), lighten($backdrop_bg_color,15%));
|
||||
$backdrop_borders_color: mix($borders_color, $bg_color, 90%);
|
||||
$backdrop_dark_fill: mix($backdrop_borders_color,$backdrop_bg_color, 35%);
|
||||
|
||||
// derived checked colors
|
||||
$checked_bg_color: if($variant=='light', darken($bg_color, 7%), lighten($bg_color, 7%));
|
||||
$checked_fg_color: if($variant=='light', darken($fg_color, 7%), lighten($fg_color, 7%)); // NOTE: Unused in GNOME Shell 43
|
||||
|
||||
// derived hover colors
|
||||
$hover_bg_color: if($variant=='light', darken($bg_color, 3%), lighten($bg_color, 10%));
|
||||
$hover_fg_color: if($variant=='light', darken($fg_color, 5%), lighten($fg_color, 10%));
|
||||
|
||||
// derived active colors
|
||||
$active_bg_color: if($variant=='light', darken($bg_color, 5%), lighten($bg_color, 12%));
|
||||
$active_fg_color: if($variant=='light', darken($fg_color, 5%), lighten($fg_color, 12%));
|
15
data/shell/templates/43/gnome-shell.template
Normal file
15
data/shell/templates/43/gnome-shell.template
Normal file
|
@ -0,0 +1,15 @@
|
|||
$variant: {{theme_variant}};
|
||||
|
||||
/* Generated with Gradience
|
||||
*
|
||||
* Issues caused by theming should be reported to Gradience repository, and not upstream
|
||||
*
|
||||
* https://github.com/GradienceTeam/Gradience
|
||||
*/
|
||||
|
||||
@import "gnome-shell-sass/_colors"; //use gtk colors
|
||||
@import "gnome-shell-sass/_drawing";
|
||||
@import "gnome-shell-sass/_common";
|
||||
@import "gnome-shell-sass/_widgets";
|
||||
|
||||
{{custom_css}}
|
46
data/shell/templates/43/palette.template
Normal file
46
data/shell/templates/43/palette.template
Normal file
|
@ -0,0 +1,46 @@
|
|||
//GNOME Color Palette
|
||||
$blue_1: {{blue_1}};
|
||||
$blue_2: {{blue_2}};
|
||||
$blue_3: {{blue_3}};
|
||||
$blue_4: {{blue_4}};
|
||||
$blue_5: {{blue_5}};
|
||||
$green_1: {{green_1}};
|
||||
$green_2: {{green_2}};
|
||||
$green_3: {{green_3}};
|
||||
$green_4: {{green_4}};
|
||||
$green_5: {{green_5}};
|
||||
$yellow_1: {{yellow_1}};
|
||||
$yellow_2: {{yellow_2}};
|
||||
$yellow_3: {{yellow_3}};
|
||||
$yellow_4: {{yellow_4}};
|
||||
$yellow_5: {{yellow_5}};
|
||||
$orange_1: {{orange_1}};
|
||||
$orange_2: {{orange_2}};
|
||||
$orange_3: {{orange_3}};
|
||||
$orange_4: {{orange_4}};
|
||||
$orange_5: {{orange_5}};
|
||||
$red_1: {{red_1}};
|
||||
$red_2: {{red_2}};
|
||||
$red_3: {{red_3}};
|
||||
$red_4: {{red_4}};
|
||||
$red_5: {{red_5}};
|
||||
$purple_1: {{purple_1}};
|
||||
$purple_2: {{purple_2}};
|
||||
$purple_3: {{purple_3}};
|
||||
$purple_4: {{purple_4}};
|
||||
$purple_5: {{purple_5}};
|
||||
$brown_1: {{brown_1}};
|
||||
$brown_2: {{brown_2}};
|
||||
$brown_3: {{brown_3}};
|
||||
$brown_4: {{brown_4}};
|
||||
$brown_5: {{brown_5}};
|
||||
$light_1: {{light_1}};
|
||||
$light_2: {{light_2}};
|
||||
$light_3: {{light_3}};
|
||||
$light_4: {{light_4}};
|
||||
$light_5: {{light_5}};
|
||||
$dark_1: {{dark_1}};
|
||||
$dark_2: {{dark_2}};
|
||||
$dark_3: {{dark_3}};
|
||||
$dark_4: {{dark_4}};
|
||||
$dark_5: {{dark_5}};
|
233
data/shell/templates/43/panel.template
Normal file
233
data/shell/templates/43/panel.template
Normal file
|
@ -0,0 +1,233 @@
|
|||
/* Top Bar */
|
||||
// a.k.a. the panel
|
||||
|
||||
$panel_bg_color: #000; // TODO: Allow to modify this value
|
||||
$panel_fg_color: if($variant == 'light', lighten($bg_color, 10%), darken($fg_color, 5%)); // TODO: Allow to modify this value
|
||||
$panel_height: 2.2em; // TODO: Allow to modify this value
|
||||
$panel_transition_duration: 250ms; // same as the overview transition duration
|
||||
|
||||
#panel {
|
||||
background-color: $panel_bg_color;
|
||||
font-weight: bold;
|
||||
height: $panel_height;
|
||||
@extend %numeric;
|
||||
transition-duration: $panel_transition_duration;
|
||||
|
||||
// transparent panel on lock & login screens
|
||||
&.unlock-screen,
|
||||
&.login-screen,
|
||||
&:overview {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
// panel menus
|
||||
.panel-button {
|
||||
font-weight: bold;
|
||||
color: $panel_fg_color;
|
||||
-natural-hpadding: $base_padding * 2;
|
||||
-minimum-hpadding: $base_padding;
|
||||
transition-duration: 150ms;
|
||||
border: 3px solid transparent;
|
||||
border-radius: 99px;
|
||||
|
||||
&.clock-display {
|
||||
.clock {
|
||||
transition-duration: 150ms;
|
||||
border: 3px solid transparent;
|
||||
border-radius: 99px;
|
||||
}
|
||||
}
|
||||
|
||||
&.screen-recording-indicator {
|
||||
box-shadow: inset 0 0 0 100px $screenshot_ui_button_red;
|
||||
}
|
||||
&.screen-sharing-indicator {
|
||||
box-shadow: inset 0 0 0 100px $warning_color;
|
||||
StBoxLayout { margin: 0 $base_padding; }
|
||||
}
|
||||
|
||||
&.screen-recording-indicator,
|
||||
&.screen-sharing-indicator {
|
||||
StBoxLayout {
|
||||
spacing: $base_padding;
|
||||
}
|
||||
|
||||
StIcon {
|
||||
icon-size: $base_icon_size;
|
||||
}
|
||||
}
|
||||
|
||||
&:active, &:overview, &:focus, &:checked {
|
||||
box-shadow: inset 0 0 0 100px transparentize($panel_fg_color, 0.8);
|
||||
|
||||
// The clock display needs to have the background on .clock because
|
||||
// we want to exclude the do-not-disturb indicator from the background
|
||||
&.clock-display {
|
||||
box-shadow: none;
|
||||
|
||||
.clock {
|
||||
box-shadow: inset 0 0 0 100px transparentize($panel_fg_color, 0.8);
|
||||
}
|
||||
}
|
||||
|
||||
&.screen-recording-indicator {
|
||||
box-shadow: inset 0 0 0 100px transparentize($screenshot_ui_button_red, 0.15);
|
||||
}
|
||||
&.screen-sharing-indicator {
|
||||
box-shadow: inset 0 0 0 100px transparentize($warning_color, 0.15);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
box-shadow: inset 0 0 0 100px transparentize($panel_fg_color, 0.85);
|
||||
&.clock-display {
|
||||
box-shadow: none;
|
||||
.clock {
|
||||
box-shadow: inset 0 0 0 100px transparentize($panel_fg_color, 0.85);
|
||||
}
|
||||
}
|
||||
|
||||
&.screen-recording-indicator {
|
||||
box-shadow: inset 0 0 0 100px transparentize($screenshot_ui_button_red, 0.1);
|
||||
}
|
||||
&.screen-sharing-indicator {
|
||||
box-shadow: inset 0 0 0 100px transparentize($warning_color, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
&:active:hover, &:overview:hover, &:focus:hover, &:checked:hover {
|
||||
box-shadow: inset 0 0 0 100px transparentize($panel_fg_color, 0.75);
|
||||
&.clock-display {
|
||||
box-shadow: none;
|
||||
.clock {
|
||||
box-shadow: inset 0 0 0 100px transparentize($panel_fg_color, 0.75);
|
||||
}
|
||||
}
|
||||
|
||||
&.screen-recording-indicator {
|
||||
box-shadow: inset 0 0 0 100px transparentize($screenshot_ui_button_red, 0.2);
|
||||
}
|
||||
&.screen-sharing-indicator {
|
||||
box-shadow: inset 0 0 0 100px transparentize($warning_color, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
// status area icons
|
||||
.system-status-icon {
|
||||
icon-size: $base_icon_size;
|
||||
padding: $base_padding - 1px;
|
||||
margin: 0 $base_margin;
|
||||
}
|
||||
|
||||
.panel-status-indicators-box .system-status-icon,
|
||||
.panel-status-menu-box .system-status-icon {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
// app menu icon
|
||||
.app-menu-icon {
|
||||
-st-icon-style: symbolic;
|
||||
// dimensions of the icon are hardcoded
|
||||
}
|
||||
|
||||
&#panelActivities {
|
||||
-natural-hpadding: $base_padding * 3;
|
||||
}
|
||||
}
|
||||
|
||||
&.unlock-screen,
|
||||
&.login-screen,
|
||||
&:overview {
|
||||
.panel-button {
|
||||
&:active, &:overview, &:focus, &:checked {
|
||||
box-shadow: inset 0 0 0 100px rgba(255,255,255, 0.15);
|
||||
|
||||
&.clock-display {
|
||||
box-shadow: none;
|
||||
|
||||
.clock {
|
||||
box-shadow: inset 0 0 0 100px rgba(255,255,255, 0.15);
|
||||
}
|
||||
}
|
||||
|
||||
&.screen-recording-indicator {
|
||||
box-shadow: inset 0 0 0 100px transparentize($screenshot_ui_button_red, 0.15);
|
||||
}
|
||||
&.screen-sharing-indicator {
|
||||
box-shadow: inset 0 0 0 100px transparentize($warning_color, 0.15);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
box-shadow: inset 0 0 0 100px rgba(255,255,255, 0.10);
|
||||
&.clock-display {
|
||||
box-shadow: none;
|
||||
.clock {
|
||||
box-shadow: inset 0 0 0 100px rgba(255,255,255, 0.10);
|
||||
}
|
||||
}
|
||||
|
||||
&.screen-recording-indicator {
|
||||
box-shadow: inset 0 0 0 100px transparentize($screenshot_ui_button_red, 0.1);
|
||||
}
|
||||
&.screen-sharing-indicator {
|
||||
box-shadow: inset 0 0 0 100px transparentize($warning_color, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
&:active:hover, &:overview:hover, &:focus:hover, &:checked:hover {
|
||||
box-shadow: inset 0 0 0 100px rgba(255,255,255, 0.2);
|
||||
&.clock-display {
|
||||
box-shadow: none;
|
||||
.clock {
|
||||
box-shadow: inset 0 0 0 100px rgba(255,255,255, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
&.screen-recording-indicator {
|
||||
box-shadow: inset 0 0 0 100px transparentize($screenshot_ui_button_red, 0.2);
|
||||
}
|
||||
&.screen-sharing-indicator {
|
||||
box-shadow: inset 0 0 0 100px transparentize($warning_color, 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.panel-status-indicators-box,
|
||||
.panel-status-menu-box {
|
||||
spacing: 2px;
|
||||
}
|
||||
|
||||
// spacing between power icon and (optional) percentage label
|
||||
.power-status.panel-status-indicators-box {
|
||||
spacing: 0;
|
||||
}
|
||||
|
||||
// indicator for active
|
||||
.screencast-indicator,
|
||||
.remote-access-indicator { color: $warning_color; }
|
||||
}
|
||||
|
||||
// App Menu
|
||||
#appMenu {
|
||||
spacing: $base_padding;
|
||||
.label-shadow { color: transparent; }
|
||||
}
|
||||
|
||||
#appMenu .panel-status-menu-box {
|
||||
padding: 0 $base_padding;
|
||||
spacing: $base_padding;
|
||||
}
|
||||
|
||||
|
||||
// Clock
|
||||
|
||||
.clock-display-box {
|
||||
spacing: 2px;
|
||||
|
||||
.clock {
|
||||
padding-left: $base_padding * 2;
|
||||
padding-right: $base_padding * 2;
|
||||
}
|
||||
}
|
16
data/shell/templates/43/switches.template
Normal file
16
data/shell/templates/43/switches.template
Normal file
|
@ -0,0 +1,16 @@
|
|||
/* Switches */
|
||||
|
||||
// these are equal to the size of the SVG assets
|
||||
$switch_height: 26px;
|
||||
$switch_width: 48px;
|
||||
|
||||
.toggle-switch {
|
||||
color: $fg_color;
|
||||
height: $switch_height;
|
||||
width: $switch_width;
|
||||
background-size: contain;
|
||||
background-image: if($variant == 'light', url("resource:///org/gnome/shell/theme/toggle-off-light.svg"), url("resource:///org/gnome/shell/theme/toggle-off.svg"));
|
||||
&:checked {
|
||||
background-image: if($variant == 'light', url("resource:///org/gnome/shell/theme/toggle-on-light.svg"), url("assets/toggle-on.svg"));
|
||||
}
|
||||
}
|
18
data/shell/templates/44/check-box.template
Normal file
18
data/shell/templates/44/check-box.template
Normal file
|
@ -0,0 +1,18 @@
|
|||
/* Check Boxes */
|
||||
|
||||
// these are equal to the size of the SVG assets
|
||||
$check_height: 24px;
|
||||
$check_width: 24px;
|
||||
|
||||
|
||||
.check-box {
|
||||
StBoxLayout { spacing: .8em; }
|
||||
StBin {
|
||||
width: $check_width;
|
||||
height: $check_height;
|
||||
background-image: if($variant == 'light', url("resource:///org/gnome/shell/theme/checkbox-off-light.svg"), url("resource:///org/gnome/shell/theme/checkbox-off.svg"));
|
||||
}
|
||||
&:focus StBin { background-image: if($variant == 'light', url("resource:///org/gnome/shell/theme/checkbox-off-focused-light.svg"), url("resource:///org/gnome/shell/theme/checkbox-off-focused.svg"));; }
|
||||
&:checked StBin { background-image: url("resource:///org/gnome/shell/theme/checkbox.svg"); }
|
||||
&:focus:checked StBin { background-image: url("resource:///org/gnome/shell/theme/checkbox-focused.svg"); }
|
||||
}
|
73
data/shell/templates/44/colors.template
Normal file
73
data/shell/templates/44/colors.template
Normal file
|
@ -0,0 +1,73 @@
|
|||
// When color definition differs for dark and light variant,
|
||||
// it gets @if-ed depending on $variant
|
||||
|
||||
@import '_palette.scss';
|
||||
|
||||
$is_highcontrast: false;
|
||||
|
||||
$_dark_base_color: darken(desaturate({{bg_color}}, 2%), 2%);
|
||||
|
||||
$base_color: $_dark_base_color;
|
||||
$bg_color: if($variant == 'light', darken($base_color, 5%), lighten($base_color, 5%));
|
||||
$fg_color: if($variant == 'light', transparentize(black, .2), {{fg_color}});
|
||||
|
||||
$accent_fg_color: {{accent_fg_color}};
|
||||
$selected_fg_color: if($variant == 'light', $accent_fg_color, {{selected_fg_color}});
|
||||
$selected_bg_color: {{selected_bg_color}};
|
||||
$selected_borders_color: if($variant == 'light', darken($selected_bg_color, 15%), darken($selected_bg_color, 30%));
|
||||
|
||||
$borders_color: if($variant == 'light', transparentize($fg_color, .5), transparentize($fg_color, .9));
|
||||
$outer_borders_color: if($variant == 'light', rgba(255,255,255,0.8), lighten($bg_color, 5%));
|
||||
|
||||
$link_color: if($variant == 'light', darken($selected_bg_color, 10%), lighten($selected_bg_color, 20%));
|
||||
$link_visited_color: if($variant == 'light', darken($selected_bg_color, 20%), lighten($selected_bg_color, 10%)); // NOTE: Unused in GNOME Shell 44
|
||||
|
||||
$warning_color: {{warning_bg_color}};
|
||||
$error_color: {{error_bg_color}};
|
||||
$success_color: {{success_bg_color}}; // NOTE: Unused in GNOME Shell 44
|
||||
$destructive_color: {{destructive_bg_color}};
|
||||
|
||||
// NOTE: Used also in overview for folder colors, in search results, partially in text and for indicators below app icons
|
||||
$osd_fg_color: {{osd_fg_color}};
|
||||
$osd_bg_color: $_dark_base_color; // hardcoded for both light & dark
|
||||
$osd_insensitive_bg_color: transparentize(mix($osd_fg_color, opacify($osd_bg_color, 1), 10%), 0.5); // NOTE: Unused in GNOME Shell 44
|
||||
$osd_insensitive_fg_color: if($variant == 'light', mix($osd_fg_color, $osd_bg_color, 80%), mix($osd_fg_color, $osd_bg_color, 70%));
|
||||
$osd_borders_color: transparentize(black, 0.3);
|
||||
$osd_outer_borders_color: transparentize($osd_fg_color, 0.9);
|
||||
|
||||
$shadow_color: if($variant == 'light', rgba(0,0,0,0.1), rgba(0,0,0,0.2));
|
||||
|
||||
// button
|
||||
$button_mix_factor: 9%;
|
||||
|
||||
// notifications
|
||||
$bubble_buttons_color: if($variant == 'light', darken($bg_color, 7%), lighten($bg_color, 5%));
|
||||
|
||||
// overview background color
|
||||
$system_bg_color: darken(desaturate({{system_bg_color}}, 2%), 2%);
|
||||
|
||||
//insensitive state derived colors
|
||||
$insensitive_fg_color: mix($fg_color, $bg_color, 50%);
|
||||
$insensitive_bg_color: mix($bg_color, $base_color, 60%);
|
||||
$insensitive_borders_color: mix($borders_color, $base_color, 60%); // NOTE: Unused in GNOME Shell 44
|
||||
|
||||
//colors for the backdrop state, derived from the main colors.
|
||||
// NOTE: This entire section doesn't seem to be used anywhere in GNOME Shell 44
|
||||
$backdrop_base_color: if($variant =='light', darken($base_color,1%), lighten($base_color,1%));
|
||||
$backdrop_bg_color: $bg_color;
|
||||
$backdrop_fg_color: mix($fg_color, $backdrop_bg_color, 80%);
|
||||
$backdrop_insensitive_color: if($variant =='light', darken($backdrop_bg_color,15%), lighten($backdrop_bg_color,15%));
|
||||
$backdrop_borders_color: mix($borders_color, $bg_color, 90%);
|
||||
$backdrop_dark_fill: mix($backdrop_borders_color,$backdrop_bg_color, 35%);
|
||||
|
||||
// derived checked colors
|
||||
$checked_bg_color: if($variant=='light', darken($bg_color, 7%), lighten($bg_color, 7%));
|
||||
$checked_fg_color: if($variant=='light', darken($fg_color, 7%), lighten($fg_color, 7%)); // NOTE: Unused in GNOME Shell 44
|
||||
|
||||
// derived hover colors
|
||||
$hover_bg_color: if($variant=='light', darken($bg_color, 3%), lighten($bg_color, 10%));
|
||||
$hover_fg_color: if($variant=='light', darken($fg_color, 5%), lighten($fg_color, 10%));
|
||||
|
||||
// derived active colors
|
||||
$active_bg_color: if($variant=='light', darken($bg_color, 5%), lighten($bg_color, 12%));
|
||||
$active_fg_color: if($variant=='light', darken($fg_color, 5%), lighten($fg_color, 12%));
|
15
data/shell/templates/44/gnome-shell.template
Normal file
15
data/shell/templates/44/gnome-shell.template
Normal file
|
@ -0,0 +1,15 @@
|
|||
$variant: {{theme_variant}};
|
||||
|
||||
/* Generated with Gradience
|
||||
*
|
||||
* Issues caused by theming should be reported to Gradience repository, and not upstream
|
||||
*
|
||||
* https://github.com/GradienceTeam/Gradience
|
||||
*/
|
||||
|
||||
@import "gnome-shell-sass/_colors"; //use gtk colors
|
||||
@import "gnome-shell-sass/_drawing";
|
||||
@import "gnome-shell-sass/_common";
|
||||
@import "gnome-shell-sass/_widgets";
|
||||
|
||||
{{custom_css}}
|
46
data/shell/templates/44/palette.template
Normal file
46
data/shell/templates/44/palette.template
Normal file
|
@ -0,0 +1,46 @@
|
|||
//GNOME Color Palette
|
||||
$blue_1: {{blue_1}};
|
||||
$blue_2: {{blue_2}};
|
||||
$blue_3: {{blue_3}};
|
||||
$blue_4: {{blue_4}};
|
||||
$blue_5: {{blue_5}};
|
||||
$green_1: {{green_1}};
|
||||
$green_2: {{green_2}};
|
||||
$green_3: {{green_3}};
|
||||
$green_4: {{green_4}};
|
||||
$green_5: {{green_5}};
|
||||
$yellow_1: {{yellow_1}};
|
||||
$yellow_2: {{yellow_2}};
|
||||
$yellow_3: {{yellow_3}};
|
||||
$yellow_4: {{yellow_4}};
|
||||
$yellow_5: {{yellow_5}};
|
||||
$orange_1: {{orange_1}};
|
||||
$orange_2: {{orange_2}};
|
||||
$orange_3: {{orange_3}};
|
||||
$orange_4: {{orange_4}};
|
||||
$orange_5: {{orange_5}};
|
||||
$red_1: {{red_1}};
|
||||
$red_2: {{red_2}};
|
||||
$red_3: {{red_3}};
|
||||
$red_4: {{red_4}};
|
||||
$red_5: {{red_5}};
|
||||
$purple_1: {{purple_1}};
|
||||
$purple_2: {{purple_2}};
|
||||
$purple_3: {{purple_3}};
|
||||
$purple_4: {{purple_4}};
|
||||
$purple_5: {{purple_5}};
|
||||
$brown_1: {{brown_1}};
|
||||
$brown_2: {{brown_2}};
|
||||
$brown_3: {{brown_3}};
|
||||
$brown_4: {{brown_4}};
|
||||
$brown_5: {{brown_5}};
|
||||
$light_1: {{light_1}};
|
||||
$light_2: {{light_2}};
|
||||
$light_3: {{light_3}};
|
||||
$light_4: {{light_4}};
|
||||
$light_5: {{light_5}};
|
||||
$dark_1: {{dark_1}};
|
||||
$dark_2: {{dark_2}};
|
||||
$dark_3: {{dark_3}};
|
||||
$dark_4: {{dark_4}};
|
||||
$dark_5: {{dark_5}};
|
232
data/shell/templates/44/panel.template
Normal file
232
data/shell/templates/44/panel.template
Normal file
|
@ -0,0 +1,232 @@
|
|||
/* Top Bar */
|
||||
// a.k.a. the panel
|
||||
|
||||
$panel_height: 2.2em;
|
||||
$panel_transition_duration: 250ms; // same as the overview transition duration
|
||||
|
||||
#panel {
|
||||
background-color: $panel_bg_color;
|
||||
font-weight: bold;
|
||||
height: $panel_height;
|
||||
@extend %numeric;
|
||||
transition-duration: $panel_transition_duration;
|
||||
|
||||
// transparent panel on lock & login screens
|
||||
&.unlock-screen,
|
||||
&.login-screen,
|
||||
&:overview {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
// panel menus
|
||||
.panel-button {
|
||||
font-weight: bold;
|
||||
color: $panel_fg_color;
|
||||
-natural-hpadding: $base_padding * 2;
|
||||
-minimum-hpadding: $base_padding;
|
||||
transition-duration: 150ms;
|
||||
border: 3px solid transparent;
|
||||
border-radius: 99px;
|
||||
|
||||
&.clock-display {
|
||||
.clock {
|
||||
transition-duration: 150ms;
|
||||
border: 3px solid transparent;
|
||||
border-radius: 99px;
|
||||
}
|
||||
}
|
||||
|
||||
&.screen-recording-indicator {
|
||||
box-shadow: inset 0 0 0 100px $screenshot_ui_button_red;
|
||||
}
|
||||
|
||||
&.screen-sharing-indicator {
|
||||
box-shadow: inset 0 0 0 100px $warning_color;
|
||||
StBoxLayout { margin: 0 $base_padding; }
|
||||
}
|
||||
|
||||
&.screen-recording-indicator,
|
||||
&.screen-sharing-indicator {
|
||||
StBoxLayout {
|
||||
spacing: $base_padding;
|
||||
}
|
||||
|
||||
StIcon {
|
||||
icon-size: $base_icon_size;
|
||||
}
|
||||
}
|
||||
|
||||
&:active, &:overview, &:focus, &:checked {
|
||||
box-shadow: inset 0 0 0 100px transparentize($panel_fg_color, 0.8);
|
||||
|
||||
// The clock display needs to have the background on .clock because
|
||||
// we want to exclude the do-not-disturb indicator from the background
|
||||
&.clock-display {
|
||||
box-shadow: none;
|
||||
|
||||
.clock {
|
||||
box-shadow: inset 0 0 0 100px transparentize($panel_fg_color, 0.8);
|
||||
}
|
||||
}
|
||||
|
||||
&.screen-recording-indicator {
|
||||
box-shadow: inset 0 0 0 100px transparentize($screenshot_ui_button_red, 0.15);
|
||||
}
|
||||
&.screen-sharing-indicator {
|
||||
box-shadow: inset 0 0 0 100px transparentize($warning_color, 0.15);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
box-shadow: inset 0 0 0 100px transparentize($panel_fg_color, 0.85);
|
||||
&.clock-display {
|
||||
box-shadow: none;
|
||||
.clock {
|
||||
box-shadow: inset 0 0 0 100px transparentize($panel_fg_color, 0.85);
|
||||
}
|
||||
}
|
||||
|
||||
&.screen-recording-indicator {
|
||||
box-shadow: inset 0 0 0 100px transparentize($screenshot_ui_button_red, 0.1);
|
||||
}
|
||||
&.screen-sharing-indicator {
|
||||
box-shadow: inset 0 0 0 100px transparentize($warning_color, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
&:active:hover, &:overview:hover, &:focus:hover, &:checked:hover {
|
||||
box-shadow: inset 0 0 0 100px transparentize($panel_fg_color, 0.75);
|
||||
&.clock-display {
|
||||
box-shadow: none;
|
||||
.clock {
|
||||
box-shadow: inset 0 0 0 100px transparentize($panel_fg_color, 0.75);
|
||||
}
|
||||
}
|
||||
|
||||
&.screen-recording-indicator {
|
||||
box-shadow: inset 0 0 0 100px transparentize($screenshot_ui_button_red, 0.2);
|
||||
}
|
||||
&.screen-sharing-indicator {
|
||||
box-shadow: inset 0 0 0 100px transparentize($warning_color, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
// status area icons
|
||||
.system-status-icon {
|
||||
icon-size: $base_icon_size;
|
||||
padding: $base_padding - 1px;
|
||||
margin: 0 $base_margin;
|
||||
}
|
||||
|
||||
.panel-status-indicators-box .system-status-icon,
|
||||
.panel-status-menu-box .system-status-icon {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
// app menu icon
|
||||
.app-menu-icon {
|
||||
-st-icon-style: symbolic;
|
||||
// dimensions of the icon are hardcoded
|
||||
}
|
||||
|
||||
&#panelActivities {
|
||||
-natural-hpadding: $base_padding * 3;
|
||||
}
|
||||
}
|
||||
|
||||
&.unlock-screen,
|
||||
&.login-screen,
|
||||
&:overview {
|
||||
.panel-button {
|
||||
&:active, &:overview, &:focus, &:checked {
|
||||
box-shadow: inset 0 0 0 100px rgba(255,255,255, 0.15);
|
||||
|
||||
&.clock-display {
|
||||
box-shadow: none;
|
||||
|
||||
.clock {
|
||||
box-shadow: inset 0 0 0 100px rgba(255,255,255, 0.15);
|
||||
}
|
||||
}
|
||||
|
||||
&.screen-recording-indicator {
|
||||
box-shadow: inset 0 0 0 100px transparentize($screenshot_ui_button_red, 0.15);
|
||||
}
|
||||
&.screen-sharing-indicator {
|
||||
box-shadow: inset 0 0 0 100px transparentize($warning_color, 0.15);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
box-shadow: inset 0 0 0 100px rgba(255,255,255, 0.10);
|
||||
&.clock-display {
|
||||
box-shadow: none;
|
||||
.clock {
|
||||
box-shadow: inset 0 0 0 100px rgba(255,255,255, 0.10);
|
||||
}
|
||||
}
|
||||
|
||||
&.screen-recording-indicator {
|
||||
box-shadow: inset 0 0 0 100px transparentize($screenshot_ui_button_red, 0.1);
|
||||
}
|
||||
&.screen-sharing-indicator {
|
||||
box-shadow: inset 0 0 0 100px transparentize($warning_color, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
&:active:hover, &:overview:hover, &:focus:hover, &:checked:hover {
|
||||
box-shadow: inset 0 0 0 100px rgba(255,255,255, 0.2);
|
||||
&.clock-display {
|
||||
box-shadow: none;
|
||||
.clock {
|
||||
box-shadow: inset 0 0 0 100px rgba(255,255,255, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
&.screen-recording-indicator {
|
||||
box-shadow: inset 0 0 0 100px transparentize($screenshot_ui_button_red, 0.2);
|
||||
}
|
||||
&.screen-sharing-indicator {
|
||||
box-shadow: inset 0 0 0 100px transparentize($warning_color, 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.panel-status-indicators-box,
|
||||
.panel-status-menu-box {
|
||||
spacing: 2px;
|
||||
}
|
||||
|
||||
// spacing between power icon and (optional) percentage label
|
||||
.power-status.panel-status-indicators-box {
|
||||
spacing: 0;
|
||||
}
|
||||
|
||||
// indicator for active
|
||||
.screencast-indicator,
|
||||
.remote-access-indicator { color: $warning_color; }
|
||||
}
|
||||
|
||||
// App Menu
|
||||
#appMenu {
|
||||
spacing: $base_padding;
|
||||
.label-shadow { color: transparent; }
|
||||
}
|
||||
|
||||
#appMenu .panel-status-menu-box {
|
||||
padding: 0 $base_padding;
|
||||
spacing: $base_padding;
|
||||
}
|
||||
|
||||
|
||||
// Clock
|
||||
|
||||
.clock-display-box {
|
||||
spacing: 2px;
|
||||
|
||||
.clock {
|
||||
padding-left: $base_padding * 2;
|
||||
padding-right: $base_padding * 2;
|
||||
}
|
||||
}
|
16
data/shell/templates/44/switches.template
Normal file
16
data/shell/templates/44/switches.template
Normal file
|
@ -0,0 +1,16 @@
|
|||
/* Switches */
|
||||
|
||||
// these are equal to the size of the SVG assets
|
||||
$switch_height: 26px;
|
||||
$switch_width: 48px;
|
||||
|
||||
.toggle-switch {
|
||||
color: $fg_color;
|
||||
height: $switch_height;
|
||||
width: $switch_width;
|
||||
background-size: contain;
|
||||
background-image: if($variant == 'light', url("resource:///org/gnome/shell/theme/toggle-off-light.svg"), url("resource:///org/gnome/shell/theme/toggle-off.svg"));
|
||||
&:checked {
|
||||
background-image: if($variant == 'light', url("resource:///org/gnome/shell/theme/toggle-on-light.svg"), url("assets/toggle-on.svg"));
|
||||
}
|
||||
}
|
1
data/submodules
Submodule
1
data/submodules
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 49a44283b22d831abc7898c2dd52313e3b951be3
|
|
@ -5,9 +5,11 @@ blueprints = custom_target('blueprints',
|
|||
'option_row.blp',
|
||||
'window.blp',
|
||||
'log_out_dialog.blp',
|
||||
'monet_theming_group.blp',
|
||||
'app_type_dialog.blp',
|
||||
'custom_css_group.blp',
|
||||
'presets_manager_window.blp',
|
||||
'reset_preset_group.blp',
|
||||
'preferences_window.blp',
|
||||
'plugin_row.blp',
|
||||
'welcome_window.blp',
|
||||
|
@ -15,9 +17,12 @@ blueprints = custom_target('blueprints',
|
|||
'builtin_preset_row.blp',
|
||||
'explore_preset_row.blp',
|
||||
'save_dialog.blp',
|
||||
'shell_prefs_window.blp',
|
||||
'shell_theming_group.blp',
|
||||
'repo_row.blp',
|
||||
'no_plugin_window.blp',
|
||||
'share_window.blp',
|
||||
'theming_empty_group.blp',
|
||||
),
|
||||
output: '.',
|
||||
command: [find_program('blueprint-compiler'), 'batch-compile', '@OUTPUT@', '@CURRENT_SOURCE_DIR@', '@INPUT@']
|
||||
|
|
44
data/ui/monet_theming_group.blp
Normal file
44
data/ui/monet_theming_group.blp
Normal file
|
@ -0,0 +1,44 @@
|
|||
using Gtk 4.0;
|
||||
using Adw 1;
|
||||
|
||||
template GradienceMonetThemingGroup : Adw.PreferencesGroup {
|
||||
title: _("Monet Engine");
|
||||
description: _("Monet is an engine that generates a Material Design 3 palette from extracting image's colors.");
|
||||
|
||||
Adw.ExpanderRow monet-theming-expander {
|
||||
title: _("Monet Engine Options");
|
||||
subtitle: _("Choose an image, and change the parameters of a generated Monet palette");
|
||||
expanded: true;
|
||||
|
||||
[action]
|
||||
Button monet-apply-button {
|
||||
valign: center;
|
||||
label: _("Apply");
|
||||
tooltip-text: _("Apply a palette");
|
||||
clicked => on_apply_button_clicked();
|
||||
styles ["suggested-action"]
|
||||
}
|
||||
|
||||
Adw.ActionRow file-chooser-row {
|
||||
title: _("Select an Image");
|
||||
|
||||
[suffix]
|
||||
Button file-chooser-button {
|
||||
valign: center;
|
||||
clicked => on_file_chooser_button_clicked();
|
||||
|
||||
Adw.ButtonContent {
|
||||
icon-name: "folder-pictures-symbolic";
|
||||
label: _("Choose a File");
|
||||
use-underline: true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Gtk.FileChooserNative monet-file-chooser {
|
||||
title: _("Choose a Image File");
|
||||
modal: true;
|
||||
//response => on_monet_file_chooser_response();
|
||||
}
|
|
@ -30,11 +30,9 @@ template GradienceOptionRow : Adw.ActionRow {
|
|||
ColorButton color-value {
|
||||
rgba: "#00000000";
|
||||
use-alpha: true;
|
||||
color-set => on_color_value_changed();
|
||||
}
|
||||
Entry text-value {
|
||||
text: "#00000000";
|
||||
changed => on_text_value_changed();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,9 @@ template GradiencePreferencesWindow : Adw.PreferencesWindow {
|
|||
modal: true;
|
||||
|
||||
Adw.PreferencesPage general_page {
|
||||
title: _("General");
|
||||
icon-name: "applications-system-symbolic";
|
||||
|
||||
Adw.PreferencesGroup flatpak_group {
|
||||
title: _("GTK 4 Flatpak Applications");
|
||||
|
||||
|
@ -79,4 +82,32 @@ template GradiencePreferencesWindow : Adw.PreferencesWindow {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
Adw.PreferencesPage theming_page {
|
||||
title: _("Theming");
|
||||
icon-name: "larger-brush-symbolic";
|
||||
|
||||
Adw.PreferencesGroup preset_group {
|
||||
title: _("Theme Engines");
|
||||
description: _("Theme Engines are the built-in theme generators for various customizable programs/frameworks.");
|
||||
|
||||
Adw.ActionRow {
|
||||
title: _("Monet Engine");
|
||||
subtitle: _("Monet Engine generates a Material Design 3 palette from extracting image's colors.");
|
||||
activatable-widget: monet_engine_switch;
|
||||
Switch monet_engine_switch {
|
||||
valign: center;
|
||||
}
|
||||
}
|
||||
|
||||
Adw.ActionRow {
|
||||
title: _("Shell Engine");
|
||||
subtitle: _("Shell Engine generates a custom GNOME Shell theme based of a currently chosen preset.");
|
||||
activatable-widget: gnome_shell_engine_switch;
|
||||
Switch gnome_shell_engine_switch {
|
||||
valign: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
47
data/ui/reset_preset_group.blp
Normal file
47
data/ui/reset_preset_group.blp
Normal file
|
@ -0,0 +1,47 @@
|
|||
using Gtk 4.0;
|
||||
using Adw 1;
|
||||
|
||||
template GradienceResetPresetGroup : Adw.PreferencesGroup {
|
||||
title: _("Reset & Restore Presets");
|
||||
description: _("This section allows you to reset an currently applied preset or restore the previous one.");
|
||||
|
||||
Adw.ActionRow {
|
||||
title: _("Libadwaita and GTK 4 Applications");
|
||||
|
||||
Button restore_libadw_button {
|
||||
valign: center;
|
||||
icon-name: "edit-undo-symbolic";
|
||||
tooltip-text: _("Restore Previous Preset");
|
||||
clicked => on_libadw_restore_button_clicked();
|
||||
styles ["flat"]
|
||||
}
|
||||
|
||||
Button reset_libadw_button {
|
||||
valign: center;
|
||||
label: _("Reset");
|
||||
tooltip-text: _("Reset Applied Preset");
|
||||
clicked => on_libadw_reset_button_clicked();
|
||||
styles ["destructive-action"]
|
||||
}
|
||||
}
|
||||
|
||||
Adw.ActionRow {
|
||||
title: _("GTK 3 Applications");
|
||||
|
||||
Button restore_gtk3_button {
|
||||
valign: center;
|
||||
icon-name: "edit-undo-symbolic";
|
||||
tooltip-text: _("Restore Previous Preset");
|
||||
clicked => on_gtk3_restore_button_clicked();
|
||||
styles ["flat"]
|
||||
}
|
||||
|
||||
Button reset_gtk3_button {
|
||||
valign: center;
|
||||
label: _("Reset");
|
||||
tooltip-text: _("Reset Applied Preset");
|
||||
clicked => on_gtk3_reset_button_clicked();
|
||||
styles ["destructive-action"]
|
||||
}
|
||||
}
|
||||
}
|
17
data/ui/shell_prefs_window.blp
Normal file
17
data/ui/shell_prefs_window.blp
Normal file
|
@ -0,0 +1,17 @@
|
|||
using Gtk 4.0;
|
||||
using Adw 1;
|
||||
|
||||
template GradienceShellPrefsWindow : Adw.PreferencesWindow {
|
||||
title: _("Shell Engine Preferences");
|
||||
search-enabled: false;
|
||||
default-height: 620;
|
||||
default-width: 500;
|
||||
modal: true;
|
||||
|
||||
Adw.PreferencesPage {
|
||||
Adw.PreferencesGroup custom-colors-group {
|
||||
title: _("Custom Shell Colors");
|
||||
description: _("This section allows you to customize colors that will be used in Shell theme.");
|
||||
}
|
||||
}
|
||||
}
|
60
data/ui/shell_theming_group.blp
Normal file
60
data/ui/shell_theming_group.blp
Normal file
|
@ -0,0 +1,60 @@
|
|||
using Gtk 4.0;
|
||||
using Adw 1;
|
||||
|
||||
template GradienceShellThemingGroup : Adw.PreferencesGroup {
|
||||
title: _("Shell Engine");
|
||||
description: _("Shell Engine generates a custom GNOME Shell theme based on the colors of a currently selected preset.\nWARNING: Extensions that modify Shell stylesheet can cause issues with themes.");
|
||||
|
||||
Adw.ExpanderRow shell-theming-expander {
|
||||
title: _("Shell Engine Options");
|
||||
subtitle: _("Change the parameters of a generated GNOME Shell theme");
|
||||
expanded: true;
|
||||
|
||||
[action]
|
||||
Button shell-apply-button {
|
||||
valign: center;
|
||||
label: _("Apply");
|
||||
tooltip-text: _("Apply a Shell theme");
|
||||
clicked => on_apply_button_clicked();
|
||||
styles ["suggested-action"]
|
||||
}
|
||||
|
||||
Adw.ActionRow custom-colors-row {
|
||||
title: _("Customize Shell Theme");
|
||||
|
||||
[suffix]
|
||||
Button custom-colors-button {
|
||||
valign: center;
|
||||
label: _("Open Shell Preferences");
|
||||
clicked => on_custom_colors_button_clicked();
|
||||
}
|
||||
}
|
||||
|
||||
Adw.ComboRow variant-row {
|
||||
title: _("Preset Variant");
|
||||
subtitle: _("Select which preset variant you have currently applied");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Adw.ActionRow other-options-row {
|
||||
[prefix]
|
||||
Button restore_libadw_button {
|
||||
valign: center;
|
||||
icon-name: "edit-undo-symbolic";
|
||||
sensitive: false;
|
||||
//tooltip-text: _("Restore Previous Theme");
|
||||
tooltip-text: _("Currently unavailable");
|
||||
clicked => on_restore_button_clicked();
|
||||
styles ["flat"]
|
||||
}
|
||||
|
||||
[suffix]
|
||||
Button reset_theme_button {
|
||||
valign: center;
|
||||
label: _("Reset Theme");
|
||||
tooltip-text: _("Reset an applied theme");
|
||||
clicked => on_reset_theme_clicked();
|
||||
styles ["destructive-action"]
|
||||
}
|
||||
}
|
19
data/ui/theming_empty_group.blp
Normal file
19
data/ui/theming_empty_group.blp
Normal file
|
@ -0,0 +1,19 @@
|
|||
using Gtk 4.0;
|
||||
using Adw 1;
|
||||
|
||||
template GradienceEmptyThemingGroup : Adw.PreferencesGroup {
|
||||
title: _("No Theme Engines");
|
||||
description: _("Theme Engines extends the functionality of Gradience. They can be enabled in the Preferences.");
|
||||
|
||||
Adw.ActionRow open-preferences {
|
||||
title: _("Open Preferences to manage Theme Engines");
|
||||
|
||||
[suffix]
|
||||
Button open {
|
||||
valign: center;
|
||||
label: _("Open Preferences");
|
||||
tooltip-text: _("Open Preferences");
|
||||
action-name: "app.preferences";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -68,8 +68,8 @@ template GradienceWelcomeWindow: Adw.Window {
|
|||
|
||||
Adw.StatusPage page_release {
|
||||
icon-name: "software-update-available-symbolic";
|
||||
title: _("What's new in 0.3.2");
|
||||
description: _("In this release, we fixed the Firefox GNOME theme plugin, issues with presets always being saved with the same name, as well as some UX polish, and more.");
|
||||
title: _("What's new in 0.8.0");
|
||||
description: _("In this release, we added GNOME Shell theming support and reworked how Gradience work internally.");
|
||||
}
|
||||
|
||||
Adw.StatusPage page_agreement {
|
||||
|
|
|
@ -88,16 +88,16 @@ template GradienceMainWindow : Adw.ApplicationWindow {
|
|||
title: _("_Colors");
|
||||
icon-name: "larger-brush-symbolic";
|
||||
|
||||
child: Adw.PreferencesPage content { };
|
||||
child: Adw.PreferencesPage content-colors { };
|
||||
use-underline: true;
|
||||
}
|
||||
|
||||
Adw.ViewStackPage {
|
||||
name: "monet";
|
||||
title: _("_Monet");
|
||||
name: "theming";
|
||||
title: _("_Theming");
|
||||
icon-name: "color-picker-symbolic";
|
||||
|
||||
child: Adw.PreferencesPage content_monet { };
|
||||
child: Adw.PreferencesPage content-theming { };
|
||||
use-underline: true;
|
||||
}
|
||||
|
||||
|
@ -106,7 +106,7 @@ template GradienceMainWindow : Adw.ApplicationWindow {
|
|||
title: _("_Advanced");
|
||||
icon-name: "settings-symbolic";
|
||||
|
||||
child: Adw.PreferencesPage content_plugins { };
|
||||
child: Adw.PreferencesPage content-plugins { };
|
||||
use-underline: true;
|
||||
}
|
||||
}
|
||||
|
@ -123,17 +123,6 @@ template GradienceMainWindow : Adw.ApplicationWindow {
|
|||
|
||||
|
||||
menu main-menu {
|
||||
section {
|
||||
item {
|
||||
label: _("Restore Applied Color Scheme");
|
||||
action: "app.restore_color_scheme";
|
||||
}
|
||||
|
||||
item {
|
||||
label: _("Reset Applied Color Scheme");
|
||||
action: "app.reset_color_scheme";
|
||||
}
|
||||
}
|
||||
section {
|
||||
item {
|
||||
label: _("Preferences");
|
||||
|
|
|
@ -18,19 +18,8 @@
|
|||
|
||||
import re
|
||||
|
||||
from gradience.backend.globals import adw_palette_prefixes
|
||||
|
||||
# Adwaita named palette colors dict
|
||||
adw_colors = [
|
||||
"blue_",
|
||||
"green_",
|
||||
"yellow_",
|
||||
"orange_",
|
||||
"red_",
|
||||
"purple_",
|
||||
"brown_",
|
||||
"light_",
|
||||
"dark_",
|
||||
]
|
||||
|
||||
# Regular expressions
|
||||
define_color = re.compile(r"(@define-color .*[^\s])")
|
||||
|
@ -41,7 +30,7 @@ def parse_css(path):
|
|||
variables = {}
|
||||
palette = {}
|
||||
|
||||
for color in adw_colors:
|
||||
for color in adw_palette_prefixes:
|
||||
palette[color] = {}
|
||||
|
||||
with open(path, "r", encoding="utf-8") as sheet:
|
||||
|
@ -51,7 +40,7 @@ def parse_css(path):
|
|||
if cdefine_match != None: # If @define-color variable declarations were found
|
||||
palette_part = cdefine_match.__getitem__(1) # Get the second item of the re.Match object
|
||||
name, color = palette_part.split(" ", 1)[1].split(" ", 1)
|
||||
if name.startswith(tuple(adw_colors)): # Palette colors
|
||||
if name.startswith(tuple(adw_palette_prefixes)): # Palette colors
|
||||
palette[name[:-1]][name[-1:]] = color[:-1]
|
||||
else: # Other color variables
|
||||
variables[name] = color[:-1]
|
||||
|
|
28
gradience/backend/exceptions.py
Normal file
28
gradience/backend/exceptions.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
# css_parser.py
|
||||
#
|
||||
# Change the look of Adwaita, with ease
|
||||
# Copyright (C) 2023, Gradience Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
class GradienceError(Exception):
|
||||
""" Base class for all other exceptions in Gradience. """
|
||||
pass
|
||||
|
||||
|
||||
# TODO: Move this module somewhere else later
|
||||
class UnsupportedShellVersion(GradienceError):
|
||||
""" Exception raised when the shell version is not supported. """
|
||||
pass
|
|
@ -23,40 +23,67 @@ from gi.repository import Xdp
|
|||
from gradience.backend import constants
|
||||
|
||||
|
||||
presets_dir = os.path.join(
|
||||
os.environ.get("XDG_CONFIG_HOME", os.environ["HOME"] + "/.config"),
|
||||
"presets"
|
||||
user_config_dir = os.environ.get(
|
||||
"XDG_CONFIG_HOME", os.environ["HOME"] + "/.config"
|
||||
)
|
||||
|
||||
user_data_dir = os.environ.get(
|
||||
"XDG_DATA_HOME", os.environ["HOME"] + "/.local/share"
|
||||
)
|
||||
|
||||
user_cache_dir = os.environ.get(
|
||||
"XDG_CACHE_HOME", os.environ["HOME"] + "/.cache"
|
||||
)
|
||||
|
||||
presets_dir = os.path.join(user_config_dir, "presets")
|
||||
|
||||
user_plugin_dir = os.path.join(user_data_dir, "gradience", "plugins")
|
||||
system_plugin_dir = os.path.join(constants.pkgdatadir, "plugins")
|
||||
|
||||
preset_repos = {
|
||||
"Official": "https://github.com/GradienceTeam/Community/raw/next/official.json",
|
||||
"Curated": "https://github.com/GradienceTeam/Community/raw/next/curated.json"
|
||||
}
|
||||
|
||||
user_plugin_dir = os.path.join(
|
||||
os.environ.get("XDG_DATA_HOME", os.environ["HOME"] + "/.local/share"),
|
||||
"gradience",
|
||||
"plugins"
|
||||
)
|
||||
# Adwaita named UI colors prefixes list
|
||||
# NOTE: Remember to update this list if new libadwaita version brings up new variables
|
||||
adw_variables_prefixes = [
|
||||
"accent_",
|
||||
"destructive_",
|
||||
"success_",
|
||||
"warning_",
|
||||
"error_",
|
||||
"window_",
|
||||
"view_",
|
||||
"headerbar_",
|
||||
"card_",
|
||||
"dialog_",
|
||||
"popover_",
|
||||
"shade_",
|
||||
"scrollbar_",
|
||||
"borders"
|
||||
]
|
||||
|
||||
system_plugin_dir = os.path.join(
|
||||
constants.pkgdatadir,
|
||||
"plugins"
|
||||
)
|
||||
# Adwaita named palette colors prefixes list
|
||||
# NOTE: Remember to update this list if new libadwaita version brings up new variables
|
||||
adw_palette_prefixes = [
|
||||
"blue_",
|
||||
"green_",
|
||||
"yellow_",
|
||||
"orange_",
|
||||
"red_",
|
||||
"purple_",
|
||||
"brown_",
|
||||
"light_",
|
||||
"dark_"
|
||||
]
|
||||
|
||||
def get_gtk_theme_dir(app_type):
|
||||
def get_gtk_theme_dir(app_type: str):
|
||||
if app_type == "gtk4":
|
||||
theme_dir = os.path.join(
|
||||
os.environ.get("XDG_CONFIG_HOME",
|
||||
os.environ["HOME"] + "/.config"),
|
||||
"gtk-4.0"
|
||||
)
|
||||
elif app_type == "gtk3":
|
||||
theme_dir = os.path.join(
|
||||
os.environ.get("XDG_CONFIG_HOME",
|
||||
os.environ["HOME"] + "/.config"),
|
||||
"gtk-3.0"
|
||||
)
|
||||
theme_dir = os.path.join(user_config_dir, "gtk-4.0")
|
||||
|
||||
if app_type == "gtk3":
|
||||
theme_dir = os.path.join(user_config_dir, "gtk-3.0")
|
||||
|
||||
return theme_dir
|
||||
|
||||
|
@ -66,6 +93,3 @@ def is_sandboxed():
|
|||
is_sandboxed = portal.running_under_sandbox()
|
||||
|
||||
return is_sandboxed
|
||||
|
||||
def get_available_sassc():
|
||||
pass
|
||||
|
|
|
@ -30,6 +30,7 @@ gradience_sources = [
|
|||
'flatpak_overrides.py',
|
||||
'globals.py',
|
||||
'logger.py',
|
||||
'preset_downloader.py'
|
||||
'preset_downloader.py',
|
||||
'exceptions.py'
|
||||
]
|
||||
PY_INSTALLDIR.install_sources(gradience_sources, subdir: backenddir)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# preset.py
|
||||
#
|
||||
# Change the look of Adwaita, with ease
|
||||
# Copyright (C) 2022 Gradience Team
|
||||
# Copyright (C) 2022-2023, Gradience Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -27,7 +27,7 @@ from gradience.backend.logger import Logger
|
|||
logging = Logger()
|
||||
|
||||
|
||||
# Adwaita default colors palette
|
||||
# Adwaita default colors palette dict
|
||||
adw_palette = {
|
||||
"blue_": {
|
||||
"1": "#99c1f1",
|
||||
|
@ -94,23 +94,25 @@ adw_palette = {
|
|||
}
|
||||
}
|
||||
|
||||
# Supported app types that can utilize custom CSS
|
||||
custom_css_app_types = [
|
||||
# Supported GTK versions that can utilize custom CSS
|
||||
custom_css_gtk_versions = [
|
||||
"gtk4",
|
||||
"gtk3"
|
||||
]
|
||||
|
||||
|
||||
class Preset:
|
||||
display_name = "New Preset"
|
||||
preset_path = "new_preset"
|
||||
|
||||
variables = {}
|
||||
palette = adw_palette
|
||||
custom_css = {
|
||||
"gtk4": "",
|
||||
"gtk3": "",
|
||||
"gtk3": ""
|
||||
}
|
||||
|
||||
plugins = {}
|
||||
display_name = "New Preset"
|
||||
preset_path = "new_preset"
|
||||
plugins_list = {}
|
||||
badges = {}
|
||||
|
||||
|
@ -122,10 +124,13 @@ class Preset:
|
|||
|
||||
if display_name:
|
||||
self.display_name = display_name
|
||||
|
||||
if palette:
|
||||
self.palette = palette
|
||||
|
||||
if custom_css:
|
||||
self.custom_css = custom_css
|
||||
|
||||
if badges:
|
||||
self.badges = badges
|
||||
|
||||
|
@ -182,7 +187,7 @@ class Preset:
|
|||
if "custom_css" in preset:
|
||||
self.custom_css = preset["custom_css"]
|
||||
else:
|
||||
for app_type in custom_css_app_types:
|
||||
for app_type in custom_css_gtk_versions:
|
||||
self.custom_css[app_type] = ""
|
||||
except Exception as e:
|
||||
logging.error("Failed to create a new preset object.", exc=e)
|
||||
|
@ -193,8 +198,8 @@ class Preset:
|
|||
self.display_name = name
|
||||
old_path = self.preset_path
|
||||
self.preset_path = os.path.join(
|
||||
os.path.dirname(self.preset_path),
|
||||
to_slug_case(name) + ".json")
|
||||
os.path.dirname(self.preset_path), to_slug_case(name) + ".json"
|
||||
)
|
||||
|
||||
self.save_to_file(to=self.preset_path)
|
||||
os.remove(old_path)
|
||||
|
@ -207,6 +212,7 @@ class Preset:
|
|||
"custom_css": self.custom_css,
|
||||
"plugins": self.plugins_list
|
||||
}
|
||||
|
||||
json_output = json.dumps(preset_dict, indent=indent)
|
||||
|
||||
return json_output
|
||||
|
@ -218,23 +224,14 @@ class Preset:
|
|||
if to is None:
|
||||
filename = to_slug_case(name) if name else to_slug_case(self.display_name)
|
||||
self.preset_path = os.path.join(
|
||||
presets_dir, "user", filename + ".json")
|
||||
presets_dir, "user", filename + ".json"
|
||||
)
|
||||
else:
|
||||
self.preset_path = to
|
||||
|
||||
if not os.path.exists(
|
||||
os.path.join(
|
||||
presets_dir,
|
||||
"user",
|
||||
)
|
||||
):
|
||||
if not os.path.exists(os.path.join(presets_dir, "user")):
|
||||
try:
|
||||
os.makedirs(
|
||||
os.path.join(
|
||||
presets_dir,
|
||||
"user",
|
||||
)
|
||||
)
|
||||
os.makedirs(os.path.join(presets_dir, "user"))
|
||||
except OSError as e:
|
||||
logging.error("Failed to create a new preset directory.", exc=e)
|
||||
raise
|
||||
|
|
|
@ -3,6 +3,7 @@ themingdir = 'gradience/backend/theming'
|
|||
gradience_sources = [
|
||||
'__init__.py',
|
||||
'monet.py',
|
||||
'preset_utils.py'
|
||||
'preset.py',
|
||||
'shell.py'
|
||||
]
|
||||
PY_INSTALLDIR.install_sources(gradience_sources, subdir: themingdir)
|
||||
|
|
|
@ -18,9 +18,13 @@
|
|||
|
||||
import os
|
||||
|
||||
import material_color_utilities_python as monet
|
||||
|
||||
from svglib.svglib import svg2rlg
|
||||
from reportlab.graphics import renderPM
|
||||
import material_color_utilities_python as monet
|
||||
|
||||
from gradience.backend.models.preset import Preset
|
||||
from gradience.backend.utils.colors import argb_to_color_code
|
||||
|
||||
from gradience.backend.logger import Logger
|
||||
|
||||
|
@ -31,7 +35,7 @@ class Monet:
|
|||
def __init__(self):
|
||||
self.palette = None
|
||||
|
||||
def generate_from_image(self, image_path: str) -> dict:
|
||||
def generate_palette_from_image(self, image_path: str) -> dict:
|
||||
if image_path.endswith(".svg"):
|
||||
drawing = svg2rlg(image_path)
|
||||
image_path = os.path.join(
|
||||
|
@ -52,6 +56,7 @@ class Monet:
|
|||
basewidth = 64
|
||||
wpercent = basewidth / float(monet_img.size[0])
|
||||
hsize = int((float(monet_img.size[1]) * float(wpercent)))
|
||||
|
||||
monet_img = monet_img.resize(
|
||||
(basewidth, hsize), monet.Image.Resampling.LANCZOS
|
||||
)
|
||||
|
@ -59,3 +64,114 @@ class Monet:
|
|||
self.palette = monet.themeFromImage(monet_img)
|
||||
|
||||
return self.palette
|
||||
|
||||
def new_preset_from_monet(self, name=None, monet_palette=None, props=None, obj_only=False) -> Preset or None:
|
||||
preset = Preset()
|
||||
|
||||
if props:
|
||||
tone = props[0]
|
||||
theme = props[1]
|
||||
else:
|
||||
raise AttributeError("Properties 'tone' and/or 'theme' missing")
|
||||
|
||||
if not monet_palette:
|
||||
raise AttributeError("Property 'monet_palette' missing")
|
||||
|
||||
if theme == "light":
|
||||
light_theme = monet_palette["schemes"]["light"]
|
||||
variable = {
|
||||
"accent_color": argb_to_color_code(light_theme.primary),
|
||||
"accent_bg_color": argb_to_color_code(light_theme.primary),
|
||||
"accent_fg_color": argb_to_color_code(light_theme.onPrimary),
|
||||
"destructive_color": argb_to_color_code(light_theme.error),
|
||||
"destructive_bg_color": argb_to_color_code(light_theme.errorContainer),
|
||||
# Avoid using .onError as it causes contrast issues
|
||||
"destructive_fg_color": argb_to_color_code(light_theme.onErrorContainer),
|
||||
"success_color": argb_to_color_code(light_theme.tertiary),
|
||||
"success_bg_color": argb_to_color_code(light_theme.tertiaryContainer),
|
||||
"success_fg_color": argb_to_color_code(light_theme.onTertiaryContainer),
|
||||
"warning_color": argb_to_color_code(light_theme.secondary),
|
||||
"warning_bg_color": argb_to_color_code(light_theme.secondaryContainer),
|
||||
"warning_fg_color": argb_to_color_code(light_theme.onSecondaryContainer),
|
||||
"error_color": argb_to_color_code(light_theme.error),
|
||||
"error_bg_color": argb_to_color_code(light_theme.errorContainer),
|
||||
# Avoid using .onError as it causes contrast issues
|
||||
"error_fg_color": argb_to_color_code(light_theme.onErrorContainer),
|
||||
"window_bg_color": argb_to_color_code(light_theme.surface),
|
||||
"window_fg_color": argb_to_color_code(light_theme.onSurface),
|
||||
"view_bg_color": argb_to_color_code(light_theme.secondaryContainer),
|
||||
"view_fg_color": argb_to_color_code(light_theme.onSurface),
|
||||
"headerbar_bg_color": argb_to_color_code(light_theme.primary, "0.08"),
|
||||
"headerbar_fg_color": argb_to_color_code(light_theme.onSecondaryContainer),
|
||||
"headerbar_border_color": argb_to_color_code(light_theme.onSurface, "0.8"),
|
||||
"headerbar_backdrop_color": "@window_bg_color",
|
||||
"headerbar_shade_color": argb_to_color_code(light_theme.onSurface, "0.07"),
|
||||
"card_bg_color": argb_to_color_code(light_theme.primary, "0.05"),
|
||||
"card_fg_color": argb_to_color_code(light_theme.onSecondaryContainer),
|
||||
"card_shade_color": argb_to_color_code(light_theme.shadow, "0.07"),
|
||||
"dialog_bg_color": argb_to_color_code(light_theme.secondaryContainer),
|
||||
"dialog_fg_color": argb_to_color_code(light_theme.onSecondaryContainer),
|
||||
"popover_bg_color": argb_to_color_code(light_theme.secondaryContainer),
|
||||
"popover_fg_color": argb_to_color_code(light_theme.onSecondaryContainer),
|
||||
"shade_color": argb_to_color_code(light_theme.shadow, "0.07"),
|
||||
"scrollbar_outline_color": argb_to_color_code(light_theme.outline),
|
||||
}
|
||||
elif theme == "dark":
|
||||
dark_theme = monet_palette["schemes"]["dark"]
|
||||
variable = {
|
||||
"accent_color": argb_to_color_code(dark_theme.primary),
|
||||
"accent_bg_color": argb_to_color_code(dark_theme.primary),
|
||||
"accent_fg_color": argb_to_color_code(dark_theme.onPrimary),
|
||||
"destructive_color": argb_to_color_code(dark_theme.error),
|
||||
"destructive_bg_color": argb_to_color_code(dark_theme.errorContainer),
|
||||
# Avoid using .onError as it causes contrast issues
|
||||
"destructive_fg_color": argb_to_color_code(dark_theme.onErrorContainer),
|
||||
"success_color": argb_to_color_code(dark_theme.tertiary),
|
||||
"success_bg_color": argb_to_color_code(dark_theme.tertiaryContainer),
|
||||
"success_fg_color": argb_to_color_code(dark_theme.onTertiaryContainer),
|
||||
"warning_color": argb_to_color_code(dark_theme.secondary),
|
||||
"warning_bg_color": argb_to_color_code(dark_theme.secondaryContainer),
|
||||
"warning_fg_color": argb_to_color_code(dark_theme.onSecondaryContainer),
|
||||
"error_color": argb_to_color_code(dark_theme.error),
|
||||
"error_bg_color": argb_to_color_code(dark_theme.errorContainer),
|
||||
# Avoid using .onError as it causes contrast issues
|
||||
"error_fg_color": argb_to_color_code(dark_theme.onErrorContainer),
|
||||
"window_bg_color": argb_to_color_code(dark_theme.surface),
|
||||
"window_fg_color": argb_to_color_code(dark_theme.onSurface),
|
||||
"view_bg_color": argb_to_color_code(dark_theme.secondaryContainer),
|
||||
"view_fg_color": argb_to_color_code(dark_theme.onSurface),
|
||||
"headerbar_bg_color": argb_to_color_code(dark_theme.primary, "0.08"),
|
||||
"headerbar_fg_color": argb_to_color_code(dark_theme.onSecondaryContainer),
|
||||
"headerbar_border_color": argb_to_color_code(dark_theme.onSurface, "0.8"),
|
||||
"headerbar_backdrop_color": "@window_bg_color",
|
||||
"headerbar_shade_color": argb_to_color_code(dark_theme.onSurface, "0.07"),
|
||||
"card_bg_color": argb_to_color_code(dark_theme.primary, "0.05"),
|
||||
"card_fg_color": argb_to_color_code(dark_theme.onSecondaryContainer),
|
||||
"card_shade_color": argb_to_color_code(dark_theme.shadow, "0.07"),
|
||||
"dialog_bg_color": argb_to_color_code(dark_theme.secondaryContainer),
|
||||
"dialog_fg_color": argb_to_color_code(dark_theme.onSecondaryContainer),
|
||||
"popover_bg_color": argb_to_color_code(dark_theme.secondaryContainer),
|
||||
"popover_fg_color": argb_to_color_code(dark_theme.onSecondaryContainer),
|
||||
"shade_color": argb_to_color_code(dark_theme.shadow, "0.36"),
|
||||
"scrollbar_outline_color": argb_to_color_code(dark_theme.outline, "0.5"),
|
||||
}
|
||||
else:
|
||||
raise AttributeError("Unknown theme variant selected")
|
||||
|
||||
if obj_only == False and not name:
|
||||
raise AttributeError("You either need to set 'obj_only' property to True, or add value to 'name' property")
|
||||
|
||||
if obj_only:
|
||||
if name:
|
||||
preset.new(variables=variable, display_name=name)
|
||||
else:
|
||||
preset.new(variables=variable)
|
||||
return preset
|
||||
|
||||
if obj_only == False:
|
||||
preset.new(variables=variable, display_name=name)
|
||||
|
||||
try:
|
||||
preset.save_to_file()
|
||||
except OSError:
|
||||
raise
|
||||
|
|
158
gradience/backend/theming/preset.py
Normal file
158
gradience/backend/theming/preset.py
Normal file
|
@ -0,0 +1,158 @@
|
|||
# preset.py
|
||||
#
|
||||
# Change the look of Adwaita, with ease
|
||||
# Copyright (C) 2022-2023, Gradience Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import json
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from gi.repository import GLib, Gio
|
||||
|
||||
from gradience.backend.models.preset import Preset
|
||||
|
||||
from gradience.backend.utils.theming import generate_gtk_css
|
||||
from gradience.backend.globals import user_config_dir, presets_dir, get_gtk_theme_dir
|
||||
|
||||
from gradience.backend.logger import Logger
|
||||
|
||||
logging = Logger()
|
||||
|
||||
|
||||
class PresetUtils:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def get_presets_list(self, repo=None, full_list=False) -> dict:
|
||||
presets_list = {}
|
||||
|
||||
def __get_repo_presets(repo):
|
||||
if repo.is_dir():
|
||||
for file_name in repo.iterdir():
|
||||
file_name = str(file_name)
|
||||
if file_name.endswith(".json"):
|
||||
try:
|
||||
with open(os.path.join(presets_dir, file_name), "r", encoding="utf-8") as file:
|
||||
preset_text = file.read()
|
||||
file.close()
|
||||
except (OSError, KeyError) as e:
|
||||
logging.error("Failed to load preset information.", exc=e)
|
||||
raise
|
||||
else:
|
||||
preset = json.loads(preset_text)
|
||||
|
||||
if preset.get("variables") is None:
|
||||
raise KeyError("'variables' section missing in loaded preset file")
|
||||
|
||||
if preset.get("palette") is None:
|
||||
raise KeyError("'palette' section missing in loaded preset file")
|
||||
|
||||
presets_list[file_name] = preset["name"]
|
||||
elif repo.is_file():
|
||||
# this exists to keep compatibility with old preset structure
|
||||
if repo.name.endswith(".json"):
|
||||
logging.warning("Legacy preset structure found. Moving to a new structure.")
|
||||
|
||||
try:
|
||||
if not os.path.isdir(os.path.join(presets_dir, "user")):
|
||||
os.mkdir(os.path.join(presets_dir, "user"))
|
||||
|
||||
os.rename(repo, os.path.join(
|
||||
presets_dir, "user", repo.name))
|
||||
|
||||
with open(os.path.join(presets_dir, "user", repo), "r", encoding="utf-8") as file:
|
||||
preset_text = file.read()
|
||||
file.close()
|
||||
except (OSError, KeyError) as e:
|
||||
logging.error("Failed to load preset information.", exc=e)
|
||||
raise
|
||||
else:
|
||||
preset = json.loads(preset_text)
|
||||
|
||||
if preset.get("variables") is None:
|
||||
raise KeyError("'variables' section missing in loaded preset file")
|
||||
|
||||
if preset.get("palette") is None:
|
||||
raise KeyError("'palette' section missing in loaded preset file")
|
||||
|
||||
presets_list["user"][file_name] = preset["name"]
|
||||
|
||||
if full_list:
|
||||
for repo in Path(presets_dir).iterdir():
|
||||
logging.debug(f"presets_dir.iterdir: {repo}")
|
||||
__get_repo_presets(repo)
|
||||
|
||||
return presets_list
|
||||
elif repo:
|
||||
__get_repo_presets(repo)
|
||||
|
||||
return presets_list
|
||||
else:
|
||||
raise AttributeError("You either need to set 'repo' property, or change 'full_list' property to True")
|
||||
|
||||
def apply_preset(self, app_type: str, preset: Preset) -> None:
|
||||
theme_dir = get_gtk_theme_dir(app_type)
|
||||
gtk_css_path = os.path.join(theme_dir, "gtk.css")
|
||||
|
||||
if not os.path.exists(theme_dir):
|
||||
os.makedirs(theme_dir)
|
||||
|
||||
try:
|
||||
with open(gtk_css_path, "r", encoding="utf-8") as css_file:
|
||||
contents = css_file.read()
|
||||
css_file.close()
|
||||
except FileNotFoundError:
|
||||
logging.warning(f"gtk.css file not found in {gtk_css_path}. Generating new stylesheet.")
|
||||
else:
|
||||
with open(gtk_css_path + ".bak", "w", encoding="utf-8") as backup:
|
||||
backup.write(contents)
|
||||
backup.close()
|
||||
finally:
|
||||
with open(gtk_css_path, "w", encoding="utf-8") as css_file:
|
||||
css_file.write(generate_gtk_css(app_type, preset))
|
||||
css_file.close()
|
||||
|
||||
def restore_preset(self, app_type: str) -> None:
|
||||
theme_dir = get_gtk_theme_dir(app_type)
|
||||
gtk_css_path = os.path.join(theme_dir, "gtk.css")
|
||||
|
||||
try:
|
||||
with open(gtk_css_path + ".bak", "r", encoding="utf-8") as backup:
|
||||
contents = backup.read()
|
||||
backup.close()
|
||||
|
||||
with open(gtk_css_path, "w", encoding="utf-8") as css_file:
|
||||
css_file.write(contents)
|
||||
css_file.close()
|
||||
except OSError as e:
|
||||
logging.error(f"Unable to restore {app_type.capitalize()} preset backup.", exc=e)
|
||||
raise
|
||||
|
||||
def reset_preset(self, app_type: str) -> None:
|
||||
theme_dir = get_gtk_theme_dir(app_type)
|
||||
gtk_css_path = os.path.join(theme_dir, "gtk.css")
|
||||
|
||||
file = Gio.File.new_for_path(gtk_css_path)
|
||||
|
||||
try:
|
||||
file.delete()
|
||||
except GLib.GError as e:
|
||||
if e.code == 1:
|
||||
return
|
||||
|
||||
logging.error("Unable to delete current preset.", exc=e)
|
||||
raise
|
|
@ -1,356 +0,0 @@
|
|||
# preset_utils.py
|
||||
#
|
||||
# Change the look of Adwaita, with ease
|
||||
# Copyright (C) 2022 Gradience Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import json
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from gi.repository import GLib, Gio
|
||||
|
||||
from gradience.backend.models.preset import Preset
|
||||
from gradience.backend.utils.colors import argb_to_color_code
|
||||
|
||||
from gradience.backend.globals import presets_dir, get_gtk_theme_dir
|
||||
|
||||
from gradience.backend.logger import Logger
|
||||
|
||||
logging = Logger()
|
||||
|
||||
|
||||
class PresetUtils:
|
||||
def __init__(self):
|
||||
self.preset = Preset()
|
||||
|
||||
def generate_gtk_css(self, app_type: str, preset: Preset) -> str:
|
||||
variables = preset.variables
|
||||
palette = preset.palette
|
||||
custom_css = preset.custom_css
|
||||
|
||||
final_css = ""
|
||||
|
||||
for key in variables.keys():
|
||||
final_css += f"@define-color {key} {variables[key]};\n"
|
||||
|
||||
for prefix_key in palette.keys():
|
||||
for key in palette[prefix_key].keys():
|
||||
final_css += f"@define-color {prefix_key + key} {palette[prefix_key][key]};\n"
|
||||
|
||||
final_css += custom_css.get(app_type, "")
|
||||
|
||||
return final_css
|
||||
|
||||
def new_preset_from_monet(self, name=None, monet_palette=None, props=None, obj_only=False) -> Preset or None:
|
||||
if props:
|
||||
tone = props[0]
|
||||
theme = props[1]
|
||||
else:
|
||||
raise AttributeError("Properties 'tone' and/or 'theme' missing")
|
||||
|
||||
if not monet_palette:
|
||||
raise AttributeError("Property 'monet_palette' missing")
|
||||
|
||||
if theme == "light":
|
||||
light_theme = monet_palette["schemes"]["light"]
|
||||
variable = {
|
||||
"accent_color": argb_to_color_code(light_theme.primary),
|
||||
"accent_bg_color": argb_to_color_code(light_theme.primary),
|
||||
"accent_fg_color": argb_to_color_code(light_theme.onPrimary),
|
||||
"destructive_color": argb_to_color_code(light_theme.error),
|
||||
"destructive_bg_color": argb_to_color_code(light_theme.errorContainer),
|
||||
# Avoid using .onError as it causes contrast issues
|
||||
"destructive_fg_color": argb_to_color_code(light_theme.onErrorContainer),
|
||||
"success_color": argb_to_color_code(light_theme.tertiary),
|
||||
"success_bg_color": argb_to_color_code(light_theme.tertiaryContainer),
|
||||
"success_fg_color": argb_to_color_code(light_theme.onTertiaryContainer),
|
||||
"warning_color": argb_to_color_code(light_theme.secondary),
|
||||
"warning_bg_color": argb_to_color_code(light_theme.secondaryContainer),
|
||||
"warning_fg_color": argb_to_color_code(light_theme.onSecondaryContainer),
|
||||
"error_color": argb_to_color_code(light_theme.error),
|
||||
"error_bg_color": argb_to_color_code(light_theme.errorContainer),
|
||||
# Avoid using .onError as it causes contrast issues
|
||||
"error_fg_color": argb_to_color_code(light_theme.onErrorContainer),
|
||||
"window_bg_color": argb_to_color_code(light_theme.surface),
|
||||
"window_fg_color": argb_to_color_code(light_theme.onSurface),
|
||||
"view_bg_color": argb_to_color_code(light_theme.secondaryContainer),
|
||||
"view_fg_color": argb_to_color_code(light_theme.onSurface),
|
||||
"headerbar_bg_color": argb_to_color_code(light_theme.primary, "0.08"),
|
||||
"headerbar_fg_color": argb_to_color_code(light_theme.onSecondaryContainer),
|
||||
"headerbar_border_color": argb_to_color_code(light_theme.onSurface, "0.8"),
|
||||
"headerbar_backdrop_color": "@window_bg_color",
|
||||
"headerbar_shade_color": argb_to_color_code(light_theme.onSurface, "0.07"),
|
||||
"card_bg_color": argb_to_color_code(light_theme.primary, "0.05"),
|
||||
"card_fg_color": argb_to_color_code(light_theme.onSecondaryContainer),
|
||||
"card_shade_color": argb_to_color_code(light_theme.shadow, "0.07"),
|
||||
"dialog_bg_color": argb_to_color_code(light_theme.secondaryContainer),
|
||||
"dialog_fg_color": argb_to_color_code(light_theme.onSecondaryContainer),
|
||||
"popover_bg_color": argb_to_color_code(light_theme.secondaryContainer),
|
||||
"popover_fg_color": argb_to_color_code(light_theme.onSecondaryContainer),
|
||||
"shade_color": argb_to_color_code(light_theme.shadow, "0.07"),
|
||||
"scrollbar_outline_color": argb_to_color_code(light_theme.outline),
|
||||
}
|
||||
elif theme == "dark":
|
||||
dark_theme = monet_palette["schemes"]["dark"]
|
||||
variable = {
|
||||
"accent_color": argb_to_color_code(dark_theme.primary),
|
||||
"accent_bg_color": argb_to_color_code(dark_theme.primary),
|
||||
"accent_fg_color": argb_to_color_code(dark_theme.onPrimary),
|
||||
"destructive_color": argb_to_color_code(dark_theme.error),
|
||||
"destructive_bg_color": argb_to_color_code(dark_theme.errorContainer),
|
||||
# Avoid using .onError as it causes contrast issues
|
||||
"destructive_fg_color": argb_to_color_code(dark_theme.onErrorContainer),
|
||||
"success_color": argb_to_color_code(dark_theme.tertiary),
|
||||
"success_bg_color": argb_to_color_code(dark_theme.tertiaryContainer),
|
||||
"success_fg_color": argb_to_color_code(dark_theme.onTertiaryContainer),
|
||||
"warning_color": argb_to_color_code(dark_theme.secondary),
|
||||
"warning_bg_color": argb_to_color_code(dark_theme.secondaryContainer),
|
||||
"warning_fg_color": argb_to_color_code(dark_theme.onSecondaryContainer),
|
||||
"error_color": argb_to_color_code(dark_theme.error),
|
||||
"error_bg_color": argb_to_color_code(dark_theme.errorContainer),
|
||||
# Avoid using .onError as it causes contrast issues
|
||||
"error_fg_color": argb_to_color_code(dark_theme.onErrorContainer),
|
||||
"window_bg_color": argb_to_color_code(dark_theme.surface),
|
||||
"window_fg_color": argb_to_color_code(dark_theme.onSurface),
|
||||
"view_bg_color": argb_to_color_code(dark_theme.secondaryContainer),
|
||||
"view_fg_color": argb_to_color_code(dark_theme.onSurface),
|
||||
"headerbar_bg_color": argb_to_color_code(dark_theme.primary, "0.08"),
|
||||
"headerbar_fg_color": argb_to_color_code(dark_theme.onSecondaryContainer),
|
||||
"headerbar_border_color": argb_to_color_code(dark_theme.onSurface, "0.8"),
|
||||
"headerbar_backdrop_color": "@window_bg_color",
|
||||
"headerbar_shade_color": argb_to_color_code(dark_theme.onSurface, "0.07"),
|
||||
"card_bg_color": argb_to_color_code(dark_theme.primary, "0.05"),
|
||||
"card_fg_color": argb_to_color_code(dark_theme.onSecondaryContainer),
|
||||
"card_shade_color": argb_to_color_code(dark_theme.shadow, "0.07"),
|
||||
"dialog_bg_color": argb_to_color_code(dark_theme.secondaryContainer),
|
||||
"dialog_fg_color": argb_to_color_code(dark_theme.onSecondaryContainer),
|
||||
"popover_bg_color": argb_to_color_code(dark_theme.secondaryContainer),
|
||||
"popover_fg_color": argb_to_color_code(dark_theme.onSecondaryContainer),
|
||||
"shade_color": argb_to_color_code(dark_theme.shadow, "0.36"),
|
||||
"scrollbar_outline_color": argb_to_color_code(dark_theme.outline, "0.5"),
|
||||
}
|
||||
|
||||
if obj_only == False and not name:
|
||||
raise AttributeError("You either need to set 'obj_only' property to True, or add value to 'name' property")
|
||||
|
||||
if obj_only:
|
||||
if name:
|
||||
logging.debug("with name, obj_only")
|
||||
self.preset.new(variables=variable, display_name=name)
|
||||
else:
|
||||
logging.debug("no name, obj_only")
|
||||
self.preset.new(variables=variable)
|
||||
return self.preset
|
||||
|
||||
if obj_only == False:
|
||||
logging.debug("no obj_only, name")
|
||||
self.preset.new(variables=variable, display_name=name)
|
||||
|
||||
try:
|
||||
self.preset.save_to_file()
|
||||
except OSError:
|
||||
raise
|
||||
|
||||
def get_presets_list(self, repo=None, full_list=False) -> dict:
|
||||
presets_list = {}
|
||||
|
||||
def get_repo_presets(repo):
|
||||
if repo.is_dir():
|
||||
for file_name in repo.iterdir():
|
||||
file_name = str(file_name)
|
||||
if file_name.endswith(".json"):
|
||||
try:
|
||||
with open(
|
||||
os.path.join(presets_dir, file_name),
|
||||
"r",
|
||||
encoding="utf-8",
|
||||
) as file:
|
||||
preset_text = file.read()
|
||||
file.close()
|
||||
except (OSError, KeyError) as e:
|
||||
logging.error("Failed to load preset information.", exc=e)
|
||||
raise
|
||||
else:
|
||||
preset = json.loads(preset_text)
|
||||
if preset.get("variables") is None:
|
||||
raise KeyError("'variables' section missing in loaded preset file")
|
||||
if preset.get("palette") is None:
|
||||
raise KeyError("'palette' section missing in loaded preset file")
|
||||
presets_list[file_name] = preset[
|
||||
"name"
|
||||
]
|
||||
elif repo.is_file():
|
||||
# this exists to keep compatibility with old presets
|
||||
if repo.name.endswith(".json"):
|
||||
logging.warning("Legacy preset found. Moving to new structure.")
|
||||
|
||||
try:
|
||||
if not os.path.isdir(os.path.join(presets_dir, "user")):
|
||||
os.mkdir(os.path.join(presets_dir, "user"))
|
||||
|
||||
os.rename(repo, os.path.join(
|
||||
presets_dir, "user", repo.name))
|
||||
|
||||
with open(
|
||||
os.path.join(presets_dir, "user", repo),
|
||||
"r",
|
||||
encoding="utf-8",
|
||||
) as file:
|
||||
preset_text = file.read()
|
||||
file.close()
|
||||
except (OSError, KeyError) as e:
|
||||
logging.error("Failed to load preset information.", exc=e)
|
||||
raise
|
||||
else:
|
||||
preset = json.loads(preset_text)
|
||||
if preset.get("variables") is None:
|
||||
raise KeyError("'variables' section missing in loaded preset file")
|
||||
if preset.get("palette") is None:
|
||||
raise KeyError("'palette' section missing in loaded preset file")
|
||||
presets_list["user"][file_name] = preset[
|
||||
"name"
|
||||
]
|
||||
|
||||
if full_list:
|
||||
for repo in Path(presets_dir).iterdir():
|
||||
logging.debug(f"presets_dir.iterdir: {repo}")
|
||||
get_repo_presets(repo)
|
||||
return presets_list
|
||||
elif repo:
|
||||
get_repo_presets(repo)
|
||||
return presets_list
|
||||
else:
|
||||
raise AttributeError("You either need to set 'repo' property, or change 'full_list' property to True")
|
||||
|
||||
def apply_preset(self, app_type: str, preset: Preset) -> None:
|
||||
if app_type == "gtk4":
|
||||
theme_dir = get_gtk_theme_dir(app_type)
|
||||
|
||||
if not os.path.exists(theme_dir):
|
||||
os.makedirs(theme_dir)
|
||||
|
||||
gtk4_css = self.generate_gtk_css("gtk4", preset)
|
||||
contents = ""
|
||||
|
||||
try:
|
||||
with open(
|
||||
os.path.join(theme_dir, "gtk.css"), "r", encoding="utf-8"
|
||||
) as file:
|
||||
contents = file.read()
|
||||
except FileNotFoundError: # first run
|
||||
pass
|
||||
else:
|
||||
with open(
|
||||
os.path.join(theme_dir, "gtk.css.bak"), "w", encoding="utf-8"
|
||||
) as file:
|
||||
file.write(contents)
|
||||
finally:
|
||||
with open(
|
||||
os.path.join(theme_dir, "gtk.css"), "w", encoding="utf-8"
|
||||
) as file:
|
||||
file.write(gtk4_css)
|
||||
elif app_type == "gtk3":
|
||||
theme_dir = get_gtk_theme_dir(app_type)
|
||||
|
||||
if not os.path.exists(theme_dir):
|
||||
os.makedirs(theme_dir)
|
||||
|
||||
gtk3_css = self.generate_gtk_css("gtk3", preset)
|
||||
contents = ""
|
||||
|
||||
try:
|
||||
with open(
|
||||
os.path.join(theme_dir, "gtk.css"), "r", encoding="utf-8"
|
||||
) as file:
|
||||
contents = file.read()
|
||||
except FileNotFoundError: # first run
|
||||
pass
|
||||
else:
|
||||
with open(
|
||||
os.path.join(theme_dir, "gtk.css.bak"), "w", encoding="utf-8"
|
||||
) as file:
|
||||
file.write(contents)
|
||||
finally:
|
||||
with open(
|
||||
os.path.join(theme_dir, "gtk.css"), "w", encoding="utf-8"
|
||||
) as file:
|
||||
file.write(gtk3_css)
|
||||
|
||||
def restore_gtk4_preset(self) -> None:
|
||||
try:
|
||||
with open(
|
||||
os.path.join(
|
||||
os.environ.get(
|
||||
"XDG_CONFIG_HOME", os.environ["HOME"] +
|
||||
"/.config"
|
||||
),
|
||||
"gtk-4.0/gtk.css.bak",
|
||||
),
|
||||
"r",
|
||||
encoding="utf-8",
|
||||
) as backup:
|
||||
contents = backup.read()
|
||||
backup.close()
|
||||
|
||||
with open(
|
||||
os.path.join(
|
||||
os.environ.get(
|
||||
"XDG_CONFIG_HOME", os.environ["HOME"] +
|
||||
"/.config"
|
||||
),
|
||||
"gtk-4.0/gtk.css",
|
||||
),
|
||||
"w",
|
||||
encoding="utf-8",
|
||||
) as gtk4css:
|
||||
gtk4css.write(contents)
|
||||
gtk4css.close()
|
||||
except OSError as e:
|
||||
logging.error("Unable to restore Gtk4 backup.", exc=e)
|
||||
raise
|
||||
|
||||
def reset_preset(self, app_type: str) -> None:
|
||||
if app_type == "gtk4":
|
||||
file = Gio.File.new_for_path(
|
||||
os.path.join(
|
||||
os.environ.get(
|
||||
"XDG_CONFIG_HOME", os.environ["HOME"] + "/.config"
|
||||
),
|
||||
"gtk-4.0/gtk.css",
|
||||
)
|
||||
)
|
||||
|
||||
try:
|
||||
file.delete()
|
||||
except GLib.GError as e:
|
||||
logging.error("Unable to delete current preset.", exc=e)
|
||||
raise
|
||||
elif app_type == "gtk3":
|
||||
file = Gio.File.new_for_path(
|
||||
os.path.join(
|
||||
os.environ.get(
|
||||
"XDG_CONFIG_HOME", os.environ["HOME"] + "/.config"
|
||||
),
|
||||
"gtk-3.0/gtk.css",
|
||||
)
|
||||
)
|
||||
|
||||
try:
|
||||
file.delete()
|
||||
except GLib.GError as e:
|
||||
logging.error("Unable to delete current preset.", exc=e)
|
||||
raise
|
347
gradience/backend/theming/shell.py
Normal file
347
gradience/backend/theming/shell.py
Normal file
|
@ -0,0 +1,347 @@
|
|||
# shell.py
|
||||
#
|
||||
# Change the look of Adwaita, with ease
|
||||
# Copyright (C) 2023, Gradience Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import os.path
|
||||
import sass
|
||||
|
||||
from gi.repository import GObject, Gio, GLib
|
||||
|
||||
from gradience.backend.models.preset import Preset
|
||||
from gradience.backend.utils.colors import color_vars_to_color_code
|
||||
from gradience.backend.utils.gnome import get_shell_version, get_shell_colors
|
||||
from gradience.backend.utils.gsettings import GSettingsSetting, FlatpakGSettings, GSettingsMissingError
|
||||
from gradience.backend.constants import datadir
|
||||
|
||||
from gradience.backend.logger import Logger
|
||||
from gradience.backend.exceptions import UnsupportedShellVersion
|
||||
from gradience.backend.globals import is_sandboxed
|
||||
|
||||
logging = Logger(logger_name="ShellTheme")
|
||||
|
||||
|
||||
class ShellTheme:
|
||||
# Supported GNOME Shell versions: 42, 43, 44
|
||||
shell_versions = [42, 43, 44]
|
||||
shell_versions_str = [str(version) for version in shell_versions]
|
||||
version_target = None
|
||||
|
||||
theme_variant = None
|
||||
|
||||
shell_colors = {}
|
||||
|
||||
preset_variables = {}
|
||||
preset_palette = {}
|
||||
|
||||
custom_css = None
|
||||
|
||||
def __init__(self, shell_version=None):
|
||||
self._cancellable = Gio.Cancellable()
|
||||
|
||||
if not shell_version:
|
||||
self._detect_shell_version()
|
||||
elif shell_version and shell_version in self.shell_versions:
|
||||
self.version_target = shell_version
|
||||
else:
|
||||
raise UnsupportedShellVersion(
|
||||
f"GNOME Shell version {shell_version} is not supported. (Supported versions: {', '.join(self.shell_versions_str)})")
|
||||
|
||||
self.THEME_GSETTINGS_SCHEMA_ID = "org.gnome.shell.extensions.user-theme"
|
||||
self.THEME_GSETTINGS_SCHEMA_PATH = "/org/gnome/shell/extensions/user-theme/"
|
||||
self.THEME_GSETTINGS_SCHEMA_KEY = "name"
|
||||
|
||||
self.THEME_EXT_NAME = "user-theme@gnome-shell-extensions.gcampax.github.com"
|
||||
self.THEME_GSETTINGS_DIR = os.path.join(GLib.get_home_dir(), ".local/share/",
|
||||
"gnome-shell", "extensions", self.THEME_EXT_NAME, "schemas")
|
||||
|
||||
try:
|
||||
if os.path.exists(self.THEME_GSETTINGS_DIR):
|
||||
if not is_sandboxed():
|
||||
self.settings = GSettingsSetting(self.THEME_GSETTINGS_SCHEMA_ID,
|
||||
schema_dir=self.THEME_GSETTINGS_DIR)
|
||||
else:
|
||||
self.settings = FlatpakGSettings(self.THEME_GSETTINGS_SCHEMA_ID,
|
||||
schema_dir=self.THEME_GSETTINGS_DIR)
|
||||
else:
|
||||
if not is_sandboxed():
|
||||
self.settings = GSettingsSetting(self.THEME_GSETTINGS_SCHEMA_ID)
|
||||
else:
|
||||
self.settings = FlatpakGSettings(self.THEME_GSETTINGS_SCHEMA_ID)
|
||||
except (GSettingsMissingError, GLib.GError):
|
||||
raise
|
||||
|
||||
# Theme source/output paths
|
||||
self.templates_dir = os.path.join(datadir, "gradience", "shell", "templates", str(self.version_target))
|
||||
self.source_dir = os.path.join(GLib.get_home_dir(), ".cache", "gradience", "gradience-shell", str(self.version_target))
|
||||
|
||||
if os.path.exists(self.source_dir):
|
||||
shutil.rmtree(self.source_dir)
|
||||
|
||||
# Copy shell theme source directories to ~/.cache/gradience/gradience-shell
|
||||
shutil.copytree(os.path.join(datadir, "gradience", "shell",
|
||||
str(self.version_target)), self.source_dir, dirs_exist_ok=True
|
||||
)
|
||||
|
||||
# TODO: Allow user to use different name than "gradience-shell" (also, with default name, we should append "-light" suffix when generated from light preset)
|
||||
self.output_dir = os.path.join(GLib.get_home_dir(), ".local/share/themes", "gradience-shell", "gnome-shell")
|
||||
|
||||
self.main_template = os.path.join(self.templates_dir, "gnome-shell.template")
|
||||
self.colors_template = os.path.join(self.templates_dir, "colors.template")
|
||||
self.palette_template = os.path.join(self.templates_dir, "palette.template")
|
||||
self.switches_template = os.path.join(self.templates_dir, "switches.template")
|
||||
|
||||
self.main_source = os.path.join(self.source_dir, "gnome-shell.scss")
|
||||
self.colors_source = os.path.join(self.source_dir, "gnome-shell-sass", "_colors.scss")
|
||||
self.palette_source = os.path.join(self.source_dir, "gnome-shell-sass", "_palette.scss")
|
||||
self.switches_source = os.path.join(self.source_dir, "gnome-shell-sass", "widgets", "_switches.scss")
|
||||
|
||||
self.assets_output = os.path.join(self.output_dir, "assets")
|
||||
|
||||
def get_cancellable(self) -> Gio.Cancellable:
|
||||
return self._cancellable
|
||||
|
||||
def apply_theme_async(self, caller:GObject.Object, callback:callable,
|
||||
theme_variant:str,
|
||||
preset: Preset):
|
||||
task = Gio.Task.new(caller, None, callback, self._cancellable)
|
||||
self.async_data = [theme_variant, preset]
|
||||
|
||||
task.set_return_on_cancel(True)
|
||||
task.run_in_thread(self._apply_theme_thread)
|
||||
|
||||
def _apply_theme_thread(self, task:Gio.Task, source_object:GObject.Object,
|
||||
task_data:object,
|
||||
cancellable:Gio.Cancellable):
|
||||
if task.return_error_if_cancelled():
|
||||
return
|
||||
|
||||
theme_variant = self.async_data[0]
|
||||
preset = self.async_data[1]
|
||||
|
||||
output = self.apply_theme(source_object, theme_variant, preset)
|
||||
task.return_value(output)
|
||||
|
||||
# TODO: Make it accept either dict or callable in `parent` parameter
|
||||
def apply_theme(self, parent: callable, theme_variant: str, preset: Preset):
|
||||
if theme_variant in ("light", "dark"):
|
||||
self.theme_variant = theme_variant
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Theme variant {theme_variant} not in list: [light, dark]")
|
||||
|
||||
try:
|
||||
self._create_theme(parent, preset)
|
||||
except (OSError, GLib.GError) as e:
|
||||
raise
|
||||
|
||||
def _create_theme(self, parent: callable, preset: Preset):
|
||||
# Convert GTK color variables to normal color values
|
||||
self.preset_variables = color_vars_to_color_code(preset.variables, preset.palette)
|
||||
self.preset_palette = preset.palette
|
||||
self.custom_css = preset.custom_css
|
||||
|
||||
# TODO: Move custom Shell colors list to Shell modules
|
||||
self.shell_colors = parent.shell_colors if parent != None else None
|
||||
|
||||
self._insert_variables()
|
||||
self._recolor_assets()
|
||||
|
||||
if not os.path.exists(self.output_dir):
|
||||
try:
|
||||
dirs = Gio.File.new_for_path(self.output_dir)
|
||||
dirs.make_directory_with_parents(None)
|
||||
except GLib.GError as e:
|
||||
logging.error(f"Unable to create directories.", exc=e)
|
||||
raise
|
||||
|
||||
self._compile_sass(os.path.join(self.source_dir, "gnome-shell.scss"),
|
||||
os.path.join(self.output_dir, "gnome-shell.css"))
|
||||
|
||||
self._set_shell_theme()
|
||||
|
||||
def _insert_variables(self):
|
||||
# hexcode_regex = re.compile(r".*#[0-9a-f]{3,6}")
|
||||
template_regex = re.compile(r"{{(.*?)}}")
|
||||
|
||||
palette_content = ""
|
||||
|
||||
with open(self.palette_template, "r", encoding="utf-8") as template:
|
||||
for line in template:
|
||||
template_match = re.search(template_regex, line)
|
||||
if template_match != None:
|
||||
_key = template_match.__getitem__(1)
|
||||
prefix = _key.split("_")[0] + "_"
|
||||
key = _key.split("_")[1]
|
||||
inserted = line.replace("{{" + _key + "}}", self.preset_palette[prefix][key])
|
||||
palette_content += inserted
|
||||
else:
|
||||
palette_content += line
|
||||
template.close()
|
||||
|
||||
with open(self.palette_source, "w", encoding="utf-8") as sheet:
|
||||
sheet.write(palette_content)
|
||||
sheet.close()
|
||||
|
||||
colors_content = ""
|
||||
|
||||
with open(self.colors_template, "r", encoding="utf-8") as template:
|
||||
for line in template:
|
||||
template_match = re.search(template_regex, line)
|
||||
if template_match != None:
|
||||
key = template_match.__getitem__(1)
|
||||
shell_colors = get_shell_colors(self.preset_variables)
|
||||
try:
|
||||
if self.shell_colors:
|
||||
inserted = line.replace(
|
||||
"{{" + key + "}}", self.shell_colors[key])
|
||||
else:
|
||||
inserted = line.replace(
|
||||
"{{" + key + "}}", shell_colors[key])
|
||||
except KeyError:
|
||||
inserted = line.replace(
|
||||
"{{" + key + "}}", self.preset_variables[key])
|
||||
colors_content += inserted
|
||||
else:
|
||||
colors_content += line
|
||||
template.close()
|
||||
|
||||
with open(self.colors_source, "w", encoding="utf-8") as sheet:
|
||||
sheet.write(colors_content)
|
||||
sheet.close()
|
||||
|
||||
main_content = ""
|
||||
|
||||
with open(self.main_template, "r", encoding="utf-8") as template:
|
||||
key = "theme_variant"
|
||||
|
||||
for line in template:
|
||||
if key in line:
|
||||
inserted = line.replace(
|
||||
"{{" + key + "}}", f"'{self.theme_variant}'")
|
||||
main_content += inserted
|
||||
elif "custom_css" in line:
|
||||
key = "custom_css"
|
||||
try:
|
||||
inserted = line.replace(
|
||||
"{{" + key + "}}", self.custom_css['shell'])
|
||||
except KeyError: # No custom CSS
|
||||
inserted = line.replace("{{" + key + "}}", "")
|
||||
main_content += inserted
|
||||
else:
|
||||
main_content += line
|
||||
template.close()
|
||||
|
||||
with open(self.main_source, "w", encoding="utf-8") as sheet:
|
||||
sheet.write(main_content)
|
||||
sheet.close()
|
||||
|
||||
def _compile_sass(self, sass_path, output_path):
|
||||
try:
|
||||
compiled = sass.compile(filename=sass_path, output_style="nested")
|
||||
except (GLib.GError, sass.CompileError) as e:
|
||||
logging.error(
|
||||
f"Failed to compile SCSS source files.", exc=e)
|
||||
else:
|
||||
with open(output_path, "w", encoding="utf-8") as css_file:
|
||||
css_file.write(compiled)
|
||||
css_file.close()
|
||||
|
||||
# TODO: Add recoloring for other assets
|
||||
def _recolor_assets(self):
|
||||
accent_bg = self.preset_variables["accent_bg_color"]
|
||||
|
||||
switch_on_source = os.path.join(self.source_dir, "toggle-on.svg")
|
||||
|
||||
shutil.copy(
|
||||
self.switches_template,
|
||||
self.switches_source
|
||||
)
|
||||
|
||||
with open(switch_on_source, "r", encoding="utf-8") as svg_data:
|
||||
switch_on_svg = svg_data.read()
|
||||
switch_on_svg = switch_on_svg.replace(
|
||||
"fill:#3584e4", f"fill:{accent_bg}")
|
||||
svg_data.close()
|
||||
|
||||
with open(switch_on_source, "w", encoding="utf-8") as svg_data:
|
||||
svg_data.write(switch_on_svg)
|
||||
svg_data.close()
|
||||
|
||||
if not os.path.exists(self.assets_output):
|
||||
try:
|
||||
dirs = Gio.File.new_for_path(self.assets_output)
|
||||
dirs.make_directory_with_parents(None)
|
||||
except GLib.GError as e:
|
||||
logging.error(f"Unable to create directories.", exc=e)
|
||||
raise
|
||||
|
||||
shutil.copy(
|
||||
switch_on_source,
|
||||
os.path.join(self.assets_output, "toggle-on.svg")
|
||||
)
|
||||
|
||||
def _set_shell_theme(self):
|
||||
key = self.THEME_GSETTINGS_SCHEMA_KEY
|
||||
|
||||
# Set default theme
|
||||
self.settings.reset(key)
|
||||
|
||||
if is_sandboxed():
|
||||
# Set theme generated by Gradience
|
||||
self.settings.set(key, "gradience-shell")
|
||||
else:
|
||||
# Set theme generated by Gradience
|
||||
self.settings.set_string(key, "gradience-shell")
|
||||
|
||||
def _detect_shell_version(self):
|
||||
shell_ver = get_shell_version()
|
||||
|
||||
if shell_ver.startswith("3"):
|
||||
raise UnsupportedShellVersion(
|
||||
f"GNOME Shell version {shell_ver} is not supported. (Supported versions: {', '.join(self.shell_versions_str)})")
|
||||
|
||||
if shell_ver.startswith("4"):
|
||||
shell_ver = int(shell_ver[:2])
|
||||
|
||||
if shell_ver in self.shell_versions:
|
||||
self.version_target = shell_ver
|
||||
else:
|
||||
raise UnsupportedShellVersion(
|
||||
f"GNOME Shell version {shell_ver} is not supported. (Supported versions: {', '.join(self.shell_versions_str)})")
|
||||
|
||||
def reset_theme_async(self, caller:GObject.Object, callback:callable):
|
||||
task = Gio.Task.new(caller, None, callback, self._cancellable)
|
||||
|
||||
task.set_return_on_cancel(True)
|
||||
task.run_in_thread(self._reset_theme_thread)
|
||||
|
||||
def reset_theme(self):
|
||||
key = self.THEME_GSETTINGS_SCHEMA_KEY
|
||||
|
||||
# Set default theme
|
||||
self.settings.reset(key)
|
||||
|
||||
def _reset_theme_thread(self, task:Gio.Task, source_object:GObject.Object,
|
||||
task_data:object, cancellable:Gio.Cancellable):
|
||||
if task.return_error_if_cancelled():
|
||||
return
|
||||
|
||||
output = self.reset_theme()
|
||||
task.return_value(output)
|
|
@ -1,7 +1,7 @@
|
|||
# colors.py
|
||||
#
|
||||
# Change the look of Adwaita, with ease
|
||||
# Copyright (C) 2022 Gradience Team
|
||||
# Copyright (C) 2022-2023, Gradience Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -18,17 +18,12 @@
|
|||
|
||||
import material_color_utilities_python as monet
|
||||
|
||||
from gradience.backend.globals import adw_variables_prefixes, adw_palette_prefixes
|
||||
|
||||
def rgba_from_argb(argb, alpha=None) -> str:
|
||||
base = "rgba({}, {}, {}, {})"
|
||||
from gradience.backend.logger import Logger
|
||||
|
||||
red = monet.redFromArgb(argb)
|
||||
green = monet.greenFromArgb(argb)
|
||||
blue = monet.blueFromArgb(argb)
|
||||
if not alpha:
|
||||
alpha = monet.alphaFromArgb(argb)
|
||||
logging = Logger(logger_name="ColorUtils")
|
||||
|
||||
return base.format(red, green, blue, alpha)
|
||||
|
||||
def rgb_to_hash(rgb) -> [str, float]:
|
||||
"""
|
||||
|
@ -82,3 +77,47 @@ def argb_to_color_code(argb, alpha=None) -> str:
|
|||
return monet.hexFromArgb(argb)
|
||||
|
||||
return rgba_base.format(red, green, blue, alpha)
|
||||
|
||||
def color_vars_to_color_code(variables: dict, palette: dict) -> dict:
|
||||
"""
|
||||
This function converts GTK color variables to color code
|
||||
(hexadecimal code if no transparency channel, RGBA format if otherwise).
|
||||
|
||||
You can bypass passing a `palette` parameter if you put an None value to it.
|
||||
This isn't recommended however, because in most cases you'll be unable to determine
|
||||
if variables you pass don't contain any palette color variables.
|
||||
"""
|
||||
|
||||
output = variables
|
||||
|
||||
if palette == None:
|
||||
logging.warning("Palette parameter in `color_vars_to_color_code()` function not set. Incoming bugs ahead!")
|
||||
|
||||
def __has_palette_prefix(color):
|
||||
return any(prefix in color for prefix in adw_palette_prefixes)
|
||||
|
||||
def __has_variable_prefix(color):
|
||||
return any(prefix in color for prefix in adw_variables_prefixes)
|
||||
|
||||
def __update_vars(var_type, variable, color_value):
|
||||
if var_type == "palette":
|
||||
output[variable] = palette[color_value[:-1]][color_value[-1:]]
|
||||
elif var_type == "variable":
|
||||
output[variable] = output[color_value]
|
||||
|
||||
if __has_variable_prefix(output[variable]):
|
||||
__update_variable_vars(variable, output[variable])
|
||||
|
||||
if __has_palette_prefix(output[variable]):
|
||||
__update_palette_vars(variable, output[variable])
|
||||
|
||||
for variable, color in output.items():
|
||||
color_value = color[1:] # Remove '@' from the beginning of the color variable
|
||||
|
||||
if __has_palette_prefix(color_value) and palette != None:
|
||||
__update_vars("palette", variable, color_value)
|
||||
|
||||
if __has_variable_prefix(color_value):
|
||||
__update_vars("variable", variable, color_value)
|
||||
|
||||
return output
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# common.py
|
||||
#
|
||||
# Change the look of Adwaita, with ease
|
||||
# Copyright (C) 2022 Gradience Team
|
||||
# Copyright (C) 2022-2023, Gradience Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -18,18 +18,25 @@
|
|||
|
||||
import re
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
from anyascii import anyascii
|
||||
|
||||
from gi.repository import Gio
|
||||
|
||||
|
||||
def to_slug_case(non_slug) -> str:
|
||||
return re.sub(r"[^0-9a-z]+", "-", anyascii(non_slug).lower()).strip("-")
|
||||
|
||||
def run_command(command, *args, **kwargs):
|
||||
if isinstance(command, str): # run on the host
|
||||
command = [command]
|
||||
if os.environ.get('FLATPAK_ID'): # run in flatpak
|
||||
command = ['flatpak-spawn', '--host'] + command
|
||||
def extract_version(text, prefix_text=None):
|
||||
'''
|
||||
Extracts version number from a provided text.
|
||||
|
||||
return subprocess.run(command, *args, **kwargs, check=True)
|
||||
You can also set the prefix_text parameter to reduce searching to
|
||||
lines with only this text prefixed to the version number.
|
||||
'''
|
||||
if not prefix_text:
|
||||
version = re.search(r"\s*([0-9.]+)", text)
|
||||
else:
|
||||
version = re.search(prefix_text + r"\s*([0-9.]+)", text)
|
||||
|
||||
return version.__getitem__(1)
|
||||
|
|
99
gradience/backend/utils/gnome.py
Normal file
99
gradience/backend/utils/gnome.py
Normal file
|
@ -0,0 +1,99 @@
|
|||
# shell.py
|
||||
#
|
||||
# Change the look of Adwaita, with ease
|
||||
# Copyright (C) 2023, Gradience Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
|
||||
from gradience.backend.models.preset import Preset
|
||||
from gradience.backend.utils.subprocess import GradienceSubprocess
|
||||
from gradience.backend.utils.common import extract_version
|
||||
|
||||
# TODO: Remove this import later (imports from gradience.frontend are not allowed in backend)
|
||||
from gradience.frontend.schemas.shell_schema import shell_schema
|
||||
|
||||
|
||||
# TODO: Return failure if command was not found
|
||||
def get_shell_version() -> str:
|
||||
cmd_list = ["gnome-shell", "--version"]
|
||||
process = GradienceSubprocess()
|
||||
|
||||
completed = process.run(cmd_list, allow_escaping=True)
|
||||
stdout = process.get_stdout_data(completed, decode=True)
|
||||
|
||||
shell_version = extract_version(stdout, "GNOME Shell")
|
||||
|
||||
return shell_version
|
||||
|
||||
def get_full_shell_version() -> str:
|
||||
cmd_list = ["gnome-shell", "--version"]
|
||||
process = GradienceSubprocess()
|
||||
|
||||
completed = process.run(cmd_list, allow_escaping=True)
|
||||
stdout = process.get_stdout_data(completed, decode=True)
|
||||
|
||||
shell_version = stdout[12:]
|
||||
|
||||
return shell_version
|
||||
|
||||
def is_gnome_available() -> bool:
|
||||
xdg_current_desktop = os.environ.get("XDG_CURRENT_DESKTOP").lower()
|
||||
|
||||
if "gnome" in xdg_current_desktop:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def is_shell_ext_installed(uuid: str, check_enabled: bool = False) -> bool:
|
||||
"""
|
||||
Checks if Shell extension with provided UUID from `uuid` parameter
|
||||
is installed in system.
|
||||
|
||||
`check_enabled` parameter allows for checking if extension is enabled.
|
||||
"""
|
||||
|
||||
if check_enabled:
|
||||
cmd_list = ["gnome-extensions", "list", "--enabled"]
|
||||
else:
|
||||
cmd_list = ["gnome-extensions", "list"]
|
||||
|
||||
process = GradienceSubprocess()
|
||||
|
||||
completed = process.run(cmd_list, allow_escaping=True)
|
||||
stdout = process.get_stdout_data(completed, decode=True)
|
||||
|
||||
ext_list = stdout.split("\n")
|
||||
if ext_list[-1] == "":
|
||||
ext_list.pop(-1)
|
||||
|
||||
if uuid in ext_list:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def get_shell_colors(preset_variables: Preset.variables) -> dict:
|
||||
shell_colors = {}
|
||||
|
||||
for variable in shell_schema["variables"]:
|
||||
shell_colors[variable["name"]] = variable["var_name"]
|
||||
|
||||
for shell_key, var_name in shell_colors.items():
|
||||
if shell_key == "panel_bg_color":
|
||||
shell_colors[shell_key] = shell_schema["variables"][5]["default_value"]
|
||||
continue
|
||||
shell_colors[shell_key] = preset_variables[var_name]
|
||||
|
||||
return shell_colors
|
273
gradience/backend/utils/gsettings.py
Normal file
273
gradience/backend/utils/gsettings.py
Normal file
|
@ -0,0 +1,273 @@
|
|||
# gsettings.py
|
||||
#
|
||||
# Change the look of Adwaita, with ease
|
||||
# Copyright (c) 2011, John Stowers
|
||||
# Copyright (C) 2023, Gradience Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# NOTICE:
|
||||
# This code is from the GNOME Tweaks application, which is licensed under the GPL-3.0 license.
|
||||
# https://gitlab.gnome.org/GNOME/gnome-tweaks/-/blob/master/gtweak/gsettings.py
|
||||
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import os.path
|
||||
import xml.dom.minidom
|
||||
import gettext
|
||||
|
||||
from subprocess import SubprocessError, CompletedProcess
|
||||
|
||||
from gi.repository import Gio, GLib
|
||||
|
||||
from gradience.backend.utils.subprocess import GradienceSubprocess
|
||||
from gradience.backend.constants import localedir, app_id
|
||||
|
||||
from gradience.backend.logger import Logger
|
||||
|
||||
logging = Logger(logger_name="GSettings")
|
||||
|
||||
|
||||
_SCHEMA_CACHE = {}
|
||||
_GSETTINGS_SCHEMAS = set(Gio.Settings.list_schemas())
|
||||
_GSETTINGS_RELOCATABLE_SCHEMAS = set(Gio.Settings.list_relocatable_schemas())
|
||||
|
||||
|
||||
class GSettingsMissingError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class _GSettingsSchema:
|
||||
def __init__(self, schema_name, schema_dir=None, schema_filename=None, **options):
|
||||
if not schema_filename:
|
||||
schema_filename = schema_name + ".gschema.xml"
|
||||
if not schema_dir:
|
||||
schema_dir = app_id
|
||||
for xdg_dir in GLib.get_system_data_dirs():
|
||||
dir = os.path.join(xdg_dir, "glib-2.0", "schemas")
|
||||
if os.path.exists(os.path.join(dir, schema_filename)):
|
||||
schema_dir = dir
|
||||
break
|
||||
|
||||
schema_path = os.path.join(schema_dir, schema_filename)
|
||||
if not os.path.exists(schema_path):
|
||||
logging.critical("Could not find schema %s" % schema_path)
|
||||
assert (False)
|
||||
|
||||
self._schema_name = schema_name
|
||||
self._schema = {}
|
||||
|
||||
try:
|
||||
dom = xml.dom.minidom.parse(schema_path)
|
||||
global_gettext_domain = dom.documentElement.getAttribute(
|
||||
'gettext-domain')
|
||||
try:
|
||||
if global_gettext_domain:
|
||||
# We can't know where the schema owner was installed, let's assume it's
|
||||
# the same prefix as ours
|
||||
global_translation = gettext.translation(
|
||||
global_gettext_domain, localedir)
|
||||
else:
|
||||
global_translation = gettext.NullTranslations()
|
||||
except IOError:
|
||||
global_translation = None
|
||||
logging.debug("No translated schema for %s (domain: %s)" % (
|
||||
schema_name, global_gettext_domain))
|
||||
for schema in dom.getElementsByTagName("schema"):
|
||||
gettext_domain = schema.getAttribute('gettext-domain')
|
||||
try:
|
||||
if gettext_domain:
|
||||
translation = gettext.translation(
|
||||
gettext_domain, localedir)
|
||||
else:
|
||||
translation = global_translation
|
||||
except IOError:
|
||||
translation = None
|
||||
logging.debug("Schema not translated %s (domain: %s)" % (
|
||||
schema_name, gettext_domain))
|
||||
if schema_name == schema.getAttribute("id"):
|
||||
for key in schema.getElementsByTagName("key"):
|
||||
name = key.getAttribute("name")
|
||||
# summary is 'compulsory', description is optional
|
||||
# …in theory, but we should not barf on bad schemas ever
|
||||
try:
|
||||
summary = key.getElementsByTagName(
|
||||
"summary")[0].childNodes[0].data
|
||||
except:
|
||||
summary = ""
|
||||
logging.info("Schema missing summary %s (key %s)" %
|
||||
(os.path.basename(schema_path), name))
|
||||
try:
|
||||
description = key.getElementsByTagName(
|
||||
"description")[0].childNodes[0].data
|
||||
except:
|
||||
description = ""
|
||||
|
||||
# if missing translations, use the untranslated values
|
||||
self._schema[name] = dict(
|
||||
summary=translation.gettext(
|
||||
summary) if translation else summary,
|
||||
description=translation.gettext(
|
||||
description) if translation else description
|
||||
)
|
||||
|
||||
except:
|
||||
logging.critical("Error parsing schema %s (%s)" %
|
||||
(schema_name, schema_path), exc_info=True)
|
||||
|
||||
def __repr__(self):
|
||||
return "<gradience._GSettingsSchema: %s>" % self._schema_name
|
||||
|
||||
|
||||
class GSettingsSetting(Gio.Settings):
|
||||
def __init__(self, schema_name, schema_dir=None, schema_path=None, **options):
|
||||
|
||||
if schema_dir is None:
|
||||
if schema_path is None and schema_name not in _GSETTINGS_SCHEMAS:
|
||||
raise GSettingsMissingError(schema_name)
|
||||
|
||||
if schema_path is not None and schema_name not in _GSETTINGS_RELOCATABLE_SCHEMAS:
|
||||
raise GSettingsMissingError(schema_name)
|
||||
|
||||
if schema_path is None:
|
||||
Gio.Settings.__init__(self, schema=schema_name)
|
||||
else:
|
||||
Gio.Settings.__init__(
|
||||
self, schema=schema_name, path=schema_path)
|
||||
else:
|
||||
GioSSS = Gio.SettingsSchemaSource
|
||||
schema_source = GioSSS.new_from_directory(schema_dir,
|
||||
GioSSS.get_default(),
|
||||
False)
|
||||
schema_obj = schema_source.lookup(schema_name, True)
|
||||
if not schema_obj:
|
||||
raise GSettingsMissingError(schema_name)
|
||||
|
||||
Gio.Settings.__init__(self, None, settings_schema=schema_obj)
|
||||
|
||||
if schema_name not in _SCHEMA_CACHE:
|
||||
_SCHEMA_CACHE[schema_name] = _GSettingsSchema(
|
||||
schema_name, schema_dir=schema_dir, **options)
|
||||
logging.debug("Caching gsettings: %s" % _SCHEMA_CACHE[schema_name])
|
||||
|
||||
self._schema = _SCHEMA_CACHE[schema_name]
|
||||
|
||||
def _on_changed(self, settings, key_name):
|
||||
logging.debug("Change: %s %s -> %s" %
|
||||
(self.props.schema, key_name, self[key_name]))
|
||||
|
||||
def _setting_check_is_list(self, key):
|
||||
variant = Gio.Settings.get_value(self, key)
|
||||
return variant.get_type_string() == "as"
|
||||
|
||||
def schema_get_summary(self, key):
|
||||
return self._schema._schema[key]["summary"]
|
||||
|
||||
def schema_get_description(self, key):
|
||||
return self._schema._schema[key]["description"]
|
||||
|
||||
def schema_get_all(self, key):
|
||||
return self._schema._schema[key]
|
||||
|
||||
def setting_add_to_list(self, key, value):
|
||||
""" helper function, ensures value is present in the GSettingsList at key """
|
||||
assert self._setting_check_is_list(key)
|
||||
|
||||
vals = self[key]
|
||||
if value not in vals:
|
||||
vals.append(value)
|
||||
self[key] = vals
|
||||
return True
|
||||
|
||||
def setting_remove_from_list(self, key, value):
|
||||
""" helper function, removes value in the GSettingsList at key (if present)"""
|
||||
assert self._setting_check_is_list(key)
|
||||
|
||||
vals = self[key]
|
||||
try:
|
||||
vals.remove(value)
|
||||
self[key] = vals
|
||||
return True
|
||||
except ValueError:
|
||||
# not present
|
||||
pass
|
||||
|
||||
def setting_is_in_list(self, key, value):
|
||||
assert self._setting_check_is_list(key)
|
||||
return value in self[key]
|
||||
|
||||
|
||||
class FlatpakGSettings:
|
||||
def __init__(self, schema_name, schema_dir=None, **options):
|
||||
self.schema_name = schema_name
|
||||
self.schema_dir = schema_dir
|
||||
|
||||
def list_keys(self) -> str:
|
||||
dconf_cmd = ["gsettings", "list-keys", self.schema_name]
|
||||
process = GradienceSubprocess()
|
||||
|
||||
if self.schema_dir:
|
||||
self._insert_schemadir(dconf_cmd)
|
||||
|
||||
try:
|
||||
completed = process.run(dconf_cmd, allow_escaping=True)
|
||||
stdout = process.get_stdout_data(completed, decode=True)
|
||||
except SubprocessError:
|
||||
raise
|
||||
else:
|
||||
return stdout
|
||||
|
||||
def get(self, key:str) -> CompletedProcess:
|
||||
dconf_cmd = ["gsettings", "get", self.schema_name, key]
|
||||
process = GradienceSubprocess()
|
||||
|
||||
if self.schema_dir:
|
||||
self._insert_schemadir(dconf_cmd)
|
||||
|
||||
try:
|
||||
completed = process.run(dconf_cmd, allow_escaping=True)
|
||||
stdout = process.get_stdout_data(completed, decode=True)
|
||||
except SubprocessError:
|
||||
raise
|
||||
else:
|
||||
return stdout
|
||||
|
||||
def set(self, key:str, value:str) -> None:
|
||||
dconf_cmd = ["gsettings", "set", self.schema_name, key, value]
|
||||
process = GradienceSubprocess()
|
||||
|
||||
if self.schema_dir:
|
||||
self._insert_schemadir(dconf_cmd)
|
||||
|
||||
try:
|
||||
process.run(dconf_cmd, allow_escaping=True)
|
||||
except SubprocessError:
|
||||
raise
|
||||
|
||||
def reset(self, key:str = None) -> None:
|
||||
dconf_cmd = ["gsettings", "reset", self.schema_name, key]
|
||||
process = GradienceSubprocess()
|
||||
|
||||
if self.schema_dir:
|
||||
self._insert_schemadir(dconf_cmd)
|
||||
|
||||
try:
|
||||
process.run(dconf_cmd, allow_escaping=True)
|
||||
except SubprocessError:
|
||||
raise
|
||||
|
||||
def _insert_schemadir(self, dconf_cmd):
|
||||
dconf_cmd.insert(1, "--schemadir")
|
||||
dconf_cmd.insert(2, self.schema_dir)
|
|
@ -3,6 +3,10 @@ utilsdir = 'gradience/backend/utils'
|
|||
gradience_sources = [
|
||||
'__init__.py',
|
||||
'colors.py',
|
||||
'common.py'
|
||||
'common.py',
|
||||
'gnome.py',
|
||||
'gsettings.py',
|
||||
'subprocess.py',
|
||||
'theming.py'
|
||||
]
|
||||
PY_INSTALLDIR.install_sources(gradience_sources, subdir: utilsdir)
|
||||
|
|
58
gradience/backend/utils/shell.py
Normal file
58
gradience/backend/utils/shell.py
Normal file
|
@ -0,0 +1,58 @@
|
|||
# shell.py
|
||||
#
|
||||
# Change the look of Adwaita, with ease
|
||||
# Copyright (C) 2023, Gradience Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
from gi.repository import Gio
|
||||
|
||||
from gradience.backend.models.preset import Preset
|
||||
from gradience.backend.utils.common import extract_version, run_command
|
||||
|
||||
# TODO: Remove this import later (imports from gradience.frontend are not allowed in backend)
|
||||
from gradience.frontend.schemas.shell_schema import shell_schema
|
||||
|
||||
|
||||
def get_shell_version():
|
||||
stdout = run_command(["gnome-shell", "--version"],
|
||||
get_stdout_text=True,
|
||||
allow_escaping=True).replace("\n", "")
|
||||
|
||||
shell_version = extract_version(stdout, "GNOME Shell")
|
||||
|
||||
return shell_version
|
||||
|
||||
def get_full_shell_version():
|
||||
stdout = run_command(["gnome-shell", "--version"],
|
||||
get_stdout_text=True,
|
||||
allow_escaping=True).replace("\n", "")
|
||||
|
||||
shell_version = stdout[12:]
|
||||
|
||||
return shell_version
|
||||
|
||||
def get_shell_colors(preset_variables: Preset.variables):
|
||||
shell_colors = {}
|
||||
|
||||
for variable in shell_schema["variables"]:
|
||||
shell_colors[variable["name"]] = variable["var_name"]
|
||||
|
||||
for shell_key, var_name in shell_colors.items():
|
||||
if shell_key == "panel_bg_color":
|
||||
shell_colors[shell_key] = shell_schema["variables"][5]["default_value"]
|
||||
continue
|
||||
shell_colors[shell_key] = preset_variables[var_name]
|
||||
|
||||
return shell_colors
|
94
gradience/backend/utils/subprocess.py
Normal file
94
gradience/backend/utils/subprocess.py
Normal file
|
@ -0,0 +1,94 @@
|
|||
# subprocess.py
|
||||
#
|
||||
# Change the look of Adwaita, with ease
|
||||
# Copyright (C) 2022-2023, Gradience Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
#import signal
|
||||
|
||||
from typing import Union
|
||||
|
||||
import subprocess
|
||||
from subprocess import SubprocessError, CompletedProcess
|
||||
|
||||
from gradience.backend.logger import Logger
|
||||
|
||||
logging = Logger(logger_name="GradienceSubprocess")
|
||||
|
||||
|
||||
# TODO: Check how Dev Toolbox has its backend done fully in async using Gio.Task.
|
||||
# Example: https://github.com/aleiepure/devtoolbox/blob/main/src/services/gzip_compressor.py
|
||||
# TODO: Replace subprocess.run() with subprocess.Popen() for more control over subprocesses
|
||||
class GradienceSubprocess:
|
||||
"""
|
||||
Wrapper for Python's `subprocess` module to provide an easy to use
|
||||
synchronous process spawning and stdout data retrievement with support
|
||||
for Flatpak sandbox escape.
|
||||
|
||||
Documentation: https://docs.python.org/3/library/subprocess.html
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def run(self, command: list, timeout: int = None, allow_escaping: bool = False) -> CompletedProcess:
|
||||
"""
|
||||
Spawns synchronously a new child process (subprocess) using Python's `subprocess` module.
|
||||
|
||||
You can set the `timeout` parameter to kill the process after a
|
||||
specified amount of seconds.
|
||||
|
||||
You can enable executing commands outside Flatpak sandbox by
|
||||
enabling `allow_escaping` parameter.
|
||||
"""
|
||||
|
||||
if allow_escaping and os.environ.get('FLATPAK_ID'):
|
||||
command = ['flatpak-spawn', '--host'] + command
|
||||
|
||||
logging.debug(f"Spawning: {command}")
|
||||
|
||||
try:
|
||||
process = subprocess.run(command, check=True,
|
||||
capture_output=True, timeout=timeout)
|
||||
except SubprocessError:
|
||||
raise
|
||||
except FileNotFoundError:
|
||||
raise
|
||||
|
||||
return process
|
||||
|
||||
def get_stdout_data(self, process: CompletedProcess, decode: bool = False) -> Union[str, bytes]:
|
||||
"""
|
||||
Returns a data retrieved from stdout stream.
|
||||
|
||||
Default behavior returns a full data collection in bytes array.
|
||||
Setting ``decode`` parameter to True will automatically decode data to string object.
|
||||
"""
|
||||
|
||||
if decode:
|
||||
stdout_string = process.stdout.decode()
|
||||
return stdout_string
|
||||
|
||||
return process.stdout
|
||||
|
||||
'''def stop(self, process: CompletedProcess) -> None:
|
||||
logging.debug(f"Terminating process, ID {process.get_identifier()}")
|
||||
process.send_signal(signal.SIGTERM)
|
||||
|
||||
def kill(self, process: CompletedProcess) -> None:
|
||||
logging.debug(f"Killing process, ID {process.get_identifier()}")
|
||||
self.cancel_read()
|
||||
process.send_signal(signal.SIGKILL)'''
|
50
gradience/backend/utils/theming.py
Normal file
50
gradience/backend/utils/theming.py
Normal file
|
@ -0,0 +1,50 @@
|
|||
# theming.py
|
||||
#
|
||||
# Change the look of Adwaita, with ease
|
||||
# Copyright (C) 2023, Gradience Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
from gradience.backend.models.preset import Preset
|
||||
|
||||
|
||||
def generate_gtk_css(app_type: str, preset: Preset) -> str:
|
||||
variables = preset.variables
|
||||
palette = preset.palette
|
||||
custom_css = preset.custom_css
|
||||
|
||||
theming_warning = """/*
|
||||
Generated with Gradience
|
||||
|
||||
Issues caused by theming should be reported to Gradience repository, and not upstream
|
||||
|
||||
https://github.com/GradienceTeam/Gradience
|
||||
*/
|
||||
|
||||
"""
|
||||
|
||||
gtk_css = ""
|
||||
|
||||
for key in variables.keys():
|
||||
gtk_css += f"@define-color {key} {variables[key]};\n"
|
||||
|
||||
for prefix_key in palette.keys():
|
||||
for key in palette[prefix_key].keys():
|
||||
gtk_css += f"@define-color {prefix_key + key} {palette[prefix_key][key]};\n"
|
||||
|
||||
gtk_css += custom_css.get(app_type, "")
|
||||
|
||||
final_css = theming_warning + gtk_css
|
||||
|
||||
return final_css
|
|
@ -25,9 +25,12 @@ import shutil
|
|||
import signal
|
||||
import argparse
|
||||
import warnings
|
||||
import locale
|
||||
import gettext
|
||||
|
||||
version = "@VERSION@"
|
||||
is_local = @local_build@
|
||||
localedir = '@LOCALE_DIR@'
|
||||
|
||||
if is_local:
|
||||
# In the local use case, use gradience module from the sourcetree
|
||||
|
@ -37,19 +40,26 @@ if is_local:
|
|||
os.environ["XDG_DATA_DIRS"] = '@SCHEMAS_DIR@:' + os.environ.get("XDG_DATA_DIRS", "")
|
||||
|
||||
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
||||
gettext.install('gradience', localedir)
|
||||
|
||||
locale.bindtextdomain('gradience', localedir)
|
||||
locale.textdomain('gradience')
|
||||
|
||||
warnings.filterwarnings("ignore") # suppress GTK warnings
|
||||
|
||||
from gi.repository import GLib, Gio
|
||||
|
||||
from gradience.backend.utils.gnome import is_gnome_available, is_shell_ext_installed
|
||||
from gradience.backend.utils.common import to_slug_case
|
||||
from gradience.backend.globals import preset_repos, presets_dir
|
||||
|
||||
from gradience.backend.theming.monet import Monet
|
||||
from gradience.backend.models.preset import Preset
|
||||
from gradience.backend.theming.preset_utils import PresetUtils
|
||||
from gradience.backend.theming.shell import ShellTheme
|
||||
from gradience.backend.theming.preset import PresetUtils
|
||||
from gradience.backend.preset_downloader import PresetDownloader
|
||||
from gradience.backend.flatpak_overrides import list_file_access, allow_file_access, disallow_file_access, create_gtk_user_override, remove_gtk_user_override
|
||||
from gradience.backend.flatpak_overrides import (list_file_access, allow_file_access,
|
||||
disallow_file_access, create_gtk_user_override, remove_gtk_user_override)
|
||||
|
||||
from gradience.backend.logger import Logger
|
||||
|
||||
|
@ -82,13 +92,13 @@ class CLI:
|
|||
|
||||
apply_parser = subparsers.add_parser("apply", help="apply a preset")
|
||||
apply_group = apply_parser.add_mutually_exclusive_group(required=True)
|
||||
apply_group.add_argument("-n", "--preset-name", help="display name for a preset")
|
||||
apply_group.add_argument("-p", "--preset-path", help="absolute path to a preset file")
|
||||
apply_group.add_argument("-n", "--preset-name", help="display name of the preset")
|
||||
apply_group.add_argument("-p", "--preset-path", help="absolute path to the preset file")
|
||||
apply_parser.add_argument("--gtk", choices=["gtk4", "gtk3", "both"], default="gtk4", help="types of applications you want to theme (default: gtk4)")
|
||||
|
||||
#new_parser = subparsers.add_parser("new", help="create a new preset")
|
||||
#new_parser.add_argument("-i", "--interactive", action="store_true", help="")
|
||||
#new_parser.add_argument("-n", "--name", help="display name for a preset", required=True)
|
||||
#new_parser.add_argument("-n", "--name", help="display name of the preset", required=True)
|
||||
#new_parser.add_argument("--colors", help="", required=True)
|
||||
#new_parser.add_argument("--palette", help="")
|
||||
#new_parser.add_argument("--custom-css", help="")
|
||||
|
@ -99,6 +109,12 @@ class CLI:
|
|||
download_parser.add_argument("-n", "--preset-name", help="name of a preset you want to get", required=True)
|
||||
#download_parser.add_argument("--custom-url", help="use custom repository's presets.json to download other presets")
|
||||
|
||||
shell_parser = subparsers.add_parser("gnome-shell", help="generate a GNOME Shell theme from any preset")
|
||||
choose_preset_group = shell_parser.add_mutually_exclusive_group(required=True)
|
||||
choose_preset_group.add_argument("-n", "--preset-name", help="display name of the preset")
|
||||
choose_preset_group.add_argument("-p", "--preset-path", help="absolute path to the preset file")
|
||||
shell_parser.add_argument("-v", "--preset-variant", choices=["light", "dark"], help="select which preset variant you use to generate a theme")
|
||||
|
||||
monet_parser = subparsers.add_parser("monet", help="generate Material You preset from an image")
|
||||
#monet_parser.add_argument("-a", "--apply", help="apply Monet's generated preset after it has been created", action='store_true')
|
||||
monet_parser.add_argument("-n", "--preset-name", help="name for a generated preset", required=True)
|
||||
|
@ -150,6 +166,9 @@ class CLI:
|
|||
elif args.command == "download":
|
||||
self.download_preset(args)
|
||||
|
||||
elif args.command == "gnome-shell":
|
||||
self.gnome_shell(args)
|
||||
|
||||
elif args.command == "monet":
|
||||
self.generate_monet(args)
|
||||
|
||||
|
@ -339,6 +358,85 @@ class CLI:
|
|||
continue
|
||||
repo_no += 1
|
||||
|
||||
# TODO: Add support for custom colors
|
||||
def gnome_shell(self, args):
|
||||
_preset_name = args.preset_name
|
||||
_preset_path = args.preset_path
|
||||
_preset_variant = args.preset_variant
|
||||
|
||||
try:
|
||||
presets_list = PresetUtils().get_presets_list(full_list=True)
|
||||
except (OSError, KeyError, AttributeError) as e:
|
||||
logging.error("Failed to retrieve a list of presets.", exc=e)
|
||||
exit(1)
|
||||
|
||||
presets_name = list(presets_list.values())
|
||||
|
||||
def __get_preset_from_name():
|
||||
for path, name in presets_list.items():
|
||||
if name == _preset_name:
|
||||
preset = Preset().new_from_path(path)
|
||||
return preset
|
||||
|
||||
if _preset_name:
|
||||
if _preset_name in presets_name:
|
||||
preset = __get_preset_from_name()
|
||||
else:
|
||||
logging.error(f"Failed to find preset named {_preset_name}. Verify if you wrote the name right with `presets` command.")
|
||||
exit(1)
|
||||
elif _preset_path:
|
||||
try:
|
||||
preset = Preset().new_from_path(_preset_path)
|
||||
except OSError as e:
|
||||
exit(1)
|
||||
|
||||
if not is_gnome_available():
|
||||
logging.warning("Shell Engine is designed to work only on systems running GNOME. You can still generate themes on other desktop environments, but it won't have any affect on them.")
|
||||
prompt = input("Do you want to continue? [N/y] ")
|
||||
|
||||
if prompt.lower() == "y":
|
||||
pass
|
||||
elif prompt.lower() == "n" or prompt == "":
|
||||
logging.info("Aborting all operations...")
|
||||
exit(0)
|
||||
else:
|
||||
logging.info("Aborting all operations...")
|
||||
exit(0)
|
||||
|
||||
shell_engine = ShellTheme()
|
||||
|
||||
is_user_themes_available = is_shell_ext_installed(shell_engine.THEME_EXT_NAME)
|
||||
is_user_themes_enabled = is_shell_ext_installed(shell_engine.THEME_EXT_NAME, check_enabled=True)
|
||||
|
||||
if not is_user_themes_available:
|
||||
logging.warning("Gradience requires User Themes extension installed in order to apply Shell theme. You can still generate a theme, but you won't be able to apply it without this extension.")
|
||||
prompt = input("Do you want to continue? [N/y] ")
|
||||
|
||||
if prompt.lower() == "y":
|
||||
pass
|
||||
elif prompt.lower() == "n" or prompt == "":
|
||||
logging.info("Aborting all operations...")
|
||||
exit(0)
|
||||
else:
|
||||
logging.info("Aborting all operations...")
|
||||
exit(0)
|
||||
elif not is_user_themes_enabled:
|
||||
logging.warning("User Themes extension is currently disabled on your system. Please enable it in order to apply theme.")
|
||||
prompt = input("Do you want to continue? [N/y] ")
|
||||
|
||||
if prompt.lower() == "y":
|
||||
pass
|
||||
elif prompt.lower() == "n" or prompt == "":
|
||||
logging.info("Aborting all operations...")
|
||||
exit(0)
|
||||
else:
|
||||
logging.info("Aborting all operations...")
|
||||
exit(0)
|
||||
|
||||
shell_engine.apply_theme(None, _preset_variant, preset)
|
||||
logging.info("GNOME Shell theme generated successfully.")
|
||||
exit(0)
|
||||
|
||||
# NOTE: Possible useful portals to use in future: org.freedesktop.portal.Documents \
|
||||
# (support missing in libportal, only D-Bus calls), org.freedesktop.portal.FileChooser
|
||||
def generate_monet(self, args):
|
||||
|
@ -350,7 +448,7 @@ class CLI:
|
|||
_json = args.json
|
||||
|
||||
try:
|
||||
palette = Monet().generate_from_image(_image_path)
|
||||
palette = Monet().generate_palette_from_image(_image_path)
|
||||
except (OSError, ValueError) as e:
|
||||
logging.info("If you are getting an `no such file or directory` error on Gradience installed as Flatpak, "
|
||||
"try adding the file to the access list by using `gradience-cli access-file --allow 'path/to/file'` command.")
|
||||
|
@ -360,8 +458,8 @@ class CLI:
|
|||
|
||||
if _json:
|
||||
try:
|
||||
preset = PresetUtils().new_preset_from_monet(_preset_name,
|
||||
palette, props, True)
|
||||
preset = Monet().new_preset_from_monet(_preset_name,
|
||||
palette, props, True)
|
||||
except (OSError, AttributeError) as e:
|
||||
logging.error("Unexpected error while generating preset from Monet palette.", exc=e)
|
||||
exit(1)
|
||||
|
@ -371,8 +469,7 @@ class CLI:
|
|||
exit(0)
|
||||
|
||||
try:
|
||||
PresetUtils().new_preset_from_monet(_preset_name, palette, props)
|
||||
#raise OSError()
|
||||
Monet().new_preset_from_monet(_preset_name, palette, props)
|
||||
except (OSError, AttributeError) as e:
|
||||
logging.error("Unexpected error while generating preset from Monet palette.", exc=e)
|
||||
exit(1)
|
||||
|
|
|
@ -28,22 +28,18 @@ class GradienceAppTypeDialog(Adw.MessageDialog):
|
|||
gtk4_app_type = Gtk.Template.Child("gtk4-app-type")
|
||||
gtk3_app_type = Gtk.Template.Child("gtk3-app-type")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
parent,
|
||||
heading,
|
||||
body,
|
||||
ok_res_name,
|
||||
ok_res_label,
|
||||
ok_res_appearance,
|
||||
**kwargs
|
||||
):
|
||||
def __init__(self, parent, heading, body, ok_res_name, ok_res_label, ok_res_appearance, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.parent = parent
|
||||
self.app = self.parent.get_application()
|
||||
|
||||
self.set_transient_for(self.app.get_active_window())
|
||||
if isinstance(self.parent, Gtk.Window):
|
||||
self.win = self.parent
|
||||
else:
|
||||
self.win = self.app.get_active_window()
|
||||
|
||||
self.set_transient_for(self.win)
|
||||
|
||||
self.set_heading(heading)
|
||||
self.set_body(body)
|
||||
|
|
|
@ -31,7 +31,12 @@ class GradienceLogOutDialog(Adw.MessageDialog):
|
|||
self.parent = parent
|
||||
self.app = self.parent.get_application()
|
||||
|
||||
self.set_transient_for(self.app.get_active_window())
|
||||
if isinstance(self.parent, Gtk.Window):
|
||||
self.win = self.parent
|
||||
else:
|
||||
self.win = self.app.get_active_window()
|
||||
|
||||
self.set_transient_for(self.win)
|
||||
|
||||
self.add_response("ok", _("OK"))
|
||||
self.set_default_response("ok")
|
||||
|
|
|
@ -4,6 +4,7 @@ gradience_sources = [
|
|||
'__init__.py',
|
||||
'app_type_dialog.py',
|
||||
'log_out_dialog.py',
|
||||
'save_dialog.py'
|
||||
'save_dialog.py',
|
||||
'unsupported_shell_dialog.py'
|
||||
]
|
||||
PY_INSTALLDIR.install_sources(gradience_sources, subdir: dialogsdir)
|
||||
|
|
|
@ -37,10 +37,15 @@ class GradienceSaveDialog(Adw.MessageDialog):
|
|||
|
||||
self.body = _(
|
||||
"Saving preset to <tt>{0}</tt>. If that preset already "
|
||||
"exists, it will be overwritten!"
|
||||
"exists, it will be overwritten."
|
||||
)
|
||||
|
||||
self.set_transient_for(self.app.get_active_window())
|
||||
if isinstance(self.parent, Gtk.Window):
|
||||
self.win = self.parent
|
||||
else:
|
||||
self.win = self.app.get_active_window()
|
||||
|
||||
self.set_transient_for(self.win)
|
||||
|
||||
if heading:
|
||||
self.heading = heading
|
||||
|
|
45
gradience/frontend/dialogs/unsupported_shell_dialog.py
Normal file
45
gradience/frontend/dialogs/unsupported_shell_dialog.py
Normal file
|
@ -0,0 +1,45 @@
|
|||
# unsupported_shell_version.py
|
||||
#
|
||||
# Change the look of Adwaita, with ease
|
||||
# Copyright (C) 2022-2023, Gradience Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
from gi.repository import Gtk, Adw
|
||||
|
||||
from gradience.backend.constants import rootdir
|
||||
from gradience.backend.utils.gnome import get_full_shell_version
|
||||
|
||||
class GradienceUnsupportedShellDialog(Adw.MessageDialog):
|
||||
__gtype_name__ = "GradienceUnsupportedShellDialog"
|
||||
|
||||
def __init__(self, parent, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.parent = parent
|
||||
self.app = self.parent.get_application()
|
||||
|
||||
if isinstance(self.parent, Gtk.Window):
|
||||
self.win = self.parent
|
||||
else:
|
||||
self.win = self.app.get_active_window()
|
||||
|
||||
self.set_transient_for(self.win)
|
||||
|
||||
self.set_heading(_(f"Unsupported Shell Version ({get_full_shell_version()})"))
|
||||
self.set_body(_("The Shell version you are using is not supported by Gradience. Please upgrade to a newer version of GNOME."))
|
||||
|
||||
self.add_response("ok", _("OK"))
|
||||
self.set_default_response("ok")
|
||||
self.set_close_response("ok")
|
|
@ -22,13 +22,15 @@ import threading
|
|||
|
||||
from pathlib import Path
|
||||
from material_color_utilities_python import hexFromArgb
|
||||
from gi.repository import Gtk, Gdk, Gio, Adw, GLib
|
||||
from gi.repository import GObject, Gtk, Gdk, Gio, Adw, GLib
|
||||
|
||||
from gradience.backend.globals import presets_dir, get_gtk_theme_dir
|
||||
from gradience.backend.css_parser import parse_css
|
||||
from gradience.backend.models.preset import Preset
|
||||
from gradience.backend.theming.preset_utils import PresetUtils
|
||||
from gradience.backend.theming.preset import PresetUtils
|
||||
from gradience.backend.theming.monet import Monet
|
||||
from gradience.backend.utils.common import to_slug_case
|
||||
from gradience.backend.utils.theming import generate_gtk_css
|
||||
from gradience.backend.constants import rootdir, app_id, rel_ver
|
||||
|
||||
from gradience.frontend.views.main_window import GradienceMainWindow
|
||||
|
@ -99,6 +101,8 @@ class GradienceApplication(Adw.Application):
|
|||
|
||||
self.win = self.props.active_window
|
||||
|
||||
self.setup_signals()
|
||||
|
||||
if not self.win:
|
||||
self.win = GradienceMainWindow(
|
||||
application=self,
|
||||
|
@ -126,15 +130,9 @@ class GradienceApplication(Adw.Application):
|
|||
self.actions.create_action("apply_color_scheme",
|
||||
self.show_apply_color_scheme_dialog)
|
||||
|
||||
self.actions.create_action("restore_color_scheme",
|
||||
self.show_restore_color_scheme_dialog)
|
||||
|
||||
self.actions.create_action("manage_presets",
|
||||
self.show_presets_manager)
|
||||
|
||||
self.actions.create_action("reset_color_scheme",
|
||||
self.show_reset_color_scheme_dialog)
|
||||
|
||||
self.actions.create_action("preferences",
|
||||
self.show_preferences)
|
||||
|
||||
|
@ -145,7 +143,6 @@ class GradienceApplication(Adw.Application):
|
|||
self.show_about_window)
|
||||
|
||||
self.load_preset_from_css()
|
||||
|
||||
self.reload_user_defined_presets()
|
||||
|
||||
if self.first_run:
|
||||
|
@ -159,6 +156,16 @@ class GradienceApplication(Adw.Application):
|
|||
logging.debug("normal run")
|
||||
self.win.present()
|
||||
|
||||
def setup_signals(self):
|
||||
# Custom signals
|
||||
GObject.signal_new(
|
||||
"preset-reload",
|
||||
self,
|
||||
GObject.SignalFlags.RUN_LAST,
|
||||
bool,
|
||||
(object,)
|
||||
)
|
||||
|
||||
def save_favourite(self):
|
||||
self.settings.set_value(
|
||||
"favourite", GLib.Variant("as", self.favourite))
|
||||
|
@ -368,7 +375,7 @@ class GradienceApplication(Adw.Application):
|
|||
preset_variant = "light"
|
||||
|
||||
try:
|
||||
preset_object = PresetUtils().new_preset_from_monet(monet_palette=monet,
|
||||
preset_object = Monet().new_preset_from_monet(monet_palette=monet,
|
||||
props=[tone, preset_variant], obj_only=True)
|
||||
except (OSError, AttributeError) as e:
|
||||
logging.error("An error occurred while generating preset from Monet palette.", exc=e)
|
||||
|
@ -410,7 +417,7 @@ class GradienceApplication(Adw.Application):
|
|||
|
||||
def reload_variables(self):
|
||||
parsing_errors = []
|
||||
gtk_css = PresetUtils().generate_gtk_css("gtk4", self.preset)
|
||||
gtk_css = generate_gtk_css("gtk4", self.preset)
|
||||
css_provider = Gtk.CssProvider()
|
||||
|
||||
def on_error(_, section, error):
|
||||
|
@ -455,6 +462,7 @@ class GradienceApplication(Adw.Application):
|
|||
)
|
||||
self.current_css_provider = css_provider
|
||||
|
||||
self.emit("preset-reload", object())
|
||||
self.is_ready = True
|
||||
|
||||
def load_preset_action(self, _unused, *args):
|
||||
|
@ -507,33 +515,6 @@ class GradienceApplication(Adw.Application):
|
|||
dialog.connect("response", self.apply_color_scheme)
|
||||
dialog.present()
|
||||
|
||||
def show_restore_color_scheme_dialog(self, *_args):
|
||||
dialog = GradienceAppTypeDialog(
|
||||
self.win,
|
||||
_("Restore applied color scheme?"),
|
||||
_("Make sure you have the current settings saved as a preset."),
|
||||
"restore",
|
||||
_("_Restore"),
|
||||
Adw.ResponseAppearance.DESTRUCTIVE
|
||||
)
|
||||
|
||||
dialog.gtk3_app_type.set_sensitive(False)
|
||||
dialog.connect("response", self.restore_color_scheme)
|
||||
dialog.present()
|
||||
|
||||
def show_reset_color_scheme_dialog(self, *_args):
|
||||
dialog = GradienceAppTypeDialog(
|
||||
self.win,
|
||||
_("Reset applied color scheme?"),
|
||||
_("Make sure you have the current settings saved as a preset."),
|
||||
"reset",
|
||||
_("_Reset"),
|
||||
Adw.ResponseAppearance.DESTRUCTIVE
|
||||
)
|
||||
|
||||
dialog.connect("response", self.reset_color_scheme)
|
||||
dialog.present()
|
||||
|
||||
def show_save_preset_dialog(self, *_args):
|
||||
dialog = GradienceSaveDialog(self.win, path=os.path.join(
|
||||
presets_dir,
|
||||
|
@ -627,40 +608,6 @@ class GradienceApplication(Adw.Application):
|
|||
dialog = GradienceLogOutDialog(self.win)
|
||||
dialog.present()
|
||||
|
||||
def restore_color_scheme(self, widget, response):
|
||||
if response == "restore":
|
||||
if widget.get_app_types()["gtk4"]:
|
||||
try:
|
||||
PresetUtils().restore_gtk4_preset()
|
||||
except OSError:
|
||||
self.win.toast_overlay.add_toast(
|
||||
Adw.Toast(title=_("Unable to restore GTK 4 backup"))
|
||||
)
|
||||
|
||||
dialog = GradienceLogOutDialog(self.win)
|
||||
dialog.present()
|
||||
|
||||
def reset_color_scheme(self, widget, response):
|
||||
if response == "reset":
|
||||
if widget.get_app_types()["gtk4"]:
|
||||
try:
|
||||
PresetUtils().reset_preset("gtk4")
|
||||
except GLib.GError:
|
||||
self.win.toast_overlay.add_toast(
|
||||
Adw.Toast(title=_("Unable to delete current preset"))
|
||||
)
|
||||
|
||||
if widget.get_app_types()["gtk3"]:
|
||||
try:
|
||||
PresetUtils().reset_preset("gtk3")
|
||||
except GLib.GError:
|
||||
self.win.toast_overlay.add_toast(
|
||||
Adw.Toast(title=_("Unable to delete current preset"))
|
||||
)
|
||||
|
||||
dialog = GradienceLogOutDialog(self.win)
|
||||
dialog.present()
|
||||
|
||||
def show_preferences(self, *_args):
|
||||
prefs = GradiencePreferencesWindow(self.win)
|
||||
prefs.present()
|
||||
|
|
|
@ -2,6 +2,7 @@ schemasdir = 'gradience/frontend/schemas'
|
|||
|
||||
gradience_sources = [
|
||||
'__init__.py',
|
||||
'preset_schema.py'
|
||||
'preset_schema.py',
|
||||
'shell_schema.py'
|
||||
]
|
||||
PY_INSTALLDIR.install_sources(gradience_sources, subdir: schemasdir)
|
||||
|
|
|
@ -299,6 +299,23 @@ preset_schema = {
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "thumbnail_colors",
|
||||
"title": _("Thumbnail Colors"),
|
||||
"description": _("These colors are used for Tab Overview thumbnails."),
|
||||
"variables": [
|
||||
{
|
||||
"name": "thumbnail_bg_color",
|
||||
"title": _("Background Color"),
|
||||
"adw_gtk3_support": "yes",
|
||||
},
|
||||
{
|
||||
"name": "thumbnail_fg_color",
|
||||
"title": _("Foreground Color"),
|
||||
"adw_gtk3_support": "yes",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "dialog_colors",
|
||||
"title": _("Dialog Colors"),
|
||||
|
@ -372,5 +389,5 @@ preset_schema = {
|
|||
{"prefix": "light_", "title": _("Light"), "n_shades": 5},
|
||||
{"prefix": "dark_", "title": _("Dark"), "n_shades": 5},
|
||||
],
|
||||
"custom_css_app_types": ["gtk4", "gtk3"],
|
||||
"custom_css_app_types": ["gtk4", "gtk3"]
|
||||
}
|
||||
|
|
64
gradience/frontend/schemas/shell_schema.py
Normal file
64
gradience/frontend/schemas/shell_schema.py
Normal file
|
@ -0,0 +1,64 @@
|
|||
# shell_schema.py
|
||||
#
|
||||
# Change the look of Adwaita, with ease
|
||||
# Copyright (C) 2023, Gradience Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
shell_schema = {
|
||||
"variables": [
|
||||
{
|
||||
"name": "bg_color",
|
||||
"var_name": "window_bg_color",
|
||||
"title": _("Base Background Color")
|
||||
},
|
||||
{
|
||||
"name": "fg_color",
|
||||
"var_name": "window_fg_color",
|
||||
"title": _("Base Foreground Color")
|
||||
},
|
||||
{
|
||||
"name": "system_bg_color",
|
||||
"var_name": "window_bg_color",
|
||||
"title": _("Overview Background Color")
|
||||
},
|
||||
{
|
||||
"name": "selected_bg_color",
|
||||
"var_name": "accent_bg_color",
|
||||
"title": _("Accent Background Color")
|
||||
},
|
||||
{
|
||||
"name": "selected_fg_color",
|
||||
"var_name": "window_fg_color",
|
||||
"title": _("Accent Foreground Color")
|
||||
},
|
||||
# TODO: Fix panel background color injection code
|
||||
#{
|
||||
# "name": "panel_bg_color",
|
||||
# "var_name": "panel_bg_color",
|
||||
# "title": _("Panel Background Color"),
|
||||
# "default_value": "#000"
|
||||
#},
|
||||
#{
|
||||
# "name": "osd_bg_color",
|
||||
# "var_name": "window_bg_color",
|
||||
# "title": _("OSD Background Color")
|
||||
#},
|
||||
{
|
||||
"name": "osd_fg_color",
|
||||
"var_name": "window_fg_color",
|
||||
"title": _("OSD Foreground Color")
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
# main_window.py
|
||||
#
|
||||
# Change the look of Adwaita, with ease
|
||||
# Copyright (C) 2022 Gradience Team
|
||||
# Copyright (C) 2022-2023, Gradience Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -20,12 +20,14 @@ from enum import Enum
|
|||
|
||||
from gi.repository import Gtk, Adw, Gio
|
||||
|
||||
from gradience.backend.theming.monet import Monet
|
||||
from gradience.backend.constants import rootdir, app_id, build_type
|
||||
|
||||
from gradience.frontend.widgets.error_list_row import GradienceErrorListRow
|
||||
from gradience.frontend.widgets.shell_theming_group import GradienceShellThemingGroup
|
||||
from gradience.frontend.widgets.monet_theming_group import GradienceMonetThemingGroup
|
||||
from gradience.frontend.widgets.palette_shades import GradiencePaletteShades
|
||||
from gradience.frontend.widgets.error_list_row import GradienceErrorListRow
|
||||
from gradience.frontend.widgets.option_row import GradienceOptionRow
|
||||
from gradience.frontend.widgets.theming_empty_group import GradienceEmptyThemingGroup
|
||||
from gradience.frontend.schemas.preset_schema import preset_schema
|
||||
|
||||
from gradience.backend.logger import Logger
|
||||
|
@ -37,17 +39,18 @@ logging = Logger()
|
|||
class GradienceMainWindow(Adw.ApplicationWindow):
|
||||
__gtype_name__ = "GradienceMainWindow"
|
||||
|
||||
content = Gtk.Template.Child()
|
||||
content_colors = Gtk.Template.Child("content-colors")
|
||||
content_theming = Gtk.Template.Child("content-theming")
|
||||
content_plugins = Gtk.Template.Child("content-plugins")
|
||||
|
||||
toast_overlay = Gtk.Template.Child()
|
||||
content_monet = Gtk.Template.Child("content_monet")
|
||||
content_plugins = Gtk.Template.Child("content_plugins")
|
||||
|
||||
save_preset_button = Gtk.Template.Child("save-preset-button")
|
||||
main_menu = Gtk.Template.Child("main-menu")
|
||||
errors_button = Gtk.Template.Child("errors-button")
|
||||
|
||||
errors_list = Gtk.Template.Child("errors-list")
|
||||
presets_dropdown = Gtk.Template.Child("presets-dropdown")
|
||||
presets_menu = Gtk.Template.Child("presets-menu")
|
||||
monet_image_file = None
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
@ -55,21 +58,34 @@ class GradienceMainWindow(Adw.ApplicationWindow):
|
|||
self.app = Gtk.Application.get_default()
|
||||
self.settings = Gio.Settings(app_id)
|
||||
|
||||
self.presets_dropdown.get_popover().connect(
|
||||
"show", self.on_presets_dropdown_activate
|
||||
self.style_manager = self.app.style_manager
|
||||
|
||||
self.monet_image_file = None
|
||||
|
||||
self.enabled_theme_engines = set(
|
||||
self.settings.get_value("enabled-theme-engines").unpack()
|
||||
)
|
||||
|
||||
self.setup_signals()
|
||||
self.setup()
|
||||
|
||||
def setup_signals(self):
|
||||
self.presets_dropdown.get_popover().connect("show",
|
||||
self.on_presets_dropdown_activate)
|
||||
|
||||
self.connect("close-request",
|
||||
self.on_close_request)
|
||||
|
||||
self.connect("unrealize",
|
||||
self.save_window_props)
|
||||
|
||||
def setup(self):
|
||||
# Set devel style
|
||||
if build_type == "debug":
|
||||
self.get_style_context().add_class("devel")
|
||||
|
||||
self.setup_monet_page()
|
||||
self.setup_colors_page()
|
||||
|
||||
self.connect("close-request", self.on_close_request)
|
||||
self.connect("unrealize", self.save_window_props)
|
||||
|
||||
self.style_manager = self.app.style_manager
|
||||
self.setup_theming_page()
|
||||
self.setup_colors_group()
|
||||
|
||||
# TODO: Check if org.freedesktop.portal.Settings portal will allow us to \
|
||||
# read org.gnome.desktop.background DConf key
|
||||
|
@ -88,15 +104,12 @@ class GradienceMainWindow(Adw.ApplicationWindow):
|
|||
image_basename = self.monet_image_file.get_basename()
|
||||
logging.debug(image_basename)
|
||||
self.monet_image_file = self.monet_image_file.get_path()
|
||||
self.monet_file_chooser_button.set_label(image_basename)
|
||||
self.monet_file_chooser_button.set_tooltip_text(self.monet_image_file)
|
||||
#self.monet_file_chooser_button.set_label(image_basename)
|
||||
#self.monet_file_chooser_button.set_tooltip_text(self.monet_image_file)
|
||||
logging.debug(self.monet_image_file)
|
||||
# self.on_apply_button() # Comment out for now, because it always shows
|
||||
# self.on_apply_button_clicked() # Comment out for now, because it always shows
|
||||
# that annoying toast on startup'''
|
||||
|
||||
def on_file_picker_button_clicked(self, *args):
|
||||
self.monet_file_chooser_dialog.show()
|
||||
|
||||
def on_close_request(self, *args):
|
||||
if self.app.is_dirty:
|
||||
logging.debug("Window close request")
|
||||
|
@ -113,143 +126,47 @@ class GradienceMainWindow(Adw.ApplicationWindow):
|
|||
self.settings.set_boolean("window-maximized", self.is_maximized())
|
||||
self.settings.set_boolean("window-fullscreen", self.is_fullscreen())
|
||||
|
||||
def on_monet_file_chooser_response(self, widget, response):
|
||||
if response == Gtk.ResponseType.ACCEPT:
|
||||
self.monet_image_file = self.monet_file_chooser_dialog.get_file()
|
||||
image_basename = self.monet_image_file.get_basename()
|
||||
self.monet_file_chooser_button.set_label(image_basename)
|
||||
self.monet_file_chooser_button.set_tooltip_text(image_basename)
|
||||
def setup_theming_page(self):
|
||||
# TODO: Show fallback page if no theme engines are enabled
|
||||
no_engines_label = Gtk.Label.new("No Theme Engines enabled")
|
||||
|
||||
self.monet_file_chooser_dialog.hide()
|
||||
self.setup_empty_page()
|
||||
self.setup_shell_group()
|
||||
self.setup_monet_group()
|
||||
|
||||
if response == Gtk.ResponseType.ACCEPT:
|
||||
self.monet_image_file = self.monet_image_file.get_path()
|
||||
self.on_apply_button()
|
||||
def setup_empty_page(self):
|
||||
self.empty_page = GradienceEmptyThemingGroup(self)
|
||||
|
||||
def setup_monet_page(self):
|
||||
self.monet_pref_group = Adw.PreferencesGroup()
|
||||
self.monet_pref_group.set_name("monet")
|
||||
self.monet_pref_group.set_title(_("Monet Engine"))
|
||||
self.monet_pref_group.set_description(
|
||||
_(
|
||||
"Monet is an engine that generates a Material Design 3 "
|
||||
"palette from an image's color."
|
||||
)
|
||||
)
|
||||
if not self.enabled_theme_engines:
|
||||
self.content_theming.add(self.empty_page)
|
||||
|
||||
self.apply_button = Gtk.Button()
|
||||
self.apply_button.set_label(_("Apply"))
|
||||
self.apply_button.set_valign(Gtk.Align.CENTER)
|
||||
self.apply_button.connect("clicked", self.on_apply_button)
|
||||
self.apply_button.set_css_classes("suggested-action")
|
||||
def setup_shell_group(self):
|
||||
self.shell_group = GradienceShellThemingGroup(self)
|
||||
|
||||
self.monet_pref_group.set_header_suffix(self.apply_button)
|
||||
if "shell" in self.enabled_theme_engines:
|
||||
self.content_theming.add(self.shell_group)
|
||||
|
||||
self.monet_file_chooser_row = Adw.ActionRow()
|
||||
self.monet_file_chooser_row.set_title(_("Select an Image"))
|
||||
def setup_monet_group(self):
|
||||
self.monet_group = GradienceMonetThemingGroup(self)
|
||||
|
||||
self.monet_file_chooser_dialog = Gtk.FileChooserNative()
|
||||
self.monet_file_chooser_dialog.set_title(_("Choose a Image File"))
|
||||
self.monet_file_chooser_dialog.set_transient_for(self)
|
||||
self.monet_file_chooser_dialog.set_modal(True)
|
||||
if "monet" in self.enabled_theme_engines:
|
||||
self.content_theming.add(self.monet_group)
|
||||
|
||||
self.monet_file_chooser_button = Gtk.Button()
|
||||
self.monet_file_chooser_button.set_valign(Gtk.Align.CENTER)
|
||||
def reload_theming_page(self):
|
||||
if self.shell_group.is_ancestor(self.content_theming):
|
||||
self.content_theming.remove(self.shell_group)
|
||||
|
||||
child_button = Gtk.Box()
|
||||
label = Gtk.Label()
|
||||
label.set_label(_("Choose a File"))
|
||||
child_button.append(label)
|
||||
if self.monet_group.is_ancestor(self.content_theming):
|
||||
self.content_theming.remove(self.monet_group)
|
||||
|
||||
icon = Gtk.Image()
|
||||
icon.set_from_icon_name("folder-pictures-symbolic")
|
||||
child_button.append(icon)
|
||||
child_button.set_spacing(10)
|
||||
if self.empty_page.is_ancestor(self.content_theming):
|
||||
self.content_theming.remove(self.empty_page)
|
||||
|
||||
self.monet_file_chooser_button.set_child(child_button)
|
||||
self.setup_shell_group()
|
||||
self.setup_monet_group()
|
||||
self.setup_empty_page()
|
||||
|
||||
self.monet_file_chooser_button.connect(
|
||||
"clicked", self.on_file_picker_button_clicked
|
||||
)
|
||||
|
||||
self.monet_file_chooser_dialog.connect(
|
||||
"response", self.on_monet_file_chooser_response
|
||||
)
|
||||
|
||||
self.monet_file_chooser_row.add_suffix(self.monet_file_chooser_button)
|
||||
self.monet_pref_group.add(self.monet_file_chooser_row)
|
||||
|
||||
self.monet_palette_shades = GradiencePaletteShades(
|
||||
"monet", _("Monet Palette"), 6
|
||||
)
|
||||
self.app.pref_palette_shades["monet"] = self.monet_palette_shades
|
||||
self.monet_pref_group.add(self.monet_palette_shades)
|
||||
|
||||
# FIXME: Comment out for now
|
||||
'''self.tone_row = Adw.ComboRow()
|
||||
self.tone_row.set_title(_("Tone"))
|
||||
|
||||
store = Gtk.StringList()
|
||||
store_values = []
|
||||
for i in range(20, 80, 5):
|
||||
store_values.append(str(i))
|
||||
for v in store_values:
|
||||
store.append(v)
|
||||
self.tone_row.set_model(store)
|
||||
self.monet_pref_group.add(self.tone_row)'''
|
||||
|
||||
self.monet_theme_row = Adw.ComboRow()
|
||||
self.monet_theme_row.set_title(_("Theme"))
|
||||
|
||||
store = Gtk.StringList()
|
||||
store.append(_("Auto"))
|
||||
store.append(_("Light"))
|
||||
store.append(_("Dark"))
|
||||
self.monet_theme_row.set_model(store)
|
||||
self.monet_pref_group.add(self.monet_theme_row)
|
||||
|
||||
self.content_monet.add(self.monet_pref_group)
|
||||
|
||||
def on_apply_button(self, *_args):
|
||||
if self.monet_image_file:
|
||||
try:
|
||||
self.theme = Monet().generate_from_image(self.monet_image_file)
|
||||
#self.tone = self.tone_row.get_selected_item() # TODO: Remove tone requirement from Monet Engine
|
||||
variant_pos = self.monet_theme_row.props.selected
|
||||
|
||||
class variantEnum(Enum):
|
||||
AUTO = 0
|
||||
LIGHT = 1
|
||||
DARK = 2
|
||||
|
||||
def __get_variant_string():
|
||||
if variant_pos == variantEnum.AUTO.value:
|
||||
return "auto"
|
||||
elif variant_pos == variantEnum.DARK.value:
|
||||
return "dark"
|
||||
elif variant_pos == variantEnum.LIGHT.value:
|
||||
return "light"
|
||||
|
||||
variant_str = __get_variant_string()
|
||||
|
||||
self.app.custom_css_group.reset_buffer()
|
||||
|
||||
self.app.update_theme_from_monet(self.theme, variant_str)
|
||||
except (OSError, AttributeError, ValueError) as e:
|
||||
logging.error("Failed to generate Monet palette.", exc=e)
|
||||
self.toast_overlay.add_toast(
|
||||
Adw.Toast(title=_("Failed to generate Monet palette"))
|
||||
)
|
||||
else:
|
||||
self.toast_overlay.add_toast(
|
||||
Adw.Toast(title=_("Palette generated"))
|
||||
)
|
||||
else:
|
||||
self.toast_overlay.add_toast(
|
||||
Adw.Toast(title=_("Select a background first"))
|
||||
)
|
||||
|
||||
def setup_colors_page(self):
|
||||
def setup_colors_group(self):
|
||||
for group in preset_schema["groups"]:
|
||||
pref_group = Adw.PreferencesGroup()
|
||||
pref_group.set_name(group["name"])
|
||||
|
@ -264,9 +181,11 @@ class GradienceMainWindow(Adw.ApplicationWindow):
|
|||
variable["adw_gtk3_support"],
|
||||
)
|
||||
pref_group.add(pref_variable)
|
||||
|
||||
pref_variable.connect_signals(update_vars=True)
|
||||
self.app.pref_variables[variable["name"]] = pref_variable
|
||||
|
||||
self.content.add(pref_group)
|
||||
self.content_colors.add(pref_group)
|
||||
|
||||
palette_pref_group = Adw.PreferencesGroup()
|
||||
palette_pref_group.set_name("palette_colors")
|
||||
|
@ -285,7 +204,7 @@ class GradienceMainWindow(Adw.ApplicationWindow):
|
|||
)
|
||||
palette_pref_group.add(palette_shades)
|
||||
self.app.pref_palette_shades[color["prefix"]] = palette_shades
|
||||
self.content.add(palette_pref_group)
|
||||
self.content_colors.add(palette_pref_group)
|
||||
|
||||
def update_errors(self, errors):
|
||||
child = self.errors_list.get_row_at_index(0)
|
||||
|
|
|
@ -9,6 +9,7 @@ gradience_sources = [
|
|||
'preferences_window.py',
|
||||
'presets_manager_window.py',
|
||||
'share_window.py',
|
||||
'shell_prefs_window.py',
|
||||
'welcome_window.py'
|
||||
]
|
||||
PY_INSTALLDIR.install_sources(gradience_sources, subdir: viewsdir)
|
||||
|
|
|
@ -16,11 +16,14 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
from gi.repository import Gtk, Adw
|
||||
from gi.repository import GLib, Gtk, Adw
|
||||
|
||||
from gradience.backend.flatpak_overrides import create_gtk_user_override, remove_gtk_user_override
|
||||
from gradience.backend.flatpak_overrides import create_gtk_global_override, remove_gtk_global_override
|
||||
|
||||
from gradience.frontend.widgets.reset_preset_group import GradienceResetPresetGroup
|
||||
from gradience.frontend.views.main_window import GradienceMainWindow
|
||||
|
||||
from gradience.backend.constants import rootdir
|
||||
|
||||
from gradience.backend.logger import Logger
|
||||
|
@ -32,12 +35,18 @@ logging = Logger()
|
|||
class GradiencePreferencesWindow(Adw.PreferencesWindow):
|
||||
__gtype_name__ = "GradiencePreferencesWindow"
|
||||
|
||||
general_page = Gtk.Template.Child()
|
||||
theming_page = Gtk.Template.Child()
|
||||
|
||||
gtk4_user_theming_switch = Gtk.Template.Child()
|
||||
gtk4_global_theming_switch = Gtk.Template.Child()
|
||||
|
||||
gtk3_user_theming_switch = Gtk.Template.Child()
|
||||
gtk3_global_theming_switch = Gtk.Template.Child()
|
||||
|
||||
monet_engine_switch = Gtk.Template.Child()
|
||||
gnome_shell_engine_switch = Gtk.Template.Child()
|
||||
|
||||
def __init__(self, parent, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
@ -53,6 +62,32 @@ class GradiencePreferencesWindow(Adw.PreferencesWindow):
|
|||
|
||||
def setup(self):
|
||||
self.setup_flatpak_group()
|
||||
self.setup_theme_engines_group()
|
||||
self.setup_reset_preset_group()
|
||||
|
||||
def setup_reset_preset_group(self):
|
||||
self.reset_preset_group = GradienceResetPresetGroup(self)
|
||||
|
||||
self.theming_page.add(self.reset_preset_group)
|
||||
|
||||
def setup_theme_engines_group(self):
|
||||
if "shell" in self.win.enabled_theme_engines:
|
||||
self.gnome_shell_engine_switch.set_state(True)
|
||||
else:
|
||||
self.gnome_shell_engine_switch.set_state(False)
|
||||
|
||||
if "monet" in self.win.enabled_theme_engines:
|
||||
self.monet_engine_switch.set_state(True)
|
||||
else:
|
||||
self.monet_engine_switch.set_state(False)
|
||||
|
||||
self.gnome_shell_engine_switch.connect(
|
||||
"state-set", self.on_gnome_shell_engine_switch_toggled
|
||||
)
|
||||
|
||||
self.monet_engine_switch.connect(
|
||||
"state-set", self.on_monet_engine_switch_toggled
|
||||
)
|
||||
|
||||
def setup_flatpak_group(self):
|
||||
user_flatpak_theming_gtk4 = self.settings.get_boolean(
|
||||
|
@ -72,7 +107,7 @@ class GradiencePreferencesWindow(Adw.PreferencesWindow):
|
|||
self.gtk3_user_theming_switch.set_state(
|
||||
user_flatpak_theming_gtk3
|
||||
)
|
||||
|
||||
|
||||
# self.gtk3_global_theming_switch.set_state(global_flatpak_theming_gtk3)
|
||||
|
||||
self.gtk4_user_theming_switch.connect(
|
||||
|
@ -130,3 +165,37 @@ class GradiencePreferencesWindow(Adw.PreferencesWindow):
|
|||
logging.debug(
|
||||
f"global-flatpak-theming-gtk3: {self.settings.get_boolean('global-flatpak-theming-gtk3')}"
|
||||
)
|
||||
|
||||
def on_gnome_shell_engine_switch_toggled(self, *args):
|
||||
state = self.gnome_shell_engine_switch.props.state
|
||||
|
||||
if not state:
|
||||
self.win.enabled_theme_engines.add("shell")
|
||||
else:
|
||||
self.win.enabled_theme_engines.remove("shell")
|
||||
|
||||
enabled_engines = GLib.Variant.new_strv(list(self.win.enabled_theme_engines))
|
||||
self.settings.set_value("enabled-theme-engines", enabled_engines)
|
||||
|
||||
self.win.reload_theming_page()
|
||||
|
||||
logging.debug(
|
||||
f"enabled-theme-engines: {self.settings.get_value('enabled-theme-engines')}"
|
||||
)
|
||||
|
||||
def on_monet_engine_switch_toggled(self, *args):
|
||||
state = self.monet_engine_switch.props.state
|
||||
|
||||
if not state:
|
||||
self.win.enabled_theme_engines.add("monet")
|
||||
else:
|
||||
self.win.enabled_theme_engines.remove("monet")
|
||||
|
||||
enabled_engines = GLib.Variant.new_strv(list(self.win.enabled_theme_engines))
|
||||
self.settings.set_value("enabled-theme-engines", enabled_engines)
|
||||
|
||||
self.win.reload_theming_page()
|
||||
|
||||
logging.debug(
|
||||
f"enabled-theme-engines: {self.settings.get_value('enabled-theme-engines')}"
|
||||
)
|
||||
|
|
|
@ -24,7 +24,7 @@ from pathlib import Path
|
|||
from gi.repository import Gtk, Adw, GLib
|
||||
|
||||
from gradience.backend.preset_downloader import PresetDownloader
|
||||
from gradience.backend.theming.preset_utils import PresetUtils
|
||||
from gradience.backend.theming.preset import PresetUtils
|
||||
from gradience.backend.globals import presets_dir, preset_repos
|
||||
from gradience.backend.constants import rootdir
|
||||
|
||||
|
@ -73,8 +73,6 @@ class GradiencePresetWindow(Adw.Window):
|
|||
def __init__(self, parent, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.app = Gtk.Application.get_default()
|
||||
|
||||
self.parent = parent
|
||||
self.settings = parent.settings
|
||||
self.app = self.parent.get_application()
|
||||
|
|
94
gradience/frontend/views/shell_prefs_window.py
Normal file
94
gradience/frontend/views/shell_prefs_window.py
Normal file
|
@ -0,0 +1,94 @@
|
|||
# shell_prefs_window.py
|
||||
#
|
||||
# Change the look of Adwaita, with ease
|
||||
# Copyright (C) 2023, Gradience Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
from gi.repository import Gtk, Adw
|
||||
|
||||
from gradience.backend.utils.colors import rgb_to_hash
|
||||
from gradience.backend.constants import rootdir
|
||||
|
||||
from gradience.frontend.widgets.option_row import GradienceOptionRow
|
||||
from gradience.frontend.schemas.shell_schema import shell_schema
|
||||
|
||||
from gradience.backend.logger import Logger
|
||||
|
||||
logging = Logger()
|
||||
|
||||
|
||||
@Gtk.Template(resource_path=f"{rootdir}/ui/shell_prefs_window.ui")
|
||||
class GradienceShellPrefsWindow(Adw.PreferencesWindow):
|
||||
__gtype_name__ = "GradienceShellPrefsWindow"
|
||||
|
||||
custom_colors_group = Gtk.Template.Child("custom-colors-group")
|
||||
|
||||
def __init__(self, parent, shell_colors: dict, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.shell_colors = shell_colors
|
||||
|
||||
self.parent = parent
|
||||
self.settings = parent.settings
|
||||
self.app = self.parent.get_application()
|
||||
|
||||
self.set_transient_for(self.app.get_active_window())
|
||||
|
||||
self.setup()
|
||||
|
||||
def setup(self):
|
||||
for variable in shell_schema["variables"]:
|
||||
pref_variable = GradienceOptionRow(
|
||||
variable["name"],
|
||||
variable["title"]
|
||||
#variable.get("explanation")
|
||||
)
|
||||
self.custom_colors_group.add(pref_variable)
|
||||
|
||||
pref_variable.color_value.connect("color-set", self.on_color_value_changed, pref_variable)
|
||||
pref_variable.text_value.connect("changed", self.on_text_value_changed, pref_variable)
|
||||
|
||||
self.set_colors(pref_variable, variable)
|
||||
|
||||
def set_colors(self, widget, variable):
|
||||
if len(self.shell_colors) != len(shell_schema["variables"]):
|
||||
try:
|
||||
self.shell_colors[variable["name"]] = variable["default_value"]
|
||||
except KeyError:
|
||||
try:
|
||||
self.shell_colors[variable["name"]] = self.app.variables[variable["var_name"]]
|
||||
except KeyError:
|
||||
raise
|
||||
finally:
|
||||
widget.update_value(self.shell_colors[variable["name"]])
|
||||
else:
|
||||
widget.update_value(self.shell_colors[variable["name"]])
|
||||
|
||||
def on_color_value_changed(self, widget, parent, *_args):
|
||||
color_name = parent.props.name
|
||||
color_value = widget.get_rgba().to_string()
|
||||
|
||||
if color_value.startswith("rgb") or color_value.startswith("rgba"):
|
||||
color_hex, alpha = rgb_to_hash(color_value)
|
||||
if not alpha:
|
||||
color_value = color_hex
|
||||
|
||||
self.shell_colors[color_name] = color_value
|
||||
|
||||
def on_text_value_changed(self, widget, parent, *_args):
|
||||
color_name = parent.props.name
|
||||
color_value = widget.get_text()
|
||||
|
||||
self.shell_colors[color_name] = color_value
|
|
@ -42,6 +42,7 @@ class GradienceCustomCSSGroup(Adw.PreferencesGroup):
|
|||
|
||||
def load_custom_css(self, custom_css):
|
||||
self.custom_css = custom_css
|
||||
|
||||
self.custom_css_text_view.get_buffer().set_text(
|
||||
list(self.custom_css.values())[
|
||||
self.app_type_dropdown.get_selected()]
|
||||
|
|
|
@ -6,10 +6,14 @@ gradience_sources = [
|
|||
'custom_css_group.py',
|
||||
'error_list_row.py',
|
||||
'explore_preset_row.py',
|
||||
'monet_theming_group.py',
|
||||
'option_row.py',
|
||||
'palette_shades.py',
|
||||
'plugin_row.py',
|
||||
'preset_row.py',
|
||||
'repo_row.py'
|
||||
'repo_row.py',
|
||||
'reset_preset_group.py',
|
||||
'shell_theming_group.py',
|
||||
'theming_empty_group.py',
|
||||
]
|
||||
PY_INSTALLDIR.install_sources(gradience_sources, subdir: widgetsdir)
|
||||
|
|
159
gradience/frontend/widgets/monet_theming_group.py
Normal file
159
gradience/frontend/widgets/monet_theming_group.py
Normal file
|
@ -0,0 +1,159 @@
|
|||
# monet_theming_group.py
|
||||
#
|
||||
# Change the look of Adwaita, with ease
|
||||
# Copyright (C) 2023, Gradience Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
from enum import Enum
|
||||
|
||||
from gi.repository import Gtk, Adw
|
||||
|
||||
from gradience.backend.theming.monet import Monet
|
||||
from gradience.backend.constants import rootdir
|
||||
|
||||
from gradience.frontend.widgets.palette_shades import GradiencePaletteShades
|
||||
|
||||
from gradience.backend.logger import Logger
|
||||
|
||||
logging = Logger()
|
||||
|
||||
|
||||
@Gtk.Template(resource_path=f"{rootdir}/ui/monet_theming_group.ui")
|
||||
class GradienceMonetThemingGroup(Adw.PreferencesGroup):
|
||||
__gtype_name__ = "GradienceMonetThemingGroup"
|
||||
|
||||
monet_theming_expander = Gtk.Template.Child("monet-theming-expander")
|
||||
monet_file_chooser = Gtk.Template.Child("monet-file-chooser")
|
||||
monet_file_chooser_button = Gtk.Template.Child("file-chooser-button")
|
||||
|
||||
def __init__(self, parent, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.parent = parent
|
||||
self.app = self.parent.get_application()
|
||||
|
||||
self.monet_image_file = None
|
||||
|
||||
self.setup_signals()
|
||||
self.setup()
|
||||
|
||||
def setup_signals(self):
|
||||
self.monet_file_chooser.connect(
|
||||
"response", self.on_monet_file_chooser_response)
|
||||
|
||||
def setup(self):
|
||||
self.monet_file_chooser.set_transient_for(self.parent)
|
||||
|
||||
self.setup_palette_shades()
|
||||
#self.setup_tone_row()
|
||||
self.setup_theme_row()
|
||||
|
||||
def setup_palette_shades(self):
|
||||
self.monet_palette_shades = GradiencePaletteShades(
|
||||
"monet", _("Monet Palette"), 6
|
||||
)
|
||||
self.app.pref_palette_shades["monet"] = self.monet_palette_shades
|
||||
|
||||
self.monet_theming_expander.add_row(self.monet_palette_shades)
|
||||
|
||||
# TODO: Rethink how it should be implemented
|
||||
'''def setup_tone_row(self):
|
||||
self.tone_row = Adw.ComboRow()
|
||||
self.tone_row.set_title(_("Tone"))
|
||||
|
||||
tone_store = Gtk.StringList()
|
||||
tone_store_values = []
|
||||
|
||||
for i in range(20, 80, 5):
|
||||
tone_store_values.append(str(i))
|
||||
|
||||
for v in tone_store_values:
|
||||
tone_store.append(v)
|
||||
|
||||
self.tone_row.set_model(tone_store)
|
||||
|
||||
self.monet_theming_expander.add_row(self.tone_row)'''
|
||||
|
||||
def setup_theme_row(self):
|
||||
self.theme_row = Adw.ComboRow()
|
||||
self.theme_row.set_title(_("Theme"))
|
||||
|
||||
theme_store = Gtk.StringList()
|
||||
theme_store.append(_("Auto"))
|
||||
theme_store.append(_("Light"))
|
||||
theme_store.append(_("Dark"))
|
||||
|
||||
self.theme_row.set_model(theme_store)
|
||||
|
||||
self.monet_theming_expander.add_row(self.theme_row)
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
def on_apply_button_clicked(self, *_args):
|
||||
if self.monet_image_file:
|
||||
try:
|
||||
monet_theme = Monet().generate_palette_from_image(self.monet_image_file)
|
||||
#tone = self.tone_row.get_selected_item().get_string() # TODO: Remove tone requirement from Monet Engine
|
||||
variant_pos = self.theme_row.props.selected
|
||||
|
||||
class variantEnum(Enum):
|
||||
AUTO = 0
|
||||
LIGHT = 1
|
||||
DARK = 2
|
||||
|
||||
def __get_variant_string():
|
||||
if variant_pos == variantEnum.AUTO.value:
|
||||
return "auto"
|
||||
elif variant_pos == variantEnum.DARK.value:
|
||||
return "dark"
|
||||
elif variant_pos == variantEnum.LIGHT.value:
|
||||
return "light"
|
||||
|
||||
variant_str = __get_variant_string()
|
||||
|
||||
self.app.custom_css_group.reset_buffer()
|
||||
|
||||
self.app.update_theme_from_monet(monet_theme, variant_str)
|
||||
except (OSError, AttributeError, ValueError) as e:
|
||||
logging.error("Failed to generate Monet palette", exc=e)
|
||||
self.parent.toast_overlay.add_toast(
|
||||
Adw.Toast(title=_("Failed to generate Monet palette"))
|
||||
)
|
||||
else:
|
||||
logging.info("Monet palette generated successfully")
|
||||
self.parent.toast_overlay.add_toast(
|
||||
Adw.Toast(title=_("Palette generated"))
|
||||
)
|
||||
else:
|
||||
logging.error("Input image for Monet generation not selected")
|
||||
self.parent.toast_overlay.add_toast(
|
||||
Adw.Toast(title=_("Select an image first"))
|
||||
)
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
def on_file_chooser_button_clicked(self, *_args):
|
||||
self.monet_file_chooser.show()
|
||||
|
||||
def on_monet_file_chooser_response(self, widget, response):
|
||||
if response == Gtk.ResponseType.ACCEPT:
|
||||
self.monet_image_file = self.monet_file_chooser.get_file()
|
||||
image_basename = self.monet_image_file.get_basename()
|
||||
self.monet_file_chooser_button.set_label(image_basename)
|
||||
self.monet_file_chooser_button.set_tooltip_text(image_basename)
|
||||
|
||||
self.monet_file_chooser.hide()
|
||||
|
||||
if response == Gtk.ResponseType.ACCEPT:
|
||||
self.monet_image_file = self.monet_image_file.get_path()
|
||||
self.on_apply_button_clicked()
|
|
@ -1,7 +1,7 @@
|
|||
# option_row.py
|
||||
#
|
||||
# Change the look of Adwaita, with ease
|
||||
# Copyright (C) 2022 Gradience Team
|
||||
# Copyright (C) 2022-2023, Gradience Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -35,7 +35,7 @@ class GradienceOptionRow(Adw.ActionRow):
|
|||
explanation_button = Gtk.Template.Child("explanation-button")
|
||||
explanation_label = Gtk.Template.Child("explanation-label")
|
||||
|
||||
def __init__(self, name, title, explanation, adw_gtk3_support="yes", **kwargs):
|
||||
def __init__(self, name, title, explanation=None, adw_gtk3_support=None, update_var=None, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.app = Gtk.Application.get_default()
|
||||
|
@ -44,7 +44,7 @@ class GradienceOptionRow(Adw.ActionRow):
|
|||
self.set_title(title)
|
||||
self.set_subtitle("@" + name)
|
||||
|
||||
if adw_gtk3_support == "yes":
|
||||
if adw_gtk3_support == "yes" or not adw_gtk3_support:
|
||||
self.warning_button.set_visible(False)
|
||||
elif adw_gtk3_support == "partial":
|
||||
self.warning_button.add_css_class("warning")
|
||||
|
@ -58,11 +58,17 @@ class GradienceOptionRow(Adw.ActionRow):
|
|||
)
|
||||
|
||||
self.explanation_label.set_label(explanation or "")
|
||||
if explanation is None:
|
||||
|
||||
if not explanation:
|
||||
self.explanation_button.set_visible(False)
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
def on_color_value_changed(self, *_args):
|
||||
self.update_var = update_var
|
||||
|
||||
def connect_signals(self, update_vars):
|
||||
self.color_value.connect("color-set", self.on_color_value_changed, update_vars)
|
||||
self.text_value.connect("changed", self.on_text_value_changed, update_vars)
|
||||
|
||||
def on_color_value_changed(self, _unused, update_vars, *_args):
|
||||
color_value = self.color_value.get_rgba().to_string()
|
||||
|
||||
if color_value.startswith("rgb") or color_value.startswith("rgba"):
|
||||
|
@ -71,14 +77,14 @@ class GradienceOptionRow(Adw.ActionRow):
|
|||
color_value = color_hex
|
||||
|
||||
self.update_value(
|
||||
color_value, update_from="color_value"
|
||||
color_value, update_from="color_value", update_vars=update_vars
|
||||
)
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
def on_text_value_changed(self, *_args):
|
||||
def on_text_value_changed(self, _unused, update_vars, *_args):
|
||||
color_value = self.text_value.get_text()
|
||||
|
||||
self.update_value(
|
||||
color_value, update_from="text_value"
|
||||
color_value, update_from="text_value", update_vars=update_vars
|
||||
)
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
|
@ -88,7 +94,7 @@ class GradienceOptionRow(Adw.ActionRow):
|
|||
else:
|
||||
self.value_stack.set_visible_child(self.color_value)
|
||||
|
||||
def update_value(self, new_value, **kwargs):
|
||||
def update_value(self, new_value, update_vars=False, **kwargs):
|
||||
rgba = Gdk.RGBA()
|
||||
is_app_ready = self.app.is_ready
|
||||
|
||||
|
@ -109,7 +115,11 @@ class GradienceOptionRow(Adw.ActionRow):
|
|||
self.color_value.set_rgba(rgba)
|
||||
self.color_value.set_tooltip_text(_("Not a color, see text value"))
|
||||
|
||||
if is_app_ready and kwargs.get("update_from") == "text_value" and new_value != "":
|
||||
self.app.variables[self.get_name()] = new_value
|
||||
self.app.mark_as_dirty()
|
||||
self.app.reload_variables()
|
||||
if update_vars == True:
|
||||
if is_app_ready and kwargs.get("update_from") == "text_value" and new_value != "":
|
||||
if self.update_var:
|
||||
self.update_var[self.get_name()] = new_value
|
||||
else:
|
||||
self.app.variables[self.get_name()] = new_value
|
||||
self.app.mark_as_dirty()
|
||||
self.app.reload_variables()
|
||||
|
|
98
gradience/frontend/widgets/reset_preset_group.py
Normal file
98
gradience/frontend/widgets/reset_preset_group.py
Normal file
|
@ -0,0 +1,98 @@
|
|||
# reset_preset_group.py
|
||||
#
|
||||
# Change the look of Adwaita, with ease
|
||||
# Copyright (C) 2023, Gradience Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
from gi.repository import GLib, Gtk, Adw
|
||||
|
||||
from gradience.backend.constants import rootdir
|
||||
from gradience.backend.logger import Logger
|
||||
from gradience.backend.theming.preset import PresetUtils
|
||||
|
||||
from gradience.frontend.dialogs.log_out_dialog import GradienceLogOutDialog
|
||||
|
||||
logging = Logger()
|
||||
|
||||
|
||||
@Gtk.Template(resource_path=f"{rootdir}/ui/reset_preset_group.ui")
|
||||
class GradienceResetPresetGroup(Adw.PreferencesGroup):
|
||||
__gtype_name__ = "GradienceResetPresetGroup"
|
||||
|
||||
def __init__(self, parent, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.parent = parent
|
||||
|
||||
self.app = self.parent.get_application()
|
||||
self.win = self.parent
|
||||
|
||||
self.setup_signals()
|
||||
self.setup()
|
||||
|
||||
def setup_signals(self):
|
||||
pass
|
||||
|
||||
def setup(self):
|
||||
pass
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
def on_libadw_restore_button_clicked(self, *_args):
|
||||
try:
|
||||
PresetUtils().restore_preset("gtk4")
|
||||
except GLib.GError:
|
||||
self.parent.add_toast(
|
||||
Adw.Toast(title=_("Unable to restore GTK 4 backup"))
|
||||
)
|
||||
else:
|
||||
dialog = GradienceLogOutDialog(self.win)
|
||||
dialog.present()
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
def on_libadw_reset_button_clicked(self, *_args):
|
||||
try:
|
||||
PresetUtils().reset_preset("gtk4")
|
||||
except GLib.GError:
|
||||
self.parent.add_toast(
|
||||
Adw.Toast(title=_("Unable to delete current preset"))
|
||||
)
|
||||
else:
|
||||
dialog = GradienceLogOutDialog(self.win)
|
||||
dialog.present()
|
||||
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
def on_gtk3_restore_button_clicked(self, *_args):
|
||||
try:
|
||||
PresetUtils().restore_preset("gtk3")
|
||||
except GLib.GError:
|
||||
self.parent.add_toast(
|
||||
Adw.Toast(title=_("Unable to restore GTK 3 backup"))
|
||||
)
|
||||
else:
|
||||
dialog = GradienceLogOutDialog(self.win)
|
||||
dialog.present()
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
def on_gtk3_reset_button_clicked(self, *_args):
|
||||
try:
|
||||
PresetUtils().reset_preset("gtk3")
|
||||
except GLib.GError:
|
||||
self.parent.add_toast(
|
||||
Adw.Toast(title=_("Unable to delete current preset"))
|
||||
)
|
||||
else:
|
||||
dialog = GradienceLogOutDialog(self.win)
|
||||
dialog.present()
|
246
gradience/frontend/widgets/shell_theming_group.py
Normal file
246
gradience/frontend/widgets/shell_theming_group.py
Normal file
|
@ -0,0 +1,246 @@
|
|||
# shell_theming_group.py
|
||||
#
|
||||
# Change the look of Adwaita, with ease
|
||||
# Copyright (C) 2023, Gradience Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
from enum import Enum
|
||||
from subprocess import SubprocessError
|
||||
|
||||
from gi.repository import GObject, GLib, Gio, Gtk, Adw
|
||||
|
||||
from gradience.backend.utils.gnome import is_gnome_available, is_shell_ext_installed
|
||||
from gradience.backend.utils.subprocess import GradienceSubprocess
|
||||
from gradience.backend.constants import rootdir
|
||||
from gradience.backend.exceptions import UnsupportedShellVersion
|
||||
from gradience.backend.logger import Logger
|
||||
|
||||
from gradience.backend.theming.shell import ShellTheme
|
||||
|
||||
from gradience.frontend.schemas.shell_schema import shell_schema
|
||||
from gradience.frontend.dialogs.unsupported_shell_dialog import GradienceUnsupportedShellDialog
|
||||
from gradience.frontend.views.shell_prefs_window import GradienceShellPrefsWindow
|
||||
|
||||
logging = Logger()
|
||||
|
||||
|
||||
@Gtk.Template(resource_path=f"{rootdir}/ui/shell_theming_group.ui")
|
||||
class GradienceShellThemingGroup(Adw.PreferencesGroup):
|
||||
__gtype_name__ = "GradienceShellThemingGroup"
|
||||
|
||||
variant_row = Gtk.Template.Child("variant-row")
|
||||
shell_theming_expander = Gtk.Template.Child("shell-theming-expander")
|
||||
other_options_row = Gtk.Template.Child("other-options-row")
|
||||
|
||||
def __init__(self, parent, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.shell_colors = {}
|
||||
|
||||
self.parent = parent
|
||||
self.settings = parent.settings
|
||||
|
||||
self.app = parent.get_application()
|
||||
self.win = self.app.get_active_window()
|
||||
self.toast_overlay = parent.toast_overlay
|
||||
|
||||
self.setup_signals()
|
||||
self.setup()
|
||||
|
||||
def setup_signals(self):
|
||||
self.app.connect("preset-reload", self.reload_colors)
|
||||
|
||||
def setup(self):
|
||||
self.setup_variant_row()
|
||||
|
||||
self.shell_theming_expander.add_row(self.other_options_row)
|
||||
|
||||
def setup_variant_row(self):
|
||||
variant_store = Gtk.StringList()
|
||||
variant_store.append(_("Dark"))
|
||||
variant_store.append(_("Light"))
|
||||
|
||||
self.variant_row.set_model(variant_store)
|
||||
|
||||
# TODO: Maybe allow it when using export option?
|
||||
'''def setup_version_row(self):
|
||||
version_store = Gtk.StringList()
|
||||
version_store.append(_("Auto"))
|
||||
version_store.append(_("43"))
|
||||
version_store.append(_("42"))
|
||||
|
||||
self.shell_version_row.set_model(version_store)'''
|
||||
|
||||
def reload_colors(self, *args):
|
||||
try:
|
||||
for variable in shell_schema["variables"]:
|
||||
self.set_colors(variable)
|
||||
except Exception as e:
|
||||
logging.error("An unexpected error occurred while loading variable colors.", exc=e)
|
||||
self.toast_overlay.add_toast(
|
||||
Adw.Toast(
|
||||
title=_("An unexpected error occurred while loading variable colors."))
|
||||
)
|
||||
|
||||
def set_colors(self, variable):
|
||||
try:
|
||||
self.shell_colors[variable["name"]] = variable["default_value"]
|
||||
except KeyError:
|
||||
try:
|
||||
self.shell_colors[variable["name"]] = self.app.variables[variable["var_name"]]
|
||||
except KeyError:
|
||||
raise
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
def on_custom_colors_button_clicked(self, *_args):
|
||||
self.shell_pref_window = GradienceShellPrefsWindow(self.parent, self.shell_colors)
|
||||
self.shell_pref_window.present()
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
def on_apply_button_clicked(self, *_args):
|
||||
user_themes_available = is_shell_ext_installed(ShellTheme().THEME_EXT_NAME)
|
||||
user_themes_enabled = is_shell_ext_installed(
|
||||
ShellTheme().THEME_EXT_NAME, check_enabled=True)
|
||||
|
||||
if not is_gnome_available():
|
||||
dialog = Adw.MessageDialog(transient_for=self.win, heading=_("GNOME Shell Missing"),
|
||||
body=_("Shell Engine is designed to work only on systems running GNOME. You can still generate themes on other desktop environments, but it won't have any affect on them."))
|
||||
|
||||
dialog.add_response("disable-engine", _("Disable Engine"))
|
||||
dialog.add_response("continue-anyway", _("Continue Anyway"))
|
||||
dialog.set_response_appearance("disable-engine", Adw.ResponseAppearance.DESTRUCTIVE)
|
||||
dialog.set_default_response("continue-anyway")
|
||||
|
||||
dialog.connect("response", self.on_shell_missing_response)
|
||||
dialog.present()
|
||||
elif is_gnome_available() and not user_themes_available:
|
||||
dialog = Adw.MessageDialog(transient_for=self.win, heading=_("User Themes Extension Missing"),
|
||||
body=_("Gradience requires User Themes extension installed in order to apply Shell theme. You can still generate a theme, but you won't be able to apply it without this extension."))
|
||||
|
||||
dialog.add_response("install-extension", _("Install Extension"))
|
||||
dialog.add_response("continue-anyway", _("Continue Anyway"))
|
||||
dialog.set_response_appearance("install-extension", Adw.ResponseAppearance.SUGGESTED)
|
||||
dialog.set_default_response("continue-anyway")
|
||||
|
||||
dialog.connect("response", self.on_user_themes_missing_response)
|
||||
dialog.present()
|
||||
elif is_gnome_available() and user_themes_available and not user_themes_enabled:
|
||||
dialog = Adw.MessageDialog(transient_for=self.win, heading=_("User Themes Extension Disabled"),
|
||||
body=_("User Themes extension is currently disabled on your system. Please enable it in order to apply theme."))
|
||||
|
||||
dialog.add_response("cancel", _("Cancel"))
|
||||
#dialog.add_response("enable-extension", _("Enable Extension"))
|
||||
dialog.add_response("continue-anyway", _("Continue Anyway"))
|
||||
dialog.set_response_appearance("cancel", Adw.ResponseAppearance.SUGGESTED)
|
||||
#dialog.set_response_appearance("enable-extension", Adw.ResponseAppearance.SUGGESTED)
|
||||
dialog.set_default_response("continue-anyway")
|
||||
|
||||
dialog.connect("response", self.on_user_themes_disabled_response)
|
||||
dialog.present()
|
||||
else:
|
||||
self.apply_shell_theme()
|
||||
|
||||
def apply_shell_theme(self):
|
||||
variant_pos = self.variant_row.props.selected
|
||||
|
||||
class variantEnum(Enum):
|
||||
DARK = 0
|
||||
LIGHT = 1
|
||||
|
||||
def __get_variant_string():
|
||||
if variant_pos == variantEnum.DARK.value:
|
||||
return "dark"
|
||||
elif variant_pos == variantEnum.LIGHT.value:
|
||||
return "light"
|
||||
|
||||
variant_str = __get_variant_string()
|
||||
|
||||
try:
|
||||
ShellTheme().apply_theme_async(self, self._on_shell_theme_done,
|
||||
variant_str, self.app.preset)
|
||||
except UnsupportedShellVersion as exception_message:
|
||||
logging.error(exception_message)
|
||||
GradienceUnsupportedShellDialog(self.parent).present()
|
||||
except (ValueError, OSError, GLib.GError) as e:
|
||||
logging.error(
|
||||
"An error occurred while generating a Shell theme.", exc=e)
|
||||
self.toast_overlay.add_toast(
|
||||
Adw.Toast(
|
||||
title=_("An error occurred while generating a Shell theme."))
|
||||
)
|
||||
|
||||
def _on_shell_theme_done(self, source_widget:GObject.Object,
|
||||
result:Gio.AsyncResult, user_data:GObject.GPointer):
|
||||
logging.debug("It works! \o/")
|
||||
self.toast_overlay.add_toast(
|
||||
Adw.Toast(title=_("Shell theme applied successfully."))
|
||||
)
|
||||
|
||||
def on_shell_missing_response(self, widget, response, *args):
|
||||
if response == "disable-engine":
|
||||
self.win.enabled_theme_engines.remove("shell")
|
||||
|
||||
enabled_engines = GLib.Variant.new_strv(list(self.win.enabled_theme_engines))
|
||||
self.settings.set_value("enabled-theme-engines", enabled_engines)
|
||||
|
||||
self.win.reload_theming_page()
|
||||
elif response == "continue-anyway":
|
||||
self.apply_shell_theme()
|
||||
|
||||
# FIXME: Hangs until Extension Manager is closed. We might something like `run_app` function \
|
||||
# with subrocess.Popen instead of subrocess.run to make it not hang Gradience
|
||||
def on_user_themes_missing_response(self, widget, response, *args):
|
||||
if response == "install-extension":
|
||||
try:
|
||||
cmd_list = ["xdg-open", "gnome-extensions://user-theme%40gnome-shell-extensions.gcampax.github.com?action=install"]
|
||||
GradienceSubprocess().run(cmd_list, allow_escaping=True)
|
||||
except SubprocessError:
|
||||
logging.warning("Can't open 'gnome-extensions://' URI scheme, trying to open EGO webpage")
|
||||
try:
|
||||
cmd_list = ["xdg-open", "https://extensions.gnome.org/extension/19/user-themes/"]
|
||||
GradienceSubprocess().run(cmd_list, allow_escaping=True)
|
||||
except SubprocessError as e:
|
||||
logging.error("Failed to load extension's website", exc=e)
|
||||
self.toast_overlay.add_toast(
|
||||
Adw.Toast(title=_("Failed to load extension's install link."))
|
||||
)
|
||||
except FileNotFoundError:
|
||||
logging.error("xdg-open command missing, are you even on GNOME? \nOpen this link: https://extensions.gnome.org/extension/19/user-themes/")
|
||||
self.toast_overlay.add_toast(
|
||||
Adw.Toast(title=_("Failed to load extension's install link."))
|
||||
)
|
||||
elif response == "continue-anyway":
|
||||
self.apply_shell_theme()
|
||||
|
||||
def on_user_themes_disabled_response(self, widget, response, *args):
|
||||
'''if response == "enable-extension":
|
||||
pass'''
|
||||
if response == "continue-anyway":
|
||||
self.apply_shell_theme()
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
def on_reset_theme_clicked(self, *_args):
|
||||
# TODO: Make this function actually remove Shell theme
|
||||
ShellTheme().reset_theme_async(self, self._on_reset_theme_done)
|
||||
|
||||
def _on_reset_theme_done(self, source_widget:GObject.Object,
|
||||
result:Gio.AsyncResult, user_data:GObject.GPointer):
|
||||
self.toast_overlay.add_toast(
|
||||
Adw.Toast(title=_("Shell theme successfully reset."))
|
||||
)
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
def on_restore_button_clicked(self, *_args):
|
||||
logging.debug("Nothing here yet /o\ ")
|
51
gradience/frontend/widgets/theming_empty_group.py
Normal file
51
gradience/frontend/widgets/theming_empty_group.py
Normal file
|
@ -0,0 +1,51 @@
|
|||
# theming_empty_group.py
|
||||
#
|
||||
# Change the look of Adwaita, with ease
|
||||
# Copyright (C) 2023, Gradience Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
from enum import Enum
|
||||
|
||||
from gi.repository import Adw, Gio, GLib, Gtk
|
||||
|
||||
from gradience.backend.constants import rootdir
|
||||
from gradience.backend.exceptions import UnsupportedShellVersion
|
||||
from gradience.backend.logger import Logger
|
||||
from gradience.backend.theming.shell import ShellTheme
|
||||
|
||||
from gradience.frontend.views.shell_prefs_window import GradienceShellPrefsWindow
|
||||
|
||||
logging = Logger()
|
||||
|
||||
|
||||
@Gtk.Template(resource_path=f"{rootdir}/ui/theming_empty_group.ui")
|
||||
class GradienceEmptyThemingGroup(Adw.PreferencesGroup):
|
||||
__gtype_name__ = "GradienceEmptyThemingGroup"
|
||||
|
||||
def __init__(self, parent, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.parent = parent
|
||||
self.settings = parent.settings
|
||||
self.app = self.parent.get_application()
|
||||
|
||||
self.setup_signals()
|
||||
self.setup()
|
||||
|
||||
def setup_signals(self):
|
||||
pass
|
||||
|
||||
def setup(self):
|
||||
pass
|
33
po/POTFILES
33
po/POTFILES
|
@ -5,6 +5,7 @@ data/ui/app_type_dialog.blp
|
|||
data/ui/builtin_preset_row.blp
|
||||
data/ui/custom_css_group.blp
|
||||
data/ui/explore_preset_row.blp
|
||||
data/ui/monet_theming_group.blp
|
||||
data/ui/log_out_dialog.blp
|
||||
data/ui/no_plugin_window.blp
|
||||
data/ui/option_row.blp
|
||||
|
@ -12,28 +13,38 @@ data/ui/plugin_row.blp
|
|||
data/ui/preferences_window.blp
|
||||
data/ui/preset_row.blp
|
||||
data/ui/presets_manager_window.blp
|
||||
data/ui/reset_preset_group.blp
|
||||
data/ui/save_dialog.blp
|
||||
data/ui/share_window.blp
|
||||
data/ui/shell_prefs_window.blp
|
||||
data/ui/shell_theming_group.blp
|
||||
data/ui/theming_empty_group.blp
|
||||
data/ui/welcome_window.blp
|
||||
data/ui/window.blp
|
||||
gradience/frontend/schemas/preset_schema.py
|
||||
gradience/frontend/schemas/shell_schema.py
|
||||
gradience/frontend/dialogs/app_type_dialog.py
|
||||
gradience/frontend/dialogs/log_out_dialog.py
|
||||
gradience/frontend/dialogs/save_dialog.py
|
||||
gradience/frontend/main.py
|
||||
gradience/frontend/schemas/preset_schema.py
|
||||
gradience/frontend/dialogs/unsupported_shell_dialog.py
|
||||
gradience/frontend/widgets/builtin_preset_row.py
|
||||
gradience/frontend/widgets/custom_css_group.py
|
||||
gradience/frontend/widgets/error_list_row.py
|
||||
gradience/frontend/widgets/explore_preset_row.py
|
||||
gradience/frontend/widgets/monet_theming_group.py
|
||||
gradience/frontend/widgets/option_row.py
|
||||
gradience/frontend/widgets/palette_shades.py
|
||||
gradience/frontend/widgets/plugin_row.py
|
||||
gradience/frontend/widgets/preset_row.py
|
||||
gradience/frontend/widgets/repo_row.py
|
||||
gradience/frontend/widgets/reset_preset_group.py
|
||||
gradience/frontend/widgets/shell_theming_group.py
|
||||
gradience/frontend/views/about_window.py
|
||||
gradience/frontend/views/main_window.py
|
||||
gradience/frontend/views/no_plugin_window.py
|
||||
gradience/frontend/views/plugins_list.py
|
||||
gradience/frontend/views/preferences_window.py
|
||||
gradience/frontend/views/presets_manager_window.py
|
||||
gradience/frontend/views/shell_prefs_window.py
|
||||
gradience/frontend/views/welcome_window.py
|
||||
gradience/frontend/widgets/builtin_preset_row.py
|
||||
gradience/frontend/widgets/custom_css_group.py
|
||||
gradience/frontend/widgets/error_list_row.py
|
||||
gradience/frontend/widgets/explore_preset_row.py
|
||||
gradience/frontend/widgets/option_row.py
|
||||
gradience/frontend/widgets/palette_shades.py
|
||||
gradience/frontend/widgets/plugin_row.py
|
||||
gradience/frontend/widgets/preset_row.py
|
||||
gradience/frontend/widgets/repo_row.py
|
||||
gradience/frontend/main.py
|
||||
|
|
|
@ -31,3 +31,4 @@ material-color-utilities-python
|
|||
svglib
|
||||
yapsy
|
||||
Jinja2
|
||||
libsass
|
Loading…
Reference in a new issue