macOS changes

This commit is contained in:
Kat Inskip 2023-10-06 14:43:37 -07:00
parent b2cf9c4870
commit 6cc517c163
Signed by: kat
GPG key ID: 465E64DECEA8CF0F
5 changed files with 224 additions and 80 deletions

252
gui.py
View file

@ -11,70 +11,89 @@ 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
from humanfriendly import format_timespan
class Konawall(wx.adv.TaskBarIcon):
def __init__(self, file_logger):
# Prevents it from closing before it has done any work on macOS
if wx.Platform == "__WXMAC__":
self.hidden_frame = wx.Frame(None)
self.hidden_frame.Hide()
self.wallpaper_rotation_counter = 0
self.file_logger = file_logger
#self.console_logger = console_logger
self.loaded = False
wx.adv.TaskBarIcon.__init__(self)
# Pre-setup initialization
self.loaded_before = False
# Call the super function, make sure that the type is the statusitem for macOS
wx.adv.TaskBarIcon.__init__(self, wx.adv.TBI_CUSTOM_STATUSITEM)
# Detect environment and timer settings
self.environment = detect_environment()
self.automatic_item = None
self.automatic_timer = wx.Timer(self, wx.ID_ANY)
# Reload (actually load) the config
self.toggle_wallpaper_rotation_item = None
self.wallpaper_rotation_timer = wx.Timer(self, wx.ID_ANY)
# Reload (actually load) the config and modules.
self.reload()
self.load_modules()
self.automatic_timer.Start(self.seconds * 1000)
# Set up the taskbar icon
self.SetIcon(create_icon(), "Konawall")
# Start the timer to run every second
self.wallpaper_rotation_timer.Start(1000)
# Set up the taskbar icon, menu, bindings, ...
icon = self.generate_icon()
self.SetIcon(icon, "Konawall")
self.create_menu()
self.create_bindings()
def automatic_item_state(self):
if self.automatic:
return "Disable Automatic"
else:
return "Enable Automatic"
# Run the first time, manually
self.run(None)
# wxPython requires a wx.Bitmap, so we generate one from a PIL.Image
def generate_icon(self):
width = 128
height = 128
# Missing texture style, magenta and black checkerboard
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))
if "wxMSW" in wx.PlatformInfo:
image = image.Scale(16, 16)
elif "wxGTK" in wx.PlatformInfo:
image = image.Scale(22, 22)
# Write image to temporary file
temp = tempfile.NamedTemporaryFile(suffix=".png", delete=False)
image.save(temp.name)
# Convert to wxPython icon
icon = wx.Icon()
icon.CopyFromBitmap(wx.Bitmap(temp.name))
return icon
def toggle_wallpaper_rotation_status(self):
return f"{'Dis' if self.rotate else 'En'}able Timer"
# Load in our source and environment handlers
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")
# Load a TOML file's key-value pairs into our class
def read_config(self):
# check if config file exists
if os.path.isfile("config.toml"):
# If the config file exists, load it as a dictionary into the config variable.
with open("config.toml", "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():
kv_print(f"Loaded {k}", v)
setattr(self, k, v)
else:
# If there is no config file, get complainy.
dialog = wx.MessageDialog(
None,
"No config file found, using defaults.",
@ -83,17 +102,70 @@ class Konawall(wx.adv.TaskBarIcon):
)
dialog.ShowModal()
dialog.Destroy()
self.automatic = True
self.seconds = 10*60
# Set some arbitrary defaults
self.rotate = True
self.interval = 10*60
self.tags = ["rating:s"]
self.logging = {}
self.logging["file"] = "INFO"
# Create the menu
def create_menu(self):
# Make it easier to define menu items
def create_menu_item(menu, label, func, help="", kind=wx.ITEM_NORMAL):
item = wx.MenuItem(menu, wx.ID_ANY, label)
menu.Bind(wx.EVT_MENU, func, id=item.GetId())
menu.Append(item)
return item
def create_separator(menu):
item = wx.MenuItem(menu, id=wx.ID_SEPARATOR, kind=wx.ITEM_SEPARATOR)
menu.Append(item)
return item
# Create our Menu object
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")
# Time remaining for automatic wallpaper rotation
self.wallpaper_rotation_status = wx.MenuItem(self.menu, -1, "Time remaining")
self.wallpaper_rotation_status.Enable(False)
self.menu.Append(self.wallpaper_rotation_status)
create_separator(self.menu)
# Change wallpapers
create_menu_item(
self.menu,
"Rotate Wallpapers",
self.run,
"Fetch new wallpapers and set them as your wallpapers"
)
# Toggle automatic wallpaper rotation
self.toggle_wallpaper_rotation_item = create_menu_item(
self.menu,
self.toggle_wallpaper_rotation_status(),
self.toggle_wallpaper_rotation,
"Toggle the automatic wallpaper rotation timer"
)
create_separator(self.menu)
# Interactive config editing
create_menu_item(
self.menu,
"Edit Config",
self.edit_config,
"Interactively edit the config file"
)
# Exit
create_menu_item(
self.menu,
"Exit",
self.Destroy,
"Quit the application"
)
# Interactively edit the config file
def edit_config(self, event):
kv_print("User is editing", "config.toml")
# Check if we're on Windows, if so use Notepad
@ -107,58 +179,82 @@ class Konawall(wx.adv.TaskBarIcon):
kv_print("User has edited", "config.toml")
self.reload()
# Reload the application
def reload(self):
if self.loaded:
kv_print("Reloading config from", "config.toml")
else:
kv_print("Loading config from", "config.toml")
kv_print(f"{'Rel' if self.loaded_before else 'L'}oading 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)
if self.loaded_before == True:
# If we're reloading, we need to make sure the timer and menu item reflect our current state.
self.respect_wallpaper_rotation_toggle()
# Finished loading
self.loaded = True
self.loaded_before = 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())
# Set whether to rotate wallpapers automatically or not
def toggle_wallpaper_rotation(self, event):
self.rotate = not self.rotate
self.respect_wallpaper_rotation_toggle()
def show_menu(self, event):
self.PopupMenu(self.menu)
# Update the timer and the menu item to reflect our current state
def respect_wallpaper_rotation_toggle(self):
if self.rotate and not self.wallpaper_rotation_timer.IsRunning():
self.wallpaper_rotation_timer.Start(1000)
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.wallpaper_rotation_status.SetItemLabel(f"Automatic wallpaper rotation disabled")
# Update the menu item for the toggle
self.toggle_wallpaper_rotation_item.SetItemLabel(self.toggle_wallpaper_rotation_status())
# Update wallpaper rotation time left counter
def respect_wallpaper_rotation_status(self):
self.wallpaper_rotation_status.SetItemLabel(f"{format_timespan(self.interval - self.wallpaper_rotation_counter)} remaining")
# Perform the purpose of the application; get new wallpaper media and set 'em.
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)
# For macOS
def CreatePopupMenu(self):
self.PopupMenu(self.menu)
# For everybody else who has bindable events
def show_menu(self, event):
self.PopupMenu(self.menu)
# Every second, check if the wallpaper rotation timer has ticked over
def handle_timer_tick(self, event):
if self.wallpaper_rotation_counter >= self.interval:
# If it has, run the fetch and set mechanism
self.run(None)
self.wallpaper_rotation_counter = 0
else:
self.wallpaper_rotation_counter += 1
# Update the time left counter
self.respect_wallpaper_rotation_status()
# Bind application events
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__":
# Implement the wallpaper rotation timer
self.Bind(wx.EVT_TIMER, self.handle_timer_tick, self.wallpaper_rotation_timer)
def main():
file_logger = logging.FileHandler("app.log", mode="a")
#console_logger = logging.StreamHandler()
logging.basicConfig(
@ -169,6 +265,10 @@ if __name__ == "__main__":
file_logger,
]
)
app = wx.App(False)
app = wx.App(redirect=False)
app.SetExitOnFrameDelete(False)
Konawall(file_logger)
app.MainLoop()
app.MainLoop()
if __name__ == "__main__":
main()