Skip to content

Instantly share code, notes, and snippets.

@homebysix
Last active June 4, 2023 20:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save homebysix/077b373264a101d84a3f8cb48e9bca84 to your computer and use it in GitHub Desktop.
Save homebysix/077b373264a101d84a3f8cb48e9bca84 to your computer and use it in GitHub Desktop.
docklib_example.py
#!/usr/local/bin/managed_python3
"""docklib_example.py
This example script demonstrates how Mac admins can use the docklib module to
manage the default state of Mac users' Docks. The script is meant to run at
login in user context using a tool like Outset or a custom LaunchAgent.
For details, see: https://www.elliotjordan.com/posts/resilient-docklib/
"""
__author__ = "Elliot Jordan"
__version__ = "1.0.2"
import getpass
import logging
import os
import shutil
import socket
import subprocess
import sys
from datetime import datetime
from time import sleep
from urllib.parse import unquote, urlparse
from docklib import Dock
# Set up logging config
logging.basicConfig(
level=logging.INFO, format="%(asctime)s [%(filename)s] %(levelname)s: %(message)s"
)
def wait_for_dock(max_time=60):
"""Wait for Dock to launch. Bail out if we reach max_time seconds."""
curr_time = max_time
check_cmd = ["/usr/bin/pgrep", "-qx", "Dock"]
# Check every 1 second for the Dock process
while subprocess.run(check_cmd, check=False).returncode != 0:
if curr_time <= 0:
# We reached our max_time
logging.error("Dock did not start within %s seconds. Exiting.", max_time)
sys.exit(1)
elif curr_time % 5 == 0:
# Provide status output every 5 seconds
logging.info("Waiting up to %d seconds for Dock to start...", curr_time)
# Decrement count and wait one second before looping
curr_time -= 1
sleep(1)
def is_default(dock):
"""Return True if the dock is uncustomized from macOS default; False otherwise."""
# List of default Dock items from recent versions of macOS. Sources:
# /System/Library/CoreServices/Dock.app/Contents/Resources/default.plist
# /System/Library/CoreServices/Dock.app/Contents/Resources/com.apple.dockfixup.plist
# https://512pixels.net/projects/aqua-screenshot-library/
# fmt: off
apple_default_apps = [
"App Store", "Calendar", "Contacts", "FaceTime", "Finder", "Freeform",
"iBooks", "iCal", "iTunes", "Keynote", "Launchpad", "Mail", "Maps",
"Messages", "Mission Control", "Music", "News", "Notes", "Numbers",
"Pages", "Photo Booth", "Photos", "Podcasts", "Reminders", "Safari",
"Siri", "System Preferences", "TV",
]
# fmt: on
# Gather a list of default/custom apps for script output
apps = {"default": [], "custom": []}
for item in dock.items.get("persistent-apps", []):
try:
# Compare the path, not the label, due to possible localization
pathurl = item["tile-data"]["file-data"]["_CFURLString"]
path = urlparse(unquote(pathurl)).path.rstrip("/")
app_name = os.path.split(path)[-1].replace(".app", "")
# Add each app into either "custom" or "default" list
if app_name in apple_default_apps:
apps["default"].append(app_name)
else:
apps["custom"].append(app_name)
except Exception as err:
logging.error("Exception encountered when processing an item:\n%s", item)
logging.error("Raising traceback and leaving Dock unchanged...")
raise err
logging.info("Apple default apps: %s", ", ".join(apps["default"]))
logging.info("Custom apps: %s", ", ".join(apps["custom"]))
# Dock is default if no custom apps were found
return not apps["custom"]
def make_backup():
"""Make a backup of the current Dock configuration prior to applying changes."""
dock_plist = os.path.expanduser("~/Library/Preferences/com.apple.dock.plist")
backup_dir = os.path.expanduser("~/Library/PretendCo/backup/")
if os.path.isfile(dock_plist):
logging.info("Making a backup of the current Dock config in %s...", backup_dir)
if not os.path.isdir(backup_dir):
os.makedirs(backup_dir)
datestamp = datetime.strftime(datetime.now(), "%Y-%m-%d %H-%M-%S")
shutil.copy(
dock_plist,
os.path.join(backup_dir, "com.apple.dock (%s).plist" % datestamp),
)
def main():
"""Main process."""
# Wait maximum 60 seconds for Dock to start
wait_for_dock(60)
logging.info("Loading current Dock...")
dock = Dock()
if not is_default(dock):
logging.info("Dock appears to be customized already. Exiting.")
sys.exit(0)
logging.info("Dock is not customized.")
hostname = socket.gethostname()
logging.info("Hostname: %s", hostname)
username = getpass.getuser()
logging.info("Current user: %s", username)
# Define list of apps and autohide based on hostname pattern
if hostname.startswith("mac-build") or username == "build":
logging.info("Setting build Dock...")
desired_apps = [
"/Applications/Xcode.app",
"/System/Applications/Utilities/Activity Monitor.app",
"/System/Applications/Utilities/Console.app",
"/System/Applications/Utilities/Terminal.app",
]
elif hostname.startswith("mac-dash") or username == "dashboard":
logging.info("Setting dashboard Dock...")
desired_apps = [
"/Applications/Google Chrome.app",
]
dock.autohide = True
elif hostname.startswith("mac-av") or username == "av":
logging.info("Setting av Dock...")
desired_apps = [
"/System/Applications/QuickTime Player.app",
"/System/Applications/VLC.app",
]
dock.autohide = True
elif username == "itadmin":
logging.info("Setting itadmin Dock...")
desired_apps = [
"/System/Applications/Launchpad.app",
"/Applications/Google Chrome.app",
"/Applications/Malwarebytes.app",
"/System/Applications/Utilities/Activity Monitor.app",
"/System/Applications/Utilities/Console.app",
"/System/Applications/Utilities/Disk Utility.app",
"/System/Applications/Utilities/Terminal.app",
]
else:
# Generic Dock configuration for users/hostnames not specified above
logging.info("Setting user Dock...")
desired_apps = [
"/System/Applications/Launchpad.app",
"/Applications/Google Chrome.app",
"/Applications/Microsoft Outlook.app",
"/Applications/Slack.app",
"/Applications/Managed Software Center.app",
"/System/Applications/System Preferences.app",
]
# Set persistent-apps as desired
dock.items["persistent-apps"] = [
dock.makeDockAppEntry(x) for x in desired_apps if os.path.isdir(x)
]
# Back up existing dock before making changes
make_backup()
logging.info("Saving and relaunching Dock...")
dock.save()
logging.info("Done.")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment