blob: f729a751ef8c70cbaa989102b3932e66cb1f419e [file] [log] [blame]
// Copyright 2017 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/win32/application_win32.h"
#include <windows.h> // NOLINT(build/include_order)
#include <windowsx.h> // NOLINT(build/include_order)
#include <cstdio>
#include <string>
#include "starboard/input.h"
#include "starboard/key.h"
#include "starboard/shared/starboard/application.h"
#include "starboard/shared/win32/dialog.h"
#include "starboard/shared/win32/error_utils.h"
#include "starboard/shared/win32/minidump.h"
#include "starboard/shared/win32/thread_private.h"
#include "starboard/shared/win32/wchar_utils.h"
#include "starboard/shared/win32/window_internal.h"
#include "starboard/system.h"
using starboard::shared::starboard::Application;
using starboard::shared::starboard::CommandLine;
using starboard::shared::win32::ApplicationWin32;
using starboard::shared::win32::CStringToWString;
using starboard::shared::win32::DebugLogWinError;
namespace {
static const int kSbMouseDeviceId = 1;
static const TCHAR kWindowClassName[] = L"window_class_name";
const char kMiniDumpFilePath[] = "mini_dump_file_path";
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM w_param, LPARAM l_param) {
return ApplicationWin32::Get()->WindowProcess(hWnd, msg, w_param, l_param);
}
bool RegisterWindowClass() {
WNDCLASSEX window_class;
window_class.cbSize = sizeof(WNDCLASSEX);
// https://msdn.microsoft.com/en-us/library/windows/desktop/ff729176(v=vs.85).aspx
window_class.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
window_class.lpfnWndProc = WndProc;
window_class.cbClsExtra = 0;
window_class.cbWndExtra = 0;
window_class.hInstance = GetModuleHandle(nullptr);
// TODO: Add YouTube icon.
window_class.hIcon = LoadIcon(window_class.hInstance, IDI_APPLICATION);
window_class.hIconSm = LoadIcon(window_class.hInstance, IDI_APPLICATION);
window_class.hCursor = LoadCursor(NULL, IDC_ARROW);
window_class.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
window_class.lpszMenuName = NULL;
window_class.lpszClassName = kWindowClassName;
if (!::RegisterClassEx(&window_class)) {
SB_LOG(ERROR) << "Failed to register window";
DebugLogWinError();
return false;
}
return true;
}
// Create a Windows window.
HWND CreateWindowInstance(const SbWindowOptions& options) {
DWORD dwStyle = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_CLIPSIBLINGS |
WS_CLIPCHILDREN | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;
if (options.windowed) {
dwStyle |= WS_MAXIMIZE;
}
const std::wstring wide_window_name = CStringToWString(options.name);
const HWND window = CreateWindow(
kWindowClassName, wide_window_name.c_str(), dwStyle, CW_USEDEFAULT,
CW_USEDEFAULT, options.size.width, options.size.height, nullptr, nullptr,
GetModuleHandle(nullptr), nullptr);
SetForegroundWindow(window);
if (window == nullptr) {
SB_LOG(ERROR) << "Failed to create window.";
DebugLogWinError();
}
return window;
}
void AttachMiniDumpHandler(const CommandLine& cmd_line) {
std::string file_path;
if (cmd_line.HasSwitch(kMiniDumpFilePath)) {
// If there is a mini dump file path then use that.
file_path = cmd_line.GetSwitchValue(kMiniDumpFilePath);
} else {
// Otherwise use the file path and append ".dmp" to it.
const auto& args = cmd_line.argv();
if (!args.empty()) {
file_path = args[0];
file_path.append(".dmp");
}
}
if (!file_path.empty()) {
starboard::shared::win32::InitMiniDumpHandler(file_path.c_str());
}
}
} // namespace
namespace starboard {
namespace shared {
namespace win32 {
#if SB_API_VERSION >= 15
ApplicationWin32::ApplicationWin32(
SbEventHandleCallback sb_event_handle_callback)
: localized_strings_(SbSystemGetLocaleId()),
QueueApplication(sb_event_handle_callback) {}
#else
ApplicationWin32::ApplicationWin32()
: localized_strings_(SbSystemGetLocaleId()) {}
#endif // SB_API_VERSION >= 15
ApplicationWin32::~ApplicationWin32() {}
SbWindow ApplicationWin32::CreateWindowForWin32(
const SbWindowOptions* options) {
if (SbWindowIsValid(window_.get())) {
SB_LOG(WARNING) << "Returning existing window instance.";
return window_.get();
}
RegisterWindowClass();
HWND window;
if (options) {
window = CreateWindowInstance(*options);
window_.reset(new SbWindowPrivate(options, window));
} else {
SbWindowOptions default_options;
SbWindowSetDefaultOptions(&default_options);
window = CreateWindowInstance(default_options);
window_.reset(new SbWindowPrivate(&default_options, window));
}
ShowWindow(window, SW_SHOW);
UpdateWindow(window);
return window_.get();
}
bool ApplicationWin32::DestroyWindow(SbWindow window) {
if (!SbWindowIsValid(window) || window != window_.get()) {
return false;
}
HWND window_handle = window_->GetWindowHandle();
window_.reset();
if (!::DestroyWindow(window_handle)) {
SB_LOG(WARNING) << "Unable to destroy window";
}
if (!::UnregisterClass(kWindowClassName, NULL)) {
SB_LOG(ERROR) << "Failed to unregister window class.";
DebugLogWinError();
}
return true;
}
bool ApplicationWin32::OnSbSystemRaisePlatformError(
SbSystemPlatformErrorType type,
SbSystemPlatformErrorCallback callback,
void* user_data) {
// This was never being deleted, but it should be.
if (type != kSbSystemPlatformErrorTypeConnectionError)
SB_NOTREACHED();
ApplicationWin32* app = ApplicationWin32::Get();
const bool created_dialog = ShowOkCancelDialog(
app->GetCoreWindow()->GetWindowHandle(),
"", // No title.
app->GetLocalizedString("UNABLE_TO_CONTACT_YOUTUBE_1",
"Sorry, could not connect to YouTube."),
app->GetLocalizedString("RETRY_BUTTON", "Retry"),
[this, callback, user_data]() {
callback(kSbSystemPlatformErrorResponsePositive, user_data);
},
app->GetLocalizedString("EXIT_BUTTON", "Exit"),
[this, callback, user_data]() {
callback(kSbSystemPlatformErrorResponseNegative, user_data);
});
SB_DCHECK(!created_dialog);
if (!created_dialog) {
SB_LOG(ERROR) << "Failed to create dialog!";
}
return true;
}
Application::Event* ApplicationWin32::WaitForSystemEventWithTimeout(
SbTime time) {
ProcessNextSystemMessage();
if (pending_event_) {
Event* out = pending_event_;
pending_event_ = nullptr;
return out;
}
ScopedLock lock(stop_waiting_for_system_events_mutex_);
if (time <= SbTimeGetMonotonicNow() || stop_waiting_for_system_events_) {
stop_waiting_for_system_events_ = false;
return nullptr;
}
return WaitForSystemEventWithTimeout(time);
}
LRESULT ApplicationWin32::WindowProcess(HWND hWnd,
UINT msg,
WPARAM w_param,
LPARAM l_param) {
switch (msg) {
// Input message handling.
case WM_MBUTTONDOWN:
case WM_LBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_MBUTTONUP:
case WM_LBUTTONUP:
case WM_RBUTTONUP:
case WM_MOUSEMOVE:
case WM_MOUSEWHEEL:
pending_event_ =
ProcessWinMouseEvent(GetCoreWindow(), msg, w_param, l_param);
break;
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
case WM_KEYUP:
case WM_SYSKEYUP:
pending_event_ =
ProcessWinKeyEvent(GetCoreWindow(), msg, w_param, l_param);
break;
case WM_DESTROY:
if (window_.get()) {
// Freeze the application first so we can do some cleanup before the
// window is destroyed (e.g. stopping rasterization).
InjectAndProcess(kSbEventTypeFreeze, /* checkSystemEvents */ false);
PostQuitMessage(0);
}
break;
default:
return DefWindowProcW(hWnd, msg, w_param, l_param);
}
return 0;
}
int ApplicationWin32::Run(int argc, char** argv) {
CommandLine cmd_line(argc, argv);
AttachMiniDumpHandler(cmd_line);
int return_val = Application::Run(argc, argv);
return return_val;
}
void ApplicationWin32::ProcessNextSystemMessage() {
MSG msg;
BOOL peek_message_return = PeekMessage(&msg, NULL, 0, 0, PM_REMOVE);
if (peek_message_return == 0) { // 0 indicates no messages available.
return;
}
if (!DialogHandleMessage(&msg)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
if (msg.message == WM_QUIT) {
SB_LOG(INFO) << "Received Quit message; stopping application";
SbSystemRequestStop(msg.wParam);
}
}
Application::Event* ApplicationWin32::ProcessWinMouseEvent(SbWindow window,
UINT msg,
WPARAM w_param,
LPARAM l_param) {
SbInputData* data = new SbInputData();
memset(data, 0, sizeof(*data));
data->window = window;
data->device_type = kSbInputDeviceTypeMouse;
data->device_id = kSbMouseDeviceId;
switch (msg) {
case WM_LBUTTONDOWN:
data->key = kSbKeyMouse1;
data->type = kSbInputEventTypePress;
break;
case WM_RBUTTONDOWN:
data->key = kSbKeyMouse2;
data->type = kSbInputEventTypePress;
break;
case WM_MBUTTONDOWN:
data->key = kSbKeyMouse3;
data->type = kSbInputEventTypePress;
break;
case WM_LBUTTONUP:
data->key = kSbKeyMouse1;
data->type = kSbInputEventTypeUnpress;
break;
case WM_RBUTTONUP:
data->key = kSbKeyMouse2;
data->type = kSbInputEventTypeUnpress;
break;
case WM_MBUTTONUP:
data->key = kSbKeyMouse3;
data->type = kSbInputEventTypeUnpress;
break;
case WM_MOUSEMOVE:
data->type = kSbInputEventTypeMove;
break;
case WM_MOUSEWHEEL: {
data->type = kSbInputEventTypeWheel;
int wheel_delta = GET_WHEEL_DELTA_WPARAM(w_param);
// Per MSFT, standard mouse wheel increments are multiples of 120. For
// smooth scrolling, this may be less.
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms645617(v=vs.85).aspx
data->delta.y = wheel_delta / 120.0f;
} break;
default:
SB_LOG(WARNING) << "Received unrecognized MSG code " << msg;
return nullptr;
}
data->pressure = NAN;
data->size = {NAN, NAN};
data->tilt = {NAN, NAN};
data->position.x = GET_X_LPARAM(l_param);
data->position.y = GET_Y_LPARAM(l_param);
return new Application::Event(kSbEventTypeInput, data,
&Application::DeleteDestructor<SbInputData>);
}
} // namespace win32
} // namespace shared
} // namespace starboard