Python Outlook win32 event trigger when email is opened - python

my goal is to use a python script to trigger an event handler whenever I open up an email on Outlook, from there I should be able to get the data of the email that was opened, then do something with the data. There is a similar thread on how to do it via VBA (here), but I can't figure out how to translate that to python using win32com.
I have looked through the microsoft docs but can't figure out how to trigger events on MailItem objects.
https://learn.microsoft.com/en-us/office/vba/api/outlook.mailitem
The closest I had come to doing something close was by doing something like the below, which is probably not the solution as item in this case(as the doc states) does not contain the data.
import win32com.client
import pythoncom
import re
class Handler_Class(object):
def OnItemLoad(self, item):
print(item.Class)
outlook = win32com.client.DispatchWithEvents("Outlook.Application",Handler_Class)
Any ideas/suggestions appreciated! Thanks in advance!

Here's something that worked for me as proof-of-concept, from an amalgam of SO posts including this one:
How to pass arguments to win32com event handler. It prints out the Subject line and Body of a MailItem when Read.
The extra step from the OP's code is to handle the Application.ItemLoad event, and with the information passed go on to separately set a handler for the Item. Also, since your MailItem handler doesn't receive the this or self pointer in the event handler call (ie the IDispatch interface to the MailItem) you have to save it away yourself for later.
import win32com.client
import pythoncom
#Handler for Application Object
class Application_Handler(object):
def OnItemLoad(self, item):
print('Application::OnItemLoad')
#Only want to work with MailItems
if( item.Class == win32com.client.constants.olMail ):
#Get a Dispatch interface to the item
cli = win32com.client.Dispatch(item)
#Set up a handler
handler = win32com.client.WithEvents(cli,MailItem_Handler)
#Store the MailItem's Dispatch interface for use later
handler.setDisp(cli)
#Handler for MailItem object
class MailItem_Handler(object):
def setDisp(self,disp):
self._disp = disp
def OnOpen(self,bCancel):
print('MailItem::OnOpen')
def OnRead(self):
print('MailItem::OnRead')
subj = self._disp.Subject
print('Subject:',subj)
body = self._disp.Body
print('Body:',body)
outlook = win32com.client.DispatchWithEvents("Outlook.Application", Application_Handler)
#Message loop
pythoncom.PumpMessages()

Related

Dynamically add Items to a QListWIdget

I have this function
def search(self):
#creating a list just ignore
self.listWidget.clear()
self.gogo.keywordSetter(self.lineEdit.text())
self.gogo.linkSetter()
self.gogo.resultsGetter()
#iterarting through the generated lsit
for x in self.gogo.resultsContainer:
self.listWidget.update()
#print(x[2])
#x[2] is the url to an image
url = x[2]
print(url)
req = Request(url, headers={'User-Agent': 'Mozilla/5.0'})
webpage = urlopen(req).read()
pixmap = QPixmap()
pixmap.loadFromData(webpage)
icon = QIcon(pixmap)
#x[1] is a string I want to display it's a title basically
item = QListWidgetItem(icon, x[1])
size = QSize()
size.setHeight(100)
size.setWidth(400)
item.setSizeHint(size)
#item.iconSize(QSize(100, 400))
self.listWidget.addItem(item)
It works, my problem is that it displays everything only after it iterates through every item.
What I mean is that I can see it using the print statement that it IS going through the list and creating the items but no items is being displayed.
They get all displayed at once after it completely iterates through the list.
It's very slow. I know part of it is due to the image download and I can do nothing about it. But adding the items dynamically would at least make it a little more bearable.
tried to use update() and it didn't really work.
another weird behaviour is despite the clear() being the first instruction it doesn't clear the listWidget as soon as the function is called, it looks like it's due to the same thing that leads to everything being displayed at one.
UI systems use an "event loop", which is responsible of "drawing" elements and allow user interaction; that loop must be left free to do its job, and functions that operate within it must return as soon as possible to prevent "freezing" of the UI: the window is not refreshed or properly displayed, and it seems unresponsive to keyboard or mouse events.
Your function does exactly that: it blocks everything until it's finished.
Calling update() won't be enough, as it only schedules a repainting, which will only happen as soon as the main loop is able to process events. That's why long or possibly infinite for/while loops should always be avoided, as much as any blocking function like time.sleep, even for short amounts of time.
"I can do nothing about it."
Actually, not only you can, but you have to.
A possibility is to use threading, specifically QThread, which is an interface that allows to execute calls in a separate thread while providing Qt's signal/slot mechanism that can work asynchronously between thread. Using QThread and signals to communicate with the main thread is extremely important, as UI elements are not thread-safe, and must never be accessed (nor created) from external threads.
class Downloader(QThread):
imageDownloaded = pyqtSignal(int, object)
def __init__(self, parent, urlList):
super().__init__(parent)
self.urlList = urlList
def run(self):
for i, url in enumerate(self.urlList):
req = Request(url, headers={'User-Agent': 'Mozilla/5.0'})
webpage = urlopen(req).read()
self.imageDownloaded.emit(i, webpage)
class YourWidget(QWidget):
# ...
def search(self):
self.listWidget.clear()
self.gogo.keywordSetter(self.lineEdit.text())
self.gogo.linkSetter()
self.gogo.resultsGetter()
#iterating through the generated list
urlList = []
for x in self.gogo.resultsContainer:
url = x[2]
urlList.append(url)
item = QListWidgetItem(x[1])
item.setSizeHint(QSize(400, 100))
self.listWidget.addItem(item)
# the parent argument is mandatory, otherwise there won't be any
# persistent reference to the downloader, and it will be deleted
# as soon as this function returns; connecting the finished signal
# to deleteLater will delete the object when it will be completed.
downloadThread = Downloader(self, urlList)
downloadThread.imageDownloaded.connect(self.updateImage)
downloadThread.finished.connect(downloadThread.deleteLater)
downloadThread.start()
def updateImage(self, index, data):
pixmap = QPixmap()
if not pixmap.loadFromData(data) or index >= self.listWidget.count():
return
self.listWidget.item(index).setIcon(QIcon(pixmap))
Note: the code above is untested, as it's not clear what modules you're actually using for downloading, nor what self.gogo is.
A slightly different alternative of the above is to use a persistent "download manager" thread, and queue requests using a python Queue, which will be read in the run() implementation.
Consider that Qt provides the QtNetwork module that already works asynchronously, and implements the same concept.
You have to create a QNetworkAccessManager instance (one is usually enough for the whole application), then create a QNetworkRequest for each url, and finally call the manager's get() with that request, which will return a QNetworkReply that will later be used for retrieving the downloaded data.
In the following example, I'm also setting a custom property for the reply, so that when the reply will be received, we will know to what index it corresponds to: this is even more important than what done above, as QNetworkAccessManager can download in parallel, and downloads can be completed in a different order than they were requested (for instance, if the images have different sizes, or are being downloaded from different servers).
Note that the index must be set as a Qt property, and cannot (or, better, should not) be set as a python attribute, like reply.index = i. This is because the reply we're using in python is just a wrapper around the actual Qt object, and unless we keep a persistent reference to that wrapper (for instance, by adding it to a list), that attribute would be lost.
from PyQt5.QtNetwork import *
class YourWidget(QWidget):
def __init__(self):
# ...
self.downloadManager = QNetworkAccessManager()
self.downloadManager.finished.connect(self.updateImage)
# ...
def search(self):
self.listWidget.clear()
self.gogo.keywordSetter(self.lineEdit.text())
self.gogo.linkSetter()
self.gogo.resultsGetter()
for i, x in enumerate(self.gogo.resultsContainer):
item = QListWidgetItem(x[1])
item.setSizeHint(QSize(400, 100))
self.listWidget.addItem(item)
url = QUrl(x[2])
request = QNetworkRequest(url)
request.setHeader(request.UserAgentHeader, 'Mozilla/5.0')
reply = self.downloadManager.get(request)
reply.setProperty('index', i)
def updateImage(self, reply):
index = reply.property('index')
if isinstance(index, int):
pixmap = QPixmap()
if pixmap.loadFromData(reply.readAll()):
item = self.listWidget.item(index)
if item is not None:
item.setIcon(QIcon(pixmap))
reply.deleteLater()

windows toast notifications with action using python winrt module

I've been trying to get this working for a long time now and i always get stuck at detecting button presses. I made a toast notification that looks like this:
Here's my code :
import winrt.windows.ui.notifications as notifications
import winrt.windows.data.xml.dom as dom
app = '{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\\WindowsPowerShell\\v1.0\\powershell.exe'
#create notifier
nManager = notifications.ToastNotificationManager
notifier = nManager.create_toast_notifier(app)
#define your notification as string
tString = """
<toast>
<visual>
<binding template='ToastGeneric'>
<text>New notifications</text>
<text>Text</text>
<text>Second text</text>
</binding>
</visual>
<actions>
<action
content="test1"
arguments="test1"
activationType="backround"/>
<action
content="test2"
arguments="test2"
activationType="backround"/>
</actions>
</toast>
"""
print(type(notifier.update))
#convert notification to an XmlDocument
xDoc = dom.XmlDocument()
xDoc.load_xml(tString)
#display notification
notifier.show(notifications.ToastNotification(xDoc))
I don't know how to detect button presses
the only thing i figured out is that if i change the argument of the buttons to a link like this:
arguments="https://google.com"
then it will open it
Is there any way i could implement this? or is there documentation for this XML format these toast notifications use. That explains how arguments work?
Alright so I know It's been a while, but I was trying to figure out the same thing and I couldn't find a good, conclusive answer anywhere. I've finally gotten something to work with WinRT in Python 3.9 so I wanted there to be an answer somewhere that people could find!
So to start, I'm not intimately familiar with how the 'arguments' attribute works, but it doesn't seem to be important for at least simple use cases. Most of what I know came from the Windows Toast docs. Here's some code that should produce a notification and open your Documents folder when you click the button. I got a headstart from an answer in this thread but it was missing some very important steps.
import os,sys,time
import subprocess
import threading
import winrt.windows.ui.notifications as notifications
import winrt.windows.data.xml.dom as dom
# this is not called on the main thread!
def handle_activated(sender, _):
path = os.path.expanduser("~\Documents")
subprocess.Popen('explorer "{}"'.format(path))
def test_notification():
#define your notification as
tString = """
<toast duration="short">
<visual>
<binding template='ToastGeneric'>
<text>New notifications</text>
<text>Text</text>
<text>Second text</text>
</binding>
</visual>
<actions>
<action
content="Test Button!"
arguments=""
activationType="foreground"/>
</actions>
</toast>
"""
#convert notification to an XmlDocument
xDoc = dom.XmlDocument()
xDoc.load_xml(tString)
notification = notifications.ToastNotification(xDoc)
# add the activation token.
notification.add_activated(handle_activated)
#create notifier
nManager = notifications.ToastNotificationManager
#link it to your Python executable (or whatever you want I guess?)
notifier = nManager.create_toast_notifier(sys.executable)
#display notification
notifier.show(notification)
duration = 7 # "short" duration for Toast notifications
# We have to wait for the results from the notification
# If we don't, the program will just continue and maybe even end before a button is clicked
thread = threading.Thread(target=lambda: time.sleep(duration))
thread.start()
print("We can still do things while the notification is displayed")
if __name__=="__main__":
test_notification()
The key thing to note here is that you need to find a way to wait for the response to the notification, since the notification is handled by a different thread than the program that produces it. This is why your "www.google.com" example worked while others didn't, because it didn't have anything to do with the Python program.
There's likely a more elegant solution, but a quick and easy way is to just create a Python thread and wait there for a duration. This way it doesn't interfere with the rest of your program in case you need to be doing something else. If you want your program to wait for a response, use time.sleep(duration) without all the threading code to pause the whole program.
I'm not sure how it works exactly, but it seems like the add_activated function just assigns a callback handler to the next available block in the XML. So if you wanted to add another button, my guess is that you can just do add_activated with another callback handler in the same order as you've listed your buttons.
Edit: I played around with it some and it turns out this lets you click anywhere, not just on the button. Not sure where to go from there but it's worth a heads up.

Inproc speech recognition engine in Python

I'm currently using PySpeech to recognize speech. I'm trying to get voice recognition to start without Windows Speech Recognition's default commands.
From googling, I've found that changing this line in speech.py from:
_recognizer = win32com.client.Dispatch("SAPI.SpSharedRecognizer")
to:
_recognizer = win32com.client.Dispatch("SAPI.SpInprocRecognizer")
doesn't include all the default commands. When I test whether or not the recognizer is listening, it returns false. At this point I'm just trying to get pySpeech to recognize what I'm saying and say it back to me.
Test Code:
import speech
speech.say("say something") #<--- says "say something"
print speech.input() #<--- gets stuck here
print speech.islistening() #<----- prints False
speech.py:
"""
speech recognition and voice synthesis module.
Please let me know if you like or use this module -- it would make my day!
speech.py: Copyright 2008 Michael Gundlach (gundlach at gmail)
License: Apache 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
For this module to work, you'll need pywin32 (http://tinyurl.com/5ezco9
for Python 2.5 or http://tinyurl.com/5uzpox for Python 2.4) and
the Microsoft Speech kit (http://tinyurl.com/zflb).
Classes:
Listener: represents a command to execute when phrases are heard.
Functions:
say(phrase): Say the given phrase out loud.
input(prompt, phraselist): Block until input heard, then return text.
stoplistening(): Like calling stoplistening() on all Listeners.
islistening(): True if any Listener is listening.
listenforanything(callback): Run a callback when any text is heard.
listenfor(phraselist, callback): Run a callback when certain text is heard.
Very simple usage example:
import speech
speech.say("Say something.")
print "You said " + speech.input()
def L1callback(phrase, listener):
print phrase
def L2callback(phrase, listener):
if phrase == "wow":
listener.stoplistening()
speech.say(phrase)
# callbacks are executed on a separate events thread.
L1 = speech.listenfor(["hello", "good bye"], L1callback)
L2 = speech.listenforanything(L2callback)
assert speech.islistening()
assert L2.islistening()
L1.stoplistening()
assert not L1.islistening()
speech.stoplistening()
"""
from win32com.client import constants as _constants
import win32com.client
import pythoncom
import time
import thread
# Make sure that we've got our COM wrappers generated.
from win32com.client import gencache
gencache.EnsureModule('{C866CA3A-32F7-11D2-9602-00C04F8EE628}', 0, 5, 0)
_voice = win32com.client.Dispatch("SAPI.SpVoice")
_recognizer = win32com.client.Dispatch("SAPI.SpInprocRecognizer")
_listeners = []
_handlerqueue = []
_eventthread=None
class Listener(object):
"""Listens for speech and calls a callback on a separate thread."""
_all = set()
def __init__(self, context, grammar, callback):
"""
This should never be called directly; use speech.listenfor()
and speech.listenforanything() to create Listener objects.
"""
self._grammar = grammar
Listener._all.add(self)
# Tell event thread to create an event handler to call our callback
# upon hearing speech events
_handlerqueue.append((context, self, callback))
_ensure_event_thread()
def islistening(self):
"""True if this Listener is listening for speech."""
return self in Listener._all
def stoplistening(self):
"""Stop listening for speech. Returns True if we were listening."""
try:
Listener._all.remove(self)
except KeyError:
return False
# This removes all refs to _grammar so the event handler can die
self._grammar = None
if not Listener._all:
global _eventthread
_eventthread = None # Stop the eventthread if it exists
return True
_ListenerBase = win32com.client.getevents("SAPI.SpSharedRecoContext")
class _ListenerCallback(_ListenerBase):
"""Created to fire events upon speech recognition. Instances of this
class automatically die when their listener loses a reference to
its grammar. TODO: we may need to call self.close() to release the
COM object, and we should probably make goaway() a method of self
instead of letting people do it for us.
"""
def __init__(self, oobj, listener, callback):
_ListenerBase.__init__(self, oobj)
self._listener = listener
self._callback = callback
def OnRecognition(self, _1, _2, _3, Result):
# When our listener stops listening, it's supposed to kill this
# object. But COM can be funky, and we may have to call close()
# before the object will die.
if self._listener and not self._listener.islistening():
self.close()
self._listener = None
if self._callback and self._listener:
newResult = win32com.client.Dispatch(Result)
phrase = newResult.PhraseInfo.GetText()
self._callback(phrase, self._listener)
def say(phrase):
"""Say the given phrase out loud."""
_voice.Speak(phrase)
def input(prompt=None, phraselist=None):
"""
Print the prompt if it is not None, then listen for a string in phraselist
(or anything, if phraselist is None.) Returns the string response that is
heard. Note that this will block the thread until a response is heard or
Ctrl-C is pressed.
"""
def response(phrase, listener):
if not hasattr(listener, '_phrase'):
listener._phrase = phrase # so outside caller can find it
listener.stoplistening()
if prompt:
print prompt
if phraselist:
listener = listenfor(phraselist, response)
else:
listener = listenforanything(response)
while listener.islistening():
time.sleep(.1)
return listener._phrase # hacky way to pass back a response...
def stoplistening():
"""
Cause all Listeners to stop listening. Returns True if at least one
Listener was listening.
"""
listeners = set(Listener._all) # clone so stoplistening can pop()
returns = [l.stoplistening() for l in listeners]
return any(returns) # was at least one listening?
def islistening():
"""True if any Listeners are listening."""
return not not Listener._all
def listenforanything(callback):
"""
When anything resembling English is heard, callback(spoken_text, listener)
is executed. Returns a Listener object.
The first argument to callback will be the string of text heard.
The second argument will be the same listener object returned by
listenforanything().
Execution takes place on a single thread shared by all listener callbacks.
"""
return _startlistening(None, callback)
def listenfor(phraselist, callback):
"""
If any of the phrases in the given list are heard,
callback(spoken_text, listener) is executed. Returns a Listener object.
The first argument to callback will be the string of text heard.
The second argument will be the same listener object returned by
listenfor().
Execution takes place on a single thread shared by all listener callbacks.
"""
return _startlistening(phraselist, callback)
def _startlistening(phraselist, callback):
"""
Starts listening in Command-and-Control mode if phraselist is
not None, or dictation mode if phraselist is None. When a phrase is
heard, callback(phrase_text, listener) is executed. Returns a
Listener object.
The first argument to callback will be the string of text heard.
The second argument will be the same listener object returned by
listenfor().
Execution takes place on a single thread shared by all listener callbacks.
"""
# Make a command-and-control grammar
context = _recognizer.CreateRecoContext()
grammar = context.CreateGrammar()
if phraselist:
grammar.DictationSetState(0)
# dunno why we pass the constants that we do here
rule = grammar.Rules.Add("rule",
_constants.SRATopLevel + _constants.SRADynamic, 0)
rule.Clear()
for phrase in phraselist:
rule.InitialState.AddWordTransition(None, phrase)
# not sure if this is needed - was here before but dupe is below
grammar.Rules.Commit()
# Commit the changes to the grammar
grammar.CmdSetRuleState("rule", 1) # active
grammar.Rules.Commit()
else:
grammar.DictationSetState(1)
return Listener(context, grammar, callback)
def _ensure_event_thread():
"""
Make sure the eventthread is running, which checks the handlerqueue
for new eventhandlers to create, and runs the message pump.
"""
global _eventthread
if not _eventthread:
def loop():
while _eventthread:
pythoncom.PumpWaitingMessages()
if _handlerqueue:
(context,listener,callback) = _handlerqueue.pop()
# Just creating a _ListenerCallback object makes events
# fire till listener loses reference to its grammar object
_ListenerCallback(context, listener, callback)
time.sleep(.5)
_eventthread = 1 # so loop doesn't terminate immediately
_eventthread = thread.start_new_thread(loop, ())
Well, as I mentioned, in-process recognizers don't have default input sources or recognition engines set up. In order to get the in-process recognizer to listen, you need to set these via _recognizer.SetInput (to set the input source) and _recognizer.SetRecognizer (to set the recognition engine)
The challenge for you is to get the default input source and recognition engine, respectively. If you were using C++, this would be straightforward; there's a helper function in sphelper.h that gets the default input source: SpGetDefaultTokenFromCategoryId(SPCAT_AUDIOIN, &cpToken), and I published a function on my blog that gets the default recognition engine.
But I don't know how to translate those functions into Python; perhaps you do.
I found the following post when I faced the same problem.
Basically, you have to modify some lines on speech.py:
Change the line _recognizer = win32com.client.Dispatch("SAPI.SpSharedRecognizer") into _recognizer = win32com.client.Dispatch("SAPI.SpInProcRecognizer")
Add this line after the previous line: _recognizer.AudioInputStream = win32com.client.Dispatch("SAPI.SpMMAudioIn")
Change the line _ListenerBase = win32com.client.getevents("SAPI.SpSharedRecoContext") into _ListenerBase = win32com.client.getevents("SAPI.SpInProcRecoContext")
This question is really old but here's a module based on PySpeech (Python 2) that supports both InProcRecognizers and SharedRecognizer: winspeech. Works for both Python 2 and 3.

libvlc and dbus interface

I'm trying a to create a basic media player using libvlc which will be controlled through dbus. I'm using the gtk and libvlc bindings for python. The code is based on the official example from the vlc website
The only thing I modified is to add the dbus interface to the vlc instance
# Create a single vlc.Instance() to be shared by (possible) multiple players.
instance = vlc.Instance()
print vlc.libvlc_add_intf(instance, "dbus"); // this is what i added. // returns 0 which is ok
All is well, the demo works and plays any video files. but for some reason the dbus control module doesn't work (I can't believe I just said the dreaded "doesn't work" words):
I already have the working client dbus code which binds to the MPRIS 2 interface. I can control a normal instance of a VLC media player - that works just fine, but with the above example nothing happens. The dbus control module is loaded properly, since libvlc_add_intf doesn't return an error and i can see the MPRIS 2 service in D-Feet (org.mpris.MediaPlayer2.vlc).
Even in D-Feet, trying to call any of the methods of the dbus vlc object returns no error but nothing happens.
Do I need to configure something else in order to make the dbus module control the libvlc player?
Thanks
UPDATE
It seems that creating the vlc Instance and setting a higher verbosity, shows that the DBus calls are received but they have no effect whatsoever on the player itself.
Also, adding the RC interface to the instance instead of DBus, has some problems too: When I run the example from the command line it drops me to the RC interface console where i can type the control commands, but it has the same behaviour as DBus - nothing happens, no error, nada, absolutely nothing. It ignores the commands completely.
Any thoughts?
UPDATE 2
Here is the code that uses libvlc to create a basic player:
from dbus.mainloop.glib import DBusGMainLoop
import gtk
import gobject
import sys
import vlc
from gettext import gettext as _
# Create a single vlc.Instance() to be shared by (possible) multiple players.
instance = vlc.Instance("--one-instance --verbose 2")
class VLCWidget(gtk.DrawingArea):
"""Simple VLC widget.
Its player can be controlled through the 'player' attribute, which
is a vlc.MediaPlayer() instance.
"""
def __init__(self, *p):
gtk.DrawingArea.__init__(self)
self.player = instance.media_player_new()
def handle_embed(*args):
if sys.platform == 'win32':
self.player.set_hwnd(self.window.handle)
else:
self.player.set_xwindow(self.window.xid)
return True
self.connect("map", handle_embed)
self.set_size_request(640, 480)
class VideoPlayer:
"""Example simple video player.
"""
def __init__(self):
self.vlc = VLCWidget()
def main(self, fname):
self.vlc.player.set_media(instance.media_new(fname))
w = gtk.Window()
w.add(self.vlc)
w.show_all()
w.connect("destroy", gtk.main_quit)
self.vlc.player.play()
DBusGMainLoop(set_as_default = True)
gtk.gdk.threads_init()
gobject.MainLoop().run()
if __name__ == '__main__':
if not sys.argv[1:]:
print "You must provide at least 1 movie filename"
sys.exit(1)
if len(sys.argv[1:]) == 1:
# Only 1 file. Simple interface
p=VideoPlayer()
p.main(sys.argv[1])
the script can be run from the command line like:
python example_vlc.py file.avi
The client code which connects to the vlc dbus object is too long to post so instead pretend that i'm using D-Feet to get the bus connection and post messages to it.
Once the example is running, i can see the players dbus interface in d-feet, but i am unable to control it. Is there anything else that i should add to the code above to make it work?
I can't see your implementation of your event loop, so it's hard to tell what might be causing commands to not be recognized or to be dropped. Is it possible your threads are losing the stacktrace information and are actually throwing exceptions?
You might get more responses if you added either a psuedo-code version of your event loop and DBus command parsing or a simplified version?
The working programs found on nullege.com use ctypes. One which acted as a server used rpyc. Ignoring that one.
The advantages of ctypes over dbus is a huge speed advantage (calling the C library code, not interacting using python) as well as not requiring the library to implement the dbus interface.
Didn't find any examples using gtk or dbus ;-(
Notable examples
PyNuvo vlc.py
Milonga Tango DJing program
Using dbus / gtk
dbus uses gobject mainloop, not gtk mainloop. Totally different beasts. Don't cross the streams! Some fixes:
Don't need this. Threads are evil.
gtk.gdk.threads_init()
gtk.main_quit() shouldn't work when using gobject Mainloop. gobject mainloop can't live within ur class.
if __name__ == '__main__':
loop = gobject.MainLoop()
loop.run()
Pass in loop into ur class. Then call to quit the app
loop.quit()
dbus (notify) / gtk working example
Not going to write ur vlc app for u. But here is a working example of using dbus / gtk. Just adapt to vlc. Assumed u took my advise on gtk above. As u know any instance of DesktopNotify must be called while using gobject.Mainloop . But u can place it anywhere within ur main class.
desktop_notify.py
from __future__ import print_function
import gobject
import time, dbus
from dbus.exceptions import DBusException
from dbus.mainloop.glib import DBusGMainLoop
class DesktopNotify(object):
""" Notify-OSD ubuntu's implementation has a 20 message limit. U've been warned. When queue is full, delete old message before adding new messages."""
#Static variables
dbus_loop = None
dbus_proxy = None
dbus_interface = None
loop = None
#property
def dbus_name(self):
return ("org.freedesktop.Notifications")
#property
def dbus_path(self):
return ("/org/freedesktop/Notifications")
#property
def dbus_interface(self):
return self.dbus_name
def __init__(self, strInit="initializing passive notification messaging")
strProxyInterface = "<class 'dbus.proxies.Interface'>"
""" Reinitializing dbus when making a 2nd class instance would be bad"""
if str(type(DesktopNotify.dbus_interface)) != strProxyInterface:
DesktopNotify.dbus_loop = DBusGMainLoop(set_as_default=True)
bus = dbus.SessionBus(mainloop=DesktopNotify.dbus_loop)
DesktopNotify.dbus_proxy = bus.get_object(self.dbus_name, self.dbus_path)
DesktopNotify.dbus_interface = dbus.Interface(DesktopNotify.dbus_proxy, self.dbus_interface )
DesktopNotify.dbus_proxy.connect_to_signal("NotificationClosed", self.handle_closed)
def handle_closed(self, *arg, **kwargs):
""" Notification closed by user or by code. Print message or not"""
lngNotificationId = int(arg[0])
lngReason = int(arg[1])
def pop(self, lngID):
""" ID stored in database, but i'm going to skip this and keep it simple"""
try:
DesktopNotify.dbus_interface.CloseNotification(lngID)
except DBusException as why:
print(self.__class__.__name__ + ".pop probably no message with id, lngID, why)
finally:
pass
def push(self, strMsgTitle, strMsg, dictField):
""" Create a new passive notification (took out retrying and handling full queues)"""
now = time.localtime( time.time() )
strMsgTime = strMsg + " " + time.asctime(now)
del now
strMsgTime = strMsgTime % dictField
app_name="[your app name]"
app_icon = ''
actions = ''
hint = ''
expire_timeout = 10000 #Use seconds * 1000
summary = strMsgTitle
body = strMsgTime
lngNotificationID = None
try:
lngNotificationID = DesktopNotify.dbus_interfacec.Notify(app_name, 0, app_icon, summary, body, actions, hint, expire_timeout)
except DBusException as why:
#Excellent spot to delete oldest notification and then retry
print(self.__class__.__name__ + ".push Being lazy. Posting passive notification was unsuccessful.", why)
finally:
#Excellent spot to add to database upon success
pass

Working with Mountain Lion's Notification Center using PyObjC

I'm trying to send notifications to Mountain Lion from my python script and react to clicks on the notifications. Sending the notifications works perfectly find by now. But yet I was not able to get Lion to call back my script upon a click.
Here is what I do. I implemented a Notification class. The only purpose of an instance of that class is to provide notifications by invoking notify(). In the same method I set the object the app's delegate.
import Foundation
import objc
import AppKit
class MountainLionNotification(Foundation.NSObject, Notification):
def notify(self, title, subtitle, text, url):
NSUserNotification = objc.lookUpClass('NSUserNotification')
NSUserNotificationCenter = objc.lookUpClass('NSUserNotificationCenter')
notification = NSUserNotification.alloc().init()
notification.setTitle_(str(title))
notification.setSubtitle_(str(subtitle))
notification.setInformativeText_(str(text))
notification.setSoundName_("NSUserNotificationDefaultSoundName")
notification.setUserInfo_({"action":"open_url", "value":url})
AppKit.NSApplication.sharedApplication().setDelegate_(self)
NSUserNotificationCenter.defaultUserNotificationCenter().scheduleNotification_(notification)
def applicationDidFinishLaunching_(self, sender):
userInfo = sender.userInfo()
if userInfo["action"] == "open_url":
import subprocess
subprocess.Popen(['open', userInfo["value"]])
Now I expected applicationDidFinishLaunching_() to be called upon a click on the notification. Unfortunately that never happens. What am I doing wrong?
Ok, found it. Didn't run AppHelper.runEventLoop(). Obviously a facepalm mistake. The following code works:
class MountainLionNotification(Foundation.NSObject, Notification):
def notify(self, title, subtitle, text, url):
NSUserNotification = objc.lookUpClass('NSUserNotification')
NSUserNotificationCenter = objc.lookUpClass('NSUserNotificationCenter')
notification = NSUserNotification.alloc().init()
notification.setTitle_(str(title))
notification.setSubtitle_(str(subtitle))
notification.setInformativeText_(str(text))
notification.setSoundName_("NSUserNotificationDefaultSoundName")
notification.setHasActionButton_(True)
notification.setOtherButtonTitle_("View")
notification.setUserInfo_({"action":"open_url", "value":url})
NSUserNotificationCenter.defaultUserNotificationCenter().setDelegate_(self)
NSUserNotificationCenter.defaultUserNotificationCenter().scheduleNotification_(notification)
def userNotificationCenter_didActivateNotification_(self, center, notification):
userInfo = notification.userInfo()
if userInfo["action"] == "open_url":
import subprocess
subprocess.Popen(['open', userInfo["value"]])

Categories

Resources