Merge remote-tracking branch 'munki/master'

This commit is contained in:
Nick McSpadden
2016-07-08 15:15:12 -07:00
111 changed files with 2238 additions and 2440 deletions
+7
View File
@@ -0,0 +1,7 @@
language: python
python:
- "2.7"
install:
- pip install pep8
script:
- pep8 code
+7 -17
View File
@@ -17,27 +17,17 @@ Munki is currently in use at organizations all over the world, managing software
Get started with Munki here: [Getting Started with Munki](https://github.com/munki/munki/wiki/)
Check out the Wiki for some notes and documentation, and browse and/or check out the source. See the [releases page](https://github.com/munki/munki/releases) for pre-built installer packages of supported releases, or [munkibuilds.org](https://munkibuilds.org) for packages built from the current Git revision, which may contain development, testing, or work-in-progress code.
Check out the [Wiki](https://github.com/munki/munki/wiki) for some notes and documentation, and browse and/or check out the source. See the [releases page](https://github.com/munki/munki/releases) for pre-built installer packages of supported releases, or [munkibuilds.org](https://munkibuilds.org) for packages built from the current Git revision, which may contain development, testing, or work-in-progress code.
Some video learning resources are [here](https://github.com/munki/munki/wiki/More-Links-And-Tools#video-resources).
####Get help
If you have questions, or need additional help getting started, the [munki-discuss](https://groups.google.com/group/munki-discuss) group is the best place to start. Please don't post support questions as comments on wiki documentation pages, or as GitHub code issues.
The wiki is an in-depth resource: https://github.com/munki/munki/wiki
Frequently Asked Questions are here: https://github.com/munki/munki/wiki/FAQ
If you have additional questions, or need even more help getting started, post a question to [munki-discuss](https://groups.google.com/group/munki-discuss). Please don't post support questions as comments on wiki documentation pages, or as GitHub code issues.
Issues with MunkiWebAdmin should be discussed in its group: [munki-web-admin](https://groups.google.com/group/munki-web-admin).
![](https://github.com/munki/munki/wiki/images/managed_software_center.png)
###Announcement
An exploit has been discovered against Munki tools older than version 2.1.
Untrusted input can be passed to the curl binary, causing arbitrary files to be downloaded to arbitrary locations.
Recommendation is to update to Munki 2.1 or later, which is not susceptible to this exploit, as version 2.1 and later no longer use the curl binary for http/https communication.
This vulnerability has been assigned a CVE ID: CVE-2015-2211
If you cannot update to Munki 2.1, there is a patch for Munki 2.0.1 here:
https://github.com/munki/munki/releases/tag/v2.0.1.2254
And another for Munki 1.0.0 here:
https://github.com/munki/munki/releases/tag/v1.0.0.1896.0
@@ -2,7 +2,7 @@
File: DockTilePlugIn.h
Copyright 2015 Greg Neagle.
Copyright 2015-2016 Greg Neagle.
Liberally adapted from Apple sample code:
https://developer.apple.com/library/mac/samplecode/DockTile/Listings/DockTilePlugIn_DockTilePlugIn_h.html
@@ -2,7 +2,7 @@
File: DockTilePlugIn.m
Copyright 2015 Greg Neagle.
Copyright 2015-2016 Greg Neagle.
Liberally adapted from Apple sample code:
https://developer.apple.com/library/mac/samplecode/DockTile/Listings/DockTilePlugIn_DockTilePlugIn_h.html
@@ -7,7 +7,7 @@
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>com.googlecode.munki.$(PRODUCT_NAME:rfc1034identifier)</string>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
@@ -19,7 +19,7 @@
<key>CFBundleVersion</key>
<string>1</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2015 The Munki Project. All rights reserved.</string>
<string>Copyright © 2016 The Munki Project. All rights reserved.</string>
<key>NSPrincipalClass</key>
<string>DockTile</string>
</dict>
@@ -11,6 +11,7 @@
C00A4C57185FCEC9004EB3B7 /* FoundationPlist.py in Resources */ = {isa = PBXBuildFile; fileRef = C00A4C56185FCEC9004EB3B7 /* FoundationPlist.py */; };
C02C98891911B81D00425167 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C02C98871911B81D00425167 /* Localizable.strings */; };
C035274E18A9C582004A5AE4 /* libpython2.6.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = C035274D18A9C582004A5AE4 /* libpython2.6.dylib */; };
C0453A211CCEF7B60002D396 /* MSCLogWindowController.py in Resources */ = {isa = PBXBuildFile; fileRef = C0453A201CCEF7B60002D396 /* MSCLogWindowController.py */; };
C049C9951AEC77DD00251D45 /* updatesTemplate.png in Resources */ = {isa = PBXBuildFile; fileRef = C049C9941AEC77DD00251D45 /* updatesTemplate.png */; };
C05C3CEF188391F200095E65 /* munki.py in Resources */ = {isa = PBXBuildFile; fileRef = C05C3CEE188391F200095E65 /* munki.py */; };
C079D9C118BD435200BAD62E /* AlertController.py in Resources */ = {isa = PBXBuildFile; fileRef = C079D9C018BD435200BAD62E /* AlertController.py */; };
@@ -34,7 +35,7 @@
C0B9E8B619AF7E9E00DB7247 /* Managed Software Center 10_6.icns in Resources */ = {isa = PBXBuildFile; fileRef = C0B9E8B519AF7E9E00DB7247 /* Managed Software Center 10_6.icns */; };
C0E098BC1857A3C80045DEEB /* msclib.py in Resources */ = {isa = PBXBuildFile; fileRef = C0E098BB1857A3C80045DEEB /* msclib.py */; };
C0EF96BA1ADDB9B2002C02FF /* DockTilePlugIn.m in Sources */ = {isa = PBXBuildFile; fileRef = C0EF96B91ADDB9B2002C02FF /* DockTilePlugIn.m */; };
C0EF96BD1ADDBD88002C02FF /* MSCDockTilePlugin.docktileplugin in Copy Files */ = {isa = PBXBuildFile; fileRef = C0EF96B11ADDB90B002C02FF /* MSCDockTilePlugin.docktileplugin */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
C0EF96BD1ADDBD88002C02FF /* MSCDockTilePlugin.docktileplugin in Copy Files */ = {isa = PBXBuildFile; fileRef = C0EF96B11ADDB90B002C02FF /* MSCDockTilePlugin.docktileplugin */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
C0F1586E187D256200052F9A /* MyStuffTemplate.png in Resources */ = {isa = PBXBuildFile; fileRef = C0F1586D187D256200052F9A /* MyStuffTemplate.png */; };
/* End PBXBuildFile section */
@@ -96,6 +97,7 @@
C02C98901911B83000425167 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = "<group>"; };
C02C98911911B83100425167 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = "<group>"; };
C035274D18A9C582004A5AE4 /* libpython2.6.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; name = libpython2.6.dylib; path = ../../../../../../System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/config/libpython2.6.dylib; sourceTree = SOURCE_ROOT; };
C0453A201CCEF7B60002D396 /* MSCLogWindowController.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = MSCLogWindowController.py; sourceTree = "<group>"; };
C046261519FFF8C000AF1E48 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/MainMenu.strings; sourceTree = "<group>"; };
C046261619FFF8CC00AF1E48 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = "<group>"; };
C046261719FFF8DA00AF1E48 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = "<group>"; };
@@ -239,6 +241,7 @@
C0AAA21F18BC67F90012663F /* mschtml.py */,
C079D9C018BD435200BAD62E /* AlertController.py */,
650B29A319B69FC800A5E946 /* MSCToolbar.py */,
C0453A201CCEF7B60002D396 /* MSCLogWindowController.py */,
);
path = "Managed Software Center";
sourceTree = "<group>";
@@ -335,7 +338,7 @@
isa = PBXProject;
attributes = {
CLASSPREFIX = MSC;
LastUpgradeCheck = 0510;
LastUpgradeCheck = 0720;
ORGANIZATIONNAME = "The Munki Project";
TargetAttributes = {
C0EF96B01ADDB90B002C02FF = {
@@ -422,6 +425,7 @@
C05C3CEF188391F200095E65 /* munki.py in Resources */,
C079D9C118BD435200BAD62E /* AlertController.py in Resources */,
C00A4C57185FCEC9004EB3B7 /* FoundationPlist.py in Resources */,
C0453A211CCEF7B60002D396 /* MSCLogWindowController.py in Resources */,
C090050816CDD84E00BE34CE /* MSCAppDelegate.py in Resources */,
C0AE865A186D32AF00C87AE7 /* MSCBadgedTemplateImage.py in Resources */,
C0A71B76188A47C700A6EE82 /* MSCMainWindowController.py in Resources */,
@@ -606,6 +610,7 @@
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_ENABLE_OBJC_EXCEPTIONS = YES;
@@ -649,7 +654,6 @@
C090050F16CDD84E00BE34CE /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = "$(ARCHS_STANDARD_32_64_BIT)";
COMBINE_HIDPI_IMAGES = YES;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "Managed Software Center/Managed Software Center-Prefix.pch";
@@ -665,6 +669,7 @@
);
MACOSX_DEPLOYMENT_TARGET = 10.6;
ONLY_ACTIVE_ARCH = NO;
PRODUCT_BUNDLE_IDENTIFIER = com.googlecode.munki.ManagedSoftwareCenter;
PRODUCT_NAME = "Managed Software Center";
SDKROOT = macosx;
WRAPPER_EXTENSION = app;
@@ -674,7 +679,6 @@
C090051016CDD84E00BE34CE /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = "$(ARCHS_STANDARD_32_64_BIT)";
COMBINE_HIDPI_IMAGES = YES;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "Managed Software Center/Managed Software Center-Prefix.pch";
@@ -689,6 +693,7 @@
"$(SYSTEM_LIBRARY_DIR)/Frameworks/Python.framework/Versions/2.6/lib/python2.6/config",
);
MACOSX_DEPLOYMENT_TARGET = 10.6;
PRODUCT_BUNDLE_IDENTIFIER = com.googlecode.munki.ManagedSoftwareCenter;
PRODUCT_NAME = "Managed Software Center";
SDKROOT = macosx;
WRAPPER_EXTENSION = app;
@@ -698,7 +703,6 @@
C0EF96B61ADDB90B002C02FF /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = "$(ARCHS_STANDARD_32_64_BIT)";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = NO;
CLANG_WARN_BOOL_CONVERSION = YES;
@@ -731,6 +735,7 @@
"-framework",
AppKit,
);
PRODUCT_BUNDLE_IDENTIFIER = "com.googlecode.munki.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
SKIP_INSTALL = NO;
@@ -741,7 +746,6 @@
C0EF96B71ADDB90B002C02FF /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = "$(ARCHS_STANDARD_32_64_BIT)";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = NO;
CLANG_WARN_BOOL_CONVERSION = YES;
@@ -770,6 +774,7 @@
"-framework",
AppKit,
);
PRODUCT_BUNDLE_IDENTIFIER = "com.googlecode.munki.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
SKIP_INSTALL = NO;
@@ -1,6 +1,6 @@
# encoding: utf-8
#
# Copyright 2009-2014 Greg Neagle.
# Copyright 2009-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -3,7 +3,7 @@
# MSCAppDelegate.py
# Managed Software Center
#
# Copyright 2013-2014 Greg Neagle.
# Copyright 2013-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -3,7 +3,7 @@
# MSCBadgedTemplateImage.py
# Managed Software Center
#
# Copyright 2014 Greg Neagle.
# Copyright 2014-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -68,8 +68,8 @@ class MSCBadgedTemplateImage(NSImage):
badgeOutlineWidth, badgeOutlineHeight)
# shift the rects around to look better. These are magic numbers.
badgeStringRect = NSOffsetRect(badgeStringRect, -4.75, -1)
badgeOutlineRect = NSOffsetRect(badgeOutlineRect, -1, -4)
badgeStringRect = NSOffsetRect(badgeStringRect, -4.75, -2)
badgeOutlineRect = NSOffsetRect(badgeOutlineRect, -1, -5)
# our erase rect needs to be a little bigger than the badge itself
badgeEraseRect = NSInsetRect(badgeOutlineRect, -1.5, -1.5)
@@ -0,0 +1,205 @@
# -*- coding: utf-8 -*-
#
# MSCLogWindowController.py
# Managed Software Center
#
# Created by Greg Neagle on 4/25/16.
# Copyright (c) 2016 Munki Project. All rights reserved.
#
# Much code borrowed from https://github.com/MagerValp/LoginLog
# with the blessing of MagerValp
#
from objc import YES, NO, IBAction, IBOutlet
# pylint: disable=wildcard-import
# pylint: disable=unused-wildcard-import
# pylint: disable=redefined-builtin
from Foundation import *
from AppKit import *
# pylint: enable=redefined-builtin
# pylint: enable=wildcard-import
import munki
import os
# lots of camelCase names, following Cocoa convention
# pylint: disable=invalid-name
class MSCLogViewDataSource(NSObject):
"""Data source for an NSTableView that displays an array of text lines.
Line breaks are assumed to be LF, and partial lines from incremental
reading is handled."""
# since this subclasses NSObject,
# it doesn't have a Python __init__method
# pylint: disable=no-init
logFileData = NSMutableArray.alloc().init()
filteredData = logFileData
lastLineIsPartial = False
filterText = ''
def tableView_writeRowsWithIndexes_toPasteboard_(
self, aTableView, rowIndexes, pasteboard):
'''Implements drag-n-drop of text rows to external apps'''
text_to_copy = ''
index_set = aTableView.selectedRowIndexes()
index = index_set.firstIndex()
while index != NSNotFound:
line = self.filteredData.objectAtIndex_(index)
text_to_copy += line + '\n'
index = index_set.indexGreaterThanIndex_(index)
#changeCount = pasteboard.clearContents()
result = pasteboard.writeObjects_([text_to_copy])
return YES
def applyFilterToData(self):
'''Filter our log data'''
if len(self.filterText):
filterPredicate = NSPredicate.predicateWithFormat_(
'self CONTAINS[cd] %@', self.filterText)
self.filteredData = (
self.logFileData.filteredArrayUsingPredicate_(filterPredicate))
else:
self.filteredData = self.logFileData
def addLine_partial_(self, line, isPartial):
'''Add a line to our datasource'''
if self.lastLineIsPartial:
joinedLine = self.logFileData.lastObject() + line
self.logFileData.removeLastObject()
self.logFileData.addObject_(joinedLine)
else:
self.logFileData.addObject_(line)
self.lastLineIsPartial = isPartial
self.applyFilterToData()
def removeAllLines(self):
'''Remove all data from our datasource'''
self.logFileData.removeAllObjects()
def lineCount(self):
'''Return the number of lines in our filtered data'''
return self.filteredData.count()
def numberOfRowsInTableView_(self, tableView):
'''Required datasource method'''
return self.lineCount()
def tableView_objectValueForTableColumn_row_(self, tableView, column, row):
'''Required datasource method -- returns the text data for the
given row and column'''
if column.identifier() == 'data':
return self.filteredData.objectAtIndex_(row)
else:
return ''
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()
@@ -3,7 +3,7 @@
# MSCMainWindowController.py
# Managed Software Center
#
# Copyright 2013-2014 Greg Neagle.
# Copyright 2013-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -2,7 +2,7 @@
#
# MSCStatusController.py
#
# Copyright 2009-2014 Greg Neagle.
# Copyright 2009-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -2,22 +2,16 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSUserNotificationAlertStyle</key>
<string>alert</string>
<key>NSDockTilePlugIn</key>
<string>MSCDockTilePlugin.docktileplugin</string>
<key>CFBundleDisplayName</key>
<string>${PRODUCT_NAME}</string>
<key>LSHasLocalizedDisplayName</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIconFile</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>com.googlecode.munki.ManagedSoftwareCenter</string>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
@@ -25,21 +19,9 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>4.1</string>
<string>4.2</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>GitRevision</key>
<string></string>
<key>LSMinimumSystemVersion</key>
<string>${MACOSX_DEPLOYMENT_TARGET}</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2015 The Munki Project https://github.com/munki/munki</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
@@ -51,5 +33,28 @@
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>1</string>
<key>GitRevision</key>
<string></string>
<key>LSHasLocalizedDisplayName</key>
<true/>
<key>LSMinimumSystemVersion</key>
<string>${MACOSX_DEPLOYMENT_TARGET}</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>NSDockTilePlugIn</key>
<string>MSCDockTilePlugin.docktileplugin</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2016 The Munki Project https://github.com/munki/munki</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>NSUserNotificationAlertStyle</key>
<string>alert</string>
</dict>
</plist>
@@ -5,7 +5,7 @@
#
# Created by Greg Neagle on 2/21/14.
#
# Copyright 2014 Greg Neagle.
# Copyright 2014-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -49,7 +49,7 @@ option {
}
body, button, input {
font-family: "Helvetica", "Arial";
font-family: -apple-system, "Helvetica Neue", "Helvetica", "Arial";
font-smooth: always;
-webkit-font-smoothing: antialiased
}
@@ -223,8 +223,8 @@ a {
html, body {
-webkit-background-size: 36px 635px;
background-repeat: repeat-x;
background-color: #f5f6f7;
font-family: "Lucida Grande", Helvetica, sans-serif;
background-color: white;
font-family: -apple-system, "Helvetica Neue", Helvetica, sans-serif;
font-size: 11px
}
@@ -276,7 +276,7 @@ div.columns {
.sidebar {
width: 200px;
margin-left: 15px
margin-left: 26px;
}
.sidebar h1, .main>h1 {
@@ -284,15 +284,14 @@ div.columns {
-webkit-font-smoothing: subpixel-antialiased;
background-color: #002354;
color: transparent;
font-family: Helvetica;
font-family: -apple-system, "Helvetica Neue", Helvetica;
font-size: 24px;
font-weight: bold;
font-weight: normal;
line-height: 26px;
margin-bottom: 5px;
overflow: visible;
position: relative;
text-shadow: rgba(255, 255, 255, .496094) 0 1px 2px;
top: -5px
top: -5px;
}
footer {
@@ -309,7 +308,7 @@ footer {
}
footer div.bottom-links {
margin: 0 35px 0 0;
margin: 0 0 0 0;
border-top: 1px solid #d3d7db;
position: relative
}
@@ -324,7 +323,6 @@ footer div.bottom-links * {
color: #4f5f6e;
font-size: 10px;
font-weight: normal;
text-shadow: #fff 0 1px 0
}
footer div.bottom-links>span {
@@ -333,7 +331,7 @@ footer div.bottom-links>span {
footer div.bottom-links li a {
color: #576982;
font-weight: bold
font-weight: normal
}
footer div.bottom-links>ul>li:not(:last-child):after {
@@ -396,14 +394,14 @@ div.select {
div.select select {
background: -webkit-gradient(linear, left top, left bottom, from( #fff), to( #eaeaea));
font-size: 11px;
font-weight: bold;
color: #626262;
font-weight: normal;
/*color: #626262;*/
outline: 0;
line-height: 15px;
margin: 0;
padding: 0 25px 1px 5px;
border: 0;
text-shadow: #fff 0 1px 0;
/*text-shadow: #fff 0 1px 0;*/
-webkit-appearance: none;
-webkit-box-shadow: rgba(0, 0, 0, .35) 0 1px 2px, rgba(0, 0, 0, .8) 0 0 1px;
box-shadow: rgba(0, 0, 0, .35) 0 1px 2px, rgba(0, 0, 0, .2) 0 0 1px;
@@ -443,19 +441,14 @@ div.select:after {
}
div.titled-box {
background-color: #fff;
margin-bottom: 15px;
padding-bottom: 10px;
-webkit-border-radius: 5px;
-webkit-box-shadow: rgba(0, 0, 0, .45) 0 1px 3px, rgba(0, 0, 0, .8) 0 0 1px;
box-shadow: rgba(0, 0, 0, .45) 0 1px 4px, rgba(0, 0, 0, .2) 0 0 1px;
-webkit-background-clip: padding
}
div.titled-box h2 {
font: bold 13px 'Lucida Grande';
font: normal 14px -apple-system, "Helvetica Neue", 'Helvetica';
color: #53565e;
padding: 0 10px;
line-height: 24px;
overflow: hidden;
text-overflow: ellipsis;
@@ -463,9 +456,9 @@ div.titled-box h2 {
}
div.titled-box h2 a {
font: bold 12px 'Lucida Grande';
font: normal 12px -apple-system, "Helvetica Neue", 'Helvetica';
color: #53565e;
line-height: 25px
line-height: 24px
}
div.titled-box>div.content {
@@ -477,11 +470,11 @@ div.titled-box.quick-links>div.content {
}
div.titled-box h2, div.titled-box hr {
border-width: 0 0 3px 0;
border: none;
}
div.titled-box hr {
margin: 8px 0
margin: 14px 0
}
div.titled-box li.popup {
@@ -507,7 +500,7 @@ div.titled-box li.link {
padding: 0 11px;
height: 19px;
line-height: 16px;
font-weight: bold;
font-weight: normal;
border-color: transparent;
border-width: 1px 0;
border-style: solid
@@ -523,7 +516,7 @@ div.titled-box li.link.selected {
div.titled-box li.link a {
display: block;
font-size: 11px;
font-weight: bold;
font-weight: normal;
color: #222;
overflow: hidden;
text-overflow: ellipsis;
@@ -539,32 +532,24 @@ body.screen-reader div.chart ol>li div.lockup div.lockup-info>div.multi-button {
}
div.lockup-container, div.container, div.titled-container {
background-color: #fff;
-webkit-border-radius: 5px;
-webkit-box-shadow: rgba(0, 0, 0, .45) 0 1px 3px, rgba(0, 0, 0, .8) 0 0 1px;
box-shadow: rgba(0, 0, 0, .45) 0 1px 4px, rgba(0, 0, 0, .2) 0 0 1px;
-webkit-background-clip: padding
/*-webkit-border-radius: 5px;*/
}
div.lockup-container .title, div.titled-container .title {
padding: 0 10px;
height: 32px;
border-bottom: 1px solid #d7d7d7
}
div.lockup-container .title h2, div.titled-container .title h2 {
display: inline-block;
font: bold 14px 'Lucida Grande';
font: normal 14px -apple-system, "Helvetica Neue", 'Helvetica';
color: #53565e;
padding: 0 10px 0 0;
line-height: 32px
}
div.lockup-container .title, div.titled-container .title, div.titled-box h2 {
text-shadow: 0 1px 0 #fff;
-webkit-border-top-left-radius: 5px;
-webkit-border-top-right-radius: 5px;
background: -webkit-gradient(linear, left bottom, left top, from( #e3e3e3), color-stop(0.49, #ededed), color-stop(0.5, #f4f4f4), to(#fff))
}
div.titled-box h2 {
@@ -572,14 +557,16 @@ div.titled-box h2 {
border-bottom: 1px solid #cacaca;
border-width: 0 0 1px !important;
margin-bottom: 6px;
-webkit-border-image: none
margin-top: 10px;
-webkit-border-image: none;
font-size: 13px;
line-height: 22px
}
div.lockup-container .title h1 {
display: inline-block;
font: bold 14px Lucida Grande;
font: normal 14px -apple-system, "Helvetica Neue", Helvetica;
color: #53565e;
padding: 0 10px 0 0;
line-height: 32px
}
@@ -591,7 +578,7 @@ div.lockup-container .title h1 a {
div.lockup-container .title span {
color: #53565e;
font-size: 10px;
font-weight: bold
font-weight: normal
}
div.lockup-container .content-and-controls, div.titled-container .content-and-controls {
@@ -604,7 +591,6 @@ div.lockup-container .content-and-controls:before, div.titled-container .content
position: absolute;
width: 100%;
top: -1px;
border-width: 0 0 3px 0;
}
div.lockup-container .content-and-controls:after, div.titled-container .content-and-controls:before {
@@ -623,9 +609,6 @@ div.lockup-container div.lockup {
float: left;
width: 33.3%;
height: 98px;
border-right: 1px solid #d9d9d9;
border-bottom: 1px solid #d9d9d9;
border-left: 1px solid #fff
}
div.lockup-container div.lockup div.artwork {
@@ -673,18 +656,6 @@ div.lockup-container[data-columns-current="3"] div.lockup:nth-of-type(3n+2) {
width: 33.4%
}
div.lockup-container[data-columns-current="3"] div.lockup:nth-of-type(6n+1),
div.lockup-container[data-columns-current="3"] div.lockup:nth-of-type(6n+2),
div.lockup-container[data-columns-current="3"] div.lockup:nth-of-type(6n+3) {
background: #fff
}
div.lockup-container[data-columns-current="3"] div.lockup:nth-of-type(6n+4),
div.lockup-container[data-columns-current="3"] div.lockup:nth-of-type(6n+5),
div.lockup-container[data-columns-current="3"] div.lockup:nth-of-type(6n+6) {
background: -webkit-gradient(linear, left top, left bottom, from( #f3f3f3), to( #eaeaea))
}
div.lockup-container[data-columns-current="3"] div.lockup:nth-of-type(3n+1) {
border-left: 0
}
@@ -693,43 +664,16 @@ div.lockup-container:not(.shelf)[data-columns-current="3"] div.lockup:nth-of-typ
display: table-cell;
/*float: none;*/
/*height: auto;*/
border-right: 0;
}
div.lockup-container[data-columns-current="3"] div.lockup:nth-last-of-type(3) {
-webkit-border-bottom-left-radius: 5px
}
div.lockup-container[data-columns-current="4"] div.lockup:nth-of-type(8n+1),
div.lockup-container[data-columns-current="4"] div.lockup:nth-of-type(8n+2),
div.lockup-container[data-columns-current="4"] div.lockup:nth-of-type(8n+3),
div.lockup-container[data-columns-current="4"] div.lockup:nth-of-type(8n+4) {
background: #fff
}
div.lockup-container[data-columns-current="4"] div.lockup:nth-of-type(8n+5),
div.lockup-container[data-columns-current="4"] div.lockup:nth-of-type(8n+6),
div.lockup-container[data-columns-current="4"] div.lockup:nth-of-type(8n+7),
div.lockup-container[data-columns-current="4"] div.lockup:nth-of-type(8n+8) {
background: -webkit-gradient(linear, left top, left bottom, from( #f3f3f3), to( #eaeaea))
}
div.lockup-container[data-columns-current="4"] div.lockup, div.lockup-container[data-columns-current="4"] div.lockup:nth-of-type(3n+2) {
width: 25%
}
div.lockup-container[data-columns-current="4"] div.lockup:nth-of-type(3n+1) {
border-left: 1px solid #fff
}
div.lockup-container[data-columns-current="4"] div.lockup:nth-of-type(3n) {
border-right: 1px solid #d9d9d9
}
div.lockup-container[data-columns-current="4"] div.lockup:nth-of-type(4n+1) {
border-left: 0
}
div.lockup-container:not(.shelf)[data-columns-current="4"] div.lockup:nth-of-type(4n) {
display: table-cell;
/*float: none;
@@ -749,41 +693,10 @@ div.lockup-container[data-columns-current="5"] div.lockup, div.lockup-container[
width: 20%
}
div.lockup-container[data-columns-current="5"] div.lockup:nth-of-type(10n+1),
div.lockup-container[data-columns-current="5"] div.lockup:nth-of-type(10n+2),
div.lockup-container[data-columns-current="5"] div.lockup:nth-of-type(10n+3),
div.lockup-container[data-columns-current="5"] div.lockup:nth-of-type(10n+4),
div.lockup-container[data-columns-current="5"] div.lockup:nth-of-type(10n+5) {
background: #fff
}
div.lockup-container[data-columns-current="5"] div.lockup:nth-of-type(10n+6),
div.lockup-container[data-columns-current="5"] div.lockup:nth-of-type(10n+7),
div.lockup-container[data-columns-current="5"] div.lockup:nth-of-type(10n+8),
div.lockup-container[data-columns-current="5"] div.lockup:nth-of-type(10n+9),
div.lockup-container[data-columns-current="5"] div.lockup:nth-of-type(10n+10) {
background: -webkit-gradient(linear, left top, left bottom, from( #f3f3f3), to( #eaeaea))
}
div.lockup-container[data-columns-current="5"] div.lockup:nth-of-type(3n+1),
div.lockup-container[data-columns-current="5"] div.lockup:nth-of-type(4n+1) {
border-left: 1px solid #fff
}
div.lockup-container[data-columns-current="5"] div.lockup:nth-of-type(3n),
div.lockup-container[data-columns-current="5"] div.lockup:nth-of-type(4n) {
border-right: 1px solid #d9d9d9
}
div.lockup-container[data-columns-current="5"] div.lockup:nth-of-type(5n+1) {
border-left: 0
}
div.lockup-container:not(.shelf)[data-columns-current="5"] div.lockup:nth-of-type(5n) {
display: table-cell;
/*float: none;
height: auto;*/
border-right: 0
}
div.lockup-container[data-columns-current="5"] div.lockup:nth-last-of-type(5) {
@@ -817,7 +730,6 @@ div.lockup ul li {
div.lockup li.name {
color: #000;
font-weight: bold;
max-width: 190px
}
@@ -839,8 +751,7 @@ div.lockup-container div.lockup ul li {
div.lockup-container div.lockup.category ul {
margin-top: 6px;
font-weight: bold;
text-shadow: 0 1px 0 #fff
font-weight: normal;
}
div.lockup-container div.lockup.category li.genre a {
@@ -853,13 +764,13 @@ div.msc-button.small {
div.msc-button-inner {
height: 13px;
-webkit-border-radius: 10px;
background: -webkit-gradient(linear, 0% 0, 0% 100%, from( #A8AAAF), color-stop(0.05, #A6A8AE), color-stop(0.5, #A1A3A9), color-stop(0.96, #9C9EA4), to( #989DA3));
-webkit-border-radius: 3px;
background: -webkit-gradient(linear, 0% 0, 0% 100%, from(rgb(166, 168, 174)), to(rgb(157, 159, 164)));
border: 1px solid rgba(136, 136, 138, .746094);
text-transform: uppercase;
/*text-shadow: rgba(0, 0, 0, .5) 0 -1px 0;*/
color: #fff;
font: normal normal bold 9px/11px 'Lucida Grande';
font: normal normal bold 9px/12px 'Helvetica';
padding-left: 10px;
padding-right: 10px;
}
@@ -878,7 +789,7 @@ div.msc-button-inner.removing {
}
div.msc-button-inner:not(.installed-not-removable):hover {
background: -webkit-gradient(linear, left top, left bottom, from( #85868d), color-stop(0.05, #84858c), color-stop(0.5, #888188), color-stop(0.96, #7b7c83), to( #7a7bb2))
background: -webkit-gradient(linear, left top, left bottom, from( #85868d), to( #7a7bb2))
}
div.msc-button button {
@@ -901,11 +812,10 @@ div.msc-button-inner.large {
font-size: 13px;
font-family: "Helvetica";
text-transform: capitalize;
line-height: 24px;
border: 0;
line-height: 21px;
border: 1px solid rgb(42, 73, 118);
color: #fff;
text-shadow: rgba(0, 0, 0, .5) 0 -1px 0;
background: -webkit-gradient(linear, left top, left bottom, from( #367FD4), color-stop(0.50, #2255A0), color-stop(0.49, #2B68B6), to( #234F92));
background: -webkit-gradient(linear, left top, left bottom, from(rgb(73,124,205)), to(rgb(47,79,143)));
border-color: #1F4A7A;
-webkit-border-radius: 5px;
}
@@ -925,7 +835,7 @@ div.msc-button-inner.large.removing {
}
div.msc-button-inner.large:not(.installed-not-removable):hover {
background: -webkit-gradient(linear, left top, left bottom, from( #0067B8), color-stop(0.49, #00509A), color-stop(0.50, #003E85), to( #003777))
background: -webkit-gradient(linear, left top, left bottom, from( #0067B8), to( #003777))
}
div.msc-button.install-updates {
@@ -1072,9 +982,9 @@ div.msc-button-inner.install-updates:not(.installed-not-removable):hover {
div.progress-spinner {
background: url(progress-spinner.png) 0 0 no-repeat;
-webkit-background-size: 21px 20px;
-webkit-background-size: 20px 20px;
opacity: 1;
width: 21px;
width: 20px;
height: 20px;
-webkit-animation-name: 'generic-loading-spinner-animation';
-webkit-animation-iteration-count: infinite;
@@ -1202,7 +1112,6 @@ div.showcase {
overflow: hidden;
-webkit-transition: opacity .25s;
-webkit-border-bottom-right-radius: 8px;
-webkit-box-shadow: 0 1px 3px #999;
position: relative;
left: 0;
margin-right: 0;
@@ -1223,7 +1132,7 @@ div.stage>img {
position: absolute;
height: 200px;
left: 0;
background-color: #000;
/*background-color: #000;*/
text-align: left;
overflow: hidden;
-webkit-transition: opacity 1s ease-in-out;
@@ -1346,9 +1255,9 @@ div.lockup li.removing span.progress-spinner {
visibility: visible !important;
float: right;
background: url(progress-spinner.png) 0 0 no-repeat;
-webkit-background-size: 21px 20px;
-webkit-background-size: 20px 20px;
opacity: 1;
width: 21px;
width: 20px;
height: 20px;
-webkit-animation-name: 'generic-loading-spinner-animation';
-webkit-animation-iteration-count: infinite;
@@ -1361,8 +1270,8 @@ div.lockup li.preparing-removal span.progress-spinner,
div.lockup li.installing span.progress-spinner,
div.lockup li.removing span.progress-spinner {
position: absolute;
bottom: 2px;
right: 2px;
bottom: 20px;
right: 20px;
}
div.lockup li {
@@ -40,11 +40,11 @@ body.product .product-detail .product-info {
}
body.product .product-detail .product-info .title h1 {
margin-bottom: 12px;
font-family: Helvetica;
font-family: -apple-system, "Helvetica Neue", Helvetica;
font-size: 24px;
line-height: 22px;
font-weight: bold;
color: #6d83a2;
/*font-weight: bold;*/
/*color: #6d83a2;*/
text-shadow: rgba(255,255,255,1) 0 1px 0
}
body.product .product-detail .product-info .title a {
@@ -52,7 +52,7 @@ color: currentcolor
}
body.product .product-detail .product-info .product-review h4 {
margin-bottom: 4px;
font-family: Helvetica;
font-family: -apple-system, "Helvetica Neue", Helvetica;
font-size: 16px;
font-weight: bold;
color: #6d83a2;
@@ -63,7 +63,6 @@ font-size: 12px;
line-height: 16px
}
body.product .sidebar .app-info .content {
padding: 0 10px;
font-size: 11px;
color: #000
}
@@ -72,7 +71,7 @@ margin-bottom: 5px
}
body.product .sidebar .app-info li .label,
body.product .sidebar .app-info p span.label {
font-weight: bold;
font-weight: normal;
color: #787878
}
body.product .sidebar .more-by {
@@ -85,7 +84,6 @@ body.product .sidebar .more-by div.lockup {
display: -webkit-box;
padding: 7px 10px;
border-width: 0 0 1px 0;
background-image: -webkit-gradient(linear,left top,left bottom,from(#fff),to( #f9f9f9));
height: 55px
}
body.product .sidebar .more-by div.lockup:last-child {
@@ -115,7 +113,7 @@ overflow: hidden
body.product .sidebar .more-by div.lockup li.name {
margin-bottom: 1px;
font-size: 11px;
font-weight: bold
/*font-weight: bold*/
}
body.product .sidebar .more-by div.lockup li.genre {
color: #6c6c6c;
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

@@ -22,6 +22,9 @@
.main>div:not(:last-of-type) {
margin-bottom: 20px
}
div.status-results {
border-bottom: 1px solid #d9d9d9
}
div.installations {
margin: 14px 0;
position: relative
@@ -41,19 +44,15 @@ div.installations table:not(.no-header):before {
}
div.installations thead {
border-radius: 5px 5px 0 0;
padding: 0 0 0 10px;
height: 32px;
border-bottom: 1px solid #d7d7d7;
text-shadow: 0 1px 0 #fff;
background: -webkit-gradient(linear,left bottom,left top,from( #e3e3e3),color-stop(0.49, #ededed),color-stop(0.5, #f4f4f4),to(#fff))
}
div.installations th {
color: #53565e;
font-size: 14px;
font-weight: bold;
font-weight: normal;
height: 32px;
line-height: 14px;
padding: 0 10px;
text-align: left
}
#updates div.installations th {
@@ -63,7 +62,13 @@ div.installations th {
}
#update-count-string {
margin-left: -10px
/*margin-left: -10px*/
}
#updates-progress-spinner {
margin-right: 4px;
position: relative;
left: 0px;
}
div.installations th:first-child {
@@ -72,9 +77,6 @@ div.installations th:first-child {
div.installations th:last-child {
-webkit-border-top-right-radius: 5px
}
div.installations tbody tr:nth-of-type(even) {
background: -webkit-gradient(linear,left top,left bottom,from( #f3f3f3),to( #eaeaea))
}
.installation td {
border-top: 1px solid #fff;
border-bottom: 1px solid #d9d9d9;
@@ -83,6 +85,9 @@ div.installations tbody tr:nth-of-type(even) {
.installation td,.installation td a {
color: #494949
}
.installation td.install-info-cell,.installation td.install-info-cell a, li.install-info-cell {
color: rgb(121,121,121)
}
.installation:first-child td {
border-top: transparent
}
@@ -100,7 +105,7 @@ div.installations tbody tr:nth-of-type(even) {
.installation h2 {
color: #565656;
font-size: 13px;
font-weight: bold;
font-weight: normal;
overflow: hidden;
text-overflow: ellipsis;
max-width: 300px
@@ -133,7 +138,7 @@ div.installations tbody tr:nth-of-type(even) {
-webkit-box-shadow: none
}
#other-updates #os-updates,#os-and-app-updates #os-updates {
border-bottom: 1px solid #d9d9d9
/*border-bottom: 1px solid #d9d9d9*/
}
#other-updates #os-updatesth:last-child,#os-and-app-updates #os-updates th:last-child {
width: 100%;
@@ -148,29 +153,21 @@ div.installations tbody tr:nth-of-type(even) {
top: 6px;
left: 0
}
#os-and-app-updates #os-updates:not(.hidden)+#app-updates tbody tr:nth-of-type(even) {
background: 0
}
#os-and-app-updates #os-updates:not(.hidden)+#app-updates tbody tr:nth-of-type(odd) {
background: -webkit-gradient(linear,left top,left bottom,from( #f3f3f3),to( #eaeaea))
}
@-webkit-keyframes fade-out-and-remove {
0% {opacity: 1}
100% {opacity: 0}
}
#purchases {
text-shadow:#fff 0 1px 0
}
div.purchases .installation td {
height: 41px
}
div.purchases .installation:last-child td:first-child {
/*div.purchases .installation:last-child td:first-child {
-webkit-border-bottom-left-radius: 5px
}
div.purchases .installation:last-child td:last-child {
-webkit-border-bottom-right-radius: 5px
}
}*/
div.purchases .installation td:first-child {
width: 225px
}
@@ -238,9 +235,8 @@ div.purchases .installation td.status span.preparing-removal
}
#updates #header h1 {
font-size: 15px;
font-weight: bold;
font-weight: normal;
color: #929292;
text-shadow: 0 1px 0 #fff;
text-align: center
}
div.updates table {
@@ -385,13 +381,12 @@ div.os-updates.expanded tbody tr:nth-child(n+3):before {
top: 0;
right: 100px;
left: 240px;
background: -webkit-linear-gradient(left,rgba(0,0,0,.04) 0,rgba(0,0,0,.16) 20%,rgba(0,0,0,.16) 80%,rgba(0,0,0,.04) 100%)
}
div.os-updates.expanded tbody tr.sosumi:before {
display: none
}
.activity-indicator {
font: bold 20px Helvetica
font: normal 20px -apple-system, "Helvetica Neue", Helvetica
}
#updates #header .scan-progress {
padding: 0 0 1px 13px
@@ -2,4 +2,4 @@
"CFBundleName" = "Managed Software Center";
"CFBundleDisplayName" = "Managed Software Center";
NSHumanReadableCopyright = "Copyright © 2010-2014 The Munki Project\nhttps://github.com/munki/munki";
NSHumanReadableCopyright = "Copyright © 2010-2016 The Munki Project\nhttps://github.com/munki/munki";
@@ -146,5 +146,8 @@
/* Class = "NSMenuItem"; title = "Updates"; ObjectID = "tv9-wZ-XWN"; */
"tv9-wZ-XWN.title" = "Opdateringer";
/* Class = "NSMenuItem"; title = "Show Log"; ObjectID = "vPs-dO-LDa"; */
"vPs-dO-LDa.title" = "Vis log";
/* Class = "NSMenuItem"; title = "Forward"; ObjectID = "z4Z-vu-XGX"; */
"z4Z-vu-XGX.title" = "Frem";
@@ -2,4 +2,4 @@
"CFBundleName" = "Geführte Softwareaktualisierung";
"CFBundleDisplayName" = "Geführte Softwareaktualisierung";
NSHumanReadableCopyright = "Copyright © 2010-2014 The Munki Project\nhttps://github.com/munki/munki";
NSHumanReadableCopyright = "Copyright © 2010-2016 The Munki Project\nhttps://github.com/munki/munki";
@@ -146,5 +146,8 @@
/* Class = "NSMenuItem"; title = "Updates"; ObjectID = "tv9-wZ-XWN"; */
"tv9-wZ-XWN.title" = "Updates";
/* Class = "NSMenuItem"; title = "Show Log"; ObjectID = "vPs-dO-LDa"; */
"vPs-dO-LDa.title" = "Protokoll einblenden";
/* Class = "NSMenuItem"; title = "Forward"; ObjectID = "z4Z-vu-XGX"; */
"z4Z-vu-XGX.title" = "Weiter";
@@ -2,4 +2,4 @@
"CFBundleName" = "Managed Software Center";
"CFBundleDisplayName" = "Managed Software Center";
NSHumanReadableCopyright = "Copyright © 2010-2014 The Munki Project\nhttps://github.com/munki/munki";
NSHumanReadableCopyright = "Copyright © 2010-2016 The Munki Project\nhttps://github.com/munki/munki";
@@ -1,9 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="7531" systemVersion="14D136" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10116" systemVersion="15E65" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none">
<dependencies>
<deployment version="1060" identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="7531"/>
<plugIn identifier="com.apple.WebKitIBPlugin" version="7531"/>
<development version="6300" identifier="xcode"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="10116"/>
<plugIn identifier="com.apple.WebKitIBPlugin" version="10116"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication"/>
@@ -182,6 +183,12 @@
<action selector="toggleFullScreen:" target="-1" id="3pL-iV-Ndq"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="lBR-K5-svb"/>
<menuItem title="Show Log" keyEquivalent="l" id="vPs-dO-LDa">
<connections>
<action selector="showLogWindow:" target="a6z-Lq-EqL" id="sqE-gM-htm"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
@@ -242,7 +249,7 @@
<size key="minSize" width="48" height="30"/>
<size key="maxSize" width="48" height="30"/>
<button key="view" id="YGi-pE-3SV" customClass="MSCToolbarButton">
<rect key="frame" x="8" y="14" width="48" height="30"/>
<rect key="frame" x="9" y="14" width="48" height="30"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="squareTextured" bezelStyle="texturedSquare" image="toolbarCategoriesTemplate" imagePosition="only" alignment="center" inset="2" id="Vtx-wt-JLV" customClass="MSCToolbarButtonCell">
<behavior key="behavior" pushIn="YES" changeContents="YES" lightByContents="YES"/>
@@ -274,7 +281,7 @@
<size key="minSize" width="48" height="30"/>
<size key="maxSize" width="48" height="30"/>
<button key="view" id="caG-fL-UJh" customClass="MSCToolbarButton">
<rect key="frame" x="1" y="14" width="48" height="30"/>
<rect key="frame" x="2" y="14" width="48" height="30"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="squareTextured" bezelStyle="texturedSquare" image="updatesTemplate" imagePosition="overlaps" alignment="center" inset="2" id="ZVD-Hl-dlF" customClass="MSCToolbarButtonCell">
<behavior key="behavior" pushIn="YES" changeContents="YES" lightByContents="YES"/>
@@ -290,7 +297,7 @@
<size key="minSize" width="4" height="30"/>
<size key="maxSize" width="4" height="31"/>
<customView key="view" id="Mui-Sc-36b">
<rect key="frame" x="38" y="14" width="4" height="30"/>
<rect key="frame" x="39" y="14" width="4" height="30"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
</customView>
</toolbarItem>
@@ -305,12 +312,12 @@
<rect key="frame" x="0.0" y="0.0" width="150" height="30"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<progressIndicator horizontalHuggingPriority="750" verticalHuggingPriority="750" maxValue="100" displayedWhenStopped="NO" bezeled="NO" indeterminate="YES" controlSize="small" style="spinning" id="mXP-yD-2El">
<rect key="frame" x="62" y="2" width="16" height="16"/>
<progressIndicator wantsLayer="YES" horizontalHuggingPriority="750" verticalHuggingPriority="750" maxValue="100" displayedWhenStopped="NO" bezeled="NO" indeterminate="YES" controlSize="small" style="spinning" id="mXP-yD-2El">
<rect key="frame" x="62" y="3" width="16" height="16"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
</progressIndicator>
<segmentedControl verticalHuggingPriority="750" id="jcc-R8-zGb">
<rect key="frame" x="1" y="-2" width="53" height="25"/>
<rect key="frame" x="1" y="-1" width="53" height="25"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<segmentedCell key="cell" borderStyle="border" alignment="left" style="texturedRounded" trackingMode="momentary" id="ZsM-fh-8z5">
<font key="font" metaFont="system"/>
@@ -326,7 +333,7 @@
</subviews>
</view>
<color key="borderColor" white="0.0" alpha="0.41999999999999998" colorSpace="calibratedWhite"/>
<color key="fillColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<color key="fillColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</box>
</toolbarItem>
<toolbarItem implicitItemIdentifier="E1A9A599-F94D-4780-9330-F2B0F75FA3BA" label="" paletteLabel="Search" tag="-1" id="fbJ-cF-weR">
@@ -341,7 +348,7 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<searchField wantsLayer="YES" verticalHuggingPriority="750" id="gLI-O8-siB">
<rect key="frame" x="0.0" y="0.0" width="150" height="22"/>
<rect key="frame" x="0.0" y="1" width="150" height="22"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<searchFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" borderStyle="bezel" usesSingleLineMode="YES" bezelStyle="round" sendsWholeSearchString="YES" id="Rxm-nO-bmO">
<font key="font" metaFont="system"/>
@@ -355,7 +362,7 @@
</subviews>
</view>
<color key="borderColor" white="0.0" alpha="0.41999999999999998" colorSpace="calibratedWhite"/>
<color key="fillColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<color key="fillColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</box>
</toolbarItem>
</allowedToolbarItems>
@@ -409,7 +416,105 @@
<outlet property="windowMenuSeperatorItem" destination="92" id="eCt-83-MtH"/>
</connections>
</customObject>
<customObject id="a6z-Lq-EqL" customClass="MSCLogWindowController">
<connections>
<outlet property="logView" destination="UAy-M3-yRr" id="cvs-RK-Mg2"/>
<outlet property="pathControl" destination="WBU-xF-NX3" id="90X-cm-AqK"/>
<outlet property="searchField" destination="N3t-et-cTX" id="4hW-Ue-VWp"/>
<outlet property="window" destination="X5I-7f-aJs" id="2H4-ci-ApV"/>
</connections>
</customObject>
<userDefaultsController representsSharedInstance="YES" id="602"/>
<window title="Log" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" animationBehavior="default" id="X5I-7f-aJs" userLabel="Log Window">
<windowStyleMask key="styleMask" titled="YES" closable="YES" resizable="YES"/>
<rect key="contentRect" x="636" y="390" width="512" height="480"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="877"/>
<value key="minSize" type="size" width="512" height="360"/>
<view key="contentView" id="dBq-Rn-ing">
<rect key="frame" x="0.0" y="0.0" width="512" height="480"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<scrollView autohidesScrollers="YES" horizontalLineScroll="17" horizontalPageScroll="10" verticalLineScroll="17" verticalPageScroll="10" usesPredominantAxisScrolling="NO" id="Kh7-rR-yrV">
<rect key="frame" x="-1" y="-1" width="514" height="449"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<clipView key="contentView" id="rwq-IA-IvG">
<rect key="frame" x="1" y="1" width="512" height="447"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView focusRingType="none" verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" alternatingRowBackgroundColors="YES" columnReordering="NO" columnResizing="NO" autosaveColumns="NO" typeSelect="NO" id="UAy-M3-yRr">
<rect key="frame" x="0.0" y="0.0" width="512" height="17"/>
<autoresizingMask key="autoresizingMask"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
<tableColumns>
<tableColumn identifier="spacer" editable="NO" width="10" minWidth="8" maxWidth="3.4028234663852886e+38" id="BrL-gn-tp4">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" alignment="left" title="Text Cell" placeholderString="" id="a1e-6Q-iNS">
<font key="font" metaFont="fixedUser" size="11"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
</tableColumn>
<tableColumn identifier="data" editable="NO" width="502" minWidth="40" maxWidth="2560" id="LA1-bD-SzC">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" white="0.33333298560000002" alpha="1" colorSpace="calibratedWhite"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="charWrapping" selectable="YES" allowsUndo="NO" alignment="left" title="Text Cell" id="Rl8-0j-Dpu">
<font key="font" metaFont="fixedUser" size="11"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
</tableColumn>
</tableColumns>
</tableView>
</subviews>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</clipView>
<scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="YES" id="jbz-w3-Ald">
<rect key="frame" x="1" y="427" width="630" height="16"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="NO" id="050-d8-vNg">
<rect key="frame" x="-100" y="-100" width="15" height="303"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
</scrollView>
<searchField wantsLayer="YES" verticalHuggingPriority="750" id="N3t-et-cTX">
<rect key="frame" x="301" y="452" width="200" height="22"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
<searchFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" borderStyle="bezel" usesSingleLineMode="YES" bezelStyle="round" id="Nc2-ny-iaV">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</searchFieldCell>
<connections>
<action selector="searchFilterChanged:" target="a6z-Lq-EqL" id="bVX-7y-fs3"/>
</connections>
</searchField>
<pathControl verticalHuggingPriority="750" allowsExpansionToolTips="YES" id="WBU-xF-NX3">
<rect key="frame" x="4" y="452" width="278" height="22"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<pathCell key="cell" selectable="YES" refusesFirstResponder="YES" alignment="left" id="gwm-XM-tLs">
<font key="font" metaFont="system"/>
<url key="url" string="file://localhost/Applications/"/>
<color key="backgroundColor" name="windowBackgroundColor" catalog="System" colorSpace="catalog"/>
</pathCell>
</pathControl>
</subviews>
</view>
<connections>
<outlet property="delegate" destination="a6z-Lq-EqL" id="BQa-PG-rdk"/>
</connections>
<point key="canvasLocation" x="68" y="887"/>
</window>
</objects>
<resources>
<image name="AllItemsTemplate" width="28" height="26"/>
@@ -417,6 +522,6 @@
<image name="NSGoLeftTemplate" width="9" height="12"/>
<image name="NSGoRightTemplate" width="9" height="12"/>
<image name="toolbarCategoriesTemplate" width="25" height="24"/>
<image name="updatesTemplate" width="28" height="27.5"/>
<image name="updatesTemplate" width="24" height="24"/>
</resources>
</document>
@@ -2,4 +2,4 @@
"CFBundleName" = "Managed Software Centre";
"CFBundleDisplayName" = "Managed Software Centre";
NSHumanReadableCopyright = "Copyright © 2010-2014 The Munki Project\nhttps://github.com/munki/munki";
NSHumanReadableCopyright = "Copyright © 2010-2016 The Munki Project\nhttps://github.com/munki/munki";
@@ -2,4 +2,4 @@
"CFBundleName" = "Managed Software Centre";
"CFBundleDisplayName" = "Managed Software Centre";
NSHumanReadableCopyright = "Copyright © 2010-2014 The Munki Project\nhttps://github.com/munki/munki";
NSHumanReadableCopyright = "Copyright © 2010-2016 The Munki Project\nhttps://github.com/munki/munki";
@@ -2,4 +2,4 @@
"CFBundleName" = "Managed Software Centre";
"CFBundleDisplayName" = "Managed Software Centre";
NSHumanReadableCopyright = "Copyright © 2010-2014 The Munki Project\nhttps://github.com/munki/munki";
NSHumanReadableCopyright = "Copyright © 2010-2016 The Munki Project\nhttps://github.com/munki/munki";
@@ -2,4 +2,4 @@
"CFBundleName" = "Centro de aplicaciones";
"CFBundleDisplayName" = "Centro de aplicaciones";
NSHumanReadableCopyright = "Copyright © 2010-2014 The Munki Project\nhttps://github.com/munki/munki";
NSHumanReadableCopyright = "Copyright © 2010-2016 The Munki Project\nhttps://github.com/munki/munki";
@@ -146,5 +146,8 @@
/* Class = "NSMenuItem"; title = "Updates"; ObjectID = "tv9-wZ-XWN"; */
"tv9-wZ-XWN.title" = "Actualizaciones";
/* Class = "NSMenuItem"; title = "Show Log"; ObjectID = "vPs-dO-LDa"; */
"vPs-dO-LDa.title" = "Mostrar registro";
/* Class = "NSMenuItem"; title = "Forward"; ObjectID = "z4Z-vu-XGX"; */
"z4Z-vu-XGX.title" = "Siguiente";
@@ -2,4 +2,4 @@
"CFBundleName" = "Managed Software Center";
"CFBundleDisplayName" = "Managed Software Center";
NSHumanReadableCopyright = "Copyright © 2010-2014 The Munki Project\nhttps://github.com/munki/munki";
NSHumanReadableCopyright = "Copyright © 2010-2016 The Munki Project\nhttps://github.com/munki/munki";
@@ -146,5 +146,8 @@
/* Class = "NSMenuItem"; title = "Updates"; ObjectID = "tv9-wZ-XWN"; */
"tv9-wZ-XWN.title" = "Päivitykset";
/* Class = "NSMenuItem"; title = "Show Log"; ObjectID = "vPs-dO-LDa"; */
"vPs-dO-LDa.title" = "Näytä loki";
/* Class = "NSMenuItem"; title = "Forward"; ObjectID = "z4Z-vu-XGX"; */
"z4Z-vu-XGX.title" = "Eteenpäin";
@@ -2,4 +2,4 @@
"CFBundleName" = "Centre de gestion des logiciels";
"CFBundleDisplayName" = "Centre de gestion des logiciels";
NSHumanReadableCopyright = "Copyright © 2010-2014 The Munki Project\nhttps://github.com/munki/munki";
NSHumanReadableCopyright = "Copyright © 2010-2016 The Munki Project\nhttps://github.com/munki/munki";
@@ -146,5 +146,8 @@
/* Class = "NSMenuItem"; title = "Updates"; ObjectID = "tv9-wZ-XWN"; */
"tv9-wZ-XWN.title" = "Mises à jour";
/* Class = "NSMenuItem"; title = "Show Log"; ObjectID = "vPs-dO-LDa"; */
"vPs-dO-LDa.title" = "Afficher lhistorique";
/* Class = "NSMenuItem"; title = "Forward"; ObjectID = "z4Z-vu-XGX"; */
"z4Z-vu-XGX.title" = "Suivant";
@@ -2,4 +2,4 @@
"CFBundleName" = "Centro Gestione Applicazioni";
"CFBundleDisplayName" = "Centro Gestione Applicazioni";
NSHumanReadableCopyright = "Copyright © 2010-2014 The Munki Project\nhttps://github.com/munki/munki";
NSHumanReadableCopyright = "Copyright © 2010-2016 The Munki Project\nhttps://github.com/munki/munki";
@@ -146,5 +146,8 @@
/* Class = "NSMenuItem"; title = "Updates"; ObjectID = "tv9-wZ-XWN"; */
"tv9-wZ-XWN.title" = "Aggiornamenti";
/* Class = "NSMenuItem"; title = "Show Log"; ObjectID = "vPs-dO-LDa"; */
"vPs-dO-LDa.title" = "Mostra log";
/* Class = "NSMenuItem"; title = "Forward"; ObjectID = "z4Z-vu-XGX"; */
"z4Z-vu-XGX.title" = "Avanti";
@@ -134,5 +134,9 @@
/* Class = "NSMenuItem"; title = "Updates"; ObjectID = "tv9-wZ-XWN"; */
"tv9-wZ-XWN.title" = "アップデート";
/* Class = "NSMenuItem"; title = "Show Log"; ObjectID = "vPs-dO-LDa"; */
"vPs-dO-LDa.title" = "ログを表示";
/* Class = "NSMenuItem"; title = "Forward"; ObjectID = "z4Z-vu-XGX"; */
"z4Z-vu-XGX.title" = "次へ";
@@ -2,7 +2,7 @@
// main.m
// Managed Software Center
//
// Copyright 2013-2014 Greg Neagle.
// Copyright 2013-2016 Greg Neagle.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@ from PyObjCTools import AppHelper
import MSCAppDelegate
import MSCMainWindowController
import MSCStatusController
import MSCLogWindowController
import MSCToolbar
# get more debugging info on exceptions
@@ -3,7 +3,7 @@
# msclib.py
#
# Created by Greg Neagle on 12/10/13.
# Copyright 2010-2014 Greg Neagle.
# Copyright 2010-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -6,7 +6,7 @@
# Created by Greg Neagle on 2/23/14.
# Original by John Randolph <jrand@google.com>
#
# Copyright 2009-2014 Greg Neagle.
# Copyright 2009-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -4,7 +4,7 @@
# Managed Software Center
#
# Created by Greg Neagle on 2/11/10.
# Copyright 2010-2014 Greg Neagle.
# Copyright 2010-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -2,4 +2,4 @@
"CFBundleName" = "Managed Software Center";
"CFBundleDisplayName" = "Managed Software Center";
NSHumanReadableCopyright = "Copyright © 2010-2014 The Munki Project\nhttps://github.com/munki/munki";
NSHumanReadableCopyright = "Copyright © 2010-2016 The Munki Project\nhttps://github.com/munki/munki";
@@ -146,5 +146,8 @@
/* Class = "NSMenuItem"; title = "Updates"; ObjectID = "tv9-wZ-XWN"; */
"tv9-wZ-XWN.title" = "Oppdateringer";
/* Class = "NSMenuItem"; title = "Show Log"; ObjectID = "vPs-dO-LDa"; */
"vPs-dO-LDa.title" = "Vis logg";
/* Class = "NSMenuItem"; title = "Forward"; ObjectID = "z4Z-vu-XGX"; */
"z4Z-vu-XGX.title" = "Fram";
@@ -2,4 +2,4 @@
"CFBundleName" = "Managed Software Center";
"CFBundleDisplayName" = "Managed Software Center";
NSHumanReadableCopyright = "Copyright © 2010-2014 The Munki Project\nhttps://github.com/munki/munki";
NSHumanReadableCopyright = "Copyright © 2010-2016 The Munki Project\nhttps://github.com/munki/munki";
@@ -146,5 +146,8 @@
/* Class = "NSMenuItem"; title = "Updates"; ObjectID = "tv9-wZ-XWN"; */
"tv9-wZ-XWN.title" = "Updates";
/* Class = "NSMenuItem"; title = "Show Log"; ObjectID = "vPs-dO-LDa"; */
"vPs-dO-LDa.title" = "Toon logbestand";
/* Class = "NSMenuItem"; title = "Forward"; ObjectID = "z4Z-vu-XGX"; */
"z4Z-vu-XGX.title" = "Volgende";
@@ -2,4 +2,4 @@
"CFBundleName" = "Центр Управления ПО";
"CFBundleDisplayName" = "Центр Управления ПО";
NSHumanReadableCopyright = "Copyright © 2010-2014 The Munki Project\nhttps://github.com/munki/munki";
NSHumanReadableCopyright = "Copyright © 2010-2016 The Munki Project\nhttps://github.com/munki/munki";
@@ -146,5 +146,8 @@
/* Class = "NSMenuItem"; title = "Updates"; ObjectID = "tv9-wZ-XWN"; */
"tv9-wZ-XWN.title" = "Обновления";
/* Class = "NSMenuItem"; title = "Show Log"; ObjectID = "vPs-dO-LDa"; */
"vPs-dO-LDa.title" = "Показать журнал";
/* Class = "NSMenuItem"; title = "Forward"; ObjectID = "z4Z-vu-XGX"; */
"z4Z-vu-XGX.title" = "Вперед";
@@ -2,4 +2,4 @@
"CFBundleName" = "Managed Software Center";
"CFBundleDisplayName" = "Managed Software Center";
NSHumanReadableCopyright = "Copyright © 2010-2014 The Munki Project\nhttps://github.com/munki/munki";
NSHumanReadableCopyright = "Copyright © 2010-2016 The Munki Project\nhttps://github.com/munki/munki";
@@ -146,5 +146,8 @@
/* Class = "NSMenuItem"; title = "Updates"; ObjectID = "tv9-wZ-XWN"; */
"tv9-wZ-XWN.title" = "Uppdateringar";
/* Class = "NSMenuItem"; title = "Show Log"; ObjectID = "vPs-dO-LDa"; */
"vPs-dO-LDa.title" = "Visa logg";
/* Class = "NSMenuItem"; title = "Forward"; ObjectID = "z4Z-vu-XGX"; */
"z4Z-vu-XGX.title" = "Framåt";
@@ -7,12 +7,12 @@
</a>
<ul class="list info">
<li><h2><a title="${display_name_escaped}" href="${detail_link}">${display_name_escaped}</a></h2></li>
<li>${developer_escaped}</li>
<li class="install-info-cell">${developer_escaped}</li>
</ul>
</td>
<td>${version_to_install}</td>
<td>${size}</td>
<td class="status">
<td class="install-info-cell">${version_to_install}</td>
<td class="install-info-cell">${size}</td>
<td class="status install-info-cell">
<span class="${status}" id="${name_escaped}_status_text">
${status_text}
<a class="follow" href="updates.html">
Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

@@ -10,7 +10,6 @@
C002ECE51913F6D6003DD155 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C002ECE21913F6D6003DD155 /* Localizable.strings */; };
C00A4C57185FCEC9004EB3B7 /* FoundationPlist.py in Resources */ = {isa = PBXBuildFile; fileRef = C00A4C56185FCEC9004EB3B7 /* FoundationPlist.py */; };
C035275018A9C5BC004A5AE4 /* libpython2.6.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = C035274F18A9C5BC004A5AE4 /* libpython2.6.dylib */; };
C046261B1A00015600AF1E48 /* MunkiStatus-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = C09004FB16CDD84E00BE34CE /* MunkiStatus-Info.plist */; };
C046261E1A00019800AF1E48 /* MainMenu.strings in Resources */ = {isa = PBXBuildFile; fileRef = C002ECE01913F6D6003DD155 /* MainMenu.strings */; };
C05C3CEF188391F200095E65 /* munki.py in Resources */ = {isa = PBXBuildFile; fileRef = C05C3CEE188391F200095E65 /* munki.py */; };
C09004F216CDD84E00BE34CE /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C09004F116CDD84E00BE34CE /* Cocoa.framework */; };
@@ -23,6 +22,7 @@
C094B6D61891826700E06897 /* BorderlessWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = C094B6D31891826700E06897 /* BorderlessWindow.m */; };
C094B6D71891826700E06897 /* ScaledImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = C094B6D51891826700E06897 /* ScaledImageView.m */; };
C0AE8658186D2DF900C87AE7 /* MunkiStatus.icns in Resources */ = {isa = PBXBuildFile; fileRef = C0AE8657186D2DF900C87AE7 /* MunkiStatus.icns */; };
C0D67B581CC55BFD009E8C2F /* MSULogWindowController.py in Resources */ = {isa = PBXBuildFile; fileRef = C0D67B571CC55BFD009E8C2F /* MSULogWindowController.py */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
@@ -57,6 +57,7 @@
C046261C1A00016200AF1E48 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = "<group>"; };
C046261D1A00019800AF1E48 /* it */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = it; path = it.lproj/MainMenu.xib; sourceTree = "<group>"; };
C05C3CEE188391F200095E65 /* munki.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; name = munki.py; path = MunkiStatus/munki.py; sourceTree = SOURCE_ROOT; };
C06137A51C9CB3BD00EC298E /* BorderlessWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BorderlessWindow.h; sourceTree = "<group>"; };
C07E956C1913ECEF00B40B9A /* fr */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = fr; path = fr.lproj/MainMenu.xib; sourceTree = "<group>"; };
C07E956D1913ECEF00B40B9A /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/InfoPlist.strings; sourceTree = "<group>"; };
C07E956E1913ECF400B40B9A /* de */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = de; path = de.lproj/MainMenu.xib; sourceTree = "<group>"; };
@@ -91,6 +92,7 @@
C094B6D41891826700E06897 /* ScaledImageView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ScaledImageView.h; sourceTree = "<group>"; };
C094B6D51891826700E06897 /* ScaledImageView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ScaledImageView.m; sourceTree = "<group>"; };
C0AE8657186D2DF900C87AE7 /* MunkiStatus.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = MunkiStatus.icns; sourceTree = "<group>"; };
C0D67B571CC55BFD009E8C2F /* MSULogWindowController.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = MSULogWindowController.py; sourceTree = "<group>"; };
E65810B31993C96E00E53A48 /* en_CA */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en_CA; path = en_CA.lproj/Localizable.strings; sourceTree = "<group>"; };
E65810B51993C97500E53A48 /* en_CA */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en_CA; path = en_CA.lproj/MainMenu.strings; sourceTree = "<group>"; };
E65810B61993C97C00E53A48 /* en_CA */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en_CA; path = en_CA.lproj/InfoPlist.strings; sourceTree = "<group>"; };
@@ -157,6 +159,7 @@
C09004F916CDD84E00BE34CE /* MunkiStatus */ = {
isa = PBXGroup;
children = (
C06137A51C9CB3BD00EC298E /* BorderlessWindow.h */,
C094B6D31891826700E06897 /* BorderlessWindow.m */,
C094B6D41891826700E06897 /* ScaledImageView.h */,
C094B6D51891826700E06897 /* ScaledImageView.m */,
@@ -169,6 +172,7 @@
C0AE8657186D2DF900C87AE7 /* MunkiStatus.icns */,
C090050916CDD84E00BE34CE /* MainMenu.xib */,
C09004FA16CDD84E00BE34CE /* Supporting Files */,
C0D67B571CC55BFD009E8C2F /* MSULogWindowController.py */,
);
path = MunkiStatus;
sourceTree = "<group>";
@@ -288,7 +292,6 @@
buildActionMask = 2147483647;
files = (
C09004FE16CDD84E00BE34CE /* InfoPlist.strings in Resources */,
C046261B1A00015600AF1E48 /* MunkiStatus-Info.plist in Resources */,
C090050616CDD84E00BE34CE /* main.py in Resources */,
C090050816CDD84E00BE34CE /* MSUAppDelegate.py in Resources */,
C094B6D1188F7CE100E06897 /* MSUStatusWindowController.py in Resources */,
@@ -298,6 +301,7 @@
C090050B16CDD84E00BE34CE /* MainMenu.xib in Resources */,
C00A4C57185FCEC9004EB3B7 /* FoundationPlist.py in Resources */,
C0AE8658186D2DF900C87AE7 /* MunkiStatus.icns in Resources */,
C0D67B581CC55BFD009E8C2F /* MSULogWindowController.py in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -2,7 +2,7 @@
// BorderlessWindow.h
//
// Created by Greg Neagle on 5/16/09.
// Copyright 2009-2014 Greg Neagle.
// Copyright 2009-2016 Greg Neagle.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -2,7 +2,7 @@
// BorderlessWindow.m
//
// Created by Greg Neagle on 5/16/09.
// Copyright 2009-2014 Greg Neagle.
// Copyright 2009-2016 Greg Neagle.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -1,7 +1,7 @@
#!/usr/bin/python
# encoding: utf-8
#
# Copyright 2009-2014 Greg Neagle.
# Copyright 2009-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -3,7 +3,7 @@
# MSUAppDelegate.py
# MunkiStatus
#
# Copyright 2013-2014 Greg Neagle.
# Copyright 2013-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -46,6 +46,7 @@ class MSUAppDelegate(NSObject):
# pylint: disable=unused-argument
statusWindowController = IBOutlet()
logWindowController = IBOutlet()
def applicationWillFinishLaunching_(self, sender):
'''NSApplicationDelegate method
@@ -0,0 +1,229 @@
# -*- coding: utf-8 -*-
#
# MSULogWindowController.py
# MunkiStatus
#
# Created by Greg Neagle on 4/18/16.
# Copyright (c) 2016 Munki Project. All rights reserved.
#
# Much code borrowed from https://github.com/MagerValp/LoginLog
# with the blessing of MagerValp
#
from objc import YES, NO, IBAction, IBOutlet
# pylint: disable=wildcard-import
# pylint: disable=unused-wildcard-import
# pylint: disable=redefined-builtin
from Foundation import *
from AppKit import *
# pylint: enable=redefined-builtin
# pylint: enable=wildcard-import
import munki
import os
# lots of camelCase names, following Cocoa convention
# pylint: disable=invalid-name
class MSULogViewDataSource(NSObject):
"""Data source for an NSTableView that displays an array of text lines.
Line breaks are assumed to be LF, and partial lines from incremental
reading is handled."""
# since this subclasses NSObject,
# it doesn't have a Python __init__method
# pylint: disable=no-init
logFileData = NSMutableArray.alloc().init()
filteredData = logFileData
lastLineIsPartial = False
filterText = ''
def tableView_writeRowsWithIndexes_toPasteboard_(
self, aTableView, rowIndexes, pasteboard):
'''Implements drag-n-drop of text rows to external apps'''
text_to_copy = ''
index_set = aTableView.selectedRowIndexes()
index = index_set.firstIndex()
while index != NSNotFound:
line = self.filteredData.objectAtIndex_(index)
text_to_copy += line + '\n'
index = index_set.indexGreaterThanIndex_(index)
#changeCount = pasteboard.clearContents()
result = pasteboard.writeObjects_([text_to_copy])
return YES
def applyFilterToData(self):
'''Filter our log data'''
if len(self.filterText):
filterPredicate = NSPredicate.predicateWithFormat_(
'self CONTAINS[cd] %@', self.filterText)
self.filteredData = (
self.logFileData.filteredArrayUsingPredicate_(filterPredicate))
else:
self.filteredData = self.logFileData
def addLine_partial_(self, line, isPartial):
'''Add a line to our datasource'''
if self.lastLineIsPartial:
joinedLine = self.logFileData.lastObject() + line
self.logFileData.removeLastObject()
self.logFileData.addObject_(joinedLine)
else:
self.logFileData.addObject_(line)
self.lastLineIsPartial = isPartial
self.applyFilterToData()
def removeAllLines(self):
'''Remove all data from our datasource'''
self.logFileData.removeAllObjects()
def lineCount(self):
'''Return the number of lines in our filtered data'''
return self.filteredData.count()
def numberOfRowsInTableView_(self, tableView):
'''Required datasource method'''
return self.lineCount()
def tableView_objectValueForTableColumn_row_(self, tableView, column, row):
'''Required datasource method -- returns the text data for the
given row and column'''
if column.identifier() == 'data':
return self.filteredData.objectAtIndex_(row)
else:
return ''
class MSULogWindowController(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 = MSULogViewDataSource.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()
def getWindowLevel(self):
'''Gets our NSWindowLevel. Works around issues with the loginwindow
PolicyBanner in 10.11+ Some code based on earlier work by Pepijn
Bruienne'''
window_level = NSScreenSaverWindowLevel - 1
# Get our Darwin major version
darwin_vers = int(os.uname()[2].split('.')[0])
have_policy_banner = False
for test_file in ['/Library/Security/PolicyBanner.txt',
'/Library/Security/PolicyBanner.rtf',
'/Library/Security/PolicyBanner.rtfd']:
if os.path.exists(test_file):
have_policy_banner = True
break
# bump our NSWindowLevel if we have a PolicyBanner in ElCap+
if have_policy_banner and darwin_vers > 14:
window_level = NSScreenSaverWindowLevel
return window_level
@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
consoleuser = munki.getconsoleuser()
if consoleuser == None or consoleuser == u"loginwindow":
self.window.setCanBecomeVisibleWithoutLogin_(True)
self.window.setLevel_(self.getWindowLevel())
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()
@@ -3,7 +3,7 @@
# MSUStatusWindowController.py
# MunkiStatus
#
# Copyright 2009-2014 Greg Neagle.
# Copyright 2009-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@ from objc import YES, NO, IBAction, IBOutlet, nil
from PyObjCTools import AppHelper
import os
import munki
import FoundationPlist
@@ -69,6 +70,7 @@ class MSUStatusWindowController(NSObject):
# pylint: disable=no-init
window = IBOutlet()
logWindow = IBOutlet()
messageFld = IBOutlet()
detailFld = IBOutlet()
progressIndicator = IBOutlet()
@@ -86,6 +88,8 @@ class MSUStatusWindowController(NSObject):
timeout_counter = 0
saw_process = False
managedsoftwareupdate_pid = None
window_level = NSScreenSaverWindowLevel - 1
@IBAction
def stopBtnClicked_(self, sender):
@@ -146,43 +150,63 @@ class MSUStatusWindowController(NSObject):
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)
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.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
# pylint: disable=line-too-long
self.timer = NSTimer.scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(
5.0, self, self.checkProcess, None, YES)
# pylint: enable=line-too-long
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
@@ -194,6 +218,13 @@ class MSUStatusWindowController(NSObject):
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
@@ -231,39 +262,79 @@ class MSUStatusWindowController(NSObject):
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'''
if self.backdropWindow:
self.backdropWindow.setCanBecomeVisibleWithoutLogin_(True)
self.backdropWindow.setLevel_(NSStatusWindowLevel)
screenRect = NSScreen.mainScreen().frame()
self.backdropWindow.setFrame_display_(screenRect, True)
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 a transparent/translucent window to prevent interaction
# with the login UI
self.backdropImageFld.setHidden_(True)
translucentColor = NSColor.blackColor(
).colorWithAlphaComponent_(0.35)
self.backdropWindow.setBackgroundColor_(translucentColor)
self.backdropWindow.setOpaque_(False)
self.backdropWindow.setIgnoresMouseEvents_(False)
self.backdropWindow.setAlphaValue_(0.0)
darwin_vers = int(os.uname()[2].split('.')[0])
if darwin_vers < 11:
if self.backdropImageFld:
bgImage = getLoginwindowPicture()
self.backdropImageFld.setImage_(bgImage)
self.backdropWindow.orderFrontRegardless()
self.backdropWindow.animator().setAlphaValue_(1.0)
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
@@ -288,6 +359,7 @@ class MSUStatusWindowController(NSObject):
command = info.get('command')
if command == 'activate':
NSApp.activateIgnoringOtherApps_(YES)
self.window.orderFrontRegardless()
elif command == 'showRestartAlert':
# clean up timer
@@ -29,7 +29,7 @@
<key>LSMinimumSystemVersion</key>
<string>${MACOSX_DEPLOYMENT_TARGET}</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2014 The Munki Project https://github.com/munki/munki</string>
<string>Copyright © 2016 The Munki Project https://github.com/munki/munki</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
@@ -3,7 +3,7 @@
//
// Created by Greg Neagle on 5/27/09.
//
// Copyright 2009-2014 Greg Neagle.
// Copyright 2009-2016 Greg Neagle.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -3,7 +3,7 @@
//
// Created by Greg Neagle on 5/27/09.
//
// Copyright 2009-2014 Greg Neagle.
// Copyright 2009-2016 Greg Neagle.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -64,6 +64,9 @@
/* Class = "NSMenuItem"; title = "Zoom"; ObjectID = "239"; */
"239.title" = "Zoom";
/* Class = "NSMenuItem"; title = "Show Log"; ObjectID = "1B8-Pq-rf7"; */
"1B8-Pq-rf7.title" = "Vis log";
/* Class = "NSMenu"; title = "Window"; ObjectID = "24"; */
"24.title" = "Vindue";
@@ -64,6 +64,9 @@
/* Class = "NSMenuItem"; title = "Zoom"; ObjectID = "239"; */
"239.title" = "Zoomen";
/* Class = "NSMenuItem"; title = "Show Log"; ObjectID = "1B8-Pq-rf7"; */
"1B8-Pq-rf7.title" = "Protokoll einblenden";
/* Class = "NSMenu"; title = "Window"; ObjectID = "24"; */
"24.title" = "Fenster";
@@ -2,4 +2,4 @@
"CFBundleName" = "MunkiStatus";
"CFBundleDisplayName" = "MunkiStatus";
NSHumanReadableCopyright = "Copyright © 2010-2014 The Munki Project\nhttps://github.com/munki/munki";
NSHumanReadableCopyright = "Copyright © 2010-2016 The Munki Project\nhttps://github.com/munki/munki";
@@ -1,13 +1,13 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="5056" systemVersion="13F34" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10116" systemVersion="15E65" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none">
<dependencies>
<deployment version="1060" defaultVersion="1060" identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="5056"/>
<deployment version="1060" identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="10116"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication"/>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application">
<customObject id="-3" userLabel="Application" customClass="NSObject">
<connections>
<outlet property="delegate" destination="373" id="374"/>
</connections>
@@ -122,6 +122,12 @@
<action selector="performZoom:" target="-1" id="240"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="Wf0-EL-aNa"/>
<menuItem title="Show Log" keyEquivalent="l" id="1B8-Pq-rf7">
<connections>
<action selector="showLogWindow:" target="2ep-ou-xmD" id="91u-X0-9md"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
@@ -140,6 +146,7 @@
</menu>
<customObject id="373" userLabel="MSUAppDelegate" customClass="MSUAppDelegate">
<connections>
<outlet property="logWindowController" destination="2ep-ou-xmD" id="3US-AY-I9H"/>
<outlet property="statusWindowController" destination="I9V-I1-u8s" id="GJ8-cn-O6v"/>
</connections>
</customObject>
@@ -149,12 +156,21 @@
<outlet property="backdropWindow" destination="VCy-58-vwp" id="FI2-0s-zHg"/>
<outlet property="detailFld" destination="Xa0-XA-1hm" id="ffw-hJ-LcP"/>
<outlet property="imageFld" destination="aZW-jZ-KiY" id="MKX-b8-T7K"/>
<outlet property="logWindow" destination="LgH-P6-zEw" id="psa-Jx-p9K"/>
<outlet property="messageFld" destination="aUN-kO-8dF" id="nB6-fN-F3i"/>
<outlet property="progressIndicator" destination="yFK-Ya-d24" id="vCk-kB-lLo"/>
<outlet property="stopBtn" destination="rxM-GK-Unu" id="ogB-ah-l40"/>
<outlet property="window" destination="1Nd-E7-vfR" id="nMB-as-gvt"/>
</connections>
</customObject>
<customObject id="2ep-ou-xmD" customClass="MSULogWindowController">
<connections>
<outlet property="logView" destination="YcU-fs-F3z" id="BDP-0f-Yab"/>
<outlet property="pathControl" destination="M6b-hp-fwf" id="UhS-g1-OVj"/>
<outlet property="searchField" destination="43A-RJ-9xo" id="ERQ-GP-P0k"/>
<outlet property="window" destination="LgH-P6-zEw" id="tzw-ed-aBF"/>
</connections>
</customObject>
<customObject id="420" customClass="NSFontManager"/>
<userDefaultsController representsSharedInstance="YES" id="602"/>
<window title="Managed Software Center" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="1Nd-E7-vfR" userLabel="Status Window">
@@ -213,9 +229,9 @@
<connections>
<outlet property="delegate" destination="I9V-I1-u8s" id="1bd-RW-Bhf"/>
</connections>
<point key="canvasLocation" x="-486" y="451.5"/>
</window>
<window title="BackdropWindow" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hasShadow="NO" oneShot="NO" showsToolbarButton="NO" visibleAtLaunch="NO" animationBehavior="default" id="VCy-58-vwp" userLabel="BackdropWindow" customClass="BorderlessWindow">
<windowStyleMask key="styleMask" titled="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="196" y="240" width="480" height="230"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="878"/>
@@ -231,6 +247,96 @@
</subviews>
</view>
</window>
<window title="Log" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" animationBehavior="default" id="LgH-P6-zEw" userLabel="Log Window">
<windowStyleMask key="styleMask" titled="YES" closable="YES" resizable="YES"/>
<rect key="contentRect" x="636" y="390" width="512" height="480"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="877"/>
<value key="minSize" type="size" width="512" height="360"/>
<view key="contentView" id="V0z-aa-fWm">
<rect key="frame" x="0.0" y="0.0" width="512" height="480"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<scrollView autohidesScrollers="YES" horizontalLineScroll="17" horizontalPageScroll="10" verticalLineScroll="17" verticalPageScroll="10" usesPredominantAxisScrolling="NO" id="GSP-c9-k1b">
<rect key="frame" x="-1" y="-1" width="514" height="449"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<clipView key="contentView" id="MTK-yi-vFK">
<rect key="frame" x="1" y="1" width="512" height="447"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView focusRingType="none" verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" alternatingRowBackgroundColors="YES" columnReordering="NO" columnResizing="NO" autosaveColumns="NO" typeSelect="NO" id="YcU-fs-F3z">
<rect key="frame" x="0.0" y="0.0" width="514" height="17"/>
<autoresizingMask key="autoresizingMask"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
<tableColumns>
<tableColumn identifier="spacer" editable="NO" width="10" minWidth="8" maxWidth="3.4028234663852886e+38" id="tsJ-4j-iZA">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" alignment="left" title="Text Cell" placeholderString="" id="nGI-3K-7Wv">
<font key="font" metaFont="fixedUser" size="11"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
</tableColumn>
<tableColumn identifier="data" editable="NO" width="502" minWidth="40" maxWidth="2560" id="UYm-QX-K69">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" white="0.33333298560000002" alpha="1" colorSpace="calibratedWhite"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="charWrapping" selectable="YES" allowsUndo="NO" alignment="left" title="Text Cell" id="8Nl-My-27d">
<font key="font" metaFont="fixedUser" size="11"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
</tableColumn>
</tableColumns>
</tableView>
</subviews>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</clipView>
<scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="YES" id="I4n-9v-poQ">
<rect key="frame" x="1" y="427" width="630" height="16"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="NO" id="Z2r-Od-tTj">
<rect key="frame" x="-100" y="-100" width="15" height="303"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
</scrollView>
<searchField wantsLayer="YES" verticalHuggingPriority="750" id="43A-RJ-9xo">
<rect key="frame" x="301" y="452" width="200" height="22"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
<searchFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" borderStyle="bezel" usesSingleLineMode="YES" bezelStyle="round" id="tIL-Oa-wuU">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</searchFieldCell>
<connections>
<action selector="searchFilterChanged:" target="2ep-ou-xmD" id="bxe-I5-Bq0"/>
</connections>
</searchField>
<pathControl verticalHuggingPriority="750" allowsExpansionToolTips="YES" id="M6b-hp-fwf">
<rect key="frame" x="4" y="452" width="278" height="22"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<pathCell key="cell" selectable="YES" refusesFirstResponder="YES" alignment="left" id="RHv-ZI-67x">
<font key="font" metaFont="system"/>
<url key="url" string="file://localhost/Applications/"/>
<color key="backgroundColor" name="windowBackgroundColor" catalog="System" colorSpace="catalog"/>
</pathCell>
</pathControl>
</subviews>
</view>
<connections>
<outlet property="delegate" destination="2ep-ou-xmD" id="FgY-Kk-GWr"/>
</connections>
<point key="canvasLocation" x="114" y="398"/>
</window>
</objects>
<resources>
<image name="MunkiStatus" width="512" height="512"/>
@@ -64,6 +64,9 @@
/* Class = "NSMenuItem"; title = "Zoom"; ObjectID = "239"; */
"239.title" = "Zoom";
/* Class = "NSMenuItem"; title = "Show Log"; ObjectID = "1B8-Pq-rf7"; */
"1B8-Pq-rf7.title" = "Mostrar registro";
/* Class = "NSMenu"; title = "Window"; ObjectID = "24"; */
"24.title" = "Ventana";
@@ -64,6 +64,9 @@
/* Class = "NSMenuItem"; title = "Zoom"; ObjectID = "239"; */
"239.title" = "Zoomaa";
/* Class = "NSMenuItem"; title = "Show Log"; ObjectID = "1B8-Pq-rf7"; */
"1B8-Pq-rf7.title" = "Näytä loki";
/* Class = "NSMenu"; title = "Window"; ObjectID = "24"; */
"24.title" = "Ikkuna";
@@ -2,4 +2,4 @@
"CFBundleName" = "Centre de gestion des logiciels";
"CFBundleDisplayName" = "Centre de gestion des logiciels";
NSHumanReadableCopyright = "Copyright © 2010-2014 The Munki Project\nhttps://github.com/munki/munki";
NSHumanReadableCopyright = "Copyright © 2010-2016 The Munki Project\nhttps://github.com/munki/munki";
@@ -77,6 +77,9 @@
/* Class = "NSMenuItem"; title = "Zoom"; ObjectID = "239"; */
"239.title" = "Zoom";
/* Class = "NSMenuItem"; title = "Show Log"; ObjectID = "1B8-Pq-rf7"; */
"1B8-Pq-rf7.title" = "Afficher lhistorique";
/* Class = "NSWindow"; title = "Managed Software Center"; ObjectID = "1Nd-E7-vfR"; */
"1Nd-E7-vfR.title" = "Centre de gestion des logiciels";
@@ -2,4 +2,4 @@
"CFBundleName" = "MunkiStatus";
"CFBundleDisplayName" = "MunkiStatus";
NSHumanReadableCopyright = "Copyright © 2010-2014 The Munki Project\nhttps://github.com/munki/munki";
NSHumanReadableCopyright = "Copyright © 2010-2016 The Munki Project\nhttps://github.com/munki/munki";
@@ -77,6 +77,9 @@
/* Class = "NSMenuItem"; title = "Zoom"; ObjectID = "239"; */
"239.title" = "Zoom";
/* Class = "NSMenuItem"; title = "Show Log"; ObjectID = "1B8-Pq-rf7"; */
"1B8-Pq-rf7.title" = "Mostra log";
/* Class = "NSWindow"; title = "Managed Software Center"; ObjectID = "1Nd-E7-vfR"; */
"1Nd-E7-vfR.title" = "Centro Gestione Applicazioni";
@@ -71,6 +71,9 @@
/* Class = "NSMenuItem"; title = "Zoom"; ObjectID = "239"; */
"239.title" = "ズーム";
/* Class = "NSMenuItem"; title = "Show Log"; ObjectID = "1B8-Pq-rf7"; */
"1B8-Pq-rf7.title" = "ログを表示";
/* Class = "NSWindow"; title = "Managed Software Center"; ObjectID = "1Nd-E7-vfR"; */
"1Nd-E7-vfR.title" = "Managed Software Center";
+1 -1
View File
@@ -2,7 +2,7 @@
// main.m
// MunkiStatus
//
// Copyright 2013-2014 Greg Neagle.
// Copyright 2013-2016 Greg Neagle.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
+2 -1
View File
@@ -2,7 +2,7 @@
# main.py
# MunkiStatus
#
# Copyright 2013-2014 Greg Neagle.
# Copyright 2013-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@ from PyObjCTools import AppHelper
# import modules containing classes required to start application and load MainMenu.nib
import MSUAppDelegate
import MSUStatusWindowController
import MSULogWindowController
# get more debugging info on exceptions
objc.setVerbose(1)
+21 -1
View File
@@ -4,7 +4,7 @@
# MunkiStatus
#
# Created by Greg Neagle on 2/11/10.
# Copyright 2010-2014 Greg Neagle.
# Copyright 2010-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -23,10 +23,30 @@
import os
import stat
import subprocess
from Foundation import CFPreferencesCopyAppValue
from SystemConfiguration import SCDynamicStoreCopyConsoleUser
INSTALLATLOGOUTFILE = "/private/tmp/com.googlecode.munki.installatlogout"
BUNDLE_ID = u'ManagedInstalls'
def pref(pref_name):
"""Return a preference. Since this uses CFPreferencesCopyAppValue,
Preferences can be defined several places. Precedence is:
- MCX
- ~/Library/Preferences/ManagedInstalls.plist
- /Library/Preferences/ManagedInstalls.plist
- default_prefs defined here.
"""
default_prefs = {
'LogFile': '/Library/Managed Installs/Logs/ManagedSoftwareUpdate.log'
}
pref_value = CFPreferencesCopyAppValue(pref_name, BUNDLE_ID)
if pref_value == None:
pref_value = default_prefs.get(pref_name)
return pref_value
def call(cmd):
'''Convenience function; works around an issue with subprocess.call
in PyObjC in Snow Leopard'''
@@ -64,6 +64,9 @@
/* Class = "NSMenuItem"; title = "Zoom"; ObjectID = "239"; */
"239.title" = "Zoom";
/* Class = "NSMenuItem"; title = "Show Log"; ObjectID = "1B8-Pq-rf7"; */
"1B8-Pq-rf7.title" = "Vis logg";
/* Class = "NSMenu"; title = "Window"; ObjectID = "24"; */
"24.title" = "Vindu";
@@ -64,6 +64,9 @@
/* Class = "NSMenuItem"; title = "Zoom"; ObjectID = "239"; */
"239.title" = "Vergroot/verklein";
/* Class = "NSMenuItem"; title = "Show Log"; ObjectID = "1B8-Pq-rf7"; */
"1B8-Pq-rf7.title" = "Toon logbestand";
/* Class = "NSMenu"; title = "Window"; ObjectID = "24"; */
"24.title" = "Venster";
@@ -64,6 +64,9 @@
/* Class = "NSMenuItem"; title = "Zoom"; ObjectID = "239"; */
"239.title" = "Изменить масштаб";
/* Class = "NSMenuItem"; title = "Show Log"; ObjectID = "1B8-Pq-rf7"; */
"1B8-Pq-rf7.title" = "Показать журнал";
/* Class = "NSMenu"; title = "Window"; ObjectID = "24"; */
"24.title" = "Окно";
@@ -64,6 +64,9 @@
/* Class = "NSMenuItem"; title = "Zoom"; ObjectID = "239"; */
"239.title" = "Zooma";
/* Class = "NSMenuItem"; title = "Show Log"; ObjectID = "1B8-Pq-rf7"; */
"1B8-Pq-rf7.title" = "Visa logg";
/* Class = "NSMenu"; title = "Window"; ObjectID = "24"; */
"24.title" = "Fönster";
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/python
# encoding: utf-8
#
# Copyright 2010-2014 Greg Neagle.
# Copyright 2010-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the 'License');
# you may not use this file except in compliance with the License.
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/python
# encoding: utf-8
#
# Copyright 2010-2014 Greg Neagle.
# Copyright 2010-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
+4 -5
View File
@@ -1,7 +1,7 @@
#!/usr/bin/python
# encoding: utf-8
#
# Copyright 2011-2014 Greg Neagle.
# Copyright 2011-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -100,13 +100,12 @@ def alertUserOfForcedLogout(info=None):
launchfile = '/var/run/com.googlecode.munki.ManagedSoftwareUpdate'
f = open(launchfile, 'w')
f.close()
time.sleep(0.5)
if os.path.exists(launchfile):
os.unlink(launchfile)
# now wait a bit for it to launch before proceeding
# because if we don't, sending the logoutwarn notification
# may fall on deaf ears.
time.sleep(4)
time.sleep(5)
if os.path.exists(launchfile):
os.unlink(launchfile)
# if set, convert Python dictionary to NSDictionary.
if info is not None:
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
# encoding: utf-8
#
# Copyright 2009-2014 Greg Neagle.
# Copyright 2009-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
+8 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/python
# encoding: utf-8
#
# Copyright 2008-2014 Greg Neagle.
# Copyright 2008-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -327,6 +327,13 @@ def getiteminfo(itempath):
infodict[key] = plist[key]
if 'LSMinimumSystemVersion' in plist:
infodict['minosversion'] = plist['LSMinimumSystemVersion']
elif 'LSMinimumSystemVersionByArchitecture' in plist:
# just grab the highest version if more than one is listed
versions = [item[1] for item in
plist['LSMinimumSystemVersionByArchitecture'].items()]
highest_version = str(max([munkicommon.MunkiLooseVersion(version)
for version in versions]))
infodict['minosversion'] = highest_version
elif 'SystemVersionCheck:MinimumSystemVersion' in plist:
infodict['minosversion'] = \
plist['SystemVersionCheck:MinimumSystemVersion']
+20 -15
View File
@@ -1,7 +1,7 @@
#!/usr/bin/python
# encoding: utf-8
#
# Copyright 2009-2014 Greg Neagle.
# Copyright 2009-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the 'License');
# you may not use this file except in compliance with the License.
@@ -202,7 +202,7 @@ def runScript(script, display_name, runtype):
except utils.ScriptNotFoundError:
pass # script is not required, so pass
except utils.RunExternalScriptError, err:
munkicommon.display_warning(str(err))
munkicommon.display_warning(unicode(err))
return result
@@ -435,7 +435,7 @@ def notifyUserOfUpdates(force=False):
launchfile = '/var/run/com.googlecode.munki.ManagedSoftwareUpdate'
f = open(launchfile, 'w')
f.close()
time.sleep(0.5)
time.sleep(5)
if os.path.exists(launchfile):
os.unlink(launchfile)
user_was_notified = True
@@ -634,7 +634,7 @@ def main():
munkicommon.log("### Starting managedsoftwareupdate run: %s ###" % runtype)
if options.verbose:
print 'Managed Software Update Tool'
print 'Copyright 2010-2014 The Munki Project'
print 'Copyright 2010-2016 The Munki Project'
print 'https://github.com/munki/munki\n'
munkicommon.display_status_major('Starting...')
@@ -710,6 +710,7 @@ def main():
if options.manualcheck:
# record our result
recordUpdateCheckResult(-1)
if options.munkistatusoutput:
# connect to socket and quit
munkistatus.activate()
munkistatus.quit()
@@ -738,7 +739,8 @@ def main():
updatecheckresult = None
if not skip_munki_check:
try:
updatecheckresult = updatecheck.check(client_id=options.id)
updatecheckresult = updatecheck.check(
client_id=options.id.decode('UTF-8'))
except:
munkicommon.display_error('Unexpected error in updatecheck:')
munkicommon.log(traceback.format_exc())
@@ -919,16 +921,19 @@ def main():
# so we need to check InstallInfo.plist and
# AppleUpdates.plist again
updatesavailable = munkiUpdatesAvailable()
try:
appleupdatesavailable = \
appleupdates.appleSoftwareUpdatesAvailable(
suppresscheck=True, client_id=options.id)
except:
munkicommon.display_error(
'Unexpected error in appleupdates:')
munkicommon.log(traceback.format_exc())
munkicommon.savereport()
raise
if appleupdatesavailable:
# there were Apple updates available, but we might have
# installed some unattended
try:
appleupdatesavailable = (
appleupdates.appleSoftwareUpdatesAvailable(
suppresscheck=True, client_id=options.id))
except:
munkicommon.display_error(
'Unexpected error in appleupdates:')
munkicommon.log(traceback.format_exc())
munkicommon.savereport()
raise
if appleupdatesavailable or updatesavailable:
# set a flag to notify the user of available updates
# after we conclude this run.
+261 -219
View File
File diff suppressed because it is too large Load Diff
+132 -106
View File
@@ -1,7 +1,7 @@
#!/usr/bin/python
# encoding: utf-8
#
# Copyright 2010-2014 Greg Neagle.
# Copyright 2010-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the 'License');
# you may not use this file except in compliance with the License.
@@ -22,18 +22,54 @@ Created by Greg Neagle on 2010-09-29.
Assists with importing installer items into the munki repo
"""
import sys
import ctypes
import os
#import readline
import readline
import subprocess
import sys
import time
import thread
from ctypes.util import find_library
from optparse import OptionParser, BadOptionError, AmbiguousOptionError
from munkilib import iconutils
from munkilib import munkicommon
from munkilib import FoundationPlist
if 'libedit' in readline.__doc__:
# readline module was compiled against libedit
LIBEDIT = ctypes.cdll.LoadLibrary(find_library('libedit'))
else:
LIBEDIT = None
def raw_input_with_default(prompt, default_text):
'''A nasty, nasty hack to get around Python readline limitations under
OS X. Gives us editable default text for munkiimport choices'''
def insert_default_text(prompt, text):
'''Helper function'''
time.sleep(0.01)
LIBEDIT.rl_set_prompt(prompt)
readline.insert_text(text)
LIBEDIT.rl_forced_update_display()
readline.clear_history()
if not default_text:
return unicode(raw_input(prompt), encoding=sys.stdin.encoding)
elif LIBEDIT:
# readline module was compiled against libedit
thread.start_new_thread(insert_default_text, (prompt, default_text))
return unicode(raw_input(), encoding=sys.stdin.encoding)
else:
readline.set_startup_hook(lambda: readline.insert_text(default_text))
try:
return unicode(raw_input(prompt), encoding=sys.stdin.encoding)
finally:
readline.set_startup_hook()
class PassThroughOptionParser(OptionParser):
"""
An unknown option pass-through implementation of OptionParser.
@@ -56,18 +92,7 @@ class PassThroughOptionParser(OptionParser):
return self.epilog
def raw_input_with_default(prompt='', default=''):
'''Get input from user with a prompt and a suggested default value'''
if default:
prompt = '%s [%s]: ' % (prompt, default)
return raw_input(prompt).decode('UTF-8') or default.decode('UTF-8')
else:
# no default value, just call raw_input
return raw_input(prompt + ": ").decode('UTF-8')
def makeDMG(pkgpath):
def make_dmg(pkgpath):
"""Wraps a non-flat package into a disk image.
Returns path to newly-created disk image."""
@@ -94,7 +119,7 @@ def makeDMG(pkgpath):
return diskimagepath
def repoAvailable():
def repo_available():
"""Checks the repo path for proper directory structure.
If the directories look wrong we probably don't have a
valid repo path. Returns True if things look OK."""
@@ -102,7 +127,7 @@ def repoAvailable():
print >> sys.stderr, 'No repo path specified.'
return False
if not os.path.exists(REPO_PATH):
mountRepoCLI()
mount_repo_cli()
if not os.path.exists(REPO_PATH):
return False
for subdir in ['catalogs', 'manifests', 'pkgs', 'pkgsinfo']:
@@ -113,22 +138,7 @@ def repoAvailable():
return True
def mountRepoGUI():
"""Attempts to connect to the repo fileshare
Returns nothing whether we succeed or fail"""
if not REPO_PATH or not REPO_URL:
return
print 'Attempting to connect to munki repo...'
cmd = ['/usr/bin/open', REPO_URL]
dummy_retcode = subprocess.call(cmd)
for dummy_i in range(60):
# wait up to 60 seconds to connect to repo
if os.path.exists(REPO_PATH):
break
time.sleep(1)
def mountRepoCLI():
def mount_repo_cli():
"""Attempts to connect to the repo fileshare"""
global WE_MOUNTED_THE_REPO
if os.path.exists(REPO_PATH):
@@ -151,7 +161,7 @@ def mountRepoCLI():
WE_MOUNTED_THE_REPO = True
def unmountRepoCLI():
def unmount_repo_cli():
"""Attempts to unmount the repo fileshare"""
if not os.path.exists(REPO_PATH):
return
@@ -164,7 +174,7 @@ class RepoCopyError(Exception):
pass
def copyItemToRepo(itempath, vers, subdirectory=''):
def copy_item_to_repo(itempath, vers, subdirectory=''):
"""Copies an item to the appropriate place in the repo.
If itempath is a path within the repo/pkgs directory, copies nothing.
Renames the item if an item already exists with that name.
@@ -217,7 +227,7 @@ def copyItemToRepo(itempath, vers, subdirectory=''):
return os.path.join(subdirectory, item_name)
def getIconPath(pkginfo):
def get_icon_path(pkginfo):
"""Return path for icon"""
icon_name = pkginfo.get('icon_name') or pkginfo['name']
if not os.path.splitext(icon_name)[1]:
@@ -225,17 +235,17 @@ def getIconPath(pkginfo):
return os.path.join(REPO_PATH, u'icons', icon_name)
def iconExistsInRepo(pkginfo):
def icon_exists_in_repo(pkginfo):
"""Returns True if there is an icon for this item in the repo"""
icon_path = getIconPath(pkginfo)
icon_path = get_icon_path(pkginfo)
if os.path.exists(icon_path):
return True
return False
def addIconHashToPkginfo(pkginfo):
def add_icon_hash_to_pkginfo(pkginfo):
"""Adds the icon hash tp pkginfo if the icon exists in repo"""
icon_path = getIconPath(pkginfo)
icon_path = get_icon_path(pkginfo)
if os.path.isfile(icon_path):
pkginfo['icon_hash'] = munkicommon.getsha256hash(icon_path)
@@ -302,6 +312,7 @@ def generate_pngs_from_installer_pkg(item_path, pkginfo):
def convert_and_install_icon(pkginfo, icon_path, index=None):
'''Convert icon file to png and save to repo icon path'''
destination_path = os.path.join(REPO_PATH, 'icons')
if not os.path.exists(destination_path):
try:
@@ -324,7 +335,7 @@ def convert_and_install_icon(pkginfo, icon_path, index=None):
print >> sys.stderr, u'Error converting %s to png.' % icon_path
def copyIconToRepo(iconpath):
def copy_icon_to_repo(iconpath):
"""Saves a product icon to the repo"""
destination_path = os.path.join(REPO_PATH, 'icons')
if not os.path.exists(destination_path):
@@ -351,7 +362,7 @@ def copyIconToRepo(iconpath):
% (iconpath, destination_path_name))
def copyPkginfoToRepo(pkginfo, subdirectory=''):
def copy_pkginfo_to_repo(pkginfo, subdirectory=''):
"""Saves pkginfo to munki_repo_path/pkgsinfo/subdirectory"""
# less error checking because we copy the installer_item
# first and bail if it fails...
@@ -383,7 +394,7 @@ def copyPkginfoToRepo(pkginfo, subdirectory=''):
return pkginfo_path
def openPkginfoInEditor(pkginfo_path):
def open_pkginfo_in_editor(pkginfo_path):
"""Opens pkginfo list in the user's chosen editor."""
editor = pref('editor')
if editor:
@@ -398,13 +409,13 @@ def openPkginfoInEditor(pkginfo_path):
'Problem running editor %s: %s.' % (editor, err))
def promptForSubdirectory(subdirectory):
def prompt_for_subdirectory(subdirectory):
"""Prompts the user for a subdirectory for the pkg and pkginfo"""
while True:
newdir = raw_input(
'Upload item to subdirectory path [%s]: ' % subdirectory)
if newdir:
if not repoAvailable():
if not repo_available():
raise RepoCopyError('Could not connect to munki repo.')
if APPLEMETADATA:
destination_path = os.path.join(REPO_PATH, 'pkgsinfo', newdir)
@@ -427,7 +438,7 @@ class CatalogDBException(Exception):
pass
def makeCatalogDB():
def make_catalog_db():
"""Returns a dict we can use like a database"""
all_items_path = os.path.join(REPO_PATH, 'catalogs', 'all')
@@ -523,7 +534,7 @@ def makeCatalogDB():
return pkgdb
def findMatchingPkginfo(pkginfo):
def find_matching_pkginfo(pkginfo):
"""Looks through repo catalogs looking for matching pkginfo
Returns a pkginfo dictionary, or an empty dict"""
@@ -533,7 +544,7 @@ def findMatchingPkginfo(pkginfo):
munkicommon.MunkiLooseVersion(value_a))
try:
catdb = makeCatalogDB()
catdb = make_catalog_db()
except CatalogDBException:
return {}
@@ -600,7 +611,7 @@ def findMatchingPkginfo(pkginfo):
return {}
def makePkgInfo(options=None, test_mode=False):
def make_pkginfo(options=None, test_mode=False):
"""Calls makepkginfo to generate the pkginfo for item_path."""
# first look for a makepkginfo in the same dir as us
mydir = os.path.dirname(os.path.abspath(__file__))
@@ -645,7 +656,7 @@ def makePkgInfo(options=None, test_mode=False):
return FoundationPlist.readPlistFromString(stdout)
def makeCatalogs():
def make_catalogs():
"""Calls makecatalogs to rebuild our catalogs"""
# first look for a makecatalogs in the same dir as us
mydir = os.path.dirname(os.path.abspath(__file__))
@@ -653,7 +664,7 @@ def makeCatalogs():
if not os.path.exists(makecatalogs_path):
# didn't find it; assume the default install path
makecatalogs_path = '/usr/local/munki/makecatalogs'
if not repoAvailable():
if not repo_available():
raise RepoCopyError('Could not connect to munki repo.')
if not VERBOSE:
print 'Rebuilding catalogs at %s...' % REPO_PATH
@@ -673,16 +684,16 @@ def makeCatalogs():
print errors
def cleanupAndExit(exitcode):
def cleanup_and_exit(exitcode):
"""Unmounts the repo if we mounted it, then exits"""
result = 0
if WE_MOUNTED_THE_REPO:
if not NOINTERACTIVE:
answer = raw_input('Unmount the repo fileshare? [y/n] ')
if answer.lower().startswith('y'):
result = unmountRepoCLI()
result = unmount_repo_cli()
else:
result = unmountRepoCLI()
result = unmount_repo_cli()
# clean up tmpdir
munkicommon.cleanUpTmpDir()
@@ -710,10 +721,11 @@ def configure():
'Repo fileshare URL (example: afp://munki.example.com/repo)'),
('pkginfo_extension', 'pkginfo extension (Example: .plist)'),
('editor',
'pkginfo editor (examples: /usr/bin/vi or TextMate.app)'),
'pkginfo editor (examples: /usr/bin/vi or TextMate.app; '
'leave empty to not open an editor after import)'),
('default_catalog', 'Default catalog to use (example: testing)')]:
_prefs[key] = raw_input_with_default('%15s' % prompt, pref(key))
_prefs[key] = raw_input_with_default('%15s: ' % prompt, pref(key))
try:
FoundationPlist.writePlist(_prefs, PREFSPATH)
@@ -826,20 +838,20 @@ def main():
# Verify that arguments, presumed to be for
# 'makepkginfo' are valid and return installer_item
return_dict = makePkgInfo(
return_dict = make_pkginfo(
options=arguments, test_mode=True)
try:
return_dict = FoundationPlist.readPlistFromString(return_dict)
except FoundationPlist.FoundationPlistException, err:
print >> sys.stderr, (
'Error getting info from makepkginfo: %s' % err)
cleanupAndExit(-1)
cleanup_and_exit(-1)
installer_item = return_dict.get('installeritem')
uninstaller_item = return_dict.get('uninstalleritem')
APPLEMETADATA = return_dict.get('installer_type') == 'apple_update_metadata'
if not installer_item and not APPLEMETADATA:
cleanupAndExit(-1)
cleanup_and_exit(-1)
if not APPLEMETADATA:
# Remove the installer_item from arguments
@@ -870,7 +882,7 @@ def main():
'tool, or provide with --repo-path')
exit(-1)
if not repoAvailable():
if not repo_available():
print >> sys.stderr, ('Could not connect to munki repo. Check the '
'configuration and try again.')
exit(-1)
@@ -880,17 +892,17 @@ def main():
if munkicommon.hasValidDiskImageExt(installer_item):
# a directory named foo.dmg or foo.iso!
print >> sys.stderr, '%s is an unknown type.' % installer_item
cleanupAndExit(-1)
cleanup_and_exit(-1)
else:
# we need to convert to dmg
dmg_path = makeDMG(installer_item)
dmg_path = make_dmg(installer_item)
if dmg_path:
installer_item = dmg_path
else:
print >> sys.stderr, (
'Could not convert %s to a disk image.'
% installer_item)
cleanupAndExit(-1)
cleanup_and_exit(-1)
# append the installer_item to arguments which
# may have changed if bundle was wrapped into dmg
@@ -902,31 +914,31 @@ def main():
# a directory named foo.dmg or foo.iso!
print >> sys.stderr, (
'%s is an unknown type.' % uninstaller_item)
cleanupAndExit(-1)
cleanup_and_exit(-1)
else:
# we need to convert to dmg
dmg_path = makeDMG(uninstaller_item)
dmg_path = make_dmg(uninstaller_item)
if dmg_path:
uninstaller_item = dmg_path
else:
print >> sys.stderr, (
'Could not convert %s to a disk image.'
% uninstaller_item)
cleanupAndExit(-1)
cleanup_and_exit(-1)
# if catalog/catalogs have not been explictly specified via command-line,
# append our default catalog
if not '--catalog' in arguments and not '-c' in arguments:
default_catalog = pref('default_catalog') or 'testing'
arguments.extend(['--catalog', default_catalog])
pkginfo = makePkgInfo(arguments)
pkginfo = make_pkginfo(arguments)
if not pkginfo:
# makepkginfo returned an error
print >> sys.stderr, 'Getting package info failed.'
cleanupAndExit(-1)
cleanup_and_exit(-1)
if not options.nointeractive:
# try to find existing pkginfo items that match this one
matchingpkginfo = findMatchingPkginfo(pkginfo)
matchingpkginfo = find_matching_pkginfo(pkginfo)
exactmatch = False
if matchingpkginfo:
if ('installer_item_hash' in matchingpkginfo and
@@ -944,12 +956,13 @@ def main():
('Installer item path', 'installer_item_location'))
for (name, key) in fields:
print '%21s: %s' % (
name, matchingpkginfo.get(key, '').encode('UTF-8'))
name, matchingpkginfo.get(key, '').encode(
'UTF-8'))
print
if exactmatch:
answer = raw_input('Import this item anyway? [y/n] ')
if not answer.lower().startswith('y'):
cleanupAndExit(0)
cleanup_and_exit(0)
answer = raw_input('Use existing item as a template? [y/n] ')
if answer.lower().startswith('y'):
@@ -978,20 +991,31 @@ def main():
pkginfo[key] = matchingpkginfo[key]
# now let user do some basic editing
editfields = (('Item name', 'name'),
('Display name', 'display_name'),
('Description', 'description'),
('Version', 'version'),
('Category', 'category'),
('Developer', 'developer'),
editfields = (('Item name', 'name', 'str'),
('Display name', 'display_name', 'str'),
('Description', 'description', 'str'),
('Version', 'version', 'str'),
('Category', 'category', 'str'),
('Developer', 'developer', 'str'),
('Unattended install', 'unattended_install', 'bool'),
('Unattended uninstall', 'unattended_uninstall', 'bool'),
)
for (name, key) in editfields:
prompt = '%15s' % name
default = pkginfo.get(key, '').encode('UTF-8')
for (name, key, kind) in editfields:
prompt = '%20s: ' % name
if kind == 'bool':
default = str(pkginfo.get(key, False))
else:
default = pkginfo.get(key, '').encode('UTF-8')
pkginfo[key] = raw_input_with_default(prompt, default)
if kind == 'bool':
value = pkginfo[key].lower().strip()
if value.startswith(('y', 't')):
pkginfo[key] = True
else:
pkginfo[key] = False
# special handling for catalogs array
prompt = '%15s' % 'Catalogs'
prompt = '%20s: ' % 'Catalogs'
default = ', '.join(pkginfo['catalogs'])
newvalue = raw_input_with_default(prompt, default)
pkginfo['catalogs'] = [item.strip()
@@ -1003,17 +1027,19 @@ def main():
'\'installs\' items for this installer '
'item. You will need to add at least '
'one item to the \'installs\' list.')
#TO-DO: provide a way to add 'installs' items right here
print
for (name, key) in editfields:
print '%15s: %s' % (name, pkginfo.get(key, '').encode('UTF-8'))
print '%15s: %s' % (
'Catalogs', ', '.join(pkginfo['catalogs']).encode('UTF-8'))
print
#for (name, key, kind) in editfields:
# if kind == 'bool':
# print '%20s: %s' % (name, pkginfo.get(key, False))
# else:
# print '%20s: %s' % (name, pkginfo.get(key, '').encode('UTF-8'))
#print '%20s: %s' % (
# 'Catalogs', ', '.join(pkginfo['catalogs']).encode('UTF-8'))
#print
answer = raw_input('Import this item? [y/n] ')
if not answer.lower().startswith('y'):
cleanupAndExit(0)
cleanup_and_exit(0)
if options.subdirectory == '':
pkgs_path = os.path.join(REPO_PATH, 'pkgs')
@@ -1023,10 +1049,10 @@ def main():
installer_item_dirpath = os.path.dirname(installer_item)
options.subdirectory = \
installer_item_dirpath[len(pkgs_path)+1:]
options.subdirectory = promptForSubdirectory(
options.subdirectory = prompt_for_subdirectory(
options.subdirectory)
if (not iconExistsInRepo(pkginfo) and not options.icon_path
if (not icon_exists_in_repo(pkginfo) and not options.icon_path
and not APPLEMETADATA
and not pkginfo.get('installer_type') == 'profile'):
print 'No existing product icon found.'
@@ -1050,12 +1076,12 @@ def main():
if not APPLEMETADATA:
try:
uploaded_pkgpath = copyItemToRepo(installer_item,
pkginfo.get('version'),
options.subdirectory)
uploaded_pkgpath = copy_item_to_repo(installer_item,
pkginfo.get('version'),
options.subdirectory)
except RepoCopyError, errmsg:
print >> sys.stderr, errmsg
cleanupAndExit(-1)
cleanup_and_exit(-1)
# adjust the installer_item_location to match
# the actual location and name
@@ -1063,12 +1089,12 @@ def main():
if uninstaller_item:
try:
uploaded_pkgpath = copyItemToRepo(uninstaller_item,
pkginfo.get('version'),
options.subdirectory)
uploaded_pkgpath = copy_item_to_repo(uninstaller_item,
pkginfo.get('version'),
options.subdirectory)
except RepoCopyError, errmsg:
print >> sys.stderr, errmsg
cleanupAndExit(-1)
cleanup_and_exit(-1)
# adjust the uninstaller_item_location to match
# the actual location and name; update size and hash
@@ -1086,27 +1112,27 @@ def main():
print >> sys.stderr, errmsg
# add icon to pkginfo if in repository
addIconHashToPkginfo(pkginfo)
add_icon_hash_to_pkginfo(pkginfo)
# installer_item upload was successful, so upload pkginfo to repo
try:
pkginfo_path = copyPkginfoToRepo(pkginfo, options.subdirectory)
pkginfo_path = copy_pkginfo_to_repo(pkginfo, options.subdirectory)
except RepoCopyError, errmsg:
print >> sys.stderr, errmsg
cleanupAndExit(-1)
cleanup_and_exit(-1)
if not options.nointeractive:
# open the pkginfo file in the user's editor
openPkginfoInEditor(pkginfo_path)
open_pkginfo_in_editor(pkginfo_path)
answer = raw_input('Rebuild catalogs? [y/n] ')
if answer.lower().startswith('y'):
try:
makeCatalogs()
make_catalogs()
except RepoCopyError, errmsg:
print >> sys.stderr, errmsg
cleanupAndExit(-1)
cleanup_and_exit(-1)
cleanupAndExit(0)
cleanup_and_exit(0)
if __name__ == '__main__':
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/python
# encoding: utf-8
#
# Copyright 2009-2014 Greg Neagle.
# Copyright 2009-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
+1 -1
View File
@@ -7,7 +7,7 @@ Utilities to enable munki to install/uninstall Adobe CS3/CS4/CS5 products
using the CS3/CS4/CS5 Deployment Toolkits.
"""
# Copyright 2009-2014 Greg Neagle.
# Copyright 2009-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
File diff suppressed because it is too large Load Diff
+44 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/python
# encoding: utf-8
#
# Copyright 2011-2014 Greg Neagle.
# Copyright 2011-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the 'License');
# you may not use this file except in compliance with the License.
@@ -24,6 +24,7 @@ Created by Greg Neagle on 2011-09-29.
#standard libs
import calendar
import errno
import imp
import os
import shutil
import time
@@ -60,6 +61,40 @@ DEFAULT_USER_AGENT = "managedsoftwareupdate/%s Darwin/%s" % (
machine['munki_version'], darwin_version)
def import_middleware():
'''Check munki folder for a python file that starts with 'middleware'.
If the file exists and has a callable 'process_request_options' attribute,
the module is loaded under the 'middleware' name'''
required_function_name = 'process_request_options'
munki_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
for filename in os.listdir(munki_dir):
if (filename.startswith('middleware')
and os.path.splitext(filename)[1] == '.py'):
name = os.path.splitext(filename)[0]
filepath = os.path.join(munki_dir, filename)
_tmp = imp.load_source(name, filepath)
if hasattr(_tmp, required_function_name):
if callable(getattr(_tmp, required_function_name)):
munkicommon.display_debug1(
'Loading middleware module %s' % filename)
globals()['middleware'] = _tmp
return
else:
munkicommon.display_warning(
'%s attribute in %s is not callable.'
% (required_function_name, filepath))
munkicommon.display_warning('Ignoring %s' % filepath)
else:
munkicommon.display_warning(
'%s does not have a %s function'
% (filepath, required_function_name))
munkicommon.display_warning('Ignoring %s' % filepath)
return
middleware = None
import_middleware()
class GurlError(Exception):
"""General exception for gurl errors"""
pass
@@ -159,6 +194,14 @@ def get_url(url, destinationpath,
'logging_function': munkicommon.display_debug2}
munkicommon.display_debug2('Options: %s' % options)
# Allow middleware to modify options
if middleware:
munkicommon.display_debug2('Processing options through middleware')
# middleware module must have process_request_options function
# and must return usable options
options = middleware.process_request_options(options)
munkicommon.display_debug2('Options: %s' % options)
connection = Gurl.alloc().initWithOptions_(options)
stored_percent_complete = -1
stored_bytes_received = 0
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/python
# encoding: utf-8
#
# Copyright 2010-2014 Greg Neagle.
# Copyright 2010-2016 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 -66
View File
@@ -1,7 +1,7 @@
#!/usr/bin/python
# encoding: utf-8
#
# Copyright 2009-2014 Greg Neagle.
# Copyright 2009-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -31,8 +31,10 @@ import adobeutils
import launchd
import munkicommon
import munkistatus
import powermgr
import profiles
import updatecheck
import xattr
import FoundationPlist
from removepackages import removepackages
@@ -40,42 +42,11 @@ from removepackages import removepackages
# No name 'Foo' in module 'Bar' warnings. Disable them.
# pylint: disable=E0611
from Foundation import NSDate
# stuff for IOKit/PowerManager, courtesy Michael Lynn, pudquick@github
from ctypes import c_uint32, cdll, c_void_p, POINTER, byref
from CoreFoundation import CFStringCreateWithCString
from CoreFoundation import kCFStringEncodingASCII
from objc import pyobjc_id
# pylint: enable=E0611
# lots of camelCase names
# pylint: disable=C0103
libIOKit = cdll.LoadLibrary('/System/Library/Frameworks/IOKit.framework/IOKit')
libIOKit.IOPMAssertionCreateWithName.argtypes = [
c_void_p, c_uint32, c_void_p, POINTER(c_uint32)]
libIOKit.IOPMAssertionRelease.argtypes = [c_uint32]
def CFSTR(py_string):
'''Returns a CFString given a Python string'''
return CFStringCreateWithCString(None, py_string, kCFStringEncodingASCII)
def raw_ptr(pyobjc_string):
'''Returns a pointer to a CFString'''
return pyobjc_id(pyobjc_string.nsstring())
def IOPMAssertionCreateWithName(assert_name, assert_level, assert_msg):
'''Creaes a PowerManager assertion'''
assertID = c_uint32(0)
p_assert_name = raw_ptr(CFSTR(assert_name))
p_assert_msg = raw_ptr(CFSTR(assert_msg))
errcode = libIOKit.IOPMAssertionCreateWithName(
p_assert_name, assert_level, p_assert_msg, byref(assertID))
return (errcode, assertID)
IOPMAssertionRelease = libIOKit.IOPMAssertionRelease
# end IOKit/PowerManager bindings
# initialize our report fields
# we do this here because appleupdates.installAppleUpdates()
# calls installWithInfo()
@@ -467,13 +438,23 @@ def copyItemsFromMountpoint(mountpoint, itemlist):
# all tests passed, OK to copy
munkicommon.display_status_minor(
"Copying %s to %s" % (source_itemname, full_destpath))
retcode = subprocess.call(["/bin/cp", "-pR",
retcode = subprocess.call(["/usr/bin/ditto", "--noqtn",
source_itempath, full_destpath])
if retcode:
munkicommon.display_error(
"Error copying %s to %s" % (source_itempath, full_destpath))
return retcode
# remove com.apple.quarantine xattr since `man ditto` lies and doesn't
# seem to actually always remove it
try:
if "com.apple.quarantine" in xattr.xattr(full_destpath).list():
xattr.xattr(full_destpath).remove("com.apple.quarantine")
except BaseException as err:
munkicommon.display_warning(
"Error removing com.apple.quarantine from %s: %s",
full_destpath, err)
# set owner
user = item.get('user', 'root')
munkicommon.display_detail(
@@ -506,20 +487,6 @@ def copyItemsFromMountpoint(mountpoint, itemlist):
"Error setting mode for %s" % (full_destpath))
return retcode
# remove com.apple.quarantine attribute from copied item
cmd = ["/usr/bin/xattr", full_destpath]
proc = subprocess.Popen(cmd, shell=False, bufsize=-1,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
(out, dummy_err) = proc.communicate()
if out:
xattrs = str(out).splitlines()
if "com.apple.quarantine" in xattrs:
dummy_result = subprocess.call(
["/usr/bin/xattr", "-d", "com.apple.quarantine",
full_destpath])
# all items copied successfully!
return 0
@@ -1019,7 +986,7 @@ def processRemovals(removallist, only_unattended=False):
munkicommon.display_error(message)
else:
munkicommon.log(
"Uninstall of %s was successful.", display_name)
"Uninstall of %s was successful." % display_name)
elif uninstallmethod.startswith("Adobe"):
retcode = adobeutils.doAdobeRemoval(item)
@@ -1186,21 +1153,6 @@ def blockingApplicationsRunning(pkginfoitem):
return False
def assertNoIdleSleep():
"""Uses IOKit functions to prevent idle sleep"""
# based on code by Michael Lynn, pudquick@github
kIOPMAssertionTypeNoIdleSleep = "NoIdleSleepAssertion"
kIOPMAssertionLevelOn = 255
reason = "Munki is installing software"
dummy_errcode, assertID = IOPMAssertionCreateWithName(
kIOPMAssertionTypeNoIdleSleep,
kIOPMAssertionLevelOn,
reason)
return assertID
def run(only_unattended=False):
"""Runs the install/removal session.
@@ -1208,7 +1160,7 @@ def run(only_unattended=False):
only_unattended: Boolean. If True, only do unattended_(un)install pkgs.
"""
# hold onto the assertionID so we can release it later
no_idle_sleep_assertion_id = assertNoIdleSleep()
no_idle_sleep_assertion_id = powermgr.assertNoIdleSleep()
managedinstallbase = munkicommon.pref('ManagedInstallDir')
installdir = os.path.join(managedinstallbase, 'Cache')
@@ -1328,7 +1280,6 @@ def run(only_unattended=False):
munkicommon.savereport()
# release our Power Manager assertion
dummy_errcode = IOPMAssertionRelease(no_idle_sleep_assertion_id)
powermgr.removeNoIdleSleepAssertion(no_idle_sleep_assertion_id)
return removals_need_restart or installs_need_restart
+2 -2
View File
@@ -1,7 +1,7 @@
#!/usr/bin/python
# encoding: utf-8
#
# Copyright 2014 Greg Neagle.
# Copyright 2014-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the 'License');
# you may not use this file except in compliance with the License.
@@ -483,7 +483,7 @@ def debug_output():
munkicommon.display_debug1(
security('show-keychain-info', keychainfile))
except SecurityError, err:
munkicommon.display_error(str(err))
munkicommon.display_error(unicode(err))
class SecurityError(Exception):
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/python
# encoding: utf-8
#
# Copyright 2011-2014 Greg Neagle.
# Copyright 2011-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
+16 -7
View File
@@ -1,7 +1,7 @@
#!/usr/bin/python
# encoding: utf-8
#
# Copyright 2009-2014 Greg Neagle.
# Copyright 2009-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the 'License');
# you may not use this file except in compliance with the License.
@@ -519,7 +519,15 @@ def validateDateFormat(datetime_string):
def log(msg, logname=''):
"""Generic logging function."""
logging.info(msg) # noop unless configure_syslog() is called first.
if len(msg) > 1000:
# See http://bugs.python.org/issue11907 and RFC-3164
# break up huge msg into chunks and send 1000 characters at a time
msg_buffer = msg
while msg_buffer:
logging.info(msg_buffer[:1000])
msg_buffer = msg_buffer[1000:]
else:
logging.info(msg) # noop unless configure_syslog() is called first.
# date/time format string
formatstr = '%b %d %Y %H:%M:%S %z'
@@ -2347,8 +2355,8 @@ def get_hardware_info():
return {}
def get_ipv4_addresses():
'''Uses system profiler to get active IPv4 addresses for this machine'''
def get_ip_addresses(version):
'''Uses system profiler to get active IP addresses for this machine'''
ip_addresses = []
cmd = ['/usr/sbin/system_profiler', 'SPNetworkDataType', '-xml']
proc = subprocess.Popen(cmd, shell=False, bufsize=-1,
@@ -2367,9 +2375,9 @@ def get_ipv4_addresses():
for item in items:
try:
ip_addresses.extend(item['IPv4']['Addresses'])
ip_addresses.extend(item[version]['Addresses'])
except KeyError:
# 'IPv4" or 'Addresses' is empty, so we ignore
# 'IPv4", 'IPv6' or 'Addresses' is empty, so we ignore
# this item
pass
return ip_addresses
@@ -2401,7 +2409,8 @@ def getMachineFacts():
hardware_info = get_hardware_info()
MACHINE['machine_model'] = hardware_info.get('machine_model', 'UNKNOWN')
MACHINE['munki_version'] = get_version()
MACHINE['ipv4_address'] = get_ipv4_addresses()
MACHINE['ipv4_address'] = get_ip_addresses('IPv4')
MACHINE['ipv6_address'] = get_ip_addresses('IPv6')
MACHINE['serial_number'] = hardware_info.get('serial_number', 'UNKNOWN')
if MACHINE['arch'] == 'x86_64':
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/python
# encoding: utf-8
#
# Copyright 2009-2014 Greg Neagle.
# Copyright 2009-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
+75
View File
@@ -0,0 +1,75 @@
#!/usr/bin/python
# encoding: utf-8
#
# Copyright 2009-2016 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
#
# https://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.
"""
powermgr.py
munki module to toggle IOKit/PowerManager idle sleep assertions.
"""
# pylint: disable=E0611
# stuff for IOKit/PowerManager, courtesy Michael Lynn, pudquick@github
from ctypes import c_uint32, cdll, c_void_p, POINTER, byref
from CoreFoundation import CFStringCreateWithCString
from CoreFoundation import kCFStringEncodingASCII
from objc import pyobjc_id
# pylint: enable=E0611
# lots of camelCase names
# pylint: disable=C0103
libIOKit = cdll.LoadLibrary('/System/Library/Frameworks/IOKit.framework/IOKit')
libIOKit.IOPMAssertionCreateWithName.argtypes = [
c_void_p, c_uint32, c_void_p, POINTER(c_uint32)]
libIOKit.IOPMAssertionRelease.argtypes = [c_uint32]
def _CFSTR(py_string):
"""Returns a CFString given a Python string."""
return CFStringCreateWithCString(None, py_string, kCFStringEncodingASCII)
def _rawPointer(pyobjc_string):
"""Returns a pointer to a CFString."""
return pyobjc_id(pyobjc_string.nsstring())
def _IOPMAssertionCreateWithName(assert_name, assert_level, assert_msg):
"""Creaes a PowerManager assertion."""
assertID = c_uint32(0)
p_assert_name = _rawPointer(_CFSTR(assert_name))
p_assert_msg = _rawPointer(_CFSTR(assert_msg))
errcode = libIOKit.IOPMAssertionCreateWithName(
p_assert_name, assert_level, p_assert_msg, byref(assertID))
return (errcode, assertID)
def assertNoIdleSleep():
"""Uses IOKit functions to prevent idle sleep."""
# based on code by Michael Lynn, pudquick@github
kIOPMAssertionTypeNoIdleSleep = "NoIdleSleepAssertion"
kIOPMAssertionLevelOn = 255
reason = "Munki is installing software"
dummy_errcode, assertID = _IOPMAssertionCreateWithName(
kIOPMAssertionTypeNoIdleSleep,
kIOPMAssertionLevelOn,
reason)
return assertID
def removeNoIdleSleepAssertion(assertion_id):
"""Uses IOKit functions to remove a "no idle sleep" assertion."""
return libIOKit.IOPMAssertionRelease(assertion_id)
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/python
# encoding: utf-8
#
# Copyright 2014 Greg Neagle.
# Copyright 2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
+2 -2
View File
@@ -1,7 +1,7 @@
#!/usr/bin/python
# encoding: utf-8
#
# Copyright 2009-2014 Greg Neagle.
# Copyright 2009-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -335,7 +335,7 @@ def ImportBom(bompath, curs):
package database into our internal package database.
"""
# If we completely trusted the accuracy of Apple's database, we wouldn't
# need the bom files, but in my enviroment at least, the bom files are
# need the bom files, but in my environment at least, the bom files are
# a better indicator of what flat packages have actually been installed
# on the current machine.
# We still need to consult Apple's package database

Some files were not shown because too many files have changed in this diff Show More