CahtGPTを使ってMacのネイティブアプリを作ってみよう〜その3〜

プログラミング

クリックできてなさそう問題を解決すべく、今回は基本GPTを利用するものの、ネット情報も駆使していこうと思います。

実行と終了でアイコンの色を分けよう

まずは見た目がわかりにくいので、実行中の状態と中止中の状態でアイコンの色を変えたいと思います。GPTには次のようにお願いしました。「以下のプログラムで開始状態と終了状態でメニューバーのアイコンの色を変えてください。」すると以下のコードが返ってきました。終了状態では灰色、開始状態では青色にアイコンが変わるようになりました。

import SwiftUI
import Cocoa

@main
struct MenuBarApp: App {
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

var body: some Scene {
Settings {
EmptyView()
}
}
}

class AppDelegate: NSObject, NSApplicationDelegate {
var statusItem: NSStatusItem?
var popover: NSPopover?
var timer: Timer?

func applicationDidFinishLaunching(_ notification: Notification) {
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)

if let button = statusItem?.button {
button.image = NSImage(systemSymbolName: “star.fill”, accessibilityDescription: nil)?.tinted(with: .gray)
}

let menu = NSMenu()

let startItem = NSMenuItem(title: “開始”, action: #selector(startTimer), keyEquivalent: “S”)
menu.addItem(startItem)

let stopItem = NSMenuItem(title: “終了”, action: #selector(stopTimer), keyEquivalent: “E”)
menu.addItem(stopItem)

statusItem?.menu = menu
}

@objc func startTimer() {
stopTimer() // 既存のタイマーがある場合は停止
timer = Timer.scheduledTimer(withTimeInterval: 3, repeats: true) { _ in
self.simulateMouseClick()
}
updateIconColor(isRunning: true)
print(“Timer started”)
}

@objc func stopTimer() {
timer?.invalidate()
timer = nil
updateIconColor(isRunning: false)
print(“Timer stopped”)
}

func simulateMouseClick() {
let loc = NSEvent.mouseLocation
print(“Simulating mouse click at \(loc)”) // クリック位置をログに記録
let clickEvent = CGEvent(mouseEventSource: nil, mouseType: .leftMouseDown, mouseCursorPosition: loc, mouseButton: .left)
clickEvent?.post(tap: .cghidEventTap)

let releaseEvent = CGEvent(mouseEventSource: nil, mouseType: .leftMouseUp, mouseCursorPosition: loc, mouseButton: .left)
releaseEvent?.post(tap: .cghidEventTap)
}

func updateIconColor(isRunning: Bool) {
if let button = statusItem?.button {
let color = isRunning ? NSColor.systemBlue : NSColor.gray
button.image = NSImage(systemSymbolName: “star.fill”, accessibilityDescription: nil)?.tinted(with: color)
}
}
}

extension NSImage {
func tinted(with color: NSColor) -> NSImage {
guard isTemplate else { return self }
let image = self.copy() as! NSImage
image.lockFocus()
color.set()
let imageRect = NSRect(origin: .zero, size: image.size)
imageRect.fill(using: .sourceAtop)
image.unlockFocus()
image.isTemplate = false
return image
}
}

struct ContentView: View {
@State private var counter = 0

var body: some View {
VStack {
Text(“Counter: \(counter)”)
.font(.largeTitle)
.padding()

Button(action: {
self.counter += 1
self.simulateMouseClick()
}) {
Text(“Click Me”)
.font(.title)
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(10)
}
}
}

func simulateMouseClick() {
let loc = NSEvent.mouseLocation
print(“Simulating mouse click at \(loc)”) // クリック位置をログに記録
let clickEvent = CGEvent(mouseEventSource: nil, mouseType: .leftMouseDown, mouseCursorPosition: loc, mouseButton: .left)
clickEvent?.post(tap: .cghidEventTap)

let releaseEvent = CGEvent(mouseEventSource: nil, mouseType: .leftMouseUp, mouseCursorPosition: loc, mouseButton: .left)
releaseEvent?.post(tap: .cghidEventTap)
}
}

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

マウスがクリックされていないように見える問題への対応

その1ではクリックが目検でも確認できていたので、その時のソースと比較してみます。simulateMouseClick()を比較してみましたが、ログのための処理にしか差分がありません。GPTに色々聞いてみて、様々なソースを試しましたが、どれもクリックがされず。

GPTでなくてネットで調べてみることにしましたが、ダメな原因がなかなかわからず・・・。サンドボックスだとできないという書き込みを見かけたのですが、サンドボックスは有効にしていなかったですし、これは手詰まりでしょうか。

過去に動いた実績があるので、何かしら条件があるんだと思うのですがわからず。新しく環境を作り直してそちらで実装してみましょうかね。