From 4ec574cefd3bfe4e6cd83328e1a00a3d2fba7ab8 Mon Sep 17 00:00:00 2001 From: Kajus Naujokaitis Date: Wed, 18 Dec 2024 11:46:50 +0200 Subject: [PATCH] WIP: Volume mixer widget - Desktop widget design rework - Move clock widget to center --- modules/desktop/graphics/ewwbar.nix | 363 ++++++++++-------- modules/desktop/graphics/labwc.nix | 2 +- .../microvm/virtualization/microvm/guivm.nix | 4 + 3 files changed, 217 insertions(+), 152 deletions(-) diff --git a/modules/desktop/graphics/ewwbar.nix b/modules/desktop/graphics/ewwbar.nix index 5fd13f279..416b3e1c1 100644 --- a/modules/desktop/graphics/ewwbar.nix +++ b/modules/desktop/graphics/ewwbar.nix @@ -17,6 +17,10 @@ let }; inherit (config.ghaf.services.audio) pulseaudioTcpControlPort; + iconThemeBase = "/run/current-system/sw/share/icons/${ + if cfg.gtk.colorScheme == "prefer-dark" then "${cfg.gtk.iconTheme}-Dark" else cfg.gtk.iconTheme + }"; + launcher-icon = "${pkgs.ghaf-artwork}/icons/launcher.svg"; battery-0-icon = "${pkgs.ghaf-artwork}/icons/battery-0.svg"; @@ -50,8 +54,7 @@ let lock-icon = "${pkgs.ghaf-artwork}/icons/lock.svg"; logout-icon = "${pkgs.ghaf-artwork}/icons/logout.svg"; - - arrow-right-icon = "${pkgs.ghaf-artwork}/icons/arrow-right.svg"; + arrow-right-icon = "${iconThemeBase}/24x24/actions/adjustlevels.svg"; # Called by eww.yuck for updates and reloads ewwCmd = "${pkgs.eww}/bin/eww -c /etc/eww"; @@ -282,28 +285,37 @@ let pkgs.gawk pkgs.pulseaudio pkgs.pamixer + pkgs.gnused + pkgs.jq ]; bashOptions = [ ]; text = '' export PULSE_SERVER=audio-vm:${toString pulseaudioTcpControlPort} - icon() { - if [[ "$2" == "true" || "$1" -eq 0 ]]; then - echo "${volume-0-icon}" - elif [ "$1" -lt 25 ]; then - echo "${volume-1-icon}" - elif [ "$1" -lt 75 ]; then - echo "${volume-2-icon}" - else - echo "${volume-3-icon}" - fi - } - get() { - volume=$(pamixer --get-volume) - muted=$(pamixer --get-mute) - icon=$(icon "$volume" "$muted") - echo "{ \"level\": \"$volume\", \"muted\": \"$muted\", \"icon\": \"$icon\" }" + volume=$(pamixer --get-volume) + muted=$(pamixer --get-mute) + + sink_inputs_json=$(pactl -f json list sink-inputs | jq -c ' + map({ + level: (.volume."front-left".value_percent // "0" | sub("%$"; "")), + muted: (.mute // "false"), + name: (.properties."application.name" // ""), + icon_name: (.properties."application.icon_name" // ""), + id: (.index // "-1") + }) + ' || echo "[]") + + # Output the final JSON + jq -c --unbuffered -n --argjson sinkInputs "$sink_inputs_json" --arg level "$volume" --arg muted "$muted" ' + { + system: { + level: $level, + muted: $muted + }, + sinkInputs: $sinkInputs + } + ' } listen() { @@ -386,6 +398,34 @@ let ''; }; + eww-open-widget = pkgs.writeShellApplication { + name = "eww-open-widget"; + bashOptions = [ ]; + text = '' + # List of available widgets + windows=("quick-settings" "power-menu" "calendar") + + # Get the current active windows + active_windows=$(${ewwCmd} active-windows) + + if [[ "$active_windows" == *"$1"* ]]; then + ${ewwCmd} close "$1" closer & + exit 0 + fi + + # Close all windows except the target + ( + for window in "''${windows[@]}"; do + if [[ "$window" != "$1" ]]; then + ${ewwCmd} close "$window" & + fi + done + ) & + + ${ewwCmd} open-many closer "$1" --arg screen="$2" --arg closer:window="$1" + ''; + }; + mkPopupHandler = { name, @@ -439,8 +479,8 @@ in ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defpoll keyboard_layout :interval "5s" "${pkgs.xorg.setxkbmap}/bin/setxkbmap -query | ${pkgs.gawk}/bin/awk '/layout/{print $2}' | tr a-z A-Z") (defpoll battery :interval "5s" :initial "{}" "${eww-bat}/bin/eww-bat get") - (deflisten brightness "${eww-brightness}/bin/eww-brightness listen") - (deflisten volume "${eww-volume}/bin/eww-volume listen") + (deflisten brightness :initial "{}" "${eww-brightness}/bin/eww-brightness listen") + (deflisten volume :initial "{}" "${eww-volume}/bin/eww-volume listen") (deflisten workspace :initial "1" "${ghaf-workspace}/bin/ghaf-workspace subscribe") (defvar calendar_day "date '+%d'") @@ -451,7 +491,8 @@ in (defvar brightness-popup-visible "false") (defvar workspace-popup-visible "false") (defvar workspaces-visible "false") - ;; (defpoll bluetooth :interval "3s" :initial "{}" "${pkgs.bt-launcher}/bin/bt-launcher status") + (defvar volume-mixer-visible "false") + (defvar mixer-sliders "") ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Widgets ;; @@ -464,9 +505,9 @@ in :style "background-image: url(\"${launcher-icon}\")"))) ;; Generic slider widget ;; - (defwidget sys_slider [?header icon ?settings-icon level ?onchange ?settings-onclick ?icon-onclick ?class ?font-icon ?min] + (defwidget sys_slider [?header ?icon ?app_icon ?settings-icon level ?onchange ?settings-onclick ?icon-onclick ?class ?font-icon ?min] (box :orientation "v" - :class "qs-slider" + :class "''${class}" :spacing 10 :space-evenly false (label :class "header" @@ -483,9 +524,14 @@ in :onclick icon-onclick :hexpand false :class "icon_settings" - (box :class "icon" - :hexpand false - :style "background-image: url(\"''${icon}\")")) + (overlay + (box :class "icon" + :hexpand false + :style "background-image: url(\"''${icon}\"); opacity: ''${app_icon != "" ? "0" : "1"}") + (box :class "icon" + :hexpand false + :style "background-image: url(\"''${app_icon}\"); opacity: ''${level == 0 || level == min ? "0.5" : "1"}") + )) (label :class "icon" :visible {font-icon != "" ? "true" : "false"} :text font-icon) (eventbox :valign "CENTER" @@ -496,8 +542,9 @@ in :orientation "h" :halign "fill" :value level + :round-digits 0 :onchange onchange - :max 101 + :max 100 :min { min ?: 0 })) (eventbox :visible { settings-onclick != "" && settings-onclick != "null" ? "true" : "false" } @@ -505,7 +552,8 @@ in :class "settings" (box :class "icon" :hexpand false - :style "background-image: url(\"''${settings-icon}\")"))))) + :style "background-image: url(\"''${settings-icon}\")"))) + (children))) (defwidget sys_sliders [] (box @@ -514,18 +562,55 @@ in :hexpand false :space-evenly false (sys_slider + :class "qs-slider" :header "Volume" - :icon {volume.icon} + :icon { volume.system.muted == "true" || volume.system.level == 0 ? "${volume-0-icon}" : + volume.system.level <= 25 ? "${volume-1-icon}" : + volume.system.level <= 75 ? "${volume-2-icon}" : "${volume-3-icon}" } :icon-onclick "${eww-volume}/bin/eww-volume mute &" :settings-icon "${arrow-right-icon}" - :level { volume.muted == "true" ? "0" : volume.level } - :onchange "PULSE_SERVER=audio-vm:${toString pulseaudioTcpControlPort} ${pkgs.pamixer}/bin/pamixer --unmute --set-volume {} &") + :settings-onclick {volume-mixer-visible == "false" ? "''${EWW_CMD} update volume-mixer-visible=true &" : "''${EWW_CMD} update volume-mixer-visible=false &"} + :level { volume.system.muted == "true" ? "0" : volume.system.level } + :onchange "PULSE_SERVER=audio-vm:${toString pulseaudioTcpControlPort} ${pkgs.pamixer}/bin/pamixer --unmute --set-volume {} &" + (revealer + :transition "slidedown" + :duration "250ms" + :reveal volume-mixer-visible + (box :orientation "v" + (label :text "No audio streams" :visible {arraylength(volume.sinkInputs) == 0} :halign "center" :valign "center") + (volume_mixer) + ))) (sys_slider + :class "qs-slider" :header "Brightness" :level {brightness.screen.level} :icon {brightness.icon} :min "5" - :onchange "${eww-brightness}/bin/eww-brightness set_screen {} &"))) + :onchange "${eww-brightness}/bin/eww-brightness set_screen {} &") + ) + ) + + (defwidget volume_mixer [] + (scroll + :hscroll "false" + :vscroll "true" + :height { 50 * min(3,arraylength(volume.sinkInputs)) } + (box :orientation "v" :space-evenly false + (for entry in {volume.sinkInputs} + (sys_slider + :class "qs-slider" + :header {entry.name} + :level {entry.muted == "true" ? "0" : entry.level} + :app_icon {entry.icon_name != "" ? "${iconThemeBase}/24x24/apps/''${entry.icon_name}.svg" : ""} + :icon { entry.muted == "true" || entry.level == 0 ? "${volume-0-icon}" : + entry.level <= 25 ? "${volume-1-icon}" : + entry.level <= 75 ? "${volume-2-icon}" : "${volume-3-icon}" } + :onchange "${pkgs.pulseaudio}/bin/pactl -s audio-vm:${toString pulseaudioTcpControlPort} set-sink-input-volume ''${entry.id} {}% & ${pkgs.pulseaudio}/bin/pactl -s audio-vm:${toString pulseaudioTcpControlPort} set-sink-input-mute ''${entry.id} 0 &" + :icon-onclick "${pkgs.pulseaudio}/bin/pactl -s audio-vm:${toString pulseaudioTcpControlPort} set-sink-input-mute ''${entry.id} toggle &") + ) + ) + ) + ) ;; Generic Widget Buttons For Quick Settings ;; (defwidget widget_button [icon ?title ?header ?subtitle ?onclick ?font-icon ?class] @@ -611,7 +696,7 @@ in (widget_button :icon "${bluetooth-1-icon}" :header "Bluetooth" - :onclick "''${EWW_CMD} close quick-settings closer & ${pkgs.bt-launcher}/bin/bt-launcher &") + :onclick "''${EWW_CMD} close quick-settings closer & ''${EWW_CMD} update volume-mixer-visible=false & ${pkgs.bt-launcher}/bin/bt-launcher &") (box :hexpand true :vexpand true @@ -635,69 +720,59 @@ in (widget_button :icon "${settings-icon}" :header "Settings" - :onclick "''${EWW_CMD} close quick-settings closer & ${pkgs.ctrl-panel}/bin/ctrl-panel >/dev/null &"))) + :onclick "''${EWW_CMD} close quick-settings closer & ''${EWW_CMD} update volume-mixer-visible=false & ${pkgs.ctrl-panel}/bin/ctrl-panel >/dev/null &"))) ''} + ;; Reusable Widgets ;; + (defwidget desktop-widget [?space-evenly ?spacing ?orientation ?class] + (box + :class "floating-widget ''${class}" + :space-evenly {space-evenly != "" ? space-evenly : "false"} + :spacing {spacing != "" ? spacing : "10"} + :orientation {orientation != "" ? orientation : "v"} + (children))) + ;; Quick Settings Widget ;; (defwidget quick-settings-widget [] - (box :class "floating-widget" - :orientation "v" - :space-evenly false - (box - :class "wrapper_widget" - :space-evenly false - :spacing 10 - :orientation "v" - (etc) - (sys_sliders)))) + (desktop-widget + (etc) + (sys_sliders))) ;; Power Menu Widget ;; (defwidget power-menu-widget [] - (box :class "floating-widget" - :orientation "v" - :space-evenly false - (box - :class "wrapper_widget" - :space-evenly false - :orientation "v" - (power_menu)))) + (desktop-widget + (power_menu))) ;; Brightness Popup Widget ;; (defwidget brightness-popup [] (revealer :transition "crossfade" :duration "200ms" :reveal brightness-popup-visible :active false - (box :class "wrapper_widget" - (box :class "hotkey" + (desktop-widget :class "popup" (sys_slider :valign "center" :icon {brightness.icon} - :level {brightness.screen.level}))))) + :level {brightness.screen.level})))) ;; Volume Popup Widget ;; (defwidget volume-popup [] (revealer :transition "crossfade" :duration "200ms" :reveal volume-popup-visible :active false - (box :class "wrapper_widget" - (box :class "hotkey" + (desktop-widget :class "popup" (sys_slider :valign "center" - :icon {volume.icon} - :level {volume.level}))))) + :icon { volume.system.muted == "true" || volume.system.level == 0 ? "${volume-0-icon}" : + volume.system.level <= 25 ? "${volume-1-icon}" : + volume.system.level <= 75 ? "${volume-2-icon}" : "${volume-3-icon}" } + :level {volume.system.level})))) ;; Workspace Popup Widget ;; (defwidget workspace-popup [] (revealer :transition "crossfade" :duration "200ms" :reveal workspace-popup-visible :active false - (box :class "wrapper_widget" - (box :class "hotkey" - (label :text "Desktop ''${workspace}"))))) + (desktop-widget :class "popup" + (label :text "Desktop ''${workspace}")))) ;; Quick Settings Button ;; (defwidget quick-settings-button [screen bat-icon vol-icon bright-icon] (button :class "icon_button" - :onclick "if ''${EWW_CMD} active-windows | grep -q 'quick-settings'; then \ - ''${EWW_CMD} close closer quick-settings & \ - else \ - ''${EWW_CMD} close power-menu calendar & \ - ''${EWW_CMD} open --screen \"''${screen}\" closer --arg window=\"quick-settings\" && ''${EWW_CMD} open --screen \"''${screen}\" quick-settings; \ - fi &" + :onclick "''${EWW_CMD} update volume-mixer-visible=false & ${eww-open-widget}/bin/eww-open-widget quick-settings \"''${screen}\" &" (box :orientation "h" :space-evenly "false" :spacing 14 @@ -714,15 +789,10 @@ in ;; Power Menu Launcher ;; (defwidget power-menu-launcher [screen] - (button :class "icon_button icon" - :halign "center" - :valign "center" - :onclick "if ''${EWW_CMD} active-windows | grep -q 'power-menu'; then \ - ''${EWW_CMD} close closer power-menu & \ - else \ - ''${EWW_CMD} close quick-settings calendar & \ - ''${EWW_CMD} open --screen \"''${screen}\" closer --arg window=\"power-menu\" && ''${EWW_CMD} open --screen \"''${screen}\" power-menu; \ - fi &" + (button :class "icon_button icon" + :halign "center" + :valign "center" + :onclick "${eww-open-widget}/bin/eww-open-widget power-menu \"''${screen}\" &" (box :class "icon" :hexpand false :style "background-image: url(\"${power-icon}\")"))) @@ -740,7 +810,9 @@ in :class "control" (quick-settings-button :screen screen :bright-icon {brightness.icon} - :vol-icon {volume.icon} + :vol-icon { volume.system.muted == "true" || volume.system.level == 0 ? "${volume-0-icon}" : + volume.system.level <= 25 ? "${volume-1-icon}" : + volume.system.level <= 75 ? "${volume-2-icon}" : "${volume-3-icon}" } :bat-icon {battery.icon}))) ;; Divider ;; @@ -759,33 +831,20 @@ in :visible "false" (label :text keyboard_layout))) - ;; Clock ;; - (defwidget time [] - (label - :text "''${formattime(EWW_TIME, "%H:%M")}" - :class "time")) - - ;; Date ;; - (defwidget date [screen] - (button - :onclick "''${EWW_CMD} update calendar_day=\"$(date +%d)\" calendar_month=\"$(date +%-m)\" calendar_year=\"$(date +%Y)\" & \ - if ''${EWW_CMD} active-windows | grep -q 'calendar'; then \ - ''${EWW_CMD} close closer calendar & \ - else \ - ''${EWW_CMD} close quick-settings power-menu & \ - ''${EWW_CMD} open --screen \"''${screen}\" closer --arg window=\"calendar\" && ''${EWW_CMD} open --screen \"''${screen}\" calendar; \ - fi &" - :class "icon_button date" "''${formattime(EWW_TIME, "%a %b %-d")}")) + ;; DateTime Widget ;; + (defwidget datetime [screen] + (button + :onclick "${eww-open-widget}/bin/eww-open-widget calendar \"''${screen}\" &" + :class "icon_button date" "''${formattime(EWW_TIME, "%H:%M %a %b %-d")}")) ;; Calendar ;; (defwidget cal [] - (box :class "floating-widget" - (box :class "wrapper_widget" - (calendar :class "cal" - :show-week-numbers false - :day calendar_day - :month calendar_month - :year calendar_year)))) + (desktop-widget + (calendar :class "cal" + :show-week-numbers false + :day calendar_day + :month calendar_month + :year calendar_year))) ;; Left Widgets ;; (defwidget workspaces [] @@ -813,9 +872,9 @@ in ) }))))) - (defwidget left [] - (box - :orientation "h" + (defwidget bar_left [] + (box + :orientation "h" :space-evenly "false" :spacing 14 :halign "start" @@ -824,54 +883,58 @@ in (divider) (workspaces))) + ;; Center Widgets ;; + (defwidget bar_center [screen] + (box + :orientation "h" + :space-evenly "false" + :spacing 14 + :halign "center" + :valign "center" + (datetime :screen screen))) + ;; Right Widgets ;; (defwidget datetime-locale [screen] - (box + (box :orientation "h" :space-evenly "false" :spacing 14 (language) - (box - :orientation "h" - :space-evenly "false" - :spacing 14 - (time) - (date :screen screen)))) + (datetime :screen screen))) ;; End Widgets ;; - (defwidget end [screen] - (box :orientation "h" - :space-evenly "false" - :halign "end" - :valign "center" + (defwidget bar_right [screen] + (box :orientation "h" + :space-evenly "false" + :halign "end" + :valign "center" :spacing 14 (systray :orientation "h" :spacing 14 :prepend-new true :class "tray") (divider) ${lib.optionalString useGivc "(control :screen screen) (divider)"} - (datetime-locale :screen screen) - (divider) (power-menu-launcher :screen screen))) ;; Bar ;; (defwidget bar [screen] - (box + (centerbox :class "eww_bar" :orientation "h" :vexpand "false" :hexpand "false" - (left) - (end :screen screen))) + (bar_left) + (bar_center :screen screen) + (bar_right :screen screen))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Windows ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Bar Window ;; (defwindow bar [screen] - :geometry (geometry - :x "0px" - :y "0px" - :height "36px" - :width "100%" + :geometry (geometry + :x "0px" + :y "0px" + :height "28px" + :width "100%" :anchor "top center") :focusable "false" :hexpand "false" @@ -884,7 +947,7 @@ in (defwindow calendar :geometry (geometry :y "0px" :x "0px" - :anchor "top right") + :anchor "top center") :stacking "fg" (cal)) @@ -954,9 +1017,10 @@ in $bg-secondary: #2B2B2B; $text-base: #FFFFFF; $text-disabled: #9c9c9c; - $text-success: #5AC379; + $text-success: rgba(90, 195, 121, 1); $icon-subdued: #3D3D3D; - $stroke-success: #5AC379; + $stroke-success: rgba(90, 195, 121, 1); + $stroke-success-muted: rgba(90, 195, 121, 0.75); $font-bold: 600; @mixin unset($rec: false) { @@ -986,24 +1050,20 @@ in background-color: $bg-primary; } - @mixin widget($bg: #161616, $padding: 10px, $radius: 12px){ + @mixin widget($bg: $bg-primary, $padding: 10px, $radius: 12px){ border-radius: $radius; background-color: $bg; padding: $padding; } - @mixin wrapper_widget($padding: 14px, $radius: 6px){ - @include widget($padding: 14px, $radius: 6px, $bg: $bg-primary); - } - - @mixin floating_widget($margin: 0.3em 0.3em 0em 0em, $padding: 14px, $radius: 6px, $unset: true) { + @mixin floating_widget($bg: $bg-primary, $margin: 0.3em 0.3em 0em 0em, $padding: 14px, $radius: 12px, $unset: true) { @if $unset { @include unset($rec: true); } + background-color: $bg; border-radius: $radius; margin: $margin; - - .wrapper_widget { @include wrapper_widget($padding: $padding, $radius: $radius); } + padding: $padding; } @mixin icon(){ @@ -1013,7 +1073,7 @@ in background-size: contain; min-height: 24px; min-width: 24px; - font-family: Fira Code; + font-family: ${cfg.gtk.fontName}; font-size: 1.5em; color: #FFFFFF; } @@ -1041,7 +1101,7 @@ in } } - @mixin slider($slider-width: 225px, $slider-height: 2px, $thumb: true, $thumb-width: 1em, $focusable: true, $radius: 7px, $shadows: true, $trough-bg: $widget-hover) { + @mixin slider($slider-width: 225px, $slider-height: 2px, $thumb: true, $thumb-width: 1em, $focusable: true, $radius: 7px, $shadows: true, $trough-bg: $widget-hover, $trough-fg: $stroke-success) { trough { border-radius: $radius; border: 0; @@ -1052,7 +1112,7 @@ in highlight, progress { - background-color: $stroke-success; + background-color: $trough-fg; border-radius: $radius; } } @@ -1188,11 +1248,6 @@ in @include qs-widget; } - .wrapper_widget { - @include unset($rec: true); - @include wrapper_widget; - } - .icon { @include icon; } .floating-widget { @include floating_widget; } @@ -1204,11 +1259,16 @@ in padding: 0.8em; } - .hotkey { - @include floating_widget($margin: 0, $padding: 10px 12px); - @include icon; + .popup { .slider{ @include slider($slider-width: 150px, $thumb: false, $slider-height: 5px); } + .icon_settings{ + @include icon-button; + margin-right: 0.15em; + } font-size: 1.3em; + :disabled { + color: $text-base; + } } .widget-button {@include widget-button; } @@ -1217,7 +1277,7 @@ in .eww_bar { background-color: $bg-primary; - padding: 0.2em 1em 0.2em 1em; + padding: 0.2em 0.5em 0.2em 0.5em; } .icon_button { @@ -1240,7 +1300,7 @@ in } .date { - padding: 0.4em 0.25em; + padding: 0.4em 0.4em; border-radius: 0.25em; font-weight: $font-bold; font-size: 1em; @@ -1264,6 +1324,7 @@ in } .cal { + @include unset($rec: true); font-size: 1.2em; padding: 0.2em 0.2em; diff --git a/modules/desktop/graphics/labwc.nix b/modules/desktop/graphics/labwc.nix index f4d5d7d93..ab0787701 100644 --- a/modules/desktop/graphics/labwc.nix +++ b/modules/desktop/graphics/labwc.nix @@ -124,7 +124,7 @@ in theme = "Adwaita"; iconTheme = "Papirus"; colorScheme = "prefer-dark"; - fontName = "Cantarell"; + fontName = "Inter"; fontSize = "11"; }; description = "Global gtk+ configuration"; diff --git a/modules/microvm/virtualization/microvm/guivm.nix b/modules/microvm/virtualization/microvm/guivm.nix index 8dcc32706..3a165e262 100644 --- a/modules/microvm/virtualization/microvm/guivm.nix +++ b/modules/microvm/virtualization/microvm/guivm.nix @@ -208,6 +208,10 @@ let pkgs.pamixer pkgs.eww pkgs.wlr-randr + pkgs.pulseaudio + pkgs.pamixer + pkgs.gnused + pkgs.jq ] ++ [ pkgs.ctrl-panel ] ++ (lib.optional (