Unverified Commit baa35379 authored by Arne Keller's avatar Arne Keller Committed by GitHub
Browse files

opengamepadui: init at 0.35.7 (#368017)

parents f0413353 931f637c
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -121,6 +121,8 @@

- [nvidia-gpu](https://github.com/utkuozdemir/nvidia_gpu_exporter), a Prometheus exporter that scrapes `nvidia-smi` for GPU metrics. Available as [services.prometheus.exporters.nvidia-gpu](#opt-services.prometheus.exporters.nvidia-gpu.enable).

- [OpenGamepadUI](https://github.com/ShadowBlip/OpenGamepadUI/), an open source gamepad-native game launcher and overlay for Linux. Available as [programs.opengamepadui](#opt-programs.opengamepadui.enable).

- [InputPlumber](https://github.com/ShadowBlip/InputPlumber/), an open source input router and remapper daemon for Linux. Available as [services.inputplumber](#opt-services.inputplumber.enable).

- [PowerStation](https://github.com/ShadowBlip/PowerStation/), an open source TDP control and performance daemon with DBus interface for Linux. Available as [services.powerstation](#opt-services.powerstation.enable).
+1 −0
Original line number Diff line number Diff line
@@ -267,6 +267,7 @@
  ./programs/ns-usbloader.nix
  ./programs/oblogout.nix
  ./programs/oddjobd.nix
  ./programs/opengamepadui.nix
  ./programs/openvpn3.nix
  ./programs/obs-studio.nix
  ./programs/partition-manager.nix
+271 −0
Original line number Diff line number Diff line
{
  config,
  lib,
  pkgs,
  ...
}:

let
  cfg = config.programs.opengamepadui;
  gamescopeCfg = config.programs.gamescope;

  opengamepadui-gamescope =
    let
      exports = lib.mapAttrsToList (n: v: "export ${n}=${v}") cfg.gamescopeSession.env;
    in
    # Based on gamescope-session-plus from ChimeraOS
    pkgs.writeShellScriptBin "opengamepadui-gamescope" ''
      ${builtins.concatStringsSep "\n" exports}

      # Enable Mangoapp
      export MANGOHUD_CONFIGFILE=$(mktemp /tmp/mangohud.XXXXXXXX)
      export RADV_FORCE_VRS_CONFIG_FILE=$(mktemp /tmp/radv_vrs.XXXXXXXX)

      # Plop GAMESCOPE_MODE_SAVE_FILE into $XDG_CONFIG_HOME (defaults to ~/.config).
      export GAMESCOPE_MODE_SAVE_FILE="''${XDG_CONFIG_HOME:-$HOME/.config}/gamescope/modes.cfg"
      export GAMESCOPE_PATCHED_EDID_FILE="''${XDG_CONFIG_HOME:-$HOME/.config}/gamescope/edid.bin"

      # Make path to gamescope mode save file.
      mkdir -p "$(dirname "$GAMESCOPE_MODE_SAVE_FILE")"
      touch "$GAMESCOPE_MODE_SAVE_FILE"

      # Make path to Gamescope edid patched file.
      mkdir -p "$(dirname "$GAMESCOPE_PATCHED_EDID_FILE")"
      touch "$GAMESCOPE_PATCHED_EDID_FILE"

      # Initially write no_display to our config file
      # so we don't get mangoapp showing up before OpenGamepadUI initializes
      # on OOBE and stuff.
      mkdir -p "$(dirname "$MANGOHUD_CONFIGFILE")"
      echo "no_display" >"$MANGOHUD_CONFIGFILE"

      # Prepare our initial VRS config file
      # for dynamic VRS in Mesa.
      mkdir -p "$(dirname "$RADV_FORCE_VRS_CONFIG_FILE")"
      echo "1x1" >"$RADV_FORCE_VRS_CONFIG_FILE"

      # To play nice with the short term callback-based limiter for now
      export GAMESCOPE_LIMITER_FILE=$(mktemp /tmp/gamescope-limiter.XXXXXXXX)

      ulimit -n 524288

      # Setup socket for gamescope
      # Create run directory file for startup and stats sockets
      tmpdir="$([[ -n ''${XDG_RUNTIME_DIR+x} ]] && mktemp -p "$XDG_RUNTIME_DIR" -d -t gamescope.XXXXXXX)"
      socket="''${tmpdir:+$tmpdir/startup.socket}"
      stats="''${tmpdir:+$tmpdir/stats.pipe}"

      # Fail early if we don't have a proper runtime directory setup
      if [[ -z $tmpdir || -z ''${XDG_RUNTIME_DIR+x} ]]; then
        echo >&2 "!! Failed to find run directory in which to create stats session sockets (is \$XDG_RUNTIME_DIR set?)"
        exit 0
      fi

      export GAMESCOPE_STATS="$stats"
      mkfifo -- "$stats"
      mkfifo -- "$socket"

      # Start gamescope compositor, log it's output and background it
      echo gamescope ${lib.escapeShellArgs cfg.gamescopeSession.args}  -R $socket -T $stats >"$HOME"/.gamescope-cmd.log
      gamescope ${lib.escapeShellArgs cfg.gamescopeSession.args} -R $socket -T $stats >"$HOME"/.gamescope-stdout.log 2>&1 &
      gamescope_pid="$!"

      if read -r -t 3 response_x_display response_wl_display <>"$socket"; then
        export DISPLAY="$response_x_display"
        export GAMESCOPE_WAYLAND_DISPLAY="$response_wl_display"
        # We're done!
      else
        echo "gamescope failed"
        kill -9 "$gamescope_pid"
        wait -n "$gamescope_pid"
        exit 1
        # Systemd or Session manager will have to restart session
      fi

      # If we have mangoapp binary start it
      if command -v mangoapp >/dev/null; then
        (while true; do
          sleep 1
          mangoapp >"$HOME"/.mangoapp-stdout.log 2>&1
        done) &
      fi

      # Start OpenGamepadUI
      opengamepadui ${lib.escapeShellArgs cfg.args}

      # When the client exits, kill gamescope nicely
      kill $gamescope_pid
    '';

  gamescopeSessionFile =
    (pkgs.writeTextDir "share/wayland-sessions/opengamepadui.desktop" ''
      [Desktop Entry]
      Name=opengamepadui
      Comment=OpenGamepadUI Session
      Exec=${opengamepadui-gamescope}/bin/opengamepadui-gamescope
      Type=Application
    '').overrideAttrs
      (_: {
        passthru.providedSessions = [ "opengamepadui" ];
      });
in
{
  options.programs.opengamepadui = {
    enable = lib.mkEnableOption "opengamepadui";

    args = lib.mkOption {
      type = lib.types.listOf lib.types.str;
      default = [ ];
      description = ''
        Arguments to be passed to OpenGamepadUI
      '';
    };

    package = lib.mkPackageOption pkgs "OpenGamepadUI" {
      default = [ "opengamepadui" ];
    };

    extraPackages = lib.mkOption {
      type = lib.types.listOf lib.types.package;
      default = [ ];
      example = lib.literalExpression ''
        with pkgs; [
          gamescope
        ]
      '';
      description = ''
        Additional packages to add to the OpenGamepadUI environment.
      '';
    };

    fontPackages = lib.mkOption {
      type = lib.types.listOf lib.types.package;
      default = config.fonts.packages;
      defaultText = lib.literalExpression "builtins.filter lib.types.package.check config.fonts.packages";
      example = lib.literalExpression "with pkgs; [ source-han-sans ]";
      description = ''
        Font packages to use in OpenGamepadUI.

        Defaults to system fonts, but could be overridden to use other fonts — useful for users who would like to customize CJK fonts used in opengamepadui. According to the [upstream issue](https://github.com/ValveSoftware/opengamepadui-for-linux/issues/10422#issuecomment-1944396010), opengamepadui only follows the per-user fontconfig configuration.
      '';
    };

    gamescopeSession = lib.mkOption {
      description = "Run a GameScope driven OpenGamepadUI session from your display-manager";
      default = { };
      type = lib.types.submodule {
        options = {
          enable = lib.mkEnableOption "GameScope Session";
          args = lib.mkOption {
            type = lib.types.listOf lib.types.str;
            default = [
              "--prefer-output"
              "*,eDP-1"
              "--xwayland-count"
              "2"
              "--default-touch-mode"
              "4"
              "--hide-cursor-delay"
              "3000"
              "--fade-out-duration"
              "200"
              "--steam"
            ];
            description = ''
              Arguments to be passed to GameScope for the session.
            '';
          };

          env = lib.mkOption {
            type = lib.types.attrsOf lib.types.str;
            default = { };
            description = ''
              Environmental variables to be passed to GameScope for the session.
            '';
          };
        };
      };
    };

    inputplumber.enable = lib.mkEnableOption ''
      Run InputPlumber service for input management and gamepad configuration.
    '';

    powerstation.enable = lib.mkEnableOption ''
      Run PowerStation service for TDP control and performance settings.
    '';
  };

  config = lib.mkIf cfg.enable {
    hardware.graphics = {
      # this fixes the "glXChooseVisual failed" bug, context: https://github.com/NixOS/nixpkgs/issues/47932
      enable = true;
      enable32Bit = pkgs.stdenv.hostPlatform.isx86_64;
    };

    security.wrappers = lib.mkIf (cfg.gamescopeSession.enable && gamescopeCfg.capSysNice) {
      # needed or steam plugin fails
      bwrap = {
        owner = "root";
        group = "root";
        source = lib.getExe pkgs.bubblewrap;
        setuid = true;
      };
    };

    programs.opengamepadui.extraPackages = cfg.fontPackages;

    programs.gamescope.enable = true;
    services.displayManager.sessionPackages = lib.mkIf cfg.gamescopeSession.enable [
      gamescopeSessionFile
    ];

    programs.opengamepadui.gamescopeSession.env = {
      # Fix intel color corruption
      # might come with some performance degradation but is better than a corrupted
      # color image
      INTEL_DEBUG = "norbc";
      mesa_glthread = "true";
      # This should be used by default by gamescope. Cannot hurt to force it anyway.
      # Reported better framelimiting with this enabled
      ENABLE_GAMESCOPE_WSI = "1";
      # Force Qt applications to run under xwayland
      QT_QPA_PLATFORM = "xcb";
      # Some environment variables by default (taken from Deck session)
      SDL_VIDEO_MINIMIZE_ON_FOCUS_LOSS = "0";
      # There is no way to set a color space for an NV12
      # buffer in Wayland. And the color management protocol that is
      # meant to let this happen is missing the color range...
      # So just workaround this with an ENV var that Remote Play Together
      # and Gamescope will use for now.
      GAMESCOPE_NV12_COLORSPACE = "k_EStreamColorspace_BT601";
      # Workaround older versions of vkd3d-proton setting this
      # too low (desc.BufferCount), resulting in symptoms that are potentially like
      # swapchain starvation.
      VKD3D_SWAPCHAIN_LATENCY_FRAMES = "3";
      # To expose vram info from radv
      WINEDLLOVERRIDES = "dxgi=n";
      # Don't wait for buffers to idle on the client side before sending them to gamescope
      vk_xwayland_wait_ready = "false";
      # Temporary crutch until dummy plane interactions / etc are figured out
      GAMESCOPE_DISABLE_ASYNC_FLIPS = "1";
    };

    # optionally enable 32bit pulseaudio support if pulseaudio is enabled
    services.pulseaudio.support32Bit = config.services.pulseaudio.enable;
    services.pipewire.alsa.support32Bit = config.services.pipewire.alsa.enable;

    hardware.steam-hardware.enable = true;

    services.inputplumber.enable = lib.mkDefault cfg.inputplumber.enable;
    services.powerstation.enable = lib.mkDefault cfg.powerstation.enable;

    environment.pathsToLink = [ "/share" ];

    environment.systemPackages = [
      cfg.package
    ] ++ lib.optional cfg.gamescopeSession.enable opengamepadui-gamescope;
  };

  meta.maintainers = with lib.maintainers; [ shadowapex ];
}
+117 −0
Original line number Diff line number Diff line
{
  alsa-lib,
  autoPatchelfHook,
  cargo,
  dbus,
  fetchFromGitHub,
  gamescope,
  godot_4,
  godot_4-export-templates,
  hwdata,
  lib,
  libGL,
  libpulseaudio,
  mesa-demos,
  nix-update-script,
  pkg-config,
  rustPlatform,
  stdenv,
  udev,
  upower,
  vulkan-loader,
  xorg,
  withDebug ? false,
}:

stdenv.mkDerivation (finalAttrs: {
  pname = "opengamepadui";
  version = "0.35.7";

  buildType = if withDebug then "debug" else "release";

  src = fetchFromGitHub {
    owner = "ShadowBlip";
    repo = "OpenGamepadUI";
    tag = "v${finalAttrs.version}";
    hash = "sha256-yOFxtDDYdxCtN8L8RtXgxebjRwK1Blsp/RG1E+UfUEI=";
  };

  cargoDeps = rustPlatform.fetchCargoVendor {
    inherit (finalAttrs) src;
    sourceRoot = "source/${finalAttrs.cargoRoot}";
    hash = "sha256-sTzMewIfKHbmVhSPZgUIzFFz1ahK+PMoQ5oB4GEt8nY=";
  };
  cargoRoot = "extensions";

  nativeBuildInputs = [
    autoPatchelfHook
    cargo
    godot_4
    godot_4-export-templates
    pkg-config
    rustPlatform.cargoSetupHook
  ];

  runtimeDependencies = [
    alsa-lib
    dbus
    gamescope
    hwdata
    libGL
    libpulseaudio
    mesa-demos
    udev
    upower
    vulkan-loader
    xorg.libX11
    xorg.libXcursor
    xorg.libXext
    xorg.libXi
    xorg.libXrandr
    xorg.libXres
    xorg.libXtst
  ];

  dontStrip = withDebug;

  env =
    let
      versionAndRelease = lib.splitString "-" godot_4.version;
    in
    {
      GODOT = lib.getExe godot_4;
      GODOT_VERSION = lib.elemAt versionAndRelease 0;
      GODOT_RELEASE = lib.elemAt versionAndRelease 1;
      EXPORT_TEMPLATE = "${godot_4-export-templates}";
      BUILD_TYPE = "${finalAttrs.buildType}";
    };

  makeFlags = [ "PREFIX=$(out)" ];

  buildFlags = [ "build" ];

  preBuild = ''
    # Godot looks for export templates in HOME
    export HOME=$(mktemp -d)
    mkdir -p $HOME/.local/share/godot/export_templates
    ln -s "${godot_4-export-templates}" "$HOME/.local/share/godot/export_templates/$GODOT_VERSION.$GODOT_RELEASE"
  '';

  postInstall = ''
    # The Godot binary looks in "../lib" for gdextensions
    mkdir -p $out/share/lib
    mv $out/share/opengamepadui/*.so $out/share/lib
  '';

  passthru.updateScript = nix-update-script { };

  meta = {
    description = "Open source gamepad-native game launcher and overlay";
    homepage = "https://github.com/ShadowBlip/OpenGamepadUI";
    license = lib.licenses.gpl3Only;
    platforms = [ "x86_64-linux" ];
    changelog = "https://github.com/ShadowBlip/OpenGamepadUI/releases/tag/v${finalAttrs.version}";
    maintainers = with lib.maintainers; [ shadowapex ];
    mainProgram = "opengamepadui";
  };
})