Compare commits

..

328 commits

Author SHA1 Message Date
Peio Rigaux
1156bd78c0 Reduce rights of uploaded files 2026-01-15 12:19:56 +01:00
Ghislain MARY
befa4a2635 Add support of Jabra headsets via the hidapi library. 2026-01-06 17:27:11 +01:00
Christophe Deschamps
0cf3938dc3 Move Call Forward under the save scope in settings 2025-12-17 18:49:47 +01:00
Christophe Deschamps
c3b160ec3e Move Codecs under the save scope in settings 2025-12-17 18:49:41 +01:00
Christophe Deschamps
dc1ec216e8 Move FPS under the save scope in settings + fix the save popup showing when not saved 2025-12-17 18:49:35 +01:00
Christophe Deschamps
0bfa29dc55 Move AutoStart under the save scope in settings 2025-12-17 18:49:30 +01:00
Christophe Deschamps
0df7065b5e Move IPV6 under the save scope in settings 2025-12-17 18:49:23 +01:00
Gaelle Braud
6d9b5efcc5 hide current call in transfer call list #LINQT-2256 2025-12-15 15:58:22 +01:00
Christophe Deschamps
50ec67298e Update unread count when unread incoming message is retracted 2025-12-12 11:04:00 +01:00
Christophe Deschamps
1bae93aab5 Chat message edition 2025-12-11 15:41:40 +01:00
Christophe Deschamps
d40045d5bb Chat message retraction 2025-12-10 19:20:16 +00:00
Christophe Deschamps
13ec790648 Fix - when accessing and existing settings making no changes do not show Save? popup 2025-12-10 09:52:32 +01:00
gaelle
528dc1e2bd fix macos ci (use flat runner for SDK 5.5) 2025-12-09 14:28:09 +01:00
Julien Wadel
31726b46cd Fix unfound Microsoft runtimes while installing by removing the set on MSVC_VERSION that should be already set. 2025-12-09 12:48:55 +01:00
Sylvain Berfini
876fdbe619 Updated version code in CMakeLists to fix build since 6.2.0-alpha tag 2025-12-04 18:26:07 +01:00
Christophe Deschamps
e849891548 Update sdk 2025-12-04 16:16:06 +01:00
Christophe Deschamps
c672762b63 Option to set CCMP server URL in account settings 2025-12-04 15:41:59 +01:00
Christophe Deschamps
e23a49fbd3 Replace core.removeAccount by core.removeAccountWithData() 2025-12-04 15:40:41 +01:00
Christophe Deschamps
a9a1249ecd Add button to export ICS of meeting in meetings and chat views 2025-12-04 15:36:42 +01:00
Christophe Deschamps
ed57ec1ea5 Enable e2e encryption in scheduled conf chat rooms 2025-12-04 15:32:47 +01:00
gaelle
d4c1387c43 Merge branch 'release/6.1' 2025-12-04 14:34:47 +01:00
gaelle
88aea7ba25 update SDK to 5.4.67 2025-12-04 14:14:42 +01:00
Gaelle Braud
605ffdcdd5 refresh register when click on account deactivated status 2025-12-02 17:17:43 +01:00
Gaelle Braud
21e8e2aaba ChatCore: wait for deleted state before emitting deleted signal
fix chats selection and remove useless signal
2025-12-02 17:17:43 +01:00
Sylvain Berfini
60517741a2 CMakeLists changes regarding app name & windows debug symbols 2025-12-02 12:36:16 +00:00
Gaelle Braud
5a90959125 separate enable camera and video and use enableCamera when user activates webcam (fix #LINQT-2216) 2025-12-02 12:38:35 +01:00
Gaelle Braud
c8428d6ade fix automatic presence when app focus changes #LINQT-2172 2025-12-02 11:31:18 +01:00
Gaelle Braud
b2ae9213a2 force enableAudio/Video to false when creating chatroom params to avoid sdk warning 2025-12-01 17:00:43 +01:00
Gaelle Braud
d39a84ca4e update SDK to 5.4.65 2025-12-01 10:35:22 +01:00
Gaelle Braud
dfc88b7657 Only create a chatroom for the current call when the chat button is pressed (fix #LINQT-2228) 2025-12-01 10:35:03 +01:00
Gaelle Braud
f405754e24 FIXES:
auto switch to away/online status when app is inactive/active #LINQT-2172

fix loading ui for chats/meetings lists

use uri only for account uris to remove the < and >
2025-11-28 15:24:29 +01:00
Gaelle Braud
cca8db9055 only switch automatically to main page when account added and app is being restarted (fix #LINQT-2224) 2025-11-27 11:32:42 +01:00
Gaelle Braud
8ee8058065 wait for chat room to receive Created state callback before selecting it #LINQT-2226 2025-11-27 10:08:18 +01:00
Gaelle Braud
96b20f42e2 let the sdk check if registrar uri and outbound proxy uri are valid instead of using regex #LINQT-2227 2025-11-27 09:11:14 +01:00
gaelle
5bbffa79d8 fix crash when account null
fix notification display if max instances reached
2025-11-26 16:53:35 +01:00
Gaelle Braud
e3edeb1bcf update SDK to 5.4.62 2025-11-26 15:50:58 +01:00
Gaelle Braud
7c2e9f6c12 fix binding loop on popup button closed
remove debug

do not force declining call when user in do not disturb status #LINQT-2129 #LINQT-2171

do not handle chat notifications when chat disabled

change transfer direction (transfer paused call to current) #LINQT-2211

fix chat message image size in call #LINQT-2142

update translations

fix crash

remove auto switch audio device to avoid binding loop
2025-11-26 15:50:58 +01:00
Gaelle Braud
ca4bdd3736 remove debug 2025-11-26 10:19:51 +01:00
Gaelle Braud
251f711250 fix third party connection #LINQT-2180 #LINQT-2181 2025-11-26 09:25:04 +01:00
Gaelle Braud
37db390d5c fix chat list view and really fix chat selection after filter reset #LINQT-2199 2025-11-25 09:52:44 +01:00
Gaelle Braud
04d2744bf2 fix force adding chat to list when creating it 2025-11-24 19:40:13 +01:00
Gaelle Braud
a7ba374b8f improve chat messages list view
fix auto scroll when receiving new message #LINQT-2154
2025-11-24 19:11:38 +01:00
Gaelle Braud
29691485bf hide conference joined/left events if not in a chat group #LINQT-2146 2025-11-24 15:17:34 +01:00
Gaelle Braud
735c473b3c add unread notification count on class window chat button #LINQT-2138
reset last active window if destroyed
2025-11-21 16:15:41 +01:00
Gaelle Braud
bba3edd4b6 fix check for update root url #LINQT-2177 2025-11-21 16:15:39 +01:00
Gaelle Braud
514c337192 fix chatroom selection when filter change #LINQT-2199 2025-11-21 14:02:06 +01:00
gaelle.braud
fea9a1b7be fix binding loop in getLastFocusableItemInItem (fix #LINQT-2210) 2025-11-21 14:01:40 +01:00
gaelle
afd3514965 hide chat button if not supported for conference #LINQT-2192 2025-11-21 10:56:48 +01:00
gaelle
3f5797f453 to fix : meeting detail ui
fix meeting detail view #LINQT-2193
2025-11-20 17:55:06 +01:00
gaelle
db5f6dc2af fix thumbnail path initialized with \\ when sent from Windows device 2025-11-20 15:21:04 +01:00
gaelle
d0cf951fe4 fix thumbnail download paths leading to wrong thumbnail path and thumbnail not displayed in message when multiple files sent
remove qt5compat dependency README
2025-11-19 14:39:47 +01:00
gaelle.braud
b802fec33c hide chatroom actions if user left group #LINQT-2189
hide new group chat button if no conference factory uri defined for the account #LINQT-2178
2025-11-18 16:08:17 +01:00
Julien Wadel
3c264fd3ee Set camera state on call creation in order to avoid using not wanted state while updating the call.
Cause: toggling screensharing will activate the camera if the call is a video call.
2025-11-18 11:27:59 +01:00
Gaelle Braud
72e32ec160 fix third party connection #LINQT-2180 #LINQT-2181 2025-11-18 08:33:13 +01:00
Gaelle Braud
bfbafab84b fix verification when trying to connect to an account that is already connected 2025-11-17 17:45:20 +01:00
Gaelle Braud
7a4adbcbb4 remove loader from image so the icons are not reloaded each time a list layout change
remove button color warning
2025-11-17 17:30:03 +01:00
Gaelle Braud
fa3ef0b1a8 try to fix crash when ChatCore destroyed and connection with model is still alive 2025-11-17 15:22:16 +01:00
Gaelle Braud
2fc4439e16 Disable notifications if Offline status #LINQT-2173 2025-11-17 10:56:57 +01:00
Gaelle Braud
0b9a0ead2a fix screen sharing 2025-11-17 10:49:29 +01:00
Gaelle Braud
3870037905 update SDK to 5.4.60 2025-11-15 00:15:41 +01:00
Gaelle Braud
e113058ae9 hide group call button if no videoconference uri set
avoid displaying more messages when the list is not yet initialized

fix meeting form end hour combobox list display #LINQT-2127

check if at least one participant when creating group chat #LINQT-2134

display all the errors at a time if they all occur #LINQT-2133

add signal to add chat to list when state Created #LINQT-2131

fix chat deletion
2025-11-15 00:15:41 +01:00
Gaelle Braud
a4b38e4fd1 Fix crash by replacing unsafe reverse iterator loop in EventLogList (cherry pick 23875ce1) 2025-11-14 10:12:00 +01:00
Gaelle Braud
a0850b9967 fix #LINQT-2130 enable video in conference for the participant to be able to see shared screen without activating camera
do not display notification if message already read

display notif when copying account sip address #LINQT-2126

prevent adding our own address in participants (fix #LINQT-2164)
2025-11-13 17:43:55 +01:00
Andrea Gianarda
bc5022c8f5 Move include of application_info.cmake after setting LINPHONEAPP_APPLICATION_NAME because its value must be known to set the APPLICATION_NAME variable 2025-11-13 13:40:52 +00:00
Gaelle Braud
ac03de6663 update audio device when changed automatically in sdk 2025-11-13 12:51:39 +01:00
Gaelle Braud
58eb93d13b UI fixes :
display real error message when carddav sync fails

hide mark as read button if no unread message #LINQT-2144

fix screencast panel hidden while user is sharing screen #LINQT-2136

disable selection for cancelled meetings #LINQT-2121
2025-11-13 09:23:28 +01:00
Julien Wadel
11487b3aeb - Use of SDK master to prepare for 6.2 (SDK 5.5):
- Remove deprecations (Qt, LDAP, AudioDevice, Compose)
- Fix absolute paths that can be wrong with temporary binaries images like Appimage. This way rootca will target the packaged one.
- Remove some packaged path as they are already set by SDK (from a fix on its side).
- Remove duplicated rootca packaging.
2025-11-10 17:23:10 +01:00
Alexandre Jörgensen
ca73193f6c Fix accessibility screen reader:
* Error on login page not sayed #LINQT-2095
* Incorect translation of close button in french #LINQT-2094
2025-11-07 17:21:14 +00:00
Alexandre Jörgensen
f2e49f21b0 Correct typo weight #LINQT-2078 2025-11-07 16:55:05 +00:00
gaelle
a7c8e8db90 do not minimize app on Windows auto start (fix #BC-97) 2025-11-07 16:41:31 +01:00
Gaelle Braud
afc03daa22 fix admin rights modification #LINQT-2114 2025-11-07 15:51:35 +01:00
Sylvain Berfini
74b2cf299b Updated translations from Weblate 2025-11-07 10:31:05 +00:00
Gaelle Braud
fcdbcdc9c1 Set minimum required version to Qt6.10.0
Display Qt version in troubleshooting page
2025-11-07 11:11:35 +01:00
Gaelle Braud
3dbea1ccb2 display qt version in help page 2025-11-07 10:22:31 +01:00
gaelle.braud
d741e79e2e fix file preview display #LINQT-2098 2025-11-07 08:18:17 +00:00
Gaelle Braud
b3b40d6f99 use native player to open video files #LINQT-2116
hide e2e conf creation toggle #LINQT-2112
2025-11-07 08:18:17 +00:00
Gaelle Braud
d957fae94e fix message content download crash 2025-11-07 08:18:17 +00:00
Alexandre Jörgensen
e224f24e92 Update QT to 6.10.0 and increase security:
* Use QT 6.10.0
* Change invalidateFilter (deprecated) to beginFilterChange/endFilterChange
* Remove warning of presenceStatusItem
* Add changelog with this modification for 6.1.0

* Do not use anymore variables to build docker images. Qt versions are now hardcoded in images to allow multiple opensource Qt versions.
2025-11-06 16:33:34 +01:00
gaelle.braud
cef650fb6a update SDK to 5.4.57 2025-11-06 11:10:45 +01:00
Gaelle Braud
9e10d5e9bd change sip login advanced params order 2025-11-06 09:55:35 +01:00
Gaelle Braud
36c783c9e5 always show scrollbar when needed (fix #LINQT-2102) 2025-11-06 09:45:13 +01:00
Gaelle Braud
0d2e83a60d fix #LINQT-2086 disable video only when starting audio call 2025-11-06 09:45:13 +01:00
Gaelle Braud
c70426f770 fix crash (showing window while it is destroyed) (fix #LINQT-2077, #LINQT-2087, #LINQT-2110) 2025-11-06 09:11:53 +01:00
gaelle
0c87a8d94e change Qt minimum version to Qt6.9.0 #○LINQT-2099 2025-11-05 15:31:53 +01:00
Gaelle Braud
6f4e925766 fix #LINQT-2108 : do not use <= or >= in lessThan comparator (c++ restriction) 2025-11-05 11:05:38 +01:00
Gaelle Braud
99c2a6ddc6 fix #LINQT-1657 do not manipulate internal gains 2025-11-05 09:32:29 +01:00
Gaelle Braud
cbd91b868d update SDK to 5.4.56 2025-11-05 09:31:10 +01:00
Gaelle Braud
80bf126b23 check for update 2025-11-04 17:57:15 +01:00
Peio Rigaux
db1f04350a Fix syntax error in GNU/Linux CI deploy script 2025-10-31 10:03:44 +00:00
Julien Wadel
08f2292881 Remove Core5Compat dependency and use our own opacity implementation.
(cherry picked from commit 229fbe1713)
2025-10-30 17:12:40 +00:00
Julien Wadel
229fbe1713 Remove Core5Compat dependency and use our own opacity implementation. 2025-10-30 17:42:43 +01:00
Gaelle Braud
2d9f568e3d Fixes :
rename fr translation

set enableVideo to false if audio call #LINQT-2086

try to fix crash on macos when switching from call window to main window (#LINQT-2077 and #LINQT-2087)

set error message when not able to download attached file

fix remove chat from list
fix new chat connection after first message sent #LINQT-2090
clean code + invalidate() for Qt6.10 compatibility

fix translations
2025-10-30 17:07:15 +01:00
Simon Morlat
d4ce80f8c6 README.md mentions QT6 additional modules. 2025-10-30 11:13:45 +00:00
Gaelle Braud
eb9fa8aefe update German and French translations (Mantis + #LINQT-2092) 2025-10-30 11:40:17 +01:00
gaelle
cb63b1a112 set enableVideo to false if audio call #LINQT-2086 2025-10-27 18:34:10 +01:00
Gaelle Braud
b3135ea177 Fixes:
fix wrong thread function call

hide recordings button while not implemented

display error message if cannot retrieve remote provisioning
2025-10-27 14:37:31 +01:00
Sylvain Berfini
c0a150f3e1 Updated translations from Weblate 2025-10-27 10:24:07 +00:00
Gaelle Braud
8e10decd65 Fixes :
do not display notification when it comes from the currently displayed chat #LINQT-2079

do not create account model for each chat, too much listener sending signals

fix crash in ConferenceInfoList #LINQT-2080
fix warnings on endResetModel
fix messages added twice because of displayMore() function called when lUpdate is running
Plain text in sending text area
2025-10-24 17:08:51 +02:00
Gaelle Braud
55a54dc10e screen ratio in function 2025-10-23 12:39:16 +02:00
Peio Rigaux
11c586792d Allow switch between QT proprietary and opensource for same version. Updated docker image and CI scripts 2025-10-21 14:15:13 +02:00
Peio Rigaux
95d5345450 Factorized Linux scripts for build, packaging and upload to avoid duplication if distribution-specific files are copy-pasted 2025-10-21 14:15:11 +02:00
Peio Rigaux
9d3bd909ca Remove CI dead code (unused builds) 2025-10-21 14:11:56 +02:00
Peio Rigaux
300bf3e409 Using needs in place of dependencies, even for empty needs 2025-10-21 14:11:16 +02:00
Peio Rigaux
a74ef62634 Add QT proprietary versions for Linux to Dockerfile and docker image.
Add QT proprietary 5.15.14, 5.15.19, 6.8.1, 6.8.3 in Dockerfile.
Modified CI and dockerfile to use qtchooser, to keep the same behaviour without having to rely on a specific entrypoint.

Split opensource versions from proprietary versions by using a different directory.
2025-10-21 14:11:15 +02:00
Gaelle Braud
b17bc8cc27 Fixes:
fix get size with screen ratio function

fix chat sending area ui #LINQT-2068

print debug logs in linphone files for futur debugging

fix call history details ui when no video conference factory set

use remote name of each call if in local conference #LINQT-2058
2025-10-21 11:25:17 +02:00
Alexandre Jörgensen
7825646edd Add a all message read button to application task bar icon #LINQT-2072 2025-10-21 10:20:10 +02:00
Alexandre Jörgensen
4a05b727c6 Fix global unread notifications #LINQT-2024 2025-10-20 12:27:07 +02:00
Gaelle Braud
453d16250a Fixes :
Hybrid mode conference video #LINQT-1869

fix switch size

hide some chat header buttons if in call

fix call notification display name #LINQT-2075
2025-10-17 19:51:19 +02:00
Gaelle Braud
4e81981c07 Fixes:
try to remove ffmpeg dependency #LINQT-2015

Clean friends manager maps when restarting app as the linphone core will be reset (fix #LINQT-1933)

only show error message once when linphone restarts if remote configuration failed #LINQT-2030

auto update contact list on carddav synchronized (fix #LINQT-1983)
2025-10-17 14:00:46 +02:00
Sylvain Berfini
af373148e3 Fix linphone.desktop file not created/updated on Linux 2025-10-17 11:06:22 +02:00
Alexandre Jörgensen
5f292ad545 Correct translations 2025-10-16 12:56:34 +02:00
Alexandre Jörgensen
89b9d62297 Update SDK to 5.4.50 2025-10-16 12:03:50 +02:00
Alexandre Jörgensen
396bd56d50 Add keyboard shortcuts to accept/decline call 2025-10-16 10:47:47 +02:00
Gaelle Braud
97b1d11adb Fixes:
improve unencrypted conversations warning indicator #LINQT-2061

allow user to choose an address for sending message when multiple addresses in contact #LINQT-2054

verify friend has a core to avoid crash in liblinphone #LINQT-1933

wait for window to be active before calling markAsRead (fix #LINQT-2048)

fix button text color (fix #LINQT-1832)

change format for mkv #LINQT-2056

Registration : check phone number format #LINQT-2044

fix window closing even if a new call is started #LINQT-2055

display popup to delete meetings on right click in meeting list item (allow to delete canceled meetings which cannot be displayed in the right panel)
2025-10-15 17:25:00 +02:00
Alexandre Jörgensen
a9a78cb4bf Accessibility and code improvments:
* Normalize and correct linphone color
* Add border when user focus using keyboard navigation
* Correct some keyboard navigation
* Add accessibility screen reading to interactive elements except chat and meeting
2025-10-15 12:50:43 +02:00
Gaelle Braud
1f97112306 fix chat message research #LINQT-2047
try to fix persistent spinner on chat message state icon

remove medias section from contact page #LINQT-2060 #LINQT-2066
2025-10-13 17:34:02 +02:00
Gaelle Braud
82679ab997 use display name instead of address for security example so the magic search does not search this fake address
outboundproxy configuration #LINQT-2012

fix chat events order

fix image picto only when file does not exist #LINQT-2049/2059
2025-10-10 18:32:39 +02:00
Gaelle Braud
9fd686c2ed update SDK to 5.4.47 2025-10-07 16:07:01 +02:00
Gaelle Braud
c4db4d132d only load 20 elements each time in chat messages list to improve perf
fix image error ui
black rectangle on unstarted video

fix text edit loses focus when message sent (#LINQT-2009)

try to fix wrong message spinner icon #LINQT-2010

hide security parameters #LINQT-2019

fix open contextual menu component to open settings/help pages #LINQT-2021/2022

fix muted status not visible on local preview #LINQT-2023
2025-10-07 16:05:44 +02:00
Christophe Deschamps
7e5f037332 Fix wrong theme handling 2025-10-03 12:47:09 +02:00
Christophe Deschamps
89122ff92d Theme - picture in help/about view ui|theme_about_picture_url 2025-10-03 10:51:26 +02:00
Christophe Deschamps
295dbcb4c3 Theme colors via config ui|theme_main_color 2025-10-03 10:29:30 +02:00
Gaelle Braud
8a2e842cd7 enlarge video size if single file sent
wait for chat messages model to be reset before changing chat (fix #LINQT-2011)
2025-10-02 18:04:02 +02:00
Gaelle Braud
924224abc5 enlarge image size when single in message #LINQT-2008 2025-10-01 18:13:57 +02:00
Gaelle Braud
580819df3a "No information" if device last updated date is invalid #LINQT-1646
do not display group call in meeting list
display cancelled meetings

fix account audio devices

meeting description uncapitalize #LINQT-1892
2025-10-01 18:13:57 +02:00
Gaelle Braud
faa8f73230 Fixes:
fix active speaker preview visibility

fix display chat after creation #LINQT-2001
+ error message if creation fails

fix meeting list

reset conf layout to the previous one when stop sharing screen #LINQT-1952
2025-09-26 19:20:14 +02:00
Gaelle Braud
641a081e60 update sdk to 5.4.46 2025-09-25 17:14:33 +02:00
Gaelle Braud
c9d058e6e3 Fixes:
fix wrong thread cancel conf creation
fix login button ui

add banner screen sharing #LINQT-1951
auto close screen sharing panel when screen selected #LINQT-1949
disable sharing button while no screen/window selected #LINQT-1948

hide record button if chat in call #LINQT-1946

show video preview when alone in conf #LINQT-1922
2025-09-25 17:14:03 +02:00
Gaelle Braud
013ba607ec FIXES:
fix inifnite loop #LINQT-2002

display name: do not modify username ui if there is not display name set by a user
remove unused signals

fix contact info update chat list #LINQT-1992

send invitations in secured chatrooms #LINQT-1996

extend meeting detail height #LINQT-1999

scrollbar call history #LINQT-1998

do not sort chat list when filtering it #LINQT-2003

remove useless signal which refresh the view for ephemeral messages (fix #LINQT-1989)
2025-09-24 17:36:46 +02:00
Gaelle Braud
e901f0046b UI fixes:
button color #LINQT-1894
fix display name call history
2025-09-23 18:41:11 +02:00
Gaelle Braud
598b45bbf4 prevent cloning when not necessary 2025-09-23 16:29:27 +02:00
Gaelle Braud
285e6645f8 fix chat list slowness 2025-09-23 11:41:54 +02:00
Gaelle Braud
8452ecbdfd add id and proxy address for third part connection #LINQT-2000 2025-09-23 11:41:25 +02:00
gaelle.braud
5b7dc1bcc8 only retrieve audio devices instead of extended #LINQT-1968 2025-09-23 11:41:25 +02:00
gaelle.braud
77da7183f4 ui fixes
meeting creation/ sip login page margins, typography
2025-09-23 11:41:25 +02:00
Alexandre Jörgensen
4541ee3079 Update readme for build on MacOS and gitignore to use QtCreator 2025-09-18 14:46:56 +02:00
gaelle
4fca0a4140 fix time combo box interaction #LINQT-1913
fix arrow indicator on combobox #LINQT-1954
2025-09-16 17:33:42 +02:00
gaelle
23234bafc4 fix #LINQT-1994 wrong avatars
fix emoji initials for avatar
2025-09-16 17:33:42 +02:00
gaelle
73358b7a25 Conf info fixes :
set default videoconference factory uri

fix conf info message ui

fix conversation infos call action

always update all conf info on conf info received as we don't know which account is concerned

fix #LINQT-1980 display meeting detail

fix #LINQT-1986 force conferenceInfoReceived signal on meeting creation
fix do not add conference if state is cancelled
2025-09-16 17:33:40 +02:00
gaelle.braud
c007e975aa update sdk to 5.4.44 2025-09-15 17:55:28 +02:00
Gaelle Braud
97d8bd621e voice recording ui #LINQT-1990 2025-09-15 17:50:19 +02:00
Gaelle Braud
2560691c84 fix ldap property for friend (fix #LINQT-1974) 2025-09-15 17:50:19 +02:00
Gaelle Braud
5da7a9fd6b fix has file content chat message
only show address for suggestions

do not refresh devices if current account is null

fix crash

add error message on account parameters saved and apply changes on text changed instead of edited (fix #LINQT-1935)
fix disable meeting feature setting in wrong thread
destroy parameter page when closed (to avoid multiplied connections)

fix show/add contact in conversation info
2025-09-15 17:50:19 +02:00
Gaelle Braud
a819d134fb enable new ldap list by default #LINQT-1973 2025-09-15 14:39:12 +02:00
Gaelle Braud
097c48ebba change layout icon #LINQT-1991 2025-09-15 10:41:06 +02:00
Gaelle Braud
452ef79ac0 fix conference invitation message ui #LINQT-1979 2025-09-12 12:07:42 +02:00
Gaelle Braud
cdb23ce0a4 fix changing video layout conference #LINQT-1896 2025-09-12 12:07:42 +02:00
Gaelle Braud
2a57289e18 disable splitview handle if readonly chat
do not create notification if empty
2025-09-12 12:07:41 +02:00
Gaelle Braud
46f20a3dc1 update sdk to 5.4.43 2025-09-11 15:51:19 +02:00
Gaelle Braud
08c9b0daaa fix ui glitch conversation details panel #LINQT-1905 2025-09-11 15:45:49 +02:00
Gaelle Braud
f8119e607d fix last message text if file #LINQT-1976 2025-09-11 15:39:49 +02:00
Gaelle Braud
a3996fc33c fix get auto download for compatibility with previous versions of linphonerc 2025-09-11 14:40:22 +02:00
Gaelle Braud
3866d7ecdd scrollbar in calls window right panel for small screens #LINQT-1897 (TODO fix numeric pad view) 2025-09-11 14:40:22 +02:00
Gaelle Braud
123c9022ec update sdk to 5.4.42 2025-09-09 09:13:44 +02:00
Gaelle Braud
6c6cab6661 update ci file (add xinerama) 2025-09-08 16:32:46 +02:00
Gaelle Braud
c9065e1f12 add more error reasons call model 2025-09-05 10:03:59 +02:00
Gaelle Braud
251f404ced try to fix persistent call window 2025-09-04 21:06:18 +02:00
Gaelle Braud
bad52def4d scrollbar in meeting and conversation participant list #LINQT-1890 2025-09-03 17:17:54 +02:00
Gaelle Braud
12e1e51c0c fix #LINQT-1799 default avatar 2025-09-03 17:17:54 +02:00
Gaelle Braud
1f529f333e update sdk 4.4.41 2025-09-03 17:17:54 +02:00
Gaelle Braud
a457ac2c82 fix imdn status order in message info #LINQT-1970 2025-09-03 13:18:28 +02:00
Gaelle Braud
cbc9c5e2b9 fix double notification for messages 2025-09-03 12:05:23 +02:00
Gaelle Braud
d5841e45ec reset current chat when switching account (fix #LINQT-1971) 2025-09-03 11:38:20 +02:00
Gaelle Braud
d3d352948b fix #LINQT-1936 enable video and only change media direction to switch video in call 2025-09-03 11:09:39 +02:00
Gaelle Braud
f30d8f3d7f align right short local messages #LINQT-1975 2025-09-03 11:00:56 +02:00
Gaelle Braud
f974a78fb9 fix display name in call notification #LINQT-1928 2025-09-03 11:00:55 +02:00
Gaelle Braud
13ef034516 point to point encrypted conference #LINQT-1958 2025-09-02 17:04:16 +02:00
Gaelle Braud
1a4da6fb18 update conversation title on conference joined #LINQT-1879 2025-09-02 17:01:46 +02:00
Gaelle Braud
139a3531a4 do not allow exception in app 2025-09-02 17:00:30 +02:00
Gaelle Braud
3c85d25dfc new meeting erase title hint #LINQT-1966 2025-09-02 15:25:22 +02:00
Gaelle Braud
a406bac4a9 update sdk 2025-09-02 15:19:33 +02:00
Gaelle Braud
2f52844fca participant list design #LINQT-1926 2025-09-02 15:00:12 +02:00
Gaelle Braud
6ecf1f394e add non admin participant in participant list (fix #LINQT-1925) 2025-09-02 11:29:00 +02:00
Gaelle Braud
e3aebd3c00 add today button meeting list #LINQT-1886 2025-09-02 11:26:55 +02:00
Gaelle Braud
1ddfae8802 fix autoscroll to current date meeting #LINQT-1886 2025-08-28 18:22:51 +02:00
Gaelle Braud
d742fa9ea0 automatically wrap line in meeting description #LINQT-1889 (+ add scrollbar if description is too long to fit in the window) 2025-08-28 18:22:51 +02:00
Gaelle Braud
4b613f1787 update sdk 2025-08-28 18:22:51 +02:00
Gaelle Braud
afe81094ba open lock icon on basic chatrooms #LINQT-1903 2025-08-28 18:22:51 +02:00
Gaelle Braud
358125dfa6 allow changing call ringtone #LINQT-1321 2025-08-28 18:22:51 +02:00
Gaelle Braud
61d62c91ca fix create chat room from conference 2025-08-28 18:22:51 +02:00
Christophe Deschamps
033051066b Fix typo in voicemail count display 2025-08-28 17:38:36 +02:00
Gaelle Braud
9074914f39 fix #LINQT-1927 wrong conference link copy 2025-08-28 10:40:28 +02:00
Gaelle Braud
e55779d257 fix #LINQT-1912 meeting form left padding
fix #LINQT-1921 settings button in waiting room
2025-08-27 11:49:35 +02:00
Gaelle Braud
11f3546ba6 fix #LINQT-1915 crash when changing meeting locale 2025-08-27 10:41:24 +02:00
Gaelle Braud
e0286fdc42 try to fix #LINQT-1906 2025-08-26 17:42:05 +02:00
Gaelle Braud
60ebaf73a9 fix #LINQT-1885 start research in messages with arrow buttons as well 2025-08-26 16:53:28 +02:00
gaelle.braud
e6266dcaf2 allow using only a company name for a contact #LINQT-1602 2025-08-26 16:00:21 +02:00
gaelle.braud
443bcbf6c6 MacOS fixes :
fix cmake minimum version for compilation
crash notification in task bar
2025-08-26 15:54:47 +02:00
Gaelle Braud
99dd528e55 update sdk 2025-08-26 12:33:33 +02:00
Gaelle Braud
f516505287 fix #LINQT-1910 wrong proxy index when chat list filtered 2025-08-26 12:33:27 +02:00
Gaelle Braud
4203a9c9c0 fix #LINQT-1887 add scrollbars in meeting details for small screens 2025-08-26 12:29:20 +02:00
Gaelle Braud
5e09348c3d fix #LINQT-1904 last message imdn 2025-08-25 16:55:23 +02:00
Gaelle Braud
54eac3ddc1 loading ui meeting list 2025-08-25 11:42:39 +02:00
Gaelle Braud
d3360cdbf8 fix #LINQT-1799 default contact avatar 2025-08-22 16:23:51 +02:00
Gaelle Braud
5911cce057 fix crash macos noification in task bar 2025-08-22 11:23:58 +02:00
Gaelle Braud
2361f49c1d fix ephemeral messages #LINQT-1880 2025-08-21 17:14:56 +02:00
Gaelle Braud
969995283a update sdk 2025-08-21 15:28:13 +02:00
gaelle
be531b5e9f fix #LINQT-11899 text in messages disappearing (compute encoding to rich format only once for each message instead of doing it everytime something changes in the list) 2025-08-21 10:00:47 +02:00
Gaelle Braud
836d0b1da3 notifications in navigation bar 2025-08-20 11:03:18 +02:00
Gaelle Braud
8566f830d1 maj copyright 2025-08-20 11:03:18 +02:00
Gaelle Braud
d42af52ba1 update sdk 2025-08-20 11:03:18 +02:00
Gaelle Braud
80d6a75377 fix #LINQT-1878 emoji picker popup closing 2025-08-20 11:00:31 +02:00
Gaelle Braud
c24cfe135a message notification sound #LINQT-1666 2025-08-20 11:00:12 +02:00
Gaelle Braud
90ad29c78e fix forwarding message #LINQT-1877 2025-08-20 11:00:12 +02:00
Gaelle Braud
93418cb7c9 hide empty chat rooms flag default value to true #LINQT-1893
fix crash

call history/chats/chat messages lists loading ui
2025-08-20 11:00:12 +02:00
Gaelle Braud
c9e61d1e22 try to add xinerama for screen sharing 2025-08-07 16:56:34 +02:00
Gaelle Braud
9e67c86bc5 update sdk 2025-08-07 15:53:48 +02:00
Gaelle Braud
4b1cb237bf fix typo #LINQT-1876
fix chat room menu ui #LINQT-1872

update current chat message list when current chat removed #LINQT-1873

fix : remove unused connect disconnecting a usefull one
2025-08-07 15:53:47 +02:00
Gaelle Braud
a1e2c253bf fix #LINQT-1839 force switching to main page when account added and remote provisioning configured 2025-08-07 09:25:02 +02:00
Gaelle Braud
38a47b4680 fix remote prov error message 2025-08-06 17:05:31 +02:00
gaelle
1c820c4041 do not sort list as sdk already do it 2025-08-06 14:35:26 +02:00
gaelle
2b328583ec fix : do not force capture and display unless video is not supported 2025-08-06 13:42:21 +02:00
Gaelle Braud
a638a7d852 fix crash 2025-08-06 15:36:18 +02:00
Gaelle Braud
206d084bbd update sdk 2025-08-05 16:25:44 +02:00
Gaelle Braud
3a7ec59336 fix #LINQT-1868 magic search list display name/addresses ui 2025-08-05 16:25:32 +02:00
Gaelle Braud
e0cb654746 switch default account if clicking on a notification leading to a chat room from another account 2025-08-05 16:25:32 +02:00
Gaelle Braud
da2ea7d114 fix #LINQT-1799 secured status of conversation based on security level of all participants
fix manage participants
2025-08-05 14:30:04 +02:00
Gaelle Braud
a7f3476568 fix focus for key binding in chat message lit
fix participant info view
2025-08-05 14:30:04 +02:00
Gaelle Braud
6ccbdf6ef8 animation on progress status account connection + key binding open research in history 2025-08-04 17:03:09 +02:00
Gaelle Braud
55ce938d0b fix search in history + move search button in the top bar of the chat message view 2025-08-04 14:58:28 +02:00
Gaelle Braud
3f3f29b2ec reverse list to load most recent messages first 2025-08-04 14:58:28 +02:00
Gaelle Braud
d17a61e6d0 fix double message insertion 2025-08-04 14:58:28 +02:00
Gaelle Braud
ac4b5e60f7 update sdk 2025-08-04 14:56:22 +02:00
Gaelle Braud
8774f63e50 change bell icon 2025-08-04 14:56:22 +02:00
Gaelle Braud
785e15d9fd show error message when remote provisioning failed on start 2025-08-04 14:56:22 +02:00
Gaelle Braud
98ad40e750 spinner on chat message when state idle/in progress 2025-07-31 09:48:17 +02:00
Gaelle Braud
4e37fb70f1 only allow leaving if group chat 2025-07-30 11:30:55 +02:00
Gaelle Braud
521240cfd6 fix #LINQT-1860 refresh unread count on chatroom marked as read 2025-07-30 11:30:54 +02:00
Gaelle Braud
e90869c781 fix #LINQT-1862 double conference info on creation (remove unnecessary signal) 2025-07-30 11:30:54 +02:00
Gaelle Braud
4f494696d9 fix tab button width 2025-07-30 11:30:54 +02:00
Gaelle Braud
c3d2bd8293 fix #LINQT-1258 set mka extension for voice messages 2025-07-30 11:30:54 +02:00
Gaelle Braud
24c2c94633 fix #LINQT-1648 playback refresh (typo in signal) 2025-07-28 17:54:36 +02:00
Gaelle Braud
aae8e7b63e fix #LINQT-1836 microphone muted in conference 2025-07-28 17:18:23 +02:00
Gaelle Braud
2e0d963bd2 leave conversation 2025-07-28 16:52:53 +02:00
Gaelle Braud
3546287649 fix #LINQT-1835 security circle avatar 2025-07-28 10:33:00 +02:00
Gaelle Braud
41618bbdaa update sdk 2025-07-25 11:37:30 +02:00
Gaelle Braud
b1005eac5d fix #LINQT-1853 update popup x if exceed width 2025-07-25 11:24:04 +02:00
Gaelle Braud
c0f879bd19 fix #LINQT-1852 senf files without text 2025-07-25 10:39:02 +02:00
Gaelle Braud
8eae18512c fix #LINQT-1849 #LINQT-1850 shared files/medias ui 2025-07-25 10:12:03 +02:00
Gaelle Braud
c076a37540 fix #LINQT-1655 set height offset for new notification if there is already some displayed 2025-07-24 16:01:07 +02:00
Gaelle Braud
46f19d5d64 fix registration state ui in account list 2025-07-24 16:01:07 +02:00
Gaëlle Braud
ecf79d530a Fixes in chat messages (navigation with read button, sorting) 2025-07-23 21:09:14 +00:00
Gaelle Braud
8ac30ed393 update sdk 2025-07-23 13:14:10 +02:00
Gaëlle Braud
023b07743a Chat fixes : \
hidden button for debug chatroom address (top right of the avatar in conversation info, press and hold right click) \
display year in meeting list if not this year \
try to fix #LINQT-1834 not all imdn status displayed \
chat details panel refacto
2025-07-22 16:10:58 +00:00
Julien Wadel
5c7741abe3 Update AppImage tool URL to remove obsolete version. 2025-07-22 10:25:26 +02:00
Gaëlle Braud
34106a8008 fix #LINQT-1833 select chat when one result in chat list after research \
fix notify account when not sender \
display security settings ui \
rename hide_sip_addresses flag \
fix #LINQT-1843 disconnect old chatroom properly to avoid having message from another chatroom in the new one selected
2025-07-17 15:42:14 +00:00
Gaëlle Braud
98d9179419 fix #LINQT-1796 don't scroll to main list when selecting contact from favorites \
fix crash
2025-07-17 13:31:47 +00:00
Gaëlle Braud
dce74eb958 fixes 2025-07-14 18:11:06 +00:00
Gaelle Braud
86bbb41623 update sdk 2025-07-10 12:26:41 +02:00
Gaelle Braud
e8feb1c0dc auto play voice recording message 2025-07-10 12:26:05 +02:00
Gaelle Braud
4c08c28bd5 search message in history 2025-07-09 17:47:32 +02:00
Gaelle Braud
a6f228c263 update sdk 2025-07-09 11:08:32 +02:00
Gaelle Braud
1e5ec027bb forward message 2025-07-09 11:08:32 +02:00
Gaelle Braud
b1ab4224ef ephemeral messages 2025-07-04 09:52:02 +02:00
Gaelle Braud
430e6916bd update sdk 2025-07-03 16:35:59 +02:00
Gaelle Braud
a5851855d9 show shared medias/files + expand button on chat participant list 2025-07-03 15:20:37 +02:00
Gaelle Braud
c015375ae9 fix unread messages
fix do not add chat in list if events intended for another account

chat notif in vertical bar / account list

fix cursor on hovered link

fix sending area height
2025-07-02 12:55:21 +02:00
Gaelle Braud
0655672c32 removed submodule qtkeychain (we must use our own repo) 2025-07-01 18:13:31 +02:00
Gaelle Braud
e0d864a2d3 mark as read chat list 2025-07-01 17:34:38 +02:00
Gaelle Braud
47e7250b4e fix missing library 2025-06-30 19:29:09 +02:00
Gaelle Braud
5c3ce75c02 update ci file 2025-06-30 14:28:22 +02:00
Gaelle Braud
754c116ff1 update sdk 2025-06-27 12:07:05 +02:00
Gaelle Braud
de6d62021a mentions 2025-06-27 12:06:36 +02:00
Gaelle Braud
a7e39ab276 fix crash
fix add chat to list if first message received
2025-06-27 12:06:36 +02:00
Christophe Deschamps
61fecd8c93 Mark chatroom as read-only if it is not secure and account has mandatory encryption 2025-06-24 13:37:37 +00:00
Christophe Deschamps
3edc55800a Ephemeral settings 2025-06-24 11:56:27 +02:00
Christophe Deschamps
54a3501ddf Refactor and reposition participant management in group chat room 2025-06-24 11:54:35 +02:00
Gaelle Braud
4a1f1a895b message reply 2025-06-23 16:15:26 +02:00
Christophe Deschamps
f82a4db826 Wrap text in action button in room info 2025-06-20 19:24:55 +02:00
Gaelle Braud
a02a58ecc9 imdn details 2025-06-20 15:50:31 +02:00
Gaelle Braud
1d7010c381 emoji details 2025-06-20 15:50:31 +02:00
Christophe Deschamps
7484962441 Mute/Unmute Chatrooms 2025-06-19 18:30:57 +02:00
Christophe Deschamps
0470988c32 - Manage participants inside group chat room
- Wires Set/Unset Admin to model function
- Fixed "is(not) now admin" messages inversion
- Fixed event log list not updating (building ID from timestamp and type)
- Updated text to reflect that a participant can no longer be in a conversation without an explicit "leaving" action from him (admin removed for example)
2025-06-19 11:55:42 +02:00
Christophe Deschamps
279ac22463 Import Qt.labs.qmlmodels for DelegateChooser 2025-06-17 19:48:58 +02:00
Christophe Deschamps
4a042392ac Schedule meeting from chat room 2025-06-17 14:57:48 +02:00
Gaelle Braud
cbe29c66ee fix ci 2025-06-17 11:17:35 +02:00
Gaelle Braud
f82931d6c6 record message
auto download attached files setting
2025-06-17 11:17:35 +02:00
Gaelle Braud
6d4506c5ae update sdk 2025-06-16 11:04:32 +02:00
Gaelle Braud
6aadc2f292 sound player 2025-06-16 11:04:32 +02:00
Christophe Deschamps
6ff3cc0ae7 Fix crash with CardDav 2025-06-13 12:23:51 +02:00
Christophe Deschamps
f4e3db8a07 Call from chat room 2025-06-12 20:08:20 +02:00
Christophe Deschamps
ff78a5abf1 Feature/group chat infos 2025-06-12 14:19:02 +00:00
Christophe Deschamps
4ef65219ad Prevent freeze when left chatroom is opened 2025-06-10 12:00:23 +02:00
Christophe Deschamps
c56db0f429 1-1 Conversations Info, wired functions : 2025-06-09 14:20:34 +00:00
Gaelle Braud
8e08eb0127 update sdk 2025-06-06 15:03:18 +02:00
Gaelle Braud
f69c5c3703 send message with files 2025-06-06 15:03:18 +02:00
Gaelle Braud
9d5935fb53 Chat message content 2025-06-05 17:01:57 +02:00
Gaelle Braud
af2350cd16 add qtkeychain submodule 2025-06-05 16:45:29 +02:00
Christophe Deschamps
7617996dc4 Chatroom infos in Chatroom lists - composing/muted/ephemeral/imdn ... 2025-06-04 16:42:00 +02:00
Christophe Deschamps
460c0334c4 Add events to chat 2025-05-28 08:33:55 +00:00
Gaelle Braud
672ae55ea6 try to fix read access violation (Mantis 0013842) 2025-05-26 09:52:15 +02:00
Gaelle Braud
0614520e5a fix sending area wiped everytime we receive a message : set a sending text in chatcore (also allows to write something to someone and let it unfinished for later)
fix chat message insertion in list
2025-05-23 09:06:53 +00:00
Christophe Deschamps
7abfe4508c Set a maximum dp to avoid too big display on large screens 2025-05-23 08:20:53 +02:00
Gaelle Braud
9daaf523e3 changelog 2025-05-22 07:42:00 +00:00
Gaelle Braud
30538cf464 fix #LINQT-1776 wrong translations on numerus forms 2025-05-21 17:15:04 +02:00
Gaelle Braud
c766fb0237 fix #LINQT-1774 forgot to set visible property en e2e encrypted header 2025-05-21 17:15:04 +02:00
Gaelle Braud
481f9500c6 emoji picker
emoji picker for adding in chat message / sending reaction
2025-05-21 17:14:49 +02:00
Gaelle Braud
01a1fdd04b emoji svgs + model 2025-05-21 17:13:29 +02:00
Christophe Deschamps
9e04968b20 Leave chatroom 2025-05-20 17:00:45 +02:00
Christophe Deschamps
f8276ac834 Meeting invitation display in chat conversations 2025-05-19 15:50:14 +00:00
Gaelle Braud
ac7164fb0b e2e encrypted header chat message list
translations update

update sdk

fix #LINQT-1772
2025-05-19 09:42:01 +02:00
Gaelle Braud
75e71be14d create new chat 2025-05-16 15:22:34 +02:00
Gaelle Braud
5808368b9a fix scroll tu first unread index 2025-05-15 11:37:09 +02:00
gaelle
f3a1b5de62 chat translations 2025-05-15 11:37:09 +02:00
Gaelle Braud
4bb1e5da43 open chat when clicking on message notification
select chat by clicking on notification

close all notifications when one clicked and chat is open
2025-05-15 11:37:09 +02:00
Christophe Deschamps
e178bd43cf Adds rich text format display in chat sending text zone 2025-05-15 09:37:09 +02:00
Christophe Deschamps
096501ee20 Chat - clickable links and emoji display in bubbles 2025-05-14 12:55:10 +02:00
Gaelle Braud
32e5d5bc1d update sdk 2025-05-13 12:39:30 +02:00
Gaelle Braud
9b67794752 imdn on each chat message
imdn on chat last message

change message notification ui

press enter to send message
2025-05-13 12:39:30 +02:00
Gaelle Braud
c0dbc4b0e5 try to improve friend research (clean address before insertion in map) 2025-05-13 12:38:55 +02:00
Christophe Deschamps
dad3cb084f Presence 2025-05-13 08:34:38 +00:00
Sylvain Berfini
187c6753bd Update README links, added required Qt6 packages for Fedora build 2025-05-13 07:06:02 +00:00
Gaelle Braud
478c90b7bb update gitlab ci file 2025-05-12 18:48:21 +02:00
Gaelle Braud
09a76935c0 docker update : ubuntu 22.04, qt6.9.0 2025-05-12 16:02:37 +02:00
Gaelle Braud
03576d48e8 scroll to first unread message + mark as read 2025-05-07 18:39:14 +02:00
Gaelle Braud
73b83771be copy message text to clipboard + improve chat message and sending area ui 2025-05-07 18:39:14 +02:00
Gaelle Braud
ecd9373df9 Send message, composing notification 2025-05-07 18:39:14 +02:00
Gaelle Braud
bdff2bddcd delete history for chatroom
delete chat message

delete chat room
2025-05-07 18:39:14 +02:00
Peio Rigaux
179be1bb15 Fix windows signature return code, as we are using Invoke-Expression, and makes sure that this job doesn't prevent upload even when it fails 2025-05-06 14:57:39 +02:00
Christophe Deschamps
305973038d Call Forward 2025-05-05 15:36:58 +00:00
Gaelle Braud
3396d6d848 update sdk 2025-05-05 14:36:46 +02:00
Gaelle Braud
d24cef5e17 try to fix crash in chatrooms 2025-05-05 14:36:46 +02:00
Gaelle Braud
b79b324027 open chat room from call/conf
navigate to chat from contact/history/magic search

select chat when joining from contact/history/magic search

message notification

filter chat rooms
2025-05-05 14:36:46 +02:00
Christophe Deschamps
8516a3febf German translations 2025-05-05 08:23:12 +00:00
Jehan Monnier
0fde929da3 Fix basic auth mode for oidc.
Make sure refresh token is optionnal
2025-05-04 18:11:05 +02:00
Gaelle Braud
8fb42c333c chat list
chat messages view

update sdk
2025-04-29 14:16:55 +02:00
Sylvain Berfini
9b9994b358 Merge remote-tracking branch 'origin/release/6.0' 2025-04-29 11:35:27 +02:00
Christophe Deschamps
0f435d32af Command line to run upon incoming call 2025-04-23 10:52:28 +02:00
3871 changed files with 175210 additions and 14355 deletions

9
.gitignore vendored
View file

@ -52,3 +52,12 @@ linphone.spec
# VSCode
linphone60*.log
linphone61*.log
linphone62*.log
# QT Creator
*.cflags
*.config
*.creator
*.cxxflags
*.files
*.includes

View file

@ -1,171 +0,0 @@
.factorize_ubuntu2004: &docker_image_platform_and_runner_tag
tags: [ "docker" ]
image: gitlab.linphone.org:4567/bc/public/linphone-desktop/bc-dev-ubuntu-20-04-lts:$UBUNTU_2004_IMAGE_VERSION
ubuntu2004-ninja-gcc:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event" && $DOCKER_UPDATE == null && $SKIP_LINUX == null
variables:
CMAKE_GENERATOR: Ninja
CMAKE_OPTIONS: -DENABLE_PQCRYPTO=ON
CC: gcc
CXX: g++
extends: .linux-desktop
<<: *docker_image_platform_and_runner_tag
#################################################
# Nightly
#################################################
ubuntu2004-makefile-gcc:
rules:
- !reference [.rules-merge-request-manual, rules]
- if: $NIGHTLY_MASTER
variables:
CMAKE_GENERATOR: Unix Makefiles
CMAKE_OPTIONS: -DENABLE_PQCRYPTO=ON
CC: gcc
CXX: g++
ADDITIONAL_BUILD_OPTIONS: -j$MAKEFILE_JOBS
extends: .linux-desktop
<<: *docker_image_platform_and_runner_tag
ubuntu2004-ninja-clang:
rules:
- !reference [.rules-merge-request-manual, rules]
- if: $NIGHTLY_MASTER
variables:
CMAKE_OPTIONS: -DENABLE_DOC=ON -DENABLE_G729=ON -DENABLE_PQCRYPTO=ON -DENABLE_GPL_THIRD_PARTIES=ON
CMAKE_GENERATOR: Ninja
CC: clang
CXX: clang++
extends: .linux-desktop
allow_failure: true
<<: *docker_image_platform_and_runner_tag
ubuntu2004-ninja-clang-small:
rules:
- !reference [.rules-merge-request-manual, rules]
- if: $NIGHTLY_MASTER
variables:
CMAKE_OPTIONS: -DENABLE_VIDEO=NO -DENABLE_ADVANCED_IM=NO -DENABLE_DB_STORAGE=NO -DENABLE_PQCRYPTO=OFF
allow_failure: true
extends: ubuntu2004-ninja-clang
ubuntu2004-makefile-gcc:
rules:
- !reference [.rules-merge-request-manual, rules]
- if: $NIGHTLY_MASTER
- if: $DEPLOY_PLUGINS
variables:
CMAKE_OPTIONS: -DLINPHONE_BUILDER_SIGNING_IDENTITY=$GPG_SIGNING_KEYID -DENABLE_G729=ON -DENABLE_PQCRYPTO=ON -DENABLE_GPL_THIRD_PARTIES=ON
CMAKE_GENERATOR: Unix Makefiles
CC: gcc
CXX: g++
ADDITIONAL_BUILD_OPTIONS: -j$MAKEFILE_JOBS
APPIMAGETOOL_SIGN_PASSPHRASE: $GPG_SIGNING_PASS
script:
- echo "$GPG_SIGNING_PUB" > file.key && sed -i 's/\r /\n/g' file.key && chmod 600 file.key
- gpg --import file.key
- rm -f file.key
- echo "$GPG_SIGNING_KEY" > file.key && sed -i 's/\r /\n/g' file.key && chmod 600 file.key
- base64 -w 0 file.key | base64 -d | gpg --import --no-tty --batch --yes
- rm -f file.key
- cmake --version
- export CC=$CC
- export CXX=$CXX
- mkdir -p build/OUTPUT
- echo $CI_BUILD_TYPE
- echo $CMAKE_GENERATOR
- echo $DEFAULT_LINUX_CMAKE_OPTIONS
- echo $CMAKE_SANITIZER_OPTIONS
- eval "$(qtchooser -qt=$QT_LINUX_VER -print-env)"
- export PATH=${QTTOOLDIR}:$PATH
- export Qt6_DIR=${QTLIBDIR}/cmake/Qt6
- echo "Using Qt $QT_LINUX_VER at ${QTLIBDIR}"
- cd build
- cmake .. -G "$CMAKE_GENERATOR" -DCMAKE_VERBOSE_MAKEFILE=ON -DLINPHONESDK_PLATFORM=Desktop -DCMAKE_BUILD_TYPE=$CI_BUILD_TYPE -DLINPHONEAPP_APPLICATION_NAME="$APPLICATION_NAME" -DLINPHONEAPP_EXECUABLE_NAME="$EXECUTABLE_NAME" $DEFAULT_LINUX_CMAKE_OPTIONS $CMAKE_OPTIONS $SCHEDULE_CMAKE_OPTIONS $CMAKE_SANITIZER_OPTIONS
- cmake --build . --target install --config $CI_BUILD_TYPE $LBC_NODEBUG_OPTIONS
extends: .linux-desktop
<<: *docker_image_platform_and_runner_tag
#################################################
# Package - Nightly
#################################################
ubuntu2004-makefile-gcc-package:
stage: package
tags: [ "docker" ]
image: gitlab.linphone.org:4567/bc/public/linphone-desktop/bc-dev-ubuntu-20-04-lts:$UBUNTU_2004_IMAGE_VERSION
dependencies: []
rules:
- !reference [.rules-merge-request-manual, rules]
- if: $NIGHTLY_MASTER
- if: $PACKAGE_LINUX
- if: $DEPLOY_LINUX
variables:
CMAKE_OPTIONS: -DENABLE_APP_PACKAGING=YES -DLINPHONE_BUILDER_SIGNING_IDENTITY=$GPG_SIGNING_KEYID -DENABLE_G729=ON -DLINPHONE_SDK_MAKE_RELEASE_FILE_URL=$MAKE_RELEASE_FILE_URL/$LINUX_PLATFORM/$APP_FOLDER -DENABLE_PQCRYPTO=ON -DENABLE_GPL_THIRD_PARTIES=ON
CMAKE_GENERATOR: Unix Makefiles
CC: gcc
CXX: g++
APPIMAGETOOL_SIGN_PASSPHRASE: $GPG_SIGNING_PASS
extends: .linux-desktop
script:
- echo "$GPG_SIGNING_PUB" > file.key && sed -i 's/\r /\n/g' file.key && chmod 600 file.key
- gpg --import file.key
- rm -f file.key
- echo "$GPG_SIGNING_KEY" > file.key && sed -i 's/\r /\n/g' file.key && chmod 600 file.key
- base64 -w 0 file.key | base64 -d | gpg --import --no-tty --batch --yes
- rm -f file.key
- cmake --version
- export CC=$CC
- export CXX=$CXX
- mkdir -p build/OUTPUT
- echo $CI_BUILD_TYPE
- echo $CMAKE_GENERATOR
- echo $DEFAULT_LINUX_CMAKE_OPTIONS
- echo $CMAKE_SANITIZER_OPTIONS
- eval "$(qtchooser -qt=$QT_LINUX_VER -print-env)"
- export PATH=${QTTOOLDIR}:$PATH
- cd build
- cmake .. -G "$CMAKE_GENERATOR" -DCMAKE_VERBOSE_MAKEFILE=ON -DLINPHONESDK_PLATFORM=Desktop -DCMAKE_BUILD_TYPE=$CI_BUILD_TYPE -DLINPHONEAPP_APPLICATION_NAME="$APPLICATION_NAME" -DLINPHONEAPP_EXECUTABLE_NAME="$EXECUTABLE_NAME" $DEFAULT_LINUX_CMAKE_OPTIONS $CMAKE_OPTIONS $SCHEDULE_CMAKE_OPTIONS $CMAKE_SANITIZER_OPTIONS
- cmake --build . --target install --config $CI_BUILD_TYPE $LBC_NODEBUG_OPTIONS
artifacts:
paths:
- build/OUTPUT/*
expire_in: 1 week
#################################################
# Deploy - Nightly
#################################################
ubuntu2004-makefile-gcc-deploy:
stage: deploy
tags: [ "deploy" ]
needs:
- ubuntu2004-makefile-gcc-package
only:
variables:
- $NIGHTLY_MASTER
- $DEPLOY_LINUX
script:
- rsync -rlv --ignore-existing build/OUTPUT/Packages/*.AppImage $DEPLOY_SERVER:$UPLOAD_ROOT_PATH/$LINUX_PLATFORM/$APP_FOLDER
- |-
if [[ $MAKE_RELEASE_FILE_URL != "" ]]; then
rsync -rlv build/OUTPUT/Packages/RELEASE $DEPLOY_SERVER:$UPLOAD_ROOT_PATH/$LINUX_PLATFORM
rsync -rlv build/OUTPUT/Packages/RELEASE $MAIN_DEPLOY_SERVER:$UPLOAD_ROOT_PATH/$LINUX_PLATFORM
fi
ubuntu2004-makefile-gcc-plugins-deploy:
stage: deploy
tags: [ "deploy" ]
needs:
- ubuntu2004-makefile-gcc
only:
variables:
- $DEPLOY_PLUGINS
script:
- rsync -rlv --ignore-existing build/OUTPUT/plugins/app/*.so $DEPLOY_SERVER:$UPLOAD_ROOT_PATH/$LINUX_PLATFORM/$APP_FOLDER/plugins/

View file

@ -1,5 +1,5 @@
.factorize_ubuntu2204: &docker_image_platform_and_runner_tag
tags: [ "docker" ]
tags: [ "docker-flat" ]
image: gitlab.linphone.org:4567/bc/public/linphone-desktop/bc-dev-ubuntu-22-04-lts:$UBUNTU_2204_IMAGE_VERSION
ubuntu2204-ninja-gcc:
@ -36,7 +36,7 @@ ubuntu2204-ninja-clang:
- !reference [.rules-merge-request-manual, rules]
- if: $NIGHTLY_MASTER
variables:
CMAKE_OPTIONS: -DENABLE_DOC=ON -DENABLE_G729=ON -DENABLE_PQCRYPTO=ON -DENABLE_GPL_THIRD_PARTIES=ON
CMAKE_OPTIONS: -DENABLE_DOC=ON -DENABLE_G729=ON -DENABLE_PQCRYPTO=ON -DENABLE_GPL_THIRD_PARTIES=OFF
CMAKE_GENERATOR: Ninja
CC: clang
CXX: clang++
@ -53,41 +53,17 @@ ubuntu2204-ninja-clang-small:
allow_failure: true
extends: ubuntu2204-ninja-clang
ubuntu2204-makefile-gcc:
ubuntu2204-makefile-gcc-signed:
rules:
- !reference [.rules-merge-request-manual, rules]
- if: $NIGHTLY_MASTER
- if: $DEPLOY_PLUGINS
variables:
CMAKE_OPTIONS: -DLINPHONE_BUILDER_SIGNING_IDENTITY=$GPG_SIGNING_KEYID -DENABLE_G729=ON -DENABLE_PQCRYPTO=ON -DENABLE_GPL_THIRD_PARTIES=ON
CMAKE_GENERATOR: Unix Makefiles
CC: gcc
CXX: g++
ADDITIONAL_BUILD_OPTIONS: -j$MAKEFILE_JOBS
APPIMAGETOOL_SIGN_PASSPHRASE: $GPG_SIGNING_PASS
script:
- echo "$GPG_SIGNING_PUB" > file.key && sed -i 's/\r /\n/g' file.key && chmod 600 file.key
- gpg --import file.key
- rm -f file.key
- echo "$GPG_SIGNING_KEY" > file.key && sed -i 's/\r /\n/g' file.key && chmod 600 file.key
- base64 -w 0 file.key | base64 -d | gpg --import --no-tty --batch --yes
- rm -f file.key
- cmake --version
- export CC=$CC
- export CXX=$CXX
- mkdir -p build/OUTPUT
- echo $CI_BUILD_TYPE
- echo $CMAKE_GENERATOR
- echo $DEFAULT_LINUX_CMAKE_OPTIONS
- echo $CMAKE_SANITIZER_OPTIONS
- eval "$(qtchooser -qt=$QT_LINUX_VER -print-env)"
- export PATH=${QTTOOLDIR}:$PATH
- export Qt6_DIR=${QTLIBDIR}/cmake/Qt6
- echo "Using Qt $QT_LINUX_VER at ${QTLIBDIR}"
- cd build
- cmake .. -G "$CMAKE_GENERATOR" -DCMAKE_VERBOSE_MAKEFILE=ON -DLINPHONESDK_PLATFORM=Desktop -DCMAKE_BUILD_TYPE=$CI_BUILD_TYPE -DLINPHONEAPP_APPLICATION_NAME="$APPLICATION_NAME" -DLINPHONEAPP_EXECUABLE_NAME="$EXECUTABLE_NAME" $DEFAULT_LINUX_CMAKE_OPTIONS $CMAKE_OPTIONS $SCHEDULE_CMAKE_OPTIONS $CMAKE_SANITIZER_OPTIONS
- cmake --build . --target install --config $CI_BUILD_TYPE $LBC_NODEBUG_OPTIONS
extends: .linux-desktop
extends: .linux-sign-build
<<: *docker_image_platform_and_runner_tag
#################################################
@ -95,77 +71,41 @@ ubuntu2204-makefile-gcc:
#################################################
ubuntu2204-makefile-gcc-package:
stage: package
tags: [ "docker" ]
tags: [ "docker-flat" ]
image: gitlab.linphone.org:4567/bc/public/linphone-desktop/bc-dev-ubuntu-22-04-lts:$UBUNTU_2204_IMAGE_VERSION
dependencies: []
needs: []
rules:
- !reference [.rules-merge-request-manual, rules]
- if: $NIGHTLY_MASTER
- if: $PACKAGE_LINUX
- if: $DEPLOY_LINUX
variables:
CMAKE_OPTIONS: -DENABLE_APP_PACKAGING=YES -DLINPHONE_BUILDER_SIGNING_IDENTITY=$GPG_SIGNING_KEYID -DENABLE_G729=ON -DLINPHONE_SDK_MAKE_RELEASE_FILE_URL=$MAKE_RELEASE_FILE_URL/$LINUX_PLATFORM/$APP_FOLDER -DENABLE_PQCRYPTO=ON -DENABLE_GPL_THIRD_PARTIES=ON
CMAKE_GENERATOR: Unix Makefiles
CC: gcc
CXX: g++
APPIMAGETOOL_SIGN_PASSPHRASE: $GPG_SIGNING_PASS
extends: .linux-desktop
script:
- echo "$GPG_SIGNING_PUB" > file.key && sed -i 's/\r /\n/g' file.key && chmod 600 file.key
- gpg --import file.key
- rm -f file.key
- echo "$GPG_SIGNING_KEY" > file.key && sed -i 's/\r /\n/g' file.key && chmod 600 file.key
- base64 -w 0 file.key | base64 -d | gpg --import --no-tty --batch --yes
- rm -f file.key
- cmake --version
- export CC=$CC
- export CXX=$CXX
- mkdir -p build/OUTPUT
- echo $CI_BUILD_TYPE
- echo $CMAKE_GENERATOR
- echo $DEFAULT_LINUX_CMAKE_OPTIONS
- echo $CMAKE_SANITIZER_OPTIONS
- eval "$(qtchooser -qt=$QT_LINUX_VER -print-env)"
- export PATH=${QTTOOLDIR}:$PATH
- cd build
- cmake .. -G "$CMAKE_GENERATOR" -DCMAKE_VERBOSE_MAKEFILE=ON -DLINPHONESDK_PLATFORM=Desktop -DCMAKE_BUILD_TYPE=$CI_BUILD_TYPE -DLINPHONEAPP_APPLICATION_NAME="$APPLICATION_NAME" -DLINPHONEAPP_EXECUTABLE_NAME="$EXECUTABLE_NAME" $DEFAULT_LINUX_CMAKE_OPTIONS $CMAKE_OPTIONS $SCHEDULE_CMAKE_OPTIONS $CMAKE_SANITIZER_OPTIONS
- cmake --build . --target install --config $CI_BUILD_TYPE $LBC_NODEBUG_OPTIONS
artifacts:
paths:
- build/OUTPUT/*
expire_in: 1 week
extends: .linux-sign-package
#################################################
# Deploy - Nightly
#################################################
ubuntu2204-makefile-gcc-deploy:
stage: deploy
tags: [ "deploy" ]
needs:
- ubuntu2204-makefile-gcc-package
only:
extends: .linux-deploy
needs:
- ubuntu2204-makefile-gcc-package
only:
variables:
- $NIGHTLY_MASTER
- $DEPLOY_LINUX
script:
- rsync -rlv --ignore-existing build/OUTPUT/Packages/*.AppImage $DEPLOY_SERVER:$UPLOAD_ROOT_PATH/$LINUX_PLATFORM/$APP_FOLDER
- |-
if [[ $MAKE_RELEASE_FILE_URL != "" ]]; then
rsync -rlv build/OUTPUT/Packages/RELEASE $DEPLOY_SERVER:$UPLOAD_ROOT_PATH/$LINUX_PLATFORM
rsync -rlv build/OUTPUT/Packages/RELEASE $MAIN_DEPLOY_SERVER:$UPLOAD_ROOT_PATH/$LINUX_PLATFORM
fi
ubuntu2204-makefile-gcc-plugins-deploy:
stage: deploy
tags: [ "deploy" ]
needs:
- ubuntu2204-makefile-gcc
only:
stage: deploy
tags: [ "deploy-flat" ]
needs:
- ubuntu2204-makefile-gcc
only:
variables:
- $DEPLOY_PLUGINS
script:
- rsync -rlv --ignore-existing build/OUTPUT/plugins/app/*.so $DEPLOY_SERVER:$UPLOAD_ROOT_PATH/$LINUX_PLATFORM/$APP_FOLDER/plugins/
script:
- rsync -rlv --ignore-existing build/OUTPUT/plugins/app/*.so $DEPLOY_SERVER:$UPLOAD_ROOT_PATH/$LINUX_PLATFORM/$APP_FOLDER/plugins/

View file

@ -2,7 +2,7 @@
# BUILD
#################################################
.build_all_linux_script: &build_all_linux_script |
.common_linux: &common_linux |
cmake --version
export CC=$CC
export CXX=$CXX
@ -11,20 +11,68 @@
echo $CMAKE_GENERATOR
echo $DEFAULT_LINUX_CMAKE_OPTIONS
echo $CMAKE_SANITIZER_OPTIONS
cd build
eval "$(qtchooser -qt=$QT_LINUX_VER -print-env)"
eval "$(qtchooser -qt=$QT_LINUX_VER-${QT_OPEN_SOURCE_DIRECTORY:-proprietary} -print-env)"
export PATH=${QTTOOLDIR}:$PATH
export Qt6_DIR=${QTLIBDIR}/cmake/Qt6
echo "Using Qt $QT_LINUX_VER at ${QTLIBDIR}"
cd build
.build_all_linux_script: &build_all_linux_script |
cmake .. -G "$CMAKE_GENERATOR" -DCMAKE_VERBOSE_MAKEFILE=ON -DLINPHONESDK_PLATFORM=Desktop -DCMAKE_BUILD_TYPE=$CI_BUILD_TYPE -DLINPHONEAPP_APPLICATION_NAME="$APPLICATION_NAME" -DLINPHONEAPP_EXECUABLE_NAME="$EXECUTABLE_NAME" $DEFAULT_LINUX_CMAKE_OPTIONS $CMAKE_OPTIONS $SCHEDULE_CMAKE_OPTIONS $CMAKE_SANITIZER_OPTIONS
cmake --build . --target install --config $CI_BUILD_TYPE $LBC_NODEBUG_OPTIONS
.common_signed_linux: &common_signed_linux |
echo "$GPG_SIGNING_PUB" > file.key && sed -i 's/\r /\n/g' file.key && chmod 600 file.key
gpg --import file.key
rm -f file.key
echo "$GPG_SIGNING_KEY" > file.key && sed -i 's/\r /\n/g' file.key && chmod 600 file.key
base64 -w 0 file.key | base64 -d | gpg --import --no-tty --batch --yes
rm -f file.key
.deploy_linux: &deploy_linux |
rsync -rlv --chmod=Fu=rw,Fg=r,Fo=r --ignore-existing build/OUTPUT/Packages/*.AppImage $DEPLOY_SERVER:$UPLOAD_ROOT_PATH/$LINUX_PLATFORM/$APP_FOLDER
if [[ $MAKE_RELEASE_FILE_URL != "" ]]; then
rsync -rlv --chmod=Fu=rw,Fg=r,Fo=r build/OUTPUT/Packages/RELEASE $DEPLOY_SERVER:$UPLOAD_ROOT_PATH/$LINUX_PLATFORM
rsync -rlv --chmod=Fu=rw,Fg=r,Fo=r build/OUTPUT/Packages/RELEASE $MAIN_DEPLOY_SERVER:$UPLOAD_ROOT_PATH/$LINUX_PLATFORM
fi
.linux-desktop:
stage: build
extends: .linux-prepare
script:
- *common_linux
- *build_all_linux_script
artifacts:
paths:
- build/OUTPUT
expire_in: 1 week
.linux-sign-build:
extends: .linux-desktop
variables:
CMAKE_OPTIONS: -DLINPHONE_BUILDER_SIGNING_IDENTITY=$GPG_SIGNING_KEYID -DENABLE_G729=ON -DENABLE_PQCRYPTO=ON -DENABLE_GPL_THIRD_PARTIES=OFF
APPIMAGETOOL_SIGN_PASSPHRASE: $GPG_SIGNING_PASS
script:
- *common_signed_linux
- *common_linux
- cmake .. -G "$CMAKE_GENERATOR" -DCMAKE_VERBOSE_MAKEFILE=ON -DLINPHONESDK_PLATFORM=Desktop -DCMAKE_BUILD_TYPE=$CI_BUILD_TYPE -DLINPHONEAPP_APPLICATION_NAME="$APPLICATION_NAME" -DLINPHONEAPP_EXECUABLE_NAME="$EXECUTABLE_NAME" $DEFAULT_LINUX_CMAKE_OPTIONS $CMAKE_OPTIONS $SCHEDULE_CMAKE_OPTIONS $CMAKE_SANITIZER_OPTIONS
- cmake --build . --target install --config $CI_BUILD_TYPE $LBC_NODEBUG_OPTIONS
.linux-sign-package:
stage: package
extends: .linux-desktop
variables:
CMAKE_OPTIONS: -DENABLE_APP_PACKAGING=YES -DLINPHONE_BUILDER_SIGNING_IDENTITY=$GPG_SIGNING_KEYID -DENABLE_G729=ON -DLINPHONE_SDK_MAKE_RELEASE_FILE_URL=$MAKE_RELEASE_FILE_URL/$LINUX_PLATFORM/$APP_FOLDER -DENABLE_PQCRYPTO=ON -DENABLE_GPL_THIRD_PARTIES=OFF
APPIMAGETOOL_SIGN_PASSPHRASE: $GPG_SIGNING_PASS
script:
- *common_signed_linux
- *common_linux
- cmake .. -G "$CMAKE_GENERATOR" -DCMAKE_VERBOSE_MAKEFILE=ON -DLINPHONESDK_PLATFORM=Desktop -DCMAKE_BUILD_TYPE=$CI_BUILD_TYPE -DLINPHONEAPP_APPLICATION_NAME="$APPLICATION_NAME" -DLINPHONEAPP_EXECUTABLE_NAME="$EXECUTABLE_NAME" $DEFAULT_LINUX_CMAKE_OPTIONS $CMAKE_OPTIONS $SCHEDULE_CMAKE_OPTIONS $CMAKE_SANITIZER_OPTIONS
- cmake --build . --target install --config $CI_BUILD_TYPE $LBC_NODEBUG_OPTIONS
.linux-deploy:
stage: deploy
tags: [ "deploy-flat" ]
script:
- *deploy_linux

View file

@ -26,7 +26,7 @@
.macosx-desktop:
stage: build
tags: [ "macos-min-xcode12.2" ]
tags: [ "macmini-m1-xcode15-flat" ]
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event" && $DOCKER_UPDATE == null && $SKIP_MACOSX == null
- if: $CI_PIPELINE_SOURCE == "schedule" && $DOCKER_UPDATE == null && $SKIP_MACOSX == null
@ -93,8 +93,8 @@ macosx-ninja-novideo:
# WAIT for QT6 for arm64
macosx-ninja-package:
stage: package
tags: [ "macos-min-xcode12.2" ]
dependencies: []
tags: [ "macmini-m1-xcode15-flat" ]
needs: []
rules:
- !reference [.rules-merge-request-manual, rules]
- if: $CI_PIPELINE_SOURCE == "schedule" && $DOCKER_UPDATE == null && $SKIP_MACOSX == null
@ -102,7 +102,7 @@ macosx-ninja-package:
- if: $PACKAGE_MACOSX
- if: $DEPLOY_MACOSX
variables:
CMAKE_OPTIONS: -DPython3_ROOT_DIR=/opt/bc/pip-packages/ -DENABLE_APP_PACKAGING=ON -DENABLE_GPL_THIRD_PARTIES=ON -DENABLE_G729=ON
CMAKE_OPTIONS: -DPython3_ROOT_DIR=/opt/bc/pip-packages/ -DENABLE_APP_PACKAGING=ON -DENABLE_GPL_THIRD_PARTIES=OFF -DENABLE_G729=ON
RELEASE_FILE: -DLINPHONE_SDK_MAKE_RELEASE_FILE_URL=$MAKE_RELEASE_FILE_URL/$MACOSX_PLATFORM/$APP_FOLDER
extends: macosx-ninja
script:
@ -117,7 +117,7 @@ macosx-ninja-package:
macosx-codesigning:
stage: signing
tags: [ "macos-min-xcode12.2" ]
tags: [ "macmini-m1-xcode15-flat" ]
needs:
- macosx-ninja-package
rules:
@ -142,7 +142,7 @@ macosx-codesigning:
macosx-deploy:
stage: deploy
tags: [ "macos-min-xcode12.2" ]
tags: [ "macmini-m1-xcode15-flat" ]
needs:
- macosx-codesigning
only:
@ -160,7 +160,7 @@ macosx-deploy:
macosx-makefile-plugins-deploy:
stage: deploy
tags: [ "macos-min-xcode12.2" ]
tags: [ "macmini-m1-xcode15-flat" ]
needs:
- macosx-makefile
only:

View file

@ -9,7 +9,7 @@
- if: $CI_PIPELINE_SOURCE == "merge_request_event" && $DOCKER_UPDATE == null && $SKIP_WINDOWS == null
- if: $CI_PIPELINE_SOURCE == "schedule" && $DOCKER_UPDATE == null && $SKIP_WINDOWS == null
variables:
CMAKE_OPTIONS: -DENABLE_UNIT_TESTS=ON -DENABLE_G729=ON -DENABLE_PQCRYPTO=ON -DENABLE_GPL_THIRD_PARTIES=ON
CMAKE_OPTIONS: -DENABLE_UNIT_TESTS=ON -DENABLE_G729=ON -DENABLE_PQCRYPTO=ON -DENABLE_GPL_THIRD_PARTIES=OFF
LINPHONESDK_PLATFORM: Desktop
OUTPUT_ZIP_FOLDER: win64
MINGW_TYPE: mingw64
@ -92,11 +92,11 @@
.windows-vs2022:
extends: .windows-vs
tags: [ "windows-powershell-vs-17-2022" ]
tags: [ "windows-powershell-vs-17-2022-flat" ]
.windows-codesigning:
extends: .prepare
tags: [ "windows-powershell-vs-17-2022-apps" ]
tags: [ "windows-powershell-vs-17-2022-apps-flat" ]
.windows-msbuild-variables:
variables:
@ -177,7 +177,7 @@ win64-ninja-vs2022-scheduled-windows:
.vs-win64-package:
stage: package
dependencies: []
needs: []
rules:
- !reference [.rules-merge-request-manual, rules]
- if: $NIGHTLY_MASTER
@ -185,7 +185,7 @@ win64-ninja-vs2022-scheduled-windows:
- if: $PACKAGE_WINDOWS
- if: $DEPLOY_WINDOWS
variables:
CMAKE_OPTIONS: -DENABLE_APP_PACKAGING=YES -DENABLE_G729=ON -DENABLE_PQCRYPTO=ON -DENABLE_GPL_THIRD_PARTIES=ON
CMAKE_OPTIONS: -DENABLE_APP_PACKAGING=YES -DENABLE_G729=ON -DENABLE_PQCRYPTO=ON -DENABLE_GPL_THIRD_PARTIES=OFF
RELEASE_FILE: -DLINPHONE_SDK_MAKE_RELEASE_FILE_URL=$MAKE_RELEASE_FILE_URL/$WINDOWS_PLATFORM/$APP_FOLDER
win64-ninja-vs2022-package-windows:
@ -232,15 +232,15 @@ win64-codesigning:
.win64-upload:
stage: deploy
tags: [ "windows-powershell" ]
tags: [ "windows-powershell-flat" ]
rules:
- if: $NIGHTLY_MASTER
- if: $DEPLOY_WINDOWS
script:
- scp -pr build-desktop/OUTPUT/Packages/*.exe ${DEPLOY_SERVER}:${UPLOAD_ROOT_PATH}/${WINDOWS_PLATFORM}/${APP_FOLDER}
- if ($MAKE_RELEASE_FILE_URL) { scp -pr build-desktop/OUTPUT/Packages/RELEASE ${DEPLOY_SERVER}:${UPLOAD_ROOT_PATH}/${WINDOWS_PLATFORM}/ }
- if ($MAKE_RELEASE_FILE_URL) { scp -pr build-desktop/OUTPUT/Packages/RELEASE ${MAIN_DEPLOY_SERVER}:${UPLOAD_ROOT_PATH}/${WINDOWS_PLATFORM}/ }
- rsync --perms --chmod=Fu=rw,Fg=r,Fo=r build-desktop/OUTPUT/Packages/*.exe ${DEPLOY_SERVER}:${UPLOAD_ROOT_PATH}/${WINDOWS_PLATFORM}/${APP_FOLDER}
- if ($MAKE_RELEASE_FILE_URL) { rsync --perms --chmod=Fu=rw,Fg=r,Fo=r build-desktop/OUTPUT/Packages/RELEASE ${DEPLOY_SERVER}:${UPLOAD_ROOT_PATH}/${WINDOWS_PLATFORM}/ }
- if ($MAKE_RELEASE_FILE_URL) { rsync --perms --chmod=Fu=rw,Fg=r,Fo=r build-desktop/OUTPUT/Packages/RELEASE ${MAIN_DEPLOY_SERVER}:${UPLOAD_ROOT_PATH}/${WINDOWS_PLATFORM}/ }
win64-ninja-vs2022-upload:
extends:
@ -250,7 +250,7 @@ win64-ninja-vs2022-upload:
.win64-plugins-upload:
stage: deploy
tags: [ "windows" ]
tags: [ "windows-flat" ]
rules:
- if: $DEPLOY_PLUGINS
script:

View file

@ -21,13 +21,7 @@ variables:
#CMAKE_OPTIONS: -DENABLE_LIME_X3DH=YES
# Docker image version
ARCHLINUX_IMAGE_VERSION: latestupdated
CENTOS_7_QT_IMAGE_VERSION: 20211012_add_qtwebview
DEBIAN_9_QT_IMAGE_VERSION: 20230417_qtopen_gstreamer
DEBIAN_10_IMAGE_VERSION: 20210217_python3
UBUNTU_ROLLING_IMAGE_VERSION: 20211012_add_qtwebview
UBUNTU_2004_IMAGE_VERSION: 20250226_qt6-8-0
UBUNTU_2204_IMAGE_VERSION: 20250512_qt6-9-0
UBUNTU_2204_IMAGE_VERSION: 20251106_add_commercial_qt_version_5.15.14_5.15.19_6.8.1_6.8.3_6.9.1_6.10.0
workflow:
@ -51,7 +45,6 @@ include:
- '.gitlab-ci-files/rules.yml'
- '.gitlab-ci-files/linux-prepare.yml'
- '.gitlab-ci-files/linux-desktop.yml'
# - '.gitlab-ci-files/linux-desktop-ubuntu-2004.yml'
- '.gitlab-ci-files/linux-desktop-ubuntu-2204.yml'
- '.gitlab-ci-files/windows-desktop.yml'
- '.gitlab-ci-files/macosx-desktop.yml'

2
.gitmodules vendored
View file

@ -1,3 +1,3 @@
[submodule "linphone-sdk"]
path = external/linphone-sdk
url = https://gitlab.linphone.org/BC/public/linphone-sdk.git
url = https://gitlab.linphone.org/BC/public/linphone-sdk.git

View file

@ -31,3 +31,9 @@ Group changes to describe their impact on the project, as follows:
- Default screen (between contacts, call history, conversations & meetings list) will change depending on where you were when the app was paused or killed, and you will return to that last visited screen on the next startup.
- Minimum supported Qt version is now 6.5.3
- Some settings have changed name and/or section in linphonerc file.
## [6.1.0] - XXXX-XX-XX
### Changed
- Minimum supported Qt version is now 6.10.0

View file

@ -60,7 +60,6 @@ project(linphoneqt)
include(GNUInstallDirs)
include(CheckCXXCompilerFlag)
include(Linphone/application_info.cmake)
set(CMAKE_CXX_STANDARD 17)
if(LINPHONEAPP_INSTALL_PREFIX)
@ -72,8 +71,11 @@ endif()
set(CMAKE_INSTALL_PREFIX "${APPLICATION_OUTPUT_DIR}")
set(LINPHONEAPP_APPLICATION_NAME "Linphone6" CACHE STRING "Application name" )
set(LINPHONEAPP_EXECUTABLE_NAME "linphone6" CACHE STRING "Executable name" )
set(LINPHONEAPP_APPLICATION_NAME "Linphone" CACHE STRING "Application name" )
set(LINPHONEAPP_EXECUTABLE_NAME "linphone" CACHE STRING "Executable name" )
# Include application_info.cmake here as the LINPHONEAPP_APPLICATION_NAME variable must be known as it is set to the APPLICATION_NAME variable.
include(Linphone/application_info.cmake)
if( APPLE )
set(LINPHONEAPP_MACOS_ARCHS "arm64" CACHE STRING "MacOS architectures to build: comma-separated list of values in [arm64, x86_64]")
@ -163,7 +165,7 @@ add_option(OPTION_LIST ENABLE_FFMPEG "Build mediastreamer2 with ffmpeg video sup
add_option(OPTION_LIST ENABLE_LDAP "Enable LDAP support." YES)
add_option(OPTION_LIST ENABLE_NON_FREE_CODECS "Enable the use of non free codecs" YES)
add_option(OPTION_LIST ENABLE_NON_FREE_FEATURES "Enable the use of non free codecs" ${ENABLE_NON_FREE_CODECS})
add_option(OPTION_LIST ENABLE_QT_KEYCHAIN "Build QtKeychain to manage VFS from System key stores." ON)
add_option(OPTION_LIST ENABLE_QT_KEYCHAIN "Build QtKeychain to manage VFS from System key stores." OFF)
add_option(OPTION_LIST ENABLE_QRCODE "Enable QRCode support" OFF)#Experimental
add_option(OPTION_LIST ENABLE_RELATIVE_PREFIX "Set Internal packages relative to the binary" ON)
add_option(OPTION_LIST ENABLE_SANITIZER "Enable sanitizer." OFF)
@ -185,9 +187,9 @@ add_option(OPTION_LIST ENABLE_SCREENSHARING "Enable screen sharing." ${ENABLE_VI
# QtKeychain
add_option(OPTION_LIST LIBSECRET_SUPPORT "Build with libsecret support" OFF) # Need libsecret-devel
if(WIN32)
add_cache(OPTION_LIST QTKEYCHAIN_TARGET_NAME "Override Qt5Keychain library name for a workaround with windeployqt" "EQt5Keychain")
add_cache(OPTION_LIST QTKEYCHAIN_TARGET_NAME "Override Qt6Keychain library name for a workaround with windeployqt" "EQt6Keychain")
else()
add_cache(OPTION_LIST QTKEYCHAIN_TARGET_NAME "Override Qt5Keychain library name" "Qt5Keychain")
add_cache(OPTION_LIST QTKEYCHAIN_TARGET_NAME "Override Qt6Keychain library name" "Qt6Keychain")
endif()
if(WIN32)
add_option(OPTION_LIST ENABLE_OPENSSL_EXPORT "Enable OpenSSL deployment" YES)
@ -206,7 +208,7 @@ set(ENABLE_CSHARP_WRAPPER OFF CACHE BOOL "Build the CSharp wrapper for Liblinpho
set(ENABLE_THEORA OFF)
set(ENABLE_QT_GL ${ENABLE_VIDEO})
find_package(Qt6 REQUIRED COMPONENTS Core)
find_package(Qt6 REQUIRED COMPONENTS Core Quick Widgets)
if(NOT Qt6_FOUND)
message(FATAL_ERROR "Minimum supported Qt6!")
@ -227,18 +229,11 @@ endif()
if(NOT APPLE OR MONO_ARCH)
add_custom_target(linphone-deps)
if(NOT LINPHONE_QT_ONLY)
function(add_external)
set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) # Prevent project from overriding the options we just set here
add_subdirectory("external")
endfunction()
add_external()
if(ENABLE_QT_KEYCHAIN)
function(add_linphone_keychain)
#add_subdirectory("external/qtkeychain")
endfunction()
add_linphone_keychain()
endif()
function(add_external)
set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) # Prevent project from overriding the options we just set here
add_subdirectory("external")
endfunction()
add_external()
endif()
function(add_linphone_app)
set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) # Prevent project from overriding the options we just set here
@ -270,4 +265,16 @@ else()
include(cmake/TasksMacos.cmake)
endif()
# if (ENABLE_QT_KEYCHAIN)
# target_include_directories(${TARGET_NAME} SYSTEM PUBLIC ${PROJECT_SOURCE_DIR}/external/qtkeychain)
# target_link_libraries(${TARGET_NAME} PUBLIC ${QTKEYCHAIN_TARGET_NAME})
# message(STATUS "link libraries: ${TARGET_NAME} ${QTKEYCHAIN_TARGET_NAME}")
# message(STATUS "CMAKE_CURRENT_SOURCE_DIR: ${CMAKE_CURRENT_SOURCE_DIR}")
# message(STATUS "PROJECT_SOURCE_DIR: ${PROJECT_SOURCE_DIR}")
# message(STATUS "Contents of qtkeychain:")
# file(GLOB QTKEYCHAIN_HEADERS "${PROJECT_SOURCE_DIR}/external/qtkeychain/qtkeychain/*.h")
# message(STATUS "Found headers: ${QTKEYCHAIN_HEADERS}")
# endif()
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/cmake/hook/pre-commit" DESTINATION "${CMAKE_CURRENT_SOURCE_DIR}/.git/hooks/")

View file

@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.16)
project(Linphone VERSION 6.0.0 LANGUAGES CXX)
project(Linphone VERSION 6.2.0 LANGUAGES CXX)
################################################################
# PACKAGES
@ -19,14 +19,14 @@ endforeach()
set(TARGET_NAME Linphone)
set(APP_TARGETS ${LinphoneCxx_TARGET}
${Mediastreamer2_TARGET}#MediastreamerUtils
${LibLinphone_TARGET})#MediastreamerUtils
${LibLinphone_TARGET})#Liblinphone
set(QT_DEFAULT_MAJOR_VERSION 6)
set(QT_PACKAGES Core Quick Qml Widgets Svg Multimedia Test NetworkAuth Concurrent)# Search Core at first for initialize Qt scripts for next find_packages.
set(QT_PACKAGES Quick Qml Widgets Svg Multimedia Test NetworkAuth Concurrent)# Search Core at first for initialize Qt scripts for next find_packages.
if (UNIX AND NOT APPLE)
list(APPEND QT_PACKAGES DBus)
endif()
find_package(Qt6 REQUIRED COMPONENTS Core)
find_package(Qt6 REQUIRED COMPONENTS Core)
find_package(Qt6 REQUIRED COMPONENTS ${QT_PACKAGES})
find_package(Qt6 REQUIRED COMPONENTS LinguistTools)
@ -98,15 +98,11 @@ endif()
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/config.h.cmake" "${CMAKE_CURRENT_BINARY_DIR}/config.h")
if(${Qt6_VERSION} VERSION_LESS "6.3.0")
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
else()
qt6_standard_project_setup()
if(${Qt6_VERSION} VERSION_LESS "6.10.0")
message( FATAL_ERROR "Linphone requires Qt 6.10.0 or newer. Exiting CMake." )
endif()
qt6_standard_project_setup()
################################################################
@ -127,7 +123,7 @@ add_subdirectory(core)
set(LANGUAGES_DIRECTORY "data/languages")
set(I18N_FILENAME i18n.qrc)
set(LANGUAGES en fr_FR de)
set(LANGUAGES en fr de)
# Add languages support.
add_subdirectory("${LANGUAGES_DIRECTORY}" "data/languages")
@ -174,6 +170,12 @@ qt6_add_qml_module(Linphone
RESOURCES data/fonts.qrc
)
if (WIN32)
if(MSVC AND (CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo"))
install(FILES "$<TARGET_PDB_FILE:${TARGET_NAME}>" DESTINATION ${CMAKE_INSTALL_BINDIR})
endif()
endif()
qt6_add_resources(Linphone "resources" PREFIX "/" FILES ${_LINPHONEAPP_RC_FILES})
set_property(TARGET Linphone PROPERTY POSITION_INDEPENDENT_CODE ON) #Need by Qt
@ -199,6 +201,7 @@ set_target_properties(${TARGET_NAME} PROPERTIES
if(MSVC)
set_target_properties(${TARGET_NAME} PROPERTIES PDB_NAME "${EXECUTABLE_NAME}_app")
endif()
set_target_properties(${TARGET_NAME} PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON)
if(WIN32)
if(ENABLE_SCREENSHARING)
@ -214,6 +217,7 @@ foreach(T ${APP_TARGETS})
target_link_libraries(${TARGET_NAME} PUBLIC ${T})
endforeach()
target_link_libraries(${TARGET_NAME} PRIVATE Qt6::Core)
foreach(T ${QT_PACKAGES})
target_link_libraries(${TARGET_NAME} PRIVATE Qt6::${T})
endforeach()

View file

@ -22,7 +22,6 @@
#include "App.hpp"
#include <QAction>
#include <QCoreApplication>
#include <QDirIterator>
#include <QFileSelector>
@ -53,9 +52,19 @@
#include "core/call/CallList.hpp"
#include "core/call/CallProxy.hpp"
#include "core/camera/CameraGui.hpp"
#include "core/chat/ChatProxy.hpp"
#include "core/chat/files/ChatMessageFileProxy.hpp"
#include "core/chat/message/ChatMessageGui.hpp"
#include "core/chat/message/EventLogGui.hpp"
#include "core/chat/message/EventLogList.hpp"
#include "core/chat/message/EventLogProxy.hpp"
#include "core/chat/message/content/ChatMessageContentGui.hpp"
#include "core/chat/message/content/ChatMessageContentProxy.hpp"
#include "core/chat/message/imdn/ImdnStatusProxy.hpp"
#include "core/conference/ConferenceGui.hpp"
#include "core/conference/ConferenceInfoGui.hpp"
#include "core/conference/ConferenceInfoProxy.hpp"
#include "core/emoji/EmojiProxy.hpp"
#include "core/fps-counter/FPSCounter.hpp"
#include "core/friend/FriendCore.hpp"
#include "core/friend/FriendGui.hpp"
@ -64,34 +73,50 @@
#include "core/notifier/Notifier.hpp"
#include "core/participant/ParticipantDeviceProxy.hpp"
#include "core/participant/ParticipantGui.hpp"
#include "core/participant/ParticipantInfoProxy.hpp"
#include "core/participant/ParticipantProxy.hpp"
#include "core/payload-type/PayloadTypeCore.hpp"
#include "core/payload-type/PayloadTypeGui.hpp"
#include "core/payload-type/PayloadTypeProxy.hpp"
#include "core/phone-number/PhoneNumber.hpp"
#include "core/phone-number/PhoneNumberProxy.hpp"
#include "core/recorder/RecorderGui.hpp"
#include "core/register/RegisterPage.hpp"
#include "core/screen/ScreenList.hpp"
#include "core/screen/ScreenProxy.hpp"
#include "core/search/MagicSearchProxy.hpp"
#include "core/setting/SettingsCore.hpp"
#include "core/singleapplication/singleapplication.h"
#include "core/sound-player/SoundPlayerGui.hpp"
#include "core/timezone/TimeZoneProxy.hpp"
#include "core/translator/DefaultTranslatorCore.hpp"
#include "core/variant/VariantList.hpp"
#include "core/videoSource/VideoSourceDescriptorGui.hpp"
#include "model/friend/FriendsManager.hpp"
#include "model/object/VariantObject.hpp"
#include "model/tool/ToolModel.hpp"
#include "tool/Constants.hpp"
#include "tool/EnumsToString.hpp"
#include "tool/Utils.hpp"
#include "tool/accessibility/AccessibilityHelper.hpp"
#include "tool/accessibility/FocusHelper.hpp"
#include "tool/accessibility/KeyboardShortcuts.hpp"
#include "tool/native/DesktopTools.hpp"
#include "tool/providers/AvatarProvider.hpp"
#include "tool/providers/EmojiProvider.hpp"
#include "tool/providers/ImageProvider.hpp"
#include "tool/providers/ScreenProvider.hpp"
#include "tool/providers/ThumbnailProvider.hpp"
#include "tool/request/CallbackHelper.hpp"
#include "tool/request/RequestDialog.hpp"
#include "tool/thread/Thread.hpp"
#include "tool/ui/DashRectangle.hpp"
#if defined(Q_OS_MACOS)
#include "core/event-count-notifier/EventCountNotifierMacOs.hpp"
#else
#include "core/event-count-notifier/EventCountNotifierSystemTrayIcon.hpp"
#endif // if defined(Q_OS_MACOS)
DEFINE_ABSTRACT_OBJECT(App)
@ -242,9 +267,7 @@ void App::setAutoStart(bool enabled) {
void App::setAutoStart(bool enabled) {
QSettings settings(AutoStartSettingsFilePath, QSettings::NativeFormat);
QString parameters;
if (!mSettings->getExitOnClose()) parameters = " --minimized";
if (enabled) settings.setValue(EXECUTABLE_NAME, QDir::toNativeSeparators(applicationFilePath()) + parameters);
if (enabled) settings.setValue(EXECUTABLE_NAME, QDir::toNativeSeparators(applicationFilePath()));
else settings.remove(EXECUTABLE_NAME);
mAutoStart = enabled;
@ -259,10 +282,14 @@ void App::setAutoStart(bool enabled) {
// -----------------------------------------------------------------------------
App::App(int &argc, char *argv[])
: SingleApplication(argc, argv, true, Mode::User | Mode::ExcludeAppPath | Mode::ExcludeAppVersion) {
: SingleApplication(argc, argv, true, Mode::User | Mode::ExcludeAppPath | Mode::ExcludeAppVersion) {
// Do not use APPLICATION_NAME here.
// The EXECUTABLE_NAME will be used in qt standard paths. It's our goal.
QDir::setCurrent(
QCoreApplication::applicationDirPath()); // Set working directory as the executable to allow relative paths.
QThread::currentThread()->setPriority(QThread::HighPriority);
qDebug() << "app thread is" << QThread::currentThread();
lDebug() << "Starting app with Qt version" << qVersion();
QCoreApplication::setApplicationName(EXECUTABLE_NAME);
QApplication::setOrganizationDomain(EXECUTABLE_NAME);
QCoreApplication::setApplicationVersion(APPLICATION_SEMVER);
@ -279,7 +306,7 @@ App::App(int &argc, char *argv[])
.arg(applicationVersion())
.arg(Utils::getOsProduct())
.arg(qVersion());
lInfo() << "at " << QDir().absolutePath();
mCurrentDate = QDate::currentDate();
mAutoStart = autoStartEnabled();
mDateUpdateTimer.setInterval(60000);
@ -291,7 +318,12 @@ App::App(int &argc, char *argv[])
emit currentDateChanged();
}
});
mEventCountNotifier = new EventCountNotifier(this);
mDateUpdateTimer.start();
#ifdef Q_OS_LINUX
exportDesktopFile();
#endif
}
App::~App() {
@ -305,7 +337,7 @@ void App::setSelf(QSharedPointer<App>(me)) {
auto callCore = CallCore::create(call);
mCoreModelConnection->invokeToCore([this, callCore] {
auto callGui = new CallGui(callCore);
auto win = getCallsWindow(QVariant::fromValue(callGui));
auto win = getOrCreateCallsWindow(QVariant::fromValue(callGui));
Utils::smartShowWindow(win);
auto mainwin = getMainWindow();
QMetaObject::invokeMethod(mainwin, "callCreated");
@ -349,21 +381,106 @@ void App::setSelf(QSharedPointer<App>(me)) {
}
});
mCoreModelConnection->makeConnectToModel(&CoreModel::authenticationRequested, &App::onAuthenticationRequested);
// Config error message
mCoreModelConnection->makeConnectToModel(
&CoreModel::configuringStatus, [this](const std::shared_ptr<linphone::Core> &core,
linphone::ConfiguringState status, const std::string &message) {
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
if (status == linphone::ConfiguringState::Failed) {
mCoreModelConnection->invokeToCore([this, message]() {
mustBeInMainThread(log().arg(Q_FUNC_INFO));
//: Error
Utils::showInformationPopup(
tr("info_popup_error_title"),
tr("info_popup_configuration_failed_message").arg(Utils::coreStringToAppString(message)),
false);
});
}
});
mCoreModelConnection->makeConnectToModel(
&CoreModel::accountAdded,
[this](const std::shared_ptr<linphone::Core> &core, const std::shared_ptr<linphone::Account> &account) {
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
if (CoreModel::getInstance()->mConfigStatus == linphone::ConfiguringState::Successful) {
bool accountConnected = account && account->getState() == linphone::RegistrationState::Ok;
mCoreModelConnection->invokeToCore([this, accountConnected]() {
mustBeInMainThread(log().arg(Q_FUNC_INFO));
// There is an account added by a remote provisioning, force switching to main page
// because the account may not be connected already
// if (accountConnected)
if (mPossiblyLookForAddedAccount) {
QMetaObject::invokeMethod(mMainWindow, "openMainPage", Qt::DirectConnection,
Q_ARG(QVariant, accountConnected));
}
mPossiblyLookForAddedAccount = false;
});
}
});
// Synchronize state for because linphoneCore was ran before any connections.
mCoreModelConnection->invokeToModel([this]() {
auto state = CoreModel::getInstance()->getCore()->getGlobalState();
mCoreModelConnection->invokeToCore([this, state] { setCoreStarted(state == linphone::GlobalState::On); });
});
mCoreModelConnection->makeConnectToModel(&CoreModel::unreadNotificationsChanged, [this] {
int n = mEventCountNotifier->getCurrentEventCount();
mCoreModelConnection->invokeToCore([this, n] { mEventCountNotifier->notifyEventCount(n); });
});
mCoreModelConnection->makeConnectToModel(&CoreModel::defaultAccountChanged, [this] {
int n = mEventCountNotifier->getCurrentEventCount();
mCoreModelConnection->invokeToCore([this, n] {
mEventCountNotifier->notifyEventCount(n);
emit defaultAccountChanged();
});
});
// Check update
mCoreModelConnection->makeConnectToModel(
&CoreModel::versionUpdateCheckResultReceived,
[this](const std::shared_ptr<linphone::Core> &core, linphone::VersionUpdateCheckResult result,
const std::string &version, const std::string &url, bool checkRequestedByUser) {
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
mCoreModelConnection->invokeToCore([this, result, version, url, checkRequestedByUser] {
switch (result) {
case linphone::VersionUpdateCheckResult::Error:
Utils::showInformationPopup(tr("info_popup_error_title"),
//: An error occured while trying to check update. Please
//: try again later or contact support team.
tr("info_popup_error_checking_update"), false);
break;
case linphone::VersionUpdateCheckResult::NewVersionAvailable: {
QString downloadLink =
QStringLiteral("<a href='%1'><font color='DefaultStyle.main2_600'>%2</a>")
.arg(url)
//: Download it !
.arg(tr("info_popup_new_version_download_label"));
Utils::showInformationPopup(
//: New version available !
tr("info_popup_new_version_available_title"),
//: A new version of Linphone (%1) is available. %2
tr("info_popup_new_version_available_message").arg(version).arg(downloadLink));
break;
}
case linphone::VersionUpdateCheckResult::UpToDate:
if (checkRequestedByUser)
//: Up to date
Utils::showInformationPopup(tr("info_popup_version_up_to_date_title"),
//: Your version is up to date
tr("info_popup_version_up_to_date_message"));
}
});
});
//---------------------------------------------------------------------------------------------
mCliModelConnection = SafeConnection<App, CliModel>::create(me, CliModel::getInstance());
mCliModelConnection->makeConnectToCore(&App::receivedMessage, [this](int, const QByteArray &byteArray) {
QString command(byteArray);
if (command.isEmpty()) {
lDebug() << log().arg("Check with CliModel for commands");
lInfo() << log().arg("Check with CliModel for commands");
mCliModelConnection->invokeToModel([]() { CliModel::getInstance()->runProcess(); });
} else {
qInfo() << QStringLiteral("Received command from other application: `%1`.").arg(command);
lInfo() << log().arg("Received command from other application: `%1`.").arg(command);
mCliModelConnection->invokeToModel([command]() { CliModel::getInstance()->executeCommand(command); });
}
});
@ -376,13 +493,22 @@ App *App::getInstance() {
return dynamic_cast<App *>(QApplication::instance());
}
QThread *App::getLinphoneThread() {
Thread *App::getLinphoneThread() {
return App::getInstance()->mLinphoneThread;
}
Notifier *App::getNotifier() const {
return mNotifier;
}
EventCountNotifier *App::getEventCountNotifier() {
return mEventCountNotifier;
}
int App::getEventCount() const {
return mEventCountNotifier ? mEventCountNotifier->getEventCount() : 0;
}
//-----------------------------------------------------------
// Initializations
//-----------------------------------------------------------
@ -422,10 +548,12 @@ void App::init() {
}
void App::initCore() {
mustBeInMainThread(log().arg(Q_FUNC_INFO));
// Core. Manage the logger so it must be instantiate at first.
CoreModel::create("", mLinphoneThread);
if (mParser->isSet("verbose")) QtLogger::enableVerbose(true);
if (mParser->isSet("qt-logs-only")) QtLogger::enableQtOnly(true);
qDebug() << "linphone thread is" << mLinphoneThread;
QMetaObject::invokeMethod(
mLinphoneThread->getThreadId(),
[this, settings = mSettings]() mutable {
@ -483,10 +611,12 @@ void App::initCore() {
initCppInterfaces();
mEngine->addImageProvider(ImageProvider::ProviderId, new ImageProvider());
mEngine->addImageProvider(EmojiProvider::ProviderId, new EmojiProvider());
mEngine->addImageProvider(AvatarProvider::ProviderId, new AvatarProvider());
mEngine->addImageProvider(ScreenProvider::ProviderId, new ScreenProvider());
mEngine->addImageProvider(WindowProvider::ProviderId, new WindowProvider());
mEngine->addImageProvider(WindowIconProvider::ProviderId, new WindowIconProvider());
mEngine->addImageProvider(ThumbnailProvider::ProviderId, new ThumbnailProvider());
// Enable notifications.
mNotifier = new Notifier(mEngine);
@ -497,6 +627,14 @@ void App::initCore() {
mAccountList->setInitialized(false);
mAccountList->lUpdate(true);
}
// Update global unread Notifications when an account updates his unread Notifications
connect(mAccountList.get(), &AccountList::unreadNotificationsChanged, this, [this]() {
lDebug() << "unreadNotificationsChanged of AccountList";
mCoreModelConnection->invokeToModel([this] {
int n = mEventCountNotifier->getCurrentEventCount();
mCoreModelConnection->invokeToCore([this, n] { mEventCountNotifier->notifyEventCount(n); });
});
});
if (!mCallList) setCallList(CallList::create());
else mCallList->lUpdate();
if (!mSettings) {
@ -522,7 +660,7 @@ void App::initCore() {
setLocale(settings->getConfigLocale());
setAutoStart(settings->getAutoStart());
setQuitOnLastWindowClosed(settings->getExitOnClose());
}
}
const QUrl url("qrc:/qt/qml/Linphone/view/Page/Window/Main/MainWindow.qml");
QObject::connect(
mEngine, &QQmlApplicationEngine::objectCreated, this,
@ -534,18 +672,45 @@ void App::initCore() {
}
auto window = qobject_cast<QQuickWindow *>(obj);
setMainWindow(window);
#ifndef __APPLE__
#if defined(__APPLE__)
setMacOSDockActions();
#else
// Enable TrayIconSystem.
if (!QSystemTrayIcon::isSystemTrayAvailable())
qWarning("System tray not found on this system.");
else setSysTrayIcon();
#endif // ifndef __APPLE__
#endif // if defined(__APPLE__)
static bool firstOpen = true;
if (!firstOpen || !mParser->isSet("minimized")) {
lDebug() << log().arg("Openning window");
window->show();
if (window) window->show();
} else lInfo() << log().arg("Stay minimized");
firstOpen = false;
lInfo() << log().arg("Checking remote provisioning");
if (mIsRestarting) {
if (CoreModel::getInstance()->mConfigStatus == linphone::ConfiguringState::Failed) {
QMetaObject::invokeMethod(thread(), [this]() {
auto message = CoreModel::getInstance()->mConfigMessage;
//: not reachable
if (message.isEmpty()) message = tr("configuration_error_detail");
mustBeInMainThread(log().arg(Q_FUNC_INFO));
//: Error
Utils::showInformationPopup(
tr("info_popup_error_title"),
//: Remote provisioning failed : %1
tr("info_popup_configuration_failed_message").arg(message), false);
});
} else {
mPossiblyLookForAddedAccount = true;
}
}
checkForUpdate();
mIsRestarting = false;
//---------------------------------------------------------------------------------------------
lDebug() << log().arg("Creating KeyboardShortcuts");
KeyboardShortcuts::create(getMainWindow());
}
},
Qt::QueuedConnection);
@ -557,7 +722,6 @@ void App::initCore() {
}
static inline bool installLocale(App &app, QTranslator &translator, const QLocale &locale) {
auto appPath = QStandardPaths::ApplicationsLocation;
bool ok = translator.load(locale.name(), Constants::LanguagePath);
ok = ok && app.installTranslator(&translator);
if (ok) QLocale::setDefault(locale);
@ -565,7 +729,6 @@ static inline bool installLocale(App &app, QTranslator &translator, const QLocal
}
void App::initLocale() {
// Try to use preferred locale.
QString locale;
@ -573,28 +736,28 @@ void App::initLocale() {
mLocale = QLocale(QLocale::English);
if (!installLocale(*this, *mDefaultTranslatorCore, mLocale)) qFatal("Unable to install default translator.");
// if (installLocale(*this, *mTranslatorCore, getLocale())) {
// qDebug() << "installed locale" << getLocale().name();
// return;
// }
// if (installLocale(*this, *mTranslatorCore, getLocale())) {
// qDebug() << "installed locale" << getLocale().name();
// return;
// }
// Try to use system locale.
// #ifdef Q_OS_MACOS
// Use this workaround if there is still an issue about detecting wrong language from system on Mac. Qt doesn't use
// the current system language on QLocale::system(). So we need to get it from user settings and overwrite its
// Locale.
// QSettings settings;
// QString preferredLanguage = settings.value("AppleLanguages").toStringList().first();
// QStringList qtLocale = QLocale::system().name().split('_');
// if(qtLocale[0] != preferredLanguage){
// qInfo() << "Override Qt language from " << qtLocale[0] << " to the preferred language : " <<
// preferredLanguage; qtLocale[0] = preferredLanguage;
// }
// QLocale sysLocale = QLocale(qtLocale.join('_'));
// #else
QLocale sysLocale(QLocale::system().name()); // Use Locale from name because Qt has a bug where it didn't use the
// QLocale::language (aka : translator.language != locale.language) on
// Mac. #endif
// Try to use system locale.
// #ifdef Q_OS_MACOS
// Use this workaround if there is still an issue about detecting wrong language from system on Mac. Qt doesn't
// use the current system language on QLocale::system(). So we need to get it from user settings and overwrite
// its Locale.
// QSettings settings;
// QString preferredLanguage = settings.value("AppleLanguages").toStringList().first();
// QStringList qtLocale = QLocale::system().name().split('_');
// if(qtLocale[0] != preferredLanguage){
// qInfo() << "Override Qt language from " << qtLocale[0] << " to the preferred language : " <<
// preferredLanguage; qtLocale[0] = preferredLanguage;
// }
// QLocale sysLocale = QLocale(qtLocale.join('_'));
// #else
QLocale sysLocale(QLocale::system().name()); // Use Locale from name because Qt has a bug where it didn't use
// the QLocale::language (aka : translator.language !=
// locale.language) on Mac. #endif
if (installLocale(*this, *mTranslatorCore, sysLocale)) {
qDebug() << "installed sys locale" << sysLocale.name();
setLocale(sysLocale.name());
@ -632,10 +795,18 @@ void App::initCppInterfaces() {
"SettingsCpp", 1, 0, "SettingsCpp",
[this](QQmlEngine *engine, QJSEngine *) -> QObject * { return mSettings.get(); });
qmlRegisterSingletonType<AccessibilityHelper>(
"AccessibilityHelperCpp", 1, 0, "AccessibilityHelperCpp",
[](QQmlEngine *engine, QJSEngine *) -> QObject * { return new AccessibilityHelper(engine); });
qmlRegisterType<FocusHelper>("CustomControls", 1, 0, "FocusHelper");
qmlRegisterType<DashRectangle>(Constants::MainQmlUri, 1, 0, "DashRectangle");
qmlRegisterType<PhoneNumberProxy>(Constants::MainQmlUri, 1, 0, "PhoneNumberProxy");
qmlRegisterType<VariantObject>(Constants::MainQmlUri, 1, 0, "VariantObject");
qmlRegisterType<VariantList>(Constants::MainQmlUri, 1, 0, "VariantList");
qmlRegisterType<ParticipantInfoProxy>(Constants::MainQmlUri, 1, 0, "ParticipantInfoProxy");
qmlRegisterType<ParticipantProxy>(Constants::MainQmlUri, 1, 0, "ParticipantProxy");
qmlRegisterType<ParticipantGui>(Constants::MainQmlUri, 1, 0, "ParticipantGui");
qmlRegisterType<ConferenceInfoProxy>(Constants::MainQmlUri, 1, 0, "ConferenceInfoProxy");
@ -652,6 +823,16 @@ void App::initCppInterfaces() {
qmlRegisterType<CallHistoryProxy>(Constants::MainQmlUri, 1, 0, "CallHistoryProxy");
qmlRegisterType<CallGui>(Constants::MainQmlUri, 1, 0, "CallGui");
qmlRegisterType<CallProxy>(Constants::MainQmlUri, 1, 0, "CallProxy");
qmlRegisterType<ChatList>(Constants::MainQmlUri, 1, 0, "ChatList");
qmlRegisterType<ChatProxy>(Constants::MainQmlUri, 1, 0, "ChatProxy");
qmlRegisterType<ChatGui>(Constants::MainQmlUri, 1, 0, "ChatGui");
qmlRegisterType<EventLogGui>(Constants::MainQmlUri, 1, 0, "EventLogGui");
qmlRegisterType<ChatMessageGui>(Constants::MainQmlUri, 1, 0, "ChatMessageGui");
qmlRegisterType<EventLogList>(Constants::MainQmlUri, 1, 0, "EventLogList");
qmlRegisterType<EventLogProxy>(Constants::MainQmlUri, 1, 0, "EventLogProxy");
qmlRegisterType<ChatMessageContentProxy>(Constants::MainQmlUri, 1, 0, "ChatMessageContentProxy");
qmlRegisterType<ChatMessageFileProxy>(Constants::MainQmlUri, 1, 0, "ChatMessageFileProxy");
qmlRegisterType<ChatMessageContentGui>(Constants::MainQmlUri, 1, 0, "ChatMessageContentGui");
qmlRegisterUncreatableType<ConferenceCore>(Constants::MainQmlUri, 1, 0, "ConferenceCore",
QLatin1String("Uncreatable"));
qmlRegisterType<ConferenceGui>(Constants::MainQmlUri, 1, 0, "ConferenceGui");
@ -661,6 +842,11 @@ void App::initCppInterfaces() {
qmlRegisterType<MagicSearchList>(Constants::MainQmlUri, 1, 0, "MagicSearchList");
qmlRegisterType<CameraGui>(Constants::MainQmlUri, 1, 0, "CameraGui");
qmlRegisterType<FPSCounter>(Constants::MainQmlUri, 1, 0, "FPSCounter");
qmlRegisterType<EmojiModel>(Constants::MainQmlUri, 1, 0, "EmojiModel");
qmlRegisterType<EmojiProxy>(Constants::MainQmlUri, 1, 0, "EmojiProxy");
qmlRegisterType<ImdnStatusProxy>(Constants::MainQmlUri, 1, 0, "ImdnStatusProxy");
qmlRegisterType<SoundPlayerGui>(Constants::MainQmlUri, 1, 0, "SoundPlayerGui");
qmlRegisterType<RecorderGui>(Constants::MainQmlUri, 1, 0, "RecorderGui");
qmlRegisterType<TimeZoneProxy>(Constants::MainQmlUri, 1, 0, "TimeZoneProxy");
@ -701,6 +887,14 @@ void App::initFonts() {
allFamilies << QFontDatabase::applicationFontFamilies(id);
}
}
QDirIterator itEmojis(":/emoji/font/", QDirIterator::Subdirectories);
while (itEmojis.hasNext()) {
QString ttf = itEmojis.next();
if (itEmojis.fileInfo().isFile()) {
auto id = QFontDatabase::addApplicationFont(ttf);
allFamilies << QFontDatabase::applicationFontFamilies(id);
}
}
#ifdef Q_OS_LINUX
QDirIterator itFonts(":/linux/font/", QDirIterator::Subdirectories);
while (itFonts.hasNext()) {
@ -748,8 +942,10 @@ void App::clean() {
}
void App::restart() {
mCoreModelConnection->invokeToModel([this]() {
FriendsManager::getInstance()->clearMaps();
CoreModel::getInstance()->getCore()->stop();
mCoreModelConnection->invokeToCore([this]() {
mIsRestarting = true;
closeCallsWindow();
setMainWindow(nullptr);
mEngine->clearComponentCache();
@ -775,34 +971,37 @@ void App::createCommandParser() {
//: "A free and open source SIP video-phone."
mParser->setApplicationDescription(tr("application_description"));
//: "Send an order to the application towards a command line"
mParser->addPositionalArgument("command", tr("command_line_arg_order").replace("%1", APPLICATION_NAME), "[command]");
mParser->addPositionalArgument("command", tr("command_line_arg_order").replace("%1", APPLICATION_NAME),
"[command]");
mParser->addOptions({
//: "Show this help"
{{"h", "help"}, tr("command_line_option_show_help")},
//: "Show this help"
{{"h", "help"}, tr("command_line_option_show_help")},
//{"cli-help", tr("commandLineOptionCliHelp").replace("%1", APPLICATION_NAME)},
//:"Show app version"
{{"v", "version"}, tr("command_line_option_show_app_version")},
//:"Show app version"
{{"v", "version"}, tr("command_line_option_show_app_version")},
//{"config", tr("command_line_option_config").replace("%1", EXECUTABLE_NAME), tr("command_line_option_config_arg")},
//{"config", tr("command_line_option_config").replace("%1", EXECUTABLE_NAME),
// tr("command_line_option_config_arg")},
{"fetch-config",
//: "Specify the linphone configuration file to be fetched. It will be merged with the current configuration."
tr("command_line_option_config_to_fetch")
.replace("%1", EXECUTABLE_NAME),
//: "URL, path or file"
tr("command_line_option_config_to_fetch_arg")},
//: "Specify the linphone configuration file to be fetched. It will be merged with the current
//: configuration."
tr("command_line_option_config_to_fetch").replace("%1", EXECUTABLE_NAME),
//: "URL, path or file"
tr("command_line_option_config_to_fetch_arg")},
//{{"c", "call"}, tr("command_line_option_call").replace("%1", EXECUTABLE_NAME), tr("command_line_option_call_arg")},
//{{"c", "call"}, tr("command_line_option_call").replace("%1", EXECUTABLE_NAME),
// tr("command_line_option_call_arg")},
{"minimized", tr("command_line_option_minimized")},
{"minimized", tr("command_line_option_minimized")},
//: "Log to stdout some debug information while running"
{{"V", "verbose"}, tr("command_line_option_log_to_stdout")},
//: "Log to stdout some debug information while running"
{{"V", "verbose"}, tr("command_line_option_log_to_stdout")},
//: "Print only logs from the application"
{"qt-logs-only", tr("command_line_option_print_app_logs_only")},
//: "Print only logs from the application"
{"qt-logs-only", tr("command_line_option_print_app_logs_only")},
});
}
// Should be call only at first start
@ -855,9 +1054,9 @@ bool App::notify(QObject *receiver, QEvent *event) {
try {
done = QApplication::notify(receiver, event);
} catch (const std::exception &ex) {
lCritical() << log().arg("Exception has been catch in notify: %1").arg(ex.what());
lFatal() << log().arg("Exception has been catch in notify: %1").arg(ex.what());
} catch (...) {
lCritical() << log().arg("Generic exeption has been catch in notify");
lFatal() << log().arg("Generic exeption has been catch in notify");
}
if (event->type() == QEvent::MouseButtonPress) {
auto window = findParentWindow(receiver);
@ -871,7 +1070,38 @@ bool App::notify(QObject *receiver, QEvent *event) {
return done;
}
QQuickWindow *App::getCallsWindow(QVariant callGui) {
void App::handleAppActivity() {
auto handle = [this](QSharedPointer<AccountCore> accountCore) {
if (!accountCore) return;
auto accountPresence = accountCore->getPresence();
if ((mMainWindow && mMainWindow->isActive() || (mCallsWindow && mCallsWindow->isActive())) &&
accountPresence == LinphoneEnums::Presence::Away)
accountCore->lSetPresence(LinphoneEnums::Presence::Online);
if (((!mMainWindow || !mMainWindow->isActive()) && (!mCallsWindow || !mCallsWindow->isActive())) &&
accountPresence == LinphoneEnums::Presence::Online)
accountCore->lSetPresence(LinphoneEnums::Presence::Away);
};
if (mAccountList) {
for (auto &account : mAccountList->getSharedList<AccountCore>())
handle(account);
} else {
connect(
this, &App::accountsChanged, this,
[this, &handle] {
if (mAccountList) {
for (auto &account : mAccountList->getSharedList<AccountCore>())
handle(account);
}
},
Qt::SingleShotConnection);
}
}
QQuickWindow *App::getCallsWindow() {
return mCallsWindow;
}
QQuickWindow *App::getOrCreateCallsWindow(QVariant callGui) {
mustBeInMainThread(getClassName());
if (!mCallsWindow) {
const QUrl callUrl("qrc:/qt/qml/Linphone/view/Page/Window/Call/CallsWindow.qml");
@ -906,6 +1136,7 @@ QQuickWindow *App::getCallsWindow(QVariant callGui) {
}
// window->setParent(mMainWindow);
mCallsWindow = window;
connect(mCallsWindow, &QQuickWindow::activeChanged, this, &App::handleAppActivity);
}
if (!callGui.isNull() && callGui.isValid()) mCallsWindow->setProperty("call", callGui);
return mCallsWindow;
@ -916,7 +1147,7 @@ void App::setCallsWindowProperty(const char *id, QVariant property) {
}
void App::closeCallsWindow() {
if (mCallsWindow) {
if (mCallsWindow && mCallList && !mCallList->getHaveCall()) {
mCallsWindow->close();
mCallsWindow->deleteLater();
mCallsWindow = nullptr;
@ -928,8 +1159,11 @@ QQuickWindow *App::getMainWindow() const {
}
void App::setMainWindow(QQuickWindow *data) {
if (mMainWindow) disconnect(mMainWindow, &QQuickWindow::activeChanged, this, nullptr);
if (mMainWindow != data) {
mMainWindow = data;
connect(mMainWindow, &QQuickWindow::activeChanged, this, &App::handleAppActivity);
handleAppActivity();
emit mainWindowChanged();
}
}
@ -1137,6 +1371,16 @@ bool App::event(QEvent *event) {
} else if (event->type() == QEvent::ApplicationStateChange) {
auto state = static_cast<QApplicationStateChangeEvent *>(event);
if (state->applicationState() == Qt::ApplicationActive) Utils::smartShowWindow(getLastActiveWindow());
} else if (event->type() == QEvent::ApplicationActivate) {
for (int i = 0; i < getAccountList()->rowCount(); ++i) {
auto accountCore = getAccountList()->getAt<AccountCore>(i);
emit accountCore->lSetPresence(LinphoneEnums::Presence::Online, false, false);
}
} else if (event->type() == QEvent::ApplicationDeactivate) {
for (int i = 0; i < getAccountList()->rowCount(); ++i) {
auto accountCore = getAccountList()->getAt<AccountCore>(i);
emit accountCore->lSetPresence(LinphoneEnums::Presence::Away, false, false);
}
}
return SingleApplication::event(event);
@ -1149,6 +1393,7 @@ bool App::event(QEvent *event) {
//-----------------------------------------------------------
void App::setSysTrayIcon() {
qDebug() << "setSysTrayIcon";
QQuickWindow *root = getMainWindow();
QSystemTrayIcon *systemTrayIcon =
(mSystemTrayIcon ? mSystemTrayIcon
@ -1164,7 +1409,7 @@ void App::setSysTrayIcon() {
//: "Afficher"
restoreAction->setText(visible ? tr("hide_action") : tr("show_action"));
};
setRestoreActionText(root->isVisible());
setRestoreActionText(root ? root->isVisible() : false);
connect(root, &QWindow::visibleChanged, restoreAction, setRestoreActionText);
root->connect(restoreAction, &QAction::triggered, this, [this, restoreAction](bool checked) {
@ -1180,6 +1425,9 @@ void App::setSysTrayIcon() {
QAction *quitAction = new QAction(tr("quit_action"), root);
root->connect(quitAction, &QAction::triggered, this, &App::quit);
//: "Mark all as read"
QAction *markAllReadAction = createMarkAsReadAction(root);
// trayIcon: Left click actions.
QMenu *menu = mSystemTrayIcon ? mSystemTrayIcon->contextMenu() : new QMenu();
menu->clear();
@ -1189,6 +1437,13 @@ void App::setSysTrayIcon() {
menu->addAction(restoreAction);
menu->addSeparator();
}
menu->addAction(markAllReadAction);
//: Check for update
if (mSettings->isCheckForUpdateAvailable()) {
QAction *checkForUpdateAction = new QAction(tr("check_for_update"), root);
root->connect(checkForUpdateAction, &QAction::triggered, this, [this] { checkForUpdate(true); });
menu->addAction(checkForUpdateAction);
}
menu->addAction(quitAction);
if (!mSystemTrayIcon) {
systemTrayIcon->setContextMenu(menu); // This is a Qt bug. We cannot call setContextMenu more than once. So
@ -1208,6 +1463,24 @@ void App::setSysTrayIcon() {
if (!QSystemTrayIcon::isSystemTrayAvailable()) qInfo() << "System tray is not available";
}
//-----------------------------------------------------------
// MacOS dock menu actions
//-----------------------------------------------------------
#ifdef __APPLE__
/**
* Set more actions to the MacOS Dock actions
* WARNING: call this function only on macOS
*/
void App::setMacOSDockActions() {
QMenu *menu = new QMenu();
QQuickWindow *root = getMainWindow();
QAction *markAllReadAction = createMarkAsReadAction(root);
menu->addAction(markAllReadAction);
menu->setAsDockMenu();
}
#endif
//-----------------------------------------------------------
// Locale TODO - App only in French now.
//-----------------------------------------------------------
@ -1249,3 +1522,48 @@ QString App::getSdkVersion() {
return tr('unknown');
#endif
}
QString App::getQtVersion() const {
return qVersion();
}
void App::checkForUpdate(bool requestedByUser) {
mustBeInMainThread(log().arg(Q_FUNC_INFO));
if (CoreModel::getInstance() && mCoreModelConnection) {
mCoreModelConnection->invokeToModel([this, requestedByUser] {
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
CoreModel::getInstance()->checkForUpdate(Utils::appStringToCoreString(applicationVersion()),
requestedByUser);
});
}
}
ChatGui *App::getCurrentChat() const {
return mCurrentChat;
}
void App::setCurrentChat(ChatGui *chat) {
if (chat != mCurrentChat) {
mCurrentChat = chat;
emit currentChatChanged();
}
}
float App::getScreenRatio() const {
return mScreenRatio;
}
void App::setScreenRatio(float ratio) {
mScreenRatio = ratio;
}
QAction *App::createMarkAsReadAction(QQuickWindow *window) {
QAction *markAllReadAction = new QAction(tr("mark_all_read_action"), window);
window->connect(markAllReadAction, &QAction::triggered, this, [this] {
lDebug() << "Mark all as read";
emit mAccountList->lResetMissedCalls();
emit mAccountList->lResetUnreadMessages();
mCoreModelConnection->invokeToModel([this]() { CoreModel::getInstance()->unreadNotificationsChanged(); });
});
return markAllReadAction;
}

View file

@ -18,12 +18,14 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QAction>
#include <QCommandLineParser>
#include <QQmlApplicationEngine>
#include <QSharedPointer>
#include "core/account/AccountProxy.hpp"
#include "core/call/CallProxy.hpp"
#include "core/chat/ChatGui.hpp"
#include "core/setting/SettingsCore.hpp"
#include "core/singleapplication/singleapplication.h"
#include "model/cli/CliModel.hpp"
@ -31,6 +33,7 @@
#include "tool/AbstractObject.hpp"
class CallGui;
class ChatGui;
class Thread;
class Notifier;
class QQuickWindow;
@ -43,17 +46,22 @@ class App : public SingleApplication, public AbstractObject {
Q_PROPERTY(AccountList *accounts READ getAccounts NOTIFY accountsChanged)
Q_PROPERTY(CallList *calls READ getCalls NOTIFY callsChanged)
Q_PROPERTY(QString shortApplicationVersion READ getShortApplicationVersion CONSTANT)
Q_PROPERTY(QString qtVersion READ getQtVersion CONSTANT)
Q_PROPERTY(QString gitBranchName READ getGitBranchName CONSTANT)
Q_PROPERTY(QString sdkVersion READ getSdkVersion CONSTANT)
Q_PROPERTY(ChatGui *currentChat READ getCurrentChat WRITE setCurrentChat NOTIFY currentChatChanged)
public:
App(int &argc, char *argv[]);
~App();
void setSelf(QSharedPointer<App>(me));
static App *getInstance();
static QThread *getLinphoneThread();
static Thread *getLinphoneThread();
Notifier *getNotifier() const;
EventCountNotifier *getEventCountNotifier();
int getEventCount() const;
// App::postModelAsync(<lambda>) => run lambda in model thread and continue.
// App::postModelSync(<lambda>) => run lambda in current thread and block connection.
template <typename Func, typename... Args>
@ -119,6 +127,10 @@ public:
void restart();
bool autoStartEnabled();
void setSysTrayIcon();
QSystemTrayIcon *getSystemTrayIcon() const {
return mSystemTrayIcon;
}
void updateSysTrayCount(int n);
QLocale getLocale();
void onLoggerInitialized();
@ -127,7 +139,9 @@ public:
bool getCoreStarted() const;
void setCoreStarted(bool started);
QQuickWindow *getCallsWindow(QVariant callGui = QVariant());
QQuickWindow *getCallsWindow();
Q_INVOKABLE void handleAppActivity();
QQuickWindow *getOrCreateCallsWindow(QVariant callGui = QVariant());
void setCallsWindowProperty(const char *id, QVariant property);
void closeCallsWindow();
@ -153,6 +167,14 @@ public:
QString getShortApplicationVersion();
QString getGitBranchName();
QString getSdkVersion();
QString getQtVersion() const;
Q_INVOKABLE void checkForUpdate(bool requestedByUser = false);
ChatGui *getCurrentChat() const;
void setCurrentChat(ChatGui *chat);
float getScreenRatio() const;
Q_INVOKABLE void setScreenRatio(float ratio);
#ifdef Q_OS_LINUX
Q_INVOKABLE void exportDesktopFile();
@ -172,22 +194,30 @@ signals:
void mainWindowChanged();
void coreStartedChanged(bool coreStarted);
void accountsChanged();
void defaultAccountChanged();
void callsChanged();
void currentDateChanged();
void currentChatChanged();
// void executeCommand(QString command);
private:
void createCommandParser();
QAction *createMarkAsReadAction(QQuickWindow *window);
void setMacOSDockActions(); // Should only be called on MacOS
void setAutoStart(bool enabled);
void setLocale(QString configLocale);
QCommandLineParser *mParser = nullptr;
Thread *mLinphoneThread = nullptr;
Notifier *mNotifier = nullptr;
EventCountNotifier *mEventCountNotifier = nullptr;
QSystemTrayIcon *mSystemTrayIcon = nullptr;
QQuickWindow *mMainWindow = nullptr;
QQuickWindow *mCallsWindow = nullptr;
QQuickWindow *mLastActiveWindow = nullptr;
// Holds the current chat displayed in the view
// to know if we need to display the notification
ChatGui *mCurrentChat = nullptr;
QSharedPointer<SettingsCore> mSettings;
QSharedPointer<AccountList> mAccountList;
QSharedPointer<CallList> mCallList;
@ -195,11 +225,14 @@ private:
QSharedPointer<SafeConnection<App, CliModel>> mCliModelConnection;
bool mAutoStart = false;
bool mCoreStarted = false;
bool mIsRestarting = false;
bool mPossiblyLookForAddedAccount = false;
QLocale mLocale = QLocale::system();
DefaultTranslatorCore *mTranslatorCore = nullptr;
DefaultTranslatorCore *mDefaultTranslatorCore = nullptr;
QTimer mDateUpdateTimer;
QDate mCurrentDate;
float mScreenRatio = 1;
DECLARE_ABSTRACT_OBJECT
};

View file

@ -19,6 +19,28 @@ list(APPEND _LINPHONEAPP_SOURCES
core/camera/CameraGui.cpp
core/camera/CameraDummy.cpp
core/camera/PreviewManager.cpp
core/chat/ChatCore.cpp
core/chat/ChatGui.cpp
core/chat/ChatList.cpp
core/chat/ChatProxy.cpp
core/chat/message/ChatMessageCore.cpp
core/chat/message/ChatMessageGui.cpp
core/chat/message/EventLogCore.cpp
core/chat/message/EventLogGui.cpp
core/chat/message/EventLogList.cpp
core/chat/message/EventLogProxy.cpp
core/chat/message/content/ChatMessageContentCore.cpp
core/chat/message/content/ChatMessageContentGui.cpp
core/chat/message/content/ChatMessageContentList.cpp
core/chat/message/content/ChatMessageContentProxy.cpp
core/chat/files/ChatMessageFileList.cpp
core/chat/files/ChatMessageFileProxy.cpp
core/chat/message/imdn/ImdnStatusList.cpp
core/chat/message/imdn/ImdnStatusProxy.cpp
core/emoji/EmojiList.cpp
core/emoji/EmojiModel.cpp
core/emoji/EmojiProxy.cpp
core/event-count-notifier/AbstractEventCountNotifier.cpp
core/fps-counter/FPSCounter.cpp
core/friend/FriendCore.cpp
core/friend/FriendGui.cpp
@ -64,10 +86,18 @@ list(APPEND _LINPHONEAPP_SOURCES
core/participant/ParticipantDeviceProxy.cpp
core/participant/ParticipantList.cpp
core/participant/ParticipantProxy.cpp
core/participant/ParticipantInfoList.cpp
core/participant/ParticipantInfoProxy.cpp
core/screen/ScreenList.cpp
core/screen/ScreenProxy.cpp
core/sound-player/SoundPlayerCore.cpp
core/sound-player/SoundPlayerGui.cpp
core/recorder/RecorderCore.cpp
core/recorder/RecorderGui.cpp
core/videoSource/VideoSourceDescriptorCore.cpp
core/videoSource/VideoSourceDescriptorGui.cpp
@ -99,5 +129,9 @@ else() # Use QDBus for Linux
core/singleapplication/SingleApplicationDBusPrivate.hpp
core/singleapplication/SingleApplicationDBus.cpp)
endif()
if(APPLE)
list(APPEND _LINPHONEAPP_SOURCES core/event-count-notifier/EventCountNotifierMacOs.m)
else()
list(APPEND _LINPHONEAPP_SOURCES core/event-count-notifier/EventCountNotifierSystemTrayIcon.cpp)
endif()
set(_LINPHONEAPP_SOURCES ${_LINPHONEAPP_SOURCES} PARENT_SCOPE)

View file

@ -43,18 +43,18 @@ AccountCore::AccountCore(const std::shared_ptr<linphone::Account> &account) : QO
auto address = account->getContactAddress();
mContactAddress = address ? Utils::coreStringToAppString(account->getContactAddress()->asStringUriOnly()) : "";
auto params = account->getParams()->clone();
auto identityAddress = params->getIdentityAddress()->clone();
auto identityAddress = params->getIdentityAddress();
mIdentityAddress = identityAddress ? Utils::coreStringToAppString(identityAddress->asStringUriOnly()) : "";
mPictureUri = Utils::coreStringToAppString(params->getPictureUri());
mRegistrationState = LinphoneEnums::fromLinphone(account->getState());
mIsDefaultAccount = CoreModel::getInstance()->getCore()->getDefaultAccount() == account;
// mUnreadNotifications = account->getUnreadChatMessageCount() + account->getMissedCallsCount(); // TODO
mUnreadNotifications = account->getMissedCallsCount();
mUnreadNotifications = account->getMissedCallsCount() + account->getUnreadChatMessageCount();
mDisplayName = Utils::coreStringToAppString(identityAddress->getDisplayName());
if (mDisplayName.isEmpty()) {
mDisplayName = ToolModel::getDisplayName(identityAddress);
identityAddress->setDisplayName(Utils::appStringToCoreString(mDisplayName));
params->setIdentityAddress(identityAddress);
auto copyAddress = identityAddress->clone();
copyAddress->setDisplayName(Utils::appStringToCoreString(mDisplayName));
params->setIdentityAddress(copyAddress);
account->setParams(params);
}
mRegisterEnabled = params->registerEnabled();
@ -65,9 +65,11 @@ AccountCore::AccountCore(const std::shared_ptr<linphone::Account> &account) : QO
<< "TLS"
<< "DTLS";
mTransport = LinphoneEnums::toString(LinphoneEnums::fromLinphone(params->getTransport()));
mServerAddress =
mRegistrarUri =
params->getServerAddress() ? Utils::coreStringToAppString(params->getServerAddress()->asString()) : "";
mOutboundProxyEnabled = params->outboundProxyEnabled();
auto routesAddresses = params->getRoutesAddresses();
mOutboundProxyUri =
routesAddresses.empty() ? "" : Utils::coreStringToAppString(routesAddresses.front()->asString());
auto policy = params->getNatPolicy() ? params->getNatPolicy() : account->getCore()->createNatPolicy();
mStunServer = Utils::coreStringToAppString(policy->getStunServer());
mIceEnabled = policy->iceEnabled();
@ -82,10 +84,19 @@ AccountCore::AccountCore(const std::shared_ptr<linphone::Account> &account) : QO
? Utils::coreStringToAppString(params->getAudioVideoConferenceFactoryAddress()->asString())
: "";
mLimeServerUrl = Utils::coreStringToAppString(params->getLimeServerUrl());
mCcmpServerUrl = Utils::coreStringToAppString(params->getCcmpServerUrl());
// Add listener
mAccountModel = Utils::makeQObject_ptr<AccountModel>(account); // OK
mAccountModel->setSelf(mAccountModel);
mExplicitPresence = LinphoneEnums::fromString(
Utils::coreStringToAppString(CoreModel::getInstance()->getCore()->getConfig()->getString(
ToolModel::configAccountSection(account), "explicit_presence", "")));
mPresenceNote = Utils::coreStringToAppString(CoreModel::getInstance()->getCore()->getConfig()->getString(
ToolModel::configAccountSection(account), "presence_note", ""));
mMaxPresenceNoteSize = CoreModel::getInstance()->getCore()->getConfig()->getInt(
ToolModel::configAccountSection(account), "max_presence_note_size", 140);
mPresence = mAccountModel->getPresence();
mNotificationsAllowed = mAccountModel->getNotificationsAllowed();
mDialPlan = Utils::createDialPlanVariant("", " ");
mDialPlans << mDialPlan;
@ -128,8 +139,8 @@ AccountCore::AccountCore(const AccountCore &accountCore) {
mVoicemailAddress = accountCore.mVoicemailAddress;
mTransport = accountCore.mTransport;
mTransports = accountCore.mTransports;
mServerAddress = accountCore.mServerAddress;
mOutboundProxyEnabled = accountCore.mOutboundProxyEnabled;
mRegistrarUri = accountCore.mRegistrarUri;
mOutboundProxyUri = accountCore.mOutboundProxyUri;
mStunServer = accountCore.mStunServer;
mIceEnabled = accountCore.mIceEnabled;
mAvpfEnabled = accountCore.mAvpfEnabled;
@ -138,6 +149,7 @@ AccountCore::AccountCore(const AccountCore &accountCore) {
mConferenceFactoryAddress = accountCore.mConferenceFactoryAddress;
mAudioVideoConferenceFactoryAddress = accountCore.mAudioVideoConferenceFactoryAddress;
mLimeServerUrl = accountCore.mLimeServerUrl;
mCcmpServerUrl = accountCore.mCcmpServerUrl;
}
void AccountCore::setSelf(QSharedPointer<AccountCore> me) {
@ -148,6 +160,12 @@ void AccountCore::setSelf(QSharedPointer<AccountCore> me) {
mAccountModelConnection->invokeToCore(
[this, account, state, message]() { onRegistrationStateChanged(account, state, message); });
});
mAccountModelConnection->makeConnectToModel(
&AccountModel::conferenceInformationUpdated,
[this](const std::shared_ptr<linphone::Account> &account,
const std::list<std::shared_ptr<linphone::ConferenceInfo>> &infos) {
mAccountModelConnection->invokeToCore([this]() { emit conferenceInformationUpdated(); });
});
// From Model
mAccountModelConnection->makeConnectToModel(&AccountModel::defaultAccountChanged, [this](bool isDefault) {
mAccountModelConnection->invokeToCore([this, isDefault]() { onDefaultAccountChanged(isDefault); });
@ -187,11 +205,11 @@ void AccountCore::setSelf(QSharedPointer<AccountCore> me) {
mAccountModelConnection->invokeToCore(
[this, value]() { onTransportChanged(LinphoneEnums::toString(LinphoneEnums::fromLinphone(value))); });
});
mAccountModelConnection->makeConnectToModel(&AccountModel::serverAddressChanged, [this](QString value) {
mAccountModelConnection->invokeToCore([this, value]() { onServerAddressChanged(value); });
mAccountModelConnection->makeConnectToModel(&AccountModel::registrarUriChanged, [this](QString value) {
mAccountModelConnection->invokeToCore([this, value]() { onRegistrarUriChanged(value); });
});
mAccountModelConnection->makeConnectToModel(&AccountModel::outboundProxyEnabledChanged, [this](bool value) {
mAccountModelConnection->invokeToCore([this, value]() { onOutboundProxyEnabledChanged(value); });
mAccountModelConnection->makeConnectToModel(&AccountModel::outboundProxyUriChanged, [this](QString value) {
mAccountModelConnection->invokeToCore([this, value]() { onOutboundProxyUriChanged(value); });
});
mAccountModelConnection->makeConnectToModel(&AccountModel::stunServerChanged, [this](QString value) {
mAccountModelConnection->invokeToCore([this, value]() { onStunServerChanged(value); });
@ -220,9 +238,23 @@ void AccountCore::setSelf(QSharedPointer<AccountCore> me) {
mAccountModelConnection->makeConnectToModel(&AccountModel::limeServerUrlChanged, [this](QString value) {
mAccountModelConnection->invokeToCore([this, value]() { onLimeServerUrlChanged(value); });
});
mAccountModelConnection->makeConnectToModel(&AccountModel::ccmpServerUrlChanged, [this](QString value) {
mAccountModelConnection->invokeToCore([this, value]() { onCcmpServerUrlChanged(value); });
});
mAccountModelConnection->makeConnectToModel(
&AccountModel::removed, [this]() { mAccountModelConnection->invokeToCore([this]() { emit removed(); }); });
mAccountModelConnection->makeConnectToModel(
&AccountModel::presenceChanged, [this](LinphoneEnums::Presence presence, bool userInitiated) {
mAccountModelConnection->invokeToCore([this, presence, userInitiated]() {
if (userInitiated) mExplicitPresence = presence;
else mExplicitPresence = LinphoneEnums::Presence::Undefined;
mPresence = presence;
emit presenceChanged();
});
});
// From GUI
mAccountModelConnection->makeConnectToCore(&AccountCore::lSetPictureUri, [this](QString uri) {
mAccountModelConnection->invokeToModel([this, uri]() { mAccountModel->setPictureUri(uri); });
@ -233,19 +265,17 @@ void AccountCore::setSelf(QSharedPointer<AccountCore> me) {
mAccountModelConnection->makeConnectToCore(&AccountCore::lResetMissedCalls, [this]() {
mAccountModelConnection->invokeToModel([this]() { mAccountModel->resetMissedCallsCount(); });
});
mAccountModelConnection->makeConnectToCore(&AccountCore::lResetUnreadMessages, [this]() {
mAccountModelConnection->invokeToModel([this]() {
auto chatRooms = mAccountModel->getChatRooms();
for (auto const &chatRoom : chatRooms) {
chatRoom->markAsRead();
}
});
});
mAccountModelConnection->makeConnectToCore(&AccountCore::lRefreshNotifications, [this]() {
mAccountModelConnection->invokeToModel([this]() { mAccountModel->refreshUnreadNotifications(); });
});
mCoreModelConnection = SafeConnection<AccountCore, CoreModel>::create(me, CoreModel::getInstance());
mAccountModelConnection->makeConnectToCore(&AccountCore::unreadCallNotificationsChanged, [this]() {
mAccountModelConnection->invokeToModel([this]() { CoreModel::getInstance()->unreadNotificationsChanged(); });
});
mAccountModelConnection->makeConnectToCore(&AccountCore::unreadMessageNotificationsChanged, [this]() {
mAccountModelConnection->invokeToModel([this]() { CoreModel::getInstance()->unreadNotificationsChanged(); });
});
mAccountModelConnection->makeConnectToCore(&AccountCore::unreadNotificationsChanged, [this]() {
mAccountModelConnection->invokeToModel([this]() { CoreModel::getInstance()->unreadNotificationsChanged(); });
});
mAccountModelConnection->makeConnectToCore(&AccountCore::lSetDisplayName, [this](QString displayName) {
mAccountModelConnection->invokeToModel([this, displayName]() { mAccountModel->setDisplayName(displayName); });
});
@ -260,6 +290,13 @@ void AccountCore::setSelf(QSharedPointer<AccountCore> me) {
mAccountModelConnection->makeConnectToCore(&AccountCore::lSetNotificationsAllowed, [this](bool value) {
mAccountModelConnection->invokeToModel([this, value]() { mAccountModel->setNotificationsAllowed(value); });
});
mAccountModelConnection->makeConnectToCore(
&AccountCore::lSetPresence, [this](LinphoneEnums::Presence presence, bool userInitiated, bool resetToAuto) {
mAccountModelConnection->invokeToModel(
[this, presence, userInitiated, resetToAuto, presenceNote = mPresenceNote]() {
mAccountModel->setPresence(presence, userInitiated, resetToAuto, presenceNote);
});
});
DEFINE_CORE_GET_CONNECT(mAccountModelConnection, AccountCore, AccountModel, mAccountModel, int, voicemailCount,
VoicemailCount)
@ -269,6 +306,14 @@ void AccountCore::setSelf(QSharedPointer<AccountCore> me) {
mAccountModelConnection->makeConnectToModel(&AccountModel::voicemailAddressChanged, [this](QString value) {
mAccountModelConnection->invokeToCore([this, value]() { setVoicemailAddress(value); });
});
mCoreModelConnection = SafeConnection<AccountCore, CoreModel>::create(me, CoreModel::getInstance());
mCoreModelConnection->makeConnectToModel(&CoreModel::messageReadInChatRoom,
[this] { mAccountModel->refreshUnreadNotifications(); });
mAccountModelConnection->makeConnectToModel(&AccountModel::setValueFailed, [this](const QString &errorMessage) {
mAccountModelConnection->invokeToCore([this, errorMessage]() { emit setValueFailed(errorMessage); });
});
}
void AccountCore::reset(const AccountCore &accountCore) {
@ -278,8 +323,8 @@ void AccountCore::reset(const AccountCore &accountCore) {
setMwiServerAddress(accountCore.mMwiServerAddress);
setVoicemailAddress(accountCore.mVoicemailAddress);
setTransport(accountCore.mTransport);
setServerAddress(accountCore.mServerAddress);
setOutboundProxyEnabled(accountCore.mOutboundProxyEnabled);
setRegistrarUri(accountCore.mRegistrarUri);
setOutboundProxyUri(accountCore.mOutboundProxyUri);
setStunServer(accountCore.mStunServer);
setIceEnabled(accountCore.mIceEnabled);
setAvpfEnabled(accountCore.mAvpfEnabled);
@ -288,6 +333,7 @@ void AccountCore::reset(const AccountCore &accountCore) {
setConferenceFactoryAddress(accountCore.mConferenceFactoryAddress);
setAudioVideoConferenceFactoryAddress(accountCore.mAudioVideoConferenceFactoryAddress);
setLimeServerUrl(accountCore.mLimeServerUrl);
setCcmpServerUrl(accountCore.mCcmpServerUrl);
}
const std::shared_ptr<AccountModel> &AccountCore::getModel() const {
@ -347,8 +393,8 @@ void AccountCore::setUnreadMessageNotifications(int unread) {
void AccountCore::onRegistrationStateChanged(const std::shared_ptr<linphone::Account> &account,
linphone::RegistrationState state,
const std::string &message) {
lDebug() << log().arg(Q_FUNC_INFO) << (int)state;
mRegistrationState = LinphoneEnums::fromLinphone(state);
qDebug() << log().arg(Q_FUNC_INFO) << mRegistrationState;
emit registrationStateChanged(Utils::coreStringToAppString(message));
}
@ -423,6 +469,30 @@ QString AccountCore::getHumanReadableRegistrationState() const {
}
}
QColor AccountCore::getRegistrationColor() const {
mustBeInMainThread(log().arg(Q_FUNC_INFO));
switch (mRegistrationState) {
case LinphoneEnums::RegistrationState::Ok:
return Utils::getDefaultStyleColor("success_500_main");
case LinphoneEnums::RegistrationState::Refreshing:
return Utils::getDefaultStyleColor("main2_500_main");
case LinphoneEnums::RegistrationState::Progress:
return Utils::getDefaultStyleColor("main2_500_main");
case LinphoneEnums::RegistrationState::Failed:
return Utils::getDefaultStyleColor("danger_500_main");
case LinphoneEnums::RegistrationState::None:
case LinphoneEnums::RegistrationState::Cleared:
return Utils::getDefaultStyleColor("warning_600");
default:
return " ";
}
}
QUrl AccountCore::getRegistrationIcon() const {
mustBeInMainThread(log().arg(Q_FUNC_INFO));
return Utils::getRegistrationStateIcon(mRegistrationState);
}
QString AccountCore::getHumanReadableRegistrationStateExplained() const {
switch (mRegistrationState) {
case LinphoneEnums::RegistrationState::Ok:
@ -471,12 +541,12 @@ QString AccountCore::getTransport() {
return mTransport;
}
QString AccountCore::getServerAddress() {
return mServerAddress;
QString AccountCore::getRegistrarUri() {
return mRegistrarUri;
}
bool AccountCore::getOutboundProxyEnabled() {
return mOutboundProxyEnabled;
QString AccountCore::getOutboundProxyUri() {
return mOutboundProxyUri;
}
QString AccountCore::getStunServer() {
@ -511,6 +581,10 @@ QString AccountCore::getLimeServerUrl() {
return mLimeServerUrl;
}
QString AccountCore::getCcmpServerUrl() {
return mCcmpServerUrl;
}
void AccountCore::setMwiServerAddress(QString value) {
if (mMwiServerAddress != value) {
mMwiServerAddress = value;
@ -529,32 +603,24 @@ void AccountCore::setVoicemailAddress(QString value) {
void AccountCore::setTransport(QString value) {
if (mTransport != value) {
mAccountModelConnection->invokeToModel([this, value] {
mustBeInLinphoneThread(getClassName() + Q_FUNC_INFO);
LinphoneEnums::TransportType transport;
LinphoneEnums::fromString(value, &transport);
mAccountModel->setTransport(LinphoneEnums::toLinphone(transport), false);
});
mTransport = value;
emit transportChanged();
setIsSaved(false);
}
}
void AccountCore::setServerAddress(QString value) {
if (mServerAddress != value) {
mAccountModelConnection->invokeToModel([this, value, transportString = mTransport] {
LinphoneEnums::TransportType transport;
LinphoneEnums::fromString(transportString, &transport);
mustBeInLinphoneThread(getClassName() + Q_FUNC_INFO);
mAccountModel->setServerAddress(value, LinphoneEnums::toLinphone(transport), false);
});
void AccountCore::setRegistrarUri(QString value) {
if (mRegistrarUri != value) {
mRegistrarUri = value;
emit registrarUriChanged();
setIsSaved(false);
}
}
void AccountCore::setOutboundProxyEnabled(bool value) {
if (mOutboundProxyEnabled != value) {
mOutboundProxyEnabled = value;
emit outboundProxyEnabledChanged();
void AccountCore::setOutboundProxyUri(QString value) {
if (mOutboundProxyUri != value) {
mOutboundProxyUri = value;
emit outboundProxyUriChanged();
setIsSaved(false);
}
}
@ -623,6 +689,14 @@ void AccountCore::setLimeServerUrl(QString value) {
}
}
void AccountCore::setCcmpServerUrl(QString value) {
if (mCcmpServerUrl != value) {
mCcmpServerUrl = value;
emit ccmpServerUrlChanged();
setIsSaved(false);
}
}
bool AccountCore::isSaved() const {
return mIsSaved;
}
@ -662,19 +736,20 @@ void AccountCore::onTransportChanged(QString value) {
}
}
void AccountCore::onServerAddressChanged(QString value) {
if (value != mServerAddress) {
mServerAddress = value;
emit serverAddressChanged();
void AccountCore::onRegistrarUriChanged(QString value) {
if (value != mRegistrarUri) {
mRegistrarUri = value;
emit registrarUriChanged();
}
}
void AccountCore::onOutboundProxyEnabledChanged(bool value) {
if (value != mOutboundProxyEnabled) {
mOutboundProxyEnabled = value;
emit outboundProxyEnabledChanged();
void AccountCore::onOutboundProxyUriChanged(QString value) {
if (value != mOutboundProxyUri) {
mOutboundProxyUri = value;
emit outboundProxyUriChanged();
}
}
void AccountCore::onStunServerChanged(QString value) {
if (value != mStunServer) {
mStunServer = value;
@ -734,14 +809,21 @@ void AccountCore::onLimeServerUrlChanged(QString value) {
}
}
void AccountCore::onCcmpServerUrlChanged(QString value) {
if (value != mCcmpServerUrl) {
mCcmpServerUrl = value;
emit ccmpServerUrlChanged();
}
}
void AccountCore::writeIntoModel(std::shared_ptr<AccountModel> model) const {
mustBeInLinphoneThread(getClassName() + Q_FUNC_INFO);
model->setMwiServerAddress(mMwiServerAddress);
LinphoneEnums::TransportType transport;
LinphoneEnums::fromString(mTransport, &transport);
model->setTransport(LinphoneEnums::toLinphone(transport), true);
model->setServerAddress(mServerAddress, LinphoneEnums::toLinphone(transport), true);
model->setOutboundProxyEnabled(mOutboundProxyEnabled);
model->setRegistrarUri(mRegistrarUri);
model->setOutboundProxyUri(mOutboundProxyUri);
model->setStunServer(mStunServer);
model->setIceEnabled(mIceEnabled);
model->setAvpfEnabled(mAvpfEnabled);
@ -750,27 +832,28 @@ void AccountCore::writeIntoModel(std::shared_ptr<AccountModel> model) const {
model->setConferenceFactoryAddress(mConferenceFactoryAddress);
model->setAudioVideoConferenceFactoryAddress(mAudioVideoConferenceFactoryAddress);
model->setLimeServerUrl(mLimeServerUrl);
model->setCcmpServerUrl(mCcmpServerUrl);
model->setVoicemailAddress(mVoicemailAddress);
}
void AccountCore::writeFromModel(const std::shared_ptr<AccountModel> &model) {
mustBeInLinphoneThread(getClassName() + Q_FUNC_INFO);
mUnreadCallNotifications = model->getMissedCallsCount();
mUnreadMessageNotifications = model->getUnreadMessagesCount();
mMwiServerAddress = model->getMwiServerAddress();
mTransport = LinphoneEnums::toString(LinphoneEnums::fromLinphone(model->getTransport()));
mServerAddress = model->getServerAddress();
mOutboundProxyEnabled = model->getOutboundProxyEnabled();
mStunServer = model->getStunServer();
mIceEnabled = model->getIceEnabled();
mAvpfEnabled = model->getAvpfEnabled();
mBundleModeEnabled = model->getBundleModeEnabled();
mExpire = model->getExpire();
mConferenceFactoryAddress = model->getConferenceFactoryAddress();
mAudioVideoConferenceFactoryAddress = model->getAudioVideoConferenceFactoryAddress();
mLimeServerUrl = model->getLimeServerUrl();
mVoicemailAddress = model->getVoicemailAddress();
setUnreadCallNotifications(model->getMissedCallsCount());
setUnreadMessageNotifications(model->getUnreadMessagesCount());
onMwiServerAddressChanged(model->getMwiServerAddress());
onTransportChanged(LinphoneEnums::toString(LinphoneEnums::fromLinphone(model->getTransport())));
onRegistrarUriChanged(model->getRegistrarUri());
onOutboundProxyUriChanged(model->getOutboundProxyUri());
onStunServerChanged(model->getStunServer());
onIceEnabledChanged(model->getIceEnabled());
onAvpfEnabledChanged(model->getAvpfEnabled());
onBundleModeEnabledChanged(model->getBundleModeEnabled());
onExpireChanged(model->getExpire());
onConferenceFactoryAddressChanged(model->getConferenceFactoryAddress());
onAudioVideoConferenceFactoryAddressChanged(model->getAudioVideoConferenceFactoryAddress());
onLimeServerUrlChanged(model->getLimeServerUrl());
onCcmpServerUrlChanged(model->getCcmpServerUrl());
onVoicemailAddressChanged(model->getVoicemailAddress());
}
void AccountCore::save() {
@ -802,3 +885,38 @@ void AccountCore::undo() {
});
}
}
LinphoneEnums::Presence AccountCore::getPresence() {
return mPresence;
}
QColor AccountCore::getPresenceColor() {
return Utils::getPresenceColor(mPresence);
}
QUrl AccountCore::getPresenceIcon() {
return Utils::getPresenceIcon(mPresence);
}
QString AccountCore::getPresenceStatus() {
return Utils::getPresenceStatus(mPresence);
}
void AccountCore::resetToAutomaticPresence() {
emit lSetPresence(LinphoneEnums::Presence::Online, false, true);
}
LinphoneEnums::Presence AccountCore::getExplicitPresence() {
return mExplicitPresence;
}
void AccountCore::setPresenceNote(QString presenceNote) {
if (presenceNote != mPresenceNote) {
mPresenceNote = presenceNote;
emit lSetPresence(mPresence, mExplicitPresence != LinphoneEnums::Presence::Undefined, false);
}
}
QString AccountCore::getPresenceNote() {
return mPresenceNote;
}

View file

@ -58,9 +58,9 @@ public:
QString mwiServerAddress READ getMwiServerAddress WRITE setMwiServerAddress NOTIFY mwiServerAddressChanged)
Q_PROPERTY(QStringList transports READ getTransports CONSTANT)
Q_PROPERTY(QString transport READ getTransport WRITE setTransport NOTIFY transportChanged)
Q_PROPERTY(QString serverAddress READ getServerAddress WRITE setServerAddress NOTIFY serverAddressChanged)
Q_PROPERTY(bool outboundProxyEnabled READ getOutboundProxyEnabled WRITE setOutboundProxyEnabled NOTIFY
outboundProxyEnabledChanged)
Q_PROPERTY(QString registrarUri READ getRegistrarUri WRITE setRegistrarUri NOTIFY registrarUriChanged)
Q_PROPERTY(
QString outboundProxyUri READ getOutboundProxyUri WRITE setOutboundProxyUri NOTIFY outboundProxyUriChanged)
Q_PROPERTY(QString stunServer READ getStunServer WRITE setStunServer NOTIFY stunServerChanged)
Q_PROPERTY(bool iceEnabled READ getIceEnabled WRITE setIceEnabled NOTIFY iceEnabledChanged)
Q_PROPERTY(bool avpfEnabled READ getAvpfEnabled WRITE setAvpfEnabled NOTIFY avpfEnabledChanged)
@ -75,6 +75,16 @@ public:
Q_PROPERTY(bool isSaved READ isSaved WRITE setIsSaved NOTIFY isSavedChanged)
Q_PROPERTY(
QString voicemailAddress READ getVoicemailAddress WRITE setVoicemailAddress NOTIFY voicemailAddressChanged)
Q_PROPERTY(LinphoneEnums::Presence presence READ getPresence WRITE lSetPresence NOTIFY presenceChanged)
Q_PROPERTY(QColor presenceColor READ getPresenceColor NOTIFY presenceChanged)
Q_PROPERTY(QUrl presenceIcon READ getPresenceIcon NOTIFY presenceChanged)
Q_PROPERTY(QString presenceStatus READ getPresenceStatus NOTIFY presenceChanged)
Q_PROPERTY(QColor registrationColor READ getRegistrationColor NOTIFY registrationStateChanged)
Q_PROPERTY(QUrl registrationIcon READ getRegistrationIcon NOTIFY registrationStateChanged)
Q_PROPERTY(LinphoneEnums::Presence explicitPresence MEMBER mExplicitPresence NOTIFY presenceChanged)
Q_PROPERTY(QString presenceNote READ getPresenceNote WRITE setPresenceNote NOTIFY presenceChanged)
Q_PROPERTY(int maxPresenceNoteSize MEMBER mMaxPresenceNoteSize CONSTANT)
Q_PROPERTY(QString ccmpServerUrl READ getCcmpServerUrl WRITE setCcmpServerUrl NOTIFY ccmpServerUrlChanged)
DECLARE_CORE_GET(int, voicemailCount, VoicemailCount)
static QSharedPointer<AccountCore> create(const std::shared_ptr<linphone::Account> &account);
@ -114,6 +124,8 @@ public:
void onDialPlanChanged(QVariantMap internationalPrefix);
QString getHumanReadableRegistrationState() const;
QString getHumanReadableRegistrationStateExplained() const;
QColor getRegistrationColor() const;
QUrl getRegistrationIcon() const;
bool getRegisterEnabled() const;
void onRegisterEnabledChanged(bool enabled);
@ -121,8 +133,8 @@ public:
QString getMwiServerAddress();
QString getTransport();
QStringList getTransports();
QString getServerAddress();
bool getOutboundProxyEnabled();
QString getRegistrarUri();
QString getOutboundProxyUri();
QString getStunServer();
bool getIceEnabled();
bool getAvpfEnabled();
@ -132,11 +144,12 @@ public:
QString getAudioVideoConferenceFactoryAddress();
QString getLimeServerUrl();
QString getVoicemailAddress();
QString getCcmpServerUrl();
void setMwiServerAddress(QString value);
void setTransport(QString value);
void setServerAddress(QString value);
void setOutboundProxyEnabled(bool value);
void setRegistrarUri(QString value);
void setOutboundProxyUri(QString value);
void setStunServer(QString value);
void setIceEnabled(bool value);
void setAvpfEnabled(bool value);
@ -146,6 +159,7 @@ public:
void setAudioVideoConferenceFactoryAddress(QString value);
void setLimeServerUrl(QString value);
void setVoicemailAddress(QString value);
void setCcmpServerUrl(QString value);
bool isSaved() const;
void setIsSaved(bool saved);
@ -154,8 +168,8 @@ public:
void onMwiServerAddressChanged(QString value);
void onVoicemailAddressChanged(QString value);
void onTransportChanged(QString value);
void onServerAddressChanged(QString value);
void onOutboundProxyEnabledChanged(bool value);
void onRegistrarUriChanged(QString value);
void onOutboundProxyUriChanged(QString value);
void onStunServerChanged(QString value);
void onIceEnabledChanged(bool value);
void onAvpfEnabledChanged(bool value);
@ -164,15 +178,26 @@ public:
void onConferenceFactoryAddressChanged(QString value);
void onAudioVideoConferenceFactoryAddressChanged(QString value);
void onLimeServerUrlChanged(QString value);
void onCcmpServerUrlChanged(QString value);
DECLARE_CORE_GET(bool, showMwi, ShowMwi)
Q_INVOKABLE void save();
Q_INVOKABLE void undo();
QColor getPresenceColor();
QUrl getPresenceIcon();
QString getPresenceStatus();
LinphoneEnums::Presence getPresence();
Q_INVOKABLE void resetToAutomaticPresence();
LinphoneEnums::Presence getExplicitPresence();
void setPresenceNote(QString presenceNote);
QString getPresenceNote();
signals:
void pictureUriChanged();
void registrationStateChanged(const QString &message);
void conferenceInformationUpdated();
void defaultAccountChanged(bool isDefault);
void unreadNotificationsChanged(int unread);
void unreadCallNotificationsChanged(int unread);
@ -185,8 +210,8 @@ signals:
void notificationsAllowedChanged();
void mwiServerAddressChanged();
void transportChanged();
void serverAddressChanged();
void outboundProxyEnabledChanged();
void registrarUriChanged();
void outboundProxyUriChanged();
void stunServerChanged();
void iceEnabledChanged();
void avpfEnabledChanged();
@ -198,16 +223,22 @@ signals:
void removed();
void isSavedChanged();
void voicemailAddressChanged();
void presenceChanged();
void ccmpServerUrlChanged();
void setValueFailed(const QString &error);
// Account requests
void lSetPictureUri(QString pictureUri);
void lSetDefaultAccount();
void lResetMissedCalls();
void lResetUnreadMessages();
void lRefreshNotifications();
void lSetDisplayName(QString displayName);
void lSetDialPlan(QVariantMap internationalPrefix);
void lSetRegisterEnabled(bool enabled);
void lSetNotificationsAllowed(bool value);
void lSetPresence(LinphoneEnums::Presence presence, bool userInitiated = true, bool resetToAuto = false);
protected:
void writeIntoModel(std::shared_ptr<AccountModel> model) const;
@ -231,8 +262,8 @@ private:
QString mMwiServerAddress;
QString mTransport;
QStringList mTransports;
QString mServerAddress;
bool mOutboundProxyEnabled;
QString mRegistrarUri;
QString mOutboundProxyUri;
QString mStunServer;
bool mIceEnabled;
bool mAvpfEnabled;
@ -242,8 +273,14 @@ private:
QString mAudioVideoConferenceFactoryAddress;
QString mLimeServerUrl;
QString mVoicemailAddress;
QString mCcmpServerUrl;
LinphoneEnums::Presence mPresence = LinphoneEnums::Presence::Undefined;
LinphoneEnums::Presence mExplicitPresence;
QString mPresenceNote;
int mMaxPresenceNoteSize;
bool mIsSaved = true;
std::shared_ptr<AccountModel> mAccountModel;
QSharedPointer<SafeConnection<AccountCore, AccountModel>> mAccountModelConnection;
QSharedPointer<SafeConnection<AccountCore, CoreModel>> mCoreModelConnection;

View file

@ -69,16 +69,14 @@ void AccountDeviceList::setAccount(const QSharedPointer<AccountCore> &accountCor
mAccountCore = accountCore;
lDebug() << log().arg("Set account model") << mAccountCore.get();
// oldConnect.unlock();
refreshDevices();
if (mAccountCore) refreshDevices();
// }
}
}
void AccountDeviceList::refreshDevices() {
mustBeInMainThread(log().arg(Q_FUNC_INFO));
beginResetModel();
clearData();
endResetModel();
resetData();
if (mAccountCore) {
auto requestDeviceList = [this] {
if (!mAccountManagerServicesModelConnection) return;
@ -150,14 +148,14 @@ void AccountDeviceList::setSelf(QSharedPointer<AccountDeviceList> me) {
&AccountManagerServicesModel::requestError,
[this](const std::shared_ptr<const linphone::AccountManagerServicesRequest> &request, int statusCode,
const std::string &errorMessage,
const std::shared_ptr<const linphone::Dictionary> &parameterErrors) {
lDebug() << "REQUEST ERROR" << errorMessage << "/" << int(request->getType());
QString message = QString::fromStdString(errorMessage);
if (request->getType() == linphone::AccountManagerServicesRequest::Type::GetDevicesList) {
//: "Erreur lors de la récupération des appareils"
message = tr("manage_account_no_device_found_error_message");
}
emit requestError(message);
const std::shared_ptr<const linphone::Dictionary> &parameterErrors) {
lDebug() << "REQUEST ERROR" << errorMessage << "/" << int(request->getType());
QString message = QString::fromStdString(errorMessage);
if (request->getType() == linphone::AccountManagerServicesRequest::Type::GetDevicesList) {
//: "Erreur lors de la récupération des appareils"
message = tr("manage_account_no_device_found_error_message");
}
emit requestError(message);
});
mAccountManagerServicesModelConnection->makeConnectToModel(
&AccountManagerServicesModel::devicesListFetched,

View file

@ -21,9 +21,9 @@
#ifndef ACCOUNT_DEVICE_LIST_H_
#define ACCOUNT_DEVICE_LIST_H_
#include "../proxy/ListProxy.hpp"
#include "AccountDeviceCore.hpp"
#include "core/account/AccountGui.hpp"
#include "core/proxy/ListProxy.hpp"
#include "model/account/AccountManagerServicesModel.hpp"
#include "tool/AbstractObject.hpp"
#include "tool/thread/SafeConnection.hpp"

View file

@ -61,6 +61,11 @@ void AccountList::setSelf(QSharedPointer<AccountList> me) {
auto model = AccountCore::create(it);
if (it == defaultAccount) defaultAccountCore = model;
accounts->push_back(model);
connect(model.get(), &AccountCore::unreadNotificationsChanged, this,
[this] { emit unreadNotificationsChanged(); });
connect(model.get(), &AccountCore::removed, this, [this, model]() {
disconnect(model.get(), &AccountCore::unreadNotificationsChanged, this, nullptr);
});
}
mModelConnection->invokeToCore([this, accounts, defaultAccountCore, isInitialization]() {
mustBeInMainThread(getClassName());
@ -68,8 +73,14 @@ void AccountList::setSelf(QSharedPointer<AccountList> me) {
setHaveAccount(accounts->size() > 0);
setDefaultAccount(defaultAccountCore);
if (isInitialization) setInitialized(true);
for (const QSharedPointer<AccountCore> &accountCore : *accounts) {
if (accountCore->getExplicitPresence() != LinphoneEnums::Presence::Undefined)
emit accountCore->lSetPresence(accountCore->getExplicitPresence(), true, false);
}
delete accounts;
});
// Update notification count at startup
if (isInitialization) emit unreadNotificationsChanged();
});
});
mModelConnection->makeConnectToModel(
@ -88,7 +99,8 @@ void AccountList::setSelf(QSharedPointer<AccountList> me) {
// with the open id account
mModelConnection->makeConnectToModel(&CoreModel::bearerAccountAdded, [this] {
setInitialized(false);
emit lUpdate(true); });
emit lUpdate(true);
});
mModelConnection->makeConnectToModel(
&CoreModel::globalStateChanged,
@ -158,6 +170,18 @@ void AccountList::setInitialized(bool init) {
}
}
void AccountList::lResetMissedCalls() {
for (auto &accountCore : getSharedList<AccountCore>()) {
accountCore->lResetMissedCalls();
}
}
void AccountList::lResetUnreadMessages() {
for (auto &accountCore : getSharedList<AccountCore>()) {
emit accountCore->lResetUnreadMessages();
}
}
QVariant AccountList::data(const QModelIndex &index, int role) const {
int row = index.row();
if (!index.isValid() || row < 0 || row >= mList.count()) return QVariant();

View file

@ -51,6 +51,8 @@ public:
bool isInitialized() const;
void setInitialized(bool init);
void lResetMissedCalls(); // Reset missed calls of all accounts
void lResetUnreadMessages(); // Reset unread messages of all accounts
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
signals:
@ -58,6 +60,7 @@ signals:
void haveAccountChanged();
void defaultAccountChanged();
void initializedChanged(bool init);
void unreadNotificationsChanged();
private:
bool mHaveAccount = false;

View file

@ -73,8 +73,11 @@ void CarddavCore::remove() {
void CarddavCore::setSelf(QSharedPointer<CarddavCore> me) {
mCarddavModelConnection = SafeConnection<CarddavCore, CarddavModel>::create(me, mCarddavModel);
mCarddavModelConnection->makeConnectToModel(&CarddavModel::saved, [this](bool success) {
mCarddavModelConnection->invokeToCore([this, success]() { emit saved(success); });
mCarddavModelConnection->makeConnectToModel(&CarddavModel::saved, [this](bool success, QString message) {
mCarddavModelConnection->invokeToCore([this, success, message]() {
if (success) emit App::getInstance() -> getSettings()->cardDAVAddressBookSynchronized();
emit saved(success, message);
});
});
}

View file

@ -50,7 +50,7 @@ public:
DECLARE_CORE_MEMBER(bool, storeNewFriendsInIt, StoreNewFriendsInIt)
signals:
void saved(bool success);
void saved(bool success, QString message);
private:
std::shared_ptr<CarddavModel> mCarddavModel;

View file

@ -46,8 +46,7 @@ CallHistoryCore::CallHistoryCore(const std::shared_ptr<linphone::CallLog> &callL
mustBeInLinphoneThread(getClassName());
mCallHistoryModel = std::make_shared<CallHistoryModel>(callLog);
auto addr = callLog->getRemoteAddress()->clone();
addr->clean();
auto addr = callLog->getRemoteAddress();
mStatus = LinphoneEnums::fromLinphone(callLog->getStatus());
mDate = QDateTime::fromMSecsSinceEpoch(callLog->getStartDate() * 1000);
mIsOutgoing = callLog->getDir() == linphone::Call::Dir::Outgoing;
@ -129,14 +128,16 @@ void CallHistoryCore::setSelf(QSharedPointer<CallHistoryCore> me) {
mCoreModelConnection->makeConnectToModel(&CoreModel::friendRemoved, &CallHistoryCore::onRemoved);
// Update display name when display name has been requested from magic search cause not found in linphone friends
// (required to get the right display name if ldap friends cleared)
mCoreModelConnection->makeConnectToModel(&CoreModel::magicSearchResultReceived, [this, remoteAddress = mRemoteAddress] {
auto displayName = ToolModel::getDisplayName(remoteAddress);
mCoreModelConnection->invokeToCore([this, displayName]() {
mDisplayName = displayName;
emit displayNameChanged();
});
});
// This replace the display name set by a user by a default one, use the linphone address to
// get a correct display name
// mCoreModelConnection->makeConnectToModel(&CoreModel::magicSearchResultReceived,
// [this, remoteAddress = mRemoteAddress] {
// auto displayName = ToolModel::getDisplayName(remoteAddress);
// mCoreModelConnection->invokeToCore([this, displayName]() {
// mDisplayName = displayName;
// emit displayNameChanged();
// });
// });
}
ConferenceInfoGui *CallHistoryCore::getConferenceInfoGui() const {

View file

@ -57,10 +57,12 @@ void CallHistoryList::setSelf(QSharedPointer<CallHistoryList> me) {
mModelConnection = SafeConnection<CallHistoryList, CoreModel>::create(me, CoreModel::getInstance());
mModelConnection->makeConnectToCore(&CallHistoryList::lUpdate, [this]() {
clearData();
emit listAboutToBeReset();
mModelConnection->invokeToModel([this]() {
mustBeInLinphoneThread(getClassName());
// Avoid copy to lambdas
QList<QSharedPointer<CallHistoryCore>> *callLogs = new QList<QSharedPointer<CallHistoryCore>>();
mustBeInLinphoneThread(getClassName());
std::list<std::shared_ptr<linphone::CallLog>> linphoneCallLogs;
if (auto account = CoreModel::getInstance()->getCore()->getDefaultAccount()) {
linphoneCallLogs = account->getCallLogs();

View file

@ -62,6 +62,7 @@ signals:
void lUpdate();
void lRemoveEntriesForAddress(QString address);
void lRemoveAllEntries();
void listAboutToBeReset();
private:
// Check the state from CallHistoryCore: sender() must be a CallHistoryCore.

View file

@ -27,6 +27,7 @@ DEFINE_ABSTRACT_OBJECT(CallHistoryProxy)
CallHistoryProxy::CallHistoryProxy(QObject *parent) : LimitProxy(parent) {
mHistoryList = CallHistoryList::create();
connect(mHistoryList.get(), &CallHistoryList::listAboutToBeReset, this, &CallHistoryProxy::listAboutToBeReset);
setSourceModels(new SortFilterList(mHistoryList.get(), Qt::DescendingOrder));
connect(App::getInstance(), &App::currentDateChanged, this, [this] { emit mHistoryList->lUpdate(); });
}
@ -56,7 +57,7 @@ bool CallHistoryProxy::SortFilterList::filterAcceptsRow(int sourceRow, const QMo
QRegularExpression::CaseInsensitiveOption |
QRegularExpression::UseUnicodePropertiesOption);
auto callLog = getItemAtSource<CallHistoryList, CallHistoryCore>(sourceRow);
show = callLog->mDisplayName.contains(search) || callLog->mRemoteAddress.contains(search);
show = callLog && (callLog->mDisplayName.contains(search) || callLog->mRemoteAddress.contains(search));
}
return show;
}

View file

@ -41,6 +41,9 @@ public:
Q_INVOKABLE void removeEntriesWithFilter(QString filter);
Q_INVOKABLE void reload();
signals:
void listAboutToBeReset();
protected:
QSharedPointer<CallHistoryList> mHistoryList;

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
* Copyright (c) 2010-2026 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
@ -28,6 +28,8 @@
#include "tool/Utils.hpp"
#include "tool/thread/SafeConnection.hpp"
#include <QQuickWindow>
DEFINE_ABSTRACT_OBJECT(CallCore)
/***********************************************************************/
@ -107,18 +109,17 @@ CallCore::CallCore(const std::shared_ptr<linphone::Call> &call) : QObject(nullpt
mCallModel->setSelf(mCallModel);
mDuration = call->getDuration();
mIsStarted = mDuration > 0;
mMicrophoneMuted = call->getMicrophoneMuted();
mSpeakerMuted = call->getSpeakerMuted();
auto videoDirection = call->getParams()->getVideoDirection();
auto callParams = call->getParams();
auto videoDirection = callParams->getVideoDirection();
mLocalVideoEnabled =
videoDirection == linphone::MediaDirection::SendOnly || videoDirection == linphone::MediaDirection::SendRecv;
mCameraEnabled = callParams->cameraEnabled();
auto remoteParams = call->getRemoteParams();
videoDirection = remoteParams ? remoteParams->getVideoDirection() : linphone::MediaDirection::Inactive;
mRemoteVideoEnabled =
videoDirection == linphone::MediaDirection::SendOnly || videoDirection == linphone::MediaDirection::SendRecv;
mState = LinphoneEnums::fromLinphone(call->getState());
auto remoteAddress = call->getCallLog()->getRemoteAddress()->clone();
remoteAddress->clean();
auto remoteAddress = call->getCallLog()->getRemoteAddress();
mRemoteAddress = Utils::coreStringToAppString(remoteAddress->asStringUriOnly());
mRemoteUsername = Utils::coreStringToAppString(remoteAddress->getUsername());
auto linphoneFriend = ToolModel::findFriendByAddress(remoteAddress);
@ -136,7 +137,7 @@ CallCore::CallCore(const std::shared_ptr<linphone::Call> &call) : QObject(nullpt
mTransferState = LinphoneEnums::fromLinphone(call->getTransferState());
mLocalToken = Utils::coreStringToAppString(mCallModel->getLocalAtuhenticationToken());
mRemoteTokens = mCallModel->getRemoteAtuhenticationTokens();
mEncryption = LinphoneEnums::fromLinphone(call->getParams()->getMediaEncryption());
mEncryption = LinphoneEnums::fromLinphone(callParams->getMediaEncryption());
auto tokenVerified = call->getAuthenticationTokenVerified();
mIsMismatch = call->getZrtpCacheMismatchFlag();
mIsSecured = (mEncryption == LinphoneEnums::MediaEncryption::Zrtp && tokenVerified) ||
@ -158,10 +159,12 @@ CallCore::CallCore(const std::shared_ptr<linphone::Call> &call) : QObject(nullpt
if (mIsConference) {
mConference = ConferenceCore::create(conference);
}
mMicrophoneMuted = conference ? conference->getMicrophoneMuted() : call->getMicrophoneMuted();
mSpeakerMuted = call->getSpeakerMuted();
mPaused = mState == LinphoneEnums::CallState::Pausing || mState == LinphoneEnums::CallState::Paused ||
mState == LinphoneEnums::CallState::PausedByRemote;
mRecording = call->getParams() && call->getParams()->isRecording();
mRecording = callParams && callParams->isRecording();
mRemoteRecording = call->getRemoteParams() && call->getRemoteParams()->isRecording();
auto settingsModel = SettingsModel::getInstance();
mMicrophoneVolume = call->getRecordVolume();
@ -194,8 +197,8 @@ void CallCore::setSelf(QSharedPointer<CallCore> me) {
mCallModelConnection->makeConnectToModel(&CallModel::speakerMutedChanged, [this](bool isMuted) {
mCallModelConnection->invokeToCore([this, isMuted]() { setSpeakerMuted(isMuted); });
});
mCallModelConnection->makeConnectToCore(&CallCore::lSetLocalVideoEnabled, [this](bool enabled) {
mCallModelConnection->invokeToModel([this, enabled]() { mCallModel->setLocalVideoEnabled(enabled); });
mCallModelConnection->makeConnectToCore(&CallCore::lSetCameraEnabled, [this](bool enabled) {
mCallModelConnection->invokeToModel([this, enabled]() { mCallModel->setCameraEnabled(enabled); });
});
mCallModelConnection->makeConnectToCore(&CallCore::lStartRecording, [this]() {
mCallModelConnection->invokeToModel([this]() { mCallModel->startRecording(); });
@ -205,14 +208,15 @@ void CallCore::setSelf(QSharedPointer<CallCore> me) {
});
mCallModelConnection->makeConnectToModel(
&CallModel::recordingChanged, [this](const std::shared_ptr<linphone::Call> &call, bool recording) {
mCallModelConnection->invokeToCore([this, recording]() {
auto recordFile = QString::fromStdString(mCallModel->getRecordFile());
mCallModelConnection->invokeToCore([this, recording, recordFile]() {
setRecording(recording);
if (recording == false) {
//: "Enregistrement terminé"
Utils::showInformationPopup(tr("call_record_end_message"),
//: "L'appel a été enregistré dans le fichier : %1"
tr("call_record_saved_in_file_message").arg(QString::fromStdString(mCallModel->getRecordFile())),
true, App::getInstance()->getCallsWindow());
//: "Enregistrement terminé"
Utils::showInformationPopup(tr("call_record_end_message"),
//: "L'appel a été enregistré dans le fichier : %1"
tr("call_record_saved_in_file_message").arg(recordFile), true,
App::getInstance()->getOrCreateCallsWindow());
}
});
});
@ -244,6 +248,9 @@ void CallCore::setSelf(QSharedPointer<CallCore> me) {
mCallModelConnection->makeConnectToModel(&CallModel::localVideoEnabledChanged, [this](bool enabled) {
mCallModelConnection->invokeToCore([this, enabled]() { setLocalVideoEnabled(enabled); });
});
mCallModelConnection->makeConnectToModel(&CallModel::cameraEnabledChanged, [this](bool enabled) {
mCallModelConnection->invokeToCore([this, enabled]() { setCameraEnabled(enabled); });
});
mCallModelConnection->makeConnectToModel(&CallModel::durationChanged, [this](int duration) {
mCallModelConnection->invokeToCore([this, duration]() { setDuration(duration); });
});
@ -261,6 +268,7 @@ void CallCore::setSelf(QSharedPointer<CallCore> me) {
bool isConf = call && call->getConference() != nullptr;
auto subject = call->getConference() ? Utils::coreStringToAppString(call->getConference()->getSubject()) : "";
mCallModelConnection->invokeToCore([this, state, subject, isConf]() {
lDebug() << log().arg("::onStateChanged") << LinphoneEnums::fromLinphone(state);
setRecordable(state == linphone::Call::State::StreamsRunning);
setPaused(state == linphone::Call::State::Paused || state == linphone::Call::State::PausedByRemote);
if (mConference) mConference->setSubject(subject);
@ -308,8 +316,8 @@ void CallCore::setSelf(QSharedPointer<CallCore> me) {
[this, call, encryption, tokenVerified, localToken, remoteTokens, isCaseMismatch]() {
setLocalToken(localToken);
setRemoteTokens(remoteTokens);
setIsMismatch(isCaseMismatch);
setTokenVerified(tokenVerified);
setIsMismatch(isCaseMismatch);
setTokenVerified(tokenVerified);
setEncryption(encryption);
});
auto mediaEncryption = call->getParams()->getMediaEncryption();
@ -321,7 +329,7 @@ void CallCore::setSelf(QSharedPointer<CallCore> me) {
zrtpStats.mHashAlgorithm = Utils::coreStringToAppString(stats->getZrtpHashAlgo());
zrtpStats.mAuthenticationAlgorithm = Utils::coreStringToAppString(stats->getZrtpAuthTagAlgo());
zrtpStats.mSasAlgorithm = Utils::coreStringToAppString(stats->getZrtpSasAlgo());
zrtpStats.mIsPostQuantum = stats->isZrtpKeyAgreementAlgoPostQuantum();
zrtpStats.mIsPostQuantum = stats->isZrtpKeyAgreementAlgoPostQuantum();
mCallModelConnection->invokeToCore([this, zrtpStats]() { setZrtpStats(zrtpStats); });
}
});
@ -343,6 +351,8 @@ void CallCore::setSelf(QSharedPointer<CallCore> me) {
});
mCallModelConnection->makeConnectToModel(&CallModel::conferenceChanged, [this]() {
auto conference = mCallModel->getMonitor()->getConference();
// Force enable video if in conference to handle screen sharing
if (conference && !mCallModel->videoEnabled()) mCallModel->enableVideo(true);
QSharedPointer<ConferenceCore> core = conference ? ConferenceCore::create(conference) : nullptr;
mCallModelConnection->invokeToCore([this, core]() { setConference(core); });
});
@ -388,23 +398,23 @@ void CallCore::setSelf(QSharedPointer<CallCore> me) {
auto codecType = playloadType ? playloadType->getMimeType() : "";
auto codecRate = playloadType ? playloadType->getClockRate() / 1000 : 0;
audioStats.mCodec =
//: "Codec: %1 / %2 kHz"
tr("call_stats_codec_label").arg(Utils::coreStringToAppString(codecType)).arg(codecRate);
//: "Codec: %1 / %2 kHz"
tr("call_stats_codec_label").arg(Utils::coreStringToAppString(codecType)).arg(codecRate);
auto linAudioStats = call->getAudioStats();
if (linAudioStats) {
//: "Bande passante : %1 %2 kbits/s %3 %4 kbits/s"
audioStats.mBandwidth = tr("call_stats_bandwidth_label")
//: "Bande passante : %1 %2 kbits/s %3 %4 kbits/s"
audioStats.mBandwidth = tr("call_stats_bandwidth_label")
.arg("")
.arg(round(linAudioStats->getUploadBandwidth()))
.arg("")
.arg(round(linAudioStats->getDownloadBandwidth()));
//: "Taux de perte: %1% %2%"
audioStats.mLossRate = tr("call_stats_loss_rate_label")
//: "Taux de perte: %1% %2%"
audioStats.mLossRate = tr("call_stats_loss_rate_label")
.arg(linAudioStats->getSenderLossRate())
.arg(linAudioStats->getReceiverLossRate());
//: "Tampon de gigue: %1 ms"
audioStats.mJitterBufferSize =
tr("call_stats_jitter_buffer_label").arg(linAudioStats->getJitterBufferSizeMs());
//: "Tampon de gigue: %1 ms"
audioStats.mJitterBufferSize =
tr("call_stats_jitter_buffer_label").arg(linAudioStats->getJitterBufferSizeMs());
}
setAudioStats(audioStats);
} else if (stats->getType() == linphone::StreamType::Video) {
@ -414,15 +424,15 @@ void CallCore::setSelf(QSharedPointer<CallCore> me) {
auto codecType = playloadType ? playloadType->getMimeType() : "";
auto codecRate = playloadType ? playloadType->getClockRate() / 1000 : 0;
videoStats.mCodec =
tr("call_stats_codec_label").arg(Utils::coreStringToAppString(codecType)).arg(codecRate);
tr("call_stats_codec_label").arg(Utils::coreStringToAppString(codecType)).arg(codecRate);
auto linVideoStats = call->getVideoStats();
if (stats) {
videoStats.mBandwidth = tr("call_stats_bandwidth_label")
videoStats.mBandwidth = tr("call_stats_bandwidth_label")
.arg("")
.arg(round(linVideoStats->getUploadBandwidth()))
.arg("")
.arg(round(linVideoStats->getDownloadBandwidth()));
videoStats.mLossRate = tr("call_stats_loss_rate_label")
videoStats.mLossRate = tr("call_stats_loss_rate_label")
.arg(linVideoStats->getSenderLossRate())
.arg(linVideoStats->getReceiverLossRate());
}
@ -430,17 +440,55 @@ void CallCore::setSelf(QSharedPointer<CallCore> me) {
params->getSentVideoDefinition() ? params->getSentVideoDefinition()->getName() : "";
auto receivedResolution =
params->getReceivedVideoDefinition() ? params->getReceivedVideoDefinition()->getName() : "";
//: "Définition vidéo : %1 %2 %3 %4"
videoStats.mResolution = tr("call_stats_resolution_label")
//: "Définition vidéo : %1 %2 %3 %4"
videoStats.mResolution = tr("call_stats_resolution_label")
.arg("", Utils::coreStringToAppString(sentResolution), "",
Utils::coreStringToAppString(receivedResolution));
auto sentFps = params->getSentFramerate();
auto receivedFps = params->getReceivedFramerate();
//: "FPS : %1 %2 %3 %4"
videoStats.mFps = tr("call_stats_fps_label").arg("").arg(sentFps).arg("").arg(receivedFps);
//: "FPS : %1 %2 %3 %4"
videoStats.mFps = tr("call_stats_fps_label").arg("").arg(sentFps).arg("").arg(receivedFps);
setVideoStats(videoStats);
}
});
mCallModelConnection->makeConnectToModel(&CallModel::headsetAnswerCallRequested, [this]() {
mCallModelConnection->invokeToCore([this]() {
const auto callList = App::getInstance()->getCallList();
const auto currentPendingCall = callList->getFirstIncommingPendingCall();
if (!currentPendingCall.isNull()) {
const auto gui = new CallGui(currentPendingCall);
Utils::openCallsWindow(gui);
currentPendingCall->lAccept(false);
}
});
});
mCallModelConnection->makeConnectToModel(&CallModel::headsetEndCallRequested, [this]() {
mCallModelConnection->invokeToCore([this]() {
const auto window = Utils::getOrCreateCallsWindow();
window->setProperty("callTerminatedByUser", true);
lTerminate();
});
});
mCallModelConnection->makeConnectToModel(&CallModel::headsetHoldCallRequested, [this]() {
mCallModelConnection->invokeToCore([this]() { lSetPaused(true); });
});
mCallModelConnection->makeConnectToModel(&CallModel::headsetMicrophoneMuteToggled, [this](bool mute) {
mCallModelConnection->invokeToCore([this, mute]() { lSetMicrophoneMuted(mute); });
});
mCallModelConnection->makeConnectToModel(&CallModel::headsetRejectCallRequested, [this]() {
mCallModelConnection->invokeToCore([this]() {
const auto callList = App::getInstance()->getCallList();
const auto currentPendingCall = callList->getFirstIncommingPendingCall();
if (!currentPendingCall.isNull()) {
currentPendingCall->lDecline();
}
});
});
mCallModelConnection->makeConnectToModel(&CallModel::headsetResumeCallRequested, [this]() {
mCallModelConnection->invokeToCore([this]() { lSetPaused(false); });
});
if (mShouldFindRemoteFriend) findRemoteFriend(me);
}
@ -557,6 +605,18 @@ void CallCore::setLocalVideoEnabled(bool enabled) {
}
}
bool CallCore::getCameraEnabled() const {
return mCameraEnabled;
}
void CallCore::setCameraEnabled(bool enabled) {
if (mCameraEnabled != enabled) {
mCameraEnabled = enabled;
lDebug() << "CameraEnabled: " << mCameraEnabled;
emit cameraEnabledChanged();
}
}
bool CallCore::getPaused() const {
return mPaused;
}

View file

@ -117,8 +117,8 @@ public:
Q_PROPERTY(QStringList remoteTokens WRITE setRemoteTokens MEMBER mRemoteTokens NOTIFY remoteTokensChanged)
Q_PROPERTY(
bool remoteVideoEnabled READ getRemoteVideoEnabled WRITE setRemoteVideoEnabled NOTIFY remoteVideoEnabledChanged)
Q_PROPERTY(
bool localVideoEnabled READ getLocalVideoEnabled WRITE lSetLocalVideoEnabled NOTIFY localVideoEnabledChanged)
Q_PROPERTY(bool localVideoEnabled READ getLocalVideoEnabled NOTIFY localVideoEnabledChanged)
Q_PROPERTY(bool cameraEnabled READ getCameraEnabled WRITE lSetCameraEnabled NOTIFY cameraEnabledChanged)
Q_PROPERTY(bool recording READ getRecording WRITE setRecording NOTIFY recordingChanged)
Q_PROPERTY(bool remoteRecording READ getRemoteRecording WRITE setRemoteRecording NOTIFY remoteRecordingChanged)
Q_PROPERTY(bool recordable READ getRecordable WRITE setRecordable NOTIFY recordableChanged)
@ -201,6 +201,9 @@ public:
bool getLocalVideoEnabled() const;
void setLocalVideoEnabled(bool enabled);
bool getCameraEnabled() const;
void setCameraEnabled(bool enabled);
bool getRecording() const;
void setRecording(bool recording);
@ -255,6 +258,7 @@ signals:
void remoteTokensChanged();
void remoteVideoEnabledChanged(bool remoteVideoEnabled);
void localVideoEnabledChanged();
void cameraEnabledChanged();
void recordingChanged();
void remoteRecordingChanged();
void recordableChanged();
@ -275,7 +279,7 @@ signals:
void lTerminateAllCalls(); // Hangup all calls
void lSetSpeakerMuted(bool muted);
void lSetMicrophoneMuted(bool isMuted);
void lSetLocalVideoEnabled(bool enabled);
void lSetCameraEnabled(bool enabled);
void lSetVideoEnabled(bool enabled);
void lSetPaused(bool paused);
void lTransferCall(QString address);
@ -331,6 +335,7 @@ private:
bool mSpeakerMuted = false;
bool mMicrophoneMuted = false;
bool mLocalVideoEnabled = false;
bool mCameraEnabled = false;
bool mVideoEnabled = false;
bool mPaused = false;
bool mRemoteVideoEnabled = false;

View file

@ -22,6 +22,7 @@
#include "CallCore.hpp"
#include "CallGui.hpp"
#include "core/App.hpp"
#include "model/tool/ToolModel.hpp"
#include <QSharedPointer>
#include <linphone++/linphone.hh>
@ -96,40 +97,32 @@ void CallList::setSelf(QSharedPointer<CallList> me) {
bool enablingVideo = false;
if (currentCall) enablingVideo = currentCall->getCurrentParams()->videoEnabled();
if (!conference) {
auto parameters = core->createConferenceParams(conference);
auto audioVideoConfFactoryUri =
core->getDefaultAccount()->getParams()->getAudioVideoConferenceFactoryAddress();
if (audioVideoConfFactoryUri) {
parameters->setConferenceFactoryAddress(audioVideoConfFactoryUri);
parameters->setSubject("Meeting");
QString subject = audioVideoConfFactoryUri
//: Remote group call
? tr("remote_group_call")
//: "Local group call"
: tr("local_group_call");
auto conference = ToolModel::createConference(subject, nullptr);
if (!conference) {
lWarning() << log().arg("Failed to merge calls");
mModelConnection->invokeToCore([] {
Utils::showInformationPopup(tr("info_popup_error_title"),
//: Failed to merge calls !
tr("info_popup_merge_calls_failed_message"), false);
});
return;
} else {
parameters->setSubject("Local meeting");
}
parameters->enableVideo(enablingVideo);
conference = core->createConferenceWithParams(parameters);
}
std::list<std::shared_ptr<linphone::Address>> allLinphoneAddresses;
std::list<std::shared_ptr<linphone::Address>> newCalls;
std::list<std::shared_ptr<linphone::Call>> runningCallsToAdd;
for (auto call : currentCalls) {
if (!call->getConference()) {
runningCallsToAdd.push_back(call);
conference->addParticipants(currentCalls);
}
}
// 1) Add running calls
if (runningCallsToAdd.size() > 0) {
conference->addParticipants(runningCallsToAdd);
}
// emit lUpdate();
});
});
mModelConnection->makeConnectToModel(&CoreModel::firstCallStarted,
[this]() { mModelConnection->invokeToCore([this]() { lUpdate(); }); });
[this]() { mModelConnection->invokeToCore([this]() { lUpdate(); }); });
mModelConnection->makeConnectToModel(&CoreModel::lastCallEnded, [this]() {
mModelConnection->invokeToCore([this]() {
setHaveCall(false);
@ -158,12 +151,8 @@ CallGui *CallList::getCurrentCall() const {
else return nullptr;
}
void CallList::setCurrentCall(CallGui* callGui) {
auto callCore = callGui ? callGui->mCore : nullptr;
if (mCurrentCall != callCore) {
mCurrentCall = callCore;
emit currentCallChanged();
}
void CallList::setCurrentCall(CallGui *callGui) {
setCurrentCallCore(callGui ? callGui->mCore : nullptr);
}
void CallList::setCurrentCallCore(QSharedPointer<CallCore> call) {
@ -184,16 +173,21 @@ void CallList::setHaveCall(bool haveCall) {
}
}
QSharedPointer<CallCore> CallList::getNextCall() const {
QSharedPointer<CallCore> call;
QSharedPointer<CallCore> CallList::getNextCall() {
auto currentCall = getCurrentCallCore();
for (auto it = mList.rbegin(); !call && it != mList.rend(); ++it) {
if (*it != currentCall) {
call = it->objectCast<CallCore>();
}
for (auto &item : getSharedList<CallCore>()) {
if (item != currentCall) return item;
}
return nullptr;
}
return call;
QSharedPointer<CallCore> CallList::getFirstIncommingPendingCall() {
auto callList = getSharedList<CallCore>();
auto it = std::find_if(callList.begin(), callList.end(), [](const QSharedPointer<CallCore> call) {
return call->getState() == LinphoneEnums::CallState::IncomingReceived;
});
if (it == callList.end()) return nullptr;
return *it;
}
void CallList::onStateChanged() {
@ -202,21 +196,26 @@ void CallList::onStateChanged() {
case LinphoneEnums::CallState::StreamsRunning:
case LinphoneEnums::CallState::Resuming: {
auto sharedCall = get(call);
setCurrentCallCore(sharedCall.objectCast<CallCore>());
setCurrentCallCore(sharedCall ? sharedCall.objectCast<CallCore>() : nullptr);
break;
}
case LinphoneEnums::CallState::Released: {
auto sharedCall = get(call);
auto currentCall = getCurrentCallCore();
// Update current call
if (sharedCall == currentCall) {
// Unpause the next call. The current call will change on resume.
// Assumption: All calls that are not the current are paused.
auto nextCall = getNextCall();
if (nextCall) nextCall->lSetPaused(false);
if (sharedCall) {
auto currentCall = getCurrentCallCore();
sharedCall->disconnect(this);
// Update current call
if (currentCall == sharedCall) {
auto nextCall = getNextCall();
if (nextCall) {
// Unpause the next call. The current call will change on resume.
// Assumption: All calls that are not the current are paused.
nextCall->lSetPaused(false);
}
setCurrentCallCore(nextCall);
}
bool removed = remove(sharedCall);
}
sharedCall->disconnect(this);
remove(sharedCall);
break;
}
default: {

View file

@ -33,7 +33,7 @@ class CoreModel;
class CallList : public ListProxy, public AbstractObject {
Q_OBJECT
Q_PROPERTY(CallGui* currentCall READ getCurrentCall WRITE setCurrentCall NOTIFY currentCallChanged)
Q_PROPERTY(CallGui *currentCall READ getCurrentCall WRITE setCurrentCall NOTIFY currentCallChanged)
public:
static QSharedPointer<CallList> create();
// Create a CallCore and make connections to List.
@ -45,7 +45,7 @@ public:
CallGui *getCurrentCall() const; // Used for Ui
QSharedPointer<CallCore> getCurrentCallCore() const;
void setCurrentCall(CallGui* callGui);
void setCurrentCall(CallGui *callGui);
void setCurrentCallCore(QSharedPointer<CallCore> call);
bool getHaveCall() const;
@ -53,7 +53,9 @@ public:
// Get the next call after the current one. Used to switch the current call.
// At the moment, it select the last call in the list.
QSharedPointer<CallCore> getNextCall() const;
QSharedPointer<CallCore> getNextCall();
QSharedPointer<CallCore> getFirstIncommingPendingCall();
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
signals:

View file

@ -25,20 +25,32 @@
DEFINE_ABSTRACT_OBJECT(CallProxy)
CallProxy::CallProxy(QObject *parent) : LimitProxy(parent) {
CallProxy::CallProxy() : SortFilterProxy() {
mShowCurrentCall = true;
}
CallProxy::~CallProxy() {
}
CallGui *CallProxy::getCurrentCall() {
auto model = getListModel<CallList>();
auto model = qobject_cast<CallList *>(sourceModel());
if (!mCurrentCall && model) mCurrentCall = model->getCurrentCall();
return mCurrentCall;
}
void CallProxy::setShowCurrentCall(bool show) {
if (mShowCurrentCall != show) {
mShowCurrentCall = show;
emit showCurrentCallChanged();
}
}
bool CallProxy::showCurrentCall() const {
return mShowCurrentCall;
}
void CallProxy::setCurrentCall(CallGui *call) {
getListModel<CallList>()->setCurrentCall(call);
qobject_cast<CallList *>(sourceModel())->setCurrentCall(call);
}
// Reset the default account to let UI build its new object if needed.
@ -48,12 +60,12 @@ void CallProxy::resetCurrentCall() {
}
bool CallProxy::getHaveCall() const {
auto model = getListModel<CallList>();
auto model = qobject_cast<CallList *>(sourceModel());
return model ? model->getHaveCall() : false;
}
void CallProxy::setSourceModel(QAbstractItemModel *model) {
auto oldCallList = getListModel<CallList>();
auto oldCallList = qobject_cast<CallList *>(sourceModel());
if (oldCallList) {
disconnect(oldCallList);
}
@ -63,24 +75,24 @@ void CallProxy::setSourceModel(QAbstractItemModel *model) {
connect(newCallList, &CallList::haveCallChanged, this, &CallProxy::haveCallChanged, Qt::QueuedConnection);
connect(this, &CallProxy::lMergeAll, newCallList, &CallList::lMergeAll);
}
setSourceModels(new SortFilterList(model, Qt::AscendingOrder));
QSortFilterProxyModel::setSourceModel(model);
}
bool CallProxy::SortFilterList::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const {
bool CallProxy::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const {
bool show = (mFilterText.isEmpty() || mFilterText == "*");
auto callList = qobject_cast<CallList *>(sourceModel());
auto call = callList->getAt<CallCore>(sourceRow);
if (!mShowCurrentCall && call == callList->getCurrentCallCore()) return false;
if (!show) {
QRegularExpression search(QRegularExpression::escape(mFilterText),
QRegularExpression::CaseInsensitiveOption |
QRegularExpression::UseUnicodePropertiesOption);
auto call = qobject_cast<CallList *>(sourceModel())->getAt<CallCore>(sourceRow);
show = call->getRemoteAddress().contains(search);
}
return show;
}
bool CallProxy::SortFilterList::lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const {
bool CallProxy::lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const {
auto l = getItemAtSource<CallList, CallCore>(sourceLeft.row());
auto r = getItemAtSource<CallList, CallCore>(sourceRight.row());

View file

@ -28,15 +28,14 @@
// =============================================================================
class CallProxy : public LimitProxy, public AbstractObject {
class CallProxy : public SortFilterProxy, public AbstractObject {
Q_OBJECT
Q_PROPERTY(CallGui *currentCall READ getCurrentCall WRITE setCurrentCall NOTIFY currentCallChanged)
Q_PROPERTY(bool haveCall READ getHaveCall NOTIFY haveCallChanged)
Q_PROPERTY(bool showCurrentCall READ showCurrentCall WRITE setShowCurrentCall NOTIFY showCurrentCallChanged)
public:
DECLARE_SORTFILTER_CLASS()
CallProxy(QObject *parent = Q_NULLPTR);
CallProxy();
~CallProxy();
// Get a new object from List or give the stored one.
@ -48,15 +47,23 @@ public:
bool getHaveCall() const;
void setShowCurrentCall(bool show);
bool showCurrentCall() const;
void setSourceModel(QAbstractItemModel *sourceModel) override;
virtual bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
virtual bool lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const override;
signals:
void lMergeAll();
void currentCallChanged();
void haveCallChanged();
void showCurrentCallChanged();
protected:
CallGui *mCurrentCall = nullptr; // When null, a new UI object is build from List
bool mShowCurrentCall = false;
DECLARE_ABSTRACT_OBJECT
};

View file

@ -71,10 +71,12 @@ QQuickFramebufferObject::Renderer *PreviewManager::subscribe(const CameraGui *ca
App::postModelBlock([&renderer, isFirst = (itCandidate == mCandidates.begin()),
name = itCandidate->first->getQmlName()]() {
renderer =
(QQuickFramebufferObject::Renderer *)CoreModel::getInstance()->getCore()->createNativePreviewWindowId();
(QQuickFramebufferObject::Renderer *)CoreModel::getInstance()->getCore()->createNativePreviewWindowId(
nullptr);
if (!renderer) { // TODO debug
renderer =
(QQuickFramebufferObject::Renderer *)CoreModel::getInstance()->getCore()->createNativePreviewWindowId();
(QQuickFramebufferObject::Renderer *)CoreModel::getInstance()->getCore()->createNativePreviewWindowId(
nullptr);
}
if (isFirst) {
lDebug() << "[PreviewManager] " << name << " Set Native Preview Id with " << renderer;

View file

@ -0,0 +1,686 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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 "ChatCore.hpp"
#include "core/App.hpp"
#include "core/chat/message/content/ChatMessageContentGui.hpp"
#include "core/friend/FriendCore.hpp"
#include "core/setting/SettingsCore.hpp"
#include "model/chat/message/EventLogModel.hpp"
#include "model/core/CoreModel.hpp"
#include "model/friend/FriendModel.hpp"
#include "model/tool/ToolModel.hpp"
#include "tool/Utils.hpp"
#include <QQuickWindow>
DEFINE_ABSTRACT_OBJECT(ChatCore)
/***********************************************************************/
QSharedPointer<ChatCore> ChatCore::create(const std::shared_ptr<linphone::ChatRoom> &chatRoom) {
auto sharedPointer = QSharedPointer<ChatCore>(new ChatCore(chatRoom), &QObject::deleteLater);
sharedPointer->setSelf(sharedPointer);
sharedPointer->moveToThread(App::getInstance()->thread());
return sharedPointer;
}
ChatCore::ChatCore(const std::shared_ptr<linphone::ChatRoom> &chatRoom) : QObject(nullptr) {
// lDebug() << "[ChatCore] new" << this;
mustBeInLinphoneThread(getClassName());
App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership);
mLastUpdatedTime = QDateTime::fromSecsSinceEpoch(chatRoom->getLastUpdateTime());
auto chatRoomAddress = chatRoom->getPeerAddress();
mChatRoomAddress = Utils::coreStringToAppString(chatRoomAddress->asStringUriOnly());
if (chatRoom->hasCapability((int)linphone::ChatRoom::Capabilities::Basic)) {
mTitle = ToolModel::getDisplayName(chatRoomAddress);
mAvatarUri = ToolModel::getDisplayName(chatRoomAddress);
mParticipantAddress = Utils::coreStringToAppString(chatRoomAddress->asStringUriOnly());
mIsGroupChat = false;
mIsBasic = true;
mConferenceJoined = true;
} else {
mIsBasic = false;
auto participants = chatRoom->getParticipants();
if (chatRoom->hasCapability((int)linphone::ChatRoom::Capabilities::OneToOne)) {
if (participants.size() > 0) {
auto peer = participants.front();
auto peerAddress = peer->getAddress();
if (peer) mTitle = ToolModel::getDisplayName(peerAddress);
mAvatarUri = ToolModel::getDisplayName(peerAddress);
if (participants.size() == 1) {
if (peerAddress) mParticipantAddress = Utils::coreStringToAppString(peerAddress->asStringUriOnly());
}
}
mIsGroupChat = false;
} else if (chatRoom->hasCapability((int)linphone::ChatRoom::Capabilities::Conference)) {
mTitle = Utils::coreStringToAppString(chatRoom->getSubject());
mAvatarUri = Utils::coreStringToAppString(chatRoom->getSubject());
mIsGroupChat = true;
mMeAdmin = chatRoom->getMe() && chatRoom->getMe()->isAdmin();
}
mConferenceJoined = participants.size() != 0;
}
mUnreadMessagesCount = chatRoom->getUnreadMessagesCount();
connect(this, &ChatCore::unreadMessagesCountChanged, this, [this] {
if (mUnreadMessagesCount == 0) emit lMarkAsRead();
});
mChatModel = Utils::makeQObject_ptr<ChatModel>(chatRoom);
mChatModel->setSelf(mChatModel);
auto lastMessage = chatRoom->getLastMessageInHistory();
mLastMessage = lastMessage ? ChatMessageCore::create(lastMessage) : nullptr;
int filter = mIsGroupChat ? static_cast<int>(linphone::ChatRoom::HistoryFilter::ChatMessage) |
static_cast<int>(linphone::ChatRoom::HistoryFilter::InfoNoDevice)
: static_cast<int>(linphone::ChatRoom::HistoryFilter::ChatMessage);
mIdentifier = Utils::coreStringToAppString(chatRoom->getIdentifier());
mChatRoomState = LinphoneEnums::fromLinphone(chatRoom->getState());
mIsEncrypted = chatRoom->hasCapability((int)linphone::ChatRoom::Capabilities::Encrypted);
auto localAccount = ToolModel::findAccount(chatRoom->getLocalAddress());
mLocalAddress = Utils::coreStringToAppString(chatRoom->getLocalAddress()->asStringUriOnly());
bool associatedAccountHasIMEncryptionMandatory =
localAccount && localAccount->getParams() &&
localAccount->getParams()->getInstantMessagingEncryptionMandatory();
mIsReadOnly = chatRoom->isReadOnly() || (!mIsEncrypted && associatedAccountHasIMEncryptionMandatory);
connect(this, &ChatCore::eventsInserted, this, &ChatCore::lUpdateLastMessage);
mEphemeralEnabled = chatRoom->ephemeralEnabled();
mEphemeralLifetime = chatRoom->ephemeralEnabled() ? chatRoom->getEphemeralLifetime() : 0;
mIsMuted = chatRoom->getMuted();
mParticipants = buildParticipants(chatRoom);
connect(this, &ChatCore::participantsChanged, this, [this] {
// refresh secured status of the chatroom
setIsSecured(computeSecuredStatus());
});
mIsSecured = computeSecuredStatus();
}
ChatCore::~ChatCore() {
lDebug() << "[ChatCore] delete" << this;
mustBeInMainThread("~" + getClassName());
if (mChatModelConnection) mChatModelConnection->disconnect();
emit mChatModel->removeListener();
}
void ChatCore::setSelf(QSharedPointer<ChatCore> me) {
mChatModelConnection = SafeConnection<ChatCore, ChatModel>::create(me, mChatModel);
mChatModelConnection->makeConnectToCore(&ChatCore::lDeleteHistory, [this]() {
mChatModelConnection->invokeToModel([this]() { mChatModel->deleteHistory(); });
});
mChatModelConnection->makeConnectToCore(&ChatCore::lDeleteMessage, [this](ChatMessageGui *message) {
mChatModelConnection->invokeToModel([this, core = message ? message->mCore : nullptr]() {
auto messageModel = core ? core->getModel() : nullptr;
if (messageModel) {
mChatModel->deleteMessage(messageModel->getMonitor());
}
});
});
mChatModelConnection->makeConnectToCore(
&ChatCore::lLeave, [this]() { mChatModelConnection->invokeToModel([this]() { mChatModel->leave(); }); });
mChatModelConnection->makeConnectToModel(&ChatModel::historyDeleted, [this]() {
mChatModelConnection->invokeToCore([this]() {
emit eventListCleared();
//: Deleted
Utils::showInformationPopup(tr("info_toast_deleted_title"),
//: Message history has been deleted
tr("info_toast_deleted_message_history"), true);
});
});
mChatModelConnection->makeConnectToCore(&ChatCore::lUpdateUnreadCount, [this]() {
mChatModelConnection->invokeToModel([this]() {
auto count = mChatModel->getUnreadMessagesCount();
mChatModelConnection->invokeToCore([this, count] { setUnreadMessagesCount(count); });
});
});
mChatModelConnection->makeConnectToCore(&ChatCore::lUpdateLastUpdatedTime, [this]() {
mChatModelConnection->invokeToModel([this]() {
auto time = mChatModel->getLastUpdateTime();
mChatModelConnection->invokeToCore([this, time]() { setLastUpdatedTime(time); });
});
});
mChatModelConnection->makeConnectToCore(&ChatCore::lDelete, [this]() {
mChatModelConnection->invokeToModel([this]() { mChatModel->deleteChatRoom(); });
});
mChatModelConnection->makeConnectToModel(
&ChatModel::stateChanged,
[this](const std::shared_ptr<linphone::ChatRoom> &chatRoom, linphone::ChatRoom::State newState) {
auto state = LinphoneEnums::fromLinphone(newState);
bool isReadOnly = chatRoom->isReadOnly();
if (newState == linphone::ChatRoom::State::Deleted) emit deleted();
mChatModelConnection->invokeToCore([this, state, isReadOnly]() {
setChatRoomState(state);
setIsReadOnly(isReadOnly);
});
});
mChatModelConnection->makeConnectToModel(
&ChatModel::conferenceJoined, [this](const std::shared_ptr<linphone::ChatRoom> &chatRoom,
const std::shared_ptr<const linphone::EventLog> &eventLog) {
auto participants = buildParticipants(chatRoom);
if (chatRoom->hasCapability((int)linphone::ChatRoom::Capabilities::OneToOne)) {
QString title, avatarUri;
auto linParticipants = chatRoom->getParticipants();
if (linParticipants.size() > 0) {
auto peer = linParticipants.front();
if (peer) title = ToolModel::getDisplayName(peer->getAddress());
avatarUri = ToolModel::getDisplayName(peer->getAddress());
if (linParticipants.size() == 1) {
auto peerAddress = peer->getAddress();
if (peerAddress)
mParticipantAddress = Utils::coreStringToAppString(peerAddress->asStringUriOnly());
}
}
mChatModelConnection->invokeToCore([this, title, avatarUri]() {
setTitle(title);
setAvatarUri(avatarUri);
mConferenceJoined = true;
emit conferenceJoined();
});
}
mChatModelConnection->invokeToCore([this, participants]() { setParticipants(participants); });
});
// Events (excluding messages)
mChatModelConnection->makeConnectToModel(
&ChatModel::newEvent, [this](const std::shared_ptr<linphone::ChatRoom> &chatRoom,
const std::shared_ptr<const linphone::EventLog> &eventLog) {
if (mChatModel->getMonitor() != chatRoom) return;
lDebug() << "EVENT LOG RECEIVED IN CHATROOM" << mChatModel->getTitle();
auto event = EventLogCore::create(eventLog, chatRoom);
if (event->isHandled()) {
mChatModelConnection->invokeToCore([this, event]() { emit eventsInserted({event}); });
}
mChatModelConnection->invokeToCore([this, event]() { emit lUpdateLastUpdatedTime(); });
});
// Chat messages
mChatModelConnection->makeConnectToModel(
&ChatModel::chatMessagesReceived, [this](const std::shared_ptr<linphone::ChatRoom> &chatRoom,
const std::list<std::shared_ptr<linphone::EventLog>> &eventsLog) {
if (mChatModel->getMonitor() != chatRoom) return;
lDebug() << "CHAT MESSAGE RECEIVED IN CHATROOM" << mChatModel->getTitle();
QList<QSharedPointer<EventLogCore>> list;
for (auto &e : eventsLog) {
auto event = EventLogCore::create(e, chatRoom);
list.push_back(event);
}
mChatModelConnection->invokeToCore([this, list]() {
emit eventsInserted(list);
emit lUpdateUnreadCount();
emit lUpdateLastUpdatedTime();
});
});
mChatModelConnection->makeConnectToCore(&ChatCore::lMarkAsRead, [this]() {
auto lastActiveWindow = Utils::getLastActiveWindow();
if (lastActiveWindow && lastActiveWindow->isActive())
mChatModelConnection->invokeToModel([this]() { mChatModel->markAsRead(); });
else {
connect(lastActiveWindow, &QQuickWindow::activeChanged, this, [this, lastActiveWindow] {
if (lastActiveWindow->isActive()) {
disconnect(lastActiveWindow, &QQuickWindow::activeChanged, this, nullptr);
mChatModelConnection->invokeToModel([this, lastActiveWindow] { mChatModel->markAsRead(); });
}
});
}
});
mChatModelConnection->makeConnectToModel(&ChatModel::messagesRead, [this]() {
auto unread = mChatModel->getUnreadMessagesCount();
mChatModelConnection->invokeToCore([this, unread]() { setUnreadMessagesCount(unread); });
});
mChatModelConnection->makeConnectToCore(&ChatCore::lUpdateLastMessage, [this]() {
auto lastMessageModel = mLastMessage ? mLastMessage->getModel() : nullptr;
mChatModelConnection->invokeToModel([this, lastMessageModel]() {
auto linphoneMessage = mChatModel->getLastChatMessage();
if (linphoneMessage && (!lastMessageModel || lastMessageModel->getMonitor() != linphoneMessage)) {
auto chatMessageCore = ChatMessageCore::create(linphoneMessage);
mChatModelConnection->invokeToCore([this, chatMessageCore]() { setLastMessage(chatMessageCore); });
}
});
});
mChatModelConnection->makeConnectToCore(&ChatCore::lSendTextMessage, [this](QString message) {
if (Utils::isEmptyMessage(message)) return;
mChatModelConnection->invokeToModel([this, message]() {
auto linMessage = mChatModel->createTextMessageFromText(message);
linMessage->send();
});
});
mChatModelConnection->makeConnectToCore(&ChatCore::lSendMessage, [this](QString message, QVariantList files) {
if (Utils::isEmptyMessage(message) && files.size() == 0) return;
QList<std::shared_ptr<ChatMessageContentModel>> filesContent;
for (auto &file : files) {
auto contentGui = qvariant_cast<ChatMessageContentGui *>(file);
if (contentGui) {
auto contentCore = contentGui->mCore;
filesContent.append(contentCore->getContentModel());
}
}
mChatModelConnection->invokeToModel([this, message, filesContent]() {
auto linMessage = mChatModel->createMessage(message, filesContent);
linMessage->send();
});
});
mChatModelConnection->makeConnectToModel(
&ChatModel::chatMessageSending, [this](const std::shared_ptr<linphone::ChatRoom> &chatRoom,
const std::shared_ptr<const linphone::EventLog> &eventLog) {
auto event = EventLogCore::create(eventLog, chatRoom);
mChatModelConnection->invokeToCore([this, event]() { emit eventsInserted({event}); });
});
mChatModelConnection->makeConnectToCore(
&ChatCore::lCompose, [this]() { mChatModelConnection->invokeToModel([this]() { mChatModel->compose(); }); });
mChatModelConnection->makeConnectToModel(
&ChatModel::isComposingReceived,
[this](const std::shared_ptr<linphone::ChatRoom> &chatRoom,
const std::shared_ptr<const linphone::Address> &remoteAddress, bool isComposing) {
if (mChatModel->getMonitor() != chatRoom) return;
QString name = isComposing ? ToolModel::getDisplayName(remoteAddress) : QString();
auto remoteAddr = remoteAddress;
// remoteAddr->clean();
mChatModelConnection->invokeToCore(
[this, name, address = Utils::coreStringToAppString(remoteAddr->asStringUriOnly())]() {
setComposingName(name);
setComposingAddress(address);
});
});
mChatModelConnection->makeConnectToCore(&ChatCore::lSetMuted, [this](bool muted) {
mChatModelConnection->invokeToModel([this, muted]() { mChatModel->setMuted(muted); });
});
mChatModelConnection->makeConnectToModel(&ChatModel::mutedChanged, [this](bool muted) {
mChatModelConnection->invokeToCore([this, muted]() {
if (mIsMuted != muted) {
mIsMuted = muted;
emit mutedChanged();
}
});
});
mChatModelConnection->makeConnectToCore(&ChatCore::lEnableEphemeral, [this](bool enable) {
mChatModelConnection->invokeToModel([this, enable]() { mChatModel->enableEphemeral(enable); });
});
mChatModelConnection->makeConnectToModel(&ChatModel::ephemeralEnableChanged, [this](bool enable) {
mChatModelConnection->invokeToCore([this, enable]() {
if (mEphemeralEnabled != enable) {
mEphemeralEnabled = enable;
emit ephemeralEnabledChanged();
}
});
});
mChatModelConnection->makeConnectToCore(&ChatCore::lSetEphemeralLifetime, [this](int time) {
mChatModelConnection->invokeToModel([this, time]() { mChatModel->setEphemeralLifetime(time); });
});
mChatModelConnection->makeConnectToModel(&ChatModel::ephemeralLifetimeChanged, [this](int time) {
mChatModelConnection->invokeToCore([this, time]() {
if (mEphemeralLifetime != time) {
mEphemeralLifetime = time;
emit ephemeralLifetimeChanged();
}
});
});
mChatModelConnection->makeConnectToCore(&ChatCore::lSetSubject, [this](QString subject) {
mChatModelConnection->invokeToModel([this, subject]() { mChatModel->setSubject(subject); });
});
mChatModelConnection->makeConnectToModel(
&ChatModel::subjectChanged, [this](const std::shared_ptr<linphone::ChatRoom> &chatRoom,
const std::shared_ptr<const linphone::EventLog> &eventLog) {
QString subject = Utils::coreStringToAppString(chatRoom->getSubject());
mChatModelConnection->invokeToCore([this, subject]() { setTitle(subject); });
});
mChatModelConnection->makeConnectToModel(
&ChatModel::participantAdded, [this](const std::shared_ptr<linphone::ChatRoom> &chatRoom,
const std::shared_ptr<const linphone::EventLog> &eventLog) {
auto participants = buildParticipants(chatRoom);
mChatModelConnection->invokeToCore([this, participants]() { setParticipants(participants); });
});
mChatModelConnection->makeConnectToModel(
&ChatModel::participantRemoved, [this](const std::shared_ptr<linphone::ChatRoom> &chatRoom,
const std::shared_ptr<const linphone::EventLog> &eventLog) {
auto participants = buildParticipants(chatRoom);
mChatModelConnection->invokeToCore([this, participants]() { setParticipants(participants); });
});
mChatModelConnection->makeConnectToModel(&ChatModel::participantAdminStatusChanged,
[this](const std::shared_ptr<linphone::ChatRoom> &chatRoom,
const std::shared_ptr<const linphone::EventLog> &eventLog) {
auto participants = buildParticipants(chatRoom);
bool meAdmin = chatRoom->getMe()->isAdmin();
mChatModelConnection->invokeToCore([this, participants, meAdmin]() {
setParticipants(participants);
setMeAdmin(meAdmin);
});
});
mChatModelConnection->makeConnectToCore(&ChatCore::lRemoveParticipantAtIndex, [this](int index) {
mChatModelConnection->invokeToModel([this, index]() { mChatModel->removeParticipantAtIndex(index); });
});
mChatModelConnection->makeConnectToCore(&ChatCore::lSetParticipantsAddresses, [this](QStringList addresses) {
mChatModelConnection->invokeToModel([this, addresses]() { mChatModel->setParticipantAddresses(addresses); });
});
mChatModelConnection->makeConnectToCore(&ChatCore::lToggleParticipantAdminStatusAtIndex, [this](int index) {
mChatModelConnection->invokeToModel(
[this, index]() { mChatModel->toggleParticipantAdminStatusAtIndex(index); });
});
mCoreModelConnection = SafeConnection<ChatCore, CoreModel>::create(me, CoreModel::getInstance());
if (!ToolModel::findFriendByAddress(mParticipantAddress))
mCoreModelConnection->makeConnectToModel(&CoreModel::friendCreated,
[this](std::shared_ptr<linphone::Friend> f) { updateInfo(f); });
mCoreModelConnection->makeConnectToModel(&CoreModel::friendUpdated,
[this](std::shared_ptr<linphone::Friend> f) { updateInfo(f); });
mCoreModelConnection->makeConnectToModel(&CoreModel::friendRemoved,
[this](std::shared_ptr<linphone::Friend> f) { updateInfo(f, true); });
}
QDateTime ChatCore::getLastUpdatedTime() const {
return mLastUpdatedTime;
}
void ChatCore::setLastUpdatedTime(QDateTime time) {
if (mLastUpdatedTime != time) {
mLastUpdatedTime = time;
emit lastUpdatedTimeChanged(time);
}
}
QString ChatCore::getTitle() const {
return mTitle;
}
void ChatCore::setTitle(QString title) {
if (mTitle != title) {
mTitle = title;
emit titleChanged(title);
}
}
QString ChatCore::getSendingText() const {
return mSendingText;
}
void ChatCore::setSendingText(const QString &text) {
if (mSendingText != text) {
mSendingText = text;
emit sendingTextChanged(text);
}
}
bool ChatCore::isGroupChat() const {
return mIsGroupChat;
}
bool ChatCore::isEncrypted() const {
return mIsEncrypted;
}
QString ChatCore::getIdentifier() const {
return mIdentifier;
}
QString ChatCore::getParticipantAddress() const {
return mParticipantAddress;
}
QString ChatCore::getChatRoomAddress() const {
return mChatRoomAddress;
}
QString ChatCore::getAvatarUri() const {
return mAvatarUri;
}
void ChatCore::setAvatarUri(QString avatarUri) {
if (mAvatarUri != avatarUri) {
mAvatarUri = avatarUri;
emit avatarUriChanged();
}
}
QString ChatCore::getLastMessageText() const {
return mLastMessage ? mLastMessage->getText() : QString();
}
LinphoneEnums::ChatMessageState ChatCore::getLastMessageState() const {
return mLastMessage ? mLastMessage->getMessageState() : LinphoneEnums::ChatMessageState::StateIdle;
}
LinphoneEnums::ChatRoomState ChatCore::getChatRoomState() const {
return mChatRoomState;
}
void ChatCore::setChatRoomState(LinphoneEnums::ChatRoomState state) {
if (mChatRoomState != state) {
mChatRoomState = state;
emit chatRoomStateChanged();
}
}
void ChatCore::setIsReadOnly(bool readOnly) {
if (mIsReadOnly != readOnly) {
mIsReadOnly = readOnly;
emit readOnlyChanged();
}
}
bool ChatCore::getIsReadOnly() const {
return mIsReadOnly;
}
ChatMessageGui *ChatCore::getLastMessage() const {
return mLastMessage ? new ChatMessageGui(mLastMessage) : nullptr;
}
void ChatCore::setLastMessage(QSharedPointer<ChatMessageCore> lastMessage) {
if (mLastMessage != lastMessage) {
if (mLastMessage) disconnect(mLastMessage.get(), &ChatMessageCore::messageStateChanged, this, nullptr);
mLastMessage = lastMessage;
connect(mLastMessage.get(), &ChatMessageCore::messageStateChanged, this, &ChatCore::lastMessageChanged);
emit lastMessageChanged();
}
}
int ChatCore::getUnreadMessagesCount() const {
return mUnreadMessagesCount;
}
void ChatCore::setUnreadMessagesCount(int count) {
if (mUnreadMessagesCount != count) {
mUnreadMessagesCount = count;
emit unreadMessagesCountChanged(count);
}
}
QString ChatCore::getComposingName() const {
return mComposingName;
}
void ChatCore::setComposingName(QString composingName) {
if (mComposingAddress != composingName) {
mComposingName = composingName;
emit composingUserChanged();
}
}
void ChatCore::setComposingAddress(QString composingAddress) {
if (mComposingAddress != composingAddress) {
mComposingAddress = composingAddress;
emit composingUserChanged();
}
}
QString ChatCore::getComposingAddress() const {
return mComposingAddress;
}
QList<QSharedPointer<ChatMessageContentCore>> ChatCore::getFileList() const {
return mFileList;
}
void ChatCore::resetFileList(QList<QSharedPointer<ChatMessageContentCore>> list) {
mFileList = list;
emit fileListChanged();
}
std::shared_ptr<ChatModel> ChatCore::getModel() const {
return mChatModel;
}
bool ChatCore::isMuted() const {
return mIsMuted;
}
bool ChatCore::isEphemeralEnabled() const {
return mEphemeralEnabled;
}
int ChatCore::getEphemeralLifetime() const {
return mEphemeralLifetime;
}
void ChatCore::setMeAdmin(bool admin) {
if (mMeAdmin != admin) {
mMeAdmin = admin;
emit meAdminChanged();
}
}
bool ChatCore::getMeAdmin() const {
return mMeAdmin;
}
bool ChatCore::isSecured() const {
return mIsSecured;
}
void ChatCore::setIsSecured(bool secured) {
if (mIsSecured != secured) {
mIsSecured = secured;
emit isSecuredChanged();
}
}
bool ChatCore::computeSecuredStatus() const {
if (mParticipants.size() == 0) return false;
for (auto &participant : mParticipants) {
if (participant->getSecurityLevel() != LinphoneEnums::SecurityLevel::EndToEndEncryptedAndVerified) return false;
}
return true;
}
QVariantList ChatCore::getParticipantsGui() const {
QVariantList result;
for (auto participantCore : mParticipants) {
auto participantGui = new ParticipantGui(participantCore);
result.append(QVariant::fromValue(participantGui));
}
return result;
}
QStringList ChatCore::getParticipantsAddresses() const {
QStringList result;
for (auto participantCore : mParticipants) {
result.append(participantCore->getSipAddress());
}
return result;
}
void ChatCore::setParticipants(QList<QSharedPointer<ParticipantCore>> participants) {
mustBeInMainThread(log().arg(Q_FUNC_INFO));
mParticipants = participants;
emit participantsChanged();
}
QList<QSharedPointer<ParticipantCore>>
ChatCore::buildParticipants(const std::shared_ptr<linphone::ChatRoom> &chatRoom) const {
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
QList<QSharedPointer<ParticipantCore>> result;
for (auto participant : chatRoom->getParticipants()) {
auto participantCore = ParticipantCore::create(participant);
result.append(participantCore);
}
return result;
}
QList<QSharedPointer<ParticipantCore>> ChatCore::getParticipants() const {
return mParticipants;
}
QString ChatCore::getLocalAddress() const {
return mLocalAddress;
}
void ChatCore::updateInfo(const std::shared_ptr<linphone::Friend> &updatedFriend, bool isRemoval) {
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
auto fAddress = ToolModel::interpretUrl(mParticipantAddress);
bool isThisFriend = mFriendModel && updatedFriend == mFriendModel->getFriend();
if (!isThisFriend)
for (auto f : updatedFriend->getAddresses()) {
if (f->weakEqual(fAddress)) {
isThisFriend = true;
break;
}
}
if (isThisFriend) {
if (isRemoval) {
mFriendModel = nullptr;
}
int capabilities = mChatModel->getCapabilities();
auto chatroom = mChatModel->getMonitor();
auto chatRoomAddress = chatroom->getPeerAddress();
if (mChatModel->hasCapability((int)linphone::ChatRoom::Capabilities::Basic)) {
auto title = ToolModel::getDisplayName(chatRoomAddress);
auto avatarUri = ToolModel::getDisplayName(chatRoomAddress);
mChatModelConnection->invokeToCore([this, title, avatarUri] {
setTitle(title);
setAvatarUri(avatarUri);
});
} else {
if (mChatModel->hasCapability((int)linphone::ChatRoom::Capabilities::OneToOne)) {
auto participants = chatroom->getParticipants();
if (participants.size() > 0) {
auto peer = participants.front();
if (peer) {
auto title = ToolModel::getDisplayName(peer->getAddress());
auto avatarUri = ToolModel::getDisplayName(peer->getAddress());
mChatModelConnection->invokeToCore([this, title, avatarUri] {
setTitle(title);
setAvatarUri(avatarUri);
});
}
}
} else if (mChatModel->hasCapability((int)linphone::ChatRoom::Capabilities::Conference)) {
auto title = Utils::coreStringToAppString(chatroom->getSubject());
auto avatarUri = Utils::coreStringToAppString(chatroom->getSubject());
mChatModelConnection->invokeToCore([this, title, avatarUri] {
setTitle(title);
setAvatarUri(avatarUri);
});
}
}
}
}

View file

@ -0,0 +1,236 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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/>.
*/
#ifndef CHAT_CORE_H_
#define CHAT_CORE_H_
#include "core/chat/message/EventLogGui.hpp"
#include "core/participant/ParticipantCore.hpp"
#include "message/ChatMessageGui.hpp"
#include "model/chat/ChatModel.hpp"
#include "model/search/MagicSearchModel.hpp"
#include "tool/LinphoneEnums.hpp"
#include "tool/thread/SafeConnection.hpp"
#include <QObject>
#include <QSharedPointer>
#include <linphone++/linphone.hh>
class EventLogCore;
class FriendModel;
class AccountCore;
class ChatCore : public QObject, public AbstractObject {
Q_OBJECT
public:
Q_PROPERTY(QString title READ getTitle WRITE setTitle NOTIFY titleChanged)
Q_PROPERTY(QString identifier READ getIdentifier CONSTANT)
Q_PROPERTY(QString peerAddress READ getParticipantAddress CONSTANT)
Q_PROPERTY(QString chatRoomAddress READ getChatRoomAddress CONSTANT)
Q_PROPERTY(QString avatarUri READ getAvatarUri WRITE setAvatarUri NOTIFY avatarUriChanged)
Q_PROPERTY(QDateTime lastUpdatedTime READ getLastUpdatedTime WRITE setLastUpdatedTime NOTIFY lastUpdatedTimeChanged)
Q_PROPERTY(QString lastMessageText READ getLastMessageText NOTIFY lastMessageChanged)
Q_PROPERTY(ChatMessageGui *lastMessage READ getLastMessage NOTIFY lastMessageChanged)
Q_PROPERTY(LinphoneEnums::ChatMessageState lastMessageState READ getLastMessageState NOTIFY lastMessageChanged)
Q_PROPERTY(LinphoneEnums::ChatRoomState state READ getChatRoomState NOTIFY chatRoomStateChanged)
Q_PROPERTY(int unreadMessagesCount READ getUnreadMessagesCount WRITE setUnreadMessagesCount NOTIFY
unreadMessagesCountChanged)
Q_PROPERTY(QString composingName READ getComposingName WRITE setComposingName NOTIFY composingUserChanged)
Q_PROPERTY(QString composingAddress READ getComposingAddress WRITE setComposingAddress NOTIFY composingUserChanged)
Q_PROPERTY(bool isGroupChat READ isGroupChat CONSTANT)
Q_PROPERTY(bool isEncrypted READ isEncrypted CONSTANT)
Q_PROPERTY(bool isReadOnly READ getIsReadOnly WRITE setIsReadOnly NOTIFY readOnlyChanged)
Q_PROPERTY(bool isSecured READ isSecured WRITE setIsSecured NOTIFY isSecuredChanged)
Q_PROPERTY(bool isBasic MEMBER mIsBasic CONSTANT)
Q_PROPERTY(QString sendingText READ getSendingText WRITE setSendingText NOTIFY sendingTextChanged)
Q_PROPERTY(bool ephemeralEnabled READ isEphemeralEnabled WRITE lEnableEphemeral NOTIFY ephemeralEnabledChanged)
Q_PROPERTY(
int ephemeralLifetime READ getEphemeralLifetime WRITE lSetEphemeralLifetime NOTIFY ephemeralLifetimeChanged)
Q_PROPERTY(bool muted READ isMuted WRITE lSetMuted NOTIFY mutedChanged)
Q_PROPERTY(bool meAdmin READ getMeAdmin WRITE setMeAdmin NOTIFY meAdminChanged)
Q_PROPERTY(QVariantList participants READ getParticipantsGui NOTIFY participantsChanged)
Q_PROPERTY(QStringList participantsAddresses READ getParticipantsAddresses WRITE lSetParticipantsAddresses NOTIFY
participantsChanged)
Q_PROPERTY(QList<QSharedPointer<ChatMessageContentCore>> fileList READ getFileList NOTIFY fileListChanged)
// Should be call from model Thread. Will be automatically in App thread after initialization
static QSharedPointer<ChatCore> create(const std::shared_ptr<linphone::ChatRoom> &chatRoom);
ChatCore(const std::shared_ptr<linphone::ChatRoom> &chatRoom);
~ChatCore();
void setSelf(QSharedPointer<ChatCore> me);
QDateTime getLastUpdatedTime() const;
void setLastUpdatedTime(QDateTime time);
QString getTitle() const;
void setTitle(QString title);
bool isGroupChat() const;
bool isEncrypted() const;
bool isMuted() const;
bool isEphemeralEnabled() const;
int getEphemeralLifetime() const;
QString getIdentifier() const;
QString getSendingText() const;
void setSendingText(const QString &text);
ChatMessageGui *getLastMessage() const;
QString getLastMessageText() const;
QList<QSharedPointer<ChatMessageContentCore>> getFileList() const;
void resetFileList(QList<QSharedPointer<ChatMessageContentCore>> list);
LinphoneEnums::ChatMessageState getLastMessageState() const;
LinphoneEnums::ChatRoomState getChatRoomState() const;
void setChatRoomState(LinphoneEnums::ChatRoomState state);
bool getIsReadOnly() const;
void setIsReadOnly(bool readOnly);
QSharedPointer<ChatMessageCore> getLastMessageCore() const;
void setLastMessage(QSharedPointer<ChatMessageCore> lastMessage);
int getUnreadMessagesCount() const;
void setUnreadMessagesCount(int count);
QString getChatRoomAddress() const;
QString getParticipantAddress() const;
bool getMeAdmin() const;
void setMeAdmin(bool admin);
bool isSecured() const;
void setIsSecured(bool secured);
bool computeSecuredStatus() const;
// void resetEventLogList(QList<QSharedPointer<EventLogCore>> list);
// void appendEventLogToEventLogList(QSharedPointer<EventLogCore> event);
// void appendEventLogsToEventLogList(QList<QSharedPointer<EventLogCore>> list);
// void removeEventLogsFromEventLogList(QList<QSharedPointer<EventLogCore>> list);
// void clearEventLogList();
QString getAvatarUri() const;
void setAvatarUri(QString avatarUri);
QString getComposingName() const;
QString getComposingAddress() const;
void setComposingName(QString composingName);
void setComposingAddress(QString composingAddress);
std::shared_ptr<ChatModel> getModel() const;
void setParticipants(QList<QSharedPointer<ParticipantCore>> participants);
QList<QSharedPointer<ParticipantCore>> buildParticipants(const std::shared_ptr<linphone::ChatRoom> &chatRoom) const;
QList<QSharedPointer<ParticipantCore>> getParticipants() const;
QVariantList getParticipantsGui() const;
QStringList getParticipantsAddresses() const;
QString getLocalAddress() const;
void updateInfo(const std::shared_ptr<linphone::Friend> &updatedFriend, bool isRemoval = false);
signals:
// used to close all the notifications when one is clicked
void messageOpen();
void lastUpdatedTimeChanged(QDateTime time);
void lastMessageChanged();
void titleChanged(QString title);
void unreadMessagesCountChanged(int count);
void eventListCleared();
void eventsInserted(QList<QSharedPointer<EventLogCore>> list);
void avatarUriChanged();
void deleted();
void composingUserChanged();
void chatRoomStateChanged();
void readOnlyChanged();
void sendingTextChanged(QString text);
void mutedChanged();
void ephemeralEnabledChanged();
void ephemeralLifetimeChanged();
void meAdminChanged();
void participantsChanged();
void fileListChanged();
void isSecuredChanged();
void conferenceJoined();
void lDeleteMessage(ChatMessageGui *message);
void lDelete();
void lDeleteHistory();
void lMarkAsRead();
void lUpdateLastMessage();
void lUpdateUnreadCount();
void lUpdateLastUpdatedTime();
void lSendTextMessage(QString message);
void lSendMessage(QString message, QVariantList files);
void lSendVoiceMessage();
void lCompose();
void lLeave();
void lSetMuted(bool muted);
void lEnableEphemeral(bool enable);
void lSetEphemeralLifetime(int time);
void lSetSubject(QString subject);
void lRemoveParticipantAtIndex(int index);
void lSetParticipantsAddresses(QStringList addresses);
void lToggleParticipantAdminStatusAtIndex(int index);
private:
QString id;
QDateTime mLastUpdatedTime;
QString mParticipantAddress;
QString mChatRoomAddress;
QString mTitle;
QString mIdentifier;
QString mAvatarUri;
QString mSendingText;
int mUnreadMessagesCount;
QString mComposingName;
QString mComposingAddress;
QString mLocalAddress;
bool mIsGroupChat = false;
bool mIsEncrypted = false;
bool mIsReadOnly = false;
bool mEphemeralEnabled = false;
// ChatRoom is secured if all its participants are
// EndToEndEncryptedAndVerified friends
bool mIsSecured = false;
bool mIsBasic = false;
int mEphemeralLifetime = 0;
QList<QSharedPointer<ChatMessageContentCore>> mFileList;
bool mIsMuted = false;
bool mMeAdmin = false;
bool mConferenceJoined = false;
QList<QSharedPointer<ParticipantCore>> mParticipants;
LinphoneEnums::ChatRoomState mChatRoomState;
std::shared_ptr<ChatModel> mChatModel;
QSharedPointer<ChatMessageCore> mLastMessage;
std::shared_ptr<FriendModel> mFriendModel;
QSharedPointer<SafeConnection<ChatCore, ChatModel>> mChatModelConnection;
QSharedPointer<SafeConnection<ChatCore, CoreModel>> mCoreModelConnection;
DECLARE_ABSTRACT_OBJECT
};
Q_DECLARE_METATYPE(ChatCore *)
#endif

View file

@ -0,0 +1,38 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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 "ChatGui.hpp"
#include "core/App.hpp"
DEFINE_ABSTRACT_OBJECT(ChatGui)
ChatGui::ChatGui(QSharedPointer<ChatCore> core) {
App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::JavaScriptOwnership);
mCore = core;
if (isInLinphoneThread()) moveToThread(App::getInstance()->thread());
}
ChatGui::~ChatGui() {
mustBeInMainThread("~" + getClassName());
}
ChatCore *ChatGui::getCore() const {
return mCore.get();
}

View file

@ -0,0 +1,41 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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/>.
*/
#ifndef CHAT_GUI_H_
#define CHAT_GUI_H_
#include "ChatCore.hpp"
#include <QObject>
#include <QSharedPointer>
class ChatGui : public QObject, public AbstractObject {
Q_OBJECT
Q_PROPERTY(ChatCore *core READ getCore CONSTANT)
public:
ChatGui(QSharedPointer<ChatCore> core);
~ChatGui();
ChatCore *getCore() const;
QSharedPointer<ChatCore> mCore;
DECLARE_ABSTRACT_OBJECT
};
#endif

View file

@ -0,0 +1,209 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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 "ChatList.hpp"
#include "ChatCore.hpp"
#include "ChatGui.hpp"
#include "core/App.hpp"
#include "model/tool/ToolModel.hpp"
#include <QSharedPointer>
#include <linphone++/linphone.hh>
// =============================================================================
DEFINE_ABSTRACT_OBJECT(ChatList)
QSharedPointer<ChatList> ChatList::create() {
auto model = QSharedPointer<ChatList>(new ChatList(), &QObject::deleteLater);
model->moveToThread(App::getInstance()->thread());
model->setSelf(model);
return model;
}
QSharedPointer<ChatCore> ChatList::createChatCore(const std::shared_ptr<linphone::ChatRoom> &chatroom) {
auto chatCore = ChatCore::create(chatroom);
return chatCore;
}
ChatList::ChatList(QObject *parent) : ListProxy(parent) {
mustBeInMainThread(getClassName());
App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership);
}
ChatList::~ChatList() {
mustBeInMainThread("~" + getClassName());
mModelConnection = nullptr;
}
void ChatList::connectItem(QSharedPointer<ChatCore> chat) {
connect(
chat.get(), &ChatCore::deleted, this,
[this, chat] {
disconnect(chat.get(), &ChatCore::unreadMessagesCountChanged, this, nullptr);
disconnect(chat.get(), &ChatCore::lastUpdatedTimeChanged, this, nullptr);
disconnect(chat.get(), &ChatCore::lastMessageChanged, this, nullptr);
remove(chat);
},
Qt::SingleShotConnection);
auto dataChange = [this, chat] {
int i = -1;
get(chat.get(), &i);
if (i != -1) {
auto modelIndex = index(i);
emit dataChanged(modelIndex, modelIndex);
}
};
connect(chat.get(), &ChatCore::unreadMessagesCountChanged, this, [this, dataChange] {
dataChange();
auto defaultAccount = App::getInstance()->getAccountList()->getDefaultAccountCore();
if (defaultAccount) emit defaultAccount->lRefreshNotifications();
});
connect(chat.get(), &ChatCore::lastUpdatedTimeChanged, this, dataChange);
connect(chat.get(), &ChatCore::lastMessageChanged, this, dataChange);
}
void ChatList::setSelf(QSharedPointer<ChatList> me) {
mModelConnection = SafeConnection<ChatList, CoreModel>::create(me, CoreModel::getInstance());
mModelConnection->makeConnectToCore(&ChatList::lUpdate, [this]() {
if (mIsUpdating) {
connect(this, &ChatList::isUpdatingChanged, this, [this] {
if (!mIsUpdating) {
disconnect(this, &ChatList::isUpdatingChanged, this, nullptr);
lUpdate();
}
});
return;
}
setIsUpdating(true);
mModelConnection->invokeToModel([this]() {
mustBeInLinphoneThread(getClassName());
beginResetModel();
// Avoid copy to lambdas
QList<QSharedPointer<ChatCore>> *chats = new QList<QSharedPointer<ChatCore>>();
auto currentAccount = CoreModel::getInstance()->getCore()->getDefaultAccount();
if (!currentAccount) {
setIsUpdating(false);
endResetModel();
return;
}
auto linphoneChatRooms = currentAccount->filterChatRooms(Utils::appStringToCoreString(mFilter));
for (auto it : linphoneChatRooms) {
auto model = createChatCore(it);
chats->push_back(model);
}
mModelConnection->invokeToCore([this, chats]() {
mustBeInMainThread(getClassName());
for (auto &chat : getSharedList<ChatCore>()) {
if (chat) {
disconnect(chat.get(), &ChatCore::deleted, this, nullptr);
disconnect(chat.get(), &ChatCore::unreadMessagesCountChanged, this, nullptr);
disconnect(chat.get(), &ChatCore::lastUpdatedTimeChanged, this, nullptr);
disconnect(chat.get(), &ChatCore::lastMessageChanged, this, nullptr);
}
}
mList.clear();
for (auto &chat : *chats) {
connectItem(chat);
}
add(*chats);
endResetModel();
setIsUpdating(false);
delete chats;
});
});
});
mModelConnection->makeConnectToModel(
&CoreModel::defaultAccountChanged,
[this](std::shared_ptr<linphone::Core> core, std::shared_ptr<linphone::Account> account) { lUpdate(); });
auto addChatToList = [this](const std::shared_ptr<linphone::Core> &core,
const std::shared_ptr<linphone::ChatRoom> &room,
const std::shared_ptr<linphone::ChatMessage> &message) {
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
if (!message) return;
if (room->getAccount() != core->getDefaultAccount()) {
qWarning() << log().arg("Chat room does not refer to current account, return");
return;
}
auto chatCore = ChatCore::create(room);
mModelConnection->invokeToCore([this, chatCore] { addChatInList(chatCore); });
};
mModelConnection->makeConnectToModel(&CoreModel::messageReceived,
[this, addChatToList](const std::shared_ptr<linphone::Core> &core,
const std::shared_ptr<linphone::ChatRoom> &room,
const std::shared_ptr<linphone::ChatMessage> &message) {
addChatToList(core, room, message);
});
mModelConnection->makeConnectToModel(
&CoreModel::messagesReceived,
[this, addChatToList](const std::shared_ptr<linphone::Core> &core,
const std::shared_ptr<linphone::ChatRoom> &room,
const std::list<std::shared_ptr<linphone::ChatMessage>> &messages) {
addChatToList(core, room, messages.front());
});
mModelConnection->makeConnectToModel(
&CoreModel::newMessageReaction,
[this, addChatToList](const std::shared_ptr<linphone::Core> &core,
const std::shared_ptr<linphone::ChatRoom> &room,
const std::shared_ptr<linphone::ChatMessage> &message,
const std::shared_ptr<const linphone::ChatMessageReaction> &reaction) {
addChatToList(core, room, message);
});
connect(this, &ChatList::filterChanged, [this](QString filter) {
mFilter = filter;
lUpdate();
});
lUpdate();
}
int ChatList::findChatIndex(ChatGui *chatGui) {
if (!chatGui) return -1;
auto core = chatGui->mCore;
auto chatList = getSharedList<ChatCore>();
auto it = std::find_if(chatList.begin(), chatList.end(), [core](const QSharedPointer<ChatCore> item) {
return item->getIdentifier() == core->getIdentifier();
});
return it == chatList.end() ? -1 : std::distance(chatList.begin(), it);
}
bool ChatList::addChatInList(QSharedPointer<ChatCore> chatCore) {
mustBeInMainThread(log().arg(Q_FUNC_INFO));
auto chatList = getSharedList<ChatCore>();
auto it = std::find_if(chatList.begin(), chatList.end(), [chatCore](const QSharedPointer<ChatCore> item) {
return item && chatCore && item->getIdentifier() == chatCore->getIdentifier();
});
if (it == chatList.end()) {
connectItem(chatCore);
add(chatCore);
emit chatAdded();
return true;
}
return false;
}
QVariant ChatList::data(const QModelIndex &index, int role) const {
int row = index.row();
if (!index.isValid() || row < 0 || row >= mList.count()) return QVariant();
if (role == Qt::DisplayRole) return QVariant::fromValue(new ChatGui(mList[row].objectCast<ChatCore>()));
return QVariant();
}

View file

@ -0,0 +1,60 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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/>.
*/
#ifndef CHAT_LIST_H_
#define CHAT_LIST_H_
#include "../proxy/ListProxy.hpp"
#include "tool/AbstractObject.hpp"
#include "tool/thread/SafeConnection.hpp"
#include <QLocale>
class ChatGui;
class ChatCore;
// =============================================================================
class ChatList : public ListProxy, public AbstractObject {
Q_OBJECT
public:
static QSharedPointer<ChatList> create();
// Create a ChatCore and make connections to List.
QSharedPointer<ChatCore> createChatCore(const std::shared_ptr<linphone::ChatRoom> &chatroom);
ChatList(QObject *parent = Q_NULLPTR);
~ChatList();
void setSelf(QSharedPointer<ChatList> me);
void connectItem(QSharedPointer<ChatCore> chat);
int findChatIndex(ChatGui *chat);
bool addChatInList(QSharedPointer<ChatCore> chatCore);
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
signals:
void lUpdate();
void filterChanged(QString filter);
void chatAdded();
void chatUpdated();
private:
QString mFilter;
QSharedPointer<SafeConnection<ChatList, CoreModel>> mModelConnection;
DECLARE_ABSTRACT_OBJECT
};
#endif

View file

@ -0,0 +1,81 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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 "ChatProxy.hpp"
#include "ChatGui.hpp"
#include "ChatList.hpp"
#include "core/App.hpp"
DEFINE_ABSTRACT_OBJECT(ChatProxy)
ChatProxy::ChatProxy(QObject *parent) {
mList = ChatList::create();
setSourceModel(mList.get());
setDynamicSortFilter(true);
}
ChatProxy::~ChatProxy() {
}
void ChatProxy::setSourceModel(QAbstractItemModel *model) {
auto oldChatList = dynamic_cast<ChatList *>(sourceModel());
if (oldChatList) {
disconnect(this, &ChatProxy::filterTextChanged, oldChatList, nullptr);
disconnect(oldChatList, &ChatList::chatAdded, this, nullptr);
disconnect(oldChatList, &ChatList::dataChanged, this, nullptr);
}
auto newChatList = dynamic_cast<ChatList *>(model);
if (newChatList) {
connect(this, &ChatProxy::filterTextChanged, newChatList,
[this, newChatList] { emit newChatList->filterChanged(getFilterText()); });
connect(newChatList, &ChatList::chatAdded, this, [this] { invalidate(); });
connect(newChatList, &ChatList::dataChanged, this, [this] { invalidate(); });
}
QSortFilterProxyModel::setSourceModel(newChatList);
sort(0);
}
int ChatProxy::findChatIndex(ChatGui *chatGui) {
auto chatList = dynamic_cast<ChatList *>(sourceModel());
if (chatList) {
auto listIndex = chatList->findChatIndex(chatGui);
if (listIndex != -1) {
listIndex = mapFromSource(chatList->index(listIndex, 0)).row();
return listIndex;
}
}
return -1;
}
bool ChatProxy::addChatInList(ChatGui *chatGui) {
auto chatList = dynamic_cast<ChatList *>(sourceModel());
if (chatList && chatGui) {
return chatList->addChatInList(chatGui->mCore);
}
return false;
}
bool ChatProxy::lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const {
if (!mFilterText.isEmpty()) return false;
auto l = getItemAtSource<ChatList, ChatCore>(sourceLeft.row());
auto r = getItemAtSource<ChatList, ChatCore>(sourceRight.row());
if (l && r) return l->getLastUpdatedTime() > r->getLastUpdatedTime();
return false;
}

View file

@ -0,0 +1,50 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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/>.
*/
#ifndef CHAT_PROXY_H_
#define CHAT_PROXY_H_
#include "../proxy/SortFilterProxy.hpp"
#include "core/chat/ChatGui.hpp"
#include "core/chat/ChatList.hpp"
#include "tool/AbstractObject.hpp"
// =============================================================================
class ChatProxy : public SortFilterProxy, public AbstractObject {
Q_OBJECT
public:
ChatProxy(QObject *parent = Q_NULLPTR);
~ChatProxy();
void setSourceModel(QAbstractItemModel *sourceModel) override;
bool lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const override;
Q_INVOKABLE int findChatIndex(ChatGui *chatGui);
Q_INVOKABLE bool addChatInList(ChatGui *chatGui);
protected:
QSharedPointer<ChatList> mList;
DECLARE_ABSTRACT_OBJECT
};
#endif

View file

@ -0,0 +1,127 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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 "ChatMessageFileList.hpp"
#include "core/App.hpp"
#include "core/chat/message/content/ChatMessageContentCore.hpp"
#include <QSharedPointer>
#include <linphone++/linphone.hh>
// =============================================================================
DEFINE_ABSTRACT_OBJECT(ChatMessageFileList)
QSharedPointer<ChatMessageFileList> ChatMessageFileList::create() {
auto model = QSharedPointer<ChatMessageFileList>(new ChatMessageFileList(), &QObject::deleteLater);
model->setSelf(model);
model->moveToThread(App::getInstance()->thread());
return model;
}
ChatMessageFileList::ChatMessageFileList(QObject *parent) : ListProxy(parent) {
mustBeInMainThread(getClassName());
App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership);
}
ChatMessageFileList::~ChatMessageFileList() {
mustBeInMainThread("~" + getClassName());
mList.clear();
}
void ChatMessageFileList::setSelf(QSharedPointer<ChatMessageFileList> me) {
mCoreModelConnection = SafeConnection<ChatMessageFileList, CoreModel>::create(me, CoreModel::getInstance());
mCoreModelConnection->makeConnectToCore(&ChatMessageFileList::lUpdate, [this]() {
mustBeInMainThread(log().arg(Q_FUNC_INFO));
beginResetModel();
mList.clear();
if (!mChat) {
endResetModel();
return;
}
auto chatModel = mChat->getModel();
if (!chatModel) {
endResetModel();
return;
}
mCoreModelConnection->invokeToModel([this, chatModel]() {
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
std::list<std::shared_ptr<linphone::Content>> medias;
std::list<std::shared_ptr<linphone::Content>> docs;
QList<QSharedPointer<ChatMessageContentCore>> *contents =
new QList<QSharedPointer<ChatMessageContentCore>>();
if (mFilterType == (int)FilterContentType::Medias) {
medias = chatModel->getSharedMedias();
} else if (mFilterType == (int)FilterContentType::Documents) {
docs = chatModel->getSharedDocuments();
} else {
medias = chatModel->getSharedMedias();
docs = chatModel->getSharedDocuments();
}
for (auto it : medias) {
auto model = ChatMessageContentCore::create(it, nullptr);
contents->push_back(model);
}
for (auto it : docs) {
auto model = ChatMessageContentCore::create(it, nullptr);
contents->push_back(model);
}
mCoreModelConnection->invokeToCore([this, contents] {
for (auto i : *contents)
mList << i.template objectCast<QObject>();
endResetModel();
});
});
});
}
QSharedPointer<ChatCore> ChatMessageFileList::getChatCore() const {
return mChat;
}
void ChatMessageFileList::setChatCore(QSharedPointer<ChatCore> chatCore) {
if (mChat != chatCore) {
// if (mChat) disconnect(mChat.get());
mChat = chatCore;
// if (mChat) connect(mChat.get(), &ChatCore::fileListChanged, this, lUpdate);
lUpdate();
emit chatChanged();
}
}
int ChatMessageFileList::getFilterType() const {
return mFilterType;
}
void ChatMessageFileList::setFilterType(int filterType) {
if (mFilterType != filterType) {
mFilterType = filterType;
lUpdate();
}
}
QVariant ChatMessageFileList::data(const QModelIndex &index, int role) const {
int row = index.row();
if (!index.isValid() || row < 0 || row >= mList.count()) return QVariant();
if (role == Qt::DisplayRole)
return QVariant::fromValue(new ChatMessageContentGui(mList[row].objectCast<ChatMessageContentCore>()));
return QVariant();
}

View file

@ -0,0 +1,61 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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/>.
*/
#ifndef CHAT_MESSAGE_FILE_LIST_H_
#define CHAT_MESSAGE_FILE_LIST_H_
#include "core/chat/ChatCore.hpp"
#include "core/proxy/ListProxy.hpp"
#include "tool/AbstractObject.hpp"
#include "tool/thread/SafeConnection.hpp"
#include <QLocale>
// =============================================================================
class ChatMessageFileList : public ListProxy, public AbstractObject {
Q_OBJECT
public:
enum class FilterContentType { All = 0, Medias = 1, Documents = 2 };
static QSharedPointer<ChatMessageFileList> create();
ChatMessageFileList(QObject *parent = Q_NULLPTR);
~ChatMessageFileList();
void setSelf(QSharedPointer<ChatMessageFileList> me);
QSharedPointer<ChatCore> getChatCore() const;
void setChatCore(QSharedPointer<ChatCore> chatCore);
int getFilterType() const;
void setFilterType(int filterType);
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
signals:
void chatChanged();
void lUpdate();
void filterTypeChanged();
private:
int mFilterType;
QSharedPointer<ChatCore> mChat;
QSharedPointer<SafeConnection<ChatMessageFileList, CoreModel>> mCoreModelConnection;
DECLARE_ABSTRACT_OBJECT
};
#endif

View file

@ -0,0 +1,65 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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 "ChatMessageFileProxy.hpp"
#include "core/App.hpp"
#include "core/chat/ChatGui.hpp"
DEFINE_ABSTRACT_OBJECT(ChatMessageFileProxy)
ChatMessageFileProxy::ChatMessageFileProxy(QObject *parent) : LimitProxy(parent) {
mList = ChatMessageFileList::create();
connect(mList.get(), &ChatMessageFileList::chatChanged, this, &ChatMessageFileProxy::chatGuiChanged);
connect(this, &ChatMessageFileProxy::filterTypeChanged, this, [this] { invalidate(); });
setSourceModels(new SortFilterList(mList.get()));
}
ChatMessageFileProxy::~ChatMessageFileProxy() {
}
ChatGui *ChatMessageFileProxy::getChatGui() const {
return mList && mList->getChatCore() ? new ChatGui(mList->getChatCore()) : nullptr;
}
void ChatMessageFileProxy::setChatGui(ChatGui *chat) const {
if (mList) mList->setChatCore(chat ? chat->mCore : nullptr);
}
bool ChatMessageFileProxy::SortFilterList::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const {
if (getFilterType() == (int)FilterContentType::All) return true;
else {
auto contentCore = getItemAtSource<ChatMessageFileList, ChatMessageContentCore>(sourceRow);
if (!contentCore) return false;
bool isMedia = Utils::isVideo(contentCore->getFilePath()) || Utils::isImage(contentCore->getFilePath()) ||
Utils::isAnimatedImage(contentCore->getFilePath());
if (getFilterType() == (int)FilterContentType::Medias) {
return isMedia;
} else {
return !isMedia;
}
return false;
}
}
bool ChatMessageFileProxy::SortFilterList::lessThan(const QModelIndex &sourceLeft,
const QModelIndex &sourceRight) const {
return true;
}

View file

@ -0,0 +1,56 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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/>.
*/
#ifndef CHAT_MESSAGE_FILE_PROXY_H_
#define CHAT_MESSAGE_FILE_PROXY_H_
#include "ChatMessageFileList.hpp"
#include "core/chat/message/ChatMessageCore.hpp"
#include "core/proxy/LimitProxy.hpp"
#include "tool/AbstractObject.hpp"
// =============================================================================
class ChatMessageFileProxy : public LimitProxy, public AbstractObject {
Q_OBJECT
Q_PROPERTY(ChatGui *chat READ getChatGui WRITE setChatGui NOTIFY chatGuiChanged)
public:
enum class FilterContentType { All = 0, Medias = 1, Documents = 2 };
Q_ENUM(FilterContentType)
DECLARE_SORTFILTER_CLASS()
ChatMessageFileProxy(QObject *parent = Q_NULLPTR);
~ChatMessageFileProxy();
ChatGui *getChatGui() const;
void setChatGui(ChatGui *chat) const;
signals:
void chatGuiChanged();
void filterChanged();
protected:
QSharedPointer<ChatMessageFileList> mList;
DECLARE_ABSTRACT_OBJECT
};
#endif

View file

@ -0,0 +1,694 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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 "ChatMessageCore.hpp"
#include "core/App.hpp"
#include "model/tool/ToolModel.hpp"
#include <QQuickWindow>
DEFINE_ABSTRACT_OBJECT(ChatMessageCore)
/***********************************************************************/
ImdnStatus ImdnStatus::operator=(ImdnStatus r) {
mAddress = r.mAddress;
mState = r.mState;
mLastUpdatedTime = r.mLastUpdatedTime;
return *this;
}
bool ImdnStatus::operator==(const ImdnStatus &r) const {
return r.mState == mState && r.mAddress == mAddress && r.mLastUpdatedTime == mLastUpdatedTime;
}
bool ImdnStatus::operator!=(ImdnStatus r) {
return r.mState != mState || r.mAddress != mAddress || r.mLastUpdatedTime != mLastUpdatedTime;
}
ImdnStatus::ImdnStatus() {
}
ImdnStatus::ImdnStatus(const QString &address,
const LinphoneEnums::ChatMessageState &state,
QDateTime lastUpdatedTime) {
mState = state;
mAddress = address;
mLastUpdatedTime = lastUpdatedTime;
}
ImdnStatus ImdnStatus::createMessageImdnStatusVariant(const QString &address,
const LinphoneEnums::ChatMessageState &state,
QDateTime lastUpdatedTime) {
ImdnStatus s;
s.mState = state;
s.mAddress = address;
s.mLastUpdatedTime = lastUpdatedTime;
return s;
}
QVariant createImdnStatusSingletonVariant(const LinphoneEnums::ChatMessageState &state, int count = 1) {
QVariantMap map;
map.insert("state", QVariant::fromValue(state));
map.insert("count", count);
return map;
}
/***********************************************************************/
Reaction Reaction::operator=(Reaction r) {
mAddress = r.mAddress;
mBody = r.mBody;
return *this;
}
bool Reaction::operator==(const Reaction &r) const {
return r.mBody == mBody && r.mAddress == mAddress;
}
bool Reaction::operator!=(Reaction r) {
return r.mBody != mBody || r.mAddress != mAddress;
}
Reaction Reaction::createMessageReactionVariant(const QString &body, const QString &address) {
Reaction r;
r.mBody = body;
r.mAddress = address;
return r;
}
QVariant createReactionSingletonVariant(const QString &body, int count = 1) {
QVariantMap map;
map.insert("body", body);
map.insert("count", count);
return map;
}
/***********************************************************************/
QSharedPointer<ChatMessageCore> ChatMessageCore::create(const std::shared_ptr<linphone::ChatMessage> &chatmessage) {
auto sharedPointer = QSharedPointer<ChatMessageCore>(new ChatMessageCore(chatmessage), &QObject::deleteLater);
sharedPointer->setSelf(sharedPointer);
sharedPointer->moveToThread(App::getInstance()->thread());
return sharedPointer;
}
ChatMessageCore::ChatMessageCore(const std::shared_ptr<linphone::ChatMessage> &chatmessage) {
// lDebug() << "[ChatMessageCore] new" << this;
mustBeInLinphoneThread(getClassName());
App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership);
if (chatmessage) {
mChatMessageModel = Utils::makeQObject_ptr<ChatMessageModel>(chatmessage);
mChatMessageModel->setSelf(mChatMessageModel);
mText = ToolModel::getMessageFromMessage(chatmessage);
mUtf8Text = mChatMessageModel->getUtf8Text();
mHasTextContent = mChatMessageModel->getHasTextContent();
mTimestamp = QDateTime::fromSecsSinceEpoch(chatmessage->getTime());
mIsOutgoing = chatmessage->isOutgoing();
mIsRetractable =
chatmessage->isOutgoing() && chatmessage->isRetractable() && !chatmessage->getChatRoom()->isReadOnly();
mIsRetracted = chatmessage->isRetracted();
mIsEditable =
chatmessage->isOutgoing() && chatmessage->isEditable() && !chatmessage->getChatRoom()->isReadOnly();
mIsEdited = chatmessage->isEdited();
mIsRemoteMessage = !chatmessage->isOutgoing();
mPeerAddress = Utils::coreStringToAppString(chatmessage->getPeerAddress()->asStringUriOnly());
mPeerName = ToolModel::getDisplayName(chatmessage->getPeerAddress());
auto fromAddress = chatmessage->getFromAddress();
// fromAddress->clean();
mFromAddress = Utils::coreStringToAppString(fromAddress->asStringUriOnly());
mFromName = ToolModel::getDisplayName(chatmessage->getFromAddress());
mToName = ToolModel::getDisplayName(chatmessage->getToAddress());
auto chatroom = chatmessage->getChatRoom();
mIsFromChatGroup = chatroom->hasCapability((int)linphone::ChatRoom::Capabilities::Conference) &&
!chatroom->hasCapability((int)linphone::ChatRoom::Capabilities::OneToOne);
mIsRead = chatmessage->isRead();
mMessageState = LinphoneEnums::fromLinphone(chatmessage->getState());
mIsEphemeral = chatmessage->isEphemeral();
if (mIsEphemeral) {
auto now = QDateTime::currentDateTime();
mEphemeralDuration = chatmessage->getEphemeralExpireTime() == 0
? chatmessage->getEphemeralLifetime()
: now.secsTo(QDateTime::fromSecsSinceEpoch(chatmessage->getEphemeralExpireTime()));
}
mMessageId = Utils::coreStringToAppString(chatmessage->getMessageId());
for (auto content : chatmessage->getContents()) {
auto contentCore = ChatMessageContentCore::create(content, mChatMessageModel);
mChatMessageContentList.push_back(contentCore);
if ((content->isFile() || content->isFileTransfer()) && !content->isVoiceRecording())
mHasFileContent = true;
if (content->isIcalendar()) mIsCalendarInvite = true;
if (content->isVoiceRecording()) {
mIsVoiceRecording = true;
mVoiceRecordingContent = contentCore;
}
}
//: "Reactions": all reactions for one message label
mTotalReactionsLabel = tr("all_reactions_label");
auto reac = chatmessage->getOwnReaction();
mOwnReaction = reac ? Utils::coreStringToAppString(reac->getBody()) : QString();
for (auto &reaction : chatmessage->getReactions()) {
if (reaction) {
auto fromAddr = reaction->getFromAddress()->clone();
fromAddr->clean();
auto reac =
Reaction::createMessageReactionVariant(Utils::coreStringToAppString(reaction->getBody()),
Utils::coreStringToAppString(fromAddr->asStringUriOnly()));
mReactions.append(reac);
auto it = std::find_if(mReactionsSingletonMap.begin(), mReactionsSingletonMap.end(),
[body = reac.mBody](QVariant data) {
auto dataBody = data.toMap()["body"].toString();
return body == dataBody;
});
if (it == mReactionsSingletonMap.end())
mReactionsSingletonMap.push_back(createReactionSingletonVariant(reac.mBody, 1));
else {
auto map = it->toMap();
auto count = map["count"].toInt();
++count;
map.remove("count");
map.insert("count", count);
mReactionsSingletonMap.erase(it);
mReactionsSingletonMap.push_back(map);
}
}
}
connect(this, &ChatMessageCore::messageReactionChanged, this, &ChatMessageCore::resetReactionsSingleton);
mIsForward = chatmessage->isForward();
mIsReply = chatmessage->isReply();
if (mIsReply) {
auto replymessage = chatmessage->getReplyMessage();
if (replymessage) {
mReplyText = ToolModel::getMessageFromMessage(replymessage);
if (mIsFromChatGroup) mRepliedToName = ToolModel::getDisplayName(replymessage->getFromAddress());
}
}
mImdnStatusList = computeDeliveryStatus(chatmessage);
}
}
ChatMessageCore::~ChatMessageCore() {
}
void ChatMessageCore::setSelf(QSharedPointer<ChatMessageCore> me) {
mChatMessageModelConnection = SafeConnection<ChatMessageCore, ChatMessageModel>::create(me, mChatMessageModel);
mChatMessageModelConnection->makeConnectToCore(&ChatMessageCore::lDelete, [this] {
mChatMessageModelConnection->invokeToModel([this] { mChatMessageModel->deleteMessageFromChatRoom(true); });
});
mChatMessageModelConnection->makeConnectToCore(&ChatMessageCore::lRetract, [this] {
mChatMessageModelConnection->invokeToModel([this] { mChatMessageModel->retractMessageFromChatRoom(); });
});
mChatMessageModelConnection->makeConnectToModel(&ChatMessageModel::messageDeleted, [this](bool deletedByUser) {
mChatMessageModelConnection->invokeToCore([this, deletedByUser] {
//: Deleted
if (deletedByUser)
Utils::showInformationPopup(tr("info_toast_deleted_title"),
//: The message has been deleted
tr("info_toast_deleted_message"), true);
emit deleted();
});
});
mChatMessageModelConnection->makeConnectToCore(&ChatMessageCore::lMarkAsRead, [this] {
auto mainWindow = Utils::getMainWindow();
if (mainWindow->isActive())
mChatMessageModelConnection->invokeToModel([this] { mChatMessageModel->markAsRead(); });
else {
connect(mainWindow, &QQuickWindow::activeChanged, this, [this, mainWindow] {
if (mainWindow->isActive()) {
disconnect(mainWindow, &QQuickWindow::activeChanged, this, nullptr);
mChatMessageModelConnection->invokeToModel([this, mainWindow] { mChatMessageModel->markAsRead(); });
}
});
}
});
mChatMessageModelConnection->makeConnectToModel(&ChatMessageModel::messageRead, [this]() {
mChatMessageModelConnection->invokeToCore([this] { setIsRead(true); });
});
mChatMessageModelConnection->makeConnectToCore(&ChatMessageCore::lSendReaction, [this](const QString &reaction) {
mChatMessageModelConnection->invokeToModel([this, reaction] { mChatMessageModel->sendReaction(reaction); });
});
mChatMessageModelConnection->makeConnectToCore(&ChatMessageCore::lRemoveReaction, [this]() {
mChatMessageModelConnection->invokeToModel([this] { mChatMessageModel->removeReaction(); });
});
mChatMessageModelConnection->makeConnectToCore(&ChatMessageCore::lSend, [this]() {
mChatMessageModelConnection->invokeToModel([this] { mChatMessageModel->send(); });
});
mChatMessageModelConnection->makeConnectToModel(
&ChatMessageModel::newMessageReaction,
[this](const std::shared_ptr<linphone::ChatMessage> &message,
const std::shared_ptr<const linphone::ChatMessageReaction> &reaction) {
auto ownReac = message->getOwnReaction();
auto own = ownReac ? Utils::coreStringToAppString(message->getOwnReaction()->getBody()) : QString();
// We must reset all the reactions each time cause reactionRemoved is not emitted
// when someone change its current reaction
QList<Reaction> reactions;
for (auto &reaction : message->getReactions()) {
if (reaction) {
auto fromAddr = reaction->getFromAddress()->clone();
fromAddr->clean();
reactions.append(Reaction::createMessageReactionVariant(
Utils::coreStringToAppString(reaction->getBody()),
Utils::coreStringToAppString(fromAddr->asStringUriOnly())));
}
}
mChatMessageModelConnection->invokeToCore([this, own, reactions] {
setOwnReaction(own);
setReactions(reactions);
});
});
mChatMessageModelConnection->makeConnectToModel(
&ChatMessageModel::reactionRemoved, [this](const std::shared_ptr<linphone::ChatMessage> &message,
const std::shared_ptr<const linphone::Address> &address) {
auto reac = message->getOwnReaction();
auto own = reac ? Utils::coreStringToAppString(message->getOwnReaction()->getBody()) : QString();
auto addr = address->clone();
addr->clean();
QString addressString = Utils::coreStringToAppString(addr->asStringUriOnly());
mChatMessageModelConnection->invokeToCore([this, own, addressString] {
removeReaction(addressString);
setOwnReaction(own);
});
});
mChatMessageModelConnection->makeConnectToModel(
&ChatMessageModel::msgStateChanged,
[this](const std::shared_ptr<linphone::ChatMessage> &message, linphone::ChatMessage::State state) {
auto imdnStatusList = computeDeliveryStatus(message);
auto msgState = LinphoneEnums::fromLinphone(state);
mChatMessageModelConnection->invokeToCore([this, msgState, imdnStatusList] {
setImdnStatusList(imdnStatusList);
setMessageState(msgState);
});
});
mChatMessageModelConnection->makeConnectToModel(
&ChatMessageModel::fileTransferProgressIndication,
[this](const std::shared_ptr<linphone::ChatMessage> &message, const std::shared_ptr<linphone::Content> &content,
size_t offset, size_t total) {
mChatMessageModelConnection->invokeToCore([this, content, offset, total] {
auto it =
std::find_if(mChatMessageContentList.begin(), mChatMessageContentList.end(),
[content](QSharedPointer<ChatMessageContentCore> item) {
return item->getContentModel()->getContent()->getName() == content->getName();
});
if (it != mChatMessageContentList.end()) {
auto contentCore = mChatMessageContentList.at(std::distance(mChatMessageContentList.begin(), it));
assert(contentCore);
contentCore->setFileOffset(offset);
}
});
});
mChatMessageModelConnection->makeConnectToModel(
&ChatMessageModel::fileTransferTerminated, [this](const std::shared_ptr<linphone::ChatMessage> &message,
const std::shared_ptr<linphone::Content> &content) {
mChatMessageModelConnection->invokeToCore([this, content] {
auto it =
std::find_if(mChatMessageContentList.begin(), mChatMessageContentList.end(),
[content](QSharedPointer<ChatMessageContentCore> item) {
return item->getContentModel()->getContent()->getName() == content->getName();
});
if (it != mChatMessageContentList.end()) {
auto contentCore = mChatMessageContentList.at(std::distance(mChatMessageContentList.begin(), it));
assert(contentCore);
contentCore->setWasDownloaded(true);
}
});
});
mChatMessageModelConnection->makeConnectToModel(
&ChatMessageModel::fileTransferRecv,
[this](const std::shared_ptr<linphone::ChatMessage> &message, const std::shared_ptr<linphone::Content> &content,
const std::shared_ptr<const linphone::Buffer> &buffer) { lInfo() << log().arg("transfer received"); });
mChatMessageModelConnection->makeConnectToModel(
&ChatMessageModel::fileTransferSend,
[this](const std::shared_ptr<linphone::ChatMessage> &message, const std::shared_ptr<linphone::Content> &content,
size_t offset, size_t size) { lInfo() << log().arg("transfer send"); });
mChatMessageModelConnection->makeConnectToModel(
&ChatMessageModel::fileTransferSendChunk,
[this](const std::shared_ptr<linphone::ChatMessage> &message, const std::shared_ptr<linphone::Content> &content,
size_t offset, size_t size,
const std::shared_ptr<linphone::Buffer> &buffer) { lInfo() << log().arg("transfer send chunk"); });
mChatMessageModelConnection->makeConnectToModel(
&ChatMessageModel::participantImdnStateChanged,
[this](const std::shared_ptr<linphone::ChatMessage> &message,
const std::shared_ptr<const linphone::ParticipantImdnState> &state) {
auto imdnStatusList = computeDeliveryStatus(message);
mChatMessageModelConnection->invokeToCore([this, imdnStatusList] { setImdnStatusList(imdnStatusList); });
});
mChatMessageModelConnection->makeConnectToModel(
&ChatMessageModel::ephemeralMessageTimeUpdated,
[this](const std::shared_ptr<linphone::ChatMessage> &message, int expireTime) {
auto now = QDateTime::currentDateTime();
int duration = now.secsTo(QDateTime::fromSecsSinceEpoch(expireTime));
mChatMessageModelConnection->invokeToCore([this, duration] { setEphemeralDuration(duration); });
});
mChatMessageModelConnection->makeConnectToModel(&ChatMessageModel::retracted,
[this](const std::shared_ptr<linphone::ChatMessage> &message) {
QString text = ToolModel::getMessageFromMessage(message);
mChatMessageModelConnection->invokeToCore([this, text] {
setText(text);
setRetracted();
});
});
mChatMessageModelConnection->makeConnectToModel(&ChatMessageModel::contentEdited,
[this](const std::shared_ptr<linphone::ChatMessage> &message) {
mChatMessageModelConnection->invokeToCore([this] {
mIsEdited = true;
emit edited();
});
});
}
QList<ImdnStatus> ChatMessageCore::computeDeliveryStatus(const std::shared_ptr<linphone::ChatMessage> &message) {
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
QList<ImdnStatus> imdnStatusList;
auto createImdnStatus = [this](std::shared_ptr<linphone::ParticipantImdnState> participant,
linphone::ChatMessage::State state) -> ImdnStatus {
auto address = participant->getParticipant() ? participant->getParticipant()->getAddress()->clone() : nullptr;
auto lastUpdated = QDateTime::fromSecsSinceEpoch(participant->getStateChangeTime());
if (address) {
address->clean();
auto addrString = Utils::coreStringToAppString(address->asStringUriOnly());
auto imdn =
ImdnStatus::createMessageImdnStatusVariant(addrString, LinphoneEnums::fromLinphone(state), lastUpdated);
return imdn;
}
return ImdnStatus();
};
// Read
for (auto &participant : message->getParticipantsByImdnState(linphone::ChatMessage::State::Displayed)) {
auto imdn = createImdnStatus(participant, linphone::ChatMessage::State::Displayed);
imdnStatusList.append(imdn);
}
// Received
for (auto &participant : message->getParticipantsByImdnState(linphone::ChatMessage::State::DeliveredToUser)) {
auto imdn = createImdnStatus(participant, linphone::ChatMessage::State::DeliveredToUser);
imdnStatusList.append(imdn);
}
// Sent
for (auto &participant : message->getParticipantsByImdnState(linphone::ChatMessage::State::Delivered)) {
auto imdn = createImdnStatus(participant, linphone::ChatMessage::State::Delivered);
imdnStatusList.append(imdn);
}
// Error
for (auto &participant : message->getParticipantsByImdnState(linphone::ChatMessage::State::NotDelivered)) {
auto imdn = createImdnStatus(participant, linphone::ChatMessage::State::NotDelivered);
imdnStatusList.append(imdn);
}
return imdnStatusList;
}
QDateTime ChatMessageCore::getTimestamp() const {
return mTimestamp;
}
QString ChatMessageCore::getText() const {
return mText;
}
void ChatMessageCore::setText(QString text) {
if (mText != text) {
mText = text;
emit textChanged(text);
}
}
QString ChatMessageCore::getPeerAddress() const {
return mPeerAddress;
}
QString ChatMessageCore::getPeerName() const {
return mPeerName;
}
QString ChatMessageCore::getFromAddress() const {
return mFromAddress;
}
QString ChatMessageCore::getFromName() const {
return mFromName;
}
QString ChatMessageCore::getToAddress() const {
return mToAddress;
}
QString ChatMessageCore::getToName() const {
return mToName;
}
QString ChatMessageCore::getMessageId() const {
return mMessageId;
}
bool ChatMessageCore::isRemoteMessage() const {
return mIsRemoteMessage;
}
bool ChatMessageCore::isFromChatGroup() const {
return mIsFromChatGroup;
}
bool ChatMessageCore::isEphemeral() const {
return mIsEphemeral;
}
int ChatMessageCore::getEphemeralDuration() const {
return mEphemeralDuration;
}
void ChatMessageCore::setEphemeralDuration(int duration) {
if (mEphemeralDuration != duration) {
mEphemeralDuration = duration;
emit ephemeralDurationChanged(duration);
}
}
bool ChatMessageCore::hasFileContent() const {
return mHasFileContent;
}
bool ChatMessageCore::isRead() const {
return mIsRead;
}
void ChatMessageCore::setIsRead(bool read) {
if (mIsRead != read) {
mIsRead = read;
emit isReadChanged(read);
}
}
void ChatMessageCore::setRetracted() {
if (!mIsRetracted) {
mIsRetracted = true;
emit isRetractedChanged();
emit messageStateChanged();
}
}
bool ChatMessageCore::isRetracted() const {
return mIsRetracted;
}
bool ChatMessageCore::isEdited() const {
return mIsEdited;
}
QString ChatMessageCore::getOwnReaction() const {
return mOwnReaction;
}
void ChatMessageCore::setOwnReaction(const QString &reaction) {
if (mOwnReaction != reaction) {
mOwnReaction = reaction;
emit messageReactionChanged();
}
}
QString ChatMessageCore::getTotalReactionsLabel() const {
return mTotalReactionsLabel;
}
QList<Reaction> ChatMessageCore::getReactions() const {
return mReactions;
}
QList<QVariant> ChatMessageCore::getReactionsSingleton() const {
return mReactionsSingletonMap;
}
QStringList ChatMessageCore::getReactionsSingletonAsStrings() const {
QStringList reacStringList;
int totalCount = 0;
for (auto &reac : mReactionsSingletonMap) {
auto map = reac.toMap();
auto count = map["count"].toInt();
totalCount += count;
reacStringList.append(QString("%1 %2").arg(map["body"].toString()).arg(count));
}
reacStringList.prepend(QString("%1 %2").arg(mTotalReactionsLabel).arg(totalCount));
return reacStringList;
}
QList<QSharedPointer<ChatMessageContentCore>> ChatMessageCore::getChatMessageContentList() const {
return mChatMessageContentList;
}
void ChatMessageCore::setReactions(const QList<Reaction> &reactions) {
mustBeInMainThread(log().arg(Q_FUNC_INFO));
mReactions = reactions;
emit messageReactionChanged();
}
void ChatMessageCore::resetReactionsSingleton() {
mReactionsSingletonMap.clear();
for (auto &reac : mReactions) {
auto it = std::find_if(mReactionsSingletonMap.begin(), mReactionsSingletonMap.end(),
[body = reac.mBody](QVariant data) {
auto dataBody = data.toMap()["body"].toString();
return body == dataBody;
});
if (it == mReactionsSingletonMap.end())
mReactionsSingletonMap.push_back(createReactionSingletonVariant(reac.mBody, 1));
else {
auto map = it->toMap();
auto count = map["count"].toInt();
++count;
map.remove("count");
map.insert("count", count);
mReactionsSingletonMap.erase(it);
mReactionsSingletonMap.push_back(map);
}
}
emit singletonReactionMapChanged();
}
void ChatMessageCore::removeReaction(const Reaction &reaction) {
int i = 0;
for (const auto &r : mReactions) {
if (reaction == r) {
mReactions.removeAt(i);
emit messageReactionChanged();
}
++i;
}
}
void ChatMessageCore::removeOneReactionFromSingletonMap(const QString &body) {
auto it = std::find_if(mReactionsSingletonMap.begin(), mReactionsSingletonMap.end(), [body](QVariant data) {
auto dataBody = data.toMap()["body"].toString();
return body == dataBody;
});
if (it != mReactionsSingletonMap.end()) {
auto map = it->toMap();
auto count = map["count"].toInt();
if (count <= 1) mReactionsSingletonMap.erase(it);
else {
--count;
map.remove("count");
map.insert("count", count);
}
emit messageReactionChanged();
}
}
void ChatMessageCore::removeReaction(const QString &address) {
int n = mReactions.removeIf([address, this](Reaction r) {
if (r.mAddress == address) {
removeOneReactionFromSingletonMap(r.mBody);
return true;
}
return false;
});
if (n > 0) emit messageReactionChanged();
}
LinphoneEnums::ChatMessageState ChatMessageCore::getMessageState() const {
return mMessageState;
}
void ChatMessageCore::setMessageState(LinphoneEnums::ChatMessageState state) {
if (mMessageState != state) {
mMessageState = state;
emit messageStateChanged();
}
}
QList<ImdnStatus> ChatMessageCore::getImdnStatusList() const {
return mImdnStatusList;
}
void ChatMessageCore::setImdnStatusList(QList<ImdnStatus> status) {
mImdnStatusList = status;
emit imdnStatusListChanged();
}
QStringList ChatMessageCore::getImdnStatusListLabels() const {
QStringList statusList;
int count = 0;
auto imdnSingletons = getImdnStatusAsSingletons();
for (auto &status : imdnSingletons) {
auto map = status.toMap();
auto val = map["state"].value<LinphoneEnums::ChatMessageState>();
auto count = map["count"].toInt();
statusList.append(QString("%1 %2").arg(LinphoneEnums::toString(val)).arg(count));
}
return statusList;
}
QVariantList ChatMessageCore::getImdnStatusAsSingletons() const {
QVariantList statusSingletons;
statusSingletons.append(createImdnStatusSingletonVariant(LinphoneEnums::ChatMessageState::StateDisplayed, 0));
statusSingletons.append(createImdnStatusSingletonVariant(LinphoneEnums::ChatMessageState::StateDeliveredToUser, 0));
statusSingletons.append(createImdnStatusSingletonVariant(LinphoneEnums::ChatMessageState::StateDelivered, 0));
statusSingletons.append(createImdnStatusSingletonVariant(LinphoneEnums::ChatMessageState::StateNotDelivered, 0));
for (auto &stat : mImdnStatusList) {
auto it = std::find_if(statusSingletons.begin(), statusSingletons.end(), [state = stat.mState](QVariant data) {
auto dataState = data.toMap()["state"].value<LinphoneEnums::ChatMessageState>();
return state == dataState;
});
if (it == statusSingletons.end()) {
if (!stat.mAddress.isEmpty()) statusSingletons.append(createImdnStatusSingletonVariant(stat.mState, 1));
} else {
auto map = it->toMap();
auto count = map["count"].toInt();
++count;
map.remove("count");
map.insert("count", count);
int index = std::distance(statusSingletons.begin(), it);
statusSingletons.replace(index, map);
}
}
return statusSingletons;
}
std::shared_ptr<ChatMessageModel> ChatMessageCore::getModel() const {
return mChatMessageModel;
}
// ConferenceInfoGui *ChatMessageCore::getConferenceInfoGui() const {
// return mConferenceInfo ? new ConferenceInfoGui(mConferenceInfo) : nullptr;
// }
ChatMessageContentGui *ChatMessageCore::getVoiceRecordingContent() const {
return new ChatMessageContentGui(mVoiceRecordingContent);
}

View file

@ -0,0 +1,246 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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/>.
*/
#ifndef CHAT_MESSAGE_CORE_H_
#define CHAT_MESSAGE_CORE_H_
#include "EventLogCore.hpp"
#include "core/chat/message/content/ChatMessageContentGui.hpp"
#include "core/chat/message/content/ChatMessageContentProxy.hpp"
#include "core/conference/ConferenceInfoCore.hpp"
#include "core/conference/ConferenceInfoGui.hpp"
#include "model/chat/message/ChatMessageModel.hpp"
#include "tool/AbstractObject.hpp"
#include "tool/thread/SafeConnection.hpp"
#include <QObject>
#include <QSharedPointer>
#include <linphone++/linphone.hh>
struct ImdnStatus {
Q_GADGET
Q_PROPERTY(QString address MEMBER mAddress)
Q_PROPERTY(LinphoneEnums::ChatMessageState state MEMBER mState)
Q_PROPERTY(QDateTime lastUpdatedTime MEMBER mLastUpdatedTime)
public:
QString mAddress;
LinphoneEnums::ChatMessageState mState;
QDateTime mLastUpdatedTime;
ImdnStatus(const QString &address, const LinphoneEnums::ChatMessageState &state, QDateTime mLastUpdatedTime);
ImdnStatus();
ImdnStatus operator=(ImdnStatus r);
bool operator==(const ImdnStatus &r) const;
bool operator!=(ImdnStatus r);
static ImdnStatus createMessageImdnStatusVariant(const QString &address,
const LinphoneEnums::ChatMessageState &state,
QDateTime mLastUpdatedTime);
};
struct Reaction {
Q_GADGET
Q_PROPERTY(QString body MEMBER mBody)
Q_PROPERTY(QString address MEMBER mAddress)
public:
QString mBody;
QString mAddress;
Reaction operator=(Reaction r);
bool operator==(const Reaction &r) const;
bool operator!=(Reaction r);
static Reaction createMessageReactionVariant(const QString &body, const QString &address);
};
class ChatCore;
class EventLogCore;
class ChatMessageCore : public QObject, public AbstractObject {
Q_OBJECT
Q_PROPERTY(QDateTime timestamp READ getTimestamp CONSTANT)
Q_PROPERTY(QString text READ getText WRITE setText NOTIFY textChanged)
Q_PROPERTY(QString utf8Text MEMBER mUtf8Text CONSTANT)
Q_PROPERTY(bool hasTextContent MEMBER mHasTextContent CONSTANT)
Q_PROPERTY(QString peerAddress READ getPeerAddress CONSTANT)
Q_PROPERTY(QString fromAddress READ getFromAddress CONSTANT)
Q_PROPERTY(QString toAddress READ getToAddress CONSTANT)
Q_PROPERTY(QString peerName READ getPeerName CONSTANT)
Q_PROPERTY(QString fromName READ getFromName CONSTANT)
Q_PROPERTY(QString totalReactionsLabel READ getTotalReactionsLabel CONSTANT)
Q_PROPERTY(LinphoneEnums::ChatMessageState messageState READ getMessageState WRITE setMessageState NOTIFY
messageStateChanged)
Q_PROPERTY(bool isRemoteMessage READ isRemoteMessage CONSTANT)
Q_PROPERTY(bool isFromChatGroup READ isFromChatGroup CONSTANT)
Q_PROPERTY(bool isEphemeral READ isEphemeral CONSTANT)
Q_PROPERTY(
int ephemeralDuration READ getEphemeralDuration WRITE setEphemeralDuration NOTIFY ephemeralDurationChanged)
Q_PROPERTY(bool isRead READ isRead WRITE setIsRead NOTIFY isReadChanged)
Q_PROPERTY(QString ownReaction READ getOwnReaction WRITE setOwnReaction NOTIFY messageReactionChanged)
Q_PROPERTY(QStringList imdnStatusListAsString READ getImdnStatusListLabels NOTIFY imdnStatusListChanged)
Q_PROPERTY(QList<ImdnStatus> imdnStatusList READ getImdnStatusList NOTIFY imdnStatusListChanged)
Q_PROPERTY(QVariantList imdnStatusAsSingletons READ getImdnStatusAsSingletons NOTIFY imdnStatusListChanged)
Q_PROPERTY(QList<Reaction> reactions READ getReactions WRITE setReactions NOTIFY messageReactionChanged)
Q_PROPERTY(QList<QVariant> reactionsSingleton READ getReactionsSingleton NOTIFY singletonReactionMapChanged)
Q_PROPERTY(
QStringList reactionsSingletonAsStrings READ getReactionsSingletonAsStrings NOTIFY singletonReactionMapChanged)
Q_PROPERTY(bool isForward MEMBER mIsForward CONSTANT)
Q_PROPERTY(bool isReply MEMBER mIsReply CONSTANT)
Q_PROPERTY(QString replyText MEMBER mReplyText CONSTANT)
Q_PROPERTY(QString repliedToName MEMBER mRepliedToName CONSTANT)
Q_PROPERTY(bool hasFileContent MEMBER mHasFileContent CONSTANT)
Q_PROPERTY(bool isVoiceRecording MEMBER mIsVoiceRecording CONSTANT)
Q_PROPERTY(bool isCalendarInvite MEMBER mIsCalendarInvite CONSTANT)
Q_PROPERTY(bool isOutgoing MEMBER mIsOutgoing CONSTANT)
Q_PROPERTY(bool isRetractable MEMBER mIsRetractable CONSTANT)
Q_PROPERTY(bool isRetracted READ isRetracted NOTIFY isRetractedChanged)
Q_PROPERTY(bool isEditable MEMBER mIsEditable CONSTANT)
Q_PROPERTY(bool isEdited READ isEdited NOTIFY edited)
public:
static QSharedPointer<ChatMessageCore> create(const std::shared_ptr<linphone::ChatMessage> &chatmessage);
ChatMessageCore(const std::shared_ptr<linphone::ChatMessage> &chatmessage);
~ChatMessageCore();
void setSelf(QSharedPointer<ChatMessageCore> me);
QList<ImdnStatus> computeDeliveryStatus(const std::shared_ptr<linphone::ChatMessage> &message);
QString getText() const;
void setText(QString text);
QString getPeerAddress() const;
QString getPeerName() const;
QString getFromAddress() const;
QString getFromName() const;
QString getToAddress() const;
QString getToName() const;
QString getMessageId() const;
bool isRemoteMessage() const;
bool isFromChatGroup() const;
bool isEphemeral() const;
int getEphemeralDuration() const;
void setEphemeralDuration(int duration);
QDateTime getTimestamp() const;
bool hasFileContent() const;
bool isRead() const;
void setIsRead(bool read);
bool isRetracted() const;
void setRetracted();
bool isEdited() const;
QString getOwnReaction() const;
void setOwnReaction(const QString &reaction);
QString getTotalReactionsLabel() const;
QList<Reaction> getReactions() const;
QList<QVariant> getReactionsSingleton() const;
QStringList getReactionsSingletonAsStrings() const;
QList<QSharedPointer<ChatMessageContentCore>> getChatMessageContentList() const;
void removeOneReactionFromSingletonMap(const QString &body);
void resetReactionsSingleton();
void setReactions(const QList<Reaction> &reactions);
void removeReaction(const Reaction &reaction);
void removeReaction(const QString &address);
LinphoneEnums::ChatMessageState getMessageState() const;
void setMessageState(LinphoneEnums::ChatMessageState state);
QList<ImdnStatus> getImdnStatusList() const;
void setImdnStatusList(QList<ImdnStatus> status);
QVariantList getImdnStatusAsSingletons() const;
QStringList getImdnStatusListLabels() const;
std::shared_ptr<ChatMessageModel> getModel() const;
Q_INVOKABLE ChatMessageContentGui *getVoiceRecordingContent() const;
signals:
void textChanged(QString text);
void utf8TextChanged(QString text);
void isReadChanged(bool read);
void isRemoteMessageChanged(bool isRemote);
void messageStateChanged();
void imdnStatusListChanged();
void messageReactionChanged();
void singletonReactionMapChanged();
void ephemeralDurationChanged(int duration);
void isRetractedChanged();
void edited();
void lDelete();
void deleted();
void lRetract();
void lMarkAsRead();
void readChanged();
void lSendReaction(const QString &reaction);
void lRemoveReaction();
void lSend();
private:
DECLARE_ABSTRACT_OBJECT
QString mText;
QString mUtf8Text;
bool mHasTextContent;
QString mPeerAddress;
QString mFromAddress;
QString mToAddress;
QString mFromName;
QString mToName;
QString mPeerName;
QString mMessageId;
QString mOwnReaction;
QList<Reaction> mReactions;
QList<ImdnStatus> mImdnStatusList;
QList<QVariant> mReactionsSingletonMap;
QDateTime mTimestamp;
bool mIsRemoteMessage = false;
bool mIsFromChatGroup = false;
bool mIsRead = false;
bool mIsForward = false;
bool mIsReply = false;
QString mReplyText;
QString mRepliedToName;
bool mHasFileContent = false;
bool mIsCalendarInvite = false;
bool mIsVoiceRecording = false;
bool mIsEphemeral = false;
int mEphemeralDuration = 0;
bool mIsRetractable = false;
bool mIsRetracted = false;
bool mIsEditable = false;
bool mIsEdited = false;
bool mIsOutgoing = false;
QString mTotalReactionsLabel;
LinphoneEnums::ChatMessageState mMessageState;
QList<QSharedPointer<ChatMessageContentCore>> mChatMessageContentList;
// for voice recording creation message
QSharedPointer<ChatMessageContentCore> mVoiceRecordingContent;
// QSharedPointer<ConferenceInfoCore> mConferenceInfo = nullptr;
std::shared_ptr<ChatMessageModel> mChatMessageModel;
QSharedPointer<SafeConnection<ChatMessageCore, ChatMessageModel>> mChatMessageModelConnection;
};
#endif // CHAT_MESSAGE_CORE_H_

View file

@ -0,0 +1,39 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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 "ChatMessageGui.hpp"
#include "ChatMessageCore.hpp"
#include "core/App.hpp"
DEFINE_ABSTRACT_OBJECT(ChatMessageGui)
ChatMessageGui::ChatMessageGui(QSharedPointer<ChatMessageCore> core) {
App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::JavaScriptOwnership);
mCore = core;
if (isInLinphoneThread()) moveToThread(App::getInstance()->thread());
}
ChatMessageGui::~ChatMessageGui() {
mustBeInMainThread("~" + getClassName());
}
ChatMessageCore *ChatMessageGui::getCore() const {
return mCore.get();
}

View file

@ -0,0 +1,41 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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/>.
*/
#ifndef CHAT_MESSAGE_GUI_H_
#define CHAT_MESSAGE_GUI_H_
#include "ChatMessageCore.hpp"
#include <QObject>
#include <QSharedPointer>
class ChatMessageGui : public QObject, public AbstractObject {
Q_OBJECT
Q_PROPERTY(ChatMessageCore *core READ getCore CONSTANT)
public:
ChatMessageGui(QSharedPointer<ChatMessageCore> core);
~ChatMessageGui();
ChatMessageCore *getCore() const;
QSharedPointer<ChatMessageCore> mCore;
DECLARE_ABSTRACT_OBJECT
};
#endif

View file

@ -0,0 +1,173 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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 "EventLogCore.hpp"
#include "core/App.hpp"
#include "core/chat/ChatCore.hpp"
#include "model/chat/message/EventLogModel.hpp"
#include "model/tool/ToolModel.hpp"
DEFINE_ABSTRACT_OBJECT(EventLogCore)
QSharedPointer<EventLogCore> EventLogCore::create(const std::shared_ptr<const linphone::EventLog> &eventLog,
const std::shared_ptr<linphone::ChatRoom> &chatRoom) {
auto sharedPointer = QSharedPointer<EventLogCore>(new EventLogCore(eventLog, chatRoom), &QObject::deleteLater);
sharedPointer->setSelf(sharedPointer);
sharedPointer->moveToThread(App::getInstance()->thread());
return sharedPointer;
}
EventLogCore::EventLogCore(const std::shared_ptr<const linphone::EventLog> &eventLog,
const std::shared_ptr<linphone::ChatRoom> &chatRoom) {
mustBeInLinphoneThread(getClassName());
App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership);
mEventLogType = LinphoneEnums::fromLinphone(eventLog->getType());
mEventLogModel = Utils::makeQObject_ptr<EventLogModel>(eventLog);
mTimestamp = QDateTime::fromMSecsSinceEpoch(eventLog->getCreationTime() * 1000);
auto chatmessage = eventLog->getChatMessage();
if (chatmessage) {
mChatMessageCore = ChatMessageCore::create(chatmessage);
mEventId = Utils::coreStringToAppString(chatmessage->getMessageId());
mTimestamp = QDateTime::fromSecsSinceEpoch(chatmessage->getTime());
} else if (eventLog->getCallLog()) {
mCallHistoryCore = CallHistoryCore::create(eventLog->getCallLog());
mEventId = Utils::coreStringToAppString(eventLog->getCallLog()->getCallId());
}
if (mEventId.isEmpty()) { // getNotifyId
QString type = QString::fromLatin1(
QMetaEnum::fromType<LinphoneEnums::EventLogType>().valueToKey(static_cast<int>(mEventLogType)));
mEventId = type + QString::number(static_cast<qint64>(eventLog->getCreationTime()));
computeEvent(eventLog, chatRoom);
}
}
EventLogCore::~EventLogCore() {
}
void EventLogCore::setSelf(QSharedPointer<EventLogCore> me) {
}
QString EventLogCore::getEventLogId() {
return mEventId;
}
QSharedPointer<ChatMessageCore> EventLogCore::getChatMessageCore() {
return mChatMessageCore;
}
ChatMessageGui *EventLogCore::getChatMessageGui() {
return mChatMessageCore ? new ChatMessageGui(mChatMessageCore) : nullptr;
}
QSharedPointer<CallHistoryCore> EventLogCore::getCallHistoryCore() {
return mCallHistoryCore;
}
ChatMessageCore *EventLogCore::getChatMessageCorePointer() {
return mChatMessageCore.get();
}
CallHistoryCore *EventLogCore::getCallHistoryCorePointer() {
return mCallHistoryCore.get();
}
QDateTime EventLogCore::getTimestamp() const {
return mTimestamp;
}
std::shared_ptr<EventLogModel> EventLogCore::getModel() const {
return mEventLogModel;
}
// Events (other than ChatMessage and CallLog which are handled in their respective Core)
void EventLogCore::computeEvent(const std::shared_ptr<const linphone::EventLog> &eventLog,
const std::shared_ptr<linphone::ChatRoom> &chatRoom) {
mustBeInLinphoneThread(getClassName());
mHandled = true;
mImportant = false;
mEphemeralRelated = false;
auto participantAddress = eventLog->getParticipantAddress() ? eventLog->getParticipantAddress() : nullptr;
switch (eventLog->getType()) {
case linphone::EventLog::Type::ConferenceCreated:
if (chatRoom->hasCapability((int)linphone::ChatRoom::Capabilities::OneToOne) &&
!chatRoom->hasCapability((int)linphone::ChatRoom::Capabilities::Conference))
mHandled = false;
mEventDetails = tr("conference_created_event");
break;
case linphone::EventLog::Type::ConferenceTerminated:
if (chatRoom->hasCapability((int)linphone::ChatRoom::Capabilities::OneToOne) &&
!chatRoom->hasCapability((int)linphone::ChatRoom::Capabilities::Conference))
mHandled = false;
mEventDetails = tr("conference_created_terminated");
mImportant = true;
break;
case linphone::EventLog::Type::ConferenceParticipantAdded:
mEventDetails = tr("conference_participant_added_event").arg(ToolModel::getDisplayName(participantAddress));
break;
case linphone::EventLog::Type::ConferenceParticipantRemoved:
mEventDetails =
tr("conference_participant_removed_event").arg(ToolModel::getDisplayName(participantAddress));
mImportant = true;
break;
case linphone::EventLog::Type::ConferenceSecurityEvent: {
if (eventLog->getSecurityEventType() == linphone::EventLog::SecurityEventType::SecurityLevelDowngraded) {
auto faultyParticipant = eventLog->getSecurityEventFaultyDeviceAddress()
? eventLog->getSecurityEventFaultyDeviceAddress()
: nullptr;
if (faultyParticipant)
mEventDetails = tr("conference_security_event").arg(ToolModel::getDisplayName(faultyParticipant));
else if (participantAddress)
mEventDetails = tr("conference_security_event").arg(ToolModel::getDisplayName(participantAddress));
mImportant = true;
} else mHandled = false;
break;
}
case linphone::EventLog::Type::ConferenceEphemeralMessageEnabled:
mEphemeralRelated = true;
mEventDetails = tr("conference_ephemeral_message_enabled_event")
.arg(Utils::getEphemeralFormatedTime(eventLog->getEphemeralMessageLifetime()));
break;
case linphone::EventLog::Type::ConferenceEphemeralMessageLifetimeChanged:
mEphemeralRelated = true;
mHandled = eventLog->getEphemeralMessageLifetime() != 0; // Disabled is sent in case of 0.
mEventDetails = tr("conference_ephemeral_message_lifetime_changed_event")
.arg(Utils::getEphemeralFormatedTime(eventLog->getEphemeralMessageLifetime()));
break;
case linphone::EventLog::Type::ConferenceEphemeralMessageDisabled:
mEphemeralRelated = true;
mEventDetails = tr("conference_ephemeral_message_disabled_event");
mImportant = true;
break;
case linphone::EventLog::Type::ConferenceSubjectChanged:
mEventDetails = tr("conference_subject_changed_event").arg(QString::fromStdString(eventLog->getSubject()));
break;
case linphone::EventLog::Type::ConferenceParticipantSetAdmin:
mEventDetails =
tr("conference_participant_set_admin_event").arg(ToolModel::getDisplayName(participantAddress));
break;
case linphone::EventLog::Type::ConferenceParticipantUnsetAdmin:
mEventDetails =
tr("conference_participant_unset_admin_event").arg(ToolModel::getDisplayName(participantAddress));
break;
default:
mHandled = false;
}
}

View file

@ -0,0 +1,96 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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/>.
*/
#ifndef EVENT_LOG_CORE_H_
#define EVENT_LOG_CORE_H_
#include "ChatMessageCore.hpp"
#include "core/call-history/CallHistoryCore.hpp"
#include "core/conference/ConferenceInfoCore.hpp"
#include "core/conference/ConferenceInfoGui.hpp"
#include "model/chat/message/ChatMessageModel.hpp"
#include "tool/AbstractObject.hpp"
#include "tool/LinphoneEnums.hpp"
#include "tool/thread/SafeConnection.hpp"
#include <QObject>
#include <QSharedPointer>
#include <linphone++/linphone.hh>
class ChatMessageCore;
class ChatMessageGui;
class EventLogModel;
class EventLogCore : public QObject, public AbstractObject {
Q_OBJECT
Q_PROPERTY(LinphoneEnums::EventLogType type MEMBER mEventLogType CONSTANT)
Q_PROPERTY(ChatMessageGui *chatMessageGui READ getChatMessageGui CONSTANT)
// Q_PROPERTY(NotifyCore *notification MEMBER mNotifyCore CONSTANT)
Q_PROPERTY(CallHistoryCore *callLog READ getCallHistoryCorePointer CONSTANT)
Q_PROPERTY(bool important MEMBER mImportant CONSTANT)
Q_PROPERTY(bool handled MEMBER mHandled CONSTANT)
Q_PROPERTY(QString eventDetails MEMBER mEventDetails CONSTANT)
Q_PROPERTY(QDateTime timestamp READ getTimestamp CONSTANT)
public:
static QSharedPointer<EventLogCore> create(const std::shared_ptr<const linphone::EventLog> &eventLog,
const std::shared_ptr<linphone::ChatRoom> &chatRoom);
EventLogCore(const std::shared_ptr<const linphone::EventLog> &eventLog,
const std::shared_ptr<linphone::ChatRoom> &chatRoom);
~EventLogCore();
void setSelf(QSharedPointer<EventLogCore> me);
QString getEventLogId();
QSharedPointer<ChatMessageCore> getChatMessageCore();
ChatMessageGui *getChatMessageGui();
QSharedPointer<CallHistoryCore> getCallHistoryCore();
QDateTime getTimestamp() const;
bool isHandled() const {
return mHandled;
}
bool isEphemeralRelated() const {
return mEphemeralRelated;
}
std::shared_ptr<EventLogModel> getModel() const;
private:
DECLARE_ABSTRACT_OBJECT
QString mEventId;
QSharedPointer<ChatMessageCore> mChatMessageCore = nullptr;
QSharedPointer<CallHistoryCore> mCallHistoryCore = nullptr;
LinphoneEnums::EventLogType mEventLogType;
bool mHandled = false;
bool mImportant;
bool mEphemeralRelated;
QString mEventDetails;
QDateTime mTimestamp;
ChatMessageCore *getChatMessageCorePointer();
CallHistoryCore *getCallHistoryCorePointer();
std::shared_ptr<EventLogModel> mEventLogModel;
void computeEvent(const std::shared_ptr<const linphone::EventLog> &eventLog,
const std::shared_ptr<linphone::ChatRoom> &chatRoom);
};
#endif // EventLogCore_H_

View file

@ -0,0 +1,38 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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 "EventLogGui.hpp"
#include "core/App.hpp"
DEFINE_ABSTRACT_OBJECT(EventLogGui)
EventLogGui::EventLogGui(QSharedPointer<EventLogCore> core) {
App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::JavaScriptOwnership);
mCore = core;
if (isInLinphoneThread()) moveToThread(App::getInstance()->thread());
}
EventLogGui::~EventLogGui() {
mustBeInMainThread("~" + getClassName());
}
EventLogCore *EventLogGui::getCore() const {
return mCore.get();
}

View file

@ -0,0 +1,41 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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/>.
*/
#ifndef EVENT_LOG_GUI_H_
#define EVENT_LOG_GUI_H_
#include "EventLogCore.hpp"
#include <QObject>
#include <QSharedPointer>
class EventLogGui : public QObject, public AbstractObject {
Q_OBJECT
Q_PROPERTY(EventLogCore *core READ getCore CONSTANT)
public:
EventLogGui(QSharedPointer<EventLogCore> core);
~EventLogGui();
EventLogCore *getCore() const;
QSharedPointer<EventLogCore> mCore;
DECLARE_ABSTRACT_OBJECT
};
#endif

View file

@ -0,0 +1,391 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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 "EventLogList.hpp"
#include "ChatMessageCore.hpp"
#include "ChatMessageGui.hpp"
#include "EventLogGui.hpp"
#include "core/App.hpp"
#include "core/call-history/CallHistoryGui.hpp"
#include "core/chat/ChatCore.hpp"
#include "core/chat/ChatGui.hpp"
#include "model/chat/message/EventLogModel.hpp"
#include <QSharedPointer>
#include <linphone++/linphone.hh>
// =============================================================================
DEFINE_ABSTRACT_OBJECT(EventLogList)
QSharedPointer<EventLogList> EventLogList::create() {
auto model = QSharedPointer<EventLogList>(new EventLogList(), &QObject::deleteLater);
model->moveToThread(App::getInstance()->thread());
model->setSelf(model);
return model;
}
EventLogList::EventLogList(QObject *parent) : ListProxy(parent) {
mustBeInMainThread(getClassName());
App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership);
}
EventLogList::~EventLogList() {
mustBeInMainThread("~" + getClassName());
}
ChatGui *EventLogList::getChat() const {
if (mChatCore) return new ChatGui(mChatCore);
else return nullptr;
}
QSharedPointer<ChatCore> EventLogList::getChatCore() const {
return mChatCore;
}
void EventLogList::disconnectItem(const QSharedPointer<EventLogCore> &item) {
auto message = item->getChatMessageCore();
if (message) {
disconnect(message.get(), &ChatMessageCore::isReadChanged, this, nullptr);
disconnect(message.get(), &ChatMessageCore::deleted, this, nullptr);
disconnect(message.get(), &ChatMessageCore::edited, this, nullptr);
disconnect(message.get(), &ChatMessageCore::isRetractedChanged, this, nullptr);
}
}
void EventLogList::connectItem(const QSharedPointer<EventLogCore> &item) {
auto message = item->getChatMessageCore();
if (message) {
connect(message.get(), &ChatMessageCore::isReadChanged, this, [this] {
if (mChatCore) emit mChatCore->lUpdateUnreadCount();
});
connect(message.get(), &ChatMessageCore::deleted, this, [this, item] {
if (mChatCore) emit mChatCore->lUpdateLastMessage();
remove(item);
});
connect(message.get(), &ChatMessageCore::isRetractedChanged, this, [this, item] {
if (mChatCore) emit mChatCore->lUpdateUnreadCount();
});
connect(message.get(), &ChatMessageCore::edited, this, [this, item] {
auto eventLogModel = item->getModel();
mCoreModelConnection->invokeToModel([this, eventLogModel, item]() {
auto chatRoom = mChatCore->getModel()->getMonitor();
auto newEventLog = EventLogCore::create(eventLogModel->getEventLog(), chatRoom);
bool wasLastMessage =
mChatCore->getModel()->getLastChatMessage() == eventLogModel->getEventLog()->getChatMessage();
mCoreModelConnection->invokeToCore([this, newEventLog, wasLastMessage, item] {
connectItem(newEventLog);
replace(item, newEventLog);
if (wasLastMessage) mChatCore->setLastMessage(newEventLog->getChatMessageCore());
});
});
});
}
}
void EventLogList::setChatCore(QSharedPointer<ChatCore> core) {
if (mChatCore != core) {
if (mChatCore) {
disconnect(mChatCore.get(), &ChatCore::eventsInserted, this, nullptr);
disconnect(mChatCore.get(), &ChatCore::eventListCleared, this, nullptr);
}
mChatCore = core;
if (mChatCore) {
connect(mChatCore.get(), &ChatCore::eventListCleared, this, [this] { resetData(); });
connect(mChatCore.get(), &ChatCore::eventsInserted, this, [this](QList<QSharedPointer<EventLogCore>> list) {
auto eventsList = getSharedList<EventLogCore>();
for (auto &event : list) {
auto it = std::find_if(eventsList.begin(), eventsList.end(),
[event](const QSharedPointer<EventLogCore> item) { return item == event; });
if (it == eventsList.end()) {
connectItem(event);
prepend(event);
int index;
get(event.get(), &index);
if (event->getChatMessageCore() && !event->getChatMessageCore()->isRemoteMessage()) {
emit eventInsertedByUser(index);
}
}
}
});
}
lUpdate();
// setIsUpdating(false);
emit chatGuiChanged();
}
}
void EventLogList::setChatGui(ChatGui *chat) {
auto chatCore = chat ? chat->mCore : nullptr;
setChatCore(chatCore);
}
void EventLogList::setDisplayItemsStep(int displayItemsStep) {
if (mDisplayItemsStep != displayItemsStep) {
mDisplayItemsStep = displayItemsStep;
emit displayItemsStepChanged();
}
}
void EventLogList::markIndexAsRead(int index) {
if (index < mList.count()) {
auto eventLog = mList[index].objectCast<EventLogCore>();
if (eventLog && eventLog->getChatMessageCore()) eventLog->getChatMessageCore()->lMarkAsRead();
}
}
void EventLogList::displayMore() {
auto loadMoreItems = [this] {
if (!mChatCore) return;
auto chatModel = mChatCore->getModel();
if (!chatModel) return;
mCoreModelConnection->invokeToModel([this, chatModel]() {
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
int maxSize = chatModel->getHistorySizeEvents();
int totalItemsCount = mList.count();
auto newCount = std::min(totalItemsCount + mDisplayItemsStep, maxSize);
if (newCount <= totalItemsCount) {
return;
}
auto linphoneLogs = chatModel->getHistoryRange(totalItemsCount, newCount);
QList<QSharedPointer<EventLogCore>> *events = new QList<QSharedPointer<EventLogCore>>();
for (auto it : linphoneLogs) {
auto model = EventLogCore::create(it, chatModel->getMonitor());
if (it->getChatMessage() || model->isHandled()) events->push_front(model);
}
mCoreModelConnection->invokeToCore([this, events] {
int currentCount = mList.count();
if (!events->isEmpty()) {
for (int i = events->size() - 1; i >= 0; --i) {
const auto &ev = events->at(i);
connectItem(ev);
}
add(*events);
}
});
});
};
if (mIsUpdating) {
connect(this, &EventLogList::isUpdatingChanged, this, [this, loadMoreItems] {
if (!mIsUpdating) {
disconnect(this, &EventLogList::isUpdatingChanged, this, nullptr);
loadMoreItems();
}
});
return;
} else loadMoreItems();
}
void EventLogList::loadMessagesUpTo(std::shared_ptr<linphone::EventLog> event) {
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
auto oldestEventLoaded = mList.count() > 0 ? getAt<EventLogCore>(mList.count() - 1) : nullptr;
auto linOldest = oldestEventLoaded
? std::const_pointer_cast<linphone::EventLog>(oldestEventLoaded->getModel()->getEventLog())
: nullptr;
auto chatModel = mChatCore->getModel();
assert(chatModel);
if (!chatModel) return;
int filters = static_cast<int>(linphone::ChatRoom::HistoryFilter::ChatMessage) |
static_cast<int>(linphone::ChatRoom::HistoryFilter::InfoNoDevice);
auto beforeEvents = chatModel->getHistoryRangeNear(mItemsToLoadBeforeSearchResult, 0, event, filters);
auto linphoneLogs = chatModel->getHistoryRangeBetween(event, linOldest, filters);
QList<QSharedPointer<EventLogCore>> *events = new QList<QSharedPointer<EventLogCore>>();
const auto &linChatRoom = chatModel->getMonitor();
for (const auto &it : beforeEvents) {
auto model = EventLogCore::create(it, linChatRoom);
if (it->getChatMessage() || model->isHandled()) events->push_front(model);
}
for (const auto &it : linphoneLogs) {
auto model = EventLogCore::create(it, linChatRoom);
if (it->getChatMessage() || model->isHandled()) events->push_front(model);
}
mCoreModelConnection->invokeToCore([this, events, event] {
for (const auto &e : *events) {
connectItem(e);
}
add(*events);
emit messagesLoadedUpTo(event);
});
}
int EventLogList::findFirstUnreadIndex() {
auto eventList = getSharedList<EventLogCore>();
auto it = std::find_if(eventList.rbegin(), eventList.rend(), [](const QSharedPointer<EventLogCore> item) {
auto chatmessage = item->getChatMessageCore();
return chatmessage && !chatmessage->isRead();
});
return it == eventList.rend() ? -1 : std::distance(it, eventList.rend()) - 1;
}
void EventLogList::findChatMessageWithFilter(QString filter, int startIndex, bool forward, bool isFirstResearch) {
if (mChatCore) {
if (isFirstResearch) mLastFoundResult.reset();
auto chatModel = mChatCore->getModel();
auto startEvent =
startIndex >= 0 && startIndex < mList.count() ? mList[startIndex].objectCast<EventLogCore>() : nullptr;
lInfo() << log().arg("searching event starting from index") << startIndex << "| event :"
<< (startEvent && startEvent->getChatMessageCore() ? startEvent->getChatMessageCore()->getText()
: "null")
<< "| filter :" << filter;
auto startEventModel = startEvent ? startEvent->getModel() : nullptr;
mCoreModelConnection->invokeToModel([this, chatModel, startEventModel, filter, forward, isFirstResearch] {
auto linStartEvent = startEventModel ? startEventModel->getEventLog() : nullptr;
auto eventLog = chatModel->searchMessageByText(filter, linStartEvent, forward);
if (!eventLog) {
// event not found, search in the entire history
lInfo() << log().arg("not found, search in entire history");
auto eventLog = chatModel->searchMessageByText(filter, nullptr, forward);
}
int index = -1;
if (eventLog) {
lInfo() << log().arg("event with filter found") << eventLog.get();
auto eventList = getSharedList<EventLogCore>();
auto it = std::find_if(eventList.begin(), eventList.end(),
[eventLog](const QSharedPointer<EventLogCore> item) {
return item->getModel()->getEventLog() == eventLog;
});
if (it != eventList.end()) {
int index = std::distance(eventList.begin(), it);
if (mLastFoundResult && mLastFoundResult == *it) index = -1;
mLastFoundResult = *it;
mCoreModelConnection->invokeToCore([this, index] { emit messageWithFilterFound(index); });
} else {
connect(this, &EventLogList::messagesLoadedUpTo, this,
[this](std::shared_ptr<linphone::EventLog> event) {
auto eventList = getSharedList<EventLogCore>();
auto it = std::find_if(eventList.begin(), eventList.end(),
[event](const QSharedPointer<EventLogCore> item) {
return item->getModel()->getEventLog() == event;
});
int index = it != eventList.end() ? std::distance(eventList.begin(), it) : -1;
if (mLastFoundResult && mLastFoundResult == *it) index = -1;
mLastFoundResult = *it;
mCoreModelConnection->invokeToCore(
[this, index] { emit messageWithFilterFound(index); });
});
loadMessagesUpTo(eventLog);
}
} else {
lInfo() << log().arg("event not found at all in history");
mCoreModelConnection->invokeToCore([this, index] { emit messageWithFilterFound(index); });
}
});
}
}
void EventLogList::setSelf(QSharedPointer<EventLogList> me) {
mCoreModelConnection = SafeConnection<EventLogList, CoreModel>::create(me, CoreModel::getInstance());
mCoreModelConnection->makeConnectToCore(&EventLogList::lUpdate, [this]() {
mustBeInMainThread(log().arg(Q_FUNC_INFO));
if (mIsUpdating) {
connect(this, &EventLogList::isUpdatingChanged, this, [this] {
if (!mIsUpdating) {
disconnect(this, &EventLogList::isUpdatingChanged, this, nullptr);
lUpdate();
}
});
return;
}
setIsUpdating(true);
beginResetModel();
for (auto &event : getSharedList<EventLogCore>()) {
disconnectItem(event);
}
mList.clear();
if (!mChatCore) {
endResetModel();
setIsUpdating(false);
return;
}
auto chatModel = mChatCore->getModel();
if (!chatModel) {
endResetModel();
setIsUpdating(false);
return;
}
mCoreModelConnection->invokeToModel([this, chatModel]() {
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
auto linphoneLogs = chatModel->getHistoryRange(0, mDisplayItemsStep);
QList<QSharedPointer<EventLogCore>> *events = new QList<QSharedPointer<EventLogCore>>();
for (auto it : linphoneLogs) {
auto model = EventLogCore::create(it, chatModel->getMonitor());
if (it->getChatMessage() || model->isHandled()) events->push_front(model);
}
mCoreModelConnection->invokeToCore([this, events] {
for (auto &event : *events) {
connectItem(event);
mList.append(event);
}
endResetModel();
setIsUpdating(false);
});
});
});
connect(this, &EventLogList::filterChanged, [this](QString filter) {
mFilter = filter;
lUpdate();
});
lUpdate();
}
QVariant EventLogList::data(const QModelIndex &index, int role) const {
int row = index.row();
if (!index.isValid() || row < 0 || row >= mList.count()) return QVariant();
auto core = mList[row].objectCast<EventLogCore>();
if (core->getChatMessageCore()) {
switch (role) {
case Qt::DisplayRole:
return QVariant::fromValue(new EventLogGui(core));
case Qt::DisplayRole + 1:
return "chatMessage";
}
} else if (core->getCallHistoryCore()) {
switch (role) {
case Qt::DisplayRole:
return QVariant::fromValue(new EventLogGui(core));
case Qt::DisplayRole + 1:
return "callLog";
}
} else if (core->isEphemeralRelated()) {
switch (role) {
case Qt::DisplayRole:
return QVariant::fromValue(new EventLogGui(core));
case Qt::DisplayRole + 1:
return "ephemeralEvent";
}
} else {
switch (role) {
case Qt::DisplayRole:
return QVariant::fromValue(new EventLogGui(core));
case Qt::DisplayRole + 1:
return "event";
}
}
return QVariant();
}
QHash<int, QByteArray> EventLogList::roleNames() const {
QHash<int, QByteArray> roles;
roles[Qt::DisplayRole] = "modelData";
roles[Qt::DisplayRole + 1] = "eventType";
return roles;
}

View file

@ -0,0 +1,88 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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/>.
*/
#ifndef EVENT_LOG_LIST_H_
#define EVENT_LOG_LIST_H_
#include "core/proxy/ListProxy.hpp"
#include "tool/AbstractObject.hpp"
#include "tool/thread/SafeConnection.hpp"
#include <QLocale>
class EventLogGui;
class EventLogCore;
class ChatCore;
class ChatGui;
class ChatModel;
// =============================================================================
class EventLogList : public ListProxy, public AbstractObject {
Q_OBJECT
public:
static QSharedPointer<EventLogList> create();
EventLogList(QObject *parent = Q_NULLPTR);
~EventLogList();
QSharedPointer<ChatCore> getChatCore() const;
ChatGui *getChat() const;
void setChatCore(QSharedPointer<ChatCore> core);
void setChatGui(ChatGui *chat);
void connectItem(const QSharedPointer<EventLogCore> &item);
void disconnectItem(const QSharedPointer<EventLogCore> &item);
void loadMessagesUpTo(std::shared_ptr<linphone::EventLog> event);
void setLastFoundResult(const QSharedPointer<EventLogCore> &eventLog);
int findFirstUnreadIndex();
void markIndexAsRead(int index);
void displayMore();
void setDisplayItemsStep(int displayItemsStep);
void findChatMessageWithFilter(QString filter, int startIndex, bool forward = true, bool isFirstResearch = true);
void setSelf(QSharedPointer<EventLogList> me);
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override;
signals:
void lUpdate();
void filterChanged(QString filter);
void eventInsertedByUser(int index);
void messageWithFilterFound(int index);
void listAboutToBeReset();
void chatGuiChanged();
void displayItemsStepChanged();
void messagesLoadedUpTo(std::shared_ptr<linphone::EventLog> event);
private:
QString mFilter;
QSharedPointer<ChatCore> mChatCore;
QSharedPointer<SafeConnection<EventLogList, CoreModel>> mCoreModelConnection;
int mDisplayItemsStep = 0;
int mItemsToLoadBeforeSearchResult = 3;
QSharedPointer<EventLogCore> mLastFoundResult;
DECLARE_ABSTRACT_OBJECT
};
#endif

View file

@ -0,0 +1,208 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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 "EventLogProxy.hpp"
#include "EventLogGui.hpp"
#include "EventLogList.hpp"
// #include "core/chat/ChatGui.hpp"
#include "core/App.hpp"
DEFINE_ABSTRACT_OBJECT(EventLogProxy)
EventLogProxy::EventLogProxy(QObject *parent) : QSortFilterProxyModel(parent) {
mList = EventLogList::create();
setSourceModel(mList.get());
}
EventLogProxy::~EventLogProxy() {
}
void EventLogProxy::setSourceModel(QAbstractItemModel *model) {
auto oldEventLogList = dynamic_cast<EventLogList *>(sourceModel());
if (oldEventLogList) {
disconnect(oldEventLogList, &EventLogList::displayItemsStepChanged, this, nullptr);
disconnect(oldEventLogList, &EventLogList::messageWithFilterFound, this, nullptr);
disconnect(oldEventLogList, &EventLogList::eventInsertedByUser, this, nullptr);
}
auto newEventLogList = dynamic_cast<EventLogList *>(model);
if (newEventLogList) {
connect(this, &EventLogProxy::displayItemsStepChanged, newEventLogList,
[this, newEventLogList] { newEventLogList->setDisplayItemsStep(mDisplayItemsStep); });
connect(newEventLogList, &EventLogList::messageWithFilterFound, this, [this, newEventLogList](int i) {
auto model = dynamic_cast<EventLogList *>(sourceModel());
int proxyIndex = mapFromSource(newEventLogList->index(i, 0)).row();
if (i != -1) {
loadUntil(proxyIndex);
}
emit indexWithFilterFound(proxyIndex);
});
connect(newEventLogList, &EventLogList::eventInsertedByUser, this, [this, newEventLogList](int i) {
int proxyIndex = mapFromSource(newEventLogList->index(i, 0)).row();
emit eventInsertedByUser(proxyIndex);
});
}
QSortFilterProxyModel::setSourceModel(model);
}
ChatGui *EventLogProxy::getChatGui() {
auto model = dynamic_cast<EventLogList *>(sourceModel());
if (!mChatGui && model) mChatGui = model->getChat();
return mChatGui;
}
void EventLogProxy::setChatGui(ChatGui *chat) {
auto model = dynamic_cast<EventLogList *>(sourceModel());
if (model) model->setChatGui(chat);
}
EventLogGui *EventLogProxy::getEventAtIndex(int i) {
auto eventCore = getEventCoreAtIndex(i);
return eventCore == nullptr ? nullptr : new EventLogGui(eventCore);
}
int EventLogProxy::getCount() const {
return rowCount();
}
int EventLogProxy::getInitialDisplayItems() const {
return mInitialDisplayItems;
}
void EventLogProxy::setInitialDisplayItems(int initialItems) {
if (mInitialDisplayItems != initialItems) {
mInitialDisplayItems = initialItems;
if (getMaxDisplayItems() <= mInitialDisplayItems) setMaxDisplayItems(initialItems);
if (getDisplayItemsStep() <= 0) setDisplayItemsStep(initialItems);
emit initialDisplayItemsChanged();
}
}
int EventLogProxy::getDisplayCount(int listCount, int maxCount) {
return maxCount >= 0 ? qMin(listCount, maxCount) : listCount;
}
int EventLogProxy::getDisplayCount(int listCount) const {
return getDisplayCount(listCount, mMaxDisplayItems);
}
QSharedPointer<EventLogCore> EventLogProxy::getEventCoreAtIndex(int i) {
auto model = dynamic_cast<EventLogList *>(sourceModel());
if (model) {
return model->getAt<EventLogCore>(mapToSource(index(i, 0)).row());
}
return nullptr;
}
void EventLogProxy::displayMore() {
auto model = dynamic_cast<EventLogList *>(sourceModel());
if (model) {
model->displayMore();
}
}
int EventLogProxy::getMaxDisplayItems() const {
return mMaxDisplayItems;
}
void EventLogProxy::setMaxDisplayItems(int maxItems) {
if (mMaxDisplayItems != maxItems) {
auto model = sourceModel();
int modelCount = model ? model->rowCount() : 0;
int oldCount = getDisplayCount(modelCount);
mMaxDisplayItems = maxItems;
if (getInitialDisplayItems() > mMaxDisplayItems) setInitialDisplayItems(maxItems);
if (getDisplayItemsStep() <= 0) setDisplayItemsStep(maxItems);
emit maxDisplayItemsChanged();
if (model && getDisplayCount(modelCount) != oldCount) {
invalidate();
}
}
}
int EventLogProxy::getDisplayItemsStep() const {
return mDisplayItemsStep;
}
void EventLogProxy::setDisplayItemsStep(int step) {
if (step > 0 && mDisplayItemsStep != step) {
mDisplayItemsStep = step;
emit displayItemsStepChanged();
}
}
void EventLogProxy::loadUntil(int index) {
if (mMaxDisplayItems < index) setMaxDisplayItems(index + mDisplayItemsStep);
}
int EventLogProxy::findFirstUnreadIndex() {
auto eventLogList = dynamic_cast<EventLogList *>(sourceModel());
if (eventLogList) {
auto listIndex = eventLogList->findFirstUnreadIndex();
if (listIndex != -1) {
listIndex = mapFromSource(eventLogList->index(listIndex, 0)).row();
if (mMaxDisplayItems <= listIndex) setMaxDisplayItems(listIndex + mDisplayItemsStep);
return listIndex;
} else {
return 0;
}
}
return 0;
}
QString EventLogProxy::getFilterText() const {
return mFilterText;
}
void EventLogProxy::setFilterText(const QString &filter) {
if (mFilterText != filter) {
#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0)
beginFilterChange();
mFilterText = filter;
endFilterChange();
#else
mFilterText = filter;
invalidateFilter();
#endif
emit filterTextChanged();
}
}
QSharedPointer<EventLogCore> EventLogProxy::getAt(int atIndex) const {
auto model = dynamic_cast<EventLogList *>(sourceModel());
if (model) {
return model->getAt<EventLogCore>(mapToSource(index(atIndex, 0)).row());
}
return nullptr;
}
void EventLogProxy::markIndexAsRead(int proxyIndex) {
auto event = getAt(proxyIndex);
if (event && event->getChatMessageCore()) event->getChatMessageCore()->lMarkAsRead();
}
void EventLogProxy::findIndexCorrespondingToFilter(int startIndex, bool forward, bool isFirstResearch) {
auto filter = getFilterText();
if (filter.isEmpty()) return;
auto eventLogList = dynamic_cast<EventLogList *>(sourceModel());
if (eventLogList) {
auto listIndex = mapToSource(index(startIndex, 0)).row();
eventLogList->findChatMessageWithFilter(filter, listIndex, forward, isFirstResearch);
}
}

View file

@ -0,0 +1,102 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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/>.
*/
#ifndef EVENT_LIST_PROXY_H_
#define EVENT_LIST_PROXY_H_
#include "EventLogList.hpp"
// #include "core/proxy/LimitProxy.hpp"
#include "tool/AbstractObject.hpp"
#include <QSortFilterProxyModel>
// =============================================================================
class ChatGui;
class EventLogProxy : public QSortFilterProxyModel, public AbstractObject {
Q_OBJECT
Q_PROPERTY(int count READ getCount NOTIFY countChanged)
Q_PROPERTY(ChatGui *chatGui READ getChatGui WRITE setChatGui NOTIFY chatGuiChanged)
Q_PROPERTY(int initialDisplayItems READ getInitialDisplayItems WRITE setInitialDisplayItems NOTIFY
initialDisplayItemsChanged)
Q_PROPERTY(int maxDisplayItems READ getMaxDisplayItems WRITE setMaxDisplayItems NOTIFY maxDisplayItemsChanged)
Q_PROPERTY(int displayItemsStep READ getDisplayItemsStep WRITE setDisplayItemsStep NOTIFY displayItemsStepChanged)
Q_PROPERTY(QString filterText READ getFilterText WRITE setFilterText NOTIFY filterTextChanged)
public:
// DECLARE_SORTFILTER_CLASS()
EventLogProxy(QObject *parent = Q_NULLPTR);
~EventLogProxy();
ChatGui *getChatGui();
void setChatGui(ChatGui *chat);
void setSourceModel(QAbstractItemModel *sourceModel) override;
virtual int getCount() const;
static int getDisplayCount(int listCount, int maxCount);
int getDisplayCount(int listCount) const;
int getInitialDisplayItems() const;
void setInitialDisplayItems(int initialItems);
int getMaxDisplayItems() const;
void setMaxDisplayItems(int maxItems);
int getDisplayItemsStep() const;
void setDisplayItemsStep(int step);
QString getFilterText() const;
void setFilterText(const QString &filter);
// bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
// bool lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const override;
QSharedPointer<EventLogCore> getAt(int atIndex) const;
Q_INVOKABLE void displayMore();
Q_INVOKABLE void loadUntil(int index);
Q_INVOKABLE EventLogGui *getEventAtIndex(int i);
QSharedPointer<EventLogCore> getEventCoreAtIndex(int i);
Q_INVOKABLE int findFirstUnreadIndex();
Q_INVOKABLE void markIndexAsRead(int proxyIndex);
Q_INVOKABLE void findIndexCorrespondingToFilter(int startIndex, bool forward = true, bool isFirstResearch = true);
signals:
void eventInsertedByUser(int index);
void indexWithFilterFound(int index);
void chatGuiChanged();
void countChanged();
void initialDisplayItemsChanged();
void maxDisplayItemsChanged();
void displayItemsStepChanged();
void filterTextChanged();
protected:
QSharedPointer<EventLogList> mList;
QSharedPointer<EventLogCore> mLastSearchStart;
ChatGui *mChatGui = nullptr;
int mInitialDisplayItems = -1;
int mMaxDisplayItems = -1;
int mDisplayItemsStep = 5;
QString mFilterText;
DECLARE_ABSTRACT_OBJECT
};
#endif

View file

@ -0,0 +1,272 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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 "ChatMessageContentCore.hpp"
#include "core/App.hpp"
#include "core/chat/ChatCore.hpp"
#include "model/tool/ToolModel.hpp"
#include "tool/providers/ThumbnailProvider.hpp"
DEFINE_ABSTRACT_OBJECT(ChatMessageContentCore)
QSharedPointer<ChatMessageContentCore>
ChatMessageContentCore::create(const std::shared_ptr<linphone::Content> &content,
std::shared_ptr<ChatMessageModel> chatMessageModel) {
auto sharedPointer = QSharedPointer<ChatMessageContentCore>(new ChatMessageContentCore(content, chatMessageModel),
&QObject::deleteLater);
sharedPointer->setSelf(sharedPointer);
sharedPointer->moveToThread(App::getInstance()->thread());
return sharedPointer;
}
ChatMessageContentCore::ChatMessageContentCore(const std::shared_ptr<linphone::Content> &content,
std::shared_ptr<ChatMessageModel> chatMessageModel) {
if (content) {
mName = Utils::coreStringToAppString(content->getName());
if (mName.isEmpty()) { // Try to find the name from file Path
QString fileName = Utils::coreStringToAppString(content->getFilePath());
if (!fileName.isEmpty()) {
mName = QFileInfo(fileName).baseName();
}
}
mFilePath = QDir::fromNativeSeparators(Utils::coreStringToAppString(content->getFilePath()));
mIsFile = content->isFile();
mIsFileEncrypted = content->isFileEncrypted();
mIsFileTransfer = content->isFileTransfer();
mIsCalendar = content->isIcalendar();
if (content->isIcalendar()) {
auto conferenceInfo = linphone::Factory::get()->createConferenceInfoFromIcalendarContent(content);
mConferenceInfo = ConferenceInfoCore::create(conferenceInfo);
}
mIsMultipart = content->isMultipart();
mIsText = content->isText();
mIsVoiceRecording = content->isVoiceRecording();
mIsVideo = Utils::isVideo(mFilePath);
mFileSize = (quint64)content->getFileSize();
mFileDuration = content->getFileDuration();
mFileOffset = 0;
mUtf8Text = Utils::coreStringToAppString(content->getUtf8Text());
auto chatRoom = chatMessageModel ? chatMessageModel->getMonitor()->getChatRoom() : nullptr;
mRichFormatText = ToolModel::encodeTextToQmlRichFormat(mUtf8Text, {}, chatRoom);
mWasDownloaded = !mFilePath.isEmpty() && QFileInfo(mFilePath).isFile();
mThumbnail = mFilePath.isEmpty()
? QUrl()
: QUrl(QString("image://%1/%2").arg(ThumbnailProvider::ProviderId).arg(mFilePath));
mChatMessageContentModel = Utils::makeQObject_ptr<ChatMessageContentModel>(content, chatMessageModel);
}
}
ChatMessageContentCore ::~ChatMessageContentCore() {
}
void ChatMessageContentCore::setSelf(QSharedPointer<ChatMessageContentCore> me) {
mChatMessageContentModelConnection =
SafeConnection<ChatMessageContentCore, ChatMessageContentModel>::create(me, mChatMessageContentModel);
auto updateThumbnailType = [this] {
if (Utils::isVideo(mFilePath)) mIsVideo = true;
emit isVideoChanged();
};
mChatMessageContentModelConnection->makeConnectToCore(
&ChatMessageContentCore::lCreateThumbnail, [this](const bool &force = false) {
mChatMessageContentModelConnection->invokeToModel(
[this, force] { mChatMessageContentModel->createThumbnail(); });
});
mChatMessageContentModelConnection->makeConnectToModel(
&ChatMessageContentModel::thumbnailChanged, [this, updateThumbnailType](QString thumbnail) {
mChatMessageContentModelConnection->invokeToCore([this, thumbnail] { setThumbnail(QUrl(thumbnail)); });
});
mChatMessageContentModelConnection->makeConnectToCore(&ChatMessageContentCore::lDownloadFile, [this]() {
mChatMessageContentModelConnection->invokeToModel([this] {
QString *error = new QString();
bool downloaded = mChatMessageContentModel->downloadFile(mName, error);
if (!downloaded) {
mChatMessageContentModelConnection->invokeToCore([this, error] {
//: Error downloading file %1
if (error->isEmpty()) *error = tr("download_file_default_error").arg(mName);
Utils::showInformationPopup(tr("info_popup_error_titile"), *error, false);
delete error;
});
} else delete error;
});
});
mChatMessageContentModelConnection->makeConnectToModel(
&ChatMessageContentModel::wasDownloadedChanged,
[this](const std::shared_ptr<linphone::Content> &content, bool downloaded) {
mChatMessageContentModelConnection->invokeToCore([this, downloaded] { setWasDownloaded(downloaded); });
});
mChatMessageContentModelConnection->makeConnectToModel(
&ChatMessageContentModel::filePathChanged,
[this](const std::shared_ptr<linphone::Content> &content, QString filePath) {
auto isFile = content->isFile();
auto isFileTransfer = content->isFileTransfer();
auto isFileEncrypted = content->isFileEncrypted();
mChatMessageContentModelConnection->invokeToCore([this, filePath, isFile, isFileTransfer, isFileEncrypted] {
setIsFile(isFile || QFileInfo(filePath).isFile());
setIsFileTransfer(isFileTransfer);
setIsFileEncrypted(isFileEncrypted);
setFilePath(filePath);
});
});
mChatMessageContentModelConnection->makeConnectToCore(&ChatMessageContentCore::lCancelDownloadFile, [this]() {
mChatMessageContentModelConnection->invokeToModel([this] { mChatMessageContentModel->cancelDownloadFile(); });
});
mChatMessageContentModelConnection->makeConnectToCore(
&ChatMessageContentCore::lOpenFile, [this](bool showDirectory = false) {
if (!QFileInfo(mFilePath).exists()) {
//: Error
Utils::showInformationPopup(tr("popup_error_title"),
//: Could not open file : unknown path %1
tr("popup_open_file_error_does_not_exist_message").arg(mFilePath), false);
} else {
mChatMessageContentModelConnection->invokeToModel([this, showDirectory] {
mChatMessageContentModel->openFile(mName, mWasDownloaded, showDirectory);
});
}
});
mChatMessageContentModelConnection->makeConnectToModel(
&ChatMessageContentModel::messageStateChanged, [this](linphone::ChatMessage::State state) {
mChatMessageContentModelConnection->invokeToCore(
[this, msgState = LinphoneEnums::fromLinphone(state)] { emit msgStateChanged(msgState); });
});
}
bool ChatMessageContentCore::isFile() const {
return mIsFile;
}
void ChatMessageContentCore::setIsFile(bool isFile) {
if (mIsFile != isFile) {
mIsFile = isFile;
emit isFileChanged();
}
}
bool ChatMessageContentCore::isVideo() const {
return mIsVideo;
}
bool ChatMessageContentCore::isFileEncrypted() const {
return mIsFileEncrypted;
}
void ChatMessageContentCore::setIsFileEncrypted(bool isFileEncrypted) {
if (mIsFileEncrypted != isFileEncrypted) {
mIsFileEncrypted = isFileEncrypted;
emit isFileEncryptedChanged();
}
}
bool ChatMessageContentCore::isFileTransfer() const {
return mIsFileTransfer;
}
void ChatMessageContentCore::setIsFileTransfer(bool isFileTransfer) {
if (mIsFileTransfer != isFileTransfer) {
mIsFileTransfer = isFileTransfer;
emit isFileTransferChanged();
}
}
bool ChatMessageContentCore::isCalendar() const {
return mIsCalendar;
}
bool ChatMessageContentCore::isMultipart() const {
return mIsMultipart;
}
bool ChatMessageContentCore::isText() const {
return mIsText;
}
bool ChatMessageContentCore::isVoiceRecording() const {
return mIsVoiceRecording;
}
QString ChatMessageContentCore::getFilePath() const {
return mFilePath;
}
void ChatMessageContentCore::setFilePath(QString path) {
if (mFilePath != path) {
mFilePath = path;
emit filePathChanged();
}
}
QString ChatMessageContentCore::getUtf8Text() const {
return mUtf8Text;
}
QString ChatMessageContentCore::getName() const {
return mName;
}
quint64 ChatMessageContentCore::getFileSize() const {
return mFileSize;
}
quint64 ChatMessageContentCore::getFileOffset() const {
return mFileOffset;
}
void ChatMessageContentCore::setFileOffset(quint64 fileOffset) {
if (mFileOffset != fileOffset) {
mFileOffset = fileOffset;
emit fileOffsetChanged();
}
}
int ChatMessageContentCore::getFileDuration() const {
return mFileDuration;
}
ConferenceInfoGui *ChatMessageContentCore::getConferenceInfoGui() const {
return mConferenceInfo ? new ConferenceInfoGui(mConferenceInfo) : nullptr;
}
bool ChatMessageContentCore::wasDownloaded() const {
return mWasDownloaded;
}
QUrl ChatMessageContentCore::getThumbnail() const {
return mThumbnail;
}
void ChatMessageContentCore::setThumbnail(const QUrl &data) {
if (mThumbnail != data) {
mThumbnail = data;
emit thumbnailChanged();
}
}
void ChatMessageContentCore::setWasDownloaded(bool wasDownloaded) {
if (mWasDownloaded != wasDownloaded) {
mWasDownloaded = wasDownloaded;
emit wasDownloadedChanged(wasDownloaded);
}
}
const std::shared_ptr<ChatMessageContentModel> &ChatMessageContentCore::getContentModel() const {
return mChatMessageContentModel;
}

View file

@ -0,0 +1,138 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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/>.
*/
#ifndef CHAT_MESSAGE_CONTENT_CORE_H_
#define CHAT_MESSAGE_CONTENT_CORE_H_
#include "core/conference/ConferenceInfoCore.hpp"
#include "core/conference/ConferenceInfoGui.hpp"
#include "model/chat/message/content/ChatMessageContentModel.hpp"
#include "tool/AbstractObject.hpp"
#include "tool/thread/SafeConnection.hpp"
#include <QObject>
#include <QSharedPointer>
#include <linphone++/linphone.hh>
class ChatMessageContentCore : public QObject, public AbstractObject {
Q_OBJECT
Q_PROPERTY(QString name READ getName CONSTANT)
Q_PROPERTY(quint64 fileOffset READ getFileOffset WRITE setFileOffset NOTIFY fileOffsetChanged)
Q_PROPERTY(QUrl thumbnail READ getThumbnail WRITE setThumbnail NOTIFY thumbnailChanged)
Q_PROPERTY(bool wasDownloaded READ wasDownloaded WRITE setWasDownloaded NOTIFY wasDownloadedChanged)
Q_PROPERTY(QString filePath READ getFilePath WRITE setFilePath NOTIFY filePathChanged)
Q_PROPERTY(QString utf8Text READ getUtf8Text CONSTANT)
Q_PROPERTY(QString richFormatText MEMBER mRichFormatText CONSTANT)
Q_PROPERTY(bool isFile READ isFile WRITE setIsFile NOTIFY isFileChanged)
Q_PROPERTY(bool isFileEncrypted READ isFileEncrypted WRITE setIsFileEncrypted NOTIFY isFileEncryptedChanged)
Q_PROPERTY(bool isFileTransfer READ isFileTransfer WRITE setIsFileTransfer NOTIFY isFileTransferChanged)
Q_PROPERTY(bool isCalendar READ isCalendar CONSTANT)
Q_PROPERTY(ConferenceInfoGui *conferenceInfo READ getConferenceInfoGui CONSTANT)
Q_PROPERTY(bool isMultipart READ isMultipart CONSTANT)
Q_PROPERTY(bool isText READ isText CONSTANT)
Q_PROPERTY(bool isVideo READ isVideo NOTIFY isVideoChanged)
Q_PROPERTY(bool isVoiceRecording READ isVoiceRecording CONSTANT)
Q_PROPERTY(int fileDuration READ getFileDuration CONSTANT)
Q_PROPERTY(quint64 fileSize READ getFileSize CONSTANT)
public:
static QSharedPointer<ChatMessageContentCore> create(const std::shared_ptr<linphone::Content> &content,
std::shared_ptr<ChatMessageModel> chatMessageModel);
ChatMessageContentCore(const std::shared_ptr<linphone::Content> &content,
std::shared_ptr<ChatMessageModel> chatMessageModel);
~ChatMessageContentCore();
void setSelf(QSharedPointer<ChatMessageContentCore> me);
bool isFile() const;
void setIsFile(bool isFile);
bool isFileEncrypted() const;
void setIsFileEncrypted(bool isFileEncrypted);
bool isFileTransfer() const;
void setIsFileTransfer(bool isFileTransfer);
bool isVideo() const;
bool isCalendar() const;
bool isMultipart() const;
bool isText() const;
bool isVoiceRecording() const;
QString getUtf8Text() const;
QString getName() const;
quint64 getFileSize() const;
quint64 getFileOffset() const;
void setFileOffset(quint64 fileOffset);
QString getFilePath() const;
void setFilePath(QString path);
int getFileDuration() const;
ConferenceInfoGui *getConferenceInfoGui() const;
void setThumbnail(const QUrl &data);
QUrl getThumbnail() const;
bool wasDownloaded() const;
void setWasDownloaded(bool downloaded);
const std::shared_ptr<ChatMessageContentModel> &getContentModel() const;
signals:
void msgStateChanged(LinphoneEnums::ChatMessageState state);
void thumbnailChanged();
void fileOffsetChanged();
void filePathChanged();
void isFileChanged();
void isFileTransferChanged();
void isFileEncryptedChanged();
void wasDownloadedChanged(bool downloaded);
void isVideoChanged();
void lCreateThumbnail(const bool &force = false);
void lRemoveDownloadedFile();
void lDownloadFile();
void lCancelDownloadFile();
void lOpenFile(bool showDirectory = false);
bool lSaveAs(const QString &path);
private:
DECLARE_ABSTRACT_OBJECT
bool mIsFile;
bool mIsVideo;
bool mIsFileEncrypted;
bool mIsFileTransfer;
bool mIsCalendar;
bool mIsMultipart;
bool mIsText;
bool mIsVoiceRecording;
int mFileDuration;
QUrl mThumbnail;
QString mUtf8Text;
QString mRichFormatText;
QString mFilePath;
QString mName;
quint64 mFileSize;
quint64 mFileOffset;
bool mWasDownloaded;
QSharedPointer<ConferenceInfoCore> mConferenceInfo = nullptr;
std::shared_ptr<ChatMessageContentModel> mChatMessageContentModel;
QSharedPointer<SafeConnection<ChatMessageContentCore, ChatMessageContentModel>> mChatMessageContentModelConnection;
};
#endif // CHAT_MESSAGE_CONTENT_CORE_H_

View file

@ -0,0 +1,38 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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 "ChatMessageContentGui.hpp"
#include "core/App.hpp"
DEFINE_ABSTRACT_OBJECT(ChatMessageContentGui)
ChatMessageContentGui::ChatMessageContentGui(QSharedPointer<ChatMessageContentCore> core) {
App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::JavaScriptOwnership);
mCore = core;
if (isInLinphoneThread()) moveToThread(App::getInstance()->thread());
}
ChatMessageContentGui::~ChatMessageContentGui() {
mustBeInMainThread("~" + getClassName());
}
ChatMessageContentCore *ChatMessageContentGui::getCore() const {
return mCore.get();
}

View file

@ -0,0 +1,41 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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/>.
*/
#ifndef CHAT_MESSAGE_CONTENT_GUI_H_
#define CHAT_MESSAGE_CONTENT_GUI_H_
#include "core/chat/message/content/ChatMessageContentCore.hpp"
#include <QObject>
#include <QSharedPointer>
class ChatMessageContentGui : public QObject, public AbstractObject {
Q_OBJECT
Q_PROPERTY(ChatMessageContentCore *core READ getCore CONSTANT)
public:
ChatMessageContentGui(QSharedPointer<ChatMessageContentCore> core);
~ChatMessageContentGui();
ChatMessageContentCore *getCore() const;
QSharedPointer<ChatMessageContentCore> mCore;
DECLARE_ABSTRACT_OBJECT
};
#endif

View file

@ -0,0 +1,227 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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 "ChatMessageContentList.hpp"
#include "core/App.hpp"
#include "core/chat/ChatCore.hpp"
#include "core/chat/message/content/ChatMessageContentGui.hpp"
#include <QMimeDatabase>
#include <QSharedPointer>
#include <linphone++/linphone.hh>
// =============================================================================
DEFINE_ABSTRACT_OBJECT(ChatMessageContentList)
QSharedPointer<ChatMessageContentList> ChatMessageContentList::create() {
auto model = QSharedPointer<ChatMessageContentList>(new ChatMessageContentList(), &QObject::deleteLater);
model->moveToThread(App::getInstance()->thread());
model->setSelf(model);
return model;
}
ChatMessageContentList::ChatMessageContentList(QObject *parent) : ListProxy(parent) {
mustBeInMainThread(getClassName());
App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership);
}
ChatMessageContentList::~ChatMessageContentList() {
mustBeInMainThread("~" + getClassName());
mModelConnection = nullptr;
}
ChatMessageGui *ChatMessageContentList::getChatMessage() const {
if (mChatMessageCore) return new ChatMessageGui(mChatMessageCore);
else return nullptr;
}
QSharedPointer<ChatMessageCore> ChatMessageContentList::getChatMessageCore() const {
return mChatMessageCore;
}
void ChatMessageContentList::setChatMessageCore(QSharedPointer<ChatMessageCore> core) {
if (mChatMessageCore != core) {
// if (mChatMessageCore) disconnect(mChatMessageCore.get(), &ChatCore::, this, nullptr);
mChatMessageCore = core;
// if (mChatMessageCore)
// connect(mChatMessageCore.get(), &ChatCore::messageListChanged, this, &ChatMessageContentList::lUpdate);
emit chatMessageChanged();
lUpdate();
}
}
void ChatMessageContentList::setChatMessageGui(ChatMessageGui *chat) {
auto chatCore = chat ? chat->mCore : nullptr;
setChatMessageCore(chatCore);
}
int ChatMessageContentList::findFirstUnreadIndex() {
auto chatList = getSharedList<ChatMessageCore>();
auto it = std::find_if(chatList.begin(), chatList.end(),
[](const QSharedPointer<ChatMessageCore> item) { return !item->isRead(); });
return it == chatList.end() ? -1 : std::distance(chatList.begin(), it);
}
void ChatMessageContentList::addFiles(const QStringList &paths) {
mustBeInMainThread(log().arg(Q_FUNC_INFO));
QStringList finalList;
QList<QFileInfo> fileList;
int nbNotFound = 0;
QString lastNotFound;
int nbExcess = 0;
int count = rowCount();
for (auto &path : paths) {
QFileInfo file(path.toUtf8());
// #ifdef _WIN32
// // A bug from FileDialog suppose that the file is local and overwrite the uri by removing "\\".
// if (!file.exists()) {
// path.prepend("\\\\");
// file.setFileName(path);
// }
// #endif
if (!file.exists()) {
++nbNotFound;
lastNotFound = path;
continue;
}
if (count + finalList.count() >= 12) {
++nbExcess;
continue;
}
finalList.append(path);
fileList.append(file);
}
if (nbNotFound > 0) {
//: Error adding file
Utils::showInformationPopup(tr("popup_error_title"),
//: File was not found: %1
(nbNotFound == 1 ? tr("popup_error_path_does_not_exist_message").arg(lastNotFound)
//: %n files were not found
: tr("popup_error_nb_files_not_found_message").arg(nbNotFound)),
false);
}
if (nbExcess > 0) {
//: Error
Utils::showInformationPopup(tr("popup_error_title"),
//: You can send 12 files maximum at a time. %n files were ignored
tr("popup_error_max_files_count_message", "", nbExcess), false);
}
mModelConnection->invokeToModel([this, finalList, fileList] {
int nbTooBig = 0;
int nbMimeError = 0;
QString lastMimeError;
QList<QSharedPointer<ChatMessageContentCore>> contentList;
for (auto &file : fileList) {
qint64 fileSize = file.size();
if (fileSize > Constants::FileSizeLimit) {
++nbTooBig;
lWarning() << log().arg("Unable to send file. (Size limit=%1)").arg(Constants::FileSizeLimit);
continue;
}
auto name = file.fileName().toStdString();
auto path = file.filePath();
std::shared_ptr<linphone::Content> content = CoreModel::getInstance()->getCore()->createContent();
QStringList mimeType = QMimeDatabase().mimeTypeForFile(path).name().split('/');
if (mimeType.length() != 2) {
++nbMimeError;
lastMimeError = path;
lWarning() << log().arg("Unable to get supported mime type for: `%1`.").arg(path);
continue;
}
content->setType(Utils::appStringToCoreString(mimeType[0]));
content->setSubtype(Utils::appStringToCoreString(mimeType[1]));
content->setSize(size_t(fileSize));
content->setName(name);
content->setFilePath(Utils::appStringToCoreString(path));
contentList.append(ChatMessageContentCore::create(content, nullptr));
}
if (nbTooBig > 0) {
//: Error adding file
Utils::showInformationPopup(
tr("popup_error_title"),
//: %n files were ignored cause they exceed the maximum size. (Size limit=%2)
tr("popup_error_file_too_big_message").arg(nbTooBig).arg(Constants::FileSizeLimit), false);
}
if (nbMimeError > 0) {
//: Error adding file
Utils::showInformationPopup(tr("popup_error_title"),
//: Unable to get supported mime type for: `%1`.
(nbMimeError == 1
? tr("popup_error_unsupported_file_message").arg(lastMimeError)
//: Unable to get supported mime type for %1 files.
: tr("popup_error_unsupported_files_message").arg(nbMimeError)),
false);
}
mModelConnection->invokeToCore([this, contentList] {
for (auto &contentCore : contentList) {
connect(contentCore.get(), &ChatMessageContentCore::isFileChanged, this, [this, contentCore] {
int i = -1;
get(contentCore.get(), &i);
emit dataChanged(index(i), index(i));
});
add(contentCore);
contentCore->lCreateThumbnail(
true); // Was not created because linphone::Content is not considered as a file (yet)
}
});
});
}
void ChatMessageContentList::setSelf(QSharedPointer<ChatMessageContentList> me) {
mModelConnection = SafeConnection<ChatMessageContentList, CoreModel>::create(me, CoreModel::getInstance());
mModelConnection->makeConnectToCore(&ChatMessageContentList::lUpdate, [this]() {
for (auto &content : getSharedList<ChatMessageContentCore>()) {
if (content) disconnect(content.get(), &ChatMessageContentCore::wasDownloadedChanged, this, nullptr);
if (content) disconnect(content.get(), &ChatMessageContentCore::thumbnailChanged, this, nullptr);
}
if (!mChatMessageCore) return;
auto contents = mChatMessageCore->getChatMessageContentList();
for (auto &content : contents) {
connect(content.get(), &ChatMessageContentCore::wasDownloadedChanged, this,
[this, content](bool wasDownloaded) {
if (wasDownloaded) {
content->lCreateThumbnail();
}
});
connect(content.get(), &ChatMessageContentCore::thumbnailChanged, this, [this] { emit lUpdate(); });
}
resetData<ChatMessageContentCore>(contents);
});
mModelConnection->makeConnectToCore(&ChatMessageContentList::lAddFiles,
[this](const QStringList &paths) { addFiles(paths); });
emit lUpdate();
}
QVariant ChatMessageContentList::data(const QModelIndex &index, int role) const {
int row = index.row();
if (!index.isValid() || row < 0 || row >= mList.count()) return QVariant();
if (role == Qt::DisplayRole)
return QVariant::fromValue(new ChatMessageContentGui(mList[row].objectCast<ChatMessageContentCore>()));
return QVariant();
}

View file

@ -0,0 +1,64 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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/>.
*/
#ifndef CHAT_MESSAGE_CONTENT_LIST_H_
#define CHAT_MESSAGE_CONTENT_LIST_H_
#include "core/proxy/ListProxy.hpp"
#include "tool/AbstractObject.hpp"
#include "tool/thread/SafeConnection.hpp"
#include <QLocale>
class ChatMessageGui;
class ChatMessageCore;
// =============================================================================
class ChatMessageContentList : public ListProxy, public AbstractObject {
Q_OBJECT
public:
static QSharedPointer<ChatMessageContentList> create();
ChatMessageContentList(QObject *parent = Q_NULLPTR);
~ChatMessageContentList();
QSharedPointer<ChatMessageCore> getChatMessageCore() const;
ChatMessageGui *getChatMessage() const;
void setChatMessageCore(QSharedPointer<ChatMessageCore> core);
void setChatMessageGui(ChatMessageGui *chat);
int findFirstUnreadIndex();
void addFiles(const QStringList &paths);
void setSelf(QSharedPointer<ChatMessageContentList> me);
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
signals:
void lAddFiles(QStringList paths);
void isFileChanged();
void lUpdate();
void chatMessageChanged();
private:
QSharedPointer<ChatMessageCore> mChatMessageCore;
QSharedPointer<SafeConnection<ChatMessageContentList, CoreModel>> mModelConnection;
DECLARE_ABSTRACT_OBJECT
};
#endif

View file

@ -0,0 +1,103 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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 "ChatMessageContentProxy.hpp"
#include "ChatMessageContentGui.hpp"
#include "core/App.hpp"
#include "core/chat/message/ChatMessageGui.hpp"
DEFINE_ABSTRACT_OBJECT(ChatMessageContentProxy)
ChatMessageContentProxy::ChatMessageContentProxy(QObject *parent) : LimitProxy(parent) {
mList = ChatMessageContentList::create();
setSourceModel(mList.get());
}
ChatMessageContentProxy::~ChatMessageContentProxy() {
}
void ChatMessageContentProxy::setSourceModel(QAbstractItemModel *model) {
auto oldChatMessageContentList = getListModel<ChatMessageContentList>();
if (oldChatMessageContentList) {
// disconnect(oldChatMessageContentList);
}
auto newChatMessageContentList = dynamic_cast<ChatMessageContentList *>(model);
if (newChatMessageContentList) {
// connect(newChatMessageContentList, &ChatMessageContentList::chatChanged, this,
// &ChatMessageContentProxy::chatChanged);
}
setSourceModels(new SortFilterList(model));
sort(0);
}
ChatMessageGui *ChatMessageContentProxy::getChatMessageGui() {
auto model = getListModel<ChatMessageContentList>();
if (!mChatMessageGui && model) mChatMessageGui = model->getChatMessage();
return mChatMessageGui;
}
void ChatMessageContentProxy::setChatMessageGui(ChatMessageGui *chat) {
getListModel<ChatMessageContentList>()->setChatMessageGui(chat);
}
ChatMessageContentGui *ChatMessageContentProxy::getChatMessageContentAtIndex(int i) {
auto model = getListModel<ChatMessageContentList>();
auto sourceIndex = mapToSource(index(i, 0)).row();
if (model) {
auto chat = model->getAt<ChatMessageContentCore>(sourceIndex);
if (chat) return new ChatMessageContentGui(chat);
else return nullptr;
}
return nullptr;
}
void ChatMessageContentProxy::addFiles(const QStringList &paths) {
auto model = getListModel<ChatMessageContentList>();
if (model) emit model->lAddFiles(paths);
}
void ChatMessageContentProxy::removeContent(ChatMessageContentGui *contentGui) {
auto model = getListModel<ChatMessageContentList>();
if (model && contentGui) model->remove(contentGui->mCore);
}
void ChatMessageContentProxy::clear() {
auto model = getListModel<ChatMessageContentList>();
if (model) model->clearData();
}
bool ChatMessageContentProxy::SortFilterList::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const {
auto contentCore = getItemAtSource<ChatMessageContentList, ChatMessageContentCore>(sourceRow);
if (contentCore) {
if (mFilterType == (int)FilterContentType::Unknown) return false;
else if (mFilterType == (int)FilterContentType::File) {
return !contentCore->isVoiceRecording() && (contentCore->isFile() || contentCore->isFileTransfer());
} else if (mFilterType == (int)FilterContentType::Text) return contentCore->isText();
else if (mFilterType == (int)FilterContentType::Voice) return contentCore->isVoiceRecording();
else if (mFilterType == (int)FilterContentType::Conference) return contentCore->isCalendar();
else if (mFilterType == (int)FilterContentType::All) return true;
}
return false;
}
bool ChatMessageContentProxy::SortFilterList::lessThan(const QModelIndex &sourceLeft,
const QModelIndex &sourceRight) const {
return true;
}

View file

@ -0,0 +1,66 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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/>.
*/
#ifndef CHAT_MESSAGE_CONENT_PROXY_H_
#define CHAT_MESSAGE_CONENT_PROXY_H_
#include "ChatMessageContentList.hpp"
#include "core/proxy/LimitProxy.hpp"
#include "tool/AbstractObject.hpp"
// =============================================================================
class ChatMessageGui;
class ChatMessageContentGui;
class ChatMessageContentProxy : public LimitProxy, public AbstractObject {
Q_OBJECT
Q_PROPERTY(ChatMessageGui *chatMessageGui READ getChatMessageGui WRITE setChatMessageGui NOTIFY chatChanged)
public:
enum class FilterContentType { Unknown = 0, File = 1, Text = 2, Voice = 3, Conference = 4, All = 5 };
Q_ENUM(FilterContentType)
DECLARE_SORTFILTER_CLASS(ChatMessageContentProxy *mHideListProxy = nullptr;)
ChatMessageContentProxy(QObject *parent = Q_NULLPTR);
~ChatMessageContentProxy();
ChatMessageGui *getChatMessageGui();
void setChatMessageGui(ChatMessageGui *chat);
Q_INVOKABLE ChatMessageContentGui *getChatMessageContentAtIndex(int i);
void setSourceModel(QAbstractItemModel *sourceModel) override;
Q_INVOKABLE void addFiles(const QStringList &paths);
Q_INVOKABLE void removeContent(ChatMessageContentGui *contentGui);
Q_INVOKABLE void clear();
signals:
void chatChanged();
protected:
QSharedPointer<ChatMessageContentList> mList;
ChatMessageGui *mChatMessageGui = nullptr;
DECLARE_ABSTRACT_OBJECT
};
#endif

View file

@ -0,0 +1,61 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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 "ImdnStatusList.hpp"
#include "core/App.hpp"
#include <QSharedPointer>
#include <linphone++/linphone.hh>
// =============================================================================
DEFINE_ABSTRACT_OBJECT(ImdnStatusList)
QSharedPointer<ImdnStatusList> ImdnStatusList::create() {
auto model = QSharedPointer<ImdnStatusList>(new ImdnStatusList(), &QObject::deleteLater);
model->moveToThread(App::getInstance()->thread());
return model;
}
ImdnStatusList::ImdnStatusList(QObject *parent) : AbstractListProxy<ImdnStatus>(parent) {
mustBeInMainThread(getClassName());
App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership);
}
ImdnStatusList::~ImdnStatusList() {
mustBeInMainThread("~" + getClassName());
mList.clear();
}
QList<ImdnStatus> ImdnStatusList::getImdnStatusList() {
return mList;
}
void ImdnStatusList::setImdnStatusList(QList<ImdnStatus> imdnStatusList) {
resetData(imdnStatusList);
}
QVariant ImdnStatusList::data(const QModelIndex &index, int role) const {
int row = index.row();
if (!index.isValid() || row < 0 || row >= mList.count()) return QVariant();
if (role == Qt::DisplayRole) return QVariant::fromValue(mList.at(row));
return QVariant();
}

View file

@ -0,0 +1,51 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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/>.
*/
#ifndef IMDN_STATUS_LIST_H_
#define IMDN_STATUS_LIST_H_
#include "core/chat/message/ChatMessageCore.hpp"
#include "core/proxy/AbstractListProxy.hpp"
#include "tool/AbstractObject.hpp"
#include "tool/thread/SafeConnection.hpp"
#include <QLocale>
// =============================================================================
class ImdnStatusList : public AbstractListProxy<ImdnStatus>, public AbstractObject {
Q_OBJECT
public:
static QSharedPointer<ImdnStatusList> create();
ImdnStatusList(QObject *parent = Q_NULLPTR);
~ImdnStatusList();
QList<ImdnStatus> getImdnStatusList();
void setImdnStatusList(QList<ImdnStatus> imdnStatusList);
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
signals:
void imdnStatusListChanged();
private:
DECLARE_ABSTRACT_OBJECT
};
#endif

View file

@ -0,0 +1,65 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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 "ImdnStatusProxy.hpp"
#include "ImdnStatusList.hpp"
#include "core/App.hpp"
// #include "core/chat/message/ChatMessageGui.hpp"
DEFINE_ABSTRACT_OBJECT(ImdnStatusProxy)
ImdnStatusProxy::ImdnStatusProxy(QObject *parent) : LimitProxy(parent) {
mList = ImdnStatusList::create();
setSourceModel(mList.get());
connect(mList.get(), &ImdnStatusList::modelReset, this, &ImdnStatusProxy::imdnStatusListChanged);
connect(this, &ImdnStatusProxy::filterChanged, this, [this] { invalidate(); });
}
ImdnStatusProxy::~ImdnStatusProxy() {
}
QList<ImdnStatus> ImdnStatusProxy::getImdnStatusList() {
return mList->getImdnStatusList();
}
void ImdnStatusProxy::setImdnStatusList(QList<ImdnStatus> statusList) {
mList->setImdnStatusList(statusList);
}
LinphoneEnums::ChatMessageState ImdnStatusProxy::getFilter() const {
return mFilter;
}
void ImdnStatusProxy::setFilter(LinphoneEnums::ChatMessageState filter) {
if (mFilter != filter) {
mFilter = filter;
emit filterChanged();
}
}
bool ImdnStatusProxy::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const {
auto imdn = mList->getAt(sourceRow);
return imdn.mState == mFilter;
}
bool ImdnStatusProxy::lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const {
return true;
}

View file

@ -0,0 +1,62 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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/>.
*/
#ifndef IMDN_STATUS_PROXY_H_
#define IMDN_STATUS_PROXY_H_
#include "core/chat/message/ChatMessageCore.hpp"
#include "core/proxy/LimitProxy.hpp"
#include "tool/AbstractObject.hpp"
// =============================================================================
class ImdnStatusList;
class ImdnStatusProxy : public LimitProxy, public AbstractObject {
Q_OBJECT
Q_PROPERTY(
QList<ImdnStatus> imdnStatusList READ getImdnStatusList WRITE setImdnStatusList NOTIFY imdnStatusListChanged)
Q_PROPERTY(LinphoneEnums::ChatMessageState filter READ getFilter WRITE setFilter NOTIFY filterChanged)
public:
ImdnStatusProxy(QObject *parent = Q_NULLPTR);
~ImdnStatusProxy();
QList<ImdnStatus> getImdnStatusList();
void setImdnStatusList(QList<ImdnStatus> imdnStatusList);
LinphoneEnums::ChatMessageState getFilter() const;
void setFilter(LinphoneEnums::ChatMessageState filter);
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
bool lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const override;
signals:
void imdnStatusListChanged();
void filterChanged();
protected:
LinphoneEnums::ChatMessageState mFilter;
QSharedPointer<ImdnStatusList> mList;
DECLARE_ABSTRACT_OBJECT
};
#endif

View file

@ -47,7 +47,12 @@ ConferenceCore::ConferenceCore(const std::shared_ptr<linphone::Conference> &conf
mIsLocalScreenSharing = mConferenceModel->isLocalScreenSharing();
mIsScreenSharingEnabled = mConferenceModel->isScreenSharingEnabled();
mIsRecording = conference->isRecording();
if (conference->getCurrentParams()) mIsChatEnabled = conference->getCurrentParams()->chatEnabled();
auto me = conference->getMe();
auto confAddress = conference->getConferenceAddress();
if (confAddress) {
mConfUri = Utils::coreStringToAppString(confAddress->asStringUriOnly());
}
if (me) {
mMe = ParticipantCore::create(me);
}
@ -75,7 +80,14 @@ void ConferenceCore::setSelf(QSharedPointer<ConferenceCore> me) {
if (newState == linphone::Conference::State::Created) {
if (auto participantDevice = conference->getActiveSpeakerParticipantDevice()) {
auto device = ParticipantDeviceCore::create(participantDevice);
mConferenceModelConnection->invokeToCore([this, device]() { setActiveSpeakerDevice(device); });
QString address;
auto confAddress = conference->getConferenceAddress();
if (confAddress) address = Utils::coreStringToAppString(confAddress->asStringUriOnly());
mConferenceModelConnection->invokeToCore([this, device, address]() {
setActiveSpeakerDevice(device);
mConfUri = address;
emit conferenceUriChanged();
});
} else if (conference->getParticipantDeviceList().size() > 1) {
for (auto &device : conference->getParticipantDeviceList()) {
if (!ToolModel::isMe(device->getAddress())) {
@ -194,6 +206,10 @@ void ConferenceCore::setIsScreenSharingEnabled(bool state) {
}
}
bool ConferenceCore::isChatEnabled() const {
return mIsChatEnabled;
}
std::shared_ptr<ConferenceModel> ConferenceCore::getModel() const {
return mConferenceModel;
}

View file

@ -37,12 +37,14 @@ class ConferenceCore : public QObject, public AbstractObject {
Q_OBJECT
public:
Q_PROPERTY(QDateTime startDate READ getStartDate CONSTANT)
Q_PROPERTY(bool isChatEnabled READ isChatEnabled CONSTANT)
// Q_PROPERTY(ParticipantDeviceList *participantDevices READ getParticipantDeviceList CONSTANT)
// Q_PROPERTY(ParticipantModel* localParticipant READ getLocalParticipant NOTIFY localParticipantChanged)
Q_PROPERTY(bool isReady MEMBER mIsReady WRITE setIsReady NOTIFY isReadyChanged)
Q_PROPERTY(bool isRecording READ isRecording WRITE setRecording NOTIFY isRecordingChanged)
Q_PROPERTY(QString subject READ getSubject WRITE setSubject NOTIFY subjectChanged)
Q_PROPERTY(QString uri MEMBER mConfUri NOTIFY conferenceUriChanged)
Q_PROPERTY(bool isLocalScreenSharing MEMBER mIsLocalScreenSharing WRITE setIsLocalScreenSharing NOTIFY
isLocalScreenSharingChanged)
Q_PROPERTY(bool isScreenSharingEnabled MEMBER mIsScreenSharingEnabled WRITE setIsScreenSharingEnabled NOTIFY
@ -80,6 +82,8 @@ public:
void setIsLocalScreenSharing(bool state);
void setIsScreenSharingEnabled(bool state);
bool isChatEnabled() const;
std::shared_ptr<ConferenceModel> getModel() const;
//---------------------------------------------------------------------------
@ -92,6 +96,7 @@ signals:
void activeSpeakerDeviceChanged();
void subjectChanged();
void isRecordingChanged();
void conferenceUriChanged();
void lToggleScreenSharing();
@ -106,7 +111,9 @@ private:
bool mIsRecording = false;
bool mIsLocalScreenSharing = false;
bool mIsScreenSharingEnabled = false;
bool mIsChatEnabled = false;
QString mSubject;
QString mConfUri;
QDateTime mStartDate = QDateTime::currentDateTime();
DECLARE_ABSTRACT_OBJECT

View file

@ -1,4 +1,4 @@
/*
/*
* Copyright (c) 2021 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
@ -21,12 +21,15 @@
#include "ConferenceInfoCore.hpp"
#include "core/App.hpp"
#include "core/path/Paths.hpp"
#include "core/proxy/ListProxy.hpp"
#include "model/object/VariantObject.hpp"
#include "model/tool/ToolModel.hpp"
#include "tool/Utils.hpp"
#include "tool/thread/SafeConnection.hpp"
#include <QDesktopServices>
DEFINE_ABSTRACT_OBJECT(ConferenceInfoCore)
QSharedPointer<ConferenceInfoCore>
@ -69,16 +72,13 @@ ConferenceInfoCore::ConferenceInfoCore(std::shared_ptr<linphone::ConferenceInfo>
mUri = address && address->isValid() && !address->getDomain().empty()
? Utils::coreStringToAppString(address->asStringUriOnly())
: "";
mIcalendarString = Utils::coreStringToAppString(conferenceInfo->getIcalendarString());
mDateTime = QDateTime::fromMSecsSinceEpoch(conferenceInfo->getDateTime() * 1000);
mDuration = conferenceInfo->getDuration();
mEndDateTime = mDateTime.addSecs(mDuration * 60);
mIsScheduled = mDateTime.isValid();
mOrganizerAddress = Utils::coreStringToAppString(conferenceInfo->getOrganizer()->asStringUriOnly());
mOrganizerName = Utils::coreStringToAppString(conferenceInfo->getOrganizer()->getDisplayName());
if (mOrganizerName.isEmpty()) {
mOrganizerName = Utils::coreStringToAppString(conferenceInfo->getOrganizer()->getUsername());
mOrganizerName.replace(".", " ");
}
mOrganizerName = mConferenceInfoModel->getOrganizerName();
mSubject = Utils::coreStringToAppString(conferenceInfo->getSubject());
mDescription = Utils::coreStringToAppString(conferenceInfo->getDescription());
mIsEnded = getDateTimeUtc().addSecs(mDuration * 60) < QDateTime::currentDateTimeUtc();
@ -127,6 +127,7 @@ ConferenceInfoCore::ConferenceInfoCore(const ConferenceInfoCore &conferenceInfoC
mIsEnded = conferenceInfoCore.mIsEnded;
mInviteEnabled = conferenceInfoCore.mInviteEnabled;
mConferenceInfoState = conferenceInfoCore.mConferenceInfoState;
mIcalendarString = conferenceInfoCore.mIcalendarString;
}
ConferenceInfoCore::~ConferenceInfoCore() {
@ -179,6 +180,13 @@ void ConferenceInfoCore::setSelf(QSharedPointer<ConferenceInfoCore> me) {
}
});
});
mConfInfoModelConnection->makeConnectToCore(&ConferenceInfoCore::lCancelCreation, [this]() {
mConfInfoModelConnection->invokeToModel([this] {
if (mConferenceInfoModel) {
mConferenceInfoModel->setConferenceScheduler(nullptr);
}
});
});
mConfInfoModelConnection->makeConnectToCore(&ConferenceInfoCore::lDeleteConferenceInfo, [this]() {
mConfInfoModelConnection->invokeToModel([this] { mConferenceInfoModel->deleteConferenceInfo(); });
});
@ -192,21 +200,15 @@ void ConferenceInfoCore::setSelf(QSharedPointer<ConferenceInfoCore> me) {
QString uri;
if (state == linphone::ConferenceScheduler::State::Ready) {
uri = mConferenceInfoModel->getConferenceScheduler()->getUri();
emit CoreModel::getInstance() -> conferenceInfoReceived(
CoreModel::getInstance()->getCore(),
mConferenceInfoModel->getConferenceInfo());
}
mConfInfoModelConnection->invokeToCore([this, state = LinphoneEnums::fromLinphone(state),
infoState = LinphoneEnums::fromLinphone(confInfoState),
uri] {
setConferenceSchedulerState(state);
setConferenceInfoState(infoState);
if (state == LinphoneEnums::ConferenceSchedulerState::Ready) {
setUri(uri);
mConfInfoModelConnection->invokeToModel([this, uri, infoState] {
if (infoState == LinphoneEnums::ConferenceInfoState::New)
emit CoreModel::getInstance()->conferenceInfoCreated(
mConferenceInfoModel->getConferenceInfo());
});
}
setConferenceSchedulerState(state);
});
});
mConfInfoModelConnection->makeConnectToModel(
@ -328,7 +330,8 @@ void ConferenceInfoCore::setTimeZoneModel(TimeZoneModel *model) {
mTimeZoneModel->getStandardTimeOffset() != model->getStandardTimeOffset() ||
mTimeZoneModel->getTimeZone() != model->getTimeZone()) {
mTimeZoneModel = QSharedPointer<TimeZoneModel>(model);
mTimeZoneModel = QSharedPointer<TimeZoneModel>(new TimeZoneModel(model->getTimeZone()));
emit timeZoneModelChanged();
}
}
@ -555,6 +558,10 @@ void ConferenceInfoCore::writeIntoModel(std::shared_ptr<ConferenceInfoModel> mod
model->setParticipantInfos(participantInfos);
}
std::shared_ptr<ConferenceInfoModel> ConferenceInfoCore::getModel() const {
return mConferenceInfoModel;
}
void ConferenceInfoCore::save() {
mustBeInMainThread(getClassName() + "::save()");
ConferenceInfoCore *thisCopy = new ConferenceInfoCore(*this); // Pointer to avoid multiple copies in lambdas
@ -575,8 +582,8 @@ void ConferenceInfoCore::save() {
linphone::RegistrationState::Ok) {
//: "Erreur"
Utils::showInformationPopup(tr("information_popup_error_title"),
//: "Votre compte est déconnecté"
tr("information_popup_disconnected_account_message"), false);
//: "Votre compte est déconnecté"
tr("information_popup_disconnected_account_message"), false);
emit saveFailed();
return;
}
@ -599,6 +606,9 @@ void ConferenceInfoCore::save() {
} else lCritical() << "No default account";
// Add text capability for chat in conf
linphoneConf->setCapability(linphone::StreamType::Text, true);
if (SettingsModel::getInstance()->getCreateEndToEndEncryptedMeetingsAndGroupCalls())
linphoneConf->setSecurityLevel(linphone::Conference::SecurityLevel::EndToEnd);
else linphoneConf->setSecurityLevel(linphone::Conference::SecurityLevel::PointToPoint);
auto confInfoModel = Utils::makeQObject_ptr<ConferenceInfoModel>(linphoneConf);
auto confSchedulerModel = confInfoModel->getConferenceScheduler();
if (!confSchedulerModel) {
@ -637,12 +647,6 @@ void ConferenceInfoCore::undo() {
}
}
void ConferenceInfoCore::cancelCreation() {
if (mConferenceInfoModel) {
mConferenceInfoModel->setConferenceScheduler(nullptr);
}
}
//-------------------------------------------------------------------------------------------------
void ConferenceInfoCore::onInvitationsSent(const std::list<std::shared_ptr<linphone::Address>> &failedInvitations) {
@ -654,3 +658,14 @@ bool ConferenceInfoCore::isAllDayConf() const {
return mDateTime.time().hour() == 0 && mDateTime.time().minute() == 0 && mEndDateTime.time().hour() == 23 &&
mEndDateTime.time().minute() == 59;
}
void ConferenceInfoCore::exportConferenceToICS() const {
QString filePath(Paths::getAppLocalDirPath() + "conference.ics");
QFile file(filePath);
if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
QTextStream out(&file);
out << mIcalendarString;
file.close();
}
QDesktopServices::openUrl(QUrl::fromLocalFile(filePath));
}

View file

@ -1,4 +1,4 @@
/*
/*
* Copyright (c) 2022 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
@ -126,14 +126,17 @@ public:
void writeFromModel(const std::shared_ptr<ConferenceInfoModel> &model);
void writeIntoModel(std::shared_ptr<ConferenceInfoModel> model);
std::shared_ptr<ConferenceInfoModel> getModel() const;
Q_INVOKABLE void save();
Q_INVOKABLE void undo();
Q_INVOKABLE void cancelCreation();
virtual void onInvitationsSent(const std::list<std::shared_ptr<linphone::Address>> &failedInvitations);
Q_INVOKABLE bool isAllDayConf() const;
Q_INVOKABLE void exportConferenceToICS() const;
signals:
void dateTimeChanged();
void endDateTimeChanged();
@ -158,6 +161,7 @@ signals:
void invitationsSent();
void removed(ConferenceInfoCore *confInfo);
void lCancelCreation();
void lCancelConferenceInfo();
void lDeleteConferenceInfo(); // Remove completly this conference info from DB
@ -175,6 +179,7 @@ private:
QString mSubject;
QString mDescription;
QString mUri;
QString mIcalendarString;
QVariantList mParticipants;
QSharedPointer<TimeZoneModel> mTimeZoneModel;
LinphoneEnums::ConferenceSchedulerState mConferenceSchedulerState;

View file

@ -58,58 +58,119 @@ ConferenceInfoList::~ConferenceInfoList() {
void ConferenceInfoList::setSelf(QSharedPointer<ConferenceInfoList> me) {
mCoreModelConnection = SafeConnection<ConferenceInfoList, CoreModel>::create(me, CoreModel::getInstance());
mCoreModelConnection->makeConnectToCore(&ConferenceInfoList::lUpdate, [this](bool isInitialization) {
mCoreModelConnection->invokeToModel([this, isInitialization]() {
QList<QSharedPointer<ConferenceInfoCore>> *items = new QList<QSharedPointer<ConferenceInfoCore>>();
mCoreModelConnection->makeConnectToCore(&ConferenceInfoList::lUpdate, [this]() {
if (mIsUpdating) {
connect(this, &ConferenceInfoList::isUpdatingChanged, this, [this] {
if (!mIsUpdating) {
disconnect(this, &ConferenceInfoList::isUpdatingChanged, this, nullptr);
lUpdate();
}
});
return;
}
setIsUpdating(true);
mCoreModelConnection->invokeToModel([this]() {
mustBeInLinphoneThread(getClassName());
beginResetModel();
mList.clear();
QList<QSharedPointer<ConferenceInfoCore>> *items = new QList<QSharedPointer<ConferenceInfoCore>>();
auto defaultAccount = CoreModel::getInstance()->getCore()->getDefaultAccount();
if (!defaultAccount) return;
setAccountConnected(defaultAccount && defaultAccount->getState() == linphone::RegistrationState::Ok);
if (!defaultAccount || !mAccountConnected) {
endResetModel();
setIsUpdating(false);
return;
}
std::list<std::shared_ptr<linphone::ConferenceInfo>> conferenceInfos =
defaultAccount->getConferenceInformationList();
if (conferenceInfos.empty()) {
endResetModel();
setIsUpdating(false);
return;
}
items->push_back(nullptr); // Add Dummy conference for today
for (auto conferenceInfo : conferenceInfos) {
if (conferenceInfo->getState() == linphone::ConferenceInfo::State::Cancelled) {
auto myAddress = defaultAccount->getParams()->getIdentityAddress();
if (!myAddress || myAddress->weakEqual(conferenceInfo->getOrganizer())) continue;
}
// if (conferenceInfo->getState() == linphone::ConferenceInfo::State::Cancelled) {
// auto myAddress = defaultAccount->getParams()->getIdentityAddress();
// if (!myAddress || myAddress->weakEqual(conferenceInfo->getOrganizer())) continue;
// }
auto confInfoCore = build(conferenceInfo);
// Cancelled conference organized ourself me must be hidden
if (confInfoCore) {
// qDebug() << log().arg("Add conf") << confInfoCore->getSubject() << "with state"
// << confInfoCore->getConferenceInfoState();
items->push_back(confInfoCore);
}
}
mCoreModelConnection->invokeToCore([this, items, isInitialization]() {
mCoreModelConnection->invokeToCore([this, items]() {
mustBeInMainThread(getClassName());
for (auto &item : *items) {
connectItem(item);
mList << item.template objectCast<QObject>();
}
resetData(*items);
updateHaveCurrentDate();
endResetModel();
setIsUpdating(false);
delete items;
if (isInitialization) {
emit initialized();
}
});
});
});
// This is needed because account does not have a contact address until
// it is connected, so we can't verify if it is the organizer of a deleted
// conference (which must hidden)
mCoreModelConnection->makeConnectToModel(&CoreModel::defaultAccountChanged, [this]() { emit lUpdate(true); });
mCoreModelConnection->makeConnectToModel(
&CoreModel::conferenceInfoCreated,
[this](const std::shared_ptr<linphone::ConferenceInfo> &confInfo) { addConference(confInfo); });
mCoreModelConnection->makeConnectToModel(
&CoreModel::conferenceInfoReceived,
[this](const std::shared_ptr<linphone::Core> &core,
const std::shared_ptr<const linphone::ConferenceInfo> &conferenceInfo) {
lDebug() << log().arg("conference info received") << conferenceInfo->getSubject();
addConference(conferenceInfo->clone());
lInfo() << log().arg("conference info received") << conferenceInfo->getSubject();
// We must refresh all the conference infos cause we are not able to determine
// which account is concerned by the signal if multiple accounts are connected
emit lUpdate();
});
emit lUpdate(true);
// This is needed because account does not have a contact address until
// it is connected, so we can't verify if it is the organizer of a deleted
// conference (which must hidden)
mCoreModelConnection->makeConnectToModel(
&CoreModel::defaultAccountChanged,
[this](const std::shared_ptr<linphone::Core> &core, const std::shared_ptr<linphone::Account> &account) {
auto accountCore = account ? AccountCore::create(account) : nullptr;
mCoreModelConnection->invokeToCore([this, accountCore] {
if (mCurrentAccountCore) {
disconnect(mCurrentAccountCore.get(), &AccountCore::registrationStateChanged, this, nullptr);
disconnect(mCurrentAccountCore.get(), &AccountCore::conferenceInformationUpdated, this, nullptr);
}
mCurrentAccountCore = accountCore;
if (mCurrentAccountCore) {
connect(mCurrentAccountCore.get(), &AccountCore::registrationStateChanged, this,
[this] { emit lUpdate(); });
connect(mCurrentAccountCore.get(), &AccountCore::conferenceInformationUpdated, this,
[this] { emit lUpdate(); });
}
emit lUpdate();
});
});
mCoreModelConnection->invokeToModel([this] {
auto defaultAccount = CoreModel::getInstance()->getCore()->getDefaultAccount();
auto accountCore = defaultAccount ? AccountCore::create(defaultAccount) : nullptr;
mCoreModelConnection->invokeToCore([this, accountCore] {
mCurrentAccountCore = accountCore;
if (mCurrentAccountCore) {
connect(mCurrentAccountCore.get(), &AccountCore::registrationStateChanged, this,
[this] { emit lUpdate(); });
connect(mCurrentAccountCore.get(), &AccountCore::conferenceInformationUpdated, this,
[this] { emit lUpdate(); });
}
});
});
emit lUpdate();
}
void ConferenceInfoList::setAccountConnected(bool connected) {
if (mAccountConnected != connected) {
mAccountConnected = connected;
emit accountConnectedChanged(mAccountConnected);
}
}
bool ConferenceInfoList::getAccountConnected() const {
return mAccountConnected;
}
void ConferenceInfoList::resetData(QList<QSharedPointer<ConferenceInfoCore>> data) {
@ -129,6 +190,7 @@ void ConferenceInfoList::addConference(const std::shared_ptr<linphone::Conferenc
return confInfo->getUri()->weakEqual(confAddr);
});
if (haveConf == list.end()) {
if (confInfo->getState() == linphone::ConferenceInfo::State::Cancelled) return;
auto confInfoCore = build(confInfo);
mCoreModelConnection->invokeToCore([this, confInfoCore] {
connectItem(confInfoCore);
@ -173,13 +235,17 @@ int ConferenceInfoList::getCurrentDateIndex() {
return it == confInfoList.end() ? -1 : std::distance(confInfoList.begin(), it);
}
QSharedPointer<ConferenceInfoCore> ConferenceInfoList::getCurrentDateConfInfo() {
QSharedPointer<ConferenceInfoCore> ConferenceInfoList::getCurrentDateConfInfo(bool enableCancelledConference) {
auto today = QDate::currentDate();
auto confInfoList = getSharedList<ConferenceInfoCore>();
QList<QSharedPointer<ConferenceInfoCore>>::iterator it;
if (mHaveCurrentDate) {
it = std::find_if(confInfoList.begin(), confInfoList.end(),
[today](const QSharedPointer<ConferenceInfoCore> &item) {
[today, enableCancelledConference](const QSharedPointer<ConferenceInfoCore> &item) {
if (!item) return false;
if (!enableCancelledConference &&
item->getConferenceInfoState() == LinphoneEnums::ConferenceInfoState::Cancelled)
return false;
return item && item->getDateTimeUtc().date() == today;
});
} else it = std::find(confInfoList.begin(), confInfoList.end(), nullptr);
@ -236,7 +302,9 @@ QVariant ConferenceInfoList::data(const QModelIndex &index, int role) const {
if (role == Qt::DisplayRole) {
return QVariant::fromValue(new ConferenceInfoGui(mList[row].objectCast<ConferenceInfoCore>()));
} else if (role == Qt::DisplayRole + 1) {
return Utils::toDateMonthString(mList[row].objectCast<ConferenceInfoCore>()->getDateTimeUtc());
auto date = mList[row].objectCast<ConferenceInfoCore>()->getDateTimeUtc();
if (date.date().year() != QDate::currentDate().year()) return Utils::toDateMonthAndYearString(date);
else return Utils::toDateMonthString(date);
}
} else { // Dummy date
if (role == Qt::DisplayRole) {

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
@ -48,28 +48,32 @@ public:
void updateHaveCurrentDate();
int getCurrentDateIndex();
QSharedPointer<ConferenceInfoCore> getCurrentDateConfInfo();
QSharedPointer<ConferenceInfoCore> getCurrentDateConfInfo(bool enableCancelledConference = false);
QSharedPointer<ConferenceInfoCore> build(const std::shared_ptr<linphone::ConferenceInfo> &conferenceInfo);
void connectItem(QSharedPointer<ConferenceInfoCore> confInfoCore);
QHash<int, QByteArray> roleNames() const override;
void setAccountConnected(bool connected);
bool getAccountConnected() const;
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
signals:
void lUpdate(bool isInitialization = false);
void initialized();
void lUpdate();
void addCurrentDateChanged();
void haveCurrentDateChanged();
void currentDateIndexChanged(int index);
void confInfoInserted(QSharedPointer<ConferenceInfoCore> data);
void confInfoUpdated(QSharedPointer<ConferenceInfoCore> data);
void accountConnectedChanged(bool connected);
private:
QSharedPointer<SafeConnection<ConferenceInfoList, CoreModel>> mCoreModelConnection;
QSharedPointer<AccountCore> mCurrentAccountCore;
bool mHaveCurrentDate = false;
bool mAccountConnected = false;
DECLARE_ABSTRACT_OBJECT
};
#endif // CONFERENCE_INFO_LIST_H_

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
@ -57,20 +57,26 @@ ConferenceInfoProxy::ConferenceInfoProxy(QObject *parent) : LimitProxy(parent) {
if (isSignalConnected(conferenceInfoUpdatedSignal)) emit conferenceInfoUpdated(new ConferenceInfoGui(data));
},
Qt::QueuedConnection);
connect(mList.get(), &ConferenceInfoList::initialized, this, &ConferenceInfoProxy::initialized);
connect(mList.get(), &ConferenceInfoList::accountConnectedChanged, this,
&ConferenceInfoProxy::accountConnectedChanged);
}
ConferenceInfoProxy::~ConferenceInfoProxy() {
}
bool ConferenceInfoProxy::haveCurrentDate() const {
return mList->haveCurrentDate();
return mList && mList->haveCurrentDate();
}
bool ConferenceInfoProxy::getAccountConnected() const {
return mList && mList->getAccountConnected();
}
bool ConferenceInfoProxy::SortFilterList::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const {
auto list = qobject_cast<ConferenceInfoList *>(sourceModel());
auto ciCore = list->getAt<ConferenceInfoCore>(sourceRow);
if (ciCore) {
if (ciCore->getDuration() == 0) return false;
bool searchTextInSubject = false;
bool searchTextInParticipant = false;
if (ciCore->getSubject().contains(mFilterText, Qt::CaseInsensitive)) searchTextInSubject = true;
@ -99,6 +105,14 @@ void ConferenceInfoProxy::clear() {
mList->clearData();
}
ConferenceInfoGui *ConferenceInfoProxy::getCurrentDateConfInfo(bool enableCancelledConference) {
if (mList) {
auto confInfo = mList->getCurrentDateConfInfo();
return confInfo ? new ConferenceInfoGui(confInfo) : nullptr;
}
return nullptr;
}
int ConferenceInfoProxy::loadUntil(ConferenceInfoGui *confInfo) {
return loadUntil(confInfo ? confInfo->mCore : nullptr);
}
@ -136,7 +150,7 @@ bool ConferenceInfoProxy::SortFilterList::lessThan(const QModelIndex &sourceLeft
auto nowDate = QDate::currentDate();
if (!l || !r) { // sort on date
auto rdate = r ? r->getDateTimeUtc().date() : QDate::currentDate();
return !l ? nowDate <= r->getDateTimeUtc().date() : l->getDateTimeUtc().date() < nowDate;
return !l ? nowDate < r->getDateTimeUtc().date() : l->getDateTimeUtc().date() < nowDate;
} else {
return l->getDateTimeUtc() < r->getDateTimeUtc();
}

View file

@ -32,6 +32,7 @@ class ConferenceInfoProxy : public LimitProxy, public AbstractObject {
Q_OBJECT
Q_PROPERTY(bool haveCurrentDate READ haveCurrentDate NOTIFY haveCurrentDateChanged)
Q_PROPERTY(bool accountConnected READ getAccountConnected NOTIFY accountConnectedChanged)
public:
enum ConferenceInfoFiltering { None = 0, Future = 1 };
@ -43,15 +44,17 @@ public:
~ConferenceInfoProxy();
bool haveCurrentDate() const;
bool getAccountConnected() const;
Q_INVOKABLE void clear();
Q_INVOKABLE ConferenceInfoGui *getCurrentDateConfInfo(bool enableCancelledConference = false);
Q_INVOKABLE int loadUntil(ConferenceInfoGui *confInfo);
int loadUntil(QSharedPointer<ConferenceInfoCore> data);
signals:
void initialized();
void haveCurrentDateChanged();
void conferenceInfoCreated(ConferenceInfoGui *confInfo);
void conferenceInfoUpdated(ConferenceInfoGui *confInfo);
void accountConnectedChanged(bool connected);
private:
QSharedPointer<ConferenceInfoList> mList;

View file

@ -0,0 +1,63 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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 "EmojiList.hpp"
#include "core/App.hpp"
#include "core/chat/ChatCore.hpp"
#include "core/chat/message/content/ChatMessageContentGui.hpp"
#include <QMimeDatabase>
#include <QSharedPointer>
#include <linphone++/linphone.hh>
// =============================================================================
DEFINE_ABSTRACT_OBJECT(EmojiList)
QSharedPointer<EmojiList> EmojiList::create() {
auto model = QSharedPointer<EmojiList>(new EmojiList(), &QObject::deleteLater);
model->moveToThread(App::getInstance()->thread());
return model;
}
EmojiList::EmojiList(QObject *parent) : AbstractListProxy<Reaction>(parent) {
mustBeInMainThread(getClassName());
App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership);
}
EmojiList::~EmojiList() {
mustBeInMainThread("~" + getClassName());
}
QList<Reaction> EmojiList::getReactions() {
return mList;
}
void EmojiList::setReactions(QList<Reaction> reactions) {
resetData(reactions);
}
QVariant EmojiList::data(const QModelIndex &index, int role) const {
int row = index.row();
if (!index.isValid() || row < 0 || row >= mList.count()) return QVariant();
if (role == Qt::DisplayRole) return QVariant::fromValue(mList.at(row));
return QVariant();
}

View file

@ -0,0 +1,49 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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/>.
*/
#ifndef EMOJI_LIST_H_
#define EMOJI_LIST_H_
#include "core/chat/message/ChatMessageCore.hpp"
#include "core/proxy/AbstractListProxy.hpp"
#include "tool/AbstractObject.hpp"
#include "tool/thread/SafeConnection.hpp"
#include <QLocale>
// =============================================================================
class EmojiList : public AbstractListProxy<Reaction>, public AbstractObject {
Q_OBJECT
public:
static QSharedPointer<EmojiList> create();
EmojiList(QObject *parent = Q_NULLPTR);
~EmojiList();
QList<Reaction> getReactions();
void setReactions(QList<Reaction> reactions);
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
private:
QList<Reaction> mReactions;
DECLARE_ABSTRACT_OBJECT
};
#endif

View file

@ -0,0 +1,91 @@
/*
* MIT License
Copyright (c) 2023 AmirHosseinCH
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "EmojiModel.hpp"
#include "core/path/Paths.hpp"
#include "tool/Constants.hpp"
EmojiModel::EmojiModel() {
QFile file(QString(":/data/emoji/emoji.json"));
auto open = file.open(QIODevice::ReadOnly);
QByteArray data = file.readAll();
file.close();
QJsonDocument doc = QJsonDocument::fromJson(data);
QJsonObject rootObj = doc.object();
for (auto category{rootObj.begin()}; category != rootObj.end(); ++category) {
emojies[category.key()] = category.value().toArray();
QJsonArray &emojiesData = emojies[category.key()];
for (auto it{emojiesData.begin()}; it != emojiesData.end(); ++it) {
QJsonObject emoji = it->toObject();
QJsonArray allKeywords = emoji.value("keywords").toArray();
for (auto k{allKeywords.begin()}; k != allKeywords.end(); ++k) {
keywords[k->toString()].append(emoji);
}
}
}
}
int EmojiModel::count(QString category) {
return emojies[category].size();
}
QString EmojiModel::path(QString category, int index, int skinColor) {
QJsonObject emoji = emojies[category][index].toObject();
if (emoji.contains("types") && skinColor != -1) {
QJsonArray types = emoji.value("types").toArray();
return mIconsPath + types[skinColor].toString() + mIconsType;
} else return mIconsPath + emoji.value("code").toString() + mIconsType;
}
QVector<QString> EmojiModel::search(QString searchKey, int skinColor) {
bool foundFirstItem{false};
QVector<QString> searchResult;
for (auto it{keywords.begin()}; it != keywords.end(); ++it) {
if (it.key().startsWith(searchKey)) {
QVector<QJsonObject> &emojiesData{it.value()};
for (auto emoji{emojiesData.begin()}; emoji != emojiesData.end(); ++emoji) {
if (emoji->contains("types") && skinColor != -1) {
QJsonArray types = emoji->value("types").toArray();
QString path = mIconsPath + types[skinColor].toString() + mIconsType;
if (!searchResult.contains(path)) searchResult.append(path);
} else {
QString path = mIconsPath + emoji->value("code").toString() + mIconsType;
if (!searchResult.contains(path)) searchResult.append(path);
}
}
foundFirstItem = true;
} else if (foundFirstItem) {
break;
}
}
return searchResult;
}
void EmojiModel::setIconsPath(QString path) {
mIconsPath = path;
}
void EmojiModel::setIconsType(QString type) {
mIconsType = type;
}

View file

@ -0,0 +1,54 @@
/*
* MIT License
Copyright (c) 2023 AmirHosseinCH
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#ifndef EMOJIMODEL_H
#define EMOJIMODEL_H
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QObject>
class EmojiModel : public QObject {
Q_OBJECT
Q_PROPERTY(QString iconsPath WRITE setIconsPath MEMBER mIconsPath)
Q_PROPERTY(QString iconsType WRITE setIconsType MEMBER mIconsType)
public:
EmojiModel();
void setIconsPath(QString);
void setIconsType(QString);
public slots:
int count(QString);
QString path(QString, int, int = -1);
QVector<QString> search(QString, int = -1);
private:
QString mIconsPath;
QString mIconsType;
QMap<QString, QJsonArray> emojies;
QMap<QString, QVector<QJsonObject>> keywords;
};
#endif

View file

@ -0,0 +1,65 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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 "EmojiProxy.hpp"
#include "EmojiList.hpp"
#include "core/App.hpp"
// #include "core/chat/message/ChatMessageGui.hpp"
DEFINE_ABSTRACT_OBJECT(EmojiProxy)
EmojiProxy::EmojiProxy(QObject *parent) : LimitProxy(parent) {
mList = EmojiList::create();
setSourceModel(mList.get());
connect(mList.get(), &EmojiList::modelReset, this, &EmojiProxy::reactionsChanged);
connect(this, &EmojiProxy::filterChanged, this, [this] { invalidate(); });
}
EmojiProxy::~EmojiProxy() {
}
QList<Reaction> EmojiProxy::getReactions() {
return mList->getReactions();
}
void EmojiProxy::setReactions(QList<Reaction> reactions) {
mList->setReactions(reactions);
}
QString EmojiProxy::getFilter() const {
return mFilter;
}
void EmojiProxy::setFilter(QString filter) {
if (mFilter != filter) {
mFilter = filter;
emit filterChanged();
}
}
bool EmojiProxy::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const {
auto emoji = mList->getAt(sourceRow);
return emoji.mBody.contains(mFilter);
}
bool EmojiProxy::lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const {
return true;
}

View file

@ -0,0 +1,62 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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/>.
*/
#ifndef EMOJI_PROXY_H_
#define EMOJI_PROXY_H_
#include "core/chat/message/ChatMessageCore.hpp"
#include "core/emoji/EmojiModel.hpp"
#include "core/proxy/LimitProxy.hpp"
#include "tool/AbstractObject.hpp"
// =============================================================================
class EmojiList;
class EmojiProxy : public LimitProxy, public AbstractObject {
Q_OBJECT
Q_PROPERTY(QList<Reaction> reactions READ getReactions WRITE setReactions NOTIFY reactionsChanged)
Q_PROPERTY(QString filter READ getFilter WRITE setFilter NOTIFY filterChanged)
public:
EmojiProxy(QObject *parent = Q_NULLPTR);
~EmojiProxy();
QList<Reaction> getReactions();
void setReactions(QList<Reaction> reactions);
QString getFilter() const;
void setFilter(QString filter);
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
bool lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const override;
signals:
void reactionsChanged();
void filterChanged();
protected:
QString mFilter;
QSharedPointer<EmojiList> mList;
DECLARE_ABSTRACT_OBJECT
};
#endif

View file

@ -0,0 +1,62 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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 <QtDebug>
#include "core/App.hpp"
#include "model/core/CoreModel.hpp"
#include "model/setting/SettingsModel.hpp"
#include "AbstractEventCountNotifier.hpp"
// =============================================================================
using namespace std;
DEFINE_ABSTRACT_OBJECT(AbstractEventCountNotifier)
AbstractEventCountNotifier::AbstractEventCountNotifier(QObject *parent) : QObject(parent) {
}
int AbstractEventCountNotifier::getEventCount() const {
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
auto coreModel = CoreModel::getInstance();
int count = coreModel->getCore()->getMissedCallsCount();
if (SettingsModel::getInstance()->getStandardChatEnabled() || SettingsModel::getInstance()->getSecureChatEnabled())
count += coreModel->getCore()->getUnreadChatMessageCountFromActiveLocals();
return count;
}
int AbstractEventCountNotifier::getCurrentEventCount() const {
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
auto coreModel = CoreModel::getInstance();
int count = coreModel->getCore()->getMissedCallsCount();
bool filtered = SettingsModel::getInstance()->isSystrayNotificationFiltered();
bool global = SettingsModel::getInstance()->isSystrayNotificationGlobal();
// if (global && !filtered)
if (true) return getEventCount();
else {
auto currentAccount = CoreModel::getInstance()->getCore()->getDefaultAccount();
if (currentAccount) {
count += currentAccount->getUnreadChatMessageCount();
}
return count;
}
}

View file

@ -0,0 +1,58 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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/>.
*/
#ifndef ABSTRACT_EVENT_COUNT_NOTIFIER_H_
#define ABSTRACT_EVENT_COUNT_NOTIFIER_H_
#include <QHash>
#include <QObject>
#include <QPair>
#include <QSystemTrayIcon>
#include "tool/AbstractObject.hpp"
#include "tool/thread/SafeConnection.hpp"
// =============================================================================
namespace linphone {
class ChatMessage;
}
class CallModel;
class ChatRoomModel;
class HistoryModel;
class AbstractEventCountNotifier : public QObject, public AbstractObject {
Q_OBJECT
public:
AbstractEventCountNotifier(QObject *parent = Q_NULLPTR);
int getEventCount() const; // global
int getCurrentEventCount() const; // Current account
protected:
virtual void notifyEventCount(int n) = 0;
private:
DECLARE_ABSTRACT_OBJECT
};
#endif // ABSTRACT_EVENT_COUNT_NOTIFIER_H_

View file

@ -0,0 +1,40 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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/>.
*/
#ifndef EVENT_COUNT_NOTIFIER_MAC_OS_H_
#define EVENT_COUNT_NOTIFIER_MAC_OS_H_
#include "AbstractEventCountNotifier.hpp"
// =============================================================================
extern "C" void notifyEventCountMacOs(int n);
class EventCountNotifier : public AbstractEventCountNotifier {
public:
EventCountNotifier(QObject *parent = Q_NULLPTR) : AbstractEventCountNotifier(parent) {
}
void notifyEventCount(int n) override {
notifyEventCountMacOs(n);
}
};
#endif // EVENT_COUNT_NOTIFIER_MAC_OS_H_

View file

@ -0,0 +1,30 @@
/*
* EventCountNotifierMacOs.m
* Copyright (C) 2017-2018 Belledonne Communications, Grenoble, France
*
* 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 2
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Created on: June 30, 2017
* Author: Ghislain MARY
*/
#import <Cocoa/Cocoa.h>
// =============================================================================
void notifyEventCountMacOs (int n) {
NSString *badgeStr = (n > 0) ? [NSString stringWithFormat:@"%d", n] : @"";
[[NSApp dockTile] setBadgeLabel:badgeStr];
}

View file

@ -0,0 +1,134 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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 <QIcon>
#include <QPainter>
#include <QSvgRenderer>
#include <QSystemTrayIcon>
#include <QTimer>
#include <QWindow>
#include "core/App.hpp"
#include "model/core/CoreModel.hpp"
#include "model/setting/SettingsModel.hpp"
#include "tool/Constants.hpp"
#include "tool/Utils.hpp"
#include "EventCountNotifierSystemTrayIcon.hpp"
// =============================================================================
namespace {
constexpr int IconWidth = 256;
constexpr int IconHeight = 256;
constexpr int IconCounterBackgroundRadius = 100;
constexpr int IconCounterBlinkInterval = 1000;
constexpr int IconCounterTextPixelSize = 144;
} // namespace
DEFINE_ABSTRACT_OBJECT(EventCountNotifier)
QSharedPointer<EventCountNotifier> EventCountNotifier::create(QObject *parent) {
auto sharedPointer = QSharedPointer<EventCountNotifier>(new EventCountNotifier(parent), &QObject::deleteLater);
sharedPointer->setSelf(sharedPointer);
sharedPointer->moveToThread(App::getInstance()->thread());
return sharedPointer;
}
EventCountNotifier::EventCountNotifier(QObject *parent) : AbstractEventCountNotifier(parent) {
QSvgRenderer renderer((QString(Constants::WindowIconPath)));
if (!renderer.isValid()) qFatal("Invalid SVG Image.");
QPixmap buf(IconWidth, IconHeight);
buf.fill(QColor(Qt::transparent));
QPainter painter(&buf);
renderer.render(&painter);
mBuf = new QPixmap(buf);
mBufWithCounter = new QPixmap();
mBlinkTimer = new QTimer(this);
mBlinkTimer->setInterval(IconCounterBlinkInterval);
connect(mBlinkTimer, &QTimer::timeout, this, &EventCountNotifier::update);
}
void EventCountNotifier::setSelf(QSharedPointer<EventCountNotifier> me) {
}
EventCountNotifier::~EventCountNotifier() {
delete mBuf;
delete mBufWithCounter;
}
// -----------------------------------------------------------------------------
void EventCountNotifier::notifyEventCount(int n) {
mustBeInMainThread(log().arg(Q_FUNC_INFO));
n = n > 99 ? 99 : n;
QSystemTrayIcon *sysTrayIcon = App::getInstance()->getSystemTrayIcon();
if (!sysTrayIcon) return;
if (!n) {
mBlinkTimer->stop();
sysTrayIcon->setIcon(QIcon(*mBuf));
return;
}
*mBufWithCounter = *mBuf;
QPainter p(mBufWithCounter);
const int width = mBufWithCounter->width();
const int height = mBufWithCounter->height();
// Draw background.
{
p.setBrush(QColor(Utils::getDefaultStyleColor("main1_100")));
p.drawEllipse(QPointF(width / 2, height / 2), IconCounterBackgroundRadius, IconCounterBackgroundRadius);
}
// Draw text.
{
QFont font = p.font();
font.setPixelSize(IconCounterTextPixelSize);
p.setFont(font);
p.setPen(QPen(QColor(Utils::getDefaultStyleColor("main1_500_main"))));
p.drawText(QRect(0, 0, width, height), Qt::AlignCenter, QString::number(n));
}
// Change counter.
mBlinkTimer->stop();
auto coreModel = CoreModel::getInstance();
if (!coreModel->isInitialized() || SettingsModel::getInstance()->isSystrayNotificationBlinkEnabled())
mBlinkTimer->start();
mDisplayCounter = true;
update();
}
void EventCountNotifier::update() {
mustBeInMainThread(log().arg(Q_FUNC_INFO));
QSystemTrayIcon *sysTrayIcon = App::getInstance()->getSystemTrayIcon();
if (sysTrayIcon) {
sysTrayIcon->setIcon(QIcon(mDisplayCounter ? *mBufWithCounter : *mBuf));
}
mDisplayCounter = !mDisplayCounter;
}

View file

@ -0,0 +1,50 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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/>.
*/
#ifndef EVENT_COUNT_NOTIFIER_SYSTEM_TRAY_ICON_H_
#define EVENT_COUNT_NOTIFIER_SYSTEM_TRAY_ICON_H_
#include "AbstractEventCountNotifier.hpp"
// =============================================================================
class QTimer;
class EventCountNotifier : public AbstractEventCountNotifier {
public:
static QSharedPointer<EventCountNotifier> create(QObject *parent = Q_NULLPTR);
EventCountNotifier(QObject *parent = Q_NULLPTR);
void setSelf(QSharedPointer<EventCountNotifier> me);
~EventCountNotifier();
void notifyEventCount(int n) override;
private:
const QPixmap *mBuf = nullptr;
QPixmap *mBufWithCounter = nullptr;
QTimer *mBlinkTimer = nullptr;
bool mDisplayCounter = false;
QSharedPointer<SafeConnection<EventCountNotifier, CoreModel>> mCoreModelConnection;
void update();
DECLARE_ABSTRACT_OBJECT
};
#endif // EVENT_COUNT_NOTIFIER_SYSTEM_TRAY_ICON_H_

View file

@ -48,8 +48,9 @@ FriendCore::FriendCore(const std::shared_ptr<linphone::Friend> &contact, bool is
mustBeInLinphoneThread(getClassName());
mFriendModel = Utils::makeQObject_ptr<FriendModel>(contact);
mFriendModel->setSelf(mFriendModel);
mConsolidatedPresence = LinphoneEnums::fromLinphone(contact->getConsolidatedPresence());
mPresenceTimestamp = mFriendModel->getPresenceTimestamp();
auto presence = mFriendModel->getPresence(contact);
auto note = mFriendModel->getPresenceNote(contact);
App::postCoreAsync([this, presence, note]() { setPresence(presence, note); });
mPictureUri = Utils::coreStringToAppString(contact->getPhoto());
mFullName = mFriendModel->getFullName();
auto defaultAddress = contact->getAddress();
@ -65,7 +66,7 @@ FriendCore::FriendCore(const std::shared_ptr<linphone::Friend> &contact, bool is
auto addresses = contact->getAddresses();
for (auto &address : addresses) {
mAddressList.append(Utils::createFriendAddressVariant(
tr("sip_address"), Utils::coreStringToAppString(address->asStringUriOnly())));
tr("sip_address"), Utils::coreStringToAppString(address->asStringUriOnly())));
}
mDefaultAddress = defaultAddress ? Utils::coreStringToAppString(defaultAddress->asStringUriOnly()) : QString();
mDefaultFullAddress = defaultAddress ? Utils::coreStringToAppString(defaultAddress->asString()) : QString();
@ -90,7 +91,8 @@ FriendCore::FriendCore(const std::shared_ptr<linphone::Friend> &contact, bool is
mStarred = contact->getStarred();
mIsSaved = true;
mIsStored = isStored;
mIsLdap = ToolModel::friendIsInFriendList(ToolModel::getLdapFriendList(), contact);
mIsLdap = ToolModel::friendIsInFriendList(ToolModel::getLdapFriendList(), contact) ||
(sourceFlags & (int)linphone::MagicSearch::Source::LdapServers) != 0;
mIsCardDAV = (sourceFlags & (int)linphone::MagicSearch::Source::RemoteCardDAV) != 0;
mIsAppFriend = ToolModel::friendIsInFriendList(ToolModel::getAppFriendList(), contact);
} else {
@ -142,8 +144,7 @@ void FriendCore::setSelf(QSharedPointer<FriendCore> me) {
mFriendModelConnection->makeConnectToModel(
&FriendModel::removed, [this]() { mFriendModelConnection->invokeToCore([this]() { removed(this); }); });
mFriendModelConnection->makeConnectToModel(
&FriendModel::presenceReceived,
[this](LinphoneEnums::ConsolidatedPresence consolidatedPresence, QDateTime presenceTimestamp) {
&FriendModel::presenceReceived, [this](LinphoneEnums::Presence presence, QString presenceNote) {
auto devices = mFriendModel->getDevices();
QVariantList devicesList;
for (auto &device : devices) {
@ -153,14 +154,11 @@ void FriendCore::setSelf(QSharedPointer<FriendCore> me) {
Utils::coreStringToAppString(device->getAddress()->asString()),
LinphoneEnums::fromLinphone(device->getSecurityLevel())));
}
mFriendModelConnection->invokeToCore(
[this, consolidatedPresence, presenceTimestamp, devicesList]() {
setConsolidatedPresence(consolidatedPresence);
setPresenceTimestamp(presenceTimestamp);
setDevices(devicesList);
updateVerifiedDevicesCount();
});
mFriendModelConnection->invokeToCore([this, presence, devicesList, presenceNote]() {
setPresence(presence, presenceNote);
setDevices(devicesList);
updateVerifiedDevicesCount();
});
});
mFriendModelConnection->makeConnectToModel(&FriendModel::pictureUriChanged, [this](const QString &uri) {
mFriendModelConnection->invokeToCore([this, uri]() { this->onPictureUriChanged(uri); });
@ -188,7 +186,7 @@ void FriendCore::setSelf(QSharedPointer<FriendCore> me) {
QList<QVariant> addr;
for (auto &num : numbers) {
addr.append(Utils::createFriendAddressVariant(
tr("sip_address"), Utils::coreStringToAppString(num->asStringUriOnly())));
tr("sip_address"), Utils::coreStringToAppString(num->asStringUriOnly())));
}
mFriendModelConnection->invokeToCore([this, addr]() { resetPhoneNumbers(addr); });
});
@ -419,9 +417,10 @@ void FriendCore::appendAddress(const QString &addr) {
QString interpretedFullAddress = linphoneAddr ? Utils::coreStringToAppString(linphoneAddr->asString()) : "";
QString interpretedAddress = linphoneAddr ? Utils::coreStringToAppString(linphoneAddr->asStringUriOnly()) : "";
mCoreModelConnection->invokeToCore([this, interpretedAddress, interpretedFullAddress]() {
if (interpretedAddress.isEmpty()) Utils::showInformationPopup(tr("information_popup_error_title"),
//: "Adresse invalide"
tr("information_popup_invalid_address_message"), false);
if (interpretedAddress.isEmpty())
Utils::showInformationPopup(tr("information_popup_error_title"),
//: "Adresse invalide"
tr("information_popup_invalid_address_message"), false);
else {
mAddressList.append(Utils::createFriendAddressVariant(tr("sip_address"), interpretedAddress));
if (mDefaultFullAddress.isEmpty()) mDefaultFullAddress = interpretedFullAddress;
@ -507,30 +506,6 @@ void FriendCore::setDefaultAddress(const QString &address) {
}
}
LinphoneEnums::ConsolidatedPresence FriendCore::getConsolidatedPresence() const {
return mConsolidatedPresence;
}
void FriendCore::setConsolidatedPresence(LinphoneEnums::ConsolidatedPresence presence) {
mustBeInMainThread(log().arg(Q_FUNC_INFO));
if (mConsolidatedPresence != presence) {
mConsolidatedPresence = presence;
emit consolidatedPresenceChanged(mConsolidatedPresence);
}
}
QDateTime FriendCore::getPresenceTimestamp() const {
return mPresenceTimestamp;
}
void FriendCore::setPresenceTimestamp(QDateTime presenceTimestamp) {
mustBeInMainThread(log().arg(Q_FUNC_INFO));
if (mPresenceTimestamp != presenceTimestamp) {
mPresenceTimestamp = presenceTimestamp;
emit presenceTimestampChanged(mPresenceTimestamp);
}
}
QString FriendCore::getPictureUri() const {
return mPictureUri;
}
@ -612,8 +587,8 @@ void FriendCore::writeFromModel(const std::shared_ptr<FriendModel> &model) {
QList<QVariant> addresses;
for (auto &addr : model->getAddresses()) {
addresses.append(
Utils::createFriendAddressVariant(tr("sip_address"), Utils::coreStringToAppString(addr->asStringUriOnly())));
addresses.append(Utils::createFriendAddressVariant(tr("sip_address"),
Utils::coreStringToAppString(addr->asStringUriOnly())));
}
mAddressList = addresses;
mDefaultAddress = model->getDefaultAddress();
@ -759,3 +734,29 @@ bool FriendCore::getReadOnly() const {
std::shared_ptr<FriendModel> FriendCore::getFriendModel() {
return mFriendModel;
}
void FriendCore::setPresence(LinphoneEnums::Presence presence, QString presenceNote) {
mustBeInMainThread(log().arg(Q_FUNC_INFO));
bool notify = false;
if (presence != mPresence) {
mPresence = presence;
notify = true;
}
if (presenceNote != mPresenceNote) {
mPresenceNote = presenceNote;
notify = true;
}
if (notify) emit presenceChanged();
}
QUrl FriendCore::getPresenceIcon() {
return Utils::getPresenceIcon(mPresence);
}
QColor FriendCore::getPresenceColor() {
return Utils::getPresenceColor(mPresence);
}
QString FriendCore::getPresenceStatus() {
return Utils::getPresenceStatus(mPresence);
}

View file

@ -29,8 +29,8 @@
#include "tool/thread/SafeSharedPointer.hpp"
#include <linphone++/linphone.hh>
#include <QColor>
#include <QDateTime>
#include <QMap>
#include <QObject>
#include <QSharedPointer>
@ -63,9 +63,11 @@ class FriendCore : public QObject, public AbstractObject {
Q_PROPERTY(QString defaultAddress READ getDefaultAddress WRITE setDefaultAddress NOTIFY defaultAddressChanged)
Q_PROPERTY(QString defaultFullAddress READ getDefaultFullAddress WRITE setDefaultFullAddress NOTIFY
defaultFullAddressChanged)
Q_PROPERTY(QDateTime presenceTimestamp READ getPresenceTimestamp NOTIFY presenceTimestampChanged)
Q_PROPERTY(LinphoneEnums::ConsolidatedPresence consolidatedPresence READ getConsolidatedPresence NOTIFY
consolidatedPresenceChanged)
Q_PROPERTY(LinphoneEnums::Presence presence MEMBER mPresence NOTIFY presenceChanged)
Q_PROPERTY(QUrl presenceIcon READ getPresenceIcon NOTIFY presenceChanged)
Q_PROPERTY(QColor presenceColor READ getPresenceColor NOTIFY presenceChanged)
Q_PROPERTY(QString presenceStatus READ getPresenceStatus NOTIFY presenceChanged)
Q_PROPERTY(QString presenceNote MEMBER mPresenceNote NOTIFY presenceChanged)
Q_PROPERTY(bool isSaved READ getIsSaved NOTIFY isSavedChanged)
Q_PROPERTY(bool isStored READ getIsStored NOTIFY isStoredChanged)
Q_PROPERTY(QString pictureUri READ getPictureUri WRITE setPictureUri NOTIFY pictureUriChanged)
@ -130,12 +132,6 @@ public:
void setDevices(QVariantList devices);
Q_INVOKABLE LinphoneEnums::SecurityLevel getSecurityLevelForAddress(const QString &address) const;
LinphoneEnums::ConsolidatedPresence getConsolidatedPresence() const;
void setConsolidatedPresence(LinphoneEnums::ConsolidatedPresence presence);
QDateTime getPresenceTimestamp() const;
void setPresenceTimestamp(QDateTime presenceTimestamp);
bool getIsSaved() const;
void setIsSaved(bool isSaved);
@ -146,8 +142,6 @@ public:
void setPictureUri(const QString &uri);
void onPictureUriChanged(QString uri);
void onPresenceReceived(LinphoneEnums::ConsolidatedPresence consolidatedPresence, QDateTime presenceTimestamp);
bool isLdap() const;
bool isAppFriend() const;
bool isCardDAV() const;
@ -159,6 +153,10 @@ public:
Q_INVOKABLE void save();
Q_INVOKABLE void undo();
QColor getPresenceColor();
QString getPresenceStatus();
QUrl getPresenceIcon();
protected:
void resetPhoneNumbers(QList<QVariant> newList);
void resetAddresses(QList<QVariant> newList);
@ -173,8 +171,6 @@ signals:
void addressChanged();
void organizationChanged();
void jobChanged();
void consolidatedPresenceChanged(LinphoneEnums::ConsolidatedPresence level);
void presenceTimestampChanged(QDateTime presenceTimestamp);
void pictureUriChanged();
void saved();
void isSavedChanged(bool isSaved);
@ -186,13 +182,16 @@ signals:
void devicesChanged();
void verifiedDevicesChanged();
void lSetStarred(bool starred);
void presenceChanged();
protected:
void writeIntoModel(std::shared_ptr<FriendModel> model) const;
void writeFromModel(const std::shared_ptr<FriendModel> &model);
LinphoneEnums::ConsolidatedPresence mConsolidatedPresence = LinphoneEnums::ConsolidatedPresence::Offline;
QDateTime mPresenceTimestamp;
LinphoneEnums::Presence mPresence = LinphoneEnums::Presence::Undefined;
QColor mPresenceColor;
QString mPresenceStatus;
QString mPresenceNote = "";
QString mGivenName;
QString mFamilyName;
QString mFullName;
@ -214,6 +213,9 @@ protected:
QSharedPointer<SafeConnection<FriendCore, FriendModel>> mFriendModelConnection;
QSharedPointer<SafeConnection<FriendCore, CoreModel>> mCoreModelConnection;
private:
void setPresence(LinphoneEnums::Presence presence, QString presenceNote);
DECLARE_ABSTRACT_OBJECT
};

View file

@ -64,22 +64,27 @@ void LoginPage::login(const QString &username,
const QString &password,
QString displayName,
QString domain,
LinphoneEnums::TransportType transportType) {
LinphoneEnums::TransportType transportType,
QString registrarUri,
QString outboundProxyAddress,
QString connectionId) {
setErrorMessage("");
App::postModelAsync([=]() {
// Create on Model thread.
AccountManager *accountManager = new AccountManager();
connect(accountManager, &AccountManager::registrationStateChanged, this,
[accountManager, this](linphone::RegistrationState state, QString message) mutable {
[accountManager, this](linphone::RegistrationState state, linphone::Reason reason,
QString message) mutable {
// View thread
setRegistrationState(state);
mBadIds = reason == linphone::Reason::Forbidden;
emit reasonChanged();
switch (state) {
case linphone::RegistrationState::Failed: {
if (message.isEmpty())
//: Erreur durant la connexion
setErrorMessage(tr("default_account_connection_state_error_toast"));
else
setErrorMessage(message);
if (message.isEmpty())
//: Erreur durant la connexion, veuillez vérifier vos paramètres
setErrorMessage(tr("default_account_connection_state_error_toast"));
else setErrorMessage(message);
if (accountManager) {
accountManager->deleteLater();
accountManager = nullptr;
@ -110,9 +115,9 @@ void LoginPage::login(const QString &username,
QString error;
if (!accountManager->login(username, password, displayName, domain, LinphoneEnums::toLinphone(transportType),
&error)) {
&error, registrarUri, outboundProxyAddress, connectionId)) {
setErrorMessage(error);
emit accountManager->registrationStateChanged(linphone::RegistrationState::None);
emit accountManager->registrationStateChanged(linphone::RegistrationState::None, linphone::Reason::None);
}
});
}

View file

@ -35,12 +35,16 @@ public:
Q_PROPERTY(linphone::RegistrationState registrationState READ getRegistrationState NOTIFY registrationStateChanged)
Q_PROPERTY(QString errorMessage READ getErrorMessage NOTIFY errorMessageChanged)
Q_PROPERTY(bool badIds MEMBER mBadIds NOTIFY reasonChanged)
Q_INVOKABLE void login(const QString &username,
const QString &password,
QString displayName = QString(),
QString domain = QString(),
LinphoneEnums::TransportType transportType = LinphoneEnums::TransportType::Tls);
LinphoneEnums::TransportType transportType = LinphoneEnums::TransportType::Tls,
QString registrarUri = QString(),
QString outboundProxyAddress = QString(),
QString connectionId = QString());
linphone::RegistrationState getRegistrationState() const;
void setRegistrationState(linphone::RegistrationState status);
@ -51,10 +55,12 @@ public:
signals:
void registrationStateChanged();
void errorMessageChanged(QString error);
void reasonChanged();
private:
linphone::RegistrationState mRegistrationState = linphone::RegistrationState::None;
QString mErrorMessage;
bool mBadIds = false;
DECLARE_ABSTRACT_OBJECT
};

View file

@ -33,8 +33,10 @@
#include "core/App.hpp"
#include "core/call/CallGui.hpp"
#include "core/chat/ChatGui.hpp"
#include "model/tool/ToolModel.hpp"
#include "tool/LinphoneEnums.hpp"
#include "tool/accessibility/AccessibilityHelper.hpp"
#include "tool/providers/AvatarProvider.hpp"
#include "tool/providers/ImageProvider.hpp"
@ -85,9 +87,9 @@ void setProperty(QObject &object, const char *property, const T &value) {
// =============================================================================
const QHash<int, Notifier::Notification> Notifier::Notifications = {
//{Notifier::ReceivedMessage, {Notifier::ReceivedMessage, "NotificationReceivedMessage.qml", 10}},
{Notifier::ReceivedMessage, {Notifier::ReceivedMessage, "NotificationReceivedMessage.qml", 10}},
//{Notifier::ReceivedFileMessage, {Notifier::ReceivedFileMessage, "NotificationReceivedFileMessage.qml", 10}},
{Notifier::ReceivedCall, {Notifier::ReceivedCall, "NotificationReceivedCall.qml", 30}},
{Notifier::ReceivedCall, {Notifier::ReceivedCall, "NotificationReceivedCall.qml", 30}}
//{Notifier::NewVersionAvailable, {Notifier::NewVersionAvailable, "NotificationNewVersionAvailable.qml", 30}},
//{Notifier::SnapshotWasTaken, {Notifier::SnapshotWasTaken, "NotificationSnapshotWasTaken.qml", 10}},
//{Notifier::RecordingCompleted, {Notifier::RecordingCompleted, "NotificationRecordingCompleted.qml", 10}}
@ -127,8 +129,8 @@ Notifier::~Notifier() {
bool Notifier::createNotification(Notifier::NotificationType type, QVariantMap data) {
mMutex->lock();
Q_ASSERT(mInstancesNumber <= MaxNotificationsNumber);
if (mInstancesNumber == MaxNotificationsNumber) { // Check existing instances.
// Q_ASSERT(mInstancesNumber <= MaxNotificationsNumber);
if (mInstancesNumber >= MaxNotificationsNumber) { // Check existing instances.
qWarning() << QStringLiteral("Unable to create another notification.");
mMutex->unlock();
return false;
@ -163,7 +165,6 @@ bool Notifier::createNotification(Notifier::NotificationType type, QVariantMap d
engine->deleteLater();
exit(-1);
} else {
lDebug() << engine->rootObjects()[0];
auto window = qobject_cast<QQuickWindow *>(obj);
if (window) {
window->setProperty(NotificationPropertyData, data);
@ -171,28 +172,24 @@ bool Notifier::createNotification(Notifier::NotificationType type, QVariantMap d
// window->setProperty(it.key().toLatin1(), it.value());
const int timeout = Notifications[type].getTimeout() * 1000;
auto updateNotificationCoordinates = [this, window, screen](int width, int height) {
int *screenHeightOffset = &mScreenHeightOffset[screen->name()]; // Access optimization
auto screenHeightOffset =
screen ? mScreenHeightOffset.value(screen->name()) : 0; // Access optimization
QRect availableGeometry = screen->availableGeometry();
int heightOffset = availableGeometry.y() +
(availableGeometry.height() -
height); //*screen->devicePixelRatio(); when using manual scaler
window->setX(availableGeometry.x() +
(availableGeometry.width() -
width)); //*screen->devicePixelRatio()); when using manual scaler
window->setY(heightOffset - (*screenHeightOffset % heightOffset));
window->setY(availableGeometry.y() + availableGeometry.height() - screenHeightOffset -
height);
};
QObject::connect(window, &QQuickWindow::widthChanged,
[window, updateNotificationCoordinates](int w) {
updateNotificationCoordinates(w, window->height());
});
QObject::connect(window, &QQuickWindow::heightChanged,
[window, updateNotificationCoordinates](int h) {
updateNotificationCoordinates(window->width(), h);
});
updateNotificationCoordinates(window->width(), window->height());
QObject::connect(window, &QQuickWindow::closing, window,
[this, window] { deleteNotification(QVariant::fromValue(window)); });
auto screenHeightOffset =
screen ? mScreenHeightOffset.take(screen->name()) : 0; // Access optimization
mScreenHeightOffset.insert(screen->name(), screenHeightOffset + window->height());
QObject::connect(window, &QQuickWindow::closing, window, [this, window] {
qDebug() << "closing notification";
deleteNotification(QVariant::fromValue(window));
});
showNotification(window, timeout);
lInfo() << QStringLiteral("Create notification:") << QVariant::fromValue(window);
}
@ -212,8 +209,6 @@ bool Notifier::createNotification(Notifier::NotificationType type, QVariantMap d
void Notifier::showNotification(QObject *notification, int timeout) {
// Display notification.
QMetaObject::invokeMethod(notification, NotificationShowMethodName, Qt::DirectConnection);
QTimer *timer = new QTimer(notification);
timer->setInterval(timeout);
timer->setSingleShot(true);
@ -285,63 +280,153 @@ void Notifier::notifyReceivedCall(const shared_ptr<linphone::Call> &call) {
auto account = ToolModel::findAccount(call->getToAddress());
if (account) {
auto accountModel = Utils::makeQObject_ptr<AccountModel>(account);
accountModel->setSelf(accountModel);
if (!accountModel->getNotificationsAllowed()) {
qInfo()
<< "Notifications have been disabled for this account - not creating a notification for incoming call";
lInfo() << log().arg(
"Notifications have been disabled for this account - not creating a notification for incoming call");
if (accountModel->forwardToVoiceMailInDndPresence()) {
lInfo() << log().arg("Transferring call to voicemail");
auto voicemailAddress = linphone::Factory::get()->createAddress(
Utils::appStringToCoreString(accountModel->getVoicemailAddress()));
if (voicemailAddress) call->transferTo(voicemailAddress);
}
return;
}
accountModel->deleteLater();
}
auto model = CallCore::create(call);
auto gui = new CallGui(model);
gui->moveToThread(App::getInstance()->thread());
App::postCoreAsync([this, gui]() {
auto callLog = call->getCallLog();
auto displayName = callLog && callLog->getConferenceInfo()
? Utils::coreStringToAppString(callLog->getConferenceInfo()->getSubject())
: ToolModel::getDisplayName(call->getRemoteAddress());
// Accessibility alert
//: New call from %1
AccessibilityHelper::announceMessage(tr("new_call_alert_accessible_name").arg(displayName));
App::postCoreAsync([this, gui, displayName]() {
mustBeInMainThread(getClassName());
QVariantMap map;
map["displayName"].setValue(displayName);
map["call"].setValue(gui);
CREATE_NOTIFICATION(Notifier::ReceivedCall, map)
});
}
/*
void Notifier::notifyReceivedMessages(const list<shared_ptr<linphone::ChatMessage>> &messages) {
QVariantMap map;
QString txt;
if (messages.size() > 0) {
shared_ptr<linphone::ChatMessage> message = messages.front();
void Notifier::notifyReceivedMessages(const std::shared_ptr<linphone::ChatRoom> &room,
const list<shared_ptr<linphone::ChatMessage>> &messages) {
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
if (messages.size() == 1) {
auto fileContent = message->getFileTransferInformation();
if (!fileContent) {
foreach (auto content, message->getContents()) {
if (content->isText()) txt += content->getUtf8Text().c_str();
}
} else if (fileContent->isVoiceRecording())
//: 'Voice message received!' : message to warn the user in a notofication for voice messages.
txt = tr("new_voice_message");
else txt = tr("new_file_message");
if (txt.isEmpty() && message->hasConferenceInvitationContent())
//: 'Conference invitation received!' : Notification about receiving an invitation to a conference.
txt = tr("new_conference_invitation");
} else
//: 'New messages received!' Notification that warn the user of new messages.
txt = tr("new_chat_room_messages");
map["message"] = txt;
shared_ptr<linphone::ChatRoom> chatRoom(message->getChatRoom());
map["timelineModel"].setValue(
CoreManager::getInstance()->getTimelineListModel()->getTimeline(chatRoom, true).get());
if (messages.size() == 1) { // Display only sender on mono message.
map["remoteAddress"] = Utils::coreStringToAppString(message->getFromAddress()->asStringUriOnly());
map["fullremoteAddress"] = Utils::coreStringToAppString(message->getFromAddress()->asString());
}
map["localAddress"] = Utils::coreStringToAppString(message->getToAddress()->asStringUriOnly());
map["fullLocalAddress"] = Utils::coreStringToAppString(message->getToAddress()->asString());
map["window"].setValue(App::getInstance()->getMainWindow());
CREATE_NOTIFICATION(Notifier::ReceivedMessage, map)
}
if (room->getMuted()) return;
QString txt;
QString remoteAddress;
if (messages.size() > 0) {
shared_ptr<linphone::ChatMessage> message = messages.front();
auto receiverAccount = ToolModel::findAccount(message->getToAddress());
if (receiverAccount) {
auto senderAccount = ToolModel::findAccount(message->getFromAddress());
auto currentAccount = CoreModel::getInstance()->getCore()->getDefaultAccount();
if (senderAccount && senderAccount->getContactAddress() && currentAccount->getContactAddress() &&
senderAccount->getContactAddress()->weakEqual(currentAccount->getContactAddress())) {
qDebug() << "sender is current account, return";
return;
}
auto accountModel = Utils::makeQObject_ptr<AccountModel>(receiverAccount);
if (!accountModel->getNotificationsAllowed()) {
lInfo() << log().arg(
"Notifications have been disabled for this account - not creating a notification for "
"incoming message");
return;
}
accountModel->deleteLater();
}
auto getMessage = [this, &remoteAddress, &txt](const shared_ptr<linphone::ChatMessage> &message) {
if (message->isRead()) return;
auto remoteAddr = message->getFromAddress();
// remoteAddr->clean();
remoteAddress = Utils::coreStringToAppString(remoteAddr->asStringUriOnly());
auto fileContent = message->getFileTransferInformation();
if (!fileContent) {
for (auto content : message->getContents()) {
if (content->isText()) txt += content->getUtf8Text().c_str();
}
} else if (fileContent->isVoiceRecording())
//: 'Voice message received!' : message to warn the user in a notofication for voice messages.
txt = tr("new_voice_message");
else txt = tr("new_file_message");
if (txt.isEmpty() && message->hasConferenceInvitationContent())
//: 'Conference invitation received!' : Notification about receiving an invitation to a conference.
txt = tr("new_conference_invitation");
};
if (messages.size() == 1) { // Display only sender on mono message.
if (message->isRead()) return;
getMessage(message);
if (txt.isEmpty()) { // Do not notify message without content
qDebug() << "empty notif, return";
return;
}
} else {
int unreadCount = 0;
for (auto &message : messages) {
if (!message->isRead()) {
++unreadCount;
if (unreadCount == 1) getMessage(message);
}
}
if (unreadCount == 0) return;
if (unreadCount > 1)
//: 'New messages received!' Notification that warn the user of new messages.
txt = tr("new_chat_room_messages");
}
// Play noitification sound
auto settings = SettingsModel::getInstance();
if (settings && !settings->dndEnabled()) {
CoreModel::getInstance()->getCore()->playLocal(
Utils::appStringToCoreString(settings->getChatNotificationSoundPath()));
}
// If chat currently displayed, do not display notification
auto currentlyDisplayedChat = App::getInstance()->getCurrentChat();
auto mainWin = App::getInstance()->getMainWindow();
if (currentlyDisplayedChat && mainWin->isActive()) {
auto linphoneCurrent = currentlyDisplayedChat->mCore->getModel()->getMonitor();
if (linphoneCurrent->getIdentifier() == room->getIdentifier()) {
lInfo() << log().arg("Chat is currently displayed in the view, do not show notification");
return;
}
}
auto chatCore = ChatCore::create(room);
App::postCoreAsync([this, txt, chatCore, remoteAddress]() {
mustBeInMainThread(getClassName());
// Accessibility alert
//: New message on chatroom %1
AccessibilityHelper::announceMessage(tr("new_message_alert_accessible_name").arg(chatCore->getTitle()));
QVariantMap map;
map["message"] = txt;
map["remoteAddress"] = remoteAddress;
map["chatRoomName"] = chatCore->getTitle();
map["chatRoomAddress"] = chatCore->getChatRoomAddress();
map["avatarUri"] = chatCore->getAvatarUri();
map["isGroupChat"] = chatCore->isGroupChat();
map["chat"] = QVariant::fromValue(chatCore ? new ChatGui(chatCore) : nullptr);
CREATE_NOTIFICATION(Notifier::ReceivedMessage, map)
});
}
}
/*
void Notifier::notifyReceivedReactions(
const QList<QPair<std::shared_ptr<linphone::ChatMessage>, std::shared_ptr<const linphone::ChatMessageReaction>>>

View file

@ -41,9 +41,9 @@ public:
~Notifier();
enum NotificationType {
// ReceivedMessage,
ReceivedMessage,
// ReceivedFileMessage,
ReceivedCall,
ReceivedCall
// NewVersionAvailable,
// SnapshotWasTaken,
// RecordingCompleted
@ -52,8 +52,9 @@ public:
// void notifyReceivedCall(Call *call);
void notifyReceivedCall(const std::shared_ptr<linphone::Call> &call); // Call from Linphone
void notifyReceivedMessages(const std::shared_ptr<linphone::ChatRoom> &room,
const std::list<std::shared_ptr<linphone::ChatMessage>> &messages);
/*
void notifyReceivedMessages(const std::list<std::shared_ptr<linphone::ChatMessage>> &messages);
void notifyReceivedReactions(
const QList<QPair<std::shared_ptr<linphone::ChatMessage>, std::shared_ptr<const
linphone::ChatMessageReaction>>> &reactions); void notifyReceivedFileMessage(const

View file

@ -46,12 +46,16 @@ ParticipantCore::ParticipantCore(const std::shared_ptr<linphone::Participant> &p
mParticipantModel->moveToThread(CoreModel::getInstance()->thread());
if (participant) {
mAdminStatus = participant->isAdmin();
mSipAddress = Utils::coreStringToAppString(participant->getAddress()->asStringUriOnly());
auto participantAddress = participant->getAddress();
mUsername = Utils::coreStringToAppString(participantAddress->getUsername());
mSipAddress = Utils::coreStringToAppString(participantAddress->asStringUriOnly());
mIsMe = ToolModel::isMe(mSipAddress);
mCreationTime = QDateTime::fromSecsSinceEpoch(participant->getCreationTime());
mDisplayName = Utils::coreStringToAppString(participant->getAddress()->getDisplayName());
if (mDisplayName.isEmpty())
mDisplayName = Utils::coreStringToAppString(participant->getAddress()->getUsername());
mDisplayName = Utils::coreStringToAppString(participantAddress->getDisplayName());
if (mDisplayName.isEmpty()) mDisplayName = ToolModel::getDisplayName(participantAddress);
auto isFriend = ToolModel::findFriendByAddress(participantAddress);
if (isFriend && isFriend->getCore()) mSecurityLevel = LinphoneEnums::fromLinphone(isFriend->getSecurityLevel());
else mSecurityLevel = LinphoneEnums::SecurityLevel::None;
for (auto &device : participant->getDevices()) {
auto name = Utils::coreStringToAppString(device->getName());
auto address = Utils::coreStringToAppString(device->getAddress()->asStringUriOnly());
@ -75,7 +79,7 @@ void ParticipantCore::setSelf(QSharedPointer<ParticipantCore> me) {
connect(this, &ParticipantCore::sipAddressChanged, this, &ParticipantCore::updateIsMe);
}
int ParticipantCore::getSecurityLevel() const {
LinphoneEnums::SecurityLevel ParticipantCore::getSecurityLevel() const {
return mSecurityLevel;
}
@ -121,6 +125,17 @@ QString ParticipantCore::getDisplayName() const {
return mDisplayName;
}
void ParticipantCore::setUsername(const QString &name) {
if (mUsername != name) {
mUsername = name;
emit usernameChanged();
}
}
QString ParticipantCore::getUsername() const {
return mUsername;
}
QDateTime ParticipantCore::getCreationTime() const {
return mCreationTime;
}
@ -153,7 +168,7 @@ void ParticipantCore::setIsFocus(const bool &focus) {
}
}
void ParticipantCore::setSecurityLevel(int level) {
void ParticipantCore::setSecurityLevel(LinphoneEnums::SecurityLevel level) {
if (level != mSecurityLevel) {
mSecurityLevel = level;
emit securityLevelChanged();

View file

@ -40,11 +40,12 @@ class ParticipantCore : public QObject, public AbstractObject {
Q_PROPERTY(QString sipAddress READ getSipAddress WRITE setSipAddress NOTIFY sipAddressChanged)
Q_PROPERTY(QString displayName READ getDisplayName WRITE setDisplayName NOTIFY displayNameChanged)
Q_PROPERTY(QString username READ getUsername WRITE setUsername NOTIFY usernameChanged)
Q_PROPERTY(bool isAdmin READ isAdmin WRITE setIsAdmin NOTIFY isAdminChanged)
Q_PROPERTY(bool isMe READ isMe NOTIFY isMeChanged)
Q_PROPERTY(QDateTime creationTime READ getCreationTime CONSTANT)
Q_PROPERTY(bool focus READ isFocus CONSTANT)
Q_PROPERTY(int securityLevel READ getSecurityLevel NOTIFY securityLevelChanged)
Q_PROPERTY(LinphoneEnums::SecurityLevel securityLevel READ getSecurityLevel NOTIFY securityLevelChanged)
Q_PROPERTY(int deviceCount READ getDeviceCount NOTIFY deviceCountChanged)
Q_PROPERTY(QList<QVariant> devices READ getParticipantDevices NOTIFY deviceChanged)
@ -56,11 +57,12 @@ public:
void setSelf(QSharedPointer<ParticipantCore> me);
QString getDisplayName() const;
QString getUsername() const;
QString getSipAddress() const;
QDateTime getCreationTime() const;
bool isAdmin() const;
bool isFocus() const;
int getSecurityLevel() const;
LinphoneEnums::SecurityLevel getSecurityLevel() const;
int getDeviceCount() const;
bool isMe() const;
@ -69,10 +71,11 @@ public:
void setSipAddress(const QString &address);
void setDisplayName(const QString &name);
void setUsername(const QString &name);
void setCreationTime(const QDateTime &date);
void setIsAdmin(const bool &status);
void setIsFocus(const bool &focus);
void setSecurityLevel(int level);
void setSecurityLevel(LinphoneEnums::SecurityLevel level);
QList<QVariant> getParticipantDevices();
@ -92,6 +95,7 @@ signals:
void invitingChanged();
void creationTimeChanged();
void displayNameChanged();
void usernameChanged();
void lStartInvitation(const int &secs = 30);
void lSetIsAdmin(bool status);
@ -107,11 +111,12 @@ private:
QList<QVariant> mParticipantDevices;
QString mDisplayName;
QString mUsername;
QString mSipAddress;
QDateTime mCreationTime;
bool mAdminStatus;
bool mIsFocus;
int mSecurityLevel;
LinphoneEnums::SecurityLevel mSecurityLevel;
bool mIsMe;
DECLARE_ABSTRACT_OBJECT

View file

@ -44,12 +44,13 @@ ParticipantDeviceCore::ParticipantDeviceCore(const std::shared_ptr<linphone::Par
mustBeInLinphoneThread(getClassName());
if (device) {
mName = Utils::coreStringToAppString(device->getName());
auto deviceAddress = device->getAddress()->clone();
auto deviceAddress = device->getAddress();
mUniqueAddress = Utils::coreStringToAppString(deviceAddress->asString());
mAddress = Utils::coreStringToAppString(deviceAddress->asStringUriOnly());
// the display name of the device himself may be the uncleaned sip uri
// Use the participant name instead
mDisplayName = Utils::coreStringToAppString(device->getParticipant()->getAddress()->getDisplayName());
auto participant = device->getParticipant();
mDisplayName = Utils::coreStringToAppString(participant ? participant->getAddress()->getDisplayName() : "");
if (mDisplayName.isEmpty()) {
mDisplayName = ToolModel::getDisplayName(deviceAddress);
}

View file

@ -0,0 +1,76 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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 "ParticipantInfoList.hpp"
#include "core/App.hpp"
#include "core/chat/ChatCore.hpp"
#include "core/participant/ParticipantGui.hpp"
#include "tool/Utils.hpp"
DEFINE_ABSTRACT_OBJECT(ParticipantInfoList)
QSharedPointer<ParticipantInfoList> ParticipantInfoList::create() {
auto model = QSharedPointer<ParticipantInfoList>(new ParticipantInfoList(), &QObject::deleteLater);
model->moveToThread(App::getInstance()->thread());
return model;
}
QSharedPointer<ParticipantInfoList> ParticipantInfoList::create(const QSharedPointer<ChatCore> &chatCore) {
auto model = create();
model->setChatCore(chatCore);
return model;
}
ParticipantInfoList::ParticipantInfoList(QObject *parent) : ListProxy(parent) {
App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership);
}
ParticipantInfoList::~ParticipantInfoList() {
mList.clear();
}
void ParticipantInfoList::setChatCore(const QSharedPointer<ChatCore> &chatCore) {
mustBeInMainThread(log().arg(Q_FUNC_INFO));
if (mChatCore) disconnect(mChatCore.get(), &ChatCore::participantsChanged, this, nullptr);
mChatCore = chatCore;
lDebug() << "[ParticipantInfoList] : set Chat " << mChatCore.get();
clearData();
if (mChatCore) {
auto buildList = [this] {
QStringList participantAddresses;
QList<QSharedPointer<ParticipantGui>> participantList;
auto participants = mChatCore->getParticipants();
resetData();
for (auto p : participants)
add(p);
};
connect(mChatCore.get(), &ChatCore::participantsChanged, this, buildList);
buildList();
}
}
QVariant ParticipantInfoList::data(const QModelIndex &index, int role) const {
int row = index.row();
if (!index.isValid() || row < 0 || row >= mList.count()) return QVariant();
if (role == Qt::DisplayRole) {
return QVariant::fromValue(new ParticipantGui(mList[row].objectCast<ParticipantCore>()));
}
return QVariant();
}

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