From dff54bf596151ddc9142371d6ed7c82f408c7a0c Mon Sep 17 00:00:00 2001 From: Fern Zapata Date: Wed, 6 Nov 2024 03:08:33 -0700 Subject: [PATCH 01/12] Build: Move source code to a subdirectory Preparation for something clever. Maybe too clever. --- src/CMakeLists.txt | 46 +----------------------- src/dragdrop-dialog/CMakeLists.txt | 45 +++++++++++++++++++++++ src/{ => dragdrop-dialog}/application.cc | 0 src/{ => dragdrop-dialog}/application.hh | 0 src/{ => dragdrop-dialog}/dragarea.cc | 0 src/{ => dragdrop-dialog}/dragarea.hh | 0 src/{ => dragdrop-dialog}/droparea.cc | 0 src/{ => dragdrop-dialog}/droparea.hh | 0 src/{ => dragdrop-dialog}/main.cc | 0 src/{ => dragdrop-dialog}/window.cc | 0 src/{ => dragdrop-dialog}/window.hh | 0 11 files changed, 46 insertions(+), 45 deletions(-) create mode 100644 src/dragdrop-dialog/CMakeLists.txt rename src/{ => dragdrop-dialog}/application.cc (100%) rename src/{ => dragdrop-dialog}/application.hh (100%) rename src/{ => dragdrop-dialog}/dragarea.cc (100%) rename src/{ => dragdrop-dialog}/dragarea.hh (100%) rename src/{ => dragdrop-dialog}/droparea.cc (100%) rename src/{ => dragdrop-dialog}/droparea.hh (100%) rename src/{ => dragdrop-dialog}/main.cc (100%) rename src/{ => dragdrop-dialog}/window.cc (100%) rename src/{ => dragdrop-dialog}/window.hh (100%) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index baa89b8..fa09887 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,45 +1 @@ -set(BIN_NAME ${PROJECT_NAME} CACHE STRING "Executable name") - -set(PROJECT_SOURCES - application.cc - application.hh - dragarea.cc - dragarea.hh - droparea.cc - droparea.hh - main.cc - window.cc - window.hh -) - -if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) - qt_add_executable(${BIN_NAME} - MANUAL_FINALIZATION - ${PROJECT_SOURCES} - ) -else() - add_executable(${BIN_NAME} - ${PROJECT_SOURCES} - ) -endif() - -target_link_libraries( - ${BIN_NAME} PRIVATE - Qt${QT_VERSION_MAJOR}::Widgets -) -target_compile_definitions( - ${BIN_NAME} PRIVATE - DRAGDROP_NAME="${BIN_NAME}" - DRAGDROP_VERSION="${PROJECT_VERSION}" -) -target_compile_options( - ${BIN_NAME} PRIVATE - $<$:/W3> - $<$>:-Wall> -) - -install(TARGETS ${BIN_NAME}) - -if(QT_VERSION_MAJOR EQUAL 6) - qt_finalize_executable(qt-cmake-test) -endif() +add_subdirectory(dragdrop-dialog) diff --git a/src/dragdrop-dialog/CMakeLists.txt b/src/dragdrop-dialog/CMakeLists.txt new file mode 100644 index 0000000..baa89b8 --- /dev/null +++ b/src/dragdrop-dialog/CMakeLists.txt @@ -0,0 +1,45 @@ +set(BIN_NAME ${PROJECT_NAME} CACHE STRING "Executable name") + +set(PROJECT_SOURCES + application.cc + application.hh + dragarea.cc + dragarea.hh + droparea.cc + droparea.hh + main.cc + window.cc + window.hh +) + +if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) + qt_add_executable(${BIN_NAME} + MANUAL_FINALIZATION + ${PROJECT_SOURCES} + ) +else() + add_executable(${BIN_NAME} + ${PROJECT_SOURCES} + ) +endif() + +target_link_libraries( + ${BIN_NAME} PRIVATE + Qt${QT_VERSION_MAJOR}::Widgets +) +target_compile_definitions( + ${BIN_NAME} PRIVATE + DRAGDROP_NAME="${BIN_NAME}" + DRAGDROP_VERSION="${PROJECT_VERSION}" +) +target_compile_options( + ${BIN_NAME} PRIVATE + $<$:/W3> + $<$>:-Wall> +) + +install(TARGETS ${BIN_NAME}) + +if(QT_VERSION_MAJOR EQUAL 6) + qt_finalize_executable(qt-cmake-test) +endif() diff --git a/src/application.cc b/src/dragdrop-dialog/application.cc similarity index 100% rename from src/application.cc rename to src/dragdrop-dialog/application.cc diff --git a/src/application.hh b/src/dragdrop-dialog/application.hh similarity index 100% rename from src/application.hh rename to src/dragdrop-dialog/application.hh diff --git a/src/dragarea.cc b/src/dragdrop-dialog/dragarea.cc similarity index 100% rename from src/dragarea.cc rename to src/dragdrop-dialog/dragarea.cc diff --git a/src/dragarea.hh b/src/dragdrop-dialog/dragarea.hh similarity index 100% rename from src/dragarea.hh rename to src/dragdrop-dialog/dragarea.hh diff --git a/src/droparea.cc b/src/dragdrop-dialog/droparea.cc similarity index 100% rename from src/droparea.cc rename to src/dragdrop-dialog/droparea.cc diff --git a/src/droparea.hh b/src/dragdrop-dialog/droparea.hh similarity index 100% rename from src/droparea.hh rename to src/dragdrop-dialog/droparea.hh diff --git a/src/main.cc b/src/dragdrop-dialog/main.cc similarity index 100% rename from src/main.cc rename to src/dragdrop-dialog/main.cc diff --git a/src/window.cc b/src/dragdrop-dialog/window.cc similarity index 100% rename from src/window.cc rename to src/dragdrop-dialog/window.cc diff --git a/src/window.hh b/src/dragdrop-dialog/window.hh similarity index 100% rename from src/window.hh rename to src/dragdrop-dialog/window.hh From 4cbf279359934e0822dda9d3f1de1a4aed318c38 Mon Sep 17 00:00:00 2001 From: Fern Zapata Date: Sat, 9 Nov 2024 07:04:06 -0700 Subject: [PATCH 02/12] Refactor: Run program through a wrapper This is gonna be pretty roundabout, but it just works. --- src/CMakeLists.txt | 1 + src/dragdrop-dialog/CMakeLists.txt | 42 +++++++-------------- src/dragdrop/CMakeLists.txt | 30 +++++++++++++++ src/dragdrop/application.cc | 59 ++++++++++++++++++++++++++++++ src/dragdrop/application.hh | 28 ++++++++++++++ src/dragdrop/main.cc | 11 ++++++ 6 files changed, 143 insertions(+), 28 deletions(-) create mode 100644 src/dragdrop/CMakeLists.txt create mode 100644 src/dragdrop/application.cc create mode 100644 src/dragdrop/application.hh create mode 100644 src/dragdrop/main.cc diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fa09887..5d50cbc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1 +1,2 @@ +add_subdirectory(dragdrop) add_subdirectory(dragdrop-dialog) diff --git a/src/dragdrop-dialog/CMakeLists.txt b/src/dragdrop-dialog/CMakeLists.txt index baa89b8..982ef48 100644 --- a/src/dragdrop-dialog/CMakeLists.txt +++ b/src/dragdrop-dialog/CMakeLists.txt @@ -1,45 +1,31 @@ -set(BIN_NAME ${PROJECT_NAME} CACHE STRING "Executable name") - -set(PROJECT_SOURCES - application.cc - application.hh - dragarea.cc - dragarea.hh - droparea.cc - droparea.hh - main.cc - window.cc - window.hh -) +cmake_path(GET CMAKE_CURRENT_LIST_DIR FILENAME BIN_NAME) +file(GLOB SRC CONFIGURE_DEPENDS "*.cc" "*.hh" "*.ui") if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) - qt_add_executable(${BIN_NAME} - MANUAL_FINALIZATION - ${PROJECT_SOURCES} - ) + qt_add_executable(${BIN_NAME} MANUAL_FINALIZATION ${SRC}) else() - add_executable(${BIN_NAME} - ${PROJECT_SOURCES} - ) + add_executable(${BIN_NAME} ${SRC}) endif() -target_link_libraries( - ${BIN_NAME} PRIVATE +target_link_libraries(${BIN_NAME} +PRIVATE Qt${QT_VERSION_MAJOR}::Widgets ) -target_compile_definitions( - ${BIN_NAME} PRIVATE +target_compile_definitions(${BIN_NAME} +PRIVATE DRAGDROP_NAME="${BIN_NAME}" DRAGDROP_VERSION="${PROJECT_VERSION}" ) -target_compile_options( - ${BIN_NAME} PRIVATE +target_compile_options(${BIN_NAME} +PRIVATE $<$:/W3> $<$>:-Wall> ) -install(TARGETS ${BIN_NAME}) +install(TARGETS ${BIN_NAME} + DESTINATION ${CMAKE_INSTALL_LIBEXECDIR} +) if(QT_VERSION_MAJOR EQUAL 6) - qt_finalize_executable(qt-cmake-test) + qt_finalize_executable(${BIN_NAME}) endif() diff --git a/src/dragdrop/CMakeLists.txt b/src/dragdrop/CMakeLists.txt new file mode 100644 index 0000000..785ee55 --- /dev/null +++ b/src/dragdrop/CMakeLists.txt @@ -0,0 +1,30 @@ +cmake_path(GET CMAKE_CURRENT_LIST_DIR FILENAME BIN_NAME) +file(GLOB SRC CONFIGURE_DEPENDS "*.cc" "*.hh" "*.ui") + +if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) + qt_add_executable(${BIN_NAME} MANUAL_FINALIZATION ${SRC}) +else() + add_executable(${BIN_NAME} ${SRC}) +endif() + +target_link_libraries(${BIN_NAME} +PRIVATE + Qt${QT_VERSION_MAJOR}::Core +) +target_compile_definitions(${BIN_NAME} +PRIVATE + LIBEXEC_DIR="${CMAKE_INSTALL_LIBEXECDIR}" + PROGRAM_NAME="${BIN_NAME}" + PROGRAM_VERSION="${PROJECT_VERSION}" +) +target_compile_options(${BIN_NAME} +PRIVATE + $<$:/W3> + $<$>:-Wall> +) + +install(TARGETS ${BIN_NAME}) + +if(QT_VERSION_MAJOR EQUAL 6) + qt_finalize_executable(${BIN_NAME}) +endif() diff --git a/src/dragdrop/application.cc b/src/dragdrop/application.cc new file mode 100644 index 0000000..a819a49 --- /dev/null +++ b/src/dragdrop/application.cc @@ -0,0 +1,59 @@ +/* Copyright © 2024 Fern Zapata + * This file is under the terms of the GNU GPL ver. 3, or, at your + * option, any later version. If a copy of the GPL wasn't included + * with this file, see . */ + +#include "application.hh" +#include + +namespace DragDrop { + +Application::Application(int& argc, char** argv) + : QCoreApplication(argc, argv) +{ + setApplicationName(QStringLiteral(PROGRAM_NAME)); + setApplicationVersion(QStringLiteral(PROGRAM_VERSION)); + + args.setApplicationDescription( + tr("Drag-and-drop file source for the terminal")); + args.addHelpOption(); + args.addVersionOption(); + + args.addOptions({ + {{"o", "once"}, tr("Exit after a single drag or drop.")}, + {{"u", "uris"}, tr("Print URIs instead of paths on drop.")}, + {{"0", "null"}, + tr("Separate printed paths with a null character.")}, + {"dirs-first", + tr("List directories before files on the source dialog")}, + }); + args.addPositionalArgument( + "files", tr("Files to display"), tr("[files...]")); + + args.process(*this); +} + +auto Application::exec() -> int +{ + auto command = applicationDirPath() + + QStringLiteral("/../" LIBEXEC_DIR "/" PROGRAM_NAME "-dialog"); + prog.setProgram(command); + prog.setArguments(arguments().mid(1)); + prog.setProcessChannelMode( + QProcess::ProcessChannelMode::ForwardedChannels); + + connect(&prog, + QOverload::of(&QProcess::finished), + this, &Application::dialogFinished); + + prog.start(); + + return QCoreApplication::exec(); +} + +void Application::dialogFinished(int code, QProcess::ExitStatus status) +{ + exit(); +} + +}; // namespace DragDrop diff --git a/src/dragdrop/application.hh b/src/dragdrop/application.hh new file mode 100644 index 0000000..0b5db19 --- /dev/null +++ b/src/dragdrop/application.hh @@ -0,0 +1,28 @@ +/* Copyright © 2024 Fern Zapata + * This file is under the terms of the GNU GPL ver. 3, or, at your + * option, any later version. If a copy of the GPL wasn't included + * with this file, see . */ + +#pragma once + +#include +#include +#include + +namespace DragDrop { + +class Application : public QCoreApplication { + Q_OBJECT + +public: + Application(int& argc, char** argv); + auto exec() -> int; + +private: + QCommandLineParser args; + QProcess prog; + + void dialogFinished(int code, QProcess::ExitStatus exitStatus); +}; + +}; // namespace DragDrop diff --git a/src/dragdrop/main.cc b/src/dragdrop/main.cc new file mode 100644 index 0000000..104c88d --- /dev/null +++ b/src/dragdrop/main.cc @@ -0,0 +1,11 @@ +/* Copyright © 2024 Fern Zapata + * This file is under the terms of the GNU GPL ver. 3, or, at your + * option, any later version. If a copy of the GPL wasn't included + * with this file, see . */ + +#include "application.hh" + +auto main(int argc, char** argv) -> int +{ + return DragDrop::Application(argc, argv).exec(); +} From 32ef37358835564a12f4e9b55ae3b4dc98f68a21 Mon Sep 17 00:00:00 2001 From: Fern Zapata Date: Wed, 14 May 2025 02:04:16 -0600 Subject: [PATCH 03/12] Refactor: Move option handling to the wrapper --- src/dragdrop-dialog/application.cc | 48 +++------------- src/dragdrop-dialog/application.hh | 15 ++--- src/dragdrop-dialog/window.cc | 38 ++++++------- src/dragdrop-dialog/window.hh | 34 ++++-------- src/dragdrop/application.cc | 89 +++++++++++++++++++++++++----- src/dragdrop/application.hh | 18 ++++-- 6 files changed, 129 insertions(+), 113 deletions(-) diff --git a/src/dragdrop-dialog/application.cc b/src/dragdrop-dialog/application.cc index f3e2f60..6b9d5a5 100644 --- a/src/dragdrop-dialog/application.cc +++ b/src/dragdrop-dialog/application.cc @@ -1,7 +1,7 @@ -/* Copyright © 2022-2024 Fern Zapata - * This program is subject to the terms of the GNU GPL, version 3 - * or, at your option, any later version. If a copy of it was not - * included with this file, see https://www.gnu.org/licenses/. */ +/* Copyright © 2022-2025 Fern Zapata + * This file is under the terms of the GNU GPL ver. 3, or (at your + * option) any later version. If a copy of the GPL wasn't included + * along with this file, see . */ #include "application.hh" #include "window.hh" @@ -12,49 +12,17 @@ namespace DragDrop { Application::Application(int& argc, char** argv) : QApplication(argc, argv) { - setApplicationName(QStringLiteral(DRAGDROP_NAME)); - setApplicationVersion(QStringLiteral(DRAGDROP_VERSION)); - parser.setApplicationDescription( - tr("Drag and drop in the terminal.")); - parser.addHelpOption(); - parser.addVersionOption(); - parser.addPositionalArgument( - "files", tr("File to display"), tr("[files...]")); - parser.addOptions({ - {{"o", "once"}, - tr("Exit after a single drag or drop.")}, - {{"u", "uris"}, - tr("Print URIs instead of paths on drop.")}, - {{"0", "null"}, - tr("Separate printed paths with a null character.")}, - {"dirs-first", - tr("List directories before files on the source dialog")}, - }); - parser.process(*this); } -int Application::exec() +auto Application::exec() -> int { QList files; - int last_dir = 0; - bool dirs_1st = parser.isSet(QStringLiteral("dirs-first")); - const auto args = parser.positionalArguments(); - for (const QString& filename : args) { - const QFileInfo file(filename); - if (dirs_1st && file.isDir()) { - files.insert(last_dir++, file); - } else if (file.exists()) { - files << file; - } + for (const auto& name : arguments().mid(1)) { + files << QFileInfo(name); } - const auto opts = Window::Options() - .setFlag(Window::Option::Null, parser.isSet(QStringLiteral("null"))) - .setFlag(Window::Option::URIs, parser.isSet(QStringLiteral("uris"))) - .setFlag(Window::Option::Once, parser.isSet(QStringLiteral("once"))); - - Window win(files, opts); + Window win(files); win.show(); return QApplication::exec(); } diff --git a/src/dragdrop-dialog/application.hh b/src/dragdrop-dialog/application.hh index a4f8f14..2b7dedb 100644 --- a/src/dragdrop-dialog/application.hh +++ b/src/dragdrop-dialog/application.hh @@ -1,10 +1,9 @@ -/* Copyright © 2022 Fern Zapata - * This program is subject to the terms of the GNU GPL, version 3 - * or, at your option, any later version. If a copy of it was not - * included with this file, see https://www.gnu.org/licenses/. */ +/* Copyright © 2022-2025 Fern Zapata + * This file is under the terms of the GNU GPL ver. 3, or (at your + * option) any later version. If a copy of the GPL wasn't included + * along with this file, see . */ -#ifndef DRAGDROP_APPLICATION_HH -#define DRAGDROP_APPLICATION_HH +#pragma once #include #include @@ -17,12 +16,10 @@ class Application : public QApplication public: Application(int& argc, char** argv); - int exec(); + auto exec() -> int; private: QCommandLineParser parser; }; }; // namespace DragDrop - -#endif // DRAGDROP_APPLICATION_HH diff --git a/src/dragdrop-dialog/window.cc b/src/dragdrop-dialog/window.cc index 0b1589b..4fd2120 100644 --- a/src/dragdrop-dialog/window.cc +++ b/src/dragdrop-dialog/window.cc @@ -1,25 +1,26 @@ -/* Copyright © 2022-2023 Fern Zapata - * This program is subject to the terms of the GNU GPL, version 3 - * or, at your option, any later version. If a copy of it was not - * included with this file, see https://www.gnu.org/licenses/. */ +/* Copyright © 2022-2025 Fern Zapata + * This file is under the terms of the GNU GPL ver. 3, or (at your + * option) any later version. If a copy of the GPL wasn't included + * along with this file, see . */ #include "window.hh" #include "dragarea.hh" #include "droparea.hh" #include -#include -#include namespace DragDrop { -Window::Window(const QList& files, const Options& opts, - QWidget* parent) +Window::Window(const QList& files, QWidget* parent) : QDialog(parent) - , m_opts(opts) - , m_term(opts & Option::Null ? '\0' : '\n') + , mOutput(this) + , mWriter(&mOutput) { setWindowTitle(tr("Drag and Drop")); + if (not mOutput.open(stdout, QIODevice::WriteOnly)) { + close(); + } + auto layout = new QVBoxLayout(this); layout->setContentsMargins({}); QWidget* area = nullptr; @@ -37,23 +38,18 @@ Window::Window(const QList& files, const Options& opts, void Window::onFilesSent() { - if (m_opts & Option::Once) { - QTimer::singleShot(500, this, &Window::close); - } + mWriter.append(QCborSimpleType::Null); + mOutput.flush(); } void Window::onFilesReceived(const QList& files) { - QTextStream out(stdout); + mWriter.startArray(files.length()); for (const auto& f : files) { - out << (m_opts & Option::URIs ? f.url() : f.path()) - << m_term - << Qt::flush; - } - - if (m_opts & Option::Once) { - close(); + mWriter.append(f.url()); } + mWriter.endArray(); + mOutput.flush(); } }; // namespace DragDrop diff --git a/src/dragdrop-dialog/window.hh b/src/dragdrop-dialog/window.hh index f05fd4b..9fc0eac 100644 --- a/src/dragdrop-dialog/window.hh +++ b/src/dragdrop-dialog/window.hh @@ -1,11 +1,11 @@ -/* Copyright © 2022 Fern Zapata - * This program is subject to the terms of the GNU GPL, version 3 - * or, at your option, any later version. If a copy of it was not - * included with this file, see https://www.gnu.org/licenses/. */ +/* Copyright © 2022-2025 Fern Zapata + * This file is under the terms of the GNU GPL ver. 3, or (at your + * option) any later version. If a copy of the GPL wasn't included + * along with this file, see . */ -#ifndef DRAGDROP_WINDOW_HH -#define DRAGDROP_WINDOW_HH +#pragma once +#include #include #include #include @@ -17,29 +17,15 @@ class Window : public QDialog Q_OBJECT public: - enum Option { - None = 0, - URIs = 1 << 0, - Once = 1 << 1, - Null = 1 << 2, - }; - Q_DECLARE_FLAGS(Options, Option) - - Window(const QList& files, - const Options& opts = Option::None, - QWidget* parent = nullptr); + Window(const QList& files, QWidget* parent = nullptr); public slots: void onFilesReceived(const QList& files); void onFilesSent(); private: - const Options m_opts; - const char m_term; + QFile mOutput; + QCborStreamWriter mWriter; }; -Q_DECLARE_OPERATORS_FOR_FLAGS(Window::Options) - -}; // namespace DragDrop - -#endif // DRAGDROP_WINDOW_HH +} // namespace DragDrop diff --git a/src/dragdrop/application.cc b/src/dragdrop/application.cc index a819a49..eb1f8cd 100644 --- a/src/dragdrop/application.cc +++ b/src/dragdrop/application.cc @@ -1,15 +1,20 @@ -/* Copyright © 2024 Fern Zapata - * This file is under the terms of the GNU GPL ver. 3, or, at your - * option, any later version. If a copy of the GPL wasn't included - * with this file, see . */ +/* Copyright © 2024-2025 Fern Zapata + * This file is under the terms of the GNU GPL ver. 3, or (at your + * option) any later version. If a copy of the GPL wasn't included + * along with this file, see . */ #include "application.hh" +#include #include +#include +#include namespace DragDrop { Application::Application(int& argc, char** argv) : QCoreApplication(argc, argv) + , mProg(this) + , mOutput(stdout, QIODevice::WriteOnly) { setApplicationName(QStringLiteral(PROGRAM_NAME)); setApplicationVersion(QStringLiteral(PROGRAM_VERSION)); @@ -35,22 +40,80 @@ Application::Application(int& argc, char** argv) auto Application::exec() -> int { - auto command = applicationDirPath() - + QStringLiteral("/../" LIBEXEC_DIR "/" PROGRAM_NAME "-dialog"); - prog.setProgram(command); - prog.setArguments(arguments().mid(1)); - prog.setProcessChannelMode( - QProcess::ProcessChannelMode::ForwardedChannels); - - connect(&prog, + mProg.setProgram(applicationDirPath() + + "/../" LIBEXEC_DIR "/" PROGRAM_NAME "-dialog"); + + int dirPos = 0; + QStringList files; + for (const auto& name : args.positionalArguments()) { + const QFileInfo file(name); + if (args.isSet(QStringLiteral("dirs-first")) and file.isDir()) { + files.insert(dirPos++, name); + } else if (file.exists()) { + files << name; + } + } + mProg.setArguments(files); + + connect(&mProg, &QProcess::readyReadStandardOutput, this, + &Application::dialogOutput); + connect(&mProg, QOverload::of(&QProcess::finished), this, &Application::dialogFinished); - prog.start(); + mProg.start(); return QCoreApplication::exec(); } +static auto decodeString(QCborStreamReader& reader) +{ + QString str; + QCborStreamReader::StringResult r; + while ((r = reader.readString()).status == QCborStreamReader::Ok) { + str += r.data; + } + if (r.status == QCborStreamReader::Error) { + str.clear(); + } + return str; +} + +static void handleStream( + QCborStreamReader& reader, QTextStream& out, bool asUri, char term) +{ + QUrl uri; + switch (reader.type()) { + case QCborStreamReader::ByteArray: + case QCborStreamReader::String: + uri = decodeString(reader); + out << (asUri ? uri.url() : uri.path()) << term; + break; + case QCborStreamReader::Array: + reader.enterContainer(); + while (not reader.lastError() and reader.hasNext()) { + handleStream(reader, out, asUri, term); + } + if (not reader.lastError()) { + reader.leaveContainer(); + } + break; + default: + reader.next(); + } +} + +void Application::dialogOutput() +{ + mReader.setDevice(&mProg); + handleStream(mReader, mOutput, args.isSet(QStringLiteral("uris")), + args.isSet(QStringLiteral("null")) ? '\0' : '\n'); + mOutput.flush(); + if (args.isSet(QStringLiteral("once"))) { + QTimer::singleShot(300, &mProg, &QProcess::terminate); + } +} + void Application::dialogFinished(int code, QProcess::ExitStatus status) { exit(); diff --git a/src/dragdrop/application.hh b/src/dragdrop/application.hh index 0b5db19..b131019 100644 --- a/src/dragdrop/application.hh +++ b/src/dragdrop/application.hh @@ -1,17 +1,19 @@ -/* Copyright © 2024 Fern Zapata - * This file is under the terms of the GNU GPL ver. 3, or, at your - * option, any later version. If a copy of the GPL wasn't included - * with this file, see . */ +/* Copyright © 2024-2025 Fern Zapata + * This file is under the terms of the GNU GPL ver. 3, or (at your + * option) any later version. If a copy of the GPL wasn't included + * along with this file, see . */ #pragma once +#include #include #include #include namespace DragDrop { -class Application : public QCoreApplication { +class Application : public QCoreApplication +{ Q_OBJECT public: @@ -19,9 +21,13 @@ public: auto exec() -> int; private: + QCborStreamReader mReader; QCommandLineParser args; - QProcess prog; + QProcess mProg; + QTextStream mOutput; +private slots: + void dialogOutput(); void dialogFinished(int code, QProcess::ExitStatus exitStatus); }; From 8d22a35c7ec68a9813a8d592f03f31c825a28169 Mon Sep 17 00:00:00 2001 From: Fern Zapata Date: Wed, 14 May 2025 08:01:51 -0600 Subject: [PATCH 04/12] Refactor: Use a stack layout for the dialog Should make it easier to switch dynamically between the two modes. --- src/dragdrop-dialog/droparea.cc | 12 ++++++------ src/dragdrop-dialog/droparea.hh | 15 ++++++--------- src/dragdrop-dialog/window.cc | 28 ++++++++++++++-------------- src/dragdrop-dialog/window.hh | 2 +- 4 files changed, 27 insertions(+), 30 deletions(-) diff --git a/src/dragdrop-dialog/droparea.cc b/src/dragdrop-dialog/droparea.cc index 5e97e30..51eb207 100644 --- a/src/dragdrop-dialog/droparea.cc +++ b/src/dragdrop-dialog/droparea.cc @@ -1,7 +1,7 @@ -/* Copyright © 2022-2023 Fern Zapata - * This program is subject to the terms of the GNU GPL, version 3 - * or, at your option, any later version. If a copy of it was not - * included with this file, see https://www.gnu.org/licenses/. */ +/* Copyright © 2022-2025 Fern Zapata + * This file is under the terms of the GNU GPL ver. 3, or (at your + * option) any later version. If a copy of the GPL wasn't included + * along with this file, see . */ #include "droparea.hh" #include @@ -16,7 +16,7 @@ DropArea::DropArea(QWidget* parent) : QLabel(parent) { setAcceptDrops(true); - setMargin(32); + setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); setPixmap(QIcon::fromTheme("folder-open-symbolic").pixmap(48)); } @@ -29,7 +29,7 @@ void DropArea::dragEnterEvent(QDragEnterEvent* event) void DropArea::dropEvent(QDropEvent* event) { - emit filesReceived(event->mimeData()->urls()); + emit filesRecv(event->mimeData()->urls()); event->setDropAction(Qt::CopyAction); event->accept(); } diff --git a/src/dragdrop-dialog/droparea.hh b/src/dragdrop-dialog/droparea.hh index 44ac6e3..c9efa42 100644 --- a/src/dragdrop-dialog/droparea.hh +++ b/src/dragdrop-dialog/droparea.hh @@ -1,10 +1,9 @@ -/* Copyright © 2022 Fern Zapata - * This program is subject to the terms of the GNU GPL, version 3 - * or, at your option, any later version. If a copy of it was not - * included with this file, see https://www.gnu.org/licenses/. */ +/* Copyright © 2022-2025 Fern Zapata + * This file is under the terms of the GNU GPL ver. 3, or (at your + * option) any later version. If a copy of the GPL wasn't included + * along with this file, see . */ -#ifndef DRAGDROP_DROPAREA_HH -#define DRAGDROP_DROPAREA_HH +#pragma once #include #include @@ -23,9 +22,7 @@ protected: void dropEvent(QDropEvent* event) override; signals: - void filesReceived(const QList& urls); + void filesRecv(const QList& urls); }; }; // namespace DragDrop - -#endif // DRAGDROP_DROPAREA_HH diff --git a/src/dragdrop-dialog/window.cc b/src/dragdrop-dialog/window.cc index 4fd2120..def8def 100644 --- a/src/dragdrop-dialog/window.cc +++ b/src/dragdrop-dialog/window.cc @@ -6,7 +6,7 @@ #include "window.hh" #include "dragarea.hh" #include "droparea.hh" -#include +#include namespace DragDrop { @@ -21,19 +21,19 @@ Window::Window(const QList& files, QWidget* parent) close(); } - auto layout = new QVBoxLayout(this); - layout->setContentsMargins({}); - QWidget* area = nullptr; - if (files.isEmpty()) { - area = new DropArea; - connect(static_cast(area), &DropArea::filesReceived, - this, &Window::onFilesReceived); - } else { - area = new DragArea(files); - connect(static_cast(area), &DragArea::filesSent, - this, &Window::onFilesSent); + auto layout = new QStackedLayout(this); + auto drop = new DropArea(this); + auto drag = new DragArea(files, this); + + connect(drop, &DropArea::filesRecv, this, &Window::onFilesRecv); + connect(drag, &DragArea::filesSent, this, &Window::onFilesSent); + + layout->addWidget(drop); + layout->addWidget(drag); + + if (drag->count()) { + layout->setCurrentWidget(drag); } - layout->addWidget(area); } void Window::onFilesSent() @@ -42,7 +42,7 @@ void Window::onFilesSent() mOutput.flush(); } -void Window::onFilesReceived(const QList& files) +void Window::onFilesRecv(const QList& files) { mWriter.startArray(files.length()); for (const auto& f : files) { diff --git a/src/dragdrop-dialog/window.hh b/src/dragdrop-dialog/window.hh index 9fc0eac..9213683 100644 --- a/src/dragdrop-dialog/window.hh +++ b/src/dragdrop-dialog/window.hh @@ -20,7 +20,7 @@ public: Window(const QList& files, QWidget* parent = nullptr); public slots: - void onFilesReceived(const QList& files); + void onFilesRecv(const QList& files); void onFilesSent(); private: From de05d1f2ca689420d3ce02642780ef65e5d99649 Mon Sep 17 00:00:00 2001 From: Fern Zapata Date: Mon, 13 Oct 2025 00:52:06 -0600 Subject: [PATCH 05/12] Ref: Make widgets into members --- src/dragdrop-dialog/window.cc | 22 +++++++++------------- src/dragdrop-dialog/window.hh | 6 ++++++ 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/dragdrop-dialog/window.cc b/src/dragdrop-dialog/window.cc index def8def..b1be594 100644 --- a/src/dragdrop-dialog/window.cc +++ b/src/dragdrop-dialog/window.cc @@ -4,9 +4,6 @@ * along with this file, see . */ #include "window.hh" -#include "dragarea.hh" -#include "droparea.hh" -#include namespace DragDrop { @@ -14,6 +11,9 @@ Window::Window(const QList& files, QWidget* parent) : QDialog(parent) , mOutput(this) , mWriter(&mOutput) + , mLayout(this) + , mDrag(files, this) + , mDrop(this) { setWindowTitle(tr("Drag and Drop")); @@ -21,18 +21,14 @@ Window::Window(const QList& files, QWidget* parent) close(); } - auto layout = new QStackedLayout(this); - auto drop = new DropArea(this); - auto drag = new DragArea(files, this); + connect(&mDrop, &DropArea::filesRecv, this, &Window::onFilesRecv); + connect(&mDrag, &DragArea::filesSent, this, &Window::onFilesSent); - connect(drop, &DropArea::filesRecv, this, &Window::onFilesRecv); - connect(drag, &DragArea::filesSent, this, &Window::onFilesSent); + mLayout.addWidget(&mDrop); + mLayout.addWidget(&mDrag); - layout->addWidget(drop); - layout->addWidget(drag); - - if (drag->count()) { - layout->setCurrentWidget(drag); + if (mDrag.count()) { + mLayout.setCurrentWidget(&mDrag); } } diff --git a/src/dragdrop-dialog/window.hh b/src/dragdrop-dialog/window.hh index 9213683..b3052a3 100644 --- a/src/dragdrop-dialog/window.hh +++ b/src/dragdrop-dialog/window.hh @@ -5,9 +5,12 @@ #pragma once +#include "dragarea.hh" +#include "droparea.hh" #include #include #include +#include #include namespace DragDrop { @@ -26,6 +29,9 @@ public slots: private: QFile mOutput; QCborStreamWriter mWriter; + QStackedLayout mLayout; + DragArea mDrag; + DropArea mDrop; }; } // namespace DragDrop From 03ed5128dd07925d7c911bce418d03df01098f6a Mon Sep 17 00:00:00 2001 From: Fern Zapata Date: Mon, 13 Oct 2025 01:24:48 -0600 Subject: [PATCH 06/12] Ref: Split off URL parsing into a library --- src/CMakeLists.txt | 1 + src/common/CMakeLists.txt | 22 ++++++++++++++ src/common/fileparser.cc | 58 +++++++++++++++++++++++++++++++++++++ src/common/fileparser.hh | 36 +++++++++++++++++++++++ src/dragdrop/CMakeLists.txt | 1 + src/dragdrop/application.cc | 53 ++++++++------------------------- src/dragdrop/application.hh | 8 +++-- 7 files changed, 135 insertions(+), 44 deletions(-) create mode 100644 src/common/CMakeLists.txt create mode 100644 src/common/fileparser.cc create mode 100644 src/common/fileparser.hh diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5d50cbc..6af99ec 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,2 +1,3 @@ +add_subdirectory(common) add_subdirectory(dragdrop) add_subdirectory(dragdrop-dialog) diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt new file mode 100644 index 0000000..53c84ae --- /dev/null +++ b/src/common/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_path(GET CMAKE_CURRENT_LIST_DIR FILENAME TARGET_NAME) +file(GLOB SRC CONFIGURE_DEPENDS "*.cc" "*.hh") + +if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) + qt_add_library(${TARGET_NAME} STATIC ${SRC}) +else() + add_library(${TARGET_NAME} ${SRC}) +endif() + +target_link_libraries(${TARGET_NAME} +PUBLIC + Qt${QT_VERSION_MAJOR}::Core +) +target_include_directories(${TARGET_NAME} +PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} +) +target_compile_options(${TARGET_NAME} +PRIVATE + $<$:/W4> + $<$>:-Wall -Wpedantic> +) diff --git a/src/common/fileparser.cc b/src/common/fileparser.cc new file mode 100644 index 0000000..e79da86 --- /dev/null +++ b/src/common/fileparser.cc @@ -0,0 +1,58 @@ +/* Copyright © 2025 Fern Zapata + * This file is under the terms of the GNU GPL version 3, or (at your + * option) any later version. If you didn't receive a copy of the GPL + * along with this file, see . */ + +#include "fileparser.hh" + +namespace DragDrop { + +FileParser::FileParser(QIODevice* device, QObject* parent) + : QObject(parent) + , mDevice(device) +{ + connect(mDevice, &QIODevice::readyRead, this, &FileParser::onRead); +} + +static auto decodeString(QCborStreamReader& reader) +{ + QString str; + QCborStreamReader::StringResult r; + while ((r = reader.readString()).status == QCborStreamReader::Ok) { + str += r.data; + } + if (r.status == QCborStreamReader::Error) { + str.clear(); + } + return str; +} + +void FileParser::handleStream() +{ + switch (mReader.type()) { + case QCborStreamReader::ByteArray: + case QCborStreamReader::String: + emit parsedURL(decodeString(mReader)); + break; + case QCborStreamReader::Array: + mReader.enterContainer(); + while (not mReader.lastError() and mReader.hasNext()) { + handleStream(); + } + if (not mReader.lastError()) { + mReader.leaveContainer(); + } + break; + default: + mReader.next(); + } +} + +void FileParser::onRead() +{ + mReader.setDevice(mDevice); + handleStream(); + emit finished(); +} + +}; // namespace DragDrop diff --git a/src/common/fileparser.hh b/src/common/fileparser.hh new file mode 100644 index 0000000..acf8cb4 --- /dev/null +++ b/src/common/fileparser.hh @@ -0,0 +1,36 @@ +/* Copyright © 2025 Fern Zapata + * This file is under the terms of the GNU GPL version 3, or (at your + * option) any later version. If you didn't receive a copy of the GPL + * along with this file, see . */ + +#pragma once + +#include +#include +#include +#include + +namespace DragDrop { + +class FileParser : public QObject +{ + Q_OBJECT + +public: + FileParser(QIODevice* device, QObject* parent = nullptr); + +signals: + void parsedURL(QUrl url); + void finished(); + +private: + QIODevice* mDevice; + QCborStreamReader mReader; + + void handleStream(); + +private slots: + void onRead(); +}; + +}; // namespace DragDrop diff --git a/src/dragdrop/CMakeLists.txt b/src/dragdrop/CMakeLists.txt index 785ee55..e516b73 100644 --- a/src/dragdrop/CMakeLists.txt +++ b/src/dragdrop/CMakeLists.txt @@ -10,6 +10,7 @@ endif() target_link_libraries(${BIN_NAME} PRIVATE Qt${QT_VERSION_MAJOR}::Core + common ) target_compile_definitions(${BIN_NAME} PRIVATE diff --git a/src/dragdrop/application.cc b/src/dragdrop/application.cc index eb1f8cd..bf5b400 100644 --- a/src/dragdrop/application.cc +++ b/src/dragdrop/application.cc @@ -15,6 +15,7 @@ Application::Application(int& argc, char** argv) : QCoreApplication(argc, argv) , mProg(this) , mOutput(stdout, QIODevice::WriteOnly) + , mParser(&mProg, this) { setApplicationName(QStringLiteral(PROGRAM_NAME)); setApplicationVersion(QStringLiteral(PROGRAM_VERSION)); @@ -55,8 +56,10 @@ auto Application::exec() -> int } mProg.setArguments(files); - connect(&mProg, &QProcess::readyReadStandardOutput, this, - &Application::dialogOutput); + connect(&mParser, &FileParser::parsedURL, this, + &Application::parserOutput); + connect(&mParser, &FileParser::finished, this, + &Application::parserFinished); connect(&mProg, QOverload::of(&QProcess::finished), this, &Application::dialogFinished); @@ -66,49 +69,17 @@ auto Application::exec() -> int return QCoreApplication::exec(); } -static auto decodeString(QCborStreamReader& reader) +void Application::parserOutput(QUrl url) { - QString str; - QCborStreamReader::StringResult r; - while ((r = reader.readString()).status == QCborStreamReader::Ok) { - str += r.data; - } - if (r.status == QCborStreamReader::Error) { - str.clear(); - } - return str; -} - -static void handleStream( - QCborStreamReader& reader, QTextStream& out, bool asUri, char term) -{ - QUrl uri; - switch (reader.type()) { - case QCborStreamReader::ByteArray: - case QCborStreamReader::String: - uri = decodeString(reader); - out << (asUri ? uri.url() : uri.path()) << term; - break; - case QCborStreamReader::Array: - reader.enterContainer(); - while (not reader.lastError() and reader.hasNext()) { - handleStream(reader, out, asUri, term); - } - if (not reader.lastError()) { - reader.leaveContainer(); - } - break; - default: - reader.next(); - } + auto u = args.isSet(QStringLiteral("uris")); + auto e = args.isSet(QStringLiteral("null")); + mOutput << (u ? url.url() : url.path()); + mOutput << (e ? '\0' : '\n'); + mOutput.flush(); } -void Application::dialogOutput() +void Application::parserFinished() { - mReader.setDevice(&mProg); - handleStream(mReader, mOutput, args.isSet(QStringLiteral("uris")), - args.isSet(QStringLiteral("null")) ? '\0' : '\n'); - mOutput.flush(); if (args.isSet(QStringLiteral("once"))) { QTimer::singleShot(300, &mProg, &QProcess::terminate); } diff --git a/src/dragdrop/application.hh b/src/dragdrop/application.hh index b131019..5abbb07 100644 --- a/src/dragdrop/application.hh +++ b/src/dragdrop/application.hh @@ -5,10 +5,11 @@ #pragma once -#include #include #include #include +#include +#include namespace DragDrop { @@ -21,13 +22,14 @@ public: auto exec() -> int; private: - QCborStreamReader mReader; QCommandLineParser args; QProcess mProg; QTextStream mOutput; + FileParser mParser; private slots: - void dialogOutput(); + void parserOutput(QUrl url); + void parserFinished(); void dialogFinished(int code, QProcess::ExitStatus exitStatus); }; From a50d6c0cba376b0fdfa565bbf444219106b1ca3f Mon Sep 17 00:00:00 2001 From: Fern Zapata Date: Mon, 13 Oct 2025 06:20:33 -0600 Subject: [PATCH 07/12] Ref: Add files without building a list first --- src/dragdrop-dialog/application.cc | 8 ++------ src/dragdrop-dialog/application.hh | 4 ++-- src/dragdrop-dialog/dragarea.cc | 19 ++++++++----------- src/dragdrop-dialog/dragarea.hh | 11 ++++++----- src/dragdrop-dialog/window.cc | 12 +++++++----- src/dragdrop-dialog/window.hh | 4 +++- 6 files changed, 28 insertions(+), 30 deletions(-) diff --git a/src/dragdrop-dialog/application.cc b/src/dragdrop-dialog/application.cc index 6b9d5a5..d663caf 100644 --- a/src/dragdrop-dialog/application.cc +++ b/src/dragdrop-dialog/application.cc @@ -4,7 +4,6 @@ * along with this file, see . */ #include "application.hh" -#include "window.hh" #include namespace DragDrop { @@ -16,14 +15,11 @@ Application::Application(int& argc, char** argv) auto Application::exec() -> int { - QList files; - for (const auto& name : arguments().mid(1)) { - files << QFileInfo(name); + mWindow.addFile(QFileInfo(name)); } - Window win(files); - win.show(); + mWindow.show(); return QApplication::exec(); } diff --git a/src/dragdrop-dialog/application.hh b/src/dragdrop-dialog/application.hh index 2b7dedb..07e96e1 100644 --- a/src/dragdrop-dialog/application.hh +++ b/src/dragdrop-dialog/application.hh @@ -5,8 +5,8 @@ #pragma once +#include "window.hh" #include -#include namespace DragDrop { @@ -19,7 +19,7 @@ public: auto exec() -> int; private: - QCommandLineParser parser; + Window mWindow; }; }; // namespace DragDrop diff --git a/src/dragdrop-dialog/dragarea.cc b/src/dragdrop-dialog/dragarea.cc index 7876466..5b2e8bd 100644 --- a/src/dragdrop-dialog/dragarea.cc +++ b/src/dragdrop-dialog/dragarea.cc @@ -13,7 +13,7 @@ namespace DragDrop { -DragArea::DragArea(const QList& files, QWidget* parent) +DragArea::DragArea(QWidget* parent) : QListWidget(parent) { setSelectionMode(DragArea::ExtendedSelection); @@ -22,29 +22,26 @@ DragArea::DragArea(const QList& files, QWidget* parent) setFrameShape(DragArea::NoFrame); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setSizeAdjustPolicy(DragArea::AdjustToContents); - - for (const auto& file : files) { - addFile(file); - } } -QSize DragArea::sizeHint() const +auto DragArea::sizeHint() const -> QSize { auto screen_h = screen()->size().height(); return QSize(sizeHintForColumn(0), - qMin(sizeHintForRow(0) * count(), screen_h / 3)); + qMin(sizeHintForRow(0) * count(), screen_h / 3)); } -QStringList DragArea::mimeTypes() const +auto DragArea::mimeTypes() const -> QStringList { return QStringList(QStringLiteral("text/uri-list")); } -QMimeData* DragArea::mimeData(const QListDAREA_REF items) const +auto DragArea::mimeData( + const QList DAREA_REF items) const -> QMimeData* { auto data = new QMimeData; QList urls; - for (const auto& item : items) { + for (auto const& item : items) { urls << item->data(Qt::UserRole).toUrl(); } data->setUrls(urls); @@ -57,7 +54,7 @@ void DragArea::startDrag(Qt::DropActions actions) emit filesSent(); } -void DragArea::addFile(const QFileInfo& file) +void DragArea::addFile(QFileInfo const& file) { static QMimeDatabase mime; auto type = mime.mimeTypeForFile(file); diff --git a/src/dragdrop-dialog/dragarea.hh b/src/dragdrop-dialog/dragarea.hh index 52d855d..3388776 100644 --- a/src/dragdrop-dialog/dragarea.hh +++ b/src/dragdrop-dialog/dragarea.hh @@ -5,8 +5,8 @@ #pragma once -#include #include +#include #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) #define DAREA_REF & @@ -21,15 +21,16 @@ class DragArea : public QListWidget Q_OBJECT public: - DragArea(const QList& files, QWidget* parent = nullptr); - QSize sizeHint() const override; + DragArea(QWidget* parent = nullptr); + auto sizeHint() const -> QSize override; void addFile(const QFileInfo& file); protected: void startDrag(Qt::DropActions actions) override; - QStringList mimeTypes() const override; - QMimeData* mimeData(const QListDAREA_REF items) const override; + auto mimeTypes() const -> QStringList override; + auto mimeData(const QList DAREA_REF items) const + -> QMimeData* override; signals: void filesSent(); diff --git a/src/dragdrop-dialog/window.cc b/src/dragdrop-dialog/window.cc index b1be594..a58316d 100644 --- a/src/dragdrop-dialog/window.cc +++ b/src/dragdrop-dialog/window.cc @@ -7,12 +7,12 @@ namespace DragDrop { -Window::Window(const QList& files, QWidget* parent) +Window::Window(QWidget* parent) : QDialog(parent) , mOutput(this) , mWriter(&mOutput) , mLayout(this) - , mDrag(files, this) + , mDrag(this) , mDrop(this) { setWindowTitle(tr("Drag and Drop")); @@ -26,10 +26,12 @@ Window::Window(const QList& files, QWidget* parent) mLayout.addWidget(&mDrop); mLayout.addWidget(&mDrag); +} - if (mDrag.count()) { - mLayout.setCurrentWidget(&mDrag); - } +void Window::addFile(QFileInfo const& file) +{ + mDrag.addFile(file); + mLayout.setCurrentWidget(&mDrag); } void Window::onFilesSent() diff --git a/src/dragdrop-dialog/window.hh b/src/dragdrop-dialog/window.hh index b3052a3..273d672 100644 --- a/src/dragdrop-dialog/window.hh +++ b/src/dragdrop-dialog/window.hh @@ -20,7 +20,9 @@ class Window : public QDialog Q_OBJECT public: - Window(const QList& files, QWidget* parent = nullptr); + Window(QWidget* parent = nullptr); + + void addFile(QFileInfo const& file); public slots: void onFilesRecv(const QList& files); From 349b23f49614b9c9f7f2227a4eb14654050cce31 Mon Sep 17 00:00:00 2001 From: Fern Zapata Date: Tue, 14 Oct 2025 21:05:44 -0600 Subject: [PATCH 08/12] Fix: Prevent quitting before the drop is handled Qt has no way to detect when the receiving app has handled a drop, so the 'once' mode can make the drop fail by quitting too fast, specially when the drop triggers something interactive, like how file managers open a menu asking whether to copy, link, move, etc. To work around this we make the dialog into its own process, which stays hidden but still running after the drop, independently of the command line program, which quits immediately. --- src/dragdrop-dialog/application.cc | 8 +++++++- src/dragdrop-dialog/application.hh | 2 ++ src/dragdrop-dialog/window.cc | 17 +++++++++++++++++ src/dragdrop-dialog/window.hh | 4 ++++ src/dragdrop/application.cc | 6 ++++-- src/dragdrop/application.hh | 4 ++-- src/dragdrop/process.cc | 28 ++++++++++++++++++++++++++++ src/dragdrop/process.hh | 21 +++++++++++++++++++++ 8 files changed, 85 insertions(+), 5 deletions(-) create mode 100644 src/dragdrop/process.cc create mode 100644 src/dragdrop/process.hh diff --git a/src/dragdrop-dialog/application.cc b/src/dragdrop-dialog/application.cc index d663caf..f89fa88 100644 --- a/src/dragdrop-dialog/application.cc +++ b/src/dragdrop-dialog/application.cc @@ -11,15 +11,21 @@ namespace DragDrop { Application::Application(int& argc, char** argv) : QApplication(argc, argv) { + mArgs.addOptions({ + {{"o", "once"}, ""}, + }); + mArgs.process(*this); } auto Application::exec() -> int { - for (const auto& name : arguments().mid(1)) { + for (auto const& name : mArgs.positionalArguments()) { mWindow.addFile(QFileInfo(name)); } + mWindow.setOnce(mArgs.isSet(QStringLiteral("once"))); mWindow.show(); + return QApplication::exec(); } diff --git a/src/dragdrop-dialog/application.hh b/src/dragdrop-dialog/application.hh index 07e96e1..6bf223f 100644 --- a/src/dragdrop-dialog/application.hh +++ b/src/dragdrop-dialog/application.hh @@ -7,6 +7,7 @@ #include "window.hh" #include +#include namespace DragDrop { @@ -19,6 +20,7 @@ public: auto exec() -> int; private: + QCommandLineParser mArgs; Window mWindow; }; diff --git a/src/dragdrop-dialog/window.cc b/src/dragdrop-dialog/window.cc index a58316d..4604fe3 100644 --- a/src/dragdrop-dialog/window.cc +++ b/src/dragdrop-dialog/window.cc @@ -4,6 +4,8 @@ * along with this file, see . */ #include "window.hh" +#include +#include namespace DragDrop { @@ -34,10 +36,16 @@ void Window::addFile(QFileInfo const& file) mLayout.setCurrentWidget(&mDrag); } +void Window::setOnce(bool once) +{ + mOnce = once; +} + void Window::onFilesSent() { mWriter.append(QCborSimpleType::Null); mOutput.flush(); + waitAndExit(); } void Window::onFilesRecv(const QList& files) @@ -48,6 +56,15 @@ void Window::onFilesRecv(const QList& files) } mWriter.endArray(); mOutput.flush(); + waitAndExit(); +} + +void Window::waitAndExit() +{ + if (mOnce) { + hide(); + QTimer::singleShot(5000, this, [] { QApplication::exit(); }); + } } }; // namespace DragDrop diff --git a/src/dragdrop-dialog/window.hh b/src/dragdrop-dialog/window.hh index 273d672..d577f1b 100644 --- a/src/dragdrop-dialog/window.hh +++ b/src/dragdrop-dialog/window.hh @@ -23,12 +23,16 @@ public: Window(QWidget* parent = nullptr); void addFile(QFileInfo const& file); + void setOnce(bool once); public slots: void onFilesRecv(const QList& files); void onFilesSent(); private: + void waitAndExit(); + + bool mOnce; QFile mOutput; QCborStreamWriter mWriter; QStackedLayout mLayout; diff --git a/src/dragdrop/application.cc b/src/dragdrop/application.cc index bf5b400..9c21d61 100644 --- a/src/dragdrop/application.cc +++ b/src/dragdrop/application.cc @@ -6,7 +6,6 @@ #include "application.hh" #include #include -#include #include namespace DragDrop { @@ -54,6 +53,9 @@ auto Application::exec() -> int files << name; } } + if (args.isSet(QStringLiteral("once"))) { + files << "-o"; + } mProg.setArguments(files); connect(&mParser, &FileParser::parsedURL, this, @@ -81,7 +83,7 @@ void Application::parserOutput(QUrl url) void Application::parserFinished() { if (args.isSet(QStringLiteral("once"))) { - QTimer::singleShot(300, &mProg, &QProcess::terminate); + exit(); } } diff --git a/src/dragdrop/application.hh b/src/dragdrop/application.hh index 5abbb07..279f1fb 100644 --- a/src/dragdrop/application.hh +++ b/src/dragdrop/application.hh @@ -5,9 +5,9 @@ #pragma once +#include "process.hh" #include #include -#include #include #include @@ -23,7 +23,7 @@ public: private: QCommandLineParser args; - QProcess mProg; + Process mProg; QTextStream mOutput; FileParser mParser; diff --git a/src/dragdrop/process.cc b/src/dragdrop/process.cc new file mode 100644 index 0000000..15a3077 --- /dev/null +++ b/src/dragdrop/process.cc @@ -0,0 +1,28 @@ +/* Copyright © 2025 Fern Zapata + * This file is under the terms of the GNU GPL version 3, or (at your + * option) any later version. If you didn't receive a copy of the GPL + * along with this file, see . */ + +#include "process.hh" + +namespace DragDrop { + +Process::Process(QObject* parent) + : QProcess(parent) +{ + setUnixProcessParameters(UnixProcessFlag::CloseFileDescriptors + | UnixProcessFlag::CreateNewSession); +} + +Process::~Process() +{ + // We want the dialog process to stay alive after quitting, + // but `startDetached` makes it impossible to capture output, + // so we just lie here so `QProcess` doesn't kill the dialog. + + if (state() == Running) { + setProcessState(NotRunning); + } +} + +} // namespace DragDrop diff --git a/src/dragdrop/process.hh b/src/dragdrop/process.hh new file mode 100644 index 0000000..82986a3 --- /dev/null +++ b/src/dragdrop/process.hh @@ -0,0 +1,21 @@ +/* Copyright © 2025 Fern Zapata + * This file is under the terms of the GNU GPL version 3, or (at your + * option) any later version. If you didn't receive a copy of the GPL + * along with this file, see . */ + +#pragma once + +#include + +namespace DragDrop { + +class Process : public QProcess +{ + Q_OBJECT + +public: + Process(QObject* parent = nullptr); + ~Process() override; +}; + +} // namespace DragDrop From 48aaf5d5c1e6cfb37dd345ad6e368374a0a047bd Mon Sep 17 00:00:00 2001 From: Fern Zapata Date: Tue, 14 Oct 2025 21:55:40 -0600 Subject: [PATCH 09/12] Fix: Extend the time before the dialog quits --- src/dragdrop-dialog/window.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dragdrop-dialog/window.cc b/src/dragdrop-dialog/window.cc index 4604fe3..8195a79 100644 --- a/src/dragdrop-dialog/window.cc +++ b/src/dragdrop-dialog/window.cc @@ -63,7 +63,7 @@ void Window::waitAndExit() { if (mOnce) { hide(); - QTimer::singleShot(5000, this, [] { QApplication::exit(); }); + QTimer::singleShot(30000, this, [] { QApplication::exit(); }); } } From 8992cf408ba96480757778c6b5484d4ec6406802 Mon Sep 17 00:00:00 2001 From: Fern Zapata Date: Tue, 14 Oct 2025 22:04:41 -0600 Subject: [PATCH 10/12] Ref: Remove redundant method --- src/dragdrop/application.cc | 7 +------ src/dragdrop/application.hh | 1 - 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/dragdrop/application.cc b/src/dragdrop/application.cc index 9c21d61..e27dd90 100644 --- a/src/dragdrop/application.cc +++ b/src/dragdrop/application.cc @@ -64,7 +64,7 @@ auto Application::exec() -> int &Application::parserFinished); connect(&mProg, QOverload::of(&QProcess::finished), - this, &Application::dialogFinished); + this, &Application::exit); mProg.start(); @@ -87,9 +87,4 @@ void Application::parserFinished() } } -void Application::dialogFinished(int code, QProcess::ExitStatus status) -{ - exit(); -} - }; // namespace DragDrop diff --git a/src/dragdrop/application.hh b/src/dragdrop/application.hh index 279f1fb..781f5a7 100644 --- a/src/dragdrop/application.hh +++ b/src/dragdrop/application.hh @@ -30,7 +30,6 @@ private: private slots: void parserOutput(QUrl url); void parserFinished(); - void dialogFinished(int code, QProcess::ExitStatus exitStatus); }; }; // namespace DragDrop From dc2609a74a0e4f3eac2cf79a23e52d78837dec66 Mon Sep 17 00:00:00 2001 From: Fern Zapata Date: Wed, 15 Oct 2025 00:20:27 -0600 Subject: [PATCH 11/12] Fix: Make the dialog process compatible with Qt5 --- src/dragdrop/process.cc | 10 ++++++++++ src/dragdrop/process.hh | 5 +++++ 2 files changed, 15 insertions(+) diff --git a/src/dragdrop/process.cc b/src/dragdrop/process.cc index 15a3077..5e6f63c 100644 --- a/src/dragdrop/process.cc +++ b/src/dragdrop/process.cc @@ -4,14 +4,17 @@ * along with this file, see . */ #include "process.hh" +#include namespace DragDrop { Process::Process(QObject* parent) : QProcess(parent) { +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) setUnixProcessParameters(UnixProcessFlag::CloseFileDescriptors | UnixProcessFlag::CreateNewSession); +#endif } Process::~Process() @@ -25,4 +28,11 @@ Process::~Process() } } +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) +void Process::setupChildProcess() +{ + ::setsid(); +} +#endif + } // namespace DragDrop diff --git a/src/dragdrop/process.hh b/src/dragdrop/process.hh index 82986a3..9ec2166 100644 --- a/src/dragdrop/process.hh +++ b/src/dragdrop/process.hh @@ -16,6 +16,11 @@ class Process : public QProcess public: Process(QObject* parent = nullptr); ~Process() override; + +protected: +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + void setupChildProcess() override; +#endif }; } // namespace DragDrop From 76b8fda9b08bf6db75ec8fc5b62ac245f4f904bf Mon Sep 17 00:00:00 2001 From: Fern Zapata Date: Wed, 15 Oct 2025 18:47:52 -0600 Subject: [PATCH 12/12] Fix: Close dialog when the CLI dies to a signal --- src/dragdrop/application.cc | 6 +++ src/dragdrop/application.hh | 2 + src/dragdrop/signalhandler.cc | 70 +++++++++++++++++++++++++++++++++++ src/dragdrop/signalhandler.hh | 38 +++++++++++++++++++ 4 files changed, 116 insertions(+) create mode 100644 src/dragdrop/signalhandler.cc create mode 100644 src/dragdrop/signalhandler.hh diff --git a/src/dragdrop/application.cc b/src/dragdrop/application.cc index e27dd90..0fb4507 100644 --- a/src/dragdrop/application.cc +++ b/src/dragdrop/application.cc @@ -7,6 +7,7 @@ #include #include #include +#include namespace DragDrop { @@ -15,10 +16,13 @@ Application::Application(int& argc, char** argv) , mProg(this) , mOutput(stdout, QIODevice::WriteOnly) , mParser(&mProg, this) + , mSignals(this) { setApplicationName(QStringLiteral(PROGRAM_NAME)); setApplicationVersion(QStringLiteral(PROGRAM_VERSION)); + mSignals.connectSignals(SIGINT, SIGTERM, SIGHUP); + args.setApplicationDescription( tr("Drag-and-drop file source for the terminal")); args.addHelpOption(); @@ -65,6 +69,8 @@ auto Application::exec() -> int connect(&mProg, QOverload::of(&QProcess::finished), this, &Application::exit); + connect(&mSignals, &SignalHandler::activated, &mProg, + &QProcess::terminate); mProg.start(); diff --git a/src/dragdrop/application.hh b/src/dragdrop/application.hh index 781f5a7..12bdd41 100644 --- a/src/dragdrop/application.hh +++ b/src/dragdrop/application.hh @@ -6,6 +6,7 @@ #pragma once #include "process.hh" +#include "signalhandler.hh" #include #include #include @@ -26,6 +27,7 @@ private: Process mProg; QTextStream mOutput; FileParser mParser; + SignalHandler mSignals; private slots: void parserOutput(QUrl url); diff --git a/src/dragdrop/signalhandler.cc b/src/dragdrop/signalhandler.cc new file mode 100644 index 0000000..a388027 --- /dev/null +++ b/src/dragdrop/signalhandler.cc @@ -0,0 +1,70 @@ +/* Copyright © 2025 Fern Zapata + * This file is under the terms of the GNU GPL version 3, or (at your + * option) any later version. If you didn't receive a copy of the GPL + * along with this file, see . */ + +#include "signalhandler.hh" +#include +#include +#include + +static auto sockfd = std::array {}; + +static void dispatchSignal(int sig) +{ + Q_ASSERT(sockfd[0]); + if (::write(sockfd[0], &sig, sizeof(sig)) == -1) { + qFatal("Could not write to the signal socket"); + } +} + +namespace DragDrop { + +SignalHandler::SignalHandler(QObject* parent) + : QObject(parent) +{ + if (not sockfd[0]) { + if (::socketpair(AF_UNIX, SOCK_STREAM, 0, sockfd.data())) { + qFatal("Could not create signal socket pair"); + } + } + + mNotifier + = new QSocketNotifier(sockfd[1], QSocketNotifier::Read, this); + connect(mNotifier, &QSocketNotifier::activated, this, + &SignalHandler::checkSocket); +} + +void SignalHandler::connectSignal(int sig) +{ + mSignals << sig; + + struct sigaction action = {}; + action.sa_handler = dispatchSignal; + action.sa_flags |= SA_RESTART; + sigemptyset(&action.sa_mask); + + if (::sigaction(sig, &action, nullptr)) { + qFatal("Could not set up signal `%d` callback", sig); + } +} + +void SignalHandler::checkSocket() +{ + Q_ASSERT(sockfd[1]); + mNotifier->setEnabled(false); + + auto sig = 0; + if (::read(sockfd[1], &sig, sizeof(sig)) != sizeof(sig)) { + qWarning("No bytes read on the signal socket"); + } + qDebug("Signal `%d` received", sig); + + if (mSignals.contains(sig)) { + emit activated(sig); + } + + mNotifier->setEnabled(true); +} + +} // namespace DragDrop diff --git a/src/dragdrop/signalhandler.hh b/src/dragdrop/signalhandler.hh new file mode 100644 index 0000000..b9d6a5b --- /dev/null +++ b/src/dragdrop/signalhandler.hh @@ -0,0 +1,38 @@ +/* Copyright © 2025 Fern Zapata + * This file is under the terms of the GNU GPL version 3, or (at your + * option) any later version. If you didn't receive a copy of the GPL + * along with this file, see . */ + +#pragma once + +#include +#include + +namespace DragDrop { + +class SignalHandler : public QObject +{ + Q_OBJECT + +public: + SignalHandler(QObject* parent = nullptr); + + void connectSignal(int sig); + + template void connectSignals(Args... sigs) + { + (connectSignal(sigs), ...); + } + +signals: + void activated(int signal); + +private: + QSet mSignals; + QSocketNotifier* mNotifier; + +private slots: + void checkSocket(); +}; + +} // namespace DragDrop