本文接著前幾天那兩篇《為 shell 腳本添加交互界面》和《一行代碼實(shí)現(xiàn)通用 Linux 桌面通知》,進(jìn)一步介紹如何在 Linux 下制作托盤圖標(biāo),實(shí)現(xiàn)駐留在系統(tǒng)托盤區(qū)域的程序。
最簡單的托盤程序
因?yàn)闆]有找到使用 shell 工具實(shí)現(xiàn)的托盤工具,所以就使用 Python 實(shí)現(xiàn)了。
# 導(dǎo)入 signal 以便后面使用 ctrl-c 關(guān)掉程序。
import signal
# 導(dǎo)入 Gtk 和 AppIndicator3,什么用的一眼就能看出來。
from gi.repository import Gtk as gtk
from gi.repository import AppIndicator3 as appindicator
# 使用的參數(shù)和前面那篇 zenity 的類似,無非就是“名稱”、“分類”這些。
APPINDICATOR_ID = 'myappindicator'
def main():
indicator = appindicator.Indicator.new(APPINDICATOR_ID, 'whatever', appindicator.IndicatorCategory.SYSTEM_SERVICES)
indicator.set_status(appindicator.IndicatorStatus.ACTIVE)
indicator.set_menu(gtk.Menu())
gtk.main()
# 為了方便在終端關(guān)閉程序而加上的代碼
if __name__ == "__main__":
signal.signal(signal.SIGINT, signal.SIG_DFL)
main()
直接保存為 base.py 然后使用 python 運(yùn)行即可(我是 Python 3 環(huán)境)。

添加菜單
現(xiàn)在只是有了個圖標(biāo),接下來要為托盤程序添加菜單:
import signal
from gi.repository import Gtk as gtk
from gi.repository import AppIndicator3 as appindicator
APPINDICATOR_ID = 'myappindicator'
def main():
indicator = appindicator.Indicator.new(APPINDICATOR_ID, 'whatever', appindicator.IndicatorCategory.SYSTEM_SERVICES)
indicator.set_status(appindicator.IndicatorStatus.ACTIVE)
indicator.set_menu(build_menu())
gtk.main()
def build_menu():
menu = gtk.Menu()
item_quit = gtk.MenuItem('退出')
item_quit.connect('activate', quit)
menu.append(item_quit)
menu.show_all()
return menu
def quit(source):
gtk.main_quit()
if __name__ == "__main__":
signal.signal(signal.SIGINT, signal.SIG_DFL)
main()

自定義圖標(biāo)
現(xiàn)在默認(rèn)的圖標(biāo)是圖片沒找到的圖片,所以接下來我們自定義圖標(biāo),先去 icons 目錄隨便找張 svg 來充數(shù),笑。
import os
import signal
from gi.repository import Gtk as gtk
from gi.repository import AppIndicator3 as appindicator
APPINDICATOR_ID = 'myappindicator'
def main():
indicator = appindicator.Indicator.new(APPINDICATOR_ID, os.path.abspath('my_icon.svg'), appindicator.IndicatorCategory.SYSTEM_SERVICES)
indicator.set_status(appindicator.IndicatorStatus.ACTIVE)
indicator.set_menu(build_menu())
gtk.main()
def build_menu():
menu = gtk.Menu()
item_quit = gtk.MenuItem('退出')
item_quit.connect('activate', quit)
menu.append(item_quit)
menu.show_all()
return menu
def quit(source):
gtk.main_quit()
if __name__ == "__main__":
signal.signal(signal.SIGINT, signal.SIG_DFL)
main()

添加通知?dú)馀莨δ?/h2>
很多時候我們需要發(fā)送通知到桌面提醒用戶,這里用到的是 Python 的包,實(shí)際上通過 Python 調(diào)用 zenity 命令也是可以的。
import os
import signal
import json
from gi.repository import Gtk as gtk
from gi.repository import AppIndicator3 as appindicator
from gi.repository import Notify as notify
APPINDICATOR_ID = 'myappindicator'
def main():
indicator = appindicator.Indicator.new(APPINDICATOR_ID, os.path.abspath('my_icon.svg'), appindicator.IndicatorCategory.SYSTEM_SERVICES)
indicator.set_status(appindicator.IndicatorStatus.ACTIVE)
indicator.set_menu(build_menu())
notify.init(APPINDICATOR_ID)
gtk.main()
def build_menu():
menu = gtk.Menu()
item_message = gtk.MenuItem('通知')
item_message.connect('activate', message)
menu.append(item_message)
item_quit = gtk.MenuItem('退出')
item_quit.connect('activate', quit)
menu.append(item_quit)
menu.show_all()
return menu
def fetch_message():
return "hello world"
def message(_):
notify.Notification.new("<b>Message</b>", fetch_message(), None).show()
def quit(_):
notify.uninit()
gtk.main_quit()
if __name__ == "__main__":
signal.signal(signal.SIGINT, signal.SIG_DFL)
main()

為程序添加窗口
有了基本的圖標(biāo)、菜單、通知,有時候還需要一些窗口顯示更豐富的界面。
下面代碼中調(diào)整了之前的例子:
#!/usr/bin/env python3.3
from gi.repository import Gtk
from gi.repository import AppIndicator3 as appindicator
class MyIndicator:
def __init__(self, root):
self.app = root
self.ind = appindicator.Indicator.new(
self.app.name,
"indicator-messages",
appindicator.IndicatorCategory.APPLICATION_STATUS)
self.ind.set_status (appindicator.IndicatorStatus.ACTIVE)
self.menu = Gtk.Menu()
item = Gtk.MenuItem()
item.set_label("程序窗口")
item.connect("activate", self.app.main_win.cb_show, '')
self.menu.append(item)
item = Gtk.MenuItem()
item.set_label("配置")
item.connect("activate", self.app.conf_win.cb_show, '')
self.menu.append(item)
item = Gtk.MenuItem()
item.set_label("退出")
item.connect("activate", self.cb_exit, '')
self.menu.append(item)
self.menu.show_all()
self.ind.set_menu(self.menu)
def cb_exit(self, w, data):
Gtk.main_quit()
class MyConfigWin(Gtk.Window):
def __init__(self, root):
super().__init__()
self.app = root
self.set_title(self.app.name + ' 配置窗口')
def cb_show(self, w, data):
self.show()
class MyMainWin(Gtk.Window):
def __init__(self, root):
super().__init__()
self.app = root
self.set_title(self.app.name)
def cb_show(self, w, data):
self.show()
class MyApp(Gtk.Application):
def __init__(self, app_name):
super().__init__()
self.name = app_name
self.main_win = MyMainWin(self)
self.conf_win = MyConfigWin(self)
self.indicator = MyIndicator(self)
def run(self):
Gtk.main()
if __name__ == '__main__':
app = MyApp('測試應(yīng)用')
app.run()


設(shè)置動態(tài)圖標(biāo)
除了以上這些,有時候我們還需要動態(tài)地設(shè)置托盤圖標(biāo),以表現(xiàn)程序運(yùn)行狀態(tài)。直接重置圖標(biāo)的值即可,下面是在托盤區(qū)域顯示 CPU 使用情況的例子:
from gi.repository import Gtk, GLib
try:
from gi.repository import AppIndicator3 as AppIndicator
except:
from gi.repository import AppIndicator
import re
class IndicatorCPUSpeed:
def __init__(self):
# param1: identifier of this indicator
# param2: name of icon. this will be searched for in the standard them
# dirs
# finally, the category. We're monitoring CPUs, so HARDWARE.
self.ind = AppIndicator.Indicator.new(
"indicator-cpuspeed",
"onboard-mono",
AppIndicator.IndicatorCategory.HARDWARE)
# some more information about the AppIndicator:
# http://developer.ubuntu.com/api/ubuntu-12.04/python/AppIndicator3-0.1.html
# http://developer.ubuntu.com/resources/technologies/application-indicators/
# need to set this for indicator to be shown
self.ind.set_status (AppIndicator.IndicatorStatus.ACTIVE)
# have to give indicator a menu
self.menu = Gtk.Menu()
# you can use this menu item for experimenting
item = Gtk.MenuItem()
item.set_label("Test")
item.connect("activate", self.handler_menu_test)
item.show()
self.menu.append(item)
# this is for exiting the app
item = Gtk.MenuItem()
item.set_label("Exit")
item.connect("activate", self.handler_menu_exit)
item.show()
self.menu.append(item)
self.menu.show()
self.ind.set_menu(self.menu)
# initialize cpu speed display
self.update_cpu_speeds()
# then start updating every 2 seconds
# http://developer.gnome.org/pygobject/stable/glib-functions.html#function-glib--timeout-add-seconds
GLib.timeout_add_seconds(2, self.handler_timeout)
def get_cpu_speeds(self):
"""Use regular expression to parse speeds of all CPU cores from
/proc/cpuinfo on Linux.
"""
f = open('/proc/cpuinfo')
# this gives us e.g. ['2300', '2300']
s = re.findall('cpu MHz\s*:\s*(\d+)\.', f.read())
# this will give us ['2.3', '2.3']
f = ['%.1f' % (float(i) / 1000,) for i in s]
return f
def handler_menu_exit(self, evt):
Gtk.main_quit()
def handler_menu_test(self, evt):
# we can change the icon at any time
self.ind.set_icon("indicator-messages-new")
def handler_timeout(self):
"""This will be called every few seconds by the GLib.timeout.
"""
# read, parse and put cpu speeds in the label
self.update_cpu_speeds()
# return True so that we get called again
# returning False will make the timeout stop
return True
def update_cpu_speeds(self):
f = self.get_cpu_speeds()
self.ind.set_label(' '.join(f), "8.8 8.8 8.8 8.8")
def main(self):
Gtk.main()
if __name__ == "__main__":
ind = IndicatorCPUSpeed()
ind.main()
參考資料
除了 Python,還可以使用 Electron 實(shí)現(xiàn),不過 Electron 打包運(yùn)行實(shí)在不小,我就不玩了,而且文檔好少。當(dāng)然 Qt 啦什么的也可以,但是我不會呀。
話說回來,要是有一個像 zenity 這樣可以直接用 shell 生成托盤應(yīng)用的工具那就太好了,如果你知道請務(wù)必評論留言啊~~