Skip to content

Instantly share code, notes, and snippets.

@jfparis
Created February 22, 2022 20:50
Show Gist options
  • Save jfparis/e129db1b9919aa1bcd81de8e3413dbfd to your computer and use it in GitHub Desktop.
Save jfparis/e129db1b9919aa1bcd81de8e3413dbfd to your computer and use it in GitHub Desktop.
import sys
import json
import rumps
import base64
import traceback
from threading import Thread
from Foundation import NSData
from AppKit import NSImage, NSApplication
from ctypes import CDLL, Structure, POINTER, c_uint32, byref
from ctypes.util import find_library
class ProcessSerialNumber(Structure):
_fields_ = [('highLongOfPSN', c_uint32),
('lowLongOfPSN', c_uint32)]
lookup = {}
app = None
def transform_app_type(toForeground):
kCurrentProcess = 2
kProcessTransformToForegroundApplication = 1
kProcessTransformToBackgroundApplication = 2
kProcessTransformToUIElementAppication = 4
apptype = kProcessTransformToBackgroundApplication
if toForeground:
apptype = kProcessTransformToForegroundApplication
ApplicationServices = CDLL(find_library('ApplicationServices'))
TransformProcessType = ApplicationServices.TransformProcessType
TransformProcessType.argtypes = [POINTER(ProcessSerialNumber), c_uint32]
psn = ProcessSerialNumber(0, kCurrentProcess)
ApplicationServices.TransformProcessType(psn, apptype)
def item_clicked(sender):
for k in lookup:
if lookup[k].title == sender.title:
print("click:%s" % k)
sys.stdout.flush()
def parsemenus(cfg):
menuitems = []
for n in cfg["Menus"]:
mn = rumps.MenuItem(n["Text"])
if "Enabled" in n and not n["Enabled"]:
mn.set_callback(None)
else:
mn.set_callback(item_clicked)
lookup[n["Key"]] = mn
menuitems.append(mn)
return menuitems
def get_input():
try:
while True:
line = sys.stdin.readline()
if not line:
break
if line == '':
continue
print("info:Read %s" % line)
sys.stdout.flush()
cfg = None
try:
cfg = json.loads(line)
except:
pass
if cfg == None:
print("info:Unable to parse line")
sys.stdout.flush()
continue
if "Action" in cfg:
print("info:Running %s" % cfg["Action"])
sys.stdout.flush()
if cfg["Action"] == "setmenu":
menu = cfg["Menu"]
if menu["Key"] in lookup:
lookup[menu["Key"]].title = menu["Text"]
if "Enabled" in menu and not menu["Enabled"]:
lookup[menu["Key"]].set_callback(None)
else:
lookup[menu["Key"]].set_callback(item_clicked)
app.menu.update([])
else:
print("warn:Key not found %s" % cfg["Action"])
sys.stdout.flush()
elif cfg["Action"] == "setmenus":
app.menu.clear()
app.menu = parsemenus(cfg)
print("info:Updated menus")
sys.stdout.flush()
elif cfg["Action"] == "seticon":
try:
raw = base64.b64decode(cfg["Image"])
data = NSData.dataWithBytes_length_(raw, len(raw))
img = NSImage.alloc().initWithData_(data)
img.setScalesWhenResized_(True)
img.setSize_((18, 18))
img.setTemplate_(True)
app.icon = img
print("info:Image updated")
sys.stdout.flush()
except:
print("warn:Failed to set image")
sys.stdout.flush()
elif cfg["Action"] == "setappicon":
try:
raw = base64.b64decode(cfg["Image"])
data = NSData.dataWithBytes_length_(raw, len(raw))
img = NSImage.alloc().initWithData_(data)
#img.setScalesWhenResized_(True)
#img.setSize_((21, 21))
NSApplication.sharedApplication().setApplicationIconImage_(img)
print("info:AppImage updated")
sys.stdout.flush()
except:
print("warn:Failed to set image")
sys.stdout.flush()
elif cfg["Action"] == "shutdown":
break
elif cfg["Action"] == "foreground":
transform_app_type(True)
elif cfg["Action"] == "background":
transform_app_type(False)
elif cfg["Action"] == "notification":
if rumps._NOTIFICATIONS:
title = cfg["Title"]
message = cfg["Message"]
subtitle = ''
playSound = True
image = None
if "SubTitle" in cfg:
subtitle = cfg["SubTitle"]
if "PlaySound" in cfg:
playSound = cfg["PlaySound"]
if "Image" in cfg:
try:
raw = base64.b64decode(cfg["Image"])
data = NSData.dataWithBytes_length_(raw, len(raw))
image = NSImage.alloc().initWithData_(data)
except:
print("warn:Failed to decode image")
sys.stdout.flush()
rumps.notification(cfg["Title"], subtitle, cfg["Message"], sound=playSound, image=image)
finally:
try:
traceback.print_exc()
except:
pass
rumps.quit_application()
print("info:Shutdown")
sys.stdout.flush()
print("info:Stdin close")
sys.stdout.flush()
sys.stdin.close()
if __name__ == "__main__":
if len(sys.argv) == 2 and sys.argv[1] == "TEST":
sys.exit(0)
app = rumps.App('', quit_button=None)
transform_app_type(False)
t = Thread(target=get_input)
t.start()
app.run()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment