From 8cc708ad9059751a137c121ebf76cc9cfa412a56 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Sun, 8 Apr 2012 01:23:22 +0200 Subject: [PATCH] new snarl notification plugin --- snarl_notifications/__init__.py | 1 + snarl_notifications/manifest.ini | 11 + snarl_notifications/plugin.py | 89 ++++++++ snarl_notifications/pySnarl.py | 365 +++++++++++++++++++++++++++++++ 4 files changed, 466 insertions(+) create mode 100644 snarl_notifications/__init__.py create mode 100644 snarl_notifications/manifest.ini create mode 100644 snarl_notifications/plugin.py create mode 100644 snarl_notifications/pySnarl.py diff --git a/snarl_notifications/__init__.py b/snarl_notifications/__init__.py new file mode 100644 index 0000000..b504dfd --- /dev/null +++ b/snarl_notifications/__init__.py @@ -0,0 +1 @@ +from plugin import SnarlNotificationsPlugin diff --git a/snarl_notifications/manifest.ini b/snarl_notifications/manifest.ini new file mode 100644 index 0000000..93d0675 --- /dev/null +++ b/snarl_notifications/manifest.ini @@ -0,0 +1,11 @@ +[info] +name: Snarl Notifications +short_name: snarl_notifications +version: 0.1 +description: Shows events notification using Snarl (http://www.fullphat.net/) under Windows. Snarl needs to be installed in system. + PySnarl bindings are used (http://sourceforge.net/projects/pysnarl/). +authors = Yann Leboulanger +homepage = http://trac-plugins.gajim.org/wiki/SnarlNotificationsPlugin + + + diff --git a/snarl_notifications/plugin.py b/snarl_notifications/plugin.py new file mode 100644 index 0000000..659fdf3 --- /dev/null +++ b/snarl_notifications/plugin.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- +## +## This file is part of Gajim. +## +## Gajim is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 3 only. +## +## Gajim is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with Gajim. If not, see . +## +''' +Events notifications using Snarl + +Fancy events notifications under Windows using Snarl infrastructure. + +:note: plugin is at proof-of-concept state. + +:author: Yann Leboulanger +:since: 08 April 2012 +:copyright: Copyright (2012) Yann Leboulanger +:license: GPL +''' + +import pySnarl + +from common import gajim +from plugins import GajimPlugin +from plugins.helpers import log_calls, log +from common import ged +import os.path + +class SnarlActionHandler(pySnarl.EventHandler): + def OnNotificationInvoked(self, uid): + account, jid, msg_type = uid.split() + gajim.interface.handle_event(account, jid, msg_type) + +class SnarlNotificationsPlugin(GajimPlugin): + + @log_calls('SnarlNotificationsPlugin') + def init(self): + self.description = _('Shows events notification using Snarl ' + '(http://www.fullphat.net/) under Windows. ' + 'Snarl needs to be installed in system.\n' + 'PySnarl bindings are used (http://code.google.com/p/pysnarl/).') + self.config_dialog = None + self.h = SnarlActionHandler + self.snarl_win = pySnarl.SnarlApp( + "pySnarl/Gajim", # app signature + "Gajim", # app title + os.path.abspath("..\data\pixmaps\gajim.ico"), # icon + "", # config Tool + "Gajim will use Snarl to display notifications", # hint + False, # IsDaemon + self.h, # event handler + [] # classes + ) + + self.events_handlers = {'notification' : (ged.PRECORE, self.notif)} + + @log_calls('SnarlNotificationsPlugin') + def notif(self, obj): + if obj.do_popup: + uid = obj.conn.name + " " + obj.jid + " " + obj.popup_msg_type + self.snarl_win.notify( + [], # actions + "", # callbackScript + "", # callbackScriptType + "", # class + "", # defaultCallback + 5, # duration + os.path.abspath(obj.popup_image),#r"C:\Documents and Settings\Administrateur\Mes documents\gajim\data\pixmaps\gajim.ico", # icon + "", # mergeUID + 0, # priority + "", # replaceUID + obj.popup_text, + obj.popup_title, + uid, # UID + "", # sound + -1, # percent + 0, # log + 64, # sensitivity + ) + obj.do_popup = False diff --git a/snarl_notifications/pySnarl.py b/snarl_notifications/pySnarl.py new file mode 100644 index 0000000..5488cbe --- /dev/null +++ b/snarl_notifications/pySnarl.py @@ -0,0 +1,365 @@ +# -*- coding: utf-8 -*- +# ActiveX/COM Snarl python library +# For proper functioning requires Snarl 2.5.1 or later ! +# Version 0.0.2 + +# Changelog (in reverse chronological order): +# ------------------------------------------- +# 0.0.2 by Pako 2012-01-30 14:12 UTC+1 +# - base64 icon format is now supported +# 0.0.1 by Pako 2012-01-06 15:30 UTC+1 +# - initial version +#------------------------------------------------------- + +from win32com.client import constants, gencache, Dispatch, DispatchWithEvents +#=============================================================================== + +def IsRunning(): + from win32gui import FindWindow + return FindWindow("w>Snarl", "Snarl") +#=============================================================================== + +class EventHandler: + + def OnActivated(self): + self.fn("DaemonActivated") + + def OnNotificationActionSelected(self, uid, command): + self.fn("ActionSelected.%s" % command, payload = uid) + + def OnNotificationClosed(self, uid): + self.fn("Closed", payload = uid) + + def OnNotificationExpired(self, uid): + self.fn("Expired", payload = uid) + + def OnNotificationInvoked(self, uid): + self.fn("Invoked", payload = uid) + + def OnQuit(self): + self.fn("AppQuit") + + def OnShowAbout(self): + self.fn("ShowAbout") + + def OnShowConfig(self): + self.fn("ShowConfig") + + def OnSnarlLaunched(self): + self.fn("Launched") + + def OnSnarlQuit(self): + self.fn("Quit") + + def OnSnarlStarted(self): + self.fn("Started") + + def OnSnarlStopped(self): + self.fn("Stopped") + + def OnUserAway(self): + self.fn("UserAway") + + def OnUserReturned(self): + self.fn("UserReturned") +#=============================================================================== + +class SnarlApp(object): + """Creates an SnarlApp object""" + + def __init__( + self, + signature, + title, + icon = "", + configTool = "", + hint = "", + isDaemon = False, + eventHandler = None, + classes = [] + ): + self.app = gencache.EnsureDispatch("libsnarl25.SnarlApp") + for d in constants.__dicts__: # we must find the right dictionary ... + if 'ERROR_NOTIFICATION_NOT_FOUND' in d: # this is it ! + break + codes = d.items() + codes.sort(reverse = True) + self.statCodes = dict([[v,k] for k,v in codes[6:]]) + self.statCodes[codes[2][1]] = codes[2][0] # added SUCCESS + self.classes = NotifClasses(classes) + if eventHandler: + self.SetEventHandler(eventHandler) + self.SetTo( + configTool, + hint, + icon, + isDaemon, + signature, + title + ) + + + def SetTo( + self, + configTool, + hint, + icon, + isDaemon, + signature, + title + ): + self.app.Classes = self.classes.Classes() + self.app.ConfigTool = configTool + self.app.Hint = hint + self.app.Icon = icon + self.app.IsDaemon = isDaemon + self.app.Signature = signature + self.app.Title = title + + + def GetStatusCode(self, code): + return self.statCodes[code] + + + def SetEventHandler(self, handler): + self.events = DispatchWithEvents(self.app, handler) + + + def register(self): + return self.GetStatusCode(self.app.Register()) + + + def unregister(self): + return self.GetStatusCode(self.app.Unregister()) + + + def tidyUp(self): + self.app.TidyUp() + + + def addClass( + self, + classId, + name, + enabled = True, + title = "", + text = "", + icon = "", + callback = "", + duration = -1, + sound = "", + ): + cntOld = self.classes.count() + cntNew = self.classes.add(classId, name, enabled, title, text, icon, callback, duration, sound) + return int(not cntNew == cntOld + 1) + + + def remClass(self, id): + cntOld = self.classes.count() + cntNew = self.classes.remove(id) + return int(not cntNew == cntOld - 1) + + + def clearClasses(self): + return self.classes.makeEmpty() + + + def classesCount(self): + return self.classes.count() + + + def notify( + self, + actions, + callbackScript, + callbackScriptType, + cls, + defaultCallback, + duration, + icon, + mergeUID, + priority, + replaceUID, + text, + title, + uid, + sound = None, + percent = None, + log = None, + sensitivity = None + ): + note = Notification( + actions, + callbackScript, + callbackScriptType, + cls, + defaultCallback, + duration, + "", + mergeUID, + priority, + replaceUID, + text, + title, + uid, + ) + if icon: + if icon[1] == ":": + note.Add("icon", icon, True) + else: + note.Add("icon-base64", icon.replace("=","%"), True) + if sound: + note.Add("sound", sound, True) + if percent is not None and percent > -1: + note.Add("value-percent", str(percent), True) + if log is not None: + note.Add("log", str(log), True) + if sensitivity is not None: + note.Add("sensitivity", str(sensitivity), True) + return self.GetStatusCode(self.app.Show(note.Note())[0]) + + + def hideNotification(self, uid): + return self.GetStatusCode(self.app.Hide(uid)) + + + def isVisible(self, uid): + return self.GetStatusCode(self.app.IsVisible(uid)) + + + def getEtcPath(self): + return self.app.GetEtcPath() + + + def makePath(self, pth): + return self.app.GetEtcPath(pth) + + + def isInstalled(self): + return self.app.IsSnarlInstalled() + + + def isRunning(self): + return self.app.IsSnarlRunning() + + + def version(self): + ver = self.app.SnarlVersion() + return ver if ver > 0 else self.GetStatusCode(-ver) + + + def isConnected(self): + return self.app.IsConnected + + + def getLibVersion(self): + return self.app.LibVersion + + + def getLibRevision(self): + return self.app.LibRevision + + + def Destroy(self): + self.app.TidyUp() + if self.events: + del self.events + del self.app +#=============================================================================== + +class NotifClasses(object): + def __init__(self, classes = []): + self.clss = Dispatch("libsnarl25.Classes") + for cls in classes: + self.add(*cls) + + def count(self): + return self.clss.Count() + + def add(self, *cls): + self.clss.Add(*cls) + return self.clss.Count() + + def remove(self, cls): + self.clss.Remove(cls) + return self.clss.Count() + + def makeEmpty(self): + self.clss.MakeEmpty() + return self.clss.Count() + + def Classes(self): + return self.clss +#=============================================================================== + +class Notification(object): + def __init__( + self, + actions, + callbackScript, + callbackScriptType, + cls, + defaultCallback, + duration, + icon, + mergeUID, + priority, + replaceUID, + text, + title, + uid, + ): + nt = Dispatch("libsnarl25.Notification") + nt.Actions = NotifActions(actions).Actions() + nt.CallbackScript = callbackScript + nt.CallbackScriptType = callbackScriptType + nt.Class = cls + nt.DefaultCallback = defaultCallback + nt.Duration = duration + if icon: + nt.Icon = icon + nt.MergeUID = mergeUID + nt.Priority = priority + nt.ReplaceUID = replaceUID + nt.Text = text + nt.Title = title + nt.UID = uid + self.nt = nt + + def Add(self, name, value, update): + self.nt.Add(name, value, True) + + def Note(self): + return self.nt +#=============================================================================== + +class NotifActions(object): + def __init__(self, actions=[]): + self.actns = Dispatch("libsnarl25.Actions") + self.actions = actions + for action in actions: + self.actns.Add(*action) + + def add(self, actn): + self.actns.Add(*actn) + self.actions.append(actn) + return self.actns.Count() + + def remove(self, actn): + if actn in self.actions: + ix = self.actions.index(actn) + self.actns.Remove(ix + 1) + self.actions.pop(ix) + return self.actns.Count() + + def makeEmpty(self): + self.actns.MakeEmpty() + self.actions = [] + return self.actns.Count() + + def Actions(self): + return self.actns +#=============================================================================== + + +