diff --git a/GNUmakefile b/GNUmakefile index 03ad17f..8a2fcd5 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -43,6 +43,7 @@ GNUSTEP_LOCAL_ADDITIONAL_MAKEFILES=back.make include $(GNUSTEP_MAKEFILES)/common.make include ./Version +-include ../back/Version # # The list of subproject directories diff --git a/Source/win32/GNUmakefile b/Source/win32/GNUmakefile index 6488f51..1bf77fb 100644 --- a/Source/win32/GNUmakefile +++ b/Source/win32/GNUmakefile @@ -33,6 +33,9 @@ include ../../config.make # The library to be compiled, as a library or as a bundle SUBPROJECT_NAME=win32 +#win32_SUBPROJECTS = MSUserNotifications +SUBPROJECTS = MSUserNotifications + win32_LOCALIZED_RESOURCE_FILES = \ # The C source files to be compiled @@ -53,5 +56,6 @@ w32_GLcontext.m \ -include GNUmakefile.preamble include $(GNUSTEP_MAKEFILES)/subproject.make +include $(GNUSTEP_MAKEFILES)/aggregate.make -include GNUmakefile.postamble diff --git a/Source/win32/MSUserNotifications/GNUmakefile b/Source/win32/MSUserNotifications/GNUmakefile new file mode 100644 index 0000000..afc5651 --- /dev/null +++ b/Source/win32/MSUserNotifications/GNUmakefile @@ -0,0 +1,59 @@ +ifeq ($(GNUSTEP_MAKEFILES),) + GNUSTEP_MAKEFILES := $(shell gnustep-config --variable=GNUSTEP_MAKEFILES 2>/dev/null) + ifeq ($(GNUSTEP_MAKEFILES),) + $(warning ) + $(warning Unable to obtain GNUSTEP_MAKEFILES setting from gnustep-config!) + $(warning Perhaps gnustep-make is not properly installed,) + $(warning so gnustep-config is not in your PATH.) + $(warning ) + $(warning Your PATH is currently $(PATH)) + $(warning ) + endif +endif + +ifeq ($(GNUSTEP_MAKEFILES),) + $(error You need to set GNUSTEP_MAKEFILES before compiling!) +endif + +BUNDLE_NAME = NSUserNotification + +NSUserNotification_NEEDS_GUI = NO +NSUserNotification_CFLAGS += -DWINVER=0x0600 -D_WIN32_IE=0x0600 -DBUILD_DLL -fms-extensions +NSUserNotification_OBJCFLAGS += -DWINVER=0x0600 -D_WIN32_IE=0x0600 -DBUILD_DLL -fms-extensions +NSUserNotification_OBJCCFLAGS += -DWINVER=0x0600 -D_WIN32_IE=0x0600 -DBUILD_DLL -fms-extensions +NSUserNotification_PRINCIPAL_CLASS = MSUserNotificationCenter +NSUserNotification_OBJC_FILES = +NSUserNotification_OBJCC_FILES = MSUserNotification.mm +NSUserNotification_BUNDLE_LIBS = -lgnustep-gui -lgdi32 +#NSUserNotification_LIBS = stdc++ +#NSUserNotification_INCLUDE_DIRS += \ +# -I/mingw/lib/gcc/mingw32/4.8.1/include/c++/mingw32 \ +# -I/mingw/lib/gcc/mingw32/4.8.1/include/c++ +NSUserNotification_RESOURCE_FILES = \ + ToastNotifications/obj/ToastNotifications-0.dll \ + TaskbarNotifications/obj/TaskbarNotifications-0.dll +#NSUserNotification_SUBPROJECTS = ToastNotifications TaskbarNotifications +SUBPROJECTS = ToastNotifications TaskbarNotifications + +# Set this to the ROOT DIRECTORY of your GNUstep build that contains the 'core' directory... +BASE_VERSION_DEFINED = +ifneq ($(MAJOR_VERSION),) + ifneq ($(MINOR_VERSION),) + BASE_VERSION_DEFINED=1 + endif +endif +ifdef ($(BASE_VERSION_DEFINED),) + ifeq ($(GNUSTEP_BUILD_ROOT),) + $(error You need to checkout core/base and/or set GNUSTEP_BUILD_ROOT to your gnustep root containing 'core/base' before compiling!) + endif + include ${GNUSTEP_BUILD_ROOT}/core/base/Version + BASE_VERSION_DEFINED=1 +endif +libgnustep-base_INTERFACE_VERSION=$(MAJOR_VERSION).$(MINOR_VERSION) +NSUserNotification_INSTALL_DIR = $(GNUSTEP_LIBRARY)/Libraries/gnustep-base/Versions/$(libgnustep-base_INTERFACE_VERSION)/Resources/ + +#include GNUmakefile.postamble + +include $(GNUSTEP_MAKEFILES)/common.make +include $(GNUSTEP_MAKEFILES)/aggregate.make +include $(GNUSTEP_MAKEFILES)/bundle.make diff --git a/Source/win32/MSUserNotifications/GNUmakefile.postamble b/Source/win32/MSUserNotifications/GNUmakefile.postamble new file mode 100644 index 0000000..e313110 --- /dev/null +++ b/Source/win32/MSUserNotifications/GNUmakefile.postamble @@ -0,0 +1,12 @@ + +# +# Make list of class names for DLL exports. Uses the actual classes from +# the .o files, so it should really have everything needed. +# +libNSUserNotifications.def: $(OBJ_FILES_TO_LINK) + rm -f $@ + rm -f _tmp.def + cat win32-def.top > $@ + nm $^ | grep '^........ [TR] _' | sed 's/[^_]*_//' > _tmp.def + cat _tmp.def | grep "_class_name_" >> $@ + rm -rf _tmp.def diff --git a/Source/win32/MSUserNotifications/Images/.gitignore b/Source/win32/MSUserNotifications/Images/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/Source/win32/MSUserNotifications/MSUserNotification.h b/Source/win32/MSUserNotifications/MSUserNotification.h new file mode 100644 index 0000000..10ffc46 --- /dev/null +++ b/Source/win32/MSUserNotifications/MSUserNotification.h @@ -0,0 +1,75 @@ +/* Interface for DKUserNotification for GNUstep + Copyright (C) 2014 Free Software Foundation, Inc. + + Written by: Marcus Mueller + Date: 2014 + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02111 USA. +*/ + +// NOTE: for the time being, NSUserNotificationCenter needs this feature. +// Whenever this restriction is lifted, we can get rid of it here as well. +#if __has_feature(objc_default_synthesize_properties) + +#include +#include + +// C++ header includes... +#include + +#include "MSUserNotificationAPI.h" + +#import + +#if OS_API_VERSION(MAC_OS_X_VERSION_10_8,GS_API_LATEST) +#include + +#import + +#if defined(__cplusplus) +extern "C" { +#endif + +// DLL API Function prototypes... +typedef BOOL __cdecl (*SendNotificationFunctionPtr)(HWND, HICON, SEND_NOTE_INFO_PTR); +typedef BOOL __cdecl (*RemoveNotificationFunctionPtr)(HICON, REMOVE_NOTE_INFO_PTR); +typedef BOOL __cdecl (*ActivationCallbackFunctionPtr)(void); +typedef BOOL __cdecl (*SetActivationCallbackPtr)(ActivationCallbackFunctionPtr); + +@class NSConnection; +@class NSArray; +@class NSMutableDictionary; + +@protocol Notifications; + +@interface MSUserNotificationCenter : NSUserNotificationCenter +{ + HICON appIcon; + NSString *appIconPath; + NSMutableDictionary *imageToIcon; + NSArray *caps; + NSUInteger uniqueID; + NSMutableDictionary *idToNotes; +} + +@end + +#if defined(__cplusplus) +} +#endif + +#endif /* OS_API_VERSION(MAC_OS_X_VERSION_10_8,GS_API_LATEST) */ +#endif /* __has_feature(objc_default_synthesize_properties) */ diff --git a/Source/win32/MSUserNotifications/MSUserNotification.mm b/Source/win32/MSUserNotifications/MSUserNotification.mm new file mode 100644 index 0000000..35f847a --- /dev/null +++ b/Source/win32/MSUserNotifications/MSUserNotification.mm @@ -0,0 +1,633 @@ +/* Implementation for NSUserNotification for GNUstep/Windows + Copyright (C) 2014 Free Software Foundation, Inc. + + Written by: Marcus Mueller + Date: 2014 + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02111 USA. +*/ + +// NOTE: for the time being, NSUserNotificationCenter needs this feature. +// Whenever this restriction is lifted, we can get rid of it here as well. +#if __has_feature(objc_default_synthesize_properties) + +#define EXPOSE_NSUserNotification_IVARS 1 +#define EXPOSE_NSUserNotificationCenter_IVARS 1 + +#import "MSUserNotification.h" +#import +#import "GNUstepBase/NSObject+GNUstepBase.h" +#import "GNUstepBase/NSDebug+GNUstepBase.h" +#import +#import +#import +#import +#import +#import +#import +#import "Foundation/NSException.h" +#import +#import +#import +#import +#import +#import + +#include +#include +#include +#include + +#if defined(__cplusplus) +extern "C" { +#endif + +static SendNotificationFunctionPtr pSendNotification = NULL; +static RemoveNotificationFunctionPtr pRemoveNotification = NULL; +static HMODULE hNotificationLib = NULL; + +static NSString * const kButtonActionKey = @"show"; + +@interface NSImage (Private) +- (NSString*)_filename; +@end + +@implementation NSImage (Private) +- (NSString*)_filename +{ + return _fileName; +} +@end + +@interface NSUserNotification () +@property (readwrite, retain) NSDate *actualDeliveryDate; +@property (readwrite, getter=isPresented) BOOL presented; +@property (readwrite) NSUserNotificationActivationType activationType; +@end + +@interface NSUserNotificationCenter (Private) +- (NSUserNotification *) deliveredNotificationWithUniqueId: (id)uniqueId; +@end + +@interface MSUserNotificationCenter (Private) +- (NSString *) cleanupTextIfNecessary: (NSString *)rawText; +@end + +@implementation MSUserNotificationCenter + +- (id) init +{ + NSDebugLLog(@"NSUserNotification", @"initializing..."); + self = [super init]; + if (self) + { + NS_DURING + { + // Initialize instance variables... + imageToIcon = [NSMutableDictionary new]; + uniqueID = 1; + + NSBundle *classBundle = [NSBundle bundleForClass: [self class]]; + NSBundle *mainBundle = [NSBundle mainBundle]; + NSDictionary *infoDict = [mainBundle infoDictionary]; + NSString *imageName = [[infoDict objectForKey:@"CFBundleIconFiles"] objectAtIndex:0]; + NSString *imageType = @""; + NSString *path = nil; + NSImage *image = [self _imageForBundleInfo:infoDict]; + + appIcon = [self _iconFromImage:image]; + appIconPath = [image _filename]; + NSLog(@"%s:bundle: %@ image: %@ icon: %p path: %@", __PRETTY_FUNCTION__, classBundle, image, appIcon, appIconPath); + + if (appIcon == NULL) + { + NSLog(@"%s:unable to load icon for bundle: %@ GetLastError: %ld", __PRETTY_FUNCTION__, mainBundle, GetLastError()); + } + + OSVERSIONINFO osvi = { 0 }; + WINBOOL bIsWindows81orLater = false; + + ZeroMemory(&osvi, sizeof(OSVERSIONINFO)); + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + + if (GetVersionEx(&osvi)== 0) + { + NSLog(@"%s:failed getting OS version with error id: %ld", __PRETTY_FUNCTION__, GetLastError()); + } + else + { +#if defined(DEBUG) + NSLog(@"%s:version number is: %d", __PRETTY_FUNCTION__, osvi.dwMajorVersion); +#endif + if (osvi.dwMajorVersion >= 6) + { + if (osvi.dwMinorVersion == 1) + { + bIsWindows81orLater = 0; + } + else + { + bIsWindows81orLater = 1; + } + + if(bIsWindows81orLater) + path = [classBundle pathForResource: @"ToastNotifications-0" ofType: @"dll"]; + else + path = [classBundle pathForResource: @"TaskbarNotifications-0" ofType: @"dll"]; + + // Try loading the corresponding DLL required for notifications... + hNotificationLib = LoadLibrary([path UTF8String]); + + // For for not good conditions... + if (hNotificationLib == NULL) + { + NSLog(@"%s:DLL load error: %ld", __PRETTY_FUNCTION__, GetLastError()); + } + else + { + pSendNotification = (SendNotificationFunctionPtr)GetProcAddress(hNotificationLib, "sendNotification"); + pRemoveNotification = (RemoveNotificationFunctionPtr)GetProcAddress(hNotificationLib, "removeNotification"); +#if 1 //defined(DEBUG) + NSLog(@"%s:DLL ptr: %p send notification ptr: %p remove ptr: %p", __PRETTY_FUNCTION__, + hNotificationLib, pSendNotification, pRemoveNotification); +#endif + } + } + } + } + NS_HANDLER + { + } + NS_ENDHANDLER + } + return self; +} + +- (void) dealloc +{ + // Cleanup any icons we generated... + NSEnumerator *iter = [imageToIcon objectEnumerator]; + HICON icon = NULL; + while ((icon = (HICON)[[iter nextObject] pointerValue]) != NULL) + { + DestroyIcon(icon); + } + + [imageToIcon release]; + [super dealloc]; +} + +- (NSImage*) _imageForBundleInfo:(NSDictionary*)infoDict +{ + NSImage *image = nil; + NSString *appIconFile = [infoDict objectForKey: @"NSIcon"]; + + if (appIconFile && ![appIconFile isEqual: @""]) + { + image = [NSImage imageNamed: appIconFile]; + } + + // Try to look up the icns file. + appIconFile = [infoDict objectForKey: @"CFBundleIconFile"]; + if (appIconFile && ![appIconFile isEqual: @""]) + { + image = [NSImage imageNamed: appIconFile]; + } + + if (image == nil) + { + image = [NSImage imageNamed: @"GNUstep"]; + } + else + { + /* Set the new image to be named 'NSApplicationIcon' ... to do that we + * must first check that any existing image of the same name has its + * name removed. + */ + [(NSImage*)[NSImage imageNamed: @"NSApplicationIcon"] setName: nil]; + // We need to copy the image as we may have a proxy here + image = AUTORELEASE([image copy]); + [image setName: @"NSApplicationIcon"]; + } + return image; +} + +- (HICON) _iconFromRep: (NSBitmapImageRep*)rep +{ + HICON result = NULL; + + if (rep) + { + int w = [rep pixelsWide]; + int h = [rep pixelsHigh]; + + // Create a windows bitmap from the image representation's bitmap... + if ((w > 0) && (h > 0)) + { + BITMAP bm; + HDC hDC = GetDC(NULL); + HDC hMainDC = CreateCompatibleDC(hDC); + HDC hAndMaskDC = CreateCompatibleDC(hDC); + HDC hXorMaskDC = CreateCompatibleDC(hDC); + HBITMAP hAndMaskBitmap = NULL; + HBITMAP hXorMaskBitmap = NULL; + + // Create the source bitmap... + HBITMAP hSourceBitmap = CreateBitmap(w, h, [rep numberOfPlanes], [rep bitsPerPixel], [rep bitmapData]); + + // Get the dimensions of the source bitmap + GetObject(hSourceBitmap, sizeof(BITMAP), &bm); + + // Create compatible bitmaps for the device context... + hAndMaskBitmap = CreateCompatibleBitmap(hDC, bm.bmWidth, bm.bmHeight); + hXorMaskBitmap = CreateCompatibleBitmap(hDC, bm.bmWidth, bm.bmHeight); + + // Select the bitmaps to DC + HBITMAP hOldMainBitmap = (HBITMAP)SelectObject(hMainDC, hSourceBitmap); + HBITMAP hOldAndMaskBitmap = (HBITMAP)SelectObject(hAndMaskDC, hAndMaskBitmap); + HBITMAP hOldXorMaskBitmap = (HBITMAP)SelectObject(hXorMaskDC, hXorMaskBitmap); + + /* On windows, to calculate the color for a pixel, first an AND is done + * with the background and the "and" bitmap, then an XOR with the "xor" + * bitmap. This means that when the data in the "and" bitmap is 0, the + * pixel will get the color as specified in the "xor" bitmap. + * However, if the data in the "and" bitmap is 1, the result will be the + * background XOR'ed with the value in the "xor" bitmap. In case the "xor" + * data is completely black (0x000000) the pixel will become transparent, + * in case it's white (0xffffff) the pixel will become the inverse of the + * background color. + */ + + // Scan each pixel of the souce bitmap and create the masks + int y; + int *pixel = (int*)[rep bitmapData]; + for(y = 0; y < bm.bmHeight; ++y) + { + int x; + for (x = 0; x < bm.bmWidth; ++x) + { + if (*pixel++ == 0x00000000) + { + SetPixel(hAndMaskDC, x, y, RGB(255, 255, 255)); + SetPixel(hXorMaskDC, x, y, RGB(0, 0, 0)); + } + else + { + SetPixel(hAndMaskDC, x, y, RGB(0, 0, 0)); + SetPixel(hXorMaskDC, x, y, GetPixel(hMainDC, x, y)); + } + } + } + + // Reselect the old bitmap objects... + SelectObject(hMainDC, hOldMainBitmap); + SelectObject(hAndMaskDC, hOldAndMaskBitmap); + SelectObject(hXorMaskDC, hOldXorMaskBitmap); + + // Create the cursor from the generated and/xor data... + ICONINFO iconinfo = { 0 }; + iconinfo.fIcon = FALSE; + iconinfo.xHotspot = 0; + iconinfo.yHotspot = 0; + iconinfo.hbmMask = hAndMaskBitmap; + iconinfo.hbmColor = hXorMaskBitmap; + + // Finally, try to create the cursor... + result = CreateIconIndirect(&iconinfo); + + // Cleanup the DC's... + DeleteDC(hXorMaskDC); + DeleteDC(hAndMaskDC); + DeleteDC(hMainDC); + + // Cleanup the bitmaps... + DeleteObject(hXorMaskBitmap); + DeleteObject(hAndMaskBitmap); + DeleteObject(hSourceBitmap); + + // Release the screen HDC... + ReleaseDC(NULL,hDC); + } + } + + return(result); +} + +- (NSBitmapImageRep*) _getStandardBitmap:(NSImage *)image +{ + NSBitmapImageRep *rep; + + if (image == nil) + { + return nil; + } + + rep = (NSBitmapImageRep *)[image bestRepresentationForDevice: nil]; + if (!rep || ![rep respondsToSelector: @selector(samplesPerPixel)]) + { + /* FIXME: We might create a blank cursor here? */ + //NSLog(@"%s:could not convert cursor bitmap data for image: %@", __PRETTY_FUNCTION__, image); + return nil; + } + else + { + // Convert into something usable by the backend + return [rep _convertToFormatBitsPerSample: 8 + samplesPerPixel: [rep hasAlpha] ? 4 : 3 + hasAlpha: [rep hasAlpha] + isPlanar: NO + colorSpaceName: NSCalibratedRGBColorSpace + bitmapFormat: 0 + bytesPerRow: 0 + bitsPerPixel: 0]; + } +} + +- (HICON) _iconFromImage: (NSImage *)image +{ + // Default the return cursur ID to NULL... + HICON result = NULL; + +#if defined(DEBUG) + NSLog(@"%s:image: %@ imageToIcon dict: %@", __PRETTY_FUNCTION__, image, imageToIcon); +#endif + if ([image name] == nil) + { + NSLog(@"%s:cannot create/store image icon for NIL image names: %@", __PRETTY_FUNCTION__, result, [image name]); + } + else if ([imageToIcon objectForKey:[image name]]) + { + result = (HICON)[[imageToIcon objectForKey: image] pointerValue]; +#if defined(DEBUG) + NSLog(@"%s:reusing icon: %p for imageName: %@", __PRETTY_FUNCTION__, result, [image name]); +#endif + } + else + { + NSBitmapImageRep *rep = [self _getStandardBitmap:image]; + + if (rep == NULL) + { + NSLog(@"%s:error creating standard bitmap for image: %@", __PRETTY_FUNCTION__, image); + } + else + { + // Try to create the icon from the image... + result = [self _iconFromRep:rep]; + + // Need to save these created cursors to remove later... + if (result != NULL) + { + [imageToIcon setObject:[NSValue valueWithPointer:result] forKey:[image name]]; +#if defined(DEBUG) + NSLog(@"%s:saving icon: %p for imageName: %@", __PRETTY_FUNCTION__, result, [image name]); +#endif + } + } + } + + // Return whatever we were able to generate... + return(result); +} + +- (GUID)guidFromUUIDString:(NSString*)uuidString +{ + NSLog(@"%s:UUIDString: %@", __PRETTY_FUNCTION__, uuidString); + GUID theGUID = { 0 }; + NSUInteger value = 0; + NSArray *components = [uuidString componentsSeparatedByString: @"-"]; + NSScanner *scanner1 = [NSScanner scannerWithString: [components objectAtIndex: 0]]; + NSScanner *scanner2 = [NSScanner scannerWithString: [components objectAtIndex: 1]]; + NSScanner *scanner3 = [NSScanner scannerWithString: [components objectAtIndex: 2]]; + NSString *data4 = [[components objectAtIndex: 3] stringByAppendingString: [components objectAtIndex: 4]]; + NSScanner *scanner4 = [NSScanner scannerWithString: data4]; + + [scanner1 scanHexInt: (NSUInteger*)&theGUID.Data1]; + [scanner2 scanHexInt: (NSUInteger*)&value]; + theGUID.Data2 = (WORD) value; + [scanner3 scanHexInt: (NSUInteger*)&value]; + theGUID.Data3 = (WORD) value; + + return theGUID; +} + +- (GUID)guidFromUUID:(NSUUID*)uuid +{ + // Note: This is an example GUID only and should not be used. + // Normally, you should use a GUID-generating tool to provide the value to + // assign to guidItem. + return([self guidFromUUIDString:[uuid UUIDString]]); +} + +- (NSUUID*)generateUUID +{ + return [NSUUID UUID]; +} + +- (GUID)generateGUID +{ + return [self guidFromUUID:[self generateUUID]]; +} + +- (NSNumber*)_showNotification:(NSUserNotification*)note forWindow:(NSWindow*)forWindow +{ + NSAutoreleasePool *pool = [NSAutoreleasePool new]; + +#if defined(DEBUG) + NSLog(@"%s:title: %@ informativeText: %@ contentImage: %@ (%@)", __PRETTY_FUNCTION__, note.title, note.informativeText, note.contentImage, [note.contentImage _filename]); +#endif + + NSNumber *result = nil; + + + if (pSendNotification != NULL) + { + NSUUID *uuid = [self generateUUID]; + NSString *UUIDString = [uuid UUIDString]; + std::string uuidString = std::string([UUIDString UTF8String]); + + // Try to send the notification... + SEND_NOTE_INFO_T noteInfo = { 0 }; + noteInfo.uuidString = [UUIDString UTF8String]; + noteInfo.title = [note.title UTF8String]; + noteInfo.informativeText = [note.informativeText UTF8String]; + noteInfo.appIconPath = [appIconPath UTF8String]; + + // Content image is for displaying within the notification... + // i.e. Shell notes - within the balloon somwhere... + // Toast notes - with the toast window somewhere + // Check for content image and generate a windows icon from it... + if (note.contentImage != nil) + { + // Attempt to create a window icon from image... + noteInfo.contentIcon = [self _iconFromImage:note.contentImage]; + +#if defined(DEBUG) + NSLog(@"%s:image: %@ icon: %p", __PRETTY_FUNCTION__, note.contentImage, noteInfo.contentIcon); +#endif + } + + BOOL status = pSendNotification((HWND)GetModuleHandle(NULL), appIcon, ¬eInfo); + + if (status) + { + note.identifier = [[UUIDString copy] autorelease]; + note.presented = YES; + +#if defined(DEBUG) + NSLog(@"%s:status: %d uniqueID: %d", __PRETTY_FUNCTION__, status, uniqueID); +#endif + + return [NSNumber numberWithBool:uniqueID++]; + } + } + + // Cleanup... + [pool drain]; + + // TODO: Should we return something else here???? + return [NSNumber numberWithBool:0]; +} + +- (void) _deliverNotification: (NSUserNotification *)un +{ + NSAutoreleasePool *pool = [NSAutoreleasePool new]; + NSString *appName = nil; + NSString *imageName = nil; + NSURL *imageURL = nil; + NSURL *soundFileURL = nil; + + NSBundle *bundle = [NSBundle mainBundle]; + if (bundle) + { + NSDictionary *info = [bundle localizedInfoDictionary]; + if (info) + { + appName = [info objectForKey: @"NSBundleName"]; + imageName = [info objectForKey: @"NSIcon"]; + if (imageName) + imageURL = [bundle URLForResource: imageName withExtension: nil]; + } + if (un.soundName && [caps containsObject:@"sound"]) + { + soundFileURL = [bundle URLForResource: un.soundName + withExtension: nil]; + } + } + + // fallback + if (!appName) + appName = [[NSProcessInfo processInfo] processName]; + + NSMutableArray *actions = [NSMutableArray array]; +#if 0 + if ([un hasActionButton]) + { + NSString *actionButtonTitle = un.actionButtonTitle; + if (!actionButtonTitle) + actionButtonTitle = _(@"Show"); + + // NOTE: don't use "default", as it's used by convention and seems + // to remove the actionButton entirely + // (tested with Notification Daemon (0.3.7)) + [actions addObject: kButtonActionKey]; + [actions addObject: [self cleanupTextIfNecessary: actionButtonTitle]]; + } +#endif + + NSDebugMLLog(@"NSUserNotification", + @"appName: %@ imageName: %@ imageURL: %@ soundFileURL: %@", + appName, imageName, imageURL, soundFileURL); + + NSMutableDictionary *hints = [NSMutableDictionary dictionary]; + if (un.userInfo) + [hints addEntriesFromDictionary: un.userInfo]; + + if (imageURL) + [hints setObject: [imageURL absoluteString] forKey: @"image-path"]; + if (soundFileURL) + [hints setObject: [soundFileURL path] forKey: @"sound-file"]; + + NSString *summary = [self cleanupTextIfNecessary: un.title]; + NSString *body = [self cleanupTextIfNecessary: un.informativeText]; + NSNumber *uniqueId = [self _showNotification:un forWindow: nil]; + + ASSIGN(un->_uniqueId, uniqueId); + un.presented = ([uniqueId integerValue] != 0) ? YES : NO; + + [pool drain]; +} + +- (void)_removeDeliveredNotification:(NSUserNotification *)theNote +{ +#if defined(DEBUG) + NSLog(@"%s:note: %@ ID: %@", __PRETTY_FUNCTION__, theNote, theNote->_uniqueId); +#endif + + if (pRemoveNotification != NULL) + { + REMOVE_NOTE_INFO_T noteInfo = { 0 }; + noteInfo.uniqueID = [theNote->_uniqueId integerValue]; + pRemoveNotification(appIcon, ¬eInfo); + } +} + +- (NSString *)cleanupTextIfNecessary:(NSString *)rawText +{ + if (!rawText || ![caps containsObject:@"body-markup"]) + return nil; + + NSMutableString *t = (NSMutableString *)[rawText mutableCopy]; + [t replaceOccurrencesOfString: @"&" withString: @"&" options: 0 range: NSMakeRange(0, [t length])]; // must be first! + [t replaceOccurrencesOfString: @"<" withString: @"<" options: 0 range: NSMakeRange(0, [t length])]; + [t replaceOccurrencesOfString: @">" withString: @">" options: 0 range: NSMakeRange(0, [t length])]; + [t replaceOccurrencesOfString: @"\"" withString: @""" options: 0 range: NSMakeRange(0, [t length])]; + [t replaceOccurrencesOfString: @"'" withString: @"'" options: 0 range: NSMakeRange(0, [t length])]; + return t; +} + +// SIGNALS + +- (void)receiveNotificationClosedNotification:(NSNotification *)n +{ + id nId = [[n userInfo] objectForKey: @"arg0"]; + NSUserNotification *un = [self deliveredNotificationWithUniqueId: nId]; + NSDebugMLLog(@"NSUserNotification", @"%@", un); +} + +- (void)receiveActionInvokedNotification:(NSNotification *)n +{ + id nId = [[n userInfo] objectForKey: @"arg0"]; + NSUserNotification *un = [self deliveredNotificationWithUniqueId: nId]; + NSString *action = [[n userInfo] objectForKey: @"arg1"]; + + NSDebugMLLog(@"NSUserNotification", @"%@ -- action: %@", un, action); + if ([action isEqual:kButtonActionKey]) + un.activationType = NSUserNotificationActivationTypeActionButtonClicked; + else + un.activationType = NSUserNotificationActivationTypeContentsClicked; + + if (self.delegate && [self.delegate respondsToSelector:@selector(userNotificationCenter:didActivateNotification:)]) + [self.delegate userNotificationCenter: self didActivateNotification: un]; +} + +@end + +#if defined(__cplusplus) +} +#endif + +#endif /* __has_feature(objc_default_synthesize_properties) */ diff --git a/Source/win32/MSUserNotifications/MSUserNotificationAPI.h b/Source/win32/MSUserNotifications/MSUserNotificationAPI.h new file mode 100644 index 0000000..31f4ec4 --- /dev/null +++ b/Source/win32/MSUserNotifications/MSUserNotificationAPI.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +#if defined(__cplusplus) +extern "C" { // Only if you are using C++ rather than C +#endif + +typedef struct _SEND_NOTE_INFO +{ + const char *uuidString; + const char *title; + const char *informativeText; + HICON contentIcon; + const char *appIconPath; +} SEND_NOTE_INFO_T, *SEND_NOTE_INFO_PTR; + +typedef struct _REMOVE_NOTE_INFO +{ + UINT uniqueID; +} REMOVE_NOTE_INFO_T, *REMOVE_NOTE_INFO_PTR; + +#if defined(__cplusplus) +} +#endif diff --git a/Source/win32/MSUserNotifications/TaskbarNotifications/GNUmakefile b/Source/win32/MSUserNotifications/TaskbarNotifications/GNUmakefile new file mode 100644 index 0000000..30bcfbf --- /dev/null +++ b/Source/win32/MSUserNotifications/TaskbarNotifications/GNUmakefile @@ -0,0 +1,30 @@ +ifeq ($(GNUSTEP_MAKEFILES),) + GNUSTEP_MAKEFILES := $(shell gnustep-config --variable=GNUSTEP_MAKEFILES 2>/dev/null) + ifeq ($(GNUSTEP_MAKEFILES),) + $(warning ) + $(warning Unable to obtain GNUSTEP_MAKEFILES setting from gnustep-config!) + $(warning Perhaps gnustep-make is not properly installed,) + $(warning so gnustep-config is not in your PATH.) + $(warning ) + $(warning Your PATH is currently $(PATH)) + $(warning ) + endif +endif + +ifeq ($(GNUSTEP_MAKEFILES),) + $(error You need to set GNUSTEP_MAKEFILES before compiling!) +endif + +LIBRARY_NAME = TaskbarNotifications + +TaskbarNotifications_NEEDS_GUI = NO +TaskbarNotifications_CFLAGS += -DWINVER=0x0600 -D_WIN32_IE=0x0600 -DBUILD_DLL -fms-extensions +TaskbarNotifications_OBJCFLAGS += -DWINVER=0x0600 -D_WIN32_IE=0x0600 -DBUILD_DLL -fms-extensions +TaskbarNotifications_OBJCCFLAGS += -DWINVER=0x0600 -D_WIN32_IE=0x0600 -DBUILD_DLL -fms-extensions +TaskbarNotifications_OBJC_FILES = +TaskbarNotifications_OBJCC_FILES = TaskbarNotifications.mm +TaskbarNotifications_LIBRARIES_DEPEND_UPON += -lgnustep-gui -lgdi32 +TaskbarNotifications_RESOURCE_FILES += + +include $(GNUSTEP_MAKEFILES)/common.make +include $(GNUSTEP_MAKEFILES)/library.make diff --git a/Source/win32/MSUserNotifications/TaskbarNotifications/TaskbarNotifications.mm b/Source/win32/MSUserNotifications/TaskbarNotifications/TaskbarNotifications.mm new file mode 100644 index 0000000..26b5259 --- /dev/null +++ b/Source/win32/MSUserNotifications/TaskbarNotifications/TaskbarNotifications.mm @@ -0,0 +1,863 @@ +// Windows documentation - see: https://msdn.microsoft.com/en-us/library/windows/desktop/bb773352(v=vs.85).aspx +// Several members of this structure are only supported for Windows 2000 and later. To enable these members, +// include one of the following lines in your header: +// // Windows Vista and later: +// #define NTDDI_VERSION NTDDI_WIN2K +// #define NTDDI_VERSION NTDDI_WINXP +// #define NTDDI_VERSION NTDDI_VISTA +// +// // Windows XP and earlier: +// #define _WIN32_IE 0x0500 +// +// We've defined these in the GNUmakefile!!!! + +// MUST BE FIRST!!! +#include <../MSUserNotification.h> +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +//#include +#include + +#if !defined(NIIF_USER) +#define NIIF_USER 0x00000004 +#endif + +#if !defined(NIN_SELECT) +#define NIN_SELECT (WM_USER + 0) +#endif +#if !defined(NIN_BALLOONSHOW) +#define NIN_BALLOONSHOW (WM_USER + 2) +#endif +#if !defined(NIN_BALLOONHIDE) +#define NIN_BALLOONHIDE (WM_USER + 3) +#endif +#if !defined(NIN_BALLOONTIMEOUT) +#define NIN_BALLOONTIMEOUT (WM_USER + 4) +#endif +#if !defined(NIN_BALLOONUSERCLICK) +#define NIN_BALLOONUSERCLICK (WM_USER + 5) +#endif + +#ifdef BUILD_DLL +#define EXPORT __declspec(dllexport) +#else +#define EXPORT __declspec(dllimport) +#endif + +#define WIN_EXTRABYTES 0 +#define WINDOW_CLASS_NAME TEXT("NSUserNotificationTaskbar") +#define WINDOW_TITLE_NAME TEXT("TaskBarNotifierWin") +#define NOTIFY_MESSAGE_NAME TEXT("NSUserNotificationWindowsMessage") + +#if defined(__cplusplus) +extern "C" { +#endif + +void _registerWindowsClass(); +void _unregisterWindowsClass(); +void _initWin32Context(); +void _destroyWin32Context(); +LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + +HICON _iconForBundle(); +void _setupNotifyDataIcon(NOTIFYICONDATA&, HICON); +void _setupNotifyDataUUID(NOTIFYICONDATA &, const char *); +void _setupNotifyData(NOTIFYICONDATA&); +UINT _addApplicationIcon(DWORD, const char *, HICON); +void _removeApplicationIcon(DWORD, HICON); +void _removeProcessInfo(); +void _removeApplicationIconForID(UINT appIconID); + +static HANDLE gHandleDLL = NULL; +static HWND gHandleWin = NULL; +static UINT gNotifyMsg = 0; +static UINT gNotifyCnt = 0; + +// Objective-C/GNUstep references... +static NSString *gUuidString = nil; +static NSMutableDictionary *gProcessInfo = nil; + +// Need to capture the DLL instance handle.... +BOOL WINAPI DllMain(HANDLE hinstDLL, DWORD dwReason, LPVOID lpvReserved) +{ + DWORD processID = GetCurrentProcessId(); + +#if defined(DEBUG) + NSLog(@"%s:hinstDLL: %p dwReason: %d lpvReserved: %p procID: %p", __PRETTY_FUNCTION__, hinstDLL, dwReason, lpvReserved, processID); +#endif + + switch (dwReason) + { + case DLL_PROCESS_ATTACH: // Do process attach specific stuff here... + // TODO: DO we need to worry about OTHER PROCESSES ATTACHING??? + // Save the DLL instance handle... + // FIXME: Need process specific code here to setup for capturing the generated icons + gHandleDLL = hinstDLL; + +#if 0 // DOES NOT WORK - error code 126 - "The specified module could not be found" + // even though the instance handle is the same as the modile handle... + // Disable thread attache/detach invocations... + if (DisableThreadLibraryCalls((HMODULE)hinstDLL) == 0) + { + NSLog(@"%s:PROCESS_ATTACH:disable thread library calls error: %d", __PRETTY_FUNCTION__, GetLastError()); + return FALSE; + } +#endif + + // General intialization... + _initWin32Context(); + + // If no window... + if (gHandleWin == NULL) + return FALSE; // This is bad... + + // Log our info... +#if 0 //defined(DEBUG) + NSLog(@"%s:PROCESS_ATTACH:gHandleDLL: %p gHandleWin: %p", __PRETTY_FUNCTION__, gHandleDLL, gHandleWin); +#endif + break; + + case DLL_PROCESS_DETACH: // Do process attach specific stuff here... + // FIXME: Need process specific code here to remove any generated icons +#if 0 //defined(DEBUG) + NSLog(@"%s:PROCESS_DETACH:gHandleDLL: %p gHandleWin: %p", __PRETTY_FUNCTION__, gHandleDLL, gHandleWin); +#endif + + // Cleanup window stuff... + _destroyWin32Context(); + + break; + + case DLL_THREAD_ATTACH: // Do thread attach specific stuff here... + // FIXME: Do we need thread specific code here... +#if 0 //defined(DEBUG) + NSLog(@"%s:THREAD_ATTACH:gHandleDLL: %p gHandleWin: %p", __PRETTY_FUNCTION__, gHandleDLL, gHandleWin); +#endif + break; + + case DLL_THREAD_DETACH: // Do thread detach specific stuff here... + // FIXME: Do we need thread specific code here... +#if 0 //defined(DEBUG) + NSLog(@"%s:THREAD_DETACH:gHandleDLL: %p gHandleWin: %p", __PRETTY_FUNCTION__, gHandleDLL, gHandleWin); +#endif + break; + } + return TRUE; +} + +void _initWin32Context() +{ + NSString *gHandleString = nil; + NSNumber *value = [NSNumber numberWithInteger:GetCurrentProcessId()]; + + // Register our message window type...... + _registerWindowsClass(); + + // Register the windows notify message we want... + gNotifyMsg = RegisterWindowMessage(NOTIFY_MESSAGE_NAME); + if (gNotifyMsg == 0) + { + NSLog(@"%s:error registering windos message - error: %d", __PRETTY_FUNCTION__, GetLastError()); + return; + } + + // Create a message only window...if it hasn't been created yet... + gHandleWin = CreateWindowEx( 0, WINDOW_CLASS_NAME, WINDOW_TITLE_NAME, 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL ); + if (gHandleWin == NULL) + { + NSLog(@"%s:PROCESS_ATTACH:create window error: %d", __PRETTY_FUNCTION__, GetLastError()); + return; + } + + // Initialize necessary data structures... + gProcessInfo = [NSMutableDictionary new]; + [gProcessInfo setObject:[NSMutableDictionary dictionary] forKey:@"AppIcons"]; + [gProcessInfo setObject:[NSMutableDictionary dictionary] forKey:@"AppNotes"]; + [gProcessInfo setObject:value forKey:@"ProcessID"]; +} + +void _destroyWin32Context() +{ + // Remove the process' info dictionary... + _removeProcessInfo(); + + // Destroy the message window... + if (gHandleWin != NULL) + DestroyWindow(gHandleWin); + + // Unregister the window class... + _unregisterWindowsClass(); +} + +NSImage *_imageForBundleInfo(NSDictionary*infoDict) +{ + NSImage *image = nil; + NSString *appIconFile = [infoDict objectForKey: @"NSIcon"]; + + if (appIconFile && ![appIconFile isEqual: @""]) + { + image = [NSImage imageNamed: appIconFile]; + } + + // Try to look up the icns file. + appIconFile = [infoDict objectForKey: @"CFBundleIconFile"]; + if (appIconFile && ![appIconFile isEqual: @""]) + { + image = [NSImage imageNamed: appIconFile]; + } + + if (image == nil) + { + image = [NSImage imageNamed: @"GNUstep"]; + } + else + { + /* Set the new image to be named 'NSApplicationIcon' ... to do that we + * must first check that any existing image of the same name has its + * name removed. + */ + [(NSImage*)[NSImage imageNamed: @"NSApplicationIcon"] setName: nil]; + // We need to copy the image as we may have a proxy here + image = AUTORELEASE([image copy]); + [image setName: @"NSApplicationIcon"]; + } + return image; +} + +HICON _iconFromRep(NSBitmapImageRep* rep) +{ + HICON result = NULL; + + if (rep) + { + int w = [rep pixelsWide]; + int h = [rep pixelsHigh]; + + // Create a windows bitmap from the image representation's bitmap... + if ((w > 0) && (h > 0)) + { + BITMAP bm; + HDC hDC = GetDC(NULL); + HDC hMainDC = CreateCompatibleDC(hDC); + HDC hAndMaskDC = CreateCompatibleDC(hDC); + HDC hXorMaskDC = CreateCompatibleDC(hDC); + HBITMAP hAndMaskBitmap = NULL; + HBITMAP hXorMaskBitmap = NULL; + + // Create the source bitmap... + HBITMAP hSourceBitmap = CreateBitmap(w, h, [rep numberOfPlanes], [rep bitsPerPixel], [rep bitmapData]); + + // Get the dimensions of the source bitmap + GetObject(hSourceBitmap, sizeof(BITMAP), &bm); + + // Create compatible bitmaps for the device context... + hAndMaskBitmap = CreateCompatibleBitmap(hDC, bm.bmWidth, bm.bmHeight); + hXorMaskBitmap = CreateCompatibleBitmap(hDC, bm.bmWidth, bm.bmHeight); + + // Select the bitmaps to DC + HBITMAP hOldMainBitmap = (HBITMAP)SelectObject(hMainDC, hSourceBitmap); + HBITMAP hOldAndMaskBitmap = (HBITMAP)SelectObject(hAndMaskDC, hAndMaskBitmap); + HBITMAP hOldXorMaskBitmap = (HBITMAP)SelectObject(hXorMaskDC, hXorMaskBitmap); + + /* On windows, to calculate the color for a pixel, first an AND is done + * with the background and the "and" bitmap, then an XOR with the "xor" + * bitmap. This means that when the data in the "and" bitmap is 0, the + * pixel will get the color as specified in the "xor" bitmap. + * However, if the data in the "and" bitmap is 1, the result will be the + * background XOR'ed with the value in the "xor" bitmap. In case the "xor" + * data is completely black (0x000000) the pixel will become transparent, + * in case it's white (0xffffff) the pixel will become the inverse of the + * background color. + */ + + // Scan each pixel of the souce bitmap and create the masks + int y; + int *pixel = (int*)[rep bitmapData]; + for(y = 0; y < bm.bmHeight; ++y) + { + int x; + for (x = 0; x < bm.bmWidth; ++x) + { + if (*pixel++ == 0x00000000) + { + SetPixel(hAndMaskDC, x, y, RGB(255, 255, 255)); + SetPixel(hXorMaskDC, x, y, RGB(0, 0, 0)); + } + else + { + SetPixel(hAndMaskDC, x, y, RGB(0, 0, 0)); + SetPixel(hXorMaskDC, x, y, GetPixel(hMainDC, x, y)); + } + } + } + + // Reselect the old bitmap objects... + SelectObject(hMainDC, hOldMainBitmap); + SelectObject(hAndMaskDC, hOldAndMaskBitmap); + SelectObject(hXorMaskDC, hOldXorMaskBitmap); + + // Create the cursor from the generated and/xor data... + ICONINFO iconinfo = { 0 }; + iconinfo.fIcon = FALSE; + iconinfo.xHotspot = 0; + iconinfo.yHotspot = 0; + iconinfo.hbmMask = hAndMaskBitmap; + iconinfo.hbmColor = hXorMaskBitmap; + + // Finally, try to create the cursor... + result = CreateIconIndirect(&iconinfo); + + // Cleanup the DC's... + DeleteDC(hXorMaskDC); + DeleteDC(hAndMaskDC); + DeleteDC(hMainDC); + + // Cleanup the bitmaps... + DeleteObject(hXorMaskBitmap); + DeleteObject(hAndMaskBitmap); + DeleteObject(hSourceBitmap); + + // Release the screen HDC... + ReleaseDC(NULL,hDC); + } + } + + return(result); +} + +NSBitmapImageRep *_getStandardBitmap(NSImage *image) +{ + NSBitmapImageRep *rep; + + if (image == nil) + { + return nil; + } + + rep = (NSBitmapImageRep *)[image bestRepresentationForDevice: nil]; + if (!rep || ![rep respondsToSelector: @selector(samplesPerPixel)]) + { + /* FIXME: We might create a blank cursor here? */ +#if defined(DEBUG) + NSLog(@"%s:could not convert cursor bitmap data for image: %@", __PRETTY_FUNCTION__, image); +#endif + return nil; + } + else + { + // Convert into something usable by the backend + return [rep _convertToFormatBitsPerSample: 8 + samplesPerPixel: [rep hasAlpha] ? 4 : 3 + hasAlpha: [rep hasAlpha] + isPlanar: NO + colorSpaceName: NSCalibratedRGBColorSpace + bitmapFormat: 0 + bytesPerRow: 0 + bitsPerPixel: 0]; + } +} + +HICON _iconFromImage(NSImage *image) +{ + // Default the return cursur ID to NULL... + HICON result = NULL; + NSBitmapImageRep *rep = _getStandardBitmap(image); + + if (rep == NULL) + { + NSLog(@"%s:error creating standard bitmap for image: %@", __PRETTY_FUNCTION__, image); + } + else + { + // Try to create the icon from the image... + result = _iconFromRep(rep); + } + + // Return whatever we were able to generate... + return(result); +} + +HICON _iconForBundle() +{ + NSBundle *mainBundle = [NSBundle mainBundle]; + NSDictionary *infoDict = [mainBundle infoDictionary]; + NSLog(@"%s:infoDict: %@", __PRETTY_FUNCTION__, infoDict); + NSString *imageName = [[infoDict objectForKey:@"CFBundleIconFiles"] objectAtIndex:0]; + NSString *imageType = @""; + NSString *path = [mainBundle pathForResource:imageName ofType:imageType]; + NSImage *image = _imageForBundleInfo(infoDict); + return _iconFromImage(image); +} + +void _registerWindowsClass() +{ + WNDCLASSEX wc = { 0 }; + + // Register the main window class. + wc.cbSize = sizeof(wc); + wc.style = 0; + wc.lpfnWndProc = (WNDPROC)MainWndProc; + wc.cbClsExtra = 0; + // Keep extra space for each window, for OFF_LEVEL and OFF_ORDERED + wc.cbWndExtra = WIN_EXTRABYTES; + wc.hInstance = (HINSTANCE)gHandleDLL; + wc.hIcon = NULL; + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); + wc.lpszMenuName = NULL; + wc.lpszClassName = WINDOW_CLASS_NAME; + wc.hIconSm = NULL; + + if (!RegisterClassEx(&wc)) + { + NSLog(@"%s:error registering windows class - error: %d", __PRETTY_FUNCTION__, GetLastError()); + return; + } + // FIXME We should use GetSysColor to get standard colours from MS Window and + // use them in NSColor + + // Should we create a message only window here, so we can get events, even when + // no windows are created? +} + +void _unregisterWindowsClass() +{ + UnregisterClass(WINDOW_CLASS_NAME, (HINSTANCE)gHandleDLL); +} + +/* Windows documentation..... + +See: https://msdn.microsoft.com/en-us/library/windows/desktop/bb773352(v=vs.85).aspx + +An application-defined message identifier. The system uses this identifier to send +notification messages to the window identified in hWnd. These notification messages +are sent when a mouse event or hover occurs in the bounding rectangle of the icon, +when the icon is selected or activated with the keyboard, or when those actions occur +in the balloon notification. + +When the uVersion member is either 0 or NOTIFYICON_VERSION, the wParam parameter of the +message contains the identifier of the taskbar icon in which the event occurred. This +identifier can be 32 bits in length. The lParam parameter holds the mouse or keyboard +message associated with the event. For example, when the pointer moves over a taskbar +icon, lParam is set to WM_MOUSEMOVE. + +When the uVersion member is NOTIFYICON_VERSION_4, applications continue to receive +notification events in the form of application-defined messages through the uCallbackMessage +member, but the interpretation of the lParam and wParam parameters of that message is changed +as follows: +o LOWORD(lParam) contains notification events, such as NIN_BALLOONSHOW, NIN_POPUPOPEN, or + WM_CONTEXTMENU. +o HIWORD(lParam) contains the icon ID. Icon IDs are restricted to a length of 16 bits. +o GET_X_LPARAM(wParam) returns the X anchor coordinate for notification events NIN_POPUPOPEN, + NIN_SELECT, NIN_KEYSELECT, and all mouse messages between WM_MOUSEFIRST and WM_MOUSELAST. + If any of those messages are generated by the keyboard, wParam is set to the upper-left corner + of the target icon. For all other messages, wParam is undefined. +o GET_Y_LPARAM(wParam) returns the Y anchor coordinate for notification events and messages + as defined for the X anchor. + +*/ +LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + LRESULT status = 0; + +#if defined(DEBUG) + NSLog(@"%s:hwnd: %p uMsg %d wParam: %p lParam: %p", __PRETTY_FUNCTION__, hwnd, uMsg, wParam, lParam); +#endif + + // If it's not our notify event message ID then... + if (uMsg != gNotifyMsg) + { + // invoke default windows procedure to handle the message... + status = DefWindowProc(hwnd, uMsg, wParam, lParam); + } + else // Otherwise we'll process it... + { + UINT loword = LOWORD(lParam); + + switch (loword) + { + case NIN_SELECT: +#if defined(DEBUG) + NSLog(@"%s:NIN_SELECT:", __PRETTY_FUNCTION__); +#endif + break; + + case NIN_BALLOONSHOW: +#if defined(DEBUG) + NSLog(@"%s:NIN_BALLOONSHOW:", __PRETTY_FUNCTION__); +#endif + break; + + case NIN_BALLOONHIDE: +#if defined(DEBUG) + NSLog(@"%s:NIN_BALLOONHIDE:", __PRETTY_FUNCTION__); +#endif + break; + + case NIN_BALLOONTIMEOUT: +#if defined(DEBUG) + NSLog(@"%s:NIN_BALLOONTIMEOUT:", __PRETTY_FUNCTION__); +#endif + break; + + case NIN_BALLOONUSERCLICK: +#if defined(DEBUG) + NSLog(@"%s:NIN_BALLOONUSERCLICK:", __PRETTY_FUNCTION__); +#endif + break; + + case NIN_POPUPOPEN: +#if defined(DEBUG) + NSLog(@"%s:NIN_POPUPOPEN:", __PRETTY_FUNCTION__); +#endif + break; + + case NIN_POPUPCLOSE: +#if defined(DEBUG) + NSLog(@"%s:NIN_POPUPCLOSE:", __PRETTY_FUNCTION__); +#endif + break; + + case WM_MOUSEMOVE: +#if defined(DEBUG) + NSLog(@"%s:WM_MOUSEMOVE:", __PRETTY_FUNCTION__); +#endif + break; + + case WM_LBUTTONUP: +#if defined(DEBUG) + NSLog(@"%s:WM_LBUTTONUP:", __PRETTY_FUNCTION__); +#endif + break; + + case WM_LBUTTONDOWN: +#if defined(DEBUG) + NSLog(@"%s:WM_LBUTTONDOWN:", __PRETTY_FUNCTION__); +#endif + break; + + case WM_LBUTTONDBLCLK: +#if defined(DEBUG) + NSLog(@"%s:WM_LBUTTONDBLCLK:", __PRETTY_FUNCTION__); +#endif + break; + + case WM_RBUTTONUP: +#if defined(DEBUG) + NSLog(@"%s:WM_RBUTTONUP:", __PRETTY_FUNCTION__); +#endif + break; + + case WM_RBUTTONDOWN: +#if defined(DEBUG) + NSLog(@"%s:WM_RBUTTONDOWN:", __PRETTY_FUNCTION__); +#endif + break; + + case WM_RBUTTONDBLCLK: +#if defined(DEBUG) + NSLog(@"%s:WM_RBUTTONDBLCLK:", __PRETTY_FUNCTION__); +#endif + break; + + case WM_CONTEXTMENU: +#if defined(DEBUG) + NSLog(@"%s:WM_CONTEXTMENU:", __PRETTY_FUNCTION__); +#endif + break; + + default: + NSLog(@"%s:default - unhandled notification event: %d", __PRETTY_FUNCTION__, loword); + status = 1; + break; + } + } + + // Cleanup... + [pool drain]; + + return status; +} + +NSMutableDictionary *appIconsForProcess() +{ + return [gProcessInfo objectForKey:@"AppIcons"]; +} + +NSMutableDictionary *appIconInfoForID(UINT appIconID) +{ + NSNumber *appIDValue = [NSNumber numberWithInteger:appIconID]; + NSDictionary *appIcons = appIconsForProcess(); + NSDictionary *appIcon = [appIcons objectForKey:appIDValue]; + NSLog(@"Appicon for ID: %d value: %@", __PRETTY_FUNCTION__, appIconID, appIcon); + return appIcon; +} + +void removeAppIconInfoForID(UINT appIconID) +{ + NSNumber *appIDValue = [NSNumber numberWithInteger:appIconID]; + NSMutableDictionary *appIcons = appIconsForProcess(); + [appIcons removeObjectForKey:appIDValue]; +} + +void removeAppIconsForProcess() +{ + // Clean up applications icons on task bar... + NSDictionary *appicons = appIconsForProcess(); + NSEnumerator *iconsIter = [appicons objectEnumerator]; + NSDictionary *iconInfo = nil; + + while ((iconInfo = [iconsIter nextObject])) + { + UINT appIconID = [[iconInfo objectForKey:@"AppIconID"] integerValue]; + +#if defined(DEBUG) + NSLog(@"%s:removing proc ID: %d iconID: %d", __PRETTY_FUNCTION__, GetCurrentProcessId(), appIconID); +#endif + + // Remove from task bar... + _removeApplicationIconForID(appIconID); + + // And destroy the icon memory... + DestroyIcon((HICON)[[iconInfo objectForKey:@"AppIcon"] pointerValue]); + } +} + +void _removeProcessInfo() +{ + removeAppIconsForProcess(); + [gProcessInfo release]; +} + +UINT _addApplicationIcon(DWORD processID, const char *uuidString, HICON icon) +{ + NSValue *iconValue = [NSValue valueWithPointer:icon]; + NSMutableDictionary *appIcons = appIconsForProcess(); + UINT appID = -1; + +#if defined(DEBUG) + NSLog(@"%s:icon: %p iconValue: %@ appIcons: %@", __PRETTY_FUNCTION__, icon, iconValue, appIcons); +#endif + + if ([appIcons objectForKey:iconValue] != nil) + { + NSDictionary *appIcon = [appIcons objectForKey:iconValue]; + appID = [[appIcon objectForKey:@"AppIconID"] integerValue]; + +#if defined(DEBUG) + NSLog(@"%s:re-using icon: %p with uID: %d", __PRETTY_FUNCTION__, icon, appID); +#endif + } + else + { +#if defined(DEBUG) + NSLog(@"%s:adding app icon for UUID: %p icon: %p", __PRETTY_FUNCTION__, uuidString, icon); +#endif + + NOTIFYICONDATA notifyData = { 0 }; + + // Initialize basic structure... + _setupNotifyData(notifyData); + _setupNotifyDataIcon(notifyData, icon); + notifyData.uID = gNotifyCnt++; + + // Adding... + if (Shell_NotifyIcon(NIM_ADD, ¬ifyData) == 0) + { + NSLog(@"%s:adding windows notification icon failed - error: %ld", __PRETTY_FUNCTION__, GetLastError()); + return FALSE; + } + + // Set version...... + if (Shell_NotifyIcon(NIM_SETVERSION, ¬ifyData) == 0) + { + NSLog(@"%s:setting windows notification version failed - error: %ld", __PRETTY_FUNCTION__, GetLastError()); + return FALSE; + } + + // Add the application ID for this icon... + appID = notifyData.uID; + + // Remember this information... +#if defined(DEBUG) + NSLog(@"%s:setting up icon: %p with uID: %d", __PRETTY_FUNCTION__, icon, appID); +#endif + NSMutableDictionary *appIcon = [NSMutableDictionary dictionary]; + NSNumber *appIDValue = [NSNumber numberWithInteger:appID]; + [appIcon setObject:appIDValue forKey:@"AppIconID"]; + [appIcon setObject:iconValue forKey:@"AppIcon"]; + [appIcons setObject:appIcon forKey:iconValue]; + } + + return appID; +} + +void _removeApplicationIcon(DWORD processID, HICON icon) +{ +} + +void _removeApplicationIconForID(UINT appIconID) +{ + NOTIFYICONDATA notifyData = { 0 }; + + _setupNotifyData(notifyData); + notifyData.uID = appIconID; + + // Deleting... + if (Shell_NotifyIcon(NIM_DELETE, ¬ifyData) == 0) + { + NSLog(@"%s:deleting windows notification icon failed - error: %ld", __PRETTY_FUNCTION__, GetLastError()); + } +} + +GUID guidFromUUIDString(NSString *uuidString) +{ + GUID theGUID; + NSArray *components = [uuidString componentsSeparatedByString: @"-"]; + NSScanner *scanner1 = [NSScanner scannerWithString: [components objectAtIndex: 0]]; + NSScanner *scanner2 = [NSScanner scannerWithString: [components objectAtIndex: 1]]; + NSScanner *scanner3 = [NSScanner scannerWithString: [components objectAtIndex: 2]]; + NSString *data4 = [[components objectAtIndex: 3] stringByAppendingString: [components objectAtIndex: 4]]; + NSScanner *scanner4 = [NSScanner scannerWithString: data4]; + + NSUInteger value; + [scanner1 scanHexInt: (NSUInteger*)&theGUID.Data1]; + [scanner2 scanHexInt: (NSUInteger*)&value]; + theGUID.Data2 = (WORD) value; + [scanner3 scanHexInt: (NSUInteger*)&value]; + theGUID.Data3 = (WORD) value; + + return theGUID; +} + +void _setupNotifyDataIcon(NOTIFYICONDATA ¬ifyData, HICON icon) +{ + // If we were not able to load the icon image... + if (icon == NULL) + { + notifyData.dwInfoFlags |= NIIF_INFO; + } + else + { + // otherwise use it in the notification... + notifyData.uFlags |= NIF_ICON; + notifyData.hIcon = icon; + } +} + +void _setupNotifyDataBalloonIcon(NOTIFYICONDATA ¬ifyData, HICON contentIcon) +{ +#if _WIN32_WINNT >= 0x600 + // If we were given a content icon image... + if (contentIcon != NULL) + { + // otherwise use it in the notification... + notifyData.dwInfoFlags |= NIIF_USER; + notifyData.hBalloonIcon = contentIcon; + } +#endif +} + +void _setupNotifyDataUUID(NOTIFYICONDATA ¬ifyData, const char *uuidString) +{ +#if USE_GUID + if (uuidString != NULL) + { + // Setup the flags and GUID... + notifyData.uFlags |= NIF_GUID; + notifyData.guidItem = guidFromUUIDString([NSString stringWithFormat:@"%s",uuidString]); + } +#else + // Otherwise using uID field... + notifyData.uID = 0; +#endif +} + +void _setupNotifyDataTextInfo(NOTIFYICONDATA ¬ifyData, const char *title, const char *informativeText) +{ + // Setup the flags... + notifyData.uFlags = NIF_TIP | NIF_INFO | NIF_MESSAGE; + + // This text will be shown as the icon's tooltip. + StrCpy(notifyData.szTip, informativeText); + StrCpy(notifyData.szInfo, title); + StrCpy(notifyData.szInfoTitle, informativeText); +} + +void _setupNotifyData(NOTIFYICONDATA ¬ifyData) +{ + notifyData.cbSize = sizeof(NOTIFYICONDATA); + notifyData.uCallbackMessage = gNotifyMsg; + notifyData.hWnd = gHandleWin; + notifyData.dwState |= NIS_SHAREDICON; + + // Load the /timeout/version??? + notifyData.uVersion = NOTIFYICON_VERSION; +} + +EXPORT BOOL __cdecl sendNotification(HWND hWnd, HICON icon, SEND_NOTE_INFO_T *note) +{ +#if 0 //defined(DEBUG) + NSLog(@"%s:hWnd: %p icon: %p GUID: %p UUID: %s", __PRETTY_FUNCTION__, hWnd, icon, note->uuidString); + NSLog(@"%s:note title: %s informativeText: %s", __PRETTY_FUNCTION__, note->title, note->informativeText); +#endif + + NOTIFYICONDATA notifyData = { 0 }; + _setupNotifyData(notifyData); + _setupNotifyDataUUID(notifyData, note->uuidString); + _setupNotifyDataIcon(notifyData, icon); +#if 0 + _setupNotifyDataBalloonIcon(notifyData, note->contentIcon); +#endif + _setupNotifyDataTextInfo(notifyData, note->title, note->informativeText); + + // Need the uID for this icon... + notifyData.uID = _addApplicationIcon(GetCurrentProcessId(), note->uuidString, icon); + + // Show the notification. + // Modifying... + if (Shell_NotifyIcon(NIM_MODIFY, ¬ifyData) == 0) + { + NSLog(@"%s:windows notification update failed for note title %s error: %ld", __PRETTY_FUNCTION__, note->title, GetLastError()); + return FALSE; + } + + // TODO: Should we instead return a NSString HERE instead??? + return TRUE; +} + +EXPORT BOOL __cdecl removeNotification(HICON icon, REMOVE_NOTE_INFO_T *noteinfo) +{ + NSAutoreleasePool *pool = [NSAutoreleasePool new]; + NOTIFYICONDATA notifyData = { 0 }; + BOOL status = TRUE; + NSLog(@"%s:%d:ID %d", __PRETTY_FUNCTION__, __LINE__, noteinfo->uniqueID); + + _setupNotifyData(notifyData); + notifyData.uID = _addApplicationIcon(GetCurrentProcessId(), NULL, icon); + + // Show the notification. + // Modifying... + if (Shell_NotifyIcon(NIM_MODIFY, ¬ifyData) == 0) + { + NSLog(@"%s:windows notification update failed for note ID %d error: %ld", __PRETTY_FUNCTION__, noteinfo->uniqueID, GetLastError()); + return FALSE; + } + + [pool drain]; + return status; +} + +#if defined(__cplusplus) +} +#endif diff --git a/Source/win32/MSUserNotifications/ToastNotifications/GNUmakefile b/Source/win32/MSUserNotifications/ToastNotifications/GNUmakefile new file mode 100644 index 0000000..5877c68 --- /dev/null +++ b/Source/win32/MSUserNotifications/ToastNotifications/GNUmakefile @@ -0,0 +1,23 @@ + +all: ToastNotifications-0.dll + +clean: + rm -rf obj + (cd ToastNotifications/Debug && (find . -type f | grep -v '.dll' | xargs rm -rf)) + (cd ToastNotifications/Release && (find . -type f | grep -v '.dll' | xargs rm -rf)) + (rm -rf ToastNotifications/ToastNotifications/Debug) + (rm -rf ToastNotifications/ToastNotifications/Release) + (rm -rf ToastNotifications/ipch ToastNotifications/*.sdf) + +obj: + mkdir -p obj + +install: ToastNotifications-0.dll + +ifeq ($(debug),yes) +ToastNotifications-0.dll: obj ToastNotifications/Debug/ToastNotifications.dll + cp -p ToastNotifications/Debug/ToastNotifications.dll obj/ToastNotifications-0.dll +else +ToastNotifications-0.dll: obj ToastNotifications/Release/ToastNotifications.dll + cp -p ToastNotifications/Release/ToastNotifications.dll obj/ToastNotifications-0.dll +endif \ No newline at end of file diff --git a/Source/win32/MSUserNotifications/ToastNotifications/GNUmakefile.gnustep b/Source/win32/MSUserNotifications/ToastNotifications/GNUmakefile.gnustep new file mode 100644 index 0000000..80d3e1b --- /dev/null +++ b/Source/win32/MSUserNotifications/ToastNotifications/GNUmakefile.gnustep @@ -0,0 +1,31 @@ +ifeq ($(GNUSTEP_MAKEFILES),) + GNUSTEP_MAKEFILES := $(shell gnustep-config --variable=GNUSTEP_MAKEFILES 2>/dev/null) + ifeq ($(GNUSTEP_MAKEFILES),) + $(warning ) + $(warning Unable to obtain GNUSTEP_MAKEFILES setting from gnustep-config!) + $(warning Perhaps gnustep-make is not properly installed,) + $(warning so gnustep-config is not in your PATH.) + $(warning ) + $(warning Your PATH is currently $(PATH)) + $(warning ) + endif +endif + +ifeq ($(GNUSTEP_MAKEFILES),) + $(error You need to set GNUSTEP_MAKEFILES before compiling!) +endif + +LIBRARY_NAME = ToastNotifications + +ToastNotifications_NEEDS_GUI = NO +ToastNotification_CFLAGS += -DBUILD_DLL +ToastNotification_OBJCFLAGS += -DBUILD_DLL +ToastNotifications_LDFLAGS += +ToastNotifications_OBJC_FILES = +ToastNotifications_OBJCC_FILES = ToastNotifications.mm +ToastNotifications_RESOURCE_FILES += + +ADDITIONAL_OBJCCFLAGS += -DBUILD_DLL + +include $(GNUSTEP_MAKEFILES)/common.make +include $(GNUSTEP_MAKEFILES)/library.make diff --git a/Source/win32/MSUserNotifications/ToastNotifications/GNUmakefile.windows b/Source/win32/MSUserNotifications/ToastNotifications/GNUmakefile.windows new file mode 100644 index 0000000..e151532 --- /dev/null +++ b/Source/win32/MSUserNotifications/ToastNotifications/GNUmakefile.windows @@ -0,0 +1,18 @@ + +all: ToastNotifications-0.dll + +clean: + rm -rf obj + +obj: + mkdir -p obj + +install: ToastNotifications-0.dll + +ifeq ($(debug),yes) +ToastNotifications-0.dll: obj ToastNotifications/Debug/ToastNotifications.dll + cp -p ToastNotifications/Debug/ToastNotifications.dll obj/ToastNotifications-0.dll +else +ToastNotifications-0.dll: obj ToastNotifications/Release/ToastNotifications.dll + cp -p ToastNotifications/Release/ToastNotifications.dll obj/ToastNotifications-0.dll +endif \ No newline at end of file diff --git a/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications.mm b/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications.mm new file mode 100644 index 0000000..0bcba32 --- /dev/null +++ b/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications.mm @@ -0,0 +1,70 @@ +#include +#include +#include +#include +#include +#include <../MSUserNotification.h> + +#include + +#ifdef BUILD_DLL +#define EXPORT __declspec(dllexport) +#else +#define EXPORT __declspec(dllimport) +#endif + +#if defined(__cplusplus) +extern "C" { // Only if you are using C++ rather than C +#endif +static HANDLE gHandleDLL = NULL; + +// Need to capture the DLL instance handle.... +BOOL WINAPI DllMain(HANDLE hinstDLL, DWORD dwReason, LPVOID lpvReserved) +{ + switch (dwReason) + { + case DLL_PROCESS_ATTACH: // Do process attach specific stuff here... + // Save the DLL instance handle... + gHandleDLL = hinstDLL; + break; + + case DLL_PROCESS_DETACH: // Do process attach specific stuff here... + break; + + case DLL_THREAD_ATTACH: // Do thread attach specific stuff here... + break; + + case DLL_THREAD_DETACH: // Do thread detach specific stuff here... + break; + } + return TRUE; +} + +EXPORT NSString * __cdecl sendNotification(HWND hWnd, NSMutableString *theGUID, HICON icon, NSUserNotification *note) +{ + NSString *uuid = [[NSUUID UUID] UUIDString]; + NSLog(@"%s:UUID: %@", __PRETTY_FUNCTION__, uuid); + NSArray *components = [uuid componentsSeparatedByString: @"-"]; + NSLog(@"%s:components: %@", __PRETTY_FUNCTION__, components); + GUID myGUID; + NSString *data4 = [[components objectAtIndex: 3] stringByAppendingString: [components objectAtIndex: 4]]; + myGUID.Data1 = [[components objectAtIndex: 0] longLongValue]; + myGUID.Data2 = [[components objectAtIndex: 1] integerValue]; + myGUID.Data3 = [[components objectAtIndex: 2] integerValue]; + int index; + for (index = 0; index < 8; index++) + myGUID.Data4[index] = [data4 characterAtIndex: index]; + + // TODO: Add your Toast code here... + + // IF ERROR DO NOT RETURN UUID... + if (true) + return nil; + + // TOD: Return status... + return uuid; +} + +#if defined(__cplusplus) +} +#endif diff --git a/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/Debug/ToastNotifications.dll b/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/Debug/ToastNotifications.dll new file mode 100644 index 0000000..d3c33b1 Binary files /dev/null and b/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/Debug/ToastNotifications.dll differ diff --git a/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/Release/ToastNotifications.dll b/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/Release/ToastNotifications.dll new file mode 100644 index 0000000..4fc0366 Binary files /dev/null and b/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/Release/ToastNotifications.dll differ diff --git a/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications.sln b/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications.sln new file mode 100644 index 0000000..8cbc872 --- /dev/null +++ b/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ToastNotifications", "ToastNotifications\ToastNotifications.vcxproj", "{5158BA99-59BA-4F4C-8D1D-8E8886EDF0BA}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5158BA99-59BA-4F4C-8D1D-8E8886EDF0BA}.Debug|Win32.ActiveCfg = Debug|Win32 + {5158BA99-59BA-4F4C-8D1D-8E8886EDF0BA}.Debug|Win32.Build.0 = Debug|Win32 + {5158BA99-59BA-4F4C-8D1D-8E8886EDF0BA}.Release|Win32.ActiveCfg = Release|Win32 + {5158BA99-59BA-4F4C-8D1D-8E8886EDF0BA}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications.v12.suo b/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications.v12.suo new file mode 100644 index 0000000..6453e86 Binary files /dev/null and b/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications.v12.suo differ diff --git a/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/ReadMe.txt b/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/ReadMe.txt new file mode 100644 index 0000000..e94fbef --- /dev/null +++ b/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/ReadMe.txt @@ -0,0 +1,67 @@ +======================================================================== + MICROSOFT FOUNDATION CLASS LIBRARY : ToastNotifications Project Overview +======================================================================== + + +AppWizard has created this ToastNotifications DLL for you. This DLL not only +demonstrates the basics of using the Microsoft Foundation classes but +is also a starting point for writing your DLL. + +This file contains a summary of what you will find in each of the files that +make up your ToastNotifications DLL. + +ToastNotifications.vcxproj + This is the main project file for VC++ projects generated using an Application Wizard. + It contains information about the version of Visual C++ that generated the file, and + information about the platforms, configurations, and project features selected with the + Application Wizard. + +ToastNotifications.vcxproj.filters + This is the filters file for VC++ projects generated using an Application Wizard. + It contains information about the association between the files in your project + and the filters. This association is used in the IDE to show grouping of files with + similar extensions under a specific node (for e.g. ".cpp" files are associated with the + "Source Files" filter). + +ToastNotifications.h + This is the main header file for the DLL. It declares the + CToastNotificationsApp class. + +ToastNotifications.cpp + This is the main DLL source file. It contains the class CToastNotificationsApp. + +ToastNotifications.rc + This is a listing of all of the Microsoft Windows resources that the + program uses. It includes the icons, bitmaps, and cursors that are stored + in the RES subdirectory. This file can be directly edited in Microsoft + Visual C++. + +res\ToastNotifications.rc2 + This file contains resources that are not edited by Microsoft + Visual C++. You should place all resources not editable by + the resource editor in this file. + +ToastNotifications.def + This file contains information about the DLL that must be + provided to run with Microsoft Windows. It defines parameters + such as the name and description of the DLL. It also exports + functions from the DLL. + +///////////////////////////////////////////////////////////////////////////// +Other standard files: + +StdAfx.h, StdAfx.cpp + These files are used to build a precompiled header (PCH) file + named ToastNotifications.pch and a precompiled types file named StdAfx.obj. + +Resource.h + This is the standard header file, which defines new resource IDs. + Microsoft Visual C++ reads and updates this file. + +///////////////////////////////////////////////////////////////////////////// +Other notes: + +AppWizard uses "TODO:" to indicate parts of the source code you +should add to or customize. + +///////////////////////////////////////////////////////////////////////////// diff --git a/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/Resource.h b/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/Resource.h new file mode 100644 index 0000000..b2a0e02 --- /dev/null +++ b/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/Resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by ToastNotifications.rc +// + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS + +#define _APS_NEXT_RESOURCE_VALUE 2000 +#define _APS_NEXT_CONTROL_VALUE 2000 +#define _APS_NEXT_SYMED_VALUE 2000 +#define _APS_NEXT_COMMAND_VALUE 32771 +#endif +#endif diff --git a/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/StringReferenceWrapper.h b/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/StringReferenceWrapper.h new file mode 100644 index 0000000..d8c6cf2 --- /dev/null +++ b/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/StringReferenceWrapper.h @@ -0,0 +1,60 @@ +class StringReferenceWrapper +{ +public: + + // Constructor which takes an existing string buffer and its length as the parameters. + // It fills an HSTRING_HEADER struct with the parameter. + // Warning: The caller must ensure the lifetime of the buffer outlives this + // object as it does not make a copy of the wide string memory. + + StringReferenceWrapper(_In_reads_(length) PCWSTR stringRef, _In_ UINT32 length) throw() + { + HRESULT hr = WindowsCreateStringReference(stringRef, length, &_header, &_hstring); + + if (FAILED(hr)) + { + RaiseException(static_cast(STATUS_INVALID_PARAMETER), EXCEPTION_NONCONTINUABLE, 0, nullptr); + } + } + + ~StringReferenceWrapper() + { + WindowsDeleteString(_hstring); + } + + template + StringReferenceWrapper(_In_reads_(N) wchar_t const (&stringRef)[N]) throw() + { + UINT32 length = N-1; + HRESULT hr = WindowsCreateStringReference(stringRef, length, &_header, &_hstring); + + if (FAILED(hr)) + { + RaiseException(static_cast(STATUS_INVALID_PARAMETER), EXCEPTION_NONCONTINUABLE, 0, nullptr); + } + } + + template + StringReferenceWrapper(_In_reads_(_) wchar_t (&stringRef)[_]) throw() + { + UINT32 length; + HRESULT hr = SizeTToUInt32(wcslen(stringRef), &length); + + if (FAILED(hr)) + { + RaiseException(static_cast(STATUS_INVALID_PARAMETER), EXCEPTION_NONCONTINUABLE, 0, nullptr); + } + + WindowsCreateStringReference(stringRef, length, &_header, &_hstring); + } + + HSTRING Get() const throw() + { + return _hstring; + } + + +private: + HSTRING _hstring; + HSTRING_HEADER _header; +}; diff --git a/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/ToastEventHandler.cpp b/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/ToastEventHandler.cpp new file mode 100644 index 0000000..be4edec --- /dev/null +++ b/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/ToastEventHandler.cpp @@ -0,0 +1,71 @@ +#include "stdafx.h" +#include "ToastEventHandler.h" + +using namespace ABI::Windows::UI::Notifications; + +ToastEventHandler::ToastEventHandler(_In_ HWND hToActivate, _In_ HWND hEdit) : _ref(1), _hToActivate(hToActivate), _hEdit(hEdit) +{ + +} + +ToastEventHandler::~ToastEventHandler() +{ + +} + +// DesktopToastActivatedEventHandler +IFACEMETHODIMP ToastEventHandler::Invoke(_In_ IToastNotification* sender, _In_ IInspectable* /* args */) +{ + static char str[512]; + sprintf_s(str, "%s:%d: IToastNotePtr: %p msg: The user clicked on the toast", __FUNCTION__, __LINE__, sender); + OutputDebugStringA(str); + + BOOL succeeded = SetForegroundWindow(_hToActivate); + if (succeeded) + { + LRESULT result = SendMessage(_hEdit, WM_SETTEXT, reinterpret_cast(nullptr), reinterpret_cast(L"The user clicked on the toast.")); + succeeded = result ? TRUE : FALSE; + } + return succeeded ? S_OK : E_FAIL; +} + +// DesktopToastDismissedEventHandler +IFACEMETHODIMP ToastEventHandler::Invoke(_In_ IToastNotification* sender, _In_ IToastDismissedEventArgs* e) +{ + ToastDismissalReason tdr; + HRESULT hr = e->get_Reason(&tdr); + if (SUCCEEDED(hr)) + { + wchar_t *outputText; + switch (tdr) + { + case ToastDismissalReason_ApplicationHidden: + outputText = L"The application hid the toast using ToastNotifier.hide()"; + break; + case ToastDismissalReason_UserCanceled: + outputText = L"The user dismissed this toast"; + break; + case ToastDismissalReason_TimedOut: + outputText = L"The toast has timed out"; + break; + default: + outputText = L"Toast not activated"; + break; + } + + static wchar_t str[512]; + swprintf_s(str, L"%s:%d: IToastNotePtr: %p msg: %s", TEXT(__FUNCTION__), __LINE__, sender, outputText); + OutputDebugStringW(str); + + LRESULT succeeded = SendMessage(_hEdit, WM_SETTEXT, reinterpret_cast(nullptr), reinterpret_cast(outputText)); + hr = succeeded ? S_OK : E_FAIL; + } + return hr; +} + +// DesktopToastFailedEventHandler +IFACEMETHODIMP ToastEventHandler::Invoke(_In_ IToastNotification* /* sender */, _In_ IToastFailedEventArgs* /* e */) +{ + LRESULT succeeded = SendMessage(_hEdit, WM_SETTEXT, reinterpret_cast(nullptr), reinterpret_cast(L"The toast encountered an error.")); + return succeeded ? S_OK : E_FAIL; +} diff --git a/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/ToastEventHandler.h b/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/ToastEventHandler.h new file mode 100644 index 0000000..ea66a99 --- /dev/null +++ b/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/ToastEventHandler.h @@ -0,0 +1,55 @@ +#pragma once + +typedef ABI::Windows::Foundation::ITypedEventHandler DesktopToastActivatedEventHandler; +typedef ABI::Windows::Foundation::ITypedEventHandler DesktopToastDismissedEventHandler; +typedef ABI::Windows::Foundation::ITypedEventHandler DesktopToastFailedEventHandler; + +class ToastEventHandler : + public Microsoft::WRL::Implements +{ +public: + ToastEventHandler::ToastEventHandler(_In_ HWND hToActivate, _In_ HWND hEdit); + ~ToastEventHandler(); + + // DesktopToastActivatedEventHandler + IFACEMETHODIMP Invoke(_In_ ABI::Windows::UI::Notifications::IToastNotification *sender, _In_ IInspectable* args); + + // DesktopToastDismissedEventHandler + IFACEMETHODIMP Invoke(_In_ ABI::Windows::UI::Notifications::IToastNotification *sender, _In_ ABI::Windows::UI::Notifications::IToastDismissedEventArgs *e); + + // DesktopToastFailedEventHandler + IFACEMETHODIMP Invoke(_In_ ABI::Windows::UI::Notifications::IToastNotification *sender, _In_ ABI::Windows::UI::Notifications::IToastFailedEventArgs *e); + + // IUnknown + IFACEMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&_ref); } + + IFACEMETHODIMP_(ULONG) Release() { + ULONG l = InterlockedDecrement(&_ref); + if (l == 0) delete this; + return l; + } + + IFACEMETHODIMP QueryInterface(_In_ REFIID riid, _COM_Outptr_ void **ppv) { + if (IsEqualIID(riid, IID_IUnknown)) + *ppv = static_cast(static_cast(this)); + else if (IsEqualIID(riid, __uuidof(DesktopToastActivatedEventHandler))) + *ppv = static_cast(this); + else if (IsEqualIID(riid, __uuidof(DesktopToastDismissedEventHandler))) + *ppv = static_cast(this); + else if (IsEqualIID(riid, __uuidof(DesktopToastFailedEventHandler))) + *ppv = static_cast(this); + else *ppv = nullptr; + + if (*ppv) { + reinterpret_cast(*ppv)->AddRef(); + return S_OK; + } + + return E_NOINTERFACE; + } + +private: + ULONG _ref; + HWND _hToActivate; + HWND _hEdit; +}; diff --git a/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/ToastNotifications.aps b/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/ToastNotifications.aps new file mode 100644 index 0000000..5dded7a Binary files /dev/null and b/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/ToastNotifications.aps differ diff --git a/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/ToastNotifications.cpp b/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/ToastNotifications.cpp new file mode 100644 index 0000000..1dcbead --- /dev/null +++ b/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/ToastNotifications.cpp @@ -0,0 +1,489 @@ +// ToastNotifications.cpp : Defines the initialization routines for the DLL. +// + + +#include "stdafx.h" +#include +#include +#include +#include +#include +#include +#include +#include "ToastNotifications.h" +#include "ToastEventHandler.h" +#include "../../../MSUserNotificationAPI.h" + +#include +#include +#include + +using namespace Microsoft::WRL; +using namespace ABI::Windows::UI::Notifications; +using namespace ABI::Windows::Data::Xml::Dom; +using namespace Windows::Foundation; + +#ifdef _DEBUG +#define new DEBUG_NEW +#endif + +// +//TODO: If this DLL is dynamically linked against the MFC DLLs, +// any functions exported from this DLL which call into +// MFC must have the AFX_MANAGE_STATE macro added at the +// very beginning of the function. +// +// For example: +// +// extern "C" BOOL PASCAL EXPORT ExportedFunction() +// { +// AFX_MANAGE_STATE(AfxGetStaticModuleState()); +// // normal function body here +// } +// +// It is very important that this macro appear in each +// function, prior to any calls into MFC. This means that +// it must appear as the first statement within the +// function, even before any object variable declarations +// as their constructors may generate calls into the MFC +// DLL. +// +// Please see MFC Technical Notes 33 and 58 for additional +// details. +// + +// CToastNotificationsApp + +BEGIN_MESSAGE_MAP(CToastNotificationsApp, CWinApp) +END_MESSAGE_MAP() + + +// CToastNotificationsApp construction + +CToastNotificationsApp::CToastNotificationsApp() +{ + // TODO: add construction code here, + // Place all significant initialization in InitInstance +} + +CToastNotificationsApp::~CToastNotificationsApp() +{ + +#if defined(DEBUG) + static char str[512]; + sprintf_s(str, "%s:%d: DONE", __FUNCTION__, __LINE__); + OutputDebugStringA(str); +#endif +} + +// The one and only CToastNotificationsApp object + +CToastNotificationsApp theApp; + + +// CToastNotificationsApp initialization + +BOOL CToastNotificationsApp::InitInstance() +{ + CWinApp::InitInstance(); + + return TRUE; +} + +// Create the toast XML from a template +HRESULT CToastNotificationsApp::CreateToastXml(_In_ IToastNotificationManagerStatics *toastManager, _Outptr_ IXmlDocument** inputXml, wchar_t* notificationTitle, wchar_t* notificationDescription, wchar_t* imagePath) +{ +#if defined(DEBUG) + int number = 600; + char str[256]; + sprintf_s(str, "inside create toast xml and calling GetTemplateContent %d\n", number); + OutputDebugStringA(str); +#endif + + HRESULT hr = toastManager->GetTemplateContent(ToastTemplateType_ToastImageAndText04, inputXml); + +#if defined(DEBUG) + sprintf_s(str, "done with GetTemplateContent %d\n", number); + OutputDebugStringA(str); +#endif + + if (SUCCEEDED(hr)) + { + +#if defined(DEBUG) + sprintf_s(str, "OK inside\n"); + OutputDebugStringA(str); +#endif + + //wchar_t *imagePath = _wfullpath(nullptr, L"toastImageAndText.png", MAX_PATH); + +#if defined(DEBUG) + static wchar_t wstr[512]; + swprintf_s(wstr, TEXT("%s:%d:imagePath: %s"), TEXT(__FUNCTION__), __LINE__, imagePath); + OutputDebugStringW(wstr); +#endif + + hr = imagePath != nullptr ? S_OK : HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); + + if (SUCCEEDED(hr)) + { + +#if defined(DEBUG) + sprintf_s(str, "got hte image path and now setting its source"); + OutputDebugStringA(str); +#endif + + hr = SetImageSrc(imagePath, *inputXml); + + if (SUCCEEDED(hr)) + { + +#if defined(DEBUG) + sprintf_s(str, "done setting the source"); + OutputDebugStringA(str); +#endif + + wchar_t* textValues[] = { + notificationTitle, + notificationDescription, + L" " + }; + + UINT32 textLengths[] = { wcslen(notificationTitle), wcslen(notificationDescription), 6 }; + hr = SetTextValues(textValues, 3, textLengths, *inputXml); + + } + } + else { + +#if defined(DEBUG) + sprintf_s(str, "AHh!with imagepath %ld\n", GetLastError()); + OutputDebugStringA(str); +#endif + + } + } + else { + +#if defined(DEBUG) + sprintf_s(str, "AHh! Shoot, done with GetTemplateContent %ld\n", GetLastError()); + OutputDebugStringA(str); +#endif + + } + return hr; +} + +HRESULT CToastNotificationsApp::SetTextValues(_In_reads_(textValuesCount) wchar_t **textValues, _In_ UINT32 textValuesCount, _In_reads_(textValuesCount) UINT32 *textValuesLengths, _In_ IXmlDocument *toastXml) +{ + +#if defined(DEBUG) + int number = 600; + char str[256]; + sprintf_s(str, "inside set text values %d\n", number); + OutputDebugStringA(str); +#endif + + HRESULT hr = textValues != nullptr && textValuesCount > 0 ? S_OK : E_INVALIDARG; + if (SUCCEEDED(hr)) + { + ComPtr nodeList; + +#if defined(DEBUG) + sprintf_s(str, "before calling get tag names %d\n", number); + OutputDebugStringA(str); +#endif + + hr = toastXml->GetElementsByTagName(StringReferenceWrapper(L"text").Get(), &nodeList); + if (SUCCEEDED(hr)) + { + +#if defined(DEBUG) + sprintf_s(str, "inside succeeded %d\n", number); + OutputDebugStringA(str); +#endif + + UINT32 nodeListLength; + hr = nodeList->get_Length(&nodeListLength); + + if (SUCCEEDED(hr)) + { + +#if defined(DEBUG) + sprintf_s(str, "inside got length %d\n", number); + OutputDebugStringA(str); +#endif + + hr = textValuesCount <= nodeListLength ? S_OK : E_INVALIDARG; + + if (SUCCEEDED(hr)) + { + +#if defined(DEBUG) + sprintf_s(str, "insidenode list length %d\n", number); + OutputDebugStringA(str); +#endif + + for (UINT32 i = 0; i < textValuesCount; i++) + { + ComPtr textNode; + hr = nodeList->Item(i, &textNode); + + if (SUCCEEDED(hr)) + { + +#if defined(DEBUG) + sprintf_s(str, "before calling node value string %d\n", number); + OutputDebugStringA(str); +#endif + + hr = SetNodeValueString(StringReferenceWrapper(textValues[i], textValuesLengths[i]).Get(), textNode.Get(), toastXml); + } + } + } + } + } + } + + //int number = 600; + //char str[256]; + +#if defined(DEBUG) + sprintf_s(str, "returning from set text values %d\n", number); + OutputDebugStringA(str); +#endif + + return hr; +} + +HRESULT CToastNotificationsApp::SetImageSrc(_In_z_ wchar_t *imagePath, _In_ IXmlDocument *toastXml) +{ + wchar_t imageSrc[MAX_PATH] = L"file:///"; + HRESULT hr = StringCchCat(imageSrc, ARRAYSIZE(imageSrc), imagePath); + if (SUCCEEDED(hr)) + { + ComPtr nodeList; + hr = toastXml->GetElementsByTagName(StringReferenceWrapper(L"image").Get(), &nodeList); + if (SUCCEEDED(hr)) + { + ComPtr imageNode; + hr = nodeList->Item(0, &imageNode); + if (SUCCEEDED(hr)) + { + ComPtr attributes; + + hr = imageNode->get_Attributes(&attributes); + if (SUCCEEDED(hr)) + { + ComPtr srcAttribute; + + hr = attributes->GetNamedItem(StringReferenceWrapper(L"src").Get(), &srcAttribute); + if (SUCCEEDED(hr)) + { +#if defined(DEBUG) + static char str[256]; + sprintf_s(str, "setting image source %d", hr); + OutputDebugStringA(str); +#endif + hr = SetNodeValueString(StringReferenceWrapper(imageSrc).Get(), srcAttribute.Get(), toastXml); + } + } + } + } + } + +#if defined(DEBUG) + int number = 600; + char str[256]; + sprintf_s(str, "returning from set image source %d\n", number); + OutputDebugStringA(str); +#endif + + return hr; +} + +HRESULT CToastNotificationsApp::SetNodeValueString(_In_ HSTRING inputString, _In_ IXmlNode *node, _In_ IXmlDocument *xml) +{ + +#if defined(DEBUG) + int number = 600; + char str[256]; + sprintf_s(str, "inside node value string %d\n", number); + OutputDebugStringA(str); +#endif + + ComPtr inputText; + + HRESULT hr = xml->CreateTextNode(inputString, &inputText); + + if (SUCCEEDED(hr)) + { + +#if defined(DEBUG) + sprintf_s(str, "done create node text %d\n", number); + OutputDebugStringA(str); +#endif + + ComPtr inputTextNode; + + hr = inputText.As(&inputTextNode); + if (SUCCEEDED(hr)) + { + ComPtr pAppendedChild; + hr = node->AppendChild(inputTextNode.Get(), &pAppendedChild); + } + } + +#if defined(DEBUG) + number = 600; + sprintf_s(str, "returning from set node value string %d\n", number); + OutputDebugStringA(str); +#endif + + return hr; +} + +HRESULT CToastNotificationsApp::CreateToast(_In_ IToastNotificationManagerStatics *toastManager, _In_ IXmlDocument *xml, HWND hWnd) +{ + _hwnd = hWnd; + ComPtr notifier; + HRESULT hr = toastManager->CreateToastNotifierWithId(StringReferenceWrapper(AppId).Get(), ¬ifier); + if (SUCCEEDED(hr)) + { + ComPtr factory; + hr = GetActivationFactory(StringReferenceWrapper(RuntimeClass_Windows_UI_Notifications_ToastNotification).Get(), &factory); + if (SUCCEEDED(hr)) + { + ComPtr toast; + hr = factory->CreateToastNotification(xml, &toast); + if (SUCCEEDED(hr)) + { + // Register the event handlers + EventRegistrationToken activatedToken, dismissedToken, failedToken; + ComPtr eventHandler(new ToastEventHandler(_hwnd, _hwnd)); + + toast->add_Activated(eventHandler.Get(), &activatedToken); + if (SUCCEEDED(hr)) + { + hr = toast->add_Dismissed(eventHandler.Get(), &dismissedToken); + if (SUCCEEDED(hr)) + { + hr = toast->add_Failed(eventHandler.Get(), &failedToken); + if (SUCCEEDED(hr)) + { + hr = notifier->Show(toast.Get()); + } + } + } + } + + } + } + +#if defined(DEBUG) + static wchar_t str[256]; + swprintf_s(str, TEXT("returning from create toast")); + OutputDebugString(str); +#endif + + return hr; +} + +HRESULT CToastNotificationsApp::DisplayToast(HWND hWnd, wchar_t* notificationTitle, wchar_t* notificationDescription, wchar_t* imagePath) +{ + +#if defined(DEBUG) + static wchar_t str[512]; + swprintf_s(str, L"%s:%d: note title: %su infoText: %su", TEXT(__FUNCTION__), __LINE__, notificationTitle, notificationTitle, imagePath); + OutputDebugString(str); +#endif + + ComPtr toastStatics; + + HRESULT hr = GetActivationFactory(StringReferenceWrapper(RuntimeClass_Windows_UI_Notifications_ToastNotificationManager).Get(), &toastStatics); + + if (SUCCEEDED(hr)) + { + ComPtr toastXml; + hr = CreateToastXml(toastStatics.Get(), &toastXml, notificationTitle, notificationDescription, imagePath); + + if (SUCCEEDED(hr)) + { + +#if defined(DEBUG) + char str[256]; + sprintf_s(str, "done with toast xml and calling the toast method %d\n"); + OutputDebugStringA(str); +#endif + + hr = CreateToast(toastStatics.Get(), toastXml.Get(), hWnd); + } + return hr; + } + else + + return 1; +} + +extern "C" EXPORT BOOL __cdecl sendNotification(HWND hWnd, HICON icon, SEND_NOTE_INFO_T *noteInfo) +{ + //NSLog(@"%s:hWnd: %p icon: %p GUID: %p note: %p", __PRETTY_FUNCTION__, hWnd, icon, note); + +#if defined(DEBUG) + static char str[512]; + sprintf_s(str, "%s:%d: note %p", __FUNCTION__, __LINE__, noteInfo); + OutputDebugStringA(str); + sprintf_s(str, "%s:%d: noteInfo ptrs: title: %p infoText: %p imagePath: %p", __FUNCTION__, __LINE__, noteInfo->title, noteInfo->informativeText, noteInfo->appIconPath); + OutputDebugStringA(str); +#endif + +#if defined(DEBUG) + sprintf_s(str, "%s:%d: note title: %s", __FUNCTION__, __LINE__, noteInfo->title); + OutputDebugStringA(str); + sprintf_s(str, "%s:%d: note infoText: %s", __FUNCTION__, __LINE__, noteInfo->informativeText); + OutputDebugStringA(str); + sprintf_s(str, "%s:%d: note contentPath: %s", __FUNCTION__, __LINE__, noteInfo->appIconPath); + OutputDebugStringA(str); + sprintf_s(str, "%s:%d: note UUID: %s", __FUNCTION__, __LINE__, noteInfo->uuidString); + OutputDebugStringA(str); +#endif + + std::wstring_convert> converter; + std::wstring title = converter.from_bytes(noteInfo->title); + std::wstring description = converter.from_bytes(noteInfo->informativeText); + std::wstring imagePath = TEXT(""); + + // Convert content image path if available... + if (noteInfo->appIconPath != NULL) + imagePath = converter.from_bytes(noteInfo->appIconPath); + +#if defined(DEBUG) + static wchar_t wstr[512]; + swprintf_s(wstr, TEXT("%s:%d: note title: %s infoText: %s imagePath: %s"), TEXT(__FUNCTION__), __LINE__, title.c_str(), description.c_str(), imagePath.c_str()); + OutputDebugString(wstr); +#endif + +#if 0 + CToastNotificationsApp *app = new CToastNotificationsApp(); + HRESULT hr = app->DisplayToast(hWnd, const_cast(title.c_str()), const_cast(description.c_str())); +#else + HRESULT hr = theApp.DisplayToast(hWnd, const_cast(title.c_str()), const_cast(description.c_str()), const_cast(imagePath.c_str())); +#endif + +#if defined(DEBUG) + sprintf_s(str, "%s:%d: HR %d", __FUNCTION__, __LINE__, hr); + OutputDebugStringA(str); +#endif + + if (SUCCEEDED(hr)) + { + return TRUE; + } + + return FALSE; +} + +EXPORT BOOL __cdecl removeNotification(HICON icon, REMOVE_NOTE_INFO_T *noteinfo) +{ + return FALSE; +} diff --git a/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/ToastNotifications.def b/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/ToastNotifications.def new file mode 100644 index 0000000..b973277 --- /dev/null +++ b/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/ToastNotifications.def @@ -0,0 +1,6 @@ +; ToastNotifications.def : Declares the module parameters for the DLL. + +LIBRARY + +EXPORTS + ; Explicit exports can go here diff --git a/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/ToastNotifications.h b/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/ToastNotifications.h new file mode 100644 index 0000000..d8ca7f8 --- /dev/null +++ b/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/ToastNotifications.h @@ -0,0 +1,64 @@ +// ToastNotifications.h : main header file for the ToastNotifications DLL +// + +#pragma once + +#ifndef __AFXWIN_H__ + #error "include 'stdafx.h' before including this file for PCH" +#endif + +#include "resource.h" // main symbols + +const wchar_t AppId[] = L"Microsoft.Samples.DesktopToasts"; + + + + +// CToastNotificationsApp +// See ToastNotifications.cpp for the implementation of this class +// + +class CToastNotificationsApp : public CWinApp +{ +public: + CToastNotificationsApp(); + ~CToastNotificationsApp(); + +// Overrides +public: + virtual BOOL InitInstance(); + + DECLARE_MESSAGE_MAP() + friend class ToastEventHandler; + +public: + //HRESULT DisplayToast(HWND hWnd, wchar_t* notificationTitle, wchar_t* notificationDescription); + HRESULT DisplayToast(HWND hWnd, wchar_t* notificationTitle, wchar_t* notificationDescription, wchar_t* imagePath); + HRESULT CreateToastXml( + _In_ ABI::Windows::UI::Notifications::IToastNotificationManagerStatics *toastManager, + _Outptr_ ABI::Windows::Data::Xml::Dom::IXmlDocument **xml, wchar_t* notificationTitle, wchar_t* notificationDescription, wchar_t* imagePath); + HRESULT CreateToast( + _In_ ABI::Windows::UI::Notifications::IToastNotificationManagerStatics *toastManager, + _In_ ABI::Windows::Data::Xml::Dom::IXmlDocument *xml, HWND hWnd + ); + HRESULT SetImageSrc( + _In_z_ wchar_t *imagePath, + _In_ ABI::Windows::Data::Xml::Dom::IXmlDocument *toastXml + ); + HRESULT SetTextValues( + _In_reads_(textValuesCount) wchar_t **textValues, + _In_ UINT32 textValuesCount, + _In_reads_(textValuesCount) UINT32 *textValuesLengths, + _In_ ABI::Windows::Data::Xml::Dom::IXmlDocument *toastXml + ); + HRESULT SetNodeValueString( + _In_ HSTRING onputString, + _In_ ABI::Windows::Data::Xml::Dom::IXmlNode *node, + _In_ ABI::Windows::Data::Xml::Dom::IXmlDocument *xml + ); + + +private: + HWND _hwnd; + HWND _hEdit; +}; diff --git a/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/ToastNotifications.rc b/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/ToastNotifications.rc new file mode 100644 index 0000000..ac08400 Binary files /dev/null and b/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/ToastNotifications.rc differ diff --git a/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/ToastNotifications.vcxproj b/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/ToastNotifications.vcxproj new file mode 100644 index 0000000..01fee08 --- /dev/null +++ b/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/ToastNotifications.vcxproj @@ -0,0 +1,131 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {5158BA99-59BA-4F4C-8D1D-8E8886EDF0BA} + ToastNotifications + MFCDLLProj + + + + DynamicLibrary + true + v120 + Unicode + Dynamic + + + DynamicLibrary + false + v120 + true + Unicode + Dynamic + + + + + + + + + + + + + true + + + false + + + + Use + Level3 + Disabled + BUILD_DLL;WIN32;_WINDOWS;_DEBUG;_USRDLL;%(PreprocessorDefinitions) + true + + + Windows + true + .\ToastNotifications.def + runtimeobject.lib;shlwapi.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + false + _DEBUG;%(PreprocessorDefinitions) + + + 0x0409 + _DEBUG;%(PreprocessorDefinitions) + $(IntDir);%(AdditionalIncludeDirectories) + + + + + Level3 + Use + MaxSpeed + true + true + BUILD_DLL;WIN32;_WINDOWS;NDEBUG;_USRDLL;%(PreprocessorDefinitions) + true + + + Windows + true + true + true + .\ToastNotifications.def + runtimeobject.lib;shlwapi.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + false + NDEBUG;%(PreprocessorDefinitions) + + + 0x0409 + NDEBUG;%(PreprocessorDefinitions) + $(IntDir);%(AdditionalIncludeDirectories) + + + + + + + + Create + Create + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/ToastNotifications.vcxproj.filters b/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/ToastNotifications.vcxproj.filters new file mode 100644 index 0000000..6a6ba93 --- /dev/null +++ b/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/ToastNotifications.vcxproj.filters @@ -0,0 +1,64 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Resource Files + + + + + Resource Files + + + \ No newline at end of file diff --git a/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/ToastNotifications.vcxproj.user b/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/ToastNotifications.vcxproj.user new file mode 100644 index 0000000..2a22e69 --- /dev/null +++ b/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/ToastNotifications.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/res/ToastNotifications.rc2 b/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/res/ToastNotifications.rc2 new file mode 100644 index 0000000..3fa300f Binary files /dev/null and b/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/res/ToastNotifications.rc2 differ diff --git a/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/stdafx.cpp b/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/stdafx.cpp new file mode 100644 index 0000000..9bc518d --- /dev/null +++ b/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/stdafx.cpp @@ -0,0 +1,7 @@ +// stdafx.cpp : source file that includes just the standard includes +// ToastNotifications.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + + diff --git a/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/stdafx.h b/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/stdafx.h new file mode 100644 index 0000000..51ee7c9 --- /dev/null +++ b/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/stdafx.h @@ -0,0 +1,66 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently + +#pragma once +#ifdef BUILD_DLL +#define EXPORT __declspec(dllexport) +#else +#define EXPORT __declspec(dllimport) +#endif + +#ifndef VC_EXTRALEAN +#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers +#endif + +#include "targetver.h" + +#define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS // some CString constructors will be explicit + +#include // MFC core and standard components +#include // MFC extensions + +#ifndef _AFX_NO_OLE_SUPPORT +#include // MFC OLE classes +#include // MFC OLE dialog classes +#include // MFC Automation classes +#endif // _AFX_NO_OLE_SUPPORT + +#ifndef _AFX_NO_DB_SUPPORT +#include // MFC ODBC database classes +#endif // _AFX_NO_DB_SUPPORT + +#ifndef _AFX_NO_DAO_SUPPORT +#include // MFC DAO database classes +#endif // _AFX_NO_DAO_SUPPORT + +#ifndef _AFX_NO_OLE_SUPPORT +#include // MFC support for Internet Explorer 4 Common Controls +#endif +#ifndef _AFX_NO_AFXCMN_SUPPORT +#include // MFC support for Windows Common Controls +#endif // _AFX_NO_AFXCMN_SUPPORT + +#pragma once +#pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") + + +#include + +// Windows Header Files: +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "StringReferenceWrapper.h" \ No newline at end of file diff --git a/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/targetver.h b/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/targetver.h new file mode 100644 index 0000000..90e767b --- /dev/null +++ b/Source/win32/MSUserNotifications/ToastNotifications/ToastNotifications/ToastNotifications/targetver.h @@ -0,0 +1,8 @@ +#pragma once + +// Including SDKDDKVer.h defines the highest available Windows platform. + +// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and +// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. + +#include