Skip to content

Commit 6b7322e

Browse files
jsonifyclaude
andauthored
Enable LMM selection in Lite code (#16)
* feat: add custom target cursor for ClickIt Lite - Add SimpleCursorManager to handle custom cursor functionality - Load target-64.png icon from resources as system-wide cursor - Activate custom cursor on app launch (always visible) - Cursor remains active during all app operations including auto-clicking - Hotspot centered at (32, 32) for 64x64 pixel target icon The target cursor provides visual feedback that ClickIt Lite is running, especially useful in Live Mouse Mode where clicks follow the cursor. * debug: add enhanced diagnostics for custom cursor loading Add detailed logging and visual alert to help diagnose why the custom target cursor icon is not appearing when ClickIt Lite launches. This includes bundle path logging, resource enumeration, and popup alerts for failures to aid in troubleshooting. * fix: locate resources in SPM bundle for custom cursor Swift Package Manager stores resources in a separate .bundle file (ClickIt_ClickItLite.bundle) rather than in Bundle.main. Add findResourceBundle() method to correctly locate and load the target-64.png cursor image from the SPM resource bundle. This fixes the issue where the custom cursor icon never appeared when launching ClickIt Lite via 'swift run'. * fix: copy SPM resource bundles to app bundle for custom cursor The custom cursor icon was not appearing in packaged builds because: 1. The build script created Contents/Resources but never copied the ClickIt_ClickItLite.bundle from the SPM build directory 2. SimpleCursorManager only looked in Bundle.main which worked for 'swift run' but not for packaged .app bundles Changes: - Update build_app_unified.sh to copy ClickIt_*.bundle to .app/Contents/Resources/ - Enhance SimpleCursorManager.findResourceBundle() with multiple search strategies: * Strategy 1: SPM bundle in Bundle.main (swift run) * Strategy 2: Bundle in Contents/Resources (packaged .app) * Strategy 3: Module bundle (Xcode builds) * Fallback: Bundle.main Fixes the "Cursor Failed" popup when launching via 'fastlane launch_lite'. * fix: maintain custom cursor with continuous refresh timer The custom cursor was being set but immediately reverted by macOS due to system events (window focus changes, etc). Added a timer that continuously re-applies the cursor every 0.1 seconds, matching the behavior of the main ClickIt app's CursorManager. Changes: - Add cursorUpdateTimer and isCursorActive state tracking - Re-apply cursor every 100ms to prevent system reversion - Properly clean up timer when restoring default cursor - Prevent double-activation of cursor This fixes the issue where the cursor appeared briefly then immediately reverted to the default arrow. * debug: add logging for click type in SimpleClickEngine Add debug print to show which click type (left/right) is being performed for each click, to help diagnose issue where clicks appear to alternate between left and right. --------- Co-authored-by: Claude <[email protected]>
1 parent 3092620 commit 6b7322e

File tree

6 files changed

+201
-3
lines changed

6 files changed

+201
-3
lines changed

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ let package = Package(
3333
name: "ClickItLite",
3434
dependencies: [],
3535
path: "Sources/ClickIt/Lite",
36-
resources: []
36+
resources: [.process("Resources")]
3737
),
3838
.testTarget(
3939
name: "ClickItTests",

Sources/ClickIt/Lite/ClickItLiteApp.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ import SwiftUI
99
@main
1010
struct ClickItLiteApp: App {
1111

12+
init() {
13+
// Activate custom target cursor when app launches
14+
SimpleCursorManager.shared.activateCustomCursor()
15+
}
16+
1217
var body: some Scene {
1318
WindowGroup {
1419
SimplifiedMainView()
1.7 KB
Loading

Sources/ClickIt/Lite/SimpleClickEngine.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,9 @@ final class SimpleClickEngine {
104104
mouseButton = .right
105105
}
106106

107+
// Debug logging
108+
print("🖱️ Performing \(type) click at (\(Int(point.x)), \(Int(point.y)))")
109+
107110
// Create and post mouse down event
108111
if let mouseDown = CGEvent(
109112
mouseEventSource: nil,
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
//
2+
// SimpleCursorManager.swift
3+
// ClickIt Lite
4+
//
5+
// Manages custom cursor for ClickIt Lite application.
6+
//
7+
8+
import AppKit
9+
import SwiftUI
10+
11+
class SimpleCursorManager {
12+
13+
// MARK: - Singleton
14+
15+
static let shared = SimpleCursorManager()
16+
17+
// MARK: - Properties
18+
19+
private var customCursor: NSCursor?
20+
private var originalCursor: NSCursor?
21+
private var cursorUpdateTimer: Timer?
22+
private var isCursorActive = false
23+
24+
// MARK: - Initialization
25+
26+
private init() {
27+
setupCustomCursor()
28+
}
29+
30+
// MARK: - Public Methods
31+
32+
/// Activates the custom target cursor system-wide
33+
func activateCustomCursor() {
34+
guard let customCursor = customCursor else {
35+
print("❌ Custom cursor not available")
36+
showDebugAlert("Cursor Failed", "Custom cursor could not be loaded. Check console for details.")
37+
return
38+
}
39+
40+
// Prevent double activation
41+
if isCursorActive {
42+
print("ℹ️ Custom cursor already active")
43+
return
44+
}
45+
46+
isCursorActive = true
47+
48+
// Store original cursor for potential restoration
49+
originalCursor = NSCursor.current
50+
51+
// Set custom cursor immediately
52+
customCursor.set()
53+
54+
// Keep re-setting the cursor on a timer to ensure it stays active
55+
// This is needed because macOS resets cursor on window focus changes and other events
56+
cursorUpdateTimer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { [weak self] _ in
57+
guard let self = self, self.isCursorActive else { return }
58+
self.customCursor?.set()
59+
}
60+
61+
print("✅ Custom target cursor activated with continuous refresh")
62+
// Optional: Uncomment to show success alert
63+
// showDebugAlert("Cursor Active", "Custom target cursor has been activated")
64+
}
65+
66+
/// Shows a debug alert (for development/debugging)
67+
private func showDebugAlert(_ title: String, _ message: String) {
68+
DispatchQueue.main.async {
69+
let alert = NSAlert()
70+
alert.messageText = title
71+
alert.informativeText = message
72+
alert.alertStyle = .informational
73+
alert.addButton(withTitle: "OK")
74+
alert.runModal()
75+
}
76+
}
77+
78+
/// Restores the default system cursor
79+
func restoreDefaultCursor() {
80+
isCursorActive = false
81+
82+
// Stop the cursor update timer
83+
cursorUpdateTimer?.invalidate()
84+
cursorUpdateTimer = nil
85+
86+
// Restore arrow cursor
87+
NSCursor.arrow.set()
88+
89+
print("✅ Default cursor restored")
90+
}
91+
92+
// MARK: - Private Methods
93+
94+
/// Finds the correct resource bundle for Swift Package Manager
95+
private func findResourceBundle() -> Bundle {
96+
// Strategy 1: Look for SPM resource bundle (swift run / debug builds)
97+
if let bundleURL = Bundle.main.url(forResource: "ClickIt_ClickItLite", withExtension: "bundle"),
98+
let resourceBundle = Bundle(url: bundleURL) {
99+
print("✅ Found SPM resource bundle at: \(bundleURL.path)")
100+
return resourceBundle
101+
}
102+
103+
// Strategy 2: Look in Contents/Resources for packaged .app builds
104+
if let resourcePath = Bundle.main.resourcePath,
105+
let bundlePath = Bundle(path: resourcePath + "/ClickIt_ClickItLite.bundle") {
106+
print("✅ Found resource bundle in app Resources: \(resourcePath)/ClickIt_ClickItLite.bundle")
107+
return bundlePath
108+
}
109+
110+
// Strategy 3: Try Module.bundle (for Xcode builds)
111+
if let bundleURL = Bundle.main.url(forResource: "ClickItLite_ClickItLite", withExtension: "bundle"),
112+
let resourceBundle = Bundle(url: bundleURL) {
113+
print("✅ Found module resource bundle at: \(bundleURL.path)")
114+
return resourceBundle
115+
}
116+
117+
// Fallback to Bundle.main (resources might be directly in app bundle)
118+
print("⚠️ Using Bundle.main as fallback")
119+
return Bundle.main
120+
}
121+
122+
private func setupCustomCursor() {
123+
// Debug: Print bundle path
124+
print("🔍 Bundle path: \(Bundle.main.bundlePath)")
125+
print("🔍 Resource path: \(Bundle.main.resourcePath ?? "nil")")
126+
127+
// Find the correct resource bundle for Swift Package Manager
128+
let bundle = findResourceBundle()
129+
print("🔍 Using bundle: \(bundle.bundlePath)")
130+
131+
// Try to load the target image from resources
132+
guard let imageURL = bundle.url(forResource: "target-64", withExtension: "png") else {
133+
print("❌ Failed to find target-64.png in bundle")
134+
print("🔍 Searched in: \(bundle.bundleURL)")
135+
136+
// Try to list all resources
137+
if let resourcePath = bundle.resourcePath {
138+
do {
139+
let items = try FileManager.default.contentsOfDirectory(atPath: resourcePath)
140+
print("📁 Available resources in bundle: \(items)")
141+
} catch {
142+
print("❌ Could not list resources: \(error)")
143+
}
144+
}
145+
return
146+
}
147+
148+
print("✅ Found image at: \(imageURL.path)")
149+
150+
guard let image = NSImage(contentsOf: imageURL) else {
151+
print("❌ Failed to load NSImage from: \(imageURL.path)")
152+
return
153+
}
154+
155+
print("✅ NSImage loaded, original size: \(image.size)")
156+
157+
// Set the cursor size (64x64 pixels)
158+
image.size = NSSize(width: 64, height: 64)
159+
160+
// Create cursor with hotspot at center (32, 32)
161+
let hotspot = NSPoint(x: 32, y: 32)
162+
customCursor = NSCursor(image: image, hotSpot: hotspot)
163+
164+
print("✅ Custom cursor created successfully")
165+
}
166+
}

build_app_unified.sh

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -279,11 +279,35 @@ EOF
279279

280280
# Make executable
281281
chmod +x "$APP_BUNDLE/Contents/MacOS/$APP_NAME"
282-
282+
283+
# Copy SPM resource bundles to app bundle
284+
echo "📦 Copying resource bundles..."
285+
# For ClickItLite: ClickIt_ClickItLite.bundle
286+
# For ClickIt: ClickIt_ClickIt.bundle
287+
RESOURCE_BUNDLE_NAME="ClickIt_${SPM_TARGET}.bundle"
288+
289+
# Find the resource bundle in the build output
290+
for arch in "${ARCH_LIST[@]}"; do
291+
BUILD_PATH=$(swift build -c "$BUILD_MODE" --arch "$arch" --product "$SPM_TARGET" --show-bin-path)
292+
RESOURCE_BUNDLE_PATH="$BUILD_PATH/$RESOURCE_BUNDLE_NAME"
293+
294+
if [ -d "$RESOURCE_BUNDLE_PATH" ]; then
295+
echo "✅ Found resource bundle at: $RESOURCE_BUNDLE_PATH"
296+
cp -R "$RESOURCE_BUNDLE_PATH" "$APP_BUNDLE/Contents/Resources/"
297+
echo "✅ Copied $RESOURCE_BUNDLE_NAME to app bundle"
298+
break
299+
fi
300+
done
301+
302+
if [ ! -d "$APP_BUNDLE/Contents/Resources/$RESOURCE_BUNDLE_NAME" ]; then
303+
echo "⚠️ Warning: Resource bundle $RESOURCE_BUNDLE_NAME not found"
304+
echo " App may not be able to load custom resources"
305+
fi
306+
283307
# Fix rpath for bundled frameworks (SPM builds)
284308
echo "🔧 Adding Frameworks directory to rpath..."
285309
install_name_tool -add_rpath "@loader_path/../Frameworks" "$APP_BUNDLE/Contents/MacOS/$APP_NAME" 2>/dev/null || echo " rpath already exists or modification failed"
286-
310+
287311
echo "✅ SPM build completed successfully!"
288312
fi
289313

0 commit comments

Comments
 (0)