diff --git a/src/main/index.ts b/src/main/index.ts index c6e5ae5..cb66c2a 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -3,6 +3,7 @@ import { electronApp, optimizer } from "@electron-toolkit/utils" import { createMainWindow, createPanelWindow, + createSetupWindow, makePanelWindowClosable, WINDOWS, } from "./window" @@ -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() @@ -22,7 +24,7 @@ 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()) @@ -30,7 +32,12 @@ app.whenReady().then(() => { registerServeProtocol() - createMainWindow() + if (accessibilityGranted) { + createMainWindow() + } else { + createSetupWindow() + } + createPanelWindow() listenToKeyboardEvents() @@ -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() + } } }) diff --git a/src/main/keyboard.ts b/src/main/keyboard.ts index 92c27f3..22b6508 100644 --- a/src/main/keyboard.ts +++ b/src/main/keyboard.ts @@ -1,7 +1,6 @@ import { uIOhook, UiohookKey } from "uiohook-napi" import { getWindowRendererHandlers, - showPanelWindow, showPanelWindowAndStartRecording, stopRecordingAndHidePanelWindow, WINDOWS, diff --git a/src/main/tipc.ts b/src/main/tipc.ts index 15477f6..b702c16 100644 --- a/src/main/tipc.ts +++ b/src/main/tipc.ts @@ -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() @@ -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 () => { diff --git a/src/main/utils.ts b/src/main/utils.ts new file mode 100644 index 0000000..e1200c1 --- /dev/null +++ b/src/main/utils.ts @@ -0,0 +1,7 @@ +import { systemPreferences } from "electron" + +export const isAccessibilityGranted = () => { + if (process.platform === "win32") return true + + return systemPreferences.isTrustedAccessibilityClient(false) +} diff --git a/src/main/window.ts b/src/main/window.ts index cdcf821..beffa9f 100644 --- a/src/main/window.ts +++ b/src/main/window.ts @@ -4,7 +4,6 @@ import { shell, screen, app, - webContents, } from "electron" import path from "path" import { getRendererHandlers } from "@egoist/tipc/main" @@ -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() @@ -66,7 +66,7 @@ function createBaseWindow({ ? "assets://app" : process.env["ELECTRON_RENDERER_URL"] - win.loadURL(`${baseUrl}${url || ''}`) + win.loadURL(`${baseUrl}${url || ""}`) return win } @@ -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 } @@ -93,7 +123,6 @@ export function showMainWindow(url?: string) { } } else { createMainWindow({ url }) - } } @@ -126,6 +155,8 @@ export function createPanelWindow() { url: "/panel", showWhenReady: false, windowOptions: { + hiddenInMissionControl: true, + skipTaskbar: true, closable: false, maximizable: false, frame: false, @@ -167,7 +198,7 @@ export function showPanelWindow() { export function showPanelWindowAndStartRecording() { showPanelWindow() - getWindowRendererHandlers('panel')?.startRecording.send() + getWindowRendererHandlers("panel")?.startRecording.send() } export function makePanelWindowClosable() { @@ -187,9 +218,7 @@ export const getWindowRendererHandlers = (id: WINDOW_ID) => { export const stopRecordingAndHidePanelWindow = () => { const win = WINDOWS.get("panel") if (win) { - getRendererHandlers( - win.webContents, - ).stopRecording.send() + getRendererHandlers(win.webContents).stopRecording.send() if (win.isVisible()) { win.hide() diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index 942d802..e8e206a 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -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 ? ( - - ) : ( - - )} + diff --git a/src/renderer/src/pages/settings-general.tsx b/src/renderer/src/pages/settings-general.tsx index 4b68589..b1e75bf 100644 --- a/src/renderer/src/pages/settings-general.tsx +++ b/src/renderer/src/pages/settings-general.tsx @@ -58,8 +58,21 @@ export function Component() { return (
+ + + { + saveConfig({ + hideDockIcon: value, + }) + }} + /> + + +
diff --git a/src/renderer/src/pages/setup.tsx b/src/renderer/src/pages/setup.tsx new file mode 100644 index 0000000..ef3d8d8 --- /dev/null +++ b/src/renderer/src/pages/setup.tsx @@ -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 ( +
+
+

+ Welcome to {process.env.PRODUCT_NAME} +

+

+ We need some system permissions before we can run the app +

+
+
+ {process.env.IS_MAC && ( + { + tipcClient.requestAccesssbilityAccess() + }} + enabled={isAccessibilityGrantedQuery.data} + /> + )} + + { + const granted = await tipcClient.requestMicrophoneAccess() + if (!granted) { + tipcClient.openMicrophoneInSystemPreferences() + } + }} + enabled={microphoneStatusQuery.data === "granted"} + /> +
+
+ +
+ +
+
+
+ ) +} + +const PermissionBlock = ({ + title, + description, + actionHandler, + actionText, + enabled, +}: { + title: React.ReactNode + description: React.ReactNode + actionText: string + actionHandler: () => void + enabled?: boolean +}) => { + return ( +
+
+
{title}
+
+ {description} +
+
+
+ {enabled ? ( +
+ + Granted +
+ ) : ( + + )} +
+
+ ) +} diff --git a/src/renderer/src/router.tsx b/src/renderer/src/router.tsx index 5a9232e..6e09664 100644 --- a/src/renderer/src/router.tsx +++ b/src/renderer/src/router.tsx @@ -34,6 +34,10 @@ export const router: ReturnType = }, ], }, + { + path: "/setup", + lazy: () => import("./pages/setup"), + }, { path: "/panel", lazy: () => import("./pages/panel"), diff --git a/src/shared/types.ts b/src/shared/types.ts index c04ecb8..7ba3eea 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -9,6 +9,7 @@ export type RecordingHistoryItem = { export type Config = { shortcut?: "hold-ctrl" | "ctrl-backslash" + hideDockIcon?: boolean sttProviderId?: STT_PROVIDER_ID