From 9a07e547ff6884b07ff7b72fe0dddb27d0c577bf Mon Sep 17 00:00:00 2001 From: Christoph Raitzig Date: Sun, 30 Jun 2024 17:34:56 +0200 Subject: [PATCH 1/3] Add dialog with instructions on fixing Failed to open device on Linux --- src/components/modals/information.tsx | 46 +++++++++++++++ src/components/monospace-text/index.tsx | 58 +++++++++++++++++++ src/components/panes/errors.tsx | 77 ++++++++++++++++--------- src/store/errorsSlice.ts | 2 + src/utils/keyboard-api.ts | 4 ++ src/utils/user-fixable-errors.tsx | 49 ++++++++++++++++ 6 files changed, 209 insertions(+), 27 deletions(-) create mode 100644 src/components/modals/information.tsx create mode 100644 src/components/monospace-text/index.tsx create mode 100644 src/utils/user-fixable-errors.tsx diff --git a/src/components/modals/information.tsx b/src/components/modals/information.tsx new file mode 100644 index 00000000..248750d6 --- /dev/null +++ b/src/components/modals/information.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import styled from 'styled-components'; +import { AccentButtonLarge } from '../inputs/accent-button'; + +interface InformationModalProps { + children?: React.ReactNode; + closeModal: Function; +} + +const ModalBackgroundDiv = styled.div` + background-color: rgba(0,0,0,0.7); + display: flex; + flex-wrap: wrap; + position: fixed; + right: 0; + top: 0; + justify-content: center; + align-content: center; + height: 100%; + width: 100%; + z-index: 2; + padding: 0; + margin: 0; +`; + +const ModalDiv = styled.div` + background: var(--bg_gradient); + display: flex; + flex-direction: column; + justify-content: center; + align-content: center; + height: wrap-content; + max-width: 80em; + border: 0.5em solid var(--border_color_cell); + border-radius: 0.5em; + padding: 1em; +`; + +export const InformationModal: React.FC = ({ children, closeModal }) => ( + + + {children} + closeModal()}>Okay + + +); diff --git a/src/components/monospace-text/index.tsx b/src/components/monospace-text/index.tsx new file mode 100644 index 00000000..c26c18f7 --- /dev/null +++ b/src/components/monospace-text/index.tsx @@ -0,0 +1,58 @@ +import React from 'react'; +import styled from 'styled-components'; +import { IconButtonContainer } from '../inputs/icon-button'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { IconButtonTooltip } from '../inputs/tooltip'; +import { faCopy } from '@fortawesome/free-solid-svg-icons'; + +interface MonospaceTextProps { + text: string; +} + +const TextComponent = styled.div` + font-family: monospace; + position: relative; +`; + +const ClipboardButtonDiv = styled.div` + position: absolute; + top: 0; + right: 0; +`; + +export const MonospaceText: React.FC = ({ text }) => { + return ( + +
    + {text?.split('\n').map((line, i) => ( +
  • + {line.split('').map((char, i) => { + if (char === '\t') { + return <> +   +   +   +   + + } + if (char === ' ') { + return  ; + } + return {char} + })} +
  • + ))} +
+ + navigator.clipboard.writeText(text)}> + + Copy to clipboard + + +
+ ) +}; diff --git a/src/components/panes/errors.tsx b/src/components/panes/errors.tsx index a11639b4..e3cb7991 100644 --- a/src/components/panes/errors.tsx +++ b/src/components/panes/errors.tsx @@ -4,6 +4,7 @@ import { faComputer, faDownload, faKeyboard, + faMagicWandSparkles, faWarning, } from '@fortawesome/free-solid-svg-icons'; import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; @@ -31,6 +32,7 @@ import { CategoryIconContainer, } from './grid'; import {Pane} from './pane'; +import { InformationModal } from '../modals/information'; const Container = styled.div` display: flex; @@ -81,34 +83,55 @@ const ErrorListContainer: React.FC< const AppErrors: React.FC<{}> = ({}) => { const errors = useAppSelector(getAppErrors); const dispatch = useDispatch(); + const [fixDialog, setFixDialog] = useState(undefined); return ( - dispatch(clearAppErrors())} - save={() => saveAppErrors(errors)} - hasErrors={!!errors.length} - > - {errors.map( - ({ - timestamp, - deviceInfo: {productId, productName, vendorId}, - message: error, - }) => ( - - {timestamp} -
    - {error?.split('\n').map((line) => ( -
  • {line}
  • - ))} -
-
    -
  • Device: {productName}
  • -
  • Vid: {printId(vendorId)}
  • -
  • Pid: {printId(productId)}
  • -
-
- ), - )} -
+ <> + {fixDialog !== undefined && fixDialog} + dispatch(clearAppErrors())} + save={() => saveAppErrors(errors)} + hasErrors={!!errors.length} + > + {errors.map( + ({ + timestamp, + deviceInfo: {productId, productName, vendorId}, + message: error, + isPotentiallyUserFixable: isPotentiallyUserFixable, + userFix: userFix, + }) => ( + + {timestamp} +
    + {error?.split('\n').map((line, i) => ( +
  • {line}
  • + ))} +
+
    +
  • Device: {productName}
  • +
  • Vid: {printId(vendorId)}
  • +
  • Pid: {printId(productId)}
  • +
+ {isPotentiallyUserFixable && userFix !== undefined && + setFixDialog( + setFixDialog(undefined)}> + {userFix()} + + )} + > + + Fix + + } +
+ ), + )} +
+ ); }; diff --git a/src/store/errorsSlice.ts b/src/store/errorsSlice.ts index c202c91e..c2033818 100644 --- a/src/store/errorsSlice.ts +++ b/src/store/errorsSlice.ts @@ -13,6 +13,8 @@ export type AppError = { timestamp: string; message: string; deviceInfo: DeviceInfo; + isPotentiallyUserFixable?: boolean; + userFix?: () => JSX.Element | undefined; }; export const extractDeviceInfo = (device: DeviceInfo): DeviceInfo => ({ diff --git a/src/utils/keyboard-api.ts b/src/utils/keyboard-api.ts index 56118378..0c5f38df 100644 --- a/src/utils/keyboard-api.ts +++ b/src/utils/keyboard-api.ts @@ -10,6 +10,7 @@ import { logAppError, logKeyboardAPIError, } from 'src/store/errorsSlice'; +import { getUserFixForError } from './user-fixable-errors'; // VIA Command IDs @@ -625,10 +626,13 @@ export class KeyboardAPI { res(ans); } catch (e: any) { const deviceInfo = extractDeviceInfo(this.getHID()); + const [isPotentiallyUserFixable, userFix] = getUserFixForError(e, deviceInfo); store.dispatch( logAppError({ message: getMessageFromError(e), deviceInfo, + isPotentiallyUserFixable, + userFix, }), ); rej(e); diff --git a/src/utils/user-fixable-errors.tsx b/src/utils/user-fixable-errors.tsx new file mode 100644 index 00000000..1dbf9ded --- /dev/null +++ b/src/utils/user-fixable-errors.tsx @@ -0,0 +1,49 @@ +import { MonospaceText } from 'src/components/monospace-text'; +import { DeviceInfo } from 'src/types/types'; + +const LinuxHidrawFix = (deviceInfo: DeviceInfo): () => JSX.Element => { + const textUdev = `SUBSYSTEM=="usb", ATTR{idVendor}=="${deviceInfo.vendorId.toString(16).toUpperCase().padStart(4, '0')}", ATTR{idProduct}=="${deviceInfo.productId.toString(16).toUpperCase().padStart(4, '0')}", TAG+="uaccess"" +KERNEL=="hidraw*", MODE="0660", TAG+="uaccess", TAG+="udev-acl"` + + const textDevHidraw = `#!/bin/bash +HID_NAME='${deviceInfo.productName}' +# loop over possible devices +for f in /dev/hidraw* +do + DEVICE_NAME=$(basename \${f}) + if grep "$HID_NAME" "/sys/class/hidraw/\${DEVICE_NAME}/device/uevent"; + then + # device matches product name + echo Running sudo chmod a+rw "$f" + sudo chmod a+rw "$f" + fi +done`; + + const textRunDevHidrawScript = `bash script.sh`; + + return () => ( +
+

This error can happen on Linux when the browser is not allowed to access the keyboard HID device. You can fix this either permanently or temporarily.

+

Create udev rule to fix it permanently. To do this, create a file called

+ {/* Rule has to precede /usr/lib/udev/rules.d/73-seat-late.rules, see https://wiki.archlinux.org/title/Udev#Allowing_regular_users_to_use_devices */} + +

with the following content:

+ +

Unplug and plug your keyboard back in. Reload this website and it should work.

+

If you want to temporarily allow the browser access to the keyboard device, create a script with the following content:

+ +

And run it:

+ +

Reload this website and it should work.

+
+ ) +}; + +export function getUserFixForError(e: any, deviceInfo: DeviceInfo): [boolean, () => JSX.Element | undefined] { + if (e instanceof DOMException) { + if (e.name === 'NotAllowedError' && e.message.includes('Failed to open the device')) { + return [true, LinuxHidrawFix(deviceInfo)]; + } + } + return [false, () => undefined]; +} From f2b31678ca7afd35f78006d419a8428623825426 Mon Sep 17 00:00:00 2001 From: Christoph Raitzig Date: Sun, 30 Jun 2024 17:48:04 +0200 Subject: [PATCH 2/3] Change to trigger udev rule reload by software instead of hardware --- src/utils/user-fixable-errors.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/utils/user-fixable-errors.tsx b/src/utils/user-fixable-errors.tsx index 1dbf9ded..fc2ab4cf 100644 --- a/src/utils/user-fixable-errors.tsx +++ b/src/utils/user-fixable-errors.tsx @@ -5,6 +5,8 @@ const LinuxHidrawFix = (deviceInfo: DeviceInfo): () => JSX.Element => { const textUdev = `SUBSYSTEM=="usb", ATTR{idVendor}=="${deviceInfo.vendorId.toString(16).toUpperCase().padStart(4, '0')}", ATTR{idProduct}=="${deviceInfo.productId.toString(16).toUpperCase().padStart(4, '0')}", TAG+="uaccess"" KERNEL=="hidraw*", MODE="0660", TAG+="uaccess", TAG+="udev-acl"` + const textUdevApplyRule = 'sudo udevadm control --reload-rules && sudo udevadm trigger'; + const textDevHidraw = `#!/bin/bash HID_NAME='${deviceInfo.productName}' # loop over possible devices @@ -19,7 +21,7 @@ do fi done`; - const textRunDevHidrawScript = `bash script.sh`; + const textRunDevHidrawScript = 'bash script.sh'; return () => (
@@ -29,7 +31,9 @@ done`;

with the following content:

-

Unplug and plug your keyboard back in. Reload this website and it should work.

+

Finally run:

+ +

Reload this website and it should work.

If you want to temporarily allow the browser access to the keyboard device, create a script with the following content:

And run it:

From fdd0f3591ed9c1790500d247b48225e488af6a76 Mon Sep 17 00:00:00 2001 From: Christoph Raitzig Date: Tue, 2 Jul 2024 23:39:04 +0200 Subject: [PATCH 3/3] Remove superfluous double quote --- src/utils/user-fixable-errors.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/user-fixable-errors.tsx b/src/utils/user-fixable-errors.tsx index fc2ab4cf..e38e3735 100644 --- a/src/utils/user-fixable-errors.tsx +++ b/src/utils/user-fixable-errors.tsx @@ -2,7 +2,7 @@ import { MonospaceText } from 'src/components/monospace-text'; import { DeviceInfo } from 'src/types/types'; const LinuxHidrawFix = (deviceInfo: DeviceInfo): () => JSX.Element => { - const textUdev = `SUBSYSTEM=="usb", ATTR{idVendor}=="${deviceInfo.vendorId.toString(16).toUpperCase().padStart(4, '0')}", ATTR{idProduct}=="${deviceInfo.productId.toString(16).toUpperCase().padStart(4, '0')}", TAG+="uaccess"" + const textUdev = `SUBSYSTEM=="usb", ATTR{idVendor}=="${deviceInfo.vendorId.toString(16).toUpperCase().padStart(4, '0')}", ATTR{idProduct}=="${deviceInfo.productId.toString(16).toUpperCase().padStart(4, '0')}", TAG+="uaccess" KERNEL=="hidraw*", MODE="0660", TAG+="uaccess", TAG+="udev-acl"` const textUdevApplyRule = 'sudo udevadm control --reload-rules && sudo udevadm trigger';