Compare commits

...

82 Commits

Author SHA1 Message Date
7243b10e81 interaction: move Info implementation to a cpp file
This fixes the no-libwrap build, which was broken by commit
96c00ff019 due to an additional header
file included in interaction.h

GitLab: #1828
Change-Id: Ic3490a64fdc1514e0b6533a380cb7115568ae9f1
2024-08-23 11:13:30 -04:00
964c8e0553 build.py : Added missing dependence pipewire to build.py
libpipewire-0.3-dev is used since commit Ia54dbc512aa87ae1cb1df7c1ffe71c153a4937a2

Change-Id: Ia1299ec938091a844581f8bea3c7c5486bd43f14
2024-08-23 09:30:32 -04:00
96c00ff019 interaction: set body and transferStatus of DATA_TRANSFER messages
This patch adds code in the interaction::Info::init function so that the
"body" and "transferStatus" fields are always set when an Info struct is
constructed for a message of type DATA_TRANSFER.

This removes some code duplication in conversationmodel.cpp, where these
fields were being set as an extra step after construction in three
different places.

It also fixes a bug in the ConversationModelPimpl::slotMessageUpdated
function, which did *not* set the "body" of DATA_TRANSFER messages. The
body was therefore empty instead of containing a file path, which is
what caused the image preview bug described in the following issue:
GitLab: #1671

The patch also reverts a change that was made in the
MessageListModel::update function by commit
d2eba1d91e. This change was a workaround
for the above bug, but it is no longer necessary (and it broke message
deletion, which relies on the body of the deleted message being set to
the empty string).
GitLab: #1825

Change-Id: I5848b93a12c1ef7b3735c5c6db6b32a9bbc4041d
2024-08-22 11:48:48 -04:00
c8716d1113 misc: unify terminology
Start audio call
Start video call
https://git.jami.net/savoirfairelinux/jami-client-qt/-/issues/1730#note_51880

GitLab: #1730

Change-Id: I976a6ca890c4b28501da9754cbbee012a2993d73
2024-08-21 15:06:15 -04:00
630e5e9fe4 Added a line to thanks volunteers in credits
I added "And the volunteers who contribute to this project!" at the bottom of the credits in the "about Jami" pop-up.
It's already on the jami-client-android version.

Issue #1689 on gitlab

Change-Id: I59967e5b9e1e7eac0519c9b4960692c14c80dab7
2024-08-20 09:58:25 -04:00
f447327c02 i18n: automatic bump
Change-Id: Ia68b3566b33d82ed31401345e397474499339cba
2024-08-13 14:27:22 -04:00
650f98636b misc: bump daemon
Change-Id: I93d6954cb0e30831b8a4cf036b5cc2c3eefa9196
2024-08-13 10:54:57 -04:00
b92cd902b9 packaging: fix Qt build on Debian testing/unstable
This commit adds four patches to fix various issues that were preventing
Qt 6.6.1 from building on Debian testing and Debian unstable. These
patches are backports of fixes that were applied in later versions of
Qt; links to the relevant commits are included in the description at the
top of each patch.

There are also two changes in the Dockerfiles for Debian testing and
Debian unstable:
1) CMake 3.21 is no longer installed via the install-cmake.sh script.
   This is not necessary anymore given that Debian testing and Debian
   unstable now both come with more recent versions of CMake by default
   (3.29 and 3.30 respectively).
2) The libre2-dev package (which is part of the dependencies in
   debian-qt/control) is removed from the Docker image in order to force
   Qt to build using the bundled version of the RE2 library. This is
   necessary because the system version of the library on testing and
   unstable (libre2-11) is not compatible with earlier versions due to
   an API change, c.f.
   https://codereview.qt-project.org/c/qt/qtwebengine/+/516094

GitLab: #1822
Change-Id: I763fb6692949052e2a846b3f4ce54619e6d98108
2024-08-13 10:40:20 -04:00
b99c2674b4 tests: fix account accumulation on Linux systems
On non-dockerized Linux systems, the accounts generated during tests are
only cleaned up occassionally. The test suite design implements
consistent account cleanup post test. Accumulation of these accounts
interfere with subsequent test runs, rendering the test suite ineffective.

The main test scripts incorrectly utilize a Jami Windows environment
variable for Linux systems. In adherence with the Jami client design,
this patch utilizes the correct environment variable for Linux systems.
Windows formatted paths were also modified to allow recogntion in Linux
enironments

GitLab: #1801
Change-Id: I633dbd168af1e6d20ccee53d1109cd179bd1a187
2024-08-08 11:48:31 -04:00
1524ba0177 i18n: automatic bump
Change-Id: Ic9bd89443178a6f899caea1383d1621c7cfc4aeb
2024-08-05 16:42:31 -04:00
5503769023 packaging: remove Ubuntu 23.04 (EOL)
Support for Ubuntu 23.04 ended in January 2024.

Change-Id: Ib4a7a045762a391552da69ace333e46f10afdfb9
2024-08-02 11:37:31 -04:00
89354a07e1 conversation model: split logic for SIP and Jami
GitLab: #1794
Change-Id: Ief688df6778fe2758882ff1538371def8ba75d64
2024-07-26 15:46:50 -04:00
69400bee2a misc: bump daemon submodule
Brings in some changes intended to correct/improve CI building
on Windows.

https://git.jami.net/savoirfairelinux/jami-daemon/-/issues/1027
Change-Id: I7047cd343b5711adaf4c992b99dc07097e9ff05b
2024-07-26 14:50:10 -04:00
0e07f9cee7 JamiIdentifier: fix binding for Jami ID
GitLab: #1805
Change-Id: Ic847bb61ccb2c75873d91a35cd851caaedaf518d
2024-07-23 11:20:52 -04:00
d2eba1d91e chatview: fix datatransfer messages not showing
99254f8d02 introduced 2 issues:
- transfer messages not notifying the UI
- some file URLs being erased after loading the conversation
This commit addresses both of them.

Gitlab: #1671
Change-Id: I67a003ea1149c27e749efffe496f4c9ce86615ea
2024-07-22 12:10:25 -04:00
78389d8c28 data transfer model: fix warning
Change-Id: Ia4fcc47c9f033a8f136d6f6de0fc4bb666a1cdfc
2024-07-18 14:52:28 -04:00
e6d820850a misc: bump daemon submodule
In addition to integrating the new UPnP code introduced into
dhtnet, this bump also includes numerous bug-fixes:
https://git.jami.net/savoirfairelinux/jami-client-qt/-/issues/1637
https://git.jami.net/savoirfairelinux/jami-daemon/-/issues/1021
https://git.jami.net/savoirfairelinux/jami-daemon/-/issues/1033
https://git.jami.net/savoirfairelinux/jami-daemon/-/issues/1026
https://git.jami.net/savoirfairelinux/jami-daemon/-/issues/952
https://git.jami.net/savoirfairelinux/jami-daemon/-/issues/1025

Change-Id: Ic7242e3dfe14550221d9d0de48ff84605b7fefd8
2024-07-16 14:09:50 -04:00
0a7f9349a9 smartlist: don't display misleading 'last interaction' date
If a conversation has no interactions (which shouldn't happen normally,
but sometimes occurs in practice), then its LastInteractionTimeStamp
will be zero, which causes the last interaction date in the smartlist to
be wrongly displayed as 31/12/1969 or 1/1/1970. This patch adds a check
to prevent this.

GitLab: #1794
Change-Id: I1384d6675c9fcaa1904bb6e1706589305b7618e9
2024-07-12 09:56:50 -04:00
99254f8d02 messagelistmodel: add support for file deletion
+ Add button to delete messages on file transfer
+ Show "Deleted media" on deleted files.
+ Update last interaction
+ Update icon for saving file, we're in 2024, no more floppy disk

Change-Id: I607b1a6beda443db85c60d8cf95a9aae29ce1f7c
GitLab: #1287
2024-07-10 16:00:39 -04:00
010a2c4eea linkdevicedialog: fix what seems to have been a copy-paste error
The "success" property of the wrong component was being set in the
"on-link-device" signal handler. This commit fixes that.

Gitlab: #1788
Change-Id: I99c9abbfa31ea9ea6e7828dbbed0a21081f8be19
2024-07-05 15:56:01 -04:00
61163037d4 misc: bump daemon submodule
This bump will include changes to fix failing builds on Windows.
https://git.jami.net/savoirfairelinux/jami-daemon/-/issues/1022

Change-Id: Ic34a097fbcea5723c8fe44da9c5887368dce2258
2024-07-04 16:03:20 -04:00
3577982a93 testing: added account switcher box
Change-Id: I76b67b819cd8e028062406b96583a36ae6a6d509
2024-07-04 15:53:04 -04:00
3ad0b92dcd link-preview: use non-ASCII UTF-8 characters in a UTF-8 test
Non-ASCII characters in UTF-8 are encoded with multiple bytes. Testing with these characters ensures that the decoder correctly interprets multi-byte sequences.

Gitlab: #1536
Change-Id: I0a92ee91b6cd26d70daab1f9baef3a9577aee02e
2024-07-04 13:01:02 -04:00
91475c3a3f misc: bump daemon
This fixes a serious issue on version 6.9 of the Linux kernel that is
already impacting some users:
https://git.jami.net/savoirfairelinux/jami-daemon/-/issues/1006

Change-Id: Id78643d51f38799be448034236364f746dbdfb10
2024-06-27 10:59:02 -04:00
9379af23ec callmodel: don't send profile
profile was not saved at reception anymore, this was wasting bandwidth

Change-Id: I3cbc1f1683a1e5a4b14616404cde5e27e07623e5
2024-06-21 14:30:53 -04:00
406a251c85 accountmodel: improve handling for account list reordering
This update fixes an issue where a stale account list might be used
in AccountListModel by synchronizing list invalidation with a newly
introduced signal (accountsReordered). This change prevents a data race
that could occur due to the asynchronous nature of setting the account
order over D-Bus, or when queuing the signal handling for
accountsChanged, which is emitted only once the reordering is saved.

As a result, QAbstractItemModel mutations are now performed within AccountListModel instead of in the UI.

Gitlab: #1638 (Account list in popup is incorrect after selection)
Change-Id: I7ed6eeb45eb319f21e40554f3d023ad24e139a6f
2024-06-10 10:29:24 -04:00
20e2852e44 misc: bump daemon (fixes missing API on macOS)
Change-Id: I9a04c1a87875a15b2d465982ddf5c3fa143a2dc0
2024-06-07 10:20:44 -04:00
3225f90ce8 misc: deploy and use ringtones on macOS
Gitlab: #1619
Change-Id: I7c19dfa4556f4f9dae827a1d0c967c9ebce7cc86
2024-06-04 12:36:41 -04:00
df3e76a1cf interaction: split status and transferStatus
This allow to show the correct sending/sent/state for the interaction
separated from the status of the dataTransfer. Else, we see a sent
check for a file even if the peer didn't receive the file

Change-Id: I15b9f0abc6a26a2ffd007be26827e9a37e859bca
2024-05-31 14:38:26 -04:00
c5e15d26a0 contactmodel: cache profiles for non contacts profile
If we're a member of a group swarm, we will receive profiles from
non contact (the other members of a conversation).
This patch try to get from cache before calling getContact() to be
able to retrieve profiles from non contacts if stored by the daemon

Change-Id: I864f1d5dd9f65232751e170b930606d23241d283
2024-05-31 10:22:12 -04:00
77e019b02b accountmodel: re-add save profile for JAMS account
JAMS account still use AccountProfileReceived

Change-Id: I010d459564a9230b7f06f4be55de668ec3526abd
2024-05-31 10:22:03 -04:00
89bed2bf85 avatar: fix image cropping
Take into consideration the vertical margin when cropping the image.

Change-Id: I140c96f54d2c1ae732bd104fb21ad04d66d97e9a
2024-05-30 15:09:55 -04:00
519871e458 DataTransferMessageDelegate: fix Url not defined
GitLab: #1563
Change-Id: Ie4ec46bbd7adb0fb0abf9da29224d7b87bcf0aa7
2024-05-30 11:45:53 -04:00
0a842042b0 ci/docker: add cppunit
Change-Id: I3abcdfa8d6a32eedbfd99ab0302d18b5bad6dae6
2024-05-30 10:55:55 -04:00
9aeb2377dc packaging: macos: simplify macdeployqt execution
Don't use a post build step, as it's only needed for packaging.

Change-Id: I1be4e7ac8042e1f211b4eeb15bc869e16eda1682
2024-05-29 12:45:41 -04:00
6ad5f4b850 snap: fix wayland scroll
GitLab: #1629
Change-Id: I74a651728c6d2d9c55b39772a11090d60231b3d5
2024-05-29 09:11:51 -05:00
f5c63d24fb packaging: fix opensuse leap 15.5
Change-Id: I555d6722fae688ba25c4eb2266088aeec0240528
2024-05-29 10:10:40 -04:00
5cb34bd31c misc: bump daemon
Change-Id: Id4f6d28423104965827d6b130533412dd918d536
2024-05-29 10:09:30 -04:00
e56a966de1 contactmodel: fix account avatar on link device
We do not need to write an empty profile since the daemon fully
manages the profile. Just update it from cache when new profile
is detected

GitLab: #1627
Change-Id: I31035f0666925d13f339f387e614f148b0eece8b
2024-05-29 10:08:10 -04:00
acc0c97234 contactmodel: fix contact initialization when linking a new device
getContacts() is called before sync is finished. This should be
done like conversations, after first initializing.
This fix the presence showing after sync.

GitLab: #1627
Change-Id: I4ec9b7e34b5bd93b9ae4437e6c6719dbc3b78a98
2024-05-29 10:08:10 -04:00
665af7c0c3 i18n: automatic bump
Change-Id: I1a004c1d7b2ad51eb3e59951f9e7158bc312c313
2024-05-27 16:42:57 -04:00
fa51e042e5 misc: update release name to Astarte
Change-Id: I64e7b601bbadc9055da912a7d6fe2304aa7a5169
2024-05-27 14:29:23 -04:00
3b9fb0bfca misc: bump daemon
Change-Id: I2c515128f5d2958f2e8e598f0bfd92d2ec5f1d1b
2024-05-24 14:40:00 -04:00
3673b0646c conversations: hide call view when swarm call is finished.
In the current implementation, the chatViewContainer of callStackView
is not destroyed for a swarm conference call because there are no
signals for call changes.This patch ensures chatViewContainer is used
when there is no call.

GitLab: #1625
Change-Id: Iefc39b747d92543244d30aa987eda134ff0a03f3
2024-05-24 14:39:20 -04:00
2e2f6423f8 misc: bump daemon
Change-Id: I38901aa3e8b5db4ec14c70f9b6df65a4f43eaad4
2024-05-22 08:58:16 -04:00
28c1cbbb34 call-swarm: follow daemon changes
Change-Id: I32e83c2ccf82be78fbdd6e9932105228dd6dee8b
2024-05-21 08:48:01 -04:00
87c215deb7 i18n: automatic bump
Change-Id: Ic221ee76c9b9310edea106aea28203a36fbe0617
2024-05-20 16:42:34 -04:00
77eddcd962 callmodel: fix bug in getProposed function
GitLab: #1607
Change-Id: I49062e2e02b4d8f39221243dcef49298117e2f9e
2024-05-17 13:22:48 -04:00
043a715c59 systray: restore original behaviour for systrayicon check
In response to an issue that would cause a lost window when minimizing to tray on a system without a systray, commit 316750a introduced a bug for versions of Qt that would return null icon geometry even when the icon was visible. Subsequently, this was byspassed with commit f25e66a which only applied to GNU/Linux systems, leaving the hack inplace for Windows and macOS where the bug had not originally occured.

This commit:
- makes the "MinimizeOnClose" option always visible
- uses Qt's built-in method for "isSystemTrayIconVisible"
- changes the default "MinimizeOnClose" setting to true

Gitlab: #1623
Change-Id: I3b99c7fb952eedea63ae9c12d207ceb3c9bd4988
2024-05-15 16:14:46 -04:00
5bd3ead22d chatview: do not show "Edited" on deleted message
Change-Id: I38b4f45cb3220bc52ab227976acd65a6c2d1ef93
2024-05-13 08:36:10 -04:00
a9aa1cac80 linkdevice: use a white border for the QR code regardless of theme
Change-Id: If177414fbb4223432e5c5769ef51a1b3ad745ce5
2024-05-08 10:26:54 -04:00
409ba70258 i18n: automatic bump
Change-Id: I63bea7d1001aefca8bbb1f8b4556841599fa7e88
2024-05-06 16:42:55 -04:00
dc50f19815 bump minimum macOS version
MacOS 11 is required from qt 6.5

Change-Id: I4cde10a3dccffe5d2b4778ceda5db9d3a20dd447
2024-04-30 11:41:35 -04:00
d83895dcc9 misc: bump daemon
Change-Id: If11353d884129b98652dabae6068d4dfd5222786
2024-04-30 08:23:44 -04:00
e24a3d6a4d packaging: Add Ubuntu 24.04 LTS
Change-Id: I3dc28375adfc9fa20d162f46d8c46f54a28dca7e
2024-04-30 08:23:21 -04:00
06de33e1be packaging: Add Fedora 40
Change-Id: Id7fdb845446cf226061a8eef3523698c1a4967cd
2024-04-30 08:14:38 -04:00
c8fbcd8c6b i18n: automatic bump
Change-Id: I3323e91f731240c5fc2219b3260d46705291d369
2024-04-29 16:42:33 -04:00
31269fe8fc misc: bump daemon
Change-Id: I44793c0aca9850e210be9f616598c4473d0261ba
2024-04-26 18:15:16 -04:00
a676ad395a callmodel: don't turn video on when accepting a call in audio
The function which is responsible for muting the camera when accepting
a call in audio assumes that the call's mediaList has already been
initialized, but this wasn't actually the case. This caused a bug where
the 'Accept in audio' button behaved exactly like the 'Accept in video'
button.

GitLab: #1621
Change-Id: I26251f51862cf5cd9b6d4daaf15270943c0e3c4e
2024-04-25 12:10:28 -04:00
04c71d02e0 snap: build PipeWire from source
This is necessary because we use the core20 base snap, which is built
from Ubuntu 20.04 and therefore doesn't have a recent enough version of
PipeWire available by default.

Change-Id: Id039ec446f4b3f0e89b9ec27f37f81cfd6b3587e
2024-04-19 09:40:44 -04:00
1fe60b9c33 i18n: automatic bump
Change-Id: I593de1332807a18464d4bbff51346980e90f1117
2024-04-16 09:51:34 -04:00
51ef7a83da snapcraft: fix build
Change-Id: I8ac570f0ad859b2ea2ee83ca8fe3927848148ff3
2024-04-16 09:51:17 -04:00
588a8abdac misc: remove some mac-specific compiler warnings
Change-Id: I507637344e781273974fac2482e67556b4473ed3
2024-04-15 21:55:37 -04:00
8a149b6c4f callview: use dynamic loading for call views
This commit replaces a StackLayout with a Loader allowing us to load initial and ongoing call views dynamically based on the current conversation's call state.

This may fix several issues related to conversation loading including a possible uncaught binding loop based on observing CurrentConversation.id changes.

- small header clean up

Change-Id: Idfc723d8b39f19aafb026c19f26590910b5c26cd
2024-04-15 13:24:01 -04:00
6105f4f7ce misc: improve vscode integration
- adds a task to toggle testing configuration
- removes the test run launch configuration (now a task)
- adds macOS support and fixes gtest CMake integration
- removes the IPC launch configuration as it doesn't work correctly without delaying the client

Change-Id: I89e1f3abd0e050e0bcf205f1331596c660dd0d34
2024-04-11 16:04:45 -04:00
92341b27b6 rpm: Exclude vendored libraries from the list of provides
The RPM automatic dependency generator adds provides for all
libraries under %{_libdir}. When vendoring libraries you must disable
this behaviour, otherwise other packages that link against Qt will
not pull in the distro qt packages if jami-libqt is already installed

Change-Id: I3f0a018ea12be7f29d04ca4441cbfdebc2efb909
2024-04-11 08:21:07 -04:00
f39afdac4c misc: add vscode project settings file
- specifies some include paths
- enforces LF eol
- encourages clang-format use
- adds a comment regarding the IPC task

Note: currently GNU/Linux-specific

Change-Id: Ib208aca33026bf1c15a3ef18020805ceb20aa55f
2024-04-10 15:29:00 -04:00
690f2dd85c misc: add vscode task/launch configurations
This is a good start, but will need to be adapted for other platforms and improved over time.

Change-Id: Ib64046e852c3aa9cc9b492d0af8cee33ee1ff5de
2024-04-10 15:19:14 -04:00
bd45d6a406 misc: fix argument warning for non-debug builds
The `test` option is not used in non-debug builds.

Change-Id: I25eef5b414f987ae4acc435213173f8c78390866
2024-04-09 11:26:34 -05:00
5b92e4708a splitviews: fix non-transparent handle hover zone
This commit adds configurable size to the handle, as it will likely be made transparent soon.

Gitlab: #1611

Change-Id: I1574089d57b5993b59e29732e6a0c573ef91f606
2024-04-09 12:24:23 -04:00
63c01f1439 misc: filter some noisy logs
Change-Id: I799e8f66e2008323817c73292f94e8c625564d67
2024-04-08 15:35:31 -04:00
73aeb02ebd misc: bump daemon submodule
Change-Id: I800aad6362be0124a33904b834ff8e04b560d6a8
2024-04-08 15:11:47 -04:00
9d91317089 snap: add libpipewire to build dependencies
Change-Id: Ie2d24de1aabe59c9506786cfb5fa18fcf4e8cad2
2024-04-07 12:32:32 -04:00
474bc5f6a4 incalllocalvideo: allow resizing/changing opacity of local video
This feature was accidentally removed in a recent refactoring of the
local video preview:
https://review.jami.net/c/jami-client-qt/+/27740

Change-Id: I8b621d4f692124311f0807d1bc0be0e96717a499
2024-04-04 11:39:44 -04:00
f5b64e955b i18n: automatic bump
Change-Id: I0589f432edc46aba5effaaca85f1a53df00760dd
2024-04-01 16:42:26 -04:00
b88627d125 misc: bump daemon
Change-Id: Ifcbfe71e3f9d3ab8966ddfa3af8fb70a4d3b0c7d
2024-03-25 08:57:23 -04:00
200978a044 screensharing: add Wayland support
Change-Id: Ida5516630c6f95b16aa45f31ee8111a924273b3f
2024-03-22 10:47:45 -04:00
a673ff9890 chatviewheader: show details panel when syncing
Change-Id: Ifd0caafc6ec6cf10b0a834875e9dcf6daf527868
2024-03-21 10:01:46 -04:00
7803dd0991 i18n: automatic bump
Change-Id: Ic551429e2416c8ae81640a80788d0fa6412c8653
2024-03-18 16:42:27 -04:00
a8a736bc8c misc: bump daemon submodule
Change-Id: Ifb9bca812499d5b324223dc6fabe1a109ded662d
2024-03-12 17:58:13 -04:00
ff7acf9932 localvideo: refactor preview component device control
Change-Id: Ibcd88c5a3c73a0e67f94d70bc420845aa7b8c822
2024-03-12 16:33:21 -04:00
afde816b23 i18n: automatic bump
Change-Id: I50b14a21c3c442f7dd4b805a018d9af11c2c8305
2024-03-11 16:42:29 -04:00
162 changed files with 11126 additions and 4609 deletions

13
.gitignore vendored
View File

@ -1,9 +1,18 @@
*.user
doc/Doxyfile
### VisualStudioCode ###
.vscode/**/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
### VisualStudioCode Patch ###
# Ignore all local history of files
**/.history
GeneratedFiles/
.vs/
.vscode/
x64/
x86/
[wW]in32/

49
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,49 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Jami-Client-Debug",
"type": "cppdbg",
"request": "launch",
"program": "",
"linux":{
"MIMode": "gdb",
"program": "${workspaceFolder}/build/jami",
"args": [
"-d",
],
},
"osx": {
"MIMode": "lldb",
"program": "${workspaceFolder}/build/Jami.app/Contents/MacOS/Jami",
"environment": [
{
"name": "NO_COLOR",
"value": "true",
}
],
},
"cwd": "${workspaceFolder}",
"preLaunchTask": "cmake-build",
"externalConsole": false, // A macOS dev may want to set this to true.
},
{
// Using this configuration will require manually reconfiguring the project using
// build.py --no-libwrap, otherwise the daemon executable will not be built and the
// client will not be built with ENABLE_LIBWRAP=False.
"name": "Jami-Daemon-Debug",
"type": "cppdbg",
"request": "launch",
"linux": {
"MIMode": "gdb",
"program": "${workspaceFolder}/daemon/bin/dbus/jamid",
},
"program": "",
"args": [
"-cdp",
],
"cwd": "${workspaceFolder}",
"preLaunchTask": "cmake-build",
}
]
}

15
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,15 @@
{
"C_Cpp.default.includePath": [
"${default}",
"${workspaceFolder}/**",
"/usr/lib/libqt-jami/include/**",
"/usr/lib64/qt-jami/include/**",
],
"C_Cpp.default.cppStandard": "c++17",
"C_Cpp.default.cStandard": "c11",
"cmake.configureOnOpen": true,
"editor.formatOnSave": true,
"editor.defaultFormatter": "xaver.clang-format",
"files.eol": "\n",
"cSpell.enabled": false,
}

93
.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,93 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "run-tests",
"type": "shell",
"command": "ctest",
"options": {
"cwd": "${workspaceFolder}/build/tests",
"env": {
"HOME": "/tmp"
}
},
"args": [
"-V",
"-R"
],
"group": {
"kind": "test",
"isDefault": true
},
"problemMatcher": [],
"detail": "Run the tests using CTest."
},
{
"label": "cmake-configure",
"type": "shell",
"command": "cmake",
"args": [
"-S", ".",
"-B", "build",
"-DCMAKE_BUILD_TYPE=Debug",
"-DCMAKE_PREFIX_PATH=\"/usr/lib64/qt-jami;/usr/lib/libqt-jami\"",
],
"group": "build",
"problemMatcher": [],
"detail": "Generate the build system files with CMake."
},
{
"label": "cmake-configure-tests",
"type": "shell",
"command": "cmake",
"args": [
"-S", ".",
"-B", "build",
"-DBUILD_TESTING=${input:buildTestingInput}"
],
"group": "build",
"problemMatcher": [],
"detail": "Generate the build system files with CMake."
},
{
"label": "cmake-build",
"type": "shell",
"command": "cmake",
"args": [
"--build", "build",
"-j$(nproc)",
],
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": [
"$gcc"
],
"detail": "Compile the project using the generated build system.",
"dependsOn": [
"cmake-configure"
]
},
{
"label": "cmake-clean",
"type": "shell",
"command": "rm",
"args": [
"-rf",
"build"
],
"group": "build",
"problemMatcher": [],
"detail": "Clean the build directory."
}
],
"inputs": [
{
"id": "buildTestingInput",
"type": "pickString",
"description": "Do you want to enable testing?",
"options": ["True", "False"],
}
]
}

View File

@ -455,10 +455,12 @@ elseif (NOT APPLE)
${APP_SRC_DIR}/xrectsel.c
${APP_SRC_DIR}/connectivitymonitor.cpp
${APP_SRC_DIR}/dbuserrorhandler.cpp
${APP_SRC_DIR}/appversionmanager.cpp)
${APP_SRC_DIR}/appversionmanager.cpp
${APP_SRC_DIR}/screencastportal.cpp)
list(APPEND COMMON_HEADERS
${APP_SRC_DIR}/xrectsel.h
${APP_SRC_DIR}/dbuserrorhandler.h)
${APP_SRC_DIR}/dbuserrorhandler.h
${APP_SRC_DIR}/screencastportal.h)
list(APPEND QT_MODULES DBus)
find_package(PkgConfig REQUIRED)
@ -473,6 +475,11 @@ elseif (NOT APPLE)
add_definitions(${GIO_CFLAGS})
endif()
pkg_check_modules(GIOUNIX REQUIRED gio-unix-2.0)
if(GIOUNIX_FOUND)
add_definitions(${GIOUNIX_CFLAGS})
endif()
pkg_check_modules(LIBNM libnm)
if(LIBNM_FOUND)
add_definitions(-DUSE_LIBNM)
@ -584,6 +591,7 @@ include_directories(
if(ENABLE_LIBWRAP)
list(APPEND COMMON_HEADERS
${LIBCLIENT_SRC_DIR}/qtwrapper/instancemanager_wrap.h)
add_definitions(-DENABLE_LIBWRAP=true)
endif()
# SFPM
@ -811,12 +819,20 @@ else()
"-framework Security"
compression
resolv
)
)
set(APP_CONTAINER "${CMAKE_BINARY_DIR}/${PROJECT_NAME}.app/Contents")
# ringtones. Copy the entire directory to the app bundle.
# daemon/ringtones -> Jami.app/Contents/Resources/ringtones
execute_process(
COMMAND ${CMAKE_COMMAND} -E copy_directory
${DAEMON_DIR}/ringtones
${APP_CONTAINER}/Resources/ringtones
)
# translations
if(Qt${QT_VERSION_MAJOR}LinguistTools_FOUND)
set(APP_CONTAINER
"${CMAKE_BINARY_DIR}/${PROJECT_NAME}.app/Contents")
file(GLOB TS_FILES ${PROJECT_SOURCE_DIR}/translations/*.ts)
# Generate lproj folders.
@ -844,26 +860,26 @@ else()
MACOSX_BUNDLE_SHORT_VERSION_STRING "${JAMI_VERSION}"
MACOSX_BUNDLE_BUNDLE_VERSION "${JAMI_BUILD}"
MACOSX_BUNDLE_COPYRIGHT "${PROJ_COPYRIGHT}")
if(APPSTORE)
message(STATUS "app store version")
set_target_properties(${PROJECT_NAME} PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/resources/entitlements/appstore/Jami.entitlements")
else()
set_target_properties(${PROJECT_NAME} PROPERTIES
SPARKLE_URL "${SPARKLE_URL}"
SPARKLE_PUBLIC_KEY "${SPARKLE_PUBLIC_KEY}"
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/resources/entitlements/Jami.entitlements"
XCODE_ATTRIBUTE_ENABLE_HARDENED_RUNTIME TRUE)
endif()
if(DEPLOY)
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -DQML_SRC_DIR=${SRC_DIR}
-DMAC_DEPLOY_QT_PATH=${CMAKE_PREFIX_PATH}/bin
-DEXE_NAME="${CMAKE_BINARY_DIR}/${PROJECT_NAME}.app"
-DSPARKLE_PATH=${SPARKLE_FRAMEWORK}
-DENABLE_SPARKLE=${ENABLE_SPARKLE}
-P ${EXTRAS_DIR}/build/cmake/macos_qt_deploy.cmake)
endif()
if(APPSTORE)
message(STATUS "app store version")
set_target_properties(${PROJECT_NAME} PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/resources/entitlements/appstore/Jami.entitlements")
else()
set_target_properties(${PROJECT_NAME} PROPERTIES
SPARKLE_URL "${SPARKLE_URL}"
SPARKLE_PUBLIC_KEY "${SPARKLE_PUBLIC_KEY}"
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/resources/entitlements/Jami.entitlements"
XCODE_ATTRIBUTE_ENABLE_HARDENED_RUNTIME TRUE)
endif()
if(DEPLOY)
execute_process(COMMAND
"${CMAKE_PREFIX_PATH}/bin/macdeployqt"
"${CMAKE_BINARY_DIR}/${PROJECT_NAME}.app"
-qmldir=${QML_SRC_DIR})
if(${ENABLE_SPARKLE} MATCHES true)
file(COPY ${SPARKLE_FRAMEWORK} DESTINATION ${EXE_NAME}/Contents/Frameworks/)
endif()
endif()
endif()
target_include_directories(${PROJECT_NAME} PRIVATE ${CLIENT_INCLUDE_DIRS})

View File

@ -288,7 +288,7 @@ Once the build has finished, you should then be able to use the Visual Studio So
**Set up**
- macOS minimum version 10.15
- macOS minimum version 11.0
- install python3
- download xcode
- install Qt 6.6

View File

@ -100,7 +100,7 @@ ZYPPER_DEPENDENCIES = [
'speexdsp-devel', 'speex-devel', 'libdbus-c++-devel', 'jsoncpp-devel', 'yaml-cpp-devel',
'yasm', 'libuuid-devel', 'libnettle-devel', 'libopus-devel', 'libexpat-devel',
'libgnutls-devel', 'msgpack-c-devel', 'msgpack-cxx-devel', 'libavcodec-devel', 'libavdevice-devel', 'pcre-devel',
'alsa-devel', 'libpulse-devel', 'libudev-devel', 'libva-devel', 'libvdpau-devel',
'alsa-devel', 'libpulse-devel', 'libudev-devel', 'libva-devel', 'libvdpau-devel', 'pipewire-devel',
'libopenssl-devel', 'libavutil-devel',
]
@ -130,7 +130,7 @@ DNF_DEPENDENCIES = [
'gcc-c++', 'which', 'alsa-lib-devel', 'systemd-devel', 'libuuid-devel',
'uuid-devel', 'gnutls-devel', 'nettle-devel', 'opus-devel', 'speexdsp-devel',
'yaml-cpp-devel', 'swig', 'jsoncpp-devel',
'patch', 'libva-devel', 'openssl-devel', 'libvdpau-devel', 'msgpack-devel',
'patch', 'libva-devel', 'openssl-devel', 'libvdpau-devel', 'pipewire-devel', 'msgpack-devel',
'sqlite-devel', 'openssl-static', 'pandoc', 'nasm',
'bzip2'
]
@ -154,7 +154,7 @@ APT_DEPENDENCIES = [
'libopus-dev', 'libpcre3-dev', 'libpulse-dev', 'libssl-dev',
'libspeex-dev', 'libspeexdsp-dev', 'libswscale-dev', 'libtool',
'libudev-dev', 'libyaml-cpp-dev', 'sip-tester', 'swig',
'uuid-dev', 'yasm', 'libjsoncpp-dev', 'libva-dev', 'libvdpau-dev', 'libmsgpack-dev',
'uuid-dev', 'yasm', 'libjsoncpp-dev', 'libva-dev', 'libvdpau-dev', 'libpipewire-0.3-dev', 'libmsgpack-dev',
'pandoc', 'nasm', 'dpkg-dev', 'libsystemd-dev'
]

2
daemon

Submodule daemon updated: 8596d253a2...cbf8f0af6d

View File

@ -1,7 +0,0 @@
message("Qt deploying in dir " ${QML_SRC_DIR})
execute_process(COMMAND "${MAC_DEPLOY_QT_PATH}/macdeployqt"
${EXE_NAME}
-qmldir=${QML_SRC_DIR})
if(${ENABLE_SPARKLE} MATCHES true)
file(COPY ${SPARKLE_PATH} DESTINATION ${EXE_NAME}/Contents/Frameworks/)
endif()

View File

@ -1,4 +1,4 @@
FROM ubuntu:20.04
FROM ubuntu:22.04
ENV DEBIAN_FRONTEND noninteractive
ENV QT_QUICK_BACKEND software
@ -10,7 +10,7 @@ RUN apt-get update && \
RUN apt install gnupg dirmngr ca-certificates curl --no-install-recommends
RUN curl -s https://dl.jami.net/public-key.gpg | tee /usr/share/keyrings/jami-archive-keyring.gpg > /dev/null
RUN sh -c "echo 'deb [signed-by=/usr/share/keyrings/jami-archive-keyring.gpg] https://dl.jami.net/internal/ubuntu_20.04/ jami main' > /etc/apt/sources.list.d/jami.list"
RUN sh -c "echo 'deb [signed-by=/usr/share/keyrings/jami-archive-keyring.gpg] https://dl.jami.net/internal/ubuntu_22.04/ jami main' > /etc/apt/sources.list.d/jami.list"
RUN apt-get update && apt-get install libqt-jami -y
RUN apt-get install -y -o Acquire::Retries=10 \
@ -51,6 +51,7 @@ RUN apt-get install -y -o Acquire::Retries=10 \
libswscale-dev \
libavdevice-dev \
libopus-dev \
libpipewire-0.3-dev \
libudev-dev \
libgsm1-dev \
libjsoncpp-dev \
@ -65,6 +66,7 @@ RUN apt-get install -y -o Acquire::Retries=10 \
libvdpau-dev \
libssl-dev
RUN apt-get install -y pandoc \
libcppunit-dev \
googletest \
libgtest-dev \
wget

View File

@ -49,7 +49,7 @@ QT_MAJOR := 6
QT_MINOR := 6
QT_PATCH := 1
QT_TARBALL_CHECKSUM := dd3668f65645fe270bc615d748bd4dc048bd17b9dc297025106e6ecc419ab95d
DEBIAN_QT_VERSION := $(QT_MAJOR).$(QT_MINOR).$(QT_PATCH)-0
DEBIAN_QT_VERSION := $(QT_MAJOR).$(QT_MINOR).$(QT_PATCH)-1
DEBIAN_QT_DSC_FILENAME := libqt-jami_$(DEBIAN_QT_VERSION).dsc
QT_JAMI_PREFIX := /usr/lib/libqt-jami
@ -166,11 +166,12 @@ DISTRIBUTIONS := \
debian_unstable \
ubuntu_20.04 \
ubuntu_22.04 \
ubuntu_23.04 \
ubuntu_23.10 \
ubuntu_24.04 \
fedora_37 \
fedora_38 \
fedora_39 \
fedora_40 \
alma_9 \
opensuse-leap_15.4 \
opensuse-leap_15.5 \

View File

@ -100,6 +100,7 @@ RUN dnf install -y \
cmake \
fmt-devel \
python3-html5lib \
cups-devel
cups-devel \
pipewire-devel
ADD extras/packaging/gnu-linux/scripts/build-package-rpm.sh /opt/build-package-rpm.sh
CMD ["/opt/build-package-rpm.sh"]

View File

@ -28,4 +28,10 @@ ADD extras/packaging/gnu-linux/scripts/install-cmake.sh /opt/install-cmake.sh
RUN /opt/install-cmake.sh
ADD extras/packaging/gnu-linux/scripts/build-package-debian.sh /opt/build-package-debian.sh
# Setting this variable so that FFmpeg gets built without pipewiregrab
# (see daemon/contrib/bootstrap and daemon/contrib/src/ffmpeg/rules.mak)
# We rely on PipeWire for screen sharing on Wayland, but the version available on Debian 11 is too old.
ENV DISABLE_PIPEWIRE=true
CMD ["/opt/build-package-debian.sh"]

View File

@ -27,9 +27,7 @@ RUN /opt/prebuild-package-debian.sh qt-deps
COPY extras/packaging/gnu-linux/rules/debian/control /tmp/builddeps/debian/control
RUN /opt/prebuild-package-debian.sh jami-deps
# Install CMake 3.21 for Qt 6
ADD extras/packaging/gnu-linux/scripts/install-cmake.sh /opt/install-cmake.sh
RUN /opt/install-cmake.sh
RUN apt-get remove -y libre2-dev libre2-11
ADD extras/packaging/gnu-linux/scripts/build-package-debian.sh /opt/build-package-debian.sh
CMD ["/opt/build-package-debian.sh"]

View File

@ -27,9 +27,7 @@ RUN /opt/prebuild-package-debian.sh qt-deps
COPY extras/packaging/gnu-linux/rules/debian/control /tmp/builddeps/debian/control
RUN /opt/prebuild-package-debian.sh jami-deps
# Install CMake 3.21 for Qt 6
ADD extras/packaging/gnu-linux/scripts/install-cmake.sh /opt/install-cmake.sh
RUN /opt/install-cmake.sh
RUN apt-get remove -y libre2-dev libre2-11
ADD extras/packaging/gnu-linux/scripts/build-package-debian.sh /opt/build-package-debian.sh
CMD ["/opt/build-package-debian.sh"]

View File

@ -98,6 +98,7 @@ RUN dnf install -y \
clang \
cmake \
fmt-devel \
pipewire-devel \
cups-devel #Chromium for Qt
ADD extras/packaging/gnu-linux/scripts/build-package-rpm.sh /opt/build-package-rpm.sh

View File

@ -98,7 +98,8 @@ RUN dnf install -y \
cmake \
fmt-devel \
python3-html5lib \
cups-devel
cups-devel \
pipewire-devel
ADD extras/packaging/gnu-linux/scripts/build-package-rpm.sh /opt/build-package-rpm.sh

View File

@ -97,7 +97,8 @@ RUN dnf install -y \
cmake \
fmt-devel \
python3.10 \
cups-devel
cups-devel \
pipewire-devel
ADD extras/packaging/gnu-linux/scripts/build-package-rpm.sh /opt/build-package-rpm.sh

View File

@ -0,0 +1,105 @@
FROM fedora:40
RUN dnf clean all
RUN dnf update -y
RUN dnf install -y dnf-command\(builddep\) rpmdevtools && \
dnf install -y mock
RUN dnf groupinstall -y "X Software Development"
RUN dnf install -y \
git \
rpm-build \
tar \
make \
autoconf \
automake \
nasm \
speexdsp-devel \
pulseaudio-libs-devel \
libcanberra-devel \
libcurl-devel \
libtool \
mesa-libgbm-devel \
mesa-dri-drivers \
dbus-devel \
expat-devel \
pcre-devel \
yaml-cpp-devel \
libXext-devel \
libXfixes-devel \
yasm \
speex-devel \
gsm-devel \
chrpath \
check \
astyle \
uuid-c++-devel \
gettext-devel \
gcc-c++ \
which \
alsa-lib-devel \
systemd-devel \
libuuid-devel \
uuid-devel \
gnutls-devel \
nettle-devel \
opus-devel \
patch \
jsoncpp-devel \
libnatpmp-devel \
webkitgtk4-devel \
cryptopp-devel \
libva-devel \
libvdpau-devel \
msgpack-devel \
NetworkManager-libnm-devel \
openssl-devel \
clutter-devel \
clutter-gtk-devel \
libappindicator-gtk3-devel \
libnotify-devel \
libupnp-devel \
qrencode-devel \
libargon2-devel \
libsndfile-devel \
libdrm \
gperf \
bison \
clang \
clang-devel \
llvm-devel \
nodejs \
flex \
gstreamer1 gstreamer1-devel \
gstreamer1-plugins-base-devel \
gstreamer1-plugins-good \
gstreamer1-plugins-bad-free-devel \
nss-devel \
libxcb* \
libxkb* \
libX11-devel \
vulkan-devel \
libXrender-devel \
xcb-util-* \
xz \
xkeyboard-config \
libnotify \
wget \
libstdc++-static \
sqlite-devel \
perl-generators \
perl-English \
libxshmfence-devel \
ninja-build \
clang \
cmake \
fmt-devel \
python3.10 \
cups-devel \
pipewire-devel
ADD extras/packaging/gnu-linux/scripts/build-package-rpm.sh /opt/build-package-rpm.sh
CMD ["/opt/build-package-rpm.sh"]

View File

@ -99,7 +99,8 @@ RUN zypper --non-interactive install -y \
gstreamer-plugins-bad-devel \
gstreamer-plugins-base-devel \
cmake \
wget
wget \
pipewire-devel
# openSUSE Leap 15.4 comes with Python 3.6 by default,
# but we need at least 3.7 to compile Qt 6.6.1
@ -112,4 +113,10 @@ ADD extras/packaging/gnu-linux/scripts/build-package-rpm.sh /opt/build-package-r
ENV CC=gcc
ENV CXX=g++
# Setting this variable so that FFmpeg gets built without pipewiregrab
# (see daemon/contrib/bootstrap and daemon/contrib/src/ffmpeg/rules.mak)
# We rely on PipeWire for screen sharing on Wayland, but the version available on openSUSE Leap 15.4 is too old.
ENV DISABLE_PIPEWIRE=true
CMD ["/opt/build-package-rpm.sh"]

View File

@ -1,6 +1,6 @@
FROM opensuse/leap:15.5
RUN zypper refresh
RUN zypper --gpg-auto-import-keys refresh
RUN zypper --non-interactive install -y \
dnf \
@ -100,7 +100,8 @@ RUN zypper --non-interactive install -y \
gstreamer-plugins-bad-devel \
gstreamer-plugins-base-devel \
cmake \
wget
wget \
pipewire-devel
# openSUSE Leap 15.5 comes with Python 3.6 by default,
# but we need at least 3.7 to compile Qt 6.6.1

View File

@ -33,4 +33,10 @@ ADD extras/packaging/gnu-linux/scripts/install-cmake.sh /opt/install-cmake.sh
RUN /opt/install-cmake.sh
ADD extras/packaging/gnu-linux/scripts/build-package-debian.sh /opt/build-package-debian.sh
# Setting this variable so that FFmpeg gets built without pipewiregrab
# (see daemon/contrib/bootstrap and daemon/contrib/src/ffmpeg/rules.mak)
# We rely on PipeWire for screen sharing on Wayland, but the version available on Ubuntu 20.04 is too old.
ENV DISABLE_PIPEWIRE=true
CMD ["/opt/build-package-debian.sh"]

View File

@ -1,4 +1,4 @@
FROM ubuntu:23.04
FROM ubuntu:24.04
ENV DEBIAN_FRONTEND=noninteractive
@ -10,6 +10,9 @@ RUN apt-get update && \
python-is-python3 \
wget
ADD extras/packaging/gnu-linux/scripts/install-gcc-debian.sh /opt/install-gcc-debian.sh
RUN /opt/install-gcc-debian.sh 13
ADD extras/packaging/gnu-linux/scripts/prebuild-package-debian.sh /opt/prebuild-package-debian.sh
COPY extras/packaging/gnu-linux/rules/debian-qt/control /tmp/builddeps/debian/control

View File

@ -92,6 +92,7 @@ Build-Depends: debhelper (>= 9),
libgl1-mesa-dri,
# pkg-kde-tools (>= 0.15.17~),
python3:any,
python3-bs4,
python3-html5lib,
# qtbase5-private-dev (>= 5.15.2+dfsg~),
xauth <!nocheck>,

View File

@ -0,0 +1,348 @@
From 24fb774485f719df1e84dda31605d3f69202d69f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois-Simon=20Fauteux-Chapleau?=
<francois-simon.fauteux-chapleau@savoirfairelinux.com>
Date: Thu, 8 Aug 2024 14:59:17 -0400
Subject: [PATCH] qtwebengine: enable building with Python 3.12
Replace the deprecated imp module by importlib:
https://codereview.qt-project.org/c/qt/qtwebengine-chromium/+/524014
https://codereview.qt-project.org/c/qt/qtwebengine-chromium/+/534568
Update six to fix html5lib import failure:
https://codereview.qt-project.org/c/qt/qtwebengine-chromium/+/535605
https://issues.chromium.org/issues/40286977
---
.../protobufs/binary_proto_generator.py | 8 ++++++--
.../mojo/public/tools/mojom/mojom/fileutil.py | 1 -
.../tools/mojom/mojom/fileutil_unittest.py | 5 +----
.../mojom/mojom/generate/generator_unittest.py | 7 ++-----
.../mojom/mojom/generate/translate_unittest.py | 4 ----
.../tools/mojom/mojom/parse/ast_unittest.py | 6 ------
.../mojom/parse/conditional_features_unittest.py | 8 ++------
.../mojo/public/tools/mojom/mojom/parse/lexer.py | 1 -
.../tools/mojom/mojom/parse/lexer_unittest.py | 7 ++-----
.../tools/mojom/mojom/parse/parser_unittest.py | 5 -----
.../third_party/catapult/third_party/six/six.py | 16 ++++++++++++++++
11 files changed, 29 insertions(+), 39 deletions(-)
diff --git a/qtwebengine/src/3rdparty/chromium/components/resources/protobufs/binary_proto_generator.py b/qtwebengine/src/3rdparty/chromium/components/resources/protobufs/binary_proto_generator.py
index 2a1802dccdc..8b9de65ed0b 100755
--- a/qtwebengine/src/3rdparty/chromium/components/resources/protobufs/binary_proto_generator.py
+++ b/qtwebengine/src/3rdparty/chromium/components/resources/protobufs/binary_proto_generator.py
@@ -9,7 +9,7 @@
"""
from __future__ import print_function
import abc
-import imp
+from importlib import util as imp_util
import optparse
import os
import re
@@ -68,7 +68,11 @@ class GoogleProtobufModuleImporter:
raise ImportError(fullname)
filepath = self._fullname_to_filepath(fullname)
- return imp.load_source(fullname, filepath)
+ spec = imp_util.spec_from_file_location(fullname, filepath)
+ loaded = imp_util.module_from_spec(spec)
+ spec.loader.exec_module(loaded)
+
+ return loaded
class BinaryProtoGenerator:
diff --git a/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/fileutil.py b/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/fileutil.py
index 29daec367c5..124f12c134b 100644
--- a/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/fileutil.py
+++ b/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/fileutil.py
@@ -3,7 +3,6 @@
# found in the LICENSE file.
import errno
-import imp
import os.path
import sys
diff --git a/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/fileutil_unittest.py b/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/fileutil_unittest.py
index 48eaf4eca94..c93d22898d2 100644
--- a/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/fileutil_unittest.py
+++ b/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/fileutil_unittest.py
@@ -2,19 +2,16 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-import imp
import os.path
import shutil
-import sys
import tempfile
import unittest
from mojom import fileutil
-
class FileUtilTest(unittest.TestCase):
def testEnsureDirectoryExists(self):
- """Test that EnsureDirectoryExists fuctions correctly."""
+ """Test that EnsureDirectoryExists functions correctly."""
temp_dir = tempfile.mkdtemp()
try:
diff --git a/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/generate/generator_unittest.py b/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/generate/generator_unittest.py
index 76cda3981f3..7143e07c4d7 100644
--- a/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/generate/generator_unittest.py
+++ b/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/generate/generator_unittest.py
@@ -2,12 +2,11 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-import imp
+import importlib.util
import os.path
import sys
import unittest
-
def _GetDirAbove(dirname):
"""Returns the directory "above" this file containing |dirname| (which must
also be "above" this file)."""
@@ -20,12 +19,11 @@ def _GetDirAbove(dirname):
try:
- imp.find_module("mojom")
+ importlib.util.find_spec("mojom")
except ImportError:
sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib"))
from mojom.generate import generator
-
class StringManipulationTest(unittest.TestCase):
"""generator contains some string utilities, this tests only those."""
@@ -69,6 +67,5 @@ class StringManipulationTest(unittest.TestCase):
self.assertEquals("SNAKE_D3D11_CASE",
generator.ToUpperSnakeCase("snakeD3d11Case"))
-
if __name__ == "__main__":
unittest.main()
diff --git a/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/generate/translate_unittest.py b/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/generate/translate_unittest.py
index 4259374513f..558e71e1193 100644
--- a/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/generate/translate_unittest.py
+++ b/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/generate/translate_unittest.py
@@ -2,16 +2,12 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-import imp
-import os.path
-import sys
import unittest
from mojom.generate import module as mojom
from mojom.generate import translate
from mojom.parse import ast
-
class TranslateTest(unittest.TestCase):
"""Tests |parser.Parse()|."""
diff --git a/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/parse/ast_unittest.py b/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/parse/ast_unittest.py
index c36376712e7..b289f7b11f6 100644
--- a/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/parse/ast_unittest.py
+++ b/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/parse/ast_unittest.py
@@ -2,14 +2,10 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-import imp
-import os.path
-import sys
import unittest
from mojom.parse import ast
-
class _TestNode(ast.NodeBase):
"""Node type for tests."""
@@ -20,13 +16,11 @@ class _TestNode(ast.NodeBase):
def __eq__(self, other):
return super().__eq__(other) and self.value == other.value
-
class _TestNodeList(ast.NodeListBase):
"""Node list type for tests."""
_list_item_type = _TestNode
-
class ASTTest(unittest.TestCase):
"""Tests various AST classes."""
diff --git a/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py b/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py
index 5fc582025ee..2fa5d2be6ab 100644
--- a/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py
+++ b/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py
@@ -2,12 +2,11 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-import imp
+import importlib.util
import os
import sys
import unittest
-
def _GetDirAbove(dirname):
"""Returns the directory "above" this file containing |dirname| (which must
also be "above" this file)."""
@@ -18,9 +17,8 @@ def _GetDirAbove(dirname):
if tail == dirname:
return path
-
try:
- imp.find_module('mojom')
+ importlib.util.find_spec("mojom")
except ImportError:
sys.path.append(os.path.join(_GetDirAbove('pylib'), 'pylib'))
import mojom.parse.ast as ast
@@ -29,7 +27,6 @@ import mojom.parse.parser as parser
ENABLED_FEATURES = frozenset({'red', 'green', 'blue'})
-
class ConditionalFeaturesTest(unittest.TestCase):
"""Tests |mojom.parse.conditional_features|."""
@@ -356,6 +353,5 @@ class ConditionalFeaturesTest(unittest.TestCase):
conditional_features.RemoveDisabledDefinitions,
definition, ENABLED_FEATURES)
-
if __name__ == '__main__':
unittest.main()
diff --git a/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/parse/lexer.py b/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/parse/lexer.py
index 73ca15df94c..1083a1af7bb 100644
--- a/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/parse/lexer.py
+++ b/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/parse/lexer.py
@@ -2,7 +2,6 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-import imp
import os.path
import sys
diff --git a/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py b/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py
index ce376da66e0..bc9f8354316 100644
--- a/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py
+++ b/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py
@@ -2,12 +2,11 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-import imp
+import importlib.util
import os.path
import sys
import unittest
-
def _GetDirAbove(dirname):
"""Returns the directory "above" this file containing |dirname| (which must
also be "above" this file)."""
@@ -18,17 +17,15 @@ def _GetDirAbove(dirname):
if tail == dirname:
return path
-
sys.path.insert(1, os.path.join(_GetDirAbove("mojo"), "third_party"))
from ply import lex
try:
- imp.find_module("mojom")
+ importlib.util.find_spec("mojom")
except ImportError:
sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib"))
import mojom.parse.lexer
-
# This (monkey-patching LexToken to make comparison value-based) is evil, but
# we'll do it anyway. (I'm pretty sure ply's lexer never cares about comparing
# for object identity.)
diff --git a/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/parse/parser_unittest.py b/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/parse/parser_unittest.py
index 0513343ec7e..0a26307b1a3 100644
--- a/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/parse/parser_unittest.py
+++ b/qtwebengine/src/3rdparty/chromium/mojo/public/tools/mojom/mojom/parse/parser_unittest.py
@@ -2,16 +2,12 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-import imp
-import os.path
-import sys
import unittest
from mojom.parse import ast
from mojom.parse import lexer
from mojom.parse import parser
-
class ParserTest(unittest.TestCase):
"""Tests |parser.Parse()|."""
@@ -1375,6 +1371,5 @@ class ParserTest(unittest.TestCase):
r" *associated\? MyInterface& a;$"):
parser.Parse(source3, "my_file.mojom")
-
if __name__ == "__main__":
unittest.main()
diff --git a/qtwebengine/src/3rdparty/chromium/third_party/catapult/third_party/six/six.py b/qtwebengine/src/3rdparty/chromium/third_party/catapult/third_party/six/six.py
index 83f69783d1a..5e7f0ce4437 100644
--- a/qtwebengine/src/3rdparty/chromium/third_party/catapult/third_party/six/six.py
+++ b/qtwebengine/src/3rdparty/chromium/third_party/catapult/third_party/six/six.py
@@ -71,6 +71,11 @@ else:
MAXSIZE = int((1 << 63) - 1)
del X
+if PY34:
+ from importlib.util import spec_from_loader
+else:
+ spec_from_loader = None
+
def _add_doc(func, doc):
"""Add documentation to a function."""
@@ -186,6 +191,11 @@ class _SixMetaPathImporter(object):
return self
return None
+ def find_spec(self, fullname, path, target=None):
+ if fullname in self.known_modules:
+ return spec_from_loader(fullname, self)
+ return None
+
def __get_module(self, fullname):
try:
return self.known_modules[fullname]
@@ -223,6 +233,12 @@ class _SixMetaPathImporter(object):
return None
get_source = get_code # same as get_code
+ def create_module(self, spec):
+ return self.load_module(spec.name)
+
+ def exec_module(self, module):
+ pass
+
_importer = _SixMetaPathImporter(__name__)
--
2.34.1

View File

@ -0,0 +1,16 @@
qt3d/src/3rdparty/assimp/src/code/AssetLib/FBX/FBXBinaryTokenizer.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/qt3d/src/3rdparty/assimp/src/code/AssetLib/FBX/FBXBinaryTokenizer.cpp b/qt3d/src/3rdparty/assimp/src/code/AssetLib/FBX/FBXBinaryTokenizer.cpp
index 3488120543..120e47a76f 100644
--- a/qt3d/src/3rdparty/assimp/src/code/AssetLib/FBX/FBXBinaryTokenizer.cpp
+++ b/qt3d/src/3rdparty/assimp/src/code/AssetLib/FBX/FBXBinaryTokenizer.cpp
@@ -472,7 +472,7 @@ void TokenizeBinary(TokenList& output_tokens, const char* input, size_t length)
}
catch (const DeadlyImportError& e)
{
- if (!is64bits && (length > std::numeric_limits<std::uint32_t>::max())) {
+ if (!is64bits && (length > std::numeric_limits<uint32_t>::max())) {
throw DeadlyImportError("The FBX file is invalid. This may be because the content is too big for this older version (", ai_to_string(version), ") of the FBX format. (", e.what(), ")");
}
throw;

View File

@ -0,0 +1,26 @@
From cf208d11dc8a9a02160a57283596ec8bab964a09 Mon Sep 17 00:00:00 2001
From: Sebastien Blin <sebastien.blin@savoirfairelinux.com>
Date: Mon, 27 May 2024 16:01:21 -0400
Subject: [PATCH] qtwayland: downgrade wl-seat to avoid high-resolution
scrolling events
---
qtwayland/src/client/qwaylandinputdevice.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/qtwayland/src/client/qwaylandinputdevice.cpp b/qtwayland/src/client/qwaylandinputdevice.cpp
index a4f8757e3c..ad0aa7941c 100644
--- a/qtwayland/src/client/qwaylandinputdevice.cpp
+++ b/qtwayland/src/client/qwaylandinputdevice.cpp
@@ -383,7 +383,7 @@ QWaylandInputDevice::Touch::~Touch()
}
QWaylandInputDevice::QWaylandInputDevice(QWaylandDisplay *display, int version, uint32_t id)
- : QtWayland::wl_seat(display->wl_registry(), id, qMin(version, 9))
+ : QtWayland::wl_seat(display->wl_registry(), id, qMin(version, 7))
, mQDisplay(display)
, mDisplay(display->wl_display())
{
--
2.45.0

View File

@ -0,0 +1,40 @@
From 420b3e5ac2e91b7a99488ac34577e2798a84a68c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois-Simon=20Fauteux-Chapleau?=
<francois-simon.fauteux-chapleau@savoirfairelinux.com>
Date: Tue, 6 Aug 2024 17:35:56 -0400
Subject: [PATCH] qtbase: fix CMake error
For more information, see:
https://github.com/qt/qtbase/commit/3411f2984a5325a35e3bed1f961e5973d8a565b9
---
qtbase/configure.cmake | 1 +
qtbase/src/corelib/CMakeLists.txt | 1 -
2 files changed, 1 insertion(+), 1 deletion(-)
diff --git a/qtbase/configure.cmake b/qtbase/configure.cmake
index 43de2aa026..37a82dcdb6 100644
--- a/qtbase/configure.cmake
+++ b/qtbase/configure.cmake
@@ -18,6 +18,7 @@ if(TARGET ZLIB::ZLIB)
set_property(TARGET ZLIB::ZLIB PROPERTY IMPORTED_GLOBAL TRUE)
endif()
+qt_find_package(Threads PROVIDED_TARGETS Threads::Threads)
qt_find_package(WrapOpenSSLHeaders PROVIDED_TARGETS WrapOpenSSLHeaders::WrapOpenSSLHeaders MODULE_NAME core)
# openssl_headers
# OPENSSL_VERSION_MAJOR is not defined for OpenSSL 1.1.1
diff --git a/qtbase/src/corelib/CMakeLists.txt b/qtbase/src/corelib/CMakeLists.txt
index 31b81734e8..b62e2f763b 100644
--- a/qtbase/src/corelib/CMakeLists.txt
+++ b/qtbase/src/corelib/CMakeLists.txt
@@ -1,7 +1,6 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-qt_find_package(Threads PROVIDED_TARGETS Threads::Threads)
qt_find_package(WrapPCRE2 PROVIDED_TARGETS WrapPCRE2::WrapPCRE2)
qt_find_package(WrapZLIB PROVIDED_TARGETS WrapZLIB::WrapZLIB)
--
2.34.1

View File

@ -0,0 +1,40 @@
From 4c7360faeb0fb7f1dfd995619fb8c596b4e15606 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois-Simon=20Fauteux-Chapleau?=
<francois-simon.fauteux-chapleau@savoirfairelinux.com>
Date: Thu, 8 Aug 2024 10:29:43 -0400
Subject: [PATCH] qtwebengine: add missing chromium dependencies
For more information, see:
https://codereview.qt-project.org/c/qt/qtwebengine-chromium/+/555586
---
chromium/content/public/browser/BUILD.gn | 1 +
chromium/extensions/browser/api/declarative_net_request/BUILD.gn | 1 +
2 files changed, 2 insertions(+)
diff --git a/qtwebengine/src/3rdparty/chromium/content/public/browser/BUILD.gn b/qtwebengine/src/3rdparty/chromium/content/public/browser/BUILD.gn
index b25bf5764e7..dfbfb2ec77b 100644
--- a/qtwebengine/src/3rdparty/chromium/content/public/browser/BUILD.gn
+++ b/qtwebengine/src/3rdparty/chromium/content/public/browser/BUILD.gn
@@ -515,6 +515,7 @@ jumbo_source_set("browser_sources") {
"//cc",
"//components/services/storage/public/cpp",
"//components/viz/host",
+ "//components/spellcheck:buildflags",
"//content/browser", # Must not be public_deps!
"//device/fido",
"//gpu",
diff --git a/qtwebengine/src/3rdparty/chromium/extensions/browser/api/declarative_net_request/BUILD.gn b/qtwebengine/src/3rdparty/chromium/extensions/browser/api/declarative_net_request/BUILD.gn
index 1fc492f5a0c..13a266e22f1 100644
--- a/qtwebengine/src/3rdparty/chromium/extensions/browser/api/declarative_net_request/BUILD.gn
+++ b/qtwebengine/src/3rdparty/chromium/extensions/browser/api/declarative_net_request/BUILD.gn
@@ -23,6 +23,7 @@ source_set("declarative_net_request") {
"//extensions/common",
"//extensions/common/api",
"//services/preferences/public/cpp",
+ "//components/web_cache/browser",
]
public_deps = [ "//extensions/browser:browser_sources" ]
--
2.34.1

View File

@ -0,0 +1,49 @@
From ab6d5bebaf68a9f4d00440b2adbaffe0e5b2ae6c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois-Simon=20Fauteux-Chapleau?=
<francois-simon.fauteux-chapleau@savoirfairelinux.com>
Date: Thu, 8 Aug 2024 10:55:08 -0400
Subject: [PATCH] qtwebengine: fix libxml2 build error
Version 2.12 of libxml2 introduced a change that broke chromium's build,
see: https://codereview.qt-project.org/c/qt/qtwebengine-chromium/+/523633
---
.../third_party/blink/renderer/core/xml/xslt_processor.h | 5 +++++
.../blink/renderer/core/xml/xslt_processor_libxslt.cc | 4 ++++
2 files changed, 9 insertions(+)
diff --git a/qtwebengine/src/3rdparty/chromium/third_party/blink/renderer/core/xml/xslt_processor.h b/qtwebengine/src/3rdparty/chromium/third_party/blink/renderer/core/xml/xslt_processor.h
index d53835e9675..72536e4fd7d 100644
--- a/qtwebengine/src/3rdparty/chromium/third_party/blink/renderer/core/xml/xslt_processor.h
+++ b/qtwebengine/src/3rdparty/chromium/third_party/blink/renderer/core/xml/xslt_processor.h
@@ -77,7 +77,12 @@ class XSLTProcessor final : public ScriptWrappable {
void reset();
+#if LIBXML_VERSION >= 21200
+ static void ParseErrorFunc(void* user_data, const xmlError*);
+#else
static void ParseErrorFunc(void* user_data, xmlError*);
+#endif
+
static void GenericErrorFunc(void* user_data, const char* msg, ...);
// Only for libXSLT callbacks
diff --git a/qtwebengine/src/3rdparty/chromium/third_party/blink/renderer/core/xml/xslt_processor_libxslt.cc b/qtwebengine/src/3rdparty/chromium/third_party/blink/renderer/core/xml/xslt_processor_libxslt.cc
index 133e0b3355d..e8e6a09f485 100644
--- a/qtwebengine/src/3rdparty/chromium/third_party/blink/renderer/core/xml/xslt_processor_libxslt.cc
+++ b/qtwebengine/src/3rdparty/chromium/third_party/blink/renderer/core/xml/xslt_processor_libxslt.cc
@@ -66,7 +66,11 @@ void XSLTProcessor::GenericErrorFunc(void*, const char*, ...) {
// It would be nice to do something with this error message.
}
+#if LIBXML_VERSION >= 21200
+void XSLTProcessor::ParseErrorFunc(void* user_data, const xmlError* error) {
+#else
void XSLTProcessor::ParseErrorFunc(void* user_data, xmlError* error) {
+#endif
FrameConsole* console = static_cast<FrameConsole*>(user_data);
if (!console)
return;
--
2.34.1

View File

@ -0,0 +1,6 @@
0001-qtwebengine-enable-building-with-Python-3.12.patch
0002-fix-binary-tokenizer.patch
0003-qtwayland-downgrade-wl-seat-to-avoid-high-resolution.patch
0004-qtbase-fix-CMake-error.patch
0005-qtwebengine-add-missing-chromium-dependencies.patch
0006-qtwebengine-fix-libxml2-build-error.patch

View File

@ -45,6 +45,8 @@ Build-Depends: debhelper (>= 9),
libvdpau-dev,
libssl-dev,
libargon2-dev | libargon2-0-dev,
# TODO: remove libpipewire-0.2-dev once we stop supporting Ubuntu 20.04
libpipewire-0.3-dev | libpipewire-0.2-dev,
# other
nasm,
yasm,

View File

@ -99,10 +99,10 @@ if [ -f /etc/os-release ]; then
ENDTAG="ubuntu_20.04"
elif [ "${UBUNTU_CODENAME}" = "jammy" ] || [ "${ID}_${VERSION_ID}" = "ubuntu_22.04" ]; then
ENDTAG="ubuntu_22.04"
elif [ "${UBUNTU_CODENAME}" = "lunar" ] || [ "${ID}_${VERSION_ID}" = "ubuntu_23.04" ]; then
ENDTAG="ubuntu_23.04"
elif [ "${UBUNTU_CODENAME}" = "mantic" ] || [ "${ID}_${VERSION_ID}" = "ubuntu_23.10" ]; then
ENDTAG="ubuntu_23.10"
elif [ "${UBUNTU_CODENAME}" = "noble" ] || [ "${ID}_${VERSION_ID}" = "ubuntu_24.04" ]; then
ENDTAG="ubuntu_24.04"
elif [ "${ID}" = "debian" ] && \
[ "$(command -v lsb_release)" ] && \
[ "$(lsb_release -rs)" = "testing" ]; then

View File

@ -50,6 +50,7 @@ BuildRequires: libuuid-devel
BuildRequires: libva-devel
BuildRequires: libvdpau-devel
BuildRequires: pcre-devel
BuildRequires: pipewire-devel
BuildRequires: uuid-devel
BuildRequires: yaml-cpp-devel

View File

@ -16,6 +16,9 @@
%define computed_job_count_ %(echo $(( %available_memory / %memory_required_per_core / %max_parallel_builds )))
%define computed_job_count %max %computed_job_count_ 1
%define job_count %min %cpu_count %computed_job_count
# Exclude vendored Qt6 from dependency generator
%define __provides_exclude_from ^%{_libdir}/qt-jami/.*$
%define __requires_exclude ^libQt6.*$
Name: %{name}
Version: %{version}
@ -26,6 +29,7 @@ License: GPLv3+
Vendor: Savoir-faire Linux Inc.
URL: https://jami.net/
Source: jami-libqt-%{version}.tar.xz
Patch0: 0001-fix-gcc14.patch
%global gst 0.10
%if 0%{?fedora} || 0%{?rhel} > 7
@ -61,6 +65,7 @@ This package contains Qt libraries for Jami.
%prep
%setup -n qt-everywhere-src-%{version}
%patch0 -p1
%build
echo "Building Qt using %{job_count} parallel jobs"

View File

@ -2,6 +2,9 @@
%define version RELEASE_VERSION
%define release 0
# Exclude vendored Qt6 from dependency generator
%define __requires_exclude ^libQt6.*$
Name: %{name}
Version: %{version}
Release: %{release}%{?dist}

View File

@ -0,0 +1,26 @@
From 9721082687c9529fe6ae3c5304dcf079158e8a77 Mon Sep 17 00:00:00 2001
From: Sam James <sam@gentoo.org>
Date: Sun, 04 Jun 2023 04:15:16 +0100
Subject: [PATCH] heap: Add missing <algorithm> include for std::remove
GCC 14 changes some internal includes within libstdc++ so this transient
include gets lost. Include <algorithm> explicitly for std::remove.
Change-Id: Iab8a2c751a0f9c9dc6a770d6296ad6de724ef3bb
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4583222
Reviewed-by: Michael Lippautz <mlippautz@chromium.org>
Commit-Queue: Michael Lippautz <mlippautz@chromium.org>
Cr-Commit-Position: refs/heads/main@{#88037}
---
diff --git a/qtwebengine/src/3rdparty/chromium/v8/src/heap/cppgc/stats-collector.h b/qtwebengine/src/3rdparty/chromium/v8/src/heap/cppgc/stats-collector.h
index 2cf728489d..d8414ae3c6 100644
--- a/qtwebengine/src/3rdparty/chromium/v8/src/heap/cppgc/stats-collector.h
+++ b/qtwebengine/src/3rdparty/chromium/v8/src/heap/cppgc/stats-collector.h
@@ -8,6 +8,7 @@
#include <stddef.h>
#include <stdint.h>
+#include <algorithm>
#include <atomic>
#include <vector>

View File

@ -138,6 +138,10 @@ apps:
autostart: jami.desktop
common-id: net.jami.Jami
desktop: usr/share/applications/jami.desktop
environment:
PIPEWIRE_CONFIG_NAME: "$SNAP/usr/share/pipewire/pipewire.conf"
PIPEWIRE_MODULE_DIR: "$SNAP/usr/lib/$SNAPCRAFT_ARCH_TRIPLET/pipewire-0.3"
SPA_PLUGIN_DIR: "$SNAP/usr/lib/$SNAPCRAFT_ARCH_TRIPLET/spa-0.2"
slots:
- dbus-jami
- dbus-ring
@ -168,7 +172,7 @@ package-repositories:
components: [main]
suites: [jami]
key-id: A295D773307D25A33AE72F2F64CD5FA175348F84
url: https://dl.jami.net/nightly/ubuntu_20.04/
url: https://dl.jami.net/internal/ubuntu_20.04/
parts:
desktop-launch:
@ -255,6 +259,8 @@ parts:
sed -i -E 's|(tmpName) << (PACKAGE_NAME << "_shm_")|\1 << "snap.jami." << \2|' ./daemon/src/media/video/sinkclient.cpp
sed -i -E 's|^Icon=.*|Icon=${SNAP}/usr/share/icons/hicolor/scalable/apps/jami.svg|' extras/data/jami.desktop
override-build: |
$SNAPCRAFT_PART_BUILD/extras/packaging/gnu-linux/scripts/install-pipewire-from-source.sh
cd $SNAPCRAFT_PART_BUILD/daemon/contrib
mkdir -p native
cd native

View File

@ -44,6 +44,23 @@ QUILT_REFRESH_ARGS="-p 1"
if [ ! -f "${qt_deb_path}" ] || [ "${FORCE_REBUILD_QT}" = "true" ]; then
(
# HACK: For now on ubuntu 24.04 there is no python3.10 package
# So create a PyEnv environment to install the required packages
if cat /etc/os-release | grep -Eq "24.04"; then
apt-get install git gcc make python3-pip libssl-dev curl libreadline-dev -y
curl https://pyenv.run | bash
export PYENV_ROOT="$HOME/.pyenv"
[[ -d $PYENV_ROOT/bin ]] && export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"
pyenv install 3.10.0
pyenv local 3.10.0
python -m pip install html5lib
python -m pip install six
fi
flock 9 # block until the lock file is gone
test -f "${qt_deb_path}" && exit 0 # check again

View File

@ -35,6 +35,7 @@ rpmdev-setuptree
# Copy the source tarball.
cp --reflink=auto "/src/$RELEASE_TARBALL_FILENAME" /root/rpmbuild/SOURCES
cp patches/*.patch /root/rpmbuild/SOURCES/
QT_JAMI_PREFIX="/usr/lib64/qt-jami"
PATH="${QT_JAMI_PREFIX}/bin:${PATH}"
@ -43,7 +44,7 @@ PKG_CONFIG_PATH="${QT_JAMI_PREFIX}/lib/pkgconfig:${PKG_CONFIG_PATH}"
CMAKE_PREFIX_PATH="${QT_JAMI_PREFIX}/lib/cmake:${CMAKE_PREFIX_PATH}"
QT_MAJOR=6
QT_MINOR=6
QT_PATCH=1
QT_PATCH=3
QT_RELEASE_PATCH=0
QT_MAJOR_MINOR=${QT_MAJOR}.${QT_MINOR}
@ -52,7 +53,7 @@ QT_MAJOR_MINOR_PATCH=${QT_MAJOR}.${QT_MINOR}.${QT_PATCH}
QT_TARBALL_URL=https://download.qt.io/archive/qt/$QT_MAJOR_MINOR/\
$QT_MAJOR_MINOR_PATCH/single/qt-everywhere-src-$QT_MAJOR_MINOR_PATCH.tar.xz
QT_TARBALL_SHA256="dd3668f65645fe270bc615d748bd4dc048bd17b9dc297025106e6ecc419ab95d"
QT_TARBALL_SHA256="69d0348fef415da98aa890a34651e9cfb232f1bffcee289b7b4e21386bf36104"
QT_TARBALL_FILE_NAME=$(basename "$QT_TARBALL_URL")
CACHED_QT_TARBALL=$TARBALLS/$QT_TARBALL_FILE_NAME
@ -111,6 +112,8 @@ if [ ! -f "${RPM_PATH}" ]; then
cp /root/rpmbuild/RPMS/x86_64/jami-libqt-$QT_MAJOR_MINOR_PATCH-*.fc38.x86_64.rpm "${RPM_PATH}"
elif [[ "${DISTRIBUTION}" == "fedora_39" ]]; then
cp /root/rpmbuild/RPMS/x86_64/jami-libqt-$QT_MAJOR_MINOR_PATCH-*.fc39.x86_64.rpm "${RPM_PATH}"
elif [[ "${DISTRIBUTION}" == "fedora_40" ]]; then
cp /root/rpmbuild/RPMS/x86_64/jami-libqt-$QT_MAJOR_MINOR_PATCH-*.fc40.x86_64.rpm "${RPM_PATH}"
elif [[ "${DISTRIBUTION}" == "alma_9" ]]; then
cp /root/rpmbuild/RPMS/x86_64/jami-libqt-$QT_MAJOR_MINOR_PATCH-*.el9.x86_64.rpm "${RPM_PATH}"
else

View File

@ -0,0 +1,38 @@
#!/usr/bin/env bash
# The purpose of this script is to build PipeWire from source in a snap based on core20 / Ubuntu 20.04
# It must be called in the "override-build" section of the relevant part in snapcraft.yaml
set -e
OLD_WD=$(pwd)
cd /tmp
# Install PipeWire's build dependencies
apt-get install --yes gcc git libasound2-dev libdbus-1-dev libglib2.0-dev ninja-build pkg-config
# Get a version of Meson that's recent enough to build PipeWire 1.0.5 (the one available via apt is too old)
wget -q https://github.com/mesonbuild/meson/releases/download/0.61.1/meson-0.61.1.tar.gz
echo "feb2cefb325b437dbf36146df7c6b87688ddff0b0205caa31dc64055c6da410c meson-0.61.1.tar.gz" | sha256sum --check
tar xzf meson-0.61.1.tar.gz
# Build PipeWire 1.0.5 and install it in the /usr directory of the build environment
wget -q https://gitlab.freedesktop.org/pipewire/pipewire/-/archive/1.0.5/pipewire-1.0.5.tar.gz
echo "c5a5de26d684a1a84060ad7b6131654fb2835e03fccad85059be92f8e3ffe993 pipewire-1.0.5.tar.gz" | sha256sum --check
tar xzf pipewire-1.0.5.tar.gz
cd pipewire-1.0.5
../meson-0.61.1/meson.py setup builddir -Dsession-managers=media-session -Dalsa=disabled -Dprefix=/usr
../meson-0.61.1/meson.py compile -C builddir
../meson-0.61.1/meson.py install -C builddir
# The files installed by the previous command are only for the "Build" step of the snap
# creation process (https://snapcraft.io/docs/how-snapcraft-builds). In order to ensure
# that PipeWire is installed in the final snap archive, we also need to copy all the
# required files under the $SNAPCRAFT_PART_INSTALL directory.
../meson-0.61.1/meson.py configure builddir -Dprefix=$SNAPCRAFT_PART_INSTALL/usr/
../meson-0.61.1/meson.py install -C builddir
# Cleanup
cd /tmp
rm -rf meson-0.61.1 meson-0.61.1.tar.gz pipewire-1.0.5 pipewire-1.0.5.tar.gz
cd $OLD_WD

View File

@ -34,7 +34,7 @@ cat << EOFILE > ${REPO_FOLDER}/${SPARKLE_FILE}
<pubDate>$DATE_RFC2822</pubDate>
<sparkle:version>${BUILD}</sparkle:version>
<sparkle:shortVersionString>${VERSION}</sparkle:shortVersionString>
<sparkle:minimumSystemVersion>10.15.0</sparkle:minimumSystemVersion>
<sparkle:minimumSystemVersion>11.0</sparkle:minimumSystemVersion>
<enclosure url="${REPO_URL}/$(basename ${PACKAGE})" type="application/octet-stream" $(./sign_update ${PACKAGE}) />
</item>
$(echo -e "${ITEMS}")

View File

@ -17,7 +17,7 @@
<key>CFBundleShortVersionString</key>
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
<key>LSMinimumSystemVersion</key>
<string>10.15</string>
<string>11.0</string>
<key>CFBundleVersion</key>
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
<key>LSApplicationCategoryType</key>

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48"><path d="M42 13.85V39q0 1.2-.9 2.1-.9.9-2.1.9H9q-1.2 0-2.1-.9Q6 40.2 6 39V9q0-1.2.9-2.1Q7.8 6 9 6h25.15Zm-3 1.35L32.8 9H9v30h30ZM24 35.75q2.15 0 3.675-1.525T29.2 30.55q0-2.15-1.525-3.675T24 25.35q-2.15 0-3.675 1.525T18.8 30.55q0 2.15 1.525 3.675T24 35.75ZM11.65 18.8h17.9v-7.15h-17.9ZM9 15.2V39 9Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 -960 960 960" width="48px" fill="#e8eaed"><path d="M480-313 287-506l43-43 120 120v-371h60v371l120-120 43 43-193 193ZM220-160q-24 0-42-18t-18-42v-143h60v143h520v-143h60v143q0 24-18 42t-42 18H220Z"/></svg>

Before

Width:  |  Height:  |  Size: 369 B

After

Width:  |  Height:  |  Size: 268 B

View File

@ -1,5 +1,6 @@
<h4 align="left"><span style="font-weight:600"> Created by</span></h4>
<p>Adrien Béraud<br>
<p>Abhishek Ojha<br>
Adrien Béraud<br>
Albert Babí<br>
Alexandre Lision<br>
Alexandr Sergheev<br>
@ -25,6 +26,7 @@ Emma Falkiewitz<br>
Emmanuel Lepage-Vallée<br>
Fadi Shehadeh<br>
Franck Laurent<br>
François-Simon Fauteux-Chapleau<br>
Frédéric Guimont<br>
Guillaume Heller<br>
Guillaume Roguez<br>
@ -68,3 +70,4 @@ Yang Wang<br></p>
<h4 align="left"><span style="font-weight:600"> Artwork by</span></h4>
<p>Charlotte Hoffmann<br>
Marianne Forget<br></p>
<h4 align="left"><span style="font-weight:600"> And the volunteers who contribute to this project! </span></h4>

View File

@ -145,6 +145,16 @@ ApplicationWindow {
LRCInstance.selectConversation(convUid);
}
}
ListElement {
label: "Account ID"
type: "combobox"
getDataModel: () => AccountListModel
displayRole: AccountList.Username
onIndexChanged: function(model, index) {
const accountId = JamiQmlUtils.getModelData(model, index, AccountList.ID);
LRCInstance.currentAccountId = accountId;
}
}
ListElement {
label: "Force local preview"
type: "checkbox"

View File

@ -116,7 +116,8 @@ ApplicationWindow {
function close(force = false) {
// If we're in the onboarding wizard or 'MinimizeOnClose'
// is set, then we can quit
if (force || !UtilsAdapter.getAppValue(Settings.MinimizeOnClose) || !UtilsAdapter.getAccountListSize()) {
var minimizeToTray = UtilsAdapter.getAppValue(Settings.MinimizeOnClose) && UtilsAdapter.isSystemTrayIconVisible();
if (force || !minimizeToTray || !UtilsAdapter.getAccountListSize()) {
Qt.quit();
} else {
layoutManager.closeToTray();

View File

@ -29,6 +29,35 @@ AccountListModel::AccountListModel(LRCInstance* instance, QObject* parent)
: AbstractListModelBase(parent)
{
lrcInstance_ = instance;
// Avoid resetting/redrawing the model when the account status changes.
QObject::connect(&lrcInstance_->accountModel(),
&AccountModel::accountStatusChanged,
this,
[&](const QString& accountId) {
auto accountList = lrcInstance_->accountModel().getAccountList();
auto index = accountList.indexOf(accountId);
if (index != -1) {
QModelIndex modelIndex = QAbstractListModel::index(index, 0);
Q_EMIT dataChanged(modelIndex, modelIndex /*, ALL ROLES */);
}
});
// If there's a reorder, it's reasonable to reset the model for simplicity, instead
// of computing the difference. The same goes for accounts being added and removed.
// These operations will only occur when the list is hidden, unless dbus is used while
// the list is visible.
QObject::connect(&lrcInstance_->accountModel(),
&AccountModel::accountsReordered,
this,
&AccountListModel::reset);
QObject::connect(&lrcInstance_->accountModel(),
&AccountModel::accountAdded,
this,
&AccountListModel::reset);
QObject::connect(&lrcInstance_->accountModel(),
&AccountModel::accountRemoved,
this,
&AccountListModel::reset);
}
int
@ -91,6 +120,7 @@ AccountListModel::roleNames() const
void
AccountListModel::reset()
{
// Used to invalidate proxy models.
beginResetModel();
endResetModel();
}

View File

@ -20,6 +20,8 @@
#include "appsettingsmanager.h"
#include "global.h"
#include <QCoreApplication>
#include <QLibraryInfo>
@ -101,7 +103,7 @@ AppSettingsManager::loadTranslations()
installedTr_.clear();
QString locale_name = getLanguage();
qDebug() << QString("Using locale: %1").arg(locale_name);
C_INFO << QString("Using locale: %1").arg(locale_name);
QString locale_lang = locale_name.split('_')[0];
QTranslator* qtTranslator_lang = new QTranslator(qApp);

View File

@ -43,7 +43,7 @@ extern const QString defaultDownloadPath;
// Common key-value pairs for both APPSTORE and non-APPSTORE builds
#define COMMON_KEYS \
X(MinimizeOnClose, false) \
X(MinimizeOnClose, true) \
X(DownloadPath, defaultDownloadPath) \
X(ScreenshotPath, {}) \
X(EnableNotifications, true) \

View File

@ -25,7 +25,11 @@
#include "api/devicemodel.h"
#ifdef Q_OS_LINUX
#include "screencastportal.h"
#include "xrectsel.h"
#ifndef ENABLE_LIBWRAP
#include <sys/prctl.h>
#endif
#endif
#include <QtConcurrent/QtConcurrent>
@ -58,6 +62,12 @@ AvAdapter::AvAdapter(LRCInstance* instance, QObject* parent)
&lrc::api::AVModel::onRendererFpsChange,
this,
&AvAdapter::updateRenderersFPSInfo);
#ifdef Q_OS_LINUX
connect(&lrcInstance_->behaviorController(),
&BehaviorController::callStatusChanged,
this,
&AvAdapter::onCallStatusChanged);
#endif
}
// The top left corner of primary screen is (0, 0).
@ -119,6 +129,93 @@ AvAdapter::shareEntireScreen(int screenNumber)
->addMedia(callId, resource, lrc::api::CallModel::MediaRequestType::SCREENSHARING);
}
#ifdef Q_OS_LINUX
static std::map<QString, std::unique_ptr<ScreenCastPortal>> callPortal;
void
AvAdapter::onCallStatusChanged(const QString& accountId, const QString& callId)
{
auto& accInfo = lrcInstance_->accountModel().getAccountInfo(accountId);
auto& callModel = accInfo.callModel;
const auto call = callModel->getCall(callId);
if (call.status == lrc::api::call::Status::ENDED) {
closePortal(callId);
}
}
void
AvAdapter::closePortal(const QString& callId)
{
if (callPortal.count(callId)) {
lrcInstance_->avModel().stopPreview(callPortal[callId]->videoInputId);
callPortal.erase(callId);
}
}
void
AvAdapter::shareWayland(bool entireScreen)
{
QString callId = lrcInstance_->getCurrentCallId();
closePortal(callId);
PortalCaptureType captureType = entireScreen ? PortalCaptureType::SCREEN
: PortalCaptureType::WINDOW;
auto portal = std::make_unique<ScreenCastPortal>(captureType);
int err = portal->getPipewireFd();
if (err == EACCES) {
qInfo() << "Can't share screen: permission denied";
return;
} else if (err != 0) {
qWarning() << "Failed to get PipeWire fd. Error code:" << err;
return;
}
QString resource = QString("%1%2pipewire pid:%3 fd:%4 node:%5")
.arg(libjami::Media::VideoProtocolPrefix::DISPLAY)
.arg(libjami::Media::VideoProtocolPrefix::SEPARATOR)
.arg(getpid())
.arg(portal->pipewireFd)
.arg(portal->pipewireNode);
#ifndef ENABLE_LIBWRAP
// If the daemon is running as a separate process, then it can't directly use the
// PipeWire file descriptor opened by the client, so it will attempt to duplicate
// it using the pidfd_getfd system call. This requires the daemon process to have
// ptrace permission on the client process. On some systems, this will be true by
// default (as long as the client and daemon processes have the same uid), but it
// may not be if the Yama Linux Security Module is used. The call to prctl below
// will grant permission if the Yama LSM is enabled and set to mode 1.
//
// References:
// https://man7.org/linux/man-pages/man2/pidfd_getfd.2.html
// https://man7.org/linux/man-pages/man2/prctl.2.html
// https://github.com/torvalds/linux/blob/master/Documentation/admin-guide/LSM/Yama.rst
prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY);
#endif
// We open the video input here (instead of letting the daemon do it) to ensure
// that the daemon doesn't try to restart it while we still need it, since this
// would require getting a new file descriptor for PipeWire.
portal->videoInputId = lrcInstance_->avModel().startPreview(resource);
callPortal[callId] = std::move(portal);
muteCamera_ = !isCapturing();
lrcInstance_->getCurrentCallModel()
->addMedia(callId, resource, lrc::api::CallModel::MediaRequestType::SCREENSHARING);
}
void
AvAdapter::shareEntireScreenWayland()
{
shareWayland(true);
}
void
AvAdapter::shareWindowWayland()
{
shareWayland(false);
}
#endif // Q_OS_LINUX
void
AvAdapter::shareAllScreens()
{
@ -204,10 +301,14 @@ AvAdapter::shareFile(const QString& filePath)
&lrc::api::AVModel::fileOpened,
this,
[this, callId, filePath, resource](bool hasAudio, bool hasVideo) {
lrcInstance_->avModel().setAutoRestart(resource, true);
lrcInstance_->getCurrentCallModel()
->addMedia(callId, filePath, lrc::api::CallModel::MediaRequestType::FILESHARING, false, hasAudio);
lrcInstance_->avModel().pausePlayer(resource, false);
lrcInstance_->avModel().setAutoRestart(resource, true);
lrcInstance_->getCurrentCallModel()
->addMedia(callId,
filePath,
lrc::api::CallModel::MediaRequestType::FILESHARING,
false,
hasAudio);
lrcInstance_->avModel().pausePlayer(resource, false);
});
lrcInstance_->avModel().createMediaPlayer(resource);
@ -307,6 +408,9 @@ void
AvAdapter::stopSharing(const QString& source)
{
auto callId = lrcInstance_->getCurrentCallId();
#ifdef Q_OS_LINUX
closePortal(callId);
#endif
if (!source.isEmpty() && !callId.isEmpty()) {
if (source.startsWith(libjami::Media::VideoProtocolPrefix::DISPLAY)) {
qDebug() << "Stopping display: " << source;

View File

@ -69,9 +69,18 @@ protected:
*/
Q_INVOKABLE bool hasCamera() const;
// Share the screen specificed by screen number.
// Share the screen specificed by screen number (all platforms except Wayland).
Q_INVOKABLE void shareEntireScreen(int screenNumber);
#ifdef Q_OS_LINUX
// Share a screen on Wayland.
// Sharing a screen on Wayland requires getting permission from the user. The logic for
// this is handled by the ScreenCastPortal class using xdg-desktop-portal.
// The choice of screen is also handled by xdg-desktop-portal, which is why we don't need
// an argument for it (whereas we do on other platforms, cf. shareEntireScreen above).
Q_INVOKABLE void shareEntireScreenWayland();
#endif
// Share the all screens connected.
Q_INVOKABLE void shareAllScreens();
@ -87,9 +96,18 @@ protected:
// Select screen area to display (from all screens).
Q_INVOKABLE void shareScreenArea(unsigned x, unsigned y, unsigned width, unsigned height);
// Select window to display.
// Select window to display (all platforms except Wayland).
Q_INVOKABLE void shareWindow(const QString& windowProcessId, const QString& windowId);
#ifdef Q_OS_LINUX
// Share a window on Wayland.
// Sharing a window on Wayland requires getting permission from the user. The logic for
// this is handled by the ScreenCastPortal class using xdg-desktop-portal.
// The choice of window is also handled by xdg-desktop-portal, which is why we don't need
// arguments for it (whereas we do on other platforms, cf. shareWindow above).
Q_INVOKABLE void shareWindowWayland();
#endif
// Returns the screensharing resource
Q_INVOKABLE QString getSharingResource(int screenId = -2,
const QString& windowProcessId = "",
@ -121,11 +139,25 @@ private Q_SLOTS:
void onAudioDeviceEvent();
void onRendererStarted(const QString& id, const QSize& size);
void onRendererStopped(const QString& id);
#ifdef Q_OS_LINUX
// This function needs to be called whenever a screen/window share stops on Wayland.
// Failure to do so can cause subsequent sharing attempts to fail.
void closePortal(const QString& callId);
// On Wayland, we need to be informed of call status changes so that we can call
// closePortal if a call ends while a screen/window share was in progress.
void onCallStatusChanged(const QString& accountId, const QString& callId);
#endif
private:
// Get screens arrangement rect relative to primary screen.
const QRect getAllScreensBoundingRect();
#ifdef Q_OS_LINUX
// Used internally by shareEntireScreenWayland and shareWindowWayland
void shareWayland(bool entireScreen);
#endif
// Get the screen number
int getScreenNumber(int screenId = 0) const;

View File

@ -354,10 +354,11 @@ CallAdapter::onCallInfosChanged(const QString& accountId, const QString& callId)
}
void
CallAdapter::onCallAddedToConference(const QString& callId, const QString& confId)
CallAdapter::onCallAddedToConference(const QString& callId, const QString& conversationId, const QString& confId)
{
Q_UNUSED(callId)
Q_UNUSED(confId)
Q_UNUSED(conversationId)
saveConferenceSubcalls();
}
@ -389,16 +390,13 @@ CallAdapter::hangUpACall(const QString& accountId, const QString& convUid)
}
void
CallAdapter::setCallMedia(const QString& accountId, const QString& convUid, bool video)
CallAdapter::setCallMedia(const QString& accountId, const QString& convUid, bool videoMuted)
{
const auto& convInfo = lrcInstance_->getConversationFromConvUid(convUid, accountId);
if (convInfo.uid.isEmpty())
return;
try {
lrcInstance_->getAccountInfo(accountId).callModel->updateCallMediaList(convInfo.callId,
video);
} catch (...) {
}
lrcInstance_->getAccountInfo(accountId).callModel->setVideoMuted(convInfo.callId, videoMuted);
}
void

View File

@ -67,7 +67,7 @@ public:
Q_INVOKABLE void placeAudioOnlyCall();
Q_INVOKABLE void placeCall();
Q_INVOKABLE void hangUpACall(const QString& accountId, const QString& convUid);
Q_INVOKABLE void setCallMedia(const QString& accountId, const QString& convUid, bool video);
Q_INVOKABLE void setCallMedia(const QString& accountId, const QString& convUid, bool videoMuted);
Q_INVOKABLE void acceptACall(const QString& accountId, const QString& convUid);
Q_INVOKABLE void connectCallModel(const QString& accountId);
@ -122,7 +122,7 @@ public Q_SLOTS:
void onAccountChanged();
void onCallStatusChanged(const QString& accountId, const QString& callId);
void onCallStatusChanged(const QString& callId, int code);
void onCallAddedToConference(const QString& callId, const QString& confId);
void onCallAddedToConference(const QString& callId, const QString& conversationId, const QString& confId);
void onCallStarted(const QString& callId);
void onCallEnded(const QString& callId);
void onCallInfosChanged(const QString& accountId, const QString& callId);

View File

@ -362,36 +362,32 @@ CallOverlayModel::clearControls()
}
void
CallOverlayModel::registerFilter(QObject* object, QQuickItem* item)
CallOverlayModel::setEventFilterActive(QObject* object, QQuickItem* item, bool isActive)
{
QQuickWindow* window = qobject_cast<QQuickWindow*>(object);
if (!window || !item) {
C_WARN << "Attempting to register an invalid object or item" << object << item;
C_WARN << "Attempting to" << (isActive ? "register" : "unregister")
<< "an invalid object or item" << window << item;
return;
}
if (watchedItems_.contains(item)) {
C_DBG << "Item already registered" << item;
}
watchedItems_.push_back(item);
if (watchedItems_.size() == 1) {
window->installEventFilter(this);
}
}
void
CallOverlayModel::unregisterFilter(QObject* object, QQuickItem* item)
{
QQuickWindow* window = qobject_cast<QQuickWindow*>(object);
if (!window || !item) {
C_WARN << "Attempting to unregister an invalid object or item" << object << item;
return;
}
if (!watchedItems_.contains(item)) {
C_DBG << "Item not registered" << item;
}
watchedItems_.removeOne(item);
if (watchedItems_.size() == 0) {
window->removeEventFilter(this);
if (isActive) {
if (watchedItems_.contains(item)) {
C_DBG << "Item already registered" << item;
} else {
watchedItems_.push_back(item);
if (watchedItems_.size() == 1) {
window->installEventFilter(this);
}
}
} else {
if (!watchedItems_.contains(item)) {
C_DBG << "Item not registered" << item;
} else {
watchedItems_.removeOne(item);
if (watchedItems_.size() == 0) {
window->removeEventFilter(this);
}
}
}
}

View File

@ -137,8 +137,7 @@ public:
Q_INVOKABLE QVariant overflowHiddenModel();
Q_INVOKABLE QVariant pendingConferenceesModel();
Q_INVOKABLE void registerFilter(QObject* object, QQuickItem* item);
Q_INVOKABLE void unregisterFilter(QObject* object, QQuickItem* item);
Q_INVOKABLE void setEventFilterActive(QObject* object, QQuickItem* item, bool isActive);
bool eventFilter(QObject* object, QEvent* event) override;
Q_SIGNALS:

View File

@ -40,9 +40,18 @@ Loader {
property int seq: MsgSeq.single
property string author: Author
property string body: Body
property int transferStatus: Status
property var tid: TID
property int transferStatus: TransferStatus
onTidChanged: {
if (tid === "") {
sourceComponent = deletedMsgComp
}
}
onTransferStatusChanged: {
if (transferStatus === Interaction.Status.TRANSFER_FINISHED) {
if (tid === "") {
sourceComponent = deletedMsgComp
return;
} else if (transferStatus === Interaction.TransferStatus.TRANSFER_FINISHED) {
mediaInfo = MessagesAdapter.getMediaInfo(root.body);
if (Object.keys(mediaInfo).length !== 0 && WITH_WEBENGINE) {
sourceComponent = localMediaMsgComp;
@ -58,6 +67,54 @@ Loader {
Behavior on opacity { NumberAnimation { duration: 100 } }
onLoaded: opacity = 1
Component {
id: deletedMsgComp
SBSMessageBase {
id: deletedItem
isOutgoing: Author === CurrentAccount.uri
showTime: root.showTime
seq: root.seq
author: Author
readers: Readers
timestamp: root.timestamp
formattedTime: root.formattedTime
formattedDay: root.formattedTime
extraHeight: 0
textContentWidth: textEditId.width
textContentHeight: textEditId.height
innerContent.children: [
TextEdit {
id: textEditId
anchors.right: isOutgoing ? parent.right : undefined
anchors.rightMargin: isOutgoing ? timeWidth : 0
bottomPadding: 6
topPadding: 6
leftPadding: 10
text: UtilsAdapter.getBestNameForUri(CurrentAccount.id, Author) + " " + JamiStrings.deletedMedia ;
horizontalAlignment: Text.AlignLeft
width: Math.min((2 / 3) * parent.width, implicitWidth + 18, innerContent.width - senderMargin + 18)
font.pointSize: JamiTheme.smallFontSize
font.hintingPreference: Font.PreferNoHinting
renderType: Text.NativeRendering
textFormat: Text.RichText
clip: true
readOnly: true
color: getBaseColor()
opacity: 0.5
function getBaseColor() {
bubble.isDeleted = true
return UtilsAdapter.luma(bubble.color) ? "white" : "dark"
}
}
]
}
}
Component {
id: dataTransferMsgComp
@ -66,7 +123,7 @@ Loader {
transferId: Id
property var transferStats: MessagesAdapter.getTransferStats(transferId, root.transferStatus)
property bool canOpen: root.transferStatus === Interaction.Status.TRANSFER_FINISHED || isOutgoing
property bool canOpen: root.transferStatus === Interaction.TransferStatus.TRANSFER_FINISHED || isOutgoing
property real maxMsgWidth: root.width - senderMargin -
2 * hPadding - avatarBlockWidth
- buttonsLoader.width - 24 - 6 - 24
@ -112,18 +169,18 @@ Loader {
sourceComponent: {
switch (root.transferStatus) {
case Interaction.Status.TRANSFER_CREATED:
case Interaction.Status.TRANSFER_FINISHED:
case Interaction.TransferStatus.TRANSFER_CREATED:
case Interaction.TransferStatus.TRANSFER_FINISHED:
iconSource = JamiResources.link_black_24dp_svg
return terminatedComp
case Interaction.Status.TRANSFER_CANCELED:
case Interaction.Status.TRANSFER_ERROR:
case Interaction.Status.TRANSFER_UNJOINABLE_PEER:
case Interaction.Status.TRANSFER_TIMEOUT_EXPIRED:
case Interaction.Status.TRANSFER_AWAITING_HOST:
case Interaction.TransferStatus.TRANSFER_CANCELED:
case Interaction.TransferStatus.TRANSFER_ERROR:
case Interaction.TransferStatus.TRANSFER_UNJOINABLE_PEER:
case Interaction.TransferStatus.TRANSFER_TIMEOUT_EXPIRED:
case Interaction.TransferStatus.TRANSFER_AWAITING_HOST:
iconSource = JamiResources.download_black_24dp_svg
return optionsComp
case Interaction.Status.TRANSFER_ONGOING:
case Interaction.TransferStatus.TRANSFER_ONGOING:
iconSource = JamiResources.close_black_24dp_svg
return optionsComp
default:
@ -158,7 +215,7 @@ Loader {
normalColor: JamiTheme.chatviewBgColor
imageColor: JamiTheme.chatviewButtonColor
onClicked: {
if (root.transferStatus === Interaction.Status.TRANSFER_ONGOING) {
if (root.transferStatus === Interaction.TransferStatus.TRANSFER_ONGOING) {
return MessagesAdapter.cancelFile(transferId)
} else {
return MessagesAdapter.acceptFile(transferId)
@ -191,7 +248,7 @@ Loader {
onClicked: function (mouse) {
if (canOpen) {
dataTransferItem.hoveredLink = UtilsAdapter.urlFromLocalPath(location)
Qt.openUrlExternally(new Url(dataTransferItem.hoveredLink))
Qt.openUrlExternally(new URL(dataTransferItem.hoveredLink))
} else {
dataTransferItem.hoveredLink = ""
}
@ -223,11 +280,11 @@ Loader {
: JamiTheme.chatviewTextColorDark
}
}
}
,ProgressBar {
},
ProgressBar {
id: progressBar
visible: root.transferStatus === Interaction.Status.TRANSFER_ONGOING
visible: root.transferStatus === Interaction.TransferStatus.TRANSFER_ONGOING
height: visible * implicitHeight
value: transferStats.progress / transferStats.totalSize
width: transferItem.width

View File

@ -121,10 +121,7 @@ Item {
font.pixelSize : text.length > 16 ? JamiTheme.jamiIdSmallFontSize : JamiTheme.bigFontSize
property string registeredName: CurrentAccount.registeredName
property string infohash: CurrentAccount.uri
text: registeredName ? registeredName : infohash
onRegisteredNameChanged: {
text = registeredName ? registeredName : infohash
}
text: (btnId.clicked && registeredName) ? registeredName : infohash
}
}
}
@ -231,11 +228,9 @@ Item {
toolTipText: JamiStrings.identifierURI
onClicked: {
if (clicked) {
usernameLabel.text = Qt.binding(function() {return CurrentAccount.uri} );
usernameTextEdit.staticText = Qt.binding(function() {return CurrentAccount.uri} );
btnId.toolTipText = JamiStrings.identifierRegisterName;
} else {
usernameLabel.text = Qt.binding(function() {return CurrentAccount.registeredName} );
usernameTextEdit.staticText = Qt.binding(function() {return CurrentAccount.registeredName} );
btnId.toolTipText = JamiStrings.identifierURI;
}

View File

@ -1,83 +1,97 @@
/*
* Copyright (C) 2024 Savoir-faire Linux Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
import QtQuick
import QtQuick.Controls
import net.jami.Adapters 1.1
import net.jami.Constants 1.1
// A SplitView that supports dynamic RTL and splitView state saving.
SplitView {
id: root
property bool isRTL: UtilsAdapter.isRTL
property bool isSinglePane: false
property bool isSwapped: false
onIsRTLChanged: {
if (isRTL && isSinglePane && !isSwapped)
return
if ((isRTL && !isSwapped) || (!isRTL && isSwapped))
swapItems()
}
onIsSinglePaneChanged: {
if (isSwapped || isRTL)
swapItems()
}
property string splitViewStateKey: objectName
property bool autoManageState: !(parent instanceof BaseView)
function saveSplitViewState() {
UtilsAdapter.setAppValue("sv_" + splitViewStateKey, root.saveState());
}
function restoreSplitViewState() {
root.restoreState(UtilsAdapter.getAppValue("sv_" + splitViewStateKey));
}
onResizingChanged: if (!resizing)
saveSplitViewState()
onVisibleChanged: {
if (!autoManageState)
return;
visible ? restoreSplitViewState() : saveSplitViewState();
}
function swapItems() {
isSwapped = !isSwapped
var qqci = children[0];
if (qqci.children.length > 1) {
// swap the children
var tempPane = qqci.children[0];
qqci.children[0] = qqci.children[1];
qqci.children.push(tempPane);
}
}
handle: Rectangle {
visible: !isSinglePane
implicitWidth: JamiTheme.splitViewHandlePreferredWidth
implicitHeight: root.height
color: JamiTheme.primaryBackgroundColor
Rectangle {
anchors.left: parent.left
implicitWidth: 1
implicitHeight: root.height
color: JamiTheme.tabbarBorderColor
}
}
}
/*
* Copyright (C) 2024 Savoir-faire Linux Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
import QtQuick
import QtQuick.Controls
import net.jami.Adapters 1.1
import net.jami.Constants 1.1
// A SplitView that supports dynamic RTL and splitView state saving.
SplitView {
id: control
property bool isRTL: UtilsAdapter.isRTL
property bool isSinglePane: false
property bool isSwapped: false
property real handleSize: 1
onIsRTLChanged: {
if (isRTL && isSinglePane && !isSwapped)
return
if ((isRTL && !isSwapped) || (!isRTL && isSwapped))
swapItems()
}
onIsSinglePaneChanged: {
if (isSwapped || isRTL)
swapItems()
}
property string splitViewStateKey: objectName
property bool autoManageState: !(parent instanceof BaseView)
function saveSplitViewState() {
UtilsAdapter.setAppValue("sv_" + splitViewStateKey, control.saveState());
}
function restoreSplitViewState() {
control.restoreState(UtilsAdapter.getAppValue("sv_" + splitViewStateKey));
}
onResizingChanged: if (!resizing)
saveSplitViewState()
onVisibleChanged: {
if (!autoManageState)
return;
visible ? restoreSplitViewState() : saveSplitViewState();
}
function swapItems() {
isSwapped = !isSwapped
var qqci = children[0];
if (qqci.children.length > 1) {
// swap the children
var tempPane = qqci.children[0];
qqci.children[0] = qqci.children[1];
qqci.children.push(tempPane);
}
}
handle: Rectangle {
id: handleRoot
readonly property int defaultSize: control.handleSize
implicitWidth: control.orientation === Qt.Horizontal ? handleRoot.defaultSize : control.width
implicitHeight: control.orientation === Qt.Horizontal ? control.height : handleRoot.defaultSize
color: JamiTheme.tabbarBorderColor
containmentMask: Item {
// In the default configuration, the total handle size is the sum of the default size of the
// handle and the extra handle size (4). If the layout is not right-to-left (RTL), the handle
// is positioned at 0 on the X-axis, otherwise it's positioned to the left by the extra handle
// size (4 pixels). This is done to make it easier to grab small scroll-view handles that are
// adjacent to the SplitView handle. Note: vertically oriented handles are not offset.
readonly property real extraHandleSize: 4
readonly property real handleXPosition: !isRTL ? 0 : -extraHandleSize
readonly property real handleSize: handleRoot.defaultSize + extraHandleSize
x: control.orientation === Qt.Horizontal ? handleXPosition : 0
width: control.orientation === Qt.Horizontal ? handleSize : handleRoot.width
height: control.orientation === Qt.Horizontal ? handleRoot.height : handleSize
}
}
}

View File

@ -28,12 +28,14 @@ VideoView {
crop: true
visible: isRendering && visibilityCondition
Component.onDestruction: VideoDevices.stopDevice(rendererId);
function startWithId(id, force = false) {
if (id !== undefined && id.length === 0) {
stop();
return;
}
const forceRestart = rendererId === id;
const forceRestart = rendererId === id || force;
if (!forceRestart) {
// Stop previous device
VideoDevices.stopDevice(rendererId);

View File

@ -382,7 +382,11 @@ Control {
property bool bubbleHovered
property string imgSource
width: (root.type === Interaction.Type.TEXT ? root.textContentWidth + (IsEmojiOnly || root.bigMsg ? 0 : root.timeWidth + root.editedWidth) : innerContent.childrenRect.width)
width: (root.type === Interaction.Type.TEXT || isDeleted ?
root.textContentWidth + (IsEmojiOnly || root.bigMsg ?
0
: root.timeWidth + root.editedWidth)
: innerContent.childrenRect.width)
height: innerContent.childrenRect.height + (visible ? root.extraHeight : 0) + (root.bigMsg ? 15 : 0)
HoverHandler {
@ -424,9 +428,9 @@ Control {
id: editedRow
anchors.left: root.bigMsg ? bubble.left : timestampItem.left
anchors.bottom: parent.bottom
anchors.bottomMargin: root.bigMsg || bubble.isDeleted ? 6 : 10
anchors.bottomMargin: root.bigMsg ? 6 : 10
anchors.leftMargin: root.bigMsg ? 10 : -timestampItem.width - 16
visible: bubble.isEdited
visible: bubble.isEdited && !bubble.isDeleted
z: 1
ResponsiveImage {
id: editedImage

View File

@ -153,7 +153,7 @@ BaseContextMenu {
GeneralMenuItem {
id: removeLocally
canTrigger: type === Interaction.Type.DATA_TRANSFER && Status === Interaction.Status.TRANSFER_FINISHED
canTrigger: type === Interaction.Type.DATA_TRANSFER && TransferStatus === Interaction.TransferStatus.TRANSFER_FINISHED
iconSource: JamiResources.trash_black_24dp_svg
itemName: JamiStrings.removeLocally
onClicked: {
@ -175,7 +175,7 @@ BaseContextMenu {
GeneralMenuItem {
id: deleteMessage
canTrigger: root.isOutgoing && type === Interaction.Type.TEXT
canTrigger: root.isOutgoing && (type === Interaction.Type.TEXT || type === Interaction.Type.DATA_TRANSFER)
iconSource: JamiResources.delete_svg
itemName: JamiStrings.deleteMessage
onClicked: {

View File

@ -16,6 +16,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "global.h"
#ifndef _WIN32
#include <glib.h>
#include <gio/gio.h>
@ -171,10 +173,10 @@ static void
logConnectionInfo(NMActiveConnection* connection)
{
if (connection) {
qDebug() << "primary network connection:" << nm_active_connection_get_uuid(connection)
<< "default: " << (nm_active_connection_get_default(connection) ? "yes" : "no");
C_INFO << "primary network connection:" << nm_active_connection_get_uuid(connection)
<< "default: " << (nm_active_connection_get_default(connection) ? "yes" : "no");
} else {
qWarning() << "no primary network connection detected, check network settings";
C_WARN << "no primary network connection detected, check network settings";
}
}
@ -191,11 +193,10 @@ nmClientCallback(G_GNUC_UNUSED GObject* source_object, GAsyncResult* result, Con
{
GError* error = nullptr;
if (auto nm_client = nm_client_new_finish(result, &error)) {
qDebug() << "NetworkManager client initialized, version: "
<< nm_client_get_version(nm_client)
<< ", daemon running:" << (nm_client_get_nm_running(nm_client) ? "yes" : "no")
<< ", networking enabled:"
<< (nm_client_networking_get_enabled(nm_client) ? "yes" : "no");
C_INFO << "NetworkManager client initialized, version: " << nm_client_get_version(nm_client)
<< ", daemon running:" << (nm_client_get_nm_running(nm_client) ? "yes" : "no")
<< ", networking enabled:"
<< (nm_client_networking_get_enabled(nm_client) ? "yes" : "no");
auto connection = nm_client_get_primary_connection(nm_client);
logConnectionInfo(connection);
@ -205,7 +206,7 @@ nmClientCallback(G_GNUC_UNUSED GObject* source_object, GAsyncResult* result, Con
cm);
} else {
qWarning() << "error initializing NetworkManager client: " << error->message;
C_WARN << "error initializing NetworkManager client: " << error->message;
g_clear_error(&error);
}
}
@ -222,7 +223,7 @@ ConnectivityMonitor::ConnectivityMonitor(QObject* parent)
ConnectivityMonitor::~ConnectivityMonitor()
{
qDebug() << "Destroying connectivity monitor";
C_DBG << "Destroying connectivity monitor";
}
bool

View File

@ -125,7 +125,11 @@ ConversationListModelBase::dataForItem(item_t item, int role) const
if (interaction.type == interaction::Type::UPDATE_PROFILE) {
lastInteractionBody = interaction::getProfileUpdatedString();
} else if (interaction.type == interaction::Type::DATA_TRANSFER) {
lastInteractionBody = interaction.commit.value("displayName");
if (interaction.commit.value("tid").isEmpty()) {
lastInteractionBody = tr("Deleted media");
} else {
lastInteractionBody = interaction.commit.value("displayName");
}
} else if (interaction.type == lrc::api::interaction::Type::CALL) {
const auto isOutgoing = interaction.authorUri == accInfo.profileInfo.uri;
lastInteractionBody = interaction::getCallInteractionString(isOutgoing, interaction);

View File

@ -18,6 +18,7 @@
#include "currentcall.h"
#include "callparticipantsmodel.h"
#include "global.h"
#include <api/callparticipantsmodel.h>
#include <api/devicemodel.h>
@ -223,43 +224,14 @@ CurrentCall::updateCallInfo()
set_isGrid(callInfo.layout == call::Layout::GRID);
set_isAudioOnly(callInfo.isAudioOnly);
bool isAudioMuted {};
bool isVideoMuted {};
bool isSharing {};
QString sharingSource {};
bool isCapturing {};
QString previewId {};
using namespace libjami::Media;
if (callInfo.status != lrc::api::call::Status::ENDED) {
for (const auto& media : callInfo.mediaList) {
if (media[MediaAttributeKey::MEDIA_TYPE] == Details::MEDIA_TYPE_VIDEO) {
if (media[MediaAttributeKey::SOURCE].startsWith(VideoProtocolPrefix::DISPLAY)
|| media[MediaAttributeKey::SOURCE].startsWith(VideoProtocolPrefix::FILE)) {
isSharing = true;
sharingSource = media[MediaAttributeKey::SOURCE];
}
if (media[MediaAttributeKey::ENABLED] == TRUE_STR
&& media[MediaAttributeKey::MUTED] == FALSE_STR && previewId.isEmpty()) {
previewId = media[libjami::Media::MediaAttributeKey::SOURCE];
}
if (media[libjami::Media::MediaAttributeKey::SOURCE].startsWith(
libjami::Media::VideoProtocolPrefix::CAMERA)) {
isVideoMuted |= media[MediaAttributeKey::MUTED] == TRUE_STR;
isCapturing = media[MediaAttributeKey::MUTED] == FALSE_STR;
}
} else if (media[MediaAttributeKey::MEDIA_TYPE] == Details::MEDIA_TYPE_AUDIO) {
if (media[MediaAttributeKey::LABEL] == "audio_0") {
isAudioMuted |= media[libjami::Media::MediaAttributeKey::MUTED] == TRUE_STR;
}
}
}
}
set_previewId(previewId);
set_isAudioMuted(isAudioMuted);
set_isVideoMuted(isVideoMuted);
set_isSharing(isSharing);
set_sharingSource(sharingSource);
set_isCapturing(isCapturing);
auto callInfoEx = callInfo.getCallInfoEx();
set_previewId(callInfoEx["preview_id"].toString());
set_isAudioMuted(callInfoEx["is_audio_muted"].toBool());
set_isVideoMuted(callInfoEx["is_video_muted"].toBool());
set_isSharing(callInfoEx["is_sharing"].toBool());
set_sharingSource(isSharing_ ? callInfoEx["preview_id"].toString() : QString());
set_isCapturing(callInfoEx["is_capturing"].toBool());
set_isHandRaised(callModel->isHandRaised(id_));
set_isModerator(callModel->isModerator(id_));
@ -377,7 +349,7 @@ CurrentCall::onCurrentAccountIdChanged()
auto& accInfo = lrcInstance_->getCurrentAccountInfo();
set_isSIP(accInfo.profileInfo.type == profile::Type::SIP);
} catch (const std::exception& e) {
qWarning() << "Can't update current call type" << e.what();
C_DBG << "Can't update current call type" << e.what();
}
connectModel();

View File

@ -18,6 +18,8 @@
#include "currentconversation.h"
#include "global.h"
#include <api/conversationmodel.h>
#include <api/contact.h>
@ -264,51 +266,39 @@ void
CurrentConversation::connectModel()
{
membersModel_->setMembers({}, {}, {});
auto convModel = lrcInstance_->getCurrentConversationModel();
if (!convModel)
auto currentConversationModel = lrcInstance_->getCurrentConversationModel();
auto currentCallModel = lrcInstance_->getCurrentCallModel();
if (!currentConversationModel || !currentCallModel) {
C_DBG << "CurrentConversation: can't connect to unavailable models";
return;
}
auto connectObjectSignal = [this](auto obj, auto signal, auto slot) {
connect(obj, signal, this, slot, Qt::UniqueConnection);
};
connectObjectSignal(convModel,
connectObjectSignal(currentConversationModel,
&ConversationModel::conversationUpdated,
&CurrentConversation::onConversationUpdated);
connectObjectSignal(convModel,
connectObjectSignal(currentConversationModel,
&ConversationModel::profileUpdated,
&CurrentConversation::updateProfile);
connect(lrcInstance_->getCurrentConversationModel(),
&ConversationModel::profileUpdated,
this,
&CurrentConversation::updateProfile,
Qt::UniqueConnection);
connect(lrcInstance_->getCurrentConversationModel(),
&ConversationModel::onConversationErrorsUpdated,
this,
&CurrentConversation::updateErrors,
Qt::UniqueConnection);
connect(lrcInstance_->getCurrentConversationModel(),
&ConversationModel::activeCallsChanged,
this,
&CurrentConversation::updateActiveCalls,
Qt::UniqueConnection);
connect(lrcInstance_->getCurrentConversationModel(),
&ConversationModel::conversationPreferencesUpdated,
this,
&CurrentConversation::updateConversationPreferences,
Qt::UniqueConnection);
connect(lrcInstance_->getCurrentConversationModel(),
&ConversationModel::needsHost,
this,
&CurrentConversation::onNeedsHost,
Qt::UniqueConnection);
connect(lrcInstance_->getCurrentCallModel(),
&CallModel::callStatusChanged,
this,
&CurrentConversation::onCallStatusChanged,
Qt::UniqueConnection);
connectObjectSignal(currentConversationModel,
&ConversationModel::conversationErrorsUpdated,
&CurrentConversation::updateErrors);
connectObjectSignal(currentConversationModel,
&ConversationModel::activeCallsChanged,
&CurrentConversation::updateActiveCalls);
connectObjectSignal(currentConversationModel,
&ConversationModel::conversationPreferencesUpdated,
&CurrentConversation::updateConversationPreferences);
connectObjectSignal(currentConversationModel,
&ConversationModel::needsHost,
&CurrentConversation::onNeedsHost);
connectObjectSignal(currentCallModel,
&CallModel::callStatusChanged,
&CurrentConversation::onCallStatusChanged);
}
void

View File

@ -43,9 +43,8 @@ LRCInstance::LRCInstance(const QString& updateUrl,
muteDaemon_ = muteDaemon;
threadPool_->setMaxThreadCount(1);
connect(this, &LRCInstance::currentAccountIdChanged, [this] {
// save to config, editing the accountlistmodel's underlying data
accountModel().setTopAccount(currentAccountId_);
// Update the current account when the account list changes.
connect(&accountModel(), &AccountModel::accountsReordered, this, [this] {
Q_EMIT accountListChanged();
profile::Info profileInfo;
@ -62,6 +61,11 @@ LRCInstance::LRCInstance(const QString& updateUrl,
set_currentAccountAvatarSet(!profileInfo.avatar.isEmpty());
});
connect(this, &LRCInstance::currentAccountIdChanged, [this] {
// This will trigger `AccountModel::accountsReordered`.
accountModel().setTopAccount(currentAccountId_);
});
connect(&accountModel(), &AccountModel::profileUpdated, this, [this](const QString& id) {
if (id != currentAccountId_)
return;

View File

@ -162,6 +162,8 @@ MainApplication::MainApplication(int& argc, char** argv)
"libclient.debug=false\n"
"qt.*=false\n"
"qml.debug=false\n"
"default.debug=false\n"
"client.debug=false\n"
"\n");
// These can be set in the environment as well.
// e.g. QT_LOGGING_RULES="*.debug=false;qml.debug=true"
@ -423,7 +425,8 @@ MainApplication::initQmlLayer()
&screenInfo_,
this);
QUrl url;
QUrl url = u"qrc:/MainApplicationWindow.qml"_qs;
#ifdef QT_DEBUG
if (parser_.isSet("test")) {
// List the QML files in the project source tree.
const auto targetTestComponent = findResource(parser_.value("test"));
@ -437,9 +440,8 @@ MainApplication::initQmlLayer()
engine_->rootContext()->setContextProperty("testWidth", testWidth);
engine_->rootContext()->setContextProperty("testHeight", testHeight);
url = u"qrc:/ComponentTestWindow.qml"_qs;
} else {
url = u"qrc:/MainApplicationWindow.qml"_qs;
}
#endif
QObject::connect(
engine_.get(),
&QQmlApplicationEngine::objectCreationFailed,
@ -449,7 +451,7 @@ MainApplication::initQmlLayer()
engine_->load(url);
// Report the render interface used.
C_DBG << "Main window loaded using" << getRenderInterfaceString();
C_INFO << "Main window loaded using" << getRenderInterfaceString();
}
void

View File

@ -48,10 +48,10 @@ ListSelectionView {
leftPaneItem: viewCoordinator.getView("SidePanel", true)
rightPaneItem: StackLayout {
id: conversationStackLayout
objectName: "ConversationLayout"
currentIndex: !CurrentConversation.hasCall ? 0 : 1
onCurrentIndexChanged: chatView.parent = currentIndex === 1 ? callStackView.chatViewContainer : chatViewContainer
currentIndex: CurrentConversation.hasCall ? 1 : 0
anchors.fill: parent
@ -64,24 +64,35 @@ ListSelectionView {
ChatView {
id: chatView
anchors.fill: parent
inCallView: parent == callStackView.chatViewContainer
// Use callStackView.chatViewContainer only when hasCall is true
// and callStackView.chatViewContainer not null.
// Because after a swarm call ends, callStackView.chatViewContainer might not be null
// due to a lack of call state change signals for the swarm call.
readonly property bool hasCall: CurrentConversation.hasCall
readonly property var inCallChatContainer: hasCall ? callStackView.chatViewContainer : null
// Parent the chat view to the call stack view when in call.
parent: inCallChatContainer ? inCallChatContainer : chatViewContainer
inCallView: parent === callStackView.chatViewContainer
readonly property string currentConvId: CurrentConversation.id
onCurrentConvIdChanged: {
if (!CurrentConversation.hasCall) {
Qt.callLater(focusChatView);
} else {
dismiss();
callStackView.contentView.forceActiveFocus();
}
Qt.callLater(function() {
if (CurrentConversation.hasCall) {
callStackView.contentView.forceActiveFocus();
} else {
focusChatView();
}
});
}
onDismiss: {
if (!inCallView) {
viewNode.dismiss();
} else {
if (inCallView) {
callStackView.chatViewContainer.visible = false;
callStackView.contentView.forceActiveFocus();
} else {
viewNode.dismiss();
}
}

View File

@ -34,24 +34,6 @@ Label {
property bool inSettings: viewCoordinator.currentViewName === "SettingsView"
// TODO: remove these refresh hacks use QAbstractItemModels correctly
Connections {
target: AccountAdapter
function onAccountStatusChanged(accountId) {
AccountListModel.reset();
}
}
Connections {
target: LRCInstance
function onAccountListChanged() {
root.update();
AccountListModel.reset();
}
}
function togglePopup() {
if (root.popup.opened) {
root.popup.close();

View File

@ -60,23 +60,6 @@ Popup {
property bool inSettings: viewCoordinator.currentViewName === "SettingsView"
// TODO: remove these refresh hacks use QAbstractItemModels correctly
Connections {
target: AccountAdapter
function onAccountStatusChanged(accountId) {
AccountListModel.reset();
}
}
Connections {
target: LRCInstance
function onAccountListChanged() {
AccountListModel.reset();
}
}
RowLayout {
id: mainLayout
anchors.fill: parent
@ -257,11 +240,6 @@ Popup {
color: JamiTheme.smartListHoveredColor
}
// fake footer item as workaround for Qt 5.15 bug
// https://bugreports.qt.io/browse/QTBUG-85302
// don't use the clip trick and footer item overlay
// explained here https://stackoverflow.com/a/64625149
// as it causes other complexities in handling the drop shadow
ItemDelegate {
id: addAccountItem

View File

@ -112,6 +112,7 @@ Control {
},
Action {
id: shareMenuAction
enabled: !CurrentCall.isSharing
text: JamiStrings.selectShareMethod
property int popupMode: CallActionBar.ActionPopupMode.ListElement
property var listModel: ListModel {
@ -123,7 +124,7 @@ Control {
"Name": JamiStrings.shareScreen,
"IconSource": JamiResources.laptop_black_24dp_svg
});
if (Qt.platform.os.toString() !== "osx" && !UtilsAdapter.isWayland()) {
if (Qt.platform.os.toString() !== "osx") {
shareModel.append({
"Name": JamiStrings.shareWindow,
"IconSource": JamiResources.window_black_24dp_svg
@ -293,7 +294,24 @@ Control {
},
Action {
id: muteVideoAction
onTriggered: CallAdapter.muteCameraToggle()
onTriggered: {
if (CurrentCall.isSharing && UtilsAdapter.isWayland()) {
// Unmuting the camera while a screen share is ongoing causes the daemon
// to stop sharing. However, on Wayland, every share has an associated
// ScreenCastPortal object which is managed by the client and needs to
// be destroyed when the share ends. This is why we explicitly call the
// stopSharing function below.
//
// The muteCamera variable is set whenever a share starts and is normally used
// by the stopSharing function to restore the camera to its previous state
// when a share ends. Here we know that the user wants to unmute the camera,
// so we have to explicitly set muteCamera to false.
AvAdapter.muteCamera = false;
AvAdapter.stopSharing(CurrentCall.sharingSource);
} else {
CallAdapter.muteCameraToggle();
}
}
checkable: true
icon.source: checked ? JamiResources.videocam_off_24dp_svg : JamiResources.videocam_24dp_svg
icon.color: checked ? "red" : "white"

View File

@ -114,7 +114,9 @@ Item {
}
function openShareScreen() {
if (Qt.application.screens.length === 1) {
if (UtilsAdapter.isWayland()) {
AvAdapter.shareEntireScreenWayland();
} else if (Qt.application.screens.length === 1) {
AvAdapter.shareEntireScreen(0);
} else {
SelectScreenWindowCreation.presentSelectScreenWindow(appWindow, false);
@ -122,6 +124,10 @@ Item {
}
function openShareWindow() {
if (UtilsAdapter.isWayland()) {
AvAdapter.shareWindowWayland();
return;
}
AvAdapter.getListWindows();
if (AvAdapter.windowsNames.length >= 1) {
SelectScreenWindowCreation.presentSelectScreenWindow(appWindow, true);

View File

@ -25,7 +25,11 @@ import "../../commoncomponents"
Item {
id: root
property alias chatViewContainer: ongoingCallPage.chatViewContainer
property var chatViewContainer: {
if (callStackMainView.item instanceof OngoingCallPage)
return callStackMainView.item.chatViewContainer;
return undefined;
}
property alias contentView: callStackMainView
property var sipKeys: ["1", "2", "3", "A", "4", "5", "6", "B", "7", "8", "9", "C", "*", "0", "#", "D"]
@ -61,44 +65,49 @@ Item {
// TODO: this should all be done by listening to
// parent visibility change or parent `Component.onDestruction`
function needToCloseInCallConversationAndPotentialWindow() {
ongoingCallPage.closeInCallConversation();
ongoingCallPage.closeContextMenuAndRelatedWindows();
if (callStackMainView.item instanceof OngoingCallPage) {
callStackMainView.item.closeInCallConversation();
callStackMainView.item.closeContextMenuAndRelatedWindows();
}
}
function toggleFullScreen() {
if (!layoutManager.isCallFullscreen) {
layoutManager.pushFullScreenItem(callStackMainView.currentItem, callStackMainView, null, null);
layoutManager.pushFullScreenItem(callStackMainView.item, callStackMainView, null, null);
} else {
layoutManager.removeFullScreenItem(callStackMainView.currentItem);
layoutManager.removeFullScreenItem(callStackMainView.item);
}
}
StackLayout {
Loader {
id: callStackMainView
anchors.fill: parent
property Item currentItem: itemAt(currentIndex)
currentIndex: {
sourceComponent: {
switch (CurrentCall.status) {
case Call.Status.IN_PROGRESS:
case Call.Status.CONNECTED:
case Call.Status.PAUSED:
return 1;
return ongoingCallPageComponent;
case Call.Status.SEARCHING:
case Call.Status.CONNECTING:
case Call.Status.INCOMING_RINGING:
case Call.Status.OUTGOING_RINGING:
return initialCallPageComponent;
default:
return 0;
return null;
}
}
InitialCallPage {
Component {
id: initialCallPageComponent
InitialCallPage {}
}
OngoingCallPage {
id: ongoingCallPage
Component {
id: ongoingCallPageComponent
OngoingCallPage {}
}
}
}

View File

@ -222,7 +222,7 @@ Rectangle {
onExtrasPanelWidthChanged: {
resolvePanes();
// This range should ensure that the panel won't restore to maximized.
if (extrasPanelWidth !== 0 && extrasPanelWidth !== width) {
if (extrasPanelWidth !== 0 && extrasPanelWidth !== this.width) {
console.debug("Saving previous extras panel width: %1".arg(extrasPanelWidth));
previousExtrasPanelWidth = extrasPanelWidth;
}

View File

@ -88,7 +88,7 @@ Rectangle {
mirror: UtilsAdapter.isRTL
source: JamiResources.back_24dp_svg
toolTipText: CurrentConversation.inCall ? JamiStrings.backCall : JamiStrings.hideChat
toolTipText: CurrentConversation.inCall ? JamiStrings.returnToCall : JamiStrings.hideChat
onClicked: root.backClicked()
}
@ -147,7 +147,7 @@ Rectangle {
}
JamiPushButton { QWKSetParentHitTestVisible {}
id: startAAudioCallButton
id: startAudioCallButton
visible: interactionButtonsVisibility &&
(!addMemberVisibility || UtilsAdapter.getAppValue(Settings.EnableExperimentalSwarm))
@ -158,7 +158,7 @@ Rectangle {
}
JamiPushButton { QWKSetParentHitTestVisible {}
id: startAVideoCallButton
id: startVideoCallButton
visible: interactionButtonsVisibility &&
CurrentAccount.videoEnabled_Video &&
@ -230,8 +230,7 @@ Rectangle {
checkable: true
checked: extrasPanel.isOpen(ChatView.SwarmDetailsPanel)
visible: interactionButtonsVisibility &&
(swarmDetailsVisibility || LRCInstance.currentAccountType === Profile.Type.SIP) // TODO if SIP not a request
visible: (swarmDetailsVisibility || LRCInstance.currentAccountType === Profile.Type.SIP)
source: JamiResources.swarm_details_panel_svg
toolTipText: JamiStrings.details

View File

@ -43,7 +43,7 @@ ContextMenuAutoLoader {
property list<GeneralMenuItem> menuItems: [
GeneralMenuItem {
id: startVideoCallItem
id: startVideoCall
canTrigger: CurrentAccount.videoEnabled_Video && !hasCall && !readOnly
itemName: JamiStrings.startVideoCall

View File

@ -60,7 +60,7 @@ JamiListView {
property var messageListModel: MessagesAdapter.mediaMessageListModel
readonly property int documentType: Interaction.Type.DATA_TRANSFER
readonly property int transferFinishedType: Interaction.Status.TRANSFER_FINISHED
readonly property int transferFinishedType: Interaction.TransferStatus.TRANSFER_FINISHED
readonly property int transferSuccesType: Interaction.Status.SUCCESS
onMessageListModelChanged: sourceModel = root.visible && messageListModel ? messageListModel : null

View File

@ -0,0 +1,231 @@
/*
* Copyright (C) 2024 Savoir-faire Linux Inc.
*
* 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 <https://www.gnu.org/licenses/>.
*/
import QtQuick
import QtQuick.Controls
import Qt5Compat.GraphicalEffects
import net.jami.Enums 1.1
import net.jami.Constants 1.1
import net.jami.Adapters 1.1
import "../../commoncomponents"
// This component uses anchors and they are set within this component.
LocalVideo {
id: localPreview
required property var container
required property real opacityModifier
readonly property int previewMargin: 15
readonly property int previewMarginYTop: previewMargin + 42
readonly property int previewMarginYBottom: previewMargin + 84
anchors.bottomMargin: previewMarginYBottom
anchors.leftMargin: sideMargin
anchors.rightMargin: sideMargin
anchors.topMargin: previewMarginYTop
visibilityCondition: (CurrentCall.isSharing || !CurrentCall.isVideoMuted) &&
!CurrentCall.isConference
// Keep the area of the preview a proportion of the screen size plus a
// modifier to allow the user to scale it.
readonly property real containerArea: container.width * container.height
property real scalingFactor: 1
width: Math.sqrt(containerArea / 16) * scalingFactor
height: width * invAspectRatio
flip: CurrentCall.flipSelf && !CurrentCall.isSharing
blurRadius: hidden ? 25 : 0
opacity: hidden ? opacityModifier : 1
// Allow hiding the preview (available when anchored)
readonly property bool hovered: hoverHandler.hovered
readonly property bool anchored: state !== "unanchored"
property bool hidden: false
readonly property real hiddenHandleSize: 32
// Compute the margin as a function of the preview width in order to
// apply a negative margin and expose a constant width handle.
// If not hidden, return the previewMargin.
property real sideMargin: !hidden ? previewMargin : -(width - hiddenHandleSize)
// Animate the hiddenSize with a Behavior.
Behavior on sideMargin { NumberAnimation { duration: 250; easing.type: Easing.OutExpo }}
readonly property bool onLeft: state.indexOf("left") !== -1
MouseArea {
anchors.fill: parent
enabled: !localPreview.hidden
onWheel: function(event) {
const delta = event.angleDelta.y / 120 * 0.1;
if (event.modifiers & Qt.ControlModifier) {
parent.opacity = JamiQmlUtils.clamp(parent.opacity + delta, 0.25, 1);
} else {
localPreview.scalingFactor = JamiQmlUtils.clamp(localPreview.scalingFactor + delta, 0.5, 4);
}
}
}
PushButton {
id: hidePreviewButton
objectName: "hidePreviewButton"
width: localPreview.hiddenHandleSize
state: localPreview.onLeft ?
(localPreview.hidden ? "right" : "left") :
(localPreview.hidden ? "left" : "right")
states: [
State {
name: "left"
AnchorChanges {
target: hidePreviewButton
anchors.left: parent.left
}
},
State {
name: "right"
AnchorChanges {
target: hidePreviewButton
anchors.right: parent.right
}
}
]
anchors.top: parent.top
anchors.bottom: parent.bottom
opacity: (localPreview.anchored && localPreview.hovered) || localPreview.hidden
Behavior on opacity { NumberAnimation { duration: 250; easing.type: Easing.OutExpo }}
visible: opacity > 0
background: Rectangle {
readonly property color normalColor: JamiTheme.mediumGrey
color: JamiTheme.mediumGrey
opacity: hidePreviewButton.hovered ? 0.7 : 0.5
Behavior on opacity { NumberAnimation { duration: 250; easing.type: Easing.OutExpo }}
}
normalImageSource: hidePreviewButton.state === "left" ?
JamiResources.chevron_left_black_24dp_svg :
JamiResources.chevron_right_black_24dp_svg
imageColor: JamiTheme.darkGreyColor
onClicked: localPreview.hidden = !localPreview.hidden
toolTipText: localPreview.hidden ?
JamiStrings.showLocalVideo :
JamiStrings.hideLocalVideo
}
state: "anchor_top_right"
states: [
State {
name: "unanchored"
AnchorChanges {
target: localPreview
anchors.top: undefined
anchors.right: undefined
anchors.bottom: undefined
anchors.left: undefined
}
},
State {
name: "anchor_top_left"
AnchorChanges {
target: localPreview
anchors.top: localPreview.container.top
anchors.left: localPreview.container.left
}
},
State {
name: "anchor_top_right"
AnchorChanges {
target: localPreview
anchors.top: localPreview.container.top
anchors.right: localPreview.container.right
}
},
State {
name: "anchor_bottom_right"
AnchorChanges {
target: localPreview
anchors.bottom: localPreview.container.bottom
anchors.right: localPreview.container.right
}
},
State {
name: "anchor_bottom_left"
AnchorChanges {
target: localPreview
anchors.bottom: localPreview.container.bottom
anchors.left: localPreview.container.left
}
}
]
transitions: Transition {
AnchorAnimation {
duration: 250
easing.type: Easing.OutBack
easing.overshoot: 1.5
}
}
HoverHandler {
id: hoverHandler
}
DragHandler {
id: dragHandler
readonly property var container: localPreview.container
target: parent
dragThreshold: 4
enabled: !localPreview.hidden
xAxis.maximum: container.width - parent.width - previewMargin
xAxis.minimum: previewMargin
yAxis.maximum: container.height - parent.height - previewMarginYBottom
yAxis.minimum: previewMarginYTop
onActiveChanged: {
if (active) {
localPreview.state = "unanchored";
} else {
const center = Qt.point(target.x + target.width / 2,
target.y + target.height / 2);
const containerCenter = Qt.point(container.x + container.width / 2,
container.y + container.height / 2);
if (center.x >= containerCenter.x) {
if (center.y >= containerCenter.y) {
localPreview.state = "anchor_bottom_right";
} else {
localPreview.state = "anchor_top_right";
}
} else {
if (center.y >= containerCenter.y) {
localPreview.state = "anchor_bottom_left";
} else {
localPreview.state = "anchor_top_left";
}
}
}
}
}
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: localPreview.width
height: localPreview.height
radius: JamiTheme.primaryRadius
}
}
}

View File

@ -213,12 +213,8 @@ Rectangle {
onClicked: {
if (type === "cam" || type === "mic") {
var acceptVideoMedia = true;
if (type === "cam")
acceptVideoMedia = true;
else if (type === "mic")
acceptVideoMedia = false;
CallAdapter.setCallMedia(CurrentAccount.id, CurrentConversation.id, acceptVideoMedia);
var muteVideo = (type === "mic");
CallAdapter.setCallMedia(CurrentAccount.id, CurrentConversation.id, muteVideo);
callAccepted();
} else {
callCanceled();

View File

@ -82,11 +82,11 @@ Window {
ListElement {
shortcut: "Ctrl + Shift + C"
description: qsTr("Start an audio call")
description: qsTr("Start audio call")
}
ListElement {
shortcut: "Ctrl + Shift + X"
description: qsTr("Start a video call")
description: qsTr("Start video call")
}
ListElement {
shortcut: "Ctrl + Shift + L"
@ -102,7 +102,7 @@ Window {
}
ListElement {
shortcut: "Ctrl + Shift + Delete"
description: qsTr("Remove conversation")
description: qsTr("Leave conversation")
}
ListElement {
shortcut: "Ctrl + Shift + A"
@ -117,41 +117,12 @@ Window {
description: qsTr("Cancel message edition")
}
},
ListModel {
id: keyboardSettingsShortcutsModel
ListElement {
shortcut: "Ctrl + M"
description: qsTr("Media settings")
}
ListElement {
shortcut: "Ctrl + G"
description: qsTr("General settings")
}
ListElement {
shortcut: "Ctrl + Alt + I"
description: qsTr("Account settings")
}
ListElement {
shortcut: "Ctrl + P"
description: qsTr("Plugin settings")
}
ListElement {
shortcut: "Ctrl + Shift + N"
description: qsTr("Open account creation wizard")
}
ListElement {
shortcut: "F10"
shortcut2: ""
description: qsTr("Open keyboard shortcut table")
}
},
ListModel {
id: keyboardCallsShortcutsModel
ListElement {
shortcut: "Ctrl + Y"
description: qsTr("Answer an incoming call")
description: qsTr("Answer incoming call")
}
ListElement {
shortcut: "Ctrl + D"
@ -159,7 +130,7 @@ Window {
}
ListElement {
shortcut: "Ctrl + Shift + D"
description: qsTr("Decline the call request")
description: qsTr("Decline call request")
}
ListElement {
shortcut: "M"
@ -174,6 +145,35 @@ Window {
description: qsTr("Take tile screenshot")
}
},
ListModel {
id: keyboardSettingsShortcutsModel
ListElement {
shortcut: "Ctrl + Alt + I"
description: qsTr("Open account settings")
}
ListElement {
shortcut: "Ctrl + G"
description: qsTr("Open general settings")
}
ListElement {
shortcut: "Ctrl + M"
description: qsTr("Open media settings")
}
ListElement {
shortcut: "Ctrl + E"
description: qsTr("Open extensions settings")
}
ListElement {
shortcut: "Ctrl + Shift + N"
description: qsTr("Open account creation wizard")
}
ListElement {
shortcut: "F10"
shortcut2: ""
description: qsTr("View keyboard shortcuts")
}
},
ListModel {
id: keyboardMarkdownShortcutsModel
@ -215,11 +215,11 @@ Window {
}
ListElement {
shortcut: "Shift + Alt + T"
description: qsTr("Show formatting")
description: qsTr("Show/hide formatting")
}
ListElement {
shortcut: "Shift + Alt + P"
description: qsTr("Show preview")
description: qsTr("Show preview/Continue editing")
}
}
]

View File

@ -61,17 +61,9 @@ Item {
opacity: 0
// (un)subscribe to an app-wide mouse move event trap filtered
// for the overlay's geometry
function setupFilter() {
if (visible) {
CallOverlayModel.registerFilter(appWindow, this);
} else {
CallOverlayModel.unregisterFilter(appWindow, this);
}
}
Component.onCompleted: setupFilter()
onVisibleChanged: setupFilter()
Component.onCompleted: CallOverlayModel.setEventFilterActive(appWindow, this, true)
Component.onDestruction: CallOverlayModel.setEventFilterActive(appWindow, this, false)
onVisibleChanged: CallOverlayModel.setEventFilterActive(appWindow, this, visible)
Connections {
target: CallOverlayModel

View File

@ -30,11 +30,6 @@ import "../../commoncomponents"
Rectangle {
id: root
// Constraints for the preview component.
property int previewMargin: 15
property int previewMarginYTop: previewMargin + 42
property int previewMarginYBottom: previewMargin + 84
property alias chatViewContainer: chatViewContainer
property string callPreviewId
@ -166,222 +161,15 @@ Rectangle {
}
}
LocalVideo {
// Note: this component should not be used within a layout, as
// it implements anchor management itself.
InCallLocalVideo {
id: localPreview
objectName: "localPreview"
readonly property var container: parent
readonly property string callPreviewId: root.callPreviewId
visibilityCondition: (CurrentCall.isSharing || !CurrentCall.isVideoMuted) &&
!CurrentCall.isConference
height: width * invAspectRatio
// Keep the area of the preview a proportion of the screen size plus a
// modifier to allow the user to scale it.
readonly property real containerArea: container.width * container.height
property real scalingFactor: 1
width: Math.sqrt(containerArea / 16) * scalingFactor
flip: CurrentCall.flipSelf && !CurrentCall.isSharing
blurRadius: hidden ? 25 : 0
onCallPreviewIdChanged: startWithId(callPreviewId)
onVisibleChanged: if (!visible) stop()
anchors.topMargin: previewMarginYTop
anchors.leftMargin: sideMargin
anchors.rightMargin: sideMargin
anchors.bottomMargin: previewMarginYBottom
opacity: hidden ? callOverlay.mainOverlayOpacity : 1
// Allow hiding the preview (available when anchored)
readonly property bool hovered: hoverHandler.hovered
readonly property bool anchored: state !== "unanchored"
property bool hidden: false
readonly property real hiddenHandleSize: 32
// Compute the margin as a function of the preview width in order to
// apply a negative margin and expose a constant width handle.
// If not hidden, return the previewMargin.
property real sideMargin: !hidden ? previewMargin : -(width - hiddenHandleSize)
// Animate the hiddenSize with a Behavior.
Behavior on sideMargin { NumberAnimation { duration: 250; easing.type: Easing.OutExpo }}
readonly property bool onLeft: state.indexOf("left") !== -1
PushButton {
id: hidePreviewButton
objectName: "hidePreviewButton"
width: localPreview.hiddenHandleSize
state: {
if (!localPreview.anchored) {
return "none";
}
return localPreview.onLeft ?
(localPreview.hidden ? "right" : "left") :
(localPreview.hidden ? "left" : "right")
}
states: [
State {
name: "none"
// Override visible to false when the localPreview isn't anchored.
PropertyChanges {
target: hidePreviewButton
visible: false
}
},
State {
name: "left"
AnchorChanges {
target: hidePreviewButton
anchors.left: parent.left
}
},
State {
name: "right"
AnchorChanges {
target: hidePreviewButton
anchors.right: parent.right
}
}
]
anchors.top: parent.top
anchors.bottom: parent.bottom
opacity: (localPreview.anchored && localPreview.hovered) || localPreview.hidden
Behavior on opacity { NumberAnimation { duration: 250; easing.type: Easing.OutExpo }}
visible: opacity > 0
background: Rectangle {
readonly property color normalColor: JamiTheme.mediumGrey
color: JamiTheme.mediumGrey
opacity: hidePreviewButton.hovered ? 0.7 : 0.5
Behavior on opacity { NumberAnimation { duration: 250; easing.type: Easing.OutExpo }}
}
normalImageSource: hidePreviewButton.state === "left" ?
JamiResources.chevron_left_black_24dp_svg :
JamiResources.chevron_right_black_24dp_svg
imageColor: JamiTheme.darkGreyColor
onClicked: localPreview.hidden = !localPreview.hidden
toolTipText: localPreview.hidden ?
JamiStrings.showLocalVideo :
JamiStrings.hideLocalVideo
}
state: "anchor_top_right"
states: [
State {
name: "unanchored"
AnchorChanges {
target: localPreview
anchors.top: undefined
anchors.right: undefined
anchors.bottom: undefined
anchors.left: undefined
}
},
State {
name: "anchor_top_left"
AnchorChanges {
target: localPreview
anchors.top: localPreview.container.top
anchors.left: localPreview.container.left
}
},
State {
name: "anchor_top_right"
AnchorChanges {
target: localPreview
anchors.top: localPreview.container.top
anchors.right: localPreview.container.right
}
},
State {
name: "anchor_bottom_right"
AnchorChanges {
target: localPreview
anchors.bottom: localPreview.container.bottom
anchors.right: localPreview.container.right
}
},
State {
name: "anchor_bottom_left"
AnchorChanges {
target: localPreview
anchors.bottom: localPreview.container.bottom
anchors.left: localPreview.container.left
}
}
]
transitions: Transition {
AnchorAnimation {
duration: 250
easing.type: Easing.OutBack
easing.overshoot: 1.5
}
}
HoverHandler {
id: hoverHandler
}
WheelHandler {
onWheel: function(event) {
const delta = event.angleDelta.y / 120 * 0.1;
parent.opacity = JamiQmlUtils.clamp(parent.opacity + delta, 0.25, 1);
}
acceptedModifiers: Qt.CTRL
}
WheelHandler {
onWheel: function(event) {
const delta = event.angleDelta.y / 120 * 0.1;
localPreview.scalingFactor = JamiQmlUtils.clamp(localPreview.scalingFactor + delta, 0.5, 4);
}
acceptedModifiers: Qt.NoModifier
enabled: !localPreview.hidden
}
DragHandler {
id: dragHandler
readonly property var container: localPreview.container
target: parent
dragThreshold: 4
enabled: !localPreview.hidden
xAxis.maximum: container.width - parent.width - previewMargin
xAxis.minimum: previewMargin
yAxis.maximum: container.height - parent.height - previewMarginYBottom
yAxis.minimum: previewMarginYTop
onActiveChanged: {
if (active) {
localPreview.state = "unanchored";
} else {
const center = Qt.point(target.x + target.width / 2,
target.y + target.height / 2);
const containerCenter = Qt.point(container.x + container.width / 2,
container.y + container.height / 2);
if (center.x >= containerCenter.x) {
if (center.y >= containerCenter.y) {
localPreview.state = "anchor_bottom_right";
} else {
localPreview.state = "anchor_top_right";
}
} else {
if (center.y >= containerCenter.y) {
localPreview.state = "anchor_bottom_left";
} else {
localPreview.state = "anchor_top_left";
}
}
}
}
}
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: localPreview.width
height: localPreview.height
radius: JamiTheme.primaryRadius
}
}
container: parent
rendererId: CurrentCall.previewId
opacityModifier: callOverlay.mainOverlayOpacity
}
CallOverlay {

View File

@ -38,9 +38,9 @@ ItemDelegate {
highlighted: ListView.isCurrentItem
property bool interactive: true
property string lastInteractionDate: LastInteractionTimeStamp === undefined ? "" : LastInteractionTimeStamp
property string lastInteractionFormattedDate: MessagesAdapter.getBestFormattedDate(lastInteractionDate)
property int lastInteractionTimeStamp: LastInteractionTimeStamp
property string lastInteractionFormattedDate: MessagesAdapter.getBestFormattedDate(lastInteractionTimeStamp)
property bool showSharePositionIndicator: PositionManager.isPositionSharedToConv(accountId, UID)
property bool showSharedPositionIndicator: PositionManager.isConvSharingPosition(accountId, UID)
@ -58,7 +58,7 @@ ItemDelegate {
Connections {
target: MessagesAdapter
function onTimestampUpdated() {
lastInteractionFormattedDate = MessagesAdapter.getBestFormattedDate(lastInteractionDate);
lastInteractionFormattedDate = MessagesAdapter.getBestFormattedDate(lastInteractionTimeStamp);
}
}
@ -130,7 +130,7 @@ ItemDelegate {
color: JamiTheme.textColor
}
RowLayout {
visible: ContactType !== Profile.Type.TEMPORARY && !IsBanned && lastInteractionFormattedDate !== undefined && interactive
visible: ContactType !== Profile.Type.TEMPORARY && !IsBanned && lastInteractionTimeStamp > 0 && interactive
Layout.fillWidth: true
Layout.minimumHeight: 20
Layout.alignment: Qt.AlignTop
@ -138,7 +138,7 @@ ItemDelegate {
// last Interaction date
Text {
Layout.alignment: Qt.AlignVCenter
text: lastInteractionFormattedDate === undefined ? "" : lastInteractionFormattedDate
text: lastInteractionFormattedDate
textFormat: TextEdit.PlainText
font.pointSize: JamiTheme.smallFontSize
font.weight: UnreadMessagesCount ? Font.DemiBold : Font.Normal

View File

@ -30,7 +30,7 @@ ContextMenuAutoLoader {
property list<GeneralMenuItem> menuItems: [
GeneralMenuItem {
id: startVideoCallItem
id: startVideoCall
itemName: JamiStrings.startVideoCall
canTrigger: ConversationsAdapter.dialogId(participantUri) !== ""
iconSource: JamiResources.videocam_24dp_svg

View File

@ -335,40 +335,6 @@ MessagesAdapter::onPaste()
}
}
QString
MessagesAdapter::getStatusString(int status)
{
switch (static_cast<interaction::Status>(status)) {
case interaction::Status::SENDING:
return QObject::tr("Sending");
case interaction::Status::FAILURE:
return QObject::tr("Failure");
case interaction::Status::SUCCESS:
return QObject::tr("Sent");
case interaction::Status::TRANSFER_CREATED:
return QObject::tr("Connecting");
case interaction::Status::TRANSFER_ACCEPTED:
return QObject::tr("Accept");
case interaction::Status::TRANSFER_CANCELED:
return QObject::tr("Canceled");
case interaction::Status::TRANSFER_ERROR:
case interaction::Status::TRANSFER_UNJOINABLE_PEER:
return QObject::tr("Unable to make contact");
case interaction::Status::TRANSFER_ONGOING:
return QObject::tr("Ongoing");
case interaction::Status::TRANSFER_AWAITING_PEER:
return QObject::tr("Waiting for contact");
case interaction::Status::TRANSFER_AWAITING_HOST:
return QObject::tr("Incoming transfer");
case interaction::Status::TRANSFER_TIMEOUT_EXPIRED:
return QObject::tr("Timed out waiting for contact");
case interaction::Status::TRANSFER_FINISHED:
return QObject::tr("Finished");
default:
return {};
}
}
QVariantMap
MessagesAdapter::getTransferStats(const QString& msgId, int status)
{

View File

@ -159,7 +159,6 @@ public:
const QColor& linkColor = QColor(0x06, 0x45, 0xad),
const QColor& backgroundColor = QColor(0x0, 0x0, 0x0));
Q_INVOKABLE void onPaste();
Q_INVOKABLE QString getStatusString(int status);
Q_INVOKABLE QVariantMap getTransferStats(const QString& messageId, int);
Q_INVOKABLE QVariant dataForInteraction(const QString& interactionId,
int role = Qt::DisplayRole) const;

View File

@ -47,7 +47,7 @@ Item {
// AboutPopUp
property string version: qsTr("Version") + (AppVersionManager.isCurrentVersionBeta() ? " (Beta)" : "")
property string declarationYear: "© 2015-2024"
property string slogan: "Eleutheria"
property string slogan: "Astarte"
property string declaration: qsTr('Jami, a GNU package, is software for universal and distributed peer-to-peer communication that respects the freedom and privacy of its users. Visit <a href="https://jami.net" style="color: ' + JamiTheme.buttonTintedBlue + '">jami.net</a>' + ' to learn more.')
property string noWarranty: qsTr('This program comes with absolutely no warranty. See the <a href="https://www.gnu.org/licenses/gpl-3.0.html" style="color: ' + JamiTheme.buttonTintedBlue + '">GNU General Public License</a>, version 3 or later for details.')
property string contribute: qsTr('Contribute')
@ -73,7 +73,7 @@ Item {
property string authenticate: qsTr("Authenticate")
property string deleteAccount: qsTr("Delete account")
property string inProgress: qsTr("In progress…")
property string authenticationFailed: qsTr("Authentication failed")
property string authenticationFailed: qsTr("An error occurred while authenticating account.")
property string password: qsTr("Password")
property string username: qsTr("Username")
property string alias: qsTr("Alias")
@ -181,7 +181,7 @@ Item {
property string callSettingsTitle: qsTr("Call settings")
property string chatSettingsTitle: qsTr("Chat")
property string advancedSettingsTitle: qsTr("Advanced settings")
property string audioVideoSettingsTitle: qsTr("Audio and Video")
property string audioVideoSettingsTitle: qsTr("Media")
// AudioSettings
property string audio: qsTr("Audio")
@ -259,13 +259,13 @@ Item {
property string paste: qsTr("Paste")
// ConversationContextMenu
property string startVideoCall: qsTr("Start video call")
property string startAudioCall: qsTr("Start audio call")
property string startVideoCall: qsTr("Start video call")
property string clearConversation: qsTr("Clear conversation")
property string confirmAction: qsTr("Confirm action")
property string removeConversation: qsTr("Remove conversation")
property string confirmRmConversation: qsTr("Would you really like to remove this conversation?")
property string confirmBlockConversation: qsTr("Would you really like to block this conversation?")
property string removeConversation: qsTr("Leave conversation")
property string confirmRmConversation: qsTr("Do you really want to leave this conversation?")
property string confirmBlockConversation: qsTr("Do you really want to block this conversation?")
property string removeContact: qsTr("Remove contact")
property string blockContact: qsTr("Block contact")
property string convDetails: qsTr("Conversation details")
@ -294,7 +294,7 @@ Item {
property string screenshotTaken: qsTr("Screenshot saved to %1")
property string fileSaved: qsTr("File saved to %1")
//advanced information
// Advanced information
property string renderersInformation: qsTr("Renderers information")
property string callInformation: qsTr("Call information")
property string peerNumber: qsTr("Peer number")
@ -338,17 +338,18 @@ Item {
// Chatview header
property string hideChat: qsTr("Hide chat")
property string placeAudioCall: qsTr("Place audio call")
property string placeVideoCall: qsTr("Place video call")
property string placeAudioCall: qsTr("Start audio call")
property string placeVideoCall: qsTr("Start video call")
property string showPlugins: qsTr("Show available plugins")
property string addToConversations: qsTr("Add to conversations")
property string backendError: qsTr("This is the error from the backend: %0")
property string backendError: qsTr("A backend system error occurred: %0")
property string disabledAccount: qsTr("The account is disabled")
property string noNetworkConnectivity: qsTr("No network connectivity")
property string deletedMessage: qsTr("deleted a message")
property string backCall: qsTr("Back to Call")
property string deletedMedia: qsTr("deleted a media")
property string returnToCall: qsTr("Return to call")
//MessagesResearch
// MessagesResearch
property string jumpTo: qsTr("Jump to")
property string messages: qsTr("Messages")
property string files: qsTr("Files")
@ -379,7 +380,7 @@ Item {
property string invalidUsername: qsTr("Invalid username")
property string nameAlreadyTaken: qsTr("Name already taken")
property string usernameAlreadyTaken: qsTr("Username already taken")
property string joinJamiNoPassword: qsTr("Are you sure you would like to join Jami without a username?\nIf yes, only a randomly generated 40-character identifier will be assigned to this account.")
property string joinJamiNoPassword: qsTr("Do you really want to join Jami without a username?\nIf yes, only a randomly generated 40-character identifier will be assigned to this account.")
property string usernameToolTip: qsTr("- 32 characters maximum\n- Alphabetical characters (A to Z and a to z)\n- Numeric characters (0 to 9)\n- Special characters allowed: dash (-)")
// Good to know
@ -416,11 +417,11 @@ Item {
// CurrentAccountSettings && AdvancedSettings
property string backupSuccessful: qsTr("Backup successful")
property string backupFailed: qsTr("Backup failed")
property string backupFailed: qsTr("An error occurred while backing up account.")
property string changePasswordSuccess: qsTr("Password changed successfully")
property string changePasswordFailed: qsTr("Password change failed")
property string changePasswordFailed: qsTr("An error occurred while changing account password.")
property string setPasswordSuccess: qsTr("Password set successfully")
property string setPasswordFailed: qsTr("Password set failed")
property string setPasswordFailed: qsTr("An error occurred while setting account password.")
property string changePassword: qsTr("Change password")
property string setPassword: qsTr("Encrypt account")
property string setAPassword: qsTr("Set a password")
@ -451,16 +452,16 @@ Item {
property string tipLinkNewDevice: qsTr("Link a new device to this account")
property string linkDevice: qsTr("Exporting account…")
property string removeDevice: qsTr("Remove Device")
property string sureToRemoveDevice: qsTr("Are you sure you wish to remove this device?")
property string sureToRemoveDevice: qsTr("Do you really want to unlink selected device? To continue, enter account password and click Unlink.")
property string yourPinIs: qsTr("Your PIN is:")
property string linkDeviceNetWorkError: qsTr("Error connecting to the network.\nPlease try again later.")
property string linkDeviceNetWorkError: qsTr("A network error occurred while linking device.\nPlease try again later.")
// BannedContacts
property string banned: qsTr("Banned")
property string bannedContacts: qsTr("Banned contacts")
// DeleteAccountDialog
property string confirmDeleteQuestion: qsTr("Would you really like to delete this account?")
property string confirmDeleteQuestion: qsTr("Do you really want to delete this account? To continue, click Delete.")
property string deleteAccountInfos: qsTr("If your account has not been backed up or added to another device, your account and registered username will be IRREVOCABLY LOST.")
// DeviceItemDelegate
@ -498,14 +499,14 @@ Item {
// File transfer settings
property string fileTransfer: qsTr("File transfer")
property string autoAcceptFiles: qsTr("Automatically accept incoming files")
property string acceptTransferBelow: qsTr("Accept transfer limit (in Mb)")
property string acceptTransferTooltip: qsTr("in MB, 0 = unlimited")
property string acceptTransferBelow: qsTr("Accept transfer limit (Mb)")
property string acceptTransferTooltip: qsTr("MB, 0 = unlimited")
// JamiUserIdentity settings
property string register: qsTr("Register")
property string incorrectPassword: qsTr("Incorrect password")
property string networkError: qsTr("Network error")
property string somethingWentWrong: qsTr("Something went wrong")
property string somethingWentWrong: qsTr("An unexpected error occurred.")
// Context Menu
property string saveFile: qsTr("Save file")
@ -518,9 +519,9 @@ Item {
property string enableAutoUpdates: qsTr("Enable/Disable automatic updates")
property string updatesTitle: qsTr("Updates")
property string updateDialogTitle: qsTr("Update")
property string updateFound: qsTr("A new version of Jami was found\nWould you like to update now?")
property string updateFound: qsTr("A new version of Jami was found.\nDo you want to update Jami now?\nTo continue, click Update.")
property string updateNotFound: qsTr("No new version of Jami was found")
property string updateCheckError: qsTr("An error occured when checking for a new version")
property string updateCheckError: qsTr("An error occurred when checking for a new version.")
property string updateNetworkError: qsTr("Network error")
property string updateSSLError: qsTr("SSL error")
property string updateDownloadCanceled: qsTr("Installer download canceled")
@ -529,9 +530,9 @@ Item {
property string networkDisconnected: qsTr("Network disconnected")
property string accessError: qsTr("Content access error")
property string contentNotFoundError: qsTr("Content not found")
property string genericError: qsTr("Something went wrong")
property string genericError: qsTr("An unexpected error occurred.")
//Troubleshoot Settings
// Troubleshoot Settings
property string troubleshootTitle: qsTr("Troubleshoot")
property string troubleshootButton: qsTr("Open logs")
property string troubleshootText: qsTr("Get logs")
@ -546,9 +547,9 @@ Item {
property string callRecording: qsTr("Call recording")
property string alwaysRecordCalls: qsTr("Always record calls")
// KeyboardShortCutTable
property string keyboardShortcutTableWindowTitle: qsTr("Keyboard Shortcut Table")
property string keyboardShortcuts: qsTr("Keyboard Shortcuts")
// Keyboard shortcuts
property string keyboardShortcutTableWindowTitle: qsTr("Keyboard shortcuts")
property string keyboardShortcuts: qsTr("Keyboard shortcuts")
property string conversationKeyboardShortcuts: qsTr("Conversation")
property string callKeyboardShortcuts: qsTr("Call")
property string settings: qsTr("Settings")
@ -557,11 +558,11 @@ Item {
// View Logs
property string logsViewTitle: qsTr("Debug")
property string logsViewCopy: qsTr("Copy")
property string logsViewReport: qsTr("Report Bug")
property string logsViewReport: qsTr("Submit issue")
property string logsViewClear: qsTr("Clear")
property string cancel: qsTr("Cancel")
property string logsViewCopied: qsTr("Copied to clipboard!")
property string logsViewDisplay: qsTr("Receive Logs")
property string logsViewDisplay: qsTr("View logs")
// ImportFromBackupPage
property string archive: qsTr("Archive")
@ -618,7 +619,7 @@ Item {
property string takePhoto: qsTr("Take photo")
property string imageFiles: qsTr("Image Files (*.jpeg *.jpg *.png *.JPEG* .JPG *.PNG)")
// Plugins
// Extensions
property string autoUpdate: qsTr("Auto update")
property string disableAll: qsTr("Disable all")
property string installed: qsTr("Installed")
@ -627,16 +628,16 @@ Item {
property string installManually: qsTr("Install manually")
property string installMannuallyDescription: qsTr("Install an extension directly from your device.")
property string pluginStoreTitle: qsTr("Available")
property string pluginStoreNotAvailable: qsTr("Plugins store is not available")
property string storeNotSupportedPlatform: qsTr("The Jami Extension Store currently has no extension available for the platform in use. Check again later!")
property string pluginStoreNotAvailable: qsTr("The Jami Extension Store is not currently available. Please try again later.")
property string storeNotSupportedPlatform: qsTr("There are no extensions currently available in the Jami Extension Store for the platform in use. Please check again later.")
property string pluginPreferences: qsTr("Preferences")
property string installationFailed: qsTr("Installation failed")
property string pluginInstallationFailed: qsTr("The installation of the plugin failed")
property string installationFailed: qsTr("Installation error")
property string pluginInstallationFailed: qsTr("An error occurred while installing the extension.")
property string reset: qsTr("Reset")
property string uninstall: qsTr("Uninstall")
property string resetPreferences: qsTr("Reset Preferences")
property string selectPluginInstall: qsTr("Select a plugin to install")
property string uninstallPlugin: qsTr("Uninstall plugin")
property string selectPluginInstall: qsTr("Select extension to install")
property string uninstallPlugin: qsTr("Uninstall extension")
property string pluginResetConfirmation: qsTr("Are you sure you wish to reset %1 preferences?")
property string pluginUninstallConfirmation: qsTr("Are you sure you wish to uninstall %1?")
property string goBackToPluginsList: qsTr("Go back to plugins list")
@ -653,6 +654,7 @@ Item {
property string lastUpdate: qsTr("Last update %1")
property string by: qsTr("By %1")
property string proposedBy: qsTr("Proposed by %1")
// ProfilePage
property string information: qsTr("Information")
property string moreInformation: qsTr("More information")
@ -662,9 +664,9 @@ Item {
property string confirmRemovalRequest: qsTr("Enter the account password to confirm the removal of this device")
// SelectScreen
property string selectScreen: qsTr("Select a screen to share")
property string selectWindow: qsTr("Select a window to share")
property string allScreens: qsTr("All Screens")
property string selectScreen: qsTr("Select screen to share")
property string selectWindow: qsTr("Select window to share")
property string allScreens: qsTr("All screens")
property string screens: qsTr("Screens")
property string windows: qsTr("Windows")
property string screen: qsTr("Screen %1")
@ -682,7 +684,7 @@ Item {
property string connectJAMSServer: qsTr("Connect to a JAMS server")
property string createFromJAMS: qsTr("Create account from Jami Account Management Server (JAMS)")
property string addSIPAccount: qsTr("Configure a SIP account")
property string errorCreateAccount: qsTr("Error while creating your account. Check your credentials.")
property string errorCreateAccount: qsTr("An error occurred while creating the account. Check check credentials and try again.")
property string createNewRV: qsTr("Create a rendezvous point")
property string joinJami: qsTr("Join Jami")
property string createNewJamiAccount: qsTr("Create new Jami account")
@ -694,9 +696,9 @@ Item {
property string welcomeToJami: qsTr("Welcome to Jami")
// SmartList
property string clearText: qsTr("Clear Text")
property string clearText: qsTr("Clear text")
property string conversations: qsTr("Conversations")
property string searchResults: qsTr("Search Results")
property string searchResults: qsTr("Search results")
// SmartList context menu
property string declineContactRequest: qsTr("Decline contact request")
@ -706,13 +708,14 @@ Item {
property string update: qsTr("Automatically check for updates")
// Generic dialog options
property string optionOk: qsTr("Ok")
property string optionOk: qsTr("OK")
property string optionSave: qsTr("Save")
property string optionCancel: qsTr("Cancel")
property string optionUpgrade: qsTr("Upgrade")
property string optionLater: qsTr("Later")
property string optionDelete: qsTr("Delete")
property string optionRemove: qsTr("Remove")
property string optionLeave: qsTr("Leave")
property string optionBlock: qsTr("Block")
property string optionUnban: qsTr("Unban")
@ -735,8 +738,8 @@ Item {
property string removeDefaultModerator: qsTr("Remove default moderator")
// Daemon reconnection
property string reconnectDaemon: qsTr("Trying to reconnect to the Jami daemon (jamid)…")
property string reconnectionFailed: qsTr("Could not re-connect to the Jami daemon (jamid).\nJami will now quit.")
property string reconnectDaemon: qsTr("Attempting to reconnect to the Jami daemon (jamid)…")
property string reconnectionFailed: qsTr("An error occurred while reconnecting to the Jami daemon (jamid).\nThe application will now exit.")
// Message view
property string addEmoji: qsTr("Add emoji")
@ -791,23 +794,23 @@ Item {
// Invitation View
property string invitationViewSentRequest: qsTr("%1 has sent you a request for a conversation.")
property string invitationViewJoinConversation: qsTr("Hello,\nWould you like to join the conversation?")
property string invitationViewJoinConversation: qsTr("Hello,\nDo you want to join this conversation?")
property string invitationViewAcceptedConversation: qsTr("You have accepted\nthe conversation request")
property string invitationViewWaitingForSync: qsTr("Waiting until %1\nconnects to synchronize the conversation.")
// SwarmDetailsPanel
property string members: qsTr("%1 Members")
property string member: qsTr("Member")
property string swarmName: qsTr("Swarm's name")
property string contactName: qsTr("Contact's name")
property string swarmName: qsTr("Swarm name")
property string contactName: qsTr("Contact name")
property string addADescription: qsTr("Add a description")
property string muteConversation: qsTr("Mute conversation")
property string ignoreNotificationsTooltip: qsTr("Ignore all notifications from this conversation")
property string chooseAColor: qsTr("Choose a color")
property string chooseAColor: qsTr("Color")
property string defaultCallHost: qsTr("Default host (calls)")
property string leaveConversation: qsTr("Leave conversation")
property string typeOfSwarm: qsTr("Type of swarm")
property string typeOfSwarm: qsTr("Type")
property string none: qsTr("None")
// NewSwarmPage
@ -864,20 +867,20 @@ Item {
property string theme: qsTr("Theme")
property string zoomLevel: qsTr("Text zoom level")
//Donation campaign
// Donation campaign
property string donationTipBoxText: qsTr("Free and private sharing. <a href=\"https://jami.net/whydonate/\">Donate</a> to expand it.")
property string donation: qsTr("Donate")
property string donationText: qsTr("If you enjoy using Jami and believe in our mission, would you make a donation?")
property string notNow: qsTr("Not now")
property string enableDonation: qsTr("Enable donation campaign")
//Chat setting page
// Chat setting page
property string enter: qsTr("Enter")
property string shiftEnter: qsTr("Shift+Enter")
property string textFormattingDescription: qsTr("Enter or Shift+Enter to insert a new line")
property string textFormatting: qsTr("Text formatting")
//Connection monitoring
// Connection monitoring
property string connected: qsTr("Connected")
property string connectingTLS: qsTr("Connecting TLS")
property string connectingICE: qsTr("Connecting ICE")

View File

@ -0,0 +1,520 @@
/*!
* Copyright (C) 2024 Savoir-faire Linux Inc.
*
* 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/>.
*/
#include "screencastportal.h"
#include <QDebug>
#include <unistd.h>
#define REQUEST_PATH "/org/freedesktop/portal/desktop/request/%s/%s"
/*
* PipeWire supported cursor modes
*/
enum PortalCursorMode {
PORTAL_CURSOR_MODE_HIDDEN = 1 << 0,
PORTAL_CURSOR_MODE_EMBEDDED = 1 << 1,
PORTAL_CURSOR_MODE_METADATA = 1 << 2,
};
/*
* Helper function to allow getPipewireFd to stop and return an error
* code if a DBus operation/callback fails.
*/
void
ScreenCastPortal::abort(int error, const char* message)
{
portal_error = error;
qWarning() << "Aborting:" << message;
if (glib_main_loop && g_main_loop_is_running(glib_main_loop)) {
g_main_loop_quit(glib_main_loop);
}
}
/*
* Callback to free a DbusCallData object's memory and unsubscribe from the
* associated dbus signal.
*/
void
ScreenCastPortal::dbusCallDataFree(DbusCallData* ptr_dbus_call_data)
{
if (!ptr_dbus_call_data)
return;
if (ptr_dbus_call_data->signal_id)
g_dbus_connection_signal_unsubscribe(ptr_dbus_call_data->portal->connection,
ptr_dbus_call_data->signal_id);
g_clear_pointer(&ptr_dbus_call_data->request_path, g_free);
}
DbusCallData*
ScreenCastPortal::subscribeToSignal(const char* path, GDBusSignalCallback callback)
{
DbusCallData* ptr_dbus_call_data = new DbusCallData;
ptr_dbus_call_data->portal = this;
ptr_dbus_call_data->request_path = g_strdup(path);
ptr_dbus_call_data->signal_id
= g_dbus_connection_signal_subscribe(connection,
"org.freedesktop.portal.Desktop" /*sender*/,
"org.freedesktop.portal.Request" /*interface_name*/,
"Response" /*member: dbus signal name*/,
ptr_dbus_call_data->request_path /*object_path*/,
NULL,
G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
callback,
ptr_dbus_call_data,
NULL);
return ptr_dbus_call_data;
}
void
ScreenCastPortal::openPipewireRemote()
{
GUnixFDList* fd_list = NULL;
GVariant* result = NULL;
GError* error = NULL;
int fd_index;
GVariantBuilder builder;
g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
result = g_dbus_proxy_call_with_unix_fd_list_sync(proxy,
"OpenPipeWireRemote",
g_variant_new("(oa{sv})",
session_handle,
&builder),
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
&fd_list,
NULL,
&error);
if (error)
goto fail;
g_variant_get(result, "(h)", &fd_index);
g_variant_unref(result);
pipewireFd = g_unix_fd_list_get(fd_list, fd_index, &error);
g_object_unref(fd_list);
if (error)
goto fail;
g_main_loop_quit(glib_main_loop);
return;
fail:
qWarning() << "Error retrieving PipeWire fd:" << error->message;
g_error_free(error);
abort(EIO, "Failed to open PipeWire remote");
}
void
ScreenCastPortal::onStartResponseReceivedCallback(GDBusConnection* connection,
const char* sender_name,
const char* object_path,
const char* interface_name,
const char* signal_name,
GVariant* parameters,
gpointer user_data)
{
GVariant* stream_properties = NULL;
GVariant* streams = NULL;
GVariant* result = NULL;
GVariantIter iter;
uint32_t response;
DbusCallData* ptr_dbus_call_data = (DbusCallData*) user_data;
ScreenCastPortal* portal = ptr_dbus_call_data->portal;
g_clear_pointer(&ptr_dbus_call_data, dbusCallDataFree);
g_variant_get(parameters, "(u@a{sv})", &response, &result);
if (response) {
g_variant_unref(result);
portal->abort(EACCES, "Failed to start screencast, denied or cancelled by user");
return;
}
streams = g_variant_lookup_value(result, "streams", G_VARIANT_TYPE_ARRAY);
g_variant_iter_init(&iter, streams);
g_variant_iter_loop(&iter, "(u@a{sv})", &portal->pipewireNode, &stream_properties);
qInfo() << "Monitor selected, setting up screencast\n";
g_variant_unref(result);
g_variant_unref(streams);
g_variant_unref(stream_properties);
portal->openPipewireRemote();
}
int
ScreenCastPortal::callDBusMethod(const gchar* method_name, GVariant* parameters)
{
GVariant* result;
GError* error = NULL;
result = g_dbus_proxy_call_sync(proxy,
method_name,
parameters,
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
&error);
if (error) {
qWarning() << "Call to DBus method" << method_name << "failed:" << error->message;
g_error_free(error);
return EIO;
}
g_variant_unref(result);
return 0;
}
void
ScreenCastPortal::start()
{
int ret;
const char* request_token;
g_autofree char* request_path;
GVariantBuilder builder;
GVariant* parameters;
struct DbusCallData* ptr_dbus_call_data;
request_token = "pipewiregrabStart";
request_path = g_strdup_printf(REQUEST_PATH, sender_name, request_token);
qInfo() << "Asking for monitor...";
ptr_dbus_call_data = subscribeToSignal(request_path, onStartResponseReceivedCallback);
if (!ptr_dbus_call_data) {
abort(ENOMEM, "Failed to allocate DBus call data");
return;
}
g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
g_variant_builder_add(&builder, "{sv}", "handle_token", g_variant_new_string(request_token));
parameters = g_variant_new("(osa{sv})", session_handle, "", &builder);
ret = callDBusMethod("Start", parameters);
if (ret != 0)
abort(ret, "Failed to start screen cast session");
}
void
ScreenCastPortal::onSelectSourcesResponseReceivedCallback(GDBusConnection* connection,
const char* sender_name,
const char* object_path,
const char* interface_name,
const char* signal_name,
GVariant* parameters,
gpointer user_data)
{
GVariant* ret = NULL;
uint32_t response;
struct DbusCallData* ptr_dbus_call_data = (DbusCallData*) user_data;
ScreenCastPortal* portal = ptr_dbus_call_data->portal;
g_clear_pointer(&ptr_dbus_call_data, dbusCallDataFree);
g_variant_get(parameters, "(u@a{sv})", &response, &ret);
g_variant_unref(ret);
if (response) {
portal->abort(EACCES, "Failed to select screencast sources, denied or cancelled by user");
return;
}
portal->start();
}
void
ScreenCastPortal::selectSources()
{
int ret;
const char* request_token;
g_autofree char* request_path;
GVariantBuilder builder;
GVariant* parameters;
struct DbusCallData* ptr_dbus_call_data;
request_token = "pipewiregrabSelectSources";
request_path = g_strdup_printf(REQUEST_PATH, sender_name, request_token);
ptr_dbus_call_data = subscribeToSignal(request_path, onSelectSourcesResponseReceivedCallback);
if (!ptr_dbus_call_data) {
abort(ENOMEM, "Failed to allocate DBus call data");
return;
}
g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
g_variant_builder_add(&builder, "{sv}", "types", g_variant_new_uint32(capture_type));
g_variant_builder_add(&builder, "{sv}", "multiple", g_variant_new_boolean(FALSE));
g_variant_builder_add(&builder, "{sv}", "handle_token", g_variant_new_string(request_token));
if ((available_cursor_modes & PORTAL_CURSOR_MODE_EMBEDDED) && draw_mouse)
g_variant_builder_add(&builder,
"{sv}",
"cursor_mode",
g_variant_new_uint32(PORTAL_CURSOR_MODE_EMBEDDED));
else
g_variant_builder_add(&builder,
"{sv}",
"cursor_mode",
g_variant_new_uint32(PORTAL_CURSOR_MODE_HIDDEN));
parameters = g_variant_new("(oa{sv})", session_handle, &builder);
ret = callDBusMethod("SelectSources", parameters);
if (ret != 0)
abort(ret, "Failed to select sources for screen cast session");
}
void
ScreenCastPortal::onCreateSessionResponseReceivedCallback(GDBusConnection* connection,
const char* sender_name,
const char* object_path,
const char* interface_name,
const char* signal_name,
GVariant* parameters,
gpointer user_data)
{
uint32_t response;
GVariant* result = NULL;
DbusCallData* ptr_dbus_call_data = (DbusCallData*) user_data;
ScreenCastPortal* portal = ptr_dbus_call_data->portal;
g_clear_pointer(&ptr_dbus_call_data, dbusCallDataFree);
g_variant_get(parameters, "(u@a{sv})", &response, &result);
if (response != 0) {
g_variant_unref(result);
portal->abort(EACCES, "Failed to create screencast session, denied or cancelled by user");
return;
}
qDebug() << "Screencast session created";
g_variant_lookup(result, "session_handle", "s", &portal->session_handle);
g_variant_unref(result);
portal->selectSources();
}
void
ScreenCastPortal::createSession()
{
int ret;
GVariantBuilder builder;
GVariant* parameters;
const char* request_token;
g_autofree char* request_path;
DbusCallData* ptr_dbus_call_data;
request_token = "pipewiregrabCreateSession";
request_path = g_strdup_printf(REQUEST_PATH, sender_name, request_token);
ptr_dbus_call_data = subscribeToSignal(request_path, onCreateSessionResponseReceivedCallback);
if (!ptr_dbus_call_data) {
abort(ENOMEM, "Failed to allocate DBus call data");
return;
}
g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
g_variant_builder_add(&builder, "{sv}", "handle_token", g_variant_new_string(request_token));
g_variant_builder_add(&builder,
"{sv}",
"session_handle_token",
g_variant_new_string("pipewiregrab"));
parameters = g_variant_new("(a{sv})", &builder);
ret = callDBusMethod("CreateSession", parameters);
if (ret != 0)
abort(ret, "Failed to create screen cast session");
}
/*
* Helper function: get available cursor modes and update the
* PipewireGrabContext accordingly
*/
void
ScreenCastPortal::updateAvailableCursorModes()
{
GVariant* cached_cursor_modes = NULL;
cached_cursor_modes = g_dbus_proxy_get_cached_property(proxy, "AvailableCursorModes");
available_cursor_modes = cached_cursor_modes ? g_variant_get_uint32(cached_cursor_modes) : 0;
// Only use embedded or hidden mode for now
available_cursor_modes &= PORTAL_CURSOR_MODE_EMBEDDED | PORTAL_CURSOR_MODE_HIDDEN;
g_variant_unref(cached_cursor_modes);
}
int
ScreenCastPortal::createDBusProxy()
{
GError* error = NULL;
proxy = g_dbus_proxy_new_sync(connection,
G_DBUS_PROXY_FLAGS_NONE,
NULL,
"org.freedesktop.portal.Desktop",
"/org/freedesktop/portal/desktop",
"org.freedesktop.portal.ScreenCast",
NULL,
&error);
if (error) {
qWarning() << "Error creating proxy:" << error->message;
g_error_free(error);
return EPERM;
}
return 0;
}
/*
* Create DBus connection and related objects
*/
int
ScreenCastPortal::createDBusConnection()
{
char* aux;
GError* error = NULL;
connection = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &error);
if (error) {
qWarning() << "Error getting session bus:" << error->message;
g_error_free(error);
return EPERM;
}
sender_name = g_strdup(g_dbus_connection_get_unique_name(connection) + 1);
while ((aux = g_strstr_len(sender_name, -1, ".")) != NULL)
*aux = '_';
return 0;
}
/*
* Use XDG Desktop Portal's ScreenCast interface to open a file descriptor that
* can be used by PipeWire to access the screen cast streams.
* (https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.ScreenCast.html)
*/
int
ScreenCastPortal::getPipewireFd()
{
int ret = 0;
GMainContext* glib_main_context;
// Create a new GLib context and set it as the default for the current thread.
// This ensures that the callbacks from DBus operations started in this thread are
// handled by the GLib main loop defined below, even if pipewiregrab_init was
// called by a program which also uses GLib and already had its own main loop running.
glib_main_context = g_main_context_new();
g_main_context_push_thread_default(glib_main_context);
glib_main_loop = g_main_loop_new(glib_main_context, FALSE);
if (!glib_main_loop) {
qWarning() << "g_main_loop_new failed!";
ret = ENOMEM;
}
ret = createDBusConnection();
if (ret != 0)
goto exit_glib_loop;
ret = createDBusProxy();
if (ret != 0)
goto exit_glib_loop;
updateAvailableCursorModes();
createSession();
if (portal_error) {
ret = portal_error;
goto exit_glib_loop;
}
g_main_loop_run(glib_main_loop);
// The main loop will run until it's stopped by openPipewireRemote (if
// all DBus method calls were successfully), abort (in case of error) or
// on_cancelled_callback (if a DBus request is cancelled).
// In the latter two cases, pw_ctx->portal_error gets set to a nonzero value.
if (portal_error)
ret = portal_error;
exit_glib_loop:
g_main_loop_unref(glib_main_loop);
glib_main_loop = NULL;
g_main_context_pop_thread_default(glib_main_context);
g_main_context_unref(glib_main_context);
return ret;
}
ScreenCastPortal::ScreenCastPortal(PortalCaptureType captureType)
: draw_mouse(true)
, pipewireFd(0)
{
switch (captureType) {
case PortalCaptureType::SCREEN:
capture_type = 1;
break;
case PortalCaptureType::WINDOW:
capture_type = 2;
break;
}
}
ScreenCastPortal::~ScreenCastPortal()
{
if (session_handle) {
g_dbus_connection_call(connection,
"org.freedesktop.portal.Desktop",
session_handle,
"org.freedesktop.portal.Session",
"Close",
NULL,
NULL,
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
NULL,
NULL);
g_clear_pointer(&session_handle, g_free);
}
g_clear_object(&connection);
g_clear_object(&proxy);
g_clear_pointer(&sender_name, g_free);
#ifndef ENABLE_LIBWRAP
// If the daemon is running as a separate process, then it can't directly use the
// PipeWire file descriptor opened by the client, so it will have to duplicate it.
// The duplicated file descriptor will be closed by the daemon, but the original
// file descriptor needs to be closed by the client.
if (close(pipewireFd) != 0) {
int err = errno;
qWarning() << "Error while attempting to close PipeWire file descriptor: errno =" << err;
} else {
qInfo() << "Successfully closed PipeWire file descriptor";
}
#endif
}

102
src/app/screencastportal.h Normal file
View File

@ -0,0 +1,102 @@
/*!
* Copyright (C) 2024 Savoir-faire Linux Inc.
*
* 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/>.
*/
#pragma once
#include <QString>
#include <cstdint>
#include <gio/gio.h>
#include <gio/gunixfdlist.h>
enum class PortalCaptureType {
SCREEN = 1,
WINDOW = 2,
};
struct DbusCallData;
class ScreenCastPortal
{
public:
ScreenCastPortal(PortalCaptureType captureType);
~ScreenCastPortal();
int getPipewireFd();
int pipewireFd;
uint32_t pipewireNode = 0;
QString videoInputId;
private:
void createSession();
void selectSources();
void start();
void openPipewireRemote();
void abort(int error, const char* message);
static void onCreateSessionResponseReceivedCallback(GDBusConnection* connection,
const char* sender_name,
const char* object_path,
const char* interface_name,
const char* signal_name,
GVariant* parameters,
gpointer user_data);
static void onSelectSourcesResponseReceivedCallback(GDBusConnection* connection,
const char* sender_name,
const char* object_path,
const char* interface_name,
const char* signal_name,
GVariant* parameters,
gpointer user_data);
static void onStartResponseReceivedCallback(GDBusConnection* connection,
const char* sender_name,
const char* object_path,
const char* interface_name,
const char* signal_name,
GVariant* parameters,
gpointer user_data);
int callDBusMethod(const gchar* method_name, GVariant* parameters);
int createDBusProxy();
int createDBusConnection();
void updateAvailableCursorModes();
DbusCallData* subscribeToSignal(const char* path, GDBusSignalCallback callback);
static void dbusCallDataFree(DbusCallData* ptr_dbus_call_data);
GDBusConnection* connection = nullptr;
GDBusProxy* proxy = nullptr;
char* sender_name = nullptr;
char* session_handle = nullptr;
uint32_t available_cursor_modes = 0;
GMainLoop* glib_main_loop = nullptr;
struct pw_thread_loop* thread_loop = nullptr;
struct pw_context* context = nullptr;
guint32 capture_type;
bool draw_mouse;
int portal_error = 0;
};
struct DbusCallData
{
ScreenCastPortal* portal;
char* request_path;
guint signal_id;
};

View File

@ -18,6 +18,8 @@
#include "screensaver.h"
#include "global.h"
#include <QDebug>
ScreenSaver::ScreenSaver(QObject* parent)
@ -31,8 +33,7 @@ ScreenSaver::ScreenSaver(QObject* parent)
}
#else
: QObject(parent)
{
}
{}
#endif
#ifdef Q_OS_LINUX
@ -50,7 +51,7 @@ ScreenSaver::createInterface(void)
services_[i],
sessionBus_);
if (screenSaverInterface_ && screenSaverInterface_->isValid()) {
qDebug() << "Screen saver dbus interface: " << services_[i];
C_INFO << "Screen saver dbus interface: " << services_[i];
return true;
}
}

View File

@ -53,7 +53,7 @@ BaseModalDialog {
pinRectangle.visible = true
exportedPIN.text = pin;
} else {
pinRectangle.success = false;
infoLabel.success = false;
infoLabel.visible = true;
switch (status) {
case NameDirectory.ExportOnRingStatus.WRONG_PASSWORD:
@ -274,7 +274,7 @@ BaseModalDialog {
height: qrImage.height + 4
anchors.centerIn: parent
radius: 5
color: JamiTheme.darkTheme ? JamiTheme.whiteColor : JamiTheme.jamiButtonBorderColor
color: JamiTheme.whiteColor
Image {
id: qrImage
anchors.centerIn: parent

View File

@ -101,8 +101,7 @@ SettingsPageBase {
id: closeOrMinCheckBox
Layout.fillWidth: true
visible: UtilsAdapter.isSystemTrayIconVisible()
checked: UtilsAdapter.getAppValue(Settings.MinimizeOnClose) && UtilsAdapter.isSystemTrayIconVisible()
checked: UtilsAdapter.getAppValue(Settings.MinimizeOnClose)
labelText: JamiStrings.keepMinimized
onSwitchToggled: UtilsAdapter.setAppValue(Settings.Key.MinimizeOnClose, checked)
}

View File

@ -80,13 +80,10 @@ SettingsPageBase {
Component.onCompleted: {
flipControl.checked = UtilsAdapter.getAppValue(Settings.FlipSelf);
hardwareAccelControl.checked = AvAdapter.getHardwareAcceleration();
if (previewWidget.visible)
startPreviewing(true);
startPreviewing(true);
}
Component.onDestruction: {
previewWidget.startWithId("");
}
Component.onDestruction: previewWidget.stop()
// video Preview
Rectangle {

View File

@ -19,6 +19,7 @@
#include "systemtray.h"
#include "appsettingsmanager.h"
#include "global.h"
#ifdef USE_LIBNOTIFY
#include <libnotify/notification.h>
@ -106,8 +107,8 @@ SystemTray::SystemTray(AppSettingsManager* settingsManager, QObject* parent)
char* spec = nullptr;
if (notify_get_server_info(&name, &vendor, &version, &spec)) {
qDebug() << QString("notify server name: %1, vendor: %2, version: %3, spec: %4")
.arg(name, vendor, version, spec);
C_INFO << QString("notify server name: %1, vendor: %2, version: %3, spec: %4")
.arg(name, vendor, version, spec);
}
// check notify server capabilities
@ -167,7 +168,7 @@ SystemTray::hideNotification(const QString& id)
// Close
GError* error = nullptr;
if (!notify_notification_close(notification->second.nn.get(), &error)) {
qWarning("could not close notification: %s", error->message);
C_WARN << QString("could not close notification: %1").arg(error->message);
g_clear_error(&error);
return false;
}
@ -235,7 +236,7 @@ SystemTray::showNotification(const QString& id,
GError* error = nullptr;
notify_notification_show(notification.get(), &error);
if (error) {
qWarning("failed to show notification: %s", error->message);
C_WARN << QString("failed to show notification: %1").arg(error->message);
g_clear_error(&error);
}
#else

View File

@ -24,6 +24,7 @@
#include "jamiavatartheme.h"
#include "lrcinstance.h"
#include "global.h"
#include <api/contact.h>
@ -419,8 +420,14 @@ Utils::contactPhoto(LRCInstance* instance,
try {
auto& accInfo = instance->accountModel().getAccountInfo(
accountId.isEmpty() ? instance->get_currentAccountId() : accountId);
auto contactInfo = accInfo.contactModel->getContact(contactUri);
auto contactPhoto = accInfo.contactModel->avatar(contactUri);
if (!contactPhoto.isEmpty()) {
photo = imageFromBase64String(contactPhoto);
if (!photo.isNull())
return Utils::scaleAndFrame(photo, size);
}
// If no avatar is found, generate one
auto contactInfo = accInfo.contactModel->getContact(contactUri);
auto bestName = accInfo.contactModel->bestNameForContact(contactUri);
if (accInfo.profileInfo.type == profile::Type::SIP
&& contactInfo.profileInfo.type == profile::Type::TEMPORARY) {
@ -428,12 +435,6 @@ Utils::contactPhoto(LRCInstance* instance,
} else if (contactInfo.profileInfo.type == profile::Type::TEMPORARY
&& contactInfo.profileInfo.uri.isEmpty()) {
photo = Utils::fallbackAvatar(QString(), QString());
} else if (!contactPhoto.isEmpty()) {
photo = imageFromBase64String(contactPhoto);
if (photo.isNull()) {
auto avatarName = contactInfo.profileInfo.uri == bestName ? QString() : bestName;
photo = Utils::fallbackAvatar("jami:" + contactInfo.profileInfo.uri, avatarName);
}
} else {
auto avatarName = contactInfo.profileInfo.uri == bestName ? QString() : bestName;
photo = Utils::fallbackAvatar("jami:" + contactInfo.profileInfo.uri, avatarName);
@ -488,7 +489,7 @@ Utils::conversationAvatar(LRCInstance* instance,
painter.drawImage(avatar.rect(), peerAAvatar);
painter.drawImage(avatar.rect(), peerBAvatar);
} catch (const std::exception& e) {
qDebug() << Q_FUNC_INFO << e.what();
C_DBG << e.what();
}
return avatar;
}
@ -539,13 +540,20 @@ Utils::getCirclePhoto(const QImage original, int sizePhoto)
Qt::KeepAspectRatioByExpanding,
Qt::SmoothTransformation)
.convertToFormat(QImage::Format_ARGB32_Premultiplied);
int margin = 0;
int marginX = 0;
int marginY = 0;
if (scaledPhoto.width() > sizePhoto) {
margin = (scaledPhoto.width() - sizePhoto) / 2;
marginX = (scaledPhoto.width() - sizePhoto) / 2;
}
if (scaledPhoto.height() > sizePhoto) {
marginY = (scaledPhoto.height() - sizePhoto) / 2;
}
painter.drawEllipse(0, 0, sizePhoto, sizePhoto);
painter.setCompositionMode(QPainter::CompositionMode_SourceIn);
painter.drawImage(0, 0, scaledPhoto, margin, 0);
painter.drawImage(0, 0, scaledPhoto, marginX, marginY);
return target;
}

View File

@ -26,6 +26,7 @@
#include "systemtray.h"
#include "utils.h"
#include "version.h"
#include "global.h"
#include <api/datatransfermodel.h>
#include <api/contact.h>
@ -229,7 +230,7 @@ UtilsAdapter::getConvIdForUri(const QString& accountId, const QString& uri)
return {};
return convInfo->get().uid;
} catch (const std::out_of_range& e) {
qDebug() << e.what();
C_DBG << e.what();
return "";
}
}
@ -242,7 +243,7 @@ UtilsAdapter::getPeerUri(const QString& accountId, const QString& uid)
const auto& convInfo = convModel->getConversationForUid(uid).value();
return convInfo.get().participants.front().uri;
} catch (const std::out_of_range& e) {
qDebug() << e.what();
C_DBG << e.what();
return "";
}
}
@ -745,7 +746,7 @@ UtilsAdapter::isSystemThemeDark()
#endif
return readAppsUseLightThemeRegistry(true);
#else
qWarning("System theme detection is not implemented or is not supported");
C_WARN << "System theme detection is not implemented or is not supported";
return false;
#endif // WIN32
#endif // __has_include(<gio/gio.h>)
@ -827,13 +828,7 @@ UtilsAdapter::isRTL()
bool
UtilsAdapter::isSystemTrayIconVisible()
{
if (!systemTray_)
return false;
// https://bugreports.qt.io/browse/QTBUG-118656
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
return true;
#endif
return systemTray_->geometry() != QRect();
return QSystemTrayIcon::isSystemTrayAvailable() && systemTray_;
}
QString
@ -891,7 +886,7 @@ UtilsAdapter::createDummyImage() const
qInfo() << "Dummy image created" << QDir::tempPath() + "/dummy.png";
return QDir::tempPath() + "/dummy.png";
} else {
qWarning() << "Could not create dummy image";
C_WARN << "Could not create dummy image";
return "";
}
}

View File

@ -18,7 +18,7 @@
#include "videodevices.h"
#include "api/devicemodel.h"
#include "global.h"
VideoInputDeviceModel::VideoInputDeviceModel(LRCInstance* lrcInstance,
VideoDevices* videoDeviceInstance)
@ -256,13 +256,14 @@ VideoDevices::startDevice(const QString& id, bool force)
void
VideoDevices::stopDevice(const QString& id)
{
if (!id.isEmpty()) {
qInfo() << "Stopping device" << id;
if (lrcInstance_->avModel().stopPreview(id)) {
deviceOpen_ = false;
} else {
qWarning() << "Failed to stop device" << id;
}
if (id.isEmpty()) {
return;
}
if (lrcInstance_->avModel().stopPreview(id)) {
deviceOpen_ = false;
} else {
C_DBG << "Failed to stop device" << id;
}
}

View File

@ -149,6 +149,8 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
endif()
if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
# Remove warnings related to `import`.
add_definitions(-Wno-import-preprocessor-directive-pedantic)
set(CMAKE_MACOSX_RPATH on)
set(CMAKE_SKIP_BUILD_RPATH false)
set(CMAKE_BUILD_WITH_INSTALL_RPATH false)
@ -273,6 +275,7 @@ set(LIBCLIENT_SOURCES
# other
avmodel.cpp
pluginmodel.cpp
interaction.cpp
namedirectory.cpp
renderer.cpp)

View File

@ -166,7 +166,6 @@ public Q_SLOTS:
* @param ok
*/
void slotMigrationEnded(const QString& accountId, bool ok);
/**
* Emit accountProfileReceived
* @param accountId
@ -176,7 +175,6 @@ public Q_SLOTS:
void slotAccountProfileReceived(const QString& accountId,
const QString& displayName,
const QString& userPhoto);
/**
* Emit new position
* @param accountId
@ -415,10 +413,6 @@ AccountModelPimpl::AccountModelPimpl(AccountModel& linked,
&CallbacksHandler::migrationEnded,
this,
&AccountModelPimpl::slotMigrationEnded);
connect(&callbacksHandler,
&CallbacksHandler::accountProfileReceived,
this,
&AccountModelPimpl::slotAccountProfileReceived);
connect(&callbacksHandler,
&CallbacksHandler::newPosition,
this,
@ -434,8 +428,9 @@ AccountModelPimpl::updateAccounts()
const auto previousAccountIdListSize = accountIdList.size();
accountIdList = configurationManager.getAccountList();
// If this is just a reordering of the accounts, don't do anything
// If this is just a reordering of the accounts, just notify the view.
if (accountIdList.size() == previousAccountIdListSize) {
Q_EMIT linked.accountsReordered();
return;
}
@ -531,11 +526,12 @@ AccountModelPimpl::slotAccountStatusChanged(const QString& accountID,
if (status != api::account::Status::INITIALIZING
&& accountInfo.status == api::account::Status::INITIALIZING) {
// Detect when a new account is generated (keys are ready). During
// the generation, a Ring account got the "INITIALIZING" status.
// the generation, an account got the "INITIALIZING" status.
// When keys are generated, the status will change.
// The account is already added and initialized. Just update details from daemon
updateAccountDetails(accountInfo);
// This will load swarms as the account was not loaded before.
accountInfo.contactModel->initContacts();
accountInfo.conversationModel->initConversations();
Q_EMIT linked.accountAdded(accountID);
} else if (!accountInfo.profileInfo.uri.isEmpty()) {
@ -690,25 +686,6 @@ AccountModelPimpl::slotMigrationEnded(const QString& accountId, bool ok)
Q_EMIT linked.migrationEnded(accountId, ok);
}
void
AccountModelPimpl::slotAccountProfileReceived(const QString& accountId,
const QString& displayName,
const QString& userPhoto)
{
LC_WARN << accountId << displayName;
auto account = accounts.find(accountId);
if (account == accounts.end())
return;
auto& accountInfo = account->second.first;
accountInfo.profileInfo.avatar = userPhoto;
accountInfo.profileInfo.alias = displayName;
storage::vcard::setProfile(accountInfo.id, accountInfo.profileInfo);
Q_EMIT linked.profileUpdated(accountId);
}
void
AccountModelPimpl::slotNewPosition(const QString& accountId,
const QString& peerId,
@ -719,6 +696,24 @@ AccountModelPimpl::slotNewPosition(const QString& accountId,
Q_EMIT linked.newPosition(accountId, peerId, body, timestamp, daemonId);
}
void
AccountModelPimpl::slotAccountProfileReceived(const QString& accountId,
const QString& displayName,
const QString& userPhoto)
{
LC_WARN << accountId << displayName;
auto account = accounts.find(accountId);
if (account == accounts.end() || userPhoto.isEmpty())
return;
// NOTE: This signal is still used for JAMS account where the avatar is
// retrieven from the server. In this case we MUST save it.
auto& accountInfo = account->second.first;
accountInfo.profileInfo.avatar = userPhoto;
accountInfo.profileInfo.alias = displayName;
storage::vcard::setProfile(accountInfo.id, accountInfo.profileInfo);
Q_EMIT linked.profileUpdated(accountId);
}
void
AccountModelPimpl::addToAccounts(const QString& accountId)
{

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