diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..5e8eb96 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,7 @@ +Copyright 2023 kittywitch + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.MD similarity index 100% rename from README.md rename to README.MD diff --git a/konawall/config.toml.example b/config.toml.example similarity index 73% rename from konawall/config.toml.example rename to config.toml.example index 90ea12e..f9769a5 100644 --- a/konawall/config.toml.example +++ b/config.toml.example @@ -1,5 +1,5 @@ -seconds = 300 -automatic = true +interval = 300 +rotate = true source = "konachan" tags = [ "rating:s" diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..d90ffc9 --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1694529238, + "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1699099776, + "narHash": "sha256-X09iKJ27mGsGambGfkKzqvw5esP1L/Rf8H3u3fCqIiU=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "85f1ba3e51676fa8cc604a3d863d729026a6b8eb", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..bd6fc83 --- /dev/null +++ b/flake.nix @@ -0,0 +1,21 @@ +{ + description = "konawall-py"; + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + }; + outputs = { + self, + nixpkgs, + flake-utils, + ... + } @ inputs: + flake-utils.lib.eachDefaultSystem (system: let + pkgs = nixpkgs.legacyPackages.${system}; + in { + packages = rec { + konawall-py = pkgs.python3.pkgs.callPackage ./package.nix {}; + default = konawall-py; + }; + }); +} diff --git a/konawall/downloader.py b/konawall/downloader.py index 162dd63..774a120 100644 --- a/konawall/downloader.py +++ b/konawall/downloader.py @@ -1,7 +1,7 @@ import logging import tempfile import requests -from custom_print import kv_print +from konawall.custom_print import kv_print """ Download files given a list of URLs diff --git a/konawall/environment.py b/konawall/environment.py index f808d99..8fd6921 100644 --- a/konawall/environment.py +++ b/konawall/environment.py @@ -1,8 +1,8 @@ import sys import os import logging -from custom_errors import UnsupportedPlatform -from module_loader import environment_handlers +from konawall.custom_errors import UnsupportedPlatform +from konawall.module_loader import environment_handlers """ This detects the DE/WM from the Linux environment because it's not provided by the platform @@ -21,6 +21,7 @@ def detect_linux_environment(): "kde", # qdbus ] modified_mapping = { + "plasma": "kde", "fluxbox": "feh", "blackbox": "feh", "openbox": "feh", diff --git a/konawall/__init__.py b/konawall/environments/__init__.py similarity index 100% rename from konawall/__init__.py rename to konawall/environments/__init__.py diff --git a/konawall/environments/darwin.py b/konawall/environments/darwin.py index 56d0f3b..2d7471c 100644 --- a/konawall/environments/darwin.py +++ b/konawall/environments/darwin.py @@ -1,5 +1,5 @@ import subprocess -from module_loader import add_environment +from konawall.module_loader import add_environment """ This sets wallpapers on Darwin. diff --git a/konawall/environments/kde.py b/konawall/environments/kde.py new file mode 100644 index 0000000..e7bbd79 --- /dev/null +++ b/konawall/environments/kde.py @@ -0,0 +1,16 @@ +import subprocess +from konawall.module_loader import add_environment + +""" +This sets the wallpaper on KDE. +""" +@add_environment("kde_setter") +def set_wallpapers(files: list, displays: list): + for file in files: + kde_script = f""" + for (const desktop of desktops()) {{ + desktop.currentConfigGroup = ["Wallpaper", "org.kde.image", "General"] + desktop.writeConfig("Image", "{file}") + }} + """ + subprocess.run(["qdbus", "org.kde.plasmashell", "/PlasmaShell", "org.kde.PlasmaShell.evaluateScript", kde_script]) \ No newline at end of file diff --git a/konawall/environments/win32.py b/konawall/environments/win32.py index a61dec8..5998664 100644 --- a/konawall/environments/win32.py +++ b/konawall/environments/win32.py @@ -1,8 +1,8 @@ import os import ctypes import logging -from imager import combine_to_viewport -from module_loader import add_environment +from konawall.imager import combine_to_viewport +from konawall.module_loader import add_environment """ Pre-setting on Windows diff --git a/konawall/gui.py b/konawall/gui.py old mode 100644 new mode 100755 index 699cebb..7892a47 --- a/konawall/gui.py +++ b/konawall/gui.py @@ -9,15 +9,15 @@ import screeninfo import tomllib import subprocess import importlib.metadata -from environment import set_environment_wallpapers, detect_environment -from module_loader import import_dir, environment_handlers, source_handlers -from custom_print import kv_print +from konawall.environment import set_environment_wallpapers, detect_environment +from konawall.module_loader import import_dir, environment_handlers, source_handlers +from konawall.custom_print import kv_print from humanfriendly import format_timespan class Konawall(wx.adv.TaskBarIcon): def __init__(self, version, file_logger): # Prevents it from closing before it has done any work on macOS - if wx.Platform == "__WXMAC__": + if wx.Platform == "__WXMAC__" or wx.Platform == "__WXGTK__": self.hidden_frame = wx.Frame(None) self.hidden_frame.Hide() @@ -37,6 +37,17 @@ class Konawall(wx.adv.TaskBarIcon): self.wallpaper_rotation_timer = wx.Timer(self, wx.ID_ANY) # Reload (actually load) the config and modules. + if wx.Platform == "__WXGTK__": + from xdg_base_dirs import xdg_config_home + self.config_path = os.path.join(xdg_config_home(), "konawall", "config.toml") + if wx.Platform == "__WXMAC__": + config_path_string = "~/Library/Application Support/konawall/config.toml" + self.config_path = os.path.expanduser(config_path_string) + elif wx.Platform == "__WXMSW__": + config_path_string = "%APPDATA%\\konawall\\config.toml" + self.config_path = os.path.expandvars(config_path_string) + else: + self.config_path = os.path.join(os.path.expanduser("~"), ".config", "konawall", "config.toml") self.reload_config() self.import_modules() @@ -65,7 +76,7 @@ class Konawall(wx.adv.TaskBarIcon): if "wxMSW" in wx.PlatformInfo: image = image.resize((16, 16)) elif "wxGTK" in wx.PlatformInfo: - image = image.rescale((22, 22)) + image = image.resize((22, 22)) # Write image to temporary file temp = tempfile.NamedTemporaryFile(suffix=".png", delete=False) @@ -77,7 +88,7 @@ class Konawall(wx.adv.TaskBarIcon): return icon def toggle_timed_wallpaper_rotation_status(self): - return f"{'Dis' if self.rotate_wallpapers else 'En'}able Timer" + return f"{'Dis' if self.rotate else 'En'}able Timer" # Load in our source and environment handlers def import_modules(self): @@ -88,9 +99,9 @@ class Konawall(wx.adv.TaskBarIcon): # Load a TOML file's key-value pairs into our class def load_config(self): - if os.path.isfile("config.toml"): + if os.path.isfile(self.config_path): # If the config file exists, load it as a dictionary into the config variable. - with open("config.toml", "rb") as f: + with open(self.config_path, "rb") as f: config = tomllib.load(f) # for every key-value pair in the config variable , set the corresponding attribute of our class to it for k, v in config.items(): @@ -100,14 +111,14 @@ class Konawall(wx.adv.TaskBarIcon): # If there is no config file, get complainy. dialog = wx.MessageDialog( None, - "No config file found, using defaults.", + f"No config file found at {self.config_path}, using defaults.", self.title_string, wx.OK|wx.ICON_INFORMATION ) dialog.ShowModal() dialog.Destroy() # Set some arbitrary defaults - self.rotate_wallpapers = True + self.rotate = True self.interval = 10*60 self.tags = ["rating:s"] self.logging = {} @@ -123,7 +134,10 @@ class Konawall(wx.adv.TaskBarIcon): return item def create_info_item(menu, label, help=""): item = wx.MenuItem(menu, wx.ID_ANY, label) - item.Enable(False) + if wx.Platform == "__WXGTK__": + menu.Bind(wx.EVT_MENU, (lambda x: x), id=item.GetId()) + else: + item.Enable(False) menu.Append(item) return item def create_separator(menu): @@ -195,21 +209,21 @@ class Konawall(wx.adv.TaskBarIcon): # Interactively edit the config file def edit_config_menu_item(self, event): - kv_print("User is editing", "config.toml") + kv_print("User is editing", self.config_path) # Check if we're on Windows, if so use Notepad if sys.platform == "win32": # I don't even know how to detect the default editor on Windows - subprocess.call("notepad.exe config.toml") + subprocess.call(f"notepad.exe {self.config_path}", shell=True) else: # Open config file in default editor - subprocess.call(f"{os.environ['SHELL']} {os.environ['EDITOR']} config.toml") + subprocess.call(f"{os.environ['SHELL']} {os.environ['EDITOR']} {self.config_path}", shell=True) # When file is done being edited, reload config - kv_print("User has edited", "config.toml") + kv_print("User has edited", self.config_path) self.reload_config() # Reload the application def reload_config(self): - kv_print(f"{'Rel' if self.loaded_before else 'L'}oading config from", "config.toml") + kv_print(f"{'Rel' if self.loaded_before else 'L'}oading config from", self.config_path) self.load_config() # Handle finding the log level @@ -250,14 +264,14 @@ class Konawall(wx.adv.TaskBarIcon): # Set whether to rotate wallpapers automatically or not def toggle_timed_wallpaper_rotation(self, event): - self.rotate_wallpapers = not self.rotate_wallpapers + self.rotate = not self.rotate self.respect_timed_wallpaper_rotation_toggle() # Update the timer and the menu item to reflect our current state def respect_timed_wallpaper_rotation_toggle(self): - if self.rotate_wallpapers and not self.wallpaper_rotation_timer.IsRunning(): + if self.rotate and not self.wallpaper_rotation_timer.IsRunning(): self.wallpaper_rotation_timer.Start(1000) - elif not self.rotate_wallpapers and self.wallpaper_rotation_timer.IsRunning(): + elif not self.rotate and self.wallpaper_rotation_timer.IsRunning(): self.wallpaper_rotation_timer.Stop() # Set the time left counter to show that it is disabled self.timed_wallpaper_rotation_status_menu_item.SetItemLabel(f"Automatic wallpaper rotation disabled") @@ -316,12 +330,12 @@ def main(): version = "testing version" file_logger = logging.FileHandler("app.log", mode="a") - #console_logger = logging.StreamHandler() + console_logger = logging.StreamHandler() logging.basicConfig( level=logging.DEBUG, format="%(asctime)s - %(levelname)s - %(message)s", handlers=[ - #console_logger, + console_logger, file_logger, ] ) diff --git a/konawall/module_loader.py b/konawall/module_loader.py index 7056c79..78b2d20 100644 --- a/konawall/module_loader.py +++ b/konawall/module_loader.py @@ -3,7 +3,7 @@ import os import re import inspect import logging -from custom_print import kv_print +from konawall.custom_print import kv_print global environment_handlers global source_handlers diff --git a/konawall/sources/__init__.py b/konawall/sources/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/konawall/sources/konachan.py b/konawall/sources/konachan.py index da8574f..1fc3fe0 100644 --- a/konawall/sources/konachan.py +++ b/konawall/sources/konachan.py @@ -1,9 +1,9 @@ import requests import logging -from custom_print import kv_print -from custom_errors import RequestFailed -from module_loader import add_source -from downloader import download_files +from konawall.custom_print import kv_print +from konawall.custom_errors import RequestFailed +from konawall.module_loader import add_source +from konawall.downloader import download_files """ Turn a list of tags and a count into a list of URLs to download from diff --git a/package.nix b/package.nix new file mode 100644 index 0000000..5bd3f02 --- /dev/null +++ b/package.nix @@ -0,0 +1,32 @@ +{ + lib, + buildPythonPackage, + python311Packages, +}: let + pyproject = builtins.fromTOML (builtins.readFile ./pyproject.toml); + poetryBlock = pyproject.tool.poetry; + dependencyReplacements = { + wxpython = python311Packages.wxPython_4_2; + }; +in + buildPythonPackage rec { + pname = poetryBlock.name; + version = poetryBlock.version; + + src = ./.; + + doCheck = false; + + propagatedBuildInputs = let + dependencyNames = lib.attrNames poetryBlock.dependencies; + dependencies = map (name: python311Packages.${name} or dependencyReplacements.${name}) dependencyNames; + in + dependencies; + + meta = with lib; { + description = poetryBlock.description; + homepage = poetryBlock.homepage; + license = licenses.${toLower poetryBlock.license}; + maintainers = with lib.maintainers; [kittywitch]; + }; + } diff --git a/pyproject.toml b/pyproject.toml index c5ec5af..f34a93d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,7 @@ [tool.poetry] name = "konawall" version = "0.1.0" +license = "MIT" description = "A hopefully cross-platform service for fetching wallpapers and setting them" authors = [ "Kat Inskip " @@ -10,6 +11,9 @@ packages = [ {include = "konawall"} ] +[tool.poetry.scripts] +gui = "konawall.script" + [tool.poetry.dependencies] python = "^3.11" pillow = "^10.0.1" @@ -18,9 +22,8 @@ requests = "^2.31.0" termcolor = "^2.3.0" wxpython = "^4.2.1" humanfriendly = "^10.0" +xdg-base-dirs = "^6.0.1" [build-system] -requires = [ - "poetry-core" -] -build-backend = "poetry.core.masonry.api" \ No newline at end of file +requires = [ "poetry-core" ] +build-backend = "poetry.core.masonry.api" diff --git a/requirements.txt b/requirements.txt index b96d67b..3fd46fe 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,5 @@ screeninfo requests termcolor wxpython -humanfriendly \ No newline at end of file +humanfriendly +xdg-base-dirs \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..92639b3 --- /dev/null +++ b/setup.py @@ -0,0 +1,24 @@ +from setuptools import setup + +with open("README.MD", "r") as f: + long_description = f.read() + + +import tomllib +with open("pyproject.toml", "rb") as f: + pyproject = tomllib.load(f) + poetryBlock = pyproject["tool"]["poetry"] + +setup( + name = poetryBlock["name"], + version = poetryBlock["version"], + author = poetryBlock["authors"][0].split(" <")[0], + author_email = poetryBlock["authors"][0].split(" <")[1][:-1], + description = poetryBlock["description"], + long_description = long_description, + entry_points = { + "console_scripts": [ + "konawall = konawall.gui:main", + ], + }, +) \ No newline at end of file