Skip to content

Commit c4d61d2

Browse files
daredooleclaude
andauthored
feat(ui,clipboard): consolidate UI/UX and implement HTML/Image sync (#8)
* feat(ui,clipboard): consolidate UI/UX and implement HTML/Image sync - Replaced fragmented Zenity dialogs with professional Python/GTK3 forms. - Decluttered Tray menu with new 'Advanced' submenu and improved status labels. - Implemented full HTML and Image clipboard synchronization via MIME-aware backends. - Updated README.md with high-fidelity setup instructions and feature details. - Refactored core network and runtime layers to support structured clipboard payloads. * fix(net,protocol): zero-magic handshake, Heartbeat_v2, label constants, trailing whitespace - Accept clipboard handshake packets with zero magic (PowerToys MWB compat) - Add desId param to exchangeClipboardHandshake; use kBroadcastMachineId fallback - Force magic=0 on RequestRemoteClipboard secondary socket - Add Heartbeat_v2 (type 80) to PackageType and PackageTypeName - Rename clipboard socket labels: "text"→"TXT", "image"→"IMG" - Add debug logging throughout handshake flow - Strip trailing whitespace (CI static check) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(clipboard): fix image sync protocol types and trimming - Corrected PackageType values for clipboard (9->69, etc) to match MWB protocol. - Implemented trailing null-byte trimming for received image payloads. - Increased inline and socket clipboard max sizes to 16MB. - Use zero magic for clipboard socket handshake per observed PowerToys behavior. - Switched wl-clipboard-klipper to wl-paste --watch for reliable detection. * test(clipboard): use magic=0 in PushClipboardText to match server expectation Clipboard socket incoming handler uses magic=0 (PowerToys compat). Test was still using crypto.Get24BitHash() for the response packet, causing receivePacketOnSocket to reject it and crash the test with an unhandled std::runtime_error. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(tray,net): stabilize UI, fix reconnect loop, and revamp README * fix(ci): remove trailing whitespace and cleanup debug logs --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 1f2bbdf commit c4d61d2

15 files changed

Lines changed: 946 additions & 1031 deletions

README.md

Lines changed: 63 additions & 566 deletions
Large diffs are not rendered by default.

mwb-desktop-ui.sh

Lines changed: 109 additions & 247 deletions
Large diffs are not rendered by default.

src/AppConfig.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ struct AppConfig {
1717
bool clipboardEnabled{true};
1818
bool clipboardSendEnabled{true};
1919
bool clipboardForcePoll{false};
20-
int clipboardPollMs{1000};
20+
int clipboardPollMs{5000};
2121
bool autoConnectEnabled{true};
2222
int reconnectInitialBackoffMs{1000};
2323
int reconnectMaxBackoffMs{30000};

src/ClientRuntime.cpp

Lines changed: 40 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -307,24 +307,34 @@ int ClientRuntime::Run() {
307307
if (m_clipboard) {
308308
m_clipboardBackendName = m_clipboard->BackendName();
309309
std::cout << "[INFO] Clipboard backend: " << m_clipboardBackendName << std::endl;
310-
m_lastClipboardText = m_clipboard->GetText();
311-
if (m_lastClipboardText.has_value() && !m_lastClipboardText->empty()) {
312-
m_network->PrimeLocalClipboardText(*m_lastClipboardText);
310+
m_lastClipboardPayload = m_clipboard->GetPayload();
311+
if (m_lastClipboardPayload.has_value()) {
312+
m_network->PrimeLocalClipboardPayload(*m_lastClipboardPayload);
313313
}
314314
m_network->SetClipboardProvider(m_clipboard->MakeProvider());
315-
m_network->SetOnClipboardCallback([this](const std::string& text) {
316-
if (!m_clipboard->SetText(text)) {
317-
std::cerr << "WARN: Failed to write incoming clipboard text through backend '"
318-
<< m_clipboard->BackendName() << "'." << std::endl;
319-
return;
320-
}
315+
m_network->SetOnClipboardCallback([this](const ClipboardPayload& payload) {
316+
std::thread([this, payload]() {
317+
if (!m_clipboard->SetPayload(payload)) {
318+
std::cerr << "WARN: Failed to write incoming clipboard payload through backend '"
319+
<< m_clipboard->BackendName() << "'." << std::endl;
320+
return;
321+
}
321322

322-
{
323-
std::lock_guard<std::mutex> lock(m_clipboardStateMutex);
324-
m_lastClipboardText = text;
325-
}
323+
{
324+
std::lock_guard<std::mutex> lock(m_clipboardStateMutex);
325+
m_lastClipboardPayload = payload;
326+
}
326327

327-
std::cout << "[CLIPBOARD] Received text update (" << text.size() << " bytes)" << std::endl;
328+
if (payload.image) {
329+
std::cout << "[CLIPBOARD] Received image update (" << payload.image->bytes.size() << " bytes). Header: ";
330+
for (std::size_t i = 0; i < std::min(payload.image->bytes.size(), static_cast<std::size_t>(8)); ++i) {
331+
printf("%02x ", payload.image->bytes[i]);
332+
}
333+
std::cout << std::endl;
334+
} else if (payload.plainText) {
335+
std::cout << "[CLIPBOARD] Received text update (" << payload.plainText->size() << " bytes)" << std::endl;
336+
}
337+
}).detach();
328338
});
329339
} else if (!m_options.clipboardEnabled) {
330340
std::cerr << "WARN: Clipboard sync disabled by configuration." << std::endl;
@@ -385,23 +395,30 @@ void ClientRuntime::StartClipboardWatcher() {
385395

386396
m_clipboardWatcherRunning = true;
387397
m_clipboardWatcher = std::thread([this]() {
388-
const auto handleClipboardText = [this](const std::string& text) {
398+
const auto handleClipboardPayload = [this](const ClipboardPayload& payload) {
389399
bool changed = false;
390400
{
391401
std::lock_guard<std::mutex> lock(m_clipboardStateMutex);
392-
if (!m_lastClipboardText || *m_lastClipboardText != text) {
393-
m_lastClipboardText = text;
402+
if (!m_lastClipboardPayload ||
403+
m_lastClipboardPayload->plainText != payload.plainText ||
404+
(m_lastClipboardPayload->image.has_value() != payload.image.has_value()) ||
405+
(payload.image && m_lastClipboardPayload->image && payload.image->bytes != m_lastClipboardPayload->image->bytes)) {
406+
m_lastClipboardPayload = payload;
394407
changed = true;
395408
}
396409
}
397410

398411
if (changed && m_network) {
399-
std::cout << "[CLIPBOARD] Local text changed (" << text.size() << " bytes)" << std::endl;
400-
m_network->NotifyLocalClipboardChanged(text);
412+
if (payload.image) {
413+
std::cout << "[CLIPBOARD] Local image changed (" << payload.image->bytes.size() << " bytes)" << std::endl;
414+
} else if (payload.plainText) {
415+
std::cout << "[CLIPBOARD] Local text changed (" << payload.plainText->size() << " bytes)" << std::endl;
416+
}
417+
m_network->NotifyLocalClipboardChanged(payload);
401418
}
402419
};
403420

404-
if (m_clipboard->WatchTextChanges(m_clipboardWatcherRunning, handleClipboardText)) {
421+
if (m_clipboard->WatchPayloadChanges(m_clipboardWatcherRunning, handleClipboardPayload)) {
405422
return;
406423
}
407424

@@ -415,9 +432,9 @@ void ClientRuntime::StartClipboardWatcher() {
415432
}
416433

417434
while (m_clipboardWatcherRunning) {
418-
const auto currentText = m_clipboard->GetText();
419-
if (currentText.has_value()) {
420-
handleClipboardText(*currentText);
435+
const auto currentPayload = m_clipboard->GetPayload();
436+
if (currentPayload.has_value()) {
437+
handleClipboardPayload(*currentPayload);
421438
}
422439

423440
std::this_thread::sleep_for(std::chrono::milliseconds(m_options.clipboardPollMs));

src/ClientRuntime.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ class ClientRuntime {
7878
std::atomic<bool> m_clipboardWatcherRunning{false};
7979
std::thread m_clipboardWatcher;
8080
std::mutex m_clipboardStateMutex;
81-
std::optional<std::string> m_lastClipboardText;
81+
std::optional<ClipboardPayload> m_lastClipboardPayload;
8282
std::string m_clipboardBackendName;
8383
};
8484

0 commit comments

Comments
 (0)