| 253 | } |
| 254 | |
| 255 | RuntimeControlService::RuntimeControlService(std::string vm_id, std::string pipe_name) |
| 256 | : vm_id_(std::move(vm_id)), pipe_name_(std::move(pipe_name)) { |
| 257 | console_port_->SetDataAvailableCallback([this]() { |
| 258 | if (running_) uv_async_send(&send_wakeup_); |
| 259 | }); |
| 260 | |
| 261 | display_port_->SetFrameHandler([this](DisplayFrame frame) { |
| 262 | uint32_t resW = frame.resource_width ? frame.resource_width : frame.width; |
| 263 | uint32_t resH = frame.resource_height ? frame.resource_height : frame.height; |
| 264 | |
| 265 | // Create or resize shared framebuffer when resource dimensions change. |
| 266 | // Each resize uses a unique name (generation suffix) so the Manager |
| 267 | // can open the new mapping without conflicting with the old one that |
| 268 | // may still be mapped on its side. |
| 269 | if (!shm_fb_.IsValid() || shm_fb_.width() != resW || shm_fb_.height() != resH) { |
| 270 | if (shm_fb_.IsValid()) { |
| 271 | shm_fb_.Close(); |
| 272 | } |
| 273 | ++shm_generation_; |
| 274 | std::string shm_name = ipc::GetSharedFramebufferName(vm_id_) |
| 275 | + "_" + std::to_string(shm_generation_); |
| 276 | if (!shm_fb_.Create(shm_name, resW, resH)) { |
| 277 | LOG_ERROR("RuntimeService: failed to create shared framebuffer %ux%u", resW, resH); |
| 278 | return; |
| 279 | } |
| 280 | shm_init_sent_ = false; |
| 281 | shm_frame_seq_ = 0; |
| 282 | } |
| 283 | |
| 284 | // Send shm_init notification so the manager can open the mapping. |
| 285 | if (!shm_init_sent_) { |
| 286 | ipc::Message init; |
| 287 | init.kind = ipc::Kind::kEvent; |
| 288 | init.channel = ipc::Channel::kDisplay; |
| 289 | init.type = "display.shm_init"; |
| 290 | init.vm_id = vm_id_; |
| 291 | init.request_id = next_event_id_++; |
| 292 | init.fields["shm_name"] = shm_fb_.name(); |
| 293 | init.fields["width"] = std::to_string(resW); |
| 294 | init.fields["height"] = std::to_string(resH); |
| 295 | Send(init); |
| 296 | shm_init_sent_ = true; |
| 297 | } |
| 298 | |
| 299 | // Blit dirty rect into shared memory. |
| 300 | uint32_t dx = frame.dirty_x; |
| 301 | uint32_t dy = frame.dirty_y; |
| 302 | uint32_t dw = frame.width; |
| 303 | uint32_t dh = frame.height; |
| 304 | uint32_t src_stride = frame.stride; |
| 305 | uint32_t dst_stride = shm_fb_.stride(); |
| 306 | const uint8_t* src = frame.data(); |
| 307 | uint8_t* dst = shm_fb_.data(); |
| 308 | |
| 309 | for (uint32_t row = 0; row < dh; ++row) { |
| 310 | size_t src_off = static_cast<size_t>(row) * src_stride; |
| 311 | size_t dst_off = static_cast<size_t>(dy + row) * dst_stride + |
| 312 | static_cast<size_t>(dx) * 4; |
nothing calls this directly
no test coverage detected