Commit Graph

81 Commits

Author SHA1 Message Date
Narasimha-sc 96c662d068 android, desktop: fix crash on opening chat with extremely wide image (#7123)
* android, desktop: fix crash on opening chat with extremely wide image

An image with an extreme aspect ratio (e.g. 4000x1) made the chat
unopenable: the framed item's Box clamped its aspectRatio only on the
low side (coerceAtLeast(1f / 2.33f)), leaving very wide images
unbounded. During an intrinsic measure pass Compose derives
width = height * ratio, which for a 4000:1 image overflows Constraints
and throws IllegalArgumentException on every render.

Add the symmetric upper bound (coerceIn(1f / 2.33f, 2.33f)), matching
the existing tall-image height cap in PriorityLayout
(constraints.maxWidth * 2.33f).

* docs: add plan justifying wide-image chat crash fix
2026-06-23 12:24:35 +01:00
spaced4ndy 0e09b38ea6 core: public groups - roster of privileged members (#7017) 2026-06-22 10:15:41 +00:00
Evgeny Poberezkin 8bf571cf5d Merge branch 'stable' 2026-06-20 22:50:19 +01:00
Narasimha-sc 9b76742c6e desktop: fix in-app updater deleting the download before "Open file location" (#7104)
* desktop: fix updater deleting the download before "Open file location"

The in-app updater downloads to a temp UUID file via createTmpFileAndDelete,
then relies on `file.renameTo(newFile)` to move the bytes to the asset name so
they survive that helper's `finally { tmpFile.delete() }`. The rename's return
value was ignored: if it failed, the bytes stayed at the UUID path and the
finally block deleted the only copy, so the "Download completed" dialog appeared
but "Open file location" opened an empty /tmp/simplex.

Use Files.move with REPLACE_EXISTING instead. It performs the same in-place
rename when possible (verified: inode preserved, no copy), falls back to
copy+delete when an atomic rename isn't possible, and throws on genuine failure
- which the existing outer catch handles - instead of silently losing the file.

* docs: plan for updater open-file-location fix

* docs: plan - note Whonix compatibility (updater previously failed there)
2026-06-20 22:49:29 +01:00
Narasimha-sc 134e48fe7e android, desktop, ios: remove right gap on received messages in channels (#7106)
* android, desktop, ios: remove right gap on received messages in channels

In channels received messages now use the full row width instead of the chat-bubble right gap, matching the broadcast/feed style. Gated on ChatInfo.isChannel (useRelays), the always-present channel flag used across the channel UI; sent messages and non-channel groups, business and direct chats are unchanged.

* docs: add plan justifying removing right gap on received messages in channels

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2026-06-20 14:50:46 +01:00
Evgeny Poberezkin b5f0659945 Merge branch 'stable' 2026-06-19 23:41:17 +01:00
Narasimha-sc 547595041e ios: open SimpleX links in chat messages via in-app connect flow (#7101)
* ios: open SimpleX links in chat messages via in-app connect flow

Tapping an inline SimpleX connection link in message text was dispatched
through UIApplication.shared.open. iOS drops an open() of a URL owned by
the same app while it is in the foreground (the simplex: scheme and the
simplex.chat universal links both belong to this app), so the tap was
ignored and never reached the connection flow. Web links (Safari) and
mailto:/tel: (other apps) were unaffected, which is why only SimpleX
links appeared dead.

Route SimpleX links to ChatModel.appOpenUrl instead - the same sink
onOpenURL feeds, leading to connectViaUrl/planAndConnect. This matches
the connection-link card and the multiplatform clients, which connect
in-process rather than via an OS round-trip.

Also fixes the same problem for the "Send questions and ideas" and
"connect to SimpleX Chat developers" buttons, which open simplexTeamURL
(a simplex: link) the same broken way.

* docs: plan - justify iOS in-app dispatch for SimpleX links in messages

Root cause and justification for opening inline SimpleX links via the
in-app connect flow instead of UIApplication.shared.open (undefined
re-entry of the same foreground app for a self-owned simplex: URL).
2026-06-19 23:36:28 +01:00
Narasimha-sc c6122f9637 android, desktop, ios: show clear error when saving group profile fails (#7090)
The API.Error branch in apiUpdateGroup rendered "$r.err", printing the API.Error object reference plus a literal ".err" instead of the error message. Use "${r.err.string}" so the actual error is shown.
2026-06-18 08:44:58 +01:00
Narasimha-sc feebefcdd7 Fix close icon hidden by long file name in compose file preview (#7077)
A long file name took all the row width and squeezed the cancel (X)
icon to zero, so the file could not be dismissed before sending.
Give the file-name text the layout weight and a single line (Compose),
and lineLimit(1) on iOS, so it truncates and the close icon keeps its
space. Affects Android, Desktop and iOS.
2026-06-17 19:27:55 +01:00
Narasimha-sc e60a012d22 android, desktop: fix lag when closing support chat in large groups (#7061)
In groups and channels with thousands of members, opening any screen that
shows the member list could briefly freeze the app. The most noticeable case
was the "Chats with members" screen: closing a member's chat and returning to
the list reloaded everyone and stuttered each time.

The app was re-checking every member against every other member while loading
the list - work that grows with the square of the group size, so it got
dramatically slower as groups grew. It now does this in a single pass, so
member lists, @-mentions, channel relays and adding members all stay
responsive even in very large groups. You see exactly the same members, in
the same order - just without the lag.
2026-06-17 19:23:41 +01:00
Narasimha-sc cf6a60882f Fix IndexOutOfBounds crash when sending media with an undecodable preview (#7050)
* android, desktop: skip media with no decodable preview to fix IndexOutOfBounds on send

processPickedMedia appended to MediaPreview.content unconditionally for videos
(and size-ok animated images) but only appended to images when a preview bitmap
existed. getBitmapFromVideo returns a null preview for undecodable/corrupted
videos without throwing, desyncing the two lists; sendMessageAsync then indexes
images[index] past its end and crashes. Pair both appends behind a non-null
bitmap so the lists stay equal-length and index-aligned, and skip only the bad
item so the rest of the picked batch still sends.

A video skipped this way shows showVideoDecodingException(), guarded by
hasAlertsShown() so it neither stacks across multiple bad items nor duplicates
the alert already shown on getBitmapFromVideo's exception path. Image decode
failures are already surfaced earlier by getBitmapFromUri.

* docs: add plan justifying media preview alignment fix
2026-06-17 19:22:34 +01:00
Narasimha-sc a4cfa2ae97 android, desktop: fix overlapping warning texts after finalizing migration (#7071)
* android, desktop: fix overlapping warning texts after finalizing migration

* plans: justify migration finished screen overlap fix
2026-06-17 19:21:55 +01:00
Narasimha-sc 8504c0ce98 cli: remove help entries for removed commands (#7079)
* cli: remove help entries for commands removed long ago

/pq and /pq @<name> were removed in #4049 (PQ encryption for contacts
is now automatic); /get stats and /reset stats were removed in #4375
(legacy agent stats). All four were left documented in CLI help, so
typing them fails. Remove the stale entries.

* plans: justify removal of outdated CLI help entries

* plans: drop //get stats mention from CLI help plan
2026-06-17 19:18:32 +01:00
spaced4ndy bcd980127d docs: allow sign content messages in channels plan (#7049) 2026-06-17 14:11:04 +00:00
Evgeny Poberezkin 37f7ed3126 Merge branch 'stable' 2026-06-16 07:40:58 +01:00
Evgeny ad23da63d0 core: supporter badges using anonymous BBS credentials (#7040)
* core: supporter badges using anonymous BBS credentials

* badges in profiles

* badge in profiles

* process badges

* update simplexmq

* update simplexmq

* change types

* fix migration

* migration

* update simplexmq

* fix bot API, schema

* fix postgresql build

* refactor

* postgresql schema

* correctly set badges in all cases

* badges ffi

* plan, bot types

* FFI

* FFI: export badge symbols

* add extra field

* refactor badge types to GADT

* configurable badge key

* add badge to profile, test

* ui: badge images

* generate badge key and sign badge

* badge sign in CLI

* fix commands, ui

* rename badges

* Binary

* image size, migration

* update badge images, add public key

* send badges in more cases

* update UI, tests

* bot types, schema

* postgres schema

* tone down badges

* revert formula

* refactor badges

* smaller badges

* badge position

* better badge position

* simpler

* position

* move position

* update simplexmq

* show badge after name

* badge layout

* fix badge

* debug badge height

* shift badge

* fix badge in member name

* bigger badge

* badge layout

* differentiate badge colors

* more avatars for the user's profiles

* refactor

* remove color filter

* alerts

* multiple keys, old expired

* use new BBS api

* update badge keys, bot api

* presentation header

* simplify

* parser

* update iOS images

* update public keys

* query plans

* update simplexmq

* refactor badge types

* simplexmq

* bot api types

* update simplexmq - commoncrypto flag

* update simplexmq

* pass commoncrypto flag to simplexmq in nix iOS build

* ios ui

* update core library, fixes

* badge layout

* badge size

* badge gap

* remove extensions

* simplify

* share badge in more events, reverify badge if verification failed

* larger files with badges

* allow sending larger files

* simpler

* update simplexmq

* better decoder for badge keys

* update simplexmq

---------

Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>
Co-authored-by: shum <github.shum@liber.li>
2026-06-15 22:25:08 +01:00
Narasimha-sc 503d091ec4 android, desktop, ios: show chat name in delete / leave / clear confirmation dialogs (#7021)
* android, desktop, ios: show chat name in delete / leave / clear confirmation dialogs

The dialogs confirming delete contact, delete/leave group/channel and
clear chat now show the chat's name on its own line above the existing
warning, so the user can see which chat the destructive action will
affect.

Pure code change: no new translation strings, no signature changes, no
new helpers. The name is read via existing displayName accessors on
GroupInfo / Contact / ChatInfo.

clearNoteFolderDialog is intentionally unchanged - the notes folder is a
single-instance object and its existing warning already identifies it.

* android, desktop, ios: also show chat name when deleting pending connection

deleteContactConnectionAlert was missed in the original inventory.
Same pattern as the other dispatchers - prepend the connection's
displayName on its own line above the existing warning - so a user
who set a custom name on a pending connection can see which one
they are about to delete.

* android: use <br> instead of \n for newline in delete confirmation dialog body

On Android, the alert body goes through HtmlCompat.fromHtml which
treats the input as HTML and collapses literal \n to a single space -
so "Tech Talk\n\nGroup will be deleted..." rendered as
"Tech Talk Group will be deleted...". Switch to <br><br>, which both
HtmlCompat (Android) and the Desktop parser at Utils.desktop.kt:75
correctly render as a newline.

* android, desktop: skip HTML parsing for delete confirmation dialog text

Add parseHtml: Boolean = true parameter to showAlertDialog and
showAlertDialogButtonsColumn; when false, the body text is wrapped as
AnnotatedString and routed through the existing AnnotatedString
AlertContent overload, bypassing escapedHtmlToAnnotatedString entirely.

The 8 dispatchers that embed the user-controlled chat displayName now
opt out (parseHtml = false). This means:
 - displayName is rendered as literal text - no HTML interpretation,
   so a contact whose alias is "<b>X</b>" or "<font color=..." no
   longer renders bold or coloured in the confirmation dialog
 - the platforms align: both iOS and Kotlin now use plain "\n\n" for
   the separator (no more <br><br> Kotlin-only workaround)
 - removes coupling to escapedHtmlToAnnotatedString in this path

* android, desktop: shorten parseHtml comment in AlertManager

* remove extra text

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2026-06-08 12:13:06 +01:00
Narasimha-sc 7c5406a4e9 desktop: fix drag-and-drop videos attached as files (#7015)
onFilesAttached classified URIs by isImage only, so videos fell through
to processPickedFile and were attached as generic files. processPickedMedia
already handles video correctly; the classifier above it just never
reached that branch. Recognise videos as media inline using getFileName.
2026-06-08 00:02:54 +01:00
Narasimha-sc 7548fdae3b android, desktop: fix chat item long-press menu and ripple shape (#6997)
* android, desktop: fix chat item long-press menu and ripple shape

clipChatItem clipped the bubble shape with Modifier.clip. Modifier.clip
of the bubble GenericShape mis-hit-tests its path on very tall items, so
long-press on the lower part of a long message did not reach
combinedClickable and the context menu did not open (#6991); on desktop
the same clip also left the press ripple rendered as a rectangle.

Clip the bubble GenericShape in the draw pass (drawWithCache + clipPath)
instead: drawing is clipped identically, the press ripple included, with
no effect on hit-test. The RoundRect shape (tail disabled) hit-tests
correctly and keeps Modifier.clip.

Fixes #6991

* plans: justify chat item long-press and ripple shape fix

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
2026-06-07 23:46:59 +01:00
Evgeny Poberezkin d657bcbba5 Merge branch 'stable' 2026-06-01 21:41:26 +01:00
Evgeny 83f4f6cd38 core: rename field in protocol (#7038)
* core: rename field in protocol

* update bot apis

---------

Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>
2026-06-01 21:33:35 +01:00
Evgeny Poberezkin 60e75aa398 Merge branch 'stable' 2026-05-31 17:33:52 +01:00
Evgeny 9bb2bec3fa plan: web previews for channels (#7022)
* plan: web previews for channels

* types for recipient side to support channel web previews and domain names

* fix

* migrations

* update schema and api types

* update schema

* rename migrations

* core: check member role

---------

Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>
2026-05-31 17:12:12 +01:00
Evgeny Poberezkin 6538b15270 Revert "cli: fix redraw slowness (#6735)"
This reverts commit b801d77c74.
2026-05-31 10:38:12 +01:00
Evgeny Poberezkin 9f93e87f56 Merge branch 'stable' 2026-05-30 16:01:27 +01:00
Narasimha-sc 553f98adf4 desktop: don't copy non-message items when selecting message text (#6993)
* desktop: don't copy non-message items when selecting message text

Selecting text across messages also copied the text of event/info
items (e.g. "connected") that fell inside the selection, even though
those items are never highlighted as selected.

getSelectedCopiedText emitted text for every merged item between the
selection bounds. Event/info items have no msgContent but a non-empty
text, so as interior items their text was copied. Skip items whose
content has no msgContent - only real messages are copyable.

* plans: add 2026-05-20-fix-copy-non-msg-items.md
2026-05-30 09:01:16 +01:00
Narasimha-sc 5aace8401c core: fix /start remote host parser when iface name contains a space (#7025)
* core: fix /start remote host parser when iface name contains a space

The iface= field used jsonP (which calls takeByteString and strict-decodes
the entire remaining input as JSON). When port= followed iface=, the strict
decode failed on the trailing data and the text1P fallback stopped at the
first space inside the JSON-quoted interface name (e.g. "Ethernet 2"),
leaving unparseable junk and producing "Failed reading: empty".

Replace jsonP with a bounded quotedP that consumes only up to the closing
quote, leaving port=… for the next parser.

* plan: document fix for /start remote host iface-with-space parser bug
2026-05-30 07:33:10 +01:00
Evgeny Poberezkin 507a4de61c Merge branch 'stable' 2026-05-28 23:19:54 +01:00
Narasimha-sc f3abb7aa76 android, desktop: fix E2E encryption section divider rendered inside card (#7012)
Move SectionDividerSpaced() out of the SectionView { ... } block so it acts
as inter-section spacing instead of being rendered as the section's last
child. Matches the pattern used by every other section in ChatInfoLayout.

Plan: plans/2026-05-25-fix-e2e-encryption-section-divider.md
2026-05-25 19:34:32 +01:00
Narasimha-sc ff36d401ce desktop: fix video playback hang caused by stuck preview snapshot (#6983)
* desktop: fix video playback hang caused by stuck preview snapshot

Problem: clicking play on a video did nothing when an earlier video's
preview generation was stuck — every subsequent VideoPlayer.play() was
queued behind it on the shared playerThread.

Cause: helper player reuse across previews exhausted the libavcodec h264
frame-buffer pool with --avcodec-hw=none (PR #6924), and the synchronous
libvlc snapshots().get() call then hung waiting for a frame that was
never decoded.

Fix: drop the helper-player pool (release each helper after use) and run
preview generation on a dedicated previewThread so a stuck preview can
no longer block playback.

* plans: add 2026-05-15-fix-video-preview-snapshot-hang.md

* desktop: capture preview via callback surface, keep helper pool

Follows up on the previous commit (4a964c66). The actual hang was in
libvlc's synchronous snapshots().get() on a reused helper, not in the
pooling itself. Replace the polling loop with a CallbackVideoSurface
(the existing SkiaBitmapVideoSurface) wrapped in withTimeoutOrNull —
the wait is bounded, so a non-decoding helper can't block previewThread.
Restore the helper-player pool that the previous commit dropped.

* plans: update 2026-05-15-fix-video-preview-snapshot-hang.md for final fix
2026-05-25 16:10:55 +01:00
Narasimha-sc 9bd9e6a16c desktop: fix in-app updater on Windows, AppImage, and aarch64 (#6985)
* desktop: fix in-app updater silently failing on Windows

chooseGitHubReleaseAssets ran `which dpkg` unconditionally to probe for
Debian-derivative systems. On Windows there is no which.exe, so
Runtime.exec threw IOException, which the outer catch in checkForUpdate
logged and swallowed -- the update dialog never appeared. Gate the
probe on desktopPlatform.isLinux().

* desktop: fix in-app updater install step on AppImage

xdg-open on the downloaded .AppImage opened it in whatever the desktop
environment's default handler for the AppImage MIME type is -- usually
an archive viewer, which reports 'Archive format not recognized'. The
running AppImage was never replaced.

Detect $APPIMAGE (set by the AppImage runtime to the path of the
running .AppImage file). Copy the downloaded file to a staging file in
the target's own directory, mark it executable, then atomic-move it
onto $APPIMAGE. Staging in the target directory keeps the final move a
same-filesystem rename(2), so an interrupted copy never leaves the
running AppImage partially overwritten. On failure (permission denied,
target read-only, etc.) fall back to opening the parent directory so
the user can install manually -- the same fallback the existing
xdg-open path already used.

* desktop: fix in-app updater silently failing on aarch64 AppImage

The LINUX_AARCH64 githubAssetName had a literal leading space
(" simplex-desktop-aarch64.AppImage"), so the exact-name filter in
chooseGitHubReleaseAssets never matched the real release asset name
"simplex-desktop-aarch64.AppImage". The asset list came back empty
and checkForUpdate's early-return at "No assets to download for
current system" suppressed the dialog. Same silent-failure pattern as
the Windows bug.

* plans: justify desktop in-app updater fixes
2026-05-25 16:08:48 +01:00
spaced4ndy c017c25d0f core, ui: member full delete with messages (#6994) 2026-05-25 10:43:36 +00:00
Evgeny fe6b5186e1 core: update simplexmq (receiving services) (#6212)
* core: update simplexmq

* update agent api

* update simplexmq

* core: add flag to User to use client services

* update simplexmq

* cli command to toggle service for a user

* test, fix

* query plans, core/bot api types

* remove local package reference

* increase server queue size in tests

* show client service status in users list

* update query plans

* cli: fix redraw slowness (#6735)

* cli: add pland to fix redraw slowness

* updtae doc

* cli: decouple key reading from processing via TQueue

* schema and bot types

---------

Co-authored-by: sh <37271604+shumvgolove@users.noreply.github.com>
2026-05-25 10:37:13 +01:00
spaced4ndy 2b48b55190 core: deliver member profiles via relay (#6953) 2026-05-22 20:52:01 +00:00
spaced4ndy 92e9640e4f core, ui: relay reject rejoin (#6978) 2026-05-18 09:06:25 +00:00
sh c165663555 desktop: prevent duplicate launches (#6979)
* desktop: prevent duplicate launches

Acquires a file lock and listens on a loopback ServerSocket in dataDir.
A second launch signals the running instance to restore its window and
exits silently. See plans/2026-05-13-desktop-single-instance.md.

* desktop: un-minimize window in showWindow

toFront() does not un-minimize a JFrame on any AWT platform. Clear the
ICONIFIED bit so a minimized window restores; preserves MAXIMIZED_BOTH.
Also fixes the same case when restoring from the tray icon.

* desktop: move showWindow from DesktopTray to DesktopApp

It has callers outside the tray (single-instance signal) and belongs
next to simplexWindowState, which it operates on.

* simplify

* refactor

* desktop: start show-file watcher when choosing minimize from first-close dialog

The handleCloseRequest path already starts the watcher when minimizing to
tray; the Ask-dialog path did not, so the first-time user who picks
"Minimize to tray" got a hidden window with no signal handling — a
duplicate launch would not restore it.

* desktop: always watch for duplicate-launch signal, drop hung-instance alert

The watcher now runs for the JVM lifetime once the lock is acquired,
not only when minimized to tray. Duplicate launches always restore the
primary's window (un-minimize, un-tray-hide, toFront) instead of being
silently dropped when the primary is not minimized.

Drops the "may be hung, start anyway?" popup and the two strings — that
fallback was needed only because the watcher could miss signals. With
the always-on watcher there is no scenario where the primary fails to
consume simplex.show, so the escape hatch becomes dead code.

* desktop: alert when primary's watcher doesn't consume the show file

Restores the "another instance may be running" alert. Every duplicate
launch waits up to 1s for the primary's watcher to delete the show file
it just created. If the file is consumed within the window, the
duplicate exits silently. If still there after 1s the primary is hung
and the alert fires.

---------

Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>
2026-05-18 09:15:20 +01:00
Narasimha-sc af24d030fa core, ui: persist "Remove link tracking" setting on database import (#6977)
* core, ui: persist "Remove link tracking" setting on database import

The privacySanitizeLinks preference was stored locally only and absent from
the AppSettings round-trip, so it was lost when migrating to another device
or after a fresh install + DB import. Add the field to the Haskell, Kotlin,
and Swift AppSettings payloads and wire it through iOS group defaults.

* plans: justify privacySanitizeLinks AppSettings round-trip fix
2026-05-16 10:22:57 +01:00
Narasimha-sc 4bea161724 ios: hide private notes from share channel picker (#6980)
Companion to #6958 (Android/Desktop). Channel-link "Share via chat"
picker showed Saved Messages, which produced `Failed reading: empty`
on tap because `*<id>` has no branch in `sendRefP`. Add `includeLocal`
flag to `filterChatsToForwardTo` (default true) and to
`ChatItemForwardingView`; `shareChannelPicker` passes false.

Bug #2 from #6958 (button on plain groups) is not present on iOS —
`GroupLinkView.swift:110` already gates by `publicGroup != nil`.
2026-05-14 15:33:55 +01:00
Narasimha-sc 3b4bf92015 core: include trailing "_" and "!" characters in links (#6973)
* core: include trailing "_" and "!" characters in links

* docs: plan for keeping trailing "_" and "!" in links
2026-05-13 16:45:39 +01:00
Narasimha-sc c82bf05293 android, desktop: hide private notes from share channel picker; hide share via chat on plain groups (#6958)
* ui: hide saved messages from share channel picker; hide share via chat on plain groups

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* plans: justify share channel link picker filter and group-link button gate

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 16:43:06 +01:00
Evgeny 7497b90e7c core, ui: allow indefinite deletion from history for public channel/group owners/moderators (#6972)
* Revert "core: forward compatible support for owners/admins/moderations deleting channel and public group messages without limitations (#6962)"

This reverts commit 08108ebabb.

* core, ui: allow indefinite deletion from history for public channel/group owners/moderators

* style

Co-authored-by: Evgeny <evgeny@poberezkin.com>

* refactor

* show error on deletion

* better alerts

* test

* plan

* simplify test

* bot api docs

* refactor

* test that removed from history is not delivered to the new subscribers

* fix, refactor

* fix

* rename

* rename predicate in UI

* rename

* do not forward channel deletions from history

* remove redundant check

---------

Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>
2026-05-13 15:24:52 +01:00
sh 334a50dda5 desktop: add tray support (#6970)
* plans: design and implementation plan for desktop tray icon

Adds a system tray icon to the SimpleX desktop app with first-close
dialog and an Appearance toggle. Covered: minimize-to-tray on Linux,
Windows, and macOS via ComposeNativeTray; tri-state CloseBehavior
preference; unread-dot icon swap; show/quit menu.

The design plan covers the user-facing behavior, library choice
rationale (AWT/Compose Tray is broken on stock GNOME per JDK-8322750;
ComposeNativeTray uses platform-native APIs and is maintained), and
out-of-scope items. The implementation plan splits the work into 8
incremental commits, each leaving the build green.

* desktop: add CloseBehavior preference

* plans: switch tray to built-in Compose Tray with GNOME probe

* desktop: branch close handler on CloseBehavior preference

* desktop: first-close dialog for tray choice

* desktop: tray icon assets and menu strings

* desktop: system tray icon with show/quit menu

* desktop: unread indicator on tray icon

* desktop: Appearance toggle for minimize-to-tray

* desktop: tray feature fixes from audit

- Scope closedByError per application iteration; set before dispatchEvent.
- Short-circuit Ask to Quit when tray is unavailable.
- Sum users.unreadCount (pre-aggregated) instead of iterating chats.
- Replace dialog's captured lambdas with a top-level flag and
  ApplicationScope extension; wrap in SimpleXTheme.
- Probe SystemTray with real add/remove off the EDT.
- Drop duplicate ic_simplex_tray.svg; nudge unread dot to cy=34 to
  stop the r=6 circle clipping at the viewBox bottom.
- Use mkSafeEnumPreference, PreferenceToggle, SectionTextFooter.

* plans: sync desktop tray plans with implementation

* desktop: render close-behavior popup via AlertManager

Replaces the bespoke DialogWindow with AlertManager.shared.showAlertDialogButtonsColumn —
same in-app surface as e.g. the link-previews opt-in alert. Drops isAskingCloseBehavior,
the CloseBehaviorDialog Composable, the resetAskCloseBehavior helper, and the per-iteration
reset call: the alert's lifecycle is now bounded by AlertManager's single slot, so a crash
mid-dialog gets cleanly overwritten by the crash report.

* desktop: reset closeBehavior with 'Reset all hints'

Generalises AppPreferences.hintPreferences to a heterogeneous List<HintPref>
so non-Boolean prefs can participate. Adds closeBehavior to the list, so
'Reset all hints' brings the first-close dialog back.

* desktop: skip muted profiles in tray unread sum

Active profile still counts so the user can see their own unread; non-active
muted profiles contribute zero.

* desktop: dark-theme tray icons

Adds ic_simplex_tray_light and ic_simplex_tray_dot_light — copies of the
existing tray SVGs with the navy back-X swapped to white. Picks the variant
via isInDarkTheme() so the icon stays visible against dark tray backgrounds.
2026-05-13 08:58:42 +01:00
Narasimha-sc 1f25cec949 android, desktop: constrain image height in layout (#6959)
* android, desktop: constrain image height in layout

Clamp the image preview Box's aspect ratio so it never goes
below 1/2.33 (matching the height cap already enforced by
PriorityLayout). Prevents tall image previews from overflowing
into the caption area below.

Analogous to iOS #6732.

* plans: justify image text overlap fix
2026-05-12 16:48:25 +01:00
spaced4ndy 24859e1281 core: announce added relay (#6956) 2026-05-12 12:36:23 +00:00
sh e63c403623 simplex-chat-python: add python library (#6954)
* docs: simplex-chat-python design and implementation plan

* bots: Python wire types codegen

* simplex-chat-python: package scaffold

* simplex-chat-python: native libsimplex loader

* simplex-chat-python: async FFI wrappers

* simplex-chat-python: ChatApi with 49 api methods

* simplex-chat-python: Bot class with decorators and dispatch

* simplex-chat-python: install CLI, example bot, README

* simplex-chat-python: audit fixes

* bots: regenerate API docs and types

Catches up the markdown, TypeScript and Python codegen outputs with two
upstream schema changes:

- APIConnectPlan.connectionLink became optional (from sh/python-lib audit
  fixes); cmdString and EBNF syntax now reflect optional parameter.
- APIAddGroupRelays command and CRGroupRelaysAdded/CRGroupRelaysAddFailed
  responses added in #6917 (relay management). The TS and markdown outputs
  were regenerated when #6917 landed but the Python types module only got
  the new entries with this regeneration.

* core: refresh SQLite query plans after relay_inactive_at migration

The M20260507_relay_inactive_at migration (#6917 / #6952) shifted the
query plans that 'Save query plans' verifies. Regenerated via the test
that owns those snapshots; no behavioral change.

* bots: keep APIConnectPlan connectionLink as required parameter

The prior audit-fixes commit changed the syntax expression to `Optional ...`
because the Haskell field is `connectionLink :: Maybe AConnectionLink`.
That misrepresents the API contract: the `Maybe` is purely an internal
signal for link-parsing failure (the handler returns `CEInvalidConnReq`
on `Nothing`), not API-level optionality. Callers MUST always pass a
connection link.

Revert the syntax expression to `Param "connectionLink"` and add a
comment so the intent is preserved next time someone audits.

Regenerates COMMANDS.md, commands.ts and _commands.py to match.
2026-05-12 12:32:01 +01:00
Narasimha-sc 5d597faf7e desktop: pick a free port for the call server if 50395 is in use (#6963)
* desktop: pick a free port for the call server if 50395 is in use

startServer() bound a hard-coded port (50395); when it was already in use,
NanoWSD threw "BindException: Address already in use: bind" and the call
failed. It now falls back to an OS-assigned free port, and WebRTCController
opens the browser at the actually-bound port (server.listeningPort) -- still
50395 in the normal case, so browser camera/mic permission stays put.

* plans: justify call server port-bind fix
2026-05-12 10:12:39 +01:00
Narasimha-sc eb4f601c8b core: keep whitelisted query parameters when removing link tracking (#6965)
* core: keep whitelisted query parameters when removing link tracking

In safe mode, "remove link tracking" stripped any query parameter whose
name started with a known tracking prefix in qsSafeBlacklist, ignoring
qsWhitelist. So "list" (e.g. YouTube playlist links) was dropped because
"li" (LinkedIn) is a prefix of it, and github's "ref" was dropped too.
Make the safe-mode filter consult the whitelist, like the other branches.

* docs: plan for keeping whitelisted query parameters when removing link tracking

Design doc for the safe-mode sanitizeUri change (PR #6965): why "?list=" was
stripped from YouTube links, the root cause (safe mode ignoring qsWhitelist),
the fix, what it does/doesn't change, and alternatives considered.
2026-05-12 10:11:26 +01:00
Evgeny 08108ebabb core: forward compatible support for owners/admins/moderations deleting channel and public group messages without limitations (#6962)
* plan: delete channel messages

* core: allow deletion of own messages in public groups in channels to moderators-owners

---------

Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>
2026-05-11 15:00:37 +01:00
Narasimha-sc e10cfd02e9 desktop: keep text selection on the originally selected message when a new message arrives (#6955)
* desktop: keep text selection on the originally selected message when a new message arrives or is sent

Selection stored positional indices into the merged-items list. When a new
message was appended, the reversed list shifted every index by one — but the
stored start/end indices did not — so the highlight slid onto neighboring
messages.

Anchor the selection to ChatItem IDs instead of list positions. Offsets are
already content-relative (character cursors in rendered text), so they stay
valid across list mutations. Positional indices are derived on read via
derivedStateOf, which keeps per-item reads O(1) amortized. If either
anchored item is removed from the list, a SideEffect synchronously clears
the selection so the copy button does not flash at a stale location.

* desktop: minimize selection fix — anchor ids in SelectionRange

Replaces the previous derivedStateOf-based approach with a surgical
diff: SelectionRange carries startItemId/endItemId alongside the
existing positional indices, and a SideEffect calls resyncIndices()
to translate ids back to current positions when the items list mutates.

All existing call sites of range.startIndex / range.endIndex remain
unchanged. Net diff vs master is +19/-2.

* plans: justify desktop text selection id-anchored fix
2026-05-08 13:55:58 +01:00
Narasimha-sc da9b69ca0b android, desktop: open correct image in fullscreen viewer (#6869)
* android, desktop: open correct image in fullscreen viewer

Fullscreen image viewer occasionally opened a different image than
the one clicked. Root cause: when the LaunchedEffect probe at
ImageFullScreenView.kt:48-55 calls getMedia(initialIndex - 1) to check
whether a previous media item exists, getMedia returns null for both
"no item" and "item found but failed to load" (e.g. undecodable bytes,
missing file, crypto error). The probe treated null as "no previous
item" and called scrollToStart(), which rewrote initialChatId to the
chat's oldest media item - making the viewer display that oldest item
instead of the clicked one.

Fix: scrollToStart() no longer rewrites initialChatId. The pager is
still repositioned to page 0; getMedia(0) resolves against the
already-set initialChatId (the clicked item) and renders it correctly.

* android, desktop: regression test for fullscreen viewer anchor preservation

Drives the public providerForGallery interface: moves the anchor away from
cItemId via currentPageChanged, calls scrollToStart, then reads the anchor
back through onDismiss's scrollTo callback. The pre-fix code rewrote
initialChatId to the chat's oldest showable, which would surface as
scrollTo(2); the fix preserves the anchor and produces scrollTo(1).

* plan: design doc for fullscreen viewer wrong-image fix

Documents the pager state model, the root cause of the wrong-image bug,
why the one-line deletion in scrollToStart fixes it for both call sites,
and why the wider getMedia null-overload refactor is deliberately out of
scope for this fix.
2026-05-08 12:18:45 +01:00