quickshell clipboard

This commit is contained in:
end-4
2025-05-26 20:02:43 +02:00
parent 24b369882a
commit 262f278bb4
7 changed files with 153 additions and 27 deletions
+2 -1
View File
@@ -17,6 +17,7 @@ bind = Super, mouse_up, global, quickshell:overviewToggleReleaseInterrupt # [hi
bind = Super, mouse_down,global, quickshell:overviewToggleReleaseInterrupt # [hidden] bind = Super, mouse_down,global, quickshell:overviewToggleReleaseInterrupt # [hidden]
bindit = ,Super_L, global, quickshell:workspaceNumber # [hidden] bindit = ,Super_L, global, quickshell:workspaceNumber # [hidden]
bindd = Super, V, Clipboard history >> clipboard, global, quickshell:overviewClipboardToggle # Clipboard history >> clipboard
bindd = Super, Tab, Toggle overview, global, quickshell:overviewToggle # [hidden] Toggle overview/launcher (alt) bindd = Super, Tab, Toggle overview, global, quickshell:overviewToggle # [hidden] Toggle overview/launcher (alt)
bind = Super, B, global, quickshell:sidebarLeftToggle # [hidden] bind = Super, B, global, quickshell:sidebarLeftToggle # [hidden]
bindd = Super, A, Toggle left sidebar, global, quickshell:sidebarLeftToggle # Toggle left sidebar bindd = Super, A, Toggle left sidebar, global, quickshell:sidebarLeftToggle # Toggle left sidebar
@@ -47,7 +48,7 @@ bindd = Ctrl+Shift+Alt+Super, Delete, Shutdown, exec, systemctl poweroff || logi
##! Utilities ##! Utilities
# Screenshot, Record, OCR, Color picker, Clipboard history # Screenshot, Record, OCR, Color picker, Clipboard history
bindd = Super, V, Copy clipboard history entry, exec, pkill fuzzel || cliphist list | fuzzel --match-mode fzf --dmenu | cliphist decode | wl-copy # Clipboard history >> clipboard 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 || ~/.local/bin/fuzzel-emoji # Emoji bindd = Super, Period, Copy an emoji, exec, pkill fuzzel || ~/.local/bin/fuzzel-emoji # Emoji
bindd = Super+Shift, S, Screen snip, exec, grimblast --freeze copy area # Screen snip >> 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 bindd = Super+Shift+Alt, S, Screen snip and annotate, exec, grim -g "$(slurp)" - | swappy -f - # Screen snip and annotate
@@ -62,6 +62,7 @@ Singleton {
property list<string> excludedSites: [ "quora.com" ] property list<string> excludedSites: [ "quora.com" ]
property QtObject prefix: QtObject { property QtObject prefix: QtObject {
property string action: "/" property string action: "/"
property string clipboard: ":"
} }
} }
@@ -10,7 +10,10 @@ import Quickshell.Wayland
import Quickshell.Hyprland import Quickshell.Hyprland
Scope { Scope {
id: overviewScope
property bool dontAutoCancelSearch: false
Variants { Variants {
id: overviewVariants
model: Quickshell.screens model: Quickshell.screens
PanelWindow { PanelWindow {
id: root id: root
@@ -50,7 +53,14 @@ Scope {
Connections { Connections {
target: GlobalStates target: GlobalStates
function onOverviewOpenChanged() { function onOverviewOpenChanged() {
delayedGrabTimer.start() if (!GlobalStates.overviewOpen) {
overviewScope.dontAutoCancelSearch = false;
} else {
if (!overviewScope.dontAutoCancelSearch) {
searchWidget.cancelSearch()
}
delayedGrabTimer.start()
}
} }
} }
@@ -67,6 +77,10 @@ Scope {
implicitWidth: columnLayout.width implicitWidth: columnLayout.width
implicitHeight: columnLayout.height implicitHeight: columnLayout.height
function setSearchingText(text) {
searchWidget.setSearchingText(text);
}
ColumnLayout { ColumnLayout {
id: columnLayout id: columnLayout
visible: GlobalStates.overviewOpen visible: GlobalStates.overviewOpen
@@ -84,6 +98,7 @@ Scope {
} }
SearchWidget { SearchWidget {
id: searchWidget
panelWindow: root panelWindow: root
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
onSearchingTextChanged: (text) => { onSearchingTextChanged: (text) => {
@@ -163,5 +178,27 @@ Scope {
GlobalStates.superReleaseMightTrigger = false GlobalStates.superReleaseMightTrigger = false
} }
} }
GlobalShortcut {
name: "overviewClipboardToggle"
description: qsTr("Toggle clipboard query on overview widget")
onPressed: {
if (GlobalStates.overviewOpen) {
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.clipboard
);
GlobalStates.overviewOpen = true;
return
}
}
}
}
} }
@@ -67,7 +67,7 @@ RippleButton {
RowLayout { RowLayout {
id: rowLayout id: rowLayout
spacing: 10 spacing: iconLoader.sourceComponent === null ? 0 : 10
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: root.horizontalMargin + root.buttonHorizontalPadding anchors.leftMargin: root.horizontalMargin + root.buttonHorizontalPadding
anchors.rightMargin: root.horizontalMargin + root.buttonHorizontalPadding anchors.rightMargin: root.horizontalMargin + root.buttonHorizontalPadding
@@ -76,7 +76,9 @@ RippleButton {
Loader { Loader {
id: iconLoader id: iconLoader
active: true active: true
sourceComponent: root.materialSymbol == "" ? iconImageComponent : materialSymbolComponent sourceComponent: root.materialSymbol !== "" ? materialSymbolComponent :
root.itemIcon !== "" ? iconImageComponent :
null
} }
Component { Component {
@@ -111,6 +113,7 @@ RippleButton {
StyledText { StyledText {
Layout.fillWidth: true Layout.fillWidth: true
id: nameText id: nameText
textFormat: Text.PlainText // TODO: make cliphist entry highlighting working
font.pixelSize: Appearance.font.pixelSize.normal font.pixelSize: Appearance.font.pixelSize.normal
font.family: Appearance.font.family[root.fontType] font.family: Appearance.font.family[root.fontType]
color: Appearance.m3colors.m3onSurface color: Appearance.m3colors.m3onSurface
@@ -24,6 +24,21 @@ Item { // Wrapper
implicitHeight: searchWidgetContent.implicitHeight + Appearance.sizes.elevationMargin * 2 implicitHeight: searchWidgetContent.implicitHeight + Appearance.sizes.elevationMargin * 2
property string mathResult: "" property string mathResult: ""
property bool lastQueryWasClipboard: false
onShowResultsChanged: {
lastQueryWasClipboard = false;
}
function cancelSearch() {
searchInput.selectAll()
root.searchingText = ""
}
function setSearchingText(text) {
searchInput.text = text;
root.searchingText = text;
}
property var searchActions: [ property var searchActions: [
{ {
action: "img", action: "img",
@@ -194,7 +209,7 @@ Item { // Wrapper
Layout.leftMargin: 15 Layout.leftMargin: 15
iconSize: Appearance.font.pixelSize.huge iconSize: Appearance.font.pixelSize.huge
color: Appearance.m3colors.m3onSurface color: Appearance.m3colors.m3onSurface
text: "search" text: root.searchingText.startsWith(ConfigOptions.search.prefix.clipboard) ? 'content_paste_search' : 'search'
} }
TextField { // Search box TextField { // Search box
id: searchInput id: searchInput
@@ -219,13 +234,6 @@ Item { // Wrapper
} }
onTextChanged: root.searchingText = text onTextChanged: root.searchingText = text
Connections {
target: root
function onVisibleChanged() {
searchInput.selectAll()
root.searchingText = ""
}
}
onAccepted: { onAccepted: {
if (appResults.count > 0) { if (appResults.count > 0) {
@@ -279,10 +287,31 @@ Item { // Wrapper
model: ScriptModel { model: ScriptModel {
id: model id: model
values: { values: { // Search results are handled here
////////////////// Skip? //////////////////
if(root.searchingText == "") return []; if(root.searchingText == "") return [];
// Start math calculation, declare special result objects ///////////// Special cases ///////////////
if (root.searchingText.startsWith(ConfigOptions.search.prefix.clipboard)) { // Clipboard
if (!root.lastQueryWasClipboard) {
root.lastQueryWasClipboard = true;
Cliphist.refresh(); // Refresh clipboard entries
}
const searchString = root.searchingText.slice(ConfigOptions.search.prefix.clipboard.length);
return Cliphist.fuzzyQuery(searchString).map(entry => {
return {
name: entry.replace(/^\s*\S+\s+/, ""),
clickActionName: qsTr("Copy"),
type: `#${entry.match(/^\s*(\S+)/)?.[1] || ""}`,
execute: () => {
Hyprland.dispatch(`exec echo '${StringUtils.shellSingleQuoteEscape(entry)}' | cliphist decode | wl-copy`);
}
};
}).filter(Boolean);
}
////////////////// Init ///////////////////
nonAppResultsTimer.restart(); nonAppResultsTimer.restart();
const mathResultObject = { const mathResultObject = {
name: root.mathResult, name: root.mathResult,
@@ -322,10 +351,9 @@ Item { // Wrapper
}) })
.filter(Boolean); .filter(Boolean);
// Init result array
let result = []; let result = [];
// Add filtered application entries //////////////// Apps //////////////////
result = result.concat( result = result.concat(
AppSearch.fuzzyQuery(root.searchingText) AppSearch.fuzzyQuery(root.searchingText)
.map((entry) => { .map((entry) => {
@@ -335,23 +363,20 @@ Item { // Wrapper
}) })
); );
// Add launcher actions ////////// Launcher actions ////////////
result = result.concat(launcherActionObjects); result = result.concat(launcherActionObjects);
// Insert math result before command if search starts with a number /////////// Math result & command //////////
const startsWithNumber = /^\d/.test(root.searchingText); const startsWithNumber = /^\d/.test(root.searchingText);
if (startsWithNumber) if (startsWithNumber) {
result.push(mathResultObject); result.push(mathResultObject);
result.push(commandResultObject);
// Command } else {
result.push(commandResultObject); result.push(commandResultObject);
// If not already added, add math result after command
if (!startsWithNumber) {
result.push(mathResultObject); result.push(mathResultObject);
} }
// Web search ///////////////// Web search ////////////////
result.push({ result.push({
name: root.searchingText, name: root.searchingText,
clickActionName: qsTr("Search"), clickActionName: qsTr("Search"),
@@ -369,7 +394,8 @@ Item { // Wrapper
return result; return result;
} }
} }
delegate: SearchItem {
delegate: SearchItem { // The selectable item for each search result
entry: modelData entry: modelData
} }
} }
+57
View File
@@ -0,0 +1,57 @@
pragma Singleton
pragma ComponentBehavior: Bound
import "root:/modules/common/functions/fuzzysort.js" as Fuzzy
import "root:/modules/common"
import "root:/"
import QtQuick
import Quickshell
import Quickshell.Io
Singleton {
id: root
property list<string> entries: []
property string highlightPrefix: `<b><font color="${Appearance.m3colors.m3primary}">`
property string highlightSuffix: `</font></b>`
readonly property var preparedEntries: entries.map(a => ({
name: Fuzzy.prepare(`${a}`),
entry: a
}))
function fuzzyQuery(search: string): var {
return Fuzzy.go(search, preparedEntries, {
all: true,
key: "name"
}).map(r => {
return r.obj.entry
// console.log(JSON.stringify(r))
// return r.highlight(highlightPrefix, highlightSuffix);
});
}
function refresh() {
readProc.buffer = []
readProc.running = false
readProc.running = true
}
Process {
id: readProc
property list<string> buffer: []
command: ["cliphist", "list"]
stdout: SplitParser {
onRead: (line) => {
readProc.buffer.push(line)
}
}
onExited: (exitCode, exitStatus) => {
if (exitCode === 0) {
root.entries = readProc.buffer
} else {
console.error("[Cliphist] Failed to refresh with code", exitCode, "and status", exitStatus)
}
}
}
}
+1
View File
@@ -24,6 +24,7 @@ ShellRoot {
MaterialThemeLoader.reapplyTheme() MaterialThemeLoader.reapplyTheme()
ConfigLoader.loadConfig() ConfigLoader.loadConfig()
PersistentStateManager.loadStates() PersistentStateManager.loadStates()
Cliphist.refresh()
} }
Bar {} Bar {}