diff --git a/.reuse/dep5 b/.reuse/dep5 index 0aaecdc0f7..26d26221da 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -29,7 +29,7 @@ Copyright: None License: CC0-1.0 # ignore files -Files: src/*.json src/*.xml src/*.policy src/*/pinyin.dict src/*.js src/*.ini src/*.qss src/*.theme src/*/templates/* src/*.psd src/*/com.deepin.filemanager.daemon.conf src/dfm-base/qrc/configure/*.cpp +Files: src/*.json src/*.xml src/*.policy src/*/pinyin.dict src/*.js src/*.ini src/*.qss src/*.theme src/*/templates/* src/*.psd src/*/com.deepin.filemanager.daemon.conf src/dfm-base/qrc/configure/*.cpp examples/* Copyright: None License: CC0-1.0 diff --git a/examples/dfm-extension-example/CMakeLists.txt b/examples/dfm-extension-example/CMakeLists.txt new file mode 100644 index 0000000000..dfe0ffb52b --- /dev/null +++ b/examples/dfm-extension-example/CMakeLists.txt @@ -0,0 +1,37 @@ +cmake_minimum_required(VERSION 2.8) + +# 设置项目名称 +project(dfm-extension-example) + +set(CMAKE_CXX_STANDARD 17) + +# 只需要依赖 dfm-extension +find_package(dfm-extension REQUIRED) + + +# 扩展插件源码 +file(GLOB_RECURSE SRCS CONFIGURE_DEPENDS + "./*.h" + "./*.cpp" +) + +# 生成共享库 +add_library(${PROJECT_NAME} SHARED ${SRCS}) + +# 链接 dfm-extension +target_link_libraries(${PROJECT_NAME} + PUBLIC ${dfm-extension_LIBRARIES} + ) + +# 安裝配置 +include(GNUInstallDirs) +if(NOT DEFINED LIB_INSTALL_DIR) + set(LIB_INSTALL_DIR ${CMAKE_INSTALL_FULL_LIBDIR}) +endif() + +if(NOT DEFINED DFM_EXT_PLUGIN_DIR) + set(DFM_EXT_PLUGIN_DIR ${LIB_INSTALL_DIR}/dde-file-manager/plugins/extensions) +endif() + +# 安裝插件 +install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${DFM_EXT_PLUGIN_DIR}) diff --git a/examples/dfm-extension-example/README.md b/examples/dfm-extension-example/README.md new file mode 100644 index 0000000000..ee606627d7 --- /dev/null +++ b/examples/dfm-extension-example/README.md @@ -0,0 +1,19 @@ +# dfm-extension-example + +一个基于文件管理器(dde-file-manager)的扩展开发库(dfm-extension)的示例程序。 + +## 依赖 + +```shell +$ sudo apt install libdfm-extension-dev +``` + +## 安装 + +```shell +$ cmake -B build -DCMAKE_INSTALL_PREFIX=/usr +$ cmake --build build +$ sudo cmake --build build --target install +``` + +安装后,重启生效。 \ No newline at end of file diff --git a/examples/dfm-extension-example/dfm-extension-example.cpp b/examples/dfm-extension-example/dfm-extension-example.cpp new file mode 100644 index 0000000000..b218bfddda --- /dev/null +++ b/examples/dfm-extension-example/dfm-extension-example.cpp @@ -0,0 +1,31 @@ +#include + +#include "mymenuplugin.h" +#include "myemblemiconplugin.h" + +// 右键菜单的扩展 +static DFMEXT::DFMExtMenuPlugin *myMenu { nullptr }; +// 角标的扩展 +static DFMEXT::DFMExtEmblemIconPlugin *myEmblemIcon { nullptr }; + +extern "C" void dfm_extension_initiliaze() +{ + myMenu = new Exapmle::MyMenuPlugin; + myEmblemIcon = new Exapmle::MyEmblemIconPlugin; +} + +extern "C" void dfm_extension_shutdown() +{ + delete myMenu; + delete myEmblemIcon; +} + +extern "C" DFMEXT::DFMExtMenuPlugin *dfm_extension_menu() +{ + return myMenu; +} + +extern "C" DFMEXT::DFMExtEmblemIconPlugin *dfm_extension_emblem() +{ + return myEmblemIcon; +} diff --git a/examples/dfm-extension-example/myemblemiconplugin.cpp b/examples/dfm-extension-example/myemblemiconplugin.cpp new file mode 100644 index 0000000000..8dc0c45194 --- /dev/null +++ b/examples/dfm-extension-example/myemblemiconplugin.cpp @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "myemblemiconplugin.h" + +#include +#include +#include + +namespace Exapmle { + +USING_DFMEXT_NAMESPACE + +MyEmblemIconPlugin::MyEmblemIconPlugin() + : DFMEXT::DFMExtEmblemIconPlugin() +{ + registerLocationEmblemIcons([this](const std::string &filePath, int systemIconCount) { + return locationEmblemIcons(filePath, systemIconCount); + }); +} + +MyEmblemIconPlugin::~MyEmblemIconPlugin() +{ +} + +DFMExtEmblem MyEmblemIconPlugin::locationEmblemIcons(const std::string &filePath, int systemIconCount) const +{ + DFMExtEmblem emblem; + + // 一个文件的角标最多只有 4 个,当系统角标的数量已经达到 4 个时则无法添加扩展角标了 + // 此外,如果扩展角标添加的位置被系统角标占用,那么扩展角标将无法被显示 + if (systemIconCount >= 4) + return emblem; + + // 从文件扩展属性中获取右键菜单扩展添加的角标属性 + char buffer[FILENAME_MAX] { 0 }; + ssize_t result = getxattr(filePath.c_str(), "user.icon", buffer, FILENAME_MAX); + if (result == -1) + return emblem; + + // 添加角标 icon 到文件图标的左下角 + std::string strBuffer { buffer }; + if (!strBuffer.empty()) { + std::vector layouts; + DFMExtEmblemIconLayout iconLayout { DFMExtEmblemIconLayout::LocationType::BottomLeft, strBuffer }; + layouts.push_back(iconLayout); + emblem.setEmblem(layouts); + } + + return emblem; +} + +} // namespace Exapmle diff --git a/examples/dfm-extension-example/myemblemiconplugin.h b/examples/dfm-extension-example/myemblemiconplugin.h new file mode 100644 index 0000000000..f8cdf02c82 --- /dev/null +++ b/examples/dfm-extension-example/myemblemiconplugin.h @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef MYEMBLEMICONPLUGIN_H +#define MYEMBLEMICONPLUGIN_H + +#include + +namespace Exapmle { + +class MyEmblemIconPlugin : public DFMEXT::DFMExtEmblemIconPlugin +{ +public: + MyEmblemIconPlugin(); + ~MyEmblemIconPlugin(); + + DFMEXT::DFMExtEmblem locationEmblemIcons(const std::string &filePath, int systemIconCount) const DFM_FAKE_OVERRIDE; +}; + +} // namespace Exapmle + +#endif // MYEMBLEMICONPLUGIN_H diff --git a/examples/dfm-extension-example/mymenuplugin.cpp b/examples/dfm-extension-example/mymenuplugin.cpp new file mode 100644 index 0000000000..849873852e --- /dev/null +++ b/examples/dfm-extension-example/mymenuplugin.cpp @@ -0,0 +1,184 @@ +// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "mymenuplugin.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace Exapmle { +USING_DFMEXT_NAMESPACE + +MyMenuPlugin::MyMenuPlugin() + : DFMEXT::DFMExtMenuPlugin() +{ + registerInitialize([this](DFMEXT::DFMExtMenuProxy *proxy) { + initialize(proxy); + }); + registerBuildNormalMenu([this](DFMExtMenu *main, const std::string ¤tPath, + const std::string &focusPath, const std::list &pathList, + bool onDesktop) { + return buildNormalMenu(main, currentPath, focusPath, pathList, onDesktop); + }); + registerBuildEmptyAreaMenu([this](DFMExtMenu *main, const std::string ¤tPath, bool onDesktop) { + return buildEmptyAreaMenu(main, currentPath, onDesktop); + }); +} + +MyMenuPlugin::~MyMenuPlugin() +{ +} + +void MyMenuPlugin::initialize(DFMExtMenuProxy *proxy) +{ + m_proxy = proxy; +} + +bool MyMenuPlugin::buildNormalMenu(DFMExtMenu *main, const std::string ¤tPath, + const std::string &focusPath, const std::list &pathList, + bool onDesktop) +{ + // 可使用此方法管理自己开辟的内存,菜单关闭时被调用 + // 但是并不需要使用代理 m_proxy->deleteMenu / delteAction + // 因为只要创建的 menu 和 action 被加入了菜单 main,那么 + // 它们的内存会被自动释放 + auto memTest { new int }; + main->registerDeleted([memTest](DFMExtMenu *self) { + delete memTest; + }); + + (void)onDesktop; + (void)currentPath; + (void)focusPath; + + // 通过代理创建 action,此 action 在堆区分配,不自行释放将内存泄露! + auto rootAction { m_proxy->createAction() }; + rootAction->setText("角标管理"); + + // 通过代理创建 menu,此 menu 在堆区分配,不自行释放将内存泄露! + auto menu { m_proxy->createMenu() }; + + // 二级菜单在 Hover 中创建,以减少一级菜单显示的性能开销 + rootAction->setMenu(menu); + rootAction->registerHovered([this, pathList](DFMExtAction *action) { + if (!action->menu()->actions().empty()) + return; + auto favoriteEmblemAct { m_proxy->createAction() }; + favoriteEmblemAct->setText("角标设置为favorite"); + favoriteEmblemAct->setIcon("emblem-favorite"); + favoriteEmblemAct->registerTriggered([this, pathList](DFMExtAction *, bool) { + std::for_each(pathList.begin(), pathList.end(), [this](const std::string &path) { + setEmblemIcon(path, "emblem-favorite"); + }); + }); + + auto defaultEmblemAct { m_proxy->createAction() }; + defaultEmblemAct->setIcon("emblem-default"); + defaultEmblemAct->setText("角标设置为default"); + defaultEmblemAct->registerTriggered([this, pathList](DFMExtAction *, bool) { + std::for_each(pathList.begin(), pathList.end(), [this](const std::string &path) { + setEmblemIcon(path, "emblem-default"); + }); + }); + + auto clearEmbelmAct { m_proxy->createAction() }; + clearEmbelmAct->setIcon("emblem-important"); + clearEmbelmAct->setText("清除角标"); + clearEmbelmAct->registerTriggered([this, pathList](DFMExtAction *, bool) { + std::for_each(pathList.begin(), pathList.end(), [this](const std::string &path) { + clearEmblemIcon(path); + }); + }); + + action->menu()->addAction(favoriteEmblemAct); + action->menu()->addAction(defaultEmblemAct); + action->menu()->addAction(clearEmbelmAct); + }); + + main->addAction(rootAction); + return true; +} + +bool MyMenuPlugin::buildEmptyAreaMenu(DFMExtMenu *main, const std::string ¤tPath, bool onDesktop) +{ + assert(main); + + // 通过代理创建 action,此 action 在堆区分配,不自行释放将内存泄露! + auto action { m_proxy->createAction() }; + + // 通过 onDesktop 区分业务在桌面还是文管 + if (onDesktop) + action->setText("从文件管理器打开桌面"); + else + action->setText("打开当前路径"); + // 添加图标,也支持图片的文件绝对路径 + // 例如:/usr/share/icons/Adwaita/16x16/emblems/emblem-generic.png + action->setIcon("emblem-generic"); + + // action 被点击触发的业务处理 + action->registerTriggered([currentPath](DFMExtAction *self, bool checked) { + (void)self; + (void)checked; + pid_t pid = fork(); + if (pid == 0) { + // 子进程中调用 execvp 函数来执行 dde-file-manager 进程,并传递参数 + char *argv[] { "/usr/bin/dde-file-manager", "-n", const_cast(currentPath.c_str()), NULL }; + execvp(argv[0], argv); + } else if (pid > 0) { + // 父进程等待子进程结束 + int status; + waitpid(-1, &status, WNOHANG); + } else { + perror("fork failed"); + } + }); + + main->addAction(action); + return true; +} + +void MyMenuPlugin::setEmblemIcon(const std::string &filePath, const std::string &iconName) +{ + std::cout << "set emblem icon " << iconName << " for " << filePath << std::endl; + const std::string path { removeScheme(filePath) }; + int result = setxattr(path.c_str(), "user.icon", iconName.c_str(), iconName.size(), 0); + if (result == -1) + perror("setxattr"); +} + +void MyMenuPlugin::clearEmblemIcon(const std::string &filePath) +{ + std::cout << "clear emblem icon for " << filePath; + const std::string path { removeScheme(filePath) }; + int result = removexattr(path.c_str(), "user.icon"); + if (result == -1) + perror("removexattr"); +} + +// 在 V5 版本的文管中,menu 接收到的是文件 url 的字符串,而 V6 则直接是文件路径 +std::string MyMenuPlugin::removeScheme(const std::string &url) +{ + std::string result = url; + + // 查找第一个冒号后的斜杠位置 + size_t startPos = result.find("://"); + if (startPos != std::string::npos) { + startPos = result.find('/', startPos + 3); // 跳过冒号和两个斜杠 + if (startPos != std::string::npos) { + result = result.substr(startPos); + } + } + + return result; +} + +} // namespace Exapmle diff --git a/examples/dfm-extension-example/mymenuplugin.h b/examples/dfm-extension-example/mymenuplugin.h new file mode 100644 index 0000000000..088d069f9d --- /dev/null +++ b/examples/dfm-extension-example/mymenuplugin.h @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef MYMENUPLUGIN_H +#define MYMENUPLUGIN_H + +#include + +namespace Exapmle { + +class MyMenuPlugin : public DFMEXT::DFMExtMenuPlugin +{ +public: + MyMenuPlugin(); + ~MyMenuPlugin(); + + void initialize(DFMEXT::DFMExtMenuProxy *proxy) DFM_FAKE_OVERRIDE; + bool buildNormalMenu(DFMEXT::DFMExtMenu *main, + const std::string ¤tPath, + const std::string &focusPath, + const std::list &pathList, + bool onDesktop) DFM_FAKE_OVERRIDE; + bool buildEmptyAreaMenu(DFMEXT::DFMExtMenu *main, const std::string ¤tPath, bool onDesktop) DFM_FAKE_OVERRIDE; + +private: + void setEmblemIcon(const std::string &filePath, const std::string &iconName); + void clearEmblemIcon(const std::string &filePath); + std::string removeScheme(const std::string &url); + +private: + DFMEXT::DFMExtMenuProxy *m_proxy { nullptr }; +}; + +} // namespace Exapmle + +#endif // MYMENUPLUGIN_H