diff --git a/DEVELOPING.md b/DEVELOPING.md new file mode 100644 index 00000000..ad5341b6 --- /dev/null +++ b/DEVELOPING.md @@ -0,0 +1,43 @@ +# How this works + +There are 2 main parts of the app: + +1. renderer: this is the HTML/Javascript-based UI rendered within the Electron container. This runs Vue.js, a React-like Javascript framework for rendering front-end. +2. main: includes the main app (written in Electron). Handles keyboard shortcuts, brings up the UI and overlays. + +Note that these 2 both depend on each other, and one cannot run without the other. + +# How to develop + +The most up-to-date instructions can always be derived from CI: + +[.github/workflows/main.yml](https://github.com/SnosMe/awakened-poe-trade/blob/master/.github/workflows/main.yml) + +Here's what that looks like as of 2023-12-03. + +```shell +cd renderer +yarn install +yarn make-index-files +yarn dev + +# In a second shell +cd main +yarn install +yarn dev +``` + +# How to build + +```shell +cd renderer +yarn install +yarn make-index-files +yarn build + +cd ../main +yarn build +# We want to sign with a distribution certificate to ensure other users can +# install without errors +CSC_NAME="Certificate name in Keychain" yarn package +``` diff --git a/README.md b/README.md index c600eb4c..ad086a30 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ ### Development -Follow instructions similar to CI [.github/workflows/main.yml](https://github.com/SnosMe/awakened-poe-trade/blob/master/.github/workflows/main.yml) +See [DEVELOPING.md](./DEVELOPING.md) ### Acknowledgments diff --git a/ipc/KeyToCode.ts b/ipc/KeyToCode.ts index 35d78abb..29a8cea4 100644 --- a/ipc/KeyToCode.ts +++ b/ipc/KeyToCode.ts @@ -217,7 +217,9 @@ export const KeyToElectron = { Backslash: '\\', BracketRight: ']', Quote: "'", - Ctrl: 'CmdOrCtrl', + // Do not change Ctrl to CmdOrCtrl. It causes registered shortcuts to + // often not work on Mac for unknown reasons. + Ctrl: 'Ctrl', Alt: 'Alt', Shift: 'Shift' } diff --git a/main/electron-builder.yml b/main/electron-builder.yml index ad8b12ac..6442255d 100644 --- a/main/electron-builder.yml +++ b/main/electron-builder.yml @@ -21,6 +21,13 @@ win: linux: target: - "AppImage" +mac: + target: + - target: default + arch: + - universal + # MacOS apps can only be run on other systems if signed + forceCodeSigning: true appImage: executableArgs: - "--sandbox" diff --git a/main/src/AppTray.ts b/main/src/AppTray.ts index fe176c3a..bd768604 100644 --- a/main/src/AppTray.ts +++ b/main/src/AppTray.ts @@ -8,9 +8,21 @@ export class AppTray { serverPort = 0 constructor (server: ServerEvents) { - this.tray = new Tray( - nativeImage.createFromPath(path.join(__dirname, process.env.STATIC!, process.platform === 'win32' ? 'icon.ico' : 'icon.png')) + let trayImage = nativeImage.createFromPath( + path.join( + __dirname, + process.env.STATIC!, + process.platform === "win32" ? "icon.ico" : "icon.png" + ) ) + + if (process.platform === 'darwin') { + // Mac image size needs to be smaller, or else it looks huge. Size + // guideline is from https://iconhandbook.co.uk/reference/chart/osx/ + trayImage = trayImage.resize({ width: 22, height: 22 }) + } + + this.tray = new Tray(trayImage) this.tray.setToolTip(`Awakened PoE Trade v${app.getVersion()}`) this.rebuildMenu() diff --git a/main/src/host-files/GameConfig.ts b/main/src/host-files/GameConfig.ts index c2bd34ff..f061291a 100644 --- a/main/src/host-files/GameConfig.ts +++ b/main/src/host-files/GameConfig.ts @@ -2,6 +2,7 @@ import fs from 'fs/promises' import path from 'path' import ini from 'ini' import { app } from 'electron' +import process from 'process'; import { hotkeyToString, CodeToKey } from '../../../ipc/KeyToCode' import type { Logger } from '../RemoteLogger' import type { ServerEvents } from '../server' @@ -21,7 +22,12 @@ export class GameConfig { if (this.filePath === filePath) return if (!filePath) { - filePath = path.join(app.getPath('documents'), 'My Games', 'Path of Exile', 'production_Config.ini') + if (process.platform === 'darwin') { + filePath = path.join(app.getPath('appData'), 'Path of Exile', 'Preferences', 'production_Config.ini') + } else { + filePath = path.join(app.getPath('documents'), 'My Games', 'Path of Exile', 'production_Config.ini') + } + try { await fs.access(filePath) // this.server.sendEventTo('any', { diff --git a/main/src/host-files/GameLogWatcher.ts b/main/src/host-files/GameLogWatcher.ts index 520eb56a..035edf57 100644 --- a/main/src/host-files/GameLogWatcher.ts +++ b/main/src/host-files/GameLogWatcher.ts @@ -1,12 +1,8 @@ import { promises as fs, watchFile, unwatchFile } from 'fs' +import { app } from 'electron'; import { ServerEvents } from '../server' import { Logger } from '../RemoteLogger' -const COMMON_PATH = [ - 'C:\\Program Files (x86)\\Grinding Gear Games\\Path of Exile\\logs\\Client.txt', - 'C:\\Program Files (x86)\\Steam\\steamapps\\common\\Path of Exile\\logs\\Client.txt' -] - export class GameLogWatcher { private offset = 0 private filePath?: string @@ -22,8 +18,20 @@ export class GameLogWatcher { async restart (logFile: string | null) { if (this.filePath === logFile) return - if (!logFile && process.platform === 'win32') { - for (const filePath of COMMON_PATH) { + if (!logFile) { + let possiblePaths: string[] = [] + if (process.platform === 'win32') { + possiblePaths = [ + 'C:\\Program Files (x86)\\Grinding Gear Games\\Path of Exile\\logs\\Client.txt', + 'C:\\Program Files (x86)\\Steam\\steamapps\\common\\Path of Exile\\logs\\Client.txt' + ] + } else if (process.platform === 'darwin') { + possiblePaths = [ + `${app.getPath('home')}/Library/Caches/com.GGG.PathOfExile/Logs/Client.txt` + ] + } + + for (const filePath of possiblePaths) { try { await fs.access(filePath) // this.server.sendEventTo('any', { diff --git a/main/src/shortcuts/Shortcuts.ts b/main/src/shortcuts/Shortcuts.ts index cedca1a6..f8bf8a44 100644 --- a/main/src/shortcuts/Shortcuts.ts +++ b/main/src/shortcuts/Shortcuts.ts @@ -224,7 +224,16 @@ export class Shortcuts { function pressKeysToCopyItemText (pressedModKeys: string[] = [], showModsKey: string) { let keys = mergeTwoHotkeys('Ctrl + C', showModsKey).split(' + ') - keys = keys.filter(key => key !== 'C' && !pressedModKeys.includes(key)) + keys = keys.filter(key => key !== 'C') + if (process.platform !== 'darwin') { + // On non-Mac platforms, don't toggle keys that are already being pressed. + // + // For unknown reasons, we need to toggle pressed keys on Mac for advanced + // mod descriptions to be copied. You can test this by setting the shortcut + // to "Alt + any letter". They'll work with this line, but not if it's + // commented out. + keys = keys.filter(key => !pressedModKeys.includes(key)) + } for (const key of keys) { uIOhook.keyToggle(UiohookKey[key as UiohookKeyT], 'down') diff --git a/main/src/shortcuts/text-box.ts b/main/src/shortcuts/text-box.ts index 90f04077..88e8b1b3 100644 --- a/main/src/shortcuts/text-box.ts +++ b/main/src/shortcuts/text-box.ts @@ -1,4 +1,5 @@ import { uIOhook, UiohookKey as Key } from 'uiohook-napi' +import process from 'process'; import type { HostClipboard } from './HostClipboard' import type { OverlayWindow } from '../windowing/OverlayWindow' @@ -14,14 +15,16 @@ const AUTO_CLEAR = [ export function typeInChat (text: string, send: boolean, clipboard: HostClipboard) { clipboard.restoreShortly((clipboard) => { + const modifiers = process.platform === 'darwin' ? [Key.Meta] : [Key.Ctrl] + if (text.startsWith(PLACEHOLDER_LAST)) { text = text.slice(`${PLACEHOLDER_LAST} `.length) clipboard.writeText(text) - uIOhook.keyTap(Key.Enter, [Key.Ctrl]) + uIOhook.keyTap(Key.Enter, modifiers) } else if (text.endsWith(PLACEHOLDER_LAST)) { text = text.slice(0, -PLACEHOLDER_LAST.length) clipboard.writeText(text) - uIOhook.keyTap(Key.Enter, [Key.Ctrl]) + uIOhook.keyTap(Key.Enter, modifiers) uIOhook.keyTap(Key.Home) // press twice to focus input when using controller uIOhook.keyTap(Key.Home) @@ -30,11 +33,11 @@ export function typeInChat (text: string, send: boolean, clipboard: HostClipboar clipboard.writeText(text) uIOhook.keyTap(Key.Enter) if (!AUTO_CLEAR.includes(text[0])) { - uIOhook.keyTap(Key.A, [Key.Ctrl]) + uIOhook.keyTap(Key.A, modifiers) } } - uIOhook.keyTap(Key.V, [Key.Ctrl]) + uIOhook.keyTap(Key.V, modifiers) if (send) { uIOhook.keyTap(Key.Enter) @@ -56,7 +59,7 @@ export function stashSearch ( overlay.assertGameActive() clipboard.writeText(text) uIOhook.keyTap(Key.F, [Key.Ctrl]) - uIOhook.keyTap(Key.V, [Key.Ctrl]) + uIOhook.keyTap(Key.V, [process.platform === 'darwin' ? Key.Meta : Key.Ctrl]) uIOhook.keyTap(Key.Enter) }) } diff --git a/main/src/windowing/GameWindow.ts b/main/src/windowing/GameWindow.ts index acca46d5..bae9a10f 100644 --- a/main/src/windowing/GameWindow.ts +++ b/main/src/windowing/GameWindow.ts @@ -34,7 +34,7 @@ export class GameWindow extends EventEmitter { if (!this._isTracking) { OverlayController.events.on('focus', () => { this.isActive = true }) OverlayController.events.on('blur', () => { this.isActive = false }) - OverlayController.attachByTitle(window, title) + OverlayController.attachByTitle(window, title, { hasTitleBarOnMac: true }) this._isTracking = true } }