Compare commits

...

18 commits

Author SHA1 Message Date
Marcus
5514bb6cf7
Merge 16cce08b43 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
Nicolas Werner
048af42780
Remove manual menu bar mangling on macos
This is causing probably more issues nowadays than it fixes. Qt should
be adding those menus for us now, so let's remove it and see, what
breaks!
2024-09-25 12:11:01 +02:00
DeepBlueV7.X
cc4ace3c12
Merge pull request #1795 from q234rty/remove-virtual-keyboard-check
Always allow sending messages by enter even with IMEs
2024-09-24 18:30:52 +00:00
DeepBlueV7.X
3ef92487fd
Merge pull request #1816 from p12tic/readme-debian-requirements
Do not fetch Qt from Debian experimental when building for Qt6
2024-09-24 18:29:45 +00:00
Povilas Kanapickas
037d7e6b35 Do not fetch Qt from Debian experimental when building for Qt6
Qt version in Debian Trixie is 6.6 as of September 2024. Therefore there
is no need to use experimental repositories.
2024-09-24 20:06:54 +03:00
q234rty
87cb63e1fd
Always allow sending messages by enter even with IMEs
Qt.inputMethod.visible is true in kwin_wayland when maliit is enabled and activated,
however kwin does not actually show maliit w/o touch input [1].

Moreover, having send by enter might still be desired even for virtual keyboards.

Remove the check for virtual keyboards as suggested by @deepbluev7,
people could still use Settings.invertEnterKey for newlines.

[1]: https://invent.kde.org/plasma/kwin/-/blob/v6.1.4/src/inputmethod.cpp?ref_type=tags#L185
2024-08-14 16:52:50 +08:00
Marcus Hoffmann
16cce08b43 WelcomePage: add vertical ScrollView around content
This solves the problem of the window being sized smaller than it's
content and part of it being out of view.
2024-05-15 00:33:03 +02:00
25 changed files with 795 additions and 721 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

@ -648,9 +648,9 @@ qt_add_resources(QRC resources/res.qrc)
if(APPLE)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -framework Foundation -framework Cocoa -framework UserNotifications")
set(SRC_FILES ${SRC_FILES} src/notifications/NotificationManagerProxy.h src/notifications/MacNotificationDelegate.h src/notifications/MacNotificationDelegate.mm src/notifications/ManagerMac.mm src/notifications/ManagerMac.cpp src/emoji/MacHelper.mm src/emoji/MacHelper.h)
set(SRC_FILES ${SRC_FILES} src/notifications/NotificationManagerProxy.h src/notifications/MacNotificationDelegate.h src/notifications/MacNotificationDelegate.mm src/notifications/ManagerMac.mm src/notifications/ManagerMac.cpp)
if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.16.0")
set_source_files_properties( src/notifications/NotificationManagerProxy.h src/notifications/MacNotificationDelegate.h src/notifications/MacNotificationDelegate.mm src/notifications/ManagerMac.mm src/emoji/MacHelper.mm src/emoji/MacHelper.h PROPERTIES SKIP_PRECOMPILE_HEADERS ON)
set_source_files_properties( src/notifications/NotificationManagerProxy.h src/notifications/MacNotificationDelegate.h src/notifications/MacNotificationDelegate.mm src/notifications/ManagerMac.mm PROPERTIES SKIP_PRECOMPILE_HEADERS ON)
endif()
elseif(WIN32)
file(DOWNLOAD

View file

@ -348,12 +348,11 @@ sudo pacman -S qt6-base \
```
#### Debian 13 [Testing/Sid] (Nheko QT6 Version)
*As of February 2024, Nheko from git master requires QT 6.5 whereas Trixie has 6.4.2, so you must enable Debian's [Experimental Repository](https://wiki.debian.org/DebianExperimental) to install newer QT6. This may not be necessary in the future and the `-t experimental` can be removed for the second set of build requirements.*
```bash
# Install build requirements
sudo apt install -y cmake asciidoc-base libevent-dev libspdlog-dev libre2-dev liblmdb++-dev libcurl4-openssl-dev libssl-dev libolm-dev libcmark-dev nlohmann-json3-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libgstreamer-plugins-bad1.0-dev libkdsingleapplication-qt6-dev
sudo apt install -y -t experimental qt6-base-dev qt6-tools-dev qt6-svg-dev qt6-multimedia-dev qt6-declarative-dev qtkeychain-qt6-dev qt6-base-private-dev qt6-declarative-private-dev
sudo apt install -y qt6-base-dev qt6-tools-dev qt6-svg-dev qt6-multimedia-dev qt6-declarative-dev qtkeychain-qt6-dev qt6-base-private-dev qt6-declarative-private-dev
# Clone nheko repository from github
sudo apt install -y git
git clone https://github.com/Nheko-Reborn/nheko && cd nheko
@ -366,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

@ -173,7 +173,7 @@ Rectangle {
} else if (event.matches(StandardKey.InsertLineSeparator)) {
if (popup.opened)
popup.close();
if (Settings.invertEnterKey && (!Qt.inputMethod.visible || Qt.platform.os === "windows")) {
if (Settings.invertEnterKey) {
room.input.send();
event.accepted = true;
}
@ -195,7 +195,7 @@ Rectangle {
return;
}
}
if (!Settings.invertEnterKey && (!Qt.inputMethod.visible || Qt.platform.os === "windows")) {
if (!Settings.invertEnterKey) {
room.input.send();
event.accepted = true;
}

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 {

View file

@ -10,93 +10,98 @@ import im.nheko 1.0
import "../components/"
import ".."
ColumnLayout {
Item {
Layout.fillHeight: true
}
Image {
Layout.alignment: Qt.AlignHCenter
source: "qrc:/logos/splash.png"
Layout.preferredHeight: 256
Layout.preferredWidth: 256
}
Label {
Layout.margins: Nheko.paddingLarge
Layout.bottomMargin: 0
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
text: qsTr("Welcome to nheko! The desktop client for the Matrix protocol.")
color: palette.text
font.pointSize: fontMetrics.font.pointSize*2
wrapMode: Text.Wrap
horizontalAlignment: Text.AlignHCenter
}
Label {
Layout.margins: Nheko.paddingLarge
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
text: qsTr("Enjoy your stay!")
color: palette.text
font.pointSize: fontMetrics.font.pointSize*1.5
wrapMode: Text.Wrap
horizontalAlignment: Text.AlignHCenter
}
RowLayout {
Item {
Layout.fillWidth: true
}
FlatButton {
Layout.margins: Nheko.paddingLarge
Layout.alignment: Qt.AlignHCenter
text: qsTr("REGISTER")
onClicked: {
mainWindow.push(registerPage);
}
}
FlatButton {
Layout.margins: Nheko.paddingLarge
Layout.alignment: Qt.AlignHCenter
text: qsTr("LOGIN")
onClicked: {
mainWindow.push(loginPage);
}
}
ScrollView {
contentWidth: availableWidth
ColumnLayout {
width: parent.width
Item {
Layout.fillWidth: true
Layout.fillHeight: true
}
}
RowLayout {
Layout.alignment: Qt.AlignHCenter
Layout.margins: Nheko.paddingLarge
ToggleButton {
Layout.margins: Nheko.paddingLarge
Layout.alignment: Qt.AlignRight
checked: Settings.reducedMotion
onCheckedChanged: Settings.reducedMotion = checked
Image {
Layout.alignment: Qt.AlignHCenter
source: "qrc:/logos/splash.png"
Layout.preferredHeight: 256
Layout.preferredWidth: 256
}
Label {
Layout.alignment: Qt.AlignLeft
Layout.margins: Nheko.paddingLarge
text: qsTr("Reduce animations")
Layout.bottomMargin: 0
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
text: qsTr("Welcome to nheko! The desktop client for the Matrix protocol.")
color: palette.text
font.pointSize: fontMetrics.font.pointSize * 2
wrapMode: Text.Wrap
horizontalAlignment: Text.AlignHCenter
}
Label {
Layout.margins: Nheko.paddingLarge
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
text: qsTr("Enjoy your stay!")
color: palette.text
font.pointSize: fontMetrics.font.pointSize * 1.5
wrapMode: Text.Wrap
horizontalAlignment: Text.AlignHCenter
}
HoverHandler {
id: hovered
RowLayout {
Item {
Layout.fillWidth: true
}
ToolTip.visible: hovered.hovered
ToolTip.text: qsTr("Nheko uses animations in several places to make stuff pretty. This allows you to turn those off if they make you feel unwell.")
ToolTip.delay: Nheko.tooltipDelay
FlatButton {
Layout.margins: Nheko.paddingLarge
Layout.alignment: Qt.AlignHCenter
text: qsTr("REGISTER")
onClicked: {
mainWindow.push(registerPage);
}
}
FlatButton {
Layout.margins: Nheko.paddingLarge
Layout.alignment: Qt.AlignHCenter
text: qsTr("LOGIN")
onClicked: {
mainWindow.push(loginPage);
}
}
Item {
Layout.fillWidth: true
}
}
RowLayout {
Layout.alignment: Qt.AlignHCenter
Layout.margins: Nheko.paddingLarge
ToggleButton {
Layout.margins: Nheko.paddingLarge
Layout.alignment: Qt.AlignRight
checked: Settings.reducedMotion
onCheckedChanged: Settings.reducedMotion = checked
}
Label {
Layout.alignment: Qt.AlignLeft
Layout.margins: Nheko.paddingLarge
text: qsTr("Reduce animations")
color: palette.text
HoverHandler {
id: hovered
}
ToolTip.visible: hovered.hovered
ToolTip.text: qsTr("Nheko uses animations in several places to make stuff pretty. This allows you to turn those off if they make you feel unwell.")
ToolTip.delay: Nheko.tooltipDelay
}
}
Item {
Layout.fillHeight: true
}
}
Item {
Layout.fillHeight: true
}
}

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

@ -1,14 +0,0 @@
// SPDX-FileCopyrightText: Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <QMenuBar>
class MacHelper
{
public:
static void showEmojiWindow();
static void initializeMenus();
};

View file

@ -1,26 +0,0 @@
#include "MacHelper.h"
#include <Cocoa/Cocoa.h>
#include <Foundation/Foundation.h>
#include <Foundation/NSString.h>
#include <QCoreApplication>
void
MacHelper::showEmojiWindow()
{
NSApplication *theNSApplication = [NSApplication sharedApplication];
[theNSApplication orderFrontCharacterPalette:nil];
}
void
MacHelper::initializeMenus()
{
NSApplication *theNSApplication = [NSApplication sharedApplication];
NSArray<NSMenuItem *> *menus = [theNSApplication mainMenu].itemArray;
NSUInteger size = menus.count;
for (NSUInteger i = 0; i < size; i++) {
NSMenuItem *item = [menus objectAtIndex:i];
[item setTitle:@"Edit"];
}
}

View file

@ -37,7 +37,6 @@
#include "config/nheko.h"
#if defined(Q_OS_MACOS)
#include "emoji/MacHelper.h"
#include "notifications/Manager.h"
#endif
@ -466,10 +465,6 @@ main(int argc, char *argv[])
QStringLiteral("matrix"), ChatPage::instance(), "handleMatrixUri");
#if defined(Q_OS_MACOS)
// Temporary solution for the emoji picker until
// nheko has a proper menu bar with more functionality.
MacHelper::initializeMenus();
// Need to set up notification delegate so users can respond to messages from within the
// notification itself.
NotificationsManager::attachToMacNotifCenter();

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

@ -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;
}