AmberIndicator - Linux 下的 Systray 应用程序






4.93/5 (10投票s)
实现 ApplicationIndicator(Ubuntu 下的 NotifyIcon)
引言
本文展示了如何实现一个 AppIndicator[^],它是一种 Ubuntu 中的 SysTray
(系统托盘),并对特定的 RSS feed 进行响应。
背景
有一个名为 "AMBER Alert[^]" 的免费荷兰服务,当有儿童失踪时,它会广播全国性信息。他们提供的一个客户端是一个 Windows 应用,它驻留在您的系统托盘中,并在有警报时通知用户。在其网站上注意到还没有 Linux 版本,所以我尝试着拼凑了一个。从 Windows 平台过渡过来,我遇到了不少惊喜。本文的一半篇幅用于介绍安装包,因为我在那里花了大部分时间研究下一步该做什么。
基本上,我们监听两个 RSS feed,它们有很好的文档(英文!),在此[^]。第一个是失踪儿童列表,第二个是用于紧急警报。
- http://missing.amberalertnederland.nl/nl/rssext.xml[^]
- http://alert.amberalertnederland.nl/nl/rssext.xml[^]
如果您运行的是默认的 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
我在名为 HttpHelpers
的 static
类中将此构造推广为 DownloadIfModified
方法。这就引出了应用程序的下一个问题;您将‘本地’版本保存在哪里?您将本地数据保存在哪里?
AssemblyDatabase
决定重用一个经过测试的方法,并使用 Sqlite[^] 数据库来存储数据。还有一个名为 AssemblyDatabase
的 static
类,它创建到数据库的连接,并包含创建和初始化新数据库的逻辑。如果查看它,您会遇到一些对 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