diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml
new file mode 100644
index 0000000..915509e
--- /dev/null
+++ b/.github/workflows/python-app.yml
@@ -0,0 +1,43 @@
+# This workflow will install Python dependencies, run tests and lint with a single version of Python
+# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
+
+name: Marble installation script
+
+on:
+ push:
+ branches:
+ - "main"
+ - "gdm"
+ pull_request:
+ branches:
+ - "main"
+ - "gdm"
+
+permissions:
+ contents: read
+
+jobs:
+ build:
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+ - name: Set up Python 3.10
+ uses: actions/setup-python@v3
+ with:
+ python-version: "3.10"
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install flake8 pytest
+ if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
+ - name: Lint with flake8
+ run: |
+ # stop the build if there are Python syntax errors or undefined names
+ flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
+ # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
+ flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
+ - name: Test with pytest
+ run: |
+ pytest scripts/tests.py
diff --git a/.gitignore b/.gitignore
index d8da396..b4d6a80 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
__pycache__
+.pytest_cache
.idea
-.temp
\ No newline at end of file
+.temp
+.tests
\ No newline at end of file
diff --git a/README.md b/README.md
index 9338a8a..f62c712 100644
--- a/README.md
+++ b/README.md
@@ -66,6 +66,32 @@ Icon theme: https://github.com/vinceliuice/Colloid-icon-theme
5. Select the shell theme you want.
+## 🖥️ GDM theme
+
+
+
+1. Open the terminal.
+2. Go to the directory with the theme.
+3. Run the program with the `--gdm` option
+ ```shell
+ sudo python install.py --gdm (--your color) (--is filled)
+ ```
+ - Example:
+ ```shell
+ sudo python install.py --gdm --blue --filled
+ ```
+4. After successful file restart GDM service:
+ ```shell
+ sudo systemctl restart gdm
+ ```
+
+- 🗑️ If you want to remove the theme, run the program with the `--remove` option:
+ ```shell
+ sudo python install.py --gdm -r
+ ```
+- ☠️ If you got a death screen, you can switch to the console with the `Ctrl + Alt + F3` key combination, log in, go to the `Marble-shell-theme` directory and run the command above. If it doesn't help, try reinstalling `gnome-shell` package.
+
+
## 🏮 Installation tweaks
#### Install default color
diff --git a/colors.json b/colors.json
index e22bf48..16735ab 100644
--- a/colors.json
+++ b/colors.json
@@ -147,6 +147,37 @@
}
},
+ "ACCENT-LOGIN-COLOR" : {
+ "_comment" : "Accent color for login screen user list",
+
+ "light" : {
+ "s" : 65,
+ "l" : 65,
+ "a" : 1
+ },
+
+ "dark" : {
+ "s" : 65,
+ "l" : 55,
+ "a" : 1
+ }
+ },
+ "ACCENT-LOGIN_HOVER" : {
+ "_comment" : "Hover color for login screen user list",
+
+ "light" : {
+ "s" : 65,
+ "l" : 55,
+ "a" : 0.8
+ },
+
+ "dark" : {
+ "s" : 65,
+ "l" : 55,
+ "a" : 0.8
+ }
+ },
+
"ACCENT-FILLED-COLOR" : {
"_comment" : "Accent color but more vibrant",
diff --git a/install.py b/install.py
index 5203b4b..42a9ba4 100644
--- a/install.py
+++ b/install.py
@@ -15,241 +15,20 @@
# along with this program. If not, see .
-import colorsys # convert hsl to rgv
+import json # working with json files
import os # system commands, working with files
-import json # colors.json
import argparse # command-line options
+import shutil
import textwrap # example text in argparse
+from scripts import config # folder and files definitions
-# folder definitions
-temp_folder = "./.temp"
-gnome_folder = "gnome-shell"
-temp_gnome_folder = f"{temp_folder}/{gnome_folder}"
-tweaks_folder = "./tweaks"
-themes_folder = "~/.themes"
+from scripts.utils import (
+ remove_files, # delete already installed Marble theme
+ hex_to_rgba) # convert HEX to RGBA
-# files definitions
-gnome_shell_css = f"{temp_gnome_folder}/gnome-shell.css"
-
-
-def generate_file(folder, final_file):
- """
- Combines all files in a folder into a single file
- :param folder: source folder
- :param final_file: location where file will be created
- """
-
- opened_file = open(final_file, "w")
-
- for file in os.listdir(folder):
- opened_file.write(open(folder + file).read() + '\n')
-
- opened_file.close()
-
-
-def concatenate_files(file, edit_file):
- """
- Merge two files
- :param file: file you want to append
- :param edit_file: where it will be appended
- """
-
- open(edit_file, 'a').write('\n' + open(file).read())
-
-
-def remove_files():
- """
- Delete already installed Marble theme
- """
-
- paths = (themes_folder, "~/.local/share/themes")
-
- print("💡 You do not need to delete files if you want to update theme.\n")
-
- confirmation = input(f"Do you want to delete all \"Marble\" folders in {' and in '.join(paths)}? (y/N) ").lower()
-
- if confirmation == "y":
- for path in paths:
-
- # Check if the path exists
- if os.path.exists(os.path.expanduser(path)):
-
- # Get the list of folders in the path
- folders = os.listdir(os.path.expanduser(path))
-
- # toggle if folder has no marble theme
- found_folder = False
-
- for folder in folders:
- if folder.startswith("Marble"):
- folder_path = os.path.join(os.path.expanduser(path), folder)
- print(f"Deleting folder {folder_path}...", end='')
-
- try:
- os.system(f"rm -r {folder_path}")
-
- except Exception as e:
- print(f"Error deleting folder {folder_path}: {e}")
-
- else:
- found_folder = True
- print("Done.")
-
- if not found_folder:
- print(f"No folders starting with \"Marble\" found in {path}.")
-
- else:
- print(f"The path {path} does not exist.")
-
-
-def destination_return(path_name, theme_mode):
- """
- Copied/modified theme location
- :param path_name: color name
- :param theme_mode: theme name (light or dark)
- :return: copied files' folder location
- """
-
- return f"{themes_folder}/Marble-{path_name}-{theme_mode}/"
-
-
-def copy_files(source, destination):
- """
- Copy files from the source to another directory
- :param source: where files will be copied
- :param destination: where files will be pasted
- """
-
- destination_dirs = destination.split("/") # list of folders
- loop_create_dirs = f"{destination_dirs[0]}/"
-
- # traverse through folders and create them
- for i in range(1, len(destination_dirs)):
- loop_create_dirs += f"{destination_dirs[i]}/"
- os.system(f"mkdir -p {loop_create_dirs}")
-
- os.system(f"cp -aT {source} {destination}")
-
-
-def replace_keywords(file, *args):
- """
- Replace file with several keywords
- :param file: file name where keywords must be replaced
- :param args: (keyword, replacement), (...), ...
- """
-
- # skip binary files in project
- if not file.lower().endswith(('.css', '.scss', '.svg')):
- return
-
- with open(file, "r") as read_file:
- content = read_file.read()
-
- for keyword, replacement in args:
- content = content.replace(keyword, replacement)
-
- with open(file, "w") as write_file:
- write_file.write(content)
-
-
-def apply_colors(hue, destination, theme_mode, apply_file, sat=None):
- """
- Install accent colors from colors.json to different file
- :param hue
- :param destination: file directory
- :param theme_mode: theme name (light or dark)
- :param apply_file: file name
- :param sat: color saturation (optional)
- """
-
- # list of (keyword, replaced value)
- replaced_colors = list()
-
- # colorsys works in range(0, 1)
- h = hue / 360
- for element in colors["elements"]:
- # if color is has default color and hasn't been replaced
- if theme_mode not in colors["elements"][element] and colors["elements"][element]["default"]:
- default_element = colors["elements"][element]["default"]
- default_color = colors["elements"][default_element][theme_mode]
- colors["elements"][element][theme_mode] = default_color
-
- # convert sla to range(0, 1)
- lightness = int(colors["elements"][element][theme_mode]["l"]) / 100
- saturation = int(colors["elements"][element][theme_mode]["s"]) / 100 if sat is None else \
- int(colors["elements"][element][theme_mode]["s"]) * (sat / 100) / 100
- alpha = colors["elements"][element][theme_mode]["a"]
-
- # convert hsl to rgb and multiply every item
- red, green, blue = [int(item * 256) for item in colorsys.hls_to_rgb(h, lightness, saturation)]
-
- replaced_colors.append((element, f"rgba({red}, {green}, {blue}, {alpha})"))
-
- # replace colors
- replace_keywords(os.path.expanduser(f"{destination}/{apply_file}"), *replaced_colors)
-
-
-def apply_theme(hue, destination, theme_mode, sat=None):
- """
- Apply theme to all files listed in "apply-theme-files" (colors.json)
- :param hue
- :param destination: file directory
- :param theme_mode: theme name (light or dark)
- :param sat: color saturation (optional)
- """
-
- for apply_file in os.listdir(f"{temp_gnome_folder}/"):
- apply_colors(hue, destination, theme_mode, apply_file, sat=sat)
-
-
-def install_color(hue, name, theme_mode, sat=None):
- """
- Copy files and generate theme with different accent color
- :param hue
- :param name: theme name
- :param theme_mode: light or dark mode
- :param sat: color saturation (optional)
- """
-
- print(f"Creating {name} {', '.join(theme_mode)} theme...", end=" ")
-
- try:
- for mode in theme_mode:
- destination = destination_return(name, mode)
-
- copy_files(temp_folder, destination)
- apply_theme(hue, f"{destination}/{gnome_folder}", mode, sat=sat)
-
- except Exception as err:
- print("\nError: " + str(err))
-
- else:
- print("Done.")
-
-
-def hex_to_rgba(hex_color):
- """
- Convert hex(a) to rgba
- :param hex_color: input value
- """
-
- try:
- if len(hex_color) in range(6, 10):
- hex_color = hex_color.lstrip('#') + "ff"
- # if is convertable
- int(hex_color[:], 16)
- else:
- raise ValueError
-
- except ValueError:
- raise ValueError(f'Error: Invalid HEX color code: {hex_color}')
-
- else:
- return int(hex_color[0:2], 16), \
- int(hex_color[2:4], 16), \
- int(hex_color[4:6], 16), \
- int(hex_color[6:8], 16) / 255
+from scripts.theme import Theme
+from scripts.gdm import GlobalTheme
def main():
@@ -293,6 +72,10 @@ def main():
color_tweaks.add_argument('--sat', type=int, choices=range(0, 251),
help='custom color saturation (<100%% - reduce, >100%% - increase)', metavar='(0 - 250)%')
+ gdm_theming = parser.add_argument_group('GDM theming')
+ gdm_theming.add_argument('--gdm', action='store_true', help='install GDM theme. \
+ Requires root privileges. You must specify a specific color.')
+
panel_args = parser.add_argument_group('Panel tweaks')
panel_args.add_argument('-Pds', '--panel_default_size', action='store_true', help='set default panel size')
panel_args.add_argument('-Pnp', '--panel_no_pill', action='store_true', help='remove panel button background')
@@ -303,80 +86,114 @@ def main():
args = parser.parse_args()
- # is used as list because of install_color
- mode = [args.mode] if args.mode else ['light', 'dark']
+ colors = json.load(open(config.colors_json))
- # move files to temp folder
- copy_files(f"./theme/{gnome_folder}/", f"{temp_gnome_folder}")
- generate_file(f"./theme/{gnome_folder}_css/", gnome_shell_css)
+ if args.gdm:
+ gdm_status = 1
+ if os.geteuid() != 0:
+ print("You must run this script as root to install GDM theme.")
+ return 1
- # remove marble theme
- if args.remove:
- remove_files()
+ if args.all:
+ print("Error: You can't install all colors for GDM theme. Use specific color.")
+ return 1
- # panel tweaks
- if args.panel_default_size:
- concatenate_files(f"{tweaks_folder}/panel/def-size.css", gnome_shell_css)
+ gdm_theme = GlobalTheme(colors, f"{config.raw_theme_folder}/{config.gnome_folder}",
+ config.global_gnome_shell_theme, config.gnome_shell_gresource,
+ config.temp_folder, is_filled=args.filled)
- if args.panel_no_pill:
- concatenate_files(f"{tweaks_folder}/panel/no-pill.css", gnome_shell_css)
+ if args.remove:
+ gdm_rm_status = gdm_theme.remove()
+ if gdm_rm_status == 0:
+ print("GDM theme removed successfully.")
+ return 0
- if args.panel_text_color:
- open(f"{temp_gnome_folder}/{gnome_folder}.css", "a") \
- .write(".panel-button,\
- .clock,\
- .clock-display StIcon {\
- color: rgba(" + ', '.join(map(str, hex_to_rgba(args.panel_text_color))) + ");\
- }")
+ if args.red or args.pink or args.purple or args.blue or args.green or args.yellow or args.gray:
+ for color in colors["colors"]:
+ if getattr(args, color):
+ hue = colors["colors"][color]["h"]
+ sat = colors["colors"][color]["s"] if colors["colors"][color]["s"] is not None else args.sat
- # dock tweaks
- if args.launchpad:
- concatenate_files(f"{tweaks_folder}/launchpad/launchpad.css", gnome_shell_css)
- os.system(f"cp {tweaks_folder}/launchpad/launchpad.png {temp_gnome_folder}/")
+ gdm_status = gdm_theme.install(hue, sat)
- # color tweaks
- if args.filled:
- for apply_file in os.listdir(f"{temp_gnome_folder}/"):
- replace_keywords(f"{temp_gnome_folder}/{apply_file}",
- ("BUTTON-COLOR", "ACCENT-FILLED-COLOR"),
- ("BUTTON_HOVER", "ACCENT-FILLED_HOVER"),
- ("BUTTON_INSENSITIVE", "ACCENT-FILLED_INSENSITIVE"),
- ("BUTTON-TEXT-COLOR", "TEXT-BLACK-COLOR"),
- ("BUTTON-TEXT_SECONDARY", "TEXT-BLACK_SECONDARY"))
+ elif args.hue:
+ hue = args.hue
- # what argument colors defined
- if args.all:
- # install hue colors listed in colors.json
- for color in colors["colors"]:
- hue = colors["colors"][color]["h"]
- # if saturation already defined in color (gray)
- sat = colors["colors"][color]["s"] if colors["colors"][color]["s"] is not None else args.sat
+ gdm_status = gdm_theme.install(hue, args.sat)
- install_color(hue, color, mode, sat)
+ else:
+ print('No color arguments specified. Use -h or --help to see the available options.')
- elif args.red or args.pink or args.purple or args.blue or args.green or args.yellow or args.gray:
- for color in colors["colors"]:
- if getattr(args, color): # if argument name is in defined colors
+ if gdm_status == 0:
+ print("\nGDM theme installed successfully.")
+ print("You need to restart gdm.service to apply changes.")
+ print("Run \"systemctl restart gdm.service\" to restart GDM.")
+
+ # if not GDM theme
+ else:
+ # remove marble theme
+ if args.remove:
+ remove_files()
+
+ gnome_shell_theme = Theme("gnome-shell", colors, f"{config.raw_theme_folder}/{config.gnome_folder}",
+ config.themes_folder, config.temp_folder,
+ mode=args.mode, is_filled=args.filled)
+
+ # panel tweaks
+ if args.panel_default_size:
+ with open(f"{config.tweaks_folder}/panel/def-size.css", "r") as f:
+ gnome_shell_theme += f.read()
+
+ if args.panel_no_pill:
+ with open(f"{config.tweaks_folder}/panel/no-pill.css", "r") as f:
+ gnome_shell_theme += f.read()
+
+ if args.panel_text_color:
+ gnome_shell_theme += ".panel-button,\
+ .clock,\
+ .clock-display StIcon {\
+ color: rgba(" + ', '.join(map(str, hex_to_rgba(args.panel_text_color))) + ");\
+ }"
+
+ # dock tweaks
+ if args.launchpad:
+ with open(f"{config.tweaks_folder}/launchpad/launchpad.css", "r") as f:
+ gnome_shell_theme += f.read()
+
+ gnome_shell_theme *= f"{config.tweaks_folder}/launchpad/launchpad.png"
+
+ # what argument colors defined
+ if args.all:
+ # install hue colors listed in colors.json
+ for color in colors["colors"]:
hue = colors["colors"][color]["h"]
# if saturation already defined in color (gray)
sat = colors["colors"][color]["s"] if colors["colors"][color]["s"] is not None else args.sat
- install_color(hue, color, mode, sat)
+ gnome_shell_theme.install(hue, color, sat)
- # custom color
- elif args.hue:
- hue = args.hue
- theme_name = args.name if args.name else f'hue{hue}' # if defined name
+ elif args.red or args.pink or args.purple or args.blue or args.green or args.yellow or args.gray:
+ # install selected colors
+ for color in colors["colors"]:
+ if getattr(args, color): # if argument name is in defined colors
+ hue = colors["colors"][color]["h"]
+ # if saturation already defined in color (gray)
+ sat = colors["colors"][color]["s"] if colors["colors"][color]["s"] is not None else args.sat
- install_color(hue, theme_name, mode, args.sat)
+ gnome_shell_theme.install(hue, color, sat)
- else:
- print('No arguments or no color arguments specified. Use -h or --help to see the available options.')
+ # custom color
+ elif args.hue:
+ hue = args.hue
+ theme_name = args.name if args.name else f'hue{hue}' # if defined name
+
+ gnome_shell_theme.install(hue, theme_name, args.sat)
+
+ else:
+ print('No arguments or no color arguments specified. Use -h or --help to see the available options.')
if __name__ == "__main__":
- colors = json.load(open("colors.json")) # used as database for replacing colors
-
main()
- os.system(f"rm -r {temp_folder}")
+ shutil.rmtree(config.temp_folder, ignore_errors=True)
diff --git a/readme-images/gdm.png b/readme-images/gdm.png
new file mode 100644
index 0000000..1490c13
Binary files /dev/null and b/readme-images/gdm.png differ
diff --git a/scripts/__init__.py b/scripts/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/scripts/config.py b/scripts/config.py
new file mode 100644
index 0000000..0833347
--- /dev/null
+++ b/scripts/config.py
@@ -0,0 +1,17 @@
+# folder definitions
+temp_folder = ".temp"
+gnome_folder = "gnome-shell"
+temp_gnome_folder = f"{temp_folder}/{gnome_folder}"
+tweaks_folder = "tweaks"
+themes_folder = "~/.themes"
+raw_theme_folder = "theme"
+scripts_folder = "scripts"
+
+# GDM definitions
+global_gnome_shell_theme = "/usr/share/gnome-shell"
+gnome_shell_gresource = "gnome-shell-theme.gresource"
+extracted_gdm_folder = "theme"
+
+# files definitions
+gnome_shell_css = f"{temp_gnome_folder}/gnome-shell.css"
+colors_json = "colors.json"
diff --git a/scripts/gdm.py b/scripts/gdm.py
new file mode 100644
index 0000000..ea4fa3e
--- /dev/null
+++ b/scripts/gdm.py
@@ -0,0 +1,213 @@
+import os
+import subprocess
+import shutil
+
+from .theme import Theme
+from .utils import label_files, remove_properties, remove_keywords
+from . import config
+
+
+class GlobalTheme:
+ def __init__(self, colors_json, theme_folder, destination_folder, destination_file, temp_folder,
+ is_filled=False):
+ """
+ Initialize GlobalTheme class
+ :param colors_json: location of a json file with colors
+ :param theme_folder: raw theme location
+ :param destination_folder: folder where themes will be installed
+ :param temp_folder: folder where files will be collected
+ :param is_filled: if True, theme will be filled
+ """
+
+ self.colors_json = colors_json
+ self.theme_folder = theme_folder
+ self.destination_folder = destination_folder
+ self.destination_file = destination_file
+ self.temp_folder = f"{temp_folder}/gdm"
+
+ self.backup_file = f"{self.destination_file}.backup"
+ self.backup_trigger = "\n/* Marble theme */\n" # trigger to check if theme is installed
+ self.extracted_theme = f"{self.temp_folder}/{config.extracted_gdm_folder}"
+ self.gst = f"{self.destination_folder}/{self.destination_file}" # use backup file if theme is installed
+ self.extracted_light_theme = f"{self.extracted_theme}/gnome-shell-light.css"
+ self.extracted_dark_theme = f"{self.extracted_theme}/gnome-shell-dark.css"
+
+ os.makedirs(self.temp_folder, exist_ok=True) # create temp folder
+
+ # create light and dark themes
+ self.light_theme = Theme("gnome-shell-light", self.colors_json, self.theme_folder,
+ self.extracted_theme, self.temp_folder, mode='light', is_filled=is_filled)
+ self.dark_theme = Theme("gnome-shell-dark", self.colors_json, self.theme_folder,
+ self.extracted_theme, self.temp_folder, mode='dark', is_filled=is_filled)
+
+ def __del__(self):
+ """
+ Delete temp folder
+ """
+
+ del self.light_theme
+ del self.dark_theme
+
+ shutil.rmtree(self.temp_folder)
+
+ def __is_installed(self):
+ """
+ Check if theme is installed
+ :return: True if theme is installed, False otherwise
+ """
+
+ with open(f"{self.destination_folder}/{self.destination_file}", "rb") as f:
+ content = f.read()
+ return self.backup_trigger.encode() in content
+
+ def __extract(self):
+ """
+ Extract gresource files to temp folder
+ """
+
+ print("Extracting gresource files...")
+
+ gst = self.gst
+ workdir = self.temp_folder
+
+ # Get the list of resources
+ resources = subprocess.getoutput(f"gresource list {gst}").split("\n")
+
+ # Create directories
+ for r in resources:
+ r = r.replace("/org/gnome/shell/", "")
+ directory = os.path.join(workdir, os.path.dirname(r))
+ os.makedirs(directory, exist_ok=True)
+
+ # Extract resources
+ for r in resources:
+ output_path = os.path.join(workdir, r.replace("/org/gnome/shell/", ""))
+ subprocess.run(f"gresource extract {gst} {r} > {output_path}", shell=True)
+
+ def __add_gnome_styles(self, theme):
+ """
+ Add gnome styles to the start of the file
+ :param theme: Theme object
+ """
+
+ with open(f"{self.extracted_theme}/{theme.theme_type}.css", 'r') as gnome_theme:
+ gnome_styles = gnome_theme.read() + self.backup_trigger
+ theme.add_to_start(gnome_styles)
+
+ def __prepare(self, hue, color, sat=None):
+ """
+ Generate theme files for gnome-shell-theme.gresource.xml
+ :param hue: color hue
+ :param color: color name
+ :param sat: color saturation
+ """
+
+ # add -light label to light theme files because they are installed to the same folder
+ label_files(self.light_theme.temp_folder, "light", self.light_theme.main_styles)
+
+ # remove !important from the gnome file
+ remove_keywords(self.extracted_light_theme, "!important")
+ remove_keywords(self.extracted_dark_theme, "!important")
+
+ # remove properties from the gnome file
+ props_to_remove = ("background-color", "color", "box-shadow", "border-radius")
+ remove_properties(self.extracted_light_theme, *props_to_remove)
+ remove_properties(self.extracted_dark_theme, *props_to_remove)
+
+ # add gnome styles to the start of the file
+ self.__add_gnome_styles(self.light_theme)
+ self.__add_gnome_styles(self.dark_theme)
+
+ # build code for gnome-shell-theme.gresource.xml
+ self.light_theme.install(hue, color, sat, destination=self.extracted_theme)
+ self.dark_theme.install(hue, color, sat, destination=self.extracted_theme)
+
+ def __backup(self):
+ """
+ Backup installed theme
+ """
+
+ if self.__is_installed():
+ return
+
+ # backup installed theme
+ print("Backing up default theme...")
+ os.system(f"cp -aT {self.gst} {self.gst}.backup")
+
+ def __generte_gresource_xml(self):
+ """
+ Generates.gresource.xml
+ """
+
+ # list of files to add to gnome-shell-theme.gresource.xml
+ files = list(f"{file}" for file in os.listdir(self.extracted_theme))
+ nl = "\n" # fstring doesn't support newline character
+
+ ready_xml = f"""
+
+
+ {nl.join(files)}
+
+"""
+
+ return ready_xml
+
+ def install(self, hue, sat=None):
+ """
+ Install theme globally
+ :param hue: color hue
+ :param sat: color saturation
+ """
+
+ if self.__is_installed():
+ print("Theme is installed. Reinstalling...")
+ self.gst += ".backup"
+
+ self.__extract()
+
+ # generate theme files for global theme
+ self.__prepare(hue, 'Marble', sat)
+
+ # generate gnome-shell-theme.gresource.xml
+ with open(f"{self.extracted_theme}/{self.destination_file}.xml", 'w') as gresource_xml:
+ generated_xml = self.__generte_gresource_xml()
+ gresource_xml.write(generated_xml)
+
+ # compile gnome-shell-theme.gresource.xml
+ print("Compiling theme...")
+ subprocess.run(f"glib-compile-resources {self.destination_file}.xml",
+ shell=True, cwd=self.extracted_theme)
+
+ # backup installed theme
+ self.__backup()
+
+ # install theme
+ print("Installing theme...")
+ os.system(f"sudo cp -f {self.extracted_theme}/{self.destination_file} "
+ f"{self.destination_folder}/{self.destination_file}")
+
+ return 0
+
+ def remove(self):
+ """
+ Remove installed theme
+ """
+
+ # use backup file if theme is installed
+ if self.__is_installed():
+ print("Theme is installed. Removing...")
+
+ if os.path.isfile(f"{self.destination_folder}/{self.backup_file}"):
+ subprocess.run(f"sudo mv {self.backup_file} {self.destination_file}",
+ shell=True, cwd=self.destination_folder)
+
+ else:
+ print("Backup file not found. Try reinstalling gnome-shell package.")
+ return 1
+
+ else:
+ print("Theme is not installed. Nothing to remove.")
+ print("If theme is still installed globally, try reinstalling gnome-shell package.")
+ return 1
+
+ return 0
diff --git a/scripts/tests.py b/scripts/tests.py
new file mode 100644
index 0000000..b8ff790
--- /dev/null
+++ b/scripts/tests.py
@@ -0,0 +1,56 @@
+import unittest
+import os
+import json
+import shutil
+
+from . import config
+from .theme import Theme
+
+# folders
+tests_folder = '.tests'
+project_folder = '.'
+
+
+class TestInstall(unittest.TestCase):
+
+ def test_install_theme(self):
+ """
+ Test if theme is installed correctly (colors are replaced)
+ """
+
+ # folders
+ themes_folder = f"{tests_folder}/.themes"
+ temp_folder = f"{tests_folder}/.temp"
+
+ # colors from colors.json
+ colors_json = open(f"{project_folder}/{config.colors_json}")
+ colors = json.load(colors_json)
+ colors_json.close()
+
+ # create test theme
+ test_theme = Theme("gnome-shell", colors,
+ f"{project_folder}/{config.raw_theme_folder}/{config.gnome_folder}",
+ themes_folder, temp_folder,
+ mode='light', is_filled=True)
+
+ # install test theme
+ test_theme.install(120, 'test', 70)
+
+ # folder with installed theme (.tests/.themes/Marble-test-light/gnome-shell)
+ installed_theme = f"{themes_folder}/{os.listdir(themes_folder)[0]}/{config.gnome_folder}"
+
+ # check if files are installed
+ for file in os.listdir(installed_theme):
+ with open(f"{installed_theme}/{file}") as f:
+ read_file = f.read()
+
+ for color in colors["elements"]:
+ self.assertNotIn(color, read_file, msg=f"Color {color} is not replaced in {file}")
+
+ # delete test theme
+ del test_theme
+ shutil.rmtree(tests_folder)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/scripts/theme.py b/scripts/theme.py
new file mode 100644
index 0000000..abb4f22
--- /dev/null
+++ b/scripts/theme.py
@@ -0,0 +1,163 @@
+import os
+import shutil
+import colorsys # colorsys.hls_to_rgb(h, l, s)
+
+from .utils import (
+ replace_keywords, # replace keywords in file
+ copy_files, # copy files from source to destination
+ destination_return, # copied/modified theme location
+ generate_file) # combine files from folder to one file
+
+
+class Theme:
+ def __init__(self, theme_type, colors_json, theme_folder, destination_folder, temp_folder,
+ mode=None, is_filled=False):
+ """
+ Initialize Theme class
+ :param colors_json: location of a json file with colors
+ :param theme_type: theme type (gnome-shell, gtk, etc.)
+ :param theme_folder: raw theme location
+ :param destination_folder: folder where themes will be installed
+ :param temp_folder: folder where files will be collected
+ :param mode: theme mode (light or dark)
+ :param is_filled: if True, theme will be filled
+ """
+
+ self.colors = colors_json
+ self.temp_folder = f"{temp_folder}/{theme_type}"
+ self.theme_folder = theme_folder
+ self.theme_type = theme_type
+ self.mode = [mode] if mode else ['light', 'dark']
+ self.destination_folder = destination_folder
+ self.main_styles = f"{self.temp_folder}/{theme_type}.css"
+
+ # move files to temp folder
+ copy_files(self.theme_folder, self.temp_folder)
+ generate_file(f"{self.theme_folder}_css/", self.main_styles)
+
+ # if theme is filled
+ if is_filled:
+ for apply_file in os.listdir(f"{self.temp_folder}/"):
+ replace_keywords(f"{self.temp_folder}/{apply_file}",
+ ("BUTTON-COLOR", "ACCENT-FILLED-COLOR"),
+ ("BUTTON_HOVER", "ACCENT-FILLED_HOVER"),
+ ("BUTTON_INSENSITIVE", "ACCENT-FILLED_INSENSITIVE"),
+ ("BUTTON-TEXT-COLOR", "TEXT-BLACK-COLOR"),
+ ("BUTTON-TEXT_SECONDARY", "TEXT-BLACK_SECONDARY"))
+
+ def __add__(self, other):
+ """
+ Add to main styles another styles
+ :param other: styles to add
+ :return: new Theme object
+ """
+
+ with open(self.main_styles, 'a') as main_styles:
+ main_styles.write('\n' + other)
+ return self
+
+ def __mul__(self, other):
+ """
+ Copy files to temp folder
+ :param other: file or folder
+ :return: new Theme object
+ """
+
+ if os.path.isfile(other):
+ shutil.copy(other, self.temp_folder)
+ else:
+ shutil.copytree(other, self.temp_folder)
+
+ return self
+
+ def __del__(self):
+ # delete temp folder
+ os.system(f"rm -r {self.temp_folder}")
+
+ def __apply_colors(self, hue, destination, theme_mode, apply_file, sat=None):
+ """
+ Install accent colors from colors.json to different file
+ :param hue
+ :param destination: file directory
+ :param theme_mode: theme name (light or dark)
+ :param apply_file: file name
+ :param sat: color saturation (optional)
+ """
+
+ # list of (keyword, replaced value)
+ replaced_colors = list()
+
+ # colorsys works in range(0, 1)
+ h = hue / 360
+ for element in self.colors["elements"]:
+ # if color has default color and hasn't been replaced
+ if theme_mode not in self.colors["elements"][element] and self.colors["elements"][element]["default"]:
+ default_element = self.colors["elements"][element]["default"]
+ default_color = self.colors["elements"][default_element][theme_mode]
+ self.colors["elements"][element][theme_mode] = default_color
+
+ # convert sla to range(0, 1)
+ lightness = int(self.colors["elements"][element][theme_mode]["l"]) / 100
+ saturation = int(self.colors["elements"][element][theme_mode]["s"]) / 100 if sat is None else \
+ int(self.colors["elements"][element][theme_mode]["s"]) * (sat / 100) / 100
+ alpha = self.colors["elements"][element][theme_mode]["a"]
+
+ # convert hsl to rgb and multiply every item
+ red, green, blue = [int(item * 256) for item in colorsys.hls_to_rgb(h, lightness, saturation)]
+
+ replaced_colors.append((element, f"rgba({red}, {green}, {blue}, {alpha})"))
+
+ # replace colors
+ replace_keywords(os.path.expanduser(f"{destination}/{apply_file}"), *replaced_colors)
+
+ def __apply_theme(self, hue, source, destination, theme_mode, sat=None):
+ """
+ Apply theme to all files in directory
+ :param hue
+ :param source
+ :param destination: file directory
+ :param theme_mode: theme name (light or dark)
+ :param sat: color saturation (optional)
+ """
+
+ for apply_file in os.listdir(f"{source}/"):
+ self.__apply_colors(hue, destination, theme_mode, apply_file, sat=sat)
+
+ def install(self, hue, name, sat=None, destination=None):
+ """
+ Copy files and generate theme with different accent color
+ :param hue
+ :param name: theme name
+ :param sat: color saturation (optional)
+ :param destination: folder where theme will be installed
+ """
+
+ is_dest = bool(destination)
+
+ print(f"Creating {name} {', '.join(self.mode)} theme...", end=" ")
+
+ try:
+ for mode in self.mode:
+ if not is_dest:
+ destination = destination_return(self.destination_folder, name, mode, self.theme_type)
+
+ copy_files(self.temp_folder + '/', destination)
+ self.__apply_theme(hue, self.temp_folder, destination, mode, sat=sat)
+
+ except Exception as err:
+ print("\nError: " + str(err))
+
+ else:
+ print("Done.")
+
+ def add_to_start(self, content):
+ """
+ Add content to the start of main styles
+ :param content: content to add
+ """
+
+ with open(self.main_styles, 'r+') as main_styles:
+ main_content = main_styles.read()
+
+ main_styles.seek(0)
+ main_styles.write(content + '\n' + main_content)
diff --git a/scripts/utils.py b/scripts/utils.py
new file mode 100644
index 0000000..3f7fbf9
--- /dev/null
+++ b/scripts/utils.py
@@ -0,0 +1,223 @@
+import os
+from . import config # name of folders and files
+
+
+def generate_file(folder, final_file):
+ """
+ Combines all files in a folder into a single file
+ :param folder: source folder
+ :param final_file: location where file will be created
+ """
+
+ opened_file = open(final_file, "w")
+
+ for file in os.listdir(folder):
+ with open(folder + file) as f:
+ opened_file.write(f.read() + '\n')
+
+ opened_file.close()
+
+
+def concatenate_files(edit_file, file):
+ """
+ Merge two files
+ :param edit_file: where it will be appended
+ :param file: file you want to append
+ """
+
+ with open(file, 'r') as read_file:
+ file_content = read_file.read()
+
+ with open(edit_file, 'a') as write_file:
+ write_file.write('\n' + file_content)
+
+
+def remove_files():
+ """
+ Delete already installed Marble theme
+ """
+
+ paths = (config.themes_folder, "~/.local/share/themes")
+
+ print("💡 You do not need to delete files if you want to update theme.\n")
+
+ confirmation = input(f"Do you want to delete all \"Marble\" folders in {' and in '.join(paths)}? (y/N) ").lower()
+
+ if confirmation == "y":
+ for path in paths:
+
+ # Check if the path exists
+ if os.path.exists(os.path.expanduser(path)):
+
+ # Get the list of folders in the path
+ folders = os.listdir(os.path.expanduser(path))
+
+ # toggle if folder has no marble theme
+ found_folder = False
+
+ for folder in folders:
+ if folder.startswith("Marble"):
+ folder_path = os.path.join(os.path.expanduser(path), folder)
+ print(f"Deleting folder {folder_path}...", end='')
+
+ try:
+ os.system(f"rm -r {folder_path}")
+
+ except Exception as e:
+ print(f"Error deleting folder {folder_path}: {e}")
+
+ else:
+ found_folder = True
+ print("Done.")
+
+ if not found_folder:
+ print(f"No folders starting with \"Marble\" found in {path}.")
+
+ else:
+ print(f"The path {path} does not exist.")
+
+
+def destination_return(themes_folder, path_name, theme_mode, theme_type):
+ """
+ Copied/modified theme location
+ :param themes_folder: themes folder location
+ :param path_name: color name
+ :param theme_mode: theme name (light or dark)
+ :param theme_type: theme type (gnome-shell, gtk-4.0, ...)
+ :return: copied files' folder location
+ """
+
+ return f"{themes_folder}/Marble-{path_name}-{theme_mode}/{theme_type}/"
+
+
+def copy_files(source, destination):
+ """
+ Copy files from the source to another directory
+ :param source: where files will be copied
+ :param destination: where files will be pasted
+ """
+
+ destination = os.path.expanduser(destination) # expand ~ to /home/user
+ os.makedirs(destination, exist_ok=True)
+ os.system(f"cp -aT {source} {destination}")
+
+
+def replace_keywords(file, *args):
+ """
+ Replace file with several keywords
+ :param file: file name where keywords must be replaced
+ :param args: (keyword, replacement), (...), ...
+ """
+
+ # skip binary files in project
+ if not file.lower().endswith(('.css', '.scss', '.svg')):
+ return
+
+ with open(file, "r") as read_file:
+ content = read_file.read()
+
+ for keyword, replacement in args:
+ content = content.replace(keyword, replacement)
+
+ with open(file, "w") as write_file:
+ write_file.write(content)
+
+
+def hex_to_rgba(hex_color):
+ """
+ Convert hex(a) to rgba
+ :param hex_color: input value
+ """
+
+ try:
+ if len(hex_color) in range(6, 10):
+ hex_color = hex_color.lstrip('#') + "ff"
+ # if is convertable
+ int(hex_color[:], 16)
+ else:
+ raise ValueError
+
+ except ValueError:
+ raise ValueError(f'Error: Invalid HEX color code: {hex_color}')
+
+ else:
+ return int(hex_color[0:2], 16), \
+ int(hex_color[2:4], 16), \
+ int(hex_color[4:6], 16), \
+ int(hex_color[6:8], 16) / 255
+
+
+def label_files(directory, label, *args):
+ """
+ Add a label to all files in a directory
+ :param directory: folder where files are located
+ :param label: label to add
+ :param args: files to change links to labeled files
+ :return:
+ """
+
+ # Open all files
+ files = [open(file, 'r+') for file in args]
+ read_files = [file.read() for file in files]
+
+ for filename in os.listdir(directory):
+ # Skip if the file is already labeled
+ if label in filename:
+ continue
+
+ # Split the filename into name and extension
+ name, extension = os.path.splitext(filename)
+
+ # Form the new filename and rename the file
+ new_filename = f"{name}-{label}{extension}"
+ os.rename(os.path.join(directory, filename), os.path.join(directory, new_filename))
+
+ # Replace the filename in all files
+ for file in read_files:
+ file.replace(filename, new_filename)
+
+ # Write the changes to the files and close them
+ for i, file in enumerate(files):
+ file.seek(0)
+ file.write(read_files[i])
+ file.close()
+
+
+def remove_properties(file, *args):
+ """
+ Remove properties from a file
+ :param file: file name
+ :param args: properties to remove
+ """
+
+ new_content = ""
+
+ with open(file, "r+") as read_file:
+ content = read_file.read()
+
+ for line in content.splitlines():
+ if not any(prop in line for prop in args):
+ new_content += line + "\n"
+ elif "}" in line:
+ new_content += "}\n"
+
+ read_file.seek(0)
+ read_file.write(new_content)
+
+
+def remove_keywords(file, *args):
+ """
+ Remove keywords from a file
+ :param file: file name
+ :param args: keywords to remove
+ """
+
+ with open(file, "r+") as read_file:
+ content = read_file.read()
+
+ for arg in args:
+ content = content.replace(arg, "")
+
+ read_file.seek(0)
+ read_file.write(content)
+
\ No newline at end of file
diff --git a/theme/gnome-shell_css/apps.css b/theme/gnome-shell_css/apps.css
index 259a213..3bdda4b 100644
--- a/theme/gnome-shell_css/apps.css
+++ b/theme/gnome-shell_css/apps.css
@@ -104,9 +104,9 @@
/* Page arrow */
-
.page-navigation-arrow {
- border-radius: 99px;
+ border-radius: 15px;
+ padding: 12px;
transition-duration: 100ms;
}
diff --git a/theme/gnome-shell_css/controls.css b/theme/gnome-shell_css/controls.css
index 7edb00f..a4f58c7 100644
--- a/theme/gnome-shell_css/controls.css
+++ b/theme/gnome-shell_css/controls.css
@@ -81,6 +81,7 @@
.button:insensitive {
box-shadow: none;
color: TEXT-SECONDARY-COLOR;
+ box-shadow: inset 0 0 0 1px BORDER-SHADOW;
}
.button:hover,
@@ -104,12 +105,10 @@
.slider,
.level {
- height: 16px;
-barlevel-height: 16px;
-barlevel-background-color: ACCENT-DISABLED-COLOR;
- box-shadow: inset 0 0 0 1px BORDER-SHADOW;
- /* fill */
-barlevel-active-background-color: BUTTON-COLOR;
+ -barlevel-border-width: 0px;
/* overfill */
-barlevel-overdrive-color: #c01c28;
-barlevel-overdrive-separator-width: 2px;
diff --git a/theme/gnome-shell_css/entries.css b/theme/gnome-shell_css/entries.css
index ceb6a31..b654227 100644
--- a/theme/gnome-shell_css/entries.css
+++ b/theme/gnome-shell_css/entries.css
@@ -1,6 +1,7 @@
/* Entries */
-StEntry {
+StEntry,
+.search-entry {
border-radius: 13px;
padding: 8px;
transition-duration: 100ms;
@@ -31,14 +32,4 @@ StEntry:active {
StEntry:focus {
color: TEXT-PRIMARY-COLOR;
-}
-
-
-/* Entry in lock screen */
-.unlock-dialog StEntry {
- border: none !important;
-}
-
-.unlock-dialog StEntry:focus {
- box-shadow: inset 0 0 0 1px TEXT-DISABLED-COLOR;
}
\ No newline at end of file
diff --git a/theme/gnome-shell_css/loginlock.css b/theme/gnome-shell_css/loginlock.css
new file mode 100644
index 0000000..74da88d
--- /dev/null
+++ b/theme/gnome-shell_css/loginlock.css
@@ -0,0 +1,179 @@
+/* Lock Screen / Login screen */
+
+#lockDialogGroup {
+ background-color: #000;
+}
+
+.login-dialog {
+ background-color: BACKGROUND-OPAQUE-COLOR;
+}
+
+
+.login-dialog .caps-lock-warning-label,
+.login-dialog .login-dialog-message-warning,
+.unlock-dialog .caps-lock-warning-label,
+.unlock-dialog .login-dialog-message-warning {
+ color: TEXT-SECONDARY-COLOR;
+}
+
+
+/* not listed button */
+.login-dialog-not-listed-label {
+ color: TEXT-SECONDARY-COLOR;
+}
+.login-dialog-not-listed-button:focus .login-dialog-not-listed-label,
+.login-dialog-not-listed-button:hover .login-dialog-not-listed-label {
+ color: TEXT-PRIMARY-COLOR;
+}
+.login-dialog-not-listed-button:focus .login-dialog-not-listed-label {
+ text-decoration: underline;
+}
+
+
+/* users button */
+.login-dialog-user-list .login-dialog-user-list-item {
+ background-color: SECTION-COLOR;
+ border-radius: 20px;
+ box-shadow: inset 0 0 0 1px BORDER-SHADOW;
+}
+
+.login-dialog-user-list:expanded .login-dialog-user-list-item {
+ transition-duration: 200ms;
+}
+
+.login-dialog-user-list:expanded .login-dialog-user-list-item:hover {
+ background-color: ACCENT-LOGIN-COLOR;
+}
+
+.login-dialog-user-list:expanded .login-dialog-user-list-item:focus,
+.login-dialog-user-list:expanded .login-dialog-user-list-item:selected {
+ background-color: ACCENT-LOGIN-COLOR;
+ /* color: BUTTON-TEXT-COLOR; don't work*/
+ box-shadow: inset 0 0 0 1px BORDER-MENU-SHADOW;
+}
+
+.login-dialog-user-list:expanded .login-dialog-user-list-item:focus:hover,
+.login-dialog-user-list:expanded .login-dialog-user-list-item:selected:hover {
+ background-color: ACCENT-LOGIN_HOVER;
+}
+
+
+/* login buttons */
+.login-dialog-button.cancel-button,
+.login-dialog-button.switch-user-button,
+.login-dialog-button.login-dialog-session-list-button {
+ background-color: ACCENT-DISABLED-COLOR;
+ color: TEXT-PRIMARY-COLOR;
+ box-shadow: inset 0 0 0 1px BORDER-SHADOW;
+ border-radius: 99px;
+ transition-duration: 100ms;
+}
+
+.login-dialog-button.cancel-button:hover,
+.login-dialog-button.switch-user-button:hover,
+.login-dialog-button.login-dialog-session-list-button:hover {
+ background-color: ACCENT-DISABLED_HOVER;
+}
+
+.login-dialog-button.cancel-button:focus,
+.login-dialog-button.switch-user-button:focus,
+.login-dialog-button.login-dialog-session-list-button:focus {
+ background-color: ACCENT-DISABLED_HOVER;
+ box-shadow: inset 0 0 0 2px ACCENT-SECONDARY-COLOR !important;
+}
+
+.login-dialog-button.cancel-button:focus:active,
+.login-dialog-button.switch-user-button:focus:active,
+.login-dialog-button.login-dialog-session-list-button:focus:active,
+.login-dialog-button.cancel-button:focus:hover,
+.login-dialog-button.switch-user-button:focus:hover,
+.login-dialog-button.login-dialog-session-list-button:focus:hover {
+ background-color: ACCENT-DISABLED_HOVER;
+ box-shadow: inset 0 0 0 1px ACCENT-SECONDARY-COLOR !important;
+}
+
+.login-dialog-button.cancel-button:active,
+.login-dialog-button.switch-user-button:active,
+.login-dialog-button.login-dialog-session-list-button:active {
+ background-color: ACCENT-DISABLED_HOVER;
+}
+
+
+/* password */
+.login-dialog-prompt-entry {
+ background-color: ACCENT-DISABLED-COLOR;
+ border: none;
+ box-shadow: inset 0 0 0 1px BORDER-SHADOW;
+}
+
+.login-dialog-prompt-entry:focus {
+ background-color: ACCENT-DISABLED-COLOR !important;
+ border: none;
+ box-shadow: inset 0 0 0 1px TEXT-DISABLED-COLOR !important;
+ border-color: TEXT-DISABLED-COLOR;
+}
+
+.login-dialog-prompt-entry:insensitive {
+ background-color: ACCENT-DISABLED-COLOR;
+ color: TEXT-DISABLED-COLOR;
+}
+
+
+/* Clock */
+.unlock-dialog-clock {
+ color: TEXT-PRIMARY-COLOR;
+ spacing: 1em;
+}
+
+.unlock-dialog-clock .unlock-dialog-clock-time {
+ font-feature-settings: "normal";
+ font-weight: 500;
+}
+
+.unlock-dialog-clock .unlock-dialog-clock-date {
+ font-weight: 500;
+ font-size: 16pt;
+}
+
+.unlock-dialog-clock .unlock-dialog-clock-hint {
+ margin-top: 1em;
+ font-weight: 600;
+ font-size: 10pt;
+ color: TEXT-SECONDARY-COLOR;
+}
+
+
+/* Notifications */
+.unlock-dialog-notifications-container .notification,
+.unlock-dialog-notifications-container .unlock-dialog-notification-source {
+ padding: 12px 16px;
+ box-shadow: inset 0 0 0 1px BORDER-SHADOW;
+ background-color: ACCENT-DISABLED-COLOR;
+ color: TEXT-PRIMARY-COLOR;
+ border-radius: 16px;
+}
+.unlock-dialog-notifications-container .notification.critical,
+.unlock-dialog-notifications-container .unlock-dialog-notification-source.critical {
+ background-color: ACCENT_HOVER;
+}
+
+/* messages counter */
+.unlock-dialog-notification-count-text {
+ color: TEXT-SECONDARY-COLOR;
+ background-color: ACCENT-DISABLED-COLOR;
+ box-shadow: inset 0 0 0 1px BORDER-SHADOW;
+ border-radius: 99px;
+}
+
+
+/* User widget */
+.user-widget .user-widget-label {
+ color: TEXT-PRIMARY-COLOR;
+}
+
+.user-widget.horizontal .user-icon StIcon,
+.user-widget.vertical .user-icon StIcon {
+ color: TEXT-PRIMARY-COLOR;
+ background-color: ACCENT-DISABLED-COLOR;
+ box-shadow: inset 0 0 0 1px BORDER-SHADOW;
+}
\ No newline at end of file
diff --git a/theme/gnome-shell_css/messages.css b/theme/gnome-shell_css/messages.css
index c9faeaa..4b4b947 100644
--- a/theme/gnome-shell_css/messages.css
+++ b/theme/gnome-shell_css/messages.css
@@ -2,8 +2,8 @@
/* datemenu message-list */
.message-list {
- border: none; /* remove border between messages and calendar section */
- padding: 0;
+ border-color: transparent; /* remove border between messages and calendar section */
+ padding: 0 !important;
}
/* "No Notifications" text */
diff --git a/theme/gnome-shell_css/osd.css b/theme/gnome-shell_css/osd.css
index fadc413..0246a72 100644
--- a/theme/gnome-shell_css/osd.css
+++ b/theme/gnome-shell_css/osd.css
@@ -42,6 +42,15 @@
/* ctrl + alt + arr_left/arr_right */
.ws-switcher-indicator {
background-color: TEXT-SECONDARY-COLOR;
+ border-radius: 99px;
+ padding: 2.5px;
+ margin: 7.5px 5px;
+}
+
+.ws-switcher-indicator:active {
+ background-color: TEXT-PRIMARY-COLOR;
+ padding: 5px 20px;
+ margin: 5px;
}
diff --git a/theme/gnome-shell_css/overview.css b/theme/gnome-shell_css/overview.css
index bb8806c..704a059 100644
--- a/theme/gnome-shell_css/overview.css
+++ b/theme/gnome-shell_css/overview.css
@@ -35,9 +35,8 @@
/* thumbnails on overview (workspaces list under search) */
.workspace-thumbnail {
- border: none; /* because of impossibility round fullscreen apps */
- background-color: ACCENT-DISABLED-COLOR;
- border: 1px solid BORDER-SHADOW;
+ background-color: ACCENT-DISABLED-COLOR !important;
+ border: 1px solid BORDER-SHADOW !important;
}
/* drag and drop indicator */
diff --git a/theme/gnome-shell_css/panel.css b/theme/gnome-shell_css/panel.css
index 44af478..00ed913 100644
--- a/theme/gnome-shell_css/panel.css
+++ b/theme/gnome-shell_css/panel.css
@@ -20,7 +20,7 @@
.clock-display StIcon { /* DND / new messages icon */
color: TEXT-PRIMARY-COLOR;
border-radius: 14px;
- border: 4px solid transparent;
+ border: 4px solid transparent !important;
background-color: ACCENT-DISABLED-COLOR;
box-shadow: inset 0 0 0 1px BORDER-SHADOW;
}
@@ -35,6 +35,7 @@
/* workspaces indicator in activities button (45+) */
#panel .workspace-dot {
background-color: TEXT-PRIMARY-COLOR;
+ border-radius: 99px;
}
/* indicator for active */
diff --git a/theme/gnome-shell_css/popovers.css b/theme/gnome-shell_css/popovers.css
index 360df9b..937dba2 100644
--- a/theme/gnome-shell_css/popovers.css
+++ b/theme/gnome-shell_css/popovers.css
@@ -13,6 +13,7 @@
.popup-menu-item, /* menu items */
.app-menu { /* right-click (and panel) app menu */
margin: 3px 0;
+ border-radius: 12px;
transition-duration: 150ms;
}
@@ -64,6 +65,7 @@
margin: 0;
color: TEXT-PRIMARY-COLOR;
background-color: transparent;
+ border-radius: 0;
}
/* make rounded borders for last element */
diff --git a/theme/gnome-shell_css/quick-settings.css b/theme/gnome-shell_css/quick-settings.css
index 881907b..3242ea8 100644
--- a/theme/gnome-shell_css/quick-settings.css
+++ b/theme/gnome-shell_css/quick-settings.css
@@ -12,6 +12,7 @@
background-color: ACCENT-DISABLED-COLOR;
color: TEXT-PRIMARY-COLOR;
transition-duration: 100ms;
+ border-radius: 99px;
box-shadow: inset 0 0 0 1px BORDER-SHADOW;
}
@@ -67,6 +68,7 @@
.quick-toggle:hover,
.quick-menu-toggle .quick-toggle:hover,
.quick-menu-toggle .quick-toggle-arrow:hover {
+ color: TEXT-PRIMARY-COLOR;
background-color: ACCENT-DISABLED_HOVER;
}
@@ -81,16 +83,18 @@
.quick-toggle:checked:hover,
.quick-menu-toggle .quick-toggle:checked:hover,
.quick-menu-toggle .quick-toggle-arrow:checked:hover {
+ color: BUTTON-TEXT-COLOR;
background-color: BUTTON_HOVER;
}
/* quick slider section */
-/* fix margins in quick slider dection */
+/* fix margins in quick slider section */
.quick-slider .slider-bin {
- padding: 2px 1px;
+ padding: 4px;
margin: 0;
color: BUTTON-TEXT-COLOR;
+ border-radius: 99px;
}
.quick-slider .slider-bin:focus {
diff --git a/theme/gnome-shell_css/screenshot.css b/theme/gnome-shell_css/screenshot.css
index 8e02201..b3e47c7 100644
--- a/theme/gnome-shell_css/screenshot.css
+++ b/theme/gnome-shell_css/screenshot.css
@@ -53,6 +53,10 @@
background-color: SECTION-COLOR;
}
+.screenshot-ui-window-selector-window-border {
+ border-radius: 16px;
+}
+
/* window border when selecting */
.screenshot-ui-window-selector-window:hover .screenshot-ui-window-selector-window-border {
border-color: BUTTON-COLOR;
@@ -63,10 +67,13 @@
background-color: ACCENT-OPACITY-COLOR;
}
+.screenshot-ui-window-selector-check { color: transparent; }
+
/* check icon when selected window */
.screenshot-ui-window-selector-window:checked .screenshot-ui-window-selector-check {
color: BUTTON-TEXT-COLOR;
background-color: BUTTON-COLOR;
+ border-radius: 99px;
}
@@ -114,12 +121,14 @@
/* screenshot / screencast buttons section */
.screenshot-ui-shot-cast-container {
background-color: ACCENT-DISABLED-COLOR;
+ border-radius: 99px;
box-shadow: inset 0 0 0 1px BORDER-SHADOW;
}
/* screenshot / screencast buttons */
.screenshot-ui-shot-cast-button {
background-color: transparent;
+ border-radius: 99px;
transition-duration: 100ms;
}
@@ -135,6 +144,32 @@
color: TEXT-INVERTED-COLOR;
}
+.screenshot-ui-area-indicator-shade {
+ background-color: rgba(0, 0, 0, 0.5);
+}
+
+/* screen selection borders */
+.screenshot-ui-area-selector-handle {
+ border-radius: 99px;
+ background-color: white;
+ box-shadow: 0 1px 3px 2px rgba(0, 0, 0, 0.05);
+}
+
+
+.screenshot-ui-screen-selector {
+ background-color: rgba(0, 0, 0, 0.5);
+}
+.screenshot-ui-screen-selector:hover {
+ background-color: rgba(0, 0, 0, 0.3);
+}
+.screenshot-ui-screen-selector:active {
+ background-color: rgba(0, 0, 0, 0.7);
+}
+.screenshot-ui-screen-selector:checked {
+ background-color: transparent;
+}
+
+
/* tooltip for elements */
.screenshot-ui-tooltip {
@@ -152,6 +187,7 @@
background-color: BUTTON-CLOSE-COLOR;
color: TEXT-PRIMARY-COLOR;
border: 1px solid BORDER-SHADOW;
+ border-radius: 99px;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2);
transition-duration: 100ms;
}