Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 37 additions & 13 deletions Example/InterposeKitExample/Sources/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,31 @@ class AppDelegate: NSObject, NSApplicationDelegate {
) -> Hook {
do {
switch example {
case .NSWindow_setTitle:
return try Interpose.prepareHook(
on: self.window,
for: #selector(setter: NSWindow.title),
methodSignature: (@convention(c) (NSWindow, Selector, String) -> Void).self,
hookSignature: (@convention(block) (NSWindow, String) -> Void).self
) { hook in
return { `self`, title in
hook.original(self, hook.selector, "## \(title.uppercased()) ##")
}
}
case .NSWindow_miniaturize:
return try Interpose.prepareHook(
on: self.window,
for: #selector(NSWindow.miniaturize(_:)),
methodSignature: (@convention(c) (NSWindow, Selector, Any?) -> Void).self,
hookSignature: (@convention(block) (NSWindow, Any?) -> Void).self
) { hook in
return { `self`, sender in
let alert = NSAlert()
alert.messageText = "Miniaturization Intercepted"
alert.informativeText = "This window refused to minimize because a hook was applied to\n -[NSWindow miniaturize:]."
alert.runModal()
}
}
case .NSApplication_sendEvent:
return try Interpose.prepareHook(
on: NSApplication.shared,
Expand All @@ -76,17 +101,6 @@ class AppDelegate: NSObject, NSApplicationDelegate {
hook.original(self, hook.selector, event)
}
}
case .NSWindow_setTitle:
return try Interpose.prepareHook(
on: self.window,
for: #selector(setter: NSWindow.title),
methodSignature: (@convention(c) (NSWindow, Selector, String) -> Void).self,
hookSignature: (@convention(block) (NSWindow, String) -> Void).self
) { hook in
return { `self`, title in
hook.original(self, hook.selector, "## \(title.uppercased()) ##")
}
}
case .NSMenuItem_title:
return try Interpose.prepareHook(
on: NSMenuItem.self,
Expand All @@ -99,8 +113,18 @@ class AppDelegate: NSObject, NSApplicationDelegate {
return "## \(title) ##"
}
}
case .NSColor_controlAccentColor:
fatalError("Not implemented")
case .NSColor_labelColor:
return try Interpose.prepareHook(
on: NSColor.self,
for: #selector(getter: NSColor.labelColor),
methodKind: .class,
methodSignature: (@convention(c) (NSColor.Type, Selector) -> NSColor).self,
hookSignature: (@convention(block) (NSColor.Type) -> NSColor).self
) { hook in
return { `self` in
return self.systemPink
}
}
}
} catch {
fatalError("\(error)")
Expand Down
15 changes: 6 additions & 9 deletions Example/InterposeKitExample/Sources/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,13 @@ struct ContentView: View {
.labelsHidden()
.padding(.leading, 20)
} label: {
Group {
Text(example.selector)
.monospaced()

Text(example.description)
.font(.subheadline)
}
.opacity(example == .NSColor_controlAccentColor ? 0.5 : 1)
Text(example.selector)
.monospaced()
.foregroundStyle(Color(NSColor.labelColor))

Text(example.description)
.font(.subheadline)
}
.disabled(example == .NSColor_controlAccentColor)
}
}
}
Expand Down
35 changes: 21 additions & 14 deletions Example/InterposeKitExample/Sources/HookExample.swift
Original file line number Diff line number Diff line change
@@ -1,45 +1,52 @@
enum HookExample: CaseIterable {
case NSApplication_sendEvent
case NSWindow_setTitle
case NSWindow_miniaturize
case NSApplication_sendEvent
case NSMenuItem_title
case NSColor_controlAccentColor
case NSColor_labelColor
}

extension HookExample {
var selector: String {
switch self {
case .NSApplication_sendEvent:
return "-[NSApplication sendEvent:]"
case .NSWindow_setTitle:
return "-[NSWindow setTitle:]"
case .NSWindow_miniaturize:
return "-[NSWindow miniaturize:]"
case .NSApplication_sendEvent:
return "-[NSApplication sendEvent:]"
case .NSMenuItem_title:
return "-[NSMenuItem title]"
case .NSColor_controlAccentColor:
return "+[NSColor controlAccentColor]"
case .NSColor_labelColor:
return "+[NSColor labelColor]"
}
}

var description: String {
switch self {
case .NSApplication_sendEvent:
return """
An object hook on the shared NSApplication instance that logs all events passed \
through sendEvent(_:).
"""
case .NSWindow_setTitle:
return """
An object hook on the main NSWindow that uppercases the title and wraps it with \
decorative markers whenever it’s set. This can be tested using the text field below.
"""
case .NSWindow_miniaturize:
return """
An object hook on the main NSWindow that intercepts miniaturization and shows \
an alert instead of minimizing the window.
"""
case .NSApplication_sendEvent:
return """
A class hook on NSApplication that logs all events passed through sendEvent(_:).
"""
case .NSMenuItem_title:
return """
A class hook on NSMenuItem that wraps all menu item titles with decorative markers, \
visible in the main menu and the text field’s context menu.
"""
case .NSColor_controlAccentColor:
case .NSColor_labelColor:
return """
A class hook that overrides the system accent color by hooking the corresponding \
class method on NSColor. (Not implemented.)
A class hook that overrides the standard label color by hooking the corresponding \
class method on NSColor. Affects text in this window and menus.
"""
}
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/InterposeKit/Deprecated/NSObject+Deprecated.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ extension NSObject {
_ build: @escaping HookBuilder<MethodSignature, HookSignature>
) throws -> Hook {
let hook = try Hook(
target: .class(self),
target: .class(self, .instance),
selector: selector,
build: build
)
Expand Down
26 changes: 12 additions & 14 deletions Sources/InterposeKit/Hooks/Hook.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,14 @@ public final class Hook {
}

switch target {
case .class(let `class`):
case let .class(`class`, methodKind):
return ClassHookStrategy(
class: `class`,
methodKind: methodKind,
selector: selector,
makeHookIMP: makeHookIMP
)
case .object(let object):
case let .object(object):
return ObjectHookStrategy(
object: object,
selector: selector,
Expand Down Expand Up @@ -222,7 +223,8 @@ extension Hook: CustomDebugStringConvertible {
case .failed: description += "Failed"
}

description.append(" hook for -[\(self.class) \(self.selector)]")
let symbolPrefix = self.scope.methodKind.symbolPrefix
description.append(" hook for \(symbolPrefix)[\(self.class) \(self.selector)]")

if case .object(let object) = self.scope {
description.append(" on \(Unmanaged.passUnretained(object).toOpaque())")
Expand All @@ -236,16 +238,6 @@ extension Hook: CustomDebugStringConvertible {
}
}

public enum HookScope {

/// The scope that targets all instances of the class.
case `class`

/// The scope that targets a specific instance of the class.
case object(NSObject)

}

public enum HookState: Equatable {

/// The hook is ready to be applied.
Expand All @@ -259,7 +251,13 @@ public enum HookState: Equatable {

}

/// Represents the target of a hook operation—either a class type or a specific object instance.
internal enum HookTarget {
case `class`(AnyClass)

/// A hook targeting a method defined on a class, either an instance method or a class method.
case `class`(AnyClass, MethodKind)

/// A hook targeting a method on a specific object instance.
case object(NSObject)

}
25 changes: 25 additions & 0 deletions Sources/InterposeKit/Hooks/HookScope.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import ObjectiveC

public enum HookScope {

/// The scope that targets a method on a class type (instance or class method).
case `class`(MethodKind)

/// The scope that targets a specific object instance.
case object(NSObject)

}

extension HookScope {

/// Returns the kind of the method targeted by the hook scope.
public var methodKind: MethodKind {
switch self {
case .class(let methodKind):
return methodKind
case .object:
return .instance
}
}

}
Loading