From 0ad49a328055804ac7ccb95b4d91becf81391f01 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Thu, 23 Apr 2026 12:23:25 -0500 Subject: [PATCH 1/4] citrix-workspace: rewrite desktop launchers for wrapped binaries Rewrite the generated desktop entries to call the wrapped commands from $out/bin instead of the upstream install paths. Keep selfservice as the launcher target for menu and citrixapp URI handling, and keep adapter as the ICA file handler entrypoint. --- .../remote/citrix-workspace/generic.nix | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/pkgs/applications/networking/remote/citrix-workspace/generic.nix b/pkgs/applications/networking/remote/citrix-workspace/generic.nix index 0409ad8724bc..20dca8980792 100644 --- a/pkgs/applications/networking/remote/citrix-workspace/generic.nix +++ b/pkgs/applications/networking/remote/citrix-workspace/generic.nix @@ -268,7 +268,7 @@ stdenv.mkDerivation rec { mkWrappers = lib.concatMapStringsSep "\n"; toWrap = [ - "wfica" + "adapter" "selfservice" "util/configmgr" "util/conncenter" @@ -320,6 +320,33 @@ stdenv.mkDerivation rec { echo "Copy .desktop files." cp $out/opt/citrix-icaclient/desktop/* $out/share/applications/ + for desktop in $out/share/applications/*.desktop; do + sed -i \ + -e "s#/opt/Citrix/ICAClient#$ICAInstDir#g" \ + -e "s#$ICAInstDir/util/ctxwebhelper#ctxwebhelper#g" \ + "$desktop" + + case "$(basename "$desktop")" in + citrixapp.desktop) + sed -i \ + -e 's#^TryExec=.*#TryExec=selfservice#' \ + -e 's#^Exec=.*#Exec=selfservice %u#' \ + "$desktop" + ;; + selfservice.desktop) + sed -i \ + -e 's#^TryExec=.*#TryExec=selfservice#' \ + -e 's#^Exec=.*#Exec=selfservice#' \ + "$desktop" + ;; + wfica.desktop) + sed -i \ + -e 's#^TryExec=.*#TryExec=adapter#' \ + -e 's#^Exec=.*#Exec=adapter %f#' \ + "$desktop" + ;; + esac + done # We introduce a dependency on the source file so that it need not be redownloaded everytime echo $src >> "$out/share/workspace_dependencies.pin" From dc0976b277bbad4bd583ac3dcceed53a45f211d7 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Tue, 21 Apr 2026 16:02:06 -0500 Subject: [PATCH 2/4] citrix-workspace: 25_08_10 -> 26_01_0; support 26.01 core app Unpack the vendor-bundled WebKit 4.0 runtime, skip optional EPA, device trust, and FIDO installer steps, and make 26.01 the default Citrix Workspace package. --- .../remote/citrix-workspace/generic.nix | 41 +++++++++++++++++-- .../remote/citrix-workspace/sources.nix | 9 ++++ pkgs/top-level/all-packages.nix | 3 +- 3 files changed, 49 insertions(+), 4 deletions(-) diff --git a/pkgs/applications/networking/remote/citrix-workspace/generic.nix b/pkgs/applications/networking/remote/citrix-workspace/generic.nix index 20dca8980792..cd30abc0c1c0 100644 --- a/pkgs/applications/networking/remote/citrix-workspace/generic.nix +++ b/pkgs/applications/networking/remote/citrix-workspace/generic.nix @@ -145,6 +145,8 @@ stdenv.mkDerivation rec { dontBuild = true; dontConfigure = true; + strictDeps = true; + __structuredAttrs = true; sourceRoot = "."; preferLocalBuild = true; passthru.icaroot = "${placeholder "out"}/opt/citrix-icaclient"; @@ -243,19 +245,36 @@ stdenv.mkDerivation rec { program: if (builtins.match "selfservice(.*)" program) != null then "--icaroot" - else if (builtins.match "wfica(.*)" program != null) then + else if (builtins.match "wfica(.*)" program) != null then null else "-icaroot"; + + ldLibraryPath = + program: + lib.concatStringsSep ":" ( + lib.optional (builtins.match "wfica(.*)" program != null) "$ICAInstDir" + ++ [ + "$ICAInstDir/lib" + "$ICAInstDir/usr/lib/x86_64-linux-gnu" + "$ICAInstDir/usr/lib/x86_64-linux-gnu/webkit2gtk-4.0/injected-bundle" + ] + ); + + # Only the ICA engine needs the top-level client directory on the library + # path. Leaving it enabled for UI helpers exposes Citrix's session-only + # libproxy.so to the embedded web stack, which then fails to resolve CGP + # symbols. wrap = program: '' wrapProgram $out/opt/citrix-icaclient/${program} \ ${lib.optionalString (icaFlag program != null) ''--add-flags "${icaFlag program} $ICAInstDir"''} \ --set ICAROOT "$ICAInstDir" \ --prefix GIO_EXTRA_MODULES : "${glib-networking}/lib/gio/modules" \ - --prefix LD_LIBRARY_PATH : "$ICAInstDir:$ICAInstDir/lib:$ICAInstDir/usr/lib/x86_64-linux-gnu:$ICAInstDir/usr/lib/x86_64-linux-gnu/webkit2gtk-4.0/injected-bundle" \ + --prefix LD_LIBRARY_PATH : "${ldLibraryPath program}" \ --set LD_PRELOAD "${libredirect}/lib/libredirect.so ${lib.getLib pcsclite}/lib/libpcsclite.so" \ --set NIX_REDIRECTS "/usr/share/zoneinfo=${tzdata}/share/zoneinfo:/etc/zoneinfo=${tzdata}/share/zoneinfo:/etc/timezone=$ICAInstDir/timezone:/usr/lib/x86_64-linux-gnu=$ICAInstDir/usr/lib/x86_64-linux-gnu" ''; + wrapLink = program: '' ${wrap program} ln -sf $out/opt/citrix-icaclient/${program} $out/bin/${baseNameOf program} @@ -283,10 +302,23 @@ stdenv.mkDerivation rec { export HOME=$(mktemp -d) # Run upstream installer in the store-path. - sed -i -e 's,^ANSWER="",ANSWER="$INSTALLER_YES",g' -e 's,/bin/true,true,g' -e 's, -C / , -C . ,g' ./linuxx64/hinst + sed -i \ + -e 's,^ANSWER="",ANSWER="$INSTALLER_YES",g' \ + -e 's,/bin/true,true,g' \ + -e 's, -C / , -C . ,g' \ + -e 's,^[[:space:]]*install_deviceTrust "\$ICAInstDir", :,' \ + -e 's,^[[:space:]]*install_EPA_with_prompt "\$ICAInstDir", :,' \ + -e 's,^[[:space:]]*install_fido2Service "\$CDSourceDir" "\$ICAInstDir", :,' \ + ./linuxx64/hinst source_date=$(date --utc --date=@$SOURCE_DATE_EPOCH "+%F %T") faketime -f "$source_date" ${stdenv.shell} linuxx64/hinst CDROM "$(pwd)" + mkdir -p "$ICAInstDir/usr" + tar -xzf ./linuxx64/linuxx64.cor/Webkit2gtk4.0/webkit2gtk-4.0.tar.gz \ + --strip-components=2 \ + -C "$ICAInstDir/usr" \ + webkit2gtk-4.0-package/usr/lib + if [ -f "$ICAInstDir/util/setlog" ]; then chmod +x "$ICAInstDir/util/setlog" ln -sf "$ICAInstDir/util/setlog" "$out/bin/citrix-setlog" @@ -362,6 +394,9 @@ stdenv.mkDerivation rec { ${lib.getExe perl} -0777 -pi -e 's{/usr/lib/x86_64-linux-gnu/webkit2gtk-4.0/injected-bundle/}{"\0" x length($&)}e' \ $out/opt/citrix-icaclient/usr/lib/x86_64-linux-gnu/libwebkit2gtk-4.0.so.37.56.4 + addAutoPatchelfSearchPath --no-recurse "$out/opt/citrix-icaclient/usr/lib/x86_64-linux-gnu" + addAutoPatchelfSearchPath "$out/opt/citrix-icaclient/usr/lib/x86_64-linux-gnu/webkit2gtk-4.0/injected-bundle" + addAutoPatchelfSearchPath "$out/opt/citrix-icaclient/lib" autoPatchelf -- "$out" $out/opt/citrix-icaclient/util/ctx_rehash diff --git a/pkgs/applications/networking/remote/citrix-workspace/sources.nix b/pkgs/applications/networking/remote/citrix-workspace/sources.nix index 34f0c8e627ae..506b810333ad 100644 --- a/pkgs/applications/networking/remote/citrix-workspace/sources.nix +++ b/pkgs/applications/networking/remote/citrix-workspace/sources.nix @@ -21,6 +21,15 @@ let # # The latest versions can be found at https://www.citrix.com/downloads/workspace-app/linux/ supportedVersions = lib.mapAttrs mkVersionInfo { + "26.01.0" = { + major = "26"; + minor = "01"; + patch = "0"; + hash = "0avrf9jpqhijvp6w4jgs7xgp4gg1q2mdzak9h9klkqrbsgrvjr3p"; + suffix = "150"; + homepage = "https://www.citrix.com/downloads/workspace-app/linux/workspace-app-for-linux-latest.html"; + }; + "25.08.10" = { major = "25"; minor = "08"; diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index 86c043c419e6..f3dc8e7701b2 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -1963,9 +1963,10 @@ with pkgs; }; inherit (callPackage ../applications/networking/remote/citrix-workspace { }) + citrix_workspace_26_01_0 citrix_workspace_25_08_10 ; - citrix_workspace = citrix_workspace_25_08_10; + citrix_workspace = citrix_workspace_26_01_0; colord-gtk4 = colord-gtk.override { withGtk4 = true; }; From d5402db15b96d222ffd8dc879fbeeeb1c933aaad Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Tue, 21 Apr 2026 16:11:26 -0500 Subject: [PATCH 3/4] citrix-workspace: add webcam GStreamer runtime support Add the GStreamer plugin stack that Citrix documents for Linux webcam redirection and propagate GST_PLUGIN_SYSTEM_PATH_1_0 through the wrapped entrypoints so the client can discover those plugins at runtime. --- .../remote/citrix-workspace/generic.nix | 57 +++++++++++++++---- 1 file changed, 47 insertions(+), 10 deletions(-) diff --git a/pkgs/applications/networking/remote/citrix-workspace/generic.nix b/pkgs/applications/networking/remote/citrix-workspace/generic.nix index cd30abc0c1c0..562c61a9c51c 100644 --- a/pkgs/applications/networking/remote/citrix-workspace/generic.nix +++ b/pkgs/applications/networking/remote/citrix-workspace/generic.nix @@ -19,6 +19,7 @@ glib, glib-networking, gnome2, + gst_all_1, gtk2, gtk2-x11, gtk3, @@ -77,6 +78,7 @@ xprop, xdpyinfo, libxcb, + x264, zlib, homepage, @@ -87,6 +89,18 @@ }: let + gstPackages = [ + gst_all_1.gstreamer + gst_all_1.gst-libav + gst_all_1.gst-plugins-base + gst_all_1.gst-plugins-good + gst_all_1.gst-plugins-bad + gst_all_1.gst-plugins-ugly + gst_all_1.gst-vaapi + ]; + + gstPluginPath = lib.makeSearchPathOutput "lib" "lib/gstreamer-1.0" gstPackages; + fuse3' = symlinkJoin { name = "fuse3-backwards-compat"; paths = [ (lib.getLib fuse3) ]; @@ -216,8 +230,10 @@ stdenv.mkDerivation rec { libxaw libxmu libxtst + x264 zlib - ]; + ] + ++ gstPackages; runtimeDependencies = [ glib @@ -241,11 +257,14 @@ stdenv.mkDerivation rec { installPhase = let + isSelfservice = program: (builtins.match "selfservice(.*)" program) != null; + isWfica = program: (builtins.match "wfica(.*)" program) != null; + icaFlag = program: - if (builtins.match "selfservice(.*)" program) != null then + if isSelfservice program then "--icaroot" - else if (builtins.match "wfica(.*)" program) != null then + else if isWfica program then null else "-icaroot"; @@ -253,7 +272,7 @@ stdenv.mkDerivation rec { ldLibraryPath = program: lib.concatStringsSep ":" ( - lib.optional (builtins.match "wfica(.*)" program != null) "$ICAInstDir" + lib.optional (isWfica program) "$ICAInstDir" ++ [ "$ICAInstDir/lib" "$ICAInstDir/usr/lib/x86_64-linux-gnu" @@ -265,14 +284,23 @@ stdenv.mkDerivation rec { # path. Leaving it enabled for UI helpers exposes Citrix's session-only # libproxy.so to the embedded web stack, which then fails to resolve CGP # symbols. + wrapperArgs = + program: + lib.concatStringsSep " \\\n " ( + lib.optional (icaFlag program != null) ''--add-flags "${icaFlag program} $ICAInstDir"'' + ++ [ + ''--set ICAROOT "$ICAInstDir"'' + ''--prefix GIO_EXTRA_MODULES : "${glib-networking}/lib/gio/modules"'' + ''--prefix GST_PLUGIN_SYSTEM_PATH_1_0 : "${gstPluginPath}"'' + ''--prefix LD_LIBRARY_PATH : "${ldLibraryPath program}"'' + ''--set LD_PRELOAD "${libredirect}/lib/libredirect.so ${lib.getLib pcsclite}/lib/libpcsclite.so"'' + ''--set NIX_REDIRECTS "/usr/share/zoneinfo=${tzdata}/share/zoneinfo:/etc/zoneinfo=${tzdata}/share/zoneinfo:/etc/timezone=$ICAInstDir/timezone:/usr/lib/x86_64-linux-gnu=$ICAInstDir/usr/lib/x86_64-linux-gnu"'' + ] + ); + wrap = program: '' wrapProgram $out/opt/citrix-icaclient/${program} \ - ${lib.optionalString (icaFlag program != null) ''--add-flags "${icaFlag program} $ICAInstDir"''} \ - --set ICAROOT "$ICAInstDir" \ - --prefix GIO_EXTRA_MODULES : "${glib-networking}/lib/gio/modules" \ - --prefix LD_LIBRARY_PATH : "${ldLibraryPath program}" \ - --set LD_PRELOAD "${libredirect}/lib/libredirect.so ${lib.getLib pcsclite}/lib/libpcsclite.so" \ - --set NIX_REDIRECTS "/usr/share/zoneinfo=${tzdata}/share/zoneinfo:/etc/zoneinfo=${tzdata}/share/zoneinfo:/etc/timezone=$ICAInstDir/timezone:/usr/lib/x86_64-linux-gnu=$ICAInstDir/usr/lib/x86_64-linux-gnu" + ${wrapperArgs program} ''; wrapLink = program: '' @@ -280,6 +308,11 @@ stdenv.mkDerivation rec { ln -sf $out/opt/citrix-icaclient/${program} $out/bin/${baseNameOf program} ''; + makeBinWrapper = program: wrapperName: '' + makeWrapper $out/opt/citrix-icaclient/${program} $out/bin/${wrapperName} \ + ${wrapperArgs program} + ''; + copyCert = path: '' cp -v ${path} $out/opt/citrix-icaclient/keystore/cacerts/${baseNameOf path} ''; @@ -324,6 +357,7 @@ stdenv.mkDerivation rec { ln -sf "$ICAInstDir/util/setlog" "$out/bin/citrix-setlog" fi ${mkWrappers wrapLink toWrap} + ${makeBinWrapper "wfica" "wfica"} ${mkWrappers wrap [ "PrimaryAuthManager" "ServiceRecord" @@ -346,6 +380,9 @@ stdenv.mkDerivation rec { rm $ICAInstDir/util/{gst_aud_{play,read},gst_*0.10,libgstflatstm0.10.so} || true ln -sf $ICAInstDir/util/gst_play1.0 $ICAInstDir/util/gst_play ln -sf $ICAInstDir/util/gst_read1.0 $ICAInstDir/util/gst_read + # `hinst` disables multimedia when it cannot link into FHS plugin + # directories. In Nix we provide the plugin path via wrappers instead. + sed -i 's/^MultiMedia=Off$/MultiMedia=On/' "$ICAInstDir/config/module.ini" echo "We arbitrarily set the timezone to UTC. No known consequences at this point." echo UTC > "$ICAInstDir/timezone" From 85bf2e8a81978b6dd1b645c803ac18b7334dad80 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Thu, 30 Apr 2026 08:59:27 -0500 Subject: [PATCH 4/4] citrix-workspace: add PulseAudio runtime path HdxRtcEngine loads libpulse.so.0 dynamically, so autoPatchelf cannot discover the dependency from ELF metadata. Add libpulseaudio to the wrapper library path so Teams HDX optimization can initialize endpoint audio. --- .../networking/remote/citrix-workspace/generic.nix | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkgs/applications/networking/remote/citrix-workspace/generic.nix b/pkgs/applications/networking/remote/citrix-workspace/generic.nix index 562c61a9c51c..1ea48306fe1f 100644 --- a/pkgs/applications/networking/remote/citrix-workspace/generic.nix +++ b/pkgs/applications/networking/remote/citrix-workspace/generic.nix @@ -277,6 +277,9 @@ stdenv.mkDerivation rec { "$ICAInstDir/lib" "$ICAInstDir/usr/lib/x86_64-linux-gnu" "$ICAInstDir/usr/lib/x86_64-linux-gnu/webkit2gtk-4.0/injected-bundle" + # HdxRtcEngine loads libpulse.so.0 with dlopen, so autoPatchelf + # cannot discover it from ELF dependencies. + "${lib.getLib libpulseaudio}/lib" ] );