From f31054de4ffce6fed8cd5c3e088a363de02a2d0d Mon Sep 17 00:00:00 2001
From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com>
Date: Tue, 28 Nov 2023 06:11:53 +0800
Subject: [PATCH 1/8] desktop (windows): fix action (#3479)
---
.github/workflows/build.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 600b934bd5..afdb9bea1a 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -262,7 +262,7 @@ jobs:
# rm -rf dist-newstyle/src/direct-sq* is here because of the bug in cabal's dependency which prevents second build from finishing
- name: 'Setup MSYS2'
- if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'windows-latest'
+ if: matrix.os == 'windows-latest'
uses: msys2/setup-msys2@v2
with:
msystem: ucrt64
From f94c0311c115ec2127acc3f22ef928cb9ee0ad2f Mon Sep 17 00:00:00 2001
From: Alexander Bondarenko <486682+dpwiz@users.noreply.github.com>
Date: Sat, 2 Dec 2023 14:24:29 +0200
Subject: [PATCH 2/8] raise lower bound on mtl to a real version (#3499)
* raise lower bound on mtl to a real version
* simplexmq
---------
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
---
cabal.project | 2 +-
package.yaml | 2 +-
scripts/nix/sha256map.nix | 2 +-
simplex-chat.cabal | 16 ++++++++--------
stack.yaml | 2 +-
5 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/cabal.project b/cabal.project
index 026f963f7a..a8bb4c2f85 100644
--- a/cabal.project
+++ b/cabal.project
@@ -9,7 +9,7 @@ constraints: zip +disable-bzip2 +disable-zstd
source-repository-package
type: git
location: https://github.com/simplex-chat/simplexmq.git
- tag: 90a8fc91d35c578c3b52ad296a6f1df715da2278
+ tag: 117168ccce93ef5ac478c02e3f02a018fa8a2200
source-repository-package
type: git
diff --git a/package.yaml b/package.yaml
index 7c828ed7d5..e77d43546f 100644
--- a/package.yaml
+++ b/package.yaml
@@ -33,7 +33,7 @@ dependencies:
- http-types == 0.12.*
- http2 >= 4.2.2 && < 4.3
- memory >= 0.15 && < 0.19
- - mtl >= 2.2 && < 3
+ - mtl >= 2.3.1 && < 3
- network >= 3.1.2.7 && < 3.2
- network-transport >= 0.5.6 && < 0.6
- optparse-applicative >= 0.15 && < 0.17
diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix
index 15a4b04ec1..7e39845fea 100644
--- a/scripts/nix/sha256map.nix
+++ b/scripts/nix/sha256map.nix
@@ -1,5 +1,5 @@
{
- "https://github.com/simplex-chat/simplexmq.git"."90a8fc91d35c578c3b52ad296a6f1df715da2278" = "1yjixh6b2s1law3kh885fsbr1inv1r7iy4g9g2bn6j4ygdn8vlzy";
+ "https://github.com/simplex-chat/simplexmq.git"."117168ccce93ef5ac478c02e3f02a018fa8a2200" = "1091c4b8qjp9v29z862b7clda90w3kb4v2aqp5b2jh6yprlga98w";
"https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38";
"https://github.com/kazu-yamamoto/http2.git"."f5525b755ff2418e6e6ecc69e877363b0d0bcaeb" = "0fyx0047gvhm99ilp212mmz37j84cwrfnpmssib5dw363fyb88b6";
"https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d";
diff --git a/simplex-chat.cabal b/simplex-chat.cabal
index 6f7e61eda8..3c080e4b53 100644
--- a/simplex-chat.cabal
+++ b/simplex-chat.cabal
@@ -1,6 +1,6 @@
cabal-version: 1.12
--- This file has been generated from package.yaml by hpack version 0.36.0.
+-- This file has been generated from package.yaml by hpack version 0.35.5.
--
-- see: https://github.com/sol/hpack
@@ -185,7 +185,7 @@ library
, http-types ==0.12.*
, http2 >=4.2.2 && <4.3
, memory >=0.15 && <0.19
- , mtl >=2.2 && <3
+ , mtl >=2.3.1 && <3
, network >=3.1.2.7 && <3.2
, network-transport >=0.5.6 && <0.6
, optparse-applicative >=0.15 && <0.17
@@ -237,7 +237,7 @@ executable simplex-bot
, http-types ==0.12.*
, http2 >=4.2.2 && <4.3
, memory >=0.15 && <0.19
- , mtl >=2.2 && <3
+ , mtl >=2.3.1 && <3
, network >=3.1.2.7 && <3.2
, network-transport >=0.5.6 && <0.6
, optparse-applicative >=0.15 && <0.17
@@ -290,7 +290,7 @@ executable simplex-bot-advanced
, http-types ==0.12.*
, http2 >=4.2.2 && <4.3
, memory >=0.15 && <0.19
- , mtl >=2.2 && <3
+ , mtl >=2.3.1 && <3
, network >=3.1.2.7 && <3.2
, network-transport >=0.5.6 && <0.6
, optparse-applicative >=0.15 && <0.17
@@ -345,7 +345,7 @@ executable simplex-broadcast-bot
, http-types ==0.12.*
, http2 >=4.2.2 && <4.3
, memory >=0.15 && <0.19
- , mtl >=2.2 && <3
+ , mtl >=2.3.1 && <3
, network >=3.1.2.7 && <3.2
, network-transport >=0.5.6 && <0.6
, optparse-applicative >=0.15 && <0.17
@@ -399,7 +399,7 @@ executable simplex-chat
, http-types ==0.12.*
, http2 >=4.2.2 && <4.3
, memory >=0.15 && <0.19
- , mtl >=2.2 && <3
+ , mtl >=2.3.1 && <3
, network ==3.1.*
, network-transport >=0.5.6 && <0.6
, optparse-applicative >=0.15 && <0.17
@@ -457,7 +457,7 @@ executable simplex-directory-service
, http-types ==0.12.*
, http2 >=4.2.2 && <4.3
, memory >=0.15 && <0.19
- , mtl >=2.2 && <3
+ , mtl >=2.3.1 && <3
, network >=3.1.2.7 && <3.2
, network-transport >=0.5.6 && <0.6
, optparse-applicative >=0.15 && <0.17
@@ -541,7 +541,7 @@ test-suite simplex-chat-test
, http-types ==0.12.*
, http2 >=4.2.2 && <4.3
, memory >=0.15 && <0.19
- , mtl >=2.2 && <3
+ , mtl >=2.3.1 && <3
, network ==3.1.*
, network-transport >=0.5.6 && <0.6
, optparse-applicative >=0.15 && <0.17
diff --git a/stack.yaml b/stack.yaml
index 2866c9bfa0..69f8e21213 100644
--- a/stack.yaml
+++ b/stack.yaml
@@ -49,7 +49,7 @@ extra-deps:
# - simplexmq-1.0.0@sha256:34b2004728ae396e3ae449cd090ba7410781e2b3cefc59259915f4ca5daa9ea8,8561
# - ../simplexmq
- github: simplex-chat/simplexmq
- commit: 90a8fc91d35c578c3b52ad296a6f1df715da2278
+ commit: 117168ccce93ef5ac478c02e3f02a018fa8a2200
- github: kazu-yamamoto/http2
commit: f5525b755ff2418e6e6ecc69e877363b0d0bcaeb
# - ../direct-sqlcipher
From 6a9a67db14a3dcf4fd70f42e98081645091d8c7d Mon Sep 17 00:00:00 2001
From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
Date: Sun, 3 Dec 2023 15:42:26 +0000
Subject: [PATCH 3/8] cli: option to mark shown messages as read (off by
default) (#3506)
* cli: option to mark shown messages as read (off by default)
* fix tests
* fix tests
---
apps/simplex-broadcast-bot/src/Broadcast/Options.hs | 1 +
.../src/Directory/Options.hs | 13 +++++++------
src/Simplex/Chat/Options.hs | 8 ++++++++
src/Simplex/Chat/Terminal.hs | 6 +++---
src/Simplex/Chat/Terminal/Output.hs | 8 ++++----
tests/ChatClient.hs | 3 ++-
tests/RemoteTests.hs | 6 +++---
7 files changed, 28 insertions(+), 17 deletions(-)
diff --git a/apps/simplex-broadcast-bot/src/Broadcast/Options.hs b/apps/simplex-broadcast-bot/src/Broadcast/Options.hs
index 3758af2fcb..9a79af4b48 100644
--- a/apps/simplex-broadcast-bot/src/Broadcast/Options.hs
+++ b/apps/simplex-broadcast-bot/src/Broadcast/Options.hs
@@ -83,5 +83,6 @@ mkChatOpts BroadcastBotOpts {coreOptions} =
allowInstantFiles = True,
autoAcceptFileSize = 0,
muteNotifications = True,
+ markRead = False,
maintenance = False
}
diff --git a/apps/simplex-directory-service/src/Directory/Options.hs b/apps/simplex-directory-service/src/Directory/Options.hs
index 8f28c9013a..0ca8cee789 100644
--- a/apps/simplex-directory-service/src/Directory/Options.hs
+++ b/apps/simplex-directory-service/src/Directory/Options.hs
@@ -5,10 +5,10 @@
{-# LANGUAGE ScopedTypeVariables #-}
module Directory.Options
- ( DirectoryOpts (..),
- getDirectoryOpts,
- mkChatOpts,
- )
+ ( DirectoryOpts (..),
+ getDirectoryOpts,
+ mkChatOpts,
+ )
where
import Options.Applicative
@@ -35,8 +35,8 @@ directoryOpts appDir defaultDbFileName = do
<> help "Comma-separated list of super-users in the format CONTACT_ID:DISPLAY_NAME who will be allowed to manage the directory"
)
directoryLog <-
- Just <$>
- strOption
+ Just
+ <$> strOption
( long "directory-file"
<> metavar "DIRECTORY_FILE"
<> help "Append only log for directory state"
@@ -81,5 +81,6 @@ mkChatOpts DirectoryOpts {coreOptions} =
allowInstantFiles = True,
autoAcceptFileSize = 0,
muteNotifications = True,
+ markRead = False,
maintenance = False
}
diff --git a/src/Simplex/Chat/Options.hs b/src/Simplex/Chat/Options.hs
index a6f2b759e6..f8cab1e357 100644
--- a/src/Simplex/Chat/Options.hs
+++ b/src/Simplex/Chat/Options.hs
@@ -42,6 +42,7 @@ data ChatOpts = ChatOpts
allowInstantFiles :: Bool,
autoAcceptFileSize :: Integer,
muteNotifications :: Bool,
+ markRead :: Bool,
maintenance :: Bool
}
@@ -268,6 +269,12 @@ chatOptsP appDir defaultDbFileName = do
( long "mute"
<> help "Mute notifications"
)
+ markRead <-
+ switch
+ ( long "mark-read"
+ <> short 'r'
+ <> help "Mark shown messages as read"
+ )
maintenance <-
switch
( long "maintenance"
@@ -286,6 +293,7 @@ chatOptsP appDir defaultDbFileName = do
allowInstantFiles,
autoAcceptFileSize,
muteNotifications,
+ markRead,
maintenance
}
diff --git a/src/Simplex/Chat/Terminal.hs b/src/Simplex/Chat/Terminal.hs
index 89d234f944..c27675678e 100644
--- a/src/Simplex/Chat/Terminal.hs
+++ b/src/Simplex/Chat/Terminal.hs
@@ -44,7 +44,7 @@ simplexChatTerminal cfg opts t =
handle checkDBKeyError . simplexChatCore cfg opts $ \u cc -> do
ct <- newChatTerminal t opts
when (firstTime cc) . printToTerminal ct $ chatWelcome u
- runChatTerminal ct cc
+ runChatTerminal ct cc opts
checkDBKeyError :: SQLError -> IO ()
checkDBKeyError e = case sqlError e of
@@ -53,5 +53,5 @@ checkDBKeyError e = case sqlError e of
exitFailure
_ -> throwIO e
-runChatTerminal :: ChatTerminal -> ChatController -> IO ()
-runChatTerminal ct cc = raceAny_ [runTerminalInput ct cc, runTerminalOutput ct cc, runInputLoop ct cc]
+runChatTerminal :: ChatTerminal -> ChatController -> ChatOpts -> IO ()
+runChatTerminal ct cc opts = raceAny_ [runTerminalInput ct cc, runTerminalOutput ct cc opts, runInputLoop ct cc]
diff --git a/src/Simplex/Chat/Terminal/Output.hs b/src/Simplex/Chat/Terminal/Output.hs
index 4fa6931f57..be8aa12cfe 100644
--- a/src/Simplex/Chat/Terminal/Output.hs
+++ b/src/Simplex/Chat/Terminal/Output.hs
@@ -142,13 +142,13 @@ withTermLock ChatTerminal {termLock} action = do
action
atomically $ putTMVar termLock ()
-runTerminalOutput :: ChatTerminal -> ChatController -> IO ()
-runTerminalOutput ct cc@ChatController {outputQ, showLiveItems, logFilePath} = do
+runTerminalOutput :: ChatTerminal -> ChatController -> ChatOpts -> IO ()
+runTerminalOutput ct cc@ChatController {outputQ, showLiveItems, logFilePath} ChatOpts {markRead} = do
forever $ do
(_, outputRH, r) <- atomically $ readTBQueue outputQ
case r of
- CRNewChatItem u ci -> markChatItemRead u ci
- CRChatItemUpdated u ci -> markChatItemRead u ci
+ CRNewChatItem u ci -> when markRead $ markChatItemRead u ci
+ CRChatItemUpdated u ci -> when markRead $ markChatItemRead u ci
CRRemoteHostConnected {remoteHost = RemoteHostInfo {remoteHostId}} -> getRemoteUser remoteHostId
CRRemoteHostStopped {remoteHostId_} -> mapM_ removeRemoteUser remoteHostId_
_ -> pure ()
diff --git a/tests/ChatClient.hs b/tests/ChatClient.hs
index 53101cd073..824e6be0a0 100644
--- a/tests/ChatClient.hs
+++ b/tests/ChatClient.hs
@@ -82,6 +82,7 @@ testOpts =
allowInstantFiles = True,
autoAcceptFileSize = 0,
muteNotifications = True,
+ markRead = True,
maintenance = False
}
@@ -174,7 +175,7 @@ startTestChat_ db cfg opts user = do
t <- withVirtualTerminal termSettings pure
ct <- newChatTerminal t opts
cc <- newChatController db (Just user) cfg opts
- chatAsync <- async . runSimplexChat opts user cc . const $ runChatTerminal ct
+ chatAsync <- async . runSimplexChat opts user cc $ \_u cc' -> runChatTerminal ct cc' opts
atomically . unless (maintenance opts) $ readTVar (agentAsync cc) >>= \a -> when (isNothing a) retry
termQ <- newTQueueIO
termAsync <- async $ readTerminalOutput t termQ
diff --git a/tests/RemoteTests.hs b/tests/RemoteTests.hs
index f03e19149f..13bc2942fc 100644
--- a/tests/RemoteTests.hs
+++ b/tests/RemoteTests.hs
@@ -162,13 +162,13 @@ storedBindingsTest = testChat2 aliceProfile aliceDesktopProfile $ \mobile deskto
desktop ##> "/list remote hosts"
desktop <## "Remote hosts:"
- desktop <## "1. Mobile (connected) [lo 127.0.0.1:52230]"
+ desktop <##. "1. Mobile (connected) ["
stopDesktop mobile desktop
desktop ##> "/list remote hosts"
desktop <## "Remote hosts:"
- desktop <## "1. Mobile [lo 127.0.0.1:52230]"
+ desktop <##. "1. Mobile ["
- -- TODO: more parser tests
+-- TODO: more parser tests
remoteMessageTest :: HasCallStack => FilePath -> IO ()
remoteMessageTest = testChat3 aliceProfile aliceDesktopProfile bobProfile $ \mobile desktop bob -> do
From acaa597c908488efd9a2fb5c3b6474bd6d1f9fea Mon Sep 17 00:00:00 2001
From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
Date: Sun, 3 Dec 2023 15:42:43 +0000
Subject: [PATCH 4/8] desktop, android: fix image not appearing in view when
received (#3504)
* desktop, android: fix image not appearing in view when received
* change to KeyChangeEffect
---
.../chat/simplex/common/views/chat/item/CIImageView.kt | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.kt
index 8b0b2debca..1e5919c0b6 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.kt
@@ -164,6 +164,12 @@ fun CIImageView(
}
}
}
+ } else {
+ KeyChangeEffect(file) {
+ if (res.value == null) {
+ res.value = imageAndFilePath(file)
+ }
+ }
}
val loaded = res.value
if (loaded != null) {
From fec5ff3f15ebea7b51b56f4ac612f487cea84328 Mon Sep 17 00:00:00 2001
From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
Date: Sun, 3 Dec 2023 22:21:13 +0000
Subject: [PATCH 5/8] docs: SimpleX address (#3508)
* docs: SimpleX address
* table
* header
---
docs/guide/app-settings.md | 2 +
docs/guide/making-connections.md | 65 ++++++++++++++++++++++++++++++++
2 files changed, 67 insertions(+)
diff --git a/docs/guide/app-settings.md b/docs/guide/app-settings.md
index 5482a1297f..3a966e18e1 100644
--- a/docs/guide/app-settings.md
+++ b/docs/guide/app-settings.md
@@ -45,6 +45,8 @@ When people connect to you via this address, you will receive a connection reque
If you start receiving too many requests via this address it is always safe to remove it – all the connections you created via this address will remain active, as this address is not used to deliver the messages.
+See the comparison with [1-time invitation links](./making-connections.md#comparison-of-1-time-invitation-links-and-simplex-contact-addresses).
+
Read more in [this post](../../blog/20221108-simplex-chat-v4.2-security-audit-new-website.md#auto-accept-contact-requests).
### Chat preferences
diff --git a/docs/guide/making-connections.md b/docs/guide/making-connections.md
index 0cf46aecde..cf14883aee 100644
--- a/docs/guide/making-connections.md
+++ b/docs/guide/making-connections.md
@@ -13,6 +13,71 @@ Private Connection — connect using an invitation link or QR code via video or
Group Chat — Users have the option to create a secret group, share their contact link [which can be deleted later on], or generate a one-time invitation link.
+## Your SimpleX contact address
+
+You can [create an optional long term address](./app-settings.md#your-simplex-contact-address) for other people to connect with you. Unlike 1-time invitation links, these addresses can be used many times, that makes them good to share online, e.g. on social media platforms, or in email signatures. That helps more people discover SimpleX Chat, so please do it!
+
+When people connect to you via this address, you will receive a connection request that you can accept or reject. You can configure an automatic acceptance of connection request and an automatic welcome message that will be sent to the new contacts. You can also share this address as part of your SimpleX profile, so group members can connect to you, and you contacts can share it with others - if this is something that you want.
+
+If you start receiving too many requests via this address it is always safe to remove it – all the connections you created via this address will remain active, as this address is not used to deliver the messages.
+
+### Comparison of 1-time invitation links and SimpleX Contact addresses
+
+
+
+ |
+ 1-time invitation link |
+ SimpleX contact address |
+
+
+ | Can be used many times? |
+ No |
+ Yes |
+
+
+ | Can be included in user profile? |
+ No, as it can only be used once. |
+ Yes, to allow group members to connect directly, and your contacts to pass it on to their contacts. |
+
+
+ | When to use it? |
+ With somebody you know, via another communication channel or QR code (in person or during a video call) |
+ Where many people can see and connect via it, e.g. in email signature, website, social media or group chat. |
+
+
+ | Security |
+ More secure, as can only be used once, and the initial connection request (including profile) is encrypted with double ratchet. |
+ Initial connection request is also e2e encrypted, but without double ratchet (it is initialized when request is accepted). |
+
+
+ | Identification |
+ Both sides know who they connect to, as they know with whom and by who the link was shared. You can attach alias to this invitation as soon as you share it or use it, to identify the other person when connection is established. |
+ Only the person using the address knows who they connect to, via the channel where they found the address (email, social media, etc.). The address owner can only see the user profile of the request, and has no proof of identity from the person sending the request*. |
+
+
+ | Advantages over other platforms |
+ There is no direct analogy, other platforms don’t offer one-time invitations without any fixed part identifying the user. |
+ Unlike addresses in other platforms, SimpleX addresses are not used to deliver the messages — only the initial connection requests. It means that removing this address will not break the contacts made via it (like changing an email address would), it would only prevent new connections, which makes it a good solution against spam and abuse. |
+
+
+ | Vulnerability to attacks |
+ Until the connection is established, anybody who intercepts this link can connect to it, so it has to be verified with the original contact that the connection succeeded. |
+ These addresses are vulnerable to connection request spam. Unlike other platforms, you can delete or change the address, without losing any contacts (see above). |
+
+
+ | Passive attacks on connection links |
+ Both types of links are not vulnerable if simply observed — they only contain public keys. So they can be safely shared via insecure or public channels, as long as you can confirm that you connected to the intended person. |
+
+
+ | Active attacks on connection links |
+ If the link is substituted via the attack on the channel used to share it, the connection security can be compromised, and the original messages monitored (man-in-the-middle attack). If it is a real risk then security code should be verified to mitigate it - doing so proves** that the link and keys were not substituted, and that the end-to-end encryption is secure. |
+
+
+
+* Adding optional verified identities that we plan in the future will change it — the address owner will have an option to request identity verification before accepting the connection.
+
+** Connection security code is the cryptographic hash (SHA256) of combined public keys of both sides — there are 2256 possible security codes (1 with 77 zeros – about [1000 times smaller](https://www.wolframalpha.com/input?i=2%5E256) than the estimated number of atoms in the visible universe).
+
## Conversation preferences
Tap on one of your conversations to open conversation preferences.
From 3481d379c69b0a7e750165a52b2c77de6b27a259 Mon Sep 17 00:00:00 2001
From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
Date: Mon, 4 Dec 2023 10:01:37 +0000
Subject: [PATCH 6/8] core: compatibility with GHC 8.10.7, narrow dependency
ranges (#3503)
* Revert "raise lower bound on mtl to a real version (#3499)"
This reverts commit f94c0311c115ec2127acc3f22ef928cb9ee0ad2f.
* Revert "core: expand ranges to fit ghc 8.10 & 9.6 (#3496)"
This reverts commit 9a1c7f41f770df72891c4fd6a3f459a9062bfd42.
* update simplexmq
* remove netword-transport fork
* simplexmq
* fix test
* fix index-state in cabal.project
* simplexmq
* simplexmq
* bytestring,simplexmq
* template-haskell, simplexmq
* simplexmq
* simplexmq
* mtl
* simplexmq
---
cabal.project | 10 ++-
package.yaml | 21 ++++--
scripts/nix/sha256map.nix | 3 +-
simplex-chat.cabal | 149 +++++++++++++++++++++++++-------------
stack.yaml | 96 ------------------------
5 files changed, 124 insertions(+), 155 deletions(-)
delete mode 100644 stack.yaml
diff --git a/cabal.project b/cabal.project
index a8bb4c2f85..ecfff99fb1 100644
--- a/cabal.project
+++ b/cabal.project
@@ -4,12 +4,14 @@ packages: .
with-compiler: ghc-9.6.3
+index-state: 2023-10-20T00:00:00Z
+
constraints: zip +disable-bzip2 +disable-zstd
source-repository-package
type: git
location: https://github.com/simplex-chat/simplexmq.git
- tag: 117168ccce93ef5ac478c02e3f02a018fa8a2200
+ tag: eaf5317834b069144b5f4897f9c79831983e54dd
source-repository-package
type: git
@@ -45,3 +47,9 @@ source-repository-package
type: git
location: https://github.com/simplex-chat/android-support.git
tag: 9aa09f148089d6752ce563b14c2df1895718d806
+
+-- TODO this fork is only needed to compile with GHC 8.10.7 - it allows previous base version
+source-repository-package
+ type: git
+ location: https://github.com/simplex-chat/zip.git
+ tag: bd421c6b19cc4c465cd7af1f6f26169fb8ee1ebc
diff --git a/package.yaml b/package.yaml
index e77d43546f..6734df9fb8 100644
--- a/package.yaml
+++ b/package.yaml
@@ -19,7 +19,6 @@ dependencies:
- attoparsec == 0.14.*
- base >= 4.7 && < 5
- base64-bytestring >= 1.0 && < 1.3
- - bytestring == 0.11.*
- composition == 1.0.*
- constraints >= 0.12 && < 0.14
- containers == 0.6.*
@@ -32,10 +31,10 @@ dependencies:
- filepath == 1.4.*
- http-types == 0.12.*
- http2 >= 4.2.2 && < 4.3
- - memory >= 0.15 && < 0.19
- - mtl >= 2.3.1 && < 3
+ - memory == 0.18.*
+ - mtl >= 2.3.1 && < 3.0
- network >= 3.1.2.7 && < 3.2
- - network-transport >= 0.5.6 && < 0.6
+ - network-transport == 0.5.6
- optparse-applicative >= 0.15 && < 0.17
- process == 1.6.*
- random >= 1.1 && < 1.3
@@ -45,14 +44,12 @@ dependencies:
- socks == 0.6.*
- sqlcipher-simple == 0.4.*
- stm == 2.5.*
- - template-haskell >= 2.16 && < 2.21
- terminal == 0.2.*
- - text >= 2.0 && < 3
- time == 1.9.*
- tls >= 1.6.0 && < 1.7
- unliftio == 0.2.*
- unliftio-core == 0.2.*
- - zip >= 1.7 && < 2.1
+ - zip == 2.0.*
flags:
swift:
@@ -64,6 +61,16 @@ when:
- condition: flag(swift)
cpp-options:
- -DswiftJSON
+ - condition: impl(ghc >= 9.6.2)
+ dependencies:
+ - bytestring == 0.11.*
+ - template-haskell == 2.20.*
+ - text >= 2.0.1 && < 2.2
+ - condition: impl(ghc < 9.6.2)
+ dependencies:
+ - bytestring == 0.10.*
+ - template-haskell == 2.16.*
+ - text >= 1.2.3.0 && < 1.3
library:
source-dirs: src
diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix
index 7e39845fea..f1c383a969 100644
--- a/scripts/nix/sha256map.nix
+++ b/scripts/nix/sha256map.nix
@@ -1,5 +1,5 @@
{
- "https://github.com/simplex-chat/simplexmq.git"."117168ccce93ef5ac478c02e3f02a018fa8a2200" = "1091c4b8qjp9v29z862b7clda90w3kb4v2aqp5b2jh6yprlga98w";
+ "https://github.com/simplex-chat/simplexmq.git"."eaf5317834b069144b5f4897f9c79831983e54dd" = "0jlic1q08mq9p9sgvigmc59r6x1r5fa1zsfqvvrwd97pwain36mj";
"https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38";
"https://github.com/kazu-yamamoto/http2.git"."f5525b755ff2418e6e6ecc69e877363b0d0bcaeb" = "0fyx0047gvhm99ilp212mmz37j84cwrfnpmssib5dw363fyb88b6";
"https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d";
@@ -7,4 +7,5 @@
"https://github.com/simplex-chat/aeson.git"."aab7b5a14d6c5ea64c64dcaee418de1bb00dcc2b" = "0jz7kda8gai893vyvj96fy962ncv8dcsx71fbddyy8zrvc88jfrr";
"https://github.com/simplex-chat/haskell-terminal.git"."f708b00009b54890172068f168bf98508ffcd495" = "0zmq7lmfsk8m340g47g5963yba7i88n4afa6z93sg9px5jv1mijj";
"https://github.com/simplex-chat/android-support.git"."9aa09f148089d6752ce563b14c2df1895718d806" = "0pbf2pf13v2kjzi397nr13f1h3jv0imvsq8rpiyy2qyx5vd50pqn";
+ "https://github.com/simplex-chat/zip.git"."bd421c6b19cc4c465cd7af1f6f26169fb8ee1ebc" = "1csqfjhvc8wb5h4kxxndmb6iw7b4ib9ff2n81hrizsmnf45a6gg0";
}
diff --git a/simplex-chat.cabal b/simplex-chat.cabal
index 3c080e4b53..a53c15c78a 100644
--- a/simplex-chat.cabal
+++ b/simplex-chat.cabal
@@ -1,6 +1,6 @@
cabal-version: 1.12
--- This file has been generated from package.yaml by hpack version 0.35.5.
+-- This file has been generated from package.yaml by hpack version 0.35.0.
--
-- see: https://github.com/sol/hpack
@@ -171,7 +171,6 @@ library
, attoparsec ==0.14.*
, base >=4.7 && <5
, base64-bytestring >=1.0 && <1.3
- , bytestring ==0.11.*
, composition ==1.0.*
, constraints >=0.12 && <0.14
, containers ==0.6.*
@@ -184,10 +183,10 @@ library
, filepath ==1.4.*
, http-types ==0.12.*
, http2 >=4.2.2 && <4.3
- , memory >=0.15 && <0.19
- , mtl >=2.3.1 && <3
+ , memory ==0.18.*
+ , mtl >=2.3.1 && <3.0
, network >=3.1.2.7 && <3.2
- , network-transport >=0.5.6 && <0.6
+ , network-transport ==0.5.6
, optparse-applicative >=0.15 && <0.17
, process ==1.6.*
, random >=1.1 && <1.3
@@ -197,17 +196,25 @@ library
, socks ==0.6.*
, sqlcipher-simple ==0.4.*
, stm ==2.5.*
- , template-haskell >=2.16 && <2.21
, terminal ==0.2.*
- , text >=2.0 && <3
, time ==1.9.*
, tls >=1.6.0 && <1.7
, unliftio ==0.2.*
, unliftio-core ==0.2.*
- , zip >=1.7 && <2.1
+ , zip ==2.0.*
default-language: Haskell2010
if flag(swift)
cpp-options: -DswiftJSON
+ if impl(ghc >= 9.6.2)
+ build-depends:
+ bytestring ==0.11.*
+ , template-haskell ==2.20.*
+ , text >=2.0.1 && <2.2
+ if impl(ghc < 9.6.2)
+ build-depends:
+ bytestring ==0.10.*
+ , template-haskell ==2.16.*
+ , text >=1.2.3.0 && <1.3
executable simplex-bot
main-is: Main.hs
@@ -223,7 +230,6 @@ executable simplex-bot
, attoparsec ==0.14.*
, base >=4.7 && <5
, base64-bytestring >=1.0 && <1.3
- , bytestring ==0.11.*
, composition ==1.0.*
, constraints >=0.12 && <0.14
, containers ==0.6.*
@@ -236,10 +242,10 @@ executable simplex-bot
, filepath ==1.4.*
, http-types ==0.12.*
, http2 >=4.2.2 && <4.3
- , memory >=0.15 && <0.19
- , mtl >=2.3.1 && <3
+ , memory ==0.18.*
+ , mtl >=2.3.1 && <3.0
, network >=3.1.2.7 && <3.2
- , network-transport >=0.5.6 && <0.6
+ , network-transport ==0.5.6
, optparse-applicative >=0.15 && <0.17
, process ==1.6.*
, random >=1.1 && <1.3
@@ -250,17 +256,25 @@ executable simplex-bot
, socks ==0.6.*
, sqlcipher-simple ==0.4.*
, stm ==2.5.*
- , template-haskell >=2.16 && <2.21
, terminal ==0.2.*
- , text >=2.0 && <3
, time ==1.9.*
, tls >=1.6.0 && <1.7
, unliftio ==0.2.*
, unliftio-core ==0.2.*
- , zip >=1.7 && <2.1
+ , zip ==2.0.*
default-language: Haskell2010
if flag(swift)
cpp-options: -DswiftJSON
+ if impl(ghc >= 9.6.2)
+ build-depends:
+ bytestring ==0.11.*
+ , template-haskell ==2.20.*
+ , text >=2.0.1 && <2.2
+ if impl(ghc < 9.6.2)
+ build-depends:
+ bytestring ==0.10.*
+ , template-haskell ==2.16.*
+ , text >=1.2.3.0 && <1.3
executable simplex-bot-advanced
main-is: Main.hs
@@ -276,7 +290,6 @@ executable simplex-bot-advanced
, attoparsec ==0.14.*
, base >=4.7 && <5
, base64-bytestring >=1.0 && <1.3
- , bytestring ==0.11.*
, composition ==1.0.*
, constraints >=0.12 && <0.14
, containers ==0.6.*
@@ -289,10 +302,10 @@ executable simplex-bot-advanced
, filepath ==1.4.*
, http-types ==0.12.*
, http2 >=4.2.2 && <4.3
- , memory >=0.15 && <0.19
- , mtl >=2.3.1 && <3
+ , memory ==0.18.*
+ , mtl >=2.3.1 && <3.0
, network >=3.1.2.7 && <3.2
- , network-transport >=0.5.6 && <0.6
+ , network-transport ==0.5.6
, optparse-applicative >=0.15 && <0.17
, process ==1.6.*
, random >=1.1 && <1.3
@@ -303,17 +316,25 @@ executable simplex-bot-advanced
, socks ==0.6.*
, sqlcipher-simple ==0.4.*
, stm ==2.5.*
- , template-haskell >=2.16 && <2.21
, terminal ==0.2.*
- , text >=2.0 && <3
, time ==1.9.*
, tls >=1.6.0 && <1.7
, unliftio ==0.2.*
, unliftio-core ==0.2.*
- , zip >=1.7 && <2.1
+ , zip ==2.0.*
default-language: Haskell2010
if flag(swift)
cpp-options: -DswiftJSON
+ if impl(ghc >= 9.6.2)
+ build-depends:
+ bytestring ==0.11.*
+ , template-haskell ==2.20.*
+ , text >=2.0.1 && <2.2
+ if impl(ghc < 9.6.2)
+ build-depends:
+ bytestring ==0.10.*
+ , template-haskell ==2.16.*
+ , text >=1.2.3.0 && <1.3
executable simplex-broadcast-bot
main-is: ../Main.hs
@@ -331,7 +352,6 @@ executable simplex-broadcast-bot
, attoparsec ==0.14.*
, base >=4.7 && <5
, base64-bytestring >=1.0 && <1.3
- , bytestring ==0.11.*
, composition ==1.0.*
, constraints >=0.12 && <0.14
, containers ==0.6.*
@@ -344,10 +364,10 @@ executable simplex-broadcast-bot
, filepath ==1.4.*
, http-types ==0.12.*
, http2 >=4.2.2 && <4.3
- , memory >=0.15 && <0.19
- , mtl >=2.3.1 && <3
+ , memory ==0.18.*
+ , mtl >=2.3.1 && <3.0
, network >=3.1.2.7 && <3.2
- , network-transport >=0.5.6 && <0.6
+ , network-transport ==0.5.6
, optparse-applicative >=0.15 && <0.17
, process ==1.6.*
, random >=1.1 && <1.3
@@ -358,17 +378,25 @@ executable simplex-broadcast-bot
, socks ==0.6.*
, sqlcipher-simple ==0.4.*
, stm ==2.5.*
- , template-haskell >=2.16 && <2.21
, terminal ==0.2.*
- , text >=2.0 && <3
, time ==1.9.*
, tls >=1.6.0 && <1.7
, unliftio ==0.2.*
, unliftio-core ==0.2.*
- , zip >=1.7 && <2.1
+ , zip ==2.0.*
default-language: Haskell2010
if flag(swift)
cpp-options: -DswiftJSON
+ if impl(ghc >= 9.6.2)
+ build-depends:
+ bytestring ==0.11.*
+ , template-haskell ==2.20.*
+ , text >=2.0.1 && <2.2
+ if impl(ghc < 9.6.2)
+ build-depends:
+ bytestring ==0.10.*
+ , template-haskell ==2.16.*
+ , text >=1.2.3.0 && <1.3
executable simplex-chat
main-is: Main.hs
@@ -385,7 +413,6 @@ executable simplex-chat
, attoparsec ==0.14.*
, base >=4.7 && <5
, base64-bytestring >=1.0 && <1.3
- , bytestring ==0.11.*
, composition ==1.0.*
, constraints >=0.12 && <0.14
, containers ==0.6.*
@@ -398,10 +425,10 @@ executable simplex-chat
, filepath ==1.4.*
, http-types ==0.12.*
, http2 >=4.2.2 && <4.3
- , memory >=0.15 && <0.19
- , mtl >=2.3.1 && <3
+ , memory ==0.18.*
+ , mtl >=2.3.1 && <3.0
, network ==3.1.*
- , network-transport >=0.5.6 && <0.6
+ , network-transport ==0.5.6
, optparse-applicative >=0.15 && <0.17
, process ==1.6.*
, random >=1.1 && <1.3
@@ -412,18 +439,26 @@ executable simplex-chat
, socks ==0.6.*
, sqlcipher-simple ==0.4.*
, stm ==2.5.*
- , template-haskell >=2.16 && <2.21
, terminal ==0.2.*
- , text >=2.0 && <3
, time ==1.9.*
, tls >=1.6.0 && <1.7
, unliftio ==0.2.*
, unliftio-core ==0.2.*
, websockets ==0.12.*
- , zip >=1.7 && <2.1
+ , zip ==2.0.*
default-language: Haskell2010
if flag(swift)
cpp-options: -DswiftJSON
+ if impl(ghc >= 9.6.2)
+ build-depends:
+ bytestring ==0.11.*
+ , template-haskell ==2.20.*
+ , text >=2.0.1 && <2.2
+ if impl(ghc < 9.6.2)
+ build-depends:
+ bytestring ==0.10.*
+ , template-haskell ==2.16.*
+ , text >=1.2.3.0 && <1.3
executable simplex-directory-service
main-is: ../Main.hs
@@ -443,7 +478,6 @@ executable simplex-directory-service
, attoparsec ==0.14.*
, base >=4.7 && <5
, base64-bytestring >=1.0 && <1.3
- , bytestring ==0.11.*
, composition ==1.0.*
, constraints >=0.12 && <0.14
, containers ==0.6.*
@@ -456,10 +490,10 @@ executable simplex-directory-service
, filepath ==1.4.*
, http-types ==0.12.*
, http2 >=4.2.2 && <4.3
- , memory >=0.15 && <0.19
- , mtl >=2.3.1 && <3
+ , memory ==0.18.*
+ , mtl >=2.3.1 && <3.0
, network >=3.1.2.7 && <3.2
- , network-transport >=0.5.6 && <0.6
+ , network-transport ==0.5.6
, optparse-applicative >=0.15 && <0.17
, process ==1.6.*
, random >=1.1 && <1.3
@@ -470,17 +504,25 @@ executable simplex-directory-service
, socks ==0.6.*
, sqlcipher-simple ==0.4.*
, stm ==2.5.*
- , template-haskell >=2.16 && <2.21
, terminal ==0.2.*
- , text >=2.0 && <3
, time ==1.9.*
, tls >=1.6.0 && <1.7
, unliftio ==0.2.*
, unliftio-core ==0.2.*
- , zip >=1.7 && <2.1
+ , zip ==2.0.*
default-language: Haskell2010
if flag(swift)
cpp-options: -DswiftJSON
+ if impl(ghc >= 9.6.2)
+ build-depends:
+ bytestring ==0.11.*
+ , template-haskell ==2.20.*
+ , text >=2.0.1 && <2.2
+ if impl(ghc < 9.6.2)
+ build-depends:
+ bytestring ==0.10.*
+ , template-haskell ==2.16.*
+ , text >=1.2.3.0 && <1.3
test-suite simplex-chat-test
type: exitcode-stdio-1.0
@@ -524,7 +566,6 @@ test-suite simplex-chat-test
, attoparsec ==0.14.*
, base >=4.7 && <5
, base64-bytestring >=1.0 && <1.3
- , bytestring ==0.11.*
, composition ==1.0.*
, constraints >=0.12 && <0.14
, containers ==0.6.*
@@ -540,10 +581,10 @@ test-suite simplex-chat-test
, hspec ==2.11.*
, http-types ==0.12.*
, http2 >=4.2.2 && <4.3
- , memory >=0.15 && <0.19
- , mtl >=2.3.1 && <3
+ , memory ==0.18.*
+ , mtl >=2.3.1 && <3.0
, network ==3.1.*
- , network-transport >=0.5.6 && <0.6
+ , network-transport ==0.5.6
, optparse-applicative >=0.15 && <0.17
, process ==1.6.*
, random >=1.1 && <1.3
@@ -555,14 +596,22 @@ test-suite simplex-chat-test
, socks ==0.6.*
, sqlcipher-simple ==0.4.*
, stm ==2.5.*
- , template-haskell >=2.16 && <2.21
, terminal ==0.2.*
- , text >=2.0 && <3
, time ==1.9.*
, tls >=1.6.0 && <1.7
, unliftio ==0.2.*
, unliftio-core ==0.2.*
- , zip >=1.7 && <2.1
+ , zip ==2.0.*
default-language: Haskell2010
if flag(swift)
cpp-options: -DswiftJSON
+ if impl(ghc >= 9.6.2)
+ build-depends:
+ bytestring ==0.11.*
+ , template-haskell ==2.20.*
+ , text >=2.0.1 && <2.2
+ if impl(ghc < 9.6.2)
+ build-depends:
+ bytestring ==0.10.*
+ , template-haskell ==2.16.*
+ , text >=1.2.3.0 && <1.3
diff --git a/stack.yaml b/stack.yaml
deleted file mode 100644
index 69f8e21213..0000000000
--- a/stack.yaml
+++ /dev/null
@@ -1,96 +0,0 @@
-# This file was automatically generated by 'stack init'
-#
-# Some commonly used options have been documented as comments in this file.
-# For advanced use and comprehensive documentation of the format, please see:
-# https://docs.haskellstack.org/en/stable/yaml_configuration/
-
-# Resolver to choose a 'specific' stackage snapshot or a compiler version.
-# A snapshot resolver dictates the compiler version and the set of packages
-# to be used for project dependencies. For example:
-#
-# resolver: lts-3.5
-# resolver: nightly-2015-09-21
-# resolver: ghc-7.10.2
-#
-# The location of a snapshot can be provided as a file or url. Stack assumes
-# a snapshot provided as a file might change, whereas a url resource does not.
-#
-# resolver: ./custom-snapshot.yaml
-# resolver: https://example.com/snapshots/2018-01-01.yaml
-resolver: lts-18.21
-
-# User packages to be built.
-# Various formats can be used as shown in the example below.
-#
-# packages:
-# - some-directory
-# - https://example.com/foo/bar/baz-0.0.2.tar.gz
-# subdirs:
-# - auto-update
-# - wai
-packages:
- - .
-# Dependency packages to be pulled from upstream that are not in the resolver.
-# These entries can reference officially published versions as well as
-# forks / in-progress versions pinned to a git hash. For example:
-#
-extra-deps:
- - cryptostore-0.2.1.0@sha256:9896e2984f36a1c8790f057fd5ce3da4cbcaf8aa73eb2d9277916886978c5b19,3881
- - network-3.1.2.7@sha256:e3d78b13db9512aeb106e44a334ab42b7aa48d26c097299084084cb8be5c5568,4888
- - simple-logger-0.1.0@sha256:be8ede4bd251a9cac776533bae7fb643369ebd826eb948a9a18df1a8dd252ff8,1079
- - tls-1.6.0@sha256:7ae39373fd2de27fb80e90f76d22aeeb9a074a0ddd120cbd02c9c52f516a9e55,6987
- # below hackage dependencies are to update Aeson to 2.0.3
- - OneTuple-0.3.1@sha256:a848c096c9d29e82ffdd30a9998aa2931cbccb3a1bc137539d80f6174d31603e,2262
- - attoparsec-0.14.4@sha256:79584bdada8b730cb5138fca8c35c76fbef75fc1d1e01e6b1d815a5ee9843191,5810
- - hashable-1.4.0.2@sha256:0cddd0229d1aac305ea0404409c0bbfab81f075817bd74b8b2929eff58333e55,5005
- - semialign-1.2.0.1@sha256:0e179b4d3a8eff79001d374d6c91917c6221696b9620f0a4d86852fc6a9b9501,2836
- - text-short-0.1.5@sha256:962c6228555debdc46f758d0317dea16e5240d01419b42966674b08a5c3d8fa6,3498
- - time-compat-1.9.6.1@sha256:42d8f2e08e965e1718917d54ad69e1d06bd4b87d66c41dc7410f59313dba4ed1,5033
- # - simplexmq-1.0.0@sha256:34b2004728ae396e3ae449cd090ba7410781e2b3cefc59259915f4ca5daa9ea8,8561
- # - ../simplexmq
- - github: simplex-chat/simplexmq
- commit: 117168ccce93ef5ac478c02e3f02a018fa8a2200
- - github: kazu-yamamoto/http2
- commit: f5525b755ff2418e6e6ecc69e877363b0d0bcaeb
- # - ../direct-sqlcipher
- - github: simplex-chat/direct-sqlcipher
- commit: f814ee68b16a9447fbb467ccc8f29bdd3546bfd9
- # - ../sqlcipher-simple
- - github: simplex-chat/sqlcipher-simple
- commit: a46bd361a19376c5211f1058908fc0ae6bf42446
- # - terminal-0.2.0.0@sha256:de6770ecaae3197c66ac1f0db5a80cf5a5b1d3b64a66a05b50f442de5ad39570,2977
- - github: simplex-chat/aeson
- commit: aab7b5a14d6c5ea64c64dcaee418de1bb00dcc2b
- - github: simplex-chat/haskell-terminal
- commit: f708b00009b54890172068f168bf98508ffcd495
- - github: simplex-chat/android-support
- commit: 9aa09f148089d6752ce563b14c2df1895718d806
-#
-# extra-deps: []
-
-# Override default flag values for local packages and extra-deps
-flags:
- zip:
- disable-bzip2: true
- disable-zstd: true
- direct-sqlcipher:
- openssl: true
-# Extra package databases containing global packages
-# extra-package-dbs: []
-
-# Control whether we use the GHC we find on the path
-# system-ghc: true
-#
-# Require a specific version of stack, using version ranges
-# require-stack-version: -any # Default
-# require-stack-version: ">=2.1"
-#
-# Override the architecture used by stack, especially useful on Windows
-# arch: i386
-# arch: x86_64
-#
-# Extra directories used by stack for building
-# extra-lib-dirs: [/path/to/dir]
-#
-# Allow a newer minor version of GHC than the snapshot specifies
-# compiler-check: newer-minor
From 7099776357514c43555b90ef4df15b3a556fab2a Mon Sep 17 00:00:00 2001
From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
Date: Mon, 4 Dec 2023 19:17:20 +0400
Subject: [PATCH 7/8] docs: fix typo
---
docs/guide/making-connections.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/guide/making-connections.md b/docs/guide/making-connections.md
index cf14883aee..2dd1f4bd77 100644
--- a/docs/guide/making-connections.md
+++ b/docs/guide/making-connections.md
@@ -17,7 +17,7 @@ Group Chat — Users have the option to create a secret group, share their conta
You can [create an optional long term address](./app-settings.md#your-simplex-contact-address) for other people to connect with you. Unlike 1-time invitation links, these addresses can be used many times, that makes them good to share online, e.g. on social media platforms, or in email signatures. That helps more people discover SimpleX Chat, so please do it!
-When people connect to you via this address, you will receive a connection request that you can accept or reject. You can configure an automatic acceptance of connection request and an automatic welcome message that will be sent to the new contacts. You can also share this address as part of your SimpleX profile, so group members can connect to you, and you contacts can share it with others - if this is something that you want.
+When people connect to you via this address, you will receive a connection request that you can accept or reject. You can configure an automatic acceptance of connection request and an automatic welcome message that will be sent to the new contacts. You can also share this address as part of your SimpleX profile, so group members can connect to you, and your contacts can share it with others - if this is something that you want.
If you start receiving too many requests via this address it is always safe to remove it – all the connections you created via this address will remain active, as this address is not used to deliver the messages.
From c8e9788c292cd6fcfb9901865e6b0657af35953e Mon Sep 17 00:00:00 2001
From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com>
Date: Tue, 5 Dec 2023 05:04:58 +0800
Subject: [PATCH 8/8] desktop: enhancements to remote desktop connect UI
(#3513)
* desktop: enhancements to remote desktop connect UI
* changes
* more changes
This reverts commit e8323e8bfa6619a114fc5447a612b8780b8b82cd.
* color
* random port
---
.../views/helpers/DefaultBasicTextField.kt | 9 +-
.../helpers/ExposedDropDownSettingRow.kt | 7 +-
.../common/views/remote/ConnectMobileView.kt | 128 ++++++++++--------
.../commonMain/resources/MR/base/strings.xml | 5 +-
.../commonMain/resources/MR/de/strings.xml | 2 +-
.../commonMain/resources/MR/fr/strings.xml | 2 +-
.../commonMain/resources/MR/it/strings.xml | 2 +-
.../commonMain/resources/MR/nl/strings.xml | 2 +-
.../commonMain/resources/MR/pl/strings.xml | 2 +-
.../commonMain/resources/MR/ru/strings.xml | 2 +-
.../resources/MR/zh-rCN/strings.xml | 2 +-
11 files changed, 90 insertions(+), 73 deletions(-)
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultBasicTextField.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultBasicTextField.kt
index 65098cea25..a6f0d2c9b6 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultBasicTextField.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultBasicTextField.kt
@@ -19,8 +19,8 @@ import dev.icerock.moko.resources.compose.painterResource
import androidx.compose.ui.text.*
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.*
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.*
import chat.simplex.common.views.database.PassphraseStrength
import chat.simplex.common.views.database.validKey
import chat.simplex.res.MR
@@ -123,6 +123,7 @@ fun DefaultConfigurableTextField(
isValid: (String) -> Boolean,
keyboardActions: KeyboardActions = KeyboardActions(),
keyboardType: KeyboardType = KeyboardType.Text,
+ fontSize: TextUnit = 16.sp,
dependsOn: State? = null,
) {
var valid by remember { mutableStateOf(isValid(state.value.text)) }
@@ -175,14 +176,14 @@ fun DefaultConfigurableTextField(
textStyle = TextStyle.Default.copy(
color = color,
fontWeight = FontWeight.Normal,
- fontSize = 16.sp
+ fontSize = fontSize
),
interactionSource = interactionSource,
decorationBox = @Composable { innerTextField ->
TextFieldDefaults.TextFieldDecorationBox(
value = state.value.text,
innerTextField = innerTextField,
- placeholder = { Text(placeholder, color = MaterialTheme.colors.secondary) },
+ placeholder = { Text(placeholder, color = MaterialTheme.colors.secondary, fontSize = fontSize, maxLines = 1, overflow = TextOverflow.Ellipsis) },
singleLine = true,
enabled = enabled,
isError = !valid,
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ExposedDropDownSettingRow.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ExposedDropDownSettingRow.kt
index 904b2fc34b..290bc2cd07 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ExposedDropDownSettingRow.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ExposedDropDownSettingRow.kt
@@ -20,6 +20,7 @@ fun ExposedDropDownSetting(
values: List>,
selection: State,
textColor: Color = MaterialTheme.colors.secondary,
+ fontSize: TextUnit = 16.sp,
label: String? = null,
enabled: State = mutableStateOf(true),
minWidth: Dp = 200.dp,
@@ -43,7 +44,8 @@ fun ExposedDropDownSetting(
Modifier.widthIn(max = maxWidth),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
- color = textColor
+ color = textColor,
+ fontSize = fontSize,
)
Spacer(Modifier.size(12.dp))
Icon(
@@ -69,6 +71,7 @@ fun ExposedDropDownSetting(
maxLines = 1,
overflow = TextOverflow.Ellipsis,
color = if (isInDarkTheme()) MenuTextColorDark else Color.Black,
+ fontSize = fontSize,
)
}
}
@@ -91,6 +94,6 @@ fun ExposedDropDownSettingRow(
onSelected: (T) -> Unit
) {
SettingsActionItemWithContent(icon, title, iconColor = iconTint, disabled = !enabled.value) {
- ExposedDropDownSetting(values, selection ,textColor, label, enabled, minWidth, maxWidth, onSelected)
+ ExposedDropDownSetting(values, selection ,textColor, label = label, enabled = enabled, minWidth = minWidth, maxWidth = maxWidth, onSelected = onSelected)
}
}
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectMobileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectMobileView.kt
index c1c5d978cc..a3218c961b 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectMobileView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectMobileView.kt
@@ -174,8 +174,6 @@ private fun ConnectMobileViewLayout(
sessionCode: String?,
port: String?,
staleQrCode: Boolean = false,
- editEnabled: Boolean = false,
- editClicked: () -> Unit = {},
refreshQrCode: () -> Unit = {},
UnderQrLayout: @Composable () -> Unit = {},
) {
@@ -201,16 +199,7 @@ private fun ConnectMobileViewLayout(
}
}
SectionTextFooter(annotatedStringResource(MR.strings.open_on_mobile_and_scan_qr_code), textAlign = TextAlign.Center)
- Row(verticalAlignment = Alignment.CenterVertically) {
- SectionTextFooter(annotatedStringResource(MR.strings.waiting_for_mobile_to_connect_on_port, port), textAlign = TextAlign.Center)
- if (editEnabled) {
- Spacer(Modifier.width(4.dp))
- IconButton(editClicked, Modifier.size(16.dp)) {
- Icon(painterResource(MR.images.ic_edit), stringResource(MR.strings.edit_verb), Modifier.size(16.dp), tint = MaterialTheme.colors.primary)
- }
- Spacer(Modifier.width(DEFAULT_PADDING))
- }
- }
+ SectionTextFooter(annotatedStringResource(MR.strings.waiting_for_mobile_to_connect), textAlign = TextAlign.Center)
UnderQrLayout()
@@ -249,7 +238,9 @@ private fun ConnectMobileViewLayout(
}
}
}
- SectionBottomSpacer()
+ if (invitation != null) {
+ SectionBottomSpacer()
+ }
}
}
@@ -275,10 +266,9 @@ private fun showAddingMobileDevice(connecting: MutableState) {
@Composable
fun AddingMobileDevice(showTitle: Boolean, staleQrCode: MutableState, connecting: MutableState, close: () -> Unit) {
- val cachedR = remember { mutableStateOf(null) }
+ var cachedR by remember { mutableStateOf(null) }
val customAddress = rememberSaveable { mutableStateOf(null) }
val customPort = rememberSaveable { mutableStateOf(null) }
- var editing by rememberSaveable { mutableStateOf(false) }
val startRemoteHost = suspend {
val r = chatModel.controller.startRemoteHost(
rhId = null,
@@ -287,9 +277,9 @@ fun AddingMobileDevice(showTitle: Boolean, staleQrCode: MutableState, c
port = if (customPort.value != cachedR.port) customPort.value else cachedR.rh?.bindPort_
)
if (r != null) {
- cachedR.value = r
+ cachedR = r
connecting.value = true
- customAddress.value = cachedR.address
+ customAddress.value = cachedR.addresses.firstOrNull()
customPort.value = cachedR.port
chatModel.remoteHostPairing.value = null to RemoteHostSessionState.Starting
}
@@ -307,23 +297,20 @@ fun AddingMobileDevice(showTitle: Boolean, staleQrCode: MutableState, c
val remoteDeviceName = pairing.value?.first?.hostDeviceName
ConnectMobileViewLayout(
title = if (!showTitle) null else if (cachedSessionCode == null) stringResource(MR.strings.link_a_mobile) else stringResource(MR.strings.verify_connection),
- invitation = cachedR.invitation,
+ invitation = cachedR?.invitation,
deviceName = remoteDeviceName,
sessionCode = cachedSessionCode,
- port = cachedR.value?.ctrlPort,
- staleQrCode = staleQrCode.value || (cachedR.address != customAddress.value && customAddress.value != null) || (cachedR.port != customPort.value && customPort.value != null),
- editEnabled = !editing && cachedR.addresses.isNotEmpty(),
- editClicked = { editing = true },
+ port = cachedR?.ctrlPort,
+ staleQrCode = staleQrCode.value || (cachedR.address != customAddress.value && customAddress.value != null) || cachedR.port != customPort.value,
refreshQrCode = {
withBGApi {
if (chatController.stopRemoteHost(null)) {
startRemoteHost()
staleQrCode.value = false
- editing = false
}
}
},
- UnderQrLayout = { UnderQrLayout(editing, cachedR, customAddress, customPort) }
+ UnderQrLayout = { UnderQrLayout(cachedR, customAddress, customPort) }
)
val oldRemoteHostId by remember { mutableStateOf(chatModel.currentRemoteHost.value?.remoteHostId) }
LaunchedEffect(remember { chatModel.currentRemoteHost }.value) {
@@ -353,10 +340,9 @@ fun AddingMobileDevice(showTitle: Boolean, staleQrCode: MutableState, c
private fun showConnectMobileDevice(rh: RemoteHostInfo, connecting: MutableState) {
ModalManager.start.showModalCloseable { close ->
- val cachedR = remember { mutableStateOf(null) }
+ var cachedR by remember { mutableStateOf(null) }
val customAddress = rememberSaveable { mutableStateOf(null) }
val customPort = rememberSaveable { mutableStateOf(null) }
- var editing by rememberSaveable { mutableStateOf(false) }
val startRemoteHost = suspend {
val r = chatModel.controller.startRemoteHost(
rhId = rh.remoteHostId,
@@ -365,9 +351,9 @@ private fun showConnectMobileDevice(rh: RemoteHostInfo, connecting: MutableState
port = if (customPort.value != cachedR.port) customPort.value else cachedR.rh?.bindPort_ ?: rh.bindPort_
)
if (r != null) {
- cachedR.value = r
+ cachedR = r
connecting.value = true
- customAddress.value = cachedR.address
+ customAddress.value = cachedR.addresses.firstOrNull()
customPort.value = cachedR.port
chatModel.remoteHostPairing.value = null to RemoteHostSessionState.Starting
}
@@ -384,22 +370,19 @@ private fun showConnectMobileDevice(rh: RemoteHostInfo, connecting: MutableState
}
ConnectMobileViewLayout(
title = if (cachedSessionCode == null) stringResource(MR.strings.scan_from_mobile) else stringResource(MR.strings.verify_connection),
- invitation = cachedR.invitation,
+ invitation = cachedR?.invitation,
deviceName = pairing.value?.first?.hostDeviceName ?: rh.hostDeviceName,
sessionCode = cachedSessionCode,
- port = cachedR.value?.ctrlPort,
- staleQrCode = (cachedR.address != customAddress.value && customAddress.value != null) || (cachedR.port != customPort.value && customPort.value != null),
- editEnabled = !editing && cachedR.addresses.isNotEmpty(),
- editClicked = { editing = true },
+ port = cachedR?.ctrlPort,
+ staleQrCode = (cachedR.address != customAddress.value && customAddress.value != null) || cachedR.port != customPort.value,
refreshQrCode = {
withBGApi {
if (chatController.stopRemoteHost(rh.remoteHostId)) {
startRemoteHost()
- editing = false
}
}
},
- UnderQrLayout = { UnderQrLayout(editing, cachedR, customAddress, customPort) }
+ UnderQrLayout = { UnderQrLayout(cachedR, customAddress, customPort) }
)
LaunchedEffect(remember { chatModel.currentRemoteHost }.value) {
if (cachedR.remoteHostId != null && chatModel.currentRemoteHost.value?.remoteHostId == cachedR.remoteHostId) {
@@ -453,48 +436,75 @@ private fun showConnectedMobileDevice(rh: RemoteHostInfo, disconnectHost: () ->
}
@Composable
-private fun UnderQrLayout(editing: Boolean, cachedR: State, customAddress: MutableState, customPort: MutableState) {
- if (editing) {
- Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center) {
+private fun UnderQrLayout(cachedR: CR.RemoteHostStarted?, customAddress: MutableState, customPort: MutableState) {
+ Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center) {
+ if (cachedR.addresses.size > 1) {
ExposedDropDownSetting(
cachedR.addresses.map { it to it.address + " (${it.`interface`})" },
customAddress,
textColor = MaterialTheme.colors.onBackground,
+ fontSize = 14.sp,
minWidth = 250.dp,
maxWidth = with(LocalDensity.current) { 250.sp.toDp() },
+ enabled = remember { mutableStateOf(cachedR.addresses.size > 1) },
onSelected = {
customAddress.value = it
}
)
- val portUnsaved = rememberSaveable(stateSaver = TextFieldValue.Saver) {
- mutableStateOf(TextFieldValue((customPort.value ?: cachedR.port!!).toString()))
- }
- Spacer(Modifier.width(DEFAULT_PADDING))
+ } else {
+ Spacer(Modifier.width(10.dp))
+ Text(customAddress.value?.address + " (${customAddress.value?.`interface`})", fontSize = 14.sp, color = MaterialTheme.colors.onBackground)
+ }
+ val portUnsaved = rememberSaveable(stateSaver = TextFieldValue.Saver) {
+ mutableStateOf(TextFieldValue((customPort.value ?: cachedR.port!!).toString()))
+ }
+ Spacer(Modifier.width(DEFAULT_PADDING))
+ Box {
DefaultConfigurableTextField(
portUnsaved,
- stringResource(MR.strings.port_verb),
- modifier = Modifier.widthIn(max = 100.dp),
- isValid = { validPort(it) && it.toInt() > 1023 },
+ stringResource(MR.strings.random_port),
+ modifier = Modifier.widthIn(max = 132.dp),
+ isValid = { (validPort(it) && it.toInt() > 1023) || it.isBlank() },
keyboardActions = KeyboardActions(onDone = { defaultKeyboardAction(ImeAction.Done) }),
keyboardType = KeyboardType.Number,
+ fontSize = 14.sp,
)
- LaunchedEffect(Unit) {
- snapshotFlow { portUnsaved.value.text }
- .distinctUntilChanged()
- .collect {
- if (validPort(it) && it.toInt() > 1023) {
- customPort.value = it.toInt()
- }
+ if (validPort(portUnsaved.value.text) && portUnsaved.value.text.toInt() > 1023) {
+ Icon(painterResource(MR.images.ic_edit), stringResource(MR.strings.edit_verb), Modifier.padding(end = 56.dp).size(16.dp).align(Alignment.CenterEnd), tint = MaterialTheme.colors.secondary)
+ IconButton(::showOpenPortAlert, Modifier.align(Alignment.TopEnd).padding(top = 2.dp)) {
+ Icon(painterResource(MR.images.ic_info), null, tint = MaterialTheme.colors.primary)
+ }
+ }
+ }
+ LaunchedEffect(Unit) {
+ snapshotFlow { portUnsaved.value.text }
+ .distinctUntilChanged()
+ .collect {
+ if (validPort(it) && it.toInt() > 1023) {
+ customPort.value = it.toInt()
+ } else {
+ customPort.value = null
}
+ }
+ }
+ KeyChangeEffect(customPort.value) {
+ if (customPort.value != null) {
+ portUnsaved.value = portUnsaved.value.copy(text = customPort.value.toString())
}
}
}
}
-private val State.rh: RemoteHostInfo? get() = value?.remoteHost_
-private val State.remoteHostId: Long? get() = value?.remoteHost_?.remoteHostId
-private val State.invitation: String? get() = value?.invitation
-private val State.address: RemoteCtrlAddress? get() = value?.localAddrs?.firstOrNull()
-private val State.addresses: List get() =
- (if (controller.appPrefs.developerTools.get()) value?.localAddrs else value?.localAddrs?.filterNot { it.address == "127.0.0.1" }) ?: emptyList()
-private val State.port: Int? get() = value?.ctrlPort?.toIntOrNull()
+private fun showOpenPortAlert() {
+ AlertManager.shared.showAlertMsg(
+ title = generalGetString(MR.strings.open_port_in_firewall_title),
+ text = generalGetString(MR.strings.open_port_in_firewall_desc),
+ )
+}
+
+private val CR.RemoteHostStarted?.rh: RemoteHostInfo? get() = this?.remoteHost_
+private val CR.RemoteHostStarted?.remoteHostId: Long? get() = this?.remoteHost_?.remoteHostId
+private val CR.RemoteHostStarted?.address: RemoteCtrlAddress? get() = this?.localAddrs?.firstOrNull()
+private val CR.RemoteHostStarted?.addresses: List get() =
+ (if (controller.appPrefs.developerTools.get() || this?.localAddrs?.indexOfFirst { it.address == "127.0.0.1" } == 0) this?.localAddrs else this?.localAddrs?.filterNot { it.address == "127.0.0.1" }) ?: emptyList()
+private val CR.RemoteHostStarted?.port: Int? get() = this?.ctrlPort?.toIntOrNull()
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml
index bfd61aa9e5..840e29fc08 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml
@@ -1665,7 +1665,7 @@
Disconnect desktop?
Only one device can work at the same time
Use from desktop in mobile app and scan QR code.]]>
- %s]]>
+ Waiting for mobile to connect:
Bad desktop address
Incompatible version
Desktop app version %s is not compatible with this app.
@@ -1693,6 +1693,9 @@
Not compatible!
Refresh
No connected mobile
+ Random
+ Open port in firewall
+ To allow a mobile app to connect to the desktop, open this port in your firewall, if you have it enabled
Coming soon!
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml
index 13b5cb2394..1f8f75c126 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml
@@ -1581,7 +1581,7 @@
Schnellerer Gruppenbeitritt und zuverlässigere Nachrichtenzustellung.
Verknüpfte Mobiltelefone
Dieser Gerätename
- %s warten]]>
+ Auf die Mobiltelefonverbindung warten:
Laden der Datei
Zu einem Mobiltelefon verbinden
Vom Desktop aus nutzen
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml
index a9d315f680..efe9b1003f 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml
@@ -1486,7 +1486,7 @@
Bureau
Connecté au bureau
Ce nom d\'appareil
- %s]]>
+ En attente d\'une connexion mobile:
Chargement du fichier
Connexion au bureau
Appareils de bureau
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml
index 0883adfdbb..9ed305da76 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml
@@ -1517,6 +1517,6 @@
- avvisa facoltativamente i contatti eliminati.
\n- nomi del profilo con spazi.
\n- e molto altro!
- %s]]>
+ In attesa che il cellulare si connette:
autore
\ No newline at end of file
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml
index 0a258fdcb1..6cef9571da 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml
@@ -1516,7 +1516,7 @@
\n- en meer!
%s is verbroken]]>
auteur
- %s]]>
+ Wachten tot mobiel verbinding maakt:
Automatisch verbinden
Wachten op desktop…
Desktop gevonden
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml
index a9549080a8..8f48724788 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml
@@ -1496,7 +1496,7 @@
Szybsze dołączenie i bardziej niezawodne wiadomości.
Połączone telefony
Nazwa tego urządzenia
- %s]]>
+ Oczekiwanie na połączenie telefonu:
Ładowanie pliku
Znaleziono komputer
Urządzenia komputerowe
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml
index dc5771316c..f26f77c33b 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml
@@ -1601,7 +1601,7 @@
Проверить соединение
Соединяться автоматически
Ожидается подключение…
- %s]]>
+ Ожидается подключение мобильного:
Компьютер найден
Несовместимая версия!
автор
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml
index 2f2c27e03d..ed2b9986c0 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml
@@ -1517,7 +1517,7 @@
- 可选择通知已删除的联系人。
\n- 带空格的个人资料名称。
\n- 以及更多!
- %s 进行连接]]>
+ 正等待移动设备 进行连接:
作者
自动连接
等待桌面中…