#include #include "windows.h" #include "nt/actctx.h" #include "nt/text.h" #include "brush.h" #include "nt/nls.h" #include "gdi.h" #include "canvas.h" #include #include #include #include #include #include #include #include #include #include namespace raven { static uint64_t read(std::span 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(true); } catch (...) {} for (const auto handle : objects.window_handles()) { const auto *window=objects.window(handle); if (handle!=desktop && !window->parent && window->bitmap && presentation.remove) remove_host_window(handle); } } void NativeWindows::remove_host_window(uint64_t handle) { if (surface_cleanup) surface_cleanup(handle); if (presentation.remove) presentation.remove(handle); } void NativeWindows::set_deadline(std::chrono::steady_clock::time_point end) { deadline=end; } void NativeWindows::set_call(std::function)> caller) { invoke=std::move(caller); } void NativeWindows::set_presentation(DesktopPresentation backend) { presentation=std::move(backend); refresh_screen(); if (presentation.poll) { auto poll=presentation.poll; presentation.poll=[this,poll=std::move(poll)](std::chrono::milliseconds budget) { auto events=poll(budget); refresh_screen(); return events; }; } } void NativeWindows::refresh_screen() { if (!presentation.screen_size) return; const auto size=presentation.screen_size(); if (!size || !objects.set_screen_size(size->first,size->second)) return; if (system_table) set_system_table(system_table); if (auto *window=objects.window(desktop)) { window->outer={0,0,size->first,size->second}; window->x=window->y=0; window->width=size->first; window->height=size->second; // Existing screen DCs remain valid when a mode changes, including their guest-side capability table. if (screen_block && objects.bitmap(window->bitmap)) { const DesktopDC device{window->bitmap,window->bitmap,desktop}; for (int32_t index=0;index<128;++index) { const uint32_t value=device_caps(objects,device,index); std::memcpy(screen_block+screen_caps+size_t(index)*4,&value,4); } } } for (const auto handle : top_level_windows()) { const auto *window=objects.window(handle); if ((window->style & 0x21000000U)==0x01000000U) reshape(handle,{0,0,size->first,size->second}); if (objects.window(handle)) post(handle,0x7e,32,uint16_t(size->first) | (uint64_t(uint16_t(size->second))<<16)); } } 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(window->outer.right)-window->outer.left,1),height=std::max(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 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(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; } } bool NativeWindows::blank_client(uint64_t handle) const { const auto *window=objects.window(handle); const auto *bitmap=window ? objects.bitmap(window->bitmap) : nullptr; if (!window) return false; if (!bitmap) return true; // Only erased so far: no WM_PAINT finished on the window and no other process has drawn anything into it or one of its children. if (!window->painted) { bool drawn=false; for (const auto drawn_window : foreign_content) if (drawn_window==handle || objects.is_child(handle,drawn_window)) { drawn=true; break; } if (!drawn) return true; } const auto pixels=bitmap->pixels(); const int64_t left=std::max(int64_t(window->x)-window->outer.left,0),top=std::max(int64_t(window->y)-window->outer.top,0); const int64_t right=std::min(left+window->width,bitmap->width),bottom=std::min(top+window->height,bitmap->height); if (pixels.size()width)*bitmap->height*4) return false; for (int64_t y=top;ywidth+size_t(left))*4; for (int64_t x=left;xpainted && !waiting->update.empty() && (!waiting->thread || waiting->thread==me); for (const auto other : objects.window_handles()) { if (pending) break; const auto *candidate=std::as_const(objects).window(other); if (candidate && other!=handle && !candidate->painted && !candidate->update.empty() && objects.is_child(handle,other) && objects.window_visible(other) && (!candidate->thread || candidate->thread==me)) { pending=true; break; } } // A window whose client area nothing reached yet would show as a black rectangle too: a program that draws it from another // process, as a browser's compositor does, ends WM_PAINT without drawing. In a program drawn that way its first frame waits a // moment for that drawing. A window that hands its client area to a graphics driver shows that surface instead and is not held. const bool blank=!pending && !waiting->presented && !foreign_content.empty() && !hosting_surface.contains(handle) && blank_client(handle); const auto now=std::chrono::steady_clock::now(); if (pending || blank) { if (!waiting->present_waiting) waiting->present_waiting=now; if (now-*waiting->present_waitingpresented ? 250 : 2000)) { dirty_roots.insert(handle); return; } } waiting->present_waiting.reset(); waiting->presented=true; } 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; auto pixels=std::span(bitmap->pixels().data(),size_t(bitmap->width)*bitmap->height*4); // The frame extended into the client area shows through what the program painted black there, black with no alpha to GDI, as the // Windows compositor shows its frame, and the compositor draws the caption buttons over it when the client area covers the caption. std::vector glazed; if (window->glass) { glazed.assign(pixels.begin(),pixels.end()); const int32_t width=int32_t(bitmap->width),height=int32_t(bitmap->height); const int32_t left=std::max(window->x-window->outer.left,0),top=std::max(window->y-window->outer.top,0); const int32_t right=std::min(width,left+window->width),bottom=std::min(height,top+window->height); const auto &margins=*window->glass; const bool whole=margins[0]<0 || margins[1]<0 || margins[2]<0 || margins[3]<0; paint::Canvas canvas{glazed.data(),width,height}; const uint32_t title=caption_color(*window,active==handle); for (int32_t y=top;y=left+margins[0] && x=top+margins[2] && ylayout.close.left && top<=layout.close.top) paint_caption_buttons(handle,*window,layout,canvas,active==handle); pixels=glazed; } // A popup without a caption that another window owns (a menu, a drop-down list), or an owned tool window (a palette), 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=host_owner(*window); const auto *owning=owner && owner!=handle ? objects.window(owner) : nullptr; if (presentation.owned_popup && owning && shown_on_owner(*window)) { 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. if (presentation.maximize) presentation.maximize(handle,(window->style & 0x01000000U)!=0); presentation.present(handle,encode_utf8(window->title,true),bitmap->width,bitmap->height,pixels); place_host_surface(handle); } std::optional 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) || !objects.window_visible(handle)) return std::nullopt; // GPU drawing supplies the client pixels itself, so it cannot wait for WM_PAINT. try { present(root,true); } 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 width=uint32_t(std::max(window->width,1)),height=uint32_t(std::max(window->height,1)); const auto surface=presentation.host_surface(root,x,y,width,height); if (surface) { hosting_surface.insert(root); hosted_areas[root]={int64_t(handle),x,y,width,height}; } return surface; } void NativeWindows::place_host_surface(uint64_t root) { const auto found=hosted_areas.find(root); if (found==hosted_areas.end() || !presentation.host_surface) return; const auto handle=uint64_t(found->second[0]); const auto *top=objects.window(root); const auto *window=objects.window(handle); if (!top || !window || objects.window_root(handle)!=root) return; const std::array area{int64_t(handle),window->x-top->outer.left,window->y-top->outer.top,std::max(window->width,1), std::max(window->height,1)}; if (area==found->second) return; found->second=area; presentation.host_surface(root,int32_t(area[1]),int32_t(area[2]),uint32_t(area[3]),uint32_t(area[4])); } std::optional> NativeWindows::client_size(uint64_t handle) { const auto *window=objects.window(handle); if (!window) return std::nullopt; return std::pair{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); hosted_areas.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. A menu popup is no guest window; the menu // loop decides about its events. const bool popup=event.window>=0x7b000000ULL && event.windowvisible)) continue; receive(event); } } static uint32_t event_kinds(const DesktopEvent &event) { switch (event.kind) { case DesktopEventKind::key: case DesktopEventKind::modifiers: return 1; case DesktopEventKind::pointer_motion: return 2; case DesktopEventKind::pointer_button: case DesktopEventKind::pointer_wheel: return 4; case DesktopEventKind::pointer_relative: return 0x400; case DesktopEventKind::message: return 0x108; case DesktopEventKind::keymap: return 0; default: return 0x40; } } uint32_t NativeWindows::queue_status(uint32_t thread,uint32_t mask,bool clear) { const auto event_thread=[&](const DesktopEvent &event) { return owner(event.window ? event.window : focus); }; if (presentation.poll) for (const auto &event : presentation.poll(std::chrono::milliseconds(0))) { pending_events.push_back(event); queues[event_thread(event)].changed|=event_kinds(event); } post_due_timers(); uint32_t current=0; for (const auto &message : messages) if (!message.thread || message.thread==thread) current|=message.kinds; if (quits.contains(thread)) current|=0x108; if (sent_for(thread)) current|=0x40; for (const auto &event : pending_events) if (!event_thread(event) || event_thread(event)==thread) current|=event_kinds(event); auto &state=queues[thread]; std::map paints; 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)) continue; current|=0x20; paints[handle]=window->update_generation; const auto previous=state.paints.find(handle); if (previous==state.paints.end() || previous->second!=window->update_generation) state.changed|=0x20; } state.paints=std::move(paints); state.changed=(state.changed | (current & ~state.current)) & current; state.current=current; const uint32_t result=((current<<16) | state.changed) & ((mask<<16) | mask); if (clear) state.changed&=~mask; return result; } uint64_t NativeWindows::foreign_dc(uint64_t handle) { auto size=foreign_window_size(handle); if (!size) throw uint32_t(1400); size->first=std::max(size->first,1); size->second=std::max(size->second,1); 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 (handlewindow_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; // A browser's compositor hands a window frames of plain black before it has anything to show: first, and again while a page // it navigates to loads. Only a frame with something on it counts as drawn. Black frames are left out, so the old page stays // until the new one paints, as the browser keeps it, however long the network takes; a window never drawn waits four seconds. bool black=true; for (size_t at=0;atsecondbitmap) : 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(width),int64_t(window->width),int64_t(bitmap->width)-x}); for (int64_t row=0; rowheight && y+rowheight); ++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