From 5bef759fae099cce145076292dc58ec2e77cf3d8 Mon Sep 17 00:00:00 2001 From: Kibi Kelburton Date: Thu, 13 Aug 2026 20:08:24 +0200 Subject: [PATCH] add purposeplugin for kde --- .gitignore | 3 ++ README.md | 5 +++ install.sh | 39 ++++++++++++++++++- purpose_plugin/CMakeLists.txt | 24 ++++++++++++ purpose_plugin/build_plugin.sh | 69 +++++++++++++++++++++++++++++++++ purpose_plugin/f0ckmplugin.cpp | 65 +++++++++++++++++++++++++++++++ purpose_plugin/f0ckmplugin.json | 30 ++++++++++++++ uninstall.sh | 12 +++++- 8 files changed, 244 insertions(+), 3 deletions(-) create mode 100644 purpose_plugin/CMakeLists.txt create mode 100755 purpose_plugin/build_plugin.sh create mode 100644 purpose_plugin/f0ckmplugin.cpp create mode 100644 purpose_plugin/f0ckmplugin.json diff --git a/.gitignore b/.gitignore index 4c49bd7..a2a5e42 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ .env +__pycache__/ +*.so +*.moc diff --git a/README.md b/README.md index 1553854..dec5d32 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ A modern, high-contrast **KDE Plasma System Tray Uploader** and desktop integrat - **System Tray App with Live Progress**: Dynamic system tray icon displaying live upload percentage (`0`–`99`%). - **Single-Instance IPC Architecture**: 100% Python-based daemon (`PySide6`). Multiple invocations (hotkeys, Dolphin context menu) delegate directly to the running tray instance without spawning duplicates. - **Dolphin Context Menu Integration**: Right-click any file in Dolphin -> **Actions** -> **Upload to f0ckm**. +- **Gwenview / KDE Share Menu Integration**: Native KDE Purpose plugin adding **Upload to f0ckm** directly to the **Share** menu in Gwenview, Spectacle, Okular, and other KDE apps. - **Spectacle Screenshot Capture**: Region capture integration via hotkeys or menu trigger (`f0ckm-uploader-gui --spectacle`). - **Browser Extension Addon (ShareX Style)**: Right-click any image, video, link, or URL on the web in Chrome, Firefox, Brave, or Edge -> **Upload with f0ckm**. - **Custom Protocol Scheme (`f0ckm://`)**: Seamless URL handling (`f0ckm://upload?url=...`) delegating background remote file downloading & live tray progress directly to the system tray app. @@ -99,6 +100,10 @@ To bind a keyboard shortcut to capture a screen region and upload it immediately - Right-click any file -> **Actions** -> **Upload to f0ckm**. +### Gwenview & KDE Apps (Share Menu) + +- Click the **Share** button in Gwenview, Spectacle, or Okular -> select **Upload to f0ckm**. + ### Command Line Interface (CLI) ```bash diff --git a/install.sh b/install.sh index 90a7380..c102237 100755 --- a/install.sh +++ b/install.sh @@ -128,7 +128,41 @@ chmod +x "$SERVICE_MENU_DIR/f0ckm-uploader-servicemenu.desktop" sed "s|__EXEC_PATH__|$INSTALL_DIR/f0ckm-uploader-gui --upload|g" f0ckm-uploader-servicemenu.desktop > "$LEGACY_SERVICE_MENU_DIR/f0ckm-uploader-servicemenu.desktop" chmod +x "$LEGACY_SERVICE_MENU_DIR/f0ckm-uploader-servicemenu.desktop" -# 5. Migrate config to config.json if not present +# 5. Build and install KDE Purpose plugin (for Gwenview / KDE Share menu) +if [ -f "purpose_plugin/build_plugin.sh" ]; then + echo "Building KDE Purpose plugin for Gwenview Share Menu..." + bash purpose_plugin/build_plugin.sh purpose_plugin &> /dev/null || true + if [ -f "purpose_plugin/f0ckmpurposeplugin.so" ]; then + PURPOSE_USER_DIR64="$HOME/.local/lib64/qt6/plugins/kf6/purpose" + PURPOSE_USER_DIR32="$HOME/.local/lib/qt6/plugins/kf6/purpose" + mkdir -p "$PURPOSE_USER_DIR64" "$PURPOSE_USER_DIR32" + cp "purpose_plugin/f0ckmpurposeplugin.so" "$PURPOSE_USER_DIR64/f0ckmpurposeplugin.so" + cp "purpose_plugin/f0ckmpurposeplugin.so" "$PURPOSE_USER_DIR32/f0ckmpurposeplugin.so" + + if [ -w "/usr/lib64/qt6/plugins/kf6/purpose" ]; then + cp "purpose_plugin/f0ckmpurposeplugin.so" "/usr/lib64/qt6/plugins/kf6/purpose/f0ckmpurposeplugin.so" &>/dev/null || true + elif sudo -n true 2>/dev/null; then + sudo cp "purpose_plugin/f0ckmpurposeplugin.so" "/usr/lib64/qt6/plugins/kf6/purpose/f0ckmpurposeplugin.so" &>/dev/null || true + fi + + ENV_DIR="$HOME/.config/environment.d" + mkdir -p "$ENV_DIR" + cat << 'ENVCONF' > "$ENV_DIR/10-qt-plugins.conf" +QT_PLUGIN_PATH="${HOME}/.local/lib64/qt6/plugins:${HOME}/.local/lib/qt6/plugins:/usr/lib64/qt6/plugins:/usr/lib/qt6/plugins" +ENVCONF + + PLASMA_ENV_DIR="$HOME/.config/plasma-workspace/env" + mkdir -p "$PLASMA_ENV_DIR" + cat << 'PLASMAENV' > "$PLASMA_ENV_DIR/10-qt-plugins.sh" +export QT_PLUGIN_PATH="${HOME}/.local/lib64/qt6/plugins:${HOME}/.local/lib/qt6/plugins:${QT_PLUGIN_PATH}:/usr/lib64/qt6/plugins:/usr/lib/qt6/plugins" +PLASMAENV + chmod +x "$PLASMA_ENV_DIR/10-qt-plugins.sh" + + echo " -> Gwenview / KDE Share Menu plugin installed (Upload to f0ckm)" + fi +fi + +# 6. Migrate config to config.json if not present CONFIG_DIR="$HOME/.config/f0ckm-uploader" CONFIG_FILE="$CONFIG_DIR/config.json" if [ ! -f "$CONFIG_FILE" ]; then @@ -149,7 +183,7 @@ EOF echo "Initial configuration created at $CONFIG_FILE" fi -# 6. Ensure autostart state matches config.json +# 7. Ensure autostart state matches config.json python3 -c ' import json, os try: @@ -192,6 +226,7 @@ echo " f0ckm Uploader Installation Complete!" echo "========================================================" echo " -> Executable installed: $INSTALL_DIR/f0ckm-uploader-gui" echo " -> Dolphin context menu registered (Right-click file -> Actions -> Upload to f0ckm)" +echo " -> Gwenview / KDE Share menu integration enabled (Upload to f0ckm)" echo " -> Spectacle screenshot export integration enabled" echo " -> Browser extension helper API ready on http://127.0.0.1:18739" echo "========================================================" diff --git a/purpose_plugin/CMakeLists.txt b/purpose_plugin/CMakeLists.txt new file mode 100644 index 0000000..99320e3 --- /dev/null +++ b/purpose_plugin/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.16) +project(f0ckmpurposeplugin) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_AUTOMOC ON) + +find_package(ECM REQUIRED NO_MODULE) +set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) + +find_package(Qt6 REQUIRED COMPONENTS Core Gui) +find_package(KF6 REQUIRED COMPONENTS Purpose CoreAddons) + +kcoreaddons_add_plugin(f0ckmpurposeplugin + SOURCES f0ckmplugin.cpp + INSTALL_NAMESPACE "kf6/purpose" +) + +target_link_libraries(f0ckmpurposeplugin + PRIVATE + Qt6::Core + KF6::Purpose + KF6::CoreAddons +) diff --git a/purpose_plugin/build_plugin.sh b/purpose_plugin/build_plugin.sh new file mode 100755 index 0000000..6c1ee1c --- /dev/null +++ b/purpose_plugin/build_plugin.sh @@ -0,0 +1,69 @@ +#!/bin/bash +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +OUTPUT_TARGET="${1:-$SCRIPT_DIR}" +mkdir -p "$OUTPUT_TARGET" +OUTPUT_DIR="$(cd "$OUTPUT_TARGET" && pwd)" + +MOC_BIN="" +for candidate in /usr/libexec/qt6/moc /usr/lib64/qt6/libexec/moc moc-qt6 moc; do + if command -v "$candidate" &>/dev/null; then + MOC_BIN="$candidate" + break + elif [ -x "$candidate" ]; then + MOC_BIN="$candidate" + break + fi +done + +if [ -z "$MOC_BIN" ]; then + echo "Warning: Qt6 moc compiler not found. Skipping Purpose plugin compilation." + exit 0 +fi + +# Detect header search paths +INC_ARGS=() +if [ -d "/usr/include/KF6/Purpose" ]; then + INC_ARGS+=("-I/usr/include/KF6/Purpose" "-I/usr/include/KF6/KCoreAddons" "-I/usr/include/KF6") +elif [ -d "/tmp/purpose_devel/usr/include/KF6/Purpose" ]; then + INC_ARGS+=("-I/tmp/purpose_devel/usr/include/KF6/Purpose" "-I/tmp/purpose_devel/usr/include/KF6/KCoreAddons" "-I/tmp/purpose_devel/usr/include/KF6") +fi + +if [ -d "/usr/include/qt6" ]; then + INC_ARGS+=("-I/usr/include/qt6" "-I/usr/include/qt6/QtCore" "-I/usr/include/qt6/QtGui") +fi + +if [ ${#INC_ARGS[@]} -eq 0 ]; then + echo "Warning: KF6 Purpose headers not found. Skipping Purpose plugin compilation." + exit 0 +fi + +LIB_ARGS=() +if [ -f "/usr/lib64/libKF6Purpose.so.6" ]; then + LIB_ARGS+=("/usr/lib64/libKF6Purpose.so.6") +elif [ -f "/usr/lib/libKF6Purpose.so.6" ]; then + LIB_ARGS+=("/usr/lib/libKF6Purpose.so.6") +fi + +if [ -f "/usr/lib64/libKF6CoreAddons.so.6" ]; then + LIB_ARGS+=("/usr/lib64/libKF6CoreAddons.so.6") +elif [ -f "/usr/lib/libKF6CoreAddons.so.6" ]; then + LIB_ARGS+=("/usr/lib/libKF6CoreAddons.so.6") +fi + +BUILD_DIR="$(mktemp -d)" +trap 'rm -rf "$BUILD_DIR"' EXIT + +cp "$SCRIPT_DIR/f0ckmplugin.cpp" "$SCRIPT_DIR/f0ckmplugin.json" "$BUILD_DIR/" + +pushd "$BUILD_DIR" >/dev/null +"$MOC_BIN" "${INC_ARGS[@]}" f0ckmplugin.cpp -o f0ckmplugin.moc +g++ -shared -fPIC -O2 f0ckmplugin.cpp \ + "${INC_ARGS[@]}" \ + "${LIB_ARGS[@]}" \ + -lQt6Core \ + -o "$OUTPUT_DIR/f0ckmpurposeplugin.so" +popd >/dev/null + +echo "Successfully built f0ckmpurposeplugin.so at $OUTPUT_DIR/f0ckmpurposeplugin.so" diff --git a/purpose_plugin/f0ckmplugin.cpp b/purpose_plugin/f0ckmplugin.cpp new file mode 100644 index 0000000..4cdb7c4 --- /dev/null +++ b/purpose_plugin/f0ckmplugin.cpp @@ -0,0 +1,65 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class F0ckmJob : public Purpose::Job +{ + Q_OBJECT +public: + explicit F0ckmJob(QObject *parent = nullptr) : Purpose::Job(parent) {} + + void start() override { + QJsonObject d = data(); + QJsonArray urls = d.value(QStringLiteral("urls")).toArray(); + QStringList args; + args << QStringLiteral("--upload"); + + for (const QJsonValue &val : urls) { + QUrl url(val.toString()); + if (url.isLocalFile()) { + args << url.toLocalFile(); + } else { + args << val.toString(); + } + } + + QString execPath = QStandardPaths::findExecutable(QStringLiteral("f0ckm-uploader-gui")); + if (execPath.isEmpty()) { + execPath = QDir::homePath() + QStringLiteral("/.local/bin/f0ckm-uploader-gui"); + } + + bool ok = QProcess::startDetached(execPath, args); + if (!ok) { + setError(1); + setErrorText(QStringLiteral("Failed to launch f0ckm-uploader-gui")); + } + + emitResult(); + } +}; + +class F0ckmPlugin : public Purpose::PluginBase +{ + Q_OBJECT +public: + explicit F0ckmPlugin(QObject *parent = nullptr, const QVariantList &args = QVariantList()) + : Purpose::PluginBase(parent) + { + Q_UNUSED(args); + } + + Purpose::Job *createJob() const override { + return new F0ckmJob(); + } +}; + +K_PLUGIN_CLASS_WITH_JSON(F0ckmPlugin, "f0ckmplugin.json") + +#include "f0ckmplugin.moc" diff --git a/purpose_plugin/f0ckmplugin.json b/purpose_plugin/f0ckmplugin.json new file mode 100644 index 0000000..d4b4cb5 --- /dev/null +++ b/purpose_plugin/f0ckmplugin.json @@ -0,0 +1,30 @@ +{ + "KPlugin": { + "Name": "Upload to f0ckm", + "Description": "Upload images or videos to f0ckm", + "Icon": "f0ckm-uploader", + "Category": "Utilities" + }, + "X-Purpose-PluginTypes": [ + "Export" + ], + "X-Purpose-Constraints": [ + [ + "mimeType:image/jpeg", + "mimeType:image/png", + "mimeType:image/gif", + "mimeType:image/webp", + "mimeType:image/bmp", + "mimeType:image/svg+xml", + "mimeType:image/tiff", + "mimeType:image/x-xcf", + "mimeType:video/mp4", + "mimeType:video/webm", + "mimeType:video/x-matroska", + "mimeType:video/quicktime", + "mimeType:video/x-msvideo", + "mimeType:video/mpeg" + ] + ], + "X-Purpose-ActionDisplay": "Upload to f0ckm" +} diff --git a/uninstall.sh b/uninstall.sh index 2f7efcc..24f49fa 100644 --- a/uninstall.sh +++ b/uninstall.sh @@ -22,7 +22,17 @@ rm -f "$SERVICE_MENU_DIR/kde-uploader-servicemenu.desktop" "$SERVICE_MENU_DIR/f0 rm -f "$LEGACY_SERVICE_MENU_DIR/kde-uploader-servicemenu.desktop" "$LEGACY_SERVICE_MENU_DIR/f0ckm-uploader-servicemenu.desktop" echo "Removed service menu entries" -# 4. Remove autostart entry +# 4. Remove KDE Purpose plugin +rm -f "$HOME/.local/lib64/qt6/plugins/kf6/purpose/f0ckmpurposeplugin.so" "$HOME/.local/lib/qt6/plugins/kf6/purpose/f0ckmpurposeplugin.so" +rm -f "$HOME/.config/environment.d/10-qt-plugins.conf" "$HOME/.config/plasma-workspace/env/10-qt-plugins.sh" +if [ -w "/usr/lib64/qt6/plugins/kf6/purpose/f0ckmpurposeplugin.so" ]; then + rm -f "/usr/lib64/qt6/plugins/kf6/purpose/f0ckmpurposeplugin.so" &>/dev/null || true +elif sudo -n true 2>/dev/null; then + sudo rm -f "/usr/lib64/qt6/plugins/kf6/purpose/f0ckmpurposeplugin.so" &>/dev/null || true +fi +echo "Removed KDE Purpose plugin (Gwenview Share menu)" + +# 5. Remove autostart entry rm -f "$HOME/.config/autostart/kde-uploader-gui.desktop" "$HOME/.config/autostart/f0ckm-uploader-gui.desktop" echo "Removed autostart entries"