Compare commits

...

13 commits

Author SHA1 Message Date
mauke
d3051aa8ff
Merge d299f7af5c into 1a00d91316 2024-11-05 15:38:59 -06:00
DeepBlueV7.X
1a00d91316
Merge pull request #1833 from Integral-Tech/refactor-string-conversion
refactor: use fmt lib to avoid back-and-forth conversion
2024-10-30 01:10:07 +00:00
Integral
b7a5d714c6
refactor: use fmt lib to avoid back-and-forth conversion 2024-10-19 16:49:12 +08:00
DeepBlueV7.X
2f967978f2
Merge pull request #1825 from Integral-Tech/fix-tooltip
Add profile name to tooltip & fix message count
2024-10-13 22:19:09 +00:00
Integral
3b0df06629
Add profile name to tooltip & fix message count 2024-10-13 20:55:58 +08:00
Nicolas Werner
27683bedc4
Fix media deletion of animated files 2024-10-09 03:39:19 +02:00
Nicolas Werner
80a39cca17
Disable http3 support by default and warn if users enable it 2024-10-08 23:49:29 +02:00
Nicolas Werner
5523460f4e
Fix menu positions 2024-10-08 23:35:13 +02:00
Nicolas Werner
65c6e96e24
Get rid of platform dialogs/menus now that Qt6.8 supports native menus without them
This will look bad on some platforms and older versions for now, but
should fix a lot of crashes and we can report the rest as bugs.
2024-10-08 23:04:41 +02:00
Nicolas Werner
3a3c3def7c
Bump qt version in apple silicon build 2024-10-08 22:37:00 +02:00
Nicolas Werner
da2d7861d7
Move more templates out of the cache private header 2024-10-08 20:18:47 +02:00
Nicolas Werner
db68281a28
Limit status messages to 255 bytes 2024-10-08 16:55:07 +02:00
Lukas Mai
d299f7af5c Search users' names in addition to their messages 2023-02-09 18:23:54 +01:00
20 changed files with 714 additions and 597 deletions

View file

@ -151,7 +151,7 @@ build-clazy:
- apt-get -y install --no-install-suggests --no-install-recommends ca-certificates build-essential ninja-build cmake gcc make automake ccache liblmdb-dev
libssl-dev libqt5multimedia5-plugins libqt5multimediagsttools5 libqt5multimediaquick5 libqt5svg5-dev
qtmultimedia5-dev qtquickcontrols2-5-dev qttools5-dev qttools5-dev-tools qtdeclarative5-dev
qml-module-qtmultimedia qml-module-qtquick-controls2 qml-module-qtquick-layouts qml-module-qt-labs-platform
qml-module-qtmultimedia qml-module-qtquick-controls2 qml-module-qtquick-layouts
qt5keychain-dev ccache libcurl4-openssl-dev libevent-dev libspdlog-dev git nlohmann-json3-dev libcmark-dev asciidoc time # libolm-dev
# need recommended deps for wget
- apt-get -y install wget
@ -282,7 +282,7 @@ build-macos-as:
- pipx ensurepath
- . ~/.zshrc
- mkdir $HOME/Qt
- aqt install-qt --outputdir $HOME/qt mac desktop 6.6 clang_64 -m qtlocation qtimageformats qtmultimedia qtpositioning qtshadertools
- aqt install-qt --outputdir $HOME/qt mac desktop 6.8 clang_64 -m qtlocation qtimageformats qtmultimedia qtpositioning qtshadertools
script:
- export QTPATH=($HOME/qt/6.*/macos/bin)
- export PATH="$QTPATH:${PATH}"
@ -394,7 +394,7 @@ build-flatpak:
- apt-get -y install --no-install-suggests --no-install-recommends ca-certificates build-essential ninja-build cmake gcc make automake ccache liblmdb-dev
libssl-dev libqt5multimedia5-plugins libqt5multimediagsttools5 libqt5multimediaquick5 libqt5svg5-dev
qtmultimedia5-dev qtquickcontrols2-5-dev qttools5-dev qttools5-dev-tools qtdeclarative5-dev
qml-module-qtmultimedia qml-module-qtquick-controls2 qml-module-qtquick-layouts qml-module-qt-labs-platform
qml-module-qtmultimedia qml-module-qtquick-controls2 qml-module-qtquick-layouts
qt5keychain-dev ccache libcurl4-openssl-dev libevent-dev libspdlog-dev nlohmann-json3-dev libcmark-dev asciidoc libre2-dev libgtest-dev libgl1-mesa-dev qml-module-qtquick-particles2
# Installing the packages needed to build AppImage

View file

@ -365,7 +365,7 @@ cmake --build build
*Build requirements + qml modules needed at runtime (you may not need all of them, but the following seem to work according to reports):*
```bash
sudo apt install --no-install-recommends g++ cmake make zlib1g-dev libssl-dev libolm-dev liblmdb-dev libcmark-dev nlohmann-json3-dev libspdlog-dev libevent-dev libcurl4-openssl-dev libre2-dev libxcb-ewmh-dev asciidoc-base \
qt{base,declarative,tools,multimedia,quickcontrols2-}5-dev libqt6svg5-dev qt6keychain-dev qml-module-qt{gstreamer,multimedia,quick-extras,-labs-settings,-labs-platform,graphicaleffects,quick-controls2,quick-particles2} \
qt{base,declarative,tools,multimedia,quickcontrols2-}5-dev libqt6svg5-dev qt6keychain-dev qml-module-qt{gstreamer,multimedia,quick-extras,-labs-settings,graphicaleffects,quick-controls2,quick-particles2} \
libgstreamer1.0-dev libgstreamer-plugins-{base,bad}1.0-dev qtgstreamer-plugins-qt6 libnice-dev ninja-build
```
lmdb++-dev is too old so bundled lmdbxx must be used.

View file

@ -3,7 +3,6 @@
// SPDX-License-Identifier: GPL-3.0-or-later
import "./components"
import Qt.labs.platform 1.1 as Platform
import QtQml
import QtQuick
import QtQuick.Controls
@ -101,17 +100,18 @@ Page {
]
onClicked: Communities.setCurrentTagId(model.id)
onPressAndHold: communityContextMenu.show(model.id, model.hidden, model.muted)
onPressAndHold: communityContextMenu.show(communityItem, model.id, model.hidden, model.muted)
Item {
anchors.fill: parent
TapHandler {
id: rth
acceptedButtons: Qt.RightButton
acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad
gesturePolicy: TapHandler.ReleaseWithinBounds
onSingleTapped: communityContextMenu.show(model.id, model.hidden, model.muted)
onSingleTapped: communityContextMenu.show(rth, model.id, model.hidden, model.muted)
}
}
RowLayout {
@ -195,28 +195,28 @@ Page {
}
}
Platform.Menu {
Menu {
id: communityContextMenu
property bool hidden
property bool muted
property string tagId
function show(id_, hidden_, muted_) {
function show(parent, id_, hidden_, muted_) {
tagId = id_;
hidden = hidden_;
muted = muted_;
open();
popup(parent);
}
Platform.MenuItem {
MenuItem {
checkable: true
checked: communityContextMenu.muted
text: qsTr("Do not show notification counts for this community or tag.")
onTriggered: Communities.toggleTagMute(communityContextMenu.tagId)
}
Platform.MenuItem {
MenuItem {
checkable: true
checked: communityContextMenu.hidden
text: qsTr("Hide rooms with this tag or from this community by default.")

View file

@ -4,7 +4,6 @@
import "./ui"
import "./dialogs"
import Qt.labs.platform 1.1 as Platform
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.2
@ -393,7 +392,7 @@ Item {
}
}
}
Platform.Menu {
Menu {
id: messageContextMenuC
property string eventId
@ -421,9 +420,9 @@ Item {
else
link = "";
if (showAt_)
open(showAt_);
popup(showAt_);
else
open();
popup();
}
Component {
@ -448,7 +447,7 @@ Item {
ReportMessage {}
}
Platform.MenuItem {
MenuItem {
enabled: visible
text: qsTr("Go to &message")
visible: filteredTimeline.filterByContent
@ -458,21 +457,21 @@ Item {
room.showEvent(messageContextMenuC.eventId);
}
}
Platform.MenuItem {
MenuItem {
enabled: visible
text: qsTr("&Copy")
visible: messageContextMenuC.text
onTriggered: Clipboard.text = messageContextMenuC.text
}
Platform.MenuItem {
MenuItem {
enabled: visible
text: qsTr("Copy &link location")
visible: messageContextMenuC.link
onTriggered: Clipboard.text = messageContextMenuC.link
}
Platform.MenuItem {
MenuItem {
id: reactionOption
text: qsTr("Re&act")
@ -483,39 +482,39 @@ Item {
TimelineManager.focusMessageInput();
})
}
Platform.MenuItem {
MenuItem {
text: qsTr("Repl&y")
visible: room ? room.permissions.canSend(MtxEvent.TextMessage) : false
onTriggered: room.reply = (messageContextMenuC.eventId)
}
Platform.MenuItem {
MenuItem {
enabled: visible
text: qsTr("&Edit")
visible: messageContextMenuC.isEditable && (room ? room.permissions.canSend(MtxEvent.TextMessage) : false)
onTriggered: room.edit = (messageContextMenuC.eventId)
}
Platform.MenuItem {
MenuItem {
enabled: visible
text: qsTr("&Thread")
visible: (room ? room.permissions.canSend(MtxEvent.TextMessage) : false)
onTriggered: room.thread = (messageContextMenuC.threadId || messageContextMenuC.eventId)
}
Platform.MenuItem {
MenuItem {
enabled: visible
text: visible && room.pinnedMessages.includes(messageContextMenuC.eventId) ? qsTr("Un&pin") : qsTr("&Pin")
visible: (room ? room.permissions.canChange(MtxEvent.PinnedEvents) : false)
onTriggered: visible && room.pinnedMessages.includes(messageContextMenuC.eventId) ? room.unpin(messageContextMenuC.eventId) : room.pin(messageContextMenuC.eventId)
}
Platform.MenuItem {
MenuItem {
text: qsTr("&Read receipts")
onTriggered: room.showReadReceipts(messageContextMenuC.eventId)
}
Platform.MenuItem {
MenuItem {
text: qsTr("&Forward")
visible: messageContextMenuC.eventType == MtxEvent.ImageMessage || messageContextMenuC.eventType == MtxEvent.VideoMessage || messageContextMenuC.eventType == MtxEvent.AudioMessage || messageContextMenuC.eventType == MtxEvent.FileMessage || messageContextMenuC.eventType == MtxEvent.Sticker || messageContextMenuC.eventType == MtxEvent.TextMessage || messageContextMenuC.eventType == MtxEvent.LocationMessage || messageContextMenuC.eventType == MtxEvent.EmoteMessage || messageContextMenuC.eventType == MtxEvent.NoticeMessage
@ -526,15 +525,15 @@ Item {
timelineRoot.destroyOnClose(forwardMess);
}
}
Platform.MenuItem {
MenuItem {
text: qsTr("&Mark as read")
}
Platform.MenuItem {
MenuItem {
text: qsTr("View raw message")
onTriggered: room.viewRawMessage(messageContextMenuC.eventId)
}
Platform.MenuItem {
MenuItem {
enabled: visible
text: qsTr("View decrypted raw message")
// TODO(Nico): Fix this still being iterated over, when using keyboard to select options
@ -542,7 +541,7 @@ Item {
onTriggered: room.viewDecryptedRawMessage(messageContextMenuC.eventId)
}
Platform.MenuItem {
MenuItem {
text: qsTr("Remo&ve message")
visible: (room ? room.permissions.canRedact() : false) || messageContextMenuC.isSender
@ -554,7 +553,7 @@ Item {
timelineRoot.destroyOnClose(dialog);
}
}
Platform.MenuItem {
MenuItem {
text: qsTr("Report message")
enabled: visible
onTriggered: function () {
@ -564,21 +563,21 @@ Item {
timelineRoot.destroyOnClose(dialog);
}
}
Platform.MenuItem {
MenuItem {
enabled: visible
text: qsTr("&Save as")
visible: messageContextMenuC.eventType == MtxEvent.ImageMessage || messageContextMenuC.eventType == MtxEvent.VideoMessage || messageContextMenuC.eventType == MtxEvent.AudioMessage || messageContextMenuC.eventType == MtxEvent.FileMessage || messageContextMenuC.eventType == MtxEvent.Sticker
onTriggered: room.saveMedia(messageContextMenuC.eventId)
}
Platform.MenuItem {
MenuItem {
enabled: visible
text: qsTr("&Open in external program")
visible: messageContextMenuC.eventType == MtxEvent.ImageMessage || messageContextMenuC.eventType == MtxEvent.VideoMessage || messageContextMenuC.eventType == MtxEvent.AudioMessage || messageContextMenuC.eventType == MtxEvent.FileMessage || messageContextMenuC.eventType == MtxEvent.Sticker
onTriggered: room.openMedia(messageContextMenuC.eventId)
}
Platform.MenuItem {
MenuItem {
enabled: visible
text: qsTr("Copy link to eve&nt")
visible: messageContextMenuC.eventId
@ -592,7 +591,7 @@ Item {
ForwardCompleter {
}
}
Platform.Menu {
Menu {
id: replyContextMenuC
property string eventId
@ -606,21 +605,21 @@ Item {
open();
}
Platform.MenuItem {
MenuItem {
enabled: visible
text: qsTr("&Copy")
visible: replyContextMenuC.text
onTriggered: Clipboard.text = replyContextMenuC.text
}
Platform.MenuItem {
MenuItem {
enabled: visible
text: qsTr("Copy &link location")
visible: replyContextMenuC.link
onTriggered: Clipboard.text = replyContextMenuC.link
}
Platform.MenuItem {
MenuItem {
enabled: visible
text: qsTr("&Go to quoted message")
visible: true

View file

@ -5,8 +5,6 @@
import "./components"
import "./dialogs"
import "./ui"
import Qt.labs.platform 1.1 as Platform
import QtQml
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
@ -43,6 +41,8 @@ Page {
id: buttonRow
ImageButton {
id: startChatButton
Layout.fillWidth: true
Layout.margins: Nheko.paddingMedium
ToolTip.delay: Nheko.tooltipDelay
@ -53,17 +53,17 @@ Page {
hoverEnabled: true
image: ":/icons/icons/ui/add-square-button.svg"
onClicked: roomJoinCreateMenu.open(parent)
onClicked: roomJoinCreateMenu.popup(startChatButton)
Platform.Menu {
Menu {
id: roomJoinCreateMenu
Platform.MenuItem {
MenuItem {
text: qsTr("Join a room")
onTriggered: Nheko.openJoinRoomDialog()
}
Platform.MenuItem {
MenuItem {
text: qsTr("Create a new room")
onTriggered: {
@ -72,7 +72,7 @@ Page {
timelineRoot.destroyOnClose(createRoom);
}
}
Platform.MenuItem {
MenuItem {
text: qsTr("Start a direct chat")
onTriggered: {
@ -81,7 +81,7 @@ Page {
timelineRoot.destroyOnClose(createDirect);
}
}
Platform.MenuItem {
MenuItem {
text: qsTr("Create a new community")
onTriggered: {
@ -255,68 +255,72 @@ Page {
Nheko.setStatusMessage(text);
}
}
Platform.Menu {
Menu {
id: userInfoMenu
Platform.MenuItem {
MenuItem {
text: qsTr("Profile settings")
onTriggered: userInfoPanel.openUserProfile()
}
Platform.MenuItem {
MenuItem {
text: qsTr("Set status message")
onTriggered: statusDialog.show()
}
Platform.MenuSeparator {
MenuSeparator {
}
Platform.MenuItemGroup {
ButtonGroup {
id: onlineStateGroup
}
Platform.MenuItem {
MenuItem {
text: qsTr("Automatic online status")
group: onlineStateGroup
ButtonGroup.group: onlineStateGroup
checkable: true
checked: Settings.presence == Settings.AutomaticPresence
onTriggered: if (checked) Settings.presence = Settings.AutomaticPresence
}
Platform.MenuItem {
MenuItem {
text: qsTr("Online")
group: onlineStateGroup
ButtonGroup.group: onlineStateGroup
checkable: true
checked: Settings.presence == Settings.Online
onTriggered: if (checked) Settings.presence = Settings.Online
}
Platform.MenuItem {
MenuItem {
text: qsTr("Unavailable")
group: onlineStateGroup
ButtonGroup.group: onlineStateGroup
checkable: true
checked: Settings.presence == Settings.Unavailable
onTriggered: if (checked) Settings.presence = Settings.Unavailable
}
Platform.MenuItem {
MenuItem {
text: qsTr("Offline")
group: onlineStateGroup
ButtonGroup.group: onlineStateGroup
checkable: true
checked: Settings.presence == Settings.Offline
onTriggered: if (checked) Settings.presence = Settings.Offline
}
}
TapHandler {
id: userTapHandler
acceptedButtons: Qt.LeftButton
gesturePolicy: TapHandler.ReleaseWithinBounds
margin: -Nheko.paddingSmall
onLongPressed: userInfoMenu.open()
onLongPressed: userInfoMenu.popup(userTapHandler)
onSingleTapped: userInfoPanel.openUserProfile()
}
TapHandler {
id: userTapHandler2
acceptedButtons: Qt.RightButton
gesturePolicy: TapHandler.ReleaseWithinBounds
margin: -Nheko.paddingSmall
onSingleTapped: userInfoMenu.open()
onSingleTapped: userInfoMenu.popup(userTapHandler2)
}
}
Rectangle {
@ -525,7 +529,7 @@ Page {
}
onPressAndHold: {
if (!isInvite)
roomContextMenu.show(roomId, tags);
roomContextMenu.show(roomItem, roomId, tags);
}
Ripple {
@ -538,13 +542,15 @@ Page {
anchors.margins: 1
TapHandler {
id: roomItemTh
acceptedButtons: Qt.RightButton
acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad
gesturePolicy: TapHandler.ReleaseWithinBounds
onSingleTapped: {
if (!TimelineManager.isInvite)
roomContextMenu.show(roomId, tags);
roomContextMenu.show(roomItemTh, roomId, tags);
}
}
}
@ -734,16 +740,16 @@ Page {
roomid: roomContextMenu.roomid
}
}
Platform.Menu {
Menu {
id: roomContextMenu
property string roomid
property var tags
function show(roomid_, tags_) {
function show(parent, roomid_, tags_) {
roomid = roomid_;
tags = tags_;
open();
popup(parent);
}
InputDialog {
@ -756,7 +762,7 @@ Page {
Rooms.toggleTag(roomContextMenu.roomid, "u." + text, true);
}
}
Platform.MenuItem {
MenuItem {
text: qsTr("Open separately")
onTriggered: {
@ -768,27 +774,27 @@ Page {
destroyOnClose(roomWindow);
}
}
Platform.MenuItem {
MenuItem {
text: qsTr("Mark as read")
onTriggered: Rooms.getRoomById(roomContextMenu.roomid).markRoomAsRead()
}
Platform.MenuItem {
MenuItem {
text: qsTr("Room settings")
onTriggered: TimelineManager.openRoomSettings(roomContextMenu.roomid)
}
Platform.MenuItem {
MenuItem {
text: qsTr("Leave room")
onTriggered: TimelineManager.openLeaveRoomDialog(roomContextMenu.roomid)
}
Platform.MenuItem {
MenuItem {
text: qsTr("Copy room link")
onTriggered: Rooms.copyLink(roomContextMenu.roomid)
}
Platform.Menu {
Menu {
id: tagsMenu
title: qsTr("Tag room as:")
@ -796,7 +802,7 @@ Page {
Instantiator {
model: Communities.tagsWithDefault
delegate: Platform.MenuItem {
delegate: MenuItem {
property string t: modelData
checkable: true
@ -820,7 +826,7 @@ Page {
onObjectAdded: (index, object) => tagsMenu.insertItem(index, object)
onObjectRemoved: (index, object) => tagsMenu.removeItem(object)
}
Platform.MenuItem {
MenuItem {
text: qsTr("Create new tag...")
onTriggered: newTag.show()

View file

@ -5,10 +5,10 @@
import "./dialogs"
import "./pages"
import "./ui"
import Qt.labs.platform 1.1 as Platform
import QtQuick
import QtQuick.Controls
import QtQuick.Window
import QtQuick.Dialogs
import im.nheko
Pane {
@ -342,15 +342,13 @@ Pane {
return UIA.submit3pidToken(t);
}
}
Platform.MessageDialog {
MessageDialog {
id: uiaConfirmationLinkDialog
buttons: Platform.MessageDialog.Ok
buttons: MessageDialog.Ok
text: qsTr("Wait for the confirmation link to arrive, then continue.")
// Broken on macos, see https://bugreports.qt.io/browse/QTBUG-102078
//onAccepted: UIA.continue3pidReceived()
onOkClicked: UIA.continue3pidReceived()
onAccepted: UIA.continue3pidReceived()
}
Connections {
function onConfirm3pidToken() {

View file

@ -2,7 +2,6 @@
//
// SPDX-License-Identifier: GPL-3.0-or-later
import Qt.labs.platform 1.1 as Platform
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.2
@ -235,28 +234,28 @@ Pane {
image: ":/icons/icons/ui/options.svg"
visible: !!room
onClicked: roomOptionsMenu.open(roomOptionsButton)
onClicked: roomOptionsMenu.popup(roomOptionsButton)
Platform.Menu {
Menu {
id: roomOptionsMenu
Platform.MenuItem {
MenuItem {
text: qsTr("Invite users")
visible: room ? room.permissions.canInvite() : false
onTriggered: TimelineManager.openInviteUsers(roomId)
}
Platform.MenuItem {
MenuItem {
text: qsTr("Members")
onTriggered: TimelineManager.openRoomMembers(room)
}
Platform.MenuItem {
MenuItem {
text: qsTr("Leave room")
onTriggered: TimelineManager.openLeaveRoomDialog(roomId)
}
Platform.MenuItem {
MenuItem {
text: qsTr("Settings")
onTriggered: TimelineManager.openRoomSettings(roomId)

View file

@ -2,11 +2,11 @@
//
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.15
import Qt.labs.platform 1.1 as Platform
import im.nheko 1.0
import QtQuick
import QtQuick.Controls
import im.nheko
Platform.Menu {
Menu {
id: spacesMenu
property string roomid
@ -19,56 +19,61 @@ Platform.Menu {
onAboutToShow: loadChildren = true
//onAboutToHide: loadChildren = false
Platform.MenuItemGroup {
ButtonGroup {
id: modificationGroup
visible: position != -1
//visible: position != -1
}
Platform.MenuItem {
MenuItem {
text: qsTr("Official community for this room")
group: modificationGroup
ButtonGroup.group: modificationGroup
visible: position != -1
checkable: true
checked: spacesMenu.position >= 0 && (modelData.childValid && modelData.parentValid && modelData.canonical)
enabled: spacesMenu.position >= 0 && (modelData.canEditChild && modelData.canEditParent)
onTriggered: if (checked) Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, true, true, true)
}
Platform.MenuItem {
MenuItem {
text: qsTr("Affiliated community for this room")
group: modificationGroup
ButtonGroup.group: modificationGroup
visible: position != -1
checkable: true
checked: spacesMenu.position >= 0 && (modelData.childValid && modelData.parentValid && !modelData.canonical)
enabled: spacesMenu.position >= 0 && (modelData.canEditChild && modelData.canEditParent)
onTriggered: if (checked) Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, true, true, false)
}
Platform.MenuItem {
MenuItem {
text: qsTr("Listed only for community members")
group: modificationGroup
ButtonGroup.group: modificationGroup
visible: position != -1
checkable: true
checked: spacesMenu.position >= 0 && (modelData.childValid && !modelData.parentValid)
enabled: spacesMenu.position >= 0 && ((modelData.canEditChild || modelData.childValid) && (!modelData.parentValid || modelData.canEditParent))
onTriggered: if (checked) Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, false, true, false)
}
Platform.MenuItem {
MenuItem {
text: qsTr("Listed only for room members")
group: modificationGroup
ButtonGroup.group: modificationGroup
visible: position != -1
checkable: true
checked: spacesMenu.position >= 0 && (!modelData.childValid && modelData.parentValid)
enabled: spacesMenu.position >= 0 && ((modelData.canEditChild) && (modelData.parentValid || modelData.canEditParent))
onTriggered: if (checked) Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, true, false, false)
}
Platform.MenuItem {
MenuItem {
text: qsTr("Not related")
group: modificationGroup
ButtonGroup.group: modificationGroup
visible: position != -1
checkable: true
checked: spacesMenu.position >= 0 && (!modelData.childValid && !modelData.parentValid)
enabled: spacesMenu.position >= 0 && ((modelData.canEditChild || !modelData.childValid) && (!modelData.parentValid || modelData.canEditParent))
onTriggered: if (checked) Communities.updateSpaceStatus(modelData.roomid, spacesMenu.roomid, false, false, false)
}
Platform.MenuSeparator {
text: qsTr("Subcommunities")
group: modificationGroup
visible: modificationGroup.visible && inst.model != undefined
MenuSeparator {
//text: qsTr("Subcommunities")
ButtonGroup.group: modificationGroup
visible: position != -1 && inst.model != undefined
}
Instantiator {

View file

@ -4,11 +4,11 @@
import ".."
import "../ui"
import Qt.labs.platform 1.1 as Platform
import QtQuick 2.15
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.2
import QtQuick.Window 2.13
import QtQuick.Dialogs
import im.nheko 1.0
ApplicationWindow {
@ -580,26 +580,23 @@ ApplicationWindow {
Layout.alignment: Qt.AlignRight
}
Platform.MessageDialog {
MessageDialog {
id: confirmEncryptionDialog
title: qsTr("End-to-End Encryption")
text: qsTr(`Encryption is currently experimental and things might break unexpectedly. <br>
Please take note that it can't be disabled afterwards.`)
modality: Qt.NonModal
// Broken on macos, see https://bugreports.qt.io/browse/QTBUG-102078
//onAccepted: {
onOkClicked: {
onAccepted: {
if (roomSettings.isEncryptionEnabled)
return ;
roomSettings.enableEncryption();
}
//onRejected: {
onCancelClicked: {
onRejected: {
encryptionToggle.checked = false;
}
buttons: Platform.MessageDialog.Ok | Platform.MessageDialog.Cancel
buttons: MessageDialog.Ok | MessageDialog.Cancel
}
Label {

File diff suppressed because it is too large Load diff

View file

@ -9,12 +9,6 @@
#include <QDateTime>
#include <QString>
#if __has_include(<lmdbxx/lmdb++.h>)
#include <lmdbxx/lmdb++.h>
#else
#include <lmdb++.h>
#endif
#include <mtx/events/collections.hpp>
#include <mtx/responses/notifications.hpp>
#include <mtx/responses/sync.hpp>
@ -29,12 +23,20 @@ struct Messages;
struct StateEvents;
}
namespace lmdb {
class txn;
class dbi;
}
struct CacheDb;
class Cache final : public QObject
{
Q_OBJECT
public:
Cache(const QString &userId, QObject *parent = nullptr);
~Cache() noexcept;
std::string displayName(const std::string &room_id, const std::string &user_id);
QString displayName(const QString &room_id, const QString &user_id);
@ -97,19 +99,11 @@ public:
//! Get a specific state event
template<typename T>
std::optional<mtx::events::StateEvent<T>>
getStateEvent(const std::string &room_id, std::string_view state_key = "")
{
auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
return getStateEvent<T>(txn, room_id, state_key);
}
getStateEvent(const std::string &room_id, std::string_view state_key = "");
template<typename T>
std::vector<mtx::events::StateEvent<T>>
getStateEventsWithType(const std::string &room_id,
mtx::events::EventType type = mtx::events::state_content_to_type<T>)
{
auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
return getStateEventsWithType<T>(txn, room_id, type);
}
mtx::events::EventType type = mtx::events::state_content_to_type<T>);
//! retrieve a specific event from account data
//! pass empty room_id for global account data
@ -304,20 +298,6 @@ public:
std::is_same_v<std::remove_cv_t<std::remove_reference_t<T>>,
mtx::events::StateEvent<decltype(std::declval<T>().content)>>;
static int compare_state_key(const MDB_val *a, const MDB_val *b)
{
auto get_skey = [](const MDB_val *v) {
auto temp = std::string_view(static_cast<const char *>(v->mv_data), v->mv_size);
// allow only passing the state key, in which case no null char will be in it and we
// return the whole string because rfind returns npos.
// We search from the back, because state keys could include nullbytes, event ids can
// not.
return temp.substr(0, temp.rfind('\0'));
};
return get_skey(a).compare(get_skey(b));
}
signals:
void newReadReceipts(const QString &room_id, const std::vector<QString> &event_ids);
void roomReadStatus(const std::map<QString, bool> &status);
@ -401,107 +381,44 @@ private:
//! Sends signals for the rooms that are removed.
void
removeLeftRooms(lmdb::txn &txn, const std::map<std::string, mtx::responses::LeftRoom> &rooms)
{
for (const auto &room : rooms) {
removeRoom(txn, room.first);
// Clean up leftover invites.
removeInvite(txn, room.first);
}
}
removeLeftRooms(lmdb::txn &txn, const std::map<std::string, mtx::responses::LeftRoom> &rooms);
void updateSpaces(lmdb::txn &txn,
const std::set<std::string> &spaces_with_updates,
std::set<std::string> rooms_with_updates);
lmdb::dbi getEventsDb(lmdb::txn &txn, const std::string &room_id)
{
return lmdb::dbi::open(txn, std::string(room_id + "/events").c_str(), MDB_CREATE);
}
lmdb::dbi getEventsDb(lmdb::txn &txn, const std::string &room_id);
lmdb::dbi getEventOrderDb(lmdb::txn &txn, const std::string &room_id)
{
return lmdb::dbi::open(
txn, std::string(room_id + "/event_order").c_str(), MDB_CREATE | MDB_INTEGERKEY);
}
lmdb::dbi getEventOrderDb(lmdb::txn &txn, const std::string &room_id);
// inverse of EventOrderDb
lmdb::dbi getEventToOrderDb(lmdb::txn &txn, const std::string &room_id)
{
return lmdb::dbi::open(txn, std::string(room_id + "/event2order").c_str(), MDB_CREATE);
}
lmdb::dbi getEventToOrderDb(lmdb::txn &txn, const std::string &room_id);
lmdb::dbi getMessageToOrderDb(lmdb::txn &txn, const std::string &room_id)
{
return lmdb::dbi::open(txn, std::string(room_id + "/msg2order").c_str(), MDB_CREATE);
}
lmdb::dbi getMessageToOrderDb(lmdb::txn &txn, const std::string &room_id);
lmdb::dbi getOrderToMessageDb(lmdb::txn &txn, const std::string &room_id)
{
return lmdb::dbi::open(
txn, std::string(room_id + "/order2msg").c_str(), MDB_CREATE | MDB_INTEGERKEY);
}
lmdb::dbi getOrderToMessageDb(lmdb::txn &txn, const std::string &room_id);
lmdb::dbi getPendingMessagesDb(lmdb::txn &txn, const std::string &room_id)
{
return lmdb::dbi::open(
txn, std::string(room_id + "/pending").c_str(), MDB_CREATE | MDB_INTEGERKEY);
}
lmdb::dbi getPendingMessagesDb(lmdb::txn &txn, const std::string &room_id);
lmdb::dbi getRelationsDb(lmdb::txn &txn, const std::string &room_id)
{
return lmdb::dbi::open(
txn, std::string(room_id + "/related").c_str(), MDB_CREATE | MDB_DUPSORT);
}
lmdb::dbi getRelationsDb(lmdb::txn &txn, const std::string &room_id);
lmdb::dbi getInviteStatesDb(lmdb::txn &txn, const std::string &room_id)
{
return lmdb::dbi::open(txn, std::string(room_id + "/invite_state").c_str(), MDB_CREATE);
}
lmdb::dbi getInviteStatesDb(lmdb::txn &txn, const std::string &room_id);
lmdb::dbi getInviteMembersDb(lmdb::txn &txn, const std::string &room_id)
{
return lmdb::dbi::open(txn, std::string(room_id + "/invite_members").c_str(), MDB_CREATE);
}
lmdb::dbi getInviteMembersDb(lmdb::txn &txn, const std::string &room_id);
lmdb::dbi getStatesDb(lmdb::txn &txn, const std::string &room_id)
{
return lmdb::dbi::open(txn, std::string(room_id + "/state").c_str(), MDB_CREATE);
}
lmdb::dbi getStatesDb(lmdb::txn &txn, const std::string &room_id);
lmdb::dbi getStatesKeyDb(lmdb::txn &txn, const std::string &room_id)
{
auto db = lmdb::dbi::open(
txn, std::string(room_id + "/states_key").c_str(), MDB_CREATE | MDB_DUPSORT);
lmdb::dbi_set_dupsort(txn, db, compare_state_key);
return db;
}
lmdb::dbi getStatesKeyDb(lmdb::txn &txn, const std::string &room_id);
lmdb::dbi getAccountDataDb(lmdb::txn &txn, const std::string &room_id)
{
return lmdb::dbi::open(txn, std::string(room_id + "/account_data").c_str(), MDB_CREATE);
}
lmdb::dbi getAccountDataDb(lmdb::txn &txn, const std::string &room_id);
lmdb::dbi getMembersDb(lmdb::txn &txn, const std::string &room_id)
{
return lmdb::dbi::open(txn, std::string(room_id + "/members").c_str(), MDB_CREATE);
}
lmdb::dbi getMembersDb(lmdb::txn &txn, const std::string &room_id);
lmdb::dbi getUserKeysDb(lmdb::txn &txn) { return lmdb::dbi::open(txn, "user_key", MDB_CREATE); }
lmdb::dbi getUserKeysDb(lmdb::txn &txn);
lmdb::dbi getVerificationDb(lmdb::txn &txn)
{
return lmdb::dbi::open(txn, "verified", MDB_CREATE);
}
lmdb::dbi getVerificationDb(lmdb::txn &txn);
QString getDisplayName(const mtx::events::StateEvent<mtx::events::state::Member> &event)
{
if (!event.content.display_name.empty())
return QString::fromStdString(event.content.display_name);
return QString::fromStdString(event.state_key);
}
QString getDisplayName(const mtx::events::StateEvent<mtx::events::state::Member> &event);
std::optional<VerificationCache> verificationCache(const std::string &user_id, lmdb::txn &txn);
VerificationStatus verificationStatus_(const std::string &user_id, lmdb::txn &txn);
@ -509,27 +426,6 @@ private:
void setNextBatchToken(lmdb::txn &txn, const std::string &token);
lmdb::env env_;
lmdb::dbi syncStateDb_;
lmdb::dbi roomsDb_;
lmdb::dbi spacesChildrenDb_, spacesParentsDb_;
lmdb::dbi invitesDb_;
lmdb::dbi readReceiptsDb_;
lmdb::dbi notificationsDb_;
lmdb::dbi presenceDb_;
lmdb::dbi devicesDb_;
lmdb::dbi deviceKeysDb_;
lmdb::dbi inboundMegolmSessionDb_;
lmdb::dbi outboundMegolmSessionDb_;
lmdb::dbi megolmSessionDataDb_;
lmdb::dbi olmSessionDb_;
lmdb::dbi encryptedRooms_;
lmdb::dbi eventExpiryBgJob_;
QString localUserId_;
QString cacheDirectory_;
@ -538,6 +434,8 @@ private:
VerificationStorage verification_storage;
bool databaseReady_ = false;
std::unique_ptr<CacheDb> db;
};
namespace cache {
@ -546,11 +444,12 @@ client();
}
#define NHEKO_CACHE_GET_STATE_EVENT_FORWARD(Content) \
extern template std::optional<mtx::events::StateEvent<Content>> Cache::getStateEvent( \
lmdb::txn &txn, const std::string &room_id, std::string_view state_key); \
extern template std::optional<mtx::events::StateEvent<Content>> Cache::getStateEvent<Content>( \
const std::string &room_id, std::string_view state_key); \
\
extern template std::vector<mtx::events::StateEvent<Content>> Cache::getStateEventsWithType( \
lmdb::txn &txn, const std::string &room_id, mtx::events::EventType type);
extern template std::vector<mtx::events::StateEvent<Content>> \
Cache::getStateEventsWithType<Content>(const std::string &room_id, \
mtx::events::EventType type);
NHEKO_CACHE_GET_STATE_EVENT_FORWARD(mtx::events::state::Aliases)
NHEKO_CACHE_GET_STATE_EVENT_FORWARD(mtx::events::state::Avatar)

View file

@ -13,6 +13,9 @@
#include <mtx/responses.hpp>
#include "Logging.h"
#include "UserSettingsPage.h"
namespace http {
mtx::http::Client *
@ -20,9 +23,15 @@ client()
{
static auto client_ = [] {
auto c = std::make_shared<mtx::http::Client>();
c->alt_svc_cache_path((QStandardPaths::writableLocation(QStandardPaths::CacheLocation) +
"/curl_alt_svc_cache.txt")
.toStdString());
// Disabled by default until CPU usage and reliability improves
if (UserSettings::instance()->qsettings()->value("enable_http3").toBool()) {
nhlog::net()->warn("Enabling http3 support. This is currently usually a worse "
"experience, so you are on your own.");
c->alt_svc_cache_path((QStandardPaths::writableLocation(QStandardPaths::CacheLocation) +
"/curl_alt_svc_cache.txt")
.toStdString());
}
return c;
}();
return client_.get();

View file

@ -32,16 +32,17 @@ MxcImageProvider::MxcImageProvider()
timer->setInterval(std::chrono::hours(1));
connect(timer, &QTimer::timeout, this, [] {
QThreadPool::globalInstance()->start([] {
nhlog::net()->debug("Running media purge");
QDir dir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) +
"/media_cache",
"",
QDir::SortFlags(QDir::Name | QDir::IgnoreCase),
QDir::Filter::Writable | QDir::Filter::NoDotAndDotDot | QDir::Filter::Files);
QDir::Filter::Writable | QDir::Filter::NoDotAndDotDot | QDir::Filter::Files |
QDir::Filter::Dirs);
auto files = dir.entryInfoList();
for (const auto &fileInfo : std::as_const(files)) {
auto handleFile = [](const QFileInfo &fileInfo) {
if (fileInfo.fileTime(QFile::FileTime::FileAccessTime)
.daysTo(QDateTime::currentDateTime()) > 30) {
.daysTo(QDateTime::currentDateTime()) > 14) {
if (QFile::remove(fileInfo.absoluteFilePath()))
nhlog::net()->debug("Deleted stale media '{}'",
fileInfo.absoluteFilePath().toStdString());
@ -49,6 +50,24 @@ MxcImageProvider::MxcImageProvider()
nhlog::net()->warn("Failed to delete stale media '{}'",
fileInfo.absoluteFilePath().toStdString());
}
};
auto files = dir.entryInfoList();
for (const auto &fileInfo : std::as_const(files)) {
if (fileInfo.isDir()) {
// handle one level of legacy directories
auto nestedDir = QDir(fileInfo.absoluteFilePath(),
"",
QDir::SortFlags(QDir::Name | QDir::IgnoreCase),
QDir::Filter::Writable | QDir::Filter::NoDotAndDotDot |
QDir::Filter::Files)
.entryInfoList();
for (const auto &nestedFile : std::as_const(nestedDir)) {
handleFile(nestedFile);
}
} else {
handleFile(fileInfo);
}
}
});
});

View file

@ -11,6 +11,7 @@
#include <QWindow>
#include "TrayIcon.h"
#include "UserSettingsPage.h"
MsgCountComposedIcon::MsgCountComposedIcon(const QIcon &icon)
: QIconEngine()
@ -114,12 +115,30 @@ TrayIcon::TrayIcon(const QString &filename, QWindow *parent)
menu->addAction(viewAction_);
menu->addAction(quitAction_);
QString toolTip = QLatin1String("nheko");
QString profile = UserSettings::instance()->profile();
if (!profile.isEmpty())
toolTip.append(QStringLiteral(" | %1").arg(profile));
setToolTip(toolTip);
}
void
TrayIcon::setUnreadCount(int count)
{
qGuiApp->setBadgeNumber(count);
if (count != previousCount) {
QString toolTip = QLatin1String("nheko");
QString profile = UserSettings::instance()->profile();
if (!profile.isEmpty())
toolTip.append(QStringLiteral(" | %1").arg(profile));
if (count != 0)
toolTip.append(tr("\n%n unread message(s)", "", count));
setToolTip(toolTip);
}
#if !defined(Q_OS_MACOS) && !defined(Q_OS_WIN)
if (count != previousCount) {
@ -131,13 +150,6 @@ TrayIcon::setUnreadCount(int count)
#else
(void)previousCount;
#endif
QString toolTip = QLatin1String("nheko");
if (count > 0) {
toolTip.append(tr("\n%n unread message(s)", "", count));
}
setToolTip(toolTip);
}
#include "moc_TrayIcon.cpp"

View file

@ -20,6 +20,8 @@
#include <QVideoFrame>
#include <QVideoSink>
#include <fmt/format.h>
#include <nlohmann/json.hpp>
#include <mtx/responses/common.hpp>
@ -578,8 +580,7 @@ InputBar::message(const QString &msg, MarkdownOverride useMarkdown, bool rainbow
}
}
text.body =
QStringLiteral("%1\n%2").arg(body, QString::fromStdString(text.body)).toStdString();
text.body = fmt::format("{}\n{}", body.toStdString(), text.body);
// NOTE(Nico): rich replies always need a formatted_body!
text.format = "org.matrix.custom.html";

View file

@ -37,8 +37,14 @@ static CacheEntry *
pullPresence(const QString &id)
{
auto p = cache::presence(id.toStdString());
auto c = new CacheEntry{
utils::replaceEmoji(QString::fromStdString(p.status_msg).toHtmlEscaped()), p.presence};
auto statusMsg = QString::fromStdString(p.status_msg);
if (statusMsg.size() > 255) {
statusMsg.truncate(255);
statusMsg.append(u'');
}
auto c = new CacheEntry{utils::replaceEmoji(std::move(statusMsg).toHtmlEscaped()), p.presence};
presences.insert(id, c);
return c;
}

View file

@ -238,9 +238,11 @@ TimelineFilter::filterAcceptsRow(int source_row, const QModelIndex &) const
if (auto s = sourceModel()) {
auto idx = s->index(source_row, 0);
if (!contentFilter.isEmpty() && !s->data(idx, TimelineModel::Body)
.toString()
.contains(contentFilter, Qt::CaseInsensitive)) {
if (
!contentFilter.isEmpty()
&& !s->data(idx, TimelineModel::Body).toString().contains(contentFilter, Qt::CaseInsensitive)
&& !s->data(idx, TimelineModel::UserName).toString().contains(contentFilter, Qt::CaseInsensitive)
) {
return false;
}

View file

@ -2073,10 +2073,12 @@ TimelineModel::cacheMedia(const QString &eventId,
const auto url = mxcUrl.toStdString();
const auto name = QString(mxcUrl).remove(QStringLiteral("mxc://"));
QFileInfo filename(
QStringLiteral("%1/media_cache/%2.%3")
.arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation), name, suffix));
if (QDir::cleanPath(name) != name) {
QFileInfo filename(QStringLiteral("%1/media_cache/%2.%3")
.arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation),
QString::fromUtf8(name.toUtf8().toBase64(
QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)),
suffix));
if (QDir::cleanPath(filename.filePath()) != filename.filePath()) {
nhlog::net()->warn("mxcUrl '{}' is not safe, not downloading file", url);
return;
}

View file

@ -53,10 +53,12 @@ MxcAnimatedImage::startDownload()
const auto url = mxcUrl.toStdString();
const auto name = QString(mxcUrl).remove(QStringLiteral("mxc://"));
QFileInfo filename(
QStringLiteral("%1/media_cache/media/%2.%3")
.arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation), name, suffix));
if (QDir::cleanPath(name) != name) {
QFileInfo filename(QStringLiteral("%1/media_cache/media/%2.%3")
.arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation),
QString::fromUtf8(name.toUtf8().toBase64(
QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)),
suffix));
if (QDir::cleanPath(filename.filePath()) != filename.filePath()) {
nhlog::net()->warn("mxcUrl '{}' is not safe, not downloading file", url);
return;
}

View file

@ -96,10 +96,12 @@ MxcMediaProxy::startDownload(bool onlyCached)
const auto url = mxcUrl.toStdString();
const auto name = QString(mxcUrl).remove(QStringLiteral("mxc://"));
QFileInfo filename(
QStringLiteral("%1/media_cache/media/%2.%3")
.arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation), name, suffix));
if (QDir::cleanPath(name) != name) {
QFileInfo filename(QStringLiteral("%1/media_cache/media/%2.%3")
.arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation),
QString::fromUtf8(name.toUtf8().toBase64(
QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)),
suffix));
if (QDir::cleanPath(filename.filePath()) != filename.filePath()) {
nhlog::net()->warn("mxcUrl '{}' is not safe, not downloading file", url);
return;
}