macos.ts
1 import { Application } from './types' 2 import applescript from 'applescript' 3 import NutAutomator from './nut' 4 5 export default class extends NutAutomator { 6 constructor() { 7 super() 8 this.setupNut() 9 } 10 11 async getForemostApp(): Promise<Application | null> { 12 const script = ` 13 tell application "System Events" 14 set appProcess to first application process whose frontmost is true 15 set bundleID to bundle identifier of appProcess 16 set appName to name of appProcess 17 set appPath to POSIX path of (file of appProcess as alias) 18 set winTitle to name of first window of appProcess 19 end tell 20 return bundleID & "<|>" & appName & "<|>" & appPath & "<|>" & winTitle 21 ` 22 23 // run it 24 const app = await this.runScript(script) 25 const [id, name, realpath, window] = (app as string).split('<|>') 26 const path = realpath 27 .replace('/System/Volumes/Preboot/Cryptexes/App/System', '') 28 .replace('/Volumes/Preboot/Cryptexes/App/System', '') 29 return { id, name, path, window } 30 } 31 32 async focusApp(application: Application): Promise<boolean> { 33 try { 34 // check 35 if (!application.id) { 36 console.error('Application ID is required') 37 return false 38 } 39 40 // now focus window 41 if (application.window?.length) { 42 const script = ` 43 tell application "System Events" 44 set appProcess to first application process whose bundle identifier is "${application.id}" 45 tell appProcess 46 set frontmost to true 47 set focusedWindow to first window whose name is "${application.window.replaceAll('"', '\\"')}" 48 perform action "AXRaise" of focusedWindow 49 set value of attribute "AXFocused" of focusedWindow to true 50 end tell 51 end tell 52 ` 53 54 // run it 55 await this.runScript(script) 56 } else { 57 const script = ` 58 tell application "System Events" 59 set appProcess to first application process whose bundle identifier is "${application.id}" 60 tell appProcess 61 set frontmost to true 62 end tell 63 end tell 64 ` 65 66 // run it 67 await this.runScript(script) 68 } 69 70 // probably done 71 return true 72 } catch (error) { 73 console.error('Error while focusingApp', application, error) 74 return false 75 } 76 } 77 78 async selectAll(): Promise<void> { 79 try { 80 await super.selectAll() 81 } catch { 82 const script = ` 83 tell application "System Events" to keystroke "a" using command down 84 delay 0.1 85 ` 86 87 // run it 88 await this.runScript(script) 89 } 90 } 91 92 async moveCaretBelow(): Promise<void> { 93 try { 94 await super.moveCaretBelow() 95 } catch { 96 const script = ` 97 tell application "System Events" 98 key code 124 99 key code 36 100 key code 36 101 end tell 102 ` 103 104 // run it 105 await this.runScript(script) 106 } 107 } 108 109 async copySelectedText(): Promise<void> { 110 try { 111 // nut (custom) 112 //await super.copySelectedText(); 113 if (!(await this.setup())) throw new Error('nutjs not loaded') 114 await this.nut().keyboard.pressKey(this.commandKey(), this.nut().Key.C) 115 await this.nut().keyboard.releaseKey(this.commandKey(), this.nut().Key.C) 116 } catch { 117 // applescript 118 const script = ` 119 repeat 20 times 120 try 121 tell application "System Events" to keystroke "c" using command down 122 delay 0.02 123 set clipboardContents to the clipboard 124 if length of clipboardContents is not 0 then exit repeat 125 delay 0.1 126 on error 127 end try 128 end repeat 129 ` 130 131 // run it 132 await this.runScript(script) 133 } 134 } 135 136 async pasteText(): Promise<void> { 137 try { 138 // nut 139 await super.pasteText() 140 } catch { 141 // applescript 142 const script = ` 143 tell application "System Events" to keystroke "v" using command down 144 delay 0.1 145 ` 146 147 // run it 148 await this.runScript(script) 149 } 150 } 151 152 async deleteSelectedText(): Promise<void> { 153 try { 154 await super.deleteSelectedText() 155 } catch { 156 const script = ` 157 tell application "System Events" 158 key code 117 159 end tell 160 ` 161 162 // run it 163 await this.runScript(script) 164 } 165 } 166 167 private runScript(script: string) { 168 return new Promise((resolve, reject) => { 169 applescript.execString(script, (err: Error, rtn: any) => { 170 if (err) { 171 reject(err) 172 } else { 173 resolve(rtn) 174 } 175 }) 176 }) 177 } 178 179 protected async setupNut() { 180 const rc = await super.setup() 181 if (rc) { 182 this.nut().keyboard.config.autoDelayMs = 25 183 } 184 return rc 185 } 186 187 protected delay() { 188 return 50 189 } 190 191 protected commandKey() { 192 return this.nut().Key.LeftCmd 193 } 194 }