{ config, lib, pkgs, ... }: with lib; let cfg = config.roles.kodi; in { options.roles.kodi = { enable = mkEnableOption "Enable Kodi"; autologin = mkOption { default = false; }; wayland = mkOption { default = true; }; jellyfinScaleFactor = mkOption { type = types.nullOr types.float; default = null; description = "Scale factor for Jellyfin Media Player UI (e.g., 1.5 for 150% scaling)"; }; stremioScaleFactor = mkOption { type = types.nullOr types.float; default = null; description = "Scale factor for Stremio UI (e.g., 2.0 for 200% scaling). Sets STREMIO_FORCE_DEVICE_SCALE_FACTOR which passes --force-device-scale-factor to the CEF subprocess via a source patch."; }; appLauncherServer = { enable = mkOption { type = types.bool; default = false; description = "Enable HTTP app launcher server for remote control"; }; port = mkOption { type = types.int; default = 8081; description = "Port for the app launcher HTTP server"; }; }; }; config = let kodiBasePkg = if cfg.wayland then pkgs.kodi-wayland else pkgs.kodi; kodiPkg = kodiBasePkg.withPackages (pkgs: with pkgs; [ jellyfin steam-launcher steam-library youtube ]); jellyfinMediaPlayerPkg = if cfg.jellyfinScaleFactor != null then pkgs.symlinkJoin { name = "jellyfin-media-player-scaled"; paths = [ pkgs.qt-pinned.jellyfin-media-player ]; nativeBuildInputs = [ pkgs.makeWrapper ]; postBuild = '' mkdir -p $out/bin rm -f $out/bin/jellyfin-desktop makeWrapper ${pkgs.qt-pinned.jellyfin-media-player}/bin/jellyfin-desktop $out/bin/jellyfin-desktop \ --add-flags "--tv --scale-factor ${toString cfg.jellyfinScaleFactor}" # Update .desktop file to include scale factor and TV mode arguments mkdir -p $out/share/applications rm -f $out/share/applications/org.jellyfin.JellyfinDesktop.desktop substitute ${pkgs.qt-pinned.jellyfin-media-player}/share/applications/org.jellyfin.JellyfinDesktop.desktop \ $out/share/applications/org.jellyfin.JellyfinDesktop.desktop \ --replace-fail "Exec=jellyfin-desktop" "Exec=jellyfin-desktop --tv --scale-factor ${toString cfg.jellyfinScaleFactor}" ''; } else pkgs.qt-pinned.jellyfin-media-player; # Format float for env var: 2.0 -> "2", 1.5 -> "1.5" # (Nix's toString 2.0 gives "2.000000" which is unclean) formatScale = sf: let intPart = builtins.floor sf; in if sf == intPart then builtins.toString intPart else let s = builtins.toString sf; m = builtins.match "([0-9]+)\\.([0-9]*[1-9])0*" s; in if m != null then (builtins.elemAt m 0) + "." + (builtins.elemAt m 1) else s; # stremio-linux-shell is CEF-based. We patch it (via flake overlay) to read # STREMIO_FORCE_DEVICE_SCALE_FACTOR and pass it to CEF as # --force-device-scale-factor, which is the standard Chromium flag for HiDPI. stremioPkg = if cfg.stremioScaleFactor != null then pkgs.symlinkJoin { name = "stremio-scaled"; paths = [ pkgs.stremio-linux-shell ]; nativeBuildInputs = [ pkgs.makeWrapper ]; postBuild = '' mkdir -p $out/bin rm -f $out/bin/stremio makeWrapper ${pkgs.stremio-linux-shell}/bin/stremio $out/bin/stremio \ --set STREMIO_FORCE_DEVICE_SCALE_FACTOR ${formatScale cfg.stremioScaleFactor} # Update .desktop file to set the env var mkdir -p $out/share/applications rm -f $out/share/applications/com.stremio.Stremio.desktop substitute ${pkgs.stremio-linux-shell}/share/applications/com.stremio.Stremio.desktop \ $out/share/applications/com.stremio.Stremio.desktop \ --replace-fail "Exec=sh -c " "Exec=env STREMIO_FORCE_DEVICE_SCALE_FACTOR=${formatScale cfg.stremioScaleFactor} sh -c " ''; } else pkgs.stremio-linux-shell; in mkIf cfg.enable { users.extraUsers.kodi = { isNormalUser = true; extraGroups = [ "wheel" "networkmanager" "audio" "video" ]; }; networking.firewall = { allowedTCPPorts = [ 8080 ] ++ optional cfg.appLauncherServer.enable cfg.appLauncherServer.port; allowedUDPPorts = [ 8080 ]; }; environment.systemPackages = with pkgs; [ firefox jellyfinMediaPlayerPkg kodiPkg stremioPkg # qt-pinned.stremio # Replaced by stremio-linux-shell (Rust/CEF-based) wget ] ++ optional cfg.appLauncherServer.enable pkgs.custom.app-launcher-server; nixpkgs.config.permittedInsecurePackages = lib.warn "Allowing insecure package qtwebengine-5.15.19 as a jellyfin-media-player dependency. These are pinned to nixpkgs-qt to avoid rebuilds - update that input separately when you have time." [ "qtwebengine-5.15.19" ]; programs.kdeconnect.enable = true; systemd.user.services = mkIf cfg.appLauncherServer.enable { app-launcher-server = { description = "HTTP App Launcher Server"; wantedBy = [ "graphical-session.target" ]; after = [ "graphical-session.target" ]; serviceConfig = { Type = "simple"; ExecStart = "${pkgs.custom.app-launcher-server}/bin/app-launcher-server ${toString cfg.appLauncherServer.port}"; Restart = "always"; RestartSec = "5s"; Environment = [ "PATH=${pkgs.firefox}/bin:${kodiPkg}/bin:/run/current-system/sw/bin" ]; }; }; }; services.displayManager = mkIf cfg.autologin { autoLogin = { enable = true; user = "kodi"; }; }; # Kodi user home manager configuration home-manager.users.kodi = { home.stateVersion = "24.05"; # Auto-start Kodi in fullscreen after login xsession = mkIf (!cfg.wayland) { windowManager.command = '' ${kodiPkg}/bin/kodi --fullscreen ''; }; # Use a simple .xsession for X11 or a wayland startup script home.file = mkIf cfg.wayland { ".config/autostart/kodi.desktop".text = '' [Desktop Entry] Type=Application Name=Kodi Exec=${kodiPkg}/bin/kodi --fullscreen X-GNOME-Autostart-enabled=true Hidden=false ''; }; }; }; }