2020-10-13 开发文件管理器插件

Nemo 插件开发机制

这篇文档介绍文件管理器三种插件开发机制,并提供对应的简单示例。

插件分类

右键菜单

我们右键某个文件,弹出的菜单项有 nemo 自定义的也有插件开发的。比如文件共享、文件压缩的功能都是通过插件机制嵌入到文件管理器的菜单。
这类插件最重要的接口就是 NemoMenuProviderIface,必须实现这个接口的 iface->get_file_items 函数。函数定义:

static GList *sendto_extension_get_file_items (NemoMenuProvider *provider,
                        GtkWidget *window,
                        GList *files)

其中 files 列表就是选择的文件清单,可以只选中一个文件,也可以多个。列表成员 NemoFileInfo,许多工作都是围绕这个文件清单来做的。
主要工作:创建右键菜单项 nemo_menu_item_new,这个菜单可能需要子菜单,比如示例 test-sendto 是一个右键发送到可移动设备的原型程序,所以需要把系统中的可移动设备列表放在子菜单中。nemo_menu_new 创建子菜单,nemo_menu_item_set_submenu 把它添加到菜单项。

文件属性页

右键文件,在菜单中选择属性,弹出属性窗口,默认有三个tab页,“基本”,“权限”,“打开方式”,在这三个之外,我们也可以根据文件类型、内容的不同自定义属性页。
这类插件最重要的接口是 NemoPropertyPageProviderIface,必须实现这个接口的 iface->get_pages 函数,函数定义:

static GList *
get_property_pages (NemoPropertyPageProvider *provider,
                    GList *files)

这里的 files 文件也是我们选中的文件清单,列表成员类型NemoFileInfo,比如示例中对于所有 mimetype 是文本文件类型的都新增属性页“Text File”,就是通过检查这个文件的 nemo_file_info_get_mime_type 来做的。
主要工作:调用 nemo_property_page_new 创建属性页。属性页是个 Gtk 控件,所以具体内容就是使用 gtk 创建就可以了。

详细信息列

这个插件与上面两个略有不同,上面那两个都是选择若干文件后右键触发的,所以都能拿到一个文件清单列表,这个列表里就是一个个 NemoFileInfo,我们可以对这些文件做处理。但是这个插件比较不一样,没有选择的文件。我们打开某个目录,在列表视图下所有文件都会新增一列,比如我们示例的文本类型都会增加一个“test column”的属性,其他文件类型没有新增属性这列就为空。
这个插件除了要实现 NemoColumnProviderIface 接口,还得实现 NemoInfoProviderIface 接口,这个接口就可以帮助我们拿到所有文件的信息,根据自己的需要来设置文件属性。

代码示例

每个插件都需要实现几个固定的接口,编译生成动态链接库,安装到 nemo 的 extensions 目录,这样 nemo 启动时才能找到他们。

1、插件加载等接口

/* Extension initialization */
void nemo_module_initialize (GTypeModule  *module)
{
    sendto_extension_register_type(module);
    provider_types[0] = sendto_extension_get_type();
}

void nemo_module_shutdown(void)
{
    /* Any module-specific shutdown */
}

void nemo_module_list_types (const GType **types, int *num_types)
{
    *types = provider_types;
    *num_types = G_N_ELEMENTS (provider_types);
}

2、接口实现: 以属性页为例

/* 我们这个插件实现了什么接口就在这个函数中注册
 * 这里只实现了属性页,所以只有一个 interface
 */
static void
nemo_txt_properties_page_register_type (GTypeModule *module)
{
  GType nemo_txt_type = nemo_txt_properties_page_get_type ();

  
  static const GInterfaceInfo property_page_provider_iface_info = {
    (GInterfaceInitFunc) property_page_provider_iface_init,
    NULL,
    NULL
  };
  
  g_type_module_add_interface (module,
             nemo_txt_type,
             NEMO_TYPE_PROPERTY_PAGE_PROVIDER,
             &property_page_provider_iface_info);

}

右键菜单

按照上面介绍的,右键菜单最重要的接口就是 get_file_items,在接口注册中添加接口 sendto_extension_menu_provider_iface_init,接口中定义我们要实现的函数:
iface->get_file_items = sendto_extension_get_file_items;
函数实现:

/* 右键菜单实现最重要的就是这个函数 */
static GList *sendto_extension_get_file_items (NemoMenuProvider *provider,
                        GtkWidget *window,
                        GList *files)
{
    GList *items, *iterate;
    NemoMenuItem *separator;
    NemoMenuItem *item;
    show_menu = TRUE;
    gchar *uri = NULL;
    GFile *object = NULL;

    if (!files) {
        return NULL;
    }

    /* 这个 files 就是我们右键时选中的文件清单,可以单个也可以多个 */
    iterate = files;
    while (iterate != NULL) {
        NemoFileInfo *file = iterate->data;

        uri = nemo_file_info_get_uri (file);
    
        object = g_file_new_for_uri (uri);
    
    gchar *path = g_file_get_path (object);

    if (path == NULL) // show_menu 用来控制是否显示自定义的菜单,这个例子中不存在可移动设备就不用显示。
    {
        show_menu = FALSE;
    } else {
        g_free (path);
    }
    g_free (uri);
    iterate = iterate->next;
    }

    item = nemo_menu_item_new ("SendtoExtension::SendtoData",
                "NFS Sendto",
                "Sendto Data",
                "Sendto Description");

    g_object_set (G_OBJECT(item), "sensitive", FALSE, NULL);

    NemoMenu *sendtoMenu = nemo_menu_new();
    nemo_menu_item_set_submenu(item, sendtoMenu);

    create_sendto_target (sendtoMenu, files, item);

    separator = nemo_menu_item_new_separator ("Sendto Separator");
    items = g_list_append (NULL, separator);
    items = g_list_append(items, item);
    return items;
}

文件属性页

属性页最重要的接口是 get_pages,接口注册函数中定义我们的实现函数

static void 
property_page_provider_iface_init (NemoPropertyPageProviderIface *iface)
{
    iface->get_pages = get_property_pages;
}

函数实现:

/* 这个函数最重要,nemo 加载属性页时就会调用这里 */
static GList *
get_property_pages (NemoPropertyPageProvider *provider,
                    GList *files)
{
    GList *pages;
    NemoFileInfo *file;

    char *mime_type;
    
    /* Only show the property page if 1 file is selected */
    if (!files || files->next != NULL) {
        return NULL;
    }

    pages = NULL;
    
    file = NEMO_FILE_INFO (files->data);

    mime_type = nemo_file_info_get_mime_type (file);

    g_print ("%s %d: mime type %s\n", __func__, __LINE__, mime_type);

    if (mime_type != NULL && is_mime_type_supported (mime_type)) {
        NemoTxtPropertiesPage *page;
        NemoPropertyPage *real_page;

        page = g_object_new (nemo_txt_properties_page_get_type (), NULL);
        load_location (page, file);

        real_page = nemo_property_page_new ("NemoTxtPropertiesPage::property_page",
                                            gtk_label_new (_("Text File")),
                                            GTK_WIDGET (page));
        pages = g_list_append (pages, real_page);
    }

        g_free (mime_type);

    return pages;
}

详细信息列

这个插件最特殊的就是需要修改文件信息,因为我们要定制文件列信息,为了实现这个功能,必须修改文件属性。这个插件的注册接口:

static void
nemo_txt_column_register_type (GTypeModule *module)
{
  GType nemo_txt_type = nemo_txt_column_get_type ();

  
  static const GInterfaceInfo column_provider_iface_info = {
    (GInterfaceInitFunc) nemo_txt_column_provider_iface_init,
    NULL,
    NULL
  };

  static const GInterfaceInfo info_provider_iface_info = {
        (GInterfaceInitFunc) nemo_txt_info_provider_iface_init,
        NULL,
        NULL
    };

  g_type_module_add_interface (module,
             nemo_txt_type,
             NEMO_TYPE_COLUMN_PROVIDER,
             &column_provider_iface_info);

  g_type_module_add_interface (module,
                                 nemo_txt_type,
                                 NEMO_TYPE_INFO_PROVIDER,
                                 &info_provider_iface_info);

}

首先介绍 column provider,这个很简单,我们需要实现 get_columns接口,想增加几列就在这个函数中调用 nemo_column_new 新建列,比较重要的是有个属性信息,比如例子中自定义 title 属性,我们在 info provider 中需要用到。
NemoInfoProviderIface 的定义如下:

static void
nemo_txt_info_provider_iface_init (NemoInfoProviderIface *iface) {
    iface->update_file_info = nemo_txt_update_file_info;
    return;
}

在这个函数中,我们为了方便只是简单的判断如果是文本文件,就设置属性 title 的值为 “test column”

/* Info interfaces */
static NemoOperationResult
nemo_txt_update_file_info (NemoInfoProvider *provider,
                                NemoFileInfo *file,
                                GClosure *update_complete,
                                NemoOperationHandle **handle)
{
    char *mime_type;

    g_print ("%s %d\n", __func__, __LINE__);
    mime_type = nemo_file_info_get_mime_type (file);

    g_print ("%s %d: mime type %s\n", __func__, __LINE__, mime_type);

    if (mime_type != NULL && is_mime_type_supported (mime_type)) {

        nemo_file_info_add_string_attribute (file,
                             "title", // 对应上面的 title 
                             "test column");

    }
    return NEMO_OPERATION_IN_PROGRESS;
}

插件验证

这三个示例程序,分别编译成动态链接库文件,安装到插件目录 /usr/lib/x86_64-linux-gnu/nemo/extensions-3.0/,重启 nemo。
1、如果外接了U盘,右键某个文件,就会出现 NFS Sendto 菜单,hover 上去出现 U 盘子菜单,点击子菜单触发信号,示例程序弹一个 gtk 对话框。
2、如果选择某个文本文件,右键属性,在属性页会新增一个文本文件页,显示“文本类型” 和 “File uri” 这两条信息.
3、进入某个空文件目录,选择详细信息,新建一个文件文档。右键在顶部列表栏中选择我们自定义的 “Title”,就能看到测试值 “test column”

注意:
测试代码,没有任何保护措施,仅供参考。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269