diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index baa89b8..6af99ec 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,45 +1,3 @@ -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(common) +add_subdirectory(dragdrop) +add_subdirectory(dragdrop-dialog) diff --git a/src/application.cc b/src/application.cc deleted file mode 100644 index f3e2f60..0000000 --- a/src/application.cc +++ /dev/null @@ -1,62 +0,0 @@ -/* 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/. */ - -#include "application.hh" -#include "window.hh" -#include - -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() -{ - 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; - } - } - - 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); - win.show(); - return QApplication::exec(); -} - -}; // namespace DragDrop diff --git a/src/application.hh b/src/application.hh deleted file mode 100644 index a4f8f14..0000000 --- a/src/application.hh +++ /dev/null @@ -1,28 +0,0 @@ -/* 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/. */ - -#ifndef DRAGDROP_APPLICATION_HH -#define DRAGDROP_APPLICATION_HH - -#include -#include - -namespace DragDrop { - -class Application : public QApplication -{ - Q_OBJECT - -public: - Application(int& argc, char** argv); - int exec(); - -private: - QCommandLineParser parser; -}; - -}; // namespace DragDrop - -#endif // DRAGDROP_APPLICATION_HH 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-dialog/CMakeLists.txt b/src/dragdrop-dialog/CMakeLists.txt new file mode 100644 index 0000000..982ef48 --- /dev/null +++ b/src/dragdrop-dialog/CMakeLists.txt @@ -0,0 +1,31 @@ +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}::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} + DESTINATION ${CMAKE_INSTALL_LIBEXECDIR} +) + +if(QT_VERSION_MAJOR EQUAL 6) + qt_finalize_executable(${BIN_NAME}) +endif() diff --git a/src/dragdrop-dialog/application.cc b/src/dragdrop-dialog/application.cc new file mode 100644 index 0000000..f89fa88 --- /dev/null +++ b/src/dragdrop-dialog/application.cc @@ -0,0 +1,32 @@ +/* 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 + +namespace DragDrop { + +Application::Application(int& argc, char** argv) + : QApplication(argc, argv) +{ + mArgs.addOptions({ + {{"o", "once"}, ""}, + }); + mArgs.process(*this); +} + +auto Application::exec() -> int +{ + for (auto const& name : mArgs.positionalArguments()) { + mWindow.addFile(QFileInfo(name)); + } + + mWindow.setOnce(mArgs.isSet(QStringLiteral("once"))); + mWindow.show(); + + return QApplication::exec(); +} + +}; // namespace DragDrop diff --git a/src/dragdrop-dialog/application.hh b/src/dragdrop-dialog/application.hh new file mode 100644 index 0000000..6bf223f --- /dev/null +++ b/src/dragdrop-dialog/application.hh @@ -0,0 +1,27 @@ +/* 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 . */ + +#pragma once + +#include "window.hh" +#include +#include + +namespace DragDrop { + +class Application : public QApplication +{ + Q_OBJECT + +public: + Application(int& argc, char** argv); + auto exec() -> int; + +private: + QCommandLineParser mArgs; + Window mWindow; +}; + +}; // namespace DragDrop diff --git a/src/dragarea.cc b/src/dragdrop-dialog/dragarea.cc similarity index 76% rename from src/dragarea.cc rename to src/dragdrop-dialog/dragarea.cc index 7876466..5b2e8bd 100644 --- a/src/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/dragarea.hh b/src/dragdrop-dialog/dragarea.hh similarity index 73% rename from src/dragarea.hh rename to src/dragdrop-dialog/dragarea.hh index 52d855d..3388776 100644 --- a/src/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/droparea.cc b/src/dragdrop-dialog/droparea.cc similarity index 62% rename from src/droparea.cc rename to src/dragdrop-dialog/droparea.cc index 5e97e30..51eb207 100644 --- a/src/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 new file mode 100644 index 0000000..c9efa42 --- /dev/null +++ b/src/dragdrop-dialog/droparea.hh @@ -0,0 +1,28 @@ +/* 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 . */ + +#pragma once + +#include +#include + +namespace DragDrop { + +class DropArea : public QLabel +{ + Q_OBJECT + +public: + DropArea(QWidget* parent = nullptr); + +protected: + void dragEnterEvent(QDragEnterEvent* event) override; + void dropEvent(QDropEvent* event) override; + +signals: + void filesRecv(const QList& urls); +}; + +}; // namespace DragDrop 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/dragdrop-dialog/window.cc b/src/dragdrop-dialog/window.cc new file mode 100644 index 0000000..8195a79 --- /dev/null +++ b/src/dragdrop-dialog/window.cc @@ -0,0 +1,70 @@ +/* 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 +#include + +namespace DragDrop { + +Window::Window(QWidget* parent) + : QDialog(parent) + , mOutput(this) + , mWriter(&mOutput) + , mLayout(this) + , mDrag(this) + , mDrop(this) +{ + setWindowTitle(tr("Drag and Drop")); + + if (not mOutput.open(stdout, QIODevice::WriteOnly)) { + close(); + } + + connect(&mDrop, &DropArea::filesRecv, this, &Window::onFilesRecv); + connect(&mDrag, &DragArea::filesSent, this, &Window::onFilesSent); + + mLayout.addWidget(&mDrop); + mLayout.addWidget(&mDrag); +} + +void Window::addFile(QFileInfo const& file) +{ + mDrag.addFile(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) +{ + mWriter.startArray(files.length()); + for (const auto& f : files) { + mWriter.append(f.url()); + } + mWriter.endArray(); + mOutput.flush(); + waitAndExit(); +} + +void Window::waitAndExit() +{ + if (mOnce) { + hide(); + QTimer::singleShot(30000, this, [] { QApplication::exit(); }); + } +} + +}; // namespace DragDrop diff --git a/src/dragdrop-dialog/window.hh b/src/dragdrop-dialog/window.hh new file mode 100644 index 0000000..d577f1b --- /dev/null +++ b/src/dragdrop-dialog/window.hh @@ -0,0 +1,43 @@ +/* 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 . */ + +#pragma once + +#include "dragarea.hh" +#include "droparea.hh" +#include +#include +#include +#include +#include + +namespace DragDrop { + +class Window : public QDialog +{ + Q_OBJECT + +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; + DragArea mDrag; + DropArea mDrop; +}; + +} // namespace DragDrop diff --git a/src/dragdrop/CMakeLists.txt b/src/dragdrop/CMakeLists.txt new file mode 100644 index 0000000..e516b73 --- /dev/null +++ b/src/dragdrop/CMakeLists.txt @@ -0,0 +1,31 @@ +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 + common +) +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..0fb4507 --- /dev/null +++ b/src/dragdrop/application.cc @@ -0,0 +1,96 @@ +/* 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) + , 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(); + 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 +{ + 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; + } + } + if (args.isSet(QStringLiteral("once"))) { + files << "-o"; + } + mProg.setArguments(files); + + connect(&mParser, &FileParser::parsedURL, this, + &Application::parserOutput); + connect(&mParser, &FileParser::finished, this, + &Application::parserFinished); + connect(&mProg, + QOverload::of(&QProcess::finished), + this, &Application::exit); + connect(&mSignals, &SignalHandler::activated, &mProg, + &QProcess::terminate); + + mProg.start(); + + return QCoreApplication::exec(); +} + +void Application::parserOutput(QUrl url) +{ + 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::parserFinished() +{ + if (args.isSet(QStringLiteral("once"))) { + exit(); + } +} + +}; // namespace DragDrop diff --git a/src/dragdrop/application.hh b/src/dragdrop/application.hh new file mode 100644 index 0000000..12bdd41 --- /dev/null +++ b/src/dragdrop/application.hh @@ -0,0 +1,37 @@ +/* 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 "process.hh" +#include "signalhandler.hh" +#include +#include +#include +#include + +namespace DragDrop { + +class Application : public QCoreApplication +{ + Q_OBJECT + +public: + Application(int& argc, char** argv); + auto exec() -> int; + +private: + QCommandLineParser args; + Process mProg; + QTextStream mOutput; + FileParser mParser; + SignalHandler mSignals; + +private slots: + void parserOutput(QUrl url); + void parserFinished(); +}; + +}; // 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(); +} diff --git a/src/dragdrop/process.cc b/src/dragdrop/process.cc new file mode 100644 index 0000000..5e6f63c --- /dev/null +++ b/src/dragdrop/process.cc @@ -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 . */ + +#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() +{ + // 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); + } +} + +#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 new file mode 100644 index 0000000..9ec2166 --- /dev/null +++ b/src/dragdrop/process.hh @@ -0,0 +1,26 @@ +/* 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; + +protected: +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + void setupChildProcess() override; +#endif +}; + +} // namespace DragDrop 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 diff --git a/src/droparea.hh b/src/droparea.hh deleted file mode 100644 index 44ac6e3..0000000 --- a/src/droparea.hh +++ /dev/null @@ -1,31 +0,0 @@ -/* 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/. */ - -#ifndef DRAGDROP_DROPAREA_HH -#define DRAGDROP_DROPAREA_HH - -#include -#include - -namespace DragDrop { - -class DropArea : public QLabel -{ - Q_OBJECT - -public: - DropArea(QWidget* parent = nullptr); - -protected: - void dragEnterEvent(QDragEnterEvent* event) override; - void dropEvent(QDropEvent* event) override; - -signals: - void filesReceived(const QList& urls); -}; - -}; // namespace DragDrop - -#endif // DRAGDROP_DROPAREA_HH diff --git a/src/window.cc b/src/window.cc deleted file mode 100644 index 0b1589b..0000000 --- a/src/window.cc +++ /dev/null @@ -1,59 +0,0 @@ -/* 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/. */ - -#include "window.hh" -#include "dragarea.hh" -#include "droparea.hh" -#include -#include -#include - -namespace DragDrop { - -Window::Window(const QList& files, const Options& opts, - QWidget* parent) - : QDialog(parent) - , m_opts(opts) - , m_term(opts & Option::Null ? '\0' : '\n') -{ - setWindowTitle(tr("Drag and Drop")); - - 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); - } - layout->addWidget(area); -} - -void Window::onFilesSent() -{ - if (m_opts & Option::Once) { - QTimer::singleShot(500, this, &Window::close); - } -} - -void Window::onFilesReceived(const QList& files) -{ - QTextStream out(stdout); - for (const auto& f : files) { - out << (m_opts & Option::URIs ? f.url() : f.path()) - << m_term - << Qt::flush; - } - - if (m_opts & Option::Once) { - close(); - } -} - -}; // namespace DragDrop diff --git a/src/window.hh b/src/window.hh deleted file mode 100644 index f05fd4b..0000000 --- a/src/window.hh +++ /dev/null @@ -1,45 +0,0 @@ -/* 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/. */ - -#ifndef DRAGDROP_WINDOW_HH -#define DRAGDROP_WINDOW_HH - -#include -#include -#include - -namespace DragDrop { - -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); - -public slots: - void onFilesReceived(const QList& files); - void onFilesSent(); - -private: - const Options m_opts; - const char m_term; -}; - -Q_DECLARE_OPERATORS_FOR_FLAGS(Window::Options) - -}; // namespace DragDrop - -#endif // DRAGDROP_WINDOW_HH