/* * Copyright 2004-2013 Haiku, Inc. All rights reserved. * Distributed under the terms of the MIT License. * * Authors: * Axel Dörfler, axeld@pinc-software.de * Jérôme Duval * Marcus Overhagen * John Scipione, jscipione@gmail.com */ //! Manager for input_server add-ons (devices, filters, methods) #include "AddOnManager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "InputServer.h" #include "InputServerTypes.h" #include "MethodReplicant.h" #undef TRACE //#define TRACE_ADD_ON_MONITOR #ifdef TRACE_ADD_ON_MONITOR # define TRACE(x...) debug_printf(x) # define ERROR(x...) debug_printf(x) #else # define TRACE(x...) ; // TODO: probably better to the syslog # define ERROR(x...) debug_printf(x) #endif class AddOnManager::MonitorHandler : public AddOnMonitorHandler { public: MonitorHandler(AddOnManager* manager) { fManager = manager; } virtual void AddOnEnabled(const add_on_entry_info* entryInfo) { CALLED(); entry_ref ref; make_entry_ref(entryInfo->dir_nref.device, entryInfo->dir_nref.node, entryInfo->name, &ref); BEntry entry(&ref, false); fManager->_RegisterAddOn(entry); } virtual void AddOnDisabled(const add_on_entry_info* entryInfo) { CALLED(); entry_ref ref; make_entry_ref(entryInfo->dir_nref.device, entryInfo->dir_nref.node, entryInfo->name, &ref); BEntry entry(&ref, false); fManager->_UnregisterAddOn(entry); } private: AddOnManager* fManager; }; // #pragma mark - template T* instantiate_add_on(image_id image, const char* path, const char* type) { T* (*instantiateFunction)(); BString functionName = "instantiate_input_"; functionName += type; if (get_image_symbol(image, functionName.String(), B_SYMBOL_TYPE_TEXT, (void**)&instantiateFunction) < B_OK) { ERROR("AddOnManager::_RegisterAddOn(): can't find %s() in \"%s\"\n", functionName.String(), path); return NULL; } T* addOn = (*instantiateFunction)(); if (addOn == NULL) { ERROR("AddOnManager::_RegisterAddOn(): %s() in \"%s\" returned " "NULL\n", functionName.String(), path); return NULL; } status_t status = addOn->InitCheck(); if (status != B_OK) { ERROR("AddOnManager::_RegisterAddOn(): InitCheck() in \"%s\" " "returned %s\n", path, strerror(status)); delete addOn; return NULL; } return addOn; } // #pragma mark - AddOnManager AddOnManager::AddOnManager() : AddOnMonitor(), fHandler(new(std::nothrow) MonitorHandler(this)) { openlog("input_server", LOG_PERROR, LOG_USER); SetHandler(fHandler); } AddOnManager::~AddOnManager() { delete fHandler; } void AddOnManager::MessageReceived(BMessage* message) { CALLED(); BMessage reply; status_t status; TRACE("%s what: %.4s\n", __PRETTY_FUNCTION__, (char*)&message->what); switch (message->what) { case IS_FIND_DEVICES: status = _HandleFindDevices(message, &reply); break; case IS_WATCH_DEVICES: status = _HandleWatchDevices(message, &reply); break; case IS_NOTIFY_DEVICE: status = _HandleNotifyDevice(message, &reply); break; case IS_IS_DEVICE_RUNNING: status = _HandleIsDeviceRunning(message, &reply); break; case IS_START_DEVICE: status = _HandleStartStopDevices(message, &reply); break; case IS_STOP_DEVICE: status = _HandleStartStopDevices(message, &reply); break; case IS_CONTROL_DEVICES: status = _HandleControlDevices(message, &reply); break; case SYSTEM_SHUTTING_DOWN: status = _HandleSystemShuttingDown(message, &reply); break; case IS_METHOD_REGISTER: status = _HandleMethodReplicant(message, &reply); break; case B_PATH_MONITOR: _HandleDeviceMonitor(message); return; default: AddOnMonitor::MessageReceived(message); return; } reply.AddInt32("status", status); message->SendReply(&reply); } void AddOnManager::LoadState() { _RegisterAddOns(); } void AddOnManager::SaveState() { CALLED(); _UnregisterAddOns(); } status_t AddOnManager::StartMonitoringDevice(DeviceAddOn* addOn, const char* device) { CALLED(); BString path; if (device[0] != '/') path = "/dev/"; path += device; TRACE("AddOnMonitor::StartMonitoringDevice(%s)\n", path.String()); bool newPath; status_t status = _AddDevicePath(addOn, path.String(), newPath); if (status == B_OK && newPath) { status = BPathMonitor::StartWatching(path.String(), B_WATCH_FILES_ONLY | B_WATCH_RECURSIVELY, this); if (status != B_OK) { bool lastPath; _RemoveDevicePath(addOn, path.String(), lastPath); } } return status; } status_t AddOnManager::StopMonitoringDevice(DeviceAddOn* addOn, const char *device) { CALLED(); BString path; if (device[0] != '/') path = "/dev/"; path += device; TRACE("AddOnMonitor::StopMonitoringDevice(%s)\n", path.String()); bool lastPath; status_t status = _RemoveDevicePath(addOn, path.String(), lastPath); if (status == B_OK && lastPath) BPathMonitor::StopWatching(path.String(), this); return status; } // #pragma mark - void AddOnManager::_RegisterAddOns() { CALLED(); BAutolock locker(this); fHandler->AddAddOnDirectories("input_server/devices"); fHandler->AddAddOnDirectories("input_server/filters"); fHandler->AddAddOnDirectories("input_server/methods"); } void AddOnManager::_UnregisterAddOns() { BAutolock locker(this); // We have to stop manually the add-ons because the monitor doesn't // disable them on exit while (device_info* info = fDeviceList.RemoveItemAt(0)) { gInputServer->StartStopDevices(*info->add_on, false); delete info; } // TODO: what about the filters/methods lists in the input_server? while (filter_info* info = fFilterList.RemoveItemAt(0)) { delete info; } while (method_info* info = fMethodList.RemoveItemAt(0)) { delete info; } } bool AddOnManager::_IsDevice(const char* path) const { return strstr(path, "input_server/devices") != 0; } bool AddOnManager::_IsFilter(const char* path) const { return strstr(path, "input_server/filters") != 0; } bool AddOnManager::_IsMethod(const char* path) const { return strstr(path, "input_server/methods") != 0; } status_t AddOnManager::_RegisterAddOn(BEntry& entry) { BPath path(&entry); entry_ref ref; status_t status = entry.GetRef(&ref); if (status < B_OK) return status; TRACE("AddOnManager::RegisterAddOn(): trying to load \"%s\"\n", path.Path()); image_id image = load_add_on(path.Path()); if (image < B_OK) { ERROR("load addon %s failed\n", path.Path()); return image; } status = B_ERROR; if (_IsDevice(path.Path())) { BInputServerDevice* device = instantiate_add_on( image, path.Path(), "device"); if (device != NULL) status = _RegisterDevice(device, ref, image); } else if (_IsFilter(path.Path())) { BInputServerFilter* filter = instantiate_add_on( image, path.Path(), "filter"); if (filter != NULL) status = _RegisterFilter(filter, ref, image); } else if (_IsMethod(path.Path())) { BInputServerMethod* method = instantiate_add_on( image, path.Path(), "method"); if (method != NULL) status = _RegisterMethod(method, ref, image); } else { ERROR("AddOnManager::_RegisterAddOn(): addon type not found for " "\"%s\" \n", path.Path()); } if (status != B_OK) unload_add_on(image); return status; } status_t AddOnManager::_UnregisterAddOn(BEntry& entry) { BPath path(&entry); entry_ref ref; status_t status = entry.GetRef(&ref); if (status < B_OK) return status; TRACE("AddOnManager::UnregisterAddOn(): trying to unload \"%s\"\n", path.Path()); BAutolock _(this); if (_IsDevice(path.Path())) { for (int32 i = fDeviceList.CountItems(); i-- > 0;) { device_info* info = fDeviceList.ItemAt(i); if (!strcmp(info->ref.name, ref.name)) { gInputServer->StartStopDevices(*info->add_on, false); delete fDeviceList.RemoveItemAt(i); break; } } } else if (_IsFilter(path.Path())) { for (int32 i = fFilterList.CountItems(); i-- > 0;) { filter_info* info = fFilterList.ItemAt(i); if (!strcmp(info->ref.name, ref.name)) { BAutolock locker(InputServer::gInputFilterListLocker); InputServer::gInputFilterList.RemoveItem(info->add_on); delete fFilterList.RemoveItemAt(i); break; } } } else if (_IsMethod(path.Path())) { BInputServerMethod* method = NULL; for (int32 i = fMethodList.CountItems(); i-- > 0;) { method_info* info = fMethodList.ItemAt(i); if (!strcmp(info->ref.name, ref.name)) { BAutolock locker(InputServer::gInputMethodListLocker); InputServer::gInputMethodList.RemoveItem(info->add_on); method = info->add_on; // this will only be used as a cookie, and not referenced // anymore delete fMethodList.RemoveItemAt(i); break; } } if (fMethodList.CountItems() <= 0) { // we remove the method replicant BDeskbar().RemoveItem(REPLICANT_CTL_NAME); gInputServer->SetMethodReplicant(NULL); } else if (method != NULL) { BMessage msg(IS_REMOVE_METHOD); msg.AddInt32("cookie", method->fOwner->Cookie()); if (gInputServer->MethodReplicant()) gInputServer->MethodReplicant()->SendMessage(&msg); } } return B_OK; } //! Takes over ownership of the \a device, regardless of success. status_t AddOnManager::_RegisterDevice(BInputServerDevice* device, const entry_ref& ref, image_id addOnImage) { BAutolock locker(this); for (int32 i = fDeviceList.CountItems(); i-- > 0;) { device_info* info = fDeviceList.ItemAt(i); if (!strcmp(info->ref.name, ref.name)) { // we already know this device delete device; return B_NAME_IN_USE; } } TRACE("AddOnManager::RegisterDevice, name %s\n", ref.name); device_info* info = new(std::nothrow) device_info; if (info == NULL) { delete device; return B_NO_MEMORY; } info->ref = ref; info->add_on = device; if (!fDeviceList.AddItem(info)) { delete info; return B_NO_MEMORY; } info->image = addOnImage; return B_OK; } //! Takes over ownership of the \a filter, regardless of success. status_t AddOnManager::_RegisterFilter(BInputServerFilter* filter, const entry_ref& ref, image_id addOnImage) { BAutolock _(this); for (int32 i = fFilterList.CountItems(); i-- > 0;) { filter_info* info = fFilterList.ItemAt(i); if (strcmp(info->ref.name, ref.name) == 0) { // we already know this ref delete filter; return B_NAME_IN_USE; } } TRACE("%s, name %s\n", __PRETTY_FUNCTION__, ref.name); filter_info* info = new(std::nothrow) filter_info; if (info == NULL) { delete filter; return B_NO_MEMORY; } info->ref = ref; info->add_on = filter; if (!fFilterList.AddItem(info)) { delete info; return B_NO_MEMORY; } BAutolock locker(InputServer::gInputFilterListLocker); if (!InputServer::gInputFilterList.AddItem(filter)) { fFilterList.RemoveItem(info, false); delete info; return B_NO_MEMORY; } info->image = addOnImage; return B_OK; } //! Takes over ownership of the \a method, regardless of success. status_t AddOnManager::_RegisterMethod(BInputServerMethod* method, const entry_ref& ref, image_id addOnImage) { BAutolock _(this); for (int32 i = fMethodList.CountItems(); i-- > 0;) { method_info* info = fMethodList.ItemAt(i); if (!strcmp(info->ref.name, ref.name)) { // we already know this ref delete method; return B_NAME_IN_USE; } } TRACE("%s, name %s\n", __PRETTY_FUNCTION__, ref.name); method_info* info = new(std::nothrow) method_info; if (info == NULL) { delete method; return B_NO_MEMORY; } info->ref = ref; info->add_on = method; if (!fMethodList.AddItem(info)) { delete info; return B_NO_MEMORY; } BAutolock locker(InputServer::gInputMethodListLocker); if (!InputServer::gInputMethodList.AddItem(method)) { fMethodList.RemoveItem(info); delete info; return B_NO_MEMORY; } info->image = addOnImage; if (gInputServer->MethodReplicant() == NULL) { _LoadReplicant(); if (gInputServer->MethodReplicant()) { _BMethodAddOn_ *addon = InputServer::gKeymapMethod.fOwner; addon->AddMethod(); } } if (gInputServer->MethodReplicant() != NULL) { _BMethodAddOn_ *addon = method->fOwner; addon->AddMethod(); } return B_OK; } // #pragma mark - void AddOnManager::_UnloadReplicant() { BDeskbar().RemoveItem(REPLICANT_CTL_NAME); } void AddOnManager::_LoadReplicant() { CALLED(); app_info info; be_app->GetAppInfo(&info); status_t err = BDeskbar().AddItem(&info.ref); if (err != B_OK) ERROR("Deskbar refuses to add method replicant: %s\n", strerror(err)); BMessage request(B_GET_PROPERTY); BMessenger to; BMessenger status; request.AddSpecifier("Messenger"); request.AddSpecifier("Shelf"); // In the Deskbar the Shelf is in the View "Status" in Window "Deskbar" request.AddSpecifier("View", "Status"); request.AddSpecifier("Window", "Deskbar"); to = BMessenger("application/x-vnd.Be-TSKB", -1); BMessage reply; if (to.SendMessage(&request, &reply) == B_OK && reply.FindMessenger("result", &status) == B_OK) { // enum replicant in Status view int32 index = 0; int32 uid; while ((uid = _GetReplicantAt(status, index++)) >= B_OK) { BMessage replicantInfo; if (_GetReplicantName(status, uid, &replicantInfo) != B_OK) continue; const char *name; if (replicantInfo.FindString("result", &name) == B_OK && !strcmp(name, REPLICANT_CTL_NAME)) { BMessage replicant; if (_GetReplicantView(status, uid, &replicant) == B_OK) { BMessenger result; if (replicant.FindMessenger("result", &result) == B_OK) { gInputServer->SetMethodReplicant(new BMessenger(result)); } } } } } if (!gInputServer->MethodReplicant()) { ERROR("LoadReplicant(): Method replicant not found!\n"); } } int32 AddOnManager::_GetReplicantAt(BMessenger target, int32 index) const { // So here we want to get the Unique ID of the replicant at the given index // in the target Shelf. BMessage request(B_GET_PROPERTY);// We're getting the ID property BMessage reply; status_t err; request.AddSpecifier("ID");// want the ID request.AddSpecifier("Replicant", index);// of the index'th replicant if ((err = target.SendMessage(&request, &reply)) != B_OK) return err; int32 uid; if ((err = reply.FindInt32("result", &uid)) != B_OK) return err; return uid; } status_t AddOnManager::_GetReplicantName(BMessenger target, int32 uid, BMessage* reply) const { // We send a message to the target shelf, asking it for the Name of the // replicant with the given unique id. BMessage request(B_GET_PROPERTY); BMessage uid_specifier(B_ID_SPECIFIER);// specifying via ID status_t err; status_t e; request.AddSpecifier("Name");// ask for the Name of the replicant // IDs are specified using code like the following 3 lines: uid_specifier.AddInt32("id", uid); uid_specifier.AddString("property", "Replicant"); request.AddSpecifier(&uid_specifier); if ((err = target.SendMessage(&request, reply)) != B_OK) return err; if (((err = reply->FindInt32("error", &e)) != B_OK) || (e != B_OK)) return err ? err : e; return B_OK; } status_t AddOnManager::_GetReplicantView(BMessenger target, int32 uid, BMessage* reply) const { // We send a message to the target shelf, asking it for the Name of the // replicant with the given unique id. BMessage request(B_GET_PROPERTY); BMessage uid_specifier(B_ID_SPECIFIER); // specifying via ID status_t err; status_t e; request.AddSpecifier("View"); // ask for the Name of the replicant // IDs are specified using code like the following 3 lines: uid_specifier.AddInt32("id", uid); uid_specifier.AddString("property", "Replicant"); request.AddSpecifier(&uid_specifier); if ((err = target.SendMessage(&request, reply)) != B_OK) return err; if (((err = reply->FindInt32("error", &e)) != B_OK) || (e != B_OK)) return err ? err : e; return B_OK; } status_t AddOnManager::_HandleStartStopDevices(BMessage* message, BMessage* reply) { const char *name = NULL; int32 type = 0; if (!((message->FindInt32("type", &type) != B_OK) ^ (message->FindString("device", &name) != B_OK))) return B_ERROR; return gInputServer->StartStopDevices(name, (input_device_type)type, message->what == IS_START_DEVICE); } status_t AddOnManager::_HandleFindDevices(BMessage* message, BMessage* reply) { CALLED(); const char *name = NULL; input_device_type type; if (message->FindString("device", &name) == B_OK) { if (gInputServer->GetDeviceInfo(name, &type) != B_OK) return B_NAME_NOT_FOUND; reply->AddString("device", name); reply->AddInt32("type", type); } else { gInputServer->GetDeviceInfos(reply); } return B_OK; } status_t AddOnManager::_HandleWatchDevices(BMessage* message, BMessage* reply) { BMessenger watcherMessenger; if (message->FindMessenger("target", &watcherMessenger) != B_OK) return B_ERROR; bool startWatching; if (message->FindBool("start", &startWatching) != B_OK) return B_ERROR; if (fWatcherMessengerList.find(watcherMessenger) == fWatcherMessengerList.end()) { if (startWatching) fWatcherMessengerList.insert(watcherMessenger); else return B_BAD_VALUE; } else { if (!startWatching) fWatcherMessengerList.erase(watcherMessenger); } return B_OK; } status_t AddOnManager::_HandleNotifyDevice(BMessage* message, BMessage* reply) { if (!message->HasBool("added") && !message->HasBool("started")) return B_BAD_VALUE; syslog(LOG_NOTICE, "Notify of added/removed/started/stopped device"); BMessage changeMessage(B_INPUT_DEVICES_CHANGED); bool deviceAdded; if (message->FindBool("added", &deviceAdded) == B_OK) { if (deviceAdded) changeMessage.AddInt32("be:opcode", B_INPUT_DEVICE_ADDED); else changeMessage.AddInt32("be:opcode", B_INPUT_DEVICE_REMOVED); } bool deviceStarted; if (message->FindBool("started", &deviceStarted) == B_OK) { if (deviceStarted) changeMessage.AddInt32("be:opcode", B_INPUT_DEVICE_STARTED); else changeMessage.AddInt32("be:opcode", B_INPUT_DEVICE_STOPPED); } BString deviceName; if (message->FindString("name", &deviceName) != B_OK) return B_BAD_VALUE; changeMessage.AddString("be:device_name", deviceName); input_device_type deviceType = B_UNDEFINED_DEVICE; if (message->FindInt32("type", deviceType) != B_OK) return B_BAD_VALUE; changeMessage.AddInt32("be:device_type", deviceType); std::set::iterator it = fWatcherMessengerList.begin(); while (it != fWatcherMessengerList.end()) { const BMessenger& currentMessenger = *it; status_t result = currentMessenger.SendMessage(&changeMessage); if (result != B_OK && !currentMessenger.IsValid()) fWatcherMessengerList.erase(it++); else it++; } return B_OK; } status_t AddOnManager::_HandleIsDeviceRunning(BMessage* message, BMessage* reply) { const char* name; bool running; if (message->FindString("device", &name) != B_OK || gInputServer->GetDeviceInfo(name, NULL, &running) != B_OK) return B_NAME_NOT_FOUND; return running ? B_OK : B_ERROR; } status_t AddOnManager::_HandleControlDevices(BMessage* message, BMessage* reply) { CALLED(); const char *name = NULL; int32 type = 0; if (!((message->FindInt32("type", &type) != B_OK) ^ (message->FindString("device", &name) != B_OK))) return B_BAD_VALUE; uint32 code = 0; BMessage controlMessage; bool hasMessage = true; if (message->FindInt32("code", (int32*)&code) != B_OK) return B_BAD_VALUE; if (message->FindMessage("message", &controlMessage) != B_OK) hasMessage = false; return gInputServer->ControlDevices(name, (input_device_type)type, code, hasMessage ? &controlMessage : NULL); } status_t AddOnManager::_HandleSystemShuttingDown(BMessage* message, BMessage* reply) { CALLED(); for (int32 i = 0; i < fDeviceList.CountItems(); i++) { device_info* info = fDeviceList.ItemAt(i); info->add_on->SystemShuttingDown(); } return B_OK; } status_t AddOnManager::_HandleMethodReplicant(BMessage* message, BMessage* reply) { CALLED(); if (InputServer::gInputMethodList.CountItems() == 0) { _UnloadReplicant(); return B_OK; } _LoadReplicant(); BAutolock lock(InputServer::gInputMethodListLocker); if (gInputServer->MethodReplicant()) { _BMethodAddOn_* addon = InputServer::gKeymapMethod.fOwner; addon->AddMethod(); for (int32 i = 0; i < InputServer::gInputMethodList.CountItems(); i++) { BInputServerMethod* method = (BInputServerMethod*)InputServer::gInputMethodList.ItemAt(i); _BMethodAddOn_* addon = method->fOwner; addon->AddMethod(); } } return B_OK; } void AddOnManager::_HandleDeviceMonitor(BMessage* message) { int32 opcode; if (message->FindInt32("opcode", &opcode) != B_OK) return; switch (opcode) { case B_ENTRY_CREATED: case B_ENTRY_REMOVED: { const char* path; const char* watchedPath; if (message->FindString("watched_path", &watchedPath) != B_OK || message->FindString("path", &path) != B_OK) { #if DEBUG char string[1024]; sprintf(string, "message does not contain all fields - " "watched_path: %d, path: %d\n", message->HasString("watched_path"), message->HasString("path")); debugger(string); #endif return; } // Notify all watching devices for (int32 i = 0; i < fDeviceAddOns.CountItems(); i++) { DeviceAddOn* addOn = fDeviceAddOns.ItemAt(i); if (!addOn->HasPath(watchedPath)) continue; addOn->Device()->Control(NULL, NULL, B_NODE_MONITOR, message); } break; } } } status_t AddOnManager::_AddDevicePath(DeviceAddOn* addOn, const char* path, bool& newPath) { newPath = !fDevicePaths.HasPath(path); status_t status = fDevicePaths.AddPath(path); if (status == B_OK) { status = addOn->AddPath(path); if (status == B_OK) { if (!fDeviceAddOns.HasItem(addOn) && !fDeviceAddOns.AddItem(addOn)) { addOn->RemovePath(path); status = B_NO_MEMORY; } } else fDevicePaths.RemovePath(path); } return status; } status_t AddOnManager::_RemoveDevicePath(DeviceAddOn* addOn, const char* path, bool& lastPath) { if (!fDevicePaths.HasPath(path) || !addOn->HasPath(path)) return B_ENTRY_NOT_FOUND; fDevicePaths.RemovePath(path); lastPath = !fDevicePaths.HasPath(path); addOn->RemovePath(path); if (addOn->CountPaths() == 0) fDeviceAddOns.RemoveItem(addOn); return B_OK; }