fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! Install lucid

This commit is contained in:
Sweetbread committed 2026-11-08 17:48:19 +03:00
1 parent 9ce9fefaa7
commit 51554e4019
4 files changed
+125 -266

No files matched your search

+1 -1
View File
@@ -170,7 +170,7 @@ in {
lucidHyprlandBinds = [
(bind "SUPER + V" (exec "ghostty --title=clipse -e clipse"))
(bind "SUPER + X" (exec "qs ipc call -- vaultwarden open"))
(bind "SUPER + X" (exec "qs ipc call -- launcher search '>vault'"))
(hyprLua.bind "SUPER + SHIFT + V" (hyprLua.execLauncher "qs ipc call launcher clipboard"))
(bind "Print" (exec "qs ipc call screenshot full"))
(bind "SUPER + Print" (exec "qs ipc call screenshot full"))
+26 -260
View File
@@ -2,108 +2,50 @@ import QtQuick
import Quickshell
import Quickshell.Hyprland
import Quickshell.Io
import Quickshell.Wayland
import qs
pragma Singleton
Item {
Singleton {
id: root
property bool shown: false
property bool busy: false
property string status: ""
property var entries: []
property bool loading: false
property string error: ""
property string targetAddress: ""
property string pendingPassword: ""
property var passwordProcess: null
property var entries: []
property int selectedIndex: 0
readonly property var filteredEntries: {
const query = search.text.trim().toLocaleLowerCase();
if (query === "")
return root.entries;
signal passwordReady(string password, string targetAddress)
return root.entries.filter((entry) =>
entry.name.toLocaleLowerCase().includes(query)
|| entry.user.toLocaleLowerCase().includes(query)
|| entry.folder.toLocaleLowerCase().includes(query));
}
function openPicker() {
if (typeDelay.running)
function refresh() {
if (listReader.running)
return;
if (root.shown) {
root.closePicker();
return;
}
const active = Hyprland.activeToplevel;
root.targetAddress = active ? active.address : "";
root.shown = true;
root.busy = true;
root.status = "Loading vault…";
root.entries = [];
search.text = "";
root.selectedIndex = 0;
root.loading = true;
root.error = "";
listReader.command = ["rbw", "list", "--fields", "id,name,user,folder"];
listReader.running = true;
Qt.callLater(() => search.forceActiveFocus());
}
function closePicker() {
typeDelay.stop();
if (listReader.running)
listReader.running = false;
if (root.passwordProcess) {
root.passwordProcess.running = false;
root.passwordProcess.destroy();
root.passwordProcess = null;
}
root.shown = false;
root.busy = false;
root.status = "";
root.pendingPassword = "";
}
function choose(entry) {
if (!entry || root.busy)
function getPassword(id, target) {
if (root.passwordProcess)
return;
root.busy = true;
root.status = "Reading password…";
root.passwordProcess = passwordProcessComponent.createObject(root, { entryId: entry.id });
root.targetAddress = target || "";
root.error = "";
root.passwordProcess = passwordProcessComponent.createObject(root, { entryId: id });
if (!root.passwordProcess) {
root.busy = false;
root.status = "Could not start rbw.";
root.error = "Could not start rbw.";
return;
}
root.passwordProcess.running = true;
}
function deliverPassword(password) {
if (password === "") {
root.busy = false;
root.status = "This entry has an empty password.";
return;
}
function typePassword(password, target) {
root.pendingPassword = password;
root.shown = false;
root.status = "";
root.targetAddress = target || "";
typeDelay.restart();
}
IpcHandler {
target: "vaultwarden"
function open(): void {
root.openPicker();
}
function close(): void {
root.closePicker();
}
}
Process {
id: listReader
@@ -113,9 +55,10 @@ Item {
}
onExited: (code) => {
root.busy = false;
root.loading = false;
if (code !== 0) {
root.status = "Could not read the vault. Unlock it with rbw, then try again.";
root.error = "Could not read the vault. Unlock it with rbw, then retry.";
root.entries = [];
return;
}
@@ -128,7 +71,7 @@ Item {
folder: fields[3] || ""
};
}).filter((entry) => entry.id !== "");
root.status = root.entries.length === 0 ? "No entries in the vault." : "";
root.error = "";
}
}
@@ -148,10 +91,9 @@ Item {
if (root.passwordProcess === passwordJob)
root.passwordProcess = null;
if (code !== 0) {
root.busy = false;
root.status = "Could not read that password.";
root.error = "Could not read that password.";
} else {
root.deliverPassword(passwordOutput.text.replace(/\r?\n$/, ""));
root.passwordReady(passwordOutput.text.replace(/\r?\n$/, ""), root.targetAddress);
}
passwordJob.destroy();
}
@@ -167,197 +109,21 @@ Item {
write(root.pendingPassword);
stdinEnabled = false;
root.pendingPassword = "";
root.busy = false;
root.targetAddress = "";
}
}
Timer {
id: typeDelay
interval: 180
interval: 260
onTriggered: {
if (root.targetAddress !== "")
Hyprland.dispatch("focuswindow address:" + root.targetAddress);
root.targetAddress = "";
typer.running = false;
typer.stdinEnabled = true;
typer.running = true;
}
}
PanelWindow {
id: window
visible: root.shown
anchors {
top: true
bottom: true
left: true
right: true
}
color: "transparent"
exclusionMode: ExclusionMode.Ignore
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: root.shown ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
Rectangle {
anchors.fill: parent
color: "#99000000"
MouseArea {
anchors.fill: parent
onClicked: root.closePicker()
}
}
Rectangle {
id: card
anchors.centerIn: parent
width: Math.min(parent.width - Theme.dp(40), Theme.dp(620))
height: Math.min(parent.height - Theme.dp(40), Theme.dp(560))
radius: Theme.shapeXl
color: Theme.surfaceContainer
border.width: 1
border.color: Theme.outline
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton
onClicked: (mouse) => mouse.accepted = true
}
Column {
anchors.fill: parent
anchors.margins: Theme.dp(24)
spacing: Theme.dp(14)
Text {
text: "Vaultwarden"
color: Theme.text
font.pixelSize: Theme.fontHeadlineLg
font.weight: Font.DemiBold
}
Rectangle {
width: parent.width
height: Theme.dp(52)
radius: Theme.shapeMd
color: Theme.surfaceHigh
border.width: search.activeFocus ? 2 : 1
border.color: search.activeFocus ? Theme.accent : Theme.outline
TextInput {
id: search
anchors.fill: parent
anchors.leftMargin: Theme.dp(16)
anchors.rightMargin: Theme.dp(16)
verticalAlignment: TextInput.AlignVCenter
color: Theme.text
selectionColor: Theme.accent
selectedTextColor: Theme.fgAccent
font.pixelSize: Theme.fontTitle
clip: true
focus: root.shown
selectByMouse: true
Text {
anchors.fill: parent
verticalAlignment: Text.AlignVCenter
text: "Search entries…"
color: Theme.subtext
font: search.font
visible: search.text === ""
}
Keys.onPressed: (event) => {
if (event.key === Qt.Key_Escape) {
root.closePicker();
event.accepted = true;
} else if (event.key === Qt.Key_Down) {
root.selectedIndex = Math.max(0, Math.min(root.filteredEntries.length - 1, root.selectedIndex + 1));
if (root.filteredEntries.length > 0)
list.positionViewAtIndex(root.selectedIndex, ListView.Contain);
event.accepted = true;
} else if (event.key === Qt.Key_Up) {
root.selectedIndex = Math.max(0, root.selectedIndex - 1);
if (root.filteredEntries.length > 0)
list.positionViewAtIndex(root.selectedIndex, ListView.Contain);
event.accepted = true;
}
}
onTextChanged: root.selectedIndex = 0
onAccepted: {
if (root.filteredEntries.length > 0)
root.choose(root.filteredEntries[root.selectedIndex] || root.filteredEntries[0]);
}
}
}
ListView {
id: list
width: parent.width
height: Math.max(0, parent.height - Theme.dp(122))
clip: true
spacing: Theme.dp(4)
model: root.filteredEntries
currentIndex: root.selectedIndex
delegate: Rectangle {
required property var modelData
required property int index
width: list.width
height: Theme.dp(60)
radius: Theme.shapeMd
color: index === root.selectedIndex ? Theme.bgHover : "transparent"
Column {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Theme.dp(14)
anchors.rightMargin: Theme.dp(14)
spacing: Theme.dp(3)
Text {
width: parent.width
text: modelData.name
color: Theme.text
font.pixelSize: Theme.fontTitle
elide: Text.ElideRight
}
Text {
width: parent.width
text: [modelData.user, modelData.folder].filter((part) => part !== "").join(" · ")
color: Theme.subtext
font.pixelSize: Theme.fontLabel
elide: Text.ElideRight
visible: text !== ""
}
}
MouseArea {
anchors.fill: parent
onEntered: root.selectedIndex = index
onClicked: root.choose(modelData)
}
}
}
Text {
width: parent.width
text: root.busy || root.status !== "" ? root.status : "↑ ↓ Select Enter Type password Esc Close"
color: root.status !== "" && !root.busy ? Theme.warning : Theme.subtext
font.pixelSize: Theme.fontLabel
elide: Text.ElideRight
}
}
}
}
}
+98 -2
View File
@@ -16,7 +16,7 @@ let
desc = "Type a password from Vaultwarden";
category = "Apps";
type = "exec";
cmd = "qs ipc call -- vaultwarden open";
cmd = "qs ipc call -- launcher search '>vault'";
} ];
});
mergeVaultwardenBind = pkgs.writeText "lucid-merge-vaultwarden-bind.jq" ''
@@ -211,7 +211,96 @@ pathlib.Path(sys.argv[2]).write_text(source.split(marker, 1)[0])
PY
'';
liveDecoder = if builtins.elem osConfig.networking.hostName [ "Rias" "Eclipse" ] then "cuda" else "vaapi";
source = pkgs.applyPatches {
vaultwardenLauncherPatch = pkgs.writeText "lucid-vaultwarden-launcher.py" ''
import pathlib
import sys
face_path, dock_path = map(pathlib.Path, sys.argv[1:])
face = face_path.read_text()
dock = dock_path.read_text()
def replace_once(source, old, new, label):
count = source.count(old)
if count != 1:
raise SystemExit(f"Lucid {label} anchor changed (found {count}); refusing an unsafe launcher patch")
return source.replace(old, new, 1)
face = replace_once(face,
' "clipboard": "Search clipboard history",\n',
' "clipboard": "Search clipboard history",\n "vaultwarden": "Search Vaultwarden",\n', "launcher placeholder")
face = replace_once(face,
' "clipboard": "content_paste",\n',
' "clipboard": "content_paste",\n "vaultwarden": "key",\n', "launcher icon")
face = replace_once(face,
' "icon": "content_paste"\n }, {\n "key": "wallpaper",',
' "icon": "content_paste"\n }, {\n "key": "vaultwarden",\n "label": "Vaultwarden",\n "icon": "key"\n }, {\n "key": "wallpaper",', "launcher mode")
face = replace_once(face,
' emptyLabel: {\n',
' emptyLabel: {\n if (face.displayMode === "vaultwarden")\n return "No matching vault entries";\n', "launcher empty label")
face = replace_once(face,
' emptyHint: {\n',
' emptyHint: {\n if (face.displayMode === "vaultwarden")\n return "Unlock the vault with rbw, then search by name, username, or folder.";\n', "launcher empty hint")
dock = replace_once(dock,
' if (q.indexOf(">clip") === 0)\n return "clipboard";\n',
' if (q.indexOf(">clip") === 0)\n return "clipboard";\n\n if (q.indexOf(">vault") === 0)\n return "vaultwarden";\n', "launcher prefix")
dock = replace_once(dock,
' if (mode === "clipboard")\n return ">clip";\n',
' if (mode === "clipboard")\n return ">clip";\n\n if (mode === "vaultwarden")\n return ">vault";\n', "launcher mode prefix")
dock = replace_once(dock,
' if (dockWindow.mode === "clipboard")\n Clip.refresh();\n',
' if (dockWindow.mode === "clipboard")\n Clip.refresh();\n if (dockWindow.mode === "vaultwarden")\n Vaultwarden.refresh();\n', "launcher mode activation")
dock = replace_once(dock,
' readonly property var allCommands: [{',
""" Connections {
target: Vaultwarden
function onEntriesChanged() { dockWindow.rebuildResults(); }
function onLoadingChanged() { dockWindow.rebuildResults(); }
function onErrorChanged() { dockWindow.rebuildResults(); }
function onPasswordReady(password, targetAddress) {
dockWindow.menuOpen = false;
Vaultwarden.typePassword(password, targetAddress);
}
}
readonly property var allCommands: [{""", "vaultwarden updates")
dock = replace_once(dock,
' function rebuildResults() {',
""" function vaultRows(query) {
if (Vaultwarden.loading)
return [dockWindow.headerRow("Loading Vaultwarden…")];
if (Vaultwarden.error !== "")
return [dockWindow.headerRow(Vaultwarden.error)];
var rows = [];
var entries = Vaultwarden.entries || [];
for (var i = 0; i < entries.length; i++) {
var entry = entries[i];
var haystack = (entry.name + " " + entry.user + " " + entry.folder).toLowerCase();
if (query !== "" && haystack.indexOf(query) === -1)
continue;
var detail = [entry.user, entry.folder].filter((part) => part !== "").join(" · ");
rows.push(dockWindow.makeRow("vaultwarden", "vault-" + entry.id, entry.name, detail, {
"glyph": "key",
"payload": entry.id
}));
}
return rows;
}
function rebuildResults() {""", "vaultwarden search")
dock = replace_once(dock,
' } else if (mode === "clipboard") {\n var ents = Clip.entries;',
' } else if (mode === "vaultwarden") {\n rows = dockWindow.vaultRows(q);\n } else if (mode === "clipboard") {\n var ents = Clip.entries;', "vaultwarden results")
dock = replace_once(dock,
' } else if (row.kind === "clip") {\n Clip.copy(row.payload);\n dockWindow.menuOpen = false;\n',
' } else if (row.kind === "clip") {\n Clip.copy(row.payload);\n dockWindow.menuOpen = false;\n } else if (row.kind === "vaultwarden") {\n Vaultwarden.getPassword(row.payload, Hyprland.activeToplevel ? Hyprland.activeToplevel.address : "");\n', "vaultwarden selection")
face_path.write_text(face)
dock_path.write_text(dock)
'';
baseSource = pkgs.applyPatches {
src = pkgs.fetchgit {
url = "https://github.com/Sn3akyy1/lucid.git";
rev = "bdd7f79da18efd837f79dcecbe5d2ec178abc10c";
@@ -287,6 +376,13 @@ PY
'')
];
};
source = pkgs.runCommand "lucid-vaultwarden-launcher-source" {
nativeBuildInputs = [ pkgs.python3 ];
} ''
cp -r ${baseSource} "$out"
chmod -R u+w "$out"
python3 ${vaultwardenLauncherPatch} "$out/luciddocks/LauncherFace.qml" "$out/luciddocks/Dock.qml"
'';
wallpaperDir = "${config.home.homeDirectory}/Wallpapers";
wallpaperScript = "${config.home.homeDirectory}/.config/hypr/scripts/wallpaper/set-wallpaper.sh";
widgetLayout = if osConfig.networking.hostName == "Rias"
-3
View File
@@ -126,9 +126,6 @@ ShellRoot {
WidgetLayer {
}
Vaultwarden {
}
WidgetIpc {
}