Files
raven/native/desktop/windows.cpp
T

2165 lines
100 KiB
C++

#include <iostream>
#include "windows.h"
#include "nt/actctx.h"
#include "nt/text.h"
#include "brush.h"
#include "nt/nls.h"
#include "gdi.h"
#include <array>
#include <algorithm>
#include <cstring>
#include <limits>
#include <new>
#include <stdexcept>
#include <sys/mman.h>
#include <thread>
#include <utility>
namespace raven {
static uint64_t read(std::span<const uint8_t> bytes,size_t at,size_t width);
NativeWindows::NativeWindows(NativeProcess &process, NativeEnvironment &environment, DesktopObjects &objects)
: process(process), environment(environment), objects(objects), deadline(std::chrono::steady_clock::now()) {}
void NativeWindows::dump_windows()
{
if (std::getenv("RAVEN_DUMP_WINDOWS")) {
for (const auto handle : objects.window_handles()) {
const auto *window=objects.window(handle);
const auto *definition=objects.window_class(window->class_id);
std::cerr << "window " << std::hex << handle << std::dec << " " << (definition ? definition->name : "?") << " parent=" << std::hex << window->parent << std::dec
<< " style=" << std::hex << window->style << " ex=" << window->ex_style << std::dec << " visible=" << window->visible
<< " outer=" << window->outer.left << "," << window->outer.top << "," << window->outer.right << "," << window->outer.bottom
<< " client=" << window->x << "," << window->y << "," << window->width << "x" << window->height
<< " title=" << to_utf8(window->title) << "\n";
}
}
}
void NativeWindows::shutdown()
{
dump_windows();
try { flush_dirty(); } catch (...) {}
for (const auto handle : objects.window_handles()) {
const auto *window=objects.window(handle);
if (handle!=desktop && !window->parent && window->bitmap && presentation.remove) presentation.remove(handle);
}
}
void NativeWindows::set_deadline(std::chrono::steady_clock::time_point end)
{
deadline=end;
}
void NativeWindows::set_call(std::function<NativeResult(uint64_t,std::span<const uint64_t>)> caller)
{
invoke=std::move(caller);
}
void NativeWindows::set_presentation(DesktopPresentation backend)
{
presentation=std::move(backend);
}
uint64_t NativeWindows::surface_dc(uint64_t handle)
{
auto *window=objects.window(handle);
if (!window) throw uint32_t(1400);
if (window->dc) return window->dc;
if (window->parent) {
const auto root=objects.window_root(handle);surface_dc(root);
window=objects.window(handle);const auto bitmap=objects.window(root)->bitmap;
DesktopDC device{bitmap,bitmap,handle};objects.configure_window_dc(handle,device,false);
const auto dc=objects.add(device);if (!dc) throw uint32_t(8);
window=objects.window(handle);window->bitmap=bitmap;window->dc=dc;return dc;
}
// An empty window still owns a surface, one pixel large; its DCs clip every drawing operation away.
const auto width=std::max<int64_t>(int64_t(window->outer.right)-window->outer.left,1),height=std::max<int64_t>(int64_t(window->outer.bottom)-window->outer.top,1);
if (width>8192 || height>8192 || uint64_t(width)*uint64_t(height)>16*1024*1024)
throw uint32_t(87);
const auto size=(size_t(width)*size_t(height)*4+4095)&~size_t(4095);
std::shared_ptr<SharedPixels> shared;
uint64_t address=0;
if (const auto spare=spare_surfaces.find(handle); spare!=spare_surfaces.end() && spare->second.size>=size) {
shared=spare->second.pixels;
address=spare->second.address;
spare_surfaces.erase(spare);
} else {
drop_spare_surface(handle);
shared=std::make_shared<SharedPixels>(size);
address=process.map_shared(shared->descriptor(),size);
}
uint64_t bitmap=0,dc=0;
try {
bitmap=objects.add(DesktopBitmap{uint32_t(width),uint32_t(height),32,{},address,true,true,false,shared});
if (!bitmap) throw uint32_t(8);
DesktopDC device{bitmap,bitmap,handle};
objects.configure_window_dc(handle,device,false);
dc=objects.add(device);
if (!dc) throw uint32_t(8);
window=objects.window(handle); window->bitmap=bitmap; window->dc=dc;
objects.attach_window_dcs(handle,bitmap);
return dc;
} catch (...) { if (dc) objects.erase(dc); if (bitmap) objects.erase(bitmap); process.unmap_memory(address); throw; }
}
void NativeWindows::present(uint64_t handle)
{
// The desktop is the screen itself; guest drawing on it never becomes a host window.
if (handle==desktop) return;
handle=objects.window_root(handle);
if (!handle) throw uint32_t(1400);
if (!presentation.present) throw uint32_t(120);
dirty_roots.erase(handle);
surface_dc(handle);
paint_nonclient(handle);
paint_child_frames(handle);
paint_scrollbars(handle);
// Painting sends messages, and a handler may have destroyed the window.
const auto *window=objects.window(handle);
if (!window) return;
const auto *bitmap=objects.bitmap(window->bitmap);
if (!bitmap) return;
const auto pixels=std::span<const uint8_t>(bitmap->pixels().data(),size_t(bitmap->width)*bitmap->height*4);
// A popup without a caption that another window owns (a menu, a drop-down list) opens where the program placed it: as a popup of the
// owner's host window, since a host window of its own would be placed by the compositor.
const auto owner=window->owner ? objects.window_root(window->owner) : 0;
const auto *owning=owner && owner!=handle ? objects.window(owner) : nullptr;
if (presentation.owned_popup && owning && objects.window_visible(owner) && (window->style & 0x80000000U) && (window->style & 0x00c00000U)!=0x00c00000U) {
try {
presentation.owned_popup(handle,owner,window->outer.left-owning->outer.left,window->outer.top-owning->outer.top,bitmap->width,bitmap->height,pixels);
return;
} catch (const std::exception &) {}
}
// Host titles use replacement characters; the guest retains every UTF-16 code unit.
presentation.present(handle,encode_utf8(window->title,true),bitmap->width,bitmap->height,pixels);
}
std::optional<HostSurface> NativeWindows::host_surface(uint64_t handle)
{
if (!presentation.host_surface) return std::nullopt;
const auto root=objects.window_root(handle);
const auto *top=objects.window(root);
if (!top || !objects.window(handle)) return std::nullopt;
// The host window must exist before anything can stack over it.
try { present(root); } catch (...) { return std::nullopt; }
top=objects.window(root);
if (!top) return std::nullopt;
// Window positions are on the screen, children's included, so the client origin inside the frame the host window shows is the
// window's position less the frame's.
const auto *window=objects.window(handle);
const int32_t x=window->x-top->outer.left,y=window->y-top->outer.top;
const auto surface=presentation.host_surface(root,x,y,uint32_t(std::max(window->width,1)),uint32_t(std::max(window->height,1)));
if (surface) hosting_surface.insert(root);
return surface;
}
std::optional<std::pair<uint32_t,uint32_t>> NativeWindows::client_size(uint64_t handle)
{
const auto *window=objects.window(handle);
if (!window) return std::nullopt;
return std::pair<uint32_t,uint32_t>{uint32_t(std::max(window->width,1)),uint32_t(std::max(window->height,1))};
}
void NativeWindows::release_host_surface(uint64_t handle)
{
if (!presentation.release_host_surface) return;
const auto root=objects.window_root(handle);
hosting_surface.erase(root);
if (root) presentation.release_host_surface(root);
}
void NativeWindows::drawing(uint64_t handle)
{
if (handle==desktop) return;
const auto *window=objects.window(handle);
if (window && objects.window_visible(handle)) dirty_roots.insert(objects.window_root(handle));
}
bool NativeWindows::input_ready()
{
flush_dirty();
caret_idle();
scroll_idle();
deliver_pending();
if (presentation.poll)
for (const auto &event : presentation.poll(std::chrono::milliseconds(0)))
receive(event);
serve_sent();
const auto me=environment.thread_id();
if (queued_for(me) || quits.contains(me))
return true;
post_due_timers();
return queued_for(me) || invalid_owned(0);
}
void NativeWindows::deliver_pending()
{
while (!pending_events.empty()) {
const auto event = pending_events.front();
pending_events.pop_front();
// The window an event was collected for may have been hidden or destroyed before the event could be delivered: such an
// event no longer has a target, as one for a surface the host no longer shows.
if (event.window && (!objects.window(event.window) || !objects.window(event.window)->visible))
continue;
receive(event);
}
}
bool NativeWindows::input_pending(uint32_t thread)
{
if (presentation.poll)
for (const auto &event : presentation.poll(std::chrono::milliseconds(0)))
pending_events.push_back(event);
if (!pending_events.empty() || queued_for(thread) || quits.contains(thread) || sent_for(thread))
return true;
post_due_timers();
if (queued_for(thread))
return true;
for (const auto handle : objects.window_handles()) {
const auto *window=objects.window(handle);
if (window && !window->update.empty() && objects.window_visible(handle) && (!window->thread || window->thread==thread)) return true;
}
return false;
}
uint64_t NativeWindows::foreign_dc(uint64_t handle)
{
const auto size=foreign_window_size(handle);
if (!size) throw uint32_t(1400);
auto &target=foreign_targets[handle];
if (!target.surface || target.surface->width!=size->first || target.surface->height!=size->second) {
if (target.bitmap) {
if (const auto *bitmap=objects.bitmap(target.bitmap); bitmap && bitmap->address) process.unmap_memory(bitmap->address);
objects.erase(target.bitmap);
target.bitmap=0;
}
target.surface=ForeignSurface::open_for_drawing(handle,size->first,size->second);
if (!target.surface) throw uint32_t(8);
const auto &pixels=*target.surface->pixels;
const auto address=process.map_shared(pixels.descriptor(),pixels.size(),pixels.offset());
DesktopBitmap record{size->first,size->second,32,{},address,true,false,true,target.surface->pixels};
record.stride=size->first*4;
target.bitmap=objects.add(std::move(record));
if (!target.bitmap) { process.unmap_memory(address); throw uint32_t(8); }
}
DesktopDC device{target.bitmap,target.bitmap,0};
device.leased=true; device.foreign=handle;
const auto dc=objects.add(device);
if (!dc) throw uint32_t(8);
return dc;
}
void NativeWindows::import_foreign_frames()
{
// New frames are looked for at most every 8 ms; a window whose root was drawn over since is given its last frame again, since the
// owner's painting of the parent does not leave out the area another process draws.
const auto now=std::chrono::steady_clock::now();
const bool poll=now-foreign_checked>=std::chrono::milliseconds(8);
if (poll) foreign_checked=now;
if (!poll && dirty_roots.empty()) return;
// Only windows another process has drawn into are looked at on every poll; all of them only when the set of drawn windows changed.
bool every=false;
if (poll) {
const auto stamp=foreign_frames_stamp();
every=stamp!=foreign_stamp;
foreign_stamp=stamp;
}
for (const auto handle : objects.window_handles()) {
if (handle<window_handle_base() || handle>window_handle_base()+0xffff) continue;
const auto known=foreign_frames.find(handle);
const bool drawn=known!=foreign_frames.end() && known->second;
const bool redraw=drawn && dirty_roots.contains(objects.window_root(handle));
if (!poll && !redraw) continue;
if (poll && !redraw && !every && !drawn) continue;
auto &seen=foreign_frames[handle];
if (redraw) seen=0;
take_foreign_frame(handle,seen,[&](const uint8_t *pixels,uint32_t width,uint32_t height) {
const auto root=objects.window_root(handle);
if (!root) return;
surface_dc(root);
const auto *top=objects.window(root);
const auto *window=objects.window(handle);
const auto *bitmap=top ? objects.bitmap(top->bitmap) : nullptr;
if (!window || !bitmap || !bitmap->shared) return;
// The client origin inside the root's surface: window positions are on the screen, children's included.
const int64_t x=int64_t(window->x)-top->outer.left,y=int64_t(window->y)-top->outer.top;
const auto stride=size_t(bitmap->width)*4;
auto target=bitmap->shared->data();
const auto columns=std::min<int64_t>({int64_t(width),int64_t(window->width),int64_t(bitmap->width)-x});
for (int64_t row=0; row<int64_t(height) && row<window->height && y+row<int64_t(bitmap->height); ++row) {
if (y+row<0 || x<0 || columns<=0) continue;
auto *line=target+size_t(y+row)*stride+size_t(x)*4;
std::memcpy(line,pixels+size_t(row)*width*4,size_t(columns)*4);
for (int64_t column=0; column<columns; ++column) line[column*4+3]=0xff;
}
dirty_roots.insert(root);
});
}
}
void NativeWindows::update_warp_lock(bool warped)
{
const bool hidden=cursor_count<0;
if (warp_lock_window && (!hidden || !objects.window(warp_lock_window) || warp_lock_window!=active)) {
if (presentation.confine && clip_window!=warp_lock_window) presentation.confine(warp_lock_window,std::nullopt);
warp_lock_window=0;
}
if (!warped || !hidden || warp_lock_window || !active || !presentation.confine || clip_window==active) return;
const auto *window=objects.window(active);
if (!window || cursor_x<window->outer.left || cursor_x>=window->outer.right || cursor_y<window->outer.top || cursor_y>=window->outer.bottom) return;
const int32_t x=cursor_x-window->outer.left,y=cursor_y-window->outer.top;
presentation.confine(active,std::array<int32_t,4>{x,y,x+1,y+1});
warp_lock_window=active;
}
void NativeWindows::flush_dirty()
{
settle_focus_leave(false);
import_foreign_frames();
const auto roots=std::move(dirty_roots);
dirty_roots.clear();
if (!presentation.present) return;
for (const auto root:roots)
if (objects.window(root) && objects.window_visible(root)) present(root);
}
void NativeWindows::release_surface(uint64_t handle)
{
const auto *window=objects.window(handle);
if (!window) return;
// A surface about to disappear still shows what the guest last drew.
if (!window->parent && dirty_roots.contains(handle) && presentation.present && window->bitmap) {
try { present(handle); } catch (...) {}
}
objects.detach_window_dcs(handle);
if (!window->parent) {
// Children draw on the root's bitmap through their own surface contexts; those follow it out.
const auto drop=[&](auto &&self,uint64_t node) -> void {
for (const auto child:objects.child_windows(node)) {
self(self,child);
objects.detach_window_dcs(child);
if (auto *record=objects.window(child)) {
if (record->dc) objects.erase(record->dc);
record->dc=record->bitmap=0;
}
}
};
drop(drop,handle);
window=objects.window(handle);
}
if (focus==handle) focus=0;
if (active==handle) active=0;
// A resize keeps the host window: the next frame has the new size.
if (!window->parent && window->bitmap && presentation.remove && !keep_host_window) presentation.remove(handle);
const auto bitmap=window->bitmap,dc=window->dc;
if (dc) objects.erase(dc);
if (bitmap && !window->parent) {
const auto *record=objects.bitmap(bitmap);
const auto address=record->address;
if (resizing && record->shared) {
drop_spare_surface(handle);
spare_surfaces[handle]={record->shared,address,record->shared->size()};
} else {
process.unmap_memory(address);
}
objects.erase(bitmap);
}
if (!resizing) drop_spare_surface(handle);
}
void NativeWindows::drop_spare_surface(uint64_t handle)
{
const auto found=spare_surfaces.find(handle);
if (found==spare_surfaces.end()) return;
process.unmap_memory(found->second.address);
spare_surfaces.erase(found);
}
DesktopRect NativeWindows::update_rect(uint64_t handle,uint64_t address)
{
const auto *window=objects.window(handle); if (!window) throw uint32_t(1400);
DesktopRect rect{0,0,window->width,window->height};
if (address) {
const auto bytes=process.read_memory(address,16);
rect={int32_t(read(bytes,0,4)),int32_t(read(bytes,4,4)),int32_t(read(bytes,8,4)),int32_t(read(bytes,12,4))};
rect.left=std::max(rect.left,0); rect.top=std::max(rect.top,0);
rect.right=std::min(rect.right,window->width); rect.bottom=std::min(rect.bottom,window->height);
}
return rect;
}
NativeWindows::PaintStart NativeWindows::start_paint(uint64_t handle)
{
const auto dc=surface_dc(handle);
const bool erase=objects.start_paint(handle);
const auto rect=objects.dc(dc)->clip->bounds();
if (std::getenv("RAVEN_TRACE_PAINT")) {
const auto *device=objects.dc(dc);
std::cerr << "start " << std::hex << handle << std::dec << " clip " << rect.left << ',' << rect.top << ',' << rect.right << ',' << rect.bottom;
if (device->bounds) std::cerr << " bounds " << device->bounds->left << ',' << device->bounds->top << ',' << device->bounds->right << ',' << device->bounds->bottom;
if (device->visible) { const auto box = device->visible->bounds(); std::cerr << " visible " << box.left << ',' << box.top << ',' << box.right << ',' << box.bottom; }
std::cerr << " erase " << erase << '\n';
}
const bool pending=erase && !send(handle,0x14,dc,0);
if (!objects.window(handle)) throw std::runtime_error("background callback destroyed its painting window");
return {dc,rect,pending};
}
static uint64_t read(std::span<const uint8_t> bytes, size_t at, size_t width)
{
uint64_t value=0;
for (size_t byte=0; byte<width; ++byte) value|=uint64_t(bytes[at+byte])<<(byte*8);
return value;
}
static void put(std::span<uint8_t> bytes, size_t at, uint64_t value, size_t width)
{
for (size_t byte=0; byte<width; ++byte) bytes[at+byte]=uint8_t(value>>(byte*8));
}
// Guest MSG: hwnd, message, wParam, lParam, time, pt. Offsets differ between the 32-bit and 64-bit Windows ABIs.
struct MessageLayout { size_t size, message, wparam, lparam, time, x, y, pointer; };
static MessageLayout message_layout(bool narrow)
{
return narrow ? MessageLayout{28,4,8,12,16,20,24,4} : MessageLayout{48,8,16,24,32,36,40,8};
}
static uint32_t message_time()
{
return uint32_t(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now().time_since_epoch()).count());
}
void NativeWindows::raw_mouse_input(int64_t dx,int64_t dy,uint32_t button_flags,int16_t wheel)
{
const auto target=raw_mouse.target && objects.window(raw_mouse.target) ? raw_mouse.target : focus;
if (!target || !objects.window(target) || messages.size()>=10000) return;
// Reports nobody read are dropped oldest first, as the system discards raw input a program leaves in its queue.
while (raw_reports.size()>=1024) raw_reports.erase(raw_reports.begin());
const auto handle=next_raw_report++;
raw_reports[handle]={int32_t(std::clamp<int64_t>(dx,INT32_MIN,INT32_MAX)),int32_t(std::clamp<int64_t>(dy,INT32_MIN,INT32_MAX)),
int32_t(button_flags),int32_t(wheel),0,0};
queue({target,0,handle,0xffU,message_time(),cursor_x,cursor_y});
}
void NativeWindows::post(uint64_t window,uint32_t message,uint64_t wparam,uint64_t lparam)
{
if (window==0xffff) throw uint32_t(120);
if (window && !objects.window(window)) throw uint32_t(1400);
if (messages.size()>=10000) throw uint32_t(1816);
queue({window,wparam,lparam,message,message_time(),cursor_x,cursor_y});
}
uint32_t NativeWindows::owner(uint64_t window) const
{
const auto *record=objects.window(window);
return record ? record->thread : 0;
}
void NativeWindows::queue(Message message)
{
if (!message.thread) message.thread=message.window ? owner(message.window) : environment.thread_id();
messages.push_back(message);
}
bool NativeWindows::queued_for(uint32_t thread) const
{
return std::any_of(messages.begin(),messages.end(),[&](const Message &message) { return !message.thread || message.thread==thread; });
}
bool NativeWindows::sent_for(uint32_t thread) const
{
return std::any_of(sent.begin(),sent.end(),[&](const auto &item) { return item->thread==thread && !item->taken; });
}
void NativeWindows::serve_sent()
{
const auto me=environment.thread_id();
for (;;) {
const auto found=std::find_if(sent.begin(),sent.end(),[&](const auto &item) { return item->thread==me && !item->taken; });
if (found==sent.end()) return;
const auto item=*found;
item->taken=true;
try {
item->result=objects.window(item->window) ? send(item->window,item->message,item->wparam,item->lparam) : 0;
} catch (...) {
item->done=true;
std::erase(sent,item);
throw;
}
item->done=true;
std::erase(sent,item);
}
}
uint64_t NativeWindows::invalid_owned(uint64_t filter) const
{
const auto me=environment.thread_id();
for (const auto handle : objects.window_handles()) {
const auto *window=std::as_const(objects).window(handle);
if (!window || window->update.empty() || !objects.window_visible(handle) || (window->thread && window->thread!=me)) continue;
if (!filter || filter==handle || objects.is_child(filter,handle)) return handle;
}
return 0;
}
// A compositor that moves the keyboard between two windows of the program reports a leave first. The leave waits a moment, so the
// arrival that follows can be read as the move it is: a popup that never activates does not take the focus from where it was.
void NativeWindows::settle_focus_leave(bool now)
{
if (!focus_leaving || (!now && std::chrono::steady_clock::now()-focus_left<std::chrono::milliseconds(150))) return;
focus_leaving=false;
key_down={}; key_translation.reset();
set_focus(0);
activate(0);
}
void NativeWindows::receive(const DesktopEvent &event)
{
if (placing_press && (event.kind==DesktopEventKind::pointer_motion || event.kind==DesktopEventKind::pointer_button ||
event.kind==DesktopEventKind::pointer_wheel || event.kind==DesktopEventKind::pointer_relative || event.kind==DesktopEventKind::key)) {
held_input.push_back(event);
return;
}
// A key means one of the program's windows still has the keyboard, so a pending leave did not happen after all.
if (event.kind==DesktopEventKind::key) focus_leaving=false;
else if (event.kind!=DesktopEventKind::keyboard_focus) settle_focus_leave(false);
if (menu_event(event)) return;
if (event.kind==DesktopEventKind::message) { post(event.window,event.message,event.wparam,event.lparam); return; }
if (event.kind==DesktopEventKind::keymap) {
if (std::any_of(key_down.begin(),key_down.end(),[](bool down) { return down; }))
throw std::runtime_error("keyboard map change with held keys requires qualification");
keyboard_map.load(event.keymap); key_translation.reset(); return;
}
if (event.kind==DesktopEventKind::modifiers) {
keyboard_map.modifiers(event.message,uint32_t(event.wparam),uint32_t(event.lparam),uint32_t(event.lparam>>32));
// A virtual keyboard reports modifiers without key events; the guest still sees them go down and up.
if (focus && objects.window(focus) && messages.size()<9990) {
static constexpr uint32_t scans[3]{0x2a,0x1d,0x38};
for (uint32_t index=0;index<3;++index) {
const uint32_t code=0x10+index;
const bool active=keyboard_map.modifier_active(code),down=(vk_async[code] & 0x80)!=0;
if (active==down) continue;
const bool system=code==0x12 || (vk_async[0x12] & 0x80);
vk_async[code]=active ? (vk_async[code] | 0x80) : (vk_async[code] & 0x7f);
queue({focus,code,1 | (uint64_t(scans[index])<<16) | (uint64_t(!active)<<30) | (uint64_t(!active)<<31) | (code==0x12 ? uint64_t(1)<<29 : 0),
active ? (system ? 0x104U : 0x100U) : (system ? 0x105U : 0x101U),message_time(),cursor_x,cursor_y,0});
}
}
return;
}
if (event.kind==DesktopEventKind::keyboard_focus) {
std::array<bool,256> initial{};
for (const auto code : event.held_keys) {
if (code>=initial.size() || initial[code]) throw std::runtime_error("invalid initial keyboard key state");
initial[code]=true;
}
if (!event.window && !event.held_keys.empty())
throw std::runtime_error("keyboard focus requires its visible guest window");
// The keyboard moving between two windows of the program (a leave of one came just before) can carry a key pressed while it moved:
// the compositor reports it as held on arrival and never sends its press. That press is the program's, so it is delivered.
const bool moving=focus_leaving;
const auto before=key_down;
const auto deliver_moving_keys=[&] {
if (!moving || !focus || !objects.window(focus)) return;
for (size_t code=0;code<initial.size();++code)
if (initial[code] && !before[code]) {
key_down[code]=false;
receive({event.window ? event.window : focus,uint32_t(code),1,0,DesktopEventKind::key});
}
};
// The host can give the focus to a window the program hides or destroys before the event is read; there is nothing to focus then.
if (event.window && (!objects.window(event.window) || !objects.window(event.window)->visible)) {
// A popup gone before the compositor focused it does not take the focus either: the program's focus stays.
if (focus && objects.window(focus) && objects.window_visible(focus)) {
focus_leaving=false;
key_down=initial; key_translation.reset();
deliver_moving_keys();
} else {
settle_focus_leave(true);
}
return;
}
if (!event.window && event.held_keys.empty() && focus) {
// The keys the program saw pressed stay recorded until the leave turns out to be real.
focus_leaving=true;
focus_left=std::chrono::steady_clock::now();
return;
}
// Windows never gives the keyboard to a window that does not activate (WS_EX_NOACTIVATE) or to an owned popup without a caption
// (a completion list, a menu, a dropdown): the compositor focusing one as it appears leaves the program's focus where it is.
if (event.window && focus && objects.window(focus) && objects.window_visible(focus)) {
const auto *target=objects.window(event.window);
const bool passive=(target->ex_style & 0x08000000U) || ((target->style & 0x80000000U) && target->owner && (target->style & 0x00c00000U)!=0x00c00000U);
if (passive && objects.window_root(focus)!=event.window) {
focus_leaving=false;
key_down=initial; key_translation.reset();
deliver_moving_keys();
return;
}
}
focus_leaving=false;
key_down=initial; key_translation.reset();
if (!event.window) { set_focus(0); activate(0); }
else if (!focus || objects.window_root(focus)!=event.window) set_focus(event.window);
deliver_moving_keys();
return;
}
if (event.kind==DesktopEventKind::key) {
// Keys go to the focus window whichever of the program's host windows the compositor reports them on. The compositor sends keys
// only to the program that has the keyboard, so a key that comes without a window (the keyboard appeared after the compositor
// focused the window and no enter followed) is the program's too. With no focus left (a dialog ended by the previous key), the
// key goes nowhere.
if (!focus || !objects.window(focus) || !objects.window_visible(focus)) {
if (event.message<key_down.size()) key_down[event.message]=event.wparam!=0;
return;
}
if (event.wparam>1 || event.message>=key_down.size() || messages.size()>=10000)
throw std::runtime_error("invalid native keyboard event or queue exhaustion");
const auto key=keyboard_map.decode(event.message);
const bool pressed=event.wparam!=0,previous=key_down[event.message];
if (!pressed && !previous) return;
key_down[event.message]=pressed;
if (!key.code) return;
const bool modifier=key.code>=0x10 && key.code<=0x12;
// A modifier already reported through the keymap state is not announced twice.
if (modifier && ((vk_async[key.code] & 0x80)!=0)==pressed) return;
const bool system=key.code==0x12 || (vk_async[0x12] & 0x80);
const uint64_t flags=1 | (uint64_t(key.scan)<<16) | (uint64_t(key.extended)<<24) | (system && key.code!=0x12 ? uint64_t(1)<<29 : 0) |
(key.code==0x12 ? uint64_t(1)<<29 : 0) | (uint64_t(previous)<<30) | (uint64_t(!pressed)<<31);
queue({focus,key.code,flags,system ? (pressed ? 0x104U : 0x105U) : (pressed ? 0x100U : 0x101U),message_time(),cursor_x,cursor_y,pressed ? key.character : 0});
vk_async[key.code&255]=pressed ? (vk_async[key.code&255] | 0x80) : (vk_async[key.code&255] & 0x7f);
if (pressed && !previous && (key.code==0x14 || key.code==0x90 || key.code==0x91)) vk_async[key.code]^=1;
return;
}
if (event.kind==DesktopEventKind::resize) {
// The user resized the host window: windows with a sizing border follow it, the others keep their size.
const auto *window=objects.window(event.window);
if (!window || window->parent || !(window->style & 0x40000U) || (window->style & 0x01000000U) || event.x<=0 || event.y<=0) return;
const DesktopRect outer{window->outer.left,window->outer.top,window->outer.left+event.x,window->outer.top+event.y};
if (outer.right-outer.left==window->outer.right-window->outer.left && outer.bottom-outer.top==window->outer.bottom-window->outer.top) return;
// The compositor already resized the host window, so it stays and receives the next frame at the new size.
keep_host_window=true;
try { place_window(event.window,outer,0,0x14); } catch (...) { keep_host_window=false; throw; }
keep_host_window=false;
return;
}
if (event.kind==DesktopEventKind::pointer_relative) {
// The mouse's own movement: once the host reports it, raw input uses it rather than the change of the pointer position.
raw_mouse.relative=true;
if (raw_mouse.registered) raw_mouse_input(event.x,event.y,0,0);
return;
}
if (event.kind==DesktopEventKind::pointer_wheel) {
// The wheel goes to the focus window with the screen position of the pointer.
const auto *source=objects.window(event.window);
if (!source || !focus || !objects.window(focus) || messages.size()>=10000) return;
const auto x=int64_t(source->outer.left)+event.x,y=int64_t(source->outer.top)+event.y;
if (x<INT16_MIN || x>INT16_MAX || y<INT16_MIN || y>INT16_MAX) return;
const auto delta=uint64_t(uint16_t(int16_t(int32_t(event.message))));
if (raw_mouse.registered) raw_mouse_input(0,0,event.wparam ? 0x800 : 0x400,int16_t(int32_t(event.message)));
queue({focus,(delta<<16)|mouse_buttons|keyboard_map.mouse_modifiers(),uint16_t(x)|(uint64_t(uint16_t(y))<<16),event.wparam ? 0x20eU : 0x20aU,message_time(),int32_t(x),int32_t(y)});
return;
}
if (event.x<INT16_MIN || event.x>INT16_MAX || event.y<INT16_MIN || event.y>INT16_MAX)
throw std::runtime_error("pointer coordinates exceed the guest message contract");
uint32_t message=0x200,buttons=mouse_buttons;
if (event.kind==DesktopEventKind::pointer_button) {
uint32_t mask=0;
if (event.message==0x110) { mask=1; message=0x201; }
else if (event.message==0x111) { mask=2; message=0x204; }
else if (event.message==0x112) { mask=16; message=0x207; }
else throw std::runtime_error("unsupported pointer button");
if (event.wparam>1) throw std::runtime_error("invalid pointer button state");
if (event.wparam) buttons|=mask; else { buttons&=~mask; ++message; }
}
static const bool trace_buttons=std::getenv("RAVEN_TRACE_MSG")!=nullptr;
if (trace_buttons && event.kind==DesktopEventKind::pointer_button)
fprintf(stderr,"button %x on %llx at %d,%d capture %llx\n",message,(unsigned long long)event.window,event.x,event.y,(unsigned long long)capture);
if (!event.window && event.kind==DesktopEventKind::pointer_button) { mouse_buttons=buttons; return; }
const auto *window=objects.window(event.window);
if (!window || !window->visible) throw std::runtime_error("pointer event targets an unavailable guest window");
if (messages.size()>=10000) throw std::runtime_error("guest input queue limit reached");
const auto x=int64_t(window->outer.left)+event.x,y=int64_t(window->outer.top)+event.y;
if (x<INT32_MIN || x>INT32_MAX || y<INT32_MIN || y>INT32_MAX)
throw std::runtime_error("guest cursor coordinates overflow");
if (raw_mouse.registered) {
// The raw report carries the movement of the host pointer and the buttons that changed: down and up flags for left, right, middle.
const int64_t dx=raw_mouse.have_position && !raw_mouse.relative ? x-raw_mouse.x : 0,dy=raw_mouse.have_position && !raw_mouse.relative ? y-raw_mouse.y : 0;
raw_mouse.x=x; raw_mouse.y=y; raw_mouse.have_position=true;
uint32_t button_flags=0;
const uint32_t changed=buttons^mouse_buttons;
if (changed & 1) button_flags|=(buttons & 1) ? 0x1 : 0x2;
if (changed & 2) button_flags|=(buttons & 2) ? 0x4 : 0x8;
if (changed & 16) button_flags|=(buttons & 16) ? 0x10 : 0x20;
if (dx || dy || button_flags) raw_mouse_input(dx,dy,button_flags,0);
}
mouse_buttons=buttons; cursor_x=int32_t(x); cursor_y=int32_t(y);
if (raw_mouse.registered && raw_mouse.no_legacy) return;
if (scroll_drag.window && (message==0x200 || message==0x202 || message==0x201) && scroll_motion(message,int32_t(x),int32_t(y))) return;
if (capture && !objects.window(capture)) capture=0;
// The press is placed before any input read while the program handles the messages sent for it.
const bool press=message==0x201 || message==0x204 || message==0x207;
if (press && !placing_press) {
placing_press=true;
try {
place_pointer(event,message,buttons,x,y);
} catch (...) {
placing_press=false;
held_input.clear();
throw;
}
release_held_input();
return;
}
place_pointer(event,message,buttons,x,y);
}
static bool trace_buttons_enabled()
{
static const bool enabled=std::getenv("RAVEN_TRACE_MSG")!=nullptr;
return enabled;
}
void NativeWindows::release_held_input()
{
placing_press=false;
while (!held_input.empty()) {
const auto next=held_input.front();
held_input.pop_front();
receive(next);
}
}
void NativeWindows::place_pointer(const DesktopEvent &event,uint32_t message,uint32_t buttons,int64_t x,int64_t y)
{
const auto target=capture ? capture : pointer_child(event.window,int32_t(x),int32_t(y));auto *window=objects.window(target);
track_pointer(target);
const bool inset=window->x!=window->outer.left || window->y!=window->outer.top ||
int64_t(window->width)!=int64_t(window->outer.right)-window->outer.left ||
int64_t(window->height)!=int64_t(window->outer.bottom)-window->outer.top;
const bool inside=x>=window->outer.left && y>=window->outer.top && x<window->outer.right && y<window->outer.bottom;
const auto hit=!capture && inset && inside ? send(target,0x84,0,uint16_t(x)|(uint64_t(uint16_t(y))<<16)) : 1;
window=objects.window(target);
if (trace_buttons_enabled() && message!=0x200)
fprintf(stderr,"button %x target %llx hit %llx visible %d\n",message,(unsigned long long)target,(unsigned long long)hit,
window && objects.window_visible(target) ? 1 : 0);
if (!window || !objects.window_visible(target) || !hit) return;
// Scroll bars and the frame may run a loop of their own that waits for the release, so what was held goes in first.
if (hit!=1 && placing_press) release_held_input();
if ((hit==6 || hit==7) && scroll_pointer(target,hit,message,int32_t(x),int32_t(y))) return;
if (hit!=1) { nonclient_pointer(target,hit,message,int32_t(event.x),int32_t(event.y)); return; }
if ((message==0x201 || message==0x204 || message==0x207) && window->parent && !(window->ex_style & 4)) {
for (auto parent=window->parent;parent;) {
const auto *ancestor=objects.window(parent);if (!ancestor) break;
const auto next=ancestor->parent;
send(parent,0x210,message,uint16_t(x-ancestor->x)|(uint64_t(uint16_t(y-ancestor->y))<<16));
parent=next;
}
window=objects.window(target);if (!window || !objects.window_visible(target)) return;
}
if (messages.size()>=10000) throw std::runtime_error("guest input queue limit reached");
queue({target,buttons|keyboard_map.mouse_modifiers(),uint16_t(x-window->x)|(uint64_t(uint16_t(y-window->y))<<16),message,message_time(),int32_t(x),int32_t(y)});
}
NativeWindows::Modifiers NativeWindows::modifier_state() const
{
return Modifiers{(vk_state[0x10] & 0x80)!=0,(vk_state[0x11] & 0x80)!=0,(vk_state[0x12] & 0x80)!=0};
}
uint64_t NativeWindows::retrieve(std::span<const uint64_t> args,bool blocking)
{
const auto layout=message_layout(process.mode()==NativeMode::x86);
// A 32-bit (HWND)-1 selects thread messages exactly like its 64-bit sign extension.
const auto window=process.mode()==NativeMode::x86 && args[1]==UINT32_MAX ? UINT64_MAX : args[1];
const auto minimum=uint32_t(args[2]),maximum=uint32_t(args[3]);
if (window && window!=UINT64_MAX && !objects.window(window)) throw uint32_t(1400);
if ((minimum || maximum) && (minimum>maximum || maximum>0xffff)) throw uint32_t(87);
const auto flags=blocking ? 1U : uint32_t(args[4]);
if (flags & ~1U) throw uint32_t(120);
flush_dirty();
const auto me=environment.thread_id();
for (;;) {
deliver_pending();
if (presentation.poll)
for (const auto &event : presentation.poll(std::chrono::milliseconds(0)))
receive(event);
// Messages sent from other threads run before anything is retrieved, as they do while a Windows thread pumps its queue.
serve_sent();
auto found=std::find_if(messages.begin(),messages.end(),[&](const Message &message) {
return (!message.thread || message.thread==me) &&
(!window || (window==UINT64_MAX ? !message.window : message.window==window || objects.is_child(window,message.window))) &&
(message.message==0x12 || (!minimum && !maximum) || (message.message>=minimum && message.message<=maximum));
});
const auto quit=quits.find(me);
if (found!=messages.end() || quit!=quits.end()) {
const auto value=found!=messages.end() ? *found : Message{0,quit->second,0,0x12,message_time(),cursor_x,cursor_y};
std::vector<uint8_t> bytes(layout.size);
put(bytes,0,value.window,layout.pointer); put(bytes,layout.message,value.message,4);
put(bytes,layout.wparam,value.wparam,layout.pointer); put(bytes,layout.lparam,value.lparam,layout.pointer);
put(bytes,layout.time,value.time,4);
put(bytes,layout.x,uint32_t(value.x),4); put(bytes,layout.y,uint32_t(value.y),4);
process.write_memory(args[0],bytes);
// RAVEN_TRACE_MSG also follows what each retrieval returns, with the filter it was asked with.
static const char *const trace_retrieval=std::getenv("RAVEN_TRACE_MSG");
if (trace_retrieval)
fprintf(stderr,"get %llx %x filter %llx %x-%x %s\n",(unsigned long long)value.window,value.message,(unsigned long long)window,minimum,maximum,
(flags & 1U) ? "remove" : "peek");
if (value.message>=0x100 && value.message<=0x105 && value.message!=0x102 && value.message!=0x103) {
key_translation=value;
// Key state seen by GetKeyState follows the messages the thread has retrieved, not the physical keys.
if (flags & 1U) {
const auto code=uint32_t(value.wparam & 255);
const bool down=value.message==0x100 || value.message==0x104;
const bool repeat=(value.lparam>>30) & 1;
vk_state[code]=down ? (vk_state[code] | 0x80) : (vk_state[code] & 0x7f);
if (down && !repeat && (code==0x14 || code==0x90 || code==0x91)) vk_state[code]^=1;
}
}
if (flags & 1U) { if (found!=messages.end()) messages.erase(found); else quits.erase(me); }
if (value.message!=0x12) {
// Keyboard and mouse hooks may swallow the message; the get-message hook only observes it.
if (has_hook(2) && (value.message>=0x100 && value.message<=0x105) && (flags & 1U) &&
call_hooks(2,0,value.wparam,value.lparam)) continue;
if (has_hook(7) && value.message>=0x200 && value.message<=0x20e && (flags & 1U)) {
std::vector<uint8_t> hook(32);
put(hook,0,uint32_t(value.x),4); put(hook,4,uint32_t(value.y),4); put(hook,8,value.window,8); put(hook,16,1,4);
const auto at=scratch_allocate(hook.size()); process.write_memory(at,hook);
if (call_hooks(7,0,value.message,at)) continue;
}
if (has_hook(3)) call_hooks(3,0,(flags & 1U) ? 1 : 0,args[0]);
}
return blocking && value.message==0x12 ? 0 : 1;
}
// WM_TIMER ranks below input and paint, so it is only generated when neither is waiting.
if (found==messages.end() && quit==quits.end()) {
const auto before=messages.size();
if (!invalid_owned(window)) post_due_timers();
if (messages.size()!=before) continue;
}
const auto paint=(!minimum && !maximum) || (minimum<=0x0f && maximum>=0x0f) ? invalid_owned(window) : 0;
if (paint) {
if (std::getenv("RAVEN_TRACE_PAINT")) {
std::cerr << "paint " << std::hex << paint << " pending";
for (const auto handle : objects.window_handles())
if (const auto *other = objects.window(handle); other && !other->update.empty())
std::cerr << ' ' << handle << (objects.window_visible(handle) ? "" : "(hidden)");
std::cerr << std::dec << "\n";
}
std::vector<uint8_t> bytes(layout.size);
put(bytes,0,paint,layout.pointer); put(bytes,layout.message,0x0f,4); put(bytes,layout.time,message_time(),4);
process.write_memory(args[0],bytes);
return 1;
}
if (!blocking) return 0;
if (std::chrono::steady_clock::now()>=deadline) throw NativeResult{NativeStop::timeout,0,0,0};
if (yield_hook && yield_hook()) continue;
if (std::getenv("RAVEN_DUMP_WINDOWS") && !dumped && std::chrono::steady_clock::now()-started>std::chrono::seconds(9)) { dumped=true; dump_windows(); }
caret_idle();
scroll_idle();
flush_dirty();
deliver_pending();
if (presentation.poll && presentation.descriptor && idle_until && presentation.descriptor()>=0) {
wait_idle(deadline);
} else if (presentation.poll) {
const auto remaining=std::chrono::duration_cast<std::chrono::milliseconds>(deadline-std::chrono::steady_clock::now());
for (const auto &event : presentation.poll(std::min(remaining,std::chrono::milliseconds(20))))
receive(event);
} else std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
void NativeWindows::wait_idle(std::chrono::steady_clock::time_point deadline)
{
const auto now=std::chrono::steady_clock::now();
auto until=std::min(deadline,now+std::chrono::seconds(1));
for (const auto &timer : timers) until=std::min(until,timer.due);
if (caret && caret->hidden<=0 && !caret->blank) until=std::min(until,caret->next);
if (scroll_drag.window) until=std::min(until,scroll_drag.next);
// Windows another process draws into are looked at as often as before; one starting to draw shows in the windows directory.
if (std::any_of(foreign_frames.begin(),foreign_frames.end(),[](const auto &frame) { return frame.second!=0; }))
until=std::min(until,foreign_checked+std::chrono::milliseconds(8));
if (!pending_events.empty() || !dirty_roots.empty()) until=now;
until=idle_until(until);
if (until>now) {
std::vector<pollfd> descriptors{{presentation.descriptor(),POLLIN,0},{foreign_frames_descriptor(),POLLIN,0}};
idle_descriptors(descriptors);
const auto left=std::chrono::duration_cast<std::chrono::nanoseconds>(until-now).count();
const timespec timeout{left/1000000000,left%1000000000};
::ppoll(descriptors.data(),descriptors.size(),&timeout,nullptr);
idle_drain();
if (descriptors[1].revents & POLLIN) {
drain_foreign_frames_descriptor();
foreign_checked={};
}
}
for (const auto &event : presentation.poll(std::chrono::milliseconds(0)))
receive(event);
}
std::string NativeWindows::text(uint64_t address)
{
std::string result;
for (size_t index=0; index<256; ++index) {
const auto bytes=process.read_memory(address+index*2,2);
const auto character=read(bytes,0,2);
if (!character) return result;
if (character>127) throw uint32_t(120);
result.push_back(char(character));
}
throw uint32_t(87);
}
std::u16string NativeWindows::window_text(uint64_t address)
{
std::u16string result;
if (!address) return result;
for (size_t index=0;index<=32766;++index) {
if (address>UINT64_MAX-index*2) throw uint32_t(87);
const auto character=char16_t(read(process.read_memory(address+index*2,2),0,2));
if (!character) return result;
if (index==32766) throw uint32_t(120);
result.push_back(character);
}
throw uint32_t(120);
}
uint64_t NativeWindows::copy_window_text(uint64_t handle,uint64_t output,uint64_t capacity)
{
const auto *window=objects.window(handle);if (!window) throw uint32_t(1400);
if (!capacity) return 0;
const auto length=std::min(uint64_t(window->title.size()),capacity-1);
std::vector<uint8_t> bytes(size_t(length+1)*2);
for (size_t index=0;index<length;++index) put(bytes,index*2,window->title[index],2);
process.write_memory(output,bytes);return length;
}
uint16_t NativeWindows::atom(uint64_t name, uint64_t instance)
{
// The predefined system class atoms 0x8000..0x8004 name the classes "#32768".."#32772".
if (name>=0x8000 && name<=0x8004) return objects.find_class(instance,"#"+std::to_string(name));
if (name && name<=UINT16_MAX) {
return objects.find_class(instance,uint16_t(name));
}
auto value=text(name);
// A class an assembly of the thread's activation context declares is first looked for under its versioned name.
auto &contexts=activation_contexts();
if (contexts.current_thread)
if (const auto versioned=contexts.lookup_name(contexts.current_thread(),value))
if (const auto found=objects.find_class(instance,*versioned)) return found;
for (auto &character : value)
if (character>='A' && character<='Z') character=char(character+('a'-'A'));
return objects.find_class(instance,value);
}
uint64_t NativeWindows::send(uint64_t handle, uint32_t message, uint64_t wparam, uint64_t lparam)
{
const auto *window=objects.window(handle);
if (!window) throw uint32_t(1400);
if (!invoke) throw std::runtime_error("native window callbacks are not connected");
// A window of another thread runs its procedure on that thread: the message waits in its queue of sent messages, and this thread
// answers the messages sent to its own windows while it waits for the result.
if (const auto me=environment.thread_id(); window->thread && me && window->thread!=me && block_hook) {
auto item=std::make_shared<SentMessage>(SentMessage{handle,message,wparam,lparam,0,window->thread});
sent.push_back(item);
while (!item->done) {
serve_sent();
if (item->done) break;
block_hook([&] { return item->done || sent_for(me); });
}
return item->result;
}
const auto procedure=window->procedure;
const uint64_t arguments[]{handle,message,wparam,lparam};
// The desktop window belongs to the system and has no guest procedure.
if (!procedure) return default_message(arguments);
// RAVEN_TRACE_MSG follows every message delivered to a guest procedure; a value limits it to one window handle.
static const char *const trace_message=std::getenv("RAVEN_TRACE_MSG");
const bool traced=trace_message && (std::string_view(trace_message)=="1" || std::strtoull(trace_message,nullptr,16)==handle);
if (traced) fprintf(stderr,"msg %llx %x %llx %llx\n",(unsigned long long)handle,message,(unsigned long long)wparam,(unsigned long long)lparam);
const auto result=invoke(procedure,arguments);
if (result.stop!=NativeStop::returned) throw result;
if (traced) fprintf(stderr,"msg %llx %x -> %llx\n",(unsigned long long)handle,message,(unsigned long long)result.value);
return result.value;
}
void NativeWindows::activate(uint64_t handle)
{
if (handle) handle=objects.window_root(handle);
// A window that is activated while another activation is being delivered takes its turn when that one is done.
if (activating) { queued_activation=handle; has_queued_activation=true; return; }
for (;;) {
if (active!=handle) {
activating=true;
try {
const auto previous=active;
if (previous && objects.window(previous)) {
send(previous,0x86,0,handle);
send(previous,6,0,handle);
}
if (handle && !objects.window(handle)) handle=0;
active=handle;
if (handle) { send(handle,0x86,1,previous); send(handle,6,1,previous); }
activating=false;
} catch (...) { activating=false; has_queued_activation=false; throw; }
}
if (!has_queued_activation) return;
handle=queued_activation;
has_queued_activation=false;
if (handle && !objects.window(handle)) handle=0;
}
}
uint64_t NativeWindows::set_focus(uint64_t handle)
{
if (handle && (!objects.window(handle) || objects.window(handle)->destroying)) throw uint32_t(1400);
if (handle) activate(handle);
const auto previous=focus;
if (previous==handle) return previous;
// A handler may move the focus again; the nested change completes first and the outer one stops when it was superseded.
++focusing;
try {
focus=handle;
if (previous && objects.window(previous)) send(previous,8,handle,0);
if (handle && focus==handle && objects.window(handle)) send(handle,7,previous,0);
--focusing;
return previous;
} catch (...) { --focusing; throw; }
}
bool NativeWindows::destroy(uint64_t handle)
{
auto *window=objects.window(handle);
if (!window || window->destroying) throw uint32_t(5);
for (const auto owned:objects.window_handles())
if (objects.is_child(handle,owned) && objects.window(owned)->destroying) throw uint32_t(120);
window->destroying=true;window->visible=false;window->style&=~0x10000000U;
withdraw_window(handle);
if (active==handle) activate(0);
if (focus==handle || objects.is_child(handle,focus)) set_focus(0);
parent_notify(handle,2);
send(handle,2,0,0);
for (const auto child:objects.child_windows(handle)) if (objects.window(child)) destroy(child);
if (objects.window(handle)) send(handle,0x82,0,0);
window=objects.window(handle);
if (window && window->parent) {
const auto *parent=objects.window(window->parent);
if (parent && !parent->destroying) {
const auto relative=[](int64_t value) { return int32_t(std::clamp(value,int64_t(INT32_MIN),int64_t(INT32_MAX))); };
if (!objects.invalidate(window->parent,{relative(int64_t(window->outer.left)-parent->x),relative(int64_t(window->outer.top)-parent->y),
relative(int64_t(window->outer.right)-parent->x),relative(int64_t(window->outer.bottom)-parent->y)},true)) throw uint32_t(8);
}
}
release_surface(handle);
painted_frames.erase(handle);
return objects.erase(handle);
}
uint64_t NativeWindows::default_message(std::span<const uint64_t> args)
{
auto *window=objects.window(args[0]);
if (!window) throw uint32_t(1400);
const auto message=uint32_t(args[1]);
if (message==0x200 || message==0x201 || message==0x202 || message==0x204 || message==0x207 || message==0x208 || message==0x7b) return 0;
if (message==0x205) {
const auto x=uint16_t(int64_t(window->x)+int16_t(uint16_t(args[3])));
const auto y=uint16_t(int64_t(window->y)+int16_t(uint16_t(args[3]>>16)));
send(args[0],0x7b,args[0],uint32_t(x)|(uint32_t(y)<<16));
return 0;
}
if (message==0x210 && (uint16_t(args[2])==1 || uint16_t(args[2])==2 || uint16_t(args[2])==0x201 || uint16_t(args[2])==0x204 || uint16_t(args[2])==0x207)) return 0;
if (message==0x86) return 1;
if (message==6) { if (uint16_t(args[2]) && !uint16_t(args[2]>>16)) set_focus(args[0]); return 0; }
if (message==7 || message==8) return 0;
if (message==0x81 || message==0x0c) {
const bool narrow=process.mode()==NativeMode::x86;
const auto address=message==0x81 ? (narrow ? read(process.read_memory(args[3],48),36,4) :
read(process.read_memory(args[3],80),56,8)) : args[3];
if (!objects.set_title(args[0],window_text(address))) throw uint32_t(8);
if (objects.window_visible(args[0])) present(args[0]);
return 1;
}
if (message==0x0e) return window->title.size();
if (message==0x0d) return copy_window_text(args[0],args[3],args[2]);
if (message==0x10) { destroy(args[0]); return 0; }
if (message>=0x132 && message<=0x138) {
if (message==0x137) throw uint32_t(120);
auto *dc=objects.dc(args[2]);if (!dc) throw uint32_t(6);
const auto background=message==0x133 || message==0x134 ? 5 : 15;
dc->text_color=objects.system_color(8);dc->background_color=objects.system_color(background);
return objects.system_brush(background);
}
if (message==0x14) {
const auto background=objects.window_class(window->class_id)->background;
if (!background) return 0;
const auto *dc=objects.dc(args[2]);
const auto *bitmap=dc ? objects.bitmap(dc->bitmap) : nullptr;
if (!bitmap) throw uint32_t(6);
const auto target=dc->window; const bool leased=dc->leased;
const auto changed=fill_rect(process,objects,args[2],{INT32_MIN,INT32_MIN,INT32_MAX,INT32_MAX},background);
if (changed && target && leased) drawing(target);
return 1;
}
if (message==0x85) {
// The frame is repainted from window state each time the surface is presented.
if (objects.window_visible(args[0])) present(args[0]);
return 0;
}
if (message==0x84 && !window->parent && (frame_layout(*window).border || frame_layout(*window).caption_height || window->menu))
return nonclient_hit(*window,int16_t(uint16_t(args[3])),int16_t(uint16_t(args[3]>>16)));
if (message==0x84) {
const auto x=int16_t(uint16_t(args[3])),y=int16_t(uint16_t(args[3]>>16));
if (const auto bar=scroll_hit(*window,x,y)) return bar;
return x>=window->outer.left && y>=window->outer.top && x<window->outer.right && y<window->outer.bottom &&
x>=window->x && y>=window->y && int64_t(x)<int64_t(window->x)+window->width &&
int64_t(y)<int64_t(window->y)+window->height ? 1 : 0;
}
if (message==0x317) {
// WM_PRINT: the default procedure replays its flags as the erase and client-paint requests.
if (args[3] & 0x8) send(args[0],0x14,args[2],0);
if (objects.window(args[0]) && (args[3] & 0x4)) send(args[0],0x318,args[2],args[3]);
return 0;
}
if (message==0x318) {
// WM_PRINTCLIENT: the default procedure paints the window into the supplied device context.
if (args[2]) send(args[0],0x0f,args[2],0);
return 0;
}
if (message==0x0f && args[2]) return 0;
if (message==0x0f) {
if (window->painting) throw uint32_t(170);
start_paint(args[0]); objects.finish_paint(args[0]);
if (objects.window(args[0])->visible) present(args[0]);
return 0;
}
if (message==0x83) {
if (args[2]) throw std::runtime_error("unsupported default extended nonclient calculation");
auto rect=process.read_memory(args[3],16);
// The client area is the window rectangle minus what AdjustWindowRectEx adds for this style.
const auto frame=adjust_window_rect(objects.nonclient_metrics(),{0,0,0,0},window->style & ~0x10000000U,window->menu!=0,window->ex_style);
const int32_t edges[4]={frame.left,frame.top,frame.right,frame.bottom};
for (size_t index=0;index<4;++index)
put(rect,index*4,uint32_t(int32_t(read(rect,index*4,4))-edges[index]),4);
// Scroll bars sit between the edge and the client area on the right and bottom.
if (window->style & 0x200000U) put(rect,8,uint32_t(int32_t(read(rect,8,4))-17),4);
if (window->style & 0x100000U) put(rect,12,uint32_t(int32_t(read(rect,12,4))-17),4);
process.write_memory(args[3],rect);
return 0;
}
if (message==0x106) {
// A system character reaches the menu through the top-level window, whichever child had the focus.
const auto root=objects.window_root(args[0]);
if (root) send(root,0x112,0xf100,args[2]);
return 0;
}
if (message==0x112) {
if ((uint32_t(args[2]) & 0xfff0U)==0xf100U && window->menu) {
// SC_KEYMENU: Alt plus a menu bar mnemonic opens that menu.
if (const auto *bar=objects.menu(window->menu)) {
const auto fold=[](uint64_t c) { return c>='A' && c<='Z' ? c+32 : c; };
const auto wanted=fold(args[3]);
for (size_t index=0;index<bar->items.size();++index) {
const auto &text=bar->items[index].text;
for (size_t at=0;at+1<text.size();++at)
if (text[at]==u'&' && text[at+1]!=u'&') {
if (fold(text[at+1])==wanted) { menu_open_bar(args[0],index); return 0; }
break;
}
}
}
return 0;
}
// SC_CLOSE becomes WM_CLOSE; the other system commands need window state transitions that are not modeled.
if ((uint32_t(args[2]) & 0xfff0U)==0xf060U) { send(args[0],0x10,0,0); return 0; }
// Size commands change the window state: maximize, minimize and restore.
if ((uint32_t(args[2]) & 0xfff0U)==0xf030U) { show_window(args[0],3); return 0; }
if ((uint32_t(args[2]) & 0xfff0U)==0xf020U) { show_window(args[0],6); return 0; }
if ((uint32_t(args[2]) & 0xfff0U)==0xf120U) { show_window(args[0],9); return 0; }
return 0;
}
if (message==0x21) return 1;
if (message==0x11) return 1;
if (!message || message==1 || message==2 || message==3 || message==5 || message==0x82 || message==0x18) return 0;
// Messages whose Windows default is "no action, result 0": cursor, activation notices, window-position notices,
// non-client mouse notices, capture loss, keyboard and timer/command traffic nobody consumed.
if (message==0x20 || message==0x16 || message==0x1c || message==0x24 || message==0x46 || message==0x47 || message==0x4e ||
message==0x7f || message==0x80 || (message>=0xa0 && message<=0xa9) || message==0x215 || (message>=0x100 && message<=0x109) ||
message==0x111 || message==0x113 || (message>=0x116 && message<=0x120) || message==0x211 || message==0x212) return 0;
// Every other message has no default action in DefWindowProc and answers zero.
return 0;
}
uint64_t NativeWindows::create(std::span<const uint64_t> args)
{
const auto style=uint32_t(args[3]),ex_style=uint32_t(args[0]);
const bool child=(style & 0x40000000U)!=0;
// A message-only window (parent HWND_MESSAGE) only receives messages: it is a top-level window that is never shown.
const bool message_only=!child && (args[8]==UINT64_MAX-2 || args[8]==0xfffffffdULL);
const uint64_t owner_handle=message_only ? 0 : args[8];
const auto effective_style=message_only ? style & ~0x10000000U : style;
// A visible window needs somewhere to appear; headless runs can still create hidden windows.
if ((effective_style & 0x10000000U) && !presentation.present) throw uint32_t(120);
const auto *parent=child ? objects.window(args[8]) : nullptr;
if (child && (!parent || parent->destroying)) throw uint32_t(1400);
if (parent && !parent->client_ready) throw uint32_t(120);
// An owner of a top-level window must be a top-level window; message-only parents (HWND_MESSAGE) are not modelled.
if (!child && owner_handle && !objects.window(owner_handle)) throw uint32_t(1400);
const auto class_atom=atom(args[1],args[10]);
const auto *definition=objects.window_class(class_atom);
if (!definition) {
if (std::getenv("RAVEN_TRACE_API")) {
std::string name;
try { if (args[1]>0xffff) { const auto text=process.read_memory(args[1],64); for (size_t i=0;i<32 && (text[i*2]|text[i*2+1]);++i) name.push_back(char(text[i*2])); } else name="#"+std::to_string(args[1]); } catch (...) {}
std::cerr << "raven: window class not found: " << name << "\n";
}
throw uint32_t(1407);
}
auto x=int32_t(args[4]),y=int32_t(args[5]);
auto width=int32_t(args[6]),height=int32_t(args[7]);
// CW_USEDEFAULT (0x80000000): top-level windows get a cascading position and three quarters of the screen; children get zero.
if (x==INT32_MIN || y==INT32_MIN || width==INT32_MIN || height==INT32_MIN) {
const auto &screen=objects.nonclient_metrics();
if (x==INT32_MIN) x=child ? 0 : 0;
if (y==INT32_MIN) y=child ? 0 : 0;
if (width==INT32_MIN) width=child ? 0 : screen.screen_x*3/4;
if (height==INT32_MIN) height=child ? 0 : screen.screen_y*3/4;
}
if (width<0 || height<0 || width>8192 || height>8192 || int64_t(x)+width>INT32_MAX || int64_t(y)+height>INT32_MAX)
throw uint32_t(87);
const auto relative_x=x,relative_y=y;
if (parent) {
const auto absolute_x=int64_t(parent->x)+x,absolute_y=int64_t(parent->y)+y;
if (absolute_x<INT32_MIN || absolute_y<INT32_MIN || absolute_x+width>INT32_MAX || absolute_y+height>INT32_MAX) throw uint32_t(87);
x=int32_t(absolute_x);y=int32_t(absolute_y);
}
if (args[2]) window_text(args[2]);
DesktopWindow record{class_atom,definition->procedure,{},x,y,width,height};
record.thread=environment.thread_id();
record.instance=args[10]; record.style=effective_style | (child ? 0U : 0x04000000U);record.ex_style=ex_style;
record.parent=child ? args[8] : 0;record.child_id=child ? args[9] : 0;record.client_ready=false;
record.owner=child ? 0 : owner_handle;
if (!child) record.menu=args[9];
// A window created without a menu takes the default menu of its class.
if (!child && !args[9] && definition->menu_id) {
if (!menu_loader) throw uint32_t(120);
record.menu=menu_loader(definition->instance,definition->menu_id);
if (std::getenv("RAVEN_TRACE_API")) std::cerr << "class menu " << definition->menu_id << " instance " << std::hex << definition->instance << " -> " << record.menu << std::dec << "\n";
} else if (!child && !args[9] && !definition->menu_name.empty()) throw uint32_t(120);
record.extra.resize(definition->window_extra);
const auto handle=objects.add(std::move(record));
if (!handle) throw uint32_t(8);
// A new child sits below its siblings, so controls created first stay on top of those created later.
if (child) objects.restack(handle,false);
uint64_t scratch=0;
try {
scratch=process.map_memory(4096,PROT_READ | PROT_WRITE);
// CREATESTRUCTW followed by the 16-byte window rectangle used for WM_NCCALCSIZE.
const bool narrow=process.mode()==NativeMode::x86;
const size_t rect_at=narrow ? 48 : 80;
std::vector<uint8_t> bytes(rect_at+16);
if (narrow) {
put(bytes,0,args[11],4); put(bytes,4,args[10],4);put(bytes,8,args[9],4);put(bytes,12,args[8],4);
put(bytes,16,uint32_t(height),4); put(bytes,20,uint32_t(width),4);
put(bytes,24,uint32_t(relative_y),4); put(bytes,28,uint32_t(relative_x),4); put(bytes,32,uint32_t(args[3]),4);
put(bytes,36,args[2],4); put(bytes,40,args[1],4);put(bytes,44,ex_style,4);
} else {
put(bytes,0,args[11],8); put(bytes,8,args[10],8);put(bytes,16,args[9],8);put(bytes,24,args[8],8);
put(bytes,32,uint32_t(height),4); put(bytes,36,uint32_t(width),4);
put(bytes,40,uint32_t(relative_y),4); put(bytes,44,uint32_t(relative_x),4); put(bytes,48,uint32_t(args[3]),4);
put(bytes,56,args[2],8); put(bytes,64,args[1],8);put(bytes,72,ex_style,4);
}
put(bytes,rect_at,uint32_t(x),4); put(bytes,rect_at+4,uint32_t(y),4);
put(bytes,rect_at+8,uint32_t(x+width),4); put(bytes,rect_at+12,uint32_t(y+height),4);
process.write_memory(scratch,bytes);
const auto accepted=send(handle,0x81,0,scratch);
if (!objects.window(handle)) { process.unmap_memory(scratch); return 0; }
if (!accepted) {
send(handle,0x82,0,0);
release_surface(handle);
objects.erase(handle); process.unmap_memory(scratch); return 0;
}
// WM_NCCREATE may already have moved or sized the window, so the frame calculation starts from its current rectangle.
{
const auto &outer=objects.window(handle)->outer;
put(bytes,rect_at,uint32_t(outer.left),4); put(bytes,rect_at+4,uint32_t(outer.top),4);
put(bytes,rect_at+8,uint32_t(outer.right),4); put(bytes,rect_at+12,uint32_t(outer.bottom),4);
process.write_memory(scratch+rect_at,std::span<const uint8_t>(bytes.data()+rect_at,16));
}
send(handle,0x83,0,scratch+rect_at);
if (!objects.window(handle)) { process.unmap_memory(scratch); return 0; }
const auto client=process.read_memory(scratch+rect_at,16);
const auto left=int32_t(uint32_t(read(client,0,4))),top=int32_t(uint32_t(read(client,4,4)));
// A window smaller than its frame has an empty client area, not a negative one.
const auto right=uint32_t(std::max(int32_t(uint32_t(read(client,8,4))),left)),bottom=uint32_t(std::max(int32_t(uint32_t(read(client,12,4))),top));
if (int64_t(left)-x<INT32_MIN || int64_t(left)-x>INT32_MAX || int64_t(top)-y<INT32_MIN || int64_t(top)-y>INT32_MAX) {
send(handle,0x82,0,0);
throw uint32_t(120);
}
auto *geometry=objects.window(handle);
geometry->x=left; geometry->y=top;
geometry->width=int32_t(right-uint32_t(left)); geometry->height=int32_t(bottom-uint32_t(top));
objects.update_window_dcs(handle);geometry->client_ready=true;
const auto created=send(handle,1,0,scratch);
process.unmap_memory(scratch); scratch=0;
if (!objects.window(handle)) return 0;
if (created==UINT64_MAX) { reject_tree(handle);return 0; }
auto *window=objects.window(handle);
if (!(window->style & 0xc0000000U)) {
window->need_size=true;
} else {
send(handle,5,0,uint16_t(window->width) | (uint64_t(uint16_t(window->height))<<16));
window=objects.window(handle);
if (!window) throw uint32_t(1400);
send(handle,3,0,uint16_t(child ? int64_t(window->x)-objects.window(args[8])->x : window->x) |
(uint64_t(uint16_t(child ? int64_t(window->y)-objects.window(args[8])->y : window->y))<<16));
if (!objects.window(handle)) throw uint32_t(1400);
}
if (child) parent_notify(handle,1);
if (!objects.window(handle)) return 0;
if (const auto *created=objects.window(handle)) publish_window(handle,uint32_t(std::max(created->width,0)),uint32_t(std::max(created->height,0)));
if (effective_style & 0x10000000U) {
auto *window=objects.window(handle);window->visible=true;objects.update_window_dcs(child ? window->parent : handle);
if (objects.window_visible(handle)) present(handle);
if (!objects.invalidate(handle,{0,0,window->width,window->height},true)) throw uint32_t(8);
}
return handle;
} catch (...) {
discard_tree(handle);
if (scratch) process.unmap_memory(scratch);
throw;
}
}
uint64_t NativeWindows::map_points(uint64_t from,uint64_t to,uint64_t points,uint32_t count)
{
const auto origin = [&](uint64_t handle) {
if (!handle) return std::pair<int32_t,int32_t>{0,0};
const auto *window=objects.window(handle);
if (!window) throw uint32_t(1400);
return std::pair<int32_t,int32_t>{window->x,window->y};
};
const auto source=origin(from),destination=origin(to);
const auto dx=uint32_t(int64_t(source.first)-destination.first);
const auto dy=uint32_t(int64_t(source.second)-destination.second);
if (count>65536) throw uint32_t(120);
if (count) {
auto bytes=process.read_memory(points,size_t(count)*8);
for (size_t offset=0;offset<bytes.size();offset+=8) {
put(bytes,offset,uint32_t(read(bytes,offset,4))+dx,4);
put(bytes,offset+4,uint32_t(read(bytes,offset+4,4))+dy,4);
}
process.write_memory(points,bytes);
}
return uint16_t(dx) | (uint32_t(uint16_t(dy))<<16);
}
void NativeWindows::publish_screen_dc(uint64_t desktop)
{
if (!screen_block)
return;
auto &published=*reinterpret_cast<uint64_t *>(screen_block+screen_dc);
// A published context that is gone (deleted rather than released) is withdrawn, and so is one a caller holds: it stays that
// caller's, an ordinary context its ReleaseDC brings to the runtime, and the next GetDC(NULL) in the guest takes a new one.
if (const uint64_t current=__atomic_load_n(&published,__ATOMIC_ACQUIRE); current &&
(!objects.dc(current) || __atomic_load_n(reinterpret_cast<uint32_t *>(screen_block+screen_dc_state),__ATOMIC_ACQUIRE)))
withdraw_screen_dc(current);
if (__atomic_load_n(&published,__ATOMIC_ACQUIRE))
return;
// A context of its own, free: the caller keeps the one it asked for, which a program may hold for its whole life.
const auto *window=objects.window(desktop);
DesktopDC device{window->bitmap,window->bitmap,desktop}; device.leased=true;
objects.configure_window_dc(desktop,device,false);
uint64_t handle=0;
try { handle=objects.add(device); } catch (...) { return; }
if (!handle)
return;
for (int32_t index=0;index<128;++index) {
const uint32_t value=device_caps(objects,*objects.dc(handle),index);
std::memcpy(screen_block+screen_caps+size_t(index)*4,&value,4);
}
__atomic_store_n(reinterpret_cast<uint32_t *>(screen_block+screen_dc_state),uint32_t(0),__ATOMIC_RELEASE);
__atomic_store_n(&published,handle,__ATOMIC_RELEASE);
}
bool NativeWindows::withdraw_screen_dc(uint64_t handle)
{
if (!screen_block || !handle)
return false;
auto &published=*reinterpret_cast<uint64_t *>(screen_block+screen_dc);
if (__atomic_load_n(&published,__ATOMIC_ACQUIRE)!=handle)
return false;
// The context goes first, so a GetDC that takes the freed state finds none and goes to the runtime.
__atomic_store_n(&published,uint64_t(0),__ATOMIC_RELEASE);
__atomic_store_n(reinterpret_cast<uint32_t *>(screen_block+screen_dc_state),uint32_t(0),__ATOMIC_RELEASE);
return true;
}
void NativeWindows::set_window_table(uint8_t *table)
{
window_table=table;
objects.touched_windows().overflow=true;
publish_windows();
}
// The fixed part of a window's record: everything but its handle and properties.
static void window_fields(const DesktopWindow &window,uint8_t *record)
{
const auto put=[&](size_t offset,auto value) { std::memcpy(record+offset,&value,sizeof(value)); };
put(8,window.userdata); put(16,window.procedure); put(24,window.instance);
put(32,window.parent ? window.parent : window.owner); put(40,window.child_id);
put(48,window.style); put(52,window.ex_style);
put(56,window.width); put(60,window.height);
put(64,window.outer.left); put(68,window.outer.top); put(72,window.outer.right); put(76,window.outer.bottom);
put(88,window.parent);
// Extra bytes beyond the record are read by the runtime.
const bool extra=window.extra.size()<=NativeWindows::window_extra_bytes;
put(80,uint32_t(extra ? window.extra.size() : 0));
if (extra && !window.extra.empty()) std::memcpy(record+160,window.extra.data(),window.extra.size());
}
void NativeWindows::publish_window_record(uint64_t handle,const DesktopWindow *window)
{
auto *record=window_table+(handle%window_records)*window_record;
const auto put=[&](size_t offset,auto value) { std::memcpy(record+offset,&value,sizeof(value)); };
uint64_t occupant=0;
std::memcpy(&occupant,record,8);
if (!window) {
if (occupant==handle) put(0,uint64_t(0));
return;
}
// Two windows whose handles share the low bits: the first keeps the record and the other is answered by the runtime.
if (occupant && occupant!=handle && std::as_const(objects).window(occupant))
return;
put(0,uint64_t(0));
window_fields(*window,record);
// Properties named by an atom are keyed "#" and its number in decimal; up to four of them travel with the record.
uint32_t count=0;
for (const auto &[key,value] : window->properties) {
if (key.size()<2 || key.size()>6 || key[0]!=u'#' || (key[1]==u'0' && key.size()>2)) continue;
uint64_t atom=0;
bool digits=true;
for (size_t index=1;index<key.size() && digits;++index) {
digits=key[index]>=u'0' && key[index]<=u'9';
atom=atom*10+uint64_t(key[index]-u'0');
}
if (!digits || atom>0xffff) continue;
if (count==4) { count=UINT32_MAX; break; }
put(96+size_t(count)*16,atom); put(104+size_t(count)*16,value);
++count;
}
put(84,count);
put(0,handle);
}
void NativeWindows::publish_windows()
{
if (!window_table)
return;
// Nothing touched, watched or released since the last time: only the few published words can have changed.
uint32_t logged=0;
if (deferred_log) std::memcpy(&logged,deferred_log,4);
const auto idle=[](auto &touched) { return touched.handles.empty() && !touched.overflow; };
if (!logged && deferred_pending.empty() && idle(objects.touched_windows()) && idle(objects.touched_menus()) &&
idle(objects.touched_bitmaps()) && idle(objects.touched_dcs()) && idle(objects.touched_icons()) && watched.empty() &&
watched_bitmaps.empty() && watched_dcs.empty()) {
publish_words();
return;
}
// Handles the guest released in a call still in progress keep out of its tables until the runtime carries the release out.
deferred_pending.clear();
if (deferred_log) {
uint32_t count=0;
std::memcpy(&count,deferred_log,4);
for (uint32_t index=0;index<std::min<uint32_t>(count,deferred_entries);++index) {
uint64_t handle=0;
std::memcpy(&handle,deferred_log+16+size_t(index)*16,8);
deferred_pending.push_back(handle);
}
}
auto &touched=objects.touched_windows().handles;
if (objects.touched_windows().overflow) {
std::memset(window_table,0,window_records*window_record);
watched.clear();
for (const auto handle : objects.window_handles()) {
const auto *window=std::as_const(objects).window(handle);
publish_window_record(handle,window);
watched.push_back({handle,window,api_depth});
}
objects.touched_windows().overflow=false;
touched.clear();
}
// Windows handed out since the guest last ran are written whole; they stay watched while the call that touched them goes on.
for (const auto handle : objects.touched_windows().settled()) {
const auto *window=std::as_const(objects).window(handle);
publish_window_record(handle,window);
const auto found=std::find_if(watched.begin(),watched.end(),[&](const auto &item) { return item.first==handle; });
if (found!=watched.end()) {
if (window) { found->second=window; found->depth=std::min(found->depth,api_depth); }
else watched.erase(found);
} else if (window && watching) {
watched.push_back({handle,window,api_depth});
}
}
touched.clear();
// A watched window may have been changed through a pointer taken before the guest last ran: its fields are compared again.
for ([[maybe_unused]] const auto &[handle,window,depth] : watched) {
auto *record=window_table+(handle%window_records)*window_record;
uint64_t occupant=0;
std::memcpy(&occupant,record,8);
if (occupant!=handle) continue;
alignas(8) uint8_t fields[window_record];
window_fields(*window,fields);
const size_t extra=window->extra.size()<=window_extra_bytes ? window->extra.size() : 0;
if (std::memcmp(fields+8,record+8,76) || std::memcmp(fields+88,record+88,8) || (extra && std::memcmp(fields+160,record+160,extra)))
publish_window_record(handle,window);
}
publish_menus();
publish_bitmaps();
publish_dcs();
publish_icons();
if (!api_depth) watched.clear();
publish_words();
}
void NativeWindows::set_menu_table(uint8_t *table)
{
menu_table=table;
objects.touched_menus().overflow=true;
publish_menus();
}
static size_t menu_block_size(const DesktopMenu &menu)
{
size_t size=menu.items.size()*32;
for (const auto &item : menu.items) size+=item.text.size()*2;
return (size+7)&~size_t(7);
}
void NativeWindows::publish_menu(uint64_t handle,const DesktopMenu *menu)
{
auto *slot=menu_table+(handle%menu_slots)*16;
uint64_t occupant=0;
std::memcpy(&occupant,slot,8);
if (!menu) {
if (occupant==handle) std::memset(slot,0,8);
return;
}
// Two menus whose handles share the low bits: the first keeps the slot and the other is answered by the runtime.
if (occupant && occupant!=handle && objects.menu(occupant))
return;
// An unchanged menu is left as it is, and one whose new block fits where its old one is written there again.
if (occupant==handle && menu_matches(handle,*menu))
return;
const size_t size=menu_block_size(*menu);
size_t at=menu_used;
if (occupant==handle) {
uint32_t place=0;
std::memcpy(&place,slot+8,4);
if (const auto found=menu_capacity.find(handle); found!=menu_capacity.end() && found->second.first==place && size<=found->second.second)
at=place;
}
std::memset(slot,0,8);
if (menu->items.size()>4096 || size>menu_area-menu_blocks)
return;
if (at==menu_used && menu_used+size>menu_area) {
// The blocks are full of old copies: every menu is written again from the start.
std::memset(menu_table,0,menu_blocks);
menu_used=menu_blocks;
menu_capacity.clear();
for (const auto other : objects.menu_handles())
if (const auto *record=objects.menu(other); record && menu_used+menu_block_size(*record)<=menu_area) publish_menu(other,record);
return;
}
auto *block=menu_table+at;
uint32_t text=uint32_t(at+menu->items.size()*32);
for (const auto &item : menu->items) {
const uint32_t fields[4]={item.type,item.state,item.id,uint32_t(item.text.size())};
std::memcpy(block,fields,16);
std::memcpy(block+16,&item.submenu,8);
std::memcpy(block+24,&text,4);
std::memset(block+28,0,4);
std::memcpy(menu_table+text,item.text.data(),item.text.size()*2);
text+=uint32_t(item.text.size()*2);
block+=32;
}
const uint32_t place[2]={uint32_t(at),uint32_t(menu->items.size())};
std::memcpy(slot+8,place,8);
if (at==menu_used) {
menu_capacity[handle]={uint32_t(at),size};
menu_used+=size;
}
std::memcpy(slot,&handle,8);
}
bool NativeWindows::menu_matches(uint64_t handle,const DesktopMenu &menu) const
{
const auto *slot=menu_table+(handle%menu_slots)*16;
uint64_t occupant=0;
uint32_t place[2]={};
std::memcpy(&occupant,slot,8);
std::memcpy(place,slot+8,8);
if (occupant!=handle) return true;
if (place[1]!=menu.items.size()) return false;
const auto *block=menu_table+place[0];
for (const auto &item : menu.items) {
uint32_t fields[4]={},text=0;
uint64_t submenu=0;
std::memcpy(fields,block,16);
std::memcpy(&submenu,block+16,8);
std::memcpy(&text,block+24,4);
if (fields[0]!=item.type || fields[1]!=item.state || fields[2]!=item.id || fields[3]!=item.text.size() || submenu!=item.submenu ||
std::memcmp(menu_table+text,item.text.data(),item.text.size()*2))
return false;
block+=32;
}
return true;
}
void NativeWindows::publish_menus()
{
if (!menu_table)
return;
auto &touched=objects.touched_menus().handles;
if (objects.touched_menus().overflow) {
std::memset(menu_table,0,menu_blocks);
menu_used=menu_blocks;
menu_capacity.clear();
for (const auto handle : objects.menu_handles()) publish_menu(handle,objects.menu(handle));
objects.touched_menus().overflow=false;
touched.clear();
}
// Menus handed out since the guest last ran are written again. None is watched as windows are: the calls that change menus never run
// the guest while they hold one.
for (const auto handle : objects.touched_menus().settled())
publish_menu(handle,objects.menu(handle));
touched.clear();
}
void NativeWindows::set_bitmap_table(uint8_t *table)
{
bitmap_table=table;
objects.touched_bitmaps().overflow=true;
publish_bitmaps();
}
// What GetObject answers for a bitmap in 32 bytes (see NativeDesktop::get_object).
static std::array<uint8_t,32> bitmap_fields_of(const DesktopBitmap &bitmap)
{
std::array<uint8_t,32> bytes{};
const auto put=[&](size_t offset,auto value) { std::memcpy(bytes.data()+offset,&value,sizeof(value)); };
put(4,int32_t(bitmap.width));
put(8,int32_t(bitmap.height));
put(12,uint32_t(bitmap.stride ? bitmap.stride : bitmap.address ? size_t(bitmap.width)*4 : bitmap.bits.size()/bitmap.height));
put(16,uint16_t(1));
put(18,uint16_t(bitmap.depth));
put(24,uint64_t(bitmap.dib_section ? bitmap.address : 0));
return bytes;
}
void NativeWindows::publish_bitmap(uint64_t handle,const DesktopBitmap *bitmap,bool touched)
{
auto *record=bitmap_table+(handle%bitmap_records)*bitmap_record;
uint64_t occupant=0;
std::memcpy(&occupant,record,8);
if (!bitmap || !bitmap->height) {
if (occupant==handle) std::memset(record,0,8);
return;
}
// Two bitmaps whose handles share the low bits: the first keeps the record and the other is answered by the runtime.
if (occupant && occupant!=handle && objects.bitmap(occupant))
return;
if (deferred(handle))
return;
const auto fields=bitmap_fields_of(*bitmap);
// Bit 1: DeleteObject may release it in the guest, as it is neither a stock bitmap, nor a window's surface, nor selected anywhere.
// Only a bitmap handed out since the guest last ran can have become selected (SelectObject looks it up), so only its selection
// is looked for again; a bitmap a context let go keeps the bit clear and is released by the runtime, as before.
uint64_t previous=0;
if (occupant==handle) std::memcpy(&previous,record+8,8);
const bool deletable=touched ? !bitmap->stock && !bitmap->shared && !objects.selected(handle) : (previous & 2)!=0;
const uint64_t flags=(bitmap->dib_section ? 1 : 0) | (deletable ? 2 : 0);
if (occupant==handle && !std::memcmp(record+8,&flags,8) && !std::memcmp(record+16,fields.data(),fields.size()))
return;
std::memset(record,0,8);
std::memcpy(record+8,&flags,8);
std::memcpy(record+16,fields.data(),fields.size());
std::memcpy(record,&handle,8);
}
void NativeWindows::publish_bitmaps()
{
if (!bitmap_table)
return;
auto &touched=objects.touched_bitmaps();
if (touched.overflow) {
std::memset(bitmap_table,0,bitmap_records*bitmap_record);
watched_bitmaps.clear();
for (const auto handle : objects.bitmap_handles()) publish_bitmap(handle,std::as_const(objects).bitmap(handle),true);
touched.overflow=false;
touched.handles.clear();
}
// As with windows: bitmaps handed out since the guest last ran are written again, and stay watched while the call goes on.
for (const auto handle : touched.settled()) {
const auto *bitmap=std::as_const(objects).bitmap(handle);
publish_bitmap(handle,bitmap,true);
const auto found=std::find_if(watched_bitmaps.begin(),watched_bitmaps.end(),[&](const auto &item) { return item.first==handle; });
if (found!=watched_bitmaps.end()) {
if (bitmap) { found->second=bitmap; found->depth=std::min(found->depth,api_depth); }
else watched_bitmaps.erase(found);
} else if (bitmap && watching) {
watched_bitmaps.push_back({handle,bitmap,api_depth});
}
}
touched.handles.clear();
for ([[maybe_unused]] const auto &[handle,bitmap,depth] : watched_bitmaps) publish_bitmap(handle,bitmap,false);
if (!api_depth) watched_bitmaps.clear();
}
void NativeWindows::set_system_table(uint8_t *table)
{
system_table=table;
for (int32_t index=0;index<128;++index) {
const uint32_t value=system_metric(objects.nonclient_metrics(),index);
std::memcpy(table+size_t(index)*4,&value,4);
}
for (int32_t index=0;index<64;++index) {
const uint32_t value=objects.system_color(index);
std::memcpy(table+512+size_t(index)*4,&value,4);
}
}
void NativeWindows::set_dc_table(uint8_t *table)
{
dc_table=table;
objects.touched_dcs().overflow=true;
publish_dcs();
}
void NativeWindows::apply_dc_log()
{
if (!dc_table)
return;
uint32_t count=0;
std::memcpy(&count,dc_table,4);
for (uint32_t index=0;index<std::min<uint32_t>(count,64);++index) {
uint64_t handle=0;
std::memcpy(&handle,dc_table+8+size_t(index)*8,8);
const auto *slot=dc_table+dc_slots_at+(handle%dc_slots)*dc_slot;
uint64_t occupant=0;
std::memcpy(&occupant,slot,8);
auto *device=occupant==handle ? objects.dc(handle) : nullptr;
if (!device)
continue;
std::memcpy(&device->text_color,slot+8,4);
std::memcpy(&device->background_color,slot+12,4);
std::memcpy(&device->background_mode,slot+16,4);
std::memcpy(&device->text_align,slot+20,4);
}
std::memset(dc_table,0,4);
}
void NativeWindows::publish_dc(uint64_t handle,const DesktopDC *device)
{
auto *slot=dc_table+dc_slots_at+(handle%dc_slots)*dc_slot;
uint64_t occupant=0;
std::memcpy(&occupant,slot,8);
if (!device) {
if (occupant==handle) std::memset(slot,0,8);
return;
}
// Two contexts whose handles share the low bits: the first keeps the slot and the other is answered by the runtime.
if (occupant && occupant!=handle && objects.dc(occupant))
return;
if (deferred(handle))
return;
const uint32_t fields[5]={device->text_color,device->background_color,device->background_mode,device->text_align,device->window ? 1U : 0U};
if (occupant==handle && !std::memcmp(slot+8,fields,20))
return;
std::memset(slot,0,8);
std::memcpy(slot+8,fields,20);
std::memcpy(slot,&handle,8);
}
void NativeWindows::publish_dcs()
{
if (!dc_table)
return;
auto &touched=objects.touched_dcs();
if (touched.overflow) {
std::memset(dc_table+dc_slots_at,0,dc_slots*dc_slot);
watched_dcs.clear();
for (const auto handle : objects.dc_handles()) publish_dc(handle,std::as_const(objects).dc(handle));
touched.overflow=false;
touched.handles.clear();
}
// As with windows: contexts handed out since the guest last ran are written again, and stay watched while the call goes on.
for (const auto handle : touched.settled()) {
const auto *device=std::as_const(objects).dc(handle);
publish_dc(handle,device);
const auto found=std::find_if(watched_dcs.begin(),watched_dcs.end(),[&](const auto &item) { return item.first==handle; });
if (found!=watched_dcs.end()) {
if (device) { found->second=device; found->depth=std::min(found->depth,api_depth); }
else watched_dcs.erase(found);
} else if (device && watching) {
watched_dcs.push_back({handle,device,api_depth});
}
}
touched.handles.clear();
for ([[maybe_unused]] const auto &[handle,device,depth] : watched_dcs) publish_dc(handle,device);
if (!api_depth) watched_dcs.clear();
}
void NativeWindows::set_deferred_tables(uint8_t *log,uint8_t *icons)
{
deferred_log=log;
icon_table=icons;
objects.touched_icons().overflow=true;
publish_icons();
}
void NativeWindows::take_deferred(const std::function<void(uint32_t,uint64_t)> &apply)
{
if (!deferred_log)
return;
uint32_t count=0;
std::memcpy(&count,deferred_log,4);
if (!count)
return;
std::memset(deferred_log,0,4);
deferred_pending.clear();
for (uint32_t index=0;index<std::min<uint32_t>(count,deferred_entries);++index) {
uint64_t entry[2]={};
std::memcpy(entry,deferred_log+16+size_t(index)*16,16);
apply(uint32_t(entry[1]),entry[0]);
}
}
void NativeWindows::publish_icons()
{
if (!icon_table)
return;
auto &touched=objects.touched_icons();
const auto publish=[&](uint64_t handle) {
auto *slot=icon_table+(handle%icon_slots)*8;
uint64_t occupant=0;
std::memcpy(&occupant,slot,8);
if (deferred(handle))
return;
if (std::as_const(objects).icon(handle)) {
// Two icons whose handles share the low bits: the first keeps the slot and the other is answered by the runtime.
if (!occupant || occupant==handle || !std::as_const(objects).icon(occupant)) std::memcpy(slot,&handle,8);
} else if (occupant==handle) {
std::memset(slot,0,8);
}
};
if (touched.overflow) {
std::memset(icon_table,0,icon_slots*8);
for (const auto handle : objects.icon_handles()) publish(handle);
touched.overflow=false;
}
for (const auto handle : touched.settled()) publish(handle);
touched.handles.clear();
}
void NativeWindows::publish_words()
{
if (system_table && published_accelerators!=objects.accelerators_version()) {
published_accelerators=objects.accelerators_version();
const auto tables=objects.accelerator_handles();
const uint32_t count=tables.size()<=64 ? uint32_t(tables.size()) : UINT32_MAX;
std::memset(system_table+1024,0,4);
if (count!=UINT32_MAX) std::memcpy(system_table+1032,tables.data(),tables.size()*8);
std::memcpy(system_table+1024,&count,4);
}
if (screen_block) {
const uint32_t flags=1U | (has_hook(-1) ? 2U : 0U);
std::memcpy(screen_block+loop_flags,&flags,4);
const uint64_t shown_desktop=desktop && std::as_const(objects).window(desktop) ? desktop : 0;
std::memcpy(screen_block+desktop_handle,&shown_desktop,8);
__atomic_store_n(reinterpret_cast<uint64_t *>(screen_block+focus_window),focus,__ATOMIC_RELEASE);
__atomic_store_n(reinterpret_cast<uint64_t *>(screen_block+focus_published),uint64_t(1),__ATOMIC_RELEASE);
}
}
void NativeWindows::api_left()
{
// What the returning call touched is written now: the call is over, so no pointer of its can change it later.
watching=false;
publish_windows();
watching=true;
--api_depth;
// What the returning call touched no longer needs watching for it.
const auto outlived=[&](const auto &item) { return item.depth>api_depth; };
std::erase_if(watched,outlived);
std::erase_if(watched_bitmaps,outlived);
std::erase_if(watched_dcs,outlived);
}
void NativeWindows::screen_dc_use(std::string_view name)
{
if (!screen_block)
return;
const uint64_t published=__atomic_load_n(reinterpret_cast<uint64_t *>(screen_block+screen_dc),__ATOMIC_ACQUIRE);
if (!published || name=="GetDeviceCaps" || name=="ReleaseDC")
return;
// Only the first argument: the calls that change a context take it there, and the other registers may hold anything.
if (process.api_arguments(1)[0]==published)
__atomic_store_n(reinterpret_cast<uint32_t *>(screen_block+screen_dc_state),uint32_t(2),__ATOMIC_RELEASE);
}
bool NativeWindows::dispatch(std::string_view name)
{
if (tree_dispatch(name) || system_dispatch(name)) return true;
// A 32-bit program's window data accessors are served by the shared handlers, which apply style changes.
if (process.mode()==NativeMode::x86 && (name=="GetWindowLongW" || name=="SetWindowLongW")) return false;
const bool data=(name.starts_with("GetWindowLong") || name.starts_with("SetWindowLong") || name.starts_with("GetClassLong") || name.starts_with("SetClassLong"));
const bool info=name=="GetClassInfoW" || name=="GetClassInfoExW" || name=="GetClassNameW" || name=="GetClassNameA";
const bool registration=name=="RegisterClassExW" || name=="RegisterClassW";
const bool creation=name=="CreateWindowExW";
const bool coordinate=name=="ClientToScreen" || name=="ScreenToClient";
const bool adjustment=name=="AdjustWindowRect" || name=="AdjustWindowRectEx";
if (!adjustment && name!="GetSystemMetrics" && !coordinate && name!="MapWindowPoints" && !info && !data && name!="CallWindowProcW" && !registration && !creation && name!="DestroyWindow" && name!="SendMessageW" && name!="IsWindow" &&
name!="IsWindowUnicode" && name!="UnregisterClassW" && name!="ShowWindow" && name!="GetDC" && name!="GetWindowDC" && name!="WindowFromDC" && name!="ReleaseDC" &&
name!="UpdateWindow" && name!="IsWindowVisible" && name!="BeginPaint" && name!="EndPaint" &&
name!="PostMessageW" && name!="PostThreadMessageW" && name!="PeekMessageW" && name!="GetMessageW" && name!="DispatchMessageW" &&
name!="TranslateMessage" && name!="PostQuitMessage" && name!="DefWindowProcW" &&
name!="GetWindowTextW" && name!="InternalGetWindowText" && name!="GetWindowTextLengthW" && name!="SetWindowTextW" &&
name!="GetFocus" && name!="SetFocus" && name!="GetActiveWindow" && name!="GetClientRect" && name!="GetWindowRect" &&
name!="InvalidateRect" && name!="ValidateRect" && name!="GetUpdateRect") return false;
const auto args=process.api_arguments(adjustment ? (name=="AdjustWindowRectEx" ? 4 : 3) : coordinate ? 2 : name=="MapWindowPoints" ? 4 : info ? 3 : data ? (name.starts_with("Set") ? 3 : 2) : name=="CallWindowProcW" ? 5 : registration ? 1 : creation ? 12 : name=="SendMessageW" ? 4 :
name=="PostMessageW" || name=="PostThreadMessageW" || name=="GetMessageW" || name=="DefWindowProcW" ? 4 : name=="PeekMessageW" ? 5 :
name=="GetWindowTextW" || name=="InternalGetWindowText" || name=="InvalidateRect" || name=="GetUpdateRect" ? 3 :
name=="SetWindowTextW" || name=="GetClientRect" || name=="GetWindowRect" || name=="ValidateRect" ? 2 :
name=="UnregisterClassW" || name=="ShowWindow" || name=="ReleaseDC" || name=="BeginPaint" || name=="EndPaint" ? 2 : 1);
uint64_t result=name=="GetMessageW" ? UINT64_MAX : 0;
try {
if (name=="GetSystemMetrics") {
const auto index=int32_t(uint32_t(args[0]));
if (index==11 || index==12) {
const auto display=objects.display();result=index==11 ? display.width : display.height;
if (result>INT32_MAX) throw uint32_t(120);
} else result=uint32_t(objects.nonclient_metrics().get(index));
}
else if (adjustment) {
const auto bytes=process.read_memory(args[0],16);
const DesktopRect rect{int32_t(read(bytes,0,4)),int32_t(read(bytes,4,4)),int32_t(read(bytes,8,4)),int32_t(read(bytes,12,4))};
const auto adjusted=adjust_window_rect(objects.nonclient_metrics(),rect,uint32_t(args[1]),uint32_t(args[2])!=0,name=="AdjustWindowRectEx" ? uint32_t(args[3]) : 0);
std::array<uint8_t,16> output{};
put(output,0,uint32_t(adjusted.left),4); put(output,4,uint32_t(adjusted.top),4);
put(output,8,uint32_t(adjusted.right),4); put(output,12,uint32_t(adjusted.bottom),4);
process.write_memory(args[0],output); result=1;
} else if (coordinate) {
if (!objects.window(args[0])) throw uint32_t(1400);
map_points(name=="ClientToScreen" ? args[0] : 0,name=="ScreenToClient" ? args[0] : 0,args[1],1);
result=1;
} else if (name=="MapWindowPoints") result=map_points(args[0],args[1],args[2],uint32_t(args[3]));
else if (info) result=class_info(name,args);
else if (data) result=long_value(name,args);
else if (name=="CallWindowProcW") {
if (!args[0] || !invoke) throw uint32_t(120);
const auto called=invoke(args[0],std::span<const uint64_t>(args).subspan(1));
if (called.stop!=NativeStop::returned) throw called;
result=called.value;
}
else if (name=="GetFocus") result=focus;
else if (name=="GetActiveWindow") result=active;
else if (name=="SetFocus") result=set_focus(args[0]);
else if (name=="InvalidateRect" || name=="ValidateRect") {
if (!args[0]) throw uint32_t(120);
const auto rect=update_rect(args[0],args[1]);
if (!(name=="InvalidateRect" ? objects.invalidate(args[0],rect,args[2]!=0) : objects.validate(args[0],rect))) throw uint32_t(8);
result=1;
} else if (name=="GetUpdateRect") {
const auto *window=objects.window(args[0]); if (!window) throw uint32_t(1400);
if (args[2]) throw uint32_t(120);
const auto rect=window->update.bounds();
if (args[1]) {
std::array<uint8_t,16> bytes{};
put(bytes,0,uint32_t(rect.left),4); put(bytes,4,uint32_t(rect.top),4);
put(bytes,8,uint32_t(rect.right),4); put(bytes,12,uint32_t(rect.bottom),4);
process.write_memory(args[1],bytes);
}
result=!window->update.empty();
}
else if (name=="GetClientRect" || name=="GetWindowRect") {
const auto *window=objects.window(args[0]); if (!window) throw uint32_t(1400);
const auto bounds=name=="GetClientRect" ? DesktopRect{0,0,window->width,window->height} : window->outer;
std::array<uint8_t,16> rect{};
put(rect,0,uint32_t(bounds.left),4); put(rect,4,uint32_t(bounds.top),4);
put(rect,8,uint32_t(bounds.right),4); put(rect,12,uint32_t(bounds.bottom),4);
process.write_memory(args[1],rect); result=1;
}
else if (name=="DefWindowProcW") result=default_message(args);
else if (name=="GetWindowTextLengthW") result=send(args[0],0x0e,0,0);
else if (name=="SetWindowTextW") result=send(args[0],0x0c,0,args[1]);
else if (name=="GetWindowTextW") {
if (int32_t(args[2])>0) result=send(args[0],0x0d,uint32_t(args[2]),args[1]);
}
else if (name=="InternalGetWindowText") {
if (int32_t(args[2])>0) result=copy_window_text(args[0],args[1],uint32_t(args[2]));
}
else if (name=="PostMessageW") { post(args[0],uint32_t(args[1]),args[2],args[3]); result=1; }
// A message for a thread has no window and goes to that thread's queue.
else if (name=="PostThreadMessageW") {
if (messages.size()>=10000) throw uint32_t(1816);
queue({0,args[2],args[3],uint32_t(args[1]),message_time(),cursor_x,cursor_y,0,uint32_t(args[0])});
result=1;
}
else if (name=="PostQuitMessage") { quits[environment.thread_id()]=uint32_t(args[0]); }
else if (name=="PeekMessageW" || name=="GetMessageW") result=retrieve(args,name=="GetMessageW");
else if (name=="DispatchMessageW" || name=="TranslateMessage") {
const auto layout=message_layout(process.mode()==NativeMode::x86);
const auto bytes=process.read_memory(args[0],layout.size);
const auto message=uint32_t(read(bytes,layout.message,4));
if (name=="TranslateMessage") {
if (message==0x100 || message==0x101 || message==0x104 || message==0x105) {
if (!key_translation || key_translation->window!=read(bytes,0,layout.pointer) || key_translation->message!=message ||
key_translation->wparam!=read(bytes,layout.wparam,layout.pointer) || key_translation->lparam!=read(bytes,layout.lparam,layout.pointer) ||
key_translation->time!=read(bytes,layout.time,4))
throw std::runtime_error("keyboard translation requires the current retrieved key message");
const auto key=*key_translation;
if ((message==0x100 || message==0x104) && key.character) {
// Posted character messages are retrieved before input still waiting in the queue.
const auto units=key.character>0xffff ? 2U : 1U;
const uint32_t kind=message==0x104 ? 0x106U : 0x102U;
if (messages.size()>10000-units) throw std::runtime_error("guest character queue limit reached");
if (units==2) {
const auto value=key.character-0x10000;
messages.insert(messages.begin(),Message{key.window,0xd800+(value>>10),key.lparam,kind,key.time,key.x,key.y});
messages.insert(messages.begin()+1,Message{key.window,0xdc00+(value & 0x3ff),key.lparam,kind,key.time,key.x,key.y});
} else messages.insert(messages.begin(),Message{key.window,key.character,key.lparam,kind,key.time,key.x,key.y});
}
result=1;
} else if (message>=0x100 && message<=0x109) throw uint32_t(120);
} else if (read(bytes,0,layout.pointer)) {
const auto target=read(bytes,0,layout.pointer);
if (message==0x113 && read(bytes,layout.lparam,layout.pointer)) {
// A timer with its own callback bypasses the window procedure.
if (!invoke) throw std::runtime_error("native window callbacks are not connected");
const uint64_t arguments[]{target,0x113,read(bytes,layout.wparam,layout.pointer),read(bytes,layout.time,4)};
const auto called=invoke(read(bytes,layout.lparam,layout.pointer),arguments);
if (called.stop!=NativeStop::returned) throw called;
result=0;
} else
result=send(target,message,read(bytes,layout.wparam,layout.pointer),read(bytes,layout.lparam,layout.pointer));
}
}
else if (registration) {
const bool extended=name=="RegisterClassExW";
const bool narrow=process.mode()==NativeMode::x86;
const size_t P=narrow ? 4 : 8;
// WNDCLASSW fields follow style at 0; the 32-bit WNDCLASSEXW shifts all of them by its cbSize.
const size_t shift=narrow && extended ? 4 : 0;
const size_t size=narrow ? (extended ? 48 : 40) : (extended ? 80 : 72);
const auto bytes=process.read_memory(args[0],size);
if (extended && read(bytes,0,4)!=size) throw uint32_t(87);
const auto style=uint32_t(read(bytes,extended ? 4 : 0,4));
const auto icon=read(bytes,3*P+8+shift,P),cursor=read(bytes,4*P+8+shift,P),menu_name=read(bytes,6*P+8+shift,P);
const bool stock_cursor=cursor>=desktop_stock_cursor && cursor<desktop_stock_cursor+desktop_stock_cursor_count;
if ((icon && !objects.icon(icon)) || (cursor && !stock_cursor)) throw uint32_t(1402);
const auto class_extra=int32_t(uint32_t(read(bytes,2*P+shift,4))),window_extra=int32_t(uint32_t(read(bytes,2*P+4+shift,4)));
if (class_extra<0 || window_extra<0) throw uint32_t(87);
if (class_extra>4096 || window_extra>4096) throw uint32_t(120);
const auto background=read(bytes,5*P+8+shift,P);
if (background && background>31 && !objects.brush(background)) throw uint32_t(background<=32 ? 120 : 6);
const auto procedure=read(bytes,P+shift,P), instance=read(bytes,2*P+8+shift,P);
auto class_name=text(read(bytes,7*P+8+shift,P));
if (!procedure || class_name.empty()) throw uint32_t(87);
// Inside an activation context whose assembly declares the class, it registers under its versioned name.
if (auto &contexts=activation_contexts(); contexts.current_thread)
if (auto versioned=contexts.registered_name(contexts.current_thread(),class_name)) class_name=std::move(*versioned);
if (objects.class_registered(instance,class_name,(style & 0x4000)!=0)) throw uint32_t(1410);
DesktopClass definition{std::move(class_name),instance,procedure,background,uint32_t(window_extra),std::vector<uint8_t>(size_t(class_extra)),style};
definition.icon=icon; definition.cursor=cursor;
if (menu_name && menu_name<=UINT16_MAX) definition.menu_id=uint32_t(menu_name);
else if (menu_name) definition.menu_name=window_text(menu_name);
const auto id=objects.register_class(std::move(definition));
if (!id) throw uint32_t(8);
result=objects.window_class(id)->atom;
} else if (creation) result=create(args);
else if (name=="SendMessageW") result=send(args[0],uint32_t(args[1]),args[2],args[3]);
else if (name=="DestroyWindow") result=destroy(args[0]);
else if (name=="WindowFromDC") {
// The window a device context belongs to: a window's own context names it, the screen's names the desktop, any other has none.
const auto *device=objects.dc(args[0]);
result=device && device->window && objects.window(device->window) ? device->window : 0;
}
else if (name=="GetDC" || name=="GetWindowDC") {
// A null window selects the screen, which is the desktop window's surface.
const auto target=args[0] ? args[0] : desktop_window();
if (!objects.window(target)) { result=foreign_dc(target); process.return_api(result); return true; }
surface_dc(target);
const auto *window=objects.window(target);
DesktopDC device{window->bitmap,window->bitmap,target}; device.leased=true;
objects.configure_window_dc(target,device,name=="GetWindowDC");
result=objects.add(device);
if (!result) throw uint32_t(8);
if (!args[0] && name=="GetDC") publish_screen_dc(target);
}
else if (name=="BeginPaint") {
auto *window=objects.window(args[0]); if (!window) throw uint32_t(1400);
if (window->painting) throw uint32_t(170);
const bool narrow=process.mode()==NativeMode::x86;
const size_t P=narrow ? 4 : 8;
const auto old=process.read_memory(args[1],narrow ? 64 : 72); process.write_memory(args[1],old);
const auto paint=start_paint(args[0]);
std::vector<uint8_t> output(old.size());
put(output,0,paint.dc,P); put(output,P,paint.erase,4);
put(output,P+4,uint32_t(paint.rect.left),4); put(output,P+8,uint32_t(paint.rect.top),4);
put(output,P+12,uint32_t(paint.rect.right),4); put(output,P+16,uint32_t(paint.rect.bottom),4);
process.write_memory(args[1],output); result=paint.dc;
} else if (name=="EndPaint") {
auto *window=objects.window(args[0]); if (!window) throw uint32_t(1400);
const bool narrow=process.mode()==NativeMode::x86;
const auto paint=process.read_memory(args[1],narrow ? 64 : 72);
if (!window->painting || read(paint,0,narrow ? 4 : 8)!=window->dc) throw uint32_t(87);
objects.finish_paint(args[0]);
if (objects.window_visible(args[0])) present(args[0]);
result=1;
}
else if (name=="ReleaseDC") {
const auto owner=args[0] ? args[0] : desktop_window();
const auto *window=objects.window(owner);
const auto *device=objects.dc(args[1]);
// Releasing the context of another process's window hands the frame drawn into it to that process.
if (!window && device && device->foreign==owner) {
if (const auto found=foreign_targets.find(owner); found!=foreign_targets.end() && found->second.surface) found->second.surface->publish_frame();
result=objects.erase(args[1]);
}
if (window) {
if (!device || !device->leased || device->window!=owner) throw uint32_t(1400);
result=objects.erase(args[1]);
// The published context comes back here once a call changed it; a fresh one takes its place.
if (withdraw_screen_dc(args[1])) publish_screen_dc(owner);
}
} else if (name=="IsWindowVisible") {
result=objects.window_visible(args[0]);
} else if (name=="ShowWindow") {
result=show_window(args[0],uint32_t(args[1]));
} else if (name=="UpdateWindow") {
if (!objects.window(args[0])) throw uint32_t(1400);
if (!objects.window(args[0])->update.empty()) send(args[0],0x0f,0,0);
result=1;
}
else if (name=="UnregisterClassW") {
const auto error=objects.unregister_class(atom(args[0],args[1]));
if (error) throw error;
result=1;
} else result=objects.window(args[0])!=nullptr;
} catch (uint32_t error) { environment.set_last_error(error); }
catch (const std::bad_alloc &) { environment.set_last_error(8); }
catch (const std::runtime_error &failure) {
const std::string_view message=failure.what();
if (std::getenv("RAVEN_TRACE_API")) std::cerr << "raven: " << message << "\n";
if (message.find("readable")==message.npos && message.find("writable")==message.npos) throw;
environment.set_last_error(998);
}
process.return_api(result);
return true;
}
}