基礎使用:編寫Linux 桌面腳本
本系列文章探究了如何使用 Python 來為 GNOME 桌面、screenlet 框架和 Nautilus 創(chuàng)建腳本,進而提供高生產(chǎn)率環(huán)境。桌面上的腳本啟用拖放功能,可快速訪問您經(jīng)常使用的信息和服務。本期我們將了解如何使用 screenlet 小部件工具包構(gòu)建桌面應用程序。
為 Linux 桌面開發(fā)應用程序通常需要一些類型的圖形用戶界面(Graphical User Interface,GUI)框架作為構(gòu)建基礎。選項包括針對 GNOME 桌面的 GTK+,和針對 K 桌面環(huán)境(K Desktop Environment,KDE)的 Qt。這兩個平臺提供了開發(fā)人員構(gòu)建 GUI 應用程序所需的一切,包括庫和布局工具以便創(chuàng)建用戶看到的窗口。本文向您展示如何基于 screenlet 小部件工具包構(gòu)建桌面生產(chǎn)率應用程序。
一些現(xiàn)有的應用程序?qū)w為桌面生產(chǎn)率類別,包括 GNOME Do 和 Tomboy。這些應用程序通常允許用戶直接從桌面與它們進行交互,方法為通過特定的鍵組合或從另一個應用程序(如 Mozilla Firefox)進行拖放。Tomboy 用作桌面筆記工具,支持從其他窗口拖放文本。
Screenlet 入門
您需要安裝一些程序以便開始開發(fā) screenlet。首先,使用 Ubuntu 軟件中心或命令行安裝 screenlets 包。在 Ubuntu 軟件中心內(nèi),在 Search框中鍵入 screenlets。您應該看到主要程序包和文檔的獨立安裝包兩個選項。
Python 和 Ubuntu您可使用 Python 對 screenlet 編程。Ubuntu 10.04 的基本安裝已包含了 Python 2.6,因為許多實用程序都依賴它。您可能需要其他庫,具體取決于您的應用程序要求。對于本文,我在 Ubuntu 10.04 上安裝并測試了一切。下一步,從 screenlets.org 站點下載測試 screenlet 源代碼。測試 screenlet 位于 src/share/screenlets/Test 文件夾并使用 Cairo 和 GTK,這些也是需要您安裝的。測試程序的整個源代碼位于 TestScreenlet.py 文件中。在您最喜愛的編輯器中打開此文件來查看 screenlet 的基礎結(jié)構(gòu)。
Python 高度面向?qū)ο?,因此使?class關(guān)鍵字來定義對象。在本示例中,該類被命名為 TestScreenlet并具有一些已定義的方法。在 TestScreenlet.py 中,請注意下面代碼中的第 42 行:
def __init__(self, **keyword_args):
Python 使用前后雙下劃線(__)符號,通過預定義行為識別系統(tǒng)函數(shù)。在本例中,__init__函數(shù)針對類的構(gòu)造函數(shù)的所有的意圖和目的,且包含將要在創(chuàng)建對象的新實例時執(zhí)行的任何數(shù)量的初始化步驟。按照慣例,每個類方法的第一個參數(shù)都是對該類的當前實例的引用,并命名為 self。通過此行為,可以方便地使用 self來引用它所在的實例的方法和屬性:
self.theme_name = "default"
Screenlet 框架定義了一些命名慣例和標準,在 screenlets.org 中的開發(fā)人員頁面有概述。還有 screenlet 包的源代碼以及應用程序編程接口(Application Programming Interface,API)文檔的鏈接。查看代碼還使您可以深入了解每一個函數(shù)可以通過調(diào)用參數(shù)做什么以及返回什么。
編寫簡單的 screenlet
Screenlet 的基本組成部分包括圖標文件、源代碼文件和主題文件夾。主題文件夾包含不同主題的附加文件夾。您將在 screenlets.org 上發(fā)現(xiàn)樣例模板和幫助您入門所需的文件和文件夾。
對于第一個示例來說,使用已提供的模板來創(chuàng)建基本的 “Hello World” 應用程序。此基本應用程序的代碼如清單 1 所示。
清單 1. Hello World screenlet 的 Python 代碼
#!/usr/bin/env python import screenlets class HelloWorldScreenlet(screenlets.Screenlet): __name__ = 'HelloWorld' __version__ = '0.1' __author__ = 'John Doe' __desc__ = 'Simple Hello World Screenlet' def __init__(self, **kwargs): # Customize the width and height. screenlets.Screenlet.__init__(self, width=180, height=50, **kwargs) def on_draw(self, ctx): # Change the color to white and fill the screenlet. ctx.set_source_rgb(255, 255, 255) self.draw_rectangle(ctx, 0, 0, self.width, self.height) # Change the color to black and write the message. ctx.set_source_rgb(0, 0, 0) text = 'Hello World!' self.draw_text(ctx, text, 10, 10, "Sans 9" , 20, self.width) if __name__ == "__main__": import screenlets.session screenlets.session.create_session(HelloWorldScreenlet)
每一個應用程序都必須導入 screenlet 框架并創(chuàng)建新的會話。還有一些其他的最低要求,包括任何初始化步驟以及基本繪圖函數(shù),以便在屏幕上呈現(xiàn)小部件。TestScreenlet.py 示例具有用來初始化對象的 __init__方法。在本例中,您將看到一行,包含對 screenlet 的 __init__方法的調(diào)用,它設置要為此應用程序創(chuàng)建的窗口的初始寬度和高度。
此應用程序惟一需要的其他函數(shù)是 on_draw方法。此例程將框的背景顏色設置為白色,并使用先前定義的維度繪制矩形。它將文本顏色設置為黑色,將源文本設置為 “Hello World!”,然后再繪制該文本。圖 1 顯示了在運行此 screenlet 時您應該看到什么。在文本的后面部分,您在這些簡單塊上創(chuàng)建更有用的應用程序時,此基本結(jié)構(gòu)一直存在。
圖 1. 基本 screenlet 結(jié)構(gòu)
在更復雜的 screenlet 中重用代碼
一個有關(guān)編寫 screenlet 的好處就是能夠重用來自其他應用程序的代碼。通過基于 Python 語言的廣泛開源項目,代碼重用開拓了無限的可能性。雖然每一個 screenlet 都有相同的基本結(jié)構(gòu),但是定義了的更多方法來處理不同的行為。清單 2 顯示了名為TimeTrackerScreenlet的樣例應用程序。
清單 2. Time Tracker screenlet 的 Python 代碼
#!/usr/bin/env python import screenlets import cairo import datetime class TimeTrackerScreenlet(screenlets.Screenlet): __name__ = 'TimeTrackerScreenlet' __version__ = '0.1' __author__ = 'John Doe' __desc__ = 'A basic time tracker screenlet.' theme_dir = 'themes/default' image = 'start.png' def __init__(self, **keyword_args): screenlets.Screenlet.__init__(self, width=250, height=50, **keyword_args) self.add_default_menuitems() self.y = 25 self.theme_name = 'default' self.on = False self.started = None def on_draw(self, ctx): self.draw_scaled_image(ctx, 0, 0, self.theme_dir + '/' + self.image, self.width, self.height) def on_mouse_down(self, event): if self.on: self.started = datetime.datetime.now() self.image = 'stop.png' self.on = False else: if self.started: length = datetime.datetime.now() - self.started screenlets.show_message(None, '%s seconds' % length.seconds, 'Time') self.started = None self.image = 'start.png' self.on = True def on_draw_shape(self, ctx): self.on_draw(ctx) ctx.rectangle(0, 0, self.width, self.height) ctx.fill() if __name__ == "__main__": import screenlets.session screenlets.session.create_session(TimeTrackerScreenlet)
此示例引入了幾個在您開始構(gòu)建任何有用的程序以前需要了解的概念。所有的 screenlet 應用程序都具有響應特定用戶操作或事件(如鼠標單擊或拖放操作)的能力。在此示例中,鼠標按下事件用作更改圖標狀態(tài)的觸發(fā)器。在 screenlet 運行時,顯示 start.png 圖像。單擊該圖像將其變更為 stop.png 圖像并在 self.started中記錄開始時間。單擊停止圖像將該圖像變更回 start.png 并顯示從單擊第一個圖像開始所經(jīng)過的時間量。
響應事件是另一個關(guān)鍵功能,使得構(gòu)建任何數(shù)量的不同應用程序成為可能。雖然此示例僅使用 mouse_down事件,但是您可以對由 screenlet 框架或系統(tǒng)事件(如計時器)生成的其他事件使用相同的方法。此處引入的第二個概念是持久狀態(tài)。因為您的應用程序是持續(xù)運行的,等待事件來觸發(fā)一些操作,就能夠在內(nèi)存中保持對項目的跟蹤,如單擊開始圖像的時間。如有必要,您也可以將信息保存到磁盤以供日后檢索。
通過 screenlet 自動化任務
現(xiàn)在您已經(jīng)了解了開發(fā) screenlet 背后的概念,讓我們將所有這些放到一起。大多數(shù)用戶現(xiàn)在都在使用 Really Simple Syndication (RSS) 閱讀器來閱讀博客和新聞提要。對于這個最后的示例來說,您將構(gòu)建可配置的 screenlet ,它監(jiān)視特定提要來查找關(guān)鍵字并在文本框中顯示任何重大新聞。結(jié)果將是可單擊的鏈接,可在您默認的 Web 瀏覽器中打開文章。清單 3 顯示了 RSS Search screenlet 的源代碼。
清單 3. RSS Search screenlet 的 Python 代碼
#!/usr/bin/env python from screenlets.options import StringOption, IntOption, ListOption import xml.dom.minidom import webbrowser import screenlets import urllib2 import gobject import pango import cairo class RSSSearchScreenlet(screenlets.Screenlet): __name__ = 'RSSSearch' __version__ = '0.1' __author__ = 'John Doe' __desc__ = 'An RSS search screenlet.' topic = 'Windows Phone 7' feeds = ['http://www.engadget.com/rss.xml', 'http://feeds.gawker.com/gizmodo/full'] interval = 10 __items = [] __mousesel = 0 __selected = None def __init__(self, **kwargs): # Customize the width and height. screenlets.Screenlet.__init__(self, width=250, height=300, **kwargs) self.y = 25 def on_init(self): # Add options. self.add_options_group('Search Options', 'RSS feeds to search and topic to search for.') self.add_option(StringOption('Search Options', 'topic', self.topic, 'Topic', 'Topic to search feeds for.')) self.add_option(ListOption('Search Options', 'feeds', self.feeds, 'RSS Feeds', 'A list of feeds to search for a topic.')) self.add_option(IntOption('Search Options', 'interval', self.interval, 'Update Interval', 'How frequently to update (in seconds)')) self.update() def update(self): """Search selected feeds and update results.""" self.__items = [] # Go through each feed. for feed_url in self.feeds: # Load the raw feed and find all item elements. raw = urllib2.urlopen(feed_url).read() dom = xml.dom.minidom.parseString(raw) items = dom.getElementsByTagName('item') for item in items: # Find the title and make sure it matches the topic. title = item.getElementsByTagName('title')[0].firstChild.data if self.topic.lower() not in title.lower(): continue # Shorten the title to 30 characters. if len(title) > 30: title = title[:27]+'...' # Find the link and save the item. link = item.getElementsByTagName('link')[0].firstChild.data self.__items.append((title, link)) self.redraw_canvas() # Set to update again after self.interval. self.__timeout = gobject.timeout_add(self.interval * 1000, self.update) def on_draw(self, ctx): """Called every time the screenlet is drawn to the screen.""" # Draw the background (a gradient). gradient = cairo.LinearGradient(0, self.height * 2, 0, 0) gradient.add_color_stop_rgba(1, 1, 1, 1, 1) gradient.add_color_stop_rgba(0.7, 1, 1, 1, 0.75) ctx.set_source(gradient) self.draw_rectangle_advanced (ctx, 0, 0, self.width - 20, self.height - 20, rounded_angles=(5, 5, 5, 5), fill=True, border_size=1, border_color=(0, 0, 0, 0.25), shadow_size=10, shadow_color=(0, 0, 0, 0.25)) # Make sure we have a pango layout initialized and updated. if self.p_layout == None : self.p_layout = ctx.create_layout() else: ctx.update_layout(self.p_layout) # Configure fonts. p_fdesc = pango.FontDescription() p_fdesc.set_family("Free Sans") p_fdesc.set_size(10 * pango.SCALE) self.p_layout.set_font_description(p_fdesc) # Display our text. pos = [20, 20] ctx.set_source_rgb(0, 0, 0) x = 0 self.__selected = None for item in self.__items: ctx.save() ctx.translate(*pos) # Find if the current item is under the mouse. if self.__mousesel == x and self.mouse_is_over: ctx.set_source_rgb(0, 0, 0.5) self.__selected = item[1] else: ctx.set_source_rgb(0, 0, 0) self.p_layout.set_markup('%s' % item[0]) ctx.show_layout(self.p_layout) pos[1] += 20 ctx.restore() x += 1 def on_draw_shape(self, ctx): ctx.rectangle(0, 0, self.width, self.height) ctx.fill() def on_mouse_move(self, event): """Called whenever the mouse moves over the screenlet.""" x = event.x / self.scale y = event.y / self.scale self.__mousesel = int((y -10 )/ (20)) -1 self.redraw_canvas() def on_mouse_down(self, event): """Called when the mouse is clicked.""" if self.__selected and self.mouse_is_over: webbrowser.open_new(self.__selected) if __name__ == "__main__": import screenlets.session screenlets.session.create_session(RSSSearchScreenlet)
基于前兩個示例的概念構(gòu)建,此 screenlet 使用了一些新的概念,包括 config 頁面。在 on_init例程中,為用戶添加三個選項來指定:用于跟蹤的 RSS 提要列表、用于搜索的感興趣的主題以及更新間隔。然后更新例程在運行時使用所有這些內(nèi)容。
Python 對于此種類型的任務來說是很好的語言。標準庫包括您從 RSS 提要加載可擴展標記語言(Extensible Markup Language,XML)到可搜索列表所需的一切。在 Python 中,此任務只需三行代碼:
raw = urllib2.urlopen(feed_url).read() dom = xml.dom.minidom.parseString(raw) items = dom.getElementsByTagName('item')
這三行中使用的庫包括 urllib2和 xml。在第一行中,在 feed_url地址上發(fā)現(xiàn)的完整內(nèi)容被讀取到字符串行。下一步,因為您知道該字符串包含 XML,所以使用 Python XML 庫 dom.minidom.parseString方法來創(chuàng)建由節(jié)點對象構(gòu)成的文檔對象。
最后,創(chuàng)建與名為 item的單個 XML 元素對應的元素對象列表。然后,您可以迭代此列表以便搜索您的目標主題。使用 for關(guān)鍵字,Python 具有很好的迭代項目列表的方式,如以下代碼段所示:
for item in items: # Find the title and make sure it matches the topic. title = item.getElementsByTagName('title')[0].firstChild.data if self.topic.lower() not in title.lower(): continue
將每個匹配您標準的項目添加到當前顯示的列表,該列表與 screenlet 的實例關(guān)聯(lián)。使用此方法可以運行相同 screenlet 的多個實例,每一個實例經(jīng)配置都可搜索不同的主題。更新函數(shù)的最后部分用更新的列表重新繪制文本并基于 config 頁面上的間隔觸發(fā)新的更新計時器。在默認情況下,計時器每 10 秒觸發(fā)一次,但是您可以將其更改為您想要的任何值。此計時器機制來自 gobject庫,是 GTK 框架的一部分。
此應用程序大大擴展了 on_draw方法以便適應您的新功能。Cairo 和 Pango 庫都允許創(chuàng)建一些用于文本窗口的效果。使用漸進使小部件的背景很漂亮且具有圓角和半透明。使用 Pango 為布局添加一些函數(shù)來輕松保存和存儲當前上下文。它還提供了一種基于 screenlet 當前大小生成可縮放字體的方式。
on_draw方法中最棘手的部分是處理用戶懸停在列表中的某個項目時。通過使用 for"關(guān)鍵字,您可以迭代 screenlet 中的項目以便查看用戶是否懸停在特定項目上。如果懸停在特定項上,則設置已選擇的屬性并更改顏色以便提供視覺反饋。您還可以使用一點標記,將鏈接屬性設置為粗體 —也許并不是處理問題最精致或有效的方式,但卻是有用的。在用戶單擊框中的鏈接之一時,帶有目標 URL 的 Web 瀏覽器啟動。您可以在 on_mouse_down函數(shù)中看到此功能。Python 及其庫允許通過一行代碼來啟動默認瀏覽器顯示所期望的頁面。
圖 2. 示例 screenlet
結(jié)束語
通過 Python 和 screenlet 構(gòu)建有用的桌面應用程序并不是困難的任務。最大的障礙是適應 screenlet API 以及在不同函數(shù)之間傳遞控件的技巧。雖然該文檔可能不便于閱讀,但是包含了您使用不同的函數(shù)所需要的信息??焖匍_始工作的更好辦法是修改接近您的要求的現(xiàn)有 screenlet。
原文鏈接:http://www.ibm.com/developerworks/cn/linux/l-script-linux-desktop-1/index.html?ca=drs-
【編輯推薦】