Commit Graph
1317 Commits
Author SHA1 Message Date
evgeny a3b00984ed fix: stable ETCP reconnect (no REINIT loop), own voice playback, peersOnline via conn state, UDP log diagnostics 2026-07-30 01:21:40 +03:00
evgeny 70797f921e feature: media auto-download + manual download + UI download state indication (Android + desktop)
C-layer:
- chat_event.h: CHAT_EVT_ATTACHMENT_DOWNLOADED (14)
- chat_msg.c: fixed md_auto_download (non-NULL done_cb, correct media_base from db_path, use chat_setting for autoload checks)
- chat_msg.c: added chat_core_attachment_download + trampoline for manual download
- chat_core.h: exported attachment_dl_req + trampoline

Android (Kotlin + JNI):
- Message: downloadState field (parse from localAttrs)
- ChatRepository: parse localAttrs for download state
- NativeLib: attachmentDownload() JNI
- MessageBubble: FileBubble with download status icon + tap-to-download
- ChatViewModel: downloadAttachment() + CHAT_EVT_ATTACHMENT_DOWNLOADED handler
- ChatScreen: wire onDownloadFile handler
- android_jni_bridge: utun_bridge_attachment_download + JNI + localAttrs in JSON

Desktop (Qt/C++):
- messagedelegate.h: MsgFileDownloadStateRole, MsgChannelIdRole
- messagelist.cpp: parse localAttrs for file download state
- messagedelegate.cpp: FileBubble with download icon/text + click handler via chat_core_attachment_download_trampoline
- gui_bridge.h: GUI_EVT_ATTACHMENT_DOWNLOADED (14) + callback typedef
- gui_bridge_impl.cpp: event dispatch + setter
- mainwindow.cpp: callback registration -> refresh message list
2026-07-30 00:01:38 +03:00
evgeny b8920cf483 chat_settings: [chat] config section + live sync + fix test segfault
- New chat_setting.c/h: unified chat settings API (int/str getters)
- Config parser supports [chat] section, calls chat_setting_set per key
- chat_core exposes chat_core_set_setting / trampoline for GUI bridge
- chatgui: AudioDevice + Storage pages persist to [chat] config + live apply
- chatgui-android: ConfigProvider, NativeLib, headless handle chat settings
- MessageList: 200 msg limit, fixed saveChannelState via trampoline
- test_media_delivery_full: fix segfault at shutdown (mon() timer vs freed conns)
2026-07-29 21:18:49 +03:00
evgeny 65dbfa6813 feat: media_async + media_index modules, media_files table, tests
- media_async: thread-per-task async engine with primitives (sha256, sign, copy, uuid, file_size)
- media_index: media_files DB table with register_async for indexing chunks
- chat_msg: async media submission via register_async, simplified chat_msg_submit fields
- voice_recorder/attachment_sender: write single file (no chunk splitting on caller side)
- inputbar/messagelist: updated to new simplified submit interface
- test_media_async: 4-thread stress test (200K ops / 2 sec, 0 errors)
- test_media_index: 9 cases covering commit edge cases + async flow
- build: Makefile.am, CMakeLists.txt, utun_sources.cmake updated
2026-07-29 14:15:10 +03:00
evgeny b7988f0ef8 refactor: single source of truth — PlaybackState in ViewModel
VoiceBubble is now a pure renderer — no local state, no LaunchedEffect, no timer.
All state from ViewModel.playbackState StateFlow:
  - playing/paused/elapsedMs/progress/filePath — one atomic snapshot
  - isActive = playbackState.filePath == message.filePath — only active clip shows cursor
  - onProgress callback from AudioRecorderManager feeds real elapsed into ViewModel
  - ViewModel timer (80ms) updates progress between onProgress callbacks

Removed: VoiceBubble's playing/paused/seekVersion/PlayState/onDone/onPaused.
Removed: AudioRecorderManager's isPaused/elapsedMs/pausedElapsedMs.
2026-07-29 02:29:53 +03:00
evgeny 9742772880 fix: monotonic playGeneration instead of re-settable stopped flag
stopped flag was set before join() but cleared after, causing race:
if thread was still blocked in decodeRead when stopped was reset,
onComplete fired anyway. Monotonic playGeneration ++ never decreases,
thread captures myGen at start, compares after completion —
correctly skipped if generation changed (another stop/seek started).
2026-07-29 02:20:25 +03:00
evgeny e4af56856c fix: atomic PlayState replaces non-atomic seekVersion+elapsed+progress
PlayState data class bundles playId, elapsedMs, progress into one atomic snapshot.
LaunchedEffect keyed on playId — when it changes (seek/resume), the timer reads
elapsedMs and progress from the SAME snapshot, eliminating the race between
seekVersion++ and elapsedMs=seekMs.
2026-07-29 02:13:47 +03:00
evgeny 1481d1244b fix: seekVersion forces LaunchedEffect restart on seek/resume
When playing=true and seek happens, LaunchedEffect(playing) key didn't change,
so the elapsed timer kept using the old start time. Added seekVersion counter
as second key — increments on waveform tap and play/resume click, forcing
the timer coroutine to restart with the new elapsedMs position.
2026-07-29 02:10:51 +03:00
evgeny 0afc74e567 fix: stop/pause no longer fires onComplete from killed thread
When stopPlayback/pausePlayback kills the playback thread, the thread's
onComplete callback was still firing, resetting UI state (playing=false,
elapsed=0). Added @Volatile 'stopped' flag — onComplete only fires on
natural completion, not on external stop/pause.
2026-07-29 02:08:51 +03:00
evgeny 2edd9bcc06 cleanup: remove dead currentPlayPath, fix seekVoice _currentPlayingPath reset
- AudioRecorderManager: removed unused currentPlayPath (duplicate of ViewModel state)
- ChatViewModel: seekVoice now resets _currentPlayingPath on complete (prevents
  pause-on-already-ended after seek finishes)
2026-07-29 02:03:09 +03:00
evgeny 2e8aa89397 voice: fix waveform — pointerInput on Box, cursor + white bars stay on pause
- Canvas pointerInput moved to wrapping Box (state updates trigger recomposition)
- showCursor = playing || paused (cursor and white bars visible on pause)
- elapsed/dur stays visible on pause too
2026-07-29 02:00:43 +03:00
evgeny 6980f34df9 voice: real pause (saves position), waveform tap seek, resume from position
- AudioRecorderManager: playFromPosition(startMs), pausePlayback, elapsedMs tracking
  playVoiceFile now delegates to playFromPosition. fade skipped PCM frames on seek.
- ViewModel: playVoiceMessage with pause toggle, seekVoice method
- VoiceBubble: click tap on waveform → seek, pause state (⏸ keeps cursor visible)
  Webform canvas with detectTapGestures for seek input
2026-07-29 01:57:31 +03:00
evgeny 887bbdbf84 voice: play/pause toggle, stop previous on new track
- ViewModel: _currentPlayingPath tracks active track. Tap same → stop.
  Tap different → stop old, play new. onComplete clears currentPlayingPath.
- AudioRecorderManager: stopPlayback() already called at start (existing)
- VoiceBubble: simplified click, always sends to ViewModel for state management
2026-07-29 01:51:25 +03:00
evgeny b8e15ad1f2 voice: played bars white, yellow cursor line on waveform 2026-07-29 01:48:16 +03:00
evgeny ee8167793c voice: waveform idle bars lighter (#CCD4 80% — near white) 2026-07-29 01:45:03 +03:00
evgeny 109326717e voice: play button white circle, black icon 2026-07-29 01:44:26 +03:00
evgeny f745fb25e8 android: lighten waveform bars (idle: #A0A0B0 70%, playing unplayed: 35% alpha) 2026-07-29 01:43:55 +03:00
evgeny 4ffa59d968 unify voice waveform format: base64 wf={100chars};dur={ms}; for both desktop and Android
- inputbar.cpp: CSV waveform → base64 log-scale (same as voice_recorder.c)
- messagelist.cpp: sendVoiceMessage removes displayName|dur_sec| prefix, uses wfStr directly
  setVoiceMessageRoles: decodes new format wf={base64};dur={ms};|... from data field
- messagedelegate.cpp: waveform values now pre-normalized 0..1, no log recalculation needed
- VoiceEncoder: no changes needed (was already compatible)

Both platforms now store identical format in chat_msg_submit.data:
  wf=ABCDef...100chars;dur=NNNN;|file_size|block_size|num_blocks|sigs_hex
2026-07-29 01:43:11 +03:00
evgeny b65a091b00 voice: base64 compact waveform (100 bytes), progress bar, play/pause auto-reset
- voice_recorder: 100-char base64 (A-Za-z0-9+/) log-scale waveform, ~120 bytes total vs ~600 CSV
- ChatRepository: parse base64 'wf=<100 chars>;dur=NNNN;' → normalized 0..1 List<Float>
  CSV fallback for old messages (length < 50 or contains comma)
- VoiceBubble: progress bar (blue played / gray unplayed), ▶/⏸ toggle, elapsed timer
- onComplete callback wired through ViewModel → AudioRecorderManager → VoiceBubble auto-reset
2026-07-29 01:36:43 +03:00
evgeny 3c75c8f687 icons: replace emoji text with PNG resources + add media_dir debug logs 2026-07-29 01:27:23 +03:00
evgeny 491fa84db8 voice: fix waveform display + text buffer, 75% width, play/pause state
- bridge: txt buffer 256→2048, limit 250→2000 (voice d-field can be ~900 bytes)
- VoiceBubble: Column layout — play+waveform on top, duration|timestamp on bottom
- Play/pause toggle with ⏸/▶ icons
- Voice messages use 75% screen width max (300dp for others)
2026-07-29 01:21:35 +03:00
evgeny 94a537ebdd voice: waveform recording + display, playback wiring
- voice_recorder: waveform_levels[100] accumulated during feed, normalized to 100 values in stop()
  stored as 'wf=...;dur=...;' in chat_msg_submit data
- bridge: filePath in get_messages_json from local_attrs.fp, g_db_path moved global
- Message model: waveform: List<Float>, voiceDurationMs: Int, filePath: String
- VoiceBubble: Canvas waveform (100 bars, log scale), play button, duration
- ChatViewModel.playVoiceMessage → AudioRecorderManager.playVoiceFile
2026-07-29 01:15:57 +03:00
evgeny 39d6f19d97 fix: chat_msg media_dir path — strip filename from g_cc.db_path
g_cc.db_path is '/data/.../files/chats.db' (file path), but media blocks
are written to '/data/.../files/media/<channel>/'. chat_msg was concatenating
'chats.db/media/...' — now strips the database filename, using only the
directory portion for the media path.
2026-07-29 01:04:27 +03:00
evgeny b11e705f7a android: input bar — single right icon (PTT when empty, Send when text) 2026-07-29 00:58:26 +03:00
evgeny b90728be3c android: round send button with \u27A4 icon (matching desktop chatgui) 2026-07-29 00:55:39 +03:00
evgeny 9fca8d5e1f android: fix voice file creation (mkdir errno logging), PTT while(true)
- voice_recorder: proper mkdir with errno/EEXIST check, errno logged on fopen failure
- MessageBubble: pointerInput wrapped in while(true) so PTT works after first gesture
2026-07-29 00:53:23 +03:00
evgeny 1dac0b8c81 unify compressor: remove C++ duplicate audiocompressor, use lib/audio_compressor.c in chatgui 2026-07-29 00:52:05 +03:00
evgeny 00afc5c80a android: fix PTT — one Box across all states, release now works; 1s min duration
- PTT Box: same composable identity in idle/recording states (just changes bg color)
  Gesture coroutine survives recomposition → onPttReleased() fires on finger lift.
  When locked, Box is replaced by cancel/send IconButtons.
- Min voice duration: 500→1000ms (C and ViewModel)
2026-07-29 00:32:10 +03:00
evgeny 90de87b13d move audio_compressor from libutun_lite/ to lib/ — shared library
- audio_compressor.h/c moved to lib/ (alongside opus_codec, mem, debug_config)
- lib/Makefile.am: added to libuasync_a_SOURCES
- utun_sources.cmake: removed from _cfg_src (GLOB from lib/ picks it up)
- tests/Makefile.am: test_audio_compressor added to check_PROGRAMS
- test includes simplified (debug_config.h, mem.h — lib/ in include path)
2026-07-29 00:26:45 +03:00
evgeny d22eac55f7 fix audio_compressor: flat array instead of ring buffer, +unit test (411 asserts)
Ring buffer (pending_head/pending_tail/cap) replaced with simple flat array (pending_count/cap).
u_realloc copies linearly but ring buffer needs memmove for wrap-around — mismatch caused
SIGSEGV write to 0x10 on ARM64. Flat array: append to end, process from [0], memmove down.
Performance unchanged (max ~15 blocks).

Tests: create/destroy, passthrough disabled, basic push+flush, silence, reset, stress (200 cycles)
2026-07-29 00:11:29 +03:00
evgeny fc2adcf81d android: fix voice recording — init g_rec, move includes, check startRecording result
- voice_recorder_init(db_path) in utun_bridge_init (was never called, g_rec stayed NULL)
- voice_recorder_deinit() in utun_bridge_destroy
- ViewModel: set _isRecording only after startRecording() succeeds
2026-07-28 23:52:33 +03:00
evgeny 50214d50c1 android: fix PTT button — Box+pointerInput(hasMicPerm) instead of IconButton+onClick conflict 2026-07-28 23:35:53 +03:00
evgeny 1f8f41bda7 android: tabbed settings (Settings/Audio) with mic test, codec preset, compressor sliders
- voice_recorder: compressor config API (max_gain, lookback, lookahead, rise_rate, target_level)
- bridge/JNI: compressor_config export
- AudioRecorderManager: startMicTest/stopMicTest for level preview
- SettingsScreen: TabRow with Settings + Audio tabs
- Audio tab: mic test with live level bar, Opus preset selector, 5 compressor sliders matching desktop
2026-07-28 23:33:19 +03:00
evgeny 9b6dd6476c android: PTT swipe-to-lock, recording overlay with level bar, cancel/send buttons
- voice_recorder: peak_level tracking in feed(), voice_recorder_get_peak_level()
- bridge/JNI: voice_get_peak_level API
- ViewModel: isRecordingLocked, recordingDurationMs with 80ms timer, lock/send/cancel
- InputBar: 3 states (idle/holding/locked), custom pointerInput gesture with swipe-up lock
- ChatScreen: poll peak level 12x/sec during recording
2026-07-28 23:28:19 +03:00
evgeny 0f1e6e0006 android: fix NDK build — add Opus sources, fix Kotlin composable context, fix bridge db_path param 2026-07-28 23:20:19 +03:00
evgeny 5b6979da27 android: voice messages recording + playback, file attachments
- C99 port of AudioCompressor (libutun_lite/audio_compressor.h/c)
- Voice recorder: PCM buffer + compressor + Opus encode + block splitting + uasync send
- Attachment sender: file splitting into blocks + uasync send
- Bridge API + JNI: voice start/feed/stop/cancel, attachment send, Opus decode for playback
- Kotlin: AudioRecorderManager (AudioRecord + JNI feed), AudioPlayer (AudioTrack + JNI decode)
- UI: PTT button with press-to-talk, attach button with file picker, recording overlay
- Message display: voice messages (play button), file attachments (icon + name)
- Settings: Opus preset selector (Low/Standard/High), compressor toggle
- AndroidManifest: RECORD_AUDIO, storage permissions
2026-07-28 23:15:59 +03:00
evgeny e751e6ee69 android: voice messages + attachments architecture plan, build fixes 2026-07-28 23:07:23 +03:00
evgeny f8d7c21a79 Android: unify DB access via JNI/JSON, fix instance lifecycle, add restart + health check
- ChatRepository: remove SQLiteDatabase (READONLY 2nd handle), use JNI→shared g_cc.db (FULLMUTEX, like desktop)
- instance_lite: simplify start/stop lifecycle — start no longer waits for init, stop waits for thread exit with timeout, add internal restart (no thread kill)
- Restart trampoline: reinit UDP log target on config change
- Health check: ping/uasync_post round-trip every 3s, red dot on stall
- Settings: Restart instance button
- Fix: g_inst check instead of g_running for is_running()
- Fix: SharedFlow replay=1 + proactive DB_READY check in ViewModel
2026-07-28 21:13:11 +03:00
evgeny 03b34e9580 android: fix QR join flow + cleanup log levels + remove log artifacts
- MainActivity: call NativeLib.joinChannel directly (bypass ViewModel null repo)
- LogManager: add Log.d for Kotlin logs visibility in logcat
- ChatViewModel/NativeLib: add diagnostic logs for join path
- chat_sync: JNI-safe logs via __android_log_print, add error on !g_cs
- etcp_debug: DUMP INFO→DEBUG (remove 99.8% packet spam)
- secure_channel/etcp_connections/topo_node: keepalive PING/PONG spam INFO→DEBUG
- .gitignore: logreceiver output + build artifacts
- Remove tracked log files and build dir from repo
2026-07-28 18:11:11 +03:00
evgeny 0216b0bf50 android: utun shutdown diagnostics + non-blocking stop + logreceiver doc
- instance_lite: cleanup moved to uasync thread (self-detach), events on start/stop
- udp_log: cancel_timer before uasync_destroy, flush on stop, stopping flag
- jni_bridge: fixed UDP stop order (cancel_timer → destroy → flush)
- instance_lite: u_report_unfreed_blocks at all shutdown paths
- chat_event: CHAT_EVT_SERVICE_STARTED/STOPPED for GUI notifications
- AGENTS: logreceiver section
2026-07-28 17:06:09 +03:00
evgeny 064bed06f2 android: fix key regeneration, Clear DB, uasync eventfd, udp log from config
- jni_on_event: fix JNI_EDETACHED — AttachCurrentThread/DetachCurrentThread
- u_async.c: pipe→eventfd for reliable cross-thread wakeup
  add process_posted_tasks in poll() path, fix EINTR (saved_errno)
- bridge_debug_hook: keep udp_log_write, remove mutex from udp_log
- memory_pool.c: alloc/free logs INFO→DEBUG (reduce spam)
- instance_lite.c: chat_event_set_handler before ensure_keys,
  KEYS_GENERATED events, fchmod 0600 on keys.conf,
  UDP log from config [log_udp] section, regenerate_trampoline
- config_parser: parse [log_udp] ip/port
- NativeLib.kt: remove callbacksSet, simplify init/stop/destroy
- ChatViewModel.kt: unified event callback in init, pubKey StateFlow,
  requestRegenerateKeys, clearState fix
- SettingsScreen.kt: pubKey.collectAsState(), Clear DB via restart()
- HeadlessService.kt: remove setUdpLogTarget (now from config)
- android_udp_log.c: simplified — no mutex, buffer+send+timer in one thread
- utun_instance.c: NULL-check tcp_connections before destroy
- chat_event.h: +CHAT_EVT_KEYS_GENERATED 11, -NODE_CHANGED
- logreceiver: UDP log receiver tool
2026-07-28 16:29:33 +03:00
evgeny d0cca27edd invite: add proto field (UDP/TCP/BOTH) to invite link binary format
Format per v4 addr: family(1) + socketId(1) + proto(1) + addr(4) + port(2) = 9 bytes (was 8)
Proto values: 0x01=UDP, 0x02=TCP, 0x03=BOTH
Affects: invite_link_c.h/c (Android), invite_link.h/cpp (desktop), chat_sync.c, conn_mgr.c, joindialog.cpp, utun_node.cpp
2026-07-28 00:34:07 +03:00
evgeny 5276c1288c android: real utun instance + SQLite channels + reactive logs + QR join
- instance_lite.h/c: full uTun stack from INI config text
  (keygen, uasync, UTUN_INSTANCE, SQLite, chat_core/sync, BGP, etcp_router)
- jni_bridge: nativeStart/Stop, nativeJoinChannel → chat_sync_connect_from_invite,
  nativeSubscribeLogs, C logs → logcat + Kotlin LogManager
- Kotlin: direct read-only SQLite queries (channels, messages),
  ViewModel with live event callbacks, reactive LogScreen (tail -f),
  CameraX QR scanner with proper lifecycle, JoinChannelDialog
- HeadlessService: generates INI config from Kotlin settings → C instance
2026-07-28 00:01:57 +03:00
evgeny 9e58160480 android: initial chatgui-android project with QR join
- libutun_lite: C99 core, invite_link_c.h/c (compatible with desktop)
- jni_bridge: android_jni_bridge.c/h with logcat logging
- app: Jetpack Compose UI, CameraX + ML Kit QR scanner
- JoinChannelDialog, QrScanScreen with proper camera lifecycle
- headless: control socket with join command
- OpenSSL prebuilt libs for arm64-v8a + x86_64
2026-07-27 22:50:29 +03:00
Evgeny 2d53b3120f ffmpeg: add build script and CMake integration 2026-07-27 17:59:07 +03:00
Evgeny d497a8dc92 media_blocks: fix missing QJsonArray include 2026-07-27 17:16:02 +03:00
Evgeny d4a396f888 media: Ed25519-подписи блоков, local_attrs, MediaBlockReader, файловые вложения 2026-07-27 17:09:31 +03:00
Evgeny 6b765de5cc feat: voice playback delay compensation + waveform click-to-seek 2026-07-27 14:53:53 +03:00
Evgeny da953c6c7a fix: mic test level bar dynamic color too 2026-07-27 14:25:47 +03:00
Evgeny f08b63466c исправлены include-пути в chatgui GUI-файлах и utun_node.cpp после переноса chat в src/chat/ 2026-07-27 13:35:40 +03:00