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:
4 files changed
+125
-266
No files matched your search
@@ -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"))
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -126,9 +126,6 @@ ShellRoot {
|
||||
WidgetLayer {
|
||||
}
|
||||
|
||||
Vaultwarden {
|
||||
}
|
||||
|
||||
WidgetIpc {
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user