From 26a67f70b75a53b37629642f8e67efbee5477a15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=97=B1=20PixelyIon?= Date: Sun, 17 Nov 2019 01:50:08 +0530 Subject: [PATCH] Move services out of the kernel and fix service registration In addition, this adds a constructor for "RegionInfo". --- .../main/cpp/skyline/gpu/devices/nvdevice.h | 2 +- app/src/main/cpp/skyline/kernel/svc.cpp | 2 +- app/src/main/cpp/skyline/memory.h | 4 +++- .../main/cpp/skyline/services/am/applet.cpp | 2 +- app/src/main/cpp/skyline/services/am/applet.h | 2 +- .../skyline/services/am/appletController.cpp | 2 +- .../skyline/services/am/appletController.h | 2 +- app/src/main/cpp/skyline/services/apm/apm.cpp | 2 +- app/src/main/cpp/skyline/services/apm/apm.h | 2 +- .../main/cpp/skyline/services/base_service.h | 3 ++- .../main/cpp/skyline/services/fatal/fatal.cpp | 2 +- .../main/cpp/skyline/services/fatal/fatal.h | 2 +- app/src/main/cpp/skyline/services/fs/fs.cpp | 2 +- app/src/main/cpp/skyline/services/fs/fs.h | 2 +- app/src/main/cpp/skyline/services/hid/hid.cpp | 2 +- app/src/main/cpp/skyline/services/hid/hid.h | 2 +- .../main/cpp/skyline/services/nvdrv/nvdrv.cpp | 2 +- .../main/cpp/skyline/services/nvdrv/nvdrv.h | 2 +- .../skyline/services/nvnflinger/dispdrv.cpp | 2 +- .../cpp/skyline/services/nvnflinger/dispdrv.h | 2 +- .../main/cpp/skyline/services/serviceman.cpp | 22 +++++++++---------- .../main/cpp/skyline/services/serviceman.h | 10 ++------- app/src/main/cpp/skyline/services/set/sys.cpp | 2 +- app/src/main/cpp/skyline/services/set/sys.h | 2 +- app/src/main/cpp/skyline/services/sm/sm.cpp | 2 +- app/src/main/cpp/skyline/services/sm/sm.h | 2 +- .../main/cpp/skyline/services/time/time.cpp | 2 +- .../main/cpp/skyline/services/time/timesrv.h | 2 +- app/src/main/cpp/skyline/services/vi/vi_m.cpp | 2 +- app/src/main/cpp/skyline/services/vi/vi_m.h | 2 +- 30 files changed, 43 insertions(+), 48 deletions(-) diff --git a/app/src/main/cpp/skyline/gpu/devices/nvdevice.h b/app/src/main/cpp/skyline/gpu/devices/nvdevice.h index e91d1dd6..0644ad13 100644 --- a/app/src/main/cpp/skyline/gpu/devices/nvdevice.h +++ b/app/src/main/cpp/skyline/gpu/devices/nvdevice.h @@ -213,7 +213,7 @@ namespace skyline::gpu::device { try { function = vTable.at(cmd); } catch (std::out_of_range &) { - state.logger->Warn("Cannot find IOCTL for device '{}': 0x{:X}", getName(), deviceType, cmd); + state.logger->Warn("Cannot find IOCTL for device '{}': 0x{:X}", getName(), cmd); input.status = NvStatus::NotImplemented; return; } diff --git a/app/src/main/cpp/skyline/kernel/svc.cpp b/app/src/main/cpp/skyline/kernel/svc.cpp index c7072019..d0873c74 100644 --- a/app/src/main/cpp/skyline/kernel/svc.cpp +++ b/app/src/main/cpp/skyline/kernel/svc.cpp @@ -34,7 +34,7 @@ namespace skyline::kernel::svc { break; } if (!subFound) - region->regionInfoVec.push_back(memory::RegionInfo{.address=addr, .size=size, .isUncached=isUncached}); + region->regionInfoVec.emplace_back(addr, size, isUncached); found = true; break; } diff --git a/app/src/main/cpp/skyline/memory.h b/app/src/main/cpp/skyline/memory.h index 8c9bd72d..16c4171e 100644 --- a/app/src/main/cpp/skyline/memory.h +++ b/app/src/main/cpp/skyline/memory.h @@ -70,7 +70,9 @@ namespace skyline::memory { struct RegionInfo { u64 address; //!< The starting address of the chunk of memory u64 size; //!< The size of the chunk of memory - bool isUncached{}; //!< If the following region is uncached + bool isUncached; //!< If the following region is uncached + + RegionInfo(u64 address, u64 size, bool isUncached) : address(address), size(size), isUncached(isUncached) {} }; /** diff --git a/app/src/main/cpp/skyline/services/am/applet.cpp b/app/src/main/cpp/skyline/services/am/applet.cpp index f8fc65be..eac044ed 100644 --- a/app/src/main/cpp/skyline/services/am/applet.cpp +++ b/app/src/main/cpp/skyline/services/am/applet.cpp @@ -1,7 +1,7 @@ #include "applet.h" #include "appletController.h" -namespace skyline::kernel::service::am { +namespace skyline::service::am { appletOE::appletOE(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::am_appletOE, { {0x0, SFUNC(appletOE::OpenApplicationProxy)} }) {} diff --git a/app/src/main/cpp/skyline/services/am/applet.h b/app/src/main/cpp/skyline/services/am/applet.h index 9ff657e8..d4f22e0d 100644 --- a/app/src/main/cpp/skyline/services/am/applet.h +++ b/app/src/main/cpp/skyline/services/am/applet.h @@ -3,7 +3,7 @@ #include #include -namespace skyline::kernel::service::am { +namespace skyline::service::am { /** * @brief appletOE is used to open an application proxy (https://switchbrew.org/wiki/Applet_Manager_services#appletOE) */ diff --git a/app/src/main/cpp/skyline/services/am/appletController.cpp b/app/src/main/cpp/skyline/services/am/appletController.cpp index 87843191..c0657831 100644 --- a/app/src/main/cpp/skyline/services/am/appletController.cpp +++ b/app/src/main/cpp/skyline/services/am/appletController.cpp @@ -1,6 +1,6 @@ #include "appletController.h" -namespace skyline::kernel::service::am { +namespace skyline::service::am { void ICommonStateGetter::QueueMessage(ICommonStateGetter::Message message) { messageQueue.emplace(message); messageEvent->Signal(); diff --git a/app/src/main/cpp/skyline/services/am/appletController.h b/app/src/main/cpp/skyline/services/am/appletController.h index 828aceec..e24a7c3e 100644 --- a/app/src/main/cpp/skyline/services/am/appletController.h +++ b/app/src/main/cpp/skyline/services/am/appletController.h @@ -6,7 +6,7 @@ #include #include -namespace skyline::kernel::service::am { +namespace skyline::service::am { /** * @brief https://switchbrew.org/wiki/Applet_Manager_services#ICommonStateGetter */ diff --git a/app/src/main/cpp/skyline/services/apm/apm.cpp b/app/src/main/cpp/skyline/services/apm/apm.cpp index 57b9f058..ee15f6ef 100644 --- a/app/src/main/cpp/skyline/services/apm/apm.cpp +++ b/app/src/main/cpp/skyline/services/apm/apm.cpp @@ -1,6 +1,6 @@ #include "apm.h" -namespace skyline::kernel::service::apm { +namespace skyline::service::apm { apm::apm(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::apm, { {0x0, SFUNC(apm::OpenSession)} }) {} diff --git a/app/src/main/cpp/skyline/services/apm/apm.h b/app/src/main/cpp/skyline/services/apm/apm.h index 802aaf6d..503f6e78 100644 --- a/app/src/main/cpp/skyline/services/apm/apm.h +++ b/app/src/main/cpp/skyline/services/apm/apm.h @@ -3,7 +3,7 @@ #include #include -namespace skyline::kernel::service::apm { +namespace skyline::service::apm { /** * @brief apm is used to control performance modes of the device, this service however is mostly only used to open an ISession (https://switchbrew.org/wiki/PPC_services#apm) */ diff --git a/app/src/main/cpp/skyline/services/base_service.h b/app/src/main/cpp/skyline/services/base_service.h index 30251460..d96c3279 100644 --- a/app/src/main/cpp/skyline/services/base_service.h +++ b/app/src/main/cpp/skyline/services/base_service.h @@ -10,7 +10,8 @@ namespace skyline::kernel::type { class KSession; } -namespace skyline::kernel::service { +namespace skyline::service { + using namespace kernel; /** * @brief This contains an enum for every service that's present */ diff --git a/app/src/main/cpp/skyline/services/fatal/fatal.cpp b/app/src/main/cpp/skyline/services/fatal/fatal.cpp index 4cf879d7..b50e7829 100644 --- a/app/src/main/cpp/skyline/services/fatal/fatal.cpp +++ b/app/src/main/cpp/skyline/services/fatal/fatal.cpp @@ -1,6 +1,6 @@ #include "fatal.h" -namespace skyline::kernel::service::fatal { +namespace skyline::service::fatal { fatalU::fatalU(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::fatal_u, { {0x0, SFUNC(fatalU::ThrowFatal)}, {0x1, SFUNC(fatalU::ThrowFatal)}, diff --git a/app/src/main/cpp/skyline/services/fatal/fatal.h b/app/src/main/cpp/skyline/services/fatal/fatal.h index 35e40b02..56e70767 100644 --- a/app/src/main/cpp/skyline/services/fatal/fatal.h +++ b/app/src/main/cpp/skyline/services/fatal/fatal.h @@ -3,7 +3,7 @@ #include #include -namespace skyline::kernel::service::fatal { +namespace skyline::service::fatal { /** * @brief fatal_u is used by applications to throw errors (https://switchbrew.org/wiki/Fatal_services#fatal:u) */ diff --git a/app/src/main/cpp/skyline/services/fs/fs.cpp b/app/src/main/cpp/skyline/services/fs/fs.cpp index a9fe369a..5cdc21ff 100644 --- a/app/src/main/cpp/skyline/services/fs/fs.cpp +++ b/app/src/main/cpp/skyline/services/fs/fs.cpp @@ -1,6 +1,6 @@ #include "fs.h" -namespace skyline::kernel::service::fs { +namespace skyline::service::fs { fsp::fsp(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::fs_fsp, { {0x1, SFUNC(fsp::SetCurrentProcess)}, {0x12, SFUNC(fsp::OpenSdCardFileSystem)} diff --git a/app/src/main/cpp/skyline/services/fs/fs.h b/app/src/main/cpp/skyline/services/fs/fs.h index 1793c57b..5b11de3d 100644 --- a/app/src/main/cpp/skyline/services/fs/fs.h +++ b/app/src/main/cpp/skyline/services/fs/fs.h @@ -3,7 +3,7 @@ #include #include -namespace skyline::kernel::service::fs { +namespace skyline::service::fs { /** * @brief These are the possible types of the filesystem */ diff --git a/app/src/main/cpp/skyline/services/hid/hid.cpp b/app/src/main/cpp/skyline/services/hid/hid.cpp index 369f5599..0f549cfd 100644 --- a/app/src/main/cpp/skyline/services/hid/hid.cpp +++ b/app/src/main/cpp/skyline/services/hid/hid.cpp @@ -1,7 +1,7 @@ #include "hid.h" #include -namespace skyline::kernel::service::hid { +namespace skyline::service::hid { IAppletResource::IAppletResource(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::hid_IAppletResource, { {0x0, SFUNC(IAppletResource::GetSharedMemoryHandle)} }) {} diff --git a/app/src/main/cpp/skyline/services/hid/hid.h b/app/src/main/cpp/skyline/services/hid/hid.h index a6f2fc1f..75ac6553 100644 --- a/app/src/main/cpp/skyline/services/hid/hid.h +++ b/app/src/main/cpp/skyline/services/hid/hid.h @@ -8,7 +8,7 @@ namespace skyline::constant { constexpr size_t hidSharedMemSize = 0x40000; //!< The size of HID Shared Memory (https://switchbrew.org/wiki/HID_Shared_Memory) } -namespace skyline::kernel::service::hid { +namespace skyline::service::hid { /** * @brief IAppletResource is used to get the handle to the HID shared memory (https://switchbrew.org/wiki/HID_services#IAppletResource) */ diff --git a/app/src/main/cpp/skyline/services/nvdrv/nvdrv.cpp b/app/src/main/cpp/skyline/services/nvdrv/nvdrv.cpp index 42573181..60e142ff 100644 --- a/app/src/main/cpp/skyline/services/nvdrv/nvdrv.cpp +++ b/app/src/main/cpp/skyline/services/nvdrv/nvdrv.cpp @@ -1,7 +1,7 @@ #include "nvdrv.h" #include -namespace skyline::kernel::service::nvdrv { +namespace skyline::service::nvdrv { nvdrv::nvdrv(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::nvdrv, { {0x0, SFUNC(nvdrv::Open)}, {0x1, SFUNC(nvdrv::Ioctl)}, diff --git a/app/src/main/cpp/skyline/services/nvdrv/nvdrv.h b/app/src/main/cpp/skyline/services/nvdrv/nvdrv.h index 0a7b9b11..38f74456 100644 --- a/app/src/main/cpp/skyline/services/nvdrv/nvdrv.h +++ b/app/src/main/cpp/skyline/services/nvdrv/nvdrv.h @@ -5,7 +5,7 @@ #include #include -namespace skyline::kernel::service::nvdrv { +namespace skyline::service::nvdrv { /** * @brief nvdrv or INvDrvServices is used to access the Nvidia GPU inside the Switch (https://switchbrew.org/wiki/NV_services#nvdrv.2C_nvdrv:a.2C_nvdrv:s.2C_nvdrv:t) */ diff --git a/app/src/main/cpp/skyline/services/nvnflinger/dispdrv.cpp b/app/src/main/cpp/skyline/services/nvnflinger/dispdrv.cpp index b146fbc1..ee73047f 100644 --- a/app/src/main/cpp/skyline/services/nvnflinger/dispdrv.cpp +++ b/app/src/main/cpp/skyline/services/nvnflinger/dispdrv.cpp @@ -3,7 +3,7 @@ #include #include -namespace skyline::kernel::service::nvnflinger { +namespace skyline::service::nvnflinger { dispdrv::dispdrv(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::nvnflinger_dispdrv, { {0x0, SFUNC(dispdrv::TransactParcel)}, {0x1, SFUNC(dispdrv::AdjustRefcount)}, diff --git a/app/src/main/cpp/skyline/services/nvnflinger/dispdrv.h b/app/src/main/cpp/skyline/services/nvnflinger/dispdrv.h index 203798fc..081843a8 100644 --- a/app/src/main/cpp/skyline/services/nvnflinger/dispdrv.h +++ b/app/src/main/cpp/skyline/services/nvnflinger/dispdrv.h @@ -4,7 +4,7 @@ #include "services/serviceman.h" #include -namespace skyline::kernel::service::nvnflinger { +namespace skyline::service::nvnflinger { /** * @brief This enumerates the functions called by TransactParcel for android.gui.IGraphicBufferProducer * @refitem https://android.googlesource.com/platform/frameworks/native/+/8dc5539/libs/gui/IGraphicBufferProducer.cpp#35 diff --git a/app/src/main/cpp/skyline/services/serviceman.cpp b/app/src/main/cpp/skyline/services/serviceman.cpp index d05eeaf5..c3a28ee4 100644 --- a/app/src/main/cpp/skyline/services/serviceman.cpp +++ b/app/src/main/cpp/skyline/services/serviceman.cpp @@ -13,10 +13,13 @@ #include "vi/vi_m.h" #include "nvnflinger/dispdrv.h" -namespace skyline::kernel::service { +namespace skyline::service { ServiceManager::ServiceManager(const DeviceState &state) : state(state) {} std::shared_ptr ServiceManager::GetService(const Service serviceType) { + if(serviceMap.count(serviceType)) { + return serviceMap.at(serviceType); + } std::shared_ptr serviceObj; switch (serviceType) { case Service::sm: @@ -112,7 +115,7 @@ namespace skyline::kernel::service { default: throw exception("GetService called on missing object, type: {}", serviceType); } - serviceVec.push_back(serviceObj); + serviceMap[serviceType] = serviceObj; return serviceObj; } @@ -136,7 +139,6 @@ namespace skyline::kernel::service { } void ServiceManager::RegisterService(std::shared_ptr serviceObject, type::KSession &session, ipc::IpcResponse &response) { // NOLINT(performance-unnecessary-value-param) - serviceVec.push_back(serviceObject); handle_t handle{}; if (response.isDomain) { session.domainTable[session.handleIndex] = serviceObject; @@ -154,15 +156,15 @@ namespace skyline::kernel::service { if (session->serviceStatus == type::KSession::ServiceStatus::Open) { if (session->isDomain) { for (const auto &[objectId, service] : session->domainTable) - serviceVec.erase(std::remove(serviceVec.begin(), serviceVec.end(), service), serviceVec.end()); + serviceMap.erase(service->serviceType); } else - serviceVec.erase(std::remove(serviceVec.begin(), serviceVec.end(), session->serviceObject), serviceVec.end()); + serviceMap.erase(session->serviceObject->serviceType); session->serviceStatus = type::KSession::ServiceStatus::Closed; } }; void ServiceManager::Loop() { - for (auto &service : serviceVec) + for (auto& [type, service] : serviceMap) if (service->hasLoop) service->Loop(); } @@ -187,7 +189,7 @@ namespace skyline::kernel::service { service->HandleRequest(*session, request, response); break; case ipc::DomainCommand::CloseVHandle: - serviceVec.erase(std::remove(serviceVec.begin(), serviceVec.end(), service), serviceVec.end()); + serviceMap.erase(service->serviceType); session->domainTable.erase(request.domain->objectId); break; } @@ -209,7 +211,7 @@ namespace skyline::kernel::service { break; case ipc::ControlCommand::CloneCurrentObject: case ipc::ControlCommand::CloneCurrentObjectEx: - CloneSession(*session, request, response); + response.WriteValue(state.thisProcess->InsertItem(session)); break; case ipc::ControlCommand::QueryPointerBufferSize: response.WriteValue(0x1000); @@ -232,8 +234,4 @@ namespace skyline::kernel::service { state.logger->Warn("svcSendSyncRequest called on closed handle: 0x{:X}", handle); state.logger->Debug("====End===="); } - - void ServiceManager::CloneSession(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { - //NewService(session.serviceType, session, response); - } } diff --git a/app/src/main/cpp/skyline/services/serviceman.h b/app/src/main/cpp/skyline/services/serviceman.h index 74ef33fa..bd2e69d8 100644 --- a/app/src/main/cpp/skyline/services/serviceman.h +++ b/app/src/main/cpp/skyline/services/serviceman.h @@ -4,14 +4,14 @@ #include #include "base_service.h" -namespace skyline::kernel::service { +namespace skyline::service { /** * @brief The ServiceManager class manages passing IPC requests to the right Service and running event loops of Services */ class ServiceManager { private: const DeviceState &state; //!< The state of the device - std::vector> serviceVec; //!< A vector with all of the services + std::unordered_map> serviceMap; //!< A mapping from a Service to the underlying object /** * @param serviceType The type of service requested @@ -64,11 +64,5 @@ namespace skyline::kernel::service { * @param handle The handle of the object */ void SyncRequestHandler(const handle_t handle); - - /** - * @brief Duplicates a session - * @param handle The handle of the object - */ - void CloneSession(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); }; } diff --git a/app/src/main/cpp/skyline/services/set/sys.cpp b/app/src/main/cpp/skyline/services/set/sys.cpp index 388e4676..6e3c0020 100644 --- a/app/src/main/cpp/skyline/services/set/sys.cpp +++ b/app/src/main/cpp/skyline/services/set/sys.cpp @@ -1,7 +1,7 @@ #include "sys.h" #include -namespace skyline::kernel::service::set { +namespace skyline::service::set { sys::sys(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::set_sys, {{0x3, SFUNC(sys::GetFirmwareVersion)}}) {} void sys::GetFirmwareVersion(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { diff --git a/app/src/main/cpp/skyline/services/set/sys.h b/app/src/main/cpp/skyline/services/set/sys.h index d163961a..bc184419 100644 --- a/app/src/main/cpp/skyline/services/set/sys.h +++ b/app/src/main/cpp/skyline/services/set/sys.h @@ -3,7 +3,7 @@ #include #include -namespace skyline::kernel::service::set { +namespace skyline::service::set { /** * @brief set:sys or System Settings service provides access to system settings */ diff --git a/app/src/main/cpp/skyline/services/sm/sm.cpp b/app/src/main/cpp/skyline/services/sm/sm.cpp index fdcd219f..d8d66039 100644 --- a/app/src/main/cpp/skyline/services/sm/sm.cpp +++ b/app/src/main/cpp/skyline/services/sm/sm.cpp @@ -1,6 +1,6 @@ #include "sm.h" -namespace skyline::kernel::service::sm { +namespace skyline::service::sm { sm::sm(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::sm, { {0x0, SFUNC(sm::Initialize)}, {0x1, SFUNC(sm::GetService)} diff --git a/app/src/main/cpp/skyline/services/sm/sm.h b/app/src/main/cpp/skyline/services/sm/sm.h index 1e787fc7..b6ab50d2 100644 --- a/app/src/main/cpp/skyline/services/sm/sm.h +++ b/app/src/main/cpp/skyline/services/sm/sm.h @@ -3,7 +3,7 @@ #include #include -namespace skyline::kernel::service::sm { +namespace skyline::service::sm { /** * @brief sm: or Service Manager is responsible for providing handles to services (https://switchbrew.org/wiki/Services_API) */ diff --git a/app/src/main/cpp/skyline/services/time/time.cpp b/app/src/main/cpp/skyline/services/time/time.cpp index d73a8a5c..2e77ace0 100644 --- a/app/src/main/cpp/skyline/services/time/time.cpp +++ b/app/src/main/cpp/skyline/services/time/time.cpp @@ -1,6 +1,6 @@ #include "timesrv.h" -namespace skyline::kernel::service::time { +namespace skyline::service::time { time::time(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::time, { {0x0, SFUNC(time::GetStandardUserSystemClock)}, {0x1, SFUNC(time::GetStandardNetworkSystemClock)}, diff --git a/app/src/main/cpp/skyline/services/time/timesrv.h b/app/src/main/cpp/skyline/services/time/timesrv.h index 1ed3de55..a52d2eef 100644 --- a/app/src/main/cpp/skyline/services/time/timesrv.h +++ b/app/src/main/cpp/skyline/services/time/timesrv.h @@ -3,7 +3,7 @@ #include #include -namespace skyline::kernel::service::time { +namespace skyline::service::time { /** * @brief The type of a #SystemClockType */ diff --git a/app/src/main/cpp/skyline/services/vi/vi_m.cpp b/app/src/main/cpp/skyline/services/vi/vi_m.cpp index 385d36f4..159e7dfc 100644 --- a/app/src/main/cpp/skyline/services/vi/vi_m.cpp +++ b/app/src/main/cpp/skyline/services/vi/vi_m.cpp @@ -3,7 +3,7 @@ #include #include -namespace skyline::kernel::service::vi { +namespace skyline::service::vi { vi_m::vi_m(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::nvdrv, { {0x2, SFUNC(vi_m::GetDisplayService)} }) {} diff --git a/app/src/main/cpp/skyline/services/vi/vi_m.h b/app/src/main/cpp/skyline/services/vi/vi_m.h index 932a9a42..ca9212b1 100644 --- a/app/src/main/cpp/skyline/services/vi/vi_m.h +++ b/app/src/main/cpp/skyline/services/vi/vi_m.h @@ -5,7 +5,7 @@ #include #include -namespace skyline::kernel::service::vi { +namespace skyline::service::vi { /** * @brief This service is used to get an handle to #IApplicationDisplayService (https://switchbrew.org/wiki/Display_services#vi:m) */