From 96f1ee3583a2d87660339d8b6d2ec5519737e631 Mon Sep 17 00:00:00 2001 From: Kat Inskip Date: Sat, 9 Sep 2023 13:27:06 -0700 Subject: [PATCH] Add a systray based GUI o: --- .gitignore | 5 +- main.py => cli.py | 0 config.toml.example | 10 +++ custom_print.py | 8 +- downloader.py | 2 +- gui.py | 174 ++++++++++++++++++++++++++++++++++++++++++++ module_loader.py | 5 +- sources/konachan.py | 6 +- 8 files changed, 198 insertions(+), 12 deletions(-) rename main.py => cli.py (100%) create mode 100644 config.toml.example create mode 100644 gui.py diff --git a/.gitignore b/.gitignore index 6096aa2..fb43c15 100644 --- a/.gitignore +++ b/.gitignore @@ -159,5 +159,6 @@ cython_debug/ # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ -# App logs -app.log \ No newline at end of file +# App specific files +app.log +config.toml \ No newline at end of file diff --git a/main.py b/cli.py similarity index 100% rename from main.py rename to cli.py diff --git a/config.toml.example b/config.toml.example new file mode 100644 index 0000000..90ea12e --- /dev/null +++ b/config.toml.example @@ -0,0 +1,10 @@ +seconds = 300 +automatic = true +source = "konachan" +tags = [ + "rating:s" +] + +[logging] +file = "INFO" +console = "DEBUG" \ No newline at end of file diff --git a/custom_print.py b/custom_print.py index 0f06ee8..8f21480 100644 --- a/custom_print.py +++ b/custom_print.py @@ -6,9 +6,9 @@ Print a key-value pair with a key and value coloured differently. :param key: The key to print :param value: The value to print -:param newline: Whether to print a newline after the value +:param level: The logging level to print at :returns: None """ -def kv_print(key: str, value: str, newline: bool = False) -> None: - logging.info(f"{key}: {value}") - print(termcolor.colored(key, "cyan") + ": " + termcolor.colored(value, "white"), end="\n" if newline else " ") \ No newline at end of file +def kv_print(key: str, value: str, level: str = "INFO") -> None: + logger = getattr(logging, level.lower()) + logger(termcolor.colored(key, "cyan") + ": " + termcolor.colored(value, "white")) \ No newline at end of file diff --git a/downloader.py b/downloader.py index 36f656b..162dd63 100644 --- a/downloader.py +++ b/downloader.py @@ -25,7 +25,7 @@ def download_files(files: list) -> list: # Close the file image_file.close() # Give the user data about the downloaded image - kv_print(f"Image {str(i+1)}", image_file.name, newline=True) + kv_print(f"Image {str(i+1)}", image_file.name) # Add the file to the list of downloaded files downloaded_files.append(image_file.name) return downloaded_files \ No newline at end of file diff --git a/gui.py b/gui.py new file mode 100644 index 0000000..bdb011d --- /dev/null +++ b/gui.py @@ -0,0 +1,174 @@ +import wx +import wx.adv +import tempfile +from PIL import Image, ImageDraw +import os +import sys +import logging +import screeninfo +import tomllib +import subprocess +from environment import set_environment_wallpapers, detect_environment +from module_loader import import_dir, environment_handlers, source_handlers +from custom_print import kv_print + +def create_icon(): + width = 128 + height = 128 + # Missing texture + image = Image.new('RGB', (width, height), (0, 0, 0)) + dc = ImageDraw.Draw(image) + dc.rectangle((0, 0, width//2, height//2), fill=(255, 0, 255)) + dc.rectangle((width//2, height//2, width, height), fill=(255, 0, 255)) + # Write image to temporary file + temp = tempfile.NamedTemporaryFile(suffix=".png", delete=False) + image.save(temp.name) + icon = wx.Icon() + icon.CopyFromBitmap(wx.Bitmap(temp.name)) + return icon + + +def create_menu_item(menu, label, func): + item = wx.MenuItem(menu, -1, label) + menu.Bind(wx.EVT_MENU, func, id=item.GetId()) + menu.Append(item) + return item + +class Konawall(wx.adv.TaskBarIcon): + def __init__(self, file_logger, console_logger): + self.file_logger = file_logger + self.console_logger = console_logger + self.loaded = False + wx.adv.TaskBarIcon.__init__(self) + # Pre-setup initialization + self.environment = detect_environment() + self.automatic_item = None + self.automatic_timer = wx.Timer(self, wx.ID_ANY) + # Reload (actually load) the config + self.reload() + self.load_modules() + + self.automatic_timer.Start(self.seconds * 1000) + # Set up the taskbar icon + self.SetIcon(create_icon(), "Konawall") + self.create_menu() + self.create_bindings() + + def automatic_item_state(self): + if self.automatic: + return "Disable Automatic" + else: + return "Enable Automatic" + + def load_modules(self): + import_dir(os.path.join(os.path.dirname(os.path.abspath( __file__ )), "sources")) + kv_print("Loaded source handlers", ", ".join(source_handlers), level="debug") + import_dir(os.path.join(os.path.dirname(os.path.abspath( __file__ )), "environments")) + kv_print("Loaded environment handlers", ", ".join(environment_handlers), level="debug") + + def read_config(self): + # check if config file exists + if os.path.isfile("config.toml"): + with open("config.toml", "rb") as f: + config = tomllib.load(f) + for k, v in config.items(): + kv_print(f"Loaded {k}", v) + setattr(self, k, v) + else: + dialog = wx.MessageDialog( + None, + "No config file found, using defaults.", + "Konawall", + wx.OK|wx.ICON_INFORMATION + ) + dialog.ShowModal() + dialog.Destroy() + self.automatic = True + self.seconds = 10*60 + self.tags = ["rating:s"] + + def create_menu(self): + self.menu = wx.Menu() + create_menu_item(self.menu, "Run", self.run) + self.automatic_item = create_menu_item(self.menu, self.automatic_item_state(), self.toggle_automatic) + create_menu_item(self.menu, "Edit Config", self.edit_config) + self.menu.Append(wx.ID_EXIT, "Exit") + + def edit_config(self, event): + kv_print("User is editing", "config.toml") + # 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") + else: + # Open config file in default editor + subprocess.call(f"{os.environ['EDITOR']} config.toml") + # When file is done being edited, reload config + kv_print("User has edited", "config.toml") + self.reload() + + def reload(self): + if self.loaded: + kv_print("Reloading config from", "config.toml") + else: + kv_print("Loading config from", "config.toml") + self.read_config() + # Handle finding the log level + if "file" in self.logging: + file_log_level = getattr(logging, self.logging["file"]) + else: + file_log_level = logging.INFO + self.file_logger.setLevel(file_log_level) + if "console" in self.logging: + console_log_level = getattr(logging, self.logging["console"]) + else: + console_log_level = logging.INFO + self.console_logger.setLevel(console_log_level) + # Finished loading + self.loaded = True + + # Handle the automatic timer + if self.automatic and self.automatic_timer.IsRunning(): + self.automatic_timer.Stop() + self.automatic_timer.Start(self.seconds * 1000) + self.menu.SetLabel(self.automatic_item.Id, self.automatic_item_state()) + elif not self.automatic and self.automatic_timer.IsRunning(): + self.automatic_timer.Stop() + self.menu.SetLabel(self.automatic_item.Id, self.automatic_item_state()) + + def toggle_automatic(self, event): + self.automatic = not self.automatic + if self.automatic: + self.automatic_timer.Start(self.seconds * 1000) + else: + self.automatic_timer.Stop() + self.menu.SetLabel(self.automatic_item.Id, self.automatic_item_state()) + + def show_menu(self, event): + self.PopupMenu(self.menu) + + def run(self, event): + displays = screeninfo.get_monitors() + count = len(displays) + files = source_handlers[self.source](count, self.tags) + set_environment_wallpapers(self.environment, files, displays) + + def create_bindings(self): + self.Bind(wx.adv.EVT_TASKBAR_LEFT_DOWN, self.run) + self.Bind(wx.adv.EVT_TASKBAR_RIGHT_DOWN, self.show_menu) + self.Bind(wx.EVT_TIMER, self.run, self.automatic_timer) + +if __name__ == "__main__": + file_logger = logging.FileHandler("app.log", mode="a") + console_logger = logging.StreamHandler() + logging.basicConfig( + level=logging.DEBUG, + format="%(asctime)s - %(levelname)s - %(message)s", + handlers=[ + console_logger, + file_logger, + ] + ) + app = wx.App(False) + Konawall(file_logger, console_logger) + app.MainLoop() \ No newline at end of file diff --git a/module_loader.py b/module_loader.py index 3b2d22e..7056c79 100644 --- a/module_loader.py +++ b/module_loader.py @@ -3,6 +3,7 @@ import os import re import inspect import logging +from custom_print import kv_print global environment_handlers global source_handlers @@ -49,7 +50,7 @@ def add_environment(environment: str) -> callable: path = frame[0].f_code.co_filename def wrapper(function): environment_handlers[environment] = function - logging.debug(f"Loaded environment handler {environment} from {path}") + kv_print(f"Loaded environment handler {environment} from", path, level="debug") return wrapper """ @@ -65,5 +66,5 @@ def add_source(source: str) -> callable: path = frame[0].f_code.co_filename def wrapper(function): source_handlers[source] = function - logging.debug(f"Loaded wallpaper source {source} from {path}") + kv_print(f"Loaded wallpaper source {source} from", path, level="debug") return wrapper \ No newline at end of file diff --git a/sources/konachan.py b/sources/konachan.py index dd0ed54..96a2c7a 100644 --- a/sources/konachan.py +++ b/sources/konachan.py @@ -34,9 +34,9 @@ def request_posts(count: int, tags: list) -> list: kv_print("Post ID", post["id"]) kv_print("Author", post["author"]) kv_print("Rating", post["rating"]) - kv_print("Resolution", f"{post['width']}x{post['height']}", newline=True) - kv_print("Tags", post["tags"], newline=True) - kv_print("URL", post["file_url"], newline=True) + kv_print("Resolution", f"{post['width']}x{post['height']}") + kv_print("Tags", post["tags"]) + kv_print("URL", post["file_url"]) # Append the URL to the list post_urls.append(post["file_url"]) else: