MacOS开发系列1-使用Accessibility API对所有程序的监控和控制

6,080 阅读5分钟

如果您也想在MacOS平台上,监控、控制、获取别的程序信息,那么接下来的内容,将会对你有一些帮助。

  • Accessibility API是一套MacOS平台下的Objective-C API。使用这套API,可以让你在MacOS平台下,获取到各应用程序下的各种元素,包括窗体信息、窗体的按钮、窗体的操作事件等一系列东西.
  • 因为最近Swift大火,同时也是苹果的亲儿子,所以我接下里将阐述一些如何使用Swift调用Accessibility API

宣传一下

在这里插播一下【DFAXUIElement】:我已经把Accessibility API进行封装了,使得你可以快速的进行使用。库是swift版本同时也支持pod的安装。欢迎大家使用,如果有任何bug或者建议可以和我说。感谢大家的支持。

前置条件

  • 根据苹果的尿性,这种可以跨程序进行操作的API,必须是有限制的,而且一般途径下,是无法通过正常方式进行商店上架的。而使用Accessibility API,您的APP就必须是非沙盒模式。而非沙盒模式的APP是无法发布到苹果商店的

  • 而如果您正在开发MacOS的APP,同时使用了Accessibility API。那么巧您又必须上架到苹果商店。您可以参考stackoverflow的文章,里面有解决方案。文章需要科学上网。跳转

1.非沙盒模式:

  • 打开你的开发的APP的项目,然后打开entitlements文件,更改属性App SandboxNO.如下图:

2.Security & Privacy 中允许你的APP

  • 必须在System Preference中->Security & Privacy >Accessibility中添加您的APP,并且勾选上.如下图:

再次宣传一下

  • WindowSnap也是一个我基于Accessibility API开发的一个APP,功能类似于Windows系统下的,鼠标拖动到屏幕某一个位置后,窗体自动进行分屏。这个APP完全开源并且会一直维护。传输门

确保了上述的前置条件都设置好了之后,您就可以快乐的使用Accessibility API了.

Accessibility API包含的东西

  1. AXUIElement: 这个可以说是整个Accessibility API最基础的元素了。他代表着程序里的每一个元素。
  2. AXError: 几乎每一个Accessibility API都会返回一个AXError,从而通过此来表明,你调用的情况,包含成功、失败等状态。详情可看这个文章。传送门
  3. AXObserver: 这是一个监听,可以根据指定的key,监听到这个key所对应的值的变化。详情可看这个文章。传送门
  4. AXValue:这是一个特殊的value值,如果你的值是CGPointCGSizeCGRangeCGRect的时候,就要使用这个。详情看可看这个文章。传送门

而今天我们只介绍AXUIElement,废话不多说,直接上代码

获取整个系统的AXUIElement

let systemRef:AXUIElement = AXUIElementCreateSystemWide()

获取ApplicationAXUIElement

let applicationRef:AXUIElement = AXUIElementCreateApplication(pid)

只需要一个pid就可以获取到指定程序的Application AXUIElemen了。那么问题来了,pid是什么?

pid就是这个程序processIdentifier,即进程ID.因为进程每次重新打开都会变,所以我们就需要动态获取到进程的ID。关于如何获取进程ID,我们将在下一篇文章进行介绍。跳转

获取应用程序下所有的windows(窗口)

通过下述的方法,value就是该Application下所有的windows了,同时数组里面的每一个元素都是AXUIElement类型

var value : AnyObject?
let axError:AXError = AXUIElementCopyAttributeValue(applicationRef!, kAXWindowsAttribute as CFString, &value)
if axError == .success{
	if let windows = value as? [AXUIElement]{
    	   print("all windows \(windows)")
	}
}

可能现在很多人就会问了,kAXWindowsAttribute这个是什么? 其实它是一个Atribute Name,那我如何可以知道所有的值呢?你可以通过查阅AXAttributeConstants.h

那如果我想知道这个AXUIElement,哪些Attribute Name才是属于它的呢?那么你可以通过下面这个方法进行获取。

获取某个AXUIElement所有Attribute Name

var names : CFArray?
let error:AXError = AXUIElementCopyAttributeNames(applicationRef!, &names)
if let array = names as? [String]{
    print("attribute name \(names)")
}

获取某个AXUIElement某个Attribute Name的值

//windowRef是某一个window的AXUIElement
var position:AnyObject?
let error:AXError = AXUIElementCopyAttributeValue(window, kAXPositionAttribute as CFString, &position)
if error == .success{
    //因为这个时候的position其实是一个AXValue的类型,所以需要对他进行进一步的处理
    //从AXValue中获取值
    if CFGetTypeID(position) == AXValueGetTypeID(){
       let positionAXValue = position as! AXValue
       if AXValueGetType(positionAXValue) == AXValueType.cgPoint{
          var value = CGPoint.zero
          let isSuceess = AXValueGetValue(positionAXValue, .cgPoint, &value)
              if isSuceess{
                 print("i got the point , value is \(value)")
               }else{
                 print("faile to get the point")
                }
           }
     }
}

这样我们就获取了winodow的位置信息了,那我们如果想更改呢?

对某个AXUIElement的某个Attribute Name的设值

var position = CGPoint.init(x: 100, y: 100)
//首先我们要创建一个AXValue,里面是包含着CGPoint
if let positionAXValue = AXValueCreate(AXValueType.cgPoint, &position){
//通过AXUIElementSetAttributeValue 对AXUIElement的attribute key【kAXPositionAttribute】设值
let error:AXError = AXUIElementSetAttributeValue(curWindowRef, kAXPositionAttribute as CFString, positionAXValue)
    if error == .success {
       print("set window position sucessed")
    }
}

这样我们就完成了基本的获取和设置了。但有时候我们知道了某个Attribute Name,但我怎么知道它是否是可进行设置呢?

判断某个AXUIElement的某个Attribute Name的设值是否可设值

var attributeCanBeSet: DarwinBoolean = false;
let error:AXError = AXUIElementIsAttributeSettable(curWindowRef!, kAXPositionAttribute as CFString, &attributeCanBeSet)
if error == .success{
   if attributeCanBeSet.boolValue{
     print("attribute kAXPositionAttribute is write and set")
   }else{
     print("attribute kAXPositionAttribute is readonly")
    }
}

那我们如果想对某个元素的事件,进行模拟操作呢?也是可以的。这里我们就拿窗口的最小化按钮,进行模拟点击。

获取window的最小化按钮:

var value:AnyObject?
//其中curWindowRef 是某一个window的AXUIElement
let error : AXError = AXUIElementCopyAttributeValue(curWindowRef!, kAXMinimizeButtonAttribute as CFString, &value)
if error == .success{
   if value != nil && CFGetTypeID(value) == AXUIElementGetTypeID(){
      let minimizedBtnRef:AXUIElement = value! as! AXUIElement
      print("我是最小化按钮 \(minimizedBtnRef)")
   }
                
}

模拟点击该按钮,以实现最小化功能

其中AXPressAction是一个action的key,如果想知道更多,可以通过查阅AXActionConstants

minimizedBtnRef则是window最小化的按钮所对应的AXUIElement

let error:AXError = AXUIElementPerformAction(minimizedBtnRef, kAXPressAction as CFString)
if error == .success{
    print("press sucessed")
}

那么一个AXUIElement有多少个action key呢?都是什么呢?那么我们就可以进行如下代码,来获取该AXUIElement的所有action key

获取所有Action Keys

var actionNames:CFArray?
let error:AXError = AXUIElementCopyActionNames(minimizedBtnRef, &actionNames)
if error == .success{
   print("all action keys is  \(actionNames)")
}

这里基本就完成了对AXUIElement的简单操作了。如果需要更深入了解,请参考苹果的开发文档。

写作不易,如果这个能帮助到您,请点赞、评论。谢谢

如果有什么疑问的,也可以评论区或者私聊我,看到一定回复。

祝大家生活愉快~