diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index c697af30..b34c9226 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -1,12 +1,12 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
-patreon: # Replace with a single Patreon username
+patreon: L0drex # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
-liberapay: daehruoydeef
+liberapay: # liberapay user name
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with a single custom sponsorship URL
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
deleted file mode 100644
index 42253cd2..00000000
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ /dev/null
@@ -1,42 +0,0 @@
----
-name: Bug report
-about: Report if something doesn't work as expected
-title: ''
-labels: bug
-assignees: ''
-
----
-
-### Describe the bug
-
-
-### Enabled plugins
-
-What plugins did you have enabled? If not relevant, you can remove this.
-
-- [ ] Atom
-- [ ] Brave
-- [ ] Colors
-- [ ] Custom Script
-- [ ] Firefox
-- [ ] Gedit
-- [ ] GTK
-- [ ] Konsole
-- [ ] Kvantum
-- [ ] Only Office
-- [ ] System
-- [ ] VSCode
-- [ ] Wallpaper
-
-### Affected versions
-
-- Yin-Yang version:
-
-- Relevant application version[^1]:
-- Python version:
-- Qt version:
-
-[^1]: This refers to the application a plugin might use. For example, if you submit a bug report for the Firefox plugin, this refers to the Firefox version you are using. If the bug is in a plugin for a desktop environment, this refers to the DE (Plasma, Gnome, etc).
-
-### Additional notes
-
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
new file mode 100644
index 00000000..811323b4
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -0,0 +1,75 @@
+name: Bug Report
+description: File a bug report
+labels: ["bug"]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thanks for taking the time to fill out this bug report!
+ - type: textarea
+ id: what-happened
+ attributes:
+ label: What happened?
+ description: Also tell us, what did you expect to happen?
+ placeholder: Tell us what you see!
+ value: "A bug happened!"
+ validations:
+ required: true
+ - type: input
+ id: version
+ attributes:
+ label: Version
+ description: What version of Yin & Yang are you running?
+ validations:
+ required: true
+ - type: dropdown
+ id: source
+ attributes:
+ label: How did you install Yin & Yang?
+ options:
+ - Git clone + running install script
+ - AUR
+ - Flatpak
+ validations:
+ required: true
+ - type: dropdown
+ id: desktop
+ attributes:
+ label: What desktop environments are you seeing the problem on?
+ multiple: true
+ options:
+ - KDE
+ - Gnome
+ - Xfce
+ - Mate
+ - Cinnamon
+ - other
+ - type: dropdown
+ id: plugins
+ attributes:
+ label: Which plugin causes the issue?
+ options:
+ - Atom
+ - Brave
+ - Colors
+ - Custom Script
+ - Firefox
+ - Gedit
+ - GTK
+ - Konsole
+ - Kvantum
+ - Only Office
+ - System
+ - VSCode
+ - Wallpaper
+ - type: input
+ id: plugin_version
+ attributes:
+ label: What software version do you use?
+ description: For example, if you see a problem with the VSCode plugin, this would refer to the version of VSCode you have installed.
+ - type: textarea
+ id: logs
+ attributes:
+ label: Relevant log output
+ description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
+ render: shell
diff --git a/designer/main_window.ui b/designer/main_window.ui
index 22e8ed8d..3d6fad1a 100755
--- a/designer/main_window.ui
+++ b/designer/main_window.ui
@@ -296,13 +296,6 @@
- -
-
-
- Make a sound when switching the theme
-
-
-
-
@@ -374,8 +367,8 @@
0
0
- 518
- 88
+ 501
+ 86
diff --git a/requirements.txt b/requirements.txt
index 5e3f2fe9..c1ed9b88 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,6 +1,6 @@
-psutil==5.9.6
-PySide6==6.6.0
-PySide6-Addons==6.6.0
+psutil==5.9.7
+PySide6==6.6.1
+PySide6-Addons==6.6.1
suntime==1.2.5
systemd-python==235
requests~=2.31.0
\ No newline at end of file
diff --git a/resources/dark.wav b/resources/dark.wav
deleted file mode 100755
index 2f2cee78..00000000
Binary files a/resources/dark.wav and /dev/null differ
diff --git a/resources/light.wav b/resources/light.wav
deleted file mode 100755
index b7ee33ca..00000000
Binary files a/resources/light.wav and /dev/null differ
diff --git a/resources/translations/yin_yang.de_DE.qm b/resources/translations/yin_yang.de_DE.qm
index dfe9e222..1cde15fe 100644
Binary files a/resources/translations/yin_yang.de_DE.qm and b/resources/translations/yin_yang.de_DE.qm differ
diff --git a/resources/translations/yin_yang.de_DE.ts b/resources/translations/yin_yang.de_DE.ts
index ce9eecc2..a987ce5b 100644
--- a/resources/translations/yin_yang.de_DE.ts
+++ b/resources/translations/yin_yang.de_DE.ts
@@ -60,31 +60,26 @@
-
- Mache ein Geräusch, wenn das Thema geändert wird
-
-
-
Sende eine Benachrichtigung
-
+
Zeit die gewartet werden soll während das System startet. Standardwert ist 10 Sekunden.
-
+
Verzögerung nach Start
-
+
-
+
@@ -92,19 +87,19 @@
systray
-
+
Context menu action in the systray
Yin Yang öffnen
-
+
Context menu action in the systray
Farbschema wechseln
-
+
Context menu action in the systray
Beenden
diff --git a/resources/translations/yin_yang.nl_NL.qm b/resources/translations/yin_yang.nl_NL.qm
index 065f4499..96167969 100644
Binary files a/resources/translations/yin_yang.nl_NL.qm and b/resources/translations/yin_yang.nl_NL.qm differ
diff --git a/resources/translations/yin_yang.nl_NL.ts b/resources/translations/yin_yang.nl_NL.ts
index ff50bcde..f3898afe 100644
--- a/resources/translations/yin_yang.nl_NL.ts
+++ b/resources/translations/yin_yang.nl_NL.ts
@@ -60,31 +60,26 @@
-
- Geluid afspelen na instellen van ander thema
-
-
-
Melding tonen
-
+
-
+
-
+
-
+
Plug-ins
@@ -92,19 +87,19 @@
systray
-
+
Context menu action in the systray
-
+
Context menu action in the systray
-
+
Context menu action in the systray
diff --git a/resources/translations/yin_yang.zh_TW.qm b/resources/translations/yin_yang.zh_TW.qm
index b8a23ded..888f2cb2 100644
Binary files a/resources/translations/yin_yang.zh_TW.qm and b/resources/translations/yin_yang.zh_TW.qm differ
diff --git a/resources/translations/yin_yang.zh_TW.ts b/resources/translations/yin_yang.zh_TW.ts
index 7e7a5364..be8b9896 100644
--- a/resources/translations/yin_yang.zh_TW.ts
+++ b/resources/translations/yin_yang.zh_TW.ts
@@ -60,31 +60,26 @@
-
- 切換主題時播放音效
-
-
-
推送通知
-
+
開機後延遲秒數。預設為十秒。
-
+
延遲秒數:
-
+
秒
-
+
擴充功能
@@ -92,19 +87,19 @@
systray
-
+
Context menu action in the systray
-
+
Context menu action in the systray
-
+
Context menu action in the systray
diff --git a/yin_yang/__main__.py b/yin_yang/__main__.py
index c5d248f4..a4474c79 100755
--- a/yin_yang/__main__.py
+++ b/yin_yang/__main__.py
@@ -54,6 +54,7 @@ def setup_logger(use_systemd_journal: bool):
)
logging.root.addHandler(file_handler)
+
def systray_icon_clicked(reason: QSystemTrayIcon.ActivationReason):
match reason:
case QSystemTrayIcon.ActivationReason.MiddleClick:
@@ -68,6 +69,7 @@ def systray_icon_clicked(reason: QSystemTrayIcon.ActivationReason):
help='toggles Yin-Yang',
action='store_true')
parser.add_argument('--systemd', help='uses systemd journal handler and applies desired theme', action='store_true')
+parser.add_argument('--minimized', help='starts the program to tray bar', action='store_true')
arguments = parser.parse_args()
setup_logger(arguments.systemd)
@@ -80,6 +82,7 @@ def systray_icon_clicked(reason: QSystemTrayIcon.ActivationReason):
elif arguments.systemd:
theme_switcher.set_desired_theme()
+
else:
# load GUI
config.add_event_listener(ConfigEvent.SAVE, daemon_handler.watcher)
@@ -122,15 +125,24 @@ def systray_icon_clicked(reason: QSystemTrayIcon.ActivationReason):
icon.setToolTip('Yin & Yang')
menu = QMenu('Yin & Yang')
- menu.addAction(app.translate('systray', 'Open Yin Yang', 'Context menu action in the systray'), lambda: window.show())
- menu.addAction(app.translate('systray', 'Toggle theme', 'Context menu action in the systray'), lambda: theme_switcher.set_mode(not config.dark_mode))
- menu.addAction(QIcon.fromTheme('application-exit'), app.translate('systray', 'Quit', 'Context menu action in the systray'), app.quit)
+ menu.addAction(
+ app.translate('systray', 'Open Yin Yang', 'Context menu action in the systray'),
+ lambda: window.show())
+ menu.addAction(
+ app.translate('systray', 'Toggle theme', 'Context menu action in the systray'),
+ lambda: theme_switcher.set_mode(not config.dark_mode))
+ menu.addAction(QIcon.fromTheme('application-exit'),
+ app.translate('systray', 'Quit', 'Context menu action in the systray'),
+ app.quit)
icon.setContextMenu(menu)
icon.show()
else:
logger.debug('System tray is unsupported')
- window = main_window_connector.MainWindow()
- window.show()
- sys.exit(app.exec())
+ if arguments.minimized:
+ sys.exit(app.exec())
+ else:
+ window = main_window_connector.MainWindow()
+ window.show()
+ sys.exit(app.exec())
diff --git a/yin_yang/communicate.py b/yin_yang/communicate.py
index 2fbb7222..39dc2962 100755
--- a/yin_yang/communicate.py
+++ b/yin_yang/communicate.py
@@ -20,7 +20,7 @@
logger = logging.getLogger(__name__)
-def _move_times(time_now: datetime, time_light: dt_time, time_dark: dt_time) -> list[int, int]:
+def _move_times(time_now: datetime, time_light: dt_time, time_dark: dt_time) -> list[int]:
"""
Converts a time string to seconds since the epoch
:param time_now: the current time
diff --git a/yin_yang/config.py b/yin_yang/config.py
index c3fce903..f62d0d52 100755
--- a/yin_yang/config.py
+++ b/yin_yang/config.py
@@ -5,15 +5,12 @@
from abc import ABC, abstractmethod
from datetime import time
from functools import cache
-from time import sleep
from typing import Union, Optional
-import requests
-from PySide6.QtCore import QObject
-from PySide6.QtPositioning import QGeoPositionInfoSource, QGeoPositionInfo, QGeoCoordinate
from psutil import process_iter, NoSuchProcess
from suntime import Sun, SunTimeException
+from .position import get_current_location
from .meta import Modes, Desktop, PluginKey, ConfigEvent
from .plugins import get_plugins
@@ -116,36 +113,6 @@ def get_sun_time(latitude, longitude) -> tuple[time, time]:
logger.error(f'Error: {e}.')
-parent = QObject()
-locationSource = QGeoPositionInfoSource.createDefaultSource(parent)
-
-
-@cache
-def get_current_location() -> QGeoCoordinate:
- if locationSource is None:
- logger.error("No location source is available")
- return QGeoCoordinate(0, 0)
-
- pos: QGeoPositionInfo = locationSource.lastKnownPosition()
- if pos is None:
- locationSource.requestUpdate(10)
- tries = 0
- while pos is None and tries < 10:
- pos = locationSource.lastKnownPosition()
- tries += 1
- sleep(1)
- coordinate = pos.coordinate()
- if not coordinate.isValid():
- logger.warning('Location could not be determined. Using ipinfo.io to get location')
- # use the old method as a fallback
- loc_response = requests.get('https://www.ipinfo.io/loc').text.split(',')
- loc: [float] = [float(coordinate) for coordinate in loc_response]
- assert len(loc) == 2, 'The returned location should have exactly 2 values.'
- coordinate = QGeoCoordinate(loc[0], loc[1])
- assert coordinate.isValid()
- return coordinate
-
-
def get_desktop() -> Desktop:
desktop = os.getenv('XDG_CURRENT_DESKTOP')
if desktop is None:
@@ -154,7 +121,7 @@ def get_desktop() -> Desktop:
desktop = ''
match desktop.lower():
- case 'gnome' | 'budgie':
+ case 'gnome':
return Desktop.GNOME
case 'kde' | 'plasma' | 'plasma5':
return Desktop.KDE
@@ -166,6 +133,8 @@ def get_desktop() -> Desktop:
return Desktop.CINNAMON
case 'sway' | 'hyprland':
return Desktop.GNOME
+ case 'budgie:gnome' | 'budgie-desktop' | 'budgie':
+ return Desktop.BUDGIE
case _:
return Desktop.UNKNOWN
@@ -253,12 +222,17 @@ def load(self) -> None:
# check if config needs an update
# if the default values are set, the version number is below 0
if config_loaded['version'] < self.defaults['version']:
- config_loaded = update_config(config_loaded, self.defaults)
+ try:
+ config_loaded = update_config(config_loaded, self.defaults)
+ except Exception as e:
+ logger.error('An error ocurred while trying to update the config. Using defaults instead.')
+ logger.error(e)
+ config_loaded = self.defaults
- for pl in plugins:
- pl.theme_light = config_loaded['plugins'][pl.name.lower()]['light_theme']
- pl.theme_dark = config_loaded['plugins'][pl.name.lower()]['dark_theme']
- pl.enabled = config_loaded['plugins'][pl.name.lower()]['enabled']
+ for p in plugins:
+ p.theme_light = config_loaded['plugins'][p.name.lower()]['light_theme']
+ p.theme_dark = config_loaded['plugins'][p.name.lower()]['dark_theme']
+ p.enabled = config_loaded['plugins'][p.name.lower()]['enabled']
self.update(config_loaded)
@@ -354,7 +328,7 @@ def defaults(self) -> dict:
'running': False,
'dark_mode': False,
'mode': Modes.MANUAL.value,
- 'coordinates': (0, 0),
+ 'coordinates': (0.0, 0.0),
'update_location': False,
'update_interval': 60,
'times': ('07:00', '20:00'),
@@ -423,8 +397,17 @@ def mode(self, mode: Modes):
@property
def location(self) -> tuple[float, float]:
if self.update_location:
- coordinate = get_current_location()
- return coordinate.latitude(), coordinate.longitude()
+ try:
+ coordinate = get_current_location()
+ return coordinate.latitude(), coordinate.longitude()
+ except TypeError as e:
+ logger.error('Unable to update position. Using config values as fallback.')
+ logger.error(e)
+ pass
+ except ValueError as e:
+ logger.error('Unable to update position. Using config values as fallback.')
+ logger.error(e)
+ pass
return self['coordinates']
@@ -494,7 +477,7 @@ def boot_offset(self, value: int):
logger.info('Detected desktop:', config.desktop)
# set plugin themes
-for p in filter(lambda pl: pl.available, plugins):
- p.enabled = config.get_plugin_key(p.name, PluginKey.ENABLED)
- p.theme_bright = config.get_plugin_key(p.name, PluginKey.THEME_LIGHT)
- p.theme_dark = config.get_plugin_key(p.name, PluginKey.THEME_DARK)
+for pl in filter(lambda pl: pl.available, plugins):
+ pl.enabled = config.get_plugin_key(pl.name, PluginKey.ENABLED)
+ pl.theme_bright = config.get_plugin_key(pl.name, PluginKey.THEME_LIGHT)
+ pl.theme_dark = config.get_plugin_key(pl.name, PluginKey.THEME_DARK)
diff --git a/yin_yang/meta.py b/yin_yang/meta.py
index cbfe421f..eae84667 100644
--- a/yin_yang/meta.py
+++ b/yin_yang/meta.py
@@ -16,6 +16,7 @@ class Desktop(Enum):
UNKNOWN = 'unknown'
MATE = 'mate'
CINNAMON = 'cinnamon'
+ BUDGIE = 'budgie'
class PluginKey(Enum):
@@ -29,5 +30,11 @@ class ConfigEvent(Enum):
SAVE = auto()
+class FileFormat(Enum):
+ PLAIN = auto()
+ JSON = auto()
+ CONFIG = auto()
+
+
class UnsupportedDesktopError(NotImplementedError):
pass
diff --git a/yin_yang/plugins/__init__.py b/yin_yang/plugins/__init__.py
index c40941c2..f81da3f0 100755
--- a/yin_yang/plugins/__init__.py
+++ b/yin_yang/plugins/__init__.py
@@ -1,8 +1,8 @@
from ..meta import Desktop
from . import system, colors, gtk, icons, kvantum, wallpaper, custom
-from . import firefox, brave, gedit, only_office, okular
-from . import vscode, atom, konsole
-from . import sound, notify
+from . import firefox, only_office, okular
+from . import vscode, konsole
+from . import notify
# NOTE initialize your plugin over here:
# The order in the list specifies the order in the config gui
@@ -18,15 +18,11 @@ def get_plugins(desktop: Desktop) -> [Plugin]:
kvantum.Kvantum(),
wallpaper.Wallpaper(desktop),
firefox.Firefox(),
- brave.Brave(),
vscode.Vscode(),
- atom.Atom(),
- gedit.Gedit(),
only_office.OnlyOffice(),
okular.Okular(),
konsole.Konsole(),
custom.Custom(),
- sound.Sound(),
notify.Notification()
]
diff --git a/yin_yang/plugins/_plugin.py b/yin_yang/plugins/_plugin.py
index 05d43e59..ddce0895 100644
--- a/yin_yang/plugins/_plugin.py
+++ b/yin_yang/plugins/_plugin.py
@@ -1,12 +1,17 @@
+import copy
+import json
import logging
import subprocess
from abc import ABC, abstractmethod
+from configparser import ConfigParser
+from pathlib import Path
from typing import Optional
+from PySide6.QtDBus import QDBusConnection, QDBusMessage
from PySide6.QtGui import QColor, QRgba64
from PySide6.QtWidgets import QGroupBox, QHBoxLayout, QLineEdit, QComboBox
-from ..meta import UnsupportedDesktopError
+from ..meta import UnsupportedDesktopError, FileFormat
logger = logging.getLogger(__name__)
@@ -89,7 +94,7 @@ def get_input(self, widget):
# add all theme names
for i, curComboBox in enumerate(inputs):
themes = list(self.available_themes.values())
- themes.sort()
+ themes.sort(key=lambda s: s.casefold())
curComboBox.addItems(themes)
curComboBox.setMinimumContentsLength(4)
# set index
@@ -248,22 +253,97 @@ def set_theme(self, theme: str):
assert False, 'This is an external plugin, the mode can only be changed externally.'
-def inplace_change(filename: str, old_string: str, new_string: str):
- """Replaces a given string by a new string in a specific file
- :param filename: the full path to the file that should be changed
- :param old_string: the old string that should be found in the file
- :param new_string: the string that should replace the old string
- """
- # Safely read the input filename using 'with'
- with open(filename, 'r') as file:
- file_content = file.read()
- if old_string not in file_content:
- raise ValueError(f'{old_string} could not be found in {filename}')
+class DBusPlugin(Plugin):
+ def __init__(self, base_message: QDBusMessage):
+ super().__init__()
+ self.connection = QDBusConnection.sessionBus()
+ self.base_message = base_message
+
+ def set_theme(self, theme: str):
+ if not (self.available and self.enabled):
+ return
+
+ if not theme:
+ raise ValueError(f'Theme \"{theme}\" is invalid')
+
+ self.call(self.create_message(theme))
- # Safely write the changed content, if found in the file
- with open(filename, 'w') as file:
- file_content = file_content.replace(old_string, new_string)
- file.write(file_content)
+ def create_message(self, theme: str) -> QDBusMessage:
+ message = copy.deepcopy(self.base_message)
+ message.setArguments([theme])
+ return message
+
+ def call(self, message) -> QDBusMessage:
+ return self.connection.call(message)
+
+
+class ConfigFilePlugin(Plugin):
+ def __init__(self, config_paths: list[Path], file_format=FileFormat.PLAIN):
+ self.config_paths: list[Path] = config_paths
+ self.file_format = file_format
+ super().__init__()
+
+ @property
+ def available(self) -> bool:
+ # check if any config file exists
+ for config in self.config_paths:
+ if config.is_file():
+ return True
+
+ return False
+
+ def open_config(self, path: Path):
+ with open(path) as file:
+ match self.file_format.value:
+ case FileFormat.JSON.value:
+ try:
+ return json.load(file)
+ except json.decoder.JSONDecodeError as e:
+ return self.default_config
+ case FileFormat.CONFIG.value:
+ config = ConfigParser()
+ config.optionxform = str
+ config.read_file(file)
+ return config
+ case _:
+ return file.read()
+
+ def write_config(self, value: str | ConfigParser, path: Path, **kwargs):
+ with open(path, 'w') as file:
+ if self.file_format.value == FileFormat.CONFIG.value:
+ value.write(file, **kwargs)
+ else:
+ file.write(value)
+
+ def set_theme(self, theme: str, ignore_theme_check=False):
+ if not (self.available and self.enabled):
+ return
+
+ if not ignore_theme_check and not theme:
+ raise ValueError(f'Theme \"{theme}\" is invalid')
+
+ try:
+ for config_path in self.config_paths:
+ if not config_path.exists():
+ continue
+
+ config = self.open_config(config_path)
+ new_config = self.update_config(config, theme)
+ self.write_config(new_config, config_path)
+ except StopIteration:
+ raise FileNotFoundError(
+ 'No config file found. '
+ 'If you see this error, try to set a custom theme manually once and try again.')
+
+ @abstractmethod
+ def update_config(self, config, theme: str) -> str:
+ """Set the theme in the config."""
+ raise NotImplementedError
+
+ @property
+ def default_config(self):
+ """Fallback config file if active file should be empty."""
+ raise FileNotFoundError('Config file is empty. Try to set a theme manually once and try again.')
def get_qcolor_from_int(color_int: int) -> QColor:
@@ -276,3 +356,15 @@ def get_int_from_qcolor(color: QColor) -> int:
# ... - 2^32 converts uint to int
color_int = color.rgba64().toArgb32() - 2 ** 32
return color_int
+
+
+def flatpak_system(app_id: str) -> Path:
+ return Path(f'/var/lib/flatpak/app/{app_id}/current/active')
+
+
+def flatpak_user(app_id: str) -> Path:
+ return Path.home() / f'.var/app/{app_id}'
+
+
+def snap_path(app: str) -> Path:
+ return Path(f'/var/lib/snapd/snap/{app}/current')
diff --git a/yin_yang/plugins/atom.py b/yin_yang/plugins/atom.py
deleted file mode 100755
index cf17bf16..00000000
--- a/yin_yang/plugins/atom.py
+++ /dev/null
@@ -1,51 +0,0 @@
-import re
-from os.path import isfile
-from pathlib import Path
-
-from ._plugin import Plugin, inplace_change
-
-
-def get_old_theme(settings):
- """
- Returns the theme that is currently used.
- Uses regex to find the currently used theme, I expect that themes follow this pattern:
- XXXX-XXXX-ui XXXX-XXXX-syntax
- """
- with open(settings, "r") as file:
- string = file.read()
- themes = re.findall(r'themes: \[[\s]*"([A-Za-z0-9\-]*)"[\s]*"([A-Za-z0-9\-]*)"', string)
- if len(themes) >= 1:
- ui_theme, _ = themes[0]
- used_theme = re.findall('([A-z-A-z]*)-', ui_theme)[0]
- return used_theme
-
-
-class Atom(Plugin):
- # noinspection SpellCheckingInspection
- config_path = str(Path.home()) + "/.atom/config.cson"
-
- def __init__(self):
- super().__init__()
- self.theme_light = 'one-light'
- self.theme_dark = 'one-dark'
-
- def set_theme(self, theme: str):
- if not (self.available and self.enabled):
- return
-
- if not theme:
- raise ValueError(f'Theme \"{theme}\" is invalid')
-
- # getting the old theme first
- current_theme: str = get_old_theme(self.config_path)
-
- if not current_theme:
- raise ValueError("Current theme could not be determined."
- "If you see this error, try to set a custom theme once and then try again")
-
- # updating the old theme with theme specified in config
- inplace_change(self.config_path, current_theme, theme)
-
- @property
- def available(self) -> bool:
- return isfile(self.config_path)
diff --git a/yin_yang/plugins/brave.py b/yin_yang/plugins/brave.py
deleted file mode 100644
index 8b2cf451..00000000
--- a/yin_yang/plugins/brave.py
+++ /dev/null
@@ -1,51 +0,0 @@
-import json
-from os.path import isfile
-from pathlib import Path
-
-from PySide6.QtGui import QColor
-from PySide6.QtWidgets import QWidget, QVBoxLayout, QLineEdit, QPushButton
-
-from ._plugin import Plugin, get_int_from_qcolor
-
-path = f'{Path.home()}/.config/BraveSoftware/Brave-Browser/Default/Preferences'
-
-
-class Brave(Plugin):
- def __init__(self):
- super().__init__()
- self.theme_light = '#ffffff'
- self.theme_dark = '#000000'
-
- def set_theme(self, color_str: str):
- with open(path, 'r') as file:
- config = json.load(file)
-
- color = get_int_from_qcolor(QColor(color_str))
- config['autogenerated']['theme']['color'] = color
-
- def get_input(self, widget):
- widgets = []
-
- for dark_theme in [False, True]:
- grp = QWidget(widget)
- horizontal_layout = QVBoxLayout(grp)
-
- line = QLineEdit(grp)
- color_str = self.theme_dark if dark_theme else self.theme_light
- line.setText(color_str)
- color = QColor(color_str)
- line.setStyleSheet(f'background-color: {color_str};'
- f' color: {"white" if color.lightness() <= 128 else "black"}')
- horizontal_layout.addWidget(line)
-
- btn = QPushButton()
- btn.setText(f'Pick {"dark" if dark_theme else "light"} color')
- horizontal_layout.addWidget(btn)
-
- widgets.append(grp)
-
- return widgets
-
- @property
- def available(self) -> bool:
- return isfile(path)
diff --git a/yin_yang/plugins/firefox.py b/yin_yang/plugins/firefox.py
index 7967fac7..8407e0c4 100755
--- a/yin_yang/plugins/firefox.py
+++ b/yin_yang/plugins/firefox.py
@@ -11,14 +11,14 @@
logger = logging.getLogger(__name__)
-def get_profile_paths() -> str:
- path = str(Path.home()) + '/.mozilla/firefox/'
+def get_profile_paths() -> Path:
+ path = Path.home() / '.mozilla/firefox/'
config_parser = ConfigParser()
- config_parser.read(path + '/profiles.ini')
+ config_parser.read(path / 'profiles.ini')
for section in config_parser:
if not section.startswith('Profile'):
continue
- yield path + config_parser[section]['Path']
+ yield path / config_parser[section]['Path']
class Firefox(ExternalPlugin):
@@ -34,7 +34,7 @@ def available_themes(self) -> dict:
if not self.available:
return {}
- paths = (p + '/extensions.json' for p in get_profile_paths())
+ paths = (p / 'extensions.json' for p in get_profile_paths())
themes: dict[str, str] = {}
for path in paths:
@@ -44,7 +44,7 @@ def available_themes(self) -> dict:
for addon in content['addons']:
if addon['type'] == 'theme':
themes[addon['id']] = addon['defaultLocale']['name']
- except FileNotFoundError as e:
+ except FileNotFoundError as _:
logger.warning(f'Firefox profile has no extensions installed: {path}')
continue
diff --git a/yin_yang/plugins/gedit.py b/yin_yang/plugins/gedit.py
deleted file mode 100644
index 3fc45a70..00000000
--- a/yin_yang/plugins/gedit.py
+++ /dev/null
@@ -1,34 +0,0 @@
-import os
-from os.path import isdir
-from xml.etree import ElementTree
-
-from ._plugin import PluginCommandline
-from .system import test_gnome_availability
-
-path = '/usr/share/gtksourceview-4/styles/'
-
-
-class Gedit(PluginCommandline):
- def __init__(self):
- super(Gedit, self).__init__(['gsettings', 'set', 'org.gnome.gedit.preferences.editor', 'scheme', '{theme}'])
-
- @property
- def available(self) -> bool:
- return test_gnome_availability(self.command)
-
- @property
- def available_themes(self) -> dict:
- if not isdir(path):
- return {}
-
- themes = {}
- with os.scandir(path) as entries:
- for file in (f.path for f in entries if f.is_file() and not f.name.endswith('.rng')):
- config = ElementTree.parse(file)
- attributes = config.getroot().attrib
-
- name = attributes.get('_name')
- theme_id = attributes.get('id')
- themes[theme_id] = name if name is not None else theme_id
-
- return themes
diff --git a/yin_yang/plugins/gtk.py b/yin_yang/plugins/gtk.py
index 153e552e..fa7a411f 100755
--- a/yin_yang/plugins/gtk.py
+++ b/yin_yang/plugins/gtk.py
@@ -1,13 +1,13 @@
import logging
+import subprocess
from os import scandir, path
from pathlib import Path
-import subprocess
-from PySide6.QtDBus import QDBusConnection, QDBusMessage
+from PySide6.QtDBus import QDBusMessage
-from ..meta import Desktop
-from ._plugin import PluginDesktopDependent, Plugin, PluginCommandline
+from ._plugin import PluginDesktopDependent, PluginCommandline, DBusPlugin
from .system import test_gnome_availability
+from ..meta import Desktop
logger = logging.getLogger(__name__)
@@ -33,6 +33,8 @@ def __init__(self, desktop: Desktop):
super().__init__(_Xfce())
case Desktop.CINNAMON:
super().__init__(_Cinnamon())
+ case Desktop.BUDGIE:
+ super().__init__(_Budgie())
case _:
super().__init__(None)
@@ -61,42 +63,59 @@ def __init__(self):
@property
def available(self) -> bool:
return test_gnome_availability(self.command)
+
+
+class _Budgie(PluginCommandline):
+ name = 'GTK'
+
+ def __init__(self):
+ super().__init__(['gsettings', 'set', 'org.gnome.desktop.interface', 'gtk-theme', '{theme}'])
+ self.theme_light = 'Default'
+ self.theme_dark = 'Default'
+
+ @property
+ def available(self) -> bool:
+ return test_gnome_availability(self.command)
-class _Kde(Plugin):
+class _Kde(DBusPlugin):
name = 'GTK'
def __init__(self):
- super().__init__()
- self.theme_light = 'Breeze'
- self.theme_dark = 'Breeze'
-
- def set_theme(self, theme: str):
- connection = QDBusConnection.sessionBus()
message = QDBusMessage.createMethodCall(
'org.kde.GtkConfig',
'/GtkConfig',
'org.kde.GtkConfig',
'setGtkTheme'
)
- message.setArguments([theme])
- response = connection.call(message)
- if response.type() == QDBusMessage.MessageType.ErrorMessage:
- logger.warning('kde-gtk-config not available, try xsettingsd')
- xsettingsd_conf_path = Path.home() / '.config' / 'xsettingsd' / 'xsettingsd.conf'
- if not xsettingsd_conf_path.exists():
- logger.warning('xsettingsd not available')
- with open(xsettingsd_conf_path, 'r') as f:
- lines = f.readlines()
- for i, line in enumerate(lines):
- if line.startswith('Net/ThemeName'):
- lines[i] = f'Net/ThemeName "{theme}"\n'
- break
- with open(xsettingsd_conf_path, 'w') as f:
- f.writelines(lines)
- subprocess.run(['killall', '-HUP', 'xsettingsd'])
- else:
- logger.debug('Success by kde-gtk-config')
+ super().__init__(message)
+ self.theme_light = 'Breeze'
+ self.theme_dark = 'Breeze'
+
+ def set_theme(self, theme: str):
+ response = self.call(self.create_message(theme))
+
+ if response.type() != QDBusMessage.MessageType.ErrorMessage:
+ return
+
+ logger.warning('kde-gtk-config not available, trying xsettingsd')
+ xsettingsd_conf_path = Path.home() / '.config/xsettingsd/xsettingsd.conf'
+ if not xsettingsd_conf_path.exists():
+ logger.warning('xsettingsd not available')
+ return
+
+ with open(xsettingsd_conf_path, 'r') as f:
+ lines = f.readlines()
+ for i, line in enumerate(lines):
+ if line.startswith('Net/ThemeName'):
+ lines[i] = f'Net/ThemeName "{theme}"\n'
+ break
+
+ with open(xsettingsd_conf_path, 'w') as f:
+ f.writelines(lines)
+
+ # send signal to read new config
+ subprocess.run(['killall', '-HUP', 'xsettingsd'])
class _Xfce(PluginCommandline):
diff --git a/yin_yang/plugins/icons.py b/yin_yang/plugins/icons.py
index 3121e6c4..04cf65ec 100644
--- a/yin_yang/plugins/icons.py
+++ b/yin_yang/plugins/icons.py
@@ -1,6 +1,10 @@
from .system import test_gnome_availability
from ..meta import Desktop
from ._plugin import PluginDesktopDependent, PluginCommandline
+from pathlib import Path
+from os import scandir, path
+
+theme_directories = ['/usr/share/icons', f'{Path.home()}/.icons']
class Icons(PluginDesktopDependent):
@@ -10,6 +14,8 @@ def __init__(self, desktop: Desktop):
super().__init__(_Mate())
case Desktop.CINNAMON:
super().__init__(_Cinnamon())
+ case Desktop.BUDGIE:
+ super().__init__(_Budgie())
case _:
super().__init__(None)
@@ -34,3 +40,27 @@ def __init__(self):
@property
def available(self) -> bool:
return test_gnome_availability(self.command)
+
+
+class _Budgie(PluginCommandline):
+ def __init__(self):
+ super().__init__(['gsettings', 'set', 'org.gnome.desktop.interface', 'icon-theme', '\"{theme}\"'])
+ self.theme_light = 'Default'
+ self.theme_dark = 'Default'
+
+ @property
+ def available(self) -> bool:
+ return test_gnome_availability(self.command)
+
+ @property
+ def available_themes(self) -> dict:
+ themes = []
+
+ for directory in theme_directories:
+ if not path.isdir(directory):
+ continue
+
+ with scandir(directory) as entries:
+ themes.extend(d.name for d in entries if d.is_dir() and path.isfile(d.path + '/index.theme'))
+
+ return {t: t for t in themes}
diff --git a/yin_yang/plugins/konsole.py b/yin_yang/plugins/konsole.py
index ebe8a561..5d458138 100644
--- a/yin_yang/plugins/konsole.py
+++ b/yin_yang/plugins/konsole.py
@@ -74,7 +74,7 @@ def set_mode(self, dark: bool) -> bool:
logger.debug(f'Changing profile in konsole session {proc_id}')
set_profile(f'org.kde.konsole-{proc_id}', profile)
- set_profile('org.kde.konsole', profile) # konsole may don't have session dbus like above
+ set_profile('org.kde.konsole', profile) # konsole may don't have session dbus like above
set_profile('org.kde.yakuake', profile)
process_ids = [
diff --git a/yin_yang/plugins/kvantum.py b/yin_yang/plugins/kvantum.py
index 82854a49..f9befeb7 100755
--- a/yin_yang/plugins/kvantum.py
+++ b/yin_yang/plugins/kvantum.py
@@ -1,4 +1,4 @@
-import os
+from os import walk
from pathlib import Path
from ._plugin import PluginCommandline
@@ -11,9 +11,9 @@ def __init__(self):
self.theme_dark = 'KvFlat'
@classmethod
- def get_kvantum_theme_from_dir(cls, dir):
+ def get_kvantum_theme_from_dir(cls, directory: Path):
result = set()
- for _, _, filenames in os.walk(dir):
+ for _, _, filenames in walk(directory):
for filename in filenames:
if filename.endswith('.kvconfig'):
result.add(filename[:-9])
@@ -24,11 +24,10 @@ def available_themes(self) -> dict:
if not self.available:
return {}
- paths = ['/usr/share/Kvantum', str(Path.home()) + '/.config/Kvantum']
+ paths = [Path('/usr/share/Kvantum'), Path.home() / '.config/Kvantum']
themes = list()
for path in paths:
themes = themes + self.get_kvantum_theme_from_dir(path)
- themes_dict: dict = {}
assert len(themes) > 0, 'No themes were found'
themes.sort()
diff --git a/yin_yang/plugins/okular.py b/yin_yang/plugins/okular.py
index 79832ef7..d9d3bbb6 100644
--- a/yin_yang/plugins/okular.py
+++ b/yin_yang/plugins/okular.py
@@ -1,40 +1,25 @@
import os
-from configparser import ConfigParser
from pathlib import Path
import psutil
from PySide6.QtDBus import QDBusConnection, QDBusMessage
-from ._plugin import Plugin
+from ..meta import FileFormat
+from ._plugin import flatpak_user, ConfigFilePlugin
-class Okular(Plugin):
- """Inspired by: https://gitlab.com/LADlSLAV/yabotss/-/blob/main/darkman_examples_kde_plasma/dark-mode.d/10_set_theme_okular_dark.sh"""
+class Okular(ConfigFilePlugin):
+ """Inspired by:
+ https://gitlab.com/LADlSLAV/yabotss/-/blob/main/darkman_examples_kde_plasma/dark-mode.d/10_set_theme_okular_dark.sh
+ """
def __init__(self):
- super().__init__()
+ super().__init__([
+ Path.home() / '.config/okularpartrc',
+ flatpak_user('org.kde.okular') / 'config/okularpartrc'
+ ], file_format=FileFormat.CONFIG)
self._theme_light = ''
- self._theme_dark = ''
-
- @property
- def user_paths(self) -> [Path]:
- path = Path.home() / '.config/okularpartrc'
- if path.is_file():
- yield path
-
- path = Path.home() / '.var/app/org.kde.okular/config/okularpartrc'
- if path.is_file():
- yield path
-
- return
-
- @property
- def available(self) -> bool:
- try:
- next(self.user_paths)
- return True
- except StopIteration:
- return False
+ self._theme_dark = 'InvertLightness'
def set_mode(self, dark: bool):
if not self.enabled:
@@ -59,25 +44,18 @@ def set_mode(self, dark: bool):
connection.call(message)
# now change the config for future starts of the app
- for path in self.user_paths:
- config = ConfigParser()
- config.optionxform = str
- config.read(path)
-
- if dark:
- if not config.has_section('Document'):
- config.add_section('Document')
- config['Document']['ChangeColors'] = 'true'
- else:
- config.remove_option('Document', 'ChangeColors')
- if len(config.options('Document')) == 0:
- config.remove_section('Document')
-
- with open(path, 'w') as file:
- config.write(file, space_around_delimiters=False)
-
- def set_theme(self, theme: str):
- pass
+ self.set_theme(self.theme_dark if dark else self.theme_light, ignore_theme_check=True)
+
+ def update_config(self, config, theme: str) -> str:
+ if theme == self.theme_dark:
+ if not config.has_section('Document'):
+ config.add_section('Document')
+ config['Document']['ChangeColors'] = 'true'
+ else:
+ config.remove_option('Document', 'ChangeColors')
+ if len(config.options('Document')) == 0:
+ config.remove_section('Document')
+ return config
@property
def available_themes(self) -> dict:
@@ -108,11 +86,13 @@ def theme_dark(self):
def theme_dark(self, value):
self._theme_dark = value
- for path in self.user_paths:
- config = ConfigParser()
- config.optionxform = str
- config.read(path)
+ for config_path in self.config_paths:
+ if not config_path.exists():
+ continue
+
+ config = self.open_config(config_path)
+ # update rendering mode
if value == '':
if config.has_section('Document'):
config.remove_option('Document', 'RenderMode')
@@ -123,5 +103,4 @@ def theme_dark(self, value):
config.add_section('Document')
config['Document']['RenderMode'] = value
- with open(path, 'w') as file:
- config.write(file, space_around_delimiters=False)
+ self.write_config(config, config_path, space_around_delimiters=False)
diff --git a/yin_yang/plugins/only_office.py b/yin_yang/plugins/only_office.py
index 9b59dd07..66a82a1b 100644
--- a/yin_yang/plugins/only_office.py
+++ b/yin_yang/plugins/only_office.py
@@ -1,30 +1,22 @@
from configparser import ConfigParser
-from os.path import isfile
from pathlib import Path
-from ._plugin import Plugin
+from ..meta import FileFormat
+from ._plugin import ConfigFilePlugin, flatpak_user
-config_path = f'{Path.home()}/.config/onlyoffice/DesktopEditors.conf'
-
-class OnlyOffice(Plugin):
+class OnlyOffice(ConfigFilePlugin):
def __init__(self):
- super().__init__()
+ super().__init__([
+ Path.home() / '.config/onlyoffice/DesktopEditors.conf',
+ flatpak_user('org.onlyoffice.desktopeditors') / 'config/onlyoffice/DesktopEditors.conf'
+ ], file_format=FileFormat.CONFIG)
self.theme_light = 'theme-light'
self.theme_dark = 'theme-dark'
- def set_theme(self, theme: str):
- config = ConfigParser()
- config.optionxform = str
- config.read(config_path)
- config['General']['UITheme2'] = theme
-
- with open(config_path, 'w') as file:
- config.write(file)
-
- @property
- def available(self) -> bool:
- return isfile(config_path)
+ def update_config(self, config: ConfigParser, theme: str):
+ config['General']['UITheme'] = theme
+ return config
@property
def available_themes(self) -> dict:
diff --git a/yin_yang/plugins/sound.py b/yin_yang/plugins/sound.py
deleted file mode 100644
index a9b3fcc0..00000000
--- a/yin_yang/plugins/sound.py
+++ /dev/null
@@ -1,17 +0,0 @@
-import subprocess
-
-from ._plugin import PluginCommandline
-
-
-class Sound(PluginCommandline):
- def __init__(self):
- super(Sound, self).__init__(["paplay", '{theme}'])
- self.theme_light = './resources/light.wav'
- self.theme_dark = './resources/dark.wav'
-
- @property
- def available(self) -> bool:
- try:
- return subprocess.run(['paplay', '--help'], stdout=subprocess.DEVNULL).returncode == 0
- except FileNotFoundError:
- return False
diff --git a/yin_yang/plugins/system.py b/yin_yang/plugins/system.py
index f5c2710d..c58bb14f 100644
--- a/yin_yang/plugins/system.py
+++ b/yin_yang/plugins/system.py
@@ -31,6 +31,8 @@ def __init__(self, desktop: Desktop):
super().__init__(_Mate())
case Desktop.CINNAMON:
super().__init__(_Cinnamon())
+ case Desktop.BUDGIE:
+ super().__init__(_Budgie())
case _:
super().__init__(None)
@@ -48,6 +50,41 @@ def available(self) -> bool:
return test_gnome_availability(self.command)
+class _Budgie(PluginCommandline):
+ name = 'System'
+
+ def __init__(self):
+ super().__init__(['gsettings', 'set', 'com.solus-project.budgie-panel', 'dark-theme', '{theme}'])
+ self.theme_light = 'light'
+ self.theme_dark = 'dark'
+
+ @property
+ def available(self) -> bool:
+ return test_gnome_availability(self.command)
+
+ # Override because budgie uses a switch for dark/light mode
+ def insert_theme(self, theme: str) -> list:
+ command = self.command.copy()
+ match theme.lower():
+ case 'dark':
+ theme_bool = 'true'
+ case 'light':
+ theme_bool = 'false'
+ case _:
+ raise NotImplementedError
+
+ for i, arg in enumerate(command):
+ command[i] = arg.format(theme=theme_bool)
+
+ return command
+
+ @property
+ def available_themes(self) -> dict:
+ themes: dict[str, str] = {'dark': 'Dark', 'light': 'Light'}
+
+ return themes
+
+
def get_readable_kde_theme_name(file) -> str:
"""Searches for the long_name in the file and maps it to the found short name"""
diff --git a/yin_yang/plugins/vscode.py b/yin_yang/plugins/vscode.py
index 4bbeb15f..c63de74d 100755
--- a/yin_yang/plugins/vscode.py
+++ b/yin_yang/plugins/vscode.py
@@ -1,35 +1,35 @@
-import os
import json
import logging
+import os
from os.path import isdir, isfile
from pathlib import Path
-from ._plugin import Plugin
+from ..meta import FileFormat
+from ._plugin import flatpak_system, flatpak_user, snap_path, ConfigFilePlugin
logger = logging.getLogger(__name__)
extension_paths = [
- str(Path.home()) + '/.vscode/extensions',
- str(Path.home()) + '/.vscode-insiders/extensions',
- str(Path.home()) + '/.vscode-oss/extensions',
+ str(Path.home() / '.vscode/extensions'),
+ str(Path.home() / '.vscode-insiders/extensions'),
+ str(Path.home() / '.vscode-oss/extensions'),
'/usr/lib/code/extensions',
'/usr/lib/code-insiders/extensions',
'/usr/share/code/resources/app/extensions',
'/usr/share/code-insiders/resources/app/extensions',
'/opt/visual-studio-code/resources/app/extensions/',
'/opt/visual-studio-code-insiders/resources/app/extensions/',
- '/var/lib/snapd/snap/code/current/usr/share/code/resources/app/extensions/',
- '/var/lib/snapd/snap/code-insiders/current/usr/share/code-insiders/resources/app/extensions/'
+ str(snap_path('code') / 'usr/share/code/resources/app/extensions/'),
+ str(snap_path('code-insiders') / 'usr/share/code-insiders/resources/app/extensions/'),
+ str(flatpak_user('com.visualstudio.code') / 'data/vscode/extensions/'),
+ str(flatpak_user('com.visualstudio.code-oss') / 'data/vscode/extensions/'),
+ str(flatpak_user('com.vscodium.codium') / 'data/codium/extensions/'),
+ str(flatpak_system('com.visualstudio.code') / 'files/extra/vscode/resources/app/extensions/'),
+ str(flatpak_system('com.visualstudio.code-oss') / 'files/main/resources/app/extensions/'),
+ str(flatpak_system('com.vscodium.codium') / 'files/share/codium/resources/app/extensions/')
]
-def write_new_settings(settings, path):
- # simple adds a new field to the settings
- settings["workbench.colorTheme"] = "Default"
- with open(path, 'w') as conf:
- json.dump(settings, conf, indent=4)
-
-
def get_theme_name(path):
if not isfile(path):
return []
@@ -54,55 +54,29 @@ def get_theme_name(path):
return (theme['id'] if 'id' in theme else theme['label'] for theme in themes)
-class Vscode(Plugin):
+class Vscode(ConfigFilePlugin):
name = 'VS Code'
def __init__(self):
- super(Vscode, self).__init__()
- self.theme_light = 'Default Light+'
- self.theme_dark = 'Default Dark+'
-
- def set_theme(self, theme: str):
- if not theme:
- raise ValueError(f'Theme \"{theme}\" is invalid')
-
- if not (self.available and self.enabled):
- return
-
possible_editors = [
"VSCodium",
"Code - OSS",
"Code",
"Code - Insiders",
]
+ paths = [Path.home() / f'.config/{name}/User/settings.json' for name in possible_editors]
+ paths += [
+ flatpak_user('com.visualstudio.code') / 'config/Code/User/settings.json',
+ flatpak_user('com.visualstudio.code-oss') / 'config/Code - OSS/User/settings.json',
+ flatpak_user('com.vscodium.codium') / 'config/VSCodium/User/settings.json'
+ ]
+ super(Vscode, self).__init__(paths, file_format=FileFormat.JSON)
+ self.theme_light = 'Default Light Modern'
+ self.theme_dark = 'Default Dark Modern'
- try:
- for editor in filter(
- os.path.isfile,
- (f'{str(Path.home())}/.config/{name}/User/settings.json' for name in possible_editors)):
- # load the settings
- with open(editor, "r") as sett:
- try:
- settings = json.load(sett)
- settings['workbench.colorTheme'] = theme
- except json.decoder.JSONDecodeError as e:
- # check if the file is completely empty
- sett.seek(0)
- first_char: str = sett.read(1)
- if not first_char:
- # file is empty
- logger.info('File is empty')
- settings = {"workbench.colorTheme": theme}
- else:
- # settings file is malformed
- raise e
-
- # write changed settings into the file
- with open(editor, 'w') as sett:
- json.dump(settings, sett)
- except StopIteration:
- raise FileNotFoundError('No config file found. '
- 'If you see this error, try to set a custom theme manually once and try again.')
+ def update_config(self, config: dict, theme: str):
+ config['workbench.colorTheme'] = theme
+ return json.dumps(config)
@property
def available_themes(self) -> dict:
@@ -129,8 +103,5 @@ def __str__(self):
return 'code'
@property
- def available(self) -> bool:
- for path in extension_paths:
- if isdir(path):
- return True
- return False
+ def default_config(self):
+ return {'workbench.colorTheme': 'Default'}
diff --git a/yin_yang/plugins/wallpaper.py b/yin_yang/plugins/wallpaper.py
index d6c6c303..c91b7975 100755
--- a/yin_yang/plugins/wallpaper.py
+++ b/yin_yang/plugins/wallpaper.py
@@ -1,3 +1,4 @@
+import copy
import logging
import subprocess
from pathlib import Path
@@ -6,7 +7,7 @@
from PySide6.QtDBus import QDBusConnection, QDBusMessage
from ..meta import Desktop
-from ._plugin import PluginDesktopDependent, PluginCommandline, Plugin
+from ._plugin import PluginDesktopDependent, PluginCommandline, Plugin, DBusPlugin
from .system import test_gnome_availability
logger = logging.getLogger(__name__)
@@ -25,6 +26,8 @@ def __init__(self, desktop: Desktop):
super().__init__(_Xfce())
case Desktop.CINNAMON:
super().__init__(_Cinnamon())
+ case Desktop.BUDGIE:
+ super().__init__(_Budgie())
case _:
super().__init__(None)
@@ -54,6 +57,18 @@ class _Gnome(PluginCommandline):
def __init__(self):
super().__init__(['gsettings', 'set', 'org.gnome.desktop.background', 'picture-uri', 'file://{theme}'])
+ @property
+ def available(self) -> bool:
+ return test_gnome_availability(self.command)
+
+
+class _Budgie(PluginCommandline):
+ name = 'Wallpaper'
+
+ def __init__(self):
+ super().__init__(['gsettings', 'set', 'org.gnome.desktop.background', 'picture-uri', 'file://{theme}'])
+
+ @property
def available(self) -> bool:
return test_gnome_availability(self.command)
@@ -72,11 +87,17 @@ def check_theme(theme: str) -> bool:
return True
-class _Kde(Plugin):
+class _Kde(DBusPlugin):
name = 'Wallpaper'
def __init__(self):
- super().__init__()
+ message = QDBusMessage.createMethodCall(
+ 'org.kde.plasmashell',
+ '/PlasmaShell',
+ 'org.kde.PlasmaShell',
+ 'evaluateScript',
+ )
+ super().__init__(message)
self._theme_light = None
self._theme_dark = None
@@ -98,14 +119,12 @@ def theme_dark(self, value: str):
check_theme(value)
self._theme_dark = value
- def set_theme(self, theme: str):
- connection = QDBusConnection.sessionBus()
- message = QDBusMessage.createMethodCall(
- 'org.kde.plasmashell',
- '/PlasmaShell',
- 'org.kde.PlasmaShell',
- 'evaluateScript',
- )
+ @property
+ def available(self) -> bool:
+ return True
+
+ def create_message(self, theme: str) -> QDBusMessage:
+ message = copy.deepcopy(self.base_message)
message.setArguments([
'string:'
'var Desktops = desktops();'
@@ -116,11 +135,7 @@ def set_theme(self, theme: str):
f' d.writeConfig("Image", "file:{theme}");'
'}'
])
- connection.call(message)
-
- @property
- def available(self) -> bool:
- return True
+ return message
class _Xfce(PluginCommandline):
diff --git a/yin_yang/position.py b/yin_yang/position.py
new file mode 100644
index 00000000..79fc891d
--- /dev/null
+++ b/yin_yang/position.py
@@ -0,0 +1,71 @@
+import logging
+from time import sleep
+
+import requests
+from PySide6.QtCore import QObject
+from PySide6.QtPositioning import QGeoPositionInfoSource, QGeoCoordinate, QGeoPositionInfo
+
+logger = logging.getLogger(__name__)
+
+
+def get_current_location() -> QGeoCoordinate:
+ try:
+ return get_qt_position()
+ except TypeError as e:
+ logger.warning(e)
+
+ try:
+ return get_ipinfo_position()
+ except TypeError as e:
+ logger.warning(e)
+
+ raise TypeError('Unable to get current location')
+
+
+parent = QObject()
+location_source = QGeoPositionInfoSource.createDefaultSource(parent)
+
+
+def get_qt_position() -> QGeoCoordinate:
+ if location_source is None:
+ raise TypeError('Location source is none')
+
+ pos: QGeoPositionInfo = location_source.lastKnownPosition()
+ if pos is None:
+ location_source.requestUpdate(10)
+ tries = 0
+ while pos is None and tries < 10:
+ pos = location_source.lastKnownPosition()
+ tries += 1
+ sleep(1)
+ coordinate = pos.coordinate()
+
+ if not coordinate.isValid():
+ raise TypeError('Coordinates are not valid')
+
+ return coordinate
+
+# there is a freedesktop portal for getting the location,
+# but it's not implemented by KDE, so I have no use for it
+
+
+def get_ipinfo_position() -> QGeoCoordinate:
+ # use the old method as a fallback
+ try:
+ response = requests.get('https://www.ipinfo.io/loc')
+ except Exception as e:
+ logger.error(e)
+ raise TypeError('Error while sending a request to get location')
+
+ if not response.ok:
+ raise TypeError('Failed to get location from ipinfo.io')
+
+ loc_response = response.text.removesuffix('\n').split(',')
+ loc: [float] = [float(coordinate) for coordinate in loc_response]
+ assert len(loc) == 2, 'The returned location should have exactly 2 values.'
+ coordinate = QGeoCoordinate(loc[0], loc[1])
+
+ if not coordinate.isValid():
+ raise TypeError('Coordinates are not valid')
+
+ return coordinate
diff --git a/yin_yang/theme_switcher.py b/yin_yang/theme_switcher.py
index 2a177b1f..01c2c7fa 100755
--- a/yin_yang/theme_switcher.py
+++ b/yin_yang/theme_switcher.py
@@ -13,7 +13,6 @@
from threading import Thread
from .plugins.notify import Notification
-from .plugins.sound import Sound
from .daemon_handler import update_times
from .meta import PluginKey
from .config import config, plugins
@@ -40,7 +39,7 @@ def set_mode(dark: bool, force=False):
logger.info(f'Switching to {"dark" if dark else "light"} mode.')
for p in plugins:
if config.get_plugin_key(p.name, PluginKey.ENABLED):
- if force and (isinstance(p, Sound) or isinstance(p, Notification)):
+ if force and isinstance(p, Notification):
# skip sound and notify on apply settings
continue
try:
diff --git a/yin_yang/ui/main_window.py b/yin_yang/ui/main_window.py
index f4c1e205..a19cce38 100644
--- a/yin_yang/ui/main_window.py
+++ b/yin_yang/ui/main_window.py
@@ -3,7 +3,7 @@
################################################################################
## Form generated from reading UI file 'main_window.ui'
##
-## Created by: Qt User Interface Compiler version 6.6.0
+## Created by: Qt User Interface Compiler version 6.6.1
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
@@ -229,11 +229,6 @@ def setupUi(self, main_window):
self.settings_layout.addWidget(self.manual_buttons)
- self.toggle_sound = QCheckBox(self.settings)
- self.toggle_sound.setObjectName(u"toggle_sound")
-
- self.settings_layout.addWidget(self.toggle_sound)
-
self.toggle_notification = QCheckBox(self.settings)
self.toggle_notification.setObjectName(u"toggle_notification")
@@ -279,7 +274,7 @@ def setupUi(self, main_window):
self.plugins_scroll.setWidgetResizable(True)
self.plugins_scroll_content = QWidget()
self.plugins_scroll_content.setObjectName(u"plugins_scroll_content")
- self.plugins_scroll_content.setGeometry(QRect(0, 0, 518, 88))
+ self.plugins_scroll_content.setGeometry(QRect(0, 0, 501, 86))
self.plugins_scroll_content_layout = QVBoxLayout(self.plugins_scroll_content)
self.plugins_scroll_content_layout.setSpacing(6)
self.plugins_scroll_content_layout.setContentsMargins(11, 11, 11, 11)
@@ -358,7 +353,6 @@ def retranslateUi(self, main_window):
self.btn_location.setText(QCoreApplication.translate("main_window", u"update location automatically", None))
self.button_light.setText(QCoreApplication.translate("main_window", u"Light", None))
self.button_dark.setText(QCoreApplication.translate("main_window", u"Dark", None))
- self.toggle_sound.setText(QCoreApplication.translate("main_window", u"Make a sound when switching the theme", None))
self.toggle_notification.setText(QCoreApplication.translate("main_window", u"Send a notification", None))
#if QT_CONFIG(tooltip)
self.bootOffsetLabel.setToolTip(QCoreApplication.translate("main_window", u"Time to wait until the system finished booting. Default value is 10 seconds.", None))
diff --git a/yin_yang/ui/main_window_connector.py b/yin_yang/ui/main_window_connector.py
index ff7a45a3..4468fded 100755
--- a/yin_yang/ui/main_window_connector.py
+++ b/yin_yang/ui/main_window_connector.py
@@ -4,7 +4,7 @@
from PySide6 import QtWidgets
from PySide6.QtCore import QStandardPaths
from PySide6.QtGui import QScreen, QColor
-from PySide6.QtWidgets import QFileDialog, QMessageBox, QDialogButtonBox, QColorDialog,QGroupBox
+from PySide6.QtWidgets import QFileDialog, QMessageBox, QDialogButtonBox, QColorDialog, QGroupBox
from .main_window import Ui_main_window
from ..theme_switcher import set_desired_theme, set_mode
@@ -81,7 +81,6 @@ def load(self):
self.ui.btn_schedule.setChecked(True)
self.ui.location.setVisible(False)
- self.ui.toggle_sound.setChecked(config.get_plugin_key('sound', PluginKey.ENABLED))
self.ui.toggle_notification.setChecked(config.get_plugin_key('notification', PluginKey.ENABLED))
self.ui.bootOffset.setValue(config.boot_offset)
@@ -111,17 +110,16 @@ def load_location(self):
def load_plugins(self):
# First, remove sample plugin
- samplePlugin = cast(QGroupBox,self.ui.plugins_scroll_content.findChild(QGroupBox, 'samplePluginGroupBox'))
- samplePlugin.hide()
-
+ sample_plugin = cast(QGroupBox, self.ui.plugins_scroll_content.findChild(QGroupBox, 'samplePluginGroupBox'))
+ sample_plugin.hide()
widget: QGroupBox
for plugin in plugins:
# filter out plugins for application
- if plugin.name.casefold() in ['notification', 'sound']:
+ if plugin.name.casefold() == 'notification':
continue
- widget = cast(QGroupBox,self.ui.plugins_scroll_content.findChild(QGroupBox, 'group' + plugin.name))
+ widget = cast(QGroupBox, self.ui.plugins_scroll_content.findChild(QGroupBox, 'group' + plugin.name))
if widget is None:
widget = plugin.get_widget(self.ui.plugins_scroll_content)
self.ui.plugins_scroll_content_layout.addWidget(widget)
@@ -187,8 +185,6 @@ def setup_config_sync(self):
# connect dialog buttons
self.ui.btn_box.clicked.connect(self.save_config_to_file)
- self.ui.toggle_sound.toggled.connect(
- lambda enabled: config.update_plugin_key('sound', PluginKey.ENABLED, enabled))
self.ui.toggle_notification.toggled.connect(
lambda enabled: config.update_plugin_key('notification', PluginKey.ENABLED, enabled))
diff --git a/yin_yang/ui/resources_rc.py b/yin_yang/ui/resources_rc.py
index feafd0eb..f8be410e 100644
--- a/yin_yang/ui/resources_rc.py
+++ b/yin_yang/ui/resources_rc.py
@@ -1,133 +1,122 @@
# Resource object code (Python 3)
# Created by: object code
-# Created by: The Resource Compiler for Qt version 6.6.0
+# Created by: The Resource Compiler for Qt version 6.6.1
# WARNING! All changes made in this file will be lost!
from PySide6 import QtCore
qt_resource_data = b"\
-\x00\x00\x07w\
+\x00\x00\x06\xca\
<\
\xb8d\x18\xca\xef\x9c\x95\xcd!\x1c\xbf`\xa1\xbd\xdd\xa7\
-\x00\x00\x00\x05de_DEB\x00\x00\x00\x98\x00\x04\
-\xa8\x8b\x00\x00\x00\xd6\x00\x05\x8c\x04\x00\x00\x068\x00\x0a\
-KE\x00\x00\x02t\x00J\x88\xea\x00\x00\x01\x06\x00R\
-\xfd\xf4\x00\x00\x01\xd7\x00\x89?\xc9\x00\x00\x05x\x02\xcf\
-6\x15\x00\x00\x06f\x03^\x05u\x00\x00\x00o\x05/\
-\xdfz\x00\x00\x02\x04\x06\x99\x04U\x00\x00\x03\xeb\x07;\
-\xe0\x03\x00\x00\x03\x19\x0ai\xf3\xe7\x00\x00\x05\xf1\x0a\xa0\
-\x8cG\x00\x00\x00\x00\x0b\x0b\xe8\x0a\x00\x00\x01\x96\x0b\xa1\
-\xae>\x00\x00\x04^\x0c\xbb\x01s\x00\x00\x03\xa9\x0e\x0e\
-\x8c\xca\x00\x00\x024\x0f\x0ag\xee\x00\x00\x03@\x0fF\
-^:\x00\x00\x019i\x00\x00\x06\xb4\x03\x00\x00\x006\
-\x00A\x00u\x00t\x00o\x00m\x00a\x00t\x00i\
-\x00s\x00c\x00h\x00e\x00r\x00 \x00T\x00h\
-\x00e\x00m\x00e\x00n\x00w\x00e\x00c\x00h\
-\x00s\x00e\x00l\x08\x00\x00\x00\x00\x06\x00\x00\x00\x19\
-Automatic theme \
-switching\x07\x00\x00\x00\x0bma\
-in_window\x01\x03\x00\x00\x008\x00\
-B\x00e\x00n\x00u\x00t\x00z\x00e\x00r\x00\
-d\x00e\x00f\x00i\x00n\x00i\x00e\x00r\x00\
-t\x00e\x00r\x00 \x00Z\x00e\x00i\x00t\x00\
-r\x00a\x00u\x00m\x08\x00\x00\x00\x00\x06\x00\x00\x00\
-\x0fCustom Schedule\
-\x07\x00\x00\x00\x0bmain_window\
-\x01\x03\x00\x00\x00\x0c\x00D\x00u\x00n\x00k\x00e\
-\x00l\x08\x00\x00\x00\x00\x06\x00\x00\x00\x04Dark\
-\x07\x00\x00\x00\x0bmain_window\
-\x01\x03\x00\x00\x00\x0e\x00D\x00u\x00n\x00k\x00e\
-\x00l\x00:\x08\x00\x00\x00\x00\x06\x00\x00\x00\x05Da\
-rk:\x07\x00\x00\x00\x0bmain_win\
-dow\x01\x03\x00\x00\x00,\x00V\x00e\x00r\x00\
-z\x00\xf6\x00g\x00e\x00r\x00u\x00n\x00g\x00\
- \x00n\x00a\x00c\x00h\x00 \x00S\x00t\x00\
-a\x00r\x00t\x08\x00\x00\x00\x00\x06\x00\x00\x00\x11D\
-elay after boot:\
-\x07\x00\x00\x00\x0bmain_window\
-\x01\x03\x00\x00\x00\x18\x00B\x00r\x00e\x00i\x00t\
-\x00e\x00n\x00g\x00r\x00a\x00d\x00:\x08\x00\
-\x00\x00\x00\x06\x00\x00\x00\x09Latitude\
-:\x07\x00\x00\x00\x0bmain_windo\
-w\x01\x03\x00\x00\x00\x08\x00H\x00e\x00l\x00l\x08\
-\x00\x00\x00\x00\x06\x00\x00\x00\x05Light\x07\x00\
-\x00\x00\x0bmain_window\x01\x03\
-\x00\x00\x00\x0a\x00H\x00e\x00l\x00l\x00:\x08\x00\
-\x00\x00\x00\x06\x00\x00\x00\x06Light:\x07\x00\
-\x00\x00\x0bmain_window\x01\x03\
-\x00\x00\x00\x16\x00L\x00\xe4\x00n\x00g\x00e\x00n\
-\x00g\x00r\x00a\x00d\x00:\x08\x00\x00\x00\x00\x06\
-\x00\x00\x00\x0aLongitude:\x07\x00\
-\x00\x00\x0bmain_window\x01\x03\
-\x00\x00\x00`\x00M\x00a\x00c\x00h\x00e\x00 \
-\x00e\x00i\x00n\x00 \x00G\x00e\x00r\x00\xe4\
-\x00u\x00s\x00c\x00h\x00,\x00 \x00w\x00e\
-\x00n\x00n\x00 \x00d\x00a\x00s\x00 \x00T\
-\x00h\x00e\x00m\x00a\x00 \x00g\x00e\x00\xe4\
-\x00n\x00d\x00e\x00r\x00t\x00 \x00w\x00i\
-\x00r\x00d\x08\x00\x00\x00\x00\x06\x00\x00\x00%Ma\
-ke a sound when \
-switching the th\
-eme\x07\x00\x00\x00\x0bmain_win\
-dow\x01\x03\xff\xff\xff\xff\x08\x00\x00\x00\x00\x06\x00\
-\x00\x00\x07Plugins\x07\x00\x00\x00\x0bm\
-ain_window\x01\x03\x00\x00\x006\
-\x00S\x00e\x00n\x00d\x00e\x00 \x00e\x00i\
-\x00n\x00e\x00 \x00B\x00e\x00n\x00a\x00c\
-\x00h\x00r\x00i\x00c\x00h\x00t\x00i\x00g\
-\x00u\x00n\x00g\x08\x00\x00\x00\x00\x06\x00\x00\x00\x13\
-Send a notificat\
-ion\x07\x00\x00\x00\x0bmain_win\
-dow\x01\x03\x00\x00\x00\x1a\x00E\x00i\x00n\x00\
-s\x00t\x00e\x00l\x00l\x00u\x00n\x00g\x00\
-e\x00n\x08\x00\x00\x00\x00\x06\x00\x00\x00\x08Set\
-tings\x07\x00\x00\x00\x0bmain_w\
-indow\x01\x03\x00\x00\x00B\x00S\x00o\x00\
-n\x00n\x00e\x00n\x00a\x00u\x00f\x00g\x00\
-a\x00n\x00g\x00 \x00b\x00i\x00s\x00 \x00\
-S\x00o\x00n\x00n\x00e\x00n\x00u\x00n\x00\
-t\x00e\x00r\x00g\x00a\x00n\x00g\x08\x00\x00\
-\x00\x00\x06\x00\x00\x00\x11Sunset to\
- Sunrise\x07\x00\x00\x00\x0bmai\
-n_window\x01\x03\x00\x00\x00\xae\x00Z\
-\x00e\x00i\x00t\x00 \x00d\x00i\x00e\x00 \
-\x00g\x00e\x00w\x00a\x00r\x00t\x00e\x00t\
-\x00 \x00w\x00e\x00r\x00d\x00e\x00n\x00 \
-\x00s\x00o\x00l\x00l\x00 \x00w\x00\xe4\x00h\
-\x00r\x00e\x00n\x00d\x00 \x00d\x00a\x00s\
-\x00 \x00S\x00y\x00s\x00t\x00e\x00m\x00 \
-\x00s\x00t\x00a\x00r\x00t\x00e\x00t\x00.\
-\x00 \x00S\x00t\x00a\x00n\x00d\x00a\x00r\
-\x00d\x00w\x00e\x00r\x00t\x00 \x00i\x00s\
-\x00t\x00 \x001\x000\x00 \x00S\x00e\x00k\
-\x00u\x00n\x00d\x00e\x00n\x00.\x08\x00\x00\x00\
-\x00\x06\x00\x00\x00LTime to wa\
-it until the sys\
-tem finished boo\
-ting. Default va\
-lue is 10 second\
-s.\x07\x00\x00\x00\x0bmain_wind\
-ow\x01\x03\x00\x00\x00<\x00P\x00o\x00s\x00i\
-\x00t\x00i\x00o\x00n\x00 \x00a\x00u\x00t\
-\x00o\x00m\x00a\x00t\x00i\x00s\x00c\x00h\
-\x00 \x00b\x00e\x00s\x00t\x00i\x00m\x00m\
-\x00e\x00n\x08\x00\x00\x00\x00\x06\x00\x00\x00\x1dup\
-date location au\
-tomatically\x07\x00\x00\x00\x0b\
+\x00\x00\x00\x05de_DEB\x00\x00\x00\x90\x00\x04\
+\xa8\x8b\x00\x00\x00\xd6\x00\x05\x8c\x04\x00\x00\x05\x93\x00J\
+\x88\xea\x00\x00\x01\x06\x00R\xfd\xf4\x00\x00\x01\xd7\x00\x89\
+?\xc9\x00\x00\x04\xd3\x02\xcf6\x15\x00\x00\x05\xc1\x03^\
+\x05u\x00\x00\x00o\x05/\xdfz\x00\x00\x02\x04\x06\x99\
+\x04U\x00\x00\x03F\x07;\xe0\x03\x00\x00\x02t\x0ai\
+\xf3\xe7\x00\x00\x05L\x0a\xa0\x8cG\x00\x00\x00\x00\x0b\x0b\
+\xe8\x0a\x00\x00\x01\x96\x0b\xa1\xae>\x00\x00\x03\xb9\x0c\xbb\
+\x01s\x00\x00\x03\x04\x0e\x0e\x8c\xca\x00\x00\x024\x0f\x0a\
+g\xee\x00\x00\x02\x9b\x0fF^:\x00\x00\x019i\x00\
+\x00\x06\x0f\x03\x00\x00\x006\x00A\x00u\x00t\x00o\
+\x00m\x00a\x00t\x00i\x00s\x00c\x00h\x00e\
+\x00r\x00 \x00T\x00h\x00e\x00m\x00e\x00n\
+\x00w\x00e\x00c\x00h\x00s\x00e\x00l\x08\x00\
+\x00\x00\x00\x06\x00\x00\x00\x19Automati\
+c theme switchin\
+g\x07\x00\x00\x00\x0bmain_windo\
+w\x01\x03\x00\x00\x008\x00B\x00e\x00n\x00u\x00\
+t\x00z\x00e\x00r\x00d\x00e\x00f\x00i\x00\
+n\x00i\x00e\x00r\x00t\x00e\x00r\x00 \x00\
+Z\x00e\x00i\x00t\x00r\x00a\x00u\x00m\x08\
+\x00\x00\x00\x00\x06\x00\x00\x00\x0fCustom \
+Schedule\x07\x00\x00\x00\x0bmai\
+n_window\x01\x03\x00\x00\x00\x0c\x00D\
+\x00u\x00n\x00k\x00e\x00l\x08\x00\x00\x00\x00\x06\
+\x00\x00\x00\x04Dark\x07\x00\x00\x00\x0bmai\
+n_window\x01\x03\x00\x00\x00\x0e\x00D\
+\x00u\x00n\x00k\x00e\x00l\x00:\x08\x00\x00\x00\
+\x00\x06\x00\x00\x00\x05Dark:\x07\x00\x00\x00\x0b\
main_window\x01\x03\x00\x00\x00\
-\x1e\x00Y\x00i\x00n\x00 \x00Y\x00a\x00n\x00\
-g\x00 \x00\xf6\x00f\x00f\x00n\x00e\x00n\x08\
-\x00\x00\x00\x00\x06\x00\x00\x00\x0dOpen Yi\
-n Yang\x07\x00\x00\x00\x07systr\
-ay\x01\x03\x00\x00\x00\x0e\x00B\x00e\x00e\x00n\
-\x00d\x00e\x00n\x08\x00\x00\x00\x00\x06\x00\x00\x00\x04\
-Quit\x07\x00\x00\x00\x07systray\
-\x01\x03\x00\x00\x00&\x00F\x00a\x00r\x00b\x00s\
-\x00c\x00h\x00e\x00m\x00a\x00 \x00w\x00e\
-\x00c\x00h\x00s\x00e\x00l\x00n\x08\x00\x00\x00\
-\x00\x06\x00\x00\x00\x0cToggle the\
-me\x07\x00\x00\x00\x07systray\x01\x88\
-\x00\x00\x00\x02\x01\x01\
+,\x00V\x00e\x00r\x00z\x00\xf6\x00g\x00e\x00\
+r\x00u\x00n\x00g\x00 \x00n\x00a\x00c\x00\
+h\x00 \x00S\x00t\x00a\x00r\x00t\x08\x00\x00\
+\x00\x00\x06\x00\x00\x00\x11Delay aft\
+er boot:\x07\x00\x00\x00\x0bmai\
+n_window\x01\x03\x00\x00\x00\x18\x00B\
+\x00r\x00e\x00i\x00t\x00e\x00n\x00g\x00r\
+\x00a\x00d\x00:\x08\x00\x00\x00\x00\x06\x00\x00\x00\x09\
+Latitude:\x07\x00\x00\x00\x0bma\
+in_window\x01\x03\x00\x00\x00\x08\x00\
+H\x00e\x00l\x00l\x08\x00\x00\x00\x00\x06\x00\x00\x00\
+\x05Light\x07\x00\x00\x00\x0bmain_\
+window\x01\x03\x00\x00\x00\x0a\x00H\x00e\
+\x00l\x00l\x00:\x08\x00\x00\x00\x00\x06\x00\x00\x00\x06\
+Light:\x07\x00\x00\x00\x0bmain_\
+window\x01\x03\x00\x00\x00\x16\x00L\x00\xe4\
+\x00n\x00g\x00e\x00n\x00g\x00r\x00a\x00d\
+\x00:\x08\x00\x00\x00\x00\x06\x00\x00\x00\x0aLong\
+itude:\x07\x00\x00\x00\x0bmain_\
+window\x01\x03\xff\xff\xff\xff\x08\x00\x00\x00\
+\x00\x06\x00\x00\x00\x07Plugins\x07\x00\x00\
+\x00\x0bmain_window\x01\x03\x00\
+\x00\x006\x00S\x00e\x00n\x00d\x00e\x00 \x00\
+e\x00i\x00n\x00e\x00 \x00B\x00e\x00n\x00\
+a\x00c\x00h\x00r\x00i\x00c\x00h\x00t\x00\
+i\x00g\x00u\x00n\x00g\x08\x00\x00\x00\x00\x06\x00\
+\x00\x00\x13Send a notifi\
+cation\x07\x00\x00\x00\x0bmain_\
+window\x01\x03\x00\x00\x00\x1a\x00E\x00i\
+\x00n\x00s\x00t\x00e\x00l\x00l\x00u\x00n\
+\x00g\x00e\x00n\x08\x00\x00\x00\x00\x06\x00\x00\x00\x08\
+Settings\x07\x00\x00\x00\x0bmai\
+n_window\x01\x03\x00\x00\x00B\x00S\
+\x00o\x00n\x00n\x00e\x00n\x00a\x00u\x00f\
+\x00g\x00a\x00n\x00g\x00 \x00b\x00i\x00s\
+\x00 \x00S\x00o\x00n\x00n\x00e\x00n\x00u\
+\x00n\x00t\x00e\x00r\x00g\x00a\x00n\x00g\
+\x08\x00\x00\x00\x00\x06\x00\x00\x00\x11Sunset\
+ to Sunrise\x07\x00\x00\x00\x0b\
+main_window\x01\x03\x00\x00\x00\
+\xae\x00Z\x00e\x00i\x00t\x00 \x00d\x00i\x00\
+e\x00 \x00g\x00e\x00w\x00a\x00r\x00t\x00\
+e\x00t\x00 \x00w\x00e\x00r\x00d\x00e\x00\
+n\x00 \x00s\x00o\x00l\x00l\x00 \x00w\x00\
+\xe4\x00h\x00r\x00e\x00n\x00d\x00 \x00d\x00\
+a\x00s\x00 \x00S\x00y\x00s\x00t\x00e\x00\
+m\x00 \x00s\x00t\x00a\x00r\x00t\x00e\x00\
+t\x00.\x00 \x00S\x00t\x00a\x00n\x00d\x00\
+a\x00r\x00d\x00w\x00e\x00r\x00t\x00 \x00\
+i\x00s\x00t\x00 \x001\x000\x00 \x00S\x00\
+e\x00k\x00u\x00n\x00d\x00e\x00n\x00.\x08\
+\x00\x00\x00\x00\x06\x00\x00\x00LTime to\
+ wait until the \
+system finished \
+booting. Default\
+ value is 10 sec\
+onds.\x07\x00\x00\x00\x0bmain_w\
+indow\x01\x03\x00\x00\x00<\x00P\x00o\x00\
+s\x00i\x00t\x00i\x00o\x00n\x00 \x00a\x00\
+u\x00t\x00o\x00m\x00a\x00t\x00i\x00s\x00\
+c\x00h\x00 \x00b\x00e\x00s\x00t\x00i\x00\
+m\x00m\x00e\x00n\x08\x00\x00\x00\x00\x06\x00\x00\x00\
+\x1dupdate location\
+ automatically\x07\x00\
+\x00\x00\x0bmain_window\x01\x03\
+\x00\x00\x00\x1e\x00Y\x00i\x00n\x00 \x00Y\x00a\
+\x00n\x00g\x00 \x00\xf6\x00f\x00f\x00n\x00e\
+\x00n\x08\x00\x00\x00\x00\x06\x00\x00\x00\x0dOpen\
+ Yin Yang\x07\x00\x00\x00\x07sy\
+stray\x01\x03\x00\x00\x00\x0e\x00B\x00e\x00\
+e\x00n\x00d\x00e\x00n\x08\x00\x00\x00\x00\x06\x00\
+\x00\x00\x04Quit\x07\x00\x00\x00\x07syst\
+ray\x01\x03\x00\x00\x00&\x00F\x00a\x00r\x00\
+b\x00s\x00c\x00h\x00e\x00m\x00a\x00 \x00\
+w\x00e\x00c\x00h\x00s\x00e\x00l\x00n\x08\
+\x00\x00\x00\x00\x06\x00\x00\x00\x0cToggle \
+theme\x07\x00\x00\x00\x07systra\
+y\x01\x88\x00\x00\x00\x02\x01\x01\
\x00\x00\x07\x22\
\x00\
\x00\x1dUx\xda\xcdYKs\xdb6\x10\xbe\xe7Wp\
@@ -274,9 +263,9 @@
\x00\x00\x00\x10\x00\x02\x00\x00\x00\x01\x00\x00\x00\x03\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00.\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
-\x00\x00\x01\x8c^C\x1d\xc6\
-\x00\x00\x00P\x00\x01\x00\x00\x00\x01\x00\x00\x07{\
-\x00\x00\x01\x8cK\xf9\x0b4\
+\x00\x00\x01\x8d'?\x15:\
+\x00\x00\x00P\x00\x01\x00\x00\x00\x01\x00\x00\x06\xce\
+\x00\x00\x01\x8d#[_r\
"
def qInitResources():