most basic working being able to change key bindings.
This commit is contained in:
parent
9b5f1c5a04
commit
b58b65dabc
5 changed files with 180 additions and 28 deletions
BIN
bun.lockb
BIN
bun.lockb
Binary file not shown.
|
|
@ -19,6 +19,7 @@
|
||||||
"@tauri-apps/plugin-store": "~2",
|
"@tauri-apps/plugin-store": "~2",
|
||||||
"@tauri-apps/plugin-stronghold": "~2",
|
"@tauri-apps/plugin-stronghold": "~2",
|
||||||
"@tauri-apps/plugin-upload": "~2",
|
"@tauri-apps/plugin-upload": "~2",
|
||||||
|
"keycode": "^2.2.1",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"tailwindcss": "^4.0.2",
|
"tailwindcss": "^4.0.2",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use serde::Serialize;
|
||||||
use tauri::App;
|
use tauri::App;
|
||||||
use tauri::AppHandle;
|
use tauri::AppHandle;
|
||||||
use tauri::Manager;
|
use tauri::Manager;
|
||||||
|
|
@ -45,22 +46,54 @@ pub fn init_shortcuts(app: &App) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct BindingResponse {
|
||||||
|
success: bool,
|
||||||
|
binding: Option<ShortcutBinding>,
|
||||||
|
error: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn change_binding(app: AppHandle, id: String, binding: String) -> ShortcutBinding {
|
pub fn change_binding(
|
||||||
|
app: AppHandle,
|
||||||
|
id: String,
|
||||||
|
binding: String,
|
||||||
|
) -> Result<BindingResponse, String> {
|
||||||
let mut settings = settings::get_settings(&app);
|
let mut settings = settings::get_settings(&app);
|
||||||
|
|
||||||
// Get the binding to modify
|
// Get the binding to modify
|
||||||
let binding_to_modify = settings.bindings.get(&id).unwrap().clone();
|
let binding_to_modify = match settings.bindings.get(&id) {
|
||||||
|
Some(binding) => binding.clone(),
|
||||||
|
None => {
|
||||||
|
return Ok(BindingResponse {
|
||||||
|
success: false,
|
||||||
|
binding: None,
|
||||||
|
error: Some(format!("Binding with id '{}' not found", id)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Unregister the existing binding
|
// Unregister the existing binding
|
||||||
_unregister_shortcut(&app, binding_to_modify.clone());
|
if let Err(e) = _unregister_shortcut(&app, binding_to_modify.clone()) {
|
||||||
|
return Ok(BindingResponse {
|
||||||
|
success: false,
|
||||||
|
binding: None,
|
||||||
|
error: Some(format!("Failed to unregister shortcut: {}", e)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Create an updated binding
|
// Create an updated binding
|
||||||
let mut updated_binding = binding_to_modify;
|
let mut updated_binding = binding_to_modify;
|
||||||
updated_binding.current_binding = binding;
|
updated_binding.current_binding = binding;
|
||||||
|
|
||||||
// Register the new binding
|
// Register the new binding
|
||||||
_register_shortcut(&app, updated_binding.clone());
|
if let Err(e) = _register_shortcut(&app, updated_binding.clone()) {
|
||||||
|
return Ok(BindingResponse {
|
||||||
|
success: false,
|
||||||
|
binding: None,
|
||||||
|
error: Some(format!("Failed to register shortcut: {}", e)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Update the binding in the settings
|
// Update the binding in the settings
|
||||||
settings.bindings.insert(id, updated_binding.clone());
|
settings.bindings.insert(id, updated_binding.clone());
|
||||||
|
|
@ -69,17 +102,26 @@ pub fn change_binding(app: AppHandle, id: String, binding: String) -> ShortcutBi
|
||||||
settings::write_settings(&app, settings);
|
settings::write_settings(&app, settings);
|
||||||
|
|
||||||
// Return the updated binding
|
// Return the updated binding
|
||||||
updated_binding
|
Ok(BindingResponse {
|
||||||
|
success: true,
|
||||||
|
binding: Some(updated_binding),
|
||||||
|
error: None,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn reset_binding(app: AppHandle, id: String) -> ShortcutBinding {
|
pub fn reset_binding(app: AppHandle, id: String) -> Result<BindingResponse, String> {
|
||||||
let binding = settings::get_stored_binding(&app, &id);
|
let binding = settings::get_stored_binding(&app, &id);
|
||||||
|
|
||||||
return change_binding(app, id, binding.default_binding);
|
return change_binding(app, id, binding.default_binding);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _register_shortcut(app: &AppHandle, binding: ShortcutBinding) {
|
fn _register_shortcut(app: &AppHandle, binding: ShortcutBinding) -> Result<(), String> {
|
||||||
let shortcut = binding.current_binding.parse::<Shortcut>().unwrap();
|
// Parse shortcut and return error if it fails
|
||||||
|
let shortcut = match binding.current_binding.parse::<Shortcut>() {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => return Err(format!("Failed to parse shortcut: {}", e)),
|
||||||
|
};
|
||||||
|
|
||||||
app.global_shortcut()
|
app.global_shortcut()
|
||||||
.on_shortcut(shortcut, move |handler_app, scut, event| {
|
.on_shortcut(shortcut, move |handler_app, scut, event| {
|
||||||
|
|
@ -92,13 +134,20 @@ fn _register_shortcut(app: &AppHandle, binding: ShortcutBinding) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.expect("couldnt register shortcut");
|
.map_err(|e| format!("Couldn't register shortcut: {}", e))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _unregister_shortcut(app: &AppHandle, binding: ShortcutBinding) {
|
fn _unregister_shortcut(app: &AppHandle, binding: ShortcutBinding) -> Result<(), String> {
|
||||||
let shortcut = binding.current_binding.parse::<Shortcut>().unwrap();
|
let shortcut = match binding.current_binding.parse::<Shortcut>() {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => return Err(format!("Failed to parse shortcut: {}", e)),
|
||||||
|
};
|
||||||
|
|
||||||
app.global_shortcut()
|
app.global_shortcut()
|
||||||
.unregister(shortcut)
|
.unregister(shortcut)
|
||||||
.expect("couldnt unregister shortcut");
|
.map_err(|e| format!("Failed to unregister shortcut: {}", e))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,21 @@
|
||||||
import React, { useEffect } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { load } from "@tauri-apps/plugin-store";
|
import { load } from "@tauri-apps/plugin-store";
|
||||||
import {
|
import {
|
||||||
|
BindingResponseSchema,
|
||||||
SettingsSchema,
|
SettingsSchema,
|
||||||
ShortcutBinding,
|
|
||||||
ShortcutBindingSchema,
|
ShortcutBindingSchema,
|
||||||
ShortcutBindingsMap,
|
ShortcutBindingsMap,
|
||||||
} from "../../lib/types";
|
} from "../../lib/types";
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
import keycode from "keycode";
|
||||||
|
|
||||||
export const KeyboardShortcuts: React.FC = () => {
|
export const KeyboardShortcuts: React.FC = () => {
|
||||||
const [bindings, setBindings] = React.useState<ShortcutBindingsMap>({});
|
const [bindings, setBindings] = React.useState<ShortcutBindingsMap>({});
|
||||||
|
const [keyPressed, setKeyPressed] = useState<string[]>([]);
|
||||||
|
const [recordedKeys, setRecordedKeys] = useState<string[]>([]);
|
||||||
|
const [editingShortcutId, setEditingShortcutId] = useState<string | null>(
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
load("settings_store.json", { autoSave: false }).then((r) => {
|
load("settings_store.json", { autoSave: false }).then((r) => {
|
||||||
|
|
@ -20,17 +26,88 @@ export const KeyboardShortcuts: React.FC = () => {
|
||||||
setBindings(settings.bindings);
|
setBindings(settings.bindings);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// setTimeout(() => {
|
|
||||||
// console.log("invoked set binding");
|
|
||||||
// invoke("change_binding", { id: "test", binding: "alt+d" }).then((b) => {
|
|
||||||
// const newBinding = ShortcutBindingSchema.parse(b);
|
|
||||||
// console.log(bindings);
|
|
||||||
// setBindings((prev) => ({ ...prev, [newBinding.id]: newBinding }));
|
|
||||||
// });
|
|
||||||
// }, 1000);
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Only add event listeners when we're in editing mode
|
||||||
|
if (editingShortcutId === null) return;
|
||||||
|
|
||||||
|
console.log("keyPressed", keyPressed);
|
||||||
|
|
||||||
|
// Keyboard event listeners
|
||||||
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const key = keycode(e).toLowerCase();
|
||||||
|
console.log("You pressed", key);
|
||||||
|
|
||||||
|
if (!keyPressed.includes(key)) {
|
||||||
|
setKeyPressed((prev) => [...prev, key]);
|
||||||
|
// Also add to recorded keys if not already there
|
||||||
|
if (!recordedKeys.includes(key)) {
|
||||||
|
setRecordedKeys((prev) => [...prev, key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKeyUp = (e: KeyboardEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const key = keycode(e).toLowerCase();
|
||||||
|
|
||||||
|
// Remove from currently pressed keys
|
||||||
|
setKeyPressed((prev) => prev.filter((k) => k !== key));
|
||||||
|
|
||||||
|
// If no keys are pressed anymore, commit the shortcut
|
||||||
|
if (keyPressed.length === 1 && keyPressed[0] === key) {
|
||||||
|
// Create the shortcut string from all recorded keys
|
||||||
|
const newShortcut = recordedKeys.join("+");
|
||||||
|
|
||||||
|
if (editingShortcutId && bindings[editingShortcutId]) {
|
||||||
|
const updatedBinding = {
|
||||||
|
...bindings[editingShortcutId],
|
||||||
|
current_binding: newShortcut,
|
||||||
|
};
|
||||||
|
|
||||||
|
setBindings((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[editingShortcutId]: updatedBinding,
|
||||||
|
}));
|
||||||
|
|
||||||
|
invoke("change_binding", {
|
||||||
|
id: editingShortcutId,
|
||||||
|
binding: newShortcut,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Exit editing mode and reset states
|
||||||
|
setEditingShortcutId(null);
|
||||||
|
setKeyPressed([]);
|
||||||
|
setRecordedKeys([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("keydown", handleKeyDown);
|
||||||
|
window.addEventListener("keyup", handleKeyUp);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("keydown", handleKeyDown);
|
||||||
|
window.removeEventListener("keyup", handleKeyUp);
|
||||||
|
};
|
||||||
|
}, [keyPressed, recordedKeys, editingShortcutId, bindings]);
|
||||||
|
|
||||||
|
// Start recording a new shortcut
|
||||||
|
const startRecording = (id: string) => {
|
||||||
|
setEditingShortcutId(id);
|
||||||
|
setKeyPressed([]);
|
||||||
|
setRecordedKeys([]);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Format the current shortcut keys being recorded
|
||||||
|
const formatCurrentKeys = () => {
|
||||||
|
return recordedKeys.length > 0 ? recordedKeys.join("+") : "Press keys...";
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{Object.entries(bindings).map(([id, binding]) => (
|
{Object.entries(bindings).map(([id, binding]) => (
|
||||||
|
|
@ -45,15 +122,33 @@ export const KeyboardShortcuts: React.FC = () => {
|
||||||
<p className="text-sm text-gray-500">{binding.description}</p>
|
<p className="text-sm text-gray-500">{binding.description}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center space-x-1">
|
<div className="flex items-center space-x-1">
|
||||||
<div className="px-2 py-1 text-sm font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded">
|
{editingShortcutId === id ? (
|
||||||
{binding.current_binding}
|
<div className="px-2 py-1 text-sm font-semibold text-blue-600 bg-blue-50 border border-blue-200 rounded min-w-[100px] text-center">
|
||||||
</div>
|
{formatCurrentKeys()}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className="px-2 py-1 text-sm font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded cursor-pointer hover:bg-gray-200"
|
||||||
|
onClick={() => startRecording(id)}
|
||||||
|
>
|
||||||
|
{binding.current_binding}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<button
|
<button
|
||||||
className="px-2 py-1 text-sm font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded hover:bg-gray-50"
|
className="px-2 py-1 text-sm font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded hover:bg-gray-50"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
invoke("reset_binding", { id }).then((b) => {
|
invoke("reset_binding", { id }).then((b) => {
|
||||||
const newBinding = ShortcutBindingSchema.parse(b);
|
console.log("reset");
|
||||||
setBindings({ ...bindings, [newBinding.id]: newBinding });
|
const newBinding = BindingResponseSchema.parse(b);
|
||||||
|
|
||||||
|
if (!newBinding.success) {
|
||||||
|
console.error("Error resetting binding:", newBinding.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const binding = newBinding.binding!;
|
||||||
|
|
||||||
|
setBindings({ ...bindings, [binding.id]: binding });
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,13 @@ export const SettingsSchema = z.object({
|
||||||
bindings: ShortcutBindingsMapSchema,
|
bindings: ShortcutBindingsMapSchema,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const BindingResponseSchema = z.object({
|
||||||
|
success: z.boolean(),
|
||||||
|
binding: ShortcutBindingSchema.nullable(),
|
||||||
|
error: z.string().nullable(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type BindingResponse = z.infer<typeof BindingResponseSchema>;
|
||||||
export type ShortcutBinding = z.infer<typeof ShortcutBindingSchema>;
|
export type ShortcutBinding = z.infer<typeof ShortcutBindingSchema>;
|
||||||
export type ShortcutBindingsMap = z.infer<typeof ShortcutBindingsMapSchema>;
|
export type ShortcutBindingsMap = z.infer<typeof ShortcutBindingsMapSchema>;
|
||||||
export type Settings = z.infer<typeof SettingsSchema>;
|
export type Settings = z.infer<typeof SettingsSchema>;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue