Compare commits

..

91 commits

Author SHA1 Message Date
Alphastaire 91e743572c Sanitize internal storage file names
Sanitize an internally downloaded file's name during save process to prevent file transfer error for Windows users and potentially Linux users.
2024-08-18 13:20:06 +03:00
Alphastaire 80af06709f Retry failed file receipts & extra checks
Allows the user to click on a failed file receipt and retry the file download. There are also now checks for if a file marked as complete doesn't exist.
2024-08-17 12:26:17 +03:00
Alphastaire c28d3865bc Add filename tooltip for images
Hovering over an image with your cursor will now display a tooltip containing the filename.
This solves the difficulty of seeing what an image's file name is and makes it much more convenient.
2024-08-09 22:07:25 +03:00
Miquel Lionel c0299480ad Fix crash when toggling an account very fast (#1505)
- The switch widget in the account managment
            dialog is now not accepting input while the account
            being enabled is connecting.
2024-08-05 19:04:25 +03:00
Not so bad e55207c46e Update ru.po 2024-08-05 19:00:34 +03:00
Miquel Lionel 036d17df97 OpenGPG plugin: Show key as expired or revoked
- Show key as expired or revoked  account manager window;
- Updated French, Russian, German translation for the plugin as well.
2024-06-14 22:21:49 +03:00
Maxim Logaev 1555bd7a12 Sync CMake options with CI flatpak config
Signed-off-by: Maxim Logaev <maxlogaev@proton.me>
2024-06-14 18:37:10 +03:00
giantplaceholder a554bb1a92
Fix typo 2024-06-10 11:35:19 +06:00
giantplaceholder 3f54c42aaf
Update README.md 2024-06-10 05:28:41 +00:00
giantplaceholder 7e57a373d8
Add explicit disclaimer re: support for archs other than x86_64 2024-06-10 05:26:49 +00:00
Maxim Logaev edd0249b20 Bump flatpak runtime version to 46
Signed-off-by: Maxim Logaev <maxlogaev@proton.me>
2024-06-10 01:37:44 +03:00
Maxim Logaev 78cc398e30 Use pointer to int as HINSTANCE for ShellExecuteA
Signed-off-by: Maxim Logaev <maxlogaev@proton.me>
2024-06-10 01:20:32 +03:00
Maxim Logaev f66604560b Added fixed YoloRT for GCC 14
Signed-off-by: Maxim Logaev <maxlogaev@proton.me>
2024-06-10 01:20:32 +03:00
giantplaceholder a7c58bc2a8
Updates for the building instructions 2024-04-25 20:25:47 +04:00
Vadim Nikolaev c00d3a1e15 XEP-0215: Resume updating the field expires after reconnection 2024-04-24 18:53:05 +03:00
Alexandre Jousset 5bca23615f XEP-0215: management of the field expires [2]
Fix following comment and recommandations:

- Use DateTime? type instead of int64?
- check the computed delay value against sane values
- use a HashMap to keep track of timers and cancel them on connection
  closed
- add equals and hash funcs to XmppStream to use with the HashMap
- rename the callback to reflect its meaning
- test the TURN server conf before STUN default server
2024-04-24 18:53:05 +03:00
Alexandre Jousset d6173ba850 XEP-0215: management of the field expires
Add a field in `Xmpp.Xep.ExternalServiceDiscovery` to keep track
of the `expires` TURN service value and use it (divided by 2) to
restart periodically the external services discovery.
2024-04-24 18:53:05 +03:00
Igor Sharonov 5784530204 Force dark/light theme changing 2024-04-24 18:52:52 +03:00
Igor Sharonov cdabee7f20 Improve locale search 2024-04-24 18:34:02 +03:00
Psayker 901883399f Obfuscate password length (#64)
* Add null checks in password_hybrid.changed.connect signal
* Obfuscate password length in account dialog (fixes #797)

Co-authored-by: Miquel Lionel <lionel@les-miquelots.net>
2024-04-24 17:59:11 +03:00
Andrei Voronin 8807bbfe80 Reverting online/offline addings
This commit reverts online/offline addings, but keeps possibly useful
functions for better future. Right now functions with online
functionality saved but unused, they are replaced with almost the same
functions. Names was kept as much as possible.
2024-04-24 17:59:11 +03:00
Miquel Lionel 186b4f46d4 Fixed bug: Dino does not add own account to roster (#696)
Co-authored-by: Psayker <kirill970528@yandex.ru>
2024-04-24 17:58:41 +03:00
Igor Sharonov e6b8ff34ed Support dark theme switch in settings 2024-04-24 16:13:37 +03:00
Maxim Logaev 863cbfb53a Added a workaround for the bug that breaks apt update
Signed-off-by: Maxim Logaev <maxlogaev@proton.me>
2024-04-24 15:56:36 +03:00
Maxim Logaev c83e67354a Added array_length=false attribute for subkeys and uids
Signed-off-by: Maxim Logaev <maxlogaev@proton.me>
2024-04-22 15:43:29 +03:00
Igor Sharonov 2eb0052663 windows: Enable wasapi on windows 2024-04-19 11:28:06 +03:00
Igor Sharonov c438592ab0 webrtc: Replace dsp and echoprobe by gst webrtcdsp and webrtcechoprobe 2024-04-19 11:28:06 +03:00
giantplaceholder cd5e5db816
Add info regarding builds for macOS 2024-04-18 20:15:13 +04:00
Maxim Logaev 8481890b52 Fix from Dino to Dino+ in CI
Signed-off-by: Maxim Logaev <maxlogaev@proton.me>
2024-04-18 15:20:17 +03:00
Maxim Logaev a2fd5af13e Added DEB-package generation using CPack
Signed-off-by: Maxim Logaev <maxlogaev@proton.me>
2024-04-18 15:20:17 +03:00
Maxim Logaev dc628da723 Added Dino+ info to LICENSE_SHORT
Signed-off-by: Maxim Logaev <maxlogaev@proton.me>
2024-04-18 15:20:17 +03:00
marat-yusupov 203be3ce46 Add instructions for install dino via homebrew
update macos docs
2024-04-18 13:12:48 +03:00
Konstantin Kuznetsov 698ebb88c6 Add macos build instructions 2024-04-17 17:01:18 +03:00
Vadim Lomovtsev 8f4d78910c
OpenGPG plugin : Fixed don't list expired/revoked GPG key (#57)
This commit is to implement follwoing changes:
 - closes #91;
 - Mention that GPG key may be expired or revoked:
   in the account dialog if the number of OpenPGP keys found is 0,
   the label also notes that a key may have been revoked or expired;
 - blocks input in chat box if key is use is revoked or expired;

(cherry picked from commit 2f3ddad1e87f99cdda9d42dbabc528c2b29c0476)
Signed-off-by: Vadim Lomovtsev <jelezny@gmail.com>
2024-04-17 15:33:35 +03:00
Igor Sharonov 0ca02a72f4 Add explicit audio converters to voice processor and echo probe
The VoiceProcessor and EchoProbe plugins have fixed caps: rate=48000,channels=1.
There is no such cap on windows, hence append explicit resampler and converter.
2024-04-17 14:49:24 +03:00
Maxim Logaev fc2d2dab8d Added AUR link to README.md
Signed-off-by: Maxim Logaev <maxlogaev@proton.me>
2024-04-17 14:12:06 +03:00
Maxim Logaev 33b8d1c4bc Fixed Dino+ desktop file name
Signed-off-by: Maxim Logaev <maxlogaev@proton.me>
2024-04-17 14:01:17 +03:00
Psayker bec8a5b69f Add size for incoming files 2024-04-16 18:21:42 +03:00
Xavier Del Campo Romero 595902355e 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.

(cherry picked from commit 700708b7e10ce1110528a2b83b854fae3f6be95e)
2024-04-16 18:21:42 +03:00
Konstantin Kuznetsov 3219b5eeb9 Choose audio caps based on the highest rate 2024-04-16 18:12:06 +03:00
Stanislav Malishevskiy 77e23c41e1 Fix issue with sound without webrtc library 2024-04-15 15:30:18 +03:00
Andrei Voronin 0ce6a8619b
Adding brighter icon for online status (#49)
Added two brighter version of status icons and made online status
more recognasible.
2024-04-11 18:05:57 +03:00
Andrei Voronin ffacf70a4f
Display user status online/offline with circles (#40)
Adds a display of the user's offline/online status through red and green circles, respectively.
2024-04-09 15:04:52 +03:00
Maxim Logaev 02759c59f7 meson: Hide console window for Windows
Signed-off-by: Maxim Logaev <maxlogaev@proton.me>
2024-04-09 14:43:01 +03:00
Igor Sharonov c1d2e3647b meson: Support all tests from cmake
Unified test launch for both meson and cmake: make/ninja test.
Support tests for cmake through ctest.
Enable jid test in libdino.
Enable tests for win64 CI/CD.
2024-04-09 13:20:11 +03:00
Igor Sharonov a74f2d0c82 meson: Add phone-ringer plugin 2024-04-08 16:19:59 +03:00
Linux in a Bit b6bb5b3dda Change message padding/margins
3px padding on top and bottom of all messages
10px margin on top of messages with usernames
This improves readability among other things.
2024-04-05 17:09:06 +03:00
Igor Sharonov c5587fe3b3 meson: Fix plugindir search path 2024-04-05 17:08:34 +03:00
Maxim Logaev ff713338e0 Fixed show Dino+ version in about window
Signed-off-by: Maxim Logaev <maxlogaev@proton.me>
2024-04-04 16:51:39 +03:00
Maxim Logaev efe4e439d1 Fixed set license in about window
Signed-off-by: Maxim Logaev <maxlogaev@proton.me>
2024-04-04 16:50:59 +03:00
Maxim Logaev 9d793b90e2 Added Dino with windows console
Signed-off-by: Maxim Logaev <maxlogaev@proton.me>
2024-04-04 12:52:16 +03:00
Igor Sharonov 583a381c74 Support libsoup-2.4 in meson 2024-04-02 18:09:01 +03:00
Konstantin Kuznetsov 17c451652d Allow creating new bookmark when there are no existing bookmarks
This commit removes early return from the set_autojoin function
to allow creating a new bookmark (with add_conference function).
2024-04-02 18:07:38 +03:00
Maxim Logaev a41e499bf0 Added meson build to Windows CI
Signed-off-by: Maxim Logaev <maxlogaev@proton.me>
2024-04-01 19:26:50 +03:00
giantplaceholder fd0b3d65f8
Update installation instructions in README + a few other small changes 2024-04-01 18:55:22 +04:00
eerielili e5302d1d9d Fix message stanza with body changing MUC subject (#1569)
- fixes https://github.com/dino/dino/issues/1542
            - more consistent with
              https://xmpp.org/extensions/xep-0045.html#enter-subject:
                    "Note: In accordance with the core definition of XML stanzas,
                    any message can contain a <subject/> element; only a message that
                    contains a <subject/> but no <body/> element shall be considered a
                    subject change for MUC purposes."
2024-04-01 17:48:43 +03:00
Maxim Logaev d6a6bdc546 Now don't use broken gh-describe
Signed-off-by: Maxim Logaev <maxlogaev@proton.me>
2024-04-01 17:29:17 +03:00
Vadim Lomovtsev 9afa4ddb72 merge meson & cmake build scripts
Signed-off-by: Vadim Lomovtsev <jelezny@gmail.com>
2024-04-01 16:38:27 +03:00
Vadim Lomovtsev 4912be6cff plugins/windows-notification: add meson build support
Signed-off-by: Vadim Lomovtsev <jelezny@gmail.com>
2024-04-01 16:38:27 +03:00
Vadim Lomovtsev 66403012dc add win32-fonts to meson-based windows build
This commit is to enable plugin to be build with meson for Windows
(mingw64) build.

Signed-off-by: Vadim Lomovtsev <jelezny@gmail.com>
2024-04-01 16:38:27 +03:00
Vadim Lomovtsev 799ad11339 put meson-build script for windows (mingw64)
Signed-off-by: Vadim Lomovtsev <jelezny@gmail.com>
2024-04-01 16:38:27 +03:00
Vadim Lomovtsev 81253f28cc project-wide: build & run-time fixes
While using meson some issues were faced with link and application
startup. This commit is to put fixes for the following issues:

- missed conversation_details.css file;
  add conversation_details.css to the main/data/gresources.xml;
- the 'localtime_r' symbol  can't be found while linking application
  add POSIX_C_SOURCES=1 macro definition
- meson configure complains that xmpp-vala package version is not set
  set xmpp-vala version to 0.1
- application startup failures due to unresolved symbols while creating
  initial UI
   fix: put '--export-all-symbols' to the main/meson.build for mingw64 build
- segmentation fault while running app built by meson
  meson.build: add _WIN32 definefor vala compilation
- main/meson.build: add _FILE_OFFSET_BITS definition (sync with cmake
  cfg)
- main/meson.build: compile window resources (fix missed window icon)

Signed-off-by: Vadim Lomovtsev <jelezny@gmail.com>
2024-04-01 16:38:27 +03:00
hrxi e4bd6c1ce4 Allow using OpenSSL instead of GnuTLS
In preparation of Windows support.
2024-04-01 16:38:27 +03:00
hrxi c5704ea56b Add openssl VAPI from vala-extra-apis
f73ba20fa7/openssl.vapi
2024-04-01 16:38:27 +03:00
Konstantin Kuznetsov 7976859639 experimental: display online/offline status for conversation members 2024-03-29 19:13:18 +03:00
Xavier Del Campo Romero f2096694c6 Add send button / Enter key settings
Two new switches have been added to the application preferences:

- Enable send button
- Use Enter to insert newline ('\n')

The latter cannot be active or sensitive if the former is not active.
Otherwise, users would not be able to send messages.

Thanks to horazont for suggesting a separate switch for the behaviour
of the Enter key.
2024-03-29 16:58:50 +03:00
Andrei Voronin bee7dd0ef4
Offline members (#33)
This commit is supposed to change the showing of members of chat.
Beforehead only online members was show in chat members. Now it's all
members who are shown, including the ones who is offline and the one who
didin't accept the invite
2024-03-28 17:50:34 +03:00
Andrei Voronin c0c9e1df14 Added spinner for resynck
Added spinner for resynck. Added stack with button and spinner. After
pushing the button button will be replaced witn spinning spinner
indicating process. Spinner will be replaced with button after receiving
message from server.

!Sometimes spinner will stop to spin, but the process will still be
running. You can start animation again by pressing on the spinner. It is
known issue with gtk spinner
2024-03-28 17:17:54 +03:00
giantplaceholder a23ab3ac6e
Replace screenshot in the README with an up-to-date one 2024-03-27 13:23:42 +04:00
giantplaceholder a9204e0892
Add up-to-date screenshot 2024-03-27 13:22:15 +04:00
Maxim Logaev 9ae30a0607 Added workaround for bug #17
Signed-off-by: Maxim Logaev <maxlogaev@proton.me>
2024-03-26 22:48:52 +03:00
Maxim Logaev 3e352b5a94 Use default encryption only for 1-on-1 chats
Signed-off-by: Maxim Logaev <maxlogaev@proton.me>
2024-03-26 19:59:29 +03:00
Maxim Logaev 47365dd7e0 Encryption dialog not change default encryption
Signed-off-by: Maxim Logaev <maxlogaev@proton.me>
2024-03-26 19:29:09 +03:00
Stanislav Malishevskiy 8e2a459eae Save password after succes change 2024-03-26 18:37:00 +03:00
Maxim Logaev 06496b9720 Added RU translation for default encryption dialog
Signed-off-by: Maxim Logaev <maxlogaev@proton.me>
2024-03-26 18:21:45 +03:00
Maxim Logaev f3be74b328 Fixed default encryption dialog style
Signed-off-by: Maxim Logaev <maxlogaev@proton.me>
2024-03-26 16:31:21 +03:00
Xavier Del Campo Romero 01070d089d Show modal dialog to select default encryption if unknown
Thanks to mbeko for the UX suggestions.
2024-03-26 11:22:56 +03:00
Xavier Del Campo Romero d3a2e52285 Setup default encryption settings 2024-03-26 11:22:56 +03:00
fiaxh 2a7063d992 Remove conversation closing via hover button 2024-03-26 11:22:55 +03:00
fiaxh 209b657133 Introduce conversation menu, add close option 2024-03-26 11:22:55 +03:00
fiaxh 32ed1ce245 Conversation details dialog: Fix runtime critical 2024-03-26 11:22:55 +03:00
Konstantin Kuznetsov b18f49cda3 Skip duplicate invites for active conversations 2024-03-25 17:06:44 +03:00
Konstantin Kuznetsov 3a0d2d2ffe Wait for messages instead of MAM pages 2024-03-22 16:11:31 +03:00
adversary16 ce9d848388 Added flatpak autobuild (#19) 2024-03-22 12:52:36 +03:00
Maxim Logaev aaa7c8ee75 Fixed flatpak build script style
Signed-off-by: Maxim Logaev <maxlogaev@proton.me>
2024-03-22 12:52:36 +03:00
adversary16 2376226650 Fixed flatpak builder script (#13) 2024-03-22 12:52:08 +03:00
giantplaceholder d66d037817 Update hyperlinks 2024-03-19 22:03:36 +03:00
giantplaceholder a337b0efcf Added up-to-date information about the fork (#11)
- Delete README-WIN64.md
- Update README.md
2024-03-19 17:23:48 +03:00
Maxim Logaev 4b5ed0b428 Auto-release from tag
Signed-off-by: Maxim Logaev <maxlogaev@proton.me>
2024-03-19 16:21:54 +03:00
Konstantin Kuznetsov bcac03ef6a Fetch MAM pages when scrolling in chats 2024-03-19 15:34:58 +03:00
Maxim Logaev c9241e220c Added night build
Signed-off-by: Maxim Logaev <maxlogaev@proton.me>
2024-03-19 14:42:34 +03:00
127 changed files with 3727 additions and 931 deletions

View file

@ -12,25 +12,31 @@ jobs:
install: git install: git
- run: git config --global core.autocrlf input - run: git config --global core.autocrlf input
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Build Dino with:
fetch-depth: 0
- name: Install build-dependencies
run: | run: |
msys2 -c './build-win64.sh --prepare' msys2 -c './build-win64.sh --prepare'
msys2 -c './build-win64.sh' - name: Build Dino+ (Meson, without saving)
- name: Build Dino installer run: |
msys2 -c './build-win64.sh -s meson -c -b -t -w'
- name: Build Dino+ (CMake)
run: |
msys2 -c './build-win64.sh -s cmake -c -b -t -i'
- name: Build Dino+ installer
run: | run: |
msys2 -c './build-win64.sh --build-installer' msys2 -c './build-win64.sh --build-installer'
- name: Upload Dino installer - name: Upload Dino+ installer
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: dino-installer name: dino-plus-installer
path: windows-installer/dino-installer.exe path: windows-installer/dino-installer.exe
- name: Release Dino installer - name: Release Dino+ installer
if: github.ref == 'refs/heads/master-night-build' && github.event_name == 'push' if: ${{ github.ref_type == 'tag' }}
uses: svenstaro/upload-release-action@2.9.0 uses: svenstaro/upload-release-action@2.9.0
with: with:
repo_token: ${{ secrets.GITHUB_TOKEN }} repo_token: ${{ secrets.GITHUB_TOKEN }}
file: windows-installer/dino-installer.exe file: windows-installer/dino-installer.exe
asset_name: dino-installer.exe asset_name: dino-plus-installer.exe
tag: ${{ github.ref }} tag: ${{ github.ref }}
overwrite: true release_name: Dino+ ${{ github.ref_name }}
release_name: Dino

View file

@ -7,35 +7,64 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
fetch-depth: 0 fetch-depth: 0
- run: sudo rm /etc/apt/sources.list.d/microsoft-prod.list
- run: sudo apt-get update - run: sudo apt-get update
- run: sudo apt-get remove libunwind-14-dev - run: sudo apt-get remove libunwind-14-dev
- run: sudo apt-get install -y build-essential gettext cmake valac libgee-0.8-dev libsqlite3-dev libgtk-4-dev libnotify-dev libgpgme-dev libsoup2.4-dev libgcrypt20-dev libqrencode-dev libnice-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libsrtp2-dev libwebrtc-audio-processing-dev libadwaita-1-dev libsignal-protocol-c-dev libcanberra-dev - run: sudo apt-get install -y build-essential gettext cmake valac libgee-0.8-dev libsqlite3-dev libgtk-4-dev libnotify-dev libgpgme-dev libsoup2.4-dev libgcrypt20-dev libqrencode-dev libnice-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libsrtp2-dev libadwaita-1-dev libsignal-protocol-c-dev libcanberra-dev
- run: ./configure --with-tests --with-libsignal-in-tree - run: ./configure --release --no-debug --with-tests --enable-plugin=notification-sound --prefix=/usr --without-webrtc
- run: make - run: cmake --build build
- run: build/xmpp-vala-test - run: cmake --build build --target=test
- run: build/omemo-test - name: Build DEB-package
run: cd build && cpack -G DEB
- name: Upload Dino+ DEB-package
uses: actions/upload-artifact@v4
with:
name: dino-plus-deb
path: _packages/dino-plus.deb
- name: Release Dino+ DEB-package
if: ${{ github.ref_type == 'tag' }}
uses: svenstaro/upload-release-action@2.9.0
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: _packages/dino-plus.deb
asset_name: dino-plus.deb
tag: ${{ github.ref }}
release_name: Dino+ ${{ github.ref_name }}
build-meson: build-meson:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
fetch-depth: 0 fetch-depth: 0
- run: sudo rm /etc/apt/sources.list.d/microsoft-prod.list
- run: sudo apt-get update - run: sudo apt-get update
- run: sudo apt-get remove libunwind-14-dev - run: sudo apt-get remove libunwind-14-dev
- run: sudo apt-get install -y build-essential gettext libadwaita-1-dev libcanberra-dev libgcrypt20-dev libgee-0.8-dev libgpgme-dev libgstreamer-plugins-base1.0-dev libgstreamer1.0-dev libgtk-4-dev libnice-dev libnotify-dev libqrencode-dev libsignal-protocol-c-dev libsoup-3.0-dev libsqlite3-dev libsrtp2-dev libwebrtc-audio-processing-dev meson valac - run: sudo apt-get install -y build-essential gettext libadwaita-1-dev libcanberra-dev libgcrypt20-dev libgee-0.8-dev libgpgme-dev libgstreamer-plugins-base1.0-dev libgstreamer1.0-dev libgtk-4-dev libnice-dev libnotify-dev libqrencode-dev libsignal-protocol-c-dev libsoup2.4-dev libsqlite3-dev libsrtp2-dev meson valac
- run: meson setup build - run: meson setup build -Duse-soup2=true -Dplugin-rtp-webrtc-audio-processing=disabled
- run: meson compile -C build - run: meson compile -C build
- run: meson test -C build
build-flatpak: build-flatpak:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
container: container:
image: bilelmoussaoui/flatpak-github-actions:gnome-44 image: bilelmoussaoui/flatpak-github-actions:gnome-44
options: --privileged options: --privileged
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
submodules: true submodules: true
- run: flatpak repair --user
- uses: flatpak/flatpak-github-actions/flatpak-builder@v6.1 - uses: flatpak/flatpak-github-actions/flatpak-builder@v6.1
with: with:
manifest-path: im.dino.Dino.json manifest-path: im.dino.Dino.json
build-bundle: false build-bundle: true
- name: Release Dino+ flatpak
if: ${{ github.ref_type == 'tag' }}
uses: svenstaro/upload-release-action@2.9.0
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: app.flatpak
asset_name: app.flatpak
tag: ${{ github.ref }}
release_name: Dino+ ${{ github.ref_name }}

5
.gitignore vendored
View file

@ -6,7 +6,10 @@ Makefile
.idea .idea
.sqlite3 .sqlite3
gschemas.compiled gschemas.compiled
windows-installer/win64-dist/ windows-installer/win64-*/
*.exe *.exe
*.dll *.dll
*.flatpak
flatpak-dist
.flatpak-builder .flatpak-builder
*.zst

54
BUILD_MACOS.md Normal file
View file

@ -0,0 +1,54 @@
Here are the instructions to anyone whom wants to try the build themselves.
It is assumed that you have `brew` installed, know your way around terminal, and generally understand what you're doing.
## Build
1. Start with an already existing build instructions of the original project:
`https://github.com/dino/dino/wiki/macOS`
2. On step 4, replace the path to the upstream repo with this one:
```
git clone https://github.com/mxlgv/dino
cd dino
```
3. On step 5 run `./configure --with-libsoup3`.
4. Continue the build according to the original instructions.
## Install with Brew
To build Dino using this formula, follow these instructions:
```
brew tap mxlgv/homebrew-dino
brew install mxlgv/homebrew-dino/dino
```
You can start the Dino client installed via homebrew with the following command:
```
./opt/homebrew/Cellar/dino/3/bin/dino
```
You can create a shortcut with a symbolic link pointing to this file, so that you can open it in a more convenient way.
If you encounter an error related to rpath, you need to add `DYLD_LIBRARY_PATH` to the environment variable:
```
export DYLD_LIBRARY_PATH=/opt/homebrew/Cellar/dino/3/lib
```
## Notifications
There is a draft PR which can be used to enable notifications: https://github.com/mxlgv/dino/pull/45
## Start Dino
In order to run Dino, just run in the build folder:
```
./dino
```

View file

@ -10,6 +10,16 @@ else ()
set(PROJECT_VERSION ${VERSION_FULL}) set(PROJECT_VERSION ${VERSION_FULL})
endif () endif ()
include(CTest)
option(PLUGIN_RTP_WEBRTC_AUDIO_PROCESSING "Use WebRTC audio processing" ON)
option(WITH_WASAPI "Use wasapi instead of directsound on windows" ON)
# https://gitlab.kitware.com/cmake/cmake/-/issues/19804
if (WIN32)
list(APPEND CMAKE_FIND_LIBRARY_SUFFIXES .dll)
endif()
# Prepare Plugins # Prepare Plugins
set(DEFAULT_PLUGINS omemo;openpgp;http-files;ice;rtp) set(DEFAULT_PLUGINS omemo;openpgp;http-files;ice;rtp)
if (WIN32) if (WIN32)
@ -71,6 +81,8 @@ macro(set_path what val desc)
endif() endif()
endmacro(set_path) endmacro(set_path)
set(LOCALEDIR_NAME "share/locale")
string(REGEX REPLACE "^liblib" "lib" LIBDIR_NAME "lib${LIB_SUFFIX}") string(REGEX REPLACE "^liblib" "lib" LIBDIR_NAME "lib${LIB_SUFFIX}")
set_path(CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}" "Installation directory for architecture-independent files") set_path(CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}" "Installation directory for architecture-independent files")
set_path(EXEC_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}" "Installation directory for architecture-dependent files") set_path(EXEC_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}" "Installation directory for architecture-dependent files")
@ -84,7 +96,7 @@ set_path(SERVICE_FILE_INSTALL_DIR "${SHARE_INSTALL_PREFIX}/dbus-1/services" "Ins
set_path(ICON_INSTALL_DIR "${SHARE_INSTALL_PREFIX}/icons" "Installation directory for icons") set_path(ICON_INSTALL_DIR "${SHARE_INSTALL_PREFIX}/icons" "Installation directory for icons")
set_path(INCLUDE_INSTALL_DIR "${EXEC_INSTALL_PREFIX}/include" "Installation directory for C header files") set_path(INCLUDE_INSTALL_DIR "${EXEC_INSTALL_PREFIX}/include" "Installation directory for C header files")
set_path(LIB_INSTALL_DIR "${EXEC_INSTALL_PREFIX}/${LIBDIR_NAME}" "Installation directory for object code libraries") set_path(LIB_INSTALL_DIR "${EXEC_INSTALL_PREFIX}/${LIBDIR_NAME}" "Installation directory for object code libraries")
set_path(LOCALE_INSTALL_DIR "${SHARE_INSTALL_PREFIX}/locale" "Installation directory for locale files") set_path(LOCALE_INSTALL_DIR "${EXEC_INSTALL_PREFIX}/${LOCALEDIR_NAME}" "Installation directory for locale files")
set_path(PLUGIN_INSTALL_DIR "${LIB_INSTALL_DIR}/dino/plugins" "Installation directory for dino plugin object code files") set_path(PLUGIN_INSTALL_DIR "${LIB_INSTALL_DIR}/dino/plugins" "Installation directory for dino plugin object code files")
set_path(VAPI_INSTALL_DIR "${SHARE_INSTALL_PREFIX}/vala/vapi" "Installation directory for Vala API files") set_path(VAPI_INSTALL_DIR "${SHARE_INSTALL_PREFIX}/vala/vapi" "Installation directory for Vala API files")
@ -221,6 +233,9 @@ add_subdirectory(main)
add_subdirectory(crypto-vala) add_subdirectory(crypto-vala)
add_subdirectory(plugins) add_subdirectory(plugins)
# To generate a DEB package
include(Packing)
# uninstall target # uninstall target
configure_file("${CMAKE_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in" "${CMAKE_BINARY_DIR}/cmake_uninstall.cmake" IMMEDIATE @ONLY) configure_file("${CMAKE_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in" "${CMAKE_BINARY_DIR}/cmake_uninstall.cmake" IMMEDIATE @ONLY)
add_custom_target(uninstall COMMAND ${CMAKE_COMMAND} -P ${CMAKE_BINARY_DIR}/cmake_uninstall.cmake COMMENT "Uninstall the project...") add_custom_target(uninstall COMMAND ${CMAKE_COMMAND} -P ${CMAKE_BINARY_DIR}/cmake_uninstall.cmake COMMENT "Uninstall the project...")

View file

@ -1,5 +1,6 @@
Dino - Modern Jabber/XMPP Client using GTK+/Vala Dino+, a modern XMPP/Jabber client software based on Dino
Copyright (C) 2016-2020 Dino contributors Copyright (C) 2016-2023 Dino contributors
Copyright (C) 2024 Dino+ contributors
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by

View file

@ -1,56 +0,0 @@
![Dino (WIN64)](https://dino.im/img/readme_header.svg)
=======
![screenshots](https://dino.im/img/screenshot-main.png)
Build on Windows (x86_64)
------------
- Install and configure the [MSYS2](https://www.msys2.org/) package;
- Go to `MINGW64` environment;
- Clone project:
```sh
git clone https://github.com/mxlgv/dino && cd dino
```
- Run the script to install dependencies:
```sh
./build-win64.sh --prepare
```
- Start the build (the builded distribution is available in the `windows-installer/dist-win64` folder):
```sh
./build-win64.sh
```
Note: the build script has some other options, their description can be found using the `--help`.
Build Windows Installer (NSIS)
------------
Before this, you must build the project according to the instructions above. It's worth making sure that `windows-installer/dist-win64` is not empty.
Now you should run:
```sh
./build-win64.sh --build-installer
```
The builded installer will be available in the directory `windows-installer/dino-installer.exe`.
Resources
---------
- Check out the [Dino website](https://dino.im).
- Join our XMPP channel at `chat@dino.im`.
- The [wiki](https://github.com/dino/dino/wiki) provides additional information.
License
-------
Dino - Modern Jabber/XMPP Client using GTK+/Vala
Copyright (C) 2016-2023 Dino contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.

150
README.md
View file

@ -1,37 +1,163 @@
![Dino](https://dino.im/img/readme_header.svg) This is Dino+<br />
A modern XMPP/Jabber client software, based on [Dino](https://github.com/dino/dino/)
======= =======
![screenshots](https://dino.im/img/screenshot-main.png) ![screenshots](dino_plus.png)
Installation Project description
------------ ------------
Have a look at the [prebuilt packages](https://github.com/dino/dino/wiki/Distribution-Packages). Dino+ is a fork of [Dino](https://github.com/dino/dino), a modern XMPP/Jabber client written in Vala using GTK+, which includes a few relatively minor but important quality-of-life features.
Build It was created due to upstream project's maintainers being seemingly unwilling to accept PRs from the community. As of March 2024, there are over 50 PRs pending, most of which are silently ignored.
Some of the features that Dino+ implements:
* Windows support
* Proper sync for chats history, including "scroll-to-load"
* Forced MAM sync for chats
* Password change via GUI
* Optional support for Meson build system
* Some additional QoL-features
What's currently in the works:
* Multiple UI fixes
* Fixes for sound notifications
* MacOS support for Mac silicon
* and more
Dino+ is currently to be considered an alpha-quality product. Please open an issue or send a PR if you spot or fix any bugs.
OS support
------------
* Linux (flatpaks are targeted for Ubuntu 22.04+)
* Windows 10\11
* MacOS via [brew](https://brew.sh/) (very experimental, see below)
Officially, we support only x86_64 architecture. Whilst you may be able to compile this code for ARM-based platforms (like Pinephone or MacOS), there's zero guarantees that it'll compile or function properly, as we don't have the hardware and the time to test it out.
Installation (prebuilt packages & AUR)
------------
Have a look at our [releases](https://github.com/mxlgv/dino/releases).
Windows installer is self-explanatory, and will place a shortcut on your desktop. Ignore the possible warning from Windows Defender: it's caused by the fact that installer does not have a digital signature.
Deb package is built and tested only for Ubuntu 22.04, but it may work on its derivatives as well, such as Pop!_OS or Linux Mint.
Flatpak is not present in Flathub yet, but you can install it manually. The commands below assume that you have "flatpak" package of your distro installed in your system. If it is not, please refer to https://flatpak.org/setup/
1) Download the .flatpak file from [releases](https://github.com/mxlgv/dino/releases)
2) Add default Flatpak repo:
```
flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
```
3) Change the directory to the one you've downloaded the .flatpak to and run (you may have to change the file name in this command):
```
flatpak install ./im.dino.Dino.flatpak
```
4) To launch the program, run either
```
/usr/bin/dino
```
or
```
flatpak run im.dino.Dino
```
Flatpak distribution is confirmed to be working on Arch Testing, Manjaro Stable, Void Linux and Linux Mint.
Arch Linux & Manjaro users may install `dino-plus-git` package from [AUR](https://aur.archlinux.org/packages/dino-plus-git) via any helper of their choice.
Build on Linux
----- -----
Make sure to install all [dependencies](https://github.com/dino/dino/wiki/Build#dependencies). Make sure to install all [dependencies](https://github.com/dino/dino/wiki/Build#dependencies).
./configure ./configure --release --no-debug --with-tests --enable-plugin=notification-sound --prefix=/usr
make make
build/dino build/dino
Additionally, you might need to install `gst-plugins-bad` and `webrtc-audio-processing` packages (or however they might be called in your distro), if you want to have noise cancelling and gain control. If you don't want that or your distro don't have that package, add "--without-webrtc" (without quotes) to the first command.
If you want to use `meson` build system, follow the next instructions:
meson setup build
meson configure --prefix $PWD/build/install --libdir lib build
meson compile -C build
meson install -C build
LD_LIBRARY_PATH+=:$PWD/build/install/lib build/install/bin/dino
If your `nice` library depends on `libsoup-2.4` (consider `ldd` output for the `libnice.so`), you should additionally specify `-Duse-soup2=true` option.
`LD_LIBRARY_PATH` should point to the directory containing the `libdino.so` library.
Skip `meson configure` step, if you want to install the program globally.
You can specify any convenient directory in the option `--prefix` where the program will be installed.
If there is no `webrtcdsp` plugin in your system (check this by calling `gst-inspect-1.0 webrtcdsp`) you should pass extra argument:
* `--without-webrtcdsp` for `./configure`;
* `-Dplugin-rtp-webrtc-audio-processing=disabled` for `meson`;
* `-DPLUGIN_RTP_WEBRTC_AUDIO_PROCESSING=OFF` for `cmake`.
Build on Windows (x86_64)
------------
- Install and configure the [MSYS2](https://www.msys2.org/) package;
- Go to `MINGW64` environment;
- Clone project:
```sh
git clone https://github.com/mxlgv/dino && cd dino
```
- Run the script to install dependencies:
```sh
./build-win64.sh --prepare
```
- Start the build (the builded distribution is available in the `windows-installer/dist-win64` folder):
```sh
./build-win64.sh
```
If you want to use meson build system, please use `-s meson` key as the first argument, i.e.
```sh
bash build-win64.sh -s meson -c -b
```
will do the same as commands above, but using meson.
Note: the build script has some other options, their description can be found using the `--help`.
Build Windows Installer (NSIS)
------------
Before this, you must build the project according to the instructions above. It's worth making sure that `windows-installer/dist-win64` is not empty.
Now you should run:
```sh
./build-win64.sh --build-installer
```
Build on MacOS
------------
Builds for Mac are currently available only as a [brew](https://brew.sh/) formulae. Please follow [these instructions](https://github.com/mxlgv/dino/blob/master/BUILD_MACOS.md) to produce a build.
Please note that Mac support is very experimental, and currently missing support for event notifications, among other things. You have been warned.
Resources Resources
--------- ---------
- Check out the [Dino website](https://dino.im). - Original project's [website](https://dino.im).
- Join our XMPP channel at `chat@dino.im`. - Dino's chat room `chat@dino.im` (please don't post there any issues related to this fork!)
- The [wiki](https://github.com/dino/dino/wiki) provides additional information. - The upstream project's [wiki](https://github.com/dino/dino/wiki) provides additional information.
Contribute Contribute
---------- ----------
- Pull requests are welcome. [These](https://github.com/dino/dino/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) might be good first issues. Please discuss bigger changes in our channel first. - Pull requests are welcome!
- Look at [how to debug](https://github.com/dino/dino/wiki/Debugging) Dino before you report a bug. - Look at [how to debug](https://github.com/dino/dino/wiki/Debugging) Dino before you report a bug.
- Help [translating](https://github.com/dino/dino/wiki/Translations) Dino into your language. - Help [translating](https://github.com/dino/dino/wiki/Translations) Dino into your language.
- Make a [donation](https://dino.im/#donate).
Special thanks
----------
We'd like to thank all of the contributors whom provided the PRs used in this project.
We also recognise the previous efforts of [LAGonauta](https://github.com/LAGonauta) without whom Windows build wouldn't be available.
License License
------- -------
Dino - Modern Jabber/XMPP Client using GTK+/Vala Dino+, a modern XMPP/Jabber client software based on Dino
Copyright (C) 2016-2023 Dino contributors Copyright (C) 2016-2023 Dino contributors
Copyright (C) 2024 Dino+ contributors
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by

85
build-flatpack.sh Normal file → Executable file
View file

@ -1,25 +1,90 @@
#!/bin/bash #!/bin/bash
set -e set -e
getFlatpackDependencies(){ APP_NAME="im.dino.Dino"
DIST_NAME=${DIST_NAME:-"${APP_NAME}.flatpak"}
DIST_DIR="$PWD/flatpak-dist"
BUILD_TEMP_DIR="$DIST_DIR/buildtemp"
BUILD_EXPORT_DIR="$DIST_DIR/export"
msg()
{
echo -e "\e[32m$1\e[0m"
}
fatal()
{
echo -e "\e[31m$1\e[0m"
exit 1
}
get_flatpak_dependencies()
{
msg "Installing Flatpak dependencies..."
flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
flatpak install flathub org.gnome.Sdk//44 flatpak install flathub org.gnome.Sdk//44
flatpak install flathub org.gnome.Platform//44 flatpak install flathub org.gnome.Platform//44
msg "Flatpak dependencies installed"
} }
prepareModules(){ pull_shared_modules()
{
msg "Pulling shared modules..."
git submodule init git submodule init
git submodule update git submodule update
msg "Shared modules successfully pulled"
} }
build(){ prepare()
FP_TEMP_BUILD_DIR=$(mktemp -d) {
FP_OUTDIR="builds" get_flatpak_dependencies
flatpak-builder ${FP_TEMP_BUILD_DIR} im.dino.Dino.json pull_shared_modules
flatpak build-export $FP_OUTDIR $FP_TEMP_BUILD_DIR
flatpak build-bundle $FP_OUTDIR dino.flatpak
} }
getFlatpackDependencies build()
prepareModules {
msg "Build commencing!"
rm -rf $BUILD_TEMP_DIR
flatpak-builder --install-deps-from=flathub $BUILD_TEMP_DIR "${APP_NAME}.json"
flatpak build-export $BUILD_EXPORT_DIR $BUILD_TEMP_DIR
flatpak build-bundle $BUILD_EXPORT_DIR $DIST_NAME $APP_NAME
msg "Flatpack bundle ready and saved to ${DIST_NAME}"
}
clean()
{
msg "Wiping intermediate files..."
rm -rf $BUILD_TEMP_DIR $BUILD_EXPORT_DIR
msg "Cleanup complete!"
}
help()
{
cat << EOF
usage: $0 [OPTION]
--prepare install build dependencies
--build build the project
--clean remove build artifacts
--help show this help
Bundle is saved to ${APP_NAME}.flatpak by default.
Set DIST_NAME variable to customize output file name:
'DIST_NAME=customname.flatpak $0'
Running without parameters is equivalent to running:
'--prepare', '--build' and '--clean'
EOF
}
case $1 in
"--prepare" ) prepare ;;
"--build" ) build ;;
"--help" ) help ;;
"--clean" ) clean;;
"" )
prepare
build build
clean
;;
*) fatal "Unknown argument!"
esac

View file

@ -1,8 +1,12 @@
#!/bin/bash #!/bin/bash
set -e
DIST_DIR="$PWD/windows-installer/win64-dist" set -eu
PROJ_DIR=$PWD
DIST_DIR=${PROJ_DIR}/windows-installer/win64-dist
BUILD_DIR=$PROJ_DIR/build
JOBS=$NUMBER_OF_PROCESSORS JOBS=$NUMBER_OF_PROCESSORS
build_sys='cmake'
msg() msg()
{ {
@ -17,17 +21,31 @@ fatal()
download_yolort() download_yolort()
{ {
file_name=cppwinrt-2.0.210122.3+windows-10.0.19041+yolort-835cd4e.zip file_name=yolort.zip
yolort_dir="$PWD/plugins/windows-notification/yolort" yolort_dir="$PROJ_DIR/plugins/windows-notification/yolort"
rm -rf "$yolort_dir" rm -rf "$yolort_dir"
mkdir "$yolort_dir" mkdir "$yolort_dir"
curl -L -o "$file_name" "https://github.com/LAGonauta/YoloRT/releases/download/v1.0.0/$file_name" curl -L -o "$file_name" "https://github.com/mxlgv/YoloRT/releases/download/dev1/$file_name"
echo "675a6d943c97b4acdbfaa473f68d3241d1798b31a67b5529c8d29fc0176a1707 $file_name" | sha256sum --check --status echo "c2727e390da7e842f66e0a4cf0a9f5d9dfb665115bb554152d98f108d322bbc1 $file_name" | sha256sum --check --status
unzip -o "$file_name" -d "$yolort_dir" unzip -o "$file_name" -d "$yolort_dir"
rm -f "$file_name" rm -f "$file_name"
} }
download_gtk4_git()
{
# FIXME: The bug fix https://gitlab.gnome.org/GNOME/gtk/-/issues/3749 is currently only available in the main branch,
# so GTK4 was builded from it. Needs to be replaced with a package from the MSYS2 repository when the changes get there.
url="https://github.com/mxlgv/mingw-w64-gtk4-git/releases/download/rel1"
gtk_pkg="mingw-w64-x86_64-gtk4-git-4.14.1.r62.gb1eed1c153-1-any.pkg.tar.zst"
gtk_gstreamer_pkg="mingw-w64-x86_64-gtk4-media-gstreamer-git-4.14.1.r62.gb1eed1c153-1-any.pkg.tar.zst"
curl -L -o "$gtk_pkg" "$url/$gtk_pkg"
curl -L -o "$gtk_gstreamer_pkg" "$url/$gtk_gstreamer_pkg"
pacman -U --needed --noconfirm "$gtk_pkg" "$gtk_gstreamer_pkg"
}
prepare() prepare()
{ {
msg "Installing MINGW64 build dependencies" msg "Installing MINGW64 build dependencies"
@ -36,7 +54,6 @@ prepare()
mingw64/mingw-w64-x86_64-gcc \ mingw64/mingw-w64-x86_64-gcc \
mingw64/mingw-w64-x86_64-cmake \ mingw64/mingw-w64-x86_64-cmake \
mingw64/mingw-w64-x86_64-ninja \ mingw64/mingw-w64-x86_64-ninja \
mingw64/mingw-w64-x86_64-gtk4 \
mingw64/mingw-w64-x86_64-libadwaita \ mingw64/mingw-w64-x86_64-libadwaita \
mingw64/mingw-w64-x86_64-sqlite3 \ mingw64/mingw-w64-x86_64-sqlite3 \
mingw64/mingw-w64-x86_64-openssl \ mingw64/mingw-w64-x86_64-openssl \
@ -57,12 +74,15 @@ prepare()
mingw64/mingw-w64-x86_64-nsis \ mingw64/mingw-w64-x86_64-nsis \
mingw64/mingw-w64-x86_64-libsignal-protocol-c \ mingw64/mingw-w64-x86_64-libsignal-protocol-c \
mingw64/mingw-w64-x86_64-icu \ mingw64/mingw-w64-x86_64-icu \
mingw64/mingw-w64-x86_64-webrtc-audio-processing \ mingw64/mingw-w64-x86_64-meson \
git \ git \
make \ make \
unzip \ unzip \
curl curl
msg "Downloading and install git versions of gtk4 and gtk4-media-gstreamer packages"
download_gtk4_git
msg "Successfully installed!" msg "Successfully installed!"
msg "Download YoloRT headers" msg "Download YoloRT headers"
@ -71,60 +91,100 @@ prepare()
} }
configure() configure_cmake()
{ {
msg "Running configuration for Windows" msg "Running configuration for Windows"
./configure --program-prefix="$DIST_DIR" --no-debug --release --disable-fast-vapi --with-libsoup3 ./configure --program-prefix="$DIST_DIR" --no-debug --release --disable-fast-vapi --with-libsoup3 --with-tests
msg "Configured!" msg "Configured!"
} }
build() build_cmake()
{ {
msg "Started building on $JOBS threads" msg "Started building on $JOBS threads"
make -j"$JOBS" make -j"$JOBS"
msg "Successfully builded!" msg "Successfully builded!"
msg "Installing Dino .."
make install
}
test_cmake()
{
msg "Run tests"
make test
}
configure_meson()
{
arg=${1:-"none"}
encr=${2:-"auto"}
local cmd=""
if [ x"${arg}" == x"reconfig" ]; then
cmd=--reconfigure
fi
mkdir -p $BUILD_DIR
meson setup ${cmd} --prefix "$DIST_DIR" \
-D crypto-backend=${encr} \
-D plugin-ice=enabled \
$PROJ_DIR $BUILD_DIR
}
build_meson()
{
meson compile -C $BUILD_DIR
meson install -C $BUILD_DIR
}
test_meson()
{
msg "Run tests"
meson test -C $BUILD_DIR
} }
dist_install() dist_install()
{ {
msg "Installing Dino in '$DIST_DIR'!" _dist_arg=${1:-$DIST_DIR}
make install
msg "Generate dino-with-console.exe"
cp -f "$_dist_arg/bin/dino.exe" "$_dist_arg/bin/dino-with-console.exe"
# IMAGE_SUBSYSTEM_WINDOWS_CUI = 0x0003
# SUBSYSTEM_OFFSET = 0xDC (220)
printf '\x03\x00' | dd of="$_dist_arg/bin/dino-with-console.exe" bs=1 seek=220 count=2 conv=notrunc
msg "Copying MINGW64 dependencies" msg "Copying MINGW64 dependencies"
cp /mingw64/bin/gdbus.exe "$DIST_DIR/bin" cp /mingw64/bin/gdbus.exe "$_dist_arg/bin"
cp /mingw64/bin/gspawn-win64-helper.exe "$DIST_DIR/bin" cp /mingw64/bin/gspawn-win64-helper.exe "$_dist_arg/bin"
cp /mingw64/bin/libcrypto-*-x64.dll "$DIST_DIR/bin/" cp /mingw64/bin/libcrypto-*-x64.dll "$_dist_arg/bin/"
cp -r /mingw64/lib/gstreamer-1.0 "$DIST_DIR/lib" cp -r /mingw64/lib/gstreamer-1.0 "$_dist_arg/lib"
mkdir -p "$DIST_DIR/lib/gdk-pixbuf-2.0/" && cp -r /mingw64/lib/gdk-pixbuf-2.0 "$DIST_DIR/lib/" mkdir -p "$_dist_arg/lib/gdk-pixbuf-2.0/" && cp -r /mingw64/lib/gdk-pixbuf-2.0 "$_dist_arg/lib/"
mkdir -p "$DIST_DIR/lib/gio/" && cp -r /mingw64/lib/gio "$DIST_DIR/lib/" mkdir -p "$_dist_arg/lib/gio/" && cp -r /mingw64/lib/gio "$_dist_arg/lib/"
list=`find "$DIST_DIR" -type f \( -name "*.exe" -o -name "*.dll" \) -exec \ list=`find "$_dist_arg" -type f \( -name "*.exe" -o -name "*.dll" \) -exec \
ntldd -R {} + | \ ntldd -R {} + | \
grep "mingw64" | \ grep "mingw64" | \
cut -f1 -d "=" | sort | uniq` cut -f1 -d "=" | sort | uniq`
for a in $list; do for a in $list; do
cp -fv "/mingw64/bin/$a" "$DIST_DIR/bin/" cp -fv "/mingw64/bin/$a" "$_dist_arg/bin/"
done done
msg "Removing debug information from all EXE and DLL files" msg "Removing debug information from all EXE and DLL files"
find "$DIST_DIR" -iname "*.exe" -exec strip -s {} + find "$_dist_arg" -iname "*.exe" -exec strip -s {} +
find "$DIST_DIR" -iname "*.dll" -exec strip -s {} + find "$_dist_arg" -iname "*.dll" -exec strip -s {} +
find "$DIST_DIR" -iname "*.a" -exec rm {} + find "$_dist_arg" -iname "*.a" -exec rm {} +
msg "Removing redudant header files" msg "Removing redudant header files"
rm -rf "$DIST_DIR/include" rm -rf "$_dist_arg/include"
msg "Copy LICENSE" msg "Copy LICENSE"
cp -f "$PWD/LICENSE" "$DIST_DIR/LICENSE" cp -f "$PWD/LICENSE" "$_dist_arg/LICENSE"
msg "Copy icons, themes, locales and fonts" msg "Copy icons, themes, locales and fonts"
cp -f "$PWD/main/dino.ico" "$DIST_DIR/dino.ico" cp -f "$PWD/main/dino.ico" "$_dist_arg/dino.ico"
cp -rf "/mingw64/share/xml" "$DIST_DIR/share" cp -rf "/mingw64/share/xml" "$_dist_arg/share"
mkdir -p "$DIST_DIR/etc/fonts" && cp -r /mingw64/etc/fonts "$DIST_DIR/etc/" mkdir -p "$_dist_arg/etc/fonts" && cp -r /mingw64/etc/fonts "$_dist_arg/etc/"
mkdir -p "$DIST_DIR/share/icons" && cp -r /mingw64/share/icons "$DIST_DIR/share/" mkdir -p "$_dist_arg/share/icons" && cp -r /mingw64/share/icons "$_dist_arg/share/"
mkdir -p "$DIST_DIR/share/glib-2.0/schemas" && cp -rf /mingw64/share/glib-2.0/schemas "$DIST_DIR/share/glib-2.0/" mkdir -p "$_dist_arg/share/glib-2.0/schemas" && cp -rf /mingw64/share/glib-2.0/schemas "$_dist_arg/share/glib-2.0/"
msg "Successfully installed!" msg "Successfully installed!"
} }
@ -140,24 +200,71 @@ build_installer()
clean() clean()
{ {
rm -rf build "$DIST_DIR" rm -rf $BUILD_DIR $DIST_DIR
msg "Build artifacts removed successfull!" msg "Build artifacts removed successfull!"
} }
help() help()
{ {
cat << EOF cat << EOF
usage: $0 [OPTION] Script to build Dino for windows using cmake or meson build-system.
--prepare install build dependencies By default it will be build using build directory
--configure configure the project $BUILD_DIR
--build build the project and installed to
--dist-install install the builded project $DIST_DIR
--build-installer build installer (using NSIS)
--clean remove build artifacts
--help show this help
Running without parameters is equivalent to running: Usage: $0 [option]
'--configure', '--build' and '--dist-install'
Note: you may set the multiple options, but be sure that they will be
processed sequentially (one-by-one), e.g. command
$0 -s meson -c -b
will run buld config and _after_ that run build using meson, while
$0 -c -b -s meson
will run cmake-based configure & build commands and the -s option
wont have any effect. And the one
$0 -b -s meson -c
is incorrect, as it willtry to run build(for cmake), then configure
with for meson build.
--help, -h
print this help message.
--set-buildsys, -s
set (specify) build system name to be used
possible options are: cmake or meson
--prepare, -p
install build dependencies. may be done once.
--configure, -c
configure build using selected build-system.
--build, -b
invoke build.
--test, -t
run tests.
--reconfig, -r
reconfigure project, if minor changes were
done to build config files but build has been
configured already (only for meson!).
--whipe, -w
remove build artifacts from $BUILD_DIR
--verbose, -v
verbose output enable.
--dist-install, -i
install the builded project along with its'
dependencies.
--build-installer
build installer (using NSIS)
Running without parameters will run configure, build & install
using cmake-based build-system as default one.
EOF EOF
} }
@ -165,18 +272,62 @@ if [[ "$(uname)" != "MINGW64_NT"* ]]; then
fatal "This is not a MINGW64 environment!" fatal "This is not a MINGW64 environment!"
fi fi
# no options provided,simply build with defaults
if [[ $# == 0 ]]; then
prepare
configure_${build_sys}
build_${build_sys}
dist_install
exit 0
fi
while [[ $# > 0 ]];
do
case $1 in case $1 in
"--prepare" ) prepare ;; --prepare|-p)
"--configure" ) configure ;; prepare
"--build" ) build ;; ;;
"--dist-install" ) dist_install ;; --configure|-c)
"--build-installer") build_installer ;; configure_${build_sys}
"--clean" ) clean ;; ;;
"--help" ) help ;; --build|-b)
"" ) build_${build_sys}
configure ;;
build --test|-t)
test_${build_sys}
;;
--reconfig|-r)
configure_${build_sys} reconfig
;;
--whipe|-w)
clean
;;
--dist-install|-i)
dist_install dist_install
;; ;;
*) fatal "Unknown argument!" --verbose|-v)
set -xv
;;
--help|-h)
help
exit 0;
;;
--build-installer)
build_installer
;;
--set-buildsys|-s)
if [ x"$2" != x"cmake" -a x"$2" != x"meson" ]; then
fatal "Improper build system selected: ${2}!"
exit 1;
fi
build_sys=$2
shift
;;
-*)
echo "Unknown option $1"
exit 1
;;
esac esac
shift
done

View file

@ -47,7 +47,7 @@ function(_compute_version_from_git)
return() return()
endif (NOT git_result EQUAL 0) endif (NOT git_result EQUAL 0)
if (git_tag MATCHES "^v?([0-9]+[.]?[0-9]*[.]?[0-9]*)(-[.0-9A-Za-z-]+)?([+][.0-9A-Za-z-]+)?$") if (git_tag MATCHES "^v?([0-9]+[.]?[0-9]*[.]?[0-9]*[.]?[0-9]*)(-[.0-9A-Za-z-]+)?([+][.0-9A-Za-z-]+)?$")
set(VERSION_LAST_RELEASE "${CMAKE_MATCH_1}") set(VERSION_LAST_RELEASE "${CMAKE_MATCH_1}")
else () else ()
return() return()

View file

@ -0,0 +1,11 @@
find_library(GstWebrtcDsp_LIBRARY gstwebrtcdsp PATH_SUFFIXES gstreamer-1.0)
if(GstWebrtcDsp_LIBRARY_FOUND)
find_package(Gst)
set(GstWebrtcDsp_VERSION ${Gst_VERSION})
endif()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(GstWebrtcDsp
REQUIRED_VARS GstWebrtcDsp_LIBRARY
VERSION_VAR GstWebrtcDsp_VERSION)

View file

@ -1,12 +0,0 @@
include(PkgConfigWithFallback)
find_pkg_config_with_fallback(WebRTCAudioProcessing
PKG_CONFIG_NAME webrtc-audio-processing
LIB_NAMES webrtc_audio_processing
INCLUDE_NAMES webrtc/modules/audio_processing/include/audio_processing.h
INCLUDE_DIR_SUFFIXES webrtc-audio-processing webrtc_audio_processing
)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(WebRTCAudioProcessing
REQUIRED_VARS WebRTCAudioProcessing_LIBRARY
VERSION_VAR WebRTCAudioProcessing_VERSION)

34
cmake/Packing.cmake Normal file
View file

@ -0,0 +1,34 @@
# This is a package creation module using CPack.
# Currently only DEB package generation is supported.
set(CPACK_PACKAGE_NAME dino-plus)
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY
"modern XMPP/Jabber client software, based on Dino.
Dino+ is a fork of Dino, a modern XMPP/Jabber client
written in Vala using GTK+, which includes a few
relatively minor but important quality-of-life features.")
set(CPACK_VERBATIM_VARIABLES YES)
set(CPACK_PACKAGE_FILE_NAME ${CPACK_PACKAGE_NAME})
set(CPACK_PACKAGE_INSTALL_DIRECTORY ${CPACK_PACKAGE_NAME})
set(CPACK_OUTPUT_FILE_PREFIX "${CMAKE_SOURCE_DIR}/_packages")
set(CPACK_PACKAGE_VERSION ${VERSION_FULL})
set(CPACK_PACKAGE_HOMEPAGE_URL "https://github.com/mxlgv/dino")
set(CPACK_PACKAGE_CONTACT "maxlogaev@proton.me")
set(CPACK_COMPONENTS_GROUPING ALL_COMPONENTS_IN_ONE)
set(CPACK_STRIP_FILES TRUE)
# For DEB only
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Maxim Logaev <${CPACK_PACKAGE_CONTACT}>")
set(CPACK_DEB_COMPONENT_INSTALL YES)
set(CPACK_DEBIAN_PACKAGE_SECTION "net")
set(CPACK_DEBIAN_PACKAGE_CONFLICTS "dino-im, dino-im-common")
set(CPACK_DEBIAN_PACKAGE_RECOMMENDS "ca-certificates, dbus, fonts-noto-color-emoji, network-manager")
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
set(CPACK_DEBIAN_PACKAGE_GENERATE_SHLIBS ON)
set(CPACK_DEBIAN_PACKAGE_GENERATE_SHLIBS_POLICY ">=")
install(FILES ${CMAKE_SOURCE_DIR}/LICENSE_SHORT DESTINATION ${SHARE_INSTALL_PREFIX}/doc/${CPACK_PACKAGE_NAME} RENAME copyright)
install(FILES ${CMAKE_SOURCE_DIR}/README.md DESTINATION ${SHARE_INSTALL_PREFIX}/doc/${CPACK_PACKAGE_NAME})
include(CPack)

12
configure vendored
View file

@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
OPTS=`getopt -o "h" --long \ OPTS=`getopt -o "h" --long \
help,fetch-only,no-debug,disable-fast-vapi,with-tests,release,with-libsoup3,\ help,fetch-only,no-debug,disable-fast-vapi,with-tests,release,with-libsoup3,without-webrtcdsp,\
enable-plugin:,disable-plugin:,\ enable-plugin:,disable-plugin:,\
prefix:,program-prefix:,exec-prefix:,lib-suffix:,\ prefix:,program-prefix:,exec-prefix:,lib-suffix:,\
bindir:,libdir:,includedir:,datadir:,\ bindir:,libdir:,includedir:,datadir:,\
@ -15,12 +15,13 @@ eval set -- "$OPTS"
PREFIX=${PREFIX:-/usr/local} PREFIX=${PREFIX:-/usr/local}
ENABLED_PLUGINS= ENABLED_PLUGINS=
DISABLED_PLUGINS= DISABLED_PLUGINS=
BUILD_TESTS= BUILD_TESTS=no
BUILD_TYPE=Debug BUILD_TYPE=Debug
DISABLE_FAST_VAPI= DISABLE_FAST_VAPI=
LIB_SUFFIX= LIB_SUFFIX=
NO_DEBUG= NO_DEBUG=
USE_SOUP3= USE_SOUP3=
PLUGIN_RTP_WEBRTC_AUDIO_PROCESSING=yes
EXEC_PREFIX= EXEC_PREFIX=
BINDIR= BINDIR=
@ -54,6 +55,7 @@ Configuration:
--no-debug Build without debug symbols --no-debug Build without debug symbols
--release Configure to build an optimized release version --release Configure to build an optimized release version
--with-libsoup3 Build with libsoup-3.0 --with-libsoup3 Build with libsoup-3.0
--without-webrtcdsp Build without WebRTC audio processing
--with-tests Also build tests. --with-tests Also build tests.
Plugin configuration: Plugin configuration:
@ -109,6 +111,7 @@ while true; do
--valac-flags ) VALACFLAGS="$2"; shift; shift ;; --valac-flags ) VALACFLAGS="$2"; shift; shift ;;
--lib-suffix ) LIB_SUFFIX="$2"; shift; shift ;; --lib-suffix ) LIB_SUFFIX="$2"; shift; shift ;;
--with-libsoup3 ) USE_SOUP3=yes; shift ;; --with-libsoup3 ) USE_SOUP3=yes; shift ;;
--without-webrtcdsp ) PLUGIN_RTP_WEBRTC_AUDIO_PROCESSING=no; shift ;;
--disable-fast-vapi ) DISABLE_FAST_VAPI=yes; shift ;; --disable-fast-vapi ) DISABLE_FAST_VAPI=yes; shift ;;
--no-debug ) NO_DEBUG=yes; shift ;; --no-debug ) NO_DEBUG=yes; shift ;;
--release ) BUILD_TYPE=RelWithDebInfo; shift ;; --release ) BUILD_TYPE=RelWithDebInfo; shift ;;
@ -207,7 +210,7 @@ cmake -G "$cmake_type" \
-DCMAKE_BUILD_TYPE="$BUILD_TYPE" \ -DCMAKE_BUILD_TYPE="$BUILD_TYPE" \
-DENABLED_PLUGINS="$ENABLED_PLUGINS" \ -DENABLED_PLUGINS="$ENABLED_PLUGINS" \
-DDISABLED_PLUGINS="$DISABLED_PLUGINS" \ -DDISABLED_PLUGINS="$DISABLED_PLUGINS" \
-DBUILD_TESTS="$BUILD_TESTS" \ -DBUILD_TESTING="$BUILD_TESTS" \
-DUSE_SOUP3="$USE_SOUP3" \ -DUSE_SOUP3="$USE_SOUP3" \
-DVALA_EXECUTABLE="$VALAC" \ -DVALA_EXECUTABLE="$VALAC" \
-DCMAKE_VALA_FLAGS="$VALACFLAGS" \ -DCMAKE_VALA_FLAGS="$VALACFLAGS" \
@ -219,6 +222,7 @@ cmake -G "$cmake_type" \
-DBIN_INSTALL_DIR="$BINDIR" \ -DBIN_INSTALL_DIR="$BINDIR" \
-DINCLUDE_INSTALL_DIR="$INCLUDEDIR" \ -DINCLUDE_INSTALL_DIR="$INCLUDEDIR" \
-DLIB_INSTALL_DIR="$LIBDIR" \ -DLIB_INSTALL_DIR="$LIBDIR" \
-DPLUGIN_RTP_WEBRTC_AUDIO_PROCESSING="$PLUGIN_RTP_WEBRTC_AUDIO_PROCESSING" \
-Wno-dev \ -Wno-dev \
.. || exit 9 .. || exit 9
@ -238,8 +242,6 @@ default:
@sh -c "cd build; $exec_command" @sh -c "cd build; $exec_command"
distclean: clean uninstall distclean: clean uninstall
test: default
echo "make test not yet supported"
%: %:
@sh -c "cd build; $exec_command \"\$@\"" @sh -c "cd build; $exec_command \"\$@\""
EOF EOF

View file

@ -22,6 +22,8 @@ GENERATE_VAPI
crypto-vala crypto-vala
GENERATE_HEADER GENERATE_HEADER
crypto-vala crypto-vala
DEFINITIONS
GCRYPT
) )
add_custom_target(crypto-vala-vapi add_custom_target(crypto-vala-vapi

View file

@ -1,7 +1,7 @@
dependencies = [ dependencies = [
dep_gio, dep_gio,
dep_glib, dep_glib,
dep_libgcrypt, dep_libgcrypt_or_openssl,
dep_libsrtp2, dep_libsrtp2,
] ]
sources = files( sources = files(
@ -17,6 +17,11 @@ c_args = [
vala_args = [ vala_args = [
'--vapidir', meson.current_source_dir() / 'vapi', '--vapidir', meson.current_source_dir() / 'vapi',
] ]
if crypto_backend == 'openssl'
vala_args += ['--pkg', 'openssl'] # Work around https://github.com/mesonbuild/meson/issues/2103.
elif crypto_backend == 'gnutls'
vala_args += ['-D', 'GCRYPT']
endif
lib_crypto_vala = library('crypto-vala', sources, c_args: c_args, vala_args: vala_args, dependencies: dependencies, version: '0.0', install: true, install_dir: [true, true, true]) lib_crypto_vala = library('crypto-vala', sources, c_args: c_args, vala_args: vala_args, dependencies: dependencies, version: '0.0', install: true, install_dir: [true, true, true])
dep_crypto_vala = declare_dependency(link_with: lib_crypto_vala, include_directories: include_directories('.')) dep_crypto_vala = declare_dependency(link_with: lib_crypto_vala, include_directories: include_directories('.'))

View file

@ -1,14 +1,24 @@
namespace Crypto { namespace Crypto {
public class SymmetricCipher { public class SymmetricCipher {
#if GCRYPT
private GCrypt.Cipher.Cipher cipher; private GCrypt.Cipher.Cipher cipher;
#else
bool is_encryption;
private OpenSSL.EVP.CipherContext? cipher;
#endif
public static bool supports(string algo_name) { public static bool supports(string algo_name) {
#if GCRYPT
GCrypt.Cipher.Algorithm algo; GCrypt.Cipher.Algorithm algo;
GCrypt.Cipher.Mode mode; GCrypt.Cipher.Mode mode;
GCrypt.Cipher.Flag flags; GCrypt.Cipher.Flag flags;
return parse(algo_name, out algo, out mode, out flags); return parse(algo_name, out algo, out mode, out flags);
#else
return algo_name == "AES-GCM";
#endif
} }
#if GCRYPT
private static unowned string mode_to_string(GCrypt.Cipher.Mode mode) { private static unowned string mode_to_string(GCrypt.Cipher.Mode mode) {
switch (mode) { switch (mode) {
case GCrypt.Cipher.Mode.ECB: return "ECB"; case GCrypt.Cipher.Mode.ECB: return "ECB";
@ -95,8 +105,18 @@ public class SymmetricCipher {
return algo.to_string(); return algo.to_string();
} }
} }
#endif
public SymmetricCipher(string algo_name) throws Error { public SymmetricCipher.encryption(string algo_name) throws Error {
this.initialize(algo_name, true);
}
public SymmetricCipher.decryption(string algo_name) throws Error {
this.initialize(algo_name, false);
}
private SymmetricCipher.initialize(string algo_name, bool is_encryption) throws Error {
#if GCRYPT
GCrypt.Cipher.Algorithm algo; GCrypt.Cipher.Algorithm algo;
GCrypt.Cipher.Mode mode; GCrypt.Cipher.Mode mode;
GCrypt.Cipher.Flag flags; GCrypt.Cipher.Flag flags;
@ -105,48 +125,157 @@ public class SymmetricCipher {
} else { } else {
throw new Error.ILLEGAL_ARGUMENTS(@"The algorithm $algo_name is not supported"); throw new Error.ILLEGAL_ARGUMENTS(@"The algorithm $algo_name is not supported");
} }
#else
if (algo_name == "AES-GCM") {
this.openssl(is_encryption);
} else {
throw new Error.ILLEGAL_ARGUMENTS(@"The algorithm $algo_name is not supported");
}
#endif
} }
#if GCRYPT
private SymmetricCipher.gcrypt(GCrypt.Cipher.Algorithm algo, GCrypt.Cipher.Mode mode, GCrypt.Cipher.Flag flags) throws Error { private SymmetricCipher.gcrypt(GCrypt.Cipher.Algorithm algo, GCrypt.Cipher.Mode mode, GCrypt.Cipher.Flag flags) throws Error {
may_throw_gcrypt_error(GCrypt.Cipher.Cipher.open(out this.cipher, algo, mode, flags)); may_throw_gcrypt_error(GCrypt.Cipher.Cipher.open(out this.cipher, algo, mode, flags));
} }
#else
private SymmetricCipher.openssl(bool is_encryption) throws Error {
this.is_encryption = is_encryption;
cipher = new OpenSSL.EVP.CipherContext();
if (is_encryption) {
if (cipher.encrypt_init(OpenSSL.EVP.aes_128_gcm(), null, null, null) != 1) {
openssl_error();
}
} else {
if (cipher.decrypt_init(OpenSSL.EVP.aes_128_gcm(), null, null, null) != 1) {
openssl_error();
}
}
}
#endif
public void set_key(uint8[] key) throws Error { public void set_key(uint8[] key) throws Error {
#if GCRYPT
may_throw_gcrypt_error(cipher.set_key(key)); may_throw_gcrypt_error(cipher.set_key(key));
#else
if (key.length != 16) {
throw new Crypto.Error.ILLEGAL_ARGUMENTS("key length must be 16 for AES-GCM");
}
if (is_encryption) {
if (cipher.encrypt_init(null, null, key, null) != 1) {
openssl_error();
}
} else {
if (cipher.decrypt_init(null, null, key, null) != 1) {
openssl_error();
}
}
#endif
} }
public void set_iv(uint8[] iv) throws Error { public void set_iv(uint8[] iv) throws Error {
#if GCRYPT
may_throw_gcrypt_error(cipher.set_iv(iv)); may_throw_gcrypt_error(cipher.set_iv(iv));
#else
if (iv.length != 12) {
throw new Crypto.Error.ILLEGAL_ARGUMENTS("intialization vector must be of length 16 for AES-GCM");
} }
if (is_encryption) {
public void set_counter_vector(uint8[] ctr) throws Error { if (cipher.encrypt_init(null, null, null, iv) != 1) {
may_throw_gcrypt_error(cipher.set_counter_vector(ctr)); openssl_error();
}
} else {
if (cipher.decrypt_init(null, null, null, iv) != 1) {
openssl_error();
}
}
#endif
} }
public void reset() throws Error { public void reset() throws Error {
#if GCRYPT
may_throw_gcrypt_error(cipher.reset()); may_throw_gcrypt_error(cipher.reset());
#else
throw new Crypto.Error.ILLEGAL_ARGUMENTS("can't reset OpenSSL cipher context");
#endif
} }
public uint8[] get_tag(size_t taglen) throws Error { public uint8[] get_tag(size_t taglen) throws Error {
uint8[] tag = new uint8[taglen]; uint8[] tag = new uint8[taglen];
#if GCRYPT
may_throw_gcrypt_error(cipher.get_tag(tag)); may_throw_gcrypt_error(cipher.get_tag(tag));
#else
if (!is_encryption) {
throw new Crypto.Error.ILLEGAL_ARGUMENTS("can't call get_tag on decryption context");
}
uint8[] empty = new uint8[0];
int empty_len = 0;
if (cipher.encrypt_final(empty, out empty_len) != 1) {
openssl_error();
}
if (empty_len != 0) {
throw new Crypto.Error.ILLEGAL_ARGUMENTS("get_tag called on a stream with remaining data");
}
if (cipher.ctrl(OpenSSL.EVP.CTRL_GCM_GET_TAG, (int)taglen, tag) != 1) {
openssl_error();
}
#endif
return tag; return tag;
} }
public void check_tag(uint8[] tag) throws Error { public void check_tag(uint8[] tag) throws Error {
#if GCRYPT
may_throw_gcrypt_error(cipher.check_tag(tag)); may_throw_gcrypt_error(cipher.check_tag(tag));
#else
if (is_encryption) {
throw new Crypto.Error.ILLEGAL_ARGUMENTS("can't call check_tag on encryption context");
}
if (cipher.ctrl(OpenSSL.EVP.CTRL_GCM_SET_TAG, tag.length, tag) != 1) {
openssl_error();
}
uint8[] empty = new uint8[0];
int empty_len = 0;
if (cipher.decrypt_final(empty, out empty_len) != 1) {
openssl_error();
}
if (empty_len != 0) {
throw new Crypto.Error.ILLEGAL_ARGUMENTS("check_tag called on a stream with remaining data");
}
#endif
} }
public void encrypt(uint8[] output, uint8[] input) throws Error { public void encrypt(uint8[] output, uint8[] input) throws Error {
#if GCRYPT
may_throw_gcrypt_error(cipher.encrypt(output, input)); may_throw_gcrypt_error(cipher.encrypt(output, input));
#else
if (!is_encryption) {
throw new Crypto.Error.ILLEGAL_ARGUMENTS("can't call encrypt on decryption context");
}
int output_length = output.length;
if (cipher.encrypt_update(output, out output_length, input) != 1) {
openssl_error();
}
if (output_length != output.length) {
throw new Crypto.Error.ILLEGAL_ARGUMENTS("invalid output array length");
}
#endif
} }
public void decrypt(uint8[] output, uint8[] input) throws Error { public void decrypt(uint8[] output, uint8[] input) throws Error {
#if GCRYPT
may_throw_gcrypt_error(cipher.decrypt(output, input)); may_throw_gcrypt_error(cipher.decrypt(output, input));
#else
if (is_encryption) {
throw new Crypto.Error.ILLEGAL_ARGUMENTS("can't call decrypt on encryption context");
} }
int output_length = output.length;
public void sync() throws Error { if (cipher.decrypt_update(output, out output_length, input) != 1) {
may_throw_gcrypt_error(cipher.sync()); openssl_error();
}
if (output_length != output.length) {
throw new Crypto.Error.ILLEGAL_ARGUMENTS("invalid output array length");
}
#endif
} }
} }
} }

View file

@ -3,13 +3,20 @@ namespace Crypto {
public errordomain Error { public errordomain Error {
ILLEGAL_ARGUMENTS, ILLEGAL_ARGUMENTS,
GCRYPT, GCRYPT,
OPENSSL,
AUTHENTICATION_FAILED, AUTHENTICATION_FAILED,
UNKNOWN UNKNOWN
} }
#if GCRYPT
internal void may_throw_gcrypt_error(GCrypt.Error e) throws Error { internal void may_throw_gcrypt_error(GCrypt.Error e) throws Error {
if (((int)e) != 0) { if (((int)e) != 0) {
throw new Crypto.Error.GCRYPT(e.to_string()); throw new Crypto.Error.GCRYPT(e.to_string());
} }
} }
#else
internal void openssl_error() throws Error {
throw new Crypto.Error.OPENSSL(OpenSSL.ERR.reason_error_string(OpenSSL.ERR.get_error()));
}
#endif
} }

View file

@ -1,5 +1,9 @@
namespace Crypto { namespace Crypto {
public static void randomize(uint8[] buffer) { public static void randomize(uint8[] buffer) {
#if GCRYPT
GCrypt.Random.randomize(buffer); GCrypt.Random.randomize(buffer);
#else
OpenSSL.RAND.bytes(buffer);
#endif
} }
} }

View file

@ -0,0 +1,822 @@
/* OpenSSL Vala Bindings
* Copyright 2020 Zuhong Tao <taozuhong@gmail>
* Copyright 2016 Guillaume Poirier-Morency <guillaumepoiriermorency@gmail>
* Copyright 1995-2016 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the OpenSSL license (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
* in the file LICENSE in the source distribution or at
* https://www.openssl.org/source/license.html
*/
[CCode (cprefix = "")]
namespace OpenSSL
{
[CCode (cprefix = "AES_", lower_case_cprefix = "AES_", cheader_filename = "openssl/aes.h")]
namespace AES
{
public const int BLOCK_SIZE;
}
[Compact]
[CCode (cname = "ENGINE", lower_case_cprefix = "ENGINE_", cprefix = "ENGINE_", cheader_filename = "openssl/engine.h", free_function = "ENGINE_free")]
public class Engine {
[CCode (cname = "ENGINE_new")]
public Engine ();
[CCode (cname = "ENGINE_by_id")]
public Engine.by_id (string id);
[CCode (cname = "ENGINE_get_default_RSA")]
public Engine.get_default_RSA ();
[CCode (cname = "ENGINE_get_default_DSA")]
public Engine.get_default_DSA ();
[CCode (cname = "ENGINE_get_default_DH")]
public Engine.get_default_DH ();
[CCode (cname = "ENGINE_get_default_RAND")]
public Engine.get_default_RAND ();
[CCode (cname = "ENGINE_get_cipher_engine")]
public Engine.get_cipher_engine (int nid);
[CCode (cname = "ENGINE_get_digest_engine")]
public Engine.get_digest_engine (int nid);
public int init ();
public int finish ();
public int set_default (uint flags);
public int set_default_RSA ();
public int set_default_DSA ();
public int set_default_DH ();
public int set_default_RAND ();
public int set_default_ciphers ();
public int set_default_digests ();
public int set_default_string (string list);
}
[CCode (cprefix = "NID_", cheader_filename = "openssl/objects.h")]
public enum NID
{
sha256
}
[Compact]
[CCode (cname = "BIO_METHOD", cheader_filename = "openssl/bio.h", free_function = "BIO_meth_free")]
public class BIOMethod
{
public const int BIO_TYPE_DESCRIPTOR;
public const int BIO_TYPE_FILTER;
public const int BIO_TYPE_SOURCE_SINK;
public const int BIO_TYPE_NONE;
public const int BIO_TYPE_MEM;
public const int BIO_TYPE_FILE;
public const int BIO_TYPE_FD;
public const int BIO_TYPE_SOCKET;
public const int BIO_TYPE_NULL;
public const int BIO_TYPE_SSL;
public const int BIO_TYPE_MD;
public const int BIO_TYPE_BUFFER;
public const int BIO_TYPE_CIPHER;
public const int BIO_TYPE_BASE64;
public const int BIO_TYPE_CONNECT;
public const int BIO_TYPE_ACCEPT;
public const int BIO_TYPE_NBIO_TEST;
public const int BIO_TYPE_NULL_FILTER;
public const int BIO_TYPE_BIO;
public const int BIO_TYPE_LINEBUFFER;
public const int BIO_TYPE_DGRAM;
public const int BIO_TYPE_ASN1;
public const int BIO_TYPE_COMP;
public const int BIO_TYPE_DGRAM_SCTP;
[CCode (cname = "BIO_meth_new")]
public BIOMethod (int type, string name);
[CCode (cname = "BIO_get_new_index")]
public int get_new_index ();
}
[Compact]
[CCode (lower_case_cprefix = "BUF_MEM_", cheader_filename = "openssl/buffer.h", free_function = "BUF_MEM_free")]
public class Buffer {
[CCode (cname = "BUF_MEM_new")]
public Buffer ();
[CCode (cname = "BUF_MEM_new_ex")]
public Buffer.with_flags ();
public int grow (int len);
public size_t grow_clean (size_t len);
}
[Compact]
[CCode (lower_case_cprefix = "BIO_", cheader_filename = "openssl/bio.h", free_function = "BIO_free")]
public class BIO
{
public const int NOCLOSE;
public static unowned BIOMethod s_mem ();
public static unowned BIOMethod s_secmem ();
public BIO (BIOMethod type);
[CCode (cname = "BIO_new_file")]
public BIO.with_file (string filename, string mode);
[CCode (cname = "BIO_new_fp")]
public BIO.with_stream (GLib.FileStream stream, int flags);
[CCode (cname = "BIO_new_mem_buf")]
public BIO.with_buffer (uint8[] buf);
public int read_filename (string name);
public int write_filename (string name);
public int append_filename (string name);
public int rw_filename (string name);
public int set_mem_eof_return (int v);
public long get_mem_data ([CCode (array_length = false)] out uint8[] pp);
public int set_mem_buf (Buffer bm, int c);
public int get_mem_ptr (out Buffer pp);
public int set (BIOMethod type);
public int read (uint8[] data);
public int write (uint8[] data);
[PrintfFormat]
public int printf (string format, ...);
[PrintfFormat]
public int vprintf (string format, va_list args);
[PrintfFormat]
public static int snprintf (uint8[] buf, string format, ...);
[PrintfFormat]
public static int vsnprintf (uint8[] buf, string format, va_list args);
public int reset ();
public int seek (int ofs);
public int pending ();
public int wpending ();
public int flush ();
public int eof ();
public int tell ();
public int set_close (long flag);
public int get_close ();
public long ctrl (int cmd, long larg, [CCode (array_length = false)] uint8[] parg);
public int read_ex (uint8[] data, out size_t readbytes);
public int write_ex (uint8[] data, out size_t written);
}
[CCode (lower_case_cprefix = "CRYPTO_", cheader_filename = "openssl/crypto.h")]
namespace Crypto
{
public int memcmp (void* v1, void* v2, size_t n);
}
[Compact]
[CCode (cname = "ASN1_PCTX", lower_case_cprefix = "ASN1_PCTX_", free_function = "ASN1_PCTX_free")]
public class ASN1_PCTX {
public ASN1_PCTX ();
public ulong get_flags ();
public void set_flags (ulong flags);
public ulong get_nm_flags ();
public void set_nm_flags (ulong flags);
public ulong get_cert_flags ();
public void set_cert_flags (ulong flags);
public ulong get_oid_flags ();
public void set_oid_flags (ulong flags);
public ulong get_str_flags ();
public void set_str_flags (ulong flags);
}
[Compact]
[CCode (cname = "ASN1_SCTX", lower_case_cprefix = "ASN1_SCTX_", free_function = "ASN1_SCTX_free")]
public class ASN1_SCTX {
public ASN1_SCTX ();
public ulong get_flags ();
}
[CCode (cprefix = "EVP_", lower_case_cprefix = "EVP_", cheader_filename = "openssl/evp.h")]
namespace EVP
{
public const int CIPH_STREAM_CIPHER;
public const int CIPH_ECB_MODE;
public const int CIPH_CBC_MODE;
public const int CIPH_CFB_MODE;
public const int CIPH_OFB_MODE;
public const int CIPH_CTR_MODE;
public const int CIPH_GCM_MODE;
public const int CIPH_CCM_MODE;
public const int CIPH_XTS_MODE;
public const int CIPH_WRAP_MODE;
public const int CIPH_OCB_MODE;
public const int CIPH_MODE;
public const int CIPH_VARIABLE_LENGTH;
public const int CIPH_CUSTOM_IV;
public const int CIPH_ALWAYS_CALL_INIT;
public const int CIPH_CTRL_INIT;
public const int CIPH_CUSTOM_KEY_LENGTH;
public const int CIPH_NO_PADDING;
public const int CIPH_RAND_KEY;
public const int CIPH_CUSTOM_COPY;
public const int CIPH_CUSTOM_IV_LENGTH;
public const int CIPH_FLAG_DEFAULT_ASN1;
public const int CIPH_FLAG_LENGTH_BITS;
public const int CIPH_FLAG_FIPS;
public const int CIPH_FLAG_NON_FIPS_ALLOW;
public const int CIPH_FLAG_CUSTOM_CIPHER;
public const int CIPH_FLAG_AEAD_CIPHER;
public const int CIPH_FLAG_TLS1_1_MULTIBLOCK;
public const int CIPH_FLAG_PIPELINE;
public const int CTRL_INIT;
public const int CTRL_SET_KEY_LENGTH;
public const int CTRL_GET_RC2_KEY_BITS;
public const int CTRL_SET_RC2_KEY_BITS;
public const int CTRL_GET_RC5_ROUNDS;
public const int CTRL_SET_RC5_ROUNDS;
public const int CTRL_RAND_KEY;
public const int CTRL_PBE_PRF_NID;
public const int CTRL_COPY;
public const int CTRL_AEAD_SET_IVLEN;
public const int CTRL_AEAD_GET_TAG;
public const int CTRL_AEAD_SET_TAG;
public const int CTRL_AEAD_SET_IV_FIXED;
public const int CTRL_GCM_SET_IVLEN;
public const int CTRL_GCM_GET_TAG;
public const int CTRL_GCM_SET_TAG;
public const int CTRL_GCM_SET_IV_FIXED;
public const int CTRL_GCM_IV_GEN;
public const int CTRL_CCM_SET_IVLEN;
public const int CTRL_CCM_GET_TAG;
public const int CTRL_CCM_SET_TAG;
public const int CTRL_CCM_SET_IV_FIXED;
public const int CTRL_CCM_SET_L;
public const int CTRL_CCM_SET_MSGLEN;
public const int CTRL_AEAD_TLS1_AAD;
public const int CTRL_AEAD_SET_MAC_KEY;
public const int CTRL_GCM_SET_IV_INV;
public const int CTRL_TLS1_1_MULTIBLOCK_AAD;
public const int CTRL_TLS1_1_MULTIBLOCK_ENCRYPT;
public const int CTRL_TLS1_1_MULTIBLOCK_DECRYPT;
public const int CTRL_TLS1_1_MULTIBLOCK_MAX_BUFSIZE;
public const int CTRL_SSL3_MASTER_SECRET;
public const int CTRL_SET_SBOX;
public const int CTRL_SBOX_USED;
public const int CTRL_KEY_MESH;
public const int CTRL_BLOCK_PADDING_MODE;
public const int CTRL_SET_PIPELINE_OUTPUT_BUFS;
public const int CTRL_SET_PIPELINE_INPUT_BUFS;
public const int CTRL_SET_PIPELINE_INPUT_LENS;
public const int CTRL_GET_IVLEN;
[CCode (cprefix = "EVP_PADDING_")]
public enum Padding {
PKCS7,
ISO7816_4,
ANSI923,
ISO10126,
ZERO,
}
[CCode (cprefix = "EVP_PKEY_OP_")]
public enum PublicKeyOperation {
UNDEFINED,
PARAMGEN,
KEYGEN,
SIGN,
VERIFY,
VERIFYRECOVER,
SIGNCTX,
VERIFYCTX,
ENCRYPT,
DECRYPT,
DERIVE,
}
public const int PKEY_OP_TYPE_SIG;
public const int PKEY_OP_TYPE_CRYPT;
public const int PKEY_OP_TYPE_NOGEN;
public const int PKEY_OP_TYPE_GEN;
[CCode (cprefix = "EVP_PKEY_CTRL_")]
public enum PublicKeyControl {
MD,
PEER_KEY,
PKCS7_ENCRYPT,
PKCS7_DECRYPT,
PKCS7_SIGN,
SET_MAC_KEY,
DIGESTINIT,
SET_IV,
CMS_ENCRYPT,
CMS_DECRYPT,
CMS_SIGN,
CIPHER,
GET_MD,
SET_DIGEST_SIZE,
}
[CCode (cprefix = "EVP_PKEY_CTRL_", cheader_filename = "openssl/rsa.h")]
public enum PublicKeyRsaControl {
RSA_PADDING,
RSA_PSS_SALTLEN,
RSA_KEYGEN_BITS,
RSA_KEYGEN_PUBEXP,
RSA_MGF1_MD,
GET_RSA_PADDING,
GET_RSA_PSS_SALTLEN,
GET_RSA_MGF1_MD,
RSA_OAEP_MD,
RSA_OAEP_LABEL,
GET_RSA_OAEP_MD,
GET_RSA_OAEP_LABEL,
RSA_KEYGEN_PRIMES,
}
[Compact]
[CCode (cname = "EVP_PKEY", lower_case_cprefix = "EVP_PKEY_", cprefix = "EVP_PKEY_", free_function = "EVP_PKEY_free")]
public class PublicKey {
public const int NONE;
public const int RSA;
public const int RSA2;
public const int RSA_PSS;
public const int DSA;
public const int DSA1;
public const int DSA2;
public const int DSA3;
public const int DSA4;
public const int DH;
public const int DHX;
public const int EC;
public const int SM2;
public const int HMAC;
public const int CMAC;
public const int SCRYPT;
public const int TLS1_PRF;
public const int HKDF;
public const int POLY1305;
public const int SIPHASH;
public const int X25519;
public const int ED25519;
public const int X448;
public const int ED448;
public PublicKey ();
[CCode (cname = "EVP_PKEY_new_raw_private_key")]
public PublicKey.raw_private_key (int type, Engine? e, uint8[] key);
[CCode (cname = "EVP_PKEY_new_raw_public_key")]
public PublicKey.raw_public_key (int type, Engine? e, uint8[] key);
[CCode (cname = "EVP_PKEY_new_CMAC_key")]
public PublicKey.CMAC_key (Engine? e, uint8[] priv, Cipher? cipher);
[CCode (cname = "EVP_PKEY_new_mac_key")]
public PublicKey.mac_key (int type, Engine? e, uint8[] key);
public int id ();
public int size ();
public int base_id ();
public static int type (int type);
public int set_alias_type (int type);
public int up_ref ();
public RSA? get1_RSA ();
public RSA? get0_RSA ();
public int set1_RSA (RSA? key);
public int assign_RSA (RSA? key);
public int security_bits ();
[CCode (instance_pos = 1.1)]
public int print_public (BIO out, int indent, ASN1_PCTX? pctx);
[CCode (instance_pos = 1.1)]
public int print_private (BIO out, int indent, ASN1_PCTX? pctx);
public Engine? get0_engine ();
public int set1_engine (Engine? engine);
public int get_raw_private_key ([CCode (array_length = false)] uint8[] priv, out size_t len);
public int get_raw_public_key ([CCode (array_length = false)] uint8[] pub, out size_t len);
}
[Compact]
[CCode (cname = "EVP_PKEY_CTX", lower_case_cprefix = "EVP_PKEY_CTX_", cprefix = "EVP_PKEY_CTX_", free_function = "EVP_PKEY_CTX_free")]
public class PublicKeyContext {
public PublicKeyContext (PublicKey pkey, Engine? e);
public PublicKeyContext.id (int id, Engine? e);
public PublicKeyContext dup ();
public int ctrl_str (string type, string value);
public int ctrl_uint64(int keytype, int optype, int cmd, uint64 value);
public int ctrl (int keytype, int optype, int cmd, int p1, [CCode (array_length = false)] uint8[] p2);
[CCode (cname = "EVP_PKEY_CTX_set_rsa_padding", cheader_filename = "openssl/rsa.h")]
public int set_rsa_padding (int pad);
[CCode (cname = "EVP_PKEY_CTX_get_rsa_padding", cheader_filename = "openssl/rsa.h")]
public int get_rsa_padding (out int pad);
[CCode (cname = "EVP_PKEY_CTX_set_rsa_pss_saltlen", cheader_filename = "openssl/rsa.h")]
public int set_rsa_pss_saltlen (int len);
[CCode (cname = "EVP_PKEY_CTX_get_rsa_pss_saltlen", cheader_filename = "openssl/rsa.h")]
public int get_rsa_pss_saltlen (out int len);
[CCode (cname = "EVP_PKEY_CTX_set_rsa_keygen_bits", cheader_filename = "openssl/rsa.h")]
public int set_rsa_keygen_bits (int mbits);
[CCode (cname = "EVP_PKEY_CTX_set_rsa_keygen_pubexp", cheader_filename = "openssl/rsa.h")]
public int set_rsa_keygen_pubexp (BIGNUM pubexp);
[CCode (cname = "EVP_PKEY_CTX_set_rsa_keygen_primes", cheader_filename = "openssl/rsa.h")]
public int set_rsa_keygen_primes (int primes);
public int md (int optype, int cmd, string md);
public int set_signature_md (MessageDigest md);
public int get_signature_md (out MessageDigest pmd);
public int set_mac_key (uint8[] key);
[CCode (cname = "EVP_PKEY_keygen_init")]
public int keygen_init ();
[CCode (cname = "EVP_PKEY_keygen")]
public int keygen (out PublicKey ppkey);
[CCode (cname = "EVP_PKEY_paramgen_init")]
public int paramgen_init ();
[CCode (cname = "EVP_PKEY_paramgen")]
public int paramgen (out PublicKey ppkey);
[CCode (cname = "EVP_PKEY_encrypt_init")]
public int encrypt_init ();
[CCode (cname = "EVP_PKEY_encrypt")]
public int encrypt ([CCode (array_length = false)] uint8[] out, out size_t outlen, uint8[] in);
[CCode (cname = "EVP_PKEY_decrypt_init")]
public int decrypt_init ();
[CCode (cname = "EVP_PKEY_decrypt")]
public int decrypt ([CCode (array_length = false)] uint8[] out, out size_t outlen, uint8[] in);
[CCode (cname = "EVP_PKEY_derive_init")]
public int derive_init ();
[CCode (cname = "EVP_PKEY_derive_set_peer")]
public int derive_set_peer (PublicKey peer);
[CCode (cname = "EVP_PKEY_derive")]
public int derive ([CCode (array_length = false)] uint8[] key, out size_t keylen);
[CCode (cname = "EVP_PKEY_sign_init")]
public int sign_init ();
[CCode (cname = "EVP_PKEY_sign")]
public int sign ([CCode (array_length = false)] uint8[] sig, out size_t siglen, uint8[] tbs);
[CCode (cname = "EVP_PKEY_verify_init")]
public int verify_init ();
[CCode (cname = "EVP_PKEY_verify")]
public int verify (uint8[] sig, uint8[] tbs);
[CCode (cname = "EVP_PKEY_verify_recover_init")]
public int verify_recover_init ();
[CCode (cname = "EVP_PKEY_verify_recover")]
public int verify_recover ([CCode (array_length = false)] uint8[] rout, out size_t routlen, uint8[] sig);
}
[Compact]
[CCode (cname = "EVP_MD")]
public class MessageDigest
{
}
public unowned MessageDigest? md_null ();
public unowned MessageDigest? md2 ();
public unowned MessageDigest? md4 ();
public unowned MessageDigest? md5 ();
public unowned MessageDigest? md5_sha1 ();
public unowned MessageDigest? blake2b512 ();
public unowned MessageDigest? blake2s256 ();
public unowned MessageDigest? sha1 ();
public unowned MessageDigest? sha224 ();
public unowned MessageDigest? sha256 ();
public unowned MessageDigest? sha384 ();
public unowned MessageDigest? sha512 ();
public unowned MessageDigest? mdc2 ();
public unowned MessageDigest? ripmed160 ();
public unowned MessageDigest? whirlpool ();
[CCode (cname = "get_digestbyname")]
public unowned MessageDigest? get_digest_by_name (string name);
[Compact]
[CCode (cname = "EVP_MD_CTX", lower_case_cprefix = "EVP_MD_CTX_")]
public class MessageDigestContext
{
public MessageDigestContext ();
[CCode (cname = "EVP_DigestInit_ex")]
public int init (MessageDigest type, Engine? engine);
[CCode (cname = "EVP_DigestUpdate")]
public int update (uint8[] d);
[CCode (cname = "EVP_DigestFinal_ex")]
public int final ([CCode (array_length = false)] uchar[] md, out int s);
}
[Compact]
[CCode (cname = "EVP_CIPHER", lower_case_cprefix = "EVP_CIPHER_")]
public class Cipher
{
[CCode (cname = "EVP_CIPHER_meth_new")]
public Cipher (int cipher_type, int block_size, int key_len);
public int key_length ();
public int iv_length ();
}
public unowned Cipher? enc_null ();
public unowned Cipher? des_ecb ();
public unowned Cipher? des_ede ();
public unowned Cipher? des_ede3 ();
public unowned Cipher? des_ede_ecb ();
public unowned Cipher? des_ede3_ecb ();
public unowned Cipher? des_cfb64 ();
public unowned Cipher? des_cfb1 ();
public unowned Cipher? des_cfb8 ();
public unowned Cipher? des_ede_cfb64 ();
public unowned Cipher? des_ede3_cfb64 ();
public unowned Cipher? des_ede3_cfb1 ();
public unowned Cipher? des_ede3_cfb8 ();
public unowned Cipher? des_ofb ();
public unowned Cipher? des_ede_ofb ();
public unowned Cipher? des_ede3_ofb ();
public unowned Cipher? des_cbc ();
public unowned Cipher? des_ede_cbc ();
public unowned Cipher? des_ede3_cbc ();
public unowned Cipher? desx_cbc ();
public unowned Cipher? des_ede3_wrap ();
public unowned Cipher? rc4 ();
public unowned Cipher? rc4_40 ();
public unowned Cipher? rc4_hmac_md5 ();
public unowned Cipher? idea_ecb ();
public unowned Cipher? idea_cfb64 ();
public unowned Cipher? idea_ofb ();
public unowned Cipher? idea_cbc ();
public unowned Cipher? rc2_ecb ();
public unowned Cipher? rc2_cbc ();
public unowned Cipher? rc2_40_cbc ();
public unowned Cipher? rc2_64_cbc ();
public unowned Cipher? rc2_cfb64 ();
public unowned Cipher? rc2_ofb ();
public unowned Cipher? bf_ecb ();
public unowned Cipher? bf_cbc ();
public unowned Cipher? bf_cfb64 ();
public unowned Cipher? bf_ofb ();
public unowned Cipher? cast5_ecb ();
public unowned Cipher? cast5_cbc ();
public unowned Cipher? cast5_cfb64 ();
public unowned Cipher? cast5_ofb ();
public unowned Cipher? rc5_32_12_16_cbc ();
public unowned Cipher? rc5_32_12_16_ecb ();
public unowned Cipher? rc5_32_12_16_cfb64 ();
public unowned Cipher? rc5_32_12_16_ofb ();
public unowned Cipher? aes_128_ecb ();
public unowned Cipher? aes_128_cbc ();
public unowned Cipher? aes_128_cfb1 ();
public unowned Cipher? aes_128_cfb8 ();
public unowned Cipher? aes_128_cfb128 ();
public unowned Cipher? aes_128_ofb ();
public unowned Cipher? aes_128_ctr ();
public unowned Cipher? aes_128_ccm ();
public unowned Cipher? aes_128_gcm ();
public unowned Cipher? aes_128_xts ();
public unowned Cipher? aes_128_wrap ();
public unowned Cipher? aes_128_wrap_pad ();
public unowned Cipher? aes_128_ocb ();
public unowned Cipher? aes_192_ecb ();
public unowned Cipher? aes_192_cbc ();
public unowned Cipher? aes_192_cfb1 ();
public unowned Cipher? aes_192_cfb8 ();
public unowned Cipher? aes_192_cfb128 ();
public unowned Cipher? aes_192_ofb ();
public unowned Cipher? aes_192_ctr ();
public unowned Cipher? aes_192_ccm ();
public unowned Cipher? aes_192_gcm ();
public unowned Cipher? aes_192_wrap ();
public unowned Cipher? aes_192_wrap_pad ();
public unowned Cipher? aes_192_ocb ();
public unowned Cipher? aes_256_ecb ();
public unowned Cipher? aes_256_cbc ();
public unowned Cipher? aes_256_cfb1 ();
public unowned Cipher? aes_256_cfb8 ();
public unowned Cipher? aes_256_cfb128 ();
public unowned Cipher? aes_256_ofb ();
public unowned Cipher? aes_256_ctr ();
public unowned Cipher? aes_256_ccm ();
public unowned Cipher? aes_256_gcm ();
public unowned Cipher? aes_256_xts ();
public unowned Cipher? aes_256_wrap ();
public unowned Cipher? aes_256_wrap_pad ();
public unowned Cipher? aes_256_ocb ();
public unowned Cipher? aes_128_cbc_hmac_sha1 ();
public unowned Cipher? aes_256_cbc_hmac_sha1 ();
public unowned Cipher? aes_128_cbc_hmac_sha256 ();
public unowned Cipher? aes_256_cbc_hmac_sha256 ();
public unowned Cipher? camellia_128_ecb ();
public unowned Cipher? camellia_128_cbc ();
public unowned Cipher? camellia_128_cfb1 ();
public unowned Cipher? camellia_128_cfb8 ();
public unowned Cipher? camellia_128_cfb128 ();
public unowned Cipher? camellia_128_ofb ();
public unowned Cipher? camellia_128_ctr ();
public unowned Cipher? camellia_192_ecb ();
public unowned Cipher? camellia_192_cbc ();
public unowned Cipher? camellia_192_cfb1 ();
public unowned Cipher? camellia_192_cfb8 ();
public unowned Cipher? camellia_192_cfb128 ();
public unowned Cipher? camellia_192_ofb ();
public unowned Cipher? camellia_192_ctr ();
public unowned Cipher? camellia_256_ecb ();
public unowned Cipher? camellia_256_cbc ();
public unowned Cipher? camellia_256_cfb1 ();
public unowned Cipher? camellia_256_cfb8 ();
public unowned Cipher? camellia_256_cfb128 ();
public unowned Cipher? camellia_256_ofb ();
public unowned Cipher? camellia_256_ctr ();
public unowned Cipher? chacha20 ();
public unowned Cipher? chacha20_poly1305 ();
public unowned Cipher? seed_ecb ();
public unowned Cipher? seed_cbc ();
public unowned Cipher? seed_cfb128 ();
public unowned Cipher? seed_ofb ();
[CCode (cname = "EVP_get_cipherbyname")]
public unowned Cipher? get_cipher_by_name (string name);
[CCode (cname = "EVP_BytesToKey")]
public int bytes_to_key (Cipher cipher, MessageDigest md, [CCode (array_length = false)] uchar[] salt, uchar[] key_data, int nrounds, [CCode (array_length = false)] uchar[] key, [CCode (array_length = false)] uchar[] iv);
[Compact]
[CCode (cname = "EVP_CIPHER_CTX", cprefix = "EVP_CIPHER_CTX_", lower_case_cprefix = "EVP_CIPHER_CTX_")]
public class CipherContext
{
public CipherContext ();
public int reset ();
public int set_key_length (int keylen);
public int set_padding (int pad);
[CCode (cname = "EVP_EncryptInit_ex")]
public int encrypt_init (Cipher? cipher, Engine? engine, [CCode (array_length = false)] uchar[]? key, [CCode (array_length = false)] uchar[]? iv);
[CCode (cname = "EVP_EncryptUpdate")]
public int encrypt_update ([CCode (array_length = false)] uchar[] ciphertext, out int ciphertext_len, uchar[] plaintext);
[CCode (cname = "EVP_EncryptFinal_ex")]
public int encrypt_final ([CCode (array_length = false)] uchar[] ciphertext, out int ciphertext_len);
[CCode (cname = "EVP_DecryptInit_ex")]
public int decrypt_init (Cipher? cipher, Engine? engine, [CCode (array_length = false)] uchar[]? key, [CCode (array_length = false)] uchar[]? iv);
[CCode (cname = "EVP_DecryptUpdate")]
public int decrypt_update ([CCode (array_length = false)] uchar[] plaintext, out int plaintext_len, uchar[] ciphertext);
[CCode (cname = "EVP_DecryptFinal_ex")]
public int decrypt_final ([CCode (array_length = false)] uchar[] plaintext, out int plaintext_len);
[CCode (simple_generics = true)]
public int ctrl<T>(int type, int arg, T? ptr);
}
}
[Compact]
[CCode (cname = "BIGNUM", cheader_filename = "openssl/rsa.h", free_function = "BN_free")]
public class BIGNUM {
[CCode (cname = "BN_new", cheader_filename = "openssl/bn.h")]
public BIGNUM ();
[CCode (cname = "BN_secure_new", cheader_filename = "openssl/bn.h")]
public BIGNUM.secure ();
[CCode (cname = "BN_clear", cheader_filename = "openssl/bn.h")]
public void clear ();
[CCode (cname = "BN_set_word", cheader_filename = "openssl/bn.h")]
public int set_word (ulong w);
}
[Compact]
[CCode (cname = "BN_GENCB", cheader_filename = "openssl/rsa.h", free_function = "BN_GENCB_free")]
public class BN_GENCB {
public delegate int BigNumGenCallback (int a, int b, BN_GENCB gcb);
[CCode (cname = "BN_GENCB_new")]
public BN_GENCB ();
[CCode (cname = "BN_GENCB_set")]
public void set (BigNumGenCallback cb, [CCode (array_length = false)] uint8[] cb_arg);
[CCode (cname = "BN_GENCB_call")]
public int call (int a, int b);
}
[Compact]
[CCode (lower_case_cprefix = "RSA_", cprefix = "RSA_", cheader_filename = "openssl/rsa.h", free_function = "RSA_free")]
public class RSA
{
public const int PKCS1_PADDING;
public const int SSLV23_PADDING;
public const int NO_PADDING;
public const int PKCS1_OAEP_PADDING;
public const int X931_PADDING;
public const int PKCS1_PSS_PADDING;
public const int F4;
[CCode (cname = "RSA_3")]
public const int RSA_3;
public RSA ();
public int size ();
public int set0_key (BIGNUM n, BIGNUM e, BIGNUM d);
public void get0_key (out BIGNUM n, out BIGNUM e, out BIGNUM d);
public void clear_flags (int flags);
public int test_flags (int flags);
public void set_flags (int flags);
public Engine get0_engine ();
[CCode (instance_pos = 1.1)]
public int print_fp (GLib.FileStream fp, int offset);
[CCode (instance_pos = 1.1)]
public int print (BIO bp, int offset);
public int generate_key_ex (int bits, BIGNUM e, BN_GENCB? cb = null);
[CCode (instance_pos = 4)]
public bool sign (int type, uint8[] m, [CCode (array_length = false)] uint8[] sigret, out int siglen);
[CCode (instance_pos = 3)]
public int verify (int type, uint8[] m, uint8[] sigbuf);
[CCode (instance_pos = 2.1)]
public int public_encrypt ([CCode (array_length_pos = 0)] uint8[] from, [CCode (array_length = false)] uint8[] to, int padding);
[CCode (instance_pos = 2.1)]
public int private_encrypt ([CCode (array_length_pos = 0)] uint8[] from, [CCode (array_length = false)] uint8[] to, int padding);
[CCode (instance_pos = 2.1)]
public int public_decrypt ([CCode (array_length_pos = 0)] uint8[] from, [CCode (array_length = false)] uint8[] to,int padding);
[CCode (instance_pos = 2.1)]
public int private_decrypt ([CCode (array_length_pos = 0)] uint8[] from, [CCode (array_length = false)] uint8[] to, int padding);
}
[CCode (lower_case_cprefix = "PEM_", cheader_filename = "openssl/pem.h")]
namespace PEM
{
[CCode (cname = "pem_password_cb")]
public delegate int PasswordCallback (uint8[] buf, int flag);
public void read_RSAPrivateKey (GLib.FileStream f, out RSA x, PasswordCallback? cb = null);
public void read_RSAPublicKey (GLib.FileStream f, out RSA x, PasswordCallback? cb = null);
public int write_RSAPrivateKey (GLib.FileStream f, RSA x, EVP.Cipher? enc, uint8[] kstr, PasswordCallback? cb = null);
public int write_RSAPublicKey (GLib.FileStream f, RSA x);
public void read_bio_RSAPublicKey (BIO bp, out RSA x, PasswordCallback? cb = null);
public void read_bio_RSAPrivateKey (BIO bp, out RSA x, PasswordCallback? cb = null);
public bool write_bio_RSAPublicKey (BIO bp, RSA x);
public bool write_bio_RSAPrivateKey (BIO bp, RSA x, EVP.Cipher? enc, uint8[] kstr, PasswordCallback? cb = null);
public void read_bio_PUBKEY (BIO bp, out EVP.PublicKey x, PasswordCallback? cb = null);
public int write_bio_PUBKEY (BIO bp, EVP.PublicKey x);
public void read_PUBKEY (GLib.FileStream fp, out EVP.PublicKey x, PasswordCallback? cb = null);
public int write_PUBKEY (GLib.FileStream fp, EVP.PublicKey x);
public void read_bio_PrivateKey (BIO bp, out EVP.PublicKey x, PasswordCallback? cb = null);
public void read_PrivateKey (GLib.FileStream fp, out EVP.PublicKey x, PasswordCallback? cb = null);
public int write_bio_PrivateKey (BIO bp, EVP.PublicKey x, EVP.Cipher? enc, uint8[] kstr, PasswordCallback? cb = null);
public int write_bio_PrivateKey_traditional (BIO bp, EVP.PublicKey x, EVP.Cipher? enc, uint8[] kstr, PasswordCallback? cb = null);
public int write_PrivateKey (GLib.FileStream fp, EVP.PublicKey x, EVP.Cipher? enc, uint8[] kstr, PasswordCallback? cb = null);
public int write_bio_PKCS8PrivateKey (BIO bp, EVP.PublicKey x, EVP.Cipher? enc, uint8[] kstr, PasswordCallback? cb = null);
public int write_bio_PKCS8PrivateKey_nid (BIO bp, EVP.PublicKey x, int nid, uint8[] kstr, PasswordCallback? cb = null);
public int write_PKCS8PrivateKey (GLib.FileStream fp, EVP.PublicKey x, EVP.Cipher? enc, uint8[] kstr, PasswordCallback? cb = null);
public int write_PKCS8PrivateKey_nid (GLib.FileStream fp, EVP.PublicKey x, int nid, uint8[] kstr, PasswordCallback? cb = null);
}
public RSA? d2i_RSA_PUBKEY (out RSA a, uint8[] ppin);
public RSA? d2i_RSA_PUBKEY_bio (BIO bp, out RSA a);
public RSA? d2i_RSA_PUBKEY_fp (GLib.FileStream fp, out RSA a);
public int i2d_RSA_PUBKEY (RSA rsa, [CCode (array_length = false)] out uint8[] ppout);
public int i2d_RSA_PUBKEY_fp (GLib.FileStream fp, RSA a);
public int i2d_RSA_PUBKEY_bio (BIO bp, RSA a);
[CCode (cprefix = "ERR_", lower_case_cprefix = "ERR_", cheader_filename = "openssl/err.h")]
namespace ERR
{
public ulong get_error();
public unowned string? reason_error_string(ulong e);
}
[CCode (cprefix = "RAND_", lower_case_cprefix = "RAND_", cheader_filename = "openssl/rand.h")]
namespace RAND
{
public int bytes(uint8[] buf);
}
}

BIN
dino_plus.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 KiB

View file

@ -1,7 +1,7 @@
{ {
"id": "im.dino.Dino", "id": "im.dino.Dino",
"runtime": "org.gnome.Platform", "runtime": "org.gnome.Platform",
"runtime-version": "44", "runtime-version": "46",
"sdk": "org.gnome.Sdk", "sdk": "org.gnome.Sdk",
"command": "dino", "command": "dino",
"finish-args": [ "finish-args": [
@ -60,7 +60,11 @@
"buildsystem": "cmake-ninja", "buildsystem": "cmake-ninja",
"builddir": true, "builddir": true,
"config-opts": [ "config-opts": [
"-DSOUP_VERSION=3" "-DSOUP_VERSION=3",
"-DNO_DEBUG=yes",
"-DCMAKE_BUILD_TYPE=Release",
"-DENABLED_PLUGINS=notification-sound",
"-DPLUGIN_RTP_WEBRTC_AUDIO_PROCESSING=OFF"
], ],
"cleanup": [ "cleanup": [
"/include", "/include",

View file

@ -102,7 +102,8 @@ DEPENDS
${CMAKE_BINARY_DIR}/exports/dino_i18n.h ${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"
-DDINO_VERSION=\"${PROJECT_VERSION}\" -DDINO_SYSTEM_LOCALEDIR_NAME="${LOCALEDIR_NAME}")
add_library(libdino SHARED ${LIBDINO_VALA_C} ${CMAKE_BINARY_DIR}/exports/dino_i18n.h) add_library(libdino SHARED ${LIBDINO_VALA_C} ${CMAKE_BINARY_DIR}/exports/dino_i18n.h)
add_dependencies(libdino dino-vapi) add_dependencies(libdino dino-vapi)
target_link_libraries(libdino xmpp-vala qlite ${LIBDINO_PACKAGES} m) target_link_libraries(libdino xmpp-vala qlite ${LIBDINO_PACKAGES} m)
@ -112,12 +113,13 @@ install(TARGETS libdino ${TARGET_INSTALL})
install(FILES ${CMAKE_BINARY_DIR}/exports/dino.vapi ${CMAKE_BINARY_DIR}/exports/dino.deps DESTINATION ${VAPI_INSTALL_DIR}) install(FILES ${CMAKE_BINARY_DIR}/exports/dino.vapi ${CMAKE_BINARY_DIR}/exports/dino.deps DESTINATION ${VAPI_INSTALL_DIR})
install(FILES ${CMAKE_BINARY_DIR}/exports/dino.h ${CMAKE_BINARY_DIR}/exports/dino_i18n.h DESTINATION ${INCLUDE_INSTALL_DIR}) install(FILES ${CMAKE_BINARY_DIR}/exports/dino.h ${CMAKE_BINARY_DIR}/exports/dino_i18n.h DESTINATION ${INCLUDE_INSTALL_DIR})
if(BUILD_TESTS) if(BUILD_TESTING)
vala_precompile(LIBDINO_TEST_VALA_C vala_precompile(LIBDINO_TEST_VALA_C
SOURCES SOURCES
"tests/weak_map.vala" "tests/weak_map.vala"
"tests/testcase.vala" "tests/testcase.vala"
"tests/common.vala" "tests/common.vala"
"tests/jid.vala"
CUSTOM_VAPIS CUSTOM_VAPIS
${CMAKE_BINARY_DIR}/exports/dino_internal.vapi ${CMAKE_BINARY_DIR}/exports/dino_internal.vapi
${CMAKE_BINARY_DIR}/exports/xmpp-vala.vapi ${CMAKE_BINARY_DIR}/exports/xmpp-vala.vapi
@ -131,4 +133,5 @@ if(BUILD_TESTS)
add_definitions(${VALA_CFLAGS}) add_definitions(${VALA_CFLAGS})
add_executable(libdino-test ${LIBDINO_TEST_VALA_C}) add_executable(libdino-test ${LIBDINO_TEST_VALA_C})
target_link_libraries(libdino-test libdino) target_link_libraries(libdino-test libdino)
endif(BUILD_TESTS) add_test(NAME libdino COMMAND libdino-test)
endif(BUILD_TESTING)

View file

@ -79,8 +79,9 @@ sources = files(
) )
sources += [version_vala] sources += [version_vala]
c_args = [ c_args = [
'-DDINO_SYSTEM_LIBDIR_NAME="@0@"'.format(get_option('prefix') / get_option('libdir')), '-DDINO_SYSTEM_LIBDIR_NAME="@0@"'.format(get_option('libdir')),
'-DDINO_SYSTEM_PLUGIN_DIR="@0@"'.format(get_option('prefix') / get_option('plugindir')), '-DDINO_SYSTEM_LOCALEDIR_NAME="@0@"'.format(get_option('localedir')),
'-DDINO_SYSTEM_PLUGIN_DIR="@0@"'.format(get_option('prefix') / get_option('libdir') / get_option('plugindir')),
'-DG_LOG_DOMAIN="libdino"', '-DG_LOG_DOMAIN="libdino"',
] ]
lib_dino = library('dino', sources, c_args: c_args, include_directories: include_directories('src'), dependencies: dependencies, version: '0.0', install: true, install_dir: [true, true, true]) lib_dino = library('dino', sources, c_args: c_args, include_directories: include_directories('src'), dependencies: dependencies, version: '0.0', install: true, install_dir: [true, true, true])
@ -88,3 +89,12 @@ dep_dino = declare_dependency(link_with: lib_dino, include_directories: include_
install_data('dino.deps', install_dir: get_option('datadir') / 'vala/vapi') # TODO: workaround for https://github.com/mesonbuild/meson/issues/9756 install_data('dino.deps', install_dir: get_option('datadir') / 'vala/vapi') # TODO: workaround for https://github.com/mesonbuild/meson/issues/9756
install_headers('src/dino_i18n.h') install_headers('src/dino_i18n.h')
sources = files(
'tests/weak_map.vala',
'tests/testcase.vala',
'tests/common.vala',
'tests/jid.vala',
)
test_libdino = executable('test_libdino', sources, dependencies: dependencies + [dep_dino])
test('libdino', test_libdino)

View file

@ -25,13 +25,13 @@ public interface Application : GLib.Application {
public abstract void handle_uri(string jid, string query, Gee.Map<string, string> options); public abstract void handle_uri(string jid, string query, Gee.Map<string, string> options);
public void init() throws Error { public void init(bool default_dark_theme) throws Error {
if (DirUtils.create_with_parents(get_storage_dir(), 0700) == -1) { if (DirUtils.create_with_parents(get_storage_dir(), 0700) == -1) {
throw new Error(-1, 0, "Could not create storage dir \"%s\": %s", get_storage_dir(), FileUtils.error_from_errno(errno).to_string()); throw new Error(-1, 0, "Could not create storage dir \"%s\": %s", get_storage_dir(), FileUtils.error_from_errno(errno).to_string());
} }
this.db = new Database(Path.build_filename(get_storage_dir(), "dino.db")); this.db = new Database(Path.build_filename(get_storage_dir(), "dino.db"));
this.settings = new Dino.Entities.Settings.from_db(db); this.settings = new Dino.Entities.Settings.from_db(db, default_dark_theme);
this.stream_interactor = new StreamInteractor(db); this.stream_interactor = new StreamInteractor(db);
MessageProcessor.start(stream_interactor, db); MessageProcessor.start(stream_interactor, db);

View file

@ -2,6 +2,8 @@ using Xmpp;
namespace Dino.Entities { namespace Dino.Entities {
const int HISTORY_SYNC_MAM_MESSAGES = 20;
public class Conversation : Object { public class Conversation : Object {
public signal void object_updated(Conversation conversation); public signal void object_updated(Conversation conversation);
@ -33,7 +35,7 @@ public class Conversation : Object {
} }
} }
} }
public Encryption encryption { get; set; default = Encryption.NONE; } public Encryption encryption { get; set; default = Encryption.UNKNOWN; }
public Message? read_up_to { get; set; } public Message? read_up_to { get; set; }
public int read_up_to_item { get; set; default=-1; } public int read_up_to_item { get; set; default=-1; }

View file

@ -11,6 +11,27 @@ namespace Dino.Entities {
public bool is_some() { public bool is_some() {
return this != NONE; return this != NONE;
} }
public static Encryption parse(string str) {
switch (str) {
case "DINO_ENTITIES_ENCRYPTION_NONE":
return NONE;
case "DINO_ENTITIES_ENCRYPTION_PGP":
return PGP;
case "DINO_ENTITIES_ENCRYPTION_OMEMO":
return OMEMO;
case "DINO_ENTITIES_ENCRYPTION_DTLS_SRTP":
return DTLS_SRTP;
case "DINO_ENTITIES_ENCRYPTION_SRTP":
return SRTP;
case "DINO_ENTITIES_ENCRYPTION_UNKNOWN":
// Fall through.
default:
break;
}
return UNKNOWN;
}
} }
} }

View file

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

View file

@ -4,7 +4,7 @@ public class Settings : Object {
private Database db; private Database db;
public Settings.from_db(Database db) { public Settings.from_db(Database db, bool default_dark_theme) {
this.db = db; this.db = db;
send_typing_ = col_to_bool_or_default("send_typing", true); send_typing_ = col_to_bool_or_default("send_typing", true);
@ -12,6 +12,10 @@ public class Settings : Object {
notifications_ = col_to_bool_or_default("notifications", true); notifications_ = col_to_bool_or_default("notifications", true);
convert_utf8_smileys_ = col_to_bool_or_default("convert_utf8_smileys", true); convert_utf8_smileys_ = col_to_bool_or_default("convert_utf8_smileys", true);
check_spelling = col_to_bool_or_default("check_spelling", true); check_spelling = col_to_bool_or_default("check_spelling", true);
default_encryption = col_to_encryption_or_default("default_encryption", Encryption.UNKNOWN);
send_button = col_to_bool_or_default("send_button", false);
enter_newline = col_to_bool_or_default("enter_newline", false);
dark_theme = col_to_bool_or_default("dark_theme", default_dark_theme);
} }
private bool col_to_bool_or_default(string key, bool def) { private bool col_to_bool_or_default(string key, bool def) {
@ -19,6 +23,12 @@ public class Settings : Object {
return val != null ? bool.parse(val) : def; return val != null ? bool.parse(val) : def;
} }
private Encryption col_to_encryption_or_default(string key, Encryption def) {
var sval = db.settings.value;
string? val = db.settings.select({sval}).with(db.settings.key, "=", key)[sval];
return val != null ? Encryption.parse(val) : def;
}
private bool send_typing_; private bool send_typing_;
public bool send_typing { public bool send_typing {
get { return send_typing_; } get { return send_typing_; }
@ -79,6 +89,60 @@ public class Settings : Object {
check_spelling_ = value; check_spelling_ = value;
} }
} }
private Encryption default_encryption_;
public Encryption default_encryption {
get { return default_encryption_; }
set {
string valstr = value.to_string();
db.settings.upsert()
.value(db.settings.key, "default_encryption", true)
.value(db.settings.value, valstr)
.perform();
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 dark_theme_update(bool is_dark);
private bool dark_theme_;
public bool dark_theme {
get { return dark_theme_; }
set {
db.settings.upsert()
.value(db.settings.key, "dark_theme", true)
.value(db.settings.value, value.to_string())
.perform();
dark_theme_ = value;
dark_theme_update(value);
}
}
} }
} }

View file

@ -60,7 +60,7 @@ public class Dino.CallState : Object {
XmppStream stream = stream_interactor.get_stream(call.account); XmppStream stream = stream_interactor.get_stream(call.account);
if (stream == null) return; if (stream == null) return;
Gee.List<Jid> occupants = stream_interactor.get_module(MucManager.IDENTITY).get_other_occupants(muc, call.account); Gee.List<Jid> occupants = stream_interactor.get_module(MucManager.IDENTITY).get_other_members(muc, call.account);
foreach (Jid occupant in occupants) { foreach (Jid occupant in occupants) {
Jid? real_jid = stream_interactor.get_module(MucManager.IDENTITY).get_real_jid(occupant, call.account); Jid? real_jid = stream_interactor.get_module(MucManager.IDENTITY).get_real_jid(occupant, call.account);
if (real_jid == null) continue; if (real_jid == null) continue;

View file

@ -6,8 +6,6 @@ using Xmpp;
namespace Dino { namespace Dino {
const int HISTORY_SYNC_MAM_PAGES = 10;
public class ContentItemStore : StreamInteractionModule, Object { public class ContentItemStore : StreamInteractionModule, Object {
public static ModuleIdentity<ContentItemStore> IDENTITY = new ModuleIdentity<ContentItemStore>("content_item_store"); public static ModuleIdentity<ContentItemStore> IDENTITY = new ModuleIdentity<ContentItemStore>("content_item_store");
public string id { get { return IDENTITY.id; } } public string id { get { return IDENTITY.id; } }
@ -260,8 +258,8 @@ public class ContentItemStore : StreamInteractionModule, Object {
if (items.size == 0 && request_from_server) { if (items.size == 0 && request_from_server) {
// Async request to get earlier messages from the server // Async request to get earlier messages from the server
var history_sync = stream_interactor.get_module(MessageProcessor.IDENTITY).history_sync; var history_sync = stream_interactor.get_module(MessageProcessor.IDENTITY).history_sync;
history_sync.fetch_data.begin(conversation.account, conversation.counterpart.bare_jid, item.time, HISTORY_SYNC_MAM_PAGES, (_, res) => { history_sync.fetch_conversation_data.begin(conversation, item.time, (_, res) => {
history_sync.fetch_data.end(res); history_sync.fetch_conversation_data.end(res);
debug("History loaded"); debug("History loaded");
history_loaded(conversation, item, count); history_loaded(conversation, item, count);
}); });

View file

@ -46,6 +46,15 @@ public class FileManager : StreamInteractionModule, Object {
return ret; return ret;
} }
private string sanitize_filename(string filename) {
#if _WIN32
GLib.Regex regex = new GLib.Regex("[<>:\"/\\|?*]");
#else
GLib.Regex regex = new GLib.Regex("[/]");
#endif
return regex.replace(filename, -1, 0, "_");
}
public async void send_file(File file, Conversation conversation) { public async void send_file(File file, Conversation conversation) {
FileTransfer file_transfer = new FileTransfer(); FileTransfer file_transfer = new FileTransfer();
file_transfer.account = conversation.account; file_transfer.account = conversation.account;
@ -120,6 +129,7 @@ public class FileManager : StreamInteractionModule, Object {
} }
yield file_sender.send_file(conversation, file_transfer, file_send_data, file_meta); yield file_sender.send_file(conversation, file_transfer, file_send_data, file_meta);
file_transfer.state = FileTransfer.State.COMPLETE;
} catch (Error e) { } catch (Error e) {
warning("Send file error: %s", e.message); warning("Send file error: %s", e.message);
@ -242,7 +252,7 @@ public class FileManager : StreamInteractionModule, Object {
} }
// Save file // Save file
string filename = Random.next_int().to_string("%x") + "_" + file_transfer.file_name; string filename = Random.next_int().to_string("%x") + "_" + sanitize_filename(file_transfer.file_name);
File file = File.new_for_path(Path.build_filename(get_storage_dir(), filename)); File file = File.new_for_path(Path.build_filename(get_storage_dir(), filename));
OutputStream os = file.create(FileCreateFlags.REPLACE_DESTINATION); OutputStream os = file.create(FileCreateFlags.REPLACE_DESTINATION);
@ -250,6 +260,7 @@ public class FileManager : StreamInteractionModule, Object {
ssize_t read; ssize_t read;
while ((read = yield input_stream.read_async(buffer, Priority.LOW, file_transfer.cancellable)) > 0) { while ((read = yield input_stream.read_async(buffer, Priority.LOW, file_transfer.cancellable)) > 0) {
buffer.length = (int) read; buffer.length = (int) read;
file_transfer.transferred_bytes += (uint64)read;
yield os.write_async(buffer, Priority.LOW, file_transfer.cancellable); yield os.write_async(buffer, Priority.LOW, file_transfer.cancellable);
buffer.length = 1024; buffer.length = 1024;
} }
@ -297,6 +308,7 @@ public class FileManager : StreamInteractionModule, Object {
file_transfer.file_name = file_meta.file_name; file_transfer.file_name = file_meta.file_name;
file_transfer.size = (int)file_meta.size; file_transfer.size = (int)file_meta.size;
file_transfer.info = info; file_transfer.info = info;
file_transfer.transferred_bytes = 0;
var encryption = file_provider.get_encryption(file_transfer, receive_data, file_meta); var encryption = file_provider.get_encryption(file_transfer, receive_data, file_meta);
if (encryption != Encryption.NONE) file_transfer.encryption = encryption; if (encryption != Encryption.NONE) file_transfer.encryption = encryption;
@ -329,11 +341,11 @@ public class FileManager : StreamInteractionModule, Object {
private async void save_file(FileTransfer file_transfer) throws FileSendError { private async void save_file(FileTransfer file_transfer) throws FileSendError {
try { try {
string filename = Random.next_int().to_string("%x") + "_" + file_transfer.file_name; string filename = Random.next_int().to_string("%x") + "_" + sanitize_filename(file_transfer.file_name);
File file = File.new_for_path(Path.build_filename(get_storage_dir(), filename)); File file = File.new_for_path(Path.build_filename(get_storage_dir(), filename));
OutputStream os = file.create(FileCreateFlags.REPLACE_DESTINATION); OutputStream os = file.create(FileCreateFlags.REPLACE_DESTINATION);
yield os.splice_async(file_transfer.input_stream, OutputStreamSpliceFlags.CLOSE_SOURCE|OutputStreamSpliceFlags.CLOSE_TARGET); 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.path = filename;
file_transfer.input_stream = yield file.read_async(); file_transfer.input_stream = yield file.read_async();
} catch (Error e) { } catch (Error e) {

View file

@ -22,6 +22,7 @@ public class Dino.HistorySync {
public HashMap<Account, DateTime> catchup_until_time = new HashMap<Account, DateTime>(Account.hash_func, Account.equals_func); public HashMap<Account, DateTime> catchup_until_time = new HashMap<Account, DateTime>(Account.hash_func, Account.equals_func);
private HashMap<string, Gee.List<Xmpp.MessageStanza>> stanzas = new HashMap<string, Gee.List<Xmpp.MessageStanza>>(); private HashMap<string, Gee.List<Xmpp.MessageStanza>> stanzas = new HashMap<string, Gee.List<Xmpp.MessageStanza>>();
private HashMap<string, int> messages_processed = new HashMap<string, int>();
public class HistorySync(Database db, StreamInteractor stream_interactor) { public class HistorySync(Database db, StreamInteractor stream_interactor) {
this.stream_interactor = stream_interactor; this.stream_interactor = stream_interactor;
@ -120,7 +121,7 @@ public class Dino.HistorySync {
} }
} }
private async PageRequestResult fetch_pages(Account account, Xmpp.MessageArchiveManagement.V2.MamQueryParams query_params, int pages) { private async PageRequestResult fetch_messages(Account account, Xmpp.MessageArchiveManagement.V2.MamQueryParams query_params) {
debug("[%s | %s] Fetch query %s - %s", account.bare_jid.to_string(), query_params.mam_server.to_string(), query_params.start != null ? query_params.start.to_string() : "", query_params.end != null ? query_params.end.to_string() : ""); debug("[%s | %s] Fetch query %s - %s", account.bare_jid.to_string(), query_params.mam_server.to_string(), query_params.start != null ? query_params.start.to_string() : "", query_params.end != null ? query_params.end.to_string() : "");
PageRequestResult? page_result = null; PageRequestResult? page_result = null;
@ -129,11 +130,13 @@ public class Dino.HistorySync {
page_result = yield get_mam_page(account, query_params, page_result, null); page_result = yield get_mam_page(account, query_params, page_result, null);
processed_pages++; processed_pages++;
debug("[%s | %s] Page result %s (got stanzas: %s)", account.bare_jid.to_string(), query_params.mam_server.to_string(), page_result.page_result.to_string(), (page_result.stanzas != null).to_string()); debug("%d messages left to process, current page is %d", messages_processed[query_params.query_id], processed_pages);
if (processed_pages == pages) { if (messages_processed[query_params.query_id] <= 1) {
debug("Done processing new messages");
break; break;
} }
debug("[%s | %s] Page result %s (got stanzas: %s)", account.bare_jid.to_string(), query_params.mam_server.to_string(), page_result.page_result.to_string(), (page_result.stanzas != null).to_string());
if (page_result.page_result == PageResult.Error || page_result.page_result == PageResult.Cancelled || page_result.query_result.first == null) { if (page_result.page_result == PageResult.Error || page_result.page_result == PageResult.Cancelled || page_result.query_result.first == null) {
return page_result; return page_result;
} }
@ -143,11 +146,36 @@ public class Dino.HistorySync {
return page_result; return page_result;
} }
public async void fetch_data(Account account, Jid target, DateTime latest, int pages) { public async void fetch_data(Account account, Jid target, DateTime latest) {
debug("Fetch history for %s", target.to_string()); debug("Fetch history for %s", target.to_string());
var query_params = new Xmpp.MessageArchiveManagement.V2.MamQueryParams.query_before(target, latest, null); var query_params = new Xmpp.MessageArchiveManagement.V2.MamQueryParams.query_before(target, latest, null);
yield fetch_pages(account, query_params, pages); string query_id = query_params.query_id;
messages_processed[query_id] = HISTORY_SYNC_MAM_MESSAGES;
yield fetch_messages(account, query_params);
}
public async void fetch_conversation_data(Conversation? conversation, DateTime latest) {
if (conversation == null) {
warning("Failed to fetch history, conversation is null");
return;
}
var target = conversation.counterpart.bare_jid;
var account = conversation.account;
debug("Fetch history for %s", target.to_string());
var query_params = new Xmpp.MessageArchiveManagement.V2.MamQueryParams.query_before(target, latest, null);
if (conversation.type_ == Conversation.Type.CHAT) {
query_params.mam_server = account.bare_jid;
query_params.with = target;
}
string query_id = query_params.query_id;
messages_processed[query_id] = HISTORY_SYNC_MAM_MESSAGES;
yield fetch_messages(account, query_params);
} }
public async void fetch_history(Account account, Jid target, Cancellable? cancellable = null) { public async void fetch_history(Account account, Jid target, Cancellable? cancellable = null) {
@ -478,6 +506,7 @@ public class Dino.HistorySync {
} }
} }
} }
if (hitted_range.has_key(query_id) && hitted_range[query_id] == -2) { if (hitted_range.has_key(query_id) && hitted_range[query_id] == -2) {
// Message got filtered out by xmpp-vala, but succesful range fetch nevertheless // Message got filtered out by xmpp-vala, but succesful range fetch nevertheless
yield send_messages_back_into_pipeline(account, query_id); yield send_messages_back_into_pipeline(account, query_id);
@ -492,15 +521,24 @@ public class Dino.HistorySync {
if (cancellable != null && cancellable.is_cancelled()) { if (cancellable != null && cancellable.is_cancelled()) {
page_result = PageResult.Cancelled; page_result = PageResult.Cancelled;
} }
return new PageRequestResult(page_result, query_result, stanzas_for_query); return new PageRequestResult(page_result, query_result, stanzas_for_query);
} }
private async void send_messages_back_into_pipeline(Account account, string query_id, Cancellable? cancellable = null) { private async void send_messages_back_into_pipeline(Account account, string query_id, Cancellable? cancellable = null) {
if (!stanzas.has_key(query_id)) return; if (!stanzas.has_key(query_id)) {
return;
}
foreach (Xmpp.MessageStanza message in stanzas[query_id]) { foreach (Xmpp.MessageStanza message in stanzas[query_id]) {
if (cancellable != null && cancellable.is_cancelled()) break; if (cancellable != null && cancellable.is_cancelled()) break;
yield stream_interactor.get_module(MessageProcessor.IDENTITY).run_pipeline_announce(account, message); bool result = yield stream_interactor.get_module(MessageProcessor.IDENTITY).run_pipeline_announce(account, message);
if (result && messages_processed.has_key(query_id)) {
int count = messages_processed[query_id];
count = count - 1;
messages_processed[query_id] = count;
}
} }
stanzas.unset(query_id); stanzas.unset(query_id);
} }

View file

@ -220,7 +220,19 @@ public class JingleFileSender : FileSender, Object {
} }
} }
try { 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) { } catch (Error e) {
throw new FileSendError.UPLOAD_FAILED(@"offer_file_stream failed: $(e.message)"); throw new FileSendError.UPLOAD_FAILED(@"offer_file_stream failed: $(e.message)");
} }

View file

@ -130,17 +130,17 @@ public class MessageProcessor : StreamInteractionModule, Object {
run_pipeline_announce.begin(account, message_stanza); run_pipeline_announce.begin(account, message_stanza);
} }
public async void run_pipeline_announce(Account account, Xmpp.MessageStanza message_stanza) { public async bool run_pipeline_announce(Account account, Xmpp.MessageStanza message_stanza) {
Entities.Message message = yield parse_message_stanza(account, message_stanza); Entities.Message message = yield parse_message_stanza(account, message_stanza);
Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation_for_message(message); Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation_for_message(message);
if (conversation == null) { if (conversation == null) {
return; return false;
} }
bool abort = yield received_pipeline.run(message, message_stanza, conversation); bool abort = yield received_pipeline.run(message, message_stanza, conversation);
if (abort) { if (abort) {
return; return false;
} }
if (message.direction == Entities.Message.DIRECTION_RECEIVED) { if (message.direction == Entities.Message.DIRECTION_RECEIVED) {
@ -150,6 +150,7 @@ public class MessageProcessor : StreamInteractionModule, Object {
} }
message_sent_or_received(message, conversation); message_sent_or_received(message, conversation);
return true;
} }
public async Entities.Message parse_message_stanza(Account account, Xmpp.MessageStanza message) { public async Entities.Message parse_message_stanza(Account account, Xmpp.MessageStanza message) {

View file

@ -107,7 +107,7 @@ public class MucManager : StreamInteractionModule, Object {
var history_sync = stream_interactor.get_module(MessageProcessor.IDENTITY).history_sync; var history_sync = stream_interactor.get_module(MessageProcessor.IDENTITY).history_sync;
if (conversation == null) { if (conversation == null) {
// We never joined the conversation before, fetch latest MAM pages // We never joined the conversation before, fetch latest MAM pages
yield history_sync.fetch_data(account, jid.bare_jid, new DateTime.now(), 10); yield history_sync.fetch_data(account, jid.bare_jid, new DateTime.now());
} else { } else {
// Fetch everything up to the last time the user actively joined // Fetch everything up to the last time the user actively joined
if (!mucs_sync_cancellables.has_key(account)) { if (!mucs_sync_cancellables.has_key(account)) {
@ -259,7 +259,23 @@ public class MucManager : StreamInteractionModule, Object {
return is_groupchat(jid, account) && !is_private_room(account, jid); return is_groupchat(jid, account) && !is_private_room(account, jid);
} }
public Gee.List<Jid>? get_occupants(Jid jid, Account account) { public Gee.List<Jid>? get_all_members(Jid jid, Account account) {
if (is_groupchat(jid, account)) {
Gee.List<Jid> ret = new ArrayList<Jid>(Jid.equals_func);
// This should return all members of the chat
Gee.List<Jid>? members = get_offline_members(jid, account);
if (members != null) {
ret.add_all(members);
}
return ret;
}
return null;
}
public Gee.List<Jid>? get_members(Jid jid, Account account) {
if (is_groupchat(jid, account)) { if (is_groupchat(jid, account)) {
Gee.List<Jid> ret = new ArrayList<Jid>(Jid.equals_func); Gee.List<Jid> ret = new ArrayList<Jid>(Jid.equals_func);
Gee.List<Jid>? full_jids = stream_interactor.get_module(PresenceManager.IDENTITY).get_full_jids(jid, account); Gee.List<Jid>? full_jids = stream_interactor.get_module(PresenceManager.IDENTITY).get_full_jids(jid, account);
@ -268,18 +284,21 @@ public class MucManager : StreamInteractionModule, Object {
// Remove eventual presence from bare jid // Remove eventual presence from bare jid
ret.remove(jid); ret.remove(jid);
} }
return ret; return ret;
} }
return null; return null;
} }
public Gee.List<Jid>? get_other_occupants(Jid jid, Account account) { public Gee.List<Jid>? get_other_members(Jid jid, Account account) {
Gee.List<Jid>? occupants = get_occupants(jid, account); Gee.List<Jid>? members = get_members(jid, account);
Jid? own_jid = get_own_jid(jid, account); Jid? own_jid = get_own_jid(jid, account);
if (occupants != null && own_jid != null) { if (members != null && own_jid != null) {
occupants.remove(own_jid); members.remove(own_jid);
} }
return occupants;
return members;
} }
public bool is_groupchat(Jid jid, Account account) { public bool is_groupchat(Jid jid, Account account) {
@ -535,12 +554,25 @@ public class MucManager : StreamInteractionModule, Object {
} }
private void on_invite_received(Account account, Jid room_jid, Jid from_jid, string? password, string? reason) { private void on_invite_received(Account account, Jid room_jid, Jid from_jid, string? password, string? reason) {
info("Invite received for room %s", room_jid.bare_jid.to_string());
Gee.List<Conversation> conversations = stream_interactor.get_module(ConversationManager.IDENTITY).get_active_conversations(account);
foreach (Conversation conversation in conversations) {
if (conversation.counterpart.bare_jid.to_string() == room_jid.bare_jid.to_string()) {
// HACK: try to skip duplicate invites for active conversations
warning("Skipping duplicate invite for conversation %s", room_jid.bare_jid.to_string());
return;
}
}
if (!invites.has_key(account)) { if (!invites.has_key(account)) {
invites[account] = new LinkedList<Jid>(Jid.equals_func); invites[account] = new LinkedList<Jid>(Jid.equals_func);
} }
if (invites[account].contains(room_jid)) return;
invites[account].add(room_jid);
if (invites[account].contains(room_jid)) {
return;
}
invites[account].add(room_jid);
invite_received(account, room_jid, from_jid, password, reason); invite_received(account, room_jid, from_jid, password, reason);
Timeout.add_seconds(5, () => { Timeout.add_seconds(5, () => {
@ -596,8 +628,8 @@ public class MucManager : StreamInteractionModule, Object {
private void set_autojoin(Account account, XmppStream stream, Jid jid, string? nick, string? password) { private void set_autojoin(Account account, XmppStream stream, Jid jid, string? nick, string? password) {
bookmarks_provider[account].get_conferences.begin(stream, (_, res) => { bookmarks_provider[account].get_conferences.begin(stream, (_, res) => {
Set<Conference>? conferences = bookmarks_provider[account].get_conferences.end(res); Set<Conference>? conferences = bookmarks_provider[account].get_conferences.end(res);
if (conferences == null) return;
if (conferences != null) {
foreach (Conference conference in conferences) { foreach (Conference conference in conferences) {
if (conference.jid.equals(jid)) { if (conference.jid.equals(jid)) {
if (!conference.autojoin) { if (!conference.autojoin) {
@ -607,6 +639,8 @@ public class MucManager : StreamInteractionModule, Object {
return; return;
} }
} }
}
Conference changed = new Xep.Bookmarks.Bookmarks1Conference(jid) { nick=nick, password=password, autojoin=true }; Conference changed = new Xep.Bookmarks.Bookmarks1Conference(jid) { nick=nick, password=password, autojoin=true };
bookmarks_provider[account].add_conference.begin(stream, changed); bookmarks_provider[account].add_conference.begin(stream, changed);
}); });

View file

@ -6,7 +6,7 @@ namespace Dino {
public class Util { public class Util {
#if _WIN32 #if _WIN32
[CCode (cname = "ShellExecuteA", cheader_filename = "windows.h")] [CCode (cname = "ShellExecuteA", cheader_filename = "windows.h")]
private static extern int ShellExecuteA(int* hwnd, string operation, string file, string parameters, string directory, int showCmd); private static extern int* ShellExecuteA(int* hwnd, string operation, string file, string parameters, string directory, int showCmd);
[CCode (cname = "CoInitialize", cheader_filename = "windows.h")] [CCode (cname = "CoInitialize", cheader_filename = "windows.h")]
private static extern int CoInitialize(void* reserved); private static extern int CoInitialize(void* reserved);
@ -14,7 +14,7 @@ public class Util {
[CCode (cname = "CoUninitialize", cheader_filename = "windows.h")] [CCode (cname = "CoUninitialize", cheader_filename = "windows.h")]
private static extern void CoUninitialize(); private static extern void CoUninitialize();
private static int ShellExecute(string operation, string file) { private static int* ShellExecute(string operation, string file) {
CoInitialize(null); CoInitialize(null);
var result = ShellExecuteA(null, operation, file, null, null, 1); var result = ShellExecuteA(null, operation, file, null, null, 1);
CoUninitialize(); CoUninitialize();

View file

@ -2,6 +2,7 @@ namespace Dino {
private extern const string SYSTEM_LIBDIR_NAME; private extern const string SYSTEM_LIBDIR_NAME;
private extern const string SYSTEM_PLUGIN_DIR; private extern const string SYSTEM_PLUGIN_DIR;
private extern const string SYSTEM_LOCALEDIR_NAME;
public class SearchPathGenerator { public class SearchPathGenerator {
@ -12,14 +13,21 @@ public class SearchPathGenerator {
} }
public string get_locale_path(string gettext_package, string locale_install_dir) { public string get_locale_path(string gettext_package, string locale_install_dir) {
string? locale_dir = null; if (exec_path != null) {
if (Path.get_dirname(exec_path).contains("dino") || Path.get_dirname(exec_path) == "." || Path.get_dirname(exec_path).contains("build")) { var exec_dir = Path.get_dirname(exec_path);
string exec_locale = Path.build_filename(Path.get_dirname(exec_path), "locale"); string[] search_paths = new string[] {
if (FileUtils.test(Path.build_filename(exec_locale, "en", "LC_MESSAGES", gettext_package + ".mo"), FileTest.IS_REGULAR)) { Path.build_filename(exec_dir, "locale"),
locale_dir = exec_locale; Path.build_filename(Path.get_dirname(exec_dir), SYSTEM_LOCALEDIR_NAME)
};
foreach (var path in search_paths) {
if (FileUtils.test(Path.build_filename(path, "en", "LC_MESSAGES", gettext_package + ".mo"), FileTest.IS_REGULAR)) {
debug(@"Found locale $(gettext_package).mo in $(path)");
return path;
} }
} }
return locale_dir ?? locale_install_dir; }
return locale_install_dir;
} }
public string[] get_plugin_paths() { public string[] get_plugin_paths() {

View file

@ -4,6 +4,7 @@ int main(string[] args) {
GLib.Test.init(ref args); GLib.Test.init(ref args);
GLib.Test.set_nonfatal_assertions(); GLib.Test.set_nonfatal_assertions();
TestSuite.get_root().add_suite(new WeakMapTest().get_suite()); TestSuite.get_root().add_suite(new WeakMapTest().get_suite());
TestSuite.get_root().add_suite(new JidTest().get_suite());
return GLib.Test.run(); return GLib.Test.run();
} }

View file

@ -1,4 +1,5 @@
using Dino.Entities; using Dino.Entities;
using Xmpp;
namespace Dino.Test { namespace Dino.Test {
@ -12,27 +13,39 @@ class JidTest : Gee.TestCase {
} }
private void test_parse() { private void test_parse() {
try {
Jid jid = new Jid("user@example.com/res"); Jid jid = new Jid("user@example.com/res");
fail_if(jid.localpart != "user"); fail_if(jid.localpart != "user");
fail_if(jid.domainpart != "example.com"); fail_if(jid.domainpart != "example.com");
fail_if(jid.resourcepart != "res"); fail_if(jid.resourcepart != "res");
fail_if(jid.to_string() != "user@example.com/res"); fail_if(jid.to_string() != "user@example.com/res");
} catch (Error e) {
fail_if_reached(@"Throws $(e.message)");
}
} }
private void test_components() { private void test_components() {
try {
Jid jid = new Jid.components("user", "example.com", "res"); Jid jid = new Jid.components("user", "example.com", "res");
fail_if(jid.localpart != "user"); fail_if(jid.localpart != "user");
fail_if(jid.domainpart != "example.com"); fail_if(jid.domainpart != "example.com");
fail_if(jid.resourcepart != "res"); fail_if(jid.resourcepart != "res");
fail_if(jid.to_string() != "user@example.com/res"); fail_if(jid.to_string() != "user@example.com/res");
} catch (Error e) {
fail_if_reached(@"Throws $(e.message)");
}
} }
private void test_with_res() { private void test_with_res() {
Jid jid = new Jid.with_resource("user@example.com", "res"); try {
Jid jid = new Jid("user@example.com").with_resource("res");
fail_if(jid.localpart != "user"); fail_if(jid.localpart != "user");
fail_if(jid.domainpart != "example.com"); fail_if(jid.domainpart != "example.com");
fail_if(jid.resourcepart != "res"); fail_if(jid.resourcepart != "res");
fail_if(jid.to_string() != "user@example.com/res"); fail_if(jid.to_string() != "user@example.com/res");
} catch (Error e) {
fail_if_reached(@"Throws $(e.message)");
}
} }
} }

View file

@ -49,6 +49,8 @@ set(RESOURCE_LIST
icons/scalable/status/dino-status-chat.svg icons/scalable/status/dino-status-chat.svg
icons/scalable/status/dino-status-dnd.svg icons/scalable/status/dino-status-dnd.svg
icons/scalable/status/dino-status-online.svg icons/scalable/status/dino-status-online.svg
icons/scalable/status/dino-status-online-bright.svg
icons/scalable/status/dino-status-offline.svg
icons/scalable/status/dino-tick-symbolic.svg icons/scalable/status/dino-tick-symbolic.svg
icons/scalable/status/dino-video-off-symbolic.svg icons/scalable/status/dino-video-off-symbolic.svg
icons/scalable/status/dino-video-symbolic.svg icons/scalable/status/dino-video-symbolic.svg
@ -67,6 +69,7 @@ set(RESOURCE_LIST
conversation_list_titlebar_csd.ui conversation_list_titlebar_csd.ui
conversation_row.ui conversation_row.ui
conversation_view.ui conversation_view.ui
default_encryption_dialog.ui
file_default_widget.ui file_default_widget.ui
file_send_overlay.ui file_send_overlay.ui
global_search.ui global_search.ui

View file

@ -17,7 +17,7 @@
<object class="GtkButton" id="file_button"> <object class="GtkButton" id="file_button">
<property name="icon-name">mail-attachment-symbolic</property> <property name="icon-name">mail-attachment-symbolic</property>
<property name="margin-top">2</property> <property name="margin-top">2</property>
<property name="valign">start</property> <property name="valign">center</property>
<style> <style>
<class name="flat"/> <class name="flat"/>
<class name="dino-chatinput-button"/> <class name="dino-chatinput-button"/>
@ -54,7 +54,7 @@
<property name="icon-name">emoji-people-symbolic</property> <property name="icon-name">emoji-people-symbolic</property>
<property name="has-frame">False</property> <property name="has-frame">False</property>
<property name="margin-top">2</property> <property name="margin-top">2</property>
<property name="valign">start</property> <property name="valign">center</property>
<style> <style>
<class name="flat"/> <class name="flat"/>
<class name="dino-chatinput-button"/> <class name="dino-chatinput-button"/>
@ -67,7 +67,21 @@
<property name="icon-name">changes-allow-symbolic</property> <property name="icon-name">changes-allow-symbolic</property>
<property name="has-frame">False</property> <property name="has-frame">False</property>
<property name="margin-top">2</property> <property name="margin-top">2</property>
<property name="valign">start</property> <property name="valign">center</property>
<style>
<class name="flat"/>
<class name="dino-chatinput-button"/>
<class name="image-button"/>
</style>
</object>
</child>
<child>
<object class="GtkButton" id="send_button">
<property name="icon-name">mail-send-symbolic</property>
<property name="has-frame">False</property>
<property name="margin-top">2</property>
<property name="valign">center</property>
<property name="sensitive">false</property>
<style> <style>
<class name="flat"/> <class name="flat"/>
<class name="dino-chatinput-button"/> <class name="dino-chatinput-button"/>

View file

@ -37,11 +37,6 @@
<property name="xalign">0</property> <property name="xalign">0</property>
</object> </object>
</child> </child>
<child>
<object class="GtkRevealer" id="time_revealer">
<property name="transition-type">slide-right</property>
<property name="transition-duration">50</property>
<property name="reveal-child">True</property>
<child> <child>
<object class="GtkLabel" id="time_label"> <object class="GtkLabel" id="time_label">
<property name="hexpand">False</property> <property name="hexpand">False</property>
@ -53,8 +48,6 @@
</child> </child>
</object> </object>
</child> </child>
</object>
</child>
<child> <child>
<object class="GtkBox"> <object class="GtkBox">
<property name="valign">end</property> <property name="valign">end</property>
@ -87,15 +80,10 @@
</attributes> </attributes>
</object> </object>
</child> </child>
<child>
<object class="GtkRevealer" id="top_row_revealer">
<property name="transition-type">slide-right</property>
<property name="transition-duration">50</property>
<property name="reveal-child">True</property>
<property name="margin-start">15</property>
<child> <child>
<object class="GtkBox"> <object class="GtkBox">
<property name="orientation">horizontal</property> <property name="orientation">horizontal</property>
<property name="margin-start">15</property>
<property name="spacing">6</property> <property name="spacing">6</property>
<child> <child>
<object class="GtkLabel" id="unread_count_label"> <object class="GtkLabel" id="unread_count_label">
@ -123,49 +111,6 @@
</child> </child>
</object> </object>
</child> </child>
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="GtkBox">
<property name="vexpand">True</property>
</object>
</child>
<child>
<object class="GtkRevealer" id="xbutton_revealer">
<property name="transition-type">slide-left</property>
<property name="transition-duration">100</property>
<property name="reveal-child">False</property>
<child>
<object class="GtkButton" id="x_button">
<property name="width-request">27</property>
<property name="height-request">27</property>
<property name="vexpand">False</property>
<property name="margin-start">5</property>
<style>
<class name="conversation_list_row_xbutton"/>
<class name="circular"/>
<class name="flat"/>
</style>
<child>
<object class="GtkImage">
<property name="icon-name">window-close-symbolic</property>
<property name="icon-size">1</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkBox">
<property name="vexpand">True</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object> </object>
</child> </child>
</template> </template>

View file

@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk" version="4.0"/>
<object class="GtkDialog" id="dialog">
<property name="can_focus">True</property>
<property name="modal">True</property>
<property name="default_width">320</property>
<property name="default_height">260</property>
<property name="resizable">False</property>
<child>
<object class="GtkBox">
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="margin-start">20</property>
<property name="margin-end">20</property>
<property name="margin-top">20</property>
<property name="margin-bottom">20</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel" id="default_encryption_warning_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">You are opening a new conversation without having set end-to-end encryption by default.
It is strongly recommended to enable it to prevent your messages from being read by third parties.
Please select an option to start this conversation with. Choosing one of the encryption methods will also set it as default in the global settings.
</property>
<property name="wrap">True</property>
</object>
</child>
<child>
<object class="GtkCheckButton" id="omemo">
<property name="label" translatable="yes">OMEMO (automatic setup)</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="active">False</property>
</object>
</child>
<child>
<object class="GtkCheckButton" id="openpgp">
<property name="label" translatable="yes">OpenPGP (external setup required)</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="active">False</property>
<property name="group">omemo</property>
</object>
</child>
<child>
<object class="GtkCheckButton" id="none">
<property name="label" translatable="yes">Unencrypted</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="active">False</property>
<property name="group">omemo</property>
</object>
</child>
<child>
<object class="GtkButton" id="accept_button">
<property name="label" translatable="yes">Apply</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="margin-top">20</property>
<style>
<class name="text-button"/>
<class name="suggested-action"/>
</style>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</interface>

View file

@ -11,11 +11,13 @@
<file>conversation_content_view/item_metadata_header.ui</file> <file>conversation_content_view/item_metadata_header.ui</file>
<file>conversation_content_view/view.ui</file> <file>conversation_content_view/view.ui</file>
<file>conversation_details.ui</file> <file>conversation_details.ui</file>
<file>conversation_details.css</file>
<file>conversation_item_widget.ui</file> <file>conversation_item_widget.ui</file>
<file>conversation_list_titlebar.ui</file> <file>conversation_list_titlebar.ui</file>
<file>conversation_list_titlebar_csd.ui</file> <file>conversation_list_titlebar_csd.ui</file>
<file>conversation_row.ui</file> <file>conversation_row.ui</file>
<file>conversation_view.ui</file> <file>conversation_view.ui</file>
<file>default_encryption_dialog.ui</file>
<file>dino-conversation-list-placeholder-arrow.svg</file> <file>dino-conversation-list-placeholder-arrow.svg</file>
<file>file_default_widget.ui</file> <file>file_default_widget.ui</file>
<file>file_send_overlay.ui</file> <file>file_send_overlay.ui</file>
@ -49,6 +51,8 @@
<file>icons/scalable/status/dino-status-chat.svg</file> <file>icons/scalable/status/dino-status-chat.svg</file>
<file>icons/scalable/status/dino-status-dnd.svg</file> <file>icons/scalable/status/dino-status-dnd.svg</file>
<file>icons/scalable/status/dino-status-online.svg</file> <file>icons/scalable/status/dino-status-online.svg</file>
<file>icons/scalable/status/dino-status-online-bright.svg</file>
<file>icons/scalable/status/dino-status-offline.svg</file>
<file>icons/scalable/status/dino-tick-symbolic.svg</file> <file>icons/scalable/status/dino-tick-symbolic.svg</file>
<file>icons/scalable/status/dino-video-off-symbolic.svg</file> <file>icons/scalable/status/dino-video-off-symbolic.svg</file>
<file>icons/scalable/status/dino-video-symbolic.svg</file> <file>icons/scalable/status/dino-video-symbolic.svg</file>

View file

@ -0,0 +1,11 @@
<?xml version="1.0"?>
<svg width="99.999997" height="99.999997" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" version="1.1">
<g class="layer">
<title>Layer 1</title>
<g id="svg_1">
<circle cx="50" cy="51" fill="#ff0000" fill-rule="evenodd" id="svg_2" r="50" stroke="#ff0000"/>
<path d="m20,51l60,0" fill="#ff0000" id="svg_3" stroke="#ff0000" stroke-linecap="round" stroke-width="15"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 444 B

View file

@ -0,0 +1,10 @@
<?xml version="1.0"?>
<svg width="99.999997" height="99.999997" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" version="1.1">
<g class="layer">
<title>Layer 1</title>
<g id="svg_1">
<circle cx="50" cy="50" fill="#00ff00" fill-rule="evenodd" id="svg_2" r="50"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 316 B

View file

@ -1,6 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0"?>
<svg width="28.222mm" height="28.222mm" version="1.1" viewBox="0 0 99.999997 99.999997" xmlns="http://www.w3.org/2000/svg"> <svg width="99.999997" height="99.999997" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" version="1.1">
<g transform="translate(-62.857 -678.08)">
<circle cx="112.86" cy="728.08" r="50" style="fill-rule:evenodd;fill:#81c784"/> <g class="layer">
<title>Layer 1</title>
<g id="svg_1">
<circle cx="50" cy="50" fill="#00ff00" fill-rule="evenodd" id="svg_2" r="50"/>
</g>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 302 B

After

Width:  |  Height:  |  Size: 316 B

View file

@ -4,7 +4,7 @@
<launchable type="desktop-id">im.dino.Dino.desktop</launchable> <launchable type="desktop-id">im.dino.Dino.desktop</launchable>
<metadata_license>CC0-1.0</metadata_license> <metadata_license>CC0-1.0</metadata_license>
<project_license>GPL-3.0+</project_license> <project_license>GPL-3.0+</project_license>
<name>Dino</name> <name>Dino+</name>
<summary>Modern XMPP Chat Client</summary> <summary>Modern XMPP Chat Client</summary>
<summary xml:lang="zh_TW">現代化的 XMPP 用戶端聊天軟件</summary> <summary xml:lang="zh_TW">現代化的 XMPP 用戶端聊天軟件</summary>
<summary xml:lang="zh_CN">现代 XMPP 聊天客户端</summary> <summary xml:lang="zh_CN">现代 XMPP 聊天客户端</summary>

View file

@ -4,7 +4,7 @@
<launchable type="desktop-id">im.dino.Dino.desktop</launchable> <launchable type="desktop-id">im.dino.Dino.desktop</launchable>
<metadata_license>CC0-1.0</metadata_license> <metadata_license>CC0-1.0</metadata_license>
<project_license>GPL-3.0+</project_license> <project_license>GPL-3.0+</project_license>
<name>Dino</name> <name>Dino+</name>
<summary>Modern XMPP Chat Client</summary> <summary>Modern XMPP Chat Client</summary>
<description> <description>
<p>Dino is a modern open-source chat client for the desktop. It focuses on providing a clean and reliable Jabber/XMPP experience while having your privacy in mind.</p> <p>Dino is a modern open-source chat client for the desktop. It focuses on providing a clean and reliable Jabber/XMPP experience while having your privacy in mind.</p>

View file

@ -6,7 +6,7 @@
<property name="margin-start">7</property> <property name="margin-start">7</property>
<property name="margin-bottom">3</property> <property name="margin-bottom">3</property>
<property name="margin-end">7</property> <property name="margin-end">7</property>
<property name="column-spacing">10</property> <property name="column-spacing">5</property>
<child> <child>
<object class="DinoUiAvatarPicture" id="picture"> <object class="DinoUiAvatarPicture" id="picture">
<property name="height-request">30</property> <property name="height-request">30</property>
@ -15,7 +15,8 @@
</child> </child>
<child> <child>
<object class="GtkLabel" id="name_label"> <object class="GtkLabel" id="name_label">
<property name="max_width_chars">1</property> <property name="width-chars">20</property>
<property name="max-width-chars">40</property>
<property name="ellipsize">end</property> <property name="ellipsize">end</property>
<property name="hexpand">1</property> <property name="hexpand">1</property>
<property name="xalign">0</property> <property name="xalign">0</property>

View file

@ -7,6 +7,49 @@
<property name="search-enabled">False</property> <property name="search-enabled">False</property>
<child> <child>
<object class="AdwPreferencesPage"> <object class="AdwPreferencesPage">
<child>
<object class="AdwPreferencesGroup">
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Default encryption for 1-on-1 chats</property>
<child>
<object class="GtkBox" id="default_encryption_box">
<child>
<object class="GtkCheckButton" id="encryption_radio_undecided">
<property name="label" translatable="yes">Ask</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="active">False</property>
<property name="group">encryption_radio_undecided</property>
</object>
</child>
<child>
<object class="GtkCheckButton" id="encryption_radio_omemo">
<property name="label" translatable="yes">OMEMO</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="active">False</property>
<property name="group">encryption_radio_undecided</property>
</object>
</child>
<child>
<object class="GtkCheckButton" id="encryption_radio_openpgp">
<property name="label" translatable="yes">OpenPGP</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="active">False</property>
<property name="group">encryption_radio_undecided</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
<child> <child>
<object class="AdwPreferencesGroup"> <object class="AdwPreferencesGroup">
<child> <child>
@ -68,6 +111,51 @@
</child> </child>
</object> </object>
</child> </child>
<child>
<object class="AdwPreferencesGroup">
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">_Display send button</property>
<property name="use-underline">True</property>
<property name="activatable-widget">send_button_switch</property>
<child type="suffix">
<object class="GtkSwitch" id="send_button_switch">
<property name="valign">center</property>
</object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">_Use Enter key to start a new line</property>
<property name="subtitle" translatable="yes">If disabled, use Shift+Enter to start a new line</property>
<property name="use-underline">True</property>
<property name="activatable-widget">enter_newline_switch</property>
<child type="suffix">
<object class="GtkSwitch" id="enter_newline_switch">
<property name="valign">center</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="AdwPreferencesGroup">
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">_Dark theme</property>
<property name="use-underline">True</property>
<property name="activatable-widget">dark_theme</property>
<child type="suffix">
<object class="GtkSwitch" id="dark_theme">
<property name="valign">center</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object> </object>
</child> </child>
</template> </template>

View file

@ -49,7 +49,11 @@ window.dino-main .dino-conversation .message-box.highlight:not(.highlight-once)
} }
window.dino-main .dino-conversation .message-box { window.dino-main .dino-conversation .message-box {
padding: 6px 15px 6px 15px; padding: 3px 15px 3px 15px;
}
window.dino-main .dino-conversation .has-skeleton {
margin-top: 10px;
} }
window.dino-main .dino-conversation .message-box:not(.has-skeleton) { window.dino-main .dino-conversation .message-box:not(.has-skeleton) {

View file

@ -104,6 +104,8 @@ sources += gnome.compile_resources(
c_args = [ c_args = [
'-DG_LOG_DOMAIN="dino"', '-DG_LOG_DOMAIN="dino"',
'-DGETTEXT_PACKAGE="dino"', '-DGETTEXT_PACKAGE="dino"',
'-D_POSIX_C_SOURCE=1',
'-D_FILE_OFFSET_BITS=64',
'-DLOCALE_INSTALL_DIR="@0@"'.format(get_option('prefix') / get_option('localedir')), '-DLOCALE_INSTALL_DIR="@0@"'.format(get_option('prefix') / get_option('localedir')),
] ]
vala_args = [ vala_args = [
@ -118,7 +120,15 @@ endif
if dep_gtk4.version() == 'unknown' or dep_gtk4.version().version_compare('>=4.8') if dep_gtk4.version() == 'unknown' or dep_gtk4.version().version_compare('>=4.8')
vala_args += ['-D', 'GTK_4_8'] vala_args += ['-D', 'GTK_4_8']
endif endif
exe_dino = executable('dino', sources, c_args: c_args, vala_args: vala_args, dependencies: dependencies, install: true)
link_args = []
if host_machine.system() == 'windows'
link_args += ['-Wl,--export-all-symbols']
windows = import('windows')
sources += windows.compile_resources('dino-info.rc')
endif
exe_dino = executable('dino', sources, c_args: c_args, vala_args: vala_args, dependencies: dependencies, install: true, link_args: link_args, win_subsystem: 'windows')
install_data('data/icons/scalable/apps/im.dino.Dino-symbolic.svg', install_dir: get_option('datadir') / 'hicolor/symbolic/apps') install_data('data/icons/scalable/apps/im.dino.Dino-symbolic.svg', install_dir: get_option('datadir') / 'hicolor/symbolic/apps')
install_data('data/icons/scalable/apps/im.dino.Dino.svg', install_dir: get_option('datadir') / 'hicolor/scalable/apps') install_data('data/icons/scalable/apps/im.dino.Dino.svg', install_dir: get_option('datadir') / 'hicolor/scalable/apps')

View file

@ -533,12 +533,10 @@ msgid "Local Settings"
msgstr "" msgstr ""
#: main/src/ui/contact_details/settings_provider.vala:28 #: main/src/ui/contact_details/settings_provider.vala:28
#: main/data/settings_dialog.ui:23
msgid "Send typing notifications" msgid "Send typing notifications"
msgstr "" msgstr ""
#: main/src/ui/contact_details/settings_provider.vala:33 #: main/src/ui/contact_details/settings_provider.vala:33
#: main/data/settings_dialog.ui:32
msgid "Send read receipts" msgid "Send read receipts"
msgstr "" msgstr ""
@ -1012,16 +1010,48 @@ msgstr ""
msgid "Add Contact" msgid "Add Contact"
msgstr "" msgstr ""
#: main/data/settings_dialog.ui:41 #: main/data/settings_dialog.ui:14
msgid "Default encryption for 1-on-1 chats"
msgstr ""
#: main/data/settings_dialog.ui:19
msgid "Ask"
msgstr ""
#: main/data/settings_dialog.ui:57
msgid "Send _Typing Notifications"
msgstr ""
#: main/data/settings_dialog.ui:85
msgid "_Notifications"
msgstr ""
#: main/data/settings_dialog.ui:69
msgid "Send _Read Receipts"
msgstr ""
#: main/data/settings_dialog.ui:86
msgid "Notify when a new message arrives" msgid "Notify when a new message arrives"
msgstr "" msgstr ""
#: main/data/settings_dialog.ui:50 #: main/data/settings_dialog.ui:102
msgid "Convert smileys to emojis" msgid "_Convert Smileys to Emoji"
msgstr "" msgstr ""
#: main/data/settings_dialog.ui:59 #: main/data/settings_dialog.ui:118
msgid "Check spelling" msgid "_Display send button"
msgstr ""
#: main/data/settings_dialog.ui:130
msgid "_Use Enter key to start a new line"
msgstr ""
#: main/data/settings_dialog.ui:131
msgid "If disabled, use Shift+Enter to start a new line"
msgstr ""
#: main/data/settings_dialog.ui:147
msgid "_Dark theme"
msgstr "" msgstr ""
#: main/data/im.dino.Dino.appdata.xml.in:7 #: main/data/im.dino.Dino.appdata.xml.in:7

View file

@ -47,6 +47,7 @@ msgstr "доставка не удалась"
#: main/src/ui/chat_input/encryption_button.vala:26 #: main/src/ui/chat_input/encryption_button.vala:26
#: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:163 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:163
#: main/data/menu_encryption.ui:14 #: main/data/menu_encryption.ui:14
#: main/data/default_encryption_dialog.ui:57
msgid "Unencrypted" msgid "Unencrypted"
msgstr "Не зашифровано" msgstr "Не зашифровано"
@ -562,7 +563,7 @@ msgstr "Уведомления"
#: main/src/ui/contact_details/settings_provider.vala:55 #: main/src/ui/contact_details/settings_provider.vala:55
msgid "Pin conversation" msgid "Pin conversation"
msgstr "" msgstr "Закрепить беседу"
#: main/src/ui/contact_details/settings_provider.vala:55 #: main/src/ui/contact_details/settings_provider.vala:55
msgid "Pins the conversation to the top of the conversation list" msgid "Pins the conversation to the top of the conversation list"
@ -825,16 +826,16 @@ msgstr "Прочтите %s, чтобы узнать о процессе авт
#: main/src/ui/conversation_content_view/message_widget.vala:213 #: main/src/ui/conversation_content_view/message_widget.vala:213
msgid "Edit message" msgid "Edit message"
msgstr "" msgstr "Редактировать сообщение"
#: main/src/ui/conversation_content_view/reactions_widget.vala:102 #: main/src/ui/conversation_content_view/reactions_widget.vala:102
msgid "You" msgid "You"
msgstr "" msgstr "Вы"
#: main/src/ui/conversation_content_view/reactions_widget.vala:128 #: main/src/ui/conversation_content_view/reactions_widget.vala:128
#: main/src/ui/conversation_content_view/item_actions.vala:8 #: main/src/ui/conversation_content_view/item_actions.vala:8
msgid "Add reaction" msgid "Add reaction"
msgstr "" msgstr "Добавить реакцию"
#: main/src/ui/conversation_content_view/file_image_widget.vala:53 #: main/src/ui/conversation_content_view/file_image_widget.vala:53
#: main/src/ui/conversation_content_view/file_default_widget.vala:57 #: main/src/ui/conversation_content_view/file_default_widget.vala:57
@ -946,7 +947,7 @@ msgstr "несколько секунд"
#: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:191 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:191
msgid "Delivered" msgid "Delivered"
msgstr "" msgstr "Доставленo"
#: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:195 #: main/src/ui/conversation_content_view/conversation_item_skeleton.vala:195
msgid "Read" msgid "Read"
@ -958,15 +959,15 @@ msgstr "Этот контакт хочет добавить вас в свой
#: main/src/ui/conversation_content_view/item_actions.vala:21 #: main/src/ui/conversation_content_view/item_actions.vala:21
msgid "This conversation does not support reactions." msgid "This conversation does not support reactions."
msgstr "" msgstr "Этот разговор не поддерживает реакции."
#: main/src/ui/conversation_content_view/item_actions.vala:33 #: main/src/ui/conversation_content_view/item_actions.vala:33
msgid "Reply" msgid "Reply"
msgstr "" msgstr "Ответить"
#: main/src/ui/conversation_content_view/item_actions.vala:43 #: main/src/ui/conversation_content_view/item_actions.vala:43
msgid "This conversation does not support replies." msgid "This conversation does not support replies."
msgstr "" msgstr "Этот разговор не поддерживает ответы"
#: main/src/ui/conversation_content_view/file_default_widget.vala:64 #: main/src/ui/conversation_content_view/file_default_widget.vala:64
#, c-format #, c-format
@ -1005,7 +1006,7 @@ msgstr "Тут пустовато"
#: main/data/unified_main_content.ui:47 #: main/data/unified_main_content.ui:47
msgid "Click + to start a chat or join a channel" msgid "Click + to start a chat or join a channel"
msgstr "" msgstr "Нажмите +, чтобы начать чат или присоединиться к каналу"
#: main/data/add_conversation/conference_details_fragment.ui:20 #: main/data/add_conversation/conference_details_fragment.ui:20
#: main/data/add_conversation/add_groupchat_dialog.ui:40 #: main/data/add_conversation/add_groupchat_dialog.ui:40
@ -1027,17 +1028,49 @@ msgstr "Псевдоним"
msgid "Add Contact" msgid "Add Contact"
msgstr "Добавить контакт" msgstr "Добавить контакт"
#: main/data/settings_dialog.ui:41 #: main/data/settings_dialog.ui:14
msgid "Default encryption for 1-on-1 chats"
msgstr "Шифрование по умолчанию для 1-на-1 чатов"
#: main/data/settings_dialog.ui:19
msgid "Ask"
msgstr "Спрашивать"
#: main/data/settings_dialog.ui:57
msgid "Send _Typing Notifications"
msgstr "Отправлять уведомления при наборе сообщения"
#: main/data/settings_dialog.ui:85
msgid "_Notifications"
msgstr "Уведомления"
#: main/data/settings_dialog.ui:69
msgid "Send _Read Receipts"
msgstr "Отправлять уведомления о прочтении"
#: main/data/settings_dialog.ui:86
msgid "Notify when a new message arrives" msgid "Notify when a new message arrives"
msgstr "Уведомлять о новых сообщениях" msgstr "Уведомлять о новых сообщениях"
#: main/data/settings_dialog.ui:50 #: main/data/settings_dialog.ui:102
msgid "Convert smileys to emojis" msgid "_Convert Smileys to Emoji"
msgstr "Превращать смайлы в эмодзи" msgstr "Превращать смайлы в эмодзи"
#: main/data/settings_dialog.ui:59 #: main/data/settings_dialog.ui:118
msgid "Check spelling" msgid "_Display send button"
msgstr "Проверка орфографии" msgstr "Показывать кнопку отправки"
#: main/data/settings_dialog.ui:130
msgid "_Use Enter key to start a new line"
msgstr "Использовать Enter для вставки новой строки"
#: main/data/settings_dialog.ui:131
msgid "If disabled, use Shift+Enter to start a new line"
msgstr "Если опция недоступна, используйте Shift+Enter для вставки новой строки"
#: main/data/settings_dialog.ui:147
msgid "_Dark theme"
msgstr "Тёмная тема"
#: main/data/im.dino.Dino.appdata.xml.in:7 #: main/data/im.dino.Dino.appdata.xml.in:7
msgid "Modern XMPP Chat Client" msgid "Modern XMPP Chat Client"
@ -1184,6 +1217,28 @@ msgstr "Новый пароль"
msgid "Confirm new password" msgid "Confirm new password"
msgstr "Подтверждение пароля" msgstr "Подтверждение пароля"
#: main/data/default_encryption_dialog.ui :27
msgid ""
"You are opening a new conversation without having set end-to-end encryption by default.\n\n"
"It is strongly recommended to enable it to prevent your messages from being read by third parties.\n\n"
"Please select an option to start this conversation with. Choosing one of the encryption methods will also set it as default in the global settings.\n"
msgstr ""
"Вы начинаете новый диалог без установления настроек сквозного шифрования по умолчанию.\n\n"
"Крайне рекомендуется включить его, чтобы исключить перехват ваших сообщений злоумышленниками.\n\n"
"Пожалуйста, выберите с какими настройками шифрования начать этот диалог. Выбирая одну из них, вы установите эту же настройку глобально по умолчанию для других диалогов.\n"
#: main/data/default_encryption_dialog.ui :38
msgid "OMEMO (automatic setup)"
msgstr "OMEMO (настроится автоматически)"
#: main/data/default_encryption_dialog.ui :47
msgid "OpenPGP (external setup required)"
msgstr "OpenPGP (потребует ручной настройки)"
#: main/data/default_encryption_dialog.ui :67
msgid "Apply"
msgstr "Применить"
#~ msgid "Click here to start a conversation or join a channel." #~ msgid "Click here to start a conversation or join a channel."
#~ msgstr "Нажмите здесь, чтобы начать беседу или присоединиться к каналу." #~ msgstr "Нажмите здесь, чтобы начать беседу или присоединиться к каналу."

View file

@ -34,7 +34,13 @@ protected class RosterList {
foreach (ulong handler_id in handler_ids) stream_interactor.get_module(RosterManager.IDENTITY).disconnect(handler_id); foreach (ulong handler_id in handler_ids) stream_interactor.get_module(RosterManager.IDENTITY).disconnect(handler_id);
}); });
foreach (Account a in accounts) fetch_roster_items(a); foreach (Account a in accounts) {
ListRow own_account_row = new ListRow.from_jid(stream_interactor, a.bare_jid, a, accounts.size > 1);
ListBoxRow own_account_lbrow = new ListBoxRow() { child = own_account_row };
list_box.append(own_account_lbrow);
fetch_roster_items(a);
}
} }
private void on_removed_roster_item(Account account, Jid jid, Roster.Item roster_item) { private void on_removed_roster_item(Account account, Jid jid, Roster.Item roster_item) {

View file

@ -29,7 +29,10 @@ public class Dino.Ui.Application : Adw.Application, Dino.Application {
public Application() throws Error { public Application() throws Error {
Object(application_id: "im.dino.Dino", flags: ApplicationFlags.HANDLES_OPEN); Object(application_id: "im.dino.Dino", flags: ApplicationFlags.HANDLES_OPEN);
init();
var style_manager = Adw.StyleManager.get_default();
bool system_dark_theme = style_manager.system_supports_color_schemes && style_manager.dark;
init(system_dark_theme);
Environment.set_application_name("Dino"); Environment.set_application_name("Dino");
Window.set_default_icon_name("im.dino.Dino"); Window.set_default_icon_name("im.dino.Dino");
@ -68,6 +71,19 @@ public class Dino.Ui.Application : Adw.Application, Dino.Application {
} }
} }
}); });
settings.dark_theme_update.connect((is_dark) => {
var manager = Adw.StyleManager.get_default();
if (is_dark != manager.dark) {
if (is_dark) {
manager.set_color_scheme(Adw.ColorScheme.FORCE_DARK);
} else {
manager.set_color_scheme(Adw.ColorScheme.FORCE_LIGHT);
}
}
});
settings.dark_theme_update(settings.dark_theme); // Change theme at startup.
}); });
activate.connect(() => { activate.connect(() => {
@ -259,21 +275,21 @@ public class Dino.Ui.Application : Adw.Application, Dino.Application {
#if Adw_1_2 #if Adw_1_2
Adw.AboutWindow about_window = new Adw.AboutWindow(); Adw.AboutWindow about_window = new Adw.AboutWindow();
about_window.application_icon = "im.dino.Dino"; about_window.application_icon = "im.dino.Dino";
about_window.application_name = "Dino"; about_window.application_name = "Dino+";
about_window.issue_url = "https://github.com/dino/dino/issues"; about_window.issue_url = "https://github.com/mxlgv/dino/issues";
#else #else
Gtk.AboutDialog about_window = new Gtk.AboutDialog(); Gtk.AboutDialog about_window = new Gtk.AboutDialog();
about_window.logo_icon_name = "im.dino.Dino"; about_window.logo_icon_name = "im.dino.Dino";
about_window.program_name = "Dino"; about_window.program_name = "Dino+";
about_window.website_label = "dino.im"; about_window.website_label = "dino.im";
#endif #endif
about_window.destroy_with_parent = true; about_window.destroy_with_parent = true;
about_window.transient_for = window; about_window.transient_for = window;
about_window.modal = true; about_window.modal = true;
about_window.title = _("About Dino"); about_window.title = _("About Dino+");
about_window.version = version; about_window.version = version;
about_window.website = "https://dino.im/"; about_window.website = "https://github.com/mxlgv/dino/";
about_window.copyright = "Copyright © 2016-2023 - Dino Team"; about_window.copyright = "Copyright © 2016-2024 - Dino and Dino+ Teams";
about_window.license_type = License.GPL_3_0; about_window.license_type = License.GPL_3_0;
if (!use_csd()) { if (!use_csd()) {

View file

@ -103,7 +103,7 @@ public class ChatInputController : Object {
private void on_encryption_changed(Encryption encryption) { private void on_encryption_changed(Encryption encryption) {
reset_input_field_status(); reset_input_field_status();
if (encryption == Encryption.NONE) return; if (encryption == Encryption.NONE || encryption == Encryption.UNKNOWN) return;
Application app = GLib.Application.get_default() as Application; Application app = GLib.Application.get_default() as Application;
var encryption_entry = app.plugin_registry.encryption_list_entries[encryption]; var encryption_entry = app.plugin_registry.encryption_list_entries[encryption];

View file

@ -101,7 +101,10 @@ public class ChatTextView : Box {
return true; return true;
} }
if ((state & ModifierType.SHIFT_MASK) > 0) { Dino.Entities.Settings settings = Dino.Application.get_default().settings;
if ((state & ModifierType.SHIFT_MASK) > 0
|| settings.enter_newline) {
text_view.buffer.insert_at_cursor("\n", 1); text_view.buffer.insert_at_cursor("\n", 1);
} else if (text_view.buffer.text.strip() != "") { } else if (text_view.buffer.text.strip() != "") {
send_text(); send_text();

View file

@ -107,7 +107,7 @@ public class OccupantsTabCompletor {
Gee.List<string> ret = generate_completions_from_messages(prefix); Gee.List<string> ret = generate_completions_from_messages(prefix);
// Then, suggest other nicks in alphabetical order // Then, suggest other nicks in alphabetical order
Gee.List<Jid>? occupants = stream_interactor.get_module(MucManager.IDENTITY).get_other_occupants(conversation.counterpart, conversation.account); Gee.List<Jid>? occupants = stream_interactor.get_module(MucManager.IDENTITY).get_other_members(conversation.counterpart, conversation.account);
Gee.List<string> filtered_occupants = new ArrayList<string>(); Gee.List<string> filtered_occupants = new ArrayList<string>();
if (occupants != null) { if (occupants != null) {
foreach (Jid jid in occupants) { foreach (Jid jid in occupants) {

View file

@ -27,6 +27,7 @@ public class View : Box {
[GtkChild] public unowned MenuButton encryption_button; [GtkChild] public unowned MenuButton encryption_button;
[GtkChild] public unowned Separator file_separator; [GtkChild] public unowned Separator file_separator;
[GtkChild] public unowned Label chat_input_status; [GtkChild] public unowned Label chat_input_status;
[GtkChild] public unowned Button send_button;
public EncryptionButton encryption_widget; public EncryptionButton encryption_widget;
@ -47,6 +48,27 @@ public class View : Box {
Util.force_css(frame, "* { border-radius: 3px; }"); Util.force_css(frame, "* { border-radius: 3px; }");
Dino.Entities.Settings settings = Dino.Application.get_default().settings;
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.visible = settings.send_button;
settings.send_button_update.connect((visible) => {
send_button.visible = visible;
});
send_button.clicked.connect(() => {
chat_text_view.send_text();
});
return this; return this;
} }

View file

@ -28,9 +28,21 @@ public class HistoryProvider : Plugins.ContactDetailsProvider, Object {
entity_info.has_feature.begin(conversation.account, conversation.counterpart, Xmpp.MessageArchiveManagement.NS_URI, (_, res) => { entity_info.has_feature.begin(conversation.account, conversation.counterpart, Xmpp.MessageArchiveManagement.NS_URI, (_, res) => {
bool can_do_mam = entity_info.has_feature.end(res); bool can_do_mam = entity_info.has_feature.end(res);
if (can_do_mam) { if (can_do_mam) {
Button resync_button = new Button.with_label(RESYNC_LABEL); Button resync_button = new Button.with_label(RESYNC_LABEL);
contact_details.add("Permissions", RESYNC_DESC_LABEL, "", resync_button); Stack resync_stack = new Stack();
Gtk.Spinner spinner = new Gtk.Spinner();
resync_stack.visible = true;
contact_details.add("Permissions", RESYNC_DESC_LABEL, "", resync_stack);
resync_stack.add_child(spinner);
resync_stack.add_child(resync_button);
resync_stack.set_visible_child(resync_button);
resync_button.clicked.connect(() => { resync_button.clicked.connect(() => {
resync_stack.set_visible_child(spinner);
spinner.start();
if (!sync_cancellables.has_key(conversation.account)) { if (!sync_cancellables.has_key(conversation.account)) {
sync_cancellables[conversation.account] = new HashMap<Jid, Cancellable>(); sync_cancellables[conversation.account] = new HashMap<Jid, Cancellable>();
} }
@ -40,6 +52,8 @@ public class HistoryProvider : Plugins.ContactDetailsProvider, Object {
var history_sync = stream_interactor.get_module(MessageProcessor.IDENTITY).history_sync; var history_sync = stream_interactor.get_module(MessageProcessor.IDENTITY).history_sync;
history_sync.fetch_history.begin(conversation.account, conversation.counterpart.bare_jid, sync_cancellables[conversation.account][conversation.counterpart.bare_jid], (_, res) => { history_sync.fetch_history.begin(conversation.account, conversation.counterpart.bare_jid, sync_cancellables[conversation.account][conversation.counterpart.bare_jid], (_, res) => {
history_sync.fetch_everything.end(res); history_sync.fetch_everything.end(res);
spinner.stop();
resync_stack.set_visible_child(resync_button);
sync_cancellables[conversation.account].unset(conversation.counterpart.bare_jid); sync_cancellables[conversation.account].unset(conversation.counterpart.bare_jid);
}); });
} }

View file

@ -564,11 +564,31 @@ public class ConversationView : Widget, Plugins.ConversationItemCollection, Plug
private void load_earlier_messages(bool request_from_server = true) { private void load_earlier_messages(bool request_from_server = true) {
was_value = scrolled.vadjustment.value; was_value = scrolled.vadjustment.value;
debug("loading earlier messages"); debug("loading earlier messages");
if (!reloading_mutex.trylock()) { if (!reloading_mutex.trylock()) {
return; return;
} }
if (content_items.size > 0) { if (content_items.size == 0) {
// List is empty, try load local data
Gee.List<ContentMetaItem> items = content_populator.populate_latest(conversation, 20);
debug("inserting new messages, size: %d", items.size);
foreach (ContentMetaItem item in items) {
do_insert_item(item);
}
if (request_from_server) {
// If the list is empty also try to load data from the server
var history_sync = stream_interactor.get_module(MessageProcessor.IDENTITY).history_sync;
history_sync.fetch_conversation_data.begin(conversation, new DateTime.now(), (_, res) => {
history_sync.fetch_conversation_data.end(res);
// Request finished, reload messages
load_earlier_messages(false);
});
}
} else if (content_items.size > 0) {
// List is not empty, fetch data before the latest available message
Gee.List<ContentMetaItem> items = content_populator.populate_before(conversation, ((ContentMetaItem) content_items.first()).content_item, 20, request_from_server); Gee.List<ContentMetaItem> items = content_populator.populate_before(conversation, ((ContentMetaItem) content_items.first()).content_item, 20, request_from_server);
debug("inserting new messages, size: %d", items.size); debug("inserting new messages, size: %d", items.size);
foreach (ContentMetaItem item in items) { foreach (ContentMetaItem item in items) {

View file

@ -39,7 +39,8 @@ public class FileDefaultWidget : Box {
}); });
} }
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; this.state = state;
spinner.stop(); // A hidden spinning spinner still uses CPU. Deactivate asap spinner.stop(); // A hidden spinning spinner still uses CPU. Deactivate asap
@ -49,7 +50,7 @@ public class FileDefaultWidget : Box {
switch (state) { switch (state) {
case FileTransfer.State.COMPLETE: case FileTransfer.State.COMPLETE:
mime_label.label = mime_description; mime_label.label = _("%s offered: %s").printf(mime_description, get_size_string(size));
image_stack.set_visible_child_name("content_type_image"); image_stack.set_visible_child_name("content_type_image");
// Create a menu // Create a menu
@ -61,7 +62,17 @@ public class FileDefaultWidget : Box {
popover_menu.closed.connect(on_pointer_left); popover_menu.closed.connect(on_pointer_left);
break; break;
case FileTransfer.State.IN_PROGRESS: 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.start(); spinner.start();
image_stack.set_visible_child_name("spinner"); image_stack.set_visible_child_name("spinner");

View file

@ -73,6 +73,9 @@ public class FileImageWidget : Box {
image_overlay_toolbar.visible = false; image_overlay_toolbar.visible = false;
}); });
// Set tooltip to display the file name on hover
image.set_tooltip_text(file_name);
this.append(overlay); this.append(overlay);
} }
} }

View file

@ -89,7 +89,9 @@ public class FileWidget : SizeRequestBox {
} }
private async void update_widget() { private async void update_widget() {
if (show_image() && state != State.IMAGE) { if (show_image() && state != State.IMAGE
&& file_transfer.state == FileTransfer.State.COMPLETE
&& file_transfer.get_file().query_exists()) {
var content_bak = content; var content_bak = content;
FileImageWidget file_image_widget = null; FileImageWidget file_image_widget = null;
@ -108,7 +110,8 @@ public class FileWidget : SizeRequestBox {
} catch (Error e) { } } catch (Error e) { }
} }
if (!show_image() && state != State.DEFAULT) { if (state != State.DEFAULT ||
(file_transfer.state == FileTransfer.State.COMPLETE && !file_transfer.get_file().query_exists())) {
if (content != null) this.remove(content); if (content != null) this.remove(content);
FileDefaultWidget default_file_widget = new FileDefaultWidget(); FileDefaultWidget default_file_widget = new FileDefaultWidget();
default_widget_controller = new FileDefaultWidgetController(default_file_widget); default_widget_controller = new FileDefaultWidgetController(default_file_widget);
@ -175,11 +178,17 @@ public class FileWidgetController : Object {
} }
private void open_file() { private void open_file() {
if (file_transfer.get_file().query_exists()) {
try { try {
Dino.Util.launch_default_for_uri(file_transfer.get_file().get_uri()); Dino.Util.launch_default_for_uri(file_transfer.get_file().get_uri());
} catch (Error err) { } catch (Error err) {
warning("Failed to open %s - %s", file_transfer.get_file().get_uri(), err.message); warning("Failed to open %s - %s", file_transfer.get_file().get_uri(), err.message);
} }
} else {
warning("File %s does not exist", file_transfer.get_file().get_uri());
file_transfer.state = FileTransfer.State.NOT_STARTED;
widget.activate_action("file.download", null);
}
} }
private void save_file() { private void save_file() {
@ -215,6 +224,7 @@ public class FileDefaultWidgetController : Object {
private FileTransfer? file_transfer; private FileTransfer? file_transfer;
public string file_transfer_state { get; set; } public string file_transfer_state { get; set; }
public string file_transfer_mime_type { get; set; } public string file_transfer_mime_type { get; set; }
public uint64 file_transfer_transferred_bytes { get; set; }
private FileTransfer.State state; private FileTransfer.State state;
@ -225,6 +235,7 @@ public class FileDefaultWidgetController : Object {
this.notify["file-transfer-state"].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-mime-type"].connect(update_file_info);
this.notify["file-transfer-transferred-bytes"].connect(update_file_info);
} }
public void set_file_transfer(FileTransfer file_transfer) { public void set_file_transfer(FileTransfer file_transfer) {
@ -234,13 +245,19 @@ public class FileDefaultWidgetController : Object {
file_transfer.bind_property("state", this, "file-transfer-state"); file_transfer.bind_property("state", this, "file-transfer-state");
file_transfer.bind_property("mime-type", this, "file-transfer-mime-type"); file_transfer.bind_property("mime-type", this, "file-transfer-mime-type");
file_transfer.bind_property("transferred-bytes", this, "file-transfer-transferred-bytes");
update_file_info(); update_file_info();
} }
private void update_file_info() { private void update_file_info() {
state = file_transfer.state; state = file_transfer.state;
widget.update_file_info(file_transfer.mime_type, file_transfer.state, file_transfer.size); if (state == FileTransfer.State.COMPLETE && !file_transfer.get_file().query_exists()) {
state = FileTransfer.State.NOT_STARTED;
file_transfer.state = FileTransfer.State.NOT_STARTED;
}
widget.update_file_info(file_transfer.mime_type, file_transfer.transferred_bytes,
file_transfer.direction, state, file_transfer.size);
} }
private void on_clicked() { private void on_clicked() {
@ -249,10 +266,11 @@ public class FileDefaultWidgetController : Object {
widget.activate_action("file.open", null); widget.activate_action("file.open", null);
break; break;
case FileTransfer.State.NOT_STARTED: case FileTransfer.State.NOT_STARTED:
case FileTransfer.State.FAILED:
widget.activate_action("file.download", null); widget.activate_action("file.download", null);
break; break;
default: default:
// Clicking doesn't do anything in FAILED and IN_PROGRESS states // Clicking doesn't do anything in IN_PROGRESS state
break; break;
} }
} }

View file

@ -14,6 +14,7 @@ namespace Dino.Ui.ConversationDetails {
if (conversation.type_ == Conversation.Type.GROUPCHAT) { if (conversation.type_ == Conversation.Type.GROUPCHAT) {
stream_interactor.get_module(MucManager.IDENTITY).get_config_form.begin(conversation.account, conversation.counterpart, (_, res) => { stream_interactor.get_module(MucManager.IDENTITY).get_config_form.begin(conversation.account, conversation.counterpart, (_, res) => {
model.data_form = stream_interactor.get_module(MucManager.IDENTITY).get_config_form.end(res); model.data_form = stream_interactor.get_module(MucManager.IDENTITY).get_config_form.end(res);
if (model.data_form == null) return;
model.data_form_bak = model.data_form.stanza_node.to_string(); model.data_form_bak = model.data_form.stanza_node.to_string();
}); });
} }

View file

@ -18,10 +18,6 @@ public class ConversationSelectorRow : ListBoxRow {
[GtkChild] protected unowned Label nick_label; [GtkChild] protected unowned Label nick_label;
[GtkChild] protected unowned Label message_label; [GtkChild] protected unowned Label message_label;
[GtkChild] protected unowned Label unread_count_label; [GtkChild] protected unowned Label unread_count_label;
[GtkChild] protected unowned Button x_button;
[GtkChild] protected unowned Revealer time_revealer;
[GtkChild] protected unowned Revealer xbutton_revealer;
[GtkChild] protected unowned Revealer top_row_revealer;
[GtkChild] protected unowned Image pinned_image; [GtkChild] protected unowned Image pinned_image;
[GtkChild] public unowned Revealer main_revealer; [GtkChild] public unowned Revealer main_revealer;
@ -98,9 +94,6 @@ public class ConversationSelectorRow : ListBoxRow {
last_content_item = stream_interactor.get_module(ContentItemStore.IDENTITY).get_latest(conversation); last_content_item = stream_interactor.get_module(ContentItemStore.IDENTITY).get_latest(conversation);
x_button.clicked.connect(() => {
stream_interactor.get_module(ConversationManager.IDENTITY).close_conversation(conversation);
});
picture.model = new ViewModel.CompatAvatarPictureModel(stream_interactor).set_conversation(conversation); picture.model = new ViewModel.CompatAvatarPictureModel(stream_interactor).set_conversation(conversation);
conversation.notify["read-up-to-item"].connect(() => update_read()); conversation.notify["read-up-to-item"].connect(() => update_read());
conversation.notify["pinned"].connect(() => { update_pinned_icon(); }); conversation.notify["pinned"].connect(() => { update_pinned_icon(); });
@ -270,19 +263,6 @@ public class ConversationSelectorRow : ListBoxRow {
} }
} }
public override void state_flags_changed(StateFlags flags) {
StateFlags curr_flags = get_state_flags();
if ((curr_flags & StateFlags.PRELIGHT) != 0) {
time_revealer.set_reveal_child(false);
top_row_revealer.set_reveal_child(false);
xbutton_revealer.set_reveal_child(true);
} else {
time_revealer.set_reveal_child(true);
top_row_revealer.set_reveal_child(true);
xbutton_revealer.set_reveal_child(false);
}
}
private static Regex dino_resource_regex = /^dino\.[a-f0-9]{8}$/; private static Regex dino_resource_regex = /^dino\.[a-f0-9]{8}$/;
private Widget generate_tooltip() { private Widget generate_tooltip() {

View file

@ -11,29 +11,41 @@ class MenuEntry : Plugins.ConversationTitlebarEntry, Object {
StreamInteractor stream_interactor; StreamInteractor stream_interactor;
private Conversation? conversation; private Conversation? conversation;
Button button = new Button() { icon_name="view-more-symbolic" }; MenuButton button = new MenuButton() { icon_name="view-more-symbolic" };
public MenuEntry(StreamInteractor stream_interactor) { public MenuEntry(StreamInteractor stream_interactor) {
this.stream_interactor = stream_interactor; this.stream_interactor = stream_interactor;
button.clicked.connect(on_clicked); Menu menu_model = new Menu();
menu_model.append(_("Conversation Details"), "conversation.details");
menu_model.append(_("Close Conversation"), "conversation.close");
Gtk.PopoverMenu popover_menu = new Gtk.PopoverMenu.from_model(menu_model);
button.popover = popover_menu;
SimpleActionGroup action_group = new SimpleActionGroup();
SimpleAction details_action = new SimpleAction("details", null);
details_action.activate.connect((parameter) => {
open_conversation_details();
});
action_group.insert(details_action);
SimpleAction close_action = new SimpleAction("close", null);
close_action.activate.connect((parameter) => {
stream_interactor.get_module(ConversationManager.IDENTITY).close_conversation(conversation);
});
action_group.insert(close_action);
button.insert_action_group("conversation", action_group);
} }
public new void set_conversation(Conversation conversation) { public new void set_conversation(Conversation conversation) {
button.sensitive = true; button.sensitive = true;
this.conversation = conversation; this.conversation = conversation;
if (conversation.type_ == Conversation.Type.GROUPCHAT) {
button.tooltip_text = Util.string_if_tooltips_active("Channel details");
} else {
button.tooltip_text = Util.string_if_tooltips_active("Conversation details");
}
} }
public new void unset_conversation() { public new void unset_conversation() {
button.sensitive = false; button.sensitive = false;
} }
private void on_clicked() { private void open_conversation_details() {
var conversation_details = ConversationDetails.setup_dialog(conversation, stream_interactor, (Window)button.get_root()); var conversation_details = ConversationDetails.setup_dialog(conversation, stream_interactor, (Window)button.get_root());
conversation_details.present(); conversation_details.present();
} }

View file

@ -114,6 +114,61 @@ public class ConversationViewController : Object {
((Gtk.Window)view.get_root()).add_shortcut(shortcut); ((Gtk.Window)view.get_root()).add_shortcut(shortcut);
} }
private void update_conversation_encryption(Conversation? conversation) {
if (conversation == null) {
return;
}
if (conversation.type_ == Conversation.Type.CHAT && conversation.encryption == UNKNOWN) {
Dino.Entities.Settings settings = Dino.Application.get_default().settings;
if (settings.default_encryption == UNKNOWN) {
var selection_dialog_builder = new Builder.from_resource("/im/dino/Dino/default_encryption_dialog.ui");
var selection_dialog = selection_dialog_builder.get_object("dialog") as Dialog;
var accept_button = selection_dialog_builder.get_object("accept_button") as Button;
var omemo_radio = selection_dialog_builder.get_object("omemo") as CheckButton;
var openpgp_radio = selection_dialog_builder.get_object("openpgp") as CheckButton;
var none_radio = selection_dialog_builder.get_object("none") as CheckButton;
selection_dialog.set_transient_for(view.get_root() as Window);
Encryption selected_default = UNKNOWN;
accept_button.sensitive = false;
omemo_radio.toggled.connect(() => {
accept_button.sensitive = true;
});
openpgp_radio.toggled.connect(() => {
accept_button.sensitive = true;
});
none_radio.toggled.connect(() => {
accept_button.sensitive = true;
});
accept_button.clicked.connect(() => {
if (omemo_radio.active) {selected_default = OMEMO;}
else if (openpgp_radio.active) {selected_default = PGP;}
else if (none_radio.active) {selected_default = NONE;}
selection_dialog.response(selected_default);
selection_dialog.close();
});
selection_dialog.response.connect((response_id) => {
if (response_id >= 0) {
conversation.encryption = response_id;
}
});
selection_dialog.show();
}
else {
conversation.encryption = settings.default_encryption;
}
}
}
public void select_conversation(Conversation? conversation, bool default_initialize_conversation) { public void select_conversation(Conversation? conversation, bool default_initialize_conversation) {
if (this.conversation != null) { if (this.conversation != null) {
conversation.notify["encryption"].disconnect(update_file_upload_status); conversation.notify["encryption"].disconnect(update_file_upload_status);
@ -124,6 +179,8 @@ public class ConversationViewController : Object {
this.conversation = conversation; this.conversation = conversation;
update_conversation_encryption(conversation);
// Set list model onto list view // Set list model onto list view
// Dino.Application app = GLib.Application.get_default() as Dino.Application; // Dino.Application app = GLib.Application.get_default() as Dino.Application;
// var map_list_model = get_conversation_content_model(new ContentItemMetaModel(app.db, conversation, stream_interactor), stream_interactor); // var map_list_model = get_conversation_content_model(new ContentItemMetaModel(app.db, conversation, stream_interactor), stream_interactor);

View file

@ -81,7 +81,8 @@ public class FileSendOverlay {
if (widget == null) { if (widget == null) {
FileDefaultWidget default_widget = new FileDefaultWidget(); FileDefaultWidget default_widget = new FileDefaultWidget();
default_widget.name_label.label = file_name; 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; widget = default_widget;
} }

View file

@ -87,8 +87,10 @@ namespace Dino.Ui{
string ret = yield stream_interactor.get_module(Register.IDENTITY).change_password(account, new_pw_input); string ret = yield stream_interactor.get_module(Register.IDENTITY).change_password(account, new_pw_input);
change_password_button.sensitive = true; change_password_button.sensitive = true;
change_password_stack.visible_child_name = "label"; change_password_stack.visible_child_name = "label";
if (ret == null) if (ret == null) {
account.password = new_pw_input;
close(); close();
}
change_password_error_label.label = ret; change_password_error_label.label = ret;

View file

@ -44,7 +44,11 @@ public class Dialog : Gtk.Dialog {
}); });
image_button.clicked.connect(show_select_avatar); image_button.clicked.connect(show_select_avatar);
alias_hybrid.entry.changed.connect(() => { selected_account.alias = alias_hybrid.text; }); alias_hybrid.entry.changed.connect(() => { selected_account.alias = alias_hybrid.text; });
password_hybrid.entry.changed.connect(() => { selected_account.password = password_hybrid.text; }); password_hybrid.entry.changed.connect(() => {
if (password_hybrid.text != "************") {
selected_account.password = password_hybrid.text;
}
});
password_change_btn.clicked.connect(show_change_psswd_dialog); password_change_btn.clicked.connect(show_change_psswd_dialog);
Util.LabelHybridGroup label_hybrid_group = new Util.LabelHybridGroup(); Util.LabelHybridGroup label_hybrid_group = new Util.LabelHybridGroup();
@ -68,6 +72,7 @@ public class Dialog : Gtk.Dialog {
settings_list.attach(widget, 1, row_index, 2); settings_list.attach(widget, 1, row_index, 2);
row_index++; row_index++;
password_hybrid.text = "************";
} }
} }
@ -192,6 +197,7 @@ public class Dialog : Gtk.Dialog {
} }
private void populate_grid_data(Account account) { private void populate_grid_data(Account account) {
active_switch.sensitive = false;
active_switch.state_set.disconnect(change_account_state); active_switch.state_set.disconnect(change_account_state);
picture.model = new ViewModel.CompatAvatarPictureModel(stream_interactor).add_participant(new Conversation(account.bare_jid, account, Conversation.Type.CHAT), account.bare_jid); picture.model = new ViewModel.CompatAvatarPictureModel(stream_interactor).add_participant(new Conversation(account.bare_jid, account, Conversation.Type.CHAT), account.bare_jid);
@ -200,7 +206,7 @@ public class Dialog : Gtk.Dialog {
alias_hybrid.text = account.alias ?? ""; alias_hybrid.text = account.alias ?? "";
password_hybrid.entry.input_purpose = InputPurpose.PASSWORD; password_hybrid.entry.input_purpose = InputPurpose.PASSWORD;
password_hybrid.text = account.password;
update_status_label(account); update_status_label(account);
@ -222,11 +228,14 @@ public class Dialog : Gtk.Dialog {
ConnectionManager.ConnectionState state = stream_interactor.connection_manager.get_state(account); ConnectionManager.ConnectionState state = stream_interactor.connection_manager.get_state(account);
switch (state) { switch (state) {
case ConnectionManager.ConnectionState.CONNECTING: case ConnectionManager.ConnectionState.CONNECTING:
active_switch.sensitive = false;
state_label.label = _("Connecting…"); break; state_label.label = _("Connecting…"); break;
case ConnectionManager.ConnectionState.CONNECTED: case ConnectionManager.ConnectionState.CONNECTED:
active_switch.sensitive = true;
password_change_btn.sensitive = true; password_change_btn.sensitive = true;
state_label.label = _("Connected"); break; state_label.label = _("Connected"); break;
case ConnectionManager.ConnectionState.DISCONNECTED: case ConnectionManager.ConnectionState.DISCONNECTED:
active_switch.sensitive = true;
password_change_btn.sensitive = false; password_change_btn.sensitive = false;
state_label.label = _("Disconnected"); break; state_label.label = _("Disconnected"); break;
} }

View file

@ -17,7 +17,9 @@ public class List : Box {
private Conversation conversation; private Conversation conversation;
private string[]? filter_values; private string[]? filter_values;
private HashMap<Jid, Widget> rows = new HashMap<Jid, Widget>(Jid.hash_func, Jid.equals_func);
// List of all chat members with corresponding widgets
private HashMap<string, Widget> rows = new HashMap<string, Widget>();
public HashMap<Widget, ListRow> row_wrappers = new HashMap<Widget, ListRow>(); public HashMap<Widget, ListRow> row_wrappers = new HashMap<Widget, ListRow>();
public List(StreamInteractor stream_interactor, Conversation conversation) { public List(StreamInteractor stream_interactor, Conversation conversation) {
@ -28,17 +30,44 @@ public class List : Box {
search_entry.search_changed.connect(refilter); search_entry.search_changed.connect(refilter);
stream_interactor.get_module(PresenceManager.IDENTITY).show_received.connect(on_show_received); stream_interactor.get_module(PresenceManager.IDENTITY).show_received.connect(on_show_received);
stream_interactor.get_module(PresenceManager.IDENTITY).received_offline_presence.connect(on_received_offline_presence); stream_interactor.get_module(PresenceManager.IDENTITY).received_offline_presence.connect(on_quit_received);
initialize_for_conversation(conversation); initialize_for_conversation(conversation);
} }
public bool get_status(Jid jid, Account account) {
Gee.List<Jid>? full_jids = stream_interactor.get_module(PresenceManager.IDENTITY).get_full_jids(jid, account);
debug("Get presence status for %s", jid.bare_jid.to_string());
string presence_str = null;
if (full_jids != null){
// Iterate over all connected devices
for (int i = 0; i < full_jids.size; i++) {
Jid full_jid = full_jids[i];
presence_str = stream_interactor.get_module(PresenceManager.IDENTITY).get_last_show(full_jid, account);
switch(presence_str) {
case "online": {
// Return online status if user is online on at least one device
return true;
}
}
}
} else {
return false;
}
return false;
}
public void initialize_for_conversation(Conversation conversation) { public void initialize_for_conversation(Conversation conversation) {
this.conversation = conversation; this.conversation = conversation;
Gee.List<Jid>? occupants = stream_interactor.get_module(MucManager.IDENTITY).get_occupants(conversation.counterpart, conversation.account);
if (occupants != null) { var identity = stream_interactor.get_module(MucManager.IDENTITY);
foreach (Jid occupant in occupants) { Gee.List<Jid>? members = identity.get_members(conversation.counterpart, conversation.account);
add_occupant(occupant); if (members != null) {
// Add all members and their status to the list
foreach (Jid member in members) {
add_member(member); //use add_member_with_status if you want to get online/offline statuses
} }
} }
list_box.invalidate_filter(); list_box.invalidate_filter();
@ -53,38 +82,135 @@ public class List : Box {
list_box.invalidate_filter(); list_box.invalidate_filter();
} }
public void add_occupant(Jid jid) { public void add_member_with_status(Jid jid, bool online) {
// HACK:
// Here we track members based on their names (not jids)
// Sometimes the same member can be referenced with different jids, for example:
// When initializing the conversation (see initialize_for_conversation function),
// we reference members like this:
// test_user@test_domain (using a local part, without a resource)
// However when updating status, we get the jid in the following format
// local_domain@test_domain/test_user (using a resource)
string member_name = null;
if (jid.resourcepart != null) {
member_name = jid.resourcepart;
} else {
member_name = jid.localpart;
}
if (member_name == null) {
return;
}
if (!rows.has_key(member_name)) {
debug("adding new member %s", jid.to_string());
debug("local %s", jid.localpart);
debug("domain %s", jid.domainpart);
debug("resource %s", jid.resourcepart);
var row_wrapper = new ListRow(stream_interactor, conversation, jid); var row_wrapper = new ListRow(stream_interactor, conversation, jid);
var widget = row_wrapper.get_widget(); var widget = row_wrapper.get_widget();
if (online) {
row_wrapper.set_online();
} else {
row_wrapper.set_offline();
}
row_wrappers[widget] = row_wrapper; row_wrappers[widget] = row_wrapper;
rows[jid] = widget; rows[member_name] = widget;
list_box.append(widget);
}
}
public void add_member(Jid jid) {
var row_wrapper = new ListRow(stream_interactor, conversation, jid);
var widget = row_wrapper.get_widget();
string member_name = null;
if (jid.resourcepart != null) {
member_name = jid.resourcepart;
} else {
member_name = jid.localpart;
}
if (member_name == null) {
return;
}
row_wrappers[widget] = row_wrapper;
rows[member_name] = widget;
list_box.append(widget); list_box.append(widget);
} }
public void remove_occupant(Jid jid) { public void remove_member(Jid jid) {
list_box.remove(rows[jid]); var member_name = jid.resourcepart;
rows.unset(jid); if (member_name == null) {
return;
}
list_box.remove(rows[member_name]);
rows.unset(member_name);
} }
private void on_received_offline_presence(Jid jid, Account account) { private void on_received_offline_presence(Jid jid, Account account) {
if (conversation != null && conversation.counterpart.equals_bare(jid) && jid.is_full()) { if (conversation != null && conversation.counterpart.equals_bare(jid) && jid.is_full()) {
if (rows.has_key(jid)) { var member_name = jid.resourcepart;
remove_occupant(jid); if (member_name == null) {
return;
} }
if (rows.has_key(member_name)) {
row_wrappers[rows[member_name]].set_offline();
debug("%s is now offline", jid.to_string());
}
list_box.invalidate_filter();
}
}
private void on_quit_received(Jid jid, Account account) {
if (conversation != null && conversation.counterpart.equals_bare(jid) && jid.is_full()) {
var member_name = jid.resourcepart;
if (member_name == null) {
return;
}
if (rows.has_key(member_name)) {
remove_member(jid);
}
list_box.invalidate_filter();
}
}
private void on_received_online_presence(Jid jid, Account account) {
if (conversation != null && conversation.counterpart.equals_bare(jid) && jid.is_full()) {
var member_name = jid.resourcepart;
if (member_name == null) {
return;
}
if (!rows.has_key(member_name)) {
add_member_with_status(jid, true);
}
row_wrappers[rows[member_name]].set_online();
debug("%s is now online", jid.to_string());
list_box.invalidate_filter(); list_box.invalidate_filter();
} }
} }
private void on_show_received(Jid jid, Account account) { private void on_show_received(Jid jid, Account account) {
if (conversation != null && conversation.counterpart.equals_bare(jid) && jid.is_full()) { if (conversation != null && conversation.counterpart.equals_bare(jid) && jid.is_full()) {
if (!rows.has_key(jid)) { var member_name = jid.resourcepart;
add_occupant(jid); if (member_name == null) {
return;
}
if (!rows.has_key(member_name)) {
add_member(jid);
} }
list_box.invalidate_filter(); list_box.invalidate_filter();
} }
} }
private void header(ListBoxRow row, ListBoxRow? before_row) { private void header(ListBoxRow row, ListBoxRow? before_row) {
ListRow row_wrapper1 = row_wrappers[row.get_child()]; ListRow row_wrapper1 = row_wrappers[row.get_child()];
Xmpp.Xep.Muc.Affiliation? a1 = stream_interactor.get_module(MucManager.IDENTITY).get_affiliation(conversation.counterpart, row_wrapper1.jid, row_wrapper1.conversation.account); Xmpp.Xep.Muc.Affiliation? a1 = stream_interactor.get_module(MucManager.IDENTITY).get_affiliation(conversation.counterpart, row_wrapper1.jid, row_wrapper1.conversation.account);

View file

@ -10,7 +10,6 @@ public class ListRow : Object {
private Grid main_grid; private Grid main_grid;
private AvatarPicture picture; private AvatarPicture picture;
public Label name_label; public Label name_label;
public Conversation? conversation; public Conversation? conversation;
public Jid? jid; public Jid? jid;
@ -19,6 +18,9 @@ public class ListRow : Object {
main_grid = (Grid) builder.get_object("main_grid"); main_grid = (Grid) builder.get_object("main_grid");
picture = (AvatarPicture) builder.get_object("picture"); picture = (AvatarPicture) builder.get_object("picture");
name_label = (Label) builder.get_object("name_label"); name_label = (Label) builder.get_object("name_label");
main_grid.set_column_spacing(10);
main_grid.set_column_homogeneous(false);
main_grid.set_baseline_row(1);
} }
public ListRow(StreamInteractor stream_interactor, Conversation conversation, Jid jid) { public ListRow(StreamInteractor stream_interactor, Conversation conversation, Jid jid) {
@ -37,6 +39,12 @@ public class ListRow : Object {
public Widget get_widget() { public Widget get_widget() {
return main_grid; return main_grid;
} }
public void set_online() {
}
public void set_offline() {
}
} }
} }

View file

@ -1,4 +1,5 @@
using Gtk; using Gtk;
using Dino.Entities;
namespace Dino.Ui { namespace Dino.Ui {
@ -9,6 +10,12 @@ class SettingsDialog : Adw.PreferencesWindow {
[GtkChild] private unowned Switch marker_switch; [GtkChild] private unowned Switch marker_switch;
[GtkChild] private unowned Switch notification_switch; [GtkChild] private unowned Switch notification_switch;
[GtkChild] private unowned Switch emoji_switch; [GtkChild] private unowned Switch emoji_switch;
[GtkChild] private unowned CheckButton encryption_radio_undecided;
[GtkChild] private unowned CheckButton encryption_radio_omemo;
[GtkChild] private unowned CheckButton encryption_radio_openpgp;
[GtkChild] private unowned Switch send_button_switch;
[GtkChild] private unowned Switch enter_newline_switch;
[GtkChild] private unowned Switch dark_theme;
Dino.Entities.Settings settings = Dino.Application.get_default().settings; Dino.Entities.Settings settings = Dino.Application.get_default().settings;
@ -19,11 +26,48 @@ class SettingsDialog : Adw.PreferencesWindow {
marker_switch.active = settings.send_marker; marker_switch.active = settings.send_marker;
notification_switch.active = settings.notifications; notification_switch.active = settings.notifications;
emoji_switch.active = settings.convert_utf8_smileys; emoji_switch.active = settings.convert_utf8_smileys;
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_switch.active = settings.send_button;
enter_newline_switch.active = settings.enter_newline;
enter_newline_switch.sensitive = settings.send_button;
dark_theme.active = settings.dark_theme;
typing_switch.notify["active"].connect(() => { settings.send_typing = typing_switch.active; } ); typing_switch.notify["active"].connect(() => { settings.send_typing = typing_switch.active; } );
marker_switch.notify["active"].connect(() => { settings.send_marker = marker_switch.active; } ); marker_switch.notify["active"].connect(() => { settings.send_marker = marker_switch.active; } );
notification_switch.notify["active"].connect(() => { settings.notifications = notification_switch.active; } ); notification_switch.notify["active"].connect(() => { settings.notifications = notification_switch.active; } );
emoji_switch.notify["active"].connect(() => { settings.convert_utf8_smileys = emoji_switch.active; }); emoji_switch.notify["active"].connect(() => { settings.convert_utf8_smileys = emoji_switch.active; });
encryption_radio_undecided.notify["active"].connect(() => {
if (encryption_radio_undecided.active) {
settings.default_encryption = Encryption.UNKNOWN;
}
});
encryption_radio_omemo.notify["active"].connect(() => {
if (encryption_radio_omemo.active) {
settings.default_encryption = Encryption.OMEMO;
}
});
encryption_radio_openpgp.notify["active"].connect(() => {
if (encryption_radio_openpgp.active) {
settings.default_encryption = Encryption.PGP;
}
});
send_button_switch.notify["active"].connect(() => { settings.send_button = send_button_switch.active; });
enter_newline_switch.notify["active"].connect(() => { settings.enter_newline = enter_newline_switch.active; });
settings.send_button_update.connect((visible) => {
enter_newline_switch.sensitive = visible;
if (visible == false) {
enter_newline_switch.active = visible;
}
});
dark_theme.notify["active"].connect(() => { settings.dark_theme = dark_theme.active; });
} }
} }

View file

@ -1,4 +1,4 @@
project('xmpp-vala', 'c', 'cpp', 'vala') project('xmpp-vala', 'c', 'cpp', 'vala', version:'0.1')
fs = import('fs') fs = import('fs')
gnome = import('gnome') gnome = import('gnome')
@ -8,6 +8,12 @@ python = import('python')
# plugin_crypto is enabled if any of the crypto plugins is enabled, auto if # plugin_crypto is enabled if any of the crypto plugins is enabled, auto if
# none of them are explicitly enabled but at least one is set to auto, or # none of them are explicitly enabled but at least one is set to auto, or
# disabled if all of them are disabled. # disabled if all of them are disabled.
#
# On Windows, it's always required because we need it for glib-networking.
if host_machine.system() == 'windows'
plugin_crypto = true
add_project_arguments('-D', '_WIN32', language: 'vala')
else
plugin_crypto = get_option('plugin-ice') plugin_crypto = get_option('plugin-ice')
foreach plugin : ['plugin-ice', 'plugin-omemo', 'plugin-rtp'] foreach plugin : ['plugin-ice', 'plugin-omemo', 'plugin-rtp']
if get_option(plugin).enabled() and not plugin_crypto.enabled() if get_option(plugin).enabled() and not plugin_crypto.enabled()
@ -16,12 +22,48 @@ foreach plugin : ['plugin-ice', 'plugin-omemo', 'plugin-rtp']
plugin_crypto = get_option(plugin) plugin_crypto = get_option(plugin)
endif endif
endforeach endforeach
endif
if get_option('crypto-backend') == 'auto'
# Prefer libgcrypt/gnutls over openssl because glib-networking is usually
# built with gnutls anyway.
dep_libgcrypt = dependency('libgcrypt', required: false)
dep_gnutls = dependency('gnutls', required: false)
if dep_libgcrypt.found() and dep_libgcrypt.found()
dep_libgcrypt_or_openssl = dep_libgcrypt
dep_gnutls_or_openssl = dep_gnutls
crypto_backend = 'gnutls'
else
dep_openssl = dependency('openssl', disabler: true, required: plugin_crypto)
dep_libgcrypt_or_openssl = dep_openssl
dep_gnutls_or_openssl = dep_openssl
crypto_backend = 'openssl'
endif
elif get_option('crypto-backend') == 'openssl'
dep_openssl = dependency('openssl', disabler: true, required: plugin_crypto)
dep_libgcrypt_or_openssl = dep_openssl
dep_gnutls_or_openssl = dep_openssl
crypto_backend = 'openssl'
elif get_option('crypto-backend') == 'gnutls'
dep_libgcrypt = dependency('libgcrypt', disabler: true, required: plugin_crypto)
dep_gnutls = dependency('gnutls', disabler: true, required: get_option('plugin-ice'))
dep_libgcrypt_or_openssl = dep_libgcrypt
dep_gnutls_or_openssl = dep_gnutls
crypto_backend = 'gnutls'
endif
if get_option('use-soup2')
libsoup_version = '2.4'
else
libsoup_version = '3.0'
endif
cc = meson.get_compiler('c')
dep_gdk_pixbuf = dependency('gdk-pixbuf-2.0') dep_gdk_pixbuf = dependency('gdk-pixbuf-2.0')
dep_gee = dependency('gee-0.8') dep_gee = dependency('gee-0.8')
dep_gio = dependency('gio-2.0') dep_gio = dependency('gio-2.0')
dep_glib = dependency('glib-2.0') dep_glib = dependency('glib-2.0')
dep_gnutls = dependency('gnutls', disabler: true, required: get_option('plugin-ice'))
dep_gmodule = dependency('gmodule-2.0') dep_gmodule = dependency('gmodule-2.0')
dep_gpgme = dependency('gpgme', disabler: true, required: get_option('plugin-openpgp')) dep_gpgme = dependency('gpgme', disabler: true, required: get_option('plugin-openpgp'))
dep_gstreamer = dependency('gstreamer-1.0', disabler: true, required: get_option('plugin-rtp')) dep_gstreamer = dependency('gstreamer-1.0', disabler: true, required: get_option('plugin-rtp'))
@ -33,18 +75,20 @@ dep_gtk4 = dependency('gtk4')
dep_icu_uc = dependency('icu-uc') dep_icu_uc = dependency('icu-uc')
dep_libadwaita = dependency('libadwaita-1') dep_libadwaita = dependency('libadwaita-1')
dep_libcanberra = dependency('libcanberra', disabler: true, required: get_option('plugin-notification-sound')) dep_libcanberra = dependency('libcanberra', disabler: true, required: get_option('plugin-notification-sound'))
dep_libgcrypt = dependency('libgcrypt', disabler: true, required: plugin_crypto)
dep_libqrencode = dependency('libqrencode', disabler: true, required: get_option('plugin-omemo')) dep_libqrencode = dependency('libqrencode', disabler: true, required: get_option('plugin-omemo'))
dep_libsrtp2 = dependency('libsrtp2', disabler: true, required: plugin_crypto) dep_libsrtp2 = dependency('libsrtp2', disabler: true, required: plugin_crypto)
# libsignal-protocol-c has a history of breaking compatibility on the patch level # libsignal-protocol-c has a history of breaking compatibility on the patch level
# we'll have to check compatibility for every new release # we'll have to check compatibility for every new release
# distro maintainers may update this dependency after compatibility tests # distro maintainers may update this dependency after compatibility tests
dep_libsignal_protocol_c = dependency('libsignal-protocol-c', version: ['>=2.3.2', '<2.3.4'], disabler: true, required: get_option('plugin-omemo')) dep_libsignal_protocol_c = dependency('libsignal-protocol-c', version: ['>=2.3.2', '<2.3.4'], disabler: true, required: get_option('plugin-omemo'))
dep_libsoup = dependency('libsoup-3.0', disabler: true, required: get_option('plugin-http-files')) dep_libsoup = dependency('libsoup-@0@'.format(libsoup_version), disabler: true, required: get_option('plugin-http-files'))
dep_nice = dependency('nice', version: '>=0.1.15', disabler: true, required: get_option('plugin-ice')) dep_nice = dependency('nice', version: '>=0.1.15', disabler: true, required: get_option('plugin-ice'))
dep_m = meson.get_compiler('c').find_library('m', required: false) dep_m = cc.find_library('m', required: false)
dep_sqlite3 = dependency('sqlite3', version: '>=3.24') dep_sqlite3 = dependency('sqlite3', version: '>=3.24')
dep_webrtc_audio_processing = dependency('webrtc-audio-processing', version: ['>=0.2', '<0.4'], required: get_option('plugin-rtp-webrtc-audio-processing'))
dep_gstreamer_bad = dependency('gstreamer-plugins-bad-1.0', disabler: true, required: get_option('plugin-rtp-webrtc-audio-processing'))
gstpluginsdir = dep_gstreamer_bad.get_variable('pluginsdir')
dep_webrtcdsp = cc.find_library('gstwebrtcdsp', dirs: gstpluginsdir, disabler: true, required: get_option('plugin-rtp-webrtc-audio-processing'))
prog_git = find_program('git', required: false) prog_git = find_program('git', required: false)
prog_python = python.find_installation() prog_python = python.find_installation()

View file

@ -1,4 +1,6 @@
option('plugindir', type: 'string', value: 'lib/dino/plugins', description: 'Dino plugin directory') option('plugindir', type: 'string', value: 'dino/plugins', description: 'Dino plugin directory inside libdir')
option('crypto-backend', type: 'combo', choices: ['auto', 'openssl', 'gnutls'], value: 'auto', description: 'Preferred crypto backend')
option('plugin-http-files', type: 'feature', description: 'HTTP file upload') option('plugin-http-files', type: 'feature', description: 'HTTP file upload')
option('plugin-ice', type: 'feature', description: '') option('plugin-ice', type: 'feature', description: '')
@ -12,3 +14,6 @@ option('plugin-rtp-msdk', type: 'feature', value: 'disabled', description: 'Inte
option('plugin-rtp-vaapi', type: 'feature', value: 'disabled', description: 'Video Acceleration API') option('plugin-rtp-vaapi', type: 'feature', value: 'disabled', description: 'Video Acceleration API')
option('plugin-rtp-vp9', type: 'feature', value: 'disabled', description: 'VP9 codec') option('plugin-rtp-vp9', type: 'feature', value: 'disabled', description: 'VP9 codec')
option('plugin-rtp-webrtc-audio-processing', type: 'feature', description: 'Voice preprocessing') option('plugin-rtp-webrtc-audio-processing', type: 'feature', description: 'Voice preprocessing')
option('use-soup2', type: 'boolean', value: false, description: 'Use libsoup version 2 instead of 3')
option('with-wasapi', type: 'boolean', value: true, description: 'Use wasapi insted of directsound on windows')

View file

@ -15,8 +15,10 @@ sources = files(
'src/register_plugin.vala', 'src/register_plugin.vala',
) )
vala_args = [ vala_args = []
'--define=SOUP_3_0', if dep_libsoup.version().version_compare('>=3.0')
] vala_args += ['--define=SOUP_3_0']
lib_http_files = shared_library('http-files', sources, name_prefix: '', vala_args: vala_args, dependencies: dependencies, install: true, install_dir: get_option('libdir') / 'dino/plugins') endif
lib_http_files = shared_library('http-files', sources, name_prefix: '', vala_args: vala_args, dependencies: dependencies, kwargs: install_options)
dep_http_files = declare_dependency(link_with: lib_http_files, include_directories: include_directories('.')) dep_http_files = declare_dependency(link_with: lib_http_files, include_directories: include_directories('.'))

View file

@ -103,6 +103,18 @@ public class HttpFileSender : FileSender, Object {
put_message.wrote_headers.connect(() => transfer_more_bytes(file_transfer.input_stream, put_message.request_body)); 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)); put_message.wrote_chunk.connect(() => transfer_more_bytes(file_transfer.input_stream, put_message.request_body));
#endif #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) { foreach (var entry in file_send_data.headers.entries) {
put_message.request_headers.append(entry.key, entry.value); put_message.request_headers.append(entry.key, entry.value);
} }

View file

@ -1,3 +1,11 @@
if crypto_backend == 'openssl'
if get_option('plugin-ice').enabled()
error('plugin-ice does not work with openssl backend yet')
else
warning('plugin-ice is not enabled!')
subdir_done()
endif
endif
dependencies = [ dependencies = [
dep_crypto_vala, dep_crypto_vala,
dep_dino, dep_dino,
@ -5,7 +13,7 @@ dependencies = [
dep_gee, dep_gee,
dep_glib, dep_glib,
dep_gmodule, dep_gmodule,
dep_gnutls, dep_gnutls_or_openssl,
dep_nice, dep_nice,
dep_qlite, dep_qlite,
dep_xmpp_vala, dep_xmpp_vala,
@ -24,5 +32,5 @@ c_args = [
vala_args = [ vala_args = [
'--vapidir', meson.current_source_dir() / 'vapi', '--vapidir', meson.current_source_dir() / 'vapi',
] ]
lib_ice = shared_library('ice', sources, name_prefix: '', c_args: c_args, vala_args: vala_args, dependencies: dependencies, install: true, install_dir: get_option('libdir') / 'dino/plugins') lib_ice = shared_library('ice', sources, name_prefix: '', c_args: c_args, vala_args: vala_args, dependencies: dependencies, kwargs: install_options)
dep_ice = declare_dependency(link_with: lib_ice, include_directories: include_directories('.')) dep_ice = declare_dependency(link_with: lib_ice, include_directories: include_directories('.'))

View file

@ -6,6 +6,21 @@ using Xmpp.Xep;
private extern const size_t NICE_ADDRESS_STRING_LEN; private extern const size_t NICE_ADDRESS_STRING_LEN;
public class Dino.Plugins.Ice.Plugin : RootInterface, Object { public class Dino.Plugins.Ice.Plugin : RootInterface, Object {
private const int64 delay_min = 300; // 10mn
private const int64 delay_max = (int64) uint.MAX;
private class TimerPayload {
public Account account { get; set; }
public uint timeout_handle_id;
public TimerPayload(Account account, uint timeout_handle_id) {
this.account = account;
this.timeout_handle_id = timeout_handle_id;
}
}
private HashMap<XmppStream, TimerPayload> timeouts = new HashMap<XmppStream, TimerPayload>(XmppStream.hash_func, XmppStream.equals_func);
public Dino.Application app; public Dino.Application app;
public void registered(Dino.Application app) { public void registered(Dino.Application app) {
@ -22,10 +37,10 @@ public class Dino.Plugins.Ice.Plugin : RootInterface, Object {
stream.get_module(JingleRawUdp.Module.IDENTITY).set_local_ip_address_handler(get_local_ip_addresses); stream.get_module(JingleRawUdp.Module.IDENTITY).set_local_ip_address_handler(get_local_ip_addresses);
} }
}); });
app.stream_interactor.stream_negotiated.connect(on_stream_negotiated); app.stream_interactor.connection_manager.connection_state_changed.connect(on_connection_state_changed);
} }
private async void on_stream_negotiated(Account account, XmppStream stream) { private async void external_discovery_refresh_services(Account account, XmppStream stream) {
Module? ice_udp_module = stream.get_module(JingleIceUdp.Module.IDENTITY) as Module; Module? ice_udp_module = stream.get_module(JingleIceUdp.Module.IDENTITY) as Module;
if (ice_udp_module == null) return; if (ice_udp_module == null) return;
Gee.List<Xep.ExternalServiceDiscovery.Service> services = yield ExternalServiceDiscovery.request_services(stream); Gee.List<Xep.ExternalServiceDiscovery.Service> services = yield ExternalServiceDiscovery.request_services(stream);
@ -45,6 +60,28 @@ public class Dino.Plugins.Ice.Plugin : RootInterface, Object {
} }
} }
} }
if (ice_udp_module.turn_service != null) {
DateTime? expires = ice_udp_module.turn_service.expires;
if (expires != null) {
int64 delay = (expires.to_unix() - new DateTime.now_utc().to_unix()) / 2;
if (delay >= delay_min && delay <= delay_max) {
debug("Next server external service discovery in %lds (because of TURN credentials' expiry time)", (long) delay);
uint timeout_handle_id = Timeout.add_seconds((uint) delay, () => {
on_timeout(stream);
return false;
});
timeouts[stream] = new TimerPayload(account, timeout_handle_id);
timeouts[stream].account = account;
timeouts[stream].timeout_handle_id = timeout_handle_id;
} else {
warning("Bogus TURN credentials' expiry time (delay value = %ld), *not* planning next service discovery", (long) delay);
}
}
}
if (ice_udp_module.stun_ip == null) { if (ice_udp_module.stun_ip == null) {
InetAddress ip = yield lookup_ipv4_addess("stun.dino.im"); InetAddress ip = yield lookup_ipv4_addess("stun.dino.im");
if (ip == null) return; if (ip == null) return;
@ -56,6 +93,31 @@ public class Dino.Plugins.Ice.Plugin : RootInterface, Object {
} }
} }
public void on_timeout(XmppStream stream) {
if (!timeouts.has_key(stream)) return;
TimerPayload pl = timeouts[stream];
timeouts.unset(stream);
external_discovery_refresh_services.begin(pl.account, stream);
}
public void on_connection_state_changed(Account account, ConnectionManager.ConnectionState state) {
switch(state)
{
case ConnectionManager.ConnectionState.DISCONNECTED:
XmppStream? stream = app.stream_interactor.connection_manager.get_stream(account);
if (stream == null) return;
if (!timeouts.has_key(stream)) return;
Source.remove(timeouts[stream].timeout_handle_id);
timeouts.unset(stream);
break;
case ConnectionManager.ConnectionState.CONNECTED:
XmppStream? stream = app.stream_interactor.connection_manager.get_stream(account);
external_discovery_refresh_services(account, stream);
break;
}
}
public void shutdown() { public void shutdown() {
// Nothing to do // Nothing to do
} }

View file

@ -1,6 +1,14 @@
install_options = {'install': true, 'install_dir': get_option('libdir') / get_option('plugindir')}
subdir('http-files') subdir('http-files')
subdir('ice') subdir('ice')
subdir('notification-sound') subdir('notification-sound')
subdir('omemo') subdir('omemo')
subdir('openpgp') subdir('openpgp')
subdir('rtp') subdir('rtp')
if host_machine.system() == 'windows'
subdir('win32-fonts')
subdir('windows-notification')
else
subdir('phone-ringer')
endif

View file

@ -15,5 +15,5 @@ sources = files(
vala_args = [ vala_args = [
'--vapidir', meson.current_source_dir() / 'vapi', '--vapidir', meson.current_source_dir() / 'vapi',
] ]
lib_notification_sound = shared_library('notification-sound', sources, name_prefix: '', vala_args: vala_args, dependencies: dependencies, install: true, install_dir: get_option('libdir') / 'dino/plugins') lib_notification_sound = shared_library('notification-sound', sources, name_prefix: '', vala_args: vala_args, dependencies: dependencies, kwargs: install_options)
dep_notification_sound = declare_dependency(link_with: lib_notification_sound, include_directories: include_directories('.')) dep_notification_sound = declare_dependency(link_with: lib_notification_sound, include_directories: include_directories('.'))

View file

@ -90,9 +90,11 @@ GENERATE_VAPI
omemo omemo
GENERATE_HEADER GENERATE_HEADER
omemo omemo
DEFINITIONS
GCRYPT
) )
add_definitions(${VALA_CFLAGS} -DGETTEXT_PACKAGE=\"${GETTEXT_PACKAGE}\" -DLOCALE_INSTALL_DIR=\"${LOCALE_INSTALL_DIR}\" -DG_LOG_DOMAIN="OMEMO") add_definitions(${VALA_CFLAGS} -DGETTEXT_PACKAGE=\"${GETTEXT_PACKAGE}\" -DLOCALE_INSTALL_DIR=\"${LOCALE_INSTALL_DIR}\" -DG_LOG_DOMAIN="OMEMO" -DGCRYPT)
add_library(omemo SHARED ${OMEMO_VALA_C} ${OMEMO_GRESOURCES_TARGET} ${CMAKE_CURRENT_SOURCE_DIR}/src/signal/signal_helper.c) add_library(omemo SHARED ${OMEMO_VALA_C} ${OMEMO_GRESOURCES_TARGET} ${CMAKE_CURRENT_SOURCE_DIR}/src/signal/signal_helper.c)
add_dependencies(omemo ${GETTEXT_PACKAGE}-translations) add_dependencies(omemo ${GETTEXT_PACKAGE}-translations)
target_include_directories(omemo PUBLIC src) target_include_directories(omemo PUBLIC src)
@ -102,7 +104,7 @@ set_target_properties(omemo PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_D
install(TARGETS omemo ${PLUGIN_INSTALL}) install(TARGETS omemo ${PLUGIN_INSTALL})
if(BUILD_TESTS) if(BUILD_TESTING)
vala_precompile(OMEMO_TEST_VALA_C vala_precompile(OMEMO_TEST_VALA_C
SOURCES SOURCES
"tests/signal/common.vala" "tests/signal/common.vala"
@ -125,4 +127,5 @@ if(BUILD_TESTS)
add_executable(omemo-test ${OMEMO_TEST_VALA_C}) add_executable(omemo-test ${OMEMO_TEST_VALA_C})
add_dependencies(omemo-test omemo) add_dependencies(omemo-test omemo)
target_link_libraries(omemo-test omemo ${OMEMO_PACKAGES}) target_link_libraries(omemo-test omemo ${OMEMO_PACKAGES})
endif(BUILD_TESTS) add_test(NAME omemo COMMAND omemo-test)
endif(BUILD_TESTING)

View file

@ -6,7 +6,7 @@ dependencies = [
dep_glib, dep_glib,
dep_gmodule, dep_gmodule,
dep_gtk4, dep_gtk4,
dep_libgcrypt, dep_libgcrypt_or_openssl,
dep_libqrencode, dep_libqrencode,
dep_libsignal_protocol_c, dep_libsignal_protocol_c,
dep_qlite, dep_qlite,
@ -61,8 +61,24 @@ c_args = [
'-DGETTEXT_PACKAGE="dino-omemo"', '-DGETTEXT_PACKAGE="dino-omemo"',
'-DLOCALE_INSTALL_DIR="@0@"'.format(get_option('prefix') / get_option('localedir')), '-DLOCALE_INSTALL_DIR="@0@"'.format(get_option('prefix') / get_option('localedir')),
] ]
if crypto_backend == 'gnutls'
c_args += ['-DGCRYPT']
endif
vala_args = [ vala_args = [
'--vapidir', meson.current_source_dir() / 'vapi', '--vapidir', meson.current_source_dir() / 'vapi',
] ]
lib_omemo = shared_library('omemo', sources, name_prefix: '', c_args: c_args, vala_args: vala_args, include_directories: include_directories('src'), dependencies: dependencies, install: true, install_dir: get_option('libdir') / 'dino/plugins') if crypto_backend == 'gnutls'
vala_args += ['-D', 'GCRYPT']
endif
lib_omemo = shared_library('omemo', sources, name_prefix: '', c_args: c_args, vala_args: vala_args, include_directories: include_directories('src'), dependencies: dependencies, kwargs: install_options)
dep_omemo = declare_dependency(link_with: lib_omemo, include_directories: include_directories('.')) dep_omemo = declare_dependency(link_with: lib_omemo, include_directories: include_directories('.'))
sources = files(
'tests/signal/common.vala',
'tests/signal/curve25519.vala',
'tests/signal/hkdf.vala',
'tests/signal/session_builder.vala',
'tests/signal/testcase.vala',
)
test_omemo = executable('test_omemo', sources, vala_args: vala_args, include_directories: include_directories('src'), dependencies: dependencies + [dep_omemo])
test('omemo', test_omemo)

View file

@ -59,7 +59,7 @@ public class OmemoFileDecryptor : FileDecryptor, Object {
file_transfer.encryption = Encryption.OMEMO; file_transfer.encryption = Encryption.OMEMO;
debug("Decrypting file %s from %s", file_transfer.file_name, file_transfer.server_file_name); debug("Decrypting file %s from %s", file_transfer.file_name, file_transfer.server_file_name);
SymmetricCipher cipher = new SymmetricCipher("AES-GCM"); SymmetricCipher cipher = new SymmetricCipher.decryption("AES-GCM");
cipher.set_key(key); cipher.set_key(key);
cipher.set_iv(iv); cipher.set_iv(iv);
return new ConverterInputStream(encrypted_stream, new SymmetricCipherDecrypter((owned) cipher, 16)); return new ConverterInputStream(encrypted_stream, new SymmetricCipherDecrypter((owned) cipher, 16));

View file

@ -31,7 +31,7 @@ public class OmemoFileEncryptor : Dino.FileEncryptor, Object {
uint8[] key = new uint8[KEY_SIZE]; uint8[] key = new uint8[KEY_SIZE];
Plugin.get_context().randomize(key); Plugin.get_context().randomize(key);
SymmetricCipher cipher = new SymmetricCipher("AES-GCM"); SymmetricCipher cipher = new SymmetricCipher.encryption("AES-GCM");
cipher.set_key(key); cipher.set_key(key);
cipher.set_iv(iv); cipher.set_iv(iv);

View file

@ -99,13 +99,13 @@ public class AesGcmCipher : Jet.Cipher, Object {
return new Jet.TransportSecret(key, iv); return new Jet.TransportSecret(key, iv);
} }
public InputStream wrap_input_stream(InputStream input, Jet.TransportSecret secret) requires (secret.transport_key.length == key_size) { public InputStream wrap_input_stream(InputStream input, Jet.TransportSecret secret) requires (secret.transport_key.length == key_size) {
SymmetricCipher cipher = new SymmetricCipher("AES-GCM"); SymmetricCipher cipher = new SymmetricCipher.decryption("AES-GCM");
cipher.set_key(secret.transport_key); cipher.set_key(secret.transport_key);
cipher.set_iv(secret.initialization_vector); cipher.set_iv(secret.initialization_vector);
return new ConverterInputStream(input, new SymmetricCipherDecrypter((owned) cipher, 16)); return new ConverterInputStream(input, new SymmetricCipherDecrypter((owned) cipher, 16));
} }
public OutputStream wrap_output_stream(OutputStream output, Jet.TransportSecret secret) requires (secret.transport_key.length == key_size) { public OutputStream wrap_output_stream(OutputStream output, Jet.TransportSecret secret) requires (secret.transport_key.length == key_size) {
Crypto.SymmetricCipher cipher = new SymmetricCipher("AES-GCM"); Crypto.SymmetricCipher cipher = new SymmetricCipher.encryption("AES-GCM");
cipher.set_key(secret.transport_key); cipher.set_key(secret.transport_key);
cipher.set_iv(secret.initialization_vector); cipher.set_iv(secret.initialization_vector);
return new ConverterOutputStream(output, new SymmetricCipherEncrypter((owned) cipher, 16)); return new ConverterOutputStream(output, new SymmetricCipherEncrypter((owned) cipher, 16));

View file

@ -12,7 +12,7 @@ public class Bundle {
assert(Plugin.ensure_context()); assert(Plugin.ensure_context());
} }
public int32 signed_pre_key_id { owned get { public int32 signed_pre_key_id { get {
if (node == null) return -1; if (node == null) return -1;
string? id = ((!)node).get_deep_attribute("signedPreKeyPublic", "signedPreKeyId"); string? id = ((!)node).get_deep_attribute("signedPreKeyPublic", "signedPreKeyId");
if (id == null) return -1; if (id == null) return -1;
@ -69,7 +69,7 @@ public class Bundle {
this.node = node; this.node = node;
} }
public int32 key_id { owned get { public int32 key_id { get {
return int.parse(node.get_attribute("preKeyId") ?? "-1"); return int.parse(node.get_attribute("preKeyId") ?? "-1");
}} }}

View file

@ -1,7 +1,7 @@
namespace Signal { namespace Signal {
public class Context { public class Context {
internal NativeContext native_context; public NativeContext native_context;
private RecMutex mutex = RecMutex(); private RecMutex mutex = RecMutex();
static void locking_function_lock(void* user_data) { static void locking_function_lock(void* user_data) {

View file

@ -1,6 +1,11 @@
#include "signal_helper.h" #include "signal_helper.h"
#ifdef GCRYPT
#include <gcrypt.h> #include <gcrypt.h>
#else
#include <openssl/evp.h>
#include <openssl/rand.h>
#endif
signal_type_base* signal_type_ref_vapi(void* instance) { signal_type_base* signal_type_ref_vapi(void* instance) {
g_return_val_if_fail(instance != NULL, NULL); g_return_val_if_fail(instance != NULL, NULL);
@ -65,16 +70,31 @@ void signal_protocol_address_set_device_id(signal_protocol_address* self, int32_
} }
int signal_vala_randomize(uint8_t *data, size_t len) { int signal_vala_randomize(uint8_t *data, size_t len) {
#ifdef GCRYPT
gcry_randomize(data, len, GCRY_STRONG_RANDOM); gcry_randomize(data, len, GCRY_STRONG_RANDOM);
return SG_SUCCESS; return SG_SUCCESS;
#else
return RAND_bytes(data, len) == 1 ? SG_SUCCESS : SG_ERR_UNKNOWN;
#endif
} }
int signal_vala_random_generator(uint8_t *data, size_t len, void *user_data) { int signal_vala_random_generator(uint8_t *data, size_t len, void *user_data) {
#ifdef GCRYPT
gcry_randomize(data, len, GCRY_STRONG_RANDOM); gcry_randomize(data, len, GCRY_STRONG_RANDOM);
return SG_SUCCESS; return SG_SUCCESS;
#else
return RAND_bytes(data, len) == 1 ? SG_SUCCESS : SG_ERR_UNKNOWN;
#endif
} }
#ifndef GCRYPT
struct SIGNAL_VALA_HMAC_CTX {
EVP_PKEY *pkey;
EVP_MD_CTX *ctx;
};
#endif
int signal_vala_hmac_sha256_init(void **hmac_context, const uint8_t *key, size_t key_len, void *user_data) { int signal_vala_hmac_sha256_init(void **hmac_context, const uint8_t *key, size_t key_len, void *user_data) {
#ifdef GCRYPT
gcry_mac_hd_t* ctx = malloc(sizeof(gcry_mac_hd_t)); gcry_mac_hd_t* ctx = malloc(sizeof(gcry_mac_hd_t));
if (!ctx) return SG_ERR_NOMEM; if (!ctx) return SG_ERR_NOMEM;
@ -91,17 +111,49 @@ int signal_vala_hmac_sha256_init(void **hmac_context, const uint8_t *key, size_t
*hmac_context = ctx; *hmac_context = ctx;
return SG_SUCCESS; return SG_SUCCESS;
#else
EVP_PKEY *pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_HMAC, NULL, key, key_len);
if (!pkey) {
return SG_ERR_NOMEM;
}
EVP_MD_CTX *ctx = EVP_MD_CTX_new();
if (!ctx) {
EVP_PKEY_free(pkey);
return SG_ERR_NOMEM;
}
if (EVP_DigestSignInit(ctx, NULL, EVP_sha256(), NULL, pkey) != 1) {
EVP_MD_CTX_free(ctx);
EVP_PKEY_free(pkey);
return SG_ERR_UNKNOWN;
}
struct SIGNAL_VALA_HMAC_CTX *hmac_ctx = malloc(sizeof(*hmac_ctx));
hmac_ctx->pkey = pkey;
hmac_ctx->ctx = ctx;
*hmac_context = hmac_ctx;
return SG_SUCCESS;
#endif
} }
int signal_vala_hmac_sha256_update(void *hmac_context, const uint8_t *data, size_t data_len, void *user_data) { int signal_vala_hmac_sha256_update(void *hmac_context, const uint8_t *data, size_t data_len, void *user_data) {
#ifdef GCRYPT
gcry_mac_hd_t* ctx = hmac_context; gcry_mac_hd_t* ctx = hmac_context;
if (gcry_mac_write(*ctx, data, data_len)) return SG_ERR_UNKNOWN; if (gcry_mac_write(*ctx, data, data_len)) return SG_ERR_UNKNOWN;
return SG_SUCCESS; return SG_SUCCESS;
#else
struct SIGNAL_VALA_HMAC_CTX *hmac_ctx = hmac_context;
if (EVP_DigestSignUpdate(hmac_ctx->ctx, data, data_len) != 1) {
return SG_ERR_UNKNOWN;
}
return SG_SUCCESS;
#endif
} }
int signal_vala_hmac_sha256_final(void *hmac_context, signal_buffer **output, void *user_data) { int signal_vala_hmac_sha256_final(void *hmac_context, signal_buffer **output, void *user_data) {
#ifdef GCRYPT
size_t len = gcry_mac_get_algo_maclen(GCRY_MAC_HMAC_SHA256); size_t len = gcry_mac_get_algo_maclen(GCRY_MAC_HMAC_SHA256);
uint8_t md[len]; uint8_t md[len];
gcry_mac_hd_t* ctx = hmac_context; gcry_mac_hd_t* ctx = hmac_context;
@ -114,17 +166,49 @@ int signal_vala_hmac_sha256_final(void *hmac_context, signal_buffer **output, vo
*output = output_buffer; *output = output_buffer;
return SG_SUCCESS; return SG_SUCCESS;
#else
size_t len;
struct SIGNAL_VALA_HMAC_CTX *hmac_ctx = hmac_context;
if (EVP_DigestSignFinal(hmac_ctx->ctx, NULL, &len) != 1) {
return SG_ERR_UNKNOWN;
}
signal_buffer *output_buffer = signal_buffer_alloc(len);
if (!output_buffer) {
return SG_ERR_NOMEM;
}
size_t another_len = len;
if (EVP_DigestSignFinal(hmac_ctx->ctx, signal_buffer_data(output_buffer), &another_len) != 1) {
signal_buffer_free(output_buffer);
return SG_ERR_UNKNOWN;
}
if (another_len != len) {
signal_buffer_free(output_buffer);
return SG_ERR_UNKNOWN;
}
*output = output_buffer;
return SG_SUCCESS;
#endif
} }
void signal_vala_hmac_sha256_cleanup(void *hmac_context, void *user_data) { void signal_vala_hmac_sha256_cleanup(void *hmac_context, void *user_data) {
#ifdef GCRYPT
gcry_mac_hd_t* ctx = hmac_context; gcry_mac_hd_t* ctx = hmac_context;
if (ctx) { if (ctx) {
gcry_mac_close(*ctx); gcry_mac_close(*ctx);
free(ctx); free(ctx);
} }
#else
struct SIGNAL_VALA_HMAC_CTX *hmac_ctx = hmac_context;
if (hmac_ctx) {
EVP_MD_CTX_free(hmac_ctx->ctx);
EVP_PKEY_free(hmac_ctx->pkey);
free(hmac_ctx);
}
#endif
} }
int signal_vala_sha512_digest_init(void **digest_context, void *user_data) { int signal_vala_sha512_digest_init(void **digest_context, void *user_data) {
#ifdef GCRYPT
gcry_md_hd_t* ctx = malloc(sizeof(gcry_mac_hd_t)); gcry_md_hd_t* ctx = malloc(sizeof(gcry_mac_hd_t));
if (!ctx) return SG_ERR_NOMEM; if (!ctx) return SG_ERR_NOMEM;
@ -136,17 +220,38 @@ int signal_vala_sha512_digest_init(void **digest_context, void *user_data) {
*digest_context = ctx; *digest_context = ctx;
return SG_SUCCESS; return SG_SUCCESS;
#else
EVP_MD_CTX *ctx = EVP_MD_CTX_new();
if (!ctx) {
return SG_ERR_NOMEM;
}
if (EVP_DigestInit_ex(ctx, EVP_sha512(), NULL) != 1) {
EVP_MD_CTX_free(ctx);
return SG_ERR_UNKNOWN;
}
*digest_context = ctx;
return SG_SUCCESS;
#endif
} }
int signal_vala_sha512_digest_update(void *digest_context, const uint8_t *data, size_t data_len, void *user_data) { int signal_vala_sha512_digest_update(void *digest_context, const uint8_t *data, size_t data_len, void *user_data) {
#ifdef GCRYPT
gcry_md_hd_t* ctx = digest_context; gcry_md_hd_t* ctx = digest_context;
gcry_md_write(*ctx, data, data_len); gcry_md_write(*ctx, data, data_len);
return SG_SUCCESS; return SG_SUCCESS;
#else
EVP_MD_CTX *ctx = digest_context;
if (EVP_DigestUpdate(ctx, data, data_len) != 1) {
return SG_ERR_UNKNOWN;
}
return SG_SUCCESS;
#endif
} }
int signal_vala_sha512_digest_final(void *digest_context, signal_buffer **output, void *user_data) { int signal_vala_sha512_digest_final(void *digest_context, signal_buffer **output, void *user_data) {
#ifdef GCRYPT
size_t len = gcry_md_get_algo_dlen(GCRY_MD_SHA512); size_t len = gcry_md_get_algo_dlen(GCRY_MD_SHA512);
gcry_md_hd_t* ctx = digest_context; gcry_md_hd_t* ctx = digest_context;
@ -162,17 +267,43 @@ int signal_vala_sha512_digest_final(void *digest_context, signal_buffer **output
*output = output_buffer; *output = output_buffer;
return SG_SUCCESS; return SG_SUCCESS;
#else
EVP_MD_CTX *ctx = digest_context;
size_t len = EVP_MD_size(EVP_sha512());
signal_buffer *output_buffer = signal_buffer_alloc(len);
if (!output_buffer) {
return SG_ERR_NOMEM;
}
if (EVP_DigestSignFinal(ctx, signal_buffer_data(output_buffer), &len) != 1) {
signal_buffer_free(output_buffer);
return SG_ERR_UNKNOWN;
}
if (len != EVP_MD_size(EVP_sha512())) {
signal_buffer_free(output_buffer);
return SG_ERR_UNKNOWN;
}
*output = output_buffer;
return SG_SUCCESS;
#endif
} }
void signal_vala_sha512_digest_cleanup(void *digest_context, void *user_data) { void signal_vala_sha512_digest_cleanup(void *digest_context, void *user_data) {
#ifdef GCRYPT
gcry_md_hd_t* ctx = digest_context; gcry_md_hd_t* ctx = digest_context;
if (ctx) { if (ctx) {
gcry_md_close(*ctx); gcry_md_close(*ctx);
free(ctx); free(ctx);
} }
#else
EVP_MD_CTX *ctx = digest_context;
if (ctx) {
EVP_MD_CTX_free(ctx);
}
#endif
} }
const int aes_cipher(int cipher, size_t key_len, int* algo, int* mode) { #ifdef GCRYPT
static int aes_cipher(int cipher, size_t key_len, int* algo, int* mode) {
switch (key_len) { switch (key_len) {
case 16: case 16:
*algo = GCRY_CIPHER_AES128; *algo = GCRY_CIPHER_AES128;
@ -201,6 +332,35 @@ const int aes_cipher(int cipher, size_t key_len, int* algo, int* mode) {
} }
return SG_SUCCESS; return SG_SUCCESS;
} }
#else
static const EVP_CIPHER *aes_cipher(int cipher, size_t key_len) {
switch (cipher) {
case SG_CIPHER_AES_CBC_PKCS5:
switch (key_len) {
case 16: return EVP_aes_128_cbc();
case 24: return EVP_aes_192_cbc();
case 32: return EVP_aes_256_cbc();
}
break;
case SG_CIPHER_AES_CTR_NOPADDING:
switch (key_len) {
case 16: return EVP_aes_128_ctr();
case 24: return EVP_aes_192_ctr();
case 32: return EVP_aes_256_ctr();
}
break;
case SG_CIPHER_AES_GCM_NOPADDING:
switch (key_len) {
case 16: return EVP_aes_128_gcm();
case 24: return EVP_aes_192_gcm();
case 32: return EVP_aes_256_gcm();
}
break;
}
return NULL;
}
#endif
int signal_vala_encrypt(signal_buffer **output, int signal_vala_encrypt(signal_buffer **output,
int cipher, int cipher,
@ -208,6 +368,7 @@ int signal_vala_encrypt(signal_buffer **output,
const uint8_t *iv, size_t iv_len, const uint8_t *iv, size_t iv_len,
const uint8_t *plaintext, size_t plaintext_len, const uint8_t *plaintext, size_t plaintext_len,
void *user_data) { void *user_data) {
#ifdef GCRYPT
int algo, mode, error_code = SG_ERR_UNKNOWN; int algo, mode, error_code = SG_ERR_UNKNOWN;
if (aes_cipher(cipher, key_len, &algo, &mode)) return SG_ERR_INVAL; if (aes_cipher(cipher, key_len, &algo, &mode)) return SG_ERR_INVAL;
@ -279,6 +440,97 @@ no_error:
gcry_cipher_close(ctx); gcry_cipher_close(ctx);
return SG_SUCCESS; return SG_SUCCESS;
#else
int result = 0;
uint8_t *out_buf = NULL;
const EVP_CIPHER *evp_cipher = aes_cipher(cipher, key_len);
if (!evp_cipher) {
// fprintf(stderr, "invalid AES mode or key size: %zu\n", key_len);
return SG_ERR_INVAL;
}
if (plaintext_len > INT_MAX - EVP_CIPHER_block_size(evp_cipher)) {
// fprintf(stderr, "invalid plaintext length: %zu\n", plaintext_len);
return SG_ERR_UNKNOWN;
}
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
int buf_extra = 0;
if (cipher == SG_CIPHER_AES_GCM_NOPADDING) {
// In GCM mode we use the last 16 bytes as auth tag
buf_extra += 16;
if (EVP_EncryptInit_ex(ctx, evp_cipher, NULL, NULL, NULL) != 1) {
// fprintf(stderr, "cannot initialize cipher\n");
result = SG_ERR_UNKNOWN;
goto complete;
}
if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, iv_len, NULL) != 1) {
// fprintf(stderr, "cannot set iv size\n");
result = SG_ERR_UNKNOWN;
goto complete;
}
if (EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv) != 1) {
// fprintf(stderr, "cannot set key/iv\n");
result = SG_ERR_UNKNOWN;
goto complete;
}
} else {
// TODO: set ivlen?
if (EVP_EncryptInit_ex(ctx, evp_cipher, 0, key, iv) != 1) {
// fprintf(stderr, "cannot initialize cipher\n");
result = SG_ERR_UNKNOWN;
goto complete;
}
}
if (cipher == SG_CIPHER_AES_CTR_NOPADDING || cipher == SG_CIPHER_AES_GCM_NOPADDING) {
if (EVP_CIPHER_CTX_set_padding(ctx, 0) != 1) {
// fprintf(stderr, "cannot set padding\n");
result = SG_ERR_UNKNOWN;
goto complete;
}
}
out_buf = malloc(plaintext_len + EVP_CIPHER_block_size(evp_cipher) + buf_extra);
if (!out_buf) {
// fprintf(stderr, "cannot allocate output buffer\n");
result = SG_ERR_NOMEM;
goto complete;
}
int out_len = 0;
if (EVP_EncryptUpdate(ctx, out_buf, &out_len, plaintext, plaintext_len) != 1) {
// fprintf(stderr, "cannot encrypt plaintext\n");
result = SG_ERR_UNKNOWN;
goto complete;
}
int final_len = 0;
if (EVP_EncryptFinal_ex(ctx, out_buf + out_len, &final_len) != 1) {
// fprintf(stderr, "cannot finish encrypting plaintext\n");
result = SG_ERR_UNKNOWN;
goto complete;
}
if (cipher == SG_CIPHER_AES_GCM_NOPADDING) {
if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, out_buf + out_len + final_len) != 1) {
// fprintf(stderr, "cannot get tag\n");
result = SG_ERR_UNKNOWN;
goto complete;
}
}
*output = signal_buffer_create(out_buf, out_len + final_len + buf_extra);
complete:
EVP_CIPHER_CTX_free(ctx);
if (out_buf) {
free(out_buf);
}
return result;
#endif
} }
int signal_vala_decrypt(signal_buffer **output, int signal_vala_decrypt(signal_buffer **output,
@ -287,6 +539,7 @@ int signal_vala_decrypt(signal_buffer **output,
const uint8_t *iv, size_t iv_len, const uint8_t *iv, size_t iv_len,
const uint8_t *ciphertext, size_t ciphertext_len, const uint8_t *ciphertext, size_t ciphertext_len,
void *user_data) { void *user_data) {
#ifdef GCRYPT
int algo, mode, error_code = SG_ERR_UNKNOWN; int algo, mode, error_code = SG_ERR_UNKNOWN;
*output = 0; *output = 0;
if (aes_cipher(cipher, key_len, &algo, &mode)) return SG_ERR_INVAL; if (aes_cipher(cipher, key_len, &algo, &mode)) return SG_ERR_INVAL;
@ -352,11 +605,104 @@ no_error:
gcry_cipher_close(ctx); gcry_cipher_close(ctx);
return SG_SUCCESS; return SG_SUCCESS;
#else
int result = 0;
uint8_t *out_buf = NULL;
const EVP_CIPHER *evp_cipher = aes_cipher(cipher, key_len);
if (!evp_cipher) {
// fprintf(stderr, "invalid AES mode or key size: %zu\n", key_len);
return SG_ERR_INVAL;
}
if (ciphertext_len > INT_MAX - EVP_CIPHER_block_size(evp_cipher)) {
// fprintf(stderr, "invalid ciphertext length: %zu\n", ciphertext_len);
return SG_ERR_UNKNOWN;
}
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
if (cipher == SG_CIPHER_AES_GCM_NOPADDING) {
// In GCM mode we use the last 16 bytes as auth tag
ciphertext_len -= 16;
if (EVP_DecryptInit_ex(ctx, evp_cipher, NULL, NULL, NULL) != 1) {
// fprintf(stderr, "cannot initialize cipher\n");
result = SG_ERR_UNKNOWN;
goto complete;
}
if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, iv_len, NULL) != 1) {
// fprintf(stderr, "cannot set iv size\n");
result = SG_ERR_UNKNOWN;
goto complete;
}
if (EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv) != 1) {
// fprintf(stderr, "cannot set key/iv\n");
result = SG_ERR_UNKNOWN;
goto complete;
}
} else {
// TODO: set ivlen?
if (EVP_DecryptInit_ex(ctx, evp_cipher, 0, key, iv) != 1) {
// fprintf(stderr, "cannot initialize cipher\n");
result = SG_ERR_UNKNOWN;
goto complete;
}
}
if (cipher == SG_CIPHER_AES_CTR_NOPADDING || cipher == SG_CIPHER_AES_GCM_NOPADDING) {
if (EVP_CIPHER_CTX_set_padding(ctx, 0) != 1) {
// fprintf(stderr, "cannot set padding\n");
result = SG_ERR_UNKNOWN;
goto complete;
}
}
out_buf = malloc(ciphertext_len + EVP_CIPHER_block_size(evp_cipher));
if (!out_buf) {
// fprintf(stderr, "cannot allocate output buffer\n");
result = SG_ERR_NOMEM;
goto complete;
}
int out_len = 0;
if (EVP_DecryptUpdate(ctx, out_buf, &out_len, ciphertext, ciphertext_len) != 1) {
// fprintf(stderr, "cannot decrypt ciphertext\n");
result = SG_ERR_UNKNOWN;
goto complete;
}
if (cipher == SG_CIPHER_AES_GCM_NOPADDING) {
if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, (void *)(ciphertext + ciphertext_len)) != 1) {
// fprintf(stderr, "cannot set tag\n");
result = SG_ERR_UNKNOWN;
goto complete;
}
}
int final_len = 0;
if (EVP_DecryptFinal_ex(ctx, out_buf + out_len, &final_len) != 1) {
// fprintf(stderr, "cannot finish decrypting ciphertexts\n");
result = SG_ERR_UNKNOWN;
goto complete;
}
*output = signal_buffer_create(out_buf, out_len + final_len);
complete:
EVP_CIPHER_CTX_free(ctx);
if (out_buf) {
free(out_buf);
}
return result;
#endif
} }
void setup_signal_vala_crypto_provider(signal_context *context) void setup_signal_vala_crypto_provider(signal_context *context)
{ {
#ifdef GCRYPT
gcry_check_version(NULL); gcry_check_version(NULL);
#endif
signal_crypto_provider provider = { signal_crypto_provider provider = {
.random_func = signal_vala_random_generator, .random_func = signal_vala_random_generator,

View file

@ -107,7 +107,7 @@ public class Store : Object {
public PreKeyStore pre_key_store { get; set; default = new SimplePreKeyStore(); } public PreKeyStore pre_key_store { get; set; default = new SimplePreKeyStore(); }
public SignedPreKeyStore signed_pre_key_store { get; set; default = new SimpleSignedPreKeyStore(); } public SignedPreKeyStore signed_pre_key_store { get; set; default = new SimpleSignedPreKeyStore(); }
public uint32 local_registration_id { get { return identity_key_store.local_registration_id; } } public uint32 local_registration_id { get { return identity_key_store.local_registration_id; } }
internal NativeStoreContext native_context {get { return native_store_context_; }} public NativeStoreContext native_context {get { return native_store_context_; }}
private NativeStoreContext native_store_context_; private NativeStoreContext native_store_context_;
static int iks_get_identity_key_pair(out Buffer public_data, out Buffer private_data, void* user_data) { static int iks_get_identity_key_pair(out Buffer public_data, out Buffer private_data, void* user_data) {

View file

@ -154,7 +154,7 @@ public class BadMessagesWidget : Box {
} else if (conversation.type_ == Conversation.Type.GROUPCHAT) { } else if (conversation.type_ == Conversation.Type.GROUPCHAT) {
who = jid.to_string(); who = jid.to_string();
// `jid` is a real JID. In MUCs, try to show nicks instead (given that the JID is currently online) // `jid` is a real JID. In MUCs, try to show nicks instead (given that the JID is currently online)
var occupants = plugin.app.stream_interactor.get_module(MucManager.IDENTITY).get_occupants(conversation.counterpart, conversation.account); var occupants = plugin.app.stream_interactor.get_module(MucManager.IDENTITY).get_members(conversation.counterpart, conversation.account);
if (occupants == null) return; if (occupants == null) return;
foreach (Jid occupant in occupants) { foreach (Jid occupant in occupants) {
if (jid.equals_bare(plugin.app.stream_interactor.get_module(MucManager.IDENTITY).get_real_jid(occupant, conversation.account))) { if (jid.equals_bare(plugin.app.stream_interactor.get_module(MucManager.IDENTITY).get_real_jid(occupant, conversation.account))) {

View file

View file

@ -39,5 +39,5 @@ c_args = [
vala_args = [ vala_args = [
'--vapidir', meson.current_source_dir() / 'vapi', '--vapidir', meson.current_source_dir() / 'vapi',
] ]
lib_openpgp = shared_library('openpgp', sources, name_prefix: '', c_args: c_args, vala_args: vala_args, include_directories: include_directories('src'), dependencies: dependencies, install: true, install_dir: get_option('libdir') / 'dino/plugins') lib_openpgp = shared_library('openpgp', sources, name_prefix: '', c_args: c_args, vala_args: vala_args, include_directories: include_directories('src'), dependencies: dependencies, kwargs: install_options)
dep_openpgp = declare_dependency(link_with: lib_openpgp, include_directories: include_directories('.')) dep_openpgp = declare_dependency(link_with: lib_openpgp, include_directories: include_directories('.'))

View file

@ -1,9 +1,10 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: dino-openpgp-0.0\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-02-07 21:31+0100\n" "POT-Creation-Date: 2024-06-09 22:16+0200\n"
"PO-Revision-Date: 2020-04-16 20:11+0000\n" "PO-Revision-Date: 2024-06-09 22:39+0200\n"
"Last-Translator: eerielili \n"
"Language-Team: German <https://hosted.weblate.org/projects/dino/plugin-" "Language-Team: German <https://hosted.weblate.org/projects/dino/plugin-"
"openpgp/de/>\n" "openpgp/de/>\n"
"Language: de\n" "Language: de\n"
@ -11,11 +12,11 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n" "Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.0.1-dev\n" "X-Generator: Poedit 3.4.2\n"
#: plugins/openpgp/src/account_settings_entry.vala:68 #: plugins/openpgp/src/account_settings_entry.vala:68
#: plugins/openpgp/src/account_settings_entry.vala:72 #: plugins/openpgp/src/account_settings_entry.vala:72
#: plugins/openpgp/src/account_settings_entry.vala:101 #: plugins/openpgp/src/account_settings_entry.vala:113
msgid "Key publishing disabled" msgid "Key publishing disabled"
msgstr "Schlüsselveröffentlichung deaktiviert" msgstr "Schlüsselveröffentlichung deaktiviert"
@ -24,18 +25,40 @@ msgid "Error in GnuPG"
msgstr "Fehler in GnuPG" msgstr "Fehler in GnuPG"
#: plugins/openpgp/src/account_settings_entry.vala:72 #: plugins/openpgp/src/account_settings_entry.vala:72
msgid "No keys available. Generate one!" msgid ""
msgstr "Keine Schlüssel vorhanden. Erzeuge einen!" "No keys available. Generate one or check if your keys aren't expired or "
"revoked!"
msgstr ""
"Keine Schlüssel vorhanden. Erstellen Sie einen oder prüfen Sie,"
"ob Ihre Schlüssel nicht abgelaufen sind oder widerrufen!"
#: plugins/openpgp/src/account_settings_entry.vala:101
#: plugins/openpgp/src/account_settings_entry.vala:95
msgid "expired!"
msgstr "abgelaufen"
#: plugins/openpgp/src/account_settings_entry.vala:95
msgid "revoked!"
msgstr "widerrufen!"
#: plugins/openpgp/src/account_settings_entry.vala:96
msgid "Attention required!"
msgstr "Achtung!"
#: plugins/openpgp/src/account_settings_entry.vala:96
#, c-format
msgid "Your key %s is %s"
msgstr "Ihr Schlüssel %s is %s"
#: plugins/openpgp/src/account_settings_entry.vala:113
msgid "Select key" msgid "Select key"
msgstr "Wähle einen Schlüssel" msgstr "Wähle einen Schlüssel"
#: plugins/openpgp/src/account_settings_entry.vala:114 #: plugins/openpgp/src/account_settings_entry.vala:126
msgid "Loading…" msgid "Loading…"
msgstr "Lade…" msgstr "Lade…"
#: plugins/openpgp/src/account_settings_entry.vala:114 #: plugins/openpgp/src/account_settings_entry.vala:126
msgid "Querying GnuPG" msgid "Querying GnuPG"
msgstr "Frage GnuPG ab" msgstr "Frage GnuPG ab"
@ -46,6 +69,3 @@ msgstr "Schlüssel nicht im Schlüsselbund"
#: plugins/openpgp/src/contact_details_provider.vala:30 #: plugins/openpgp/src/contact_details_provider.vala:30
msgid "Encryption" msgid "Encryption"
msgstr "Verschlüsselung" msgstr "Verschlüsselung"
#~ msgid "OpenPGP"
#~ msgstr "OpenPGP"

View file

@ -1,52 +1,68 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION+\n"
"Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-06-09 22:16+0200\n"
"POT-Creation-Date: 2023-02-07 21:31+0100\n" "PO-Revision-Date: 2024-06-09 22:18+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: \n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: Dino+\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language: en\n"
"Language: \n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 3.4.2\n"
"X-Poedit-Basepath: ../src\n"
"X-Poedit-KeywordsList: _(\n"
"X-Poedit-SourceCharset: UTF-8\n"
"X-Poedit-SearchPath-0: .\n"
#: plugins/openpgp/src/account_settings_entry.vala:68 #: account_settings_entry.vala:68 account_settings_entry.vala:72
#: plugins/openpgp/src/account_settings_entry.vala:72 #: account_settings_entry.vala:113
#: plugins/openpgp/src/account_settings_entry.vala:101
msgid "Key publishing disabled" msgid "Key publishing disabled"
msgstr "" msgstr ""
#: plugins/openpgp/src/account_settings_entry.vala:68 #: account_settings_entry.vala:68
msgid "Error in GnuPG" msgid "Error in GnuPG"
msgstr "" msgstr ""
#: plugins/openpgp/src/account_settings_entry.vala:72 #: account_settings_entry.vala:72
msgid "No keys available. Generate one!" msgid ""
"No keys available. Generate one or check if your keys aren't expired or "
"revoked!"
msgstr "" msgstr ""
#: plugins/openpgp/src/account_settings_entry.vala:101 #: account_settings_entry.vala:95
msgid "expired!"
msgstr ""
#: account_settings_entry.vala:95
msgid "revoked!"
msgstr ""
#: account_settings_entry.vala:96
msgid "Attention required!"
msgstr ""
#: account_settings_entry.vala:96
#, c-format
msgid "Your key %s is %s"
msgstr ""
#: account_settings_entry.vala:113
msgid "Select key" msgid "Select key"
msgstr "" msgstr ""
#: plugins/openpgp/src/account_settings_entry.vala:114 #: account_settings_entry.vala:126
msgid "Loading…" msgid "Loading…"
msgstr "" msgstr ""
#: plugins/openpgp/src/account_settings_entry.vala:114 #: account_settings_entry.vala:126
msgid "Querying GnuPG" msgid "Querying GnuPG"
msgstr "" msgstr ""
#: plugins/openpgp/src/contact_details_provider.vala:28 #: contact_details_provider.vala:28
msgid "Key not in keychain" msgid "Key not in keychain"
msgstr "" msgstr ""
#: plugins/openpgp/src/contact_details_provider.vala:30 #: contact_details_provider.vala:30
msgid "Encryption" msgid "Encryption"
msgstr "" msgstr ""

Some files were not shown because too many files have changed in this diff Show more