diff --git a/.travis.yml b/.travis.yml index 1ae26b336..04cdc4e89 100644 --- a/.travis.yml +++ b/.travis.yml @@ -103,16 +103,22 @@ matrix: cache: directories: - /opt/build-windows/x86_64 - - stage: "macOS and AppImage" + - stage: "macOS, AppImage and Flatpak" os: osx osx_image: xcode7.3 env: JOB=build-osx - - stage: "macOS and AppImage" + - stage: "macOS, AppImage and Flatpak" os: linux env: JOB=APPIMAGE script: ./appimage/build-appimage.sh services: - docker + - stage: "macOS, AppImage and Flatpak" + os: linux + env: JOB=FLATPAK + script: ./flatpak/build-flatpak.sh + services: + - docker script: "./.travis/$JOB.sh" @@ -121,13 +127,26 @@ deploy: - provider: releases api_key: secure: "BRbzTWRvadALRQSTihMKruOj64ydxusMUS9FQR//qFlS345ZYfYta43W//4LcWWDKtj6IvA6DRqNdabgWnpbpxpnm9gVftGUdOKlU3niPZhwsMkB2M12QHUnAP6DVOfGPvdciBV+6mu73SSxniEcrYjZ1CrRX7mknmehPpVKxNk=" - file: ./output/qTox-"$TRAVIS_TAG".AppImage + file_glob: true + file: ./output/* on: condition: $JOB == APPIMAGE repo: qTox/qTox tags: true skip_cleanup: true + # Linux Flatpak + - provider: releases + api_key: + secure: "BRbzTWRvadALRQSTihMKruOj64ydxusMUS9FQR//qFlS345ZYfYta43W//4LcWWDKtj6IvA6DRqNdabgWnpbpxpnm9gVftGUdOKlU3niPZhwsMkB2M12QHUnAP6DVOfGPvdciBV+6mu73SSxniEcrYjZ1CrRX7mknmehPpVKxNk=" + file_glob: true + file: ./output/* + on: + condition: $JOB == FLATPAK + repo: qTox/qTox + tags: true + skip_cleanup: true + # osx binary - provider: releases api_key: diff --git a/.travis/build-ubuntu-14-04.sh b/.travis/build-ubuntu-14-04.sh index f19b5e97e..d1c1a5e3a 100755 --- a/.travis/build-ubuntu-14-04.sh +++ b/.travis/build-ubuntu-14-04.sh @@ -35,6 +35,7 @@ sudo apt-get install -y --force-yes \ libgdk-pixbuf2.0-dev \ libglib2.0-dev \ libgtk2.0-dev \ + libkdeui5 \ libopenal-dev \ libopus-dev \ libqrencode-dev \ @@ -170,7 +171,8 @@ build_qtox() { cmake -H. -B"$BUILDDIR" \ -DSMILEYS=DISABLED \ -DENABLE_STATUSNOTIFIER=False \ - -DENABLE_GTK_SYSTRAY=False + -DENABLE_GTK_SYSTRAY=False \ + -DSPELL_CHECK=OFF bdir diff --git a/CHANGELOG.md b/CHANGELOG.md index 2161c33d7..2bb6efed0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,99 @@ + +## v1.16.3 (2018-07-18) + +This point release fixes flatpak build. No feature changes. + + + + +## v1.16.2 (2018-07-15) + +This point release fixes dialog spam from receiving invalid filenames and logs +spam. No feature changes. + +#### Bug Fixes + +* **logging:** only log toxcore messages above TRACE level ([4dc74201](https://github.com/qTox/qTox/commit/4dc7420162e69095942b392048c309e6246d6b21)) +* **ui:** don't emit filename change windows for every chat ([c1701345](https://github.com/qTox/qTox/commit/c1701345455ad5b253beeaa3d487daa01b8b1b21)) + + + + +## v1.16.1 (2018-07-04) + +This point release fixes our deployment of Flapak and AppImage on Github. No +feature changes. + +#### Features + +* **deploy:** upload Flatpak bundle to Github releases ([59b5578c](https://github.com/qTox/qTox/commit/59b5578c7bffc56f6227c60bfcb38f97d39ec8d9)) + +#### Bug Fixes + +* **deploy:** fix file path in AppImage deployment ([64602f38](https://github.com/qTox/qTox/commit/64602f38f154a3f3d2429146ae5d370b2202d1b8)) + + + + +## v1.16.0 (2018-07-02) + +The most notable additions in this release are a new fullscreen mode for +video calls, a new call end sound and support for more camera resolutions. To +distribute qTox in a more user friendly manner we now publish Flatpak and +AppImage packages. + +#### Bug Fixes + +* remove full screen btn from audio group chat ([0d3f061b](https://github.com/qTox/qTox/commit/0d3f061ba80d9f3f8a971d2b8e11a7d9b59d180a)) +* local toxcore install with bootstrap.sh ([9ca38750](https://github.com/qTox/qTox/commit/9ca3875079adf175f31f568e45aabc37e3409000), closes [#5199](https://github.com/qTox/qTox/issues/5199)) +* simple_make.sh script ([ead2152d](https://github.com/qTox/qTox/commit/ead2152d6f0d15f7e662975fb3ed8525109794c3)) +* Fix PR #5182. Eliminating the 'new' operator at ToxOptionsWrapper ([9b6cd1c0](https://github.com/qTox/qTox/commit/9b6cd1c0227006308d4fe556f2b721865c2d9b21)) +* Fix usage of unitialized functions ([06ae7ead](https://github.com/qTox/qTox/commit/06ae7ead0c7c23935c1c05c75d9cb11ed516224b)) +* two crashes, uncovered by the persistent groupchat patch ([48179b6a](https://github.com/qTox/qTox/commit/48179b6a19807383e298661a21f97db3b140eb44)) +* delete double initialization callDuration ([dc1f5ea0](https://github.com/qTox/qTox/commit/dc1f5ea0a319bf4cbf05989c414ccaea898b4826)) +* **Core:** fix use after free of proxyAddrData ([26b59d31](https://github.com/qTox/qTox/commit/26b59d312375ad6391228308aabe45f0a85a1194)) +* **appimage:** build sqlcipher form source ([64a7c24b](https://github.com/qTox/qTox/commit/64a7c24b2b5ad11a6df5dbb11da6e3aa7c0fd6f3)) +* **audio:** + * fix error introduced in 67f2605971cf43093c72f811e4df90ab70544dd6 ([40d30153](https://github.com/qTox/qTox/commit/40d30153aed223b65b596dc7d3bf17573b04f3e9)) + * connect the correct audio callbacks ([a00af087](https://github.com/qTox/qTox/commit/a00af087778c6315ef55ed77c4209cbb63a6323d)) + * close the audio device after playing a sound ([a3370173](https://github.com/qTox/qTox/commit/a3370173df24cd6880e3e3845ddbbc7c090b7aed)) +* **build:** + * Elimination the build warnings (Wunused-variable, Wreorder) ([2cd65610](https://github.com/qTox/qTox/commit/2cd65610fcce0c3dcf8a5e9cb9f313a76167c09a)) + * correct install script nsis for win64 ([25e69572](https://github.com/qTox/qTox/commit/25e69572f89d816cfab5a8c0d1c261bae34d3cdd)) + * make qTox compile with ffmpeg 4.0 and newer ([44193176](https://github.com/qTox/qTox/commit/441931765ffe3de349b28a28bf10a006edcc9949)) +* **chatform:** + * name in window title and close detached chats ([39968a31](https://github.com/qTox/qTox/commit/39968a313d78c727046837901e6cc3d6c31d18e0)) + * check for empty path when exporting profile ([757791ee](https://github.com/qTox/qTox/commit/757791eea4be390bb6d1cdc908d1cd3c4b18728d), closes [#5146](https://github.com/qTox/qTox/issues/5146)) +* **core:** Clean illegal chars from filenames ([ab85716f](https://github.com/qTox/qTox/commit/ab85716f00acfe00ff8035670919dd548d7f7f83)) +* **docs:** update toxcore build instructions ([b00cbc1d](https://github.com/qTox/qTox/commit/b00cbc1d6f3a7f8406e4a96e732c534068fde22c)) +* **file:** don't clean the filenames of avatar transfer ([2a8ab03e](https://github.com/qTox/qTox/commit/2a8ab03e46dd08efc4051a01bea56fe6a4c38a11)) +* **history:** don't save both action prefix and displayed name ([dfd2de83](https://github.com/qTox/qTox/commit/dfd2de836eae605e02a1afb270620dd9274f6385)) +* **leak:** Fix few memory leaks ([daaa5518](https://github.com/qTox/qTox/commit/daaa5518dd7c02c2de45690daa3f592206fc4023)) +* **login:** start login screen on profile select by -p option ([1af3ad69](https://github.com/qTox/qTox/commit/1af3ad69e884bc4e74a4fcdd452a6aff10bffd62)) +* **settings:** + * automatically disable UDP when a proxy is set ([977b7fc9](https://github.com/qTox/qTox/commit/977b7fc9a02b2b44164ffb77ab35f4cdfae90542)) + * prevent segfault on wrong proxy settings ([dfd5232e](https://github.com/qTox/qTox/commit/dfd5232e2fb727685a20804d7ca3b932ea239332)) +* **simple_make:** correct variable initialization ([1537f83e](https://github.com/qTox/qTox/commit/1537f83e85ff28dd73fb66161ae2cd5eeef692d1)) +* **theme:** clear stylesheet cache on theme colour change ([8ba8ce91](https://github.com/qTox/qTox/commit/8ba8ce91f3317794b72fb4937c459dac2856d367)) +* **ui:** increase number of low res camera options ([72931514](https://github.com/qTox/qTox/commit/72931514695a8691593d6a5abd2df1e340f95002)) +* **video:** unsubscribe the video device correctly ([e55f86c6](https://github.com/qTox/qTox/commit/e55f86c6a5b0344642fcb3d7a2550df6e899a6e5)) +* **wayland:** Fix desktop file name in Qt properties ([c1caeb58](https://github.com/qTox/qTox/commit/c1caeb585a8845eaa72c7db79fb334262eafdb8f)) + +#### Features + +* Add ability to remove dialog from content dialog with middle click ([aae567ed](https://github.com/qTox/qTox/commit/aae567ed8e299fc0cdd700e2e0020042ee1cba11)) +* Add ability to quit group with middle click ([228c431c](https://github.com/qTox/qTox/commit/228c431c890a7e68d078b441311892c691643926)) +* Add middle mouse clicked signal for GenericChatroom ([65fc1dc2](https://github.com/qTox/qTox/commit/65fc1dc266da29e0679f2b645c31bc428f0cf575)) +* **appimage:** build appimage on TravisCI ([f7345e4d](https://github.com/qTox/qTox/commit/f7345e4db264a5681490b9094981a65cac68d317)) +* **call:** add call end sound ([65896e45](https://github.com/qTox/qTox/commit/65896e45017f8f748bc5b9db10a4400d7fd418dc)) +* **chat:** + * add UI option to mute group peers ([2fae2a30](https://github.com/qTox/qTox/commit/2fae2a30f76978ce722c5b24236384c8052ebfc4)) + * full screen video chat ([d6df8883](https://github.com/qTox/qTox/commit/d6df8883e399b95a55c5a5870497c1dcd45a3917)) +* **core:** put c-toxcore log messages in the qTox log ([4faab075](https://github.com/qTox/qTox/commit/4faab0750d3841beeb08c7d17e85044b5013aea8)) +* **history:** load set number of messages from history ([ca32e77d](https://github.com/qTox/qTox/commit/ca32e77d7400e23a6a839f6a8d1f322bfe48bbf0)) + + + ## v1.15.0 (2018-04-18) @@ -45,88 +141,7 @@ -## (2018-03-12) - - -#### Bug Fixes - -* Not quit on close if this setting is enabled ([e73dc10c](https://github.com/qTox/qTox/commit/e73dc10c7fd23b887cc5e2d5d4021bc02c8555ec)) -* add search symbol ' in history ([3e05279c](https://github.com/qTox/qTox/commit/3e05279c097b33b09cedcebae4150c839a23af35)) -* Use real channels number ([e74cc37a](https://github.com/qTox/qTox/commit/e74cc37a2d02e9d4cbd016bac9dbb7697e8445e7)) -* Allocate memory to input buffer ([900f2a1a](https://github.com/qTox/qTox/commit/900f2a1ad3b328359a0ae089e778b15280512a9d)) -* Call doAudio on timer timeout ([2353a66f](https://github.com/qTox/qTox/commit/2353a66fded32174421c9663ced5cfe4ceabe00b)) -* [un]subscribe output in avform ([8c05399e](https://github.com/qTox/qTox/commit/8c05399e418f2c0147ce2d9c7dd220a0cdc97765)) -* Correct display the call confirm window (CallConfirmWidget) ([f4fe343e](https://github.com/qTox/qTox/commit/f4fe343eca3eaf84f9ce300b59be9e83a70c204e)) -* elimination of warning '-Wreorder' ([0869d3d8](https://github.com/qTox/qTox/commit/0869d3d8fdc9e9de2f1df51c377ddba71a1ce523)) -* Use epsilon to compare float ([91dabf11](https://github.com/qTox/qTox/commit/91dabf11d31807f499d6e949373bf22762e80f5b)) -* **UI:** prevent deadlocks on logout and profile delete ([a49e3458](https://github.com/qTox/qTox/commit/a49e34589f40edfb3fc46d5700573f87d5dfe3d0)) -* **build:** - * move Appdata file installation to /usr/share/metainfo ([5db0bdd3](https://github.com/qTox/qTox/commit/5db0bdd381f0f08c5685501702f2a2eb9d2f5674)) - * add needed ffmpeg decoder to configuration ([8973a521](https://github.com/qTox/qTox/commit/8973a5216f49e65adc48d5fada8a574db598cced)) - * Add missing dependency for openSUSE ([f7e089f7](https://github.com/qTox/qTox/commit/f7e089f7a71c41ff31d311fe7148e57b5c6fb60a)) -* **chatform:** Broaden URL matching to include unicode ([e564b85e](https://github.com/qTox/qTox/commit/e564b85e3c485b283855bfdf00dfc0ec5427fad4)) -* **chatlog:** - * Match multi-character emoticons again ([9643e48e](https://github.com/qTox/qTox/commit/9643e48ef1d68948d52feec4e1be28c3ad61c0da)) - * parse multi-length emoji properly ([5df63f9c](https://github.com/qTox/qTox/commit/5df63f9c2e6d78f4799447b0a22cdb9fb70c3fea)) -* **chatwidget:** fix send file button not working ([af1aebfd](https://github.com/qTox/qTox/commit/af1aebfd1a7409ea821be2a616067561b62751c0)) -* **cmake:** - * fix platform extensions for windows ([7ad68e2f](https://github.com/qTox/qTox/commit/7ad68e2f43b458cd00ca27b9cfb20abf0b9ae46c)) - * add missing dependency ([423f0956](https://github.com/qTox/qTox/commit/423f095622824a34d081fb69bddd83cddf83ca03)) -* **core:** - * Adapt qtox to new conferences state change callback. ([1111949f](https://github.com/qTox/qTox/commit/1111949f450fb4fe63321386f7f452ee1663f07a)) - * Use new callback API for bitrate set ([d2deec7c](https://github.com/qTox/qTox/commit/d2deec7c554b3df651fe789dfb7964748329eff4)) - * Use new API for bitrate set ([2c8f03da](https://github.com/qTox/qTox/commit/2c8f03dada443e30d6189050c7cf6d42e01827c5)) -* **cpu:** Reduce CPU usage by avatar render ([8db61f96](https://github.com/qTox/qTox/commit/8db61f96ec78ac53479dd8db36eb192f6a1ddbcd)) -* **friendwidget:** Use queued connection to avoid removing 'this' ([9b4972e0](https://github.com/qTox/qTox/commit/9b4972e0459de2921370cda9de645eb64e37ecfc)) -* **group:** Show correct count of user on first creation ([0a590336](https://github.com/qTox/qTox/commit/0a590336b1467405a903464085dcdfc4474f93e6)) -* **install:** Fix gzip invalid usage ([266f63f6](https://github.com/qTox/qTox/commit/266f63f6dfb1869aa2339d48cdc9b52ece3597ce)) -* **l10n:** - * Correction of the translation into Russian ([3fb42b75](https://github.com/qTox/qTox/commit/3fb42b75d75bf6c0240748ffff368b912b14a838)) - * Correction of the translation into Russian ([9229fdd1](https://github.com/qTox/qTox/commit/9229fdd17e013a8bd60102648a200734890c2140)) -* **smiley:** change license of classic smileys to CC BY-SA 4.0 ([da7c12e2](https://github.com/qTox/qTox/commit/da7c12e20cac1ac7340b4bb4ec89f782e2e4a159)) -* **travis:** - * try working around Travis + gitstats issue ([4c980945](https://github.com/qTox/qTox/commit/4c98094551ff4a1e7377a206b72fedd470b8be96)) - * switch back to older Ubuntu Image ([378daeaa](https://github.com/qTox/qTox/commit/378daeaad4c5992a7acd2b650ff081d213556e10)) -* **video:** - * improve debug message ([ff2fc18b](https://github.com/qTox/qTox/commit/ff2fc18be164fcbc89bfd46d64f4b0096a97aee5)) - * choose first available resolution in preview automatically ([81522dea](https://github.com/qTox/qTox/commit/81522deabdc3fb11fd8d3e1feb59274a96583121)) - * use float framerates also for V4L2 ([a2927de2](https://github.com/qTox/qTox/commit/a2927de27d4776b52303e07c07ce89e8dadf86c5)) - * allow not integer framerates ([db7ee65d](https://github.com/qTox/qTox/commit/db7ee65d0efbe23a45e385a148b20701e521a5c5)) - * Fix square form of a video ([8de8c14a](https://github.com/qTox/qTox/commit/8de8c14a76908cf84a322a0bfd9e2c7ad2b4fa16)) -* **widget:** Fix status pic alignment ([d9118cfc](https://github.com/qTox/qTox/commit/d9118cfc71e2b030914187df7fd9fb3d98378cf1)) -* **windows:** %APPDATA -> %APPDATA% in template ([f53b8282](https://github.com/qTox/qTox/commit/f53b82825bf76be5a6793d18f2d102ed7b222313)) - -#### Features - -* Add the cmake option USE_CCACHE ([aa9cff31](https://github.com/qTox/qTox/commit/aa9cff315d659a7ca2010fb4791893abc8c5abdb)) -* update to the new c-toxcore 0.2.0 conferences api ([d3d81bbd](https://github.com/qTox/qTox/commit/d3d81bbdf3c198a7c1258c6ad6405c6ab61cedd4)) -* add hot keys for search ([ffb51e8a](https://github.com/qTox/qTox/commit/ffb51e8a0ea7dc3fb01f1f7650edc80b779a9be2)) -* optimise search in history ([18fa8a74](https://github.com/qTox/qTox/commit/18fa8a745bdafddc00ba2f577c36451f40edfd61)) -* add search in text in group chats ([7718734c](https://github.com/qTox/qTox/commit/7718734c9ab9705c1a1274b2a447611c1a2e22b4)) -* remove search button and add line in context menu ([8bb80c77](https://github.com/qTox/qTox/commit/8bb80c770c1d21d1bdfc03c3d0569fabe6535e8f)) -* edit load history for search ([de9c9061](https://github.com/qTox/qTox/commit/de9c9061175c97a9ee203d18a39e73f77544d5e6)) -* add text search ([b881d32d](https://github.com/qTox/qTox/commit/b881d32d1bddb7352b8d24e2442ef6277ff0d583)) -* add form for search ([863c46c7](https://github.com/qTox/qTox/commit/863c46c73d1a2fc677f9142ba8d7a2e8dc659c2a)) -* add a button to search ([47d9da98](https://github.com/qTox/qTox/commit/47d9da98cf6811a30d35a1204e5342a4f7f4bf94)) -* Prefere new line as message break ([3b52402f](https://github.com/qTox/qTox/commit/3b52402fa20d2d5418e129e5f001b626401a9ae5)) -* **UI:** new status icons for message notifications ([4288785d](https://github.com/qTox/qTox/commit/4288785d31e215bc379223577f7d4dd65664ed86)) -* **avatar:** Add outline hightlight on mouse hover ([bb26485d](https://github.com/qTox/qTox/commit/bb26485db6fed706f4ebccaffe35740394210032)) -* **groupchat:** mark blocked users with different color ([a729f2f8](https://github.com/qTox/qTox/commit/a729f2f8c00d29d2837b6e380f5af1b95c344bad)) -* **l10n:** - * add Macedonian translation ([1a06f85d](https://github.com/qTox/qTox/commit/1a06f85d3ccc91ff6f759a38534483fa40aaaa29)) - * add Macedonian translation using Weblate ([41420331](https://github.com/qTox/qTox/commit/414203310a30720e02e06719bfcafbb8bcff9018)) - * update French translation from Weblate ([a7e90969](https://github.com/qTox/qTox/commit/a7e9096919d4c0b89f061e8b77741d517f574838)) - * update Portuguese translation from Weblate ([3bad087b](https://github.com/qTox/qTox/commit/3bad087bbff2fbff4c4d543df1f96931784c93df)) - * update Portuguese translation from Weblate ([8c3be522](https://github.com/qTox/qTox/commit/8c3be5225f484469aed43dde04f03bc588ca2c15)) - -#### Performance - -* **widget:** don't save on setExpanded if categorywidget is unchanged Fix #4932 ([b9845e1d](https://github.com/qTox/qTox/commit/b9845e1d23eb23380f447692e3a813413e897c2d)) - - - - -## (2018-03-12) +## v1.14.0 (2018-03-12) #### Bug Fixes diff --git a/CMakeLists.txt b/CMakeLists.txt index f087f2c1a..c9aa7a49a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,7 @@ option(USE_FILTERAUDIO "Enable the echo canceling backend" ON) # AUTOUPDATE is currently broken and thus disabled option(AUTOUPDATE "Enable the auto updater" OFF) option(USE_CCACHE "Use ccache when available" ON) +option(SPELL_CHECK "Enable spell cheching support" ON) if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Debug) @@ -256,6 +257,10 @@ set(${PROJECT_NAME}_SOURCES src/core/toxfile.h src/core/toxid.cpp src/core/toxid.h + src/core/toxlogger.cpp + src/core/toxlogger.h + src/core/toxoptions.cpp + src/core/toxoptions.h src/core/toxpk.cpp src/core/toxpk.h src/core/toxstring.cpp @@ -269,6 +274,11 @@ set(${PROJECT_NAME}_SOURCES src/model/about/aboutfriend.cpp src/model/about/aboutfriend.h src/model/about/iaboutfriend.h + src/model/chatroom/chatroom.h + src/model/chatroom/friendchatroom.cpp + src/model/chatroom/friendchatroom.h + src/model/chatroom/groupchatroom.cpp + src/model/chatroom/groupchatroom.h src/model/contact.cpp src/model/contact.h src/model/friend.cpp diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 236aa89ca..d38c79f13 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,22 +1,22 @@ - [Filing an issue](#filing-an-issue) - - [Must read](#must-read) - - [Good to know](#good-to-know) + - [Must read](#must-read) + - [Good to know](#good-to-know) - [How to start contributing](#how-to-start-contributing) - - [Before you start…](#before-you-start) - - [Must read](#must-read) - - [Pull request](#pull-request) - - [How to open a pull request](#how-to-open-a-pull-reqeust) - - [How to deal with large amounts of merge conflicts](#merge-conflicts) - - [Git Commit Guidelines](#commit) - - [Commit Message Format](#commit-message-format) - - [Header](#header) - - [Type](#type) - - [Scope](#scope) - - [Subject](#subject) - - [Body](#body) - - [Reviewing](#reviewing) + - [Before you start…](#before-you-start) + - [Must read](#must-read) + - [Pull request](#pull-request) + - [How to open a pull request](#how-to-open-a-pull-request) + - [How to deal with large amounts of merge conflicts](#merge-conflicts) + - [Git Commit Guidelines](#commit) + - [Commit Message Format](#commit-message-format) + - [Header](#header) + - [Type](#type) + - [Scope](#scope) + - [Subject](#subject) + - [Body](#body) + - [Reviewing](#reviewing) - [Testing PRs](#testing-prs) - - [Git config](#git-config) +- [Git config](#git-config) - [Coding guidelines](#coding-guidelines) @@ -122,6 +122,9 @@ git clone git@github.com:/qTox.git # Add the "upstream" remote to be able to fetch from the qTox upstream repository: git remote add upstream https://github.com/qTox/qTox.git +# Fetch from the "upstream" repository +git fetch upstream + # Point the local "master" branch to the "upstream" repository git branch master --set-upstream-to=upstream/master ``` diff --git a/INSTALL.fa.md b/INSTALL.fa.md index 6f77201df..0831639cc 100644 --- a/INSTALL.fa.md +++ b/INSTALL.fa.md @@ -632,7 +632,7 @@ cd .. ```bash git clone https://github.com/toktok/c-toxcore.git toxcore cd toxcore -git checkout v0.2.1 +git checkout v0.2.3 autoreconf -if ./configure make -j$(nproc) diff --git a/INSTALL.md b/INSTALL.md index 0717197f6..f3a43863a 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -5,6 +5,7 @@ - [Arch](#arch-easy) - [Fedora](#fedora-easy) - [Gentoo](#gentoo-easy) + - [openSUSE](#opensuse-easy) - [Slackware](#slackware-easy) - [FreeBSD](#freebsd-easy) - [Install git](#install-git) @@ -22,13 +23,6 @@ - [Slackware](#slackware-other-deps) - [Ubuntu >=15.04](#ubuntu-other-deps) - [Ubuntu >=16.04](#ubuntu-other-1604-deps) - - [toxcore dependencies](#toxcore-dependencies) - - [Arch](#arch-toxcore) - - [Debian](#debian-toxcore) - - [Fedora](#fedora-toxcore) - - [openSUSE](#opensuse-toxcore) - - [Slackware](#slackware-toxcore) - - [Ubuntu >=15.04](#ubuntu-toxcore) - [sqlcipher](#sqlcipher) - [Compile toxcore](#compile-toxcore) - [Compile qTox](#compile-qtox) @@ -69,6 +63,18 @@ dependencies are missing. |---------|---------| | [Check] | >= 0.9 | +### Spell checking support + +| Name | Version | +|----------|---------| +| [sonnet] | >= 5.45 | + +Use `-DSPELL_CHECK=OFF` to disable it. + +**Note:** Specified version was tested and works well. You can try to use older +version, but in this case you may have some errors (including a complete lack +of spell check). + ### Linux #### Auto-away support @@ -155,6 +161,24 @@ To install: emerge qtox ``` + + +#### openSUSE + +qTox is available in openSUSE Factory. + +To install in openSUSE 15.0 or newer: + +```bash +zypper in qtox +``` + +To install in openSUSE 42.3: + +```bash +zypper ar -f https://download.opensuse.org/repositories/server:/messaging/openSUSE_Leap_42.3 server:messaging +zypper in qtox +``` @@ -263,7 +287,7 @@ corresponding parts. #### Arch Linux ```bash -sudo pacman -S --needed base-devel qt5 openal libxss qrencode ffmpeg +sudo pacman -S --needed base-devel qt5 openal libxss qrencode ffmpeg opus libvpx libsodium ``` @@ -275,18 +299,28 @@ sudo pacman -S --needed base-devel qt5 openal libxss qrencode ffmpeg ```bash sudo apt-get install \ + automake \ + autotools-dev \ build-essential \ + check \ + checkinstall \ cmake \ ffmpeg \ libavcodec-dev \ + libavdevice-dev \ libexif-dev \ libgdk-pixbuf2.0-dev \ libgtk2.0-dev \ + libkdeui5 \ libopenal-dev \ + libopus-dev \ libqrencode-dev \ libqt5opengl5-dev \ libqt5svg5-dev \ + libsodium-dev \ libsqlcipher-dev \ + libtool \ + libvpx-dev \ libxss-dev \ pkg-config \ qrencode \ @@ -310,20 +344,28 @@ have to compile it yourself, otherwise compiling qTox will fail.** sudo dnf groupinstall "Development Tools" "C Development Tools and Libraries" # (can also use sudo dnf install @"Development Tools") sudo dnf install \ + autoconf \ + automake \ + check \ + check-devel \ ffmpeg-devel \ gtk2-devel \ + kf5-sonnet \ libexif-devel \ - libXScrnSaver-devel \ + libsodium-devel \ libtool \ + libvpx-devel \ + libXScrnSaver-devel \ openal-soft-devel \ openssl-devel \ + opus-devel \ qrencode-devel \ - qt-creator \ - qt-devel \ - qt-doc \ qt5-linguist \ qt5-qtsvg \ qt5-qtsvg-devel \ + qt-creator \ + qt-devel \ + qt-doc \ qtsingleapplication \ sqlcipher \ sqlcipher-devel @@ -338,25 +380,32 @@ sudo dnf install \ ```bash sudo zypper install \ libexif-devel \ + libffmpeg-devel \ + libopus-devel \ libQt5Concurrent-devel \ + libqt5-linguist \ libQt5Network-devel \ libQt5OpenGL-devel \ - libQt5Xml-devel \ - libXScrnSaver-devel \ - libffmpeg-devel \ - libqt5-linguist \ libqt5-qtbase-common-devel \ libqt5-qtsvg-devel \ + libQt5Xml-devel \ + libsodium-devel \ + libvpx-devel \ + libXScrnSaver-devel \ openal-soft-devel \ patterns-openSUSE-devel_basis \ qrencode-devel \ - sqlcipher-devel + sqlcipher-devel \ + sonnet-devel ``` #### Slackware +List of all the toxcore dependencies and their SlackBuilds can be found +here: http://slackbuilds.org/repository/14.2/network/toxcore/ + List of all the qTox dependencies and their SlackBuilds can be found here: http://slackbuilds.org/repository/14.2/network/qTox/ @@ -367,7 +416,11 @@ http://slackbuilds.org/repository/14.2/network/qTox/ ```bash sudo apt-get install \ + automake \ + autotools-dev \ build-essential cmake \ + check \ + checkinstall \ libavcodec-ffmpeg-dev \ libavdevice-ffmpeg-dev \ libavfilter-ffmpeg-dev \ @@ -376,13 +429,18 @@ sudo apt-get install \ libgdk-pixbuf2.0-dev \ libglib2.0-dev \ libgtk2.0-dev \ + libkdeui5 \ libopenal-dev \ + libopus-dev \ libqrencode-dev \ libqt5opengl5-dev \ libqt5svg5-dev \ + libsodium-dev \ libsqlcipher-dev \ libswresample-ffmpeg-dev \ libswscale-ffmpeg-dev \ + libtool \ + libvpx-dev \ libxss-dev \ qrencode \ qt5-default \ @@ -405,13 +463,17 @@ sudo apt-get install \ libgdk-pixbuf2.0-dev \ libglib2.0-dev \ libgtk2.0-dev \ + libkdeui5 \ libopenal-dev \ + libopus-dev \ libqrencode-dev \ libqt5opengl5-dev \ libqt5svg5-dev \ + libsodium-dev \ libsqlcipher-dev \ libswresample-dev \ libswscale-dev \ + libvpx-dev \ libxss-dev \ qrencode \ qt5-default \ @@ -419,63 +481,6 @@ sudo apt-get install \ qttools5-dev ``` -### toxcore dependencies - -Install all of the toxcore dependencies. - - - -#### Arch Linux - -```bash -sudo pacman -S --needed opus libvpx libsodium -``` - - - -#### Debian - -```bash -sudo apt-get install libtool autotools-dev automake checkinstall check \ -libopus-dev libvpx-dev libsodium-dev libavdevice-dev -``` - - - -#### Fedora - -```bash -sudo dnf install libtool autoconf automake check check-devel libsodium-devel \ -opus-devel libvpx-devel -``` - - - -#### openSUSE - -```bash -sudo zypper install libsodium-devel libvpx-devel libopus-devel \ -patterns-openSUSE-devel_basis -``` - - - -#### Slackware - -List of all the toxcore dependencies and their SlackBuilds can be found -here: http://slackbuilds.org/repository/14.2/network/toxcore/ - - - - -#### Ubuntu >=15.04 - -```bash -sudo apt-get install libtool autotools-dev automake checkinstall check \ -libopus-dev libvpx-dev libsodium-dev -``` - - ### sqlcipher If you are not using an old version of Fedora, skip this section, and go @@ -494,12 +499,14 @@ cd .. ### Compile toxcore +Normally you don't want to do that, `bootstrap.sh` will do it for you. + Provided that you have all required dependencies installed, you can simply run: ```bash git clone https://github.com/toktok/c-toxcore.git toxcore cd toxcore -git checkout v0.2.2 +git checkout v0.2.3 cmake . make -j$(nproc) sudo make install @@ -720,9 +727,9 @@ Download the MinGW installer for Windows from [sourceforge.net](http://sourceforge.net/projects/mingw/files/Installer/). Make sure to install MSYS (a set of Unix tools for Windows). The following steps assume that MinGW is installed at `C:\MinGW`. If you decided to choose another -location, replace corresponding parts. Select `mingw-developer-toolkit`, -`mingw32-base`, `mingw32-gcc-g++`, `msys-base` and `mingw32-pthreads-w32` -packages using MinGW Installation Manager (`mingw-get.exe`). Check that the +location, replace corresponding parts. Select `mingw-developer-toolkit`, +`mingw32-base`, `mingw32-gcc-g++`, `msys-base` and `mingw32-pthreads-w32` +packages using MinGW Installation Manager (`mingw-get.exe`). Check that the version of MinGW, corresponds to the version of the QT component! #### Wget @@ -750,7 +757,7 @@ second box search for the `PATH` variable and press `Edit...`. The input box separated with a semicolon. Extend the input box by adding `;C:\MinGW\bin;C:\MinGW\msys\1.0\bin;C:\Program Files (x86)\CMake 2.8\bin;C:\Program Files (x86)\GnuWin32\bin`. The very first semicolon must only be added if it is missing. CMake may be added -by installer automatically. Make sure that paths containing alternative `sh`, +by installer automatically. Make sure that paths containing alternative `sh`, `bash` implementations such as `C:\Program Files\OpenSSH\bin` are at the end of `PATH` or build may fail. @@ -815,3 +822,4 @@ Switches: [sqlcipher]: https://www.zetetic.net/sqlcipher/ [toxcore]: https://github.com/TokTok/c-toxcore/ [filteraudio]: https://github.com/irungentoo/filter_audio +[sonnet]: https://github.com/KDE/sonnet diff --git a/MAINTAINING.md b/MAINTAINING.md index b6d62bf3e..73e66d7e5 100644 --- a/MAINTAINING.md +++ b/MAINTAINING.md @@ -110,42 +110,16 @@ translations, on the other, it lessened problems that were happening with To get translations into qTox: -1. Add Weblate: `git remote add weblate - https://hosted.weblate.org/git/tox/qtox/` -2. Fetch newest: `git fetch weblate` -3. Check what has been changed compared to master: `git log --no-merges - master..weblate/master` -4. Cherry-pick from the oldest commit. - - check if there are multiple commits from the same author for the same - translation. If there are, cherry-pick them accordingly: - - ``` - git cherry-pick - ``` - -5. If there were multiple commits, squash them into a single one, leaving only - a single `feat(l10n): …` line in the commit message. -6. Get rid of Weblate's formatting and amend the commit using - [`./tools/deweblate-translation-file.sh`], i.e.: - - ``` - ./tools/deweblate-translation-file.sh - ``` - -7. For translations that haven't yet been cherry-picked repeat steps 4-6. -8. Once done with cherry-picking, update all translation files, so that Weblate - would get newest strings that changed in qTox: - - ``` - ./tools/update-translation-files.sh ALL - ``` - -9. Once PR with translation gets merged, `Reset` Weblate to current `master`, - since without reset there would be a git conflict that would prevent Weblate - from getting new strings. - -**It's a good idea to lock translations on Weblate while they're in merge -process, so that no translation effort would be lost when resetting Weblate.** +1. Go to `https://hosted.weblate.org/projects/tox/qtox/#repository` and lock + the repository for translations. +2. Make sure you have git setup to automatically gpg sign commits +3. In the root of the qTox repository execute the script + `tools/update-weblate.sh` +4. Check what has been changed compared to master: `git diff upstream/master` +5. Checkout a new branch with e.g. `git checkout -b update_weblate` and open + a Pull Request for it on Github. +6. After the Pull Request has been merged, unlock Weblate and `reset` it to + master ## Adding new translations @@ -215,6 +189,12 @@ Follow steps for adding translations from Weblate up to step 5. Next: - Update download links on https://tox.chat to point to the new release. - Write a short blog post for https://github.com/qTox/blog/ and advertise the post on Tox IRC channels, popular Tox groups, reddit, or whatever other platforms. +- Open a PR to update the Flatpak manifest of our [Flathub repository] with the + changes from [`./flatpak/io.github.qtox.qTox.json`]. +- Comment to the PR with `bot, build` to execute a test build +- After the build passed for qTox on all architectures on + [the Flathub build bot], merge the PR into the master branch of our + [Flathub repository]. # How to become a maintainer? @@ -236,3 +216,6 @@ helping for a while, ask to be added to the `qTox` organization on GitHub. [`./tools/deweblate-translation-file.sh`]: /tools/deweblate-translation-file.sh [`./tools/create-tarball.sh`]: /tools/create-tarball.sh [`./tools/update-versions.sh`]: /tools/update-versions.sh +[Flathub repository]: https://github.com/flathub/io.github.qtox.qTox +[`./flatpak/io.github.qtox.qTox.json`]: flatpak/io.github.qtox.qTox.json +[the Flathub build bot]: https://flathub.org/builds/#/ diff --git a/README.md b/README.md index b05666920..f7a912792 100644 --- a/README.md +++ b/README.md @@ -45,9 +45,9 @@ while running on all major platforms. Windows | Linux | OS X | FreeBSD --------|-------|------|-------- -**[64 bit release]**| **[Arch]**, **[Fedora]**, **[Gentoo]** | **[Latest release]** | **[Package & Port]** -[32 bit release]| | [Building instructions] | -[64 bit][64nightly], [32 bit][32nightly] nigthly | [Other] | | +**[64 bit release]**| **[Arch]**, **[Fedora]**, **[Gentoo]**, **[openSUSE]** | **[Latest release]** | **[Package & Port]** +[32 bit release]|**[AppImage]**, [Flatpak] | [Building instructions] | +[64 bit][64nightly], [32 bit][32nightly] nigthly | [From Source] | | _**Bold** options are recommended._ @@ -70,6 +70,25 @@ Some of them are: * [Translating, it's easy] * [Reviewing and testing pull requests] – you don't need to be able to code to do that :wink: +* Take a task from our Roadmap below + +### Roadmap + +Currently qTox is under a feature freeze to clean up our codebase and tools. +During this time we want to prepare qTox for upcoming new features of toxcore. + +The next steps are: + + +* remove support for toxcore < 0.2.0 +* move all toxcore abstractions into their own subproject +* write basic tests for this Core +* format the code base +* rework our TravisCI setup for faster PR checks +* rethink our Issue tracker + +The current state is tracked in the [Code cleanup] project. + ### Screenshots @@ -117,17 +136,17 @@ Active qTox maintainers: ``` 7EB3 39FE 8817 47E7 01B7 D472 EBE3 6E66 A842 9B99 - Anthony Bilinski 3103 9166 FA90 2CA5 0D05 D608 5AF9 F2E2 9107 C727 – Diadlo -C7A2 552D 0B25 0F98 3827 742C 1332 03A3 AC39 9151 – initramfs CA92 21C5 389B 7C50 AA5F 7793 52A5 0775 BE13 DF17 - noavarice DA26 2CC9 3C0E 1E52 5AD2 1C85 9677 5D45 4B8E BF44 – sudden6 141C 880E 8BA2 5B19 8D0F 850F 7C13 2143 C1A3 A7D4 – tox-user 2880 C860 D95C 909D 3DA4 5C68 7E08 6DD6 6126 3264 – tux3 -BA78 83E2 2F9D 3594 5BA3 3760 5313 7C30 33F0 9008 – zetok ``` Past qTox maintainers: ``` +C7A2 552D 0B25 0F98 3827 742C 1332 03A3 AC39 9151 – initramfs +BA78 83E2 2F9D 3594 5BA3 3760 5313 7C30 33F0 9008 – zetok F365 8D0A 04A5 76A4 1072 FC0D 296F 0B76 4741 106C – agilob 1157 616B BD86 0C53 9926 F813 9591 A163 FF9B E04C – antis81 1D29 8BC7 25B7 BE82 65BA EAB9 3DB8 E053 15C2 20AA – Dubslow @@ -140,23 +159,26 @@ AED3 1134 9C23 A123 E5C4 AA4B 139C A045 3DA2 D773 ``` [#qtox@freenode]: https://webchat.freenode.net/?channels=qtox -[64 bit release]: https://github.com/qTox/qTox/releases/download/v1.15.0/setup-qtox-x86_64-release.exe -[32 bit release]: https://github.com/qTox/qTox/releases/download/v1.15.0/setup-qtox-i686-release.exe +[64 bit release]: https://github.com/qTox/qTox/releases/download/v1.16.3/setup-qtox-x86_64-release.exe +[32 bit release]: https://github.com/qTox/qTox/releases/download/v1.16.3/setup-qtox-i686-release.exe [32nightly]: https://build.tox.chat/view/qtox/job/qTox-cmake-nightly_build_windows_x86_release/lastSuccessfulBuild/artifact/qTox-cmake-nightly_build_windows_x86_release.zip [64nightly]: https://build.tox.chat/view/qtox/job/qTox-cmake-nightly_build_windows_x86-64_release/lastSuccessfulBuild/artifact/qTox-cmake-nightly_build_windows_x86-64_release.zip +[Flatpak]: https://github.com/qTox/qTox/releases/download/v1.16.1/qTox-v1.16.1.x86_64.flatpak +[AppImage]: https://github.com/qTox/qTox/releases/download/v1.16.1/qTox-v1.16.1.x86_64.AppImage [Arch]: /INSTALL.md#arch [Building instructions]: /INSTALL.md#os-x [Contributing]: /CONTRIBUTING.md#how-to-start-contributing [easy issues]: https://github.com/qTox/qTox/labels/E-easy -[Latest release]: https://github.com/qTox/qTox/releases/download/v1.15.0/qTox.dmg +[Latest release]: https://github.com/qTox/qTox/releases/download/v1.16.3/qTox.dmg [Fedora]: /INSTALL.md#fedora [Gentoo]: /INSTALL.md#gentoo +[openSUSE]: /INSTALL.md#opensuse [Install/Build]: /INSTALL.md [IRC logs]: https://github.com/qTox/qtox-irc-logs [issues that need help]: https://github.com/qTox/qTox/labels/help%20wanted [Jenkins builds]: https://build.tox.chat/ [Mailing list]: https://lists.tox.chat -[Other]: /INSTALL.md#linux +[From Source]: /INSTALL.md#linux [qTox-dev mailing list]: https://lists.tox.chat/listinfo/qtox-dev [Package & Port]: /INSTALL.md#freebsd-easy [Report bugs]: https://github.com/qTox/qTox/wiki/Writing-Useful-Bug-Reports @@ -169,3 +191,4 @@ AED3 1134 9C23 A123 E5C4 AA4B 139C A045 3DA2 D773 [Translating, it's easy]: /translations/README.md [User Manual]: /doc/user_manual_en.md [voice it in the issues that need it]: https://github.com/qTox/qTox/labels/I-feedback-wanted +[Code cleanup]: https://github.com/qTox/qTox/projects/3?fullscreen=true diff --git a/appimage/build-appimage.sh b/appimage/build-appimage.sh index 6ef5868eb..eb4ce24ca 100755 --- a/appimage/build-appimage.sh +++ b/appimage/build-appimage.sh @@ -22,6 +22,16 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. +# usage: ./appimage/build-appimage.sh [Debug] +# +# If [Debug] is set to "Debug" the container will run in interactive mode and +# stay open to poke around in the filesystem. + +readonly DEBUG="$1" + +# Fail out on error +set -exo pipefail + # This script should be run from the root of the repository if [ ! -f ./appimage/build-appimage.sh ]; then @@ -35,14 +45,26 @@ fi mkdir -p ./output -docker run --rm \ - -v $PWD:/qtox \ - -v $PWD/output:/output \ - debian:stretch-slim \ - /bin/bash -c "/qtox/appimage/build.sh" - +if [ "$DEBUG" == "Debug" ] +then + echo "Execute: /qtox/appimage/build.sh to start the build script" + echo "Execute: exit to leave the container" + + docker run --rm -it \ + -v $PWD:/qtox \ + -v $PWD/output:/output \ + debian:stretch-slim \ + /bin/bash +else + docker run --rm \ + -v $PWD:/qtox \ + -v $PWD/output:/output \ + debian:stretch-slim \ + /bin/bash -c "/qtox/appimage/build.sh" +fi + # use the version number in the name when building a tag on Travis CI if [ -n "$TRAVIS_TAG" ] then - mv ./output/*.AppImage ./output/qTox-"$TRAVIS_TAG".AppImage + mv ./output/*.AppImage ./output/qTox-"$TRAVIS_TAG".x86_64.AppImage fi diff --git a/bootstrap.sh b/bootstrap.sh index a01515b38..c43bf9fdd 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -31,8 +31,8 @@ readonly INSTALL_DIR=libs readonly BASE_DIR="${SCRIPT_DIR}/${INSTALL_DIR}" # versions of libs to checkout -readonly TOXCORE_VERSION="v0.2.2" -readonly SQLCIPHER_VERSION="v3.4.0" +readonly TOXCORE_VERSION="v0.2.3" +readonly SQLCIPHER_VERSION="v3.4.2" # directory names of cloned repositories readonly TOXCORE_DIR="libtoxcore-$TOXCORE_VERSION" @@ -93,32 +93,16 @@ install_toxcore() { "${BASE_DIR}/${TOXCORE_DIR}" pushd ${BASE_DIR}/${TOXCORE_DIR} - ./autogen.sh - # configure - if [[ $SYSTEM_WIDE = "false" ]] - then - ./configure --prefix=${BASE_DIR} - else - ./configure - fi - - # ensure A/V support is enabled - if ! grep -Fxq "BUILD_AV_TRUE=''" config.log - then - echo "A/V support of libtoxcore is disabled but required by qTox. Aborting." - echo "Maybe the dev-packages of libopus and libvpx are not installed?" - exit 1 - fi - - # compile - make -j $(nproc) - - # install + # compile and install if [[ $SYSTEM_WIDE = "false" ]] then + cmake . -DCMAKE_INSTALL_PREFIX=${BASE_DIR} + make -j $(nproc) make install else + cmake . + make -j $(nproc) sudo make install sudo ldconfig fi diff --git a/cmake/Dependencies.cmake b/cmake/Dependencies.cmake index f77190ef4..ac3fa5de8 100644 --- a/cmake/Dependencies.cmake +++ b/cmake/Dependencies.cmake @@ -112,6 +112,16 @@ search_dependency(LIBSWSCALE PACKAGE libswscale) search_dependency(SQLCIPHER PACKAGE sqlcipher) search_dependency(VPX PACKAGE vpx) +if(${SPELL_CHECK}) + find_package(KF5Sonnet) + if(KF5Sonnet_FOUND) + add_definitions(-DSPELL_CHECKING) + add_dependency(KF5::SonnetUi) + else() + message(WARNING "Sonnet not found. Spell checking will be disabled.") + endif() +endif() + # Try to find cmake toxcore libraries if(WIN32) search_dependency(TOXCORE PACKAGE toxcore OPTIONAL STATIC_PACKAGE) diff --git a/doc/TCS_state.md b/doc/TCS_state.md new file mode 100644 index 000000000..80416dbc2 --- /dev/null +++ b/doc/TCS_state.md @@ -0,0 +1,55 @@ +# qTox TCS Support state + +All section number refer to a section in the [TCS document]. + +|Symbol|Description| +|------|-----------| +|Y| qTox implements this point| +|N| qTox is missing this point, add issue number| +|U| Unknown if qTox supports this point| + +|TCS Section | qTox State | +|------------|------------| +| 1.0.1 |U| +| 1.0.2 |U| +| 1.0.3 |U| +| 1.0.4 |U| +| 2.1.1 |U| +| 2.1.2 |U| +| 2.2.1 |U| +| 2.2.2 |U| +| 2.2.3 |U| +| 2.2.4 |U| +| 2.2.5 |U| +| 2.2.6 |U| +| 2.2.7 |U| +| 2.2.8 |U| +| 2.2.9 |U| +| 2.2.10 |U| +| 2.2.11 |U| +| 3.1.1 |U| +| 3.1.2 |U| +| 3.2.1 |U| +| 3.2.2 |U| +| 3.2.3 |U| +| 3.3.1 |U| +| 3.3.2 |U| +| 3.3.3 |U| +| 3.4.1 |U| +| 3.4.2 |U| +| 3.3.1 |U| +| 3.3.2 |U| +| 3.3.3 |U| +| 3.3.4 |U| +| 3.3.5 |U| +| 4.0.1 |U| +| 4.0.2 |U| +| 5.0.1 |U| +| 5.0.2 |U| +| 5.0.3 |U| +| 5.0.4 |U| +| 5.1.1 |U| +| 5.1.2 |U| +| 5.1.3 |U| + +[TCS document]: https://tox.gitbooks.io/tox-client-standard/content/ diff --git a/docker/Dockerfile.debian b/docker/Dockerfile.debian new file mode 100644 index 000000000..e1a41b128 --- /dev/null +++ b/docker/Dockerfile.debian @@ -0,0 +1,46 @@ +FROM debian:stretch + +RUN apt-get update && \ + apt-get -y --force-yes install \ + automake \ + autotools-dev \ + build-essential \ + check \ + checkinstall \ + cmake \ + ffmpeg \ + git \ + libavcodec-dev \ + libavdevice-dev \ + libexif-dev \ + libgdk-pixbuf2.0-dev \ + libgtk2.0-dev \ + libopenal-dev \ + libopus-dev \ + libqrencode-dev \ + libqt5opengl5-dev \ + libqt5svg5-dev \ + libsodium-dev \ + libsqlcipher-dev \ + libtool \ + libvpx-dev \ + libxss-dev \ + pkg-config \ + qrencode \ + qt5-default \ + qttools5-dev \ + qttools5-dev-tools \ + yasm + +RUN git clone https://github.com/toktok/c-toxcore.git /toxcore +WORKDIR /toxcore +RUN git checkout v0.2.2 && \ + cmake . && \ + cmake --build . && \ + make install && \ + echo '/usr/local/lib/' >> /etc/ld.so.conf.d/locallib.conf && \ + ldconfig + +COPY . /qtox +WORKDIR /qtox +RUN cmake . && cmake --build . diff --git a/docker/Dockerfile.ubuntu b/docker/Dockerfile.ubuntu new file mode 100644 index 000000000..3d14ad46e --- /dev/null +++ b/docker/Dockerfile.ubuntu @@ -0,0 +1,43 @@ +FROM ubuntu:16.04 + +RUN apt-get update && \ + apt-get -y --force-yes install \ + build-essential \ + cmake \ + git \ + libavcodec-dev \ + libavdevice-dev \ + libavfilter-dev \ + libavutil-dev \ + libexif-dev \ + libgdk-pixbuf2.0-dev \ + libglib2.0-dev \ + libgtk2.0-dev \ + libopenal-dev \ + libopus-dev \ + libqrencode-dev \ + libqt5opengl5-dev \ + libqt5svg5-dev \ + libsodium-dev \ + libsqlcipher-dev \ + libswresample-dev \ + libswscale-dev \ + libvpx-dev \ + libxss-dev \ + qrencode \ + qt5-default \ + qttools5-dev-tools \ + qttools5-dev + +RUN git clone https://github.com/toktok/c-toxcore.git /toxcore +WORKDIR /toxcore +RUN git checkout v0.2.2 && \ + cmake . && \ + cmake --build . && \ + make install && \ + echo '/usr/local/lib/' >> /etc/ld.so.conf.d/locallib.conf && \ + ldconfig + +COPY . /qtox +WORKDIR /qtox +RUN cmake . && cmake --build . diff --git a/docker/build-debian.sh b/docker/build-debian.sh new file mode 100755 index 000000000..d15c4a476 --- /dev/null +++ b/docker/build-debian.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +cd "$(dirname "$0")/.." +docker build . -f docker/Dockerfile.debian -t qtox +cd - diff --git a/docker/build-ubuntu.sh b/docker/build-ubuntu.sh new file mode 100755 index 000000000..87ed7c357 --- /dev/null +++ b/docker/build-ubuntu.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +cd "$(dirname "$0")/.." +docker build . -f docker/Dockerfile.ubuntu -t qtox +cd - diff --git a/docker/start-qtox.sh b/docker/start-qtox.sh new file mode 100755 index 000000000..8162f43b3 --- /dev/null +++ b/docker/start-qtox.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +XSOCK=/tmp/.X11-unix +XAUTH=/tmp/.docker.xauth +touch $XAUTH +xauth nlist $DISPLAY | sed -e 's/^..../ffff/' | xauth -f $XAUTH nmerge - +docker run -ti --rm -v $XSOCK:$XSOCK -v $XAUTH:$XAUTH -e DISPLAY=$DISPLAY -e XAUTHORITY=$XAUTH qtox ./qtox diff --git a/flatpak/build-flatpak.sh b/flatpak/build-flatpak.sh new file mode 100755 index 000000000..b6b74bb66 --- /dev/null +++ b/flatpak/build-flatpak.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash + +# SPDX-License-Identifier: GPL-3.0+ +# +# Copyright © 2018 by The qTox Project Contributors +# +# This script should be run from the root of the repository + +# usage: ./flatpak/build-flatpak.sh [Debug] +# +# If [Debug] is set to "Debug" the container will run in interactive mode and +# stay open to poke around in the filesystem. + +readonly DEBUG="$1" + +# Fail out on error +set -exo pipefail + +if [ ! -f ./flatpak/build-flatpak.sh ]; then + echo "" + echo "You are attempting to run the build-flatpak.sh from a wrong directory." + echo "If you wish to run this script, you'll have to have" + echo "the repository root directory as the working directory." + echo "" + exit 1 +fi + +mkdir -p ./output + +if [ "$DEBUG" == "Debug" ] +then + echo "Execute: /qtox/appimage/build.sh to start the build script" + echo "Execute: exit to leave the container" + + docker run --rm --privileged -it \ + -v $PWD:/qtox \ + -v $PWD/output:/output \ + debian:stretch-slim \ + /bin/bash +else + docker run --rm --privileged \ + -v $PWD:/qtox \ + -v $PWD/output:/output \ + debian:stretch-slim \ + /bin/bash -c "/qtox/flatpak/build.sh" +fi + +# use the version number in the name when building a tag on Travis CI +if [ -n "$TRAVIS_TAG" ] +then + mv ./output/*.flatpak ./output/qTox-"$TRAVIS_TAG".x86_64.flatpak +fi diff --git a/flatpak/build.sh b/flatpak/build.sh new file mode 100755 index 000000000..262bb32ef --- /dev/null +++ b/flatpak/build.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash + +# SPDX-License-Identifier: GPL-3.0+ +# +# Copyright © 2018 by The qTox Project Contributors + +# Fail out on error +set -exuo pipefail + +# directory paths +readonly QTOX_SRC_DIR="/qtox" +readonly OUTPUT_DIR="/output" +readonly BUILD_DIR="/build" +readonly QTOX_BUILD_DIR="$BUILD_DIR"/qtox +readonly FP_BUILD_DIR="$BUILD_DIR"/flatpak +readonly APT_FLAGS="-y --no-install-recommends" +# flatpak manifest download location +readonly MANIFEST_FILE="flatpak/io.github.qtox.qTox.json" +# directory containing necessary patches +readonly PATCH_DIR="flatpak/patches" +# use multiple cores when building +export MAKEFLAGS="-j$(nproc)" + +# add backports repo, needed for a recent enough flatpak +echo "deb http://ftp.debian.org/debian stretch-backports main" > /etc/apt/sources.list.d/stretch-backports.list + +# Get packages +apt-get update +apt-get install $APT_FLAGS ca-certificates git elfutils wget xz-utils patch bzip2 + +# install recent flatpak packages +apt-get install $APT_FLAGS -t stretch-backports flatpak flatpak-builder + +# create build directory +mkdir -p "$BUILD_DIR" +cd "$BUILD_DIR" + +# copy qtox source +cp -r "$QTOX_SRC_DIR" "$QTOX_BUILD_DIR" +cd "$QTOX_BUILD_DIR" + +# create flatpak build directory +mkdir -p "$FP_BUILD_DIR" +cd "$FP_BUILD_DIR" + +# Add 'https://flathub.org' remote: +flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo + +# Build the qTox flatpak +flatpak-builder --disable-rofiles-fuse --install-deps-from=flathub --force-clean --repo=tox-repo qTox-flatpak "$QTOX_BUILD_DIR"/flatpak/io.github.qtox.qTox.json + +# Create a bundle for distribution +flatpak build-bundle tox-repo "$OUTPUT_DIR"/qtox.flatpak io.github.qtox.qTox + +# Chmod since everything is root:root +chmod 755 -R "$OUTPUT_DIR" diff --git a/flatpak/io.github.qtox.qTox.json b/flatpak/io.github.qtox.qTox.json new file mode 100644 index 000000000..bc03f4718 --- /dev/null +++ b/flatpak/io.github.qtox.qTox.json @@ -0,0 +1,157 @@ +{ + "app-id": "io.github.qtox.qTox", + "runtime": "org.kde.Platform", + "sdk": "org.kde.Sdk", + "runtime-version": "5.11", + "command": "qtox", + "rename-icon": "qtox", + "finish-args": [ + "--share=network", + "--socket=pulseaudio", + "--socket=wayland", + "--socket=x11", + "--share=ipc", + "--filesystem=xdg-desktop", + "--filesystem=xdg-documents", + "--filesystem=xdg-download", + "--filesystem=xdg-music", + "--filesystem=xdg-pictures", + "--filesystem=xdg-videos", + "--filesystem=/media", + "--device=all" + ], + "build-options": { + "cflags": "-O3 -DSQLITE_HAS_CODEC", + "cxxflags": "-O3" + }, + "cleanup": [ + "/include", + "/lib/pkgconfig", + "/share/man" + ], + "modules": [ + { + "name": "libv4l2", + "config-opts": + [ + "--disable-libdvbv5", + "--disable-v4l-utils", + "--disable-qv4l2" + ], + "sources": + [ + { + "type": "archive", + "url": "https://linuxtv.org/downloads/v4l-utils/v4l-utils-1.14.2.tar.bz2", + "sha256" : "e6b962c4b1253cf852c31da13fd6b5bb7cbe5aa9e182881aec55123bae680692" + } + ] + }, + { + "name": "ffmpeg", + "config-opts": [ + "--disable-everything", + "--enable-gpl", + "--disable-debug", + "--enable-optimizations", + "--enable-shared", + "--disable-programs", + "--disable-protocols", + "--disable-doc", + "--disable-avfilter", + "--disable-avresample", + "--disable-filters", + "--disable-iconv", + "--disable-network", + "--disable-postproc", + "--enable-libv4l2", + "--enable-indev=v4l2", + "--enable-libxcb", + "--enable-indev=xcbgrab", + "--enable-demuxer=h264", + "--enable-demuxer=mjpeg", + "--enable-parser=h264", + "--enable-parser=mjpeg", + "--enable-decoder=h264", + "--enable-decoder=mjpeg", + "--enable-decoder=rawvideo" + ], + "sources": [ + { + "type": "archive", + "url": "https://ffmpeg.org/releases/ffmpeg-4.0.1.tar.bz2", + "sha256" : "7ee591b1e7fb66f055fa514fbd5d98e092ddb3dbe37d2e50ea5c16ab51c21670" + } + ] + }, + { + "name": "sqlcipher", + "rm-configure": true, + "config-opts": [ + "--enable-tempstore=yes", + "--disable-tcl" + ], + "sources": [ + { + "type": "git", + "url": "https://github.com/sqlcipher/sqlcipher", + "tag": "v3.4.2", + "commit": "c6f709fca81c910ba133aaf6330c28e01ccfe5f8", + "disable-fsckobjects" : true + }, + { + "type": "script", + "dest-filename": "autogen.sh", + "commands": [ + "AUTOMAKE=\"automake --foreign\" autoreconf -vfi" + ] + } + ] + }, + { + "name": "libsodium", + "sources": [ + { + "type": "git", + "url": "https://github.com/jedisct1/libsodium", + "tag": "1.0.16", + "commit": "675149b9b8b66ff44152553fb3ebf9858128363d" + } + ] + }, + { + "name": "libqrencode", + "sources": [ + { + "type": "git", + "url": "https://github.com/fukuchi/libqrencode", + "tag": "v4.0.2", + "commit": "59ee597f913fcfda7a010a6e106fbee2595f68e4" + } + ] + }, + { + "name": "c-toxcore", + "buildsystem": "cmake-ninja", + "sources": [ + { + "type": "git", + "url": "https://github.com/toktok/c-toxcore", + "tag": "v0.2.3", + "commit": "ae7899cab8104fa3c3078a3e61ddfa58a826e39a" + } + ] + }, + { + "name": "qTox", + "buildsystem": "cmake-ninja", + "sources": [ + { + "type": "dir", + "path": "/build/qtox/" + } + ] + } + ] +} + diff --git a/osx/info.plist b/osx/info.plist index 2c17665a8..e91fedaba 100644 --- a/osx/info.plist +++ b/osx/info.plist @@ -65,7 +65,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.15.0 + 1.16.3 CFBundleSignature toxq CFBundleURLTypes @@ -84,7 +84,7 @@ CFBundleVersion - 1.15.0 + 1.16.3 NSPrincipalClass NSApplication UTImportedTypeDeclarations diff --git a/osx/qTox-Mac-Deployer-ULTIMATE.sh b/osx/qTox-Mac-Deployer-ULTIMATE.sh index 132b48985..9ce616656 100755 --- a/osx/qTox-Mac-Deployer-ULTIMATE.sh +++ b/osx/qTox-Mac-Deployer-ULTIMATE.sh @@ -32,7 +32,8 @@ then MAIN_DIR="${TRAVIS_BUILD_DIR}" QTOX_DIR="${MAIN_DIR}" else - MAIN_DIR="/Users/${USER}" + # the directory which qTox is cloned in, wherever that is + MAIN_DIR="$(dirname $(readlink -f $0))/../.." QTOX_DIR="${MAIN_DIR}/qTox${SUBGIT}" fi QT_DIR="/usr/local/Cellar/qt5" # Folder name of QT install @@ -70,11 +71,10 @@ build_toxcore() { [[ $TRAVIS != true ]] \ && sleep 3 - autoreconf -if - + mkdir _build && cd _build + fcho "Starting cmake ..." #Make sure the correct version of libsodium is used - ./configure --with-libsodium-headers="${LS_DIR_VER}/include/" --with-libsodium-libs="${LS_DIR_VER}/lib/" --prefix="${LIB_INSTALL_PREFIX}" - + cmake -DBOOTSTRAP_DAEMON=OFF -DLIBSODIUM_CFLAGS="-I${LS_DIR_VER}/include/" -DLIBSODIUM_LDFLAGS="L${LS_DIR_VER}/lib/" -DCMAKE_INSTALL_PREFIX="${LIB_INSTALL_PREFIX}" .. make clean &> /dev/null fcho "Compiling toxcore." make > /dev/null || exit 1 @@ -112,7 +112,7 @@ install() { if [[ $TRAVIS != true ]] then sleep 3 - brew install git wget libtool autoconf automake pkgconfig + brew install git wget libtool cmake pkgconfig fi brew install check libvpx opus libsodium @@ -127,7 +127,7 @@ install() { git pull else fcho "Cloning Toxcore git ... " - git clone --branch v0.2.2 --depth=1 https://github.com/toktok/c-toxcore "$TOXCORE_DIR" + git clone --branch v0.2.3 --depth=1 https://github.com/toktok/c-toxcore "$TOXCORE_DIR" fi # qTox if [[ $TRAVIS = true ]] @@ -167,7 +167,11 @@ install() { else brew install cmake fi - brew install ffmpeg libexif qrencode qt5 sqlcipher openal-soft + + # needed for kf5-sonnet + brew tap kde-mac/kde + + brew install ffmpeg libexif qrencode qt5 sqlcipher openal-soft kf5-sonnet fcho "Cloning filter_audio ... " git clone --branch v0.0.1 --depth=1 https://github.com/irungentoo/filter_audio "$FILTERAUIO_DIR" diff --git a/simple_make.sh b/simple_make.sh index 7f8ab359e..c845123cd 100755 --- a/simple_make.sh +++ b/simple_make.sh @@ -1,11 +1,11 @@ #!/usr/bin/env bash -set -eu -o pipefail - # additional flags for apt-get, used for CI readonly APT_FLAGS=$1 readonly WITHOUT_SQLCIPHER=$2 +set -eu -o pipefail + apt_install() { local apt_packages=( automake @@ -35,7 +35,7 @@ apt_install() { ) if [ "$WITHOUT_SQLCIPHER" != "True" ]; then - apt_packages+=libsqlcipher-dev + apt_packages+=("libsqlcipher-dev") fi sudo apt-get install $APT_FLAGS "${apt_packages[@]}" diff --git a/src/audio/backend/openal2.cpp b/src/audio/backend/openal2.cpp index 094a183af..3d9907116 100644 --- a/src/audio/backend/openal2.cpp +++ b/src/audio/backend/openal2.cpp @@ -318,16 +318,23 @@ void OpenAL2::doOutput() } ALdouble latency[2] = {0}; - alGetSourcedvSOFT(alProxySource, AL_SEC_OFFSET_LATENCY_SOFT, latency); + if (echoCancelSupported) { + alGetSourcedvSOFT(alProxySource, AL_SEC_OFFSET_LATENCY_SOFT, latency); + } + checkAlError(); ALshort outBuf[AUDIO_FRAME_SAMPLE_COUNT_PER_CHANNEL] = {0}; - alcMakeContextCurrent(alProxyContext); - alcRenderSamplesSOFT(alProxyDev, outBuf, AUDIO_FRAME_SAMPLE_COUNT_PER_CHANNEL); - checkAlcError(alProxyDev); + if (echoCancelSupported) { + alcMakeContextCurrent(alProxyContext); + alcRenderSamplesSOFT(alProxyDev, outBuf, AUDIO_FRAME_SAMPLE_COUNT_PER_CHANNEL); + checkAlcError(alProxyDev); + + alcMakeContextCurrent(alOutContext); + } - alcMakeContextCurrent(alOutContext); alBufferData(bufids[0], AL_FORMAT_MONO16, outBuf, AUDIO_FRAME_SAMPLE_COUNT_PER_CHANNEL * 2, AUDIO_SAMPLE_RATE); + alSourceQueueBuffers(alProxySource, 1, bufids); // initialize echo canceler if supported diff --git a/src/chatlog/chatlog.cpp b/src/chatlog/chatlog.cpp index ff26e85f7..f21eecc8f 100644 --- a/src/chatlog/chatlog.cpp +++ b/src/chatlog/chatlog.cpp @@ -197,8 +197,14 @@ void ChatLog::mousePressEvent(QMouseEvent* ev) clearSelection(); } - // Counts only single clicks and first click of doule click - clickCount++; + if (lastClickButton == ev->button()) { + // Counts only single clicks and first click of doule click + clickCount++; + } + else { + clickCount = 1; // restarting counter + lastClickButton = ev->button(); + } lastClickPos = ev->pos(); // Triggers on odd click counts @@ -477,8 +483,14 @@ void ChatLog::mouseDoubleClickEvent(QMouseEvent* ev) emit selectionChanged(); } - // Counts the second click of double click - clickCount++; + if (lastClickButton == ev->button()) { + // Counts the second click of double click + clickCount++; + } + else { + clickCount = 1; // restarting counter + lastClickButton = ev->button(); + } lastClickPos = ev->pos(); // Triggers on even click counts diff --git a/src/chatlog/chatlog.h b/src/chatlog/chatlog.h index 56d0dda54..d98a86a6e 100644 --- a/src/chatlog/chatlog.h +++ b/src/chatlog/chatlog.h @@ -155,6 +155,7 @@ private: AutoScrollDirection selectionScrollDir = NoDirection; int clickCount = 0; QPoint lastClickPos; + Qt::MouseButton lastClickButton; // worker vars int workerLastIndex = 0; diff --git a/src/chatlog/chatmessage.cpp b/src/chatlog/chatmessage.cpp index 136fc0703..81a6e595d 100644 --- a/src/chatlog/chatmessage.cpp +++ b/src/chatlog/chatmessage.cpp @@ -124,7 +124,7 @@ ChatMessage::Ptr ChatMessage::createChatInfoMessage(const QString& rawMessage, msg->addColumn(new Image(QSize(18, 18), img), ColumnFormat(NAME_COL_WIDTH, ColumnFormat::FixedSize, ColumnFormat::Right)); - msg->addColumn(new Text("" + text + "", baseFont, false, ""), + msg->addColumn(new Text("" + text + "", baseFont, false, text), ColumnFormat(1.0, ColumnFormat::VariableSize, ColumnFormat::Left)); msg->addColumn(new Timestamp(date, Settings::getInstance().getTimestampFormat(), baseFont), ColumnFormat(TIME_COL_WIDTH, ColumnFormat::FixedSize, ColumnFormat::Right)); diff --git a/src/core/core.cpp b/src/core/core.cpp index f82a5d2e2..7751767d7 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -22,11 +22,12 @@ #include "corefile.h" #include "src/core/coreav.h" #include "src/core/icoresettings.h" +#include "src/core/toxlogger.h" +#include "src/core/toxoptions.h" #include "src/core/toxstring.h" #include "src/model/groupinvite.h" #include "src/nexus.h" #include "src/persistence/profile.h" -#include "src/widget/gui.h" #include #include @@ -35,343 +36,46 @@ #include #include -#include +#include const QString Core::TOX_EXT = ".tox"; -QThread* Core::coreThread{nullptr}; - -static const int MAX_PROXY_ADDRESS_LENGTH = 255; #define MAX_GROUP_MESSAGE_LEN 1024 -Core::Core(QThread* CoreThread, Profile& profile, const ICoreSettings* const settings) +#define ASSERT_CORE_THREAD assert(QThread::currentThread() == coreThread.get()) + +Core::Core(QThread* coreThread) : tox(nullptr) , av(nullptr) - , profile(profile) - , ready(false) - , s{settings} + , coreLoopLock(new QMutex(QMutex::Recursive)) + , coreThread(coreThread) { - coreThread = CoreThread; - toxTimer = new QTimer(this); - toxTimer->setSingleShot(true); - connect(toxTimer, &QTimer::timeout, this, &Core::process); - s->connectTo_dhtServerListChanged([=](const QList& servers){ - process(); - }); -} - -void Core::deadifyTox() -{ - if (av) { - delete av; - av = nullptr; - } - - if (tox) { - tox_kill(tox); - tox = nullptr; - } + toxTimer.setSingleShot(true); + connect(&this->toxTimer, &QTimer::timeout, this, &Core::process); } Core::~Core() { - if (coreThread->isRunning()) { - if (QThread::currentThread() == coreThread) { - killTimers(false); - } else { - QMetaObject::invokeMethod(this, "killTimers", Qt::BlockingQueuedConnection, - Q_ARG(bool, false)); - } + if (QThread::currentThread() == coreThread.get()) { + killTimers(); + } else { + // ensure the timer is stopped, even if not called from this thread + QMetaObject::invokeMethod(this, "killTimers", Qt::BlockingQueuedConnection); } coreThread->exit(0); - if (QThread::currentThread() != coreThread) { - while (coreThread->isRunning()) { - qApp->processEvents(); - coreThread->wait(500); - } - } - deadifyTox(); + // need to reset av first, because it uses tox + av.reset(); + tox.reset(); } /** - * @brief Returns the global widget's Core instance + * @brief Registers all toxcore callbacks + * @param tox Tox instance to register the callbacks on */ -Core* Core::getInstance() +void Core::registerCallbacks(Tox* tox) { - return Nexus::getCore(); -} - -const CoreAV* Core::getAv() const -{ - return av; -} - -CoreAV* Core::getAv() -{ - return av; -} - -namespace { - -class ToxOptionsDeleter -{ -public: - void operator()(Tox_Options* options) - { - tox_options_free(options); - } -}; - -using ToxOptionsPtr = std::unique_ptr; - -/** - * @brief Map TOX_LOG_LEVEL to a string - * @param level log level - * @return Descriptive string for the log level - */ -static QString getToxLogLevel(TOX_LOG_LEVEL level) { - switch (level) { - case TOX_LOG_LEVEL_TRACE: - return QLatin1Literal("TRACE"); - case TOX_LOG_LEVEL_DEBUG: - return QLatin1Literal("DEBUG"); - case TOX_LOG_LEVEL_INFO: - return QLatin1Literal("INFO "); - case TOX_LOG_LEVEL_WARNING: - return QLatin1Literal("WARN "); - case TOX_LOG_LEVEL_ERROR: - return QLatin1Literal("ERROR"); - default: - // Invalid log level - return QLatin1Literal("INVAL"); - } -} - -/** - * @brief Log message handler for toxcore log messages - * @note See tox.h for the parameter definitions - */ -void onLogMessage(Tox *tox, TOX_LOG_LEVEL level, const char *file, uint32_t line, - const char *func, const char *message, void *user_data) -{ - // for privacy, make the path relative to the c-toxcore source directory - const QRegularExpression pathCleaner(QLatin1Literal{"[\\s|\\S]*c-toxcore."}); - const QString cleanPath = QString{file}.remove(pathCleaner); - - const QString logMsg = getToxLogLevel(level) % QLatin1Literal{":"} % cleanPath - % QLatin1Literal{":"} % func % QStringLiteral(":%1: ").arg(line) - % message; - - switch (level) { - case TOX_LOG_LEVEL_TRACE: - case TOX_LOG_LEVEL_DEBUG: - case TOX_LOG_LEVEL_INFO: - qDebug() << logMsg; - break; - case TOX_LOG_LEVEL_WARNING: - case TOX_LOG_LEVEL_ERROR: - qWarning() << logMsg; - } -} - -/** - * @brief Initializes Tox_Options instance - * @param savedata Previously saved Tox data - * @return Tox_Options instance needed to create Tox instance - */ -ToxOptionsPtr initToxOptions(const QByteArray& savedata, const ICoreSettings* s) -{ - // IPv6 needed for LAN discovery, but can crash some weird routers. On by default, can be - // disabled in options. - const bool enableIPv6 = s->getEnableIPv6(); - const bool forceTCP = s->getForceTCP(); - // LAN requiring UDP is a toxcore limitation, ideally wouldn't be related - const bool enableLanDiscovery = s->getEnableLanDiscovery() && !forceTCP; - ICoreSettings::ProxyType proxyType = s->getProxyType(); - quint16 proxyPort = s->getProxyPort(); - QString proxyAddr = s->getProxyAddr(); - QByteArray proxyAddrData = proxyAddr.toUtf8(); - - if (!enableLanDiscovery) { - qWarning() << "Core starting without LAN discovery. Peers can only be found through DHT."; - } - if (enableIPv6) { - qDebug() << "Core starting with IPv6 enabled"; - } else if(enableLanDiscovery) { - qWarning() << "Core starting with IPv6 disabled. LAN discovery may not work properly."; - } - - ToxOptionsPtr toxOptions = ToxOptionsPtr(tox_options_new(nullptr)); - // register log first, to get messages as early as possible - tox_options_set_log_callback(toxOptions.get(), onLogMessage); - - tox_options_set_ipv6_enabled(toxOptions.get(), enableIPv6); - tox_options_set_udp_enabled(toxOptions.get(), !forceTCP); - tox_options_set_local_discovery_enabled(toxOptions.get(), enableLanDiscovery); - tox_options_set_start_port(toxOptions.get(), 0); - tox_options_set_end_port(toxOptions.get(), 0); - - // No proxy by default - tox_options_set_proxy_type(toxOptions.get(), TOX_PROXY_TYPE_NONE); - tox_options_set_proxy_host(toxOptions.get(), nullptr); - tox_options_set_proxy_port(toxOptions.get(), 0); - tox_options_set_savedata_type(toxOptions.get(), !savedata.isNull() ? TOX_SAVEDATA_TYPE_TOX_SAVE : TOX_SAVEDATA_TYPE_NONE); - tox_options_set_savedata_data(toxOptions.get(), reinterpret_cast(savedata.data()), savedata.size()); - - if (proxyType != ICoreSettings::ProxyType::ptNone) { - if (proxyAddr.length() > MAX_PROXY_ADDRESS_LENGTH) { - qWarning() << "proxy address" << proxyAddr << "is too long"; - } else if (!proxyAddr.isEmpty() && proxyPort > 0) { - qDebug() << "using proxy" << proxyAddr << ":" << proxyPort; - // protection against changings in TOX_PROXY_TYPE enum - if (proxyType == ICoreSettings::ProxyType::ptSOCKS5) { - tox_options_set_proxy_type(toxOptions.get(), TOX_PROXY_TYPE_SOCKS5); - } else if (proxyType == ICoreSettings::ProxyType::ptHTTP) { - tox_options_set_proxy_type(toxOptions.get(), TOX_PROXY_TYPE_HTTP); - } - - tox_options_set_proxy_host(toxOptions.get(), proxyAddrData.data()); - tox_options_set_proxy_port(toxOptions.get(), proxyPort); - } - } - - return toxOptions; -} - -} // namespace - -/** - * @brief Creates Tox instance from previously saved data - * @param savedata Previously saved Tox data - null, if new profile was created - */ -void Core::makeTox(QByteArray savedata) -{ - ToxOptionsPtr toxOptions = initToxOptions(savedata, s); - if (toxOptions == nullptr) { - qCritical() << "could not allocate Tox Options data structure"; - emit failedToStart(); - return; - } - - TOX_ERR_NEW tox_err; - tox = tox_new(toxOptions.get(), &tox_err); - - switch (tox_err) { - case TOX_ERR_NEW_OK: - break; - - case TOX_ERR_NEW_LOAD_BAD_FORMAT: - qCritical() << "failed to parse Tox save data"; - emit failedToStart(); - return; - - case TOX_ERR_NEW_PORT_ALLOC: - if (s->getEnableIPv6()) { - tox_options_set_ipv6_enabled(toxOptions.get(), false); - tox = tox_new(toxOptions.get(), &tox_err); - if (tox_err == TOX_ERR_NEW_OK) { - qWarning() << "Core failed to start with IPv6, falling back to IPv4. LAN discovery " - "may not work properly."; - break; - } - } - - qCritical() << "can't to bind the port"; - emit failedToStart(); - return; - - case TOX_ERR_NEW_PROXY_BAD_HOST: - case TOX_ERR_NEW_PROXY_BAD_PORT: - case TOX_ERR_NEW_PROXY_BAD_TYPE: - qCritical() << "bad proxy, error code:" << tox_err; - emit badProxy(); - return; - - case TOX_ERR_NEW_PROXY_NOT_FOUND: - qCritical() << "proxy not found"; - emit badProxy(); - return; - - case TOX_ERR_NEW_LOAD_ENCRYPTED: - qCritical() << "attempted to load encrypted Tox save data"; - emit failedToStart(); - return; - - case TOX_ERR_NEW_MALLOC: - qCritical() << "memory allocation failed"; - emit failedToStart(); - return; - - case TOX_ERR_NEW_NULL: - qCritical() << "a parameter was null"; - emit failedToStart(); - return; - - default: - qCritical() << "Tox core failed to start, unknown error code:" << tox_err; - emit failedToStart(); - return; - } -} - -/** - * @brief Initializes the core, must be called before anything else - */ -void Core::start(const QByteArray& savedata) -{ - bool isNewProfile = profile.isNewProfile(); - if (isNewProfile) { - qDebug() << "Creating a new profile"; - makeTox(QByteArray()); - setStatusMessage(tr("Toxing on qTox")); - setUsername(profile.getName()); - } else { - qDebug() << "Loading user profile"; - if (savedata.isEmpty()) { - emit failedToStart(); - return; - } - - makeTox(savedata); - } - - qsrand(time(nullptr)); - if (!tox) { - ready = true; - GUI::setEnabled(true); - return; - } - - // toxcore is successfully created, create toxav - av = new CoreAV(tox); - if (!av->getToxAv()) { - qCritical() << "Toxav failed to start"; - emit failedToStart(); - deadifyTox(); - return; - } - - // set GUI with user and statusmsg - QString name = getUsername(); - if (!name.isEmpty()) { - emit usernameSet(name); - } - - QString msg = getStatusMessage(); - if (!msg.isEmpty()) { - emit statusMessageSet(msg); - } - - ToxId id = getSelfId(); - // TODO: probably useless check, comes basically directly from toxcore - if (id.isValid()) { - emit idSet(id); - } - - loadFriends(); - tox_callback_friend_request(tox, onFriendRequest); tox_callback_friend_message(tox, onFriendMessage); tox_callback_friend_name(tox, onFriendNameChange); @@ -393,22 +97,200 @@ void Core::start(const QByteArray& savedata) tox_callback_file_recv(tox, CoreFile::onFileReceiveCallback); tox_callback_file_recv_chunk(tox, CoreFile::onFileRecvChunkCallback); tox_callback_file_recv_control(tox, CoreFile::onFileControlCallback); +} - ready = true; +/** + * @brief Factory method for the Core object + * @param savedata empty if new profile or saved data else + * @param settings Settings specific to Core + * @return nullptr or a Core object ready to start + */ +ToxCorePtr Core::makeToxCore(const QByteArray& savedata, const ICoreSettings* const settings, + ToxCoreErrors* err) +{ + QThread* thread = new QThread(); + if (thread == nullptr) { + qCritical() << "could not allocate Core thread"; + return {}; + } + thread->setObjectName("qTox Core"); - if (isNewProfile) { - profile.saveToxSave(); + auto toxOptions = ToxOptions::makeToxOptions(savedata, settings); + if (toxOptions == nullptr) { + qCritical() << "could not allocate Tox Options data structure"; + if (err) { + *err = ToxCoreErrors::ERROR_ALLOC; + } + return {}; } - if (isReady()) { - GUI::setEnabled(true); + ToxCorePtr core(new Core(thread)); + if (core == nullptr) { + if (err) { + *err = ToxCoreErrors::ERROR_ALLOC; + } + return {}; } + TOX_ERR_NEW tox_err; + core->tox = ToxPtr(tox_new(*toxOptions, &tox_err)); + + switch (tox_err) { + case TOX_ERR_NEW_OK: + break; + + case TOX_ERR_NEW_LOAD_BAD_FORMAT: + qCritical() << "failed to parse Tox save data"; + if (err) { + *err = ToxCoreErrors::BAD_PROXY; + } + return {}; + + case TOX_ERR_NEW_PORT_ALLOC: + if (toxOptions->getIPv6Enabled()) { + toxOptions->setIPv6Enabled(false); + core->tox = ToxPtr(tox_new(*toxOptions, &tox_err)); + if (tox_err == TOX_ERR_NEW_OK) { + qWarning() << "Core failed to start with IPv6, falling back to IPv4. LAN discovery " + "may not work properly."; + break; + } + } + + qCritical() << "can't to bind the port"; + if (err) { + *err = ToxCoreErrors::FAILED_TO_START; + } + return {}; + + case TOX_ERR_NEW_PROXY_BAD_HOST: + case TOX_ERR_NEW_PROXY_BAD_PORT: + case TOX_ERR_NEW_PROXY_BAD_TYPE: + qCritical() << "bad proxy, error code:" << tox_err; + if (err) { + *err = ToxCoreErrors::BAD_PROXY; + } + return {}; + + case TOX_ERR_NEW_PROXY_NOT_FOUND: + qCritical() << "proxy not found"; + if (err) { + *err = ToxCoreErrors::BAD_PROXY; + } + return {}; + + case TOX_ERR_NEW_LOAD_ENCRYPTED: + qCritical() << "attempted to load encrypted Tox save data"; + if (err) { + *err = ToxCoreErrors::INVALID_SAVE; + } + return {}; + + case TOX_ERR_NEW_MALLOC: + qCritical() << "memory allocation failed"; + if (err) { + *err = ToxCoreErrors::ERROR_ALLOC; + } + return {}; + + case TOX_ERR_NEW_NULL: + qCritical() << "a parameter was null"; + if (err) { + *err = ToxCoreErrors::FAILED_TO_START; + } + return {}; + + default: + qCritical() << "Tox core failed to start, unknown error code:" << tox_err; + if (err) { + *err = ToxCoreErrors::FAILED_TO_START; + } + return {}; + } + + // provide a list of bootstrap nodes + core->bootstrapNodes = settings->getDhtServerList(); + + // tox should be valid by now + assert(core->tox != nullptr); + + // toxcore is successfully created, create toxav + core->av = std::unique_ptr(new CoreAV(core->tox.get())); + if (!core->av || !core->av->getToxAv()) { + qCritical() << "Toxav failed to start"; + if (err) { + *err = ToxCoreErrors::FAILED_TO_START; + } + return {}; + } + + registerCallbacks(core->tox.get()); + + // connect the thread with the Core + connect(thread, &QThread::started, core.get(), &Core::onStarted); + core->moveToThread(thread); + // since this is allocated in the constructor move it to the other thread too + core->toxTimer.moveToThread(thread); + + // when leaving this function 'core' should be ready for it's start() action or + // a nullptr + return core; +} + +void Core::onStarted() +{ + ASSERT_CORE_THREAD; + + // One time initialization stuff + QString name = getUsername(); + if (!name.isEmpty()) { + emit usernameSet(name); + } + + QString msg = getStatusMessage(); + if (!msg.isEmpty()) { + emit statusMessageSet(msg); + } + + ToxId id = getSelfId(); + // Id comes from toxcore, must be valid + assert(id.isValid()); + emit idSet(id); + + loadFriends(); + process(); // starts its own timer av->start(); emit avReady(); } +/** + * @brief Starts toxcore and it's event loop, can be called from any thread + */ +void Core::start() +{ + coreThread->start(); +} + + +/** + * @brief Returns the global widget's Core instance + */ +Core* Core::getInstance() +{ + return Nexus::getCore(); +} + +const CoreAV* Core::getAv() const +{ + return av.get(); +} + +CoreAV* Core::getAv() +{ + return av.get(); +} + /* Using the now commented out statements in checkConnection(), I watched how * many ticks disconnects-after-initial-connect lasted. Out of roughly 15 trials, * 5 disconnected; 4 were DCd for less than 20 ticks, while the 5th was ~50 ticks. @@ -423,19 +305,23 @@ void Core::start(const QByteArray& savedata) */ void Core::process() { + QMutexLocker ml{coreLoopLock.get()}; + + ASSERT_CORE_THREAD; if (!isReady()) { av->stop(); return; } static int tolerance = CORE_DISCONNECT_TOLERANCE; - tox_iterate(tox, getInstance()); + tox_iterate(tox.get(), this); #ifdef DEBUG // we want to see the debug messages immediately fflush(stdout); #endif + // TODO(sudden6): recheck if this is still necessary if (checkConnection()) { tolerance = CORE_DISCONNECT_TOLERANCE; } else if (!(--tolerance)) { @@ -443,14 +329,16 @@ void Core::process() tolerance = 3 * CORE_DISCONNECT_TOLERANCE; } - unsigned sleeptime = qMin(tox_iteration_interval(tox), CoreFile::corefileIterationInterval()); - toxTimer->start(sleeptime); + unsigned sleeptime = + qMin(tox_iteration_interval(tox.get()), CoreFile::corefileIterationInterval()); + toxTimer.start(sleeptime); } bool Core::checkConnection() { + ASSERT_CORE_THREAD; static bool isConnected = false; - bool toxConnected = tox_self_get_connection_status(tox) != TOX_CONNECTION_NONE; + bool toxConnected = tox_self_get_connection_status(tox.get()) != TOX_CONNECTION_NONE; if (toxConnected && !isConnected) { qDebug() << "Connected to the DHT"; emit connected(); @@ -468,8 +356,8 @@ bool Core::checkConnection() */ void Core::bootstrapDht() { - QList dhtServerList = s->getDhtServerList(); - int listSize = dhtServerList.size(); + ASSERT_CORE_THREAD; + int listSize = bootstrapNodes.size(); if (!listSize) { qWarning() << "no bootstrap list?!?"; return; @@ -479,7 +367,7 @@ void Core::bootstrapDht() static int j = qrand() % listSize; // i think the more we bootstrap, the more we jitter because the more we overwrite nodes while (i < 2) { - const DhtServer& dhtServer = dhtServerList[j % listSize]; + const DhtServer& dhtServer = bootstrapNodes[j % listSize]; QString dhtServerAddress = dhtServer.address.toLatin1(); QString port = QString::number(dhtServer.port); QString name = dhtServer.name; @@ -491,11 +379,11 @@ void Core::bootstrapDht() const uint8_t* pkPtr = reinterpret_cast(pk.getBytes()); - if (!tox_bootstrap(tox, address.constData(), dhtServer.port, pkPtr, nullptr)) { + if (!tox_bootstrap(tox.get(), address.constData(), dhtServer.port, pkPtr, nullptr)) { qDebug() << "Error bootstrapping from " + dhtServer.name; } - if (!tox_add_tcp_relay(tox, address.constData(), dhtServer.port, pkPtr, nullptr)) { + if (!tox_add_tcp_relay(tox.get(), address.constData(), dhtServer.port, pkPtr, nullptr)) { qDebug() << "Error adding TCP relay from " + dhtServer.name; } @@ -570,8 +458,8 @@ void Core::onConnectionStatusChanged(Tox*, uint32_t friendId, TOX_CONNECTION sta } } -void Core::onGroupInvite(Tox* tox, uint32_t friendId, TOX_CONFERENCE_TYPE type, const uint8_t* cookie, - size_t length, void* vCore) +void Core::onGroupInvite(Tox* tox, uint32_t friendId, TOX_CONFERENCE_TYPE type, + const uint8_t* cookie, size_t length, void* vCore) { Core* core = static_cast(vCore); // static_cast is used twice to replace using unsafe reinterpret_cast @@ -592,8 +480,7 @@ void Core::onGroupInvite(Tox* tox, uint32_t friendId, TOX_CONFERENCE_TYPE type, qDebug() << QString("AV group invite by %1").arg(friendId); if (friendId == UINT32_MAX) { // Rejoining existing (persistent) AV conference after disconnect and reconnect. - toxav_join_av_groupchat(tox, friendId, cookie, length, - CoreAV::groupCallCallback, core); + toxav_join_av_groupchat(tox, friendId, cookie, length, CoreAV::groupCallCallback, core); return; } emit core->groupInviteReceived(inviteInfo); @@ -625,8 +512,8 @@ void Core::onGroupPeerListChange(Tox*, uint32_t groupId, void* core) emit static_cast(core)->groupPeerlistChanged(groupId); } -void Core::onGroupPeerNameChange(Tox*, uint32_t groupId, uint32_t peerId, - const uint8_t* name, size_t length, void* core) +void Core::onGroupPeerNameChange(Tox*, uint32_t groupId, uint32_t peerId, const uint8_t* name, + size_t length, void* core) { const auto newName = ToxString(name, length).getQString(); qDebug() << QString("Group %1, Peer %2, name changed to %3").arg(groupId).arg(peerId).arg(newName); @@ -639,7 +526,7 @@ void Core::onGroupNamelistChange(Tox*, uint32_t groupId, uint32_t peerId, TOX_CONFERENCE_STATE_CHANGE change, void* core) { CoreAV* coreAv = static_cast(core)->getAv(); - const auto changed = change == TOX_CONFERENCE_STATE_CHANGE_PEER_EXIT; + const auto changed = change == TOX_CONFERENCE_STATE_CHANGE_PEER_EXIT; if (changed && coreAv->isGroupAvEnabled(groupId)) { CoreAV::invalidateGroupCallPeerSource(groupId, peerId); } @@ -664,12 +551,13 @@ void Core::onReadReceiptCallback(Tox*, uint32_t friendId, uint32_t receipt, void void Core::acceptFriendRequest(const ToxPk& friendPk) { + QMutexLocker ml{coreLoopLock.get()}; // TODO: error handling - uint32_t friendId = tox_friend_add_norequest(tox, friendPk.getBytes(), nullptr); + uint32_t friendId = tox_friend_add_norequest(tox.get(), friendPk.getBytes(), nullptr); if (friendId == std::numeric_limits::max()) { emit failedToAddFriend(friendPk); } else { - profile.saveToxSave(); + emit saveRequest(); emit friendAdded(friendId, friendPk); } } @@ -682,6 +570,8 @@ void Core::acceptFriendRequest(const ToxPk& friendPk) */ QString Core::getFriendRequestErrorMessage(const ToxId& friendId, const QString& message) const { + QMutexLocker ml{coreLoopLock.get()}; + if (!friendId.isValid()) { return tr("Invalid Tox ID", "Error while sending friendship request"); } @@ -704,17 +594,19 @@ QString Core::getFriendRequestErrorMessage(const ToxId& friendId, const QString& void Core::requestFriendship(const ToxId& friendId, const QString& message) { + QMutexLocker ml{coreLoopLock.get()}; + ToxPk friendPk = friendId.getPublicKey(); QString errorMessage = getFriendRequestErrorMessage(friendId, message); if (!errorMessage.isNull()) { emit failedToAddFriend(friendPk, errorMessage); - profile.saveToxSave(); + emit saveRequest(); return; } ToxString cMessage(message); uint32_t friendNumber = - tox_friend_add(tox, friendId.getBytes(), cMessage.data(), cMessage.size(), nullptr); + tox_friend_add(tox.get(), friendId.getBytes(), cMessage.data(), cMessage.size(), nullptr); if (friendNumber == std::numeric_limits::max()) { qDebug() << "Failed to request friendship"; emit failedToAddFriend(friendPk); @@ -724,32 +616,34 @@ void Core::requestFriendship(const ToxId& friendId, const QString& message) emit requestSent(friendPk, message); } - profile.saveToxSave(); + emit saveRequest(); } int Core::sendMessage(uint32_t friendId, const QString& message) { - QMutexLocker ml(&messageSendMutex); + QMutexLocker ml(coreLoopLock.get()); ToxString cMessage(message); - int receipt = tox_friend_send_message(tox, friendId, TOX_MESSAGE_TYPE_NORMAL, cMessage.data(), - cMessage.size(), nullptr); + int receipt = tox_friend_send_message(tox.get(), friendId, TOX_MESSAGE_TYPE_NORMAL, + cMessage.data(), cMessage.size(), nullptr); emit messageSentResult(friendId, message, receipt); return receipt; } int Core::sendAction(uint32_t friendId, const QString& action) { - QMutexLocker ml(&messageSendMutex); + QMutexLocker ml(coreLoopLock.get()); ToxString cMessage(action); - int receipt = tox_friend_send_message(tox, friendId, TOX_MESSAGE_TYPE_ACTION, cMessage.data(), - cMessage.size(), nullptr); + int receipt = tox_friend_send_message(tox.get(), friendId, TOX_MESSAGE_TYPE_ACTION, + cMessage.data(), cMessage.size(), nullptr); emit messageSentResult(friendId, action, receipt); return receipt; } void Core::sendTyping(uint32_t friendId, bool typing) { - if (!tox_self_set_typing(tox, friendId, typing, nullptr)) { + QMutexLocker ml{coreLoopLock.get()}; + + if (!tox_self_set_typing(tox.get(), friendId, typing, nullptr)) { emit failedToSetTyping(typing); } } @@ -784,12 +678,15 @@ bool parseConferenceSendMessageError(TOX_ERR_CONFERENCE_SEND_MESSAGE error) void Core::sendGroupMessageWithType(int groupId, const QString& message, TOX_MESSAGE_TYPE type) { + QMutexLocker ml{coreLoopLock.get()}; + QStringList cMessages = splitMessage(message, MAX_GROUP_MESSAGE_LEN); for (auto& part : cMessages) { ToxString cMsg(part); TOX_ERR_CONFERENCE_SEND_MESSAGE error; - bool ok = tox_conference_send_message(tox, groupId, type, cMsg.data(), cMsg.size(), &error); + bool ok = + tox_conference_send_message(tox.get(), groupId, type, cMsg.data(), cMsg.size(), &error); if (!ok || !parseConferenceSendMessageError(error)) { emit groupSentFailed(groupId); return; @@ -799,19 +696,25 @@ void Core::sendGroupMessageWithType(int groupId, const QString& message, TOX_MES void Core::sendGroupMessage(int groupId, const QString& message) { + QMutexLocker ml{coreLoopLock.get()}; + sendGroupMessageWithType(groupId, message, TOX_MESSAGE_TYPE_NORMAL); } void Core::sendGroupAction(int groupId, const QString& message) { + QMutexLocker ml{coreLoopLock.get()}; + sendGroupMessageWithType(groupId, message, TOX_MESSAGE_TYPE_ACTION); } void Core::changeGroupTitle(int groupId, const QString& title) { + QMutexLocker ml{coreLoopLock.get()}; + ToxString cTitle(title); TOX_ERR_CONFERENCE_TITLE error; - bool success = tox_conference_set_title(tox, groupId, cTitle.data(), cTitle.size(), &error); + bool success = tox_conference_set_title(tox.get(), groupId, cTitle.data(), cTitle.size(), &error); if (success && error == TOX_ERR_CONFERENCE_TITLE_OK) { emit groupTitleChanged(groupId, getUsername(), title); return; @@ -838,67 +741,87 @@ void Core::changeGroupTitle(int groupId, const QString& title) void Core::sendFile(uint32_t friendId, QString filename, QString filePath, long long filesize) { + QMutexLocker ml{coreLoopLock.get()}; + CoreFile::sendFile(this, friendId, filename, filePath, filesize); } void Core::sendAvatarFile(uint32_t friendId, const QByteArray& data) { + QMutexLocker ml{coreLoopLock.get()}; + CoreFile::sendAvatarFile(this, friendId, data); } void Core::pauseResumeFileSend(uint32_t friendId, uint32_t fileNum) { + QMutexLocker ml{coreLoopLock.get()}; + CoreFile::pauseResumeFileSend(this, friendId, fileNum); } void Core::pauseResumeFileRecv(uint32_t friendId, uint32_t fileNum) { + QMutexLocker ml{coreLoopLock.get()}; + CoreFile::pauseResumeFileRecv(this, friendId, fileNum); } void Core::cancelFileSend(uint32_t friendId, uint32_t fileNum) { + QMutexLocker ml{coreLoopLock.get()}; + CoreFile::cancelFileSend(this, friendId, fileNum); } void Core::cancelFileRecv(uint32_t friendId, uint32_t fileNum) { + QMutexLocker ml{coreLoopLock.get()}; + CoreFile::cancelFileRecv(this, friendId, fileNum); } void Core::rejectFileRecvRequest(uint32_t friendId, uint32_t fileNum) { + QMutexLocker ml{coreLoopLock.get()}; + CoreFile::rejectFileRecvRequest(this, friendId, fileNum); } void Core::acceptFileRecvRequest(uint32_t friendId, uint32_t fileNum, QString path) { + QMutexLocker ml{coreLoopLock.get()}; + CoreFile::acceptFileRecvRequest(this, friendId, fileNum, path); } void Core::removeFriend(uint32_t friendId, bool fake) { + QMutexLocker ml{coreLoopLock.get()}; + if (!isReady() || fake) { return; } - if (!tox_friend_delete(tox, friendId, nullptr)) { + if (!tox_friend_delete(tox.get(), friendId, nullptr)) { emit failedToRemoveFriend(friendId); return; } - profile.saveToxSave(); + emit saveRequest(); emit friendRemoved(friendId); } void Core::removeGroup(int groupId, bool fake) { + QMutexLocker ml{coreLoopLock.get()}; + if (!isReady() || fake) { return; } TOX_ERR_CONFERENCE_DELETE error; - bool success = tox_conference_delete(tox, groupId, &error); + bool success = tox_conference_delete(tox.get(), groupId, &error); if (success && error == TOX_ERR_CONFERENCE_DELETE_OK) { av->leaveGroupCall(groupId); return; @@ -920,14 +843,16 @@ void Core::removeGroup(int groupId, bool fake) */ QString Core::getUsername() const { + QMutexLocker ml{coreLoopLock.get()}; + QString sname; if (!tox) { return sname; } - int size = tox_self_get_name_size(tox); + int size = tox_self_get_name_size(tox.get()); uint8_t* name = new uint8_t[size]; - tox_self_get_name(tox, name); + tox_self_get_name(tox.get(), name); sname = ToxString(name, size).getQString(); delete[] name; return sname; @@ -935,20 +860,20 @@ QString Core::getUsername() const void Core::setUsername(const QString& username) { + QMutexLocker ml{coreLoopLock.get()}; + if (username == getUsername()) { return; } ToxString cUsername(username); - if (!tox_self_set_name(tox, cUsername.data(), cUsername.size(), nullptr)) { + if (!tox_self_set_name(tox.get(), cUsername.data(), cUsername.size(), nullptr)) { emit failedToSetUsername(username); return; } emit usernameSet(username); - if (ready) { - profile.saveToxSave(); - } + emit saveRequest(); } /** @@ -956,8 +881,10 @@ void Core::setUsername(const QString& username) */ ToxId Core::getSelfId() const { + QMutexLocker ml{coreLoopLock.get()}; + uint8_t friendId[TOX_ADDRESS_SIZE] = {0x00}; - tox_self_get_address(tox, friendId); + tox_self_get_address(tox.get(), friendId); return ToxId(friendId, TOX_ADDRESS_SIZE); } @@ -967,8 +894,10 @@ ToxId Core::getSelfId() const */ ToxPk Core::getSelfPublicKey() const { + QMutexLocker ml{coreLoopLock.get()}; + uint8_t friendId[TOX_ADDRESS_SIZE] = {0x00}; - tox_self_get_address(tox, friendId); + tox_self_get_address(tox.get(), friendId); return ToxPk(friendId); } @@ -977,6 +906,8 @@ ToxPk Core::getSelfPublicKey() const */ QPair Core::getKeypair() const { + QMutexLocker ml{coreLoopLock.get()}; + QPair keypair; if (!tox) { return keypair; @@ -984,8 +915,8 @@ QPair Core::getKeypair() const QByteArray pk(TOX_PUBLIC_KEY_SIZE, 0x00); QByteArray sk(TOX_SECRET_KEY_SIZE, 0x00); - tox_self_get_public_key(tox, reinterpret_cast(pk.data())); - tox_self_get_secret_key(tox, reinterpret_cast(sk.data())); + tox_self_get_public_key(tox.get(), reinterpret_cast(pk.data())); + tox_self_get_secret_key(tox.get(), reinterpret_cast(sk.data())); keypair.first = pk; keypair.second = sk; return keypair; @@ -996,14 +927,16 @@ QPair Core::getKeypair() const */ QString Core::getStatusMessage() const { + QMutexLocker ml{coreLoopLock.get()}; + QString sname; if (!tox) { return sname; } - size_t size = tox_self_get_status_message_size(tox); + size_t size = tox_self_get_status_message_size(tox.get()); uint8_t* name = new uint8_t[size]; - tox_self_get_status_message(tox, name); + tox_self_get_status_message(tox.get(), name); sname = ToxString(name, size).getQString(); delete[] name; return sname; @@ -1014,30 +947,33 @@ QString Core::getStatusMessage() const */ Status Core::getStatus() const { - return static_cast(tox_self_get_status(tox)); + QMutexLocker ml{coreLoopLock.get()}; + + return static_cast(tox_self_get_status(tox.get())); } void Core::setStatusMessage(const QString& message) { + QMutexLocker ml{coreLoopLock.get()}; + if (message == getStatusMessage()) { return; } ToxString cMessage(message); - if (!tox_self_set_status_message(tox, cMessage.data(), cMessage.size(), nullptr)) { + if (!tox_self_set_status_message(tox.get(), cMessage.data(), cMessage.size(), nullptr)) { emit failedToSetStatusMessage(message); return; } - if (ready) { - profile.saveToxSave(); - } - + emit saveRequest(); emit statusMessageSet(message); } void Core::setStatus(Status status) { + QMutexLocker ml{coreLoopLock.get()}; + TOX_USER_STATUS userstatus; switch (status) { case Status::Online: @@ -1057,8 +993,8 @@ void Core::setStatus(Status status) break; } - tox_self_set_status(tox, userstatus); - profile.saveToxSave(); + tox_self_set_status(tox.get(), userstatus); + emit saveRequest(); emit statusSet(status); } @@ -1067,39 +1003,43 @@ void Core::setStatus(Status status) */ QByteArray Core::getToxSaveData() { - uint32_t fileSize = tox_get_savedata_size(tox); + QMutexLocker ml{coreLoopLock.get()}; + + uint32_t fileSize = tox_get_savedata_size(tox.get()); QByteArray data; data.resize(fileSize); - tox_get_savedata(tox, (uint8_t*)data.data()); + tox_get_savedata(tox.get(), (uint8_t*)data.data()); return data; } // Declared to avoid code duplication -#define GET_FRIEND_PROPERTY(property, function, checkSize) \ - const size_t property##Size = function##_size(tox, ids[i], nullptr); \ - if ((!checkSize || property##Size) && property##Size != SIZE_MAX) { \ - uint8_t* prop = new uint8_t[property##Size]; \ - if (function(tox, ids[i], prop, nullptr)) { \ - QString propStr = ToxString(prop, property##Size).getQString(); \ - emit friend##property##Changed(ids[i], propStr); \ - } \ - \ - delete[] prop; \ +#define GET_FRIEND_PROPERTY(property, function, checkSize) \ + const size_t property##Size = function##_size(tox.get(), ids[i], nullptr); \ + if ((!checkSize || property##Size) && property##Size != SIZE_MAX) { \ + uint8_t* prop = new uint8_t[property##Size]; \ + if (function(tox.get(), ids[i], prop, nullptr)) { \ + QString propStr = ToxString(prop, property##Size).getQString(); \ + emit friend##property##Changed(ids[i], propStr); \ + } \ + \ + delete[] prop; \ } void Core::loadFriends() { - const uint32_t friendCount = tox_self_get_friend_list_size(tox); + QMutexLocker ml{coreLoopLock.get()}; + + const uint32_t friendCount = tox_self_get_friend_list_size(tox.get()); if (friendCount == 0) { return; } // assuming there are not that many friends to fill up the whole stack uint32_t* ids = new uint32_t[friendCount]; - tox_self_get_friend_list(tox, ids); + tox_self_get_friend_list(tox.get(), ids); uint8_t friendPk[TOX_PUBLIC_KEY_SIZE] = {0x00}; for (uint32_t i = 0; i < friendCount; ++i) { - if (!tox_friend_get_public_key(tox, ids[i], friendPk, nullptr)) { + if (!tox_friend_get_public_key(tox.get(), ids[i], friendPk, nullptr)) { continue; } @@ -1113,7 +1053,9 @@ void Core::loadFriends() void Core::checkLastOnline(uint32_t friendId) { - const uint64_t lastOnline = tox_friend_get_last_online(tox, friendId, nullptr); + QMutexLocker ml{coreLoopLock.get()}; + + const uint64_t lastOnline = tox_friend_get_last_online(tox.get(), friendId, nullptr); if (lastOnline != std::numeric_limits::max()) { emit friendLastSeenChanged(friendId, QDateTime::fromTime_t(lastOnline)); } @@ -1124,9 +1066,11 @@ void Core::checkLastOnline(uint32_t friendId) */ QVector Core::getFriendList() const { + QMutexLocker ml{coreLoopLock.get()}; + QVector friends; - friends.resize(tox_self_get_friend_list_size(tox)); - tox_self_get_friend_list(tox, friends.data()); + friends.resize(tox_self_get_friend_list_size(tox.get())); + tox_self_get_friend_list(tox.get(), friends.data()); return friends; } @@ -1154,6 +1098,7 @@ bool Core::parsePeerQueryError(TOX_ERR_CONFERENCE_PEER_QUERY error) const return false; default: + qCritical() << "Unknow error code:" << error; return false; } } @@ -1164,8 +1109,10 @@ bool Core::parsePeerQueryError(TOX_ERR_CONFERENCE_PEER_QUERY error) const */ uint32_t Core::getGroupNumberPeers(int groupId) const { + QMutexLocker ml{coreLoopLock.get()}; + TOX_ERR_CONFERENCE_PEER_QUERY error; - uint32_t count = tox_conference_peer_count(tox, groupId, &error); + uint32_t count = tox_conference_peer_count(tox.get(), groupId, &error); if (!parsePeerQueryError(error)) { return std::numeric_limits::max(); } @@ -1178,15 +1125,17 @@ uint32_t Core::getGroupNumberPeers(int groupId) const */ QString Core::getGroupPeerName(int groupId, int peerId) const { + QMutexLocker ml{coreLoopLock.get()}; + TOX_ERR_CONFERENCE_PEER_QUERY error; - size_t length = tox_conference_peer_get_name_size(tox, groupId, peerId, &error); + size_t length = tox_conference_peer_get_name_size(tox.get(), groupId, peerId, &error); if (!parsePeerQueryError(error)) { return QString{}; } QByteArray name(length, Qt::Uninitialized); uint8_t* namePtr = static_cast(static_cast(name.data())); - bool success = tox_conference_peer_get_name(tox, groupId, peerId, namePtr, &error); + bool success = tox_conference_peer_get_name(tox.get(), groupId, peerId, namePtr, &error); if (!parsePeerQueryError(error) || !success) { qWarning() << "getGroupPeerName: Unknown error"; return QString{}; @@ -1200,9 +1149,11 @@ QString Core::getGroupPeerName(int groupId, int peerId) const */ ToxPk Core::getGroupPeerPk(int groupId, int peerId) const { + QMutexLocker ml{coreLoopLock.get()}; + uint8_t friendPk[TOX_PUBLIC_KEY_SIZE] = {0x00}; TOX_ERR_CONFERENCE_PEER_QUERY error; - bool success = tox_conference_peer_get_public_key(tox, groupId, peerId, friendPk, &error); + bool success = tox_conference_peer_get_public_key(tox.get(), groupId, peerId, friendPk, &error); if (!parsePeerQueryError(error) || !success) { qWarning() << "getGroupPeerToxId: Unknown error"; return ToxPk{}; @@ -1216,6 +1167,8 @@ ToxPk Core::getGroupPeerPk(int groupId, int peerId) const */ QStringList Core::getGroupPeerNames(int groupId) const { + QMutexLocker ml{coreLoopLock.get()}; + if (!tox) { qWarning() << "Can't get group peer names, tox is null"; return {}; @@ -1228,7 +1181,7 @@ QStringList Core::getGroupPeerNames(int groupId) const } TOX_ERR_CONFERENCE_PEER_QUERY error; - uint32_t count = tox_conference_peer_count(tox, groupId, &error); + uint32_t count = tox_conference_peer_count(tox.get(), groupId, &error); if (!parsePeerQueryError(error)) { return {}; } @@ -1240,14 +1193,14 @@ QStringList Core::getGroupPeerNames(int groupId) const QStringList names; for (uint32_t i = 0; i < nPeers; ++i) { - size_t length = tox_conference_peer_get_name_size(tox, groupId, i, &error); + size_t length = tox_conference_peer_get_name_size(tox.get(), groupId, i, &error); if (!parsePeerQueryError(error)) { continue; } QByteArray name(length, Qt::Uninitialized); uint8_t* namePtr = static_cast(static_cast(name.data())); - bool ok = tox_conference_peer_get_name(tox, groupId, i, namePtr, &error); + bool ok = tox_conference_peer_get_name(tox.get(), groupId, i, namePtr, &error); if (ok && parsePeerQueryError(error)) { names.append(ToxString(name).getQString()); } @@ -1292,6 +1245,7 @@ bool Core::parseConferenceJoinError(TOX_ERR_CONFERENCE_JOIN error) const return false; default: + qCritical() << "Unknow error code:" << error; return false; } } @@ -1304,6 +1258,8 @@ bool Core::parseConferenceJoinError(TOX_ERR_CONFERENCE_JOIN error) const */ uint32_t Core::joinGroupchat(const GroupInvite& inviteInfo) const { + QMutexLocker ml{coreLoopLock.get()}; + const uint32_t friendId = inviteInfo.getFriendId(); const uint8_t confType = inviteInfo.getType(); const QByteArray invite = inviteInfo.getInvite(); @@ -1313,13 +1269,13 @@ uint32_t Core::joinGroupchat(const GroupInvite& inviteInfo) const case TOX_CONFERENCE_TYPE_TEXT: { qDebug() << QString("Trying to join text groupchat invite sent by friend %1").arg(friendId); TOX_ERR_CONFERENCE_JOIN error; - uint32_t groupId = tox_conference_join(tox, friendId, cookie, cookieLength, &error); + uint32_t groupId = tox_conference_join(tox.get(), friendId, cookie, cookieLength, &error); return parseConferenceJoinError(error) ? groupId : std::numeric_limits::max(); } case TOX_CONFERENCE_TYPE_AV: { qDebug() << QString("Trying to join AV groupchat invite sent by friend %1").arg(friendId); - return toxav_join_av_groupchat(tox, friendId, cookie, cookieLength, + return toxav_join_av_groupchat(tox.get(), friendId, cookie, cookieLength, CoreAV::groupCallCallback, const_cast(this)); } @@ -1332,8 +1288,10 @@ uint32_t Core::joinGroupchat(const GroupInvite& inviteInfo) const void Core::groupInviteFriend(uint32_t friendId, int groupId) { + QMutexLocker ml{coreLoopLock.get()}; + TOX_ERR_CONFERENCE_INVITE error; - tox_conference_invite(tox, friendId, groupId, &error); + tox_conference_invite(tox.get(), friendId, groupId, &error); switch (error) { case TOX_ERR_CONFERENCE_INVITE_OK: @@ -1354,9 +1312,11 @@ void Core::groupInviteFriend(uint32_t friendId, int groupId) int Core::createGroup(uint8_t type) { + QMutexLocker ml{coreLoopLock.get()}; + if (type == TOX_CONFERENCE_TYPE_TEXT) { TOX_ERR_CONFERENCE_NEW error; - uint32_t groupId = tox_conference_new(tox, &error); + uint32_t groupId = tox_conference_new(tox.get(), &error); switch (error) { case TOX_ERR_CONFERENCE_NEW_OK: @@ -1371,7 +1331,7 @@ int Core::createGroup(uint8_t type) return std::numeric_limits::max(); } } else if (type == TOX_CONFERENCE_TYPE_AV) { - uint32_t groupId = toxav_add_av_groupchat(tox, CoreAV::groupCallCallback, this); + uint32_t groupId = toxav_add_av_groupchat(tox.get(), CoreAV::groupCallCallback, this); emit emptyGroupCreated(groupId); return groupId; } else { @@ -1385,7 +1345,9 @@ int Core::createGroup(uint8_t type) */ bool Core::isFriendOnline(uint32_t friendId) const { - TOX_CONNECTION connetion = tox_friend_get_connection_status(tox, friendId, nullptr); + QMutexLocker ml{coreLoopLock.get()}; + + TOX_CONNECTION connetion = tox_friend_get_connection_status(tox.get(), friendId, nullptr); return connetion != TOX_CONNECTION_NONE; } @@ -1394,12 +1356,14 @@ bool Core::isFriendOnline(uint32_t friendId) const */ bool Core::hasFriendWithPublicKey(const ToxPk& publicKey) const { + QMutexLocker ml{coreLoopLock.get()}; + if (publicKey.isEmpty()) { return false; } // TODO: error handling - uint32_t friendId = tox_friend_by_public_key(tox, publicKey.getBytes(), nullptr); + uint32_t friendId = tox_friend_by_public_key(tox.get(), publicKey.getBytes(), nullptr); return friendId != std::numeric_limits::max(); } @@ -1408,8 +1372,10 @@ bool Core::hasFriendWithPublicKey(const ToxPk& publicKey) const */ ToxPk Core::getFriendPublicKey(uint32_t friendNumber) const { + QMutexLocker ml{coreLoopLock.get()}; + uint8_t rawid[TOX_PUBLIC_KEY_SIZE]; - if (!tox_friend_get_public_key(tox, friendNumber, rawid, nullptr)) { + if (!tox_friend_get_public_key(tox.get(), friendNumber, rawid, nullptr)) { qWarning() << "getFriendPublicKey: Getting public key failed"; return ToxPk(); } @@ -1422,14 +1388,16 @@ ToxPk Core::getFriendPublicKey(uint32_t friendNumber) const */ QString Core::getFriendUsername(uint32_t friendnumber) const { - size_t namesize = tox_friend_get_name_size(tox, friendnumber, nullptr); + QMutexLocker ml{coreLoopLock.get()}; + + size_t namesize = tox_friend_get_name_size(tox.get(), friendnumber, nullptr); if (namesize == SIZE_MAX) { qWarning() << "getFriendUsername: Failed to get name size for friend " << friendnumber; return QString(); } uint8_t* name = new uint8_t[namesize]; - tox_friend_get_name(tox, friendnumber, name, nullptr); + tox_friend_get_name(tox.get(), friendnumber, name, nullptr); ToxString sname(name, namesize); delete[] name; return sname.getQString(); @@ -1469,20 +1437,22 @@ QStringList Core::splitMessage(const QString& message, int maxLen) QString Core::getPeerName(const ToxPk& id) const { + QMutexLocker ml{coreLoopLock.get()}; + QString name; - uint32_t friendId = tox_friend_by_public_key(tox, id.getBytes(), nullptr); + uint32_t friendId = tox_friend_by_public_key(tox.get(), id.getBytes(), nullptr); if (friendId == std::numeric_limits::max()) { qWarning() << "getPeerName: No such peer"; return name; } - const size_t nameSize = tox_friend_get_name_size(tox, friendId, nullptr); + const size_t nameSize = tox_friend_get_name_size(tox.get(), friendId, nullptr); if (nameSize == SIZE_MAX) { return name; } uint8_t* cname = new uint8_t[nameSize < tox_max_name_length() ? tox_max_name_length() : nameSize]; - if (!tox_friend_get_name(tox, friendId, cname, nullptr)) { + if (!tox_friend_get_name(tox.get(), friendId, cname, nullptr)) { qWarning() << "getPeerName: Can't get name of friend " + QString().setNum(friendId); delete[] cname; return name; @@ -1498,7 +1468,7 @@ QString Core::getPeerName(const ToxPk& id) const */ bool Core::isReady() const { - return av && av->getToxAv() && tox && ready; + return av && av->getToxAv() && tox; } /** @@ -1507,37 +1477,20 @@ bool Core::isReady() const */ void Core::setNospam(uint32_t nospam) { - tox_self_set_nospam(tox, nospam); + QMutexLocker ml{coreLoopLock.get()}; + + tox_self_set_nospam(tox.get(), nospam); emit idSet(getSelfId()); } /** - * @brief Returns the unencrypted tox save data + * @brief Stops the AV thread and the timer here */ -void Core::killTimers(bool onlyStop) +void Core::killTimers() { - assert(QThread::currentThread() == coreThread); + ASSERT_CORE_THREAD; if (av) { av->stop(); } - toxTimer->stop(); - if (!onlyStop) { - delete toxTimer; - toxTimer = nullptr; - } -} - -/** - * @brief Reinitialized the core. - * @warning Must be called from the Core thread, with the GUI thread ready to process events. - */ -void Core::reset() -{ - assert(QThread::currentThread() == coreThread); - QByteArray toxsave = getToxSaveData(); - ready = false; - killTimers(true); - deadifyTox(); - GUI::clearContacts(); - start(toxsave); + toxTimer.stop(); } diff --git a/src/core/core.h b/src/core/core.h index 626d4bc41..7faf44887 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -24,18 +24,21 @@ #include "toxfile.h" #include "toxid.h" +#include "src/core/dhtserver.h" #include #include #include +#include +#include #include +#include class CoreAV; class ICoreSettings; class GroupInvite; class Profile; -class QTimer; enum class Status { @@ -45,11 +48,24 @@ enum class Status Offline }; +class Core; + +using ToxCorePtr = std::unique_ptr; + class Core : public QObject { Q_OBJECT public: - Core(QThread* coreThread, Profile& profile, const ICoreSettings* const settings); + enum class ToxCoreErrors + { + BAD_PROXY, + INVALID_SAVE, + FAILED_TO_START, + ERROR_ALLOC + }; + + static ToxCorePtr makeToxCore(const QByteArray& savedata, const ICoreSettings* const settings, + ToxCoreErrors* err = nullptr); static Core* getInstance(); const CoreAV* getAv() const; CoreAV* getAv(); @@ -85,10 +101,7 @@ public: void sendFile(uint32_t friendId, QString filename, QString filePath, long long filesize); public slots: - void start(const QByteArray& savedata); - void reset(); - void process(); - void bootstrapDht(); + void start(); QByteArray getToxSaveData(); @@ -126,51 +139,23 @@ signals: void disconnected(); void friendRequestReceived(const ToxPk& friendPk, const QString& message); - void friendMessageReceived(uint32_t friendId, const QString& message, bool isAction); + void friendAvatarChanged(const ToxPk& friendPk, const QPixmap& pic); + void friendAvatarData(const ToxPk& friendPk, const QByteArray& data); + void friendAvatarRemoved(const ToxPk& friendPk); - void friendAdded(uint32_t friendId, const ToxPk& friendPk); void requestSent(const ToxPk& friendPk, const QString& message); - - void friendStatusChanged(uint32_t friendId, Status status); - void friendStatusMessageChanged(uint32_t friendId, const QString& message); - void friendUsernameChanged(uint32_t friendId, const QString& username); - void friendTypingChanged(uint32_t friendId, bool isTyping); - void friendAvatarChanged(uint32_t friendId, const QPixmap& pic); - void friendAvatarRemoved(uint32_t friendId); - - void friendRemoved(uint32_t friendId); - - void friendLastSeenChanged(uint32_t friendId, const QDateTime& dateTime); - - void emptyGroupCreated(int groupnumber); - void groupInviteReceived(const GroupInvite& inviteInfo); - void groupMessageReceived(int groupnumber, int peernumber, const QString& message, bool isAction); - void groupNamelistChanged(int groupnumber, int peernumber, uint8_t change); - void groupPeerlistChanged(int groupnumber); - void groupPeerNameChanged(int groupnumber, int peernumber, const QString& newName); - void groupTitleChanged(int groupnumber, const QString& author, const QString& title); - void groupPeerAudioPlaying(int groupnumber, int peernumber); + void failedToAddFriend(const ToxPk& friendPk, const QString& errorInfo = QString()); void usernameSet(const QString& username); void statusMessageSet(const QString& message); void statusSet(Status status); void idSet(const ToxId& id); - void messageSentResult(uint32_t friendId, const QString& message, int messageId); - void groupSentFailed(int groupId); - void actionSentResult(uint32_t friendId, const QString& action, int success); - - void receiptRecieved(int friedId, int receipt); - - void failedToAddFriend(const ToxPk& friendPk, const QString& errorInfo = QString()); - void failedToRemoveFriend(uint32_t friendId); void failedToSetUsername(const QString& username); void failedToSetStatusMessage(const QString& message); void failedToSetStatus(Status status); void failedToSetTyping(bool typing); - void failedToStart(); - void badProxy(); void avReady(); void fileSendStarted(ToxFile file); @@ -184,11 +169,50 @@ signals: void fileTransferInfo(ToxFile file); void fileTransferRemotePausedUnpaused(ToxFile file, bool paused); void fileTransferBrokenUnbroken(ToxFile file, bool broken); - void fileNameChanged(); + void fileNameChanged(const ToxPk& friendPk); + + void saveRequest(); + + /** + * @deprecated prefer signals using ToxPk + */ + + void fileAvatarOfferReceived(uint32_t friendId, uint32_t fileId, const QByteArray& avatarHash); + + void friendMessageReceived(uint32_t friendId, const QString& message, bool isAction); + void friendAdded(uint32_t friendId, const ToxPk& friendPk); + + void friendStatusChanged(uint32_t friendId, Status status); + void friendStatusMessageChanged(uint32_t friendId, const QString& message); + void friendUsernameChanged(uint32_t friendId, const QString& username); + void friendTypingChanged(uint32_t friendId, bool isTyping); + + void friendAvatarChangedDeprecated(uint32_t friendId, const QPixmap& pic); + void friendRemoved(uint32_t friendId); + void friendLastSeenChanged(uint32_t friendId, const QDateTime& dateTime); + + void emptyGroupCreated(int groupnumber); + void groupInviteReceived(const GroupInvite& inviteInfo); + void groupMessageReceived(int groupnumber, int peernumber, const QString& message, bool isAction); + void groupNamelistChanged(int groupnumber, int peernumber, uint8_t change); + void groupPeerlistChanged(int groupnumber); + void groupPeerNameChanged(int groupnumber, int peernumber, const QString& newName); + void groupTitleChanged(int groupnumber, const QString& author, const QString& title); + void groupPeerAudioPlaying(int groupnumber, int peernumber); + + void messageSentResult(uint32_t friendId, const QString& message, int messageId); + void groupSentFailed(int groupId); + void actionSentResult(uint32_t friendId, const QString& action, int success); + + void receiptRecieved(int friedId, int receipt); + + void failedToRemoveFriend(uint32_t friendId); void fileSendFailed(uint32_t friendId, const QString& fname); private: + Core(QThread* coreThread); + static void onFriendRequest(Tox* tox, const uint8_t* cUserId, const uint8_t* cMessage, size_t cMessageSize, void* core); static void onFriendMessage(Tox* tox, uint32_t friendId, TOX_MESSAGE_TYPE type, @@ -208,8 +232,8 @@ private: const uint8_t* cMessage, size_t length, void* vCore); #if TOX_VERSION_IS_API_COMPATIBLE(0, 2, 0) static void onGroupPeerListChange(Tox*, uint32_t groupId, void* core); - static void onGroupPeerNameChange(Tox*, uint32_t groupId, uint32_t peerId, - const uint8_t* name, size_t length, void* core); + static void onGroupPeerNameChange(Tox*, uint32_t groupId, uint32_t peerId, const uint8_t* name, + size_t length, void* core); #else static void onGroupNamelistChange(Tox* tox, uint32_t groupId, uint32_t peerId, TOX_CONFERENCE_STATE_CHANGE change, void* core); @@ -224,28 +248,41 @@ private: bool checkConnection(); void checkEncryptedHistory(); - void makeTox(QByteArray savedata); + void makeTox(QByteArray savedata, ICoreSettings* s); void makeAv(); void loadFriends(); + void bootstrapDht(); void checkLastOnline(uint32_t friendId); - void deadifyTox(); QString getFriendRequestErrorMessage(const ToxId& friendId, const QString& message) const; + static void registerCallbacks(Tox* tox); private slots: - void killTimers(bool onlyStop); + void killTimers(); + void process(); + void onStarted(); private: - Tox* tox; - CoreAV* av; - QTimer* toxTimer; - Profile& profile; - QMutex messageSendMutex; - bool ready; - const ICoreSettings* const s; + struct ToxDeleter + { + void operator()(Tox* tox) + { + tox_kill(tox); + } + }; - static QThread* coreThread; + using ToxPtr = std::unique_ptr; + ToxPtr tox; + + std::unique_ptr av; + QTimer toxTimer; + // recursive, since we might call our own functions + // pointer so we can circumvent const functions + std::unique_ptr coreLoopLock = nullptr; + + std::unique_ptr coreThread = nullptr; + QList bootstrapNodes{}; friend class Audio; ///< Audio can access our calls directly to reduce latency friend class CoreFile; ///< CoreFile can access tox* and emit our signals diff --git a/src/core/coreav.cpp b/src/core/coreav.cpp index 6335c4a1c..19813b200 100644 --- a/src/core/coreav.cpp +++ b/src/core/coreav.cpp @@ -487,7 +487,7 @@ void CoreAV::groupCallCallback(void* tox, uint32_t group, uint32_t peer, const i const Settings& s = Settings::getInstance(); // don't play the audio if it comes from a muted peer if (s.getBlackList().contains(peerPk.toString())) { - return; + return; } CoreAV* cav = c->getAv(); @@ -506,12 +506,11 @@ void CoreAV::groupCallCallback(void* tox, uint32_t group, uint32_t peer, const i } Audio& audio = Audio::getInstance(); - if (!call.getPeers()[peer]) { - // FIXME: 0 is a valid sourceId, we shouldn't necessarily re-subscribe just because we have 0 - audio.subscribeOutput(call.getPeers()[peer]); + if(!call.havePeer(peer)) { + call.addPeer(peer); } - audio.playAudioBuffer(call.getPeers()[peer], data, samples, channels, sample_rate); + audio.playAudioBuffer(call.getAlSource(peer), data, samples, channels, sample_rate); } #else void CoreAV::groupCallCallback(void* tox, int group, int peer, const int16_t* data, @@ -536,12 +535,11 @@ void CoreAV::groupCallCallback(void* tox, int group, int peer, const int16_t* da } Audio& audio = Audio::getInstance(); - if (!call.getPeers()[peer]) { - // FIXME: 0 is a valid sourceId, we shouldn't necessarily re-subscribe just because we have 0 - audio.subscribeOutput(call.getPeers()[peer]); + if(!call.havePeer(peer)) { + call.addPeer(peer); } - audio.playAudioBuffer(call.getPeers()[peer], data, samples, channels, sample_rate); + audio.playAudioBuffer(call.getAlSource(peer), data, samples, channels, sample_rate); } #endif @@ -556,9 +554,7 @@ void CoreAV::invalidateGroupCallPeerSource(int group, int peer) if (it == groupCalls.end()) { return; } - Audio& audio = Audio::getInstance(); - audio.unsubscribeOutput(it->second.getPeers()[peer]); - it->second.getPeers()[peer] = 0; + it->second.removePeer(peer); } /** @@ -700,7 +696,7 @@ bool CoreAV::isGroupCallOutputMuted(const Group* g) const */ bool CoreAV::isGroupAvEnabled(int groupId) const { - Tox* tox = Core::getInstance()->tox; + Tox* tox = Core::getInstance()->tox.get(); TOX_ERR_CONFERENCE_GET_TYPE error; TOX_CONFERENCE_TYPE type = tox_conference_get_type(tox, groupId, &error); switch (error) { @@ -752,7 +748,7 @@ bool CoreAV::isCallOutputMuted(const Friend* f) const void CoreAV::invalidateCallSources() { for (auto& kv : groupCalls) { - kv.second.getPeers().clear(); + kv.second.clearPeers(); } for (auto& kv : calls) { @@ -801,7 +797,8 @@ void CoreAV::callCallback(ToxAV* toxav, uint32_t friendNum, bool audio, bool vid return; } - auto it = self->calls.insert(std::pair(friendNum, ToxFriendCall{friendNum, video, *self})); + auto it = self->calls.insert( + std::pair(friendNum, ToxFriendCall{friendNum, video, *self})); if (it.second == false) { /// Hanging up from a callback is supposed to be UB, /// but since currently the toxav callbacks are fired from the toxcore thread, @@ -923,8 +920,7 @@ void CoreAV::audioBitrateCallback(ToxAV* toxav, uint32_t friendNum, uint32_t rat Q_ARG(uint32_t, rate), Q_ARG(void*, vSelf)); } - qDebug() << "Recommended audio bitrate with" << friendNum << " is now " << rate - << ", ignoring it"; + qDebug() << "Recommended audio bitrate with" << friendNum << " is now " << rate << ", ignoring it"; } void CoreAV::videoBitrateCallback(ToxAV* toxav, uint32_t friendNum, uint32_t rate, void* vSelf) @@ -938,8 +934,7 @@ void CoreAV::videoBitrateCallback(ToxAV* toxav, uint32_t friendNum, uint32_t rat Q_ARG(uint32_t, rate), Q_ARG(void*, vSelf)); } - qDebug() << "Recommended video bitrate with" << friendNum << " is now " << rate - << ", ignoring it"; + qDebug() << "Recommended video bitrate with" << friendNum << " is now " << rate << ", ignoring it"; } void CoreAV::audioFrameCallback(ToxAV*, uint32_t friendNum, const int16_t* pcm, size_t sampleCount, diff --git a/src/core/coreav.h b/src/core/coreav.h index 48c91b614..048226d9e 100644 --- a/src/core/coreav.h +++ b/src/core/coreav.h @@ -78,8 +78,9 @@ public: void toggleMuteCallInput(const Friend* f); void toggleMuteCallOutput(const Friend* f); #if TOX_VERSION_IS_API_COMPATIBLE(0, 2, 0) - static void groupCallCallback(void* tox, uint32_t group, uint32_t peer, const int16_t* data, unsigned samples, - uint8_t channels, uint32_t sample_rate, void* core); + static void groupCallCallback(void* tox, uint32_t group, uint32_t peer, const int16_t* data, + unsigned samples, uint8_t channels, uint32_t sample_rate, + void* core); #else static void groupCallCallback(void* tox, int group, int peer, const int16_t* data, unsigned samples, uint8_t channels, unsigned sample_rate, void* core); diff --git a/src/core/corefile.cpp b/src/core/corefile.cpp index 768956b2f..bd64221b0 100644 --- a/src/core/corefile.cpp +++ b/src/core/corefile.cpp @@ -18,8 +18,8 @@ */ -#include "core.h" #include "corefile.h" +#include "core.h" #include "toxfile.h" #include "toxstring.h" #include "src/persistence/profile.h" @@ -27,8 +27,8 @@ #include #include #include -#include #include +#include #include /** @@ -71,7 +71,7 @@ void CoreFile::sendAvatarFile(Core* core, uint32_t friendId, const QByteArray& d QMutexLocker mlocker(&fileSendMutex); if (data.isEmpty()) { - tox_file_send(core->tox, friendId, TOX_FILE_KIND_AVATAR, 0, nullptr, nullptr, 0, nullptr); + tox_file_send(core->tox.get(), friendId, TOX_FILE_KIND_AVATAR, 0, nullptr, nullptr, 0, nullptr); return; } @@ -81,7 +81,7 @@ void CoreFile::sendAvatarFile(Core* core, uint32_t friendId, const QByteArray& d uint64_t filesize = data.size(); TOX_ERR_FILE_SEND error; - uint32_t fileNum = tox_file_send(core->tox, friendId, TOX_FILE_KIND_AVATAR, filesize, + uint32_t fileNum = tox_file_send(core->tox.get(), friendId, TOX_FILE_KIND_AVATAR, filesize, avatarHash, avatarHash, TOX_HASH_LENGTH, &error); switch (error) { @@ -112,7 +112,8 @@ void CoreFile::sendAvatarFile(Core* core, uint32_t friendId, const QByteArray& d file.fileKind = TOX_FILE_KIND_AVATAR; file.avatarData = data; file.resumeFileId.resize(TOX_FILE_ID_LENGTH); - tox_file_get_file_id(core->tox, friendId, fileNum, (uint8_t*)file.resumeFileId.data(), nullptr); + tox_file_get_file_id(core->tox.get(), friendId, fileNum, (uint8_t*)file.resumeFileId.data(), + nullptr); addFile(friendId, fileNum, file); } @@ -122,8 +123,8 @@ void CoreFile::sendFile(Core* core, uint32_t friendId, QString filename, QString QMutexLocker mlocker(&fileSendMutex); QByteArray fileName = filename.toUtf8(); - uint32_t fileNum = tox_file_send(core->tox, friendId, TOX_FILE_KIND_DATA, filesize, nullptr, - (uint8_t*)fileName.data(), fileName.size(), nullptr); + uint32_t fileNum = tox_file_send(core->tox.get(), friendId, TOX_FILE_KIND_DATA, filesize, + nullptr, (uint8_t*)fileName.data(), fileName.size(), nullptr); if (fileNum == std::numeric_limits::max()) { qWarning() << "sendFile: Can't create the Tox file sender"; emit core->fileSendFailed(friendId, filename); @@ -134,7 +135,8 @@ void CoreFile::sendFile(Core* core, uint32_t friendId, QString filename, QString ToxFile file{fileNum, friendId, fileName, filePath, ToxFile::SENDING}; file.filesize = filesize; file.resumeFileId.resize(TOX_FILE_ID_LENGTH); - tox_file_get_file_id(core->tox, friendId, fileNum, (uint8_t*)file.resumeFileId.data(), nullptr); + tox_file_get_file_id(core->tox.get(), friendId, fileNum, (uint8_t*)file.resumeFileId.data(), + nullptr); if (!file.open(false)) { qWarning() << QString("sendFile: Can't open file, error: %1").arg(file.file->errorString()); } @@ -154,11 +156,13 @@ void CoreFile::pauseResumeFileSend(Core* core, uint32_t friendId, uint32_t fileI if (file->status == ToxFile::TRANSMITTING) { file->status = ToxFile::PAUSED; emit core->fileTransferPaused(*file); - tox_file_control(core->tox, file->friendId, file->fileNum, TOX_FILE_CONTROL_PAUSE, nullptr); + tox_file_control(core->tox.get(), file->friendId, file->fileNum, TOX_FILE_CONTROL_PAUSE, + nullptr); } else if (file->status == ToxFile::PAUSED) { file->status = ToxFile::TRANSMITTING; emit core->fileTransferAccepted(*file); - tox_file_control(core->tox, file->friendId, file->fileNum, TOX_FILE_CONTROL_RESUME, nullptr); + tox_file_control(core->tox.get(), file->friendId, file->fileNum, TOX_FILE_CONTROL_RESUME, + nullptr); } else { qWarning() << "pauseResumeFileSend: File is stopped"; } @@ -174,11 +178,13 @@ void CoreFile::pauseResumeFileRecv(Core* core, uint32_t friendId, uint32_t fileI if (file->status == ToxFile::TRANSMITTING) { file->status = ToxFile::PAUSED; emit core->fileTransferPaused(*file); - tox_file_control(core->tox, file->friendId, file->fileNum, TOX_FILE_CONTROL_PAUSE, nullptr); + tox_file_control(core->tox.get(), file->friendId, file->fileNum, TOX_FILE_CONTROL_PAUSE, + nullptr); } else if (file->status == ToxFile::PAUSED) { file->status = ToxFile::TRANSMITTING; emit core->fileTransferAccepted(*file); - tox_file_control(core->tox, file->friendId, file->fileNum, TOX_FILE_CONTROL_RESUME, nullptr); + tox_file_control(core->tox.get(), file->friendId, file->fileNum, TOX_FILE_CONTROL_RESUME, + nullptr); } else { qWarning() << "pauseResumeFileRecv: File is stopped or broken"; } @@ -194,7 +200,7 @@ void CoreFile::cancelFileSend(Core* core, uint32_t friendId, uint32_t fileId) file->status = ToxFile::STOPPED; emit core->fileTransferCancelled(*file); - tox_file_control(core->tox, file->friendId, file->fileNum, TOX_FILE_CONTROL_CANCEL, nullptr); + tox_file_control(core->tox.get(), file->friendId, file->fileNum, TOX_FILE_CONTROL_CANCEL, nullptr); removeFile(friendId, fileId); } @@ -207,7 +213,7 @@ void CoreFile::cancelFileRecv(Core* core, uint32_t friendId, uint32_t fileId) } file->status = ToxFile::STOPPED; emit core->fileTransferCancelled(*file); - tox_file_control(core->tox, file->friendId, file->fileNum, TOX_FILE_CONTROL_CANCEL, nullptr); + tox_file_control(core->tox.get(), file->friendId, file->fileNum, TOX_FILE_CONTROL_CANCEL, nullptr); removeFile(friendId, fileId); } @@ -220,7 +226,7 @@ void CoreFile::rejectFileRecvRequest(Core* core, uint32_t friendId, uint32_t fil } file->status = ToxFile::STOPPED; emit core->fileTransferCancelled(*file); - tox_file_control(core->tox, file->friendId, file->fileNum, TOX_FILE_CONTROL_CANCEL, nullptr); + tox_file_control(core->tox.get(), file->friendId, file->fileNum, TOX_FILE_CONTROL_CANCEL, nullptr); removeFile(friendId, fileId); } @@ -238,7 +244,7 @@ void CoreFile::acceptFileRecvRequest(Core* core, uint32_t friendId, uint32_t fil } file->status = ToxFile::TRANSMITTING; emit core->fileTransferAccepted(*file); - tox_file_control(core->tox, file->friendId, file->fileNum, TOX_FILE_CONTROL_RESUME, nullptr); + tox_file_control(core->tox.get(), file->friendId, file->fileNum, TOX_FILE_CONTROL_RESUME, nullptr); } ToxFile* CoreFile::findFile(uint32_t friendId, uint32_t fileId) @@ -277,7 +283,7 @@ void CoreFile::removeFile(uint32_t friendId, uint32_t fileId) QString CoreFile::getCleanFileName(QString filename) { - QRegularExpression regex("[<>:\"/\\|?*]"); + QRegularExpression regex{QStringLiteral(R"([<>:"/\\|?])")}; filename.replace(regex, "_"); return filename; @@ -289,60 +295,77 @@ void CoreFile::onFileReceiveCallback(Tox*, uint32_t friendId, uint32_t fileId, u { Core* core = static_cast(vCore); auto filename = ToxString(fname, fnameLen); - const auto cleanFileName = CoreFile::getCleanFileName(filename.getQString()); + const ToxPk friendPk = core->getFriendPublicKey(friendId); if (kind == TOX_FILE_KIND_AVATAR) { - const ToxPk friendPk = core->getFriendPublicKey(friendId); if (!filesize) { qDebug() << QString("Received empty avatar request %1:%2").arg(friendId).arg(fileId); // Avatars of size 0 means explicitely no avatar - emit core->friendAvatarRemoved(friendId); - core->profile.removeAvatar(friendPk); + emit core->friendAvatarRemoved(core->getFriendPublicKey(friendId)); return; } else { static_assert(TOX_HASH_LENGTH <= TOX_FILE_ID_LENGTH, "TOX_HASH_LENGTH > TOX_FILE_ID_LENGTH!"); uint8_t avatarHash[TOX_FILE_ID_LENGTH]; - tox_file_get_file_id(core->tox, friendId, fileId, avatarHash, nullptr); - if (core->profile.getAvatarHash(friendPk) - == QByteArray((char*)avatarHash, TOX_HASH_LENGTH)) { - // If it's an avatar but we already have it cached, cancel - qDebug() << QString( - "Received avatar request %1:%2, reject, since we have it in cache.") - .arg(friendId) - .arg(fileId); - tox_file_control(core->tox, friendId, fileId, TOX_FILE_CONTROL_CANCEL, nullptr); - return; - } else { - // It's an avatar and we don't have it, autoaccept the transfer - qDebug() << QString("Received avatar request %1:%2, accept, since we don't have it " - "in cache.") - .arg(friendId) - .arg(fileId); - tox_file_control(core->tox, friendId, fileId, TOX_FILE_CONTROL_RESUME, nullptr); - } + tox_file_get_file_id(core->tox.get(), friendId, fileId, avatarHash, nullptr); + QByteArray avatarBytes{static_cast(static_cast(avatarHash)), + TOX_HASH_LENGTH}; + emit core->fileAvatarOfferReceived(friendId, fileId, avatarBytes); + return; } } else { + const auto cleanFileName = CoreFile::getCleanFileName(filename.getQString()); + if (cleanFileName != filename.getQString()) { + qDebug() << QStringLiteral("Cleaned filename"); + filename = ToxString(cleanFileName); + emit core->fileNameChanged(friendPk); + } else { + qDebug() << QStringLiteral("filename already clean"); + } qDebug() << QString("Received file request %1:%2 kind %3").arg(friendId).arg(fileId).arg(kind); } - if (cleanFileName != filename.getQString()) { - qDebug() << QStringLiteral("Cleaned filename from %1 to %2").arg(filename.getQString()).arg(cleanFileName); - filename = ToxString(cleanFileName); - emit core->fileNameChanged(); - } else { - qDebug() << QStringLiteral("cleanFileName: filename already clean"); - } - ToxFile file{fileId, friendId, filename.getBytes(), "", ToxFile::RECEIVING}; file.filesize = filesize; file.fileKind = kind; file.resumeFileId.resize(TOX_FILE_ID_LENGTH); - tox_file_get_file_id(core->tox, friendId, fileId, (uint8_t*)file.resumeFileId.data(), nullptr); + tox_file_get_file_id(core->tox.get(), friendId, fileId, (uint8_t*)file.resumeFileId.data(), + nullptr); addFile(friendId, fileId, file); if (kind != TOX_FILE_KIND_AVATAR) emit core->fileReceiveRequested(file); } + +// TODO(sudden6): This whole method is a mess but needed to get stuff working for now +void CoreFile::handleAvatarOffer(uint32_t friendId, uint32_t fileId, bool accept) +{ + // TODO(sudden6): evil evil evil + auto core = Core::getInstance(); + if (!accept) { + // If it's an avatar but we already have it cached, cancel + qDebug() << QString("Received avatar request %1:%2, reject, since we have it in cache.") + .arg(friendId) + .arg(fileId); + tox_file_control(core->tox.get(), friendId, fileId, TOX_FILE_CONTROL_CANCEL, nullptr); + return; + } + + // It's an avatar and we don't have it, autoaccept the transfer + qDebug() << QString("Received avatar request %1:%2, accept, since we don't have it " + "in cache.") + .arg(friendId) + .arg(fileId); + tox_file_control(core->tox.get(), friendId, fileId, TOX_FILE_CONTROL_RESUME, nullptr); + + ToxFile file{fileId, friendId, "", "", ToxFile::RECEIVING}; + file.filesize = 0; + file.fileKind = TOX_FILE_KIND_AVATAR; + file.resumeFileId.resize(TOX_FILE_ID_LENGTH); + tox_file_get_file_id(core->tox.get(), friendId, fileId, (uint8_t*)file.resumeFileId.data(), + nullptr); + addFile(friendId, fileId, file); +} + void CoreFile::onFileControlCallback(Tox*, uint32_t friendId, uint32_t fileId, TOX_FILE_CONTROL control, void* core) { @@ -447,9 +470,10 @@ void CoreFile::onFileRecvChunkCallback(Tox* tox, uint32_t friendId, uint32_t fil pic.loadFromData(file->avatarData); if (!pic.isNull()) { qDebug() << "Got" << file->avatarData.size() << "bytes of avatar data from" << friendId; - core->profile.saveAvatar(file->avatarData, - core->getFriendPublicKey(friendId)); - emit core->friendAvatarChanged(friendId, pic); + emit core->friendAvatarData(core->getFriendPublicKey(friendId), file->avatarData); + emit core->friendAvatarChanged(core->getFriendPublicKey(friendId), pic); + // TODO(sudden6): signal below is deprecated + emit core->friendAvatarChangedDeprecated(friendId, pic); } } else { emit core->fileTransferFinished(*file); diff --git a/src/core/corefile.h b/src/core/corefile.h index 759cd2876..6a0395ba8 100644 --- a/src/core/corefile.h +++ b/src/core/corefile.h @@ -39,6 +39,10 @@ class CoreFile { friend class Core; + +public: + static void handleAvatarOffer(uint32_t friendId, uint32_t fileId, bool accept); + private: CoreFile() = delete; diff --git a/src/core/toxcall.cpp b/src/core/toxcall.cpp index be4131024..74e43cfd0 100644 --- a/src/core/toxcall.cpp +++ b/src/core/toxcall.cpp @@ -28,40 +28,10 @@ * @brief Keeps sources for users in group calls. */ -ToxCall::ToxCall(uint32_t CallId, bool VideoEnabled, CoreAV& av) +ToxCall::ToxCall(bool VideoEnabled, CoreAV& av) : av{&av} , videoEnabled{VideoEnabled} { - Audio& audio = Audio::getInstance(); - audio.subscribeInput(); - audio.subscribeOutput(alSource); - - audioInConn = QObject::connect(&Audio::getInstance(), &Audio::frameAvailable, - [&av, CallId](const int16_t* pcm, size_t samples, uint8_t chans, - uint32_t rate) { - av.sendCallAudio(CallId, pcm, samples, chans, rate); - }); - - if (!audioInConn) { - qDebug() << "Audio connection not working"; - } - - if (videoEnabled) { - videoSource = new CoreVideoSource(); - CameraSource& source = CameraSource::getInstance(); - - if (source.isNone()) { - source.setupDefault(); - } - source.subscribe(); - videoInConn = QObject::connect(&source, &VideoSource::frameAvailable, - [&av, CallId](std::shared_ptr frame) { - av.sendCallVideo(CallId, frame); - }); - if (!videoInConn) { - qDebug() << "Video connection not working"; - } - } } /** @@ -73,7 +43,6 @@ ToxCall::ToxCall(ToxCall&& other) noexcept : active{other.active}, audioInConn{other.audioInConn}, muteMic{other.muteMic}, muteVol{other.muteVol}, - alSource{other.alSource}, videoSource{other.videoSource}, videoInConn{other.videoInConn}, videoEnabled{other.videoEnabled}, @@ -82,7 +51,6 @@ ToxCall::ToxCall(ToxCall&& other) noexcept : active{other.active}, Audio& audio = Audio::getInstance(); audio.subscribeInput(); other.audioInConn = QMetaObject::Connection(); - other.alSource = 0; other.videoInConn = QMetaObject::Connection(); other.videoEnabled = false; // we don't need to subscribe video because other won't unsubscribe other.videoSource = nullptr; @@ -95,7 +63,6 @@ ToxCall::~ToxCall() QObject::disconnect(audioInConn); audio.unsubscribeInput(); - audio.unsubscribeOutput(alSource); if (videoEnabled) { QObject::disconnect(videoInConn); CameraSource::getInstance().unsubscribe(); @@ -117,9 +84,6 @@ ToxCall& ToxCall::operator=(ToxCall&& other) noexcept muteMic = other.muteMic; muteVol = other.muteVol; - alSource = other.alSource; - other.alSource = 0; - Audio::getInstance().subscribeInput(); videoInConn = other.videoInConn; @@ -190,19 +154,73 @@ CoreVideoSource* ToxCall::getVideoSource() const return videoSource; } -quint32 ToxCall::getAlSource() const +quint32 ToxFriendCall::getAlSource() const { return alSource; } -void ToxCall::setAlSource(const quint32& value) +void ToxFriendCall::setAlSource(const quint32& value) { alSource = value; } ToxFriendCall::ToxFriendCall(uint32_t FriendNum, bool VideoEnabled, CoreAV& av) - : ToxCall(FriendNum, VideoEnabled, av) + : ToxCall(VideoEnabled, av) { + // register audio + Audio& audio = Audio::getInstance(); + audio.subscribeInput(); + audioInConn = QObject::connect(&Audio::getInstance(), &Audio::frameAvailable, + [&av, FriendNum](const int16_t* pcm, size_t samples, uint8_t chans, + uint32_t rate) { + av.sendCallAudio(FriendNum, pcm, samples, chans, rate); + }); + + if (!audioInConn) { + qDebug() << "Audio input connection not working"; + } + + audio.subscribeOutput(alSource); + + // register video + if (videoEnabled) { + videoSource = new CoreVideoSource(); + CameraSource& source = CameraSource::getInstance(); + + if (source.isNone()) { + source.setupDefault(); + } + source.subscribe(); + videoInConn = QObject::connect(&source, &VideoSource::frameAvailable, + [&av, FriendNum](std::shared_ptr frame) { + av.sendCallVideo(FriendNum, frame); + }); + if (!videoInConn) { + qDebug() << "Video connection not working"; + } + } +} + +ToxFriendCall::ToxFriendCall(ToxFriendCall &&other) noexcept + : ToxCall(std::move(other)) + , alSource{other.alSource} +{ + other.alSource = 0; +} + +ToxFriendCall& ToxFriendCall::operator=(ToxFriendCall &&other) noexcept +{ + ToxCall::operator=(std::move(other)); + alSource = other.alSource; + other.alSource = 0; + + return *this; +} + +ToxFriendCall::~ToxFriendCall() +{ + auto& audio = Audio::getInstance(); + audio.unsubscribeOutput(alSource); } void ToxFriendCall::startTimeout(uint32_t callId) @@ -239,11 +257,24 @@ void ToxFriendCall::setState(const TOXAV_FRIEND_CALL_STATE& value) } ToxGroupCall::ToxGroupCall(int GroupNum, CoreAV& av) - : ToxCall(static_cast(GroupNum), false, av) + : ToxCall(false, av) { + // register audio + Audio& audio = Audio::getInstance(); + audio.subscribeInput(); + audioInConn = QObject::connect(&Audio::getInstance(), &Audio::frameAvailable, + [&av, GroupNum](const int16_t* pcm, size_t samples, uint8_t chans, + uint32_t rate) { + av.sendGroupCallAudio(GroupNum, pcm, samples, chans, rate); + }); + + if (!audioInConn) { + qDebug() << "Audio input connection not working"; + } } -ToxGroupCall::ToxGroupCall(ToxGroupCall&& other) noexcept : ToxCall(std::move(other)), peers{other.peers} +ToxGroupCall::ToxGroupCall(ToxGroupCall&& other) noexcept + : ToxCall(std::move(other)), peers{other.peers} { // all peers were moved, this ensures audio output is unsubscribed only once other.peers.clear(); @@ -269,10 +300,48 @@ ToxGroupCall& ToxGroupCall::operator=(ToxGroupCall&& other) noexcept void ToxGroupCall::removePeer(int peerId) { + auto& audio = Audio::getInstance(); + const auto& sourceId = peers.find(peerId); + if(sourceId == peers.constEnd()) { + qDebug() << "Peer:" << peerId << "does not have a source, can't remove"; + return; + } + + audio.unsubscribeOutput(sourceId.value()); peers.remove(peerId); } -QMap& ToxGroupCall::getPeers() +void ToxGroupCall::addPeer(int peerId) { - return peers; + auto& audio = Audio::getInstance(); + uint sourceId = 0; + audio.subscribeOutput(sourceId); + peers.insert(peerId, sourceId); +} + +bool ToxGroupCall::havePeer(int peerId) +{ + const auto& sourceId = peers.find(peerId); + return sourceId != peers.constEnd(); +} + +void ToxGroupCall::clearPeers() +{ + Audio& audio = Audio::getInstance(); + + for (quint32 v : peers) { + audio.unsubscribeOutput(v); + } + + peers.clear(); +} + +quint32 ToxGroupCall::getAlSource(int peer) +{ + if(!havePeer(peer)) { + qWarning() << "Getting alSource for non-existant peer"; + return 0; + } + + return peers[peer]; } diff --git a/src/core/toxcall.h b/src/core/toxcall.h index d4b60462c..2524e4e93 100644 --- a/src/core/toxcall.h +++ b/src/core/toxcall.h @@ -18,7 +18,7 @@ class ToxCall { protected: ToxCall() = delete; - ToxCall(uint32_t CallId, bool VideoEnabled, CoreAV& av); + ToxCall(bool VideoEnabled, CoreAV& av); ~ToxCall(); public: @@ -45,9 +45,6 @@ public: CoreVideoSource* getVideoSource() const; - quint32 getAlSource() const; - void setAlSource(const quint32& value); - protected: bool active{false}; CoreAV* av{nullptr}; @@ -55,7 +52,6 @@ protected: QMetaObject::Connection audioInConn; bool muteMic{false}; bool muteVol{false}; - quint32 alSource{0}; // video CoreVideoSource* videoSource{nullptr}; QMetaObject::Connection videoInConn; @@ -68,8 +64,9 @@ class ToxFriendCall : public ToxCall public: ToxFriendCall() = delete; ToxFriendCall(uint32_t friendId, bool VideoEnabled, CoreAV& av); - ToxFriendCall(ToxFriendCall&& other) noexcept = default; - ToxFriendCall& operator=(ToxFriendCall&& other) noexcept = default; + ToxFriendCall(ToxFriendCall&& other) noexcept; + ToxFriendCall& operator=(ToxFriendCall&& other) noexcept; + ~ToxFriendCall(); void startTimeout(uint32_t callId); void stopTimeout(); @@ -77,12 +74,16 @@ public: TOXAV_FRIEND_CALL_STATE getState() const; void setState(const TOXAV_FRIEND_CALL_STATE& value); + quint32 getAlSource() const; + void setAlSource(const quint32& value); + protected: std::unique_ptr timeoutTimer; private: TOXAV_FRIEND_CALL_STATE state{TOXAV_FRIEND_CALL_STATE_NONE}; static constexpr int CALL_TIMEOUT = 45000; + quint32 alSource{0}; }; class ToxGroupCall : public ToxCall @@ -96,8 +97,11 @@ public: ToxGroupCall& operator=(ToxGroupCall&& other) noexcept; void removePeer(int peerId); + void addPeer(int peerId); + bool havePeer(int peerId); + void clearPeers(); - QMap& getPeers(); + quint32 getAlSource(int peer); private: QMap peers; diff --git a/src/core/toxlogger.cpp b/src/core/toxlogger.cpp new file mode 100644 index 000000000..d8a1cf1ff --- /dev/null +++ b/src/core/toxlogger.cpp @@ -0,0 +1,60 @@ +#include "toxlogger.h" + +#include + +#include +#include +#include +#include + +/** + * @brief Map TOX_LOG_LEVEL to a string + * @param level log level + * @return Descriptive string for the log level + */ +QString getToxLogLevel(TOX_LOG_LEVEL level) { + switch (level) { + case TOX_LOG_LEVEL_TRACE: + return QLatin1Literal("TRACE"); + case TOX_LOG_LEVEL_DEBUG: + return QLatin1Literal("DEBUG"); + case TOX_LOG_LEVEL_INFO: + return QLatin1Literal("INFO "); + case TOX_LOG_LEVEL_WARNING: + return QLatin1Literal("WARN "); + case TOX_LOG_LEVEL_ERROR: + return QLatin1Literal("ERROR"); + default: + // Invalid log level + return QLatin1Literal("INVAL"); + } +} + +/** + * @brief Log message handler for toxcore log messages + * @note See tox.h for the parameter definitions + */ +void ToxLogger::onLogMessage(Tox *tox, TOX_LOG_LEVEL level, const char *file, uint32_t line, + const char *func, const char *message, void *user_data) +{ + // for privacy, make the path relative to the c-toxcore source directory + const QRegularExpression pathCleaner(QLatin1Literal{"[\\s|\\S]*c-toxcore."}); + const QString cleanPath = QString{file}.remove(pathCleaner); + + const QString logMsg = getToxLogLevel(level) % QLatin1Literal{":"} % cleanPath + % QLatin1Literal{":"} % func % QStringLiteral(":%1: ").arg(line) + % message; + + switch (level) { + case TOX_LOG_LEVEL_TRACE: + return; // trace level generates too much noise to enable by default + case TOX_LOG_LEVEL_DEBUG: + case TOX_LOG_LEVEL_INFO: + qDebug() << logMsg; + break; + case TOX_LOG_LEVEL_WARNING: + case TOX_LOG_LEVEL_ERROR: + qWarning() << logMsg; + } +} + diff --git a/src/core/toxlogger.h b/src/core/toxlogger.h new file mode 100644 index 000000000..624bf8c91 --- /dev/null +++ b/src/core/toxlogger.h @@ -0,0 +1,13 @@ +#ifndef TOXLOGGER_H +#define TOXLOGGER_H + +#include + +#include + +namespace ToxLogger { + void onLogMessage(Tox *tox, TOX_LOG_LEVEL level, const char *file, uint32_t line, + const char *func, const char *message, void *user_data); +} + +#endif // TOXLOGGER_H diff --git a/src/core/toxoptions.cpp b/src/core/toxoptions.cpp new file mode 100644 index 000000000..7b1e2c907 --- /dev/null +++ b/src/core/toxoptions.cpp @@ -0,0 +1,133 @@ +#include "toxoptions.h" + +#include "src/core/icoresettings.h" +#include "src/core/toxlogger.h" + +#include +#include + +// TODO(sudden6): replace this constant with the function from toxcore 0.2.3 +static const int MAX_PROXY_ADDRESS_LENGTH = 255; + +/** + * @brief The ToxOptions class wraps the Tox_Options struct and the matching + * proxy address data. This is needed to ensure both have equal lifetime and + * are correctly deleted. + */ + +ToxOptions::ToxOptions(Tox_Options* options, const QByteArray& proxyAddrData) + : options(options) + , proxyAddrData(proxyAddrData) +{} + +ToxOptions::~ToxOptions() +{ + tox_options_free(options); +} + +ToxOptions::ToxOptions(ToxOptions&& from) +{ + options = from.options; + proxyAddrData.swap(from.proxyAddrData); + from.options = nullptr; + from.proxyAddrData.clear(); +} + +const char* ToxOptions::getProxyAddrData() const +{ + return proxyAddrData.constData(); +} + +ToxOptions::operator Tox_Options*() +{ + return options; +} + +/** + * @brief Initializes a ToxOptions instance + * @param savedata Previously saved Tox data + * @return ToxOptions instance initialized to create Tox instance + */ +std::unique_ptr ToxOptions::makeToxOptions(const QByteArray& savedata, + const ICoreSettings* s) +{ + // IPv6 needed for LAN discovery, but can crash some weird routers. On by default, can be + // disabled in options. + const bool enableIPv6 = s->getEnableIPv6(); + bool forceTCP = s->getForceTCP(); + // LAN requiring UDP is a toxcore limitation, ideally wouldn't be related + const bool enableLanDiscovery = s->getEnableLanDiscovery() && !forceTCP; + ICoreSettings::ProxyType proxyType = s->getProxyType(); + quint16 proxyPort = s->getProxyPort(); + QString proxyAddr = s->getProxyAddr(); + + if (!enableLanDiscovery) { + qWarning() << "Core starting without LAN discovery. Peers can only be found through DHT."; + } + if (enableIPv6) { + qDebug() << "Core starting with IPv6 enabled"; + } else if (enableLanDiscovery) { + qWarning() << "Core starting with IPv6 disabled. LAN discovery may not work properly."; + } + + Tox_Options* tox_opts = tox_options_new(nullptr); + + if (!tox_opts) { + return {}; + } + + auto toxOptions = std::unique_ptr(new ToxOptions(tox_opts, proxyAddr.toUtf8())); + // register log first, to get messages as early as possible + tox_options_set_log_callback(*toxOptions, ToxLogger::onLogMessage); + + // savedata + tox_options_set_savedata_type(*toxOptions, !savedata.isNull() ? TOX_SAVEDATA_TYPE_TOX_SAVE + : TOX_SAVEDATA_TYPE_NONE); + tox_options_set_savedata_data(*toxOptions, reinterpret_cast(savedata.data()), + savedata.size()); + // No proxy by default + tox_options_set_proxy_type(*toxOptions, TOX_PROXY_TYPE_NONE); + tox_options_set_proxy_host(*toxOptions, nullptr); + tox_options_set_proxy_port(*toxOptions, 0); + + if (proxyType != ICoreSettings::ProxyType::ptNone) { + if (proxyAddr.length() > MAX_PROXY_ADDRESS_LENGTH) { + qWarning() << "proxy address" << proxyAddr << "is too long"; + } else if (!proxyAddr.isEmpty() && proxyPort > 0) { + qDebug() << "using proxy" << proxyAddr << ":" << proxyPort; + // protection against changings in TOX_PROXY_TYPE enum + if (proxyType == ICoreSettings::ProxyType::ptSOCKS5) { + tox_options_set_proxy_type(*toxOptions, TOX_PROXY_TYPE_SOCKS5); + } else if (proxyType == ICoreSettings::ProxyType::ptHTTP) { + tox_options_set_proxy_type(*toxOptions, TOX_PROXY_TYPE_HTTP); + } + + tox_options_set_proxy_host(*toxOptions, toxOptions->getProxyAddrData()); + tox_options_set_proxy_port(*toxOptions, proxyPort); + + if (!forceTCP) { + qDebug() << "Proxy and UDP enabled, this is a security risk, forcing TCP only"; + forceTCP = true; + } + } + } + + // network options + tox_options_set_udp_enabled(*toxOptions, !forceTCP); + tox_options_set_ipv6_enabled(*toxOptions, enableIPv6); + tox_options_set_local_discovery_enabled(*toxOptions, enableLanDiscovery); + tox_options_set_start_port(*toxOptions, 0); + tox_options_set_end_port(*toxOptions, 0); + + return toxOptions; +} + +bool ToxOptions::getIPv6Enabled() const +{ + return tox_options_get_ipv6_enabled(options); +} + +void ToxOptions::setIPv6Enabled(bool enabled) +{ + tox_options_set_ipv6_enabled(options, enabled); +} diff --git a/src/core/toxoptions.h b/src/core/toxoptions.h new file mode 100644 index 000000000..bdaeb9426 --- /dev/null +++ b/src/core/toxoptions.h @@ -0,0 +1,31 @@ +#ifndef TOXOPTIONS_H +#define TOXOPTIONS_H + +#include + +#include + +class ICoreSettings; +struct Tox_Options; + +class ToxOptions +{ +public: + ~ToxOptions(); + ToxOptions(ToxOptions&& from); + operator Tox_Options*(); + const char* getProxyAddrData() const; + static std::unique_ptr makeToxOptions(const QByteArray& savedata, + const ICoreSettings* s); + bool getIPv6Enabled() const; + void setIPv6Enabled(bool enabled); + +private: + ToxOptions(Tox_Options* options, const QByteArray& proxyAddrData); + +private: + Tox_Options* options = nullptr; + QByteArray proxyAddrData; +}; + +#endif // TOXOPTIONS_H diff --git a/src/main.cpp b/src/main.cpp index ba6166f14..30cb246b3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -140,7 +140,6 @@ void logMessageHandler(QtMsgType type, const QMessageLogContext& ctxt, const QSt int main(int argc, char* argv[]) { - #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); @@ -148,15 +147,16 @@ int main(int argc, char* argv[]) qInstallMessageHandler(logMessageHandler); + // initialize random number generator + qsrand(time(nullptr)); + std::unique_ptr a(new QApplication(argc, argv)); #if defined(Q_OS_UNIX) // PosixSignalNotifier is used only for terminating signals, // so it's connected directly to quit() without any filtering. - QObject::connect(&PosixSignalNotifier::globalInstance(), - &PosixSignalNotifier::activated, - a.get(), - &QApplication::quit); + QObject::connect(&PosixSignalNotifier::globalInstance(), &PosixSignalNotifier::activated, + a.get(), &QApplication::quit); PosixSignalNotifier::watchCommonTerminatingSignals(); #endif @@ -193,10 +193,14 @@ int main(int argc, char* argv[]) parser.addVersionOption(); parser.addPositionalArgument("uri", QObject::tr("Tox URI to parse")); parser.addOption( - QCommandLineOption(QStringList() << "p" << "profile", QObject::tr("Starts new instance and loads specified profile."), + QCommandLineOption(QStringList() << "p" + << "profile", + QObject::tr("Starts new instance and loads specified profile."), QObject::tr("profile"))); parser.addOption( - QCommandLineOption(QStringList() << "l" << "login", QObject::tr("Starts new instance and opens the login screen."))); + QCommandLineOption(QStringList() << "l" + << "login", + QObject::tr("Starts new instance and opens the login screen."))); parser.process(*a); uint32_t profileId = Settings::getInstance().getCurrentProfileId(); @@ -279,7 +283,7 @@ int main(int argc, char* argv[]) profileName = parser.value("p"); if (!Profile::exists(profileName)) { qWarning() << "-p profile" << profileName + ".tox" - << "doesn't exist, opening login screen"; + << "doesn't exist, opening login screen"; doIpc = false; autoLogin = false; } else { @@ -315,8 +319,10 @@ int main(int argc, char* argv[]) // If someone else processed it, we're done here, no need to actually start qTox if (ipc.waitUntilAccepted(event, 2)) { if (eventType == "activate") { - qDebug() << "Another qTox instance is already running. If you want to start a second " - "instance, please open login screen (qtox -l) or start with a profile (qtox -p )."; + qDebug() + << "Another qTox instance is already running. If you want to start a second " + "instance, please open login screen (qtox -l) or start with a profile (qtox " + "-p )."; } else { qDebug() << "Event" << eventType << "was handled by other client."; } @@ -327,8 +333,7 @@ int main(int argc, char* argv[]) Profile* profile = nullptr; // Autologin - if (autoLogin && Profile::exists(profileName) && - !Profile::isEncrypted(profileName)) { + if (autoLogin && Profile::exists(profileName) && !Profile::isEncrypted(profileName)) { profile = Profile::loadProfile(profileName); } else { LoginScreen loginScreen{profileName}; diff --git a/src/model/chatroom/chatroom.h b/src/model/chatroom/chatroom.h new file mode 100644 index 000000000..e0203ff26 --- /dev/null +++ b/src/model/chatroom/chatroom.h @@ -0,0 +1,31 @@ +/* + Copyright © 2014-2018 by The qTox Project Contributors + + This file is part of qTox, a Qt-based graphical interface for Tox. + + qTox is libre 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. + + qTox 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 qTox. If not, see . +*/ + +#ifndef MODEL_CHATROOM_H +#define MODEL_CHATROOM_H + +#include "src/model/contact.h" + +class Chatroom +{ +public: + virtual Contact* getContact() = 0; +}; + +#endif /* MODEL_CHATROOM_H */ diff --git a/src/model/chatroom/friendchatroom.cpp b/src/model/chatroom/friendchatroom.cpp new file mode 100644 index 000000000..96a339ce2 --- /dev/null +++ b/src/model/chatroom/friendchatroom.cpp @@ -0,0 +1,143 @@ +#include "src/grouplist.h" +#include "src/model/chatroom/friendchatroom.h" +#include "src/model/friend.h" +#include "src/model/group.h" +#include "src/persistence/settings.h" +#include "src/widget/contentdialog.h" + +#include + +namespace { + +QString getShortName(const QString& name) +{ + constexpr auto MAX_NAME_LENGTH = 30; + if (name.length() <= MAX_NAME_LENGTH) { + return name; + } + + return name.left(MAX_NAME_LENGTH).trimmed() + "…"; +} + +} + +FriendChatroom::FriendChatroom(Friend* frnd) + : frnd{frnd} +{ +} + +Friend* FriendChatroom::getFriend() +{ + return frnd; +} + +Contact* FriendChatroom::getContact() +{ + return frnd; +} + +void FriendChatroom::setActive(bool _active) +{ + if (active != _active) { + active = _active; + emit activeChanged(active); + } +} + +bool FriendChatroom::canBeInvited() const +{ + return frnd->getStatus() != Status::Offline; +} + +int FriendChatroom::getCircleId() const +{ + return Settings::getInstance().getFriendCircleID(frnd->getPublicKey()); +} + +QString FriendChatroom::getCircleName() const +{ + const auto circleId = getCircleId(); + return Settings::getInstance().getCircleName(circleId); +} + +void FriendChatroom::inviteToNewGroup() +{ + auto core = Core::getInstance(); + const auto friendId = frnd->getId(); + const auto groupId = core->createGroup(); + core->groupInviteFriend(friendId, groupId); +} + +QString FriendChatroom::getAutoAcceptDir() const +{ + const auto pk = frnd->getPublicKey(); + return Settings::getInstance().getAutoAcceptDir(pk); +} + +void FriendChatroom::setAutoAcceptDir(const QString& dir) +{ + const auto pk = frnd->getPublicKey(); + Settings::getInstance().setAutoAcceptDir(pk, dir); +} + +void FriendChatroom::disableAutoAccept() +{ + setAutoAcceptDir(QString{}); +} + +bool FriendChatroom::autoAcceptEnabled() const +{ + return getAutoAcceptDir().isEmpty(); +} + +void FriendChatroom::inviteFriend(const Group* group) +{ + const auto friendId = frnd->getId(); + const auto groupId = group->getId(); + Core::getInstance()->groupInviteFriend(friendId, groupId); +} + +QVector FriendChatroom::getGroups() const +{ + QVector groups; + for (const auto group : GroupList::getAllGroups()) { + const auto name = getShortName(group->getName()); + const GroupToDisplay groupToDisplay = { name, group }; + groups.push_back(groupToDisplay); + } + + return groups; +} + +/** + * @brief Return sorted list of circles exclude current circle. + */ +QVector FriendChatroom::getOtherCircles() const +{ + QVector circles; + const auto currentCircleId = getCircleId(); + const auto& s = Settings::getInstance(); + for (int i = 0; i < s.getCircleCount(); ++i) { + if (i == currentCircleId) { + continue; + } + + const auto name = getShortName(s.getCircleName(i)); + const CircleToDisplay circle = { name, i }; + circles.push_back(circle); + } + + std::sort(circles.begin(), circles.end(), + [](const CircleToDisplay& a, const CircleToDisplay& b) -> bool { + QCollator collator; + collator.setNumericMode(true); + return collator.compare(a.name, b.name) < 0; + }); + + return circles; +} + +void FriendChatroom::resetEventFlags() +{ + frnd->setEventFlag(false); +} diff --git a/src/model/chatroom/friendchatroom.h b/src/model/chatroom/friendchatroom.h new file mode 100644 index 000000000..d990c3f54 --- /dev/null +++ b/src/model/chatroom/friendchatroom.h @@ -0,0 +1,84 @@ +/* + Copyright © 2014-2017 by The qTox Project Contributors + + This file is part of qTox, a Qt-based graphical interface for Tox. + + qTox is libre 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. + + qTox 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 qTox. If not, see . +*/ + +#ifndef FRIEND_CHATROOM_H +#define FRIEND_CHATROOM_H + +#include "chatroom.h" + +#include +#include +#include + +class Friend; +class Group; + +struct GroupToDisplay +{ + QString name; + Group* group; +}; + +struct CircleToDisplay +{ + QString name; + int circleId; +}; + +class FriendChatroom : public QObject, public Chatroom +{ + Q_OBJECT +public: + FriendChatroom(Friend* frnd); + + Contact* getContact() override; + +public slots: + + Friend* getFriend(); + + void setActive(bool active); + + bool canBeInvited() const; + + int getCircleId() const; + QString getCircleName() const; + + void inviteToNewGroup(); + void inviteFriend(const Group* group); + + bool autoAcceptEnabled() const; + QString getAutoAcceptDir() const; + void disableAutoAccept(); + void setAutoAcceptDir(const QString& dir); + + QVector getGroups() const; + QVector getOtherCircles() const; + + void resetEventFlags(); + +signals: + void activeChanged(bool activated); + +private: + bool active{false}; + Friend* frnd{nullptr}; +}; + +#endif // FRIEND_H diff --git a/src/model/chatroom/groupchatroom.cpp b/src/model/chatroom/groupchatroom.cpp new file mode 100644 index 000000000..ee03c11ce --- /dev/null +++ b/src/model/chatroom/groupchatroom.cpp @@ -0,0 +1,51 @@ +#include "groupchatroom.h" + +#include "src/core/core.h" +#include "src/core/toxpk.h" +#include "src/friendlist.h" +#include "src/model/friend.h" +#include "src/model/group.h" +#include "src/persistence/settings.h" + +GroupChatroom::GroupChatroom(Group* group) + : group{group} +{ +} + +Contact* GroupChatroom::getContact() +{ + return group; +} + +Group* GroupChatroom::getGroup() +{ + return group; +} + +bool GroupChatroom::hasNewMessage() const +{ + return group->getEventFlag(); +} + +void GroupChatroom::resetEventFlags() +{ + group->setEventFlag(false); + group->setMentionedFlag(false); +} + +bool GroupChatroom::friendExists(const ToxPk& pk) +{ + return FriendList::findFriend(pk) != nullptr; +} + +void GroupChatroom::inviteFriend(const ToxPk& pk) +{ + const Friend* frnd = FriendList::findFriend(pk); + const auto friendId = frnd->getId(); + const auto groupId = group->getId(); + const auto canInvite = frnd->getStatus() != Status::Offline; + + if (canInvite) { + Core::getInstance()->groupInviteFriend(friendId, groupId); + } +} diff --git a/src/model/chatroom/groupchatroom.h b/src/model/chatroom/groupchatroom.h new file mode 100644 index 000000000..5905f71a2 --- /dev/null +++ b/src/model/chatroom/groupchatroom.h @@ -0,0 +1,49 @@ +/* + Copyright © 2014-2018 by The qTox Project Contributors + + This file is part of qTox, a Qt-based graphical interface for Tox. + + qTox is libre 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. + + qTox 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 qTox. If not, see . +*/ + +#ifndef GROUP_CHATROOM_H +#define GROUP_CHATROOM_H + +#include "chatroom.h" + +#include + +class Group; +class ToxPk; + +class GroupChatroom : public QObject, public Chatroom +{ + Q_OBJECT +public: + GroupChatroom(Group* group); + + Contact* getContact() override; + + Group* getGroup(); + + bool hasNewMessage() const; + void resetEventFlags(); + + bool friendExists(const ToxPk& pk); + void inviteFriend(const ToxPk& pk); +private: + Group* group{nullptr}; +}; + +#endif /* GROUP_CHATROOM_H */ diff --git a/src/model/profile/profileinfo.cpp b/src/model/profile/profileinfo.cpp index b363e2ecd..738355fb3 100644 --- a/src/model/profile/profileinfo.cpp +++ b/src/model/profile/profileinfo.cpp @@ -19,14 +19,14 @@ #include "profileinfo.h" #include "src/core/core.h" +#include "src/nexus.h" #include "src/persistence/profile.h" #include "src/persistence/settings.h" -#include "src/nexus.h" #include +#include #include #include -#include /** * @class ProfileInfo @@ -41,7 +41,7 @@ * @param profile Pointer to Profile. * @note All pointers parameters shouldn't be null. */ -ProfileInfo::ProfileInfo(Core* core, Profile *profile) +ProfileInfo::ProfileInfo(Core* core, Profile* profile) : profile{profile} , core{core} { @@ -55,7 +55,7 @@ ProfileInfo::ProfileInfo(Core* core, Profile *profile) * @param password New password. * @return True on success, false otherwise. */ -bool ProfileInfo::setPassword(const QString &password) +bool ProfileInfo::setPassword(const QString& password) { QString errorMsg = profile->setPassword(password); return errorMsg.isEmpty(); @@ -98,7 +98,7 @@ void ProfileInfo::copyId() const * @brief Set self user name. * @param name New name. */ -void ProfileInfo::setUsername(const QString &name) +void ProfileInfo::setUsername(const QString& name) { core->setUsername(name); } @@ -107,7 +107,7 @@ void ProfileInfo::setUsername(const QString &name) * @brief Set self status message. * @param status New status message. */ -void ProfileInfo::setStatusMessage(const QString &status) +void ProfileInfo::setStatusMessage(const QString& status) { core->setStatusMessage(status); } @@ -152,7 +152,7 @@ static QString sanitize(const QString& src) * @param name New profile name. * @return Result code of rename operation. */ -IProfileInfo::RenameResult ProfileInfo::renameProfile(const QString &name) +IProfileInfo::RenameResult ProfileInfo::renameProfile(const QString& name) { QString cur = profile->getName(); if (name.isEmpty()) { @@ -191,7 +191,7 @@ static bool tryRemoveFile(const QString& filepath) * @param path Path to save profile. * @return Result code of save operation. */ -IProfileInfo::SaveResult ProfileInfo::exportProfile(const QString &path) const +IProfileInfo::SaveResult ProfileInfo::exportProfile(const QString& path) const { QString current = profile->getName() + Core::TOX_EXT; if (path.isEmpty()) { @@ -284,7 +284,7 @@ QByteArray picToPng(const QPixmap& pic) * @param path Path to image, which should be the new avatar. * @return Code of set avatar operation. */ -IProfileInfo::SetAvatarResult ProfileInfo::setAvatar(const QString &path) +IProfileInfo::SetAvatarResult ProfileInfo::setAvatar(const QString& path) { if (path.isEmpty()) { return SetAvatarResult::EmptyPath; @@ -327,7 +327,7 @@ IProfileInfo::SetAvatarResult ProfileInfo::setAvatar(const QString &path) return SetAvatarResult::TooLarge; } - profile->setAvatar(bytes, core->getSelfPublicKey()); + profile->setAvatar(bytes); return SetAvatarResult::OK; } @@ -336,5 +336,5 @@ IProfileInfo::SetAvatarResult ProfileInfo::setAvatar(const QString &path) */ void ProfileInfo::removeAvatar() { - profile->removeAvatar(); + profile->removeSelfAvatar(); } diff --git a/src/nexus.cpp b/src/nexus.cpp index 446162618..d006e3919 100644 --- a/src/nexus.cpp +++ b/src/nexus.cpp @@ -61,8 +61,7 @@ Nexus::Nexus(QObject* parent) , profile{nullptr} , widget{nullptr} , running{true} -{ -} +{} Nexus::~Nexus() { @@ -194,9 +193,9 @@ void Nexus::showMainGUI() connect(core, &Core::connected, widget, &Widget::onConnected); connect(core, &Core::disconnected, widget, &Widget::onDisconnected); - connect(core, &Core::failedToStart, widget, &Widget::onFailedToStartCore, + connect(profile, &Profile::failedToStart, widget, &Widget::onFailedToStartCore, Qt::BlockingQueuedConnection); - connect(core, &Core::badProxy, widget, &Widget::onBadProxyCore, Qt::BlockingQueuedConnection); + connect(profile, &Profile::badProxy, widget, &Widget::onBadProxyCore, Qt::BlockingQueuedConnection); connect(core, &Core::statusSet, widget, &Widget::onStatusSet); connect(core, &Core::usernameSet, widget, &Widget::setUsername); connect(core, &Core::statusMessageSet, widget, &Widget::setStatusMessage); @@ -209,7 +208,8 @@ void Nexus::showMainGUI() connect(core, &Core::friendMessageReceived, widget, &Widget::onFriendMessageReceived); connect(core, &Core::groupInviteReceived, widget, &Widget::onGroupInviteReceived); connect(core, &Core::groupMessageReceived, widget, &Widget::onGroupMessageReceived); - connect(core, &Core::groupNamelistChanged, widget, &Widget::onGroupNamelistChangedOld); // TODO(sudden6): toxcore < 0.2.0, remove + connect(core, &Core::groupNamelistChanged, widget, + &Widget::onGroupNamelistChangedOld); // TODO(sudden6): toxcore < 0.2.0, remove connect(core, &Core::groupPeerlistChanged, widget, &Widget::onGroupPeerlistChanged); connect(core, &Core::groupPeerNameChanged, widget, &Widget::onGroupPeerNameChanged); connect(core, &Core::groupTitleChanged, widget, &Widget::onGroupTitleChanged); @@ -224,6 +224,8 @@ void Nexus::showMainGUI() connect(widget, &Widget::friendRequestAccepted, core, &Core::acceptFriendRequest); profile->startCore(); + + GUI::setEnabled(true); } /** diff --git a/src/persistence/profile.cpp b/src/persistence/profile.cpp index b84d5b711..6686aa230 100644 --- a/src/persistence/profile.cpp +++ b/src/persistence/profile.cpp @@ -32,6 +32,7 @@ #include "profilelocker.h" #include "settings.h" #include "src/core/core.h" +#include "src/core/corefile.h" #include "src/net/avatarbroadcaster.h" #include "src/nexus.h" #include "src/widget/gui.h" @@ -51,6 +52,35 @@ QStringList Profile::profiles; +void Profile::initCore(const QByteArray& toxsave, ICoreSettings& s) +{ + Core::ToxCoreErrors err; + core = Core::makeToxCore(toxsave, &s, &err); + if (!core) { + switch (err) { + case Core::ToxCoreErrors::BAD_PROXY: + emit badProxy(); + break; + case Core::ToxCoreErrors::ERROR_ALLOC: + case Core::ToxCoreErrors::FAILED_TO_START: + case Core::ToxCoreErrors::INVALID_SAVE: + default: + emit failedToStart(); + } + + qDebug() << "failed to start ToxCore"; + return; + } + + // save tox file when Core requests it + connect(core.get(), &Core::saveRequest, this, &Profile::onSaveToxSave); + // react to avatar changes + connect(core.get(), &Core::friendAvatarRemoved, this, &Profile::removeAvatar); + connect(core.get(), &Core::friendAvatarData, this, &Profile::saveAvatar); + connect(core.get(), &Core::fileAvatarOfferReceived, this, &Profile::onAvatarOfferReceived, + Qt::ConnectionType::QueuedConnection); +} + Profile::Profile(QString name, const QString& password, bool isNewProfile, const QByteArray& toxsave) : name{name} , newProfile{isNewProfile} @@ -60,24 +90,10 @@ Profile::Profile(QString name, const QString& password, bool isNewProfile, const s.setCurrentProfile(name); s.saveGlobal(); - coreThread = new QThread(); - coreThread->setObjectName("qTox Core"); - core = new Core(coreThread, *this, &Settings::getInstance()); - QObject::connect(core, &Core::idSet, this, - [this, password](const ToxId& id) { loadDatabase(id, password); }, - Qt::QueuedConnection); - core->moveToThread(coreThread); - QObject::connect(coreThread, &QThread::started, core, [=]() { - core->start(toxsave); + initCore(toxsave, s); - const ToxPk selfPk = core->getSelfPublicKey(); - QByteArray data = loadAvatarData(selfPk); - if (data.isEmpty()) { - qDebug() << "Self avatar not found, will broadcast empty avatar to friends"; - } - - setAvatar(data, selfPk); - }); + const ToxId& selfId = core->getSelfId(); + loadDatabase(selfId, password); } /** @@ -91,8 +107,7 @@ Profile::Profile(QString name, const QString& password, bool isNewProfile, const Profile* Profile::loadProfile(QString name, const QString& password) { if (ProfileLocker::hasLock()) { - qCritical() << "Tried to load profile " << name - << ", but another profile is already locked!"; + qCritical() << "Tried to load profile " << name << ", but another profile is already locked!"; return nullptr; } @@ -186,8 +201,7 @@ Profile* Profile::createProfile(QString name, QString password) } if (ProfileLocker::hasLock()) { - qCritical() << "Tried to create profile " << name - << ", but another profile is already locked!"; + qCritical() << "Tried to create profile " << name << ", but another profile is already locked!"; return nullptr; } @@ -214,14 +228,9 @@ Profile* Profile::createProfile(QString name, QString password) Profile::~Profile() { if (!isRemoved && core->isReady()) { - saveToxSave(); + onSaveToxSave(); } - core->deleteLater(); - while (coreThread->isRunning()) - qApp->processEvents(); - - delete coreThread; if (!isRemoved) { Settings::getInstance().savePersonal(this); Settings::getInstance().sync(); @@ -275,7 +284,8 @@ QStringList Profile::getProfiles() Core* Profile::getCore() { - return core; + // TODO(sudden6): this is evil + return core.get(); } QString Profile::getName() const @@ -288,7 +298,18 @@ QString Profile::getName() const */ void Profile::startCore() { - coreThread->start(); + core->start(); + + const ToxId& selfId = core->getSelfId(); + const ToxPk& selfPk = selfId.getPublicKey(); + const QByteArray data = loadAvatarData(selfPk); + if (data.isEmpty()) { + qDebug() << "Self avatar not found, will broadcast empty avatar to friends"; + } + // TODO(sudden6): moved here, because it crashes in the constructor + // reason: Core::getInstance() returns nullptr, because it's not yet initialized + // solution: kill Core::getInstance + setAvatar(data); } bool Profile::isNewProfile() @@ -300,7 +321,7 @@ bool Profile::isNewProfile() * @brief Saves the profile's .tox save, encrypted if needed. * @warning Invalid on deleted profiles. */ -void Profile::saveToxSave() +void Profile::onSaveToxSave() { assert(core->isReady()); QByteArray data = core->getToxSaveData(); @@ -308,12 +329,21 @@ void Profile::saveToxSave() saveToxSave(data); } +// TODO(sudden6): handle this better maybe? +void Profile::onAvatarOfferReceived(uint32_t friendId, uint32_t fileId, const QByteArray& avatarHash) +{ + // accept if we don't have it already + const bool accept = getAvatarHash(core->getFriendPublicKey(friendId)) != avatarHash; + CoreFile::handleAvatarOffer(friendId, fileId, accept); +} + /** * @brief Write the .tox save, encrypted if needed. * @param data Byte array of profile save. + * @return true if successfully saved, false otherwise * @warning Invalid on deleted profiles. */ -void Profile::saveToxSave(QByteArray data) +bool Profile::saveToxSave(QByteArray data) { assert(!isRemoved); ProfileLocker::assertLock(); @@ -324,7 +354,7 @@ void Profile::saveToxSave(QByteArray data) QSaveFile saveFile(path); if (!saveFile.open(QIODevice::WriteOnly)) { qCritical() << "Tox save file " << path << " couldn't be opened"; - return; + return false; } if (encrypted) { @@ -332,7 +362,7 @@ void Profile::saveToxSave(QByteArray data) if (data.isEmpty()) { qCritical() << "Failed to encrypt, can't save!"; saveFile.cancelWriting(); - return; + return false; } } @@ -345,7 +375,9 @@ void Profile::saveToxSave(QByteArray data) } else { saveFile.cancelWriting(); qCritical() << "Failed to write, can't save!"; + return false; } + return true; } /** @@ -374,8 +406,7 @@ QString Profile::avatarPath(const ToxPk& owner, bool forceUnencrypted) QByteArray hash(hashSize, 0); crypto_generichash((uint8_t*)hash.data(), hashSize, (uint8_t*)idData.data(), idData.size(), (uint8_t*)pubkeyData.data(), pubkeyData.size()); - return Settings::getInstance().getSettingsDirPath() + "avatars/" + hash.toHex().toUpper() - + ".png"; + return Settings::getInstance().getSettingsDirPath() + "avatars/" + hash.toHex().toUpper() + ".png"; } /** @@ -465,26 +496,31 @@ void Profile::loadDatabase(const ToxId& id, QString password) } } -void Profile::setAvatar(QByteArray pic, const ToxPk& owner) +/** + * @brief Sets our own avatar + * @param pic Picture to use as avatar, if empty an Identicon will be used depending on settings + * @param owner + */ +void Profile::setAvatar(QByteArray pic) { QPixmap pixmap; QByteArray avatarData; + const ToxPk& selfPk = core->getSelfPublicKey(); if (!pic.isEmpty()) { pixmap.loadFromData(pic); avatarData = pic; } else { if (Settings::getInstance().getShowIdenticons()) { // with IDENTICON_ROWS=5 this gives a 160x160 image file - const QImage identicon = Identicon(owner.getKey()).toImage(32); + const QImage identicon = Identicon(selfPk.getKey()).toImage(32); pixmap = QPixmap::fromImage(identicon); } else { pixmap.load(":/img/contact_dark.svg"); } - } - saveAvatar(avatarData, owner); + saveAvatar(selfPk, avatarData); emit selfAvatarChanged(pixmap); AvatarBroadcaster::setAvatar(avatarData); @@ -514,11 +550,10 @@ void Profile::onRequestSent(const ToxPk& friendPk, const QString& message) * @param pic Picture to save. * @param owner PK of avatar owner. */ -void Profile::saveAvatar(QByteArray pic, const ToxPk& owner) +void Profile::saveAvatar(const ToxPk& owner, const QByteArray& avatar) { - if (encrypted && !pic.isEmpty()) { - pic = passkey->encrypt(pic); - } + const bool needEncrypt = encrypted && !avatar.isEmpty(); + const QByteArray& pic = needEncrypt ? passkey->encrypt(avatar) : avatar; QString path = avatarPath(owner); QDir(Settings::getInstance().getSettingsDirPath()).mkdir("avatars"); @@ -551,7 +586,7 @@ QByteArray Profile::getAvatarHash(const ToxPk& owner) /** * @brief Removes our own avatar. */ -void Profile::removeAvatar() +void Profile::removeSelfAvatar() { removeAvatar(core->getSelfId().getPublicKey()); } @@ -583,7 +618,7 @@ void Profile::removeAvatar(const ToxPk& owner) { QFile::remove(avatarPath(owner)); if (owner == core->getSelfId().getPublicKey()) { - setAvatar({}, core->getSelfPublicKey()); + setAvatar({}); } } @@ -716,11 +751,23 @@ const ToxEncrypt* Profile::getPasskey() const void Profile::restartCore() { GUI::setEnabled(false); // Core::reset re-enables it - if (!isRemoved && core->isReady()) { - saveToxSave(); + + if (core && !isRemoved) { + // TODO(sudden6): there's a potential race condition between unlocking the core loop + // and killing the core + const QByteArray& savedata = core->getToxSaveData(); + + // save to disk just in case + if (saveToxSave(savedata)) { + qDebug() << "Restarting Core"; + initCore(savedata, Settings::getInstance()); + core->start(); + } else { + qCritical() << "Failed to save, not restarting core"; + } } - QMetaObject::invokeMethod(core, "reset"); + GUI::setEnabled(true); } /** @@ -749,7 +796,7 @@ QString Profile::setPassword(const QString& newPassword) } // apply new encryption - saveToxSave(); + onSaveToxSave(); bool dbSuccess = false; @@ -767,13 +814,13 @@ QString Profile::setPassword(const QString& newPassword) Nexus::getDesktopGUI()->reloadHistory(); QByteArray avatar = loadAvatarData(core->getSelfId().getPublicKey()); - saveAvatar(avatar, core->getSelfId().getPublicKey()); + saveAvatar(core->getSelfId().getPublicKey(), avatar); QVector friendList = core->getFriendList(); QVectorIterator i(friendList); while (i.hasNext()) { const ToxPk friendPublicKey = core->getFriendPublicKey(i.next()); - saveAvatar(loadAvatarData(friendPublicKey), friendPublicKey); + saveAvatar(friendPublicKey, loadAvatarData(friendPublicKey)); } return error; } diff --git a/src/persistence/profile.h b/src/persistence/profile.h index d0feda86e..377be8dcf 100644 --- a/src/persistence/profile.h +++ b/src/persistence/profile.h @@ -21,6 +21,7 @@ #ifndef PROFILE_H #define PROFILE_H +#include "src/core/core.h" #include "src/core/toxencrypt.h" #include "src/core/toxid.h" @@ -33,9 +34,6 @@ #include #include -class Core; -class QThread; - class Profile : public QObject { Q_OBJECT @@ -55,17 +53,12 @@ public: QString setPassword(const QString& newPassword); const ToxEncrypt* getPasskey() const; - void saveToxSave(); - void saveToxSave(QByteArray data); - QPixmap loadAvatar(); QPixmap loadAvatar(const ToxPk& owner); QByteArray loadAvatarData(const ToxPk& owner); - void setAvatar(QByteArray pic, const ToxPk& owner); - void saveAvatar(QByteArray pic, const ToxPk& owner); + void setAvatar(QByteArray pic); QByteArray getAvatarHash(const ToxPk& owner); - void removeAvatar(const ToxPk& owner); - void removeAvatar(); + void removeSelfAvatar(); bool isHistoryEnabled(); History* getHistory(); @@ -84,20 +77,30 @@ public: signals: void selfAvatarChanged(const QPixmap& pixmap); + // TODO(sudden6): this doesn't seem to be the right place for Core errors + void failedToStart(); + void badProxy(); + public slots: void onRequestSent(const ToxPk& friendPk, const QString& message); private slots: void loadDatabase(const ToxId& id, QString password); + void saveAvatar(const ToxPk& owner, const QByteArray& avatar); + void removeAvatar(const ToxPk& owner); + void onSaveToxSave(); + // TODO(sudden6): use ToxPk instead of friendId + void onAvatarOfferReceived(uint32_t friendId, uint32_t fileId, const QByteArray& avatarHash); private: Profile(QString name, const QString& password, bool newProfile, const QByteArray& toxsave); static QStringList getFilesByExt(QString extension); QString avatarPath(const ToxPk& owner, bool forceUnencrypted = false); + bool saveToxSave(QByteArray data); + void initCore(const QByteArray& toxsave, ICoreSettings& s); private: - Core* core; - QThread* coreThread; + std::unique_ptr core = nullptr; QString name; std::unique_ptr passkey = nullptr; std::shared_ptr database; diff --git a/src/persistence/settings.cpp b/src/persistence/settings.cpp index 15f39577d..70552b4be 100644 --- a/src/persistence/settings.cpp +++ b/src/persistence/settings.cpp @@ -231,6 +231,7 @@ void Settings::loadGlobal() lightTrayIcon = s.value("lightTrayIcon", false).toBool(); useEmoticons = s.value("useEmoticons", true).toBool(); statusChangeNotificationEnabled = s.value("statusChangeNotificationEnabled", false).toBool(); + spellCheckingEnabled = s.value("spellCheckingEnabled", true).toBool(); themeColor = s.value("themeColor", 0).toInt(); style = s.value("style", "").toString(); if (style == "") // Default to Fusion if available, otherwise no style @@ -547,6 +548,7 @@ void Settings::saveGlobal() s.setValue("themeColor", themeColor); s.setValue("style", style); s.setValue("statusChangeNotificationEnabled", statusChangeNotificationEnabled); + s.setValue("spellCheckingEnabled", spellCheckingEnabled); } s.endGroup(); @@ -1046,6 +1048,22 @@ void Settings::setStatusChangeNotificationEnabled(bool newValue) } } +bool Settings::getSpellCheckingEnabled() const +{ + const QMutexLocker locker{&bigLock}; + return spellCheckingEnabled; +} + +void Settings::setSpellCheckingEnabled(bool newValue) +{ + QMutexLocker locker{&bigLock}; + + if (newValue != spellCheckingEnabled) { + spellCheckingEnabled = newValue; + emit statusChangeNotificationEnabledChanged(statusChangeNotificationEnabled); + } +} + bool Settings::getShowInFront() const { QMutexLocker locker{&bigLock}; diff --git a/src/persistence/settings.h b/src/persistence/settings.h index 45f31810d..a20555a8a 100644 --- a/src/persistence/settings.h +++ b/src/persistence/settings.h @@ -91,6 +91,8 @@ class Settings : public QObject, public ICoreSettings, public IFriendSettings, Q_PROPERTY(QString dateFormat READ getDateFormat WRITE setDateFormat NOTIFY dateFormatChanged FINAL) Q_PROPERTY(bool statusChangeNotificationEnabled READ getStatusChangeNotificationEnabled WRITE setStatusChangeNotificationEnabled NOTIFY statusChangeNotificationEnabledChanged FINAL) + Q_PROPERTY(bool spellCheckingEnabled READ getSpellCheckingEnabled WRITE + setSpellCheckingEnabled NOTIFY spellCheckingEnabledChanged FINAL) // Privacy Q_PROPERTY(bool typingNotification READ getTypingNotification WRITE setTypingNotification NOTIFY @@ -212,6 +214,7 @@ signals: void timestampFormatChanged(const QString& format); void dateFormatChanged(const QString& format); void statusChangeNotificationEnabledChanged(bool enabled); + void spellCheckingEnabledChanged(bool enabled); void fauxOfflineMessagingChanged(bool enabled); // Privacy @@ -449,6 +452,9 @@ public: bool getStatusChangeNotificationEnabled() const; void setStatusChangeNotificationEnabled(bool newValue); + bool getSpellCheckingEnabled() const; + void setSpellCheckingEnabled(bool newValue); + // Privacy bool getTypingNotification() const; void setTypingNotification(bool enabled); @@ -641,6 +647,7 @@ private: QString timestampFormat; QString dateFormat; bool statusChangeNotificationEnabled; + bool spellCheckingEnabled; // Privacy bool typingNotification; diff --git a/src/video/corevideosource.h b/src/video/corevideosource.h index eb98686ad..d5f2af965 100644 --- a/src/video/corevideosource.h +++ b/src/video/corevideosource.h @@ -50,7 +50,7 @@ private: std::atomic_bool stopped; friend class CoreAV; - friend class ToxCall; + friend class ToxFriendCall; }; #endif // COREVIDEOSOURCE_H diff --git a/src/video/genericnetcamview.h b/src/video/genericnetcamview.h index 272afd15e..283d1612e 100644 --- a/src/video/genericnetcamview.h +++ b/src/video/genericnetcamview.h @@ -50,11 +50,11 @@ public slots: protected: QVBoxLayout* verLayout; VideoSurface* videoSurface; + QPushButton* enterFullScreenButton = nullptr; private: QHBoxLayout* buttonLayout = nullptr; QPushButton* toggleMessagesButton = nullptr; - QPushButton* enterFullScreenButton = nullptr; QFrame* buttonPanel = nullptr; QPushButton* videoPreviewButton = nullptr; QPushButton* volumeButton = nullptr; diff --git a/src/video/groupnetcamview.cpp b/src/video/groupnetcamview.cpp index e2942d2df..d58d09e32 100644 --- a/src/video/groupnetcamview.cpp +++ b/src/video/groupnetcamview.cpp @@ -20,8 +20,8 @@ #include "groupnetcamview.h" #include "src/audio/audio.h" #include "src/core/core.h" -#include "src/model/friend.h" #include "src/friendlist.h" +#include "src/model/friend.h" #include "src/nexus.h" #include "src/persistence/profile.h" #include "src/video/videosurface.h" @@ -56,9 +56,7 @@ public: layout->addWidget(label); } - ~LabeledVideo() - { - } + ~LabeledVideo() {} VideoSurface* getVideoSurface() const { @@ -117,6 +115,9 @@ GroupNetCamView::GroupNetCamView(int group, QWidget* parent) videoLabelSurface->layout()->setMargin(0); videoLabelSurface->setStyleSheet("QFrame { background-color: black; }"); + // remove full screen button in audio group chat since it's useless there + enterFullScreenButton->hide(); + QSplitter* splitter = new QSplitter(Qt::Vertical, this); splitter->setChildrenCollapsible(false); verLayout->insertWidget(0, splitter, 1); @@ -153,7 +154,7 @@ GroupNetCamView::GroupNetCamView(int group, QWidget* parent) setActive(); }); - connect(Core::getInstance(), &Core::friendAvatarChanged, this, + connect(Core::getInstance(), &Core::friendAvatarChangedDeprecated, this, &GroupNetCamView::friendAvatarChanged); selfVideoSurface->setText(Core::getInstance()->getUsername()); diff --git a/src/video/netcamview.cpp b/src/video/netcamview.cpp index 10a6f27e3..82bfc82ef 100644 --- a/src/video/netcamview.cpp +++ b/src/video/netcamview.cpp @@ -20,8 +20,8 @@ #include "netcamview.h" #include "camerasource.h" #include "src/core/core.h" -#include "src/model/friend.h" #include "src/friendlist.h" +#include "src/model/friend.h" #include "src/nexus.h" #include "src/persistence/profile.h" #include "src/persistence/settings.h" @@ -75,7 +75,7 @@ NetCamView::NetCamView(int friendId, QWidget* parent) connections += connect(Nexus::getProfile(), &Profile::selfAvatarChanged, [this](const QPixmap& pixmap) { selfVideoSurface->setAvatar(pixmap); }); - connections += connect(Core::getInstance(), &Core::friendAvatarChanged, + connections += connect(Core::getInstance(), &Core::friendAvatarChangedDeprecated, [this](int FriendId, const QPixmap& pixmap) { if (this->friendId == FriendId) videoSurface->setAvatar(pixmap); diff --git a/src/widget/about/aboutfriendform.cpp b/src/widget/about/aboutfriendform.cpp index 0c47a26cb..05d323e31 100644 --- a/src/widget/about/aboutfriendform.cpp +++ b/src/widget/about/aboutfriendform.cpp @@ -4,10 +4,10 @@ #include #include -AboutFriendForm::AboutFriendForm(QPointer about, QWidget* parent) +AboutFriendForm::AboutFriendForm(std::unique_ptr _about, QWidget* parent) : QDialog(parent) , ui(new Ui::AboutFriendForm) - , about{about} + , about{std::move(_about)} { ui->setupUi(this); ui->label_4->hide(); @@ -19,7 +19,7 @@ AboutFriendForm::AboutFriendForm(QPointer about, QWidget* parent) connect(ui->autogroupinvite, &QCheckBox::clicked, this, &AboutFriendForm::onAutoGroupInvite); connect(ui->selectSaveDir, &QPushButton::clicked, this, &AboutFriendForm::onSelectDirClicked); connect(ui->removeHistory, &QPushButton::clicked, this, &AboutFriendForm::onRemoveHistoryClicked); - about.data()->connectTo_autoAcceptDirChanged([=](const QString& dir){ onAutoAcceptDirChanged(dir); }); + about->connectTo_autoAcceptDirChanged([=](const QString& dir){ onAutoAcceptDirChanged(dir); }); const QString dir = about->getAutoAcceptDir(); ui->autoacceptfile->setChecked(!dir.isEmpty()); diff --git a/src/widget/about/aboutfriendform.h b/src/widget/about/aboutfriendform.h index c98731fd6..d45b0012c 100644 --- a/src/widget/about/aboutfriendform.h +++ b/src/widget/about/aboutfriendform.h @@ -6,6 +6,8 @@ #include #include +#include + namespace Ui { class AboutFriendForm; } @@ -15,12 +17,12 @@ class AboutFriendForm : public QDialog Q_OBJECT public: - AboutFriendForm(QPointer about, QWidget* parent = 0); + AboutFriendForm(std::unique_ptr about, QWidget* parent = 0); ~AboutFriendForm(); private: Ui::AboutFriendForm* ui; - QPointer about; + const std::unique_ptr about; private slots: void onAutoAcceptDirChanged(const QString& path); diff --git a/src/widget/chatformheader.cpp b/src/widget/chatformheader.cpp index 70e072253..4852d88dc 100644 --- a/src/widget/chatformheader.cpp +++ b/src/widget/chatformheader.cpp @@ -156,6 +156,8 @@ ChatFormHeader::ChatFormHeader(QWidget* parent) Translator::registerHandler(std::bind(&ChatFormHeader::retranslateUi, this), this); } +ChatFormHeader::~ChatFormHeader() = default; + void ChatFormHeader::setName(const QString& newName) { nameLabel->setText(newName); diff --git a/src/widget/chatformheader.h b/src/widget/chatformheader.h index 6765539dc..bc1b89907 100644 --- a/src/widget/chatformheader.h +++ b/src/widget/chatformheader.h @@ -55,6 +55,7 @@ public: }; ChatFormHeader(QWidget* parent = nullptr); + ~ChatFormHeader(); void setName(const QString& newName); void setMode(Mode mode); diff --git a/src/widget/contentdialog.cpp b/src/widget/contentdialog.cpp index 44257c175..e4293b3f0 100644 --- a/src/widget/contentdialog.cpp +++ b/src/widget/contentdialog.cpp @@ -27,21 +27,22 @@ #include #include -#include "contentlayout.h" -#include "friendwidget.h" -#include "groupwidget.h" -#include "style.h" -#include "widget.h" #include "src/core/core.h" -#include "src/model/friend.h" #include "src/friendlist.h" -#include "src/model/group.h" #include "src/grouplist.h" +#include "src/model/chatroom/friendchatroom.h" +#include "src/model/friend.h" +#include "src/model/group.h" #include "src/persistence/settings.h" +#include "src/widget/contentlayout.h" +#include "src/widget/friendwidget.h" +#include "src/widget/groupwidget.h" #include "src/widget/form/chatform.h" #include "src/widget/friendlistlayout.h" +#include "src/widget/style.h" +#include "src/widget/tool/adjustingscrollarea.h" #include "src/widget/translator.h" -#include "tool/adjustingscrollarea.h" +#include "src/widget/widget.h" QString ContentDialog::username = ""; ContentDialog* ContentDialog::currentDialog = nullptr; @@ -162,11 +163,12 @@ ContentDialog::~ContentDialog() Translator::unregister(this); } -FriendWidget* ContentDialog::addFriend(const Friend* frnd, GenericChatForm* form) +FriendWidget* ContentDialog::addFriend(std::shared_ptr chatroom, GenericChatForm* form) { - bool compact = Settings::getInstance().getCompactLayout(); - uint32_t friendId = frnd->getId(); - FriendWidget* friendWidget = new FriendWidget(frnd, compact); + const auto compact = Settings::getInstance().getCompactLayout(); + auto frnd = chatroom->getFriend(); + auto friendId = frnd->getId(); + auto friendWidget = new FriendWidget(chatroom, compact); friendLayout->addFriendWidget(friendWidget, frnd->getStatus()); friendChatForms[friendId] = form; @@ -187,12 +189,12 @@ FriendWidget* ContentDialog::addFriend(const Friend* frnd, GenericChatForm* form return friendWidget; } -GroupWidget* ContentDialog::addGroup(const Group* g, GenericChatForm* form) +GroupWidget* ContentDialog::addGroup(std::shared_ptr chatroom, GenericChatForm* form) { + const auto g = chatroom->getGroup(); const auto groupId = g->getId(); - const auto name = g->getName(); const auto compact = Settings::getInstance().getCompactLayout(); - GroupWidget* groupWidget = new GroupWidget(groupId, name, compact); + GroupWidget* groupWidget = new GroupWidget(chatroom, compact); groupLayout.addSortedWidget(groupWidget); groupChatForms[groupId] = form; diff --git a/src/widget/contentdialog.h b/src/widget/contentdialog.h index 9ef586883..687d904b7 100644 --- a/src/widget/contentdialog.h +++ b/src/widget/contentdialog.h @@ -20,27 +20,30 @@ #ifndef CONTENTDIALOG_H #define CONTENTDIALOG_H -#include - #include "src/widget/genericchatitemlayout.h" #include "src/widget/tool/activatedialog.h" +#include +#include + template class QHash; template class QSet; -class QSplitter; -class QVBoxLayout; class ContentDialog; class ContentLayout; +class Friend; +class FriendChatroom; +class FriendListLayout; +class FriendWidget; class GenericChatForm; class GenericChatroomWidget; -class FriendWidget; -class GroupWidget; -class FriendListLayout; -class Friend; class Group; +class GroupChatroom; +class GroupWidget; +class QSplitter; +class QVBoxLayout; using ContactInfo = std::tuple; @@ -51,8 +54,8 @@ public: explicit ContentDialog(QWidget* parent = nullptr); ~ContentDialog() override; - FriendWidget* addFriend(const Friend* f, GenericChatForm* form); - GroupWidget* addGroup(const Group* g, GenericChatForm* form); + FriendWidget* addFriend(std::shared_ptr chatroom, GenericChatForm* form); + GroupWidget* addGroup(std::shared_ptr chatroom, GenericChatForm* form); void removeFriend(int friendId); void removeGroup(int groupId); bool hasFriendWidget(int friendId, const GenericChatroomWidget* chatroomWidget) const; diff --git a/src/widget/form/chatform.cpp b/src/widget/form/chatform.cpp index 86f1850d0..1523ecefc 100644 --- a/src/widget/form/chatform.cpp +++ b/src/widget/form/chatform.cpp @@ -27,21 +27,21 @@ #include "src/core/coreav.h" #include "src/model/friend.h" #include "src/nexus.h" +#include "src/persistence/history.h" #include "src/persistence/offlinemsgengine.h" #include "src/persistence/profile.h" #include "src/persistence/settings.h" -#include "src/persistence/history.h" #include "src/video/netcamview.h" #include "src/widget/chatformheader.h" #include "src/widget/form/loadhistorydialog.h" #include "src/widget/maskablepixmapwidget.h" +#include "src/widget/searchform.h" #include "src/widget/style.h" #include "src/widget/tool/callconfirmwidget.h" #include "src/widget/tool/chattextedit.h" #include "src/widget/tool/screenshotgrabber.h" #include "src/widget/translator.h" #include "src/widget/widget.h" -#include "src/widget/searchform.h" #include #include @@ -158,7 +158,8 @@ ChatForm::ChatForm(Friend* chatFriend, History* history) const Core* core = Core::getInstance(); connect(core, &Core::fileReceiveRequested, this, &ChatForm::onFileRecvRequest); - connect(core, &Core::friendAvatarChanged, this, &ChatForm::onAvatarChange); + // TODO(sudden6): update slot to new API + connect(core, &Core::friendAvatarChangedDeprecated, this, &ChatForm::onAvatarChange); connect(core, &Core::friendAvatarRemoved, this, &ChatForm::onAvatarRemoved); connect(core, &Core::fileSendStarted, this, &ChatForm::startFileSend); connect(core, &Core::fileSendFailed, this, &ChatForm::onFileSendFailed); @@ -196,12 +197,10 @@ ChatForm::ChatForm(Friend* chatFriend, History* history) }); // reflect name changes in the header - connect(headWidget, &ChatFormHeader::nameChanged, this, [=](const QString& newName) { - f->setAlias(newName); - }); - connect(headWidget, &ChatFormHeader::callAccepted, this, [this] { - onAnswerCallTriggered(lastCallIsVideo); - }); + connect(headWidget, &ChatFormHeader::nameChanged, this, + [=](const QString& newName) { f->setAlias(newName); }); + connect(headWidget, &ChatFormHeader::callAccepted, this, + [this] { onAnswerCallTriggered(lastCallIsVideo); }); connect(headWidget, &ChatFormHeader::callRejected, this, &ChatForm::onRejectCallTriggered); updateCallButtons(); @@ -219,6 +218,7 @@ ChatForm::~ChatForm() Translator::unregister(this); delete netcam; netcam = nullptr; + delete offlineEngine; } void ChatForm::setStatusMessage(const QString& newMessage) @@ -233,8 +233,12 @@ void ChatForm::onSendTriggered() SendMessageStr(msgEdit->toPlainText()); msgEdit->clear(); } -void ChatForm::onFileNameChanged() +void ChatForm::onFileNameChanged(const ToxPk& friendPk) { + if(friendPk != f->getPublicKey()) { + return; + } + QMessageBox::warning(this, tr("Filename contained illegal characters"), tr("Illegal characters have been changed to _ \n" "so you can save the file on windows.")); @@ -263,7 +267,8 @@ void ChatForm::onTextEditChanged() void ChatForm::onAttachClicked() { - QStringList paths = QFileDialog::getOpenFileNames(Q_NULLPTR, tr("Send a file"), QDir::homePath(), 0, 0); + QStringList paths = + QFileDialog::getOpenFileNames(Q_NULLPTR, tr("Send a file"), QDir::homePath(), 0, 0); if (paths.isEmpty()) { return; @@ -709,8 +714,9 @@ void ChatForm::dropEvent(QDropEvent* ev) file.close(); if (file.isSequential()) { - QMessageBox::critical(0, tr("Bad idea"), tr("You're trying to send a sequential file, " - "which is not going to work!")); + QMessageBox::critical(0, tr("Bad idea"), + tr("You're trying to send a sequential file, " + "which is not going to work!")); continue; } @@ -720,9 +726,9 @@ void ChatForm::dropEvent(QDropEvent* ev) } } -void ChatForm::onAvatarRemoved(uint32_t friendId) +void ChatForm::onAvatarRemoved(const ToxPk& friendPk) { - if (friendId != f->getId()) { + if (friendPk != f->getPublicKey()) { return; } @@ -818,7 +824,9 @@ void ChatForm::insertChatlines(QList chatLines) verticalBar->setValue(savedSliderPos); } -QDate ChatForm::addDateLineIfNeeded(QList msgs, QDate const& lastDate, History::HistMessage const& newMessage, MessageMetadata const& metadata) +QDate ChatForm::addDateLineIfNeeded(QList msgs, QDate const& lastDate, + History::HistMessage const& newMessage, + MessageMetadata const& metadata) { // Show the date every new day QDate newDate = metadata.msgDateTime.date(); @@ -842,11 +850,13 @@ ChatForm::MessageMetadata ChatForm::getMessageMetadata(History::HistMessage cons return {isSelf, needSending, isAction, id, authorPk, msgDateTime}; } -ChatMessage::Ptr ChatForm::chatMessageFromHistMessage(History::HistMessage const& histMessage, MessageMetadata const& metadata) +ChatMessage::Ptr ChatForm::chatMessageFromHistMessage(History::HistMessage const& histMessage, + MessageMetadata const& metadata) { ToxPk authorPk(ToxId(histMessage.sender).getPublicKey()); QString authorStr = getMsgAuthorDispName(authorPk, histMessage.dispName); - QString messageText = metadata.isAction ? histMessage.message.mid(ACTION_PREFIX.length()) : histMessage.message; + QString messageText = + metadata.isAction ? histMessage.message.mid(ACTION_PREFIX.length()) : histMessage.message; ChatMessage::MessageType type = metadata.isAction ? ChatMessage::ACTION : ChatMessage::NORMAL; QDateTime dateTime = metadata.needSending ? QDateTime() : metadata.msgDateTime; auto msg = ChatMessage::createChatMessage(authorStr, messageText, type, metadata.isSelf, dateTime); @@ -986,8 +996,7 @@ void ChatForm::stopCounter(bool error) QString mess = error ? tr("Call with %1 ended unexpectedly. %2") : tr("Call with %1 ended. %2"); // TODO: add notification once notifications are implemented - addSystemInfoMessage(mess.arg(name, dhms), ChatMessage::INFO, - QDateTime::currentDateTime()); + addSystemInfoMessage(mess.arg(name, dhms), ChatMessage::INFO, QDateTime::currentDateTime()); callDurationTimer->stop(); callDuration->setText(""); callDuration->hide(); @@ -1062,9 +1071,9 @@ void ChatForm::SendMessageStr(QString msg) QString pk = f->getPublicKey().toString(); QString name = Core::getInstance()->getUsername(); history->addNewMessage(pk, historyPart, selfPk, timestamp, status, name, - [offMsgEngine, rec, ma](int64_t id) { - offMsgEngine->registerReceipt(rec, id, ma); - }); + [offMsgEngine, rec, ma](int64_t id) { + offMsgEngine->registerReceipt(rec, id, ma); + }); } else { // TODO: Make faux-offline messaging work partially with the history disabled ma->markAsSent(QDateTime::currentDateTime()); @@ -1124,12 +1133,12 @@ void ChatForm::onExportChat() QString buffer; for (const auto& it : msgs) { - QString timestamp = it.timestamp.toString(); + QString timestamp = it.timestamp.time().toString("hh:mm:ss"); + QString datestamp = it.timestamp.date().toString("yyyy-MM-dd"); ToxPk authorPk(ToxId(it.sender).getPublicKey()); QString author = getMsgAuthorDispName(authorPk, it.dispName); - QString line = QString("%1\t%2\t%3\n").arg(timestamp, author, it.message); - buffer = buffer % line; + buffer = buffer % QString{datestamp % '\t' % timestamp % '\t' % author % '\t' % it.message % '\n'}; } file.write(buffer.toUtf8()); file.close(); diff --git a/src/widget/form/chatform.h b/src/widget/form/chatform.h index e948d2351..89554d343 100644 --- a/src/widget/form/chatform.h +++ b/src/widget/form/chatform.h @@ -73,8 +73,8 @@ public slots: void onAvStart(uint32_t friendId, bool video); void onAvEnd(uint32_t friendId, bool error); void onAvatarChange(uint32_t friendId, const QPixmap& pic); - void onAvatarRemoved(uint32_t friendId); - void onFileNameChanged(); + void onAvatarRemoved(const ToxPk& friendPk); + void onFileNameChanged(const ToxPk& friendPk); protected slots: void searchInBegin(const QString& phrase, const ParameterSearch& parameter) override; @@ -110,25 +110,30 @@ private slots: void onExportChat(); private: - struct MessageMetadata { + struct MessageMetadata + { const bool isSelf; const bool needSending; const bool isAction; const qint64 id; const ToxPk authorPk; const QDateTime msgDateTime; - MessageMetadata(bool isSelf, bool needSending, bool isAction, qint64 id, ToxPk authorPk, QDateTime msgDateTime) : - isSelf{isSelf}, - needSending{needSending}, - isAction{isAction}, - id{id}, - authorPk{authorPk}, - msgDateTime{msgDateTime} {} + MessageMetadata(bool isSelf, bool needSending, bool isAction, qint64 id, ToxPk authorPk, + QDateTime msgDateTime) + : isSelf{isSelf} + , needSending{needSending} + , isAction{isAction} + , id{id} + , authorPk{authorPk} + , msgDateTime{msgDateTime} + {} }; void handleLoadedMessages(QList newHistMsgs, bool processUndelivered); - QDate addDateLineIfNeeded(QList msgs, QDate const& lastDate, History::HistMessage const& newMessage, MessageMetadata const& metadata); + QDate addDateLineIfNeeded(QList msgs, QDate const& lastDate, + History::HistMessage const& newMessage, MessageMetadata const& metadata); MessageMetadata getMessageMetadata(History::HistMessage const& histMessage); - ChatMessage::Ptr chatMessageFromHistMessage(History::HistMessage const& histMessage, MessageMetadata const& metadata); + ChatMessage::Ptr chatMessageFromHistMessage(History::HistMessage const& histMessage, + MessageMetadata const& metadata); void sendLoadedMessage(ChatMessage::Ptr chatMsg, MessageMetadata const& metadata); void insertChatlines(QList chatLines); void updateMuteMicButton(); diff --git a/src/widget/form/genericchatform.cpp b/src/widget/form/genericchatform.cpp index c4d63f4f8..dcc1f4579 100644 --- a/src/widget/form/genericchatform.cpp +++ b/src/widget/form/genericchatform.cpp @@ -46,6 +46,11 @@ #include #include #include +#include + +#ifdef SPELL_CHECKING +#include +#endif /** * @class GenericChatForm @@ -143,6 +148,11 @@ GenericChatForm::GenericChatForm(const Contact* contact, QWidget* parent) connect(&s, &Settings::chatMessageFontChanged, this, &GenericChatForm::onChatMessageFontChanged); msgEdit = new ChatTextEdit(); +#ifdef SPELL_CHECKING + if (s.getSpellCheckingEnabled()) { + decorator = new Sonnet::SpellCheckDecorator(msgEdit); + } +#endif sendButton = createButton("sendButton", this, &GenericChatForm::onSendTriggered); emoteButton = createButton("emoteButton", this, &GenericChatForm::onEmoteButtonClicked); @@ -481,17 +491,16 @@ void GenericChatForm::onSaveLogClicked() for (ChatLine::Ptr l : lines) { Timestamp* rightCol = qobject_cast(l->getContent(2)); - if (!rightCol) - break; - ChatLineContent* middleCol = l->getContent(1); ChatLineContent* leftCol = l->getContent(0); - QString timestamp = rightCol->getTime().isNull() ? tr("Not sent") : rightCol->getText(); - QString nick = leftCol->getText(); + QString nick = leftCol->getText().isNull() ? tr("[System message]") : leftCol->getText(); + QString msg = middleCol->getText(); - plainText += QString("[%2] %1\n%3\n\n").arg(nick, timestamp, msg); + QString timestamp = (rightCol == nullptr) ? tr("Not sent") : rightCol->getText(); + + plainText += QString{nick % "\t" % timestamp % "\t" % msg % "\n"}; } file.write(plainText.toUtf8()); diff --git a/src/widget/form/genericchatform.h b/src/widget/form/genericchatform.h index 62b36e575..c91fa8314 100644 --- a/src/widget/form/genericchatform.h +++ b/src/widget/form/genericchatform.h @@ -55,6 +55,12 @@ namespace Ui { class MainWindow; } +#ifdef SPELL_CHECKING +namespace Sonnet { +class SpellCheckDecorator; +} +#endif + class GenericChatForm : public QWidget { Q_OBJECT @@ -170,6 +176,9 @@ protected: SearchForm *searchForm; ChatLog* chatWidget; ChatTextEdit* msgEdit; +#ifdef SPELL_CHECKING + Sonnet::SpellCheckDecorator* decorator{nullptr}; +#endif FlyoutOverlayWidget* fileFlyout; GenericNetCamView* netcam; Widget* parent; diff --git a/src/widget/form/settings/advancedform.cpp b/src/widget/form/settings/advancedform.cpp index c9d04b010..045a30184 100644 --- a/src/widget/form/settings/advancedform.cpp +++ b/src/widget/form/settings/advancedform.cpp @@ -55,18 +55,19 @@ AdvancedForm::AdvancedForm() Settings& s = Settings::getInstance(); bodyUI->cbEnableIPv6->setChecked(s.getEnableIPv6()); bodyUI->cbMakeToxPortable->setChecked(Settings::getInstance().getMakeToxPortable()); - const bool udpEnabled = !s.getForceTCP(); - bodyUI->cbEnableUDP->setChecked(udpEnabled); - bodyUI->cbEnableLanDiscovery->setChecked(s.getEnableLanDiscovery()); - bodyUI->cbEnableLanDiscovery->setEnabled(udpEnabled); bodyUI->proxyAddr->setText(s.getProxyAddr()); quint16 port = s.getProxyPort(); - if (port > 0) + if (port > 0) { bodyUI->proxyPort->setValue(port); + } int index = static_cast(s.getProxyType()); bodyUI->proxyType->setCurrentIndex(index); on_proxyType_currentIndexChanged(index); + const bool udpEnabled = !s.getForceTCP() && (s.getProxyType() == Settings::ProxyType::ptNone); + bodyUI->cbEnableUDP->setChecked(udpEnabled); + bodyUI->cbEnableLanDiscovery->setChecked(s.getEnableLanDiscovery() && udpEnabled); + bodyUI->cbEnableLanDiscovery->setEnabled(udpEnabled); QString warningBody = tr("Unless you %1 know what you are doing, " "please do %2 change anything here. Changes " @@ -176,7 +177,9 @@ void AdvancedForm::on_cbEnableUDP_stateChanged() { const bool enableUdp = bodyUI->cbEnableUDP->isChecked(); Settings::getInstance().setForceTCP(!enableUdp); + const bool enableLanDiscovery = Settings::getInstance().getEnableLanDiscovery(); bodyUI->cbEnableLanDiscovery->setEnabled(enableUdp); + bodyUI->cbEnableLanDiscovery->setChecked(enableUdp && enableLanDiscovery); } void AdvancedForm::on_cbEnableLanDiscovery_stateChanged() @@ -191,8 +194,9 @@ void AdvancedForm::on_proxyAddr_editingFinished() void AdvancedForm::on_proxyPort_valueChanged(int port) { - if (port <= 0) + if (port <= 0) { port = 0; + } Settings::getInstance().setProxyPort(port); } @@ -200,9 +204,14 @@ void AdvancedForm::on_proxyPort_valueChanged(int port) void AdvancedForm::on_proxyType_currentIndexChanged(int index) { Settings::ProxyType proxytype = static_cast(index); + const bool proxyEnabled = proxytype != Settings::ProxyType::ptNone; + + bodyUI->proxyAddr->setEnabled(proxyEnabled); + bodyUI->proxyPort->setEnabled(proxyEnabled); + // enabling UDP and proxy can be a privacy issue + bodyUI->cbEnableUDP->setEnabled(!proxyEnabled); + bodyUI->cbEnableUDP->setChecked(!proxyEnabled); - bodyUI->proxyAddr->setEnabled(proxytype != Settings::ProxyType::ptNone); - bodyUI->proxyPort->setEnabled(proxytype != Settings::ProxyType::ptNone); Settings::getInstance().setProxyType(proxytype); } diff --git a/src/widget/form/settings/avform.cpp b/src/widget/form/settings/avform.cpp index 96794b09c..345aefa6c 100644 --- a/src/widget/form/settings/avform.cpp +++ b/src/widget/form/settings/avform.cpp @@ -191,10 +191,7 @@ void AVForm::on_videoModescomboBox_currentIndexChanged(int index) return; } - // note: grabber is self-managed and will destroy itself when done - ScreenshotGrabber* screenshotGrabber = new ScreenshotGrabber; - - auto onGrabbed = [screenshotGrabber, devName, this](QRect region) { + auto onGrabbed = [devName, this](QRect region) { VideoMode mode(region); mode.width = mode.width / 2 * 2; mode.height = mode.height / 2 * 2; @@ -210,6 +207,9 @@ void AVForm::on_videoModescomboBox_currentIndexChanged(int index) open(devName, mode); }; + // note: grabber is self-managed and will destroy itself when done + ScreenshotGrabber* screenshotGrabber = new ScreenshotGrabber; + connect(screenshotGrabber, &ScreenshotGrabber::regionChosen, this, onGrabbed, Qt::QueuedConnection); screenshotGrabber->showGrabber(); diff --git a/src/widget/form/settings/generalform.cpp b/src/widget/form/settings/generalform.cpp index 373502fce..523f9aa9a 100644 --- a/src/widget/form/settings/generalform.cpp +++ b/src/widget/form/settings/generalform.cpp @@ -105,6 +105,11 @@ GeneralForm::GeneralForm(SettingsWidget* myParent) #else bodyUI->checkUpdates->setVisible(false); #endif + +#ifndef SPELL_CHECKING + bodyUI->cbSpellChecking->setVisible(false); +#endif + bodyUI->checkUpdates->setChecked(s.getCheckUpdates()); for (int i = 0; i < locales.size(); ++i) { @@ -126,6 +131,7 @@ GeneralForm::GeneralForm(SettingsWidget* myParent) bodyUI->cbAutorun->setChecked(s.getAutorun()); + bodyUI->cbSpellChecking->setChecked(s.getSpellCheckingEnabled()); bodyUI->lightTrayIcon->setChecked(s.getLightTrayIcon()); bool showSystemTray = s.getShowSystemTray(); @@ -172,6 +178,11 @@ void GeneralForm::on_cbAutorun_stateChanged() Settings::getInstance().setAutorun(bodyUI->cbAutorun->isChecked()); } +void GeneralForm::on_cbSpellChecking_stateChanged() +{ + Settings::getInstance().setSpellCheckingEnabled(bodyUI->cbSpellChecking->isChecked()); +} + void GeneralForm::on_showSystemTray_stateChanged() { Settings::getInstance().setShowSystemTray(bodyUI->showSystemTray->isChecked()); diff --git a/src/widget/form/settings/generalform.h b/src/widget/form/settings/generalform.h index 79cc6d510..71681457f 100644 --- a/src/widget/form/settings/generalform.h +++ b/src/widget/form/settings/generalform.h @@ -42,6 +42,7 @@ public: private slots: void on_transComboBox_currentIndexChanged(int index); void on_cbAutorun_stateChanged(); + void on_cbSpellChecking_stateChanged(); void on_showSystemTray_stateChanged(); void on_startInTray_stateChanged(); void on_closeToTray_stateChanged(); diff --git a/src/widget/form/settings/generalsettings.ui b/src/widget/form/settings/generalsettings.ui index 36a3c9ad7..1da736897 100644 --- a/src/widget/form/settings/generalsettings.ui +++ b/src/widget/form/settings/generalsettings.ui @@ -119,6 +119,13 @@ + + + + Spell checking + + + diff --git a/src/widget/form/settingswidget.cpp b/src/widget/form/settingswidget.cpp index e638b28ea..10a78fea3 100644 --- a/src/widget/form/settingswidget.cpp +++ b/src/widget/form/settingswidget.cpp @@ -51,7 +51,7 @@ SettingsWidget::SettingsWidget(QWidget* parent) setAttribute(Qt::WA_DeleteOnClose); - QVBoxLayout* bodyLayout = new QVBoxLayout(); + bodyLayout = std::unique_ptr(new QVBoxLayout()); settingsWidgets = std::unique_ptr(new QTabWidget(this)); settingsWidgets->setTabPosition(QTabWidget::North); diff --git a/src/widget/form/settingswidget.h b/src/widget/form/settingswidget.h index 21ae5f53c..1d7a7987f 100644 --- a/src/widget/form/settingswidget.h +++ b/src/widget/form/settingswidget.h @@ -56,6 +56,7 @@ private: void retranslateUi(); private: + std::unique_ptr bodyLayout; std::unique_ptr settingsWidgets; std::array, 6> cfgForms; int currentIndex; diff --git a/src/widget/friendwidget.cpp b/src/widget/friendwidget.cpp index c3b0399a1..98cea27ca 100644 --- a/src/widget/friendwidget.cpp +++ b/src/widget/friendwidget.cpp @@ -24,11 +24,11 @@ #include "maskablepixmapwidget.h" #include "src/core/core.h" -#include "src/model/friend.h" -#include "src/model/about/aboutfriend.h" #include "src/friendlist.h" +#include "src/model/about/aboutfriend.h" +#include "src/model/chatroom/friendchatroom.h" +#include "src/model/friend.h" #include "src/model/group.h" -#include "src/grouplist.h" #include "src/persistence/settings.h" #include "src/widget/about/aboutfriendform.h" #include "src/widget/form/chatform.h" @@ -38,7 +38,6 @@ #include #include -#include #include #include #include @@ -49,11 +48,6 @@ #include -namespace -{ -constexpr auto MAX_NAME_LENGTH = 30; -} - /** * @class FriendWidget * @@ -61,21 +55,22 @@ constexpr auto MAX_NAME_LENGTH = 30; * For example, used on friend list. * When you click should open the chat with friend. Widget has a context menu. */ - -FriendWidget::FriendWidget(const Friend* f, bool compact) +FriendWidget::FriendWidget(std::shared_ptr chatroom, bool compact) : GenericChatroomWidget(compact) - , frnd{f} + , chatroom{chatroom} , isDefaultAvatar{true} { avatar->setPixmap(QPixmap(":/img/contact.svg")); statusPic.setPixmap(QPixmap(":/img/status/offline.svg")); statusPic.setMargin(3); - setName(f->getDisplayedName()); - nameLabel->setTextFormat(Qt::PlainText); - // update on changes of the displayed name - connect(f, &Friend::displayedNameChanged, this, &FriendWidget::setName); + + auto frnd = chatroom->getFriend(); + nameLabel->setText(frnd->getDisplayedName()); // update alias when edited - connect(nameLabel, &CroppingLabel::editFinished, f, &Friend::setAlias); + connect(nameLabel, &CroppingLabel::editFinished, frnd, &Friend::setAlias); + // update on changes of the displayed name + connect(frnd, &Friend::displayedNameChanged, nameLabel, &CroppingLabel::setText); + connect(chatroom.get(), &FriendChatroom::activeChanged, this, &FriendWidget::setActive); statusMessageLabel->setTextFormat(Qt::PlainText); } @@ -107,92 +102,77 @@ void FriendWidget::onContextMenuCalled(QContextMenuEvent* event) QMenu menu; + const auto frnd = chatroom->getFriend(); const auto friendId = frnd->getId(); - const ContentDialog* contentDialog = ContentDialog::getFriendDialog(friendId); + const auto contentDialog = ContentDialog::getFriendDialog(friendId); + // TODO: move to model if (!contentDialog || contentDialog->chatroomWidgetCount() > 1) { const auto openChatWindow = menu.addAction(tr("Open chat in new window")); connect(openChatWindow, &QAction::triggered, [=]() { emit newWindowOpened(this); }); } + // TODO: move to model if (contentDialog && contentDialog->hasFriendWidget(friendId, this)) { const auto removeChatWindow = menu.addAction(tr("Remove chat from this window")); connect(removeChatWindow, &QAction::triggered, this, &FriendWidget::removeChatWindow); } menu.addSeparator(); - QMenu* inviteMenu = menu.addMenu(tr("Invite to group", - "Menu to invite a friend to a groupchat")); - inviteMenu->setEnabled(frnd->getStatus() != Status::Offline); + QMenu* inviteMenu = + menu.addMenu(tr("Invite to group", "Menu to invite a friend to a groupchat")); + inviteMenu->setEnabled(chatroom->canBeInvited()); const auto newGroupAction = inviteMenu->addAction(tr("To new group")); - connect(newGroupAction, &QAction::triggered, this, &FriendWidget::moveToNewGroup); + connect(newGroupAction, &QAction::triggered, chatroom.get(), &FriendChatroom::inviteToNewGroup); inviteMenu->addSeparator(); - for (const Group* group : GroupList::getAllGroups()) { - auto name = group->getName(); - if (name.length() > MAX_NAME_LENGTH) { - name = name.left(MAX_NAME_LENGTH).trimmed() + ".."; - } - const auto groupAction = inviteMenu->addAction(tr("Invite to group '%1'").arg(name)); - connect(groupAction, &QAction::triggered, [=]() { inviteFriend(friendId, group); }); + for (const auto group : chatroom->getGroups()) { + const auto groupAction = inviteMenu->addAction(tr("Invite to group '%1'").arg(group.name)); + connect(groupAction, &QAction::triggered, [=]() { + chatroom->inviteFriend(group.group); + }); } - const auto& s = Settings::getInstance(); - const auto circleId = s.getFriendCircleID(frnd->getPublicKey()); - auto circleMenu = menu.addMenu(tr("Move to circle...", - "Menu to move a friend into a different circle")); + const auto circleId = chatroom->getCircleId(); + auto circleMenu = + menu.addMenu(tr("Move to circle...", "Menu to move a friend into a different circle")); const auto pk = frnd->getPublicKey(); const auto newCircleAction = circleMenu->addAction(tr("To new circle")); connect(newCircleAction, &QAction::triggered, this, &FriendWidget::moveToNewCircle); if (circleId != -1) { - const QString circleName = s.getCircleName(circleId); - const auto removeCircleAction = circleMenu->addAction( - tr("Remove from circle '%1'").arg(circleName)); + const auto circleName = chatroom->getCircleName(); + const auto removeCircleAction = + circleMenu->addAction(tr("Remove from circle '%1'").arg(circleName)); connect(removeCircleAction, &QAction::triggered, this, &FriendWidget::removeFromCircle); } circleMenu->addSeparator(); - QList circleActionList; - for (int i = 0; i < s.getCircleCount(); ++i) { - if (i == circleId) { - continue; - } - - const auto name = s.getCircleName(i); - QAction* action = new QAction(tr("Move to circle \"%1\"").arg(name), circleMenu); - connect(action, &QAction::triggered, [=]() { moveToCircle(i); }); - circleActionList.push_back(action); + for (const auto circle : chatroom->getOtherCircles()) { + QAction* action = new QAction(tr("Move to circle \"%1\"").arg(circle.name), circleMenu); + connect(action, &QAction::triggered, [=]() { moveToCircle(circle.circleId); }); + circleMenu->addAction(action); } - std::sort(circleActionList.begin(), circleActionList.end(), - [](const QAction* lhs, const QAction* rhs) -> bool { - QCollator collator; - collator.setNumericMode(true); - return collator.compare(lhs->text(), rhs->text()) < 0; - }); - - circleMenu->addActions(circleActionList); - const auto setAlias = menu.addAction(tr("Set alias...")); - connect(setAlias, &QAction::triggered, [this]() { nameLabel->editBegin(); }); + connect(setAlias, &QAction::triggered, nameLabel, &CroppingLabel::editBegin); menu.addSeparator(); - auto autoAccept = menu.addAction(tr("Auto accept files from this friend", - "context menu entry")); - const auto dir = s.getAutoAcceptDir(pk); + auto autoAccept = + menu.addAction(tr("Auto accept files from this friend", "context menu entry")); autoAccept->setCheckable(true); - autoAccept->setChecked(!dir.isEmpty()); + autoAccept->setChecked(!chatroom->autoAcceptEnabled()); connect(autoAccept, &QAction::triggered, this, &FriendWidget::changeAutoAccept); menu.addSeparator(); + // TODO: move to model if (!contentDialog || !contentDialog->hasFriendWidget(friendId, this)) { - const auto removeAction = menu.addAction( - tr("Remove friend", "Menu to remove the friend from our friendlist")); + const auto removeAction = + menu.addAction(tr("Remove friend", "Menu to remove the friend from our friendlist")); connect(removeAction, &QAction::triggered, this, [=]() { emit removeFriend(friendId); }, - Qt::QueuedConnection); + Qt::QueuedConnection); } menu.addSeparator(); @@ -211,28 +191,15 @@ void FriendWidget::onContextMenuCalled(QContextMenuEvent* event) void FriendWidget::removeChatWindow() { + const auto frnd = chatroom->getFriend(); const auto friendId = frnd->getId(); ContentDialog* contentDialog = ContentDialog::getFriendDialog(friendId); contentDialog->removeFriend(friendId); } -void FriendWidget::moveToNewGroup() -{ - const auto friendId = frnd->getId(); - const auto groupId = Core::getInstance()->createGroup(); - Core::getInstance()->groupInviteFriend(friendId, groupId); -} +namespace { -void FriendWidget::inviteFriend(uint32_t friendId, const Group* group) -{ - Core::getInstance()->groupInviteFriend(friendId, group->getId()); -} - -namespace -{ - -std::tuple getCircleAndFriendList( - const Friend* frnd, FriendWidget* fw) +std::tuple getCircleAndFriendList(const Friend* frnd, FriendWidget* fw) { const auto pk = frnd->getPublicKey(); const auto circleId = Settings::getInstance().getFriendCircleID(pk); @@ -242,10 +209,11 @@ std::tuple getCircleAndFriendList( return std::make_tuple(circleWidget, friendList); } -} +} // namespace void FriendWidget::moveToNewCircle() { + const auto frnd = chatroom->getFriend(); CircleWidget* circleWidget; FriendListWidget* friendList; std::tie(circleWidget, friendList) = getCircleAndFriendList(frnd, this); @@ -258,7 +226,7 @@ void FriendWidget::moveToNewCircle() friendList->addCircleWidget(this); } else { const auto pk = frnd->getPublicKey(); - auto &s = Settings::getInstance(); + auto& s = Settings::getInstance(); auto circleId = s.addCircle(); s.setFriendCircleID(pk, circleId); } @@ -266,6 +234,7 @@ void FriendWidget::moveToNewCircle() void FriendWidget::removeFromCircle() { + const auto frnd = chatroom->getFriend(); CircleWidget* circleWidget; FriendListWidget* friendList; std::tie(circleWidget, friendList) = getCircleAndFriendList(frnd, this); @@ -286,6 +255,7 @@ void FriendWidget::removeFromCircle() void FriendWidget::moveToCircle(int newCircleId) { + const auto frnd = chatroom->getFriend(); const auto pk = frnd->getPublicKey(); const auto oldCircleId = Settings::getInstance().getFriendCircleID(pk); auto& s = Settings::getInstance(); @@ -309,48 +279,47 @@ void FriendWidget::moveToCircle(int newCircleId) void FriendWidget::changeAutoAccept(bool enable) { - const auto pk = frnd->getPublicKey(); - auto &s = Settings::getInstance(); if (enable) { - const auto oldDir = s.getAutoAcceptDir(pk); + const auto oldDir = chatroom->getAutoAcceptDir(); const auto newDir = QFileDialog::getExistingDirectory( Q_NULLPTR, tr("Choose an auto accept directory", "popup title"), oldDir); - - const auto friendId = frnd->getId(); - qDebug() << "Setting auto accept dir for" << friendId << "to" << newDir; - s.setAutoAcceptDir(pk, newDir); + chatroom->setAutoAcceptDir(newDir); } else { - qDebug() << "not checked"; - s.setAutoAcceptDir(pk, ""); + chatroom->disableAutoAccept(); } } void FriendWidget::showDetails() { - const QPointer about = new AboutFriend(frnd, &Settings::getInstance()); - auto aboutUser = new AboutFriendForm(about, Widget::getInstance()); + const auto frnd = chatroom->getFriend(); + const auto iabout = new AboutFriend(frnd, &Settings::getInstance()); + std::unique_ptr about = std::unique_ptr(iabout); + const auto aboutUser = new AboutFriendForm(std::move(about), Widget::getInstance()); aboutUser->show(); } void FriendWidget::setAsActiveChatroom() { setActive(true); - - if (isDefaultAvatar) { - avatar->setPixmap(QPixmap(":img/contact_dark.svg")); - } } void FriendWidget::setAsInactiveChatroom() { setActive(false); +} +void FriendWidget::setActive(bool active) +{ + GenericChatroomWidget::setActive(active); if (isDefaultAvatar) { - avatar->setPixmap(QPixmap(":img/contact.svg")); + const auto uri = active ? QStringLiteral(":img/contact_dark.svg") + : QStringLiteral(":img/contact.svg"); + avatar->setPixmap(QPixmap{uri}); } } void FriendWidget::updateStatusLight() { + // clang-format off static const QString statuses[] = { ":img/status/online.svg", ":img/status/online_notification.svg", @@ -361,7 +330,9 @@ void FriendWidget::updateStatusLight() ":img/status/offline.svg", ":img/status/offline_notification.svg", }; + // clang-format on + const auto frnd = chatroom->getFriend(); const bool event = frnd->getEventFlag(); const int index = static_cast(frnd->getStatus()) * 2 + event; statusPic.setPixmap(QPixmap(statuses[index])); @@ -382,6 +353,7 @@ void FriendWidget::updateStatusLight() QString FriendWidget::getStatusString() const { + const auto frnd = chatroom->getFriend(); const int status = static_cast(frnd->getStatus()); const bool event = frnd->getEventFlag(); @@ -397,11 +369,12 @@ QString FriendWidget::getStatusString() const const Friend* FriendWidget::getFriend() const { - return frnd; + return chatroom->getFriend(); } void FriendWidget::search(const QString& searchString, bool hide) { + const auto frnd = chatroom->getFriend(); searchName(searchString, hide); const Settings& s = Settings::getInstance(); const uint32_t circleId = s.getFriendCircleID(frnd->getPublicKey()); @@ -413,14 +386,13 @@ void FriendWidget::search(const QString& searchString, bool hide) void FriendWidget::resetEventFlags() { - // Hack to avoid edit const Friend. TODO: Repalce on emit - Friend* f = FriendList::findFriend(frnd->getId()); - f->setEventFlag(false); + chatroom->resetEventFlags(); } -void FriendWidget::onAvatarChange(uint32_t friendId, const QPixmap& pic) +void FriendWidget::onAvatarChange(const ToxPk& friendPk, const QPixmap& pic) { - if (friendId != frnd->getId()) { + const auto frnd = chatroom->getFriend(); + if (friendPk != frnd->getPublicKey()) { return; } @@ -428,9 +400,10 @@ void FriendWidget::onAvatarChange(uint32_t friendId, const QPixmap& pic) avatar->setPixmap(pic); } -void FriendWidget::onAvatarRemoved(uint32_t friendId) +void FriendWidget::onAvatarRemoved(const ToxPk& friendPk) { - if (friendId != frnd->getId()) { + const auto frnd = chatroom->getFriend(); + if (friendPk != frnd->getPublicKey()) { return; } diff --git a/src/widget/friendwidget.h b/src/widget/friendwidget.h index fd82687b5..537e0400e 100644 --- a/src/widget/friendwidget.h +++ b/src/widget/friendwidget.h @@ -19,7 +19,11 @@ #define FRIENDWIDGET_H #include "genericchatroomwidget.h" +#include "src/core/toxpk.h" +#include + +class FriendChatroom; class QPixmap; class MaskablePixmapWidget; @@ -27,7 +31,8 @@ class FriendWidget : public GenericChatroomWidget { Q_OBJECT public: - FriendWidget(const Friend* f, bool compact); + FriendWidget(std::shared_ptr chatform, bool compact); + void contextMenuEvent(QContextMenuEvent* event) override final; void setAsActiveChatroom() override final; void setAsInactiveChatroom() override final; @@ -45,9 +50,10 @@ signals: void contextMenuCalled(QContextMenuEvent* event); public slots: - void onAvatarChange(uint32_t friendId, const QPixmap& pic); - void onAvatarRemoved(uint32_t friendId); + void onAvatarChange(const ToxPk& friendPk, const QPixmap& pic); + void onAvatarRemoved(const ToxPk& friendPk); void onContextMenuCalled(QContextMenuEvent* event); + void setActive(bool active); protected: virtual void mousePressEvent(QMouseEvent* ev) override; @@ -56,8 +62,6 @@ protected: private slots: void removeChatWindow(); - void moveToNewGroup(); - void inviteFriend(uint32_t friendId, const Group* group); void moveToNewCircle(); void removeFromCircle(); void moveToCircle(int circleId); @@ -65,7 +69,7 @@ private slots: void showDetails(); public: - const Friend* frnd; + std::shared_ptr chatroom; bool isDefaultAvatar; }; diff --git a/src/widget/genericchatroomwidget.h b/src/widget/genericchatroomwidget.h index eba28ffb8..f28c062ed 100644 --- a/src/widget/genericchatroomwidget.h +++ b/src/widget/genericchatroomwidget.h @@ -36,6 +36,7 @@ class GenericChatroomWidget : public GenericChatItemWidget public: explicit GenericChatroomWidget(bool compact, QWidget* parent = 0); +public slots: virtual void setAsActiveChatroom() = 0; virtual void setAsInactiveChatroom() = 0; virtual void updateStatusLight() = 0; @@ -53,7 +54,6 @@ public: virtual bool eventFilter(QObject*, QEvent*) final override; bool isActive(); - void setActive(bool active); void setName(const QString& name); void setStatusMsg(const QString& status); @@ -62,7 +62,6 @@ public: void reloadTheme(); -public slots: void activate(); void compactChange(bool compact); @@ -75,10 +74,10 @@ protected: void mouseReleaseEvent(QMouseEvent* event) override; void enterEvent(QEvent* e) override; void leaveEvent(QEvent* e) override; - - QPoint dragStartPos; + void setActive(bool active); protected: + QPoint dragStartPos; QColor lastColor; QHBoxLayout* mainLayout = nullptr; QVBoxLayout* textLayout = nullptr; diff --git a/src/widget/groupwidget.cpp b/src/widget/groupwidget.cpp index 118da094d..d10f0d64c 100644 --- a/src/widget/groupwidget.cpp +++ b/src/widget/groupwidget.cpp @@ -40,22 +40,24 @@ #include "src/widget/translator.h" #include "tool/croppinglabel.h" -GroupWidget::GroupWidget(int groupId, const QString& name, bool compact) +GroupWidget::GroupWidget(std::shared_ptr chatroom, bool compact) : GenericChatroomWidget(compact) - , groupId{groupId} + , groupId{static_cast(chatroom->getGroup()->getId())} + , chatroom{chatroom} { avatar->setPixmap(Style::scaleSvgImage(":img/group.svg", avatar->width(), avatar->height())); statusPic.setPixmap(QPixmap(":img/status/online.svg")); statusPic.setMargin(3); - nameLabel->setText(name); + + Group* g = chatroom->getGroup(); + nameLabel->setText(g->getName()); updateUserCount(); setAcceptDrops(true); - Group* g = GroupList::findGroup(groupId); connect(g, &Group::titleChanged, this, &GroupWidget::updateTitle); connect(g, &Group::userListChanged, this, &GroupWidget::updateUserCount); - connect(nameLabel, &CroppingLabel::editFinished, this, &GroupWidget::setTitle); + connect(nameLabel, &CroppingLabel::editFinished, g, &Group::setName); Translator::registerHandler(std::bind(&GroupWidget::retranslateUi, this), this); } @@ -64,12 +66,6 @@ GroupWidget::~GroupWidget() Translator::unregister(this); } -void GroupWidget::setTitle(const QString& newName) -{ - Group* g = GroupList::findGroup(groupId); - g->setName(newName); -} - void GroupWidget::updateTitle(uint32_t groupId, const QString& author, const QString& newName) { Q_UNUSED(groupId); @@ -79,8 +75,9 @@ void GroupWidget::updateTitle(uint32_t groupId, const QString& author, const QSt void GroupWidget::contextMenuEvent(QContextMenuEvent* event) { - if (!active) + if (!active) { setBackgroundRole(QPalette::Highlight); + } installEventFilter(this); // Disable leave event. @@ -89,14 +86,17 @@ void GroupWidget::contextMenuEvent(QContextMenuEvent* event) QAction* openChatWindow = nullptr; QAction* removeChatWindow = nullptr; + // TODO: Move to model ContentDialog* contentDialog = ContentDialog::getGroupDialog(groupId); - bool notAlone = contentDialog != nullptr && contentDialog->chatroomWidgetCount() > 1; + const bool notAlone = contentDialog != nullptr && contentDialog->chatroomWidgetCount() > 1; - if (contentDialog == nullptr || notAlone) + if (contentDialog == nullptr || notAlone) { openChatWindow = menu.addAction(tr("Open chat in new window")); + } - if (contentDialog && contentDialog->hasGroupWidget(groupId, this)) + if (contentDialog && contentDialog->hasGroupWidget(groupId, this)) { removeChatWindow = menu.addAction(tr("Remove chat from this window")); + } menu.addSeparator(); @@ -107,8 +107,9 @@ void GroupWidget::contextMenuEvent(QContextMenuEvent* event) removeEventFilter(this); - if (!active) + if (!active) { setBackgroundRole(QPalette::Window); + } if (!selectedItem) { return; @@ -119,6 +120,7 @@ void GroupWidget::contextMenuEvent(QContextMenuEvent* event) } else if (selectedItem == openChatWindow) { emit newWindowOpened(this); } else if (selectedItem == removeChatWindow) { + // TODO: move to model ContentDialog* contentDialog = ContentDialog::getGroupDialog(groupId); contentDialog->removeGroup(groupId); } else if (selectedItem == setTitle) { @@ -128,16 +130,18 @@ void GroupWidget::contextMenuEvent(QContextMenuEvent* event) void GroupWidget::mousePressEvent(QMouseEvent* ev) { - if (ev->button() == Qt::LeftButton) + if (ev->button() == Qt::LeftButton) { dragStartPos = ev->pos(); + } GenericChatroomWidget::mousePressEvent(ev); } void GroupWidget::mouseMoveEvent(QMouseEvent* ev) { - if (!(ev->buttons() & Qt::LeftButton)) + if (!(ev->buttons() & Qt::LeftButton)) { return; + } if ((dragStartPos - ev->pos()).manhattanLength() > QApplication::startDragDistance()) { QMimeData* mdata = new QMimeData; @@ -152,14 +156,8 @@ void GroupWidget::mouseMoveEvent(QMouseEvent* ev) void GroupWidget::updateUserCount() { - Group* g = GroupList::findGroup(groupId); - if (g) { - int peersCount = g->getPeersCount(); - if (peersCount == 1) - statusMessageLabel->setText(tr("1 user in chat")); - else - statusMessageLabel->setText(tr("%1 users in chat").arg(peersCount)); - } + int peersCount = chatroom->getGroup()->getPeersCount(); + statusMessageLabel->setText(tr("%n user(s) in chat", "", peersCount)); } void GroupWidget::setAsActiveChatroom() @@ -176,25 +174,24 @@ void GroupWidget::setAsInactiveChatroom() void GroupWidget::updateStatusLight() { - Group* g = GroupList::findGroup(groupId); + Group* g = chatroom->getGroup(); - if (!g->getEventFlag()) { - statusPic.setPixmap(QPixmap(":img/status/online.svg")); - statusPic.setMargin(3); - } else { + if (g->getEventFlag()) { statusPic.setPixmap(QPixmap(":img/status/online_notification.svg")); statusPic.setMargin(1); + } else { + statusPic.setPixmap(QPixmap(":img/status/online.svg")); + statusPic.setMargin(3); } } QString GroupWidget::getStatusString() const { - Group* g = GroupList::findGroup(groupId); - - if (!g->getEventFlag()) - return "Online"; - else - return "New Message"; + if (chatroom->hasNewMessage()) { + return tr("New Message"); + } else { + return tr("Online"); + } } void GroupWidget::editName() @@ -202,49 +199,51 @@ void GroupWidget::editName() nameLabel->editBegin(); } +// TODO: Remove Group* GroupWidget::getGroup() const { - return GroupList::findGroup(groupId); + return chatroom->getGroup(); } void GroupWidget::resetEventFlags() { - Group* g = GroupList::findGroup(groupId); - g->setEventFlag(false); - g->setMentionedFlag(false); + chatroom->resetEventFlags(); } void GroupWidget::dragEnterEvent(QDragEnterEvent* ev) { - ToxId toxId = ToxId(ev->mimeData()->text()); - Friend* frnd = FriendList::findFriend(toxId.getPublicKey()); - if (frnd) + // TODO: Send ToxPk in mimeData + const ToxId toxId = ToxId(ev->mimeData()->text()); + const ToxPk pk = toxId.getPublicKey(); + if (chatroom->friendExists(pk)) { ev->acceptProposedAction(); + } - if (!active) + if (!active) { setBackgroundRole(QPalette::Highlight); + } } void GroupWidget::dragLeaveEvent(QDragLeaveEvent*) { - if (!active) + if (!active) { setBackgroundRole(QPalette::Window); + } } void GroupWidget::dropEvent(QDropEvent* ev) { - ToxId toxId = ToxId(ev->mimeData()->text()); - Friend* frnd = FriendList::findFriend(toxId.getPublicKey()); - if (!frnd) + const ToxId toxId = ToxId(ev->mimeData()->text()); + const ToxPk pk = toxId.getPublicKey(); + if (!chatroom->friendExists(pk)) { return; - - int friendId = frnd->getId(); - if (frnd->getStatus() != Status::Offline) { - Core::getInstance()->groupInviteFriend(friendId, groupId); } - if (!active) + chatroom->inviteFriend(pk); + + if (!active) { setBackgroundRole(QPalette::Window); + } } void GroupWidget::setName(const QString& name) diff --git a/src/widget/groupwidget.h b/src/widget/groupwidget.h index c755a2430..78eb7d41d 100644 --- a/src/widget/groupwidget.h +++ b/src/widget/groupwidget.h @@ -22,11 +22,15 @@ #include "genericchatroomwidget.h" +#include "src/model/chatroom/groupchatroom.h" + +#include + class GroupWidget final : public GenericChatroomWidget { Q_OBJECT public: - GroupWidget(int GroupId, const QString& Name, bool compact); + GroupWidget(std::shared_ptr chatroom, bool compact); ~GroupWidget(); void setAsInactiveChatroom() final override; void setAsActiveChatroom() final override; @@ -51,12 +55,14 @@ protected: private slots: void retranslateUi(); - void setTitle(const QString& newName); void updateTitle(uint32_t groupId, const QString& author, const QString& newName); void updateUserCount(); public: int groupId; + +private: + std::shared_ptr chatroom; }; #endif // GROUPWIDGET_H diff --git a/src/widget/tool/croppinglabel.h b/src/widget/tool/croppinglabel.h index 9c9175b15..328f72e77 100644 --- a/src/widget/tool/croppinglabel.h +++ b/src/widget/tool/croppinglabel.h @@ -35,9 +35,10 @@ public slots: void setEditable(bool editable); void setElideMode(Qt::TextElideMode elide); - void setText(const QString& text); QString fullText(); +public slots: + void setText(const QString& text); void minimizeMaximumWidth(); signals: diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index 1804ae0a6..75759fc16 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -44,7 +44,6 @@ #include "friendlistwidget.h" #include "friendwidget.h" #include "groupwidget.h" -#include "src/model/groupinvite.h" #include "maskablepixmapwidget.h" #include "splitterrestorer.h" #include "systemtrayicon.h" @@ -52,11 +51,15 @@ #include "src/audio/audio.h" #include "src/core/core.h" #include "src/core/coreav.h" +#include "src/model/chatroom/friendchatroom.h" +#include "src/model/chatroom/groupchatroom.h" #include "src/model/friend.h" #include "src/friendlist.h" -#include "src/model/group.h" -#include "src/model/profile/profileinfo.h" #include "src/grouplist.h" +#include "src/model/friend.h" +#include "src/model/group.h" +#include "src/model/groupinvite.h" +#include "src/model/profile/profileinfo.h" #include "src/net/autoupdate.h" #include "src/nexus.h" #include "src/persistence/offlinemsgengine.h" @@ -528,11 +531,13 @@ Widget::~Widget() delete icon; delete profileForm; + delete profileInfo; delete addFriendForm; delete groupInviteForm; delete filesForm; delete timer; delete contentLayout; + delete settingsWidget; FriendList::clear(); GroupList::clear(); @@ -980,11 +985,13 @@ void Widget::addFriend(uint32_t friendId, const ToxPk& friendPk) s.updateFriendAddress(friendPk.toString()); Friend* newfriend = FriendList::addFriend(friendId, friendPk); - bool compact = s.getCompactLayout(); - FriendWidget* widget = new FriendWidget(newfriend, compact); - History* history = Nexus::getProfile()->getHistory(); - ChatForm* friendForm = new ChatForm(newfriend, history); + std::shared_ptr chatroom(new FriendChatroom(newfriend)); + const auto compact = Settings::getInstance().getCompactLayout(); + auto widget = new FriendWidget(chatroom, compact); + auto history = Nexus::getProfile()->getHistory(); + auto friendForm = new ChatForm(newfriend, history); + friendChatrooms[friendId] = chatroom; friendWidgets[friendId] = widget; chatForms[friendId] = friendForm; @@ -1020,7 +1027,7 @@ void Widget::addFriend(uint32_t friendId, const ToxPk& friendPk) QPixmap avatar = Nexus::getProfile()->loadAvatar(friendPk); if (!avatar.isNull()) { friendForm->onAvatarChange(friendId, avatar); - widget->onAvatarChange(friendId, avatar); + widget->onAvatarChange(friendPk, avatar); } FilterCriteria filter = getFilterCriteria(); @@ -1240,8 +1247,9 @@ void Widget::addFriendDialog(const Friend* frnd, ContentDialog* dialog) onAddClicked(); } - ChatForm* form = chatForms[friendId]; - FriendWidget* friendWidget = dialog->addFriend(frnd, form); + auto form = chatForms[friendId]; + auto chatroom = friendChatrooms[friendId]; + FriendWidget* friendWidget = dialog->addFriend(chatroom, form); friendWidget->setStatusMsg(widget->getStatusMsg()); @@ -1251,9 +1259,8 @@ void Widget::addFriendDialog(const Friend* frnd, ContentDialog* dialog) auto widgetRemoveFriend = static_cast(&Widget::removeFriend); #endif connect(friendWidget, &FriendWidget::removeFriend, this, widgetRemoveFriend); - connect(friendWidget, &FriendWidget::middleMouseClicked, dialog, [=]() { - dialog->removeFriend(friendId); - }); + connect(friendWidget, &FriendWidget::middleMouseClicked, dialog, + [=]() { dialog->removeFriend(friendId); }); connect(friendWidget, &FriendWidget::copyFriendIdToClipboard, this, &Widget::copyFriendIdToClipboard); @@ -1263,16 +1270,14 @@ void Widget::addFriendDialog(const Friend* frnd, ContentDialog* dialog) connect(friendWidget, &FriendWidget::contextMenuCalled, widget, [=](QContextMenuEvent* event) { emit widget->contextMenuCalled(event); }); - connect(friendWidget, &FriendWidget::chatroomWidgetClicked, - [=](GenericChatroomWidget* w) { - Q_UNUSED(w); - emit widget->chatroomWidgetClicked(widget); - }); - connect(friendWidget, &FriendWidget::newWindowOpened, - [=](GenericChatroomWidget* w) { - Q_UNUSED(w); - emit widget->newWindowOpened(widget); - }); + connect(friendWidget, &FriendWidget::chatroomWidgetClicked, [=](GenericChatroomWidget* w) { + Q_UNUSED(w); + emit widget->chatroomWidgetClicked(widget); + }); + connect(friendWidget, &FriendWidget::newWindowOpened, [=](GenericChatroomWidget* w) { + Q_UNUSED(w); + emit widget->newWindowOpened(widget); + }); // FIXME: emit should be removed emit widget->chatroomWidgetClicked(widget); @@ -1282,7 +1287,7 @@ void Widget::addFriendDialog(const Friend* frnd, ContentDialog* dialog) QPixmap avatar = Nexus::getProfile()->loadAvatar(frnd->getPublicKey()); if (!avatar.isNull()) { - friendWidget->onAvatarChange(friendId, avatar); + friendWidget->onAvatarChange(frnd->getPublicKey(), avatar); } } @@ -1298,7 +1303,8 @@ void Widget::addGroupDialog(Group* group, ContentDialog* dialog) } auto chatForm = groupChatForms[groupId]; - auto groupWidget = dialog->addGroup(group, chatForm); + auto chatroom = groupChatrooms[groupId]; + auto groupWidget = dialog->addGroup(chatroom, chatForm); #if (QT_VERSION >= QT_VERSION_CHECK(5, 7, 0)) auto removeGroup = QOverload::of(&Widget::removeGroup); #else @@ -1306,25 +1312,22 @@ void Widget::addGroupDialog(Group* group, ContentDialog* dialog) #endif connect(groupWidget, &GroupWidget::removeGroup, this, removeGroup); connect(groupWidget, &GroupWidget::chatroomWidgetClicked, chatForm, &GroupChatForm::focusInput); - connect(groupWidget, &GroupWidget::middleMouseClicked, dialog, [=]() { - dialog->removeGroup(groupId); - }); + connect(groupWidget, &GroupWidget::middleMouseClicked, dialog, + [=]() { dialog->removeGroup(groupId); }); connect(groupWidget, &GroupWidget::chatroomWidgetClicked, chatForm, &ChatForm::focusInput); // Signal transmission from the created `groupWidget` (which shown in // ContentDialog) to the `widget` (which shown in main widget) // FIXME: emit should be removed - connect(groupWidget, &GroupWidget::chatroomWidgetClicked, - [=](GenericChatroomWidget* w) { - Q_UNUSED(w); - emit widget->chatroomWidgetClicked(widget); - }); + connect(groupWidget, &GroupWidget::chatroomWidgetClicked, [=](GenericChatroomWidget* w) { + Q_UNUSED(w); + emit widget->chatroomWidgetClicked(widget); + }); - connect(groupWidget, &GroupWidget::newWindowOpened, - [=](GenericChatroomWidget* w) { - Q_UNUSED(w); - emit widget->newWindowOpened(widget); - }); + connect(groupWidget, &GroupWidget::newWindowOpened, [=](GenericChatroomWidget* w) { + Q_UNUSED(w); + emit widget->newWindowOpened(widget); + }); // FIXME: emit should be removed emit widget->chatroomWidgetClicked(widget); @@ -1915,11 +1918,12 @@ Group* Widget::createGroup(int groupId) bool enabled = coreAv->isGroupAvEnabled(groupId); Group* newgroup = GroupList::addGroup(groupId, groupName, enabled, core->getUsername()); - bool compact = Settings::getInstance().getCompactLayout(); - GroupWidget* widget = new GroupWidget(groupId, groupName, compact); - groupWidgets[groupId] = widget; - + std::shared_ptr chatroom(new GroupChatroom(newgroup)); + const auto compact = Settings::getInstance().getCompactLayout(); + auto widget = new GroupWidget(chatroom, compact); auto form = new GroupChatForm(newgroup); + groupWidgets[groupId] = widget; + groupChatrooms[groupId] = chatroom; groupChatForms[groupId] = form; contactListWidget->addGroupWidget(widget); @@ -1934,9 +1938,7 @@ Group* Widget::createGroup(int groupId) auto widgetRemoveGroup = static_cast(&Widget::removeGroup); #endif connect(widget, &GroupWidget::removeGroup, this, widgetRemoveGroup); - connect(widget, &GroupWidget::middleMouseClicked, this, [=]() { - removeGroup(groupId); - }); + connect(widget, &GroupWidget::middleMouseClicked, this, [=]() { removeGroup(groupId); }); connect(widget, &GroupWidget::chatroomWidgetClicked, form, &ChatForm::focusInput); connect(form, &GroupChatForm::sendMessage, core, &Core::sendGroupMessage); connect(form, &GroupChatForm::sendAction, core, &Core::sendGroupAction); @@ -2275,8 +2277,7 @@ inline QIcon Widget::prepareIcon(QString path, int w, int h) } desktop = desktop.toLower(); - if (desktop == "xfce" || desktop.contains("gnome") || desktop == "mate" - || desktop == "x-cinnamon") { + if (desktop == "xfce" || desktop.contains("gnome") || desktop == "mate" || desktop == "x-cinnamon") { if (w > 0 && h > 0) { QSvgRenderer renderer(path); diff --git a/src/widget/widget.h b/src/widget/widget.h index bd82f551b..0b3fbbb63 100644 --- a/src/widget/widget.h +++ b/src/widget/widget.h @@ -48,11 +48,13 @@ class ContentLayout; class Core; class FilesForm; class Friend; +class FriendChatroom; class FriendListWidget; class FriendWidget; class GenericChatroomWidget; class Group; class GroupChatForm; +class GroupChatroom; class GroupInvite; class GroupInviteForm; class GroupWidget; @@ -303,9 +305,12 @@ private: unsigned int unreadGroupInvites; int icon_size; - QMap groupWidgets; QMap friendWidgets; + QMap> friendChatrooms; QMap chatForms; + + QMap groupWidgets; + QMap> groupChatrooms; QMap groupChatForms; #ifdef Q_OS_MAC diff --git a/translations/bg.ts b/translations/bg.ts index d86140c19..d47760582 100644 --- a/translations/bg.ts +++ b/translations/bg.ts @@ -81,39 +81,39 @@ which may lead to problems with video calls. Enables the experimental audio backend with echo cancelling support, needs qTox restart to take effect. - + Включва експериментален звуков процесор с потискане на ехото, изисква рестарт на qTox. Enable experimental audio backend - + Включва експериментален звуков процесор Audio quality - + Качество на звука Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. - + Качество на предавания звук. Намалете тази настройка ако нямате достатъчно широколентова връзка или ако искате да намалите използването на интернет. High (64 kbps) - + Високо (64 kбита/с) Medium (32 kbps) - + Средно (32 kбита/с) Low (16 kbps) - + Ниско (16 kбита/с) Very low (8 kbps) - + Много ниско (8 kбита/с) Threshold - + Прагова стойност @@ -198,7 +198,7 @@ which may lead to problems with video calls. Public key: - Публичен ключ: + Публичен ключ: Used aliases: @@ -242,7 +242,7 @@ which may lead to problems with video calls. Automatically accept group chat invitations from this contact if set. - + Автоматично приемане на покани за групов чат от този контакт ако е включено. Auto accept group invites @@ -355,7 +355,7 @@ which may lead to problems with video calls. %1 Tox ID is invalid or does not exist Toxme error - + %1 Tox ID е невалиден или не съществува You can't add yourself as a friend! @@ -364,24 +364,24 @@ which may lead to problems with video calls. Open contact list - + Отваряне на списък с контакти Couldn't open file - + Не може да се отвори файла Couldn't open the contact file Error message when trying to open a contact list file to import - + Не може да се отвори файла с контакти Invalid file - + Невалиден файл We couldn't find any contacts to import in this file! - + Не са намерени контакти за зареждане в този файл! Tox ID @@ -401,11 +401,11 @@ which may lead to problems with video calls. Open Button to choose a file with a list of contacts to import - + Отваряне Send friend requests - + Изпращане на заявка за приятелство %1 here! Tox me maybe? @@ -414,7 +414,7 @@ which may lead to problems with video calls. Import a list of contacts, one Tox ID per line - + Зареждане на списък с контакти, по един Tox ID на ред Ready to import %n contact(s), click send to confirm @@ -426,7 +426,7 @@ which may lead to problems with video calls. Import contacts - + Зареждане на контакти @@ -658,7 +658,7 @@ which may lead to problems with video calls. Export to file - + Изнасяне към файл Save chat log @@ -666,7 +666,7 @@ which may lead to problems with video calls. Call with %1 ended unexpectedly. %2 - + Връзката с %1 завърши неочаквано. %2 @@ -929,7 +929,7 @@ which may lead to problems with video calls. Never - + Никога @@ -1297,7 +1297,7 @@ instead of system taskbar. GroupInviteWidget Invited by %1 on %2 at %3. - Поканен от %1 на %2 в %3 + Поканен от %1на %2 в %3. Join @@ -1581,7 +1581,7 @@ Profile does not contain your history. %1 messages - + %1 съобщения @@ -1803,7 +1803,7 @@ You may want to create one. Contact search input for known friends - + Входно поле за търсене на известни приятели Sorting and visibility @@ -1961,18 +1961,18 @@ If you are getting spammed with friend requests, change the NoSpam. BlackList - + Черен списък Filter group message by group member's public key. Put public key here, one per line. - + Филтриране на групови съобщения по публичен ключ на членовете. Поставете побличните ключове тук, по един на ред. Profile Failed to derive key from password, the profile won't use the new password. - + Неуспешно произвеждане на ключ от парола, профилът няма за използва новата парола. Couldn't change password on the database, it might be corrupted or use the old password. @@ -2128,7 +2128,7 @@ Please use another image. Couldn't change password - + Неуспешна смяна на парола This bunch of characters tells other Tox clients how to contact you. @@ -2139,7 +2139,7 @@ This ID includes the NoSpam code (in blue), and the checksum (in gray). Empty path is unavaliable - + Празен път не е достъпен Failed to rename @@ -2155,15 +2155,15 @@ This ID includes the NoSpam code (in blue), and the checksum (in gray). Empty name - + Празно име Empty name is unavaliable - + Празно име не е достъпно Empty path - + Празен път Couldn't change password on the database, it might be corrupted or use the old password. @@ -2409,11 +2409,11 @@ It will be installed when qTox restarts. Reformatting text in progress.. - + Изпълнява се преформатиране на текст.. Starts new instance and opens the login screen. - + Стартира ново копие и отваря екрана за вписване. @@ -2709,7 +2709,7 @@ It will be installed when qTox restarts. Use identicons instead of empty avatars - + Използване на идентикони вместо празни аватари diff --git a/translations/ko.ts b/translations/ko.ts index 44d5b6eaf..cdd6d196a 100644 --- a/translations/ko.ts +++ b/translations/ko.ts @@ -33,7 +33,7 @@ Playback device - + 재생 기기 Use slider to set volume of your speakers. @@ -87,7 +87,7 @@ which may lead to problems with video calls. Audio quality - + 오디오 음질 Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. @@ -95,23 +95,23 @@ which may lead to problems with video calls. High (64 kbps) - + 높음(64kbps) Medium (32 kbps) - + 중간(32kbps) Low (16 kbps) - + 낮음 (16kbps) Very low (8 kbps) - + 아주 낮음 (8kbps) Threshold - + 한계점 @@ -135,7 +135,7 @@ which may lead to problems with video calls. You are using qTox version %1. - qTox 버젼 %1. + qTox 버젼 %1 사용중. Commit hash: %1 @@ -184,7 +184,7 @@ which may lead to problems with video calls. AboutFriendForm Dialog - + 대화 username @@ -232,11 +232,11 @@ which may lead to problems with video calls. Audio - 오디오 + 오디오 Audio + Video - 오디오 + 비디오 + 오디오 + 비디오 Automatically accept group chat invitations from this contact if set. @@ -244,7 +244,7 @@ which may lead to problems with video calls. Auto accept group invites - + 그룹 초대를 받아드립니다. Remove history (operation can not be undone!) @@ -264,11 +264,11 @@ which may lead to problems with video calls. History removed - 기록이 삭제되었습니다 + 기록 삭제 Chat history with %1 removed! - %1 채팅기록이 삭제되었습니다! + %1 채팅기록이 삭제되었습니다! Choose an auto accept directory @@ -357,7 +357,7 @@ which may lead to problems with video calls. You can't add yourself as a friend! When trying to add your own Tox ID as friend - 자기 자신을 친구로 추가할 수 없습니다! + 자신을 친구로 추가할 수 없습니다! Open contact list @@ -365,7 +365,7 @@ which may lead to problems with video calls. Couldn't open file - + 파일을 열 수 없습니다 Couldn't open the contact file @@ -393,16 +393,16 @@ which may lead to problems with video calls. Message The message you send in friend requests - 메시지 + 메시지 Open Button to choose a file with a list of contacts to import - + 열기 Send friend requests - + 친구 요청을 보냄 %1 here! Tox me maybe? @@ -475,7 +475,7 @@ which may lead to problems with video calls. Save File - 파일보관 + 파일저장 Logs (*.log) @@ -499,7 +499,7 @@ which may lead to problems with video calls. Portable - + 이동가능 Connection Settings @@ -584,7 +584,7 @@ which may lead to problems with video calls. Bad idea - + 좋지않은 의견 %1 calling @@ -606,7 +606,7 @@ which may lead to problems with video calls. qTox wasn't able to save the screenshot laut Duden ist Screenshot schon deutsch - + 스크린샷을 저장할 수 없었습니다. Call with %1 ended. %2 @@ -646,7 +646,7 @@ which may lead to problems with video calls. online contact status - + 온라인 %1 is now %2 @@ -659,7 +659,7 @@ which may lead to problems with video calls. Save chat log - + 채팅 기록을 저장 Call with %1 ended unexpectedly. %2 @@ -674,7 +674,7 @@ which may lead to problems with video calls. Start audio call - + 음성통화 시작 End audio call @@ -694,7 +694,7 @@ which may lead to problems with video calls. Start video call - + 영상통화 시작 End video call @@ -718,7 +718,7 @@ which may lead to problems with video calls. Mute call - + 음소거 전화 Microphone can be muted only during a call @@ -795,12 +795,12 @@ which may lead to problems with video calls. Your message is too long! Error while sending friendship request - 제한된 메시지 길이를 초과했습니다! + 제한된 메시지 길이를 초과했습니다! Friend is already added Error while sending friendship request - 이미 추가된 친구입니다 + 이미 추가된 친구입니다 @@ -895,7 +895,7 @@ which may lead to problems with video calls. Transferred Files "Headline" of the window - + 전송 된 파일 Downloads @@ -910,23 +910,23 @@ which may lead to problems with video calls. FriendListWidget Today - 오늘 + 오늘 Yesterday - 어제 + 어제 Last 7 days - 지난 7일 + 지난 7일 This month - 이달내 + 이 달 Older than 6 Months - 6개월 이전 + 6개월 이전 Never @@ -2726,23 +2726,23 @@ It will be installed when qTox restarts. File - + 파일 Edit Profile - + 프로필 편집 Change Status - + 상태 바꾸기 Log out - + 로그아웃 Edit - + 편집 Logout @@ -2772,7 +2772,7 @@ It will be installed when qTox restarts. Previous Conversation - + 이전의 대화 Executable file @@ -2803,7 +2803,7 @@ It will be installed when qTox restarts. <Empty> Placeholder when someone's name in a group chat is empty - + 비었습니다 Message failed to send @@ -2874,17 +2874,17 @@ It will be installed when qTox restarts. Add friend title of the window - + 친구 추가 Group invites title of the window - + 그룹 초대 File transfers title of the window - + 파일 이동 Settings @@ -2894,7 +2894,7 @@ It will be installed when qTox restarts. My profile title of the window - + 내 프로필 diff --git a/translations/sv.ts b/translations/sv.ts index 267eb3639..3057d175c 100644 --- a/translations/sv.ts +++ b/translations/sv.ts @@ -113,7 +113,7 @@ vilket kan leda till problem med videosamtal. Threshold - + Tröskel @@ -169,7 +169,7 @@ vilket kan leda till problem med videosamtal. bug-tracker Replaces `%1` in the `A list of all known…` - felbevakare + felbevakare Writing Useful Bug Reports @@ -179,7 +179,7 @@ vilket kan leda till problem med videosamtal. contributors Replaces `%1` in `See a full list of…` - bidragare + bidragsgivare @@ -190,11 +190,11 @@ vilket kan leda till problem med videosamtal. username - användarnamn + användarnamn status message - statusmeddelande + statusmeddelande Public key: @@ -214,7 +214,7 @@ vilket kan leda till problem med videosamtal. Auto accept files - Acceptera automatiskt filer + Acceptera filer automatiskt Default directory to save files: @@ -222,23 +222,23 @@ vilket kan leda till problem med videosamtal. Auto accept for this contact is disabled - Acceptera automatiskt för den här kontakten är avaktiverad + Acceptera automatiskt för den här kontakten är inaktiverat Auto accept call: - Acceptera automatiskt samtal: + Acceptera samtal automatiskt: Manual - Handbok + Handbok Audio - Ljud + Ljud Audio + Video - Ljud + Video + Ljud + video Automatically accept group chat invitations from this contact if set. @@ -246,15 +246,15 @@ vilket kan leda till problem med videosamtal. Auto accept group invites - Acceptera automatiskt gruppinbjudningar + Acceptera gruppinbjudningar automatiskt Remove history (operation can not be undone!) - Ta bort historia (operation kan inte ångras!) + Ta bort historik (operation kan inte ångras!) Notes - Anteckningar + Anteckningar Input field for notes about the contact @@ -266,11 +266,11 @@ vilket kan leda till problem med videosamtal. History removed - Historik raderad + Historik borttagen Chat history with %1 removed! - Chatthistorik med %1 raderad! + Chatthistorik med %1 borttagen! Choose an auto accept directory @@ -359,7 +359,7 @@ vilket kan leda till problem med videosamtal. You can't add yourself as a friend! When trying to add your own Tox ID as friend - Du kan inte lägga till dig själv som vän! + Du kan inte lägga till dig själv som vän! Open contact list @@ -385,17 +385,17 @@ vilket kan leda till problem med videosamtal. Tox ID Tox ID of the person you're sending a friend request to - Tox-ID + Tox-ID either 76 hexadecimal characters or name@example.com Tox ID format description - antingen 76 hexadecimala tecken eller name@example.com + antingen 76 hexadecimala tecken eller namn@exempel.se Message The message you send in friend requests - Meddelande + Meddelande Open @@ -418,11 +418,9 @@ vilket kan leda till problem med videosamtal. Ready to import %n contact(s), click send to confirm Shows the number of contacts we're about to import from a file (at least one) - - - - - + Klar för att importera %n kontakt(er), klicka på skicka för att bekräfta + Klar för att importera %n kontakter, klicka på skicka för att bekräfta + Import contacts Importera kontakter @@ -680,15 +678,15 @@ vilket kan leda till problem med videosamtal. End audio call - Avsluta ljudsamtal + Avsluta ljudsamtal Cancel audio call - Avbryt ljudsamtal + Avbryt ljudsamtal Accept audio call - Acceptera ljudsamtal + Acceptera ljudsamtal Can't start video call @@ -700,23 +698,23 @@ vilket kan leda till problem med videosamtal. End video call - Avsluta videosamtal + Avsluta videosamtal Cancel video call - Avbryt videosamtal + Avbryt videosamtal Accept video call - Acceptera videosamtal + Acceptera videosamtal Sound can be disabled only during a call - Ljud kan endast avaktiveras under ett samtal + Ljud kan endast inaktiveras under ett samtal Unmute call - Slå på samtal + Slå på mikrofon Mute call @@ -1964,7 +1962,7 @@ Om du blir spammad med vänförfrågningar, ändra NoSpam. Filter group message by group member's public key. Put public key here, one per line. - + Filtrera gruppmeddelande genom gruppmedlems allmänna nyckel. Ange den offentliga nyckeln här, en per rad. @@ -2141,39 +2139,39 @@ Detta ID inkluderar NoSpam-koden (i blått) och checksum (i grått). Empty path is unavaliable - + Tom sökväg är inte tillgänglig Failed to rename - Det gick inte att byta namn + Det gick inte att byta namn Profile already exists - Profil finns redan + Profilen finns redan A profile named "%1" already exists. - En profil med namnet "%1" finns redan. + En profil med namnet "%1" finns redan. Empty name - + Tomt namn Empty name is unavaliable - + Tomt namn är inte tillgängligt Empty path - + Tom sökväg Couldn't change password on the database, it might be corrupted or use the old password. - Kunde inte byta lösenord på databasen, den kan vara trasig eller använda det gamla lösenordet. + Det gick inte att byta lösenord på databasen, den kan vara trasig eller använda det gamla lösenordet. Export profile - Exportera profil + Exportera profil Tox save file (*.tox) @@ -2183,17 +2181,17 @@ Detta ID inkluderar NoSpam-koden (i blått) och checksum (i grått). The following files could not be deleted: deletion failed text part 1 - Följande filer kan inte tas bort: + Följande filer kunde inte tas bort: Please manually remove them. deletion failed text part 2 - Vänligen ta bort dem manuellt. + Ta bort dem manuellt. Are you sure you want to delete your password? deletion confirmation text - Är du säker på att du vill ta bort ditt lösenord? + Är du säker på att du vill ta bort ditt lösenord? @@ -2707,11 +2705,11 @@ Den kommer att installeras när qTox startas om. If enabled every contact without an avatar set will have a generated avatar based on their Tox ID instead of a default picture. Requires restart to apply. toolTip for show identicons - + Om den är aktiverad kommer varje kontakt utan en avatar att ha en genererad avatar baserat på deras Tox-ID istället för en standardbild. Kräver omstart att tillämpa. Use identicons instead of empty avatars - + Använd identicons istället för tomma avatarer diff --git a/translations/ta.ts b/translations/ta.ts index 733fa48bc..b4e31683d 100644 --- a/translations/ta.ts +++ b/translations/ta.ts @@ -89,7 +89,7 @@ which may lead to problems with video calls. Audio quality - + ஒலித்தரம் Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. diff --git a/translations/uk.ts b/translations/uk.ts index 33e362e64..e506fcd26 100644 --- a/translations/uk.ts +++ b/translations/uk.ts @@ -93,7 +93,7 @@ which may lead to problems with video calls. Transmitted audio quality. Lower this setting if your bandwidth is not high enough or if you want to lower the internet usage. - + Якість передаваємого звуку. Зменшіть цей параметр якщо пропускна здатність з'єднання недостатня або ви хочете зменшити використання Інтернет трафіку. High (64 kbps) @@ -137,7 +137,7 @@ which may lead to problems with video calls. You are using qTox version %1. - Ви використовуєте qTox версії %1. + Ви використовуєте qTox версії %1. Commit hash: %1 @@ -145,11 +145,11 @@ which may lead to problems with video calls. toxcore version: %1 - Версія toxcore: %1 + Версія toxcore: %1 Qt version: %1 - Версія Qt: %1 + Версія Qt: %1 A list of all known issues may be found at our %1 at Github. If you discover a bug or security vulnerability within qTox, please report it according to the guidelines in our %2 wiki article. @@ -169,7 +169,7 @@ which may lead to problems with video calls. bug-tracker Replaces `%1` in the `A list of all known…` - баг-трекері + баг-трекері Writing Useful Bug Reports @@ -367,40 +367,40 @@ which may lead to problems with video calls. Couldn't open file - + Помилка відкриття файлу Couldn't open the contact file Error message when trying to open a contact list file to import - + Помилка відкриття файлу контактів Invalid file - + Неправильний файл We couldn't find any contacts to import in this file! - + Контактів для імпорту не знайдено! Tox ID Tox ID of the person you're sending a friend request to - + Ідентифікатор Tox either 76 hexadecimal characters or name@example.com Tox ID format description - 76 шістнадцяткових цифр або name@example.com + 76 шістнадцяткових символів або name@example.com Message The message you send in friend requests - Повідомлення + Повідомлення Open Button to choose a file with a list of contacts to import - + Відкрити Send friend requests @@ -413,7 +413,7 @@ which may lead to problems with video calls. Import a list of contacts, one Tox ID per line - + Імпортувати список контактів, один Ідентифікатор Tox на рядок Ready to import %n contact(s), click send to confirm @@ -426,14 +426,14 @@ which may lead to problems with video calls. Import contacts - + Імпортувати контакти AdvancedForm Advanced - Додатково + Більше Unless you %1 know what you are doing, please do %2 change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history. @@ -441,7 +441,7 @@ which may lead to problems with video calls. really - впевнені + дійсно not @@ -461,11 +461,11 @@ which may lead to problems with video calls. Yes - Так + Так No - Ні + Ні Call active @@ -483,7 +483,7 @@ which may lead to problems with video calls. Logs (*.log) - + Логи (*.log) @@ -512,7 +512,7 @@ which may lead to problems with video calls. Enable IPv6 (recommended) Text on a checkbox to enable IPv6 - Дозволити IPv6 (рекомендовано) + Увімкнути IPv6 (рекомендовано) Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. @@ -522,21 +522,21 @@ which may lead to problems with video calls. Enable UDP (recommended) Text on checkbox to disable UDP - Дозволити UDP (рекомендовано) + Увімкнути UDP (рекомендовано) Proxy type: - Тип проксі: + Тип проксі: Address: Text on proxy addr label - Адреса проксі: + Адреса: Port: Text on proxy port label - Порт: + Порт: None @@ -544,28 +544,28 @@ which may lead to problems with video calls. SOCKS5 - SOCKS5 + SOCKS5 HTTP - HTTP + HTTP Reconnect reconnect button - Повторно під'єднатись + Перепідключитись Debug - Налагодження + Відладка Export Debug Log - Вивантажити логи налагодження + Експортувати логи відладки Copy Debug Log - Скопіювати логи налагодження + Скопіювати логи відладки @@ -600,20 +600,20 @@ which may lead to problems with video calls. Failed to send file "%1" - Не вдалось відправити файл «%1» + Не вдалось відправити файл "%1" Failed to open temporary file Temporary file for screenshot - Помилка під час відкриття тимчасового файлу + Помилка відкриття тимчасового файлу qTox wasn't able to save the screenshot - qTox не може зберегти снімок екрану + qTox не зміг зберегти скриншот Call with %1 ended. %2 - Виклик із %1 завершено. %2 + Дзвінок з %1 завершено. %2 Call duration: diff --git a/ui/chatArea/chatHead.css b/ui/chatArea/chatHead.css index d7a3846f0..c0f9ee85b 100644 --- a/ui/chatArea/chatHead.css +++ b/ui/chatArea/chatHead.css @@ -1,39 +1,42 @@ -QLineEdit { +QLineEdit +{ color: @black; background: white; border: 0px; } -#nameLabel { +#nameLabel +{ color: @black; font: @mediumBold; font-size:12px; } -#statusLabel { +#statusLabel +{ color: @mediumGrey; font: @medium; font-size:12px; } -#peersLabel { +#peersLabel +{ color: @mediumGrey; font: @medium; font-size:12px; } -QLabel[peerType="our"] { +QLabel[peerType="our"] +{ color: green; } -QLabel[peerType="muted"] { +QLabel[peerType="muted"] +{ color: darkred; } -QLabel[playingAudio="true"] { +QLabel[playingAudio="true"] +{ font-weight: bold; } - -QMenu:disabled { - color: gray; -} diff --git a/ui/friendList/friendList.css b/ui/friendList/friendList.css index 3d7972065..3e18a085e 100644 --- a/ui/friendList/friendList.css +++ b/ui/friendList/friendList.css @@ -1,64 +1,80 @@ -QScrollArea { +QScrollArea +{ background: @themeMedium; } -QScrollBar:vertical { +QScrollBar:vertical +{ background: @themeMedium; width: 16px; padding: 0px 3px 0px 3px; } -QScrollBar:handle:vertical { +QScrollBar:handle:vertical +{ background: @themeDark; min-height: 20px; border-radius: 5px; margin: 3px 0px 3px 0px; } -QScrollBar:handle:vertical:hover { +QScrollBar:handle:vertical:hover +{ background: @themeMediumDark; } -QScrollBar:handle:vertical:pressed { +QScrollBar:handle:vertical:pressed +{ background: @themeDark; } -QScrollBar:add-line:vertical {height: 0px;subcontrol-position: bottom;subcontrol-origin: margin;} +QScrollBar:add-line:vertical +{ + height: 0px; + subcontrol-position: bottom; + subcontrol-origin: margin; +} -QScrollBar:sub-line:vertical {height: 0px;subcontrol-position: top;subcontrol-origin: margin;} +QScrollBar:sub-line:vertical +{ + height: 0px; + subcontrol-position: top; + subcontrol-origin: margin; +} -QScrollBar:add-page:vertical, QScrollBar::sub-page:vertical { +QScrollBar:add-page:vertical, QScrollBar::sub-page:vertical +{ background: none; } QWidget#circleWidgetContainer > QFrame#line { - color: white; + color: white; } QWidget#circleWidgetContainer { - background-color: @themeMedium; + background-color: @themeMedium; } QWidget#circleWidgetContainer:hover { - background-color: @themeLight; + background-color: @themeLight; } QWidget#circleWidgetContainer QLineEdit { - background-color: @themeLight; + background-color: @themeLight; } QWidget#circleWidgetContainer > QLabel#status { - font: @small; - color: @lightGrey; + font: @small; + color: @lightGrey; } QWidget#circleWidgetContainer > QLabel#name { - font: @big; - color: @white; + font: @big; + color: @white; } diff --git a/ui/loginScreen/loginScreen.css b/ui/loginScreen/loginScreen.css index 9bc063f9f..074070f05 100644 --- a/ui/loginScreen/loginScreen.css +++ b/ui/loginScreen/loginScreen.css @@ -1,58 +1,68 @@ -#LoginScreen { - background-color: #1c1c1c; +#LoginScreen +{ + background-color: #1c1c1c; } -#line { - color: #757575; +#line +{ + color: #757575; } -#stackedWidget { - background-color: #d6d2Cf; +#stackedWidget +{ + background-color: #d6d2Cf; } #labelNewProfile, -#labelLoadProfile { - border-style: solid; - border-width: 15px 0 15px 15px; - border-color: #d6d2cf #d6d2cf #d6d2cf #1c1c1c; +#labelLoadProfile +{ + border-style: solid; + border-width: 15px 0 15px 15px; + border-color: #d6d2cf #d6d2cf #d6d2cf #1c1c1c; } -#labelNewProfile { - margin-top:125px; - margin-bottom:45px; +#labelNewProfile +{ + margin-top:125px; + margin-bottom:45px; } -#labelLoadProfile { - margin-top:165px; - margin-bottom:5px; +#labelLoadProfile +{ + margin-top:165px; + margin-bottom:5px; } LoginScreen > QPushButton, QStackedWidget QPushButton { - background: transparent; - border: none; - color: white; - font-size: 9pt; - font-weight: bold; + background: transparent; + border: none; + color: white; + font-size: 9pt; + font-weight: bold; } #loginButton, #importButton, -#createAccountButton { - border-radius:5px; - padding:5px; - background-color:#42a33a; +#createAccountButton +{ + border-radius:5px; + padding:5px; + background-color:#42a33a; } #loginButton:hover, #importButton:hover, -#createAccountButton:hover { - background: #0c530d; +#createAccountButton:hover +{ + background: #0c530d; } -QLabel, QCheckBox, QProgressBar { - color: black; +QLabel, QCheckBox, QProgressBar +{ + color: black; } -QCheckBox:disabled { - color: gray; +QCheckBox:disabled +{ + color: gray; } diff --git a/ui/msgEdit/msgEdit.css b/ui/msgEdit/msgEdit.css index e9845285d..27125a6c1 100644 --- a/ui/msgEdit/msgEdit.css +++ b/ui/msgEdit/msgEdit.css @@ -1,27 +1,33 @@ -QTextEdit { - border-color: @lightGrey; - border-style: solid; - border-width: 1px 0 1px 1px; +QTextEdit +{ + border-color: @lightGrey; + border-style: solid; + border-width: 1px 0 1px 1px; background: white; } -QTextEdit:hover { - border-color: #d7d4d1; +QTextEdit:hover +{ + border-color: #d7d4d1; } -QTextEdit:pressed { - border-color: #4ea6ea; +QTextEdit:pressed +{ + border-color: #4ea6ea; } -QTextEdit#group { - /*border-radius: 0 6px 6px 0; would use to round corners in groupchat, but Qt's implementation seems to be bugged*/ - border: 1px solid #c4c1bd; +QTextEdit#group +{ + /*border-radius: 0 6px 6px 0; would use to round corners in groupchat, but Qt's implementation seems to be bugged*/ + border: 1px solid #c4c1bd; } -QTextEdit#group:hover { - border-color: #d7d4d1; +QTextEdit#group:hover +{ + border-color: #d7d4d1; } -QTextEdit#group:pressed { - border-color: #4ea6ea; +QTextEdit#group:pressed +{ + border-color: #4ea6ea; } diff --git a/ui/statusButton/statusButton.css b/ui/statusButton/statusButton.css index 0a149ff5a..f97975d2c 100644 --- a/ui/statusButton/statusButton.css +++ b/ui/statusButton/statusButton.css @@ -10,21 +10,22 @@ QPushButton QPushButton:default { - background-color: @themeMediumDark; + background-color: @themeMediumDark; } /*Bugged in Qt, but it's probably better to leave enabled so that users can tell it's clickable*/ QPushButton:hover { - background-color: @themeMedium; + background-color: @themeMedium; } QPushButton:pressed { - background-color: @themeMediumDark; + background-color: @themeMediumDark; } -QPushButton:focus { +QPushButton:focus +{ outline: none; } @@ -32,9 +33,9 @@ QPushButton::menu-indicator {image: none;} QPushButton::menu-indicator:pressed, QPushButton::menu-indicator:open { - image: url(":ui/statusButton/menu_indicator.svg"); - subcontrol-origin: padding; - subcontrol-position: bottom center; - position: relative; + image: url(":ui/statusButton/menu_indicator.svg"); + subcontrol-origin: padding; + subcontrol-position: bottom center; + position: relative; bottom: 2px; } diff --git a/ui/window/profile.css b/ui/window/profile.css index 9c1e97056..daddea876 100644 --- a/ui/window/profile.css +++ b/ui/window/profile.css @@ -1,4 +1,5 @@ -#selfAvatar:hover { +#selfAvatar:hover +{ border-radius: 6px; background-color: #ccc; } diff --git a/ui/window/statusPanel.css b/ui/window/statusPanel.css index 5344a79a7..3b94f248e 100644 --- a/ui/window/statusPanel.css +++ b/ui/window/statusPanel.css @@ -7,7 +7,8 @@ QLineEdit border-radius: 4px; } -QToolButton { +QToolButton +{ background: none; background-color: @themeMedium; color: white; @@ -15,17 +16,20 @@ QToolButton { border-radius: 4px; } -QToolButton:pressed { +QToolButton:pressed +{ background-color: @themeMediumDark; border-radius: 4px; color: white; } -QToolButton::menu-indicator { +QToolButton::menu-indicator +{ image: none } -QPushButton#green { +QPushButton#green +{ background: none; background-color: #6bc260; color: white; @@ -48,12 +52,14 @@ QPushButton#green:pressed /** Uncomment this after https://github.com/qTox/qTox/pull/1640 is merged! -QComboBox:down-arrow { +QComboBox:down-arrow +{ image: url(ui/css/down_arrow.png); } **/ -QListView { +QListView +{ background-color: @themeLight; border-style: none; border-radius: 4px; @@ -69,13 +75,15 @@ QListView { background-color: @themeDark; } -#statusPanel > #statusHead > #nameLabel { +#statusPanel > #statusHead > #nameLabel +{ background-color: @themeDark; font: @extraBig; color: @white; } -#statusPanel > #statusHead > #statusLabel { +#statusPanel > #statusHead > #statusLabel +{ background-color: @themeDark; font: @medium; color: @lightGrey; @@ -102,7 +110,8 @@ QListView { background-color: @themeMedium; } -#statusPanel > #statusHead > #statusButton:focus { +#statusPanel > #statusHead > #statusButton:focus +{ outline: none; } @@ -116,4 +125,3 @@ QListView { position: relative; bottom: 2px; } - diff --git a/ui/window/window.css b/ui/window/window.css index 84c771a95..49c60da8c 100644 --- a/ui/window/window.css +++ b/ui/window/window.css @@ -35,7 +35,8 @@ QPushButton#minimizeButton { border: 0px solid transparent; background-color: transparent; - image: url(":ui/window/minimizeButton.png");} + image: url(":ui/window/minimizeButton.png"); +} QPushButton#minimizeButton:hover {image: url(":ui/window/minimizeButtonHover.png");} QPushButton#minimizeButton:pressed {image: url(":ui/window/minimizeButtonPressed.png");} QPushButton#minimizeButton:focus {outline: none;} @@ -44,7 +45,8 @@ QPushButton#maximizeButton { border: 0px solid transparent; background-color: transparent; - image: url(":ui/window/maximizeButton.png");} + image: url(":ui/window/maximizeButton.png"); +} QPushButton#maximizeButton:hover {image: url(":ui/window/maximizeButtonHover.png");} QPushButton#maximizeButton:pressed {image: url(":ui/window/maximizeButtonPressed.png");} QPushButton#maximizeButton:focus {outline: none;} @@ -53,7 +55,8 @@ QPushButton#closeButton { border: 0px solid transparent; background-color: transparent; - image: url(":ui/window/closeButton.png");} + image: url(":ui/window/closeButton.png"); +} QPushButton#closeButton:hover {image: url(":ui/window/closeButtonHover.png");} QPushButton#closeButton:pressed {image: url(":ui/window/closeButtonPressed.png");} QPushButton#closeButton:focus {outline: none;} @@ -62,7 +65,8 @@ QPushButton#restoreButton { border: 0px solid transparent; background-color: transparent; - image: url(":ui/window/restoreButton.png");} + image: url(":ui/window/restoreButton.png"); +} QPushButton#restoreButton:hover {image: url(":ui/window/restoreButtonHover.png");} QPushButton#restoreButton:pressed {image: url(":ui/window/restoreButtonPressed.png");} QPushButton#restoreButton:focus {outline: none;} diff --git a/windows/cross-compile/build.sh b/windows/cross-compile/build.sh index 12fdba540..c7947792d 100644 --- a/windows/cross-compile/build.sh +++ b/windows/cross-compile/build.sh @@ -26,10 +26,6 @@ # - Doesn't build qTox updater, because it wasn't ported to cmake yet and # because it requires static Qt, which means we'd need to build Qt twice, and # building Qt takes really long time. -# -# - FFmpeg 3.3 doesn't cross-compile correctly, qTox build fails when linking -# against the 3.3 FFmpeg. They have removed `--enable-memalign-hack` switch, -# which might be what causes this. Further research needed. set -euo pipefail @@ -217,15 +213,16 @@ OPENSSL_PREFIX_DIR="$DEP_DIR/libopenssl" OPENSSL_VERSION=1.0.2o # hash from https://www.openssl.org/source/ OPENSSL_HASH="ec3f5c9714ba0fd45cb4e087301eb1336c317e0d20b575a125050470e8089e4d" +OPENSSL_FILENAME="openssl-$OPENSSL_VERSION.tar.gz" if [ ! -f "$OPENSSL_PREFIX_DIR/done" ] then rm -rf "$OPENSSL_PREFIX_DIR" mkdir -p "$OPENSSL_PREFIX_DIR" - wget https://www.openssl.org/source/openssl-$OPENSSL_VERSION.tar.gz - check_sha256 "$OPENSSL_HASH" "openssl-$OPENSSL_VERSION.tar.gz" - bsdtar --no-same-owner --no-same-permissions -xf openssl*.tar.gz - rm openssl*.tar.gz + wget "https://www.openssl.org/source/$OPENSSL_FILENAME" + check_sha256 "$OPENSSL_HASH" "$OPENSSL_FILENAME" + bsdtar --no-same-owner --no-same-permissions -xf "$OPENSSL_FILENAME" + rm $OPENSSL_FILENAME cd openssl* CONFIGURE_OPTIONS="--prefix=$OPENSSL_PREFIX_DIR shared" @@ -256,19 +253,20 @@ fi QT_PREFIX_DIR="$DEP_DIR/libqt5" QT_MAJOR=5 QT_MINOR=9 -QT_PATCH=5 +QT_PATCH=6 QT_VERSION=$QT_MAJOR.$QT_MINOR.$QT_PATCH -# hash from https://download.qt.io/archive/qt/5.9/5.9.5/single/qt-everywhere-opensource-src-5.9.5.tar.xz.mirrorlist -QT_HASH="a75b87f46240a374fde93fb60038d63e3b570457785268c766c639b5dc18ccf6" +# hash from https://download.qt.io/archive/qt/5.9/5.9.6/single/qt-everywhere-opensource-src-5.9.6.tar.xz.mirrorlist +QT_HASH="dacc995ae3a7cdad80eb9fdf6470299a8fac41f468a9bb941670ece523b62af4" +QT_FILENAME="qt-everywhere-opensource-src-$QT_VERSION.tar.xz" if [ ! -f "$QT_PREFIX_DIR/done" ] then rm -rf "$QT_PREFIX_DIR" mkdir -p "$QT_PREFIX_DIR" - wget https://download.qt.io/official_releases/qt/$QT_MAJOR.$QT_MINOR/$QT_VERSION/single/qt-everywhere-opensource-src-$QT_VERSION.tar.xz - check_sha256 "$QT_HASH" "qt-everywhere-opensource-src-$QT_VERSION.tar.xz" - bsdtar --no-same-owner --no-same-permissions -xf qt*.tar.xz - rm qt*.tar.xz + wget "https://download.qt.io/official_releases/qt/$QT_MAJOR.$QT_MINOR/$QT_VERSION/single/$QT_FILENAME" + check_sha256 "$QT_HASH" "$QT_FILENAME" + bsdtar --no-same-owner --no-same-permissions -xf $QT_FILENAME + rm $QT_FILENAME cd qt* export PKG_CONFIG_PATH="$OPENSSL_PREFIX_DIR/lib/pkgconfig" @@ -374,15 +372,16 @@ set -u SQLCIPHER_PREFIX_DIR="$DEP_DIR/libsqlcipher" SQLCIPHER_VERSION=v3.4.2 SQLCIPHER_HASH="69897a5167f34e8a84c7069f1b283aba88cdfa8ec183165c4a5da2c816cfaadb" +SQLCIPHER_FILENAME="$SQLCIPHER_VERSION.tar.gz" if [ ! -f "$SQLCIPHER_PREFIX_DIR/done" ] then rm -rf "$SQLCIPHER_PREFIX_DIR" mkdir -p "$SQLCIPHER_PREFIX_DIR" - wget https://github.com/sqlcipher/sqlcipher/archive/$SQLCIPHER_VERSION.tar.gz -O sqlcipher.tar.gz - check_sha256 "$SQLCIPHER_HASH" "sqlcipher.tar.gz" - bsdtar --no-same-owner --no-same-permissions -xf sqlcipher.tar.gz - rm sqlcipher.tar.gz + wget "https://github.com/sqlcipher/sqlcipher/archive/$SQLCIPHER_FILENAME" + check_sha256 "$SQLCIPHER_HASH" "$SQLCIPHER_FILENAME" + bsdtar --no-same-owner --no-same-permissions -xf "$SQLCIPHER_FILENAME" + rm $SQLCIPHER_FILENAME cd sqlcipher* sed -i s/'LIBS="-lcrypto $LIBS"'/'LIBS="-lcrypto -lgdi32 $LIBS"'/g configure @@ -397,8 +396,8 @@ then @@ -1074,7 +1074,7 @@ $(TOP)/ext/fts5/fts5_varint.c \ $(TOP)/ext/fts5/fts5_vocab.c \ - --fts5parse.c: $(TOP)/ext/fts5/fts5parse.y lemon + +-fts5parse.c: $(TOP)/ext/fts5/fts5parse.y lemon +fts5parse.c: $(TOP)/ext/fts5/fts5parse.y lemon$(BEXE) cp $(TOP)/ext/fts5/fts5parse.y . rm -f fts5parse.h @@ -431,17 +430,18 @@ fi # FFmpeg FFMPEG_PREFIX_DIR="$DEP_DIR/libffmpeg" -FFMPEG_VERSION=3.2.10 -FFMPEG_HASH="3c1626220c7b68ff6be7312559f77f3c65ff6809daf645d4470ac0189926bdbc" +FFMPEG_VERSION=4.0.1 +FFMPEG_HASH="605f5c01c60db35d3b617a79cabb2c7032412be243554602eeed1b628125c0ee" +FFMPEG_FILENAME="ffmpeg-$FFMPEG_VERSION.tar.xz" if [ ! -f "$FFMPEG_PREFIX_DIR/done" ] then rm -rf "$FFMPEG_PREFIX_DIR" mkdir -p "$FFMPEG_PREFIX_DIR" - wget https://www.ffmpeg.org/releases/ffmpeg-$FFMPEG_VERSION.tar.xz - check_sha256 "$FFMPEG_HASH" "ffmpeg-$FFMPEG_VERSION.tar.xz" - bsdtar --no-same-owner --no-same-permissions -xf ffmpeg*.tar.xz - rm ffmpeg*.tar.xz + wget "https://www.ffmpeg.org/releases/$FFMPEG_FILENAME" + check_sha256 "$FFMPEG_HASH" "$FFMPEG_FILENAME" + bsdtar --no-same-owner --no-same-permissions -xf $FFMPEG_FILENAME + rm $FFMPEG_FILENAME cd ffmpeg* if [[ "$ARCH" == "x86_64"* ]] @@ -453,6 +453,7 @@ then fi ./configure $CONFIGURE_OPTIONS \ + --enable-gpl \ --prefix="$FFMPEG_PREFIX_DIR" \ --target-os="mingw32" \ --cross-prefix="$ARCH-w64-mingw32-" \ @@ -460,11 +461,12 @@ then --extra-cflags="-static -O2 -g0" \ --extra-ldflags="-lm -static" \ --pkg-config-flags="--static" \ + --disable-debug \ --disable-shared \ --disable-programs \ --disable-protocols \ --disable-doc \ - --disable-sdl \ + --disable-sdl2 \ --disable-avfilter \ --disable-avresample \ --disable-filters \ @@ -492,14 +494,14 @@ then --disable-decoders \ --disable-demuxers \ --disable-parsers \ + --disable-bsfs \ --enable-demuxer=h264 \ --enable-demuxer=mjpeg \ --enable-parser=h264 \ --enable-parser=mjpeg \ --enable-decoder=h264 \ --enable-decoder=mjpeg \ - --enable-decoder=rawvideo \ - --enable-memalign-hack + --enable-decoder=rawvideo make make install echo -n $FFMPEG_VERSION > $FFMPEG_PREFIX_DIR/done @@ -702,17 +704,18 @@ fi # QREncode QRENCODE_PREFIX_DIR="$DEP_DIR/libqrencode" -QRENCODE_VERSION=4.0.0 -QRENCODE_HASH="c90035e16921117d4086a7fdee65aab85be32beb4a376f6b664b8a425d327d0b" +QRENCODE_VERSION=4.0.2 +QRENCODE_HASH="c9cb278d3b28dcc36b8d09e8cad51c0eca754eb004cb0247d4703cb4472b58b4" +QRENCODE_FILENAME="qrencode-$QRENCODE_VERSION.tar.bz2" if [ ! -f "$QRENCODE_PREFIX_DIR/done" ] then rm -rf "$QRENCODE_PREFIX_DIR" mkdir -p "$QRENCODE_PREFIX_DIR" - wget https://fukuchi.org/works/qrencode/qrencode-$QRENCODE_VERSION.tar.bz2 - check_sha256 "$QRENCODE_HASH" "qrencode-$QRENCODE_VERSION.tar.bz2" - bsdtar --no-same-owner --no-same-permissions -xf qrencode*.tar.bz2 - rm qrencode*.tar.bz2 + wget https://fukuchi.org/works/qrencode/$QRENCODE_FILENAME + check_sha256 "$QRENCODE_HASH" "$QRENCODE_FILENAME" + bsdtar --no-same-owner --no-same-permissions -xf "$QRENCODE_FILENAME" + rm $QRENCODE_FILENAME cd qrencode* CFLAGS="-O2 -g0" ./configure --host="$ARCH-w64-mingw32" \ @@ -738,15 +741,16 @@ fi EXIF_PREFIX_DIR="$DEP_DIR/libexif" EXIF_VERSION=0.6.21 EXIF_HASH="16cdaeb62eb3e6dfab2435f7d7bccd2f37438d21c5218ec4e58efa9157d4d41a" +EXIF_FILENAME=libexif-$EXIF_VERSION.tar.bz2 if [ ! -f "$EXIF_PREFIX_DIR/done" ] then rm -rf "$EXIF_PREFIX_DIR" mkdir -p "$EXIF_PREFIX_DIR" - wget https://sourceforge.net/projects/libexif/files/libexif/0.6.21/libexif-$EXIF_VERSION.tar.bz2 - check_sha256 "$EXIF_HASH" "libexif-$EXIF_VERSION.tar.bz2" - bsdtar --no-same-owner --no-same-permissions -xf libexif*.tar.bz2 - rm libexif*.tar.bz2 + wget https://sourceforge.net/projects/libexif/files/libexif/$EXIF_VERSION/$EXIF_FILENAME + check_sha256 "$EXIF_HASH" "$EXIF_FILENAME" + bsdtar --no-same-owner --no-same-permissions -xf $EXIF_FILENAME + rm $EXIF_FILENAME cd libexif* CFLAGS="-O2 -g0" ./configure --host="$ARCH-w64-mingw32" \ @@ -771,15 +775,16 @@ fi OPUS_PREFIX_DIR="$DEP_DIR/libopus" OPUS_VERSION=1.2.1 OPUS_HASH="cfafd339ccd9c5ef8d6ab15d7e1a412c054bf4cb4ecbbbcc78c12ef2def70732" +OPUS_FILENAME="opus-$OPUS_VERSION.tar.gz" if [ ! -f "$OPUS_PREFIX_DIR/done" ] then rm -rf "$OPUS_PREFIX_DIR" mkdir -p "$OPUS_PREFIX_DIR" - wget https://archive.mozilla.org/pub/opus/opus-$OPUS_VERSION.tar.gz - check_sha256 "$OPUS_HASH" "opus-$OPUS_VERSION.tar.gz" - bsdtar --no-same-owner --no-same-permissions -xf opus*.tar.gz - rm opus*.tar.gz + wget "https://archive.mozilla.org/pub/opus/$OPUS_FILENAME" + check_sha256 "$OPUS_HASH" "$OPUS_FILENAME" + bsdtar --no-same-owner --no-same-permissions -xf "$OPUS_FILENAME" + rm $OPUS_FILENAME cd opus* CFLAGS="-O2 -g0" ./configure --host="$ARCH-w64-mingw32" \ @@ -804,15 +809,16 @@ fi SODIUM_PREFIX_DIR="$DEP_DIR/libsodium" SODIUM_VERSION=1.0.16 SODIUM_HASH="eeadc7e1e1bcef09680fb4837d448fbdf57224978f865ac1c16745868fbd0533" +SODIUM_FILENAME="libsodium-$SODIUM_VERSION.tar.gz" if [ ! -f "$SODIUM_PREFIX_DIR/done" ] then rm -rf "$SODIUM_PREFIX_DIR" mkdir -p "$SODIUM_PREFIX_DIR" - wget https://download.libsodium.org/libsodium/releases/libsodium-$SODIUM_VERSION.tar.gz - check_sha256 "$SODIUM_HASH" "libsodium-$SODIUM_VERSION.tar.gz" - bsdtar --no-same-owner --no-same-permissions -xf libsodium*.tar.gz - rm libsodium*.tar.gz + wget "https://download.libsodium.org/libsodium/releases/$SODIUM_FILENAME" + check_sha256 "$SODIUM_HASH" "$SODIUM_FILENAME" + bsdtar --no-same-owner --no-same-permissions -xf "$SODIUM_FILENAME" + rm "$SODIUM_FILENAME" cd libsodium* ./configure --host="$ARCH-w64-mingw32" \ @@ -836,15 +842,16 @@ fi VPX_PREFIX_DIR="$DEP_DIR/libvpx" VPX_VERSION=v1.7.0 VPX_HASH="1fec931eb5c94279ad219a5b6e0202358e94a93a90cfb1603578c326abfc1238" +VPX_FILENAME="libvpx-$VPX_VERSION.tar.bz2" if [ ! -f "$VPX_PREFIX_DIR/done" ] then rm -rf "$VPX_PREFIX_DIR" mkdir -p "$VPX_PREFIX_DIR" - wget https://github.com/webmproject/libvpx/archive/$VPX_VERSION.tar.gz -O libvpx-$VPX_VERSION.tar.gz - check_sha256 "$VPX_HASH" "libvpx-$VPX_VERSION.tar.gz" - bsdtar --no-same-owner --no-same-permissions -xf libvpx-*.tar.gz - rm libvpx*.tar.gz + wget https://github.com/webmproject/libvpx/archive/$VPX_VERSION.tar.gz -O $VPX_FILENAME + check_sha256 "$VPX_HASH" "$VPX_FILENAME" + bsdtar --no-same-owner --no-same-permissions -xf "$VPX_FILENAME" + rm $VPX_FILENAME cd libvpx* if [[ "$ARCH" == "x86_64" ]] @@ -877,17 +884,18 @@ fi # Toxcore TOXCORE_PREFIX_DIR="$DEP_DIR/libtoxcore" -TOXCORE_VERSION=0.2.2 -TOXCORE_HASH=a3b25d8bd92b9526b47ba1f60a2893d2154a80bb7ae690f44b5a2dea41c76ea1 +TOXCORE_VERSION=0.2.3 +TOXCORE_HASH=22c52f286c46d3f802edb6978bcf2a53f8301363e2b745784613427a33ba3a34 +TOXCORE_FILENAME="c-toxcore-$TOXCORE_VERSION.tar.gz" if [ ! -f "$TOXCORE_PREFIX_DIR/done" ] then rm -rf "$TOXCORE_PREFIX_DIR" mkdir -p "$TOXCORE_PREFIX_DIR" - wget https://github.com/TokTok/c-toxcore/archive/v$TOXCORE_VERSION.tar.gz -O c-toxcore-$TOXCORE_VERSION.tar.gz - check_sha256 "$TOXCORE_HASH" "c-toxcore-$TOXCORE_VERSION.tar.gz" - bsdtar --no-same-owner --no-same-permissions -xf c-toxcore*.tar.gz - rm c-toxcore*.tar.gz + wget https://github.com/TokTok/c-toxcore/archive/v$TOXCORE_VERSION.tar.gz -O $TOXCORE_FILENAME + check_sha256 "$TOXCORE_HASH" "$TOXCORE_FILENAME" + bsdtar --no-same-owner --no-same-permissions -xf "$TOXCORE_FILENAME" + rm "$TOXCORE_FILENAME" cd c-toxcore* mkdir -p build @@ -1117,15 +1125,18 @@ then fi set -u +# Spell check on windows currently not supported, disable if [[ "$BUILD_TYPE" == "release" ]] then cmake -DCMAKE_TOOLCHAIN_FILE=./toolchain.cmake \ -DCMAKE_BUILD_TYPE=Release \ + -DSPELL_CHECK=OFF \ .. elif [[ "$BUILD_TYPE" == "debug" ]] then cmake -DCMAKE_TOOLCHAIN_FILE=./toolchain.cmake \ -DCMAKE_BUILD_TYPE=Debug \ + -DSPELL_CHECK=OFF \ .. fi diff --git a/windows/qtox.nsi b/windows/qtox.nsi index 349d4529d..8ada7378c 100644 --- a/windows/qtox.nsi +++ b/windows/qtox.nsi @@ -276,7 +276,7 @@ Section "Install" ${WriteRegStr} "${REG_ROOT}" "${REG_APP_PATH}" "" "$INSTDIR\${MAIN_APP_EXE}" ${WriteRegStr} "${REG_ROOT}" "${REG_APP_PATH}" "Path" "$INSTDIR\bin\" ${WriteRegStr} ${REG_ROOT} "${UNINSTALL_PATH}" "DisplayName" "qTox" - ${WriteRegStr} ${REG_ROOT} "${UNINSTALL_PATH}" "DisplayVersion" "1.15.0" + ${WriteRegStr} ${REG_ROOT} "${UNINSTALL_PATH}" "DisplayVersion" "1.16.3" ${WriteRegStr} ${REG_ROOT} "${UNINSTALL_PATH}" "Publisher" "The qTox Project" ${WriteRegStr} ${REG_ROOT} "${UNINSTALL_PATH}" "UninstallString" "$INSTDIR\uninstall.exe" ${WriteRegStr} ${REG_ROOT} "${UNINSTALL_PATH}" "URLInfoAbout" "https://qtox.github.io" diff --git a/windows/qtox64.nsi b/windows/qtox64.nsi index 7b90b6a09..b943388b9 100755 --- a/windows/qtox64.nsi +++ b/windows/qtox64.nsi @@ -277,7 +277,7 @@ Section "Install" ${WriteRegStr} "${REG_ROOT}" "${REG_APP_PATH}" "" "$INSTDIR\${MAIN_APP_EXE}" ${WriteRegStr} "${REG_ROOT}" "${REG_APP_PATH}" "Path" "$INSTDIR\bin\" ${WriteRegStr} ${REG_ROOT} "${UNINSTALL_PATH}" "DisplayName" "qTox" - ${WriteRegStr} ${REG_ROOT} "${UNINSTALL_PATH}" "DisplayVersion" "1.15.0" + ${WriteRegStr} ${REG_ROOT} "${UNINSTALL_PATH}" "DisplayVersion" "1.16.3" ${WriteRegStr} ${REG_ROOT} "${UNINSTALL_PATH}" "Publisher" "The qTox Project" ${WriteRegStr} ${REG_ROOT} "${UNINSTALL_PATH}" "UninstallString" "$INSTDIR\uninstall.exe" ${WriteRegStr} ${REG_ROOT} "${UNINSTALL_PATH}" "URLInfoAbout" "https://qtox.github.io"