feat(home): package Claude Desktop and T3 Code, with per-host UI scaling

Add two Electron AI desktop apps built from their official upstream
binaries and wire them into the home config (x86_64 only):

- claude-desktop: Anthropic's official Linux .deb, unpacked via dpkg +
  autoPatchelfHook. Wrapper disables the SUID sandbox (nix store can't
  set it up) and adds the NixOS GL driver path so it hardware-accelerates
  instead of falling back to software rendering.
- t3-code: pingdotgg/t3code AppImage via appimageTools.wrapType2, with
  desktop entry + hicolor icons.

Both share a new custom.aiApps.deviceScaleFactor option feeding
--force-device-scale-factor; konishi's 4K@1x monitors set it to "1.5",
arrakis stays native.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Miguel Palhas
2026-07-16 14:06:47 +01:00
parent f64cc3a803
commit 938a900fbe
6 changed files with 259 additions and 13 deletions
+37 -13
View File
@@ -1,20 +1,44 @@
{ pkgs, inputs, ... }:
{ {
home = { config,
packages = with pkgs; [ pkgs,
inputs.claude-code.packages.${pkgs.stdenv.hostPlatform.system}.default inputs,
lib,
...
}:
let
# Official Anthropic Claude desktop app (Linux build, x86_64 only).
claude-desktop = pkgs.callPackage ../../../../pkgs/claude-desktop/package.nix {
inherit (config.custom.aiApps) deviceScaleFactor;
};
in
{
# Shared UI-scale knob for the Electron AI desktop apps (Claude Desktop, T3 Code).
# Set per-host (e.g. konishi's 4K@1x monitors want ~"1.5"); null = native scale.
options.custom.aiApps.deviceScaleFactor = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
example = "1.5";
description = "--force-device-scale-factor value for Claude Desktop and T3 Code.";
};
# sandbox config.home = {
bubblewrap packages =
socat with pkgs;
libseccomp [
inputs.claude-code.packages.${pkgs.stdenv.hostPlatform.system}.default
# voice # sandbox
sox bubblewrap
socat
libseccomp
# beads # voice
dolt sox
];
# beads
dolt
]
++ lib.optional pkgs.stdenv.hostPlatform.isx86_64 claude-desktop;
file = { file = {
".default-npm-packages".text = '' ".default-npm-packages".text = ''
+1
View File
@@ -5,6 +5,7 @@
{ {
imports = [ imports = [
./ralph-claude-code.nix ./ralph-claude-code.nix
./t3-code.nix
]; ];
programs = { programs = {
+16
View File
@@ -0,0 +1,16 @@
{
config,
pkgs,
lib,
...
}:
let
# T3 Code: open-source desktop control plane for coding agents (x86_64 only).
# Shares the custom.aiApps.deviceScaleFactor knob (defined in ../claude).
t3-code = pkgs.callPackage ../../../pkgs/t3-code/package.nix {
inherit (config.custom.aiApps) deviceScaleFactor;
};
in
{
home.packages = lib.optional pkgs.stdenv.hostPlatform.isx86_64 t3-code;
}
+3
View File
@@ -35,6 +35,9 @@
retroarch = true; retroarch = true;
}; };
# 4K@1x monitors render the Electron AI apps tiny; scale their UI up. Tune to taste.
custom.aiApps.deviceScaleFactor = "1.5";
wayland.windowManager.hyprland.settings = { wayland.windowManager.hyprland.settings = {
exec-once = [ exec-once = [
# boot-into-lock: paired with SDDM autologin (host config), lock the # boot-into-lock: paired with SDDM autologin (host config), lock the
+161
View File
@@ -0,0 +1,161 @@
{
lib,
stdenv,
fetchurl,
autoPatchelfHook,
dpkg,
makeWrapper,
wrapGAppsHook3,
# runtime / autopatchelf libs
alsa-lib,
at-spi2-atk,
at-spi2-core,
atk,
cairo,
cups,
dbus,
expat,
gdk-pixbuf,
glib,
gtk3,
libcap_ng,
libGL,
libdrm,
libnotify,
libseccomp,
libsecret,
libuuid,
libxkbcommon,
mesa,
nspr,
nss,
pango,
systemd,
xdg-utils,
libX11,
libxcb,
libxcomposite,
libxcursor,
libxdamage,
libxext,
libxfixes,
libxi,
libxkbfile,
libxrandr,
libxscrnsaver,
libxshmfence,
libxtst,
# UI scaling: null = native, or a string like "1.5" for --force-device-scale-factor.
deviceScaleFactor ? null,
}:
stdenv.mkDerivation (finalAttrs: {
pname = "claude-desktop";
version = "1.21459.3";
# Official Anthropic Linux build, pulled straight from their apt pool.
# To bump: find the newest entry in
# https://downloads.claude.ai/claude-desktop/apt/stable/dists/stable/main/binary-amd64/Packages
# then update version + hash (nix hash convert --to sri the listed SHA256).
src = fetchurl {
url = "https://downloads.claude.ai/claude-desktop/apt/stable/pool/main/c/claude-desktop/claude-desktop_${finalAttrs.version}_amd64.deb";
hash = "sha256-My0k7439p83WM+9iTvM1zrMatUtHHijXhavGAWrmqb4=";
};
nativeBuildInputs = [
autoPatchelfHook
dpkg
makeWrapper
wrapGAppsHook3
];
buildInputs = [
alsa-lib
at-spi2-atk
at-spi2-core
atk
cairo
cups
dbus
expat
gdk-pixbuf
glib
gtk3
libcap_ng
libGL
libdrm
libnotify
libseccomp
libsecret
libuuid
libxkbcommon
mesa
nspr
nss
pango
stdenv.cc.cc.lib
systemd
libX11
libxcb
libxcomposite
libxcursor
libxdamage
libxext
libxfixes
libxi
libxkbfile
libxrandr
libxscrnsaver
libxshmfence
libxtst
];
# dlopen'd at runtime; make autoPatchelf bake them into the rpath.
runtimeDependencies = [
libnotify
libGL
(lib.getLib systemd)
];
unpackCmd = "dpkg-deb -x $curSrc .";
sourceRoot = ".";
dontConfigure = true;
dontBuild = true;
# gappsWrapperArgs feed into the makeWrapper call below.
dontWrapGApps = true;
installPhase = ''
runHook preInstall
mkdir -p $out
cp -r usr/lib $out/lib
cp -r usr/share $out/share
# chrome-sandbox needs setuid-root, which the nix store can't provide.
# Disable the SUID sandbox and rely on user namespaces instead.
# The bundled Chromium/ANGLE dlopen()s the system libEGL.so.1 for GL; give it
# libglvnd + NixOS's GPU driver path so it hardware-accelerates instead of
# falling back to software rendering.
makeWrapper $out/lib/claude-desktop/claude-desktop $out/bin/claude-desktop \
"''${gappsWrapperArgs[@]}" \
--add-flags "--no-sandbox${lib.optionalString (deviceScaleFactor != null) " --force-device-scale-factor=${deviceScaleFactor}"}" \
--prefix PATH : ${lib.makeBinPath [ xdg-utils ]} \
--prefix LD_LIBRARY_PATH : "${lib.makeLibraryPath [ libGL mesa ]}:/run/opengl-driver/lib"
# Point the .desktop Exec/Icon at our wrapper (names already match).
substituteInPlace $out/share/applications/com.anthropic.Claude.desktop \
--replace-fail "Exec=claude-desktop" "Exec=$out/bin/claude-desktop"
runHook postInstall
'';
meta = {
description = "Desktop application for Claude.ai (official Linux build)";
homepage = "https://claude.ai";
license = lib.licenses.unfree;
sourceProvenance = [ lib.sourceTypes.binaryNativeCode ];
platforms = [ "x86_64-linux" ];
mainProgram = "claude-desktop";
};
})
+41
View File
@@ -0,0 +1,41 @@
{
lib,
appimageTools,
fetchurl,
# UI scaling: null = native, or a string like "1.5" for --force-device-scale-factor.
deviceScaleFactor ? null,
}:
let
pname = "t3-code";
version = "0.0.28";
# T3 Code (pingdotgg/t3code): open-source control plane for coding agents.
# Ships only an x86_64 AppImage; bump version + hash from the releases page:
# https://github.com/pingdotgg/t3code/releases
src = fetchurl {
url = "https://github.com/pingdotgg/t3code/releases/download/v${version}/T3-Code-${version}-x86_64.AppImage";
hash = "sha256-+mBp+wPrJRV/HpaimQHcqBuwqZcPWTbKJVNCVW7ELgo=";
};
appimageContents = appimageTools.extract { inherit pname version src; };
in
appimageTools.wrapType2 {
inherit pname version src;
extraInstallCommands = ''
# Desktop entry + icons from the AppImage, repointed at the wrapped binary.
install -Dm444 ${appimageContents}/t3code.desktop $out/share/applications/t3code.desktop
substituteInPlace $out/share/applications/t3code.desktop \
--replace-fail "Exec=AppRun --no-sandbox" "Exec=${pname} --no-sandbox${lib.optionalString (deviceScaleFactor != null) " --force-device-scale-factor=${deviceScaleFactor}"}"
cp -r ${appimageContents}/usr/share/icons $out/share/icons
'';
meta = {
description = "Open-source control plane for coding agents (Claude Code, Codex, OpenCode, Cursor)";
homepage = "https://t3.codes";
license = lib.licenses.mit;
sourceProvenance = [ lib.sourceTypes.binaryNativeCode ];
platforms = [ "x86_64-linux" ];
mainProgram = "t3-code";
};
}