Compare commits

...
Author SHA1 Message Date
1c30b1bbf7
fix(font-atlas): wait for the GPU before the image goes; DOM stubs for SetAppId and SetTextInput
~FontAtlas destroyed the image while frames that sample it could still
run (HelloUI declares the atlas after the window, so it goes first); it
waits on the queue now, as the descriptor heap does. The DOM build got
the two new Window methods as no-ops, like the other backend-specific
ones.
2026-10-18 03:28:11 +02:00
1498a5bf4b
fix(wayland): text input enables once per focus; touches route by their finger's window
enable resets the input method's state, so sending it with every caret
move (an application keeps the cursor rectangle current while typing)
dropped the composition at each keystroke: enable now goes once per
focus and a moved caret sends its rectangle alone; disable only takes
back an enable. The compositor's leave ends both, and a window still
showing composing text hears onPreedit("") then. A finger's up and motion
go to the window it went down on, not the window of the latest down. The
seat creates its pointer and keyboard once, as it already did the touch:
capabilities come again when a device appears or goes, and a second
keyboard proxy delivered every key twice.
2026-10-18 03:28:02 +02:00
c8b5739e1a
fix(window): the destructor cancels the frame callback; a window without a display or swapchain
wl_surface_frame_done re-armed the frame callback through its local and
left the member stale, so the destructor could not cancel it and a done
already queued would dispatch into the freed window; the member now
names the live callback and the destructor destroys it. The destructor
also flushed a null display for a window created without
Device::Initialize (tests do that) and freed never-created command
buffers (a default-constructed Window after Initialize); both guarded.
And wp_scale was one inline static for every window, which the destructor
destroyed on behalf of all of them: it is a member now, like the viewport.
2026-10-18 03:27:50 +02:00
cc04d4e121
fix(wayland): constant-initialised statics in Device, for clang 23.1 on aarch64-musl
With the touch positions in an inline static std::unordered_map and the
text input's strings in inline static std::strings, clang 23.1 crashed
compiling Device.cpp for aarch64-alpine-linux-musl against that sysroot's
libc++ 22 — in LazyCallGraph at -O3, in the ThinLTO bitcode writer at -O0
(a dangling global); the native build was fine. The fingers now live in a
constant-initialised array of slots and the strings in function-local
statics of the implementation unit; nothing else changes.
2026-10-18 02:06:30 +02:00
0d8287c122
feat(wayland): Window::SetAppId
xdg_toplevel.set_app_id, so the compositor can match the window to its
.desktop file: the icon, the task switcher, activation.
2026-10-18 01:57:09 +02:00
678f38964f
feat(wayland): text input through zwp_text_input_v3
A window can now say that a text field has the focus and where its caret
is — Window::SetTextInput(active, caret rectangle in physical pixels,
purpose) — which is what opens the on-screen keyboard on a phone (Maliit
under KWin, squeekboard under phoc). What the input method produces comes
back as events: committed text as onTextInput, the composing text as
onPreedit (empty clears it), deletions around the cursor as
onTextInputDelete; done() applies them in the protocol's order. The state
a window wants is remembered and sent again whenever the keyboard focus
returns to it. The protocol's client code is generated with wayland-scanner
1.26.0 from wayland-protocols' text-input-unstable-v3.xml, under lib/ like
the others.
2026-10-18 01:57:09 +02:00
aed6142cce
feat(wayland): touch, with the first finger standing in for the pointer
The seat's wl_touch was never bound, so a touchscreen could not drive a
window at all. Every finger is now reported through onTouchDown, onTouchMove,
onTouchUp and onTouchCancel (TouchPoint: the compositor's id and the
position in physical pixels). The first finger down also enters the window —
Device::focusedWindow and the pointer serial the clipboard needs — presses
the left button, moves it and releases it when it lifts, so an application
written for a pointer works on a phone as it is.
2026-10-18 01:48:30 +02:00
b16f1e99d5
fix(font-atlas): the atlas destroys its image
FontAtlas::Initialize creates an ImageVulkan and nothing ever called its
Destroy(): every atlas left its 1024x1024 image, memory and view behind.
The atlas now owns the image for its lifetime and is no longer copyable.
ImageVulkan's handles start as VK_NULL_HANDLE and Destroy() resets them,
so an atlas that was never initialised, or an image destroyed by hand
before the atlas goes, is a no-op in the destructor.

The owner waits for the GPU before the atlas goes and, in sets mode,
destroys the descriptor heap that holds the image's view before the atlas.
2026-10-18 01:12:21 +02:00
6f5b192078
fix(compute): destroy the shader module once its pipeline exists
ComputeShader::Load built a VulkanShader on the stack and let it go with
its VkShaderModule still alive: nothing in the library destroys one. A
process that loads the UI shaders once never noticed; one that creates a
window again and again (an app that hides and re-shows its window) leaked
five modules per window. Vulkan allows destroying the module as soon as the
pipeline is created, so Load does, on success and on failure alike.
2026-10-18 01:12:21 +02:00
b412640fac
feat(window): a destructor for the native backends
A Window could be created but never destroyed: the constructor's command
buffers, semaphores, fences, swapchain and Vulkan surface, and on Wayland
the viewport, fractional scale, decoration, xdg toplevel, xdg surface and
wl_surface stayed alive, and Device::windows kept a dangling pointer the
pointer listeners still compared against. An app that hides its window
and shows it again — a resident messaging app closing to a notification
— needs the window to go for real.

The destructor waits for the device, frees and destroys the Vulkan objects
in dependency order (the swapchain before the surface), releases the
Wayland objects from the cursor down to the wl_surface, flushes, and
removes the window from Device::windows and the focus pointers. The
decoration object the constructor used to leak is a member now. The frame
callback member is left alone: wl_surface_frame_done destroys the callback
it is handed and re-arms through its own local, so the member may name a
freed one.
2026-10-18 00:40:25 +02:00
bfc5e108e5
fix(wayland): route keys by keyboard focus, not pointer focus
keyboard_enter and keyboard_leave were empty and keyboard_key dereferenced
Device::focusedWindow, which only a pointer-enter sets: the first key
event in a window the pointer never entered — every key on a touch phone,
or a key typed while the pointer hovers another window — dereferenced a
null pointer, and keys typed after the pointer left went nowhere.

Keyboard focus is its own thing in Wayland. wl_keyboard.enter now resolves
its surface to the Window into Device::keyboardWindow, leave clears it
together with the held keys (their releases happen elsewhere) and stops
the repeat, and keyboard_key and TickKeyRepeats deliver to that window and
drop a key that has none.
2026-10-17 23:48:01 +02:00
11 changed files with 1685 additions and 38 deletions

View file

@ -74,8 +74,12 @@ void ComputeShader::Load(const std::filesystem::path& spvPath, DescriptorHeapVul
}, },
.layout = layout, .layout = layout,
}; };
Device::CheckVkResult(vkCreateComputePipelines( const VkResult created = vkCreateComputePipelines(
Device::device, Device::pipelineCache, 1, &info, nullptr, &pipeline)); Device::device, Device::pipelineCache, 1, &info, nullptr, &pipeline);
// The module is only an input to pipeline creation; VulkanShader does
// not destroy it, so it is released here either way.
vkDestroyShaderModule(Device::device, shader.shader, nullptr);
Device::CheckVkResult(created);
return; return;
} }
@ -106,8 +110,10 @@ void ComputeShader::Load(const std::filesystem::path& spvPath, DescriptorHeapVul
}, },
.layout = VK_NULL_HANDLE, .layout = VK_NULL_HANDLE,
}; };
Device::CheckVkResult(vkCreateComputePipelines( const VkResult created = vkCreateComputePipelines(
Device::device, Device::pipelineCache, 1, &info, nullptr, &pipeline)); Device::device, Device::pipelineCache, 1, &info, nullptr, &pipeline);
vkDestroyShaderModule(Device::device, shader.shader, nullptr);
Device::CheckVkResult(created);
} }
void ComputeShader::Dispatch(VkCommandBuffer cmd, void ComputeShader::Dispatch(VkCommandBuffer cmd,

View file

@ -15,6 +15,7 @@ module;
#include "../lib/wayland-xdg-decoration-unstable-v1-client-protocol.h" #include "../lib/wayland-xdg-decoration-unstable-v1-client-protocol.h"
#include "../lib/fractional-scale-v1.h" #include "../lib/fractional-scale-v1.h"
#include "../lib/viewporter.h" #include "../lib/viewporter.h"
#include "../lib/text-input-unstable-v3-client-protocol.h"
#include <wayland-client.h> #include <wayland-client.h>
#include <wayland-client-protocol.h> #include <wayland-client-protocol.h>
#include <stdio.h> #include <stdio.h>
@ -303,6 +304,10 @@ void Device::handle_global(void *data, wl_registry *registry, std::uint32_t name
if (dataDeviceManager != nullptr && dataDevice == nullptr) { if (dataDeviceManager != nullptr && dataDevice == nullptr) {
dataDevice = wl_data_device_manager_get_data_device(dataDeviceManager, seat); dataDevice = wl_data_device_manager_get_data_device(dataDeviceManager, seat);
} }
if (textInputManager != nullptr && textInput == nullptr) {
textInput = zwp_text_input_manager_v3_get_text_input(textInputManager, seat);
zwp_text_input_v3_add_listener(textInput, &text_input_listener, nullptr);
}
} else if (compositor == nullptr && strcmp(interface, wl_compositor_interface.name) == 0) { } else if (compositor == nullptr && strcmp(interface, wl_compositor_interface.name) == 0) {
compositor = reinterpret_cast<wl_compositor*>(wl_registry_bind(registry, name, &wl_compositor_interface, 3)); compositor = reinterpret_cast<wl_compositor*>(wl_registry_bind(registry, name, &wl_compositor_interface, 3));
} else if (strcmp(interface, xdg_wm_base_interface.name) == 0) { } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
@ -324,7 +329,134 @@ void Device::handle_global(void *data, wl_registry *registry, std::uint32_t name
if (seat != nullptr && dataDevice == nullptr) { if (seat != nullptr && dataDevice == nullptr) {
dataDevice = wl_data_device_manager_get_data_device(dataDeviceManager, seat); dataDevice = wl_data_device_manager_get_data_device(dataDeviceManager, seat);
} }
} else if (strcmp(interface, zwp_text_input_manager_v3_interface.name) == 0) {
textInputManager = reinterpret_cast<zwp_text_input_manager_v3*>(wl_registry_bind(registry, name, &zwp_text_input_manager_v3_interface, 1));
if (seat != nullptr && textInput == nullptr) {
textInput = zwp_text_input_manager_v3_get_text_input(textInputManager, seat);
zwp_text_input_v3_add_listener(textInput, &text_input_listener, nullptr);
} }
}
}
namespace {
// A finger's slot by id, or a free one for a new finger; nullptr with more
// than ten fingers down.
Crafter::Device::TouchSlot* SlotFor(std::int32_t id) {
Crafter::Device::TouchSlot* free = nullptr;
for (Crafter::Device::TouchSlot& slot : Crafter::Device::touchSlots) {
if (slot.used && slot.id == id) return &slot;
if (!slot.used && free == nullptr) free = &slot;
}
if (free != nullptr) {
free->used = true;
free->id = id;
free->window = nullptr;
}
return free;
}
bool AnyTouch() {
for (const Crafter::Device::TouchSlot& slot : Crafter::Device::touchSlots) {
if (slot.used) return true;
}
return false;
}
// The text input's pending strings live here, as function-local statics:
// an inline static std::string in the interface made clang 23.1 crash
// in ThinLTO for aarch64-alpine-linux-musl.
struct TextInputStrings {
std::string preedit;
std::string shown; // what the window was last told
std::string commit;
};
TextInputStrings& TextStrings() {
static TextInputStrings strings;
return strings;
}
}
void Device::text_input_enter(void*, zwp_text_input_v3*, wl_surface* surface) {
textInputWindow = nullptr;
for (Window* window : windows) {
if (window->surface == surface) textInputWindow = window;
}
if (textInputWindow != nullptr && textInputWindow->textInputActive_) ApplyTextInput(textInputWindow);
}
// The compositor's leave ends the enable and the composition: the window
// hears that its composing text is gone, and the next enter enables anew.
void Device::text_input_leave(void*, zwp_text_input_v3*, wl_surface*) {
Window* window = textInputWindow;
textInputWindow = nullptr;
textInputHasPreedit = textInputHasCommit = textInputHasDelete = false;
TextStrings().preedit.clear();
if (window != nullptr) {
window->textInputEnabled_ = false;
if (!TextStrings().shown.empty()) window->onPreedit.Invoke(std::string_view());
}
TextStrings().shown.clear();
}
void Device::text_input_preedit(void*, zwp_text_input_v3*, const char* text, std::int32_t, std::int32_t) {
TextStrings().preedit = text == nullptr ? "" : text;
textInputHasPreedit = true;
}
void Device::text_input_commit(void*, zwp_text_input_v3*, const char* text) {
TextStrings().commit = text == nullptr ? "" : text;
textInputHasCommit = true;
}
void Device::text_input_delete(void*, zwp_text_input_v3*, std::uint32_t before, std::uint32_t after) {
textInputDeleteBefore = before;
textInputDeleteAfter = after;
textInputHasDelete = true;
}
// The protocol's order: the deletion around the cursor, then the committed
// text at the cursor, then the composing text after it. The pending values
// reset with every done, so a round that set no preedit means an empty one;
// the window hears about it only when that differs from what it shows.
void Device::text_input_done(void*, zwp_text_input_v3*, std::uint32_t) {
Window* window = textInputWindow;
TextInputStrings& strings = TextStrings();
if (!textInputHasPreedit) strings.preedit.clear();
if (window != nullptr) {
if (textInputHasDelete && (textInputDeleteBefore > 0 || textInputDeleteAfter > 0)) window->onTextInputDelete.Invoke({ textInputDeleteBefore, textInputDeleteAfter });
if (textInputHasCommit && !strings.commit.empty()) window->onTextInput.Invoke(strings.commit);
if (strings.preedit != strings.shown || (textInputHasCommit && !strings.commit.empty())) {
strings.shown = strings.preedit;
window->onPreedit.Invoke(strings.preedit);
}
}
textInputHasPreedit = textInputHasCommit = textInputHasDelete = false;
strings.commit.clear();
}
// enable resets the input method's state, so it goes once per focus; a
// caret that moved sends its rectangle alone.
void Device::ApplyTextInput(Window* window) {
if (textInput == nullptr || window == nullptr || textInputWindow != window) return;
if (window->textInputActive_) {
if (!window->textInputEnabled_) zwp_text_input_v3_enable(textInput);
window->textInputEnabled_ = true;
const bool phone = window->textInputPurpose_ == TextInputPurpose::Phone;
zwp_text_input_v3_set_content_type(textInput,
phone ? ZWP_TEXT_INPUT_V3_CONTENT_HINT_NONE : (ZWP_TEXT_INPUT_V3_CONTENT_HINT_COMPLETION | ZWP_TEXT_INPUT_V3_CONTENT_HINT_SPELLCHECK | ZWP_TEXT_INPUT_V3_CONTENT_HINT_AUTO_CAPITALIZATION),
phone ? ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PHONE : ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NORMAL);
const float s = window->scale > 0.0f ? window->scale : 1.0f;
zwp_text_input_v3_set_cursor_rectangle(textInput,
static_cast<std::int32_t>(window->textInputRect_[0] / s), static_cast<std::int32_t>(window->textInputRect_[1] / s),
std::max(1, static_cast<std::int32_t>(window->textInputRect_[2] / s)), std::max(1, static_cast<std::int32_t>(window->textInputRect_[3] / s)));
} else {
if (!window->textInputEnabled_) return;
window->textInputEnabled_ = false;
zwp_text_input_v3_disable(textInput);
}
zwp_text_input_v3_commit(textInput);
wl_display_flush(display);
} }
void Device::handle_global_remove(void* data, wl_registry* registry, uint32_t name) { void Device::handle_global_remove(void* data, wl_registry* registry, uint32_t name) {
@ -441,11 +573,23 @@ void Device::keyboard_keymap(void *data, wl_keyboard *keyboard, uint32_t format,
} }
void Device::keyboard_enter(void *data, wl_keyboard *keyboard, uint32_t serial, wl_surface *surface, wl_array *keys) { void Device::keyboard_enter(void *data, wl_keyboard *keyboard, uint32_t serial, wl_surface *surface, wl_array *keys) {
for (Window* window : windows) {
if (window->surface == surface) {
keyboardWindow = window;
return;
}
}
keyboardWindow = nullptr;
} }
void Device::keyboard_leave(void *data, wl_keyboard *keyboard, uint32_t serial, wl_surface *surface) { void Device::keyboard_leave(void *data, wl_keyboard *keyboard, uint32_t serial, wl_surface *surface) {
// Releases happen elsewhere while the focus is away: nothing we still
// hold is held, and a repeat must not keep firing into a window that
// lost the keyboard.
if (keyboardWindow != nullptr) keyboardWindow->heldKeys.clear();
keyboardWindow = nullptr;
keyRepeat.active = false;
keyRepeat.utf8.clear();
} }
void Device::keyboard_key(void *data, wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) { void Device::keyboard_key(void *data, wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) {
@ -456,12 +600,16 @@ void Device::keyboard_key(void *data, wl_keyboard *keyboard, uint32_t serial, ui
KeyCode code = key; KeyCode code = key;
xkb_keycode_t xkbKeycode = key + 8; xkb_keycode_t xkbKeycode = key + 8;
// No enter for any of our surfaces (a compositor quirk, or a key for a
// window already gone): nowhere to deliver it.
if (keyboardWindow == nullptr) return;
if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
if (focusedWindow->heldKeys.contains(code)) { if (keyboardWindow->heldKeys.contains(code)) {
focusedWindow->onRawKeyHold.Invoke(code); keyboardWindow->onRawKeyHold.Invoke(code);
} else { } else {
focusedWindow->heldKeys.insert(code); keyboardWindow->heldKeys.insert(code);
focusedWindow->onRawKeyDown.Invoke(code); keyboardWindow->onRawKeyDown.Invoke(code);
} }
std::string buf; std::string buf;
@ -471,7 +619,7 @@ void Device::keyboard_key(void *data, wl_keyboard *keyboard, uint32_t serial, ui
if (n > 0 && (unsigned char)buf[0] >= 0x20 && buf[0] != 0x7f) { if (n > 0 && (unsigned char)buf[0] >= 0x20 && buf[0] != 0x7f) {
buf.resize(n); buf.resize(n);
utf8 = buf; utf8 = buf;
focusedWindow->onTextInput.Invoke(utf8); keyboardWindow->onTextInput.Invoke(utf8);
} }
// Replace the active repeat with this key — most recent press wins, // Replace the active repeat with this key — most recent press wins,
@ -482,8 +630,8 @@ void Device::keyboard_key(void *data, wl_keyboard *keyboard, uint32_t serial, ui
keyRepeat.pressTime = std::chrono::steady_clock::now(); keyRepeat.pressTime = std::chrono::steady_clock::now();
keyRepeat.lastFireTime = keyRepeat.pressTime; keyRepeat.lastFireTime = keyRepeat.pressTime;
} else { } else {
focusedWindow->heldKeys.erase(code); keyboardWindow->heldKeys.erase(code);
focusedWindow->onRawKeyUp.Invoke(code); keyboardWindow->onRawKeyUp.Invoke(code);
// If the released key was the one repeating, stop. Otherwise leave // If the released key was the one repeating, stop. Otherwise leave
// the existing repeat alone (user pressed/released a modifier // the existing repeat alone (user pressed/released a modifier
@ -506,7 +654,7 @@ void Device::keyboard_repeat_info(void *data, wl_keyboard *keyboard, int32_t rat
} }
void Device::TickKeyRepeats() { void Device::TickKeyRepeats() {
if (!keyRepeat.active || !focusedWindow) return; if (!keyRepeat.active || !keyboardWindow) return;
if (keyRepeat.rate <= 0) return; if (keyRepeat.rate <= 0) return;
auto now = std::chrono::steady_clock::now(); auto now = std::chrono::steady_clock::now();
@ -521,24 +669,111 @@ void Device::TickKeyRepeats() {
// Catch up — emit one event per missed period so a paused frame doesn't // Catch up — emit one event per missed period so a paused frame doesn't
// make the repeat permanently lag behind. // make the repeat permanently lag behind.
while (now - keyRepeat.lastFireTime >= period) { while (now - keyRepeat.lastFireTime >= period) {
focusedWindow->onRawKeyDown.Invoke(keyRepeat.key); keyboardWindow->onRawKeyDown.Invoke(keyRepeat.key);
focusedWindow->onRawKeyHold.Invoke(keyRepeat.key); keyboardWindow->onRawKeyHold.Invoke(keyRepeat.key);
if (!keyRepeat.utf8.empty()) { if (!keyRepeat.utf8.empty()) {
focusedWindow->onTextInput.Invoke(keyRepeat.utf8); keyboardWindow->onTextInput.Invoke(keyRepeat.utf8);
} }
keyRepeat.lastFireTime += period; keyRepeat.lastFireTime += period;
} }
} }
void Device::seat_handle_capabilities(void* data, wl_seat* seat, uint32_t capabilities) { void Device::ForgetTouches() {
seat = seat; for (TouchSlot& slot : touchSlots) slot = TouchSlot{};
if (capabilities & WL_SEAT_CAPABILITY_POINTER) { }
wl_pointer* pointer = wl_seat_get_pointer(seat);
wl_pointer_add_listener(pointer, &pointer_listener, nullptr); // The first finger stands in for the pointer: it enters the window (so the
// clipboard has a serial and Device::focusedWindow a window), presses the
// left button, moves it and releases it. Every finger is also reported as
// such, in physical pixels.
void Device::touch_down(void*, wl_touch*, std::uint32_t serial, std::uint32_t, wl_surface* surface, std::int32_t id, wl_fixed_t x, wl_fixed_t y) {
Window* window = nullptr;
for (Window* candidate : windows) {
if (candidate->surface == surface) window = candidate;
} }
if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) { if (window == nullptr) return;
wl_keyboard* keyboard = wl_seat_get_keyboard(seat); touchWindow = window;
wl_keyboard_add_listener(keyboard, &keyboard_listener, nullptr); window->lastPointerSerial_ = serial;
const Vector<float, 2> pos = Vector<float, 2>(wl_fixed_to_double(x), wl_fixed_to_double(y)) * window->scale;
if (TouchSlot* slot = SlotFor(id)) {
slot->x = pos.x;
slot->y = pos.y;
slot->window = window;
}
window->onTouchDown.Invoke({ id, pos });
if (window->primaryTouchId < 0) {
window->primaryTouchId = id;
focusedWindow = window;
window->currentMousePos = pos;
window->mouseLeftHeld = true;
window->onMouseLeftClick.Invoke();
}
}
void Device::touch_up(void*, wl_touch*, std::uint32_t, std::uint32_t, std::int32_t id) {
if (touchWindow == nullptr) return;
Window* window = touchWindow;
Vector<float, 2> pos = window->currentMousePos;
if (TouchSlot* slot = SlotFor(id)) {
if (slot->window != nullptr) window = slot->window;
pos = Vector<float, 2>(slot->x, slot->y);
*slot = TouchSlot{};
}
window->onTouchUp.Invoke({ id, pos });
if (id == window->primaryTouchId) {
window->primaryTouchId = -1;
window->mouseLeftHeld = false;
window->onMouseLeftRelease.Invoke();
}
if (!AnyTouch()) touchWindow = nullptr;
}
void Device::touch_motion(void*, wl_touch*, std::uint32_t, std::int32_t id, wl_fixed_t x, wl_fixed_t y) {
if (touchWindow == nullptr) return;
Window* window = touchWindow;
TouchSlot* slot = SlotFor(id);
if (slot != nullptr && slot->window != nullptr) window = slot->window;
const Vector<float, 2> pos = Vector<float, 2>(wl_fixed_to_double(x), wl_fixed_to_double(y)) * window->scale;
if (slot != nullptr) {
slot->x = pos.x;
slot->y = pos.y;
}
window->onTouchMove.Invoke({ id, pos });
if (id == window->primaryTouchId) {
window->currentMousePos = pos;
window->onMouseMove.Invoke();
}
}
void Device::touch_frame(void*, wl_touch*) {}
void Device::touch_cancel(void*, wl_touch*) {
if (touchWindow == nullptr) return;
Window* window = touchWindow;
ForgetTouches();
touchWindow = nullptr;
if (window->primaryTouchId >= 0) {
window->primaryTouchId = -1;
window->mouseLeftHeld = false;
window->onMouseLeftRelease.Invoke();
}
window->onTouchCancel.Invoke();
}
// Capabilities come again when a device appears or goes; each object is
// created once, or every event would arrive once per copy.
void Device::seat_handle_capabilities(void*, wl_seat* seat, uint32_t capabilities) {
if ((capabilities & WL_SEAT_CAPABILITY_POINTER) && seatPointer == nullptr) {
seatPointer = wl_seat_get_pointer(seat);
wl_pointer_add_listener(seatPointer, &pointer_listener, nullptr);
}
if ((capabilities & WL_SEAT_CAPABILITY_TOUCH) && wlTouch == nullptr) {
wlTouch = wl_seat_get_touch(seat);
wl_touch_add_listener(wlTouch, &touch_listener, nullptr);
}
if ((capabilities & WL_SEAT_CAPABILITY_KEYBOARD) && seatKeyboard == nullptr) {
seatKeyboard = wl_seat_get_keyboard(seat);
wl_keyboard_add_listener(seatKeyboard, &keyboard_listener, nullptr);
} }
} }

View file

@ -11,6 +11,7 @@ module;
#include "../lib/wayland-xdg-decoration-unstable-v1-client-protocol.h" #include "../lib/wayland-xdg-decoration-unstable-v1-client-protocol.h"
#include "../lib/fractional-scale-v1.h" #include "../lib/fractional-scale-v1.h"
#include "../lib/viewporter.h" #include "../lib/viewporter.h"
#include "../lib/text-input-unstable-v3-client-protocol.h"
#include <string.h> #include <string.h>
#include <cassert> #include <cassert>
#include <linux/input.h> #include <linux/input.h>
@ -299,7 +300,7 @@ Window::Window(std::uint32_t width, std::uint32_t height) : width(width), height
wl_surface_commit(surface); wl_surface_commit(surface);
zxdg_toplevel_decoration_v1* decoration = zxdg_decoration_manager_v1_get_toplevel_decoration(Device::manager, xdgToplevel); decoration = zxdg_decoration_manager_v1_get_toplevel_decoration(Device::manager, xdgToplevel);
zxdg_toplevel_decoration_v1_set_mode(decoration, ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); zxdg_toplevel_decoration_v1_set_mode(decoration, ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
wpViewport = wp_viewporter_get_viewport(Device::wpViewporter, surface); wpViewport = wp_viewporter_get_viewport(Device::wpViewporter, surface);
@ -515,6 +516,58 @@ Window::Window(std::uint32_t width, std::uint32_t height) : width(width), height
currentMousePos = {0,0}; currentMousePos = {0,0};
} }
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
Window::~Window() {
// Nothing may still run against the semaphores, fences or images below.
if (Device::device != VK_NULL_HANDLE) vkDeviceWaitIdle(Device::device);
if (Device::device != VK_NULL_HANDLE) {
if (drawCmdBuffers[0] != VK_NULL_HANDLE) vkFreeCommandBuffers(Device::device, Device::commandPool, numFrames, drawCmdBuffers);
for (std::uint8_t i = 0; i < numFrames; i++) {
if (renderFinishedSemaphores[i]) vkDestroySemaphore(Device::device, renderFinishedSemaphores[i], nullptr);
if (waitFences[i]) vkDestroyFence(Device::device, waitFences[i], nullptr);
}
for (std::uint8_t i = 0; i < numAcquireSemaphores; i++) {
if (imageAcquiredSemaphores[i]) vkDestroySemaphore(Device::device, imageAcquiredSemaphores[i], nullptr);
}
if (swapChain != VK_NULL_HANDLE) vkDestroySwapchainKHR(Device::device, swapChain, nullptr);
swapChain = VK_NULL_HANDLE;
}
if (vulkanSurface != VK_NULL_HANDLE) vkDestroySurfaceKHR(Device::instance, vulkanSurface, nullptr);
vulkanSurface = VK_NULL_HANDLE;
#ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND
SetDefaultCursor();
// A pending frame callback would still dispatch into this window;
// destroying the proxy makes libwayland drop its event.
if (cb) wl_callback_destroy(cb);
cb = nullptr;
if (wpViewport) wp_viewport_destroy(wpViewport);
if (wp_scale) {
wp_fractional_scale_v1_destroy(wp_scale);
wp_scale = nullptr;
}
if (decoration) zxdg_toplevel_decoration_v1_destroy(decoration);
if (xdgToplevel) xdg_toplevel_destroy(xdgToplevel);
if (xdgSurface) xdg_surface_destroy(xdgSurface);
if (surface) wl_surface_destroy(surface);
wpViewport = nullptr;
decoration = nullptr;
xdgToplevel = nullptr;
xdgSurface = nullptr;
surface = nullptr;
if (Device::display) wl_display_flush(Device::display);
std::erase(Device::windows, this);
if (Device::focusedWindow == this) Device::focusedWindow = nullptr;
if (Device::keyboardWindow == this) Device::keyboardWindow = nullptr;
if (Device::touchWindow == this) {
Device::touchWindow = nullptr;
Device::ForgetTouches();
}
if (Device::textInputWindow == this) Device::textInputWindow = nullptr;
#endif
}
#endif
void Window::Resize(std::uint32_t newWidth, std::uint32_t newHeight) { void Window::Resize(std::uint32_t newWidth, std::uint32_t newHeight) {
// Skip degenerate resizes. Win32 minimised windows give (0, 0); Wayland // Skip degenerate resizes. Win32 minimised windows give (0, 0); Wayland
// sometimes echoes the current size in a configure. // sometimes echoes the current size in a configure.
@ -568,6 +621,24 @@ void Window::SetTitle(const std::string_view title) {
#endif #endif
} }
void Window::SetAppId(const std::string_view id) {
#ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND
xdg_toplevel_set_app_id(xdgToplevel, std::string(id).c_str());
#endif
}
void Window::SetTextInput(bool active, std::int32_t x, std::int32_t y, std::int32_t width, std::int32_t height, TextInputPurpose purpose) {
#ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND
textInputActive_ = active;
textInputRect_[0] = x;
textInputRect_[1] = y;
textInputRect_[2] = width;
textInputRect_[3] = height;
textInputPurpose_ = purpose;
Device::ApplyTextInput(this);
#endif
}
void Window::SetCursorImage(std::uint16_t width, std::uint16_t height, void Window::SetCursorImage(std::uint16_t width, std::uint16_t height,
std::uint16_t hotspotX, std::uint16_t hotspotY, std::uint16_t hotspotX, std::uint16_t hotspotY,
const std::uint8_t* pixels) { const std::uint8_t* pixels) {
@ -1229,12 +1300,13 @@ void Window::wl_surface_frame_done(void* data, struct wl_callback *cb, uint32_t
wl_callback_destroy(cb); wl_callback_destroy(cb);
Window* window = reinterpret_cast<Window*>(data); Window* window = reinterpret_cast<Window*>(data);
// The member names the live callback, so the destructor can cancel it.
if(window->updating) { if(window->updating) {
cb = wl_surface_frame(window->surface); window->cb = wl_surface_frame(window->surface);
wl_callback_add_listener(cb, &Window::wl_callback_listener, window); wl_callback_add_listener(window->cb, &Window::wl_callback_listener, window);
window->Update(); window->Update();
} else { } else {
cb = nullptr; window->cb = nullptr;
} }
} }
@ -1568,6 +1640,10 @@ void Window::SetTitle(const std::string_view title) {
Crafter::DomEnv::domSetTitle(title.data(), static_cast<std::int32_t>(title.size())); Crafter::DomEnv::domSetTitle(title.data(), static_cast<std::int32_t>(title.size()));
} }
void Window::SetAppId(const std::string_view) {}
void Window::SetTextInput(bool, std::int32_t, std::int32_t, std::int32_t, std::int32_t, TextInputPurpose) {}
void Window::Resize(std::uint32_t newWidth, std::uint32_t newHeight) { void Window::Resize(std::uint32_t newWidth, std::uint32_t newHeight) {
if (newWidth == 0 || newHeight == 0) return; if (newWidth == 0 || newHeight == 0) return;
if (newWidth == width && newHeight == height) return; if (newWidth == width && newHeight == height) return;

View file

@ -12,6 +12,7 @@ module;
#include "../lib/wayland-xdg-decoration-unstable-v1-client-protocol.h" #include "../lib/wayland-xdg-decoration-unstable-v1-client-protocol.h"
#include "../lib/fractional-scale-v1.h" #include "../lib/fractional-scale-v1.h"
#include "../lib/viewporter.h" #include "../lib/viewporter.h"
#include "../lib/text-input-unstable-v3-client-protocol.h"
#include <xkbcommon/xkbcommon.h> #include <xkbcommon/xkbcommon.h>
#endif #endif
export module Crafter.Graphics:Device; export module Crafter.Graphics:Device;
@ -49,12 +50,32 @@ export namespace Crafter {
inline static xkb_keymap* xkb_keymap; inline static xkb_keymap* xkb_keymap;
inline static wl_compositor* compositor = nullptr; inline static wl_compositor* compositor = nullptr;
inline static Window* focusedWindow = nullptr; inline static Window* focusedWindow = nullptr;
// The window with keyboard focus (wl_keyboard.enter/leave), apart from
// the pointer's: a key arrives without the pointer ever having entered
// (a touch phone, a key typed while the pointer hovers elsewhere), so the
// keyboard handlers route by this and never by focusedWindow.
inline static Window* keyboardWindow = nullptr;
inline static wl_shm* shm = nullptr; inline static wl_shm* shm = nullptr;
inline static wp_viewporter* wpViewporter = nullptr; inline static wp_viewporter* wpViewporter = nullptr;
inline static xkb_context* xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); inline static xkb_context* xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
inline static xkb_state* xkb_state; inline static xkb_state* xkb_state;
inline static std::vector<Window*> windows; inline static std::vector<Window*> windows;
inline static wl_pointer* wlPointer; inline static wl_pointer* wlPointer;
inline static wl_pointer* seatPointer = nullptr;
inline static wl_keyboard* seatKeyboard = nullptr;
inline static wl_touch* wlTouch = nullptr;
inline static Window* touchWindow = nullptr; // the window under the fingers
// The fingers down, by the compositor's id, in physical pixels. Plain
// data: the statics here are constant-initialised on purpose.
struct TouchSlot {
std::int32_t id;
float x;
float y;
bool used;
Window* window;
};
inline static std::array<TouchSlot, 10> touchSlots{};
static void ForgetTouches();
// wl_data_device_manager + wl_data_device drive copy/paste. Bound // wl_data_device_manager + wl_data_device drive copy/paste. Bound
// lazily in handle_global; the data device is created once both // lazily in handle_global; the data device is created once both
// the manager and the seat are present (registry binding order // the manager and the seat are present (registry binding order
@ -68,6 +89,19 @@ export namespace Crafter {
// whole detent yet is carried here so smooth-scroll devices add up // whole detent yet is carried here so smooth-scroll devices add up
// across events. Reset on pointer leave. // across events. Reset on pointer leave.
inline static double scrollDetentRemainder = 0.0; inline static double scrollDetentRemainder = 0.0;
// zwp_text_input_v3: one object per seat; the compositor says which
// surface it follows (enter/leave), the events are state that done()
// applies as a whole.
inline static zwp_text_input_manager_v3* textInputManager = nullptr;
inline static zwp_text_input_v3* textInput = nullptr;
inline static Window* textInputWindow = nullptr;
inline static std::uint32_t textInputDeleteBefore = 0;
inline static std::uint32_t textInputDeleteAfter = 0;
inline static bool textInputHasPreedit = false;
inline static bool textInputHasCommit = false;
inline static bool textInputHasDelete = false;
// Sends the window's wanted text-input state when it has the focus.
static void ApplyTextInput(Window* window);
static void seat_handle_capabilities(void* data, wl_seat* seat, uint32_t capabilities); static void seat_handle_capabilities(void* data, wl_seat* seat, uint32_t capabilities);
static void xdg_surface_handle_preferred_scale(void* data, wp_fractional_scale_v1*, std::uint32_t scale); static void xdg_surface_handle_preferred_scale(void* data, wp_fractional_scale_v1*, std::uint32_t scale);
@ -85,6 +119,17 @@ export namespace Crafter {
static void PointerListenerHandleAxis(void*, wl_pointer*, std::uint32_t time, std::uint32_t axis, wl_fixed_t value); static void PointerListenerHandleAxis(void*, wl_pointer*, std::uint32_t time, std::uint32_t axis, wl_fixed_t value);
static void PointerListenerHandleEnter(void* data, wl_pointer* wl_pointer, std::uint32_t serial, wl_surface* surface, wl_fixed_t surface_x, wl_fixed_t surface_y); static void PointerListenerHandleEnter(void* data, wl_pointer* wl_pointer, std::uint32_t serial, wl_surface* surface, wl_fixed_t surface_x, wl_fixed_t surface_y);
static void PointerListenerHandleLeave(void*, wl_pointer*, std::uint32_t, wl_surface*); static void PointerListenerHandleLeave(void*, wl_pointer*, std::uint32_t, wl_surface*);
static void touch_down(void* data, wl_touch* touch, std::uint32_t serial, std::uint32_t time, wl_surface* surface, std::int32_t id, wl_fixed_t x, wl_fixed_t y);
static void touch_up(void* data, wl_touch* touch, std::uint32_t serial, std::uint32_t time, std::int32_t id);
static void touch_motion(void* data, wl_touch* touch, std::uint32_t time, std::int32_t id, wl_fixed_t x, wl_fixed_t y);
static void touch_frame(void* data, wl_touch* touch);
static void touch_cancel(void* data, wl_touch* touch);
static void text_input_enter(void* data, zwp_text_input_v3* textInput, wl_surface* surface);
static void text_input_leave(void* data, zwp_text_input_v3* textInput, wl_surface* surface);
static void text_input_preedit(void* data, zwp_text_input_v3* textInput, const char* text, std::int32_t cursorBegin, std::int32_t cursorEnd);
static void text_input_commit(void* data, zwp_text_input_v3* textInput, const char* text);
static void text_input_delete(void* data, zwp_text_input_v3* textInput, std::uint32_t before, std::uint32_t after);
static void text_input_done(void* data, zwp_text_input_v3* textInput, std::uint32_t serial);
constexpr static wl_pointer_listener pointer_listener = { constexpr static wl_pointer_listener pointer_listener = {
.enter = PointerListenerHandleEnter, .enter = PointerListenerHandleEnter,
@ -93,6 +138,21 @@ export namespace Crafter {
.button = pointer_handle_button, .button = pointer_handle_button,
.axis = PointerListenerHandleAxis, .axis = PointerListenerHandleAxis,
}; };
constexpr static wl_touch_listener touch_listener = {
.down = touch_down,
.up = touch_up,
.motion = touch_motion,
.frame = touch_frame,
.cancel = touch_cancel,
};
constexpr static zwp_text_input_v3_listener text_input_listener = {
.enter = text_input_enter,
.leave = text_input_leave,
.preedit_string = text_input_preedit,
.commit_string = text_input_commit,
.delete_surrounding_text = text_input_delete,
.done = text_input_done,
};
constexpr static wl_keyboard_listener keyboard_listener = { constexpr static wl_keyboard_listener keyboard_listener = {
.keymap = keyboard_keymap, .keymap = keyboard_keymap,
.enter = keyboard_enter, .enter = keyboard_enter,

View file

@ -96,6 +96,20 @@ export namespace Crafter {
bool dirty = false; bool dirty = false;
DirtyRect dirtyRect; DirtyRect dirtyRect;
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
FontAtlas() = default;
FontAtlas(const FontAtlas&) = delete;
FontAtlas& operator=(const FontAtlas&) = delete;
// The image goes with the atlas, once the GPU is done with it; in
// sets mode the owner destroys the heap holding the image's view
// before the atlas.
~FontAtlas() {
if (image.image == VK_NULL_HANDLE) return;
if (Device::queue != VK_NULL_HANDLE) vkQueueWaitIdle(Device::queue);
image.Destroy();
}
#endif
void Initialize(GraphicsCommandBuffer cmd); void Initialize(GraphicsCommandBuffer cmd);
// Returns the row-major byte pointer the CPU writes pixels into. // Returns the row-major byte pointer the CPU writes pixels into.

View file

@ -56,8 +56,8 @@ export namespace Crafter {
std::uint16_t width; std::uint16_t width;
std::uint16_t height; std::uint16_t height;
std::uint8_t mipLevels; std::uint8_t mipLevels;
VkImage image; VkImage image = VK_NULL_HANDLE;
VkDeviceMemory imageMemory; VkDeviceMemory imageMemory = VK_NULL_HANDLE;
VulkanBuffer<PixelType, true> buffer; VulkanBuffer<PixelType, true> buffer;
// Transient host-visible staging for the compressed Update path. Same // Transient host-visible staging for the compressed Update path. Same
// lifetime contract as Mesh::compressedStaging: the compressed Update // lifetime contract as Mesh::compressedStaging: the compressed Update
@ -66,7 +66,7 @@ export namespace Crafter {
// submit's frame has cleared instead of pinning it for the image's life. // submit's frame has cleared instead of pinning it for the image's life.
// Between Updates the handle is null; the next Update's Resize re-creates it. // Between Updates the handle is null; the next Update's Resize re-creates it.
VulkanBuffer<std::byte, true> compressedStaging; VulkanBuffer<std::byte, true> compressedStaging;
VkImageView imageView; VkImageView imageView = VK_NULL_HANDLE;
VkDescriptorImageInfo descriptor; VkDescriptorImageInfo descriptor;
// Pipeline stage that samples this image after upload. Set once at // Pipeline stage that samples this image after upload. Set once at
// Create and reused by every Update so the upload barrier's dst (and // Create and reused by every Update so the upload barrier's dst (and
@ -378,6 +378,9 @@ export namespace Crafter {
vkDestroyImageView(Device::device, imageView, nullptr); vkDestroyImageView(Device::device, imageView, nullptr);
vkDestroyImage(Device::device, image, nullptr); vkDestroyImage(Device::device, image, nullptr);
vkFreeMemory(Device::device, imageMemory, nullptr); vkFreeMemory(Device::device, imageMemory, nullptr);
imageView = VK_NULL_HANDLE;
image = VK_NULL_HANDLE;
imageMemory = VK_NULL_HANDLE;
// Free any staging this image still owns: a streamed image's // Free any staging this image still owns: a streamed image's
// persistent `buffer`, or either staging buffer on an image // persistent `buffer`, or either staging buffer on an image
// destroyed before its first upload released them (issue #114). // destroyed before its first upload released them (issue #114).

View file

@ -18,6 +18,22 @@ export namespace Crafter {
Vector<float, 2> mouseDelta; Vector<float, 2> mouseDelta;
}; };
// One finger on a touchscreen, in the window's physical pixels; the id is
// the compositor's and stays with the finger until it lifts.
struct TouchPoint {
std::int32_t id;
Vector<float, 2> position;
};
// The input method asks for bytes around the cursor to go (text-input-v3
// counts UTF-8 bytes).
struct TextDelete {
std::uint32_t beforeBytes;
std::uint32_t afterBytes;
};
enum class TextInputPurpose : std::uint8_t { Normal, Phone };
struct ScaleData2D { struct ScaleData2D {
Vector<std::int16_t, 2, 2> position; Vector<std::int16_t, 2, 2> position;
Vector<std::int16_t, 2, 2> size; Vector<std::int16_t, 2, 2> size;

View file

@ -13,6 +13,7 @@ module;
#include "../lib/wayland-xdg-decoration-unstable-v1-client-protocol.h" #include "../lib/wayland-xdg-decoration-unstable-v1-client-protocol.h"
#include "../lib/fractional-scale-v1.h" #include "../lib/fractional-scale-v1.h"
#include "../lib/viewporter.h" #include "../lib/viewporter.h"
#include "../lib/text-input-unstable-v3-client-protocol.h"
#include <string.h> #include <string.h>
#include <linux/input.h> #include <linux/input.h>
#include <sys/mman.h> #include <sys/mman.h>
@ -98,6 +99,19 @@ export namespace Crafter {
// touchpads, free-spinning wheels) is accumulated backend-side // touchpads, free-spinning wheels) is accumulated backend-side
// until a whole detent is reached. // until a whole detent is reached.
Event<std::uint32_t> onMouseScroll; Event<std::uint32_t> onMouseScroll;
// Touch, from the seat's wl_touch. The first finger down also drives
// currentMousePos and the left-button events until it lifts, so a
// pointer-only application works on a touchscreen as it is.
Event<TouchPoint> onTouchDown;
Event<TouchPoint> onTouchMove;
Event<TouchPoint> onTouchUp;
Event<void> onTouchCancel;
std::int32_t primaryTouchId = -1;
// Text input (zwp_text_input_v3): the input method's composing text
// (an empty string clears it) and its deletions around the cursor;
// the text it commits arrives as onTextInput, like a key's.
Event<const std::string_view> onPreedit;
Event<TextDelete> onTextInputDelete;
Vector<float, 2> currentMousePos; Vector<float, 2> currentMousePos;
Vector<float, 2> lastMousePos; Vector<float, 2> lastMousePos;
Vector<float, 2> mouseDelta; Vector<float, 2> mouseDelta;
@ -110,16 +124,19 @@ export namespace Crafter {
Window(Window&) = delete; Window(Window&) = delete;
Window(Window&&) = delete; Window(Window&&) = delete;
Window& operator=(const Window&) = delete; Window& operator=(const Window&) = delete;
#ifdef CRAFTER_GRAPHICS_WINDOW_DOM
// DOM mode keeps a process-global pointer to the live Window so // DOM mode keeps a process-global pointer to the live Window so
// the JS bridge can deliver document/window events back. If the // the JS bridge can deliver document/window events back. If the
// Window is destroyed (e.g. it was stack-allocated in main and // Window is destroyed (e.g. it was stack-allocated in main and
// main returned), the global must be cleared so subsequent // main returned), the global must be cleared so subsequent
// browser callbacks become no-ops instead of dereferencing // browser callbacks become no-ops instead of dereferencing
// freed memory. Native builds have nothing to clean up at the // freed memory. Native builds release what the constructor made:
// Window level (Vulkan resources teardown is the user's job). // after a device wait, the command buffers, semaphores and fences,
// the swapchain and the Vulkan surface, then (Wayland) the cursor,
// viewport, fractional scale, decoration, xdg toplevel, xdg surface
// and wl_surface, and the window leaves Device::windows and the
// focus pointers — so a window can be closed and another created,
// which a resident app does on every hide and show.
~Window(); ~Window();
#endif
// Enter the platform event loop and never return. In DOM mode this // Enter the platform event loop and never return. In DOM mode this
// hands the loop to requestAnimationFrame and _Exit(0)s so the wasm // hands the loop to requestAnimationFrame and _Exit(0)s so the wasm
@ -140,6 +157,14 @@ export namespace Crafter {
void StartUpdate(); void StartUpdate();
void StopUpdate(); void StopUpdate();
void SetTitle(const std::string_view title); void SetTitle(const std::string_view title);
// The xdg app id, which the compositor matches to a .desktop file
// for the icon, the task switcher and activation.
void SetAppId(const std::string_view id);
// Tells the compositor's input method that a text field has focus
// (an on-screen keyboard opens) and where its caret is, in physical
// pixels, or that none has. Remembered: it is applied again whenever
// the keyboard focus comes back to this window.
void SetTextInput(bool active, std::int32_t x = 0, std::int32_t y = 0, std::int32_t width = 0, std::int32_t height = 0, TextInputPurpose purpose = TextInputPurpose::Normal);
void Resize(std::uint32_t width, std::uint32_t height); void Resize(std::uint32_t width, std::uint32_t height);
void Render(); void Render();
void Update(); void Update();
@ -191,6 +216,7 @@ export namespace Crafter {
std::int32_t pendingLogicalWidth = 0; std::int32_t pendingLogicalWidth = 0;
std::int32_t pendingLogicalHeight = 0; std::int32_t pendingLogicalHeight = 0;
xdg_toplevel* xdgToplevel = nullptr; xdg_toplevel* xdgToplevel = nullptr;
zxdg_toplevel_decoration_v1* decoration = nullptr;
wp_viewport* wpViewport = nullptr; wp_viewport* wpViewport = nullptr;
wl_surface* surface = nullptr; wl_surface* surface = nullptr;
xdg_surface* xdgSurface = nullptr; xdg_surface* xdgSurface = nullptr;
@ -207,6 +233,10 @@ export namespace Crafter {
// Needed so `SetCursorImage` can re-issue `wl_pointer_set_cursor` // Needed so `SetCursorImage` can re-issue `wl_pointer_set_cursor`
// mid-session (the hotspot only updates when set_cursor is recalled). // mid-session (the hotspot only updates when set_cursor is recalled).
std::uint32_t lastPointerSerial_ = 0; std::uint32_t lastPointerSerial_ = 0;
bool textInputActive_ = false;
bool textInputEnabled_ = false; // enable sent and not taken back since
std::int32_t textInputRect_[4] = { 0, 0, 0, 0 };
TextInputPurpose textInputPurpose_ = TextInputPurpose::Normal;
static void xdg_surface_handle_preferred_scale(void* data, wp_fractional_scale_v1*, std::uint32_t scale); static void xdg_surface_handle_preferred_scale(void* data, wp_fractional_scale_v1*, std::uint32_t scale);
static void wl_surface_frame_done(void *data, wl_callback *cb, uint32_t time); static void wl_surface_frame_done(void *data, wl_callback *cb, uint32_t time);
@ -227,7 +257,7 @@ export namespace Crafter {
constexpr static wp_fractional_scale_v1_listener wp_fractional_scale_v1_listener = { constexpr static wp_fractional_scale_v1_listener wp_fractional_scale_v1_listener = {
.preferred_scale = xdg_surface_handle_preferred_scale, .preferred_scale = xdg_surface_handle_preferred_scale,
}; };
inline static wp_fractional_scale_v1* wp_scale = nullptr; wp_fractional_scale_v1* wp_scale = nullptr;
#endif #endif
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM #ifndef CRAFTER_GRAPHICS_WINDOW_DOM
@ -269,7 +299,7 @@ export namespace Crafter {
// lazily in Render() (post-acquire) rather than up front. // lazily in Render() (post-acquire) rather than up front.
std::array<bool, numFrames> imageInitialised{}; std::array<bool, numFrames> imageInitialised{};
std::thread thread; std::thread thread;
VkCommandBuffer drawCmdBuffers[numFrames]; VkCommandBuffer drawCmdBuffers[numFrames] = {};
// Per-frame Vulkan info structs whose contents are almost entirely // Per-frame Vulkan info structs whose contents are almost entirely
// invariant. Initialised once in the constructor; Render() patches only // invariant. Initialised once in the constructor; Render() patches only
// the few fields that change each frame (the two barriers' image and // the few fields that change each frame (the two barriers' image and

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,103 @@
/* Generated by wayland-scanner 1.26.0 */
/*
* Copyright © 2012, 2013 Intel Corporation
* Copyright © 2015, 2016 Jan Arne Petersen
* Copyright © 2017, 2018 Red Hat, Inc.
* Copyright © 2018 Purism SPC
*
* Permission to use, copy, modify, distribute, and sell this
* software and its documentation for any purpose is hereby granted
* without fee, provided that the above copyright notice appear in
* all copies and that both that copyright notice and this permission
* notice appear in supporting documentation, and that the name of
* the copyright holders not be used in advertising or publicity
* pertaining to distribution of the software without specific,
* written prior permission. The copyright holders make no
* representations about the suitability of this software for any
* purpose. It is provided "as is" without express or implied
* warranty.
*
* THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
* SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
* SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
* ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
* THIS SOFTWARE.
*/
#include <stdbool.h>
#include <stdlib.h>
#include <stdint.h>
#include "wayland-util.h"
#ifndef __has_attribute
# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
#endif
#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
#define WL_PRIVATE __attribute__ ((visibility("hidden")))
#else
#define WL_PRIVATE
#endif
extern const struct wl_interface wl_seat_interface;
extern const struct wl_interface wl_surface_interface;
extern const struct wl_interface zwp_text_input_v3_interface;
static const struct wl_interface *text_input_unstable_v3_types[] = {
NULL,
NULL,
NULL,
NULL,
&wl_surface_interface,
&wl_surface_interface,
&zwp_text_input_v3_interface,
&wl_seat_interface,
};
static const struct wl_message zwp_text_input_v3_requests[] = {
{ "destroy", "", text_input_unstable_v3_types + 0 },
{ "enable", "", text_input_unstable_v3_types + 0 },
{ "disable", "", text_input_unstable_v3_types + 0 },
{ "set_surrounding_text", "sii", text_input_unstable_v3_types + 0 },
{ "set_text_change_cause", "u", text_input_unstable_v3_types + 0 },
{ "set_content_type", "uu", text_input_unstable_v3_types + 0 },
{ "set_cursor_rectangle", "iiii", text_input_unstable_v3_types + 0 },
{ "commit", "", text_input_unstable_v3_types + 0 },
{ "set_available_actions", "2a", text_input_unstable_v3_types + 0 },
{ "show_input_panel", "2", text_input_unstable_v3_types + 0 },
{ "hide_input_panel", "2", text_input_unstable_v3_types + 0 },
};
static const struct wl_message zwp_text_input_v3_events[] = {
{ "enter", "o", text_input_unstable_v3_types + 4 },
{ "leave", "o", text_input_unstable_v3_types + 5 },
{ "preedit_string", "?sii", text_input_unstable_v3_types + 0 },
{ "commit_string", "?s", text_input_unstable_v3_types + 0 },
{ "delete_surrounding_text", "uu", text_input_unstable_v3_types + 0 },
{ "done", "u", text_input_unstable_v3_types + 0 },
{ "action", "2uu", text_input_unstable_v3_types + 0 },
{ "language", "2s", text_input_unstable_v3_types + 0 },
{ "preedit_hint", "2uuu", text_input_unstable_v3_types + 0 },
};
WL_PRIVATE const struct wl_interface zwp_text_input_v3_interface = {
"zwp_text_input_v3", 2,
11, zwp_text_input_v3_requests,
9, zwp_text_input_v3_events,
};
static const struct wl_message zwp_text_input_manager_v3_requests[] = {
{ "destroy", "", text_input_unstable_v3_types + 0 },
{ "get_text_input", "no", text_input_unstable_v3_types + 6 },
};
WL_PRIVATE const struct wl_interface zwp_text_input_manager_v3_interface = {
"zwp_text_input_manager_v3", 2,
2, zwp_text_input_manager_v3_requests,
0, NULL,
};

View file

@ -89,6 +89,7 @@ extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> a
cfg.cFiles.push_back("lib/wayland-xdg-decoration-unstable-v1-client-protocol"); cfg.cFiles.push_back("lib/wayland-xdg-decoration-unstable-v1-client-protocol");
cfg.cFiles.push_back("lib/fractional-scale-v1"); cfg.cFiles.push_back("lib/fractional-scale-v1");
cfg.cFiles.push_back("lib/viewporter"); cfg.cFiles.push_back("lib/viewporter");
cfg.cFiles.push_back("lib/text-input-unstable-v3-protocol");
} }
// Vulkan is the only renderer on native targets. Software fallback is // Vulkan is the only renderer on native targets. Software fallback is