![](https://yourselfhood.com/storage/2023/07/1689047586python-control-macos-jpg.webp)
In this quick tip, excerpted from Useful Python, Stuart looks at ways to control the Windows OS with Python.
Working on a Mac, we can control almost everything about the system using pyobjc, the Python-to-Objective-C bridge. Apple makes most of its OS controllable via the AppKit module, and pyobjc
gives Python access to all of this. This will be most useful if we already know the AppKit way to do the thing we want, but with a little exploration it’s possible to make our way through the operating system APIs.
Let’s try an example. First, we’ll need pyobjc
, which can be installed with pip install pyobjc
. This will install a whole list of operating system API bridges, allowing access to all sorts of aspects of macOS. For now, we’ll consider AppKit, which is the tool used to build and control running apps on a Mac desktop.
We can list all the applications currently running using AppKit:
<code class="lang- <a href="https://yourselfhood.com/how-to-write-efficient-python-code-a-tutorial-for-beginners/" class="lar_link" data-linkid="2205" data-postid="2301" title="Python" target="_blank" >python</a>"> <a href="https://yourselfhood.com/how-to-write-efficient-python-code-a-tutorial-for-beginners/" class="lar_link" data-linkid="2205" data-postid="2301" title="Python" target="_blank" >Python</a> 3.9.6 (default, Oct 18 2022, 12:41:40) [Clang 14.0.0 (clang-1400.0.29.202)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> from AppKit import NSWorkspace >>> NSWorkspace.sharedWorkspace().runningApplications() ( "<NSRunningApplication: 0x60000145c000 (com.apple.loginwindow - 148) LSASN:{hi=0x0;lo=0x6006}>", "<NSRunningApplication: 0x60000145c080 (com.apple.backgroundtaskmanagement.agent - 475) LSASN:{hi=0x0;lo=0xb00b}>", "<NSRunningApplication: 0x60000145c100 (com.apple.WindowManager - 474) LSASN:{hi=0x0;lo=0xc00c}>", "<NSRunningApplication: 0x60000145c180 (com.apple.CoreLocationAgent - 500) LSASN:{hi=0x0;lo=0xe00e}>", "<NSRunningApplication: 0x60000145c980 (com.apple.Terminal - 1302) LSASN:{hi=0x0;lo=0x24024}>", "<NSRunningApplication: 0x60000145ca00 (com.apple.Safari - 1303) LSASN:{hi=0x0;lo=0x25025}>", "<NSRunningApplication: 0x60000145cb80 (com.apple.Spotlight - 1310) LSASN:{hi=0x0;lo=0x28028}>", "<NSRunningApplication: 0x60000145cc00 (com.apple.finder - 1306) LSASN:{hi=0x0;lo=0x29029}>", ) >>> </code>
This will give a long list of NSRunningApplication
objects. Each one corresponds to a specific application currently running on the desktop. Many are “invisible” applications (things that are running but aren’t necessarily showing a window), but others are things that we might think of as actual applications that we can see—such as Safari, Terminal, and so on. NSRunningApplication
is documented at developer.apple.com, where its properties can be seen. For example, each application has a localizedName
and a bundleIdentifier
:
<code class="lang- <a href="https://yourselfhood.com/how-to-write-efficient-python-code-a-tutorial-for-beginners/" class="lar_link" data-linkid="2205" data-postid="2301" title="Python" target="_blank" >python</a>">>>> for nsapp in NSWorkspace.sharedWorkspace().runningApplications(): ... print(f"{nsapp.localizedName()} -> {nsapp.bundleIdentifier()}") ... loginwindow -> com.apple.loginwindow BackgroundTaskManagementAgent -> com.apple.backgroundtaskmanagement.agent WindowManager -> com.apple.WindowManager CoreLocationAgent -> com.apple.CoreLocationAgent Terminal -> com.apple.Terminal Safari -> com.apple.Safari Spotlight -> com.apple.Spotlight Finder -> com.apple.finder </code>
We can also see that a NSRunningApplication
object has an activate function, which we can call to activate that app as though we had clicked its icon in the Dock. So, to find Safari and then activate it, we would use that activate function. The call to activate
requires a value for options
, as the documentation describes, and that also needs to be imported from AppKit:
<code class="lang- <a href="https://yourselfhood.com/how-to-write-efficient-python-code-a-tutorial-for-beginners/" class="lar_link" data-linkid="2205" data-postid="2301" title="Python" target="_blank" >python</a>">>>> from AppKit import NSWorkspace, NSApplicationActivateIgnoringOtherApps >>> safari_list = [x for x in NSWorkspace.sharedWorkspace().runningApplications() if x.bundleIdentifier() == 'com.apple.Safari'] >>> safari = safari_list[0] >>> safari.activateWithOptions_(NSApplicationActivateIgnoringOtherApps) </code>
Now Safari is activated.
Finding Python Versions of macOS APIs
Finding the name of something in Python that corresponds to the Objective-C name can be a little tricky. As shown in the code above, the Objective-C activate
function is called activateWithOptions_
in Python. There’s a set of rules for this name translation, which the pyobjc documentation explains, but it can sometimes be quicker to use Python’s own dir()
function to show all the properties of an object and then pick out the one that looks most plausible:
<code class="lang- <a href="https://yourselfhood.com/how-to-write-efficient-python-code-a-tutorial-for-beginners/" class="lar_link" data-linkid="2205" data-postid="2301" title="Python" target="_blank" >python</a>">>>> print(len(dir(safari))) 452 </code>
Ouch! Our safari
instance of an NSRunningApplication
has 452 properties! Well, the one we want is probably called something like “activate”, so:
<code class="lang- <a href="https://yourselfhood.com/how-to-write-efficient-python-code-a-tutorial-for-beginners/" class="lar_link" data-linkid="2205" data-postid="2301" title="Python" target="_blank" >python</a>">>>> print([x for x in dir(safari) if "activate" in x.lower()]) ['activateWithOptions_', 'activateWithOptions_'] </code>
Aha! So activateWithOptions_
is the name of the function we need to call. Similarly, the name of the option we want to pass to that function is in AppKit itself:
<code class="lang- <a href="https://yourselfhood.com/how-to-write-efficient-python-code-a-tutorial-for-beginners/" class="lar_link" data-linkid="2205" data-postid="2301" title="Python" target="_blank" >python</a>">>>> [x for x in dir(AppKit) if "ignoringotherapps" in x.lower()] ['NSApplicationActivateIgnoringOtherApps'] </code>
This process can feel a little exploratory at times, but it’s possible to do anything that Objective-C can do from Python as well.
This article is excerpted from Useful Python, available on SitePoint Premium and from ebook retailers.