From acc0b03522fef6dcf597e643671eaa6e2cca3ae6 Mon Sep 17 00:00:00 2001 From: relikd Date: Tue, 26 Oct 2021 23:57:27 +0200 Subject: [PATCH] ask for for accessibility permission + fix restore current space only --- Makefile | 33 ++++++++++++++++++++---------- src/Info.plist | 2 +- src/main.swift | 55 +++++++++++++++++++++++++++++++------------------- 3 files changed, 57 insertions(+), 33 deletions(-) diff --git a/Makefile b/Makefile index 9ea327e..7d9f850 100755 --- a/Makefile +++ b/Makefile @@ -6,21 +6,32 @@ else CFLAGS=-O endif -APP_DIR = Memmon.app/Contents +PLIST=$(shell grep -A1 $(1) src/Info.plist | tail -1 | cut -d'>' -f2 | cut -d'<' -f1) -Memmon.app: SDK_PATH = $(shell xcrun --show-sdk-path --sdk macosx) + +Memmon.app: SDK_PATH=$(shell xcrun --show-sdk-path --sdk macosx) Memmon.app: src/* - mkdir -p Memmon.app/Contents/MacOS/ - swiftc $(CFLAGS) src/main.swift \ + @mkdir -p Memmon.app/Contents/MacOS/ + swiftc ${CFLAGS} src/main.swift \ -target arm64-apple-macos10.10 -target x86_64-apple-macos10.10 \ - -emit-executable -sdk $(SDK_PATH) -o Memmon.app/Contents/MacOS/Memmon - mkdir -p Memmon.app/Contents/Resources/ - cp src/AppIcon.icns Memmon.app/Contents/Resources/AppIcon.icns - cp src/Info.plist Memmon.app/Contents/Info.plist - echo 'APPL????' > Memmon.app/Contents/PkgInfo + -emit-executable -sdk ${SDK_PATH} -o Memmon.app/Contents/MacOS/Memmon + @echo 'APPL????' > Memmon.app/Contents/PkgInfo + @mkdir -p Memmon.app/Contents/Resources/ + @cp src/AppIcon.icns Memmon.app/Contents/Resources/AppIcon.icns + @cp src/Info.plist Memmon.app/Contents/Info.plist @touch Memmon.app + @echo + codesign -v -s 'Apple Development' --options=runtime --timestamp Memmon.app + @echo + @echo 'Verify Signature...' + @echo + codesign -dvv Memmon.app + @echo + codesign -vvv --deep --strict Memmon.app + @echo + spctl -vvv --assess --type exec Memmon.app .PHONY: release -release: VERSION=$(shell grep -A1 CFBundleShortVersionString src/Info.plist | tail -1 | tr -d '[a-z \t]') +release: VERSION=$(call PLIST,CFBundleShortVersionString) release: Memmon.app - tar -czf "Memmon_v$(VERSION).tar.gz" Memmon.app + tar -czf "Memmon_v${VERSION}.tar.gz" Memmon.app diff --git a/src/Info.plist b/src/Info.plist index 634b8a6..368c8e4 100644 --- a/src/Info.plist +++ b/src/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.1 + 1.2 CFBundleVersion 42 LSMinimumSystemVersion diff --git a/src/main.swift b/src/main.swift index 1f798d1..c10da69 100755 --- a/src/main.swift +++ b/src/main.swift @@ -2,8 +2,10 @@ import Cocoa import AppKit -typealias WinPos = (Int32, CGRect) // win-num, bounds -typealias WinConf = [Int32: [WinPos]] // app-pid, window-list +typealias AppPID = Int32 // see kCGWindowOwnerPID +typealias WinNum = Int32 // see kCGWindowNumber +typealias WinPos = (WinNum, CGRect) // win-num, bounds +typealias WinConf = [AppPID: [WinPos]] // app-pid, window-list class AppDelegate: NSObject, NSApplicationDelegate { private var statusItem: NSStatusItem! @@ -11,6 +13,9 @@ class AppDelegate: NSObject, NSApplicationDelegate { private var state: [Int: WinConf] = [:] // [screencount: [pid: [windows]]] func applicationDidFinishLaunching(_ aNotification: Notification) { + // show Accessibility Permissions popup + AXIsProcessTrustedWithOptions([kAXTrustedCheckOptionPrompt.takeUnretainedValue() : true] as CFDictionary) + // create status menu icon UserDefaults.standard.register(defaults: ["icon": 2]) let icon = UserDefaults.standard.integer(forKey: "icon") if icon == 0 { return } @@ -23,7 +28,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { } } self.statusItem.menu = NSMenu(title: "") - self.statusItem.menu!.addItem(withTitle: "Memmon (v1.1)", action: nil, keyEquivalent: "") + self.statusItem.menu!.addItem(withTitle: "Memmon (v1.2)", action: nil, keyEquivalent: "") self.statusItem.menu!.addItem(withTitle: "Hide Status Icon", action: #selector(self.enableInvisbleMode), keyEquivalent: "") self.statusItem.menu!.addItem(withTitle: "Quit", action: #selector(NSApp.terminate), keyEquivalent: "q") } @@ -36,12 +41,16 @@ class AppDelegate: NSObject, NSApplicationDelegate { if numScreens != NSScreen.screens.count { self.saveState() numScreens = NSScreen.screens.count - if let previous = self.state[numScreens] { - self.restoreState(previous) - } + self.restoreState() } } + private func getWinIds(allSpaces: Bool) -> [WinNum] { + NSWindow.windowNumbers(options: allSpaces ? [.allApplications, .allSpaces] : .allApplications)?.map{ $0.int32Value } ?? [] + } + + // MARK: - Save State (CGWindow) - + private func saveState() { let newState = self.getState() self.state[numScreens] = newState @@ -63,17 +72,8 @@ class AppDelegate: NSObject, NSApplicationDelegate { } } - private func restoreState(_ state: WinConf) { - for (pid, bounds) in state { - self.setWindowSizes(pid, bounds) - } - } - private func getState() -> WinConf { - var allWinNums: [Int32] = [] - for winNum in NSWindow.windowNumbers(options: [.allApplications, .allSpaces]) ?? [] { - allWinNums.append(winNum.int32Value) - } + let allWinNums = self.getWinIds(allSpaces: true) var state: WinConf = [:] let windowList = CGWindowListCopyWindowInfo(.optionAll, kCGNullWindowID) as NSArray? as? [[String: AnyObject]] @@ -82,11 +82,11 @@ class AppDelegate: NSObject, NSApplicationDelegate { if entry[kCGWindowLayer as String] as! CGWindowLevel != kCGNormalWindowLevel { continue } - let winNum = entry[kCGWindowNumber as String] as! Int32 + let winNum = entry[kCGWindowNumber as String] as! WinNum guard let insIdx = allWinNums.firstIndex(of: winNum) else { continue } - let pid = entry[kCGWindowOwnerPID as String] as! Int32 + let pid = entry[kCGWindowOwnerPID as String] as! AppPID let b = entry[kCGWindowBounds as String] as! [String: Int] let bounds = CGRect(x: b["X"]!, y: b["Y"]!, width: b["Width"]!, height: b["Height"]!) if (state[pid] == nil) { @@ -103,7 +103,16 @@ class AppDelegate: NSObject, NSApplicationDelegate { return state } - private func setWindowSizes(_ pid: Int32, _ sizes: [WinPos]) { + // MARK: - Restore State (AXUIElement) - + + private func restoreState() { + for (pid, bounds) in self.state[numScreens] ?? [:] { + let spaceWinNums = getWinIds(allSpaces: false) + self.setWindowSizes(pid, bounds.filter{ spaceWinNums.contains($0.0) }) + } + } + + private func setWindowSizes(_ pid: pid_t, _ sizes: [WinPos]) { let win = self.axWinList(pid) guard win.count > 0, win.count == sizes.count else { return @@ -118,9 +127,9 @@ class AppDelegate: NSObject, NSApplicationDelegate { } } - private func axWinList(_ pid: Int32) -> [AXUIElement] { + private func axWinList(_ pid: pid_t) -> [AXUIElement] { let appRef = AXUIElementCreateApplication(pid) - var value: AnyObject? + var value: CFTypeRef? AXUIElementCopyAttributeValue(appRef, kAXWindowsAttribute as CFString, &value) if let windowList = value as? [AXUIElement] { var tmp: [AXUIElement] = [] @@ -137,6 +146,8 @@ class AppDelegate: NSObject, NSApplicationDelegate { } } +// MARK: - Status Bar Icon - + extension NSImage { static var statusIconDots: NSImage { let img = NSImage.init(size: .init(width: 20, height: 20), flipped: true) { @@ -179,6 +190,8 @@ extension NSImage { } } +// MARK: - Main Entry + let delegate = AppDelegate() NSApplication.shared.delegate = delegate NSApplication.shared.run()