Skip to content

Commit

Permalink
Log with Growl on 10.7, with notification center on 10.8 and up. Fix #59
Browse files Browse the repository at this point in the history
.
  • Loading branch information
Fred Wenzel committed Nov 7, 2014
1 parent a619223 commit 8f549de
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 31 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -6,6 +6,7 @@ master
* New icon, retina and dark mode compatible. Fix #48. Fix #41.
* Updated and fixed background image for DMG file. Fix #61.
* Added menu item to search for updates. Fix #54.
* Log with Growl on 10.7, with notification center on 10.8 and up. Fix #59.

v2.0 (8/22/2014)
----------------
Expand Down
10 changes: 10 additions & 0 deletions lib/logger.py
@@ -0,0 +1,10 @@
"""Global logging helper"""
import logging


LOG_LEVEL = logging.DEBUG


logging.basicConfig(level=LOG_LEVEL)

log = logging.getLogger('upshot')
117 changes: 110 additions & 7 deletions lib/notifications.py
Expand Up @@ -3,8 +3,60 @@
import objc
from Foundation import *

from lib.logger import log

class Growler(NSObject):

# Darwin 12.0 is OS X 10.8, and so forth
try:
# uname is (sysname, nodename, release, version, machine)
OS_VERSION = os.uname()[3].split('.')[0]
except:
OS_VERSION = 0


class CommonNotifier(NSObject):
"""Shared code between notifiers."""

callbacks = {} # References to callback handlers.

def register_callback(self, callback):
"""
Callback is a callable we want to hold on to for later click handling.
It'll be stored as a weak reference (i.e., garbage collectable) in
self.callbacks.
Returns serializable callback ID.
"""
callback_id = str(callback.__hash__())

# XXX: We're not cleaning these up because more than one notification
# will have the same handler. If we do this a lot, this will leak memory.
self.callbacks[callback_id] = callback

log.debug('Registered callback ID %s' % callback_id)
return callback_id


def click_handler(self, context):
"""
Handle a click by handing context data back to a previously registered
callback.
context is 'callback_id:string', e.g., '12345:/a/path/to/open'.
"""
if not context:
return

callback_id, data = context.split(':', 1)

callback = self.callbacks.get(callback_id)
if callback:
log.debug('Click callback %s with context %s' % (
callback.__name__, data))
callback(data)


class Growler(CommonNotifier):
"""Growl wrapper."""

def init(self):
Expand All @@ -17,19 +69,70 @@ def init(self):

self._growl = GrowlApplicationBridge
self._growl.setGrowlDelegate_(self)
self._callback = lambda ctx: None

return self

def growlNotificationWasClicked_(self, context):
self._callback(context)

def setCallback(self, callback):
self._callback = callback
if context:
self.click_handler(context)

def notify(self, title='', description='', context=None):
GrowlApplicationBridge.notifyWithTitle_description_notificationName_iconData_priority_isSticky_clickContext_(
#title, description, self.name,
NSString.stringWithString_(title),
NSString.stringWithString_(description),
NSString.stringWithString_(self.name),
None, 0, False, context or NSDate.date())
None, 0, False, context or '')


class OSXNotifier(CommonNotifier):
"""Notification Center wrapper."""
_center = None

def init(self):
self = super(OSXNotifier, self).init()

self._center = NSUserNotificationCenter.defaultUserNotificationCenter()
self._center.setDelegate_(self)

return self

def userNotificationCenter_didActivateNotification_(self, center, notification):
"""Handle click."""
context = notification.userInfo()
if context:
self.click_handler(context['data'])

def notify(self, title='', description='', context=None):
notification = NSUserNotification.alloc().init()
notification.setTitle_(title)
#notification.setSubtitle_(title)
notification.setInformativeText_(description)

if context:
notification.setUserInfo_({'data': context})

self._center.deliverNotification_(notification)


_notifier = None # Singleton.
def notify(title='', description='', context=None, callback=None):
"""Fire a notification to the right notification service."""
global _notifier

# Initialize notification center.
if not _notifier:
if OS_VERSION >= 12:
_notifier = OSXNotifier.alloc().init()
else:
_notifier = Growler.alloc().init()

# Register callback handler and store its ID in the serializable context
# data.
if callback:
callback_id = _notifier.register_callback(callback)
ctx = ':'.join(map(str, (callback_id, context)))
else:
ctx = None

_notifier.notify(title, description, ctx)
41 changes: 17 additions & 24 deletions upshot.py
@@ -1,6 +1,5 @@
#!/usr/bin/env python
import glob
import logging
import os
import shutil
import sys
Expand All @@ -17,8 +16,9 @@

import DropboxDetect
import Preferences
from lib import utils
from lib.notifications import Growler
from lib import notifications, utils
from lib.logger import log
from lib.notifications import notify
from lib.updater import SparkleUpdater
from lib.windows import alert

Expand All @@ -35,11 +35,6 @@

TIME_THRESHOLD = 15 # How many seconds after creation do we handle a file?

# Set up logging
LOG_LEVEL = logging.DEBUG
logging.basicConfig(level=LOG_LEVEL)
log = logging.getLogger('upshot')

# Local settings
try:
from settings_local import *
Expand Down Expand Up @@ -86,7 +81,9 @@ def applicationDidFinishLaunching_(self, notification):
sw.openURL_(NSURL.URLWithString_(DROPBOX_PUBLIC_INFO))
self.quit_(self)

# Initialize things.
self.build_menu()

# Go do something useful.
if utils.get_pref('dropboxid'):
self.startListening_()
Expand Down Expand Up @@ -218,8 +215,7 @@ def startListening_(self, sender=None):
log.debug('Listening for screen shots to be added to: %s' % (
SCREENSHOT_DIR))

growl = Growler.alloc().init()
growl.notify('UpShot started', 'and listening for screenshots!')
notify('UpShot started', 'and listening for screenshots!')

def stopListening_(self, sender=None):
"""Stop listening to changes ot the screenshot dir."""
Expand All @@ -229,13 +225,12 @@ def stopListening_(self, sender=None):
self.observer = None
log.debug('Stop listening for screenshots.')

growl = Growler.alloc().init()
if sender == self.menuitems['quit']:
growl.notify('UpShot shutting down',
'Not listening for screenshots anymore!')
notify('UpShot shutting down',
'Not listening for screenshots anymore!')
else:
growl.notify('UpShot paused',
'Not listening for screenshots for now!')
notify('UpShot paused',
'Not listening for screenshots for now!')
self.update_menu()

def restart_(self, sender=None):
Expand Down Expand Up @@ -310,22 +305,20 @@ def handle_screenshot_candidate(self, f):

# Create shared URL.
url = utils.share_url(urllib.quote(shared_name))
logging.debug('Share URL is %s' % url)
log.debug('Share URL is %s' % url)

logging.debug('Copying to clipboard.')
log.debug('Copying to clipboard.')
utils.pbcopy(url)

# Notify user.
growl = Growler.alloc().init()
growl.setCallback(self.notify_callback)
growl.notify('Screenshot shared!',
'Your URL is: %s\n\n'
'Click here to view file.' % url,
context=target_file)
notify('Screenshot shared!',
'Your URL is: %s\n\n'
'Click here to view file.' % url,
context=target_file, callback=self.notify_callback)

def notify_callback(self, filepath):
"""
When growl notification is clicked, open Finder with shared file.
When notification is clicked, open Finder with shared file.
"""
ws = NSWorkspace.sharedWorkspace()
ws.activateFileViewerSelectingURLs_(
Expand Down

0 comments on commit 8f549de

Please sign in to comment.