From 4703a46eb92451e2a96165af0fd71d9a64a9b3a8 Mon Sep 17 00:00:00 2001 From: evgeny Date: Sun, 18 Oct 2026 02:15:16 +0200 Subject: [PATCH] Add per-send encryption toggle for group files and voice --- tools/chatgui/src/inputbar.cpp | 38 +++++++++++++++++++++++++++++++++- tools/chatgui/src/inputbar.h | 2 ++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/tools/chatgui/src/inputbar.cpp b/tools/chatgui/src/inputbar.cpp index 9974b579..1edd68ac 100644 --- a/tools/chatgui/src/inputbar.cpp +++ b/tools/chatgui/src/inputbar.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -132,6 +133,13 @@ InputBar::InputBar(EmojiPanel *emojiPanel, AudioRecorder *recorder, QWidget *par QCoreApplication::translate("ChatGui", "Attach"), this); connect(m_attachBtn, &QPushButton::clicked, this, &InputBar::onAttachClicked); layout->addWidget(wrapBottomBtn(m_attachBtn, 3), 0, Qt::AlignBottom); + m_encryptBtn = new QPushButton(QString::fromUtf8("🔒"), this); + m_encryptBtn->setObjectName("encryptAttachmentButton"); + m_encryptBtn->setCheckable(true); + m_encryptBtn->setFixedSize(32, 32); + m_encryptBtn->setToolTip(QCoreApplication::translate("ChatGui", "Encrypt attachment; approve each recipient's key request")); + m_encryptBtn->setAccessibleName(QCoreApplication::translate("ChatGui", "Encrypt attachment")); + layout->addWidget(wrapBottomBtn(m_encryptBtn, 3), 0, Qt::AlignBottom); m_input->setAcceptRichText(false); m_input->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); @@ -405,7 +413,29 @@ void InputBar::onSendClicked() { if (hasAttach) { const quint64 requestId = beginReplySend(reply, m_replyGeneration, m_channelIdForRecord, {}, m_pendingAttachSrc); - if (m_pendingAttachAsImage) { + const bool encrypted = !m_dmMode && m_encryptBtn->isChecked(); + if (encrypted) { + auto* request = (struct attachment_send_req*)u_calloc(1, sizeof(struct attachment_send_req)); + QByteArray source = m_pendingAttachSrc.toUtf8(), name = m_pendingAttachName.toUtf8(); + if (!request || source.size() >= (int)sizeof(request->source) || name.size() >= (int)sizeof(request->info.name)) { + GUI_ERROR("encrypted attachment: invalid path/name or allocation failed"); + u_free(request); submissionResult(requestId, false); return; + } + request->encrypted = 1; + request->info.kind = m_pendingAttachAsVideo ? ATTACHMENT_VIDEO : ATTACHMENT_FILE; + request->transcode = m_pendingAttachAsVideo; + if (request->transcode) name = (QFileInfo(m_pendingAttachName).completeBaseName() + ".mp4").toUtf8(); + if (m_pendingAttachAsImage) { + const QByteArray type = QMimeDatabase().mimeTypeForFile(m_pendingAttachSrc, QMimeDatabase::MatchExtension).name().toUtf8(); + if (type.size() < (int)sizeof(request->content_type)) snprintf(request->content_type, sizeof(request->content_type), "%s", type.constData()); + } + snprintf(request->source, sizeof(request->source), "%s", source.constData()); + snprintf(request->info.name, sizeof(request->info.name), "%s", name.constData()); + if (reply.size() == 32) memcpy(request->reply_to, reply.constData(), 32); + request->request_id = requestId; + emit submitAttachment(m_channelIdForRecord, request); + m_encryptBtn->setChecked(false); + } else if (m_pendingAttachAsImage) { GUI_INFO("send: image=%s name=%s", qPrintable(m_pendingAttachSrc), qPrintable(m_pendingAttachName)); emit sendImageAttachment(m_channelIdForRecord, m_pendingAttachSrc, m_pendingAttachDest, m_pendingAttachName, reply, requestId); } else if (m_pendingAttachAsVideo) { @@ -451,11 +481,14 @@ void InputBar::setRecordingContext(const QString& mediaDirBase, const QString& c void InputBar::setDmMode(bool enabled) { m_dmMode = enabled; + m_encryptBtn->setVisible(!enabled); + if (enabled) m_encryptBtn->setChecked(false); m_pttBtn->setEnabled(true); m_attachBtn->setEnabled(true); } void InputBar::setMediaEnabled(bool enabled) { + if (m_encryptBtn) m_encryptBtn->setEnabled(enabled); if (m_pttBtn) m_pttBtn->setEnabled(enabled); if (m_attachBtn) m_attachBtn->setEnabled(enabled); if (!enabled && m_dropHint) m_dropHint->hide(); @@ -468,6 +501,7 @@ void InputBar::onPttPressed() { m_pttActive = m_recorder->isRecording(); if (!m_pttActive) { DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "voice recording did not start; composer remains idle"); return; } m_recordTarget = m_channelIdForRecord; + m_recordEncrypted = !m_dmMode && m_encryptBtn->isChecked(); m_recordReply = m_replyTo; m_recordReplyGeneration = m_replyGeneration; m_replyFailed = false; m_pttBtn->setStyleSheet(m_pttBtn->styleSheet() + "QPushButton { background: #e74c3c; border-radius: 4px; }"); @@ -490,10 +524,12 @@ void InputBar::onPttReleased() { auto* request = (struct attachment_send_req*)u_calloc(1, sizeof(struct attachment_send_req)); if (!request) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "InputBar: voice request allocation failed"); return; } request->info.kind = ATTACHMENT_VOICE; request->preset = VoiceEncoder::preset(); + request->encrypted = m_recordEncrypted; if (!m_recorder->takeRecording(request)) { u_free(request); return; } if (m_recordReply.size() == 32) memcpy(request->reply_to, m_recordReply.constData(), 32); request->request_id = beginReplySend(m_recordReply, m_recordReplyGeneration, m_recordTarget); emit submitAttachment(m_recordTarget, request); + if (m_recordTarget == m_channelIdForRecord) m_encryptBtn->setChecked(false); } void InputBar::onAttachClicked() { diff --git a/tools/chatgui/src/inputbar.h b/tools/chatgui/src/inputbar.h index 07595f3d..2eefaad9 100644 --- a/tools/chatgui/src/inputbar.h +++ b/tools/chatgui/src/inputbar.h @@ -83,6 +83,8 @@ private: QPushButton *m_emojiBtn; QPushButton *m_pttBtn; QPushButton *m_attachBtn = nullptr; + QPushButton *m_encryptBtn = nullptr; + bool m_recordEncrypted = false; EmojiPanel *m_emojiPanel; AudioRecorder *m_recorder; QWidget *m_recordOverlay = nullptr;