diff --git a/Commons/Settings.qml b/Commons/Settings.qml
index 1b6b33c..bc5d6c4 100644
--- a/Commons/Settings.qml
+++ b/Commons/Settings.qml
@@ -109,7 +109,7 @@ Singleton {
property JsonObject bar
bar: JsonObject {
- property string position: "top" // Possible values: "top", "bottom"
+ property string position: "top" // Possible values: "top", "bottom"
property bool showActiveWindow: true
property bool showSystemInfo: false
property bool showMedia: false
diff --git a/Helpers/sha256.js b/Helpers/sha256.js
new file mode 100644
index 0000000..39f255d
--- /dev/null
+++ b/Helpers/sha256.js
@@ -0,0 +1,192 @@
+function sha256(message) {
+ // SHA-256 constants
+ const K = [
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
+ ];
+
+ // Initial hash values
+ let h0 = 0x6a09e667;
+ let h1 = 0xbb67ae85;
+ let h2 = 0x3c6ef372;
+ let h3 = 0xa54ff53a;
+ let h4 = 0x510e527f;
+ let h5 = 0x9b05688c;
+ let h6 = 0x1f83d9ab;
+ let h7 = 0x5be0cd19;
+
+ // Convert string to UTF-8 bytes manually
+ const msgBytes = stringToUtf8Bytes(message);
+ const msgLength = msgBytes.length;
+ const bitLength = msgLength * 8;
+
+ // Calculate padding
+ // Message + 1 bit (0x80) + padding zeros + 8 bytes for length = multiple of 64 bytes
+ const totalBitsNeeded = bitLength + 1 + 64; // message bits + padding bit + 64-bit length
+ const totalBytesNeeded = Math.ceil(totalBitsNeeded / 8);
+ const paddedLength = Math.ceil(totalBytesNeeded / 64) * 64; // Round up to multiple of 64
+
+ const paddedMsg = new Array(paddedLength).fill(0);
+
+ // Copy original message
+ for (let i = 0; i < msgLength; i++) {
+ paddedMsg[i] = msgBytes[i];
+ }
+
+ // Add padding bit (0x80 = 10000000 in binary)
+ paddedMsg[msgLength] = 0x80;
+
+ // Add length as 64-bit big-endian integer at the end
+ // JavaScript numbers are not precise enough for 64-bit integers, so we handle high/low separately
+ const highBits = Math.floor(bitLength / 0x100000000);
+ const lowBits = bitLength % 0x100000000;
+
+ // Write 64-bit length in big-endian format
+ paddedMsg[paddedLength - 8] = (highBits >>> 24) & 0xFF;
+ paddedMsg[paddedLength - 7] = (highBits >>> 16) & 0xFF;
+ paddedMsg[paddedLength - 6] = (highBits >>> 8) & 0xFF;
+ paddedMsg[paddedLength - 5] = highBits & 0xFF;
+ paddedMsg[paddedLength - 4] = (lowBits >>> 24) & 0xFF;
+ paddedMsg[paddedLength - 3] = (lowBits >>> 16) & 0xFF;
+ paddedMsg[paddedLength - 2] = (lowBits >>> 8) & 0xFF;
+ paddedMsg[paddedLength - 1] = lowBits & 0xFF;
+
+ // Process message in 512-bit (64-byte) chunks
+ for (let chunk = 0; chunk < paddedLength; chunk += 64) {
+ const w = new Array(64);
+
+ // Break chunk into sixteen 32-bit big-endian words
+ for (let i = 0; i < 16; i++) {
+ const offset = chunk + i * 4;
+ w[i] = (paddedMsg[offset] << 24) |
+ (paddedMsg[offset + 1] << 16) |
+ (paddedMsg[offset + 2] << 8) |
+ paddedMsg[offset + 3];
+ // Ensure unsigned 32-bit
+ w[i] = w[i] >>> 0;
+ }
+
+ // Extend the sixteen 32-bit words into sixty-four 32-bit words
+ for (let i = 16; i < 64; i++) {
+ const s0 = rightRotate(w[i - 15], 7) ^ rightRotate(w[i - 15], 18) ^ (w[i - 15] >>> 3);
+ const s1 = rightRotate(w[i - 2], 17) ^ rightRotate(w[i - 2], 19) ^ (w[i - 2] >>> 10);
+ w[i] = (w[i - 16] + s0 + w[i - 7] + s1) >>> 0;
+ }
+
+ // Initialize working variables for this chunk
+ let a = h0, b = h1, c = h2, d = h3, e = h4, f = h5, g = h6, h = h7;
+
+ // Main loop
+ for (let i = 0; i < 64; i++) {
+ const S1 = rightRotate(e, 6) ^ rightRotate(e, 11) ^ rightRotate(e, 25);
+ const ch = (e & f) ^ (~e & g);
+ const temp1 = (h + S1 + ch + K[i] + w[i]) >>> 0;
+ const S0 = rightRotate(a, 2) ^ rightRotate(a, 13) ^ rightRotate(a, 22);
+ const maj = (a & b) ^ (a & c) ^ (b & c);
+ const temp2 = (S0 + maj) >>> 0;
+
+ h = g;
+ g = f;
+ f = e;
+ e = (d + temp1) >>> 0;
+ d = c;
+ c = b;
+ b = a;
+ a = (temp1 + temp2) >>> 0;
+ }
+
+ // Add this chunk's hash to result so far
+ h0 = (h0 + a) >>> 0;
+ h1 = (h1 + b) >>> 0;
+ h2 = (h2 + c) >>> 0;
+ h3 = (h3 + d) >>> 0;
+ h4 = (h4 + e) >>> 0;
+ h5 = (h5 + f) >>> 0;
+ h6 = (h6 + g) >>> 0;
+ h7 = (h7 + h) >>> 0;
+ }
+
+ // Produce the final hash value as a hex string
+ return [h0, h1, h2, h3, h4, h5, h6, h7]
+ .map(h => h.toString(16).padStart(8, '0'))
+ .join('');
+}
+
+function stringToUtf8Bytes(str) {
+ const bytes = [];
+ for (let i = 0; i < str.length; i++) {
+ let code = str.charCodeAt(i);
+
+ if (code < 0x80) {
+ // 1-byte character (ASCII)
+ bytes.push(code);
+ } else if (code < 0x800) {
+ // 2-byte character
+ bytes.push(0xC0 | (code >> 6));
+ bytes.push(0x80 | (code & 0x3F));
+ } else if (code < 0xD800 || code > 0xDFFF) {
+ // 3-byte character (not surrogate)
+ bytes.push(0xE0 | (code >> 12));
+ bytes.push(0x80 | ((code >> 6) & 0x3F));
+ bytes.push(0x80 | (code & 0x3F));
+ } else {
+ // 4-byte character (surrogate pair)
+ i++; // Move to next character
+ const code2 = str.charCodeAt(i);
+ const codePoint = 0x10000 + (((code & 0x3FF) << 10) | (code2 & 0x3FF));
+ bytes.push(0xF0 | (codePoint >> 18));
+ bytes.push(0x80 | ((codePoint >> 12) & 0x3F));
+ bytes.push(0x80 | ((codePoint >> 6) & 0x3F));
+ bytes.push(0x80 | (codePoint & 0x3F));
+ }
+ }
+ return bytes;
+}
+
+function rightRotate(value, amount) {
+ return ((value >>> amount) | (value << (32 - amount))) >>> 0;
+}
+
+// Test function to verify implementation
+// function testSHA256() {
+// const tests = [
+// {
+// input: "",
+// expected:
+// "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+// },
+// {
+// input: "Hello World",
+// expected:
+// "a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e",
+// },
+// {
+// input: "abc",
+// expected:
+// "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
+// },
+// {
+// input: "The quick brown fox jumps over the lazy dog",
+// expected:
+// "d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592",
+// },
+// ];
+
+// console.log("Running SHA-256 tests:");
+// tests.forEach((test, i) => {
+// const result = Crypto.sha256(test.input);
+// const passed = result === test.expected;
+// console.log(`Test ${i + 1}: ${passed ? "PASS" : "FAIL"}`);
+// if (!passed) {
+// console.log(` Input: "${test.input}"`);
+// console.log(` Expected: ${test.expected}`);
+// console.log(` Got: ${result}`);
+// }
+// });
+// }
diff --git a/Modules/Background/ScreenCorners.qml b/Modules/Background/ScreenCorners.qml
index ba04642..f8c612f 100644
--- a/Modules/Background/ScreenCorners.qml
+++ b/Modules/Background/ScreenCorners.qml
@@ -43,12 +43,10 @@ NLoader {
}
margins {
- top: (Settings.data.bar.monitors.includes(modelData.name)
- || (Settings.data.bar.monitors.length === 0)) && Settings.data.bar.position === "top"
- ? Math.floor(Style.barHeight * scaling) : 0
- bottom: (Settings.data.bar.monitors.includes(modelData.name)
- || (Settings.data.bar.monitors.length === 0)) && Settings.data.bar.position === "bottom"
- ? Math.floor(Style.barHeight * scaling) : 0
+ top: (Settings.data.bar.monitors.includes(modelData.name) || (Settings.data.bar.monitors.length === 0))
+ && Settings.data.bar.position === "top" ? Math.floor(Style.barHeight * scaling) : 0
+ bottom: (Settings.data.bar.monitors.includes(modelData.name) || (Settings.data.bar.monitors.length === 0))
+ && Settings.data.bar.position === "bottom" ? Math.floor(Style.barHeight * scaling) : 0
}
// Source we want to show only as a ring
diff --git a/Modules/Bar/BluetoothMenu.qml b/Modules/Bar/BluetoothMenu.qml
index 7198f40..e0eff50 100644
--- a/Modules/Bar/BluetoothMenu.qml
+++ b/Modules/Bar/BluetoothMenu.qml
@@ -76,10 +76,10 @@ NLoader {
anchors {
right: parent.right
rightMargin: Style.marginXS * scaling
- top: Settings.data.bar.position === "top" ? parent.top : undefined
- bottom: Settings.data.bar.position === "bottom" ? parent.bottom : undefined
- topMargin: Settings.data.bar.position === "top" ? Style.marginXS * scaling : undefined
- bottomMargin: Settings.data.bar.position === "bottom" ? Style.barHeight * scaling + Style.marginXS * scaling : undefined
+ top: Settings.data.bar.position === "top" ? parent.top : undefined
+ bottom: Settings.data.bar.position === "bottom" ? parent.bottom : undefined
+ topMargin: Settings.data.bar.position === "top" ? Style.marginXS * scaling : undefined
+ bottomMargin: Settings.data.bar.position === "bottom" ? Style.barHeight * scaling + Style.marginXS * scaling : undefined
}
// Animation properties
diff --git a/Modules/Bar/TrayMenu.qml b/Modules/Bar/TrayMenu.qml
index eb87abc..a3a24fd 100644
--- a/Modules/Bar/TrayMenu.qml
+++ b/Modules/Bar/TrayMenu.qml
@@ -254,4 +254,4 @@ PopupWindow {
}
}
}
-}
\ No newline at end of file
+}
diff --git a/Modules/Bar/WiFiMenu.qml b/Modules/Bar/WiFiMenu.qml
index 94c8998..f66f9cf 100644
--- a/Modules/Bar/WiFiMenu.qml
+++ b/Modules/Bar/WiFiMenu.qml
@@ -91,10 +91,10 @@ NLoader {
anchors {
right: parent.right
rightMargin: Style.marginXS * scaling
- top: Settings.data.bar.position === "top" ? parent.top : undefined
- bottom: Settings.data.bar.position === "bottom" ? parent.bottom : undefined
- topMargin: Settings.data.bar.position === "top" ? Style.marginXS * scaling : undefined
- bottomMargin: Settings.data.bar.position === "bottom" ? Style.barHeight * scaling + Style.marginXS * scaling : undefined
+ top: Settings.data.bar.position === "top" ? parent.top : undefined
+ bottom: Settings.data.bar.position === "bottom" ? parent.bottom : undefined
+ topMargin: Settings.data.bar.position === "top" ? Style.marginXS * scaling : undefined
+ bottomMargin: Settings.data.bar.position === "bottom" ? Style.barHeight * scaling + Style.marginXS * scaling : undefined
}
// Animation properties
diff --git a/Modules/SettingsPanel/Tabs/ColorSchemeTab.qml b/Modules/SettingsPanel/Tabs/ColorSchemeTab.qml
index 02a8306..a1d74a2 100644
--- a/Modules/SettingsPanel/Tabs/ColorSchemeTab.qml
+++ b/Modules/SettingsPanel/Tabs/ColorSchemeTab.qml
@@ -346,19 +346,19 @@ ColumnLayout {
id: matugenCheck
command: ["which", "matugen"]
running: false
-
- onExited: function(exitCode) {
+
+ onExited: function (exitCode) {
if (exitCode === 0) {
// Matugen exists, enable it
Settings.data.colorSchemes.useWallpaperColors = true
ColorSchemeService.changedWallpaper()
- ToastService.showNotice("Matugen", "Enabled!")
- } else {
- // Matugen not found
- ToastService.showWarning("Matugen", "Not installed!")
+ ToastService.showNotice("Matugen", "Enabled!")
+ } else {
+ // Matugen not found
+ ToastService.showWarning("Matugen", "Not installed!")
}
}
-
+
stdout: StdioCollector {}
stderr: StdioCollector {}
}
diff --git a/Modules/SettingsPanel/Tabs/NetworkTab.qml b/Modules/SettingsPanel/Tabs/NetworkTab.qml
index 031b33d..7029ecb 100644
--- a/Modules/SettingsPanel/Tabs/NetworkTab.qml
+++ b/Modules/SettingsPanel/Tabs/NetworkTab.qml
@@ -8,69 +8,69 @@ import qs.Services
import qs.Widgets
ColumnLayout {
- id: root
- spacing: 0
+ id: root
+ spacing: 0
- ScrollView {
- id: scrollView
+ ScrollView {
+ id: scrollView
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ padding: Style.marginM * scaling
+ clip: true
+
+ ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
+ ScrollBar.vertical.policy: ScrollBar.AsNeeded
+
+ ColumnLayout {
+ width: scrollView.availableWidth
+ spacing: 0
+
+ Item {
Layout.fillWidth: true
- Layout.fillHeight: true
- padding: Style.marginM * scaling
- clip: true
+ Layout.preferredHeight: 0
+ }
- ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
- ScrollBar.vertical.policy: ScrollBar.AsNeeded
+ ColumnLayout {
+ spacing: Style.marginL * scaling
+ Layout.fillWidth: true
- ColumnLayout {
- width: scrollView.availableWidth
- spacing: 0
-
- Item {
- Layout.fillWidth: true
- Layout.preferredHeight: 0
- }
-
- ColumnLayout {
- spacing: Style.marginL * scaling
- Layout.fillWidth: true
-
- NText {
- text: "Interfaces"
- font.pointSize: Style.fontSizeXXL * scaling
- font.weight: Style.fontWeightBold
- color: Color.mOnSurface
- }
-
- NToggle {
- label: "WiFi Enabled"
- description: "Enable WiFi connectivity."
- checked: Settings.data.network.wifiEnabled
- onToggled: checked => {
- Settings.data.network.wifiEnabled = checked
- NetworkService.setWifiEnabled(checked)
- if (checked) {
- ToastService.showNotice("WiFi", "Enabled")
- } else {
- ToastService.showNotice("WiFi", "Disabled")
- }
- }
- }
-
- NToggle {
- label: "Bluetooth Enabled"
- description: "Enable Bluetooth connectivity."
- checked: Settings.data.network.bluetoothEnabled
- onToggled: checked => {
- Settings.data.network.bluetoothEnabled = checked
- BluetoothService.setBluetoothEnabled(checked)
- if (checked) {
- ToastService.showNotice("Bluetooth", "Enabled")
- } else {
- ToastService.showNotice("Bluetooth", "Disabled")
- }
- }
- }
- }
+ NText {
+ text: "Interfaces"
+ font.pointSize: Style.fontSizeXXL * scaling
+ font.weight: Style.fontWeightBold
+ color: Color.mOnSurface
}
+
+ NToggle {
+ label: "WiFi Enabled"
+ description: "Enable WiFi connectivity."
+ checked: Settings.data.network.wifiEnabled
+ onToggled: checked => {
+ Settings.data.network.wifiEnabled = checked
+ NetworkService.setWifiEnabled(checked)
+ if (checked) {
+ ToastService.showNotice("WiFi", "Enabled")
+ } else {
+ ToastService.showNotice("WiFi", "Disabled")
+ }
+ }
+ }
+
+ NToggle {
+ label: "Bluetooth Enabled"
+ description: "Enable Bluetooth connectivity."
+ checked: Settings.data.network.bluetoothEnabled
+ onToggled: checked => {
+ Settings.data.network.bluetoothEnabled = checked
+ BluetoothService.setBluetoothEnabled(checked)
+ if (checked) {
+ ToastService.showNotice("Bluetooth", "Enabled")
+ } else {
+ ToastService.showNotice("Bluetooth", "Disabled")
+ }
+ }
+ }
+ }
}
+ }
}
diff --git a/Modules/SettingsPanel/Tabs/WallpaperTab.qml b/Modules/SettingsPanel/Tabs/WallpaperTab.qml
index a87b421..5353714 100644
--- a/Modules/SettingsPanel/Tabs/WallpaperTab.qml
+++ b/Modules/SettingsPanel/Tabs/WallpaperTab.qml
@@ -348,19 +348,19 @@ ColumnLayout {
id: swwwCheck
command: ["which", "swww"]
running: false
-
- onExited: function(exitCode) {
+
+ onExited: function (exitCode) {
if (exitCode === 0) {
// SWWW exists, enable it
Settings.data.wallpaper.swww.enabled = true
WallpaperService.startSWWWDaemon()
- ToastService.showNotice("SWWW", "Enabled!")
- } else {
- // SWWW not found
- ToastService.showWarning("SWWW", "Not installed!")
+ ToastService.showNotice("SWWW", "Enabled!")
+ } else {
+ // SWWW not found
+ ToastService.showWarning("SWWW", "Not installed!")
}
}
-
+
stdout: StdioCollector {}
stderr: StdioCollector {}
}
diff --git a/Modules/Toast/ToastManager.qml b/Modules/Toast/ToastManager.qml
index 1eb69ff..35fe01e 100644
--- a/Modules/Toast/ToastManager.qml
+++ b/Modules/Toast/ToastManager.qml
@@ -24,18 +24,18 @@ Variants {
left: true
right: true
}
-
+
// Set margins based on bar position
margins.top: Settings.data.bar.position === "top" ? (Style.barHeight + Style.marginS) * scaling : 0
margins.bottom: Settings.data.bar.position === "bottom" ? (Style.barHeight + Style.marginS) * scaling : 0
-
+
// Small height when hidden, appropriate height when visible
implicitHeight: toast.visible ? toast.height + Style.marginS * scaling : 1
// Transparent background
color: Color.transparent
-
- // High layer to appear above other panels
+
+ // High layer to appear above other panels
//WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
exclusionMode: PanelWindow.ExclusionMode.Ignore
@@ -43,22 +43,20 @@ Variants {
NToast {
id: toast
scaling: root.scaling
-
+
// Simple positioning - margins already account for bar
targetY: Style.marginS * scaling
-
+
// Hidden position based on bar location
- hiddenY: Settings.data.bar.position === "top"
- ? -toast.height - 20
- : toast.height + 20
-
+ hiddenY: Settings.data.bar.position === "top" ? -toast.height - 20 : toast.height + 20
+
Component.onCompleted: {
// Register this toast with the service
ToastService.currentToast = toast
-
+
// Connect dismissal signal
toast.dismissed.connect(ToastService.onToastDismissed)
}
}
}
-}
\ No newline at end of file
+}
diff --git a/README.md b/README.md
index ff07f1a..837d0fb 100644
--- a/README.md
+++ b/README.md
@@ -25,19 +25,12 @@ A sleek, minimal, and thoughtfully crafted desktop shell for Wayland using **Qui
## Preview
-
-Click to expand preview images
-
-
-
-
-
-
-

-
-
+
+
+Support for Dark or Light color schemes.
+
---
@@ -71,7 +64,6 @@ A sleek, minimal, and thoughtfully crafted desktop shell for Wayland using **Qui
- `quickshell-git` - Core shell framework
- `ttf-material-symbols-variable-git` - Icon font for UI elements
- `xdg-desktop-portal-gnome` - Desktop integration (or alternative portal)
-- `sha256sum` - To generate checksums for wallpaper caching
### Optional
@@ -94,7 +86,7 @@ A sleek, minimal, and thoughtfully crafted desktop shell for Wayland using **Qui
yay -S quickshell-git
# Download and install Noctalia
-mkdir -p ~/.config/quickshell && curl -sL https://github.com/noctalia-dev/noctalia-shell/releases/latest/download/noctalia-shell-latest.tar.gz | tar -xz --strip-components=1 -C ~/.config/quickshell/
+mkdir -p ~/.config/quickshell && curl -sL https://github.com/noctalia-dev/noctalia-shell/archive/refs/heads/main.tar.gz | tar -xz --strip-components=1 -C ~/.config/quickshell
```
### Usage
@@ -153,7 +145,6 @@ The launcher supports special commands for enhanced functionality:
| Error | `#e9899d` | Soft rose red |
| On Error | `#1e1418` | Dark text on error |
| Outline | `#4d445a` | Purple-tinted outline |
-| Outline Variant | `#342c42` | Variant outline color |
| Shadow | `#120f18` | Deep purple-tinted shadow |
@@ -234,15 +225,15 @@ Contributions are welcome! Don't worry about being perfect - every contribution
---
-## Acknowledgment
+## 💜 Credits
-Special thanks to the creators of [**Caelestia**](https://github.com/caelestia-dots/shell) and [**DankMaterialShell**](https://github.com/AvengeMedia/DankMaterialShell) for their inspirational designs and clever implementation techniques.
+A heartfelt thank you to our incredible community of [**contributors**](https://github.com/noctalia-dev/noctalia-shell/graphs/contributors). We are immensely grateful for your dedicated participation and the constructive feedback you've provided, which continue to shape and improve our project for everyone.
---
-## 💜 Credits
+## Acknowledgment
-Huge thanks to [**@ferrreo**](https://github.com/ferrreo) and [**@quadbyte**](https://github.com/quadbyte) for their contributions and the cool features they added!
+Special thanks to the creators of [**Caelestia**](https://github.com/caelestia-dots/shell) and [**DankMaterialShell**](https://github.com/AvengeMedia/DankMaterialShell) for their inspirational designs and clever implementation techniques.
---
@@ -252,10 +243,6 @@ While I actually didn't want to accept donations, more and more people are askin
[](https://ko-fi.com/R6R01IX85B)
----
-
-#### Special Thanks
-
Thank you to everyone who supports me and this project 💜!
* Gohma
diff --git a/Services/BluetoothService.qml b/Services/BluetoothService.qml
index b81d6d2..ba354fb 100644
--- a/Services/BluetoothService.qml
+++ b/Services/BluetoothService.qml
@@ -147,7 +147,7 @@ Singleton {
console.warn("BluetoothService: No adapter available")
return
}
-
+
adapter.enabled = enabled
}
}
diff --git a/Services/CompositorService.qml b/Services/CompositorService.qml
index 8cf2bba..a84d831 100644
--- a/Services/CompositorService.qml
+++ b/Services/CompositorService.qml
@@ -145,7 +145,7 @@ Singleton {
try {
const hlToplevels = Hyprland.toplevels.values
const windowsList = []
-
+
for (var i = 0; i < hlToplevels.length; i++) {
const toplevel = hlToplevels[i]
windowsList.push({
@@ -158,7 +158,7 @@ Singleton {
}
windows = windowsList
-
+
// Update focused window index
focusedWindowIndex = -1
for (var j = 0; j < windowsList.length; j++) {
@@ -167,7 +167,7 @@ Singleton {
break
}
}
-
+
updateFocusedWindowTitle()
activeWindowChanged()
} catch (e) {
diff --git a/Services/ScalingService.qml b/Services/ScalingService.qml
index 812daf0..9679bc5 100644
--- a/Services/ScalingService.qml
+++ b/Services/ScalingService.qml
@@ -20,6 +20,7 @@ Singleton {
}
}
} catch (e) {
+
//Logger.warn(e)
}
diff --git a/Services/ToastService.qml b/Services/ToastService.qml
index 5553986..b08a366 100644
--- a/Services/ToastService.qml
+++ b/Services/ToastService.qml
@@ -6,177 +6,184 @@ import qs.Commons
QtObject {
id: root
-
+
// Queue of pending toast messages
property var messageQueue: []
property bool isShowingToast: false
-
+
// Reference to the current toast instance (set by ToastManager)
property var currentToast: null
-
+
// Methods to show different types of messages
function showNotice(label, description = "", persistent = false, duration = 3000) {
showToast(label, description, "notice", persistent, duration)
}
-
+
function showWarning(label, description = "", persistent = false, duration = 4000) {
showToast(label, description, "warning", persistent, duration)
}
-
+
// Utility function to check if a command exists and show appropriate toast
function checkCommandAndToast(command, successMessage, failMessage, onSuccess = null) {
var checkProcess = Qt.createQmlObject(`
- import QtQuick
- import Quickshell.Io
- Process {
- id: checkProc
- command: ["which", "${command}"]
- running: true
-
- property var onSuccessCallback: null
- property bool hasFinished: false
-
- onExited: {
- if (!hasFinished) {
- hasFinished = true
- if (exitCode === 0) {
- ToastService.showNotice("${successMessage}")
- if (onSuccessCallback) onSuccessCallback()
- } else {
- ToastService.showWarning("${failMessage}")
- }
- checkProc.destroy()
- }
- }
-
- // Fallback collectors to prevent issues
- stdout: StdioCollector {}
- stderr: StdioCollector {}
- }
- `, root)
-
+ import QtQuick
+ import Quickshell.Io
+ Process {
+ id: checkProc
+ command: ["which", "${command}"]
+ running: true
+
+ property var onSuccessCallback: null
+ property bool hasFinished: false
+
+ onExited: {
+ if (!hasFinished) {
+ hasFinished = true
+ if (exitCode === 0) {
+ ToastService.showNotice("${successMessage}")
+ if (onSuccessCallback) onSuccessCallback()
+ } else {
+ ToastService.showWarning("${failMessage}")
+ }
+ checkProc.destroy()
+ }
+ }
+
+ // Fallback collectors to prevent issues
+ stdout: StdioCollector {}
+ stderr: StdioCollector {}
+ }
+ `, root)
+
checkProcess.onSuccessCallback = onSuccess
}
-
+
// Simple function to show a random toast (useful for testing or fun messages)
function showRandomToast() {
- var messages = [
- { type: "notice", text: "Everything is working smoothly!" },
- { type: "notice", text: "Noctalia is looking great today!" },
- { type: "notice", text: "Your desktop setup is amazing!" },
- { type: "warning", text: "Don't forget to take a break!" },
- { type: "notice", text: "Configuration saved successfully!" },
- { type: "warning", text: "Remember to backup your settings!" }
- ]
-
+ var messages = [{
+ "type": "notice",
+ "text": "Everything is working smoothly!"
+ }, {
+ "type": "notice",
+ "text": "Noctalia is looking great today!"
+ }, {
+ "type": "notice",
+ "text": "Your desktop setup is amazing!"
+ }, {
+ "type": "warning",
+ "text": "Don't forget to take a break!"
+ }, {
+ "type": "notice",
+ "text": "Configuration saved successfully!"
+ }, {
+ "type": "warning",
+ "text": "Remember to backup your settings!"
+ }]
+
var randomMessage = messages[Math.floor(Math.random() * messages.length)]
showToast(randomMessage.text, randomMessage.type)
}
-
+
// Convenience function for quick notifications
function quickNotice(message) {
showNotice(message, false, 2000) // Short duration
}
-
+
function quickWarning(message) {
showWarning(message, false, 3000) // Medium duration
}
-
+
// Generic command runner with toast feedback
function runCommandWithToast(command, args, successMessage, failMessage, onSuccess = null) {
var fullCommand = [command].concat(args || [])
var runProcess = Qt.createQmlObject(`
- import QtQuick
- import Quickshell.Io
- Process {
- id: runProc
- command: ${JSON.stringify(fullCommand)}
- running: true
-
- property var onSuccessCallback: null
- property bool hasFinished: false
-
- onExited: {
- if (!hasFinished) {
- hasFinished = true
- if (exitCode === 0) {
- ToastService.showNotice("${successMessage}")
- if (onSuccessCallback) onSuccessCallback()
- } else {
- ToastService.showWarning("${failMessage}")
- }
- runProc.destroy()
- }
- }
-
- stdout: StdioCollector {}
- stderr: StdioCollector {}
- }
- `, root)
-
+ import QtQuick
+ import Quickshell.Io
+ Process {
+ id: runProc
+ command: ${JSON.stringify(fullCommand)}
+ running: true
+
+ property var onSuccessCallback: null
+ property bool hasFinished: false
+
+ onExited: {
+ if (!hasFinished) {
+ hasFinished = true
+ if (exitCode === 0) {
+ ToastService.showNotice("${successMessage}")
+ if (onSuccessCallback) onSuccessCallback()
+ } else {
+ ToastService.showWarning("${failMessage}")
+ }
+ runProc.destroy()
+ }
+ }
+
+ stdout: StdioCollector {}
+ stderr: StdioCollector {}
+ }
+ `, root)
+
runProcess.onSuccessCallback = onSuccess
}
-
+
// Check if a file/directory exists
function checkPathAndToast(path, successMessage, failMessage, onSuccess = null) {
runCommandWithToast("test", ["-e", path], successMessage, failMessage, onSuccess)
}
-
+
// Show toast after a delay (useful for delayed feedback)
function delayedToast(message, type = "notice", delayMs = 1000) {
var timer = Qt.createQmlObject(`
- import QtQuick
- Timer {
- interval: ${delayMs}
- repeat: false
- running: true
- onTriggered: {
- ToastService.showToast("${message}", "${type}")
- destroy()
- }
- }
- `, root)
+ import QtQuick
+ Timer {
+ interval: ${delayMs}
+ repeat: false
+ running: true
+ onTriggered: {
+ ToastService.showToast("${message}", "${type}")
+ destroy()
+ }
+ }
+ `, root)
}
-
+
// Generic method to show a toast
function showToast(label, description = "", type = "notice", persistent = false, duration = 3000) {
var toastData = {
- label: label,
- description: description,
- type: type,
- persistent: persistent,
- duration: duration,
- timestamp: Date.now()
+ "label": label,
+ "description": description,
+ "type": type,
+ "persistent": persistent,
+ "duration": duration,
+ "timestamp": Date.now()
}
-
-
// Add to queue
messageQueue.push(toastData)
-
+
// Process queue if not currently showing a toast
if (!isShowingToast) {
processQueue()
}
}
-
+
// Process the message queue
function processQueue() {
if (messageQueue.length === 0 || !currentToast) {
isShowingToast = false
return
}
-
+
if (isShowingToast) {
// Wait for current toast to finish
return
}
-
+
var toastData = messageQueue.shift()
isShowingToast = true
-
-
// Configure and show toast
currentToast.label = toastData.label
currentToast.description = toastData.description
@@ -185,32 +192,32 @@ QtObject {
currentToast.duration = toastData.duration
currentToast.show()
}
-
+
// Called when a toast is dismissed
function onToastDismissed() {
isShowingToast = false
-
+
// Small delay before showing next toast
- Qt.callLater(function() {
+ Qt.callLater(function () {
processQueue()
})
}
-
+
// Clear all pending messages
function clearQueue() {
messageQueue = []
}
-
+
// Hide current toast
function hideCurrentToast() {
if (currentToast && isShowingToast) {
currentToast.hide()
}
}
-
+
Component.onCompleted: {
}
-}
\ No newline at end of file
+}
diff --git a/Widgets/NImageCached.qml b/Widgets/NImageCached.qml
index 3980370..c53646a 100644
--- a/Widgets/NImageCached.qml
+++ b/Widgets/NImageCached.qml
@@ -4,6 +4,7 @@ import QtQuick
import Quickshell
import Quickshell.Io
import qs.Commons
+import "../Helpers/sha256.js" as Crypto
Image {
id: root
@@ -20,8 +21,8 @@ Image {
smooth: true
onImagePathChanged: {
if (imagePath) {
- hashProcess.command = ["sha256sum", imagePath]
- hashProcess.running = true
+ imageHash = Crypto.sha256(imagePath)
+ Logger.log("NImageCached", imagePath, imageHash)
} else {
source = ""
imageHash = ""
@@ -47,13 +48,4 @@ Image {
})
}
}
-
- Process {
- id: hashProcess
- stdout: StdioCollector {
- onStreamFinished: {
- root.imageHash = text.split(" ")[0]
- }
- }
- }
}
diff --git a/Widgets/NToast.qml b/Widgets/NToast.qml
index 227966b..2483c33 100644
--- a/Widgets/NToast.qml
+++ b/Widgets/NToast.qml
@@ -7,29 +7,29 @@ import qs.Widgets
Item {
id: root
-
+
property string label: ""
property string description: ""
property string type: "notice" // "notice", "warning"
property int duration: 5000 // Auto-hide after 5 seconds, 0 = no auto-hide
property bool persistent: false // If true, requires manual dismiss
-
+
property real scaling: 1.0 // Will be set by parent
-
+
// Animation properties
property real targetY: 0
property real hiddenY: -height - 20
-
- signal dismissed()
-
+
+ signal dismissed
+
width: Math.min(500 * scaling, parent.width * 0.8)
height: Math.max(60 * scaling, contentLayout.implicitHeight + Style.marginL * 2 * scaling)
-
+
// Position at top center of parent
anchors.horizontalCenter: parent.horizontalCenter
y: hiddenY
z: 1000 // High z-index to appear above everything
-
+
function show() {
visible = true
showAnimation.start()
@@ -37,18 +37,18 @@ Item {
autoHideTimer.start()
}
}
-
+
function hide() {
hideAnimation.start()
}
-
+
// Auto-hide timer
Timer {
id: autoHideTimer
interval: root.duration
onTriggered: hide()
}
-
+
// Show animation
PropertyAnimation {
id: showAnimation
@@ -58,7 +58,7 @@ Item {
duration: Style.animationNormal
easing.type: Easing.OutCubic
}
-
+
// Hide animation
PropertyAnimation {
id: hideAnimation
@@ -72,26 +72,29 @@ Item {
root.dismissed()
}
}
-
+
// Main toast container
Rectangle {
id: container
anchors.fill: parent
radius: Style.radiusL * scaling
-
+
// Clean surface background
color: Color.mSurface
-
+
// Simple colored border all around
border.color: {
switch (root.type) {
- case "warning": return Color.mError
- case "notice": return Color.mPrimary
- default: return Color.mOutline
+ case "warning":
+ return Color.mError
+ case "notice":
+ return Color.mPrimary
+ default:
+ return Color.mOutline
}
}
border.width: Math.max(2, Style.borderM * scaling)
-
+
// Drop shadow effect
layer.enabled: true
layer.effect: MultiEffect {
@@ -100,43 +103,49 @@ Item {
shadowBlur: 20 * scaling
shadowVerticalOffset: 4 * scaling
}
-
+
RowLayout {
id: contentLayout
anchors.fill: parent
anchors.margins: Style.marginM * scaling
spacing: Style.marginS * scaling
-
+
// Icon
NIcon {
id: icon
text: {
switch (root.type) {
- case "warning": return "warning"
- case "notice": return "info"
- default: return "info"
+ case "warning":
+ return "warning"
+ case "notice":
+ return "info"
+ default:
+ return "info"
}
}
-
+
color: {
switch (root.type) {
- case "warning": return Color.mError
- case "notice": return Color.mPrimary
- default: return Color.mPrimary
+ case "warning":
+ return Color.mError
+ case "notice":
+ return Color.mPrimary
+ default:
+ return Color.mPrimary
}
}
-
+
font.pointSize: Style.fontSizeXXL * 1.5 * scaling // 150% size to cover two lines
Layout.alignment: Qt.AlignVCenter
}
-
+
// Label and description
Column {
id: textColumn
spacing: Style.marginXXS * scaling
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
-
+
NText {
id: labelText
text: root.label
@@ -147,7 +156,7 @@ Item {
width: parent.width
visible: text.length > 0
}
-
+
NText {
id: descriptionText
text: root.description
@@ -158,23 +167,23 @@ Item {
visible: text.length > 0
}
}
-
+
// Close button (only if persistent or manual dismiss needed)
NIconButton {
id: closeButton
icon: "close"
visible: root.persistent || root.duration === 0
-
+
color: Color.mOnSurface
-
+
fontPointSize: Style.fontSize * scaling
sizeMultiplier: 0.8
Layout.alignment: Qt.AlignTop
-
+
onClicked: hide()
}
}
-
+
// Click to dismiss (if not persistent)
MouseArea {
anchors.fill: parent
@@ -183,9 +192,9 @@ Item {
cursorShape: Qt.PointingHandCursor
}
}
-
+
// Initial state
Component.onCompleted: {
visible = false
}
-}
\ No newline at end of file
+}