diff --git a/Makefile b/Makefile index 27ab5ea1..4c392cbe 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,8 @@ install: mkdir -p /usr/local/share/sparkleshare cp SparkleShare/bin/Debug/SparkleShare.exe /usr/local/share/sparkleshare/ cp SparkleShare/bin/Debug/SparkleShare.exe.mdb /usr/local/share/sparkleshare/ + cp SparkleShare/bin/Debug/notify-sharp.dll /usr/local/share/sparkleshare/ + cp SparkleShare/bin/Debug/notify-sharp.dll.mdb /usr/local/share/sparkleshare/ chmod 755 /usr/local/share/sparkleshare/SparkleShare.exe cp sparkleshare /usr/local/bin/ chmod 755 /usr/local/bin/sparkleshare @@ -17,9 +19,7 @@ install: uninstall: rm /usr/local/bin/sparkleshare - rm /usr/local/share/sparkleshare/SparkleShare.exe - rm /usr/local/share/sparkleshare/SparkleShare.exe.mdb - rmdir /usr/local/share/sparkleshare + rm -rf /usr/local/share/sparkleshare rm /usr/share/icons/hicolor/*x*/places/folder-sparkleshare.png rm /usr/share/icons/hicolor/*x*/places/folder-sync*.png rm /usr/share/icons/hicolor/*x*/status/document-*ed.png @@ -29,5 +29,5 @@ uninstall: rm ~/.config/autostart/sparkleshare.desktop clean: - rm SparkleShare/bin/Debug/SparkleShare.exe - rm SparkleShare/bin/Debug/SparkleShare.exe.mdb + rm -rf SparkleShare/bin + rm -rf notify-sharp/bin diff --git a/SparkleShare.sln b/SparkleShare.sln index 068c1202..903866f0 100644 --- a/SparkleShare.sln +++ b/SparkleShare.sln @@ -1,13 +1,20 @@ + Microsoft Visual Studio Solution File, Format Version 9.00 # Visual Studio 2005 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SparkleShare", "SparkleShare\SparkleShare.csproj", "{728483AA-E34B-4441-BF2C-C8BC2901E4E0}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "notify-sharp", "notify-sharp\notify-sharp.csproj", "{005CCA8E-DFBF-464A-B6DA-452C62D4589C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {005CCA8E-DFBF-464A-B6DA-452C62D4589C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {005CCA8E-DFBF-464A-B6DA-452C62D4589C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {005CCA8E-DFBF-464A-B6DA-452C62D4589C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {005CCA8E-DFBF-464A-B6DA-452C62D4589C}.Release|Any CPU.Build.0 = Release|Any CPU {728483AA-E34B-4441-BF2C-C8BC2901E4E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {728483AA-E34B-4441-BF2C-C8BC2901E4E0}.Debug|Any CPU.Build.0 = Debug|Any CPU {728483AA-E34B-4441-BF2C-C8BC2901E4E0}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/SparkleShare/SparkleShare.csproj b/SparkleShare/SparkleShare.csproj index 99ba5dfb..923cd71a 100644 --- a/SparkleShare/SparkleShare.csproj +++ b/SparkleShare/SparkleShare.csproj @@ -31,7 +31,6 @@ - @@ -48,4 +47,10 @@ + + + {005CCA8E-DFBF-464A-B6DA-452C62D4589C} + notify-sharp + + diff --git a/notify-sharp/Global.cs b/notify-sharp/Global.cs new file mode 100644 index 00000000..eedefbf0 --- /dev/null +++ b/notify-sharp/Global.cs @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2006-2007 Sebastian Dröge + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +using System; +using System.Reflection; +using System.Collections.Generic; + +using NDesk.DBus; +using org.freedesktop; +using org.freedesktop.DBus; + +namespace Notifications { + [Interface ("org.freedesktop.Notifications")] + internal interface INotifications : Introspectable, Properties { + ServerInformation ServerInformation { get; } + string[] Capabilities { get; } + void CloseNotification (uint id); + uint Notify (string app_name, uint id, string icon, string summary, string body, + string[] actions, IDictionary hints, int timeout); + event NotificationClosedHandler NotificationClosed; + event ActionInvokedHandler ActionInvoked; + } + + public enum CloseReason : uint { + Expired = 1, + User = 2, + API = 3, + Reserved = 4 + } + + internal delegate void NotificationClosedHandler (uint id, uint reason); + internal delegate void ActionInvokedHandler (uint id, string action); + + public struct ServerInformation { + public string Name; + public string Vendor; + public string Version; + public string SpecVersion; + } + + public static class Global { + private const string interface_name = "org.freedesktop.Notifications"; + private const string object_path = "/org/freedesktop/Notifications"; + + private static INotifications dbus_object = null; + private static object dbus_object_lock = new object (); + + internal static INotifications DBusObject { + get { + if (dbus_object != null) + return dbus_object; + + lock (dbus_object_lock) { + if (! Bus.Session.NameHasOwner (interface_name)) + Bus.Session.StartServiceByName (interface_name); + + dbus_object = Bus.Session.GetObject + (interface_name, new ObjectPath (object_path)); + return dbus_object; + } + } + } + + public static string[] Capabilities { + get { + return DBusObject.Capabilities; + } + } + + public static ServerInformation ServerInformation { + get { + return DBusObject.ServerInformation; + } + } + } +} diff --git a/notify-sharp/Notification.cs b/notify-sharp/Notification.cs new file mode 100644 index 00000000..09ef83e6 --- /dev/null +++ b/notify-sharp/Notification.cs @@ -0,0 +1,384 @@ +/* + * Copyright (c) 2006-2007 Sebastian Dröge + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +using System; +using System.Reflection; +using System.Collections.Generic; + +using GLib; +using Gdk; +using Gtk; + +using NDesk.DBus; +using org.freedesktop; +using org.freedesktop.DBus; + +namespace Notifications { + public enum Urgency : byte { + Low = 0, + Normal, + Critical + } + + public class ActionArgs : EventArgs { + private string action; + public string Action { + get { return action; } + } + + public ActionArgs (string action) { + this.action = action; + } + } + + public class CloseArgs : EventArgs { + private CloseReason reason; + public CloseReason Reason { + get { return reason; } + } + + public CloseArgs (CloseReason reason) { + this.reason = reason; + } + } + + public delegate void ActionHandler (object o, ActionArgs args); + public delegate void CloseHandler (object o, CloseArgs args); + + public class Notification { + private struct IconData { + public int Width; + public int Height; + public int Rowstride; + public bool HasAlpha; + public int BitsPerSample; + public int NChannels; + public byte[] Pixels; + } + + private struct ActionTuple { + public string Label; + public ActionHandler Handler; + + public ActionTuple (string label, ActionHandler handler) { + Label = label; + Handler = handler; + } + } + + private INotifications nf; + + private bool updates_pending = false; + private bool shown = false; + + private string app_name; + private uint id = 0; + private int timeout = -1; + private string summary = String.Empty, body = String.Empty; + private string icon = String.Empty; + private Gtk.Widget attach_widget = null; + private Gtk.StatusIcon status_icon = null; + private IDictionary action_map = new Dictionary (); + private IDictionary hints = new Dictionary (); + + public event EventHandler Closed; + + static Notification () { + BusG.Init (); + } + + public Notification () { + nf = Global.DBusObject; + + nf.NotificationClosed += OnClosed; + nf.ActionInvoked += OnActionInvoked; + + this.app_name = Assembly.GetCallingAssembly().GetName().Name; + } + + public Notification (string summary, string body) : this () { + this.summary = summary; + this.body = body; + } + + public Notification (string summary, string body, string icon) : this (summary, body) { + this.icon = icon; + } + + public Notification (string summary, string body, Pixbuf icon) : this (summary, body) { + SetPixbufHint (icon); + } + + public Notification (string summary, string body, Pixbuf icon, Gtk.Widget widget) : this (summary, body, icon) { + AttachToWidget (widget); + } + + public Notification (string summary, string body, string icon, Gtk.Widget widget) : this (summary, body, icon) { + AttachToWidget (widget); + } + + public Notification (string summary, string body, Pixbuf icon, Gtk.StatusIcon status_icon) : this (summary, body, icon) { + AttachToStatusIcon (status_icon); + } + + public Notification (string summary, string body, string icon, Gtk.StatusIcon status_icon) : this (summary, body, icon) { + AttachToStatusIcon (status_icon); + } + + + public string Summary { + set { + summary = value; + Update (); + } + get { + return summary; + } + } + + public string Body { + set { + body = value; + Update (); + } + get { + return body; + } + } + + public int Timeout { + set { + timeout = value; + Update (); + } + get { + return timeout; + } + } + + public Urgency Urgency { + set { + hints["urgency"] = (byte) value; + Update (); + } + get { + return hints.ContainsKey ("urgency") ? (Urgency) hints["urgency"] : Urgency.Normal; + } + } + + public string Category { + set { + hints["category"] = value; + Update (); + } + get { + return hints.ContainsKey ("category") ? (string) hints["category"] : String.Empty; + } + + } + + public Pixbuf Icon { + set { + SetPixbufHint (value); + icon = String.Empty; + Update (); + } + } + + public string IconName { + set { + icon = value; + hints.Remove ("icon_data"); + Update (); + } + } + + public uint Id { + get { + return id; + } + } + + public Gtk.Widget AttachWidget { + get { + return attach_widget; + } + set { + AttachToWidget (value); + } + } + + public Gtk.StatusIcon StatusIcon { + get { + return status_icon; + } + set { + AttachToStatusIcon (value); + } + } + + private void SetPixbufHint (Pixbuf pixbuf) { + IconData icon_data = new IconData (); + icon_data.Width = pixbuf.Width; + icon_data.Height = pixbuf.Height; + icon_data.Rowstride = pixbuf.Rowstride; + icon_data.HasAlpha = pixbuf.HasAlpha; + icon_data.BitsPerSample = pixbuf.BitsPerSample; + icon_data.NChannels = pixbuf.NChannels; + + int len = (icon_data.Height - 1) * icon_data.Rowstride + icon_data.Width * + ((icon_data.NChannels * icon_data.BitsPerSample + 7) / 8); + icon_data.Pixels = new byte[len]; + System.Runtime.InteropServices.Marshal.Copy (pixbuf.Pixels, icon_data.Pixels, 0, len); + + hints["icon_data"] = icon_data; + } + + public void AttachToWidget (Gtk.Widget widget) { + int x, y; + + widget.GdkWindow.GetOrigin (out x, out y); + + if (widget.GetType() != typeof (Gtk.Window) || ! widget.GetType().IsSubclassOf(typeof (Gtk.Window))) { + x += widget.Allocation.X; + y += widget.Allocation.Y; + } + + x += widget.Allocation.Width / 2; + y += widget.Allocation.Height / 2; + + SetGeometryHints (widget.Screen, x, y); + attach_widget = widget; + status_icon = null; + } + + public void AttachToStatusIcon (Gtk.StatusIcon status_icon) { + Gdk.Screen screen; + Gdk.Rectangle rect; + Orientation orientation; + int x, y; + + if (!status_icon.GetGeometry (out screen, out rect, out orientation)) { + return; + } + + x = rect.X + rect.Width / 2; + y = rect.Y + rect.Height / 2; + + SetGeometryHints (screen, x, y); + + this.status_icon = status_icon; + attach_widget = null; + } + + public void SetGeometryHints (Screen screen, int x, int y) { + hints["x"] = x; + hints["y"] = y; + hints["xdisplay"] = screen.MakeDisplayName (); + Update (); + } + + private void Update () { + if (shown && !updates_pending) { + updates_pending = true; + GLib.Timeout.Add (100, delegate { + if (updates_pending) { + Show (); + updates_pending = false; + } + return false; + }); + } + } + + public void Show () { + string[] actions; + lock (action_map) { + actions = new string[action_map.Keys.Count * 2]; + int i = 0; + foreach (KeyValuePair pair in action_map) { + actions[i++] = pair.Key; + actions[i++] = pair.Value.Label; + } + } + id = nf.Notify (app_name, id, icon, summary, body, actions, hints, timeout); + shown = true; + } + + public void Close () { + nf.CloseNotification (id); + id = 0; + shown = false; + } + + private void OnClosed (uint id, uint reason) { + if (this.id == id) { + this.id = 0; + shown = false; + if (Closed != null) { + Closed (this, new CloseArgs ((CloseReason) reason)); + } + } + } + + public void AddAction (string action, string label, ActionHandler handler) { + if (Notifications.Global.Capabilities != null && + Array.IndexOf (Notifications.Global.Capabilities, "actions") > -1) { + lock (action_map) { + action_map[action] = new ActionTuple (label, handler); + } + Update (); + } + } + + public void RemoveAction (string action) { + lock (action_map) { + action_map.Remove (action); + } + Update (); + } + + public void ClearActions () { + lock (action_map) { + action_map.Clear (); + } + Update (); + } + + private void OnActionInvoked (uint id, string action) { + lock (action_map) { + if (this.id == id && action_map.ContainsKey (action)) + action_map[action].Handler (this, new ActionArgs (action)); + } + } + + public void AddHint (string name, object value) { + hints[name] = value; + Update (); + } + + public void RemoveHint (string name) { + hints.Remove (name); + Update (); + } + } +} diff --git a/notify-sharp/notify-sharp.csproj b/notify-sharp/notify-sharp.csproj new file mode 100644 index 00000000..6053fdf9 --- /dev/null +++ b/notify-sharp/notify-sharp.csproj @@ -0,0 +1 @@ + Debug AnyCPU 8.0.50727 2.0 {005CCA8E-DFBF-464A-B6DA-452C62D4589C} Library notifysharp notify-sharp true full false bin\Debug DEBUG prompt 4 none false bin\Release prompt 4 \ No newline at end of file