Compare commits

..

1 commit

Author SHA1 Message Date
Loren Burkholder
d509ef93d6
Merge 52346d972e into 048af42780 2024-09-26 03:03:14 +00:00
19 changed files with 593 additions and 708 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-qtmultimedia qml-module-qtquick-controls2 qml-module-qtquick-layouts qml-module-qt-labs-platform
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.8 clang_64 -m qtlocation qtimageformats qtmultimedia qtpositioning qtshadertools
- aqt install-qt --outputdir $HOME/qt mac desktop 6.6 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-qtmultimedia qml-module-qtquick-controls2 qml-module-qtquick-layouts qml-module-qt-labs-platform
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,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,-labs-platform,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,6 +3,7 @@
// 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
@ -100,18 +101,17 @@ Page {
]
onClicked: Communities.setCurrentTagId(model.id)
onPressAndHold: communityContextMenu.show(communityItem, model.id, model.hidden, model.muted)
onPressAndHold: communityContextMenu.show(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(rth, model.id, model.hidden, model.muted)
onSingleTapped: communityContextMenu.show(model.id, model.hidden, model.muted)
}
}
RowLayout {
@ -195,28 +195,28 @@ Page {
}
}
Menu {
Platform.Menu {
id: communityContextMenu
property bool hidden
property bool muted
property string tagId
function show(parent, id_, hidden_, muted_) {
function show(id_, hidden_, muted_) {
tagId = id_;
hidden = hidden_;
muted = muted_;
popup(parent);
open();
}
MenuItem {
Platform.MenuItem {
checkable: true
checked: communityContextMenu.muted
text: qsTr("Do not show notification counts for this community or tag.")
onTriggered: Communities.toggleTagMute(communityContextMenu.tagId)
}
MenuItem {
Platform.MenuItem {
checkable: true
checked: communityContextMenu.hidden
text: qsTr("Hide rooms with this tag or from this community by default.")

View file

@ -4,6 +4,7 @@
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
@ -392,7 +393,7 @@ Item {
}
}
}
Menu {
Platform.Menu {
id: messageContextMenuC
property string eventId
@ -420,9 +421,9 @@ Item {
else
link = "";
if (showAt_)
popup(showAt_);
open(showAt_);
else
popup();
open();
}
Component {
@ -447,7 +448,7 @@ Item {
ReportMessage {}
}
MenuItem {
Platform.MenuItem {
enabled: visible
text: qsTr("Go to &message")
visible: filteredTimeline.filterByContent
@ -457,21 +458,21 @@ Item {
room.showEvent(messageContextMenuC.eventId);
}
}
MenuItem {
Platform.MenuItem {
enabled: visible
text: qsTr("&Copy")
visible: messageContextMenuC.text
onTriggered: Clipboard.text = messageContextMenuC.text
}
MenuItem {
Platform.MenuItem {
enabled: visible
text: qsTr("Copy &link location")
visible: messageContextMenuC.link
onTriggered: Clipboard.text = messageContextMenuC.link
}
MenuItem {
Platform.MenuItem {
id: reactionOption
text: qsTr("Re&act")
@ -482,39 +483,39 @@ Item {
TimelineManager.focusMessageInput();
})
}
MenuItem {
Platform.MenuItem {
text: qsTr("Repl&y")
visible: room ? room.permissions.canSend(MtxEvent.TextMessage) : false
onTriggered: room.reply = (messageContextMenuC.eventId)
}
MenuItem {
Platform.MenuItem {
enabled: visible
text: qsTr("&Edit")
visible: messageContextMenuC.isEditable && (room ? room.permissions.canSend(MtxEvent.TextMessage) : false)
onTriggered: room.edit = (messageContextMenuC.eventId)
}
MenuItem {
Platform.MenuItem {
enabled: visible
text: qsTr("&Thread")
visible: (room ? room.permissions.canSend(MtxEvent.TextMessage) : false)
onTriggered: room.thread = (messageContextMenuC.threadId || messageContextMenuC.eventId)
}
MenuItem {
Platform.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)
}
MenuItem {
Platform.MenuItem {
text: qsTr("&Read receipts")
onTriggered: room.showReadReceipts(messageContextMenuC.eventId)
}
MenuItem {
Platform.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
@ -525,15 +526,15 @@ Item {
timelineRoot.destroyOnClose(forwardMess);
}
}
MenuItem {
Platform.MenuItem {
text: qsTr("&Mark as read")
}
MenuItem {
Platform.MenuItem {
text: qsTr("View raw message")
onTriggered: room.viewRawMessage(messageContextMenuC.eventId)
}
MenuItem {
Platform.MenuItem {
enabled: visible
text: qsTr("View decrypted raw message")
// TODO(Nico): Fix this still being iterated over, when using keyboard to select options
@ -541,7 +542,7 @@ Item {
onTriggered: room.viewDecryptedRawMessage(messageContextMenuC.eventId)
}
MenuItem {
Platform.MenuItem {
text: qsTr("Remo&ve message")
visible: (room ? room.permissions.canRedact() : false) || messageContextMenuC.isSender
@ -553,7 +554,7 @@ Item {
timelineRoot.destroyOnClose(dialog);
}
}
MenuItem {
Platform.MenuItem {
text: qsTr("Report message")
enabled: visible
onTriggered: function () {
@ -563,21 +564,21 @@ Item {
timelineRoot.destroyOnClose(dialog);
}
}
MenuItem {
Platform.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)
}
MenuItem {
Platform.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)
}
MenuItem {
Platform.MenuItem {
enabled: visible
text: qsTr("Copy link to eve&nt")
visible: messageContextMenuC.eventId
@ -591,7 +592,7 @@ Item {
ForwardCompleter {
}
}
Menu {
Platform.Menu {
id: replyContextMenuC
property string eventId
@ -605,21 +606,21 @@ Item {
open();
}
MenuItem {
Platform.MenuItem {
enabled: visible
text: qsTr("&Copy")
visible: replyContextMenuC.text
onTriggered: Clipboard.text = replyContextMenuC.text
}
MenuItem {
Platform.MenuItem {
enabled: visible
text: qsTr("Copy &link location")
visible: replyContextMenuC.link
onTriggered: Clipboard.text = replyContextMenuC.link
}
MenuItem {
Platform.MenuItem {
enabled: visible
text: qsTr("&Go to quoted message")
visible: true

View file

@ -5,6 +5,8 @@
import "./components"
import "./dialogs"
import "./ui"
import Qt.labs.platform 1.1 as Platform
import QtQml
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
@ -41,8 +43,6 @@ 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.popup(startChatButton)
onClicked: roomJoinCreateMenu.open(parent)
Menu {
Platform.Menu {
id: roomJoinCreateMenu
MenuItem {
Platform.MenuItem {
text: qsTr("Join a room")
onTriggered: Nheko.openJoinRoomDialog()
}
MenuItem {
Platform.MenuItem {
text: qsTr("Create a new room")
onTriggered: {
@ -72,7 +72,7 @@ Page {
timelineRoot.destroyOnClose(createRoom);
}
}
MenuItem {
Platform.MenuItem {
text: qsTr("Start a direct chat")
onTriggered: {
@ -81,7 +81,7 @@ Page {
timelineRoot.destroyOnClose(createDirect);
}
}
MenuItem {
Platform.MenuItem {
text: qsTr("Create a new community")
onTriggered: {
@ -255,72 +255,68 @@ Page {
Nheko.setStatusMessage(text);
}
}
Menu {
Platform.Menu {
id: userInfoMenu
MenuItem {
Platform.MenuItem {
text: qsTr("Profile settings")
onTriggered: userInfoPanel.openUserProfile()
}
MenuItem {
Platform.MenuItem {
text: qsTr("Set status message")
onTriggered: statusDialog.show()
}
MenuSeparator {
Platform.MenuSeparator {
}
ButtonGroup {
Platform.MenuItemGroup {
id: onlineStateGroup
}
MenuItem {
Platform.MenuItem {
text: qsTr("Automatic online status")
ButtonGroup.group: onlineStateGroup
group: onlineStateGroup
checkable: true
checked: Settings.presence == Settings.AutomaticPresence
onTriggered: if (checked) Settings.presence = Settings.AutomaticPresence
}
MenuItem {
Platform.MenuItem {
text: qsTr("Online")
ButtonGroup.group: onlineStateGroup
group: onlineStateGroup
checkable: true
checked: Settings.presence == Settings.Online
onTriggered: if (checked) Settings.presence = Settings.Online
}
MenuItem {
Platform.MenuItem {
text: qsTr("Unavailable")
ButtonGroup.group: onlineStateGroup
group: onlineStateGroup
checkable: true
checked: Settings.presence == Settings.Unavailable
onTriggered: if (checked) Settings.presence = Settings.Unavailable
}
MenuItem {
Platform.MenuItem {
text: qsTr("Offline")
ButtonGroup.group: onlineStateGroup
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.popup(userTapHandler)
onLongPressed: userInfoMenu.open()
onSingleTapped: userInfoPanel.openUserProfile()
}
TapHandler {
id: userTapHandler2
acceptedButtons: Qt.RightButton
gesturePolicy: TapHandler.ReleaseWithinBounds
margin: -Nheko.paddingSmall
onSingleTapped: userInfoMenu.popup(userTapHandler2)
onSingleTapped: userInfoMenu.open()
}
}
Rectangle {
@ -529,7 +525,7 @@ Page {
}
onPressAndHold: {
if (!isInvite)
roomContextMenu.show(roomItem, roomId, tags);
roomContextMenu.show(roomId, tags);
}
Ripple {
@ -542,15 +538,13 @@ 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(roomItemTh, roomId, tags);
roomContextMenu.show(roomId, tags);
}
}
}
@ -740,16 +734,16 @@ Page {
roomid: roomContextMenu.roomid
}
}
Menu {
Platform.Menu {
id: roomContextMenu
property string roomid
property var tags
function show(parent, roomid_, tags_) {
function show(roomid_, tags_) {
roomid = roomid_;
tags = tags_;
popup(parent);
open();
}
InputDialog {
@ -762,7 +756,7 @@ Page {
Rooms.toggleTag(roomContextMenu.roomid, "u." + text, true);
}
}
MenuItem {
Platform.MenuItem {
text: qsTr("Open separately")
onTriggered: {
@ -774,27 +768,27 @@ Page {
destroyOnClose(roomWindow);
}
}
MenuItem {
Platform.MenuItem {
text: qsTr("Mark as read")
onTriggered: Rooms.getRoomById(roomContextMenu.roomid).markRoomAsRead()
}
MenuItem {
Platform.MenuItem {
text: qsTr("Room settings")
onTriggered: TimelineManager.openRoomSettings(roomContextMenu.roomid)
}
MenuItem {
Platform.MenuItem {
text: qsTr("Leave room")
onTriggered: TimelineManager.openLeaveRoomDialog(roomContextMenu.roomid)
}
MenuItem {
Platform.MenuItem {
text: qsTr("Copy room link")
onTriggered: Rooms.copyLink(roomContextMenu.roomid)
}
Menu {
Platform.Menu {
id: tagsMenu
title: qsTr("Tag room as:")
@ -802,7 +796,7 @@ Page {
Instantiator {
model: Communities.tagsWithDefault
delegate: MenuItem {
delegate: Platform.MenuItem {
property string t: modelData
checkable: true
@ -826,7 +820,7 @@ Page {
onObjectAdded: (index, object) => tagsMenu.insertItem(index, object)
onObjectRemoved: (index, object) => tagsMenu.removeItem(object)
}
MenuItem {
Platform.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,13 +342,15 @@ Pane {
return UIA.submit3pidToken(t);
}
}
MessageDialog {
Platform.MessageDialog {
id: uiaConfirmationLinkDialog
buttons: MessageDialog.Ok
buttons: Platform.MessageDialog.Ok
text: qsTr("Wait for the confirmation link to arrive, then continue.")
onAccepted: UIA.continue3pidReceived()
// Broken on macos, see https://bugreports.qt.io/browse/QTBUG-102078
//onAccepted: UIA.continue3pidReceived()
onOkClicked: UIA.continue3pidReceived()
}
Connections {
function onConfirm3pidToken() {

View file

@ -2,6 +2,7 @@
//
// 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
@ -234,28 +235,28 @@ Pane {
image: ":/icons/icons/ui/options.svg"
visible: !!room
onClicked: roomOptionsMenu.popup(roomOptionsButton)
onClicked: roomOptionsMenu.open(roomOptionsButton)
Menu {
Platform.Menu {
id: roomOptionsMenu
MenuItem {
Platform.MenuItem {
text: qsTr("Invite users")
visible: room ? room.permissions.canInvite() : false
onTriggered: TimelineManager.openInviteUsers(roomId)
}
MenuItem {
Platform.MenuItem {
text: qsTr("Members")
onTriggered: TimelineManager.openRoomMembers(room)
}
MenuItem {
Platform.MenuItem {
text: qsTr("Leave room")
onTriggered: TimelineManager.openLeaveRoomDialog(roomId)
}
MenuItem {
Platform.MenuItem {
text: qsTr("Settings")
onTriggered: TimelineManager.openRoomSettings(roomId)

View file

@ -2,11 +2,11 @@
//
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick
import QtQuick.Controls
import im.nheko
import QtQuick 2.15
import Qt.labs.platform 1.1 as Platform
import im.nheko 1.0
Menu {
Platform.Menu {
id: spacesMenu
property string roomid
@ -19,61 +19,56 @@ Menu {
onAboutToShow: loadChildren = true
//onAboutToHide: loadChildren = false
ButtonGroup {
Platform.MenuItemGroup {
id: modificationGroup
//visible: position != -1
visible: position != -1
}
MenuItem {
Platform.MenuItem {
text: qsTr("Official community for this room")
ButtonGroup.group: modificationGroup
visible: position != -1
group: modificationGroup
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)
}
MenuItem {
Platform.MenuItem {
text: qsTr("Affiliated community for this room")
ButtonGroup.group: modificationGroup
visible: position != -1
group: modificationGroup
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)
}
MenuItem {
Platform.MenuItem {
text: qsTr("Listed only for community members")
ButtonGroup.group: modificationGroup
visible: position != -1
group: modificationGroup
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)
}
MenuItem {
Platform.MenuItem {
text: qsTr("Listed only for room members")
ButtonGroup.group: modificationGroup
visible: position != -1
group: modificationGroup
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)
}
MenuItem {
Platform.MenuItem {
text: qsTr("Not related")
ButtonGroup.group: modificationGroup
visible: position != -1
group: modificationGroup
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)
}
MenuSeparator {
//text: qsTr("Subcommunities")
ButtonGroup.group: modificationGroup
visible: position != -1 && inst.model != undefined
Platform.MenuSeparator {
text: qsTr("Subcommunities")
group: modificationGroup
visible: modificationGroup.visible && 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,23 +580,26 @@ ApplicationWindow {
Layout.alignment: Qt.AlignRight
}
MessageDialog {
Platform.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
onAccepted: {
// Broken on macos, see https://bugreports.qt.io/browse/QTBUG-102078
//onAccepted: {
onOkClicked: {
if (roomSettings.isEncryptionEnabled)
return ;
roomSettings.enableEncryption();
}
onRejected: {
//onRejected: {
onCancelClicked: {
encryptionToggle.checked = false;
}
buttons: MessageDialog.Ok | MessageDialog.Cancel
buttons: Platform.MessageDialog.Ok | Platform.MessageDialog.Cancel
}
Label {

File diff suppressed because it is too large Load diff

View file

@ -9,6 +9,12 @@
#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>
@ -23,20 +29,12 @@ 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);
@ -99,11 +97,19 @@ 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 = "");
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);
}
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>);
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);
}
//! retrieve a specific event from account data
//! pass empty room_id for global account data
@ -298,6 +304,20 @@ 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);
@ -381,44 +401,107 @@ private:
//! Sends signals for the rooms that are removed.
void
removeLeftRooms(lmdb::txn &txn, const std::map<std::string, mtx::responses::LeftRoom> &rooms);
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);
}
}
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);
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 getEventOrderDb(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);
}
// inverse of EventOrderDb
lmdb::dbi getEventToOrderDb(lmdb::txn &txn, const std::string &room_id);
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 getMessageToOrderDb(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 getOrderToMessageDb(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 getPendingMessagesDb(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 getRelationsDb(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 getInviteStatesDb(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 getInviteMembersDb(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 getStatesDb(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 getStatesKeyDb(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 getAccountDataDb(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 getMembersDb(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 getUserKeysDb(lmdb::txn &txn);
lmdb::dbi getUserKeysDb(lmdb::txn &txn) { return lmdb::dbi::open(txn, "user_key", MDB_CREATE); }
lmdb::dbi getVerificationDb(lmdb::txn &txn);
lmdb::dbi getVerificationDb(lmdb::txn &txn)
{
return lmdb::dbi::open(txn, "verified", MDB_CREATE);
}
QString getDisplayName(const mtx::events::StateEvent<mtx::events::state::Member> &event);
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);
}
std::optional<VerificationCache> verificationCache(const std::string &user_id, lmdb::txn &txn);
VerificationStatus verificationStatus_(const std::string &user_id, lmdb::txn &txn);
@ -426,6 +509,27 @@ 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_;
@ -434,8 +538,6 @@ private:
VerificationStorage verification_storage;
bool databaseReady_ = false;
std::unique_ptr<CacheDb> db;
};
namespace cache {
@ -444,12 +546,11 @@ client();
}
#define NHEKO_CACHE_GET_STATE_EVENT_FORWARD(Content) \
extern template std::optional<mtx::events::StateEvent<Content>> Cache::getStateEvent<Content>( \
const std::string &room_id, std::string_view state_key); \
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::vector<mtx::events::StateEvent<Content>> \
Cache::getStateEventsWithType<Content>(const std::string &room_id, \
mtx::events::EventType type);
extern template std::vector<mtx::events::StateEvent<Content>> Cache::getStateEventsWithType( \
lmdb::txn &txn, 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,9 +13,6 @@
#include <mtx/responses.hpp>
#include "Logging.h"
#include "UserSettingsPage.h"
namespace http {
mtx::http::Client *
@ -23,15 +20,9 @@ client()
{
static auto client_ = [] {
auto c = std::make_shared<mtx::http::Client>();
// 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());
}
c->alt_svc_cache_path((QStandardPaths::writableLocation(QStandardPaths::CacheLocation) +
"/curl_alt_svc_cache.txt")
.toStdString());
return c;
}();
return client_.get();

View file

@ -32,17 +32,16 @@ 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::Dirs);
QDir::Filter::Writable | QDir::Filter::NoDotAndDotDot | QDir::Filter::Files);
auto handleFile = [](const QFileInfo &fileInfo) {
auto files = dir.entryInfoList();
for (const auto &fileInfo : std::as_const(files)) {
if (fileInfo.fileTime(QFile::FileTime::FileAccessTime)
.daysTo(QDateTime::currentDateTime()) > 14) {
.daysTo(QDateTime::currentDateTime()) > 30) {
if (QFile::remove(fileInfo.absoluteFilePath()))
nhlog::net()->debug("Deleted stale media '{}'",
fileInfo.absoluteFilePath().toStdString());
@ -50,24 +49,6 @@ 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,7 +11,6 @@
#include <QWindow>
#include "TrayIcon.h"
#include "UserSettingsPage.h"
MsgCountComposedIcon::MsgCountComposedIcon(const QIcon &icon)
: QIconEngine()
@ -115,30 +114,12 @@ 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) {
@ -150,6 +131,13 @@ 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,8 +20,6 @@
#include <QVideoFrame>
#include <QVideoSink>
#include <fmt/format.h>
#include <nlohmann/json.hpp>
#include <mtx/responses/common.hpp>
@ -580,7 +578,8 @@ InputBar::message(const QString &msg, MarkdownOverride useMarkdown, bool rainbow
}
}
text.body = fmt::format("{}\n{}", body.toStdString(), text.body);
text.body =
QStringLiteral("%1\n%2").arg(body, QString::fromStdString(text.body)).toStdString();
// NOTE(Nico): rich replies always need a formatted_body!
text.format = "org.matrix.custom.html";

View file

@ -37,14 +37,8 @@ static CacheEntry *
pullPresence(const QString &id)
{
auto p = cache::presence(id.toStdString());
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};
auto c = new CacheEntry{
utils::replaceEmoji(QString::fromStdString(p.status_msg).toHtmlEscaped()), p.presence};
presences.insert(id, c);
return c;
}

View file

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

View file

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

View file

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