Compare commits

...

23 Commits

Author SHA1 Message Date
Xavier Del Campo Romero 353329bbea
BUILD.md: Add dependencies for Alpine Linux 2023-10-09 16:38:03 +02:00
Xavier Del Campo Romero 1159a169b0
Inhibit suspend on file upload/download 2023-10-09 16:38:03 +02:00
Xavier Del Campo Romero 9214e31b98
WIP call notifications 2023-10-09 16:38:03 +02:00
Xavier Del Campo Romero bfff1a2e51
Remove USE_SOUP3
CMake had already replaced it with SOUP_VERSION, so it made no sense for
configure to insist on USE_SOUP3.
2023-10-09 16:38:03 +02:00
Xavier Del Campo Romero 6717986e7b
SoupVersion.cmake: Force version required by Nice
Otherwise, Dino would crash due to conflicting Soup 2 and 3 symbols.
2023-10-09 16:38:02 +02:00
Xavier Del Campo Romero 8cd96e0a14
connection_manager.vala: Use yield on disconnect() 2023-10-09 16:38:02 +02:00
Xavier Del Campo Romero 2c292f16c8
file_sender.vala: Split very long line 2023-10-09 16:38:02 +02:00
Xavier Del Campo Romero e1fd11775c
Check XmppStream against null
Most of the calls to stream_interactor.get_stream(account) were already
doing null checks, but there were still some missing.
2023-10-09 16:38:02 +02:00
Xavier Del Campo Romero 2c445501a4
file_manager.vala: Disable automatic download 2023-10-09 16:38:02 +02:00
Xavier Del Campo Romero 6d3aef2718
Show file upload/download progress
Fixes upstream issue #1350.

Notes:

Image uploads were incorrectly handled by Dino, as they were always
reported as completed even if they were not, maybe so as to show the
image preview from the start. Now, Dino shows the upload progress for
all file types, and the image is only shown when completed.
2023-10-09 16:38:02 +02:00
Xavier Del Campo Romero f3c50f0739
http-files: Avoid null dereferences 2023-10-09 14:09:00 +02:00
Xavier Del Campo Romero aa39852931
connection_manager.vala: Refactor reconnect stream 2023-10-09 14:09:00 +02:00
Xavier Del Campo Romero 0f3d7e27ce
history_sync.vala: Remove noisy debug 2023-10-09 14:09:00 +02:00
Xavier Del Campo Romero 4fa765affc
Add screenshots 2023-10-09 14:08:59 +02:00
Xavier Del Campo Romero f0c83c9c42
README.md: Update features 2023-10-09 14:08:59 +02:00
Xavier Del Campo Romero 7fbdd24b50
Allow to copy messages 2023-10-09 14:08:59 +02:00
Xavier Del Campo Romero 964de8706d
Add BACKPORT 2023-10-09 14:08:59 +02:00
Xavier Del Campo Romero 52a34986af
Show warning dialog on failed GTK video sink creation 2023-10-09 14:08:59 +02:00
Xavier Del Campo Romero 677ac164a9
Make Ctrl-Q accelerator user-configurable
Some devices, such as those using virtual keyboards such as Squeekboard,
might find the Ctrl-Q accelerator annoying, as it can be easily
mistyped.
2023-10-09 14:08:59 +02:00
Xavier Del Campo Romero 2f1f04b3e8
Make send button settings-selectable 2023-10-09 14:08:59 +02:00
Xavier Del Campo Romero be7e0c2d56
Backport Meson build support
Meson builds have better integration with Vala. For example, Meson
handles incremental compilation of Vala source files better than
CMake.

Limitations:

As done with CMake builds, gresource.xml should be compiled. Now, it has
been generated from a CMake build and manually copied into the source
tree.
2023-10-09 14:08:59 +02:00
Xavier Del Campo Romero 1cc99a640f
Limit max_content_height 2023-10-09 14:08:58 +02:00
Xavier Del Campo Romero fefcf35352
Allow to resume failed transfers 2023-10-09 14:08:52 +02:00
54 changed files with 1284 additions and 117 deletions

343
BACKPORT Normal file
View File

@ -0,0 +1,343 @@
This fork of Dino diverged from upstream on this commit:
f25bfb00969a7e09996da2d5500e6718f4cc0148
However, this fork of Dino aims to backport all compatible security
fixes and improvements from upstream. This is an exhaustive list of
every commit that was reviewed and its status, plus additional
comments:
7e7dcedaf31ee35499875491c9f569c575d28435: Not applied.
As its commit message suggests, this commit ported Dino from GTK3 to
GTK4, but this fork will keep using GTK3 as a requirement.
2b3ce5fc95c63ed7d54e207db0585c8b8bbcd603: Not applied.
GTK4-related changes are out of scope for this fork.
f44cbe02c17df1f02ad49c63cd784fec0ea02d85: Not applied.
GTK4-related changes are out of scope for this fork.
e51b55432fe98e0fbc036fe785ef50fbf1589034: Not applied.
GTK4-related changes are out of scope for this fork.
0af92393f134c59646deaab7d963a32c0d3d58b2: Not applied.
GTK4-related changes are out of scope for this fork.
517363dfc9dab81c4b59310c739bcf71266fabe0: Not applied.
GTK4-related changes are out of scope for this fork.
6bfa70fc7045669be435d3757456e1da7d341927: Not applied.
GTK4-related changes are out of scope for this fork.
5103a7fb7b32fcee6656dbae0eec54fb26f89d80: Not applied.
The resource tree for Dino got changed during the GTK4 port, including
some icons. However, this commit is not needed for this fork.
7b252d040a845b7e9c515f61927fbf5bf7ed4c3a: Partially applied.
However, it had to be slightly adapted to the dependencies as listed
by our version of plugins/rtp/CMakeLists.txt.
d6afa6e8ff4cfb533140d9434b83f18f627f11ca: Not applied.
GTK4-related changes are out of scope for this fork.
054d3fec1627a9ee8d84d636d578aa68d3494d19: Not applied.
None of these changes seem required or seem to fix anything in our
fork.
e85477bb19166e742e34852b2a99b33e55565ded: Not applied.
I am unsure whether this commit would break anything, and so far I had
no problems on 0.56.0, which is older than 0.56.1.
Also, these changes were introduced by the change from GTK3 to GTK4.
Some further reference: https://github.com/dino/dino/pull/1234.
0aa73c4569a90031d8a88173f8689c18d02ddbe2: Not applied.
Because of the same reasons as e85477bb19166e742e34852b2a99b33e55565ded.
14bc3d6717515e0b34b02ef5b5ad4c3ec52ccdc2: Not applied.
This is regression introduced by
f44cbe02c17df1f02ad49c63cd784fec0ea02d85, which was part of the effort
to port Dino to GTK4.
117f19381233207e4b5aef03c82e7dd4b2d1debd: Not applied.
Again, these look like regressions introduced with the migration to
GTK4.
b8e84c83268a11ae41ad1d673999362427fd755c: Applied.
7ad52d9335579d03613036a7da9967fcf0c5d6b3: Not applied.
This commit looks incompatible with GTK3, but I might be wrong.
146af3152475f12c9b19a92c4779a53f6fc517ce: Not applied.
Some resources were moved when porting Dino to GTK4, but this does not
apply to our fork.
21ab48e09aa6b0ade8f25bdc93f89f8d3aa462e7: Not applied.
It looks like this is trying to solve a regression introduced during
the migration from GTK3 to GTK4, as the calls to
jid_entry.key_release_event.connect() and
nick_entry.key_release_event.connect() are not commented out on this
fork.
85342ee2eb2aa3e6d7599c503d17c00d861bafcb: Not applied.
Drag and drop uploading always forked for this fork.
03878eee495cac8dcc8baf0ff4f84e4c9e76114e: Not applied.
These were disabled during the migration from GTK3 to GTK4, so this
commit does not apply to this fork.
7d8b08deca0aa4eb24def6b9af4ec180c0bc9a27: Partially applied.
Whereas the changes on list_row.ui caused many conflicts, the null
check on conversation_view_controller.vala is required.
9c736af765d8c62838440afbfd2ad7ee78b44951: Not applied.
check_if_done() was introduced by
21ab48e09aa6b0ade8f25bdc93f89f8d3aa462e7, which was not applied,
either.
6c6e7e3aa7935ec513b7e5ea9b53a92b741ecf92: Partially applied.
Almost everything could be cleanly backported, except from
main/src/ui/add_conversation/conference_list.vala, where the original
implementation was preferred.
80258a874ddfeb87b4b71f5791eab94a2465de6d: Not applied.
I have no personal interest in XMPP reactions for this fork, as other
popular XMPP clients such as Conversations are still rely on styling as
described on https://xmpp.org/extensions/xep-0393.html#quote.
bc5a1d35cbf5c1aca406fa0fe81ca60d6b280bd5: Applied.
7e0d1db1965555720db2bef7380e61c23ef6dbcd: Applied.
f82f788f43e385391db2827cde151830fc91bc14: Applied.
1bf57a42fa5c36977132d21f59ca6637fcd0c3d3: Applied.
11b6e615b73e4183a06f9d456634c44ead612336: Not applied.
GTK4-related changes are out of scope for this fork.
09829b33824ab7d1fbf9886b7ed3e42cd8c34ff2: Not applied.
This commit fixes a regression introduced by
80258a874ddfeb87b4b71f5791eab94a2465de6d, which is related to
reactions, something this fork does not implement.
a45280f8dfe45f8908b44cd13996316af44117e9: Not applied.
This commit is related to reactions, which this fork does not
implement.
2ab7374aa53f70b30765a02865d92e6d71c6e623: Not applied.
This commit is related to reactions, which this fork does not
implement.
a2f2224781a82121c86a1f19b309245bc8369a91: Applied.
809c1579e41000f2f43eeb05735afc8165a1a430: Applied.
The commit message was a bit misleading: the real intention behind it
was to use the same implementation for both libsoup2/3.
6e37f3fe3fa0f4ce9a25a91e9d97191c8e4abec1: Applied.
e62955d3cf266a7f7ff0f2085a64f1c99021127c: Not applied.
Async uploads and downloads seem to work well. I do not see the need
for this patch.
7a19a25156a73e7e6b6d77fabc7621e7d2c443f0: Partially applied.
The changes to libdino/src/service/reactions.vala are out of scope for
this fork.
a2e894dda132f1679ee8e9998879be6bda7ab320: Applied.
cdd22e404eca3db640b6f2f2789314f7cbb65de6: Not applied.
GTK4-related changes are out of scope for this fork.
d1fb22ebedca7dbbbd0f693baa3c38d99ab5c344: Not applied.
This commit is related to reactions, which this fork does not
implement.
4d50c51a75de70c0f30a196e1f128154ba1651fa: Applied.
30f99d1347f8f5e2db364a25910d76b0faf2ea36: Applied.
799d09a4c98d1a00790f261600d3f4d813140954: Applied.
4d7809bb12199a598b531ca3ca019a4bb5a867f7: Partially applied.
- Too many changes are introduced to
main/src/ui/chat_input/encryption_button.vala, which might break this
fork.
- Changes related to own_occupant_ids are
related to reactions (as introduced by
80258a874ddfeb87b4b71f5791eab94a2465de6d), and thus are out of scope
for this fork.
- This commit removes plugins/omemo/src/ui/manage_key_dialog.vala, which
this fork still relies on.
- headerbar is still used by plugins/omemo/src/ui/manage_key_dialog.vala,
and thus should not be removed.
- Too many changes are introduced to
main/src/ui/conversation_content_view/conversation_item_skeleton.vala,
which might break this fork.
- The changes in
main/src/ui/conversation_content_view/conversation_view.vala break
the build.
- The changes in
main/src/ui/conversation_view_controller.vala break the build.
dc52e7595cca06d0a2da7d11b3c88cb2f7ce529c: Not applied.
I am not interested in XEP-0461 (replies) for this fork, as the
fallback implementations is already provided here.
60371331e0882758b0b9c2efedb3821e716defd7: Not applied.
Replies are out of scope for this fork.
424a4290622246303a7b73410d7e4a5a6d57dd6b: Not applied.
Replies are out of scope for this fork.
0c4aea96ffbc05d6efeb9a83424b872ce7f30d88: Not applied.
Replies are out of scope for this fork.
cb3b19b01deb8460627578b885339e7528411f6f: Not applied.
Replies are out of scope for this fork.
75500dc767f2cf657c0fbb5d2a4d4557183ed2e9: Not applied.
This commit breaks the build in some places, and I am not particulary
interested in conversation pinning anyway.
860c72bfc93d252d45eb97e71cf9ff22985c7ef9: Not applied.
This commit fixes regressions introduced by other commits that were not
introduced by this fork, such as
7e7dcedaf31ee35499875491c9f569c575d28435.
73c0263f35a73b68d20d299ee7fe8c37b9a6ffeb: Not applied.
This commit depends on other commits that were not introduced by this
fork, such as cb3b19b01deb8460627578b885339e7528411f6f, which was
related to replies (not implemented by this fork).
05289e0b4dc9bc076955e27b30b386cb7f0604c7: Not applied.
Replies are out of scope for this fork.
7da79864b384c9370a5937d480230e771834d91a: Not applied.
Conversation pinning is out of scope for this fork.
7e0d1db1965555720db2bef7380e61c23ef6dbcd: Applied.
f6e73d85c00a60a719da95a048ba2c15712325c3: Not applied.
GTK4-related changes are out of scope for this fork.
1ef42b47d22d21600ccf1e2d8b4d80605448660d: Not applied.
GTK4-related changes are out of scope for this fork.
2741bf21ae6d53324a512dacef65d540be840fe4: Not applied.
GTK4-related changes are out of scope for this fork.
ba9462503c0561dbe8306e3bf6aa49392bfc8078: Not applied.
GTK4-related changes are out of scope for this fork.
04acab82c98f5d9cdb798ba3baac8d73b097b1df: Not applied.
GTK4-related changes are out of scope for this fork.
e934a76a1139938ae668836b812102cd5d9c9d9f: Not applied.
GTK4-related changes are out of scope for this fork.
Also, this fork already has a back button for ConversationTitleBar.
0d7c8bb6e117f8cdd631730302413aad21632c2d: Not applied.
GTK4-related changes are out of scope for this fork.
92aca5672db723121471e513e83b68742761d1b5: Not applied.
GTK4-related changes are out of scope for this fork.
4b391f3f31c2272be11a24c8301641b045260e99: Not applied.
GTK4-related changes are out of scope for this fork.
ef98adb18a016dba65162602eb336fb82c64805e: Not applied.
GTK4-related changes are out of scope for this fork.
6a182ba313026b93d54a9d2246a0ab68894c6833: Not applied.
GTK4-related changes are out of scope for this fork.
99d9cb383abb1a33f6d0572deb4292dbf358f3ce: Not applied.
GTK4-related changes are out of scope for this fork.
cc7db3b85f7b29bfac333937d8bf09a81d8dc4a5: Not applied.
GTK4-related changes are out of scope for this fork.
e35df88d4a00c3a34f2b4d9fb7f10bb5d877bd29: Not applied.
GTK4-related changes are out of scope for this fork.
26be9d4bb40b223cb8a657b03e6457988a8cc269: Not applied.
Reactions are out of scope for this fork.
5d9978b38bb0e729dcccecddd08cc59e5585a6cc: Not applied.
Reactions are out of scope for this fork.
c813a6d2405980c71450cefa10abdf11fab8e995: Not applied.
GTK4-related changes are out of scope for this fork.
e833a924b5a66048506b4e0b0885ce2e35cac6fc: Applied.
e833a924b5a66048506b4e0b0885ce2e35cac6fc: Applied.
04eb0e763b0e02ea1bc698f1ccb24f84b0154010: Not applied.
This commit is way too big to merge and is tighly coupled to upstream
source code. Maybe a similar commit can be provided independently.
d0a00e1e7549609f7d83e7b432f7f547a1fcebb0: Applied.
1dbacbbcab139d0f8036446441ad143ef7e7eb30: Applied.
10a2bce5122dcd1e6fef037633a26568bf27d4d1: Applied.
b0b81b88c6948dcfd2b1b82a9fe7357316a3af1f: Not applied.
Both reactions and replies are out of scope for this fork.
e3c833bce0713e9a0290841306c7727dfc1e3860: Applied.
This fork defines different logic for update_received_mark(), so the
commit was adapted to it.
1e23b7bbd2a66e5ff40ae5fc5aa6523fa604f242: Not applied.
Replies are out of scope for this fork.
9e11bef219880b5bdc5d299c31ec6249596a86ba: Not applied.
string_if_tooltips_active() was added by
6bfa70fc7045669be435d3757456e1da7d341927, which is GTK4-related.
921f28c84bbda56ec93df7dcde7c828eaabb0b58: Not applied.
Reactions are out of scope for this fork.
3aa3912dc3ea740a5b93f8b694ead45e1b655238: Not applied.
main/src/ui/widgets/date_separator.vala depends on GTK4 libadwaita.
e0ece2aa62aa6b8350be83c787ea003a75f07437: Partially applied.
The changes to main/data/unified_main_content.ui and
main/src/ui/conversation_selector/conversation_selector.vala depend on
other commits related to GTK4 and libadwaita.
95fefaff51e5506d3f0e5fe8bced14aeb3fbe037: Applied.
18321ed15ce782ff5d1f24de9f2fb459d714d125: Partially applied.
The changes to libdino/src/service/reactions.vala and
xmpp-vala/src/module/xep/0444_reactions.vala are not relevant to this
fork.
d76e12b215eb62e4eda5a0f92fbf5c1bd7c1848e: Applied.
f74c1f18b12df0d650f74b6fa43b7f2f0a9bce79: Applied.
1d123c7e66d963fd8cc8cc4250b5813a62676f56: Applied.
d092473fe401a5a668e57f054efbd1e84ac6ca59: Applied.
1559a7a60370c2aa0203e5c4222def4ae3258006: Applied.
32ae87a3c4cebaa05e0e702d744900cd414000db: Not applied.
Seems to cause many conflicts while providing little benefit.
116682e311edca6665a0497c8b225b4fe69859a7: Partially applied.
Changes to main/src/ui/conversation_content_view/quote_widget.vala are
not relevant to this fork.
9c5e36020d8997452d4fd07c5a153e1e7fc24088: Applied.
e73b556a1ae5ea4af41c610f7b05545cf60d59c4: Not applied.
This commit is way too big to merge and is tighly coupled to upstream
source code. Maybe a similar commit can be provided independently.
5568bbc6bf505c4f8ea93fc460dbeff6f4d36e15: Partially applied.
Changes related to libadwaita are out of scope for this fork.

View File

@ -1,10 +1,21 @@
# An unbranded Dino fork
## Screenshots
![Screenshot showing mobile form factor and OMEMO-by-default support](doc/screenshot-2.png)
![Screenshot showing a MUC room in desktip form factor](doc/screenshot-1.png)
## Features
- **OMEMO encryption set by default**.
- Portrait screen support with `libhandy`.
- Improved [`connection_manager.vala`](libdino/src/service/connection_manager.vala).
- Quote messages according to XEP-0393 Message Styling.
- Copy messages.
- Optional send button and Enter-to-send accelerator.
- Keeps using GTK3.
- Includes some features that did not make into upstream.
- Backports bugfixes and improvements from upstream.
- Backports bugfixes and improvements from upstream (see [`BACKPORT`](BACKPORT)).
## TODO

View File

@ -1,5 +1,5 @@
find_package(Nice QUIET)
if (Nice_FOUND AND NOT SOUP_VERSION AND NOT USE_SOUP3)
if (Nice_FOUND)
file(GET_RUNTIME_DEPENDENCIES
RESOLVED_DEPENDENCIES_VAR Nice_DEPENDENCIES
UNRESOLVED_DEPENDENCIES_VAR Nice_UNRESOLVED_DEPENDENCIES
@ -9,11 +9,21 @@ if (Nice_FOUND AND NOT SOUP_VERSION AND NOT USE_SOUP3)
)
foreach (lib ${Nice_DEPENDENCIES})
if (lib MATCHES ".*/libsoup-3.*")
if(SOUP_VERSION AND NOT SOUP_VERSION EQUAL 3)
message(FATAL_ERROR "libnice-${Nice_VERSION} depends on "
"libsoup-3, but SOUP_VERSION=${SOUP_VERSION} was given.")
endif()
set(SOUP_VERSION 3)
endif ()
endforeach ()
foreach (lib ${Nice_DEPENDENCIES})
if (lib MATCHES ".*/libsoup-2.*")
if(SOUP_VERSION AND NOT SOUP_VERSION EQUAL 2)
message(FATAL_ERROR "libnice-${Nice_VERSION} depends on "
"libsoup-2, but SOUP_VERSION=${SOUP_VERSION} was given.")
endif()
set(SOUP_VERSION 2)
endif ()
endforeach ()
@ -24,7 +34,7 @@ elseif (NOT SOUP_VERSION)
find_package(Soup2 QUIET)
find_package(Soup3 QUIET)
# Only use libsoup 3 if specifically requested or when libsoup 2 is not available
if (Soup3_FOUND AND NOT Soup2_FOUND OR USE_SOUP3)
if (Soup3_FOUND AND NOT Soup2_FOUND)
set(SOUP_VERSION 3)
else ()
set(SOUP_VERSION 2)

6
configure vendored
View File

@ -22,7 +22,7 @@ DISABLE_FAST_VAPI=
LIB_SUFFIX=
NO_DEBUG=
FETCH_ONLY=
USE_SOUP3=
SOUP_VERSION=2
EXEC_PREFIX=
BINDIR=
@ -113,7 +113,7 @@ while true; do
--valac-flags ) VALAC_FLAGS="$2"; shift; shift ;;
--lib-suffix ) LIB_SUFFIX="$2"; shift; shift ;;
--with-libsignal-in-tree ) BUILD_LIBSIGNAL_IN_TREE=yes; shift ;;
--with-libsoup3 ) USE_SOUP3=yes; shift ;;
--with-libsoup3 ) SOUP_VERSION=3; shift ;;
--disable-fast-vapi ) DISABLE_FAST_VAPI=yes; shift ;;
--no-debug ) NO_DEBUG=yes; shift ;;
--fetch-only ) FETCH_ONLY=yes; shift ;;
@ -259,7 +259,7 @@ cmake -G "$cmake_type" \
-DDISABLED_PLUGINS="$DISABLED_PLUGINS" \
-DBUILD_TESTS="$BUILD_TESTS" \
-DBUILD_LIBSIGNAL_IN_TREE="$BUILD_LIBSIGNAL_IN_TREE" \
-DUSE_SOUP3="$USE_SOUP3" \
-DSOUP_VERSION="$SOUP_VERSION" \
-DVALA_EXECUTABLE="$VALAC" \
-DCMAKE_VALA_FLAGS="$VALACFLAGS" \
-DDISABLE_FAST_VAPI="$DISABLE_FAST_VAPI" \

View File

@ -20,6 +20,12 @@ sudo zypper install cmake gcc-c++ gpgme-devel libnotify-devel libgcrypt-devel pk
sudo pacman -S cmake vala ninja glib2 glib-networking gtk3 gpgme libgee>=0.10 libgcrypt libsoup sqlite qrencode gspell gstreamer gst-plugins-base gst-plugins-good gst-plugin-gtk webrtc-audio-processing libnice libsrtp libsignal-protocol-c
```
### Alpine Linux
```
sudo apk add git make cmake gcc g++ vala libgee-dev sqlite-dev gst-plugins-good gtk4.0-dev libadwaita-dev libgcrypt-dev libsrtp-dev libsoup-dev libnice-dev musl-utils libqrencode-dev libsignal-protocol-c-dev gpgme-dev gstreamer-dev gst-plugins-base-dev
```
# libsignal-protocol-c
If build complains about missing or incompatible libsignal-protocol-c and it is not provided by your distribution, you can fetch and build it in tree by using `./configure --with-libsignal-in-tree`.

BIN
doc/screenshot-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

BIN
doc/screenshot-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -6,8 +6,15 @@ find_packages(LIBDINO_PACKAGES REQUIRED
GObject
)
file(WRITE ${CMAKE_CURRENT_LIST_DIR}/version.vala
"namespace Dino {
public const string VERSION = \"v${PROJECT_VERSION}\";
}"
)
vala_precompile(LIBDINO_VALA_C
SOURCES
version.vala
src/application.vala
src/dbus/login1.vala
@ -90,7 +97,7 @@ DEPENDS
${CMAKE_BINARY_DIR}/exports/dino_i18n.h
)
add_definitions(${VALA_CFLAGS} -DDINO_SYSTEM_PLUGIN_DIR="${PLUGIN_INSTALL_DIR}" -DDINO_SYSTEM_LIBDIR_NAME="${LIBDIR_NAME}" -DG_LOG_DOMAIN="libdino" -DDINO_VERSION=\"${PROJECT_VERSION}\")
add_definitions(${VALA_CFLAGS} -DDINO_SYSTEM_PLUGIN_DIR="${PLUGIN_INSTALL_DIR}" -DDINO_SYSTEM_LIBDIR_NAME="${LIBDIR_NAME}" -DG_LOG_DOMAIN="libdino")
add_library(libdino SHARED ${LIBDINO_VALA_C} ${CMAKE_BINARY_DIR}/exports/dino_i18n.h)
add_dependencies(libdino dino-vapi)
target_link_libraries(libdino xmpp-vala qlite ${LIBDINO_PACKAGES} m)

82
libdino/meson.build Normal file
View File

@ -0,0 +1,82 @@
# version_vala
dot_git = meson.current_source_dir() / '../.git'
version_file = meson.current_source_dir() / '../VERSION'
command = [prog_python, files('version.py'), version_file, '@OUTPUT@', '--git-repo', meson.current_source_dir()]
if prog_git.found()
command += ['--git', prog_git]
endif
depend_files = []
if fs.exists(dot_git)
depend_files += [dot_git]
endif
if fs.exists(version_file)
depend_files += [version_file]
endif
version_vala = custom_target('libdino_version_vala', command: command, output: 'version.vala', depend_files: depend_files)
# libdino
dependencies = [
dep_gdk_pixbuf,
dep_gee,
dep_gio,
dep_glib,
dep_gmodule,
dep_qlite,
dep_xmpp_vala
]
sources = files(
'src/application.vala',
'src/dbus/login1.vala',
'src/dbus/notifications.vala',
'src/dbus/upower.vala',
'src/entity/account.vala',
'src/entity/call.vala',
'src/entity/conversation.vala',
'src/entity/encryption.vala',
'src/entity/file_transfer.vala',
'src/entity/message.vala',
'src/entity/settings.vala',
'src/plugin/interfaces.vala',
'src/plugin/loader.vala',
'src/plugin/registry.vala',
'src/service/avatar_manager.vala',
'src/service/blocking_manager.vala',
'src/service/call_store.vala',
'src/service/call_state.vala',
'src/service/call_peer_state.vala',
'src/service/calls.vala',
'src/service/chat_interaction.vala',
'src/service/connection_manager.vala',
'src/service/content_item_store.vala',
'src/service/conversation_manager.vala',
'src/service/counterpart_interaction_manager.vala',
'src/service/database.vala',
'src/service/entity_capabilities_storage.vala',
'src/service/entity_info.vala',
'src/service/file_manager.vala',
'src/service/file_transfer_storage.vala',
'src/service/jingle_file_transfers.vala',
'src/service/message_correction.vala',
'src/service/message_processor.vala',
'src/service/message_storage.vala',
'src/service/module_manager.vala',
'src/service/muc_manager.vala',
'src/service/notification_events.vala',
'src/service/presence_manager.vala',
'src/service/registration.vala',
'src/service/roster_manager.vala',
'src/service/search_processor.vala',
'src/service/stream_interactor.vala',
'src/service/util.vala',
'src/util/display_name.vala',
'src/util/util.vala',
'src/util/weak_map.vala',
)
sources += [version_vala]
c_args = [
'-DDINO_SYSTEM_LIBDIR_NAME="@0@"'.format(get_option('prefix') / get_option('libdir')),
'-DDINO_SYSTEM_PLUGIN_DIR="@0@"'.format(get_option('prefix') / get_option('plugindir')),
'-DG_LOG_DOMAIN="libdino"',
]
lib_dino = library('dino', sources, c_args: c_args, include_directories: include_directories('src'), dependencies: dependencies)
dep_dino = declare_dependency(link_with: lib_dino, include_directories: include_directories('.', 'src'))

View File

@ -2,7 +2,6 @@ using Dino.Entities;
namespace Dino {
extern const string VERSION;
public string get_version() { return VERSION; }
public string get_short_version() {
if (!VERSION.contains("~")) return VERSION;
@ -16,6 +15,8 @@ public interface Application : GLib.Application {
public abstract StreamInteractor stream_interactor { get; set; }
public abstract Plugins.Registry plugin_registry { get; set; }
public abstract SearchPathGenerator? search_path_generator { get; set; }
public abstract uint inhibit_app(string reason);
public abstract void uninhibit_app(uint cookie);
internal static string print_xmpp;

View File

@ -70,6 +70,7 @@ public class FileTransfer : Object {
public State state { get; set; default=State.NOT_STARTED; }
public int provider { get; set; }
public string info { get; set; }
public uint64 transferred_bytes {get; set; }
public Cancellable cancellable { get; default=new Cancellable(); }
private Database? db;

View File

@ -13,6 +13,9 @@ public class Settings : Object {
convert_utf8_smileys_ = col_to_bool_or_default("convert_utf8_smileys", true);
check_spelling = col_to_bool_or_default("check_spelling", true);
default_encryption = col_to_encryption_or_default("default_encryption", Encryption.OMEMO);
send_button = col_to_bool_or_default("send_button", false);
enter_newline = col_to_bool_or_default("enter_newline", false);
quit_ctrl_q = col_to_bool_or_default("quit_ctrl_q", true);
}
private bool col_to_bool_or_default(string key, bool def) {
@ -98,6 +101,46 @@ public class Settings : Object {
default_encryption_ = value;
}
}
public signal void send_button_update(bool visible);
private bool send_button_;
public bool send_button {
get { return send_button_; }
set {
db.settings.upsert()
.value(db.settings.key, "send_button", true)
.value(db.settings.value, value.to_string())
.perform();
send_button_ = value;
send_button_update(value);
}
}
private bool enter_newline_;
public bool enter_newline {
get { return enter_newline_; }
set {
db.settings.upsert()
.value(db.settings.key, "enter_newline", true)
.value(db.settings.value, value.to_string())
.perform();
enter_newline_ = value;
}
}
public signal void quit_ctrl_q_update(bool active);
private bool quit_ctrl_q_;
public bool quit_ctrl_q {
get { return quit_ctrl_q_; }
set {
db.settings.upsert()
.value(db.settings.key, "quit_ctrl_q", true)
.value(db.settings.value, value.to_string())
.perform();
quit_ctrl_q_ = value;
quit_ctrl_q_update(value);
}
}
}
}

View File

@ -3,14 +3,14 @@ using Gee;
namespace Dino.Plugins {
public class Registry {
internal ArrayList<EncryptionListEntry> encryption_list_entries = new ArrayList<EncryptionListEntry>();
internal HashMap<string, CallEncryptionEntry> call_encryption_entries = new HashMap<string, CallEncryptionEntry>();
internal ArrayList<AccountSettingsEntry> account_settings_entries = new ArrayList<AccountSettingsEntry>();
internal ArrayList<ContactDetailsProvider> contact_details_entries = new ArrayList<ContactDetailsProvider>();
internal Map<string, TextCommand> text_commands = new HashMap<string, TextCommand>();
internal Gee.List<ConversationAdditionPopulator> conversation_addition_populators = new ArrayList<ConversationAdditionPopulator>();
internal Gee.List<NotificationPopulator> notification_populators = new ArrayList<NotificationPopulator>();
internal Gee.Collection<ConversationTitlebarEntry> conversation_titlebar_entries = new Gee.TreeSet<ConversationTitlebarEntry>((a, b) => {
public ArrayList<EncryptionListEntry> encryption_list_entries = new ArrayList<EncryptionListEntry>();
public HashMap<string, CallEncryptionEntry> call_encryption_entries = new HashMap<string, CallEncryptionEntry>();
public ArrayList<AccountSettingsEntry> account_settings_entries = new ArrayList<AccountSettingsEntry>();
public ArrayList<ContactDetailsProvider> contact_details_entries = new ArrayList<ContactDetailsProvider>();
public Map<string, TextCommand> text_commands = new HashMap<string, TextCommand>();
public Gee.List<ConversationAdditionPopulator> conversation_addition_populators = new ArrayList<ConversationAdditionPopulator>();
public Gee.List<NotificationPopulator> notification_populators = new ArrayList<NotificationPopulator>();
public Gee.Collection<ConversationTitlebarEntry> conversation_titlebar_entries = new Gee.TreeSet<ConversationTitlebarEntry>((a, b) => {
return (int)(a.order - b.order);
});
public VideoCallPlugin? video_call_plugin;

View File

@ -144,7 +144,7 @@ public class AvatarManager : StreamInteractionModule, Object {
}
uint8[] buffer;
pixbuf.save_to_buffer(out buffer, "png");
XmppStream stream = stream_interactor.get_stream(account);
XmppStream? stream = stream_interactor.get_stream(account);
if (stream != null) {
Xmpp.Xep.UserAvatars.publish_png(stream, buffer, pixbuf.width, pixbuf.height);
}

View File

@ -21,22 +21,26 @@ public class BlockingManager : StreamInteractionModule, Object {
}
public bool is_blocked(Account account, Jid jid) {
XmppStream stream = stream_interactor.get_stream(account);
XmppStream? stream = stream_interactor.get_stream(account);
return stream != null && stream.get_module(Xmpp.Xep.BlockingCommand.Module.IDENTITY).is_blocked(stream, jid.to_string());
}
public void block(Account account, Jid jid) {
XmppStream stream = stream_interactor.get_stream(account);
stream.get_module(Xmpp.Xep.BlockingCommand.Module.IDENTITY).block(stream, { jid.to_string() });
XmppStream? stream = stream_interactor.get_stream(account);
if (stream != null) {
stream.get_module(Xmpp.Xep.BlockingCommand.Module.IDENTITY).block(stream, { jid.to_string() });
}
}
public void unblock(Account account, Jid jid) {
XmppStream stream = stream_interactor.get_stream(account);
stream.get_module(Xmpp.Xep.BlockingCommand.Module.IDENTITY).unblock(stream, { jid.to_string() });
XmppStream? stream = stream_interactor.get_stream(account);
if (stream != null) {
stream.get_module(Xmpp.Xep.BlockingCommand.Module.IDENTITY).unblock(stream, { jid.to_string() });
}
}
public bool is_supported(Account account) {
XmppStream stream = stream_interactor.get_stream(account);
XmppStream? stream = stream_interactor.get_stream(account);
return stream != null && stream.get_module(Xmpp.Xep.BlockingCommand.Module.IDENTITY).is_supported(stream);
}
}

View File

@ -126,7 +126,7 @@ public class Dino.CallState : Object {
call.state = Call.State.DECLINED;
if (use_cim) {
XmppStream stream = stream_interactor.get_stream(call.account);
XmppStream? stream = stream_interactor.get_stream(call.account);
if (stream == null) return;
stream.get_module(Xep.CallInvites.Module.IDENTITY).send_reject(stream, cim_counterpart, cim_call_id, cim_message_type);
}
@ -143,7 +143,7 @@ public class Dino.CallState : Object {
peers_cpy.add_all(peers.values);
if (group_call != null) {
XmppStream stream = stream_interactor.get_stream(call.account);
XmppStream? stream = stream_interactor.get_stream(call.account);
if (stream != null) {
stream.get_module(Xep.Muc.Module.IDENTITY).exit(stream, group_call.muc_jid);
}

View File

@ -56,15 +56,15 @@ public class ConnectionManager : Object {
public bool acked;
public Connection() {
reset();
reset.begin();
}
public void reset() {
public async void reset() {
acked = false;
if (stream != null) {
stream.detach_modules();
stream.disconnect.begin();
yield stream.disconnect();
}
stream = null;
uuid = Xmpp.random_uuid();
@ -252,11 +252,32 @@ public class ConnectionManager : Object {
}
}
private void check_ping_reconnect(Xmpp.Xep.Ping.Module identity,
XmppStream stream, Account account) {
identity.send_ping.begin(stream, account.bare_jid.domain_jid, () => {
if (connections[account].stream != stream) return;
connections[account].acked = true;
change_connection_state(account, ConnectionState.CONNECTED);
});
Timeout.add_seconds(10, () => {
if (!connections.has_key(account)) return false;
if (connections[account].stream != stream) return false;
if (connections[account].acked) return false;
// Reconnect. Nothing gets through the stream.
debug("[%s %p] Ping timeouted. Reconnecting", account.bare_jid.to_string(), stream);
change_connection_state(account, ConnectionState.DISCONNECTED);
connections[account].reset();
connect_stream.begin(account);
return false;
});
}
private void check_reconnect(Account account, bool directly_reconnect = false) {
if (!connections.has_key(account)) return;
XmppStream? stream = connections[account].stream;
var cancellable = new Cancellable();
debug(@"account.domainpart=$(account.domainpart)");
@ -267,29 +288,23 @@ public class ConnectionManager : Object {
debug(@"can-reach: $(reachable)");
if (reachable) {
Xmpp.Xep.Ping.Module? identity = null;
if (connections[account].connection_state == ConnectionState.CONNECTING) return;
XmppStream? stream = connections[account].stream;
connections[account].acked = false;
change_connection_state(account, ConnectionState.CONNECTING);
stream.get_module(Xep.Ping.Module.IDENTITY).send_ping.begin(stream, account.bare_jid.domain_jid, () => {
connections[account].acked = true;
if (connections[account].stream != stream) return;
change_connection_state(account, ConnectionState.CONNECTED);
});
Timeout.add_seconds(10, () => {
if (!connections.has_key(account)) return false;
if (connections[account].stream != stream) return false;
if (connections[account].acked) return false;
// Reconnect. Nothing gets through the stream.
debug("[%s %p] Ping timeouted. Reconnecting", account.bare_jid.to_string(), stream);
if (stream != null
&& (identity = stream.get_module(Xep.Ping.Module.IDENTITY)) != null) {
change_connection_state(account, ConnectionState.CONNECTING);
check_ping_reconnect(identity, stream, account);
}
else {
change_connection_state(account, ConnectionState.DISCONNECTED);
connections[account].reset();
connect_stream.begin(account);
return false;
});
}
}
} catch (Error e) {

View File

@ -120,6 +120,7 @@ public class FileManager : StreamInteractionModule, Object {
}
yield file_sender.send_file(conversation, file_transfer, file_send_data, file_meta);
file_transfer.state = FileTransfer.State.COMPLETE;
} catch (Error e) {
warning("Send file error: %s", e.message);
@ -210,6 +211,8 @@ public class FileManager : StreamInteractionModule, Object {
private async void download_file_internal(FileProvider file_provider, FileTransfer file_transfer, Conversation conversation) {
try {
file_transfer.cancellable.reset();
// Get meta info
FileReceiveData receive_data = file_provider.get_file_receive_data(file_transfer);
FileDecryptor? file_decryptor = null;
@ -246,14 +249,20 @@ public class FileManager : StreamInteractionModule, Object {
File file = File.new_for_path(Path.build_filename(get_storage_dir(), filename));
OutputStream os = file.create(FileCreateFlags.REPLACE_DESTINATION);
file_transfer.cancellable.reset();
uint inhibit_cookie = Application.get_default()
.inhibit_app("Ongoing file download");
uint8[] buffer = new uint8[1024];
ssize_t read;
while ((read = yield input_stream.read_async(buffer, Priority.LOW, file_transfer.cancellable)) > 0) {
buffer.length = (int) read;
file_transfer.transferred_bytes += (uint64)read;
yield os.write_async(buffer, Priority.LOW, file_transfer.cancellable);
buffer.length = 1024;
}
yield input_stream.close_async(Priority.LOW, file_transfer.cancellable);
Application.get_default().uninhibit_app(inhibit_cookie);
yield os.close_async(Priority.LOW, file_transfer.cancellable);
file_transfer.path = file.get_basename();
file_transfer.input_stream = yield file.read_async();
@ -285,6 +294,7 @@ public class FileManager : StreamInteractionModule, Object {
file_transfer.file_name = file_meta.file_name;
file_transfer.size = (int)file_meta.size;
file_transfer.info = info;
file_transfer.transferred_bytes = 0;
var encryption = file_provider.get_encryption(file_transfer, receive_data, file_meta);
if (encryption != Encryption.NONE) file_transfer.encryption = encryption;
@ -304,11 +314,6 @@ public class FileManager : StreamInteractionModule, Object {
warning("Error downloading file: %s", e.message);
file_transfer.state = FileTransfer.State.FAILED;
}
if (file_transfer.size >= 0 && file_transfer.size < 5000000) {
download_file_internal.begin(file_provider, file_transfer, conversation, (_, res) => {
download_file_internal.end(res);
});
}
}
conversation.last_active = file_transfer.time;
@ -321,7 +326,7 @@ public class FileManager : StreamInteractionModule, Object {
File file = File.new_for_path(Path.build_filename(get_storage_dir(), filename));
OutputStream os = file.create(FileCreateFlags.REPLACE_DESTINATION);
yield os.splice_async(file_transfer.input_stream, OutputStreamSpliceFlags.CLOSE_SOURCE|OutputStreamSpliceFlags.CLOSE_TARGET);
file_transfer.state = FileTransfer.State.COMPLETE;
file_transfer.state = FileTransfer.State.IN_PROGRESS;
file_transfer.path = filename;
file_transfer.input_stream = yield file.read_async();
} catch (Error e) {

View File

@ -366,7 +366,10 @@ public class Dino.HistorySync {
* prev_page_result: null if this is the first page request
**/
private async PageRequestResult get_mam_page(Account account, Xmpp.MessageArchiveManagement.V2.MamQueryParams query_params, PageRequestResult? prev_page_result, Cancellable? cancellable = null) {
XmppStream stream = stream_interactor.get_stream(account);
XmppStream? stream = stream_interactor.get_stream(account);
if (stream == null) {
return new PageRequestResult.with_result(PageResult.Error);
}
Xmpp.MessageArchiveManagement.QueryResult query_result = null;
if (prev_page_result == null) {
query_result = yield Xmpp.MessageArchiveManagement.V2.query_archive(stream, query_params, cancellable);
@ -537,7 +540,6 @@ public class Dino.HistorySync {
if (to_delete.contains(range1)) continue;
foreach (MamRange range2 in ranges[server_jid]) {
debug("[%s | %s] | %s - %s vs %s - %s", account.bare_jid.to_string(), server_jid.to_string(), range1.from_time.to_string(), range1.to_time.to_string(), range2.from_time.to_string(), range2.to_time.to_string());
if (range1 == range2 || to_delete.contains(range2)) continue;
// Check if range2 is a subset of range1
@ -593,5 +595,9 @@ public class Dino.HistorySync {
this.query_result = query_result;
this.stanzas = stanzas;
}
public PageRequestResult.with_result(PageResult page_result) {
this.page_result = page_result;
}
}
}

View File

@ -220,7 +220,19 @@ public class JingleFileSender : FileSender, Object {
}
}
try {
yield stream.get_module(Xep.JingleFileTransfer.Module.IDENTITY).offer_file_stream(stream, full_jid, file_transfer.input_stream, file_transfer.server_file_name, file_meta.size, precondition_name, precondition_options);
var? module = stream.get_module(Xep.JingleFileTransfer.Module.IDENTITY);
if (module == null)
throw new FileSendError.UPLOAD_FAILED("unexpected null module");
module.transferred_bytes.connect((bytes) => {
file_transfer.transferred_bytes += bytes;
});
yield module.offer_file_stream(stream, full_jid,
file_transfer.cancellable, file_transfer.input_stream,
file_transfer.server_file_name, file_meta.size,
precondition_name, precondition_options);
} catch (Error e) {
throw new FileSendError.UPLOAD_FAILED(@"offer_file_stream failed: $(e.message)");
}

View File

@ -96,7 +96,8 @@ public class NotificationEvents : StreamInteractionModule, Object {
}
break;
case CallItem.TYPE:
// handled in `on_call_incoming`
// Also handled in `on_call_incoming`
notify_content_item(item, conversation);
break;
}
}

36
libdino/version.py Normal file
View File

@ -0,0 +1,36 @@
import argparse
import subprocess
VERSION_VALA = """\
namespace Dino {{
public const string VERSION = "{}";
}}
"""
def compute_version(file, git_repo, git):
try:
with open(file) as f:
return f.read().strip()
except FileNotFoundError:
pass
return subprocess.check_output([git, "describe", "--tags"], cwd=git_repo, text=True).strip()
def generate_version_vala(version):
if "\\" in version or "\"" in version:
raise ValueError(f"invalid version {version!r}")
return VERSION_VALA.format(version)
def main():
p = argparse.ArgumentParser(description="Compute the Dino version")
p.add_argument("--git-repo", help="Path to checked out git repository")
p.add_argument("--git", help="Path to git executable", default="git")
p.add_argument("version_file", metavar="VERSION_FILE", help="Use this file's contents as version if the file exists")
p.add_argument("output", metavar="OUTPUT", help="Vala file to output to")
args = p.parse_args()
out = generate_version_vala(compute_version(args.version_file, args.git_repo, args.git))
with open(args.output, "w") as f:
f.write(out)
if __name__ == "__main__":
main()

View File

@ -214,7 +214,7 @@ OPTIONS
${MAIN_EXTRA_OPTIONS}
)
add_definitions(${VALA_CFLAGS} -DGETTEXT_PACKAGE=\"${GETTEXT_PACKAGE}\" -DLOCALE_INSTALL_DIR=\"${LOCALE_INSTALL_DIR}\" -DDINO_VERSION=\"${PROJECT_VERSION}\")
add_definitions(${VALA_CFLAGS} -DGETTEXT_PACKAGE=\"${GETTEXT_PACKAGE}\" -DLOCALE_INSTALL_DIR=\"${LOCALE_INSTALL_DIR}\")
add_executable(dino ${MAIN_VALA_C} ${MAIN_GRESOURCES_TARGET} src/emojichooser.c)
add_dependencies(dino ${GETTEXT_PACKAGE}-translations)
target_include_directories(dino PRIVATE src)

View File

@ -17,7 +17,7 @@
<child>
<object class="GtkScrolledWindow">
<property name="propagate_natural_height">True</property>
<property name="max_content_height">500</property>
<property name="max_content_height">128</property>
<property name="hscrollbar_policy">never</property>
<property name="expand">True</property>
<property name="visible">True</property>

1
main/data/gresource.xml Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><gresources><gresource prefix="/im/dino/Dino"><file>icons/dino-account-plus-symbolic.svg</file><file>icons/dino-changes-allowed-symbolic.svg</file><file>icons/dino-changes-prevent-symbolic.svg</file><file>icons/dino-conversation-list-placeholder-arrow.svg</file><file>icons/dino-double-tick-symbolic.svg</file><file>icons/dino-emoticon-symbolic.svg</file><file>icons/dino-qr-code-symbolic.svg</file><file>icons/dino-security-high-symbolic.svg</file><file>icons/dino-microphone-off-symbolic.svg</file><file>icons/dino-microphone-symbolic.svg</file><file>icons/dino-party-popper-symbolic.svg</file><file>icons/dino-phone-hangup-symbolic.svg</file><file>icons/dino-phone-in-talk-symbolic.svg</file><file>icons/dino-phone-missed-symbolic.svg</file><file>icons/dino-phone-ring-symbolic.svg</file><file>icons/dino-phone-symbolic.svg</file><file>icons/dino-status-away.svg</file><file>icons/dino-status-chat.svg</file><file>icons/dino-status-dnd.svg</file><file>icons/dino-status-online.svg</file><file>icons/im.dino.Dino.svg</file><file>icons/im.dino.Dino-symbolic.svg</file><file>icons/dino-tick-symbolic.svg</file><file>icons/dino-video-off-symbolic.svg</file><file>icons/dino-video-symbolic.svg</file><file>icons/dino-device-desktop-symbolic.svg</file><file>icons/dino-device-phone-symbolic.svg</file><file>icons/dino-file-document-symbolic.svg</file><file>icons/dino-file-download-symbolic.svg</file><file>icons/dino-file-image-symbolic.svg</file><file>icons/dino-file-music-symbolic.svg</file><file>icons/dino-file-symbolic.svg</file><file>icons/dino-file-table-symbolic.svg</file><file>icons/dino-file-video-symbolic.svg</file><file>add_conversation/add_contact_dialog.ui</file><file>add_conversation/add_groupchat_dialog.ui</file><file>add_conversation/conference_details_fragment.ui</file><file>add_conversation/list_row.ui</file><file>add_conversation/select_jid_fragment.ui</file><file>call_widget.ui</file><file>chat_input.ui</file><file>contact_details_dialog.ui</file><file>conversation_list_titlebar.ui</file><file>conversation_list_titlebar_csd.ui</file><file>conversation_row.ui</file><file>conversation_view.ui</file><file>file_default_widget.ui</file><file>file_send_overlay.ui</file><file>emojichooser.ui</file><file>global_search.ui</file><file>conversation_content_view/item_metadata_header.ui</file><file>conversation_content_view/view.ui</file><file>manage_accounts/account_row.ui</file><file>manage_accounts/add_account_dialog.ui</file><file>manage_accounts/dialog.ui</file><file>menu_add.ui</file><file>menu_app.ui</file><file>menu_conversation.ui</file><file>menu_encryption.ui</file><file>message_item_widget_edit_mode.ui</file><file>occupant_list.ui</file><file>occupant_list_item.ui</file><file>search_autocomplete.ui</file><file>settings_dialog.ui</file><file>shortcuts.ui</file><file>unified_main_content.ui</file><file>unified_window_placeholder.ui</file><file>theme.css</file></gresource></gresources>

View File

@ -348,7 +348,7 @@
<property name="visible">True</property>
<child>
<object class="GtkScrolledWindow">
<property name="max_content_height">300</property>
<property name="max_content_height">128</property>
<property name="propagate_natural_height">True</property>
<property name="hscrollbar_policy">never</property>
<property name="visible">True</property>

View File

@ -20,7 +20,7 @@
</child>
<child>
<object class="GtkScrolledWindow">
<property name="max_content_height">500</property>
<property name="max_content_height">128</property>
<property name="propagate_natural_height">True</property>
<property name="visible">True</property>
<child>

View File

@ -148,6 +148,42 @@
<property name="height">1</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="send_button_checkbutton">
<property name="label" translatable="yes">Display send button</property>
<property name="visible">True</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">6</property>
<property name="width">1</property>
<property name="height">1</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="enter_newline_checkbutton">
<property name="label" translatable="yes">Use Enter key to start a new line</property>
<property name="visible">True</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">7</property>
<property name="width">1</property>
<property name="height">1</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="quit_ctrl_q_checkbutton">
<property name="label" translatable="yes">Quit Dino with Ctrl+Q</property>
<property name="visible">True</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">8</property>
<property name="width">1</property>
<property name="height">1</property>
</packing>
</child>
</object>
</child>
</object>

107
main/meson.build Normal file
View File

@ -0,0 +1,107 @@
dependencies = [
dep_dino,
dep_gee,
dep_glib,
dep_gmodule,
dep_gspell,
dep_gtk3,
dep_icu_uc,
dep_libhandy,
dep_m,
dep_qlite,
dep_xmpp_vala,
]
sources = files(
'src/emojichooser.c',
'src/main.vala',
'src/ui/add_conversation/add_conference_dialog.vala',
'src/ui/add_conversation/add_contact_dialog.vala',
'src/ui/add_conversation/add_groupchat_dialog.vala',
'src/ui/add_conversation/conference_details_fragment.vala',
'src/ui/add_conversation/conference_list.vala',
'src/ui/add_conversation/list_row.vala',
'src/ui/add_conversation/roster_list.vala',
'src/ui/add_conversation/select_contact_dialog.vala',
'src/ui/add_conversation/select_jid_fragment.vala',
'src/ui/avatar_drawer.vala',
'src/ui/avatar_image.vala',
'src/ui/application.vala',
'src/ui/call_window/audio_settings_popover.vala',
'src/ui/call_window/call_bottom_bar.vala',
'src/ui/call_window/call_connection_details_window.vala',
'src/ui/call_window/call_encryption_button.vala',
'src/ui/call_window/call_window.vala',
'src/ui/call_window/call_window_controller.vala',
'src/ui/call_window/participant_widget.vala',
'src/ui/call_window/video_settings_popover.vala',
'src/ui/chat_input/chat_input_controller.vala',
'src/ui/chat_input/chat_text_view.vala',
'src/ui/chat_input/edit_history.vala',
'src/ui/chat_input/encryption_button.vala',
'src/ui/chat_input/occupants_tab_completer.vala',
'src/ui/chat_input/smiley_converter.vala',
'src/ui/chat_input/spell_checker.vala',
'src/ui/chat_input/view.vala',
'src/ui/contact_details/blocking_provider.vala',
'src/ui/contact_details/dialog.vala',
'src/ui/contact_details/muc_config_form_provider.vala',
'src/ui/contact_details/permissions_provider.vala',
'src/ui/contact_details/settings_provider.vala',
'src/ui/conversation_content_view/call_widget.vala',
'src/ui/conversation_content_view/chat_state_populator.vala',
'src/ui/conversation_content_view/content_populator.vala',
'src/ui/conversation_content_view/conversation_item_skeleton.vala',
'src/ui/conversation_content_view/conversation_view.vala',
'src/ui/conversation_content_view/date_separator_populator.vala',
'src/ui/conversation_content_view/file_default_widget.vala',
'src/ui/conversation_content_view/file_image_widget.vala',
'src/ui/conversation_content_view/file_widget.vala',
'src/ui/conversation_content_view/message_widget.vala',
'src/ui/conversation_content_view/subscription_notification.vala',
'src/ui/conversation_list_titlebar.vala',
'src/ui/conversation_selector/conversation_selector.vala',
'src/ui/conversation_selector/conversation_selector_row.vala',
'src/ui/conversation_titlebar/call_entry.vala',
'src/ui/conversation_titlebar/conversation_titlebar.vala',
'src/ui/conversation_titlebar/menu_entry.vala',
'src/ui/conversation_titlebar/occupants_entry.vala',
'src/ui/conversation_titlebar/search_entry.vala',
'src/ui/conversation_view.vala',
'src/ui/conversation_view_controller.vala',
'src/ui/file_send_overlay.vala',
'src/ui/global_search.vala',
'src/ui/main_window.vala',
'src/ui/main_window_controller.vala',
'src/ui/manage_accounts/account_row.vala',
'src/ui/manage_accounts/add_account_dialog.vala',
'src/ui/manage_accounts/dialog.vala',
'src/ui/notifier_freedesktop.vala',
'src/ui/notifier_gnotifications.vala',
'src/ui/occupant_menu/list.vala',
'src/ui/occupant_menu/list_row.vala',
'src/ui/occupant_menu/view.vala',
'src/ui/settings_dialog.vala',
'src/ui/util/accounts_combo_box.vala',
'src/ui/util/config.vala',
'src/ui/util/data_forms.vala',
'src/ui/util/helper.vala',
'src/ui/util/label_hybrid.vala',
'src/ui/util/preview_file_chooser_native.vala',
'src/ui/util/scaling_image.vala',
'src/ui/util/size_request_box.vala',
'src/ui/util/sizing_bin.vala',
'vapi/emojichooser.vapi',
)
sources += import('gnome').compile_resources(
'dino-resources',
'data/gresource.xml',
source_dir: 'data',
)
c_args = [
'-DG_LOG_DOMAIN="dino"',
'-DGETTEXT_PACKAGE="dino"',
'-DLOCALE_INSTALL_DIR="@0@"'.format(get_option('prefix') / get_option('localedir')),
]
exe_dino = executable('dino', sources, include_directories: 'src', c_args: c_args, vala_args: ['--vapidir', meson.current_source_dir() / 'vapi'], dependencies: dependencies)

View File

@ -117,6 +117,8 @@ public class Dino.Ui.Application : Gtk.Application, Dino.Application {
}
private void create_actions() {
Dino.Entities.Settings settings = Dino.Application.get_default().settings;
SimpleAction accounts_action = new SimpleAction("accounts", null);
accounts_action.activate.connect(show_accounts_window);
add_action(accounts_action);
@ -132,7 +134,20 @@ public class Dino.Ui.Application : Gtk.Application, Dino.Application {
SimpleAction quit_action = new SimpleAction("quit", null);
quit_action.activate.connect(quit);
add_action(quit_action);
set_accels_for_action("app.quit", KEY_COMBINATION_QUIT);
if (settings.quit_ctrl_q) {
set_accels_for_action("app.quit", KEY_COMBINATION_QUIT);
}
settings.quit_ctrl_q_update.connect((active) =>
{
if (active) {
set_accels_for_action("app.quit", KEY_COMBINATION_QUIT);
}
else {
set_accels_for_action("app.quit", {null});
}
});
SimpleAction open_conversation_action = new SimpleAction("open-conversation", VariantType.INT32);
open_conversation_action.activate.connect((variant) => {
@ -327,5 +342,20 @@ public class Dino.Ui.Application : Gtk.Application, Dino.Application {
});
dialog.present();
}
}
private uint inhibit_app(string reason) {
uint inhibit_cookie = inhibit(window, SUSPEND, reason);
if (inhibit_cookie == 0) {
warning("suspend inhibit request failed or unsupported");
}
return inhibit_cookie;
}
private void uninhibit_app(uint inhibit_cookie) {
if (inhibit_cookie != 0) {
uninhibit(inhibit_cookie);
}
}
}

View File

@ -42,7 +42,7 @@ public class ChatTextView : ScrolledWindow {
private SpellChecker spell_checker;
construct {
max_content_height = 300;
max_content_height = 128;
propagate_natural_height = true;
this.add(text_view);
@ -76,8 +76,11 @@ public class ChatTextView : ScrolledWindow {
private bool on_text_input_key_press(EventKey event) {
if (event.keyval in new uint[]{Key.Return, Key.KP_Enter}) {
if ((event.state & ModifierType.CONTROL_MASK) > 0) {
text_view.buffer.insert_at_cursor("\n", 1);
Dino.Entities.Settings settings = Dino.Application.get_default().settings;
if ((event.state & ModifierType.SHIFT_MASK) > 0
|| settings.enter_newline) {
text_view.buffer.insert_at_cursor("\n", 1);
} else if (text_view.buffer.text.strip() != "") {
send_text();
edit_history.reset_history();

View File

@ -55,39 +55,43 @@ public class View : Box {
outer_box.add(encryption_widget);
{
MenuButton send_button = new MenuButton() {
tooltip_text="Send message",
relief=ReliefStyle.NONE,
margin_top=3,
valign=Align.CENTER,
visible=true,
sensitive=false
};
Dino.Entities.Settings settings = Dino.Application.get_default().settings;
send_button.get_style_context().add_class("flat");
send_button.get_style_context().add_class("dino-chatinput-button");
send_button.image = new Image.from_icon_name("document-send", IconSize.BUTTON) {
visible=true,
icon_size=3
};
MenuButton send_button = new MenuButton() {
tooltip_text="Send message",
relief=ReliefStyle.NONE,
margin_top=3,
valign=Align.CENTER,
visible=settings.send_button,
sensitive=false
};
chat_text_view.text_view.buffer.changed.connect(() => {
if (chat_text_view.text_view.buffer.text != "") {
send_button.sensitive = true;
}
else {
send_button.sensitive = false;
}
});
settings.send_button_update.connect(() => {
send_button.visible = settings.send_button;
});
send_button.button_release_event.connect(() => {
chat_text_view.send_text();
return true;
});
send_button.get_style_context().add_class("flat");
send_button.get_style_context().add_class("dino-chatinput-button");
send_button.image = new Image.from_icon_name("document-send", IconSize.BUTTON) {
visible=true,
icon_size=3
};
outer_box.add(send_button);
}
chat_text_view.text_view.buffer.changed.connect(() => {
if (chat_text_view.text_view.buffer.text != "") {
send_button.sensitive = true;
}
else {
send_button.sensitive = false;
}
});
send_button.button_release_event.connect(() => {
chat_text_view.send_text();
return true;
});
outer_box.add(send_button);
Util.force_css(frame, "* { border-radius: 3px; }");

View File

@ -11,6 +11,7 @@ namespace Dino.Ui.ConversationSummary {
public class ConversationView : Box, Plugins.ConversationItemCollection, Plugins.NotificationCollection {
public signal void on_quote_text(string text);
public signal void on_copy_text(string text);
public Conversation? conversation { get; private set; }
[GtkChild] public unowned ScrolledWindow scrolled;
@ -330,6 +331,7 @@ public class ConversationView : Box, Plugins.ConversationItemCollection, Plugins
MessageMetaItem current_item = item as MessageMetaItem;
current_item.on_quote_text.connect((t, text) => on_quote_text(text));
current_item.on_copy_text.connect((t, text) => on_copy_text(text));
}
meta_items.add(item);
}

View File

@ -31,7 +31,8 @@ public class FileDefaultWidget : EventBox {
cancel_button = new ModelButton() { text=_("Cancel"), visible=true };
}
public void update_file_info(string? mime_type, FileTransfer.State state, long size) {
public void update_file_info(string? mime_type, uint64 transferred_bytes,
bool direction, FileTransfer.State state, long size) {
this.state = state;
spinner.active = false; // A hidden spinning spinner still uses CPU. Deactivate asap
@ -58,7 +59,17 @@ public class FileDefaultWidget : EventBox {
popover_menu.closed.connect(on_pointer_left);
break;
case FileTransfer.State.IN_PROGRESS:
mime_label.label = _("Downloading %s…").printf(get_size_string(size));
uint progress = 0;
if (size > 0)
progress = (uint)((transferred_bytes * (uint64)100) / (uint64)size);
if (direction == FileTransfer.DIRECTION_SENT) {
mime_label.label = _("Uploading %s (%u%%)…").printf(get_size_string(size), progress);
}
else {
mime_label.label = _("Downloading %s (%u%%)…").printf(get_size_string(size), progress);
}
spinner.active = true;
image_stack.set_visible_child_name("spinner");

View File

@ -65,7 +65,8 @@ public class FileWidget : SizeRequestBox {
}
private async void update_widget() {
if (show_image() && state != State.IMAGE) {
if (show_image() && state != State.IMAGE
&& file_transfer.state == FileTransfer.State.COMPLETE) {
var content_bak = content;
FileImageWidget file_image_widget = null;
@ -120,6 +121,7 @@ public class FileDefaultWidgetController : Object {
public string file_transfer_path { get; set; }
public string file_transfer_state { get; set; }
public string file_transfer_mime_type { get; set; }
public uint64 file_transfer_transferred_bytes { get; set; }
private StreamInteractor? stream_interactor;
private string file_uri;
@ -143,10 +145,12 @@ public class FileDefaultWidgetController : Object {
file_transfer.bind_property("path", this, "file-transfer-path");
file_transfer.bind_property("state", this, "file-transfer-state");
file_transfer.bind_property("mime-type", this, "file-transfer-mime-type");
file_transfer.bind_property("transferred-bytes", this, "file-transfer-transferred-bytes");
this.notify["file-transfer-path"].connect(update_file_info);
this.notify["file-transfer-state"].connect(update_file_info);
this.notify["file-transfer-mime-type"].connect(update_file_info);
this.notify["file-transfer-transferred-bytes"].connect(update_file_info);
update_file_info();
}
@ -155,13 +159,15 @@ public class FileDefaultWidgetController : Object {
file_uri = file.get_uri();
state = FileTransfer.State.COMPLETE;
widget.name_label.label = this.file_name = file_name;
widget.update_file_info(mime_type, state, -1);
widget.update_file_info(mime_type, 0, false, state, -1);
}
private void update_file_info() {
file_uri = file_transfer.get_file().get_uri();
state = file_transfer.state;
widget.update_file_info(file_transfer.mime_type, file_transfer.state, file_transfer.size);
widget.update_file_info(file_transfer.mime_type,
file_transfer.transferred_bytes,
file_transfer.direction, file_transfer.state, file_transfer.size);
}
private void open_file() {
@ -198,12 +204,13 @@ public class FileDefaultWidgetController : Object {
open_file();
}
break;
case FileTransfer.State.FAILED:
case FileTransfer.State.NOT_STARTED:
assert(stream_interactor != null && file_transfer != null);
stream_interactor.get_module(FileManager.IDENTITY).download_file.begin(file_transfer);
break;
default:
// Clicking doesn't do anything in FAILED and IN_PROGRESS states
// Clicking doesn't do anything in IN_PROGRESS states
break;
}
return false;

View File

@ -11,6 +11,7 @@ namespace Dino.Ui.ConversationSummary {
public class MessageMetaItem : ContentMetaItem {
public signal void on_quote_text(string text);
public signal void on_copy_text(string text);
private StreamInteractor stream_interactor;
private MessageItemWidget message_item_widget;
private MessageItem message_item;
@ -56,6 +57,14 @@ public class MessageMetaItem : ContentMetaItem {
};
actions.add(action2);
Plugins.MessageAction copy_action = new Plugins.MessageAction();
copy_action.icon_name = "edit-copy";
copy_action.callback = (button, content_meta_item_activated, widget) => {
string text = ((MessageItem) message_item_widget.content_item).message.body;
on_copy_text(text);
};
actions.add(copy_action);
if (allowed && !in_edit_mode) {
Plugins.MessageAction action1 = new Plugins.MessageAction();
action1.icon_name = "document-edit-symbolic";

View File

@ -14,10 +14,12 @@ public class ConversationView : Gtk.Overlay {
[GtkChild] public unowned ChatInput.View chat_input;
[GtkChild] public unowned ConversationSummary.ConversationView conversation_frame;
[GtkChild] public unowned Revealer white_revealer;
public signal void copy_text(string text);
construct {
white_revealer.notify["child-revealed"].connect_after(on_child_revealed_changed);
conversation_frame.on_quote_text.connect((t, text) => on_quote_text(text));
conversation_frame.on_copy_text.connect((t, text) => copy_text(text));
}
public void on_quote_text(string text) {

View File

@ -74,7 +74,8 @@ public class FileSendOverlay : Gtk.EventBox {
if (widget == null) {
FileDefaultWidget default_widget = new FileDefaultWidget() { visible=true };
default_widget.name_label.label = file_name;
default_widget.update_file_info(mime_type, FileTransfer.State.COMPLETE, (long)file_info.get_size());
default_widget.update_file_info(mime_type, 0, FileTransfer.DIRECTION_SENT,
FileTransfer.State.COMPLETE, (long)file_info.get_size());
widget = default_widget;
}

View File

@ -78,6 +78,12 @@ public class MainWindow : Gtk.Window {
search_entry = (SearchEntry) builder.get_object("search_entry");
Image conversation_list_placeholder_image = (Image) builder.get_object("conversation_list_placeholder_image");
conversation_list_placeholder_image.set_from_pixbuf(new Pixbuf.from_resource("/im/dino/Dino/icons/dino-conversation-list-placeholder-arrow.svg"));
conversation_view.copy_text.connect((text) => {
var display = get_display();
var clipboard = Gtk.Clipboard.get_default(display);
clipboard.set_text(text, text.length);
});
}
private void update_headerbar() {

View File

@ -14,6 +14,9 @@ class SettingsDialog : Dialog {
[GtkChild] private unowned RadioButton encryption_radio_undecided;
[GtkChild] private unowned RadioButton encryption_radio_omemo;
[GtkChild] private unowned RadioButton encryption_radio_openpgp;
[GtkChild] private unowned CheckButton send_button_checkbutton;
[GtkChild] private unowned CheckButton enter_newline_checkbutton;
[GtkChild] private unowned CheckButton quit_ctrl_q_checkbutton;
Dino.Entities.Settings settings = Dino.Application.get_default().settings;
@ -28,6 +31,10 @@ class SettingsDialog : Dialog {
encryption_radio_undecided.active = settings.default_encryption == Encryption.UNKNOWN;
encryption_radio_omemo.active = settings.default_encryption == Encryption.OMEMO;
encryption_radio_openpgp.active = settings.default_encryption == Encryption.PGP;
send_button_checkbutton.active = settings.send_button;
enter_newline_checkbutton.active = settings.enter_newline;
enter_newline_checkbutton.sensitive = settings.send_button;
quit_ctrl_q_checkbutton.active = settings.quit_ctrl_q;
typing_checkbutton.toggled.connect(() => { settings.send_typing = typing_checkbutton.active; } );
marker_checkbutton.toggled.connect(() => { settings.send_marker = marker_checkbutton.active; } );
@ -52,6 +59,17 @@ class SettingsDialog : Dialog {
settings.default_encryption = Encryption.PGP;
}
});
send_button_checkbutton.toggled.connect(() => { settings.send_button = send_button_checkbutton.active; });
enter_newline_checkbutton.toggled.connect(() => { settings.enter_newline = enter_newline_checkbutton.active; });
settings.send_button_update.connect((visible) => {
enter_newline_checkbutton.sensitive = visible;
if (visible == false) {
enter_newline_checkbutton.active = visible;
}
});
quit_ctrl_q_checkbutton.toggled.connect(() => { settings.quit_ctrl_q = quit_ctrl_q_checkbutton.active; });
}
}

29
meson.build Normal file
View File

@ -0,0 +1,29 @@
project('dino', 'vala', 'c')
fs = import('fs')
python = import('python')
dep_gcrypt = dependency('libgcrypt')
dep_gdk_pixbuf = dependency('gdk-pixbuf-2.0')
dep_gee = dependency('gee-0.8')
dep_gio = dependency('gio-2.0')
dep_glib = dependency('glib-2.0')
dep_gmodule = dependency('gmodule-2.0')
dep_gspell = dependency('gspell-1')
dep_gtk3 = dependency('gtk+-3.0')
dep_icu_uc = dependency('icu-uc')
dep_libhandy = dependency('libhandy-1')
dep_m = meson.get_compiler('c').find_library('m', required: false)
dep_sqlite3 = dependency('sqlite3', version: '>=3.24')
dep_soup2 = dependency('libsoup-2.4')
dep_soup3 = dependency('libsoup-3.0')
dep_libsignal_protocol_c = dependency('libsignal-protocol-c', version: '>=2.3.2')
prog_git = find_program('git', required: false)
prog_python = python.find_installation()
subdir('qlite')
subdir('xmpp-vala')
subdir('libdino')
subdir('main')
subdir('plugins')

1
meson_options.txt Normal file
View File

@ -0,0 +1 @@
option('plugindir', type: 'string', value: 'lib/dino/plugins', description: 'Plugin directory for Dino plugins')

View File

@ -0,0 +1,22 @@
dependencies = [
dep_gee,
dep_glib,
dep_gtk3,
dep_gmodule,
dep_soup2,
dep_dino,
dep_soup2,
]
sources = files(
'src/file_provider.vala',
'src/file_sender.vala',
'src/plugin.vala',
'src/register_plugin.vala',
)
lib_http_files = library('http-files',
sources,
c_args: c_args,
include_directories: include_directories('src'),
dependencies: dependencies)

View File

@ -117,7 +117,9 @@ public class FileProvider : Dino.FileProvider, Object {
});
file_meta.mime_type = content_type;
if (content_length != null) {
file_meta.size = int64.parse(content_length);
if (int64.try_parse(content_length, out file_meta.size) == false) {
throw new FileReceiveError.GET_METADATA_FAILED("failed to parse size");
}
}
return file_meta;
@ -152,10 +154,23 @@ public class FileProvider : Dino.FileProvider, Object {
}
public FileMeta get_file_meta(FileTransfer file_transfer) throws FileReceiveError {
Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(file_transfer.counterpart.bare_jid, file_transfer.account);
if (conversation == null) throw new FileReceiveError.GET_METADATA_FAILED("No conversation");
var? cm_identity = stream_interactor.get_module(ConversationManager.IDENTITY);
if (cm_identity == null) {
throw new FileReceiveError.GET_METADATA_FAILED("null ConversationManager");
}
Message? message = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_id(int.parse(file_transfer.info), conversation);
Conversation? conversation = cm_identity.get_conversation(file_transfer.counterpart.bare_jid, file_transfer.account);
if (conversation == null) throw new FileReceiveError.GET_METADATA_FAILED("No conversation");
else if (file_transfer.info == null) {
throw new FileReceiveError.GET_METADATA_FAILED("null file_transfer.info");
}
int info;
bool result = int.try_parse(file_transfer.info, out info);
if (result == false) {
throw new FileReceiveError.GET_METADATA_FAILED("failed to parse file_transfer.info");
}
Message? message = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_id(info, conversation);
if (message == null) throw new FileReceiveError.GET_METADATA_FAILED("No message");
var file_meta = new HttpFileMeta();
@ -170,10 +185,20 @@ public class FileProvider : Dino.FileProvider, Object {
}
public FileReceiveData? get_file_receive_data(FileTransfer file_transfer) {
Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(file_transfer.counterpart.bare_jid, file_transfer.account);
var? cm_identity = stream_interactor.get_module(ConversationManager.IDENTITY);
if (cm_identity == null) return null;
Conversation? conversation = cm_identity.get_conversation(file_transfer.counterpart.bare_jid, file_transfer.account);
if (conversation == null) return null;
Message? message = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_id(int.parse(file_transfer.info), conversation);
var? ms_identity = stream_interactor.get_module(MessageStorage.IDENTITY);
if (ms_identity == null || file_transfer.info == null) return null;
int info;
bool result = int.try_parse(file_transfer.info, out info);
if (result == false) return null;
Message? message = ms_identity.get_message_by_id(info, conversation);
if (message == null) return null;
var receive_data = new HttpFileReceiveData();

View File

@ -25,7 +25,11 @@ public class HttpFileSender : FileSender, Object {
if (stream == null) return null;
try {
var slot_result = yield stream_interactor.module_manager.get_module(file_transfer.account, Xmpp.Xep.HttpFileUpload.Module.IDENTITY).request_slot(stream, file_transfer.server_file_name, file_meta.size, file_meta.mime_type);
var slot_result = yield stream_interactor.module_manager
.get_module(file_transfer.account,
Xmpp.Xep.HttpFileUpload.Module.IDENTITY)
.request_slot(stream, file_transfer.server_file_name,
file_meta.size, file_meta.mime_type);
send_data.url_down = slot_result.url_get;
send_data.url_up = slot_result.url_put;
send_data.headers = slot_result.headers;
@ -81,6 +85,10 @@ public class HttpFileSender : FileSender, Object {
body.complete();
return;
}
else if (read < 0) {
warning("transfer_more_bytes: stream.read failed");
return;
}
bytes.length = (int)read;
body.append_buffer(new Soup.Buffer.take(bytes));
}
@ -102,19 +110,38 @@ public class HttpFileSender : FileSender, Object {
put_message.wrote_headers.connect(() => transfer_more_bytes(file_transfer.input_stream, put_message.request_body));
put_message.wrote_chunk.connect(() => transfer_more_bytes(file_transfer.input_stream, put_message.request_body));
#endif
file_transfer.transferred_bytes = 0;
put_message.wrote_body_data.connect((chunk) => {
if (file_transfer.size != 0) {
#if SOUP_3_0
file_transfer.transferred_bytes += chunk;
#else
file_transfer.transferred_bytes += chunk.length;
#endif
}
});
foreach (var entry in file_send_data.headers.entries) {
put_message.request_headers.append(entry.key, entry.value);
}
uint inhibit_cookie = 0;
try {
inhibit_cookie = Application.get_default()
.inhibit_app("Ongoing file upload");
#if SOUP_3_0
yield session.send_async(put_message, GLib.Priority.LOW, file_transfer.cancellable);
#else
yield session.send_async(put_message, file_transfer.cancellable);
#endif
Application.get_default().uninhibit_app(inhibit_cookie);
if (put_message.status_code < 200 || put_message.status_code >= 300) {
throw new FileSendError.UPLOAD_FAILED("HTTP status code %s".printf(put_message.status_code.to_string()));
}
} catch (Error e) {
Application.get_default().uninhibit_app(inhibit_cookie);
throw new FileSendError.UPLOAD_FAILED("HTTP upload error: %s".printf(e.message));
}
}

5
plugins/meson.build Normal file
View File

@ -0,0 +1,5 @@
subdir('signal-protocol')
#subdir('gpgme-vala')
subdir('http-files')
#subdir('ice')
#subdir('rtp')

View File

@ -8,9 +8,35 @@ public class Plugin : RootInterface, Object {
public void registered(Dino.Application app) {
this.app = app;
Canberra.Context.create(out sound_context);
string sound_id = "",
description = "Unknown notification";
app.stream_interactor.get_module(NotificationEvents.IDENTITY).notify_content_item.connect((item, conversation) => {
sound_context.play(0, Canberra.PROP_EVENT_ID, "message-new-instant", Canberra.PROP_EVENT_DESCRIPTION, "New Dino message");
var? module = app.stream_interactor.get_module(NotificationEvents.IDENTITY);
if (module == null)
return;
module.notify_content_item.connect((item, conversation) => {
switch (item.type_) {
case MessageItem.TYPE:
description = "New Dino message";
sound_id = "message-new-instant";
break;
case CallItem.TYPE:
description = "Incoming call";
sound_id = "phone-incoming-call";
break;
case FileItem.TYPE:
description = "Incoming file";
sound_id = "message-new-instant";
break;
}
sound_context.play(0,
Canberra.PROP_EVENT_ID,
sound_id,
Canberra.PROP_EVENT_DESCRIPTION,
description);
});
}

View File

@ -41,6 +41,14 @@ public class Dino.Plugins.Rtp.VideoWidget : Gtk.Bin, Dino.Plugins.VideoCallWidge
add(widget);
widget.visible = true;
} else {
var dialog = new Gtk.MessageDialog(null, MODAL, WARNING, OK, null);
dialog.text = "Could not create GTK video sink";
dialog.secondary_text = "Please install package gstreamer1.0-gtk3 or equivalent.";
dialog.response.connect((response_id) => {
dialog.close();
});
dialog.run();
warning("Could not create GTK video sink. Won't display videos.");
}
size_allocate.connect_after(after_size_allocate);

View File

@ -0,0 +1,26 @@
dependencies = [
dep_gee,
dep_glib,
dep_gcrypt,
dep_dino,
dep_libsignal_protocol_c,
]
sources = files(
'src/context.vala',
'src/simple_iks.vala',
'src/simple_ss.vala',
'src/simple_pks.vala',
'src/simple_spks.vala',
'src/store.vala',
'src/util.vala',
'vapi/signal-protocol-public.vapi',
'vapi/signal-protocol-native.vapi',
)
lib_signal_protocol = library('signal-protocol-vala',
sources,
c_args: c_args,
vala_args: ['--vapidir', meson.current_source_dir() / 'vapi'],
include_directories: include_directories('src'),
dependencies: dependencies)

22
qlite/meson.build Normal file
View File

@ -0,0 +1,22 @@
dependencies = [
dep_gee,
dep_glib,
dep_sqlite3,
]
sources = files(
'src/column.vala',
'src/database.vala',
'src/delete_builder.vala',
'src/insert_builder.vala',
'src/query_builder.vala',
'src/row.vala',
'src/statement_builder.vala',
'src/table.vala',
'src/update_builder.vala',
'src/upsert_builder.vala',
)
c_args = [
'-DG_LOG_DOMAIN="qlite"',
]
lib_qlite = library('qlite', sources, c_args: c_args, vala_args: ['--vapidir', meson.current_source_dir() / 'vapi'], dependencies: dependencies)
dep_qlite = declare_dependency(link_with: lib_qlite, include_directories: include_directories('.'))

124
xmpp-vala/meson.build Normal file
View File

@ -0,0 +1,124 @@
dependencies = [
dep_gdk_pixbuf,
dep_gee,
dep_gio,
dep_glib,
dep_icu_uc,
dep_m,
]
sources = files(
'src/core/direct_tls_xmpp_stream.vala',
'src/core/io_xmpp_stream.vala',
'src/core/module_flag.vala',
'src/core/namespace_state.vala',
'src/core/stanza_attribute.vala',
'src/core/stanza_node.vala',
'src/core/stanza_reader.vala',
'src/core/stanza_writer.vala',
'src/core/starttls_xmpp_stream.vala',
'src/core/stream_connect.vala',
'src/core/tls_xmpp_stream.vala',
'src/core/xmpp_log.vala',
'src/core/xmpp_stream.vala',
'src/glib_fixes.vapi',
'src/module/bind.vala',
'src/module/bookmarks_provider.vala',
'src/module/conference.vala',
'src/module/iq/module.vala',
'src/module/iq/stanza.vala',
'src/module/jid.vala',
'src/module/message/module.vala',
'src/module/message/stanza.vala',
'src/module/presence/flag.vala',
'src/module/presence/module.vala',
'src/module/presence/stanza.vala',
'src/module/roster/flag.vala',
'src/module/roster/item.vala',
'src/module/roster/module.vala',
'src/module/roster/versioning_module.vala',
'src/module/sasl.vala',
'src/module/session.vala',
'src/module/stanza.vala',
'src/module/stanza_error.vala',
'src/module/stream_error.vala',
'src/module/util.vala',
'src/module/xep/0004_data_forms.vala',
'src/module/xep/0030_service_discovery/flag.vala',
'src/module/xep/0030_service_discovery/identity.vala',
'src/module/xep/0030_service_discovery/info_result.vala',
'src/module/xep/0030_service_discovery/item.vala',
'src/module/xep/0030_service_discovery/items_result.vala',
'src/module/xep/0030_service_discovery/module.vala',
'src/module/xep/0045_muc/flag.vala',
'src/module/xep/0045_muc/module.vala',
'src/module/xep/0045_muc/status_code.vala',
'src/module/xep/0047_in_band_bytestreams.vala',
'src/module/xep/0048_bookmarks.vala',
'src/module/xep/0048_conference.vala',
'src/module/xep/0049_private_xml_storage.vala',
'src/module/xep/0054_vcard/module.vala',
'src/module/xep/0060_pubsub.vala',
'src/module/xep/0065_socks5_bytestreams.vala',
'src/module/xep/0066_out_of_band_data.vala',
'src/module/xep/0077_in_band_registration.vala',
'src/module/xep/0082_date_time_profiles.vala',
'src/module/xep/0084_user_avatars.vala',
'src/module/xep/0085_chat_state_notifications.vala',
'src/module/xep/0115_entity_capabilities.vala',
'src/module/xep/0166_jingle/component.vala',
'src/module/xep/0166_jingle/content.vala',
'src/module/xep/0166_jingle/content_description.vala',
'src/module/xep/0166_jingle/content_node.vala',
'src/module/xep/0166_jingle/content_security.vala',
'src/module/xep/0166_jingle/content_transport.vala',
'src/module/xep/0166_jingle/jingle_flag.vala',
'src/module/xep/0166_jingle/jingle_module.vala',
'src/module/xep/0166_jingle/jingle_structs.vala',
'src/module/xep/0166_jingle/reason_element.vala',
'src/module/xep/0166_jingle/session.vala',
'src/module/xep/0166_jingle/session_info.vala',
'src/module/xep/0167_jingle_rtp/content_parameters.vala',
'src/module/xep/0167_jingle_rtp/content_type.vala',
'src/module/xep/0167_jingle_rtp/jingle_rtp_module.vala',
'src/module/xep/0167_jingle_rtp/payload_type.vala',
'src/module/xep/0167_jingle_rtp/session_info_type.vala',
'src/module/xep/0167_jingle_rtp/stream.vala',
'src/module/xep/0176_jingle_ice_udp/candidate.vala',
'src/module/xep/0176_jingle_ice_udp/jingle_ice_udp_module.vala',
'src/module/xep/0176_jingle_ice_udp/transport_parameters.vala',
'src/module/xep/0177_jingle_raw_udp.vala',
'src/module/xep/0184_message_delivery_receipts.vala',
'src/module/xep/0191_blocking_command.vala',
'src/module/xep/0198_stream_management.vala',
'src/module/xep/0199_ping.vala',
'src/module/xep/0203_delayed_delivery.vala',
'src/module/xep/0215_external_service_discovery.vala',
'src/module/xep/0234_jingle_file_transfer.vala',
'src/module/xep/0249_direct_muc_invitations.vala',
'src/module/xep/0260_jingle_socks5_bytestreams.vala',
'src/module/xep/0261_jingle_in_band_bytestreams.vala',
'src/module/xep/0272_muji.vala',
'src/module/xep/0280_message_carbons.vala',
'src/module/xep/0298_coin.vala',
'src/module/xep/0308_last_message_correction.vala',
'src/module/xep/0313_message_archive_management.vala',
'src/module/xep/0333_chat_markers.vala',
'src/module/xep/0334_message_processing_hints.vala',
'src/module/xep/0353_call_invite_message.vala',
'src/module/xep/0353_jingle_message_initiation.vala',
'src/module/xep/0359_unique_stable_stanza_ids.vala',
'src/module/xep/0363_http_file_upload.vala',
'src/module/xep/0380_explicit_encryption.vala',
'src/module/xep/0384_omemo/omemo_decryptor.vala',
'src/module/xep/0384_omemo/omemo_encryptor.vala',
'src/module/xep/0391_jingle_encrypted_transports.vala',
'src/module/xep/0402_bookmarks2.vala',
'src/module/xep/0410_muc_self_ping.vala',
'src/module/xep/pixbuf_storage.vala',
'src/util.vala',
)
c_args = [
'-DG_LOG_DOMAIN="xmpp-vala"',
]
lib_xmpp_vala = library('xmpp-vala', sources, c_args: c_args, vala_args: ['--vapidir', meson.current_source_dir() / 'vapi'], dependencies: dependencies)
dep_xmpp_vala = declare_dependency(link_with: lib_xmpp_vala, include_directories: include_directories('.'))

View File

@ -8,6 +8,7 @@ private const string NS_URI = "urn:xmpp:jingle:apps:file-transfer:5";
public class Module : Jingle.ContentType, XmppStreamModule {
public signal void transferred_bytes(size_t bytes);
public signal void file_incoming(XmppStream stream, FileTransfer file_transfer);
public static Xmpp.ModuleIdentity<Module> IDENTITY = new Xmpp.ModuleIdentity<Module>(NS_URI, "0234_jingle_file_transfer");
@ -42,7 +43,10 @@ public class Module : Jingle.ContentType, XmppStreamModule {
return yield stream.get_module(Jingle.Module.IDENTITY).is_available(stream, required_transport_type, required_components, full_jid);
}
public async void offer_file_stream(XmppStream stream, Jid receiver_full_jid, InputStream input_stream, string basename, int64 size, string? precondition_name = null, Object? precondition_options = null) throws Jingle.Error {
public async void offer_file_stream(XmppStream stream, Jid receiver_full_jid,
Cancellable cancellable, InputStream input_stream, string basename,
int64 size, string? precondition_name = null,
Object? precondition_options = null) throws Jingle.Error {
StanzaNode file_node;
StanzaNode description = new StanzaNode.build("description", NS_URI)
.add_self_xmlns()
@ -107,7 +111,18 @@ public class Module : Jingle.ContentType, XmppStreamModule {
}
IOStream io_stream = yield connection.stream.wait_async();
yield io_stream.input_stream.close_async();
yield io_stream.output_stream.splice_async(input_stream, OutputStreamSpliceFlags.CLOSE_SOURCE|OutputStreamSpliceFlags.CLOSE_TARGET);
ssize_t read;
var buffer = new uint8[1024];
while ((read = yield input_stream.read_async(buffer, Priority.LOW, cancellable)) > 0) {
buffer.length = (int) read;
transferred_bytes((size_t)read);
yield io_stream.output_stream.write_async(buffer, Priority.LOW, cancellable);
buffer.length = 1024;
}
yield input_stream.close_async();
yield io_stream.output_stream.close_async();
yield connection.terminate(true);
} catch (Error e) {
if (session != null) {

View File

@ -39,7 +39,11 @@ namespace Xmpp.MessageArchiveManagement.V2 {
}
}
private StanzaNode create_base_query(XmppStream stream, MamQueryParams mam_params) {
private StanzaNode create_base_query(XmppStream? stream, MamQueryParams mam_params) {
if (stream == null) {
return new StanzaNode();
}
var fields = new ArrayList<DataForms.DataForm.Field>();
if (mam_params.with != null) {
@ -61,7 +65,12 @@ namespace Xmpp.MessageArchiveManagement.V2 {
return MessageArchiveManagement.create_base_query(stream, MessageArchiveManagement.NS_URI_2, mam_params.query_id, fields);
}
public async QueryResult query_archive(XmppStream stream, MamQueryParams mam_params, Cancellable? cancellable = null) {
public async QueryResult query_archive(XmppStream? stream, MamQueryParams mam_params, Cancellable? cancellable = null) {
if (stream == null) {
var result = new QueryResult();
result.error = true;
return result;
}
var query_node = create_base_query(stream, mam_params);
if (!mam_params.use_ns2_extended) {
query_node.put_node(ResultSetManagement.create_set_rsm_node_before(mam_params.end_id));

View File

@ -53,7 +53,11 @@ public class Module : XmppStreamModule {
}
}
internal StanzaNode create_base_query(XmppStream stream, string ns, string? queryid, Gee.List<DataForms.DataForm.Field> fields) {
internal StanzaNode create_base_query(XmppStream? stream, string ns, string? queryid, Gee.List<DataForms.DataForm.Field> fields) {
if (stream == null) {
return new StanzaNode();
}
DataForms.DataForm data_form = new DataForms.DataForm();
DataForms.DataForm.HiddenField form_type_field = new DataForms.DataForm.HiddenField() { var="FORM_TYPE" };
@ -156,7 +160,11 @@ public class MessageFlag : Xmpp.MessageFlag {
}
private static string NS_VER(XmppStream stream) {
return stream.get_flag(Flag.IDENTITY).ns_ver;
var? flag = stream.get_flag(Flag.IDENTITY);
if (flag == null) {
return "";
}
return flag.ns_ver;
}
}