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"]])
Related
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()
I am trying to load a web page using PySide's QtWebKit module. According to the documentation (Elements of QWebView; QWebFrame::toHtml()), the following script should print the HTML of the Google Search Page:
from PySide import QtCore
from PySide import QtGui
from PySide import QtWebKit
# Needed if we want to display the webpage in a widget.
app = QtGui.QApplication([])
view = QtWebKit.QWebView(None)
view.setUrl(QtCore.QUrl("http://www.google.com/"))
frame = view.page().mainFrame()
print(frame.toHtml())
But alas it does not. All that is printed is the method's equivalent of a null response:
<html><head></head><body></body></html>
So I took a closer look at the setUrl documentation:
The view remains the same until enough data has arrived to display the new url.
This made me think that maybe I was calling the toHtml() method too soon, before a response has been received from the server. So I wrote a class that overrides the setUrl method, blocking until the loadFinished signal is triggered:
import time
class View(QtWebKit.QWebView):
def __init__(self, *args, **kwargs):
super(View, self).__init__(*args, **kwargs)
self.completed = True
self.loadFinished.connect(self.setCompleted)
def setCompleted(self):
self.completed = True
def setUrl(self, url):
self.completed = False
super(View, self).setUrl(url)
while not self.completed:
time.sleep(0.2)
view = View(None)
view.setUrl(QtCore.QUrl("http://www.google.com/"))
frame = view.page().mainFrame()
print(frame.toHtml())
That made no difference at all. What am I missing here?
EDIT: Merely getting the HTML of a page is not my end game here. This is a simplified example of code that was not working the way I expected it to. Credit to Oleh for suggesting replacing time.sleep() with app.processEvents()
Copied from my other answer:
from PySide.QtCore import QObject, QUrl, Slot
from PySide.QtGui import QApplication
from PySide.QtWebKit import QWebPage, QWebSettings
qapp = QApplication([])
def load_source(url):
page = QWebPage()
page.settings().setAttribute(QWebSettings.AutoLoadImages, False)
page.mainFrame().setUrl(QUrl(url))
class State(QObject):
src = None
finished = False
#Slot()
def loaded(self, success=True):
self.finished = True
if self.src is None:
self.src = page.mainFrame().toHtml()
state = State()
# Optional; reacts to DOM ready, which happens before a full load
def js():
page.mainFrame().addToJavaScriptWindowObject('qstate$', state)
page.mainFrame().evaluateJavaScript('''
document.addEventListener('DOMContentLoaded', qstate$.loaded);
''')
page.mainFrame().javaScriptWindowObjectCleared.connect(js)
page.mainFrame().loadFinished.connect(state.loaded)
while not state.finished:
qapp.processEvents()
return state.src
load_source downloads the data from an URL and returns the HTML after modification by WebKit. It wraps Qt's event loop with its asynchronous events, and is a blocking function.
But you really should think what you're doing. Do you actually need to invoke the engine and get the modified HTML? If you just want to download HTML of some webpage, there are much, much simpler ways to do this.
Now, the problem with the code in your answer is you don't let Qt do anything. There is no magic happening, no code running in background. Qt is based on an event loop, and you never let it enter that loop. This is usually achieved by calling QApplication.exec_ or with a workaround processEvents as shown in my code. You can replace time.sleep(0.2) with app.processEvents() and it might just work.
I am trying to be able to manage multiple instances of Selenium at the same time, but haven't had much luck. I'm not 100% sure if it's possible. I have an application with a GUI built with PyQT that retrieves our client's information from our SQL database. It's a fairly simple app that lets our users easily log in and out of our clients' accounts. They click the client's name, press "Login", it launches an instance of Firefox, logs into the account, and stays open so the user can do whatever they need to do. When they are done, they click the "Logout" button, and it logs out of the account and quits the webdriver instance.
What I'm trying to provide is a way for them to log into multiple accounts at once, while still maintaining the ability to click one of the client's names that they are logged into, process the logout on that account, and close that browser instance.
One thing I was hoping is to be able to control the webdriver by either a process ID, or unique ID, in which I can store in a dictionary linking it to that client, so when they click the client's name in the app, and press logout, it uses something in PyQT like "client_name = self.list_item.currentItem().text()" to get the name of the client they have selected (which I'm already using for other things, too), finds the unique ID or process ID, and sends the logout command to that instance, and then closes that instance.
This may not be the best way to go about doing it, but it's the only thing I could think of.
EDIT: I also know that you can retrieve the Selenium session_id with driver.session_id (considering your webdriver instance is assigned as 'driver'), but i have seen nothing so far on being able to control a webdriver instance by this session_id.
EDIT2: Here is an incredibly stripped down version of what I have:
from selenium import webdriver
from PyQt4 import QtGui, QtCore
class ClientAccountManager(QtGui.QMainWindow):
def __init__(self):
super(ClientAccountManager, self).__init__()
grid = QtGui.QGridLayout()
# Creates the list box
self.client_list = QtGui.QListWidget(self)
# Populates the list box with owner data
for name in client_names.itervalues():
item = QtGui.QListWidgetItem(name)
self.client_list.addItem(item)
# Creates the login button
login_btn = QtGui.QPushButton("Login", self)
login_btn.connect(login_btn, QtCore.SIGNAL('clicked()'), self.login)
# Creates the logout button
logout_btn = QtGui.QPushButton("Logout", self)
logout_btn.connect(logout_btn, QtCore.SIGNAL('clicked()'), self.logout)
def login(self):
# Finds the owner info based on who is selected
client_name = self.client_list.currentItem().text()
client_username, client_password = get_credentials(client_name)
# Creates browser instance
driver = webdriver.Firefox()
# Logs in
driver.get('https://www.....com/login.php')
driver.find_element_by_id('userNameId').send_keys(client_username)
driver.find_element_by_id('passwordId').send_keys(client_password)
driver.find_element_by_css_selector('input[type=submit]').click()
def logout(self):
# Finds the owner info based on who is selected
client_name = self.client_list.currentItem().text()
# Logs out
driver.get('https://www....com/logout.php')
# Closes the browser instance
driver.quit()
def main():
app = QtGui.QApplication(sys.argv)
cpm = ClientAccountManager()
cpm.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
You can have multiple drivers. Just call webdriver.Firefox() multiple times and keep references to each driver. Some people report oddball behavior, but it basically works.
driver.close() will close the browser and does not take an id.
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
I have created a brand new project in XCode and have the following in my AppDelegate.py file:
from Foundation import *
from AppKit import *
class MyApplicationAppDelegate(NSObject):
def applicationDidFinishLaunching_(self, sender):
NSLog("Application did finish launching.")
statusItem = NSStatusBar.systemStatusBar().statusItemWithLength_(NSVariableStatusItemLength)
statusItem.setTitle_(u"12%")
statusItem.setHighlightMode_(TRUE)
statusItem.setEnabled_(TRUE)
However, when I launch the application no status bar item shows up. All the other code in main.py and main.m is default.
The above usage of .retain() is required because the statusItem is being destroyed upon return from the applicationDidFinishLaunching() method. Bind that variable as a field in instances of MyApplicationAppDelegate using self.statusItem instead.
Here is a modified example that does not require a .xib / etc...
from Foundation import *
from AppKit import *
from PyObjCTools import AppHelper
start_time = NSDate.date()
class MyApplicationAppDelegate(NSObject):
state = 'idle'
def applicationDidFinishLaunching_(self, sender):
NSLog("Application did finish launching.")
self.statusItem = NSStatusBar.systemStatusBar().statusItemWithLength_(NSVariableStatusItemLength)
self.statusItem.setTitle_(u"Hello World")
self.statusItem.setHighlightMode_(TRUE)
self.statusItem.setEnabled_(TRUE)
# Get the timer going
self.timer = NSTimer.alloc().initWithFireDate_interval_target_selector_userInfo_repeats_(start_time, 5.0, self, 'tick:', None, True)
NSRunLoop.currentRunLoop().addTimer_forMode_(self.timer, NSDefaultRunLoopMode)
self.timer.fire()
def sync_(self, notification):
print "sync"
def tick_(self, notification):
print self.state
if __name__ == "__main__":
app = NSApplication.sharedApplication()
delegate = MyApplicationAppDelegate.alloc().init()
app.setDelegate_(delegate)
AppHelper.runEventLoop()
I had to do this to make it work:
Open MainMenu.xib. Make sure the class of the app delegate is MyApplicationAppDelegate. I'm not sure if you will have to do this, but I did. It was wrong and so the app delegate never got called in the first place.
Add statusItem.retain() because it gets autoreleased right away.