/* * Deskflow -- mouse and keyboard sharing utility * SPDX-FileCopyrightText: (C) 2025 Deskflow Developers * SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception */ #include "WlClipboardTests.h" #include "base/LogLevel.h" #include "common/PlatformInfo.h" #include "deskflow/ClipboardTypes.h" #include "platform/WlClipboard.h" #include #include #include #include void WlClipboardTests::defaultCtor() { WlClipboard clipboard(kClipboardClipboard); QCOMPARE(kClipboardClipboard, clipboard.getID()); WlClipboard primaryClipboard(kClipboardSelection); QCOMPARE(kClipboardSelection, primaryClipboard.getID()); } void WlClipboardTests::isAvailable() { // This test may fail on systems without wl-clipboard installed // In CI environments, we might need to skip this test or mock the commands bool available = WlClipboard::isAvailable(); // We don't assert true/false here because it depends on system setup // Just verify the method doesn't crash and returns a boolean QVERIFY(available == true || available == false); } void WlClipboardTests::initTestCase() { m_arch.init(); m_log.setFilter(LogLevel::Verbose); // Only run tests if Wayland clipboard tools are available if (!deskflow::platform::isWayland()) { QSKIP("not running on wayland skipping Wayland clipboard tests"); } // Only run tests if Wayland clipboard tools are available if (!WlClipboard::isAvailable()) { QSKIP("wl-clipboard tools not available, skipping Wayland clipboard tests"); } // Test if Wayland commands actually work (not just exist) WlClipboard testClipboard(kClipboardClipboard); if (!testClipboard.open(0)) { QSKIP("Failed to open Wayland clipboard, likely no Wayland session"); } // Try to clear clipboard - if this fails, we're probably not in a Wayland environment if (!testClipboard.empty()) { testClipboard.close(); QSKIP("Wayland clipboard operations not working, skipping tests"); } testClipboard.close(); } void WlClipboardTests::cleanupTestCase() { // Clean up by emptying clipboards try { WlClipboard clipboard(kClipboardClipboard); if (clipboard.open(0)) { clipboard.empty(); clipboard.close(); } } catch (...) { // Ignore cleanup errors } } void WlClipboardTests::open() { WlClipboard clipboard(kClipboardClipboard); QVERIFY(clipboard.open(0)); // Opening again should return false QVERIFY(!clipboard.open(1)); clipboard.close(); // Should be able to open again after closing QVERIFY(clipboard.open(2)); clipboard.close(); } void WlClipboardTests::empty() { WlClipboard clipboard(kClipboardClipboard); QVERIFY(clipboard.open(0)); // First add some known content clipboard.add(IClipboard::Format::Text, m_testString); QVERIFY(clipboard.has(IClipboard::Format::Text)); QCOMPARE(clipboard.get(IClipboard::Format::Text), m_testString); // Now empty the clipboard QVERIFY(clipboard.empty()); // Wait for the empty operation to complete and verify our content is gone // Use longer timeout for clipboard operations QVERIFY(waitForClipboardEmpty(clipboard, m_testString, 3000)); clipboard.close(); } void WlClipboardTests::singleFormat() { WlClipboard clipboard(kClipboardClipboard); QVERIFY(clipboard.open(0)); // Clear clipboard first - if this fails, skip test if (!clipboard.empty()) { clipboard.close(); QSKIP("Wayland clipboard operations not working in test environment"); } // Add text data clipboard.add(IClipboard::Format::Text, m_testString); // Wait for the data to be available if (!waitForClipboardContent(clipboard, IClipboard::Format::Text, m_testString)) { clipboard.close(); QSKIP("Wl-clipboard did not receive contents in time"); } // Add different text data - this should replace the previous data if (!clipboard.empty()) { clipboard.close(); QSKIP("Wayland clipboard clear operations not working in test environment"); } // Skip the wait for empty since we know clipboard operations aren't fully functional clipboard.add(IClipboard::Format::Text, m_testString2); clipboard.close(); } void WlClipboardTests::multipleFormats() { WlClipboard clipboard(kClipboardClipboard); QVERIFY(clipboard.open(0)); // Clear clipboard first QVERIFY(clipboard.empty()); // Add text data first clipboard.add(IClipboard::Format::Text, m_testString); // Note: wl-clipboard typically handles one format at a time // So we test formats separately rather than simultaneously if (!waitForClipboardContent(clipboard, IClipboard::Format::Text, m_testString)) { clipboard.close(); QSKIP("Wl-clipboard did not receive contents in time"); } // HTML format is currently not supported in WlClipboard implementation // So we skip HTML testing and just verify text format works // Clear and add different text data to test format replacement QVERIFY(clipboard.empty()); QVERIFY(waitForClipboardEmpty(clipboard, m_testString)); clipboard.add(IClipboard::Format::Text, m_testString2); if (!waitForClipboardContent(clipboard, IClipboard::Format::Text, m_testString2)) { clipboard.close(); QSKIP("Wl-clipboard did not receive contents in time"); } clipboard.close(); } void WlClipboardTests::hasFormat() { WlClipboard clipboard(kClipboardClipboard); QVERIFY(clipboard.open(0)); // Clear clipboard first QVERIFY(clipboard.empty()); // Wait for empty to take effect with longer timeout QVERIFY(waitForClipboardEmpty(clipboard, "any", 3000)); // Initially should not have any formats QVERIFY(!clipboard.has(IClipboard::Format::Text)); QVERIFY(!clipboard.has(IClipboard::Format::HTML)); QVERIFY(!clipboard.has(IClipboard::Format::Bitmap)); // Add text and verify clipboard.add(IClipboard::Format::Text, m_testString); if (!waitForClipboardContent(clipboard, IClipboard::Format::Text, m_testString)) { clipboard.close(); QSKIP("Wl-clipboard did not receive contents in time"); } QVERIFY(clipboard.has(IClipboard::Format::Text)); QVERIFY(!clipboard.has(IClipboard::Format::HTML)); // HTML not supported QVERIFY(!clipboard.has(IClipboard::Format::Bitmap)); clipboard.close(); } void WlClipboardTests::getTime() { WlClipboard clipboard(kClipboardClipboard); QVERIFY(clipboard.open(100)); // Should return the time passed to open() QCOMPARE(clipboard.getTime(), static_cast(100)); clipboard.close(); QVERIFY(clipboard.open(200)); QCOMPARE(clipboard.getTime(), static_cast(200)); clipboard.close(); } void WlClipboardTests::monitoring() { WlClipboard clipboard(kClipboardClipboard); QVERIFY(clipboard.open(0)); // Clear clipboard first QVERIFY(clipboard.empty()); // Start monitoring clipboard.startMonitoring(); // Initially should not have changed QVERIFY(!clipboard.hasChanged()); // Make a change to the clipboard using a separate clipboard instance // to simulate external changes WlClipboard externalClipboard(kClipboardClipboard); if (externalClipboard.open(1)) { externalClipboard.empty(); externalClipboard.add(IClipboard::Format::Text, m_testString); externalClipboard.close(); } // Wait for monitoring thread to detect change auto changeDetected = [&clipboard]() { return clipboard.hasChanged(); }; waitForClipboardCondition(clipboard, changeDetected, 1000); // Stop monitoring clipboard.stopMonitoring(); clipboard.close(); // Note: Change detection might not work reliably in all test environments // This test mainly verifies that monitoring doesn't crash } void WlClipboardTests::primaryClipboard() { // Test that primary clipboard works independently from regular clipboard WlClipboard clipboard(kClipboardClipboard); WlClipboard primaryClipboard(kClipboardSelection); QVERIFY(clipboard.open(0)); QVERIFY(primaryClipboard.open(1)); // Clear both clipboards QVERIFY(clipboard.empty()); QVERIFY(primaryClipboard.empty()); // Add different data to each clipboard.add(IClipboard::Format::Text, m_testString); primaryClipboard.add(IClipboard::Format::Text, m_testString2); if (!waitForClipboardContent(clipboard, IClipboard::Format::Text, m_testString) || !waitForClipboardContent(primaryClipboard, IClipboard::Format::Text, m_testString2)) { clipboard.close(); primaryClipboard.close(); QSKIP("Wl-clipboard did not receive contents in time"); } // Make sure they are different QCOMPARE_NE(clipboard.get(IClipboard::Format::Text), primaryClipboard.get(IClipboard::Format::Text)); clipboard.close(); primaryClipboard.close(); } void WlClipboardTests::closeWithoutOpen() { WlClipboard clipboard(kClipboardClipboard); // Should be safe to call close without open clipboard.close(); // After close, open should work QVERIFY(clipboard.open(0)); clipboard.close(); } void WlClipboardTests::addWithoutOpen() { WlClipboard clipboard(kClipboardClipboard); // Should not crash when adding without open clipboard.add(IClipboard::Format::Text, m_testString); // Should still be able to open and use normally QVERIFY(clipboard.open(0)); clipboard.close(); } void WlClipboardTests::getWithoutOpen() { WlClipboard clipboard(kClipboardClipboard); // Should return empty string when getting without open std::string result = clipboard.get(IClipboard::Format::Text); QVERIFY(result.empty()); // Should return false for has() without open QVERIFY(!clipboard.has(IClipboard::Format::Text)); // Should still be able to open and use normally QVERIFY(clipboard.open(0)); clipboard.close(); } bool WlClipboardTests::waitForClipboardCondition(WlClipboard &, std::function condition, int timeoutMs) { auto startTime = std::chrono::steady_clock::now(); auto timeout = std::chrono::milliseconds(timeoutMs); while (std::chrono::steady_clock::now() - startTime < timeout) { if (condition()) { return true; } std::this_thread::sleep_for(std::chrono::milliseconds(10)); } return false; } bool WlClipboardTests::waitForClipboardEmpty(WlClipboard &clipboard, const std::string &previousContent, int timeoutMs) { auto condition = [&clipboard, &previousContent]() { // Check if clipboard is empty or no longer contains our previous content if (!clipboard.has(IClipboard::Format::Text)) { return true; } std::string currentContent = clipboard.get(IClipboard::Format::Text); // Consider it empty if content is empty or significantly different return currentContent.empty() || currentContent != previousContent; }; return waitForClipboardCondition(clipboard, condition, timeoutMs); } bool WlClipboardTests::waitForClipboardContent( WlClipboard &clipboard, IClipboard::Format format, const std::string &expectedContent, int timeoutMs ) { auto condition = [&clipboard, format, &expectedContent]() { if (!clipboard.has(format)) { return false; } return clipboard.get(format) == expectedContent; }; return waitForClipboardCondition(clipboard, condition, timeoutMs); } QTEST_MAIN(WlClipboardTests)