本文主要总结了本人在研究实现 dde-file-manager 文件管理器右键菜单方案中,收集到的一些有用的信息。值得一提的是,在本文中dde-file-manager版本信息是至关重要的,甚至大于方案本身的重要性,请仔细比对之。
综合考虑自定义性、接口维护性、dfm版本等多重因素,我姑且认为以下接口的排序应当是这样的:
1
2
|
DFMExtMenuPlugin > oem > menuinterface > json
Gtihub: https://github.com/pinkkmin/dfm-examples
|
oem 的方式
文件管理上下文右键菜单规范 - deepin开发者平台
oem的方式有支持两种格式,分别是.desktop
和.conf
.
对于.desktop
格式,将菜单文件移至目标目录/usr/share/deepin/dde-file-manager/oem-extensions/
,重启文管以重新载入菜单。以下是示例.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
[Desktop Entry]
Version=1.0
Type=Application
Actions=Foo1;Foo2;
Name=Foo
Icon=Foo
#文件类型
#MimeType=image/x-foo
#支持xxx.foo文件
X-DDE-FileManager-SupportSuffix=foo
#不在桌面显示
X-DDE-FileManager-NotShowIn=Desktop
#file://
X-DDE-FileManager-SupportSchemes=file
#不支持的文件类型
#X-DDE-FileManager-ExcludeMimeTypes=image/x-foo2
#仅支持单选文件
X-DDE-FileManager-MenuTypes=SingleFile;
[Desktop Action Foo1]
Exec=/usr/bin/foo --foo1 %f
Icon=foo1
Name=Open as foo1
Name[zh_CN]=用foo1打开
[Desktop Action Foo2]
Exec=/usr/bin/foo --foo2 %f
Name=Open as foo2
Name[zh_CN]=用foo2打开
Icon=foo2
|
对于.conf
格式,目标目录为/usr/share/applications/context-menus
,以下是示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
|
#右键菜单入口
[Menu Entry]
#该配置文件采用的规范协议版本
Version=1.0
#描述
Comment=This is a test file!!!
#本地化描述
Comment[zh_CN]=这是一个测试文件
#在[Menu Entry]下包含另外有2个分组,入口名对应为ActionOne和ActionTwo,作为一级菜单
Actions=ActionOne:ActionTwo
[Menu Action ActionOne]
#菜单名
Name=Menu one %b
Name[zh_CN]=菜单1 %b
#选中单个文件和多个文件时生效
X-DDE-FileManager-MenuTypes=SingleFile:MultiFiles
#支持文件类型
MimeType=image/x-foo:image/x-foo1;
#不支持的文件类型
#X-DDE-FileManager-ExcludeMimeTypes=image/x-foo2:image/x-foo3
#支持file://
X-DDE-FileManager-SupportSchemes=file
#不支持桌面
X-DDE-FileManager-NotShowIn=Desktop
#支持xxx.foo文件
X-DDE-FileManager-SupportSuffix=foo
#默认在右键菜单的一级菜单的第二个位置插入该项
X-DDE-FileManager-PosNum=1
#选中多个文件时在右键菜单的输入第三个位置
X-DDE-FileManager-PosNum-MultiFiles=3
#在该项的上方插入分割线
X-DDE-FileManager-Separator=Top
#菜单执行命令,该一级菜单为执行动作的菜单,无子菜单
Exec=/usr/bin/foo %F
[Menu Action ActionTwo]
#菜单名
Name=Menu two %d
Name[zh_CN]=菜单2 %d
#在空白区时生效
X-DDE-FileManager-MenuTypes=BlankSpace
#不支持文件管理器
X-DDE-FileManager-NotShowIn=Filemanager
#默认在右键菜单的一级菜单的第三个位置插入该项
X-DDE-FileManager-PosNum=3
#在该项的上下方插入分割线
X-DDE-FileManager-Separator=Both
#菜单包含两个子菜单
Actions=ActionThree:ActionFour
[Menu Action ActionThree]
Name=Menu three
Name[zh_CN]=菜单3
X-DDE-FileManager-PosNum=1
#菜单执行命令
Exec=/usr/bin/foo --xxx %p
[Menu Action ActionFour]
Name=Menu four
Name[zh_CN]=菜单4
X-DDE-FileManager-PosNum=2
#菜单执行命令
Exec=/usr/bin/foo --yyy %p
|
在菜单方案中,我采用的是.conf
的格式。但是,.conf
的方式不支持菜单带图标。
dde-file-manager >= 5.5.10 (10 Dec 2021)
depends: libdfm-extension-dev
👀 结合tag和commit以及系统的发布日志,发现此接口从5.5.10开始支持。
History for src/dde-file-manager-extension - linuxdeepin/dde-file-manager
👊 我实践此接口时(2022-09-02)并没有找到可参考的文档或示例,所以我邮件请教了dfm的开发者。他提供了一份有用的文档(见docs/dfm-extension-doc(answerofdeveloper).md)可以参考。
⚠️不过应当小心此文档的时效性,还是以官方的最新文档以及github仓库信息优先。
1
2
3
4
|
extern "C" void dfm_extension_initiliaze();
extern "C" void dfm_extension_shutdown();
extern "C" DFMExtMenuPlugin *dfm_extension_menu();
extern "C" DFMExtEmblemIconPlugin *dfm_extension_emblem();
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
class DFMExtMenuPlugin
{
DFM_DISABLE_COPY(DFMExtMenuPlugin)
public:
using InitializeFunc = std::function<void(DFMEXT::DFMExtMenuProxy *proxy)>;
using BuildNormalMenuFunc = std::function<bool(DFMExtMenu *,
const std::string &,
const std::string &,
const std::list<std::string> &, bool)>;
using BuildEmptyAreaMenuFunc = std::function<bool(DFMEXT::DFMExtMenu *, const std::string &, bool)>;
public:
DFMExtMenuPlugin();
~DFMExtMenuPlugin();
DFM_FAKE_VIRTUAL void initialize(DFMEXT::DFMExtMenuProxy *proxy);
DFM_FAKE_VIRTUAL bool buildNormalMenu(DFMEXT::DFMExtMenu *main,
const std::string ¤tUrl,
const std::string &focusUrl,
const std::list<std::string> &urlList,
bool onDesktop);
DFM_FAKE_VIRTUAL bool buildEmptyAreaMenu(DFMEXT::DFMExtMenu *main, const std::string ¤tUrl, bool onDesktop);
public:
void registerInitialize(const InitializeFunc &func);
void registerBuildNormalMenu(const BuildNormalMenuFunc &func);
void registerBuildEmptyAreaMenu(const BuildEmptyAreaMenuFunc &func);
private:
DFMExtMenuPluginPrivate *d;
};
|
阅读dde-file-manager源码,之后:
-
extension插件的管理在于类DFMExtPluginManager
, 而动态库的加载在DFMExtPluginLoader
中,可以看到对dfm_extension_menu
的转换.
-
如上类DFMExtMenuPlugin
的定义可见,要实现右键菜单,我们需要继承此类。而创建一个我们的实例,并为之register绑定处理函数。
-
可见DFMExtMenuPlugin.cpp
,如果没有register对应的函数也是没关系的。
-
因此,基于此接口,我们需要做的事情就是:
-
先实现三个dfm_extension_
接口,在dfm_extension_menu
返回我们的实例;
-
为我们的实例注册函数,菜单的展示主要是BuildNormalMenuFunc
。
-
以下,展示了一份简单的菜单示例,获取工程请参见examples
目录。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
|
//dfmctxmenuplugin.cpp
#include "dfm-extension.h"
#include "menu/dfmextmenu.h"
#include "menu/dfmextaction.h"
#include "menu/dfmextmenuplugin.h"
#include <QDebug>
#include <QWidget>
#include <QMessageLogger>
USING_DFMEXT_NAMESPACE
DFMExtMenuPlugin dsm_inst;
DFMEXT::DFMExtMenuProxy *menuProxy = nullptr;
DFMExtMenuPlugin::InitializeFunc initFunc;
DFMExtMenuPlugin::BuildNormalMenuFunc buildnFunc;
DFMExtAction::TriggeredFunc triggerFunc;
void DSM_ActionTrigger(DFMExtAction *, bool check)
{
QWidget *dlg = new QWidget();
dlg->show();
}
void DSM_Initialize(DFMEXT::DFMExtMenuProxy *proxy)
{
qInfo()<<"########################## DSM_Initialize proxy.......";
menuProxy = proxy;
}
bool DSM_BuildNormalMenu(DFMEXT::DFMExtMenu *main,
const std::string ¤tUrl,
const std::string &focusUrl,
const std::list<std::string> &urlList,
bool onDesktop)
{
qInfo()<<"########################## DSM_BuildNormalMenu begin";
if (main == nullptr)
return false;
// top menu1
DFMExtAction *action1 = menuProxy->createAction();
action1->setText("top-menu1");
action1->registerTriggered(triggerFunc);
action1->setIcon("://Dame.png");
// sub menus
DFMExtMenu *menu = menuProxy->createMenu();
{
// sub menu1
DFMExtAction *action1 = menuProxy->createAction();
action1->setText("sub-menu1");
action1->registerTriggered(triggerFunc);
// sub menu2
DFMExtAction *action2 = menuProxy->createAction();
action2->setText("sub-menu2");
action2->registerTriggered(triggerFunc);
menu->addAction(action1);
menu->addAction(action2);
}
action1->setMenu(menu);
// top menu2
DFMExtAction *action2 = menuProxy->createAction();
action2->setText("top-menu2");
action2->registerTriggered(triggerFunc);
action2->setIcon("://Dame.png");
std::list<DFMExtAction *> mainActions = main->actions();
if(mainActions.size() > 0)
{
DFMExtAction* posAction = mainActions.front();
main->insertAction(posAction, action1);
main->insertAction(action1, action2);
}
else
{
main->addAction(action1);
main->addAction(action2);
}
// DFMExtAction *menuAction();
// std::list<DFMExtAction *> actions();
return true;
}
extern "C" void dfm_extension_initiliaze()
{
qInfo()<<"########################## dfm_extension_initiliaze begin";
initFunc = DSM_Initialize;
buildnFunc = DSM_BuildNormalMenu;
triggerFunc = DSM_ActionTrigger;
dsm_inst.registerInitialize(initFunc);
dsm_inst.registerBuildNormalMenu(buildnFunc);
qInfo()<<"########################## dfm_extension_initiliaze end";
}
extern "C" void dfm_extension_shutdown()
{
qInfo()<<"########################## dfm_extension_shutdown ";
}
extern "C" DFMExtMenuPlugin *dfm_extension_menu()
{
qInfo()<<"########################## dfm_extesion_menu-----------";
return &dsm_inst;
}
|
将编译完成的动态库放置/usr/lib/$${QMAKE_HOST.arch}-linux-gnu/dde-file-manager/plugins/extensions
,在支持extensions
版本的dde-file-manager
此目录已自动创建。
杀死dde-file-manager
进程,并重启之,即可加载我们的插件.
dde-file-manager < 5.0.3
depends: libdde-file-manager-dev
⚠️ 此接口在5.0.3已经被移除,具体可查看dde-file-manager仓库的pluginmanager.cpp文件的commit记录。
⚠️ 此链接的内容可能已过期。Context Menu Extension (zh) | Deepin File Manager
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
/*
* 文件管理器的右键菜单插件接口
*/
class MenuInterface : public QObject
{
public:
explicit MenuInterface(QObject *parent = nullptr)
: QObject(parent) {}
virtual ~MenuInterface() {}
/*!
* \brief additionalMenu 选中文件菜单接口
* \param files: 选中的文件路径列表
* \param currentDir:右键所在的文件目录
* \return QList<QAction*>:返回一组QAction指针列表,每个action对应菜单的一个菜单项
*/
virtual QList<QAction*> additionalMenu(const QStringList &files, const QString& currentDir){
Q_UNUSED(files)
Q_UNUSED(currentDir)
QList<QAction*> actions;
return actions;
}
/*!
* \brief additionalEmptyMenu 空白菜单接口
* \param currentDir: 空白菜单所在的文件目录
* \param onDesktop: 空白菜单是否为在桌面上触发的
* \return QList<QAction*>:返回一组QAction指针列表,每个action对应菜单的一个菜单项
*/
virtual QList<QAction*> additionalEmptyMenu(const QString ¤tDir, bool onDesktop = false){
Q_UNUSED(currentDir)
Q_UNUSED(onDesktop)
QList<QAction*> actions;
return actions;
}
};
|
以下是基于此接口实现的右键菜单插件,和peony的菜单插件类似。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
// dfmctxmenuplugin.h
#ifndef DFMCTXMENUPLUGIN_H
#define DFMCTXMENUPLUGIN_H
#include "dde-file-manager-plugins/menuinterface.h"
class DFMCtxMenuPlugin : public MenuInterface
{
Q_OBJECT
Q_INTERFACES(MenuInterface)
Q_PLUGIN_METADATA(IID MenuInterface_iid)
public:
DFMCtxMenuPlugin();
QList<QAction*> additionalMenu(const QStringList &files, const QString& currentDir) override;
};
#endif // DFMCTXMENUPLUGIN_H
|
1
2
3
4
5
6
7
8
9
10
11
|
// dfmctxmenuplugin.cpp
#include "dfmctxmenuplugin.h"
DFMCtxMenuPlugin::DFMCtxMenuPlugin()
{
}
QList<QAction *> DFMCtxMenuPlugin::additionalMenu(const QStringList &files, const QString ¤tDir)
{
QList<QAction *> actionPtrList;
actionPtrList.append(new QAction("Sample Text"));
return actionPtrList;
}
|
将编译完成的动态库放置目录/usr/lib/$${QMAKE_HOST.arch}-linux-gnu/dde-file-manager/plugins/menu
.
然后,杀死 dde-file-manager
进程,并重启之便会加载我们的插件.
Json的方式(类似oem)
dde-file-manager < 5.0
⚠️ 此接口在5.0.3已经被移除,具体可查看dde-file-manager仓库的pluginmanager.cpp文件的commit记录。
1
2
3
4
5
6
7
8
9
10
11
|
[
{
"MenuType": "EmptyArea",
"Icon": "Files",
"Text[zh_CN]": "在 VSCode 中打开",
"Text[en_US]": "Open with VSCode",
"Exec": "code",
"Args": ["."],
"NotShowIn": ["Desktop"],
}
]
|
Demo
oem
desktop
file-manager
无可演示的合适版本DFM.
Json
由于我没有低于5.0版本的dfm,因此此部分缺失。
目前选定方案-oem(.conf)
dde-file-manager 5.2.45
date: 2022年12月29日16:36:30
由于目前适配的dfm版本相对略低,因此插件动态库的方式并不支持,无奈只能采取oem的方式生成菜单了。