mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2026-07-10 02:04:42 -06:00
* initial shadnet client (based on my previous prs) * some np_handler work mostly based on our previous pr * improved login/logout event of shadnet * added np_score support from previous pr * friends and notifications ui * clang is not my friend * clang fix again * added webapi support for shadnet * applied metrik's fix * clang * fixed some wrong LOG_ERRORS * updated emulator settings * trying to fix npcommid bugs * fixed npcommid * improved NPcommid detection so it won't write null values to database * metrik's suggestion * removed one line * more command types * fixed duplicated log print * fixed compile * improved reconnection retries * simplify error code * stephen suggestion * made friends hotkey * fixup * more proper handling of reconnecting * fixed return code
505 lines
18 KiB
C++
505 lines
18 KiB
C++
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#include <SDL3/SDL_events.h>
|
|
#include <SDL3/SDL_hints.h>
|
|
#include <SDL3/SDL_init.h>
|
|
#include <SDL3/SDL_properties.h>
|
|
#include <SDL3/SDL_timer.h>
|
|
#include <SDL3/SDL_video.h>
|
|
#include <cmrc/cmrc.hpp>
|
|
#include <stb_image.h>
|
|
|
|
#include "common/assert.h"
|
|
#include "common/elf_info.h"
|
|
#include "common/io_file.h"
|
|
#include "common/logging/formatter.h"
|
|
#include "common/scope_exit.h"
|
|
#include "core/debug_state.h"
|
|
#include "core/devtools/layer.h"
|
|
#include "core/emulator_settings.h"
|
|
#include "core/libraries/kernel/time.h"
|
|
#include "core/libraries/pad/pad.h"
|
|
#include "core/libraries/system/userservice.h"
|
|
#include "core/user_settings.h"
|
|
#include "imgui/friends_layer.h"
|
|
#include "imgui/renderer/imgui_core.h"
|
|
#include "input/controller.h"
|
|
#include "input/input_handler.h"
|
|
#include "input/input_mouse.h"
|
|
#include "sdl_window.h"
|
|
#include "video_core/renderdoc.h"
|
|
|
|
#ifdef __APPLE__
|
|
#include <SDL3/SDL_metal.h>
|
|
#endif
|
|
#include <core/emulator_settings.h>
|
|
#include "core/libraries/mouse/sdl_mouse.h"
|
|
|
|
CMRC_DECLARE(res);
|
|
|
|
namespace Frontend {
|
|
|
|
using namespace Libraries::Pad;
|
|
|
|
static OrbisPadButtonDataOffset SDLGamepadToOrbisButton(u8 button) {
|
|
using OPBDO = OrbisPadButtonDataOffset;
|
|
|
|
switch (button) {
|
|
case SDL_GAMEPAD_BUTTON_DPAD_DOWN:
|
|
return OPBDO::Down;
|
|
case SDL_GAMEPAD_BUTTON_DPAD_UP:
|
|
return OPBDO::Up;
|
|
case SDL_GAMEPAD_BUTTON_DPAD_LEFT:
|
|
return OPBDO::Left;
|
|
case SDL_GAMEPAD_BUTTON_DPAD_RIGHT:
|
|
return OPBDO::Right;
|
|
case SDL_GAMEPAD_BUTTON_SOUTH:
|
|
return OPBDO::Cross;
|
|
case SDL_GAMEPAD_BUTTON_NORTH:
|
|
return OPBDO::Triangle;
|
|
case SDL_GAMEPAD_BUTTON_WEST:
|
|
return OPBDO::Square;
|
|
case SDL_GAMEPAD_BUTTON_EAST:
|
|
return OPBDO::Circle;
|
|
case SDL_GAMEPAD_BUTTON_START:
|
|
return OPBDO::Options;
|
|
case SDL_GAMEPAD_BUTTON_TOUCHPAD:
|
|
return OPBDO::TouchPad;
|
|
case SDL_GAMEPAD_BUTTON_BACK:
|
|
return OPBDO::TouchPad;
|
|
case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER:
|
|
return OPBDO::L1;
|
|
case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER:
|
|
return OPBDO::R1;
|
|
case SDL_GAMEPAD_BUTTON_LEFT_STICK:
|
|
return OPBDO::L3;
|
|
case SDL_GAMEPAD_BUTTON_RIGHT_STICK:
|
|
return OPBDO::R3;
|
|
default:
|
|
return OPBDO::None;
|
|
}
|
|
}
|
|
|
|
static Uint32 SDLCALL PollController(void* userdata, SDL_TimerID timer_id, Uint32 interval) {
|
|
auto* controller = reinterpret_cast<Input::GameController*>(userdata);
|
|
controller->UpdateAxisSmoothing();
|
|
controller->Gyro(0);
|
|
controller->Acceleration(0);
|
|
return interval;
|
|
}
|
|
|
|
static Uint32 SDLCALL PollControllerLightColour(void* userdata, SDL_TimerID timer_id,
|
|
Uint32 interval) {
|
|
auto* controller = reinterpret_cast<Input::GameController*>(userdata);
|
|
controller->PollLightColour();
|
|
return interval;
|
|
}
|
|
|
|
WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameControllers* controllers_,
|
|
std::string_view window_title)
|
|
: width{width_}, height{height_}, controllers{*controllers_} {
|
|
if (!SDL_SetHint(SDL_HINT_APP_NAME, "shadPS4")) {
|
|
UNREACHABLE_MSG("Failed to set SDL window hint: {}", SDL_GetError());
|
|
}
|
|
if (!SDL_Init(SDL_INIT_VIDEO)) {
|
|
UNREACHABLE_MSG("Failed to initialize SDL video subsystem: {}", SDL_GetError());
|
|
}
|
|
if (!SDL_Init(SDL_INIT_CAMERA)) {
|
|
LOG_ERROR(Input, "Failed to initialize SDL camera subsystem: {}", SDL_GetError());
|
|
}
|
|
SDL_InitSubSystem(SDL_INIT_AUDIO);
|
|
|
|
SDL_PropertiesID props = SDL_CreateProperties();
|
|
SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING,
|
|
std::string(window_title).c_str());
|
|
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, SDL_WINDOWPOS_CENTERED);
|
|
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, SDL_WINDOWPOS_CENTERED);
|
|
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, width);
|
|
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, height);
|
|
SDL_SetNumberProperty(props, "flags", SDL_WINDOW_VULKAN);
|
|
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_RESIZABLE_BOOLEAN, true);
|
|
window = SDL_CreateWindowWithProperties(props);
|
|
SDL_DestroyProperties(props);
|
|
if (window == nullptr) {
|
|
UNREACHABLE_MSG("Failed to create window handle: {}", SDL_GetError());
|
|
}
|
|
|
|
SDL_SetWindowMinimumSize(window, 640, 360);
|
|
|
|
bool error = false;
|
|
const SDL_DisplayID displayIndex = SDL_GetDisplayForWindow(window);
|
|
if (displayIndex < 0) {
|
|
LOG_ERROR(Frontend, "Error getting display index: {}", SDL_GetError());
|
|
error = true;
|
|
}
|
|
const SDL_DisplayMode* displayMode;
|
|
if ((displayMode = SDL_GetCurrentDisplayMode(displayIndex)) == 0) {
|
|
LOG_ERROR(Frontend, "Error getting display mode: {}", SDL_GetError());
|
|
error = true;
|
|
}
|
|
if (!error) {
|
|
SDL_SetWindowFullscreenMode(
|
|
window, EmulatorSettings.GetFullScreenMode() == "Fullscreen" ? displayMode : NULL);
|
|
}
|
|
SDL_SetWindowFullscreen(window, EmulatorSettings.IsFullScreen());
|
|
SDL_SyncWindow(window);
|
|
|
|
SDL_InitSubSystem(SDL_INIT_GAMEPAD);
|
|
|
|
#if defined(SDL_PLATFORM_WIN32)
|
|
window_info.type = WindowSystemType::Windows;
|
|
window_info.render_surface = SDL_GetPointerProperty(SDL_GetWindowProperties(window),
|
|
SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL);
|
|
#elif defined(SDL_PLATFORM_LINUX) || defined(__FreeBSD__)
|
|
// SDL doesn't have a platform define for FreeBSD AAAAAAAAAA
|
|
if (SDL_strcmp(SDL_GetCurrentVideoDriver(), "x11") == 0) {
|
|
window_info.type = WindowSystemType::X11;
|
|
window_info.display_connection = SDL_GetPointerProperty(
|
|
SDL_GetWindowProperties(window), SDL_PROP_WINDOW_X11_DISPLAY_POINTER, NULL);
|
|
window_info.render_surface = (void*)SDL_GetNumberProperty(
|
|
SDL_GetWindowProperties(window), SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0);
|
|
} else if (SDL_strcmp(SDL_GetCurrentVideoDriver(), "wayland") == 0) {
|
|
window_info.type = WindowSystemType::Wayland;
|
|
window_info.display_connection = SDL_GetPointerProperty(
|
|
SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WAYLAND_DISPLAY_POINTER, NULL);
|
|
window_info.render_surface = SDL_GetPointerProperty(
|
|
SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, NULL);
|
|
}
|
|
#elif defined(SDL_PLATFORM_MACOS)
|
|
window_info.type = WindowSystemType::Metal;
|
|
window_info.render_surface = SDL_Metal_GetLayer(SDL_Metal_CreateView(window));
|
|
#endif
|
|
// input handler init-s
|
|
Input::ControllerOutput::LinkJoystickAxes();
|
|
Input::ParseInputConfig(std::string(Common::ElfInfo::Instance().GameSerial()));
|
|
controllers.TryOpenSDLControllers();
|
|
|
|
if (EmulatorSettings.IsBackgroundControllerInput()) {
|
|
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
|
|
}
|
|
}
|
|
|
|
WindowSDL::~WindowSDL() = default;
|
|
|
|
void WindowSDL::SetIcon(const std::filesystem::path& path) {
|
|
if (!std::filesystem::exists(path)) {
|
|
LOG_WARNING(Core, "Could not find icon file '{}', using default icon.",
|
|
fmt::UTF(path.u8string()));
|
|
SetDefaultWindowIcon(window);
|
|
return;
|
|
}
|
|
|
|
Common::FS::IOFile file{path, Common::FS::FileAccessMode::Read,
|
|
Common::FS::FileType::BinaryFile,
|
|
Common::FS::FileShareFlag::ShareReadWrite};
|
|
if (!file.IsOpen()) {
|
|
LOG_ERROR(Core, "Failed to open window icon file '{}'.", fmt::UTF(path.u8string()));
|
|
SetDefaultWindowIcon(window);
|
|
return;
|
|
}
|
|
|
|
const u64 fileSize = file.GetSize();
|
|
std::vector<u8> buf(fileSize);
|
|
const size_t bytesRead = file.ReadRaw<u8>(buf.data(), fileSize);
|
|
file.Close();
|
|
if (bytesRead < fileSize) {
|
|
LOG_ERROR(Core, "Failed to read window icon file '{}'.", fmt::UTF(path.u8string()));
|
|
SetDefaultWindowIcon(window);
|
|
return;
|
|
}
|
|
|
|
SetWindowIcon(window, buf);
|
|
}
|
|
|
|
void WindowSDL::WaitEvent() {
|
|
// Called on main thread
|
|
SDL_Event event;
|
|
|
|
if (!SDL_WaitEvent(&event)) {
|
|
return;
|
|
}
|
|
|
|
if (Libraries::Mouse::PushSDLEvent(event)) {
|
|
return;
|
|
}
|
|
|
|
if (ImGui::Core::ProcessEvent(&event)) {
|
|
return;
|
|
}
|
|
|
|
switch (event.type) {
|
|
case SDL_EVENT_WINDOW_RESIZED:
|
|
case SDL_EVENT_WINDOW_MAXIMIZED:
|
|
case SDL_EVENT_WINDOW_RESTORED:
|
|
OnResize();
|
|
break;
|
|
case SDL_EVENT_WINDOW_MINIMIZED:
|
|
case SDL_EVENT_WINDOW_EXPOSED:
|
|
is_shown = event.type == SDL_EVENT_WINDOW_EXPOSED;
|
|
OnResize();
|
|
break;
|
|
case SDL_EVENT_MOUSE_BUTTON_DOWN:
|
|
case SDL_EVENT_MOUSE_BUTTON_UP:
|
|
case SDL_EVENT_MOUSE_WHEEL:
|
|
case SDL_EVENT_MOUSE_WHEEL_OFF:
|
|
case SDL_EVENT_KEY_DOWN:
|
|
case SDL_EVENT_KEY_UP:
|
|
OnKeyboardMouseInput(&event);
|
|
break;
|
|
case SDL_EVENT_GAMEPAD_ADDED:
|
|
case SDL_EVENT_GAMEPAD_REMOVED:
|
|
controllers.TryOpenSDLControllers();
|
|
break;
|
|
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
|
|
case SDL_EVENT_GAMEPAD_BUTTON_UP:
|
|
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
|
|
case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN:
|
|
case SDL_EVENT_GAMEPAD_TOUCHPAD_UP:
|
|
case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION:
|
|
case SDL_EVENT_GAMEPAD_SENSOR_UPDATE:
|
|
OnGamepadEvent(&event);
|
|
break;
|
|
case SDL_EVENT_QUIT:
|
|
is_open = false;
|
|
break;
|
|
case SDL_EVENT_QUIT_DIALOG:
|
|
Overlay::ToggleQuitWindow();
|
|
break;
|
|
case SDL_EVENT_TOGGLE_FULLSCREEN: {
|
|
if (SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN) {
|
|
SDL_SetWindowFullscreen(window, 0);
|
|
} else {
|
|
SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN);
|
|
}
|
|
break;
|
|
}
|
|
case SDL_EVENT_TOGGLE_PAUSE:
|
|
if (DebugState.IsGuestThreadsPaused()) {
|
|
LOG_INFO(Frontend, "Game Resumed");
|
|
DebugState.ResumeGuestThreads();
|
|
} else {
|
|
LOG_INFO(Frontend, "Game Paused");
|
|
DebugState.PauseGuestThreads();
|
|
}
|
|
break;
|
|
case SDL_EVENT_CHANGE_CONTROLLER:
|
|
UNREACHABLE_MSG("todo");
|
|
break;
|
|
case SDL_EVENT_TOGGLE_SIMPLE_FPS:
|
|
Overlay::ToggleSimpleFps();
|
|
break;
|
|
case SDL_EVENT_TOGGLE_FRIENDS:
|
|
ImGui::Friends::Toggle();
|
|
break;
|
|
case SDL_EVENT_RELOAD_INPUTS:
|
|
Input::ParseInputConfig(std::string(Common::ElfInfo::Instance().GameSerial()));
|
|
break;
|
|
case SDL_EVENT_MOUSE_TO_JOYSTICK:
|
|
SDL_SetWindowRelativeMouseMode(this->GetSDLWindow(),
|
|
Input::ToggleMouseModeTo(Input::MouseMode::Joystick));
|
|
break;
|
|
case SDL_EVENT_MOUSE_TO_GYRO:
|
|
SDL_SetWindowRelativeMouseMode(this->GetSDLWindow(),
|
|
Input::ToggleMouseModeTo(Input::MouseMode::Gyro));
|
|
break;
|
|
case SDL_EVENT_MOUSE_TO_TOUCHPAD:
|
|
SDL_SetWindowRelativeMouseMode(this->GetSDLWindow(),
|
|
Input::ToggleMouseModeTo(Input::MouseMode::Touchpad));
|
|
SDL_SetWindowRelativeMouseMode(this->GetSDLWindow(), false);
|
|
break;
|
|
case SDL_EVENT_ADD_VIRTUAL_USER:
|
|
for (int i = 0; i < 4; i++) {
|
|
if (controllers[i]->user_id == -1) {
|
|
auto u = UserManagement.GetUserByPlayerIndex(i + 1);
|
|
if (!u) {
|
|
break;
|
|
}
|
|
controllers[i]->user_id = u->user_id;
|
|
controllers[i]->ConnectController(controllers[i]->m_sdl_gamepad);
|
|
UserManagement.LoginUser(u, i + 1);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case SDL_EVENT_REMOVE_VIRTUAL_USER:
|
|
LOG_INFO(Input, "Remove user");
|
|
for (int i = 3; i >= 0; i--) {
|
|
if (controllers[i]->user_id != -1) {
|
|
UserManagement.LogoutUser(UserManagement.GetUserByID(controllers[i]->user_id));
|
|
controllers[i]->DisconnectController();
|
|
controllers[i]->user_id = -1;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case SDL_EVENT_RDOC_CAPTURE:
|
|
if (VideoCore::IsRenderDocLoaded()) {
|
|
VideoCore::TriggerCapture();
|
|
} else {
|
|
VideoCore::RequestScreenshot(VideoCore::ScreenshotRequest::GameOnly);
|
|
}
|
|
break;
|
|
case SDL_EVENT_SCREENSHOT_WITH_OVERLAYS:
|
|
VideoCore::RequestScreenshot(VideoCore::ScreenshotRequest::WithOverlays);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void WindowSDL::InitTimers() {
|
|
for (int i = 0; i < 4; ++i) {
|
|
SDL_AddTimer(4, &PollController, controllers[i]);
|
|
}
|
|
SDL_AddTimer(33, Input::MousePolling, (void*)controllers[0]);
|
|
}
|
|
|
|
void WindowSDL::RequestKeyboard() {
|
|
if (keyboard_grab == 0) {
|
|
SDL_RunOnMainThread(
|
|
[](void* userdata) { SDL_StartTextInput(static_cast<SDL_Window*>(userdata)); }, window,
|
|
true);
|
|
}
|
|
keyboard_grab++;
|
|
}
|
|
|
|
void WindowSDL::ReleaseKeyboard() {
|
|
ASSERT(keyboard_grab > 0);
|
|
keyboard_grab--;
|
|
if (keyboard_grab == 0) {
|
|
SDL_RunOnMainThread(
|
|
[](void* userdata) { SDL_StopTextInput(static_cast<SDL_Window*>(userdata)); }, window,
|
|
true);
|
|
}
|
|
}
|
|
|
|
void WindowSDL::OnResize() {
|
|
SDL_GetWindowSizeInPixels(window, &width, &height);
|
|
ImGui::Core::OnResize();
|
|
}
|
|
|
|
Uint32 wheelOffCallback(void* og_event, Uint32 timer_id, Uint32 interval) {
|
|
SDL_Event off_event = *(SDL_Event*)og_event;
|
|
off_event.type = SDL_EVENT_MOUSE_WHEEL_OFF;
|
|
SDL_PushEvent(&off_event);
|
|
delete (SDL_Event*)og_event;
|
|
return 0;
|
|
}
|
|
|
|
void WindowSDL::OnKeyboardMouseInput(const SDL_Event* event) {
|
|
using Libraries::Pad::OrbisPadButtonDataOffset;
|
|
|
|
// get the event's id, if it's keyup or keydown
|
|
const bool input_down = event->type == SDL_EVENT_KEY_DOWN ||
|
|
event->type == SDL_EVENT_MOUSE_BUTTON_DOWN ||
|
|
event->type == SDL_EVENT_MOUSE_WHEEL;
|
|
Input::InputEvent input_event = Input::InputBinding::GetInputEventFromSDLEvent(*event);
|
|
|
|
// if it's a wheel event, make a timer that turns it off after a set time
|
|
if (event->type == SDL_EVENT_MOUSE_WHEEL) {
|
|
const SDL_Event* copy = new SDL_Event(*event);
|
|
SDL_AddTimer(33, wheelOffCallback, (void*)copy);
|
|
}
|
|
|
|
// add/remove it from the list
|
|
bool inputs_changed = Input::UpdatePressedKeys(input_event);
|
|
|
|
// update bindings
|
|
if (inputs_changed) {
|
|
Input::ActivateOutputsFromInputs();
|
|
}
|
|
}
|
|
|
|
void WindowSDL::OnGamepadEvent(const SDL_Event* event) {
|
|
bool input_down = event->type == SDL_EVENT_GAMEPAD_AXIS_MOTION ||
|
|
event->type == SDL_EVENT_GAMEPAD_BUTTON_DOWN;
|
|
Input::InputEvent input_event = Input::InputBinding::GetInputEventFromSDLEvent(*event);
|
|
|
|
// the touchpad button shouldn't be rebound to anything else,
|
|
// as it would break the entire touchpad handling
|
|
// You can still bind other things to it though
|
|
if (event->gbutton.button == SDL_GAMEPAD_BUTTON_TOUCHPAD) {
|
|
controllers[controllers.GetGamepadIndexFromJoystickId(event->gbutton.which)]->Button(
|
|
OrbisPadButtonDataOffset::TouchPad, input_down);
|
|
return;
|
|
}
|
|
|
|
u8 gamepad;
|
|
|
|
switch (event->type) {
|
|
case SDL_EVENT_GAMEPAD_SENSOR_UPDATE:
|
|
switch ((SDL_SensorType)event->gsensor.sensor) {
|
|
case SDL_SENSOR_GYRO:
|
|
gamepad = controllers.GetGamepadIndexFromJoystickId(event->gsensor.which);
|
|
if (gamepad < 5) {
|
|
controllers[gamepad]->UpdateGyro(event->gsensor.data);
|
|
}
|
|
break;
|
|
case SDL_SENSOR_ACCEL:
|
|
gamepad = controllers.GetGamepadIndexFromJoystickId(event->gsensor.which);
|
|
if (gamepad < 5) {
|
|
controllers[gamepad]->UpdateAcceleration(event->gsensor.data);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return;
|
|
case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN:
|
|
case SDL_EVENT_GAMEPAD_TOUCHPAD_UP:
|
|
case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION:
|
|
controllers[controllers.GetGamepadIndexFromJoystickId(event->gtouchpad.which)]
|
|
->SetTouchpadState(event->gtouchpad.finger,
|
|
event->type != SDL_EVENT_GAMEPAD_TOUCHPAD_UP, event->gtouchpad.x,
|
|
event->gtouchpad.y);
|
|
return;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// add/remove it from the list
|
|
bool inputs_changed = Input::UpdatePressedKeys(input_event);
|
|
|
|
if (inputs_changed) {
|
|
// update bindings
|
|
Input::ActivateOutputsFromInputs();
|
|
}
|
|
}
|
|
|
|
#ifndef __APPLE__
|
|
void SetWindowIcon(SDL_Window* window, const std::vector<u8>& png) {
|
|
int imageWidth = 0;
|
|
int imageHeight = 0;
|
|
constexpr int numChannels = 4;
|
|
unsigned char* imageData = stbi_load_from_memory(png.data(), png.size(), &imageWidth,
|
|
&imageHeight, nullptr, numChannels);
|
|
if (imageData == nullptr) {
|
|
LOG_ERROR(Core, "Failed to load window icon image: {}", stbi_failure_reason());
|
|
return;
|
|
}
|
|
SCOPE_EXIT {
|
|
stbi_image_free(imageData);
|
|
};
|
|
|
|
SDL_Surface* surface = SDL_CreateSurfaceFrom(imageWidth, imageHeight, SDL_PIXELFORMAT_RGBA32,
|
|
imageData, imageWidth * numChannels);
|
|
if (surface == nullptr) {
|
|
LOG_ERROR(Core, "Failed to create SDL surface for window icon: {}", SDL_GetError());
|
|
}
|
|
if (!SDL_SetWindowIcon(window, surface)) {
|
|
LOG_ERROR(Core, "Failed to set SDL window icon: {}", SDL_GetError());
|
|
}
|
|
SDL_DestroySurface(surface);
|
|
}
|
|
#endif
|
|
|
|
void SetDefaultWindowIcon(SDL_Window* window) {
|
|
const auto resource = cmrc::res::get_filesystem();
|
|
const auto file = resource.open("src/resources/shadps4.png");
|
|
const std::vector<u8> texData = std::vector<u8>(file.begin(), file.end());
|
|
SetWindowIcon(window, texData);
|
|
}
|
|
|
|
} // namespace Frontend
|