如何使用 Python 为 GNOME 桌面、screenlets 架构以及 Nautilus 创建脚本,来交付高生产能力的环境?桌面脚本实现拖放功能,以及快速访问常用信息与服务的功能。本文将介绍如何使用 Python 来增加功能,从而实现对桌面 Nautilus 的扩展。
对于 GNOME 桌面用户来说,Nautilus 编程可能是更比较常用的应用程序之一。它能够通过简单的图形界面,来处理所有的文件复制、移动、重命名、以及搜索的问题。从表面上看,似乎不存在 Nautilus 不能处理的文件相关事务 — 除非考虑执行具有 shell 脚本的任务。
Nautilus 开发工具提供了多个不必打开主代码库而增加新功能的方法。最简单的方法是使用能执行那些通常在终端提示符上执行的命令的 bash 或者 bash 脚本。该方法使得尝试使用这一命令来确保他们完成想要首先完成的任务。还可以采用其他语言,包括 C 脚本语言、GnomeBasic、Perl、以及 Python。本文介绍如何利用 Python 语言来为 Nautilus 增加新功能。假定读者已对 Python 语言及 Python 标准库有所了解。
Nautilus 脚本
扩展 Nautilus 的第一个方法是通过在 /home 中发现的名为 .gnome2/nautilus-scripts 的特定目录。当在 Scripts 菜单下的文件或者文件夹上点击鼠标右键时,该目录下所有可执行文件将会出现。还可以选择多个文件或者文件夹,并采用相同的右击方法,将文件清单传递给脚本。
当调用脚本时,Nautilus 支持多个包含当前目录以及所选文件等内容的环境变量。表 1 展示了这些环境变量。
表 1. Nautilus 环境变量
环境变量 | 描述 |
NAUTILUS_SCRIPT_SELECTED_FILE_PATHS | 所选文件的新行分割路径(仅针对本地) |
NAUTILUS_SCRIPT_SELECTED_URIS | 所选文件的新行分割 URIs |
NAUTILUS_SCRIPT_CURRENT_URI | 当前位置 |
NAUTILUS_SCRIPT_WINDOW_GEOMETRY | 当前窗口的位置和大小 |
在 Python 中,通过对 os.environ.get 函数的一个调用来获取这些变量的值,具体如下:
selected = os.environ.get('NAUTILUS_SCRIPT_SELECTED_FILE_PATHS,'')
此调用返回一个字符串,其包含到达由换行符分隔的全部所选文件。Python 利用下列代码,简化了将这一字符串返回到可迭代列表中的操作:
targets = selected.splitlines()
此时,也许应该停下来探讨一下用户交互。当控制从 Nautilus 传送到脚本后,在该点上确实不存在对脚本的限制。根据脚本作用的不同,甚至不需要任何用户反馈,除了一些类型的完成或错误消息,这样通过一些简单的消息框就可处理好。由于在编写 Nautilus 时采用了 gtk windowing 工具包,所以尽管这不是必须的,但是采用相同的做法很合乎逻辑。您可以很方便地使用 TkInter 或者 wxPython。
鉴于本文的目的,您将采用 gtk。生成一个用于通信完成状态的简单消息框,仅需几行代码,出于方便阅读的目的,如果想创建简单的函数来生成消息,这个代码将最为合适。总共需要 4 行代码:
def alert(msg): dialog = gtk.MessageDialog() dialog.set_markup(msg) dialog.run()
#p#
示例:创建简单脚本来返回所选文件的数量
第一个示例程序将多个程序段合并成一个简单脚本,来返回当前所选文件的数量。这一脚本可用于文件或者目录。可利用另一个 Python 库函数,os.walk,递归地构建每个目录中文件的清单。总共有 38 行代码,如清单 1 所示,这就是这一小工具所需的全部内容,其中还包括了空行。
清单 1. 用于 Filecount 脚本的 代码 Python
#!/usr/bin/env python import pygtk pygtk.require('2.0') import gtk import os def alert(msg): """Show a dialog with a simple message.""" dialog = gtk.MessageDialog() dialog.set_markup(msg) dialog.run() def main(): selected = os.environ.get('NAUTILUS_SCRIPT_SELECTED_URIS', '') curdir = os.environ.get('NAUTILUS_SCRIPT_CURRENT_URI', os.curdir) if selected: targets = selected.splitlines() else: targets = [curdir] files = [] directories = [] for target in targets: if target.startswith('file:///'): target = target[7:] for dirname, dirnames, filenames in os.walk(target): for dirname in dirnames: directories.append(dirname) for filename in filenames: files.append(filename) alert('%s directories and %s files' % (len(directories),len(files))) if __name__ == "__main__": main()
图 1 展示了当在文件上右击鼠标或者选择一组文件时所看到的内容。Scripts 菜单选项展示 .gnome2/nautilus-scripts 中所有的可执行文件,并给出了打开文件夹的选项。选择一个文件来执行该脚本。
图 1.在 Nautilus 中选择文件
图 2 展示了 Filecount.py 脚本的运行结果。
图 2. Filecount.py 输出
在调试 Nautilus 脚本时,有几件事需要注意。第一件事是关闭 Nautilus 的所有实例,来使它完全重新加载,并找到新脚本或者扩展。可采用如下命令:
nautilus -q
下一个常用命令可实现不必打开首选或者配置数据,而直接运行 Nautilus。这在解决脚本或者扩展在无意间造成破坏之类的问题时,会节省很多步骤。命令如下:
nautilus -no-desktop
确保 filecount 工具可被 Nautilus 访问所剩的最后一步是将其复制到 ~/.gnome2/nautilus-scripts 目录,并改变文件代码来允许执行,相关命令是:
chmod +x Filecount.py
#p#
示例:创建文件 cleanup 工具
第二个例子是,创建文件 cleanup 工具,来查找任何可能由 Vim 或者 EMACS 之类的编辑器临时生成的文件。仅通过简单地修改 check 函数,就可利用相同的概念来清除任何特定文件的目录。这一代码属于静默操作,这意味着它执行后不向用户提供任何反馈。
该脚本的主函数看上去基本与前面具的示例相同,除了几个微不足道的异常。此代码会利用递归概念多次调用主函数,直至处理完最后一个目录为止。您可以采用 os.walk 函数,而不必采用递归来完成相同的任务。文件检查发生在 check 函数中,仅简单地检查以波浪号(~)或者井号(#)结束的文件,以井号后开始或扩展名 .pyc 结束的文件。该示例展示了 Python 标准库 os 模块所提供的数量众多的函数。它还提供了独立于操作系统方式来操作路径名和目录,以及执行文件操作的示例。清单 2 展示了该脚本的代码。
清单 2. 用于 cleanup 脚本的 Python 代码
#!/usr/bin/env python import pygtk pygtk.require('2.0') import gtk import os def check(path): """Returns true to indicate a file should be removed.""" if path.endswith('~'): return True if path.startswith('#') and basename.endswith('#'): return True if path.endswith('.pyc'): return True return False def walk(dirname=None): selected = os.environ.get('NAUTILUS_SCRIPT_SELECTED_FILE_PATHS', '') curdir = os.environ.get('NAUTILUS_SCRIPT_CURRENT_URI', os.curdir) if dirname is not None: targets = [dirname] elif selected: targets = selected.splitlines() else: targets = [curdir] for target in targets: if target.startswith('file:///'): target = target[7:] if not os.path.isdir(target): continue for dirname, dirnames, files in os.walk(target): for dir in dirnames: dir = os.path.join(dirname, dir) walk(dir) for file in files: file = os.path.join(dirname, file) if check(file): os.remove(file) if __name__ == '__main__': walk()
#p#
Nautilus 扩展
增强 Nautilus 的第二个方法是通过创建扩展。此方法比第一个复杂,但有很多优势。Nautilus 扩展可被内嵌到文件展示窗口中,那么就可以编写利用以前没有的信息来填充列的扩展。首先要做的就是利用如下命令安装 python-nautilus 扩展:
sudo apt-get install python-nautilus
此命令下载并安装所需的文件,包括文档和示例。可在目录 /usr/share/doc/python-nautilus/examples 中到找到示例代码。安装完成后,就可以访问一组 Nautilus 类和提供程序来再次对其进行编码。表 2 展示了该清单。
表 2. Nautilus 类与供应商
类或者供应商 | 描述 |
nautilus.Column | 引用 Nautilus column 对象 |
nautilus.FileInfo | 引用 Nautilus fileinfo 对象 |
nautilus.Menu | 引用 Nautilus menu 对象 |
nautilus.MenuItem | 引用 Nautilus menuitem 对象 |
nautilus.PropertyPage | 引用 Nautilus propertypage 对象 |
nautilus.ColumnProvider | 允许在 Nautilus 列中展示输出 |
nautilus.InfoProvider | 提供关于文件的信息 |
nautilus.LocationWidgetProvider | 展示位置 |
nautilus.MenuProvider | 为右击菜单增加新功能 |
nautilus.PropertyPageProvider | 为属性页面增加信息 |
gnome.org 站点上提供的示例展示了 MenuProvider(background-image.py 和 open-terminal.py)、ColumnProvider 以及 InfoProvider(block-size-column.py)、和 PropertyPageProvider(md5sum-property-page.py)的使用。ColumnProvider 采用 13 行 Python 可执行代码来向 Nautilus 引入新的列。一旦该代码被放置到合适的目录中(~/.nautilus/python-extensions)并且 Nautilus 已重启,在单击 View > Visible Columns 时将会看到新的选项。当将查看类型设置为 List 时,才会出现 Visible Columns 选项。通过选择展示以下 Python 库调用结果的复选框,来启用 Block size 列:
str(os.stat(filename).st_blksize))
任何 Python 扩展的基本模式都是对现有 Nautilus 提供程序基本类进行子类划分,然后执行一系列指令,并最终返回合适的 Nautilus 对象。在 block-size-column.py 例子中,返回的对象是 nautilus.Column。必须向 Nautilus 传递 4 个参数,包括 name、attribute、label、以及 description。本例子的 Python 代码是:
return nautilus.Column("NautilusPython::block_size_column", "block_size", "Block size", "Get the block size")
编写新扩展的代码涉及继承来自特定基本类的信息。 在 block-size-column.py 的例子中,nautilus.ColumnProvider 与 nautilus.InfoProvider 在类定义中有举例,因此新类要从这两处继承。接下来需要覆盖来自基类或者类的任何方法来填充列。在 block-size-column.py 例子中,可通过覆盖 get_columns 和 update_file_info 方法来完成。
向 Nautilus 扩展传递信息的方法与脚本示例不同。Nautilus 实际上是启动新的流程来执行脚本,并设置多个环境变量来传递信息。在与 Nautilus 相同的流程中执行的扩展,能够访问对象、方法、和属性。通过 nautilus.FileInfo 传递的文件信息,包括 file_type、location、name、uri、以及 mime_type。想要向 FileInfo 对象增加信息,必须调用 add_string_attribute 方法。下面的例子是采用这一方法,来向 FileInfo 对象增加新的属性。#p#
示例:列出了文件中的行数
第一个例子使用 PropertyPageProvider 方法在文件(或多个文件)上单击右键显示行数和参数,然后单击 Properties。这一扩展背后的基本思想是计算文件中的行数和参数个数,并在文件属性页的新选项卡中报告结果。扩展可以直接访问了 Nautilus 数据结构,包括 file 对象。惟一要做的是利用 urllib.unquote 库函数来打开名字,操作如下:
filename = urllib.unquote(file.get_uri()[7:]
Python 中的一些行完成了对行及参数计数的主要工作。对于本例来说,创建 count 函数来将整个文件读取到一个大字符串中,然后计算参数数量及新添参数数量。因为属性页面可被显示为很多选中的文件及目录,所以必须预先计算多个文件。此时,惟一要做的就是将结果添加到属性页上的新页中。本例创建了示例 gtk.Hbox,然后利用获取的信息来填充大量标签,如清单 3 所示。
清单 3. Linecountextension.py 文件
import nautilus import urllib import gtk import os types = ['.py','.js','.html','.css','.txt','.rst','.cgi'] exceptions = ('MochiKit.js',) class LineCountPropertyPage(nautilus.PropertyPageProvider): def __init__(self): pass def count(self, filename): s = open(filename).read() return s.count('\n'), len(s) def get_property_pages(self, files): if not len(files): return lines = 0 chars = 0 for file in files: if not file.is_directory(): result = self.count(urllib.unquote(file.get_uri()[7:])) lines += result[0] chars += result[1] self.property_label = gtk.Label('Linecount') self.property_label.show() self.hbox = gtk.HBox(0, False) self.hbox.show() label = gtk.Label('Lines:') label.show() self.hbox.pack_start(label) self.value_label = gtk.Label() self.hbox.pack_start(self.value_label) self.value_label.set_text(str(lines)) self.value_label.show() self.chars_label = gtk.Label('Characters:') self.chars_label.show() self.hbox.pack_start(self.chars_label) self.chars_value = gtk.Label() self.hbox.pack_start(self.chars_value) self.chars_value.set_text(str(chars)) self.chars_value.show() return nautilus.PropertyPage("NautilusPython::linecount", self.property_label, self.hbox),
图 3 展示了在文件上单击右键并单击 Linecount 选项卡的结果。此时,需要注意,这一特性可用于文件或者任何一组选定的文件和目录。所报告的数字将代表所有文件中的所有行。
图 3. 单击 Linecount 选项卡来查看文件的行数
最后,修改扩展函数来填充一列而不是整个属性页。因而代码的修改相当少,尽管需要同时从 nautilus.ColumnProvider 和 nautilus.InfoProvider 继承。还必须执行 get_columns 和 update_file_info。方法 get_columns 仅返回由方法 count 获取的信息。
方法 count 为列提供程序扩展采用不同的技术。Python 的 readlines 例程用于将一个文件的所有行读取到一列字符串中。计算行的总数就是在 len(s) 语句中返回的清单元素的数量。在两个例子中都要进行文件类型检查:这是要确保仅对包含需要计数行的文本文件进行计数。可利用如下行来创建一列可接受的文件扩展:
types = ['.py','.js','.html','.css','.txt','.rst','.cgi']
第二个清单包含了不会被计数的异常,对于本例来说,包含具有如下行的文件:
exceptions = ['MochiKit.js']
这两个清单用于包括或者排除具有如下两行代码的文件:
if ext not in types or basename in exceptions: return 0
整个扩展需要 26 行可执行代码。您可能想要修改扩展,并输入清单来包含或者排除感兴趣的文件,清单 4 展示了完整的扩展。
清单 4. 用于 Linecountcolumn 扩展的 Python 代码
import nautilus import urllib import os types = ['.py','.js','.html','.css','.txt','.rst','.cgi'] exceptions = ['MochiKit.js'] class LineCountExtension(nautilus.ColumnProvider, nautilus.InfoProvider): def __init__(self): pass def count(self, filename): ext = os.path.splitext(filename)[1] basename = os.path.basename(filename) if ext not in types or basename in exceptions: return 0 s = open(filename).readlines() return len(s) def get_columns(self): return nautilus.Column("NautilusPython::linecount", "linecount", "Line Count", "The number of lines of code"), def update_file_info(self, file): if file.is_directory(): lines = 'n/a' else: lines = self.count(urllib.unquote(file.get_uri()[7:])) file.add_string_attribute('linecount', str(lines))
图 4 显示了启用 Line Count 列的 Nautilus 窗口。每个单独的文件显示全部行数。您需要利用该方法进行一次计算就可以知道您总共需要多少个文件。
图 4. Nautilus 窗口中的 Line Count 列
结束语
利用 Python 来扩展 Nautilus 的确是个简单的过程。Python 与 Python 标准库非常的精巧,可用于编写高效而易读的代码。理解 gnome.org 站点中的文档与示例是很有挑战性的,但也不是不可能。Google 中的一些搜索结果也能提供一些例子。此处的例子可用于帮助您掌握如何扩展 Nautilus 来满足特定需求。如果您对 Python 编程很熟悉,那将不会有什么问题。
原文:http://www.ibm.com/developerworks/cn/linux/l-script-linux-desktop-2/index.html?ca=drs
【编辑推荐】