15インチMacBook Air用の充電アダプタの位置合わせ用「チャージガイド・シール」を発売しました。
→ 販売ページ
注文が発生したらプリンターで印刷するので、在庫の山で部屋が埋もれるといったことはないはずです。
15インチMacBook Air用の充電アダプタの位置合わせ用「チャージガイド・シール」を発売しました。
→ 販売ページ
注文が発生したらプリンターで印刷するので、在庫の山で部屋が埋もれるといったことはないはずです。
たまにMac用のWebブラウザが新登場しているのですが、ほとんどがGoogle Chromiumの互換といいますか、Chromiumのぱちも……といいますか、ほぼそのまま利用して作られていたりして、そのおかげで割としっかりとAppleScript対応機能が実装されたまま多数のWebブラウザがリリースされるというメリットがありました。
そんな中、たまたま見つけた「Orion」。Kagiが作ったWebブラウザとのことです。
AppleScriptから見ると、見たこともないような挙動をするWebブラウザです。
用語辞書の傾向はChrome系というよりも、Safariからとんがったコマンドを削除したようなたたずまい。
そして、必要な機能がついていなくて、割と「何これ?」と首をひねってしまうような出来です。
SafariもGoogle Chromeも、現在のタブを指し示すactive tabとかcurrent tabといった予約語でアクセスできるのですが、それができません。一応、Windowのプロパティとしてcurrent tabがあるのですが、このtabからIDとかIndexが取得できません。
do JavaScriptコマンドがあるので、ちょっとOrion固有の制限が加わりつつもJavaScriptコマンドを実行してWebのソースコードやテキストを取得できたりはしつつ、Webブラウザ上で選択中の要素を取得できなかったりします。
そして、肝心のdo JavaScriptコマンドでtabを指定する必要があるものの、current tabが何であるかを特定できず……仕方ないので、tabから取得できるURLをもとにどのtabがcurrent tabであるかを探し回ってなんとか。
windowの新規作成は、documentを作成することで行うことに。
人間らしく、というレベルはクリアできていないような気がします。久しぶりに、挙動がここまで不完全というか不自然なWebブラウザを見かけました。
AppleScript名:Orionのアプリのプロパティを取得.scpt |
tell application "Orion" properties –> {frontmost:false, class:application, name:"Orion", version:"0.99"} end tell |
AppleScript名:OrionでWindowのtitleを取得.scpt |
–同じURLを持つTabが複数あった場合には処理が破綻する tell application "Orion" tell window 1 set aURL to URL of current tab set tList to URL of every tab set tInd to retIndexNumInArray(tList, aURL) of me if tInd = 0 then return tell tab tInd –current tabのindexを求める方法がないのに、current tabを指定できないし、具体的にtab idを指定しないといけない set aRes to do JavaScript "document.querySelector(’title’).textContent;" end tell end tell end tell –1Dimensionリスト中のシーケンシャルサーチ on retIndexNumInArray(aList, aTarget) set aCount to 1 set hitF to false repeat with i in aList set j to contents of i if aTarget = j then return aCount set aCount to aCount + 1 end repeat if hitF = false then return 0 end retIndexNumInArray |
AppleScript名:Orionで新規URLをオープン(新規Tab).scpt |
tell application "Orion" open location "https://www.apple.com/jp" –新規Tabでオープン end tell |
AppleScript名:Orionの最前面のWindowのcurrent tabで新規URLをオープン.scpt |
tell application "Orion" set URL of current tab of window 1 to "https://www.apple.com/jp" end tell |
AppleScript名:Orionでcurrent tabに対してJavaScript実行してソースを取得.scpt |
–同じURLを持つTabが複数あった場合には処理が破綻する tell application "Orion" tell window 1 set aURL to URL of current tab set tList to URL of every tab set tInd to retIndexNumInArray(tList, aURL) of me if tInd = 0 then return tell tab tInd –current tabのindexを求める方法がないのに、current tabを指定できないし、具体的にtab idを指定しないといけない set aRes to do JavaScript "document.getElementsByTagName(’html’)[0].innerHTML" end tell end tell end tell –1Dimensionリスト中のシーケンシャルサーチ on retIndexNumInArray(aList, aTarget) set aCount to 1 set hitF to false repeat with i in aList set j to contents of i if aTarget = j then return aCount set aCount to aCount + 1 end repeat if hitF = false then return 0 end retIndexNumInArray |
AppleScript名:Orion、WindowのCloseができるが、makeできない.scpt |
tell application "Orion" close every window end tell |
AppleScript名:Orionで新規document作成.scpt |
tell application "Orion" make new document end tell |
AppleScript名:新規tabの作成.scpt |
tell application "Orion" tell window 1 make new tab with properties {URL:"http://www.apple.com/jp"} end tell end tell |
背景が透過しているPNG画像で、余計な余白部分を自動でトリミングするAppleScriptの試作品です。実際に画像のトリミングを行います。1024×1024ぐらいまでの大きさのPNG画像が想定しているターゲットで、4Kとか8Kぐらいの画像は想定していません。
既存のCやObjective-Cなどの言語で書かれたトリミングのプログラムは、1ドットずつスキャンしていく方式だったので、そのままの処理をインタプリタ方式のAppleScriptで行うと、遅くて話になりません。
そこで、ブロックごとに分割して、ブロック単位で描画部分がないか(同サイズの透明イメージと比較して同じかどうか)チェックするという方式を採用しました。これは、画像がすべて空白かどうかをチェックする処理の原理を応用したものです。
▲テストに利用したPNG画像(196 × 309 ピクセル)
前バージョン(v2)を実際の用途に利用してみたら、トンでもなく使い物にならなかったので、プログラムのおかしなところを見直して、2段階で処理を行うことでさらに精度を上げてみたのが、このv3です。
画像全体を16×16のブロックに分割して、個別に透明なエリアか描画されているエリアかを判定します(パス1)。
そのままだと処理がラフすぎるので、外周部のブロックをさらに16分割し、外周部だけ透明ブロックかどうかの判定を行います(パス2)。
このテスト画像に対しては、それなりの結果が出ています。前バージョンと比べると大幅に性能向上してます。実戦投入してみるとまた別の感想が出てくるかもしれませんが……。
▲実戦投入。SF Symbolsをコピーした内容を指定色で画像化してクリップボードに入れるAppleScriptで使用してみたら、けっこういい感じに切り抜いてくれました
プログラムもまだ試作品段階なので、無駄な処理が多そうです。パス1とパス2で共通部分が多いので、見直すとメイン部分の長さが半分ぐらいになりそうですが、試作品段階なのであんまり短くしすぎると内容を理解しにくくなりそうで。
AppleScript名:余白トリミング実験 v3.scptd |
— – Created by: Takaaki Naganoya – Created on: 2024/11/21 — – Copyright © 2024 Piyomaru Software, All Rights Reserved — use AppleScript use framework "Foundation" use framework "AppKit" use scripting additions property NSData : a reference to current application’s NSData property |NSURL| : a reference to current application’s |NSURL| property NSColor : a reference to current application’s NSColor property NSString : a reference to current application’s NSString property NSImage : a reference to current application’s NSImage property NSScreen : a reference to current application’s NSScreen property NSBezierPath : a reference to current application’s NSBezierPath property NSPNGFileType : a reference to current application’s NSPNGFileType property NSMutableArray : a reference to current application’s NSMutableArray property NSBitmapImageRep : a reference to current application’s NSBitmapImageRep –画像分割ブロックサイズ property imgDivXStep : 16 property imgDivYStep : 16 set aPOSIXpath to POSIX path of (choose file with prompt "PNG画像を選択") –時間計測用 set a1Dat to current application’s NSDate’s timeIntervalSinceReferenceDate() –画像自動切り抜き set cImage to autoTrimImageFromPath(aPOSIXpath) of me –時間計測用 set b1Dat to current application’s NSDate’s timeIntervalSinceReferenceDate() set c1Dat to b1Dat – a1Dat log c1Dat –ファイル保存 set outPath to POSIX path of (choose file name with prompt "PNG画像の保存ファイル名を指定してください(拡張子入力必須)") set sRes to saveImageRepAtPathAsPNG(cImage, outPath) of me –POSIX pathで示したPNG画像(多分)の外周部の余白を自動トリミング on autoTrimImageFromPath(aPOSIXpath) set aURL to |NSURL|’s fileURLWithPath:aPOSIXpath set aImage to NSImage’s alloc()’s initWithContentsOfURL:aURL return autoTrimImage(aImage) of me –return NSImage end autoTrimImageFromPath –NSImageの外周部の余白を自動トリミング on autoTrimImage(aImage) script spd property outList : {} –Pass 1用 property pass2Res : {} –Pass 2用 end script –PASS 1 16 x 16 Whole Area Scan set sRes to aImage’s |size|() –> example: {width:196.0, height:309.0} set aHeight to height of sRes set aWidth to width of sRes set xBlockSize to (aWidth div imgDivXStep) set yBlockSize to (aHeight div imgDivYStep) –transparent block sample for PASS 1 set blankImg1 to makeNSImageWithFilledWithColor(xBlockSize, yBlockSize, current application’s NSColor’s clearColor()) of me set blankBit1 to blankImg1’s TIFFRepresentation() set (outList of spd) to {} repeat with yy from 0 to (aHeight – yBlockSize) by yBlockSize set oneLine to "" repeat with xx from 0 to (aWidth – xBlockSize) by xBlockSize –crop a part of image and check transparent set tmpImg to (my cropNSImageBy:{xx, yy, xBlockSize, yBlockSize} fromImage:aImage) set tmpBit to tmpImg’s TIFFRepresentation() set chkTrans to (blankBit1’s isEqualToData:tmpBit) as boolean if chkTrans = false then –Not transparent block set the end of (outList of spd) to {xPos:xx, yPos:yy} end if end repeat end repeat –最大値、最小値を取得 set nArray to (NSMutableArray’s arrayWithArray:(outList of spd)) set xMin to calcMin("xPos", nArray) of me set xMax to calcMax("xPos", nArray) of me set yMin to calcMin("yPos", nArray) of me set yMax to calcMax("yPos", nArray) of me –PASS 2: Edge Blocks 64 x 64 scan Most Edge blocks only –transparent block sample for PASS 2 (1/4 size) set blankImg2 to makeNSImageWithFilledWithColor(xBlockSize div 4, yBlockSize div 4, current application’s NSColor’s clearColor()) of me set blankBit2 to blankImg2’s TIFFRepresentation() set aArray to current application’s NSArray’s arrayWithArray:(outList of spd) set westList to filterRecListByLabel1(aArray, "xPos == %@", xMin) of me as list set eastList to filterRecListByLabel1(aArray, "xPos == %@", xMax) of me as list set northLIst to filterRecListByLabel1(aArray, "yPos == %@", yMax) of me as list set southLIst to filterRecListByLabel1(aArray, "yPos == %@", yMin) of me as list set pass2List to westList & eastList & northLIst & southLIst set (pass2Res of spd) to {} repeat with i in pass2List set origPX to xPos of i set origPY to yPos of i repeat with y from 0 to 3 repeat with x from 0 to 3 set tmpPX to origPX + (xBlockSize div 4) * x set tmpPY to origPY + (yBlockSize div 4) * y set tmpImg to (my cropNSImageBy:{tmpPX, tmpPY, xBlockSize div 4, yBlockSize div 4} fromImage:aImage) set tmpBit2 to tmpImg’s TIFFRepresentation() set chkTrans to (blankBit2’s isEqualToData:tmpBit2) as boolean if chkTrans = false then –Not transparent block set the end of (pass2Res of spd) to {xPos:tmpPX, yPos:tmpPY} end if end repeat end repeat end repeat set bArray to current application’s NSArray’s arrayWithArray:(pass2Res of spd) set x2Min to calcMin("xPos", bArray) of me set x2Max to calcMax("xPos", bArray) of me set y2Min to calcMin("yPos", bArray) of me set y2Max to calcMax("yPos", bArray) of me –オリジナル画像を切り抜きして返す(NSImage) return my cropNSImageTo:{x2Min, y2Min, x2Max + (xBlockSize div 4), y2Max + (yBlockSize div 4)} fromImage:aImage end autoTrimImage on calcMin(aLabel as string, nArray) set aStr to "@min." & aLabel set aRes to nArray’s valueForKeyPath:aStr return aRes as anything end calcMin on calcMax(aLabel as string, nArray) set aStr to "@max." & aLabel set aRes to nArray’s valueForKeyPath:aStr return aRes as anything end calcMax –指定サイズの画像を作成し、指定色で塗って返す on makeNSImageWithFilledWithColor(aWidth, aHeight, fillColor) set retinaF to 1.0 —(NSScreen’s mainScreen()’s backingScaleFactor()) as real –> 2.0 (Retina) / 1.0 (Non Retina) set anImage to current application’s NSImage’s alloc()’s initWithSize:(current application’s NSMakeSize(aWidth, aHeight)) anImage’s lockFocus() — set theRect to {{x:0, y:0}, {height:aHeight / retinaF, width:aWidth / retinaF}} set theNSBezierPath to current application’s NSBezierPath’s bezierPath theNSBezierPath’s appendBezierPathWithRect:theRect — fillColor’s |set|() –色設定 theNSBezierPath’s fill() –ぬりつぶし — anImage’s unlockFocus() — return anImage end makeNSImageWithFilledWithColor on cropNSImageTo:{x1, y1, x2, y2} fromImage:theImage set newWidth to x2 – x1 set newHeight to y2 – y1 set newRect to {{x:x1, y:y1}, {width:newWidth, height:newHeight}} theImage’s lockFocus() set theRep to NSBitmapImageRep’s alloc()’s initWithFocusedViewRect:newRect theImage’s unlockFocus() set outImage to NSImage’s alloc()’s initWithSize:(theRep’s |size|()) outImage’s addRepresentation:theRep return outImage end cropNSImageTo:fromImage: –NSImageを指定の大きさでトリミング on cropNSImageBy:{x1, y1, newWidth, newHeight} fromImage:theImage set theSize to (theImage’s |size|()) as record set oldHeight to height of theSize set newRect to {{x:x1, y:y1}, {width:newWidth, height:newHeight}} theImage’s lockFocus() set theRep to NSBitmapImageRep’s alloc()’s initWithFocusedViewRect:newRect theImage’s unlockFocus() set outImage to NSImage’s alloc()’s initWithSize:(theRep’s |size|()) outImage’s addRepresentation:theRep return outImage end cropNSImageBy:fromImage: –画像を指定パスにPNG形式で保存 on saveImageRepAtPathAsPNG(anImage, outPath) set imageRep to anImage’s TIFFRepresentation() set aRawimg to NSBitmapImageRep’s imageRepWithData:imageRep –パスのチルダ展開処理 set pathString to NSString’s stringWithString:outPath set newPath to pathString’s stringByExpandingTildeInPath() set myNewImageData to (aRawimg’s representationUsingType:(NSPNGFileType) |properties|:(missing value)) return (myNewImageData’s writeToFile:newPath atomically:true) as boolean end saveImageRepAtPathAsPNG –リストに入れたレコードを、指定の属性ラベルの値で抽出 on filterRecListByLabel1(aArray, aPredicate as string, aVal) set aPredicate to current application’s NSPredicate’s predicateWithFormat_(aPredicate, aVal) set filteredArray to aArray’s filteredArrayUsingPredicate:aPredicate return filteredArray end filterRecListByLabel1 |
背景が透過しているPNG画像で、余計な余白部分を自動でトリミングするAppleScriptの試作品です。実際に画像のトリミングを行います。1024×1024ぐらいまでの大きさのPNG画像が想定しているターゲットで、4Kとか8Kぐらいの画像は想定していません。
この、背景透過画像の余計な余白を自動トリミングする、という部品は割と(個人的に)重要なのでいろいろ試してみたものです。
▲テストに利用したPNG画像(196 × 309 ピクセル)
▲オリジナル画像をPreview.appで表示させたところ。背景の余白がある
いろいろチューニングしてみましたが、画像の空白エリアの検出は、「元画像の分割数」に応じていろいろ切り抜きすぎたり、余白が多すぎたりとかなり変動が生じました。
32×32分割だと処理速度的に0.4秒@M2なので、16×16前後が向いている感じでした。20×20ぐらいでも試していますが、結局、切り抜きすぎるのは避けたいところです。
AppleScript名:余白トリミング実験 v2.scptd |
— – Created by: Takaaki Naganoya – Created on: 2024/11/21 — – Copyright © 2024 Piyomaru Software, All Rights Reserved — use AppleScript version "2.8" — macOS 12 or later use framework "Foundation" use framework "AppKit" use scripting additions property NSData : a reference to current application’s NSData property |NSURL| : a reference to current application’s |NSURL| property NSColor : a reference to current application’s NSColor property NSString : a reference to current application’s NSString property NSImage : a reference to current application’s NSImage property NSScreen : a reference to current application’s NSScreen property NSBezierPath : a reference to current application’s NSBezierPath property NSPNGFileType : a reference to current application’s NSPNGFileType property NSMutableArray : a reference to current application’s NSMutableArray property NSBitmapImageRep : a reference to current application’s NSBitmapImageRep –画像分割ブロックサイズ property imgDivXStep : 16 property imgDivYStep : 16 script spd property outList : {} end script set aFile to POSIX path of (choose file with prompt "PNG画像を選択") –時間計測用 set a1Dat to current application’s NSDate’s timeIntervalSinceReferenceDate() set aURL to |NSURL|’s fileURLWithPath:aFile set aImage to NSImage’s alloc()’s initWithContentsOfURL:aURL set sRes to aImage’s |size|() –> example: {width:196.0, height:309.0} set aHeight to height of sRes set aWidth to width of sRes set xBlockSize to (aWidth div imgDivXStep) set yBlockSize to (aHeight div imgDivYStep) –transparent block sample set blankImg to makeNSImageWithFilledWithColor(xBlockSize, yBlockSize, current application’s NSColor’s clearColor()) of me set blankBit to blankImg’s TIFFRepresentation() set (outList of spd) to {} repeat with y from 0 to (aHeight – yBlockSize) by yBlockSize set oneLine to "" repeat with x from 0 to (aWidth – xBlockSize) by xBlockSize –crop a part of image and check transparent set tmpImg to (my cropNSImageBy:{x, y, xBlockSize, yBlockSize} fromImage:aImage) set tmpBit to tmpImg’s TIFFRepresentation() set chkTrans to (blankBit’s isEqualToData:tmpBit) as boolean if chkTrans = false then –Not transparent block set the end of (outList of spd) to {xPos:x, yPos:y} end if end repeat end repeat –時間計測用 set b1Dat to current application’s NSDate’s timeIntervalSinceReferenceDate() set c1Dat to b1Dat – a1Dat log c1Dat –最大値、最小値を取得 set nArray to (NSMutableArray’s arrayWithArray:(outList of spd)) set xMin to calcMin("xPos", nArray) of me set xMax to calcMax("xPos", nArray) of me set yMin to calcMin("yPos", nArray) of me set yMax to calcMax("yPos", nArray) of me –オリジナル画像を切り抜き set cImage to my cropNSImageTo:{xMin + (xBlockSize / 2), yMin + (yBlockSize / 2), xMax + (xBlockSize / 2), yMax + (yBlockSize / 2)} fromImage:aImage –ファイル保存 set outPath to POSIX path of (choose file name with prompt "PNG画像の保存ファイル名を指定してください(拡張子入力必須)") saveImageRepAtPathAsPNG(cImage, outPath) of me on calcMin(aLabel as string, nArray) set aStr to "@min." & aLabel set aRes to nArray’s valueForKeyPath:aStr return aRes as anything end calcMin on calcMax(aLabel as string, nArray) set aStr to "@max." & aLabel set aRes to nArray’s valueForKeyPath:aStr return aRes as anything end calcMax –指定サイズの画像を作成し、指定色で塗ってファイル書き出し on makeNSImageWithFilledWithColor(aWidth, aHeight, fillColor) set retinaF to (NSScreen’s mainScreen()’s backingScaleFactor()) as real –> 2.0 (Retina) / 1.0 (Non Retina) set anImage to current application’s NSImage’s alloc()’s initWithSize:(current application’s NSMakeSize(aWidth, aHeight)) anImage’s lockFocus() — set theRect to {{x:0, y:0}, {height:aHeight / retinaF, width:aWidth / retinaF}} set theNSBezierPath to current application’s NSBezierPath’s bezierPath theNSBezierPath’s appendBezierPathWithRect:theRect — fillColor’s |set|() –色設定 theNSBezierPath’s fill() –ぬりつぶし — anImage’s unlockFocus() — return anImage end makeNSImageWithFilledWithColor on cropNSImageTo:{x1, y1, x2, y2} fromImage:theImage set newWidth to x2 – x1 set newHeight to y2 – y1 set theSize to (theImage’s |size|()) as record set oldHeight to height of theSize — transpose y value for Cocoa coordintates set y1 to oldHeight – newHeight – y1 set newRect to {{x:x1, y:y1}, {width:newWidth, height:newHeight}} theImage’s lockFocus() set theRep to NSBitmapImageRep’s alloc()’s initWithFocusedViewRect:newRect theImage’s unlockFocus() set outImage to NSImage’s alloc()’s initWithSize:(theRep’s |size|()) outImage’s addRepresentation:theRep return outImage end cropNSImageTo:fromImage: –NSImageを指定の大きさでトリミング on cropNSImageBy:{x1, y1, newWidth, newHeight} fromImage:theImage set theSize to (theImage’s |size|()) as record set oldHeight to height of theSize — transpose y value for Cocoa coordintates set y1 to oldHeight – newHeight – y1 set newRect to {{x:x1, y:y1}, {width:newWidth, height:newHeight}} theImage’s lockFocus() set theRep to NSBitmapImageRep’s alloc()’s initWithFocusedViewRect:newRect theImage’s unlockFocus() set outImage to NSImage’s alloc()’s initWithSize:(theRep’s |size|()) outImage’s addRepresentation:theRep return outImage end cropNSImageBy:fromImage: –画像を指定パスにPNG形式で保存 on saveImageRepAtPathAsPNG(anImage, outPath) set imageRep to anImage’s TIFFRepresentation() set aRawimg to NSBitmapImageRep’s imageRepWithData:imageRep –パスのチルダ展開処理 set pathString to NSString’s stringWithString:outPath set newPath to pathString’s stringByExpandingTildeInPath() set myNewImageData to (aRawimg’s representationUsingType:(NSPNGFileType) |properties|:(missing value)) return (myNewImageData’s writeToFile:newPath atomically:true) as boolean end saveImageRepAtPathAsPNG |
Numbersは、表からのデータ取得は高速なのに表にデータを設定するのが1000倍以上遅いという特徴を持っています。Numbersの表にデータを連続設定するのが、ある意味現行環境で一番重いAppleScriptのベンチマークと言われてしまうほどに。
そんな中、「ignoring application responses」句で囲って、非同期コマンド実行を行うと、Numbersでも100倍ぐらいの高速化が図れます。AppleScriptの処理系はすぐに解放されますが、Numbers側でコマンドをバッファリングして処理を継続します。
速くなりすぎなので、どこかに副作用があるのでは? と、かなり懐疑的な目で見ていましたが、どうやら危ないポイントが見えてきました。
5×5000セルを連続値で埋める、というテストを「ignoring application responses」つきで行なっていたところ、2万2000を超えたあたりでNumbersの表にデータが書き込まれなくなっていました。
▲10x 5,000のセルを選択して、連続値で埋める処理を非同期モードで実行した結果。この処理では、31,717まで実行したところで終わった
AppleEventのコマンドのバッファリングをアプリ側で行なっているような挙動だったのですが、このバッファに一定の上限があるという仮説を立てています。
ちょっとした処理を行うだけではこの「上限」に達することはないものと思われますが、Numbersに対してセルに値を連続かつ大量に設定するような場合には気をつけてください。
実施環境は、Numbers v14.2+macOS 15.2beta+MacBook Air M2(RAM 16GB)です。
ちなみに、同一環境でMicrosoft Excelで選択範囲(10×5,000)のデータ取得+書き換え+書き戻しをAppleScriptで行なってみたら、1.1秒で終わりました。データの取りこぼしは一切ありません。
AppleScript名:選択中のセルをシーケンシャル値で埋める(ベンチマーク用).scpt |
use AppleScript use scripting additions use framework "Foundation" script spd property aList : {} end script set a1Dat to current application’s NSDate’s timeIntervalSinceReferenceDate() set sNum to 1 tell application "Numbers" tell front document tell active sheet tell table 1 set (aList of spd) to cell of selection range set iCount to 0 set newList to {} repeat with i in (aList of spd) set the end of newList to sNum + iCount ignoring application responses set value of i to sNum end ignoring set sNum to sNum + 1 end repeat end tell end tell end tell end tell set b1Dat to current application’s NSDate’s timeIntervalSinceReferenceDate() set c1Dat to (b1Dat – a1Dat) |
新刊電子書籍「AppleScript基礎テクニック(33)複数のアプリをコントロール」を刊行しました。全74ページ、サンプルAppleScriptアーカイブつき。
→ 販売ページ
macOS上のGUIアプリの書類やウィンドウ中で選択中のオブジェクトを取得し、その情報をもとにAppleScriptで処理を行なって、もとの選択オブジェクトに反映させるといった処理を、日常的に行っています。
このような書き方ができると、まるでAppleScriptによってGUIアプリの機能を拡張しているようにも見えます。実際には外部で処理を行なっていたとしても、使い勝手としては拡張しているように振る舞えます。
ある意味、AppleScriptの備える最強の機能といってもよいでしょう。本書で、この強力なselection機能について、その概要から実例、注意点にいたるまで幅広くご紹介いたします。
PDF 74ページ、Zipアーカイブ添付
■最初に:macOS 13以降では最初にステージマネージャを必ずオフにしてください
macOS 13.x
macOS 14.x
macOS 15.x
その他、オフにすることが望ましい機能
■アプリ上の選択中のオブジェクトにアクセスする予約語「selection」
GUIアプリ上の選択中のオブジェクトを知る
選択中のオブジェクトは、1つだけじゃない
選択中のオブジェクトを加工することも
選択中のオブジェクトの情報分析
■選択中のオブジェクトの情報をもとに他のアプリで処理
selection系の機能をサポートしているアプリ一覧
selectを含む予約語を持つアプリと予約語①
selectを含む予約語を持つアプリと予約語②
selectを含む予約語を持つアプリと予約語③
■selectionの主な実例
selectionの取得方法①(Finder)
selectionの取得方法②(Finder)
selectionの書き換え①(Finder)
selectionの書き換え②(Finder)
selectionの取得方法③(Finder)
selectionの取得方法(住所録)
selectionの書き換え(住所録)
selectionの書き換え(住所録)
selectionの取得方法①(Keynote)
selectionの取得方法②(Keynote)
selectionの書き換え(Keynote)
selectionの取得方法①(ミュージック)
selectionの取得方法②(ミュージック)
selectionの書き換え(ミュージック)
selectionの取得方法①(CotEditor)
selectionの取得方法②(CotEditor)
selectionの書き換え(CotEditor)
selectionの取得方法①(Numbers)
selectionの取得方法②(Numbers)
active sheetの取得方法(Numbers)
selection rangeの取得方法①(Numbers)
selection rangeの取得方法②(Numbers)
selection rangeのセル内容書き換え①(Numbers)
selection rangeのセル内容書き換え②(Numbers)
selection rangeのセル内容書き換え③(Numbers)
selectionの取得方法①(Excel)
selectionの取得方法②(Excel)
選択中のワークシートの取得方法(Excel)
selectionの書き換え(Excel)
selectionの取得方法(Pixelmator Pro)
select-を含む予約語解説①(Pixelmator Pro)
select-を含む予約語解説②(Pixelmator Pro)
select-を含むコマンド実例①(Pixelmator Pro)
select-を含むコマンド実例②(Pixelmator Pro)
select-を含むコマンド実例③(Pixelmator Pro)
select-を含むコマンド実例④(Pixelmator Pro)
select-を含むコマンド実例⑤(Pixelmator Pro)
select-を含むコマンド実例⑥(Pixelmator Pro)
select-を含むコマンド実例⑦(Pixelmator Pro)
select-を含むコマンド実例⑧(Pixelmator Pro)
select-を含むコマンド実例⑨(Pixelmator Pro)
select-を含むコマンド実例⑩(Pixelmator Pro)
select-を含むコマンド実例⑪(Pixelmator Pro)
■selectionを使用うえで注意すべき点
注意点1:大量のオブジェクトの受け渡しに要注意
注意点2:情報の書き戻し時には時間がかかる例も
注意点3:選択オブジェクトの種別判定を①
注意点3:選択オブジェクトの種別判定を②
電子書籍新刊「AppleScript基礎テクニック(32)複数のアプリをコントロール」を刊行しました。全370ページ、サンプルAppleScriptアーカイブつき。
→ 販売ページ
1つのAppleScriptで複数のアプリを操作して処理するのは「常識」ですが、複数のアプリをAppleScriptで操作して処理を行うスタイルは割と独特なものだと指摘されました。
複数アプリの操作はとくに特殊な話ではありませんし、それほど難しい話でもありません。日常的にやっていることなので、その原理がわかれば柔軟で多用途なScriptを書いて「業務を積極的に楽にする」手助けとなることでしょう。
PDF 39ページ、Zipアーカイブ添付
最初に:macOS 13以降では最初にステージマネージャを必ずオフにしてください
macOS 13.x
macOS 14.x
macOS 15.x
その他、オフにすることが望ましい機能
■複数アプリのコントロール3つのパターン
複数アプリのコントロール
自動処理フローから見た各アプリの役割
複数アプリ操作パターン①コマンド呼び出し
複数アプリ操作パターン②ファイル入出力
複数アプリ操作パターン③選択箇所を処理
■複数アプリのコントロール処理の具体例
各アプリは他のアプリのオブジェクトを理解できない
他のアプリのオブジェクトを理解するには
オブジェクトの属性値をよく見てみよう①
オブジェクトの属性値をよく見てみよう②
Keynote上のテキストをPages上に再現①
Keynote上のテキストをPages上に再現②
Keynote上のテキストをPages上に再現③
Keynote上のテキストをPages上に再現④
■GUI Scriptingによるコピペではほとんどの処理を実現できません
GUI Scriptingでコピー&ペースト動作?
異なるアプリ間のオブジェクトのテキスト色の反映
■複数アプリのコントロールその具体例
アプリAのデータをもとにアプリBの機能を利用
複数アプリ、機能の操作例①
複数アプリ、機能の操作例②
複数アプリ、機能の操作例②(参考資料)
複数アプリ、機能の操作例③
背景が透過しているPNG画像で、余計な余白部分を自動でトリミングするAppleScriptの試作品です。
▲テストに利用したPNG画像(196 × 309 ピクセル)
いまのところ、画像を細かく分割して、透明な画像かどうかをブロックごとにチェックするだけです。
画像が真っ白かどうか、透明かどうかといったチェックは、すでに高速処理できるノウハウがあったので、ただそれを応用したものです。
実際にAppleScriptだけで記述して、そこそこの速度が出ています。画像の分割ブロック数が8×8だといまひとつで、16×16でもイマイチ。32 x 32分割ブロックぐらいで「そんなもんだろ」という印象。
▲8×8分割ブロック(0.04秒ぐらい @MacBook Air M2)
▲16×16分割ブロック(0.1秒ぐらい @MacBook Air M2)
▲32×32分割ブロック(0.5秒ぐらい @MacBook Air M2)
当初は可変サイズ分割を考えていましたが、そこまで難しい処理をしなくても良さそうな雰囲気。
何らかの画像処理を行った結果で透明部分が余計に存在するエリアを削除しておくぐらいなので、そんなにシビアにチューニングしなくてもよさそうな気がします。そんなに巨大な画像を処理しなければいいんじゃないでしょうか。
もしも、巨大な画像を処理する場合には、サムネイル画像を作成して、その画像に対してこのような処理を行えばいいんじゃないでしょうか。
このScriptは、背景が透過していることを前提に組んでありますが、画像の4隅から色をピックアップして、多数決で透過なのか白なのかを決定してもいいかもしれません。「余白がかならず存在する」画像であれば、このやり方でいいかも?
AppleScript名:余白トリミング実験 v1.scptd |
— – Created by: Takaaki Naganoya – Created on: 2024/11/21 — – Copyright © 2024 Piyomaru Software, All Rights Reserved — use AppleScript version "2.8" — macOS 12 or later use framework "Foundation" use framework "AppKit" use scripting additions property NSData : a reference to current application’s NSData property |NSURL| : a reference to current application’s |NSURL| property NSColor : a reference to current application’s NSColor property NSString : a reference to current application’s NSString property NSImage : a reference to current application’s NSImage property NSBezierPath : a reference to current application’s NSBezierPath property NSPNGFileType : a reference to current application’s NSPNGFileType property NSBitmapImageRep : a reference to current application’s NSBitmapImageRep property imgDivXStep : 8 property imgDivYStep : 8 set aFile to POSIX path of (choose file) set a1Dat to current application’s NSDate’s timeIntervalSinceReferenceDate() set aURL to |NSURL|’s fileURLWithPath:aFile set aImage to NSImage’s alloc()’s initWithContentsOfURL:aURL set sRes to aImage’s |size|() –> example: {width:196.0, height:309.0} set aHeight to height of sRes set aWidth to width of sRes set xBlockSize to (aWidth div imgDivXStep) set yBlockSize to (aHeight div imgDivYStep) –transparent block sample set blankImg to makeNSImageWithFilledWithColor(xBlockSize, yBlockSize, current application’s NSColor’s clearColor()) of me set blankBit to blankImg’s TIFFRepresentation() set outList to "" repeat with y from 0 to (aHeight – yBlockSize) by yBlockSize set oneLine to "" repeat with x from 0 to (aWidth – xBlockSize) by xBlockSize –crop a part of image and check transparent set tmpImg to (my cropNSImageBy:{x, y, xBlockSize, yBlockSize} fromImage:aImage) set tmpBit to tmpImg’s TIFFRepresentation() set chkTrans to (blankBit’s isEqualToData:tmpBit) as boolean if chkTrans = true then set oneLine to oneLine & "□" –Transparent Block else set oneLine to oneLine & "■" –Not transparent Block end if end repeat set outList to outList & oneLine & return end repeat set b1Dat to current application’s NSDate’s timeIntervalSinceReferenceDate() set c1Dat to b1Dat – a1Dat log c1Dat return outList –指定サイズの画像を作成し、指定色で塗ってファイル書き出し on makeNSImageWithFilledWithColor(aWidth, aHeight, fillColor) set anImage to current application’s NSImage’s alloc()’s initWithSize:(current application’s NSMakeSize(aWidth, aHeight)) anImage’s lockFocus() — set theRect to {{x:0, y:0}, {height:aHeight, width:aWidth}} set theNSBezierPath to current application’s NSBezierPath’s bezierPath theNSBezierPath’s appendBezierPathWithRect:theRect — fillColor’s |set|() –色設定 theNSBezierPath’s fill() –ぬりつぶし — anImage’s unlockFocus() — return anImage end makeNSImageWithFilledWithColor on cropNSImageTo:{x1, y1, x2, y2} fromImage:theImage set newWidth to x2 – x1 set newHeight to y2 – y1 set theSize to (theImage’s |size|()) as record set oldHeight to height of theSize — transpose y value for Cocoa coordintates set y1 to oldHeight – newHeight – y1 set newRect to {{x:x1, y:y1}, {width:newWidth, height:newHeight}} theImage’s lockFocus() set theRep to NSBitmapImageRep’s alloc()’s initWithFocusedViewRect:newRect theImage’s unlockFocus() set outImage to NSImage’s alloc()’s initWithSize:(theRep’s |size|()) outImage’s addRepresentation:theRep return outImage end cropNSImageTo:fromImage: –NSImageを指定の大きさでトリミング on cropNSImageBy:{x1, y1, newWidth, newHeight} fromImage:theImage set theSize to (theImage’s |size|()) as record set oldHeight to height of theSize — transpose y value for Cocoa coordintates set y1 to oldHeight – newHeight – y1 set newRect to {{x:x1, y:y1}, {width:newWidth, height:newHeight}} theImage’s lockFocus() set theRep to NSBitmapImageRep’s alloc()’s initWithFocusedViewRect:newRect theImage’s unlockFocus() set outImage to NSImage’s alloc()’s initWithSize:(theRep’s |size|()) outImage’s addRepresentation:theRep return outImage end cropNSImageBy:fromImage: |
アプリケーションフォルダ以下にあるアプリをすべてリストアップして、各アプリに「キーワード」が登録されているかをチェックするAppleScriptです。「キーワード」が設定されているものをPOSIX pathの一覧で返します。
「キーワード」というのは、Finder上で「情報を見る」コマンドで表示させたウィンドウで、「詳細情報」の下に表示される「キーワード」です。
最近、これを登録してあるアプリが出始めてきたので、全体でどの程度の数になっているのか調査すべく、書いてみました。実行には「Metadata Lib」AppleScriptライブラリを必要とします。実行環境はScript Debuggerである必要はありません。普通にスクリプトエディタで大丈夫です。
App Store申請時に記述する検索用キーワードとは異なるようです。他のアプリを検索したときにヒットすることを目的に、「iPhoto」「Aperture」などと指定してあるところが戦略的です。たしかに、Spotlightで「Aperture」をキーワードに検索するとPixelmator Proが出てきます。
AppleScript名:指定フォルダ以下のアプリケーションを取得して、アプリのキーワード登録をチェック.scptd |
— – Created by: Takaaki Naganoya – Created on: 2024/11/21 — – Copyright © 2024 Piyomaru Software, All Rights Reserved — use AppleScript use scripting additions use framework "Foundation" use framework "AppKit" use mdLib : script "Metadata Lib" version "2.0.0" –https://macosxautomation.com/applescript/apps/Script_Libs.html set origPath to {(POSIX path of (path to applications folder))} set aResList to mdLib’s searchFolders:(origPath) searchString:("kMDItemContentType == %@") searchArgs:{"com.apple.application-bundle"} set sList to {} repeat with i in aResList set j to contents of i set sRes to retAppKeywordsFromBundleIPath(j) of me if sRes ≠ "" then set the end of sList to j end if end repeat return sList –> {"/Applications/Pixelmator Pro.app", "/Applications/Dropzone 4.app", "/Applications/Spotify.app", "/Applications/MarsEdit.localized/MarsEdit.app", "/Applications/Keka.app", "/Applications/MindNode.app", "/Applications/NeoFinder.app", "/Applications/Linearity Curve.app", "/Applications/Amazon Kindle.app", "/Applications/PrimeVideo.app"} –指定パスからアプリケーションの「キーワード」メタデータを取得する on retAppKeywordsFromBundleIPath(appPath as string) set theRecord to mdLib’s fetchMetadataFor:appPath return kMDItemKeywords of theRecord end retAppKeywordsFromBundleIPath |
画像から透過部分を周囲から切り抜く、KGPixelBoundsClipをCooca Framework化して利用していますが、macOS 15上でこれが動作しないことを確認しました。
このFrameworkをAppleScriptから呼び出せることで、Adobe PhotoshopなどのGUIアプリの併用が必要ないため、応用範囲がものすごく広くなりました。
一般向けに公開していない個人用ツールで、本フレームワークを利用しまくっていました。
しかし、macOS 15+MacBook Air M2(Retina Display)の環境では以前のように動作しません(Universal Binaryビルドずみ)。
本プログラムの動作原理はおそろしく簡単です。
周囲から1ピクセルずつ色を拾って、透明色が続く矩形エリアを検出します。CとかObjective-Cだとパフォーマンスを確保しやすい処理内容ですが(脳筋プログラミング)、インタプリタ型のAppleScriptだとこういう脳筋な処理内容だと著しく不利になります。
目下、
(1)かわりの部品をオープンソースのプロジェクトからみつくろう
(2)その後、OS内で類似の機能が追加されていないか調査する
(3)もう、あとくされないようにAppleScriptで書き換える
といった対処を検討中です。
(1)については、Image+Trim.swiftといったSwiftで書かれたプログラムが見つかっています。AppleScriptから呼び出すためには、Bridging Headerを書く必要があるので、ひたすらダルいです。
(2)については、たぶん実装されていないんでないかと(未調査)
(3)については、1ピクセルずつチェックするのでなければ作れそうです。画像の外周部を画像のサイズから計算して細分化したブロックに分割、このブロックごとに透明かどうかをチェックし、ある程度まとめて処理することで速度を稼げそうな気がします。
あとは、最初にサムネイル画像を作ってしまって、その小さくした画像で外周部から色をピックアップしていくという手段もあります。いくつかの方法を試してどれが一番速いかを調べておく必要がありそうです。
macOS 10.12で導入されたセキュリティ系の機能によって、AppleScriptドロップレットの動作が妨げられる現象が確認されてきました。
ドロップしたファイルが数回に分かれてon openハンドラに渡されたり、ドラッグ&ドロップしたファイルすべてがドロップレット側に認識されなかったりするという動作です。
これは、Gatekeeperの動作とquarantine機能(もたらされたファイルが安全かどうか確認されるまでプログラム側に引き渡されない仕組み)の相互作用によるもの、という話になっていますが……認証のためのインタフェースが用意されておらず、ユーザー側から見れば「説明もなしに動かなくなった」という状態でした。
macOS 10.12、10.13、10.14、10.15、11、12、13、14とずーーっとそのままです。レポートしても修正されず、Scripter側でも対策コードを共有するほかなく、この素朴な機能は問題を抱えたままになっていました。
一応、Shane Stanleyによる対策コードによってドロップレットが運用できていたのと、display drop dialogなどの他のドラッグ&ドロップのためのインタフェースを用意できていたのと、もともと個人的にはドロップレットが性に合わなかった(Finder上の選択中のファイルを取得したほうが事故が起こらなくて簡単)ため、対策はできていました。
ただ、初心者にこの状態をどう説明するの? Apple側で説明する気は一切ありません。完全にバグであり、不具合としかユーザーには映りません。
macOS 15betaでも問題が解決されていなかったので、この問題は放置状態のまま解決されないものとばかり思っていました。
それが、macOS 15.2betaか15.1あたりで解消されたようで(バグが出ても直してもレポートを出さないのがApple風味)、確認してみると13.7.2でもドロップしたファイルを見失うといった動作をしなくなった……ような気がします。
macOS 13/14/15と現行のmacOSに対しては同様のアップデートが行われたのではないでしょうか。
まだ確認が不十分だと感じるのと、どうせmacOSのマイナーアップデートでまた使えなくなったりするんだろうというところです。
一方で、Cocoa AppleScript AppletについてはmacOS 12.6以降ずっと動作しない状況が続いています。こちらもレポートしても一切の反応がない状態です。直す気があるのであればいいのですが、その気がないのならスクリプトエディタのテンプレートから削除するといった処置が必要(試してみて動かないというのが最悪)でしょう。
Keynote書類上で複数Shapeないしtext itemが選択された状態で、一番上にあるitemの幅に、下にあるitemを等分割+位置合わせを行うAppleScriptです。
言葉で言い表すと難しいのですが、画面キャプチャで表現すると一目瞭然です。
▲処理後、上側のshapeの幅で下のshapeを等分割+ギャップ指定
電子書籍作成時によく行う作業をワンアクションで行えるようにしてみました。
2D ListのソートはVanilla AppleScriptで書かれたものを使っていますが、これはソートする対象のデータが極小なのと、本ScriptをmacOS標準搭載のScript Menuから呼び出すために作ったためです。
2D Listのソートを行うためだけに、BridgePlusを使用=Script DebuggerでEnhanced Appletに書き出す必要があるというのは面倒なので。
AppleScript名:最前面のKeynote書類で選択中のshapeを一番上のガイドオブジェクトの幅をもとに、残りを等分割 |
— – Created by: Takaaki Naganoya – Created on: 2024/11/15 — – Copyright © 2024 Piyomaru Software, All Rights Reserved — use AppleScript version "2.4" — Yosemite (10.10) or later use framework "Foundation" use scripting additions property xGap : 8 –X軸方向のオブジェクトの隙間座標値 tell application "Keynote" tell front document set aSel to selection if aSel = {} then return set fClass to class of first item of aSel if fClass = slide then return –スライド上のオブジェクトではなく、スライドが選択されていた場合には処理しない set objList to {} set posList to {} set aCount to 1 repeat with i in aSel set j to contents of i set tmpClass to class of j –条件に合うオブジェクトのみ処理対象に if tmpClass is in {text item, shape} then set the end of objList to j set the end of posList to ((position of j) & (width of j) & aCount) set aCount to aCount + 1 end if end repeat set newPosList to shellSortListAscending(posList, 2) of sortLib –Y軸値をもとにソート set guideObj to first item of newPosList –Y軸座標が一番少ない(上)のものをガイドオブジェクトとしてみなす set newPosList to shellSortListAscending(rest of newPosList, 1) of sortLib –ガイドオブジェクト以外のオブジェクト座標値をX軸値でソート set guideWidth to contents of item 3 of guideObj set xStartPos to item 1 of item 1 of newPosList set yPos to item 2 of item 1 of newPosList set itemsCount to length of newPosList set itemWidth to (guideWidth – (xGap * (itemsCount – 1))) / itemsCount –下側オブジェクトの位置と幅修正処理 copy xStartPos to x repeat with i from 1 to itemsCount set y to item 2 of (item i of newPosList) set targObj to item (item 4 of (item i of newPosList)) of objList tell targObj set position of it to {x, y} set width of it to itemWidth end tell set x to x + itemWidth + (xGap) end repeat end tell end tell –Vanilla AppleScriptで書いた2D ListのSorting Lib script sortLib –シェルソートで入れ子のリストを昇順ソート on shellSortListAscending(aSortList, aKeyItem) script oBj property list : aSortList end script set len to count oBj’s list’s items set gap to 1 repeat while (gap ≤ len) set gap to ((gap * 3) + 1) end repeat repeat while (gap > 0) set gap to (gap div 3) if (gap < len) then repeat with i from gap to (len – 1) set temp to oBj’s list’s item (i + 1) set j to i repeat while ((j ≥ gap) and (contents of item aKeyItem of (oBj’s list’s item (j – gap + 1)) > item aKeyItem of temp)) set oBj’s list’s item (j + 1) to oBj’s list’s item (j – gap + 1) set j to j – gap end repeat set oBj’s list’s item (j + 1) to temp end repeat end if end repeat return oBj’s list end shellSortListAscending –シェルソートで入れ子のリストを降順ソート on shellSortListDescending(aSortList, aKeyItem) script oBj property list : aSortList end script set len to count oBj’s list’s items set gap to 1 repeat while (gap ≤ len) set gap to ((gap * 3) + 1) end repeat repeat while (gap > 0) set gap to (gap div 3) if (gap < len) then repeat with i from gap to (len – 1) set temp to oBj’s list’s item (i + 1) set j to i repeat while ((j ≥ gap) and (contents of item aKeyItem of (oBj’s list’s item (j – gap + 1)) < item aKeyItem of temp)) set oBj’s list’s item (j + 1) to oBj’s list’s item (j – gap + 1) set j to j – gap end repeat set oBj’s list’s item (j + 1) to temp end repeat end if end repeat return oBj’s list end shellSortListDescending end script |
macOS 13で搭載された「ステージマネージャ」機能のオン/オフを行うAppleScriptです。
ステージマネージャは、最前面のアプリのウィンドウだけを表示するようにする仕組みで、iPadOSに搭載されたものがそのままmacOSにも搭載されました。ドラッグ&ドロップをこのステージマネージャに対して行えるのと、表示ウィンドウを最前面のものだけに切り替えるものです。
つまり、マルチウィンドウのGUIに不慣れなユーザーのために用意された機能です。Windowsユーザー向けに用意した、ともいえるでしょう。
このステージマネージャのOn/Offを行います。動作内容はご覧のとおり、単にshellコマンドを呼び出しているだけです。現在オンになっているかどうかも、defaults readコマンドで同様に実行できることでしょう。
▲真っ先にオフにして、二度とオンにすることはなかったステージマネージャ(画面左端)
AppleScript名:ステージマネージャのON_OFF |
— – Created by: Takaaki Naganoya – Created on: 2024/11/15 — – Copyright © 2024 Piyomaru Software, All Rights Reserved — use AppleScript version "2.4" — Yosemite (10.10) or later use framework "Foundation" use scripting additions set aBool to true stageManagerControl(aBool) of me delay 5 set aBool to false stageManagerControl(aBool) of me on stageManagerControl(aBool as boolean) set sText to "defaults write com.apple.WindowManager GloballyEnabled -bool " & (aBool as string) do shell script sText end stageManagerControl |
macOS 15.2 Beta(24C5079e)において、以前本BlogでレポートしていたスクリプトエディタでFinderやスクリプトエディタ自身のAppleScript用語辞書をオープンできない問題が解消されていることを確認しました。
ただし、本問題は再発する可能性が高いものと見ています。電子書籍「AppleScript最新リファレンス v2.8対応」のmacOS 15向けの改修を行なっていますが、
「macOS 15でFinderのAppleScript用語辞書を閲覧できない件」についての対処方法の追加記事が無駄になったことは、喜ばしいことなのでしょうか。きっと、再現して必要になるのでは? と思っているものです。
スクリプトエディタのコンテクストメニューに、絵文字を使ったファイル名のAppleScriptやフォルダが複数回表示されるバグの場合にも、Betaで修正を知らされてもRelease版では直っていなかったため、複数プロジェクト間で矛盾した設定や処理が行われていること自体が解消されてこなかったのでしょう。
Appleの会社組織が極度に縦割り化されているため、チーム間の連携とかチーム間にまたがる問題がいっこうに解決されないといった問題が発生しており、Aチームで修正を行っても他のBチームやCチームでは以前どおりの処理を行なってAチームの修正が無駄になるといった話を散々目撃してきました。
macOS 15.2betaにおいて、macOS標準搭載アプリ「写真」(Photos.app)の表示用バージョン番号が誤って「1.0」と記載されています。macOS 15 Betaの段階では「10.0」と記載されていたので、Release後にバージョン番号がおかしくなったのでしょう。
実際にAppleScriptからバージョンを求めても、”1.0″が返ってきます。
AppleScript名:macOS 15.2betaでPhotosのバージョンを間違っている検証 |
tell application "Photos" version –> "1.0" end tell system version of (system info) –> "15.2" |
なお、これまでの写真.appのバージョン履歴は以下のようになっています。
macOS上のアプリのバージョン記述ミスについては、これまでにも何度か発生しており、有名なところではmacOS 13上の「連絡先」(Contacts)がバージョン2539といった謎表記になっていることが知られています(おそらくこれはビルドNo.で、バージョン記述するのを忘れたままになっていたのでしょう)。
自社アプリがApp Storeの審査を通れないのでは? それ以前に、バージョン管理してるんでしょうか????
2024年11月1日付けでPixelmator Teamから発表された発表において、同社がAppleに買収されたとのこと。
Conguratulations!
Pixelmator ProなどのソフトウェアがそのままAppleの製品ラインナップに組み込まれるのか(Apertureの後釜)、あるいは既存のApple製品(Photos.appとか?)との融合が行われるのかはいまのところ不明です。
名前は、変わるかもしれないですね>Pixelmator Pro
以前にKeynote用に作っておいたAppleScriptを、Pages用に書き直しました。
Pages書類、主に奥付けのテキストなどで、「項目名」「:」(セパレータ)「内容」みたいに列挙している箇所を行頭からセパレータの場所の前まで太字の書体に変更します。ヒラギノ角ゴシックでチェックしており、欧文フォントは考慮していません。
Pages v14.2+macOS 15.2Betaで実験しています。
AppleScript名:フォントを記号の前まで太らせる.scptd |
— – Created by: Takaaki Naganoya – Created on: 2024/11/01 — – Copyright © 2024 Piyomaru Software, All Rights Reserved — use AppleScript version "2.4" — Yosemite (10.10) or later use framework "Foundation" use framework "AppKit" use scripting additions property NSFont : a reference to current application’s NSFont property NSFontManager : a reference to current application’s NSFontManager –セパレータリスト、表記ゆらぎ対応(ゆらぎ表記個数は可変) property separatorList : {{":", ":"}, {"mm", "㎜"}, {"cm", "㎝"}} tell application "Pages" tell front document set aSel to selection –Keynote上の選択中のオブジェクトでループ repeat with i in aSel set j to contents of i set tmpClass to class of j –選択中のオブジェクトがテキストアイテムの場合に……. if tmpClass = shape then set objText to object text of j set fontName to font of object text of j set fontSize to size of object text of j –フォントを太らせる(ウェイトを上げる) set fFamilyCount to countFontsInItsFamily(fontName) of me if fFamilyCount = 2 then –ヒラギノ角ゴProN W3 → ヒラギノ角ゴProN W6 set newFont to incrementFontWeight(fontName, 1) of me else if fFamilyCount > 4 then –ヒラギノ角ゴシック Wn のウェイトを上げ set newFont to incrementFontWeight(fontName, 4) of me end if set aCount to 1 set tList to splitByLInes(objText) of me –行ごとにParseした行ごとのテキストでループ repeat with ii in tList set jj to contents of ii set anOffset to 0 –セパレータでループ repeat with iii in separatorList –セパレータの「ゆらぎ」表記を考慮してループ repeat with iiii in iii set jjjj to contents of iiii set anOffset to offset of jjjj in jj if anOffset is not equal to 0 then exit repeat end if end repeat if anOffset is not equal to 0 then exit repeat end repeat if anOffset is not equal to 0 then try set font of characters 1 thru (anOffset – 1) of paragraph aCount of object text of j to newFont end try end if set aCount to aCount + 1 end repeat end if end repeat end tell end tell –テキストを行ごとにParse on splitByLInes(someText) — free to a good home set theString to current application’s NSString’s stringWithString:someText set theList to theString’s componentsSeparatedByCharactersInSet:(current application’s NSCharacterSet’s newlineCharacterSet()) return theList as list end splitByLInes –フォントを太らせる。欧文フォントは考慮していない(別の方法で行う) on incrementFontWeight(psFontName, incNum) set aFont to current application’s NSFont’s fontWithName:psFontName |size|:9.0 –> (NSCTFont) "HiraginoSans-W0 9.00 pt. P [] (0x12870af00) fobj=0x11b1e90d0, spc=1.98" set fontM to current application’s NSFontManager’s sharedFontManager() repeat incNum times set aFont to fontM’s convertWeight:true ofFont:aFont end repeat return (aFont’s fontName()) as string end incrementFontWeight –指定フォントのファミリーに属するフォント数を取得 on countFontsInItsFamily(aPSName) set aFont to current application’s NSFont’s fontWithName:(aPSName) |size|:9.0 set aFamily to aFont’s familyName() set fMan to current application’s NSFontManager’s sharedFontManager() set fList to fMan’s availableMembersOfFontFamily:aFamily return length of (fList as list) end countFontsInItsFamily |
GUIアプリを作ったときに、ステータスメニュー上に置いたアイコン>メニューからアプリ本体のウィンドウをアクティブにできないという問題が発生しています。
macOS 13、14、15とずーーっとこの動作なので、全世界的に困らされている状況があって……久しぶりにXcodeでGUIアプリをAppleScriptで作って、この問題に例外なく自分も悩まされていました。
その解決を行ったAppleScriptプロジェクトです。macOS 13.7.1上でXcode 15.2で試しています。
# 本記事投稿後にプログラムの見直しを行い、掲載リストおよびXcodeプロジェクトの差し替えを行いました。
GUIアプリ内から生成したステータスバー上のステータスアイテム。ステータスアイテムからメニュー表示。表示されたメニューから項目選択でメインウィンドウを最前面に表示する。
tell current application to activate
では何も起こりません(macOS 12まではこれで大丈夫だった)。
そういう操作を行なってもメインウィンドウが最前面に表示されず、メインウィンドウを強制的にアクティブにするようなコードを入れても、「UIがないプロセスから他のプロセスをアクティブにできない」といったワーニングが表示されるだけで、何も起こりません。
メチャクチャ困ります。
そこで、edama2さんに相談していろいろ試してみたところ、強制的にGUIアプリを最前面に持っていけました。
こちらの議論を参考にしています。
on activateMe:aSender
theWindow's makeKeyAndOrderFront:(me)
theWindow's orderFrontRegardless()
end activateMe:
当初、ウィンドウの生成を動的に行なっていましたが、その部分を削除しても動作することを確認できたため、削除しています。
なお、macOS 15.2+Xcode 16.1でビルドして実行してみたら、「tell current application to activate」だけでウィンドウを最前面に持っていけています。ここで紹介している処理は、macOS 13、14で同様の処理ができるように必要になってしまうようです。
macOS 13/14(+15.0〜15.1?)のバグと言って差し支えないでしょう。
AppleScript名:AppDelegate.applescript |
— — AppDelegate.applescript — process Control test — — Created by Takaaki Naganoya2 on 2024/11/01. — — script AppDelegate property parent : class "NSObject" — IBOutlets property theWindow : missing value property NSMenu : a reference to current application’s NSMenu property NSMenuItem : a reference to current application’s NSMenuItem property NSStatusBar : a reference to current application’s NSStatusBar property NSVariableStatusItemLength : a reference to current application’s NSVariableStatusItemLength on applicationWillFinishLaunching:aNotification my initMenu:me end applicationWillFinishLaunching: on applicationShouldTerminate:sender return current application’s NSTerminateNow end applicationShouldTerminate: on clicked:aSender my activateMe:(me) set aTag to (tag of aSender) as integer –Important!! set aTitle to (title of aSender) as string if aTag is in {41, 42, 43, 44, 45, 46} then my activateMe:aSender else if aTag = 100 then display dialog "Clicked" end if if aTitle = "Quit" then quit end clicked: on activateMe:aSender current application’s NSApp’s activateIgnoringOtherApps:(true) theWindow’s makeKeyAndOrderFront:(me) theWindow’s orderFrontRegardless() end activateMe: –Status Menu Structure on initMenu:aSender set aList to {"Piyomaru", "Software", "", "Function", {"1", "2", "3", "4", "5"}, "", "Quit"} set aStatusItem to current application’s NSStatusBar’s systemStatusBar()’s statusItemWithLength:(current application’s NSVariableStatusItemLength) aStatusItem’s setTitle:" TEST " aStatusItem’s setHighlightMode:true aStatusItem’s setMenu:(createMenu(aList) of me) end initMenu: on createMenu(aList) set aMenu to current application’s NSMenu’s alloc()’s init() set aCount to 10 set prevMenuItem to "" repeat with i in aList set j to contents of i set aClass to (class of j) as string if j is equal to "" then set aMenuItem to (current application’s NSMenuItem’s separatorItem()) (aMenu’s addItem:aMenuItem) else if (aClass = "text") or (aClass = "string") then if j = "Quit" then set aMenuItem to (current application’s NSMenuItem’s alloc()’s initWithTitle:j action:"clicked:" keyEquivalent:"") else set aMenuItem to (current application’s NSMenuItem’s alloc()’s initWithTitle:j action:"clicked:" keyEquivalent:"") end if (aMenuItem’s setTag:aCount) (aMenuItem’s setTarget:me) (aMenu’s addItem:aMenuItem) set aCount to aCount + 10 copy aMenuItem to prevMenuItem else if aClass = "list" then –Generate Submenu set subMenu to current application’s NSMenu’s new() (aMenuItem’s setSubmenu:subMenu) set subCounter to 1 repeat with ii in j set jj to contents of ii set subMenuItem1 to (current application’s NSMenuItem’s alloc()’s initWithTitle:jj action:"clicked:" keyEquivalent:"") (subMenuItem1’s setTarget:me) (subMenuItem1’s setTag:(aCount + subCounter)) (subMenu’s addItem:subMenuItem1) set subCounter to subCounter + 1 end repeat end if end if end repeat return aMenu end createMenu end script |