基础版系统资源管理器右键菜单功能扩展开发说明
1.基础版系统右键菜单功能扩展接口
在基础版系统图形界面中,一般用户主要通过文件管理器(Nemo)来管理系统资源。文件管理器,集多重功能于一身,能够在图形环境下更加简便、安全的管理和操作文件,还包括了浏览文本和图像,访问和共享网络资源等功能。而其中,右键菜单作为用户最常用的操作, 也成为了开发者需要添加扩展功能的对象。本文介绍了基础版操作系统的右键菜单功能扩展接口,开发者可以以此开发可添加于右键菜单的扩展插件。
2.基础版系统预装的Nemo扩展
基础版文件管理器Nemo为了扩展自身功能,开发了一套功能相对完整、稳定的扩展机制。从下图可以看出,Nemo将自己的扩展机制分为行为(Action)、脚本(Scripts)和扩展(Extensions)三类。
对于行为(Action)和脚本(Scripts)两类,点击目录浏览,发现默认目录是:“$HOME/.local/share/nemo/actions” 和 “$HOME/.local/share/nemo/scripts”。
可以看出是用户级自定义,仅仅对当前用户有效。如果想要编写对全部用户都有效的扩展,需要放到系统级别的“share/nemo/actions”目录中。Scripts目前不支持系统级定义。
相对于前两者,最后一类扩展(Extensions)的组成要更复杂。它们是真正意义上的扩展,实现的功能更加复杂,对Nemo的依赖也更加紧密,是可以独立安装、卸载的包。如上图中所示,系统中已经集成了的两个,它们的对应关系是:
扩展名 | 说明 | 包名 |
---|---|---|
NemoFileCompress | 允许用户进行一个文件或文件夹的压缩 | nemo-file-compress |
Nemo Share | 允许用户从上下文菜单中快速的分享一个文件夹 | nemo-share |
基础版为了方便用户的使用,预装了一些Nemo扩展,如下表所示:
扩展类别 | 是否预装 | 预装内容 | 文件路径 |
---|---|---|---|
行为(Action) | 是 | 更改桌面背景、创建一个新的起动器、修改屏幕分辨率 | 系统级:/usr/share/nemo/actions;用户级:~/.local/share/nemo/actions |
脚本(Script) | 否 | 无 | 目录:~/.local/share/nemo/scripts(如果想自定义) |
扩展(Extension) | 是 | NemoShare、NemoFileCompress | 目录:/usr/lib/x86_64-linux-gnu/nemo/extensions-3.0/中以静态库或者动态库的方式存在 |
说明:系统级、用户级指是否内被其他用户使用。支持的库文件类型,目前包括静态库的*.a或*.la,动态库的*.so文件。
2.1.行为 Action 类插件编写说明
Nemo其实提供了Action编写的示例,由英文编写,本地文档地址是“/usr/share/nemo/actions/sample.nemo_action”。这里结合实践来说明如何使用这些规则。
debug模式:
Nemo Action提供了debug机制,需要设置NEMO_ACTION_VERBOSE环境变量,就能打印出有用的调试信息。使用方法:
$ nemo --quit
$ NEMO_ACTION_VERBOSE=1 nemo
Active字段[可选]
设置这个动作是否用有效,可用于故障排除。如果此字段被忽略,则该action处于激活状态。
Name字段[必选]
下面这些标准tokens,可在Name、Comment(悬浮提示)以及Exec字段中使用的:
- %U - 在选择中插入URI列表
- %F - 在选择中插入path列表
- %P - 插入当前目录的父目录path
- %f or %N (已废弃) - 插入选中第一个文件的display name
- %p - 插入父目录的display name
- %D - 插入文件的设备路径 (例如: /dev/sdb1)
Name字段是要在菜单中显示的名称,使用标准桌面规范支持的区域设置:
Name=Test Custom Action
Name[zh_CN]=测试自定义动作
Name[zh_TW]=測試自定義動作
Comment字段
悬浮提示,支持语言环境(出现在状态栏中)。
Exec字段[必选]
表示本action运行的可执行文件。
Icon-Name字段
在菜单中使用的图标名称,必须是主题图标名称。
Stock-Id字段
使用Gtk Stock ID来定义图标。注意,如果同时定义了Icon-Name和Stock-Id,Stock-Id优先。
Selection字段[必选]
什么类型的选择:s代表单选,m代表多选,any代表任何,notnone代表非空,none(背景点击),或者表示必须选择显示多少个文件的数字。
Extensions字段[必须]
显示什么扩展名 - 这是一个数组,以分号结尾单个条目选项,以分号结尾。
- “dir”表示目录选择;
- “none”表示无扩展;
- “nodirs”表示除目录之外的任何选择;
- “any”表示任何文件类型,包括目录。
个别特定的扩展可以是以分号结尾的列表。扩展不区分大小写。jpg将匹配JPG,jPg,jpg等。
Mimetypes字段
什么样的Mime类型显示,这是一个数组,以分号结尾。例如,设置背景里功能里,就设置了Mimetypes=image/*。
Separator字段[可选]
要使用的分隔符(如果有的话) - 添加一个字符串,在exec行中的“path/url”条目之间插入。 如果不填写,则会插入一个空格。请注意,您可以在此处放置空格。
Quote字段[可选]
引用类型(如果有的话) - 用引号括起“path/url”。默认为不含引号。可以是:single、double、backtick。
Dependencies字段
表明此操作需要的可执行文件。Nemo会在路径中搜索这些文件,如果缺失将不再不显示该action。还可以提供绝对路径(即/usr/lib/gvfs/gvfsd-archive)来检查路径中的可执行文件,而不是路径中的可执行文件。这是一个数组,用分号分隔条目,并以分号结尾。
Conditions字段
条件 - 以分号分隔的特殊条件数组,可添加的条件如下:
- “desktop” - 当前(父)文件夹是桌面;
- “removable” - 目标(第一个选择的是可移动的);
- “gsettings
” - 为真时显示; - “gsettings
<[eq|ne|gt|lt]> ” - 设置特定的值; - “dbus
” - 存在时显示。
例如修改背景,要求在存在名为org.Cdos的dbus、且为桌面目录上:“Conditions=desktop;dbus org.Cdos;”
EscapeSpaces字段[可选]
避免文件名中途截断,有时候这可能比获取必须用引号括起来的原始文件名更受欢迎。默认为false。
Action类插件示例:
下面是使用Action类插件实现的“使用Sublime打开”的右键菜单扩展功能:
[Nemo Action]
Name=Open by Sublime Text
Name[zh_CN]=使用Sublime打开
Comment=Copyright by niyl
Comment[zh_CN]=niyl版权所属
Exec=subl %F
Icon-name=cs-backgrounds
Selection=Notnone
Extensions=any;
Dependencies=subl;
Conditions=desktop;dbus org.Cdos;
EscapeSpaces=true
2.2.脚本 Script 类插件编写说明
当从本地文件夹执行时,脚本将传递选定的文件名。当从远程文件夹(例如显示web或ftp内容的文件夹)执行时,脚本将不会传递参数。
在所有情况下,以下环境变量将由nemo设置,脚本可以使用这些变量:
NEMO_SCRIPT_SELECTED_FILE_PATHS
选定文件的换行符分隔路径(仅限本地)。
NEMO_SCRIPT_SELECTED_URIS
所选文件的换行符分隔的URI。
NEMO_SCRIPT_CURRENT_URI
当前地址的URI。
NEMO_SCRIPT_WINDOW_GEOMETRY
当前窗口的位置和尺寸。
NEMO_SCRIPT_NEXT_PANE_SELECTED_FILE_PATHS
拆分视图窗口的非活动窗格中选定文件的换行符分隔路径(仅限本地)。
NEMO_SCRIPT_NEXT_PANE_SELECTED_URIS
拆分视图窗口的非活动窗格中选定文件的换行符分隔的URI。
NEMO_SCRIPT_NEXT_PANE_CURRENT_URI
拆分视图窗口的非活动窗格中当前位置的URI。
Script类插件示例:
下面是一个Python格式的script实例,存放目录为用户级目录,可以读取环境变量NEMO_SCRIPT_SELECTED_FILE_PATHS。注意,此变量必须在右键选中文件之后才会被读取到,在空白处右键点击是没有任何效果的。所有的脚本都会被放在右键菜单->脚本的集合内。目前已知的支持格式包括shell脚本,python脚本。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import sys
import gtk
import gettext
gettext.bindtextdomain('nemo', '/usr/share/cdos-de/locale')
gettext.textdomain('nemo')
_ = gettext.gettext
try:
message = gtk.MessageDialog(type=gtk.MESSAGE_WARNING,
buttons=gtk.BUTTONS_OK)
file_paths = os.environ.get('NEMO_SCRIPT_SELECTED_FILE_PATHS')
message.set_markup(str(file_paths))
message.run()
sys.exit(1)
except KeyboardInterrupt:
print "Interrupt, quit!"
sys.exit(1)
实现的右键菜单效果如下图所示。
Shell格式类似,也可以直接使用上面的环境变量。说明:script的使用场景,应该是简单、常用的功能集合。此外,由于暂时不支持系统级脚本,所以只能用户自己使用,无法被其他用户使用。
#!/bin/sh
# write by niyl
# to test nemo scripts
cdos-session-quit --logout
2.3.扩展 Extensions 类插件编写说明
Nemo extension的编写需要很大的工程量,通常都是为了实现某一特定目的,专门编写一个软件包。根据nemo官方给出的实例集合来看,它同样支持Python与C这两种语言格式。这里只对如何将编写的软件包,在右键菜单中呈现出来,作出说明。
这里使用引入的库是 libnemo-extension/nemo-menu-provider.h 来实现的。想要在nemo右键菜单中,加入新的菜单项,就必须使用类型注册的方式来新建菜单项。几个关键函数如下:
注册函数
所有static const类型的变量仅在初始化的时候,运行一次。这里使用了 g_type_module_add_interface 的方式来添加接口,类型使用 NEMO_TYPE_MENU_PROVIDER 的类型。在 menu_provider_ifcae_info 中第一个参数是接口的初始化操作。
static void
nemo_foo_register_type (GTypeModule *module)
{
static const GTypeInfo info = {
sizeof (NemoFooClass),
(GBaseInitFunc)NULL,
(GBaseFinalizeFunc)NULL,
(GClassInitFunc)nemo_foo_class_init,
NULL,
NULL,
sizeof (NemoFoo),
0,
(GInstanceInitFunc)nemo_foo_instance_init,
};
static const GInterfaceInfo menu_provider_iface_info = {
(GInterfaceInitFunc)nemo_foo_menu_provider_iface_init, NULL, NULL};
static const GInterfaceInfo nemo_foo_nd_provider_iface_info = {
(GInterfaceInitFunc)nemo_foo_nd_provider_iface_init, NULL, NULL};
nemo_foo_type = g_type_module_register_type (
module, G_TYPE_OBJECT, "NemoFoo", &info, 0);
/* ... add interfaces ... */
g_type_module_add_interface (module,
nemo_foo_type,
NEMO_TYPE_MENU_PROVIDER,
&menu_provider_iface_info);
g_type_module_add_interface (module,
nemo_foo_type,
NEMO_TYPE_NAME_AND_DESC_PROVIDER,
&nemo_foo_nd_provider_iface_info);
}
接口函数初始化
函数接口的初始化,传入的参数类型为 NemoMenuProviderIface 类型,这里重写了 iface->get_file_items 函数,设置为自定义函数 nemo_foo_get_file_items,对菜单进行初始化。
/* Interfaces */
static void
nemo_foo_menu_provider_iface_init (NemoMenuProviderIface *iface)
{
iface->get_file_items = nemo_foo_get_file_items;
return;
}
菜单初始化
初始化使用 nemo_menu_item_new 来创建菜单项,第二项是显示的名称,第四项是菜单之前显示的图标名称。所有用这种方法创建的菜单,都需要监听“activate”信号,它是菜单激活是会发送的信号。这里设置 nemo_foo_cb 为激活后的回调函数。同时,为新菜单项设置了数据“nemo_foo_files”、它的数据来源和自定义的销毁函数。
/* Menu interfaces */
static GList *
nemo_foo_get_file_items (NemoMenuProvider *provider,
GtkWidget *window,
GList *files)
{
NemoMenuItem *item_foo;
GList *ret = NULL;
// Not given a icon-name
item_foo = nemo_menu_item_new ("NemoFoo::foo_data_menu",
"open a Gtk Dialog",
"Open a Gtk Dialog, Use first file as title",
NULL);
g_signal_connect (item_foo, "activate", G_CALLBACK (nemo_foo_cb), provider);
g_object_set_data_full (G_OBJECT (item_foo),
"nemo_foo_files",
nemo_file_info_list_copy (files),
(GDestroyNotify)nemo_file_info_list_free);
ret = g_list_append (NULL, item_foo);
return ret;
}
菜单点击回调
当激活菜单后,通过数据读取,将文件列表传入进来,这里通过 for 循环直接打印出来所选择的文件名。
/* activate callback */
static void
nemo_foo_cb (NemoMenuItem *item, gpointer user_data)
{
GList *files = NULL;
GList *l = NULL;
files = g_object_get_data (G_OBJECT (item), "nemo_foo_files");
for (l = files; l != NULL; l = l->next)
{
gchar *name = NULL;
NemoFileInfo *file_info = NEMO_FILE_INFO (l->data);
name = nemo_file_info_get_name (file_info);
g_print ("\n Nemo Extension Foo: %s, \n", name);
g_free (name);
}
GtkWidget *window;
gtk_init(0, NULL);
window=gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(window, "Hello World!");
g_signal_connect(window, "destroy", gtk_main_quit, NULL);
gtk_widget_show_all(window);
gtk_main();
}
通过完善GObject代码固定的结构,完善代码,编写工作基本完成了。这里的编译,需要注意一下。例如,现在编写的文件名是 libnemo-foo.c,为了实现扩展能正常工作,必须将本文件编译成共享库的形式。类似的Makefile如下:
libnemo-foo.so:
gcc -shared libnemo-foo.c -o libnemo-foo.so -fPIC \
`pkg-config libnemo-extension gtk+-3.0 --libs --cflags`
install:
sudo mv libnemo-foo.so /usr/lib/x86_64-linux-gnu/nemo/extensions-3.0/
clean:
rm *.so
编译成功之后,将它移动到 /usr/lib/x86_64-linux-gnu/nemo/extensions-3.0/ 目录下。为了测试创建的插件是否被正确加载,给出一种检测方法:
$ nemo -q
$ strace -e trace=open nemo -n
通过 strace 命令查看,加载的 so 文件里,有没有 libnemo-foo.so 文件。当然也可以通过其他办法,比如打印 log 的形式查看,插件的 log 是可以在 nemo 启动的终端中输出来的。
如上图是strace启动nemo的日志截图,出现了libnemo-foo,表示加载成功。
3.基础版系统右键菜单扩展功能开发规范
基础版系统右键菜单扩展功能开发规范如下表所示:
插件类别 | 行为(Action) | 脚本(Script) | 扩展(Extension) |
---|---|---|---|
存放位置 | 可以存放在用户级目录与系统级目录 | 目前只能用户级目录 | 只支持系统级目录 |
显示位置 | 右键菜单上部分,nemo_action集中显示 | 统一被收纳在“脚本”菜单项里 | 右键菜单下部分,“属性”菜单项之上。扩展也是集中显示的。 |
权限 | 由于存在exec、gksu这种能以图形化界面获取管理员权限的命令,是否可以获取管理员权限已经不存在任何问题。完全根据需要。 | ||
文件类型 | 原生支持,可以通过 mimetype 设置过滤 | 原生不支持,可以通过命令查询过滤 | 原生不支持,可以通过编写程序自己过滤 |
备注 | 推荐。语法简单,设置灵活,但是受限于键值固定,但基本能满足与日常工作。 | 官方只给出说明,没有任何例子。可以写一些小工具,执行重复性操作。 | 半推荐,虽然功能强大,但是编写难度大。能与nemo紧密结合,目前系统中已经集成nemo-file-compress,nemo-share。建议专业人员在由明确特定需求的时候,统一编写。 |
4.开发注意事项
插件类别选择
从官方提供的资料来看,开发右键扩展功能选用的插件类别的推荐级别是:行为(Action)> 扩展(Extension) > 脚本(Script)。理由是Script并没有在新版的Cinnamon桌面中被使用。Actions有着详细的样板与实例,包括说明文档,是官方最为推荐的方式。
编写Extension难度很高
很少有人独立去编写Nemo Extensions,原因是查看Nemo Extension官方仓库,会发现大部分的Extension都是由Nautilus Extension转化而来的。它们功能强大,专业性高,能够显示复杂的菜单集合。但是,官方没有提供详细的插件编写指导。
要合理区分显示情况
编写扩展的时候,对于扩展在某些情况是否显示,要经过充分思考。不能出现一大堆没有使用场景的菜单项,这样会使得右键菜单过于冗余,带来不好的用户体验。
注意移植的版本问题
如果决定使用网上已经编写好的Extension,那么在移植的时候,需要注意一些问题。注意扩展和Nemo版本的匹配,需要完善的测试才能引入。