65.9K
CodeProject 正在变化。 阅读更多。
Home

AmberIndicator - Linux 下的 Systray 应用程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (10投票s)

2012年9月27日

CPOL

5分钟阅读

viewsIcon

27123

downloadIcon

154

实现 ApplicationIndicator(Ubuntu 下的 NotifyIcon)

引言

本文展示了如何实现一个 AppIndicator[^],它是一种 Ubuntu 中的 SysTray(系统托盘),并对特定的 RSS feed 进行响应。

背景

有一个名为 "AMBER Alert[^]" 的免费荷兰服务,当有儿童失踪时,它会广播全国性信息。他们提供的一个客户端是一个 Windows 应用,它驻留在您的系统托盘中,并在有警报时通知用户。在其网站上注意到还没有 Linux 版本,所以我尝试着拼凑了一个。从 Windows 平台过渡过来,我遇到了不少惊喜。本文的一半篇幅用于介绍安装包,因为我在那里花了大部分时间研究下一步该做什么。

基本上,我们监听两个 RSS feed,它们有很好的文档(英文!),在此[^]。第一个是失踪儿童列表,第二个是用于紧急警报。

如果您运行的是默认的 KDE 桌面,通用的 NotifyIcon 是不可见的;它需要一个名为 Notification Area Applet[^] 的 Gnome 面板。下图显示了在 Ubuntu 12.04 上(使用 Wine)运行的 Windows 客户端的原始 NotifyIcon,位于左下角。右上方那些是 AppIndicator

Using the Code

C# 是我喜欢的语言。我们可以使用 System.Windows.Forms.NotifyIcon[^],但那样我们还是会出现在 Gnome 面板上。幸运的是,手册中有一个使用 Gtk# 在 C# 中构建 Application Indicators[^] 的代码示例。另一个有用的示例可以在这里[^]找到。

假设您已安装 Mono 和 MonoDevelop[^] IDE,您仍然需要一个额外的包。所以,打开一个终端窗口(命令行界面),然后输入下面的命令

    apt-get install libappindicator0.1-cil-dev libappindicator0.1-cil

打开解决方案后,您会认出示例的基本框架

namespace AmberIndicator
{
    public static class Program
    {		
        static ApplicationIndicator _indicator;  // this is the "NotifyIcon"
        static ImageMenuItem _menuItemShowAlert; // a menu
        static Window _dummyForm;                // a hidden mainform	
        static string[] _stringsBag;             // magic
        static bool _alertRaised;                // whether there's an alert 
                                                 //  (an entry in the second feed)
        static bool _blinkOn;                    // whether or not the icon is 
                                                 //  blinking on/off to indicate 
                                                 //  an alert
        static System.Timers.Timer _blinkTimer;  // a timer to do said blinking
        
        public static void Main ()
        {
            Application.Init ();
			
            _stringsBag = HttpHelpers.DownloadIfModified(
                AmberQueryThread.QUERY_ROOT + "amberResources.txt")
                .Split(new string[] { "\n" }, System.StringSplitOptions.RemoveEmptyEntries);
            
            _dummyForm = new MainWindow();
            _dummyForm.Visible = false;
            		
            [...]
					
            _indicator = new ApplicationIndicator 
            (
                "amber-indicator",
                "amber16x16x8g",
                Category.ApplicationStatus,
                @"/usr/share/amberindicator" // AssemblyInfo.Location would point to /usr/bin!
            );
            _indicator.Status = Status.Active;
                
            Menu popupMenu = new Menu ();            
            [...]
            _indicator.Menu = popupMenu;
            
            new AmberQueryThread((int missingCount) => 
            {
            	UpdateAlertStatus(0 != missingCount);
            });
            
            Application.Run ();
        }

我们首先创建一个隐藏的主窗口,然后创建指示器,接着创建指示器的一些菜单,最后,我们启动一个后台线程来轮询 RSS feed。

关注点

IfModifiedSince

我们可以选择不时下载整个内容,但这会产生大量的流量。这意味着每个用户都会一遍又一遍地下载相同的 feed。巧妙的解决方案是保留服务器上最新版本的本地副本,并(不时地)询问 Web 服务器它是否有更新版本。通过设置 IfModifiedSince[^] 属性,服务器将返回一个状态码 304[^],而不是提供整个文件。这样,我们可以避免产生大量无用的流量。

    HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create("url to download");
    req.IfModifiedSince = // enter day of your local copy

    try
    {
        var response = req.GetResponse();
        string lastModified = response.Headers[HttpResponseHeader.LastModified];
        
        // since we got this far, we know that the content is outdated.
        // a 304 will throw an exception
        
        // save a local copy of the file.
    }
    catch (WebException ex)
    {
        // only handle protocol errors that have valid responses
        if (ex.Response == null || ex.Status != WebExceptionStatus.ProtocolError)
            throw;
        
        HttpStatusCode statusCode = ((HttpWebResponse)ex.Response).StatusCode;
        if (HttpStatusCode.NotModified == statusCode)  // 304, server sayin' it ain't modified
        {
            // fetch the old stored version
        }
    }
    // return the content

我在名为 HttpHelpersstatic 类中将此构造推广为 DownloadIfModified 方法。这就引出了应用程序的下一个问题;您将‘本地’版本保存在哪里?您将本地数据保存在哪里?

AssemblyDatabase

决定重用一个经过测试的方法,并使用 Sqlite[^] 数据库来存储数据。还有一个名为 AssemblyDatabasestatic 类,它创建到数据库的连接,并包含创建和初始化新数据库的逻辑。如果查看它,您会遇到一些对 AssemblyInfo.GetResourceAsString 的调用。

    internal static string GetResourceAsString(string AResourceName)
    {
        string pathAndResourceName = Path.Combine(
            @"/usr/share/amberindicator",
            AResourceName);
        return File.ReadAllText(pathAndResourceName);
    }

正如 sqlite 文档所指出的,不支持存储过程之类的东西。将查询存储为文本文件是我能想到的次优选择。它们不是传统的资源文件,只是简单的纯文本文件。您会在解决方案中找到所有 SQL 命令,在更新时简单地复制到输出目录。

StringsBag?

传统上,我们将 string 存储在资源文件中。这在一定程度上集中了事物,便于维护和翻译。由于我已经有了一种下载已修改文本文件的简单方法,因此让这些 string 在线可用似乎是一个新颖的想法。您实际上可以在此[^] 请求它们。如您所见,这是一个平面文本文件。更新它将导致客户端下载新版本的文件,从而使我能够“更新”(部分)应用程序,而无需分发更新的二进制文件。

创建安装程序

如果没有安装包,我们就无法完成,而且我们不能使用基于 MSI 的安装程序 - 我们必须创建一个所谓的“Debian 包”。这里[^] 有一个有用的教程,请务必也阅读那里的“下一页”。本质上,您需要重建要安装的整个目录树,从根目录开始。安装程序将创建下面的结构

  • /usr/bin/amberindicator.exe
  • /usr/share/amberindicator/amber16x16x8.png
  • /usr/share/amberindicator/amber16x16x8g.png
  • /usr/share/amberindicator/_CreateDb.sql
  • /usr/share/amberindicator/_FetchFromDownloadCache.sql
  • /usr/share/amberindicator/_InsertIntoDownloadCache.sql
  • /usr/share/amberindicator/_SelectLastServerModifiedFromDownloadCacheWhereSource.sql
  • /usr/share/amberindicator/_UpdateDownloadCache.sql
  • /usr/share/doc/amberindicator/changelog.Debian.gz
  • /usr/share/doc/amberindicator/changelog.gz
  • /usr/share/doc/amberindicator/copyright
  • /usr/share/man/man1/amberindicator.exe.1.gz
  • /DEBIAN/control

bin 文件夹包含主可执行文件,而应用程序的其余部分安装在 share 文件夹的子目录中。这意味着应用程序及其数据分布在两个目录中,这在我们 Windows 上是不做的。其余文件是安装程序的支持文件。

/usr/share/doc 包含(压缩的)更新日志和版权声明。这些文件必须遵循特定格式,否则包构建器将向您显示一系列错误。对于此特定项目,更新日志几乎相似。Linux 下的每个应用程序都有一个帮助文件,称为“man page”。您可以通过在终端中输入“man amberindicator”来请求手册,它也是安装程序的必需部分。

/DEBIAN/control

control 文件[^] 是您设置安装程序大部分选项的地方。这里很酷的部分是依赖项

Package: amberindicator
Version: 1.0.0-1
Section: misc
Priority: optional
Architecture: i386
Depends: debhelper (>= 5), libappindicator0.1-cil-dev (>=0.4.92-0), 
                           libappindicator0.1-cil (>=0.4.92-0), mono-runtime (>=2.10.8.1-1)
Installed-Size: 34
Maintainer: Eddy Vluggen <amberindicator@eddyvluggen.info>
Homepage: http://www.compu-link.net/index.php?id=amber-client-for-linux
Description: Statusindicator for the Dutch AMBER-alert service.
 The Dutch AMBER-alert service (http://www.amberalertnederland.nl)
 exposes a RSS-feed. Whenever a child is missing and an Alert is
 sent out, the feed is updated. The statusindicator queries the
 feed in the background, and provides a direct link to the
 page with further details.
 .
 Written under Mono.    
</amberindicator@eddyvluggen.info>

要构建它,请打开终端窗口,切换到您创建的目录结构的根目录,然后输入下面的命令

    fakeroot dpkg-deb --build debian
    lintian debian.deb

lintian 命令检查安装程序是否“足够好”。如果它不包含 lintian 所需的所有内容,则在安装应用程序时您会收到一个恼人的警告。

..最后,当一切完成后,结果如下所示

历史

  • 2012 年 9 月 26 日:版本 1.0
© . All rights reserved.