Compare commits

..

1 commit

Author SHA1 Message Date
mauke
4c33d10e19
Merge d299f7af5c into 048af42780 2024-10-03 14:55:02 -04: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 - 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 libssl-dev libqt5multimedia5-plugins libqt5multimediagsttools5 libqt5multimediaquick5 libqt5svg5-dev
qtmultimedia5-dev qtquickcontrols2-5-dev qttools5-dev qttools5-dev-tools qtdeclarative5-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 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 # need recommended deps for wget
- apt-get -y install wget - apt-get -y install wget
@ -282,7 +282,7 @@ build-macos-as:
- pipx ensurepath - pipx ensurepath
- . ~/.zshrc - . ~/.zshrc
- mkdir $HOME/Qt - 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: script:
- export QTPATH=($HOME/qt/6.*/macos/bin) - export QTPATH=($HOME/qt/6.*/macos/bin)
- export PATH="$QTPATH:${PATH}" - 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 - 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 libssl-dev libqt5multimedia5-plugins libqt5multimediagsttools5 libqt5multimediaquick5 libqt5svg5-dev
qtmultimedia5-dev qtquickcontrols2-5-dev qttools5-dev qttools5-dev-tools qtdeclarative5-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 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 # 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):* *Build requirements + qml modules needed at runtime (you may not need all of them, but the following seem to work according to reports):*
```bash ```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 \ 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 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. 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 // SPDX-License-Identifier: GPL-3.0-or-later
import "./components" import "./components"
import Qt.labs.platform 1.1 as Platform
import QtQml import QtQml
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
@ -100,18 +101,17 @@ Page {
] ]
onClicked: Communities.setCurrentTagId(model.id) 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 { Item {
anchors.fill: parent anchors.fill: parent
TapHandler { TapHandler {
id: rth
acceptedButtons: Qt.RightButton acceptedButtons: Qt.RightButton
acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad
gesturePolicy: TapHandler.ReleaseWithinBounds gesturePolicy: TapHandler.ReleaseWithinBounds
onSingleTapped: communityContextMenu.show(rth, model.id, model.hidden, model.muted) onSingleTapped: communityContextMenu.show(model.id, model.hidden, model.muted)
} }
} }
RowLayout { RowLayout {
@ -195,28 +195,28 @@ Page {
} }
} }
Menu { Platform.Menu {
id: communityContextMenu id: communityContextMenu
property bool hidden property bool hidden
property bool muted property bool muted
property string tagId property string tagId
function show(parent, id_, hidden_, muted_) { function show(id_, hidden_, muted_) {
tagId = id_; tagId = id_;
hidden = hidden_; hidden = hidden_;
muted = muted_; muted = muted_;
popup(parent); open();
} }
MenuItem { Platform.MenuItem {
checkable: true checkable: true
checked: communityContextMenu.muted checked: communityContextMenu.muted
text: qsTr("Do not show notification counts for this community or tag.") text: qsTr("Do not show notification counts for this community or tag.")
onTriggered: Communities.toggleTagMute(communityContextMenu.tagId) onTriggered: Communities.toggleTagMute(communityContextMenu.tagId)
} }
MenuItem { Platform.MenuItem {
checkable: true checkable: true
checked: communityContextMenu.hidden checked: communityContextMenu.hidden
text: qsTr("Hide rooms with this tag or from this community by default.") text: qsTr("Hide rooms with this tag or from this community by default.")

View file

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

View file

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

View file

@ -5,10 +5,10 @@
import "./dialogs" import "./dialogs"
import "./pages" import "./pages"
import "./ui" import "./ui"
import Qt.labs.platform 1.1 as Platform
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Window import QtQuick.Window
import QtQuick.Dialogs
import im.nheko import im.nheko
Pane { Pane {
@ -342,13 +342,15 @@ Pane {
return UIA.submit3pidToken(t); return UIA.submit3pidToken(t);
} }
} }
MessageDialog { Platform.MessageDialog {
id: uiaConfirmationLinkDialog id: uiaConfirmationLinkDialog
buttons: MessageDialog.Ok buttons: Platform.MessageDialog.Ok
text: qsTr("Wait for the confirmation link to arrive, then continue.") 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 { Connections {
function onConfirm3pidToken() { function onConfirm3pidToken() {

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

@ -9,6 +9,12 @@
#include <QDateTime> #include <QDateTime>
#include <QString> #include <QString>
#if __has_include(<lmdbxx/lmdb++.h>)
#include <lmdbxx/lmdb++.h>
#else
#include <lmdb++.h>
#endif
#include <mtx/events/collections.hpp> #include <mtx/events/collections.hpp>
#include <mtx/responses/notifications.hpp> #include <mtx/responses/notifications.hpp>
#include <mtx/responses/sync.hpp> #include <mtx/responses/sync.hpp>
@ -23,20 +29,12 @@ struct Messages;
struct StateEvents; struct StateEvents;
} }
namespace lmdb {
class txn;
class dbi;
}
struct CacheDb;
class Cache final : public QObject class Cache final : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
Cache(const QString &userId, QObject *parent = nullptr); Cache(const QString &userId, QObject *parent = nullptr);
~Cache() noexcept;
std::string displayName(const std::string &room_id, const std::string &user_id); std::string displayName(const std::string &room_id, const std::string &user_id);
QString displayName(const QString &room_id, const QString &user_id); QString displayName(const QString &room_id, const QString &user_id);
@ -99,11 +97,19 @@ public:
//! Get a specific state event //! Get a specific state event
template<typename T> template<typename T>
std::optional<mtx::events::StateEvent<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> template<typename T>
std::vector<mtx::events::StateEvent<T>> std::vector<mtx::events::StateEvent<T>>
getStateEventsWithType(const std::string &room_id, 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 //! retrieve a specific event from account data
//! pass empty room_id for global 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>>, std::is_same_v<std::remove_cv_t<std::remove_reference_t<T>>,
mtx::events::StateEvent<decltype(std::declval<T>().content)>>; 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: signals:
void newReadReceipts(const QString &room_id, const std::vector<QString> &event_ids); void newReadReceipts(const QString &room_id, const std::vector<QString> &event_ids);
void roomReadStatus(const std::map<QString, bool> &status); void roomReadStatus(const std::map<QString, bool> &status);
@ -381,44 +401,107 @@ private:
//! Sends signals for the rooms that are removed. //! Sends signals for the rooms that are removed.
void 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, void updateSpaces(lmdb::txn &txn,
const std::set<std::string> &spaces_with_updates, const std::set<std::string> &spaces_with_updates,
std::set<std::string> rooms_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 // 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); std::optional<VerificationCache> verificationCache(const std::string &user_id, lmdb::txn &txn);
VerificationStatus verificationStatus_(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); 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 localUserId_;
QString cacheDirectory_; QString cacheDirectory_;
@ -434,8 +538,6 @@ private:
VerificationStorage verification_storage; VerificationStorage verification_storage;
bool databaseReady_ = false; bool databaseReady_ = false;
std::unique_ptr<CacheDb> db;
}; };
namespace cache { namespace cache {
@ -444,12 +546,11 @@ client();
} }
#define NHEKO_CACHE_GET_STATE_EVENT_FORWARD(Content) \ #define NHEKO_CACHE_GET_STATE_EVENT_FORWARD(Content) \
extern template std::optional<mtx::events::StateEvent<Content>> Cache::getStateEvent<Content>( \ extern template std::optional<mtx::events::StateEvent<Content>> Cache::getStateEvent( \
const std::string &room_id, std::string_view state_key); \ lmdb::txn &txn, const std::string &room_id, std::string_view state_key); \
\ \
extern template std::vector<mtx::events::StateEvent<Content>> \ extern template std::vector<mtx::events::StateEvent<Content>> Cache::getStateEventsWithType( \
Cache::getStateEventsWithType<Content>(const std::string &room_id, \ lmdb::txn &txn, const std::string &room_id, mtx::events::EventType type);
mtx::events::EventType type);
NHEKO_CACHE_GET_STATE_EVENT_FORWARD(mtx::events::state::Aliases) NHEKO_CACHE_GET_STATE_EVENT_FORWARD(mtx::events::state::Aliases)
NHEKO_CACHE_GET_STATE_EVENT_FORWARD(mtx::events::state::Avatar) NHEKO_CACHE_GET_STATE_EVENT_FORWARD(mtx::events::state::Avatar)

View file

@ -13,9 +13,6 @@
#include <mtx/responses.hpp> #include <mtx/responses.hpp>
#include "Logging.h"
#include "UserSettingsPage.h"
namespace http { namespace http {
mtx::http::Client * mtx::http::Client *
@ -23,15 +20,9 @@ client()
{ {
static auto client_ = [] { static auto client_ = [] {
auto c = std::make_shared<mtx::http::Client>(); auto c = std::make_shared<mtx::http::Client>();
c->alt_svc_cache_path((QStandardPaths::writableLocation(QStandardPaths::CacheLocation) +
// Disabled by default until CPU usage and reliability improves "/curl_alt_svc_cache.txt")
if (UserSettings::instance()->qsettings()->value("enable_http3").toBool()) { .toStdString());
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 c;
}(); }();
return client_.get(); return client_.get();

View file

@ -32,17 +32,16 @@ MxcImageProvider::MxcImageProvider()
timer->setInterval(std::chrono::hours(1)); timer->setInterval(std::chrono::hours(1));
connect(timer, &QTimer::timeout, this, [] { connect(timer, &QTimer::timeout, this, [] {
QThreadPool::globalInstance()->start([] { QThreadPool::globalInstance()->start([] {
nhlog::net()->debug("Running media purge");
QDir dir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QDir dir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) +
"/media_cache", "/media_cache",
"", "",
QDir::SortFlags(QDir::Name | QDir::IgnoreCase), 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 handleFile = [](const QFileInfo &fileInfo) { auto files = dir.entryInfoList();
for (const auto &fileInfo : std::as_const(files)) {
if (fileInfo.fileTime(QFile::FileTime::FileAccessTime) if (fileInfo.fileTime(QFile::FileTime::FileAccessTime)
.daysTo(QDateTime::currentDateTime()) > 14) { .daysTo(QDateTime::currentDateTime()) > 30) {
if (QFile::remove(fileInfo.absoluteFilePath())) if (QFile::remove(fileInfo.absoluteFilePath()))
nhlog::net()->debug("Deleted stale media '{}'", nhlog::net()->debug("Deleted stale media '{}'",
fileInfo.absoluteFilePath().toStdString()); fileInfo.absoluteFilePath().toStdString());
@ -50,24 +49,6 @@ MxcImageProvider::MxcImageProvider()
nhlog::net()->warn("Failed to delete stale media '{}'", nhlog::net()->warn("Failed to delete stale media '{}'",
fileInfo.absoluteFilePath().toStdString()); 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 <QWindow>
#include "TrayIcon.h" #include "TrayIcon.h"
#include "UserSettingsPage.h"
MsgCountComposedIcon::MsgCountComposedIcon(const QIcon &icon) MsgCountComposedIcon::MsgCountComposedIcon(const QIcon &icon)
: QIconEngine() : QIconEngine()
@ -115,30 +114,12 @@ TrayIcon::TrayIcon(const QString &filename, QWindow *parent)
menu->addAction(viewAction_); menu->addAction(viewAction_);
menu->addAction(quitAction_); menu->addAction(quitAction_);
QString toolTip = QLatin1String("nheko");
QString profile = UserSettings::instance()->profile();
if (!profile.isEmpty())
toolTip.append(QStringLiteral(" | %1").arg(profile));
setToolTip(toolTip);
} }
void void
TrayIcon::setUnreadCount(int count) TrayIcon::setUnreadCount(int count)
{ {
qGuiApp->setBadgeNumber(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 !defined(Q_OS_MACOS) && !defined(Q_OS_WIN)
if (count != previousCount) { if (count != previousCount) {
@ -150,6 +131,13 @@ TrayIcon::setUnreadCount(int count)
#else #else
(void)previousCount; (void)previousCount;
#endif #endif
QString toolTip = QLatin1String("nheko");
if (count > 0) {
toolTip.append(tr("\n%n unread message(s)", "", count));
}
setToolTip(toolTip);
} }
#include "moc_TrayIcon.cpp" #include "moc_TrayIcon.cpp"

View file

@ -20,8 +20,6 @@
#include <QVideoFrame> #include <QVideoFrame>
#include <QVideoSink> #include <QVideoSink>
#include <fmt/format.h>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <mtx/responses/common.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! // NOTE(Nico): rich replies always need a formatted_body!
text.format = "org.matrix.custom.html"; text.format = "org.matrix.custom.html";

View file

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

View file

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

View file

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

View file

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