diff --git a/.config/hypr/hyprland/keybinds.conf b/.config/hypr/hyprland/keybinds.conf index 3d550762..2b6f7da0 100644 --- a/.config/hypr/hyprland/keybinds.conf +++ b/.config/hypr/hyprland/keybinds.conf @@ -18,6 +18,7 @@ bind = Super, mouse_down,global, quickshell:overviewToggleReleaseInterrupt # [hi bindit = ,Super_L, global, quickshell:workspaceNumber # [hidden] bindd = Super, V, Clipboard history >> clipboard, global, quickshell:overviewClipboardToggle # Clipboard history >> clipboard +bindd = Super, Period, Clipboard history >> clipboard, global, quickshell:overviewEmojiToggle # Emoji >> clipboard bindd = Super, Tab, Toggle overview, global, quickshell:overviewToggle # [hidden] Toggle overview/launcher (alt) bindd = Super, A, Toggle left sidebar, global, quickshell:sidebarLeftToggle # Toggle left sidebar bind = Super+Alt, A, global, quickshell:sidebarLeftToggleDetach # [hidden] @@ -52,7 +53,7 @@ bindd = Ctrl+Shift+Alt+Super, Delete, Shutdown, exec, systemctl poweroff || logi ##! Utilities # Screenshot, Record, OCR, Color picker, Clipboard history bindd = Super, V, Copy clipboard history entry, exec, qs ipc call TEST_ALIVE || pkill fuzzel || cliphist list | fuzzel --match-mode fzf --dmenu | cliphist decode | wl-copy # Clipboard history >> clipboard -bindd = Super, Period, Copy an emoji, exec, pkill fuzzel || ~/.config/hypr/hyprland/scripts/fuzzel-emoji.sh copy # Emoji +bindd = Super, Period, Copy an emoji, exec, qs ipc call TEST_ALIVE || pkill fuzzel || ~/.config/hypr/hyprland/scripts/fuzzel-emoji.sh copy # [hidden] Emoji >> clipboard bindd = Super+Shift, S, Screen snip, exec, grimblast --freeze copy area # Screen snip >> clipboard bindd = Super+Shift+Alt, S, Screen snip and annotate, exec, grim -g "$(slurp)" - | swappy -f - # Screen snip and annotate # OCR diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index ad4ccf4d..c50f5613 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -84,7 +84,8 @@ Singleton { property bool sloppy: false // Uses levenshtein distance based scoring instead of fuzzy sort. Very weird. property QtObject prefix: QtObject { property string action: "/" - property string clipboard: ":" + property string clipboard: ";" + property string emojis: ":" } } diff --git a/.config/quickshell/modules/overview/Overview.qml b/.config/quickshell/modules/overview/Overview.qml index fe7ba1e5..6fa85e89 100644 --- a/.config/quickshell/modules/overview/Overview.qml +++ b/.config/quickshell/modules/overview/Overview.qml @@ -210,4 +210,27 @@ Scope { } } + GlobalShortcut { + name: "overviewEmojiToggle" + description: qsTr("Toggle emoji query on overview widget") + + onPressed: { + if (GlobalStates.overviewOpen && overviewScope.dontAutoCancelSearch) { + GlobalStates.overviewOpen = false; + return; + } + for (let i = 0; i < overviewVariants.instances.length; i++) { + let panelWindow = overviewVariants.instances[i]; + if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) { + overviewScope.dontAutoCancelSearch = true; + panelWindow.setSearchingText( + ConfigOptions.search.prefix.emojis + ); + GlobalStates.overviewOpen = true; + return + } + } + } + } + } diff --git a/.config/quickshell/modules/overview/SearchItem.qml b/.config/quickshell/modules/overview/SearchItem.qml index e76067c4..00d5da74 100644 --- a/.config/quickshell/modules/overview/SearchItem.qml +++ b/.config/quickshell/modules/overview/SearchItem.qml @@ -24,6 +24,7 @@ RippleButton { property var itemExecute: entry?.execute property string fontType: entry?.fontType ?? "main" property string itemClickActionName: entry?.clickActionName + property string bigText: entry?.bigText ?? "" property string materialSymbol: entry?.materialSymbol ?? "" property string cliphistRawString: entry?.cliphistRawString ?? "" @@ -120,6 +121,7 @@ RippleButton { id: iconLoader active: true sourceComponent: root.materialSymbol !== "" ? materialSymbolComponent : + root.bigText ? bigTextComponent : root.itemIcon !== "" ? iconImageComponent : null } @@ -142,6 +144,15 @@ RippleButton { } } + Component { + id: bigTextComponent + StyledText { + text: root.bigText + font.pixelSize: Appearance.font.pixelSize.larger + color: Appearance.m3colors.m3onSurface + } + } + // Main text ColumnLayout { id: contentColumn diff --git a/.config/quickshell/modules/overview/SearchWidget.qml b/.config/quickshell/modules/overview/SearchWidget.qml index df397294..88b6f70f 100644 --- a/.config/quickshell/modules/overview/SearchWidget.qml +++ b/.config/quickshell/modules/overview/SearchWidget.qml @@ -302,7 +302,22 @@ Item { // Wrapper } }; }).filter(Boolean); - } + } + if (root.searchingText.startsWith(ConfigOptions.search.prefix.emojis)) { // Clipboard + const searchString = root.searchingText.slice(ConfigOptions.search.prefix.emojis.length); + return Emojis.fuzzyQuery(searchString).map(entry => { + return { + cliphistRawString: entry, + bigText: entry.match(/^\s*(\S+)/)?.[1] || "", + name: entry.replace(/^\s*\S+\s+/, ""), + clickActionName: "", + type: "Emoji", + execute: () => { + Hyprland.dispatch(`exec wl-copy '${StringUtils.shellSingleQuoteEscape(entry.match(/^\s*(\S+)/)?.[1])}'`); + } + }; + }).filter(Boolean); + } ////////////////// Init /////////////////// diff --git a/.config/quickshell/services/Cliphist.qml b/.config/quickshell/services/Cliphist.qml index d0193f14..bebafb10 100644 --- a/.config/quickshell/services/Cliphist.qml +++ b/.config/quickshell/services/Cliphist.qml @@ -3,7 +3,6 @@ pragma ComponentBehavior: Bound import "root:/modules/common/functions/fuzzysort.js" as Fuzzy import "root:/modules/common/functions/levendist.js" as Levendist -import "root:/modules/common/functions/string_utils.js" as StringUtils import "root:/modules/common" import "root:/" import QtQuick diff --git a/.config/quickshell/services/Emojis.qml b/.config/quickshell/services/Emojis.qml new file mode 100644 index 00000000..852c831b --- /dev/null +++ b/.config/quickshell/services/Emojis.qml @@ -0,0 +1,65 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +import "root:/modules/common/functions/fuzzysort.js" as Fuzzy +import "root:/modules/common/functions/levendist.js" as Levendist +import "root:/modules/common" +import QtQuick +import Quickshell +import Quickshell.Io + +/** + * Emojis. + */ +Singleton { + id: root + property string emojiScriptPath: `${Directories.config}/hypr/hyprland/scripts/fuzzel-emoji.sh` + property string lineBeforeData: "### DATA ###" + property list list + readonly property var preparedEntries: list.map(a => ({ + name: Fuzzy.prepare(`${a}`), + entry: a + })) + function fuzzyQuery(search: string): var { + if (root.sloppySearch) { + const results = entries.slice(0, 100).map(str => ({ + entry: str, + score: Levendist.computeTextMatchScore(str.toLowerCase(), search.toLowerCase()) + })).filter(item => item.score > root.scoreThreshold) + .sort((a, b) => b.score - a.score) + return results + .map(item => item.entry) + } + + return Fuzzy.go(search, preparedEntries, { + all: true, + key: "name" + }).map(r => { + return r.obj.entry + }); + } + + function load() { + emojiFileView.reload() + } + + function updateEmojis(fileContent) { + const lines = fileContent.split("\n") + const dataIndex = lines.indexOf(root.lineBeforeData) + if (dataIndex === -1) { + console.warn("No data section found in emoji script file.") + return + } + const emojis = lines.slice(dataIndex + 1).filter(line => line.trim() !== "") + root.list = emojis.map(line => line.trim()) + } + + FileView { + id: emojiFileView + path: Qt.resolvedUrl(root.emojiScriptPath) + onLoadedChanged: { + const fileContent = emojiFileView.text() + root.updateEmojis(fileContent) + } + } +}