// CVLoader.mm: The CocoaVanilla Loader (OVLoader for OS X) // // Copyright (c) 2004-2006 The OpenVanilla Project (http://openvanilla.org) // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions // are met: // // 1. Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // 3. Neither the name of OpenVanilla nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. // #define OV_DEBUG #include #include #include #include "CVLoader.h" #include "TSBundle.h" #include #include "CVLoaderUtility.h" #include "CVLoaderConfig.h" #include "CVDictionary.h" #include "CVKeyCode.h" #include "NSDictionaryExtension.h" #include "OVDisplayServer.h" // A experiment to make CocoaVanilla safer class CVAutorelease { public: CVAutorelease() { // NSLog(@"CVAutorelease()"); ap=[[NSAutoreleasePool alloc] init]; // NSLog(@"%d, multi=%d", [ap retainCount], [NSThread isMultiThreaded]); } ~CVAutorelease() { // NSLog(@"~%d", [ap retainCount]); // NSLog(@"~CVAutorelease()"); // [ap release]; } protected: NSAutoreleasePool *ap; }; #define CVSAFETY CVAutorelease _cvar; #define CV_INTERNAL_VERSION @"0.7.2 stable (r2008)" enum { // CVLMI = CVLoader Menu Item CVLMI_KPGROUPSTART=1000, CVLMI_IMGROUPSTART=2000, CVLMI_OFGROUPSTART=3000, CVLMI_FASTIMSWITCH=4000, CVLMI_ABOUT=4001, CVLMI_PREFERENCES=4002, CVLMI_HELP=4003, }; CVLoader::CVLoader() { // actual initialization code in CVLoadver::init() } CVLoader::~CVLoader() { // although this is a VERY BAD practice, but we DO NOT DELETE anything here // the reason: text component is never released once it's loaded, // everything goes away as the application is closed by OS X } int CVLoader::init(MenuRef m) { CVSAFETY; NSLog(@"An instance of CocoaVanilla version %@ created", CV_INTERNAL_VERSION); // a snippet that knows who the host application is // (and along with which bundles OV is loaded) NSArray *aba=[NSBundle allBundles]; int ai; for (ai=0; ai<[aba count]; ai++) { NSLog(@"Loaded along with bundle %d %@", ai, [[aba objectAtIndex:ai] bundleIdentifier]); if ([[[aba objectAtIndex:ai] bundleIdentifier] isEqualToString:@"org.openvanilla.webkitserver"]) { NSLog(@"Can't be loaded with WebKitServer, die."); return 0; } if ([[[aba objectAtIndex:ai] bundleIdentifier] isEqualToString:@"com.blacktree.Quicksilver"]) { NSLog(@"Can't be loaded with QuickSilver, die."); return 0; } } // UInt32 beginTime; // beginTime = TickCount(); // murmur("CVLoader::init begin"); // get CVLoader.bundle loaderbundle=[NSBundle bundleWithIdentifier:@ TSBUNDLEID]; if (!loaderbundle) { NSLog (@"CVLoader: fatal error, bundle %s not found", TSBUNDLEID); return 0; } // connect to the display server if (!connectDisplayServer()) { NSLog ([NSString stringWithFormat: @"CVLoader: init failed, cannot start up OVDisplayServer at %@", CVLC_DISPLAYSERVER]); return 0; } // gets factory (default) settings CVConfig *factorydef=[[CVConfig alloc] initWithFile:CVLC_FACTORY_DEFAULTS defaultData:nil]; [factorydef autorelease]; // NSLog([[factorydef dictionary] description]); // member variables initialization activecontext=NULL; immenu=m; srv=new CVService(CVGetUserSpacePath(), dspsrvr); candi=new CVCandidate(dspsrvr); cfg=[[CVConfig alloc] initWithFile:CVGetUserConfigFilename() defaultData:[factorydef dictionary]]; modarray=[NSMutableArray new]; imarray=[NSMutableArray new]; ofarray=[NSMutableArray new]; // load configuration loaderdict=[[cfg dictionary] valueForKey:@"OVLoader" default:[[NSMutableDictionary new] autorelease]]; NSString *timeout=[loaderdict valueForKey:@"atomicInitLockTimeout" default:@"500"]; // check if our last atomic init failed if (checkIfLastAtomicInitFailed([timeout intValue])) return 0; // get library-exclude list and module-exclude list NSArray *libexclude=[loaderdict valueForKey:@"excludeLibraryList" default:[[NSArray new] autorelease]]; NSArray *modexclude=[loaderdict valueForKey:@"excludeModuleList" default:[[NSArray new] autorelease]]; NSMutableDictionary *loadhistory=[[NSMutableDictionary new] autorelease]; // load everything! [modarray addObjectsFromArray: CVLoadEverything(CVGetModuleLoadPath(), srv, libexclude, modexclude, loadhistory, CVGetAtomicInitLockFilename())]; // NSLog([loadhistory description]); // create menu groups and check all menu items, then sync config menudict=nil; kpmenugroup=immenugroup=ofmenugroup=NULL; refreshMenu(); syncMenuAndConfig(); // murmur("CVLoader::init ticks: %d", TickCount() - beginTime ); return 1; } void CVLoader::refreshMenu() { NSDictionary *cfgmenukey=[[cfg dictionary] valueForKey:@"OVMenuManager" default:[[NSMutableDictionary new] autorelease]]; if (!menudict) { menudict=[NSMutableDictionary new]; [menudict addEntriesFromDictionary:cfgmenukey]; createMenuGroups(); } else { if (![menudict isEqualToDictionary:cfgmenukey]) { // NSLog(@"OVMenuManager config node changed, rebuilding the menu"); [menudict removeAllObjects]; [menudict addEntriesFromDictionary:cfgmenukey]; createMenuGroups(); } } checkMenuItems(); } CVContext *CVLoader::newContext() { CVSAFETY; return new CVContext(this); } void CVLoader::setActiveContext(CVContext *c) { activecontext=c; if (!c) return; // check if the config has been changed and thus need reload! if (![cfg needSync]) return; [cfg sync]; // we have to reload loaderdict as it might have been gone! loaderdict=[[cfg dictionary] valueForKey:@"OVLoader" default:[[NSMutableDictionary new] autorelease]]; // now we tell every usable module to update config NSMutableDictionary *cfgdict=[cfg dictionary]; NSEnumerator *e=[modarray objectEnumerator]; CVModuleWrapper *m; while (m=[e nextObject]) { if (![m usable]) continue; NSString *mid=[m identifier]; NSMutableDictionary *mdict=[cfgdict valueForKey:mid default:[[NSMutableDictionary new] autorelease]]; CVDictionary cvd(mdict); OVModule *om=[m module]; om->update(&cvd, srv); } // now we refresh our menu refreshMenu(); syncMenuAndConfig(); } void CVLoader::menuHandler(unsigned int cmd) { CVSAFETY; if (immenugroup->clickItem(cmd)) { NSString *pIM=[NSString stringWithString: [loaderdict valueForKey:@"primaryInputMethod" default:@""]]; syncMenuAndConfig(); if (activecontext) { activecontext->syncConfig(); } // overwrite lastPrimaryInputMethod key NSString *npIM=[NSString stringWithString: [loaderdict valueForKey:@"primaryInputMethod" default:@""]]; if (![npIM isEqualToString:pIM]) [loaderdict setValue:pIM forKey:@"lastPrimaryInputMethod"]; return; } // if the item falls in key preprocessor group if (kpmenugroup->clickItem(cmd)) { syncMenuAndConfig(); if (activecontext) { activecontext->syncConfig(); CVSmartMenuItem *i=kpmenugroup->getMenuItem(cmd); // we re-use the function to display the enable/disable status showOutputFilterStatus([i idTag], [i checked]); } return; } // if the item falls in output filter group if (ofmenugroup->clickItem(cmd)) { syncMenuAndConfig(); if (activecontext) { CVSmartMenuItem *i=ofmenugroup->getMenuItem(cmd); showOutputFilterStatus([i idTag], [i checked]); } return; } switch (cmd) { case CVLMI_FASTIMSWITCH: switchToLastPrimaryIM(); return; case CVLMI_ABOUT: // the effect wasn't very satisfactory; we should launch an About application for this [dspsrvr aboutDialog]; return; case CVLMI_PREFERENCES: system([[NSString stringWithFormat:@"open %@", CVLC_PREFERENCEUTIL] UTF8String]); return; case CVLMI_HELP: if (!strcasecmp(srv->locale(), "zh_TW")) { system(CVLC_DOCUMENT_ZH_TW); } else if (!strcasecmp(srv->locale(), "zh_CN")) { system(CVLC_DOCUMENT_ZH_CN); } else { system(CVLC_DOCUMENT_EN); } return; } murmur("unknown menu item %d clicked", cmd); } id CVLoader::connectDisplayServer() { dspsrvr=nil; dspsrvr=[[NSConnection rootProxyForConnectionWithRegisteredName:OVDSPSRVR_NAME host:nil] retain]; if (!dspsrvr) { system([[NSString stringWithFormat:@"open %@", CVLC_DISPLAYSERVER] UTF8String]); // a total timeout of 1 sec for (int retry=0; retry<20; retry++) { NSLog([NSString stringWithFormat:@"OpenVanilla connects to OVDisplayServer, tick %d", retry]); usleep(50000); // wait for the server to be brought up (0.05 sec) dspsrvr=[[NSConnection rootProxyForConnectionWithRegisteredName:OVDSPSRVR_NAME host:nil] retain]; if (dspsrvr) break; } } if (dspsrvr) [dspsrvr setProtocolForProxy:@protocol(OVDisplayServer)]; return dspsrvr; } void CVLoader::createMenuGroups() { NSDictionary *cfgmenukey=[[cfg dictionary] valueForKey:@"OVMenuManager" default:[[NSMutableDictionary new] autorelease]]; if (kpmenugroup) delete kpmenugroup; if (immenugroup) delete immenugroup; if (ofmenugroup) delete ofmenugroup; CVDeleteMenu(immenu); kpmenugroup=new CVSmartMenuGroup(immenu, CVLMI_KPGROUPSTART, loaderbundle, CVSM_MULTIPLE); immenugroup=new CVSmartMenuGroup(immenu, CVLMI_IMGROUPSTART, loaderbundle, CVSM_EXCLUSIVE); ofmenugroup=new CVSmartMenuGroup(immenu, CVLMI_OFGROUPSTART, loaderbundle, CVSM_MULTIPLE); NSArray *kpa=CVGetModulesByType(modarray, @"OVKeyPreprocessor"); if ([kpa count]) { kpmenugroup->insertTitle(MSG(@"Key Preprocessors")); // get the "sorted-by" key to make KP submenu ordered by user settings NSArray *kporderedby=[cfgmenukey valueForKey:@"keyPreprocessorOrder" default:[[NSArray new] autorelease]]; pourModuleArrayIntoMenu(kpa, kpmenugroup, kporderedby); kpmenugroup->insertSeparator(); } NSArray *ima=CVGetModulesByType(modarray, @"OVInputMethod"); if ([ima count]) { immenugroup->insertTitle(MSG(@"Input Methods")); } else { immenugroup->insertTitle(MSG(@"No Input Method Available")); } pourModuleArrayIntoMenu(ima, immenugroup); immenugroup->insertSeparator(); // we get the "sorted-by" key to make our menu ordered by user's settings // we read our own cfgmenukey because we want to force sync the config file // if the key hasn't existed yet NSArray *oforderedby=[cfgmenukey valueForKey:@"outputFilterOrder" default:[[NSArray new] autorelease]]; NSArray *ofa=CVGetModulesByType(modarray, @"OVOutputFilter"); if ([ofa count]) { ofmenugroup->insertTitle(MSG(@"Output Filters")); } pourModuleArrayIntoMenu(ofa, ofmenugroup, oforderedby); if ([ofa count]) ofmenugroup->insertSeparator(); // the fastIMSwitch has a menudict key called "fastIMSwitch" if ([ima count]) { CVInsertMenuItem(immenu, CVLMI_FASTIMSWITCH, MSG(@"Input method toggle"), 0, [menudict valueForKey:@"fastIMSwitch"]); } CVInsertMenuItem(immenu, CVLMI_PREFERENCES, MSG(@"Preferences"), 0); CVInsertMenuItem(immenu, CVLMI_HELP, MSG(@"Help"), 0); CVInsertMenuItem(immenu, CVLMI_ABOUT, MSG(@"About"), 0); } void CVLoader::checkMenuItems() { // uncheck everything kpmenugroup->uncheckAll(); immenugroup->uncheckAll(); ofmenugroup->uncheckAll(); // check keyPreprocessors NSArray *kpa=[loaderdict valueForKey:@"keyPreprocessorArray" default:[[NSMutableArray new] autorelease]]; kpmenugroup->checkItemArray(kpa); // check primaryInputMethod NSString *pIM=[loaderdict valueForKey:@"primaryInputMethod" default:@""]; immenugroup->checkItem(pIM); // fall back when there is no input method selected NSArray *imchk=immenugroup->getCheckedItems(); if (![imchk count]) { murmur ("no IM checked, using the first item on the menu"); immenugroup->checkFirstItem(); } // check outputFilters NSArray *ofa=[loaderdict valueForKey:@"outputFilterArray" default:[[NSMutableArray new] autorelease]]; ofmenugroup->checkItemArray(ofa); } void CVLoader::syncMenuAndConfig() { // Carbon-app-friendly addition NSAutoreleasePool *pool=[NSAutoreleasePool new]; // anyway, are both groups are created according to module array, // we can be very sure that every checked item is in the module array // (they are autoreleased, so there's no memory management problem) NSArray *kpmgitems=kpmenugroup->getCheckedItems(); NSArray *immgitems=immenugroup->getCheckedItems(); NSArray *ofmgitems=ofmenugroup->getCheckedItems(); // remember the result object from CVFindModules is autoreleased NSArray *kpsrc=CVFindModules(modarray, kpmgitems, @"OVKeyPreprocessor"); NSArray *imsrc=CVFindModules(modarray, immgitems, @"OVInputMethod"); NSArray *ofsrc=CVFindModules(modarray, ofmgitems, @"OVOutputFilter"); // NOTA BENE: there is no "kparray" in CVLoader. Instead all loaded // key preprocessors are put into "imarray"--since key preprocessors // are themselves (derived) input methods. By doing this we also // put everything in order, i.e. all modules are put according to // the menu order into the imarray NSMutableArray *tmp=[[NSMutableArray new] autorelease]; [imarray removeAllObjects]; // since initializeModules() deletes everything in dst, we need this initializeModules(kpsrc, tmp, kpmenugroup); [imarray addObjectsFromArray:tmp]; initializeModules(imsrc, tmp, immenugroup); [imarray addObjectsFromArray:tmp]; initializeModules(ofsrc, ofarray, ofmenugroup); // after so much work, we write these back to config kpmgitems=kpmenugroup->getCheckedItems(); immgitems=immenugroup->getCheckedItems(); ofmgitems=ofmenugroup->getCheckedItems(); [loaderdict setValue:kpmgitems forKey:@"keyPreprocessorArray"]; [loaderdict setValue:ofmgitems forKey:@"outputFilterArray"]; if ([immgitems count]) { [loaderdict setValue:[immgitems objectAtIndex:0] forKey:@"primaryInputMethod"]; // write if lastPrimaryInputMethod doesn't exist yet [loaderdict valueForKey:@"lastPrimaryInputMethod" default:[immgitems objectAtIndex:0]]; } else { [loaderdict setValue:@"" forKey:@"primaryInputMethod"]; } // and get OVDisplayServer settings too NSDictionary *dsrvdict=[[cfg dictionary] valueForKey:@"OVDisplayServer" default:CVGetDisplayServerConfig()]; if (dspsrvr) { // NSLog(@"syncing config with display server"); [dspsrvr setConfig:dsrvdict]; } // sound and notification style settings NSString *shouldbeep=[loaderdict valueForKey:@"shouldBeep" default:@"1"]; NSString *beepsound=[loaderdict valueForKey:@"beepSound" default:@""]; srv->setShouldBeep([shouldbeep intValue]); srv->setBeepSound(beepsound); [cfg sync]; [pool release]; } void CVLoader::initializeModules(NSArray *src, NSMutableArray* dst, CVSmartMenuGroup *fallout) { // clean up dst array [dst removeAllObjects]; NSMutableDictionary *cfgdict=[cfg dictionary]; NSEnumerator *e=[src objectEnumerator]; CVModuleWrapper *m; while (m=[e nextObject]) { NSString *mid=[m identifier]; NSMutableDictionary *mdict=[cfgdict valueForKey:mid default:[[NSMutableDictionary new] autorelease]]; CVDictionary cvd(mdict); NSString *atomic=CVGetAtomicInitLockFilename(); CVAtomicInitStart(atomic, [m fromLibrary]); if ([m initializeWithConfig:&cvd service:srv]) { [dst addObject:m]; // added to our target array } else { // module init failed! cross out from our menu! fallout->uncheckItem(mid); fallout->disableItem(mid); } CVAtomicInitEnd(atomic); } } void CVLoader::pourModuleArrayIntoMenu(NSArray *ma, CVSmartMenuGroup *g, NSArray *orderedby) { NSMutableArray *a=[NSMutableArray arrayWithArray:ma]; NSEnumerator *e; const char *lc=srv->locale(); if (orderedby) { e=[orderedby objectEnumerator]; NSString *mid; while (mid=[e nextObject]) { CVModuleWrapper *w=CVFindModule(a, mid); if (!w) continue; OVModule *m=[w module]; g->insertItem([w identifier], [NSString stringWithUTF8String: m->localizedName(lc)], [menudict valueForKey:[w identifier]]); [a removeObject:w]; } } // pour in the remaining items e=[a objectEnumerator]; CVModuleWrapper *w; while (w=[e nextObject]) { OVModule *m=[w module]; g->insertItem([w identifier], [NSString stringWithUTF8String: m->localizedName(lc)], [menudict valueForKey:[w identifier]]); } } void CVLoader::showOutputFilterStatus(NSString *modid, BOOL s) { if (!activecontext) return; CVModuleWrapper *w=CVFindModule(modarray, modid); if (!w) return; OVModule *m=[w module]; NSString *display=[NSString stringWithFormat:@"%@ %@", [NSString stringWithUTF8String:m->localizedName(srv->locale())], s ? MSG(@"enabled") : MSG(@"disabled")]; srv->notify([display UTF8String]); srv->fadeNotification(); } void CVLoader::switchToLastPrimaryIM() { NSString *pIM=[NSString stringWithString: [loaderdict valueForKey:@"primaryInputMethod" default:@""]]; NSString *lIM=[NSString stringWithString: [loaderdict valueForKey:@"lastPrimaryInputMethod" default:@""]]; if ([pIM isEqualToString:lIM]) { // NSLog(@"lastPIM=PIM, return"); return; } // NSLog(@"lastPIM!=PIM, switching"); [loaderdict setValue:lIM forKey:@"primaryInputMethod"]; [loaderdict setValue:pIM forKey:@"lastPrimaryInputMethod"]; checkMenuItems(); syncMenuAndConfig(); if (activecontext) { activecontext->syncConfig(); } } BOOL CVLoader::checkIfLastAtomicInitFailed(int timeout) { NSString *atomic=CVGetAtomicInitLockFilename(); if (!CVIfPathExists(atomic)) return NO; NSLog(@"CVLoader: found atomic lock file, wait to see if it's a failure or a lock in another process."); int tick=0; while (tick < timeout) { if (!CVIfPathExists(atomic)) return NO; usleep(10000); // sleep for 1/100 second tick++; if (!(tick % 10)) NSLog(@"CVLoader: still waiting for the unlock (tick=%d, timeout=%d)", tick, timeout); } NSError *err; NSString *libname=[NSString stringWithContentsOfFile:atomic encoding:NSUTF8StringEncoding error:&err]; NSString *datestr=[[NSDate date] descriptionWithCalendarFormat:nil timeZone:nil locale:nil]; NSString *msg=[NSString stringWithFormat:MSG(@"AtomicInitFailed"), libname, atomic, datestr]; char *username=getenv("USER"); NSString *msgfile=[NSString stringWithFormat:@"%@-%s", CVGetAtomicInitErrorMessageFilename(), username ? username : "unknown-user"]; // this won't work in 10.3 // [msg writeToFile:msgfile atomically:YES encoding:NSUTF8StringEncoding error:&err]; [msg writeToFile:msgfile atomically:NO]; // unlink([atomic UTF8String]); system([[NSString stringWithFormat:@"open %@", msgfile] UTF8String]); return YES; } NSString *CVLoader::MSG(NSString *m) { return [loaderbundle localizedStringForKey:m value:nil table:nil]; } @protocol OVKeyReceiver - (void)sendKey:(char)c; @end @interface CVKeyReceiver : NSObject { CVContext *ctxt; } - (id)initWithContext:(CVContext*)c; - (void)sendKey:(char)c; - (void)unregister; - (void)dealloc; @end @implementation CVKeyReceiver - (void)dealloc { NSLog(@"object dealloc"); [super dealloc]; } - (void)unregister { NSLog(@"unregistering object"); NSConnection *cnc=[NSConnection defaultConnection]; // [cnc invalidate]; // [cnc setRootObject:nil]; if ([cnc registerName:nil]) { NSLog(@"unregister succeeded"); } else { NSLog(@"unregister failed"); } NSLog(@"invalidating connection"); } - (id)initWithContext:(CVContext*)c { ctxt=NULL; if ((self = [super init])) { ctxt=c; NSConnection *cnc=[NSConnection defaultConnection]; [cnc setRootObject:self]; if ([cnc registerName:@"OVKeyReceiverTest"]) { NSLog(@"OVKeyReceiverTest registered"); } else { NSLog(@"OVKeyReceiverTest registration failed"); } } return self; } - (void)sendKey:(char)c { if (ctxt) { NSLog(@"Sending key event via registered object. Key='%c'", c); ctxt->event(c, 0); } } @end CVContext::CVContext(CVLoader *p) { loader=p; buf=new CVBuffer(NULL, loader->ofarray, loader->srv); candistate=NULL; contexts=nil; // NSLog(@"dumping cfg"); // NSLog([loader->cfg description]); stamp=[loader->cfg timeStamp]; syncConfig(1); } CVContext::~CVContext() { CVSAFETY; delete buf; [contexts release]; } void CVContext::activate(TSComposingBuffer *b) { keyrcvr=[CVKeyReceiver alloc]; if (keyrcvr) [(CVKeyReceiver*)keyrcvr initWithContext:this]; CVSAFETY; buf->setComposingBuffer(b); loader->setActiveContext(this); // repositionInfoBoxes(); loader->srv->closeNotification(); if (!contexts) { contexts=[NSMutableArray new]; syncConfig(1); } else { syncConfig(); } loader->candi->hide()->clear()->update(); // if we find a saved candistate exists if (candistate) { if (!buf->isEmpty()) { // buffer not empty, we restore the state loader->candi->restoreState(candistate); } delete candistate; candistate=NULL; } else { // the context is actually activated anew NSEnumerator *e=[contexts objectEnumerator]; CVContextWrapper *w; while (w=[e nextObject]) { OVInputMethodContext *c=[w context]; if (c) c->start(buf, loader->candi, loader->srv); } } // showPrimaryIM(); } void CVContext::deactivate() { CVSAFETY; [keyrcvr unregister]; [keyrcvr release]; // if the buf is not empty, it means the user just switches away from the // current IM temporarily if (!buf->isEmpty()) { candistate=loader->candi->saveState(); } else { // context is not only deactivated but is also switched off NSEnumerator *e=[contexts objectEnumerator]; CVContextWrapper *w; while (w=[e nextObject]) { OVInputMethodContext *c=[w context]; if (c) c->end(); } } buf->setComposingBuffer(NULL); loader->setActiveContext(NULL); loader->candi->hide()->clear()->update(); loader->srv->closeNotification(); } void CVContext::fix() { CVSAFETY; // we can be 100% sure this is only called when an IM is actually // being deactivated, so we send out the remaining characters in the buffer if (!buf->isEmpty()) buf->send(); clearAll(); } int CVContext::event(char charcode, int modifiers) { CVSAFETY; CVKeyCode key(charcode, modifiers); loader->srv->closeNotification(); repositionInfoBoxes(); NSEnumerator *e=[contexts objectEnumerator]; CVContextWrapper *w; while (w=[e nextObject]) { OVInputMethodContext *c=[w context]; if (c) { if (c->keyEvent(&key, buf, loader->candi, loader->srv)) { // NSLog(@"key handled"); return 1; } } } return 0; } void CVContext::repositionInfoBoxes() { // reposition our two boxes Point p=buf->getAppCursorPosition(); p.v += 5; // to make the infoboxes a bit lower, makes 'em look better loader->candi->setPosition(p); // p.v += loader->candi->height(); // obsolete loader->srv->setNotificationPosition(p); } void CVContext::clearAll() { if (!buf->isEmpty()) buf->clear()->update(); if (loader->candi->onScreen()) loader->candi->hide(); loader->candi->clear()->update(); loader->srv->closeNotification(); NSEnumerator *e=[contexts objectEnumerator]; CVContextWrapper *w; while (w=[e nextObject]) { OVInputMethodContext *c=[w context]; if (c) c->clear(); } } void CVContext::syncConfig(int forced) { // if (forced) murmur("CVContext: forced update"); CVTimeTag loaderst=[loader->cfg timeStamp]; // if (!(stamp==loaderst)) murmur("config change: update"); if (forced || !(stamp==loaderst)) { // we have to sync config and switch IM stamp=loaderst; clearAll(); NSEnumerator *e=[contexts objectEnumerator]; CVContextWrapper *w; while (w=[e nextObject]) { OVInputMethodContext *c=[w context]; if (c) c->end(); } // murmur ("reloading contexts array, delete all context objects"); // NSLog([loader->imarray description]); [contexts removeAllObjects]; e=[loader->imarray objectEnumerator]; CVModuleWrapper *mw; while (mw=[e nextObject]) { OVInputMethod *m=(OVInputMethod*)[mw module]; // murmur ("creating context for IM %s", m->identifier()); OVInputMethodContext *c=m->newContext(); c->start(buf, loader->candi, loader->srv); CVContextWrapper *cw=[[CVContextWrapper alloc] initWithContext:c]; [contexts addObject:cw]; [cw release]; } if (!forced) showPrimaryIM(); } } void CVContext::showPrimaryIM() { NSString *pim=[loader->loaderdict valueForKey:@"primaryInputMethod"]; int c=[loader->imarray count]; if (!c) return; for (int i=0; iimarray objectAtIndex:i]; if (!mw) continue; if ([[mw identifier] isEqualToString:pim]) { OVModule *om=[mw module]; loader->srv->notify(om->localizedName(loader->srv->locale())); loader->srv->fadeNotification(); } } }