From f1b19c46b162fcc9b9e056a754fdcd9e31cc9dc1 Mon Sep 17 00:00:00 2001
From: Dimitris Lolis
Date: Wed, 9 Sep 2026 11:56:07 +0200
Subject: [PATCH 1/5] feat: add native macOS port
---
.github/workflows/macos.yml | 31 +++++++
.gitignore | 3 +-
MACOS.md | 46 ++++++++++
NOTICE | 14 +++
README.md | 4 +-
electron/clipboard/formats.ts | 41 ++++++++-
electron/main/fullscreen.ts | 15 +++-
electron/main/index.ts | 8 +-
electron/main/ipc.ts | 17 ++++
electron/main/loginItems.ts | 39 +++++++-
electron/main/macos.ts | 81 +++++++++++++++++
electron/main/tray.ts | 11 +++
electron/main/updater.ts | 6 ++
electron/main/window.ts | 8 ++
electron/preload/index.ts | 2 +
index.html | 2 +-
package.json | 36 ++++++--
resources/icon-mac.png | Bin 0 -> 23553 bytes
resources/macos/EdgeDropMacHelper.swift | 115 ++++++++++++++++++++++++
resources/macos/build-helper.sh | 20 +++++
shared/bridge.ts | 2 +
src/components/HotkeyRecorder.tsx | 18 +++-
tests/launchAtLoginFixes.test.ts | 8 +-
tests/macosPort.test.ts | 48 ++++++++++
tests/storeLoginApply.test.ts | 4 +-
tests/storeLoginItem.test.ts | 6 +-
tests/storePackaging.test.ts | 9 +-
tests/storePaths.test.ts | 4 +-
tests/trayTheme.test.ts | 4 +-
29 files changed, 573 insertions(+), 29 deletions(-)
create mode 100644 .github/workflows/macos.yml
create mode 100644 MACOS.md
create mode 100644 NOTICE
create mode 100644 electron/main/macos.ts
create mode 100644 resources/icon-mac.png
create mode 100644 resources/macos/EdgeDropMacHelper.swift
create mode 100644 resources/macos/build-helper.sh
create mode 100644 tests/macosPort.test.ts
diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml
new file mode 100644
index 0000000..8858358
--- /dev/null
+++ b/.github/workflows/macos.yml
@@ -0,0 +1,31 @@
+name: macOS
+
+on:
+ push:
+ branches: [main, macos-port]
+ pull_request:
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+jobs:
+ build:
+ runs-on: macos-14
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 22
+ cache: npm
+ - run: npm ci
+ - run: npm run typecheck
+ - run: npm test
+ - run: npm run build:mac
+ - uses: actions/upload-artifact@v4
+ with:
+ name: edge-drop-macos-arm64
+ path: |
+ dist/*.dmg
+ dist/*.zip
+ if-no-files-found: error
diff --git a/.gitignore b/.gitignore
index a465b03..825c461 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,8 @@
node_modules
out
dist
+resources/macos/build
+resources/macos/bin
*.log
drag_debug.txt
clipboard_debug.log
@@ -21,4 +23,3 @@ icons-showcase.svg
scripts/generate_showcase.mjs
electron-clipboard-search-implementation/
scratch/verify-*.cjs
-
diff --git a/MACOS.md b/MACOS.md
new file mode 100644
index 0000000..f209a8a
--- /dev/null
+++ b/MACOS.md
@@ -0,0 +1,46 @@
+# Edge-Drop macOS port
+
+This branch keeps the Electron/React interface and adds native macOS adapters for the OS-specific behavior.
+
+## Run from source
+
+Requirements:
+
+- macOS 12 or newer
+- Node.js 22
+- Xcode Command Line Tools (`xcode-select --install`)
+
+```bash
+cd /Users/dimitrislolis/Projects/Edge-Drop
+npm install
+npm run dev:mac
+```
+
+The app runs as a menu-bar utility and does not appear in the Dock. Hover at the configured left or right screen edge, press the global shortcut, or use the menu-bar icon.
+
+## Permissions
+
+Clipboard history, file drag-in/out, and edge hover require no special macOS permission. The first click-to-paste action asks for **Accessibility** permission because macOS protects synthesized Command+V keyboard events. Grant it under **System Settings → Privacy & Security → Accessibility**. Copy-only actions work without it.
+
+Edge-Drop respects common macOS concealed/sensitive pasteboard types used by password managers.
+
+## Build an installable image
+
+```bash
+npm run typecheck
+npm test
+npm run build:mac
+```
+
+Artifacts are written to `dist/` as an ARM64 DMG and ZIP. This local build is unsigned. Public distribution should use `npm run build:mac:release` with an Apple Developer ID certificate and notarization credentials configured for electron-builder.
+
+## Native macOS implementation
+
+- `resources/macos/EdgeDropMacHelper.swift` reads and writes Finder file URLs through `NSPasteboard`, writes image-plus-file payloads atomically, detects full-screen foreground windows, and sends Command+V through Core Graphics.
+- The panel is an accessory/menu-bar app, hidden from Mission Control, and visible across Spaces and full-screen Spaces.
+- Launch at login uses Electron's macOS login-item API rather than Windows registry keys.
+- The menu-bar icon is a macOS template image and adapts automatically to light/dark menu bars.
+
+## Current distribution note
+
+Automatic updating is disabled on macOS until this fork has a signed and notarized release feed. Windows behavior remains unchanged.
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000..9dbdf02
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,14 @@
+Edge-Drop macOS port
+Copyright 2026 the macOS fork contributors
+
+This product is derived from Edge-Drop by Deepender25:
+https://github.com/Deepender25/Edge-Drop
+
+The upstream project and this derivative are licensed under the Apache
+License, Version 2.0. See LICENSE. Files changed on the macos-port branch
+include platform adapters, packaging, tests, documentation, and build
+configuration for macOS.
+
+Third-party artwork:
+Twemoji graphics copyright Twitter, Inc. and other contributors, licensed
+under CC-BY 4.0: https://github.com/twitter/twemoji
diff --git a/README.md b/README.md
index 12fe3e3..b3ea607 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,8 @@
+> **macOS port:** the `macos-port` branch adds a native macOS pasteboard helper, menu-bar/Spaces behavior, Finder file copy/paste, Command+V automation, full-screen detection, launch at login, and DMG/ZIP packaging. See [MACOS.md](MACOS.md) for setup and current distribution notes.
+
Edge-Drop
@@ -37,7 +39,7 @@
-
+
diff --git a/electron/clipboard/formats.ts b/electron/clipboard/formats.ts
index 1e101c4..0b46fa3 100644
--- a/electron/clipboard/formats.ts
+++ b/electron/clipboard/formats.ts
@@ -36,6 +36,7 @@ export function getClipboardSequenceNumber(): number {
import { getSystemPowerShellPath, getWritableCwd } from '../main/powershell'
import { filterValidPaths } from '../main/pathValidation'
import { isStoreBuild } from '../main/config'
+import { readMacClipboardFiles } from '../main/macos'
const execFileAsync = promisify(execFile)
@@ -53,6 +54,15 @@ export const CF_FILE_LIST = 'FileNameW'
*/
async function readFileListAsync(): Promise {
try {
+ const advertisedFormats = clipboard.availableFormats().map((format) => format.toLowerCase())
+ const hasMacFileUrls = advertisedFormats.includes('public.file-url') ||
+ advertisedFormats.includes('nsfilenamespboardtype')
+ if (process.platform === 'darwin' && hasMacFileUrls) {
+ const paths = await readMacClipboardFiles()
+ const valid = filterValidPaths(paths ?? [])
+ return valid.length ? valid : null
+ }
+
// First, confirm there is actually a file list on the clipboard before
// spawning a process. FileNameW being present is sufficient signal.
const buf = clipboard.readBuffer(CF_FILE_LIST)
@@ -97,6 +107,24 @@ async function readFileListAsync(): Promise {
/** Fast, non-blocking check of FileNameW contents for clipboard signatures. */
function readFileListFast(): string[] | null {
try {
+ if (process.platform === 'darwin') {
+ for (const format of ['public.file-url', 'NSFilenamesPboardType']) {
+ const buf = clipboard.readBuffer(format)
+ if (!buf || buf.length === 0) continue
+ const raw = buf.toString('utf8').replace(/\0/g, '')
+ const urls = raw.match(/file:\/\/[^\s<>'"]+/g) ?? []
+ const paths = filterValidPaths(urls.flatMap((url) => {
+ try {
+ return [decodeURIComponent(new URL(url).pathname)]
+ } catch {
+ return []
+ }
+ }))
+ if (paths.length) return paths
+ }
+ return null
+ }
+
const buf = clipboard.readBuffer(CF_FILE_LIST)
if (!buf || buf.length < 4) return null
const wide = buf.toString('utf16le')
@@ -116,6 +144,12 @@ function readFileListFast(): string[] | null {
*/
export function clipboardHasFileNameW(): boolean {
try {
+ if (process.platform === 'darwin') {
+ return clipboard.availableFormats().some((format) => {
+ const lower = format.toLowerCase()
+ return lower === 'public.file-url' || lower === 'nsfilenamespboardtype'
+ })
+ }
const buf = clipboard.readBuffer(CF_FILE_LIST)
return !!(buf && buf.length >= 4)
} catch {
@@ -137,6 +171,10 @@ export function clipboardHasFileNameW(): boolean {
*/
export function clipboardFilesContentKey(): string | null {
try {
+ if (process.platform === 'darwin') {
+ const paths = readFileListFast()
+ return paths?.length ? `files|${paths.join('\n')}` : null
+ }
const buf = clipboard.readBuffer(CF_FILE_LIST)
if (!buf || buf.length < 4) return null
const fromName = buf
@@ -178,7 +216,8 @@ function clipboardAdvertisesFileList(): boolean {
try {
return clipboard.availableFormats().some((f) => {
const l = f.toLowerCase()
- return l === 'filenamew' || l === 'filename' || l.includes('shell idlist')
+ return l === 'filenamew' || l === 'filename' || l.includes('shell idlist') ||
+ l === 'public.file-url' || l === 'nsfilenamespboardtype'
})
} catch {
return false
diff --git a/electron/main/fullscreen.ts b/electron/main/fullscreen.ts
index be3f2c6..ac2cde1 100644
--- a/electron/main/fullscreen.ts
+++ b/electron/main/fullscreen.ts
@@ -14,6 +14,7 @@
* the panel suppresses itself after a game goes fullscreen, which is fine.
*/
import koffi from 'koffi'
+import { isMacFullscreenAppActive } from './macos'
// Windows QUERY_USER_NOTIFICATION_STATE enum values:
// 1 = QUNS_NOT_PRESENT (screen saver / locked)
@@ -70,6 +71,7 @@ if (process.platform === 'win32') {
let isFullscreenActiveCache = false
let checkTimer: ReturnType | null = null
let onFullscreenDetectedFn: (() => void) | null = null
+let macCheckInFlight = false
export function registerFullscreenActiveListener(fn: () => void): void {
onFullscreenDetectedFn = fn
@@ -112,6 +114,17 @@ export function isFullscreenAppActive(): boolean {
}
export function triggerFullscreenCheck(): void {
+ if (process.platform === 'darwin') {
+ if (macCheckInFlight) return
+ macCheckInFlight = true
+ void isMacFullscreenAppActive().then((isNowFullscreen) => {
+ isFullscreenActiveCache = isNowFullscreen
+ if (isNowFullscreen) onFullscreenDetectedFn?.()
+ }).finally(() => {
+ macCheckInFlight = false
+ })
+ return
+ }
if (process.platform !== 'win32') return
const state = queryNotificationState()
if (state < 0) return // koffi unavailable or call failed
@@ -143,7 +156,7 @@ export function triggerFullscreenCheck(): void {
const FULLSCREEN_CHECK_INTERVAL_MS = 800
export function startFullscreenMonitor(): void {
- if (process.platform !== 'win32') return
+ if (process.platform !== 'win32' && process.platform !== 'darwin') return
if (checkTimer !== null) return
triggerFullscreenCheck() // seed cache immediately
diff --git a/electron/main/index.ts b/electron/main/index.ts
index 7c845fb..ac900a7 100644
--- a/electron/main/index.ts
+++ b/electron/main/index.ts
@@ -35,6 +35,12 @@ import { getThumbnailPayload, thumbnailCacheControl } from './thumbnailCache'
// Electron requires this call before the ready event.
app.disableHardwareAcceleration()
+// Edge-Drop is a menu-bar utility on macOS. Keep it out of the Dock and app
+// switcher while its edge panel remains available across Spaces.
+if (process.platform === 'darwin' && typeof app.setActivationPolicy === 'function') {
+ app.setActivationPolicy('accessory')
+}
+
// Restrict the renderer to a single webContents and forbid remote module usage.
app.enableSandbox()
@@ -93,7 +99,7 @@ app.on('before-quit', () => {
app.whenReady().then(() => {
// GitHub NSIS needs an explicit AUMID. Store packages already have one from
// the AppX identity; overriding it breaks toasts and taskbar grouping.
- if (!isStoreBuild()) {
+ if (process.platform === 'win32' && !isStoreBuild()) {
app.setAppUserModelId('com.edgedrop.app')
}
diff --git a/electron/main/ipc.ts b/electron/main/ipc.ts
index c7d4774..11c0529 100644
--- a/electron/main/ipc.ts
+++ b/electron/main/ipc.ts
@@ -25,6 +25,7 @@ import { isStoreBuild } from './config'
import { applyLaunchAtLogin, refreshLaunchAtLoginFromOs } from './loginItems'
import { toUnpackagedFilePath, toUnpackagedFilePaths } from '../store/paths'
import { isPasteableEmoji } from '../../shared/emoji'
+import { simulateMacPaste, writeMacClipboardFiles, writeMacClipboardImage } from './macos'
export { isStoreBuild }
@@ -71,6 +72,9 @@ function simulatePaste(): void {
})
})
}
+ if (process.platform === 'darwin') {
+ void simulateMacPaste()
+ }
}
/**
@@ -107,6 +111,9 @@ async function writeFileListToClipboard(rawPaths: string[]): Promise {
console.error('[ipc] writeFileListToClipboard PowerShell failed, using text fallback:', err)
}
}
+ if (process.platform === 'darwin') {
+ return writeMacClipboardFiles(validPaths)
+ }
// Non-Windows / PowerShell failure fallback: plain text paths (best-effort)
clipboard.clear()
clipboard.writeText(validPaths.join('\r\n'))
@@ -141,6 +148,9 @@ export async function writeImageToClipboard(imagePath: string | null): Promise {
+ if (process.platform === 'darwin') {
+ return writeMacClipboardImage(imagePath, [namedPath])
+ }
if (process.platform !== 'win32') return false
try {
const b64Img = Buffer.from(imagePath, 'utf8').toString('base64')
@@ -860,6 +870,13 @@ export async function writeItemToClipboard(data: ItemData, capturedAt?: number):
return writeFileListToClipboard(stagedFiles)
}
+ if (process.platform === 'darwin') {
+ return writeMacClipboardImage(
+ toUnpackagedFilePath(firstSrc),
+ stagedFiles.map((p) => toUnpackagedFilePath(p))
+ )
+ }
+
// Multi-file: all pretty-named refs + first image as bitmap.
try {
const exposed = stagedFiles.map((p) => toUnpackagedFilePath(p))
diff --git a/electron/main/loginItems.ts b/electron/main/loginItems.ts
index 910f567..1f44c7e 100644
--- a/electron/main/loginItems.ts
+++ b/electron/main/loginItems.ts
@@ -33,6 +33,33 @@ export interface LaunchAtLoginResult {
ok: boolean
}
+function readMacLaunchAtLogin(): LaunchAtLoginResult {
+ try {
+ const settings = app.getLoginItemSettings()
+ return {
+ enabled: settings.openAtLogin || settings.executableWillLaunchAtLogin,
+ blockedByUser: false,
+ ok: true
+ }
+ } catch {
+ return { enabled: false, blockedByUser: false, ok: false }
+ }
+}
+
+function applyMacLaunchAtLogin(wantLaunch: boolean): LaunchAtLoginResult {
+ try {
+ app.setLoginItemSettings({
+ openAtLogin: wantLaunch,
+ openAsHidden: true
+ })
+ const actual = readMacLaunchAtLogin()
+ return { ...actual, ok: actual.ok && actual.enabled === wantLaunch }
+ } catch (err) {
+ console.error('[LoginItems] macOS login-item update failed:', err)
+ return { enabled: !wantLaunch, blockedByUser: false, ok: false }
+ }
+}
+
export function normalizeLoginPath(p: string): string {
let s = p.trim().replace(/\//g, '\\').toLowerCase()
if (s.startsWith('"')) {
@@ -383,6 +410,9 @@ export async function readLaunchAtLogin(): Promise {
if (!app.isPackaged) {
return { enabled: loadSettings().launchAtLogin, blockedByUser: false, ok: true }
}
+ if (process.platform === 'darwin') {
+ return readMacLaunchAtLogin()
+ }
if (isStoreBuild()) {
return resultFromState(await getStatus())
}
@@ -397,6 +427,9 @@ export async function applyLaunchAtLogin(wantLaunch: boolean): Promise {
if (!os.ok) {
// GitHub: even when the Electron query fails, a healthy raw key means
// we are actually fine. Heal the quoting/stale path opportunistically.
- if (settings.launchAtLogin && !isStoreBuild()) {
+ if (process.platform === 'win32' && settings.launchAtLogin && !isStoreBuild()) {
try {
if (!isGithubRunKeyHealthy()) {
applyGithubLaunchAtLogin(true)
@@ -446,7 +479,7 @@ export async function reconcileLaunchAtLoginOnStartup(): Promise {
// If user has settings=false, but the Run key exists for Edge-Drop and is NOT blocked by user:
// This happens when 0.3.1's false-negative poll bug erroneously saved launchAtLogin: false.
// Self-heal: restore settings to true and ensure key is properly quoted!
- if (!isStoreBuild()) {
+ if (process.platform === 'win32' && !isStoreBuild()) {
const exe = app.getPath('exe')
const raw = getRawGithubRunCommand(CANONICAL_LOGIN_ITEM_NAME)
if (raw && isOurLoginExe(raw, exe) && !isBlockedInStartupApproved(CANONICAL_LOGIN_ITEM_NAME)) {
@@ -484,7 +517,7 @@ export async function reconcileLaunchAtLoginOnStartup(): Promise {
}
}
- if (settings.launchAtLogin && os.enabled && !isStoreBuild()) {
+ if (process.platform === 'win32' && settings.launchAtLogin && os.enabled && !isStoreBuild()) {
// Self-heal quoting / stale path / missing --hidden on every launch so
// users updating from 0.3.0 (unquoted) get fixed without touching UI.
try {
diff --git a/electron/main/macos.ts b/electron/main/macos.ts
new file mode 100644
index 0000000..790896b
--- /dev/null
+++ b/electron/main/macos.ts
@@ -0,0 +1,81 @@
+import { app } from 'electron'
+import { execFile } from 'node:child_process'
+import { existsSync } from 'node:fs'
+import { join } from 'node:path'
+import { promisify } from 'node:util'
+
+const execFileAsync = promisify(execFile)
+
+function helperPath(): string | null {
+ if (process.platform !== 'darwin') return null
+ const candidate = app.isPackaged
+ ? join(process.resourcesPath, 'macos', 'EdgeDropMacHelper')
+ : join(app.getAppPath(), 'resources', 'macos', 'bin', 'EdgeDropMacHelper')
+ return existsSync(candidate) ? candidate : null
+}
+
+async function runHelper(args: string[], timeout = 3000): Promise {
+ const helper = helperPath()
+ if (!helper) throw new Error('macOS helper is not built')
+ const { stdout } = await execFileAsync(helper, args, {
+ encoding: 'utf8',
+ timeout,
+ maxBuffer: 1024 * 1024
+ })
+ return stdout
+}
+
+export async function readMacClipboardFiles(): Promise {
+ if (process.platform !== 'darwin') return null
+ try {
+ const raw = await runHelper(['read-files'])
+ const value: unknown = JSON.parse(raw || '[]')
+ return Array.isArray(value) ? value.filter((item): item is string => typeof item === 'string') : null
+ } catch (err) {
+ console.error('[macOS] Failed to read pasteboard file URLs:', err)
+ return null
+ }
+}
+
+export async function writeMacClipboardFiles(paths: string[]): Promise {
+ if (process.platform !== 'darwin' || paths.length === 0) return false
+ try {
+ await runHelper(['write-files', ...paths])
+ return true
+ } catch (err) {
+ console.error('[macOS] Failed to write pasteboard file URLs:', err)
+ return false
+ }
+}
+
+export async function writeMacClipboardImage(imagePath: string, filePaths: string[] = []): Promise {
+ if (process.platform !== 'darwin') return false
+ try {
+ await runHelper(['write-image', imagePath, ...filePaths])
+ return true
+ } catch (err) {
+ console.error('[macOS] Failed to write pasteboard image:', err)
+ return false
+ }
+}
+
+export async function simulateMacPaste(): Promise {
+ if (process.platform !== 'darwin') return false
+ try {
+ await runHelper(['paste'], 5000)
+ return true
+ } catch (err) {
+ console.error('[macOS] Command+V simulation failed:', err)
+ return false
+ }
+}
+
+export async function isMacFullscreenAppActive(): Promise {
+ if (process.platform !== 'darwin') return false
+ try {
+ return (await runHelper(['frontmost-fullscreen'], 1500)).trim() === '1'
+ } catch (err) {
+ console.error('[macOS] Fullscreen detection failed:', err)
+ return false
+ }
+}
diff --git a/electron/main/tray.ts b/electron/main/tray.ts
index b9b7960..ad104e5 100644
--- a/electron/main/tray.ts
+++ b/electron/main/tray.ts
@@ -65,6 +65,17 @@ export function isTaskbarLightTheme(): boolean {
/** Resolves the appropriate 32x32 tray icon based on the current taskbar theme. */
export function getTrayImage(): Electron.NativeImage {
+ if (process.platform === 'darwin') {
+ const source = existsSync(PATHS.trayIcon())
+ ? nativeImage.createFromPath(PATHS.trayIcon())
+ : fallbackIcon()
+ const image = source.resize({ width: 18, height: 18, quality: 'best' })
+ if (typeof image.setTemplateImage === 'function') {
+ image.setTemplateImage(true)
+ return image
+ }
+ }
+
const isLight = isTaskbarLightTheme()
const preferredPath = isLight ? PATHS.trayDarkIcon() : PATHS.trayIcon()
const fallbackPath = PATHS.trayIcon()
diff --git a/electron/main/updater.ts b/electron/main/updater.ts
index 3ff619d..f7fac57 100644
--- a/electron/main/updater.ts
+++ b/electron/main/updater.ts
@@ -177,6 +177,12 @@ export function initAutoUpdater(): void {
console.log('[AutoUpdater] Store build detected — auto-updater disabled.')
return
}
+ if (process.platform === 'darwin') {
+ // The fork has no signed/notarized macOS release feed yet. Avoid a noisy
+ // request to the Windows-only upstream release on every launch.
+ console.log('[AutoUpdater] macOS release feed not configured — automatic updates disabled.')
+ return
+ }
try {
const { autoUpdater } = require('electron-updater')
diff --git a/electron/main/window.ts b/electron/main/window.ts
index b7f45ab..a2e053d 100644
--- a/electron/main/window.ts
+++ b/electron/main/window.ts
@@ -554,6 +554,14 @@ export function createWindow(): BrowserWindow {
// Start click-through with no forwarding — edge detection is done via cursor poll.
mainWindow.setIgnoreMouseEvents(true, { forward: false })
+ if (process.platform === 'darwin') {
+ mainWindow.setVisibleOnAllWorkspaces(true, {
+ visibleOnFullScreen: true,
+ skipTransformProcessType: true
+ })
+ mainWindow.setHiddenInMissionControl(true)
+ }
+
// Apply WS_EX_NOACTIVATE so clicking the panel never steals OS focus from the active application.
applyNoActivateStyle(mainWindow, true)
diff --git a/electron/preload/index.ts b/electron/preload/index.ts
index f5f5eb0..8c891e2 100644
--- a/electron/preload/index.ts
+++ b/electron/preload/index.ts
@@ -176,6 +176,8 @@ win.addEventListener('drop', (e: any) => {
}, true)
const api = {
+ platform: process.platform,
+
/* Renderer -> Main */
loadState: () => invoke('state:load'),
setPinned: (id: string, pinned: boolean) => invoke('item:set-pinned', id, pinned),
diff --git a/index.html b/index.html
index 38aff56..8324959 100644
--- a/index.html
+++ b/index.html
@@ -4,7 +4,7 @@
-
+
Edge-Drop
diff --git a/package.json b/package.json
index c199912..784f09e 100644
--- a/package.json
+++ b/package.json
@@ -9,11 +9,15 @@
"type": "module",
"scripts": {
"dev": "electron-vite dev",
+ "dev:mac": "npm run build:mac-helper && electron-vite dev",
"build": "electron-vite build",
"build:github": "npm run build && electron-builder --win nsis -c.extraMetadata.buildTarget=github",
"build:store": "npm run build && electron-builder --win appx -c.extraMetadata.buildTarget=store",
"build:msix": "npm run build && electron-builder --win appx -c.extraMetadata.buildTarget=store",
"build:win": "npm run build:github",
+ "build:mac-helper": "bash resources/macos/build-helper.sh",
+ "build:mac": "npm run build:mac-helper && npm run build && electron-builder --mac dmg zip --publish never -c.mac.identity=null -c.mac.hardenedRuntime=false",
+ "build:mac:release": "npm run build:mac-helper && npm run build && electron-builder --mac dmg zip --publish never",
"package": "npm run build:github",
"preview": "electron-vite preview",
"typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false",
@@ -49,10 +53,6 @@
"!**/node_modules/koffi/doc/**"
],
"extraResources": [
- {
- "from": "resources/startup/EdgeDropStartup.exe",
- "to": "startup/EdgeDropStartup.exe"
- },
{
"from": "node_modules/emoji-datasource-twitter/img/twitter/64",
"to": "emoji/64"
@@ -84,7 +84,31 @@
]
}
],
- "icon": "resources/icon.ico"
+ "icon": "resources/icon.ico",
+ "extraResources": [
+ {
+ "from": "resources/startup/EdgeDropStartup.exe",
+ "to": "startup/EdgeDropStartup.exe"
+ }
+ ]
+ },
+ "mac": {
+ "target": [
+ "dmg",
+ "zip"
+ ],
+ "category": "public.app-category.productivity",
+ "icon": "resources/icon-mac.png",
+ "artifactName": "Edge-Drop-${version}-${arch}.${ext}",
+ "extendInfo": {
+ "LSUIElement": true
+ },
+ "extraResources": [
+ {
+ "from": "resources/macos/bin/EdgeDropMacHelper",
+ "to": "macos/EdgeDropMacHelper"
+ }
+ ]
},
"nsis": {
"oneClick": false,
@@ -140,4 +164,4 @@
"react": "^18.2.0",
"zustand": "^4.5.2"
}
-}
\ No newline at end of file
+}
diff --git a/resources/icon-mac.png b/resources/icon-mac.png
new file mode 100644
index 0000000000000000000000000000000000000000..c7bd10a61b4f06b501d2f7965ad46ee341531992
GIT binary patch
literal 23553
zcmeFZ`9GBV`v?A-u`i{h;$-VomXwrJ4q`?siXw`m2&Y6PghsN=v~i9~O197tQHKzc
zeI`zkrBNz-ma^|#%$S+`d)?~v{{Ha&2fmNT=NG4Q&wan{*K)mH*L6Lg&+Go%@}S8Q
z@n6IdLQD3U?zTcm5dJBMM1|o$R0WNz2py~5x7+CO#e~t012-K$#_4c^s$MqM-U$68
z`|O8y(FB6&X|E3hdw-K{So8NEE`Li(W-VQ}CakW;yS}I5+g}Iec8cCL3x8Bfl>cSV
z!*wCnY?@cQ_K$8E}cFiRa}
zQ~8}(HUT;#d;tC+v~n5#mn?*L6Il}QU(jJP{`dcW`oG!0&EWrRgN#qZ|80l=+YY!H
z{Qr16kXbCdl5z5M!$;E=w(ulknf64OG
zZLA{R$XI_+mJH9_mPo5zP5MQMNDrE#hc7`|ZqP(*vdNs`&ej&&2|wd2G_wuM=5OCn
zx?Mp)veGc3e6_jA>fpze#xzW)DrOo=>mO)k)sq&5o&FAd+Zy2Rpph^%8ti*4qE*zj{mbzXc
zRmS)ezMLSUh6~VQv+y2LvIe3Vg5zbX++xj;ipJ0HA$3M((K@5Fl(hv=k{Jvx$4GsC
zx~3?&FkTSpDn_hEzL&xH#*_uP!G>7fD3^5oBtPZ4^ttM@BBrs2kA|(6Uby84t5vny)-3w!IkAYT=tmd`XduBdR
zSRyV+8-*6N)WoSF8=HhRw9B8r8CKE5bY^6&~S`Ir>6Oz?`EnA(ptaXXGUtI$Iod7T9y-OB`e@}eXz*w
zX0;~y3&xY7(&%dq6V}&D6>o2Y06%wFDo~?IP*d?ZWj&(lOTz;aA>5Z83di)iO%Zyr
z0gQDz{9XkWtwI2u<+!Uh>bFKo{^Acu5pBDK}rbrVLep`)b<9K)W#Z6#SJ-h4(
z$L?eh9Sur%-j_mXE!=HQ87qA;!eQ*xo0f97&_k=1@@u@vXfLd}@9Pr*O6sq$<#U|M
zYjR6P$uN5Zf>-%Y#n%vmf5$$_=PHq&pKMUdho3BUV2ZR;dX-vL1<_-CR^u9`r1GX;
z54H5wPZ5(eVQ5FAI`~o6W@+5Yn;VenGDJ3j*W{m?YtK3~N%z5+Bf3kG=|%6(
zFDV6>FG3w_p}A0K?ul9Vcqz#@W*JIa3T>sSlqf3eoUE?UG1K9y(rNJ}Fh{s;12J
zf;nuKentB5MQXL7TXf99dn-{8te{h)3}&e`jS$BI_GFqGKlz|8(W|Q{=6-fLCoNEj
ztOTQI)`AB&Pa|pA#}?4)j@^?||GtT8tS@^00{T)8{qwS2=%=3x-=w%vBZ(4DL^JrJ
z=wHem;V5k`!O#hM@RWa^*TlzMVv+^)v;7k+_i3Aho1<1T{dC1q0|}b-I1P()YPK#r
zUO|6M-H{Mz8~)#v%|P-%8{hX18(Ux~lnJ!I4S-REZ%$OxKpZl3+%I(iJJ7|Dd&h|5S;%rD_Airkps*T^Ixh{o4tf+Ll62G#-n~l9^@Bt-
zph-aF>)vBn#~fAvl1Oao2!9x^s^C`>7DUnp%OsS3tBS^#%i&jOs_uq_$oEFY
z1oyD}GP29nHBj*`IG66U&oL9+7EqFj
zVmA;FlWYJp1dBWg9erAdsyF?(-EzDfv&r-6FLM#nI%b=F{49)bHTcw1CeRl7r*Y9d
zOhw4xkz*C0E#}EC`b#Rg2|v%}FOEIRB|6!FaS5E5P--~hA%J2x!%ejB2H<<=1VVXm
zmdQq#^H?)fnr3m}}&e>-@-aY_}PgQsQ^0Fan!L#5O1so%L6}$3<^$E8PwAaxj}Y>nX3P7t{u?u
zrIA