mirror of
https://github.com/WinampDesktop/winamp.git
synced 2025-05-11 16:35:42 -04:00
484 lines
16 KiB
C++
484 lines
16 KiB
C++
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
|
|
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
|
|
|
#include "wayland_nogui_platform.h"
|
|
#include "common/assert.h"
|
|
#include "common/log.h"
|
|
#include "common/string_util.h"
|
|
#include "common/threading.h"
|
|
#include "core/host.h"
|
|
#include "nogui_host.h"
|
|
#include "nogui_platform.h"
|
|
|
|
#include <linux/input-event-codes.h>
|
|
#include <sys/mman.h>
|
|
#include <thread>
|
|
#include <unistd.h>
|
|
|
|
Log_SetChannel(WaylandNoGUIPlatform);
|
|
|
|
WaylandNoGUIPlatform::WaylandNoGUIPlatform()
|
|
{
|
|
m_message_loop_running.store(true, std::memory_order_release);
|
|
}
|
|
|
|
WaylandNoGUIPlatform::~WaylandNoGUIPlatform()
|
|
{
|
|
if (m_xkb_state)
|
|
xkb_state_unref(m_xkb_state);
|
|
if (m_xkb_keymap)
|
|
xkb_keymap_unref(m_xkb_keymap);
|
|
if (m_wl_keyboard)
|
|
wl_keyboard_destroy(m_wl_keyboard);
|
|
if (m_wl_pointer)
|
|
wl_pointer_destroy(m_wl_pointer);
|
|
if (m_wl_seat)
|
|
wl_seat_destroy(m_wl_seat);
|
|
if (m_xkb_context)
|
|
xkb_context_unref(m_xkb_context);
|
|
if (m_registry)
|
|
wl_registry_destroy(m_registry);
|
|
}
|
|
|
|
bool WaylandNoGUIPlatform::Initialize()
|
|
{
|
|
m_xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
|
|
if (!m_xkb_context)
|
|
{
|
|
Panic("Failed to create XKB context");
|
|
return false;
|
|
}
|
|
|
|
m_display = wl_display_connect(nullptr);
|
|
if (!m_display)
|
|
{
|
|
Panic("Failed to connect to Wayland display.");
|
|
return false;
|
|
}
|
|
|
|
static const wl_registry_listener registry_listener = {GlobalRegistryHandler, GlobalRegistryRemover};
|
|
m_registry = wl_display_get_registry(m_display);
|
|
wl_registry_add_listener(m_registry, ®istry_listener, this);
|
|
|
|
// Call back to registry listener to get compositor/shell.
|
|
wl_display_dispatch_pending(m_display);
|
|
wl_display_roundtrip(m_display);
|
|
|
|
// We need a shell/compositor, or at least one we understand.
|
|
if (!m_compositor || !m_xdg_wm_base)
|
|
{
|
|
Panic("Missing Wayland shell/compositor\n");
|
|
return false;
|
|
}
|
|
|
|
static const xdg_wm_base_listener xdg_wm_base_listener = {XDGWMBasePing};
|
|
xdg_wm_base_add_listener(m_xdg_wm_base, &xdg_wm_base_listener, this);
|
|
wl_display_dispatch_pending(m_display);
|
|
wl_display_roundtrip(m_display);
|
|
return true;
|
|
}
|
|
|
|
void WaylandNoGUIPlatform::ReportError(const std::string_view& title, const std::string_view& message)
|
|
{
|
|
// not implemented
|
|
}
|
|
|
|
bool WaylandNoGUIPlatform::ConfirmMessage(const std::string_view& title, const std::string_view& message)
|
|
{
|
|
// not implemented
|
|
return true;
|
|
}
|
|
|
|
void WaylandNoGUIPlatform::SetDefaultConfig(SettingsInterface& si) {}
|
|
|
|
bool WaylandNoGUIPlatform::CreatePlatformWindow(std::string title)
|
|
{
|
|
s32 window_x, window_y, window_width, window_height;
|
|
bool has_window_pos = NoGUIHost::GetSavedPlatformWindowGeometry(&window_x, &window_y, &window_width, &window_height);
|
|
if (!has_window_pos)
|
|
{
|
|
window_x = 0;
|
|
window_y = 0;
|
|
window_width = DEFAULT_WINDOW_WIDTH;
|
|
window_height = DEFAULT_WINDOW_HEIGHT;
|
|
}
|
|
|
|
// Create the compositor and shell surface.
|
|
if (!(m_surface = wl_compositor_create_surface(m_compositor)) ||
|
|
!(m_xdg_surface = xdg_wm_base_get_xdg_surface(m_xdg_wm_base, m_surface)) ||
|
|
!(m_xdg_toplevel = xdg_surface_get_toplevel(m_xdg_surface)))
|
|
{
|
|
Log_ErrorPrintf("Failed to create compositor/shell surfaces");
|
|
return false;
|
|
}
|
|
|
|
static const xdg_surface_listener shell_surface_listener = {XDGSurfaceConfigure};
|
|
xdg_surface_add_listener(m_xdg_surface, &shell_surface_listener, this);
|
|
|
|
static const xdg_toplevel_listener toplevel_listener = {TopLevelConfigure, TopLevelClose};
|
|
xdg_toplevel_add_listener(m_xdg_toplevel, &toplevel_listener, this);
|
|
|
|
// Create region in the surface to draw into.
|
|
m_region = wl_compositor_create_region(m_compositor);
|
|
wl_region_add(m_region, 0, 0, window_width, window_height);
|
|
wl_surface_set_opaque_region(m_surface, m_region);
|
|
wl_surface_commit(m_surface);
|
|
|
|
// This doesn't seem to have any effect on kwin...
|
|
if (has_window_pos)
|
|
{
|
|
xdg_surface_set_window_geometry(m_xdg_surface, window_x, window_y, window_width, window_height);
|
|
}
|
|
|
|
if (m_decoration_manager)
|
|
{
|
|
m_toplevel_decoration = zxdg_decoration_manager_v1_get_toplevel_decoration(m_decoration_manager, m_xdg_toplevel);
|
|
if (m_toplevel_decoration)
|
|
zxdg_toplevel_decoration_v1_set_mode(m_toplevel_decoration, ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
|
|
}
|
|
|
|
m_window_info.surface_width = static_cast<u32>(window_width);
|
|
m_window_info.surface_height = static_cast<u32>(window_height);
|
|
m_window_info.surface_scale = 1.0f;
|
|
m_window_info.type = WindowInfo::Type::Wayland;
|
|
m_window_info.window_handle = m_surface;
|
|
m_window_info.display_connection = m_display;
|
|
|
|
wl_display_dispatch_pending(m_display);
|
|
wl_display_roundtrip(m_display);
|
|
return true;
|
|
}
|
|
|
|
bool WaylandNoGUIPlatform::HasPlatformWindow() const
|
|
{
|
|
return (m_surface != nullptr);
|
|
}
|
|
|
|
void WaylandNoGUIPlatform::DestroyPlatformWindow()
|
|
{
|
|
m_window_info = {};
|
|
|
|
if (m_toplevel_decoration)
|
|
{
|
|
zxdg_toplevel_decoration_v1_destroy(m_toplevel_decoration);
|
|
m_toplevel_decoration = {};
|
|
}
|
|
|
|
if (m_xdg_toplevel)
|
|
{
|
|
xdg_toplevel_destroy(m_xdg_toplevel);
|
|
m_xdg_toplevel = {};
|
|
}
|
|
|
|
if (m_xdg_surface)
|
|
{
|
|
xdg_surface_destroy(m_xdg_surface);
|
|
m_xdg_surface = {};
|
|
}
|
|
|
|
if (m_surface)
|
|
{
|
|
wl_surface_destroy(m_surface);
|
|
m_surface = {};
|
|
}
|
|
|
|
wl_display_dispatch_pending(m_display);
|
|
wl_display_roundtrip(m_display);
|
|
}
|
|
|
|
std::optional<WindowInfo> WaylandNoGUIPlatform::GetPlatformWindowInfo()
|
|
{
|
|
if (m_window_info.type == WindowInfo::Type::Wayland)
|
|
return m_window_info;
|
|
else
|
|
return std::nullopt;
|
|
}
|
|
|
|
void WaylandNoGUIPlatform::SetPlatformWindowTitle(std::string title)
|
|
{
|
|
if (m_xdg_toplevel)
|
|
xdg_toplevel_set_title(m_xdg_toplevel, title.c_str());
|
|
}
|
|
|
|
std::optional<u32> WaylandNoGUIPlatform::ConvertHostKeyboardStringToCode(const std::string_view& str)
|
|
{
|
|
std::unique_lock lock(m_key_map_mutex);
|
|
for (const auto& it : m_key_map)
|
|
{
|
|
if (StringUtil::Strncasecmp(it.second.c_str(), str.data(), str.length()) == 0)
|
|
return it.first;
|
|
}
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::optional<std::string> WaylandNoGUIPlatform::ConvertHostKeyboardCodeToString(u32 code)
|
|
{
|
|
std::unique_lock lock(m_key_map_mutex);
|
|
const auto it = m_key_map.find(static_cast<s32>(code));
|
|
return (it != m_key_map.end()) ? std::optional<std::string>(it->second) : std::nullopt;
|
|
}
|
|
|
|
void WaylandNoGUIPlatform::GlobalRegistryHandler(void* data, wl_registry* registry, uint32_t id, const char* interface,
|
|
uint32_t version)
|
|
{
|
|
WaylandNoGUIPlatform* platform = static_cast<WaylandNoGUIPlatform*>(data);
|
|
if (std::strcmp(interface, wl_compositor_interface.name) == 0)
|
|
{
|
|
platform->m_compositor =
|
|
static_cast<wl_compositor*>(wl_registry_bind(platform->m_registry, id, &wl_compositor_interface, 1));
|
|
}
|
|
else if (std::strcmp(interface, xdg_wm_base_interface.name) == 0)
|
|
{
|
|
platform->m_xdg_wm_base =
|
|
static_cast<xdg_wm_base*>(wl_registry_bind(platform->m_registry, id, &xdg_wm_base_interface, 1));
|
|
}
|
|
else if (std::strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0)
|
|
{
|
|
platform->m_decoration_manager = static_cast<zxdg_decoration_manager_v1*>(
|
|
wl_registry_bind(platform->m_registry, id, &zxdg_decoration_manager_v1_interface, 1));
|
|
}
|
|
else if (std::strcmp(interface, wl_seat_interface.name) == 0)
|
|
{
|
|
static const wl_seat_listener seat_listener = {&WaylandNoGUIPlatform::SeatCapabilities};
|
|
platform->m_wl_seat = static_cast<wl_seat*>(wl_registry_bind(registry, id, &wl_seat_interface, 1));
|
|
wl_seat_add_listener(platform->m_wl_seat, &seat_listener, platform);
|
|
}
|
|
}
|
|
|
|
void WaylandNoGUIPlatform::GlobalRegistryRemover(void* data, wl_registry* registry, uint32_t id) {}
|
|
|
|
void WaylandNoGUIPlatform::XDGWMBasePing(void* data, struct xdg_wm_base* xdg_wm_base, uint32_t serial)
|
|
{
|
|
xdg_wm_base_pong(xdg_wm_base, serial);
|
|
}
|
|
|
|
void WaylandNoGUIPlatform::XDGSurfaceConfigure(void* data, struct xdg_surface* xdg_surface, uint32_t serial)
|
|
{
|
|
xdg_surface_ack_configure(xdg_surface, serial);
|
|
}
|
|
|
|
void WaylandNoGUIPlatform::TopLevelConfigure(void* data, struct xdg_toplevel* xdg_toplevel, int32_t width,
|
|
int32_t height, struct wl_array* states)
|
|
{
|
|
// If this is zero, it's asking us to set the size.
|
|
if (width == 0 || height == 0)
|
|
return;
|
|
|
|
WaylandNoGUIPlatform* platform = static_cast<WaylandNoGUIPlatform*>(data);
|
|
platform->m_window_info.surface_width = width;
|
|
platform->m_window_info.surface_height = height;
|
|
|
|
NoGUIHost::ProcessPlatformWindowResize(width, height, platform->m_window_info.surface_scale);
|
|
}
|
|
|
|
void WaylandNoGUIPlatform::TopLevelClose(void* data, struct xdg_toplevel* xdg_toplevel)
|
|
{
|
|
Host::RunOnCPUThread([]() { Host::RequestExit(false); });
|
|
}
|
|
|
|
void WaylandNoGUIPlatform::SeatCapabilities(void* data, wl_seat* seat, uint32_t capabilities)
|
|
{
|
|
WaylandNoGUIPlatform* platform = static_cast<WaylandNoGUIPlatform*>(data);
|
|
if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD)
|
|
{
|
|
static const wl_keyboard_listener keyboard_listener = {
|
|
&WaylandNoGUIPlatform::KeyboardKeymap, &WaylandNoGUIPlatform::KeyboardEnter, &WaylandNoGUIPlatform::KeyboardLeave,
|
|
&WaylandNoGUIPlatform::KeyboardKey, &WaylandNoGUIPlatform::KeyboardModifiers};
|
|
platform->m_wl_keyboard = wl_seat_get_keyboard(seat);
|
|
wl_keyboard_add_listener(platform->m_wl_keyboard, &keyboard_listener, platform);
|
|
}
|
|
if (capabilities & WL_SEAT_CAPABILITY_POINTER)
|
|
{
|
|
static const wl_pointer_listener pointer_listener = {
|
|
&WaylandNoGUIPlatform::PointerEnter, &WaylandNoGUIPlatform::PointerLeave, &WaylandNoGUIPlatform::PointerMotion,
|
|
&WaylandNoGUIPlatform::PointerButton, &WaylandNoGUIPlatform::PointerAxis};
|
|
platform->m_wl_pointer = wl_seat_get_pointer(seat);
|
|
wl_pointer_add_listener(platform->m_wl_pointer, &pointer_listener, platform);
|
|
}
|
|
}
|
|
|
|
void WaylandNoGUIPlatform::KeyboardKeymap(void* data, wl_keyboard* keyboard, uint32_t format, int32_t fd, uint32_t size)
|
|
{
|
|
WaylandNoGUIPlatform* platform = static_cast<WaylandNoGUIPlatform*>(data);
|
|
char* keymap_string = static_cast<char*>(mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0));
|
|
if (platform->m_xkb_keymap)
|
|
xkb_keymap_unref(platform->m_xkb_keymap);
|
|
platform->m_xkb_keymap = xkb_keymap_new_from_string(platform->m_xkb_context, keymap_string, XKB_KEYMAP_FORMAT_TEXT_V1,
|
|
XKB_KEYMAP_COMPILE_NO_FLAGS);
|
|
munmap(keymap_string, size);
|
|
close(fd);
|
|
|
|
if (platform->m_xkb_state)
|
|
xkb_state_unref(platform->m_xkb_state);
|
|
platform->m_xkb_state = xkb_state_new(platform->m_xkb_keymap);
|
|
|
|
platform->InitializeKeyMap();
|
|
}
|
|
|
|
void WaylandNoGUIPlatform::InitializeKeyMap()
|
|
{
|
|
m_key_map.clear();
|
|
Log_VerbosePrintf("Init keymap");
|
|
|
|
const xkb_keycode_t min_keycode = xkb_keymap_min_keycode(m_xkb_keymap);
|
|
const xkb_keycode_t max_keycode = xkb_keymap_max_keycode(m_xkb_keymap);
|
|
DebugAssert(max_keycode >= min_keycode);
|
|
|
|
for (xkb_keycode_t keycode = min_keycode; keycode <= max_keycode; keycode++)
|
|
{
|
|
const xkb_layout_index_t num_layouts = xkb_keymap_num_layouts_for_key(m_xkb_keymap, keycode);
|
|
if (num_layouts == 0)
|
|
continue;
|
|
|
|
// Take the first layout which we find a valid keysym for.
|
|
bool found_keysym = false;
|
|
for (xkb_layout_index_t layout = 0; layout < num_layouts && !found_keysym; layout++)
|
|
{
|
|
const xkb_level_index_t num_levels = xkb_keymap_num_levels_for_key(m_xkb_keymap, keycode, layout);
|
|
if (num_levels == 0)
|
|
continue;
|
|
|
|
// Take the first level which we find a valid keysym for.
|
|
for (xkb_level_index_t level = 0; level < num_levels; level++)
|
|
{
|
|
const xkb_keysym_t* keysyms;
|
|
const int num_syms = xkb_keymap_key_get_syms_by_level(m_xkb_keymap, keycode, layout, level, &keysyms);
|
|
if (num_syms == 0)
|
|
continue;
|
|
|
|
// Just take the first. Should only be one in most cases anyway.
|
|
const xkb_keysym_t keysym = xkb_keysym_to_upper(keysyms[0]);
|
|
|
|
char keysym_name_buf[64];
|
|
if (xkb_keysym_get_name(keysym, keysym_name_buf, sizeof(keysym_name_buf)) <= 0)
|
|
continue;
|
|
|
|
m_key_map.emplace(static_cast<s32>(keycode), keysym_name_buf);
|
|
found_keysym = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void WaylandNoGUIPlatform::KeyboardEnter(void* data, wl_keyboard* keyboard, uint32_t serial, wl_surface* surface,
|
|
wl_array* keys)
|
|
{
|
|
}
|
|
|
|
void WaylandNoGUIPlatform::KeyboardLeave(void* data, wl_keyboard* keyboard, uint32_t serial, wl_surface* surface) {}
|
|
|
|
void WaylandNoGUIPlatform::KeyboardKey(void* data, wl_keyboard* keyboard, uint32_t serial, uint32_t time, uint32_t key,
|
|
uint32_t state)
|
|
{
|
|
const xkb_keycode_t keycode = static_cast<xkb_keycode_t>(key + 8);
|
|
const bool pressed = (state == WL_KEYBOARD_KEY_STATE_PRESSED);
|
|
NoGUIHost::ProcessPlatformKeyEvent(static_cast<s32>(keycode), pressed);
|
|
}
|
|
|
|
void WaylandNoGUIPlatform::KeyboardModifiers(void* data, wl_keyboard* keyboard, uint32_t serial,
|
|
uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked,
|
|
uint32_t group)
|
|
{
|
|
WaylandNoGUIPlatform* platform = static_cast<WaylandNoGUIPlatform*>(data);
|
|
xkb_state_update_mask(platform->m_xkb_state, mods_depressed, mods_latched, mods_locked, 0, 0, group);
|
|
}
|
|
|
|
void WaylandNoGUIPlatform::PointerEnter(void* data, wl_pointer* pointer, uint32_t serial, wl_surface* surface,
|
|
wl_fixed_t surface_x, wl_fixed_t surface_y)
|
|
{
|
|
}
|
|
|
|
void WaylandNoGUIPlatform::PointerLeave(void* data, wl_pointer* pointer, uint32_t serial, wl_surface* surface) {}
|
|
|
|
void WaylandNoGUIPlatform::PointerMotion(void* data, wl_pointer* pointer, uint32_t time, wl_fixed_t x, wl_fixed_t y)
|
|
{
|
|
const float pos_x = static_cast<float>(wl_fixed_to_double(x));
|
|
const float pos_y = static_cast<float>(wl_fixed_to_double(y));
|
|
NoGUIHost::ProcessPlatformMouseMoveEvent(static_cast<int>(pos_x), static_cast<int>(pos_y));
|
|
}
|
|
|
|
void WaylandNoGUIPlatform::PointerButton(void* data, wl_pointer* pointer, uint32_t serial, uint32_t time,
|
|
uint32_t button, uint32_t state)
|
|
{
|
|
if (button < BTN_MOUSE || (button - BTN_MOUSE) >= 32)
|
|
return;
|
|
|
|
const s32 button_index = (button - BTN_MOUSE);
|
|
const bool button_pressed = (state == WL_POINTER_BUTTON_STATE_PRESSED);
|
|
NoGUIHost::ProcessPlatformMouseButtonEvent(button_index, button_pressed);
|
|
}
|
|
|
|
void WaylandNoGUIPlatform::PointerAxis(void* data, wl_pointer* pointer, uint32_t time, uint32_t axis, wl_fixed_t value)
|
|
{
|
|
const float x = (axis == 1) ? std::clamp(static_cast<float>(wl_fixed_to_double(value)), -1.0f, 1.0f) : 0.0f;
|
|
const float y = (axis == 0) ? std::clamp(static_cast<float>(-wl_fixed_to_double(value)), -1.0f, 1.0f) : 0.0f;
|
|
NoGUIHost::ProcessPlatformMouseWheelEvent(x, y);
|
|
}
|
|
|
|
void WaylandNoGUIPlatform::RunMessageLoop()
|
|
{
|
|
while (m_message_loop_running.load(std::memory_order_acquire))
|
|
{
|
|
wl_display_dispatch_pending(m_display);
|
|
|
|
{
|
|
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 WaylandNoGUIPlatform::ExecuteInMessageLoop(std::function<void()> func)
|
|
{
|
|
std::unique_lock lock(m_callback_queue_mutex);
|
|
m_callback_queue.push_back(std::move(func));
|
|
}
|
|
|
|
void WaylandNoGUIPlatform::QuitMessageLoop()
|
|
{
|
|
m_message_loop_running.store(false, std::memory_order_release);
|
|
}
|
|
|
|
void WaylandNoGUIPlatform::SetFullscreen(bool enabled)
|
|
{
|
|
// how the heck can we do this?
|
|
}
|
|
|
|
bool WaylandNoGUIPlatform::RequestRenderWindowSize(s32 new_window_width, s32 new_window_height)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool WaylandNoGUIPlatform::OpenURL(const std::string_view& url)
|
|
{
|
|
Log_ErrorPrintf("WaylandNoGUIPlatform::OpenURL() not implemented: %.*s", static_cast<int>(url.size()), url.data());
|
|
return false;
|
|
}
|
|
|
|
bool WaylandNoGUIPlatform::CopyTextToClipboard(const std::string_view& text)
|
|
{
|
|
Log_ErrorPrintf("WaylandNoGUIPlatform::CopyTextToClipboard() not implemented: %.*s", static_cast<int>(text.size()), text.data());
|
|
return false;
|
|
}
|
|
|
|
std::unique_ptr<NoGUIPlatform> NoGUIPlatform::CreateWaylandPlatform()
|
|
{
|
|
std::unique_ptr<WaylandNoGUIPlatform> ret = std::unique_ptr<WaylandNoGUIPlatform>(new WaylandNoGUIPlatform());
|
|
if (!ret->Initialize())
|
|
return {};
|
|
|
|
return ret;
|
|
}
|