mirror of
https://github.com/munki/munki.git
synced 2026-02-06 15:20:36 -06:00
More macOS 26 updates; begin splitting of MainWindowController.swift into multiple files
This commit is contained in:
@@ -15,6 +15,10 @@
|
||||
8428C7EA2E0CB59200D83D41 /* MainWindowController+Toolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8428C7E92E0CB59200D83D41 /* MainWindowController+Toolbar.swift */; };
|
||||
8428C7EE2E0DA9AF00D83D41 /* LogWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 8428C7ED2E0DA9AF00D83D41 /* LogWindow.xib */; };
|
||||
8428C7F02E0DABC700D83D41 /* LogWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8428C7EF2E0DABC700D83D41 /* LogWindowController.swift */; };
|
||||
849BF6362E104D9D004A020D /* MainWindowController+NSWindowDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849BF6352E104D9D004A020D /* MainWindowController+NSWindowDelegate.swift */; };
|
||||
849BF6382E104F74004A020D /* MainWindowController+NSOutlineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849BF6372E104F74004A020D /* MainWindowController+NSOutlineView.swift */; };
|
||||
849BF63A2E1050E2004A020D /* MainWindowController+WKNavigationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849BF6392E1050E2004A020D /* MainWindowController+WKNavigationDelegate.swift */; };
|
||||
849BF63C2E1051BE004A020D /* MainWindowController+WKScriptMessageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849BF63B2E1051BE004A020D /* MainWindowController+WKScriptMessageHandler.swift */; };
|
||||
849D46162E0907420054599E /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = 849D46152E0907420054599E /* AppIcon.icon */; };
|
||||
C012D5342C25121F004FDB5A /* BlurredBackgroundController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C012D5332C25121F004FDB5A /* BlurredBackgroundController.swift */; };
|
||||
C01603CF20CF8B6100DEF9E4 /* iconutils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C01603CE20CF8B6100DEF9E4 /* iconutils.swift */; };
|
||||
@@ -85,6 +89,10 @@
|
||||
8428C7E92E0CB59200D83D41 /* MainWindowController+Toolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainWindowController+Toolbar.swift"; sourceTree = "<group>"; };
|
||||
8428C7ED2E0DA9AF00D83D41 /* LogWindow.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LogWindow.xib; sourceTree = "<group>"; };
|
||||
8428C7EF2E0DABC700D83D41 /* LogWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogWindowController.swift; sourceTree = "<group>"; };
|
||||
849BF6352E104D9D004A020D /* MainWindowController+NSWindowDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainWindowController+NSWindowDelegate.swift"; sourceTree = "<group>"; };
|
||||
849BF6372E104F74004A020D /* MainWindowController+NSOutlineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainWindowController+NSOutlineView.swift"; sourceTree = "<group>"; };
|
||||
849BF6392E1050E2004A020D /* MainWindowController+WKNavigationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainWindowController+WKNavigationDelegate.swift"; sourceTree = "<group>"; };
|
||||
849BF63B2E1051BE004A020D /* MainWindowController+WKScriptMessageHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainWindowController+WKScriptMessageHandler.swift"; sourceTree = "<group>"; };
|
||||
849D46152E0907420054599E /* AppIcon.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = AppIcon.icon; sourceTree = "<group>"; };
|
||||
C012D5332C25121F004FDB5A /* BlurredBackgroundController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurredBackgroundController.swift; sourceTree = "<group>"; };
|
||||
C01603CE20CF8B6100DEF9E4 /* iconutils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iconutils.swift; sourceTree = "<group>"; };
|
||||
@@ -275,6 +283,8 @@
|
||||
children = (
|
||||
C04F828C20BB32EA00F9C57D /* LogViewController.swift */,
|
||||
C04CA61D20E6ADA100711461 /* MainWindowController.swift */,
|
||||
849BF6372E104F74004A020D /* MainWindowController+NSOutlineView.swift */,
|
||||
849BF6352E104D9D004A020D /* MainWindowController+NSWindowDelegate.swift */,
|
||||
8428C7E92E0CB59200D83D41 /* MainWindowController+Toolbar.swift */,
|
||||
C0C5DF6B20F5C27700CA0687 /* MSCStatusController.swift */,
|
||||
C0546BD520FBE61A003FE5A6 /* MSCAlertController.swift */,
|
||||
@@ -284,6 +294,8 @@
|
||||
8428C7E52E0CB44200D83D41 /* MainContentViewController.swift */,
|
||||
8428C7E72E0CB55000D83D41 /* MainSplitViewController.swift */,
|
||||
8428C7EF2E0DABC700D83D41 /* LogWindowController.swift */,
|
||||
849BF6392E1050E2004A020D /* MainWindowController+WKNavigationDelegate.swift */,
|
||||
849BF63B2E1051BE004A020D /* MainWindowController+WKScriptMessageHandler.swift */,
|
||||
);
|
||||
path = Controllers;
|
||||
sourceTree = "<group>";
|
||||
@@ -460,6 +472,7 @@
|
||||
8428C7E62E0CB44200D83D41 /* MainContentViewController.swift in Sources */,
|
||||
C0C5DF6C20F5C27700CA0687 /* MSCStatusController.swift in Sources */,
|
||||
C0B2E4D921460E7D00FA9806 /* MSCWebView.swift in Sources */,
|
||||
849BF6382E104F74004A020D /* MainWindowController+NSOutlineView.swift in Sources */,
|
||||
C0E2599E210AD8CE00C3A3D9 /* Socket.swift in Sources */,
|
||||
C01603D120D0A7FA00DEF9E4 /* SelfService.swift in Sources */,
|
||||
8428C7E42E0CB41F00D83D41 /* SidebarViewControiller.swift in Sources */,
|
||||
@@ -474,12 +487,15 @@
|
||||
C04F827F20BB319B00F9C57D /* AppDelegate.swift in Sources */,
|
||||
C0D30BEB20CA445A005E876E /* MunkiItems.swift in Sources */,
|
||||
C04CA61A20E682AE00711461 /* passwdutil.swift in Sources */,
|
||||
849BF63A2E1050E2004A020D /* MainWindowController+WKNavigationDelegate.swift in Sources */,
|
||||
56C33E772173DC8E00D727DC /* MSCTableRowView.swift in Sources */,
|
||||
C03107C520C8F220007FE337 /* msclog.swift in Sources */,
|
||||
C094C26C28B7E17800CFB0D8 /* osinstaller.swift in Sources */,
|
||||
C0546BDC20FE961E003FE5A6 /* MSCPasswordAlertController.swift in Sources */,
|
||||
C04F828D20BB32EA00F9C57D /* LogViewController.swift in Sources */,
|
||||
849BF63C2E1051BE004A020D /* MainWindowController+WKScriptMessageHandler.swift in Sources */,
|
||||
C0546BD820FDAD82003FE5A6 /* power.swift in Sources */,
|
||||
849BF6362E104D9D004A020D /* MainWindowController+NSWindowDelegate.swift in Sources */,
|
||||
C012D5342C25121F004FDB5A /* BlurredBackgroundController.swift in Sources */,
|
||||
C04F829120BB34D100F9C57D /* Localization.swift in Sources */,
|
||||
C0546BD620FBE61A003FE5A6 /* MSCAlertController.swift in Sources */,
|
||||
|
||||
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
//
|
||||
// MainWindowController+NSOutlineView.swift
|
||||
// Managed Software Center
|
||||
//
|
||||
// Created by Greg Neagle on 6/28/25.
|
||||
// Copyright © 2025 The Munki Project. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
extension MainWindowController: NSOutlineViewDataSource {
|
||||
// Number of items in the sidebar
|
||||
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
|
||||
return sidebar_items.count
|
||||
}
|
||||
|
||||
// Items to be added to sidebar
|
||||
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
|
||||
return sidebar_items[index]
|
||||
}
|
||||
|
||||
// Whether rows are expandable by an arrow
|
||||
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func outlineView(_ outlineView: NSOutlineView, rowViewForItem item: Any) -> NSTableRowView? {
|
||||
return MSCTableRowView(frame: NSZeroRect);
|
||||
}
|
||||
|
||||
func outlineView(_ outlineView: NSOutlineView, didAdd rowView: NSTableRowView, forRow row: Int) {
|
||||
rowView.selectionHighlightStyle = .regular
|
||||
}
|
||||
}
|
||||
|
||||
extension MainWindowController: NSOutlineViewDelegate {
|
||||
|
||||
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
|
||||
var view: MSCTableCellView?
|
||||
let itemDict = item as? [String: String]
|
||||
if let title = itemDict?["title"], let icon = itemDict?["icon"] {
|
||||
view = outlineView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "ItemCell"), owner: self) as? MSCTableCellView
|
||||
if let textField = view?.title {
|
||||
textField.stringValue = title.localized(withComment: "\(title) label")
|
||||
}
|
||||
if let imageView = view?.imgView {
|
||||
imageView.image = NSImage(named: NSImage.Name(icon))?.tint(color: .secondaryLabelColor)
|
||||
}
|
||||
}
|
||||
return view
|
||||
}
|
||||
}
|
||||
|
||||
extension NSImage {
|
||||
func tint(color: NSColor) -> NSImage {
|
||||
guard !self.isTemplate else { return self }
|
||||
|
||||
let image = self.copy() as! NSImage
|
||||
image.lockFocus()
|
||||
|
||||
color.set()
|
||||
|
||||
let imageRect = NSRect(origin: NSZeroPoint, size: image.size)
|
||||
imageRect.fill(using: .sourceAtop)
|
||||
|
||||
image.unlockFocus()
|
||||
image.isTemplate = false
|
||||
|
||||
return image
|
||||
}
|
||||
}
|
||||
|
||||
extension String {
|
||||
func localized(withComment comment: String? = nil) -> String {
|
||||
return NSLocalizedString(self, comment: comment ?? "")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// MainWindowController+NSWindowDelegate.swift
|
||||
// Managed Software Center
|
||||
//
|
||||
// Created by Greg Neagle on 6/28/25.
|
||||
// Copyright © 2025 The Munki Project. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
extension MainWindowController: NSWindowDelegate {
|
||||
|
||||
func windowShouldClose(_ sender: NSWindow) -> Bool {
|
||||
// NSWindowDelegate method called when user closes a window
|
||||
// for us, closing the main window should be the same as quitting
|
||||
NSApp.terminate(self)
|
||||
return false
|
||||
}
|
||||
|
||||
func windowDidBecomeMain(_ notification: Notification) {
|
||||
// Our window was activated, make sure controls enabled as needed
|
||||
sidebarList.action = #selector(self.onItemClicked)
|
||||
}
|
||||
|
||||
func windowDidResignMain(_ notification: Notification) {
|
||||
// Our window was deactivated, make sure controls enabled as needed
|
||||
}
|
||||
|
||||
func windowDidBecomeKey(_ notification: Notification) {
|
||||
// If we just became key, enforce obnoxious mode if required.
|
||||
if _obnoxiousNotificationMode {
|
||||
makeUsObnoxious()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
//
|
||||
// MainWindowController+WKNavigationDelegate.swift
|
||||
// Managed Software Center
|
||||
//
|
||||
// Created by Greg Neagle on 6/28/25.
|
||||
// Copyright © 2025 The Munki Project. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
import WebKit
|
||||
|
||||
extension MainWindowController: WKNavigationDelegate {
|
||||
|
||||
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
|
||||
if let url = navigationAction.request.url {
|
||||
msc_debug_log("Got load request for \(url)")
|
||||
if navigationAction.targetFrame == nil {
|
||||
// new window target
|
||||
// open link in default browser instead of in our app's WebView
|
||||
NSWorkspace.shared.open(url)
|
||||
decisionHandler(.cancel)
|
||||
return
|
||||
}
|
||||
}
|
||||
if let url = navigationAction.request.url, let scheme = url.scheme {
|
||||
msc_debug_log("Got URL scheme: \(scheme)")
|
||||
if scheme == "munki" {
|
||||
handleMunkiURL(url)
|
||||
decisionHandler(.cancel)
|
||||
return
|
||||
}
|
||||
if scheme == "mailto" || scheme == "http" || scheme == "https" {
|
||||
// open link in default mail client since WKWebView doesn't
|
||||
// forward these links natively
|
||||
NSWorkspace.shared.open(url)
|
||||
decisionHandler(.cancel)
|
||||
return
|
||||
}
|
||||
if url.scheme == "file" {
|
||||
// if this is a MSC page, generate it!
|
||||
if url.deletingLastPathComponent().path == htmlDir {
|
||||
let filename = url.lastPathComponent
|
||||
do {
|
||||
try buildPage(filename)
|
||||
} catch {
|
||||
msc_debug_log(
|
||||
"Could not build page for \(filename): \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
decisionHandler(.allow)
|
||||
}
|
||||
|
||||
func webView(_ webView: WKWebView,
|
||||
decidePolicyFor navigationResponse: WKNavigationResponse,
|
||||
decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
|
||||
if !(navigationResponse.canShowMIMEType) {
|
||||
if let url = navigationResponse.response.url {
|
||||
// open link in default browser instead of in our app's WebView
|
||||
NSWorkspace.shared.open(url)
|
||||
decisionHandler(.cancel)
|
||||
return
|
||||
}
|
||||
}
|
||||
decisionHandler(.allow)
|
||||
}
|
||||
|
||||
func webView(_ webView: WKWebView,
|
||||
didStartProvisionalNavigation navigation: WKNavigation!) {
|
||||
// Animate progress spinner while we load a page and highlight the
|
||||
// proper toolbar button
|
||||
pageLoadProgress?.startAnimation(self)
|
||||
if let main_url = webView.url {
|
||||
let pagename = main_url.lastPathComponent
|
||||
msc_debug_log("Requested pagename is \(pagename)")
|
||||
if (pagename == "category-all.html" ||
|
||||
pagename.hasPrefix("detail-") ||
|
||||
pagename.hasPrefix("filter-") ||
|
||||
pagename.hasPrefix("developer-")) {
|
||||
highlightSidebarItem("Software")
|
||||
} else if pagename == "categories.html" || pagename.hasPrefix("category-") {
|
||||
highlightSidebarItem("Categories")
|
||||
} else if pagename == "myitems.html" {
|
||||
highlightSidebarItem("My Items")
|
||||
} else if pagename == "updates.html" || pagename.hasPrefix("updatedetail-") {
|
||||
highlightSidebarItem("Updates")
|
||||
} else {
|
||||
// no idea what type of item it is
|
||||
highlightSidebarItem("")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
|
||||
// react to end of displaying a new page
|
||||
pageLoadProgress?.stopAnimation(self)
|
||||
clearCache()
|
||||
let allowNavigateBack = webView.canGoBack
|
||||
let page_url = webView.url
|
||||
let filename = page_url?.lastPathComponent ?? ""
|
||||
let onMainPage = (
|
||||
["category-all.html", "categories.html", "myitems.html", "updates.html"].contains(filename))
|
||||
navigateBackMenuItem.isEnabled = (allowNavigateBack && !onMainPage)
|
||||
if !navigateBackMenuItem.isEnabled {
|
||||
hideNavigationToolbarItem()
|
||||
} else {
|
||||
showNavigationToolbarItem()
|
||||
}
|
||||
}
|
||||
|
||||
func webView(_ webView: WKWebView,
|
||||
didFail navigation: WKNavigation!,
|
||||
withError error: Error) {
|
||||
// Stop progress spinner and log error
|
||||
pageLoadProgress?.stopAnimation(self)
|
||||
msc_debug_log("Committed load error: \(error)")
|
||||
}
|
||||
|
||||
func webView(_ webView: WKWebView,
|
||||
didFailProvisionalNavigation navigation: WKNavigation!,
|
||||
withError error: Error) {
|
||||
// Stop progress spinner and log
|
||||
pageLoadProgress?.stopAnimation(self)
|
||||
msc_debug_log("Provisional load error: \(error)")
|
||||
do {
|
||||
let files = try FileManager.default.contentsOfDirectory(atPath: htmlDir)
|
||||
msc_debug_log("Files in html_dir: \(files)")
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
//
|
||||
// MainWindowController+WKScriptMessageHandler.swift
|
||||
// Managed Software Center
|
||||
//
|
||||
// Created by admin on 6/28/25.
|
||||
// Copyright © 2025 The Munki Project. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
import WebKit
|
||||
|
||||
extension MainWindowController: WKScriptMessageHandler {
|
||||
|
||||
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
|
||||
// react to messages set to us by JavaScript
|
||||
print("Got message from JavaScript: \(message.name)")
|
||||
if message.name == "installButtonClicked" {
|
||||
installButtonClicked()
|
||||
}
|
||||
if message.name == "myItemsButtonClicked" {
|
||||
if let item_name = message.body as? String {
|
||||
myItemsActionButtonClicked(item_name)
|
||||
}
|
||||
}
|
||||
if message.name == "actionButtonClicked" {
|
||||
if let item_name = message.body as? String {
|
||||
actionButtonClicked(item_name)
|
||||
}
|
||||
}
|
||||
if message.name == "changeSelectedCategory" {
|
||||
if let category_name = message.body as? String {
|
||||
changeSelectedCategory(category_name)
|
||||
}
|
||||
}
|
||||
if message.name == "updateOptionalInstallButtonClicked" {
|
||||
if let item_name = message.body as? String {
|
||||
updateOptionalInstallButtonClicked(item_name)
|
||||
}
|
||||
}
|
||||
if message.name == "updateOptionalInstallButtonFinishAction" {
|
||||
if let item_name = message.body as? String {
|
||||
updateOptionalInstallButtonFinishAction(item_name)
|
||||
}
|
||||
}
|
||||
if message.name == "openExternalLink" {
|
||||
if let link = message.body as? String {
|
||||
openExternalLink(link)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// JavaScript integration
|
||||
|
||||
// handling DOM UI elements
|
||||
|
||||
func setInnerHTML(_ htmlString: String, elementID: String) {
|
||||
if let rawData = htmlString.data(using: .utf8) {
|
||||
let encodedData = rawData.base64EncodedString()
|
||||
webView.evaluateJavaScript("setInnerHTMLforElementID('\(elementID)', '\(encodedData)')")
|
||||
}
|
||||
}
|
||||
|
||||
func addToInnerHTML(_ htmlString: String, elementID: String) {
|
||||
if let rawData = htmlString.data(using: .utf8) {
|
||||
let encodedData = rawData.base64EncodedString()
|
||||
webView.evaluateJavaScript("addToInnerHTMLforElementID('\(elementID)', '\(encodedData)')")
|
||||
}
|
||||
}
|
||||
|
||||
func setInnerText(_ textString: String, elementID: String) {
|
||||
if let rawData = textString.data(using: .utf8) {
|
||||
let encodedData = rawData.base64EncodedString()
|
||||
webView.evaluateJavaScript("setInnerTextforElementID('\(elementID)', '\(encodedData)')")
|
||||
}
|
||||
}
|
||||
|
||||
func openExternalLink(_ url: String) {
|
||||
// open a link in the default browser
|
||||
msc_debug_log("External link request: \(url)")
|
||||
if let real_url = URL(string: url) {
|
||||
NSWorkspace.shared.open(real_url)
|
||||
}
|
||||
}
|
||||
|
||||
func installButtonClicked() {
|
||||
// this method is called from JavaScript when the user
|
||||
// clicks the Install button in the Updates view
|
||||
if _update_in_progress {
|
||||
// this is now a stop/cancel button
|
||||
msc_log("user", "cancel_updates")
|
||||
if let status_controller = (NSApp.delegate as? AppDelegate)?.statusController {
|
||||
status_controller.disableStopButton()
|
||||
status_controller._status_stopBtnState = 1
|
||||
}
|
||||
stop_requested = true
|
||||
// send a notification that stop button was clicked
|
||||
let stop_request_flag_file = "/private/tmp/com.googlecode.munki.managedsoftwareupdate.stop_requested"
|
||||
if !FileManager.default.fileExists(atPath: stop_request_flag_file) {
|
||||
FileManager.default.createFile(atPath: stop_request_flag_file, contents: nil, attributes: nil)
|
||||
}
|
||||
} else if getUpdateCount() == 0 {
|
||||
// no updates, the button must say "Check Again"
|
||||
msc_log("user", "refresh_clicked")
|
||||
checkForUpdates()
|
||||
} else {
|
||||
// button must say "Update"
|
||||
// we're on the Updates page, so users can see all the pending/
|
||||
// outstanding updates
|
||||
_alertedUserToOutstandingUpdates = true
|
||||
if !shouldFilterAppleUpdates() && appleUpdatesMustBeDoneWithSystemPreferences() {
|
||||
// if there are pending Apple updates, alert the user to
|
||||
// install via System Preferences
|
||||
alert_controller.alertToAppleUpdates()
|
||||
setFilterAppleUpdates(true)
|
||||
} else {
|
||||
updateNow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateOptionalInstallButtonClicked(_ item_name: String) {
|
||||
// this method is called from JavaScript when a user clicks
|
||||
// the cancel or add button in the updates list
|
||||
if let item = optionalItem(forName: item_name) {
|
||||
if (item["status"] as? String ?? "" == "update-available" &&
|
||||
item["preupgrade_alert"] != nil) {
|
||||
displayPreInstallUninstallAlert(item["preupgrade_alert"] as? PlistDict ?? PlistDict(),
|
||||
action: updateOptionalInstallButtonBeginAction,
|
||||
item: item_name)
|
||||
} else {
|
||||
updateOptionalInstallButtonBeginAction(item_name)
|
||||
}
|
||||
} else {
|
||||
msc_debug_log("Unexpected error: Can't find item for \(item_name)")
|
||||
}
|
||||
}
|
||||
|
||||
func updateOptionalInstallButtonBeginAction(_ item_name: String) {
|
||||
webView.evaluateJavaScript("fadeOutAndRemove('\(item_name)')")
|
||||
}
|
||||
|
||||
func myItemsActionButtonClicked(_ item_name: String) {
|
||||
// this method is called from JavaScript when the user clicks
|
||||
// the Install/Remove/Cancel button in the My Items view
|
||||
if let item = optionalItem(forName: item_name) {
|
||||
if (item["status"] as? String ?? "" == "installed" &&
|
||||
item["preuninstall_alert"] != nil) {
|
||||
displayPreInstallUninstallAlert(item["preuninstall_alert"] as? PlistDict ?? PlistDict(),
|
||||
action: myItemsActionButtonPerformAction,
|
||||
item: item_name)
|
||||
} else {
|
||||
myItemsActionButtonPerformAction(item_name)
|
||||
}
|
||||
} else {
|
||||
msc_debug_log("Unexpected error: Can't find item for \(item_name)")
|
||||
}
|
||||
}
|
||||
|
||||
func myItemsActionButtonPerformAction(_ item_name: String) {
|
||||
// perform action needed when user clicks
|
||||
// the Install/Remove/Cancel button in the My Items view
|
||||
guard let item = optionalItem(forName: item_name) else {
|
||||
msc_debug_log(
|
||||
"User clicked MyItems action button for \(item_name)")
|
||||
msc_debug_log("Could not find item for \(item_name)")
|
||||
return
|
||||
}
|
||||
let prior_status = item["status"] as? String ?? ""
|
||||
if !update_status_for_item(item) {
|
||||
// there was a problem, can't continue
|
||||
return
|
||||
}
|
||||
displayUpdateCount()
|
||||
let current_status = item["status"] as? String ?? ""
|
||||
if current_status == "not-installed" {
|
||||
// we removed item from list of things to install
|
||||
// now remove from display
|
||||
webView.evaluateJavaScript("removeElementByID('\(item_name)_myitems_table_row')")
|
||||
} else {
|
||||
setInnerHTML(item["myitem_action_text"] as? String ?? "", elementID: "\(item_name)_action_button_text")
|
||||
setInnerHTML(item["myitem_status_text"] as? String ?? "", elementID: "\(item_name)_status_text")
|
||||
webView.evaluateJavaScript("document.getElementById('\(item_name)_status_text')).className = 'status \(current_status)'")
|
||||
}
|
||||
if ["install-requested", "removal-requested"].contains(current_status) {
|
||||
_alertedUserToOutstandingUpdates = false
|
||||
if !_update_in_progress {
|
||||
updateNow()
|
||||
}
|
||||
} else if ["staged-os-installer",
|
||||
"will-be-installed",
|
||||
"update-will-be-installed",
|
||||
"will-be-removed"].contains(prior_status) {
|
||||
// cancelled a pending install or removal; should run an updatecheck
|
||||
checkForUpdates(suppress_apple_update_check: true)
|
||||
}
|
||||
}
|
||||
|
||||
func actionButtonClicked(_ item_name: String) {
|
||||
// this method is called from JavaScript when the user clicks
|
||||
// the Install/Remove/Cancel button in the list or detail view
|
||||
if let item = optionalItem(forName: item_name) {
|
||||
var showAlert = true
|
||||
let status = item["status"] as? String ?? ""
|
||||
if status == "not-installed" && item["preinstall_alert"] != nil {
|
||||
displayPreInstallUninstallAlert(item["preinstall_alert"] as? PlistDict ?? PlistDict(),
|
||||
action: actionButtonPerformAction,
|
||||
item: item_name)
|
||||
} else if status == "installed" && item["preuninstall_alert"] != nil {
|
||||
displayPreInstallUninstallAlert(item["preuninstall_alert"] as? PlistDict ?? PlistDict(),
|
||||
action: actionButtonPerformAction,
|
||||
item: item_name)
|
||||
} else if status == "update-available" && item["preupgrade_alert"] != nil {
|
||||
displayPreInstallUninstallAlert(item["preupgrade_alert"] as? PlistDict ?? PlistDict(),
|
||||
action: actionButtonPerformAction,
|
||||
item: item_name)
|
||||
} else {
|
||||
actionButtonPerformAction(item_name)
|
||||
showAlert = false
|
||||
}
|
||||
if showAlert {
|
||||
msc_log("user", "show_alert")
|
||||
}
|
||||
} else {
|
||||
msc_debug_log(
|
||||
"User clicked Install/Remove/Upgrade/Cancel button in the list " +
|
||||
"or detail view")
|
||||
msc_debug_log("Unexpected error: Can't find item for \(item_name)")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import Cocoa
|
||||
import WebKit
|
||||
|
||||
|
||||
class MainWindowController: NSWindowController, NSWindowDelegate, WKNavigationDelegate, WKScriptMessageHandler {
|
||||
class MainWindowController: NSWindowController {
|
||||
|
||||
var mainWindowConfigurationComplete = false
|
||||
var _alertedUserToOutstandingUpdates = false
|
||||
@@ -24,9 +24,9 @@ class MainWindowController: NSWindowController, NSWindowDelegate, WKNavigationDe
|
||||
|
||||
let sidebar_items = [
|
||||
["title": "Software", "icon": "AllItemsTemplate"],
|
||||
["title": "Categories", "icon": "toolbarCategoriesTemplate"],
|
||||
["title": "Categories", "icon": "CategoriesTemplate"],
|
||||
["title": "My Items", "icon": "MyStuffTemplate"],
|
||||
["title": "Updates", "icon": "updatesTemplate"]
|
||||
["title": "Updates", "icon": "UpdatesTemplate"]
|
||||
]
|
||||
|
||||
// status properties
|
||||
@@ -39,13 +39,9 @@ class MainWindowController: NSWindowController, NSWindowDelegate, WKNavigationDe
|
||||
|
||||
@IBOutlet weak var sidebarViewController: SidebarViewController!
|
||||
@IBOutlet weak var mainContentViewController: MainContentViewController!
|
||||
|
||||
@IBOutlet weak var toolbar: NSToolbar!
|
||||
|
||||
@IBOutlet weak var searchField: NSSearchField!
|
||||
|
||||
@IBOutlet weak var sidebarList: NSOutlineView!
|
||||
|
||||
@IBOutlet weak var navigateBackMenuItem: NSMenuItem!
|
||||
@IBOutlet weak var findMenuItem: NSMenuItem!
|
||||
@IBOutlet weak var softwareMenuItem: NSMenuItem!
|
||||
@@ -81,6 +77,9 @@ class MainWindowController: NSWindowController, NSWindowDelegate, WKNavigationDe
|
||||
splitViewController.sidebarWidth = sidebarItem.viewController.view.frame.size.width + 8.0
|
||||
|
||||
let mainContentItem = NSSplitViewItem(viewController: mainContentViewController)
|
||||
if #available(macOS 26.0, *) {
|
||||
mainContentItem.automaticallyAdjustsSafeAreaInsets = true
|
||||
}
|
||||
splitViewController.addSplitViewItem(mainContentItem)
|
||||
self.splitViewController = splitViewController
|
||||
// TODO: remove this hack. (Adding sidebar causes the window to expand, this resets it)
|
||||
@@ -89,20 +88,20 @@ class MainWindowController: NSWindowController, NSWindowDelegate, WKNavigationDe
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func onItemClicked() {
|
||||
@objc func onItemClicked() {
|
||||
if 0 ... sidebar_items.count ~= sidebarList.clickedRow {
|
||||
clearSearchField()
|
||||
switch sidebarList.clickedRow {
|
||||
case 0:
|
||||
loadAllSoftwarePage(self)
|
||||
case 1:
|
||||
loadCategoriesPage(self)
|
||||
case 2:
|
||||
loadMyItemsPage(self)
|
||||
case 3:
|
||||
loadUpdatesPage(self)
|
||||
default:
|
||||
loadUpdatesPage(self)
|
||||
clearSearchField()
|
||||
switch sidebarList.clickedRow {
|
||||
case 0:
|
||||
loadAllSoftwarePage(self)
|
||||
case 1:
|
||||
loadCategoriesPage(self)
|
||||
case 2:
|
||||
loadMyItemsPage(self)
|
||||
case 3:
|
||||
loadUpdatesPage(self)
|
||||
default:
|
||||
loadUpdatesPage(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -469,71 +468,6 @@ class MainWindowController: NSWindowController, NSWindowDelegate, WKNavigationDe
|
||||
displayUpdateCount()
|
||||
}
|
||||
|
||||
// Begin NSWindowDelegate methods
|
||||
|
||||
func windowShouldClose(_ sender: NSWindow) -> Bool {
|
||||
// NSWindowDelegate method called when user closes a window
|
||||
// for us, closing the main window should be the same as quitting
|
||||
NSApp.terminate(self)
|
||||
return false
|
||||
}
|
||||
|
||||
func windowDidBecomeMain(_ notification: Notification) {
|
||||
// Our window was activated, make sure controls enabled as needed
|
||||
sidebarList.action = #selector(onItemClicked)
|
||||
}
|
||||
|
||||
func windowDidResignMain(_ notification: Notification) {
|
||||
// Our window was deactivated, make sure controls enabled as needed
|
||||
}
|
||||
|
||||
func windowDidBecomeKey(_ notification: Notification) {
|
||||
// If we just became key, enforce obnoxious mode if required.
|
||||
if _obnoxiousNotificationMode {
|
||||
makeUsObnoxious()
|
||||
}
|
||||
}
|
||||
|
||||
// End NSWindowDelegate methods
|
||||
|
||||
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
|
||||
// react to messages set to us by JavaScript
|
||||
print("Got message from JavaScript: \(message.name)")
|
||||
if message.name == "installButtonClicked" {
|
||||
installButtonClicked()
|
||||
}
|
||||
if message.name == "myItemsButtonClicked" {
|
||||
if let item_name = message.body as? String {
|
||||
myItemsActionButtonClicked(item_name)
|
||||
}
|
||||
}
|
||||
if message.name == "actionButtonClicked" {
|
||||
if let item_name = message.body as? String {
|
||||
actionButtonClicked(item_name)
|
||||
}
|
||||
}
|
||||
if message.name == "changeSelectedCategory" {
|
||||
if let category_name = message.body as? String {
|
||||
changeSelectedCategory(category_name)
|
||||
}
|
||||
}
|
||||
if message.name == "updateOptionalInstallButtonClicked" {
|
||||
if let item_name = message.body as? String {
|
||||
updateOptionalInstallButtonClicked(item_name)
|
||||
}
|
||||
}
|
||||
if message.name == "updateOptionalInstallButtonFinishAction" {
|
||||
if let item_name = message.body as? String {
|
||||
updateOptionalInstallButtonFinishAction(item_name)
|
||||
}
|
||||
}
|
||||
if message.name == "openExternalLink" {
|
||||
if let link = message.body as? String {
|
||||
openExternalLink(link)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func addJSmessageHandlers() {
|
||||
// define messages JavaScript can send us
|
||||
wkContentController.add(self, name: "openExternalLink")
|
||||
@@ -572,9 +506,34 @@ class MainWindowController: NSWindowController, NSWindowDelegate, WKNavigationDe
|
||||
if #available(OSX 10.12, *) {
|
||||
replacementWebView.setValue(false, forKey: "drawsBackground")
|
||||
}
|
||||
// replace the placeholder in the window view with the real webview
|
||||
superview.replaceSubview(webViewPlaceholder, with: replacementWebView)
|
||||
replacementWebView.translatesAutoresizingMaskIntoConstraints = false
|
||||
if #available(macOS 26.0, *) {
|
||||
// replace the placeholder in the window view with
|
||||
// a background extension view containing the webview
|
||||
let backgroundExtensionView = NSBackgroundExtensionView()
|
||||
backgroundExtensionView.frame = superview.frame
|
||||
backgroundExtensionView.automaticallyPlacesContentView = false
|
||||
backgroundExtensionView.contentView = replacementWebView
|
||||
backgroundExtensionView.translatesAutoresizingMaskIntoConstraints = false
|
||||
superview.replaceSubview(webViewPlaceholder, with: backgroundExtensionView)
|
||||
NSLayoutConstraint.activate([
|
||||
backgroundExtensionView.leadingAnchor.constraint(equalTo: superview.leadingAnchor),
|
||||
backgroundExtensionView.trailingAnchor.constraint(equalTo: superview.trailingAnchor),
|
||||
backgroundExtensionView.topAnchor.constraint(equalTo: superview.topAnchor),
|
||||
backgroundExtensionView.bottomAnchor.constraint(equalTo: superview.bottomAnchor)
|
||||
])
|
||||
} else {
|
||||
// replace the placeholder in the window view with the real webview
|
||||
superview.replaceSubview(webViewPlaceholder, with: replacementWebView)
|
||||
}
|
||||
webView = replacementWebView
|
||||
let safeGuide = superview.safeAreaLayoutGuide
|
||||
NSLayoutConstraint.activate([
|
||||
webView.leadingAnchor.constraint(equalTo: safeGuide.leadingAnchor),
|
||||
webView.trailingAnchor.constraint(equalTo: safeGuide.trailingAnchor),
|
||||
webView.topAnchor.constraint(equalTo: superview.topAnchor),
|
||||
webView.bottomAnchor.constraint(equalTo: superview.bottomAnchor)
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -676,17 +635,7 @@ class MainWindowController: NSWindowController, NSWindowDelegate, WKNavigationDe
|
||||
@objc func checkForUpdatesSkippingAppleUpdates() {
|
||||
checkForUpdates(suppress_apple_update_check: true)
|
||||
}
|
||||
|
||||
@IBAction func reloadPage(_ sender: Any) {
|
||||
// User selected Reload page menu item. Reload the page and kick off an updatecheck
|
||||
msc_log("user", "reload_page_menu_item_selected")
|
||||
setFilterAppleUpdates(false)
|
||||
setFilterStagedOSUpdate(false)
|
||||
checkForUpdates()
|
||||
URLCache.shared.removeAllCachedResponses()
|
||||
webView.reload(sender)
|
||||
}
|
||||
|
||||
|
||||
func kickOffInstallSession() {
|
||||
// start an update install/removal session
|
||||
|
||||
@@ -866,7 +815,7 @@ class MainWindowController: NSWindowController, NSWindowDelegate, WKNavigationDe
|
||||
}
|
||||
|
||||
func displayUpdateCount() {
|
||||
// Display the update count as a badge in the window toolbar
|
||||
// Display the update count as a badge in the sidebar
|
||||
// and as an icon badge in the Dock
|
||||
let updateCount = getUpdateCount()
|
||||
|
||||
@@ -1060,307 +1009,6 @@ class MainWindowController: NSWindowController, NSWindowDelegate, WKNavigationDe
|
||||
// Fallback on earlier versions
|
||||
URLCache.shared.removeAllCachedResponses()
|
||||
}
|
||||
|
||||
// WKNavigationDelegateMethods
|
||||
|
||||
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
|
||||
if let url = navigationAction.request.url {
|
||||
msc_debug_log("Got load request for \(url)")
|
||||
if navigationAction.targetFrame == nil {
|
||||
// new window target
|
||||
// open link in default browser instead of in our app's WebView
|
||||
NSWorkspace.shared.open(url)
|
||||
decisionHandler(.cancel)
|
||||
return
|
||||
}
|
||||
}
|
||||
if let url = navigationAction.request.url, let scheme = url.scheme {
|
||||
msc_debug_log("Got URL scheme: \(scheme)")
|
||||
if scheme == "munki" {
|
||||
handleMunkiURL(url)
|
||||
decisionHandler(.cancel)
|
||||
return
|
||||
}
|
||||
if scheme == "mailto" || scheme == "http" || scheme == "https" {
|
||||
// open link in default mail client since WKWebView doesn't
|
||||
// forward these links natively
|
||||
NSWorkspace.shared.open(url)
|
||||
decisionHandler(.cancel)
|
||||
return
|
||||
}
|
||||
if url.scheme == "file" {
|
||||
// if this is a MSC page, generate it!
|
||||
if url.deletingLastPathComponent().path == htmlDir {
|
||||
let filename = url.lastPathComponent
|
||||
do {
|
||||
try buildPage(filename)
|
||||
} catch {
|
||||
msc_debug_log(
|
||||
"Could not build page for \(filename): \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
decisionHandler(.allow)
|
||||
}
|
||||
|
||||
func webView(_ webView: WKWebView,
|
||||
decidePolicyFor navigationResponse: WKNavigationResponse,
|
||||
decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
|
||||
if !(navigationResponse.canShowMIMEType) {
|
||||
if let url = navigationResponse.response.url {
|
||||
// open link in default browser instead of in our app's WebView
|
||||
NSWorkspace.shared.open(url)
|
||||
decisionHandler(.cancel)
|
||||
return
|
||||
}
|
||||
}
|
||||
decisionHandler(.allow)
|
||||
}
|
||||
|
||||
func webView(_ webView: WKWebView,
|
||||
didStartProvisionalNavigation navigation: WKNavigation!) {
|
||||
// Animate progress spinner while we load a page and highlight the
|
||||
// proper toolbar button
|
||||
pageLoadProgress?.startAnimation(self)
|
||||
if let main_url = webView.url {
|
||||
let pagename = main_url.lastPathComponent
|
||||
msc_debug_log("Requested pagename is \(pagename)")
|
||||
if (pagename == "category-all.html" ||
|
||||
pagename.hasPrefix("detail-") ||
|
||||
pagename.hasPrefix("filter-") ||
|
||||
pagename.hasPrefix("developer-")) {
|
||||
highlightSidebarItem("Software")
|
||||
} else if pagename == "categories.html" || pagename.hasPrefix("category-") {
|
||||
highlightSidebarItem("Categories")
|
||||
} else if pagename == "myitems.html" {
|
||||
highlightSidebarItem("My Items")
|
||||
} else if pagename == "updates.html" || pagename.hasPrefix("updatedetail-") {
|
||||
highlightSidebarItem("Updates")
|
||||
} else {
|
||||
// no idea what type of item it is
|
||||
highlightSidebarItem("")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
|
||||
// react to end of displaying a new page
|
||||
pageLoadProgress?.stopAnimation(self)
|
||||
clearCache()
|
||||
let allowNavigateBack = webView.canGoBack
|
||||
let page_url = webView.url
|
||||
let filename = page_url?.lastPathComponent ?? ""
|
||||
let onMainPage = (
|
||||
["category-all.html", "categories.html", "myitems.html", "updates.html"].contains(filename))
|
||||
navigateBackMenuItem.isEnabled = (allowNavigateBack && !onMainPage)
|
||||
if !navigateBackMenuItem.isEnabled {
|
||||
hideNavigationToolbarItem()
|
||||
} else {
|
||||
showNavigationToolbarItem()
|
||||
}
|
||||
}
|
||||
|
||||
func webView(_ webView: WKWebView,
|
||||
didFail navigation: WKNavigation!,
|
||||
withError error: Error) {
|
||||
// Stop progress spinner and log error
|
||||
pageLoadProgress?.stopAnimation(self)
|
||||
msc_debug_log("Committed load error: \(error)")
|
||||
}
|
||||
|
||||
func webView(_ webView: WKWebView,
|
||||
didFailProvisionalNavigation navigation: WKNavigation!,
|
||||
withError error: Error) {
|
||||
// Stop progress spinner and log
|
||||
pageLoadProgress?.stopAnimation(self)
|
||||
msc_debug_log("Provisional load error: \(error)")
|
||||
do {
|
||||
let files = try FileManager.default.contentsOfDirectory(atPath: htmlDir)
|
||||
msc_debug_log("Files in html_dir: \(files)")
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
// JavaScript integration
|
||||
|
||||
// handling DOM UI elements
|
||||
|
||||
func setInnerHTML(_ htmlString: String, elementID: String) {
|
||||
if let rawData = htmlString.data(using: .utf8) {
|
||||
let encodedData = rawData.base64EncodedString()
|
||||
webView.evaluateJavaScript("setInnerHTMLforElementID('\(elementID)', '\(encodedData)')")
|
||||
}
|
||||
}
|
||||
|
||||
func addToInnerHTML(_ htmlString: String, elementID: String) {
|
||||
if let rawData = htmlString.data(using: .utf8) {
|
||||
let encodedData = rawData.base64EncodedString()
|
||||
webView.evaluateJavaScript("addToInnerHTMLforElementID('\(elementID)', '\(encodedData)')")
|
||||
}
|
||||
}
|
||||
|
||||
func setInnerText(_ textString: String, elementID: String) {
|
||||
if let rawData = textString.data(using: .utf8) {
|
||||
let encodedData = rawData.base64EncodedString()
|
||||
webView.evaluateJavaScript("setInnerTextforElementID('\(elementID)', '\(encodedData)')")
|
||||
}
|
||||
}
|
||||
|
||||
func openExternalLink(_ url: String) {
|
||||
// open a link in the default browser
|
||||
msc_debug_log("External link request: \(url)")
|
||||
if let real_url = URL(string: url) {
|
||||
NSWorkspace.shared.open(real_url)
|
||||
}
|
||||
}
|
||||
|
||||
func installButtonClicked() {
|
||||
// this method is called from JavaScript when the user
|
||||
// clicks the Install button in the Updates view
|
||||
if _update_in_progress {
|
||||
// this is now a stop/cancel button
|
||||
msc_log("user", "cancel_updates")
|
||||
if let status_controller = (NSApp.delegate as? AppDelegate)?.statusController {
|
||||
status_controller.disableStopButton()
|
||||
status_controller._status_stopBtnState = 1
|
||||
}
|
||||
stop_requested = true
|
||||
// send a notification that stop button was clicked
|
||||
let stop_request_flag_file = "/private/tmp/com.googlecode.munki.managedsoftwareupdate.stop_requested"
|
||||
if !FileManager.default.fileExists(atPath: stop_request_flag_file) {
|
||||
FileManager.default.createFile(atPath: stop_request_flag_file, contents: nil, attributes: nil)
|
||||
}
|
||||
} else if getUpdateCount() == 0 {
|
||||
// no updates, the button must say "Check Again"
|
||||
msc_log("user", "refresh_clicked")
|
||||
checkForUpdates()
|
||||
} else {
|
||||
// button must say "Update"
|
||||
// we're on the Updates page, so users can see all the pending/
|
||||
// outstanding updates
|
||||
_alertedUserToOutstandingUpdates = true
|
||||
if !shouldFilterAppleUpdates() && appleUpdatesMustBeDoneWithSystemPreferences() {
|
||||
// if there are pending Apple updates, alert the user to
|
||||
// install via System Preferences
|
||||
alert_controller.alertToAppleUpdates()
|
||||
setFilterAppleUpdates(true)
|
||||
} else {
|
||||
updateNow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateOptionalInstallButtonClicked(_ item_name: String) {
|
||||
// this method is called from JavaScript when a user clicks
|
||||
// the cancel or add button in the updates list
|
||||
if let item = optionalItem(forName: item_name) {
|
||||
if (item["status"] as? String ?? "" == "update-available" &&
|
||||
item["preupgrade_alert"] != nil) {
|
||||
displayPreInstallUninstallAlert(item["preupgrade_alert"] as? PlistDict ?? PlistDict(),
|
||||
action: updateOptionalInstallButtonBeginAction,
|
||||
item: item_name)
|
||||
} else {
|
||||
updateOptionalInstallButtonBeginAction(item_name)
|
||||
}
|
||||
} else {
|
||||
msc_debug_log("Unexpected error: Can't find item for \(item_name)")
|
||||
}
|
||||
}
|
||||
|
||||
func updateOptionalInstallButtonBeginAction(_ item_name: String) {
|
||||
webView.evaluateJavaScript("fadeOutAndRemove('\(item_name)')")
|
||||
}
|
||||
|
||||
func myItemsActionButtonClicked(_ item_name: String) {
|
||||
// this method is called from JavaScript when the user clicks
|
||||
// the Install/Remove/Cancel button in the My Items view
|
||||
if let item = optionalItem(forName: item_name) {
|
||||
if (item["status"] as? String ?? "" == "installed" &&
|
||||
item["preuninstall_alert"] != nil) {
|
||||
displayPreInstallUninstallAlert(item["preuninstall_alert"] as? PlistDict ?? PlistDict(),
|
||||
action: myItemsActionButtonPerformAction,
|
||||
item: item_name)
|
||||
} else {
|
||||
myItemsActionButtonPerformAction(item_name)
|
||||
}
|
||||
} else {
|
||||
msc_debug_log("Unexpected error: Can't find item for \(item_name)")
|
||||
}
|
||||
}
|
||||
|
||||
func myItemsActionButtonPerformAction(_ item_name: String) {
|
||||
// perform action needed when user clicks
|
||||
// the Install/Remove/Cancel button in the My Items view
|
||||
guard let item = optionalItem(forName: item_name) else {
|
||||
msc_debug_log(
|
||||
"User clicked MyItems action button for \(item_name)")
|
||||
msc_debug_log("Could not find item for \(item_name)")
|
||||
return
|
||||
}
|
||||
let prior_status = item["status"] as? String ?? ""
|
||||
if !update_status_for_item(item) {
|
||||
// there was a problem, can't continue
|
||||
return
|
||||
}
|
||||
displayUpdateCount()
|
||||
let current_status = item["status"] as? String ?? ""
|
||||
if current_status == "not-installed" {
|
||||
// we removed item from list of things to install
|
||||
// now remove from display
|
||||
webView.evaluateJavaScript("removeElementByID('\(item_name)_myitems_table_row')")
|
||||
} else {
|
||||
setInnerHTML(item["myitem_action_text"] as? String ?? "", elementID: "\(item_name)_action_button_text")
|
||||
setInnerHTML(item["myitem_status_text"] as? String ?? "", elementID: "\(item_name)_status_text")
|
||||
webView.evaluateJavaScript("document.getElementById('\(item_name)_status_text')).className = 'status \(current_status)'")
|
||||
}
|
||||
if ["install-requested", "removal-requested"].contains(current_status) {
|
||||
_alertedUserToOutstandingUpdates = false
|
||||
if !_update_in_progress {
|
||||
updateNow()
|
||||
}
|
||||
} else if ["staged-os-installer",
|
||||
"will-be-installed",
|
||||
"update-will-be-installed",
|
||||
"will-be-removed"].contains(prior_status) {
|
||||
// cancelled a pending install or removal; should run an updatecheck
|
||||
checkForUpdates(suppress_apple_update_check: true)
|
||||
}
|
||||
}
|
||||
|
||||
func actionButtonClicked(_ item_name: String) {
|
||||
// this method is called from JavaScript when the user clicks
|
||||
// the Install/Remove/Cancel button in the list or detail view
|
||||
if let item = optionalItem(forName: item_name) {
|
||||
var showAlert = true
|
||||
let status = item["status"] as? String ?? ""
|
||||
if status == "not-installed" && item["preinstall_alert"] != nil {
|
||||
displayPreInstallUninstallAlert(item["preinstall_alert"] as? PlistDict ?? PlistDict(),
|
||||
action: actionButtonPerformAction,
|
||||
item: item_name)
|
||||
} else if status == "installed" && item["preuninstall_alert"] != nil {
|
||||
displayPreInstallUninstallAlert(item["preuninstall_alert"] as? PlistDict ?? PlistDict(),
|
||||
action: actionButtonPerformAction,
|
||||
item: item_name)
|
||||
} else if status == "update-available" && item["preupgrade_alert"] != nil {
|
||||
displayPreInstallUninstallAlert(item["preupgrade_alert"] as? PlistDict ?? PlistDict(),
|
||||
action: actionButtonPerformAction,
|
||||
item: item_name)
|
||||
} else {
|
||||
actionButtonPerformAction(item_name)
|
||||
showAlert = false
|
||||
}
|
||||
if showAlert {
|
||||
msc_log("user", "show_alert")
|
||||
}
|
||||
} else {
|
||||
msc_debug_log(
|
||||
"User clicked Install/Remove/Upgrade/Cancel button in the list " +
|
||||
"or detail view")
|
||||
msc_debug_log("Unexpected error: Can't find item for \(item_name)")
|
||||
}
|
||||
}
|
||||
|
||||
func displayPreInstallUninstallAlert(_ alert: PlistDict, action: @escaping (String)->Void, item: String) {
|
||||
// Display an alert sheet before processing item install/upgrade
|
||||
@@ -1580,7 +1228,7 @@ class MainWindowController: NSWindowController, NSWindowDelegate, WKNavigationDe
|
||||
}
|
||||
}
|
||||
|
||||
// some Cocoa UI bindings
|
||||
// MARK: IBActions
|
||||
@IBAction func showHelp(_ sender: Any) {
|
||||
if let helpURL = pref("HelpURL") as? String {
|
||||
if let finalURL = URL(string: helpURL) {
|
||||
@@ -1608,12 +1256,6 @@ class MainWindowController: NSWindowController, NSWindowDelegate, WKNavigationDe
|
||||
navigateBackButton.isHidden = !filename.hasPrefix("detail-")*/
|
||||
}
|
||||
|
||||
@IBAction func navigateForwardBtnClicked(_ sender: Any) {
|
||||
// Handle WebView forward button
|
||||
clearSearchField()
|
||||
webView.goForward(self)
|
||||
}
|
||||
|
||||
@IBAction func loadAllSoftwarePage(_ sender: Any) {
|
||||
// Called by Navigate menu item
|
||||
clearSearchField()
|
||||
@@ -1646,72 +1288,15 @@ class MainWindowController: NSWindowController, NSWindowDelegate, WKNavigationDe
|
||||
load_page("filter-\(filterString).html")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func reloadPage(_ sender: Any) {
|
||||
// User selected Reload page menu item. Reload the page and kick off an updatecheck
|
||||
msc_log("user", "reload_page_menu_item_selected")
|
||||
setFilterAppleUpdates(false)
|
||||
setFilterStagedOSUpdate(false)
|
||||
checkForUpdates()
|
||||
URLCache.shared.removeAllCachedResponses()
|
||||
webView.reload(sender)
|
||||
}
|
||||
|
||||
extension MainWindowController: NSOutlineViewDataSource {
|
||||
// Number of items in the sidebar
|
||||
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
|
||||
return sidebar_items.count
|
||||
}
|
||||
|
||||
// Items to be added to sidebar
|
||||
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
|
||||
return sidebar_items[index]
|
||||
}
|
||||
|
||||
// Whether rows are expandable by an arrow
|
||||
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func outlineView(_ outlineView: NSOutlineView, rowViewForItem item: Any) -> NSTableRowView? {
|
||||
return MSCTableRowView(frame: NSZeroRect);
|
||||
}
|
||||
|
||||
func outlineView(_ outlineView: NSOutlineView, didAdd rowView: NSTableRowView, forRow row: Int) {
|
||||
rowView.selectionHighlightStyle = .regular
|
||||
}
|
||||
}
|
||||
|
||||
extension MainWindowController: NSOutlineViewDelegate {
|
||||
|
||||
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
|
||||
var view: MSCTableCellView?
|
||||
let itemDict = item as? [String: String]
|
||||
if let title = itemDict?["title"], let icon = itemDict?["icon"] {
|
||||
view = outlineView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "ItemCell"), owner: self) as? MSCTableCellView
|
||||
if let textField = view?.title {
|
||||
textField.stringValue = title.localized(withComment: "\(title) label")
|
||||
}
|
||||
if let imageView = view?.imgView {
|
||||
imageView.image = NSImage(named: NSImage.Name(icon))?.tint(color: .secondaryLabelColor)
|
||||
}
|
||||
}
|
||||
return view
|
||||
}
|
||||
}
|
||||
|
||||
extension NSImage {
|
||||
func tint(color: NSColor) -> NSImage {
|
||||
guard !self.isTemplate else { return self }
|
||||
|
||||
let image = self.copy() as! NSImage
|
||||
image.lockFocus()
|
||||
|
||||
color.set()
|
||||
|
||||
let imageRect = NSRect(origin: NSZeroPoint, size: image.size)
|
||||
imageRect.fill(using: .sourceAtop)
|
||||
|
||||
image.unlockFocus()
|
||||
image.isTemplate = false
|
||||
|
||||
return image
|
||||
}
|
||||
}
|
||||
|
||||
extension String {
|
||||
func localized(withComment comment: String? = nil) -> String {
|
||||
return NSLocalizedString(self, comment: comment ?? "")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="24093.7"/>
|
||||
<capability name="NSView safe area layout guides" minToolsVersion="12.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
@@ -32,17 +33,17 @@
|
||||
</connections>
|
||||
</customObject>
|
||||
<customView id="431-dA-DPq" userLabel="Sidebar View">
|
||||
<rect key="frame" x="0.0" y="0.0" width="220" height="300"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="220" height="288"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<subviews>
|
||||
<scrollView borderType="none" horizontalLineScroll="42" horizontalPageScroll="10" verticalLineScroll="42" verticalPageScroll="10" hasHorizontalScroller="NO" hasVerticalScroller="NO" usesPredominantAxisScrolling="NO" horizontalScrollElasticity="none" verticalScrollElasticity="none" translatesAutoresizingMaskIntoConstraints="NO" id="WBA-O8-AVZ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="220" height="204"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="220" height="192"/>
|
||||
<clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="fUg-6X-3lt">
|
||||
<rect key="frame" x="0.0" y="0.0" width="220" height="204"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="220" height="192"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<outlineView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" selectionHighlightStyle="sourceList" multipleSelection="NO" autosaveColumns="NO" rowHeight="40" rowSizeStyle="automatic" viewBased="YES" indentationPerLevel="16" outlineTableColumn="sSx-QF-C2W" id="pGV-ZA-L14">
|
||||
<rect key="frame" x="0.0" y="0.0" width="220" height="204"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="220" height="192"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<size key="intercellSpacing" width="3" height="2"/>
|
||||
<color key="backgroundColor" name="_sourceListBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -130,7 +131,7 @@
|
||||
</scroller>
|
||||
</scrollView>
|
||||
<searchField wantsLayer="YES" focusRingType="none" verticalHuggingPriority="750" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="0L3-LH-chu">
|
||||
<rect key="frame" x="9" y="224" width="202" height="30"/>
|
||||
<rect key="frame" x="9" y="212" width="202" height="30"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="30" id="gCd-dg-I2z"/>
|
||||
</constraints>
|
||||
@@ -153,7 +154,7 @@
|
||||
<constraint firstAttribute="trailing" secondItem="0L3-LH-chu" secondAttribute="trailing" constant="9" id="eQO-po-oda"/>
|
||||
<constraint firstItem="WBA-O8-AVZ" firstAttribute="leading" secondItem="431-dA-DPq" secondAttribute="leading" id="i1m-9B-AHq"/>
|
||||
</constraints>
|
||||
<point key="canvasLocation" x="183" y="759"/>
|
||||
<point key="canvasLocation" x="183" y="764"/>
|
||||
</customView>
|
||||
<customView id="qMd-4V-cdo" userLabel="Main Content View">
|
||||
<rect key="frame" x="0.0" y="0.0" width="200" height="200"/>
|
||||
@@ -164,6 +165,8 @@
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
</customView>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="ev1-BD-wCP"/>
|
||||
<viewLayoutGuide key="layoutMargins" id="GM1-hL-jN6"/>
|
||||
<point key="canvasLocation" x="-177" y="720"/>
|
||||
</customView>
|
||||
<customObject id="wV2-o2-JY3" customClass="MSCPasswordAlertController" customModule="Managed_Software_Center" customModuleProvider="target">
|
||||
|
||||
Reference in New Issue
Block a user