| 1 | /*
 | 
|---|
| 2 |  * growlnotifier.cpp - A simple Qt interface to Growl
 | 
|---|
| 3 |  *
 | 
|---|
| 4 |  * Copyright (C) 2005  Remko Troncon
 | 
|---|
| 5 |  *
 | 
|---|
| 6 |  * This program is free software; you can redistribute it and/or
 | 
|---|
| 7 |  * modify it under the terms of the GNU General Public License
 | 
|---|
| 8 |  * as published by the Free Software Foundation; either version 2
 | 
|---|
| 9 |  * of the License, or (at your option) any later version.
 | 
|---|
| 10 |  *
 | 
|---|
| 11 |  * You can also redistribute and/or modify this program under the
 | 
|---|
| 12 |  * terms of the Psi License, specified in the accompanied COPYING
 | 
|---|
| 13 |  * file, as published by the Psi Project; either dated January 1st,
 | 
|---|
| 14 |  * 2005, or (at your option) any later version.
 | 
|---|
| 15 |  *
 | 
|---|
| 16 |  * This program is distributed in the hope that it will be useful,
 | 
|---|
| 17 |  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
|---|
| 18 |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
|---|
| 19 |  * GNU General Public License for more details.
 | 
|---|
| 20 |  *
 | 
|---|
| 21 |  * You should have received a copy of the GNU General Public License
 | 
|---|
| 22 |  * along with this library; if not, write to the Free Software
 | 
|---|
| 23 |  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 | 
|---|
| 24 |  *
 | 
|---|
| 25 |  */
 | 
|---|
| 26 | 
 | 
|---|
| 27 | 
 | 
|---|
| 28 | /*
 | 
|---|
| 29 |  * TODO:
 | 
|---|
| 30 |  * - Write a destructor, which CFReleases all datastructures
 | 
|---|
| 31 |  * - Make the signal/slot work when Growl is fixed
 | 
|---|
| 32 |  **/ 
 | 
|---|
| 33 | 
 | 
|---|
| 34 | extern "C" {
 | 
|---|
| 35 |         #include <CoreFoundation/CoreFoundation.h>
 | 
|---|
| 36 |         #include <Growl/Growl.h>
 | 
|---|
| 37 | }
 | 
|---|
| 38 | 
 | 
|---|
| 39 | #include <qstringlist.h>
 | 
|---|
| 40 | #include <qpixmap.h>
 | 
|---|
| 41 | #include <qbuffer.h>
 | 
|---|
| 42 | 
 | 
|---|
| 43 | //#include <sys/types.h>
 | 
|---|
| 44 | //#include <unistd.h>
 | 
|---|
| 45 | 
 | 
|---|
| 46 | #include "growlnotifier.h"
 | 
|---|
| 47 | 
 | 
|---|
| 48 | //------------------------------------------------------------------------------ 
 | 
|---|
| 49 | 
 | 
|---|
| 50 | /*!
 | 
|---|
| 51 |  * A class for emitting a clicked signal to the interested party.
 | 
|---|
| 52 |  */
 | 
|---|
| 53 | class GrowlNotifierSignaler : public QObject
 | 
|---|
| 54 | {
 | 
|---|
| 55 |         Q_OBJECT
 | 
|---|
| 56 | 
 | 
|---|
| 57 | public: 
 | 
|---|
| 58 |         GrowlNotifierSignaler() { };
 | 
|---|
| 59 |         void emitNotificationClicked(void* context) { emit notificationClicked(context); }
 | 
|---|
| 60 |         void emitNotificationTimeout(void* context) { emit notificationTimedOut(context); }
 | 
|---|
| 61 | 
 | 
|---|
| 62 | signals:
 | 
|---|
| 63 |         void notificationClicked(void*);
 | 
|---|
| 64 |         void notificationTimedOut(void*);
 | 
|---|
| 65 | };
 | 
|---|
| 66 | 
 | 
|---|
| 67 | //------------------------------------------------------------------------------ 
 | 
|---|
| 68 | 
 | 
|---|
| 69 | /*!
 | 
|---|
| 70 |  * Converts a QString to a CoreFoundation string, preserving Unicode.
 | 
|---|
| 71 |  *
 | 
|---|
| 72 |  * \param s the string to be converted.
 | 
|---|
| 73 |  * \return a reference to a CoreFoundation string.
 | 
|---|
| 74 |  */
 | 
|---|
| 75 | static CFStringRef qString2CFString(const QString& s)
 | 
|---|
| 76 | {
 | 
|---|
| 77 |         if (s.isNull())
 | 
|---|
| 78 |                 return 0;
 | 
|---|
| 79 |         
 | 
|---|
| 80 |         ushort* buffer = new ushort[s.length()];
 | 
|---|
| 81 |         for (unsigned int i = 0; i < s.length(); ++i)
 | 
|---|
| 82 |                 buffer[i] = s[i].unicode();
 | 
|---|
| 83 |         CFStringRef result = CFStringCreateWithBytes ( NULL, 
 | 
|---|
| 84 |                 (UInt8*) buffer, 
 | 
|---|
| 85 |                 s.length()*sizeof(ushort),
 | 
|---|
| 86 |                 kCFStringEncodingUnicode, false);
 | 
|---|
| 87 | 
 | 
|---|
| 88 |         delete buffer;
 | 
|---|
| 89 |         return result;
 | 
|---|
| 90 | }
 | 
|---|
| 91 | 
 | 
|---|
| 92 | //------------------------------------------------------------------------------ 
 | 
|---|
| 93 | 
 | 
|---|
| 94 | /*!
 | 
|---|
| 95 |  * Retrieves the values from the context.
 | 
|---|
| 96 |  *
 | 
|---|
| 97 |  * \param context the context
 | 
|---|
| 98 |  * \param receiver the receiving object which will be signaled when the
 | 
|---|
| 99 |  *      notification is clicked. May be NULL.
 | 
|---|
| 100 |  * \param clicked_slot the slot to be signaled when the notification is clicked.
 | 
|---|
| 101 |  * \param timeout_slot the slot to be signaled when the notification isn't clicked.
 | 
|---|
| 102 |  * \param context the context which will be passed back to the slot
 | 
|---|
| 103 |  *      May be NULL.
 | 
|---|
| 104 |  */
 | 
|---|
| 105 | void getContext( CFPropertyListRef context, GrowlNotifierSignaler** signaler, const QObject** receiver, const char** clicked_slot, const char** timeout_slot, void** qcontext/*, pid_t* pid*/)
 | 
|---|
| 106 | {
 | 
|---|
| 107 |         CFDataRef data;
 | 
|---|
| 108 | 
 | 
|---|
| 109 |         if (signaler) {
 | 
|---|
| 110 |                 data = (CFDataRef) CFArrayGetValueAtIndex((CFArrayRef) context, 0);
 | 
|---|
| 111 |                 CFDataGetBytes(data, CFRangeMake(0,CFDataGetLength(data)), (UInt8*) signaler);
 | 
|---|
| 112 |         }
 | 
|---|
| 113 | 
 | 
|---|
| 114 |         if (receiver){
 | 
|---|
| 115 |                 data = (CFDataRef) CFArrayGetValueAtIndex((CFArrayRef) context, 1);
 | 
|---|
| 116 |                 CFDataGetBytes(data, CFRangeMake(0,CFDataGetLength(data)), (UInt8*) receiver);
 | 
|---|
| 117 |         }
 | 
|---|
| 118 |         
 | 
|---|
| 119 |         if (clicked_slot) {
 | 
|---|
| 120 |                 data = (CFDataRef) CFArrayGetValueAtIndex((CFArrayRef) context, 2);
 | 
|---|
| 121 |                 CFDataGetBytes(data, CFRangeMake(0,CFDataGetLength(data)), (UInt8*) clicked_slot);
 | 
|---|
| 122 |         }
 | 
|---|
| 123 |         
 | 
|---|
| 124 |         if (timeout_slot) {
 | 
|---|
| 125 |                 data = (CFDataRef) CFArrayGetValueAtIndex((CFArrayRef) context, 3);
 | 
|---|
| 126 |                 CFDataGetBytes(data, CFRangeMake(0,CFDataGetLength(data)), (UInt8*) timeout_slot);
 | 
|---|
| 127 |         }
 | 
|---|
| 128 |         
 | 
|---|
| 129 |         if (qcontext) {
 | 
|---|
| 130 |                 data = (CFDataRef) CFArrayGetValueAtIndex((CFArrayRef) context, 4);
 | 
|---|
| 131 |                 CFDataGetBytes(data, CFRangeMake(0,CFDataGetLength(data)), (UInt8*) qcontext);
 | 
|---|
| 132 |         }
 | 
|---|
| 133 | 
 | 
|---|
| 134 |         //if (pid) {
 | 
|---|
| 135 |         //      data = (CFDataRef) CFArrayGetValueAtIndex((CFArrayRef) context, 5);
 | 
|---|
| 136 |         //      CFDataGetBytes(data, CFRangeMake(0,CFDataGetLength(data)), (UInt8*) pid);
 | 
|---|
| 137 |         //}
 | 
|---|
| 138 | }
 | 
|---|
| 139 | 
 | 
|---|
| 140 | //------------------------------------------------------------------------------ 
 | 
|---|
| 141 | 
 | 
|---|
| 142 | /*!
 | 
|---|
| 143 |  * Creates a context for a notification, which will be sent back by Growl
 | 
|---|
| 144 |  * when a notification is clicked.
 | 
|---|
| 145 |  *
 | 
|---|
| 146 |  * \param receiver the receiving object which will be signaled when the
 | 
|---|
| 147 |  *      notification is clicked. May be NULL.
 | 
|---|
| 148 |  * \param clicked_slot the slot to be signaled when the notification is clicked.
 | 
|---|
| 149 |  * \param timeout_slot the slot to be signaled when the notification isn't clicked.
 | 
|---|
| 150 |  * \param context the context which will be passed back to the slot
 | 
|---|
| 151 |  *      May be NULL.
 | 
|---|
| 152 |  * \return the context
 | 
|---|
| 153 |  */
 | 
|---|
| 154 | CFPropertyListRef createContext( GrowlNotifierSignaler* signaler, const QObject* receiver, const char* clicked_slot, const char* timeout_slot, void* qcontext /*, pid_t pid*/)
 | 
|---|
| 155 | {
 | 
|---|
| 156 |         CFDataRef context[5];
 | 
|---|
| 157 | 
 | 
|---|
| 158 |         context[0] = CFDataCreate(kCFAllocatorDefault, (const UInt8*) &signaler, sizeof(GrowlNotifierSignaler*));
 | 
|---|
| 159 |         context[1] = CFDataCreate(kCFAllocatorDefault, (const UInt8*) &receiver, sizeof(const QObject *));
 | 
|---|
| 160 |         context[2] = CFDataCreate(kCFAllocatorDefault, (const UInt8*) &clicked_slot, sizeof(const char*));
 | 
|---|
| 161 |         context[3] = CFDataCreate(kCFAllocatorDefault, (const UInt8*) &timeout_slot, sizeof(const char*));
 | 
|---|
| 162 |         context[4] = CFDataCreate(kCFAllocatorDefault, (const UInt8*) &qcontext, sizeof(void*));
 | 
|---|
| 163 |         //context[5] = CFDataCreate(kCFAllocatorDefault, (const UInt8*) &pid, sizeof(pid_t));
 | 
|---|
| 164 | 
 | 
|---|
| 165 |         CFArrayRef array = CFArrayCreate( kCFAllocatorDefault, 
 | 
|---|
| 166 |                 (const void **)context, 5, &kCFTypeArrayCallBacks );
 | 
|---|
| 167 | 
 | 
|---|
| 168 |         // Cleaning up
 | 
|---|
| 169 |         CFRelease(context[0]);
 | 
|---|
| 170 |         CFRelease(context[1]);
 | 
|---|
| 171 |         CFRelease(context[2]);
 | 
|---|
| 172 |         CFRelease(context[3]);
 | 
|---|
| 173 |         CFRelease(context[4]);
 | 
|---|
| 174 |         //CFRelease(context[5]);
 | 
|---|
| 175 | 
 | 
|---|
| 176 |         return array;
 | 
|---|
| 177 | }
 | 
|---|
| 178 | 
 | 
|---|
| 179 | //------------------------------------------------------------------------------ 
 | 
|---|
| 180 | 
 | 
|---|
| 181 | /*!
 | 
|---|
| 182 |  * The callback function, used by Growl to notify that a notification was
 | 
|---|
| 183 |  * clicked.
 | 
|---|
| 184 |  * \param context the context of the notification
 | 
|---|
| 185 |  */
 | 
|---|
| 186 | void notification_clicked(CFPropertyListRef context)
 | 
|---|
| 187 | {
 | 
|---|
| 188 |         GrowlNotifierSignaler* signaler;
 | 
|---|
| 189 |         const QObject* receiver;
 | 
|---|
| 190 |         const char* slot;
 | 
|---|
| 191 |         void* qcontext;
 | 
|---|
| 192 |         //pid_t pid;
 | 
|---|
| 193 | 
 | 
|---|
| 194 |         getContext(context, &signaler, &receiver, &slot, 0, &qcontext/*, &pid*/);
 | 
|---|
| 195 |         
 | 
|---|
| 196 |         //if (pid == getpid()) {
 | 
|---|
| 197 |         QObject::connect(signaler,SIGNAL(notificationClicked(void*)),receiver,slot);
 | 
|---|
| 198 |         signaler->emitNotificationClicked(qcontext);
 | 
|---|
| 199 |         QObject::disconnect(signaler,SIGNAL(notificationClicked(void*)),receiver,slot);
 | 
|---|
| 200 |         //}
 | 
|---|
| 201 | }
 | 
|---|
| 202 | 
 | 
|---|
| 203 | //------------------------------------------------------------------------------ 
 | 
|---|
| 204 | 
 | 
|---|
| 205 | /*!
 | 
|---|
| 206 |  * The callback function, used by Growl to notify that a notification has
 | 
|---|
| 207 |  * timed out.
 | 
|---|
| 208 |  * \param context the context of the notification
 | 
|---|
| 209 |  */
 | 
|---|
| 210 | void notification_timeout(CFPropertyListRef context)
 | 
|---|
| 211 | {
 | 
|---|
| 212 |         GrowlNotifierSignaler* signaler;
 | 
|---|
| 213 |         const QObject* receiver;
 | 
|---|
| 214 |         const char* slot;
 | 
|---|
| 215 |         void* qcontext;
 | 
|---|
| 216 |         //pid_t pid;
 | 
|---|
| 217 | 
 | 
|---|
| 218 |         getContext(context, &signaler, &receiver, 0, &slot, &qcontext /*, &pid*/);
 | 
|---|
| 219 |         
 | 
|---|
| 220 |         //if (pid == getpid()) {
 | 
|---|
| 221 |         QObject::connect(signaler,SIGNAL(notificationTimedOut(void*)),receiver,slot);
 | 
|---|
| 222 |         signaler->emitNotificationTimeout(qcontext);
 | 
|---|
| 223 |         QObject::disconnect(signaler,SIGNAL(notificationTimedOut(void*)),receiver,slot);
 | 
|---|
| 224 |         //}
 | 
|---|
| 225 | }
 | 
|---|
| 226 | 
 | 
|---|
| 227 | //------------------------------------------------------------------------------ 
 | 
|---|
| 228 | 
 | 
|---|
| 229 | /*!
 | 
|---|
| 230 |  * Constructs a GrowlNotifier.
 | 
|---|
| 231 |  *
 | 
|---|
| 232 |  * \param notifications the list names of all notifications that can be sent
 | 
|---|
| 233 |  *      by this notifier.
 | 
|---|
| 234 |  * \param default_notifications the list of names of the notifications that
 | 
|---|
| 235 |  *  should be enabled by default.
 | 
|---|
| 236 |  * \param app the name of the application under which the notifier should 
 | 
|---|
| 237 |  *      register with growl.
 | 
|---|
| 238 |  */
 | 
|---|
| 239 | GrowlNotifier::GrowlNotifier(
 | 
|---|
| 240 |         const QStringList& notifications, const QStringList& default_notifications,
 | 
|---|
| 241 |         const QString& app)
 | 
|---|
| 242 | {
 | 
|---|
| 243 |         // Initialize signaler
 | 
|---|
| 244 |         signaler_ = new GrowlNotifierSignaler();
 | 
|---|
| 245 | 
 | 
|---|
| 246 |         // All Notifications
 | 
|---|
| 247 |         QStringList::ConstIterator it;
 | 
|---|
| 248 |         CFMutableArrayRef allNotifications = CFArrayCreateMutable(
 | 
|---|
| 249 |                 kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
 | 
|---|
| 250 |         for ( it = notifications.begin(); it != notifications.end(); ++it ) 
 | 
|---|
| 251 |                 CFArrayAppendValue(allNotifications, qString2CFString(*it));
 | 
|---|
| 252 | 
 | 
|---|
| 253 |         // Default Notifications
 | 
|---|
| 254 |         CFMutableArrayRef defaultNotifications = CFArrayCreateMutable(
 | 
|---|
| 255 |                 kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
 | 
|---|
| 256 |         for ( it = default_notifications.begin(); it != default_notifications.end(); ++it ) 
 | 
|---|
| 257 |                 CFArrayAppendValue(defaultNotifications, qString2CFString(*it));
 | 
|---|
| 258 |         
 | 
|---|
| 259 |         // Initialize delegate
 | 
|---|
| 260 |         InitGrowlDelegate(&delegate_);
 | 
|---|
| 261 |         if (!app.isEmpty())
 | 
|---|
| 262 |                 delegate_.applicationName = qString2CFString(app);
 | 
|---|
| 263 |         CFTypeRef keys[] = { GROWL_NOTIFICATIONS_ALL, GROWL_NOTIFICATIONS_DEFAULT };
 | 
|---|
| 264 |         CFTypeRef values[] = { allNotifications, defaultNotifications };
 | 
|---|
| 265 |         delegate_.registrationDictionary = CFDictionaryCreate(
 | 
|---|
| 266 |                 kCFAllocatorDefault, keys, values, 2, 
 | 
|---|
| 267 |                 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
 | 
|---|
| 268 |         delegate_.growlNotificationWasClicked = ¬ification_clicked;
 | 
|---|
| 269 |         delegate_.growlNotificationTimedOut = ¬ification_timeout;
 | 
|---|
| 270 | 
 | 
|---|
| 271 |         // Register with Growl
 | 
|---|
| 272 |         Growl_SetDelegate(&delegate_);
 | 
|---|
| 273 | }
 | 
|---|
| 274 |         
 | 
|---|
| 275 | 
 | 
|---|
| 276 | /*!
 | 
|---|
| 277 |  * Sends a notification to Growl.
 | 
|---|
| 278 |  *
 | 
|---|
| 279 |  * \param name the registered name of the notification.
 | 
|---|
| 280 |  * \param title the title for the notification.
 | 
|---|
| 281 |  * \param description the description of the notification.
 | 
|---|
| 282 |  * \param icon the icon of the notification.
 | 
|---|
| 283 |  * \param sticky whether the notification should be sticky (i.e. require a 
 | 
|---|
| 284 |  *      click to discard.
 | 
|---|
| 285 |  * \param receiver the receiving object which will be signaled when the
 | 
|---|
| 286 |  *      notification is clicked. May be NULL.
 | 
|---|
| 287 |  * \param slot the slot to be signaled when the notification is clicked.
 | 
|---|
| 288 |  * \param context the context which will be passed back to the slot
 | 
|---|
| 289 |  *      May be NULL.
 | 
|---|
| 290 |  */
 | 
|---|
| 291 | void GrowlNotifier::notify(const QString& name, const QString& title, 
 | 
|---|
| 292 |         const QString& description, const QPixmap& p, bool sticky, 
 | 
|---|
| 293 |         const QObject* receiver, 
 | 
|---|
| 294 |         const char* clicked_slot, const char* timeout_slot, 
 | 
|---|
| 295 |         void* qcontext)
 | 
|---|
| 296 | {
 | 
|---|
| 297 |         // Convert the image if necessary
 | 
|---|
| 298 |         CFDataRef icon = 0;
 | 
|---|
| 299 |         if (!p.isNull()) {
 | 
|---|
| 300 |                 QByteArray img_data;
 | 
|---|
| 301 |                 QBuffer buffer(img_data);
 | 
|---|
| 302 |                 buffer.open(IO_WriteOnly);
 | 
|---|
| 303 |                 p.save(&buffer, "PNG");
 | 
|---|
| 304 |                 icon = CFDataCreate( NULL, (UInt8*) img_data.data(), img_data.size());
 | 
|---|
| 305 |         }
 | 
|---|
| 306 | 
 | 
|---|
| 307 |         // Convert strings
 | 
|---|
| 308 |         CFStringRef cf_title = qString2CFString(title);
 | 
|---|
| 309 |         CFStringRef cf_description = qString2CFString(description);
 | 
|---|
| 310 |         CFStringRef cf_name = qString2CFString(name);
 | 
|---|
| 311 | 
 | 
|---|
| 312 |         // Do notification
 | 
|---|
| 313 |         CFPropertyListRef context = createContext(signaler_, receiver, clicked_slot, timeout_slot, qcontext/*, getpid()*/);
 | 
|---|
| 314 |         Growl_NotifyWithTitleDescriptionNameIconPriorityStickyClickContext(
 | 
|---|
| 315 |                 cf_title, cf_description, cf_name, icon, 0, sticky, context);
 | 
|---|
| 316 |         
 | 
|---|
| 317 |         // Release intermediary datastructures
 | 
|---|
| 318 |         CFRelease(context);
 | 
|---|
| 319 |         if (icon) 
 | 
|---|
| 320 |                 CFRelease(icon);
 | 
|---|
| 321 |         if (cf_title) 
 | 
|---|
| 322 |                 CFRelease(cf_title);
 | 
|---|
| 323 |         if (cf_description) 
 | 
|---|
| 324 |                 CFRelease(cf_description);
 | 
|---|
| 325 |         if (cf_name) 
 | 
|---|
| 326 |                 CFRelease(cf_name);
 | 
|---|
| 327 | }
 | 
|---|
| 328 | 
 | 
|---|
| 329 | //----------------------------------------------------------------------------- 
 | 
|---|
| 330 | 
 | 
|---|
| 331 | #include "growlnotifier.moc"
 | 
|---|