Shujima Blog

Apple製品,技術系の話をするブログ

macOS Cocoa Appでマウス操作を常に受け取る

環境

  • macOS 10.15.5
  • Xcode 10.2.1
  • Swift 5.0.1

この記事で作ったプロジェクトを前提にしています.

www.shujima.work

なんの変哲も無いCocoa Appにボタン,ラベル × 2,テキストフィールド × 2を配置したものです.

以前の記事ではフォアグラウンドの時のみマウスの値を受け取れました.

www.shujima.work

この記事ではこのプログラムを改造してバックグラウンドでも受け取れるようにします.

今回は以前の記事のおまけ(id考慮編)の手前にあるプログラムを使います.以前の記事のおまけ(id考慮編)のプログラムを作った人は元に戻しておいてください.

なお,私はXcode使用2日目の初心者です.ツッコミどころがあればコメント,お問い合わせなどをお願いします.温かい目で見てください.

この記事でやることの説明

本記事で作成するアプリケーションは

  • マウスのカーソル位置をアプリ内外で取得する
  • マウスを特定の座標に強制的に移動させる

ことができます.

Xcodeプロジェクトの設定

今回作成するアプリケーションはユーザのマウス操作を取得するものです.

本プログラムは断じて違いますが,例えば取得対象がキーボードなどであった場合,パスワードを盗み取るなどの用途のアプリケーションを作成できてしまいます.

そこでmacOSではユーザが許可を与えない限り,そういった情報を取得できないような仕組みが備わっています.

今回作成するプログラムでは追加でそれらを設定する必要があります.

参考

まず保存(⌘+s)したのちにXcodeを閉じてください(⌘+Q).

またビルドして実行した自分のアプリケーションも閉じてください(Xcodeを閉じただけでは停止しません,ドックに下記があったら右クリックして終了を選択してください). f:id:masa_flyu:20190707124813j:plain

Xcodeにコンピュータの制御を許可させる

作成するアプリケーションはコンピュータの制御を明示的に許可させる必要があります.

ただし,開発段階においてはXcodeに許可を与えることでその代わりとできます.

f:id:masa_flyu:20190707125449j:plain

システム環境設定でセキュリティとプライバシーを開きます.

f:id:masa_flyu:20190707125524j:plain

鍵をクリックします.

f:id:masa_flyu:20190707125536j:plain

パスワードを入力します.

f:id:masa_flyu:20190707125547j:plain

Xcodeを選択し「開く」を選択します.

f:id:masa_flyu:20190707125605j:plain

Xcodeにチェックがついていれば完了です.

App Sandboxの設定

App Sandboxの設定をオフにしなければならないようです(私もよくわかってない).

Swift:キー入力・マウスクリック・ドラッグ・座標移動・ホイール入力を強制的に発動させる - Qiita

設定を変更する前にかならず製作中のアプリを実行していないことを確認してください.実行中に設定を変更することはよくありません.

f:id:masa_flyu:20190707131203j:plain

Xcodeを起動して,左のメニューから「(プロジェクト名).entitlements」を開きます.

f:id:masa_flyu:20190707131227j:plain

App Sandboxの項目のValueが初期設定ではYESになっていると思います.これを選択して,

f:id:masa_flyu:20190707131234j:plain

NOに切り替えます.これでOKです.

プログラム

準備に大変手間取りました.しかしながら,プログラムの変更はわずかに1行です.

viewDidLoad()内にイベントの登録をもうひとつ追加します.

import Cocoa

class ViewController: NSViewController {
    @IBOutlet weak var textfield_x: NSTextField!
    @IBOutlet weak var textfield_y: NSTextField!
    @IBOutlet weak var button_move: NSButton!
    @IBOutlet weak var label_left: NSTextField!
    @IBOutlet weak var label_right: NSTextField!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Do any additional setup after loading the view.
        label_left.stringValue = "Hello World!"
        //マウスが動いた時にイベントを生成し,MouseMoved()関数を呼ぶように設定
        NSEvent.addLocalMonitorForEvents(matching: .mouseMoved){ (event) -> NSEvent in
            self.MouseMoved(with: event) //MouseMoved()関数を呼ぶ
            return event
        }
        //↓1行追記
        NSEvent.addGlobalMonitorForEvents(matching: .mouseMoved, handler:self.MouseMoved)
    }
    
    //マウスが動いた時に呼ばれるイベント関数
    func MouseMoved(with event: NSEvent) {
        //左のラベルにマウス座標を書き込み
        label_left.stringValue = "X = \( event.locationInWindow.x ) \r\n"
            + "Y = \( event.locationInWindow.y ) \r\n"
    }


    override var representedObject: Any? {
        didSet {
        // Update the view, if already loaded.
        }
    }
    
    //ボタンが押されたときに呼ばれるイベント関数
    @IBAction func button_move_clicked(_ sender: Any) {
        //右のラベルにテキストフィールドの値を書き込み
        label_right.stringValue = "X = \( textfield_x.stringValue ) \r\n"
            + "Y = \( textfield_y.stringValue ) \r\n"
        //マウスを強制的に移動
        CGDisplayMoveCursorToPoint(0,CGPoint(x: Double(textfield_x.floatValue), y: Double(textfield_y.floatValue) ) )
    }
}

実行すると今までと同じように動くはずです.

ただし,他のアプリを選択した状態でもマウスの座標を検出し続けることがわかります.

説明

NSEvent.addGlobalMonitorForEvents(matching: .mouseMoved, handler:self.MouseMoved)

NSEvent.addGlobalMonitorForEvents()を用いています.

Globalとつくとおり,常にどこからでもイベントに反応できるようになります.

「.mouseMoved」をイベントの対象として登録し,呼び出し先の関数として「MouseMoved()」を指定しています.

たったこれだけです.

当ブログをご利用いただく際には免責事項をお読みください。