//..............................................................................
//
// USB Control Endpoint terminal session
//

import "doc_Session.jnc"
import "io_usb.jncx"
import "io_UsbDb.jnc"
import "ui_UsbUi.jnc"
import "ui_BufferPropertySet.jnc"
import "ui_StdSessionInfoSet.jnc"
import "log_ThroughputCalc.jnc"
import "log_ChecksumCalc.jnc"
import "parseInteger.jnc"
import "std_Buffer.jnc"

//..............................................................................

class UsbControlEndpointSession: doc.Session {
protected:
	enum State {
		Closed,
		Opened,
		Claimed,
	}

	enum ActionId {
		Refresh,
		Open,
		Claim,
		_Count,
	}

	enum IconId {
		Open,
		Close,
		Refresh,
		_Count,
	}

	enum StatusPaneId {
		State,
		_Count,
	}

protected:
	// usb

	State bindable m_state;

	io.UsbDevice* m_device;
	io.UsbInterface* m_interface;
	uint_t m_syncId;

	// toolbar, status bar & menu

	ui.Action* m_actionTable[ActionId._Count];
	ui.Icon* m_iconTable[IconId._Count];
	ui.StatusPane* m_statusPaneTable[StatusPaneId._Count];
	ui.ComboBox* m_deviceCombo;
	ui.ComboBox* m_interfaceCombo;
	ui.ComboBox* m_reqDirectionCombo;
	ui.ComboBox* m_reqRecipientCombo;
	ui.ComboBox* m_reqTypeCombo;
	ui.ComboBox* m_reqCodeCombo;
	ui.ComboBox* m_reqValueCombo;
	ui.ComboBox* m_reqIndexCombo;

	// property grid

	ui.EnumProperty* m_deviceProp;
	ui.EnumProperty* m_interfaceProp;
	ui.BoolProperty* m_autoDetachKernelDriverProp;
	ui.EnumProperty* m_reqDirectionProp;
	ui.EnumProperty* m_reqRecipientProp;
	ui.EnumProperty* m_reqTypeProp;
	ui.ComboProperty* m_reqCodeProp;
	ui.StringProperty* m_reqValueProp;
	ui.StringProperty* m_reqIndexProp;

	// information grid

	ui.StdSessionInfoSet m_stdSessionInfoSet;
	ui.ThroughputInfoSet m_throughputInfoSet;
	ui.ChecksumInfoSet m_checksumInfoSet;

	// ui reactor

	reactor m_uiReactor;

public:
	construct(doc.PluginHost* pluginHost);

	bool openDevice();
	void closeDevice();
	bool claimInterface();
	void releaseInterface();

	override void load(doc.Storage* storage);
	override void save(doc.Storage* storage);
	override void updateProperties();
	override errorcode bool applyProperties();
	override void restoreDefaultProperties();

	override size_t errorcode transmit(
		void const* p,
		size_t size
	);

	override bool errorcode dispatch(
		uint_t code,
		variant in,
		variant* out
	);

protected:
	bool errorcode startTransfer(
		uint_t direction,
		uint_t recipient,
		uint_t type,
		uint_t code,
		uint_t value,
		uint_t index,
		void const* p,
		size_t size
	);

	void onEnumerateDevices() {
		ui.refreshUsbDeviceListUi(m_deviceCombo, m_deviceProp);
	}

	void onOpenDevice();
	void onClaimInterface();

	void onTransferCompleted(
		uint_t syncId,
		uint_t type,
		uint_t code,
		uint_t value,
		uint_t index,
		void const* p,
		size_t size,
		std.Error const* error
	);

	void createUi();
}

// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

UsbControlEndpointSession.construct(doc.PluginHost* pluginHost) {
	basetype.construct(pluginHost);

	createUi();

	pluginHost.m_log.addSelectionProcessor(log.ThroughputCalcRangeProcessor.factory~(m_throughputInfoSet));
	pluginHost.m_log.addSelectionProcessor(log.ChecksumCalcRangeProcessor.factory~(m_checksumInfoSet));
}

void UsbControlEndpointSession.createUi() {
	static ui.EnumPropertyOption reqDirectionTable[] = {
		{ "Out", 0x00 },
		{ "In",  0x80 },
	}

	static ui.EnumPropertyOption reqTypeTable[] = {
		{ "Standard", 0x00 << 5 },
		{ "Class",    0x01 << 5 },
		{ "Vendor",   0x02 << 5 },
		{ "Reserved", 0x03 << 5 },
	}

	static ui.EnumPropertyOption reqRecipientTable[] = {
		{ "Device",    0x00 },
		{ "Interface", 0x01 },
		{ "Endpoint",  0x02 },
		{ "Other",     0x03 },
	}

	static ui.EnumPropertyOption reqCodeTable[] = {
		{ "Get Status",        "0x00" },
		{ "Clear Feature",     "0x01" },
		{ "Set Feature",       "0x03" },
		{ "Set Address",       "0x05" },
		{ "Get Descriptor",    "0x06" },
		{ "Set Descriptor",    "0x07" },
		{ "Get Configuration", "0x08" },
		{ "Set Configuration", "0x09" },
		{ "Get Interface",     "0x0a" },
		{ "Set Interface",     "0x0b" },
	}

	// toolbar

	m_iconTable[IconId.Open] = new ui.Icon("images/open.png");
	m_iconTable[IconId.Close] = new ui.Icon("images/close.png");
	m_iconTable[IconId.Refresh] = new ui.Icon("images/refresh.png");

	m_deviceCombo = m_pluginHost.m_toolBar.addComboBox(-1);

	m_actionTable[ActionId.Refresh]  = createAction("Re-enumerate devices", m_iconTable [IconId.Refresh]);
	m_actionTable[ActionId.Refresh].m_onTriggered += onEnumerateDevices;

	m_actionTable[ActionId.Open] = createAction("Open device", m_iconTable [IconId.Open]);
	m_actionTable[ActionId.Open].m_onTriggered += onOpenDevice;

	m_pluginHost.m_toolBar.addLabel("Iface:");
	m_interfaceCombo = m_pluginHost.m_toolBar.addComboBox();

	m_actionTable[ActionId.Claim] = createAction("Claim interface", m_iconTable [IconId.Open]);
	m_actionTable[ActionId.Claim].m_onTriggered += onClaimInterface;

	m_reqDirectionCombo = m_pluginHost.m_toolBar.addComboBox(
		"Dir:",
		reqDirectionTable,
		countof(reqDirectionTable)
	);

	m_reqRecipientCombo = m_pluginHost.m_toolBar.addComboBox(
		"Rcpt:",
		reqRecipientTable,
		countof(reqRecipientTable)
	);

	m_reqTypeCombo = m_pluginHost.m_toolBar.addComboBox(
		"Type:",
		reqTypeTable,
		countof(reqTypeTable)
	);

	m_reqCodeCombo = m_pluginHost.m_toolBar.addComboBox(
		"Req:",
		reqCodeTable,
		countof(reqCodeTable)
	);

	m_reqCodeCombo.m_isEditable = true;

	m_pluginHost.m_toolBar.addLabel("Val:");
	m_reqValueCombo = m_pluginHost.m_toolBar.addComboBox();
	m_reqValueCombo.m_isEditable = true;

	m_pluginHost.m_toolBar.addLabel("Idx:");
	m_reqIndexCombo = m_pluginHost.m_toolBar.addComboBox();
	m_reqIndexCombo.m_isEditable = true;

	// status bar

	m_statusPaneTable[StatusPaneId.State] = m_pluginHost.m_statusBar.addPane(, 80);

	// property grid

	ui.GroupProperty* group = m_pluginHost.m_propertyGrid.createGroupProperty(
		"Connection",
		"USB-specific connection settings"
	);

	m_deviceProp = m_pluginHost.m_propertyGrid.createEnumProperty(
		group,,
		"Device",
		"Target USB device",
		null, 0
	);

	m_autoDetachKernelDriverProp = m_pluginHost.m_propertyGrid.createBoolProperty(
		group,,
		"Auto-detach",
		"Auto detach kernel driver (if present)"
	);

	m_interfaceProp = m_pluginHost.m_propertyGrid.createEnumProperty(
		group,,
		"Interface",
		"Target USB interface",
		null, 0
	);

	m_reqDirectionProp = m_pluginHost.m_propertyGrid.createEnumProperty(
		group,,
		"Direction",
		"Direction of the USB control transfer",
		reqDirectionTable,
		countof(reqDirectionTable)
	);

	m_reqRecipientProp = m_pluginHost.m_propertyGrid.createEnumProperty(
		group,,
		"Recipient",
		"Target of the USB control transfer",
		reqRecipientTable,
		countof(reqRecipientTable)
	);

	m_reqTypeProp = m_pluginHost.m_propertyGrid.createEnumProperty(
		group,,
		"Type",
		"Class of the USB control request code",
		reqTypeTable,
		countof(reqTypeTable)
	);

	m_reqCodeProp = m_pluginHost.m_propertyGrid.createComboProperty(
		group,,
		"Request",
		"Enter a request code...",
		"Code of the USB control request",
		reqCodeTable,
		countof(reqCodeTable)
	);

	m_reqValueProp = m_pluginHost.m_propertyGrid.createStringProperty(
		group,,
		"Value",
		"Enter a request value...",
		"The 1st ('value') parameter of the USB control request"
	);

	m_reqIndexProp = m_pluginHost.m_propertyGrid.createStringProperty(
		group,,
		"Index",
		"Enter a request index...",
		"The 2nd ('index') parameter of the USB control request"
	);

	// information grid

	ui.InformationGroup* infoGroup = m_pluginHost.m_informationGrid.createGroup("USB connection");

	m_stdSessionInfoSet.create(m_pluginHost.m_txRxStats, m_pluginHost.m_informationGrid, infoGroup);
	m_throughputInfoSet.createInformationValuesGrouped(m_pluginHost.m_informationGrid);
	m_checksumInfoSet.createInformationValuesGrouped(m_pluginHost.m_informationGrid);

	m_uiReactor.start();

	ui.refreshUsbDeviceListUi(m_deviceCombo, m_deviceProp);
}

void UsbControlEndpointSession.onOpenDevice() {
	if (m_state >= State.Opened)
		closeDevice();
	else
		openDevice();
}

void UsbControlEndpointSession.onClaimInterface() {
	if (m_state >= State.Claimed)
		releaseInterface();
	else
		claimInterface();
}

bool UsbControlEndpointSession.openDevice() {
	if (m_deviceCombo.m_currentIndex == -1)
		return false;

	closeDevice();
	startSession();

	m_device = m_deviceCombo.m_currentData;

	io.UsbDeviceDesc const* deviceDesc = m_device.m_deviceDesc;

	m_device.open();

	m_interfaceCombo.clear();

	io.UsbConfigurationDesc const* confDesc = m_device.m_activeConfigurationDesc;
	ui.EnumPropertyOption* optionTable = new ui.EnumPropertyOption[confDesc.m_interfaceCount];

	for (size_t i = 0; i < confDesc.m_interfaceCount; i++) {
		io.UsbInterfaceDesc const* ifaceDesc = &confDesc.m_interfaceTable[i];
		char const* name =  $"$(ifaceDesc.m_interfaceId)";
		optionTable[i].m_text = name;
		optionTable[i].m_value = ifaceDesc;

		m_interfaceCombo.addItem(name, ifaceDesc);

		io.UsbInterfaceDesc const* ifaceAltDesc = ifaceDesc.m_nextAltSettingInterface;
		for (; ifaceAltDesc; ifaceAltDesc = ifaceAltDesc.m_nextAltSettingInterface) {
			m_interfaceCombo.addItem(
				$"  $(ifaceAltDesc.m_interfaceId):$(ifaceAltDesc.m_altSettingId)",
				ifaceAltDesc
			);
		}
	}

	m_interfaceProp.setOptions(optionTable, confDesc.m_interfaceCount);

	DeviceParams params;
	params.m_bus = m_device.m_bus;
	params.m_address = m_device.m_address;
	params.m_productId = deviceDesc.m_productId;
	params.m_vendorId = deviceDesc.m_vendorId;

	m_logWriter.write(UsbLogRecordCode.DeviceOpened, &params, sizeof(params));
	m_state = State.Opened;
	return true;

catch:
	m_logWriter.writeLastError(UsbLogRecordCode.DeviceOpenError);
	return false;
}

void UsbControlEndpointSession.closeDevice() {
	if (m_state == State.Closed)
		return;

	releaseInterface();
	m_device.close();
	m_device = null;

	m_logWriter.write(UsbLogRecordCode.DeviceClosed);
	m_state = State.Closed;
	m_syncId++;
}

bool UsbControlEndpointSession.claimInterface() {
	if (m_interfaceCombo.m_currentIndex == -1)
		return false;

	releaseInterface();

	io.UsbInterfaceDesc const* ifaceDesc = m_interfaceCombo.m_currentData;

	if (m_device.m_isKernelDriverActive[ifaceDesc.m_interfaceId] &&
		m_autoDetachKernelDriverProp.m_value) {
		m_device.detachKernelDriver(ifaceDesc.m_interfaceId);
		m_logWriter.write(UsbLogRecordCode.KernelDriverDetached, &ifaceDesc.m_interfaceId, sizeof(ifaceDesc.m_interfaceId));
	}

	m_interface = m_device.claimInterface(ifaceDesc.m_interfaceId, ifaceDesc.m_altSettingId);

	InterfaceParams params;
	params.m_interfaceId = ifaceDesc.m_interfaceId;
	params.m_altSettingId = ifaceDesc.m_altSettingId;

	m_logWriter.write(UsbLogRecordCode.InterfaceClaimed, &params, sizeof(params));
	m_state = State.Claimed;
	m_stdSessionInfoSet.startSession(m_pluginHost.m_mainThreadScheduler);
	return true;

catch:
	m_logWriter.writeLastError(UsbLogRecordCode.InterfaceClaimError);
	return false;
}

void UsbControlEndpointSession.releaseInterface() {
	if (m_state <= State.Opened)
		return;

	m_interface.release();
	m_interface = null;

	m_logWriter.writeLastError(UsbLogRecordCode.InterfaceReleased);
	m_state = State.Opened;
	m_stdSessionInfoSet.endSession();
}

void UsbControlEndpointSession.load(doc.Storage* storage) {
	uint_t vid = storage.readInt("deviceVid");
	uint_t pid = storage.readInt("devicePid");
	uint_t bus = storage.readInt("deviceBus");
	uint_t address = storage.readInt("deviceAddress");

	size_t index = ui.findUsbDeviceComboIndex(m_deviceCombo, vid, pid, bus, address);
	if (index != -1)
		m_deviceCombo.m_currentIndex = index;

	m_autoDetachKernelDriverProp.m_value = storage.readBool("autoDetachKernelDriver");
	m_reqDirectionCombo.m_currentIndex = storage.readInt("reqDirection");
	m_reqRecipientCombo.m_currentIndex = storage.readInt("reqRecipient");
	m_reqTypeCombo.m_currentIndex = storage.readInt("reqType");
	m_reqCodeCombo.m_editText = storage.readString("reqCode", "0x00");
	m_reqValueCombo.m_editText = storage.readString("reqValue", "0x0000");
	m_reqIndexCombo.m_editText = storage.readString("reqIndex", "0x0000");
}

void UsbControlEndpointSession.save(doc.Storage* storage) {
	io.UsbDevice* device = m_deviceCombo.m_currentData;
	if (device) {
		storage.writeInt("deviceVid", device.m_deviceDesc.m_vendorId);
		storage.writeInt("devicePid", device.m_deviceDesc.m_productId);
		storage.writeInt("deviceBus", device.m_bus);
		storage.writeInt("deviceAddress", device.m_address);
	}

	storage.writeBool("autoDetachKernelDriver", m_autoDetachKernelDriverProp.m_value);
	storage.writeInt("reqDirection", m_reqDirectionCombo.m_currentIndex);
	storage.writeInt("reqRecipient", m_reqRecipientCombo.m_currentIndex);
	storage.writeInt("reqType", m_reqTypeCombo.m_currentIndex);
	storage.writeString("reqCode", m_reqCodeCombo.m_editText);
	storage.writeString("reqValue", m_reqValueCombo.m_editText);
	storage.writeString("reqIndex", m_reqIndexCombo.m_editText);
}

void UsbControlEndpointSession.updateProperties() {
	m_deviceProp.m_currentIndex = m_deviceCombo.m_currentIndex;
	m_interfaceProp.m_currentIndex = m_interfaceCombo.m_currentIndex;
	m_reqDirectionProp.m_currentIndex = m_reqDirectionCombo.m_currentIndex;
	m_reqRecipientProp.m_currentIndex = m_reqRecipientCombo.m_currentIndex;
	m_reqTypeProp.m_currentIndex = m_reqTypeCombo.m_currentIndex;
	m_reqCodeProp.m_value = m_reqCodeCombo.m_editText;
	m_reqValueProp.m_value = m_reqValueCombo.m_editText;
	m_reqIndexProp.m_value = m_reqIndexCombo.m_editText;
}

bool errorcode UsbControlEndpointSession.applyProperties() {
	m_deviceCombo.m_currentIndex = m_deviceProp.m_currentIndex;
	m_interfaceCombo.m_currentIndex = m_interfaceProp.m_currentIndex;
	m_reqDirectionCombo.m_currentIndex = m_reqDirectionProp.m_currentIndex;
	m_reqRecipientCombo.m_currentIndex = m_reqRecipientProp.m_currentIndex;
	m_reqTypeCombo.m_currentIndex = m_reqTypeProp.m_currentIndex;
	m_reqCodeCombo.m_editText = m_reqCodeProp.m_value;
	m_reqValueCombo.m_editText = m_reqValueProp.m_value;
	m_reqIndexCombo.m_editText = m_reqIndexProp.m_value;

	return true;
}

void UsbControlEndpointSession.restoreDefaultProperties() {
	m_autoDetachKernelDriverProp.m_value = true;
	m_reqDirectionProp.m_currentIndex = 0;
	m_reqRecipientProp.m_currentIndex = 0;
	m_reqTypeProp.m_currentIndex = 0;
	m_reqCodeProp.m_value = "0x00";
	m_reqValueProp.m_value = "0x0000";
	m_reqIndexProp.m_value = "0x0000";
}

size_t errorcode UsbControlEndpointSession.transmit(
	void const* p,
	size_t size
) {
	if (!m_state) {
		std.setError("device not opened");
		return -1;
	}

	uint_t direction = m_reqDirectionCombo.m_currentData;
	uint_t recipient = m_reqRecipientCombo.m_currentData;
	uint_t type = m_reqTypeCombo.m_currentData;
	uint_t code = parseInteger(m_reqCodeCombo.m_editText);;
	uint_t value = parseInteger(m_reqValueCombo.m_editText);
	uint_t index = parseInteger(m_reqIndexCombo.m_editText);

	startTransfer(
		direction,
		recipient,
		type,
		code,
		value,
		index,
		p,
		size
	);

	return size;

catch:
	m_logWriter.writeLastError(log.StdRecordCode.Error);
	return -1;
}

bool errorcode UsbControlEndpointSession.startTransfer(
	uint_t direction,
	uint_t recipient,
	uint_t type,
	uint_t code,
	uint_t value,
	uint_t index,
	void const* p,
	size_t size
) {
	char* buffer = new char[size];
	if (!direction)
		memcpy(buffer, p, size);

	return m_device.controlTransfer(
		direction | recipient | type,
		code,
		value,
		index,
		buffer,
		size,
		-1,
		onTransferCompleted~(
			m_syncId,
			type,
			code,
			value,
			index,
			buffer
		) @ m_pluginHost.m_mainThreadScheduler
	);
}

void UsbControlEndpointSession.onTransferCompleted(
	uint_t syncId,
	uint_t type,
	uint_t code,
	uint_t value,
	uint_t index,
	void const* p,
	size_t size,
	std.Error const* error
) {
	if (syncId != m_syncId)
		return;

	ControlTransferParams params;
	params.m_type = type;
	params.m_code = code;
	params.m_value = value;
	params.m_index = index;

	m_logWriter.write(UsbLogRecordCode.ControlTransfer, &params, sizeof(params));

	if (error)
		m_logWriter.write(log.StdRecordCode.Error, error);
	else
		m_logWriter.write((type & 0x80) ? log.StdRecordCode.Rx : log.StdRecordCode.Tx, p, size);
}

bool errorcode UsbControlEndpointSession.dispatch(
	uint_t code,
	variant in,
	variant* out
) {
	switch (code) {
	case UsbControlEndpointDispatchCode.GetDeviceVid:
		io.UsbDevice const* device = m_deviceCombo.m_currentData;
		*out = device ? device.m_deviceDesc.m_vendorId : -1;
		break;

	case UsbControlEndpointDispatchCode.GetDevicePid:
		io.UsbDevice const* device = m_deviceCombo.m_currentData;
		*out = device ? device.m_deviceDesc.m_productId : -1;
		break;

	case UsbControlEndpointDispatchCode.SetDeviceVidPid:
		uint_t vidPid = (uint_t)in;
		size_t i = ui.findUsbDeviceComboIndex(m_deviceCombo, vidPid >> 16, vidPid & 0xffff);
		if (i != -1)
			m_deviceCombo.m_currentIndex = i;
		break;

	case UsbControlEndpointDispatchCode.GetInterfaceId:
		io.UsbInterfaceDesc const* ifaceDesc = m_interfaceCombo.m_currentData;
		*out = ifaceDesc ? ifaceDesc.m_interfaceId : -1;
		break;

	case UsbControlEndpointDispatchCode.SetInterfaceId:
		size_t i = ui.findUsbInterfaceComboIndex(m_interfaceCombo, (uint_t)in);
		if (i != -1)
			m_interfaceCombo.m_currentIndex = i;
		break;

	case UsbControlEndpointDispatchCode.GetReqDirection:
		*out = (UsbControlReqDirection)m_reqDirectionCombo.m_currentData;
		break;

	case UsbControlEndpointDispatchCode.SetReqDirection:
		m_reqDirectionCombo.m_currentData = (UsbControlReqDirection)in;
		break;

	case UsbControlEndpointDispatchCode.GetReqRecipient:
		*out = (UsbControlReqRecipient)m_reqRecipientCombo.m_currentData;
		break;

	case UsbControlEndpointDispatchCode.SetReqRecipient:
		m_reqRecipientCombo.m_currentData = (UsbControlReqRecipient)in;
		break;

	case UsbControlEndpointDispatchCode.GetReqType:
		*out = (UsbControlReqType)m_reqTypeCombo.m_currentData;
		break;

	case UsbControlEndpointDispatchCode.SetReqType:
		m_reqTypeCombo.m_currentData = (UsbControlReqType)in;
		break;

	case UsbControlEndpointDispatchCode.GetReqCode:
		*out = parseInteger(m_reqCodeCombo.m_editText);
		break;

	case UsbControlEndpointDispatchCode.SetReqCode:
		m_reqCodeCombo.m_editText = $"0x%02x"((uint_t)in);
		break;

	case UsbControlEndpointDispatchCode.GetReqValue:
		*out = parseInteger(m_reqValueCombo.m_editText);
		break;

	case UsbControlEndpointDispatchCode.SetReqValue:
		m_reqValueCombo.m_editText = $"0x%04x"((uint_t)in);
		break;

	case UsbControlEndpointDispatchCode.GetReqIndex:
		*out = parseInteger(m_reqIndexCombo.m_editText);
		break;

	case UsbControlEndpointDispatchCode.SetReqIndex:
		m_reqIndexCombo.m_editText = $"0x%04x"((uint_t)in);
		break;

	case UsbControlEndpointDispatchCode.OpenDevice:
		return openDevice();

	case UsbControlEndpointDispatchCode.OpenDeviceVidPid:
		uint_t vidPid = (uint_t)in;
		size_t i = ui.findUsbDeviceComboIndex(m_deviceCombo, vidPid >> 16, vidPid & 0xffff);
		if (i == -1) {
			std.setError("device not found");
			return false;
		}

		m_deviceCombo.m_currentIndex = i;
		return openDevice();

	case UsbControlEndpointDispatchCode.CloseDevice:
		closeDevice();
		break;

	case UsbControlEndpointDispatchCode.ClaimInterface:
		return claimInterface();

	case UsbControlEndpointDispatchCode.ClaimInterfaceId:
		size_t i = ui.findUsbInterfaceComboIndex(m_interfaceCombo, (uint_t)in);
		if (i == -1) {
			std.setError("interface not found");
			return false;
		}

		m_interfaceCombo.m_currentIndex = i;
		return claimInterface();

	case UsbControlEndpointDispatchCode.ReleaseInterface:
		releaseInterface();
		break;

	case UsbControlEndpointDispatchCode.StartInTransfer:
		return startTransfer(
			0x80,
			m_reqRecipientCombo.m_currentData,
			m_reqTypeCombo.m_currentData,
			parseInteger(m_reqCodeCombo.m_editText),
			parseInteger(m_reqValueCombo.m_editText),
			parseInteger(m_reqIndexCombo.m_editText),
			null,
			(size_t)in
		);

	default:
		std.setError("unsupported dispatch code");
		return false;
	}

	return true;
}

reactor UsbControlEndpointSession.m_uiReactor {
	bindable bool isOpened = m_state >= State.Opened;
	bindable bool isClaimed = m_state >= State.Claimed;

	m_title = $"USB $(m_deviceCombo.m_currentText)";
	m_actionTable[ActionId.Open].m_text = isOpened ? "Close Device" : "Open Device";
	m_actionTable[ActionId.Open].m_icon = m_iconTable[isOpened];
	m_actionTable[ActionId.Claim].m_text = isClaimed ? "Release Interface" : "Claim Interface";
	m_actionTable[ActionId.Claim].m_icon = m_iconTable[isClaimed];
	m_actionTable[ActionId.Claim].m_isEnabled = isOpened;

	m_interfaceCombo.m_isEnabled = isOpened;
	m_interfaceProp.m_isEnabled = isOpened;

	m_isTransmitEnabled = isOpened;

	m_statusPaneTable[StatusPaneId.State].m_text =
		isClaimed ? "Claimed" :
		isOpened ? "Opened" : "Closed";
}

//..............................................................................
