Skip to content

Commit

Permalink
feat: hide dock icon when window is closed, closes #1
Browse files Browse the repository at this point in the history
  • Loading branch information
egoist committed Oct 23, 2024
1 parent f5e30b2 commit 7ccc041
Show file tree
Hide file tree
Showing 10 changed files with 190 additions and 37 deletions.
22 changes: 17 additions & 5 deletions src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { electronApp, optimizer } from "@electron-toolkit/utils"
import {
createMainWindow,
createPanelWindow,
createSetupWindow,
makePanelWindowClosable,
WINDOWS,
} from "./window"
Expand All @@ -12,6 +13,7 @@ import { router } from "./tipc"
import { registerServeProtocol, registerServeSchema } from "./serve"
import { createAppMenu } from "./menu"
import { initTray } from "./tray"
import { isAccessibilityGranted } from "./utils"

registerServeSchema()

Expand All @@ -22,15 +24,20 @@ app.whenReady().then(() => {
// Set app user model id for windows
electronApp.setAppUserModelId(process.env.APP_ID)

// app.setActivationPolicy("accessory")
const accessibilityGranted = isAccessibilityGranted()

Menu.setApplicationMenu(createAppMenu())

registerIpcMain(router)

registerServeProtocol()

createMainWindow()
if (accessibilityGranted) {
createMainWindow()
} else {
createSetupWindow()
}

createPanelWindow()

listenToKeyboardEvents()
Expand All @@ -47,9 +54,14 @@ app.whenReady().then(() => {
})

app.on("activate", function () {
if (!WINDOWS.get("main")) {
console.log("create main on activate")
createMainWindow()
if (accessibilityGranted) {
if (!WINDOWS.get("main")) {
createMainWindow()
}
} else {
if (!WINDOWS.get("setup")) {
createSetupWindow()
}
}
})

Expand Down
1 change: 0 additions & 1 deletion src/main/keyboard.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { uIOhook, UiohookKey } from "uiohook-napi"
import {
getWindowRendererHandlers,
showPanelWindow,
showPanelWindowAndStartRecording,
stopRecordingAndHidePanelWindow,
WINDOWS,
Expand Down
5 changes: 2 additions & 3 deletions src/main/tipc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { RendererHandlers } from "./renderer-handlers"
import { postProcessTranscript } from "./llm"
import { state } from "./state"
import { updateTrayIcon } from "./tray"
import { isAccessibilityGranted } from "./utils"

const t = tipc.create()

Expand Down Expand Up @@ -122,9 +123,7 @@ export const router = {
}),

isAccessibilityGranted: t.procedure.action(async () => {
if (process.platform === "win32") return true

return systemPreferences.isTrustedAccessibilityClient(false)
return isAccessibilityGranted()
}),

requestAccesssbilityAccess: t.procedure.action(async () => {
Expand Down
7 changes: 7 additions & 0 deletions src/main/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { systemPreferences } from "electron"

export const isAccessibilityGranted = () => {
if (process.platform === "win32") return true

return systemPreferences.isTrustedAccessibilityClient(false)
}
45 changes: 37 additions & 8 deletions src/main/window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {
shell,
screen,
app,
webContents,
} from "electron"
import path from "path"
import { getRendererHandlers } from "@egoist/tipc/main"
Expand All @@ -14,8 +13,9 @@ import {
makeWindow,
} from "@egoist/electron-panel-window"
import { RendererHandlers } from "./renderer-handlers"
import { configStore } from "./config"

type WINDOW_ID = "main" | "panel"
type WINDOW_ID = "main" | "panel" | "setup"

export const WINDOWS = new Map<WINDOW_ID, BrowserWindow>()

Expand Down Expand Up @@ -66,7 +66,7 @@ function createBaseWindow({
? "assets://app"
: process.env["ELECTRON_RENDERER_URL"]

win.loadURL(`${baseUrl}${url || ''}`)
win.loadURL(`${baseUrl}${url || ""}`)

return win
}
Expand All @@ -80,6 +80,36 @@ export function createMainWindow({ url }: { url?: string } = {}) {
},
})

if (process.env.IS_MAC) {
win.on("close", () => {
if (configStore.get().hideDockIcon) {
app.setActivationPolicy("accessory")
app.dock.hide()
}
})

win.on("show", () => {
if (configStore.get().hideDockIcon && !app.dock.isVisible()) {
app.dock.show()
}
})
}

return win
}

export function createSetupWindow() {
const win = createBaseWindow({
id: "setup",
url: "/setup",
windowOptions: {
titleBarStyle: "hiddenInset",
width: 800,
height: 600,
resizable: false,
},
})

return win
}

Expand All @@ -93,7 +123,6 @@ export function showMainWindow(url?: string) {
}
} else {
createMainWindow({ url })

}
}

Expand Down Expand Up @@ -126,6 +155,8 @@ export function createPanelWindow() {
url: "/panel",
showWhenReady: false,
windowOptions: {
hiddenInMissionControl: true,
skipTaskbar: true,
closable: false,
maximizable: false,
frame: false,
Expand Down Expand Up @@ -167,7 +198,7 @@ export function showPanelWindow() {

export function showPanelWindowAndStartRecording() {
showPanelWindow()
getWindowRendererHandlers('panel')?.startRecording.send()
getWindowRendererHandlers("panel")?.startRecording.send()
}

export function makePanelWindowClosable() {
Expand All @@ -187,9 +218,7 @@ export const getWindowRendererHandlers = (id: WINDOW_ID) => {
export const stopRecordingAndHidePanelWindow = () => {
const win = WINDOWS.get("panel")
if (win) {
getRendererHandlers<RendererHandlers>(
win.webContents,
).stopRecording.send()
getRendererHandlers<RendererHandlers>(win.webContents).stopRecording.send()

if (win.isVisible()) {
win.hide()
Expand Down
20 changes: 1 addition & 19 deletions src/renderer/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,13 @@
import { RouterProvider } from "react-router-dom"
import { router } from "./router"
import { useQuery } from "@tanstack/react-query"
import { tipcClient } from "./lib/tipc-client"
import { Setup } from "./components/setup"
import { lazy, Suspense } from "react"

const Updater = lazy(() => import("./components/updater"))

function App(): JSX.Element {
const isAccessibilityGrantedQuery = useQuery({
queryKey: ["isAccessibilityGranted"],
queryFn: async () => {
// return false
return tipcClient.isAccessibilityGranted()
},
refetchOnWindowFocus: false,
refetchOnMount: false,
refetchOnReconnect: false,
})

return (
<>
{isAccessibilityGrantedQuery.data === false ? (
<Setup />
) : (
<RouterProvider router={router}></RouterProvider>
)}
<RouterProvider router={router}></RouterProvider>

<Suspense>
<Updater />
Expand Down
15 changes: 14 additions & 1 deletion src/renderer/src/pages/settings-general.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,21 @@ export function Component() {

return (
<div className="grid gap-4">
<ControlGroup title="App">
<Control label="Hide Dock Icon" className="px-3">
<Switch
defaultChecked={configQuery.data.hideDockIcon}
onCheckedChange={(value) => {
saveConfig({
hideDockIcon: value,
})
}}
/>
</Control>
</ControlGroup>

<ControlGroup
title="Shortcut"
title="Shortcuts"
endDescription={
<div className="flex items-center gap-1">
<div>
Expand Down
107 changes: 107 additions & 0 deletions src/renderer/src/pages/setup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { useMicrphoneStatusQuery } from "@renderer/lib/query-client"
import { Button } from "@renderer/components/ui/button"
import { tipcClient } from "@renderer/lib/tipc-client"
import { useQuery } from "@tanstack/react-query"

export function Component() {
const microphoneStatusQuery = useMicrphoneStatusQuery()
const isAccessibilityGrantedQuery = useQuery({
queryKey: ["setup-isAccessibilityGranted"],
queryFn: () => tipcClient.isAccessibilityGranted(),
})

return (
<div className="app-drag-region flex h-dvh items-center justify-center p-10">
<div className="-mt-20">
<h1 className="text-center text-3xl font-extrabold">
Welcome to {process.env.PRODUCT_NAME}
</h1>
<h2 className="mb-10 text-center text-neutral-500 dark:text-neutral-400">
We need some system permissions before we can run the app
</h2>
<div className="mx-auto max-w-screen-md">
<div className="grid divide-y rounded-lg border">
{process.env.IS_MAC && (
<PermissionBlock
title="Accessibility Access"
description={`We need Accessibility Access to capture keyboard events, so that you can hold Ctrl key to start recording, we don't log or store your keyboard events.`}
actionText="Enable in System Settings"
actionHandler={() => {
tipcClient.requestAccesssbilityAccess()
}}
enabled={isAccessibilityGrantedQuery.data}
/>
)}

<PermissionBlock
title="Microphone Access"
description={`We need Microphone Access to record your microphone, recordings are store locally on your computer only.`}
actionText={
microphoneStatusQuery.data === "denied"
? "Enable in System Settings"
: "Request Access"
}
actionHandler={async () => {
const granted = await tipcClient.requestMicrophoneAccess()
if (!granted) {
tipcClient.openMicrophoneInSystemPreferences()
}
}}
enabled={microphoneStatusQuery.data === "granted"}
/>
</div>
</div>

<div className="mt-10 flex items-center justify-center">
<Button
variant="outline"
className="gap-2"
onClick={() => {
tipcClient.restartApp()
}}
>
<span className="i-mingcute-refresh-2-line"></span>
<span>Restart App</span>
</Button>
</div>
</div>
</div>
)
}

const PermissionBlock = ({
title,
description,
actionHandler,
actionText,
enabled,
}: {
title: React.ReactNode
description: React.ReactNode
actionText: string
actionHandler: () => void
enabled?: boolean
}) => {
return (
<div className="grid grid-cols-2 gap-5 p-3">
<div>
<div className="text-lg font-bold">{title}</div>
<div className="mt-1 text-xs text-neutral-500 dark:text-neutral-400">
{description}
</div>
</div>
<div className="flex items-center justify-end">
{enabled ? (
<div className="inline-flex items-center gap-1 text-green-500">
<span className="i-mingcute-check-fill"></span>
<span>Granted</span>
</div>
) : (
<Button type="button" onClick={actionHandler}>
{actionText}
</Button>
)}
</div>
</div>
)
}
4 changes: 4 additions & 0 deletions src/renderer/src/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ export const router: ReturnType<typeof createBrowserRouter> =
},
],
},
{
path: "/setup",
lazy: () => import("./pages/setup"),
},
{
path: "/panel",
lazy: () => import("./pages/panel"),
Expand Down
1 change: 1 addition & 0 deletions src/shared/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export type RecordingHistoryItem = {

export type Config = {
shortcut?: "hold-ctrl" | "ctrl-backslash"
hideDockIcon?: boolean

sttProviderId?: STT_PROVIDER_ID

Expand Down

0 comments on commit 7ccc041

Please sign in to comment.