Simplest app can be created via https://gist.github.com/mathiasbynens/674099 e.g.
mkdir -p MyApp.app/Contents/MacOS
printf '#!/bin/bash\nsleep 5' > MyApp.app/Contents/MacOS/MyApp
chmod +x MyApp.app/Contents/MacOS/MyApp
echo "<plist><dict></dict></plist>" > MyApp.app/Contents/Info.plist
But more "appy" app can be done via the followning steps
-
Create app dir structure
mkdir -p MyApp.app/Contents/MacOS mkdir -p MyApp.app/Contents/Resources
-
Create app property list
MyApp.app/Contents/Info.plist
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>CFBundleExecutable</key> <string>MyApp</string> <key>CFBundleGetInfoString</key> <string>MyApp</string> <key>CFBundleIconFile</key> <string>MyApp.icns</string> <key>CFBundleName</key> <string>MyApp</string> <key>CFBundlePackageType</key> <string>APPL</string> </dict> </plist>
-
Create app executable
MyApp.app/Contents/MacOS/MyApp
#!/bin/bash /usr/bin/python $(cd "$(dirname "${BASH_SOURCE[0]}")/../Resources" && pwd)/main.py
-
Create app icon
MyApp.app/Contents/Resources/MyApp.icns
, e.g. https://gist.github.com/andreif/1691ee0a22458e4a6589 -
Create python app
MyApp.app/Contents/Resources/main.py
, e.g.# coding=utf-8 # Source: http://www.tkdocs.com/tutorial/firstexample.html import sys py2 = sys.version_info.major == 2 if py2: from Tkinter import * import Tkinter as ttk else: from tkinter import * import tkinter as ttk def calculate(*args): try: value = float(feet.get()) meters.set((0.3048 * value * 10000.0 + 0.5)/10000.0) except ValueError: pass root = Tk() root.title("Feet to Meters") if py2: mainframe = ttk.Frame(root) else: mainframe = ttk.Frame(root, padding="3 3 12 12") mainframe.grid(column=0, row=0, sticky=(N, W, E, S)) mainframe.columnconfigure(0, weight=1) mainframe.rowconfigure(0, weight=1) feet = StringVar() meters = StringVar() feet_entry = ttk.Entry(mainframe, width=7, textvariable=feet) feet_entry.grid(column=2, row=1, sticky=(W, E)) ttk.Label(mainframe, textvariable=meters).grid(column=2, row=2, sticky=(W, E)) ttk.Button(mainframe, text="Calculate", command=calculate).grid(column=3, row=3, sticky=W) ttk.Label(mainframe, text="feet").grid(column=3, row=1, sticky=W) ttk.Label(mainframe, text="is equivalent to").grid(column=1, row=2, sticky=E) ttk.Label(mainframe, text="meters").grid(column=3, row=2, sticky=W) for child in mainframe.winfo_children(): child.grid_configure(padx=5, pady=5) feet_entry.focus() root.bind('<Return>', calculate) root.mainloop()
-
Enjoy the result
-
To change app name in the OS X menu bar from
Python
to e.g.MyApp
, usepyobjc
, which is pre-installed in the system Python (/System/Library/Frameworks/Python/...
):from Foundation import NSBundle bundle = NSBundle.mainBundle() info = bundle.localizedInfoDictionary() or bundle.infoDictionary() info['CFBundleName'] = 'MyApp'
-
To prevent multiple instances of the same app:
try: import fcntl lockfile = open('/tmp/myapp.lock', 'w') fcntl.lockf(lockfile, fcntl.LOCK_EX | fcntl.LOCK_NB) except IOError as e: exit()
-
To focus on the already running instance is a bit hard atm, so we focus on all Python.app instances, until there is a better solution available/known
from Foundation import NSWorkspace from Cocoa import NSApplicationActivateAllWindows, NSApplicationActivateIgnoringOtherApps for app in NSWorkspace.sharedWorkspace().runningApplications(): if app.bundleIdentifier() == 'org.python.python': app.activateWithOptions_(NSApplicationActivateAllWindows | NSApplicationActivateIgnoringOtherApps)
-
One could use a pid file as a temporary solution
from Foundation import NSProcessInfo info = NSProcessInfo.processInfo() pid = info.processIdentifier() with open(PID_FILE, 'w+') as f: f.write(str(pid))
try: with open(PID_FILE) as f: pid = int(f.read()) except: pid = None for app in NSWorkspace.sharedWorkspace().runningApplications(): if app.bundleIdentifier() == 'org.python.python': if not pid or pid == app.processIdentifier(): app.activateWithOptions_(NSApplicationActivateAllWindows | NSApplicationActivateIgnoringOtherApps)