Releases: JeanExtreme002/pymacos
Release list
v1.19.1
pip install --upgrade pymacosA bug fix release.
Fixed
visionand PDFs. The docs listed PDF among the formats thevisionfunctions read, but they failed on one:lines,text,facesand the others with a cryptic "zero-dimensioned image (0 x 0)",smart_cropandscan_documentwith "not an image macOS can read". A PDF now raises a clearValueErrorthat shows the way: draw the pages you want withpdf.renderfirst.macos.vision.text(macos.pdf.render("scan.pdf", page=1, size=2048))
v1.19.0
pip install --upgrade pymacos1.19 removes sensitive text from PDFs for good, fixes two defaults bugs, and comes with reviewed docs and ready-to-use recipes. Still no dependencies.
New
pdf.redact()blacks out text in a PDF and removes it, rather than drawing a box over text that can still be copied. It takes texts, matched as whole words across line breaks, or regular expressions. It also clears the matches from form fields, comments, the title and the bookmarks, and tells how many it found on each page.If a target isn't found anywhere, it raises and writes nothing.done = macos.pdf.redact("contract.pdf", ["Jane Doe", re.compile(r"\b\d{3}-\d{2}-\d{4}\b")], "public.pdf") done.matches # {'Jane Doe': 3, '\\b\\d{3}-\\d{2}-\\d{4}\\b': 1} done.pages # {1: 2, 4: 2}: the pages to look over before sharing
Fixes
defaults.delete()no longer reportsTruefor a key that only the global domain sets.defaults.restored()no longer writes a key into an app's domain on exit when that key was only set globally. It also puts back an app's own key whose value equals the global one.
Docs
- Recipes: short scripts for everyday jobs.
- Tidy the Downloads folder as files arrive.
- Black out Social Security numbers in a folder of PDFs.
- Get warned before the AirPods run out.
- A window manager in a few lines.
- Turn Word documents into PDFs.
- Know when a long job ends.
- Copy the text out of the last screenshot.
- A full review against the code.
- Corrected:
- which exceptions derive from
MacOSError; - when hotkeys stop shortcuts from reaching the front app;
- the mouse permissions;
- the Wi-Fi quality thresholds;
- the two meanings of
displayinscreen; - 16-bit audio edits.
- which exceptions derive from
- Added: the parameters, return values, errors and macOS versions that were missing.
- Corrected:
- The API reference is now in alphabetical order, and the examples are written for a global audience.
- Doc pages shared on social networks or in chat now show a link preview.
v1.18.0
pip install --upgrade pymacos1.18 automates everyday chores: scripts that run when a folder changes or a disk is plugged in, documents converted without Word, apps uninstalled with their leftovers, VPNs switched from code, windows tiled in a grid. Still no dependencies.
New
- Run a script when something happens: when a folder changes, or when a disk is mounted. It keeps working after a restart, with no Python left running.
macos.schedule.add("tidy", "tidy_downloads.py", when_changed="~/Downloads") macos.schedule.add("backup", "copy_photos.py", at_mount=True)
macos.documentconverts between Word, RTF, HTML, OpenDocument, plain text and PDF, and reads a document's text, with no Word or LibreOffice needed.macos.document.convert("report.docx", "report.pdf") macos.document.text("minutes.docx")
apps.uninstall()moves an app to the Trash with the files it left in your Library.dry_run=Trueshows what would go.macos.apps.uninstall("Slack", dry_run=True)
- VPNs: list the ones set up in System Settings, connect and disconnect them.
macos.network.connect_vpn("Office")
power.sleep_blockers()tells what keeps the Mac from sleeping.network.bandwidth()tells how fast data is going through each network interface right now.- Time Machine exclusions: leave a folder out of the backups, or back it up again.
macos.time_machine.exclude("~/code/app/node_modules")
windows.tile_all()arranges the windows side by side in a grid;windows.tile()does the same with the windows you pick.
Notes
document.convertconverts as TextEdit would: the page layout of Word documents (columns, headers and footers, text boxes) is lost, and tables become lines when writing.docxor.doc.apps.uninstallonly touches your own Library, not/Library, and refuses apps that are running or that come with macOS.network.vpns()lists the VPNs in System Settings; VPN apps that don't add theirs there, and possibly IKEv2 VPNs, aren't listed.
v1.17.0
pip install --upgrade pymacos1.17 helps diagnose a Mac: the Wi-Fi signal, what starts by itself, the crashes and the system log, what's plugged in, the network setup and what fills the disk. PDFs get a table of contents, their pictures, and signatures placed by a label. Still no dependencies.
New
- Place a signature or a text beside a label of the page, instead of computing points.
macos.pdf.sign("contract.pdf", "signature.png", "signed.pdf", near="Signature:") macos.pdf.add_text("form.pdf", "Ana Souza", "filled.pdf", near="Name:")
- PDF tables of contents and pictures:
- read and write a PDF's bookmarks;
- save the images a PDF holds.
macos.pdf.set_bookmarks("book.pdf", [("Intro", 1), ("Chapter 1", 3), ("1.1", 4, 1)], "book-toc.pdf") macos.pdf.images("brochure.pdf", "brochure-images")
network.wifi_signal()tells the signal, noise, quality, speed and channel, without the Location permission.- Network setup: every interface with its addresses, the DNS servers and the proxies in use.
macos.network.wifi_signal().quality # 'good' macos.network.dns_servers() # ['192.168.0.1', '8.8.8.8']
- What starts by itself: launch agents and daemons, with what they run and whether they're running.
[item.label for item in macos.system.startup_items() if item.running]
- Crashes and the log: the crash reports macOS recorded, and the system log, filtered by process, text or level.
macos.system.crash_reports(since=datetime.now() - timedelta(days=7)) macos.system.logs(process="Safari", level="error", last="1h")
- What's installed and plugged in:
- every installed app, with its version;
- the USB devices;
- the charger's wattage.
finder.largest()finds the largest files in a folder, quickly, through Spotlight.
Fixes
- The video tests skip, instead of failing, when
screencapturehangs on a CI machine.
Notes
system.logs()stops atlimitmessages (1000 by default): the log holds thousands a minute.system.startup_items()leaves out Apple's own, in/System. For the apps opened at login, seeapps.login_items().- The Wi-Fi signal has no network name: macOS keeps it behind the Location permission, which Python run from a terminal can't get.
v1.16.0
pip install --upgrade pymacos1.16 tells how fast the internet is and what uses the network, the battery and the GPU, warns before a disk fails, and fills in, signs and annotates PDFs. Still no dependencies.
New
network.speed_test()measures download and upload speed, and latency idle and under load, against Apple's servers.result = macos.network.speed_test() result.download, result.upload # (43.61, 39.42): megabits per second
- What uses what:
- how much each process sent and received over the network;
- the power each process draws, in watts, with its disk reads and writes.
macos.system.network_usage(interval=2)[:5] macos.system.energy_usage()[0] # EnergyUsage(process='Xcode', watts=4.2, ...)
system.gpu_usage()tells how busy the GPU is, andsystem.disk_health()gives each disk's SMART status, which warns when a disk is about to fail.- PDF forms: read a form's fields and fill them in by name.
macos.pdf.fill_form("form.pdf", {"Full name": "Ana Souza", "Agree": True, "Plan": "Pro"}, "filled.pdf")
pdf.sign()puts a signature image on a page, in a corner or at a point, with filled-in fields still showing.pdf.add_text()writes text on a page as a text box, still editable in Preview, rotated pages included.macos.pdf.sign("filled.pdf", "signature.png", "signed.pdf") macos.pdf.add_text("contract.pdf", "Received on 29/09/2026", "stamped.pdf")
Fixes
screen.recordgives up after the recording plus a minute, instead of waiting for good on ascreencapturethat hangs.
Notes
- The speed test takes 15 to 60 seconds and moves a few hundred megabytes.
- Power is measured on Apple silicon; Intel Macs read 0. Energy use sees this user's processes; network use sees every user's.
- A signature is an image, not a cryptographic signature. Signing flattens the pages: fill the form first.
v1.15.0
pip install --upgrade pymacos1.15 looks into what your Mac is running: the processes and their CPU, the ports they listen on, the connections they have open, and the files they hold, like Activity Monitor and lsof. It also turns addresses into coordinates and back, opens the Maps app, and sets custom Finder icons. Still no dependencies.
New
- Processes: list them with their memory, processor time and % CPU, and end one.
busiest = sorted(macos.system.processes(cpu=True), key=lambda p: p.cpu_percent or 0)[-1] busiest.name, busiest.cpu_percent # ('Xcode', 187.5)
- Ports and connections: what listens on which port, and who each process is talking to.
macos.system.port_owner(8000).kill() # free the port for connection in macos.system.connections(): print(connection.process, connection.remote_address, connection.remote_port)
- Open files: which processes use a file, a folder or a disk. It tells why a disk won't eject.
macos.system.who_uses("/Volumes/Backup") # [Process(name='Preview', ...)]
macos.mapsturns an address into coordinates and back, through Apple's geocoding service, and opens the Maps app on a place or a route.macos.maps.geocode("Avenida Paulista, 1578, São Paulo")[0].latitude # -23.561463 macos.maps.directions("Aeroporto de Congonhas", by="transit")
- Custom icons: give a folder, a file or an app an icon, from an image or another item's icon.
macos.finder.set_icon("~/Projects", "logo.png")
Notes
- Processes, ports, connections and open files see every detail of this user's processes. Other users' processes, the system's included, show less: macOS keeps it from this user, as
lsofwithoutsudo. - Geocoding needs the internet but no permission. Apple limits how many requests an app makes in a short time.
- The pymacos wordmark has some room above its letters now.
v1.14.0
pip install --upgrade pymacos1.14 prints, saves your Mac's settings to a file and applies them to another Mac, and changes the resolution of your displays. It also covers the settings people look for most: macOS's own shortcuts, trackpad gestures, pointer acceleration, the menu bar's icons. Still no dependencies.
New
macos.settingssaves this Mac's settings to JSON and applies them to another: dotfiles for macOS. Only what differs changes, and the Dock and Finder restart once.json.dump(macos.settings.export(), open("my-mac.json", "w"), indent=2) macos.settings.apply(json.load(open("my-mac.json"))) # on the new Mac
macos.printerlists the printers, prints files and follows the queue, through CUPS.job = macos.printer.print_file("report.pdf", copies=2, two_sided=True) job.cancel()
- Displays: list and change the resolution and refresh rate, pick the main display, mirror a projector.
macos.screen.set_display_mode(1728, 1117) macos.screen.mirror(projector)
- Keyboard:
- turn macOS's shortcuts on and off, to free ⌘Space for Raycast or Alfred;
- give an app's menu items a shortcut;
- what the Fn (🌐) key does, inline predictions, the backlight's timeout.
macos.keyboard.set_system_shortcut("spotlight", False) macos.keyboard.set_app_shortcut("Safari", "Export as PDF…", "cmd+shift+e")
- Trackpad and mouse:
- every trackpad gesture, and the click pressure;
- the mouse's pointer acceleration.
macos.trackpad.set_gesture("mission_control", 4) macos.mouse.set_acceleration(False)
- Dock:
- folders and stacks;
- hot corners that wait for a key;
- dimmed hidden apps, only open apps, the launch animation;
- window grouping and switching spaces in Mission Control.
- Menu bar: show or hide Control Center's icons, and space them so more fit beside the notch.
- Finder: the desktop's icon size, spacing and order, and a Quit menu.
- Screen: Night Shift's schedule and strength, screenshots' file name and destination.
- Sound: the alert sound and its volume, and the interface's sound effects.
- System:
- units and temperature;
- Photos opening when an iPhone is connected;
- window animations, font smoothing.
macos.system.security_status()tells whether FileVault, the firewall, Gatekeeper and SIP are on, without an administrator's password.macos.apps.unquarantine()fixes "is damaged and can't be opened" for an app you trust.macos.defaults.restored()changes preferences for the length of awithblock, and puts them back exactly.
Fixes
- Tap to click and three-finger drag also write the copy System Settings keeps for this Mac.
Notes
- Changing the Dock or Finder settings restarts them, to apply the change. Some settings wait for the next login, as the docs say.
- The pymacos wordmark's "y" is now Python's yellow.
v1.13.0
pip install --upgrade pymacos1.13 puts System Settings in your scripts: the keyboard, trackpad, mouse, Dock, Finder, windows, appearance and menu bar, each with a reader and a setter. Set up a new Mac in a few lines, remap keys without Karabiner, and ask for Touch ID before a function runs. Still no dependencies.
New
- Keyboard settings: key repeat, press and hold, standard function keys, autocorrect, smart quotes and dashes, auto capitalization, the double-space period and full keyboard access.
macos.keyboard.set_key_repeat(0.03, delay=0.225) # System Settings' fastest macos.keyboard.set_press_and_hold(False)
keyboard.remap()makes a key act as another, on every keyboard, with no app to install.macos.keyboard.remap("caps_lock", "escape")
macos.trackpad: tap to click, natural scrolling, the pointer speed, three-finger drag and the secondary click. The mouse gets its pointer, scroll and double-click speeds.- Dock:
- hot corners;
- magnification, the minimize effect, open-app dots, recent apps;
- the autohide delay and animation;
- spacers between the apps;
- Mission Control's spaces.
macos.dock.set_hot_corner("bottom_right", "lock_screen") macos.dock.set_autohide_delay(0)
- Finder:
- the default view, the new window folder and the search scope;
- folders first, the full path in the title;
- the desktop icons and disks, the Library folder;
- the extension warning, old Trash items.
- Windows: what a double click on the title bar does, tiling by dragging to an edge, and clicking the wallpaper to show the desktop.
- Appearance: the accent color, auto Light/Dark mode, hiding the menu bar and the scroll bars.
macos.appearance.set_accent_color("purple")
- System:
- the menu bar clock, the battery percentage;
.DS_Storefiles on network and USB drives;- reopening windows, saving to iCloud by default, the expanded Save dialog.
- Screen: the screen saver delay and the screenshot thumbnail.
@macos.auth.requiredasks for Touch ID (or the password) each time a function is called, before it runs.@macos.auth.required("deploy to production") def deploy(): ...
macos.defaultstakescurrent_host=True, likedefaults -currentHost, for the settings kept per Mac.
Notes
- Most settings apply at once or when an app is reopened. The key repeat, pointer and scroll speeds,
.DS_Storefiles and spaces per display wait for the next login, as the docs say. - Changing the Dock or the Finder settings restarts them, to apply the change.
keyboard.remap()lasts until the Mac restarts: run it at login withmacos.scheduleto keep it.- None of these settings needs a permission.
v1.12.0
pip install --upgrade pymacos1.12 sets up your Mac from Python: preferences, the Dock, Finder, login items and default apps. It also adds Touch ID in your scripts, window snapping and searchable scanned PDFs. Still no dependencies.
New
macos.defaultsreads and changes the preferences of apps and of the system, like thedefaultscommand, but with Python types: no-boolor-intflags to get right.macos.defaults.write("com.apple.finder", "ShowPathbar", True) macos.defaults.read("NSGlobalDomain", "AppleInterfaceStyle") # 'Dark'
macos.dockhides, sizes and moves the Dock, and chooses the apps kept in it.macos.dock.set_autohide(True) macos.dock.add_app("Visual Studio Code")
- Finder and screenshot settings: show hidden files, extensions, the path and status bars, and where and how the screenshot shortcuts save.
macos.auth.confirm()asks for Touch ID (or the login password, or an Apple Watch) before a script does something sensitive.if macos.auth.confirm("unlock the production credentials"): token = macos.keychain.get("deploy", "prod")
Window.snap()fits a window to a half, a quarter, a third, or the whole screen, like Rectangle.macos.windows.focused().snap("left")
pdf.ocr()makes scanned PDFs searchable, adding the text Vision reads invisibly over each word.- Apps:
login_items(),add_login_item()andremove_login_item();set_default_for("md", "Visual Studio Code");install_from_dmg(), which installs the app from a disk image.
- System:
mount_image()andunmount_image();available_updates();cpu_usage()andmemory_usage();macos.time_machine, to start and follow backups.
- Finder:
compress()andextract()(zip, keeping tags and attributes), andquick_look().
Fixes
Window.set_frameresizes before moving, so macOS no longer cuts a window moved near the bottom of the screen.macos.eventsno longer frees its observer twice, which crashed a script callingevents.run()inside an autorelease pool.
Notes
- Since macOS 26, macOS asks the user to confirm a new default app, and
set_default_forwaits for the answer. - Changing the Dock or the Finder settings restarts them, to apply the change.
login_items()asks once for the Automation of System Events.
v1.11.0
pip install --upgrade pymacos1.11 continues with automation: find and click text on the screen, listen to the keyboard and mouse, and react to the network, USB devices and displays. Still no dependencies.
New
mouse.click_text()clicks a text wherever it shows on the screen, in any app, so a script doesn't depend on where a button is.screen.find_text()returns where the text is, andscreen.wait_for_text()waits for it to show up. They use Vision's text recognition, and the box surrounds just the matching word.macos.mouse.click_text("Accept") macos.screen.wait_for_text("Export complete", timeout=120)
keyboard.watch()andmouse.watch()yield every key pressed and every click, in any app. They only listen: the input still reaches the apps.for key in macos.keyboard.watch(): print(key.shortcut) # 'cmd+shift+k'
macos.eventsgains:network_changed: another Wi-Fi network, a cable, offline, back online;usb_connectedandusb_disconnected, with the device's name;displays_changed: a monitor connected, removed or rearranged.macos.events.on("usb_connected", lambda event: print("Plugged in:", event.device)) macos.events.run()
- Windows:
Window.screenshot()captures one window, even behind others.windows.wait_for()waits for a window to open.
- Finder:
finder.selection()gives the files selected in Finder, andfinder.current_folder()the folder in its front window.finder.watch()takes apattern("*.pdf").
- More:
clipboard.watch()yields every copy, for a clipboard history.system.wait_for_idle()andwait_for_activity()wait for you to step away, and come back.screen.color_at(x, y)reads the color of a point on the screen.
Notes
- The screen functions and
Window.screenshot()need the Screen Recording permission.click_text()also needs Accessibility, andkeyboard.watch()/mouse.watch()need Input Monitoring.finder.selection()asks once for the Automation of Finder. See https://macos.readthedocs.io/en/latest/permissions.html. keyboard.watch()doesn't see what's typed in password fields: macOS hides it.