blob: 0a89a0867b77ca86bdbcd5130dd05d32f6623f0e [file] [log] [blame]
// 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/common/log.h"
#include "starboard/input.h"
#include "starboard/key.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