1/* 2 * Copyright 1999-2014 Haiku, Inc. All rights reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Jeremy Friesner 7 * Jessica Hamilton, jessica.l.hamilton@gmail.com 8 * John Scipione, jscipione@gmail.com 9 */ 10 11 12#include "KeyCommandMap.h" 13 14#include <stdio.h> 15 16#include <Beep.h> 17#include <Entry.h> 18#include <File.h> 19#include <FindDirectory.h> 20#include <MessageFilter.h> 21#include <NodeMonitor.h> 22#include <OS.h> 23#include <Path.h> 24#include <PathFinder.h> 25#include <PathMonitor.h> 26#include <StringList.h> 27#include <WindowScreen.h> 28 29#include "BitFieldTesters.h" 30#include "CommandActuators.h" 31#include "ShortcutsFilterConstants.h" 32 33 34#define FILE_UPDATED 'fiUp' 35 36 37// #pragma mark - hks 38 39 40class hks { 41public: 42 hks(int32 key, BitFieldTester* tester, CommandActuator* actuator, 43 const BMessage& actuatorMessage) 44 : 45 fKey(key), 46 fTester(tester), 47 fActuator(actuator), 48 fActuatorMessage(actuatorMessage) 49 { 50 } 51 52 ~hks() 53 { 54 delete fActuator; 55 delete fTester; 56 } 57 58 int32 GetKey() const 59 { return fKey; } 60 bool DoModifiersMatch(uint32 bits) const 61 { return fTester->IsMatching(bits); } 62 const BMessage& GetActuatorMessage() const 63 { return fActuatorMessage; } 64 CommandActuator* GetActuator() 65 { return fActuator; } 66 67private: 68 int32 fKey; 69 BitFieldTester* fTester; 70 CommandActuator* fActuator; 71 const BMessage fActuatorMessage; 72}; 73 74 75// #pragma mark - KeyCommandMap 76 77 78KeyCommandMap::KeyCommandMap(const char* file) 79 : 80 BLooper("Shortcuts map watcher"), 81 fSpecs(NULL) 82{ 83 fFileName = new char[strlen(file) + 1]; 84 strcpy(fFileName, file); 85 86 BEntry fileEntry(fFileName); 87 88 BPrivate::BPathMonitor::StartWatching(fFileName, 89 B_WATCH_STAT | B_WATCH_FILES_ONLY, this); 90 91 if (fileEntry.InitCheck() == B_OK) { 92 BMessage message(FILE_UPDATED); 93 PostMessage(&message); 94 } 95 96 fPort = create_port(1, SHORTCUTS_CATCHER_PORT_NAME); 97 _PutMessageToPort(); 98 // advertise our BMessenger to the world 99} 100 101 102KeyCommandMap::~KeyCommandMap() 103{ 104 if (fPort >= 0) 105 close_port(fPort); 106 107 for (int32 i = fInjects.CountItems() - 1; i >= 0; i--) 108 delete (BMessage*)fInjects.ItemAt(i); 109 110 BPrivate::BPathMonitor::StopWatching(BMessenger(this, this)); 111 // don't know if this is necessary, but it can't hurt 112 113 _DeleteHKSList(fSpecs); 114 115 delete[] fFileName; 116} 117 118 119void 120KeyCommandMap::MouseMessageReceived(const BMessage* message) 121{ 122 // Save the mouse state for later... 123 fLastMouseMessage = *message; 124} 125 126 127filter_result 128KeyCommandMap::KeyEvent(const BMessage* keyMessage, BList* outList, 129 const BMessenger& sendTo) 130{ 131 filter_result result = B_DISPATCH_MESSAGE; 132 uint32 modifiers; 133 int32 key; 134 135 if (keyMessage->FindInt32("modifiers", (int32*)&modifiers) == B_OK 136 && keyMessage->FindInt32("key", &key) == B_OK 137 && fSpecs != NULL && fSyncSpecs.Lock()) { 138 int32 count = fSpecs->CountItems(); 139 for (int32 i = 0; i < count; i++) { 140 hks* next = (hks*)fSpecs->ItemAt(i); 141 142 if (key == next->GetKey() && next->DoModifiersMatch(modifiers)) { 143 void* asyncData = NULL; 144 result = next->GetActuator()->KeyEvent(keyMessage, outList, 145 &asyncData, &fLastMouseMessage); 146 147 if (asyncData != NULL) { 148 BMessage newMessage(*keyMessage); 149 newMessage.AddMessage("act", &next->GetActuatorMessage()); 150 newMessage.AddPointer("adata", asyncData); 151 sendTo.SendMessage(&newMessage); 152 } 153 } 154 } 155 fSyncSpecs.Unlock(); 156 } 157 158 return result; 159} 160 161 162void 163KeyCommandMap::DrainInjectedEvents(const BMessage* keyMessage, BList* outList, 164 const BMessenger& sendTo) 165{ 166 BList temp; 167 if (fSyncSpecs.Lock()) { 168 temp = fInjects; 169 fInjects.MakeEmpty(); 170 fSyncSpecs.Unlock(); 171 } 172 173 int32 count = temp.CountItems(); 174 for (int32 i = 0; i < count; i++) { 175 BMessage* message = (BMessage*)temp.ItemAt(i); 176 177 BArchivable* archive = instantiate_object(message); 178 if (archive != NULL) { 179 CommandActuator* actuator 180 = dynamic_cast<CommandActuator*>(archive); 181 if (actuator != NULL) { 182 BMessage newMessage(*keyMessage); 183 newMessage.what = B_KEY_DOWN; 184 185 void* asyncData = NULL; 186 actuator->KeyEvent(&newMessage, outList, &asyncData, 187 &fLastMouseMessage); 188 189 if (asyncData != NULL) { 190 newMessage.AddMessage("act", message); 191 newMessage.AddPointer("adata", asyncData); 192 sendTo.SendMessage(&newMessage); 193 } 194 } 195 delete archive; 196 } 197 delete message; 198 } 199} 200 201 202void 203KeyCommandMap::MessageReceived(BMessage* message) 204{ 205 switch (message->what) { 206 case EXECUTE_COMMAND: 207 { 208 BMessage actuatorMessage; 209 if (message->FindMessage("act", &actuatorMessage) == B_OK) { 210 if (fSyncSpecs.Lock()) { 211 fInjects.AddItem(new BMessage(actuatorMessage)); 212 fSyncSpecs.Unlock(); 213 214 // This evil hack forces input_server to call Filter() on 215 // us so we can process the injected event. 216 BPoint where; 217 status_t err = fLastMouseMessage.FindPoint("where", &where); 218 if (err == B_OK) 219 set_mouse_position((int32)where.x, (int32)where.y); 220 } 221 } 222 break; 223 } 224 225 case REPLENISH_MESSENGER: 226 _PutMessageToPort(); 227 break; 228 229 case B_PATH_MONITOR: 230 { 231 const char* path = ""; 232 // only fall through for appropriate file 233 if (!(message->FindString("path", &path) == B_OK 234 && strcmp(path, fFileName) == 0)) { 235 dev_t device; 236 ino_t node; 237 if (message->FindInt32("device", &device) != B_OK 238 || message->FindInt64("node", &node) != B_OK 239 || device != fNodeRef.device 240 || node != fNodeRef.node) { 241 break; 242 } 243 } 244 } 245 // fall-through 246 case FILE_UPDATED: 247 { 248 BMessage fileMessage; 249 BFile file(fFileName, B_READ_ONLY); 250 BList* newList = new BList; 251 BList* oldList = NULL; 252 if (file.InitCheck() == B_OK && fileMessage.Unflatten(&file) 253 == B_OK) { 254 file.GetNodeRef(&fNodeRef); 255 int32 i = 0; 256 BMessage message; 257 while (fileMessage.FindMessage("spec", i++, &message) == B_OK) { 258 uint32 key; 259 BMessage testerMessage; 260 BMessage actuatorMessage; 261 262 if (message.FindInt32("key", (int32*)&key) == B_OK 263 && message.FindMessage("act", &actuatorMessage) == B_OK 264 && message.FindMessage("modtester", &testerMessage) 265 == B_OK) { 266 // Leave handling of add-ons shortcuts to Tracker 267 BString command; 268 if (message.FindString("command", &command) == B_OK) { 269 BStringList paths; 270 BPathFinder::FindPaths( 271 B_FIND_PATH_ADD_ONS_DIRECTORY, "Tracker", 272 paths); 273 bool foundAddOn = false; 274 int32 count = paths.CountStrings(); 275 for (int32 i = 0; i < count; i++) { 276 if (command.StartsWith(paths.StringAt(i))) { 277 foundAddOn = true; 278 break; 279 } 280 } 281 if (foundAddOn) 282 continue; 283 } 284 285 BArchivable* archive 286 = instantiate_object(&testerMessage); 287 if (BitFieldTester* tester 288 = dynamic_cast<BitFieldTester*>(archive)) { 289 archive = instantiate_object(&actuatorMessage); 290 CommandActuator* actuator 291 = dynamic_cast<CommandActuator*>(archive); 292 if (actuator != NULL) { 293 newList->AddItem(new hks(key, tester, actuator, 294 actuatorMessage)); 295 } else { 296 delete archive; 297 delete tester; 298 } 299 } else 300 delete archive; 301 } 302 } 303 } else { 304 fNodeRef.device = -1; 305 fNodeRef.node = -1; 306 } 307 308 if (fSyncSpecs.Lock()) { 309 // swap in the new list 310 oldList = fSpecs; 311 fSpecs = newList; 312 fSyncSpecs.Unlock(); 313 } else { 314 // This should never happen... but clean up if it does. 315 oldList = newList; 316 } 317 318 _DeleteHKSList(oldList); 319 break; 320 } 321 } 322} 323 324 325// #pragma mark - KeyCommandMap private methods 326 327 328//! Deletes an HKS-filled BList and its contents. 329void 330KeyCommandMap::_DeleteHKSList(BList* list) 331{ 332 if (list == NULL) 333 return; 334 335 int32 count = list->CountItems(); 336 for (int32 i = 0; i < count; i++) 337 delete (hks*)list->ItemAt(i); 338 339 delete list; 340} 341 342 343void 344KeyCommandMap::_PutMessageToPort() 345{ 346 if (fPort >= 0) { 347 BMessage message; 348 message.AddMessenger("target", this); 349 350 char buffer[2048]; 351 ssize_t size = message.FlattenedSize(); 352 if (size <= (ssize_t)sizeof(buffer) 353 && message.Flatten(buffer, size) == B_OK) { 354 write_port_etc(fPort, 0, buffer, size, B_TIMEOUT, 250000); 355 } 356 } 357} 358