diff --git a/docs/src/ref_impl/creating_appvm.md b/docs/src/ref_impl/creating_appvm.md index f43eaee7d..5ba9f42c3 100644 --- a/docs/src/ref_impl/creating_appvm.md +++ b/docs/src/ref_impl/creating_appvm.md @@ -63,7 +63,7 @@ Each VM has the following properties: ## Adding Application Launcher in GUI VM -To add an app launcher, add an element in the [guivm.nix](../../../modules/virtualization/microvm/guivm.nix) file to the **graphics.weston.launchers** list. +To add an app launcher, add an element in the [guivm.nix](../../../modules/virtualization/microvm/guivm.nix) file to the **graphics.launchers** list. A launcher element has 2 properties: diff --git a/modules/graphics/default.nix b/modules/graphics/default.nix index 43d9058e2..9224a9af5 100644 --- a/modules/graphics/default.nix +++ b/modules/graphics/default.nix @@ -5,6 +5,7 @@ ./weston.nix ./labwc.nix ./weston.ini.nix + ./waybar.config.nix ./demo-apps.nix ./fonts.nix ./gnome.nix diff --git a/modules/graphics/demo-apps.nix b/modules/graphics/demo-apps.nix index 7f56950a8..5c10fc072 100644 --- a/modules/graphics/demo-apps.nix +++ b/modules/graphics/demo-apps.nix @@ -7,7 +7,6 @@ ... }: let cfg = config.ghaf.graphics.demo-apps; - inherit (config.ghaf.graphics) weston; /* Scaled down firefox icon @@ -36,31 +35,36 @@ in { options.ghaf.graphics.demo-apps = with lib; { chromium = mkProgramOption "Chromium browser" false; - firefox = mkProgramOption "Firefox browser" weston.enableDemoApplications; + firefox = mkProgramOption "Firefox browser" config.ghaf.graphics.enableDemoApplications; gala-app = mkProgramOption "Gala App" false; - element-desktop = mkProgramOption "Element desktop" weston.enableDemoApplications; - zathura = mkProgramOption "zathura" weston.enableDemoApplications; + element-desktop = mkProgramOption "Element desktop" config.ghaf.graphics.enableDemoApplications; + zathura = mkProgramOption "zathura" config.ghaf.graphics.enableDemoApplications; }; - config = lib.mkIf weston.enable { - ghaf.graphics.weston.launchers = + config = lib.mkIf config.ghaf.profiles.graphics.enable { + ghaf.graphics.launchers = lib.optional cfg.chromium { + name = "chromium"; path = "${pkgs.chromium}/bin/chromium --enable-features=UseOzonePlatform --ozone-platform=wayland"; icon = "${pkgs.chromium}/share/icons/hicolor/24x24/apps/chromium.png"; } ++ lib.optional cfg.firefox { + name = "firefox"; path = "${pkgs.firefox}/bin/firefox"; icon = "${firefox-icon}/share/icons/hicolor/24x24/apps/firefox.png"; } ++ lib.optional cfg.element-desktop { + name = "element"; path = "${pkgs.element-desktop}/bin/element-desktop --enable-features=UseOzonePlatform --ozone-platform=wayland"; icon = "${pkgs.element-desktop}/share/icons/hicolor/24x24/apps/element.png"; } ++ lib.optional cfg.gala-app { + name = "gala"; path = "${pkgs.gala-app}/bin/gala --enable-features=UseOzonePlatform --ozone-platform=wayland"; icon = "${pkgs.gala-app}/gala/resources/icon-24x24.png"; } ++ lib.optional cfg.zathura { + name = "zathura"; path = "${pkgs.zathura}/bin/zathura"; icon = "${pkgs.zathura}/share/icons/hicolor/32x32/apps/org.pwmt.zathura.png"; }; diff --git a/modules/graphics/fonts.nix b/modules/graphics/fonts.nix index 4f45dcbb2..d620a29df 100644 --- a/modules/graphics/fonts.nix +++ b/modules/graphics/fonts.nix @@ -6,12 +6,18 @@ config, ... }: let - cfg = config.ghaf.graphics.weston; + inherit (config.ghaf.graphics) weston labwc; in { - config = lib.mkIf cfg.enable { - fonts.packages = with pkgs; [ - fira-code - hack-font - ]; - }; + config = + lib.mkIf weston.enable { + fonts.fonts = with pkgs; [ + fira-code + hack-font + ]; + } + // lib.mkIf labwc.enable { + fonts.packages = with pkgs; [ + (nerdfonts.override {fonts = ["FiraCode"];}) + ]; + }; } diff --git a/modules/graphics/waybar.config.nix b/modules/graphics/waybar.config.nix new file mode 100644 index 000000000..380ba7d11 --- /dev/null +++ b/modules/graphics/waybar.config.nix @@ -0,0 +1,334 @@ +# Copyright 2022-2024 TII (SSRC) and the Ghaf contributors +# SPDX-License-Identifier: Apache-2.0 +{ + pkgs, + lib, + config, + ... +}: let + cfg = config.ghaf.graphics.labwc; + networkDevice = config.ghaf.hardware.definition.network.pciDevices; + mkLauncherStyle = { + name, + icon, + ... + }: '' + #custom-${name} { + font-size: 20px; background-image: url("${icon}"); + background-position: center; + background-repeat: no-repeat; + padding-left: 10; + padding-right: 10; + } + + ''; + mkLauncherPadding = {name, ...}: '' + #custom-${name}, + ''; + mkLauncherConfig = { + name, + path, + ... + }: '' + "custom/${name}": { + "format": " ", + "on-click": "${builtins.replaceStrings ["\""] ["\\\\\\\""] path}", + "tooltip": false + }, + ''; + mkLauncherModule = {name, ...}: ''"custom/${name}", ''; + + /* + Generate launchers to be used in /etc/waybar/config + + Type: mkLaunchers :: [{name, path, icon}] -> string + + */ + mkLauncherStyles = lib.concatMapStrings mkLauncherStyle; + mkLauncherConfigs = lib.concatMapStrings mkLauncherConfig; + mkLauncherPaddings = lib.concatMapStrings mkLauncherPadding; + mkLauncherModules = lib.concatMapStrings mkLauncherModule; + + defaultLauncher = [ + # Keep weston-terminal launcher always enabled explicitly since if someone adds + # a launcher on the panel, the launcher will replace weston-terminal launcher. + { + name = "terminal"; + path = "${pkgs.weston}/bin/weston-terminal"; + icon = "${pkgs.weston}/share/weston/icon_terminal.png"; + } + ]; + + wifi-signal-strength = pkgs.callPackage ../../packages/wifi-signal-strength {wifiDevice = (lib.lists.findFirst (d: d.name != null) null networkDevice).name;}; +in { + config = lib.mkIf cfg.enable { + ghaf.graphics.launchers = defaultLauncher; + environment.etc."waybar/config" = { + text = + # Modified from default waybar configuration file https://github.com/Alexays/Waybar/blob/master/resources/config + '' + { + "height": 30, // Waybar height (to be removed for auto height) + "spacing": 4, // Gaps between modules (4px) + "modules-left": [ '' + + mkLauncherModules config.ghaf.graphics.launchers + + '' ], + "modules-center": ["sway/window"], + "modules-right": ["pulseaudio", "custom/network1", "backlight", "battery", "clock", "tray"], + "keyboard-state": { + "numlock": true, + "capslock": true, + "format": "{name} {icon}", + "format-icons": { + "locked": "", + "unlocked": "" + } + }, + "tray": { + // "icon-size": 21, + "spacing": 10 + }, + "clock": { + "timezone": "${config.time.timeZone}", + "tooltip-format": "{:%Y %B}\n{calendar}", + "format-alt": "{:%Y-%m-%d}" + }, + "backlight": { + // "device": "acpi_video1", + "format": "{percent}% {icon}", + "format-icons": ["", "", "", "", "", "", "", "", ""] + }, + "battery": { + "states": { + "critical": 15 + }, + "interval": 5, + "format": "{capacity}% {icon}", + "format-charging": "{capacity}% 󰢟", + "format-plugged": "{capacity}% ", + "format-alt": "{time} {icon}", + "format-icons": ["󰂎", "󰁺", "󰁻", "󰁼", "󰁽", "󰁾", "󰁿", "󰁿", "󰂀", "󰂁", "󰂂", "󰁹"] + }, + "custom/network1": { + "format": "{}", + "interval": 15, + "exec": "${wifi-signal-strength}/bin/wifi-signal-strength", + "return-type": "json", + "on-click": "${pkgs.nm-launcher}/bin/nm-launcher", + }, + "pulseaudio": { + // "scroll-step": 1, // %, can be a float + "format": "{volume}% {icon} {format_source}", + "format-bluetooth": "{volume}% {icon} {format_source}", + "format-bluetooth-muted": " {icon} {format_source}", + "format-muted": "󰝟 {format_source}", + "format-source": "{volume}% ", + "format-source-muted": "", + "format-icons": { + "headphone": "", + "default": ["", "", ""] + }, + }, + '' + + mkLauncherConfigs config.ghaf.graphics.launchers + + ''}''; + + # The UNIX file mode bits + mode = "0644"; + }; + environment.etc."waybar/style.css" = { + text = + # Modified from default waybar style file https://github.com/Alexays/Waybar/blob/master/resources/style.css + '' + * { + font-family: FontAwesome, Roboto, Helvetica, Arial, sans-serif; + font-size: 13px; + } + + window#waybar { + background-color: rgba(43, 48, 59, 0.5); + border-bottom: 3px solid rgba(100, 114, 125, 0.5); + color: #ffffff; + transition-property: background-color; + transition-duration: .5s; + } + + window#waybar.hidden { + opacity: 0.2; + } + + window#waybar.termite { + background-color: #3F3F3F; + } + + window#waybar.chromium { + background-color: #000000; + border: none; + } + + button { + box-shadow: inset 0 -3px transparent; + border: none; + border-radius: 0; + } + + button:hover { + background: inherit; + box-shadow: inset 0 -3px #ffffff; + } + + #workspaces button { + padding: 0 5px; + background-color: transparent; + color: #ffffff; + } + + #workspaces button:hover { + background: rgba(0, 0, 0, 0.2); + } + + #workspaces button.focused { + background-color: #64727D; + box-shadow: inset 0 -3px #ffffff; + } + + #workspaces button.urgent { + background-color: #eb4d4b; + } + + '' + + mkLauncherPaddings config.ghaf.graphics.launchers + + '' + #clock, + #battery, + #backlight, + #custom-network1, + #pulseaudio, + #tray, + + #window, + #workspaces { + margin: 0 4px; + } + + .modules-left > widget:first-child > #workspaces { + margin-left: 0; + } + + .modules-right > widget:last-child > #workspaces { + margin-right: 0; + } + + #clock { + background-color: #64727D; + padding-left: 10; + padding-right: 10; + } + + #battery { + background-color: #ffffff; + color: #000000; + padding-left: 10; + padding-right: 10; + } + + #battery.charging, #battery.plugged { + color: #ffffff; + background-color: #26A65B; + } + + @keyframes blink { + to { + background-color: #ffffff; + color: #000000; + } + } + + #battery.critical:not(.charging) { + background-color: #f53c3c; + color: #ffffff; + animation-name: blink; + animation-duration: 0.5s; + animation-timing-function: linear; + animation-iteration-count: infinite; + animation-direction: alternate; + } + + label:focus { + background-color: #000000; + } + + #backlight { + background-color: #90b1b1; + padding-left: 10; + padding-right: 10; + } + + #custom-network1 { + background-color: #2980b9; + min-width: 16px; + padding-left: 10; + padding-right: 10; + } + + #custom-network1.disconnected { + background-color: #f53c3c; + } + + #pulseaudio { + background-color: #f1c40f; + color: #000000; + padding-left: 10; + padding-right: 10; + } + + #pulseaudio.muted { + background-color: #90b1b1; + color: #2a5c45; + } + + #tray { + background-color: #2980b9; + } + + #tray > .passive { + -gtk-icon-effect: dim; + } + + #tray > .needs-attention { + -gtk-icon-effect: highlight; + background-color: #eb4d4b; + } + + #language { + background: #00b093; + color: #740864; + padding: 0 5px; + margin: 0 5px; + min-width: 16px; + } + + #keyboard-state { + background: #97e1ad; + color: #000000; + padding: 0 0px; + margin: 0 5px; + min-width: 16px; + } + + #keyboard-state > label { + padding: 0 5px; + } + + #keyboard-state > label.locked { + background: rgba(0, 0, 0, 0.2); + } + + '' + + mkLauncherStyles config.ghaf.graphics.launchers; + + # The UNIX file mode bits + mode = "0644"; + }; + }; +} diff --git a/modules/graphics/weston.ini.nix b/modules/graphics/weston.ini.nix index 03fe78276..f474fc5a5 100644 --- a/modules/graphics/weston.ini.nix +++ b/modules/graphics/weston.ini.nix @@ -8,10 +8,13 @@ }: let cfg = config.ghaf.graphics.weston; mkLauncher = { + # Add the name field to unify with Labwc launchers + name, path, icon, }: '' [launcher] + name=${name} path=${path} icon=${icon} @@ -29,33 +32,14 @@ # Keep weston-terminal launcher always enabled explicitly since if someone adds # a launcher on the panel, the launcher will replace weston-terminal launcher. { + name = "terminal"; path = "${pkgs.weston}/bin/weston-terminal"; icon = "${pkgs.weston}/share/weston/icon_terminal.png"; } ]; in { - options.ghaf.graphics.weston = with lib; { - launchers = mkOption { - description = "Weston application launchers to show in launch bar"; - default = []; - type = with types; - listOf - (submodule { - options.path = mkOption { - description = "Path to the executable to be launched"; - type = path; - }; - options.icon = mkOption { - description = "Path of the icon"; - type = path; - }; - }); - }; - enableDemoApplications = mkEnableOption "some applications for demoing"; - }; - config = lib.mkIf cfg.enable { - ghaf.graphics.weston.launchers = defaultLauncher; + ghaf.graphics.launchers = defaultLauncher; environment.etc."xdg/weston/weston.ini" = { text = '' @@ -79,7 +63,7 @@ in { font-size=16 '' - + mkLaunchers cfg.launchers; + + mkLaunchers config.ghaf.graphics.launchers; # The UNIX file mode bits mode = "0644"; diff --git a/modules/hardware/definition.nix b/modules/hardware/definition.nix index b32394669..3d61a1a7e 100644 --- a/modules/hardware/definition.nix +++ b/modules/hardware/definition.nix @@ -29,6 +29,13 @@ PCI Product ID (optional) ''; }; + name = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + PCI device name (optional) + ''; + }; }; }; in { diff --git a/modules/profiles/applications.nix b/modules/profiles/applications.nix index d3aafb1ff..c891708cc 100644 --- a/modules/profiles/applications.nix +++ b/modules/profiles/applications.nix @@ -20,7 +20,7 @@ in # across different window managers. ghaf = { profiles.graphics.enable = true; - graphics.weston.enableDemoApplications = true; + graphics.enableDemoApplications = true; }; }; } diff --git a/modules/profiles/graphics.nix b/modules/profiles/graphics.nix index 7018b6238..afbdb9929 100644 --- a/modules/profiles/graphics.nix +++ b/modules/profiles/graphics.nix @@ -23,6 +23,30 @@ in }; }; + options.ghaf.graphics = with lib; { + launchers = mkOption { + description = "Labwc application launchers to show in launch bar"; + default = []; + type = with types; + listOf + (submodule { + options.name = mkOption { + description = "Name of the application"; + type = str; + }; + options.path = mkOption { + description = "Path to the executable to be launched"; + type = path; + }; + options.icon = mkOption { + description = "Path of the icon"; + type = path; + }; + }); + }; + enableDemoApplications = mkEnableOption "some applications for demoing"; + }; + config = mkIf cfg.enable { ghaf.graphics.weston.enable = cfg.compositor == "weston"; ghaf.graphics.gnome.enable = cfg.compositor == "gnome"; diff --git a/modules/windows-launcher/default.nix b/modules/windows-launcher/default.nix index e9f555156..4c9df80e6 100644 --- a/modules/windows-launcher/default.nix +++ b/modules/windows-launcher/default.nix @@ -28,8 +28,9 @@ in { }; config = lib.mkIf cfg.enable { - ghaf.graphics.weston.launchers = lib.mkIf (!cfg.spice) [ + ghaf.graphics.launchers = lib.mkIf (!cfg.spice) [ { + name = "windows"; path = "${windows-launcher}/bin/windows-launcher-ui"; icon = "${pkgs.gnome.adwaita-icon-theme}/share/icons/Adwaita/16x16/mimetypes/application-x-executable.png"; } diff --git a/overlays/custom-packages/labwc/default.nix b/overlays/custom-packages/labwc/default.nix index 1d742ce2a..57987c256 100644 --- a/overlays/custom-packages/labwc/default.nix +++ b/overlays/custom-packages/labwc/default.nix @@ -21,7 +21,7 @@ substituteInPlace ../docs/autostart \ --replace swaybg ${final.swaybg}/bin/swaybg \ --replace kanshi ${final.kanshi}/bin/kanshi \ - --replace waybar ${final.waybar}/bin/waybar \ + --replace waybar "${final.waybar}/bin/waybar -s /etc/waybar/style.css -c /etc/waybar/config" \ --replace mako ${final.mako}/bin/mako \ --replace swayidle ${final.swayidle}/bin/swayidle diff --git a/packages/wifi-signal-strength/default.nix b/packages/wifi-signal-strength/default.nix new file mode 100644 index 000000000..c463bbdb7 --- /dev/null +++ b/packages/wifi-signal-strength/default.nix @@ -0,0 +1,62 @@ +# Copyright 2022-2024 TII (SSRC) and the Ghaf contributors +# SPDX-License-Identifier: Apache-2.0 +{ + stdenvNoCC, + pkgs, + lib, + wifiDevice, + ... +}: let + # Replace the IP address with "net-vm.ghaf" after DNS/DHCP module merge + netvm_address = "192.168.100.1"; + wifiSignalStrength = + pkgs.writeShellScript + "wifi-signal-strength" + '' + export DBUS_SESSION_BUS_ADDRESS=unix:path=/tmp/ssh_session_dbus.sock + export DBUS_SYSTEM_BUS_ADDRESS=unix:path=/tmp/ssh_system_dbus.sock + ${pkgs.openssh}/bin/ssh -M -S /tmp/nmcli_socket \ + -f -N -q ghaf@${netvm_address} \ + -i /run/waypipe-ssh/id_ed25519 \ + -o StrictHostKeyChecking=no \ + -o StreamLocalBindUnlink=yes \ + -o ExitOnForwardFailure=yes \ + -L /tmp/ssh_session_dbus.sock:/run/user/1000/bus \ + -L /tmp/ssh_system_dbus.sock:/run/dbus/system_bus_socket + signal0=󰤟 + signal1=󰤢 + signal2=󰤥 + signal3=󰤨 + no_signal=󰤭 + # Get IP address of netvm + address=$(${pkgs.networkmanager}/bin/nmcli device show ${wifiDevice} | ${pkgs.gawk}/bin/awk '{ if ($1=="IP4.ADDRESS[1]:") {print $2}}') + # Get signal strength and ssid + connection=($(${pkgs.networkmanager}/bin/nmcli -f IN-USE,SIGNAL,SSID dev wifi | ${pkgs.gawk}/bin/awk '/^\*/{if (NR!=1) {print $2; print $3}}')) + connection[0]=$(if [ -z ''${connection[0]} ]; then echo "-1"; else echo ''${connection[0]}; fi) + # Set the icon of signal level + signal_level=$(if [ ''${connection[0]} -gt 80 ]; then echo $signal3; elif [ ''${connection[0]} -gt 60 ]; then echo $signal2; elif [ ''${connection[0]} -gt 30 ]; then echo $signal1; elif [ ''${connection[0]} -gt 0 ]; then echo signal0; else echo $no_signal; fi) + tooltip=$(if [ -z ''${connection[1]} ]; then echo "No connection"; else echo ''${connection[1]} ''${connection[0]}%; fi) + text=$(if [ -z $address ]; then echo $signal_level; else echo $address $signal_level; fi) + # Return as json format for waybar + echo "{\"percentage\":\""''${connection[0]}"\", \"text\":\""$text"\", \"tooltip\":\""$tooltip"\", \"class\":\"1\"}" + # Use the control socket to close the ssh tunnel. + ${pkgs.openssh}/bin/ssh -q -S /tmp/nmcli_socket -O exit ghaf@${netvm_address} + ''; +in + stdenvNoCC.mkDerivation { + name = "wifi-signal-strength"; + + phases = ["installPhase"]; + + installPhase = '' + mkdir -p $out/bin + cp ${wifiSignalStrength} $out/bin/wifi-signal-strength + ''; + + meta = with lib; { + description = "Script to get wifi data from nmcli to show network of netvm using D-Bus over SSH on Waybar."; + platforms = [ + "x86_64-linux" + ]; + }; + } diff --git a/targets/lenovo-x1-carbon.nix b/targets/lenovo-x1-carbon.nix index fe2b7cb78..df5405dd7 100644 --- a/targets/lenovo-x1-carbon.nix +++ b/targets/lenovo-x1-carbon.nix @@ -20,6 +20,7 @@ path = "0000:00:14.3"; vendorId = "8086"; productId = "51f1"; + name = "wlp0s5f0"; } ]; gpu.pciDevices = [ @@ -113,6 +114,7 @@ ]; guivmConfig = hostConfiguration.config.ghaf.virtualization.microvm.guivm; winConfig = hostConfiguration.config.ghaf.windows-launcher; + networkDevice = hostConfiguration.config.ghaf.hardware.definition.network.pciDevices; guivmExtraModules = [ { # Early KMS needed for GNOME to work inside GuiVM @@ -128,31 +130,40 @@ # Lenovo X1 AC adapter "-device" "acad" + # Connect sound device to hosts pulseaudio socket + "-audiodev" + "pa,id=pa1,server=unix:/run/pulse/native" ]; } ({pkgs, ...}: { - ghaf.graphics.weston.launchers = [ + ghaf.hardware.definition.network.pciDevices = networkDevice; + ghaf.graphics.launchers = [ { + name = "chromium"; path = "${pkgs.openssh}/bin/ssh -i /run/waypipe-ssh/id_ed25519 -o StrictHostKeyChecking=no chromium-vm.ghaf ${pkgs.waypipe}/bin/waypipe --border \"#ff5733,5\" --vsock -s ${toString guivmConfig.waypipePort} server chromium --enable-features=UseOzonePlatform --ozone-platform=wayland"; icon = "${../assets/icons/png/browser.png}"; } { + name = "gala"; path = "${pkgs.openssh}/bin/ssh -i /run/waypipe-ssh/id_ed25519 -o StrictHostKeyChecking=no gala-vm.ghaf ${pkgs.waypipe}/bin/waypipe --border \"#33ff57,5\" --vsock -s ${toString guivmConfig.waypipePort} server gala --enable-features=UseOzonePlatform --ozone-platform=wayland"; icon = "${../assets/icons/png/app.png}"; } { + name = "zathura"; path = "${pkgs.openssh}/bin/ssh -i /run/waypipe-ssh/id_ed25519 -o StrictHostKeyChecking=no zathura-vm.ghaf ${pkgs.waypipe}/bin/waypipe --border \"#337aff,5\" --vsock -s ${toString guivmConfig.waypipePort} server zathura"; icon = "${../assets/icons/png/pdf.png}"; } { + name = "windows"; path = "${pkgs.virt-viewer}/bin/remote-viewer -f spice://${winConfig.spice-host}:${toString winConfig.spice-port}"; icon = "${../assets/icons/png/windows.png}"; } { + name = "nm-launcher"; path = "${pkgs.nm-launcher}/bin/nm-launcher"; icon = "${pkgs.networkmanagerapplet}/share/icons/hicolor/22x22/apps/nm-device-wwan.png"; } diff --git a/targets/nvidia-jetson-orin/default.nix b/targets/nvidia-jetson-orin/default.nix index 6cd20effc..c8ecf69f2 100644 --- a/targets/nvidia-jetson-orin/default.nix +++ b/targets/nvidia-jetson-orin/default.nix @@ -107,7 +107,7 @@ hostConfiguration = tgt.hostConfiguration.extendModules { modules = [ { - ghaf.graphics.weston.enableDemoApplications = lib.mkForce false; + ghaf.graphics.enableDemoApplications = lib.mkForce false; } ]; };