devenv shenanigans

This commit is contained in:
TheK0tYaRa 2026-03-11 00:44:53 +02:00
parent 8dc75613c7
commit 586c3e3e3a
5 changed files with 289 additions and 0 deletions

2
.envrc Normal file
View file

@ -0,0 +1,2 @@
eval "$(devenv direnvrc)"
use devenv

81
PLAN-calls.md Normal file
View file

@ -0,0 +1,81 @@
# Stabilize Existing VoIP Call Flow and Add Non-Fatal Diagnostics
## Summary
Tighten the existing Matrix call flow in the current VoIP stack, focusing on real correctness issues already present in src/voip/CallManager.cpp and src/voip/WebRTCSession.cpp, and
add structured logging around non-fatal failures so future call bugs are diagnosable without crashing or silently retrying forever.
This plan targets:
- Core invite/answer/candidate/select-answer stability
- Inbound renegotiation support for m.call.negotiate
- Logging of non-critical exceptions and ignored/invalid call events
- No user-visible feature expansion beyond making current calls more reliable
## Implementation Changes
- Fix the CallSelectAnswer rejection lookup bug in CallManager::handleEvent(const RoomEvent<CallSelectAnswer>&) by changing the std::find(begin, begin, ...) calls to search through
end(). This is a concrete logic bug that currently prevents expected branch behavior.
- Add strict call-id and state guards across inbound handlers in CallManager and log every early-return branch that currently drops events silently. Include at least:
- mismatched call_id
- wrong local webrtc::State
- duplicate answers/select-answer/reject
- self-echo cases
- candidates received before a call is initialized
- Implement WebRTCSession::acceptNegotiation(const std::string &) instead of the current unconditional false. Use the same parsing/validation style as acceptOffer:
- require an active session, not DISCONNECTED
- parse SDP as a remote offer
- validate required audio media and optional VP8 video media
- update remote video direction flags
- call set-remote-description
- create and send an answer through the existing answerCreated path
- keep wire format unchanged; do not add local renegotiation initiation in this pass
- Extend CallManager::handleEvent(const RoomEvent<CallNegotiate>&) so renegotiation only applies to the active call, logs dropped mismatches, and drains queued ICE candidates only
after renegotiation is accepted.
- Harden acceptICECandidates handling:
- log when candidates are ignored because the session is not ready
- keep buffering only in manager-side inbound invite/renegotiation waiting states
- avoid silent no-ops when candidates arrive for the wrong call or after teardown
- Improve failure handling around setup paths:
- log TURN retrieval failures in retrieveTurnServer() with the request error and retry interval
- log why createOffer, acceptOffer, acceptAnswer, and renegotiation fail, including current state and call id where available
- log pipeline creation failures with the missing device/element context instead of only generic “Problem setting up call”
- Add non-critical exception logging around call orchestration paths that touch cache/state but should not kill the process:
- syncEvent dispatch
- inbound call event handlers that query room info/members
- outbound invite setup that reads room/member data
- TURN response parsing/conversion
Catch std::exception and log with nhlog at warn or error depending on whether the call can continue; rethrow nothing for these paths. Add a final catch (...) only where needed
to prevent silent termination, with a clear “unknown exception” log line.
- Keep user notifications mostly unchanged. Prefer logs for diagnostics and only surface UI notifications for existing fatal outcomes such as setup failure, ICE failure, reject,
timeout, or answered elsewhere.
## Public Interfaces / Behavior
- No QML property or signal changes.
- No Matrix event schema changes.
- Existing CallAnswer, CallCandidates, CallNegotiate, and CallSelectAnswer messages remain the transport surface.
- Behavioral change: inbound m.call.negotiate will now be handled instead of always failing.
- Behavioral change: previously silent dropped call events and recoverable exceptions will now emit logs with call id, party id, and local state.
## Test Plan
- Add focused unit-style coverage where practical for pure call-manager logic, or otherwise add regression tests around isolated helper logic if the repo lacks VoIP integration
tests.
- Verify manually or with targeted harness coverage:
- outbound call reaches OFFERSENT, receives answer, transitions to CONNECTED
- inbound call invite buffers ICE before accept, then drains after accept
- duplicate CallAnswer and CallSelectAnswer are ignored with logs, not state corruption
- CallSelectAnswer rejection-party matching now works after the std::find fix
- incoming CallNegotiate on the active call produces a local answer instead of immediate failure
- CallNegotiate for a different call_id is ignored with a diagnostic log
- TURN retrieval failure logs retry details without crashing
- exceptions thrown from room/member lookup in call paths are logged and do not abort the process
- Run a build check after the changes. If the environment still lacks cmake, document that verification gap and at minimum run the strongest available static inspection.
## Assumptions
- Use existing nhlog logging categories rather than raw printf; “prints” here means persistent diagnostic logging.
- Keep the existing user-facing notification policy unless a failure is already fatal to the active call.
- Renegotiation scope is inbound-only for now because the current codebase already receives m.call.negotiate but does not expose a local renegotiation initiator.
- No attempt will be made in this pass to redesign the entire call state machine or add automatic media-session recovery after teardown.

65
devenv.lock Normal file
View file

@ -0,0 +1,65 @@
{
"nodes": {
"devenv": {
"locked": {
"dir": "src/modules",
"lastModified": 1773179136,
"narHash": "sha256-aOxRGdpITEvasogdZXGqlSrr8pJQ+QGiRxpzmT3i/UQ=",
"owner": "cachix",
"repo": "devenv",
"rev": "169c048513ca064c02a4233ace0ecbdd935d4f80",
"type": "github"
},
"original": {
"dir": "src/modules",
"owner": "cachix",
"repo": "devenv",
"type": "github"
}
},
"nixpkgs": {
"inputs": {
"nixpkgs-src": "nixpkgs-src"
},
"locked": {
"lastModified": 1772749504,
"narHash": "sha256-eqtQIz0alxkQPym+Zh/33gdDjkkch9o6eHnMPnXFXN0=",
"owner": "cachix",
"repo": "devenv-nixpkgs",
"rev": "08543693199362c1fddb8f52126030d0d374ba2e",
"type": "github"
},
"original": {
"owner": "cachix",
"ref": "rolling",
"repo": "devenv-nixpkgs",
"type": "github"
}
},
"nixpkgs-src": {
"flake": false,
"locked": {
"lastModified": 1772173633,
"narHash": "sha256-MOH58F4AIbCkh6qlQcwMycyk5SWvsqnS/TCfnqDlpj4=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "c0f3d81a7ddbc2b1332be0d8481a672b4f6004d6",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"devenv": "devenv",
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

129
devenv.nix Normal file
View file

@ -0,0 +1,129 @@
{ pkgs, lib, config, inputs, ... }:
let
qtPackages = with pkgs.qt6Packages; [
qtbase
qtdeclarative
qtimageformats
qtkeychain
qtmultimedia
qtsvg
qttools
qtwayland
qt-jdenticon
];
gstreamerPackages = [
pkgs.gst_all_1.gstreamer
pkgs.gst_all_1.gst-plugins-base
(pkgs.gst_all_1.gst-plugins-good.override { qt6Support = true; })
pkgs.gst_all_1.gst-plugins-bad
];
cmakePackages = [
pkgs.cmark
pkgs.coeurl
pkgs.curl
pkgs.kdsingleapplication
pkgs.libevent
pkgs.libsecret
pkgs.lmdb
pkgs.lmdbxx
pkgs.mtxclient
pkgs.nlohmann_json
pkgs.olm
pkgs.re2
pkgs.spdlog
] ++ qtPackages ++ gstreamerPackages ++ [ pkgs.libnice ];
runtimePackages = [
pkgs.pipewire
];
qtPluginPath = lib.makeSearchPath "lib/qt-6/plugins" qtPackages;
qmlImportPath = lib.makeSearchPath "lib/qt-6/qml" qtPackages;
gstreamerPluginPath = lib.makeSearchPath "lib/gstreamer-1.0" gstreamerPackages;
runtimeLibraryPath = lib.makeLibraryPath runtimePackages;
cmakePrefixPath =
lib.concatStringsSep ":" (
(map (pkg: "${lib.getDev pkg}/lib/cmake") cmakePackages)
++ (map (pkg: "${lib.getDev pkg}") cmakePackages)
);
in
{
packages = [
pkgs.asciidoc
pkgs.cmake
pkgs.direnv
pkgs.gcc
pkgs.git
pkgs.gnumake
pkgs.gdb
pkgs.ninja
pkgs.pkg-config
pkgs.pipewire
pkgs.qt6Packages.wrapQtAppsHook
] ++ cmakePackages;
env = {
CMAKE_GENERATOR = "Ninja";
CMAKE_PREFIX_PATH = cmakePrefixPath;
GST_PLUGIN_SYSTEM_PATH_1_0 = gstreamerPluginPath;
LD_LIBRARY_PATH = runtimeLibraryPath;
NHEKO_BUILD_DIR = "build";
QT_PLUGIN_PATH = qtPluginPath;
QML2_IMPORT_PATH = qmlImportPath;
};
scripts."configure-nheko".exec = ''
cmake -S . -B "$NHEKO_BUILD_DIR" \
-G "$CMAKE_GENERATOR" \
-DCMAKE_BUILD_TYPE="''${CMAKE_BUILD_TYPE:-RelWithDebInfo}" \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON
'';
scripts."build-nheko".exec = ''
if [ ! -f "$NHEKO_BUILD_DIR/CMakeCache.txt" ]; then
configure-nheko
fi
cmake --build "$NHEKO_BUILD_DIR" --parallel "''${NIX_BUILD_CORES:-$(nproc)}"
'';
scripts."test-nheko".exec = ''
build-nheko
ctest --test-dir "$NHEKO_BUILD_DIR" --output-on-failure
binary="$(find "$NHEKO_BUILD_DIR" -maxdepth 4 -type f -name nheko -executable | head -n 1)"
if [ -z "$binary" ]; then
echo "Unable to locate the built nheko binary under $NHEKO_BUILD_DIR" >&2
exit 1
fi
QT_QPA_PLATFORM=offscreen "$binary" --help >/dev/null
'';
scripts."run-nheko".exec = ''
build-nheko
binary="$(find "$NHEKO_BUILD_DIR" -maxdepth 4 -type f -name nheko -executable | head -n 1)"
if [ -z "$binary" ]; then
echo "Unable to locate the built nheko binary under $NHEKO_BUILD_DIR" >&2
exit 1
fi
exec "$binary" "$@"
'';
enterShell = ''
echo "nheko devenv ready"
echo " configure-nheko Configure the CMake build tree"
echo " build-nheko Build the nheko binary"
echo " test-nheko Run ctest and a headless --help smoke test"
echo " run-nheko Launch the built client"
'';
enterTest = ''
test-nheko
'';
}

12
devenv.yaml Normal file
View file

@ -0,0 +1,12 @@
# yaml-language-server: $schema=https://devenv.sh/devenv.schema.json
inputs:
nixpkgs:
url: github:cachix/devenv-nixpkgs/rolling
nixpkgs:
permittedInsecurePackages:
- "olm-3.2.16"
# If you have more than one devenv you can merge them
#imports:
# - ./backend