Skip to content

Commit 6a742e1

Browse files
committed
feat: Add unlock/lock handling.
1 parent 8df6f1e commit 6a742e1

File tree

5 files changed

+169
-57
lines changed

5 files changed

+169
-57
lines changed

src/App.tsx

Lines changed: 62 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { AppHeader } from "./AppHeader";
22

33
import {
4+
call_rpc,
45
create_rpc_connection,
56
RpcConnection,
67
} from "@zmkfirmware/zmk-studio-ts-client";
@@ -22,7 +23,10 @@ import {
2223
} from "./tauri/serial";
2324
import Keyboard from "./keyboard/Keyboard";
2425
import { UndoRedoContext, useUndoRedo } from "./undoRedo";
25-
import { usePub } from "./usePubSub";
26+
import { usePub, useSub } from "./usePubSub";
27+
import { LockState } from "@zmkfirmware/zmk-studio-ts-client/core";
28+
import { LockStateContext } from "./rpc/LockStateContext";
29+
import { UnlockModal } from "./UnlockModal";
2630

2731
declare global {
2832
interface Window {
@@ -125,43 +129,70 @@ function App() {
125129
const [conn, setConn] = useState<RpcConnection | null>(null);
126130
const [doIt, undo, redo, canUndo, canRedo, reset] = useUndoRedo();
127131

132+
const [lockState, setLockState] = useState<LockState>(
133+
LockState.ZMK_STUDIO_CORE_LOCK_STATE_LOCKED
134+
);
135+
136+
useSub("rpc_notification.core.lockStateChanged", (ls) => {
137+
setLockState(ls);
138+
});
139+
128140
useEffect(() => {
129141
if (!conn) {
130142
reset();
143+
setLockState(LockState.ZMK_STUDIO_CORE_LOCK_STATE_LOCKED);
144+
}
145+
146+
async function updateLockState() {
147+
if (!conn) {
148+
return;
149+
}
150+
151+
let locked_resp = await call_rpc(conn, { core: { getLockState: true } });
152+
153+
setLockState(
154+
locked_resp.core?.getLockState ||
155+
LockState.ZMK_STUDIO_CORE_LOCK_STATE_LOCKED
156+
);
131157
}
132-
}, [conn]);
158+
159+
updateLockState();
160+
}, [conn, setLockState]);
133161

134162
return (
135163
<ConnectionContext.Provider value={conn}>
136-
<UndoRedoContext.Provider value={doIt}>
137-
<ConnectModal
138-
open={!conn}
139-
transports={TRANSPORTS}
140-
onTransportCreated={(t) => connect(t, setConn)}
141-
/>
142-
<div className="bg-bg-base text-text-base h-full grid grid-cols-[auto] grid-rows-[auto_1fr]">
143-
<AppHeader connectedDeviceLabel={conn?.label} />
144-
<Keyboard />
145-
<button
146-
type="button"
147-
className="disabled:text-gray-500"
148-
id="undo"
149-
disabled={!canUndo}
150-
onClick={() => undo()}
151-
>
152-
Undo
153-
</button>
154-
<button
155-
type="button"
156-
className="disabled:text-gray-500"
157-
id="redo"
158-
disabled={!canRedo}
159-
onClick={() => redo()}
160-
>
161-
Redo
162-
</button>
163-
</div>
164-
</UndoRedoContext.Provider>
164+
<LockStateContext.Provider value={lockState}>
165+
<UndoRedoContext.Provider value={doIt}>
166+
<UnlockModal />
167+
<ConnectModal
168+
open={!conn}
169+
transports={TRANSPORTS}
170+
onTransportCreated={(t) => connect(t, setConn)}
171+
/>
172+
<div className="bg-bg-base text-text-base h-full grid grid-cols-[auto] grid-rows-[auto_1fr]">
173+
<AppHeader connectedDeviceLabel={conn?.label} />
174+
<Keyboard />
175+
<button
176+
type="button"
177+
className="disabled:text-gray-500"
178+
id="undo"
179+
disabled={!canUndo}
180+
onClick={() => undo()}
181+
>
182+
Undo
183+
</button>
184+
<button
185+
type="button"
186+
className="disabled:text-gray-500"
187+
id="redo"
188+
disabled={!canRedo}
189+
onClick={() => redo()}
190+
>
191+
Redo
192+
</button>
193+
</div>
194+
</UndoRedoContext.Provider>
195+
</LockStateContext.Provider>
165196
</ConnectionContext.Provider>
166197
);
167198
}

src/UnlockModal.tsx

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { useContext, useEffect, useRef } from "react";
2+
3+
import type { RpcTransport } from "@zmkfirmware/zmk-studio-ts-client/transport/index";
4+
import type { AvailableDevice } from "./tauri/index";
5+
import { LockStateContext } from "./rpc/LockStateContext";
6+
import { LockState } from "@zmkfirmware/zmk-studio-ts-client/core";
7+
import { ConnectionContext } from "./rpc/ConnectionContext";
8+
9+
export type TransportFactory = {
10+
label: string;
11+
connect?: () => Promise<RpcTransport>;
12+
pick_and_connect?: {
13+
list: () => Promise<Array<AvailableDevice>>;
14+
connect: (dev: AvailableDevice) => Promise<RpcTransport>;
15+
};
16+
};
17+
18+
export interface UnlockModalProps {}
19+
20+
export const UnlockModal = ({}: UnlockModalProps) => {
21+
const dialog = useRef<HTMLDialogElement | null>(null);
22+
23+
let conn = useContext(ConnectionContext);
24+
let lockState = useContext(LockStateContext);
25+
26+
useEffect(() => {
27+
let open =
28+
!!conn && lockState != LockState.ZMK_STUDIO_CORE_LOCK_STATE_UNLOCKED;
29+
30+
if (dialog.current) {
31+
if (open) {
32+
if (!dialog.current.open) {
33+
dialog.current.showModal();
34+
}
35+
} else {
36+
dialog.current.close();
37+
}
38+
}
39+
}, [lockState, conn]);
40+
41+
return (
42+
<dialog ref={dialog} className="p-5 rounded-lg border-text-base border">
43+
<h1 className="text-xl">Unlock To Continue</h1>
44+
<p>
45+
For security reasons, your keyboard requires unlocking before using ZMK
46+
Studio.
47+
</p>
48+
</dialog>
49+
);
50+
};

src/keyboard/Keyboard.tsx

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,22 @@ import { ConnectionContext } from "../rpc/ConnectionContext";
2424
import { UndoRedoContext } from "../undoRedo";
2525
import { BehaviorBindingPicker } from "../behaviors/BehaviorBindingPicker";
2626
import { produce } from "immer";
27+
import { LockStateContext } from "../rpc/LockStateContext";
28+
import { LockState } from "@zmkfirmware/zmk-studio-ts-client/core";
2729

2830
type BehaviorMap = Record<number, GetBehaviorDetailsResponse>;
2931

3032
function useBehaviors(): BehaviorMap {
3133
let connection = useContext(ConnectionContext);
34+
let lockState = useContext(LockStateContext);
3235

3336
const [behaviors, setBehaviors] = useState<BehaviorMap>({});
3437

3538
useEffect(() => {
36-
if (!connection) {
39+
if (
40+
!connection ||
41+
lockState != LockState.ZMK_STUDIO_CORE_LOCK_STATE_UNLOCKED
42+
) {
3743
setBehaviors({});
3844
return;
3945
}
@@ -83,7 +89,7 @@ function useBehaviors(): BehaviorMap {
8389
return () => {
8490
ignore = true;
8591
};
86-
}, [connection]);
92+
}, [connection, lockState]);
8793

8894
return behaviors;
8995
}
@@ -95,6 +101,7 @@ function useLayouts(): [
95101
React.Dispatch<SetStateAction<number>>
96102
] {
97103
let connection = useContext(ConnectionContext);
104+
let lockState = useContext(LockStateContext);
98105

99106
const [layouts, setLayouts] = useState<PhysicalLayout[] | undefined>(
100107
undefined
@@ -103,7 +110,10 @@ function useLayouts(): [
103110
useState<number>(0);
104111

105112
useEffect(() => {
106-
if (!connection) {
113+
if (
114+
!connection ||
115+
lockState != LockState.ZMK_STUDIO_CORE_LOCK_STATE_UNLOCKED
116+
) {
107117
setLayouts(undefined);
108118
return;
109119
}
@@ -133,7 +143,7 @@ function useLayouts(): [
133143
return () => {
134144
ignore = true;
135145
};
136-
}, [connection]);
146+
}, [connection, lockState]);
137147

138148
return [
139149
layouts,
@@ -152,7 +162,8 @@ export default function Keyboard() {
152162
] = useLayouts();
153163
const [keymap, setKeymap] = useConnectedDeviceData<Keymap>(
154164
{ keymap: { getKeymap: true } },
155-
(keymap) => keymap?.keymap?.getKeymap
165+
(keymap) => keymap?.keymap?.getKeymap,
166+
true
156167
);
157168
const [selectedLayerIndex, setSelectedLayerIndex] = useState<number>(0);
158169
const [selectedKeyPosition, setSelectedKeyPosition] = useState<

src/rpc/LockStateContext.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { createContext } from "react";
2+
3+
import { LockState } from "@zmkfirmware/zmk-studio-ts-client/core";
4+
5+
export const LockStateContext = createContext<LockState>(
6+
LockState.ZMK_STUDIO_CORE_LOCK_STATE_LOCKED
7+
);

src/rpc/useConnectedDeviceData.ts

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,40 +6,53 @@ import {
66
Request,
77
RequestResponse,
88
} from "@zmkfirmware/zmk-studio-ts-client";
9+
import { LockStateContext } from "./LockStateContext";
10+
import { LockState } from "@zmkfirmware/zmk-studio-ts-client/core";
911

1012
export function useConnectedDeviceData<T>(
1113
req: Omit<Request, "requestId">,
12-
response_mapper: (resp: RequestResponse) => T | undefined
14+
response_mapper: (resp: RequestResponse) => T | undefined,
15+
requireUnlock?: boolean
1316
): [T | undefined, React.Dispatch<SetStateAction<T | undefined>>] {
1417
let connection = useContext(ConnectionContext);
18+
let lockState = useContext(LockStateContext);
1519
let [data, setData] = useState<T | undefined>(undefined);
1620

17-
useEffect(() => {
18-
if (!connection) {
19-
setData(undefined);
20-
return;
21-
}
22-
23-
async function startRequest() {
24-
setData(undefined);
25-
if (!connection) {
21+
useEffect(
22+
() => {
23+
if (
24+
!connection ||
25+
(requireUnlock &&
26+
lockState != LockState.ZMK_STUDIO_CORE_LOCK_STATE_UNLOCKED)
27+
) {
28+
setData(undefined);
2629
return;
2730
}
2831

29-
let response = response_mapper(await call_rpc(connection, req));
32+
async function startRequest() {
33+
setData(undefined);
34+
if (!connection) {
35+
return;
36+
}
3037

31-
if (!ignore) {
32-
setData(response);
33-
}
34-
}
38+
let response = response_mapper(await call_rpc(connection, req));
3539

36-
let ignore = false;
37-
startRequest();
40+
if (!ignore) {
41+
setData(response);
42+
}
43+
}
3844

39-
return () => {
40-
ignore = true;
41-
};
42-
}, [connection]);
45+
let ignore = false;
46+
startRequest();
47+
48+
return () => {
49+
ignore = true;
50+
};
51+
},
52+
requireUnlock
53+
? [connection, requireUnlock, lockState]
54+
: [connection, requireUnlock]
55+
);
4356

4457
return [data, setData];
4558
}

0 commit comments

Comments
 (0)