| /* |
| * If not stated otherwise in this file or this component's LICENSE file the |
| * following copyright and licenses apply: |
| * |
| * Copyright 2020 RDK Management |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "Module.h" |
| #include <interfaces/IMemory.h> |
| #include <interfaces/IBrowser.h> |
| |
| extern "C" { |
| |
| int StarboardMain(int argc, char **argv); |
| void SbRdkHandleDeepLink(const char* link); |
| void SbRdkSuspend(); |
| void SbRdkResume(); |
| void SbRdkQuit(); |
| |
| } // extern "C" |
| |
| namespace WPEFramework { |
| |
| namespace Plugin { |
| |
| static void SetThunderAccessPointIfNeeded() { |
| const std::string envName = _T("THUNDER_ACCESS"); |
| std::string envVal; |
| if (Core::SystemInfo::GetEnvironment(envName, envVal)) |
| return; |
| |
| Core::File file("/etc/WPEFramework/config.json", false); |
| if (!file.Open(true)) |
| return; |
| |
| JsonObject config; |
| if (config.IElement::FromFile(file)) { |
| Core::JSON::String port = config.Get("port"); |
| Core::JSON::String binding = config.Get("binding"); |
| envVal = binding.Value() + ":" + port.Value(); |
| Core::SystemInfo::SetEnvironment(envName, envVal); |
| } |
| file.Close(); |
| } |
| |
| class CobaltImplementation: |
| public Exchange::IBrowser, |
| public PluginHost::IStateControl { |
| private: |
| class Config: public Core::JSON::Container { |
| private: |
| Config(const Config&); |
| Config& operator=(const Config&); |
| |
| public: |
| Config() : |
| Core::JSON::Container(), Url() { |
| Add(_T("url"), &Url); |
| Add(_T("clientidentifier"), &ClientIdentifier); |
| } |
| ~Config() { |
| } |
| |
| public: |
| Core::JSON::String Url; |
| Core::JSON::String ClientIdentifier; |
| }; |
| |
| class NotificationSink: public Core::Thread { |
| private: |
| NotificationSink() = delete; |
| NotificationSink(const NotificationSink&) = delete; |
| NotificationSink& operator=(const NotificationSink&) = delete; |
| |
| public: |
| NotificationSink(CobaltImplementation &parent) : |
| _parent(parent), _waitTime(0), _command( |
| PluginHost::IStateControl::SUSPEND) { |
| } |
| virtual ~NotificationSink() { |
| Stop(); |
| Wait(Thread::STOPPED | Thread::BLOCKED, Core::infinite); |
| } |
| |
| public: |
| void RequestForStateChange( |
| const PluginHost::IStateControl::command command) { |
| _lock.Lock(); |
| _command = command; |
| _lock.Unlock(); |
| Run(); |
| } |
| |
| private: |
| virtual uint32_t Worker() { |
| bool success = false; |
| |
| _lock.Lock(); |
| const PluginHost::IStateControl::command command = _command; |
| _lock.Unlock(); |
| |
| if ((IsRunning() == true) && (success == false)) { |
| success = _parent.RequestForStateChange(command); |
| } |
| Block(); |
| _parent.StateChangeCompleted(success, command); |
| |
| if (success == true) { |
| _lock.Lock(); |
| bool completed = (command == _command); |
| _lock.Unlock(); |
| // Spin one more time |
| if (!completed) |
| Run(); |
| } |
| |
| return (Core::infinite); |
| } |
| |
| private: |
| CobaltImplementation &_parent; |
| uint32_t _waitTime; |
| PluginHost::IStateControl::command _command; |
| mutable Core::CriticalSection _lock; |
| }; |
| |
| class CobaltWindow : public Core::Thread { |
| private: |
| CobaltWindow(const CobaltWindow&) = delete; |
| CobaltWindow& operator=(const CobaltWindow&) = delete; |
| |
| public: |
| CobaltWindow(CobaltImplementation& parent) |
| : Core::Thread(0, _T("Cobalt")) |
| , _url("https://www.youtube.com/tv") |
| , _parent(parent) |
| { |
| } |
| virtual ~CobaltWindow() |
| { |
| Block(); |
| SbRdkQuit(); |
| Wait(Thread::BLOCKED | Thread::STOPPED | Thread::STOPPING, Core::infinite); |
| exit(_exitCode); |
| } |
| |
| uint32_t Configure(PluginHost::IShell* service) { |
| uint32_t result = Core::ERROR_NONE; |
| |
| Config config; |
| config.FromString(service->ConfigLine()); |
| |
| Core::Directory(service->PersistentPath().c_str()).CreatePath(); |
| Core::SystemInfo::SetEnvironment(_T("HOME"), service->PersistentPath()); |
| Core::SystemInfo::SetEnvironment(_T("COBALT_TEMP"), service->VolatilePath()); |
| if (config.ClientIdentifier.IsSet() == true) { |
| string value(service->Callsign() + ',' + config.ClientIdentifier.Value()); |
| Core::SystemInfo::SetEnvironment(_T("CLIENT_IDENTIFIER"), value); |
| Core::SystemInfo::SetEnvironment(_T("WAYLAND_DISPLAY"), config.ClientIdentifier.Value()); |
| } else { |
| Core::SystemInfo::SetEnvironment(_T("CLIENT_IDENTIFIER"), service->Callsign()); |
| } |
| |
| std::string tmpVal; |
| if (!Core::SystemInfo::GetEnvironment(_T("COBALT_CONTENT_DIR"), tmpVal)) { |
| const char kContentDir[] = |
| "/usr/share/content/data:" |
| "/media/apps/libcobalt/usr/share/content/data:" |
| "/tmp/libcobalt/usr/share/content/data"; |
| Core::SystemInfo::SetEnvironment(_T("COBALT_CONTENT_DIR"), kContentDir); |
| } |
| |
| SetThunderAccessPointIfNeeded(); |
| |
| if (config.Url.IsSet() == true) { |
| _url = config.Url.Value(); |
| } |
| |
| Run(); |
| return result; |
| } |
| |
| bool Suspend(const bool suspend) |
| { |
| if (suspend == true) { |
| SbRdkSuspend(); |
| } |
| else { |
| SbRdkResume(); |
| } |
| return (true); |
| } |
| |
| string Url() const { return _url; } |
| |
| private: |
| bool Initialize() override |
| { |
| sigset_t mask; |
| sigemptyset(&mask); |
| sigaddset(&mask, SIGQUIT); |
| sigaddset(&mask, SIGUSR1); |
| sigaddset(&mask, SIGCONT); |
| pthread_sigmask(SIG_UNBLOCK, &mask, nullptr); |
| return (true); |
| } |
| uint32_t Worker() override |
| { |
| const std::string cmdURL = "--url=" + _url; |
| const char* argv[] = {"Cobalt", cmdURL.c_str()}; |
| if (IsRunning() == true) |
| _exitCode = StarboardMain(2, const_cast<char**>(argv)); |
| if (IsRunning() == true) // app initiated exit |
| _parent.StateChange(PluginHost::IStateControl::EXITED); |
| Block(); |
| return (Core::infinite); |
| } |
| |
| int _exitCode { 0 }; |
| string _url; |
| CobaltImplementation &_parent; |
| }; |
| |
| private: |
| CobaltImplementation(const CobaltImplementation&) = delete; |
| CobaltImplementation& operator=(const CobaltImplementation&) = delete; |
| |
| public: |
| CobaltImplementation() : |
| _window(*this), |
| _adminLock(), |
| _state(PluginHost::IStateControl::UNINITIALIZED), |
| _statePending(PluginHost::IStateControl::UNINITIALIZED), |
| _cobaltClients(), |
| _stateControlClients(), |
| _sink(*this) { |
| } |
| |
| virtual ~CobaltImplementation() { |
| } |
| |
| virtual uint32_t Configure(PluginHost::IShell *service) { |
| uint32_t result = _window.Configure(service); |
| _state = PluginHost::IStateControl::RESUMED; |
| return (result); |
| } |
| |
| virtual void SetURL(const string &URL) override { |
| SbRdkHandleDeepLink(URL.c_str()); |
| } |
| |
| virtual string GetURL() const override { |
| return _window.Url(); |
| } |
| |
| virtual uint32_t GetFPS() const override { |
| return 0; |
| } |
| |
| virtual void Hide(const bool hidden) { |
| } |
| |
| virtual void Register(Exchange::IBrowser::INotification *sink) { |
| _adminLock.Lock(); |
| |
| // Make sure a sink is not registered multiple times. |
| ASSERT( |
| std::find(_cobaltClients.begin(), _cobaltClients.end(), sink) |
| == _cobaltClients.end()); |
| |
| _cobaltClients.push_back(sink); |
| sink->AddRef(); |
| |
| _adminLock.Unlock(); |
| } |
| |
| virtual void Unregister(Exchange::IBrowser::INotification *sink) { |
| _adminLock.Lock(); |
| |
| std::list<Exchange::IBrowser::INotification*>::iterator index( |
| std::find(_cobaltClients.begin(), _cobaltClients.end(), sink)); |
| |
| // Make sure you do not unregister something you did not register !!! |
| ASSERT(index != _cobaltClients.end()); |
| |
| if (index != _cobaltClients.end()) { |
| (*index)->Release(); |
| _cobaltClients.erase(index); |
| } |
| |
| _adminLock.Unlock(); |
| } |
| |
| virtual void Register(PluginHost::IStateControl::INotification *sink) { |
| _adminLock.Lock(); |
| |
| // Make sure a sink is not registered multiple times. |
| ASSERT( |
| std::find(_stateControlClients.begin(), |
| _stateControlClients.end(), sink) |
| == _stateControlClients.end()); |
| |
| _stateControlClients.push_back(sink); |
| sink->AddRef(); |
| |
| _adminLock.Unlock(); |
| } |
| |
| virtual void Unregister(PluginHost::IStateControl::INotification *sink) { |
| _adminLock.Lock(); |
| |
| std::list<PluginHost::IStateControl::INotification*>::iterator index( |
| std::find(_stateControlClients.begin(), |
| _stateControlClients.end(), sink)); |
| |
| // Make sure you do not unregister something you did not register !!! |
| ASSERT(index != _stateControlClients.end()); |
| |
| if (index != _stateControlClients.end()) { |
| (*index)->Release(); |
| _stateControlClients.erase(index); |
| } |
| |
| _adminLock.Unlock(); |
| } |
| |
| virtual PluginHost::IStateControl::state State() const { |
| return (_state); |
| } |
| |
| virtual uint32_t Request(const PluginHost::IStateControl::command command) { |
| uint32_t result = Core::ERROR_ILLEGAL_STATE; |
| |
| _adminLock.Lock(); |
| |
| if (_state == PluginHost::IStateControl::UNINITIALIZED) { |
| // Seems we are passing state changes before we reached an operational Cobalt. |
| // Just move the state to what we would like it to be :-) |
| _state = ( |
| command == PluginHost::IStateControl::SUSPEND ? |
| PluginHost::IStateControl::SUSPENDED : |
| PluginHost::IStateControl::RESUMED); |
| result = Core::ERROR_NONE; |
| } else { |
| switch (command) { |
| case PluginHost::IStateControl::SUSPEND: |
| if (_state == PluginHost::IStateControl::RESUMED || _statePending == PluginHost::IStateControl::RESUMED) { |
| _statePending = PluginHost::IStateControl::SUSPENDED; |
| _sink.RequestForStateChange( |
| PluginHost::IStateControl::SUSPEND); |
| result = Core::ERROR_NONE; |
| } |
| break; |
| case PluginHost::IStateControl::RESUME: |
| if (_state == PluginHost::IStateControl::SUSPENDED || _statePending == PluginHost::IStateControl::SUSPENDED) { |
| _statePending = PluginHost::IStateControl::RESUMED; |
| _sink.RequestForStateChange( |
| PluginHost::IStateControl::RESUME); |
| result = Core::ERROR_NONE; |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| |
| _adminLock.Unlock(); |
| |
| return result; |
| } |
| |
| void StateChangeCompleted(bool success, |
| const PluginHost::IStateControl::command request) { |
| if (success) { |
| switch (request) { |
| case PluginHost::IStateControl::RESUME: |
| |
| _adminLock.Lock(); |
| |
| if (_state != PluginHost::IStateControl::RESUMED) { |
| StateChange(PluginHost::IStateControl::RESUMED); |
| } |
| |
| _adminLock.Unlock(); |
| break; |
| case PluginHost::IStateControl::SUSPEND: |
| |
| _adminLock.Lock(); |
| |
| if (_state != PluginHost::IStateControl::SUSPENDED) { |
| StateChange(PluginHost::IStateControl::SUSPENDED); |
| } |
| |
| _adminLock.Unlock(); |
| break; |
| default: |
| ASSERT(false); |
| break; |
| } |
| } else { |
| StateChange(PluginHost::IStateControl::EXITED); |
| } |
| } |
| |
| BEGIN_INTERFACE_MAP (CobaltImplementation) |
| INTERFACE_ENTRY (Exchange::IBrowser) |
| INTERFACE_ENTRY (PluginHost::IStateControl) |
| END_INTERFACE_MAP |
| |
| private: |
| inline bool RequestForStateChange( |
| const PluginHost::IStateControl::command command) { |
| bool result = false; |
| |
| switch (command) { |
| case PluginHost::IStateControl::SUSPEND: { |
| if (_window.Suspend(true) == true) { |
| result = true; |
| } |
| break; |
| } |
| case PluginHost::IStateControl::RESUME: { |
| if (_window.Suspend(false) == true) { |
| result = true; |
| } |
| break; |
| } |
| default: |
| ASSERT(false); |
| break; |
| } |
| return result; |
| } |
| |
| void StateChange(const PluginHost::IStateControl::state newState) { |
| _adminLock.Lock(); |
| |
| _state = newState; |
| _statePending = PluginHost::IStateControl::UNINITIALIZED; |
| |
| std::list<PluginHost::IStateControl::INotification*>::iterator index( |
| _stateControlClients.begin()); |
| |
| while (index != _stateControlClients.end()) { |
| (*index)->StateChange(newState); |
| index++; |
| } |
| |
| _adminLock.Unlock(); |
| } |
| |
| private: |
| CobaltWindow _window; |
| mutable Core::CriticalSection _adminLock; |
| PluginHost::IStateControl::state _state; |
| PluginHost::IStateControl::state _statePending; |
| std::list<Exchange::IBrowser::INotification*> _cobaltClients; |
| std::list<PluginHost::IStateControl::INotification*> _stateControlClients; |
| NotificationSink _sink; |
| }; |
| |
| SERVICE_REGISTRATION(CobaltImplementation, 1, 0); |
| |
| } // namespace Plugin |
| |
| namespace Cobalt { |
| |
| class MemoryObserverImpl: public Exchange::IMemory { |
| private: |
| MemoryObserverImpl(); |
| MemoryObserverImpl(const MemoryObserverImpl&); |
| MemoryObserverImpl& operator=(const MemoryObserverImpl&); |
| |
| public: |
| MemoryObserverImpl(const RPC::IRemoteConnection* connection) : |
| _main(connection == nullptr ? Core::ProcessInfo().Id() : connection->RemoteId()) { |
| } |
| ~MemoryObserverImpl() { |
| } |
| |
| public: |
| virtual uint64_t Resident() const { |
| return _main.Resident(); |
| } |
| virtual uint64_t Allocated() const { |
| return _main.Allocated(); |
| } |
| virtual uint64_t Shared() const { |
| return _main.Shared(); |
| } |
| virtual uint8_t Processes() const { |
| return (IsOperational() ? 1 : 0); |
| } |
| |
| virtual const bool IsOperational() const { |
| return _main.IsActive(); |
| } |
| |
| BEGIN_INTERFACE_MAP (MemoryObserverImpl) |
| INTERFACE_ENTRY (Exchange::IMemory) |
| END_INTERFACE_MAP |
| |
| private: |
| Core::ProcessInfo _main; |
| }; |
| |
| Exchange::IMemory* MemoryObserver(const RPC::IRemoteConnection* connection) { |
| ASSERT(connection != nullptr); |
| Exchange::IMemory* result = Core::Service<MemoryObserverImpl>::Create<Exchange::IMemory>(connection); |
| return (result); |
| } |
| |
| } // namespace Cobalt |
| |
| } // namespace WPEFramework |