| // Copyright 2016 The Cobalt Authors. All Rights Reserved. |
| // |
| // 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 "starboard/shared/directfb/application_directfb.h" |
| |
| #include <algorithm> |
| #include <iomanip> |
| |
| #include <directfb/directfb_version.h> // NOLINT(build/include_order) |
| |
| // Anything less than 1.7.x needs special behavior on teardown. |
| // Issue confirmed on 1.7.7, and separate issue perhaps seen on 1.2.x |
| #define NEEDS_DIRECTFB_TEARDOWN_WORKAROUND \ |
| ((DIRECTFB_MAJOR_VERSION == 1) && (DIRECTFB_MINOR_VERSION <= 7)) |
| |
| #if NEEDS_DIRECTFB_TEARDOWN_WORKAROUND |
| #include <setjmp.h> |
| #include <signal.h> |
| #include <unistd.h> |
| #endif |
| |
| #include "starboard/input.h" |
| #include "starboard/key.h" |
| #include "starboard/log.h" |
| #include "starboard/memory.h" |
| #include "starboard/shared/directfb/window_internal.h" |
| #include "starboard/shared/posix/time_internal.h" |
| #include "starboard/shared/starboard/audio_sink/audio_sink_internal.h" |
| #include "starboard/time.h" |
| |
| namespace starboard { |
| |
| namespace { |
| |
| SbKey DFBKeyEventToSbKey(const DFBInputEvent& event) { |
| SB_DCHECK(event.type == DIET_KEYPRESS || event.type == DIET_KEYRELEASE); |
| SB_DCHECK(event.flags & DIEF_KEYID); |
| |
| // The following Starboard keys are currently not being generated by the code |
| // below: |
| // kSbKeyKana |
| // kSbKeyHangul |
| // kSbKeyHanja |
| // kSbKeyKanji |
| // kSbKeyConvert |
| // kSbKeyNonconvert |
| // kSbKeyDbeDbcschar |
| // kSbKeyExecute |
| // kSbKeyF13 |
| // kSbKeyF14 |
| // kSbKeyF15 |
| // kSbKeyF16 |
| // kSbKeyF17 |
| // kSbKeyF18 |
| // kSbKeyF19 |
| // kSbKeyF20 |
| // kSbKeyF21 |
| // kSbKeyF22 |
| // kSbKeyF23 |
| // kSbKeyF24 |
| // kSbKeyBrowserSearch |
| // kSbKeyBrowserHome |
| // kSbKeyMediaLaunchApp |
| // kSbKeyWla |
| // kSbKeyBrightnessDown |
| // kSbKeyBrightnessUp |
| // kSbKeyKbdBrightnessDown |
| // kSbKeyKbdBrightnessUp |
| // kSbKeyBrowserBack |
| // kSbKeyBrowserForward |
| // kSbKeyBrowserRefresh |
| // kSbKeyBrowserStop |
| // kSbKeyBrowserFavorites |
| |
| if (event.flags & DIEF_KEYSYMBOL) { |
| switch (event.key_symbol) { |
| case DIKS_CLEAR: |
| return kSbKeyClear; |
| case DIKS_SELECT: |
| return kSbKeySelect; |
| case DIKS_HELP: |
| return kSbKeyHelp; |
| case DIKS_MENU: |
| return kSbKeyApps; |
| |
| // For supporting multimedia buttons on a USB keyboard. |
| case DIKS_MUTE: |
| return kSbKeyVolumeMute; |
| case DIKS_VOLUME_DOWN: |
| return kSbKeyVolumeDown; |
| case DIKS_VOLUME_UP: |
| return kSbKeyVolumeUp; |
| case DIKS_NEXT: |
| return kSbKeyMediaNextTrack; |
| case DIKS_PREVIOUS: |
| return kSbKeyMediaPrevTrack; |
| case DIKS_STOP: |
| return kSbKeyMediaStop; |
| case DIKS_PLAY: |
| return kSbKeyMediaPlayPause; |
| case DIKS_MAIL: |
| return kSbKeyMediaLaunchMail; |
| case DIKS_CALCULATOR: |
| return kSbKeyMediaLaunchApp2; |
| case DIKS_POWER: |
| case DIKS_POWER2: |
| return kSbKeyPower; |
| |
| default: { |
| // Follow through to switching on event.key_id. |
| } |
| } |
| } |
| |
| switch (event.key_id) { |
| case DIKI_BACKSPACE: |
| return kSbKeyBack; |
| case DIKI_DELETE: |
| return kSbKeyDelete; |
| case DIKI_TAB: |
| return kSbKeyTab; |
| case DIKI_ENTER: |
| case DIKI_KP_ENTER: |
| return kSbKeyReturn; |
| case DIKI_SPACE: |
| return kSbKeySpace; |
| case DIKI_HOME: |
| return kSbKeyHome; |
| case DIKI_END: |
| return kSbKeyEnd; |
| case DIKI_PAGE_UP: |
| return kSbKeyPrior; |
| case DIKI_PAGE_DOWN: |
| return kSbKeyNext; |
| case DIKI_LEFT: |
| return kSbKeyLeft; |
| case DIKI_RIGHT: |
| return kSbKeyRight; |
| case DIKI_DOWN: |
| return kSbKeyDown; |
| case DIKI_UP: |
| return kSbKeyUp; |
| case DIKI_ESCAPE: |
| return kSbKeyEscape; |
| case DIKI_A: |
| return kSbKeyA; |
| case DIKI_B: |
| return kSbKeyB; |
| case DIKI_C: |
| return kSbKeyC; |
| case DIKI_D: |
| return kSbKeyD; |
| case DIKI_E: |
| return kSbKeyE; |
| case DIKI_F: |
| return kSbKeyF; |
| case DIKI_G: |
| return kSbKeyG; |
| case DIKI_H: |
| return kSbKeyH; |
| case DIKI_I: |
| return kSbKeyI; |
| case DIKI_J: |
| return kSbKeyJ; |
| case DIKI_K: |
| return kSbKeyK; |
| case DIKI_L: |
| return kSbKeyL; |
| case DIKI_M: |
| return kSbKeyM; |
| case DIKI_N: |
| return kSbKeyN; |
| case DIKI_O: |
| return kSbKeyO; |
| case DIKI_P: |
| return kSbKeyP; |
| case DIKI_Q: |
| return kSbKeyQ; |
| case DIKI_R: |
| return kSbKeyR; |
| case DIKI_S: |
| return kSbKeyS; |
| case DIKI_T: |
| return kSbKeyT; |
| case DIKI_U: |
| return kSbKeyU; |
| case DIKI_V: |
| return kSbKeyV; |
| case DIKI_W: |
| return kSbKeyW; |
| case DIKI_X: |
| return kSbKeyX; |
| case DIKI_Y: |
| return kSbKeyY; |
| case DIKI_Z: |
| return kSbKeyZ; |
| |
| case DIKI_0: |
| case DIKI_1: |
| case DIKI_2: |
| case DIKI_3: |
| case DIKI_4: |
| case DIKI_5: |
| case DIKI_6: |
| case DIKI_7: |
| case DIKI_8: |
| case DIKI_9: |
| return static_cast<SbKey>(kSbKey0 + (event.key_id - DIKI_0)); |
| |
| case DIKI_KP_0: |
| case DIKI_KP_1: |
| case DIKI_KP_2: |
| case DIKI_KP_3: |
| case DIKI_KP_4: |
| case DIKI_KP_5: |
| case DIKI_KP_6: |
| case DIKI_KP_7: |
| case DIKI_KP_8: |
| case DIKI_KP_9: |
| return static_cast<SbKey>(kSbKeyNumpad0 + (event.key_id - DIKI_KP_0)); |
| |
| case DIKI_KP_MULT: |
| return kSbKeyMultiply; |
| case DIKI_KP_PLUS: |
| return kSbKeyAdd; |
| case DIKI_KP_SEPARATOR: |
| return kSbKeySeparator; |
| case DIKI_KP_MINUS: |
| return kSbKeySubtract; |
| case DIKI_KP_DECIMAL: |
| return kSbKeyDecimal; |
| case DIKI_KP_DIV: |
| return kSbKeyDivide; |
| case DIKI_KP_EQUAL: |
| case DIKI_EQUALS_SIGN: |
| return kSbKeyOemPlus; |
| case DIKI_COMMA: |
| return kSbKeyOemComma; |
| case DIKI_MINUS_SIGN: |
| return kSbKeyOemMinus; |
| case DIKI_PERIOD: |
| return kSbKeyOemPeriod; |
| case DIKI_SEMICOLON: |
| return kSbKeyOem1; |
| case DIKI_SLASH: |
| return kSbKeyOem2; |
| case DIKI_QUOTE_LEFT: |
| return kSbKeyOem3; |
| case DIKI_BRACKET_LEFT: |
| return kSbKeyOem4; |
| case DIKI_BACKSLASH: |
| return kSbKeyOem5; |
| case DIKI_BRACKET_RIGHT: |
| return kSbKeyOem6; |
| case DIKI_QUOTE_RIGHT: |
| return kSbKeyOem7; |
| case DIKI_SHIFT_L: |
| case DIKI_SHIFT_R: |
| return kSbKeyShift; |
| case DIKI_CONTROL_L: |
| case DIKI_CONTROL_R: |
| return kSbKeyControl; |
| case DIKI_META_L: |
| case DIKI_META_R: |
| case DIKI_ALT_L: |
| case DIKI_ALT_R: |
| return kSbKeyMenu; |
| case DIKI_PAUSE: |
| return kSbKeyPause; |
| case DIKI_CAPS_LOCK: |
| return kSbKeyCapital; |
| case DIKI_NUM_LOCK: |
| return kSbKeyNumlock; |
| case DIKI_SCROLL_LOCK: |
| return kSbKeyScroll; |
| case DIKI_PRINT: |
| return kSbKeyPrint; |
| case DIKI_INSERT: |
| return kSbKeyInsert; |
| case DIKI_SUPER_L: |
| return kSbKeyLwin; |
| case DIKI_SUPER_R: |
| return kSbKeyRwin; |
| case DIKI_F1: |
| case DIKI_F2: |
| case DIKI_F3: |
| case DIKI_F4: |
| case DIKI_F5: |
| case DIKI_F6: |
| case DIKI_F7: |
| case DIKI_F8: |
| case DIKI_F9: |
| case DIKI_F10: |
| case DIKI_F11: |
| case DIKI_F12: |
| return static_cast<SbKey>(kSbKeyF1 + (event.key_id - DIKI_F1)); |
| case DIKI_KP_F1: |
| case DIKI_KP_F2: |
| case DIKI_KP_F3: |
| case DIKI_KP_F4: |
| return static_cast<SbKey>(kSbKeyF1 + (event.key_id - DIKI_KP_F1)); |
| |
| default: { |
| SB_DLOG(WARNING) << "Unknown event.key_id: 0x" << std::hex |
| << event.key_id; |
| } |
| } |
| |
| return kSbKeyUnknown; |
| } // NOLINT(readability/fn_size) |
| |
| SbKeyLocation DFBKeyEventToSbKeyLocation(const DFBInputEvent& event) { |
| SB_DCHECK(event.type == DIET_KEYPRESS || event.type == DIET_KEYRELEASE); |
| SB_DCHECK(event.flags & DIEF_KEYID); |
| |
| switch (event.key_id) { |
| case DIKI_SHIFT_L: |
| case DIKI_CONTROL_L: |
| case DIKI_META_L: |
| case DIKI_ALT_L: |
| return kSbKeyLocationLeft; |
| case DIKI_SHIFT_R: |
| case DIKI_CONTROL_R: |
| case DIKI_META_R: |
| case DIKI_ALT_R: |
| return kSbKeyLocationRight; |
| |
| default: {} |
| } |
| |
| return kSbKeyLocationUnspecified; |
| } |
| |
| unsigned int DFBKeyEventToSbKeyModifiers(const DFBInputEvent& event) { |
| unsigned int key_modifiers = kSbKeyModifiersNone; |
| if (event.modifiers & DIMM_ALT) { |
| key_modifiers |= kSbKeyModifiersAlt; |
| } |
| if (event.modifiers & DIMM_CONTROL) { |
| key_modifiers |= kSbKeyModifiersCtrl; |
| } |
| if (event.modifiers & DIMM_SHIFT) { |
| key_modifiers |= kSbKeyModifiersShift; |
| } |
| return key_modifiers; |
| } |
| |
| } // namespace |
| |
| ApplicationDirectFB::ApplicationDirectFB() |
| : directfb_(NULL), window_(kSbWindowInvalid) { |
| SbAudioSinkPrivate::Initialize(); |
| } |
| |
| ApplicationDirectFB::~ApplicationDirectFB() { |
| SbAudioSinkPrivate::TearDown(); |
| } |
| |
| SbWindow ApplicationDirectFB::CreateWindow(const SbWindowOptions* options) { |
| if (SbWindowIsValid(window_)) { |
| // Cannot create a window if one is already created. |
| return kSbWindowInvalid; |
| } |
| |
| window_ = new SbWindowPrivate(GetDirectFB(), options); |
| return window_; |
| } |
| |
| bool ApplicationDirectFB::DestroyWindow(SbWindow window) { |
| if (!SbWindowIsValid(window)) { |
| return false; |
| } |
| if (window != window_) { |
| return false; |
| } |
| |
| delete window_; |
| window_ = kSbWindowInvalid; |
| |
| return true; |
| } |
| |
| IDirectFB* ApplicationDirectFB::GetDirectFB() { |
| if (!directfb_) { |
| // We only support one DirectFB device, so set it up and return it here. |
| int argc = 0; |
| if (DirectFBInit(&argc, NULL) != DFB_OK) { |
| SB_NOTREACHED() << "Error calling DirectFBInit()."; |
| } |
| |
| // Setup DirectFB to not provide any default window background. |
| DirectFBSetOption("bg-none", NULL); |
| // Setup DirectFB to not show their default mouse cursor. |
| DirectFBSetOption("no-cursor", NULL); |
| |
| // Create the DirectFB object. |
| if (DirectFBCreate(&directfb_) != DFB_OK) { |
| SB_NOTREACHED() << "Error calling DirectFBCreate()."; |
| } |
| if (directfb_->SetCooperativeLevel(directfb_, DFSCL_NORMAL) != DFB_OK) { |
| SB_NOTREACHED() << "Error calling SetCooperativeLevel()."; |
| } |
| } |
| return directfb_; |
| } |
| |
| SbWindow ApplicationDirectFB::GetWindow() { |
| return window_; |
| } |
| |
| void ApplicationDirectFB::Initialize() { |
| } |
| |
| namespace { |
| |
| #if NEEDS_DIRECTFB_TEARDOWN_WORKAROUND |
| jmp_buf signal_jmp_buffer; |
| |
| void on_segv(int sig /*, siginfo_t *info, void *ucontext*/) { |
| siglongjmp(signal_jmp_buffer, 1); |
| } |
| #endif // NEEDS_DIRECTFB_TEARDOWN_WORKAROUND |
| |
| } // namespace |
| |
| void ApplicationDirectFB::Teardown() { |
| SB_DCHECK(!SbWindowIsValid(window_)); |
| |
| if (directfb_) { |
| // DirectFB 1.7.7 has an uninitialized variable that causes |
| // crashes at teardown time. |
| // We swallow this crash here so that automated testing continues to |
| // work. |
| // See: http://lists.openembedded.org/pipermail/openembedded-core/2016-June/122843.html |
| // We've also seen teardown problems on 1.2.x. |
| #if NEEDS_DIRECTFB_TEARDOWN_WORKAROUND |
| if (sigsetjmp(signal_jmp_buffer, 1)) { |
| SB_LOG(WARNING) << "DirectFB segv during teardown. Expect memory leaks."; |
| // Calling _exit here to skip atexit handlers because we've seen |
| // directfb 1.2.10 hang on shutdown after this during an atexit handler. |
| // Note we exit with success so unit tests pass. |
| _exit(0); |
| } else { |
| struct sigaction sigaction_config; |
| SbMemorySet(&sigaction_config, 0, sizeof(sigaction_config)); |
| |
| sigaction_config.sa_handler = on_segv; |
| sigemptyset(&sigaction_config.sa_mask); |
| sigaction_config.sa_flags = 0; |
| |
| // Unblock SIGSEGV, which has been blocked earlier (perhaps by |
| // libdirectfb) |
| sigset_t set; |
| sigemptyset(&set); |
| sigaddset(&set, SIGSEGV); |
| sigprocmask(SIG_UNBLOCK, &set, nullptr); |
| |
| int err = sigaction(SIGSEGV, &sigaction_config, nullptr); |
| |
| directfb_->Release(directfb_); |
| } |
| #else // NEEDS_DIRECTFB_TEARDOWN_WORKAROUND |
| directfb_->Release(directfb_); |
| #endif // NEEDS_DIRECTFB_TEARDOWN_WORKAROUND |
| directfb_ = nullptr; |
| } |
| } |
| |
| shared::starboard::Application::Event* |
| ApplicationDirectFB::PollNextSystemEvent() { |
| DFBInputEvent dfb_event; |
| if (window_->event_buffer->GetEvent(window_->event_buffer, |
| DFB_EVENT(&dfb_event)) == DFB_OK) { |
| return DFBEventToEvent(dfb_event); |
| } else { |
| return NULL; |
| } |
| } |
| |
| shared::starboard::Application::Event* |
| ApplicationDirectFB::WaitForSystemEventWithTimeout(SbTime time) { |
| unsigned int seconds = time / kSbTimeSecond; |
| unsigned int milliseconds = (time % kSbTimeSecond) / kSbTimeMillisecond; |
| window_->event_buffer->WaitForEventWithTimeout(window_->event_buffer, seconds, |
| milliseconds); |
| |
| return PollNextSystemEvent(); |
| } |
| |
| bool ApplicationDirectFB::MayHaveSystemEvents() { |
| return SbWindowIsValid(window_); |
| } |
| |
| void ApplicationDirectFB::WakeSystemEventWait() { |
| if (IsCurrentThread()) { |
| return; |
| } |
| |
| SB_DCHECK(SbWindowIsValid(window_)); |
| |
| // The window is valid, call WakeUp() to break out of |
| // WaitForEventWithTimeout(), if a thread is waiting in that function right |
| // now. |
| window_->event_buffer->WakeUp(window_->event_buffer); |
| } |
| |
| shared::starboard::Application::Event* ApplicationDirectFB::DFBEventToEvent( |
| const DFBInputEvent& event) { |
| const int kKeyboardDeviceId = 1; |
| |
| if (event.type == DIET_KEYPRESS || event.type == DIET_KEYRELEASE) { |
| SB_DCHECK(event.flags & DIEF_KEYID); |
| |
| SbInputData* data = new SbInputData(); |
| SbMemorySet(data, 0, sizeof(*data)); |
| #if SB_API_VERSION >= 10 |
| data->timestamp = SbTimeGetMonotonicNow(); |
| #endif // SB_API_VERSION >= 10 |
| data->window = window_; |
| SB_DCHECK(SbWindowIsValid(data->window)); |
| data->type = (event.type == DIET_KEYPRESS ? kSbInputEventTypePress |
| : kSbInputEventTypeUnpress); |
| data->device_type = kSbInputDeviceTypeKeyboard; |
| data->device_id = kKeyboardDeviceId; |
| data->key = DFBKeyEventToSbKey(event); |
| data->key_location = DFBKeyEventToSbKeyLocation(event); |
| data->key_modifiers = DFBKeyEventToSbKeyModifiers(event); |
| return new Event(kSbEventTypeInput, data, &DeleteDestructor<SbInputData>); |
| } |
| |
| return NULL; |
| } |
| |
| } // namespace starboard |