add quickshell
This commit is contained in:
parent
3d48e35de5
commit
10960f620d
32 changed files with 1415 additions and 11 deletions
113
flake.lock
generated
113
flake.lock
generated
|
|
@ -49,6 +49,24 @@
|
|||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-parts": {
|
||||
"inputs": {
|
||||
"nixpkgs-lib": "nixpkgs-lib"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1762040540,
|
||||
"narHash": "sha256-z5PlZ47j50VNF3R+IMS9LmzI5fYRGY/Z5O5tol1c9I4=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "0010412d62a25d959151790968765a70c436598b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
|
|
@ -175,6 +193,21 @@
|
|||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-lib": {
|
||||
"locked": {
|
||||
"lastModified": 1761765539,
|
||||
"narHash": "sha256-b0yj6kfvO8ApcSE+QmA6mUfu8IYG6/uU28OFn4PaC8M=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"rev": "719359f4562934ae99f5443f20aa06c2ffff91fc",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-stable": {
|
||||
"locked": {
|
||||
"lastModified": 1775811116,
|
||||
|
|
@ -239,6 +272,67 @@
|
|||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_4": {
|
||||
"locked": {
|
||||
"lastModified": 1761236834,
|
||||
"narHash": "sha256-+pthv6hrL5VLW2UqPdISGuLiUZ6SnAXdd2DdUE+fV2Q=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "d5faa84122bc0a1fd5d378492efce4e289f8eac1",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"qml-niri": {
|
||||
"inputs": {
|
||||
"flake-parts": "flake-parts",
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
],
|
||||
"quickshell": "quickshell",
|
||||
"treefmt-nix": "treefmt-nix"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1775384967,
|
||||
"narHash": "sha256-5JuFsu0tPEZrHhj/d5C7eP0+FSLNi9hTW5h/17N3Mbk=",
|
||||
"owner": "imiric",
|
||||
"repo": "qml-niri",
|
||||
"rev": "2d60eb204aa7a9704b7f5cf228b85fb19acbbcee",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "imiric",
|
||||
"ref": "main",
|
||||
"repo": "qml-niri",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"quickshell": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"qml-niri",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1761897390,
|
||||
"narHash": "sha256-er4gYrIoThYLjlsOMTysoRfn67d1Gci+ZpqDrtQxrA0=",
|
||||
"ref": "refs/heads/master",
|
||||
"rev": "fc704e6b5d445899a1565955268c91942a4f263f",
|
||||
"revCount": 700,
|
||||
"type": "git",
|
||||
"url": "https://git.outfoxxed.me/outfoxxed/quickshell"
|
||||
},
|
||||
"original": {
|
||||
"type": "git",
|
||||
"url": "https://git.outfoxxed.me/outfoxxed/quickshell"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"claude-code": "claude-code",
|
||||
|
|
@ -249,6 +343,7 @@
|
|||
"niri-flake": "niri-flake",
|
||||
"nixpkgs": "nixpkgs_3",
|
||||
"nixpkgs-unstable": "nixpkgs-unstable",
|
||||
"qml-niri": "qml-niri",
|
||||
"sops-nix": "sops-nix"
|
||||
}
|
||||
},
|
||||
|
|
@ -287,6 +382,24 @@
|
|||
"type": "github"
|
||||
}
|
||||
},
|
||||
"treefmt-nix": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs_4"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1762410071,
|
||||
"narHash": "sha256-aF5fvoZeoXNPxT0bejFUBXeUjXfHLSL7g+mjR/p5TEg=",
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"rev": "97a30861b13c3731a84e09405414398fbf3e109f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"xwayland-satellite-stable": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,10 @@
|
|||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
claude-code.url = "github:sadjow/claude-code-nix";
|
||||
qml-niri = {
|
||||
url = "github:imiric/qml-niri/main";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
};
|
||||
|
||||
outputs = inputs:
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ in {
|
|||
];
|
||||
initContent = ''
|
||||
source ${pkgs.zsh-powerlevel10k}/share/zsh-powerlevel10k/powerlevel10k.zsh-theme
|
||||
[[ ! -f ~/.p10k.zsh ]] || source ~/.p10k.zsh
|
||||
'';
|
||||
oh-my-zsh = {
|
||||
enable = true;
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
{ ... }: {
|
||||
services.mako = {
|
||||
enable = true;
|
||||
};
|
||||
}
|
||||
|
|
@ -1,4 +1,7 @@
|
|||
{ pkgs, ... }: {
|
||||
{ config, pkgs, ... }:
|
||||
let
|
||||
wallpaper = ../../wallpapers/miku.png;
|
||||
in {
|
||||
home.sessionVariables = {
|
||||
XDG_SESSION_TYPE = "wayland";
|
||||
XDG_CURRENT_DESKTOP = "niri";
|
||||
|
|
@ -42,12 +45,16 @@
|
|||
'';
|
||||
};
|
||||
|
||||
xdg.configFile."quickshell".source =
|
||||
config.lib.file.mkOutOfStoreSymlink "${config.home.homeDirectory}/repos/nixos/quickshell";
|
||||
|
||||
xdg.configFile."niri/config.kdl".text = ''
|
||||
// Modificadores: Mod = Super (tecla Windows)
|
||||
input {
|
||||
keyboard {
|
||||
xkb {
|
||||
layout "en"
|
||||
layout "us,pt"
|
||||
variant ",br"
|
||||
}
|
||||
}
|
||||
touchpad {
|
||||
|
|
@ -67,6 +74,10 @@
|
|||
|
||||
prefer-no-csd
|
||||
|
||||
spawn-at-startup "swww-daemon"
|
||||
spawn-at-startup "swww" "img" "${wallpaper}" "--transition-type" "none"
|
||||
spawn-at-startup "quickshell"
|
||||
|
||||
screenshot-path "~/Imagens/screenshots/%Y-%m-%d %H-%M-%S.png"
|
||||
|
||||
binds {
|
||||
|
|
@ -101,13 +112,19 @@
|
|||
|
||||
// Sistema
|
||||
Mod+Shift+E { quit; }
|
||||
Mod+L { spawn "swaylock"; }
|
||||
Mod+L { spawn "swaylock" "-i" "${wallpaper}" "--scaling" "fill"; }
|
||||
|
||||
// Volume
|
||||
XF86AudioRaiseVolume { spawn "wpctl" "set-volume" "@DEFAULT_AUDIO_SINK@" "5%+"; }
|
||||
XF86AudioLowerVolume { spawn "wpctl" "set-volume" "@DEFAULT_AUDIO_SINK@" "5%-"; }
|
||||
XF86AudioMute { spawn "wpctl" "set-mute" "@DEFAULT_AUDIO_SINK@" "toggle"; }
|
||||
|
||||
// Zen mode (toggle quickshell bar)
|
||||
Mod+Z { spawn "quickshell" "ipc" "call" "zen" "toggle"; }
|
||||
|
||||
// Input layout
|
||||
Mod+Space { switch-layout "next"; }
|
||||
|
||||
// Screenshot
|
||||
Mod+Shift+S { spawn "bash" "-c" "$HOME/.local/bin/screenshot region"; }
|
||||
Mod+Shift+Alt+S { spawn "bash" "-c" "$HOME/.local/bin/screenshot screen"; }
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@
|
|||
wget
|
||||
pkgs-unstable.neovim
|
||||
btop
|
||||
nixd
|
||||
ncdu
|
||||
unrar
|
||||
p7zip
|
||||
];
|
||||
|
|
|
|||
|
|
@ -71,7 +71,6 @@
|
|||
./_home/niri.nix
|
||||
./_home/cursor.nix
|
||||
./_home/git.nix
|
||||
./_home/mako.nix
|
||||
./_home/dolphin.nix
|
||||
./_home/theme.nix
|
||||
];
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@
|
|||
angular-language-server # Angular
|
||||
vue-language-server # Vue
|
||||
tailwindcss-language-server # Tailwind
|
||||
qt6Packages.qtdeclarative # qmlls (QML/Quickshell LSP)
|
||||
# Extra
|
||||
chafa
|
||||
ripgrep
|
||||
|
|
|
|||
|
|
@ -1,7 +1,12 @@
|
|||
{ inputs, den, pkgs, ... }: {
|
||||
den.aspects.niri.nixos = { pkgs, ... }: {
|
||||
den.aspects.niri.nixos = { pkgs, ... }:
|
||||
let
|
||||
quickshell-niri = inputs.qml-niri.packages.${pkgs.system}.quickshell;
|
||||
in {
|
||||
imports = [ inputs.niri-flake.nixosModules.niri ];
|
||||
|
||||
fonts.packages = [ pkgs.nerd-fonts.jetbrains-mono ];
|
||||
|
||||
programs.niri = {
|
||||
enable = true;
|
||||
package = pkgs.niri;
|
||||
|
|
@ -10,10 +15,12 @@
|
|||
programs.gpu-screen-recorder.enable = true;
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
quickshell-niri
|
||||
waybar
|
||||
fuzzel
|
||||
swaylock
|
||||
swayidle
|
||||
swww
|
||||
grim
|
||||
slurp
|
||||
wl-clipboard
|
||||
|
|
@ -21,6 +28,7 @@
|
|||
kdePackages.xdg-desktop-portal-kde
|
||||
xwayland-satellite
|
||||
gpu-screen-recorder-gtk
|
||||
libnotify
|
||||
];
|
||||
|
||||
xdg.portal = {
|
||||
|
|
|
|||
18
quickshell/ActiveWindow.qml
Normal file
18
quickshell/ActiveWindow.qml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import QtQuick
|
||||
|
||||
Text {
|
||||
readonly property string title: niri.focusedWindow?.title ?? ""
|
||||
|
||||
text: title
|
||||
color: Theme.subtle
|
||||
font.pixelSize: Theme.fontSize
|
||||
font.family: Theme.font
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
opacity: title.length > 0 ? 1.0 : 0.0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation { duration: 120 }
|
||||
}
|
||||
}
|
||||
57
quickshell/Bar.qml
Normal file
57
quickshell/Bar.qml
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
PanelWindow {
|
||||
anchors { top: true; left: true; right: true }
|
||||
|
||||
visible: !root.zenMode
|
||||
height: Theme.barHeight
|
||||
color: "transparent"
|
||||
exclusiveZone: height
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Theme.bg
|
||||
|
||||
Rectangle {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: 1
|
||||
color: Theme.bg2
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.padding
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacing
|
||||
|
||||
Workspaces {}
|
||||
}
|
||||
|
||||
ActiveWindow {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 480
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.padding
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacing
|
||||
|
||||
NetWidget {}
|
||||
CpuWidget {}
|
||||
RamWidget {}
|
||||
GpuWidget {}
|
||||
DateWidget {}
|
||||
Clock {}
|
||||
KeyboardLayout {}
|
||||
NotificationWidget {}
|
||||
}
|
||||
}
|
||||
}
|
||||
20
quickshell/BarPill.qml
Normal file
20
quickshell/BarPill.qml
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
Rectangle {
|
||||
default property alias content: inner.data
|
||||
|
||||
property int minWidth: 0
|
||||
|
||||
color: Theme.bg1
|
||||
radius: Theme.radius
|
||||
height: Theme.barHeight - 8
|
||||
width: Math.max(inner.implicitWidth + Theme.spacing * 2, minWidth)
|
||||
clip: true
|
||||
|
||||
RowLayout {
|
||||
id: inner
|
||||
anchors.centerIn: parent
|
||||
spacing: 5
|
||||
}
|
||||
}
|
||||
19
quickshell/Clock.qml
Normal file
19
quickshell/Clock.qml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import Quickshell
|
||||
import QtQuick
|
||||
|
||||
BarPill {
|
||||
SystemClock { id: clock; precision: SystemClock.Seconds }
|
||||
|
||||
Text {
|
||||
text: ""
|
||||
color: Theme.yellow
|
||||
font.pixelSize: Theme.fontSize
|
||||
font.family: Theme.font
|
||||
}
|
||||
Text {
|
||||
text: Qt.formatDateTime(clock.date, "hh:mm")
|
||||
color: Theme.text
|
||||
font.pixelSize: Theme.fontSize
|
||||
font.family: Theme.font
|
||||
}
|
||||
}
|
||||
57
quickshell/CpuMonitor.qml
Normal file
57
quickshell/CpuMonitor.qml
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
pragma Singleton
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import QtQuick
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property int usage: 0
|
||||
property int temp: 0
|
||||
|
||||
property var _prevTotal: null
|
||||
property var _prevIdle: null
|
||||
|
||||
Timer {
|
||||
interval: 2000
|
||||
running: true
|
||||
repeat: true
|
||||
triggeredOnStart: true
|
||||
onTriggered: {
|
||||
_statProc.running = false
|
||||
_statProc.running = true
|
||||
_tempProc.running = false
|
||||
_tempProc.running = true
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: _statProc
|
||||
command: ["bash", "-c", "grep '^cpu ' /proc/stat"]
|
||||
stdout: SplitParser {
|
||||
onRead: line => {
|
||||
const p = line.trim().split(/\s+/)
|
||||
const idle = parseInt(p[4]) + parseInt(p[5])
|
||||
const total = p.slice(1, 8).reduce((a, v) => a + parseInt(v), 0)
|
||||
if (root._prevTotal !== null) {
|
||||
const dt = total - root._prevTotal
|
||||
const di = idle - root._prevIdle
|
||||
root.usage = dt > 0 ? Math.round((1 - di / dt) * 100) : 0
|
||||
}
|
||||
root._prevTotal = total
|
||||
root._prevIdle = idle
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: _tempProc
|
||||
command: ["bash", "-c", "for d in /sys/class/hwmon/hwmon*/; do [ \"$(cat ${d}name 2>/dev/null)\" = 'coretemp' ] && awk '{print int($1/1000)}' \"${d}temp1_input\" 2>/dev/null && break; done"]
|
||||
stdout: SplitParser {
|
||||
onRead: line => {
|
||||
const v = parseInt(line.trim())
|
||||
if (!isNaN(v) && v > 0) root.temp = v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
10
quickshell/CpuWidget.qml
Normal file
10
quickshell/CpuWidget.qml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import QtQuick
|
||||
|
||||
BarPill {
|
||||
minWidth: 115
|
||||
Text { text: ""; color: Theme.green; font.pixelSize: Theme.fontSize; font.family: Theme.font }
|
||||
Text { text: CpuMonitor.usage + "%"; color: Theme.text; font.pixelSize: Theme.fontSize; font.family: Theme.font }
|
||||
Text { text: "|"; color: Theme.muted; font.pixelSize: Theme.fontSize; font.family: Theme.font }
|
||||
Text { text: CpuMonitor.temp + "°"; color: Theme.text; font.pixelSize: Theme.fontSize; font.family: Theme.font }
|
||||
Text { text: ""; color: Theme.orange; font.pixelSize: Theme.fontSize; font.family: Theme.font }
|
||||
}
|
||||
19
quickshell/DateWidget.qml
Normal file
19
quickshell/DateWidget.qml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import Quickshell
|
||||
import QtQuick
|
||||
|
||||
BarPill {
|
||||
SystemClock { id: clock; precision: SystemClock.Minutes }
|
||||
|
||||
Text {
|
||||
text: ""
|
||||
color: Theme.aqua
|
||||
font.pixelSize: Theme.fontSize
|
||||
font.family: Theme.font
|
||||
}
|
||||
Text {
|
||||
text: Qt.formatDateTime(clock.date, "ddd dd/MM")
|
||||
color: Theme.text
|
||||
font.pixelSize: Theme.fontSize
|
||||
font.family: Theme.font
|
||||
}
|
||||
}
|
||||
37
quickshell/GpuMonitor.qml
Normal file
37
quickshell/GpuMonitor.qml
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
pragma Singleton
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import QtQuick
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property int usage: 0
|
||||
property int temp: 0
|
||||
|
||||
Timer {
|
||||
interval: 2000
|
||||
running: true
|
||||
repeat: true
|
||||
triggeredOnStart: true
|
||||
onTriggered: {
|
||||
_proc.running = false
|
||||
_proc.running = true
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: _proc
|
||||
command: ["nvidia-smi", "--query-gpu=utilization.gpu,temperature.gpu", "--format=csv,noheader,nounits"]
|
||||
stdout: SplitParser {
|
||||
onRead: line => {
|
||||
const parts = line.trim().split(/\s*,\s*/)
|
||||
if (parts.length < 2) return
|
||||
const u = parseInt(parts[0])
|
||||
const t = parseInt(parts[1])
|
||||
if (!isNaN(u)) root.usage = u
|
||||
if (!isNaN(t)) root.temp = t
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
10
quickshell/GpuWidget.qml
Normal file
10
quickshell/GpuWidget.qml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import QtQuick
|
||||
|
||||
BarPill {
|
||||
minWidth: 115
|
||||
Text { text: ""; color: Theme.blue; font.pixelSize: Theme.fontSize; font.family: Theme.font }
|
||||
Text { text: GpuMonitor.usage + "%"; color: Theme.text; font.pixelSize: Theme.fontSize; font.family: Theme.font }
|
||||
Text { text: "|"; color: Theme.muted; font.pixelSize: Theme.fontSize; font.family: Theme.font }
|
||||
Text { text: GpuMonitor.temp + "°"; color: Theme.text; font.pixelSize: Theme.fontSize; font.family: Theme.font }
|
||||
Text { text: ""; color: Theme.orange; font.pixelSize: Theme.fontSize; font.family: Theme.font }
|
||||
}
|
||||
69
quickshell/KeyboardLayout.qml
Normal file
69
quickshell/KeyboardLayout.qml
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import QtQuick
|
||||
|
||||
BarPill {
|
||||
id: root
|
||||
|
||||
property int currentIdx: 0
|
||||
property var names: []
|
||||
|
||||
readonly property string shortName: {
|
||||
if (names.length === 0) return "??"
|
||||
const n = names[currentIdx] ?? ""
|
||||
if (n.includes("Portuguese")) return "PT"
|
||||
if (n.includes("English")) return "EN"
|
||||
// fallback: first two chars of first word
|
||||
return n.split(" ")[0].substring(0, 2).toUpperCase()
|
||||
}
|
||||
|
||||
Process {
|
||||
command: ["niri", "msg", "--json", "keyboard-layouts"]
|
||||
running: true
|
||||
stdout: SplitParser {
|
||||
splitMarker: ""
|
||||
onRead: data => {
|
||||
try {
|
||||
const kl = JSON.parse(data)
|
||||
root.currentIdx = kl.current_idx
|
||||
root.names = kl.names
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: niri
|
||||
function onRawEventReceived(event) {
|
||||
try {
|
||||
const ev = JSON.parse(event)
|
||||
if (!ev.KeyboardLayoutsChanged) return
|
||||
const kl = ev.KeyboardLayoutsChanged.keyboard_layouts
|
||||
root.currentIdx = kl.current_idx
|
||||
root.names = kl.names
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: ""
|
||||
color: Theme.purple
|
||||
font.pixelSize: Theme.fontSize
|
||||
font.family: Theme.font
|
||||
}
|
||||
Text {
|
||||
text: root.shortName
|
||||
color: Theme.text
|
||||
font.pixelSize: Theme.fontSize
|
||||
font.family: Theme.font
|
||||
|
||||
Behavior on text {
|
||||
// Flash the label on layout change
|
||||
SequentialAnimation {
|
||||
PropertyAnimation { target: parent; property: "opacity"; to: 0; duration: 80 }
|
||||
PropertyAction { target: parent; property: "text" }
|
||||
PropertyAnimation { target: parent; property: "opacity"; to: 1; duration: 80 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
53
quickshell/NetMonitor.qml
Normal file
53
quickshell/NetMonitor.qml
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
pragma Singleton
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import QtQuick
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property real rxSpeed: 0
|
||||
property real txSpeed: 0
|
||||
|
||||
property var _prevRx: null
|
||||
property var _prevTx: null
|
||||
|
||||
function _fmt(bytes) {
|
||||
if (bytes >= 1048576) return (bytes / 1048576).toFixed(1) + " M"
|
||||
if (bytes >= 1024) return (bytes / 1024).toFixed(0) + " K"
|
||||
return bytes + " B"
|
||||
}
|
||||
|
||||
readonly property string rxStr: _fmt(rxSpeed)
|
||||
readonly property string txStr: _fmt(txSpeed)
|
||||
|
||||
Timer {
|
||||
interval: 1000
|
||||
running: true
|
||||
repeat: true
|
||||
triggeredOnStart: true
|
||||
onTriggered: {
|
||||
_proc.running = false
|
||||
_proc.running = true
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: _proc
|
||||
command: ["bash", "-c", "iface=$(ip route show default | awk '{print $5; exit}'); awk -v i=\"$iface:\" '$1==i {print $2, $10}' /proc/net/dev"]
|
||||
stdout: SplitParser {
|
||||
onRead: line => {
|
||||
const parts = line.trim().split(/\s+/)
|
||||
if (parts.length < 2) return
|
||||
const rx = parseInt(parts[0])
|
||||
const tx = parseInt(parts[1])
|
||||
if (root._prevRx !== null) {
|
||||
root.rxSpeed = Math.max(0, rx - root._prevRx)
|
||||
root.txSpeed = Math.max(0, tx - root._prevTx)
|
||||
}
|
||||
root._prevRx = rx
|
||||
root._prevTx = tx
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
9
quickshell/NetWidget.qml
Normal file
9
quickshell/NetWidget.qml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import QtQuick
|
||||
|
||||
BarPill {
|
||||
minWidth: 140
|
||||
Text { text: ""; color: Theme.aqua; font.pixelSize: Theme.fontSize; font.family: Theme.font }
|
||||
Text { text: NetMonitor.rxStr; color: Theme.text; font.pixelSize: Theme.fontSize; font.family: Theme.font }
|
||||
Text { text: ""; color: Theme.yellow; font.pixelSize: Theme.fontSize; font.family: Theme.font }
|
||||
Text { text: NetMonitor.txStr; color: Theme.text; font.pixelSize: Theme.fontSize; font.family: Theme.font }
|
||||
}
|
||||
166
quickshell/NotificationItem.qml
Normal file
166
quickshell/NotificationItem.qml
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property int nid
|
||||
required property string appName
|
||||
required property string summary
|
||||
required property string body
|
||||
required property string appIcon
|
||||
required property int urgency
|
||||
required property var timestamp
|
||||
|
||||
signal dismissed()
|
||||
|
||||
implicitHeight: inner.implicitHeight + 16
|
||||
height: implicitHeight
|
||||
|
||||
Rectangle {
|
||||
id: bg
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 4
|
||||
anchors.rightMargin: 4
|
||||
anchors.topMargin: 2
|
||||
anchors.bottomMargin: 2
|
||||
color: itemClick.containsMouse ? Theme.bg1 : "transparent"
|
||||
radius: Theme.radius
|
||||
|
||||
Behavior on color { ColorAnimation { duration: 100 } }
|
||||
|
||||
RowLayout {
|
||||
id: inner
|
||||
// Right margin reserves space so content does not overlap the close button
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: 8
|
||||
anchors.rightMargin: 32
|
||||
spacing: 8
|
||||
|
||||
Rectangle {
|
||||
width: 34
|
||||
height: 34
|
||||
radius: 17
|
||||
color: root.urgency === 2 ? Theme.redN
|
||||
: root.urgency === 0 ? Theme.bg3
|
||||
: Theme.blueN
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
Image {
|
||||
id: iconImg
|
||||
anchors.fill: parent
|
||||
anchors.margins: 5
|
||||
source: root.appIcon
|
||||
fillMode: Image.PreserveAspectFit
|
||||
visible: status === Image.Ready
|
||||
smooth: true
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
visible: !iconImg.visible
|
||||
text: root.appName.charAt(0).toUpperCase()
|
||||
color: Theme.fg
|
||||
font.pixelSize: Theme.fontSize
|
||||
font.family: Theme.font
|
||||
font.bold: true
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 2
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 6
|
||||
|
||||
Text {
|
||||
text: root.summary.length > 0 ? root.summary : root.appName
|
||||
color: Theme.text
|
||||
font.pixelSize: Theme.fontSize
|
||||
font.family: Theme.font
|
||||
font.bold: true
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Text {
|
||||
text: NotificationsState.formatTime(root.timestamp)
|
||||
color: Theme.subtle
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.family: Theme.font
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
visible: root.body.length > 0
|
||||
text: root.body
|
||||
color: Theme.fg2
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.family: Theme.font
|
||||
wrapMode: Text.WordWrap
|
||||
maximumLineCount: 2
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Text {
|
||||
visible: root.summary.length > 0
|
||||
text: root.appName
|
||||
color: Theme.muted
|
||||
font.pixelSize: Theme.fontSizeSmall - 1
|
||||
font.family: Theme.font
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: closeArea
|
||||
width: 20
|
||||
height: 20
|
||||
radius: 10
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 5
|
||||
anchors.rightMargin: 5
|
||||
color: closeBtn.containsMouse ? Theme.bg3 : "transparent"
|
||||
|
||||
Behavior on color { ColorAnimation { duration: 100 } }
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "✕"
|
||||
color: closeBtn.containsMouse ? Theme.fg : Theme.subtle
|
||||
font.pixelSize: 10
|
||||
opacity: closeBtn.containsMouse ? 1.0 : 0.5
|
||||
|
||||
Behavior on color { ColorAnimation { duration: 100 } }
|
||||
Behavior on opacity { NumberAnimation { duration: 100 } }
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: closeBtn
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.dismissed()
|
||||
}
|
||||
}
|
||||
|
||||
// Does not extend to the right edge to avoid overlapping the close button
|
||||
MouseArea {
|
||||
id: itemClick
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: 28
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
NotificationsState.invokeDefault(root.nid)
|
||||
root.dismissed()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
161
quickshell/NotificationPanel.qml
Normal file
161
quickshell/NotificationPanel.qml
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
|
||||
PanelWindow {
|
||||
id: panel
|
||||
|
||||
anchors.top: true
|
||||
anchors.right: true
|
||||
|
||||
margins.top: Theme.padding
|
||||
margins.right: Theme.padding
|
||||
|
||||
exclusiveZone: 0
|
||||
WlrLayershell.layer: WlrLayer.Overlay
|
||||
visible: NotificationsState.panelOpen && !root.zenMode
|
||||
|
||||
width: 360
|
||||
height: NotificationsState.notifications.count === 0
|
||||
? 120
|
||||
: Math.min(41 + list.contentHeight + 8, 480)
|
||||
|
||||
color: "transparent"
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Theme.bg
|
||||
radius: Theme.radius
|
||||
border.color: Theme.bg2
|
||||
border.width: 1
|
||||
|
||||
Item {
|
||||
id: header
|
||||
height: 40
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
Text {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.padding
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: "Notifications"
|
||||
color: Theme.text
|
||||
font.pixelSize: Theme.fontSize
|
||||
font.family: Theme.font
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: NotificationsState.notifications.count > 0
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.padding
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
height: 22
|
||||
width: clearLabel.implicitWidth + 12
|
||||
radius: Theme.radius
|
||||
color: clearBtn.containsMouse ? Theme.bg2 : "transparent"
|
||||
|
||||
Behavior on color { ColorAnimation { duration: 100 } }
|
||||
|
||||
Text {
|
||||
id: clearLabel
|
||||
anchors.centerIn: parent
|
||||
text: "Clear all"
|
||||
color: clearBtn.containsMouse ? Theme.fg2 : Theme.subtle
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.family: Theme.font
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: clearBtn
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: NotificationsState.dismissAll()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: sep
|
||||
height: 1
|
||||
color: Theme.bg2
|
||||
anchors.top: header.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
}
|
||||
|
||||
Item {
|
||||
visible: NotificationsState.notifications.count === 0
|
||||
anchors.top: sep.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: 79
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "No notifications"
|
||||
color: Theme.subtle
|
||||
font.pixelSize: Theme.fontSize
|
||||
font.family: Theme.font
|
||||
}
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: list
|
||||
visible: NotificationsState.notifications.count > 0
|
||||
|
||||
anchors.top: sep.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.topMargin: 4
|
||||
anchors.bottomMargin: 4
|
||||
|
||||
clip: true
|
||||
spacing: 0
|
||||
model: NotificationsState.notifications
|
||||
|
||||
section.property: "day"
|
||||
section.delegate: Item {
|
||||
width: list.width
|
||||
height: 28
|
||||
|
||||
Text {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 12
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: section
|
||||
color: Theme.accent
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.family: Theme.font
|
||||
font.bold: true
|
||||
}
|
||||
}
|
||||
|
||||
delegate: NotificationItem {
|
||||
required property var model
|
||||
required property int index
|
||||
|
||||
width: list.width
|
||||
nid: model.nid
|
||||
appName: model.appName
|
||||
summary: model.summary
|
||||
body: model.body
|
||||
appIcon: model.appIcon
|
||||
urgency: model.urgency
|
||||
timestamp: model.timestamp
|
||||
|
||||
onDismissed: NotificationsState.dismiss(index)
|
||||
}
|
||||
|
||||
ScrollBar.vertical: ScrollBar {
|
||||
policy: ScrollBar.AsNeeded
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
171
quickshell/NotificationToast.qml
Normal file
171
quickshell/NotificationToast.qml
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
PanelWindow {
|
||||
anchors.top: true
|
||||
anchors.right: true
|
||||
|
||||
margins.top: Theme.padding
|
||||
margins.right: Theme.padding
|
||||
|
||||
exclusiveZone: 0
|
||||
visible: NotificationsState.toasts.count > 0 && !root.zenMode
|
||||
|
||||
width: 320
|
||||
height: toastCol.implicitHeight
|
||||
|
||||
color: "transparent"
|
||||
|
||||
Column {
|
||||
id: toastCol
|
||||
width: parent.width
|
||||
spacing: 4
|
||||
|
||||
Repeater {
|
||||
model: NotificationsState.toasts
|
||||
|
||||
delegate: Rectangle {
|
||||
required property var model
|
||||
required property int index
|
||||
|
||||
width: toastCol.width
|
||||
height: toastInner.implicitHeight + Theme.padding * 2 + 3
|
||||
radius: Theme.radius
|
||||
color: Theme.bg
|
||||
border.color: model.urgency === 2 ? Theme.red : Theme.bg2
|
||||
border.width: 1
|
||||
|
||||
RowLayout {
|
||||
id: toastInner
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: Theme.padding
|
||||
anchors.leftMargin: Theme.padding
|
||||
anchors.rightMargin: Theme.padding
|
||||
spacing: Theme.spacing
|
||||
|
||||
Rectangle {
|
||||
width: 34
|
||||
height: 34
|
||||
radius: 17
|
||||
color: model.urgency === 2 ? Theme.redN
|
||||
: model.urgency === 0 ? Theme.bg3
|
||||
: Theme.blueN
|
||||
Layout.alignment: Qt.AlignTop
|
||||
|
||||
Image {
|
||||
id: tIcon
|
||||
anchors.fill: parent
|
||||
anchors.margins: 5
|
||||
source: model.appIcon
|
||||
fillMode: Image.PreserveAspectFit
|
||||
visible: status === Image.Ready
|
||||
smooth: true
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
visible: !tIcon.visible
|
||||
text: model.appName.charAt(0).toUpperCase()
|
||||
color: Theme.fg
|
||||
font.pixelSize: Theme.fontSize
|
||||
font.family: Theme.font
|
||||
font.bold: true
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 3
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
Text {
|
||||
text: model.summary.length > 0 ? model.summary : model.appName
|
||||
color: Theme.text
|
||||
font.pixelSize: Theme.fontSize
|
||||
font.family: Theme.font
|
||||
font.bold: true
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Text {
|
||||
visible: model.summary.length > 0
|
||||
text: model.appName
|
||||
color: Theme.subtle
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.family: Theme.font
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
visible: model.body.length > 0
|
||||
text: model.body
|
||||
color: Theme.fg2
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.family: Theme.font
|
||||
wrapMode: Text.WordWrap
|
||||
maximumLineCount: 3
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 22
|
||||
height: 22
|
||||
radius: 11
|
||||
color: closeBtn.containsMouse ? Theme.bg3 : "transparent"
|
||||
Layout.alignment: Qt.AlignTop
|
||||
|
||||
Behavior on color { ColorAnimation { duration: 100 } }
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "✕"
|
||||
color: closeBtn.containsMouse ? Theme.fg : Theme.subtle
|
||||
font.pixelSize: 11
|
||||
opacity: closeBtn.containsMouse ? 1.0 : 0.5
|
||||
|
||||
Behavior on color { ColorAnimation { duration: 100 } }
|
||||
Behavior on opacity { NumberAnimation { duration: 100 } }
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: closeBtn
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: NotificationsState.dismissToast(model.tid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
height: 3
|
||||
radius: 2
|
||||
width: parent.width * model.progress
|
||||
color: model.urgency === 2 ? Theme.red : Theme.accent
|
||||
}
|
||||
|
||||
// Does not extend to the right edge to avoid overlapping the close button
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: 34
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
NotificationsState.invokeDefault(model.tid)
|
||||
NotificationsState.dismissToast(model.tid)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
52
quickshell/NotificationWidget.qml
Normal file
52
quickshell/NotificationWidget.qml
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import QtQuick
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
width: pill.width
|
||||
height: pill.height
|
||||
|
||||
BarPill {
|
||||
id: pill
|
||||
|
||||
Text {
|
||||
text: NotificationsState.unreadCount > 0 ? "" : ""
|
||||
color: NotificationsState.unreadCount > 0 ? Theme.yellow : Theme.subtle
|
||||
font.pixelSize: Theme.fontSize
|
||||
font.family: Theme.font
|
||||
|
||||
Behavior on color { ColorAnimation { duration: 150 } }
|
||||
}
|
||||
}
|
||||
|
||||
// Overlaps the pill corner, so z and sibling anchors are used
|
||||
Rectangle {
|
||||
visible: NotificationsState.unreadCount > 0
|
||||
width: Math.max(15, badgeLabel.implicitWidth + 5)
|
||||
height: 13
|
||||
radius: 7
|
||||
color: Theme.red
|
||||
z: 10
|
||||
anchors.top: pill.top
|
||||
anchors.right: pill.right
|
||||
anchors.topMargin: -3
|
||||
anchors.rightMargin: -3
|
||||
|
||||
Text {
|
||||
id: badgeLabel
|
||||
anchors.centerIn: parent
|
||||
text: NotificationsState.unreadCount > 9
|
||||
? "9+" : NotificationsState.unreadCount.toString()
|
||||
color: "white"
|
||||
font.pixelSize: 8
|
||||
font.family: Theme.font
|
||||
font.bold: true
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: pill
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: NotificationsState.togglePanel()
|
||||
}
|
||||
}
|
||||
142
quickshell/NotificationsState.qml
Normal file
142
quickshell/NotificationsState.qml
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
pragma Singleton
|
||||
import Quickshell
|
||||
import Quickshell.Services.Notifications
|
||||
import QtQuick
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property bool panelOpen: false
|
||||
property int unreadCount: 0
|
||||
|
||||
property ListModel notifications: ListModel {}
|
||||
property var _refs: ({}) // maps nid to the live Notification object
|
||||
|
||||
property ListModel toasts: ListModel {}
|
||||
|
||||
// Runs every 100ms while toasts are active, decrementing progress (1.0 to 0.0 over 5s)
|
||||
// and removing any toast that has expired
|
||||
Timer {
|
||||
id: toastTick
|
||||
interval: 100
|
||||
repeat: true
|
||||
running: root.toasts.count > 0
|
||||
|
||||
onTriggered: {
|
||||
let i = 0
|
||||
while (i < root.toasts.count) {
|
||||
const p = root.toasts.get(i).progress - 0.02
|
||||
if (p <= 0) {
|
||||
root.toasts.remove(i, 1)
|
||||
} else {
|
||||
root.toasts.setProperty(i, "progress", p)
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NotificationServer {
|
||||
keepOnReload: true
|
||||
|
||||
onNotification: notif => {
|
||||
const ts = Date.now()
|
||||
const day = root._dayLabel(ts)
|
||||
|
||||
root.notifications.insert(0, {
|
||||
nid: notif.id,
|
||||
appName: notif.appName || "Unknown",
|
||||
summary: notif.summary || "",
|
||||
body: notif.body || "",
|
||||
appIcon: notif.appIcon || "",
|
||||
urgency: notif.urgency || 1,
|
||||
timestamp: ts,
|
||||
day: day
|
||||
})
|
||||
|
||||
const r = root._refs
|
||||
r[notif.id] = notif
|
||||
root._refs = r
|
||||
|
||||
if (!root.panelOpen) root.unreadCount++
|
||||
|
||||
root.toasts.insert(0, {
|
||||
tid: notif.id,
|
||||
appName: notif.appName || "Unknown",
|
||||
summary: notif.summary || "",
|
||||
body: notif.body || "",
|
||||
appIcon: notif.appIcon || "",
|
||||
urgency: notif.urgency || 1,
|
||||
progress: 1.0
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function dismiss(index) {
|
||||
if (index < 0 || index >= notifications.count) return
|
||||
const entry = notifications.get(index)
|
||||
const ref = _refs[entry.nid]
|
||||
if (ref) {
|
||||
try { ref.close() } catch (_) {}
|
||||
const r = _refs
|
||||
delete r[entry.nid]
|
||||
_refs = r
|
||||
}
|
||||
notifications.remove(index, 1)
|
||||
}
|
||||
|
||||
function dismissAll() {
|
||||
for (let i = 0; i < notifications.count; i++) {
|
||||
const ref = _refs[notifications.get(i).nid]
|
||||
if (ref) { try { ref.close() } catch (_) {} }
|
||||
}
|
||||
notifications.clear()
|
||||
_refs = {}
|
||||
unreadCount = 0
|
||||
}
|
||||
|
||||
// Invokes the "default" action on a notification, falling back to the first action
|
||||
function invokeDefault(nid) {
|
||||
const ref = _refs[nid]
|
||||
if (!ref) return
|
||||
const actions = ref.actions
|
||||
if (!actions || actions.length === 0) return
|
||||
for (let i = 0; i < actions.length; i++) {
|
||||
if (actions[i].identifier === "default") {
|
||||
try { actions[i].invoke() } catch (_) {}
|
||||
return
|
||||
}
|
||||
}
|
||||
try { actions[0].invoke() } catch (_) {}
|
||||
}
|
||||
|
||||
function dismissToast(tid) {
|
||||
for (let i = 0; i < toasts.count; i++) {
|
||||
if (toasts.get(i).tid === tid) {
|
||||
toasts.remove(i, 1)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function togglePanel() {
|
||||
panelOpen = !panelOpen
|
||||
if (panelOpen) unreadCount = 0
|
||||
}
|
||||
|
||||
function formatTime(ts) {
|
||||
return Qt.formatDateTime(new Date(ts), "hh:mm")
|
||||
}
|
||||
|
||||
function _dayLabel(ts) {
|
||||
const now = new Date()
|
||||
const date = new Date(ts)
|
||||
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate())
|
||||
const yesterday = new Date(today.getTime() - 86400000)
|
||||
const day = new Date(date.getFullYear(), date.getMonth(), date.getDate())
|
||||
|
||||
if (day.getTime() === today.getTime()) return "Today"
|
||||
if (day.getTime() === yesterday.getTime()) return "Yesterday"
|
||||
return Qt.formatDateTime(date, "dd/MM/yyyy")
|
||||
}
|
||||
}
|
||||
47
quickshell/RamMonitor.qml
Normal file
47
quickshell/RamMonitor.qml
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
pragma Singleton
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import QtQuick
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property int usedMb: 0
|
||||
property int totalMb: 0
|
||||
property int usage: 0
|
||||
|
||||
Timer {
|
||||
interval: 2000
|
||||
running: true
|
||||
repeat: true
|
||||
triggeredOnStart: true
|
||||
onTriggered: {
|
||||
_proc.running = false
|
||||
_proc.running = true
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: _proc
|
||||
command: ["bash", "-c", "grep -E '^(MemTotal|MemAvailable):' /proc/meminfo | awk '{print $2}'"]
|
||||
stdout: SplitParser {
|
||||
property int _total: 0
|
||||
property int _avail: 0
|
||||
property int _lines: 0
|
||||
|
||||
onRead: line => {
|
||||
const v = parseInt(line.trim())
|
||||
if (isNaN(v)) return
|
||||
if (_lines === 0) _total = v
|
||||
else _avail = v
|
||||
_lines++
|
||||
if (_lines === 2) {
|
||||
root.totalMb = Math.round(_total / 1024)
|
||||
root.usedMb = Math.round((_total - _avail) / 1024)
|
||||
root.usage = Math.round((1 - _avail / _total) * 100)
|
||||
_lines = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
10
quickshell/RamWidget.qml
Normal file
10
quickshell/RamWidget.qml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import QtQuick
|
||||
|
||||
BarPill {
|
||||
minWidth: 115
|
||||
|
||||
Text { text: ""; color: Theme.purple; font.pixelSize: Theme.fontSize; font.family: Theme.font }
|
||||
Text { text: (RamMonitor.usedMb / 1024).toFixed(1) + " GB"; color: Theme.text; font.pixelSize: Theme.fontSize; font.family: Theme.font }
|
||||
Text { text: "|"; color: Theme.muted; font.pixelSize: Theme.fontSize; font.family: Theme.font }
|
||||
Text { text: RamMonitor.usage + "%"; color: Theme.text; font.pixelSize: Theme.fontSize; font.family: Theme.font }
|
||||
}
|
||||
52
quickshell/Theme.qml
Normal file
52
quickshell/Theme.qml
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
pragma Singleton
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
|
||||
Singleton {
|
||||
// Gruvbox Dark
|
||||
readonly property color bg: "#282828"
|
||||
readonly property color bgDark: "#1d2021"
|
||||
readonly property color bg1: "#3c3836"
|
||||
readonly property color bg2: "#504945"
|
||||
readonly property color bg3: "#665c54"
|
||||
readonly property color bg4: "#7c6f64"
|
||||
|
||||
readonly property color fg: "#ebdbb2"
|
||||
readonly property color fg1: "#d5c4a1"
|
||||
readonly property color fg2: "#bdae93"
|
||||
readonly property color fg3: "#a89984"
|
||||
|
||||
readonly property color red: "#fb4934"
|
||||
readonly property color green: "#b8bb26"
|
||||
readonly property color yellow: "#fabd2f"
|
||||
readonly property color blue: "#83a598"
|
||||
readonly property color purple: "#d3869b"
|
||||
readonly property color aqua: "#8ec07c"
|
||||
readonly property color orange: "#fe8019"
|
||||
|
||||
// Neutral (softer) variants
|
||||
readonly property color redN: "#cc241d"
|
||||
readonly property color greenN: "#98971a"
|
||||
readonly property color yellowN: "#d79921"
|
||||
readonly property color blueN: "#458588"
|
||||
readonly property color purpleN: "#b16286"
|
||||
readonly property color aquaN: "#689d6a"
|
||||
readonly property color orangeN: "#d65d0e"
|
||||
|
||||
// Semantic aliases
|
||||
readonly property color accent: yellow
|
||||
readonly property color urgent: red
|
||||
readonly property color text: fg
|
||||
readonly property color subtle: fg3
|
||||
readonly property color muted: bg4
|
||||
|
||||
readonly property int barHeight: 32
|
||||
readonly property int padding: 12
|
||||
readonly property int spacing: 8
|
||||
readonly property int radius: 6
|
||||
|
||||
readonly property string font: "JetBrainsMono Nerd Font"
|
||||
readonly property string fontFallback: "monospace"
|
||||
readonly property int fontSize: 13
|
||||
readonly property int fontSizeSmall: 11
|
||||
}
|
||||
47
quickshell/Workspaces.qml
Normal file
47
quickshell/Workspaces.qml
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
RowLayout {
|
||||
spacing: Theme.spacing
|
||||
|
||||
Repeater {
|
||||
model: niri.workspaces
|
||||
|
||||
// Container com largura fixa — o layout nunca muda
|
||||
Item {
|
||||
required property var modelData
|
||||
|
||||
readonly property bool focused: modelData.isFocused
|
||||
readonly property bool occupied: modelData.activeWindowId !== null
|
||||
|
||||
width: 24
|
||||
height: 8
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
height: parent.height
|
||||
radius: height / 2
|
||||
|
||||
width: parent.focused ? 20 : 8
|
||||
color: {
|
||||
if (parent.focused) return Theme.accent
|
||||
if (parent.occupied) return Theme.subtle
|
||||
return Theme.muted
|
||||
}
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation { duration: 150; easing.type: Easing.OutCubic }
|
||||
}
|
||||
Behavior on color {
|
||||
ColorAnimation { duration: 150 }
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: niri.focusWorkspaceById(modelData.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
40
quickshell/shell.qml
Normal file
40
quickshell/shell.qml
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Wayland
|
||||
import QtQuick
|
||||
import Niri 0.1
|
||||
|
||||
ShellRoot {
|
||||
id: root
|
||||
|
||||
property bool zenMode: false
|
||||
|
||||
IpcHandler {
|
||||
target: "zen"
|
||||
function toggle() { root.zenMode = !root.zenMode }
|
||||
}
|
||||
|
||||
Niri {
|
||||
id: niri
|
||||
Component.onCompleted: connect()
|
||||
onErrorOccurred: error => console.error("[Niri]", error)
|
||||
}
|
||||
|
||||
Bar {}
|
||||
|
||||
// Fullscreen overlay to help closing open windows
|
||||
PanelWindow {
|
||||
anchors { top: true; bottom: true; left: true; right: true }
|
||||
exclusiveZone: 0
|
||||
visible: NotificationsState.panelOpen && !root.zenMode
|
||||
color: "transparent"
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: NotificationsState.togglePanel()
|
||||
}
|
||||
}
|
||||
|
||||
NotificationPanel {}
|
||||
NotificationToast {}
|
||||
}
|
||||
BIN
wallpapers/miku.png
Normal file
BIN
wallpapers/miku.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.5 MiB |
Loading…
Add table
Add a link
Reference in a new issue