4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

macOSで現在の仮想デスクトップ番号と仮想デスクトップの総数をデスクトップ間を移動したときに通知させるJXAスクリプト

Last updated at Posted at 2022-08-07

TL;DR

現在の仮想デスクトップの番号と開いている仮想デスクトップの総数を、デスクトップを跨いだときに、OSの通知欄に通知するアプリをJavascript for Automation (JXA)で作成した。

2022_08_08_03_03_15__.gif

右上に注目すると、確かにデスクトップが切り替わるタイミングで通知のところが変わっている。
2022_08_08_03_37_50.gif

Motivation

OS X(macOS)では、仮想デスクトップ(これは正確にはWindowsでの呼び方?で、OS XではSpaces という)を複数作成でき、3本指で左右にスワイプするか、デフォルトではCtrl+矢印キー(←, →)で移動できる。デスクトップNに何かを割り当てていたり、仮想デスクトップを作りすぎて所望のウィンドウがどこにあるか分からなくなったときに、3本指で上にスワイプするかCtrl+↑でMission Controlを開いて上の方にマウスを持っていって移動する。

image.png

image.png
この作業が面倒で、Mission Controlを開かずとも現在のデスクトップの番号を知りたくなるときがある。そこで今回はJXAで現在の仮想デスクトップの番号を知らせるスクリプトを書いた。

Source

/Library/Preferences/com.apple.spaces.plistからCurrent Spaceを取得する。このときにSLSCopyManagedDisplaySpacesとかいう、SpaceのAPI(非公開)を使う。
defaults read com.apple.spacesで分かるが、com.apple.spaces.plistは、SpacesDisplayConfiguration>Management Data>Monitorsという階層構造になっている。

if(user === ""){currentUserPath = fileManager.homeDirectoryForCurrentUser.fileSystemRepresentation;}
else{currentUserPath = "/Users/" + user;}

これで、userのところに自分のユーザー名を入れなくても動くようになっているはず。

Full Sourceは以下。

var sys = Application("System Events");
sys.includeStandardAdditions = true;
function file_exists(path){
	let fileManager = $.NSFileManager.defaultManager;
	return fileManager.fileExistsAtPath($(path));
}
let user=""//ここはあなたの$Userの名前
let fileManager = $.NSFileManager.defaultManager;
let currentUserPath = ""; 
if(user === ""){currentUserPath = fileManager.homeDirectoryForCurrentUser.fileSystemRepresentation;}
else{currentUserPath = "/Users/" + user;}
var currentSpaceNumber = -100
DELAY_SECONDS = 0.5;
function idle(){
    ObjC.bindFunction('SLSMainConnectionID', ['int', []])
    ObjC.bindFunction('SLSCopyManagedDisplaySpaces', ['id', ['int']])
    let spaces = ObjC.deepUnwrap($.SLSCopyManagedDisplaySpaces($.SLSMainConnectionID()))
    let output = {};
	let dict = $.NSMutableDictionary.alloc.initWithContentsOfFile(currentUserPath + "/Library/Preferences/com.apple.spaces.plist");
    let contents = ObjC.deepUnwrap(dict);
	let monitors = contents['SpacesDisplayConfiguration']['Management Data']['Monitors'];
    if (!file_exists(currentUserPath + "/Library/Preferences/com.apple.spaces.plist")) {
        sys.displayDialog(JSON.stringify({"Spaces_Check": "Required File(s) Not Found"}));
    }
	var activeSpaceID = -1
    activeSpaceID = spaces[0]['Current Space'].ManagedSpaceID; 
    if (activeSpaceID == -1){
        sys.displayDialog("Can't find current space")
    }
    for(let i = 0; i < monitors.length; i++){
        if(monitors[i]['Display Identifier'] == "Main"){
            const currentSpaceID = spaces[0]['Current Space'].ManagedSpaceID;
            let currentSpacePlace = 0;
            let totalSpaces = monitors[i]['Spaces'].length;
            for(let j = 0; j < monitors[i]['Spaces'].length; j++){
                if(currentSpaceID == monitors[i]['Spaces'][j]['ManagedSpaceID']){
                    currentSpacePlace = j + 1;
                }
            }
            output['Main Desktop'] = {};
            output['Main Desktop']['Current Space'] = currentSpacePlace;
            output['Main Desktop']['Total Spaces'] = totalSpaces;
        }
    }
    var app = Application.currentApplication()
    app.includeStandardAdditions = true
    if (currentSpaceNumber>0){
        if (currentSpaceNumber!==output['Main Desktop']['Current Space']){
            app.displayNotification("現在のデスクトップ:"+String(JSON.stringify(output['Main Desktop']['Current Space'], null, 1))+" / "+String(JSON.stringify(output['Main Desktop']['Total Spaces'], null, 1)), {
            })//通知
        }
    }
	currentSpaceNumber = output['Main Desktop']['Current Space']
    return DELAY_SECONDS;
}

通知音はなくした。
一定時間ごとに繰り返し実行するためには、

function idle(){
    return DELAY_SECONDS;
}

で、DELAY_SECONDSごとに繰り返し実行してくれる。今回は0.5秒にしている。←1秒より細かくできているか不明

導入方法

スクリプトエディタを開く
→デフォルトのAppleScriptからJavascriptに変更
→ソースをコピー
→保存するときにファイルフォーマットをアプリに変更
→オプションのハンドラの実行後に終了しないを選択
→保存

image.png

これでアプリができるので、クリックすれば起動。終了したいときはDockの右クリックから終了。

Discussion

仮想デスクトップに関して、フルスクリーンにしたときには反映されない(デスクトップ0になる?)ので注意。これを考慮したものに改良するかもしれない。
また、通知のバナーが多くてうぜぇってなるので、通知のバナーがフェードアウトする時間を(デフォルトでは4~5秒から)1秒とかに変更したくて調べたら、なぜかBig Surだと変更できない。Montereyは不明(8/25/2022現在、できなかった)。Catalina以前だと
defaults write com.apple.notificationcenterui bannerTime -int 1 && killall NotificationCenter\ osascript -e 'display notification "test"'でできるらしい。

最後に

実はTL;DRに挿入されたgifを見れば気づくかもしれないが、Menubarに密かにデスクトップ番号を知らせてくれるメニューバー常駐のアプリを入れている (cf https://github.com/Jaysce/Spaceman)
これで十分なのだが、どうにかAppleScriptまたはJavascriptでできないかなあということで今回やってみた。
defaults read com.apple.spaces | plutil -convert json - -o - | jq '.SpacesDisplayConfiguration."Management Data".Monitors[0] ' これで、Current Spaceが得られるはずが、仮想デスクトップを追加または削除しないと反映されないという仕様があり、かなり手こずった。
JXAもAppleScripも気軽に定期実行ができるしmenubarをいじれるし、Apple信者、Mac使いにとってはなかなか面白い。

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?