feat(desktop): add pinch zoom setting (#28632)

This commit is contained in:
Brendan Allan
2026-05-21 18:44:30 +08:00
committed by GitHub
parent 2697cb8001
commit 2caac055ef
11 changed files with 244 additions and 35 deletions

View File

@@ -193,6 +193,12 @@ export const SettingsGeneral: Component = () => {
{ initialValue: null as DisplayBackend | null }, { initialValue: null as DisplayBackend | null },
) )
const [pinchZoom, { mutate: setPinchZoom }] = createResource(
() => (desktop() && platform.getPinchZoomEnabled ? true : false),
() => Promise.resolve(platform.getPinchZoomEnabled?.() ?? false).catch(() => false),
{ initialValue: false },
)
onMount(() => { onMount(() => {
void theme.loadThemes() void theme.loadThemes()
}) })
@@ -239,6 +245,13 @@ export const SettingsGeneral: Component = () => {
}) })
} }
const onPinchZoomChange = (checked: boolean) => {
setPinchZoom(checked)
const update = platform.setPinchZoomEnabled?.(checked)
if (!update) return
void update.catch(() => setPinchZoom(!checked))
}
const colorSchemeOptions = createMemo((): { value: ColorScheme; label: string }[] => [ const colorSchemeOptions = createMemo((): { value: ColorScheme; label: string }[] => [
{ value: "system", label: language.t("theme.scheme.system") }, { value: "system", label: language.t("theme.scheme.system") },
{ value: "light", label: language.t("theme.scheme.light") }, { value: "light", label: language.t("theme.scheme.light") },
@@ -729,6 +742,48 @@ export const SettingsGeneral: Component = () => {
</div> </div>
) )
const DisplaySection = () => (
<Show when={desktop()}>
<div class="flex flex-col gap-1">
<h3 class="text-14-medium text-text-strong pb-2">{language.t("settings.general.section.display")}</h3>
<SettingsList>
<SettingsRow
title={language.t("settings.general.row.pinchZoom.title")}
description={language.t("settings.general.row.pinchZoom.description")}
>
<div data-action="settings-pinch-zoom">
<Switch
checked={pinchZoom.latest}
onChange={onPinchZoomChange}
/>
</div>
</SettingsRow>
<Show when={linux()}>
<SettingsRow
title={
<div class="flex items-center gap-2">
<span>{language.t("settings.general.row.wayland.title")}</span>
<Tooltip value={language.t("settings.general.row.wayland.tooltip")} placement="top">
<span class="text-text-weak">
<Icon name="help" size="small" />
</span>
</Tooltip>
</div>
}
description={language.t("settings.general.row.wayland.description")}
>
<div data-action="settings-wayland">
<Switch checked={displayBackend.latest === "wayland"} onChange={onDisplayBackendChange} />
</div>
</SettingsRow>
</Show>
</SettingsList>
</div>
</Show>
)
console.log(import.meta.env) console.log(import.meta.env)
return ( return (
<div class="flex flex-col h-full overflow-y-auto no-scrollbar px-4 pb-10 sm:px-10 sm:pb-10"> <div class="flex flex-col h-full overflow-y-auto no-scrollbar px-4 pb-10 sm:px-10 sm:pb-10">
@@ -749,31 +804,7 @@ export const SettingsGeneral: Component = () => {
<UpdatesSection /> <UpdatesSection />
<Show when={linux()}> <DisplaySection />
<div class="flex flex-col gap-1">
<h3 class="text-14-medium text-text-strong pb-2">{language.t("settings.general.section.display")}</h3>
<SettingsList>
<SettingsRow
title={
<div class="flex items-center gap-2">
<span>{language.t("settings.general.row.wayland.title")}</span>
<Tooltip value={language.t("settings.general.row.wayland.tooltip")} placement="top">
<span class="text-text-weak">
<Icon name="help" size="small" />
</span>
</Tooltip>
</div>
}
description={language.t("settings.general.row.wayland.description")}
>
<div data-action="settings-wayland">
<Switch checked={displayBackend.latest === "wayland"} onChange={onDisplayBackendChange} />
</div>
</SettingsRow>
</SettingsList>
</div>
</Show>
<Show when={desktop() && import.meta.env.VITE_OPENCODE_CHANNEL === "beta"}> <Show when={desktop() && import.meta.env.VITE_OPENCODE_CHANNEL === "beta"}>
<AdvancedSection /> <AdvancedSection />

View File

@@ -22,6 +22,7 @@ import { iife } from "@opencode-ai/core/util/iife"
import { base64Encode } from "@opencode-ai/core/util/encode" import { base64Encode } from "@opencode-ai/core/util/encode"
import { Avatar as AvatarV2 } from "@opencode-ai/ui/v2/components/avatar-v2.jsx" import { Avatar as AvatarV2 } from "@opencode-ai/ui/v2/components/avatar-v2.jsx"
import { displayName, getProjectAvatarSource, projectForSession } from "@/pages/layout/helpers" import { displayName, getProjectAvatarSource, projectForSession } from "@/pages/layout/helpers"
import { makeEventListener } from "@solid-primitives/event-listener"
type TauriDesktopWindow = { type TauriDesktopWindow = {
startDragging?: () => Promise<void> startDragging?: () => Promise<void>
@@ -298,6 +299,34 @@ export function Titlebar(props: { update?: TitlebarUpdate }) {
() => new Map(projects().flatMap((project) => (project.id ? [[project.id, project] as const] : []))), () => new Map(projects().flatMap((project) => (project.id ? [[project.id, project] as const] : []))),
) )
const currentSessionTab = () => {
if (!params.dir || !params.id) return
const href = makeSessionHref(params.dir, params.id)
if (!tabsStore.some((tab) => tab.href === href)) return
return href
}
const closeCurrentSessionTab = () => {
const href = currentSessionTab()
if (!href) return false
tabsStoreActions.removeTab(href)
return true
}
makeEventListener(
document,
"keydown",
(event) => {
if (!event.metaKey || event.ctrlKey || event.altKey || event.shiftKey) return
if (event.key.toLowerCase() !== "w") return
if (!closeCurrentSessionTab()) return
event.preventDefault()
event.stopPropagation()
},
{ capture: true },
)
const tabsEnriched = iife(() => { const tabsEnriched = iife(() => {
const base = mapArray( const base = mapArray(
() => tabsStore, () => tabsStore,
@@ -578,15 +607,15 @@ function TabNavItem(props: {
const isActive = () => !!match() const isActive = () => !!match()
return ( return (
<div <div
class="group relative flex h-7 min-w-24 max-w-60 flex-row items-center gap-1.5 overflow-hidden whitespace-nowrap rounded-[6px] bg-[var(--tab-bg)] pl-1.5 pr-8 [--tab-bg:var(--v2-background-bg-deep)] hover:[--tab-bg:var(--v2-background-bg-layer-02)] data-[active='true']:[--tab-bg:var(--v2-background-bg-layer-02)]" class="group relative flex h-7 min-w-24 max-w-60 flex-row items-center gap-1.5 overflow-hidden whitespace-nowrap rounded-[6px] bg-[var(--tab-bg)] pl-1.5 [--tab-bg:var(--v2-background-bg-deep)] hover:[--tab-bg:var(--v2-background-bg-layer-02)] data-[active='true']:[--tab-bg:var(--v2-background-bg-layer-02)]"
data-active={isActive()} data-active={isActive()}
> >
<a <a
href={props.href} href={props.href}
class="flex h-full min-w-0 flex-1 flex-row items-center gap-1.5 overflow-hidden text-[13px] font-medium leading-none tracking-[-0.04px] text-v2-text-text-faint group-data-[active='true']:text-v2-text-text-base" class="flex h-full min-w-0 flex-1 flex-row items-center gap-1.5 overflow-hidden text-[13px] font-medium text-v2-text-text-faint group-data-[active='true']:text-v2-text-text-base"
> >
<ProjectTabAvatar project={props.project} directory={props.directory} /> <ProjectTabAvatar project={props.project} directory={props.directory} />
<span class="truncate">{props.title}</span> <span class="text-clip">{props.title}</span>
</a> </a>
<div class="absolute right-0 inset-y-0 flex flex-row items-center pr-1 py-1 w-8 pl-2"> <div class="absolute right-0 inset-y-0 flex flex-row items-center pr-1 py-1 w-8 pl-2">
@@ -624,7 +653,7 @@ function ProjectTabAvatar(props: { project?: LocalProject; directory: string })
function NewSessionTabItem(props: { href: string; title: string; onClose: () => void }) { function NewSessionTabItem(props: { href: string; title: string; onClose: () => void }) {
return ( return (
<div class="group relative flex h-7 w-[135px] min-w-24 max-w-60 flex-row items-center gap-1.5 overflow-hidden rounded-[6px] bg-[var(--v2-overlay-simple-overlay-pressed)] pl-1.5 pr-8 whitespace-nowrap focus-within:outline focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-[var(--v2-border-border-focus)]"> <div class="group relative flex h-7 max-w-60 flex-row items-center gap-1.5 overflow-hidden rounded-[6px] bg-[var(--v2-overlay-simple-overlay-pressed)] pl-1.5 pr-8 whitespace-nowrap focus-within:outline focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-[var(--v2-border-border-focus)]">
<a <a
href={props.href} href={props.href}
aria-current="page" aria-current="page"

View File

@@ -93,6 +93,12 @@ export type Platform = {
/** Webview zoom level (desktop only) */ /** Webview zoom level (desktop only) */
webviewZoom?: Accessor<number> webviewZoom?: Accessor<number>
/** Get whether native pinch/Ctrl-scroll zoom gestures are enabled (desktop only) */
getPinchZoomEnabled?(): Promise<boolean> | boolean
/** Allow native pinch/Ctrl-scroll zoom gestures (desktop only) */
setPinchZoomEnabled?(enabled: boolean): Promise<void> | void
/** Run a desktop-only menu action from the app chrome */ /** Run a desktop-only menu action from the app chrome */
runDesktopMenuAction?(action: DesktopMenuAction): Promise<void> | void runDesktopMenuAction?(action: DesktopMenuAction): Promise<void> | void

View File

@@ -784,6 +784,8 @@ export const dict = {
"settings.general.row.showSessionProgressBar.title": "Show session progress bar", "settings.general.row.showSessionProgressBar.title": "Show session progress bar",
"settings.general.row.showSessionProgressBar.description": "settings.general.row.showSessionProgressBar.description":
"Display the animated progress bar at the top of the session when the agent is working", "Display the animated progress bar at the top of the session when the agent is working",
"settings.general.row.pinchZoom.title": "Pinch to zoom",
"settings.general.row.pinchZoom.description": "Allow trackpad pinch and Ctrl-scroll gestures to zoom",
"settings.general.row.wayland.title": "Use native Wayland", "settings.general.row.wayland.title": "Use native Wayland",
"settings.general.row.wayland.description": "Disable X11 fallback on Wayland. Requires restart.", "settings.general.row.wayland.description": "Disable X11 fallback on Wayland. Requires restart.",

View File

@@ -7,4 +7,5 @@ export const CHANNEL: Channel = raw === "dev" || raw === "beta" || raw === "prod
export const SETTINGS_STORE = "opencode.settings" export const SETTINGS_STORE = "opencode.settings"
export const DEFAULT_SERVER_URL_KEY = "defaultServerUrl" export const DEFAULT_SERVER_URL_KEY = "defaultServerUrl"
export const WSL_ENABLED_KEY = "wslEnabled" export const WSL_ENABLED_KEY = "wslEnabled"
export const PINCH_ZOOM_ENABLED_KEY = "pinchZoomEnabled"
export const UPDATER_ENABLED = app.isPackaged && CHANNEL !== "dev" export const UPDATER_ENABLED = app.isPackaged && CHANNEL !== "dev"

View File

@@ -14,7 +14,7 @@ import type {
} from "../preload/types" } from "../preload/types"
import { runDesktopMenuAction } from "./desktop-menu-actions" import { runDesktopMenuAction } from "./desktop-menu-actions"
import { getStore } from "./store" import { getStore } from "./store"
import { setTitlebar, updateTitlebar } from "./windows" import { getPinchZoomEnabled, setPinchZoomEnabled, setTitlebar, updateTitlebar } from "./windows"
const pickerFilters = (ext?: string[]) => { const pickerFilters = (ext?: string[]) => {
if (!ext || ext.length === 0) return undefined if (!ext || ext.length === 0) return undefined
@@ -202,6 +202,10 @@ export function registerIpcHandlers(deps: Deps) {
if (!win) return if (!win) return
updateTitlebar(win) updateTitlebar(win)
}) })
ipcMain.handle("get-pinch-zoom-enabled", () => getPinchZoomEnabled())
ipcMain.handle("set-pinch-zoom-enabled", (_event: IpcMainInvokeEvent, enabled: boolean) => {
setPinchZoomEnabled(enabled)
})
ipcMain.handle("set-titlebar", (event: IpcMainInvokeEvent, theme: TitlebarTheme) => { ipcMain.handle("set-titlebar", (event: IpcMainInvokeEvent, theme: TitlebarTheme) => {
const win = BrowserWindow.fromWebContents(event.sender) const win = BrowserWindow.fromWebContents(event.sender)
if (!win) return if (!win) return

View File

@@ -3,7 +3,9 @@ import { app, BrowserWindow, dialog, net, nativeImage, nativeTheme, protocol } f
import { dirname, isAbsolute, join, relative, resolve } from "node:path" import { dirname, isAbsolute, join, relative, resolve } from "node:path"
import { fileURLToPath, pathToFileURL } from "node:url" import { fileURLToPath, pathToFileURL } from "node:url"
import type { TitlebarTheme } from "../preload/types" import type { TitlebarTheme } from "../preload/types"
import { PINCH_ZOOM_ENABLED_KEY } from "./constants"
import { exportDebugLogs, write as writeLog } from "./logging" import { exportDebugLogs, write as writeLog } from "./logging"
import { getStore } from "./store"
import { createUnresponsiveSampler } from "./unresponsive" import { createUnresponsiveSampler } from "./unresponsive"
const root = dirname(fileURLToPath(import.meta.url)) const root = dirname(fileURLToPath(import.meta.url))
@@ -33,7 +35,10 @@ let relaunchHandler = () => {
app.exit(0) app.exit(0)
} }
const titlebarThemes = new WeakMap<BrowserWindow, Partial<TitlebarTheme>>() const titlebarThemes = new WeakMap<BrowserWindow, Partial<TitlebarTheme>>()
const pinchZoomEnabled = new WeakMap<BrowserWindow, boolean>()
const titlebarHeight = 40 const titlebarHeight = 40
const maxZoomLevel = 10
const minZoomLevel = 0.2
export function setRelaunchHandler(handler: () => void) { export function setRelaunchHandler(handler: () => void) {
relaunchHandler = handler relaunchHandler = handler
@@ -79,6 +84,20 @@ export function updateTitlebar(win: BrowserWindow) {
win.setTitleBarOverlay(overlay(titlebarThemes.get(win), win.webContents.getZoomFactor())) win.setTitleBarOverlay(overlay(titlebarThemes.get(win), win.webContents.getZoomFactor()))
} }
export function setPinchZoomEnabled(enabled: boolean) {
getStore().set(PINCH_ZOOM_ENABLED_KEY, enabled)
for (const win of BrowserWindow.getAllWindows()) {
pinchZoomEnabled.set(win, enabled)
win.webContents.send("pinch-zoom-enabled-changed", enabled)
if (!enabled && win.webContents.getZoomFactor() !== 1) win.webContents.setZoomFactor(1)
updateZoom(win)
}
}
export function getPinchZoomEnabled() {
return getStore().get(PINCH_ZOOM_ENABLED_KEY) === true
}
export function setDockIcon() { export function setDockIcon() {
if (process.platform !== "darwin") return if (process.platform !== "darwin") return
const icon = nativeImage.createFromPath(join(iconsDir(), "dock.png")) const icon = nativeImage.createFromPath(join(iconsDir(), "dock.png"))
@@ -392,13 +411,31 @@ function isRendererUrl(value?: string, html = false) {
} }
function wireZoom(win: BrowserWindow) { function wireZoom(win: BrowserWindow) {
pinchZoomEnabled.set(win, getPinchZoomEnabled())
win.webContents.setZoomFactor(1) win.webContents.setZoomFactor(1)
win.webContents.on("zoom-changed", () => { win.webContents.on("zoom-changed", (event, zoomDirection) => {
win.webContents.setZoomFactor(1) event.preventDefault()
updateTitlebar(win) if (pinchZoomEnabled.get(win)) {
win.webContents.setZoomFactor(
clampZoom(win.webContents.getZoomFactor() + (zoomDirection === "in" ? 0.2 : -0.2)),
)
updateZoom(win)
return
}
if (win.webContents.getZoomFactor() !== 1) win.webContents.setZoomFactor(1)
updateZoom(win)
}) })
} }
function clampZoom(value: number) {
return Math.min(Math.max(value, minZoomLevel), maxZoomLevel)
}
function updateZoom(win: BrowserWindow) {
updateTitlebar(win)
win.webContents.send("zoom-factor-changed", win.webContents.getZoomFactor())
}
function upsertKeyValue(obj: Record<string, any>, keyToChange: string, value: any) { function upsertKeyValue(obj: Record<string, any>, keyToChange: string, value: any) {
const keyToChangeLower = keyToChange.toLowerCase() const keyToChangeLower = keyToChange.toLowerCase()
for (const key of Object.keys(obj)) { for (const key of Object.keys(obj)) {

View File

@@ -60,6 +60,18 @@ const api: ElectronAPI = {
relaunch: () => ipcRenderer.send("relaunch"), relaunch: () => ipcRenderer.send("relaunch"),
getZoomFactor: () => ipcRenderer.invoke("get-zoom-factor"), getZoomFactor: () => ipcRenderer.invoke("get-zoom-factor"),
setZoomFactor: (factor) => ipcRenderer.invoke("set-zoom-factor", factor), setZoomFactor: (factor) => ipcRenderer.invoke("set-zoom-factor", factor),
getPinchZoomEnabled: () => ipcRenderer.invoke("get-pinch-zoom-enabled"),
setPinchZoomEnabled: (enabled) => ipcRenderer.invoke("set-pinch-zoom-enabled", enabled),
onPinchZoomEnabledChanged: (cb) => {
const handler = (_: unknown, enabled: boolean) => cb(enabled)
ipcRenderer.on("pinch-zoom-enabled-changed", handler)
return () => ipcRenderer.removeListener("pinch-zoom-enabled-changed", handler)
},
onZoomFactorChanged: (cb) => {
const handler = (_: unknown, factor: number) => cb(factor)
ipcRenderer.on("zoom-factor-changed", handler)
return () => ipcRenderer.removeListener("zoom-factor-changed", handler)
},
setTitlebar: (theme) => ipcRenderer.invoke("set-titlebar", theme), setTitlebar: (theme) => ipcRenderer.invoke("set-titlebar", theme),
runDesktopMenuAction: (action) => ipcRenderer.invoke("run-desktop-menu-action", action), runDesktopMenuAction: (action) => ipcRenderer.invoke("run-desktop-menu-action", action),
loadingWindowComplete: () => ipcRenderer.send("loading-window-complete"), loadingWindowComplete: () => ipcRenderer.send("loading-window-complete"),

View File

@@ -79,6 +79,10 @@ export type ElectronAPI = {
relaunch: () => void relaunch: () => void
getZoomFactor: () => Promise<number> getZoomFactor: () => Promise<number>
setZoomFactor: (factor: number) => Promise<void> setZoomFactor: (factor: number) => Promise<void>
getPinchZoomEnabled: () => Promise<boolean>
setPinchZoomEnabled: (enabled: boolean) => Promise<void>
onPinchZoomEnabledChanged: (cb: (enabled: boolean) => void) => () => void
onZoomFactorChanged: (cb: (factor: number) => void) => () => void
setTitlebar: (theme: TitlebarTheme) => Promise<void> setTitlebar: (theme: TitlebarTheme) => Promise<void>
runDesktopMenuAction: (action: DesktopMenuAction) => Promise<void> runDesktopMenuAction: (action: DesktopMenuAction) => Promise<void>
loadingWindowComplete: () => void loadingWindowComplete: () => void

View File

@@ -21,7 +21,7 @@ import { createEffect, createResource, onCleanup, onMount, Show } from "solid-js
import { render } from "solid-js/web" import { render } from "solid-js/web"
import pkg from "../../package.json" import pkg from "../../package.json"
import { initI18n, t } from "./i18n" import { initI18n, t } from "./i18n"
import { resetZoom, webviewZoom, zoomIn, zoomOut } from "./webview-zoom" import { resetZoom, setPinchZoomEnabled, webviewZoom, zoomIn, zoomOut } from "./webview-zoom"
import "./styles.css" import "./styles.css"
import { useTheme } from "@opencode-ai/ui/theme" import { useTheme } from "@opencode-ai/ui/theme"
@@ -274,6 +274,10 @@ const createPlatform = (): Platform => {
webviewZoom, webviewZoom,
getPinchZoomEnabled: () => window.api.getPinchZoomEnabled(),
setPinchZoomEnabled,
runDesktopMenuAction, runDesktopMenuAction,
checkAppExists: async (appName: string) => { checkAppExists: async (appName: string) => {

View File

@@ -13,9 +13,21 @@ const OS_NAME = (() => {
const [webviewZoom, setWebviewZoom] = createSignal(1) const [webviewZoom, setWebviewZoom] = createSignal(1)
let requestedZoom = 1 let requestedZoom = 1
let pinchZoomEnabled = false
let wheelPinch = undefined as
| {
active: boolean
startZoom: number
totalDelta: number
timeout: ReturnType<typeof setTimeout> | undefined
}
| undefined
const MAX_ZOOM_LEVEL = 10 const MAX_ZOOM_LEVEL = 10
const MIN_ZOOM_LEVEL = 0.2 const MIN_ZOOM_LEVEL = 0.2
const WHEEL_PINCH_THRESHOLD = 20
const WHEEL_PINCH_STEP = 0.2
const WHEEL_PINCH_END_DELAY = 160
const clamp = (value: number) => Math.min(Math.max(value, MIN_ZOOM_LEVEL), MAX_ZOOM_LEVEL) const clamp = (value: number) => Math.min(Math.max(value, MIN_ZOOM_LEVEL), MAX_ZOOM_LEVEL)
@@ -33,10 +45,77 @@ const applyZoom = (next: number) => {
}) })
} }
window.api.onZoomFactorChanged((factor) => {
requestedZoom = clamp(factor)
setWebviewZoom(requestedZoom)
})
void window.api.getPinchZoomEnabled().then((enabled) => {
pinchZoomEnabled = enabled
})
window.api.onPinchZoomEnabledChanged((enabled) => {
pinchZoomEnabled = enabled
resetWheelPinch()
})
const setPinchZoomEnabled = (enabled: boolean) => {
pinchZoomEnabled = enabled
resetWheelPinch()
return window.api.setPinchZoomEnabled(enabled)
}
const resetZoom = () => applyZoom(1) const resetZoom = () => applyZoom(1)
const zoomIn = () => applyZoom(clamp(requestedZoom + 0.2)) const zoomIn = () => applyZoom(clamp(requestedZoom + 0.2))
const zoomOut = () => applyZoom(clamp(requestedZoom - 0.2)) const zoomOut = () => applyZoom(clamp(requestedZoom - 0.2))
const resetWheelPinch = () => {
clearTimeout(wheelPinch?.timeout)
wheelPinch = undefined
}
const normalizeWheelDelta = (event: WheelEvent) => {
if (event.deltaMode === WheelEvent.DOM_DELTA_LINE) return event.deltaY * 16
if (event.deltaMode === WheelEvent.DOM_DELTA_PAGE) return event.deltaY * window.innerHeight
return event.deltaY
}
const updateWheelPinch = (event: WheelEvent) => {
wheelPinch ??= {
active: false,
startZoom: requestedZoom,
totalDelta: 0,
timeout: undefined,
}
clearTimeout(wheelPinch.timeout)
wheelPinch.timeout = setTimeout(resetWheelPinch, WHEEL_PINCH_END_DELAY)
wheelPinch.totalDelta += normalizeWheelDelta(event)
if (!wheelPinch.active && Math.abs(wheelPinch.totalDelta) < WHEEL_PINCH_THRESHOLD) return
if (!wheelPinch.active) {
wheelPinch.active = true
wheelPinch.startZoom = requestedZoom
wheelPinch.totalDelta = 0
return
}
wheelPinch.active = true
applyZoom(clamp(wheelPinch.startZoom - (wheelPinch.totalDelta / WHEEL_PINCH_THRESHOLD) * WHEEL_PINCH_STEP))
}
window.addEventListener(
"wheel",
(event) => {
if (!pinchZoomEnabled) return
if (!event.ctrlKey) return
event.preventDefault()
updateWheelPinch(event)
},
{ passive: false },
)
window.addEventListener("keydown", (event) => { window.addEventListener("keydown", (event) => {
if (!(OS_NAME === "macos" ? event.metaKey : event.ctrlKey)) return if (!(OS_NAME === "macos" ? event.metaKey : event.ctrlKey)) return
@@ -56,4 +135,4 @@ window.addEventListener("keydown", (event) => {
} }
}) })
export { webviewZoom, resetZoom, zoomIn, zoomOut } export { webviewZoom, resetZoom, setPinchZoomEnabled, zoomIn, zoomOut }