diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index ff526d0..a68c522 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -40,9 +40,9 @@ jobs: run: | sudo apt install qt6-base-dev libsystemd-dev gcc - name: Install Poetry dependencies - if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' + # if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' run: | - poetry install --no-interaction + poetry install --sync --no-interaction # Compile and build Yin-Yang - name: Compile ui, translations and resources run: poetry run ./scripts/build_ui.sh @@ -67,6 +67,11 @@ jobs: arch: [x86_64] steps: - uses: actions/checkout@v4 + - name: Install deps + run: | + dnf -y install pipx + pipx ensurepath --global + pipx install poetry==1.5.1 - name: Download build from last step uses: actions/download-artifact@v4 with: @@ -77,4 +82,4 @@ jobs: bundle: yin-yang.flatpak manifest-path: sh.oskar.yin-yang.json cache-key: flatpak-builder-${{ github.sha }} - arch: ubuntu-22.04 \ No newline at end of file + arch: x86_64 \ No newline at end of file diff --git a/.github/workflows/python-ci.yml b/.github/workflows/python-ci.yml index aed09b2..0a3c15a 100644 --- a/.github/workflows/python-ci.yml +++ b/.github/workflows/python-ci.yml @@ -17,17 +17,20 @@ jobs: os: [ubuntu-22.04] runs-on: ${{matrix.os}} steps: + # Checkout repo and set up python - uses: actions/checkout@v4 - name: Install Python uses: actions/setup-python@v5 with: python-version: ${{matrix.python-version}} + # Install and configure poetry - name: Install Poetry uses: abatilo/actions-poetry@v2 - name: Set up local virtual environment run: | poetry config virtualenvs.create true --local poetry config virtualenvs.in-project true --local + # Load cached venv if it exists - name: Cache packages id: cached-poetry-dependencies uses: actions/cache@v4 @@ -35,14 +38,14 @@ jobs: # This path is specific to ubuntu path: ./.venv key: venv-${{ hashFiles('poetry.lock') }} - # Install dependencies + # Install dependencies of cache does not exist - name: Install system dependencies run: | sudo apt install qt6-base-dev libsystemd-dev gcc - name: Install Poetry dependencies - if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' + # if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' run: | - poetry install --no-interaction + poetry install --sync --no-interaction # Build and test Yin-Yang - name: Compile ui, translations and resources run: poetry run ./scripts/build_ui.sh diff --git a/generated-poetry-sources.json b/generated-poetry-sources.json index 021fcf9..57a8dd3 100644 --- a/generated-poetry-sources.json +++ b/generated-poetry-sources.json @@ -2,7 +2,7 @@ "name": "poetry-deps", "buildsystem": "simple", "build-commands": [ - "pip3 install --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} certifi charset-normalizer colorama flake8 idna iniconfig mccabe packaging pluggy psutil pycodestyle pyflakes pyside6-addons pyside6-essentials pytest python-dateutil pyyaml requests shiboken6 six suntime systemd-python toml urllib3" + "pip3 install --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} certifi charset-normalizer colorama cython flake8 idna iniconfig mccabe packaging pluggy psutil pycodestyle pyflakes pyside6-addons pyside6-essentials pytest python-dateutil pyyaml requests setuptools shiboken6 six suntime systemd-python toml urllib3 wheel" ], "sources": [ { @@ -20,6 +20,11 @@ "url": "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", "sha256": "4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/7e/26/9d8de10005fedb1eceabe713348d43bae1dbab1786042ca0751a2e2b0f8c/Cython-0.29.37-py2.py3-none-any.whl", + "sha256": "95f1d6a83ef2729e67b3fa7318c829ce5b07ac64c084cd6af11c228e0364662c" + }, { "type": "file", "url": "https://files.pythonhosted.org/packages/e3/01/cc8cdec7b61db0315c2ab62d80677a138ef06832ec17f04d87e6ef858f7f/flake8-7.0.0-py2.py3-none-any.whl", @@ -95,6 +100,11 @@ "url": "https://files.pythonhosted.org/packages/70/8e/0e2d847013cb52cd35b38c009bb167a1a26b2ce6cd6965bf26b47bc0bf44/requests-2.31.0-py3-none-any.whl", "sha256": "58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f" }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/f7/29/13965af254e3373bceae8fb9a0e6ea0d0e571171b80d6646932131d6439b/setuptools-69.5.1-py3-none-any.whl", + "sha256": "c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32" + }, { "type": "file", "url": "https://files.pythonhosted.org/packages/77/f1/feb2a8be699f91fb27fbe8758b405fb38a22e3ae5bd5e05258dbef18d462/shiboken6-6.6.3.1-cp38-abi3-manylinux_2_28_x86_64.whl", @@ -124,6 +134,11 @@ "type": "file", "url": "https://files.pythonhosted.org/packages/a2/73/a68704750a7679d0b6d3ad7aa8d4da8e14e151ae82e6fee774e6e0d05ec8/urllib3-2.2.1-py3-none-any.whl", "sha256": "450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/7d/cd/d7460c9a869b16c3dd4e1e403cce337df165368c71d6af229a74699622ce/wheel-0.43.0-py3-none-any.whl", + "sha256": "55c570405f142630c6b9f72fe09d9b67cf1477fcf543ae5b8dcb1f5b7377da81" } ] } \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index 266a9d2..16c467a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -121,6 +121,57 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "cython" +version = "0.29.37" +description = "The Cython compiler for writing C extensions for the Python language." +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "Cython-0.29.37-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f2d621fe4cb50007446742134a890500b34e3f50abaf7993baaca02634af7e15"}, + {file = "Cython-0.29.37-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d94caf90ae9cb56116ca6d54cdcbccd3c4df6b0cb7233922b2233ee7fe81d05b"}, + {file = "Cython-0.29.37-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:852cd4378cbc9ade02f53709107ff9fdad55019a3a636e8a27663ba6cfce10b6"}, + {file = "Cython-0.29.37-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:bbce388431a2608a81c8ab13cb14c50611473843ca766031b8b24bb1723faf79"}, + {file = "Cython-0.29.37-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:4658499a41255431f6bbdca7e634e9c8d3a4c190bf24b4aa1646dac751d3da4d"}, + {file = "Cython-0.29.37-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:12192ab269e7185720f2d2f8894587bf1da4276db1b9b869e4622a093f18cae6"}, + {file = "Cython-0.29.37-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:9450e0766ab65947f8a2a36f9e59079fc879c3807ec936c61725a48c97741a52"}, + {file = "Cython-0.29.37-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:177481b0a7e003e5c49e2bf0dda1d6fe610c239f17642a5da9f18c2ad0c5f6b6"}, + {file = "Cython-0.29.37-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:b048354fd380278f2fa096e7526973beb6e0491a9d44d7e4e29df52612d25776"}, + {file = "Cython-0.29.37-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:ea6d208be1906c5df25b674777d5905c6d8e9ef0b201b830849e0729ba08caba"}, + {file = "Cython-0.29.37-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:af03854571738307a5f30cc6b724081d72db12f907699e7fdfc04c12c839158e"}, + {file = "Cython-0.29.37-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c33508ede9172a6f6f99d5a6dadc7fee23c840423b411ef8b5a403c04e530297"}, + {file = "Cython-0.29.37-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8af5975ecfae254d8c0051204fca995dda8f93cf9f0bbf7571e3cda2b0cef4d"}, + {file = "Cython-0.29.37-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:29415d8eb2fdc1ea518ca4810c50a2d062b387d4c9fbcfb3352346e93db22c6d"}, + {file = "Cython-0.29.37-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe0eaf6b1e9ee97c5ee7bfc943f00e36cf59d929db16886cb018352bff8208da"}, + {file = "Cython-0.29.37-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cc1b9ce2b73b9ee8c305e06173b35c7c202d4b82d084a0cd73dcedfd6d310aec"}, + {file = "Cython-0.29.37-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:2618af0b8df26d32ee4e8858d4ad8167546596762620aeade84954ae37194a0e"}, + {file = "Cython-0.29.37-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:ac910a28a2fd3d280faf3077b6fe63b97a4b93994ff05647581846f0e4b2f8d1"}, + {file = "Cython-0.29.37-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:8bf38373773f967cfd793997a6fb96cf972d41a9fce987ace5767349d6f15572"}, + {file = "Cython-0.29.37-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6cddb567dadb3aa3e280a8a35e5126030915ea744c2812206e9c194b8881475d"}, + {file = "Cython-0.29.37-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:79ecfc48694e156402c05561e0adb0e25a6e9d35ac0b41693733a08219d38c58"}, + {file = "Cython-0.29.37-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:9a455347e20ddfad0c5dfee32a3e855ee96811269e5fd86be622ddc4cb326404"}, + {file = "Cython-0.29.37-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:fa5b6a0f69bf1823c9fd038fa77a2568b78fda2de045a95b48a71dee4d0d578f"}, + {file = "Cython-0.29.37-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:a6164a05440dcd9daa760c6488bc91bdac1380c7b4b3aca38cf307ba66042d54"}, + {file = "Cython-0.29.37-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:562f8f911dbd6f1a1b9be8f6cba097125700355688f613994ccd4406f220557a"}, + {file = "Cython-0.29.37-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8c39c2f5a0fe29bb01de9b1fb449bf65bed6f192317c677f181732791c63fe28"}, + {file = "Cython-0.29.37-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:0a0a6d5972bb3b8c7363cf19a42a988bb0c0bb5ebd9c736c84eca85113ccfdbe"}, + {file = "Cython-0.29.37-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b82584836e9e7c0d6effee976595e5cd7fa88dbef3e96e900187983c1d4637d1"}, + {file = "Cython-0.29.37-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:b6c48f1032b379135a5b4a31976d6c468e02490688acf9254c6c8ed27bd4cbd4"}, + {file = "Cython-0.29.37-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:3f87bef1808d255cf13be378c7ad27ae7c6db6df7732217d32428d1daf4109be"}, + {file = "Cython-0.29.37-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:9e68bafeeb97d5a403fb1f7700bd4a55a1f8989824c323ae02ae8a4fcd88f6a1"}, + {file = "Cython-0.29.37-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e14cd44c830e53cf9d7269c87a6bcc638bb065ec07e24990e338162c7001d3c3"}, + {file = "Cython-0.29.37-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:0544f7a3e4437b89b356baa15387494c18214e03f2ffaddada5a2c71c3dfd24b"}, + {file = "Cython-0.29.37-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2de3e729d25f041036e81e2f15683dd129f977dfb5b06267e30e8d7acec43225"}, + {file = "Cython-0.29.37-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:2ad634dc77a6a74022881826099eccac19c9b79153942cc82e754ffac2bec116"}, + {file = "Cython-0.29.37-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e841a8b4f9ceefb2916e32dac4f28a895cd519e8ece71505144da1ee355c548a"}, + {file = "Cython-0.29.37-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:6c672089fba6a8f6690b8d7924a58c04477771401ad101d53171a13405ee12cb"}, + {file = "Cython-0.29.37-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0301d4739c6894e012f1d410052082fdda9e63888c815d9e23e0f7f82fff7d79"}, + {file = "Cython-0.29.37-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:af8e7b4397620e2d18259a11f3bfa026eff9846657e397d02616962dd5dd035a"}, + {file = "Cython-0.29.37-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b225d5e2091c224d4ab328165fef224ba3919b3ed44bd9b3241416f523b4d51a"}, + {file = "Cython-0.29.37-py2.py3-none-any.whl", hash = "sha256:95f1d6a83ef2729e67b3fa7318c829ce5b07ac64c084cd6af11c228e0364662c"}, + {file = "Cython-0.29.37.tar.gz", hash = "sha256:f813d4a6dd94adee5d4ff266191d1d95bf6d4164a4facc535422c021b2504cfb"}, +] + [[package]] name = "flake8" version = "7.0.0" @@ -394,6 +445,22 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "setuptools" +version = "69.5.1" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "setuptools-69.5.1-py3-none-any.whl", hash = "sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32"}, + {file = "setuptools-69.5.1.tar.gz", hash = "sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + [[package]] name = "shiboken6" version = "6.6.3.1" @@ -470,7 +537,21 @@ h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] +[[package]] +name = "wheel" +version = "0.43.0" +description = "A built-package format for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "wheel-0.43.0-py3-none-any.whl", hash = "sha256:55c570405f142630c6b9f72fe09d9b67cf1477fcf543ae5b8dcb1f5b7377da81"}, + {file = "wheel-0.43.0.tar.gz", hash = "sha256:465ef92c69fa5c5da2d1cf8ac40559a8c940886afcef87dcf14b9470862f1d85"}, +] + +[package.extras] +test = ["pytest (>=6.0.0)", "setuptools (>=65)"] + [metadata] lock-version = "2.0" python-versions = "^3.11,<3.13" -content-hash = "25ba4e76dfa9c5c0827768c076bc426a58732fbdf61f0d54a43c076ee8eee6bd" +content-hash = "80cb53843cb453c3213b1e16e0be79bcd3116bf0084f75e2167ae6498c86a538" diff --git a/pyproject.toml b/pyproject.toml index 5e0b196..ed76a45 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,9 @@ flake8 = "^7.0.0" pytest = "^8.1.1" pyyaml = "^6.0.1" toml = "^0.10.2" +setuptools = "^69.5.1" +wheel = "^0.43.0" +cython = "<3.0" [build-system] requires = ["poetry-core"] diff --git a/scripts/install.sh b/scripts/install.sh index ee62edf..c07d8ab 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -17,8 +17,8 @@ echo "Installing dependencies …" # Tell Poetry not to use a keyring export PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring # create virtual environment and install packages -poetry install --sync poetry env use python +poetry install --sync poetry build pip install ./dist/yin_yang-*-py3-none-any.whl diff --git a/sh.oskar.yin-yang.json b/sh.oskar.yin-yang.json index e98ea2a..821d735 100644 --- a/sh.oskar.yin-yang.json +++ b/sh.oskar.yin-yang.json @@ -22,7 +22,6 @@ "buildsystem": "simple", "build-commands": [ "poetry install --sync", - "poetry build", "find dist -name 'yin_yang-*-py3-none-any.whl' -exec pip install --no-deps --no-build-isolation --prefix=/app {} \\;", "install -D runner.sh /app/bin/runner.sh" ], diff --git a/yin_yang/NotificationHandler.py b/yin_yang/NotificationHandler.py index 0f184f9..4ce908e 100644 --- a/yin_yang/NotificationHandler.py +++ b/yin_yang/NotificationHandler.py @@ -1,14 +1,32 @@ -import logging -import subprocess from logging import Handler -logger = logging.getLogger() +from PySide6.QtDBus import QDBusConnection, QDBusMessage + + +def create_dbus_message(title: str, body: str): + message = QDBusMessage.createMethodCall( + "org.freedesktop.portal.Desktop", + "/org/freedesktop/portal/desktop", + "org.freedesktop.portal.Notification", + "AddNotification", + ) + + notification = { + "title": title, + "body": body, + "icon": "yin_yang", + "priority": "low", + } + + message.setArguments(["YingYang.ThemeChanged", notification]) + + return message + class NotificationHandler(Handler): """Shows logs as notifications""" + def emit(self, record): - try: - subprocess.call(['notify-send', record.levelname, str(record.msg), - '-a', 'Yin & Yang', '-u', 'low', '--icon', 'yin_yang']) - except FileNotFoundError: - logger.warn('notify-send not found. Notifications will not work!') + connection = QDBusConnection.sessionBus() + message = create_dbus_message(record.levelname, str(record.msg)) + connection.call(message) diff --git a/yin_yang/plugins/_plugin.py b/yin_yang/plugins/_plugin.py index fc77f86..384156a 100644 --- a/yin_yang/plugins/_plugin.py +++ b/yin_yang/plugins/_plugin.py @@ -4,7 +4,7 @@ from abc import ABC, abstractmethod from configparser import ConfigParser from pathlib import Path -from typing import Optional +from typing import Optional, List from PySide6.QtDBus import QDBusConnection, QDBusMessage from PySide6.QtGui import QColor, QRgba64 @@ -366,3 +366,20 @@ def flatpak_user(app_id: str) -> Path: def snap_path(app: str) -> Path: return Path(f'/var/lib/snapd/snap/{app}/current') + +def themes_from_theme_directories(type: str) -> List[Path]: + theme_directories = [ + Path('/usr/share/themes'), + Path('/usr/local/share/themes'), + Path.home() / '.themes', + Path.home() / '.local/share/themes', + ] + + themes = [] + for directory in theme_directories: + if not directory.is_dir(): + continue + + themes.extend(d.name for d in directory.iterdir() if d.is_dir() and (d / type).is_dir()) + + return themes diff --git a/yin_yang/plugins/gtk.py b/yin_yang/plugins/gtk.py index 7fe13db..b30b84d 100755 --- a/yin_yang/plugins/gtk.py +++ b/yin_yang/plugins/gtk.py @@ -1,23 +1,20 @@ import logging -from os import scandir, path +from os import path, scandir from pathlib import Path from PySide6.QtDBus import QDBusMessage from yin_yang import helpers -from ._plugin import PluginDesktopDependent, PluginCommandline, DBusPlugin -from .system import test_gnome_availability from ..meta import Desktop +from ._plugin import DBusPlugin, PluginCommandline, PluginDesktopDependent +from .system import test_gnome_availability logger = logging.getLogger(__name__) -theme_directories = [helpers.get_usr() + 'share/themes', f'{Path.home()}/.themes'] - - class Gtk(PluginDesktopDependent): - name = 'GTK' + name = "GTK" def __init__(self, desktop: Desktop): match desktop: @@ -26,8 +23,10 @@ def __init__(self, desktop: Desktop): case Desktop.GNOME: super().__init__(_Gnome()) 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/') + 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: @@ -41,38 +40,34 @@ def __init__(self, desktop: Desktop): @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.isdir(d.path + '/gtk-3.0')) - + themes = themes_from_theme_directories("gtk-3.0") return {t: t for t in themes} class _Gnome(PluginCommandline): - name = 'GTK' + name = "GTK" def __init__(self): - super().__init__(['gsettings', 'set', 'org.gnome.desktop.interface', 'gtk-theme', '{theme}']) - self.theme_light = 'Default' - self.theme_dark = 'Default' + 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 _Budgie(PluginCommandline): - name = 'GTK' + name = "GTK" def __init__(self): - super().__init__(['gsettings', 'set', 'org.gnome.desktop.interface', 'gtk-theme', '{theme}']) - self.theme_light = 'Default' - self.theme_dark = 'Default' + super().__init__( + ["gsettings", "set", "org.gnome.desktop.interface", "gtk-theme", "{theme}"] + ) + self.theme_light = "Default" + self.theme_dark = "Default" @property def available(self) -> bool: @@ -80,19 +75,16 @@ def available(self) -> bool: class _Kde(DBusPlugin): - name = 'GTK' + name = "GTK" def __init__(self): super().__init__() - self.theme_light = 'Breeze' - self.theme_dark = 'Breeze' + self.theme_light = "Breeze" + self.theme_dark = "Breeze" def create_message(self, theme: str) -> QDBusMessage: message = QDBusMessage.createMethodCall( - 'org.kde.GtkConfig', - '/GtkConfig', - 'org.kde.GtkConfig', - 'setGtkTheme' + "org.kde.GtkConfig", "/GtkConfig", "org.kde.GtkConfig", "setGtkTheme" ) message.setArguments([theme]) return message @@ -103,49 +95,61 @@ def set_theme(self, theme: str): 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' + 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') + logger.warning("xsettingsd not available") return - with open(xsettingsd_conf_path, 'r') as f: + with open(xsettingsd_conf_path, "r") as f: lines = f.readlines() for i, line in enumerate(lines): - if line.startswith('Net/ThemeName'): + if line.startswith("Net/ThemeName"): lines[i] = f'Net/ThemeName "{theme}"\n' break - with open(xsettingsd_conf_path, 'w') as f: + with open(xsettingsd_conf_path, "w") as f: f.writelines(lines) # send signal to read new config - helpers.run(['killall', '-HUP', 'xsettingsd']) + helpers.run(["killall", "-HUP", "xsettingsd"]) class _Xfce(PluginCommandline): 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' + 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' + 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']) + 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' + 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: diff --git a/yin_yang/plugins/notify.py b/yin_yang/plugins/notify.py index 12707bb..6184eeb 100644 --- a/yin_yang/plugins/notify.py +++ b/yin_yang/plugins/notify.py @@ -1,9 +1,14 @@ -from ._plugin import PluginCommandline +from PySide6.QtDBus import QDBusMessage +from ..NotificationHandler import create_dbus_message +from ._plugin import DBusPlugin -class Notification(PluginCommandline): + +class Notification(DBusPlugin): def __init__(self): - super().__init__(['notify-send', 'Theme changed', 'Set the theme to {theme}', - '-a', 'Yin & Yang', '-u', 'low', '--icon', 'yin_yang']) + super().__init__() self.theme_light = 'Day' self.theme_dark = 'Night' + + def create_message(self, theme: str) -> QDBusMessage: + return create_dbus_message('Theme changed', f'Set the theme to {theme}') diff --git a/yin_yang/plugins/system.py b/yin_yang/plugins/system.py index 85f2f68..8e05ec2 100644 --- a/yin_yang/plugins/system.py +++ b/yin_yang/plugins/system.py @@ -1,23 +1,28 @@ import json import logging -import pwd import os +import pwd from configparser import ConfigParser from pathlib import Path from PySide6.QtCore import QLocale +from PySide6.QtDBus import QDBusMessage, QDBusVariant + from yin_yang import helpers from ..meta import Desktop -from ._plugin import PluginDesktopDependent, PluginCommandline +from ._plugin import ( + DBusPlugin, + PluginCommandline, + PluginDesktopDependent, + themes_from_theme_directories, +) logger = logging.getLogger(__name__) def test_gnome_availability(command) -> bool: - return PluginCommandline.check_command( - [command[0], 'get', command[2], command[3]] - ) + return PluginCommandline.check_command([command[0], "get", command[2], command[3]]) class System(PluginDesktopDependent): @@ -33,17 +38,27 @@ def __init__(self, desktop: Desktop): super().__init__(_Cinnamon()) case Desktop.BUDGIE: super().__init__(_Budgie()) + case Desktop.XFCE: + super().__init__(_Xfce()) case _: super().__init__(None) class _Gnome(PluginCommandline): - name = 'System' + name = "System" # TODO allow using the default themes, not only user themes def __init__(self): - super().__init__(['gsettings', 'set', 'org.gnome.shell.extensions.user-theme', 'name', '{theme}']) + super().__init__( + [ + "gsettings", + "set", + "org.gnome.shell.extensions.user-theme", + "name", + "{theme}", + ] + ) @property def available(self) -> bool: @@ -51,12 +66,20 @@ def available(self) -> bool: class _Budgie(PluginCommandline): - name = 'System' + 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' + 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: @@ -66,10 +89,10 @@ def available(self) -> bool: 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 "dark": + theme_bool = "true" + case "light": + theme_bool = "false" case _: raise NotImplementedError @@ -80,7 +103,7 @@ def insert_theme(self, theme: str) -> list: @property def available_themes(self) -> dict: - themes: dict[str, str] = {'dark': 'Dark', 'light': 'Light'} + themes: dict[str, str] = {"dark": "Dark", "light": "Light"} return themes @@ -89,37 +112,35 @@ def get_readable_kde_theme_name(file) -> str: """Searches for the long_name in the file and maps it to the found short name""" for line in file: - if 'Name=' in line: - name: str = '' + if "Name=" in line: + name: str = "" write: bool = False for letter in line: - if letter == '\n': + if letter == "\n": write = False if write: name += letter - if letter == '=': + if letter == "=": write = True return name def get_name_key(meta): locale = filter( - lambda name: name in meta['KPlugin'], - [f'Name[{QLocale().name()}]', - f'Name[{QLocale().language()}]', - 'Name'] + lambda name: name in meta["KPlugin"], + [f"Name[{QLocale().name()}]", f"Name[{QLocale().language()}]", "Name"], ) return next(locale) class _Kde(PluginCommandline): - name = 'System' + name = "System" translations = {} def __init__(self): - super().__init__(['lookandfeeltool', '-a', '{theme}']) - self.theme_light = 'org.kde.breeze.desktop' - self.theme_dark = 'org.kde.breezedark.desktop' + super().__init__(["lookandfeeltool", "-a", "{theme}"]) + self.theme_light = "org.kde.breeze.desktop" + self.theme_dark = "org.kde.breezedark.desktop" @property def available_themes(self) -> dict: @@ -132,7 +153,9 @@ def available_themes(self) -> dict: # asks the system what themes are available # noinspection SpellCheckingInspection - long_names = helpers.check_output(["lookandfeeltool", "-l"], universal_newlines=True) + long_names = helpers.check_output( + ["lookandfeeltool", "-l"], universal_newlines=True + ) long_names = long_names.splitlines() long_names.sort() @@ -141,23 +164,30 @@ def available_themes(self) -> dict: # trying to get the Desktop file try: # json in newer versions - with open(f'{helpers.get_usr()}share/plasma/look-and-feel/{long_name}/metadata.json', 'r') as file: + with open( + f"{helpers.get_usr()}share/plasma/look-and-feel/{long_name}/metadata.json", + "r", + ) as file: meta = json.load(file) key = get_name_key(meta) - self.translations[long_name] = meta['KPlugin'][key] + self.translations[long_name] = meta["KPlugin"][key] except OSError: try: # load the name from the metadata.desktop file - with open(f'{helpers.get_usr()}share/plasma/look-and-feel/{long_name}/metadata.desktop', - 'r') as file: + with open( + f"{helpers.get_usr()}share/plasma/look-and-feel/{long_name}/metadata.desktop", + "r", + ) as file: self.translations[long_name] = get_readable_kde_theme_name(file) except OSError: # check the next path if the themes exist there try: # load the name from the metadata.desktop file - with open(f'{path}{long_name}/metadata.desktop', 'r') as file: + with open(f"{path}{long_name}/metadata.desktop", "r") as file: # search for the name - self.translations[long_name] = get_readable_kde_theme_name(file) + self.translations[long_name] = get_readable_kde_theme_name( + file + ) except OSError: # if no file exist lets just use the long name self.translations[long_name] = long_name @@ -166,12 +196,17 @@ def available_themes(self) -> dict: class _Mate(PluginCommandline): - theme_directories = [Path(helpers.get_usr() + 'share/themes'), Path.home() / '.themes'] + theme_directories = [ + Path(helpers.get_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' + 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: @@ -182,14 +217,14 @@ def available_themes(self) -> dict: continue for d in directory.iterdir(): - index = d / 'index.theme' + index = d / "index.theme" if not index.is_file(): continue config = ConfigParser() config.read(index) try: - theme = config['X-GNOME-Metatheme']['MetacityTheme'] + theme = config["X-GNOME-Metatheme"]["MetacityTheme"] themes.append(theme) except KeyError: continue @@ -198,15 +233,33 @@ def available_themes(self) -> dict: @property def available(self): - return self.check_command(['dconf', 'help']) + 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' + 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) + + +class _Xfce(DBusPlugin): + def create_message(self, theme: str) -> QDBusMessage: + message = QDBusMessage.createMethodCall( + "org.xfce.Xfconf", "/org/xfce/Xfconf", "org.xfce.Xfconf", "SetProperty" + ) + theme_variant = QDBusVariant() + theme_variant.setVariant(theme) + message.setArguments(["xfwm4", "/general/theme", theme_variant]) + return message + + @property + def available_themes(self) -> dict: + themes = themes_from_theme_directories("xfwm4") + return {t: t for t in themes}