Skip to content

Commit

Permalink
Windows & Linux AltGr Fix (#1538)
Browse files Browse the repository at this point in the history
Co-authored-by: Matt Holtzman <matt.holtzman@gmail.com>
  • Loading branch information
goosewobbler and mholtzman authored May 4, 2023
1 parent 3d23fa3 commit b9891f3
Show file tree
Hide file tree
Showing 13 changed files with 590 additions and 188 deletions.
5 changes: 5 additions & 0 deletions @types/frame/environment.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { KeyboardLayout } from '../../resources/keyboard'

declare global {
namespace NodeJS {
interface ProcessEnv {
Expand All @@ -7,6 +9,9 @@ declare global {
LOG_LEVEL?: 'silly' | 'debug' | 'verbose' | 'info' | 'warn' | 'error'
}
}
interface Navigator {
keyboard: { getLayoutMap: () => Promise<KeyboardLayout> }
}
}

export {}
2 changes: 1 addition & 1 deletion app/onboard/App/Slides/Access/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Slide, SlideBody, SlideItem, Shortcut } from '../../styled'

import link from '../../../../../resources/link'

import { getDisplayShortcut } from '../../../../../resources/app'
import { getDisplayShortcut } from '../../../../../resources/keyboard'

const Access = ({ setTitle, setProceed, platform }) => {
const { modifierKeys, shortcutKey } = getDisplayShortcut(platform, store('main.shortcuts.summon'))
Expand Down
2 changes: 1 addition & 1 deletion eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export default [
files: [
'app/**/*.js',
'main/dapps/server/inject/*.js',
'resources/app/**/*.js',
'resources/keyboard/**/*.js',
'resources/Components/**/*.js',
'resources/Hooks/**/*.js',
'resources/Native/**/*.js',
Expand Down
90 changes: 90 additions & 0 deletions main/keyboardShortcuts/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { globalShortcut } from 'electron'
import log from 'electron-log'

import store from '../store'
import { shortcutKeyMap } from '../../resources/keyboard/mappings'
import type { Shortcut } from '../store/state/types/shortcuts'

const stringifyShortcut = ({ modifierKeys, shortcutKey }: Shortcut) => ({
shortcutString: [...modifierKeys, shortcutKey].join('+'),
accelerator: [...modifierKeys.slice().sort(), shortcutKeyMap[shortcutKey] || shortcutKey].join('+')
})

const equivalentShortcuts = (shortcut1: Shortcut, shortcut2: Shortcut) =>
shortcut1.modifierKeys === shortcut2.modifierKeys && shortcut1.shortcutKey === shortcut2.shortcutKey

function unregister(shortcut: Shortcut) {
const { shortcutString, accelerator } = stringifyShortcut(shortcut)

log.verbose(`Unregistering accelerator "${accelerator}" for shortcut: ${shortcutString}`)

try {
globalShortcut.unregister(accelerator)
} catch (e) {
log.error(`Failed to unregister accelerator "${accelerator}" for shortcut: ${shortcutString}`, e)
}
}

function register(shortcut: Shortcut, shortcutHandler: (accelerator: string) => void) {
const { shortcutString, accelerator } = stringifyShortcut(shortcut)

log.verbose(`Registering accelerator "${accelerator}" for shortcut: ${shortcutString}`)

try {
if (shortcut.enabled && !shortcut.configuring) {
globalShortcut.register(accelerator, () => shortcutHandler(accelerator))
log.info(`Accelerator "${accelerator}" registered for shortcut: ${shortcutString}`)
}
} catch (e) {
log.error(`Failed to register accelerator "${accelerator}" for shortcut: ${shortcutString}`, e)
}
}

export const registerShortcut = (shortcut: Shortcut, shortcutHandler: (accelerator: string) => void) => {
const isWindows = process.platform === 'win32'
const isMacOS = process.platform === 'darwin'
const keyboardLayout = store('keyboardLayout')
const createAltGrShortcut = () => {
// remove AltGr and Alt from modifiers (Linux)
// remove AltGr, Alt and Control from modifiers (Windows)
const modifierKeys = shortcut.modifierKeys.filter((modifier) =>
isWindows ? !modifier.startsWith('Alt') && modifier !== 'Control' : !modifier.startsWith('Alt')
)

// return new modifiers depending on OS + rest of shortcut - so that AltGr / Right Alt triggers in the same way as Left Alt
return {
...shortcut,
modifierKeys: (isWindows
? [...modifierKeys, 'Control', 'Alt']
: [...modifierKeys, 'AltGr']) as typeof shortcut.modifierKeys
}
}

// Windows & Linux Non-US key layout AltGr / Right Alt fix
if (!isMacOS) {
const altGrShortcut = createAltGrShortcut()

// unregister any existing AltGr shortcut - unless it matches the one we are about to register
if (!keyboardLayout.isUS && !equivalentShortcuts(shortcut, altGrShortcut)) {
unregister(altGrShortcut)
}

if (
shortcut.modifierKeys.includes('AltGr') ||
(shortcut.modifierKeys.includes('Alt') && !keyboardLayout.isUS)
) {
// register the AltGr shortcut
register(altGrShortcut, shortcutHandler)

// replace AltGr with Alt in the main shortcut
shortcut = {
...shortcut,
modifierKeys: shortcut.modifierKeys.map((key) => (key === 'AltGr' ? 'Alt' : key))
}
}
}

// register the shortcut
unregister(shortcut)
register(shortcut, shortcutHandler)
}
5 changes: 5 additions & 0 deletions main/store/actions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,11 @@ module.exports = {
enabled: shortcut.enabled ?? existingShortcut.enabled
}))
},
setKeyboardLayout: (u, layout) => {
u('keyboardLayout', (existingLayout = {}) => ({
isUS: layout.isUS ?? existingLayout.isUS
}))
},
setAutohide: (u, v) => {
u('main.autohide', () => v)
},
Expand Down
3 changes: 3 additions & 0 deletions main/store/state/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,9 @@ const initial = {
rates: {}
},
platform: process.platform,
keyboardLayout: {
isUS: true
},
main: {
_version: main('_version', 32),
instanceId: main('instanceId', generateUuid()),
Expand Down
2 changes: 1 addition & 1 deletion main/store/state/types/shortcuts.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { z } from 'zod'

const supportedModifierKey = z.enum(['Alt', 'Control', 'Meta', 'Super', 'CommandOrCtrl'])
const supportedModifierKey = z.enum(['Alt', 'AltGr', 'Control', 'Meta', 'Super', 'CommandOrCtrl'])

const supportedShortcutKey = z.enum([
'Comma',
Expand Down
38 changes: 12 additions & 26 deletions main/windows/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ import store from '../store'
import FrameManager from './frames'
import { createWindow } from './window'
import { SystemTray, SystemTrayEventHandlers } from './systemTray'
import { getAcceleratorFromShortcut } from '../../resources/app'
import { registerShortcut } from '../keyboardShortcuts'
import { Shortcut } from '../store/state/types/shortcuts'

type Windows = { [key: string]: BrowserWindow }

Expand All @@ -39,7 +40,6 @@ let dash: Dash
let onboard: Onboard
let mouseTimeout: NodeJS.Timeout
let glide = false
let summonShortcutAccelerator = 'Alt+/'

const app = {
hide: () => {
Expand Down Expand Up @@ -554,35 +554,21 @@ const init = () => {

store.observer(() => broadcast('permissions', JSON.stringify(store('permissions'))))
store.observer(() => {
const summonShortcut = store('main.shortcuts.summon')
const accelerator = getAcceleratorFromShortcut(summonShortcut)
try {
globalShortcut.unregister(accelerator)
if (summonShortcutAccelerator) {
globalShortcut.unregister(summonShortcutAccelerator)
let summonShortcut: Shortcut = store('main.shortcuts.summon')
const summonHandler = (accelerator: string) => {
app.toggle()
if (store('windows.onboard.showing')) {
send('onboard', 'main:flex', 'shortcutActivated')
}
if (summonShortcut.enabled && !summonShortcut.configuring) {
globalShortcut.register(accelerator, () => {
app.toggle()
if (store('windows.onboard.showing')) {
send('onboard', 'main:flex', 'shortcutActivated')
}
if (tray?.isReady()) {
systemTray.setContextMenu(tray.isVisible() ? 'hide' : 'show', {
displaySummonShortcut: summonShortcut.enabled,
accelerator
})
summonShortcutAccelerator = accelerator
}
} catch (e) {
const summonShortcutStr = [...summonShortcut.modifierKeys, summonShortcut.shortcutKey].join('+')
log.error(
new Error(`Could not set accelerator "${accelerator}" for summon shortcut: ${summonShortcutStr}`)
)
}

if (tray?.isReady()) {
systemTray.setContextMenu(tray.isVisible() ? 'hide' : 'show', {
displaySummonShortcut: summonShortcut.enabled,
accelerator
})
}
registerShortcut(summonShortcut, summonHandler)
})
}

Expand Down
8 changes: 4 additions & 4 deletions resources/Components/KeyboardShortcutConfigurator/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React, { useEffect } from 'react'
import { useEffect } from 'react'
import hotkeys from 'hotkeys-js'

import link from '../../../resources/link'
import { getShortcutFromKeyEvent, getDisplayShortcut, isShortcutKey } from '../../../resources/app'
import { getShortcutFromKeyEvent, getDisplayShortcut, isShortcutKey } from '../../keyboard'

const KeyboardShortcutConfigurator = ({ actionText = '', platform, shortcut, shortcutName }) => {
const { modifierKeys, shortcutKey } = getDisplayShortcut(platform, shortcut)
Expand All @@ -12,12 +12,12 @@ const KeyboardShortcutConfigurator = ({ actionText = '', platform, shortcut, sho
hotkeys('*', { capture: true }, (event) => {
event.preventDefault()

const allowedModifierKeys = ['Meta', 'Alt', 'Control', 'Command']
const allowedModifierKeys = ['Meta', 'Alt', 'AltGr', 'Control', 'Command']
const isModifierKey = allowedModifierKeys.includes(event.key)

// ignore modifier key solo keypresses and disabled keys
if (!isModifierKey && isShortcutKey(event)) {
const newShortcut = getShortcutFromKeyEvent(event)
const newShortcut = getShortcutFromKeyEvent(event, hotkeys.getPressedKeyCodes(), platform)
// enable the new shortcut
link.send('tray:action', 'setShortcut', shortcutName, {
...newShortcut,
Expand Down
Loading

0 comments on commit b9891f3

Please sign in to comment.