/ src / main / aid / macos.ts
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  }