Add eww-hyprland package definition

This commit is contained in:
Evie Litherland-Smith 2023-07-12 16:42:58 +01:00
parent c8965eb754
commit 9301993582
34 changed files with 1806 additions and 0 deletions

View file

@ -0,0 +1,69 @@
# Eww configuration
This configuration aims to provide a shell replacement for compositors/window
managers. Features constantly get added and existing ones get improved.
## 🗃️ Components
The same daemon runs multiple windows which interact with each other:
### bar
![bar](https://github.com/fufexan/dotfiles/assets/36706276/c3339908-029c-4e56-88c5-e620dc8ce00d)
### music window
![music](https://github.com/fufexan/dotfiles/assets/36706276/4260362b-8c69-417e-94c0-1436dc9febf9)
### calendar
![calendar](https://github.com/fufexan/dotfiles/assets/36706276/ddf2a40d-f758-4072-ac14-2c254cb9393a)
### system info
![system](https://github.com/fufexan/dotfiles/assets/36706276/723fd8fe-538c-41a5-bcbf-218304dc3bdf)
## ❔ Usage
### Home Manager
If you use Home Manager, installing is as simple as adding my flake to your
inputs, passing `inputs` to `extraSpecialArgs` and importing the relevant
module:
```nix
{inputs, pkgs, ...}: {
imports = [inputs.fufexan.homeManagerModules.eww-hyprland];
programs.eww-hyprland = {
enable = true;
# default package
package = pkgs.eww-wayland;
# if you want to change colors
colors = builtins.readFile ./latte.scss;
# set to true to reload on change
autoReload = false;
};
}
```
Make sure to also add the fonts listed below. You can simply search where
they are in my config.
### Other distros
To quickly install this config, grab all the files in this directory and put
them in `~/.config/eww`. Then run `eww daemon` and `eww open bar`. Enjoy!
Dependencies:
- Icon fonts: [Material Symbols Outlined](https://github.com/google/material-design-icons/tree/master/variablefont)
(any variation can be used as long as you change the `font-family` property of `.icon`)
- Text font: [Product Sans](https://befonts.com/product-sans-font.html)
- Script deps: everything in `default.nix`'s `dependencies` list.
## 🎨 Theme
The theme colors can be changed in `css/_colors.scss`. Currently the theme used
is [Catppuccin Mocha](https://github.com/catppuccin/catppuccin).

View file

@ -0,0 +1,29 @@
.calendar-win {
padding: .2em;
@include window;
}
calendar {
padding: 5px;
:selected {
color: $mauve;
}
.header {
color: $subtext1;
}
.highlight {
color: $maroon;
font-weight: bold;
}
.button {
color: $sapphire;
}
:indeterminate {
color: $overlay0;
}
}

View file

@ -0,0 +1,35 @@
$rosewater: #f5e0dc;
$flamingo: #f2cdcd;
$pink: #f5c2e7;
$mauve: #cba6f7;
$red: #f38ba8;
$maroon: #eba0ac;
$peach: #fab387;
$yellow: #f9e2af;
$green: #a6e3a1;
$teal: #94e2d5;
$sky: #89dceb;
$sapphire: #74c7ec;
$blue: #89b4fa;
$lavender: #b4befe;
$text: #cdd6f4;
$subtext1: #bac2de;
$subtext0: #a6adc8;
$overlay2: #9399b2;
$overlay1: #7f849c;
$overlay0: #6c7086;
$surface2: #585b70;
$surface1: #45475a;
$surface0: #313244;
$base: #1e1e2e;
$mantle: #181825;
$crust: #11111b;
$fg: $text;
$bg: rgba(30, 30, 46, 0.6);
$bg1: rgba(49, 50, 68, 0.6);
$border: #28283d;
$shadow: $crust;

View file

@ -0,0 +1,26 @@
/* screenshare & mic share indicators */
.indicators {
margin: .3rem;
transition: 500ms ease-out;
:first-child {
border-radius: 1rem 0 0 1rem;
}
:last-child {
border-radius: 0 1rem 1rem 0;
}
:only-child {
border-radius: 1rem;
}
}
.indicator {
color: $base;
padding: 0 .3rem;
}
.screenshare { background: $red; }
.micshare { background: $green; }
.camerashare { background: $blue; }

View file

@ -0,0 +1,71 @@
/* module cover art */
.song-cover-art {
background-position: center;
background-size: cover;
margin: 4px 5px 4px 0;
min-height: 24px;
min-width: 24px;
@include rounding;
}
/* music window */
.music-window {
border: 1px solid $border;
@include window;
}
/* cover, left child */
.music-cover-art {
background-position: center;
background-size: cover;
border-radius: 8px;
box-shadow: 0 1px 2px -1px rgba(0,0,0,0.4);
margin: 1em;
min-height: 170px;
min-width: 170px;
}
/* info box, right child */
.music-box {
margin: 1rem 1rem 1rem 0;
}
.music-title {
font-size: 2rem;
font-weight: bold;
margin: .5rem;
}
.dark { color: $base; }
.light { color: $text; }
.music-artist {
margin: 1rem;
}
.music-button-box {
margin: 1rem 0 0;
}
.music-button label {
font-size: 2rem;
}
.music-time {
margin: 0 1rem;
}
.music-bar scale {
highlight {
background-image: linear-gradient(to right, $teal, $sky);
border-radius: 24px;
}
trough {
background-color: $bg1;
border-radius: 24px;
margin-top: 0;
min-height: 10px;
min-width: 170px;
}
}

View file

@ -0,0 +1,36 @@
.osd-part {
margin: 0 .5rem .5rem;
@include window;
label {
color: $text;
font-size: 2rem;
margin: 0 .1rem 0 .2rem;
}
scale {
margin: -.2rem 0;
}
trough {
@include rounding;
background-color: $bg1;
margin: 1rem 0 .5rem;
min-height: 10rem;
min-width: .7rem;
highlight {
@include rounding;
min-width: 0;
}
}
}
/* scale highlight gradients for volume & brightness */
.osd-volume highlight {
background-image: linear-gradient(to top, $teal, $sky);
}
.osd-brightness highlight {
background-image: linear-gradient(to top, $yellow, $peach);
}

View file

@ -0,0 +1,137 @@
/* system menu */
.system-menu-box { @include window; }
/* between net/bt and their arrows */
.separator {
font-size: 1rem;
}
/* date & time */
.top-row {
margin: 1rem 1.5rem 0;
.time { font-size: 2rem; }
.date-box {
margin: 0 1rem;
label { font-size: 1.1rem; }
.date {
background: unset;
margin: 0 .5rem 0 0;
padding: 0;
}
}
}
/* wifi/bt/airplane button boxes */
.system-row {
margin: .5rem .7rem;
/* the airplane icon is not properly spaced */
.airplane-box button {
padding: 1rem 3rem;
}
label {
font-size: 1rem;
margin: 0 .1rem;
}
}
/* wifi/bt/airplane buttons */
.element {
@include rounding;
background-color: $bg1;
margin: .3rem;
button {
@include rounding;
padding: 1rem;
label {
font-size: 1.5rem;
}
&:hover {
background-color: rgba(255, 255, 255, 0.1);
}
}
}
/* volume & brightness sliders */
.sliders {
@include rounding;
background-color: $bg1;
margin: .5rem 1rem;
padding: .6rem 1rem;
scale {
margin-right: -1rem;
min-width: 21.5rem;
trough { margin-right: 0; }
}
box { margin: .2rem 0; }
label { font-size: 1.2rem; }
}
.volume-slider-box,
.brightness-slider-box {
trough { background-color: $base; }
}
.volume-bar highlight {
@include rounding;
background-image: linear-gradient(to right, $teal, $sky);
}
.brightness-slider-box scale highlight {
@include rounding;
background-image: linear-gradient(to right, $yellow, $peach);
}
/* battery & power button */
.bottom-row {
margin: .5rem 1rem;
.battery-icon {
color: $green;
font-size: 2rem;
}
.battery-icon .low { color: $red; }
.battery-rate { color: $mauve; }
.battery-status {
color: $subtext0;
margin: 0 .5rem;
}
button {
background-color: $bg1;
border-radius: 50%;
margin-bottom: .1rem;
padding: 0 .5rem;
label { font-size: 1.5rem; }
&:hover { background-color: $bg1; }
}
}
/* set wifi/bt colors and dim buttons when hovering */
.element {
button:hover { background-color: rgba(0, 0, 0, .1); }
}
.bt-connected {
background-color: $blue;
color: $crust;
}
.net-connected {
background-color: $mauve;
color: $crust;
}

View file

@ -0,0 +1,64 @@
/* right modules (circular-progress) */
.cpubar { color: $blue; }
.membar { color: $peach; }
.batbar { color: $green; }
.batbar .low { color: $red; }
.cpubar,
.membar,
.batbar { background-color: $bg1; }
.iconmem { color: $peach; }
.iconcpu { color: $blue; }
.icon-text {
font-size: 3rem;
padding: .7rem;
}
/* system box cpu & memory info */
.sys-text-sub {
color: $text;
}
.sys-text-mem,
.sys-text-cpu {
font-size: 1rem;
font-weight: bold;
}
.sys-icon-mem,
.sys-icon-cpu {
font-size: 1.5rem;
margin: 1.5rem;
}
.system-info-box {
@include rounding;
background-color: $bg1;
margin: .5rem 1rem;
padding: .5rem;
}
.sys-mem,
.sys-cpu {
background-color: $bg;
}
.sys-icon-mem,
.sys-text-mem,
.sys-mem {
color: $peach;
}
.sys-icon-cpu,
.sys-text-cpu,
.sys-cpu {
color: $blue;
}
.sys-box {
margin: .3em;
box { margin-left: 1rem; }
}

View file

@ -0,0 +1,6 @@
/* bar volume module */
.vol-icon { color: $green; }
.volbar highlight {
background-image: linear-gradient(to right, $teal, $sky);
border-radius: 10px;
}

View file

@ -0,0 +1,127 @@
{
config,
pkgs,
lib,
...
}: let
cfg = config.programs.eww-hyprland;
dependencies = with pkgs; [
cfg.package
config.wayland.windowManager.hyprland.package
bash
blueberry
bluez
brillo
coreutils
dbus
findutils
gawk
gnome.gnome-control-center
gnused
imagemagick
jaq
jc
libnotify
material-symbols
networkmanager
pavucontrol
playerctl
procps
pulseaudio
ripgrep
socat
udev
upower
util-linux
wget
wireplumber
wlogout
];
reload_script = pkgs.writeShellScript "reload_eww" ''
windows=$(eww windows | rg '\*' | tr -d '*')
systemctl --user restart eww.service
echo $windows | while read -r w; do
eww open $w
done
'';
in {
options.programs.eww-hyprland = {
enable = lib.mkEnableOption "eww Hyprland config";
package = lib.mkOption {
type = with lib.types; nullOr package;
default = pkgs.eww-wayland;
defaultText = lib.literalExpression "pkgs.eww-wayland";
description = "Eww package to use.";
};
autoReload = lib.mkOption {
type = lib.types.bool;
default = false;
defaultText = lib.literalExpression "false";
description = "Whether to restart the eww daemon and windows on change.";
};
colors = lib.mkOption {
type = with lib.types; nullOr lines;
default = null;
defaultText = lib.literalExpression "null";
description = ''
SCSS file with colors defined in the same way as Catppuccin colors are,
to be used by eww.
Defaults to Catppuccin Mocha.
'';
};
};
config = lib.mkIf cfg.enable {
home.packages = [cfg.package];
# remove nix files
xdg.configFile."eww" = {
source = lib.cleanSourceWith {
filter = name: _type: let
baseName = baseNameOf (toString name);
in
!(lib.hasSuffix ".nix" baseName) && (baseName != "_colors.scss");
src = lib.cleanSource ./.;
};
# links each file individually, which lets us insert the colors file separately
recursive = true;
onChange =
if cfg.autoReload
then reload_script.outPath
else "";
};
# colors file
xdg.configFile."eww/css/_colors.scss".text =
if cfg.colors != null
then cfg.colors
else (builtins.readFile ./css/_colors.scss);
systemd.user.services.eww = {
Unit = {
Description = "Eww Daemon";
# not yet implemented
# PartOf = ["tray.target"];
PartOf = ["graphical-session.target"];
};
Service = {
Environment = "PATH=/run/wrappers/bin:${lib.makeBinPath dependencies}";
ExecStart = "${cfg.package}/bin/eww daemon --no-daemonize";
Restart = "on-failure";
};
Install.WantedBy = ["graphical-session.target"];
};
};
}

106
pkgs/eww-hyprland/eww.scss Normal file
View file

@ -0,0 +1,106 @@
@import 'css/colors';
@mixin rounding {
border-radius: 16px;
}
@mixin window {
background-color: $bg;
background-position: center;
background-size: cover;
border: 1px solid rgba(0,0,0,0.3);
color: $fg;
margin: 5px 5px 10px;
@include rounding;
}
* {
all: unset;
font-family: "Product Sans", Roboto, sans-serif;
transition: 200ms ease;
}
@import 'css/calendar';
@import 'css/indicators';
@import 'css/music';
@import 'css/osd';
@import 'css/sidebar';
@import 'css/system';
@import 'css/volume';
.bar0,
.bar1,
.bar2 {
background-color: $bg;
color: $fg;
label {
font-size: 1.2rem;
}
.text {
margin-top: .2rem;
}
}
tooltip {
background: $bg;
border: 1px solid $border;
border-radius: 8px;
label {
font-size: 1rem;
}
}
/* icon font */
.icon,
.icon label { font-family: Material Symbols Outlined; }
.module { margin: 0 5px; }
/* date & time */
.hour {
font-weight: bold;
padding-left: 5px;
}
.minute {
padding-right: .7rem;
}
.date {
color: $flamingo;
label {
font-size: 1.2rem;
}
}
/* right-aligned modules */
.bright-icon { color: $yellow; }
.module-bt { font-size: 1.2rem; }
/* scales */
scale trough {
background-color: $bg1;
border-radius: 24px;
margin: 0 1rem;
min-height: 10px;
min-width: 70px;
}
/* workspaces */
.focused {
background-color: $bg;
border-radius: 1rem;
margin: .3rem;
padding: .25rem;
}
.workspaces { margin-left: 10px; }
.ws {
border-radius: 2rem;
margin: .7rem .25rem;
}

View file

@ -0,0 +1,83 @@
; all variables and listeners
(include "./variables.yuck")
; various bar modules
(include "./modules/bluetooth.yuck")
(include "./modules/bright.yuck")
(include "./modules/clock.yuck")
(include "./modules/indicators.yuck")
(include "./modules/music.yuck")
(include "./modules/net.yuck")
(include "./modules/sys.yuck")
(include "./modules/volume.yuck")
(include "./modules/workspaces.yuck")
; individual windows; bar is in this file
(include "./windows/calendar.yuck")
(include "./windows/music.yuck")
(include "./windows/osd.yuck") ; on-screen-display (volume/brightness)
(include "./windows/system-menu.yuck")
; bar modules alignment
(defwidget left []
(box
:space-evenly false
:halign "start"
(workspaces)))
(defwidget center []
(box
:space-evenly false
:halign "center"
(music-module)))
(defwidget right []
(box
:space-evenly false
:halign "end"
(indicators)
(bright)
(volume-module)
(bluetooth)
(net)
(sys)
(clock_module)))
(defwidget bar-box []
(centerbox
(left)
(center)
(right)))
(defwindow bar0
:monitor 0
:geometry (geometry :x "0%"
:y "0%"
:width "100%"
:height "32px"
:anchor "top center")
:stacking "fg"
:exclusive true
(bar-box))
(defwindow bar1
:monitor 1
:geometry (geometry :x "0%"
:y "0%"
:width "100%"
:height "32px"
:anchor "top center")
:stacking "fg"
:exclusive true
(bar-box))
(defwindow bar2
:monitor 2
:geometry (geometry :x "0%"
:y "0%"
:width "100%"
:height "32px"
:anchor "top center")
:stacking "fg"
:exclusive true
(bar-box))

View file

@ -0,0 +1,8 @@
(defwidget bluetooth []
(button
:class "module-bt module icon"
:onclick "blueberry" ; clicking opens blueberry
:onrightclick "scripts/bluetooth toggle" ; rightclick to turn bluetooth on/off
:tooltip "${bluetooth.text} ${bluetooth.battery}"
:style "color: ${bluetooth.color};"
{bluetooth.icon}))

View file

@ -0,0 +1,10 @@
(defwidget bright []
(box
:class "module"
(eventbox
; scroll to change the brightness level
:onscroll "echo {} | sed -e 's/up/-U 1/g' -e 's/down/-A 1/g' | xargs brillo -S"
(label
:text {brightness.icon}
:class "bright-icon icon"
:tooltip "brightness ${round(brightness.percent, 0)}%"))))

View file

@ -0,0 +1,30 @@
(defvar date_rev false)
(defwidget clock_module []
(eventbox
:onhover "${EWW_CMD} update date_rev=true"
:onhoverlost "${EWW_CMD} update date_rev=false"
(overlay
:class "module"
; the styles make the clock invisible while hovered, and the date is shown instead
(box
:space-evenly false
:class "text"
(label
:text {formattime(EWW_TIME, "%H")}
:style {date_rev ? "color: rgba(0,0,0,0); text-shadow: none;" : ""}
:class "hour")
(label
:style {date_rev ? "color: rgba(0,0,0,0); text-shadow: none;" : ""}
:text ":")
(label
:text {formattime(EWW_TIME, "%M")}
:style {date_rev ? "color: rgba(0,0,0,0); text-shadow: none;" : ""}
:class "minute"))
(revealer
:reveal date_rev
:transition "crossfade"
(button
:class "date text"
:onclick "${EWW_CMD} open --toggle calendar"
{formattime(EWW_TIME, "%d/%m")})))))

View file

@ -0,0 +1,10 @@
; screenshare & mic share indicators
(defwidget indicators []
(box
:space-evenly: false
:class "indicators"
(for i in indicators
(label
:class "icon indicator ${i.class}"
:tooltip "${i.name} is being shared"
:text {i.icon}))))

View file

@ -0,0 +1,24 @@
(defwidget music-module []
(eventbox
:onhover "${EWW_CMD} update music_reveal=true"
:onhoverlost "${EWW_CMD} update music_reveal=false"
(box
:class "module"
:space-evenly false
(box
:class "song-cover-art"
:style "background-image: url(\"${music.cover}\");")
(button
:class "module text"
:onclick "${EWW_CMD} open --toggle music"
{music.title})
; show buttons when hovering
(revealer
:transition "slideright"
:reveal music_reveal
:duration "350ms"
(box
:class "icon text"
(button :class "song-button" :onclick "playerctl previous" "")
(button :class "song-button" :onclick "playerctl play-pause" {music.status})
(button :class "song-button" :onclick "playerctl next" ""))))))

View file

@ -0,0 +1,8 @@
(defwidget net []
(button
:class "module icon"
; forcefully open gnome-control-center in a non-GNOME environment
:onclick "XDG_CURRENT_DESKTOP=GNOME gnome-control-center &"
:tooltip {net.name}
:style "color: ${net.color};"
{net.icon}))

View file

@ -0,0 +1,31 @@
(defwidget sys []
(box
:class "module"
:space-evenly false
:spacing 5
(circular-progress
:value {EWW_CPU.avg}
:class "cpubar"
:thickness 3
(button
:tooltip "using ${round(EWW_CPU.avg,0)}% cpu"
:onclick "${EWW_CMD} open --toggle system-menu"
(label :class "icon-text" :text "")))
(circular-progress
:value {EWW_RAM.used_mem_perc}
:class "membar"
:thickness 3
(button
:tooltip "using ${round(EWW_RAM.used_mem_perc,0)}% ram"
:onclick "${EWW_CMD} open --toggle system-menu"
(label :class "icon-text" :text "")))
(circular-progress
:value {EWW_BATTERY.total_avg}
:class "batbar ${EWW_BATTERY.total_avg > 20 ? '' : 'low'}"
:thickness 3
(button
:tooltip "battery on ${round(EWW_BATTERY.total_avg,0)}%"
:onclick "${EWW_CMD} open --toggle system-menu"
(label :class "icon-text" :text "")))))

View file

@ -0,0 +1,13 @@
(defwidget volume-module []
(box
:class "module icon"
(eventbox
; scroll over the icon to change volume
; low increment so that scrolling with a touchpad doesn't shatter your eardrums
:onscroll "echo {} | sed -e 's/up/-/g' -e 's/down/+/g' | xargs -I% wpctl set-volume @DEFAULT_AUDIO_SINK@ 0.005%"
:onclick "pavucontrol &" ; open pavucontrol on click
:onrightclick "scripts/volume mute SINK" ; toggle mute on right click
(label
:class "vol-icon"
:tooltip "volume ${volume.percent}%"
:text {volume.icon}))))

View file

@ -0,0 +1,15 @@
(defwidget workspaces []
(eventbox
; scroll to change workspace
:onscroll "echo {} | sed -e \"s/up/-1/g\" -e \"s/down/+1/g\" | xargs hyprctl dispatch workspace"
(box
:class "module workspaces"
(for ws in {hyprland.workspaces}
(button
:onclick "hyprctl dispatch workspace ${ws.number}"
:class "ws icon"
:style "background-color: ${ws.color};"
; :tooltip {ws.tooltip}
(box
:class `${ws.focused ? "focused" : ""}`
:height 3))))))

View file

@ -0,0 +1,40 @@
#!/usr/bin/env bash
# shellcheck source=/dev/null
source "$(dirname "$0")"/utils
STATUS="$(rfkill list | sed -n 2p | awk '{print $3}')"
icon() {
[ "$STATUS" = "no" ] && echo "" || echo ""
}
toggle() {
if [ "$STATUS" = "no" ]; then
rfkill block all
notify-send --urgency=normal -i airplane-mode-symbolic "Airplane Mode" "Airplane mode has been turned on!"
else
rfkill unblock all
notify-send --urgency=normal -i airplane-mode-disabled-symbolic "Airplane Mode" "Airplane mode has been turned off!"
fi
}
if [ "$1" = "toggle" ]; then
toggle
else
last_time=$(get_time_ms)
STATUS="$(rfkill list | sed -n 2p | awk '{print $3}')"
icon
rfkill event | while read -r _; do
current_time=$(get_time_ms)
delta=$((current_time - last_time))
# 50ms debounce
if [[ $delta -gt 50 ]]; then
STATUS="$(rfkill list | sed -n 2p | awk '{print $3}')"
icon
# reset debounce timer
last_time=$(get_time_ms)
fi
done
fi

View file

@ -0,0 +1,66 @@
#!/usr/bin/env bash
# shellcheck source=/dev/null
source "$(dirname "$0")"/utils
toggle() {
status=$(rfkill -J | jaq -r '.rfkilldevices[] | select(.type == "bluetooth") | .soft' | head -1)
if [ "$status" = "unblocked" ]; then
rfkill block bluetooth
else
rfkill unblock bluetooth
fi
}
gen_output() {
powered=$(bluetoothctl show | rg Powered | cut -f 2- -d ' ')
status=$(bluetoothctl info)
name=$(echo "$status" | rg Name | cut -f 2- -d ' ')
mac=$(echo "$status" | head -1 | awk '{print $2}' | tr ':' '_')
if [[ "$(echo "$status" | rg Percentage)" != "" ]]; then
battery="$(upower -i /org/freedesktop/UPower/devices/headset_dev_"$mac" | rg percentage | awk '{print $2}' | cut -f 1 -d '%')%"
else
battery=""
fi
if [ "$powered" = "yes" ]; then
if [ "$status" != "Missing device address argument" ]; then
icon=""
text="$name"
color="#89b4fa"
class="bt-connected"
else
icon=""
text="Disconnected"
color="#45475a"
class=""
fi
else
icon=""
text="Bluetooth off"
color="#45475a"
class=""
fi
echo '{ "icon": "'"$icon"'", "battery": "'"$battery"'", "text": "'"$text"'", "color": "'"$color"'", "class": "'"$class"'" }'
}
if [ "$1" = "toggle" ]; then
toggle
else
gen_output
last_time=$(get_time_ms)
udevadm monitor | while read -r _; do
current_time=$(get_time_ms)
delta=$((current_time - last_time))
# 50ms debounce
if [[ $delta -gt 50 ]]; then
gen_output
# reset debounce timer
last_time=$(get_time_ms)
fi
done
fi

View file

@ -0,0 +1,33 @@
#!/usr/bin/env bash
# shellcheck source=/dev/null
source "$(dirname "$0")"/utils
icons=("" "" "")
date="$XDG_CACHE_HOME/eww/osd_brightness.date"
gen_output() {
percent="$(brillo)"
icon="${icons[$(awk -v n="$percent" 'BEGIN{print int(n/34)}')]}"
echo '{"percent": "'"$percent"'", "icon": "'"$icon"'"}'
}
if [ "$1" = "osd" ]; then
osd "$date"
else
# initial
last_time=$(get_time_ms)
gen_output
osd_handler "osd-brightness" &
udevadm monitor | rg --line-buffered "backlight" | while read -r _; do
current_time=$(get_time_ms)
delta=$((current_time - last_time))
if [[ $delta -gt 50 ]]; then
gen_output
# reset debounce timer
last_time=$(get_time_ms)
fi
done
fi

View file

@ -0,0 +1,94 @@
#!/usr/bin/env bash
# define colors
# red peach green blue
colors=("#f38ba8" "#fab387" "#a6e3a1" "#89b4fa")
empty="rgba(30, 30, 46, 0.6)"
# get initial focused workspace
focusedws=$(hyprctl -j monitors | jaq -r '.[] | select(.focused == true) | .activeWorkspace.id')
declare -A o=([1]=0 [2]=0 [3]=0 [4]=0 [5]=0 [6]=0 [7]=0 [8]=0 [9]=0 [10]=0)
declare -A monitormap
declare -A workspaces
# set color for each workspace
status() {
if [ "${o[$1]}" -eq 1 ]; then
mon=${monitormap[${workspaces[$1]}]}
echo -n "${colors[$mon]}"
else
echo -n "$empty"
fi
}
# handle workspace create/destroy
workspace_event() {
while read -r k v; do workspaces[$k]="$v"; done < <(hyprctl -j workspaces | jaq -jr '.[] | .id, " ", .monitor, "\n"')
}
# handle monitor (dis)connects
monitor_event() {
while read -r k v; do monitormap["$k"]=$v; done < <(hyprctl -j monitors | jaq -jr '.[] | .name, " ", .id, "\n"')
}
# get all apps titles in a workspace
applist() {
ws="$1"
apps=$(hyprctl -j clients | jaq -jr '.[] | select(.workspace.id == '"$ws"') | .title + "\\n"')
echo -En "${apps%"\n"}"
}
# generate the json for eww
generate() {
echo -n '{"workspaces": ['
for i in {1..10}; do
echo -n ''"$([ "$i" -eq 1 ] || echo ,)" '{"number": '"$i"', "color": "'"$(status "$i")"'", "focused": '"$([ "$focusedws" = "$i" ] && echo "true" || echo "false")"'}' #, "tooltip": "'$(applist "$i")'" }'
done
echo '], "screencast": '"$screencast"'}'
}
# setup
# add monitors
monitor_event
# add workspaces
workspace_event
# screen is not shared by default
screencast=false
# check occupied workspaces
for num in "${!workspaces[@]}"; do
o[$num]=1
done
# generate initial widget
generate
# main loop
socat -u UNIX-CONNECT:/tmp/hypr/"$HYPRLAND_INSTANCE_SIGNATURE"/.socket2.sock - | rg --line-buffered "workspace|mon(itor)?|screencast" | while read -r line; do
case ${line%>>*} in
"workspace")
focusedws=${line#*>>}
;;
"focusedmon")
focusedws=${line#*,}
;;
"createworkspace")
o[${line#*>>}]=1
;;
"destroyworkspace")
o[${line#*>>}]=0
;;
"monitor"*)
monitor_event
;;
"screencast")
screencast=$([ "$(echo "${line#*>>}" | awk -F, '{print $1}')" -eq 1 ] && echo true || echo false)
;;
esac
generate
done

View file

@ -0,0 +1,41 @@
#!/usr/bin/env bash
prevMic=""
prevScreen=""
gen_output() {
mic=$(eww get volume)
# no standard way of getting camera info
# camerareveal=$(eww get camerareveal)
screen=$(eww get hyprland)
if [ "$screen" != "$prevScreen" ] || [ "$mic" != "$prevMic" ]; then
micreveal=$(echo "$mic" | jaq -r '.in_use')
screenreveal=$(echo "$screen" | jaq -r '.screencast')
json='['
if [ "$micreveal" -ge 1 ]; then
json+='{"name": "Microphone", "icon": "", "class": "micshare"}'
fi
if [ "$screenreveal" = "true" ]; then
json+="$([ "$json" == "[" ] || echo ',')"'{"name": "Screen", "icon": "", "class": "screenshare"}'
fi
# if [ "$camerareveal" = "true" ]; then
# json+='{"name": "Camera", "icon": "", "class": "camerashare"}'
# fi
json+=']'
echo "$json"
prevMic="$mic"
prevScreen="$screen"
fi
}
gen_output
sleep 1
while true; do
gen_output
sleep 1
done

86
pkgs/eww-hyprland/scripts/net Executable file
View file

@ -0,0 +1,86 @@
#!/usr/bin/env bash
# Script to listen for networkmanager updates and return an appriopriate icon and tooltip.
# This script runs until it's manually terminated.
# Requires: NetworkManager, gawk
# Usage: networkmanager
# shellcheck source=/dev/null
source "$(dirname "$0")"/utils
# get initialize network device info and states
nm="$(nmcli d | jc --nmcli | jaq -r '.[] | select(.type | test("^(wifi|ethernet)$", "ix"))')"
icons=("" "" "" "" "")
function toggle() {
status=$(rfkill -J | jaq -r '.rfkilldevices[] | select(.type == "wlan") | .soft' | head -1)
if [ "$status" = "unblocked" ]; then
rfkill block wlan
else
rfkill unblock wlan
fi
}
function gen_wifi() {
signal=$(nmcli -t -f in-use,ssid,signal dev wifi | rg '\*' | sed 's/\"/\\"/g' | awk -F: '{print $3}')
level=$(awk -v n="$signal" 'BEGIN{print int((n-1)/20)}')
if [ "$level" -gt 4 ]; then
level=4
fi
icon=${icons[$level]}
color="#cba6f7"
class="net-connected"
name=$(echo "$nm" | jaq -r 'select(.type == "wifi") .connection')
}
function gen_ethernet() {
icon=""
class="net-connected"
color="#cba6f7"
name=$(echo "$nm" | jaq -r 'select(.type == "ethernet") .connection')
}
function make_content() {
local ethernet wifi
ethernet=$(echo "$nm" | jaq -r 'select(.type == "ethernet") .state')
wifi=$(echo "$nm" | jaq -r 'select(.type == "wifi") .state')
# test ethernet first
if [[ $ethernet == "connected" ]]; then
gen_ethernet
elif [[ $wifi == "connected" ]]; then
gen_wifi
else
icon=""
color="#988ba2"
class="net-disconnected"
name="Disconnected"
fi
jaq --null-input -r -c \
--arg icon "$icon" \
--arg name "$name" \
--arg color "$color" \
--arg class "$class" \
'{"icon": $icon, "name": $name, "color": $color, "class": $class}'
}
# first run
make_content
last_time=$(get_time_ms)
nmcli monitor | while read -r _; do
current_time=$(get_time_ms)
delta=$((current_time - last_time))
# 50ms debounce
if [[ $delta -gt 50 ]]; then
sleep 0.5
nm="$(nmcli d | jc --nmcli | jaq -r '.[] | select(.type | test("^(wifi|ethernet)$", "ix"))')"
make_content
# reset debounce timer
last_time=$(get_time_ms)
fi
done

View file

@ -0,0 +1,52 @@
#!/usr/bin/env bash
if [ ! "$XDG_CACHE_HOME" ]; then
XDG_CACHE_HOME="/home/mihai/.local/cache"
fi
function get_time_ms {
date -u +%s%3N
}
function osd {
date="$1"
if [ ! -f "$date" ]; then
mkdir -p "$XDG_CACHE_HOME/eww"
fi
date +%s >"$date"
}
function osd_handler {
lock=0
rundate=0
OBJ="$1"
if [ ! -f "$date" ]; then
mkdir -p "$XDG_CACHE_HOME/eww"
echo 0 >"$date"
fi
while true; do
# get dates
rundate=$(cat "$date")
currentdate=$(date +%s)
# handle showing
if [ "$rundate" = "$currentdate" ] && [ "$lock" -eq 0 ]; then
eww open osd >/dev/null 2>&1
eww update "$OBJ"=true >/dev/null 2>&1
lock=1
elif [ "$((currentdate - rundate))" = "2" ] && [ "$lock" -eq 1 ]; then
eww update "$OBJ"=false >/dev/null 2>&1
lock=0
if [ "$(eww get osd-volume)" = "false" ] && [ "$(eww get osd-brightness)" = "false" ]; then
sleep 1
eww close osd >/dev/null 2>&1
fi
fi
sleep 0.1
done
eww close osd >/dev/null 2>&1
}

View file

@ -0,0 +1,87 @@
#!/usr/bin/env bash
# shellcheck source=/dev/null
source "$(dirname "$0")"/utils
volicons=("" "" "")
date="$XDG_CACHE_HOME/eww/osd_vol.date"
vol() {
wpctl get-volume @DEFAULT_AUDIO_"$1"@ | awk '{print int($2*100)}'
}
ismuted() {
wpctl get-volume @DEFAULT_AUDIO_"$1"@ | rg -qi muted
echo -n $?
}
setvol() {
wpctl set-volume @DEFAULT_AUDIO_"$1"@ "$(awk -v n="$2" 'BEGIN{print (n / 100)}')"
}
setmute() {
wpctl set-mute @DEFAULT_AUDIO_"$1"@ toggle
}
gen_output() {
percent="$(vol "SINK")"
if [[ "$event" == *source* ]]; then
pactl list sources | rg -q RUNNING
[ $? -eq 1 ] && in_use=0 || in_use=1
else
lvl=$(awk -v n="$percent" 'BEGIN{print int(n/34)}')
ismuted=$(ismuted "SINK")
if [ "$ismuted" = 1 ]; then
icon="${volicons[$lvl]}"
else
icon=""
fi
fi
echo '{"icon": "'"$icon"'", "percent": "'"$percent"'", "microphone": "'"$(vol "SOURCE")"'", "in_use": "'"$in_use"'"}'
}
case "$1" in
"mute")
if [ "$2" != "SOURCE" ] && [ "$2" != "SINK" ]; then
echo "Can only mute SINK or SOURCE"
exit 1
fi
setmute "$2"
;;
"setvol")
if [ "$2" != "SOURCE" ] && [ "$2" != "SINK" ]; then
echo "Can only set volume for SINK or SOURCE"
exit 1
elif [ "$3" -lt 0 ] || [ "$3" -gt 100 ]; then
echo "Volume must be between 0 and 100"
exit 1
fi
setvol "$2" "$3"
;;
"osd")
osd "$date"
;;
"")
# initial values
in_use=0
last_time=$(get_time_ms)
gen_output
osd_handler "osd-volume" &
# event loop
pactl subscribe | rg --line-buffered ".*(source|sink).*" | while read -r event; do
current_time=$(get_time_ms)
delta=$((current_time - last_time))
# 50ms debounce
if [[ $delta -gt 50 ]]; then
gen_output
# reset debounce timer
last_time=$(get_time_ms)
fi
done
;;
*) echo "wrong usage" ;;
esac

View file

@ -0,0 +1,20 @@
(defvar bright_reveal false)
(defvar bt_rev false)
(defvar music_reveal false)
(defvar net_rev false)
(defvar time_rev false)
(defvar vol_reveal false)
(defvar osd-brightness false)
(defvar osd-volume false)
(defvar battery_icons `["","","","","","","",""]`)
(deflisten airplane "scripts/airplane")
(deflisten battery "gross battery")
(deflisten bluetooth "scripts/bluetooth")
(deflisten brightness "scripts/brightness")
(deflisten indicators "scripts/indicators")
(deflisten music "gross music")
(deflisten music_time "gross music-time")
(deflisten net "scripts/net")
(deflisten volume "scripts/volume")
(deflisten hyprland "scripts/hyprland")

View file

@ -0,0 +1,14 @@
(defwidget calendar-win []
(box
:class "calendar-win text"
(calendar)))
(defwindow calendar
:monitor 0
:geometry (geometry
:x "0%"
:y "0%"
:anchor "top right"
:width "0px"
:height "0px")
(calendar-win))

View file

@ -0,0 +1,70 @@
(defwidget music []
(box
:class "music-window"
:space-evenly false
; force background position/size, otherwise it is overridden
:style "
background-image: url('${music.background}');
background-position: center;
background-size: cover;
/*border: 1px solid ${music.border};*/
"
(box ; cover art, first child
:class "music-cover-art"
:style "background-image: url('${music.cover}');")
(box ; music info, second child
:orientation "v"
:space-evenly false
:class "music-box"
(scroll
:hscroll true
:vscroll false
(label ; title
:class "music-title text ${music.foreground}"
:justify "center"
:text {music.title}))
(scroll
:hscroll true
:vscroll false
(label ; artist
:class "music-artist text ${music.foreground}"
:justify "center"
:text {music.artist}))
(centerbox ; buttons
:halign "center"
:class "music-button-box icon text"
(button :class "music-button ${music.foreground}" :onclick "playerctl previous" "")
(button :class "music-button ${music.foreground}" :onclick "playerctl play-pause" {music.status})
(button :class "music-button ${music.foreground}" :onclick "playerctl next" ""))
(box ; time info
:orientation "v"
(box
:class "text"
(label
:xalign 0
:class "music-time ${music.foreground}"
:text {music_time.position})
(label
:xalign 1
:class "music-time ${music.foreground}"
:text {music.duration}))
(box ; clickable progress bar
:class "music-bar"
(scale
; sadly we cannot access a child (highlight) from a parent using :style
; :style "background: linear-gradient(to right, ${music.color1}, ${music.color2});"
; transform scale position percentage into song position
:onchange "playerctl position `awk -v len=$(playerctl metadata mpris:length) 'BEGIN{print {} * len / 1000000 / 100}'`"
:value {music_time.position_percent}))))))
(defwindow music
:stacking "fg"
:focusable false
:monitor 0
:geometry (geometry
:x "0%"
:y "0%"
:width "0%"
:height "0%"
:anchor "top center")
(music))

View file

@ -0,0 +1,47 @@
; shared OSD code for volume/brightnes
(defwidget osd-part [icon value class]
(box
:class "osd-part osd-${class}"
:orientation "v"
:space-evenly false
(scale
:flipped true
:orientation "v"
:value value)
(label
:class "text"
:text icon)))
; osd window widget
(defwidget osd []
(box
:class "osd-container"
:space-evenly false
; use revealers for both volume and brightness
(revealer
:reveal {osd-brightness}
:transition "slideright"
(osd-part
:class "brightness"
:icon {brightness.icon}
:value {brightness.percent}))
(revealer
:reveal {osd-volume}
:transition "slideright"
(osd-part
:class "volume"
:icon {volume.icon}
:value {volume.percent})))
)
(defwindow osd
:stacking "fg"
:focusable false
:monitor 0
:geometry (geometry
:x "1%"
:y "0%"
:width "0%"
:height "0%"
:anchor "center left")
(osd))

View file

@ -0,0 +1,218 @@
(defvar GB 1024000000)
(defvar MB 1024000)
(defwidget system-menu []
(box ; time & date
:class "system-menu-box"
:space-evenly false
:orientation "v"
(box
:class "top-row"
:space-evenly false
(label
:class "time text"
:text {formattime(EWW_TIME, "%H:%M")})
(box
:class "date-box"
:space-evenly false
(label
:class "date"
:text {formattime(EWW_TIME, "%d/%m")})
(label
:class "day"
:text {formattime(EWW_TIME, "%A")})))
(centerbox ; wifi/bluetooth/airplane buttons
:class "system-row"
(box
:class "net-box"
:space-evenly false
:orientation "v"
(box
:class "element icon ${net.class}"
:space-evenly false
(button
:class "net-button"
:onclick "scripts/net toggle"
{net.icon})
(label
:class "separator"
:text "│")
(button
:class "net-arrow-btn"
:onclick "eww close system-menu && XDG_CURRENT_DESKTOP=GNOME gnome-control-center &"
""))
(label
:class "text"
:text {net.name}
:xalign 0.5
:justify "center"
:wrap true))
(box
:class "bluetooth-box"
:space-evenly false
:orientation "v"
(box
:class "element icon ${bluetooth.class}"
:space-evenly false
(button
:class "bluetooth-button"
:onclick "scripts/bluetooth toggle"
{bluetooth.icon})
(label
:class "separator"
:text "│")
(button
:class "bluetooth-arrow-btn"
:onclick "eww close system-menu && blueberry"
""))
(label
:class "text"
:text {bluetooth.text}
:xalign 0.5
:justify "center"
:tooltip {bluetooth.battery}
:wrap true))
(box
:class "airplane-box"
:space-evenly false
:orientation "v"
(box
:class "element"
(button
:class "airplane-button"
:onclick "scripts/airplane toggle"
airplane))
(label
:class "text"
:text "Airplane Mode"
:xalign 0.5
:limit-width 16)))
(box ; brightness & volume sliders
:class "sliders"
:orientation "v"
(box
:class "volume-slider-box"
:space-evenly false
(button
:class "volume-icon icon"
:onclick "scripts/volume mute SINK"
{volume.icon})
(scale
:class "volume-bar"
:value {volume.percent}
:tooltip "volume on ${volume.percent}%"
:onchange "scripts/volume setvol SINK {}"))
(box
:class "brightness-slider-box"
:space-evenly false
(button
:class "brightness-slider-icon icon"
{brightness.icon})
(scale
:class "brightness-slider"
:value {brightness.percent}
:marks true
:onchange "brillo -S {}")))
(box ; cpu & memory info
:class "system-info-box"
; cpu
(box
:class "sys-box"
:space-evenly false
:halign "start"
(circular-progress
:value "${EWW_CPU.avg}"
:class "sys-cpu"
:thickness 3
(label
:text ""
:class "sys-icon-cpu icon"))
(box
:class "text"
:orientation "v"
:vexpand false
(label
:text "cpu"
:halign "start"
:class "sys-text-cpu")
(label
:text "${round(EWW_CPU.avg,2)}%"
:halign "start"
:class "sys-text-sub")
(label
:text "${EWW_CPU.cores[0].freq} MHz"
:halign "start"
:class "sys-text-sub")))
; memory
(box
:class "sys-box"
:space-evenly false
:halign "end"
(circular-progress
:value {EWW_RAM.used_mem_perc}
:class "sys-mem"
:thickness 3
(label
:text ""
:class "sys-icon-mem icon"))
(box
:class "text"
:orientation "v"
(label
:text "memory"
:halign "start"
:class "sys-text-mem")
(label
:text {EWW_RAM.used_mem / GB < 1 ? "${round(EWW_RAM.used_mem / MB, 1)} M used" : "${round(EWW_RAM.used_mem / GB, 1)} G used"}
:halign "start"
:class "sys-text-sub")
(label
:text {EWW_RAM.total_mem / GB < 1 ? "${round(EWW_RAM.total_mem / MB, 1)} M total" : "${round(EWW_RAM.total_mem / GB, 1)} G total"}
:halign "start"
:class "sys-text-sub"))))
(centerbox ; battery info & power button
:class "bottom-row"
(box
:class "battery-box"
:space-evenly false
:halign "start"
(label
:class "battery-icon icon ${EWW_BATTERY.total_avg > 20 ? '' : 'low'}"
:text {battery_icons[round(EWW_BATTERY.total_avg / (100 / arraylength(battery_icons)) - 1,0)]})
(label
:class "text"
:text {round(EWW_BATTERY.total_avg,0)})
(label
:class "battery-status text"
:text {battery.status})
(label
:class "battery-rate text"
:text {battery.rate}))
(label) ; empty element; centerbox requires 3 children but we only need left/right
(box ; power button
:space-evenly false
:halign "end"
(button
:halign "end"
:class "power-button icon"
:onclick "wlogout -p layer-shell &"
"")))))
(defwindow system-menu
:stacking "fg"
:monitor 0
:geometry (geometry
:x "0"
:y "0"
:width "0%"
:height "0%"
:anchor "right top")
(system-menu))