簡年7:手摸手教你制作通用 Linux 托盤應(yīng)用

本文接著前幾天那兩篇《為 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)境)。


沒有圖標(biāo)的托盤演示

添加菜單

現(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()
添加一個菜單項(xiàng)

自定義圖標(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()
自定義圖標(biāo)

添加通知?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()
發(fā)送通知菜單

為程序添加窗口

有了基本的圖標(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ù)必評論留言啊~~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容