From f25a340e43c0676c1d6a079b1779aa07b5e88292 Mon Sep 17 00:00:00 2001 From: Relintai Date: Mon, 2 Oct 2023 18:20:17 +0200 Subject: [PATCH] Added back the uwp platform. It likely won't work for a while. --- platform/uwp/SCsub | 21 + platform/uwp/app.cpp | 580 +++++++++++ platform/uwp/app.h | 115 +++ platform/uwp/context_egl_uwp.cpp | 213 ++++ platform/uwp/context_egl_uwp.h | 85 ++ platform/uwp/detect.py | 210 ++++ platform/uwp/export/export.cpp | 1591 ++++++++++++++++++++++++++++++ platform/uwp/export/export.h | 36 + platform/uwp/joypad_uwp.cpp | 170 ++++ platform/uwp/joypad_uwp.h | 90 ++ platform/uwp/logo.png | Bin 0 -> 1519 bytes platform/uwp/os_uwp.cpp | 897 +++++++++++++++++ platform/uwp/os_uwp.h | 265 +++++ platform/uwp/platform_config.h | 31 + platform/uwp/power_uwp.cpp | 77 ++ platform/uwp/power_uwp.h | 55 ++ 16 files changed, 4436 insertions(+) create mode 100644 platform/uwp/SCsub create mode 100644 platform/uwp/app.cpp create mode 100644 platform/uwp/app.h create mode 100644 platform/uwp/context_egl_uwp.cpp create mode 100644 platform/uwp/context_egl_uwp.h create mode 100644 platform/uwp/detect.py create mode 100644 platform/uwp/export/export.cpp create mode 100644 platform/uwp/export/export.h create mode 100644 platform/uwp/joypad_uwp.cpp create mode 100644 platform/uwp/joypad_uwp.h create mode 100644 platform/uwp/logo.png create mode 100644 platform/uwp/os_uwp.cpp create mode 100644 platform/uwp/os_uwp.h create mode 100644 platform/uwp/platform_config.h create mode 100644 platform/uwp/power_uwp.cpp create mode 100644 platform/uwp/power_uwp.h diff --git a/platform/uwp/SCsub b/platform/uwp/SCsub new file mode 100644 index 000000000..49f68c209 --- /dev/null +++ b/platform/uwp/SCsub @@ -0,0 +1,21 @@ +#!/usr/bin/env python + +Import("env") + +files = [ + "#platform/windows/key_mapping_windows.cpp", + "#platform/windows/windows_terminal_logger.cpp", + "joypad_uwp.cpp", + "power_uwp.cpp", + "context_egl_uwp.cpp", + "app.cpp", + "os_uwp.cpp", +] + +if "build_angle" in env and env["build_angle"]: + cmd = env.AlwaysBuild(env.ANGLE("libANGLE.lib", None)) + +prog = env.add_program("#bin/godot", files) + +if "build_angle" in env and env["build_angle"]: + env.Depends(prog, [cmd]) diff --git a/platform/uwp/app.cpp b/platform/uwp/app.cpp new file mode 100644 index 000000000..3b3ffd622 --- /dev/null +++ b/platform/uwp/app.cpp @@ -0,0 +1,580 @@ +/**************************************************************************/ +/* app.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +// +// This file demonstrates how to initialize EGL in a Windows Store app, using ICoreWindow. +// + +#include "app.h" + +#include "core/os/dir_access.h" +#include "core/os/file_access.h" +#include "core/os/keyboard.h" +#include "main/main.h" + +#include "platform/windows/key_mapping_windows.h" + +#include + +using namespace Windows::ApplicationModel::Core; +using namespace Windows::ApplicationModel::Activation; +using namespace Windows::UI::Core; +using namespace Windows::UI::Input; +using namespace Windows::Devices::Input; +using namespace Windows::UI::Xaml::Input; +using namespace Windows::Foundation; +using namespace Windows::Graphics::Display; +using namespace Windows::System; +using namespace Windows::System::Threading::Core; +using namespace Microsoft::WRL; + +using namespace GodotUWP; + +// Helper to convert a length in device-independent pixels (DIPs) to a length in physical pixels. +inline float ConvertDipsToPixels(float dips, float dpi) { + static const float dipsPerInch = 96.0f; + return floor(dips * dpi / dipsPerInch + 0.5f); // Round to nearest integer. +} + +// Implementation of the IFrameworkViewSource interface, necessary to run our app. +ref class GodotUWPViewSource sealed : Windows::ApplicationModel::Core::IFrameworkViewSource { +public: + virtual Windows::ApplicationModel::Core::IFrameworkView ^ CreateView() { + return ref new App(); + } +}; + +// The main function creates an IFrameworkViewSource for our app, and runs the app. +[Platform::MTAThread] int main(Platform::Array ^) { + auto godotApplicationSource = ref new GodotUWPViewSource(); + CoreApplication::Run(godotApplicationSource); + return 0; +} + +App::App() : + mWindowClosed(false), + mWindowVisible(true), + mWindowWidth(0), + mWindowHeight(0), + mEglDisplay(EGL_NO_DISPLAY), + mEglContext(EGL_NO_CONTEXT), + mEglSurface(EGL_NO_SURFACE) { +} + +// The first method called when the IFrameworkView is being created. +void App::Initialize(CoreApplicationView ^ applicationView) { + // Register event handlers for app lifecycle. This example includes Activated, so that we + // can make the CoreWindow active and start rendering on the window. + applicationView->Activated += + ref new TypedEventHandler(this, &App::OnActivated); + + // Logic for other event handlers could go here. + // Information about the Suspending and Resuming event handlers can be found here: + // http://msdn.microsoft.com/en-us/library/windows/apps/xaml/hh994930.aspx + + os = new OS_UWP; +} + +// Called when the CoreWindow object is created (or re-created). +void App::SetWindow(CoreWindow ^ p_window) { + window = p_window; + window->VisibilityChanged += + ref new TypedEventHandler(this, &App::OnVisibilityChanged); + + window->Activated += + ref new TypedEventHandler(this, &App::OnWindowActivated); + + window->Closed += + ref new TypedEventHandler(this, &App::OnWindowClosed); + + window->SizeChanged += + ref new TypedEventHandler(this, &App::OnWindowSizeChanged); + +#if !(WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP) + // Disable all pointer visual feedback for better performance when touching. + // This is not supported on Windows Phone applications. + auto pointerVisualizationSettings = PointerVisualizationSettings::GetForCurrentView(); + pointerVisualizationSettings->IsContactFeedbackEnabled = false; + pointerVisualizationSettings->IsBarrelButtonFeedbackEnabled = false; +#endif + + window->PointerPressed += + ref new TypedEventHandler(this, &App::OnPointerPressed); + window->PointerMoved += + ref new TypedEventHandler(this, &App::OnPointerMoved); + window->PointerReleased += + ref new TypedEventHandler(this, &App::OnPointerReleased); + window->PointerWheelChanged += + ref new TypedEventHandler(this, &App::OnPointerWheelChanged); + + mouseChangedNotifier = SignalNotifier::AttachToEvent(L"os_mouse_mode_changed", ref new SignalHandler(this, &App::OnMouseModeChanged)); + + mouseChangedNotifier->Enable(); + + window->CharacterReceived += + ref new TypedEventHandler(this, &App::OnCharacterReceived); + window->KeyDown += + ref new TypedEventHandler(this, &App::OnKeyDown); + window->KeyUp += + ref new TypedEventHandler(this, &App::OnKeyUp); + + os->set_window(window); + + unsigned int argc; + char **argv = get_command_line(&argc); + + Main::setup("uwp", argc, argv, false); + + UpdateWindowSize(Size(window->Bounds.Width, window->Bounds.Height)); + + Main::setup2(); +} + +static int _get_button(Windows::UI::Input::PointerPoint ^ pt) { + using namespace Windows::UI::Input; + +#if WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP + return BUTTON_LEFT; +#else + switch (pt->Properties->PointerUpdateKind) { + case PointerUpdateKind::LeftButtonPressed: + case PointerUpdateKind::LeftButtonReleased: + return BUTTON_LEFT; + + case PointerUpdateKind::RightButtonPressed: + case PointerUpdateKind::RightButtonReleased: + return BUTTON_RIGHT; + + case PointerUpdateKind::MiddleButtonPressed: + case PointerUpdateKind::MiddleButtonReleased: + return BUTTON_MIDDLE; + + case PointerUpdateKind::XButton1Pressed: + case PointerUpdateKind::XButton1Released: + return BUTTON_WHEEL_UP; + + case PointerUpdateKind::XButton2Pressed: + case PointerUpdateKind::XButton2Released: + return BUTTON_WHEEL_DOWN; + + default: + break; + } +#endif + + return 0; +}; + +static bool _is_touch(Windows::UI::Input::PointerPoint ^ pointerPoint) { +#if WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP + return true; +#else + using namespace Windows::Devices::Input; + switch (pointerPoint->PointerDevice->PointerDeviceType) { + case PointerDeviceType::Touch: + case PointerDeviceType::Pen: + return true; + default: + return false; + } +#endif +} + +static Windows::Foundation::Point _get_pixel_position(CoreWindow ^ window, Windows::Foundation::Point rawPosition, OS *os) { + Windows::Foundation::Point outputPosition; + +// Compute coordinates normalized from 0..1. +// If the coordinates need to be sized to the SDL window, +// we'll do that after. +#if 1 || WINAPI_FAMILY != WINAPI_FAMILY_PHONE_APP + outputPosition.X = rawPosition.X / window->Bounds.Width; + outputPosition.Y = rawPosition.Y / window->Bounds.Height; +#else + switch (DisplayProperties::CurrentOrientation) { + case DisplayOrientations::Portrait: + outputPosition.X = rawPosition.X / window->Bounds.Width; + outputPosition.Y = rawPosition.Y / window->Bounds.Height; + break; + case DisplayOrientations::PortraitFlipped: + outputPosition.X = 1.0f - (rawPosition.X / window->Bounds.Width); + outputPosition.Y = 1.0f - (rawPosition.Y / window->Bounds.Height); + break; + case DisplayOrientations::Landscape: + outputPosition.X = rawPosition.Y / window->Bounds.Height; + outputPosition.Y = 1.0f - (rawPosition.X / window->Bounds.Width); + break; + case DisplayOrientations::LandscapeFlipped: + outputPosition.X = 1.0f - (rawPosition.Y / window->Bounds.Height); + outputPosition.Y = rawPosition.X / window->Bounds.Width; + break; + default: + break; + } +#endif + + OS::VideoMode vm = os->get_video_mode(); + outputPosition.X *= vm.width; + outputPosition.Y *= vm.height; + + return outputPosition; +}; + +static int _get_finger(uint32_t p_touch_id) { + return p_touch_id % 31; // for now +}; + +void App::pointer_event(Windows::UI::Core::CoreWindow ^ sender, Windows::UI::Core::PointerEventArgs ^ args, bool p_pressed, bool p_is_wheel) { + Windows::UI::Input::PointerPoint ^ point = args->CurrentPoint; + Windows::Foundation::Point pos = _get_pixel_position(window, point->Position, os); + int but = _get_button(point); + if (_is_touch(point)) { + Ref screen_touch; + screen_touch.instance(); + screen_touch->set_device(0); + screen_touch->set_pressed(p_pressed); + screen_touch->set_position(Vector2(pos.X, pos.Y)); + screen_touch->set_index(_get_finger(point->PointerId)); + + last_touch_x[screen_touch->get_index()] = pos.X; + last_touch_y[screen_touch->get_index()] = pos.Y; + + os->input_event(screen_touch); + } else { + Ref mouse_button; + mouse_button.instance(); + mouse_button->set_device(0); + mouse_button->set_pressed(p_pressed); + mouse_button->set_button_index(but); + mouse_button->set_position(Vector2(pos.X, pos.Y)); + mouse_button->set_global_position(Vector2(pos.X, pos.Y)); + + if (p_is_wheel) { + if (point->Properties->MouseWheelDelta > 0) { + mouse_button->set_button_index(point->Properties->IsHorizontalMouseWheel ? BUTTON_WHEEL_RIGHT : BUTTON_WHEEL_UP); + } else if (point->Properties->MouseWheelDelta < 0) { + mouse_button->set_button_index(point->Properties->IsHorizontalMouseWheel ? BUTTON_WHEEL_LEFT : BUTTON_WHEEL_DOWN); + } + } + + last_touch_x[31] = pos.X; + last_touch_y[31] = pos.Y; + + os->input_event(mouse_button); + + if (p_is_wheel) { + // Send release for mouse wheel + mouse_button->set_pressed(false); + os->input_event(mouse_button); + } + } +}; + +void App::OnPointerPressed(Windows::UI::Core::CoreWindow ^ sender, Windows::UI::Core::PointerEventArgs ^ args) { + pointer_event(sender, args, true); +}; + +void App::OnPointerReleased(Windows::UI::Core::CoreWindow ^ sender, Windows::UI::Core::PointerEventArgs ^ args) { + pointer_event(sender, args, false); +}; + +void App::OnPointerWheelChanged(Windows::UI::Core::CoreWindow ^ sender, Windows::UI::Core::PointerEventArgs ^ args) { + pointer_event(sender, args, true, true); +} + +void App::OnMouseModeChanged(Windows::System::Threading::Core::SignalNotifier ^ signalNotifier, bool timedOut) { + OS::MouseMode mode = os->get_mouse_mode(); + SignalNotifier ^ notifier = mouseChangedNotifier; + + window->Dispatcher->RunAsync( + CoreDispatcherPriority::High, + ref new DispatchedHandler( + [mode, notifier, this]() { + if (mode == OS::MOUSE_MODE_CAPTURED) { + this->MouseMovedToken = MouseDevice::GetForCurrentView()->MouseMoved += + ref new TypedEventHandler(this, &App::OnMouseMoved); + + } else { + MouseDevice::GetForCurrentView()->MouseMoved -= MouseMovedToken; + } + + notifier->Enable(); + })); + + ResetEvent(os->mouse_mode_changed); +} + +void App::OnPointerMoved(Windows::UI::Core::CoreWindow ^ sender, Windows::UI::Core::PointerEventArgs ^ args) { + Windows::UI::Input::PointerPoint ^ point = args->CurrentPoint; + Windows::Foundation::Point pos = _get_pixel_position(window, point->Position, os); + + if (_is_touch(point)) { + Ref screen_drag; + screen_drag.instance(); + screen_drag->set_device(0); + screen_drag->set_position(Vector2(pos.X, pos.Y)); + screen_drag->set_index(_get_finger(point->PointerId)); + screen_drag->set_relative(Vector2(screen_drag->get_position().x - last_touch_x[screen_drag->get_index()], screen_drag->get_position().y - last_touch_y[screen_drag->get_index()])); + + os->input_event(screen_drag); + } else { + // In case the mouse grabbed, MouseMoved will handle this + if (os->get_mouse_mode() == OS::MouseMode::MOUSE_MODE_CAPTURED) { + return; + } + + Ref mouse_motion; + mouse_motion.instance(); + mouse_motion->set_device(0); + mouse_motion->set_position(Vector2(pos.X, pos.Y)); + mouse_motion->set_global_position(Vector2(pos.X, pos.Y)); + mouse_motion->set_relative(Vector2(pos.X - last_touch_x[31], pos.Y - last_touch_y[31])); + + last_mouse_pos = pos; + + os->input_event(mouse_motion); + } +} + +void App::OnMouseMoved(MouseDevice ^ mouse_device, MouseEventArgs ^ args) { + // In case the mouse isn't grabbed, PointerMoved will handle this + if (os->get_mouse_mode() != OS::MouseMode::MOUSE_MODE_CAPTURED) { + return; + } + + Windows::Foundation::Point pos; + pos.X = last_mouse_pos.X + args->MouseDelta.X; + pos.Y = last_mouse_pos.Y + args->MouseDelta.Y; + + Ref mouse_motion; + mouse_motion.instance(); + mouse_motion->set_device(0); + mouse_motion->set_position(Vector2(pos.X, pos.Y)); + mouse_motion->set_global_position(Vector2(pos.X, pos.Y)); + mouse_motion->set_relative(Vector2(args->MouseDelta.X, args->MouseDelta.Y)); + + last_mouse_pos = pos; + + os->input_event(mouse_motion); +} + +void App::key_event(Windows::UI::Core::CoreWindow ^ sender, bool p_pressed, Windows::UI::Core::KeyEventArgs ^ key_args, Windows::UI::Core::CharacterReceivedEventArgs ^ char_args) { + OS_UWP::KeyEvent ke; + + ke.control = sender->GetAsyncKeyState(VirtualKey::Control) == CoreVirtualKeyStates::Down; + ke.alt = sender->GetAsyncKeyState(VirtualKey::Menu) == CoreVirtualKeyStates::Down; + ke.shift = sender->GetAsyncKeyState(VirtualKey::Shift) == CoreVirtualKeyStates::Down; + + ke.pressed = p_pressed; + + if (key_args != nullptr) { + ke.type = OS_UWP::KeyEvent::MessageType::KEY_EVENT_MESSAGE; + ke.unicode = 0; + ke.scancode = KeyMappingWindows::get_keysym((unsigned int)key_args->VirtualKey); + ke.physical_scancode = KeyMappingWindows::get_scansym((unsigned int)key_args->KeyStatus.ScanCode, key_args->KeyStatus.IsExtendedKey); + ke.echo = (!p_pressed && !key_args->KeyStatus.IsKeyReleased) || (p_pressed && key_args->KeyStatus.WasKeyDown); + + } else { + ke.type = OS_UWP::KeyEvent::MessageType::CHAR_EVENT_MESSAGE; + ke.unicode = char_args->KeyCode; + ke.scancode = 0; + ke.physical_scancode = 0; + ke.echo = (!p_pressed && !char_args->KeyStatus.IsKeyReleased) || (p_pressed && char_args->KeyStatus.WasKeyDown); + } + + os->queue_key_event(ke); +} +void App::OnKeyDown(CoreWindow ^ sender, KeyEventArgs ^ args) { + key_event(sender, true, args); +} + +void App::OnKeyUp(CoreWindow ^ sender, KeyEventArgs ^ args) { + key_event(sender, false, args); +} + +void App::OnCharacterReceived(CoreWindow ^ sender, CharacterReceivedEventArgs ^ args) { + key_event(sender, true, nullptr, args); +} + +// Initializes scene resources +void App::Load(Platform::String ^ entryPoint) { +} + +// This method is called after the window becomes active. +void App::Run() { + if (Main::start()) + os->run(); +} + +// Terminate events do not cause Uninitialize to be called. It will be called if your IFrameworkView +// class is torn down while the app is in the foreground. +void App::Uninitialize() { + Main::cleanup(); + delete os; +} + +// Application lifecycle event handler. +void App::OnActivated(CoreApplicationView ^ applicationView, IActivatedEventArgs ^ args) { + // Run() won't start until the CoreWindow is activated. + CoreWindow::GetForCurrentThread()->Activate(); +} + +// Window event handlers. +void App::OnVisibilityChanged(CoreWindow ^ sender, VisibilityChangedEventArgs ^ args) { + mWindowVisible = args->Visible; +} + +void App::OnWindowActivated(CoreWindow ^ sender, WindowActivatedEventArgs ^ args) { + os->update_clipboard(); +} + +void App::OnWindowClosed(CoreWindow ^ sender, CoreWindowEventArgs ^ args) { + mWindowClosed = true; +} + +void App::OnWindowSizeChanged(CoreWindow ^ sender, WindowSizeChangedEventArgs ^ args) { +#if (WINAPI_FAMILY == WINAPI_FAMILY_PC_APP) + // On Windows 8.1, apps are resized when they are snapped alongside other apps, or when the device is rotated. + // The default framebuffer will be automatically resized when either of these occur. + // In particular, on a 90 degree rotation, the default framebuffer's width and height will switch. + UpdateWindowSize(args->Size); +#else if (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP) + // On Windows Phone 8.1, the window size changes when the device is rotated. + // The default framebuffer will not be automatically resized when this occurs. + // It is therefore up to the app to handle rotation-specific logic in its rendering code. + //os->screen_size_changed(); + UpdateWindowSize(args->Size); +#endif +} + +void App::UpdateWindowSize(Size size) { + float dpi; +#if (WINAPI_FAMILY == WINAPI_FAMILY_PC_APP) + DisplayInformation ^ currentDisplayInformation = DisplayInformation::GetForCurrentView(); + dpi = currentDisplayInformation->LogicalDpi; +#else if (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP) + dpi = DisplayProperties::LogicalDpi; +#endif + Size pixelSize(ConvertDipsToPixels(size.Width, dpi), ConvertDipsToPixels(size.Height, dpi)); + + mWindowWidth = static_cast(pixelSize.Width); + mWindowHeight = static_cast(pixelSize.Height); + + OS::VideoMode vm; + vm.width = mWindowWidth; + vm.height = mWindowHeight; + vm.fullscreen = true; + vm.resizable = false; + os->set_video_mode(vm); +} + +char **App::get_command_line(unsigned int *out_argc) { + static char *fail_cl[] = { "--path", "game", NULL }; + *out_argc = 2; + + FILE *f = _wfopen(L"__cl__.cl", L"rb"); + + if (f == NULL) { + wprintf(L"Couldn't open command line file.\n"); + return fail_cl; + } + +#define READ_LE_4(v) ((int)(##v[3] & 0xFF) << 24) | ((int)(##v[2] & 0xFF) << 16) | ((int)(##v[1] & 0xFF) << 8) | ((int)(##v[0] & 0xFF)) +#define CMD_MAX_LEN 65535 + + uint8_t len[4]; + int r = fread(len, sizeof(uint8_t), 4, f); + + Platform::Collections::Vector cl; + + if (r < 4) { + fclose(f); + wprintf(L"Wrong cmdline length.\n"); + return (fail_cl); + } + + int argc = READ_LE_4(len); + + for (int i = 0; i < argc; i++) { + r = fread(len, sizeof(uint8_t), 4, f); + + if (r < 4) { + fclose(f); + wprintf(L"Wrong cmdline param length.\n"); + return (fail_cl); + } + + int strlen = READ_LE_4(len); + + if (strlen > CMD_MAX_LEN) { + fclose(f); + wprintf(L"Wrong command length.\n"); + return (fail_cl); + } + + char *arg = new char[strlen + 1]; + r = fread(arg, sizeof(char), strlen, f); + arg[strlen] = '\0'; + + if (r == strlen) { + int warg_size = MultiByteToWideChar(CP_UTF8, 0, arg, -1, NULL, 0); + wchar_t *warg = new wchar_t[warg_size]; + + MultiByteToWideChar(CP_UTF8, 0, arg, -1, warg, warg_size); + + cl.Append(ref new Platform::String(warg, warg_size)); + + } else { + delete[] arg; + fclose(f); + wprintf(L"Error reading command.\n"); + return (fail_cl); + } + } + +#undef READ_LE_4 +#undef CMD_MAX_LEN + + fclose(f); + + char **ret = new char *[cl.Size + 1]; + + for (int i = 0; i < cl.Size; i++) { + int arg_size = WideCharToMultiByte(CP_UTF8, 0, cl.GetAt(i)->Data(), -1, NULL, 0, NULL, NULL); + char *arg = new char[arg_size]; + + WideCharToMultiByte(CP_UTF8, 0, cl.GetAt(i)->Data(), -1, arg, arg_size, NULL, NULL); + + ret[i] = arg; + } + ret[cl.Size] = NULL; + *out_argc = cl.Size; + + return ret; +} diff --git a/platform/uwp/app.h b/platform/uwp/app.h new file mode 100644 index 000000000..4a322131d --- /dev/null +++ b/platform/uwp/app.h @@ -0,0 +1,115 @@ +/**************************************************************************/ +/* app.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#pragma once + +#include + +#include + +// ANGLE doesn't provide a specific lib for GLES3, so we keep using GLES2 +#include "GLES2/gl2.h" +#include "os_uwp.h" + +/** clang-format does not play nice with this C++/CX hybrid, needs investigation. */ +/* clang-format off */ + +namespace GodotUWP +{ + ref class App sealed : public Windows::ApplicationModel::Core::IFrameworkView + { + public: + App(); + + // IFrameworkView Methods. + virtual void Initialize(Windows::ApplicationModel::Core::CoreApplicationView^ applicationView); + virtual void SetWindow(Windows::UI::Core::CoreWindow^ window); + virtual void Load(Platform::String^ entryPoint); + virtual void Run(); + virtual void Uninitialize(); + + property Windows::Foundation::EventRegistrationToken MouseMovedToken { + Windows::Foundation::EventRegistrationToken get() { return this->mouseMovedToken; } + void set(Windows::Foundation::EventRegistrationToken p_token) { this->mouseMovedToken = p_token; } + } + + private: + void RecreateRenderer(); + + // Application lifecycle event handlers. + void OnActivated(Windows::ApplicationModel::Core::CoreApplicationView^ applicationView, Windows::ApplicationModel::Activation::IActivatedEventArgs^ args); + + // Window event handlers. + void OnWindowSizeChanged(Windows::UI::Core::CoreWindow^ sender, Windows::UI::Core::WindowSizeChangedEventArgs^ args); + void OnVisibilityChanged(Windows::UI::Core::CoreWindow^ sender, Windows::UI::Core::VisibilityChangedEventArgs^ args); + void OnWindowActivated(CoreWindow ^ sender, WindowActivatedEventArgs ^ args); + void OnWindowClosed(Windows::UI::Core::CoreWindow^ sender, Windows::UI::Core::CoreWindowEventArgs^ args); + + void pointer_event(Windows::UI::Core::CoreWindow^ sender, Windows::UI::Core::PointerEventArgs^ args, bool p_pressed, bool p_is_wheel = false); + void OnPointerPressed(Windows::UI::Core::CoreWindow^ sender, Windows::UI::Core::PointerEventArgs^ args); + void OnPointerReleased(Windows::UI::Core::CoreWindow^ sender, Windows::UI::Core::PointerEventArgs^ args); + void OnPointerMoved(Windows::UI::Core::CoreWindow^ sender, Windows::UI::Core::PointerEventArgs^ args); + void OnMouseMoved(Windows::Devices::Input::MouseDevice^ mouse_device, Windows::Devices::Input::MouseEventArgs^ args); + void OnPointerWheelChanged(Windows::UI::Core::CoreWindow^ sender, Windows::UI::Core::PointerEventArgs^ args); + + Windows::System::Threading::Core::SignalNotifier^ mouseChangedNotifier; + Windows::Foundation::EventRegistrationToken mouseMovedToken; + void OnMouseModeChanged(Windows::System::Threading::Core::SignalNotifier^ signalNotifier, bool timedOut); + + void key_event(Windows::UI::Core::CoreWindow^ sender, bool p_pressed, Windows::UI::Core::KeyEventArgs^ key_args = nullptr, Windows::UI::Core::CharacterReceivedEventArgs^ char_args = nullptr); + void OnKeyDown(Windows::UI::Core::CoreWindow^ sender, Windows::UI::Core::KeyEventArgs^ args); + void OnKeyUp(Windows::UI::Core::CoreWindow^ sender, Windows::UI::Core::KeyEventArgs^ args); + void OnCharacterReceived(Windows::UI::Core::CoreWindow^ sender, Windows::UI::Core::CharacterReceivedEventArgs^ args); + + void UpdateWindowSize(Windows::Foundation::Size size); + void InitializeEGL(Windows::UI::Core::CoreWindow^ window); + void CleanupEGL(); + + char** get_command_line(unsigned int* out_argc); + + bool mWindowClosed; + bool mWindowVisible; + GLsizei mWindowWidth; + GLsizei mWindowHeight; + + EGLDisplay mEglDisplay; + EGLContext mEglContext; + EGLSurface mEglSurface; + + CoreWindow^ window; + OS_UWP* os; + + int last_touch_x[32]; // 20 fingers, index 31 reserved for the mouse + int last_touch_y[32]; + Windows::Foundation::Point last_mouse_pos; + }; +} + +/* clang-format on */ diff --git a/platform/uwp/context_egl_uwp.cpp b/platform/uwp/context_egl_uwp.cpp new file mode 100644 index 000000000..d80a8b386 --- /dev/null +++ b/platform/uwp/context_egl_uwp.cpp @@ -0,0 +1,213 @@ +/**************************************************************************/ +/* context_egl_uwp.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "context_egl_uwp.h" + +#include "EGL/eglext.h" + +using Platform::Exception; + +void ContextEGL_UWP::release_current() { + eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, mEglContext); +}; + +void ContextEGL_UWP::make_current() { + eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext); +}; + +int ContextEGL_UWP::get_window_width() { + return width; +}; + +int ContextEGL_UWP::get_window_height() { + return height; +}; + +void ContextEGL_UWP::reset() { + cleanup(); + + window = CoreWindow::GetForCurrentThread(); + initialize(); +}; + +void ContextEGL_UWP::swap_buffers() { + if (eglSwapBuffers(mEglDisplay, mEglSurface) != EGL_TRUE) { + cleanup(); + + window = CoreWindow::GetForCurrentThread(); + initialize(); + + // tell rasterizer to reload textures and stuff? + } +}; + +Error ContextEGL_UWP::initialize() { + EGLint configAttribList[] = { + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 8, + EGL_DEPTH_SIZE, 8, + EGL_STENCIL_SIZE, 8, + EGL_SAMPLE_BUFFERS, 0, + EGL_NONE + }; + + EGLint surfaceAttribList[] = { + EGL_NONE, EGL_NONE + }; + + EGLint numConfigs = 0; + EGLint majorVersion = 1; + EGLint minorVersion; + if (driver == GLES_2_0) { + minorVersion = 0; + } else { + minorVersion = 5; + } + EGLDisplay display = EGL_NO_DISPLAY; + EGLContext context = EGL_NO_CONTEXT; + EGLSurface surface = EGL_NO_SURFACE; + EGLConfig config = nullptr; + EGLint contextAttribs[3]; + if (driver == GLES_2_0) { + contextAttribs[0] = EGL_CONTEXT_CLIENT_VERSION; + contextAttribs[1] = 2; + contextAttribs[2] = EGL_NONE; + } else { + contextAttribs[0] = EGL_CONTEXT_CLIENT_VERSION; + contextAttribs[1] = 3; + contextAttribs[2] = EGL_NONE; + } + + try { + const EGLint displayAttributes[] = { + /*EGL_PLATFORM_ANGLE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE, + EGL_PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE, 9, + EGL_PLATFORM_ANGLE_MAX_VERSION_MINOR_ANGLE, 3, + EGL_NONE,*/ + // These are the default display attributes, used to request ANGLE's D3D11 renderer. + // eglInitialize will only succeed with these attributes if the hardware supports D3D11 Feature Level 10_0+. + EGL_PLATFORM_ANGLE_TYPE_ANGLE, + EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE, + +#ifdef EGL_ANGLE_DISPLAY_ALLOW_RENDER_TO_BACK_BUFFER + // EGL_ANGLE_DISPLAY_ALLOW_RENDER_TO_BACK_BUFFER is an optimization that can have large performance benefits on mobile devices. + // Its syntax is subject to change, though. Please update your Visual Studio templates if you experience compilation issues with it. + EGL_ANGLE_DISPLAY_ALLOW_RENDER_TO_BACK_BUFFER, + EGL_TRUE, +#endif + + // EGL_PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE is an option that enables ANGLE to automatically call + // the IDXGIDevice3::Trim method on behalf of the application when it gets suspended. + // Calling IDXGIDevice3::Trim when an application is suspended is a Windows Store application certification requirement. + EGL_PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE, + EGL_TRUE, + EGL_NONE, + }; + + PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT = reinterpret_cast(eglGetProcAddress("eglGetPlatformDisplayEXT")); + + if (!eglGetPlatformDisplayEXT) { + throw Exception::CreateException(E_FAIL, L"Failed to get function eglGetPlatformDisplayEXT"); + } + + display = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, EGL_DEFAULT_DISPLAY, displayAttributes); + + if (display == EGL_NO_DISPLAY) { + throw Exception::CreateException(E_FAIL, L"Failed to get default EGL display"); + } + + if (eglInitialize(display, &majorVersion, &minorVersion) == EGL_FALSE) { + throw Exception::CreateException(E_FAIL, L"Failed to initialize EGL"); + } + + if (eglGetConfigs(display, NULL, 0, &numConfigs) == EGL_FALSE) { + throw Exception::CreateException(E_FAIL, L"Failed to get EGLConfig count"); + } + + if (eglChooseConfig(display, configAttribList, &config, 1, &numConfigs) == EGL_FALSE) { + throw Exception::CreateException(E_FAIL, L"Failed to choose first EGLConfig count"); + } + + surface = eglCreateWindowSurface(display, config, reinterpret_cast(window), surfaceAttribList); + if (surface == EGL_NO_SURFACE) { + throw Exception::CreateException(E_FAIL, L"Failed to create EGL fullscreen surface"); + } + + context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs); + if (context == EGL_NO_CONTEXT) { + throw Exception::CreateException(E_FAIL, L"Failed to create EGL context"); + } + + if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE) { + throw Exception::CreateException(E_FAIL, L"Failed to make fullscreen EGLSurface current"); + } + } catch (...) { + return FAILED; + }; + + mEglDisplay = display; + mEglSurface = surface; + mEglContext = context; + + eglQuerySurface(display, surface, EGL_WIDTH, &width); + eglQuerySurface(display, surface, EGL_HEIGHT, &height); + + return OK; +}; + +void ContextEGL_UWP::cleanup() { + if (mEglDisplay != EGL_NO_DISPLAY && mEglSurface != EGL_NO_SURFACE) { + eglDestroySurface(mEglDisplay, mEglSurface); + mEglSurface = EGL_NO_SURFACE; + } + + if (mEglDisplay != EGL_NO_DISPLAY && mEglContext != EGL_NO_CONTEXT) { + eglDestroyContext(mEglDisplay, mEglContext); + mEglContext = EGL_NO_CONTEXT; + } + + if (mEglDisplay != EGL_NO_DISPLAY) { + eglTerminate(mEglDisplay); + mEglDisplay = EGL_NO_DISPLAY; + } +}; + +ContextEGL_UWP::ContextEGL_UWP(CoreWindow ^ p_window, Driver p_driver) : + mEglDisplay(EGL_NO_DISPLAY), + mEglContext(EGL_NO_CONTEXT), + mEglSurface(EGL_NO_SURFACE), + driver(p_driver), + window(p_window) {} + +ContextEGL_UWP::~ContextEGL_UWP() { + cleanup(); +}; diff --git a/platform/uwp/context_egl_uwp.h b/platform/uwp/context_egl_uwp.h new file mode 100644 index 000000000..de4c057ca --- /dev/null +++ b/platform/uwp/context_egl_uwp.h @@ -0,0 +1,85 @@ +/**************************************************************************/ +/* context_egl_uwp.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef CONTEXT_EGL_UWP_H +#define CONTEXT_EGL_UWP_H + +#include + +#include + +#include "core/error_list.h" +#include "core/os/os.h" + +using namespace Windows::UI::Core; + +class ContextEGL_UWP { +public: + enum Driver { + GLES_2_0, + GLES_3_0, + }; + +private: + CoreWindow ^ window; + + EGLDisplay mEglDisplay; + EGLContext mEglContext; + EGLSurface mEglSurface; + + EGLint width; + EGLint height; + + bool vsync; + + Driver driver; + +public: + void release_current(); + + void make_current(); + + int get_window_width(); + int get_window_height(); + void swap_buffers(); + + void set_use_vsync(bool use) { vsync = use; } + bool is_using_vsync() const { return vsync; } + + Error initialize(); + void reset(); + + void cleanup(); + + ContextEGL_UWP(CoreWindow ^ p_window, Driver p_driver); + ~ContextEGL_UWP(); +}; + +#endif // CONTEXT_EGL_UWP_H diff --git a/platform/uwp/detect.py b/platform/uwp/detect.py new file mode 100644 index 000000000..c9160adf2 --- /dev/null +++ b/platform/uwp/detect.py @@ -0,0 +1,210 @@ +import methods +import os +import sys + + +def is_active(): + return True + + +def get_name(): + return "UWP" + + +def can_build(): + if os.name == "nt": + # building natively on windows! + if os.getenv("VSINSTALLDIR"): + + if os.getenv("ANGLE_SRC_PATH") is None: + return False + + return True + return False + + +def get_opts(): + return [ + ("msvc_version", "MSVC version to use (ignored if the VCINSTALLDIR environment variable is set)", None), + ] + + +def get_flags(): + return [ + ("tools", False), + ("xaudio2", True), + ("builtin_pcre2_with_jit", False), + ] + + +def configure(env): + env.msvc = True + + if env["bits"] != "default": + print("Error: bits argument is disabled for MSVC") + print( + """ + Bits argument is not supported for MSVC compilation. Architecture depends on the Native/Cross Compile Tools Prompt/Developer Console + (or Visual Studio settings) that is being used to run SCons. As a consequence, bits argument is disabled. Run scons again without bits + argument (example: scons p=uwp) and SCons will attempt to detect what MSVC compiler will be executed and inform you. + """ + ) + sys.exit() + + ## Build type + + if env["target"] == "release": + env.Append(CCFLAGS=["/MD"]) + env.Append(LINKFLAGS=["/SUBSYSTEM:WINDOWS"]) + if env["optimize"] != "none": + env.Append(CCFLAGS=["/O2", "/GL"]) + env.Append(LINKFLAGS=["/LTCG"]) + + elif env["target"] == "release_debug": + env.Append(CCFLAGS=["/MD"]) + env.Append(LINKFLAGS=["/SUBSYSTEM:CONSOLE"]) + if env["optimize"] != "none": + env.Append(CCFLAGS=["/O2", "/Zi"]) + + elif env["target"] == "debug": + env.Append(CCFLAGS=["/Zi"]) + env.Append(CCFLAGS=["/MDd"]) + env.Append(LINKFLAGS=["/SUBSYSTEM:CONSOLE"]) + env.Append(LINKFLAGS=["/DEBUG"]) + + ## Compiler configuration + + env["ENV"] = os.environ + vc_base_path = os.environ["VCTOOLSINSTALLDIR"] if "VCTOOLSINSTALLDIR" in os.environ else os.environ["VCINSTALLDIR"] + + # Force to use Unicode encoding + env.AppendUnique(CCFLAGS=["/utf-8"]) + + # ANGLE + angle_root = os.getenv("ANGLE_SRC_PATH") + env.Prepend(CPPPATH=[angle_root + "/include"]) + jobs = str(env.GetOption("num_jobs")) + angle_build_cmd = ( + "msbuild.exe " + + angle_root + + "/winrt/10/src/angle.sln /nologo /v:m /m:" + + jobs + + " /p:Configuration=Release /p:Platform=" + ) + + if os.path.isfile(str(os.getenv("ANGLE_SRC_PATH")) + "/winrt/10/src/angle.sln"): + env["build_angle"] = True + + ## Architecture + + arch = "" + if str(os.getenv("Platform")).lower() == "arm": + + print("Compiled program architecture will be an ARM executable. (forcing bits=32).") + + arch = "arm" + env["bits"] = "32" + env.Append(LINKFLAGS=["/MACHINE:ARM"]) + env.Append(LIBPATH=[vc_base_path + "lib/store/arm"]) + + angle_build_cmd += "ARM" + + env.Append(LIBPATH=[angle_root + "/winrt/10/src/Release_ARM/lib"]) + + else: + compiler_version_str = methods.detect_visual_c_compiler_version(env["ENV"]) + + if compiler_version_str == "amd64" or compiler_version_str == "x86_amd64": + env["bits"] = "64" + print("Compiled program architecture will be a x64 executable (forcing bits=64).") + elif compiler_version_str == "x86" or compiler_version_str == "amd64_x86": + env["bits"] = "32" + print("Compiled program architecture will be a x86 executable. (forcing bits=32).") + else: + print( + "Failed to detect MSVC compiler architecture version... Defaulting to 32-bit executable settings (forcing bits=32). Compilation attempt will continue, but SCons can not detect for what architecture this build is compiled for. You should check your settings/compilation setup." + ) + env["bits"] = "32" + + if env["bits"] == "32": + arch = "x86" + + angle_build_cmd += "Win32" + + env.Append(LINKFLAGS=["/MACHINE:X86"]) + env.Append(LIBPATH=[vc_base_path + "lib/store"]) + env.Append(LIBPATH=[angle_root + "/winrt/10/src/Release_Win32/lib"]) + + else: + arch = "x64" + + angle_build_cmd += "x64" + + env.Append(LINKFLAGS=["/MACHINE:X64"]) + env.Append(LIBPATH=[os.environ["VCINSTALLDIR"] + "lib/store/amd64"]) + env.Append(LIBPATH=[angle_root + "/winrt/10/src/Release_x64/lib"]) + + env["PROGSUFFIX"] = "." + arch + env["PROGSUFFIX"] + env["OBJSUFFIX"] = "." + arch + env["OBJSUFFIX"] + env["LIBSUFFIX"] = "." + arch + env["LIBSUFFIX"] + + ## Compile flags + + env.Prepend(CPPPATH=["#platform/uwp", "#drivers/windows"]) + env.Append(CPPDEFINES=["UWP_ENABLED", "WINDOWS_ENABLED", "TYPED_METHOD_BIND"]) + env.Append(CPPDEFINES=["GLES_ENABLED", "GL_GLEXT_PROTOTYPES", "EGL_EGLEXT_PROTOTYPES", "ANGLE_ENABLED"]) + winver = "0x0602" # Windows 8 is the minimum target for UWP build + env.Append(CPPDEFINES=[("WINVER", winver), ("_WIN32_WINNT", winver), "WIN32"]) + + env.Append(CPPDEFINES=["__WRL_NO_DEFAULT_LIB__", ("PNG_ABORT", "abort")]) + + env.Append(CPPFLAGS=["/AI", vc_base_path + "lib/store/references"]) + env.Append(CPPFLAGS=["/AI", vc_base_path + "lib/x86/store/references"]) + + env.Append( + CCFLAGS='/FS /MP /GS /wd"4453" /wd"28204" /wd"4291" /Zc:wchar_t /Gm- /fp:precise /errorReport:prompt /WX- /Zc:forScope /Gd /EHsc /nologo'.split() + ) + env.Append(CPPDEFINES=["_UNICODE", "UNICODE", ("WINAPI_FAMILY", "WINAPI_FAMILY_APP")]) + env.Append(CXXFLAGS=["/ZW"]) + env.Append( + CCFLAGS=[ + "/AI", + vc_base_path + "\\vcpackages", + "/AI", + os.environ["WINDOWSSDKDIR"] + "\\References\\CommonConfiguration\\Neutral", + ] + ) + + ## Link flags + + env.Append( + LINKFLAGS=[ + "/MANIFEST:NO", + "/NXCOMPAT", + "/DYNAMICBASE", + "/WINMD", + "/APPCONTAINER", + "/ERRORREPORT:PROMPT", + "/NOLOGO", + "/TLBID:1", + '/NODEFAULTLIB:"kernel32.lib"', + '/NODEFAULTLIB:"ole32.lib"', + ] + ) + + LIBS = [ + "WindowsApp", + "mincore", + "ws2_32", + "libANGLE", + "libEGL", + "libGLESv2", + "bcrypt", + ] + env.Append(LINKFLAGS=[p + ".lib" for p in LIBS]) + + # Incremental linking fix + env["BUILDERS"]["ProgramOriginal"] = env["BUILDERS"]["Program"] + env["BUILDERS"]["Program"] = methods.precious_program + + env.Append(BUILDERS={"ANGLE": env.Builder(action=angle_build_cmd)}) diff --git a/platform/uwp/export/export.cpp b/platform/uwp/export/export.cpp new file mode 100644 index 000000000..ddcf85f04 --- /dev/null +++ b/platform/uwp/export/export.cpp @@ -0,0 +1,1591 @@ +/**************************************************************************/ +/* export.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "export.h" + +#include "core/bind/core_bind.h" +#include "core/crypto/crypto_core.h" +#include "core/io/marshalls.h" +#include "core/io/zip_io.h" +#include "core/object.h" +#include "core/os/dir_access.h" +#include "core/os/file_access.h" +#include "core/project_settings.h" +#include "core/version.h" +#include "editor/editor_export.h" +#include "editor/editor_node.h" +#include "platform/uwp/logo.gen.h" + +// Mono build doesn't support UWP, so we show a specific error. +// We don't bypass the whole logic so that it doesn't lose potential UWP presets +// added to export_presets.cfg from a non-Mono build (and in case third-parties +// actually have Mono-enabled UWP templates they can use). +#include "modules/modules_enabled.gen.h" // For mono. + +#include "thirdparty/minizip/unzip.h" +#include "thirdparty/minizip/zip.h" + +#include + +// Capabilities +static const char *uwp_capabilities[] = { + "allJoyn", + "codeGeneration", + "internetClient", + "internetClientServer", + "privateNetworkClientServer", + nullptr +}; +static const char *uwp_uap_capabilities[] = { + "appointments", + "blockedChatMessages", + "chat", + "contacts", + "enterpriseAuthentication", + "musicLibrary", + "objects3D", + "picturesLibrary", + "phoneCall", + "removableStorage", + "sharedUserCertificates", + "userAccountInformation", + "videosLibrary", + "voipCall", + nullptr +}; +static const char *uwp_device_capabilities[] = { + "bluetooth", + "location", + "microphone", + "proximity", + "webcam", + nullptr +}; + +class AppxPackager { + enum { + FILE_HEADER_MAGIC = 0x04034b50, + DATA_DESCRIPTOR_MAGIC = 0x08074b50, + CENTRAL_DIR_MAGIC = 0x02014b50, + END_OF_CENTRAL_DIR_MAGIC = 0x06054b50, + ZIP64_END_OF_CENTRAL_DIR_MAGIC = 0x06064b50, + ZIP64_END_DIR_LOCATOR_MAGIC = 0x07064b50, + P7X_SIGNATURE = 0x58434b50, + ZIP64_HEADER_ID = 0x0001, + ZIP_VERSION = 20, + ZIP_ARCHIVE_VERSION = 45, + GENERAL_PURPOSE = 0x00, + BASE_FILE_HEADER_SIZE = 30, + DATA_DESCRIPTOR_SIZE = 24, + BASE_CENTRAL_DIR_SIZE = 46, + EXTRA_FIELD_LENGTH = 28, + ZIP64_HEADER_SIZE = 24, + ZIP64_END_OF_CENTRAL_DIR_SIZE = (56 - 12), + END_OF_CENTRAL_DIR_SIZE = 42, + BLOCK_SIZE = 65536, + }; + + struct BlockHash { + String base64_hash; + size_t compressed_size; + }; + + struct FileMeta { + String name; + int lfh_size; + bool compressed; + size_t compressed_size; + size_t uncompressed_size; + Vector hashes; + uLong file_crc32; + ZPOS64_T zip_offset; + + FileMeta() : + lfh_size(0), + compressed(false), + compressed_size(0), + uncompressed_size(0), + file_crc32(0), + zip_offset(0) {} + }; + + String progress_task; + FileAccess *package; + + Set mime_types; + + Vector file_metadata; + + ZPOS64_T central_dir_offset; + ZPOS64_T end_of_central_dir_offset; + Vector central_dir_data; + + String hash_block(const uint8_t *p_block_data, size_t p_block_len); + + void make_block_map(const String &p_path); + void make_content_types(const String &p_path); + + _FORCE_INLINE_ unsigned int buf_put_int16(uint16_t p_val, uint8_t *p_buf) { + for (int i = 0; i < 2; i++) { + *p_buf++ = (p_val >> (i * 8)) & 0xFF; + } + return 2; + } + + _FORCE_INLINE_ unsigned int buf_put_int32(uint32_t p_val, uint8_t *p_buf) { + for (int i = 0; i < 4; i++) { + *p_buf++ = (p_val >> (i * 8)) & 0xFF; + } + return 4; + } + + _FORCE_INLINE_ unsigned int buf_put_int64(uint64_t p_val, uint8_t *p_buf) { + for (int i = 0; i < 8; i++) { + *p_buf++ = (p_val >> (i * 8)) & 0xFF; + } + return 8; + } + + _FORCE_INLINE_ unsigned int buf_put_string(String p_val, uint8_t *p_buf) { + for (int i = 0; i < p_val.length(); i++) { + *p_buf++ = p_val.utf8().get(i); + } + return p_val.length(); + } + + Vector make_file_header(FileMeta p_file_meta); + void store_central_dir_header(const FileMeta &p_file, bool p_do_hash = true); + Vector make_end_of_central_record(); + + String content_type(String p_extension); + +public: + void set_progress_task(String p_task) { progress_task = p_task; } + void init(FileAccess *p_fa); + Error add_file(String p_file_name, const uint8_t *p_buffer, size_t p_len, int p_file_no, int p_total_files, bool p_compress = false); + void finish(); + + AppxPackager(); + ~AppxPackager(); +}; + +/////////////////////////////////////////////////////////////////////////// + +String AppxPackager::hash_block(const uint8_t *p_block_data, size_t p_block_len) { + unsigned char hash[32]; + char base64[45]; + + CryptoCore::sha256(p_block_data, p_block_len, hash); + size_t len = 0; + CryptoCore::b64_encode((unsigned char *)base64, 45, &len, (unsigned char *)hash, 32); + base64[44] = '\0'; + + return String(base64); +} + +void AppxPackager::make_block_map(const String &p_path) { + FileAccess *tmp_file = FileAccess::open(p_path, FileAccess::WRITE); + + tmp_file->store_string(""); + tmp_file->store_string(""); + + for (int i = 0; i < file_metadata.size(); i++) { + FileMeta file = file_metadata[i]; + + tmp_file->store_string( + ""); + + for (int j = 0; j < file.hashes.size(); j++) { + tmp_file->store_string("store_string("Size=\"" + itos(file.hashes[j].compressed_size) + "\" "); + } + tmp_file->store_string("/>"); + } + + tmp_file->store_string(""); + } + + tmp_file->store_string(""); + + tmp_file->close(); + memdelete(tmp_file); +} + +String AppxPackager::content_type(String p_extension) { + if (p_extension == "png") { + return "image/png"; + } else if (p_extension == "jpg") { + return "image/jpg"; + } else if (p_extension == "xml") { + return "application/xml"; + } else if (p_extension == "exe" || p_extension == "dll") { + return "application/x-msdownload"; + } else { + return "application/octet-stream"; + } +} + +void AppxPackager::make_content_types(const String &p_path) { + FileAccess *tmp_file = FileAccess::open(p_path, FileAccess::WRITE); + + tmp_file->store_string(""); + tmp_file->store_string(""); + + Map types; + + for (int i = 0; i < file_metadata.size(); i++) { + String ext = file_metadata[i].name.get_extension().to_lower(); + + if (types.has(ext)) { + continue; + } + + types[ext] = content_type(ext); + + tmp_file->store_string(""); + } + + // Appx signature file + tmp_file->store_string(""); + + // Override for package files + tmp_file->store_string(""); + tmp_file->store_string(""); + tmp_file->store_string(""); + tmp_file->store_string(""); + + tmp_file->store_string(""); + + tmp_file->close(); + memdelete(tmp_file); +} + +Vector AppxPackager::make_file_header(FileMeta p_file_meta) { + Vector buf; + buf.resize(BASE_FILE_HEADER_SIZE + p_file_meta.name.length()); + + int offs = 0; + // Write magic + offs += buf_put_int32(FILE_HEADER_MAGIC, &buf.write[offs]); + + // Version + offs += buf_put_int16(ZIP_VERSION, &buf.write[offs]); + + // Special flag + offs += buf_put_int16(GENERAL_PURPOSE, &buf.write[offs]); + + // Compression + offs += buf_put_int16(p_file_meta.compressed ? Z_DEFLATED : 0, &buf.write[offs]); + + // File date and time + offs += buf_put_int32(0, &buf.write[offs]); + + // CRC-32 + offs += buf_put_int32(p_file_meta.file_crc32, &buf.write[offs]); + + // Compressed size + offs += buf_put_int32(p_file_meta.compressed_size, &buf.write[offs]); + + // Uncompressed size + offs += buf_put_int32(p_file_meta.uncompressed_size, &buf.write[offs]); + + // File name length + offs += buf_put_int16(p_file_meta.name.length(), &buf.write[offs]); + + // Extra data length + offs += buf_put_int16(0, &buf.write[offs]); + + // File name + offs += buf_put_string(p_file_meta.name, &buf.write[offs]); + + // Done! + return buf; +} + +void AppxPackager::store_central_dir_header(const FileMeta &p_file, bool p_do_hash) { + Vector &buf = central_dir_data; + int offs = buf.size(); + buf.resize(buf.size() + BASE_CENTRAL_DIR_SIZE + p_file.name.length()); + + // Write magic + offs += buf_put_int32(CENTRAL_DIR_MAGIC, &buf.write[offs]); + + // ZIP versions + offs += buf_put_int16(ZIP_ARCHIVE_VERSION, &buf.write[offs]); + offs += buf_put_int16(ZIP_VERSION, &buf.write[offs]); + + // General purpose flag + offs += buf_put_int16(GENERAL_PURPOSE, &buf.write[offs]); + + // Compression + offs += buf_put_int16(p_file.compressed ? Z_DEFLATED : 0, &buf.write[offs]); + + // Modification date/time + offs += buf_put_int32(0, &buf.write[offs]); + + // Crc-32 + offs += buf_put_int32(p_file.file_crc32, &buf.write[offs]); + + // File sizes + offs += buf_put_int32(p_file.compressed_size, &buf.write[offs]); + offs += buf_put_int32(p_file.uncompressed_size, &buf.write[offs]); + + // File name length + offs += buf_put_int16(p_file.name.length(), &buf.write[offs]); + + // Extra field length + offs += buf_put_int16(0, &buf.write[offs]); + + // Comment length + offs += buf_put_int16(0, &buf.write[offs]); + + // Disk number start, internal/external file attributes + for (int i = 0; i < 8; i++) { + buf.write[offs++] = 0; + } + + // Relative offset + offs += buf_put_int32(p_file.zip_offset, &buf.write[offs]); + + // File name + offs += buf_put_string(p_file.name, &buf.write[offs]); + + // Done! +} + +Vector AppxPackager::make_end_of_central_record() { + Vector buf; + buf.resize(ZIP64_END_OF_CENTRAL_DIR_SIZE + 12 + END_OF_CENTRAL_DIR_SIZE); // Size plus magic + + int offs = 0; + + // Write magic + offs += buf_put_int32(ZIP64_END_OF_CENTRAL_DIR_MAGIC, &buf.write[offs]); + + // Size of this record + offs += buf_put_int64(ZIP64_END_OF_CENTRAL_DIR_SIZE, &buf.write[offs]); + + // Version (yes, twice) + offs += buf_put_int16(ZIP_ARCHIVE_VERSION, &buf.write[offs]); + offs += buf_put_int16(ZIP_ARCHIVE_VERSION, &buf.write[offs]); + + // Disk number + for (int i = 0; i < 8; i++) { + buf.write[offs++] = 0; + } + + // Number of entries (total and per disk) + offs += buf_put_int64(file_metadata.size(), &buf.write[offs]); + offs += buf_put_int64(file_metadata.size(), &buf.write[offs]); + + // Size of central dir + offs += buf_put_int64(central_dir_data.size(), &buf.write[offs]); + + // Central dir offset + offs += buf_put_int64(central_dir_offset, &buf.write[offs]); + + ////// ZIP64 locator + + // Write magic for zip64 central dir locator + offs += buf_put_int32(ZIP64_END_DIR_LOCATOR_MAGIC, &buf.write[offs]); + + // Disk number + for (int i = 0; i < 4; i++) { + buf.write[offs++] = 0; + } + + // Relative offset + offs += buf_put_int64(end_of_central_dir_offset, &buf.write[offs]); + + // Number of disks + offs += buf_put_int32(1, &buf.write[offs]); + + /////// End of zip directory + + // Write magic for end central dir + offs += buf_put_int32(END_OF_CENTRAL_DIR_MAGIC, &buf.write[offs]); + + // Dummy stuff for Zip64 + for (int i = 0; i < 4; i++) { + buf.write[offs++] = 0x0; + } + for (int i = 0; i < 12; i++) { + buf.write[offs++] = 0xFF; + } + + // Size of comments + for (int i = 0; i < 2; i++) { + buf.write[offs++] = 0; + } + + // Done! + return buf; +} + +void AppxPackager::init(FileAccess *p_fa) { + package = p_fa; + central_dir_offset = 0; + end_of_central_dir_offset = 0; +} + +Error AppxPackager::add_file(String p_file_name, const uint8_t *p_buffer, size_t p_len, int p_file_no, int p_total_files, bool p_compress) { + if (p_file_no >= 1 && p_total_files >= 1) { + if (EditorNode::progress_task_step(progress_task, "File: " + p_file_name, (p_file_no * 100) / p_total_files)) { + return ERR_SKIP; + } + } + + FileMeta meta; + meta.name = p_file_name; + meta.uncompressed_size = p_len; + meta.compressed_size = p_len; + meta.compressed = p_compress; + meta.zip_offset = package->get_position(); + + Vector file_buffer; + + // Data for compression + z_stream strm; + FileAccess *strm_f = nullptr; + Vector strm_in; + strm_in.resize(BLOCK_SIZE); + Vector strm_out; + + if (p_compress) { + strm.zalloc = zipio_alloc; + strm.zfree = zipio_free; + strm.opaque = &strm_f; + + strm_out.resize(BLOCK_SIZE + 8); + + deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY); + } + + int step = 0; + + while (p_len - step > 0) { + size_t block_size = (p_len - step) > BLOCK_SIZE ? (size_t)BLOCK_SIZE : (p_len - step); + + for (uint64_t i = 0; i < block_size; i++) { + strm_in.write[i] = p_buffer[step + i]; + } + + BlockHash bh; + bh.base64_hash = hash_block(strm_in.ptr(), block_size); + + if (p_compress) { + strm.avail_in = block_size; + strm.avail_out = strm_out.size(); + strm.next_in = (uint8_t *)strm_in.ptr(); + strm.next_out = strm_out.ptrw(); + + int total_out_before = strm.total_out; + + int err = deflate(&strm, Z_FULL_FLUSH); + ERR_FAIL_COND_V(err < 0, ERR_BUG); // Negative means bug + + bh.compressed_size = strm.total_out - total_out_before; + + //package->store_buffer(strm_out.ptr(), strm.total_out - total_out_before); + int start = file_buffer.size(); + file_buffer.resize(file_buffer.size() + bh.compressed_size); + for (uint64_t i = 0; i < bh.compressed_size; i++) { + file_buffer.write[start + i] = strm_out[i]; + } + } else { + bh.compressed_size = block_size; + //package->store_buffer(strm_in.ptr(), block_size); + int start = file_buffer.size(); + file_buffer.resize(file_buffer.size() + block_size); + for (uint64_t i = 0; i < bh.compressed_size; i++) { + file_buffer.write[start + i] = strm_in[i]; + } + } + + meta.hashes.push_back(bh); + + step += block_size; + } + + if (p_compress) { + strm.avail_in = 0; + strm.avail_out = strm_out.size(); + strm.next_in = (uint8_t *)strm_in.ptr(); + strm.next_out = strm_out.ptrw(); + + int total_out_before = strm.total_out; + + deflate(&strm, Z_FINISH); + + //package->store_buffer(strm_out.ptr(), strm.total_out - total_out_before); + int start = file_buffer.size(); + file_buffer.resize(file_buffer.size() + (strm.total_out - total_out_before)); + for (uint64_t i = 0; i < (strm.total_out - total_out_before); i++) { + file_buffer.write[start + i] = strm_out[i]; + } + + deflateEnd(&strm); + meta.compressed_size = strm.total_out; + + } else { + meta.compressed_size = p_len; + } + + // Calculate file CRC-32 + uLong crc = crc32(0L, Z_NULL, 0); + crc = crc32(crc, p_buffer, p_len); + meta.file_crc32 = crc; + + // Create file header + Vector file_header = make_file_header(meta); + meta.lfh_size = file_header.size(); + + // Store the header and file; + package->store_buffer(file_header.ptr(), file_header.size()); + package->store_buffer(file_buffer.ptr(), file_buffer.size()); + + file_metadata.push_back(meta); + + return OK; +} + +void AppxPackager::finish() { + // Create and add block map file + EditorNode::progress_task_step("export", "Creating block map...", 4); + + const String &tmp_blockmap_file_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmpblockmap.xml"); + make_block_map(tmp_blockmap_file_path); + + FileAccess *blockmap_file = FileAccess::open(tmp_blockmap_file_path, FileAccess::READ); + Vector blockmap_buffer; + blockmap_buffer.resize(blockmap_file->get_len()); + + blockmap_file->get_buffer(blockmap_buffer.ptrw(), blockmap_buffer.size()); + + add_file("AppxBlockMap.xml", blockmap_buffer.ptr(), blockmap_buffer.size(), -1, -1, true); + + blockmap_file->close(); + memdelete(blockmap_file); + + // Add content types + + EditorNode::progress_task_step("export", "Setting content types...", 5); + + const String &tmp_content_types_file_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmpcontenttypes.xml"); + make_content_types(tmp_content_types_file_path); + + FileAccess *types_file = FileAccess::open(tmp_content_types_file_path, FileAccess::READ); + Vector types_buffer; + types_buffer.resize(types_file->get_len()); + + types_file->get_buffer(types_buffer.ptrw(), types_buffer.size()); + + add_file("[Content_Types].xml", types_buffer.ptr(), types_buffer.size(), -1, -1, true); + + types_file->close(); + memdelete(types_file); + + // Cleanup generated files. + DirAccess::remove_file_or_error(tmp_blockmap_file_path); + DirAccess::remove_file_or_error(tmp_content_types_file_path); + + // Pre-process central directory before signing + for (int i = 0; i < file_metadata.size(); i++) { + store_central_dir_header(file_metadata[i]); + } + + // Write central directory + EditorNode::progress_task_step("export", "Finishing package...", 6); + central_dir_offset = package->get_position(); + package->store_buffer(central_dir_data.ptr(), central_dir_data.size()); + + // End record + end_of_central_dir_offset = package->get_position(); + Vector end_record = make_end_of_central_record(); + package->store_buffer(end_record.ptr(), end_record.size()); + + package->close(); + memdelete(package); + package = nullptr; +} + +AppxPackager::AppxPackager() {} + +AppxPackager::~AppxPackager() {} + +//////////////////////////////////////////////////////////////////// + +class EditorExportPlatformUWP : public EditorExportPlatform { + GDCLASS(EditorExportPlatformUWP, EditorExportPlatform); + + Ref logo; + + enum Platform { + ARM, + X86, + X64 + }; + + bool _valid_resource_name(const String &p_name) const { + if (p_name.empty()) { + return false; + } + if (p_name.ends_with(".")) { + return false; + } + + static const char *invalid_names[] = { + "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", + "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", + nullptr + }; + + const char **t = invalid_names; + while (*t) { + if (p_name == *t) { + return false; + } + t++; + } + + return true; + } + + bool _valid_guid(const String &p_guid) const { + Vector parts = p_guid.split("-"); + + if (parts.size() != 5) { + return false; + } + if (parts[0].length() != 8) { + return false; + } + for (int i = 1; i < 4; i++) { + if (parts[i].length() != 4) { + return false; + } + } + if (parts[4].length() != 12) { + return false; + } + + return true; + } + + bool _valid_bgcolor(const String &p_color) const { + if (p_color.empty()) { + return true; + } + if (p_color.begins_with("#") && p_color.is_valid_html_color()) { + return true; + } + + // Colors from https://msdn.microsoft.com/en-us/library/windows/apps/dn934817.aspx + static const char *valid_colors[] = { + "aliceBlue", "antiqueWhite", "aqua", "aquamarine", "azure", "beige", + "bisque", "black", "blanchedAlmond", "blue", "blueViolet", "brown", + "burlyWood", "cadetBlue", "chartreuse", "chocolate", "coral", "cornflowerBlue", + "cornsilk", "crimson", "cyan", "darkBlue", "darkCyan", "darkGoldenrod", + "darkGray", "darkGreen", "darkKhaki", "darkMagenta", "darkOliveGreen", "darkOrange", + "darkOrchid", "darkRed", "darkSalmon", "darkSeaGreen", "darkSlateBlue", "darkSlateGray", + "darkTurquoise", "darkViolet", "deepPink", "deepSkyBlue", "dimGray", "dodgerBlue", + "firebrick", "floralWhite", "forestGreen", "fuchsia", "gainsboro", "ghostWhite", + "gold", "goldenrod", "gray", "green", "greenYellow", "honeydew", + "hotPink", "indianRed", "indigo", "ivory", "khaki", "lavender", + "lavenderBlush", "lawnGreen", "lemonChiffon", "lightBlue", "lightCoral", "lightCyan", + "lightGoldenrodYellow", "lightGreen", "lightGray", "lightPink", "lightSalmon", "lightSeaGreen", + "lightSkyBlue", "lightSlateGray", "lightSteelBlue", "lightYellow", "lime", "limeGreen", + "linen", "magenta", "maroon", "mediumAquamarine", "mediumBlue", "mediumOrchid", + "mediumPurple", "mediumSeaGreen", "mediumSlateBlue", "mediumSpringGreen", "mediumTurquoise", "mediumVioletRed", + "midnightBlue", "mintCream", "mistyRose", "moccasin", "navajoWhite", "navy", + "oldLace", "olive", "oliveDrab", "orange", "orangeRed", "orchid", + "paleGoldenrod", "paleGreen", "paleTurquoise", "paleVioletRed", "papayaWhip", "peachPuff", + "peru", "pink", "plum", "powderBlue", "purple", "red", + "rosyBrown", "royalBlue", "saddleBrown", "salmon", "sandyBrown", "seaGreen", + "seaShell", "sienna", "silver", "skyBlue", "slateBlue", "slateGray", + "snow", "springGreen", "steelBlue", "tan", "teal", "thistle", + "tomato", "transparent", "turquoise", "violet", "wheat", "white", + "whiteSmoke", "yellow", "yellowGreen", + nullptr + }; + + const char **color = valid_colors; + + while (*color) { + if (p_color == *color) { + return true; + } + color++; + } + + return false; + } + + bool _valid_image(const StreamTexture *p_image, int p_width, int p_height) const { + if (!p_image) { + return false; + } + + // TODO: Add resource creation or image rescaling to enable other scales: + // 1.25, 1.5, 2.0 + return p_width == p_image->get_width() && p_height == p_image->get_height(); + } + + Vector _fix_manifest(const Ref &p_preset, const Vector &p_template, bool p_give_internet) const { + String result = String::utf8((const char *)p_template.ptr(), p_template.size()); + + result = result.replace("$godot_version$", VERSION_FULL_NAME); + + result = result.replace("$identity_name$", p_preset->get("package/unique_name")); + result = result.replace("$publisher$", p_preset->get("package/publisher")); + + result = result.replace("$product_guid$", p_preset->get("identity/product_guid")); + result = result.replace("$publisher_guid$", p_preset->get("identity/publisher_guid")); + + String version = itos(p_preset->get("version/major")) + "." + itos(p_preset->get("version/minor")) + "." + itos(p_preset->get("version/build")) + "." + itos(p_preset->get("version/revision")); + result = result.replace("$version_string$", version); + + Platform arch = (Platform)(int)p_preset->get("architecture/target"); + String architecture = arch == ARM ? "arm" : (arch == X86 ? "x86" : "x64"); + result = result.replace("$architecture$", architecture); + + result = result.replace("$display_name$", String(p_preset->get("package/display_name")).empty() ? (String)ProjectSettings::get_singleton()->get("application/config/name") : String(p_preset->get("package/display_name"))); + + result = result.replace("$publisher_display_name$", p_preset->get("package/publisher_display_name")); + result = result.replace("$app_description$", p_preset->get("package/description")); + result = result.replace("$bg_color$", p_preset->get("images/background_color")); + result = result.replace("$short_name$", p_preset->get("package/short_name")); + + String name_on_tiles = ""; + if ((bool)p_preset->get("tiles/show_name_on_square150x150")) { + name_on_tiles += " \n"; + } + if ((bool)p_preset->get("tiles/show_name_on_wide310x150")) { + name_on_tiles += " \n"; + } + if ((bool)p_preset->get("tiles/show_name_on_square310x310")) { + name_on_tiles += " \n"; + } + + String show_name_on_tiles = ""; + if (!name_on_tiles.empty()) { + show_name_on_tiles = "\n" + name_on_tiles + " "; + } + + result = result.replace("$name_on_tiles$", name_on_tiles); + + String rotations = ""; + if ((bool)p_preset->get("orientation/landscape")) { + rotations += " \n"; + } + if ((bool)p_preset->get("orientation/portrait")) { + rotations += " \n"; + } + if ((bool)p_preset->get("orientation/landscape_flipped")) { + rotations += " \n"; + } + if ((bool)p_preset->get("orientation/portrait_flipped")) { + rotations += " \n"; + } + + String rotation_preference = ""; + if (!rotations.empty()) { + rotation_preference = "\n" + rotations + " "; + } + + result = result.replace("$rotation_preference$", rotation_preference); + + String capabilities_elements = ""; + const char **basic = uwp_capabilities; + while (*basic) { + if ((bool)p_preset->get("capabilities/" + String(*basic))) { + capabilities_elements += " \n"; + } + basic++; + } + const char **uap = uwp_uap_capabilities; + while (*uap) { + if ((bool)p_preset->get("capabilities/" + String(*uap))) { + capabilities_elements += " \n"; + } + uap++; + } + const char **device = uwp_device_capabilities; + while (*device) { + if ((bool)p_preset->get("capabilities/" + String(*device))) { + capabilities_elements += " \n"; + } + device++; + } + + if (!((bool)p_preset->get("capabilities/internetClient")) && p_give_internet) { + capabilities_elements += " \n"; + } + + String capabilities_string = ""; + if (!capabilities_elements.empty()) { + capabilities_string = "\n" + capabilities_elements + " "; + } + + result = result.replace("$capabilities_place$", capabilities_string); + + Vector r_ret; + r_ret.resize(result.length()); + + for (int i = 0; i < result.length(); i++) { + r_ret.write[i] = result.utf8().get(i); + } + + return r_ret; + } + + Vector _get_image_data(const Ref &p_preset, const String &p_path) { + Vector data; + StreamTexture *image = nullptr; + + if (p_path.find("StoreLogo") != -1) { + image = p_preset->get("images/store_logo").is_zero() ? nullptr : Object::cast_to(((Object *)p_preset->get("images/store_logo"))); + } else if (p_path.find("Square44x44Logo") != -1) { + image = p_preset->get("images/square44x44_logo").is_zero() ? nullptr : Object::cast_to(((Object *)p_preset->get("images/square44x44_logo"))); + } else if (p_path.find("Square71x71Logo") != -1) { + image = p_preset->get("images/square71x71_logo").is_zero() ? nullptr : Object::cast_to(((Object *)p_preset->get("images/square71x71_logo"))); + } else if (p_path.find("Square150x150Logo") != -1) { + image = p_preset->get("images/square150x150_logo").is_zero() ? nullptr : Object::cast_to(((Object *)p_preset->get("images/square150x150_logo"))); + } else if (p_path.find("Square310x310Logo") != -1) { + image = p_preset->get("images/square310x310_logo").is_zero() ? nullptr : Object::cast_to(((Object *)p_preset->get("images/square310x310_logo"))); + } else if (p_path.find("Wide310x150Logo") != -1) { + image = p_preset->get("images/wide310x150_logo").is_zero() ? nullptr : Object::cast_to(((Object *)p_preset->get("images/wide310x150_logo"))); + } else if (p_path.find("SplashScreen") != -1) { + image = p_preset->get("images/splash_screen").is_zero() ? nullptr : Object::cast_to(((Object *)p_preset->get("images/splash_screen"))); + } else { + ERR_PRINT("Unable to load logo"); + } + + if (!image) { + return data; + } + + String tmp_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("uwp_tmp_logo.png"); + + Error err = image->get_data()->save_png(tmp_path); + + if (err != OK) { + String err_string = "Couldn't save temp logo file."; + + EditorNode::add_io_error(err_string); + ERR_FAIL_V_MSG(data, err_string); + } + + FileAccess *f = FileAccess::open(tmp_path, FileAccess::READ, &err); + + if (err != OK) { + String err_string = "Couldn't open temp logo file."; + // Cleanup generated file. + DirAccess::remove_file_or_error(tmp_path); + EditorNode::add_io_error(err_string); + ERR_FAIL_V_MSG(data, err_string); + } + + data.resize(f->get_len()); + f->get_buffer(data.ptrw(), data.size()); + + f->close(); + memdelete(f); + DirAccess::remove_file_or_error(tmp_path); + + return data; + } + + static bool _should_compress_asset(const String &p_path, const Vector &p_data) { + /* TODO: This was copied verbatim from Android export. It should be + * refactored to the parent class and also be used for .zip export. + */ + + /* + * By not compressing files with little or not benefit in doing so, + * a performance gain is expected at runtime. Moreover, if the APK is + * zip-aligned, assets stored as they are can be efficiently read by + * Android by memory-mapping them. + */ + + // -- Unconditional uncompress to mimic AAPT plus some other + + static const char *unconditional_compress_ext[] = { + // From https://github.com/android/platform_frameworks_base/blob/master/tools/aapt/Package.cpp + // These formats are already compressed, or don't compress well: + ".jpg", ".jpeg", ".png", ".gif", + ".wav", ".mp2", ".mp3", ".ogg", ".aac", + ".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet", + ".rtttl", ".imy", ".xmf", ".mp4", ".m4a", + ".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2", + ".amr", ".awb", ".wma", ".wmv", + // Godot-specific: + ".webp", // Same reasoning as .png + ".cfb", // Don't let small config files slow-down startup + ".scn", // Binary scenes are usually already compressed + ".stex", // Streamable textures are usually already compressed + // Trailer for easier processing + nullptr + }; + + for (const char **ext = unconditional_compress_ext; *ext; ++ext) { + if (p_path.to_lower().ends_with(String(*ext))) { + return false; + } + } + + // -- Compressed resource? + + if (p_data.size() >= 4 && p_data[0] == 'R' && p_data[1] == 'S' && p_data[2] == 'C' && p_data[3] == 'C') { + // Already compressed + return false; + } + + // --- TODO: Decide on texture resources according to their image compression setting + + return true; + } + + static Error save_appx_file(void *p_userdata, const String &p_path, const Vector &p_data, int p_file, int p_total) { + AppxPackager *packager = (AppxPackager *)p_userdata; + String dst_path = p_path.replace_first("res://", "game/"); + + return packager->add_file(dst_path, p_data.ptr(), p_data.size(), p_file, p_total, _should_compress_asset(p_path, p_data)); + } + +public: + virtual String get_name() const { + return "UWP"; + } + virtual String get_os_name() const { + return "UWP"; + } + + virtual List get_binary_extensions(const Ref &p_preset) const { + List list; + list.push_back("appx"); + return list; + } + + virtual Ref get_logo() const { + return logo; + } + + virtual void get_preset_features(const Ref &p_preset, List *r_features) { + r_features->push_back("s3tc"); + r_features->push_back("etc"); + switch ((int)p_preset->get("architecture/target")) { + case EditorExportPlatformUWP::ARM: { + r_features->push_back("arm"); + } break; + case EditorExportPlatformUWP::X86: { + r_features->push_back("32"); + } break; + case EditorExportPlatformUWP::X64: { + r_features->push_back("64"); + } break; + } + } + + virtual void get_export_options(List *r_options) { + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), "")); + + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "architecture/target", PROPERTY_HINT_ENUM, "arm,x86,x64"), 1)); + + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "command_line/extra_args"), "")); + + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "package/display_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "package/short_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "package/unique_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game.Name"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "package/description"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "package/publisher", PROPERTY_HINT_PLACEHOLDER_TEXT, "CN=CompanyName"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "package/publisher_display_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Company Name"), "")); + + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "identity/product_guid", PROPERTY_HINT_PLACEHOLDER_TEXT, "00000000-0000-0000-0000-000000000000"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "identity/publisher_guid", PROPERTY_HINT_PLACEHOLDER_TEXT, "00000000-0000-0000-0000-000000000000"), "")); + + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "signing/certificate", PROPERTY_HINT_GLOBAL_FILE, "*.pfx"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "signing/password"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "signing/algorithm", PROPERTY_HINT_ENUM, "MD5,SHA1,SHA256"), 2)); + + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "version/major"), 1)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "version/minor"), 0)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "version/build"), 0)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "version/revision"), 0)); + + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "orientation/landscape"), true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "orientation/portrait"), true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "orientation/landscape_flipped"), true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "orientation/portrait_flipped"), true)); + + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "images/background_color"), "transparent")); + r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/store_logo", PROPERTY_HINT_RESOURCE_TYPE, "StreamTexture"), Variant())); + r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/square44x44_logo", PROPERTY_HINT_RESOURCE_TYPE, "StreamTexture"), Variant())); + r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/square71x71_logo", PROPERTY_HINT_RESOURCE_TYPE, "StreamTexture"), Variant())); + r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/square150x150_logo", PROPERTY_HINT_RESOURCE_TYPE, "StreamTexture"), Variant())); + r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/square310x310_logo", PROPERTY_HINT_RESOURCE_TYPE, "StreamTexture"), Variant())); + r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/wide310x150_logo", PROPERTY_HINT_RESOURCE_TYPE, "StreamTexture"), Variant())); + r_options->push_back(ExportOption(PropertyInfo(Variant::OBJECT, "images/splash_screen", PROPERTY_HINT_RESOURCE_TYPE, "StreamTexture"), Variant())); + + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "tiles/show_name_on_square150x150"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "tiles/show_name_on_wide310x150"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "tiles/show_name_on_square310x310"), false)); + + // Capabilities + const char **basic = uwp_capabilities; + while (*basic) { + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/" + String(*basic)), false)); + basic++; + } + + const char **uap = uwp_uap_capabilities; + while (*uap) { + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/" + String(*uap)), false)); + uap++; + } + + const char **device = uwp_device_capabilities; + while (*device) { + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/" + String(*device)), false)); + device++; + } + } + + virtual bool has_valid_export_configuration(const Ref &p_preset, String &r_error, bool &r_missing_templates) const { + String err; + bool valid = false; + + // Look for export templates (first official, and if defined custom templates). + + Platform arch = (Platform)(int)(p_preset->get("architecture/target")); + String platform_infix; + switch (arch) { + case EditorExportPlatformUWP::ARM: { + platform_infix = "arm"; + } break; + case EditorExportPlatformUWP::X86: { + platform_infix = "x86"; + } break; + case EditorExportPlatformUWP::X64: { + platform_infix = "x64"; + } break; + } + + bool dvalid = exists_export_template("uwp_" + platform_infix + "_debug.zip", &err); + bool rvalid = exists_export_template("uwp_" + platform_infix + "_release.zip", &err); + + if (p_preset->get("custom_template/debug") != "") { + dvalid = FileAccess::exists(p_preset->get("custom_template/debug")); + if (!dvalid) { + err += TTR("Custom debug template not found.") + "\n"; + } + } + if (p_preset->get("custom_template/release") != "") { + rvalid = FileAccess::exists(p_preset->get("custom_template/release")); + if (!rvalid) { + err += TTR("Custom release template not found.") + "\n"; + } + } + + valid = dvalid || rvalid; + +#ifdef MODULE_MONO_ENABLED + // If this is a Mono build, provide a custom error so that users are not confused. + // We don't bypass the whole logic to check templates because third-parties might have + // Mono-enabled UWP builds using this path. + r_missing_templates = false; // Don't warn about those. + r_error = TTR("Godot's Mono version does not support the UWP platform. Use the standard build (no C# support) if you wish to target UWP."); +#else + r_missing_templates = !valid; + + if (!err.empty()) { + r_error = err; + } +#endif // MODULE_MONO_ENABLED + + return valid; + } + + virtual bool has_valid_project_configuration(const Ref &p_preset, String &r_error) const { + String err; + bool valid = true; + +#ifdef MODULE_MONO_ENABLED + // Don't warn about project configuration issue if this is a Mono build + // without custom-provided Mono-enabled UWP templates. + // We check if we have valid templates to decide if we should actually + // validate the config. + bool tmp; + if (!has_valid_export_configuration(p_preset, err, tmp)) { + return false; + } +#endif // MODULE_MONO_ENABLED + + // Validate the rest of the configuration. + + if (!_valid_resource_name(p_preset->get("package/short_name"))) { + valid = false; + err += TTR("Invalid package short name.") + "\n"; + } + + if (!_valid_resource_name(p_preset->get("package/unique_name"))) { + valid = false; + err += TTR("Invalid package unique name.") + "\n"; + } + + if (!_valid_resource_name(p_preset->get("package/publisher_display_name"))) { + valid = false; + err += TTR("Invalid package publisher display name.") + "\n"; + } + + if (!_valid_guid(p_preset->get("identity/product_guid"))) { + valid = false; + err += TTR("Invalid product GUID.") + "\n"; + } + + if (!_valid_guid(p_preset->get("identity/publisher_guid"))) { + valid = false; + err += TTR("Invalid publisher GUID.") + "\n"; + } + + if (!_valid_bgcolor(p_preset->get("images/background_color"))) { + valid = false; + err += TTR("Invalid background color.") + "\n"; + } + + if (!p_preset->get("images/store_logo").is_zero() && !_valid_image((Object::cast_to((Object *)p_preset->get("images/store_logo"))), 50, 50)) { + valid = false; + err += TTR("Invalid Store Logo image dimensions (should be 50x50).") + "\n"; + } + + if (!p_preset->get("images/square44x44_logo").is_zero() && !_valid_image((Object::cast_to((Object *)p_preset->get("images/square44x44_logo"))), 44, 44)) { + valid = false; + err += TTR("Invalid square 44x44 logo image dimensions (should be 44x44).") + "\n"; + } + + if (!p_preset->get("images/square71x71_logo").is_zero() && !_valid_image((Object::cast_to((Object *)p_preset->get("images/square71x71_logo"))), 71, 71)) { + valid = false; + err += TTR("Invalid square 71x71 logo image dimensions (should be 71x71).") + "\n"; + } + + if (!p_preset->get("images/square150x150_logo").is_zero() && !_valid_image((Object::cast_to((Object *)p_preset->get("images/square150x150_logo"))), 150, 150)) { + valid = false; + err += TTR("Invalid square 150x150 logo image dimensions (should be 150x150).") + "\n"; + } + + if (!p_preset->get("images/square310x310_logo").is_zero() && !_valid_image((Object::cast_to((Object *)p_preset->get("images/square310x310_logo"))), 310, 310)) { + valid = false; + err += TTR("Invalid square 310x310 logo image dimensions (should be 310x310).") + "\n"; + } + + if (!p_preset->get("images/wide310x150_logo").is_zero() && !_valid_image((Object::cast_to((Object *)p_preset->get("images/wide310x150_logo"))), 310, 150)) { + valid = false; + err += TTR("Invalid wide 310x150 logo image dimensions (should be 310x150).") + "\n"; + } + + if (!p_preset->get("images/splash_screen").is_zero() && !_valid_image((Object::cast_to((Object *)p_preset->get("images/splash_screen"))), 620, 300)) { + valid = false; + err += TTR("Invalid splash screen image dimensions (should be 620x300).") + "\n"; + } + + r_error = err; + return valid; + } + +#ifdef WINDOWS_ENABLED + void extract_all_files_from_zip(String zip_file_path, String extract_path) { + FileAccess *src_f = nullptr; + zlib_filefunc_def io = zipio_create_io_from_file(&src_f); + + unzFile pkg = unzOpen2(zip_file_path.utf8().get_data(), &io); + + int ret = unzGoToFirstFile(pkg); + + while (ret == UNZ_OK) { + // get file name + unz_file_info info; + char fname[16384]; + ret = unzGetCurrentFileInfo(pkg, &info, fname, 16384, nullptr, 0, nullptr, 0); + + String path = String::utf8(fname); + + //write the files + Vector data; + + //read + data.resize(info.uncompressed_size); + unzOpenCurrentFile(pkg); + unzReadCurrentFile(pkg, data.ptrw(), data.size()); + unzCloseCurrentFile(pkg); + + //check if the subfolder doesn't exist and create it + String file_path = extract_path + "/" + path.get_base_dir(); + DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + if (!da->dir_exists(file_path)) { + da->make_dir_recursive(file_path); + } + memdelete(da); + + FileAccess *unzipped_file = FileAccess::open(extract_path + "/" + path, FileAccess::WRITE); + unzipped_file->store_buffer(data.ptrw(), data.size()); + unzipped_file->close(); + + ret = unzGoToNextFile(pkg); + } + + unzClose(pkg); + } +#endif + + virtual Error export_project(const Ref &p_preset, bool p_debug, const String &p_path, int p_flags = 0) { + ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags); + + String src_appx; + + EditorProgress ep("export", "Exporting for UWP", 7, true); + + if (p_debug) { + src_appx = p_preset->get("custom_template/debug"); + } else { + src_appx = p_preset->get("custom_template/release"); + } + + src_appx = src_appx.strip_edges(); + + Platform arch = (Platform)(int)p_preset->get("architecture/target"); + + if (src_appx == "") { + String err, infix; + switch (arch) { + case ARM: { + infix = "_arm_"; + } break; + case X86: { + infix = "_x86_"; + } break; + case X64: { + infix = "_x64_"; + } break; + } + if (p_debug) { + src_appx = find_export_template("uwp" + infix + "debug.zip", &err); + } else { + src_appx = find_export_template("uwp" + infix + "release.zip", &err); + } + if (src_appx == "") { + EditorNode::add_io_error(err); + return ERR_FILE_NOT_FOUND; + } + } + + if (!DirAccess::exists(p_path.get_base_dir())) { + return ERR_FILE_BAD_PATH; + } + + Error err = OK; + + FileAccess *fa_pack = FileAccess::open(p_path, FileAccess::WRITE, &err); + ERR_FAIL_COND_V_MSG(err != OK, ERR_CANT_CREATE, "Cannot create file '" + p_path + "'."); + + AppxPackager packager; + packager.init(fa_pack); + + FileAccess *src_f = nullptr; + zlib_filefunc_def io = zipio_create_io_from_file(&src_f); + + if (ep.step("Creating package...", 0)) { + return ERR_SKIP; + } + + unzFile pkg = unzOpen2(src_appx.utf8().get_data(), &io); + + if (!pkg) { + EditorNode::add_io_error("Could not find template appx to export:\n" + src_appx); + return ERR_FILE_NOT_FOUND; + } + + int ret = unzGoToFirstFile(pkg); + + if (ep.step("Copying template files...", 1)) { + return ERR_SKIP; + } + + EditorNode::progress_add_task("template_files", "Template files", 100); + packager.set_progress_task("template_files"); + + int template_files_amount = 9; + int template_file_no = 1; + + while (ret == UNZ_OK) { + // get file name + unz_file_info info; + char fname[16384]; + ret = unzGetCurrentFileInfo(pkg, &info, fname, 16384, nullptr, 0, nullptr, 0); + + String path = String::utf8(fname); + + if (path.ends_with("/")) { + // Ignore directories + ret = unzGoToNextFile(pkg); + continue; + } + + Vector data; + bool do_read = true; + + if (path.begins_with("Assets/")) { + path = path.replace(".scale-100", ""); + + data = _get_image_data(p_preset, path); + if (data.size() > 0) { + do_read = false; + } + } + + //read + if (do_read) { + data.resize(info.uncompressed_size); + unzOpenCurrentFile(pkg); + unzReadCurrentFile(pkg, data.ptrw(), data.size()); + unzCloseCurrentFile(pkg); + } + + if (path == "AppxManifest.xml") { + data = _fix_manifest(p_preset, data, p_flags & (DEBUG_FLAG_DUMB_CLIENT | DEBUG_FLAG_REMOTE_DEBUG)); + } + + print_line("ADDING: " + path); + + err = packager.add_file(path, data.ptr(), data.size(), template_file_no++, template_files_amount, _should_compress_asset(path, data)); + if (err != OK) { + return err; + } + + ret = unzGoToNextFile(pkg); + } + + EditorNode::progress_end_task("template_files"); + + if (ep.step("Creating command line...", 2)) { + return ERR_SKIP; + } + + Vector cl = ((String)p_preset->get("command_line/extra_args")).strip_edges().split(" "); + for (int i = 0; i < cl.size(); i++) { + if (cl[i].strip_edges().length() == 0) { + cl.remove(i); + i--; + } + } + + if (!(p_flags & DEBUG_FLAG_DUMB_CLIENT)) { + cl.push_back("--path"); + cl.push_back("game"); + } + + gen_export_flags(cl, p_flags); + + // Command line file + Vector clf; + + // Argc + clf.resize(4); + encode_uint32(cl.size(), clf.ptrw()); + + for (int i = 0; i < cl.size(); i++) { + CharString txt = cl[i].utf8(); + int base = clf.size(); + clf.resize(base + 4 + txt.length()); + encode_uint32(txt.length(), &clf.write[base]); + memcpy(&clf.write[base + 4], txt.ptr(), txt.length()); + print_line(itos(i) + " param: " + cl[i]); + } + + err = packager.add_file("__cl__.cl", clf.ptr(), clf.size(), -1, -1, false); + if (err != OK) { + return err; + } + + if (ep.step("Adding project files...", 3)) { + return ERR_SKIP; + } + + EditorNode::progress_add_task("project_files", "Project Files", 100); + packager.set_progress_task("project_files"); + + err = export_project_files(p_preset, save_appx_file, &packager, copy_shared_objects); + + EditorNode::progress_end_task("project_files"); + + if (ep.step("Closing package...", 7)) { + return ERR_SKIP; + } + + unzClose(pkg); + + packager.finish(); + +#ifdef WINDOWS_ENABLED + // Repackage it with makeappx if available + String makeappx_path = EditorSettings::get_singleton()->get("export/uwp/makeappx"); + if (makeappx_path != String()) { + if (FileAccess::exists(makeappx_path)) { + // Get uwp_temp_path + String uwp_temp_path = EditorSettings::get_singleton()->get_cache_dir() + "/uwptemp"; + + // Extract current appx file + extract_all_files_from_zip(p_path, uwp_temp_path); + + // Call makeappx + List args_makeappx; + args_makeappx.push_back("pack"); + args_makeappx.push_back("/d"); + args_makeappx.push_back(uwp_temp_path); + args_makeappx.push_back("/p"); + args_makeappx.push_back(p_path); + args_makeappx.push_back("/o"); + + OS::get_singleton()->execute(makeappx_path, args_makeappx, true); + + // Delete uwp_temp_path folder recursively + DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + Error err = da->change_dir(uwp_temp_path); + if (err == OK) { + err = da->erase_contents_recursive(); + if (err != OK) { + ERR_PRINT("Could not delete UWP temporary folder: '" + uwp_temp_path + "'."); + return err; + } else { + da->remove(uwp_temp_path); + } + } else { + ERR_PRINT("Could not change dir to UWP temporary folder: '" + uwp_temp_path + "'."); + ERR_PRINT("Could not delete UWP temporary folder: '" + uwp_temp_path + "'."); + return err; + } + memdelete(da); + } else { + ERR_PRINT("Could not find makeappx executable at " + makeappx_path + ", aborting."); + return ERR_FILE_NOT_FOUND; + } + } + + // Sign with signtool + String signtool_path = EditorSettings::get_singleton()->get("export/uwp/signtool"); + if (signtool_path == String()) { + return OK; + } + + if (!FileAccess::exists(signtool_path)) { + ERR_PRINT("Could not find signtool executable at " + signtool_path + ", aborting."); + return ERR_FILE_NOT_FOUND; + } + + static String algs[] = { "MD5", "SHA1", "SHA256" }; + + String cert_path = EditorSettings::get_singleton()->get("export/uwp/debug_certificate"); + String cert_pass = EditorSettings::get_singleton()->get("export/uwp/debug_password"); + int cert_alg = EditorSettings::get_singleton()->get("export/uwp/debug_algorithm"); + + if (!p_debug) { + cert_path = p_preset->get("signing/certificate"); + cert_pass = p_preset->get("signing/password"); + cert_alg = p_preset->get("signing/algorithm"); + } + + if (cert_path == String()) { + return OK; // Certificate missing, don't try to sign + } + + if (!FileAccess::exists(cert_path)) { + ERR_PRINT("Could not find certificate file at " + cert_path + ", aborting."); + return ERR_FILE_NOT_FOUND; + } + + if (cert_alg < 0 || cert_alg > 2) { + ERR_PRINT("Invalid certificate algorithm " + itos(cert_alg) + ", aborting."); + return ERR_INVALID_DATA; + } + + List args; + args.push_back("sign"); + args.push_back("/fd"); + args.push_back(algs[cert_alg]); + args.push_back("/a"); + args.push_back("/f"); + args.push_back(cert_path); + args.push_back("/p"); + args.push_back(cert_pass); + args.push_back(p_path); + + OS::get_singleton()->execute(signtool_path, args, true); +#endif // WINDOWS_ENABLED + + return OK; + } + + virtual void get_platform_features(List *r_features) { + r_features->push_back("pc"); + r_features->push_back("UWP"); + } + + virtual void resolve_platform_feature_priorities(const Ref &p_preset, Set &p_features) { + } + + static Error copy_shared_objects(void *p_userdata, const SharedObject &p_so) { + return save_appx_file(p_userdata, p_so.path, FileAccess::get_file_as_array(p_so.path), 0, 1); + } + + EditorExportPlatformUWP() { + Ref img = memnew(Image(_uwp_logo)); + logo.instance(); + logo->create_from_image(img); + } +}; + +void register_uwp_exporter() { +#ifdef WINDOWS_ENABLED + EDITOR_DEF("export/uwp/signtool", ""); + EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/uwp/signtool", PROPERTY_HINT_GLOBAL_FILE, "*.exe")); + EDITOR_DEF("export/uwp/makeappx", ""); + EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/uwp/makeappx", PROPERTY_HINT_GLOBAL_FILE, "*.exe")); + EDITOR_DEF("export/uwp/debug_certificate", ""); + EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/uwp/debug_certificate", PROPERTY_HINT_GLOBAL_FILE, "*.pfx")); + EDITOR_DEF("export/uwp/debug_password", ""); + EDITOR_DEF("export/uwp/debug_algorithm", 2); // SHA256 is the default + EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "export/uwp/debug_algorithm", PROPERTY_HINT_ENUM, "MD5,SHA1,SHA256")); +#endif // WINDOWS_ENABLED + + Ref exporter; + exporter.instance(); + EditorExport::get_singleton()->add_export_platform(exporter); +} diff --git a/platform/uwp/export/export.h b/platform/uwp/export/export.h new file mode 100644 index 000000000..09449fc53 --- /dev/null +++ b/platform/uwp/export/export.h @@ -0,0 +1,36 @@ +/**************************************************************************/ +/* export.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef UWP_EXPORT_H +#define UWP_EXPORT_H + +void register_uwp_exporter(); + +#endif // UWP_EXPORT_H diff --git a/platform/uwp/joypad_uwp.cpp b/platform/uwp/joypad_uwp.cpp new file mode 100644 index 000000000..29c4fd0bb --- /dev/null +++ b/platform/uwp/joypad_uwp.cpp @@ -0,0 +1,170 @@ +/**************************************************************************/ +/* joypad_uwp.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "joypad_uwp.h" +#include "core/os/os.h" + +using namespace Windows::Gaming::Input; +using namespace Windows::Foundation; + +void JoypadUWP::register_events() { + Gamepad::GamepadAdded += + ref new EventHandler(this, &JoypadUWP::OnGamepadAdded); + Gamepad::GamepadRemoved += + ref new EventHandler(this, &JoypadUWP::OnGamepadRemoved); +} + +void JoypadUWP::process_controllers() { + for (int i = 0; i < MAX_CONTROLLERS; i++) { + ControllerDevice &joy = controllers[i]; + + if (!joy.connected) + break; + + switch (joy.type) { + case ControllerType::GAMEPAD_CONTROLLER: { + GamepadReading reading = ((Gamepad ^) joy.controller_reference)->GetCurrentReading(); + + int button_mask = (int)GamepadButtons::Menu; + for (int j = 0; j < 14; j++) { + input->joy_button(joy.id, j, (int)reading.Buttons & button_mask); + button_mask *= 2; + } + + input->joy_axis(joy.id, JOY_AXIS_0, axis_correct(reading.LeftThumbstickX)); + input->joy_axis(joy.id, JOY_AXIS_1, axis_correct(reading.LeftThumbstickY, true)); + input->joy_axis(joy.id, JOY_AXIS_2, axis_correct(reading.RightThumbstickX)); + input->joy_axis(joy.id, JOY_AXIS_3, axis_correct(reading.RightThumbstickY, true)); + input->joy_axis(joy.id, JOY_AXIS_4, axis_correct(reading.LeftTrigger, false, true)); + input->joy_axis(joy.id, JOY_AXIS_5, axis_correct(reading.RightTrigger, false, true)); + + uint64_t timestamp = input->get_joy_vibration_timestamp(joy.id); + if (timestamp > joy.ff_timestamp) { + Vector2 strength = input->get_joy_vibration_strength(joy.id); + float duration = input->get_joy_vibration_duration(joy.id); + if (strength.x == 0 && strength.y == 0) { + joypad_vibration_stop(i, timestamp); + } else { + joypad_vibration_start(i, strength.x, strength.y, duration, timestamp); + } + } else if (joy.vibrating && joy.ff_end_timestamp != 0) { + uint64_t current_time = OS::get_singleton()->get_ticks_usec(); + if (current_time >= joy.ff_end_timestamp) + joypad_vibration_stop(i, current_time); + } + + break; + } + } + } +} + +JoypadUWP::JoypadUWP() { + for (int i = 0; i < MAX_CONTROLLERS; i++) + controllers[i].id = i; +} + +JoypadUWP::JoypadUWP(InputDefault *p_input) { + input = p_input; + + JoypadUWP(); +} + +void JoypadUWP::OnGamepadAdded(Platform::Object ^ sender, Windows::Gaming::Input::Gamepad ^ value) { + short idx = -1; + + for (int i = 0; i < MAX_CONTROLLERS; i++) { + if (!controllers[i].connected) { + idx = i; + break; + } + } + + ERR_FAIL_COND(idx == -1); + + controllers[idx].connected = true; + controllers[idx].controller_reference = value; + controllers[idx].id = idx; + controllers[idx].type = ControllerType::GAMEPAD_CONTROLLER; + + input->joy_connection_changed(controllers[idx].id, true, "Xbox Controller", "__UWP_GAMEPAD__"); +} + +void JoypadUWP::OnGamepadRemoved(Platform::Object ^ sender, Windows::Gaming::Input::Gamepad ^ value) { + short idx = -1; + + for (int i = 0; i < MAX_CONTROLLERS; i++) { + if (controllers[i].controller_reference == value) { + idx = i; + break; + } + } + + ERR_FAIL_COND(idx == -1); + + controllers[idx] = ControllerDevice(); + + input->joy_connection_changed(idx, false, "Xbox Controller"); +} + +float JoypadUWP::axis_correct(double p_val, bool p_negate, bool p_trigger) const { + if (p_trigger) { + // Convert to a value between -1.0f and 1.0f. + return 2.0f * p_val - 1.0f; + } + return (float)(p_negate ? -p_val : p_val); +} + +void JoypadUWP::joypad_vibration_start(int p_device, float p_weak_magnitude, float p_strong_magnitude, float p_duration, uint64_t p_timestamp) { + ControllerDevice &joy = controllers[p_device]; + if (joy.connected) { + GamepadVibration vibration; + vibration.LeftMotor = p_strong_magnitude; + vibration.RightMotor = p_weak_magnitude; + ((Gamepad ^) joy.controller_reference)->Vibration = vibration; + + joy.ff_timestamp = p_timestamp; + joy.ff_end_timestamp = p_duration == 0 ? 0 : p_timestamp + (uint64_t)(p_duration * 1000000.0); + joy.vibrating = true; + } +} + +void JoypadUWP::joypad_vibration_stop(int p_device, uint64_t p_timestamp) { + ControllerDevice &joy = controllers[p_device]; + if (joy.connected) { + GamepadVibration vibration; + vibration.LeftMotor = 0.0; + vibration.RightMotor = 0.0; + ((Gamepad ^) joy.controller_reference)->Vibration = vibration; + + joy.ff_timestamp = p_timestamp; + joy.vibrating = false; + } +} diff --git a/platform/uwp/joypad_uwp.h b/platform/uwp/joypad_uwp.h new file mode 100644 index 000000000..24fb52c5e --- /dev/null +++ b/platform/uwp/joypad_uwp.h @@ -0,0 +1,90 @@ +/**************************************************************************/ +/* joypad_uwp.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef JOYPAD_UWP_H +#define JOYPAD_UWP_H + +#include "main/input_default.h" + +ref class JoypadUWP sealed { + /** clang-format breaks this, it does not understand this token. */ + /* clang-format off */ +internal: + void register_events(); + void process_controllers(); + /* clang-format on */ + + JoypadUWP(); + JoypadUWP(InputDefault *p_input); + +private: + enum { + MAX_CONTROLLERS = 4, + }; + + enum ControllerType { + GAMEPAD_CONTROLLER, + ARCADE_STICK_CONTROLLER, + RACING_WHEEL_CONTROLLER, + }; + + struct ControllerDevice { + Windows::Gaming::Input::IGameController ^ controller_reference; + + int id; + bool connected; + ControllerType type; + float ff_timestamp; + float ff_end_timestamp; + bool vibrating; + + ControllerDevice() { + id = -1; + connected = false; + type = ControllerType::GAMEPAD_CONTROLLER; + ff_timestamp = 0.0f; + ff_end_timestamp = 0.0f; + vibrating = false; + } + }; + + ControllerDevice controllers[MAX_CONTROLLERS]; + + InputDefault *input; + + void OnGamepadAdded(Platform::Object ^ sender, Windows::Gaming::Input::Gamepad ^ value); + void OnGamepadRemoved(Platform::Object ^ sender, Windows::Gaming::Input::Gamepad ^ value); + + float axis_correct(double p_val, bool p_negate = false, bool p_trigger = false) const; + void joypad_vibration_start(int p_device, float p_weak_magnitude, float p_strong_magnitude, float p_duration, uint64_t p_timestamp); + void joypad_vibration_stop(int p_device, uint64_t p_timestamp); +}; + +#endif // JOYPAD_UWP_H diff --git a/platform/uwp/logo.png b/platform/uwp/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..9017a30636d9b9d7bd6fc819a904fca2d2c50a5a GIT binary patch literal 1519 zcmVip%03srLADGMXcKTptM8?sdTrIT2io0FezH5eI~f9AxHCID_nz-N=bpb26mz5xeyIvA*-3(W$e|rX&L(W#f@xv?LL{bf z&tgtw4)4AZQ!K=-GSY{>P6b=a%^z1?YsCrhWE!E z%lIPu{!`0Q5RnAZLl^c4p}YlvhlLOTFd4uD4Q+M62y1wPuuF#Md)(j6j`V(8jYLHP z>A`POLa4(K_9`^ipb0quU=neLzgDOTCbERf2=2q#(f)jRf0;ypw?pfXg{JpACIj2C zfy2Tj5geDah-DnZ^Xst2Lh-s10_nl?P0;d`Q1xEND&SB(v&K=o%9<5x=wzGu;{ZI6 z8R^aW%i%rl=o-49;`SaiUo$v^*_f8?kYU+mV#;H~f2@$A>CZ!@H;cIj1hDy8nCfN6 ztgg8aU;}_>!L%uFSd_!$I~%Mfp;Bu@k{;UFKo7LlNkY;&B?%M9p$@^)kNYI#5kUL( z5PlI()5#7eJsulOfbf16!V6VJP!a&TCP-5idJq6@jY(>6tdVmA=K55Un(K5*)h6id z#0XPuAcHZG@YR2MLhPwAC@)m&3>4t(}wmwID>6#ucwYCS8zM-Jo)$Uf92@X z^*MUyne9jk)g!}~6==(fv4V>B7()7K{C`@5_O)QPk5%*}|K=?^HFlejyUGTzk>Jc}j)Lp_P(1tu z!l|~>FfGpck=XJjhilb{a-Dvfktf}DW<{cGb~ty(09lzMCHY?`<#~H`l zDGe2A5#{kyB!-7ZEUWk`!MGg@|1^vu#Z&B6~zu+OoJ`$ zkw2C>{G{av`BB>{p#Rf0qiqQ);%|TF=+{4&WjXdklamnF2nN1!XPnv)C0r#tnref} z*;21lBf>Y_N6KgJ6D{LMkT}D{0!zXQui@o~Ou+5At0fSZ3}4vGeR6)kzzs=VjYju^ z1Y79V+>*>GL3<}u1LwaNmX`#I)7(d3wp&Tm4A@2vR;WyZ{X9?*W?Cs2}s z@L~oF_QMk6u!K~zaxsY){cw{&t}yP{8 +#include + +using namespace Windows::ApplicationModel::Core; +using namespace Windows::ApplicationModel::Activation; +using namespace Windows::UI::Core; +using namespace Windows::UI::Input; +using namespace Windows::UI::Popups; +using namespace Windows::Foundation; +using namespace Windows::Graphics::Display; +using namespace Microsoft::WRL; +using namespace Windows::UI::ViewManagement; +using namespace Windows::Devices::Input; +using namespace Windows::Devices::Sensors; +using namespace Windows::ApplicationModel::DataTransfer; +using namespace concurrency; + +int OS_UWP::get_video_driver_count() const { + return 2; +} + +Size2 OS_UWP::get_window_size() const { + Size2 size; + size.width = video_mode.width; + size.height = video_mode.height; + return size; +} + +int OS_UWP::get_current_video_driver() const { + return video_driver_index; +} + +void OS_UWP::set_window_size(const Size2 p_size) { + Windows::Foundation::Size new_size; + new_size.Width = p_size.width; + new_size.Height = p_size.height; + + ApplicationView ^ view = ApplicationView::GetForCurrentView(); + + if (view->TryResizeView(new_size)) { + video_mode.width = p_size.width; + video_mode.height = p_size.height; + } +} + +void OS_UWP::set_window_fullscreen(bool p_enabled) { + ApplicationView ^ view = ApplicationView::GetForCurrentView(); + + video_mode.fullscreen = view->IsFullScreenMode; + + if (video_mode.fullscreen == p_enabled) + return; + + if (p_enabled) { + video_mode.fullscreen = view->TryEnterFullScreenMode(); + + } else { + view->ExitFullScreenMode(); + video_mode.fullscreen = false; + } +} + +bool OS_UWP::is_window_fullscreen() const { + return ApplicationView::GetForCurrentView()->IsFullScreenMode; +} + +void OS_UWP::set_keep_screen_on(bool p_enabled) { + if (is_keep_screen_on() == p_enabled) + return; + + if (p_enabled) + display_request->RequestActive(); + else + display_request->RequestRelease(); + + OS::set_keep_screen_on(p_enabled); +} + +void OS_UWP::initialize_core() { + last_button_state = 0; + + //RedirectIOToConsole(); + + FileAccess::make_default(FileAccess::ACCESS_RESOURCES); + FileAccess::make_default(FileAccess::ACCESS_USERDATA); + FileAccess::make_default(FileAccess::ACCESS_FILESYSTEM); + DirAccess::make_default(DirAccess::ACCESS_RESOURCES); + DirAccess::make_default(DirAccess::ACCESS_USERDATA); + DirAccess::make_default(DirAccess::ACCESS_FILESYSTEM); + + NetSocketPosix::make_default(); + + // We need to know how often the clock is updated + QueryPerformanceFrequency((LARGE_INTEGER *)&ticks_per_second); + QueryPerformanceCounter((LARGE_INTEGER *)&ticks_start); + + IP_Unix::make_default(); + + cursor_shape = CURSOR_ARROW; +} + +bool OS_UWP::can_draw() const { + return !minimized; +}; + +void OS_UWP::set_window(Windows::UI::Core::CoreWindow ^ p_window) { + window = p_window; +} + +void OS_UWP::screen_size_changed() { + gl_context->reset(); +}; + +Error OS_UWP::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) { + main_loop = NULL; + outside = true; + + ContextEGL_UWP::Driver opengl_api_type = ContextEGL_UWP::GLES_2_0; + + if (p_video_driver == VIDEO_DRIVER_GLES2) { + opengl_api_type = ContextEGL_UWP::GLES_2_0; + } + + bool gl_initialization_error = false; + + gl_context = NULL; + while (!gl_context) { + gl_context = memnew(ContextEGL_UWP(window, opengl_api_type)); + + if (gl_context->initialize() != OK) { + memdelete(gl_context); + gl_context = NULL; + + if (GLOBAL_GET("rendering/quality/driver/fallback_to_gles2")) { + if (p_video_driver == VIDEO_DRIVER_GLES2) { + gl_initialization_error = true; + break; + } + + p_video_driver = VIDEO_DRIVER_GLES2; + opengl_api_type = ContextEGL_UWP::GLES_2_0; + } else { + gl_initialization_error = true; + break; + } + } + } + + while (true) { + if (opengl_api_type == ContextEGL_UWP::GLES_3_0) { + if (RasterizerGLES3::is_viable() == OK) { + RasterizerGLES3::register_config(); + RasterizerGLES3::make_current(); + break; + } else { + if (GLOBAL_GET("rendering/quality/driver/fallback_to_gles2")) { + p_video_driver = VIDEO_DRIVER_GLES2; + opengl_api_type = ContextEGL_UWP::GLES_2_0; + continue; + } else { + gl_initialization_error = true; + break; + } + } + } + + if (opengl_api_type == ContextEGL_UWP::GLES_2_0) { + if (RasterizerGLES2::is_viable() == OK) { + RasterizerGLES2::register_config(); + RasterizerGLES2::make_current(); + break; + } else { + gl_initialization_error = true; + break; + } + } + } + + if (gl_initialization_error) { + OS::get_singleton()->alert("Your video card driver does not support any of the supported OpenGL versions.\n" + "Please update your drivers or if you have a very old or integrated GPU, upgrade it.", + "Unable to initialize Video driver"); + return ERR_UNAVAILABLE; + } + + video_driver_index = p_video_driver; + gl_context->make_current(); + gl_context->set_use_vsync(video_mode.use_vsync); + + VideoMode vm; + vm.width = gl_context->get_window_width(); + vm.height = gl_context->get_window_height(); + vm.resizable = false; + + ApplicationView ^ view = ApplicationView::GetForCurrentView(); + vm.fullscreen = view->IsFullScreenMode; + + view->SetDesiredBoundsMode(ApplicationViewBoundsMode::UseVisible); + view->PreferredLaunchWindowingMode = ApplicationViewWindowingMode::PreferredLaunchViewSize; + + if (p_desired.fullscreen != view->IsFullScreenMode) { + if (p_desired.fullscreen) { + vm.fullscreen = view->TryEnterFullScreenMode(); + + } else { + view->ExitFullScreenMode(); + vm.fullscreen = false; + } + } + + Windows::Foundation::Size desired; + desired.Width = p_desired.width; + desired.Height = p_desired.height; + + view->PreferredLaunchViewSize = desired; + + if (view->TryResizeView(desired)) { + vm.width = view->VisibleBounds.Width; + vm.height = view->VisibleBounds.Height; + } + + set_video_mode(vm); + + visual_server = memnew(VisualServerRaster); + // FIXME: Reimplement threaded rendering + if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) { + visual_server = memnew(VisualServerWrapMT(visual_server, false)); + } + + visual_server->init(); + + input = memnew(InputDefault); + + joypad = ref new JoypadUWP(input); + joypad->register_events(); + + AudioDriverManager::initialize(p_audio_driver); + + power_manager = memnew(PowerUWP); + + Clipboard::ContentChanged += ref new EventHandler(managed_object, &ManagedType::on_clipboard_changed); + + accelerometer = Accelerometer::GetDefault(); + if (accelerometer != nullptr) { + // 60 FPS + accelerometer->ReportInterval = (1.0f / 60.0f) * 1000; + accelerometer->ReadingChanged += + ref new TypedEventHandler(managed_object, &ManagedType::on_accelerometer_reading_changed); + } + + magnetometer = Magnetometer::GetDefault(); + if (magnetometer != nullptr) { + // 60 FPS + magnetometer->ReportInterval = (1.0f / 60.0f) * 1000; + magnetometer->ReadingChanged += + ref new TypedEventHandler(managed_object, &ManagedType::on_magnetometer_reading_changed); + } + + gyrometer = Gyrometer::GetDefault(); + if (gyrometer != nullptr) { + // 60 FPS + gyrometer->ReportInterval = (1.0f / 60.0f) * 1000; + gyrometer->ReadingChanged += + ref new TypedEventHandler(managed_object, &ManagedType::on_gyroscope_reading_changed); + } + + if (is_keep_screen_on()) + display_request->RequestActive(); + + set_keep_screen_on(GLOBAL_DEF("display/window/energy_saving/keep_screen_on", true)); + + return OK; +} + +void OS_UWP::set_clipboard(const String &p_text) { + DataPackage ^ clip = ref new DataPackage(); + clip->RequestedOperation = DataPackageOperation::Copy; + clip->SetText(ref new Platform::String((const wchar_t *)p_text.c_str())); + + Clipboard::SetContent(clip); +}; + +String OS_UWP::get_clipboard() const { + if (managed_object->clipboard != nullptr) + return managed_object->clipboard->Data(); + else + return ""; +}; + +void OS_UWP::input_event(const Ref &p_event) { + input->parse_input_event(p_event); +}; + +void OS_UWP::delete_main_loop() { + if (main_loop) + memdelete(main_loop); + main_loop = NULL; +} + +void OS_UWP::set_main_loop(MainLoop *p_main_loop) { + input->set_main_loop(p_main_loop); + main_loop = p_main_loop; +} + +void OS_UWP::finalize() { + if (main_loop) + memdelete(main_loop); + + main_loop = NULL; + + visual_server->finish(); + memdelete(visual_server); +#ifdef OPENGL_ENABLED + if (gl_context) + memdelete(gl_context); +#endif + + memdelete(input); + + joypad = nullptr; +} + +void OS_UWP::finalize_core() { + NetSocketPosix::cleanup(); +} + +void OS_UWP::alert(const String &p_alert, const String &p_title) { + Platform::String ^ alert = ref new Platform::String(p_alert.c_str()); + Platform::String ^ title = ref new Platform::String(p_title.c_str()); + + MessageDialog ^ msg = ref new MessageDialog(alert, title); + + UICommand ^ close = ref new UICommand("Close", ref new UICommandInvokedHandler(managed_object, &OS_UWP::ManagedType::alert_close)); + msg->Commands->Append(close); + msg->DefaultCommandIndex = 0; + + managed_object->alert_close_handle = true; + + msg->ShowAsync(); +} + +void OS_UWP::update_clipboard() { + // See https://stackoverflow.com/questions/58660743/uwp-app-crashes-when-clipboard-getcontent-is-called-from-inside-onnavigatedto + managed_object->update_clipboard(); +} + +void OS_UWP::ManagedType::alert_close(IUICommand ^ command) { + alert_close_handle = false; +} + +void OS_UWP::ManagedType::on_clipboard_changed(Platform::Object ^ sender, Platform::Object ^ ev) { + update_clipboard(); +} + +void OS_UWP::ManagedType::update_clipboard() { + DataPackageView ^ data = Clipboard::GetContent(); + + if (data->Contains(StandardDataFormats::Text)) { + create_task(data->GetTextAsync()).then([this](Platform::String ^ clipboard_content) { + this->clipboard = clipboard_content; + }); + } +} + +void OS_UWP::ManagedType::on_accelerometer_reading_changed(Accelerometer ^ sender, AccelerometerReadingChangedEventArgs ^ args) { + AccelerometerReading ^ reading = args->Reading; + + os->input->set_accelerometer(Vector3( + reading->AccelerationX, + reading->AccelerationY, + reading->AccelerationZ)); +} + +void OS_UWP::ManagedType::on_magnetometer_reading_changed(Magnetometer ^ sender, MagnetometerReadingChangedEventArgs ^ args) { + MagnetometerReading ^ reading = args->Reading; + + os->input->set_magnetometer(Vector3( + reading->MagneticFieldX, + reading->MagneticFieldY, + reading->MagneticFieldZ)); +} + +void OS_UWP::ManagedType::on_gyroscope_reading_changed(Gyrometer ^ sender, GyrometerReadingChangedEventArgs ^ args) { + GyrometerReading ^ reading = args->Reading; + + os->input->set_magnetometer(Vector3( + reading->AngularVelocityX, + reading->AngularVelocityY, + reading->AngularVelocityZ)); +} + +void OS_UWP::set_mouse_mode(MouseMode p_mode) { + if (p_mode == MouseMode::MOUSE_MODE_CAPTURED) { + CoreWindow::GetForCurrentThread()->SetPointerCapture(); + } else { + CoreWindow::GetForCurrentThread()->ReleasePointerCapture(); + } + + if (p_mode == MouseMode::MOUSE_MODE_HIDDEN || p_mode == MouseMode::MOUSE_MODE_CAPTURED || p_mode == MouseMode::MOUSE_MODE_CONFINED_HIDDEN) { + CoreWindow::GetForCurrentThread()->PointerCursor = nullptr; + } else { + CoreWindow::GetForCurrentThread()->PointerCursor = ref new CoreCursor(CoreCursorType::Arrow, 0); + } + + mouse_mode = p_mode; + + SetEvent(mouse_mode_changed); +} + +OS_UWP::MouseMode OS_UWP::get_mouse_mode() const { + return mouse_mode; +} + +Point2 OS_UWP::get_mouse_position() const { + return Point2(old_x, old_y); +} + +int OS_UWP::get_mouse_button_state() const { + return last_button_state; +} + +void OS_UWP::set_window_title(const String &p_title) { +} + +void OS_UWP::set_video_mode(const VideoMode &p_video_mode, int p_screen) { + video_mode = p_video_mode; +} +OS::VideoMode OS_UWP::get_video_mode(int p_screen) const { + return video_mode; +} +void OS_UWP::get_fullscreen_mode_list(List *p_list, int p_screen) const { +} + +String OS_UWP::get_name() const { + return "UWP"; +} + +OS::Date OS_UWP::get_date(bool utc) const { + SYSTEMTIME systemtime; + if (utc) + GetSystemTime(&systemtime); + else + GetLocalTime(&systemtime); + + Date date; + date.day = systemtime.wDay; + date.month = Month(systemtime.wMonth); + date.weekday = Weekday(systemtime.wDayOfWeek); + date.year = systemtime.wYear; + date.dst = false; + return date; +} +OS::Time OS_UWP::get_time(bool utc) const { + SYSTEMTIME systemtime; + if (utc) + GetSystemTime(&systemtime); + else + GetLocalTime(&systemtime); + + Time time; + time.hour = systemtime.wHour; + time.min = systemtime.wMinute; + time.sec = systemtime.wSecond; + return time; +} + +OS::TimeZoneInfo OS_UWP::get_time_zone_info() const { + TIME_ZONE_INFORMATION info; + bool daylight = false; + if (GetTimeZoneInformation(&info) == TIME_ZONE_ID_DAYLIGHT) + daylight = true; + + TimeZoneInfo ret; + if (daylight) { + ret.name = info.DaylightName; + } else { + ret.name = info.StandardName; + } + + // Bias value returned by GetTimeZoneInformation is inverted of what we expect + // For example on GMT-3 GetTimeZoneInformation return a Bias of 180, so invert the value to get -180 + ret.bias = -info.Bias; + return ret; +} + +uint64_t OS_UWP::get_unix_time() const { + FILETIME ft; + SYSTEMTIME st; + GetSystemTime(&st); + SystemTimeToFileTime(&st, &ft); + + SYSTEMTIME ep; + ep.wYear = 1970; + ep.wMonth = 1; + ep.wDayOfWeek = 4; + ep.wDay = 1; + ep.wHour = 0; + ep.wMinute = 0; + ep.wSecond = 0; + ep.wMilliseconds = 0; + FILETIME fep; + SystemTimeToFileTime(&ep, &fep); + + return (*(uint64_t *)&ft - *(uint64_t *)&fep) / 10000000; +}; + +double OS_UWP::get_subsecond_unix_time() const { + // 1 Windows tick is 100ns + const uint64_t WINDOWS_TICKS_PER_SECOND = 10000000; + const uint64_t TICKS_TO_UNIX_EPOCH = 116444736000000000LL; + + SYSTEMTIME st; + GetSystemTime(&st); + FILETIME ft; + SystemTimeToFileTime(&st, &ft); + uint64_t ticks_time; + ticks_time = ft.dwHighDateTime; + ticks_time <<= 32; + ticks_time |= ft.dwLowDateTime; + + return (double)(ticks_time - TICKS_TO_UNIX_EPOCH) / WINDOWS_TICKS_PER_SECOND; +} + +void OS_UWP::delay_usec(uint32_t p_usec) const { + int msec = p_usec < 1000 ? 1 : p_usec / 1000; + + // no Sleep() + WaitForSingleObjectEx(GetCurrentThread(), msec, false); +} +uint64_t OS_UWP::get_ticks_usec() const { + uint64_t ticks; + + // This is the number of clock ticks since start + QueryPerformanceCounter((LARGE_INTEGER *)&ticks); + // Subtract the ticks at game start to get + // the ticks since the game started + ticks -= ticks_start; + + // Divide by frequency to get the time in seconds + // original calculation shown below is subject to overflow + // with high ticks_per_second and a number of days since the last reboot. + // time = ticks * 1000000L / ticks_per_second; + + // we can prevent this by either using 128 bit math + // or separating into a calculation for seconds, and the fraction + uint64_t seconds = ticks / ticks_per_second; + + // compiler will optimize these two into one divide + uint64_t leftover = ticks % ticks_per_second; + + // remainder + uint64_t time = (leftover * 1000000L) / ticks_per_second; + + // seconds + time += seconds * 1000000L; + + return time; +} + +void OS_UWP::process_events() { + joypad->process_controllers(); + process_key_events(); + input->flush_buffered_events(); +} + +void OS_UWP::process_key_events() { + for (int i = 0; i < key_event_pos; i++) { + KeyEvent &kev = key_event_buffer[i]; + + Ref key_event; + key_event.instance(); + key_event->set_alt(kev.alt); + key_event->set_shift(kev.shift); + key_event->set_control(kev.control); + key_event->set_echo(kev.echo); + key_event->set_scancode(kev.scancode); + key_event->set_physical_scancode(kev.physical_scancode); + key_event->set_unicode(kev.unicode); + key_event->set_pressed(kev.pressed); + + input_event(key_event); + } + key_event_pos = 0; +} + +void OS_UWP::queue_key_event(KeyEvent &p_event) { + // This merges Char events with the previous Key event, so + // the unicode can be retrieved without sending duplicate events. + if (p_event.type == KeyEvent::MessageType::CHAR_EVENT_MESSAGE && key_event_pos > 0) { + KeyEvent &old = key_event_buffer[key_event_pos - 1]; + ERR_FAIL_COND(old.type != KeyEvent::MessageType::KEY_EVENT_MESSAGE); + + key_event_buffer[key_event_pos - 1].unicode = p_event.unicode; + return; + } + + ERR_FAIL_COND(key_event_pos >= KEY_EVENT_BUFFER_SIZE); + + key_event_buffer[key_event_pos++] = p_event; +} + +void OS_UWP::set_cursor_shape(CursorShape p_shape) { + ERR_FAIL_INDEX(p_shape, CURSOR_MAX); + + if (cursor_shape == p_shape) + return; + + static const CoreCursorType uwp_cursors[CURSOR_MAX] = { + CoreCursorType::Arrow, + CoreCursorType::IBeam, + CoreCursorType::Hand, + CoreCursorType::Cross, + CoreCursorType::Wait, + CoreCursorType::Wait, + CoreCursorType::Arrow, + CoreCursorType::Arrow, + CoreCursorType::UniversalNo, + CoreCursorType::SizeNorthSouth, + CoreCursorType::SizeWestEast, + CoreCursorType::SizeNortheastSouthwest, + CoreCursorType::SizeNorthwestSoutheast, + CoreCursorType::SizeAll, + CoreCursorType::SizeNorthSouth, + CoreCursorType::SizeWestEast, + CoreCursorType::Help + }; + + CoreWindow::GetForCurrentThread()->PointerCursor = ref new CoreCursor(uwp_cursors[p_shape], 0); + + cursor_shape = p_shape; +} + +OS::CursorShape OS_UWP::get_cursor_shape() const { + return cursor_shape; +} + +void OS_UWP::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) { + // TODO +} + +Error OS_UWP::execute(const String &p_path, const List &p_arguments, bool p_blocking, ProcessID *r_child_id, String *r_pipe, int *r_exitcode, bool read_stderr, Mutex *p_pipe_mutex, bool p_open_console) { + return FAILED; +}; + +Error OS_UWP::kill(const ProcessID &p_pid) { + return FAILED; +}; + +bool OS_UWP::is_process_running(const ProcessID &p_pid) const { + return false; +} + +Error OS_UWP::set_cwd(const String &p_cwd) { + return FAILED; +} + +String OS_UWP::get_executable_path() const { + return ""; +} + +void OS_UWP::set_icon(const Ref &p_icon) { +} + +bool OS_UWP::has_environment(const String &p_var) const { + return false; +}; + +String OS_UWP::get_environment(const String &p_var) const { + return ""; +}; + +bool OS_UWP::set_environment(const String &p_var, const String &p_value) const { + return false; +} + +String OS_UWP::get_stdin_string() { + return String(); +} + +void OS_UWP::move_window_to_foreground() { +} + +Error OS_UWP::shell_open(String p_uri) { + return FAILED; +} + +String OS_UWP::get_locale() const { +#if WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP // this should work on phone 8.1, but it doesn't + return "en"; +#else + Platform::String ^ language = Windows::Globalization::Language::CurrentInputMethodLanguageTag; + return String(language->Data()).replace("-", "_"); +#endif +} + +void OS_UWP::release_rendering_thread() { + gl_context->release_current(); +} + +void OS_UWP::make_rendering_thread() { + gl_context->make_current(); +} + +void OS_UWP::swap_buffers() { + gl_context->swap_buffers(); +} + +bool OS_UWP::has_touchscreen_ui_hint() const { + TouchCapabilities ^ tc = ref new TouchCapabilities(); + return tc->TouchPresent != 0 || UIViewSettings::GetForCurrentView()->UserInteractionMode == UserInteractionMode::Touch; +} + +bool OS_UWP::has_virtual_keyboard() const { + return UIViewSettings::GetForCurrentView()->UserInteractionMode == UserInteractionMode::Touch; +} + +void OS_UWP::show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect, VirtualKeyboardType p_type, int p_max_input_length, int p_cursor_start, int p_cursor_end) { + InputPane ^ pane = InputPane::GetForCurrentView(); + pane->TryShow(); +} + +void OS_UWP::hide_virtual_keyboard() { + InputPane ^ pane = InputPane::GetForCurrentView(); + pane->TryHide(); +} + +static String format_error_message(DWORD id) { + LPWSTR messageBuffer = NULL; + size_t size = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, id, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&messageBuffer, 0, NULL); + + String msg = "Error " + itos(id) + ": " + String(messageBuffer, size); + + LocalFree(messageBuffer); + + return msg; +} + +Error OS_UWP::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) { + String full_path = "game/" + p_path; + p_library_handle = (void *)LoadPackagedLibrary(full_path.c_str(), 0); + ERR_FAIL_COND_V_MSG(!p_library_handle, ERR_CANT_OPEN, "Can't open dynamic library: " + full_path + ", error: " + format_error_message(GetLastError()) + "."); + return OK; +} + +Error OS_UWP::close_dynamic_library(void *p_library_handle) { + if (!FreeLibrary((HMODULE)p_library_handle)) { + return FAILED; + } + return OK; +} + +Error OS_UWP::get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional) { + p_symbol_handle = (void *)GetProcAddress((HMODULE)p_library_handle, p_name.utf8().get_data()); + if (!p_symbol_handle) { + if (!p_optional) { + ERR_FAIL_V_MSG(ERR_CANT_RESOLVE, "Can't resolve symbol " + p_name + ", error: " + String::num(GetLastError()) + "."); + } else { + return ERR_CANT_RESOLVE; + } + } + return OK; +} + +void OS_UWP::run() { + if (!main_loop) + return; + + main_loop->init(); + + uint64_t last_ticks = get_ticks_usec(); + + int frames = 0; + uint64_t frame = 0; + + while (!force_quit) { + CoreWindow::GetForCurrentThread()->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent); + if (managed_object->alert_close_handle) + continue; + process_events(); // get rid of pending events + if (Main::iteration()) + break; + }; + + main_loop->finish(); +} + +MainLoop *OS_UWP::get_main_loop() const { + return main_loop; +} + +String OS_UWP::get_user_data_dir() const { + Windows::Storage::StorageFolder ^ data_folder = Windows::Storage::ApplicationData::Current->LocalFolder; + + return String(data_folder->Path->Data()).replace("\\", "/"); +} + +bool OS_UWP::_check_internal_feature_support(const String &p_feature) { + return p_feature == "pc"; +} + +OS::PowerState OS_UWP::get_power_state() { + return power_manager->get_power_state(); +} + +int OS_UWP::get_power_seconds_left() { + return power_manager->get_power_seconds_left(); +} + +int OS_UWP::get_power_percent_left() { + return power_manager->get_power_percent_left(); +} + +OS_UWP::OS_UWP() { + key_event_pos = 0; + force_quit = false; + alt_mem = false; + gr_mem = false; + shift_mem = false; + control_mem = false; + meta_mem = false; + minimized = false; + + pressrc = 0; + old_invalid = true; + mouse_mode = MOUSE_MODE_VISIBLE; +#ifdef STDOUT_FILE + stdo = fopen("stdout.txt", "wb"); +#endif + + gl_context = NULL; + + display_request = ref new Windows::System::Display::DisplayRequest(); + + managed_object = ref new ManagedType; + managed_object->os = this; + + mouse_mode_changed = CreateEvent(NULL, TRUE, FALSE, L"os_mouse_mode_changed"); + + AudioDriverManager::add_driver(&audio_driver); + + Vector loggers; + loggers.push_back(memnew(WindowsTerminalLogger)); + _set_logger(memnew(CompositeLogger(loggers))); +} + +OS_UWP::~OS_UWP() { +#ifdef STDOUT_FILE + fclose(stdo); +#endif +} diff --git a/platform/uwp/os_uwp.h b/platform/uwp/os_uwp.h new file mode 100644 index 000000000..6b5e4c653 --- /dev/null +++ b/platform/uwp/os_uwp.h @@ -0,0 +1,265 @@ +/**************************************************************************/ +/* os_uwp.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef OS_UWP_H +#define OS_UWP_H + +#include "context_egl_uwp.h" +#include "core/math/transform_2d.h" +#include "core/os/input.h" +#include "core/os/os.h" +#include "core/ustring.h" +#include "drivers/xaudio2/audio_driver_xaudio2.h" +#include "joypad_uwp.h" +#include "main/input_default.h" +#include "power_uwp.h" +#include "servers/audio_server.h" +#include "servers/visual/rasterizer.h" +#include "servers/visual_server.h" + +#include +#include +#include +#define WIN32_LEAN_AND_MEAN +#include + +class OS_UWP : public OS { +public: + struct KeyEvent { + enum MessageType { + KEY_EVENT_MESSAGE, + CHAR_EVENT_MESSAGE + }; + + bool alt, shift, control; + MessageType type; + bool pressed; + unsigned int scancode; + unsigned int physical_scancode; + unsigned int unicode; + bool echo; + CorePhysicalKeyStatus status; + }; + +private: + enum { + JOYPADS_MAX = 8, + JOY_AXIS_COUNT = 6, + MAX_JOY_AXIS = 32768, // I've no idea + KEY_EVENT_BUFFER_SIZE = 512 + }; + + FILE *stdo; + + KeyEvent key_event_buffer[KEY_EVENT_BUFFER_SIZE]; + int key_event_pos; + + uint64_t ticks_start; + uint64_t ticks_per_second; + + bool minimized; + bool old_invalid; + bool outside; + int old_x, old_y; + Point2i center; + VisualServer *visual_server; + int pressrc; + + ContextEGL_UWP *gl_context; + Windows::UI::Core::CoreWindow ^ window; + + VideoMode video_mode; + int video_driver_index; + + MainLoop *main_loop; + + AudioDriverXAudio2 audio_driver; + + PowerUWP *power_manager; + + MouseMode mouse_mode; + bool alt_mem; + bool gr_mem; + bool shift_mem; + bool control_mem; + bool meta_mem; + bool force_quit; + uint32_t last_button_state; + + CursorShape cursor_shape; + + InputDefault *input; + + JoypadUWP ^ joypad; + + Windows::System::Display::DisplayRequest ^ display_request; + + ref class ManagedType { + public: + property bool alert_close_handle; + property Platform::String ^ clipboard; + void alert_close(Windows::UI::Popups::IUICommand ^ command); + void on_clipboard_changed(Platform::Object ^ sender, Platform::Object ^ ev); + void update_clipboard(); + void on_accelerometer_reading_changed(Windows::Devices::Sensors::Accelerometer ^ sender, Windows::Devices::Sensors::AccelerometerReadingChangedEventArgs ^ args); + void on_magnetometer_reading_changed(Windows::Devices::Sensors::Magnetometer ^ sender, Windows::Devices::Sensors::MagnetometerReadingChangedEventArgs ^ args); + void on_gyroscope_reading_changed(Windows::Devices::Sensors::Gyrometer ^ sender, Windows::Devices::Sensors::GyrometerReadingChangedEventArgs ^ args); + + /** clang-format breaks this, it does not understand this token. */ + /* clang-format off */ + internal: + ManagedType() { alert_close_handle = false; } + property OS_UWP* os; + /* clang-format on */ + }; + ManagedType ^ managed_object; + Windows::Devices::Sensors::Accelerometer ^ accelerometer; + Windows::Devices::Sensors::Magnetometer ^ magnetometer; + Windows::Devices::Sensors::Gyrometer ^ gyrometer; + + // functions used by main to initialize/deinitialize the OS +protected: + virtual int get_video_driver_count() const; + virtual int get_current_video_driver() const; + + virtual void initialize_core(); + virtual Error initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver); + + virtual void set_main_loop(MainLoop *p_main_loop); + virtual void delete_main_loop(); + + virtual void finalize(); + virtual void finalize_core(); + + void process_events(); + + void process_key_events(); + +public: + // Event to send to the app wrapper + HANDLE mouse_mode_changed; + + virtual void alert(const String &p_alert, const String &p_title = "ALERT!"); + String get_stdin_string(); + + void set_mouse_mode(MouseMode p_mode); + MouseMode get_mouse_mode() const; + + virtual Point2 get_mouse_position() const; + virtual int get_mouse_button_state() const; + virtual void set_window_title(const String &p_title); + + virtual void set_video_mode(const VideoMode &p_video_mode, int p_screen = 0); + virtual VideoMode get_video_mode(int p_screen = 0) const; + virtual void get_fullscreen_mode_list(List *p_list, int p_screen = 0) const; + virtual Size2 get_window_size() const; + virtual void set_window_size(const Size2 p_size); + virtual void set_window_fullscreen(bool p_enabled); + virtual bool is_window_fullscreen() const; + virtual void set_keep_screen_on(bool p_enabled); + + virtual MainLoop *get_main_loop() const; + + virtual String get_name() const; + + virtual Date get_date(bool utc) const; + virtual Time get_time(bool utc) const; + virtual TimeZoneInfo get_time_zone_info() const; + virtual uint64_t get_unix_time() const; + virtual double get_subsecond_unix_time() const; + + virtual bool can_draw() const; + virtual Error set_cwd(const String &p_cwd); + + virtual void delay_usec(uint32_t p_usec) const; + virtual uint64_t get_ticks_usec() const; + + virtual Error execute(const String &p_path, const List &p_arguments, bool p_blocking = true, ProcessID *r_child_id = NULL, String *r_pipe = NULL, int *r_exitcode = NULL, bool read_stderr = false, Mutex *p_pipe_mutex = NULL, bool p_open_console = false); + virtual Error kill(const ProcessID &p_pid); + virtual bool is_process_running(const ProcessID &p_pid) const; + + virtual bool has_environment(const String &p_var) const; + virtual String get_environment(const String &p_var) const; + virtual bool set_environment(const String &p_var, const String &p_value) const; + + virtual void set_clipboard(const String &p_text); + virtual String get_clipboard() const; + + void set_cursor_shape(CursorShape p_shape); + CursorShape get_cursor_shape() const; + virtual void set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot); + void set_icon(const Ref &p_icon); + + virtual String get_executable_path() const; + + virtual String get_locale() const; + + virtual void move_window_to_foreground(); + virtual String get_user_data_dir() const; + + virtual bool _check_internal_feature_support(const String &p_feature); + + void update_clipboard(); + void set_window(Windows::UI::Core::CoreWindow ^ p_window); + void screen_size_changed(); + + virtual void release_rendering_thread(); + virtual void make_rendering_thread(); + virtual void swap_buffers(); + + virtual bool has_touchscreen_ui_hint() const; + + virtual bool has_virtual_keyboard() const; + virtual void show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), VirtualKeyboardType p_type = KEYBOARD_TYPE_DEFAULT, int p_max_input_length = -1, int p_cursor_start = -1, int p_cursor_end = -1); + virtual void hide_virtual_keyboard(); + + virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false); + virtual Error close_dynamic_library(void *p_library_handle); + virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional = false); + + virtual Error shell_open(String p_uri); + + void run(); + + virtual bool get_swap_ok_cancel() { return true; } + + void input_event(const Ref &p_event); + + virtual OS::PowerState get_power_state(); + virtual int get_power_seconds_left(); + virtual int get_power_percent_left(); + + void queue_key_event(KeyEvent &p_event); + + OS_UWP(); + ~OS_UWP(); +}; + +#endif // OS_UWP_H diff --git a/platform/uwp/platform_config.h b/platform/uwp/platform_config.h new file mode 100644 index 000000000..964e341ce --- /dev/null +++ b/platform/uwp/platform_config.h @@ -0,0 +1,31 @@ +/**************************************************************************/ +/* platform_config.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include diff --git a/platform/uwp/power_uwp.cpp b/platform/uwp/power_uwp.cpp new file mode 100644 index 000000000..263b036dd --- /dev/null +++ b/platform/uwp/power_uwp.cpp @@ -0,0 +1,77 @@ +/**************************************************************************/ +/* power_uwp.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "power_uwp.h" + +PowerUWP::PowerUWP() : + nsecs_left(-1), + percent_left(-1), + power_state(OS::POWERSTATE_UNKNOWN) { +} + +PowerUWP::~PowerUWP() { +} + +bool PowerUWP::UpdatePowerInfo() { + // TODO, WinRT: Battery info is available on at least one WinRT platform (Windows Phone 8). Implement UpdatePowerInfo as appropriate. */ + /* Notes from SDL: + * - the Win32 function, GetSystemPowerStatus, is not available for use on WinRT + * - Windows Phone 8 has a 'Battery' class, which is documented as available for C++ + * - More info on WP8's Battery class can be found at http://msdn.microsoft.com/library/windowsphone/develop/jj207231 + */ + return false; +} + +OS::PowerState PowerUWP::get_power_state() { + if (UpdatePowerInfo()) { + return power_state; + } else { + WARN_PRINT("Power management is not implemented on this platform, defaulting to POWERSTATE_UNKNOWN"); + return OS::POWERSTATE_UNKNOWN; + } +} + +int PowerUWP::get_power_seconds_left() { + if (UpdatePowerInfo()) { + return nsecs_left; + } else { + WARN_PRINT("Power management is not implemented on this platform, defaulting to -1"); + return -1; + } +} + +int PowerUWP::get_power_percent_left() { + if (UpdatePowerInfo()) { + return percent_left; + } else { + WARN_PRINT("Power management is not implemented on this platform, defaulting to -1"); + return -1; + } +} diff --git a/platform/uwp/power_uwp.h b/platform/uwp/power_uwp.h new file mode 100644 index 000000000..84b2a5bc8 --- /dev/null +++ b/platform/uwp/power_uwp.h @@ -0,0 +1,55 @@ +/**************************************************************************/ +/* power_uwp.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef POWER_UWP_H +#define POWER_UWP_H + +#include "core/os/dir_access.h" +#include "core/os/file_access.h" +#include "core/os/os.h" + +class PowerUWP { +private: + int nsecs_left; + int percent_left; + OS::PowerState power_state; + + bool UpdatePowerInfo(); + +public: + PowerUWP(); + virtual ~PowerUWP(); + + OS::PowerState get_power_state(); + int get_power_seconds_left(); + int get_power_percent_left(); +}; + +#endif // POWER_UWP_H