forked from KolibriOS/kolibrios
221 lines
7.7 KiB
HTML
221 lines
7.7 KiB
HTML
; =============================================================================
|
||
; Модуль : VBox Shared Clipboard Service
|
||
; Файл : services/clipboard/clipboard.inc
|
||
;
|
||
; Архитектура : Async state machine + IOCTL ABI
|
||
; clipboard_init — HGCM connect + аллокация listener пакета
|
||
; clipboard_enable — отправка первого MSG_OLD_GET_WAIT (async)
|
||
; clipboard_on_tick — проверка завершения async запроса + resubmit
|
||
;
|
||
; Данные хоста и гостя передаются через IOCTL (приложение):
|
||
; CLIP_STATUS (10) — статус (formats, host_new)
|
||
; CLIP_READ (11) — чтение данных хоста (HGCM DATA_READ)
|
||
; CLIP_WRITE (12) — запись данных гостя (буферизация + HGCM REPORT_FORMATS)
|
||
;
|
||
; Источник протокола: VBox 7.2.6
|
||
; include/VBox/HostServices/VBoxClipboardSvc.h
|
||
; =============================================================================
|
||
|
||
svc_clipboard_name db "CLIPBOARD", 0
|
||
|
||
align 4
|
||
sz_clipboard_hgcm_service db 'VBoxSharedClipboard', 0
|
||
|
||
align 4
|
||
clip_state CLIPBOARD_STATE
|
||
|
||
; clipboard_init — Подключиться к HGCM + выделить listener пакет
|
||
proc clipboard_init uses ebx
|
||
DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] Initializing...\n"
|
||
|
||
mov eax, [vbox_device.hgcm_connect_virt]
|
||
test eax, eax
|
||
jz .hgcm_not_ready
|
||
|
||
cmp dword [clip_state.connected], 1
|
||
je .already_connected
|
||
|
||
; --- Подключиться к HGCM ---
|
||
DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] Connecting to '%s'...\n", sz_clipboard_hgcm_service
|
||
stdcall hgcm_connect, sz_clipboard_hgcm_service
|
||
test eax, eax
|
||
jz .connect_failed
|
||
|
||
mov [clip_state.client_id], eax
|
||
mov dword [clip_state.connected], 1
|
||
mov dword [clip_state.listen_state], CLIP_LISTEN_IDLE
|
||
mov dword [clip_state.error_count], 0
|
||
mov dword [clip_state.formats_host], 0
|
||
mov dword [clip_state.formats_guest], 0
|
||
mov dword [clip_state.has_data], 0
|
||
DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] Connected, client_id=%d\n", eax
|
||
|
||
; --- Выделить listener пакет (или переиспользовать существующий) ---
|
||
; При fire-and-forget disconnect пакет не освобождается (хост может
|
||
; ещё DMA-писать DONE flag), поэтому переиспользуем при re-enable.
|
||
cmp dword [clip_state.listen_pkt_virt], 0
|
||
jne .pkt_ready
|
||
|
||
invoke KernelAlloc, 4096
|
||
test eax, eax
|
||
jz .alloc_failed
|
||
|
||
mov [clip_state.listen_pkt_virt], eax
|
||
invoke GetPhysAddr
|
||
mov [clip_state.listen_pkt_phys], eax
|
||
.pkt_ready:
|
||
|
||
DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] Listener pkt: virt=0x%x, phys=0x%x\n", \
|
||
[clip_state.listen_pkt_virt], [clip_state.listen_pkt_phys]
|
||
|
||
DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] Initialized OK\n"
|
||
xor eax, eax
|
||
ret
|
||
|
||
.alloc_failed:
|
||
DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] KernelAlloc failed for listener pkt\n"
|
||
stdcall hgcm_disconnect, [clip_state.client_id]
|
||
mov dword [clip_state.connected], 0
|
||
mov eax, VERR_NO_MEMORY
|
||
ret
|
||
|
||
.connect_failed:
|
||
DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] HGCM connect failed\n"
|
||
mov eax, VERR_HGCM_SERVICE_NOT_FOUND
|
||
ret
|
||
.hgcm_not_ready:
|
||
DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] HGCM not ready\n"
|
||
mov eax, VERR_NOT_READY
|
||
ret
|
||
.already_connected:
|
||
DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] Already connected\n"
|
||
xor eax, eax
|
||
ret
|
||
endp
|
||
|
||
; clipboard_enable — Отправить первый MSG_OLD_GET_WAIT
|
||
proc clipboard_enable
|
||
DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] Enabling (async listener)...\n"
|
||
|
||
cmp dword [clip_state.connected], 0
|
||
jne .connected
|
||
|
||
; После disable HGCM отключен — переподключиться
|
||
DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] Not connected, re-initializing...\n"
|
||
call clipboard_init
|
||
test eax, eax
|
||
jnz .init_failed
|
||
|
||
.connected:
|
||
; Отправить первый listener запрос
|
||
call clipboard_listener_submit
|
||
test eax, eax
|
||
jnz .submit_failed
|
||
|
||
DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] Enabled OK\n"
|
||
xor eax, eax
|
||
ret
|
||
|
||
.init_failed:
|
||
DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] Enable: re-init failed (0x%x)\n", eax
|
||
ret
|
||
|
||
.submit_failed:
|
||
DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] Enable: first submit failed (0x%x)\n", eax
|
||
xor eax, eax
|
||
ret
|
||
endp
|
||
|
||
; clipboard_on_event — Вызывается из IRQ (dispatcher_dispatch)
|
||
; ПРИМЕЧАНИЕ: IRQ обработка полностью делегирована on_tick,
|
||
; поэтому здесь просто возвращаем (событие уже обработано в tick)
|
||
proc clipboard_on_event
|
||
xor eax, eax
|
||
ret
|
||
endp
|
||
|
||
; clipboard_on_tick — Вызывается из таймера каждые 100ms
|
||
proc clipboard_on_tick
|
||
cmp dword [clip_state.connected], 0
|
||
je .done
|
||
|
||
cmp dword [clip_state.error_count], CLIP_MAX_ERRORS
|
||
jae .done
|
||
|
||
mov eax, [clip_state.listen_state]
|
||
|
||
cmp eax, CLIP_LISTEN_SUBMITTED
|
||
je .check
|
||
|
||
; IDLE — отправить новый запрос
|
||
call clipboard_listener_submit
|
||
jmp .done
|
||
|
||
.check:
|
||
call clipboard_listener_check
|
||
|
||
.done:
|
||
ret
|
||
endp
|
||
|
||
; clipboard_disable — Остановить listener, disconnect HGCM
|
||
proc clipboard_disable uses edi
|
||
DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] Disabling...\n"
|
||
|
||
cmp dword [clip_state.connected], 0
|
||
je .done
|
||
|
||
; Если listener pending — disconnect fire-and-forget (без busy-wait).
|
||
; При shutdown VBox не может быстро обработать disconnect из-за pending
|
||
; MSG_OLD_GET_WAIT, и hgcm_send_request зависает в hgcm_wait_async
|
||
; на ~30-60 сек (HGCM_TIMEOUT_MS = 500000 итераций busy-loop).
|
||
cmp dword [clip_state.listen_state], CLIP_LISTEN_SUBMITTED
|
||
je .fast_disconnect
|
||
|
||
; Нет pending запроса — обычный disconnect (sync, быстрый)
|
||
stdcall hgcm_disconnect, [clip_state.client_id]
|
||
jmp .cleanup
|
||
|
||
.fast_disconnect:
|
||
DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] Listener pending — fire-and-forget disconnect\n"
|
||
|
||
mov edi, [vbox_device.hgcm_disconnect_virt]
|
||
test edi, edi
|
||
jz .cleanup
|
||
|
||
hgcm_prepare_header edi, sizeof.HGCM_DISCONNECT, VMMDEV_HGCM_DISCONNECT
|
||
mov eax, [clip_state.client_id]
|
||
mov [edi + HGCM_DISCONNECT.client_id], eax
|
||
|
||
; Отправить через VMMDev напрямую (non-blocking out dx, eax)
|
||
; НЕ вызываем hgcm_send_request чтобы избежать hgcm_wait_async
|
||
stdcall vmmdev_send_request, [vbox_device.hgcm_disconnect_phys]
|
||
|
||
.cleanup:
|
||
; НЕ освобождаем listener пакет — хост может ещё DMA-писать DONE flag
|
||
; в эту страницу. Пакет будет переиспользован при следующем enable.
|
||
|
||
; Освободить буфер guest→host данных
|
||
cmp dword [clip_guest_buf_ptr], 0
|
||
je .no_guest_buf
|
||
invoke KernelFree, [clip_guest_buf_ptr]
|
||
mov dword [clip_guest_buf_ptr], 0
|
||
mov dword [clip_guest_buf_size], 0
|
||
.no_guest_buf:
|
||
|
||
mov dword [clip_state.connected], 0
|
||
mov dword [clip_state.client_id], 0
|
||
mov dword [clip_state.listen_state], CLIP_LISTEN_IDLE
|
||
mov dword [clip_host_new], 0
|
||
|
||
DEBUGF __DEBUG_CB__, "[VBoxGuest] [Clipboard] Disabled\n"
|
||
.done:
|
||
xor eax, eax
|
||
ret
|
||
endp
|
||
|
||
include 'clipboard_listener.inc'
|
||
|
||
REGISTER_SERVICE svc_clipboard_name, CLIPBOARD_EVENT_MASK, CLIPBOARD_CAPS_MASK, \
|
||
clipboard_init, clipboard_enable, clipboard_disable, clipboard_on_event, clipboard_on_tick, AUTOSTART_CLIPBOARD
|