[Bug]: Hyprland SnapShot ignores disabled animations on boolean getoption replies #35973
Send to my agentSummary#
SnapShot's native capture animation remains enabled when Hyprland reports that compositor animations are disabled using its boolean option format. The helper should honor that disabled setting. A disposable Unix-socket fixture reproduced the incorrect decision in the production IPC function; source inspection connects that decision to capture feedback, while no live compositor animation was exercised.
Before submitting#
- Related issues: related geometry reports listed under Additional context; no matching disabled-animation report found in open and closed issue/PR searches.
- Removed credentials, tokens, private paths, and sensitive data.
Area#
apps/desktop
Observed behavior#
The production ipc::animations_enabled() returns true for the valid response {"option":"animations:enabled","bool":false,"set":true}. The same function returns false for the older integer-zero response and true for boolean true. The socket fixture calls the unchanged production module copied from the checkout and validates the exact j/getoption animations:enabled request. Source-only consequence: when the desktop Capture animations preference supplies animate=true, native/hyprland-snap-shot/src/feedback.rs#L175-L176 leaves it enabled instead of suppressing motion.
Reproduction#
- Use a clean checkout at the commit below on a Unix host with Rust, Cargo, Python 3 and the locked serde dependency versions cached for offline Cargo use.
- Save and run the script below. It copies the production IPC module into a disposable Rust harness and serves synthetic replies through the documented Hyprland command-socket path.
- Observe that the integer-zero and boolean-true controls behave correctly, but boolean false still produces
animation enabled=true.
One run reproduced the failure deterministically at the IPC decision boundary. No Linux compositor, screen capture, real window or desktop session bus was used.
Save as repro.sh and run sh repro.sh /path/to/t3code. The script needs a Unix host, POSIX shell, Rust/Cargo, Python 3 and the listed Cargo dependencies cached locally. It puts HOME, TMPDIR, XDG directories, target build output and synthetic sockets in a temporary directory, reuses only the existing Cargo/rustup tool and dependency caches, removes its temporary directory on exit and leaves the checkout unchanged. Exit 1 means the defect reproduced, exit 0 means the contract is restored, and exit 2 means setup or a control failed.
Reproduction script: repro.sh
#!/bin/sh
set -u
checkout=${1:?Usage: sh repro.sh /path/to/t3code}
work=$(mktemp -d /tmp/t3-hypr-motion.XXXXXX) || exit 2
trap 'rm -rf "$work"' EXIT HUP INT TERM
cargo --version
rustc --version
python3 --version
export RUSTUP_HOME="${RUSTUP_HOME:-$HOME/.rustup}"
export CARGO_HOME="${CARGO_HOME:-$HOME/.cargo}"
export CARGO_TARGET_DIR="$work/target"
export HOME="$work/home"
export TMPDIR="$work/tmp"
export XDG_CONFIG_HOME="$work/config"
export XDG_DATA_HOME="$work/data"
export XDG_STATE_HOME="$work/state"
export XDG_CACHE_HOME="$work/cache"
export XDG_RUNTIME_DIR="$work/run"
export XDG_CURRENT_DESKTOP=Hyprland
export HYPRLAND_INSTANCE_SIGNATURE=fixture
unset FLATPAK_ID SNAP DBUS_SESSION_BUS_ADDRESS WAYLAND_DISPLAY DISPLAY
mkdir -p "$HOME" "$TMPDIR" "$XDG_CONFIG_HOME" "$XDG_DATA_HOME" "$XDG_STATE_HOME" "$XDG_CACHE_HOME" "$XDG_RUNTIME_DIR/hypr/fixture" "$work/src"
cp "$checkout/native/hyprland-snap-shot/src/ipc.rs" "$work/src/ipc.rs" || exit 2
cat > "$work/Cargo.toml" <<'MANIFEST'
[package]
name = "hyprland-motion-contract-probe"
version = "0.1.0"
edition = "2024"
[dependencies]
serde = { version = "=1.0.229", features = ["derive"] }
serde_json = "=1.0.151"
MANIFEST
cat > "$work/src/main.rs" <<'RUST'
#![allow(dead_code)]
mod ipc;
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
use std::{io::{Read, Write}, os::unix::net::UnixListener, thread};
fn query(reply: &'static str) -> bool {
let path = ipc::session_directory().unwrap().join(".socket.sock");
let listener = UnixListener::bind(&path).unwrap();
let server = thread::spawn(move || {
let (mut socket, _) = listener.accept().unwrap();
let mut request = [0; 256];
let count = socket.read(&mut request).unwrap();
assert_eq!(&request[..count], b"j/getoption animations:enabled");
socket.write_all(reply.as_bytes()).unwrap();
});
let enabled = ipc::animations_enabled();
server.join().unwrap();
std::fs::remove_file(path).unwrap();
enabled
}
fn main() {
let legacy_disabled = query(r#"{"option":"animations:enabled","int":0,"set":true}"#);
let bool_enabled = query(r#"{"option":"animations:enabled","bool":true,"set":true}"#);
println!("legacy int=0: animation enabled={legacy_disabled}");
println!("boolean true: animation enabled={bool_enabled}");
if legacy_disabled || !bool_enabled { std::process::exit(2); }
let bool_disabled = query(r#"{"option":"animations:enabled","bool":false,"set":true}"#);
println!("boolean false: animation enabled={bool_disabled}");
std::process::exit(if bool_disabled { 1 } else { 0 });
}
RUST
cargo run --offline --quiet --manifest-path "$work/Cargo.toml"
status=$?
case "$status" in
0) echo 'RESULT: expected behavior';;
1) echo 'RESULT: defect reproduced';;
*) status=2; echo 'RESULT: inconclusive';;
esac
exit "$status"
Expected behavior#
When Hyprland reports animations disabled, the helper should suppress the optional capture flight animation even if Capture animations is enabled in T3 Code. The helper's own native/hyprland-snap-shot/src/ipc.rs#L144-L150 states that it honors Hyprland's switch. Released Hyprland v0.57.2 declares animations:enabled as a Bool in src/config/values/ConfigValues.cpp#L345-L348, and its src/ipc/s1/Commands.cpp#L1770-L1774 serializes BOOL option values into the JSON bool field. Both boolean false and the older integer-zero representation should disable capture animation; boolean true should keep it enabled.
Impact and scope#
Impact: P2 - the compositor's disabled-motion preference is ignored for optional SnapShot capture feedback when the reply uses the released boolean format. Users can explicitly turn off Capture animations in T3 Code Settings → SnapShots; this preference gates feedback before the helper call in the source. The screenshot itself is not changed by this decision. The wrong boolean decision was observed directly; live animation playback and the settings workaround were not exercised in a compositor.
Affected input: valid boolean false getoption JSON replies, including the format defined by Hyprland v0.57.2. Integer-zero responses pass the control. NOT_ENOUGH_INFO: the first Hyprland release that changed the option type was not established; comparing older tagged option declarations would establish that boundary.
Version#
T3 Code commit 59e64eef881632a23f89c4138bcf37f23d92bf93. The current default-branch IPC source also still contains the integer-only decision, observed through GitHub's contents API. Hyprland source contract: released v0.57.2; no installed Hyprland runtime was used.
Runtime and platform#
Synthetic Unix transport fixture on macOS, Darwin arm64. Cargo 1.99.0, rustc 1.99.0, Python 3.15.2. Harness dependency pins match the checkout: serde 1.0.229 and serde_json 1.0.151. Linux compositor execution was not run.
Interface#
Other: desktop SnapShots native Hyprland helper and Hyprland command socket.
Earliest failure stage#
Integration or transport
Relevant output or evidence#
Observed output from the script above:
cargo 1.99.0 (5f94df478 2026-08-27)
rustc 1.99.0 (b940084d7 2026-09-28)
Python 3.15.2
legacy int=0: animation enabled=false
boolean true: animation enabled=true
boolean false: animation enabled=true
RESULT: defect reproduced
The production reader selects only JSON int and compares it against zero in native/hyprland-snap-shot/src/ipc.rs#L144-L150. Boolean false therefore reaches the enabled return value. The native feedback caller applies that result to its animation option at the source location above. Evidence provenance: observed synthetic Unix transport with the unchanged production module, plus static released compositor source and static feedback call path. Live graphical verification was not run.
Additional context#
- https://github.com/pingdotgg/t3code/issues/10644 — addresses incorrect animation geometry; its full discussion does not cover honoring disabled compositor animations.
- https://github.com/pingdotgg/t3code/pull/10646 — repairs viewport resizing artifacts; this report concerns whether animation starts when the compositor setting is disabled.
Restoration check: run the same script and require both disabled representations to return false, boolean true to return true, RESULT: expected behavior, and exit 0. A live follow-up can enable T3 Code Capture animations, disable Hyprland compositor animations, and capture a synthetic window to check the full visual boundary.
tanstack-source added labelsbug
github-actions[bot](GitHub profile, opens in a new tab) added labelsvouch:denounced
Sign in to join this conversation.
Checked the source on our side: Hyprland v0.57.2
src/ipc/s1/Commands.cpp(lines 1772-1774) reports BOOL options in a"bool"field, andnative/hyprland-snap-shot/src/ipc.rsanimations_enabled()reads only"int", so{"bool":false}still counts as enabled. A local harness showed the bool-false case failing before the change and passing after it, the same as #35978.Julius's #35978 links this issue and carries that fix, so we're standing down here.