+/*
+ * Copyright (c) 2007 Apple Computer, Inc. All rights reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ *
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ *
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ *
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+/*
+ * Modification History
+ *
+ * October 24, 2007 Allan Nathanson <ajn@apple.com>
+ * - initial revision
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include <SystemConfiguration/SystemConfiguration.h>
+#include <SystemConfiguration/SCPrivate.h>
+#include <ApplicationServices/ApplicationServices.h>
+#include "UserEventAgentInterface.h"
+
+#define MY_BUNDLE_ID CFSTR("com.apple.SystemConfiguration.SCMonitor")
+#define MY_ICON_PATH "/System/Library/PreferencePanes/Network.prefPane/Contents/Resources/Network.icns"
+
+#define NETWORK_PREF_APP "/System/Library/PreferencePanes/Network.prefPane"
+#define NETWORK_PREF_CMD "New Interface"
+
+typedef struct {
+ UserEventAgentInterfaceStruct *_UserEventAgentInterface;
+ CFUUIDRef _factoryID;
+ UInt32 _refCount;
+
+ CFRunLoopSourceRef monitorRls;
+
+ CFMutableSetRef knownInterfaces;
+
+ CFMutableArrayRef userInterfaces;
+ CFUserNotificationRef userNotification;
+ CFRunLoopSourceRef userRls;
+} MyType;
+
+static CFMutableDictionaryRef notify_to_instance = NULL;
+
+
+#pragma mark -
+#pragma mark Watch for new [network] interfaces
+
+
+static void
+open_NetworkPrefPane(void)
+{
+ AEDesc aeDesc = { typeNull, NULL };
+ CFArrayRef prefArray;
+ CFURLRef prefURL;
+ LSLaunchURLSpec prefSpec;
+ OSStatus status;
+
+ prefURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
+ CFSTR(NETWORK_PREF_APP),
+ kCFURLPOSIXPathStyle,
+ FALSE);
+ prefArray = CFArrayCreate(NULL, (const void **)&prefURL, 1, &kCFTypeArrayCallBacks);
+ CFRelease(prefURL);
+
+ status = AECreateDesc('ptru',
+ (const void *)NETWORK_PREF_CMD,
+ strlen(NETWORK_PREF_CMD),
+ &aeDesc);
+ if (status != noErr) {
+ SCLog(TRUE, LOG_ERR, CFSTR("SCMonitor: AECreateDesc() failed: %d"), status);
+ }
+
+ prefSpec.appURL = NULL;
+ prefSpec.itemURLs = prefArray;
+ prefSpec.passThruParams = &aeDesc;
+ prefSpec.launchFlags = kLSLaunchAsync | kLSLaunchDontAddToRecents;
+ prefSpec.asyncRefCon = NULL;
+
+ status = LSOpenFromURLSpec(&prefSpec, NULL);
+ if (status != noErr) {
+ SCLog(TRUE, LOG_ERR, CFSTR("SCMonitor: LSOpenFromURLSpec() failed: %d"), status);
+ }
+
+ CFRelease(prefArray);
+ if (aeDesc.descriptorType != typeNull) AEDisposeDesc(&aeDesc);
+ return;
+}
+
+
+static void
+notify_remove(MyType *myInstance, Boolean cancel)
+{
+ if (myInstance->userInterfaces != NULL) {
+ CFRelease(myInstance->userInterfaces);
+ myInstance->userInterfaces = NULL;
+ }
+
+ if (myInstance->userRls != NULL) {
+ CFRunLoopSourceInvalidate(myInstance->userRls);
+ CFRelease(myInstance->userRls);
+ myInstance->userRls = NULL;
+ }
+
+ if (myInstance->userNotification != NULL) {
+ if (cancel) {
+ SInt32 status;
+
+ status = CFUserNotificationCancel(myInstance->userNotification);
+ if (status != 0) {
+ SCLog(TRUE, LOG_ERR,
+ CFSTR("SCMonitor: CFUserNotificationCancel() failed, status=%d"),
+ status);
+ }
+ }
+ CFRelease(myInstance->userNotification);
+ myInstance->userNotification = NULL;
+ }
+
+ return;
+}
+
+
+static void
+notify_reply(CFUserNotificationRef userNotification, CFOptionFlags response_flags)
+{
+ MyType *myInstance = NULL;
+
+ // get instance for notification
+ if (notify_to_instance != NULL) {
+ myInstance = (MyType *)CFDictionaryGetValue(notify_to_instance, userNotification);
+ if (myInstance != NULL) {
+ CFDictionaryRemoveValue(notify_to_instance, userNotification);
+ if (CFDictionaryGetCount(notify_to_instance) == 0) {
+ CFRelease(notify_to_instance);
+ notify_to_instance = NULL;
+ }
+ }
+ }
+ if (myInstance == NULL) {
+ SCLog(TRUE, LOG_ERR, CFSTR("SCMonitor: can't find user notification"));
+ return;
+ }
+
+ // process response
+ switch (response_flags & 0x3) {
+ case kCFUserNotificationDefaultResponse:
+ // user asked to configure interface
+ open_NetworkPrefPane();
+ break;
+ default:
+ // user cancelled
+ break;
+ }
+
+ notify_remove(myInstance, FALSE);
+ return;
+}
+
+static void
+notify_add(MyType *myInstance)
+{
+ CFBundleRef bundle;
+ CFMutableDictionaryRef dict = NULL;
+ SInt32 error = 0;
+ CFIndex i;
+ CFMutableArrayRef message;
+ CFIndex n = CFArrayGetCount(myInstance->userInterfaces);
+ CFURLRef url = NULL;
+
+ if (myInstance->userNotification != NULL) {
+ CFMutableArrayRef save = NULL;
+
+ if (n > 0) {
+ CFRetain(myInstance->userInterfaces);
+ save = myInstance->userInterfaces;
+ }
+ notify_remove(myInstance, TRUE);
+ myInstance->userInterfaces = save;
+ if (n == 0) {
+ return;
+ }
+ }
+
+ dict = CFDictionaryCreateMutable(NULL,
+ 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+
+ // set localization URL
+ bundle = CFBundleGetBundleWithIdentifier(MY_BUNDLE_ID);
+ if (bundle != NULL) {
+ url = CFBundleCopyBundleURL(bundle);
+ }
+ if (url != NULL) {
+ // set URL
+ CFDictionarySetValue(dict, kCFUserNotificationLocalizationURLKey, url);
+ CFRelease(url);
+ } else {
+ SCLog(TRUE, LOG_NOTICE, CFSTR("SCMonitor: can't find bundle"));
+ goto done;
+ }
+
+ // set icon URL
+ url = CFURLCreateFromFileSystemRepresentation(NULL,
+ (const UInt8 *)MY_ICON_PATH,
+ strlen(MY_ICON_PATH),
+ FALSE);
+ if (url != NULL) {
+ CFDictionarySetValue(dict, kCFUserNotificationIconURLKey, url);
+ CFRelease(url);
+ }
+
+ // header
+ CFDictionarySetValue(dict,
+ kCFUserNotificationAlertHeaderKey,
+ (n == 1) ? CFSTR("HEADER_1") : CFSTR("HEADER_N"));
+
+ // message
+ message = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ CFArrayAppendValue(message,
+ (n == 1) ? CFSTR("MESSAGE_S1") : CFSTR("MESSAGE_SN"));
+ for (i = 0; i < n; i++) {
+ SCNetworkInterfaceRef interface;
+ CFStringRef name;
+
+ interface = CFArrayGetValueAtIndex(myInstance->userInterfaces, i);
+ name = SCNetworkInterfaceGetLocalizedDisplayName(interface);
+ if (n == 1) {
+ CFArrayAppendValue(message, name);
+ } else {
+ CFStringRef str;
+
+ str = CFStringCreateWithFormat(NULL, NULL, CFSTR("\r\t%@"), name);
+ CFArrayAppendValue(message, str);
+ CFRelease(str);
+ }
+ }
+ CFArrayAppendValue(message,
+ (n == 1) ? CFSTR("MESSAGE_E1") : CFSTR("MESSAGE_EN"));
+ CFDictionarySetValue(dict, kCFUserNotificationAlertMessageKey, message);
+ CFRelease(message);
+
+ // button titles
+ CFDictionaryAddValue(dict, kCFUserNotificationDefaultButtonTitleKey, CFSTR("OPEN_NP"));
+ CFDictionaryAddValue(dict, kCFUserNotificationAlternateButtonTitleKey, CFSTR("CANCEL"));
+
+ // create and post notification
+ myInstance->userNotification = CFUserNotificationCreate(NULL,
+ 0,
+ kCFUserNotificationNoteAlertLevel,
+ &error,
+ dict);
+ if (myInstance->userNotification == NULL) {
+ SCLog(TRUE, LOG_ERR, CFSTR("SCMonitor: CFUserNotificationCreate() failed, %d"), error);
+ goto done;
+ }
+
+ // establish callback
+ myInstance->userRls = CFUserNotificationCreateRunLoopSource(NULL,
+ myInstance->userNotification,
+ notify_reply,
+ 0);
+ if (myInstance->userRls == NULL) {
+ SCLog(TRUE, LOG_ERR, CFSTR("SCMonitor: CFUserNotificationCreateRunLoopSource() failed"));
+ CFRelease(myInstance->userNotification);
+ myInstance->userNotification = NULL;
+ goto done;
+ }
+ CFRunLoopAddSource(CFRunLoopGetCurrent(), myInstance->userRls, kCFRunLoopDefaultMode);
+
+ // add instance for notification
+ if (notify_to_instance == NULL) {
+ notify_to_instance = CFDictionaryCreateMutable(NULL,
+ 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ NULL); // no retain/release/... for values
+ }
+ CFDictionarySetValue(notify_to_instance, myInstance->userNotification, myInstance);
+
+ done :
+
+ if (dict != NULL) CFRelease(dict);
+ return;
+}
+
+
+static void
+updateInterfaceList(SCDynamicStoreRef store, CFArrayRef changes, void * arg)
+{
+ CFIndex i;
+ CFArrayRef interfaces;
+ MyType *myInstance = (MyType *)arg;
+ CFIndex n;
+ SCPreferencesRef prefs;
+ CFMutableSetRef previouslyKnown = NULL;
+ SCNetworkSetRef set = NULL;
+
+ prefs = SCPreferencesCreate(NULL, CFSTR("SCMonitor"), NULL);
+ if (prefs == NULL) {
+ return;
+ }
+
+ set = SCNetworkSetCopyCurrent(prefs);
+ if (set == NULL) {
+ set = SCNetworkSetCreate(prefs);
+ if (set == NULL) {
+ goto done;
+ }
+ }
+
+ previouslyKnown = CFSetCreateMutableCopy(NULL, 0, myInstance->knownInterfaces);
+
+ interfaces = SCNetworkInterfaceCopyAll();
+ if (interfaces != NULL) {
+
+ n = CFArrayGetCount(interfaces);
+ for (i = 0; i < n; i++) {
+ CFStringRef bsdName;
+ SCNetworkInterfaceRef interface;
+ Boolean ok;
+
+ interface = CFArrayGetValueAtIndex(interfaces, i);
+ bsdName = SCNetworkInterfaceGetBSDName(interface);
+ if (bsdName == NULL) {
+ // if no BSD name
+ continue;
+ }
+
+ CFSetRemoveValue(previouslyKnown, bsdName);
+
+ if (CFSetContainsValue(myInstance->knownInterfaces, bsdName)) {
+ // if known interface
+ continue;
+ }
+
+ CFSetAddValue(myInstance->knownInterfaces, bsdName);
+
+ ok = SCNetworkSetEstablishDefaultInterfaceConfiguration(set, interface);
+ if (ok) {
+ // this is a *new* interface
+ if (myInstance->userInterfaces == NULL) {
+ myInstance->userInterfaces = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ }
+ CFArrayAppendValue(myInstance->userInterfaces, interface);
+ }
+ }
+
+ CFRelease(interfaces);
+ }
+
+ n = CFSetGetCount(previouslyKnown);
+ if (n > 0) {
+ const void * names_q[32];
+ const void ** names = names_q;
+
+ if (n > (CFIndex)(sizeof(names_q) / sizeof(CFTypeRef)))
+ names = CFAllocatorAllocate(NULL, n * sizeof(CFTypeRef), 0);
+ CFSetGetValues(previouslyKnown, names);
+ for (i = 0; i < n; i++) {
+ if (myInstance->userInterfaces != NULL) {
+ CFIndex j;
+
+ j = CFArrayGetCount(myInstance->userInterfaces);
+ while (--j >= 0) {
+ CFStringRef bsdName;
+ SCNetworkInterfaceRef interface;
+
+ interface = CFArrayGetValueAtIndex(myInstance->userInterfaces, j);
+ bsdName = SCNetworkInterfaceGetBSDName(interface);
+ if (CFEqual(bsdName, names[i])) {
+ // if we have previously posted a notification
+ // for this no-longer-present interface
+ CFArrayRemoveValueAtIndex(myInstance->userInterfaces, j);
+ }
+ }
+ }
+
+ CFSetRemoveValue(myInstance->knownInterfaces, names[i]);
+ }
+ if (names != names_q) CFAllocatorDeallocate(NULL, names);
+ }
+
+ done :
+
+ // post notification
+ if (myInstance->userInterfaces != NULL) {
+ notify_add(myInstance);
+ }
+
+ if (set != NULL) CFRelease(set);
+ CFRelease(prefs);
+ return;
+}
+
+
+static void
+watcher_remove(MyType *myInstance)
+{
+ if (myInstance->monitorRls != NULL) {
+ CFRunLoopSourceInvalidate(myInstance->monitorRls);
+ CFRelease(myInstance->monitorRls);
+ myInstance->monitorRls = NULL;
+ }
+
+ if (myInstance->knownInterfaces != NULL) {
+ CFRelease(myInstance->knownInterfaces);
+ myInstance->knownInterfaces = NULL;
+ }
+
+ return;
+}
+
+
+static void
+watcher_add(MyType *myInstance)
+{
+ SCDynamicStoreContext context = { 0, (void *)myInstance, NULL, NULL, NULL };
+ CFDictionaryRef dict;
+ CFStringRef key;
+ CFArrayRef keys;
+ SCDynamicStoreRef store;
+
+ store = SCDynamicStoreCreate(NULL, CFSTR("SCMonitor"), updateInterfaceList, &context);
+ if (store == NULL) {
+ SCLog(TRUE, LOG_ERR,
+ CFSTR("SCMonitor: SCDynamicStoreCreate() failed: %s"),
+ SCErrorString(SCError()));
+ return;
+ }
+
+ key = SCDynamicStoreKeyCreateNetworkInterface(NULL, kSCDynamicStoreDomainState);
+
+ // watch for changes to the list of network interfaces
+ keys = CFArrayCreate(NULL, (const void **)&key, 1, &kCFTypeArrayCallBacks);
+ SCDynamicStoreSetNotificationKeys(store, NULL, keys);
+ CFRelease(keys);
+ myInstance->monitorRls = SCDynamicStoreCreateRunLoopSource(NULL, store, 0);
+ CFRunLoopAddSource(CFRunLoopGetCurrent(),
+ myInstance->monitorRls,
+ kCFRunLoopDefaultMode);
+
+ // initialize the list of known interfaces
+ myInstance->knownInterfaces = CFSetCreateMutable(NULL, 0, &kCFTypeSetCallBacks);
+ dict = SCDynamicStoreCopyValue(store, key);
+ if (dict != NULL) {
+ if (isA_CFDictionary(dict)) {
+ CFIndex i;
+ CFArrayRef interfaces;
+ CFIndex n;
+
+ interfaces = CFDictionaryGetValue(dict, kSCPropNetInterfaces);
+ n = isA_CFArray(interfaces) ? CFArrayGetCount(interfaces) : 0;
+ for (i = 0; i < n; i++) {
+ CFStringRef bsdName;
+
+ bsdName = CFArrayGetValueAtIndex(interfaces, i);
+ if (isA_CFString(bsdName)) {
+ CFSetAddValue(myInstance->knownInterfaces, bsdName);
+ }
+ }
+ }
+
+ CFRelease(dict);
+ }
+
+ CFRelease(key);
+ CFRelease(store);
+ return;
+}
+
+
+#pragma mark -
+#pragma mark UserEventAgent stubs
+
+
+static HRESULT
+myQueryInterface(void *myInstance, REFIID iid, LPVOID *ppv)
+{
+ CFUUIDRef interfaceID = CFUUIDCreateFromUUIDBytes(NULL, iid);
+
+ // Test the requested ID against the valid interfaces.
+ if (CFEqual(interfaceID, kUserEventAgentInterfaceID)) {
+ ((MyType *) myInstance)->_UserEventAgentInterface->AddRef(myInstance);
+ *ppv = myInstance;
+ CFRelease(interfaceID);
+ return S_OK;
+ }
+
+ if (CFEqual(interfaceID, IUnknownUUID)) {
+ ((MyType *) myInstance)->_UserEventAgentInterface->AddRef(myInstance);
+ *ppv = myInstance;
+ CFRelease(interfaceID);
+ return S_OK;
+ }
+
+ // Requested interface unknown, bail with error.
+ *ppv = NULL;
+ CFRelease(interfaceID);
+ return E_NOINTERFACE;
+}
+
+
+static ULONG
+myAddRef(void *myInstance)
+{
+ ((MyType *) myInstance)->_refCount++;
+ return ((MyType *) myInstance)->_refCount;
+}
+
+
+static ULONG
+myRelease(void *myInstance)
+{
+ ((MyType *) myInstance)->_refCount--;
+ if (((MyType *) myInstance)->_refCount == 0) {
+ CFUUIDRef factoryID = ((MyType *) myInstance)->_factoryID;
+
+ if (factoryID != NULL) {
+ CFPlugInRemoveInstanceForFactory(factoryID);
+ CFRelease(factoryID);
+
+ watcher_remove((MyType *)myInstance);
+ notify_remove((MyType *)myInstance, TRUE);
+ }
+ free(myInstance);
+ return 0;
+ }
+
+ return ((MyType *) myInstance)->_refCount;
+}
+
+
+static void
+myInstall(void *myInstance)
+{
+ watcher_add((MyType *)myInstance);
+ return;
+}
+
+
+static UserEventAgentInterfaceStruct UserEventAgentInterfaceFtbl = {
+ NULL, // Required padding for COM
+ myQueryInterface, // These three are the required COM functions
+ myAddRef,
+ myRelease,
+ myInstall // Interface implementation
+};
+
+
+void *
+UserEventAgentFactory(CFAllocatorRef allocator, CFUUIDRef typeID)
+{
+ MyType *newOne = NULL;
+
+ if (CFEqual(typeID, kUserEventAgentTypeID)) {
+ newOne = (MyType *)malloc(sizeof(MyType));
+ bzero(newOne, sizeof(*newOne));
+ newOne->_UserEventAgentInterface = &UserEventAgentInterfaceFtbl;
+ newOne->_factoryID = (CFUUIDRef)CFRetain(kUserEventAgentFactoryID);
+ CFPlugInAddInstanceForFactory(kUserEventAgentFactoryID);
+ newOne->_refCount = 1;
+ }
+
+ return newOne;
+}
+
+
+#ifdef MAIN
+int
+main(int argc, char **argv)
+{
+ MyType *newOne = (MyType *)malloc(sizeof(MyType));
+
+ _sc_log = FALSE;
+ _sc_verbose = (argc > 1) ? TRUE : FALSE;
+
+ bzero(newOne, sizeof(*newOne));
+ myInstall(newOne);
+ CFRunLoopRun();
+ exit(0);
+ return (0);
+}
+#endif // MAIN