diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 6514857..9904ddc 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1 +1,3 @@ github: 0pandadev +buy_me_a_coffee: pandadev_ +ko_fi: pandadev_ diff --git a/README.md b/README.md index 0caf872..982721b 100644 --- a/README.md +++ b/README.md @@ -5,31 +5,31 @@ The fixed and simple clipboard manager for both Windows and Linux. - + Windows (x64) • - + Windows (arm64)
- + Linux (deb) • - + Linux (rpm) • - + Linux (AppImage)
- + macOS (Silicon) • - + macOS (Intel)
@@ -123,7 +123,7 @@ bun build ## 📝 License -Qopy is licensed under AGPL-3. See the [LICENSE file](./LICENCE) for more information. +Qopy is licensed under GPL-3. See the [LICENSE file](./LICENCE) for more information. [codespaces-link]: https://codespaces.new/0pandadev/Qopy [codespaces-shield]: https://github.com/codespaces/badge.svg diff --git a/package.json b/package.json index d1a8374..2dbe25d 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "sass-embedded": "1.85.1", "uuid": "11.1.0", "vue": "3.5.13", - "@waradu/keyboard": "4.2.0" + "wrdu-keyboard": "github:0PandaDEV/keyboard" }, "overrides": { "chokidar": "^3.6.0" diff --git a/pages/index.vue b/pages/index.vue index ddce6ca..774b0a0 100644 --- a/pages/index.vue +++ b/pages/index.vue @@ -1,68 +1,41 @@ diff --git a/plugins/keyboard.ts b/plugins/keyboard.ts index dfc7d44..17e8d63 100644 --- a/plugins/keyboard.ts +++ b/plugins/keyboard.ts @@ -1,27 +1,281 @@ +import { Key, keyboard } from "wrdu-keyboard"; import { platform } from "@tauri-apps/plugin-os"; -import { useKeyboard, Key } from "@waradu/keyboard"; + +type KeyboardHandler = (event: KeyboardEvent) => void; + +const activeContexts = new Set(); +const handlersByContext: Record< + string, + Array<{ + keys: Key[]; + callback: KeyboardHandler; + prevent: boolean; + priority?: number; + }> +> = {}; + +const PRIORITY = { + HIGH: 100, + MEDIUM: 50, + LOW: 0, +}; + +let currentOS = "windows"; + +const useKeyboard = { + PRIORITY, + + registerContext: (contextName: string) => { + if (!handlersByContext[contextName]) { + handlersByContext[contextName] = []; + } + }, + + enableContext: (contextName: string) => { + if (!handlersByContext[contextName]) { + useKeyboard.registerContext(contextName); + } + activeContexts.add(contextName); + + initKeyboardHandlers(); + }, + + disableContext: (contextName: string) => { + activeContexts.delete(contextName); + + initKeyboardHandlers(); + }, + + on: ( + contextName: string, + keys: Key[], + callback: KeyboardHandler, + options: { prevent?: boolean; priority?: number } = {} + ) => { + if (!handlersByContext[contextName]) { + useKeyboard.registerContext(contextName); + } + + const existingHandlerIndex = handlersByContext[contextName].findIndex( + (handler) => + handler.keys.length === keys.length && + handler.keys.every((key, i) => key === keys[i]) && + handler.callback.toString() === callback.toString() + ); + + if (existingHandlerIndex !== -1) { + handlersByContext[contextName][existingHandlerIndex] = { + keys, + callback, + prevent: options.prevent ?? true, + priority: options.priority ?? PRIORITY.LOW, + }; + } else { + handlersByContext[contextName].push({ + keys, + callback, + prevent: options.prevent ?? true, + priority: options.priority ?? PRIORITY.LOW, + }); + } + + if (activeContexts.has(contextName)) { + initKeyboardHandlers(); + } + }, + + clearAll: () => { + keyboard.clear(); + }, + + setupAppShortcuts: (options: { + onNavigateUp?: () => void; + onNavigateDown?: () => void; + onSelect?: () => void; + onEscape?: () => void; + onToggleActions?: () => void; + contextName?: string; + priority?: number; + }) => { + const { + onNavigateUp, + onNavigateDown, + onSelect, + onEscape, + onToggleActions, + contextName = "app", + priority = PRIORITY.LOW, + } = options; + + if (!handlersByContext[contextName]) { + useKeyboard.registerContext(contextName); + } + + if (onNavigateUp) { + useKeyboard.on(contextName, [Key.UpArrow], () => onNavigateUp(), { + priority, + }); + } + + if (onNavigateDown) { + useKeyboard.on(contextName, [Key.DownArrow], () => onNavigateDown(), { + priority, + }); + } + + if (onSelect) { + useKeyboard.on(contextName, [Key.Enter], () => onSelect(), { priority }); + } + + if (onEscape) { + useKeyboard.on(contextName, [Key.Escape], () => onEscape(), { priority }); + } + + if (onToggleActions) { + const togglePriority = Math.max(priority, PRIORITY.HIGH); + + if (currentOS === "macos") { + useKeyboard.on( + contextName, + [Key.LeftMeta, Key.K], + () => onToggleActions(), + { priority: togglePriority } + ); + useKeyboard.on( + contextName, + [Key.RightMeta, Key.K], + () => onToggleActions(), + { priority: togglePriority } + ); + } else { + useKeyboard.on( + contextName, + [Key.LeftControl, Key.K], + () => onToggleActions(), + { priority: togglePriority } + ); + useKeyboard.on( + contextName, + [Key.RightControl, Key.K], + () => onToggleActions(), + { priority: togglePriority } + ); + } + } + }, + + setupKeybindCapture: (options: { + onCapture: (key: string) => void; + onComplete: () => void; + }) => { + const { onCapture, onComplete } = options; + + keyboard.prevent.down([Key.All], (event: KeyboardEvent) => { + if (event.code === "Escape") { + onComplete(); + return; + } + onCapture(event.code); + }); + }, +}; + +const initKeyboardHandlers = () => { + keyboard.clear(); + + let allHandlers: Array<{ + keys: Key[]; + callback: KeyboardHandler; + prevent: boolean; + priority: number; + contextName: string; + }> = []; + + for (const contextName of activeContexts) { + const handlers = handlersByContext[contextName] || []; + allHandlers = [ + ...allHandlers, + ...handlers.map((handler) => ({ + ...handler, + priority: handler.priority ?? PRIORITY.LOW, + contextName, + })), + ]; + } + + allHandlers.sort((a, b) => b.priority - a.priority); + + const handlersByKeyCombination: Record< + string, + Array<(typeof allHandlers)[0]> + > = {}; + + allHandlers.forEach((handler) => { + const keyCombo = handler.keys.sort().join("+"); + if (!handlersByKeyCombination[keyCombo]) { + handlersByKeyCombination[keyCombo] = []; + } + handlersByKeyCombination[keyCombo].push(handler); + }); + + Object.entries(handlersByKeyCombination).forEach(([_keyCombo, handlers]) => { + handlers.sort((a, b) => b.priority - a.priority); + const handler = handlers[0]; + + const wrappedCallback: KeyboardHandler = (event) => { + const isMetaCombo = + handler.keys.length > 1 && + (handler.keys.includes(Key.LeftMeta) || + handler.keys.includes(Key.RightMeta) || + handler.keys.includes(Key.LeftControl) || + handler.keys.includes(Key.RightControl)); + + const isNavigationKey = + event.key === "ArrowUp" || + event.key === "ArrowDown" || + event.key === "Enter" || + event.key === "Escape"; + + const isInInput = + event.target instanceof HTMLInputElement || + event.target instanceof HTMLTextAreaElement; + + if ( + (isMetaCombo || isNavigationKey || !isInInput) && + activeContexts.has(handler.contextName) + ) { + handler.callback(event); + } + }; + + if (handler.prevent) { + keyboard.prevent.down(handler.keys, wrappedCallback); + } else { + keyboard.down(handler.keys, wrappedCallback); + } + }); +}; export default defineNuxtPlugin(async (nuxtApp) => { - const keyboardInstance = useKeyboard(); - let currentOS = "windows"; try { - const osName = await Promise.resolve(platform()); + const osName = platform(); currentOS = osName.toLowerCase().includes("mac") ? "macos" : "windows"; } catch (error) { console.error("Error detecting platform:", error); } - // Defer initialization until the app is mounted - nuxtApp.hook('app:mounted', () => { - keyboardInstance.init(); + initKeyboardHandlers(); + + nuxtApp.hook("page:finish", () => { + initKeyboardHandlers(); }); - nuxtApp.provide('keyboard', { - listen: keyboardInstance.listen.bind(keyboardInstance), - init: keyboardInstance.init.bind(keyboardInstance), - Key, - currentOS, - // Provide a clear method if users need to manually clear all listeners from the instance - clearAll: keyboardInstance.clear ? keyboardInstance.clear.bind(keyboardInstance) : () => { console.warn('@waradu/keyboard instance does not have a clear method'); } - }); + return { + provide: { + keyboard: { + ...useKeyboard, + Key, + }, + }, + }; }); diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index add115b..356fe53 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -105,9 +105,9 @@ dependencies = [ [[package]] name = "arbitrary" -version = "1.4.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" dependencies = [ "derive_arbitrary", ] @@ -628,13 +628,12 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.16" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" +checksum = "504bdec147f2cc13c8b57ed9401fd8a147cc66b67ad5cb241394244f2c947549" dependencies = [ "jobserver", "libc", - "shlex", ] [[package]] @@ -999,9 +998,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.21" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crunchy" @@ -1125,9 +1124,9 @@ dependencies = [ [[package]] name = "derive_arbitrary" -version = "1.4.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" +checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2", "quote", @@ -2620,7 +2619,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ "hermit-abi 0.5.0", "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -2842,7 +2841,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -3574,9 +3573,9 @@ checksum = "cde51589ab56b20a6f686b2c68f7a0bd6add753d697abf720d63f8db3ab7b1ad" [[package]] name = "openssl" -version = "0.10.71" +version = "0.10.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd" +checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" dependencies = [ "bitflags 2.8.0", "cfg-if", @@ -3606,9 +3605,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.106" +version = "0.9.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd" +checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" dependencies = [ "cc", "libc", @@ -4561,14 +4560,15 @@ checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" [[package]] name = "ring" -version = "0.17.14" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", "cfg-if", "getrandom 0.2.15", "libc", + "spin", "untrusted", "windows-sys 0.52.0", ] @@ -4971,12 +4971,6 @@ dependencies = [ "digest", ] -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -7733,9 +7727,9 @@ dependencies = [ [[package]] name = "zip" -version = "2.4.1" +version = "2.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "938cc23ac49778ac8340e366ddc422b2227ea176edb447e23fc0627608dddadd" +checksum = "40dd8c92efc296286ce1fbd16657c5dbefff44f1b4ca01cc5f517d8b7b3d3e2e" dependencies = [ "arbitrary", "crc32fast", @@ -7743,7 +7737,7 @@ dependencies = [ "displaydoc", "indexmap 2.3.0", "memchr", - "thiserror 2.0.3", + "thiserror 1.0.63", ] [[package]] diff --git a/types/keyboard.d.ts b/types/keyboard.d.ts deleted file mode 100644 index 62d26e4..0000000 --- a/types/keyboard.d.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { Key as WaraduKey, useKeyboard } from '@waradu/keyboard'; - -declare module '#app' { - interface NuxtApp { - $keyboard: { - listen: ReturnType['listen']; - init: ReturnType['init']; - Key: typeof WaraduKey; - currentOS: string; - clearAll: () => void; - }; - } -} - -declare module 'vue' { - interface ComponentCustomProperties { - $keyboard: { - listen: ReturnType['listen']; - init: ReturnType['init']; - Key: typeof WaraduKey; - currentOS: string; - clearAll: () => void; - }; - } -} - -export {}; \ No newline at end of file