diff --git a/misc/.config/hypr/hyprland.conf b/misc/.config/hypr/hyprland.conf
index d728518..467a3f9 100644
--- a/misc/.config/hypr/hyprland.conf
+++ b/misc/.config/hypr/hyprland.conf
@@ -3,7 +3,7 @@
# Wayland related environment variables.
env = HYPRCURSOR_THEME,McMojave
env = HYPRCURSOR_SIZE,30
-env = TERMINAL,foot
+env = TERMINAL,st
env = QT_QPA_PLATFORM,wayland
opengl {
@@ -37,7 +37,7 @@ input {
sensitivity = 0 # -1.0 - 1.0, 0 means no modification.
- kb_options = caps:swapescape
+ # kb_options = caps:swapescape
}
general {
@@ -112,6 +112,7 @@ windowrulev2 = bordersize 0, floating:0, onworkspace:w[tv1]
windowrulev2 = rounding 0, floating:0, onworkspace:w[tv1]
windowrulev2 = bordersize 0, floating:0, onworkspace:f[1]
windowrulev2 = rounding 0, floating:0, onworkspace:f[1]
+windowrulev2 = float, class:nm-tray
gestures {
# See https://wiki.hyprland.org/Configuring/Variables/ for more
@@ -122,7 +123,7 @@ misc {
# See https://wiki.hyprland.org/Configuring/Variables/ for more
force_default_wallpaper = 0 # Set to 0 to disable the anime mascot wallpapers
# enable_swallow = true
- # swallow_regex = ^(st|foot|footclient)$
+ # swallow_regex = ^(st-256color)$
mouse_move_enables_dpms = true
}
@@ -140,29 +141,28 @@ layerrule = ignorezero, waybar
$mainMod = SUPER
# Program spawning hotkeys
-bind = CONTROL ALT, BACKSPACE, exec, foot -e zsh -c 'btop'
+bind = CONTROL ALT, BACKSPACE, exec, st -e zsh -c 'btop'
bind = $mainMod CONTROL, W, exec, [floating] foot -W 78x38 -e nmtui
-bind = $mainMod, RETURN, exec, foot
-bind = $mainMod, W, exec, firefox
-bind = $mainMod, E, exec, foot -e zsh -c 'tmp="$(mktemp -t "yazi-cwd.XXXXX")"; yazi "$@" --cwd-file="$tmp"; printf "\033]0;foot\007"; if cwd="$(cat -- "$tmp")" && [ -n "$cwd" ] && [ "$cwd" != "$PWD" ]; then; cd -- "$cwd"; fi; rm -f -- "$tmp"; exec $SHELL'
+bind = $mainMod, RETURN, exec, st
+bind = $mainMod, W, exec, zen
+bind = $mainMod, E, exec, st -e zsh -c 'tmp="$(mktemp -t "yazi-cwd.XXXXX")"; yazi "$@" --cwd-file="$tmp"; printf "\033]0;st\007"; if cwd="$(cat -- "$tmp")" && [ -n "$cwd" ] && [ "$cwd" != "$PWD" ]; then; cd -- "$cwd"; fi; rm -f -- "$tmp"; exec $SHELL'
bind = $mainMod SHIFT, E, exec, emacsclient -c -a 'emacs'
-bind = $mainMod, C, exec, foot -e zsh -c 'khal interactive'
-bind = $mainMod, M, exec, foot -e zsh -c 'neomutt'
+bind = $mainMod, C, exec, st -e zsh -c 'khal interactive'
+bind = $mainMod, M, exec, st -e zsh -c 'neomutt'
# Menu hoykeys
bind = $mainMod, R, exec, fuzzel
# System control keybinds
-binde = $mainMod, MINUS, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 1%-; test -z "$(pidof waybar)" && notify-send -r 44 "$(wpctl get-volume @DEFAULT_AUDIO_SINK@)"
-binde = $mainMod SHIFT, MINUS, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-; test -z "$(pidof waybar)" && notify-send -r 44 "$(wpctl get-volume @DEFAULT_AUDIO_SINK@)"
-binde = $mainMod, EQUAL, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 1%+; test -z "$(pidof waybar)" && notify-send -r 44 "$(wpctl get-volume @DEFAULT_AUDIO_SINK@)"
-binde = $mainMod SHIFT, EQUAL, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+; test -z "$(pidof waybar)" && notify-send -r 44 "$(wpctl get-volume @DEFAULT_AUDIO_SINK@)"
+binde = $mainMod, MINUS, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 1%-
+binde = $mainMod SHIFT, MINUS, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-
+binde = $mainMod, EQUAL, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 1%+
+binde = $mainMod SHIFT, EQUAL, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+
binde = ,F7, exec, change-brightness up
binde = ,XF86MonBrightnessUp, exec, change-brightness up
binde = ,F6, exec, change-brightness down
binde = ,XF86MonBrightnessDown,exec, change-brightness down
-bind = $mainMod, L, exec, swaylock -e -c 000000
-bind = $mainMod, L, exec, sleep 5; hyprctl dispatch dpms off
+bind = $mainMod, L, exec, hyprlock
bind = $mainMod CONTROL SHIFT, R, exec, startw # Reload all graphical daemons
@@ -176,7 +176,9 @@ bind = $mainMod, D, exec, hyprctl dispatch submap menu-submap; sleep 2; hyprctl
submap = menu-submap
bind = ,A, exec, menu-accent
bind = ,A, submap, reset
-bind = ,M, exec, menu-man
+bind = ,B, exec, [floating] foot -W 80x17 -e dash -c -i 'menu-bookmark'
+bind = ,B, submap, reset
+bind = ,M, exec, [floating] foot -W 78x38 -e dash -c -i 'menu-man'
bind = ,M, submap, reset
bind = ,W, exec, networkmanager_dmenu
bind = ,W, submap, reset
@@ -186,7 +188,7 @@ bind = ,I, exec, menu-wpio source
bind = ,I, submap, reset
bind = ,L, exec, hyprmonitors menu
bind = ,L, submap, reset
-bind = ,P, exec, menu-pass
+bind = ,P, exec, [floating] foot -W 80x17 -e zsh -c -i 'menu-pass'
bind = ,P, submap, reset
submap = reset
diff --git a/misc/.config/hyprland-autoname-workspaces/config.toml b/misc/.config/hyprland-autoname-workspaces/config.toml
index 65b820f..058ccbc 100644
--- a/misc/.config/hyprland-autoname-workspaces/config.toml
+++ b/misc/.config/hyprland-autoname-workspaces/config.toml
@@ -1,29 +1,34 @@
version = "1.1.15"
+[title."(?i)foot"]
+Yazi = ""
+
[class]
DEFAULT = "{class}"
-"(?i)Anki" = " "
+"(?i)anki" = " "
"(?i)blueman-manager-wrapped" = " "
"(?i)chromium" = " "
"(?i)discord" = " "
-"(?i)Element" = " "
+"(?i)element" = " "
"(?i)emacs" = " "
"(?i)firefox" = " "
"(?i)foot" = " "
"(?i)ghidra" = " "
-"(?i)Gimp" = " "
-"(?i)Kitty" = " "
+"(?i)gimp" = " "
+"(?i)kitty" = " "
"(?i)libreoffice-draw" = " "
"(?i)libreoffice-writer" = " "
"(?i)mpv" = " "
"(?i)mullvad vpn" = " "
-"(?i)Nsxiv" = " "
+"(?i)nsxiv" = " "
+"(?i)signal" = " "
"(?i)slack" = " "
"(?i)spotify" = " "
"(?i)st" = " "
"(?i)steam" = " "
"(?i)virtualbox" = " "
"(?i)zathura" = " "
+"(?i)zen" = " "
[class_active]
@@ -52,7 +57,7 @@ DEFAULT = "{class}"
"(?i)neomutt" = "neomutt"
[title_in_class_active."(?i)firefox"]
-"(?i)twitch" = "{icon}"
+"(?i)twitch" = "{icon}"
[title_in_initial_class]
@@ -79,9 +84,9 @@ dedup_inactive_fullscreen = false
delim = ""
workspace = "{id}:{delim}{clients}"
workspace_empty = "{id}"
-client = "{icon}"
+client = "{icon}"
client_fullscreen = "[{icon}]"
-client_active = "{icon}"
+client_active = "{icon}"
client_dup = "{icon}{counter_sup}"
client_dup_active = "*{icon}*{delim}{icon}{counter_unfocused_sup}"
client_dup_fullscreen = "[{icon}]{delim}{icon}{counter_unfocused_sup}"
diff --git a/misc/.config/waybar/config b/misc/.config/waybar/config
index 430c2d0..bc7ec94 100644
--- a/misc/.config/waybar/config
+++ b/misc/.config/waybar/config
@@ -3,10 +3,14 @@
"position": "top",
"height": 27,
- "modules-left": ["hyprland/workspaces", "custom/scratch", "hyprland/mode", "hyprland/window"],
+ "modules-left": ["custom/icon", "hyprland/workspaces", "custom/scratch", "hyprland/mode", "hyprland/window"],
"modules-center": [],
"modules-right": ["tray", "memory", "pulseaudio", "battery", "clock"],
+ "custom/icon": {
+ "format": "",
+ },
+
"hyprland/workspaces": {
"disable-scroll": true,
"disable-markup": true,
diff --git a/misc/.config/waybar/style.css b/misc/.config/waybar/style.css
index ff09087..7e9fe40 100644
--- a/misc/.config/waybar/style.css
+++ b/misc/.config/waybar/style.css
@@ -29,9 +29,12 @@ button:hover {
}
-#custom-scratch {
- color: #b8b8b8;
+#custom-icon {
+ color: #ffffff;
padding: 0px 9px 0px 9px;
+ margin: 0px 0px 0px 0px;
+ border: none;
+ border-radius: 0;
}
#workspaces button {
diff --git a/misc/.local/bin/startw b/misc/.local/bin/startw
index c48e77d..058d0a6 100755
--- a/misc/.local/bin/startw
+++ b/misc/.local/bin/startw
@@ -5,24 +5,11 @@
swww kill
pkill fcitx5
-# Start daemons that can turn on asynchronously
-(
- foot --server &
- systemctl --user restart network-manager-applet
- pkill emacs; emacs --daemon
-) &
-
-# Rearrange monitors
-hyprmonitors auto
-
systemctl --user restart hyprland-autoname-workspaces
swww-daemon &
fcitx5 &
-waybar &
-
-# Sometimes the monitor config does not want to apply properly on the first
-# go. I then have to reload the configuration to make sure that the monitors
-# are actually in their intended place.
-hyprctl reload
+quickshell &
+hypridle &
+pkill emacs; emacs --daemo
diff --git a/nixos/.config/nixos/core.nix b/nixos/.config/nixos/core.nix
index 72e09be..2c4359d 100644
--- a/nixos/.config/nixos/core.nix
+++ b/nixos/.config/nixos/core.nix
@@ -1,8 +1,6 @@
{ config, pkgs, ... }:
let
- # # Only allowing unfree for a useUasm yazi fix. Remove once patched.
- # nixos-unstable = (import {config.allowUnfree = true;});
flake-compat = builtins.fetchTarball "https://github.com/edolstra/flake-compat/archive/master.tar.gz";
in {
# Nix settings
@@ -31,17 +29,17 @@ in {
];
};
- i18n.defaultLocale = "en_US.UTF-8";
+ i18n.defaultLocale = "en_US.UTF-8";
i18n.extraLocaleSettings = {
- LC_ADDRESS = "en_US.UTF-8";
+ LC_ADDRESS = "en_US.UTF-8";
LC_IDENTIFICATION = "en_US.UTF-8";
- LC_MEASUREMENT = "en_US.UTF-8";
- LC_MONETARY = "en_US.UTF-8";
- LC_NAME = "en_US.UTF-8";
- LC_NUMERIC = "en_US.UTF-8";
- LC_PAPER = "en_US.UTF-8";
- LC_TELEPHONE = "en_US.UTF-8";
- LC_TIME = "en_US.UTF-8";
+ LC_MEASUREMENT = "en_US.UTF-8";
+ LC_MONETARY = "en_US.UTF-8";
+ LC_NAME = "en_US.UTF-8";
+ LC_NUMERIC = "en_US.UTF-8";
+ LC_PAPER = "en_US.UTF-8";
+ LC_TELEPHONE = "en_US.UTF-8";
+ LC_TIME = "en_US.UTF-8";
};
fonts.packages = with pkgs; [
@@ -150,7 +148,6 @@ in {
openconnect # Connect to VPNs
pass-nodmenu # CLI password store (without dmenu dependency)
pinentry-curses # Terminal-based pinentry program
- python311 # Python
socat # Interact with sockets
stow # Simlink farm (used for dotfile management)
tldr # Brief info about a command
@@ -179,10 +176,16 @@ in {
nix-prefetch-git # Like nix-prefetch-url, but for git
nvd # See diffs between builds
- # Pop into an environment abiding by the Filesystem Hierarchy Standard to run
- # applications which do not play nicely with NixOS.
- (
- let
+ # Custom overlays. See below for explanations
+ fhs-run
+ python-common
+ ];
+
+
+ nixpkgs.overlays = [
+ (self: super: {
+ # Pop into an environment abiding by the Filesystem Hierarchy Standard
+ # to run applications which do not play nicely with NixOS.
fhs-run = pkgs.buildFHSUserEnv {
name = "fhs-run";
targetPkgs = pkgs: [];
@@ -192,35 +195,14 @@ in {
eval "$@" # Execute whatever arguments
'';
};
- in
- fhs-run
- )
- # Defining an environment to run "make" with the proper libraries installed
- # "make", in the main environment, references the script, which envokes the
- # environment, and passes the args to gnumake.
- (
- let
- make-shell = pkgs.buildEnv {
- name = "make-shell";
- paths = with pkgs; [
- # Tools
- gnumake
- pkg-config
-
- # Libraries
- harfbuzz
- xorg.libX11.dev
- xorg.libXft
- xorg.libXinerama
- ];
- };
- in
- (pkgs.writeScriptBin "make" ''
- #!/usr/bin/env sh
- nix-shell -p ${make-shell} --run "make $*"
- '')
- )
+ # When evoking the command `python` from outside a shell, it runs the
+ # commands inside a nix shell containing common python packages that I
+ # always want to be available.
+ python-common = pkgs.python3.withPackages (ps: with ps; [
+ requests
+ ]);
+ })
];
nixpkgs.config.packageOverrides = pkgs: {
diff --git a/nixos/.config/nixos/derivations/ags.nix b/nixos/.config/nixos/derivations/ags.nix
deleted file mode 100644
index 6a9ad53..0000000
--- a/nixos/.config/nixos/derivations/ags.nix
+++ /dev/null
@@ -1,103 +0,0 @@
-{ lib
-, stdenv
-, buildNpmPackage
-, fetchFromGitLab
-, nodePackages
-, meson
-, pkg-config
-, ninja
-, gobject-introspection
-, gtk3
-, libpulseaudio
-, gjs
-, wrapGAppsHook
-, upower
-, gnome
-, gtk-layer-shell
-, glib-networking
-, networkmanager
-, libdbusmenu-gtk3
-, gvfs
-, libsoup_3
-, libnotify
-, pam
-, extraPackages ? [ ]
-, version ? "git"
-, buildTypes ? false
-}:
-
-let
- gvc-src = fetchFromGitLab {
- domain = "gitlab.gnome.org";
- owner = "GNOME";
- repo = "libgnome-volume-control";
- rev = "8e7a5a4c3e51007ce6579292642517e3d3eb9c50";
- sha256 = "sha256-FosJwgTCp6/EI6WVbJhPisokRBA6oT0eo7d+Ya7fFX8=";
- };
-in
-stdenv.mkDerivation rec {
- pname = "ags";
- inherit version;
-
- src = buildNpmPackage {
- name = pname;
- src = ../.;
-
- dontBuild = true;
-
- npmDepsHash = "sha256-ucWdADdMqAdLXQYKGOXHNRNM9bhjKX4vkMcQ8q/GZ20=";
-
- installPhase = ''
- mkdir $out
- cp -r * $out
- '';
- };
-
- mesonFlags = builtins.concatLists [
- (lib.optional buildTypes "-Dbuild_types=true")
- ];
-
- prePatch = ''
- mkdir -p ./subprojects/gvc
- cp -r ${gvc-src}/* ./subprojects/gvc
- '';
-
- postPatch = ''
- chmod +x post_install.sh
- patchShebangs post_install.sh
- '';
-
- nativeBuildInputs = [
- pkg-config
- meson
- ninja
- nodePackages.typescript
- wrapGAppsHook
- gobject-introspection
- ];
-
- buildInputs = [
- gjs
- gtk3
- libpulseaudio
- upower
- gnome.gnome-bluetooth
- gtk-layer-shell
- glib-networking
- networkmanager
- libdbusmenu-gtk3
- gvfs
- libsoup_3
- libnotify
- pam
- ] ++ extraPackages;
-
- meta = with lib; {
- description = "A customizable and extensible shell";
- homepage = "https://github.com/Aylur/ags";
- platforms = [ "x86_64-linux" "aarch64-linux" ];
- license = licenses.gpl3;
- meta.maintainers = [ lib.maintainers.Aylur ];
- };
-}
-
diff --git a/nixos/.config/nixos/profiles/wm/hyprland.nix b/nixos/.config/nixos/profiles/wm/hyprland.nix
index afc724b..82a9ace 100644
--- a/nixos/.config/nixos/profiles/wm/hyprland.nix
+++ b/nixos/.config/nixos/profiles/wm/hyprland.nix
@@ -6,6 +6,7 @@ let
flake-compat = builtins.fetchTarball "https://github.com/edolstra/flake-compat/archive/master.tar.gz";
hyprland_nightly = (import flake-compat {
src = builtins.fetchGit {
+ ref = "main";
url = "https://github.com/hyprwm/Hyprland.git";
submodules = true;
};
@@ -24,11 +25,10 @@ in {
hyprland = { # Dynamic tiling window manager
enable = true;
xwayland.enable = true;
- package = nixos-unstable.hyprland.override(o: {
- aquamarine = nixos-unstable.aquamarine;
- });
+ # package = nixos-unstable.hyprland;
# package = hyprland_nightly.packages.${pkgs.stdenv.hostPlatform.system}.hyprland;
};
+ nm-applet.enable = true;
};
systemd.user.services = {
@@ -41,16 +41,16 @@ in {
serviceConfig.Restart = "always";
serviceConfig.RestartSec = 1;
};
- network-manager-applet = {
- description = "Start the network manager applet";
- after = [ "graphical-session.target" ];
- requires = [ "graphical-session.target" ];
- wantedBy = [ "graphical-session.target" ];
- serviceConfig.Type = "forking";
- serviceConfig.Restart = "always";
- serviceConfig.RestartSec = 1;
- serviceConfig.ExecStart = "${pkgs.networkmanagerapplet}/bin/nm-applet";
- };
+ # network-manager-applet = {
+ # description = "Start the network manager applet";
+ # after = [ "graphical-session.target" ];
+ # requires = [ "graphical-session.target" ];
+ # wantedBy = [ "graphical-session.target" ];
+ # serviceConfig.Type = "forking";
+ # serviceConfig.Restart = "always";
+ # serviceConfig.RestartSec = 1;
+ # serviceConfig.ExecStart = "${pkgs.networkmanagerapplet}/bin/nm-applet";
+ # };
};
environment.systemPackages = with pkgs; [
@@ -63,6 +63,7 @@ in {
grimblast # Allows freezing screen
grim # Screenshot tool
hicolor-icon-theme # Icons
+ hypridle # Do commands upon user idle
hyprland-autoname-workspaces # Add icons to workspace titles
hyprlock # Screen locking utility
hyprpaper
@@ -70,7 +71,7 @@ in {
kanshi # Autorandr substitute
libnotify # Send messages to notification daemon
libreoffice # MSOffice btfo
- networkmanagerapplet # Wifi dropdown menu
+ # networkmanagerapplet # Wifi dropdown menu
networkmanager_dmenu # Manage wifi with dmenu
nsxiv # Image viewer
nwg-displays
@@ -80,6 +81,7 @@ in {
rofi-pass # Rofi frontend for password store
sassc # SCSS interpreter
slurp # Screen selection utility
+ st
swaylock # Wayland session locker
swww # Sets background images
texlive.combined.scheme-full # LaTeX to create documents
@@ -94,6 +96,7 @@ in {
xwaylandvideobridge # Allows screensharing from XWayland programs
xorg.xcursorthemes
zathura # Minimalist PDF reader
+ zen-browser
# GTK Themes
lxappearance-gtk2 # Theme switcher
@@ -101,10 +104,28 @@ in {
];
nixpkgs.overlays = [
- (self: super: {
+ (final: prev: {
hyprland-autoname-workspaces = nixos-unstable.hyprland-autoname-workspaces;
waybar = nixos-unstable.waybar;
typst = nixos-unstable.typst;
+ # typst = (import flake-compat {
+ # src = builtins.fetchGit {
+ # url = "https://github.com/typst/typst.git";
+ # };
+ # }).outputs.packages.${pkgs.stdenv.hostPlatform.system}.default;
+ zen-browser = (import flake-compat {
+ src = builtins.fetchGit {
+ url = "https://github.com/0xc000022070/zen-browser-flake.git";
+ };
+ }).outputs.packages.${pkgs.stdenv.hostPlatform.system}.default;
+ st = prev.st.overrideAttrs (o: {
+ src = /home/vince/.config/st;
+ buildInputs = o.buildInputs ++ (with pkgs; [
+ # Extra libraries needed to build patches
+ harfbuzz
+ imlib2
+ ]);
+ });
})
];
}
diff --git a/quickshell/.config/quickshell/Globals.qml b/quickshell/.config/quickshell/Globals.qml
new file mode 100644
index 0000000..8c4a5af
--- /dev/null
+++ b/quickshell/.config/quickshell/Globals.qml
@@ -0,0 +1,18 @@
+pragma Singleton
+
+import QtQuick
+import Quickshell
+
+Singleton {
+ id: root
+ property var popupContext: PopupContext {};
+ property var date: new Date()
+
+ Timer {
+ interval: 1000
+ repeat: true
+ running: true
+
+ onTriggered: root.date = new Date()
+ }
+}
diff --git a/quickshell/.config/quickshell/PopupContext.qml b/quickshell/.config/quickshell/PopupContext.qml
new file mode 100644
index 0000000..6f007c8
--- /dev/null
+++ b/quickshell/.config/quickshell/PopupContext.qml
@@ -0,0 +1,6 @@
+import QtQuick
+
+// Tracks which popup of a set is active.
+QtObject {
+ property var popup: null;
+}
diff --git a/quickshell/.config/quickshell/bar/Bar.qml b/quickshell/.config/quickshell/bar/Bar.qml
new file mode 100644
index 0000000..48a2bfa
--- /dev/null
+++ b/quickshell/.config/quickshell/bar/Bar.qml
@@ -0,0 +1,57 @@
+import Quickshell
+import Quickshell.Io
+import QtQuick
+import QtQuick.Layouts
+import "blocks" as Blocks
+
+Scope {
+ Variants {
+ model: Quickshell.screens
+
+ PanelWindow {
+ property var modelData
+ screen: modelData
+
+ color: "#cc000000"
+ height: 27
+
+ anchors {
+ top: true
+ left: true
+ right: true
+ }
+
+ RowLayout {
+ spacing: 0
+ width: parent.width
+ height: parent.height
+
+ // Left side
+ RowLayout {
+ spacing: 0
+ Layout.alignment: Qt.AlignLeft
+
+ Blocks.Icon {}
+ Blocks.Workspaces {}
+ Blocks.ActiveWorkspace {}
+ }
+
+ // Right side
+ RowLayout {
+ spacing: 0
+ Layout.alignment: Qt.AlignRight
+
+ Blocks.SystemTray {}
+ Blocks.Test {}
+ Blocks.Notifications {}
+ Blocks.Memory {}
+ Blocks.Sound {}
+ Blocks.Battery {}
+ Blocks.Date {}
+ Blocks.Time {}
+ }
+ }
+ }
+ }
+}
+
diff --git a/quickshell/.config/quickshell/bar/BarBlock.qml b/quickshell/.config/quickshell/bar/BarBlock.qml
new file mode 100644
index 0000000..9daf98a
--- /dev/null
+++ b/quickshell/.config/quickshell/bar/BarBlock.qml
@@ -0,0 +1,89 @@
+import QtQuick
+import QtQuick.Layouts
+import Quickshell
+
+Rectangle {
+ id: root
+ Layout.preferredWidth: wsText.implicitWidth + 10
+ Layout.preferredHeight: 27
+
+ property string text
+ property bool dim: false
+ property bool underline
+ property var onClicked: function() {}
+ property int leftPadding
+ property int rightPadding
+
+ property string fgColor: "white"
+ property string dimFgColor: "#999999"
+ property string hoveredBgColor: "#444444"
+
+ // Background color
+ color: {
+ if (mouseArea.containsMouse)
+ return hoveredBgColor;
+ return "transparent";
+ }
+
+ states: [
+ State {
+ when: mouseArea.containsMouse
+ PropertyChanges {
+ target: root
+ }
+ }
+ ]
+
+ Behavior on color {
+ ColorAnimation {
+ duration: 200
+ }
+ }
+
+ BarText {
+ id: wsText
+ text: root.text
+ anchors {
+ left: parent.left
+ right: parent.right
+ leftMargin: root.leftPadding
+ rightMargin: root.rightPadding
+ verticalCenter: parent.verticalCenter
+ }
+
+ color: {
+ if (mouseArea.containsMouse || !root.dim)
+ return fgColor
+ return dimFgColor
+ }
+
+ Behavior on color {
+ ColorAnimation {
+ duration: 100
+ }
+ }
+ }
+
+ MouseArea {
+ id: mouseArea
+ anchors.fill: root
+ hoverEnabled: true
+ acceptedButtons: Qt.LeftButton
+ onClicked: root.onClicked()
+ }
+
+ // While line underneath workspace
+ Rectangle {
+ id: wsLine
+ width: parent.width
+ height: 3
+
+ color: {
+ if (parent.underline)
+ return fgColor;
+ return "transparent";
+ }
+ anchors.bottom: parent.bottom
+ }
+}
+
diff --git a/quickshell/.config/quickshell/bar/BarText.qml b/quickshell/.config/quickshell/bar/BarText.qml
new file mode 100644
index 0000000..25e2299
--- /dev/null
+++ b/quickshell/.config/quickshell/bar/BarText.qml
@@ -0,0 +1,39 @@
+import Quickshell
+import Quickshell.Io
+import QtQuick
+
+Item {
+ property string text
+ property string color: "white"
+ property string mainFont: "FiraCode"
+ property string symbolFont: "Symbols Nerd Font Mono"
+ property int pointSize: 11
+
+ implicitWidth: thetext.implicitWidth
+ implicitHeight: thetext.implicitHeight
+
+ function wrapSymbols(text) {
+ const isSymbol = (codePoint) =>
+ (codePoint >= 0xE000 && codePoint <= 0xF8FF) // Private Use Area
+ || (codePoint >= 0xF0000 && codePoint <= 0xFFFFF) // Supplementary Private Use Area-A
+ || (codePoint >= 0x100000 && codePoint <= 0x10FFFF); // Supplementary Private Use Area-B
+
+ return text.replace(/./gu, (c) => isSymbol(c.codePointAt(0))
+ ? `${c}`
+ : c);
+ }
+
+ Text {
+ id: thetext
+ text: wrapSymbols(parent.text)
+ color: parent.color
+ anchors.centerIn: parent
+
+ font {
+ family: parent.mainFont
+ pointSize: parent.pointSize
+ }
+ textFormat: Text.RichText
+ }
+}
+
diff --git a/quickshell/.config/quickshell/bar/Notification.qml b/quickshell/.config/quickshell/bar/Notification.qml
new file mode 100644
index 0000000..b86a966
--- /dev/null
+++ b/quickshell/.config/quickshell/bar/Notification.qml
@@ -0,0 +1,11 @@
+import QtQuick
+
+Text {
+ required property int id
+ required property string body
+ required property string summary
+ property int margin
+
+ text: `- ${summary}: ${body}`
+}
+
diff --git a/quickshell/.config/quickshell/bar/NotificationPanel.qml b/quickshell/.config/quickshell/bar/NotificationPanel.qml
new file mode 100644
index 0000000..0ef8712
--- /dev/null
+++ b/quickshell/.config/quickshell/bar/NotificationPanel.qml
@@ -0,0 +1,101 @@
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Controls
+import Quickshell
+import Quickshell.Wayland
+import Quickshell.Services.Notifications
+
+PanelWindow {
+ // required property font custom_font
+ required property color text_color
+ property list notification_objects
+
+ width: 500
+ height: 600
+
+ color: "#171a18"
+
+ WlrLayershell.layer: WlrLayer.Overlay
+
+ Rectangle {
+ border.width: 5
+ border.color: "#8ec07c"
+ anchors.fill: parent
+ color: "transparent"
+
+ ColumnLayout {
+ id: content
+ anchors {
+ left: parent.left
+ leftMargin: 10
+ right: parent.right
+ rightMargin: 10
+ top: parent.top
+ topMargin: 10
+ }
+
+ RowLayout {
+ Layout.fillWidth: true
+
+ Text {
+ Layout.fillWidth: true
+ text: "Notifications:"
+ // font: custom_font
+ color: text_color
+ }
+
+ Text {
+ text: "clear"
+ // font: custom_font
+ color: text_color
+
+ TapHandler {
+ id: tapHandler
+ gesturePolicy: TapHandler.ReleaseWithinBounds
+ onTapped: {
+ server.trackedNotifications.values.forEach((notification) => {
+ notification.tracked = false
+ })
+ notification_objects.forEach((object) => {
+ object.destroy();
+ })
+ notification_objects = [];
+ }
+ }
+
+ HoverHandler {
+ id: mouse
+ acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
+ cursorShape: Qt.PointingHandCursor
+ }
+ }
+ }
+ }
+ }
+
+ NotificationServer {
+ id: server
+ onNotification: (notification) => {
+ notification.tracked = true
+ console.log(JSON.stringify(notification));
+ var notification_component = Qt.createComponent("Notification.qml");
+ var notification_object = notification_component
+ .createObject(content,
+ {
+ id: notification.id,
+ body: notification.body,
+ summary: notification.summary,
+ // font: custom_font,
+ color: text_color,
+ margin: 10
+ }
+ )
+ if (notification_object == null) {
+ console.log("Error creating notification")
+ } else {
+ notification_objects.push(notification_object);
+ }
+ }
+ }
+}
+
diff --git a/quickshell/.config/quickshell/bar/Tooltip.qml b/quickshell/.config/quickshell/bar/Tooltip.qml
new file mode 100644
index 0000000..7ab247d
--- /dev/null
+++ b/quickshell/.config/quickshell/bar/Tooltip.qml
@@ -0,0 +1,89 @@
+import QtQuick
+import Quickshell
+import "root:/" // for Globals
+
+LazyLoader {
+ id: root
+
+ // The item to display the tooltip at. If set to null the tooltip will be hidden.
+ property Item relativeItem: null
+
+ // Tracks the item after relativeItem is unset.
+ property Item displayItem: null
+
+ property PopupContext popupContext: Globals.popupContext
+
+ property bool hoverable: false;
+ readonly property bool hovered: item?.hovered ?? false
+
+ // The content to show in the tooltip.
+ required default property Component contentDelegate
+
+ active: displayItem != null && popupContext.popup == this
+
+ onRelativeItemChanged: {
+ if (relativeItem == null) {
+ if (item != null) item.hideTimer.start();
+ } else {
+ if (item != null) item.hideTimer.stop();
+ displayItem = relativeItem;
+ popupContext.popup = this;
+ }
+ }
+
+ PopupWindow {
+ anchor {
+ window: root.displayItem.QsWindow.window
+ rect.y: anchor.window.height + 3
+ rect.x: anchor.window.contentItem.mapFromItem(root.displayItem, root.displayItem.width / 2, 0).x
+ edges: Edges.Top
+ gravity: Edges.Bottom
+ }
+
+ visible: true
+
+ property alias hovered: body.containsMouse;
+
+ property Timer hideTimer: Timer {
+ interval: 250
+
+ // unloads the popup by causing active to become false
+ onTriggered: root.popupContext.popup = null;
+ }
+
+ color: "transparent"
+
+ // don't accept mouse input if !hoverable
+ Region { id: emptyRegion }
+ mask: root.hoverable ? null : emptyRegion
+
+ width: body.implicitWidth
+ height: body.implicitHeight
+
+ MouseArea {
+ id: body
+
+ anchors.fill: parent
+ implicitWidth: content.implicitWidth + 10
+ implicitHeight: content.implicitHeight + 10
+
+ hoverEnabled: root.hoverable
+
+ Rectangle {
+ anchors.fill: parent
+
+ radius: 5
+ border.width: 1
+ color: palette.active.toolTipBase
+ border.color: palette.active.light
+
+ Loader {
+ id: content
+ anchors.centerIn: parent
+ sourceComponent: contentDelegate
+ active: true
+ }
+ }
+ }
+ }
+}
diff --git a/quickshell/.config/quickshell/bar/blocks/ActiveWorkspace.qml b/quickshell/.config/quickshell/bar/blocks/ActiveWorkspace.qml
new file mode 100644
index 0000000..22091f5
--- /dev/null
+++ b/quickshell/.config/quickshell/bar/blocks/ActiveWorkspace.qml
@@ -0,0 +1,33 @@
+import QtQuick
+import Quickshell.Io
+import Quickshell.Hyprland
+import "../"
+
+BarText {
+ text: {
+ var str = activeWindowTitle
+ return str.length > chopLength ? str.slice(0, chopLength) + '...' : str;
+ }
+
+ property int chopLength: 70
+ property string activeWindowTitle
+
+ Process {
+ id: titleProc
+ command: ["sh", "-c", "hyprctl activewindow | grep title: | sed 's/^[^:]*: //'"]
+ running: true
+
+ stdout: SplitParser {
+ onRead: data => activeWindowTitle = data
+ }
+ }
+
+ Component.onCompleted: {
+ Hyprland.rawEvent.connect(hyprEvent)
+ }
+
+ function hyprEvent(e) {
+ titleProc.running = true
+ }
+}
+
diff --git a/quickshell/.config/quickshell/bar/blocks/Battery.qml b/quickshell/.config/quickshell/bar/blocks/Battery.qml
new file mode 100644
index 0000000..10be0e0
--- /dev/null
+++ b/quickshell/.config/quickshell/bar/blocks/Battery.qml
@@ -0,0 +1,26 @@
+import QtQuick
+import Quickshell.Io
+import "../"
+
+BarBlock {
+ property string battery
+ text: battery
+
+ Process {
+ id: batteryProc
+ command: ["block_battery"]
+ running: true
+
+ stdout: SplitParser {
+ onRead: data => battery = data
+ }
+ }
+
+ Timer {
+ interval: 1000
+ running: true
+ repeat: true
+ onTriggered: batteryProc.running = true
+ }
+}
+
diff --git a/quickshell/.config/quickshell/bar/blocks/CPU.qml b/quickshell/.config/quickshell/bar/blocks/CPU.qml
new file mode 100644
index 0000000..b7bca8d
--- /dev/null
+++ b/quickshell/.config/quickshell/bar/blocks/CPU.qml
@@ -0,0 +1,37 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Quickshell
+import Quickshell.Io
+import "../"
+
+Item {
+ implicitHeight: text.implicitHeight
+ implicitWidth: text.implicitWidth
+
+ property string percentUsed
+
+ Process {
+ id: cpuProc
+ command: ["sh", "-c", "top -bn 1 | grep \"%Cpu(s)\" | awk '{usage=100-$8; printf \"%02d%%\\n\", usage}'"]
+ running: true
+
+ stdout: SplitParser {
+ onRead: data => percentUsed = data
+ }
+ }
+
+ Timer {
+ interval: 2000
+ running: true
+ repeat: true
+ onTriggered: cpuProc.running = true
+ }
+
+ BarText {
+ id: text
+ text: "%1
%2"
+ .arg(" ") // The rest of the string
+ .arg(percentUsed) // Symbol needs its own font
+ }
+}
diff --git a/quickshell/.config/quickshell/bar/blocks/Date.qml b/quickshell/.config/quickshell/bar/blocks/Date.qml
new file mode 100644
index 0000000..8b94919
--- /dev/null
+++ b/quickshell/.config/quickshell/bar/blocks/Date.qml
@@ -0,0 +1,8 @@
+import QtQuick
+import "../"
+
+BarBlock {
+ id: text
+ text: ` ${Datetime.date}`
+}
+
diff --git a/quickshell/.config/quickshell/bar/blocks/Datetime.qml b/quickshell/.config/quickshell/bar/blocks/Datetime.qml
new file mode 100644
index 0000000..743e785
--- /dev/null
+++ b/quickshell/.config/quickshell/bar/blocks/Datetime.qml
@@ -0,0 +1,31 @@
+pragma Singleton
+
+import Quickshell
+import Quickshell.Io
+import QtQuick
+
+Singleton {
+ property string time;
+ property string date;
+
+ Process {
+ id: dateProc
+ command: ["date", "+%a %e %b|%R"]
+ running: true
+
+ stdout: SplitParser {
+ onRead: data => {
+ date = data.split("|")[0]
+ time = data.split("|")[1]
+ }
+ }
+ }
+
+ Timer {
+ interval: 1000
+ running: true
+ repeat: true
+ onTriggered: dateProc.running = true
+ }
+}
+
diff --git a/quickshell/.config/quickshell/bar/blocks/Icon.qml b/quickshell/.config/quickshell/bar/blocks/Icon.qml
new file mode 100644
index 0000000..217e40c
--- /dev/null
+++ b/quickshell/.config/quickshell/bar/blocks/Icon.qml
@@ -0,0 +1,11 @@
+import QtQuick
+import QtQuick.Layouts
+import Quickshell
+import "../"
+
+BarBlock {
+ Layout.preferredWidth: 30
+ rightPadding: 5
+ text: ""
+}
+
diff --git a/quickshell/.config/quickshell/bar/blocks/Memory.qml b/quickshell/.config/quickshell/bar/blocks/Memory.qml
new file mode 100644
index 0000000..26900c0
--- /dev/null
+++ b/quickshell/.config/quickshell/bar/blocks/Memory.qml
@@ -0,0 +1,29 @@
+import QtQuick
+import QtQuick.Controls
+import Quickshell
+import Quickshell.Io
+import "../"
+
+BarBlock {
+ id: text
+ text: ` ${Math.floor(percentFree)}%`
+
+ property real percentFree
+
+ Process {
+ id: memProc
+ command: ["sh", "-c", "free | grep Mem | awk '{print $3/$2 * 100.0}'"]
+ running: true
+
+ stdout: SplitParser {
+ onRead: data => percentFree = data
+ }
+ }
+
+ Timer {
+ interval: 2000
+ running: true
+ repeat: true
+ onTriggered: memProc.running = true
+ }
+}
diff --git a/quickshell/.config/quickshell/bar/blocks/Notifications.qml b/quickshell/.config/quickshell/bar/blocks/Notifications.qml
new file mode 100644
index 0000000..3871cc4
--- /dev/null
+++ b/quickshell/.config/quickshell/bar/blocks/Notifications.qml
@@ -0,0 +1,34 @@
+import QtQuick
+import Quickshell.Services.Notifications
+import "../"
+
+BarBlock {
+ id: root
+ property bool showNotification: false
+
+ text: " " + notifServer.trackedNotifications.values.length
+ onClicked: function() {
+ showNotification = !showNotification
+ }
+
+ NotificationServer {
+ id: notifServer
+ onNotification: (notification) => {
+ notification.tracked = true
+ }
+ }
+
+ NotificationPanel {
+ text_color: root.color
+ visible: showNotification
+
+ anchors {
+ top: parent.top
+ }
+
+ margins {
+ top: 10
+ }
+ }
+}
+
diff --git a/quickshell/.config/quickshell/bar/blocks/Sound.qml b/quickshell/.config/quickshell/bar/blocks/Sound.qml
new file mode 100644
index 0000000..0553162
--- /dev/null
+++ b/quickshell/.config/quickshell/bar/blocks/Sound.qml
@@ -0,0 +1,15 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Quickshell
+import Quickshell.Services.Pipewire
+import "../"
+
+BarBlock {
+ id: text
+ text: ` ${Math.floor(sink?.audio.volume * 100)}%`
+
+ property PwNode sink: Pipewire.defaultAudioSink
+ PwObjectTracker { objects: [ sink ] }
+}
+
diff --git a/quickshell/.config/quickshell/bar/blocks/SystemTray.qml b/quickshell/.config/quickshell/bar/blocks/SystemTray.qml
new file mode 100644
index 0000000..12818f1
--- /dev/null
+++ b/quickshell/.config/quickshell/bar/blocks/SystemTray.qml
@@ -0,0 +1,74 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Quickshell
+import Quickshell.Widgets
+import Quickshell.Services.SystemTray
+import "../" as Bar
+
+RowLayout {
+ spacing: 5
+
+ Repeater {
+ model: SystemTray.items
+
+ MouseArea {
+ id: delegate
+ required property SystemTrayItem modelData
+ property alias item: delegate.modelData
+
+ Layout.fillHeight: true
+ implicitWidth: icon.implicitWidth + 5
+
+ acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
+ hoverEnabled: true
+
+ onClicked: event => {
+ if (event.button == Qt.LeftButton) {
+ item.activate();
+ } else if (event.button == Qt.MiddleButton) {
+ item.secondaryActivate();
+ } else if (event.button == Qt.RightButton) {
+ menuAnchor.open();
+ }
+ }
+
+ onWheel: event => {
+ event.accepted = true;
+ const points = event.angleDelta.y / 120
+ item.scroll(points, false);
+ }
+
+ IconImage {
+ id: icon
+ anchors.centerIn: parent
+ source: item.icon
+
+ implicitSize: 16
+ }
+
+ QsMenuAnchor {
+ id: menuAnchor
+ menu: item.menu
+
+ anchor.window: delegate.QsWindow.window
+ anchor.adjustment: PopupAdjustment.Flip
+
+ anchor.onAnchoring: {
+ const window = delegate.QsWindow.window;
+ const widgetRect = window.contentItem.mapFromItem(delegate, 0, delegate.height, delegate.width, delegate.height);
+
+ menuAnchor.anchor.rect = widgetRect;
+ }
+ }
+
+ Bar.Tooltip {
+ relativeItem: delegate.containsMouse ? delegate : null
+
+ Label {
+ text: delegate.item.tooltipTitle || delegate.item.id
+ }
+ }
+ }
+ }
+}
diff --git a/quickshell/.config/quickshell/bar/blocks/Test.qml b/quickshell/.config/quickshell/bar/blocks/Test.qml
new file mode 100644
index 0000000..14df09d
--- /dev/null
+++ b/quickshell/.config/quickshell/bar/blocks/Test.qml
@@ -0,0 +1,77 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Quickshell
+import Quickshell.Widgets
+import Quickshell.Services.SystemTray
+import "../"
+
+Repeater {
+ // model: SystemTray.items
+ model: ScriptModel {
+ values: SystemTray.items.values
+ .filter((item) => item.id == "nm-tray")
+ }
+
+
+ BarBlock {
+ id: block
+ Layout.preferredWidth: 30
+ leftPadding: 4
+ text: " "
+
+ required property SystemTrayItem modelData
+ property alias item: block.modelData
+
+ MouseArea {
+ id: delegate
+ anchors.fill: block
+
+ property alias item: block.item
+
+ acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
+ hoverEnabled: true
+
+ onClicked: event => {
+ if (event.button == Qt.LeftButton) {
+ item.activate();
+ } else if (event.button == Qt.MiddleButton) {
+ item.secondaryActivate();
+ } else if (event.button == Qt.RightButton) {
+ menuAnchor.open();
+ }
+ }
+
+ onWheel: event => {
+ event.accepted = true;
+ const points = event.angleDelta.y / 120
+ item.scroll(points, false);
+ }
+
+ QsMenuAnchor {
+ id: menuAnchor
+ menu: item.menu
+
+ anchor.window: delegate.QsWindow.window
+ anchor.adjustment: PopupAdjustment.Flip
+
+ anchor.onAnchoring: {
+ console.log("here2")
+ const window = delegate.QsWindow.window;
+ const widgetRect = window.contentItem.mapFromItem(delegate, 0, delegate.height, delegate.width, delegate.height);
+
+ menuAnchor.anchor.rect = widgetRect;
+ }
+ }
+
+ Tooltip {
+ relativeItem: delegate.containsMouse ? delegate : null
+
+ Label {
+ text: delegate.item.tooltipTitle || delegate.item.id
+ }
+ }
+ }
+ }
+}
+
diff --git a/quickshell/.config/quickshell/bar/blocks/Time.qml b/quickshell/.config/quickshell/bar/blocks/Time.qml
new file mode 100644
index 0000000..770403d
--- /dev/null
+++ b/quickshell/.config/quickshell/bar/blocks/Time.qml
@@ -0,0 +1,8 @@
+import QtQuick
+import "../"
+
+BarBlock {
+ id: text
+ text: ` ${Datetime.time}`
+}
+
diff --git a/quickshell/.config/quickshell/bar/blocks/Workspaces.qml b/quickshell/.config/quickshell/bar/blocks/Workspaces.qml
new file mode 100644
index 0000000..5d32c48
--- /dev/null
+++ b/quickshell/.config/quickshell/bar/blocks/Workspaces.qml
@@ -0,0 +1,63 @@
+import QtQuick
+import QtQuick.Layouts
+import Quickshell
+import Quickshell.Hyprland
+import "root:/"
+import "../"
+
+RowLayout {
+ spacing: 0
+ property HyprlandMonitor monitor: Hyprland.monitorFor(screen)
+
+ Repeater {
+ model: ScriptModel {
+ values: {
+ var seenEmpty = false
+ return [...Hyprland.workspaces.values]
+ .filter((ws) => {
+ if (ws.monitor !== monitor || ws.name.includes("special"))
+ return false
+
+ // There is a flickering that can happen when switching from one
+ // empty workspace to another where both empty workspaces are shown
+ // on the bar at the same time. This ensures that only the first
+ // empty workspace is shown.
+ const isNumeric = /^\d+$/.test(ws.name);
+ if (!isNumeric)
+ return true;
+ if (!seenEmpty) {
+ seenEmpty = true
+ return true
+ }
+ return false;
+ })
+ // Sort workspaces by id
+ .sort((a, b) => a.id - b.id)
+ }
+ }
+
+ BarBlock {
+ property HyprlandWorkspace ws: modelData
+ property bool isActive: Hyprland.focusedMonitor?.activeWorkspace?.id === ws.id
+ property bool isOpen: monitor.activeWorkspace?.id === ws.id
+ property bool hasClients: ws.name.length > 2
+
+ dim: true
+ underline: isActive || isOpen
+ onClicked: function() {
+ Hyprland.dispatch(`workspace ${ws.id}`);
+ }
+ leftPadding: hasClients ? 2 : 0
+ text: {
+ if (isActive) {
+ if (!hasClients)
+ return `${ws.name}`
+ var split_i = ws.id > 9 ? 3 : 2
+ return `${ws.name.slice(0, split_i)}${ws.name.slice(split_i)}`
+ }
+ return ws.name
+ }
+ }
+ }
+}
+
diff --git a/quickshell/.config/quickshell/shell.qml b/quickshell/.config/quickshell/shell.qml
new file mode 100644
index 0000000..6d8d8da
--- /dev/null
+++ b/quickshell/.config/quickshell/shell.qml
@@ -0,0 +1,8 @@
+//@ pragma UseQApplication
+import Quickshell
+import "bar" as Bar
+
+ShellRoot {
+ Bar.Bar {}
+}
+