效率提升:使用AppleScript模拟鼠标键盘操作制作自动化工具

最近开通了我的技术博客,不定期更新一些学习和感受,目前主要是iOS,明年预期会更新一些Web端以及Flutter的内容~

文档镇楼:AppleScript Language Guide

前言

笔者学习AppleScript的起因,是因为本人键盘修饰键的使用习惯和其他同事不同,笔者习惯于将"大小写修饰键"和"Ctrl键"互调位置,以至于习惯默认修饰键配置的同事在我的电脑上进行调试,总是忍不住感叹一声:
“这TM是人用的吗?”
相信了解iOS开发的小伙伴们都知道,Ctrl + Command + 上下,可以切换.h和.m文件,但是在笔者的键盘上,执行这一操作时,不但没有任何的效果,NM还会把键盘设置成了大写模式,在一次又一次,怒吼的咆哮后,笔者痛定思通,决心要解决这个问题,在如何不改变自己键盘使用习惯的前提下,让来调试代码的同事们也感到如使用自己键盘般的流畅和丝滑呢?

于是,就有就有了这篇聊一聊脚本语言:AppleScript

AppleScript的实践应用

实践:还原修饰键默认设置

本文下面比较详细地介绍了AppleScript基础知识,在学习它们之前,只需要简单了解AppleScript中常用的几个概念,就可以实现GUI Scripting的神奇功效,我们先来看看AppleScript中的常用概念

AppleScript的常用概念

这是一个神奇的传送门☞脚本编辑器(本文所有代码均可直接在IDE中执行)

我们先了解下我们的IDE工具,打开脚本编辑器

1.png

点击图片中的按钮,可以看到上面除了"结果"选项外,还多出了"信息"、"事件"和"回复"三个选项,在这里面可以看到更为详细的打印信息!

2.png

AppleScript的必须掌握的命令:

  • tell someone do something

AppleScript的语法十分接近自然语言,想要操作一个对象执行某些操作,只需要使用"tell命令 +对象类型 + 对象名",在之后执行end tell,结束当前的回话

激活终端

tell application "Terminal"
    activate -- 告诉 终端,执行激活命令
end tell

关闭终端

tell application "Terminal"
    quit -- 告诉 终端,执行退出命令
end tell

System Events是系统应用,我们有时寄期望于在系统应用中找到某个正在执行的进程(Process)

tell application "System Events"
    tell process "Terminal"
    end tell
end tell

在执行这段代码后,你会发现,其实什么都没有执行,不用慌,这是因为你并没有告诉"终端"要做些什么,那么我们通过entire contents命令获取Terminal内的所有UI元素

#执行下面代码前,先激活"终端"

tell application "System Events"
    tell process "Terminal"
        entire contents --获取区域内所有的UI元素
    end tell
end tell

执行后,会得到"终端"内的所有UI元素的完整描述

3.png

选择日志中的一条进行分析

button 1 of window "终端 — -zsh — 80×24" of application process "Terminal" of application "System Events"

其实这其中的从属关系已经十分明确了,翻译过来就是

应用"System Events"的应用进程"Terminal"的"窗口"的"按钮1"

  • 模拟点击

button 1的描述,我们已经很清楚了,那button 1到底对应"终端"中的哪个按钮呢?其实单从名字上看,很难看出button 1对应的是哪个按钮,那么我们用一个很简单的方法,确定它的位置,让button 1执行点击操作,通过它的点击效果反推它的位置!

#执行下面代码前,先激活"终端"

tell application "System Events"
    
    tell process "Terminal"
        (*
            注意:由于环境的差异性,直接复制这段代码到你的脚本编辑器,可能得不到想要的效果
            这里一定要输入你上面entire contents后,打印的"button 1"的完整结果
            *)
        click button 1 of window "终端 — -zsh — 80×24" of application process "Terminal" of application "System Events" 
    end tell
    
end tell

执行上述操作后,发现"终端"被关闭了,原来button 1对应的就是"终端"左上角的关闭按钮

请注意:并不是所有的button 1对应的都是应用的关闭按钮,还要具体情况具体分析

  • 模拟输入

我们打开了终端,想看当前目录下存有哪些文件,该如何操作呢?

想一下这个问题,我们都知道在终端中查看目录下文件的命令是ls,那么就可以将这个问题拆分为键入'ls'和点击回车两个步骤

在AppleScript中,输入字符串的命令是keystroke,你也可以用key code实现单键点击

我们完善一下代码:

tell application "Terminal"
    activate
end tell

tell application "System Events"
    
    tell process "Terminal"
        keystroke "ls"
        delay 1 -- 延时一秒后执行
        key code 36 -- 回车的键位码为36
    end tell
    
end tell

完整的键位码传送门☞键位码

我们有时需要通过组合按键的方式执行某一操作,通过在键入命令后添加using {按键A down, 按键B down}的方式,如:

#强制关闭应用快捷键
key code 53 using {command down, option down} -- Esc的键位码为53
  • 执行shell命令

在终端中通过执行shell命令的方式可以达到和模拟键入的同样的效果

tell application "Terminal"
    activate
    do script "ls" 
end tell

当然也可打开某路径下的文件,这里我们尝试打开"系统偏好设置"中的"键盘偏好设置"

tell application "Terminal"
    activate
    #打开键盘偏好设置
    do script "open .  '/System/Library/PreferencePanes/Keyboard.prefPane'"
end tell

我们简单回顾一下,现在我们已经了解了:

  • 如何激活应用,并在当前的系统进程中找到它

  • 如何获取某一区域内的全部UI控件,并尝试点击其中的某一个单独控件

  • 如何实现模拟字符串输入、单键输入和组合输入

  • 如何关闭应用

此外,我们再多了解两个帮助我们编写代码的两个小知识点:

1.注释

AppleScript中的注释分为单行注释和多行注释

单行注释:#--

多行注释 (* 中间的部分都是注释 *)

(上文中三种注释均有用到)

2.输出日志

log命令,可以通过打印辅助信息,来帮助我们编写代码

至此,你已经掌握了实现恢复系统默认修饰键的全部基础概念,小伙伴们完全可以自己尝试写下代码


核心代码:

#激活
tell application "Terminal"
    activate
    #打开键盘偏好设置
    do script "open .  '/System/Library/PreferencePanes/Keyboard.prefPane'"
end tell

tell application "System Events"
    
    tell process "系统偏好设置"
        #点击修饰键      
        click button "修饰键…" of tab group 1 of window "键盘" -- of application process "System Preferences" of application "System Events"
        
        #更改为默认状态
        click button "修饰键…" of tab group 1 of window "键盘" -- of application process "System Preferences" of application "System Events"
        click button "恢复成默认" of sheet 1 of window "键盘" -- of application process "System Preferences" of application "System Events"
            
        click button "好" of sheet 1 of window "键盘" -- of application process "System Preferences" of ®application "System Events"
        
    end tell
    
end tell

AppleScript基础知识

1.基本模块

语法

#符号'¬'(option+'L') 用来将语句延续到第二行
display dialog "This is just a test." buttons {"Great", "OK"} ¬
default button "OK" giving up after 3 --result:调出弹窗,默认键是OK,3秒后消失

变量和属性

#变量赋值
set myName to "John"
copy 22 to myAge

#局部变量
local windowCount
local agentName,agentNumber,agentHireDate

#全局变量
global gAgentCount
global gStatementDate,gNextAgentNumber

#属性
property defaultClientName : "Mary Smith"

#字符串中引用变量
set aPerson to "GCS"
display dialog "Hello " & aPerson & "!"

类型转换

set myText to 2 as text

运算符

3 * 7 - "1" --result 20

List(数组)

#初始化数组
set myList to {1, "what", 3} --result: {1, "what", 3}
set beginning of myList to 0 --首位设置为0
set end of myList to "four" --末位设置为"four"
set item 2 of myList to 4 --第二位设置为4
myList --result: {0, 4, "what", 3, "four"}

Record(键值对)

#存值
set myFullName to {firstName:"John", lastName:"Chapman"}
#取值
set myLastName to lastName of myFullName --result "Chapman"

2.控制语句

considering/ignoring

"Hello Bob" = "HelloBob" --result: false
ignoring white space --忽略空格
    "Hello Bob" = "HelloBob" --result: true
end ignoring

"BOB" = "bob" --result: true
considering case --考虑大小写
    "BOB" = "bob" --result: false
end considering

try-error

try
    word 5 of "one two three"
on error
    error "There are not enough words."
end try

if

set currentTemp to 10
if currentTemp < 60 then
    set response to "It's a little chilly today."
else if currentTemp > 80 then
    set response to "It's getting hotter today."
else
    set response to "It's a nice day today."
end if
display dialog response

repeat-exit

set num to 0
repeat
    -- perform operations
    if num < 5 then
        set num to num + 1
    else
        display dialog num
        exit repeat
    end if
end repeat

repeat (number) times

set x to 3
set power to 3

set returnVal to x
repeat power - 1 times
    set returnVal to returnVal * x
end repeat
return returnVal

repeat while(当型循环)

set userNotDone to true
repeat while userNotDone
    set userNotDone to enterDataRecord()
end repeat

on enterDataRecord()
    delay 3
    false
end enterDataRecord

repeat with loopVariable

数值循环

set n to 3
set returnVal to 0
repeat with i from 0 to n
    set returnVal to returnVal + I
end repeat
return returnVal

(数组遍历)

set peopleList to {"Chris", "David", "Sal", "Ben"}

#方法一
repeat with aPerson in peopleList
    display dialog "Hello " & aPerson & "!"
end repeat

#方法二
repeat with i from 1 to (number of items in peopleList)
    display dialog "Hello " & item i of peopleList & "!"
end repeat

String to List

set wordList to words in "Where is the hammer?"
repeat with currentWord in wordList
    log currentWord
    if (contents of currentWord) is equal to "hammer" then
        display dialog "I found the hammer!"
    end if
end repeat

record遍历

#在文档中没有找到遍历record的方法,不知道是不是不小心遗漏了
#但是在Stack Overflow中看有如下方法,引入了OC中的Foundation库,实现了record的遍历
use framework "Foundation"
set testRecord to {a:"aaa", b:"bbb", c:"ccc"}

set objCDictionary to current application's NSDictionary's dictionaryWithDictionary:testRecord
set allKeys to objCDictionary's allKeys()

repeat with theKey in allKeys
    log theKey as text
    log (objCDictionary's valueForKey:theKey) as text
end repeat

3.函数构造

on greetClient(nameOfClient)
    display dialog ("Hello " & nameOfClient & "!")
end greetClient

greetClient("GCS_DEVELOPER")

4.脚本对象

定义/执行脚本

script John
    property HowManyTimes : 0
    
    to sayHello to someone
        set HowManyTimes to HowManyTimes + 1
        return "Hello " & someone
    end sayHello
    
end script

tell John to sayHello to "Herb"
#John's sayHello to "Jake" --result: "Hello Jake"
#sayHello of John to "Jake" --result: "Hello Jake"

初始化脚本

on makePoint(x, y)
    script thePoint
        property xCoordinate:x
        property yCoordinate:y
    end script
    return thePoint
end makePoint
 
set myPoint to makePoint(10,20)
get xCoordinate of myPoint  --result: 10
get yCoordinate of myPoint  --result: 20

继承

script Alex
    on sayHello()
        return "Hello, " & getName()
    end sayHello
    on getName()
        return "Alex"
    end getName
end script
 
script AlexJunior
    property parent : Alex
    on getName()
        return "Alex Jr"
    end getName
end script
 
-- Sample calls to handlers in the script objects:
tell Alex to sayHello() --result: "Hello, Alex"
tell AlexJunior to sayHello() --result: "Hello, Alex Jr."
 
tell Alex to getName() --result: "Alex"
tell AlexJunior to getName() --result: "Alex Jr"

写在结尾

本文记录的只是AppleScript中的一小部分内容,笔者在网上暂时还没有找到十分详细的中文文档和介绍,所以想要更加深入的了解和学习AppleScript还是查看官方文档!

一手资料☞AppleScript Language Guide

参考资料:

手把手教你用 AppleScript 模拟鼠标键盘操作,实现 macOS 系统的自动化操作

AppleScript中的保留字

appleScript保留字.png

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343