class rsimguiMainWindowController(NSWindowController): application = IBOutlet() runButton = IBOutlet() stepButton = IBOutlet() numSlotsTextField = IBOutlet() runtimeTextField = IBOutlet() def setRunButtonsEnabled_(self, state): self.runButton.setEnabled_(state) self.stepButton.setEnabled_(state) def setEditingEnabled_(self, state): self.numSlotsTextField.setEnabled_(state) #self.runtimeTextField.setEnabled_(state) def runTime(self): return self.runtimeTextField.intValue() def numSlots(self): return self.numSlotsTextField.intValue() def resetSimulation_(self, sender): self.setRunButtonsEnabled_(True) self.setEditingEnabled_(True) self.application.resetSimulation() def runSimulation_(self, sender): runtime = self.runTime() numSlots = self.numSlots() self.application.runSimulation(runtime, numSlots) def stepSimulation_(self, sender): numSlots = self.numSlots() self.application.stepSimulation(numSlots)
class controller(NSWindowController): iTunesManagesMyLibrary = ivar('iTunesManagesMyLibrary') #iTunesManagesMyLibrary = ivar() iTunesManagesMyLibraryMenuItem = IBOutlet() iTunesLibraryLocationMenuItem = IBOutlet() iTunesLibraryLocationMenuItemEnabled = ivar('iTunesLibraryLocationMenuItemEnabled') def awakeFromNib(self): #NSLog('awakeFromNib') self.iTunesManagesMyLibrary = config.get_config_option('iTunesManagesMyLibrary') self.iTunesManagesMyLibraryMenuItem.setState_(self.iTunesManagesMyLibrary) self.refreshMenuEnable() #@accessor #def iTunesManagesMyLibraryValue(self): # return self.iTunesManagesMyLibrary @accessor def setITunesManagesMyLibrary_(self, value): #NSLog('setITunesManagesMyLibrary_') self.iTunesManagesMyLibrary = value #NSLog(str(self.iTunesManagesMyLibrary)) config.set_config_option('iTunesManagesMyLibrary', value) self.refreshMenuEnable() @IBAction def setLibraryLocation_(self, sender): #NSLog('setLibraryLocation_') panel = NSOpenPanel.openPanel() panel.setCanChooseDirectories_(YES) panel.setAllowsMultipleSelection_(NO) panel.setCanChooseFiles_(NO) old_path = config.get_config_option('iTunesLibraryLocation') ret = panel.runModalForDirectory_file_types_(old_path, None, None) #NSLog(str(ret)) if ret: path = panel.filenames()[0] config.set_config_option('iTunesLibraryLocation', path) else: # Canceled pass def refreshMenuEnable(self): #NSLog('refreshMenuEnable') if self.iTunesManagesMyLibrary: #NSLog('NO') #self.iTunesLibraryLocationMenuItem.setEnabled_(NO) self.iTunesLibraryLocationMenuItemEnabled = NO else: #NSLog('YES') #self.iTunesLibraryLocationMenuItem.setEnabled_(YES) self.iTunesLibraryLocationMenuItemEnabled = YES @IBAction def toggleITunesManagesMyLibrary_(self, sender): #NSLog('toggleITunesManagesMyLibrary_') #self.refreshMenuEnable() pass
class LauncherWindow(NSWindow): # command list, currently implemented as a simple multilned label label = IBOutlet() # command text field textField = IBOutlet() # regex for arithmetic expressions calc_re = re.compile(r'[\d\(\)\.\+\-\*/\s]{2}[\d\(\)\.\+\-\*/\s]') def awakeFromNib(self): NSLog('awakeFromNib') self.selected_cmd = None def controlTextDidChange_(self, notification): text = self.textField.stringValue() # try to eval arithmetic expressions or lookup commands otherwise if self.calc_re.match(text): try: self.label.setStringValue_(eval(text)) except: pass else: self.update_commands_list(prefix=text.split(' ')[0]) @IBAction def onEnter_(self, sender): # command was selected from the commands list if self.selected_cmd: cmd = self.selected_cmd[1] arg = self.textField.stringValue()[len(self.selected_cmd[0]):] self.selected_cmd = None # command is selected from text field else: if not self.textField.stringValue(): return split = self.textField.stringValue().split(' ', 1) cmd = chili.get_command(split[0]) arg = split[1] if len(split) > 1 else None if cmd: # run command self.performSelectorInBackground_withObject_( objc.selector(self.runCmd_, signature='v@:@'), (cmd, arg)) def runCmd_(self, cmdArg): try: # call command function cmdArg[0](cmdArg[1]) except Exception, e: self.windowController().report_exception(e)
class MSUMainWindowController(NSWindowController): ''' Controls the main window ''' theTabView = IBOutlet() theWindow = IBOutlet() def windowShouldClose_(self, sender): # just quit NSApp.terminate_(self)
class MainWindowController(NSWindowController): labelField = IBOutlet() quitButton = IBOutlet() def awakeFromNib(self): self.labelField.setStringValue_("Hello, World!") @IBAction def quitButtonClicked_(self, sender): NSApp.terminate_(self)
class ___PROJECTNAMEASIDENTIFIER___AppDelegate(NSObject): window = IBOutlet() def applicationDidFinishLaunching_(self, sender): pass def applicationWillTerminate_(self, notification): pass
class MTAppDelegate(NSObject): main_window_controller = IBOutlet() def applicationDidFinishLaunching_(self, sender): NSLog("Application did finish launching.") self.main_window_controller.initMainWindow() def application_openFile_(self, app, filename): self.main_window_controller.getApplicationInfo_(filename)
class LLAppDelegate(NSObject): logWindowController = IBOutlet() prefs = NSUserDefaults.standardUserDefaults() def applicationDidFinishLaunching_(self, sender): self.prefs.registerDefaults_({ u"logfile": u"/var/log/system.log", }) logfile = self.prefs.stringForKey_(u"logfile") self.logWindowController.showLogWindow_(logfile) self.logWindowController.watchLogFile_(logfile)
class EkoAppDelegate(NSObject): targetURL = IBOutlet() namespace = IBOutlet() requestView = IBOutlet() @IBAction def updateURLs_(self, sender): target_url = self.targetURL.stringValue() namespace = self.namespace.stringValue() with object_lock(self.request_items): self.request_items[:] = [] self.requestView.reloadData() if hasattr(self, "client_thread"): self.client_thread.cancel() self.client_thread = EkoClientThread.newAtURL_usingNamespace_withItems_( target_url, namespace, self.request_items) self.client_thread.start() ##self.client_thread = NSThread.new() ##self.client_thread.initWithTarget_selector_object_( ## None, selector(runEkoClientThread_, isClassMethod=True), ## (target_url, namespace, self.request_items)) ##self.client_thread.start() #NSThread.detachNewThreadSelector_toTarget_withObject_( # selector(MyTestClass.myClassMeth_, # isClassMethod=True, # argumentTypes="s"), # MyTestClass, # "hello") #t = NSThread.new() #t.initWithTarget_selector_object_(None, selector(my_test_target), "hello") #t.start() #self.t = t def applicationDidFinishLaunching_(self, sender): logging.basicConfig(level=logging.DEBUG) logging.root.addHandler(NSLogHandler()) #CFRunLoopSourceCreate(None, 0, ()) self.request_items = NSMutableArray.new() self.requestView.setDataSource_( RequestItemDataSource.newWithSource_(self.request_items))
class MSUAppDelegate(NSObject): '''Implements some NSApplicationDelegateProtocol methods''' # since this subclasses NSObject, # it doesn't have a Python __init__method # pylint: disable=no-init # several delegate methods pass a 'sender' object that we don't use # pylint: disable=unused-argument statusWindowController = IBOutlet() logWindowController = IBOutlet() def applicationWillFinishLaunching_(self, sender): '''NSApplicationDelegate method Sent by the default notification center immediately before the application object is initialized.''' # pylint: disable=no-self-use consoleuser = munki.getconsoleuser() if consoleuser == None or consoleuser == u"loginwindow": # don't show menu bar NSMenu.setMenuBarVisible_(NO) # make sure we're active NSApp.activateIgnoringOtherApps_(YES) def applicationDidFinishLaunching_(self, sender): '''NSApplicationDelegate method Sent by the default notification center after the application has been launched and initialized but before it has received its first event.''' # Prevent automatic relaunching at login on Lion+ if NSApp.respondsToSelector_('disableRelaunchOnLogin'): NSApp.disableRelaunchOnLogin() # show the default initial view self.statusWindowController.initStatusSession()
class TextureItem(NSCollectionViewItem): icon = IBOutlet() def loadImage(self): imageobject = io.BytesIO() imgdata = self.image.copy() imgdata.thumbnail((88, 88), Image.ANTIALIAS) imgdata.save(imageobject, format='png') imageobject.seek(0) dataout = imageobject.read() d = NSData.alloc().initWithBytes_length_(dataout, len(dataout)) nsimg = NSImage.alloc().initWithData_(d) self.icon.setImage_(nsimg) def setKey_image_rotated_position_index_(self, key, image, rotated, position, index): self.key = key self.image = image self.rotated = rotated self.position = position self.index = index self.loadImage() def viewDidLoad(self): super(NSCollectionViewItem, self).viewDidLoad() @IBAction def changeIcon_(self, sender): panel = NSOpenPanel.openPanel() panel.setCanChooseFiles_(YES) panel.setCanChooseDirectories_(NO) panel.setAllowsMultipleSelection_(NO) panel.setAllowedFileTypes_(NSImage.imageTypes()) clicked = panel.runModal() if clicked == NSFileHandlingPanelOKButton: self.image = Image.open(panel.URLs()[0].path()).resize( self.image.size) self.loadImage() NSNotificationCenter.defaultCenter( ).postNotificationName_object_userInfo_("SetIcon", self, { "image": self.image, "index": self.index })
class MainWindowController(NSWindowController): spokenTextField = IBOutlet() @IBAction def speakButtonClicked_(self, sender): NSLog(u"Speak button clicked") NSLog(self.spokenTextField.stringValue()) scriptPath = NSBundle.mainBundle().pathForResource_ofType_( u"Script", u"sh") if scriptPath: scriptProcess = NSTask.launchedTaskWithLaunchPath_arguments_( scriptPath, [self.spokenTextField.stringValue()]) scriptProcess.waitUntilExit() else: NSLog(u"Couldn't find Script.sh")
class ViewController(NSViewController): label = IBOutlet() fileDisplay = IBOutlet() graphicSize = IBOutlet() blockDensity = IBOutlet() blockSize = IBOutlet() countLabel = IBOutlet() imagePreview = IBOutlet() drawBtn = IBOutlet() errCorrection = IBOutlet() def previewSVG(self): try: """drawing = svg2rlg(self.chosenFile.UTF8String()) new_bites = io.BytesIO() renderPM.drawToFile(drawing,new_bites,fmt='png')""" with wand.image.Image() as imag: with wand.color.Color('transparent') as background_color: library.MagickSetBackgroundColor(imag.wand, background_color.resource) imag.read(filename=self.chosenFile.UTF8String()) new_bites = io.BytesIO(imag.make_blob("png32")) im = Image.open(new_bites) im = im.crop(im.getbbox()) im = im.resize((im.width * 3, im.height * 3)) #im.thumbnail((133,133),Image.ANTIALIAS) im.save(join(expanduser("~"), ".GGD2_prev.png"), format='PNG') img = NSImage.alloc().initWithContentsOfFile_( join(expanduser("~"), ".GGD2_prev.png")) self.imagePreview.setImage_(img) except Exception, e: print(e)
class PMController(NSWindowController): titleField = IBOutlet() urlField = IBOutlet() bodyField = IBOutlet() charCountLabel = IBOutlet() actionButton = IBOutlet() twitterCheckbox = IBOutlet() gplusCheckbox = IBOutlet() # Confirm token sheet. confirmTokenSheet = IBOutlet() confirmTokenField = IBOutlet() # Publish log sheet. publishLogWindow = IBOutlet() publishLogField = IBOutlet() publishCancelButton = IBOutlet() twitter = TwitterSyndicator() gplus = GPlusSyndicator() ltp = LinkTextProcessor() def awakeFromNib(self): NSLog("Awake from nib.") self.setPreviewMode(True) self.bodyField.setDelegate_(self) self.urlField.setDelegate_(self) self.titleField.setDelegate_(self) # Style the bodyField. self.bodyField.setFont_(NSFont.fontWithName_size_("Monaco", 13)) self.bodyField.setRichText_(NO) self.bodyField.setUsesFontPanel_(NO) # Authenticate to twitter if we can. if self.twitter.is_authenticated(): self.twitter.login() self.twitterCheckbox.setState_(NSOnState) self.ltp.syndicators.append(self.twitter) # Authenticate to G+ if we can. if self.gplus.is_authenticated(): self.gplus.login() self.gplusCheckbox.setState_(NSOnState) self.ltp.syndicators.append(self.gplus) # Listen to the NSApplicationWillTerminateNotification. center = NSNotificationCenter.defaultCenter() center.addObserver_selector_name_object_(self, "applicationWillTerminateNotification:", NSApplicationWillTerminateNotification, None) self.setupStatusBar() self.didPublish = False self.didPreview = False def applicationWillTerminateNotification_(self, notification): NSLog("applicationWillTerminateNotification_") @IBAction def post_(self, sender): url = self.urlField.stringValue() title = self.titleField.stringValue() body = self.bodyField.string() # TODO(smus): Validate all fields. if self.isPreview: # Relinquish control to the link text processor for a preview. self.ltp.set_content(url, title, body) # Show the preview in a browser window. self.ltp.preview_content() # Go into publish mode. self.setPreviewMode(False) self.didPreview = True else: # If in publish mode, push to S3, publish to twitter & G+. print 'Syndicators: ' + str(self.ltp.syndicators) self.ltp.publish_syndicate() self.didPublish = True self.hideWindow() @IBAction def cancel_(self, sender): NSLog(u"Cancel") # Remove the link if one was created. self.ltp.clean() # Exit the application. self.hideWindow() @IBAction def twitterChecked_(self, sender): if self.twitterCheckbox.state() == NSOnState: self.twitter.login() if not self.twitter.is_authenticated(): self.currentService = self.twitter self.showTheSheet_(sender) isTwitterEnabled = bool(self.twitterCheckbox.state() == NSOnState) if isTwitterEnabled: self.ltp.syndicators.append(self.twitter) else: self.ltp.syndicators.remove(self.twitter) @IBAction def gplusChecked_(self, sender): if self.gplusCheckbox.state() == NSOnState: self.gplus.login() print self.gplus.is_authenticated() if not self.gplus.is_authenticated(): self.currentService = self.gplus self.showTheSheet_(sender) isGPlusEnabled = bool(self.gplusCheckbox.state() == NSOnState) if isGPlusEnabled: self.ltp.syndicators.append(self.gplus) else: self.ltp.syndicators.remove(self.gplus) @IBAction def confirmToken_(self, sender): NSLog("Confirmed token") verifier = self.confirmTokenField.stringValue() if self.currentService == self.twitter: self.twitter.confirm_verifier(verifier) elif self.currentService == self.gplus: self.gplus.confirm_verifier(verifier) self.endTheSheet_(sender) @IBAction def cancelToken_(self, sender): self.endTheSheet_(sender) def doPreview_(self, sender): NSLog("Doing a preview.") self.ltp.preview() def doPublish_(self, sender): NSLog("Doing a publish.") self.ltp.publish() def doLink_(self, sender): NSLog("Doing a link.") self.showWindow() def showTheSheet_(self, sender): NSApp.beginSheet_modalForWindow_modalDelegate_didEndSelector_contextInfo_( self.confirmTokenSheet, NSApp.mainWindow(), self, None, None) def endTheSheet_(self, sender): NSApp.endSheet_(self.confirmTokenSheet) self.confirmTokenSheet.orderOut_(sender) def hideWindow(self): # Hide the window. self.window().orderOut_(self) # Cleanup if the app did not publish (to keep state consistent). if not self.didPublish: self.ltp.clean() # Reset the internal state. self.didPreview = False self.didPublish = False # Clear all of the UI elements. self.titleField.setStringValue_('') self.urlField.setStringValue_('') self.bodyField.setString_('') def showWindow(self): self.window().makeKeyAndOrderFront_(self) self.window().setLevel_(NSStatusWindowLevel) # Give the window focus. NSApp.activateIgnoringOtherApps_(YES) def setPreviewMode(self, isPreview): self.isPreview = isPreview # Update the UI. self.actionButton.setTitle_(isPreview and "Preview" or "Publish") def controlTextDidChange_(self, notification): # If any of the text changes, go into preview mode. self.setPreviewMode(True) self.enableButtonIfValid() changedField = notification.object() if changedField == self.urlField: # If the URL field, try to infer the title. url = self.urlField.stringValue() title = self.inferTitleFromURL(url) if title: NSLog("Setting title to be: " + title) self.titleField.setStringValue_(title) def textDidChange_(self, notification): # Go back to preview mode. self.setPreviewMode(True) self.enableButtonIfValid() # If the body text changes, update the count. text = self.bodyField.string() self.charCountLabel.setStringValue_(len(text)) NSLog(u"Length: %d" % len(text)) def inferTitleFromURL(self, url): from mechanize import Browser from urlparse import urlparse try: result = urlparse(url) if result.scheme not in ['http', 'https']: return None browser = Browser() browser.open(url) return unicode(browser.title(), 'utf8') except Exception as e: NSLog("Exception: " + str(e)) return None def quit(self): NSApp.performSelector_withObject_afterDelay_("terminate:", None, 0); def enableButtonIfValid(self): # If all of the text fields have content in them, enable the button. # Otherwise, disable it. url = unicode(self.urlField.stringValue()) title = unicode(self.titleField.stringValue()) body = unicode(self.bodyField.string()) isEnabled = url and title and body self.actionButton.setEnabled_(isEnabled and YES or NO) def setupStatusBar(self): statusbar = NSStatusBar.systemStatusBar() statusitem = statusbar.statusItemWithLength_(20).retain() icon = NSImage.imageNamed_('status') icon.setSize_((20, 20)) statusitem.setImage_(icon) iconHighlight = NSImage.imageNamed_('status-hi') iconHighlight.setSize_((20, 20)) statusitem.setAlternateImage_(iconHighlight) statusitem.setHighlightMode_(1) # TODO: Put this whole menu creation stuff into interface builder! menu = NSMenu.alloc().init() linkMenu = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_('Post a Link', 'doLink:', '') linkMenu.setTarget_(self); # Make it possible to invoke the link menu via a keyboard shortcut. linkMenu.setKeyEquivalentModifierMask_(NSShiftKeyMask | NSCommandKeyMask) linkMenu.setKeyEquivalent_('l') menu.addItem_(linkMenu) menu.addItem_(NSMenuItem.separatorItem()) previewMenu = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_('Update Preview', 'doPreview:', '') previewMenu.setTarget_(self); menu.addItem_(previewMenu) publishMenuItem = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_('Publish', None, '') publishMenu = NSMenu.alloc().init(); s3MenuItem = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_('S3', 'doPublish:', '') s3MenuItem.setTarget_(self); publishMenu.addItem_(s3MenuItem) publishMenuItem.setSubmenu_(publishMenu) menu.addItem_(publishMenuItem) menu.addItem_(NSMenuItem.separatorItem()) quitMenu = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_('Quit', 'terminate:', '') menu.addItem_(quitMenu) statusitem.setMenu_(menu) menu.release(); @IBAction def hidePublishLog_(self, sender): self.publishLogWindow.orderOut_(self); def showPublishLog(self): self.publishLogWindow.makeKeyAndOrderFront_(self);
class IEDController(NSObject): mainWindow = IBOutlet() sourceBox = IBOutlet() sourceImage = IBOutlet() sourceLabel = IBOutlet() updateController = IBOutlet() addPkgController = IBOutlet() logController = IBOutlet() buildButton = IBOutlet() buildProgressWindow = IBOutlet() buildProgressPhase = IBOutlet() buildProgressBar = IBOutlet() buildProgressMessage = IBOutlet() advancedWindow = IBOutlet() volumeName = IBOutlet() volumeSize = IBOutlet() finalizeAsrImagescan = IBOutlet() filesystem = IBOutlet() filesystemApfs = IBOutlet() filesystemHfs = IBOutlet() def awakeFromNib(self): LogDebug("awakeFromNib") # Initialize UI. self.buildProgressBar.setMaxValue_(100.0) self.buildProgressMessage.setStringValue_("") self.setSourcePlaceholder() # We're a delegate for the drag and drop target, protocol: # (void)acceptInstaller:(NSString *)path self.sourceBox.setDelegate_(self) self.sourceImage.setDelegate_(self) self.sourceLabel.setDelegate_(self) # We're a delegate for the update controller, protocol: # (void)updateControllerChanged if self.updateController: self.updateController.setDelegate_(self) else: alert = NSAlert.alloc().init() alert.setMessageText_("Unable to initialize update controller") alert.setInformativeText_( "AutoDMG will not function correctly, check the log for details." ) alert.runModal() # Main workflow logic. self.workflow = IEDWorkflow.alloc().initWithDelegate_(self) # Enabled state for main window. self.enabled = True # When busy is true quitting gets a confirmation prompt. self._busy = False # Currently loaded template. self.templateURL = None # Filesystem selection. self.filesystem.setAutoenablesItems_(False) self.filesystemHfs.setRepresentedObject_("hfs") self.filesystemApfs.setRepresentedObject_("apfs") if IEDUtil.hostMajorVersion() < 13: self.filesystem.selectItem_(self.filesystemHfs) self.filesystemApfs.setEnabled_(False) else: self.filesystem.selectItem_(self.filesystemApfs) self.filesystemApfs.setEnabled_(True) # Methods to communicate with app delegate. def cleanup(self): self.workflow.cleanup() def busy(self): return self._busy def setBusy_(self, busy): self._busy = busy if busy: self.disableMainWindowControls() else: self.enableMainWindowControls() # Helper methods. def setSourcePlaceholder(self): self.sourceLabel.setStringValue_("Drop %s Installer Here" % (IEDUtil.hostOSName())) image = NSImage.imageNamed_("Installer Placeholder 10.%d" % IEDUtil.hostMajorVersion()) if not image: image = NSImage.imageNamed_("Installer Placeholder") self.sourceImage.animator().setImage_(image) self.sourceImage.animator().setAlphaValue_(1.0) def validateMenuItem_(self, menuItem): return not self.busy() def displayAlert_text_(self, message, text): LogDebug("Displaying alert: %@ (%@)", message, text) alert = NSAlert.alloc().init() alert.setMessageText_(message) alert.setInformativeText_(text) alert.runModal() def disableMainWindowControls(self): self.enabled = False self.sourceBox.stopAcceptingDrag() self.sourceImage.stopAcceptingDrag() self.sourceLabel.stopAcceptingDrag() self.updateController.disableControls() self.addPkgController.disableControls() self.buildButton.setEnabled_(False) def enableMainWindowControls(self): self.enabled = True self.sourceBox.startAcceptingDrag() self.sourceImage.startAcceptingDrag() self.sourceLabel.startAcceptingDrag() self.updateController.enableControls() self.addPkgController.enableControls() self.updateBuildButton() def updateBuildButton(self): buildEnabled = self.enabled and \ self.workflow.hasSource() and \ self.updateController.allUpdatesDownloaded() self.buildButton.setEnabled_(buildEnabled) # Act on user dropping an installer. def acceptSource_(self, path): self.setBusy_(True) self.workflow.setSource_(path) # Workflow delegate methods. def detachFailed_details_(self, dmgPath, details): self.displayAlert_text_("Failed to detach %s" % dmgPath, details) def ejectingSource(self): self.sourceImage.animator().setAlphaValue_(0.5) self.sourceLabel.setStringValue_("Ejecting") self.sourceLabel.setTextColor_(NSColor.disabledControlTextColor()) def examiningSource_(self, path): self.foundSourceForIcon_(path) self.sourceLabel.setStringValue_("Examining") self.sourceLabel.setTextColor_(NSColor.disabledControlTextColor()) def foundSourceForIcon_(self, path): icon = NSWorkspace.sharedWorkspace().iconForFile_(path) icon.setSize_(NSMakeSize(256.0, 256.0)) tiff = icon.TIFFRepresentation() image = NSImage.alloc().initWithData_(tiff) self.sourceImage.animator().setAlphaValue_(1.0) self.sourceImage.animator().setImage_(image) def sourceSucceeded_(self, info): self.installerName = info["name"] self.installerVersion = info["version"] self.installerBuild = info["build"] self.sourceLabel.setStringValue_( "%s %s %s" % (info["name"], info["version"], info["build"])) self.sourceLabel.setTextColor_(NSColor.controlTextColor()) self.updateController.loadProfileForVersion_build_( info["version"], info["build"]) template = info["template"] if template: LogInfo("Template found in image: %@", repr(template)) # Don't default to applying updates to an image that was built # with updates applied, and vice versa. if template.applyUpdates: self.updateController.applyUpdatesCheckbox.setState_( NSOffState) else: self.updateController.applyUpdatesCheckbox.setState_(NSOnState) else: if info["sourceType"] == IEDWorkflow.SYSTEM_IMAGE: LogInfo("No template found in image") # If the image doesn't have a template inside, assume that updates # were applied. self.updateController.applyUpdatesCheckbox.setState_( NSOffState) self.setBusy_(False) def sourceFailed_text_(self, message, text): self.displayAlert_text_(message, text) self.setSourcePlaceholder() self.sourceLabel.setTextColor_(NSColor.disabledControlTextColor()) self.setBusy_(False) # Opening installer via menu. @LogException @IBAction def locateInstaller_(self, menuItem): if menuItem.isAlternate(): try: path = (glob.glob("/Applications/*/Install*OS*.app") + glob.glob("/Applications/Install*OS*.app"))[-1] if IEDUtil.mightBeSource_(path): self.acceptSource_(path) else: NSBeep() except IndexError: NSBeep() else: IEDPanelPathManager.loadPathForName_("Installer") panel = NSOpenPanel.openPanel() panel.setDelegate_(self) panel.setExtensionHidden_(False) panel.setAllowedFileTypes_(["app", "dmg"]) result = panel.runModal() if result != NSFileHandlingPanelOKButton: return IEDPanelPathManager.savePathForName_("Installer") self.acceptSource_(panel.URL().path()) # NSOpenSavePanelDelegate. def panel_shouldEnableURL_(self, sender, url): return IEDUtil.mightBeSource_(url.path()) # Act on update controller changing. def updateControllerChanged(self): if self.enabled: self.updateBuildButton() # Act on user showing log window. @LogException @IBAction def displayAdvancedWindow_(self, sender): self.advancedWindow.makeKeyAndOrderFront_(self) # Act on build button. @LogException @IBAction def buildButtonClicked_(self, sender): IEDPanelPathManager.loadPathForName_("Image") panel = NSSavePanel.savePanel() panel.setExtensionHidden_(False) panel.setAllowedFileTypes_(["dmg"]) imageName = "osx" formatter = NSDateFormatter.alloc().init() formatter.setDateFormat_("yyMMdd") if self.updateController.packagesToInstall(): dateStr = formatter.stringFromDate_( self.updateController.profileController.publicationDate) imageName = "osx_updated_%s" % dateStr if self.addPkgController.packagesToInstall(): dateStr = formatter.stringFromDate_(NSDate.date()) imageName = "osx_custom_%s" % dateStr panel.setNameFieldStringValue_( "%s-%s-%s.%s" % (imageName, self.installerVersion, self.installerBuild, self.filesystem.selectedItem().representedObject())) result = panel.runModal() if result != NSFileHandlingPanelOKButton: return IEDPanelPathManager.savePathForName_("Image") exists, error = panel.URL().checkResourceIsReachableAndReturnError_( None) if exists: success, error = NSFileManager.defaultManager( ).removeItemAtURL_error_(panel.URL(), None) if not success: NSApp.presentError_(error) return # Create a template to save inside the image. template = IEDTemplate.alloc().init() template.setSourcePath_(self.workflow.source()) if self.updateController.packagesToInstall(): template.setApplyUpdates_(True) else: template.setApplyUpdates_(False) if not template.setAdditionalPackages_( [x.path() for x in self.addPkgController.packagesToInstall()]): self.displayAlert_text_("Additional packages failed verification", template.additionalPackageError) return template.setOutputPath_(panel.URL().path()) if self.volumeName.stringValue(): template.setVolumeName_(self.volumeName.stringValue()) self.workflow.setVolumeName_(self.volumeName.stringValue().strip()) if self.volumeSize.stringValue(): template.setVolumeSize_(self.volumeSize.intValue()) self.workflow.setVolumeSize_(self.volumeSize.intValue()) finalize_asr_imagescan = bool(self.finalizeAsrImagescan.state()) template.setFinalizeAsrImagescan_(finalize_asr_imagescan) self.workflow.setFinalizeAsrImagescan_(finalize_asr_imagescan) fsType = self.filesystem.selectedItem().representedObject() template.setFilesystem_(fsType) self.workflow.setFilesystem_(fsType) self.workflow.setTemplate_(template) self.workflow.setPackagesToInstall_( self.updateController.packagesToInstall() + self.addPkgController.packagesToInstall()) self.workflow.setOutputPath_(panel.URL().path()) self.workflow.start() # Workflow delegate methods. def buildStartingWithOutput_(self, outputPath): self.buildProgressWindow.setTitle_(os.path.basename(outputPath)) self.buildProgressPhase.setStringValue_("Starting") self.buildProgressBar.setIndeterminate_(True) self.buildProgressBar.startAnimation_(self) self.buildProgressBar.setDoubleValue_(0.0) self.buildProgressMessage.setStringValue_("") self.buildProgressWindow.makeKeyAndOrderFront_(self) self.setBusy_(True) def buildSetTotalWeight_(self, totalWeight): self.buildProgressBar.setMaxValue_(totalWeight) def buildSetPhase_(self, phase): self.buildProgressPhase.setStringValue_(phase) def buildSetProgress_(self, progress): self.buildProgressBar.setDoubleValue_(progress) self.buildProgressBar.setIndeterminate_(False) def buildSetProgressMessage_(self, message): self.buildProgressMessage.setStringValue_(message) def buildSucceeded(self): alert = NSAlert.alloc().init() alert.setMessageText_("Build successful") alert.setInformativeText_("Built %s" % os.path.basename(self.workflow.outputPath())) alert.addButtonWithTitle_("OK") alert.addButtonWithTitle_("Reveal") button = alert.runModal() if button == NSAlertSecondButtonReturn: fileURL = NSURL.fileURLWithPath_(self.workflow.outputPath()) NSWorkspace.sharedWorkspace().activateFileViewerSelectingURLs_( [fileURL]) def buildFailed_details_(self, message, details): alert = NSAlert.alloc().init() alert.setMessageText_(message) alert.setInformativeText_(details) alert.addButtonWithTitle_("OK") alert.addButtonWithTitle_("View Log") button = alert.runModal() if button == NSAlertSecondButtonReturn: self.logController.displayLogWindow_(self) def buildStopped(self): self.buildProgressWindow.orderOut_(self) self.setBusy_(False) # Load and save templates. def saveTemplate(self): if self.templateURL: self.saveTemplateToURL_(self.templateURL) else: self.saveTemplateAs() def saveTemplateAs(self): IEDPanelPathManager.loadPathForName_("Template") panel = NSSavePanel.savePanel() panel.setExtensionHidden_(False) panel.setAllowedFileTypes_(["adtmpl"]) formatter = NSDateFormatter.alloc().init() formatter.setDateFormat_("yyMMdd") dateStr = formatter.stringFromDate_(NSDate.date()) panel.setNameFieldStringValue_("AutoDMG-%s.adtmpl" % (dateStr)) result = panel.runModal() if result != NSFileHandlingPanelOKButton: return IEDPanelPathManager.savePathForName_("Template") exists, error = panel.URL().checkResourceIsReachableAndReturnError_( None) if exists: success, error = NSFileManager.defaultManager( ).removeItemAtURL_error_(panel.URL(), None) if not success: NSApp.presentError_(error) return self.saveTemplateToURL_(panel.URL()) def saveTemplateToURL_(self, url): LogDebug("saveTemplateToURL:%@", url) self.templateURL = url NSDocumentController.sharedDocumentController( ).noteNewRecentDocumentURL_(url) # Create a template from the current state. template = IEDTemplate.alloc().init() if self.workflow.source(): template.setSourcePath_(self.workflow.source()) if self.updateController.packagesToInstall(): template.setApplyUpdates_(True) else: template.setApplyUpdates_(False) if self.volumeName.stringValue(): template.setVolumeName_(self.volumeName.stringValue()) if self.volumeSize.intValue(): template.setVolumeSize_(self.volumeSize.intValue()) if self.finalizeAsrImagescan.state() == NSOffState: template.setFinalizeAsrImagescan_(False) if IEDUtil.hostMajorVersion() >= 13: template.setFilesystem_( self.filesystem.selectedItem().representedObject()) template.setAdditionalPackages_( [x.path() for x in self.addPkgController.packagesToInstall()]) error = template.saveTemplateAndReturnError_(url.path()) if error: self.displayAlert_text_("Couldn't save template", error) def openTemplate(self): IEDPanelPathManager.loadPathForName_("Template") panel = NSOpenPanel.openPanel() panel.setExtensionHidden_(False) panel.setAllowedFileTypes_(["adtmpl"]) result = panel.runModal() if result != NSFileHandlingPanelOKButton: return IEDPanelPathManager.savePathForName_("Template") return self.openTemplateAtURL_(panel.URL()) def openTemplateAtURL_(self, url): LogDebug("openTemplateAtURL:%@", url) self.templateURL = None template = IEDTemplate.alloc().init() error = template.loadTemplateAndReturnError_(url.path()) if error: self.displayAlert_text_("Couldn't open template", error) return False self.templateURL = url NSDocumentController.sharedDocumentController( ).noteNewRecentDocumentURL_(url) # AdditionalPackages. LogDebug("Setting additional packages to %@", template.additionalPackages) self.addPkgController.replacePackagesWithPaths_( template.additionalPackages) # ApplyUpdates. if template.applyUpdates: LogDebug("Enable updates") self.updateController.applyUpdatesCheckbox.setState_(NSOnState) else: LogDebug("Disable updates") self.updateController.applyUpdatesCheckbox.setState_(NSOffState) # VolumeName. self.volumeName.setStringValue_("") if template.volumeName: LogDebug("Setting volume name to %@", template.volumeName) self.volumeName.setStringValue_(template.volumeName) # VolumeSize. self.volumeSize.setStringValue_("") if template.volumeSize: LogDebug("Setting volume size to %@", template.volumeSize) self.volumeSize.setIntValue_(template.volumeSize) # Finalize task: ASR imagescan. self.finalizeAsrImagescan.setState_(NSOnState) if template.finalizeAsrImagescan == False: LogDebug("Setting 'Finalize: Scan for restore' to %@", template.finalizeAsrImagescan) self.finalizeAsrImagescan.setState_(NSOffState) # Filesystem. if template.filesystem: if IEDUtil.hostMajorVersion() < 13: if template.filesystem != "hfs": LogWarning( "Ignoring template filesystem (%@), using hfs on 10.%d", template.filesystem, IEDUtil.hostMajorVersion()) else: LogDebug("Setting filesystem to %@", template.filesystem) for item in self.filesystem.itemArray(): if item.representedObject() == template.filesystem: self.filesystem.selectItem_(item) break else: LogWarning("Unknown filesystem '%@'", template.filesystem) # SourcePath. if template.sourcePath: LogDebug("Setting source to %@", template.sourcePath) self.setBusy_(True) self.workflow.setSource_(template.sourcePath) return True
class MSCStatusController(NSObject): ''' Handles status messages from managedsoftwareupdate ''' session_started = False got_status_update = False timer = None _status_stopBtnDisabled = False _status_stopBtnHidden = False _status_message = u'' _status_detail = u'' _status_percent = -1 _status_stopBtnState = 0 statusWindowController = IBOutlet() def registerForNotifications(self): '''Register for notification messages''' notification_center = NSDistributedNotificationCenter.defaultCenter() notification_center.addObserver_selector_name_object_suspensionBehavior_( self, self.updateStatus_, 'com.googlecode.munki.managedsoftwareupdate.statusUpdate', None, NSNotificationSuspensionBehaviorDeliverImmediately) self.receiving_notifications = True def unregisterForNotifications(self): '''Tell the DistributedNotificationCenter to stop sending us notifications''' NSDistributedNotificationCenter.defaultCenter().removeObserver_(self) # set self.receiving_notifications to False so our process monitoring # thread will exit self.receiving_notifications = False def startMunkiStatusSession(self): '''Initialize things for monitoring a managedsoftwareupdate session''' self.initStatusSession() self.session_started = True # start our process monitor timer so we can be notified about # process failure self.timeout_counter = 6 self.saw_process = False self.timer = NSTimer.scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_( 5.0, self, self.checkProcess_, None, YES) def checkProcess_(self, timer): '''Monitors managedsoftwareupdate process for failure to start or unexpected exit, so we're not waiting around forever if managedsoftwareupdate isn't running.''' PYTHON_SCRIPT_NAME = u'managedsoftwareupdate' NEVER_STARTED = -2 UNEXPECTEDLY_QUIT = -1 if self.session_started: if self.got_status_update: # we got a status update since we last checked; no need to # check the process table self.timeout_counter = 6 self.saw_process = True # clear the flag so we have to get another status update self.got_status_update = False elif munki.pythonScriptRunning(PYTHON_SCRIPT_NAME): self.timeout_counter = 6 self.saw_process = True else: msclog.debug_log('managedsoftwareupdate not running...') self.timeout_counter -= 1 if self.timeout_counter == 0: msclog.debug_log( 'Timed out waiting for managedsoftwareupdate.') if self.saw_process: self.sessionEnded_(UNEXPECTEDLY_QUIT) else: self.sessionEnded_(NEVER_STARTED) def sessionStarted(self): '''Accessor method''' return self.session_started def sessionEnded_(self, result): '''clean up after a managesoftwareupdate session ends''' if self.timer: self.timer.invalidate() self.timer = None self.cleanUpStatusSession() # tell the window controller the update session is done self.statusWindowController.munkiStatusSessionEndedWithStatus_errorMessage_( result, "") def updateStatus_(self, notification): '''Got update status notification from managedsoftwareupdate''' msclog.debug_log('Got munkistatus update notification') self.got_status_update = True info = notification.userInfo() msclog.debug_log('%s' % info) # explictly get keys from info object; PyObjC in Mountain Lion # seems to need this info_keys = info.keys() if 'message' in info_keys: self.setMessage_(info['message']) if 'detail' in info_keys: self.setDetail_(info['detail']) if 'percent' in info_keys: self.setPercentageDone_(info['percent']) if 'stop_button_visible' in info_keys: if info['stop_button_visible']: self.showStopButton() else: self.hideStopButton() if 'stop_button_enabled' in info_keys: if info['stop_button_enabled']: self.enableStopButton() else: self.disableStopButton() command = info.get('command') if not self.session_started and command not in [ 'showRestartAlert', 'quit' ]: # we got a status message but we didn't start the session # so switch to the right mode self.startMunkiStatusSession() if command: msclog.debug_log('Received command: %s' % command) if command == 'activate': pass elif command == 'showRestartAlert': if self.session_started: self.sessionEnded_(0) self.doRestartAlert() elif command == 'quit': self.sessionEnded_(0) ##### required status methods ##### def initStatusSession(self): '''Initialize the main window for update status''' self.statusWindowController._update_in_progress = True if self.statusWindowController.currentPageIsUpdatesPage(): self.statusWindowController.webView.reload_(self) self.statusWindowController.displayUpdateCount() def cleanUpStatusSession(self): '''Clean up after status session ends''' self.session_started = False # reset all our status variables self.statusWindowController._update_in_progress = False self._status_stopBtnDisabled = False self._status_stopBtnHidden = False self._status_stopBtnState = 0 self._status_message = u'' self._status_detail = u'' self._status_percent = -1 def setPercentageDone_(self, percent): '''Display precentage done''' try: if float(percent) > 100.0: percent = 100 except ValueError: percent = 0 self._status_percent = percent document = self.statusWindowController.webView.mainFrameDocument() if document: spinner = document.getElementById_('updates-progress-spinner') if spinner: # we are displaying the updates status page progress = document.getElementById_('progress-bar') if progress: if float(percent) < 0: # indeterminate progress.setClassName_('indeterminate') progress.removeAttribute_('style') else: progress.setClassName_('') progress.setAttribute__('style', 'width: %s%%' % percent) def doRestartAlert(self): '''Display a restart alert -- some item just installed or removed requires a restart''' msclog.log("MSC", "restart_required") self._status_restartAlertDismissed = 0 alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_( NSLocalizedString(u"Restart Required", u"Restart Required title"), NSLocalizedString(u"Restart", u"Restart button title"), nil, nil, u"%@", NSLocalizedString( u"Software installed or removed requires a restart. You will " "have a chance to save open documents.", u"Restart Required alert detail")) alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_( self.statusWindowController.window(), self, self.restartAlertDidEnd_returnCode_contextInfo_, nil) @AppHelper.endSheetMethod def restartAlertDidEnd_returnCode_contextInfo_(self, alert, returncode, contextinfo): '''Called when restartAlert ends''' msclog.log("MSC", "restart_confirmed") self._status_restartAlertDismissed = 1 munki.restartNow() def setMessage_(self, messageText): '''Display main status message''' messageText = NSBundle.mainBundle().localizedStringForKey_value_table_( messageText, messageText, None) self._status_message = messageText document = self.statusWindowController.webView.mainFrameDocument() if document: spinner = document.getElementById_('updates-progress-spinner') if spinner: # we are displaying the updates status page textElement = document.getElementById_('primary-status-text') if textElement: if messageText: textElement.setInnerText_(messageText) else: textElement.setInnerHTML_(' ') def setDetail_(self, detailText): '''Display status detail''' detailText = NSBundle.mainBundle().localizedStringForKey_value_table_( detailText, detailText, None) self._status_detail = detailText document = self.statusWindowController.webView.mainFrameDocument() if document: spinner = document.getElementById_('updates-progress-spinner') if spinner: # we are displaying the updates status page textElement = document.getElementById_('secondary-status-text') if textElement: if detailText: textElement.setInnerText_(detailText) else: textElement.setInnerHTML_(' ') def getStopBtnState(self): '''Get the state (pressed or not) of the stop button''' return self._status_stopBtnState def hideStopButton(self): '''Hide the stop button''' if self._status_stopBtnState: return self._status_stopBtnHidden = True document = self.statusWindowController.webView.mainFrameDocument() if document: spinner = document.getElementById_('updates-progress-spinner') if spinner: # we are displaying the updates status page install_btn = document.getElementById_( 'install-all-button-text') if install_btn: btn_classes = install_btn.className().split(' ') if not 'hidden' in btn_classes: btn_classes.append('hidden') install_btn.setClassName_(' '.join(btn_classes)) def showStopButton(self): '''Show the stop button''' if self._status_stopBtnState: return self._status_stopBtnHidden = False document = self.statusWindowController.webView.mainFrameDocument() if document: spinner = document.getElementById_('updates-progress-spinner') if spinner: # we are displaying the updates status page install_btn = document.getElementById_( 'install-all-button-text') if install_btn: btn_classes = install_btn.className().split(' ') if 'hidden' in btn_classes: btn_classes.remove('hidden') install_btn.setClassName_(' '.join(btn_classes)) def enableStopButton(self): '''Enable the stop button''' if self._status_stopBtnState: return self._status_stopBtnDisabled = False document = self.statusWindowController.webView.mainFrameDocument() if document: spinner = document.getElementById_('updates-progress-spinner') if spinner: # we are displaying the updates status page install_btn = document.getElementById_( 'install-all-button-text') if install_btn: btn_classes = install_btn.className().split(' ') if 'disabled' in btn_classes: btn_classes.remove('disabled') install_btn.setClassName_(' '.join(btn_classes)) def disableStopButton(self): '''Disable the stop button''' if self._status_stopBtnState: return self._status_stopBtnDisabled = True document = self.statusWindowController.webView.mainFrameDocument() if document: spinner = document.getElementById_('updates-progress-spinner') if spinner: # we are displaying the updates status page install_btn = document.getElementById_( 'install-all-button-text') if install_btn: btn_classes = install_btn.className().split(' ') if not 'disabled' in btn_classes: btn_classes.append('disabled') install_btn.setClassName_(' '.join(btn_classes)) def getRestartAlertDismissed(self): '''Was the restart alert dimissed?''' return self._status_restartAlertDismissed
class xcPROJECTNAMEASIDENTIFIERxc_AppDelegate(NSObject): window = IBOutlet() _managedObjectModel = None _persistentStoreCoordinator = None _managedObjectContext = None def applicationDidFinishLaunching_(self, sender): self.managedObjectContext() def applicationSupportFolder(self): paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) basePath = paths[0] if (len(paths) > 0) else NSTemporaryDirectory() return os.path.join(basePath, "xcPROJECTNAMEASIDENTIFIERxc") def managedObjectModel(self): if self._managedObjectModel: return self._managedObjectModel self._managedObjectModel = NSManagedObjectModel.mergedModelFromBundles_(None) return self._managedObjectModel def persistentStoreCoordinator(self): if self._persistentStoreCoordinator: return self._persistentStoreCoordinator applicationSupportFolder = self.applicationSupportFolder() if not os.path.exists(applicationSupportFolder): os.mkdir(applicationSupportFolder) storePath = os.path.join(applicationSupportFolder, "xcPROJECTNAMEASIDENTIFIERxc.xml") url = NSURL.fileURLWithPath_(storePath) self._persistentStoreCoordinator = NSPersistentStoreCoordinator.alloc().initWithManagedObjectModel_(self.managedObjectModel()) success, error = self._persistentStoreCoordinator.addPersistentStoreWithType_configuration_URL_options_error_(NSXMLStoreType, None, url, None, None) if not success: NSApp().presentError_(error) return self._persistentStoreCoordinator def managedObjectContext(self): if self._managedObjectContext: return self._managedObjectContext coordinator = self.persistentStoreCoordinator() if coordinator: self._managedObjectContext = NSManagedObjectContext.alloc().init() self._managedObjectContext.setPersistentStoreCoordinator_(coordinator) return self._managedObjectContext def windowWillReturnUndoManager_(self, window): return self.managedObjectContext().undoManager() @IBAction def saveAction_(self, sender): success, error = self.managedObjectContext().save_(None) if not success: NSApp().presentError_(error) def applicationShouldTerminate_(self, sender): if not self._managedObjectContext: return NSTerminateNow if not self._managedObjectContext.commitEditing(): return NSTerminateCancel if self._managedObjectContext.hasChanges(): success, error = self._managedObjectContext.save_(None) if success: return NSTerminateNow if NSApp().presentError_(error): return NSTerminateCancel else: alertReturn = NSRunAlertPanel(None, "Could not save changes while quitting. Quit anyway?" , "Quit anyway", "Cancel", nil) if alertReturn == NSAlertAlternateReturn: return NSTerminateCancel return NSTerminateNow
class IEDLog(NSObject): # Singleton instance. _instance = None logWindow = IBOutlet() logTableView = IBOutlet() levelSelector = IBOutlet() logLines = list() visibleLogLines = list() def init(self): # Initialize singleton. if IEDLog._instance is not None: return IEDLog._instance self = super(IEDLog, self).init() if self is None: return None IEDLog._instance = self self.logAtBottom = True return self def awakeFromNib(self): global defaults self.levelSelector.selectItemAtIndex_( defaults.integerForKey_(u"LogLevel")) self.logTableView.setDataSource_(self) nc = NSNotificationCenter.defaultCenter() nc.addObserver_selector_name_object_( self, self.logViewScrolled_, NSViewBoundsDidChangeNotification, self.logTableView.enclosingScrollView().contentView()) # Helper methods. def addMessage_level_(self, message, level): # Log messages may come from background threads. self.performSelectorOnMainThread_withObject_waitUntilDone_( self.addMessageAndLevel_, { u"message": message, u"level": level }, False) def addMessageAndLevel_(self, messageAndLevel): message = messageAndLevel[u"message"] level = messageAndLevel[u"level"] logLine = IEDLogLine.alloc().initWithMessage_level_(message, level) self.logLines.append(logLine) if defaults.integerForKey_(u"LogLevel") >= level: self.visibleLogLines.append(logLine) if self.logTableView: self.logTableView.reloadData() if self.logAtBottom: self.logTableView.scrollRowToVisible_( len(self.visibleLogLines) - 1) # Act on user showing log window. @LogException @IBAction def displayLogWindow_(self, sender): self.logAtBottom = True self.logTableView.scrollRowToVisible_(len(self.visibleLogLines) - 1) self.logWindow.makeKeyAndOrderFront_(self) # Act on notification for log being scrolled by user. def logViewScrolled_(self, notification): tableViewHeight = self.logTableView.bounds().size.height scrollView = self.logTableView.enclosingScrollView() scrollRect = scrollView.documentVisibleRect() scrollPos = scrollRect.origin.y + scrollRect.size.height if scrollPos >= tableViewHeight: self.logAtBottom = True else: self.logAtBottom = False # Act on user filtering log. @LogException @IBAction def setLevel_(self, sender): self.visibleLogLines = [ x for x in self.logLines if x.level() <= self.levelSelector.indexOfSelectedItem() ] self.logAtBottom = True self.logTableView.reloadData() self.logTableView.scrollRowToVisible_(len(self.visibleLogLines) - 1) # Act on user clicking save button. @LogException @IBAction def saveLog_(self, sender): panel = NSSavePanel.savePanel() panel.setExtensionHidden_(False) panel.setAllowedFileTypes_([u"log", u"txt"]) formatter = NSDateFormatter.alloc().init() formatter.setDateFormat_(u"yyyy-MM-dd HH.mm") dateStr = formatter.stringFromDate_(NSDate.date()) panel.setNameFieldStringValue_(u"AutoDMG %s" % dateStr) result = panel.runModal() if result != NSFileHandlingPanelOKButton: return exists, error = panel.URL().checkResourceIsReachableAndReturnError_( None) if exists: success, error = NSFileManager.defaultManager( ).removeItemAtURL_error_(panel.URL(), None) if not success: NSApp.presentError_(error) return success, error = NSData.data().writeToURL_options_error_( panel.URL(), 0, None) if not success: NSApp.presentError_(error) return fh, error = NSFileHandle.fileHandleForWritingToURL_error_( panel.URL(), None) if fh is None: NSAlert.alertWithError_(error).runModal() return formatter = NSDateFormatter.alloc().init() formatter.setDateFormat_(u"yyyy-MM-dd HH:mm:ss") for logLine in self.logLines: textLine = NSString.stringWithFormat_( u"%@ %@: %@\n", formatter.stringFromDate_(logLine.date()), IEDLogLevelName(logLine.level()), logLine.message()) fh.writeData_(textLine.dataUsingEncoding_(NSUTF8StringEncoding)) fh.closeFile() # We're an NSTableViewDataSource. def numberOfRowsInTableView_(self, tableView): return len(self.visibleLogLines) def tableView_objectValueForTableColumn_row_(self, tableView, column, row): if column.identifier() == u"date": return self.visibleLogLines[row].date() elif column.identifier() == u"level": return IEDLogLevelName(self.visibleLogLines[row].level()) elif column.identifier() == u"message": return self.visibleLogLines[row].message()
class IEDAppDelegate(NSObject): mainWindowController = IBOutlet() appVersionController = IBOutlet() helpMenuItem = IBOutlet() def init(self): self = super(IEDAppDelegate, self).init() if self is None: return None return self def initialize(self): # Log version info on startup. version, build = IEDUtil.getAppVersion() LogInfo("AutoDMG v%@ build %@", version, build) name, version, build = IEDUtil.readSystemVersion_("/") LogInfo("%@ %@ %@", name, version, build) LogInfo("%@ %@ (%@)", platform.python_implementation(), platform.python_version(), platform.python_compiler()) LogInfo("PyObjC %@", pyObjCVersion) # Initialize user defaults before application starts. defaultsPath = NSBundle.mainBundle().pathForResource_ofType_( "Defaults", "plist") defaultsDict = NSDictionary.dictionaryWithContentsOfFile_(defaultsPath) defaults.registerDefaults_(defaultsDict) def applicationDidFinishLaunching_(self, sender): NSApplication.sharedApplication().disableRelaunchOnLogin() version, build = IEDUtil.getAppVersion() if version.lower().endswith("b"): NSApplication.sharedApplication().dockTile().setBadgeLabel_("beta") updateProfileInterval = defaults.integerForKey_( "UpdateProfileInterval") if updateProfileInterval: lastCheck = defaults.objectForKey_("LastUpdateProfileCheck") if lastCheck.timeIntervalSinceNow() < -60 * 60 * 18: self.mainWindowController.updateController.checkForProfileUpdatesSilently( ) appVersionCheckInterval = defaults.integerForKey_( "AppVersionCheckInterval") if appVersionCheckInterval: lastCheck = defaults.objectForKey_("LastAppVersionCheck") if lastCheck.timeIntervalSinceNow() < -60 * 60 * 18: self.appVersionController.checkForAppUpdateSilently_(True) def applicationShouldTerminate_(self, sender): LogDebug("applicationShouldTerminate:") if self.mainWindowController.busy(): alert = NSAlert.alloc().init() alert.setAlertStyle_(NSCriticalAlertStyle) alert.setMessageText_("Application busy") alert.setInformativeText_("Quitting now could leave the " "system in an unpredictable state.") alert.addButtonWithTitle_("Quit") alert.addButtonWithTitle_("Stay") button = alert.runModal() if button == NSAlertSecondButtonReturn: return NSTerminateCancel return NSTerminateNow def applicationWillTerminate_(self, sender): LogDebug("applicationWillTerminate:") self.mainWindowController.cleanup() @LogException @IBAction def showHelp_(self, sender): NSWorkspace.sharedWorkspace().openURL_( NSURL.URLWithString_(defaults.stringForKey_("HelpURL"))) # Trampolines for document handling. @LogException @IBAction def saveDocument_(self, sender): LogDebug("saveDocument:") self.mainWindowController.saveTemplate() @LogException @IBAction def saveDocumentAs_(self, sender): LogDebug("saveDocumentAs:") self.mainWindowController.saveTemplateAs() @LogException @IBAction def openDocument_(self, sender): LogDebug("openDocument:") self.mainWindowController.openTemplate() def validateMenuItem_(self, menuItem): if menuItem == self.helpMenuItem: return True else: return not self.mainWindowController.busy() def application_openFile_(self, application, filename): return self.mainWindowController.openTemplateAtURL_( NSURL.fileURLWithPath_(filename))
class MSUStatusWindowController(NSObject): '''Controls the status window.''' # since this subclasses NSObject, # it doesn't have a Python __init__method # pylint: disable=no-init window = IBOutlet() logWindow = IBOutlet() messageFld = IBOutlet() detailFld = IBOutlet() progressIndicator = IBOutlet() stopBtn = IBOutlet() imageFld = IBOutlet() backdropWindow = IBOutlet() backdropImageFld = IBOutlet() stopBtnState = 0 restartAlertDismissed = 0 got_status_update = False receiving_notifications = False timer = None timeout_counter = 0 saw_process = False managedsoftwareupdate_pid = None window_level = NSScreenSaverWindowLevel - 1 @IBAction def stopBtnClicked_(self, sender): '''Called when stop button is clicked in the status window''' if debug: NSLog(u"Stop button was clicked.") sender.setState_(1) self.stopBtnState = 1 sender.setEnabled_(False) # send a notification that stop button was clicked STOP_REQUEST_FLAG = ('/private/tmp/com.googlecode.munki.' 'managedsoftwareupdate.stop_requested') if not os.path.exists(STOP_REQUEST_FLAG): open(STOP_REQUEST_FLAG, 'w').close() def registerForNotifications(self): '''Register for notification messages''' dnc = NSDistributedNotificationCenter.defaultCenter() dnc.addObserver_selector_name_object_suspensionBehavior_( self, self.updateStatus_, 'com.googlecode.munki.managedsoftwareupdate.statusUpdate', None, NSNotificationSuspensionBehaviorDeliverImmediately) dnc.addObserver_selector_name_object_suspensionBehavior_( self, self.managedsoftwareupdateStarted_, 'com.googlecode.munki.managedsoftwareupdate.started', None, NSNotificationSuspensionBehaviorDeliverImmediately) dnc.addObserver_selector_name_object_suspensionBehavior_( self, self.managedsoftwareupdateEnded_, 'com.googlecode.munki.managedsoftwareupdate.ended', None, NSNotificationSuspensionBehaviorDeliverImmediately) self.receiving_notifications = True def unregisterForNotifications(self): '''Tell the DistributedNotificationCenter to stop sending us notifications''' NSDistributedNotificationCenter.defaultCenter().removeObserver_(self) # set self.receiving_notifications to False so our process monitoring # thread will exit self.receiving_notifications = False def managedsoftwareupdateStarted_(self, notification): '''Called when we get a com.googlecode.munki.managedsoftwareupdate.started notification''' if 'pid' in notification.userInfo(): self.managedsoftwareupdate_pid = notification.userInfo()['pid'] NSLog('managedsoftwareupdate pid %s started' % self.managedsoftwareupdate_pid) def managedsoftwareupdateEnded_(self, notification): '''Called when we get a com.googlecode.munki.managedsoftwareupdate.ended notification''' NSLog('managedsoftwareupdate pid %s ended' % notification.userInfo().get('pid')) def haveElCapPolicyBanner(self): '''Returns True if we are running El Cap or later and there is a loginwindow PolicyBanner in place''' # Get our Darwin major version darwin_vers = int(os.uname()[2].split('.')[0]) if darwin_vers > 14: for test_file in ['/Library/Security/PolicyBanner.txt', '/Library/Security/PolicyBanner.rtf', '/Library/Security/PolicyBanner.rtfd']: if os.path.exists(test_file): return True return False def setWindowLevel(self): '''Sets our NSWindowLevel. Works around issues with the loginwindow PolicyBanner in 10.11+ Some code based on earlier work by Pepijn Bruienne''' # bump our NSWindowLevel if we have a PolicyBanner in ElCap+ if self.haveElCapPolicyBanner(): NSLog('El Capitan+ loginwindow PolicyBanner found') self.window_level = NSScreenSaverWindowLevel def initStatusSession(self): '''Initialize our status session''' self.setWindowLevel() consoleuser = munki.getconsoleuser() if consoleuser == None or consoleuser == u"loginwindow": self.displayBackdropWindow() # needed so the window can show over the loginwindow self.window.setCanBecomeVisibleWithoutLogin_(True) self.window.setLevel_(self.window_level) self.window.center() self.messageFld.setStringValue_( NSLocalizedString(u"Starting…", None)) self.detailFld.setStringValue_(u"") self.stopBtn.setHidden_(False) self.stopBtn.setEnabled_(True) self.stopBtnState = 0 if self.imageFld: theImage = NSImage.imageNamed_("MunkiStatus") self.imageFld.setImage_(theImage) if self.progressIndicator: self.progressIndicator.setMinValue_(0.0) self.progressIndicator.setMaxValue_(100.0) self.progressIndicator.setIndeterminate_(True) self.progressIndicator.setUsesThreadedAnimation_(True) self.progressIndicator.startAnimation_(self) self.window.orderFrontRegardless() self.registerForNotifications() # start our process monitor timer so we can be notified about # process failure self.timeout_counter = 6 self.saw_process = False self.timer = (NSTimer. scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_( 5.0, self, self.checkProcess, None, YES)) def checkProcess(self): '''Monitors managedsoftwareupdate process for failure to start or unexpected exit, so we're not waiting around forever if managedsoftwareupdate isn't running.''' PYTHON_SCRIPT_NAME = 'managedsoftwareupdate' NEVER_STARTED = -2 UNEXPECTEDLY_QUIT = -1 NSLog('checkProcess timer fired') if self.window_level == NSScreenSaverWindowLevel: # we're at the loginwindow, there is a PolicyBanner, and we're # running under 10.11+. Make sure we're in the front. NSApp.activateIgnoringOtherApps_(YES) if not self.logWindow.isVisible(): self.window.makeKeyAndOrderFront_(self) if self.got_status_update: # we got a status update since we last checked; no need to # check the process table self.timeout_counter = 6 self.saw_process = True # clear the flag so we have to get another status update self.got_status_update = False elif munki.pythonScriptRunning(PYTHON_SCRIPT_NAME): self.timeout_counter = 6 self.saw_process = True else: NSLog('managedsoftwareupdate not running...') self.timeout_counter -= 1 if self.timeout_counter == 0: NSLog('Timed out waiting for managedsoftwareupdate.') if self.saw_process: self.statusSessionFailed_(UNEXPECTEDLY_QUIT) else: self.statusSessionFailed_(NEVER_STARTED) def statusSessionFailed_(self, sessionResult): '''Called if the status session fails''' NSLog('statusSessionFailed: %s' % sessionResult) self.cleanUpStatusSession() NSApp.terminate_(self) def cleanUpStatusSession(self): '''Clean things up before we exit''' self.unregisterForNotifications() if self.backdropWindow and self.backdropWindow.isVisible(): self.backdropWindow.orderOut_(self) self.window.orderOut_(self) # clean up timer if self.timer: self.timer.invalidate() self.timer = None def configureAndDisplayBackdropWindow_(self, window): '''Sets all our configuration options for our masking windows''' window.setCanBecomeVisibleWithoutLogin_(True) if self.haveElCapPolicyBanner(): self.backdropWindow.setLevel_(self.window_level) else: self.backdropWindow.setLevel_(self.window_level - 1) translucentColor = NSColor.blackColor().colorWithAlphaComponent_(0.35) window.setBackgroundColor_(translucentColor) window.setOpaque_(False) window.setIgnoresMouseEvents_(False) window.setAlphaValue_(0.0) window.orderFrontRegardless() window.animator().setAlphaValue_(1.0) def displayBackdropWindow(self): '''Draw a window that covers the login UI''' self.backdropWindow.setCanBecomeVisibleWithoutLogin_(True) if self.haveElCapPolicyBanner(): self.backdropWindow.setLevel_(self.window_level) else: self.backdropWindow.setLevel_(self.window_level - 1) screenRect = NSScreen.mainScreen().frame() self.backdropWindow.setFrame_display_(screenRect, True) darwin_vers = int(os.uname()[2].split('.')[0]) if darwin_vers < 11: if self.backdropImageFld: bgImage = getLoginwindowPicture() self.backdropImageFld.setImage_(bgImage) self.backdropWindow.orderFrontRegardless() else: # Lion+ # draw transparent/translucent windows to prevent interaction # with the login UI self.backdropImageFld.setHidden_(True) self.configureAndDisplayBackdropWindow_(self.backdropWindow) # are there any other screens? for screen in NSScreen.screens(): if screen != NSScreen.mainScreen(): # create another masking window for this secondary screen window_rect = screen.frame() window_rect.origin = NSPoint(0.0, 0.0) child_window = NSWindow.alloc( ).initWithContentRect_styleMask_backing_defer_screen_( window_rect, NSBorderlessWindowMask, NSBackingStoreBuffered, NO, screen) self.configureAndDisplayBackdropWindow_(child_window) if self.haveElCapPolicyBanner(): self.backdropWindow.addChildWindow_ordered_( child_window, NSWindowAbove) if self.haveElCapPolicyBanner(): # preserve the relative ordering of the backdrop window and the # status window IOW, clicking the backdrop window will not bring it # in front of the status window self.backdropWindow.addChildWindow_ordered_( self.window, NSWindowAbove) def updateStatus_(self, notification): '''Called when we get a com.googlecode.munki.managedsoftwareupdate.statusUpdate notification; update our status display with information from the notification''' if self.window_level == NSScreenSaverWindowLevel: # we're at the loginwindow, there is a PolicyBanner, and we're # running under 10.11+. Make sure we're in the front. NSApp.activateIgnoringOtherApps_(YES) if not self.logWindow.isVisible(): self.window.makeKeyAndOrderFront_(self) self.got_status_update = True info = notification.userInfo() # explictly get keys from info object; PyObjC in Mountain Lion # seems to need this info_keys = info.keys() if 'message' in info_keys: self.setMessage_(info['message']) if 'detail' in info_keys: self.setDetail_(info['detail']) if 'percent' in info_keys: self.setPercentageDone_(info['percent']) if self.stopBtnState == 0 and 'stop_button_visible' in info_keys: if info['stop_button_visible']: self.showStopButton() else: self.hideStopButton() if self.stopBtnState == 0 and 'stop_button_enabled' in info_keys: if info['stop_button_enabled']: self.enableStopButton() else: self.disableStopButton() command = info.get('command') if command == 'activate': NSApp.activateIgnoringOtherApps_(YES) self.window.orderFrontRegardless() elif command == 'showRestartAlert': # clean up timer if self.timer: self.timer.invalidate() self.timer = None self.doRestartAlert() elif command == 'quit': self.cleanUpStatusSession() NSApp.terminate_(self) def setPercentageDone_(self, percent): '''Set progress indicator to display percent done''' if float(percent) < 0: if not self.progressIndicator.isIndeterminate(): self.progressIndicator.setIndeterminate_(True) self.progressIndicator.startAnimation_(self) else: if self.progressIndicator.isIndeterminate(): self.progressIndicator.stopAnimation_(self) self.progressIndicator.setIndeterminate_(False) self.progressIndicator.setDoubleValue_(float(percent)) @AppHelper.endSheetMethod def restartAlertDidEnd_returnCode_contextInfo_( self, alert, returncode, contextinfo): '''Called when restart alert is dismissed''' # we don't use the returncode or contextinfo arguments # pylint: disable=unused-argument self.restartAlertDismissed = 1 munki.restartNow() def doRestartAlert(self): '''Display a restart alert''' self.restartAlertDismissed = 0 # pylint: disable=line-too-long nsa = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_( NSLocalizedString(u"Restart Required", None), NSLocalizedString(u"Restart", None), nil, nil, NSLocalizedString( u"Software installed or removed requires a restart. " "You will have a chance to save open documents.", None)) # pylint: enable=line-too-long nsa.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_( self.window, self, self.restartAlertDidEnd_returnCode_contextInfo_, nil) def setMessage_(self, messageText): '''Set the main status message''' messageText = NSBundle.mainBundle().localizedStringForKey_value_table_( messageText, messageText, None) self.messageFld.setStringValue_(messageText) def setDetail_(self, detailText): '''Set the status detail text''' detailText = NSBundle.mainBundle().localizedStringForKey_value_table_( detailText, detailText, None) self.detailFld.setStringValue_(detailText) def getStopBtnState(self): '''Return True if the stop button was clicked; False otherwise''' return self.stopBtnState def hideStopButton(self): '''Hide the stop button''' self.stopBtn.setHidden_(True) def showStopButton(self): '''Show the stop button''' self.stopBtn.setHidden_(False) def enableStopButton(self): '''Enable the stop button''' self.stopBtn.setEnabled_(True) def disableStopButton(self): '''Disable the stop button''' self.stopBtn.setEnabled_(False) def getRestartAlertDismissed(self): '''Return True if the restart alert was dismissed; False otherwise''' return self.restartAlertDismissed
class IEDAddPkgController(NSObject): addPkgLabel = IBOutlet() tableView = IBOutlet() removeButton = IBOutlet() movedRowsType = u"se.gu.it.AdditionalPackages" def init(self): self = super(IEDAddPkgController, self).init() if self is None: return None self.packages = list() self.packagePaths = set() return self def awakeFromNib(self): self.tableView.setDataSource_(self) self.tableView.registerForDraggedTypes_( [NSFilenamesPboardType, IEDAddPkgController.movedRowsType]) self.dragEnabled = True # Helper methods. def disableControls(self): self.dragEnabled = False self.addPkgLabel.setTextColor_(NSColor.disabledControlTextColor()) self.tableView.setEnabled_(False) self.removeButton.setEnabled_(False) def enableControls(self): self.dragEnabled = True self.addPkgLabel.setTextColor_(NSColor.controlTextColor()) self.tableView.setEnabled_(True) self.removeButton.setEnabled_(True) def getPackageSize_(self, path): p = subprocess.Popen([u"/usr/bin/du", u"-sk", path], stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = p.communicate() if p.returncode != 0: LogError(u"du failed with exit code %d", p.returncode) else: return int(out.split()[0]) * 1024 # External state of controller. def packagesToInstall(self): return self.packages # Act on remove button. @IBAction def removeButtonClicked_(self, sender): row = self.tableView.selectedRow() if row == -1: return self.packagePaths.remove(self.packages[row].path()) del self.packages[row] self.tableView.reloadData() # We're an NSTableViewDataSource. def numberOfRowsInTableView_(self, tableView): return len(self.packages) def tableView_objectValueForTableColumn_row_(self, tableView, column, row): # FIXME: Use bindings. if column.identifier() == u"image": return self.packages[row].image() elif column.identifier() == u"name": return self.packages[row].name() def tableView_validateDrop_proposedRow_proposedDropOperation_( self, tableView, info, row, operation): if not self.dragEnabled: return NSDragOperationNone if info.draggingSource() == tableView: return NSDragOperationMove pboard = info.draggingPasteboard() paths = pboard.propertyListForType_(NSFilenamesPboardType) if not paths: return NSDragOperationNone for path in paths: # Don't allow multiple copies. if path in self.packagePaths: return NSDragOperationNone # Ensure the file extension is pkg or mpkg. name, ext = os.path.splitext(path) if ext.lower() not in (u".pkg", u".mpkg"): return NSDragOperationNone return NSDragOperationCopy def tableView_acceptDrop_row_dropOperation_(self, tableView, info, row, operation): if not self.dragEnabled: return False pboard = info.draggingPasteboard() # If the source is the tableView, we're reordering packages within the # table and the pboard contains the source row indices. if info.draggingSource() == tableView: indices = [ int(i) for i in pboard.propertyListForType_( IEDAddPkgController.movedRowsType).split(u",") ] for i in indices: self.packages[row], self.packages[i] = self.packages[ i], self.packages[row] else: # Otherwise it's a list of paths to add to the table. paths = pboard.propertyListForType_(NSFilenamesPboardType) for i, path in enumerate(paths): package = IEDPackage.alloc().init() package.setName_(os.path.basename(path)) package.setPath_(path) package.setSize_(self.getPackageSize_(path)) package.setImage_( NSWorkspace.sharedWorkspace().iconForFile_(path)) self.packages.insert(row + i, package) self.packagePaths.add(path) tableView.reloadData() return True def tableView_writeRowsWithIndexes_toPasteboard_(self, tableView, rowIndexes, pboard): # When reordering packages put a list of indices as a string onto the pboard. indices = list() index = rowIndexes.firstIndex() while index != NSNotFound: indices.append(index) index = rowIndexes.indexGreaterThanIndex_(index) pboard.declareTypes_owner_([IEDAddPkgController.movedRowsType], self) pboard.setPropertyList_forType_(u",".join(unicode(i) for i in indices), IEDAddPkgController.movedRowsType) return True
class MSUupdatesViewController(NSViewController): ''' Controls the updates view of the main window ''' restartInfoFld = IBOutlet() restartImageFld = IBOutlet() descriptionView = IBOutlet() tableView = IBOutlet() optionalSoftwareBtn = IBOutlet() array_controller = IBOutlet() window_controller = IBOutlet() updateNowBtn = IBOutlet() _EMPTYUPDATELIST = NSArray.arrayWithArray_([{ "image": NSImage.imageNamed_("Empty.png"), "name": "", "version": "", "size": "", "description": "" }]) _updatelist = [] def updatelist(self): #NSLog(u"MSUupdatesViewController.updatelist") return self._updatelist or self._EMPTYUPDATELIST objc.accessor(updatelist) # PyObjC KVO hack def setUpdatelist_(self, newlist): #NSLog(u"MSUupdatesViewController.setUpdatelist_") self._updatelist = NSArray.arrayWithArray_(newlist) objc.accessor(setUpdatelist_) # PyObjC KVO hack @IBAction def laterBtnClicked_(self, sender): NSApp.delegate().laterBtnClicked() @IBAction def updateNowBtnClicked_(self, sender): # alert the user to logout, proceed without logout, or cancel NSApp.delegate().confirmInstallUpdates() @IBAction def optionalSoftwareBtnClicked_(self, sender): # switch to optional software pane munki.log("user", "view_optional_software") self.window_controller.theTabView.selectNextTabViewItem_(sender) NSApp.delegate().optional_view_controller.AddRemoveBtn.setEnabled_(NO) #NSApp.delegate().buildOptionalInstallsData() def updateWebKitView_(self, description): if "</html>" in description or "</HTML>" in description: self.descriptionView.mainFrame().loadHTMLString_baseURL_( description, None) else: self.descriptionView.mainFrame( ).loadData_MIMEType_textEncodingName_baseURL_( buffer(description.encode('UTF-8')), u"text/plain", u"utf-8", None) def updateDescriptionView(self): #NSLog(u"MSUupdatesViewController.updateDescriptionView") if len(self.array_controller.selectedObjects()): row = self.array_controller.selectedObjects()[0] description = row.get("description", u"") else: description = u"" self.performSelectorOnMainThread_withObject_waitUntilDone_( self.updateWebKitView_, description, YES) def tableViewSelectionDidChange_(self, sender): #NSLog(u"MSUupdatesViewController.tableViewSelectionDidChange_") #self.performSelectorOnMainThread_withObject_waitUntilDone_( # self.updateDescriptionView, None, NO) self.updateDescriptionView()
class IEDUpdateController(NSObject): profileController = IBOutlet() updateBox = IBOutlet() applyUpdatesCheckbox = IBOutlet() updateTable = IBOutlet() updateTableImage = IBOutlet() updateTableLabel = IBOutlet() downloadButton = IBOutlet() downloadWindow = IBOutlet() downloadLabel = IBOutlet() downloadProgressBar = IBOutlet() downloadStopButton = IBOutlet() updateBoxHeight = IBOutlet() def init(self): self = super(IEDUpdateController, self).init() if self is None: return None self.cache = IEDUpdateCache.alloc().initWithDelegate_(self) self.updates = list() self.downloadTotalSize = 0 self.downloads = list() self.delegate = None self.version = None self.build = None self.profileWarning = None self.boxTableSizeDelta = 0 return self def setDelegate_(self, delegate): self.delegate = delegate def awakeFromNib(self): self.cachedImage = NSImage.imageNamed_("Package") self.uncachedImage = NSImage.imageNamed_("Package blue arrow") self.updatesAllOKImage = NSImage.imageNamed_("Checkmark") self.updatesToDownloadImage = NSImage.imageNamed_("Download") self.updatesWarningImage = NSImage.imageNamed_("Exclamation") self.updateTableImage.setImage_(None) self.updateTableLabel.setStringValue_("") self.updateTable.setDataSource_(self) self.boxTableSizeDelta = self.updateBox.frame( ).size.height - self.updateTable.frame().size.height self.updateHeight() def validateMenuItem_(self, menuItem): return not self.delegate.busy() # Helper methods. def updateHeight(self): rowHeight = 18 rows = self.numberOfRowsInTableView_(self.updateTable) height = rows * rowHeight height = max(min(height, int(6.5 * rowHeight)), 3.5 * rowHeight) NSAnimationContext.beginGrouping() NSAnimationContext.currentContext().setDuration_(0.15) easeInEaseOut = Quartz.CAMediaTimingFunction.functionWithName_( Quartz.kCAMediaTimingFunctionEaseInEaseOut) NSAnimationContext.currentContext().setTimingFunction_(easeInEaseOut) self.updateBoxHeight.animator().setConstant_(height + self.boxTableSizeDelta) NSAnimationContext.endGrouping() def disableControls(self): LogDebug("disableControls") self.applyUpdatesCheckbox.setEnabled_(False) self.updateTable.setEnabled_(False) self.downloadButton.setEnabled_(False) def enableControls(self): LogDebug("enableControls") self.applyUpdatesCheckbox.setEnabled_(len(self.updates) > 0) self.updateTable.setEnabled_(len(self.updates) > 0) self.downloadButton.setEnabled_(len(self.downloads) > 0) def showRemainingDownloads(self): if self.profileWarning: self.updateTableImage.setImage_(self.updatesWarningImage) self.updateTableLabel.setStringValue_(self.profileWarning) self.updateTableLabel.setTextColor_(NSColor.controlTextColor()) return if len(self.downloads) == 0: self.updateTableLabel.setStringValue_("All updates downloaded") self.updateTableLabel.setTextColor_( NSColor.disabledControlTextColor()) self.updateTableImage.setImage_(self.updatesAllOKImage) else: sizeStr = IEDUtil.formatByteSize_(self.downloadTotalSize) plurals = "s" if len(self.downloads) >= 2 else "" downloadLabel = "%d update%s to download (%s)" % (len( self.downloads), plurals, sizeStr) self.updateTableLabel.setStringValue_(downloadLabel) self.updateTableLabel.setEnabled_(True) self.updateTableLabel.setTextColor_(NSColor.controlTextColor()) self.updateTableImage.setImage_(self.updatesToDownloadImage) def countDownloads(self): LogDebug("countDownloads") self.downloads = list() self.downloadTotalSize = 0 for package in self.updates: if self.cache.isCached_(package.sha1()): package.setImage_(self.cachedImage) else: package.setImage_(self.uncachedImage) self.downloadTotalSize += package.size() self.downloads.append(package) self.updateTable.reloadData() self.showRemainingDownloads() self.updateHeight() # External state of controller. def allUpdatesDownloaded(self): if self.applyUpdatesCheckbox.state() == NSOffState: return True return len(self.downloads) == 0 def packagesToInstall(self): if self.applyUpdatesCheckbox.state() == NSOffState: return [] return self.updates # Act on profile update requested. @LogException @IBAction def checkForProfileUpdates_(self, sender): self.silent = False self.doCheckForProfileUpdates() def checkForProfileUpdatesSilently(self): self.silent = True self.doCheckForProfileUpdates() def doCheckForProfileUpdates(self): LogInfo("Checking for updates") self.dateBeforeUpdating = self.profileController.publicationDate self.disableControls() defaults = NSUserDefaults.standardUserDefaults() url = NSURL.URLWithString_(defaults.stringForKey_("UpdateProfilesURL")) self.profileController.updateFromURL_(url) @LogException @IBAction def cancelProfileUpdateCheck_(self, sender): self.profileController.cancelUpdateDownload() # IEDProfileController delegate methods. def profileUpdateAllDone(self): self.enableControls() if not self.silent: alert = NSAlert.alloc().init() formatter = NSDateFormatter.alloc().init() formatter.setDateFormat_("yyyy-MM-dd HH.mm") dateStr = formatter.stringFromDate_( self.profileController.publicationDate) if self.dateBeforeUpdating != self.profileController.publicationDate: alert.setMessageText_("Profile updated") alert.setInformativeText_("Publication date: %s" % dateStr) else: alert.setMessageText_("No profile update available") alert.setInformativeText_("Last update: %s" % dateStr) alert.runModal() def profileUpdateFailed_(self, error): alert = NSAlert.alloc().init() alert.setMessageText_(error.localizedDescription()) alert.setInformativeText_(error.userInfo()[NSErrorFailingURLStringKey]) alert.runModal() self.silent = True def profileUpdateSucceeded_(self, publicationDate): LogDebug("profileUpdateSucceeded:%@", publicationDate) defaults = NSUserDefaults.standardUserDefaults() defaults.setObject_forKey_(NSDate.date(), "LastUpdateProfileCheck") def profilesUpdated(self): LogDebug("profilesUpdated") self.cache.pruneAndCreateSymlinks(self.profileController.updatePaths) if self.version or self.build: self.loadProfileForVersion_build_(self.version, self.build) # Load update profile. def loadProfileForVersion_build_(self, version, build): LogDebug("loadProfileForVersion:%@ build:%@", version, build) self.version = version self.build = build self.updates = list() profile = self.profileController.profileForVersion_Build_( version, build) if profile is None: # No update profile for this build, try to figure out why. self.profileWarning = self.profileController.whyNoProfileForVersion_build_( version, build) else: if self.profileController.deprecatedOS: self.profileWarning = "No longer updated by Apple" else: self.profileWarning = None for update in profile: package = IEDPackage.alloc().init() package.setName_(update["name"]) package.setPath_(self.cache.updatePath_(update["sha1"])) package.setSize_(update["size"]) package.setUrl_(update["url"]) package.setSha1_(update["sha1"]) # Image is set by countDownloads(). self.updates.append(package) self.countDownloads() # Act on apply updates checkbox changing. @LogException @IBAction def applyUpdatesCheckboxChanged_(self, sender): if self.delegate: self.delegate.updateControllerChanged() # Act on download button being clicked. @LogException @IBAction def downloadButtonClicked_(self, sender): self.disableControls() self.downloadLabel.setStringValue_("") self.downloadProgressBar.setIndeterminate_(True) self.downloadWindow.makeKeyAndOrderFront_(self) self.downloadCounter = 0 self.downloadNumUpdates = len(self.downloads) self.cache.downloadUpdates_(self.downloads) # Act on download stop button being clicked. @LogException @IBAction def downloadStopButtonClicked_(self, sender): self.cache.stopDownload() # UpdateCache delegate methods. def downloadAllDone(self): LogDebug("downloadAllDone") self.downloadWindow.orderOut_(self) self.countDownloads() self.enableControls() if self.delegate: self.delegate.updateControllerChanged() def downloadStarting_(self, package): LogDebug("downloadStarting:") self.downloadProgressBar.setIndeterminate_(False) self.downloadProgressBar.setDoubleValue_(0.0) self.downloadProgressBar.setMaxValue_(package.size()) self.downloadCounter += 1 self.downloadLabel.setStringValue_( "%s (%s)" % (package.name(), IEDUtil.formatByteSize_(package.size()))) def downloadStarted_(self, package): LogDebug("downloadStarted:") self.downloadStopButton.setEnabled_(True) def downloadStopped_(self, package): LogDebug("downloadStopped:") self.downloadStopButton.setEnabled_(False) def downloadGotData_bytesRead_(self, package, bytes): self.downloadProgressBar.setDoubleValue_(bytes) def downloadSucceeded_(self, package): LogDebug("downloadSucceeded:") self.countDownloads() if self.delegate: self.delegate.updateControllerChanged() def downloadFailed_withError_(self, package, message): LogDebug("downloadFailed:withError:") alert = NSAlert.alloc().init() alert.setMessageText_("Download failed") alert.setInformativeText_(message) alert.runModal() # We're an NSTableViewDataSource. def numberOfRowsInTableView_(self, tableView): return len(self.updates) def tableView_objectValueForTableColumn_row_(self, tableView, column, row): # FIXME: Use bindings. if column.identifier() == "image": return self.updates[row].image() elif column.identifier() == "name": return self.updates[row].name()
class PreferencesController(NSWindowController): # window windowObjc = IBOutlet() tabViewObjc = IBOutlet() # General generalTitle = IBOutlet() uiLanguageLabel = IBOutlet() uiLanguage = IBOutlet() destLanguageLabel = IBOutlet() destLanguage = IBOutlet() translationServiceLabel = IBOutlet() translationService = IBOutlet() ui_language_helper = None dest_language_helper = None translation_service_helper = None # Google googleServiceURLLabel = IBOutlet() googleServiceURL = IBOutlet() google_service_url_helper = None # Support sentryDsn = IBOutlet() def windowDidLoad(self): NSWindowController.windowDidLoad(self) # window self.windowObjc.setValue_forKey_(_('Preferences'), 'title') # tab view tab_view_objc = self.tabViewObjc.valueForKey_('tabViewItems') # General tab_view_objc.objectAtIndex_(0).setValue_forKey_(_('General'), 'label') self.uiLanguageLabel.setStringValue_(_('UI Language')) self.ui_language_helper = PopUpButtonHelper( objc_obj=self.uiLanguage, values=Languages, selected_value=config.Common.ui_language, text_map=Languages.text_map(), ) self.destLanguageLabel.setStringValue_(_('Destination Language')) self.dest_language_helper = PopUpButtonHelper( objc_obj=self.destLanguage, values=Languages, selected_value=config.Common.dest_language, text_map=Languages.text_map(), ) self.translationServiceLabel.setStringValue_(_('Translation Service')) self.translation_service_helper = PopUpButtonHelper( objc_obj=self.translationService, values=TranslationServices, selected_value=config.Common.translation_service, text_map=TranslationServices.text_map(), ) # Google self.googleServiceURLLabel.setStringValue_(_('Service URL')) self.google_service_url_helper = PopUpButtonHelper( objc_obj=self.googleServiceURL, values=GOOGLE_SERVICE_URLS, selected_value=config.Google.service_url, text_map=None, ) # Support self.sentryDsn.setStringValue_(config.Support.sentry_dsn) @IBAction def uiLanguage_(self, _): config.Common.ui_language = self \ .ui_language_helper.selected_value config.dump() i18n() @IBAction def destLanguage_(self, _): config.Common.dest_language = self \ .dest_language_helper.selected_value config.dump() @IBAction def translationService_(self, _): config.Common.translation_service = self \ .translation_service_helper.selected_value config.dump() @IBAction def googleServiceURL_(self, _): config.Google.service_url = self \ .google_service_url_helper.selected_value config.dump() @IBAction def sentryDsn_(self, _): config.Support.sentry_dsn = self.sentryDsn.stringValue() config.dump()
class LLLogWindowController(NSObject): window = IBOutlet() logView = IBOutlet() backdropWindow = IBOutlet() logFileData = LLLogViewDataSource.alloc().init() fileHandle = None updateTimer = None def showLogWindow_(self, title): # Base all sizes on the screen's dimensions. screenRect = NSScreen.mainScreen().frame() # Open a log window that covers most of the screen. self.window.setTitle_(title) self.window.setCanBecomeVisibleWithoutLogin_(True) self.window.setLevel_(NSStatusWindowLevel) self.window.orderFrontRegardless() # Resize the log window so that it leaves a border on all sides. # Add a little extra border at the bottom so we don't cover the # loginwindow message. windowRect = screenRect.copy() windowRect.origin.x = 100.0 windowRect.origin.y = 200.0 windowRect.size.width -= 200.0 windowRect.size.height -= 300.0 NSAnimationContext.beginGrouping() NSAnimationContext.currentContext().setDuration_(0.5) self.window.animator().setFrame_display_(windowRect, True) NSAnimationContext.endGrouping() # Create a transparent, black backdrop window that covers the whole # screen and fade it in slowly. self.WindowArray = NSMutableArray.new() for screen in NSScreen.screens(): Rect = screen.frame() backdropWindow = NSWindow.alloc( ).initWithContentRect_styleMask_backing_defer_( Rect, NSBorderlessWindowMask, NSBackingStoreBuffered, NO) backdropWindow.setCanBecomeVisibleWithoutLogin_(True) backdropWindow.setLevel_(NSStatusWindowLevel) #backdropWindow.setFrame_display_(screenRect, True) translucentColor = NSColor.blackColor().colorWithAlphaComponent_( 0.8) backdropWindow.setBackgroundColor_(translucentColor) backdropWindow.setOpaque_(False) backdropWindow.setIgnoresMouseEvents_(True) backdropWindow.setAlphaValue_(0.0) backdropWindow.orderFrontRegardless() NSAnimationContext.beginGrouping() NSAnimationContext.currentContext().setDuration_(1.0) backdropWindow.animator().setAlphaValue_(1.0) NSAnimationContext.endGrouping() self.WindowArray.addObject_(backdropWindow) backdropWindow.makeKeyAndOrderFront_(NSApp) def watchLogFile_(self, logFile): # Display and continuously update a log file in the main window. self.stopWatching() self.logFileData.removeAllLines() self.logView.setDataSource_(self.logFileData) self.logView.reloadData() self.fileHandle = NSFileHandle.fileHandleForReadingAtPath_(logFile) self.refreshLog() # Kick off a timer that updates the log view periodically. self.updateTimer = NSTimer.scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_( 0.25, self, u"refreshLog", None, YES) def stopWatching(self): # Release the file handle and stop the update timer. if self.fileHandle is not None: self.fileHandle.closeFile() self.fileHandle = None if self.updateTimer is not None: self.updateTimer.invalidate() self.updateTimer = None def refreshLog(self): # Check for new available data, read it, and scroll to the bottom. data = self.fileHandle.availableData() if data.length(): utf8string = NSString.alloc().initWithData_encoding_( data, NSUTF8StringEncoding) for line in utf8string.splitlines(True): if line.endswith(u"\n"): self.logFileData.addLine_partial_(line.rstrip(u"\n"), False) else: self.logFileData.addLine_partial_(line, True) self.logView.reloadData() self.logView.scrollRowToVisible_(self.logFileData.lineCount() - 1)
class ExperienceController(NSWindowController): # outlets for UI elements experienceText = IBOutlet() recordButton = IBOutlet() playAudioButton = IBOutlet() deleteAudioButton = IBOutlet() progressBar = IBOutlet() recordingAudio = False playingAudio = False audio_file = '' @IBAction def recordText_(self, sender): t = cfg.NOW() message = self.experienceText.stringValue() e = Experience(t, message) self.sniffer.activity_tracker.storage.session.add(e) # may not need to commit here, but could wait till next round of parsing self.sniffer.activity_tracker.storage.sqlcommit() print 'Received experience message of: ' + message self.expController.close() @IBAction def toggleAudioRecording_(self, sender): if self.recordingAudio: self.recordingAudio = False print "Stop audio message" # get the right name for our audio file dt = datetime.now().strftime("%y%m%d-%H%M%S%f") audioName = str(os.path.join(cfg.CURRENT_DIR, "audio/")) + dt + '.m4a' self.audio_file = audioName audioName = string.replace(audioName, "/", ":") audioName = audioName[1:] # start the audio recording s = NSAppleScript.alloc().initWithSource_("set filePath to \"" + audioName + "\" \n set placetosaveFile to a reference to file filePath \n tell application \"QuickTime Player\" \n set mydocument to document 1 \n tell document 1 \n stop \n end tell \n set newRecordingDoc to first document whose name = \"untitled\" \n export newRecordingDoc in placetosaveFile using settings preset \"Audio Only\" \n close newRecordingDoc without saving \n quit \n end tell") s.executeAndReturnError_(None) # log the experience in our table t = cfg.NOW() e = Experience(t, dt + '.m4a') self.sniffer.activity_tracker.storage.session.add(e) # may not need to commit here, but could wait till next round of parsing self.sniffer.activity_tracker.storage.sqlcommit() # reset controls self.expController.recordButton.setTitle_("Record") self.expController.recordButton.setEnabled_(False) self.expController.playAudioButton.setHidden_(False) self.expController.deleteAudioButton.setHidden_(False) self.expController.progressBar.setHidden_(False) else: self.recordingAudio = True print "Start audio message" s = NSAppleScript.alloc().initWithSource_("tell application \"QuickTime Player\" \n set new_recording to (new audio recording) \n tell new_recording \n start \n end tell \n tell application \"System Events\" \n set visible of process \"QuickTime Player\" to false \n repeat until visible of process \"QuickTime Player\" is false \n end repeat \n end tell \n end tell") s.executeAndReturnError_(None) self.expController.recordButton.setTitle_("Stop Recording") # TODO change button color to red while recording @IBAction def toggleAudioPlay_(self, sender): if self.playingAudio: self.stopAudioPlay() else: self.playingAudio = True self.expController.playAudioButton.setTitle_("Stop") s = NSAppleScript.alloc().initWithSource_("set filePath to POSIX file \"" + self.audio_file + "\" \n tell application \"QuickTime Player\" \n open filePath \n tell application \"System Events\" \n set visible of process \"QuickTime Player\" to false \n repeat until visible of process \"QuickTime Player\" is false \n end repeat \n end tell \n play the front document \n end tell") s.executeAndReturnError_(None) # Stop playback once end of audio file is reached length = mutagen.mp4.MP4(self.audio_file).info.length stop_thread = threading.Timer(length, self.stopAudioPlay) stop_thread.start() if length >= 1.0: advance_thread = threading.Timer(1.0, self.incrementProgressBar, [1.0, length]) advance_thread.start() # s = objc.selector(self.stopAudioPlay,signature='v@:') # self.playbackTimer = NSTimer.scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(length, self, s, None, False) def incrementProgressBar(self, t, m): print "Got to incrementor" i = 1.0/m * 100.0 self.expController.progressBar.incrementBy_(i) if t < m - 1.0: t += 1.0 advance_thread = threading.Timer(1.0, self.incrementProgressBar, [t, m]) advance_thread.start() def stopAudioPlay(self): self.playingAudio = False self.expController.playAudioButton.setTitle_("Play") value = self.expController.progressBar.doubleValue() self.expController.progressBar.incrementBy_(100.0 - value) self.expController.progressBar.incrementBy_(-100.0) s = NSAppleScript.alloc().initWithSource_("tell application \"QuickTime Player\" \n stop the front document \n close the front document \n end tell") s.executeAndReturnError_(None) @IBAction def deleteAudio_(self, sender): if (self.audio_file != '') & (self.audio_file != None) : if os.path.exists(self.audio_file): os.remove(self.audio_file) self.audio_file = '' # reset button visibility self.expController.recordButton.setEnabled_(True) self.expController.playAudioButton.setHidden_(True) self.expController.deleteAudioButton.setHidden_(True) self.expController.progressBar.setHidden_(True) # override window close to track when users close the experience window def overrideClose(self): s = objc.selector(self.setIgnoredAndClose_,signature='v@:@') self.expController.window().standardWindowButton_(NSWindowCloseButton).setTarget_(self.expController) self.expController.window().standardWindowButton_(NSWindowCloseButton).setAction_(s) self.expController.window().standardWindowButton_(NSWindowCloseButton).setKeyEquivalentModifierMask_(NSCommandKeyMask) self.expController.window().standardWindowButton_(NSWindowCloseButton).setKeyEquivalent_("w") def setIgnoredAndClose_(self, notification): self.ignored = True NSNotificationCenter.defaultCenter().postNotificationName_object_('experienceReceived',self) self.expController.close() def windowDidLoad(self): NSWindowController.windowDidLoad(self) def show(self, sniffer): try: # close the window if its already open if self.expController.window().isVisible(): self.expController.close() except: pass self.sniffer = sniffer # open window from NIB file, show front and center self.expController = ExperienceController.alloc().initWithWindowNibName_("experience") self.expController.showWindow_(None) self.expController.window().makeKeyAndOrderFront_(None) self.expController.window().center() self.expController.retain() # needed to show window on top of other applications # NSNotificationCenter.defaultCenter().postNotificationName_object_('makeAppActive',self) self.sniffer.app.activateIgnoringOtherApps_(True) self.overrideClose(self) return self.expController show = classmethod(show)
class PreferencesController(NSWindowController): # outlets for UI elements screenshotSizePopup = IBOutlet() screenshotSizeMenu = IBOutlet() clearDataPopup = IBOutlet() sniffer = None # handle changes to the preferences @IBAction def changeScreenshot_(self, sender): screenshots = getValueForPreference('screenshots') periodic = getValueForPreference('periodicScreenshots') if screenshots and periodic: self.sniffer.activity_tracker.startLoops() elif not screenshots or not periodic: self.sniffer.activity_tracker.stopLoops() @IBAction def changePeriodicScreenshots_(self, sender): periodic = getValueForPreference('periodicScreenshots') if periodic: self.sniffer.activity_tracker.startLoops() else: self.sniffer.activity_tracker.stopLoops() @IBAction def changePeriodicRate_(self, sender): self.sniffer.activity_tracker.restartScreenshotLoop() @IBAction def changeKeystrokeRecording_(self, sender): print "You asked us to start/stop keystroke recording" @IBAction def changeBookmark_(self, sender): print "You asked us to start/stop periodic bookmarking" @IBAction def changeBookmarkRate_(self, sender): print "You asked us to change the bookmark rate" @IBAction def clearData_(self, sender): self.sniffer.activity_tracker.clearData() def windowDidLoad(self): NSWindowController.windowDidLoad(self) # Set screenshot size options based on screen's native height self.prefController.screenshotSizeMenu.removeAllItems() nativeHeight = int(NSScreen.mainScreen().frame().size.height) menuitem = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_( str(nativeHeight) + ' px', '', '') menuitem.setTag_(nativeHeight) self.prefController.screenshotSizeMenu.addItem_(menuitem) sizes = [1080, 720, 480] for x in sizes: if x < nativeHeight: menuitem = NSMenuItem.alloc( ).initWithTitle_action_keyEquivalent_(str(x) + ' px', '', '') menuitem.setTag_(x) self.prefController.screenshotSizeMenu.addItem_(menuitem) # update newly created screenshot size dropdown to select saved preference or default size selectedSize = NSUserDefaultsController.sharedUserDefaultsController( ).values().valueForKey_('imageSize') selectedMenuItem = self.prefController.screenshotSizeMenu.itemWithTag_( selectedSize) if (selectedMenuItem): self.prefController.screenshotSizePopup.selectItemWithTag_( selectedSize) else: nativeMenuItem = self.prefController.screenshotSizeMenu.itemWithTag_( nativeHeight) NSUserDefaultsController.sharedUserDefaultsController().defaults( ).setInteger_forKey_(nativeHeight, 'imageSize') self.prefController.screenshotSizePopup.selectItemWithTag_( nativeHeight) def show(self, sniffer): try: if self.prefController: self.prefController.close() except: pass self.sniffer = sniffer # open window from NIB file, show front and center self.prefController = PreferencesController.alloc( ).initWithWindowNibName_("preferences") self.prefController.showWindow_(None) self.prefController.window().makeKeyAndOrderFront_(None) self.prefController.window().center() self.prefController.retain() # NSNotificationCenter.defaultCenter().postNotificationName_object_('makeAppActive',self) self.sniffer.app.activateIgnoringOtherApps_(True) # make window close on Cmd-w self.prefController.window().standardWindowButton_( NSWindowCloseButton).setKeyEquivalentModifierMask_( NSCommandKeyMask) self.prefController.window().standardWindowButton_( NSWindowCloseButton).setKeyEquivalent_("w") return self.prefController show = classmethod(show)
class MSCLogWindowController(NSObject): '''Controller object for our log window''' # since this subclasses NSObject, # it doesn't have a Python __init__method # pylint: disable=no-init window = IBOutlet() logView = IBOutlet() searchField = IBOutlet() pathControl = IBOutlet() logFileData = MSCLogViewDataSource.alloc().init() fileHandle = None updateTimer = None def copy_(self, sender): '''Implements copy operation so we can copy data from table view''' text_to_copy = '' index_set = self.logView.selectedRowIndexes() index = index_set.firstIndex() while index != NSNotFound: line = self.logFileData.filteredData.objectAtIndex_(index) text_to_copy += line + '\n' index = index_set.indexGreaterThanIndex_(index) pasteboard = NSPasteboard.generalPasteboard() changeCount = pasteboard.clearContents() result = pasteboard.writeObjects_([text_to_copy]) @IBAction def searchFilterChanged_(self, sender): '''User changed the search field''' filterString = self.searchField.stringValue().lower() self.logFileData.filterText = filterString self.logFileData.applyFilterToData() self.logView.reloadData() @IBAction def showLogWindow_(self, notification): '''Show the log window.''' if self.window.isVisible(): # It's already open, just move it to front self.window.makeKeyAndOrderFront_(self) return screenRect = NSScreen.mainScreen().frame() windowRect = screenRect.copy() windowRect.origin.x = 100.0 windowRect.origin.y = 200.0 windowRect.size.width -= 200.0 windowRect.size.height -= 300.0 logfile = munki.pref('LogFile') self.pathControl.setURL_(NSURL.fileURLWithPath_(logfile)) self.window.setTitle_(os.path.basename(logfile)) self.window.setFrame_display_(windowRect, NO) self.window.makeKeyAndOrderFront_(self) self.watchLogFile_(logfile) # allow dragging from table view to outside of the app self.logView.setDraggingSourceOperationMask_forLocal_( NSDragOperationAll, NO) def watchLogFile_(self, logFile): '''Display and continuously update a log file in the main window.''' self.stopWatching() self.logFileData.removeAllLines() self.logView.setDataSource_(self.logFileData) self.logView.reloadData() self.fileHandle = NSFileHandle.fileHandleForReadingAtPath_(logFile) self.refreshLog() # Kick off a timer that updates the log view periodically. self.updateTimer = ( NSTimer. scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_( 0.25, self, self.refreshLog, None, YES)) def stopWatching(self): '''Release the file handle and stop the update timer.''' if self.fileHandle is not None: self.fileHandle.closeFile() self.fileHandle = None if self.updateTimer is not None: self.updateTimer.invalidate() self.updateTimer = None def refreshLog(self): '''Check for new available data, read it, and scroll to the bottom.''' data = self.fileHandle.availableData() if data.length(): utf8string = NSString.alloc().initWithData_encoding_( data, NSUTF8StringEncoding) for line in utf8string.splitlines(True): if line.endswith(u"\n"): self.logFileData.addLine_partial_(line.rstrip(u"\n"), False) else: self.logFileData.addLine_partial_(line, True) self.logView.reloadData() self.logView.scrollRowToVisible_(self.logFileData.lineCount() - 1) def windowWillClose_(self, notification): '''NSWindow delegate method -- if our window is closing, stop watching the log file.''' self.stopWatching()
class MSCAppDelegate(NSObject): mainWindowController = IBOutlet() statusController = IBOutlet() passwordAlertController = IBOutlet() def applicationShouldTerminate_(self, sender): '''Called if user selects 'Quit' from menu''' return self.mainWindowController.appShouldTerminate() def applicationDidFinishLaunching_(self, sender): '''NSApplication delegate method called at launch''' NSLog("Finished launching") # setup client logging msclog.setup_logging() # userInfo dict can be nil, seems to be with 10.6 if sender.userInfo(): userNotification = sender.userInfo().get( 'NSApplicationLaunchUserNotificationKey') # we get this notification at launch because it's too early to have declared ourself # a NSUserNotificationCenterDelegate if userNotification: NSLog("Launched via Notification interaction") self.userNotificationCenter_didActivateNotification_( NSUserNotificationCenter.defaultUserNotificationCenter(), userNotification) # Prevent automatic relaunching at login on Lion+ if NSApp.respondsToSelector_('disableRelaunchOnLogin'): NSApp.disableRelaunchOnLogin() ver = NSBundle.mainBundle().infoDictionary().get( 'CFBundleShortVersionString') msclog.log("MSC", "launched", "VER=%s" % ver) # if we're running under Snow Leopard, swap out the Dock icon for one # without the Retina assets to avoid an appearance issue when the # icon has a badge in the Dock (and App Switcher) # Darwin major version 10 is Snow Leopard (10.6) if os.uname()[2].split('.')[0] == '10': myImage = NSImage.imageNamed_("Managed Software Center 10_6") NSApp.setApplicationIconImage_(myImage) # if we are running under Mountain Lion or later set ourselves as a delegate # for NSUserNotificationCenter notifications if os.uname()[2].split('.')[0] > '11': NSUserNotificationCenter.defaultUserNotificationCenter( ).setDelegate_(self) # have the statuscontroller register for its own notifications self.statusController.registerForNotifications() # user may have launched the app manually, or it may have # been launched by /usr/local/munki/managedsoftwareupdate # to display available updates if munki.thereAreUpdatesToBeForcedSoon(hours=2): # skip the check and just display the updates # by pretending the lastcheck is now lastcheck = NSDate.date() else: lastcheck = munki.pref('LastCheckDate') max_cache_age = munki.pref('CheckResultsCacheSeconds') # if there is no lastcheck timestamp, check for updates. if not lastcheck: self.mainWindowController.checkForUpdates() elif lastcheck.timeIntervalSinceNow() * -1 > int(max_cache_age): # check for updates if the last check is over the # configured manualcheck cache age max. self.mainWindowController.checkForUpdates() elif MunkiItems.updateCheckNeeded(): # check for updates if we have optional items selected for install # or removal that have not yet been processed self.mainWindowController.checkForUpdates() # load the initial view only if we are not already loading something else. # enables launching the app to a specific panel, eg. from URL handler if not self.mainWindowController.webView.isLoading(): self.mainWindowController.loadInitialView() def applicationWillFinishLaunching_(self, notification): '''Installs URL handler for calls outside the app eg. web clicks''' man = NSAppleEventManager.sharedAppleEventManager() man.setEventHandler_andSelector_forEventClass_andEventID_( self, "openURL:withReplyEvent:", struct.unpack(">i", "GURL")[0], struct.unpack(">i", "GURL")[0]) def openMunkiURL(self, url): '''Display page associated with munki:// url''' parsed_url = urlparse(url) if parsed_url.scheme != 'munki': msclog.debug_log("URL %s has unsupported scheme" % url) return filename = mschtml.unquote(parsed_url.netloc) # add .html if no extension if not os.path.splitext(filename)[1]: filename += u'.html' if filename.endswith(u'.html'): mschtml.build_page(filename) self.mainWindowController.load_page(filename) else: msclog.debug_log( "%s doesn't have a valid extension. Prevented from opening" % url) def openURL_withReplyEvent_(self, event, replyEvent): '''Handle openURL messages''' keyDirectObject = struct.unpack(">i", "----")[0] url = event.paramDescriptorForKeyword_( keyDirectObject).stringValue().decode('utf8') msclog.log("MSU", "Called by external URL: %s", url) self.openMunkiURL(url) def userNotificationCenter_didActivateNotification_( self, center, notification): '''User clicked on a Notification Center alert''' user_info = notification.userInfo() if user_info.get('action') == 'open_url': url = user_info.get('value', 'munki://updates') msclog.log("MSU", "Got user notification to open %s" % url) self.openMunkiURL(url) center.removeDeliveredNotification_(notification) else: msclog.log("MSU", "Got user notification with unrecognized userInfo") def userNotificationCenter_shouldPresentNotification_( self, center, notification): return True def userNotificationCenter_didDeliverNotification_(self, center, notification): pass
class IEDAddPkgController(NSObject): addPkgLabel = IBOutlet() tableView = IBOutlet() removeButton = IBOutlet() movedRowsType = "se.gu.it.AdditionalPackages" def init(self): self = super(IEDAddPkgController, self).init() if self is None: return None self.packages = list() self.packagePaths = set() return self def awakeFromNib(self): self.tableView.setDataSource_(self) self.tableView.registerForDraggedTypes_( [NSFilenamesPboardType, IEDAddPkgController.movedRowsType]) self.dragEnabled = True # Helper methods. def disableControls(self): self.dragEnabled = False self.addPkgLabel.setTextColor_(NSColor.disabledControlTextColor()) self.tableView.setEnabled_(False) self.removeButton.setEnabled_(False) def enableControls(self): self.dragEnabled = True self.addPkgLabel.setTextColor_(NSColor.controlTextColor()) self.tableView.setEnabled_(True) self.removeButton.setEnabled_(True) # External state of controller. def packagesToInstall(self): return self.packages # Loading. def replacePackagesWithPaths_(self, packagePaths): del self.packages[:] self.packagePaths.clear() for path in packagePaths: package = IEDPackage.alloc().init() package.setName_(os.path.basename(path)) package.setPath_(path) package.setSize_(IEDUtil.getPackageSize_(path)) package.setImage_(NSWorkspace.sharedWorkspace().iconForFile_(path)) self.packages.append(package) self.packagePaths.add(path) self.tableView.reloadData() # Act on remove button. @LogException @IBAction def removeButtonClicked_(self, sender): indexes = self.tableView.selectedRowIndexes() row = indexes.lastIndex() while row != NSNotFound: self.packagePaths.remove(self.packages[row].path()) del self.packages[row] row = indexes.indexLessThanIndex_(row) self.tableView.reloadData() self.tableView.deselectAll_(self) # We're an NSTableViewDataSource. def numberOfRowsInTableView_(self, tableView): return len(self.packages) def tableView_objectValueForTableColumn_row_(self, tableView, column, row): # FIXME: Use bindings. if column.identifier() == "image": return self.packages[row].image() elif column.identifier() == "name": return self.packages[row].name() def tableView_validateDrop_proposedRow_proposedDropOperation_( self, tableView, info, row, operation): if not self.dragEnabled: return NSDragOperationNone if info.draggingSource() == tableView: return NSDragOperationMove pboard = info.draggingPasteboard() paths = [ IEDUtil.resolvePath_(path) for path in pboard.propertyListForType_(NSFilenamesPboardType) ] if not paths: return NSDragOperationNone for path in paths: # Don't allow multiple copies. if path in self.packagePaths: return NSDragOperationNone # Ensure the file extension is valid for additonal packages. name, ext = os.path.splitext(path) if ext.lower() not in IEDUtil.PACKAGE_EXTENSIONS: return NSDragOperationNone return NSDragOperationCopy def tableView_acceptDrop_row_dropOperation_(self, tableView, info, row, operation): if not self.dragEnabled: return False pboard = info.draggingPasteboard() # If the source is the tableView, we're reordering packages within the # table and the pboard contains the source row indexes. if info.draggingSource() == tableView: indexes = [ int(i) for i in pboard.propertyListForType_( IEDAddPkgController.movedRowsType).split(",") ] # If the rows are dropped on top of another line, and the target # row is below the first source row, move the target row one line # down. if (operation == NSTableViewDropOn) and (indexes[0] < row): rowAdjust = 1 else: rowAdjust = 0 # Move the dragged rows out from the package list into draggedRows. draggedRows = list() for i in sorted(indexes, reverse=True): draggedRows.insert(0, (i, self.packages.pop(i))) # Adjust the target row since we have removed items. row -= len([x for x in draggedRows if x[0] < row]) row += rowAdjust # Insert them at the new place. for i, (index, item) in enumerate(draggedRows): self.packages.insert(row + i, item) # Select the newly moved lines. selectedIndexes = NSIndexSet.indexSetWithIndexesInRange_( NSMakeRange(row, len(draggedRows))) tableView.selectRowIndexes_byExtendingSelection_( selectedIndexes, False) else: # Otherwise it's a list of paths to add to the table. paths = [ IEDUtil.resolvePath_(path) for path in pboard.propertyListForType_(NSFilenamesPboardType) ] # Remove duplicates from list. seen = set() paths = [x for x in paths if x not in seen and not seen.add(x)] for i, path in enumerate(paths): package = IEDPackage.alloc().init() package.setName_(os.path.basename(path)) package.setPath_(path) package.setSize_(IEDUtil.getPackageSize_(path)) package.setImage_( NSWorkspace.sharedWorkspace().iconForFile_(path)) self.packages.insert(row + i, package) self.packagePaths.add(path) tableView.reloadData() return True def tableView_writeRowsWithIndexes_toPasteboard_(self, tableView, rowIndexes, pboard): # When reordering packages put a list of indexes as a string onto the pboard. indexes = list() index = rowIndexes.firstIndex() while index != NSNotFound: indexes.append(index) index = rowIndexes.indexGreaterThanIndex_(index) pboard.declareTypes_owner_([IEDAddPkgController.movedRowsType], self) pboard.setPropertyList_forType_(",".join(str(i) for i in indexes), IEDAddPkgController.movedRowsType) return True