void VDAgent::on_clipboard_grab()
{
    uint32_t types[clipboard_formats_count * VD_CLIPBOARD_FORMAT_MAX_TYPES];
    int count = 0;

    if (!VD_AGENT_HAS_CAPABILITY(_client_caps, _client_caps_size,
                                 VD_AGENT_CAP_CLIPBOARD_BY_DEMAND)) {
        return;
    }
    if (CountClipboardFormats() == 0) {
        return;
    }
    for (unsigned int i = 0; i < clipboard_formats_count; i++) {
        if (IsClipboardFormatAvailable(clipboard_formats[i].format)) {
            for (uint32_t* ptype = clipboard_formats[i].types; *ptype; ptype++) {
                types[count++] = *ptype;
            }
        }
    }
    if (count) {
        write_message(VD_AGENT_CLIPBOARD_GRAB, count * sizeof(types[0]), types);
        set_clipboard_owner(owner_guest);
    } else {
        UINT format = 0;
        while ((format = EnumClipboardFormats(format))) {
            vd_printf("Unsupported clipboard format %u", format);
        }
    }  
}
void RedClient::send_agent_clipboard_notify_message(uint32_t type, uint8_t *data, uint32_t size)
{
    ASSERT(data || !size);
    if (!_agent_connected) {
        return;
    }
    if (!VD_AGENT_HAS_CAPABILITY(_agent_caps, _agent_caps_size,
                                 VD_AGENT_CAP_CLIPBOARD_BY_DEMAND))
        return;
    if (_agent_out_msg) {
        DBG(0, "clipboard change is already pending");
        return;
    }
    if (Platform::get_clipboard_owner() != Platform::owner_client) {
        LOG_WARN("received clipboard data from client while clipboard is not owned by client");
        type = VD_AGENT_CLIPBOARD_NONE;
        size = 0;
    }
    _agent_out_msg_pos = 0;
    _agent_out_msg_size = sizeof(VDAgentMessage) + sizeof(VDAgentClipboard) + size;
    _agent_out_msg = (VDAgentMessage*)new uint8_t[_agent_out_msg_size];
    _agent_out_msg->protocol = VD_AGENT_PROTOCOL;
    _agent_out_msg->type = VD_AGENT_CLIPBOARD;
    _agent_out_msg->opaque = 0;
    _agent_out_msg->size = sizeof(VDAgentClipboard) + size;
    VDAgentClipboard* clipboard = (VDAgentClipboard*)_agent_out_msg->data;
    clipboard->type = type;
    memcpy(clipboard->data, data, size);
    if (_agent_tokens) {
        do_send_agent_clipboard();
    }
}
void RedClient::on_agent_announce_capabilities(
    VDAgentAnnounceCapabilities* caps, uint32_t msg_size)
{
    uint32_t caps_size = VD_AGENT_CAPS_SIZE_FROM_MSG_SIZE(msg_size);

    if (_agent_caps_size != caps_size) {
        delete[] _agent_caps;
        _agent_caps = new uint32_t[caps_size];
        ASSERT(_agent_caps != NULL);
        _agent_caps_size = caps_size;
    }
    memcpy(_agent_caps, caps->caps, sizeof(_agent_caps[0]) * caps_size);

    if (caps->request) {
        send_agent_announce_capabilities(false);
    }
    if (VD_AGENT_HAS_CAPABILITY(caps->caps, caps_size,
            VD_AGENT_CAP_DISPLAY_CONFIG) && !_agent_disp_config_sent) {
        // not sending the color depth through send_agent_monitors_config, since
        // it applies only for attached screens.
        send_agent_display_config();
    } else if (!_auto_display_res) {
        /* some agents don't support monitors/displays agent messages, so
         * we'll never reach on_agent_reply which sends this
         * ATTACH_CHANNELS message which is needed for client startup to go
         * on.
         */
        if (!_display_setting.is_empty()) {
            LOG_WARN("display options have been requested, but the agent doesn't support these options");
        }
        send_main_attach_channels();
        _application.deactivate_interval_timer(*_agent_timer);
    }
}
void VDAgent::on_clipboard_release()
{
    if (!VD_AGENT_HAS_CAPABILITY(_client_caps, _client_caps_size,
                                 VD_AGENT_CAP_CLIPBOARD_BY_DEMAND)) {
        return;
    }
    if (_clipboard_owner == owner_guest) {
        write_message(VD_AGENT_CLIPBOARD_RELEASE, 0, NULL);
    }
}
static void do_client_clipboard(struct vdagent_virtio_port *vport,
    VDAgentMessage *message_header, uint8_t *data)
{
    uint32_t msg_type = 0, data_type = 0, size = message_header->size;
    uint8_t selection = VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD;

    if (!active_session_conn) {
        syslog(LOG_WARNING,
               "Could not find an agent connection belonging to the "
               "active session, ignoring client clipboard request");
        return;
    }

    if (VD_AGENT_HAS_CAPABILITY(capabilities, capabilities_size,
                                VD_AGENT_CAP_CLIPBOARD_SELECTION)) {
      selection = data[0];
      data += 4;
      size -= 4;
    }

    switch (message_header->type) {
    case VD_AGENT_CLIPBOARD_GRAB:
        msg_type = VDAGENTD_CLIPBOARD_GRAB;
        agent_owns_clipboard[selection] = 0;
        break;
    case VD_AGENT_CLIPBOARD_REQUEST: {
        VDAgentClipboardRequest *req = (VDAgentClipboardRequest *)data;
        msg_type = VDAGENTD_CLIPBOARD_REQUEST;
        data_type = req->type;
        data = NULL;
        size = 0;
        break;
    }
    case VD_AGENT_CLIPBOARD: {
        VDAgentClipboard *clipboard = (VDAgentClipboard *)data;
        msg_type = VDAGENTD_CLIPBOARD_DATA;
        data_type = clipboard->type;
        size = size - sizeof(VDAgentClipboard);
        data = clipboard->data;
        break;
    }
    case VD_AGENT_CLIPBOARD_RELEASE:
        msg_type = VDAGENTD_CLIPBOARD_RELEASE;
        data = NULL;
        size = 0;
        break;
    }

    udscs_write(active_session_conn, msg_type, selection, data_type,
                data, size);
}
void RedClient::send_agent_clipboard_message(uint32_t message_type, uint32_t size, void* data)
{
    if (!_agent_connected)
        return;

    if (!VD_AGENT_HAS_CAPABILITY(_agent_caps, _agent_caps_size,
                                 VD_AGENT_CAP_CLIPBOARD_BY_DEMAND))
        return;

    Message* message = new Message(SPICE_MSGC_MAIN_AGENT_DATA);
    VDAgentMessage* msg = (VDAgentMessage*)
      spice_marshaller_reserve_space(message->marshaller(), sizeof(VDAgentMessage) + size);
    msg->protocol = VD_AGENT_PROTOCOL;
    msg->type = message_type;
    msg->opaque = 0;
    msg->size = size;
    if (size && data) {
        memcpy(msg->data, data, size);
    }
    ASSERT(_agent_tokens)
    _agent_tokens--;
    post_message(message);
}
// In delayed rendering, Windows requires us to SetClipboardData before we return from
// handling WM_RENDERFORMAT. Therefore, we try our best by sending CLIPBOARD_REQUEST to the
// agent, while waiting alertably for a while (hoping for good) for receiving CLIPBOARD data
// or CLIPBOARD_RELEASE from the agent, which both will signal clipboard_event.
// In case of unsupported format, wrong clipboard owner or no clipboard capability, we do nothing in
// WM_RENDERFORMAT and return immediately.
// FIXME: need to be handled using request queue
void VDAgent::on_clipboard_request(UINT format)
{
    uint32_t type;

    if (_clipboard_owner != owner_client) {
        vd_printf("Received render request event for format %u"
                  "while clipboard is not owned by client", format);
        return;
    }
    if (!(type = get_clipboard_type(format))) {
        vd_printf("Unsupported clipboard format %u", format);
        return;
    }
    if (!VD_AGENT_HAS_CAPABILITY(_client_caps, _client_caps_size,
                                 VD_AGENT_CAP_CLIPBOARD_BY_DEMAND)) {
        return;
    }

    VDAgentClipboardRequest request = {type};
    if (!write_message(VD_AGENT_CLIPBOARD_REQUEST, sizeof(request), &request)) {
        return;
    }

    _clipboard_tick = GetTickCount();
    while (_running && _clipboard_tick &&
           GetTickCount() < _clipboard_tick + VD_CLIPBOARD_TIMEOUT_MS) {
        event_dispatcher(VD_CLIPBOARD_TIMEOUT_MS, 0);
    }

    if (_clipboard_tick) {
        vd_printf("Clipboard wait timeout");
        _clipboard_tick = 0;
    } else {
        // reset incoming message state only upon completion (even after timeout)
        cleanup_in_msg();
    }
}