Rewrite host GPU abstraction
- Don't have to repeat the same thing for 4 renderers. - Add native Metal renderer.
This commit is contained in:
@@ -67,14 +67,12 @@ if(USE_WAYLAND)
|
||||
)
|
||||
endif()
|
||||
|
||||
if(USE_DRMKMS AND USE_EVDEV)
|
||||
message(STATUS "Building VTY/DRM/KMS/EVDev NoGUI Platform.")
|
||||
target_compile_definitions(duckstation-nogui PRIVATE "NOGUI_PLATFORM_VTY=1" "WITH_DRMKMS=1")
|
||||
target_sources(duckstation-nogui PRIVATE
|
||||
vty_key_names.h
|
||||
vty_nogui_platform.cpp
|
||||
vty_nogui_platform.h
|
||||
if(APPLE)
|
||||
message(STATUS "Building Cocoa NoGUI Platform.")
|
||||
target_sources(duckstation-nogui PRIVATE
|
||||
cocoa_key_names.h
|
||||
cocoa_nogui_platform.mm
|
||||
cocoa_nogui_platform.h
|
||||
)
|
||||
target_include_directories(duckstation-nogui PRIVATE ${LIBEVDEV_INCLUDE_DIRS})
|
||||
target_link_libraries(duckstation-nogui PRIVATE ${LIBEVDEV_LIBRARIES})
|
||||
endif()
|
||||
|
||||
|
||||
133
src/duckstation-nogui/cocoa_key_names.h
Normal file
133
src/duckstation-nogui/cocoa_key_names.h
Normal file
@@ -0,0 +1,133 @@
|
||||
#pragma once
|
||||
#include "common/types.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
|
||||
#include <Carbon/Carbon.h>
|
||||
|
||||
namespace CocoaKeyNames {
|
||||
static const std::map<int, const char*> s_cocoa_key_names = {
|
||||
{kVK_Return, "Return"},
|
||||
{kVK_Escape, "Escape"},
|
||||
{kVK_Delete, "Backspace"},
|
||||
{kVK_Tab, "Tab"},
|
||||
{kVK_Space, "Space"},
|
||||
{kVK_ANSI_Quote, "Quote"},
|
||||
{kVK_ANSI_Comma, "Comma"},
|
||||
{kVK_ANSI_Minus, "Minus"},
|
||||
{kVK_ANSI_Period, "Period"},
|
||||
{kVK_ANSI_Slash, "Slash"},
|
||||
{kVK_ANSI_0, "0"},
|
||||
{kVK_ANSI_1, "1"},
|
||||
{kVK_ANSI_2, "2"},
|
||||
{kVK_ANSI_3, "3"},
|
||||
{kVK_ANSI_4, "4"},
|
||||
{kVK_ANSI_5, "5"},
|
||||
{kVK_ANSI_6, "6"},
|
||||
{kVK_ANSI_7, "7"},
|
||||
{kVK_ANSI_8, "8"},
|
||||
{kVK_ANSI_9, "9"},
|
||||
{kVK_ANSI_Semicolon, "Semcolon"},
|
||||
{kVK_ANSI_Equal, "Equal"},
|
||||
{kVK_ANSI_LeftBracket, "BracketLeft"},
|
||||
{kVK_ANSI_Backslash, "Backslash"},
|
||||
{kVK_ANSI_RightBracket, "BracketRight"},
|
||||
{kVK_ANSI_Grave, "Grave"},
|
||||
{kVK_ANSI_A, "A"},
|
||||
{kVK_ANSI_B, "B"},
|
||||
{kVK_ANSI_C, "C"},
|
||||
{kVK_ANSI_D, "D"},
|
||||
{kVK_ANSI_E, "E"},
|
||||
{kVK_ANSI_F, "F"},
|
||||
{kVK_ANSI_G, "G"},
|
||||
{kVK_ANSI_H, "H"},
|
||||
{kVK_ANSI_I, "I"},
|
||||
{kVK_ANSI_J, "J"},
|
||||
{kVK_ANSI_K, "K"},
|
||||
{kVK_ANSI_L, "L"},
|
||||
{kVK_ANSI_M, "M"},
|
||||
{kVK_ANSI_N, "N"},
|
||||
{kVK_ANSI_O, "O"},
|
||||
{kVK_ANSI_P, "P"},
|
||||
{kVK_ANSI_Q, "Q"},
|
||||
{kVK_ANSI_R, "R"},
|
||||
{kVK_ANSI_S, "S"},
|
||||
{kVK_ANSI_T, "T"},
|
||||
{kVK_ANSI_U, "U"},
|
||||
{kVK_ANSI_V, "V"},
|
||||
{kVK_ANSI_W, "W"},
|
||||
{kVK_ANSI_X, "X"},
|
||||
{kVK_ANSI_Y, "Y"},
|
||||
{kVK_ANSI_Z, "Z"},
|
||||
{kVK_CapsLock, "CapsLock"},
|
||||
{kVK_F1, "F1"},
|
||||
{kVK_F2, "F2"},
|
||||
{kVK_F3, "F3"},
|
||||
{kVK_F4, "F4"},
|
||||
{kVK_F5, "F5"},
|
||||
{kVK_F6, "F6"},
|
||||
{kVK_F7, "F7"},
|
||||
{kVK_F8, "F8"},
|
||||
{kVK_F9, "F9"},
|
||||
{kVK_F10, "F10"},
|
||||
{kVK_F11, "F11"},
|
||||
{kVK_F12, "F12"},
|
||||
{kVK_Home, "Home"},
|
||||
{kVK_PageUp, "PageUp"},
|
||||
{kVK_End, "End"},
|
||||
{kVK_PageDown, "PageDown"},
|
||||
{kVK_RightArrow, "Right"},
|
||||
{kVK_LeftArrow, "Left"},
|
||||
{kVK_DownArrow, "Down"},
|
||||
{kVK_UpArrow, "Up"},
|
||||
{kVK_ANSI_KeypadDivide, "KeypadDivide"},
|
||||
{kVK_ANSI_KeypadMultiply, "KeypadMultiply"},
|
||||
{kVK_ANSI_KeypadMinus, "KeypadMinus"},
|
||||
{kVK_ANSI_KeypadPlus, "KeypadPlus"},
|
||||
{kVK_ANSI_KeypadEnter, "KeypadReturn"},
|
||||
{kVK_ANSI_Keypad1, "Keypad1"},
|
||||
{kVK_ANSI_Keypad2, "Keypad2"},
|
||||
{kVK_ANSI_Keypad3, "Keypad3"},
|
||||
{kVK_ANSI_Keypad4, "Keypad4"},
|
||||
{kVK_ANSI_Keypad5, "Keypad5"},
|
||||
{kVK_ANSI_Keypad6, "Keypad6"},
|
||||
{kVK_ANSI_Keypad7, "Keypad7"},
|
||||
{kVK_ANSI_Keypad8, "Keypad8"},
|
||||
{kVK_ANSI_Keypad9, "Keypad9"},
|
||||
{kVK_ANSI_Keypad0, "Keypad0"},
|
||||
{kVK_ANSI_KeypadDecimal, "KeypadPeriod"},
|
||||
{kVK_F13, "F13"},
|
||||
{kVK_F14, "F14"},
|
||||
{kVK_F15, "F15"},
|
||||
{kVK_F16, "F16"},
|
||||
{kVK_F17, "F17"},
|
||||
{kVK_F18, "F18"},
|
||||
{kVK_F19, "F19"},
|
||||
{kVK_F20, "F20"},
|
||||
{kVK_Help, "Help"},
|
||||
{kVK_Option, "Alt"},
|
||||
{kVK_Command, "Super"},
|
||||
{kVK_Function, "Control"},
|
||||
};
|
||||
|
||||
static const char* GetKeyName(unsigned short key)
|
||||
{
|
||||
const auto it = s_cocoa_key_names.find(key);
|
||||
return it == s_cocoa_key_names.end() ? nullptr : it->second;
|
||||
}
|
||||
|
||||
static std::optional<unsigned short> GetKeyCodeForName(const std::string_view& key_name)
|
||||
{
|
||||
for (const auto& it : s_cocoa_key_names)
|
||||
{
|
||||
if (key_name == it.second)
|
||||
return it.first;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
} // namespace CocoaKeyNames
|
||||
@@ -1,20 +1,43 @@
|
||||
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||
|
||||
#pragma once
|
||||
#include "nogui_platform.h"
|
||||
#include <atomic>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <libevdev/libevdev.h>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
class VTYNoGUIPlatform : public NoGUIPlatform
|
||||
#include <AppKit/AppKit.h>
|
||||
#include <Cocoa/Cocoa.h>
|
||||
|
||||
#ifndef __OBJC__
|
||||
#error This file needs to be compiled with Objective C++.
|
||||
#endif
|
||||
|
||||
#if __has_feature(objc_arc)
|
||||
#error ARC should not be enabled.
|
||||
#endif
|
||||
|
||||
#include "nogui_platform.h"
|
||||
|
||||
#include <atomic>
|
||||
|
||||
@interface CocoaNoGUIView : NSView<NSWindowDelegate>
|
||||
- (BOOL)acceptsFirstResponder;
|
||||
- (BOOL)canBecomeKeyView;
|
||||
- (void)mouseDown:(NSEvent *)event;
|
||||
- (void)rightMouseDown:(NSEvent *)event;
|
||||
- (void)otherMouseDown:(NSEvent *)event;
|
||||
- (void)mouseUp:(NSEvent *)event;
|
||||
- (void)rightMouseUp:(NSEvent *)event;
|
||||
- (void)otherMouseUp:(NSEvent *)event;
|
||||
- (void)mouseMoved:(NSEvent *)event;
|
||||
- (void)keyDown:(NSEvent *)event;
|
||||
- (void)keyUp:(NSEvent *)event;
|
||||
- (void)windowDidEndLiveResize:(NSNotification *)notif;
|
||||
@end
|
||||
|
||||
class CocoaNoGUIPlatform : public NoGUIPlatform
|
||||
{
|
||||
public:
|
||||
VTYNoGUIPlatform();
|
||||
~VTYNoGUIPlatform();
|
||||
CocoaNoGUIPlatform();
|
||||
~CocoaNoGUIPlatform();
|
||||
|
||||
bool Initialize();
|
||||
|
||||
@@ -24,6 +47,7 @@ public:
|
||||
void SetDefaultConfig(SettingsInterface& si) override;
|
||||
|
||||
bool CreatePlatformWindow(std::string title) override;
|
||||
bool HasPlatformWindow() const override;
|
||||
void DestroyPlatformWindow() override;
|
||||
std::optional<WindowInfo> GetPlatformWindowInfo() override;
|
||||
void SetPlatformWindowTitle(std::string title) override;
|
||||
@@ -43,21 +67,8 @@ public:
|
||||
bool CopyTextToClipboard(const std::string_view& text) override;
|
||||
|
||||
private:
|
||||
void OpenEVDevFDs();
|
||||
void CloseEVDevFDs();
|
||||
void PollEvDevKeyboards();
|
||||
void SetImGuiKeyMap();
|
||||
NSWindow* m_window = nil;
|
||||
float m_window_scale = 1.0f;
|
||||
|
||||
struct EvDevKeyboard
|
||||
{
|
||||
struct libevdev* obj;
|
||||
int fd;
|
||||
};
|
||||
|
||||
std::vector<EvDevKeyboard> m_evdev_keyboards;
|
||||
|
||||
std::deque<std::function<void()>> m_callback_queue;
|
||||
std::mutex m_callback_queue_mutex;
|
||||
|
||||
std::atomic_bool m_message_loop_running{false};
|
||||
std::atomic_bool m_fullscreen{false};
|
||||
};
|
||||
305
src/duckstation-nogui/cocoa_nogui_platform.mm
Normal file
305
src/duckstation-nogui/cocoa_nogui_platform.mm
Normal file
@@ -0,0 +1,305 @@
|
||||
// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||
|
||||
#include "cocoa_nogui_platform.h"
|
||||
#include "cocoa_key_names.h"
|
||||
#include "nogui_host.h"
|
||||
|
||||
#include "core/host.h"
|
||||
#include "core/host_settings.h"
|
||||
|
||||
#include "util/cocoa_tools.h"
|
||||
#include "util/imgui_manager.h"
|
||||
|
||||
#include "common/log.h"
|
||||
#include "common/scoped_guard.h"
|
||||
#include "common/string_util.h"
|
||||
#include "common/threading.h"
|
||||
|
||||
Log_SetChannel(CocoaNoGUIPlatform);
|
||||
|
||||
constexpr NSWindowStyleMask WINDOWED_STYLE = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable;
|
||||
|
||||
@implementation CocoaNoGUIView
|
||||
|
||||
- (BOOL)acceptsFirstResponder {
|
||||
return YES;
|
||||
}
|
||||
- (BOOL)canBecomeKeyView {
|
||||
return YES;
|
||||
}
|
||||
- (void)viewDidEndLiveResize:(NSEvent *)event {
|
||||
[super viewDidEndLiveResize:event];
|
||||
}
|
||||
- (void)mouseDown:(NSEvent *)event {
|
||||
NoGUIHost::ProcessPlatformMouseButtonEvent(0, true);
|
||||
}
|
||||
- (void)rightMouseDown:(NSEvent *)event {
|
||||
NoGUIHost::ProcessPlatformMouseButtonEvent(1, true);
|
||||
}
|
||||
- (void)otherMouseDown:(NSEvent *)event {
|
||||
NoGUIHost::ProcessPlatformMouseButtonEvent(static_cast<s32>(event.buttonNumber), true);
|
||||
}
|
||||
|
||||
- (void)mouseUp:(NSEvent *)event {
|
||||
NoGUIHost::ProcessPlatformMouseButtonEvent(0, false);
|
||||
}
|
||||
- (void)rightMouseUp:(NSEvent *)event {
|
||||
NoGUIHost::ProcessPlatformMouseButtonEvent(1, false);
|
||||
}
|
||||
- (void)otherMouseUp:(NSEvent *)event {
|
||||
NoGUIHost::ProcessPlatformMouseButtonEvent(static_cast<s32>(event.buttonNumber), false);
|
||||
}
|
||||
|
||||
- (void)mouseMoved:(NSEvent *)event {
|
||||
// Flip for lower-left origin.
|
||||
const NSView* contentView = self;
|
||||
const NSPoint pt = [contentView convertPointToBacking:[event locationInWindow]];
|
||||
const NSSize size = [contentView convertSizeToBacking:contentView.frame.size];
|
||||
const float local_x = pt.x;
|
||||
const float local_y = size.height - pt.y;
|
||||
NoGUIHost::ProcessPlatformMouseMoveEvent(local_x, local_y);
|
||||
}
|
||||
|
||||
- (void)keyDown:(NSEvent *)event {
|
||||
[super keyDown:event];
|
||||
if (ImGuiManager::WantsTextInput() && event.characters && event.characters.length > 0)
|
||||
{
|
||||
ImGuiManager::AddTextInput([event.characters UTF8String]);
|
||||
}
|
||||
|
||||
if (!event.isARepeat)
|
||||
NoGUIHost::ProcessPlatformKeyEvent(static_cast<s32>(event.keyCode), true);
|
||||
}
|
||||
|
||||
- (void)keyUp:(NSEvent *)event {
|
||||
[super keyUp:event];
|
||||
NoGUIHost::ProcessPlatformKeyEvent(static_cast<s32>(event.keyCode), false);
|
||||
}
|
||||
|
||||
- (void)windowDidEndLiveResize:(NSNotification *)notif
|
||||
{
|
||||
const NSSize size = [self convertSizeToBacking:self.frame.size];
|
||||
NoGUIHost::ProcessPlatformWindowResize(static_cast<s32>(size.width), static_cast<s32>(size.height), 1.0f);
|
||||
}
|
||||
@end
|
||||
|
||||
CocoaNoGUIPlatform::CocoaNoGUIPlatform() = default;
|
||||
|
||||
CocoaNoGUIPlatform::~CocoaNoGUIPlatform()
|
||||
{
|
||||
if (m_window)
|
||||
{
|
||||
[m_window release];
|
||||
m_window = nil;
|
||||
}
|
||||
}
|
||||
|
||||
bool CocoaNoGUIPlatform::Initialize()
|
||||
{
|
||||
[NSApplication sharedApplication];
|
||||
|
||||
// Needed for keyboard in put.
|
||||
const ProcessSerialNumber psn = {0, kCurrentProcess};
|
||||
TransformProcessType(&psn, kProcessTransformToForegroundApplication);
|
||||
return true;
|
||||
}
|
||||
|
||||
void CocoaNoGUIPlatform::ReportError(const std::string_view& title, const std::string_view& message)
|
||||
{
|
||||
if (![NSThread isMainThread])
|
||||
{
|
||||
dispatch_sync(dispatch_get_main_queue(), [this, &title, &message]() { ReportError(title, message); });
|
||||
return;
|
||||
}
|
||||
|
||||
@autoreleasepool {
|
||||
NSAlert *alert = [[[NSAlert alloc] init] autorelease];
|
||||
[alert setMessageText: CocoaTools::StringViewToNSString(title)];
|
||||
[alert setInformativeText: CocoaTools::StringViewToNSString(message)];
|
||||
[alert runModal];
|
||||
}
|
||||
}
|
||||
|
||||
bool CocoaNoGUIPlatform::ConfirmMessage(const std::string_view& title, const std::string_view& message)
|
||||
{
|
||||
if (![NSThread isMainThread])
|
||||
{
|
||||
bool result = false;
|
||||
dispatch_sync(dispatch_get_main_queue(), [this, &title, &message, &result]() { result = ConfirmMessage(title, message); });
|
||||
return result;
|
||||
}
|
||||
|
||||
@autoreleasepool {
|
||||
NSAlert *alert = [[[NSAlert alloc] init] autorelease];
|
||||
[alert setMessageText: CocoaTools::StringViewToNSString(title)];
|
||||
[alert setInformativeText: CocoaTools::StringViewToNSString(message)];
|
||||
[alert addButtonWithTitle:@"Yes"];
|
||||
[alert addButtonWithTitle:@"No"];
|
||||
return ([alert runModal] == 0);
|
||||
}
|
||||
}
|
||||
|
||||
void CocoaNoGUIPlatform::SetDefaultConfig(SettingsInterface& si)
|
||||
{
|
||||
// noop
|
||||
}
|
||||
|
||||
bool CocoaNoGUIPlatform::CreatePlatformWindow(std::string title)
|
||||
{
|
||||
@autoreleasepool {
|
||||
s32 window_x, window_y, window_width, window_height;
|
||||
const bool has_window_geom = NoGUIHost::GetSavedPlatformWindowGeometry(&window_x, &window_y, &window_width, &window_height);
|
||||
if (!has_window_geom)
|
||||
{
|
||||
window_width = DEFAULT_WINDOW_WIDTH;
|
||||
window_height = DEFAULT_WINDOW_HEIGHT;
|
||||
}
|
||||
|
||||
m_window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0.0f, 0.0f, static_cast<CGFloat>(window_width), static_cast<CGFloat>(window_height))
|
||||
styleMask:WINDOWED_STYLE
|
||||
backing:NSBackingStoreBuffered defer:YES];
|
||||
|
||||
CocoaNoGUIView* view = [[[CocoaNoGUIView alloc] init] autorelease];
|
||||
[m_window setDelegate:view];
|
||||
[m_window setContentView:view];
|
||||
|
||||
if (!has_window_geom)
|
||||
[m_window center];
|
||||
else
|
||||
[m_window setFrameOrigin:NSMakePoint(static_cast<CGFloat>(window_x), static_cast<CGFloat>(window_y))];
|
||||
|
||||
[m_window setTitle: [NSString stringWithUTF8String:title.c_str()]];
|
||||
[m_window setAcceptsMouseMovedEvents:YES];
|
||||
[m_window setReleasedWhenClosed:NO];
|
||||
[m_window setIsVisible:TRUE];
|
||||
[m_window makeKeyAndOrderFront:nil];
|
||||
}
|
||||
|
||||
if (m_fullscreen.load(std::memory_order_acquire))
|
||||
SetFullscreen(true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CocoaNoGUIPlatform::HasPlatformWindow() const
|
||||
{
|
||||
return (m_window != NULL);
|
||||
}
|
||||
|
||||
void CocoaNoGUIPlatform::DestroyPlatformWindow()
|
||||
{
|
||||
if (m_window == nil)
|
||||
return;
|
||||
|
||||
const CGPoint frame_origin = m_window.frame.origin;
|
||||
const CGSize content_size = m_window.contentView.frame.size;
|
||||
|
||||
if (!m_fullscreen.load(std::memory_order_acquire))
|
||||
{
|
||||
NoGUIHost::SavePlatformWindowGeometry(static_cast<s32>(frame_origin.x), static_cast<s32>(frame_origin.y),
|
||||
static_cast<s32>(content_size.width), static_cast<s32>(content_size.height));
|
||||
}
|
||||
|
||||
[m_window close];
|
||||
[m_window release];
|
||||
m_window = nil;
|
||||
}
|
||||
|
||||
std::optional<WindowInfo> CocoaNoGUIPlatform::GetPlatformWindowInfo()
|
||||
{
|
||||
if (m_window == nil)
|
||||
return std::nullopt;
|
||||
|
||||
NSView* contentView = [m_window contentView];
|
||||
const NSSize size = [contentView convertSizeToBacking:contentView.frame.size];
|
||||
|
||||
WindowInfo wi;
|
||||
wi.surface_width = static_cast<u32>(size.width);
|
||||
wi.surface_height = static_cast<u32>(size.height);
|
||||
wi.surface_scale = m_window_scale;
|
||||
wi.type = WindowInfo::Type::MacOS;
|
||||
wi.window_handle = static_cast<void*>(m_window.contentView);
|
||||
return wi;
|
||||
}
|
||||
|
||||
void CocoaNoGUIPlatform::SetPlatformWindowTitle(std::string title)
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue(), [this, title = std::move(title)]() {
|
||||
if (!m_window)
|
||||
return;
|
||||
|
||||
@autoreleasepool {
|
||||
[m_window setTitle: [NSString stringWithUTF8String:title.c_str()]];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
std::optional<u32> CocoaNoGUIPlatform::ConvertHostKeyboardStringToCode(const std::string_view& str)
|
||||
{
|
||||
std::optional<unsigned short> converted(CocoaKeyNames::GetKeyCodeForName(str));
|
||||
return converted.has_value() ? std::optional<u32>(static_cast<u32>(converted.value())) : std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::string> CocoaNoGUIPlatform::ConvertHostKeyboardCodeToString(u32 code)
|
||||
{
|
||||
const char* converted = CocoaKeyNames::GetKeyName(static_cast<unsigned short>(code));
|
||||
return converted ? std::optional<std::string>(converted) : std::nullopt;
|
||||
}
|
||||
|
||||
void CocoaNoGUIPlatform::RunMessageLoop()
|
||||
{
|
||||
[NSApp run];
|
||||
}
|
||||
|
||||
void CocoaNoGUIPlatform::ExecuteInMessageLoop(std::function<void()> func)
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue(), [func = std::move(func)]() {
|
||||
func();
|
||||
});
|
||||
}
|
||||
|
||||
void CocoaNoGUIPlatform::QuitMessageLoop()
|
||||
{
|
||||
[NSApp stop:nil];
|
||||
}
|
||||
|
||||
void CocoaNoGUIPlatform::SetFullscreen(bool enabled)
|
||||
{
|
||||
Log_ErrorPrint("SetFullscreen() not implemented.");
|
||||
}
|
||||
|
||||
bool CocoaNoGUIPlatform::RequestRenderWindowSize(s32 new_window_width, s32 new_window_height)
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue(), [this, new_window_width, new_window_height]() {
|
||||
if (!m_window)
|
||||
return;
|
||||
|
||||
@autoreleasepool {
|
||||
[m_window setContentSize:NSMakeSize(static_cast<CGFloat>(new_window_width), static_cast<CGFloat>(new_window_height))];
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CocoaNoGUIPlatform::OpenURL(const std::string_view& url)
|
||||
{
|
||||
Log_ErrorPrint("OpenURL() not implemented.");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CocoaNoGUIPlatform::CopyTextToClipboard(const std::string_view& text)
|
||||
{
|
||||
Log_ErrorPrint("CopyTextToClipboard() not implemented.");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<NoGUIPlatform> NoGUIPlatform::CreateCocoaPlatform()
|
||||
{
|
||||
std::unique_ptr<CocoaNoGUIPlatform> ret(new CocoaNoGUIPlatform());
|
||||
if (!ret->Initialize())
|
||||
return {};
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -3,9 +3,6 @@
|
||||
<Import Project="..\..\dep\msvc\vsprops\Configurations.props" />
|
||||
<ItemGroup>
|
||||
<ClCompile Include="nogui_host.cpp" />
|
||||
<ClCompile Include="vty_nogui_platform.cpp">
|
||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="wayland_nogui_platform.cpp">
|
||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
@@ -18,12 +15,6 @@
|
||||
<ClInclude Include="nogui_host.h" />
|
||||
<ClInclude Include="nogui_platform.h" />
|
||||
<ClInclude Include="resource.h" />
|
||||
<ClInclude Include="vty_key_names.h">
|
||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||
</ClInclude>
|
||||
<ClInclude Include="vty_nogui_platform.h">
|
||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||
</ClInclude>
|
||||
<ClInclude Include="wayland_nogui_platform.h">
|
||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||
</ClInclude>
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
<ItemGroup>
|
||||
<ClCompile Include="nogui_host.cpp" />
|
||||
<ClCompile Include="win32_nogui_platform.cpp" />
|
||||
<ClCompile Include="vty_nogui_platform.cpp" />
|
||||
<ClCompile Include="wayland_nogui_platform.cpp" />
|
||||
<ClCompile Include="x11_nogui_platform.cpp" />
|
||||
</ItemGroup>
|
||||
@@ -11,8 +10,6 @@
|
||||
<ClInclude Include="resource.h" />
|
||||
<ClInclude Include="nogui_host.h" />
|
||||
<ClInclude Include="win32_nogui_platform.h" />
|
||||
<ClInclude Include="vty_nogui_platform.h" />
|
||||
<ClInclude Include="vty_key_names.h" />
|
||||
<ClInclude Include="nogui_platform.h" />
|
||||
<ClInclude Include="wayland_nogui_platform.h" />
|
||||
<ClInclude Include="x11_nogui_platform.h" />
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
#include "core/settings.h"
|
||||
#include "core/system.h"
|
||||
|
||||
#include "util/host_display.h"
|
||||
#include "util/gpu_device.h"
|
||||
#include "util/imgui_manager.h"
|
||||
#include "util/ini_settings_interface.h"
|
||||
#include "util/input_manager.h"
|
||||
@@ -63,6 +63,9 @@ std::unique_ptr<NoGUIPlatform> g_nogui_window;
|
||||
// Local function declarations
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
namespace NoGUIHost {
|
||||
/// Starts the virtual machine.
|
||||
static void StartSystem(SystemBootParameters params);
|
||||
|
||||
static bool ParseCommandLineParametersAndInitializeConfig(int argc, char* argv[],
|
||||
std::optional<SystemBootParameters>& autoboot);
|
||||
static void PrintCommandLineVersion();
|
||||
@@ -88,8 +91,6 @@ static void UpdateWindowTitle(const std::string& game_title);
|
||||
static void CancelAsyncOp();
|
||||
static void StartAsyncOp(std::function<void(ProgressCallback*)> callback);
|
||||
static void AsyncOpThreadEntryPoint(std::function<void(ProgressCallback*)> callback);
|
||||
static bool AcquireHostDisplay(RenderAPI api);
|
||||
static void ReleaseHostDisplay();
|
||||
} // namespace NoGUIHost
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
@@ -102,7 +103,7 @@ static bool s_save_state_on_shutdown = false;
|
||||
static bool s_was_paused_by_focus_loss = false;
|
||||
|
||||
static Threading::Thread s_cpu_thread;
|
||||
static Threading::KernelSemaphore s_host_display_created_or_destroyed;
|
||||
static Threading::KernelSemaphore s_platform_window_updated;
|
||||
static std::atomic_bool s_running{false};
|
||||
static std::mutex s_cpu_thread_events_mutex;
|
||||
static std::condition_variable s_cpu_thread_event_done;
|
||||
@@ -159,7 +160,7 @@ void NoGUIHost::SetAppRoot()
|
||||
|
||||
void NoGUIHost::SetResourcesDirectory()
|
||||
{
|
||||
#ifndef __APPLE__
|
||||
#ifndef __APPLE__NOT_USED // Not using bundles yet.
|
||||
// On Windows/Linux, these are in the binary directory.
|
||||
EmuFolders::Resources = Path::Combine(EmuFolders::AppRoot, "resources");
|
||||
#else
|
||||
@@ -423,8 +424,7 @@ void NoGUIHost::StartSystem(SystemBootParameters params)
|
||||
void NoGUIHost::ProcessPlatformWindowResize(s32 width, s32 height, float scale)
|
||||
{
|
||||
Host::RunOnCPUThread([width, height, scale]() {
|
||||
// TODO: Scale
|
||||
g_host_display->ResizeWindow(width, height);
|
||||
g_gpu_device->ResizeWindow(width, height, scale);
|
||||
ImGuiManager::WindowResized();
|
||||
System::HostDisplayResized();
|
||||
});
|
||||
@@ -432,8 +432,8 @@ void NoGUIHost::ProcessPlatformWindowResize(s32 width, s32 height, float scale)
|
||||
|
||||
void NoGUIHost::ProcessPlatformMouseMoveEvent(float x, float y)
|
||||
{
|
||||
if (g_host_display)
|
||||
g_host_display->SetMousePosition(static_cast<s32>(x), static_cast<s32>(y));
|
||||
if (g_gpu_device)
|
||||
g_gpu_device->SetMousePosition(static_cast<s32>(x), static_cast<s32>(y));
|
||||
|
||||
InputManager::UpdatePointerAbsolutePosition(0, x, y);
|
||||
ImGuiManager::UpdateMousePosition(x, y);
|
||||
@@ -616,8 +616,8 @@ void NoGUIHost::CPUThreadEntryPoint()
|
||||
// input source setup must happen on emu thread
|
||||
CommonHost::Initialize();
|
||||
|
||||
// start the GS thread up and get it going
|
||||
if (AcquireHostDisplay(Settings::GetRenderAPIForRenderer(g_settings.gpu_renderer)))
|
||||
// start the fullscreen UI and get it going
|
||||
if (Host::CreateGPUDevice(Settings::GetRenderAPIForRenderer(g_settings.gpu_renderer)) && FullscreenUI::Initialize())
|
||||
{
|
||||
// kick a game list refresh if we're not in batch mode
|
||||
if (!InBatchMode())
|
||||
@@ -637,7 +637,8 @@ void NoGUIHost::CPUThreadEntryPoint()
|
||||
|
||||
if (System::IsValid())
|
||||
System::ShutdownSystem(false);
|
||||
ReleaseHostDisplay();
|
||||
Host::ReleaseGPUDevice();
|
||||
Host::ReleaseRenderWindow();
|
||||
|
||||
CommonHost::Shutdown();
|
||||
g_nogui_window->QuitMessageLoop();
|
||||
@@ -655,58 +656,35 @@ void NoGUIHost::CPUThreadMainLoop()
|
||||
|
||||
Host::PumpMessagesOnCPUThread();
|
||||
Host::RenderDisplay(false);
|
||||
if (!g_host_display->IsVsyncEnabled())
|
||||
g_host_display->ThrottlePresentation();
|
||||
if (!g_gpu_device->IsVsyncEnabled())
|
||||
g_gpu_device->ThrottlePresentation();
|
||||
}
|
||||
}
|
||||
|
||||
bool NoGUIHost::AcquireHostDisplay(RenderAPI api)
|
||||
std::optional<WindowInfo> Host::AcquireRenderWindow(bool recreate_window)
|
||||
{
|
||||
Assert(!g_host_display);
|
||||
std::optional<WindowInfo> wi;
|
||||
|
||||
g_nogui_window->ExecuteInMessageLoop([api]() {
|
||||
if (g_nogui_window->CreatePlatformWindow(GetWindowTitle(System::GetGameTitle())))
|
||||
g_nogui_window->ExecuteInMessageLoop([&wi, recreate_window]() {
|
||||
bool res = g_nogui_window->HasPlatformWindow();
|
||||
if (!res || recreate_window)
|
||||
{
|
||||
const std::optional<WindowInfo> wi(g_nogui_window->GetPlatformWindowInfo());
|
||||
if (wi.has_value())
|
||||
{
|
||||
g_host_display = Host::CreateDisplayForAPI(api);
|
||||
if (g_host_display && !g_host_display->CreateDevice(wi.value(), System::ShouldUseVSync()))
|
||||
g_host_display.reset();
|
||||
}
|
||||
|
||||
if (g_host_display)
|
||||
g_host_display->DoneCurrent();
|
||||
else
|
||||
if (res)
|
||||
g_nogui_window->DestroyPlatformWindow();
|
||||
}
|
||||
|
||||
s_host_display_created_or_destroyed.Post();
|
||||
res = g_nogui_window->CreatePlatformWindow(NoGUIHost::GetWindowTitle(System::GetGameTitle()));
|
||||
}
|
||||
if (res)
|
||||
wi = g_nogui_window->GetPlatformWindowInfo();
|
||||
s_platform_window_updated.Post();
|
||||
});
|
||||
|
||||
s_host_display_created_or_destroyed.Wait();
|
||||
s_platform_window_updated.Wait();
|
||||
|
||||
if (!g_host_display)
|
||||
if (!wi.has_value())
|
||||
{
|
||||
g_nogui_window->ReportError("Error", "Failed to create host display.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!g_host_display->MakeCurrent() || !g_host_display->SetupDevice() || !ImGuiManager::Initialize() ||
|
||||
!CommonHost::CreateHostDisplayResources())
|
||||
{
|
||||
ImGuiManager::Shutdown();
|
||||
CommonHost::ReleaseHostDisplayResources();
|
||||
g_host_display.reset();
|
||||
g_nogui_window->DestroyPlatformWindow();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!FullscreenUI::Initialize())
|
||||
{
|
||||
g_nogui_window->ReportError("Error", "Failed to initialize fullscreen UI");
|
||||
ReleaseHostDisplay();
|
||||
return false;
|
||||
g_nogui_window->ReportError("Error", "Failed to create render window.");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// reload input sources, since it might use the window handle
|
||||
@@ -714,44 +692,18 @@ bool NoGUIHost::AcquireHostDisplay(RenderAPI api)
|
||||
auto lock = Host::GetSettingsLock();
|
||||
InputManager::ReloadSources(*Host::GetSettingsInterface(), lock);
|
||||
}
|
||||
return true;
|
||||
|
||||
return wi;
|
||||
}
|
||||
|
||||
bool Host::AcquireHostDisplay(RenderAPI api)
|
||||
void Host::ReleaseRenderWindow()
|
||||
{
|
||||
if (g_host_display && g_host_display->GetRenderAPI() == api)
|
||||
{
|
||||
// current is fine
|
||||
return true;
|
||||
}
|
||||
|
||||
// otherwise we need to switch
|
||||
NoGUIHost::ReleaseHostDisplay();
|
||||
return NoGUIHost::AcquireHostDisplay(api);
|
||||
}
|
||||
|
||||
void NoGUIHost::ReleaseHostDisplay()
|
||||
{
|
||||
if (!g_host_display)
|
||||
return;
|
||||
|
||||
// close input sources, since it might use the window handle
|
||||
InputManager::CloseSources();
|
||||
|
||||
CommonHost::ReleaseHostDisplayResources();
|
||||
FullscreenUI::Shutdown();
|
||||
ImGuiManager::Shutdown();
|
||||
g_host_display.reset();
|
||||
// Need to block here, otherwise the recreation message associates with the old window.
|
||||
g_nogui_window->ExecuteInMessageLoop([]() {
|
||||
g_nogui_window->DestroyPlatformWindow();
|
||||
s_host_display_created_or_destroyed.Post();
|
||||
s_platform_window_updated.Post();
|
||||
});
|
||||
s_host_display_created_or_destroyed.Wait();
|
||||
}
|
||||
|
||||
void Host::ReleaseHostDisplay()
|
||||
{
|
||||
// we keep the fsui going, so no need to do anything here
|
||||
s_platform_window_updated.Wait();
|
||||
}
|
||||
|
||||
void Host::OnSystemStarting()
|
||||
@@ -786,38 +738,10 @@ void Host::OnSystemDestroyed()
|
||||
Log_VerbosePrintf("Host::OnSystemDestroyed()");
|
||||
}
|
||||
|
||||
void Host::InvalidateDisplay()
|
||||
void Host::BeginPresentFrame()
|
||||
{
|
||||
RenderDisplay(false);
|
||||
}
|
||||
|
||||
void Host::RenderDisplay(bool skip_present)
|
||||
{
|
||||
// acquire for IO.MousePos.
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
|
||||
if (!skip_present)
|
||||
{
|
||||
FullscreenUI::Render();
|
||||
ImGuiManager::RenderTextOverlays();
|
||||
ImGuiManager::RenderOSDMessages();
|
||||
}
|
||||
|
||||
// Debug windows are always rendered, otherwise mouse input breaks on skip.
|
||||
ImGuiManager::RenderOverlayWindows();
|
||||
ImGuiManager::RenderDebugWindows();
|
||||
|
||||
g_host_display->Render(skip_present);
|
||||
|
||||
ImGuiManager::NewFrame();
|
||||
}
|
||||
|
||||
// void Host::ResizeHostDisplay(u32 new_window_width, u32 new_window_height, float new_window_scale)
|
||||
// {
|
||||
// s_host_display->ResizeRenderWindow(new_window_width, new_window_height, new_window_scale);
|
||||
// ImGuiManager::WindowResized();
|
||||
// }
|
||||
|
||||
void Host::RequestResizeHostDisplay(s32 width, s32 height)
|
||||
{
|
||||
g_nogui_window->RequestRenderWindowSize(width, height);
|
||||
@@ -879,7 +803,7 @@ std::unique_ptr<NoGUIPlatform> NoGUIHost::CreatePlatform()
|
||||
#if defined(_WIN32)
|
||||
ret = NoGUIPlatform::CreateWin32Platform();
|
||||
#elif defined(__APPLE__)
|
||||
// nothing yet
|
||||
ret = NoGUIPlatform::CreateCocoaPlatform();
|
||||
#else
|
||||
// linux
|
||||
const char* platform = std::getenv("DUCKSTATION_NOGUI_PLATFORM");
|
||||
@@ -891,10 +815,6 @@ std::unique_ptr<NoGUIPlatform> NoGUIHost::CreatePlatform()
|
||||
if (!ret && (!platform || StringUtil::Strcasecmp(platform, "x11") == 0) && std::getenv("DISPLAY"))
|
||||
ret = NoGUIPlatform::CreateX11Platform();
|
||||
#endif
|
||||
#ifdef NOGUI_PLATFORM_VTY
|
||||
if (!ret && (!platform || StringUtil::Strcasecmp(platform, "vty") == 0))
|
||||
ret = NoGUIPlatform::CreateVTYPlatform();
|
||||
#endif
|
||||
#endif
|
||||
|
||||
return ret;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
#pragma once
|
||||
#include "common/types.h"
|
||||
#include "core/system.h"
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
@@ -12,9 +12,6 @@ namespace NoGUIHost {
|
||||
bool InBatchMode();
|
||||
void SetBatchMode(bool enabled);
|
||||
|
||||
/// Starts the virtual machine.
|
||||
void StartSystem(SystemBootParameters params);
|
||||
|
||||
/// Returns the application name and version, optionally including debug/devel config indicator.
|
||||
std::string GetAppNameAndVersion();
|
||||
|
||||
@@ -36,4 +33,4 @@ void PlatformWindowFocusLost();
|
||||
void PlatformDevicesChanged();
|
||||
bool GetSavedPlatformWindowGeometry(s32* x, s32* y, s32* width, s32* height);
|
||||
void SavePlatformWindowGeometry(s32 x, s32 y, s32 width, s32 height);
|
||||
} // namespace NoGUIHost
|
||||
} // namespace NoGUIHost
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "util/gpu_device.h"
|
||||
|
||||
#include "common/types.h"
|
||||
#include "util/host_display.h"
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
@@ -24,6 +26,7 @@ public:
|
||||
virtual void SetDefaultConfig(SettingsInterface& si) = 0;
|
||||
|
||||
virtual bool CreatePlatformWindow(std::string title) = 0;
|
||||
virtual bool HasPlatformWindow() const = 0;
|
||||
virtual void DestroyPlatformWindow() = 0;
|
||||
|
||||
virtual std::optional<WindowInfo> GetPlatformWindowInfo() = 0;
|
||||
@@ -46,20 +49,19 @@ public:
|
||||
#ifdef _WIN32
|
||||
static std::unique_ptr<NoGUIPlatform> CreateWin32Platform();
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
static std::unique_ptr<NoGUIPlatform> CreateCocoaPlatform();
|
||||
#endif
|
||||
#ifdef NOGUI_PLATFORM_WAYLAND
|
||||
static std::unique_ptr<NoGUIPlatform> CreateWaylandPlatform();
|
||||
#endif
|
||||
#ifdef NOGUI_PLATFORM_X11
|
||||
static std::unique_ptr<NoGUIPlatform> CreateX11Platform();
|
||||
#endif
|
||||
#ifdef NOGUI_PLATFORM_VTY
|
||||
static std::unique_ptr<NoGUIPlatform> CreateVTYPlatform();
|
||||
#endif
|
||||
|
||||
protected:
|
||||
static constexpr s32 DEFAULT_WINDOW_WIDTH = 1280;
|
||||
static constexpr s32 DEFAULT_WINDOW_HEIGHT = 720;
|
||||
};
|
||||
|
||||
extern std::unique_ptr<NoGUIPlatform> g_nogui_window;
|
||||
extern std::unique_ptr<NoGUIPlatform> g_nogui_window;
|
||||
|
||||
@@ -1,281 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||
|
||||
#pragma once
|
||||
#include "common/string.h"
|
||||
#include "common/types.h"
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <linux/input-event-codes.h>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
|
||||
namespace VTYKeyNames {
|
||||
|
||||
static const std::map<int, const char*> s_evdev_key_names = {{KEY_ESC, "Escape"},
|
||||
{KEY_1, "1"},
|
||||
{KEY_2, "2"},
|
||||
{KEY_3, "3"},
|
||||
{KEY_4, "4"},
|
||||
{KEY_5, "5"},
|
||||
{KEY_6, "6"},
|
||||
{KEY_7, "7"},
|
||||
{KEY_8, "8"},
|
||||
{KEY_9, "9"},
|
||||
{KEY_0, "0"},
|
||||
{KEY_MINUS, "Minus"},
|
||||
{KEY_EQUAL, "Equal"},
|
||||
{KEY_BACKSPACE, "Backspace"},
|
||||
{KEY_TAB, "Tab"},
|
||||
{KEY_Q, "Q"},
|
||||
{KEY_W, "W"},
|
||||
{KEY_E, "E"},
|
||||
{KEY_R, "R"},
|
||||
{KEY_T, "T"},
|
||||
{KEY_Y, "Y"},
|
||||
{KEY_U, "U"},
|
||||
{KEY_I, "I"},
|
||||
{KEY_O, "O"},
|
||||
{KEY_P, "P"},
|
||||
{KEY_LEFTBRACE, "Leftbrace"},
|
||||
{KEY_RIGHTBRACE, "Rightbrace"},
|
||||
{KEY_ENTER, "Return"},
|
||||
{KEY_LEFTCTRL, "Leftctrl"},
|
||||
{KEY_A, "A"},
|
||||
{KEY_S, "S"},
|
||||
{KEY_D, "D"},
|
||||
{KEY_F, "F"},
|
||||
{KEY_G, "G"},
|
||||
{KEY_H, "H"},
|
||||
{KEY_J, "J"},
|
||||
{KEY_K, "K"},
|
||||
{KEY_L, "L"},
|
||||
{KEY_SEMICOLON, "Semicolon"},
|
||||
{KEY_APOSTROPHE, "Apostrophe"},
|
||||
{KEY_GRAVE, "Grave"},
|
||||
{KEY_LEFTSHIFT, "Leftshift"},
|
||||
{KEY_BACKSLASH, "Backslash"},
|
||||
{KEY_Z, "Z"},
|
||||
{KEY_X, "X"},
|
||||
{KEY_C, "C"},
|
||||
{KEY_V, "V"},
|
||||
{KEY_B, "B"},
|
||||
{KEY_N, "N"},
|
||||
{KEY_M, "M"},
|
||||
{KEY_COMMA, "Comma"},
|
||||
{KEY_DOT, "Dot"},
|
||||
{KEY_SLASH, "Slash"},
|
||||
{KEY_RIGHTSHIFT, "Rightshift"},
|
||||
{KEY_KPASTERISK, "Kpasterisk"},
|
||||
{KEY_LEFTALT, "Leftalt"},
|
||||
{KEY_SPACE, "Space"},
|
||||
{KEY_CAPSLOCK, "Capslock"},
|
||||
{KEY_F1, "F1"},
|
||||
{KEY_F2, "F2"},
|
||||
{KEY_F3, "F3"},
|
||||
{KEY_F4, "F4"},
|
||||
{KEY_F5, "F5"},
|
||||
{KEY_F6, "F6"},
|
||||
{KEY_F7, "F7"},
|
||||
{KEY_F8, "F8"},
|
||||
{KEY_F9, "F9"},
|
||||
{KEY_F10, "F10"},
|
||||
{KEY_NUMLOCK, "Numlock"},
|
||||
{KEY_SCROLLLOCK, "Scrolllock"},
|
||||
{KEY_KP7, "Kp7"},
|
||||
{KEY_KP8, "Kp8"},
|
||||
{KEY_KP9, "Kp9"},
|
||||
{KEY_KPMINUS, "Kpminus"},
|
||||
{KEY_KP4, "Kp4"},
|
||||
{KEY_KP5, "Kp5"},
|
||||
{KEY_KP6, "Kp6"},
|
||||
{KEY_KPPLUS, "Kpplus"},
|
||||
{KEY_KP1, "Kp1"},
|
||||
{KEY_KP2, "Kp2"},
|
||||
{KEY_KP3, "Kp3"},
|
||||
{KEY_KP0, "Kp0"},
|
||||
{KEY_KPDOT, "Kpdot"},
|
||||
{KEY_ZENKAKUHANKAKU, "Zenkakuhankaku"},
|
||||
{KEY_102ND, "102nd"},
|
||||
{KEY_F11, "F11"},
|
||||
{KEY_F12, "F12"},
|
||||
{KEY_RO, "Ro"},
|
||||
{KEY_KATAKANA, "Katakana"},
|
||||
{KEY_HIRAGANA, "Hiragana"},
|
||||
{KEY_HENKAN, "Henkan"},
|
||||
{KEY_KATAKANAHIRAGANA, "Katakanahiragana"},
|
||||
{KEY_MUHENKAN, "Muhenkan"},
|
||||
{KEY_KPJPCOMMA, "Kpjpcomma"},
|
||||
{KEY_KPENTER, "Kpenter"},
|
||||
{KEY_RIGHTCTRL, "Rightctrl"},
|
||||
{KEY_KPSLASH, "Kpslash"},
|
||||
{KEY_SYSRQ, "Sysrq"},
|
||||
{KEY_RIGHTALT, "RightAlt"},
|
||||
{KEY_LINEFEED, "Linefeed"},
|
||||
{KEY_HOME, "Home"},
|
||||
{KEY_UP, "Up"},
|
||||
{KEY_PAGEUP, "PageUp"},
|
||||
{KEY_LEFT, "Left"},
|
||||
{KEY_RIGHT, "Right"},
|
||||
{KEY_END, "End"},
|
||||
{KEY_DOWN, "Down"},
|
||||
{KEY_PAGEDOWN, "PageDown"},
|
||||
{KEY_INSERT, "Insert"},
|
||||
{KEY_DELETE, "Delete"},
|
||||
{KEY_MACRO, "Macro"},
|
||||
{KEY_MUTE, "Mute"},
|
||||
{KEY_VOLUMEDOWN, "VolumeDown"},
|
||||
{KEY_VOLUMEUP, "VolumeUp"},
|
||||
{KEY_POWER, "Power"},
|
||||
{KEY_KPEQUAL, "Kpequal"},
|
||||
{KEY_KPPLUSMINUS, "Kpplusminus"},
|
||||
{KEY_PAUSE, "Pause"},
|
||||
{KEY_SCALE, "Scale"},
|
||||
{KEY_KPCOMMA, "Kpcomma"},
|
||||
{KEY_HANGEUL, "Hangeul"},
|
||||
{KEY_HANGUEL, "Hanguel"},
|
||||
{KEY_HANJA, "Hanja"},
|
||||
{KEY_YEN, "Yen"},
|
||||
{KEY_LEFTMETA, "Leftmeta"},
|
||||
{KEY_RIGHTMETA, "Rightmeta"},
|
||||
{KEY_COMPOSE, "Compose"},
|
||||
{KEY_STOP, "Stop"},
|
||||
{KEY_AGAIN, "Again"},
|
||||
{KEY_PROPS, "Props"},
|
||||
{KEY_UNDO, "Undo"},
|
||||
{KEY_FRONT, "Front"},
|
||||
{KEY_COPY, "Copy"},
|
||||
{KEY_OPEN, "Open"},
|
||||
{KEY_PASTE, "Paste"},
|
||||
{KEY_FIND, "Find"},
|
||||
{KEY_CUT, "Cut"},
|
||||
{KEY_HELP, "Help"},
|
||||
{KEY_MENU, "Menu"},
|
||||
{KEY_CALC, "Calc"},
|
||||
{KEY_SETUP, "Setup"},
|
||||
{KEY_SLEEP, "Sleep"},
|
||||
{KEY_WAKEUP, "Wakeup"},
|
||||
{KEY_FILE, "File"},
|
||||
{KEY_SENDFILE, "Sendfile"},
|
||||
{KEY_DELETEFILE, "Deletefile"},
|
||||
{KEY_XFER, "Xfer"},
|
||||
{KEY_PROG1, "Prog1"},
|
||||
{KEY_PROG2, "Prog2"},
|
||||
{KEY_WWW, "Www"},
|
||||
{KEY_MSDOS, "Msdos"},
|
||||
{KEY_COFFEE, "Coffee"},
|
||||
{KEY_SCREENLOCK, "Screenlock"},
|
||||
{KEY_ROTATE_DISPLAY, "Rotate_display"},
|
||||
{KEY_DIRECTION, "Direction"},
|
||||
{KEY_CYCLEWINDOWS, "Cyclewindows"},
|
||||
{KEY_MAIL, "Mail"},
|
||||
{KEY_BOOKMARKS, "Bookmarks"},
|
||||
{KEY_COMPUTER, "Computer"},
|
||||
{KEY_BACK, "Back"},
|
||||
{KEY_FORWARD, "Forward"},
|
||||
{KEY_CLOSECD, "Closecd"},
|
||||
{KEY_EJECTCD, "Ejectcd"},
|
||||
{KEY_EJECTCLOSECD, "Ejectclosecd"},
|
||||
{KEY_NEXTSONG, "Nextsong"},
|
||||
{KEY_PLAYPAUSE, "Playpause"},
|
||||
{KEY_PREVIOUSSONG, "Previoussong"},
|
||||
{KEY_STOPCD, "Stopcd"},
|
||||
{KEY_RECORD, "Record"},
|
||||
{KEY_REWIND, "Rewind"},
|
||||
{KEY_PHONE, "Phone"},
|
||||
{KEY_ISO, "Iso"},
|
||||
{KEY_CONFIG, "Config"},
|
||||
{KEY_HOMEPAGE, "Homepage"},
|
||||
{KEY_REFRESH, "Refresh"},
|
||||
{KEY_EXIT, "Exit"},
|
||||
{KEY_MOVE, "Move"},
|
||||
{KEY_EDIT, "Edit"},
|
||||
{KEY_SCROLLUP, "Scrollup"},
|
||||
{KEY_SCROLLDOWN, "Scrolldown"},
|
||||
{KEY_KPLEFTPAREN, "Kpleftparen"},
|
||||
{KEY_KPRIGHTPAREN, "Kprightparen"},
|
||||
{KEY_NEW, "New"},
|
||||
{KEY_REDO, "Redo"},
|
||||
{KEY_F13, "F13"},
|
||||
{KEY_F14, "F14"},
|
||||
{KEY_F15, "F15"},
|
||||
{KEY_F16, "F16"},
|
||||
{KEY_F17, "F17"},
|
||||
{KEY_F18, "F18"},
|
||||
{KEY_F19, "F19"},
|
||||
{KEY_F20, "F20"},
|
||||
{KEY_F21, "F21"},
|
||||
{KEY_F22, "F22"},
|
||||
{KEY_F23, "F23"},
|
||||
{KEY_F24, "F24"},
|
||||
{KEY_PLAYCD, "Playcd"},
|
||||
{KEY_PAUSECD, "Pausecd"},
|
||||
{KEY_PROG3, "Prog3"},
|
||||
{KEY_PROG4, "Prog4"},
|
||||
{KEY_DASHBOARD, "Dashboard"},
|
||||
{KEY_SUSPEND, "Suspend"},
|
||||
{KEY_CLOSE, "Close"},
|
||||
{KEY_PLAY, "Play"},
|
||||
{KEY_FASTFORWARD, "Fastforward"},
|
||||
{KEY_BASSBOOST, "Bassboost"},
|
||||
{KEY_PRINT, "Print"},
|
||||
{KEY_HP, "Hp"},
|
||||
{KEY_CAMERA, "Camera"},
|
||||
{KEY_SOUND, "Sound"},
|
||||
{KEY_QUESTION, "Question"},
|
||||
{KEY_EMAIL, "Email"},
|
||||
{KEY_CHAT, "Chat"},
|
||||
{KEY_SEARCH, "Search"},
|
||||
{KEY_CONNECT, "Connect"},
|
||||
{KEY_FINANCE, "Finance"},
|
||||
{KEY_SPORT, "Sport"},
|
||||
{KEY_SHOP, "Shop"},
|
||||
{KEY_ALTERASE, "Alterase"},
|
||||
{KEY_CANCEL, "Cancel"},
|
||||
{KEY_BRIGHTNESSDOWN, "Brightnessdown"},
|
||||
{KEY_BRIGHTNESSUP, "Brightnessup"},
|
||||
{KEY_MEDIA, "Media"},
|
||||
{KEY_SWITCHVIDEOMODE, "Switchvideomode"},
|
||||
{KEY_KBDILLUMTOGGLE, "Kbdillumtoggle"},
|
||||
{KEY_KBDILLUMDOWN, "Kbdillumdown"},
|
||||
{KEY_KBDILLUMUP, "Kbdillumup"},
|
||||
{KEY_SEND, "Send"},
|
||||
{KEY_REPLY, "Reply"},
|
||||
{KEY_FORWARDMAIL, "Forwardmail"},
|
||||
{KEY_SAVE, "Save"},
|
||||
{KEY_DOCUMENTS, "Documents"},
|
||||
{KEY_BATTERY, "Battery"},
|
||||
{KEY_BLUETOOTH, "Bluetooth"},
|
||||
{KEY_WLAN, "Wlan"},
|
||||
{KEY_UWB, "Uwb"},
|
||||
{KEY_UNKNOWN, "Unknown"},
|
||||
{KEY_VIDEO_NEXT, "Video_next"},
|
||||
{KEY_VIDEO_PREV, "Video_prev"},
|
||||
{KEY_BRIGHTNESS_CYCLE, "Brightness_cycle"},
|
||||
{KEY_BRIGHTNESS_AUTO, "Brightness_auto"},
|
||||
{KEY_BRIGHTNESS_ZERO, "Brightness_zero"},
|
||||
{KEY_DISPLAY_OFF, "Display_off"},
|
||||
{KEY_WWAN, "Wwan"},
|
||||
{KEY_WIMAX, "Wimax"},
|
||||
{KEY_RFKILL, "Rfkill"},
|
||||
{KEY_MICMUTE, "Micmute"}};
|
||||
|
||||
static inline const char* GetKeyName(int key)
|
||||
{
|
||||
const auto it = s_evdev_key_names.find(key);
|
||||
return it == s_evdev_key_names.end() ? nullptr : it->second;
|
||||
}
|
||||
|
||||
static inline std::optional<int> GetKeyCodeForName(const std::string_view key_name)
|
||||
{
|
||||
for (const auto& it : s_evdev_key_names)
|
||||
{
|
||||
if (key_name == it.second)
|
||||
return it.first;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace VTYKeyNames
|
||||
@@ -1,248 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||
|
||||
#include "vty_nogui_platform.h"
|
||||
#include "common/log.h"
|
||||
#include "common/string_util.h"
|
||||
#include "common/threading.h"
|
||||
#include "core/host.h"
|
||||
#include "core/host_settings.h"
|
||||
#include "nogui_host.h"
|
||||
#include "resource.h"
|
||||
#include "vty_key_names.h"
|
||||
#include <fcntl.h>
|
||||
#include <linux/input-event-codes.h>
|
||||
#include <thread>
|
||||
#include <unistd.h>
|
||||
Log_SetChannel(VTYNoGUIPlatform);
|
||||
|
||||
#ifdef WITH_DRMKMS
|
||||
#include "common/drm_display.h"
|
||||
#endif
|
||||
|
||||
VTYNoGUIPlatform::VTYNoGUIPlatform()
|
||||
{
|
||||
m_message_loop_running.store(true, std::memory_order_release);
|
||||
}
|
||||
|
||||
VTYNoGUIPlatform::~VTYNoGUIPlatform()
|
||||
{
|
||||
CloseEVDevFDs();
|
||||
}
|
||||
|
||||
std::unique_ptr<NoGUIPlatform> NoGUIPlatform::CreateVTYPlatform()
|
||||
{
|
||||
std::unique_ptr<VTYNoGUIPlatform> platform(std::make_unique<VTYNoGUIPlatform>());
|
||||
if (!platform->Initialize())
|
||||
platform.reset();
|
||||
return platform;
|
||||
}
|
||||
|
||||
bool VTYNoGUIPlatform::Initialize()
|
||||
{
|
||||
OpenEVDevFDs();
|
||||
return true;
|
||||
}
|
||||
|
||||
void VTYNoGUIPlatform::ReportError(const std::string_view& title, const std::string_view& message)
|
||||
{
|
||||
// not implemented
|
||||
}
|
||||
|
||||
bool VTYNoGUIPlatform::ConfirmMessage(const std::string_view& title, const std::string_view& message)
|
||||
{
|
||||
// not implemented
|
||||
return true;
|
||||
}
|
||||
|
||||
void VTYNoGUIPlatform::SetDefaultConfig(SettingsInterface& si)
|
||||
{
|
||||
// noop
|
||||
}
|
||||
|
||||
bool VTYNoGUIPlatform::CreatePlatformWindow(std::string title)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void VTYNoGUIPlatform::DestroyPlatformWindow()
|
||||
{
|
||||
// noop
|
||||
}
|
||||
|
||||
std::optional<WindowInfo> VTYNoGUIPlatform::GetPlatformWindowInfo()
|
||||
{
|
||||
WindowInfo wi;
|
||||
wi.type = WindowInfo::Type::Display;
|
||||
wi.surface_width = 0;
|
||||
wi.surface_height = 0;
|
||||
wi.surface_refresh_rate = 0.0f;
|
||||
wi.surface_format = WindowInfo::SurfaceFormat::Auto;
|
||||
|
||||
const std::string fullscreen_mode = Host::GetStringSettingValue("GPU", "FullscreenMode", "");
|
||||
if (!fullscreen_mode.empty())
|
||||
{
|
||||
if (!HostDisplay::ParseFullscreenMode(fullscreen_mode, &wi.surface_width, &wi.surface_height,
|
||||
&wi.surface_refresh_rate))
|
||||
{
|
||||
Log_ErrorPrintf("Failed to parse fullscreen mode '%s'", fullscreen_mode.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef WITH_DRMKMS
|
||||
// set to current mode
|
||||
if (wi.surface_width == 0)
|
||||
{
|
||||
if (!DRMDisplay::GetCurrentMode(&wi.surface_width, &wi.surface_height, &wi.surface_refresh_rate))
|
||||
Log_ErrorPrintf("Failed to get current mode, will use default.");
|
||||
}
|
||||
#endif
|
||||
|
||||
// This isn't great, but it's an approximation at least..
|
||||
if (wi.surface_width > 0)
|
||||
wi.surface_scale = std::max(0.1f, static_cast<float>(wi.surface_width) / 1280.0f);
|
||||
|
||||
return wi;
|
||||
}
|
||||
|
||||
void VTYNoGUIPlatform::SetFullscreen(bool enabled)
|
||||
{
|
||||
// already fullscreen :-)
|
||||
}
|
||||
|
||||
bool VTYNoGUIPlatform::RequestRenderWindowSize(s32 new_window_width, s32 new_window_height)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool VTYNoGUIPlatform::OpenURL(const std::string_view& url)
|
||||
{
|
||||
Log_ErrorPrintf("VTYNoGUIPlatform::OpenURL() not implemented: %.*s", static_cast<int>(url.size()), url.data());
|
||||
return false;
|
||||
}
|
||||
|
||||
bool VTYNoGUIPlatform::CopyTextToClipboard(const std::string_view& text)
|
||||
{
|
||||
Log_ErrorPrintf("VTYNoGUIPlatform::CopyTextToClipboard() not implemented: %.*s", static_cast<int>(text.size()),
|
||||
text.data());
|
||||
return false;
|
||||
}
|
||||
|
||||
void VTYNoGUIPlatform::SetPlatformWindowTitle(std::string title)
|
||||
{
|
||||
Log_InfoPrintf("Window Title: %s", title.c_str());
|
||||
}
|
||||
|
||||
void VTYNoGUIPlatform::RunMessageLoop()
|
||||
{
|
||||
while (m_message_loop_running.load(std::memory_order_acquire))
|
||||
{
|
||||
PollEvDevKeyboards();
|
||||
|
||||
{
|
||||
std::unique_lock lock(m_callback_queue_mutex);
|
||||
while (!m_callback_queue.empty())
|
||||
{
|
||||
std::function<void()> func = std::move(m_callback_queue.front());
|
||||
m_callback_queue.pop_front();
|
||||
lock.unlock();
|
||||
func();
|
||||
lock.lock();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Make this suck less.
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
}
|
||||
|
||||
void VTYNoGUIPlatform::ExecuteInMessageLoop(std::function<void()> func)
|
||||
{
|
||||
std::unique_lock lock(m_callback_queue_mutex);
|
||||
m_callback_queue.push_back(std::move(func));
|
||||
}
|
||||
|
||||
void VTYNoGUIPlatform::QuitMessageLoop()
|
||||
{
|
||||
m_message_loop_running.store(false, std::memory_order_release);
|
||||
}
|
||||
|
||||
void VTYNoGUIPlatform::OpenEVDevFDs()
|
||||
{
|
||||
for (int i = 0; i < 1000; i++)
|
||||
{
|
||||
TinyString path;
|
||||
path.Format("/dev/input/event%d", i);
|
||||
|
||||
int fd = open(path, O_RDONLY | O_NONBLOCK);
|
||||
if (fd < 0)
|
||||
break;
|
||||
|
||||
struct libevdev* obj;
|
||||
if (libevdev_new_from_fd(fd, &obj) != 0)
|
||||
{
|
||||
Log_ErrorPrintf("libevdev_new_from_fd(%s) failed", path.GetCharArray());
|
||||
close(fd);
|
||||
continue;
|
||||
}
|
||||
|
||||
Log_DevPrintf("Input path: %s", path.GetCharArray());
|
||||
Log_DevPrintf("Input device name: \"%s\"", libevdev_get_name(obj));
|
||||
Log_DevPrintf("Input device ID: bus %#x vendor %#x product %#x", libevdev_get_id_bustype(obj),
|
||||
libevdev_get_id_vendor(obj), libevdev_get_id_product(obj));
|
||||
if (!libevdev_has_event_code(obj, EV_KEY, KEY_SPACE))
|
||||
{
|
||||
Log_DevPrintf("This device does not look like a keyboard");
|
||||
libevdev_free(obj);
|
||||
close(fd);
|
||||
continue;
|
||||
}
|
||||
|
||||
const int grab_res = libevdev_grab(obj, LIBEVDEV_GRAB);
|
||||
if (grab_res != 0)
|
||||
Log_WarningPrintf("Failed to grab '%s' (%s): %d", libevdev_get_name(obj), path.GetCharArray(), grab_res);
|
||||
|
||||
m_evdev_keyboards.push_back({obj, fd});
|
||||
}
|
||||
}
|
||||
|
||||
void VTYNoGUIPlatform::CloseEVDevFDs()
|
||||
{
|
||||
for (const EvDevKeyboard& kb : m_evdev_keyboards)
|
||||
{
|
||||
libevdev_grab(kb.obj, LIBEVDEV_UNGRAB);
|
||||
libevdev_free(kb.obj);
|
||||
close(kb.fd);
|
||||
}
|
||||
m_evdev_keyboards.clear();
|
||||
}
|
||||
|
||||
void VTYNoGUIPlatform::PollEvDevKeyboards()
|
||||
{
|
||||
for (const EvDevKeyboard& kb : m_evdev_keyboards)
|
||||
{
|
||||
struct input_event ev;
|
||||
while (libevdev_next_event(kb.obj, LIBEVDEV_READ_FLAG_NORMAL, &ev) == 0)
|
||||
{
|
||||
// auto-repeat
|
||||
// TODO: forward char to imgui
|
||||
if (ev.value == 2)
|
||||
continue;
|
||||
|
||||
const bool pressed = (ev.value == 1);
|
||||
NoGUIHost::ProcessPlatformKeyEvent(static_cast<s32>(ev.code), pressed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<u32> VTYNoGUIPlatform::ConvertHostKeyboardStringToCode(const std::string_view& str)
|
||||
{
|
||||
std::optional<int> converted(VTYKeyNames::GetKeyCodeForName(str));
|
||||
return converted.has_value() ? std::optional<u32>(static_cast<u32>(converted.value())) : std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::string> VTYNoGUIPlatform::ConvertHostKeyboardCodeToString(u32 code)
|
||||
{
|
||||
const char* keyname = VTYKeyNames::GetKeyName(static_cast<int>(code));
|
||||
return keyname ? std::optional<std::string>(std::string(keyname)) : std::nullopt;
|
||||
}
|
||||
@@ -150,6 +150,11 @@ bool WaylandNoGUIPlatform::CreatePlatformWindow(std::string title)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WaylandNoGUIPlatform::HasPlatformWindow() const
|
||||
{
|
||||
return (m_surface != nullptr);
|
||||
}
|
||||
|
||||
void WaylandNoGUIPlatform::DestroyPlatformWindow()
|
||||
{
|
||||
m_window_info = {};
|
||||
|
||||
@@ -29,6 +29,7 @@ public:
|
||||
void SetDefaultConfig(SettingsInterface& si) override;
|
||||
|
||||
bool CreatePlatformWindow(std::string title) override;
|
||||
bool HasPlatformWindow() const override;
|
||||
void DestroyPlatformWindow() override;
|
||||
std::optional<WindowInfo> GetPlatformWindowInfo() override;
|
||||
void SetPlatformWindowTitle(std::string title) override;
|
||||
|
||||
@@ -134,6 +134,11 @@ bool Win32NoGUIPlatform::CreatePlatformWindow(std::string title)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Win32NoGUIPlatform::HasPlatformWindow() const
|
||||
{
|
||||
return (m_hwnd != NULL);
|
||||
}
|
||||
|
||||
void Win32NoGUIPlatform::DestroyPlatformWindow()
|
||||
{
|
||||
if (!m_hwnd)
|
||||
|
||||
@@ -23,6 +23,7 @@ public:
|
||||
void SetDefaultConfig(SettingsInterface& si) override;
|
||||
|
||||
bool CreatePlatformWindow(std::string title) override;
|
||||
bool HasPlatformWindow() const override;
|
||||
void DestroyPlatformWindow() override;
|
||||
std::optional<WindowInfo> GetPlatformWindowInfo() override;
|
||||
void SetPlatformWindowTitle(std::string title) override;
|
||||
|
||||
@@ -99,6 +99,11 @@ bool X11NoGUIPlatform::CreatePlatformWindow(std::string title)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool X11NoGUIPlatform::HasPlatformWindow() const
|
||||
{
|
||||
return m_window != 0;
|
||||
}
|
||||
|
||||
void X11NoGUIPlatform::DestroyPlatformWindow()
|
||||
{
|
||||
m_window_info = {};
|
||||
|
||||
@@ -48,6 +48,7 @@ public:
|
||||
void SetDefaultConfig(SettingsInterface& si) override;
|
||||
|
||||
bool CreatePlatformWindow(std::string title) override;
|
||||
bool HasPlatformWindow() const override;
|
||||
void DestroyPlatformWindow() override;
|
||||
std::optional<WindowInfo> GetPlatformWindowInfo() override;
|
||||
void SetPlatformWindowTitle(std::string title) override;
|
||||
|
||||
Reference in New Issue
Block a user