mac_desktop_image.py (6025B)
1 #!/usr/bin/python 2 # This Source Code Form is subject to the terms of the Mozilla Public 3 # License, v. 2.0. If a copy of the MPL was not distributed with this 4 # file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 """ 7 mac_desktop_image.py 8 9 Mac-specific utility to get/set the desktop background image or check that 10 the current background image path matches a provided path. 11 12 Depends on Objective-C python binding imports which are in the python import 13 paths by default when using macOS's /usr/bin/python. 14 15 Includes generous amount of logging to aid debugging for use in automated tests. 16 """ 17 18 import argparse 19 import logging 20 import os 21 import sys 22 23 # 24 # These Objective-C bindings imports are included in the import path by default 25 # for the Mac-bundled python installed in /usr/bin/python. They're needed to 26 # call the Objective-C API's to set and retrieve the current desktop background 27 # image. 28 # 29 from AppKit import NSScreen, NSWorkspace 30 from Cocoa import NSURL 31 32 33 def main(): 34 parser = argparse.ArgumentParser( 35 description="Utility to print, set, or " 36 + "check the path to image being used as " 37 + "the desktop background image. By " 38 + "default, prints the path to the " 39 + "current desktop background image." 40 ) 41 parser.add_argument( 42 "-v", 43 "--verbose", 44 action="store_true", 45 help="print verbose debugging information", 46 default=False, 47 ) 48 group = parser.add_mutually_exclusive_group() 49 group.add_argument( 50 "-s", 51 "--set-background-image", 52 dest="newBackgroundImagePath", 53 required=False, 54 help="path to the new background image to set. A zero " 55 + "exit code indicates no errors occurred.", 56 default=None, 57 ) 58 group.add_argument( 59 "-c", 60 "--check-background-image", 61 dest="checkBackgroundImagePath", 62 required=False, 63 help="check if the provided background image path " 64 + "matches the provided path. A zero exit code " 65 + "indicates the paths match.", 66 default=None, 67 ) 68 args = parser.parse_args() 69 70 # Using logging for verbose output 71 if args.verbose: 72 logging.basicConfig(level=logging.DEBUG) 73 else: 74 logging.basicConfig(level=logging.CRITICAL) 75 logger = logging.getLogger("desktopImage") 76 77 # Print what we're going to do 78 if args.checkBackgroundImagePath is not None: 79 logger.debug( 80 "checking provided desktop image %s matches current " 81 "image" % args.checkBackgroundImagePath 82 ) 83 elif args.newBackgroundImagePath is not None: 84 logger.debug("setting image to %s " % args.newBackgroundImagePath) 85 else: 86 logger.debug("retrieving desktop image path") 87 88 focussedScreen = NSScreen.mainScreen() 89 if not focussedScreen: 90 raise RuntimeError("mainScreen error") 91 92 ws = NSWorkspace.sharedWorkspace() 93 if not ws: 94 raise RuntimeError("sharedWorkspace error") 95 96 # If we're just checking the image path, check it and then return. 97 # A successful exit code (0) indicates the paths match. 98 if args.checkBackgroundImagePath is not None: 99 # Get existing desktop image path and resolve it 100 existingImageURL = getCurrentDesktopImageURL(focussedScreen, ws, logger) 101 existingImagePath = existingImageURL.path() 102 existingImagePathReal = os.path.realpath(existingImagePath) 103 logger.debug("existing desktop image: %s" % existingImagePath) 104 logger.debug("existing desktop image realpath: %s" % existingImagePath) 105 106 # Resolve the path we're going to check 107 checkImagePathReal = os.path.realpath(args.checkBackgroundImagePath) 108 logger.debug("check desktop image: %s" % args.checkBackgroundImagePath) 109 logger.debug("check desktop image realpath: %s" % checkImagePathReal) 110 111 if existingImagePathReal == checkImagePathReal: 112 print("desktop image path matches provided path") 113 return True 114 115 print("desktop image path does NOT match provided path") 116 return False 117 118 # Log the current desktop image 119 if args.verbose: 120 existingImageURL = getCurrentDesktopImageURL(focussedScreen, ws, logger) 121 logger.debug("existing desktop image: %s" % existingImageURL.path()) 122 123 # Set the desktop image 124 if args.newBackgroundImagePath is not None: 125 newImagePath = args.newBackgroundImagePath 126 if not os.path.exists(newImagePath): 127 logger.critical("%s does not exist" % newImagePath) 128 return False 129 if not os.access(newImagePath, os.R_OK): 130 logger.critical("%s is not readable" % newImagePath) 131 return False 132 133 logger.debug("new desktop image to set: %s" % newImagePath) 134 newImageURL = NSURL.fileURLWithPath_(newImagePath) 135 logger.debug("new desktop image URL to set: %s" % newImageURL) 136 137 status = False 138 (status, error) = ws.setDesktopImageURL_forScreen_options_error_( 139 newImageURL, focussedScreen, None, None 140 ) 141 if not status: 142 raise RuntimeError("setDesktopImageURL error") 143 144 # Print the current desktop image 145 imageURL = getCurrentDesktopImageURL(focussedScreen, ws, logger) 146 imagePath = imageURL.path() 147 imagePathReal = os.path.realpath(imagePath) 148 logger.debug("updated desktop image URL: %s" % imageURL) 149 logger.debug("updated desktop image path: %s" % imagePath) 150 logger.debug("updated desktop image path (resolved): %s" % imagePathReal) 151 print(imagePathReal) 152 return True 153 154 155 def getCurrentDesktopImageURL(focussedScreen, workspace, logger): 156 imageURL = workspace.desktopImageURLForScreen_(focussedScreen) 157 if not imageURL: 158 raise RuntimeError("desktopImageURLForScreen returned invalid URL") 159 if not imageURL.isFileURL(): 160 logger.warning("desktop image URL is not a file URL") 161 return imageURL 162 163 164 if __name__ == "__main__": 165 if not main(): 166 sys.exit(1) 167 else: 168 sys.exit(0)