From c8a7bdfbeb832e596347f15057802588978512d2 Mon Sep 17 00:00:00 2001 From: Ian Ynda-Hummel Date: Mon, 7 Aug 2023 21:09:05 -0400 Subject: [PATCH] Implement more robust window focus mechanism --- Amethyst.xcodeproj/project.pbxproj | 26 +++++++------- Amethyst/Model/Window.swift | 57 ++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 14 deletions(-) diff --git a/Amethyst.xcodeproj/project.pbxproj b/Amethyst.xcodeproj/project.pbxproj index b9007fb4..db089fc7 100644 --- a/Amethyst.xcodeproj/project.pbxproj +++ b/Amethyst.xcodeproj/project.pbxproj @@ -41,6 +41,7 @@ 402DB6F21742E41A00D1C936 /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 402DB6F01742E41A00D1C936 /* Credits.rtf */; }; 402DB6F81742E41A00D1C936 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 402DB6F61742E41A00D1C936 /* MainMenu.xib */; }; 402DB6FF1742E44E00D1C936 /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 402DB6FE1742E44E00D1C936 /* Carbon.framework */; }; + 402F6FA62A81C9E30036B512 /* SkyLight.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 402F6FA52A81C9E30036B512 /* SkyLight.framework */; }; 403E1A2A2337173600DB7B2A /* FloatingLayoutTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 403E1A292337173600DB7B2A /* FloatingLayoutTests.swift */; }; 403E1A2C233719E500DB7B2A /* TallLayoutTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 403E1A2B233719E500DB7B2A /* TallLayoutTests.swift */; }; 4045416F268FFDA000861BE8 /* CustomLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4045416E268FFDA000861BE8 /* CustomLayout.swift */; }; @@ -124,19 +125,6 @@ }; /* End PBXContainerItemProxy section */ -/* Begin PBXCopyFilesBuildPhase section */ - 40B42F3225315F410000380A /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 12; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - /* Begin PBXFileReference section */ 1A4B46EA20AA7717003D5110 /* NSTableView+Amethyst.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSTableView+Amethyst.swift"; sourceTree = ""; }; 2A6D9A4025E5D24D006A36B5 /* AppManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppManager.swift; sourceTree = ""; }; @@ -178,6 +166,7 @@ 402DB6EB1742E41A00D1C936 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 402DB6F11742E41A00D1C936 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; name = en; path = en.lproj/Credits.rtf; sourceTree = ""; }; 402DB6FE1742E44E00D1C936 /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = System/Library/Frameworks/Carbon.framework; sourceTree = SDKROOT; }; + 402F6FA52A81C9E30036B512 /* SkyLight.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SkyLight.framework; path = /System/Library/PrivateFrameworks/SkyLight.framework; sourceTree = ""; }; 40378E22238F39B900D11E22 /* Amethyst.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Amethyst.entitlements; sourceTree = ""; }; 403E1A292337173600DB7B2A /* FloatingLayoutTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingLayoutTests.swift; sourceTree = ""; }; 403E1A2B233719E500DB7B2A /* TallLayoutTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TallLayoutTests.swift; sourceTree = ""; }; @@ -271,6 +260,7 @@ 40C3F91E1BD1B22E00F58660 /* Security.framework in Frameworks */, 40CF37C029B440A100CDB07A /* ArgumentParser in Frameworks */, ED989E6BAE0E8D035277478A /* Pods_Amethyst.framework in Frameworks */, + 402F6FA62A81C9E30036B512 /* SkyLight.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -386,6 +376,7 @@ 402DB6E01742E41A00D1C936 /* Frameworks */ = { isa = PBXGroup; children = ( + 402F6FA52A81C9E30036B512 /* SkyLight.framework */, 40C3F9241BD1B36C00F58660 /* libz.tbd */, 40C3F9221BD1B35E00F58660 /* libc++.tbd */, 40C3F91A1BD1B22E00F58660 /* Security.framework */, @@ -608,7 +599,6 @@ 402DB6DB1742E41A00D1C936 /* Frameworks */, 402DB6DC1742E41A00D1C936 /* Resources */, D47F3405558308F2EB634A66 /* [CP] Embed Pods Frameworks */, - 40B42F3225315F410000380A /* Embed Frameworks */, ); buildRules = ( ); @@ -1114,6 +1104,10 @@ PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Amethyst/Amethyst-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SYSTEM_FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks", + ); WRAPPER_EXTENSION = app; }; name = Debug; @@ -1152,6 +1146,10 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Amethyst/Amethyst-Bridging-Header.h"; + SYSTEM_FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks", + ); WRAPPER_EXTENSION = app; }; name = Release; diff --git a/Amethyst/Model/Window.swift b/Amethyst/Model/Window.swift index 7303537d..af5b0743 100644 --- a/Amethyst/Model/Window.swift +++ b/Amethyst/Model/Window.swift @@ -9,6 +9,19 @@ import Foundation import Silica +// swiftlint:disable identifier_name +@_silgen_name("GetProcessForPID") @discardableResult +func GetProcessForPID(_ pid: pid_t, _ psn: inout ProcessSerialNumber) -> OSStatus + +@_silgen_name("_SLPSSetFrontProcessWithOptions") @discardableResult +func _SLPSSetFrontProcessWithOptions(_ psn: inout ProcessSerialNumber, _ wid: UInt32, _ mode: UInt32) -> CGError + +@_silgen_name("SLPSPostEventRecordTo") @discardableResult +func SLPSPostEventRecordTo(_ psn: inout ProcessSerialNumber, _ bytes: inout UInt8) -> CGError + +let kCPSUserGenerated: UInt32 = 0x200 +// swiftlint:enable identifier_name + /// Generic protocol for objects acting as windows in the system. protocol WindowType: Equatable { associatedtype Screen: ScreenType @@ -270,8 +283,52 @@ extension AXWindow: WindowType { - Returns: `true` if the window was successfully focused, `false` otherwise. + + - Description: + What a mess. See: https://github.com/Hammerspoon/hammerspoon/issues/370#issuecomment-545545468 */ @discardableResult override func focus() -> Bool { + var pid = self.pid() + var wid = self.cgID() + var psn = ProcessSerialNumber() + let status = GetProcessForPID(pid, &psn) + + guard status == noErr else { + return false + } + + var cgStatus = _SLPSSetFrontProcessWithOptions(&psn, wid, kCPSUserGenerated) + + guard cgStatus == .success else { + return false + } + + var bytes1 = [UInt8](repeating: 0, count: 0xf8) + bytes1[0x04] = 0xF8 + bytes1[0x08] = 0x01 + bytes1[0x3a] = 0x10 + memcpy(&bytes1[0x3c], &wid, MemoryLayout.size) + memset(&bytes1[0x20], 0xFF, 0x10) + cgStatus = bytes1.withUnsafeMutableBufferPointer { pointer in + return SLPSPostEventRecordTo(&psn, &pointer.baseAddress!.pointee) + } + guard cgStatus == .success else { + return false + } + + var bytes2 = [UInt8](repeating: 0, count: 0xf8) + bytes2[0x04] = 0xF8 + bytes2[0x08] = 0x02 + bytes2[0x3a] = 0x10 + memcpy(&bytes2[0x3c], &wid, MemoryLayout.size) + memset(&bytes2[0x20], 0xFF, 0x10) + cgStatus = bytes2.withUnsafeMutableBufferPointer { pointer in + return SLPSPostEventRecordTo(&psn, &pointer.baseAddress!.pointee) + } + guard cgStatus == .success else { + return false + } + guard super.focus() else { return false }