Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
- name: Install Node.js, NPM and Yarn
uses: actions/setup-node@v6
with:
node-version: 18
node-version: 22

- name: Prepare for app notarization
if: startsWith(matrix.os, 'macos')
Expand Down
25 changes: 3 additions & 22 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,27 +182,8 @@ libnspr4

### Wayland

Due to [an issue in Electron](https://github.com/electron/electron/issues/38288), the global hotkey will not work in all applications running under Wayland. In KDE it is possible to work around this limitation by adding this Kwin script:

```javascript
function toggleHeynote() {
var client = workspace.clientList().find((c) => c.resourceClass.toLowerCase() === 'heynote');
if (client) {
if (client.minimized) {
client.minimized = false;
workspace.activeClient = client;
} else {
if (workspace.activeClient == client) {
client.minimized = true;
} else {
workspace.activeClient = client;
}
}
}
}
registerShortcut('toggleHeynote', 'Toggle Heynote', 'Ctrl+Shift+H', toggleHeynote);
```
Heynote uses the [XDG Desktop Portal GlobalShortcuts interface](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.GlobalShortcuts.html) for global hotkeys in Wayland sessions. The desktop environment controls this interface, so it may ask you to confirm or configure the shortcut and can reject shortcuts already reserved by the system or another application.

See the [KWin scripting tutorial](https://develop.kde.org/docs/plasma/kwin/) for instructions on how to install the script.
For KDE Plasma, make sure `xdg-desktop-portal-kde` is installed and select a shortcut that is not already assigned in System Settings. If a global hotkey cannot be registered, Heynote will show an error explaining that it may be in use or reserved.

Remember to enable the script in the KDE System Settings. It may also be necessary to go into the KDE System Settings and bind the "Toggle Heynote" key manually.
If you previously installed the old Heynote KWin-script workaround, it can continue to be used on its own. Do not enable Heynote's built-in global hotkey with the same shortcut while that script is enabled, or KDE will report a shortcut conflict. To use Heynote's built-in global hotkey, disable the old KWin script first.
49 changes: 49 additions & 0 deletions electron/main/global-hotkey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
type AppWithCommandLine = {
commandLine: {
appendSwitch: (switchName: string, value?: string) => void
}
}

type GlobalShortcut = {
register: (accelerator: string, callback: () => void) => boolean
unregisterAll: () => void
}

type GlobalHotkeyRegistration = {
accelerator: string
callback: () => void
enabled: boolean
globalShortcut: GlobalShortcut
}

export type GlobalHotkeyRegistrationResult =
| { success: true }
| { success: false, message: string }

export function enableWaylandGlobalShortcutsPortal(
app: AppWithCommandLine,
isLinux: boolean,
sessionType = process.env.XDG_SESSION_TYPE,
waylandDisplay = process.env.WAYLAND_DISPLAY,
) {
if (isLinux && (sessionType?.toLowerCase() === "wayland" || waylandDisplay)) {
app.commandLine.appendSwitch("enable-features", "GlobalShortcutsPortal")
}
}

export function updateGlobalHotkeyRegistration({
accelerator,
callback,
enabled,
globalShortcut,
}: GlobalHotkeyRegistration) {
globalShortcut.unregisterAll()
if (!enabled || globalShortcut.register(accelerator, callback)) {
return { success: true }
}

return {
success: false,
message: `Could not register global hotkey "${accelerator}". It may already be in use or reserved by the desktop environment.`,
}
}
36 changes: 25 additions & 11 deletions electron/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { isDev, isLinux, isMac, isWindows } from '../detect-platform';
import { initializeAutoUpdate, checkForUpdates, updateAutoInstallUpdates } from './auto-update';
import { fixElectronCors } from './cors';
import { ensureWindowBoundsVisible } from './window-bounds';
import { enableWaylandGlobalShortcutsPortal, updateGlobalHotkeyRegistration } from './global-hotkey';
import {
FileLibrary,
setupFileLibraryEventHandlers,
Expand Down Expand Up @@ -46,6 +47,8 @@ process.env.PUBLIC = process.env.VITE_DEV_SERVER_URL
// Disable GPU Acceleration for Windows 7
if (release().startsWith('6.1')) app.disableHardwareAcceleration()

enableWaylandGlobalShortcutsPortal(app, isLinux)

// Set application name for Windows 10+ notifications
if (isWindows) app.setAppUserModelId(app.getName())

Expand Down Expand Up @@ -378,10 +381,12 @@ function createTray() {
}

function registerGlobalHotkey() {
globalShortcut.unregisterAll()
if (CONFIG.get("settings.enableGlobalHotkey")) {
try {
const ret = globalShortcut.register(CONFIG.get("settings.globalHotkey"), () => {
try {
const result = updateGlobalHotkeyRegistration({
accelerator: CONFIG.get("settings.globalHotkey"),
enabled: CONFIG.get("settings.enableGlobalHotkey"),
globalShortcut,
callback: () => {
if (!win) {
return
}
Expand All @@ -406,10 +411,16 @@ function registerGlobalHotkey() {
} else {
showWindow()
}
})
} catch (error) {
console.log("Could not register global hotkey:", error)
},
})
if (!result.success) {
console.log(result.message)
return result.message
}
} catch (error) {
const errorMessage = `Could not register global hotkey: ${error.message}`
console.log(errorMessage)
return errorMessage
}
}

Expand Down Expand Up @@ -504,7 +515,10 @@ app.whenReady().then(createWindow).then(async () => {
}
})
initializeAutoUpdate(win)
registerGlobalHotkey()
const globalHotkeyError = registerGlobalHotkey()
if (globalHotkeyError) {
initErrors.push(globalHotkeyError)
}
registerShowInDock()
registerShowInMenu()
registerAlwaysOnTop()
Expand Down Expand Up @@ -689,9 +703,7 @@ ipcMain.handle('settings:set', async (event, settings) => {

win?.webContents.send(SETTINGS_CHANGE_EVENT, settings)

if (globalHotkeyChanged) {
registerGlobalHotkey()
}
const globalHotkeyError = globalHotkeyChanged ? registerGlobalHotkey() : undefined
if (showInDockChanged) {
registerShowInDock()
}
Expand All @@ -715,6 +727,8 @@ ipcMain.handle('settings:set', async (event, settings) => {
await initFileLibrary(win)
win.webContents.send("library:pathChanged")
}

return { globalHotkeyError }
})

ipcMain.handle(SAVE_TABS_STATE, async (event, tabsState) => {
Expand Down
6 changes: 6 additions & 0 deletions electron/main/protocol-privileges.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const heynoteFileProtocolPrivileges = {
standard: true,
secure: true,
supportFetchAPI: true,
corsEnabled: true,
}
3 changes: 2 additions & 1 deletion electron/main/protocol.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { protocol } from "electron"
import * as path from "node:path"
import * as jetpack from "fs-jetpack"
import * as mimetypes from "mime-types"
import { heynoteFileProtocolPrivileges } from "./protocol-privileges"

let activeFileLibrary = null
let protocolHandlerRegistered = false
Expand All @@ -10,7 +11,7 @@ let protocolHandlerRegistered = false
export function registerProtocolBeforeAppReady() {
protocol.registerSchemesAsPrivileged([{
scheme: "heynote-file",
privileges: { standard: true, secure: true, supportFetchAPI: true },
privileges: heynoteFileProtocolPrivileges,
}])
//console.log("registering heynote-file:// as privileged")
}
Expand Down
2 changes: 1 addition & 1 deletion electron/preload/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ contextBridge.exposeInMainWorld("heynote", {
settings: CONFIG.get("settings"),

setSettings(settings) {
ipcRenderer.invoke("settings:set", settings)
return ipcRenderer.invoke("settings:set", settings)
},

async getCurrencyData() {
Expand Down
Loading
Loading