diff --git a/packages/typescript-private/device-server/src/internal/channel/linux-channel.ts b/packages/typescript-private/device-server/src/internal/channel/linux-channel.ts new file mode 100644 index 000000000..ec0392c7e --- /dev/null +++ b/packages/typescript-private/device-server/src/internal/channel/linux-channel.ts @@ -0,0 +1,211 @@ +import { + DefaultDeviceSystemInfo, + DeviceSystemInfo, + DeviceWindowInfo, + ErrorResult, + FilledRuntimeInfo, + Platform, + PrivateProtocol, + ProfileMethod, + RuntimeInfo, + ScreenRecordOption, + Serial, + StreamingAnswer, +} from '@dogu-private/types'; +import { Closable, Printable, PromiseOrValue, stringify } from '@dogu-tech/common'; +import { BrowserInstallation, StreamingOfferDto } from '@dogu-tech/device-client-common'; +import { ChildProcess, isFreePort } from '@dogu-tech/node'; +import { Observable } from 'rxjs'; +import systeminformation from 'systeminformation'; +import { AppiumContext, AppiumContextKey } from '../../appium/appium.context'; +import { DeviceWebDriverHandler } from '../../device-webdriver/device-webdriver.common'; +import { SeleniumDeviceWebDriverHandler } from '../../device-webdriver/selenium.device-webdriver.handler'; +import { GamiumContext } from '../../gamium/gamium.context'; +import { logger } from '../../logger/logger.instance'; +import { DesktopCapturer } from '../externals/index'; +import { DeviceChannel, DeviceChannelOpenParam, DeviceHealthStatus, DeviceServerService, LogHandler } from '../public/device-channel'; +import { DeviceAgentService } from '../services/device-agent/device-agent-service'; +import { NullDeviceAgentService } from '../services/device-agent/null-device-agent-service'; +import { DesktopProfileService } from '../services/profile/desktop-profiler'; +import { ProfileService } from '../services/profile/profile-service'; +import { StreamingService } from '../services/streaming/streaming-service'; +import { checkTime } from '../util/check-time'; + +type DeviceControl = PrivateProtocol.DeviceControl; + +export class LinuxChannel implements DeviceChannel { + constructor( + private readonly _serial: Serial, + private readonly _info: DeviceSystemInfo, + private readonly _profile: ProfileService, + private readonly _streaming: StreamingService, + private readonly _deviceAgent: DeviceAgentService, + private readonly _seleniumDeviceWebDriverHandler: SeleniumDeviceWebDriverHandler, + readonly browserInstallations: BrowserInstallation[], + ) {} + + get serial(): Serial { + return this._serial; + } + + get serialUnique(): string { + return this._serial; + } + + get platform(): Platform { + return Platform.PLATFORM_LINUX; + } + + get info(): DeviceSystemInfo { + return this._info; + } + + get isVirtual(): boolean { + return this._info.isVirtual; + } + + static async create(param: DeviceChannelOpenParam, streaming: StreamingService, deviceServerService: DeviceServerService): Promise { + const platform = Platform.PLATFORM_LINUX; + const deviceAgent = new NullDeviceAgentService(); + + const osInfo = await checkTime('os', systeminformation.osInfo()); + const info: DeviceSystemInfo = { + ...DefaultDeviceSystemInfo(), + nickname: osInfo.hostname, + version: osInfo.release, + system: await checkTime('system', systeminformation.system()), + os: { ...osInfo, platform }, + uuid: await checkTime('uuid', systeminformation.uuid()), + cpu: await checkTime('cpu', systeminformation.cpu()), + }; + await streaming.deviceConnected(param.serial, { + serial: param.serial, + platform, + screenUrl: deviceAgent.screenUrl, + inputUrl: deviceAgent.inputUrl, + screenWidth: 0 < info.graphics.displays.length ? info.graphics.displays[0].resolutionX : 0, + screenHeight: 0 < info.graphics.displays.length ? info.graphics.displays[0].resolutionY : 0, + }); + const seleniumDeviceWebDriverHandler = new SeleniumDeviceWebDriverHandler( + platform, + param.serial, + deviceServerService.seleniumService, + deviceServerService.httpRequestRelayService, + deviceServerService.seleniumEndpointHandlerService, + deviceServerService.browserManagerService, + deviceServerService.doguLogger, + ); + + const deviceChannel = new LinuxChannel(param.serial, info, new DesktopProfileService(), streaming, deviceAgent, seleniumDeviceWebDriverHandler, []); + return Promise.resolve(deviceChannel); + } + + async queryProfile(methods: ProfileMethod[] | ProfileMethod): Promise { + const methodList = Array.isArray(methods) ? methods : [methods]; + const result = await this._profile.profile(this.serial, methodList); + return { + ...RuntimeInfo.fromPartial(result), + platform: Platform.PLATFORM_LINUX, + localTimeStamp: new Date(), + }; + } + + async startStreamingWebRtcWithTrickle(offer: StreamingOfferDto): Promise> { + return Promise.resolve(this._streaming.startStreamingWithTrickle(this.serial, offer)); + } + + async startRecord(option: ScreenRecordOption): Promise { + return Promise.resolve(this._streaming.startRecord(this.serial, option)); + } + + async stopRecord(filePath: string): Promise { + return Promise.resolve(this._streaming.stopRecord(this.serial, filePath)); + } + + control(control: DeviceControl): void { + throw new Error('Method not implemented.'); + } + + turnScreen(isOn: boolean): void { + throw new Error('Method not implemented.'); + } + + reboot(): void { + throw new Error('Method not implemented.'); + } + + checkHealth(): DeviceHealthStatus { + return { isHealthy: true, message: '' }; + } + + async killOnPort(port: number): Promise { + await ChildProcess.exec(`lsof -i tcp:${port} | grep LISTEN | awk '{print $2}' | xargs kill -9`, {}, logger); + } + + forward(hostPort: number, devicePort: number, printable?: Printable): void { + // noop + } + + unforward(hostPort: number): void { + // noop + } + + async isPortListening(port: number): Promise { + const isFree = await isFreePort(port); + return !isFree; + } + + async getWindows(): Promise { + return await DesktopCapturer.getWindows(logger); + } + + uninstallApp(appPath: string): void { + throw new Error('Method not implemented.'); + } + + installApp(appPath: string): void { + throw new Error('Method not implemented.'); + } + + runApp(appPath: string): void { + ChildProcess.spawn(appPath, [], {}, logger).catch((err) => { + logger.error(`failed to start app`, { error: stringify(err) }); + }); + } + + subscribeLog(args: string[], handler: LogHandler, printable?: Printable | undefined): PromiseOrValue { + throw new Error('Method not implemented.'); + } + + reset(): PromiseOrValue { + throw new Error('Method not implemented.'); + } + + joinWifi(ssid: string, password: string): PromiseOrValue { + throw new Error('Method not implemented.'); + } + + getAppiumContext(): null { + return null; + } + + switchAppiumContext(key: AppiumContextKey): PromiseOrValue { + throw new Error('Method not implemented.'); + } + + getAppiumCapabilities(): null { + return null; + } + + set gamiumContext(context: GamiumContext | null) { + throw new Error('Method not implemented.'); + } + + get gamiumContext(): GamiumContext | null { + return null; + } + + getWebDriverHandler(): DeviceWebDriverHandler | null { + return this._seleniumDeviceWebDriverHandler; + } +} diff --git a/packages/typescript-private/device-server/src/internal/driver/linux-driver.ts b/packages/typescript-private/device-server/src/internal/driver/linux-driver.ts new file mode 100644 index 000000000..261a7eb4b --- /dev/null +++ b/packages/typescript-private/device-server/src/internal/driver/linux-driver.ts @@ -0,0 +1,41 @@ +import { Platform, Serial } from '@dogu-private/types'; +import systeminformation from 'systeminformation'; +import { env } from '../../env'; +import { createGdcLogger, idcLogger } from '../../logger/logger.instance'; +import { LinuxChannel } from '../channel/linux-channel'; +import { DeviceChannel, DeviceChannelOpenParam, DeviceServerService } from '../public/device-channel'; +import { DeviceDriver, DeviceScanResult } from '../public/device-driver'; +import { PionStreamingService } from '../services/streaming/pion-streaming-service'; +import { StreamingService } from '../services/streaming/streaming-service'; + +export class LinuxDriver implements DeviceDriver { + private constructor(private readonly streamingService: StreamingService, private readonly deviceServerService: DeviceServerService) {} + + static async create(deviceServerService: DeviceServerService): Promise { + const streaming = await PionStreamingService.create(Platform.PLATFORM_LINUX, env.DOGU_DEVICE_SERVER_PORT, createGdcLogger(Platform.PLATFORM_LINUX)); + return new LinuxDriver(streaming, deviceServerService); + } + + get platform(): Platform { + return Platform.PLATFORM_LINUX; + } + + async scanSerials(): Promise { + const hostname = (await systeminformation.osInfo()).hostname; + const uuid = await systeminformation.uuid(); + return [{ serial: uuid.os, status: 'online', name: hostname }]; + } + + async openChannel(initParam: DeviceChannelOpenParam): Promise { + return await LinuxChannel.create(initParam, this.streamingService, this.deviceServerService); + } + + async closeChannel(seial: Serial): Promise { + return await this.streamingService.deviceDisconnected(seial); + } + + async reset(): Promise { + idcLogger.warn('LinuxDriver.reset is not implemented'); + return await Promise.resolve(); + } +} diff --git a/packages/typescript-private/device-server/src/internal/public/device-driver-factory.ts b/packages/typescript-private/device-server/src/internal/public/device-driver-factory.ts index 97709631d..03379eb97 100644 --- a/packages/typescript-private/device-server/src/internal/public/device-driver-factory.ts +++ b/packages/typescript-private/device-server/src/internal/public/device-driver-factory.ts @@ -1,6 +1,7 @@ import { Platform, PlatformType } from '@dogu-private/types'; import { AndroidDriver } from '../driver/android-driver'; import { IosDriver } from '../driver/ios-driver'; +import { LinuxDriver } from '../driver/linux-driver'; import { MacosDriver } from '../driver/macos-driver'; import { NullDeviceDriver } from '../driver/null-device-driver'; import { WindowsDriver } from '../driver/windows-driver'; @@ -38,6 +39,22 @@ export class MacOSDeviceDriverFactory implements DeviceDriverFactory { } } +export class LinuxDeviceDriverFactory implements DeviceDriverFactory { + constructor(private readonly enabledPlatforms: readonly PlatformType[], private readonly deviceServerService: DeviceServerService) {} + + async create(): Promise> { + const map = new Map(); + if (this.enabledPlatforms.includes('linux')) { + map.set(Platform.PLATFORM_LINUX, await LinuxDriver.create(this.deviceServerService)); + } + if (this.enabledPlatforms.includes('android')) { + map.set(Platform.PLATFORM_ANDROID, await AndroidDriver.create(this.deviceServerService)); + } + + return map; + } +} + export class WindowsDeviceDriverFactory implements DeviceDriverFactory { constructor(private readonly enabledPlatforms: readonly PlatformType[], private readonly deviceServerService: DeviceServerService) {} @@ -63,6 +80,8 @@ export function createDeviceDriverFactoryByHostPlatform( return new MacOSDeviceDriverFactory(enabledPlatforms, deviceServerService); case Platform.PLATFORM_WINDOWS: return new WindowsDeviceDriverFactory(enabledPlatforms, deviceServerService); + case Platform.PLATFORM_LINUX: + return new LinuxDeviceDriverFactory(enabledPlatforms, deviceServerService); default: return new NullDeviceDriverFactory(); } @@ -74,6 +93,8 @@ export async function createDeviceDriverByDevicePlatform(platform: Platform, dev return await MacosDriver.create(deviceServerService); case Platform.PLATFORM_WINDOWS: return await WindowsDriver.create(deviceServerService); + case Platform.PLATFORM_LINUX: + return await LinuxDriver.create(deviceServerService); case Platform.PLATFORM_ANDROID: return await AndroidDriver.create(deviceServerService); case Platform.PLATFORM_IOS: diff --git a/projects/go-device-controller/desktop-capturer/code/main.cpp b/projects/go-device-controller/desktop-capturer/code/main.cpp index 309b671be..24119597c 100644 --- a/projects/go-device-controller/desktop-capturer/code/main.cpp +++ b/projects/go-device-controller/desktop-capturer/code/main.cpp @@ -8,6 +8,7 @@ #include "args/args.hxx" #include "myWindows.h" #include "mywebrtc.h" +#include "webrtcUtil.h" #include @@ -97,7 +98,8 @@ void WindowCommand(args::Subparser &parser) if (info) { std::string json; - mywindows::getInfosString(json); + auto infos = webrtcUtil::getWindowInfos(); + mywindows::getInfosString(infos, json); std::cout << json << std::endl << std::flush; } } diff --git a/projects/go-device-controller/desktop-capturer/code/myWindows.cpp b/projects/go-device-controller/desktop-capturer/code/myWindows.cpp index a37b8d205..202ebbcbf 100644 --- a/projects/go-device-controller/desktop-capturer/code/myWindows.cpp +++ b/projects/go-device-controller/desktop-capturer/code/myWindows.cpp @@ -7,82 +7,10 @@ #include #include #include - -#if defined(_WIN32) -#define WEBRTC_WIN -#define NOMINMAX -#define WIN32_LEAN_AND_MEAN -#include -#elif defined(__APPLE__) -#define WEBRTC_MAC -#define WEBRTC_POSIX -#else -#define WEBRTC_POSIX -#define WEBRTC_USE_X11 -#endif // defined(_WIN32 ) - -#include "modules/desktop_capture/desktop_capture_options.h" -#include "modules/desktop_capture/desktop_capturer.h" -#include "modules/desktop_capture/desktop_frame.h" -#include "modules/desktop_capture/desktop_region.h" - -#if defined(_WIN32) -#include "modules/desktop_capture/win/window_capture_utils.h" -#include "winuser.h" -#pragma comment(lib, "User32.lib") -#elif defined(__APPLE__) -#include "modules/desktop_capture/mac/desktop_configuration.h" -#include "modules/desktop_capture/mac/full_screen_mac_application_handler.h" -#include "modules/desktop_capture/mac/window_list_utils.h" -#else -#include "modules/desktop_capture/linux/x11/window_capturer_x11.h" -#include "modules/desktop_capture/linux/x11/window_list_utils.h" -#endif // defined(_WIN32) - namespace mywindows { - -std::vector getInfos() +void getInfosString(std::vector &infos, std::string &out) { - - auto option = webrtc::DesktopCaptureOptions::CreateDefault(); - webrtc::DesktopCapturer::SourceList desktop_windows; - auto capturer = webrtc::DesktopCapturer::CreateWindowCapturer(option); - capturer->GetSourceList(&desktop_windows); - - std::vector infos; - for (auto &s : desktop_windows) - { - WindowInfo info; - info.id = s.id; - info.title = s.title; - -#if defined(_WIN32) - DWORD processId = 0; - auto threadId = GetWindowThreadProcessId(HWND(s.id), &processId); - if (0 == threadId) - { - std::cout << "GetWindowThreadProcessId failed." << std::endl; - } - info.pid = processId; - webrtc::GetWindowRect(HWND(s.id), &info.rect); -#elif defined(__APPLE__) - info.pid = webrtc::GetWindowOwnerPid(uint32_t(s.id)); - info.rect = webrtc::GetWindowBounds(uint32_t(s.id)); -#else - info.pid = 0; - webrtc::GetWindowRect(option.x_display()->display(), s.id, &info.rect); -#endif // defined(_WIN32 ) - infos.push_back(info); - } - - return infos; -} - -void getInfosString(std::string &out) -{ - auto infos = getInfos(); - std::stringstream ss; ss << "["; for (size_t i = 0; i < infos.size(); i++) diff --git a/projects/go-device-controller/desktop-capturer/code/myWindows.h b/projects/go-device-controller/desktop-capturer/code/myWindows.h index 24f0fe3f6..8af945f9a 100644 --- a/projects/go-device-controller/desktop-capturer/code/myWindows.h +++ b/projects/go-device-controller/desktop-capturer/code/myWindows.h @@ -31,8 +31,7 @@ struct WindowInfo } }; -std::vector getInfos(); -void getInfosString(std::string &out); +void getInfosString(std::vector &infos, std::string &out); } // namespace mywindows #endif /* MYWINDOWS_H */ diff --git a/projects/go-device-controller/desktop-capturer/code/mywebrtc.cpp b/projects/go-device-controller/desktop-capturer/code/mywebrtc.cpp index 96247da8f..b30fad537 100644 --- a/projects/go-device-controller/desktop-capturer/code/mywebrtc.cpp +++ b/projects/go-device-controller/desktop-capturer/code/mywebrtc.cpp @@ -1,26 +1,14 @@ #include "mywebrtc.h" #include "myWindows.h" #include "tcpClient.h" +#include "webrtcImports.h" +#include "webrtcUtil.h" #include #include #include #include -#if defined(_WIN32) -#define WEBRTC_WIN -#define NOMINMAX -#define WIN32_LEAN_AND_MEAN -#pragma comment(lib, "D3D11.lib") -#pragma comment(lib, "DXGI.lib") -#pragma comment(lib, "Winmm.lib") -#elif defined(__APPLE__) -#define WEBRTC_MAC -#define WEBRTC_POSIX -#else -#define WEBRTC_POSIX -#endif // defined(_WIN32 ) - #include "media/engine/internal_encoder_factory.h" #include "media/engine/simulcast_encoder_adapter.h" #include "modules/video_coding/codecs/vp8/libvpx_vp8_encoder.h" @@ -45,12 +33,6 @@ #include "modules/desktop_capture/desktop_frame.h" #include "modules/desktop_capture/desktop_region.h" -#if defined(__APPLE__) -#include "modules/desktop_capture/mac/desktop_configuration.h" -#include "modules/desktop_capture/mac/full_screen_mac_application_handler.h" -#include "modules/desktop_capture/mac/window_list_utils.h" -#endif // defined(__APPLE__ ) - #include "call/rtp_transport_controller_send.h" #include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" #include "modules/rtp_rtcp/source/byte_io.h" @@ -212,9 +194,8 @@ class CaptureCallback : public webrtc::DesktopCapturer::Callback std::unique_ptr createCapturer() { webrtc::DesktopCaptureOptions option = webrtc::DesktopCaptureOptions::CreateDefault(); -#ifdef WEBRTC_MAC - option.set_allow_iosurface(true); -#endif + + webrtcUtil::applyCaptureOptions(option); webrtc::DesktopCapturer::SourceList desktop_screens; @@ -232,7 +213,7 @@ std::unique_ptr createCapturer() else { - auto windows = mywindows::getInfos(); + auto windows = webrtcUtil::getWindowInfos(); for (auto &w : windows) { if (w.pid == g_pid) @@ -360,9 +341,7 @@ void startCapture() // std::cout << " CaptureFrame :" << std::this_thread::get_id() // << "\n"; // https://groups.google.com/g/discuss-webrtc/c/VsX5YrPmEmE -#ifdef WEBRTC_MAC - CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true); -#endif + webrtcUtil::onCaptureLoop(); g_capturer->CaptureFrame(); diff --git a/projects/go-device-controller/desktop-capturer/code/tcpClient.h b/projects/go-device-controller/desktop-capturer/code/tcpClient.h index 28557add0..c28dcf9b3 100644 --- a/projects/go-device-controller/desktop-capturer/code/tcpClient.h +++ b/projects/go-device-controller/desktop-capturer/code/tcpClient.h @@ -1,10 +1,3 @@ -// -// tcpClient.hpp -// webrtc_lib_practice -// -// Created by jenkins on 2022/12/05. -// - #ifndef TCPCLIENT_H #define TCPCLIENT_H diff --git a/projects/go-device-controller/desktop-capturer/code/tcpClientPosix.cpp b/projects/go-device-controller/desktop-capturer/code/tcpClientPosix.cpp index 4044303c1..30f33ac3a 100644 --- a/projects/go-device-controller/desktop-capturer/code/tcpClientPosix.cpp +++ b/projects/go-device-controller/desktop-capturer/code/tcpClientPosix.cpp @@ -1,10 +1,3 @@ -// -// tcpClient.cpp -// webrtc_lib_practice -// -// Created by Dogu on 2022/12/05. -// - #include "tcpClient.h" #if !defined(_WIN32) diff --git a/projects/go-device-controller/desktop-capturer/code/tcpClientWin.cpp b/projects/go-device-controller/desktop-capturer/code/tcpClientWin.cpp index 1b30552ee..93742e69f 100644 --- a/projects/go-device-controller/desktop-capturer/code/tcpClientWin.cpp +++ b/projects/go-device-controller/desktop-capturer/code/tcpClientWin.cpp @@ -1,10 +1,3 @@ -// -// tcpClient.cpp -// webrtc_lib_practice -// -// Created by Dogu on 2022/12/05. -// - #include "tcpClient.h" #if defined(_WIN32) diff --git a/projects/go-device-controller/desktop-capturer/code/webrtcImports.h b/projects/go-device-controller/desktop-capturer/code/webrtcImports.h new file mode 100644 index 000000000..6cd287701 --- /dev/null +++ b/projects/go-device-controller/desktop-capturer/code/webrtcImports.h @@ -0,0 +1,22 @@ +#ifndef WEBRTC_IMPORTS_H +#define WEBRTC_IMPORTS_H + +#if defined(_WIN32) +#define WEBRTC_WIN +#define NOMINMAX +#define WIN32_LEAN_AND_MEAN +#include +#pragma comment(lib, "D3D11.lib") +#pragma comment(lib, "DXGI.lib") +#pragma comment(lib, "Winmm.lib") +#elif defined(__APPLE__) +#define WEBRTC_MAC +#define WEBRTC_POSIX +#elif defined(__linux__) +#define WEBRTC_POSIX +#define WEBRTC_USE_X11 +#else +#error "Unsupported OS" +#endif // defined(_WIN32 ) + +#endif /* WEBRTC_IMPORTS_H */ \ No newline at end of file diff --git a/projects/go-device-controller/desktop-capturer/code/webrtcUtil.h b/projects/go-device-controller/desktop-capturer/code/webrtcUtil.h new file mode 100644 index 000000000..67897a6bd --- /dev/null +++ b/projects/go-device-controller/desktop-capturer/code/webrtcUtil.h @@ -0,0 +1,20 @@ +#ifndef WEBRTC_UTIL_H +#define WEBRTC_UTIL_H + +#include "myWindows.h" +#include "webrtcImports.h" + +#include "modules/desktop_capture/desktop_capture_options.h" + +#include +#include + +namespace webrtcUtil +{ +void applyCaptureOptions(webrtc::DesktopCaptureOptions &option); +void onCaptureLoop(); +std::vector getWindowInfos(); + +} // namespace webrtcUtil + +#endif /* WEBRTC_UTIL_H */ diff --git a/projects/go-device-controller/desktop-capturer/code/webrtcUtilLinux.cpp b/projects/go-device-controller/desktop-capturer/code/webrtcUtilLinux.cpp new file mode 100644 index 000000000..7108069ae --- /dev/null +++ b/projects/go-device-controller/desktop-capturer/code/webrtcUtilLinux.cpp @@ -0,0 +1,87 @@ +#if defined(__linux__) +#include "myWindows.h" +#include "webrtcUtil.h" + +#include +#include +#include +#include +#include +#include + +#define WEBRTC_POSIX +#define WEBRTC_USE_X11 + +#include "modules/desktop_capture/desktop_capture_options.h" +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/desktop_frame.h" +#include "modules/desktop_capture/desktop_region.h" + +#include "modules/desktop_capture/linux/x11/window_capturer_x11.h" +#include "modules/desktop_capture/linux/x11/window_list_utils.h" + +namespace webrtcUtil +{ + +void applyCaptureOptions(webrtc::DesktopCaptureOptions &option) +{ +} + +void onCaptureLoop() +{ +} + +int getWindowPid(::Display *display, ::Window window) +{ + Atom type, property; + int format; + unsigned long nitems, bytes_after; + unsigned char *prop = NULL; + + property = XInternAtom(display, "_NET_WM_PID", False); + if (!property) + { + return 0; + } + + int status = XGetWindowProperty(display, window, property, 0, 1024, False, AnyPropertyType, &type, &format, &nitems, &bytes_after, &prop); + + if (status != Success) + { + return 0; + } + + if (prop) + { + int pid = *((int *)prop); + XFree(prop); + return pid; + } + + return 0; +} + +std::vector getWindowInfos() +{ + auto option = webrtc::DesktopCaptureOptions::CreateDefault(); + webrtc::DesktopCapturer::SourceList desktop_windows; + auto capturer = webrtc::DesktopCapturer::CreateWindowCapturer(option); + capturer->GetSourceList(&desktop_windows); + + std::vector infos; + for (auto &s : desktop_windows) + { + mywindows::WindowInfo info; + info.id = s.id; + info.title = s.title; + + info.pid = getWindowPid(option.x_display()->display(), s.id); + webrtc::GetWindowRect(option.x_display()->display(), s.id, &info.rect); + infos.push_back(info); + } + + return infos; +} +} // namespace webrtcUtil + +#endif // defined(__linux__) \ No newline at end of file diff --git a/projects/go-device-controller/desktop-capturer/code/webrtcUtilMacOS.cpp b/projects/go-device-controller/desktop-capturer/code/webrtcUtilMacOS.cpp new file mode 100644 index 000000000..cb4bcdb40 --- /dev/null +++ b/projects/go-device-controller/desktop-capturer/code/webrtcUtilMacOS.cpp @@ -0,0 +1,60 @@ + +#if defined(__APPLE__) +#include "myWindows.h" +#include "webrtcUtil.h" + +#include +#include +#include +#include +#include +#include + +#include "modules/desktop_capture/desktop_capture_options.h" +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/desktop_frame.h" +#include "modules/desktop_capture/desktop_region.h" + +#include "modules/desktop_capture/mac/desktop_configuration.h" +#include "modules/desktop_capture/mac/full_screen_mac_application_handler.h" +#include "modules/desktop_capture/mac/window_list_utils.h" + + +namespace webrtcUtil +{ + +void applyCaptureOptions(webrtc::DesktopCaptureOptions &option) +{ + option.set_allow_iosurface(true); +} + +void onCaptureLoop() +{ + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true); +} + +std::vector getWindowInfos() +{ + auto option = webrtc::DesktopCaptureOptions::CreateDefault(); + webrtc::DesktopCapturer::SourceList desktop_windows; + auto capturer = webrtc::DesktopCapturer::CreateWindowCapturer(option); + capturer->GetSourceList(&desktop_windows); + + std::vector infos; + for (auto &s : desktop_windows) + { + mywindows::WindowInfo info; + info.id = s.id; + info.title = s.title; + + info.pid = webrtc::GetWindowOwnerPid(uint32_t(s.id)); + info.rect = webrtc::GetWindowBounds(uint32_t(s.id)); + infos.push_back(info); + } + + return infos; +} + +} // namespace webrtcUtil + +#endif // defined(__APPLE__) diff --git a/projects/go-device-controller/desktop-capturer/code/webrtcUtilWindows.cpp b/projects/go-device-controller/desktop-capturer/code/webrtcUtilWindows.cpp new file mode 100644 index 000000000..e1416697f --- /dev/null +++ b/projects/go-device-controller/desktop-capturer/code/webrtcUtilWindows.cpp @@ -0,0 +1,62 @@ + +#if defined(_WIN32) +#include "myWindows.h" +#include "webrtcUtil.h" + +#include +#include +#include +#include +#include +#include + +#include "modules/desktop_capture/desktop_capture_options.h" +#include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/desktop_frame.h" +#include "modules/desktop_capture/desktop_region.h" + +#include "modules/desktop_capture/win/window_capture_utils.h" +#include "winuser.h" +#pragma comment(lib, "User32.lib") + +namespace webrtcUtil +{ + +void applyCaptureOptions(webrtc::DesktopCaptureOptions &option) +{ +} + +void onCaptureLoop() +{ +} + +std::vector getWindowInfos() +{ + auto option = webrtc::DesktopCaptureOptions::CreateDefault(); + webrtc::DesktopCapturer::SourceList desktop_windows; + auto capturer = webrtc::DesktopCapturer::CreateWindowCapturer(option); + capturer->GetSourceList(&desktop_windows); + + std::vector infos; + for (auto &s : desktop_windows) + { + mywindows::WindowInfo info; + info.id = s.id; + info.title = s.title; + + DWORD processId = 0; + auto threadId = GetWindowThreadProcessId(HWND(s.id), &processId); + if (0 == threadId) + { + std::cout << "GetWindowThreadProcessId failed." << std::endl; + } + info.pid = processId; + webrtc::GetWindowRect(HWND(s.id), &info.rect); + infos.push_back(info); + } + + return infos; +} +} // namespace webrtcUtil + +#endif // defined(_WIN32 ) \ No newline at end of file diff --git a/projects/go-device-controller/internal/pkg/device/device.go b/projects/go-device-controller/internal/pkg/device/device.go index 6c921c976..6da75a470 100644 --- a/projects/go-device-controller/internal/pkg/device/device.go +++ b/projects/go-device-controller/internal/pkg/device/device.go @@ -31,6 +31,8 @@ func newDevice(context *types.DcGdcDeviceContext) (device, error) { return newWindowsDevice(context) case outer.Platform_PLATFORM_MACOS: return newMacOSDevice(context) + case outer.Platform_PLATFORM_LINUX: + return newLinuxDevice(context) } return nil, errors.New("invalid platform") } diff --git a/projects/go-device-controller/internal/pkg/device/linux_device.go b/projects/go-device-controller/internal/pkg/device/linux_device.go new file mode 100644 index 000000000..6941ed4d9 --- /dev/null +++ b/projects/go-device-controller/internal/pkg/device/linux_device.go @@ -0,0 +1,58 @@ +package device + +import ( + "go-device-controller/types/protocol/generated/proto/inner/types" + "go-device-controller/types/protocol/generated/proto/outer" + + "go-device-controller/internal/pkg/device/datachannel" + "go-device-controller/internal/pkg/device/robot" + "go-device-controller/internal/pkg/device/surface" + "go-device-controller/internal/pkg/structs" +) + +type linuxDevice struct { + context *types.DcGdcDeviceContext + surfaces surface.Surfaces + datachannelDemuxer datachannel.DatachannelDemuxer +} + +var _ld device = &linuxDevice{} + +func newLinuxDevice(context *types.DcGdcDeviceContext) (device, error) { + d := linuxDevice{} + d.context = context + + surfaceSourceFactory := func() surface.SurfaceSource { + return surface.NewDesktopLibwebrtcSurfaceSource() + } + surfaceFactory := func(conn *surface.Surface, surfaceType surface.SurfaceType, screenId surface.ScreenId, pid surface.Pid) { + surface.NewSurface(conn, context.Serial, outer.Platform_PLATFORM_LINUX, surfaceType, screenId, pid, surfaceSourceFactory) + } + surface.NewSurfaces(&d.surfaces, context.Serial, outer.Platform_PLATFORM_LINUX, surfaceFactory) + + datachannel.NewDatachannelDemuxer(&d.datachannelDemuxer, + []datachannel.DatachannelHandler{ + robot.NewDesktopControlHandler(outer.Platform_PLATFORM_LINUX), + }, true) + + return &d, nil +} + +func (d *linuxDevice) Context() *types.DcGdcDeviceContext { + return d.context +} + +func (d *linuxDevice) UpdateUrl(screenUrl string, inputUrl string) { +} + +func (d *linuxDevice) Surfaces() *surface.Surfaces { + return &d.surfaces +} + +func (d *linuxDevice) OnDataChannel(ctx *structs.DatachannelContext) error { + return d.datachannelDemuxer.OnDataChannel(ctx) +} + +func (d *linuxDevice) OnMessageFromPeer(data []byte) error { + return d.datachannelDemuxer.OnMessage(data) +} diff --git a/projects/go-device-controller/internal/pkg/device/robot/keyboard.go b/projects/go-device-controller/internal/pkg/device/robot/keyboard.go index 8747376ab..68f854bb6 100644 --- a/projects/go-device-controller/internal/pkg/device/robot/keyboard.go +++ b/projects/go-device-controller/internal/pkg/device/robot/keyboard.go @@ -123,8 +123,18 @@ func handleControlInjectKeyCode(c *types.DeviceControl, platform outer.Platform, return gotypes.Success } +var lastPasteTime = time.Now() + func handleSetClipboard(c *types.DeviceControl, platform outer.Platform, kb *keybd_event.KeyBonding) *outer.ErrorResult { log.Inst.Debug("MessageHandler.handleSetClipboard start") + delta := time.Now().Sub(lastPasteTime) + if delta < 2000*time.Second { + log.Inst.Debug("MessageHandler.handleSetClipboard too fast", zap.Duration("delta", delta)) + return &outer.ErrorResult{ + Code: outer.Code_CODE_DEVICE_CONTROLLER_INPUT_NOTSUPPORTED, + Message: fmt.Sprintf("MessageHandler.handleControl clipboard too fast %s", delta.String()), + } + } log.Inst.Info("MessageHandler.clearAllMetaKeys") err := clipboard.WriteAll(c.GetText()) @@ -136,6 +146,7 @@ func handleSetClipboard(c *types.DeviceControl, platform outer.Platform, kb *key } } kb.Clear() + lastPasteTime = time.Now() if runtime.GOOS == "darwin" { kb.HasSuper(true) diff --git a/projects/go-device-controller/internal/pkg/device/robot/linux_keycode.go b/projects/go-device-controller/internal/pkg/device/robot/linux_keycode.go index 12cc49cc5..d133f5a11 100644 --- a/projects/go-device-controller/internal/pkg/device/robot/linux_keycode.go +++ b/projects/go-device-controller/internal/pkg/device/robot/linux_keycode.go @@ -4,10 +4,255 @@ package robot import ( "errors" + "go-device-controller/types/protocol/generated/proto/inner/types" "go-device-controller/types/protocol/generated/proto/outer" + + "github.com/dogu-team/keybd_event" ) func getKeyCode(code types.DeviceControlKeycode, platform outer.Platform) (PseudoKey, error) { + switch code { + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_A: + return newPseudoKeyWithKey(keybd_event.VK_A) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_B: + return newPseudoKeyWithKey(keybd_event.VK_B) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_C: + return newPseudoKeyWithKey(keybd_event.VK_C) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_D: + return newPseudoKeyWithKey(keybd_event.VK_D) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_E: + return newPseudoKeyWithKey(keybd_event.VK_E) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_F: + return newPseudoKeyWithKey(keybd_event.VK_F) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_G: + return newPseudoKeyWithKey(keybd_event.VK_G) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_H: + return newPseudoKeyWithKey(keybd_event.VK_H) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_I: + return newPseudoKeyWithKey(keybd_event.VK_I) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_J: + return newPseudoKeyWithKey(keybd_event.VK_J) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_K: + return newPseudoKeyWithKey(keybd_event.VK_K) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_L: + return newPseudoKeyWithKey(keybd_event.VK_L) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_M: + return newPseudoKeyWithKey(keybd_event.VK_M) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_N: + return newPseudoKeyWithKey(keybd_event.VK_N) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_O: + return newPseudoKeyWithKey(keybd_event.VK_O) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_P: + return newPseudoKeyWithKey(keybd_event.VK_P) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_Q: + return newPseudoKeyWithKey(keybd_event.VK_Q) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_R: + return newPseudoKeyWithKey(keybd_event.VK_R) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_S: + return newPseudoKeyWithKey(keybd_event.VK_S) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_T: + return newPseudoKeyWithKey(keybd_event.VK_T) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_U: + return newPseudoKeyWithKey(keybd_event.VK_U) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_V: + return newPseudoKeyWithKey(keybd_event.VK_V) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_W: + return newPseudoKeyWithKey(keybd_event.VK_W) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_X: + return newPseudoKeyWithKey(keybd_event.VK_X) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_Y: + return newPseudoKeyWithKey(keybd_event.VK_Y) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_Z: + return newPseudoKeyWithKey(keybd_event.VK_Z) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_0: + return newPseudoKeyWithKey(keybd_event.VK_0) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_1: + return newPseudoKeyWithKey(keybd_event.VK_1) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_2: + return newPseudoKeyWithKey(keybd_event.VK_2) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_3: + return newPseudoKeyWithKey(keybd_event.VK_3) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_4: + return newPseudoKeyWithKey(keybd_event.VK_4) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_5: + return newPseudoKeyWithKey(keybd_event.VK_5) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_6: + return newPseudoKeyWithKey(keybd_event.VK_6) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_7: + return newPseudoKeyWithKey(keybd_event.VK_7) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_8: + return newPseudoKeyWithKey(keybd_event.VK_8) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_9: + return newPseudoKeyWithKey(keybd_event.VK_9) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_F1: + return newPseudoKeyWithKey(keybd_event.VK_F1) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_F2: + return newPseudoKeyWithKey(keybd_event.VK_F2) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_F3: + return newPseudoKeyWithKey(keybd_event.VK_F3) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_F4: + return newPseudoKeyWithKey(keybd_event.VK_F4) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_F5: + return newPseudoKeyWithKey(keybd_event.VK_F5) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_F6: + return newPseudoKeyWithKey(keybd_event.VK_F6) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_F7: + return newPseudoKeyWithKey(keybd_event.VK_F7) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_F8: + return newPseudoKeyWithKey(keybd_event.VK_F8) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_F9: + return newPseudoKeyWithKey(keybd_event.VK_F9) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_F10: + return newPseudoKeyWithKey(keybd_event.VK_F10) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_F11: + return newPseudoKeyWithKey(keybd_event.VK_F11) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_F12: + return newPseudoKeyWithKey(keybd_event.VK_F12) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_DEL: + return newPseudoKeyWithKey(keybd_event.VK_BACKSPACE) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_FORWARD_DEL: + return newPseudoKeyWithKey(keybd_event.VK_DELETE) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_ENTER: + return newPseudoKeyWithKey(keybd_event.VK_ENTER) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_TAB: + return newPseudoKeyWithKey(keybd_event.VK_TAB) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_ESCAPE: + return newPseudoKeyWithKey(keybd_event.VK_ESC) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_DPAD_UP: + return newPseudoKeyWithKey(keybd_event.VK_UP) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_DPAD_DOWN: + return newPseudoKeyWithKey(keybd_event.VK_DOWN) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_DPAD_RIGHT: + return newPseudoKeyWithKey(keybd_event.VK_RIGHT) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_DPAD_LEFT: + return newPseudoKeyWithKey(keybd_event.VK_LEFT) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_MOVE_HOME: + return newPseudoKeyWithKey(keybd_event.VK_HOME) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_MOVE_END: + return newPseudoKeyWithKey(keybd_event.VK_END) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_PAGE_UP: + return newPseudoKeyWithKey(keybd_event.VK_PAGEUP) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_PAGE_DOWN: + return newPseudoKeyWithKey(keybd_event.VK_PAGEDOWN) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_META_LEFT: + return PseudoKey{ + key: -1, + hasSuper: true, + }, nil + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_META_RIGHT: + return PseudoKey{ + key: -1, + hasSuper: true, + }, nil + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_ALT_LEFT: + return PseudoKey{ + key: -1, + hasALT: true, + }, nil + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_ALT_RIGHT: + return PseudoKey{ + key: -1, + hasALTGR: true, + }, nil + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_CTRL_LEFT: + return PseudoKey{ + key: -1, + hasCTRL: true, + }, nil + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_CTRL_RIGHT: + return PseudoKey{ + key: -1, + hasCTRL: true, + }, nil + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_SHIFT_LEFT: + return PseudoKey{ + key: -1, + hasSHIFT: true, + }, nil + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_SHIFT_RIGHT: + return PseudoKey{ + key: -1, + hasRSHIFT: true, + }, nil + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_SWITCH_CHARSET: + return emptyPseudoKey, errors.New("not supported") + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_CAPS_LOCK: + return newPseudoKeyWithKey(keybd_event.VK_CAPSLOCK) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_SPACE: + return newPseudoKeyWithKey(keybd_event.VK_SPACE) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_SYSRQ: + return emptyPseudoKey, errors.New("not supported") + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_VOLUME_MUTE: + return newPseudoKeyWithKey(keybd_event.VK_MUTE) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_VOLUME_DOWN: + return newPseudoKeyWithKey(keybd_event.VK_VOLUMEDOWN) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_VOLUME_UP: + return newPseudoKeyWithKey(keybd_event.VK_VOLUMEUP) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_INSERT: + return emptyPseudoKey, errors.New("not supported") + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_NUMPAD_0: + return newPseudoKeyWithKey(keybd_event.VK_KP0) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_NUMPAD_1: + return newPseudoKeyWithKey(keybd_event.VK_KP1) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_NUMPAD_2: + return newPseudoKeyWithKey(keybd_event.VK_KP2) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_NUMPAD_3: + return newPseudoKeyWithKey(keybd_event.VK_KP3) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_NUMPAD_4: + return newPseudoKeyWithKey(keybd_event.VK_KP4) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_NUMPAD_5: + return newPseudoKeyWithKey(keybd_event.VK_KP5) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_NUMPAD_6: + return newPseudoKeyWithKey(keybd_event.VK_KP6) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_NUMPAD_7: + return newPseudoKeyWithKey(keybd_event.VK_KP7) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_NUMPAD_8: + return newPseudoKeyWithKey(keybd_event.VK_KP8) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_NUMPAD_9: + return newPseudoKeyWithKey(keybd_event.VK_KP9) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_NUM_LOCK: + return emptyPseudoKey, errors.New("not supported") + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_PERIOD: + return newPseudoKeyWithKey(keybd_event.VK_KPDOT) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_NUMPAD_DOT: + return newPseudoKeyWithKey(keybd_event.VK_KPDOT) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_NUMPAD_ADD: + return newPseudoKeyWithKey(keybd_event.VK_KPPLUS) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_NUMPAD_SUBTRACT: + return newPseudoKeyWithKey(keybd_event.VK_KPMINUS) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_NUMPAD_MULTIPLY: + return newPseudoKeyWithKey(keybd_event.VK_KPASTERISK) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_NUMPAD_DIVIDE: + return newPseudoKeyWithKey(keybd_event.VK_KPSLASH) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_CLEAR: + return emptyPseudoKey, errors.New("not supported") + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_NUMPAD_ENTER: + return newPseudoKeyWithKey(keybd_event.VK_KPENTER) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_NUMPAD_EQUALS: + return newPseudoKeyWithKey(keybd_event.VK_KPEQUAL) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_COMMA: + return newPseudoKeyWithKey(keybd_event.VK_COMMA) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_SLASH: + return newPseudoKeyWithKey(keybd_event.VK_SLASH) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_BACKSLASH: + return newPseudoKeyWithKey(keybd_event.VK_BACKSLASH) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_PLUS: + return newPseudoKeyWithKey(keybd_event.VK_SP3) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_MINUS: + return newPseudoKeyWithKey(keybd_event.VK_MINUS) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_EQUALS: + return newPseudoKeyWithKey(keybd_event.VK_EQUAL) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_SEMICOLON: + return newPseudoKeyWithKey(keybd_event.VK_SEMICOLON) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_LEFT_BRACKET: + return newPseudoKeyWithKey(keybd_event.VK_SP4) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_RIGHT_BRACKET: + return newPseudoKeyWithKey(keybd_event.VK_SP5) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_GRAVE: + return newPseudoKeyWithKey(keybd_event.VK_GRAVE) + case types.DeviceControlKeycode_DEVICE_CONTROL_KEYCODE_APOSTROPHE: + return newPseudoKeyWithKey(keybd_event.VK_SP7) + } return emptyPseudoKey, errors.New("not supported") }