Merged MunkiStatus.app and Managed Software Update.app. Added UI for Optional Software.

git-svn-id: http://munki.googlecode.com/svn/trunk@579 a4e17f2e-e282-11dd-95e1-755cbddbdd66
This commit is contained in:
Greg Neagle
2010-07-21 02:52:21 +00:00
parent c43ff233fc
commit 117ef8d23f
16 changed files with 4620 additions and 1044 deletions
@@ -0,0 +1,31 @@
//
// BorderlessWindow.h
//
// Created by Greg Neagle on 5/16/09.
// Copyright 2009 Greg Neagle.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#import <Cocoa/Cocoa.h>
@interface BorderlessWindow : NSWindow
- (id) initWithContentRect: (NSRect) contentRect
styleMask: (unsigned int) aStyle
backing: (NSBackingStoreType) bufferingType
defer: (BOOL) flag;
@end
@@ -0,0 +1,41 @@
//
// BorderlessWindow.m
//
// Created by Greg Neagle on 5/16/09.
// Copyright 2009 Greg Neagle.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#import "BorderlessWindow.h"
@implementation BorderlessWindow
- (id) initWithContentRect: (NSRect) contentRect
styleMask: (unsigned int) aStyle
backing: (NSBackingStoreType) bufferingType
defer: (BOOL) flag
{
if (self = [super initWithContentRect: contentRect
styleMask: NSBorderlessWindowMask
backing: bufferingType
defer: flag])
{
// other initialization
}
return self;
}
@end
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -17,11 +17,11 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>2.0.2</string>
<string>3.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>2.0.2</string>
<string>3.0</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
@@ -0,0 +1,367 @@
#
# MSUAppDelegate.py
# Managed Software Update
#
# Created by Greg Neagle on 2/10/10.
# Copyright 2009-2010 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from Foundation import *
from AppKit import *
from objc import YES, NO
import os
import munki
import PyObjCTools
def getconsoleuser():
from SystemConfiguration import SCDynamicStoreCopyConsoleUser
cfuser = SCDynamicStoreCopyConsoleUser( None, None, None )
return cfuser[0]
class MSUAppDelegate(NSObject):
munkiStatusController = objc.IBOutlet()
mainWindowController = objc.IBOutlet()
update_view_controller = objc.IBOutlet()
update_array_controller = objc.IBOutlet()
optional_view_controller = objc.IBOutlet()
optional_array_controller = objc.IBOutlet()
_emptyImage = NSImage.imageNamed_("Empty.png")
_restartImage = NSImage.imageNamed_("RestartReq.tif")
_logoutImage = NSImage.imageNamed_("LogOutReq.tif")
_listofupdates = []
_optionalInstalls = []
restart_required = False
logout_required = False
runmode = "Normal"
managedsoftwareupdate_task = None
def applicationDidFinishLaunching_(self, sender):
NSLog(u"Managed Software Update finished launching.")
runmode = NSUserDefaults.standardUserDefaults().stringForKey_("mode") or \
os.environ.get("ManagedSoftwareUpdateMode")
if runmode:
self.runmode = runmode
NSLog("Runmode: %s" % runmode)
consoleuser = getconsoleuser()
if consoleuser == None or consoleuser == u"loginwindow":
# Status Window only
NSMenu.setMenuBarVisible_(NO)
self.munkiStatusController.startMunkiStatusSession()
elif self.runmode == "MunkiStatus":
self.munkiStatusController.startMunkiStatusSession()
else:
# display updates if available; if no available updates
# trigger an update check
if not self._listofupdates:
self.getAvailableUpdates()
if self._listofupdates:
self.buildUpdateTableData()
self.mainWindowController.theWindow.makeKeyAndOrderFront_(self)
NSApp.requestUserAttention_(NSCriticalRequest)
if self._optionalInstalls:
self.buildOptionalInstallsData()
else:
# no updates available. Should we check for some?
self.checkForUpdates()
def munkiStatusSessionEnded_(self, socketSessionResult):
consoleuser = getconsoleuser()
if self.runmode == "MunkiStatus" or consoleuser == None or consoleuser == u"loginwindow":
# Status Window only, so we should just quit
NSApp.terminate_(self)
alertMessageText = "Update check failed"
if self.managedsoftwareupdate_task == "installwithnologout":
alertMessageText = "Install session failed"
if socketSessionResult == -1:
# connection was dropped unexpectedly
self.mainWindowController.theWindow.makeKeyAndOrderFront_(self)
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(alertMessageText, u"Quit", objc.nil, objc.nil, "There is a configuration problem with the managed software installer. The process ended unexpectedly. Contact your systems administrator.")
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(self.mainWindowController.theWindow, self, self.quitAlertDidEnd_returnCode_contextInfo_, objc.nil)
return
elif socketSessionResult == -2:
# socket timed out before connection
self.mainWindowController.theWindow.makeKeyAndOrderFront_(self)
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(alertMessageText, u"Quit", objc.nil, objc.nil, "There is a configuration problem with the managed software installer. Could not start the process. Contact your systems administrator.")
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(self.mainWindowController.theWindow, self, self.quitAlertDidEnd_returnCode_contextInfo_, objc.nil)
return
if self.managedsoftwareupdate_task == "installwithnologout":
# we're done.
NSApp.terminate_(self)
elif self.managedsoftwareupdate_task == "manualcheck":
self.managedsoftwareupdate_task = None
self._listofupdates = []
self.getAvailableUpdates()
self.buildUpdateTableData()
if self._optionalInstalls:
self.buildOptionalInstallsData()
prefs = munki.getManagedInstallsPrefs()
lastCheckResult = prefs.get("LastCheckResult")
self.mainWindowController.theWindow.makeKeyAndOrderFront_(self)
if lastCheckResult == 0:
self.noUpdatesAlert()
elif lastCheckResult == 1:
NSApp.requestUserAttention_(NSCriticalRequest)
elif lastCheckResult == -1:
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(u"Cannot check for updates", u"Quit", objc.nil, objc.nil, "Managed Software Update cannot contact the update server at this time.\nIf this situtation continues, contact your systems administrator.")
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(self.mainWindowController.theWindow, self, self.quitAlertDidEnd_returnCode_contextInfo_, objc.nil)
elif lastCheckResult == -2:
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(u"Cannot check for updates", u"Quit", objc.nil, objc.nil, "Managed Software Update failed its preflight check.\nTry again later.")
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(self.mainWindowController.theWindow, self, self.quitAlertDidEnd_returnCode_contextInfo_, objc.nil)
def noUpdatesAlert(self):
if self._optionalInstalls:
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(u"Your software is up to date.", u"Quit", u"Optional software...", objc.nil, "There is no new software for your computer at this time.")
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(self.mainWindowController.theWindow, self, self.quitAlertDidEnd_returnCode_contextInfo_, objc.nil)
else:
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(u"Your software is up to date.", u"Quit", objc.nil, objc.nil, "There is no new software for your computer at this time.")
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(self.mainWindowController.theWindow, self, self.quitAlertDidEnd_returnCode_contextInfo_, objc.nil)
def checkForUpdates(self):
# kick off an update check
self.mainWindowController.theWindow.orderOut_(self)
result = munki.startUpdateCheck()
if result == 0:
self.managedsoftwareupdate_task = "manualcheck"
self.munkiStatusController.window.makeKeyAndOrderFront_(self)
self.munkiStatusController.startMunkiStatusSession()
else:
self.mainWindowController.theWindow.makeKeyAndOrderFront_(self)
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(u"Update check failed", u"Quit", objc.nil, objc.nil, "There is a configuration problem with the managed software installer. Could not start the update check process. Contact your systems administrator.")
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(self.mainWindowController.theWindow, self, self.quitAlertDidEnd_returnCode_contextInfo_, objc.nil)
def applicationDidBecomeActive_(self, sender):
pass
def getOptionalInstalls(self):
optionalInstalls = []
installinfo = munki.getInstallInfo()
if installinfo:
optionalInstalls = installinfo.get("optional_installs", [])
if optionalInstalls:
self._optionalInstalls = optionalInstalls
self.update_view_controller.optionalSoftwareBtn.setHidden_(NO)
else:
self.update_view_controller.optionalSoftwareBtn.setHidden_(YES)
def getAvailableUpdates(self):
updatelist = []
installinfo = munki.getInstallInfo()
if installinfo:
updatelist = installinfo.get("managed_installs", [])
if installinfo.get("removals"):
removallist = installinfo.get("removals")
restartNeeded = False
showRemovalDetail = munki.getRemovalDetailPrefs()
for item in removallist:
if item.get("RestartAction") == "RequireRestart" or item.get("RestartAction") == "RecommendRestart":
restartNeeded = True
if showRemovalDetail:
if display_name in item:
item["display_name"] = item["display_name"] + " (will be removed)"
elif name in item:
item["display_name"] = item["name"] + " (will be removed)"
updatelist.append(item)
if not showRemovalDetail:
row = {}
row["display_name"] = "Software removals"
row["version"] = ""
row["description"] = "Scheduled removal of managed software."
if restartNeeded:
row["RestartAction"] = "RequireRestart"
updatelist.append(row)
if updatelist:
self._listofupdates = updatelist
self.update_view_controller.updateNowBtn.setEnabled_(YES)
self.getOptionalInstalls()
else:
appleupdates = munki.getAppleUpdates()
if appleupdates:
self._listofupdates = appleupdates.get("AppleUpdates", [])
self.update_view_controller.updateNowBtn.setEnabled_(YES)
self.update_view_controller.optionalSoftwareBtn.setHidden_(YES)
else:
self.update_view_controller.updateNowBtn.setEnabled_(NO)
self.getOptionalInstalls()
def buildOptionalInstallsData(self):
table = []
for item in self._optionalInstalls:
row = {}
row['enabled'] = objc.YES
# current install state
row['installed'] = item.get("installed", objc.NO)
# user desired state
will_be_state = objc.NO
if item.get("installed") or item.get("will_be_installed"):
will_be_state = objc.YES
if item.get("will_be_removed"):
will_be_state = objc.NO
row['install'] = will_be_state
row['itemname'] = item['name']
row['name'] = item.get("display_name") or item['name']
row['version'] = munki.trimVersionString(item.get("version_to_install"),3)
row['description'] = item.get("description","")
if row['installed']:
row['size'] = "-"
status = "Installed"
if item.get("will_be_removed"):
status = "Will be removed"
elif not item.get('uninstallable'):
status = "Not removable"
row['enabled'] = objc.NO
else:
row['size'] = munki.humanReadable(item.get("installer_item_size",0))
status = "Not installed"
if item.get("will_be_installed"):
status = "Will be installed"
elif item.get("note"):
# some reason we can't install
status = item.get("note")
row['enabled'] = objc.NO
row['status'] = status
row['original_status'] = status
row_dict = NSMutableDictionary.dictionaryWithDictionary_(row)
table.append(row_dict)
if table:
self.optional_view_controller.setOptionallist_(table)
self.optional_view_controller.tableView.deselectAll_(self)
def addOrRemoveOptionalSoftware(self):
# record any requested changes in installed/removal state
# then kick off an update check
optional_install_choices = {}
optional_install_choices['managed_installs'] = []
optional_install_choices['managed_uninstalls'] = []
for row in self.optional_array_controller.arrangedObjects():
if row['status'] == "Installed" or row['status'] == "Will be installed":
optional_install_choices['managed_installs'].append(row['itemname'])
if row['status'] == "Will be removed":
optional_install_choices['managed_uninstalls'].append(row['itemname'])
munki.writeSelfServiceManifest(optional_install_choices)
self.checkForUpdates()
def buildUpdateTableData(self):
table = []
self.restart_required = False
self.logout_required = False
for item in self._listofupdates:
row = {}
if item.get("RestartAction") == "RequireRestart" or item.get("RestartAction") == "RecommendRestart":
row['image'] = self._restartImage
self.restart_required = True
elif item.get("RestartAction") == "RequireLogout" or item.get("RestartAction") == "RecommendLogout":
row['image'] = self._logoutImage
self.logout_required = True
else:
row['image'] = self._emptyImage
row['name'] = item.get("display_name") or item.get("name","")
row['version'] = munki.trimVersionString(item.get("version_to_install"),3)
if item.get("installer_item_size"):
row['size'] = munki.humanReadable(item.get("installer_item_size"))
else:
row['size'] = ""
row['description'] = item.get("description","")
row_dict = NSDictionary.dictionaryWithDictionary_(row)
table.append(row_dict)
self.update_view_controller.setUpdatelist_(table)
self.update_view_controller.tableView.deselectAll_(self)
if self.restart_required:
self.update_view_controller.restartInfoFld.setStringValue_(u"Restart will be required.")
self.update_view_controller.restartImageFld.setImage_(self._restartImage)
elif self.logout_required:
self.update_view_controller.restartInfoFld.setStringValue_(u"Logout will be required.")
self.update_view_controller.restartImageFld.setImage_(self._logoutImage)
def confirmInstallUpdates(self):
if self.mainWindowController.theWindow.isVisible() == objc.NO:
return
if len(munki.currentGUIusers()) > 1:
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(u"Other users logged in", u"Cancel", objc.nil, objc.nil, "There are other users logged into this computer.\nUpdating now could cause other users to lose their work.\n\nPlease try again later after the other users have logged out.")
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(self.mainWindowController.theWindow, self, self.multipleUserAlertDidEnd_returnCode_contextInfo_, objc.nil)
elif self.restart_required:
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(u"Restart Required", u"Log out and update", u"Cancel", objc.nil, "A restart is required after updating. Log out and update now?")
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(self.mainWindowController.theWindow, self, self.logoutAlertDidEnd_returnCode_contextInfo_, objc.nil)
elif self.logout_required or munki.installRequiresLogout():
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(u"Logout Required", u"Log out and update", u"Cancel", objc.nil, "A logout is required before updating. Log out and update now?")
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(self.mainWindowController.theWindow, self, self.logoutAlertDidEnd_returnCode_contextInfo_, objc.nil)
else:
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(u"Logout Recommended", u"Log out and update", u"Cancel", u"Update without logging out", "A logout is recommended before updating. Log out and update now?")
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(self.mainWindowController.theWindow, self, self.logoutAlertDidEnd_returnCode_contextInfo_, objc.nil)
def installSessionErrorAlert(self):
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(u"Cannot start installation session", u"Quit", objc.nil, objc.nil, "There is a configuration problem with the managed software installer. Could not start the install session. Contact your systems administrator.")
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(self.mainWindowController.theWindow, self, self.quitAlertDidEnd_returnCode_contextInfo_, objc.nil)
@PyObjCTools.AppHelper.endSheetMethod
def logoutAlertDidEnd_returnCode_contextInfo_(self, alert, returncode, contextinfo):
if returncode == 0:
NSLog("User cancelled")
elif returncode == 1:
NSLog("User chose to log out")
result = munki.logoutAndUpdate()
if result:
self.installSessionErrorAlert()
elif returncode == -1:
NSLog("User chose to update without logging out")
result = munki.justUpdate()
if result:
self.installSessionErrorAlert()
else:
self.managedsoftwareupdate_task = "installwithnologout"
self.mainWindowController.theWindow.orderOut_(self)
self.munkiStatusController.window.makeKeyAndOrderFront_(self)
self.munkiStatusController.startMunkiStatusSession()
@PyObjCTools.AppHelper.endSheetMethod
def multipleUserAlertDidEnd_returnCode_contextInfo_(self, alert, returncode, contextinfo):
pass
@PyObjCTools.AppHelper.endSheetMethod
def quitAlertDidEnd_returnCode_contextInfo_(self, alert, returncode, contextinfo):
if returncode == 1:
NSApp.terminate_(self)
else:
self.update_view_controller.optionalSoftwareBtn.setHidden_(NO)
self.buildOptionalInstallsData()
self.mainWindowController.theTabView.selectNextTabViewItem_(self)
@@ -0,0 +1,35 @@
#
# MSUMainWindowController.py
# Managed Software Update
#
# Created by Greg Neagle on 2/11/10.
# Copyright 2009-2010 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from objc import YES, NO, IBAction, IBOutlet
from Foundation import *
from AppKit import *
class MSUMainWindowController(NSWindowController):
'''
Controls the main window
'''
theTabView = IBOutlet()
theWindow = IBOutlet()
def windowShouldClose_(self, sender):
# just quit
NSApp.terminate_(self)
@@ -0,0 +1,100 @@
#
# MSUOptionalInstallsViewController.py
# Managed Software Update
#
# Created by Greg Neagle on 7/8/10.
# Copyright 2010 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from objc import YES, NO, IBAction, IBOutlet
from Foundation import *
from AppKit import *
class MSUOptionalInstallsViewController(NSViewController):
'''
Controls the optional software view of the main window
'''
descriptionView = IBOutlet()
tableView = IBOutlet()
theWindow = IBOutlet()
array_controller = IBOutlet()
window_controller = IBOutlet()
AddRemoveBtn = IBOutlet()
_optionallist = NSArray.arrayWithArray_([{"installed": NO, "install": NO, "name": "", "version": "",
"description": "", "size": "", "enabled": NO,
"status": "", "original_status": ""}])
def optionallist(self):
return self._optionallist
objc.accessor(optionallist) # PyObjC KVO hack
def setOptionallist_(self, newlist):
self._optionallist = NSArray.arrayWithArray_(newlist)
objc.accessor(setOptionallist_) # PyObjC KVO hack
@IBAction
def itemCheckBoxClicked_(self, sender):
self.updateRowStatus()
#self.updateAddRemoveBtnState()
def updateRowStatus(self):
if self.array_controller.selectedObjects():
row = self.array_controller.selectedObjects()[0]
if row['enabled']:
if row['installed']:
if row['install']:
row['status'] = "Installed"
else:
row['status'] = "Will be removed"
else:
# not row['installed']
if row['install']:
row['status'] = "Will be installed"
else:
row['status'] = "Not installed"
if row['status'] != row['original_status']:
self.AddRemoveBtn.setEnabled_(YES)
else:
self.updateAddRemoveBtnState()
def updateAddRemoveBtnState(self):
userChanges = NO
for row in self.array_controller.arrangedObjects():
if row['status'] != row['original_status']:
userChanges = YES
break
self.AddRemoveBtn.setEnabled_(userChanges)
@IBAction
def cancelBtnClicked_(self, sender):
self.window_controller.theTabView.selectPreviousTabViewItem_(sender)
if NSApp.delegate()._listofupdates == []:
NSApp.delegate().noUpdatesAlert()
@IBAction
def AddRemoveBtnClicked_(self, sender):
# process Adds and/or Removes
self.window_controller.theTabView.selectPreviousTabViewItem_(sender)
NSApp.delegate().addOrRemoveOptionalSoftware()
def tableViewSelectionDidChange_(self, sender):
if self.array_controller.selectedObjects():
row = self.array_controller.selectedObjects()[0]
self.descriptionView.mainFrame().loadHTMLString_baseURL_(row.get("description",""), None)
self.updateRowStatus()
else:
self.descriptionView.mainFrame().loadHTMLString_baseURL_(u"", None)
@@ -0,0 +1,294 @@
# encoding: utf-8
#
# MSUStatusWindowController.py
#
#
# Created by Greg Neagle on 9/21/09.
#
# Copyright 2009-2010 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import socket
import objc
from Foundation import *
from SystemConfiguration import SCDynamicStoreCopyConsoleUser
from AppKit import *
import PyObjCTools
debug = False
class NSPropertyListSerializationException(Exception):
pass
def readPlist(filepath):
"""
Read a .plist file from filepath. Return the unpacked root object
(which usually is a dictionary).
"""
plistData = NSData.dataWithContentsOfFile_(filepath)
dataObject, plistFormat, error = NSPropertyListSerialization.propertyListFromData_mutabilityOption_format_errorDescription_(plistData, NSPropertyListMutableContainers, None, None)
if error:
raise NSPropertyListSerializationException(error)
else:
return dataObject
def getLoginwindowPicture():
desktopPicturePath = ''
loginwindowPrefsPath = "/Library/Preferences/com.apple.loginwindow.plist"
if os.path.exists(loginwindowPrefsPath):
loginwindowPrefs = readPlist(loginwindowPrefsPath)
if loginwindowPrefs:
desktopPicturePath = loginwindowPrefs.get('DesktopPicture', '')
if desktopPicturePath:
if os.path.exists(desktopPicturePath):
theImage = \
NSImage.alloc().initWithContentsOfFile_(
desktopPicturePath)
if theImage:
return theImage
return NSImage.imageNamed_("Solid Aqua Blue")
theImage = NSImage.alloc().initWithContentsOfFile_(
"/System/Library/CoreServices/DefaultDesktop.jpg")
if theImage:
return theImage
else:
return NSImage.imageNamed_("Solid Aqua Blue")
def getconsoleuser():
from SystemConfiguration import SCDynamicStoreCopyConsoleUser
cfuser = SCDynamicStoreCopyConsoleUser( None, None, None )
return cfuser[0]
class MSUStatusWindowController(NSObject):
'''
Controls the status window. This was formerly part of a
seperate application known as MunkiStatus.app
'''
window = objc.IBOutlet()
messageFld = objc.IBOutlet()
detailFld = objc.IBOutlet()
progressIndicator = objc.IBOutlet()
stopBtn = objc.IBOutlet()
imageFld = objc.IBOutlet()
backdropWindow = objc.IBOutlet()
backdropImageFld = objc.IBOutlet()
stopBtnState = 0
restartAlertDismissed = 0
session_started = False
session_connected = False
@objc.IBAction
def stopBtnClicked_(self, sender):
if debug:
NSLog(u"Stop button was clicked.")
sender.setState_(1)
self.stopBtnState = 1
sender.setEnabled_(False)
def startMunkiStatusSession(self):
NSLog(u"Managed Software Update.app PID: %s" % os.getpid())
consoleuser = getconsoleuser()
if consoleuser == None or consoleuser == u"loginwindow":
if self.backdropWindow:
self.backdropWindow.setCanBecomeVisibleWithoutLogin_(True)
self.backdropWindow.setLevel_(NSStatusWindowLevel)
screenRect = NSScreen.mainScreen().frame()
self.backdropWindow.setFrame_display_(screenRect, True)
if self.backdropImageFld:
bgImage = getLoginwindowPicture()
self.backdropImageFld.setImage_(bgImage)
self.backdropWindow.orderFrontRegardless()
if self.window:
if consoleuser == None or consoleuser == u"loginwindow":
# needed so the window can show over the loginwindow
self.window.setCanBecomeVisibleWithoutLogin_(True)
self.window.setLevel_(NSScreenSaverWindowLevel - 1)
self.window.center()
self.window.setTitle_(u"Managed Software Update")
self.messageFld.setStringValue_(u"Starting…")
self.detailFld.setStringValue_(u"")
self.stopBtn.setHidden_(False)
self.stopBtn.setEnabled_(True)
if self.imageFld:
theImage = NSImage.imageNamed_("Managed Software Update")
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()
# start our message processing thread
NSThread.detachNewThreadSelector_toTarget_withObject_(
self.handleSocket,
self,
None)
self.session_started = True
#NSApp.activateIgnoringOtherApps_(True)
def sessionStarted(self):
return self.session_started
def handleSocket(self):
# Autorelease pool for memory management
pool = NSAutoreleasePool.alloc().init()
socketSessionResult = 0
socketpath = "/tmp/com.googlecode.munki.munkistatus.%s" % os.getpid()
try:
os.remove(socketpath)
except OSError:
pass
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.bind(socketpath)
s.listen(1)
s.settimeout(30)
try:
conn, addr = s.accept()
# reset the timeout on the connected socket
conn.settimeout(None)
self.session_connected = True
if debug:
NSLog(u"Socket connection established.")
buffer = ''
keepLooping = True
while keepLooping:
data = conn.recv(1024)
if not data:
# socket connection was closed, we should terminate.
NSLog(u"Socket connection closed without QUIT message.")
socketSessionResult = -1
break
if debug:
NSLog(repr(data))
buffer = buffer + data
# do we have at least one return character?
if buffer.count('\n'):
lines = buffer.splitlines(True)
buffer = ''
for line in lines:
if line.endswith('\n'):
command = line.decode('UTF-8').rstrip('\n')
if debug:
NSLog(u"Socket received command: %s" % command)
if command.startswith(u"QUIT: "):
keepLooping = False
socketSessionResult = 0
break
response = self.processSocketMsg_(command)
if response:
conn.send(response)
else:
buffer = line
break
conn.close()
except socket.timeout:
NSLog("Socket timed out before connection.")
socketSessionResult = -2
except socket.error, errcode:
NSLog("Socket error: %s." % errcode)
socketSessionResult = -1
try:
os.remove(socketpath)
except OSError:
pass
self.window.orderOut_(self)
self.session_started = False
self.session_connected = False
NSApp.delegate().munkiStatusSessionEnded_(socketSessionResult)
# Clean up autorelease pool
del pool
def processSocketMsg_(self, message):
if message.startswith(u"ACTIVATE: "):
NSApp.activateIgnoringOtherApps_(True)
return ""
if message.startswith(u"HIDE: "):
self.window.orderOut_(self)
return ""
if message.startswith(u"SHOW: "):
self.window.orderFront_(self)
return ""
if message.startswith(u"TITLE: "):
self.window.setTitle_(message[7:])
return ""
if message.startswith(u"MESSAGE: "):
self.messageFld.setStringValue_(message[9:])
return ""
if message.startswith(u"DETAIL: "):
self.detailFld.setStringValue_(message[8:])
return ""
if message.startswith(u"PERCENT: "):
self.setPercentageDone(message[9:])
return ""
if message.startswith(u"GETSTOPBUTTONSTATE: "):
return "%s\n" % self.stopBtnState
if message.startswith(u"HIDESTOPBUTTON: "):
self.stopBtn.setHidden_(True)
return ""
if message.startswith(u"SHOWSTOPBUTTON: "):
self.stopBtn.setHidden_(False)
return ""
if message.startswith(u"ENABLESTOPBUTTON: "):
self.stopBtn.setEnabled_(True)
return ""
if message.startswith(u"DISABLESTOPBUTTON: "):
self.stopBtn.setEnabled_(False)
return ""
if message.startswith(u"RESTARTALERT: "):
self.doRestartAlert()
while 1:
if self.restartAlertDismissed:
break
return "1\n"
return ""
def setPercentageDone(self, percent):
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))
@PyObjCTools.AppHelper.endSheetMethod
def alertDidEnd_returnCode_contextInfo_(self, alert, returncode, contextinfo):
self.restartAlertDismissed = 1
def doRestartAlert(self):
self.restartAlertDismissed = 0
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(u"Restart Required", u"Restart", objc.nil, objc.nil, "Software installed or removed requires a restart. You will have a chance to save open documents.")
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(self.window, self, self.alertDidEnd_returnCode_contextInfo_, objc.nil)
@@ -1,9 +1,9 @@
#
# MSUWindowController.py
# MSUupdatesViewController.py
# Managed Software Update
#
# Created by Greg Neagle on 2/11/10.
# Copyright 2009-2010 Greg Neagle.
# Created by Greg Neagle on 7/8/10.
# Copyright 2010 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -17,18 +17,23 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from objc import YES, NO, IBAction, IBOutlet
from Foundation import *
from AppKit import *
class MSUWindowController(NSWindowController):
class MSUupdatesViewController(NSViewController):
'''
Controls the updates view of the main window
'''
restartInfoFld = IBOutlet()
restartImageFld = IBOutlet()
descriptionView = IBOutlet()
tableView = IBOutlet()
theWindow = IBOutlet()
optionalSoftwareBtn = IBOutlet()
array_controller = IBOutlet()
window_controller = IBOutlet()
updateNowBtn = IBOutlet()
_updatelist = NSArray.arrayWithArray_([{"image": NSImage.imageNamed_("Empty.png"), "name": "", "version": "", "description": ""}])
@@ -49,6 +54,17 @@ class MSUWindowController(NSWindowController):
# alert the user to logout, proceed without logout, or cancel
NSApp.delegate().confirmInstallUpdates()
def windowShouldClose_(self, sender):
# just quit
NSApp.terminate_(self)
@IBAction
def optionalSoftwareBtnClicked_(self, sender):
# switch to optional software pane
self.window_controller.theTabView.selectNextTabViewItem_(sender)
NSApp.delegate().optional_view_controller.AddRemoveBtn.setEnabled_(NO)
NSApp.delegate().buildOptionalInstallsData()
def tableViewSelectionDidChange_(self, sender):
if self.array_controller.selectedObjects():
row = self.array_controller.selectedObjects()[0]
self.descriptionView.mainFrame().loadHTMLString_baseURL_(row.get("description",""), None)
else:
self.descriptionView.mainFrame().loadHTMLString_baseURL_(u"", None)
@@ -9,11 +9,15 @@
/* Begin PBXBuildFile section */
77631A270C06C501005415CB /* Python.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 77631A260C06C501005415CB /* Python.framework */; };
77631A3F0C0748CF005415CB /* main.py in Resources */ = {isa = PBXBuildFile; fileRef = 77631A3E0C0748CF005415CB /* main.py */; };
7790198F0C07548A00326F66 /* Managed_Software_UpdateAppDelegate.py in Resources */ = {isa = PBXBuildFile; fileRef = 7790198E0C07548A00326F66 /* Managed_Software_UpdateAppDelegate.py */; };
7790198F0C07548A00326F66 /* MSUAppDelegate.py in Resources */ = {isa = PBXBuildFile; fileRef = 7790198E0C07548A00326F66 /* MSUAppDelegate.py */; };
77C8C1F90C07829500965286 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 77C8C1F70C07829500965286 /* MainMenu.xib */; };
8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */; };
8D11072D0486CEB800E47090 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; };
8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; };
C0458B4B11EB888C001F1172 /* BorderlessWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = C0458B4A11EB888C001F1172 /* BorderlessWindow.m */; };
C0458B4D11EB8899001F1172 /* ScaledImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = C0458B4C11EB8899001F1172 /* ScaledImageView.m */; };
C0458B5111EB88C0001F1172 /* MSUStatusWindowController.py in Resources */ = {isa = PBXBuildFile; fileRef = C0458B5011EB88C0001F1172 /* MSUStatusWindowController.py */; };
C062868911E677820009B44B /* MSUOptionalInstallsViewController.py in Resources */ = {isa = PBXBuildFile; fileRef = C062868811E677820009B44B /* MSUOptionalInstallsViewController.py */; };
C0828C801123DBC5003D5807 /* MSUWebViewPolicyDelegate.py in Resources */ = {isa = PBXBuildFile; fileRef = C0828C7F1123DBC5003D5807 /* MSUWebViewPolicyDelegate.py */; };
C0828C901123DCFF003D5807 /* ShutDownReq.tif in Resources */ = {isa = PBXBuildFile; fileRef = C0828C871123DCFF003D5807 /* ShutDownReq.tif */; };
C0828C911123DCFF003D5807 /* RestartRec.tif in Resources */ = {isa = PBXBuildFile; fileRef = C0828C881123DCFF003D5807 /* RestartRec.tif */; };
@@ -24,9 +28,11 @@
C0828C961123DCFF003D5807 /* Restart.tif in Resources */ = {isa = PBXBuildFile; fileRef = C0828C8D1123DCFF003D5807 /* Restart.tif */; };
C0828C971123DCFF003D5807 /* package.tiff in Resources */ = {isa = PBXBuildFile; fileRef = C0828C8E1123DCFF003D5807 /* package.tiff */; };
C0828C981123DCFF003D5807 /* Installer.tiff in Resources */ = {isa = PBXBuildFile; fileRef = C0828C8F1123DCFF003D5807 /* Installer.tiff */; };
C0828CA2112461C0003D5807 /* MSUWindowController.py in Resources */ = {isa = PBXBuildFile; fileRef = C0828CA1112461C0003D5807 /* MSUWindowController.py */; };
C0828CA2112461C0003D5807 /* MSUMainWindowController.py in Resources */ = {isa = PBXBuildFile; fileRef = C0828CA1112461C0003D5807 /* MSUMainWindowController.py */; };
C0828D1011247A37003D5807 /* FoundationPlist.py in Resources */ = {isa = PBXBuildFile; fileRef = C0828D0E11247A37003D5807 /* FoundationPlist.py */; };
C0828D1211247B3A003D5807 /* munki.py in Resources */ = {isa = PBXBuildFile; fileRef = C0828D1111247B3A003D5807 /* munki.py */; };
C09D9A2D11E6DC7800B3E573 /* MSUupdatesViewController.py in Resources */ = {isa = PBXBuildFile; fileRef = C09D9A2C11E6DC7800B3E573 /* MSUupdatesViewController.py */; };
C0E9E82411EFB1EB003CE81A /* Solid Aqua Blue.png in Resources */ = {isa = PBXBuildFile; fileRef = C0E9E82311EFB1EB003CE81A /* Solid Aqua Blue.png */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
@@ -39,10 +45,16 @@
32CA4F630368D1EE00C91783 /* Managed_Software_Update_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Managed_Software_Update_Prefix.pch; sourceTree = "<group>"; };
77631A260C06C501005415CB /* Python.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Python.framework; path = /System/Library/Frameworks/Python.framework; sourceTree = "<absolute>"; };
77631A3E0C0748CF005415CB /* main.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = main.py; sourceTree = "<group>"; };
7790198E0C07548A00326F66 /* Managed_Software_UpdateAppDelegate.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = Managed_Software_UpdateAppDelegate.py; sourceTree = "<group>"; };
7790198E0C07548A00326F66 /* MSUAppDelegate.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = MSUAppDelegate.py; sourceTree = "<group>"; };
77C8C1F80C07829500965286 /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/MainMenu.xib; sourceTree = "<group>"; };
8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
8D1107320486CEB800E47090 /* Managed Software Update.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Managed Software Update.app"; sourceTree = BUILT_PRODUCTS_DIR; };
C0458B4A11EB888C001F1172 /* BorderlessWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BorderlessWindow.m; sourceTree = "<group>"; };
C0458B4C11EB8899001F1172 /* ScaledImageView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ScaledImageView.m; sourceTree = "<group>"; };
C0458B4E11EB88AD001F1172 /* BorderlessWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BorderlessWindow.h; sourceTree = "<group>"; };
C0458B4F11EB88AD001F1172 /* ScaledImageView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ScaledImageView.h; sourceTree = "<group>"; };
C0458B5011EB88C0001F1172 /* MSUStatusWindowController.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = MSUStatusWindowController.py; sourceTree = "<group>"; };
C062868811E677820009B44B /* MSUOptionalInstallsViewController.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = MSUOptionalInstallsViewController.py; sourceTree = "<group>"; };
C0828C7F1123DBC5003D5807 /* MSUWebViewPolicyDelegate.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = MSUWebViewPolicyDelegate.py; sourceTree = "<group>"; };
C0828C871123DCFF003D5807 /* ShutDownReq.tif */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = ShutDownReq.tif; sourceTree = "<group>"; };
C0828C881123DCFF003D5807 /* RestartRec.tif */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = RestartRec.tif; sourceTree = "<group>"; };
@@ -53,9 +65,11 @@
C0828C8D1123DCFF003D5807 /* Restart.tif */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = Restart.tif; sourceTree = "<group>"; };
C0828C8E1123DCFF003D5807 /* package.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = package.tiff; sourceTree = "<group>"; };
C0828C8F1123DCFF003D5807 /* Installer.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = Installer.tiff; sourceTree = "<group>"; };
C0828CA1112461C0003D5807 /* MSUWindowController.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = MSUWindowController.py; sourceTree = "<group>"; };
C0828CA1112461C0003D5807 /* MSUMainWindowController.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = MSUMainWindowController.py; sourceTree = "<group>"; };
C0828D0E11247A37003D5807 /* FoundationPlist.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = FoundationPlist.py; sourceTree = "<group>"; };
C0828D1111247B3A003D5807 /* munki.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = munki.py; sourceTree = "<group>"; };
C09D9A2C11E6DC7800B3E573 /* MSUupdatesViewController.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = MSUupdatesViewController.py; sourceTree = "<group>"; };
C0E9E82311EFB1EB003CE81A /* Solid Aqua Blue.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Solid Aqua Blue.png"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -75,8 +89,11 @@
isa = PBXGroup;
children = (
C0828C7F1123DBC5003D5807 /* MSUWebViewPolicyDelegate.py */,
7790198E0C07548A00326F66 /* Managed_Software_UpdateAppDelegate.py */,
C0828CA1112461C0003D5807 /* MSUWindowController.py */,
7790198E0C07548A00326F66 /* MSUAppDelegate.py */,
C0458B5011EB88C0001F1172 /* MSUStatusWindowController.py */,
C0828CA1112461C0003D5807 /* MSUMainWindowController.py */,
C062868811E677820009B44B /* MSUOptionalInstallsViewController.py */,
C09D9A2C11E6DC7800B3E573 /* MSUupdatesViewController.py */,
);
name = Classes;
sourceTree = "<group>";
@@ -123,6 +140,10 @@
29B97315FDCFA39411CA2CEA /* Other Sources */ = {
isa = PBXGroup;
children = (
C0458B4E11EB88AD001F1172 /* BorderlessWindow.h */,
C0458B4F11EB88AD001F1172 /* ScaledImageView.h */,
C0458B4C11EB8899001F1172 /* ScaledImageView.m */,
C0458B4A11EB888C001F1172 /* BorderlessWindow.m */,
C0828D0E11247A37003D5807 /* FoundationPlist.py */,
32CA4F630368D1EE00C91783 /* Managed_Software_Update_Prefix.pch */,
29B97316FDCFA39411CA2CEA /* main.m */,
@@ -142,6 +163,7 @@
C0828C8B1123DCFF003D5807 /* Empty.png */,
C0828C8C1123DCFF003D5807 /* RestartReq.tif */,
C0828C8D1123DCFF003D5807 /* Restart.tif */,
C0E9E82311EFB1EB003CE81A /* Solid Aqua Blue.png */,
C0828C8E1123DCFF003D5807 /* package.tiff */,
C0828C8F1123DCFF003D5807 /* Installer.tiff */,
77C8C1F70C07829500965286 /* MainMenu.xib */,
@@ -205,7 +227,7 @@
files = (
8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */,
77631A3F0C0748CF005415CB /* main.py in Resources */,
7790198F0C07548A00326F66 /* Managed_Software_UpdateAppDelegate.py in Resources */,
7790198F0C07548A00326F66 /* MSUAppDelegate.py in Resources */,
77C8C1F90C07829500965286 /* MainMenu.xib in Resources */,
C0828C801123DBC5003D5807 /* MSUWebViewPolicyDelegate.py in Resources */,
C0828C901123DCFF003D5807 /* ShutDownReq.tif in Resources */,
@@ -217,9 +239,13 @@
C0828C961123DCFF003D5807 /* Restart.tif in Resources */,
C0828C971123DCFF003D5807 /* package.tiff in Resources */,
C0828C981123DCFF003D5807 /* Installer.tiff in Resources */,
C0828CA2112461C0003D5807 /* MSUWindowController.py in Resources */,
C0828CA2112461C0003D5807 /* MSUMainWindowController.py in Resources */,
C0828D1011247A37003D5807 /* FoundationPlist.py in Resources */,
C0828D1211247B3A003D5807 /* munki.py in Resources */,
C062868911E677820009B44B /* MSUOptionalInstallsViewController.py in Resources */,
C09D9A2D11E6DC7800B3E573 /* MSUupdatesViewController.py in Resources */,
C0458B5111EB88C0001F1172 /* MSUStatusWindowController.py in Resources */,
C0E9E82411EFB1EB003CE81A /* Solid Aqua Blue.png in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -231,6 +257,8 @@
buildActionMask = 2147483647;
files = (
8D11072D0486CEB800E47090 /* main.m in Sources */,
C0458B4B11EB888C001F1172 /* BorderlessWindow.m in Sources */,
C0458B4D11EB8899001F1172 /* ScaledImageView.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1,184 +0,0 @@
#
# Managed_Software_UpdateAppDelegate.py
# Managed Software Update
#
# Created by Greg Neagle on 2/10/10.
# Copyright 2009-2010 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from Foundation import *
from AppKit import *
import munki
import PyObjCTools
class Managed_Software_UpdateAppDelegate(NSObject):
window_controller = objc.IBOutlet()
array_controller = objc.IBOutlet()
_emptyImage = NSImage.imageNamed_("Empty.png")
_restartImage = NSImage.imageNamed_("RestartReq.tif")
_logoutImage = NSImage.imageNamed_("LogOutReq.tif")
_listofupdates = []
restart_required = False
logout_required = False
def applicationDidFinishLaunching_(self, sender):
# display updates if available; if no available updates
# trigger an update check
if not self._listofupdates:
self.getAvailableUpdates()
if self._listofupdates:
self.buildTableData()
self.window_controller.theWindow.makeKeyAndOrderFront_(self)
NSApp.requestUserAttention_(NSCriticalRequest)
else:
# no updates available. Should we check for some?
result = munki.checkForUpdates()
if result == 0:
self.window_controller.theWindow.makeKeyAndOrderFront_(self)
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(u"Your software is up to date.", u"Quit", objc.nil, objc.nil, "There is no new software for your computer at this time.")
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(self.window_controller.theWindow, self, self.quitAlertDidEnd_returnCode_contextInfo_, objc.nil)
elif result == -1:
self.window_controller.theWindow.makeKeyAndOrderFront_(self)
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(u"Cannot check for updates", u"Quit", objc.nil, objc.nil, "Managed Software Update cannot contact the update server at this time.\nIf this situtation continues, contact your systems administrator.")
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(self.window_controller.theWindow, self, self.quitAlertDidEnd_returnCode_contextInfo_, objc.nil)
elif result == -2:
self.window_controller.theWindow.makeKeyAndOrderFront_(self)
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(u"Update check failed", u"Quit", objc.nil, objc.nil, "There is a configuration problem with the managed software installer. Could not start the update check process. Contact your systems administrator.")
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(self.window_controller.theWindow, self, self.quitAlertDidEnd_returnCode_contextInfo_, objc.nil)
def applicationDidBecomeActive_(self, sender):
pass
def getAvailableUpdates(self):
updatelist = []
installinfo = munki.getInstallInfo()
if installinfo:
updatelist = installinfo.get("managed_installs", [])
if installinfo.get("removals"):
removallist = installinfo.get("removals")
restartNeeded = False
showRemovalDetail = munki.getRemovalDetailPrefs()
for item in removallist:
if item.get("RestartAction") == "RequireRestart" or item.get("RestartAction") == "RecommendRestart":
restartNeeded = True
if showRemovalDetail:
if display_name in item:
item["display_name"] = item["display_name"] + " (will be removed)"
elif name in item:
item["display_name"] = item["name"] + " (will be removed)"
updatelist.append(item)
if not showRemovalDetail:
row = {}
row["display_name"] = "Software removals"
row["version"] = ""
row["description"] = "Scheduled removal of managed software."
if restartNeeded:
row["RestartAction"] = "RequireRestart"
updatelist.append(row)
if updatelist:
self._listofupdates = updatelist
else:
appleupdates = munki.getAppleUpdates()
if appleupdates:
self._listofupdates = appleupdates.get("AppleUpdates", [])
def buildTableData(self):
table = []
self.restart_required = False
self.logout_required = False
for item in self._listofupdates:
row = {}
if item.get("RestartAction") == "RequireRestart" or item.get("RestartAction") == "RecommendRestart":
row['image'] = self._restartImage
self.restart_required = True
elif item.get("RestartAction") == "RequireLogout" or item.get("RestartAction") == "RecommendLogout":
row['image'] = self._logoutImage
self.logout_required = True
else:
row['image'] = self._emptyImage
row['name'] = item.get("display_name") or item.get("name","")
row['version'] = munki.trimVersionString(item.get("version_to_install"),3)
row['description'] = item.get("description","")
row_dict = NSDictionary.dictionaryWithDictionary_(row)
table.append(row_dict)
self.window_controller.setUpdatelist_(table)
self.window_controller.tableView.deselectAll_(self)
if self.restart_required:
self.window_controller.restartInfoFld.setStringValue_(u"Restart will be required.")
self.window_controller.restartImageFld.setImage_(self._restartImage)
elif self.logout_required:
self.window_controller.restartInfoFld.setStringValue_(u"Logout will be required.")
self.window_controller.restartImageFld.setImage_(self._logoutImage)
def tableViewSelectionDidChange_(self, sender):
if self.array_controller.selectedObjects():
row = self.array_controller.selectedObjects()[0]
self.window_controller.descriptionView.mainFrame().loadHTMLString_baseURL_(row.get("description",""), None)
else:
self.window_controller.descriptionView.mainFrame().loadHTMLString_baseURL_(u"", None)
def confirmInstallUpdates(self):
if len(munki.currentGUIusers()) > 1:
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(u"Other users logged in", u"Cancel", objc.nil, objc.nil, "There are other users logged into this computer.\nUpdating now could cause other users to lose their work.\n\nPlease try again later after the other users have logged out.")
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(self.window_controller.theWindow, self, self.multipleUserAlertDidEnd_returnCode_contextInfo_, objc.nil)
elif self.restart_required:
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(u"Restart Required", u"Log out and update", u"Cancel", objc.nil, "A restart is required after updating. Log out and update now?")
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(self.window_controller.theWindow, self, self.logoutAlertDidEnd_returnCode_contextInfo_, objc.nil)
elif self.logout_required or munki.installRequiresLogout():
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(u"Logout Required", u"Log out and update", u"Cancel", objc.nil, "A logout is required before updating. Log out and update now?")
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(self.window_controller.theWindow, self, self.logoutAlertDidEnd_returnCode_contextInfo_, objc.nil)
else:
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(u"Logout Recommended", u"Log out and update", u"Cancel", u"Update without logging out", "A logout is recommended before updating. Log out and update now?")
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(self.window_controller.theWindow, self, self.logoutAlertDidEnd_returnCode_contextInfo_, objc.nil)
def installSessionErrorAlert(self):
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(u"Cannot start installation session", u"Quit", objc.nil, objc.nil, "There is a configuration problem with the managed software installer. Could not start the install session. Contact your systems administrator.")
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(self.window_controller.theWindow, self, self.quitAlertDidEnd_returnCode_contextInfo_, objc.nil)
@PyObjCTools.AppHelper.endSheetMethod
def logoutAlertDidEnd_returnCode_contextInfo_(self, alert, returncode, contextinfo):
if returncode == 0:
NSLog("User cancelled")
elif returncode == 1:
NSLog("User chose to log out")
result = munki.logoutAndUpdate()
if result:
self.installSessionErrorAlert()
elif returncode == -1:
NSLog("User chose to update without logging out")
result = munki.justUpdate()
if result:
self.installSessionErrorAlert()
else:
NSApp.terminate_(self)
@PyObjCTools.AppHelper.endSheetMethod
def multipleUserAlertDidEnd_returnCode_contextInfo_(self, alert, returncode, contextinfo):
pass
@PyObjCTools.AppHelper.endSheetMethod
def quitAlertDidEnd_returnCode_contextInfo_(self, alert, returncode, contextinfo):
NSApp.terminate_(self)
@@ -0,0 +1,27 @@
//
// ScaledImageViewView.h
//
// Created by Greg Neagle on 5/27/09.
//
// Copyright 2009 Greg Neagle.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#import <Cocoa/Cocoa.h>
@interface ScaledImageView : NSImageView
@end
@@ -0,0 +1,76 @@
//
// ScaledImageView.m
//
// Created by Greg Neagle on 5/27/09.
//
// Copyright 2009 Greg Neagle.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#import "ScaledImageView.h"
@implementation ScaledImageView
-(void)drawRect:(NSRect)rect {
NSRect dstRect = [self bounds];
float sourceWidth = [[self image] size].width;
float sourceHeight = [[self image] size].height;
float targetWidth = dstRect.size.width;
float targetHeight = dstRect.size.height;
// Calculate aspect ratios
float sourceRatio = sourceWidth / sourceHeight;
float targetRatio = targetWidth / targetHeight;
// Determine what side of the source image to use for proportional scaling
BOOL scaleWidth = (sourceRatio <= targetRatio);
// Proportionally scale source image
float scalingFactor, scaledWidth, scaledHeight;
if (scaleWidth) {
scalingFactor = 1.0 / sourceRatio;
scaledWidth = targetWidth;
scaledHeight = round(targetWidth * scalingFactor);
} else {
scalingFactor = sourceRatio;
scaledWidth = round(targetHeight * scalingFactor);
scaledHeight = targetHeight;
}
float scaleFactor = scaledHeight / sourceHeight;
// Calculate compositing rectangles
NSRect sourceRect;
float destX, destY;
// Crop from center
destX = round((scaledWidth - targetWidth) / 2.0);
destY = round((scaledHeight - targetHeight) / 2.0);
sourceRect = NSMakeRect(destX / scaleFactor, destY / scaleFactor,
targetWidth / scaleFactor, targetHeight / scaleFactor);
[[self image] drawInRect:dstRect
fromRect:sourceRect
operation:NSCompositeSourceOver
fraction:1.0];
}
@end
Binary file not shown.

After

Width:  |  Height:  |  Size: 495 B

+5 -2
View File
@@ -26,9 +26,12 @@ import AppKit
from PyObjCTools import AppHelper
# import modules containing classes required to start application and load MainMenu.nib
import Managed_Software_UpdateAppDelegate
import MSUWindowController
import MSUAppDelegate
import MSUMainWindowController
import MSUOptionalInstallsViewController
import MSUupdatesViewController
import MSUWebViewPolicyDelegate
import MSUStatusWindowController
# pass control to AppKit
AppHelper.runEventLoop()
+61 -90
View File
@@ -4,7 +4,7 @@
# Managed Software Update
#
# Created by Greg Neagle on 2/11/10.
# Copyright 2009-2010 Greg Neagle.
# Copyright 2010 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -21,12 +21,21 @@
import os
import subprocess
import FoundationPlist
import time
#import time
from Foundation import NSDate, NSURL
#from Foundation import NSDate, NSURL
_updatechecklaunchfile = "/private/tmp/.com.googlecode.munki.updatecheck.launchd"
def call(cmd):
# convenience function; works around an issue with subprocess.call
# in PyObjC in Snow Leopard
p = subprocess.Popen(cmd, bufsize=1, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
(output, err) = p.communicate()
return p.returncode
def getManagedInstallsPrefs():
# define default values
prefs = {}
@@ -54,6 +63,13 @@ def getManagedInstallsPrefs():
return prefs
def writeSelfServiceManifest(optional_install_choices):
usermanifest = "/Users/Shared/.SelfServeManifest"
try:
FoundationPlist.writePlist(optional_install_choices, usermanifest)
except:
pass
def getRemovalDetailPrefs():
return getManagedInstallsPrefs().get('ShowRemovalDetail', False)
@@ -76,88 +92,37 @@ def getInstallInfo():
def startUpdateCheck():
# does launchd magic to run managedsoftwareupdate as root
cmd = ["/usr/bin/touch", _updatechecklaunchfile]
if subprocess.call(cmd):
return -1
else:
for i in range(7):
time.sleep(1)
# check to see if we were successful in starting the update
result = updateInProgress()
if result == 1:
return 1
else:
# try again
pass
if result == -1:
try:
# this might fail if we don't own it
os.unlink(_updatechecklaunchfile)
except:
pass
return result
def updateInProgress():
cmd = ['/usr/bin/killall', '-s', 'MunkiStatus']
result = subprocess.call(cmd)
munkiStatusIsRunning = (result == 0)
if munkiStatusIsRunning:
# we're doing an update. Bring it back to the front
tellMunkiStatusToActivate()
return 1
elif os.path.exists(_updatechecklaunchfile):
# we tried to trigger the update, but it failed?
return -1
else:
# we're not updating right now
return 0
def tellMunkiStatusToActivate():
# uses oscascript to run an AppleScript
# ugly, but it works.
cmd = ['/usr/bin/osascript', '-e',
'tell application "MunkiStatus" to activate']
p = subprocess.Popen(cmd, bufsize=1, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
(output, err) = p.communicate()
result = call(["/usr/bin/touch", _updatechecklaunchfile])
return result
def checkForUpdates():
# returns 1 if we've kicked off an update check (or one is in progress),
# returns 0 if we're not going to check (because we just did)
# returns -1 if the munki server is unavailable
# returns -2 if there's an unexpected problem
# are we checking right now (MunkiStatus.app is running)?
update = updateInProgress()
if update == 1:
return 1
elif update == -1:
return -2
# when did we last check?
now = NSDate.new()
prefs = getManagedInstallsPrefs()
lastCheckedDateString = prefs.get("LastCheckDate")
if lastCheckedDateString:
lastCheckedDate = NSDate.dateWithString_(lastCheckedDateString)
if (not lastCheckedDateString) or now.timeIntervalSinceDate_(lastCheckedDate) > 10:
# we haven't checked in more than 10 seconds
result = startUpdateCheck()
if result == 1:
return 1
else:
return -2
else:
# we just finished checking
lastCheckResult = prefs.get("LastCheckResult")
if lastCheckResult == -1:
# check failed
return -1
else:
return 0
#def checkForUpdates():
# # returns 1 if we've kicked off an update check (or one is in progress),
# # returns 0 if we're not going to check (because we just did)
# # returns -1 if the munki server is unavailable
# # returns -2 if there's an unexpected problem
#
# # when did we last check?
# now = NSDate.new()
# prefs = getManagedInstallsPrefs()
# lastCheckedDateString = prefs.get("LastCheckDate")
# if lastCheckedDateString:
# lastCheckedDate = NSDate.dateWithString_(lastCheckedDateString)
# if (not lastCheckedDateString) or now.timeIntervalSinceDate_(lastCheckedDate) > 5:
# # we haven't checked in more than 5 seconds
# result = startUpdateCheck()
# if result == 1:
# return 1
# else:
# return -2
# else:
# # we just finished checking
# lastCheckResult = prefs.get("LastCheckResult")
# if lastCheckResult == -1:
# # check failed
# return -1
# else:
# return 0
def getAppleUpdates():
@@ -172,6 +137,15 @@ def getAppleUpdates():
pass
return pl
def humanReadable(kbytes):
"""Returns sizes in human-readable units."""
units = [(" KB",2**10), (" MB",2**20), (" GB",2**30), (" TB",2**40)]
for suffix, limit in units:
if kbytes > limit:
continue
else:
return str(round(kbytes/float(limit/2**10),1))+suffix
def trimVersionString(versString,tupleCount):
if versString == None or versString == "":
return ""
@@ -212,10 +186,7 @@ end ignoring
if line:
cmd.append("-e")
cmd.append(line)
p = subprocess.Popen(cmd, bufsize=1, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
(output, err) = p.communicate()
result = call(cmd)
@@ -223,7 +194,7 @@ def logoutAndUpdate():
# touch a flag so the process that runs after
# logout knows it's OK to install everything
cmd = ["/usr/bin/touch", "/private/tmp/com.googlecode.munki.installatlogout"]
result = subprocess.call(cmd)
result = call(cmd)
if result == 0:
logoutNow()
else:
@@ -235,7 +206,7 @@ def justUpdate():
# we touch a file that launchd is is watching
# launchd, in turn, launches managedsoftwareupdate --installwithnologout as root
cmd = ["/usr/bin/touch", "/private/tmp/.com.googlecode.munki.managedinstall.launchd"]
return subprocess.call(cmd)
return call(cmd)