diff --git a/.gitignore b/.gitignore
index aa9a011a..a3ea2b16 100755
--- a/.gitignore
+++ b/.gitignore
@@ -3,7 +3,7 @@ __pycache__
build-test*
build-ui-*
.vscode
-src/build.py
+yin_yang/build.py
setup.py
.idea/
diff --git a/README.md b/README.md
index 815b838c..7798c536 100755
--- a/README.md
+++ b/README.md
@@ -45,7 +45,7 @@ Yin-Yang can be downloaded from AUR as [yin-yang](https://aur.archlinux.org/pack
### Source
-Yin-Yang depends on `python-systemd` and `pyside6` from pypi. `python-systemd` requires you have installed the systemd-headers from your package manager.
+Yin-Yang depends on `python-systemd` and `pyside6` from pypi. `python-systemd` requires you have installed the systemd-headers from your package manager. You also need python development headers (e.g. `python3-devel`).
For CentOS, RHEL, and Fedora:
```bash
@@ -68,21 +68,20 @@ Then you can install Yin-Yang in a python virtual environment:
```bash
# bash is necessary to run the source command
bash
-# Removes any already present Yin-Yang code
-rm -rf Yin-Yang
# Clones the code to your local machine
git clone https://github.com/oskarsh/Yin-Yang.git
-# Enters the directory containing Yin-Yang's code
-if pwd != "Yin-Yang"; then cd Yin-Yang; fi
-## Creates a virtual environment for pypi (pip) packages
-python3 -m venv .venv
-source .venv/bin/activate
-# Installs pip requirements specified in repository
-pip3 install -r requirements.txt
+cd Yin-Yang
# Installs Yin-Yang
./scripts/install.sh
```
+For development, skip the install and instead create a venv in your home directory:
+```bash
+python -m venv .venv
+source .venv/bin/activate # this is for bash, there are similar scripts in the that directory for other shells like fish
+pip install -r requirements.txt
+```
+
## Documentation
Want to help out? Check out the wiki to learn how to contribute translations, plugins and more!
diff --git a/designer/main_window.ui b/designer/main_window.ui
index fabc5f4e..22e8ed8d 100755
--- a/designer/main_window.ui
+++ b/designer/main_window.ui
@@ -43,7 +43,7 @@
- 1
+ 0
@@ -276,6 +276,26 @@
+ -
+
+
+
-
+
+
+ Light
+
+
+
+ -
+
+
+ Dark
+
+
+
+
+
+
-
@@ -290,6 +310,30 @@
+ -
+
+
-
+
+
+ Time to wait until the system finished booting. Default value is 10 seconds.
+
+
+ Delay after boot:
+
+
+
+ -
+
+
+ s
+
+
+ 10
+
+
+
+
+
-
@@ -330,8 +374,8 @@
0
0
- 523
- 663
+ 518
+ 88
@@ -340,16 +384,19 @@
-
- Sample Plugin
+ Sample Plugin
-
+
+
+
QComboBox::AdjustToContentsOnFirstShow
- firefox-compact-light@mozilla.org
+ firefox-compact-light@mozilla.org
@@ -362,7 +409,7 @@
QComboBox::AdjustToContentsOnFirstShow
- firefox-compact-dark@mozilla.org
+ firefox-compact-dark@mozilla.org
@@ -402,12 +449,12 @@
setVisible(bool)
- 273
- 308
+ 289
+ 225
- 254
- 379
+ 270
+ 353
@@ -418,12 +465,12 @@
setVisible(bool)
- 114
- 192
+ 130
+ 109
- 219
- 280
+ 235
+ 197
@@ -434,8 +481,8 @@
setVisible(bool)
- 188
- 153
+ 198
+ 66
188
@@ -443,5 +490,21 @@
+
+ btn_enable
+ toggled(bool)
+ manual_buttons
+ setHidden(bool)
+
+
+ 109
+ 58
+
+
+ 274
+ 398
+
+
+
diff --git a/main.py b/main.py
deleted file mode 100755
index a1a14084..00000000
--- a/main.py
+++ /dev/null
@@ -1,107 +0,0 @@
-#!/bin/env python3
-
-import sys
-import logging
-from argparse import ArgumentParser
-from logging.handlers import RotatingFileHandler
-from pathlib import Path
-
-from PySide6 import QtWidgets
-from PySide6.QtCore import QTranslator, QLibraryInfo, QLocale
-from systemd import journal
-
-from src import daemon_handler
-from src.meta import ConfigEvent
-from src import yin_yang
-from src.config import config, Modes
-from src.ui import main_window_connector
-
-logger = logging.getLogger()
-
-
-def setup_logger(use_systemd_journal: bool):
- if use_systemd_journal:
- logger.addHandler(journal.JournalHandler(SYSLOG_IDENTIFIER='yin_yang'))
-
- # __debug__ is true when you run main.py without the -O argument (python main.py)
- # noinspection PyUnreachableCode
- if __debug__:
- # noinspection SpellCheckingInspection
- logging.basicConfig(
- level=logging.DEBUG,
- format='%(asctime)s %(levelname)s - %(name)s: %(message)s'
- )
- else:
- # if you run it with "python -O main.py" instead, debug is false
-
- # let the default logger print to the console
- # noinspection SpellCheckingInspection
- logging.basicConfig(
- level=logging.WARNING,
- format='%(asctime)s %(levelname)s - %(name)s: %(message)s'
- )
- # and add a handler that limits the size to 1 GB
- file_handler = RotatingFileHandler(
- str(Path.home()) + '/.local/share/yin_yang.log',
- maxBytes=10**9, backupCount=1
- )
- logging.root.addHandler(file_handler)
-
-
-def main(arguments):
- # checks whether $ yin-yang is run without args
- if len(sys.argv) == 1:
- config.add_event_listener(ConfigEvent.SAVE, daemon_handler.watcher)
- config.add_event_listener(ConfigEvent.CHANGE, daemon_handler.watcher)
- # load GUI
- app = QtWidgets.QApplication(sys.argv)
-
- # load translation
- try:
- lang = QLocale().name()
- logger.debug(f'Using language {lang}')
-
- # system translations
- path = QLibraryInfo.path(QLibraryInfo.TranslationsPath)
- translator = QTranslator(app)
- if translator.load(QLocale.system(), 'qtbase', '_', path):
- app.installTranslator(translator)
- else:
- raise FileNotFoundError('Error while loading system translations!')
-
- # application translations
- translator = QTranslator(app)
- path = ':translations'
- if translator.load(QLocale.system(), 'yin_yang', '.', path):
- app.installTranslator(translator)
- else:
- raise FileNotFoundError('Error while loading application translations!')
-
- except Exception as e:
- logger.error(str(e))
- print('Error while loading translation. Using default language.')
-
- window = main_window_connector.MainWindow()
- window.show()
- sys.exit(app.exec())
-
- if arguments.toggle:
- # terminate any running instances
- config.running = False
- config.mode = Modes.MANUAL
- yin_yang.set_mode(not config.dark_mode)
-
- if arguments.systemd:
- yin_yang.set_desired_theme()
-
-
-if __name__ == "__main__":
- # using ArgumentParser for parsing arguments
- parser = ArgumentParser()
- parser.add_argument("-t", "--toggle",
- help="toggles Yin-Yang",
- action="store_true")
- parser.add_argument("--systemd", help="uses systemd journal handler and applies desired theme", action='store_true')
- args = parser.parse_args()
- setup_logger(args.systemd)
- main(args)
diff --git a/requirements.txt b/requirements.txt
index ae0a672d..a8e71069 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,10 +1,6 @@
-numpy==1.23.5
-psutil==5.9.4
-PySide6==6.4.1
-PySide6-Addons==6.4.1
-PySide6-Essentials==6.4.1
-python-dateutil==2.8.2
-shiboken6==6.4.1
-six==1.16.0
+psutil==5.9.5
+PySide6==6.5.3
+PySide6-Addons==6.5.3
suntime==1.2.5
-systemd-python==234
+systemd-python==235
+requests~=2.28.2
\ No newline at end of file
diff --git a/resources/translations/yin_yang.de_DE.qm b/resources/translations/yin_yang.de_DE.qm
index d7028c3a..dfe9e222 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 24f7312f..1a0a1935 100644
--- a/resources/translations/yin_yang.de_DE.ts
+++ b/resources/translations/yin_yang.de_DE.ts
@@ -1,39 +1,6 @@
-
- MainWindow
-
-
-
- Sie verwenden Version {}
-
-
-
-
- Dunkler Modus wird zwischen {} und {} aktiv sein.
-
-
-
-
- Öffne helles Hintergrundbild
-
-
-
-
- Öffne dunkles Hintergrundbild
-
-
-
-
- Die Einstellungen wurden geändert. Möchten Sie sie speichern?
-
-
-
-
- Ungespeicherte Änderungen
-
-
main_window
@@ -79,37 +46,68 @@
-
+ Position automatisch bestimmen
-
+
+
+ Hell
+
+
+
+
+ Dunkel
+
+
+
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
+
+
+
+
+
+
+
+
+
+
+ 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 c0f17097..065f4499 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 73a65e07..067aec4d 100644
--- a/resources/translations/yin_yang.nl_NL.ts
+++ b/resources/translations/yin_yang.nl_NL.ts
@@ -1,39 +1,6 @@
-
- MainWindow
-
-
-
- U maakt gebruik van versie {}
-
-
-
-
- Het donkere thema wordt ingeschakeld van {} tot {}.
-
-
-
-
- Lichte achtergrond kiezen
-
-
-
-
- Donkere achtergrond kiezen
-
-
-
-
- De instellingen zijn gewijzigd. Wilt u ze opslaan?
-
-
-
-
- Niet-opgeslagen wijzigingen
-
-
main_window
@@ -82,34 +49,65 @@
-
+
+
+
+
+
+
+
+
+
+
+
Geluid afspelen na instellen van ander thema
-
+
Melding tonen
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Plug-ins
+
+
+ systray
-
-
- Voorbeeldplug-in
+
+
+ Context menu action in the systray
+
-
-
- firefox-compact-licht@mozilla.org
+
+
+ Context menu action in the systray
+
-
-
- firefox-compact-donker@mozilla.org
+
+
+ Context menu action in the systray
+
diff --git a/resources/yin-yang b/resources/yin-yang
index 24d9d2f3..a5181b56 100755
--- a/resources/yin-yang
+++ b/resources/yin-yang
@@ -1,3 +1,5 @@
#!/bin/bash
-cd /opt/yin-yang/ || exit
-python3 -O /opt/yin-yang/main.py "$@"
+cd /opt/yin-yang/ || exit 1
+# check whether the activate script is readable, then activate the venv
+[[ -r .venv/bin/activate ]] && source .venv/bin/activate
+python3 -Om yin_yang "$@"
diff --git a/resources/yin_yang.service b/resources/yin_yang.service
index d3d2b781..d091623f 100644
--- a/resources/yin_yang.service
+++ b/resources/yin_yang.service
@@ -1,5 +1,6 @@
[Unit]
Description=Automatic light and dark mode
+After=suspend.target
[Service]
ExecStart=/usr/bin/yin-yang --systemd
diff --git a/scripts/build_ui.sh b/scripts/build_ui.sh
index aebe92dc..c299d3ee 100755
--- a/scripts/build_ui.sh
+++ b/scripts/build_ui.sh
@@ -1,11 +1,11 @@
#!/bin/bash
# resource file
-pyside6-rcc ./resources/resources.qrc -o ./resources_rc.py
+pyside6-rcc ./resources/resources.qrc -o ./yin_yang/ui/resources_rc.py
# ui file from qt designer
-pyside6-uic ./designer/main_window.ui > ./src/ui/main_window.py
+pyside6-uic --from-imports ./designer/main_window.ui -o ./yin_yang/ui/main_window.py
# extract strings to translate (doesn't work with .pro file unfortunately)
-pyside6-lupdate ./designer/main_window.ui ./src/ui/main_window_connector.py \
+pyside6-lupdate ./designer/main_window.ui ./yin_yang/* \
-ts resources/translations/yin_yang.*.ts -no-obsolete
# generate binary translation files
pyside6-lrelease ./resources/translations/yin_yang.*.ts
diff --git a/scripts/install.sh b/scripts/install.sh
index 1f691161..f7801710 100755
--- a/scripts/install.sh
+++ b/scripts/install.sh
@@ -14,7 +14,12 @@ echo "Uninstalling old version, if it exists"
./scripts/uninstall.sh
echo "Installing dependencies …"
-pip3 install -r requirements.txt
+# create virtual environment
+python3 -m venv /opt/yin-yang/.venv
+source .venv/bin/activate
+/opt/yin-yang/.venv/bin/pip3 install --upgrade setuptools pip wheel
+/opt/yin-yang/.venv/bin/pip3 install -r requirements.txt
+
echo "Installing yin yang"
#check if /opt/ directory exists else create
if [ ! -d /opt/ ]; then
diff --git a/tests/test_communication.py b/tests/test_communication.py
index 0fc33c6f..6c8d0aa2 100644
--- a/tests/test_communication.py
+++ b/tests/test_communication.py
@@ -5,10 +5,10 @@
from datetime import datetime, time
from subprocess import Popen, PIPE
-import communicate
-from src.meta import PluginKey
-from src.config import config
-from src.yin_yang import should_be_dark
+from yin_yang import communicate
+from yin_yang.meta import PluginKey
+from yin_yang.config import config
+from yin_yang.theme_switcher import should_be_dark
def should_be_dark_extensions(time_current: int, time_dark: int):
diff --git a/tests/test_config.py b/tests/test_config.py
index 6ac98c5c..79b13087 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -3,8 +3,8 @@
from pathlib import Path
from typing import Optional
-from src.config import config, ConfigWatcher, update_config
-from src.meta import Desktop, Modes, PluginKey, ConfigEvent
+from yin_yang.config import config, ConfigWatcher, update_config
+from yin_yang.meta import Desktop, Modes, PluginKey, ConfigEvent
config_path = f"{Path.home()}/.config/yin_yang/yin_yang_dev.json"
diff --git a/tests/test_daemon.py b/tests/test_daemon.py
index d57a77a7..4f34729f 100644
--- a/tests/test_daemon.py
+++ b/tests/test_daemon.py
@@ -1,7 +1,7 @@
import unittest
from datetime import time
-from src.yin_yang import should_be_dark
+from yin_yang.theme_switcher import should_be_dark
class DaemonTest(unittest.TestCase):
diff --git a/tests/test_daemon_handler.py b/tests/test_daemon_handler.py
index 8173340d..0d845b43 100644
--- a/tests/test_daemon_handler.py
+++ b/tests/test_daemon_handler.py
@@ -5,9 +5,9 @@
from datetime import time
from os.path import isfile
-from src import daemon_handler
-from src.config import config
-from src.meta import Modes, ConfigEvent
+from yin_yang import daemon_handler
+from yin_yang.config import config
+from yin_yang.meta import Modes, ConfigEvent
class DaemonTest(unittest.TestCase):
diff --git a/tests/test_plugin_class.py b/tests/test_plugin_class.py
index d91a6484..61a43f54 100644
--- a/tests/test_plugin_class.py
+++ b/tests/test_plugin_class.py
@@ -2,7 +2,7 @@
from PySide6.QtGui import QColor
-from src.plugins._plugin import PluginCommandline, Plugin, get_qcolor_from_int, get_int_from_qcolor
+from yin_yang.plugins._plugin import PluginCommandline, Plugin, get_qcolor_from_int, get_int_from_qcolor
class MinimalPlugin(Plugin):
diff --git a/tests/test_plugins.py b/tests/test_plugins.py
index d10a6c28..f6fe3a5e 100644
--- a/tests/test_plugins.py
+++ b/tests/test_plugins.py
@@ -1,8 +1,8 @@
import unittest
-from src.config import config
-from src.config import plugins
-from src.plugins._plugin import Plugin, ExternalPlugin
+from yin_yang.config import config
+from yin_yang.config import plugins
+from yin_yang.plugins._plugin import Plugin, ExternalPlugin
class PluginsTest(unittest.TestCase):
diff --git a/yin_yang/NotificationHandler.py b/yin_yang/NotificationHandler.py
new file mode 100644
index 00000000..6f5a3974
--- /dev/null
+++ b/yin_yang/NotificationHandler.py
@@ -0,0 +1,9 @@
+import subprocess
+from logging import Handler
+
+
+class NotificationHandler(Handler):
+ """Shows logs as notifications"""
+ def emit(self, record):
+ subprocess.call(['notify-send', record.levelname, record.msg,
+ '-a', 'Yin & Yang', '-u', 'low', '--icon', 'yin_yang'])
diff --git a/src/__init__.py b/yin_yang/__init__.py
similarity index 100%
rename from src/__init__.py
rename to yin_yang/__init__.py
diff --git a/yin_yang/__main__.py b/yin_yang/__main__.py
new file mode 100755
index 00000000..c5d248f4
--- /dev/null
+++ b/yin_yang/__main__.py
@@ -0,0 +1,136 @@
+#!/bin/env python3
+
+import sys
+import logging
+from argparse import ArgumentParser
+from logging.handlers import RotatingFileHandler
+from pathlib import Path
+
+from PySide6 import QtWidgets
+from PySide6.QtCore import QTranslator, QLibraryInfo, QLocale, QObject
+from PySide6.QtGui import QIcon
+from PySide6.QtWidgets import QSystemTrayIcon, QMenu
+from systemd import journal
+
+from yin_yang.NotificationHandler import NotificationHandler
+from yin_yang import daemon_handler
+from yin_yang.meta import ConfigEvent
+from yin_yang import theme_switcher
+from yin_yang.config import config, Modes
+from yin_yang.ui import main_window_connector
+
+logger = logging.getLogger()
+
+
+def setup_logger(use_systemd_journal: bool):
+ notification_handler = NotificationHandler()
+ notification_handler.addFilter(lambda record: record.levelno > logging.WARNING)
+ logger.addHandler(notification_handler)
+
+ if use_systemd_journal:
+ logger.addHandler(journal.JournalHandler(SYSLOG_IDENTIFIER='yin_yang'))
+
+ # __debug__ is true when you run __main__.py without the -O argument (python __main__.py)
+ # noinspection PyUnreachableCode
+ if __debug__:
+ # noinspection SpellCheckingInspection
+ logging.basicConfig(
+ level=logging.DEBUG,
+ format='%(asctime)s %(levelname)s - %(name)s: %(message)s'
+ )
+ else:
+ # if you run it with "python -O __main__.py" instead, debug is false
+
+ # let the default logger print to the console
+ # noinspection SpellCheckingInspection
+ logging.basicConfig(
+ level=logging.WARNING,
+ format='%(asctime)s %(levelname)s - %(name)s: %(message)s'
+ )
+ # and add a handler that limits the size to 1 GB
+ file_handler = RotatingFileHandler(
+ str(Path.home()) + '/.local/share/yin_yang.log',
+ maxBytes=10**9, backupCount=1
+ )
+ logging.root.addHandler(file_handler)
+
+def systray_icon_clicked(reason: QSystemTrayIcon.ActivationReason):
+ match reason:
+ case QSystemTrayIcon.ActivationReason.MiddleClick:
+ theme_switcher.set_mode(not config.dark_mode)
+ case QSystemTrayIcon.ActivationReason.Trigger:
+ window.show()
+
+
+# using ArgumentParser for parsing arguments
+parser = ArgumentParser()
+parser.add_argument('-t', '--toggle',
+ help='toggles Yin-Yang',
+ action='store_true')
+parser.add_argument('--systemd', help='uses systemd journal handler and applies desired theme', action='store_true')
+arguments = parser.parse_args()
+setup_logger(arguments.systemd)
+
+if arguments.toggle:
+ # terminate any running instances
+ config.running = False
+ config.mode = Modes.MANUAL
+ theme_switcher.set_mode(not config.dark_mode)
+
+elif arguments.systemd:
+ theme_switcher.set_desired_theme()
+
+else:
+ # load GUI
+ config.add_event_listener(ConfigEvent.SAVE, daemon_handler.watcher)
+ config.add_event_listener(ConfigEvent.CHANGE, daemon_handler.watcher)
+ app = QtWidgets.QApplication(sys.argv)
+ # fixes icon on wayland
+ app.setDesktopFileName('Yin-Yang')
+
+ # load translation
+ try:
+ lang = QLocale().name()
+ logger.debug(f'Using language {lang}')
+
+ # system translations
+ path = QLibraryInfo.path(QLibraryInfo.TranslationsPath)
+ translator = QTranslator(app)
+ if translator.load(QLocale.system(), 'qtbase', '_', path):
+ app.installTranslator(translator)
+ else:
+ raise FileNotFoundError('Error while loading system translations!')
+
+ # application translations
+ translator = QTranslator(app)
+ path = ':translations'
+ if translator.load(QLocale.system(), 'yin_yang', '.', path):
+ app.installTranslator(translator)
+ else:
+ raise FileNotFoundError('Error while loading application translations!')
+
+ except Exception as e:
+ logger.error(str(e))
+ print('Error while loading translation. Using default language.')
+
+ # show systray icon
+ if QSystemTrayIcon.isSystemTrayAvailable():
+ app.setQuitOnLastWindowClosed(False)
+
+ icon = QSystemTrayIcon(QIcon(u':icons/logo'), app)
+ icon.activated.connect(systray_icon_clicked)
+ 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)
+
+ icon.setContextMenu(menu)
+ icon.show()
+ else:
+ logger.debug('System tray is unsupported')
+
+ window = main_window_connector.MainWindow()
+ window.show()
+ sys.exit(app.exec())
diff --git a/communicate.py b/yin_yang/communicate.py
similarity index 98%
rename from communicate.py
rename to yin_yang/communicate.py
index c7bb04dd..2fbb7222 100755
--- a/communicate.py
+++ b/yin_yang/communicate.py
@@ -12,8 +12,8 @@
from datetime import datetime, time as dt_time
from pathlib import Path
-from src.meta import PluginKey
-from src.config import config
+from .meta import PluginKey
+from .config import config
logging.basicConfig(filename=str(Path.home()) + '/.local/share/yin_yang.log', level=logging.DEBUG,
format='%(asctime)s %(levelname)s - %(name)s: %(message)s')
diff --git a/src/config.py b/yin_yang/config.py
similarity index 94%
rename from src/config.py
rename to yin_yang/config.py
index fca6e507..c3fce903 100755
--- a/src/config.py
+++ b/yin_yang/config.py
@@ -8,13 +8,14 @@
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 src.meta import Modes, Desktop, PluginKey, ConfigEvent
-from src.plugins import get_plugins
+from .meta import Modes, Desktop, PluginKey, ConfigEvent
+from .plugins import get_plugins
logger = logging.getLogger(__name__)
@@ -119,6 +120,7 @@ def get_sun_time(latitude, longitude) -> tuple[time, time]:
locationSource = QGeoPositionInfoSource.createDefaultSource(parent)
+@cache
def get_current_location() -> QGeoCoordinate:
if locationSource is None:
logger.error("No location source is available")
@@ -134,8 +136,13 @@ def get_current_location() -> QGeoCoordinate:
sleep(1)
coordinate = pos.coordinate()
if not coordinate.isValid():
- logger.error('Location could not be determined')
- return QGeoCoordinate(0, 0)
+ 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
@@ -153,6 +160,10 @@ def get_desktop() -> Desktop:
return Desktop.KDE
case 'xfce':
return Desktop.XFCE
+ case 'mate':
+ return Desktop.MATE
+ case 'x-cinnamon':
+ return Desktop.CINNAMON
case 'sway' | 'hyprland':
return Desktop.GNOME
case _:
@@ -339,7 +350,7 @@ def defaults(self) -> dict:
# NOTE: if you change or add new values here, make sure to update the version number and update_config() method
conf_default = {
- 'version': 3.2,
+ 'version': 3.3,
'running': False,
'dark_mode': False,
'mode': Modes.MANUAL.value,
@@ -347,6 +358,7 @@ def defaults(self) -> dict:
'update_location': False,
'update_interval': 60,
'times': ('07:00', '20:00'),
+ 'boot_offset': 10,
'plugins': {}
}
@@ -465,10 +477,14 @@ def desktop(self) -> Desktop:
return get_desktop()
@property
- def update_interval(self) -> int:
+ def boot_offset(self) -> int:
"""Seconds that should pass until next check"""
- return self['update_interval']
+ return self['boot_offset']
+
+ @boot_offset.setter
+ def boot_offset(self, value: int):
+ self['boot_offset'] = value
# create global object with current version
diff --git a/src/daemon_handler.py b/yin_yang/daemon_handler.py
similarity index 94%
rename from src/daemon_handler.py
rename to yin_yang/daemon_handler.py
index 765016ab..6b845bf8 100644
--- a/src/daemon_handler.py
+++ b/yin_yang/daemon_handler.py
@@ -4,8 +4,8 @@
from enum import Enum, auto
from pathlib import Path
-from src.config import ConfigWatcher, config
-from src.meta import ConfigEvent, Modes
+from .config import ConfigWatcher, config
+from .meta import ConfigEvent, Modes
logger = logging.getLogger(__name__)
SYSTEMD_PATH = Path.home() / '.local/share/systemd/user'
@@ -43,6 +43,7 @@ def update_times():
time_light, time_dark = config.times
lines[4] = f'OnCalendar={time_light}\n'
lines[5] = f'OnCalendar={time_dark}\n'
+ lines[6] = f'OnStartupSec={config.boot_offset}\n'
with TIMER_PATH.open('w') as file:
file.writelines(lines)
@@ -73,7 +74,7 @@ def _set_needed_updates(self, change_values):
self._next_timer_update = SaveWatcher._UpdateTimerStatus.STOP
else:
self._next_timer_update = SaveWatcher._UpdateTimerStatus.UPDATE_TIMES
- case 'times' | 'coordinates':
+ case 'times' | 'coordinates' | 'boot_offset':
self._next_timer_update = SaveWatcher._UpdateTimerStatus.UPDATE_TIMES
def _update_timer(self):
diff --git a/src/meta.py b/yin_yang/meta.py
similarity index 92%
rename from src/meta.py
rename to yin_yang/meta.py
index cc577c01..cbfe421f 100644
--- a/src/meta.py
+++ b/yin_yang/meta.py
@@ -14,6 +14,8 @@ class Desktop(Enum):
GNOME = 'gnome'
XFCE = 'xfce'
UNKNOWN = 'unknown'
+ MATE = 'mate'
+ CINNAMON = 'cinnamon'
class PluginKey(Enum):
diff --git a/src/plugins/__init__.py b/yin_yang/plugins/__init__.py
similarity index 59%
rename from src/plugins/__init__.py
rename to yin_yang/plugins/__init__.py
index 0c682635..c40941c2 100755
--- a/src/plugins/__init__.py
+++ b/yin_yang/plugins/__init__.py
@@ -1,12 +1,12 @@
-from src.meta import Desktop
-from src.plugins import system, colors, gtk, kvantum, wallpaper, custom
-from src.plugins import firefox, brave, gedit, only_office
-from src.plugins import vscode, atom, konsole
-from src.plugins import sound, notify
+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
# NOTE initialize your plugin over here:
# The order in the list specifies the order in the config gui
-from src.plugins._plugin import Plugin, ExternalPlugin
+from yin_yang.plugins._plugin import Plugin, ExternalPlugin
def get_plugins(desktop: Desktop) -> [Plugin]:
@@ -14,6 +14,7 @@ def get_plugins(desktop: Desktop) -> [Plugin]:
system.System(desktop),
colors.Colors(desktop),
gtk.Gtk(desktop),
+ icons.Icons(desktop),
kvantum.Kvantum(),
wallpaper.Wallpaper(desktop),
firefox.Firefox(),
@@ -22,6 +23,7 @@ def get_plugins(desktop: Desktop) -> [Plugin]:
atom.Atom(),
gedit.Gedit(),
only_office.OnlyOffice(),
+ okular.Okular(),
konsole.Konsole(),
custom.Custom(),
sound.Sound(),
@@ -29,5 +31,5 @@ def get_plugins(desktop: Desktop) -> [Plugin]:
]
-# this lets us skip all external plugins in yin_yang.py while keeping _plugin "private"
+# this lets us skip all external plugins in theme_switcher.py while keeping _plugin "private"
ExternalPlugin = ExternalPlugin
diff --git a/src/plugins/_plugin.py b/yin_yang/plugins/_plugin.py
similarity index 94%
rename from src/plugins/_plugin.py
rename to yin_yang/plugins/_plugin.py
index 430e2fc0..0568d08f 100644
--- a/src/plugins/_plugin.py
+++ b/yin_yang/plugins/_plugin.py
@@ -6,7 +6,7 @@
from PySide6.QtGui import QColor, QRgba64
from PySide6.QtWidgets import QGroupBox, QHBoxLayout, QLineEdit, QComboBox
-from src.meta import UnsupportedDesktopError
+from ..meta import UnsupportedDesktopError
logger = logging.getLogger(__name__)
@@ -144,14 +144,22 @@ def insert_theme(self, theme: str) -> list:
return command
- @property
- def available(self) -> bool:
- # Runs the first entry in the command list with --help
+ @staticmethod
+ def check_command(command) -> bool:
+ # Returns true if command execution succeeds
try:
- return subprocess.run([self.command[0], '--help'], stdout=subprocess.DEVNULL).returncode == 0
+ subprocess.check_call(command, stdout=subprocess.DEVNULL)
+ return True
except FileNotFoundError:
# if no such command is available, the plugin is not available
return False
+ except subprocess.CalledProcessError:
+ # command execution failed
+ return False
+
+ @property
+ def available(self):
+ return self.check_command([self.command[0], '--help'])
class PluginDesktopDependent(Plugin):
diff --git a/src/plugins/atom.py b/yin_yang/plugins/atom.py
similarity index 100%
rename from src/plugins/atom.py
rename to yin_yang/plugins/atom.py
diff --git a/src/plugins/brave.py b/yin_yang/plugins/brave.py
similarity index 96%
rename from src/plugins/brave.py
rename to yin_yang/plugins/brave.py
index ac845668..8b2cf451 100644
--- a/src/plugins/brave.py
+++ b/yin_yang/plugins/brave.py
@@ -5,7 +5,7 @@
from PySide6.QtGui import QColor
from PySide6.QtWidgets import QWidget, QVBoxLayout, QLineEdit, QPushButton
-from src.plugins._plugin import Plugin, get_int_from_qcolor
+from ._plugin import Plugin, get_int_from_qcolor
path = f'{Path.home()}/.config/BraveSoftware/Brave-Browser/Default/Preferences'
diff --git a/src/plugins/colors.py b/yin_yang/plugins/colors.py
similarity index 90%
rename from src/plugins/colors.py
rename to yin_yang/plugins/colors.py
index d58abd1a..5796d57d 100644
--- a/src/plugins/colors.py
+++ b/yin_yang/plugins/colors.py
@@ -1,8 +1,8 @@
import subprocess
import re
-from src.meta import Desktop
-from src.plugins._plugin import Plugin, PluginDesktopDependent, PluginCommandline
+from ..meta import Desktop
+from ._plugin import Plugin, PluginDesktopDependent, PluginCommandline
class Colors(PluginDesktopDependent):
diff --git a/src/plugins/custom.py b/yin_yang/plugins/custom.py
similarity index 94%
rename from src/plugins/custom.py
rename to yin_yang/plugins/custom.py
index 4e477816..2f5547ce 100644
--- a/src/plugins/custom.py
+++ b/yin_yang/plugins/custom.py
@@ -2,7 +2,7 @@
from PySide6.QtWidgets import QLineEdit
-from src.plugins._plugin import PluginCommandline
+from ._plugin import PluginCommandline
class Custom(PluginCommandline):
diff --git a/src/plugins/firefox.py b/yin_yang/plugins/firefox.py
similarity index 100%
rename from src/plugins/firefox.py
rename to yin_yang/plugins/firefox.py
diff --git a/src/plugins/gedit.py b/yin_yang/plugins/gedit.py
similarity index 90%
rename from src/plugins/gedit.py
rename to yin_yang/plugins/gedit.py
index ceba099b..3fc45a70 100644
--- a/src/plugins/gedit.py
+++ b/yin_yang/plugins/gedit.py
@@ -2,8 +2,8 @@
from os.path import isdir
from xml.etree import ElementTree
-from src.plugins._plugin import PluginCommandline
-from src.plugins.system import test_gnome_availability
+from ._plugin import PluginCommandline
+from .system import test_gnome_availability
path = '/usr/share/gtksourceview-4/styles/'
diff --git a/src/plugins/gtk.py b/yin_yang/plugins/gtk.py
similarity index 70%
rename from src/plugins/gtk.py
rename to yin_yang/plugins/gtk.py
index 61594451..7dad089c 100755
--- a/src/plugins/gtk.py
+++ b/yin_yang/plugins/gtk.py
@@ -4,9 +4,9 @@
from PySide6.QtDBus import QDBusConnection, QDBusMessage
-from src.meta import Desktop
-from src.plugins._plugin import PluginDesktopDependent, Plugin, PluginCommandline
-from src.plugins.system import test_gnome_availability
+from ..meta import Desktop
+from ._plugin import PluginDesktopDependent, Plugin, PluginCommandline
+from .system import test_gnome_availability
logger = logging.getLogger(__name__)
@@ -26,8 +26,12 @@ def __init__(self, desktop: Desktop):
if not self.strategy.available:
print('You need to install an extension for gnome to use it. \n'
'You can get it from here: https://extensions.gnome.org/extension/19/user-themes/')
+ case Desktop.MATE:
+ super().__init__(_Mate())
case Desktop.XFCE:
super().__init__(_Xfce())
+ case Desktop.CINNAMON:
+ super().__init__(_Cinnamon())
case _:
super().__init__(None)
@@ -83,3 +87,25 @@ def __init__(self):
super(_Xfce, self).__init__(['xfconf-query', '-c', 'xsettings', '-p', '/Net/ThemeName', '-s', '{theme}'])
self.theme_light = 'Adwaita'
self.theme_dark = 'Adwaita-dark'
+
+
+class _Mate(PluginCommandline):
+ def __init__(self):
+ super().__init__(['dconf', 'write', '/org/mate/desktop/interface/gtk-theme', '\'{theme}\''])
+ self.theme_light = 'Yaru'
+ self.theme_dark = 'Yaru-dark'
+
+ @property
+ def available(self) -> bool:
+ return self.check_command(['dconf', 'help'])
+
+
+class _Cinnamon(PluginCommandline):
+ def __init__(self):
+ super().__init__(['gsettings', 'set', 'org.cinnamon.desktop.interface', 'gtk-theme', '\"{theme}\"'])
+ self.theme_light = 'Adwaita'
+ self.theme_dark = 'Adwaita-dark'
+
+ @property
+ def available(self) -> bool:
+ return test_gnome_availability(self.command)
diff --git a/yin_yang/plugins/icons.py b/yin_yang/plugins/icons.py
new file mode 100644
index 00000000..3121e6c4
--- /dev/null
+++ b/yin_yang/plugins/icons.py
@@ -0,0 +1,36 @@
+from .system import test_gnome_availability
+from ..meta import Desktop
+from ._plugin import PluginDesktopDependent, PluginCommandline
+
+
+class Icons(PluginDesktopDependent):
+ def __init__(self, desktop: Desktop):
+ match desktop:
+ case Desktop.MATE:
+ super().__init__(_Mate())
+ case Desktop.CINNAMON:
+ super().__init__(_Cinnamon())
+ case _:
+ super().__init__(None)
+
+
+class _Mate(PluginCommandline):
+ def __init__(self):
+ super().__init__(['dconf', 'write', '/org/mate/desktop/interface/icon-theme', '\'{theme}\''])
+ self.theme_light = 'Yaru'
+ self.theme_dark = 'Yaru-dark'
+
+ @property
+ def available(self):
+ return self.check_command(['dconf', 'help'])
+
+
+class _Cinnamon(PluginCommandline):
+ def __init__(self):
+ super().__init__(['gsettings', 'set', 'org.cinnamon.desktop.interface', 'icon-theme', '\"{theme}\"'])
+ self.theme_light = 'Mint-X'
+ self.theme_dark = 'gnome'
+
+ @property
+ def available(self) -> bool:
+ return test_gnome_availability(self.command)
diff --git a/src/plugins/konsole.py b/yin_yang/plugins/konsole.py
similarity index 87%
rename from src/plugins/konsole.py
rename to yin_yang/plugins/konsole.py
index a771248c..8f10d5d1 100644
--- a/src/plugins/konsole.py
+++ b/yin_yang/plugins/konsole.py
@@ -10,7 +10,7 @@
import psutil
from PySide6.QtDBus import QDBusConnection, QDBusMessage
-from src.plugins._plugin import Plugin
+from ._plugin import Plugin
logger = logging.getLogger(__name__)
@@ -65,8 +65,8 @@ def set_mode(self, dark: bool) -> bool:
# Get the process IDs of all running Konsole instances owned by the current user
process_ids = [
- proc.pid for proc in psutil.process_iter(['name', 'username'])
- if proc.info['name'] == 'konsole' and proc.info['username'] == os.getlogin()
+ proc.pid for proc in psutil.process_iter()
+ if proc.name() == 'konsole' and proc.username() == os.getlogin()
]
# loop: console processes
@@ -77,8 +77,8 @@ def set_mode(self, dark: bool) -> bool:
set_profile('org.kde.yakuake', profile)
process_ids = [
- proc.pid for proc in psutil.process_iter(['name', 'username'])
- if proc.info['name'] == 'dolphin' and proc.info['username'] == os.getlogin()
+ proc.pid for proc in psutil.process_iter()
+ if proc.name() == 'dolphin' and proc.username() == os.getlogin()
]
# loop: dolphin processes
@@ -130,6 +130,8 @@ def default_profile(self):
# If a match is found, return the content of the wildcard '*'
if match:
value = match.group(1)
+ if not os.path.isfile(self.user_path / value):
+ value = None
if value is None:
# use the first found profile
@@ -139,8 +141,24 @@ def default_profile(self):
break
if value is not None:
logger.warning(f'No default profile found, using {value} instead.')
- else:
- raise ValueError('No Konsole profile found.')
+
+ if value is None:
+ # create a custom profile manually
+ file_content = """[Appearance]
+ColorScheme=Breeze
+
+[General]
+Command=/bin/bash
+Name=Fish
+Parent=FALLBACK/
+"""
+
+ with (self.user_path / 'Default.profile').open('w') as file:
+ file.writelines(file_content)
+
+ self.default_profile = 'Default.profile'
+
+ return 'Default.profile'
return value
@@ -176,7 +194,13 @@ def update_profile(self, dark: bool, theme: str):
profile_config = ConfigParser()
profile_config.optionxform = str
profile_config.read(file_path)
- profile_config['Appearance']['ColorScheme'] = theme
+
+ try:
+ profile_config['Appearance']['ColorScheme'] = theme
+ except KeyError:
+ profile_config.add_section('Appearance')
+ profile_config['Appearance']['ColorScheme'] = theme
+
with open(file_path, 'w') as file:
profile_config.write(file)
diff --git a/src/plugins/kvantum.py b/yin_yang/plugins/kvantum.py
similarity index 95%
rename from src/plugins/kvantum.py
rename to yin_yang/plugins/kvantum.py
index b02da05e..82854a49 100755
--- a/src/plugins/kvantum.py
+++ b/yin_yang/plugins/kvantum.py
@@ -1,7 +1,7 @@
import os
from pathlib import Path
-from src.plugins._plugin import PluginCommandline
+from ._plugin import PluginCommandline
class Kvantum(PluginCommandline):
diff --git a/src/plugins/notify.py b/yin_yang/plugins/notify.py
similarity index 85%
rename from src/plugins/notify.py
rename to yin_yang/plugins/notify.py
index 423c947b..12707bbb 100644
--- a/src/plugins/notify.py
+++ b/yin_yang/plugins/notify.py
@@ -1,4 +1,4 @@
-from src.plugins._plugin import PluginCommandline
+from ._plugin import PluginCommandline
class Notification(PluginCommandline):
diff --git a/yin_yang/plugins/okular.py b/yin_yang/plugins/okular.py
new file mode 100644
index 00000000..79832ef7
--- /dev/null
+++ b/yin_yang/plugins/okular.py
@@ -0,0 +1,127 @@
+import os
+from configparser import ConfigParser
+from pathlib import Path
+
+import psutil
+from PySide6.QtDBus import QDBusConnection, QDBusMessage
+
+from ._plugin import Plugin
+
+
+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"""
+
+ def __init__(self):
+ super().__init__()
+ 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
+
+ def set_mode(self, dark: bool):
+ if not self.enabled:
+ return False
+
+ process_ids = [
+ proc.pid for proc in psutil.process_iter(['name', 'username'])
+ if proc.name() == 'okular' and proc.username() == os.getlogin()
+ ]
+ # this is if okular is running in a flatpak
+ process_ids.append(2)
+
+ connection = QDBusConnection.sessionBus()
+ for pid in process_ids:
+ message = QDBusMessage.createMethodCall(
+ f'org.kde.okular-{pid}',
+ '/okular',
+ 'org.kde.okular',
+ 'slotSetChangeColors'
+ )
+ message.setArguments([dark])
+ 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
+
+ @property
+ def available_themes(self) -> dict:
+ # these are color changing modes in Okulars accessibility settings
+ return {
+ '': 'Invert colors',
+ 'InvertLightness': 'Invert lightness',
+ 'InvertLuma': 'Invert luma (sRGB linear)',
+ 'InvertLumaSymmetric': 'Invert luma (symmetrical)'
+ }
+
+ def get_input(self, widget):
+ inputs = super().get_input(widget)
+ n_items = len(self.available_themes)
+
+ # modify light item to make it clear that this shows the original without modifications
+ for i in range(n_items):
+ inputs[0].removeItem(0)
+ inputs[0].addItem('Don\'t modify anything')
+
+ return inputs
+
+ @property
+ def theme_dark(self):
+ return self._theme_dark
+
+ @theme_dark.setter
+ def theme_dark(self, value):
+ self._theme_dark = value
+
+ for path in self.user_paths:
+ config = ConfigParser()
+ config.optionxform = str
+ config.read(path)
+
+ if value == '':
+ if config.has_section('Document'):
+ config.remove_option('Document', 'RenderMode')
+ if len(config.options('Document')) == 0:
+ config.remove_section('Document')
+ else:
+ if not config.has_section('Document'):
+ config.add_section('Document')
+ config['Document']['RenderMode'] = value
+
+ with open(path, 'w') as file:
+ config.write(file, space_around_delimiters=False)
diff --git a/src/plugins/only_office.py b/yin_yang/plugins/only_office.py
similarity index 96%
rename from src/plugins/only_office.py
rename to yin_yang/plugins/only_office.py
index 18725854..9b59dd07 100644
--- a/src/plugins/only_office.py
+++ b/yin_yang/plugins/only_office.py
@@ -2,7 +2,7 @@
from os.path import isfile
from pathlib import Path
-from src.plugins._plugin import Plugin
+from ._plugin import Plugin
config_path = f'{Path.home()}/.config/onlyoffice/DesktopEditors.conf'
diff --git a/src/plugins/sound.py b/yin_yang/plugins/sound.py
similarity index 90%
rename from src/plugins/sound.py
rename to yin_yang/plugins/sound.py
index 2b6e32cc..a9b3fcc0 100644
--- a/src/plugins/sound.py
+++ b/yin_yang/plugins/sound.py
@@ -1,6 +1,6 @@
import subprocess
-from src.plugins._plugin import PluginCommandline
+from ._plugin import PluginCommandline
class Sound(PluginCommandline):
diff --git a/src/plugins/system.py b/yin_yang/plugins/system.py
similarity index 67%
rename from src/plugins/system.py
rename to yin_yang/plugins/system.py
index ddd0ba4e..f5c2710d 100644
--- a/src/plugins/system.py
+++ b/yin_yang/plugins/system.py
@@ -3,29 +3,21 @@
import subprocess
import pwd
import os
+from configparser import ConfigParser
+from pathlib import Path
from PySide6.QtCore import QLocale
-from src.meta import Desktop
-from src.plugins._plugin import PluginDesktopDependent, PluginCommandline
+from ..meta import Desktop
+from ._plugin import PluginDesktopDependent, PluginCommandline
logger = logging.getLogger(__name__)
def test_gnome_availability(command) -> bool:
- # Runs the first entry in the command list with --help
- try:
- # if not available, you might want to run https://gist.github.com/atiensivu/fcc3183e9a6fd74ec1a283e3b9ad05f0
- # or you have to install that extension
- process = subprocess.run(
- [command[0], 'get', command[2], command[3]],
- stdout=subprocess.DEVNULL,
- stderr=subprocess.DEVNULL
- )
- return process.returncode == 0
- except FileNotFoundError:
- # if no such command is available, the plugin is not available
- return False
+ return PluginCommandline.check_command(
+ [command[0], 'get', command[2], command[3]]
+ )
class System(PluginDesktopDependent):
@@ -35,6 +27,10 @@ def __init__(self, desktop: Desktop):
super().__init__(_Kde())
case Desktop.GNOME:
super().__init__(_Gnome())
+ case Desktop.MATE:
+ super().__init__(_Mate())
+ case Desktop.CINNAMON:
+ super().__init__(_Cinnamon())
case _:
super().__init__(None)
@@ -129,3 +125,50 @@ def available_themes(self) -> dict:
self.translations[long_name] = long_name
return self.translations
+
+
+class _Mate(PluginCommandline):
+ theme_directories = [Path('/usr/share/themes'), Path.home() / '.themes']
+
+ def __init__(self):
+ super().__init__(['dconf', 'write', '/org/mate/marco/general/theme', '\'{theme}\''])
+ self.theme_light = 'Yaru'
+ self.theme_dark = 'Yaru-dark'
+
+ @property
+ def available_themes(self) -> dict:
+ themes = []
+
+ for directory in self.theme_directories:
+ if not directory.is_dir():
+ continue
+
+ for d in directory.iterdir():
+ index = d / 'index.theme'
+ if not index.is_file():
+ continue
+
+ config = ConfigParser()
+ config.read(index)
+ try:
+ theme = config['X-GNOME-Metatheme']['MetacityTheme']
+ themes.append(theme)
+ except KeyError:
+ continue
+
+ return {t: t for t in themes}
+
+ @property
+ def available(self):
+ return self.check_command(['dconf', 'help'])
+
+
+class _Cinnamon(PluginCommandline):
+ def __init__(self):
+ super().__init__(['gsettings', 'set', 'org.cinnamon.theme', 'name', '\"{theme}\"'])
+ self.theme_light = 'Mint-X-Teal'
+ self.theme_dark = 'Mint-Y-Dark-Brown'
+
+ @property
+ def available(self) -> bool:
+ return test_gnome_availability(self.command)
diff --git a/src/plugins/vscode.py b/yin_yang/plugins/vscode.py
similarity index 100%
rename from src/plugins/vscode.py
rename to yin_yang/plugins/vscode.py
diff --git a/src/plugins/wallpaper.py b/yin_yang/plugins/wallpaper.py
similarity index 71%
rename from src/plugins/wallpaper.py
rename to yin_yang/plugins/wallpaper.py
index abc4086f..d6c6c303 100755
--- a/src/plugins/wallpaper.py
+++ b/yin_yang/plugins/wallpaper.py
@@ -1,10 +1,11 @@
import logging
import subprocess
+from pathlib import Path
from PySide6.QtWidgets import QDialogButtonBox, QVBoxLayout, QWidget, QLineEdit
from PySide6.QtDBus import QDBusConnection, QDBusMessage
-from src.meta import Desktop
+from ..meta import Desktop
from ._plugin import PluginDesktopDependent, PluginCommandline, Plugin
from .system import test_gnome_availability
@@ -22,13 +23,11 @@ def __init__(self, desktop: Desktop):
super().__init__(_Gnome())
case Desktop.XFCE:
super().__init__(_Xfce())
+ case Desktop.CINNAMON:
+ super().__init__(_Cinnamon())
case _:
super().__init__(None)
- @property
- def available(self) -> bool:
- return self.strategy is not None
-
def get_input(self, widget):
widgets = []
@@ -59,11 +58,45 @@ def available(self) -> bool:
return test_gnome_availability(self.command)
+def check_theme(theme: str) -> bool:
+ if not theme:
+ return False
+ file = Path(theme)
+ if "#" in file.name:
+ logger.error('Image files that contain a \'#\' will not work.')
+ return False
+ if not file.exists():
+ logger.error(f'Image {theme} does not exist!')
+ return False
+
+ return True
+
+
class _Kde(Plugin):
name = 'Wallpaper'
def __init__(self):
super().__init__()
+ self._theme_light = None
+ self._theme_dark = None
+
+ @property
+ def theme_light(self) -> str:
+ return self._theme_light
+
+ @theme_light.setter
+ def theme_light(self, value: str):
+ check_theme(value)
+ self._theme_light = value
+
+ @property
+ def theme_dark(self) -> str:
+ return self._theme_dark
+
+ @theme_dark.setter
+ def theme_dark(self, value: str):
+ check_theme(value)
+ self._theme_dark = value
def set_theme(self, theme: str):
connection = QDBusConnection.sessionBus()
@@ -97,3 +130,12 @@ def __init__(self):
monitor = next(p for p in properties.split('\\n') if p.endswith('/workspace0/last-image'))
super().__init__(['xfconf-query', '-c', 'xfce4-desktop', '-p', monitor, '-s', '{theme}'])
+
+
+class _Cinnamon(PluginCommandline):
+ def __init__(self):
+ super().__init__(['gsettings', 'set', 'org.cinnamon.desktop.background', 'picture-uri', 'file://\"{theme}\"'])
+
+ @property
+ def available(self) -> bool:
+ return test_gnome_availability(self.command)
diff --git a/src/yin_yang.py b/yin_yang/theme_switcher.py
similarity index 89%
rename from src/yin_yang.py
rename to yin_yang/theme_switcher.py
index ebb2c971..2a177b1f 100755
--- a/src/yin_yang.py
+++ b/yin_yang/theme_switcher.py
@@ -12,11 +12,11 @@
import time
from threading import Thread
-from src.plugins.notify import Notification
-from src.plugins.sound import Sound
-from src.daemon_handler import update_times
-from src.meta import PluginKey
-from src.config import config, plugins
+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
logger = logging.getLogger(__name__)
diff --git a/src/ui/__init__.py b/yin_yang/ui/__init__.py
similarity index 100%
rename from src/ui/__init__.py
rename to yin_yang/ui/__init__.py
diff --git a/src/ui/main_window.py b/yin_yang/ui/main_window.py
similarity index 84%
rename from src/ui/main_window.py
rename to yin_yang/ui/main_window.py
index 865f1d9d..79c0e585 100644
--- a/src/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.4.1
+## Created by: Qt User Interface Compiler version 6.5.0
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
@@ -18,10 +18,10 @@
from PySide6.QtWidgets import (QAbstractButton, QApplication, QCheckBox, QComboBox,
QDialogButtonBox, QDoubleSpinBox, QFormLayout, QFrame,
QGroupBox, QHBoxLayout, QLabel, QMainWindow,
- QRadioButton, QScrollArea, QSizePolicy, QSpacerItem,
- QStatusBar, QTabWidget, QTimeEdit, QVBoxLayout,
- QWidget)
-import resources_rc
+ QPushButton, QRadioButton, QScrollArea, QSizePolicy,
+ QSpacerItem, QSpinBox, QStatusBar, QTabWidget,
+ QTimeEdit, QVBoxLayout, QWidget)
+from . import resources_rc
class Ui_main_window(object):
def setupUi(self, main_window):
@@ -210,6 +210,25 @@ def setupUi(self, main_window):
self.settings_layout.addWidget(self.schedule_settings)
+ self.manual_buttons = QWidget(self.settings)
+ self.manual_buttons.setObjectName(u"manual_buttons")
+ self.horizontalLayout_3 = QHBoxLayout(self.manual_buttons)
+ self.horizontalLayout_3.setSpacing(6)
+ self.horizontalLayout_3.setContentsMargins(11, 11, 11, 11)
+ self.horizontalLayout_3.setObjectName(u"horizontalLayout_3")
+ self.button_light = QPushButton(self.manual_buttons)
+ self.button_light.setObjectName(u"button_light")
+
+ self.horizontalLayout_3.addWidget(self.button_light)
+
+ self.button_dark = QPushButton(self.manual_buttons)
+ self.button_dark.setObjectName(u"button_dark")
+
+ self.horizontalLayout_3.addWidget(self.button_dark)
+
+
+ self.settings_layout.addWidget(self.manual_buttons)
+
self.toggle_sound = QCheckBox(self.settings)
self.toggle_sound.setObjectName(u"toggle_sound")
@@ -220,6 +239,23 @@ def setupUi(self, main_window):
self.settings_layout.addWidget(self.toggle_notification)
+ self.bootOffsetSettings = QFormLayout()
+ self.bootOffsetSettings.setSpacing(6)
+ self.bootOffsetSettings.setObjectName(u"bootOffsetSettings")
+ self.bootOffsetLabel = QLabel(self.settings)
+ self.bootOffsetLabel.setObjectName(u"bootOffsetLabel")
+
+ self.bootOffsetSettings.setWidget(0, QFormLayout.LabelRole, self.bootOffsetLabel)
+
+ self.bootOffset = QSpinBox(self.settings)
+ self.bootOffset.setObjectName(u"bootOffset")
+ self.bootOffset.setValue(10)
+
+ self.bootOffsetSettings.setWidget(0, QFormLayout.FieldRole, self.bootOffset)
+
+
+ self.settings_layout.addLayout(self.bootOffsetSettings)
+
self.label_active = QLabel(self.settings)
self.label_active.setObjectName(u"label_active")
self.label_active.setText(u"Darkmode will be active between")
@@ -243,7 +279,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, 523, 663))
+ self.plugins_scroll_content.setGeometry(QRect(0, 0, 518, 88))
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)
@@ -253,13 +289,16 @@ def setupUi(self, main_window):
self.horizontalLayout.setObjectName(u"horizontalLayout")
self.samplePluginGroupBox = QGroupBox(self.plugins_scroll_content)
self.samplePluginGroupBox.setObjectName(u"samplePluginGroupBox")
+ self.samplePluginGroupBox.setTitle(u"Sample Plugin")
self.horizontalLayout_2 = QHBoxLayout(self.samplePluginGroupBox)
self.horizontalLayout_2.setSpacing(6)
self.horizontalLayout_2.setContentsMargins(11, 11, 11, 11)
self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
self.comboBox = QComboBox(self.samplePluginGroupBox)
self.comboBox.setObjectName(u"comboBox")
+ self.comboBox.setCurrentText(u"")
self.comboBox.setSizeAdjustPolicy(QComboBox.AdjustToContentsOnFirstShow)
+ self.comboBox.setPlaceholderText(u"firefox-compact-light@mozilla.org")
self.horizontalLayout_2.addWidget(self.comboBox)
@@ -267,6 +306,7 @@ def setupUi(self, main_window):
self.comboBox_2.setObjectName(u"comboBox_2")
self.comboBox_2.setCurrentText(u"")
self.comboBox_2.setSizeAdjustPolicy(QComboBox.AdjustToContentsOnFirstShow)
+ self.comboBox_2.setPlaceholderText(u"firefox-compact-dark@mozilla.org")
self.horizontalLayout_2.addWidget(self.comboBox_2)
@@ -299,8 +339,9 @@ def setupUi(self, main_window):
self.btn_sun.toggled.connect(self.location.setVisible)
self.btn_schedule.toggled.connect(self.time.setVisible)
self.btn_enable.toggled.connect(self.schedule_settings.setVisible)
+ self.btn_enable.toggled.connect(self.manual_buttons.setHidden)
- self.tab_widget.setCurrentIndex(1)
+ self.tab_widget.setCurrentIndex(0)
QMetaObject.connectSlotsByName(main_window)
@@ -315,12 +356,16 @@ def retranslateUi(self, main_window):
self.label_longitude.setText(QCoreApplication.translate("main_window", u"Longitude:", None))
self.label_latitude.setText(QCoreApplication.translate("main_window", u"Latitude:", None))
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))
+#endif // QT_CONFIG(tooltip)
+ self.bootOffsetLabel.setText(QCoreApplication.translate("main_window", u"Delay after boot:", None))
+ self.bootOffset.setSuffix(QCoreApplication.translate("main_window", u"s", None))
self.tab_widget.setTabText(self.tab_widget.indexOf(self.settings), QCoreApplication.translate("main_window", u"Settings", None))
- self.samplePluginGroupBox.setTitle(QCoreApplication.translate("main_window", u"Sample Plugin", None))
- self.comboBox.setPlaceholderText(QCoreApplication.translate("main_window", u"firefox-compact-light@mozilla.org", None))
- self.comboBox_2.setPlaceholderText(QCoreApplication.translate("main_window", u"firefox-compact-dark@mozilla.org", None))
self.tab_widget.setTabText(self.tab_widget.indexOf(self.plugins), QCoreApplication.translate("main_window", u"Plugins", None))
pass
# retranslateUi
diff --git a/src/ui/main_window_connector.py b/yin_yang/ui/main_window_connector.py
similarity index 94%
rename from src/ui/main_window_connector.py
rename to yin_yang/ui/main_window_connector.py
index fc571b41..ff7a45a3 100755
--- a/src/ui/main_window_connector.py
+++ b/yin_yang/ui/main_window_connector.py
@@ -6,12 +6,10 @@
from PySide6.QtGui import QScreen, QColor
from PySide6.QtWidgets import QFileDialog, QMessageBox, QDialogButtonBox, QColorDialog,QGroupBox
-from src.ui.main_window import Ui_main_window
-
-from src.yin_yang import set_desired_theme
-from src.meta import ConfigEvent
-from src.meta import PluginKey
-from src.config import config, Modes, plugins, ConfigWatcher
+from .main_window import Ui_main_window
+from ..theme_switcher import set_desired_theme, set_mode
+from ..meta import ConfigEvent, PluginKey
+from ..config import config, Modes, plugins, ConfigWatcher
logger = logging.getLogger(__name__)
@@ -73,6 +71,7 @@ def load(self):
# set the correct mode
mode = config.mode
self.ui.btn_enable.setChecked(mode != Modes.MANUAL)
+ self.ui.manual_buttons.setVisible(mode == Modes.MANUAL)
if mode == Modes.FOLLOW_SUN:
self.ui.time.setVisible(False)
@@ -84,6 +83,7 @@ def load(self):
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)
# sets the correct time based on config
self.load_times()
@@ -192,6 +192,16 @@ def setup_config_sync(self):
self.ui.toggle_notification.toggled.connect(
lambda enabled: config.update_plugin_key('notification', PluginKey.ENABLED, enabled))
+ self.ui.bootOffset.valueChanged.connect(self.update_boot_offset)
+
+ # connect manual theme buttons
+ self.ui.button_light.clicked.connect(lambda: set_mode(False))
+ self.ui.button_dark.clicked.connect(lambda: set_mode(True))
+
+ @staticmethod
+ def update_boot_offset(value: int):
+ config.boot_offset = value
+
def save_mode(self):
if not self.ui.btn_enable.isChecked():
config.mode = Modes.MANUAL
diff --git a/resources_rc.py b/yin_yang/ui/resources_rc.py
similarity index 56%
rename from resources_rc.py
rename to yin_yang/ui/resources_rc.py
index 94afa888..28b759c1 100644
--- a/resources_rc.py
+++ b/yin_yang/ui/resources_rc.py
@@ -1,141 +1,133 @@
# Resource object code (Python 3)
# Created by: object code
-# Created by: The Resource Compiler for Qt version 6.4.1
+# Created by: The Resource Compiler for Qt version 6.5.0
# WARNING! All changes made in this file will be lost!
from PySide6 import QtCore
qt_resource_data = b"\
-\x00\x00\x07\xfd\
+\x00\x00\x07w\
<\
\xb8d\x18\xca\xef\x9c\x95\xcd!\x1c\xbf`\xa1\xbd\xdd\xa7\
-\x00\x00\x00\x05de_DEB\x00\x00\x00\x90\x00\x0a\
-KE\x00\x00\x04\xd7\x00J\x88\xea\x00\x00\x03\xf3\x00l\
-\xa7\xf3\x00\x00\x02V\x00\x89?\xc9\x00\x00\x06\xc1\x00\xb6\
-\xd1\xae\x00\x00\x00\x00\x03$u=\x00\x00\x02\xb6\x03^\
-\x05u\x00\x00\x03\x8c\x05/\xdfz\x00\x00\x04g\x06\x99\
-\x04U\x00\x00\x06N\x07;\xe0\x03\x00\x00\x05|\x0a\x00\
-\x8c2\x00\x00\x01\x18\x0a\xa0\x8cG\x00\x00\x03\x1d\x0b\x0b\
-\xe8\x0a\x00\x00\x04&\x0c\xbb\x01s\x00\x00\x06\x0c\x0d\xd7\
-\xfdR\x00\x00\x00\xac\x0e\x0e\x8c\xca\x00\x00\x04\x97\x0f\x0a\
-g\xee\x00\x00\x05\xa3\x0f/\x19\xcf\x00\x00\x01\x83i\x00\
-\x00\x07B\x03\x00\x00\x00b\x00D\x00u\x00n\x00k\
-\x00l\x00e\x00r\x00 \x00M\x00o\x00d\x00u\
-\x00s\x00 \x00w\x00i\x00r\x00d\x00 \x00z\
-\x00w\x00i\x00s\x00c\x00h\x00e\x00n\x00 \
-\x00{\x00}\x00 \x00u\x00n\x00d\x00 \x00{\
-\x00}\x00 \x00a\x00k\x00t\x00i\x00v\x00 \
-\x00s\x00e\x00i\x00n\x00.\x08\x00\x00\x00\x00\x06\
-\x00\x00\x00+Dark mode wi\
-ll be active bet\
-ween {} and {}.\x07\
-\x00\x00\x00\x0aMainWindow\x01\x03\
-\x00\x00\x00:\x00\xd6\x00f\x00f\x00n\x00e\x00 \
-\x00d\x00u\x00n\x00k\x00l\x00e\x00s\x00 \
-\x00H\x00i\x00n\x00t\x00e\x00r\x00g\x00r\
-\x00u\x00n\x00d\x00b\x00i\x00l\x00d\x08\x00\
-\x00\x00\x00\x06\x00\x00\x00\x13Open dar\
-k wallpaper\x07\x00\x00\x00\x0a\
-MainWindow\x01\x03\x00\x00\x008\
-\x00\xd6\x00f\x00f\x00n\x00e\x00 \x00h\x00e\
-\x00l\x00l\x00e\x00s\x00 \x00H\x00i\x00n\
-\x00t\x00e\x00r\x00g\x00r\x00u\x00n\x00d\
-\x00b\x00i\x00l\x00d\x08\x00\x00\x00\x00\x06\x00\x00\
-\x00\x14Open light wal\
-lpaper\x07\x00\x00\x00\x0aMainW\
-indow\x01\x03\x00\x00\x00z\x00D\x00i\x00\
-e\x00 \x00E\x00i\x00n\x00s\x00t\x00e\x00\
-l\x00l\x00u\x00n\x00g\x00e\x00n\x00 \x00\
-w\x00u\x00r\x00d\x00e\x00n\x00 \x00g\x00\
-e\x00\xe4\x00n\x00d\x00e\x00r\x00t\x00.\x00\
- \x00M\x00\xf6\x00c\x00h\x00t\x00e\x00n\x00\
- \x00S\x00i\x00e\x00 \x00s\x00i\x00e\x00\
- \x00s\x00p\x00e\x00i\x00c\x00h\x00e\x00\
-r\x00n\x00?\x08\x00\x00\x00\x00\x06\x00\x00\x00:T\
-he settings have\
- been modified. \
-Do you want to s\
-ave them?\x07\x00\x00\x00\x0aMa\
-inWindow\x01\x03\x00\x00\x002\x00U\
-\x00n\x00g\x00e\x00s\x00p\x00e\x00i\x00c\
-\x00h\x00e\x00r\x00t\x00e\x00 \x00\xc4\x00n\
-\x00d\x00e\x00r\x00u\x00n\x00g\x00e\x00n\
-\x08\x00\x00\x00\x00\x06\x00\x00\x00\x0fUnsave\
-d changes\x07\x00\x00\x00\x0aMa\
-inWindow\x01\x03\x00\x00\x000\x00S\
-\x00i\x00e\x00 \x00v\x00e\x00r\x00w\x00e\
-\x00n\x00d\x00e\x00n\x00 \x00V\x00e\x00r\
-\x00s\x00i\x00o\x00n\x00 \x00{\x00}\x08\x00\
-\x00\x00\x00\x06\x00\x00\x00\x18You are \
-using version {}\
-\x07\x00\x00\x00\x0aMainWindow\x01\
-\x03\x00\x00\x006\x00A\x00u\x00t\x00o\x00m\x00\
-a\x00t\x00i\x00s\x00c\x00h\x00e\x00r\x00\
- \x00T\x00h\x00e\x00m\x00e\x00n\x00w\x00\
-e\x00c\x00h\x00s\x00e\x00l\x08\x00\x00\x00\x00\
-\x06\x00\x00\x00\x19Automatic t\
-heme switching\x07\x00\
+\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\x008\x00B\x00e\x00n\x00u\x00t\x00z\
-\x00e\x00r\x00d\x00e\x00f\x00i\x00n\x00i\
-\x00e\x00r\x00t\x00e\x00r\x00 \x00Z\x00e\
-\x00i\x00t\x00r\x00a\x00u\x00m\x08\x00\x00\x00\
-\x00\x06\x00\x00\x00\x0fCustom Sch\
-edule\x07\x00\x00\x00\x0bmain_w\
-indow\x01\x03\x00\x00\x00\x0e\x00D\x00u\x00\
-n\x00k\x00e\x00l\x00:\x08\x00\x00\x00\x00\x06\x00\
-\x00\x00\x05Dark:\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\x0a\x00\
-H\x00e\x00l\x00l\x00:\x08\x00\x00\x00\x00\x06\x00\
-\x00\x00\x06Light:\x07\x00\x00\x00\x0bma\
-in_window\x01\x03\x00\x00\x00\x16\x00\
-L\x00\xe4\x00n\x00g\x00e\x00n\x00g\x00r\x00\
-a\x00d\x00:\x08\x00\x00\x00\x00\x06\x00\x00\x00\x0aL\
-ongitude:\x07\x00\x00\x00\x0bma\
-in_window\x01\x03\x00\x00\x00`\x00\
-M\x00a\x00c\x00h\x00e\x00 \x00e\x00i\x00\
-n\x00 \x00G\x00e\x00r\x00\xe4\x00u\x00s\x00\
-c\x00h\x00,\x00 \x00w\x00e\x00n\x00n\x00\
- \x00d\x00a\x00s\x00 \x00T\x00h\x00e\x00\
-m\x00a\x00 \x00g\x00e\x00\xe4\x00n\x00d\x00\
-e\x00r\x00t\x00 \x00w\x00i\x00r\x00d\x08\
-\x00\x00\x00\x00\x06\x00\x00\x00%Make a \
-sound when switc\
-hing the theme\x07\x00\
+\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\
-\xff\xff\xff\xff\x08\x00\x00\x00\x00\x06\x00\x00\x00\x07Pl\
-ugins\x07\x00\x00\x00\x0bmain_w\
-indow\x01\x03\x00\x00\x006\x00S\x00e\x00\
-n\x00d\x00e\x00 \x00e\x00i\x00n\x00e\x00\
- \x00B\x00e\x00n\x00a\x00c\x00h\x00r\x00\
-i\x00c\x00h\x00t\x00i\x00g\x00u\x00n\x00\
-g\x08\x00\x00\x00\x00\x06\x00\x00\x00\x13Send \
-a notification\x07\x00\
+\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\x1a\x00E\x00i\x00n\x00s\x00t\x00e\
-\x00l\x00l\x00u\x00n\x00g\x00e\x00n\x08\x00\
-\x00\x00\x00\x06\x00\x00\x00\x08Settings\
-\x07\x00\x00\x00\x0bmain_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 Sunr\
-ise\x07\x00\x00\x00\x0bmain_win\
-dow\x01\x03\x00\x00\x00D\x00S\x00t\x00a\x00\
-n\x00d\x00o\x00r\x00t\x00 \x00a\x00u\x00\
-t\x00o\x00m\x00a\x00t\x00i\x00s\x00c\x00\
-h\x00 \x00a\x00k\x00t\x00u\x00a\x00l\x00\
-i\x00s\x00i\x00e\x00r\x00e\x00n\x08\x00\x00\
-\x00\x00\x06\x00\x00\x00\x1dupdate lo\
-cation automatic\
-ally\x07\x00\x00\x00\x0bmain_wi\
-ndow\x01\x88\x00\x00\x00\x02\x01\x01\
+\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\
+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\
\x00\x00\x07\x22\
\x00\
\x00\x1dUx\xda\xcdYKs\xdb6\x10\xbe\xe7Wp\
@@ -282,8 +274,8 @@
\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\x87W\x9b\xe0\xc2\
-\x00\x00\x00P\x00\x01\x00\x00\x00\x01\x00\x00\x08\x01\
+\x00\x00\x01\x88)>\x08Z\
+\x00\x00\x00P\x00\x01\x00\x00\x00\x01\x00\x00\x07{\
\x00\x00\x01\x84\x01\xd5\x8cC\
"