diff --git a/drivers/vboxguest/vboxctrl/vboxctrl.asm b/drivers/vboxguest/vboxctrl/vboxctrl.asm new file mode 100644 index 000000000..621486675 --- /dev/null +++ b/drivers/vboxguest/vboxctrl/vboxctrl.asm @@ -0,0 +1,1102 @@ +; VBoxCtrl - утилита для управления драйвером VBoxGuest. +; Показывает версию, список сервисов и синхронизирует буфер обмена +; между хостом и гостем (KolibriOS) +; +; Окно опрашивает драйвер раз в секунду этого хватает. +; Clipboard гоняется в обе стороны автоматически, раз в секунду. +; +; ; Слоты не показатель, что текст обновился, разные программы по разному работают + +format binary as "" +use32 +org 0 + + db 'MENUET01' + dd 0x01 + dd START + dd I_END + dd MEM + dd stacktop + dd 0, 0 + +__DEBUG__ = 1 +__DEBUG_LEVEL__ = 1 + +include '../../../PROGRAMS/macros.inc' +include '../../../PROGRAMS/KOSfuncs.inc' +include '../../../PROGRAMS/proc32.inc' +include '../../../PROGRAMS/debug-fdo.inc' +include '../../../PROGRAMS/dll.inc' + +; IOCTL-коды драйвера +IOCTL_GET_VERSION = 0 +IOCTL_GET_SERVICES = 1 +IOCTL_SVC_ENABLE = 2 +IOCTL_SVC_DISABLE = 3 +IOCTL_CLIP_STATUS = 10 +IOCTL_CLIP_READ = 11 +IOCTL_CLIP_WRITE = 12 + +VBOX_SHCL_FMT_UNICODETEXT = 0x01 + +WINDOW_WIDTH = 420 +WINDOW_HEIGHT = 460 +MAX_SERVICES = 8 +MAX_CLIP_BUF = 65536 +PREVIEW_SIZE = 1024 +PREVIEW_COLS = 55 +PREVIEW_LINES = 5 + +BTN_CLOSE = 1 +BTN_SVC_BASE = 20 ; кнопки сервисов идут с 20 + +; цвета кнопок Enable/Disable (бледно-зелёный / бледно-красный) +COLOR_ENABLED = 0xAADDAA +COLOR_DISABLED = 0xDDAAAA + +; структура IOCTL-запроса +virtual at 0 +IOCTL: +.handle dd ? +.io_code dd ? +.input dd ? +.inp_size dd ? +.output dd ? +.out_size dd ? +.size = $ +end virtual + +; смещения внутри SERVICE_INFO (24 байта на запись) +SVC_ID = 0 +SVC_NAME = 4 +SVC_ENABLED = 20 +SVC_SIZE = 24 + +; ========== Старт ========== +START: + mcall SF_SYS_MISC, SSF_HEAP_INIT + + ; Грузим iconv, он нужен для перекодировки clipboard. + ; Если не загрузился, то работаем без него (clipboard не будет работать). + stdcall dll.Load, @IMPORT + test eax, eax + jnz .no_iconv + + invoke iconv_open, sz_cp866, sz_utf16le + mov [cd_u16_cp866], eax ; UTF-16LE -> CP866 + + invoke iconv_open, sz_utf8, sz_utf16le + mov [cd_u16_utf8], eax ; UTF-16LE -> UTF-8 + + invoke iconv_open, sz_utf16le, sz_cp866 + mov [cd_cp866_u16], eax ; CP866 -> UTF-16LE + + invoke iconv_open, sz_utf16le, sz_cp1251 + mov [cd_cp1251_u16], eax ; CP1251 -> UTF-16LE + + invoke iconv_open, sz_utf16le, sz_utf8 + mov [cd_utf8_u16], eax ; UTF-8 -> UTF-16LE + + mov byte [iconv_loaded], 1 + jmp .iconv_ok +.no_iconv: + DEBUGF 1, "[VBoxCtrl] iconv.obj not loaded!\n" + mov byte [iconv_loaded], 0 +.iconv_ok: + + ; Загружаем драйвер VBOXGUEST + mcall SF_SYS_MISC, SSF_LOAD_DRIVER, drv_name + test eax, eax + jz .no_driver + mov [driver_handle], eax + mov byte [driver_loaded], 1 + + ; Хэндл нужно вбить во все IOCTL-структуры, они статические + mov [ioctl_ver + IOCTL.handle], eax + mov [ioctl_svcl + IOCTL.handle], eax + mov [ioctl_svc_en + IOCTL.handle], eax + mov [ioctl_svc_di + IOCTL.handle], eax + mov [ioctl_clstat + IOCTL.handle], eax + mov [ioctl_clread + IOCTL.handle], eax + mov [ioctl_clwrit + IOCTL.handle], eax + + call get_driver_version + call refresh_services + jmp .events + +.no_driver: + mov byte [driver_loaded], 0 +.events: + mcall SF_SET_EVENTS_MASK, 0x07 + +; ========== главный цикл ========== +red: + call draw_window +still: + mcall SF_WAIT_EVENT_TIMEOUT, 100 ; таймаут ~1 сек + test eax, eax + jz .timeout + dec eax + jz red + dec eax + jz key + dec eax + jz button + jmp still + +.timeout: + ; Раз в секунду обновляем список сервисов и дёргаем clipboard хоста + cmp byte [driver_loaded], 1 + jne still + call refresh_services + call poll_host_clipboard + ; Проверяем по fingerprint, не изменился ли clipboard гостя + call get_clip_fingerprint + test eax, eax + jz .skip_guest_check + cmp eax, [guest_clip_fp] + je .skip_guest_check + mov [guest_clip_fp], eax + call guest_to_host_clipboard +.skip_guest_check: + call draw_window + jmp still + +; --- обработка клавиш --- +key: + mcall SF_GET_KEY ; съедаем клавишу + cmp byte [driver_loaded], 1 + jne still + call guest_to_host_clipboard + jmp still + +; --- обработка кнопок --- +button: + mcall SF_GET_BUTTON + shr eax, 8 + cmp eax, BTN_CLOSE + je .close + ; Кнопки переключения сервисов + sub eax, BTN_SVC_BASE + jl still + cmp eax, [svc_count] + jge still + call toggle_service + call refresh_services + call draw_window + jmp still +.close: + mcall -1 + +; ========== займемся художеством =) ========== +draw_window: + mcall 12, 1 + + ; Берём системные цвета и делаем из них варианты с альфой для текста + mcall 48, 3, sc, sizeof.system_colors + mov eax, [sc.work_text] + mov [wc_work_text], eax + or eax, 0x90000000 + mov [wc_text_90], eax + mov eax, [sc.work_text] + or eax, 0x80000000 + mov [wc_text_80], eax + mov eax, [sc.work_button_text] + or eax, 0x80000000 + mov [wc_btn_text], eax + + mov edx, [sc.work] + or edx, 0x13000000 ; тип окна 3 = skinned + mcall 0, <100, WINDOW_WIDTH>, <100, WINDOW_HEIGHT>, , 0, title_str + + cmp byte [driver_loaded], 1 + je .draw_normal + + ; Драйвер не загрузился — показываем ошибку и выходим + mcall 4, <10, 30>, 0x90FF0000, str_no_driver + jmp .draw_end + +.draw_normal: + ; строка "Version: N" + mcall 4, <10, 30>, [wc_text_80], str_version + mcall 47, 0x00020000, [driver_version], <65, 30>, [wc_work_text] + + ; строка "Connected: Yes/No" + mcall 4, <120, 30>, [wc_text_80], str_connected + cmp dword [clip_connected], 0 + je .not_connected + mcall 4, <185, 30>, 0x80008000, str_yes + jmp .show_fmt +.not_connected: + mcall 4, <185, 30>, 0x80FF0000, str_no +.show_fmt: + ; формат clipboard хоста + mcall 4, <260, 30>, [wc_text_80], str_formats + mcall 47, 0x00040100, [clip_host_fmt], <302, 30>, [wc_work_text] + + ; заголовок списка сервисов + mcall 4, <10, 60>, [wc_text_90], str_services + + mov dword [tmp_idx], 0 +.svc_loop: + mov eax, [tmp_idx] + cmp eax, [svc_count] + jae .svc_done + + imul eax, 26 + add eax, 85 + mov [tmp_y], eax + + mov eax, [tmp_idx] + imul eax, SVC_SIZE + add eax, svc_out_buf + 4 + mov [tmp_ptr], eax + + ; имя сервиса + mov ebx, 15 shl 16 + mov eax, [tmp_y] + add eax, 4 + add ebx, eax + mov eax, [tmp_ptr] + lea edx, [eax + SVC_NAME] + mcall 4, , [wc_text_80] + + ; статус (Enabled/Disabled) + mov ebx, 170 shl 16 + mov eax, [tmp_y] + add eax, 4 + add ebx, eax + mov eax, [tmp_ptr] + cmp dword [eax + SVC_ENABLED], 0 + je .svc_show_dis + mcall 4, , 0x80008000, str_enabled + jmp .svc_btn +.svc_show_dis: + mcall 4, , 0x80800000, str_disabled + +.svc_btn: + ; кнопка Enable/Disable справа + mov ebx, (WINDOW_WIDTH - 115) shl 16 + 85 + mov ecx, [tmp_y] + shl ecx, 16 + or ecx, 20 + mov edx, [tmp_idx] + add edx, BTN_SVC_BASE + + mov eax, [tmp_ptr] + cmp dword [eax + SVC_ENABLED], 0 + je .svc_btn_en + + ; сервис включён — кнопка "Disable" (красноватая) + mcall 8, , , , COLOR_DISABLED + mov ebx, (WINDOW_WIDTH - 108) shl 16 + mov eax, [tmp_y] + add eax, 4 + add ebx, eax + mcall 4, , [wc_btn_text], str_disable + jmp .svc_next + +.svc_btn_en: + ; сервис выключен — кнопка "Enable" (зеленоватая) + mcall 8, , , , COLOR_ENABLED + mov ebx, (WINDOW_WIDTH - 108) shl 16 + mov eax, [tmp_y] + add eax, 4 + add ebx, eax + mcall 4, , [wc_btn_text], str_enable + +.svc_next: + inc dword [tmp_idx] + jmp .svc_loop + +.svc_done: + ; --- блок превью clipboard --- + mov eax, [svc_count] + imul eax, 26 + add eax, 100 + mov [tmp_y], eax + + mov ebx, 10 shl 16 + add ebx, eax + mcall 4, , [wc_text_90], str_clipboard + + ; размер данных, если есть что показать + cmp dword [rhc_conv_len], 0 + je .skip_size + call format_size_str + mov ebx, 250 shl 16 + add ebx, [tmp_y] + mcall 4, , [wc_text_90], size_str_buf +.skip_size: + + ; фоновый прямоугольник под текст превью + add dword [tmp_y], 20 + mov ecx, [tmp_y] + shl ecx, 16 + or ecx, (PREVIEW_LINES * 16 + 10) + mcall 13, <8, (WINDOW_WIDTH - 26)>, , [sc.work_light] + + cmp dword [preview_len], 0 + je .no_preview + + mov dword [tmp_idx], 0 ; счётчик строк + mov dword [tmp_ptr], preview_buf + mov eax, [preview_len] + mov [tmp_rem], eax + +.preview_line: + cmp dword [tmp_idx], PREVIEW_LINES + jae .draw_end + cmp dword [tmp_rem], 0 + jbe .draw_end + + ; сколько символов смотрим: min(tmp_rem, PREVIEW_COLS) + mov esi, [tmp_rem] + cmp esi, PREVIEW_COLS + jbe @f + mov esi, PREVIEW_COLS +@@: + ; ищем перевод строки в текущем фрагменте + mov edi, [tmp_ptr] + mov ecx, esi + mov al, 0x0A + repne scasb + jne .no_lf + + ; нашли LF — печатаем до него (без LF и без CR перед ним) + mov eax, edi + sub eax, [tmp_ptr] + mov [tmp_skip], eax + dec eax + mov esi, eax + test esi, esi + jz .print_line + mov edi, [tmp_ptr] + cmp byte [edi + esi - 1], 0x0D + jne .print_line + dec esi + jmp .print_line + +.no_lf: + ; LF не нашли — берём всё что есть + mov [tmp_skip], esi + +.print_line: + mov ebx, 12 shl 16 + mov eax, [tmp_y] + mov ecx, [tmp_idx] + imul ecx, 16 + add eax, ecx + add eax, 5 + add ebx, eax + test esi, esi + jz .skip_print + mcall 4, , [wc_work_text], [tmp_ptr] +.skip_print: + mov eax, [tmp_skip] + add [tmp_ptr], eax + sub [tmp_rem], eax + inc dword [tmp_idx] + jmp .preview_line + +.no_preview: + mov ebx, 12 shl 16 + mov eax, [tmp_y] + add eax, 5 + add ebx, eax + mcall 4, , 0x80808080, str_no_data + +.draw_end: + mcall SF_REDRAW, SSF_END_DRAW + ret + +; --- обёртка над mcall 68,17 (вызов ioctl драйвера) --- +; ecx = указатель на IOCTL-структуру, возвращает eax = код результата +call_ioctl: + mcall SF_SYS_MISC, SSF_CONTROL_DRIVER + ret + +; --- запрашиваем версию драйвера --- +get_driver_version: + mov ecx, ioctl_ver + call call_ioctl + test eax, eax + jnz .err + mov eax, [version_buf] + mov [driver_version], eax + ret +.err: + mov dword [driver_version], 0 + ret + +; --- обновляем список сервисов из драйвера --- +refresh_services: + mov ecx, ioctl_svcl + call call_ioctl + test eax, eax + jnz .err + mov eax, dword [svc_out_buf] ; первый dd = количество + cmp eax, MAX_SERVICES + jbe @f + mov eax, MAX_SERVICES ; больше MAX_SERVICES не бывает +@@: + mov [svc_count], eax + ret +.err: + mov dword [svc_count], 0 + ret + +; --- включить/выключить сервис; eax = индекс сервиса --- +toggle_service: + push ebx ecx + cmp byte [driver_loaded], 1 + jne .done + cmp eax, [svc_count] + jae .done + + imul eax, SVC_SIZE + add eax, svc_out_buf + 4 + + mov ebx, [eax + SVC_ID] + mov [svc_id_buf], ebx + + cmp dword [eax + SVC_ENABLED], 0 + je .enable + + mov ecx, ioctl_svc_di + call call_ioctl + jmp .done +.enable: + mov ecx, ioctl_svc_en + call call_ioctl +.done: + pop ecx ebx + ret + +; --- опрос буфера обмена хоста (вызывается раз в секунду) --- +poll_host_clipboard: + mov ecx, ioctl_clstat + call call_ioctl + test eax, eax + jnz .done + + ; разбираем ответ: connected, host_formats, host_new + mov eax, dword [clip_status_buf] + mov [clip_connected], eax + mov eax, dword [clip_status_buf + 4] + mov [clip_host_fmt], eax + mov eax, dword [clip_status_buf + 8] + + ; читаем только если есть новые данные и это текст + test eax, eax + jz .done + test dword [clip_host_fmt], VBOX_SHCL_FMT_UNICODETEXT + jz .done + call read_host_clipboard +.done: + ret + +; --- читаем clipboard хоста через IOCTL и пишем в гостевой clipboard --- +; Данные приходят в UTF-16LE, мы конвертируем в CP866. +read_host_clipboard: + pushad + + ; Начинаем с буфера 64KB. Если не влезло — попробуем больше. + mov dword [rhc_bufsize], MAX_CLIP_BUF + mcall SF_SYS_MISC, SSF_MEM_ALLOC, 4 + MAX_CLIP_BUF + test eax, eax + jz .done + mov [rhc_buf], eax + +.rhc_retry: + mov eax, [rhc_buf] + mov [ioctl_clread + IOCTL.output], eax + mov eax, [rhc_bufsize] + add eax, 4 + mov [ioctl_clread + IOCTL.out_size], eax + + mov ecx, ioctl_clread + call call_ioctl + test eax, eax + jz .rhc_ok + + ; eax=1 означает overflow — нужен буфер побольше + cmp eax, 1 + jne .free + mov esi, [rhc_buf] + mov ecx, [esi] ; сколько реально нужно + test ecx, ecx + jz .free + cmp ecx, [rhc_bufsize] + jbe .free + + ; ограничиваем 100MB — больше смысла нет + cmp ecx, 100*1024*1024 + jbe @f + mov ecx, 100*1024*1024 +@@: + mov [rhc_target_size], ecx + +.rhc_alloc_retry: + mov ecx, [rhc_target_size] + add ecx, 4 + mcall SF_SYS_MISC, SSF_MEM_ALLOC + test eax, eax + jnz .rhc_alloc_ok + + ; не хватает памяти — уменьшаем запрос вдвое и пробуем снова + mov ecx, [rhc_target_size] + shr ecx, 1 + cmp ecx, 1024*1024 ; ниже 1MB не опускаемся + jb .rhc_alloc_failed + + mov [rhc_target_size], ecx + DEBUGF 2, "[VBoxCtrl] Clipboard alloc failed, reducing to %u bytes\n", ecx + jmp .rhc_alloc_retry + +.rhc_alloc_failed: + DEBUGF 2, "[VBoxCtrl] Failed to allocate clipboard buffer (minimum 1MB)\n" + jmp .free + +.rhc_alloc_ok: + push eax ; сохраняем новый буфер (free затирает eax) + mov ecx, [rhc_buf] + mcall SF_SYS_MISC, SSF_MEM_FREE ; освобождаем старый буфер + pop eax + mov [rhc_buf], eax + jmp .rhc_retry + +.rhc_ok: + ; [buf+0] = реальный размер, [buf+4..] = данные в UTF-16LE + mov esi, [rhc_buf] + mov ecx, [esi] + DEBUGF 2, "[VBoxCtrl] read_host: actual_size=%u\n", ecx + + test ecx, ecx + jz .free + + cmp ecx, 1024*1024 + jbe @f + DEBUGF 2, "[VBoxCtrl] read_host: Large clipboard (%u bytes) - conversion in progress\n", ecx +@@: + mov [rhc_u16_size], ecx + add esi, 4 + mov [rhc_u16_ptr], esi + + ; превью: конвертируем в CP866 только первые PREVIEW_SIZE байт + mov eax, [cd_u16_cp866] + mov edi, preview_buf + mov edx, PREVIEW_SIZE + call iconv_do + mov [preview_len], eax + mov byte [preview_buf + eax], 0 + + ; для гостевого clipboard конвертируем всё целиком + ; CP866-буфер нужен максимум u16_size/2 байт + mov ecx, [rhc_u16_size] + shr ecx, 1 + add ecx, 4 + mcall SF_SYS_MISC, SSF_MEM_ALLOC + test eax, eax + jnz .cb_alloc_ok + DEBUGF 2, "[VBoxCtrl] read_host: CP866 buffer alloc FAILED (need %u)\n", [rhc_u16_size] + jmp .free +.cb_alloc_ok: + mov [rhc_conv_buf], eax + + mov esi, [rhc_u16_ptr] + mov ecx, [rhc_u16_size] + mov edi, [rhc_conv_buf] + mov edx, [rhc_u16_size] + shr edx, 1 + mov eax, [cd_u16_cp866] + call iconv_do + mov [rhc_conv_len], eax + + mov ebx, [rhc_u16_size] + cmp ebx, 1024*1024 + jbe @f + DEBUGF 2, "[VBoxCtrl] read_host: Conversion complete (%u -> %u bytes)\n", ebx, eax +@@: + DEBUGF 2, "[VBoxCtrl] read_host: cp866_len=%u (u16_size=%u)\n", eax, [rhc_u16_size] + + test eax, eax + jz .conv_failed + + ; пишем CP866 в гостевой clipboard + mov esi, [rhc_conv_buf] + mov ecx, [rhc_conv_len] + mov edx, 1 ; encoding = 1 (CP866) + call write_to_guest_clipboard + jmp .conv_ok + +.conv_failed: + DEBUGF 2, "[VBoxCtrl] read_host: CP866 conversion FAILED, skipping clipboard write\n" +.conv_ok: + + ; обновляем fingerprint чтобы не словить feedback loop + call get_clip_fingerprint + mov [guest_clip_fp], eax + + mov ecx, [rhc_conv_buf] + mcall SF_SYS_MISC, SSF_MEM_FREE + mov dword [rhc_conv_buf], 0 ; обнуляем, чтоб при следующем вызове не было мусора + +.free: + mov ecx, [rhc_buf] + mcall SF_SYS_MISC, SSF_MEM_FREE +.done: + popad + ret + +; --- записать текст в гостевой clipboard (fn54.2) --- +; esi = указатель на данные, ecx = длина, edx = кодировка (0=UTF-8, 1=cp866, 2=cp1251) +write_to_guest_clipboard: + pushad + DEBUGF 2, "[VBoxCtrl] write_guest_cb: len=%u enc=%u\n", ecx, edx + mov [wgc_ptr], esi + mov [wgc_len], ecx + mov [wgc_enc], edx + + ; буфер = 12 байт заголовка + данные + lea ecx, [ecx + 12] + mov [wgc_total], ecx + mcall SF_SYS_MISC, SSF_MEM_ALLOC + test eax, eax + jnz .alloc_ok + DEBUGF 2, "[VBoxCtrl] write_guest_cb: alloc FAILED (size=%u)\n", [wgc_total] + jmp .done +.alloc_ok: + mov [wgc_buf], eax + + ; заполняем заголовок слота clipboard + mov ecx, [wgc_total] + mov dword [eax], ecx ; total_size + mov dword [eax + 4], 0 ; type = text + mov ecx, [wgc_enc] + mov dword [eax + 8], ecx ; encoding + + ; копируем данные + lea edi, [eax + 12] + mov esi, [wgc_ptr] + mov ecx, [wgc_len] + rep movsb + + ; удаляем предыдущий слот чтобы не копилось + mcall SF_CLIPBOARD, SSF_DEL_SLOT + DEBUGF 2, "[VBoxCtrl] write_guest_cb: DEL_SLOT rc=%d\n", eax + + mcall SF_CLIPBOARD, SSF_WRITE_CB, [wgc_total], [wgc_buf] + DEBUGF 2, "[VBoxCtrl] write_guest_cb: WRITE_CB rc=%d (total=%u)\n", eax, [wgc_total] + + mov ecx, [wgc_buf] + mcall SF_SYS_MISC, SSF_MEM_FREE +.done: + popad + ret + +; --- отправить гостевой clipboard на хост через IOCTL --- +guest_to_host_clipboard: + pushad + DEBUGF 1, "[VBoxCtrl] guest->host: enter\n" + + mcall SF_CLIPBOARD, 0 ; сколько слотов в clipboard? + cmp eax, -1 + je .done_noslots + test eax, eax + jz .done_noslots + DEBUGF 1, "[VBoxCtrl] guest->host: slot_count=%u\n", eax + + ; читаем последний слот + dec eax + mov ecx, eax + mcall SF_CLIPBOARD, 1 + DEBUGF 1, "[VBoxCtrl] guest->host: read slot rc=0x%x\n", eax + + cmp eax, -1 + je .done + cmp eax, 1 + je .done + mov [gth_slot], eax + + ; проверяем: тип должен быть 0 (текст), кодировка 0/1/2 + DEBUGF 1, "[VBoxCtrl] guest->host: type=%u enc=%u\n", [eax+4], [eax+8] + + cmp dword [eax + 4], 0 + jne .bad_type + mov ebx, [eax + 8] + cmp ebx, 0 ; UTF-8 + je .enc_ok + cmp ebx, 1 ; CP866 + je .enc_ok + cmp ebx, 2 ; CP1251 + je .enc_ok + jmp .bad_type +.enc_ok: + mov ecx, [eax] ; total_size + sub ecx, 12 ; минус заголовок + jbe .free_slot + mov [gth_data_len], ecx + DEBUGF 1, "[VBoxCtrl] guest->host: data_len=%u enc=%u\n", ecx, ebx + + ; обновляем превью сразу (без конвертации — cp866 и так читается) + push ecx eax + lea esi, [eax + 12] + mov edi, preview_buf + cmp ecx, PREVIEW_SIZE + jbe @f + mov ecx, PREVIEW_SIZE +@@: + mov [preview_len], ecx + rep movsb + mov byte [edi], 0 + pop eax ecx + + ; выделяем буфер под UTF-16LE: 4 (format tag) + len*2 + 2 (null-терминатор) + push ebx + lea ecx, [ecx * 2 + 6] + mcall SF_SYS_MISC, SSF_MEM_ALLOC + pop ebx + test eax, eax + jz .alloc_fail + mov [gth_u16buf], eax + + ; format tag в начале — нужен для IOCTL_CLIP_WRITE + mov dword [eax], VBOX_SHCL_FMT_UNICODETEXT + lea edi, [eax + 4] + + ; конвертируем в UTF-16LE, дескриптор зависит от кодировки + mov esi, [gth_slot] + mov ecx, [gth_data_len] + add esi, 12 + + cmp ebx, 1 + je .gth_enc_cp866 + cmp ebx, 2 + je .gth_enc_cp1251 + mov eax, [cd_utf8_u16] ; по умолчанию UTF-8 + jmp .gth_do_conv +.gth_enc_cp866: + mov eax, [cd_cp866_u16] + jmp .gth_do_conv +.gth_enc_cp1251: + mov eax, [cd_cp1251_u16] +.gth_do_conv: + mov edx, [gth_data_len] + shl edx, 1 + add edx, 2 + call iconv_do + add edi, eax + + DEBUGF 1, "[VBoxCtrl] guest->host: utf16_bytes=%u\n", eax + + ; null-терминатор и отправляем + mov word [edi], 0 + add eax, 2 + add eax, 4 ; + format dword + + mov ecx, [gth_u16buf] + mov [ioctl_clwrit + IOCTL.input], ecx + mov [ioctl_clwrit + IOCTL.inp_size], eax + DEBUGF 1, "[VBoxCtrl] guest->host: ioctl inp_size=%u\n", eax + + mov ecx, ioctl_clwrit + call call_ioctl + DEBUGF 1, "[VBoxCtrl] guest->host: ioctl rc=0x%x\n", eax + test eax, eax + jnz @f + mov eax, [gth_data_len] + mov [gth_last_len], eax +@@: + mov ecx, [gth_u16buf] + mcall SF_SYS_MISC, SSF_MEM_FREE + jmp .free_slot + +.alloc_fail: + DEBUGF 1, "[VBoxCtrl] guest->host: alloc failed\n" + jmp .free_slot + +.bad_type: + DEBUGF 1, "[VBoxCtrl] guest->host: bad type/enc, skipping\n" + +.free_slot: + mov ecx, [gth_slot] + mcall SF_SYS_MISC, SSF_MEM_FREE + jmp .done + +.done_noslots: + DEBUGF 1, "[VBoxCtrl] guest->host: no slots\n" +.done: + popad + ret + +; --- fingerprint последнего слота clipboard --- +; Возвращает: eax = FNV-1a хэш (0 если clipboard пуст или ошибка) +; Хэшируем total_size + первые 64 байта данных — этого достаточно +; чтобы отличить разные данные и не гонять clipboard туда-обратно. +get_clip_fingerprint: + push ebx + mcall SF_CLIPBOARD, SSF_GET_SLOT_COUNT + cmp eax, -1 + je .zero + test eax, eax + jz .zero + + dec eax + mov ecx, eax + mcall SF_CLIPBOARD, SSF_READ_CB + cmp eax, -1 + je .zero + cmp eax, 1 + je .zero + + ; FNV-1a 32-bit: basis=0x811c9dc5, prime=0x01000193 + push esi ecx edx + mov ebx, 0x811c9dc5 + + ; хэшируем total_size (4 байта) + mov ecx, [eax] + mov esi, 4 +.fnv_size_loop: + movzx edx, cl + xor ebx, edx + imul ebx, ebx, 0x01000193 + shr ecx, 8 + dec esi + jnz .fnv_size_loop + + ; хэшируем до 64 байт данных (пропускаем 12-байтный заголовок) + mov ecx, [eax] + sub ecx, 12 + jbe .fnv_data_done + cmp ecx, 64 + jbe @f + mov ecx, 64 +@@: + lea esi, [eax + 12] +.fnv_data_loop: + movzx edx, byte [esi] + xor ebx, edx + imul ebx, ebx, 0x01000193 + inc esi + dec ecx + jnz .fnv_data_loop +.fnv_data_done: + ; 0 зарезервирован под "нет данных", не должны вернуть его как хэш + test ebx, ebx + jnz @f + inc ebx +@@: + pop edx ecx esi + mov ecx, eax ; сохраняем указатель на буфер + push ebx + mcall SF_SYS_MISC, SSF_MEM_FREE ; освобождаем буфер слота + pop eax + pop ebx + ret +.zero: + xor eax, eax + pop ebx + ret + +; --- обёртка вызова iconv --- +; in: eax = дескриптор, esi = вход, ecx = размер входа, edi = выход, edx = размер выхода +; out: eax = сколько байт записано в выход (0 при ошибке) +iconv_do: + cmp byte [iconv_loaded], 0 + jne .ic_do + xor eax, eax + ret + +.ic_do: + push ebx esi edi ebp + mov [ic_in_ptr], esi + mov [ic_in_left], ecx + mov [ic_out_ptr], edi + mov [ic_out_left], edx + mov ebx, edx ; запоминаем исходный размер выхода + mov ebp, eax ; дескриптор + + ; iconv(cd, &in_ptr, &in_left, &out_ptr, &out_left) — stdcall + lea eax, [ic_out_left] + push eax + lea eax, [ic_out_ptr] + push eax + lea eax, [ic_in_left] + push eax + lea eax, [ic_in_ptr] + push eax + push ebp + call [iconv] + add esp, 20 + + test eax, eax + jnz .ic_err + sub ebx, [ic_out_left] ; записано = исходный - оставшийся + mov eax, ebx + pop ebp edi esi ebx + ret +.ic_err: + DEBUGF 2, "[VBoxCtrl] iconv error: %d (cd=%x, in=%d, out=%d)\n", eax, ebp, [ic_in_left], [ic_out_left] + xor eax, eax + pop ebp edi esi ebx + ret + +; --- форматируем строку "NNNNN bytes" для отображения размера clipboard --- +format_size_str: + push ebx ecx edx edi + mov eax, [rhc_conv_len] + mov edi, size_str_buf + mov ebx, 10 + xor ecx, ecx +.push_digits: + xor edx, edx + div ebx + push edx + inc ecx + test eax, eax + jnz .push_digits +.pop_digits: + pop eax + add al, '0' + stosb + dec ecx + jnz .pop_digits + mov dword [edi], ' byt' + mov word [edi + 4], 'es' + mov byte [edi + 6], 0 + pop edi edx ecx ebx + ret + +; ========== данные ========== + +align 4 + +title_str db 'VBoxCtrl',0 +str_version db 'Version: ',0 +str_connected db 'Connected: ',0 +str_yes db 'Yes',0 +str_no db 'No',0 +str_services db 'Services',0 +str_clipboard db 'Host Clipboard',0 +str_enable db 'Enable',0 +str_disable db 'Disable',0 +str_enabled db 'Enabled ',0 +str_disabled db 'Disabled',0 +str_formats db 'Fmt: 0x',0 +str_no_driver db 'VBOXGUEST driver not loaded!',0 +str_no_data db '(no data)',0 + +drv_name db 'VBOXGUEST',0 + +; имена кодировок для iconv_open +sz_utf16le db 'UTF-16LE',0 +sz_cp866 db 'CP866',0 +sz_cp1251 db 'CP1251',0 +sz_utf8 db 'UTF-8',0 + +; IOCTL-структуры (24 байта каждая), handle вписывается при старте +align 4 +ioctl_ver: dd 0, IOCTL_GET_VERSION, 0, 0, version_buf, 4 +ioctl_svcl: dd 0, IOCTL_GET_SERVICES, 0, 0, svc_out_buf, 4 + MAX_SERVICES * SVC_SIZE +ioctl_svc_en: dd 0, IOCTL_SVC_ENABLE, svc_id_buf, 4, 0, 0 +ioctl_svc_di: dd 0, IOCTL_SVC_DISABLE, svc_id_buf, 4, 0, 0 +ioctl_clstat: dd 0, IOCTL_CLIP_STATUS, 0, 0, clip_status_buf, 12 +ioctl_clread: dd 0, IOCTL_CLIP_READ, clip_read_fmt, 4, 0, 0 ; output устанавливается динамически +ioctl_clwrit: dd 0, IOCTL_CLIP_WRITE, 0, 0, 0, 0 ; input устанавливается динамически + +clip_read_fmt dd VBOX_SHCL_FMT_UNICODETEXT + +include_debug_strings + +; ========== импорт библиотек + +align 16 +@IMPORT: + +library libiconv, 'iconv.obj' + +import libiconv, \ + iconv_open, 'iconv_open', \ + iconv, 'iconv' + +I_END: + +; ========== BSS (неинициализированные данные) ========== + +align 4 +driver_handle rd 1 +driver_loaded rb 1 + rb 3 ; выравнивание +driver_version rd 1 + +svc_count rd 1 +svc_id_buf rd 1 +svc_out_buf rb 4 + MAX_SERVICES * SVC_SIZE + +clip_status_buf rb 12 +clip_connected rd 1 +clip_host_fmt rd 1 + +version_buf rd 1 + +preview_len rd 1 +preview_buf rb PREVIEW_SIZE + 1 + rb 3 ; выравнивание +size_str_buf rb 16 ; "NNNNN bytes\0" + +; временные переменные для draw_window +tmp_idx rd 1 +tmp_y rd 1 +tmp_ptr rd 1 +tmp_rem rd 1 +tmp_skip rd 1 + +; временные переменные для работы с clipboard +rhc_buf rd 1 +rhc_bufsize rd 1 +rhc_target_size rd 1 ; целевой размер при повторной аллокации +rhc_u16_ptr rd 1 ; указатель на UTF-16LE данные в rhc_buf +rhc_u16_size rd 1 ; размер UTF-16LE данных в байтах +rhc_conv_buf rd 1 ; буфер после конвертации (CP866) +rhc_conv_len rd 1 ; длина после конвертации +wgc_ptr rd 1 +wgc_len rd 1 +wgc_enc rd 1 ; кодировка (0=UTF-8, 1=cp866, 2=cp1251) +wgc_total rd 1 +wgc_buf rd 1 +gth_slot rd 1 +gth_u16buf rd 1 +gth_data_len rd 1 +gth_last_len rd 1 + +guest_clip_fp rd 1 ; fingerprint для обнаружения изменений clipboard + +; дескрипторы iconv и параметры вызова +iconv_loaded rb 1 + rb 3 +cd_u16_cp866 rd 1 ; UTF-16LE -> CP866 +cd_u16_utf8 rd 1 ; UTF-16LE -> UTF-8 +cd_cp866_u16 rd 1 ; CP866 -> UTF-16LE +cd_cp1251_u16 rd 1 ; CP1251 -> UTF-16LE +cd_utf8_u16 rd 1 ; UTF-8 -> UTF-16LE + +ic_in_ptr rd 1 +ic_in_left rd 1 +ic_out_ptr rd 1 +ic_out_left rd 1 + +; цвета, вычисляются при каждой перерисовке окна +wc_work_text rd 1 +wc_text_90 rd 1 +wc_text_80 rd 1 +wc_btn_text rd 1 + +sc system_colors + +; стек +align 4 +rb 4096 + +stacktop: +MEM: