Compare commits

..

No commits in common. "611cd4aad523666d6c5f0cecae5f1050e93ee69c" and "d0345fd7f31a7b2be78456abc89c4c81394dff55" have entirely different histories.

4 changed files with 2 additions and 134 deletions

View File

@ -1,30 +1,15 @@
import { createAppRpc } from '../desktop/rpc.js'
import {
closeSingleInstanceServer,
ensureSingleInstanceLock
} from '../desktop/single-instance.js'
import {
registerGlobalShortcuts,
unregisterGlobalShortcuts
} from '../desktop/spotlight.js'
import {
createMainWindow,
handleDeepLink,
handleDeepLinkFromArgv,
setupDevAuthServer,
setupNavigationGestures,
showMainWindow
setupNavigationGestures
} from '../desktop/window.js'
const gotSingleInstanceLock = await ensureSingleInstanceLock({
onDeepLink: handleDeepLink,
onFocus: showMainWindow
})
if (!gotSingleInstanceLock) {
process.exit(0)
}
const rpc = createAppRpc()
const mainWindow = await createMainWindow(rpc)
@ -35,5 +20,4 @@ handleDeepLinkFromArgv()
process.on('exit', () => {
unregisterGlobalShortcuts()
closeSingleInstanceServer()
})

View File

@ -54,8 +54,8 @@ const DashboardWindowButtons = () => {
</>
) : (
<>
{minimizeButton}
{maximizeButton}
{minimizeButton}
{closeButton}
</>
)}

View File

@ -1,111 +0,0 @@
const PROTOCOL_PREFIX = 'farmcontrol://'
const SINGLE_INSTANCE_HOST = '127.0.0.1'
// Stable port derived from the app identifier so multiple apps don't collide.
function getSingleInstancePort() {
const identifier = 'com.tombutcher.farmcontrol'
let hash = 0
for (const char of identifier) {
hash = (hash * 31 + char.charCodeAt(0)) | 0
}
return 49152 + (Math.abs(hash) % 16383)
}
function findProtocolUrl(args) {
return args.find(
(arg) => typeof arg === 'string' && arg.startsWith(PROTOCOL_PREFIX)
)
}
function buildSecondInstanceMessage() {
const url = findProtocolUrl(process.argv)
return url ? { type: 'deeplink', url } : { type: 'focus' }
}
function parseIncomingMessage(data) {
const text = data.toString().trim()
if (!text) return null
try {
return JSON.parse(text)
} catch {
return null
}
}
async function forwardToPrimaryInstance(message) {
await new Promise((resolve, reject) => {
Bun.connect({
hostname: SINGLE_INSTANCE_HOST,
port: getSingleInstancePort(),
socket: {
open(socket) {
socket.write(`${JSON.stringify(message)}\n`)
socket.end()
resolve()
},
data() {},
error(_socket, error) {
reject(error)
},
close() {
resolve()
}
}
}).catch(reject)
})
}
let server = null
function startPrimaryInstanceServer({ onDeepLink, onFocus }) {
server = Bun.listen({
hostname: SINGLE_INSTANCE_HOST,
port: getSingleInstancePort(),
socket: {
data(socket, data) {
const message = parseIncomingMessage(data)
socket.end()
if (!message) return
if (message.type === 'deeplink' && message.url) {
onDeepLink?.(message.url)
} else if (message.type === 'focus') {
onFocus?.()
}
}
}
})
}
export async function ensureSingleInstanceLock({ onDeepLink, onFocus }) {
if (process.platform === 'darwin') {
return true
}
try {
startPrimaryInstanceServer({ onDeepLink, onFocus })
return true
} catch (error) {
if (error?.code !== 'EADDRINUSE') {
console.warn('Single instance lock failed:', error)
return true
}
try {
await forwardToPrimaryInstance(buildSecondInstanceMessage())
} catch (forwardError) {
console.warn('Failed to forward to running instance:', forwardError)
}
return false
}
}
export function closeSingleInstanceServer() {
server?.stop?.()
server = null
}

View File

@ -20,11 +20,6 @@ export function getMainWindow() {
return mainWindow
}
export function showMainWindow() {
mainWindow?.show?.()
mainWindow?.activate?.()
}
export async function getMainViewUrl() {
const channel = await Updater.localInfo.channel()
if (channel === 'dev' || process.env.NODE_ENV === 'development') {