diff --git a/flake.lock b/flake.lock index 7ad1735..690bc4e 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ ] }, "locked": { - "lastModified": 1749155310, - "narHash": "sha256-t0HfHg/1+TbSra5s6nNM0o4tnb3uqWedShSpZXsUMYY=", + "lastModified": 1750372185, + "narHash": "sha256-lVBKxd9dsZOH1fA6kSE5WNnt8e+09fN+NL/Q3BjTWHY=", "owner": "hyprwm", "repo": "aquamarine", - "rev": "94981cf75a9f11da0b6dd6a1abbd7c50a36ab2d3", + "rev": "7cef49d261cbbe537e8cb662485e76d29ac4cbca", "type": "github" }, "original": { @@ -35,11 +35,11 @@ }, "crane": { "locked": { - "lastModified": 1748970125, - "narHash": "sha256-UDyigbDGv8fvs9aS95yzFfOKkEjx1LO3PL3DsKopohA=", + "lastModified": 1750266157, + "narHash": "sha256-tL42YoNg9y30u7zAqtoGDNdTyXTi8EALDeCB13FtbQA=", "owner": "ipetkov", "repo": "crane", - "rev": "323b5746d89e04b22554b061522dfce9e4c49b18", + "rev": "e37c943371b73ed87faf33f7583860f81f1d5a48", "type": "github" }, "original": { @@ -111,6 +111,21 @@ "type": "github" } }, + "flake-compat_4": { + "locked": { + "lastModified": 1747046372, + "narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, "flake-parts": { "inputs": { "nixpkgs-lib": [ @@ -341,11 +356,11 @@ ] }, "locked": { - "lastModified": 1749238452, - "narHash": "sha256-8qiKEWcxUrjpUpK+WyFNg/72C8rp70LUuyTD23T+SdQ=", + "lastModified": 1750371717, + "narHash": "sha256-cNP+bVq8m5x2Rl6MTjwfQLCdwbVmKvTH7yqVc1SpiJM=", "owner": "hyprwm", "repo": "hyprgraphics", - "rev": "c7225d73755a6c4c7c72f4d4f3925ea426e325a8", + "rev": "15c6f8f3a567fec9a0f732cd310a7ff456deef88", "type": "github" }, "original": { @@ -374,11 +389,11 @@ "xdph": "xdph" }, "locked": { - "lastModified": 1750355078, - "narHash": "sha256-vNTJaHBvasZuuwD0cYOqhpICaQ0SnQ7CqBhSZLjw3sc=", + "lastModified": 1750959819, + "narHash": "sha256-KZuCRu4PpqK0QrUJ2i/9+k+51ZO7prnjdH2WfR3wkuI=", "owner": "hyprwm", "repo": "hyprland", - "rev": "b49d0ca20e4378db1e9abeb661d72f4c8c070db9", + "rev": "3d6476c9021519995239ec93bbb11f0dce6c60a0", "type": "github" }, "original": { @@ -467,11 +482,11 @@ ] }, "locked": { - "lastModified": 1749155776, - "narHash": "sha256-t1PM0wxQLQwv2F2AW23uA7pm5giwmcgYEWbNIRct9r4=", + "lastModified": 1750371812, + "narHash": "sha256-D868K1dVEACw17elVxRgXC6hOxY+54wIEjURztDWLk8=", "owner": "hyprwm", "repo": "hyprland-qtutils", - "rev": "396e8aa1c06274835b69da7f9a015fff9a9b7522", + "rev": "b13c7481e37856f322177010bdf75fccacd1adc8", "type": "github" }, "original": { @@ -496,11 +511,11 @@ ] }, "locked": { - "lastModified": 1749145882, - "narHash": "sha256-qr0KXeczF8Sma3Ae7+dR2NHhvG7YeLBJv19W4oMu6ZE=", + "lastModified": 1750371198, + "narHash": "sha256-/iuJ1paQOBoSLqHflRNNGyroqfF/yvPNurxzcCT0cAE=", "owner": "hyprwm", "repo": "hyprlang", - "rev": "1bfb84f54d50c7ae6558c794d3cfd5f6a7e6e676", + "rev": "cee01452bca58d6cadb3224e21e370de8bc20f0b", "type": "github" }, "original": { @@ -521,11 +536,11 @@ ] }, "locked": { - "lastModified": 1749819919, - "narHash": "sha256-7F/KG8dwSH9JXdlpOVrEEArS+PJSn0iEnx5eVCk89/I=", + "lastModified": 1750703126, + "narHash": "sha256-zJHmLsiW6P8h9HaH5eMKhEh/gvym3k6/Ywr4UHKpJfc=", "owner": "hyprwm", "repo": "hyprutils", - "rev": "57ab2a867d8b554ad89f29060c15efd11631db91", + "rev": "d46bd32da554c370f98180a1e465f052b9584805", "type": "github" }, "original": { @@ -546,11 +561,11 @@ ] }, "locked": { - "lastModified": 1749145760, - "narHash": "sha256-IHaGWpGrv7seFWdw/1A+wHtTsPlOGIKMrk1TUIYJEFI=", + "lastModified": 1750371869, + "narHash": "sha256-lGk4gLjgZQ/rndUkzmPYcgbHr8gKU5u71vyrjnwfpB4=", "owner": "hyprwm", "repo": "hyprwayland-scanner", - "rev": "817918315ea016cc2d94004bfb3223b5fd9dfcc6", + "rev": "aa38edd6e3e277ae6a97ea83a69261a5c3aab9fd", "type": "github" }, "original": { @@ -586,11 +601,11 @@ "rust-overlay": "rust-overlay" }, "locked": { - "lastModified": 1750168384, - "narHash": "sha256-PBfJ7dGsR02im/RYN8wXII8yNPFhKxiPdq+JDfbvD2k=", + "lastModified": 1750866260, + "narHash": "sha256-fo5NvfutMEw9OV+5rGYuCKjlNNjcnD3cKMbOfzusO/E=", "owner": "nix-community", "repo": "lanzaboote", - "rev": "38c2addd2e0cedcb03708de6e6c21fb1be86d410", + "rev": "f40a3401f86d117affeeb8ca6f0ce5cd1ca3cc24", "type": "github" }, "original": { @@ -662,11 +677,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1750134718, - "narHash": "sha256-v263g4GbxXv87hMXMCpjkIxd/viIF7p3JpJrwgKdNiI=", + "lastModified": 1750776420, + "narHash": "sha256-/CG+w0o0oJ5itVklOoLbdn2dGB0wbZVOoDm4np6w09A=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "9e83b64f727c88a7711a2c463a7b16eedb69a84c", + "rev": "30a61f056ac492e3b7cdcb69c1e6abdcf00e39cf", "type": "github" }, "original": { @@ -704,6 +719,22 @@ "type": "github" } }, + "nixpkgs_3": { + "locked": { + "lastModified": 1750339022, + "narHash": "sha256-ws4nec55lgEwmz5LovtxnrFlCFuAQiY/TEcspY8KycY=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "8b1e1e1e0fd8215f26e074ae47a73cbb2b1a328e", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, "pre-commit-hooks": { "inputs": { "flake-compat": "flake-compat", @@ -760,11 +791,11 @@ ] }, "locked": { - "lastModified": 1750335144, - "narHash": "sha256-mEudA3XHHFPjR7XHad5Si6tag5M/tTSJIB6oDLifv60=", + "lastModified": 1750880040, + "narHash": "sha256-ZwQH4akrZf5BxOfXx9Skjw7UpWAj1/ITVRDZsS/AYLA=", "ref": "refs/heads/master", - "rev": "95d0af8113394b1fdb71c94ac5160c83b8b829cb", - "revCount": 584, + "rev": "d949f913479445e4f0ca3a95a183ee45d98dc359", + "revCount": 596, "type": "git", "url": "https://git.outfoxxed.me/outfoxxed/quickshell" }, @@ -781,9 +812,11 @@ "lix-module": "lix-module", "nixpkgs": "nixpkgs", "quickshell": "quickshell", + "rust-overlay": "rust-overlay_2", "schizofox": "schizofox", "systems": "systems_3", - "watt": "watt" + "watt": "watt", + "zedless": "zedless" } }, "rust-overlay": { @@ -794,11 +827,31 @@ ] }, "locked": { - "lastModified": 1749955444, - "narHash": "sha256-CllTHvHX8KAdAZ+Lxzd23AmZTxO1Pfy+zC43/5tYkAE=", + "lastModified": 1750560265, + "narHash": "sha256-jQCojKl1/TzqE6ANOu6rP2qqxOcGK2xs6hpxZ77wrR8=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "539ba15741f0e6691a2448743dbc601d8910edce", + "rev": "076fdb0d45a9de3f379a626f51a62c78afe7efb1", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "rust-overlay_2": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1750905536, + "narHash": "sha256-Mo7yXM5IvMGNvJPiNkFsVT2UERmnvjsKgnY6UyDdySQ=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "2fa7c0aabd15fa0ccc1dc7e675a4fcf0272ad9a1", "type": "github" }, "original": { @@ -947,11 +1000,11 @@ ] }, "locked": { - "lastModified": 1749490041, - "narHash": "sha256-R9Dn9IyUdPaJHD2Oqd7XJnnxpka6M6UYw4Ld0iA46HM=", + "lastModified": 1750372504, + "narHash": "sha256-VBeZb1oqZM1cqCAZnFz/WyYhO8aF/ImagI7WWg/Z3Og=", "owner": "hyprwm", "repo": "xdg-desktop-portal-hyprland", - "rev": "3cf35e178bc192ee51e3fddfd69e531e2c106a30", + "rev": "400308fc4f9d12e0a93e483c2e7a649e12af1a92", "type": "github" }, "original": { @@ -959,6 +1012,25 @@ "repo": "xdg-desktop-portal-hyprland", "type": "github" } + }, + "zedless": { + "inputs": { + "flake-compat": "flake-compat_4", + "nixpkgs": "nixpkgs_3" + }, + "locked": { + "lastModified": 1750898414, + "narHash": "sha256-QuSbqvtgYIpEBriY/zTbsmOt+WXPuANSVAN4wnt6U1k=", + "owner": "zedless-editor", + "repo": "zed", + "rev": "3ab1a186f16d5554c3b91e1963f4e4e4acfcc5e4", + "type": "github" + }, + "original": { + "owner": "zedless-editor", + "repo": "zed", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index d4c330a..8b16869 100644 --- a/flake.nix +++ b/flake.nix @@ -20,7 +20,7 @@ system: pkgs: { inherit (import ./packages { - inherit pkgs; + inherit inputs pkgs; }) fish helix @@ -83,5 +83,15 @@ url = "https://git.lix.systems/lix-project/nixos-module/archive/2.93.0.tar.gz"; inputs.nixpkgs.follows = "nixpkgs"; }; + + # The things rust is making me do... + rust-overlay = { + url = "github:oxalica/rust-overlay"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + + zedless = { + url = "github:zedless-editor/zed"; + }; }; } diff --git a/hosts/hermit/programs.nix b/hosts/hermit/programs.nix index 3761c0d..76dcd9f 100644 --- a/hosts/hermit/programs.nix +++ b/hosts/hermit/programs.nix @@ -1,9 +1,32 @@ -{pkgs, ...}: { +{ + inputs, + pkgs, + ... +}:let + +# a newer nil version, for pipes support. +newer-nil = pkgs.nil.overrideAttrs (_: { + version = "unstable-02-06-2025"; + + src = pkgs.fetchFromGitHub { + owner = "oxalica"; + repo = "nil"; + rev = "577d160da311cc7f5042038456a0713e9863d09e"; + hash = "sha256-ggXU3RHv6NgWw+vc+HO4/9n0GPufhTIUjVuLci8Za8c="; + }; + cargoDeps = pkgs.rustPlatform.fetchCargoVendor { + inherit (newer-nil) src; + hash = "sha256-uZsLlFU9GKLvFllF7Kf5Q7HfN26KQojf4rvOb9p7Rjs="; + }; +}); + +in { environment.systemPackages = builtins.attrValues { inherit (pkgs) abook aerc + alejandra aichat alsa-utils anki @@ -79,5 +102,7 @@ xournalpp zathura ; + inherit (inputs.zedless.packages.${pkgs.stdenv.system}) zed-editor; + inherit newer-nil; }; } diff --git a/modules/services/printing/module.nix b/modules/services/printing/module.nix index 5df6b53..732fc17 100644 --- a/modules/services/printing/module.nix +++ b/modules/services/printing/module.nix @@ -18,15 +18,28 @@ in { tempDir = "/tmp/cups"; # Do not advertise shared printers. - browsing = false; + browsing = true; + + # browsedConf = '' + # BrowseDNSSDSubTypes _cups,_print + # BrowseLocalProtocols all + # BrowseRemoteProtocols all + # CreateIPPPrinterQueues All + + # BrowseProtocols all + # ''; # Enable the CUPS webinterface, accessible at localhost:631. webInterface = true; + logLevel = "debug"; + # Some local or network printers might need additional drivers. # These can be added into here. - drivers = [ - pkgs.cups-kyocera-ecosys-m552x-p502x + drivers = with pkgs; [ + gutenprint + cups-kyocera-ecosys-m552x-p502x + cups-filters ]; }; @@ -35,6 +48,7 @@ in { avahi = { enable = true; nssmdns4 = true; + nssmdns6 = true; openFirewall = true; }; }; diff --git a/modules/style/quickshell/shell/AudioPopup.qml b/modules/style/quickshell/shell/AudioPopup.qml new file mode 100644 index 0000000..b8e27ee --- /dev/null +++ b/modules/style/quickshell/shell/AudioPopup.qml @@ -0,0 +1,103 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +import QtQuick +import Quickshell +import QtQuick.Layouts +import Quickshell.Wayland +import Quickshell.Services.Pipewire + +Singleton { + id: audioPopup + property bool popupOpen: true + + property var volume: sink.ready ? audioPopup.sink.audio.volume : 0 + + property var visible: volume + + property PwNode sink: Pipewire.defaultAudioSink + + // bind the node so we can read its properties + PwObjectTracker { + objects: [audioPopup.sink] + } + + Timer { + id: timer + interval: 3000 + running: false + repeat: false + + onTriggered: audioPopup.visible = false + } + + LazyLoader { + id: loader + activeAsync: audioPopup.popupOpen + + PanelWindow { + id: popup + width: 400 + height: 30 + visible: true + + // Give the window an empty click mask so all clicks pass through it. + mask: Region {} + + // Use the wlroots specific layer property to ensure it displays over + // fullscreen windows. + WlrLayershell.layer: WlrLayer.Overlay + + color: "transparent" + + anchors { + bottom: true + } + + margins { + bottom: 250 + } + + Rectangle { + id: rect + Layout.fillWidth: true + anchors.verticalCenter: parent.verticalCenter + color: "white" + height: parent.height + width: parent.width + radius: 5 + opacity: 0 + + anchors { + left: parent.left + } + + Behavior on width { + NumberAnimation { + duration: 200 + easing.type: Easing.OutCubic + } + } + + Rectangle { + color: "black" + height: 20 + radius: height / 2 + + anchors { + left: parent.left + } + + topLeftRadius: 0 + bottomLeftRadius: 0 + + anchors.verticalCenter: parent.verticalCenter + width: parent.width * audioPopup.sink.audio.volume + } + } + } + } + + function init() { + } +} diff --git a/modules/style/quickshell/shell/ClockWidget.qml b/modules/style/quickshell/shell/ClockWidget.qml new file mode 100644 index 0000000..6cf0ad7 --- /dev/null +++ b/modules/style/quickshell/shell/ClockWidget.qml @@ -0,0 +1,46 @@ +import QtQuick +import Quickshell + +Rectangle { + + + width: text.width + implicitHeight: text.height + // border.color: "black" + border.width: 2 + radius: 0 + color: "transparent" + + Behavior on implicitHeight { + NumberAnimation { + duration: 100 + easing.type: Easing.OutCubic + } + } + + Item { + width: parent.width + height: text.height + + anchors.centerIn: parent + + SystemClock { + id: clock + precision: SystemClock.Seconds + } + + Text { + id: text + anchors.centerIn: parent + property var date: Date() + + text: Qt.formatDateTime(clock.date, "hh mm") + + font.family: "ComicShannsMono Nerd Font Mono" + font.weight: Font.ExtraBold + font.pointSize: 12 + + color: "black" + } + } +} diff --git a/modules/style/quickshell/shell/Launcher.qml b/modules/style/quickshell/shell/Launcher.qml new file mode 100644 index 0000000..183175e --- /dev/null +++ b/modules/style/quickshell/shell/Launcher.qml @@ -0,0 +1,310 @@ +pragma Singleton +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import Quickshell.Wayland +import Quickshell.Widgets + +import "./config" + +Singleton { + id: launcher + property bool launcherOpen: false + + IpcHandler { + target: "launcher" + + function open(): void { + launcher.launcherOpen = true; + } + + function close(): void { + launcher.launcherOpen = false; + } + + function toggle(): void { + launcher.launcherOpen = !launcher.launcherOpen; + } + } + + LazyLoader { + id: loader + activeAsync: launcher.launcherOpen + + PanelWindow { + implicitWidth: 450 + implicitHeight: 7 + searchContainer.implicitHeight + list.topMargin * 2 + list.delegateHeight * 10 + color: "transparent" + WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive + + Rectangle { + + height: 7 + searchContainer.implicitHeight + list.topMargin + list.bottomMargin + Math.min(list.contentHeight, list.delegateHeight * 10) + Behavior on height { + NumberAnimation { + duration: 200 + easing.type: Easing.OutCubic + } + } + width: 450 + color: Config.catppuccin.base + radius: 5 + border.color: Config.catppuccin.mantle + border.width: 2 + + ColumnLayout { + anchors.fill: parent + anchors.margins: 7 + anchors.bottomMargin: 0 + spacing: 0 + + Rectangle { + id: searchContainer + Layout.fillWidth: true + implicitHeight: searchbox.implicitHeight + 10 + color: Config.catppuccin.base + radius: 3 + border.color: Config.catppuccin.mantle + + RowLayout { + id: searchbox + anchors.fill: parent + anchors.margins: 5 + + TextInput { + id: search + Layout.fillWidth: true + color: Config.catppuccin.text + font.pointSize: 13 + + focus: true + Keys.forwardTo: [list] + Keys.onEscapePressed: launcher.launcherOpen = false + + Keys.onPressed: event => { + if (event.modifiers & Qt.ControlModifier) { + if (event.key == Qt.Key_J) { + list.currentIndex = list.currentIndex == list.count - 1 ? 0 : list.currentIndex + 1; + event.accepted = true; + } else if (event.key == Qt.Key_K) { + list.currentIndex = list.currentIndex == 0 ? list.count - 1 : list.currentIndex - 1; + event.accepted = true; + } + } + } + + onAccepted: { + if (list.currentItem) { + list.currentItem.clicked(null); + } + } + + onTextChanged: { + list.currentIndex = 0; + } + } + } + } + + ListView { + id: list + Layout.fillWidth: true + Layout.fillHeight: true + clip: true + cacheBuffer: 0 // works around QTBUG-131106 + //reuseItems: true + model: ScriptModel { + values: DesktopEntries.applications.values.map(object => { + const stxt = search.text.toLowerCase(); + const ntxt = object.name.toLowerCase(); + let si = 0; + let ni = 0; + + let matches = []; + let startMatch = -1; + + for (let si = 0; si != stxt.length; ++si) { + const sc = stxt[si]; + + while (true) { + // Drop any entries with letters that don't exist in order + if (ni == ntxt.length) + return null; + + const nc = ntxt[ni++]; + + if (nc == sc) { + if (startMatch == -1) + startMatch = ni; + break; + } else { + if (startMatch != -1) { + matches.push({ + index: startMatch, + length: ni - startMatch + }); + + startMatch = -1; + } + } + } + } + + if (startMatch != -1) { + matches.push({ + index: startMatch, + length: ni - startMatch + 1 + }); + } + + return { + object: object, + matches: matches + }; + }).filter(entry => entry !== null).sort((a, b) => { + let ai = 0; + let bi = 0; + let s = 0; + + while (ai != a.matches.length && bi != b.matches.length) { + const am = a.matches[ai]; + const bm = b.matches[bi]; + + s = bm.length - am.length; + if (s != 0) + return s; + + s = am.index - bm.index; + if (s != 0) + return s; + + ++ai; + ++bi; + } + + s = a.matches.length - b.matches.length; + if (s != 0) + return s; + + s = a.object.name.length - b.object.name.length; + if (s != 0) + return s; + + return a.object.name.localeCompare(b.object.name); + }).map(entry => entry.object) + + onValuesChanged: list.currentIndex = 0 + } + + topMargin: 7 + bottomMargin: list.count == 0 ? 0 : 7 + + add: Transition { + NumberAnimation { + property: "opacity" + from: 0 + to: 1 + duration: 100 + } + } + + displaced: Transition { + NumberAnimation { + property: "y" + duration: 200 + easing.type: Easing.OutCubic + } + NumberAnimation { + property: "opacity" + to: 1 + duration: 100 + } + } + + move: Transition { + NumberAnimation { + property: "y" + duration: 200 + easing.type: Easing.OutCubic + } + NumberAnimation { + property: "opacity" + to: 1 + duration: 100 + } + } + + remove: Transition { + NumberAnimation { + property: "y" + duration: 200 + easing.type: Easing.OutCubic + } + NumberAnimation { + property: "opacity" + to: 0 + duration: 100 + } + } + + highlight: Rectangle { + radius: 5 + color: "transparent" + border.color: Config.catppuccin.lavender + border.width: 2 + } + keyNavigationEnabled: true + keyNavigationWraps: true + highlightMoveVelocity: -1 + highlightMoveDuration: 50 + preferredHighlightBegin: list.topMargin + preferredHighlightEnd: list.height - list.bottomMargin + highlightRangeMode: ListView.ApplyRange + snapMode: ListView.SnapToItem + + readonly property real delegateHeight: 44 + + delegate: MouseArea { + required property DesktopEntry modelData + + implicitHeight: list.delegateHeight + implicitWidth: ListView.view.width + + onClicked: { + modelData.execute(); + launcher.launcherOpen = false; + } + + RowLayout { + id: delegateLayout + anchors { + verticalCenter: parent.verticalCenter + left: parent.left + leftMargin: 5 + } + + IconImage { + Layout.alignment: Qt.AlignVCenter + asynchronous: true + implicitSize: 30 + source: Quickshell.iconPath(modelData.icon) + } + Text { + text: modelData.name + color: Config.catppuccin.text + font.family: "JetBrainsMono Nerd Font Mono" + font.pointSize: 13 + Layout.alignment: Qt.AlignVCenter + } + } + } + } + } + } + } + } + function init() { + } +} diff --git a/modules/style/quickshell/shell/ReloadPopup.qml b/modules/style/quickshell/shell/ReloadPopup.qml new file mode 100644 index 0000000..9c6e2fc --- /dev/null +++ b/modules/style/quickshell/shell/ReloadPopup.qml @@ -0,0 +1,127 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Layouts +import Quickshell + +Scope { + id: root + property bool failed + property string errorString + + // Connect to the Quickshell global to listen for the reload signals. + Connections { + target: Quickshell + + function onReloadCompleted() { + root.failed = false; + popupLoader.loading = true; + } + + function onReloadFailed(error: string) { + // Close any existing popup before making a new one. + popupLoader.active = false; + + root.failed = true; + root.errorString = error; + popupLoader.loading = true; + } + } + + // Keep the popup in a loader because it isn't needed most of the timeand will take up + // memory that could be used for something else. + LazyLoader { + id: popupLoader + + PanelWindow { + id: popup + + anchors { + top: true + right: true + } + + margins { + top: 25 + left: 25 + } + + width: rect.width + height: rect.height + + // color blending is a bit odd as detailed in the type reference. + color: "transparent" + + Rectangle { + id: rect + color: root.failed ? "#40802020" : "#40009020" + + implicitHeight: layout.implicitHeight + 50 + implicitWidth: layout.implicitWidth + 30 + + // Fills the whole area of the rectangle, making any clicks go to it, + // which dismiss the popup. + MouseArea { + id: mouseArea + anchors.fill: parent + onClicked: popupLoader.active = false + + // makes the mouse area track mouse hovering, so the hide animation + // can be paused when hovering. + hoverEnabled: true + } + + ColumnLayout { + id: layout + anchors { + top: parent.top + topMargin: 20 + horizontalCenter: parent.horizontalCenter + } + + Text { + text: root.failed ? "Reloaing failed." : "Reloading completed!" + color: "white" + } + + Text { + text: root.errorString + color: "white" + // When visible is false, it also takes up no space. + visible: root.errorString != "" + } + } + + // A progress bar on the bottom of the screen, showing how long until the + // popup is removed. + Rectangle { + id: bar + color: "#20ffffff" + anchors.bottom: parent.bottom + anchors.left: parent.left + height: 20 + + PropertyAnimation { + id: anim + target: bar + property: "width" + from: rect.width + to: 0 + duration: failed ? 10000 : 800 + onFinished: popupLoader.active = false + + // Pause the animation when the mouse is hovering over the popup, + // so it stays onscreen while reading. This updates reactively + // when the mouse moves on and off the popup. + paused: mouseArea.containsMouse + } + } + + // We could set `running: true` inside the animation, but the width of the + // rectangle might not be calculated yet, due to the layout. + // In the `Component.onCompleted` event handler, all of the component's + // properties and children have been initialized. + Component.onCompleted: anim.start() + } + } + } +} diff --git a/modules/style/quickshell/shell/SysTray.qml b/modules/style/quickshell/shell/SysTray.qml new file mode 100644 index 0000000..7445bcd --- /dev/null +++ b/modules/style/quickshell/shell/SysTray.qml @@ -0,0 +1,57 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Layouts +import QtQuick.Effects +import QtQuick.Controls +import Quickshell +import Quickshell.Services.SystemTray + +Rectangle { + id: root + required property var bar + + width: parent.width + height: column.height + 10 + color: "#30c0ffff" + radius: 5 + border.color: "black" + border.width: 2 + + RowLayout { + id: column + spacing: 10 + + anchors.centerIn: parent + + Repeater { + model: SystemTray.items + + Item { + id: item + + required property SystemTrayItem modelData + + height: 35 + width: 35 + + Image { + source: item.modelData.icon + anchors.fill: parent + + MouseArea { + anchors.fill: parent + onClicked: function (mouse) { + if (mouse.button === Qt.LeftButton) { + item.modelData.activate(); + } + if (mouse.button === Qt.RightButton) { + if (item.modelData.hasMenu) {} + } + } + } + } + } + } + } +} diff --git a/modules/style/quickshell/shell/config/Config.qml b/modules/style/quickshell/shell/config/Config.qml new file mode 100644 index 0000000..28994a9 --- /dev/null +++ b/modules/style/quickshell/shell/config/Config.qml @@ -0,0 +1,50 @@ +pragma Singleton + +import Quickshell +import QtQuick + +Singleton { + id: root + + readonly property QtObject bar: QtObject { + readonly property int width: 50 + readonly property var colors: QtObject { + readonly property color bar: "#1e1e2e" + readonly property color barOutline: "#50ffffff" + readonly property color widget: "#25ceffff" + readonly property color widgetActive: "#80ceffff" + readonly property color widgetOutline: "#40ffffff" + readonly property color widgetOutlineSeparate: "#20ffffff" + readonly property color separator: "#60ffffff" + } + } + + readonly property QtObject border: QtObject { + readonly property int thickness: 8 + readonly property color color: "#1e1e2e" + readonly property int rounding: 25 + } + + readonly property QtObject catppuccin: QtObject { + readonly property color base: "#1e1e2e" + readonly property color mantle: "#181825" + readonly property color surface0: "#313244" + readonly property color surface1: "#45475a" + readonly property color surface2: "#585b70" + readonly property color text: "#cdd6f4" + readonly property color rosewater: "#f5e0dc" + readonly property color lavender: "#b4befe" + readonly property color red: "#f38ba8" + readonly property color peach: "#fab387" + readonly property color yellow: "#f9e2af" + readonly property color green: "#a6e3a1" + readonly property color teal: "#a6e3a1" + readonly property color blue: "#89b4fa" + readonly property color mauve: "#cba6f7" + readonly property color flamingo: "#f2cdcd" + } + readonly property QtObject volumeslider: QtObject { + readonly property int width: 50 + } + +} diff --git a/modules/style/quickshell/shell/modules/bar/Bar.qml b/modules/style/quickshell/shell/modules/bar/Bar.qml new file mode 100644 index 0000000..b0fc519 --- /dev/null +++ b/modules/style/quickshell/shell/modules/bar/Bar.qml @@ -0,0 +1,50 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Layouts +import Quickshell + +import "../../config" +import "components" + +Item { + id: root + + required property ShellScreen screen + + anchors { + top: parent.top + bottom: parent.bottom + left: parent.left + } + + implicitWidth: Config.bar.width + + Item { + id: child + + anchors { + top: parent.top + bottom: parent.bottom + horizontalCenter: parent.horizontalCenter + } + + implicitWidth: Math.max(clock.implicitWidth, workspaces.implicitWidth) + + ColumnLayout { + spacing: 2 + anchors { + top: parent.top + left: parent.left + right: parent.right + margins: 0 + } + Clock { + id: clock + } + Workspaces { + id: workspaces + screen: root.screen + } + } + } +} diff --git a/modules/style/quickshell/shell/modules/bar/components/Clock.qml b/modules/style/quickshell/shell/modules/bar/components/Clock.qml new file mode 100644 index 0000000..4826851 --- /dev/null +++ b/modules/style/quickshell/shell/modules/bar/components/Clock.qml @@ -0,0 +1,34 @@ +import QtQuick +import Quickshell + +import "../../../config" + +Rectangle { + id: root + + width: text.width + 5 + height: text.height + 5 + implicitWidth: width + border.color: Config.catppuccin.rosewater + border.width: 0 + radius: 5 + color: "transparent" + + Text { + id: text + anchors.centerIn: parent + property var date: Date() + + text: Qt.formatDateTime(clock.date, "hh mm") + + font.family: "JetBrainsMono NF Mono" + font.pointSize: 15 + + color: Config.catppuccin.text + } + + SystemClock { + id: clock + precision: SystemClock.Seconds + } +} diff --git a/modules/style/quickshell/shell/modules/bar/components/Workspaces.qml b/modules/style/quickshell/shell/modules/bar/components/Workspaces.qml new file mode 100644 index 0000000..91bc9d2 --- /dev/null +++ b/modules/style/quickshell/shell/modules/bar/components/Workspaces.qml @@ -0,0 +1,109 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import Quickshell +import Quickshell.Io +import QtQuick.Layouts + +import "../../../services/niri" +import "../../../config" + +Rectangle { + id: root + + required property ShellScreen screen + property var workspaces: Niri.workspaces + property var wsCount: Niri.workspaces.length + property var activeWorkspace: Niri.activeWorkspace + property var activeWorkspaceIndex: Niri.activeWorkspaceIndex + + property int wsItemHeight: 15 + + signal workspaceAdded(workspace: var) + function onWorkspaceAdded(workspace: var) { + root.workspaces.push(workspace); + } + + // property bool _: log() + // function log() { + // console.log(workspaces.values); + // return true; + // } + + // Works + height: 300 + + // Gives warning + // height: workspaces.length * root.wsItemHeight + implicitWidth: list.implicitWidth + color: "transparent" + border.color: Config.catppuccin.rosewater + border.width: 0 + radius: 7 + + Layout.fillWidth: true + + ListView { + id: list + model: root.workspaces + implicitHeight: contentHeight + implicitWidth: contentItem.childrenRect.width + anchors.horizontalCenter: parent.horizontalCenter + + // anchors.fill: parent + + delegate: Item { + id: wsItem + // Name of the workspace + property string name: "VOID" + // ID of the workspace + required property string id + + required property string output + + property bool isActive: (id - 1 == root.activeWorkspaceIndex) + + property real animActive: isActive ? 1 : 0.65 + Behavior on animActive { + NumberAnimation { + duration: 150 + } + } + + // property bool isCorrectScreen: log() + // function log() { + // console.log("Screen name: " + root.screen.name); + // console.log(wsItem.output); + // console.log(wsItem.id); + + // let isCorrect = root.screen.name == wsItem.output; + // console.log("isCorrect: ", isCorrect); + // return root.screen.name == wsItem.output; + // } + + implicitHeight: root.wsItemHeight + implicitWidth: 50 + + anchors { + right: parent.right + left: parent.left + } + + Rectangle { + anchors.centerIn: parent + height: wsItem.height - 5 + width: parent.width * wsItem.animActive + radius: height / 2 + border.color: Config.catppuccin.mantle + border.width: 0 + color: Config.catppuccin.blue + } + } + } + + Component.onCompleted: { + Niri.workspaces.forEach(workspace => { + root.workspaceAdded(workspace); + }); + } +} diff --git a/modules/style/quickshell/shell/modules/drawers/Backgrounds.qml b/modules/style/quickshell/shell/modules/drawers/Backgrounds.qml new file mode 100644 index 0000000..388efc4 --- /dev/null +++ b/modules/style/quickshell/shell/modules/drawers/Backgrounds.qml @@ -0,0 +1,9 @@ +import Quickshell +import QtQuick +import QtQuick.Shapes + +import "../notifications" as Notifications + +Rectangle { + required property Item bar +} diff --git a/modules/style/quickshell/shell/modules/drawers/Border.qml b/modules/style/quickshell/shell/modules/drawers/Border.qml new file mode 100644 index 0000000..7e9c8a8 --- /dev/null +++ b/modules/style/quickshell/shell/modules/drawers/Border.qml @@ -0,0 +1,55 @@ +import Quickshell +import QtQuick +import QtQuick.Effects + +import "../../config" + +Item { + id: root + + required property Item bar + + anchors.fill: parent + + Rectangle { + id: rect + + // Parent has a mask applied that cuts out all except the border and the bar. + anchors.fill: parent + color: Config.border.color + visible: false + + + Behavior on color { + ColorAnimation { + duration: 150 + easing.type: Easing.BezierSpline + } + } + } + + Item { + id: mask + + anchors.fill: parent + layer.enabled: true + visible: false + + Rectangle { + anchors.fill: parent + anchors.margins: Config.border.thickness + anchors.leftMargin: root.bar.implicitWidth + radius: Config.border.rounding + } + } + + MultiEffect { + anchors.fill: parent + maskEnabled: true + maskInverted: true + maskSource: mask + source: rect + maskThresholdMin: 0.5 + maskSpreadAtMin: 1 + } +} diff --git a/modules/style/quickshell/shell/modules/drawers/Drawers.qml b/modules/style/quickshell/shell/modules/drawers/Drawers.qml new file mode 100644 index 0000000..4a53f95 --- /dev/null +++ b/modules/style/quickshell/shell/modules/drawers/Drawers.qml @@ -0,0 +1,104 @@ +pragma ComponentBehavior: Bound + +import Quickshell +import Quickshell.Wayland +import Quickshell.Services.Notifications +import QtQuick +import QtQuick.Effects + +import "../bar" +import "../volume" +import "../notifications" + +import "../../services" +import "../../config" + +Variants { + model: Quickshell.screens + + Scope { + id: scope + required property ShellScreen modelData + + Exclusions { + screen: scope.modelData + bar: bar + } + + PanelWindow { + id: win + + screen: scope.modelData + color: "transparent" + + WlrLayershell.exclusionMode: ExclusionMode.Ignore + WlrLayershell.keyboardFocus: WlrKeyboardFocus.None + + // Clickthrough mask. + // Clickable areas of the window are determined by the provided region. + mask: Region { + // Start at the bottom left; right of the bar and on top of the border + x: bar.implicitWidth + y: Config.border.thickness + + // Width is the window width - the bar's width - the border thickness + width: win.width - bar.implicitWidth - Config.border.thickness + + // Height is window width - the border thickness x2 —top border and bottom border. + height: win.height - Config.border.thickness * 2 + + // Setting the intersection mode to Xor will invert the mask and make everything in the mask region not clickable and pass through clicks inside it through the window. + intersection: Intersection.Xor + Region { + item: volume + intersection: Intersection.Subtract + } + } + + anchors { + top: true + bottom: true + left: true + right: true + } + + Item { + id: background + + anchors.fill: parent + visible: false + + Border { + bar: bar + } + + Backgrounds { + bar: bar + } + } + + MultiEffect { + anchors.fill: source + source: background + shadowEnabled: true + blurMax: 15 + } + + Bar { + id: bar + screen: scope.modelData + } + + // VolumeSlider { + // id: volume + // screen: scope.modelData + // } + + // Hover { + // id: hover + // screen: scope.modelData + // bar: bar + // } + } + } +} diff --git a/modules/style/quickshell/shell/modules/drawers/Exclusions.qml b/modules/style/quickshell/shell/modules/drawers/Exclusions.qml new file mode 100644 index 0000000..d99092d --- /dev/null +++ b/modules/style/quickshell/shell/modules/drawers/Exclusions.qml @@ -0,0 +1,38 @@ + +pragma ComponentBehavior: Bound +import Quickshell +import QtQuick + +import "../../config" + +Scope { + id: root + required property ShellScreen screen + required property Item bar + + ExclusionZone { + anchors.left: true + exclusiveZone: root.bar.implicitWidth + } + + ExclusionZone { + anchors.top: true + } + + ExclusionZone { + anchors.right: true + } + + ExclusionZone { + anchors.bottom: true + } + + component ExclusionZone: PanelWindow { + screen: root.screen + color: "transparent" + exclusiveZone: Config.border.thickness + implicitHeight: Config.border.thickness + implicitWidth: Config.border.thickness + mask: Region {} + } +} diff --git a/modules/style/quickshell/shell/modules/launcher/Launcher.qml b/modules/style/quickshell/shell/modules/launcher/Launcher.qml new file mode 100644 index 0000000..8cf4f37 --- /dev/null +++ b/modules/style/quickshell/shell/modules/launcher/Launcher.qml @@ -0,0 +1,319 @@ +pragma Singleton +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import Quickshell.Wayland +import Quickshell.Widgets + +Singleton { + id: launcher + property bool launcherOpen: false + + IpcHandler { + target: "launcher" + + function open(): void { + launcher.launcherOpen = true; + } + + function close(): void { + launcher.launcherOpen = false; + } + + function toggle(): void { + launcher.launcherOpen = !launcher.launcherOpen; + } + } + + LazyLoader { + id: loader + activeAsync: launcher.launcherOpen + + PanelWindow { + width: 450 + height: 7 + searchContainer.implicitHeight + list.topMargin * 2 + list.delegateHeight * 10 + color: "transparent" + + + anchors { + bottom: parent.bottom + } + + + WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive + WlrLayershell.namespace: "shell:launcher" + + Rectangle { + + height: 7 + searchContainer.implicitHeight + list.topMargin + list.bottomMargin + Math.min(list.contentHeight, list.delegateHeight * 10) + Behavior on height { + NumberAnimation { + duration: 200 + easing.type: Easing.OutCubic + } + } + width: 450 + color: "#30c0afaf" + radius: 5 + border.color: "black" + border.width: 2 + + ColumnLayout { + anchors.fill: parent + anchors.margins: 7 + anchors.bottomMargin: 0 + spacing: 0 + + Rectangle { + id: searchContainer + Layout.fillWidth: true + implicitHeight: searchbox.implicitHeight + 10 + color: "#30c0ffff" + radius: 3 + border.color: "#50ffffff" + + RowLayout { + id: searchbox + anchors.fill: parent + anchors.margins: 5 + + IconImage { + implicitSize: parent.height + source: "root:icons/magnifying-glass.svg" + } + + TextInput { + id: search + Layout.fillWidth: true + color: "black" + + focus: true + Keys.forwardTo: [list] + Keys.onEscapePressed: launcher.launcherOpen = false + + Keys.onPressed: event => { + if (event.modifiers & Qt.ControlModifier) { + if (event.key == Qt.Key_J) { + list.currentIndex = list.currentIndex == list.count - 1 ? 0 : list.currentIndex + 1; + event.accepted = true; + } else if (event.key == Qt.Key_K) { + list.currentIndex = list.currentIndex == 0 ? list.count - 1 : list.currentIndex - 1; + event.accepted = true; + } + } + } + + onAccepted: { + if (list.currentItem) { + list.currentItem.clicked(null); + } + } + + onTextChanged: { + list.currentIndex = 0; + } + } + } + } + + ListView { + id: list + Layout.fillWidth: true + Layout.fillHeight: true + clip: true + cacheBuffer: 0 // works around QTBUG-131106 + //reuseItems: true + model: ScriptModel { + values: DesktopEntries.applications.values.map(object => { + const stxt = search.text.toLowerCase(); + const ntxt = object.name.toLowerCase(); + let si = 0; + let ni = 0; + + let matches = []; + let startMatch = -1; + + for (let si = 0; si != stxt.length; ++si) { + const sc = stxt[si]; + + while (true) { + // Drop any entries with letters that don't exist in order + if (ni == ntxt.length) + return null; + + const nc = ntxt[ni++]; + + if (nc == sc) { + if (startMatch == -1) + startMatch = ni; + break; + } else { + if (startMatch != -1) { + matches.push({ + index: startMatch, + length: ni - startMatch + }); + + startMatch = -1; + } + } + } + } + + if (startMatch != -1) { + matches.push({ + index: startMatch, + length: ni - startMatch + 1 + }); + } + + return { + object: object, + matches: matches + }; + }).filter(entry => entry !== null).sort((a, b) => { + let ai = 0; + let bi = 0; + let s = 0; + + while (ai != a.matches.length && bi != b.matches.length) { + const am = a.matches[ai]; + const bm = b.matches[bi]; + + s = bm.length - am.length; + if (s != 0) + return s; + + s = am.index - bm.index; + if (s != 0) + return s; + + ++ai; + ++bi; + } + + s = a.matches.length - b.matches.length; + if (s != 0) + return s; + + s = a.object.name.length - b.object.name.length; + if (s != 0) + return s; + + return a.object.name.localeCompare(b.object.name); + }).map(entry => entry.object) + + onValuesChanged: list.currentIndex = 0 + } + + topMargin: 7 + bottomMargin: list.count == 0 ? 0 : 7 + + add: Transition { + NumberAnimation { + property: "opacity" + from: 0 + to: 1 + duration: 100 + } + } + + displaced: Transition { + NumberAnimation { + property: "y" + duration: 200 + easing.type: Easing.OutCubic + } + NumberAnimation { + property: "opacity" + to: 1 + duration: 100 + } + } + + move: Transition { + NumberAnimation { + property: "y" + duration: 200 + easing.type: Easing.OutCubic + } + NumberAnimation { + property: "opacity" + to: 1 + duration: 100 + } + } + + remove: Transition { + NumberAnimation { + property: "y" + duration: 200 + easing.type: Easing.OutCubic + } + NumberAnimation { + property: "opacity" + to: 0 + duration: 100 + } + } + + highlight: Rectangle { + radius: 5 + color: "#20e0ffff" + border.color: "#30ffffff" + border.width: 1 + } + keyNavigationEnabled: true + keyNavigationWraps: true + highlightMoveVelocity: -1 + highlightMoveDuration: 50 + preferredHighlightBegin: list.topMargin + preferredHighlightEnd: list.height - list.bottomMargin + highlightRangeMode: ListView.ApplyRange + snapMode: ListView.SnapToItem + + readonly property real delegateHeight: 44 + + delegate: MouseArea { + required property DesktopEntry modelData + + implicitHeight: list.delegateHeight + implicitWidth: ListView.view.width + + onClicked: { + modelData.execute(); + launcher.launcherOpen = false; + } + + RowLayout { + id: delegateLayout + anchors { + verticalCenter: parent.verticalCenter + left: parent.left + leftMargin: 5 + } + + IconImage { + Layout.alignment: Qt.AlignVCenter + asynchronous: true + implicitSize: 30 + source: Quickshell.iconPath(modelData.icon) + } + Text { + text: modelData.name + color: "black" + font.family: "ComicShannsMono Nerd Font Mono" + Layout.alignment: Qt.AlignVCenter + } + } + } + } + } + } + } + } + function init() { + } +} diff --git a/modules/style/quickshell/shell/modules/notifications/Notification.qml b/modules/style/quickshell/shell/modules/notifications/Notification.qml new file mode 100644 index 0000000..9e6f298 --- /dev/null +++ b/modules/style/quickshell/shell/modules/notifications/Notification.qml @@ -0,0 +1,61 @@ +import QtQuick +import QtQuick.Shapes + +import "../../config" +import "../../services" + +Shape { + id: root + anchors.top: parent.top + // anchors.right: parent.right + anchors.horizontalCenter: parent.horizontalCenter + + readonly property real rounding: Config.border.rounding + + implicitWidth: 1000 + implicitHeight: 300 + ShapePath { + fillColor: Config.catppuccin.base + strokeWidth: -1 + + PathArc { + relativeX: root.rounding + relativeY: root.rounding + radiusX: root.rounding + radiusY: root.rounding + } + PathLine { + relativeX: 0 + relativeY: 100 + } + PathLine { + relativeX: 300 + relativeY: 0 + } + PathLine { + relativeX: 0 + relativeY: -100 + } + PathArc { + relativeX: root.rounding + relativeY: -root.rounding + radiusX: root.rounding + radiusY: root.rounding + } + + Behavior on fillColor { + ColorAnimation { + duration: 500 + easing.type: Easing.BezierSpline + } + } + } + + Text { + anchors.centerIn: parent + text: "This is a test" + color: "white" + font.pointSize: 15 + font.bold: true + } +} diff --git a/modules/style/quickshell/shell/modules/volume/VolumeSlider.qml b/modules/style/quickshell/shell/modules/volume/VolumeSlider.qml new file mode 100644 index 0000000..a843d0f --- /dev/null +++ b/modules/style/quickshell/shell/modules/volume/VolumeSlider.qml @@ -0,0 +1,82 @@ +pragma ComponentBehavior: Bound + +import Quickshell +import QtQuick +import QtQuick.Shapes +import "../../config" + +Rectangle { + id: root + required property ShellScreen screen + readonly property real rounding: Config.border.rounding + color: "transparent" + + anchors { + right: parent.right + verticalCenter: parent.verticalCenter + } + + implicitWidth: Config.volumeslider.width + implicitHeight: screen.height / 3 + + Rectangle { + anchors.right: parent.right + color: "green" + implicitWidth: hover.hovered ? Config.volumeslider.width : Config.border.thickness + + implicitHeight: root.screen.height / 3 + + HoverHandler { + id: hover + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad + } + + Behavior on implicitWidth { + NumberAnimation { + duration: 400 + easing.type: Easing.OutCubic + } + } + } + + Shape { + implicitWidth: hover.hovered ? Config.volumeslider.width : Config.border.thickness + implicitHeight: root.screen.height / 3 + ShapePath { + fillColor: Config.catppuccin.base + strokeWidth: -1 + PathArc { + relativeX: -root.rounding + relativeY: root.rounding + radiusX: root.rounding + radiusY: root.rounding + direction: PathArc.Counterclockwise + } + PathLine { + relativeY: root.screen.height / 3 + relativeX: 0 + } + PathArc { + relativeX: root.rounding + // relativeX: 50 + relativeY: -root.rounding + radiusX: root.rounding + radiusY: root.rounding + // direction: PathArc.Counterclockwise + // useLargeArc: true + } + PathLine { + relativeX: 50 + relativeY: 0 + } + PathLine { + relativeY: -root.screen.height / 3 + relativeX: 0 + } + PathLine { + relativeX: -50 + relativeY: 0 + } + } + } +} diff --git a/modules/style/quickshell/shell/services/Hover.qml b/modules/style/quickshell/shell/services/Hover.qml new file mode 100644 index 0000000..cd29894 --- /dev/null +++ b/modules/style/quickshell/shell/services/Hover.qml @@ -0,0 +1,49 @@ +import Quickshell +import QtQuick +import "../config" + +MouseArea { + id: root + + anchors.fill: parent + hoverEnabled: true + + required property ShellScreen screen + // required property Panels panels + required property Item bar + + property bool showVolumeMenu: false + property bool isInRightPanel: false + + // function withinPanelHeight(panel: Item, x: real, y: real): bool { + // const panelY = Config.border.thickness + panel.y; + // return y >= panelY - Config.border.rounding && y <= panelY + panel.height + Config.border.rounding; + // } + + // function inLeftBorder(x: real, y: real): bool { + // return x <= Config.border.thickness; + // } + + function inRightPanel(x: real, y: real): bool { + // Cursor is in middle veritcal third of screen + // Cursor is in the right border + return y >= root.screen.height / 3 && y <= (root.screen.height / 3) * 2 && x >= root.screen.width - Config.border.thickness; + } + + // Update on mouse cursor movement + onPositionChanged: event => { + const x = event.x; + const y = event.y; + + root.isInRightPanel = inRightPanel(x, y); + + console.log("In right panel: " + root.isInRightPanel); + + console.log("x:" + x + " y: " + y); + } + onContainsMouseChanged: { + if (!containsMouse) { + root.isInRightPanel = false; + } + } +} diff --git a/modules/style/quickshell/shell/services/Notification.qml b/modules/style/quickshell/shell/services/Notification.qml new file mode 100644 index 0000000..bdf52de --- /dev/null +++ b/modules/style/quickshell/shell/services/Notification.qml @@ -0,0 +1,20 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +import QtQuick +import Quickshell +import Quickshell.Io +import Quickshell.Services.Notifications + +/** + * Provides extra features not in Quickshell.Services.Notifications: + * - Persistent storage + * - Popup notifications, with timeout + * - Notification groups by app + */ +Singleton { + id: root + NotificationServer { + + } +} diff --git a/modules/style/quickshell/shell/services/niri/Niri.qml b/modules/style/quickshell/shell/services/niri/Niri.qml new file mode 100644 index 0000000..de16157 --- /dev/null +++ b/modules/style/quickshell/shell/services/niri/Niri.qml @@ -0,0 +1,94 @@ +// Kind thanks to https://github.com/MapoMagpie/nixos-flakes/blob/main/home/ui/quickshell/config/Data/Niri.qml +// This file was taken from there and further modified. + +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Io + +Singleton { + id: root + // property var data + property var workspaces: [] + property var activeWorkspace: "VOID" + property var activeWorkspaceIndex: 0 + property var windows: [] + // property var activedWindowId: 0 + + Process { + id: proc + command: ["niri", "msg", "-j", "event-stream"] + + running: true + stdout: SplitParser { + onRead: data => { + var event = JSON.parse(data); + let workspaces = []; + if (event.WorkspacesChanged) { + root.workspaces = event.WorkspacesChanged.workspaces; + root.workspaces = root.workspaces.sort((a, b) => a.id - b.id); + root.activeWorkspaceIndex = root.workspaces.findIndex(w => w.is_focused); + if (root.activeWorkspaceIndex < 0) { + root.activeWorkspaceIndex = 0; + } + root.activeWorkspace = root.workspaces[root.activeWorkspaceIndex].name; + } + if (event.WindowsChanged) { + root.windows = [...event.WindowsChanged.windows].sort((a, b) => a.id - b.id); + } + if (event.WindowOpenedOrChanged) { + const window = event.WindowOpenedOrChanged.window; + const index = root.windows.findIndex(w => w.id === window.id); + // console.log("window opened or changed: ", index, ", win id: ", window.id); + if (index >= 0) { + // console.log("replace window, old: ", root.windows[index].id, ", new: ", window.id); + root.windows[index] = window; + } else { + // console.log("push window, new: ", window.id); + root.windows.push(window); + } + root.windows = [...root.windows.sort((a, b) => a.id - b.id)]; + } + if (event.WindowClosed) { + const index = root.windows.findIndex(w => w.id === event.WindowClosed.id); + // console.log("window closed: ", index, ", win id: ", event.WindowClosed.id); + if (index >= 0) { + root.windows.splice(index, 1); + } + root.windows = [...root.windows.sort((a, b) => a.id - b.id)]; + } + if (event.WorkspaceActivated) { + root.activeWorkspaceIndex = root.workspaces.findIndex(w => w.id === event.WorkspaceActivated.id); + if (root.activeWorkspaceIndex < 0) { + root.activeWorkspaceIndex = 0; + } + root.activeWorkspace = root.workspaces[root.activeWorkspaceIndex].name; + } + } + } + } + // component Workspace: QtObject { + // required property int id + // property int idx + // property string name: "VOID" + // required property string output + // property bool is_active + // property bool is_focused + // property int active_window_id + // } +} + +// { +// "workspaces": [ +// { +// "id": 5, +// "idx": 4, +// "name": "GAME", +// "output": "DP-3", +// "is_active": false, +// "is_focused": false, +// "active_window_id": null +// }, +// ] +// } diff --git a/modules/style/quickshell/shell/shell.qml b/modules/style/quickshell/shell/shell.qml index e69de29..05fea55 100644 --- a/modules/style/quickshell/shell/shell.qml +++ b/modules/style/quickshell/shell/shell.qml @@ -0,0 +1,21 @@ +//@ pragma Env QS_NO_RELOAD_POPUP=1 + +import Quickshell +import QtQuick + +import "modules" +import "modules/drawers" +import "services" + +// import "modules/background" + +ShellRoot { + id: shellroot + + Component.onCompleted: [Launcher.init()] + + Drawers {} + // Background {}Popup + // + +} diff --git a/modules/system/hardware/power.nix b/modules/system/hardware/power.nix index a867c14..69a725b 100644 --- a/modules/system/hardware/power.nix +++ b/modules/system/hardware/power.nix @@ -33,73 +33,176 @@ in { enable = true; # sample config from https://github.com/NotAShelf/watt#sample-configuration settings = { - charger = { - # CPU governor to use - governor = "performance"; - # Turbo boost setting: "always", "auto", or "never" - turbo = "auto"; - - # Enable or disable automatic turbo management (when turbo = "auto") - enable_auto_turbo = true; - # Custom thresholds for auto turbo management - turbo_auto_settings = { - load_threshold_high = 70.0; - load_threshold_low = 30.0; - temp_threshold_high = 75.0; - initial_turbo_state = false; # whether turbo should be initially enabled (false = disabled) - }; - # Energy Performance Preference - epp = "performance"; - # Energy Performance Bias (0-15 scale or named value) - epb = "balance_performance"; - # Platform profile (if supported) - platform_profile = "performance"; - # Min/max frequency in MHz (optional) - min_freq_mhz = 800; - max_freq_mhz = 3500; - # Optional: Profile-specific battery charge thresholds (overrides global setting) - # battery_charge_thresholds = [40, 80] # Start at 40%, stop at 80% - }; - - battery = { - governor = "powersave"; - turbo = "auto"; - - # More conservative auto turbo settings on battery - enable_auto_turbo = true; - turbo_auto_settings = { - load_threshold_high = 80.0; - load_threshold_low = 40.0; - temp_threshold_high = 70.0; - # start with turbo disabled on battery for power savings - initial_turbo_state = false; - }; - epp = "power"; - epb = "balance_power"; - platform_profile = "low-power"; - - # Global battery charging thresholds (applied to both profiles unless overridden) - # Start charging at 40%, stop at 80% - extends battery lifespan - # take precedence over this global setting - battery_charge_thresholds = [40 90]; - min_freq_mhz = 800; - max_freq_mhz = 2500; - }; - - daemon = { - # Base polling interval in seconds - poll_interval_sec = 5; - # Enable adaptive polling that changes with system state - adaptive_interval = true; - # Minimum polling interval for adaptive polling (seconds) - min_poll_interval_sec = 1; - # Maximum polling interval for adaptive polling (seconds) - max_poll_interval_sec = 30; - # Double the polling interval when on battery to save power - throttle_on_battery = true; - # Logging level: Error, Warning, Info, Debug - log_level = "Info"; - }; + rule = [ + { + cpu = { + energy-performance-preference = "power"; + frequency-mhz-maximum = 2000; + governor = "powersave"; + turbo = false; + }; + "if" = { + is-more-than = 85; + value = "$cpu-temperature"; + }; + priority = 100; + } + { + cpu = { + energy-performance-preference = "power"; + frequency-mhz-maximum = 800; + governor = "powersave"; + turbo = false; + }; + "if" = { + all = [ + "?discharging" + { + is-less-than = 0.3; + value = "%power-supply-charge"; + } + ]; + }; + power = {platform-profile = "low-power";}; + priority = 90; + } + { + cpu = { + energy-performance-preference = "performance"; + governor = "performance"; + turbo = true; + }; + "if" = { + all = [ + { + is-more-than = 0.8; + value = "%cpu-usage"; + } + { + is-less-than = 30; + value = "$cpu-idle-seconds"; + } + { + is-less-than = 75; + value = "$cpu-temperature"; + } + ]; + }; + priority = 80; + } + { + cpu = { + energy-performance-bias = "balance_performance"; + energy-performance-preference = "performance"; + governor = "performance"; + turbo = true; + }; + "if" = { + all = [ + {not = "?discharging";} + { + is-more-than = 0.1; + value = "%cpu-usage"; + } + { + is-less-than = 80; + value = "$cpu-temperature"; + } + ]; + }; + priority = 70; + } + { + cpu = { + energy-performance-preference = "balance_performance"; + governor = "schedutil"; + }; + "if" = { + all = [ + { + is-more-than = 0.4; + value = "%cpu-usage"; + } + { + is-less-than = 0.8; + value = "%cpu-usage"; + } + ]; + }; + priority = 60; + } + { + cpu = { + energy-performance-preference = "power"; + governor = "powersave"; + turbo = false; + }; + "if" = { + all = [ + { + is-less-than = 0.2; + value = "%cpu-usage"; + } + { + is-more-than = 60; + value = "$cpu-idle-seconds"; + } + ]; + }; + priority = 50; + } + { + cpu = { + energy-performance-preference = "power"; + frequency-mhz-maximum = 1600; + governor = "powersave"; + turbo = false; + }; + "if" = { + is-more-than = 300; + value = "$cpu-idle-seconds"; + }; + priority = 40; + } + { + cpu = { + energy-performance-preference = "power"; + frequency-mhz-maximum = 2000; + governor = "powersave"; + turbo = false; + }; + "if" = { + all = [ + "?discharging" + { + is-less-than = 0.5; + value = "%power-supply-charge"; + } + ]; + }; + power = {platform-profile = "low-power";}; + priority = 30; + } + { + cpu = { + energy-performance-bias = "balance_power"; + energy-performance-preference = "power"; + frequency-mhz-maximum = 1800; + frequency-mhz-minimum = 200; + governor = "powersave"; + turbo = false; + }; + "if" = "?discharging"; + priority = 20; + } + { + cpu = { + energy-performance-preference = "balance_performance"; + governor = "schedutil"; + }; + priority = 0; + } + ]; }; }; services = { diff --git a/modules/system/module.nix b/modules/system/module.nix index 8fc29c9..d2ba40a 100644 --- a/modules/system/module.nix +++ b/modules/system/module.nix @@ -3,11 +3,11 @@ in { system = { # faster rebuilds - switch = { - enable = false; - enableNg = true; - }; - # our state version + # switch = { + # enable = false; + # }; + + # My state version. stateVersion = "23.11"; }; environment.etc."machine-id".text = "${machine-id}\n"; diff --git a/modules/system/nix/module.nix b/modules/system/nix/module.nix index 80e2b4c..925d18a 100644 --- a/modules/system/nix/module.nix +++ b/modules/system/nix/module.nix @@ -148,10 +148,12 @@ in { # Substituters to pull from. substituters = [ "https://cache.nixos.org" + "https://cache.privatevoid.net" ]; trusted-public-keys = [ "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=" + "cache.privatevoid.net:SErQ8bvNWANeAvtsOESUwVYr2VJynfuc9JRwlzTTkVg=" ]; }; }; diff --git a/modules/wms/wayland/niri/module.nix b/modules/wms/wayland/niri/module.nix index ced7ddb..82612dd 100644 --- a/modules/wms/wayland/niri/module.nix +++ b/modules/wms/wayland/niri/module.nix @@ -1,5 +1,6 @@ { config, + inputs, lib, pkgs, ... @@ -9,6 +10,27 @@ inherit (config.modules.system) isGraphical; cfg = config.modules.desktops.niri; + + latestNightly = (inputs.rust-overlay.lib.mkRustBin {} pkgs).nightly.latest.default; + rustPlatform = pkgs.makeRustPlatform { + rustc = latestNightly; + cargo = latestNightly; + stdenv = pkgs.clangStdenv; + }; + + new-swww = (pkgs.swww.override {inherit rustPlatform;}).overrideAttrs (prev: { + src = pkgs.fetchFromGitHub { + inherit (prev.src) owner repo; + rev = "398a4048e389341dfd55285c53518a8ea3930ec4"; + hash = "sha256-ZAC5TbkshztW2IeDorhaxUmVCHf2tJCSGIGHSNl91Ns"; + }; + doInstallCheck = false; + + cargoDeps = pkgs.rustPlatform.fetchCargoVendor { + inherit (new-swww) src; + hash = "sha256-YH2gcy/8EtUmTHzwt38bBOFX3saN1wHIGQ5/eWqvSeM="; + }; + }); in { options.modules.desktops.niri.enable = mkEnableOption "Niri, a scolling tiling wayland compositor"; @@ -28,6 +50,7 @@ in { environment.systemPackages = builtins.attrValues { inherit (pkgs) xwayland-satellite avizo; + inherit new-swww; }; xdg.portal = { @@ -35,11 +58,15 @@ in { xdgOpenUsePortal = true; extraPortals = [ pkgs.xdg-desktop-portal-gtk + pkgs.xdg-desktop-portal-gnome ]; config = { common.default = ["*"]; hyprland.default = ["gtk"]; }; + configPackages = [ + pkgs.niri + ]; }; }; } diff --git a/packages/default.nix b/packages/default.nix index 6b50927..8fb4dfb 100644 --- a/packages/default.nix +++ b/packages/default.nix @@ -1,6 +1,17 @@ -{pkgs}: let +{ + inputs, + pkgs, +}: let inherit (pkgs) lib; - helix = pkgs.callPackage ./helix {}; + helix = let + latestNightly = (inputs.rust-overlay.lib.mkRustBin {} pkgs).nightly.latest.default; + rustPlatform = pkgs.makeRustPlatform { + rustc = latestNightly; + cargo = latestNightly; + stdenv = pkgs.clangStdenv; + }; + in + pkgs.callPackage ./helix {inherit rustPlatform;}; kakoune = pkgs.callPackage ./kakoune.nix {}; fish = pkgs.callPackage ./fish {inherit lib;}; in { diff --git a/packages/helix/default.nix b/packages/helix/default.nix index e93d18d..730cee9 100644 --- a/packages/helix/default.nix +++ b/packages/helix/default.nix @@ -41,20 +41,28 @@ }: let inherit (lib.meta) getExe; - custom-helix = helix.overrideAttrs (_: rec { - version = "25.06.1"; - src = fetchzip { - url = "https://github.com/bloxx12/helix/releases/download/${version}/helix-${version}-source.tar.xz"; - hash = "sha256-941moaBUF+aGsbFapK1cp5+NFdecSfRCTdnVUtkDQps="; - stripRoot = false; - }; + custom-helix = + (helix.override {inherit rustPlatform;}).overrideAttrs + (_: let + inherit (custom-helix) version; + in { + version = "25.06.1"; + src = fetchzip { + url = "https://github.com/bloxx12/helix/releases/download/${version}/helix-${version}-source.tar.xz"; + hash = "sha256-941moaBUF+aGsbFapK1cp5+NFdecSfRCTdnVUtkDQps="; + stripRoot = false; + }; - doInstallCheck = false; - cargoDeps = rustPlatform.fetchCargoVendor { - inherit (custom-helix) src; - hash = "sha256-w07ZV1tR3lzYz4N+hI9alvFp0AHCcsItPRhVt9Sluo8="; - }; - }); + cargoBuildFeatures = ["unicode-lines"]; + RUSTFLAGS = "-Ctarget-cpu=native -Clto=fat"; + + doInstallCheck = false; + + cargoDeps = rustPlatform.fetchCargoVendor { + inherit (custom-helix) src; + hash = "sha256-w07ZV1tR3lzYz4N+hI9alvFp0AHCcsItPRhVt9Sluo8="; + }; + }); toml = formats.toml {};