Virtual channels are referred to by a seven-character (or shorter) ASCII name. In several previous versions of the ICA protocol, virtual channels were numbered; the numbers are now assigned dynamically based on the ASCII name, making implementation easier.
When developing virtual channel code for internal use only, you can use any seven-character name that does not conflict with existing virtual channels. Use only upper and lowercase ASCII letters and numbers. Follow the existing naming convention when adding your own virtual channels.
The predefined channels, which begin with the OEM identifier CTX, are for use only by Citrix.
Follow these suggestions to make your virtual channels easier to design and enhance:
- When you design your own virtual channel protocol, allow for the flexibility to add features. Virtual channels have version numbers that are exchanged during initialization so that both the client and the server detect the maximum level of functionality that can be used.
For example, if the client is at Version 3 and the server is at Version 5, the server does not send any packets with functionality beyond Version 3 because the client does not know how to interpret the newer packets.
- Because the server side of a virtual channel protocol can be implemented as a separate process, it is easier to write code that interfaces with the Citrix-provided virtual channel support on the server than on the client (where the code must fit into an existing code structure). The server side of a virtual channel simply opens the channel, reads from and writes to it, and closes it when done. Writing code for the server-side is similar to writing an application, which uses services exported by the system. It is easier to write an application to handle the virtual channel communication because it can then be run once for each ICA connection supporting the virtual channel.
Writing for the client-side is similar to writing a driver, which must provide services to the system in addition to using system services. If a service is written, it must manage multiple connections.
- If you are designing new hardware for use with new virtual channels (for example, an improved compressed video format), make sure the hardware can be detected so that the client can determine whether or not it is installed.
Then the client can communicate to the server if the hardware is available before the server uses the new data format. Optionally, you could have the virtual driver translate the new data format for use with older hardware.
There might be limitations preventing your new virtual channel from performing at an optimum level. If the client is connecting to the server running Citrix Virtual Apps and Desktops through a modem or serial connection, the bandwidth might not be great enough to properly support audio or video data. You can make your protocol adaptive, so that as bandwidth decreases, performance degrades gracefully, possibly by sending sound normally but reducing the frame rate of the video to fit the available bandwidth.
To identify where problems are occurring (connection, implementation, or protocol), first get the connection and communication working. Then, after the virtual channel is complete and debugged, do some time trials and record the results.These results establish a baseline for measuring further optimizations such as compression and other enhancements so that the channel requires less bandwidth.
The time stamp in the pVdPoll variable can be helpful for resolving timing issues in your virtual driver. It is a ULONG containing the current time in milliseconds. The pVdPoll variable is a pointer to a DLLPOLL structure. See Dllapi.h (in src/inc/) for definitions of these structures.
Server-side functions overview
Server-side functions are entry points to virtual channel services provided by the ICAsubsystem on the Citrix Virtual Apps and Desktops server. Wfapi.h contains constants and function prototypes.
Use these functions to open and close virtual channels and to read, write, query, and purge incoming or outgoing data.
The words IN and OUT in the function calling conventions are for clarification only. They are defined as blank in Windef.h. If you do not have access to Windef.h, add the following to a header file for your project:
# ifndef IN # define IN # endif # ifndef OUT # endif <!--NeedCopy-->
|WFVirtualChannelClose||Closes an open virtual channel handle.|
|WFVirtualChannelOpen||Opens a handle to a specific virtual channel.|
|WFVirtualChannelPurgeInput||Purges all queued input data sent from the client to the server on a specific virtual channel.|
|WFVirtualChannelPurgeOutput||Purges all queued output data sent from the server to the client on a specific virtual channel.|
|WFVirtualChannelQuery||Returns data related to a virtual channel.|
|WFVirtualChannelRead||Reads data from a virtual channel.|
|WFVirtualChannelWrite||Writes data to a virtual channel.|
Client-side functions overview
The client software is built on a modular configurable architecture that allows replaceable, configurable modules (such as virtual channel drivers) to handle various aspects of an ICA connection. These modules are specially formatted and dynamically loadable. To accomplish this modular capability, each module (including virtual channel drivers) implements a fixed set of function entry points.
There are three groups of functions: user-defined, virtual driver helper, and memory INI.
To make writing virtual channels easier, dynamic loading is handled by the WinStation driver, which in turn calls user-defined functions. This simplifies creating the virtual channel because all you have to do is fill in the functions and link your virtual channel driver with Vdapi.lib (provided with this SDK).
|DriverClose||Frees private driver data. Called before unloading a virtual driver (generally upon client exit).|
|DriverGetLastError||Returns the last error set by the virtual driver. Not used; links with the common front end, VDAPI.|
|DriverInfo||Retrieves information about the virtual driver.|
|DriverOpen||Performs all initialization for the virtual driver. Called once when the client loads the virtual driver (at startup).|
|DriverPoll||Allows driver to check timers and other state information, sends queued data to the server, and performs any other required processing. Called periodically to see if the virtual driver has any data to write.|
|DriverQueryInformation||Retrieves run-time information from the virtual driver.|
|DriverSetInformation||Sets run-time information in the virtual driver.|
|ICADataArrival||Indicates that data was delivered. Called when data arrives on the virtual channel.|
The virtual driver uses helper functions to send data and manage the virtual channel. When the WinStation driver initializes the virtual driver, the WinStation driver passes pointers to the helper functions and the virtual driver passes pointers to the user-defined functions.
VdCallWd is linked in as part of VDAPI and is available in all user-implemented functions. The others are obtained during DriverOpen when VdCallWd is called with the WDxSETINFORMATION parameter.
Virtual channel drivers can send data from private buffers via the SendData or QueueVirtualWrite functions obtained during DriverOpen. Either of these functions may decline to accept the data if the WinSation Driver itself cannot buffer it. The channel will then need to retry the send operation on the next DriverPoll.
|SendData||To send a packet of channel protocol to the server, with a notification option.|
|QueueVirtualWrite||To send a packet of channel protocol to the server. This is a legacy function. Use SendData above for new virtual drivers.|
|VdCallWd||Used to query and set information from the WinStation driver (WD).|
These functions represent a new set of APIs for reading and writing data to and from a virtual channel. They simplify this process and should serve as an adequate replacement for the functions described in the previous section.
|VdOpen||Retrieve channel number for virtual channel with the given name.|
|VdRegisterIoCallback||Registers an IO callback which is used to receive data for the virtual channel driver.|
|VdUnregisterIoCallback||Unregisters an IO callback and releases the associated IO context.|
|VdWriteData||Write data to the virtual channel.|
|VdServiceIo||Sends any pending outgoing data that has been queued for the virtual channel.|
These functions enable virtual drivers to get information about the viewport for Citrix Workspace App. A viewport represents a local window in which a remote desktop session is displayed. This structure contains various information about a viewport, such as the viewport mode or type, the window handle, dimensions, etc.
There are two basic types of viewports: Windowed mode and seamless apps. In a windowed session, all application windows (and their contents) within the session are composited and rendered in a single window on the local endpoint device. Seamless applications get their own window/viewport on the local endpoint device.
There are three main display modes for a remote desktop session:
- Resolution matching. The display resolution in the session is made to match the display resolution on the local endpoint device. Scaling and panning are not necessary.
- Scale to fit. The graphics for the remote desktop session are scaled to account for the different display resolutions. Any overlays need to also be scaled.
- Actual size. The graphics for the remote desktop session are rendered in the remote desktop’s display resolution. Since this may not fit in the local endpoint viewport, scroll bars are activated and panning/scrolling offsets are maintained. Any overlays need to be repositioned according to these panning offsets.
|VdGetViewportInformation||Retrieve basic information about the local viewport window.|
|VdGetViewportScaleFactors||Retrieve the session scale factors from the viewport information.|
|VdRegisterViewportChangedCallback||Register for viewport change notifications.|
|VdUnregisterViewportChangedCallback||Unregister for viewport change notifications.|
|VdTranslateAppWindow||Find the local window handle that maps to the given remote/virtual window handle.|
|VdTranslateVirtualRect||Translate a rectangle from the VDA to local coordinates.|
These functions are used in conjunction with the app-sharing APIs that are part of the WFAPI SDK. Refer to the documentation for WFAPI SDK for more details. Applications can use those APIs to capture graphics contents for certain applications in a remote session. These functions provide a way to consume these graphics.
|VdAcquireGraphicsListener||Obtain a graphics listener for a given virtual channel.|
|VdReleaseGraphicsListener||Release a graphics listener and free any resources used by it.|
|VdAuthorizeGraphicsListener||Activates an existing graphics listener.|
|VdLockFrameBuffer||Lock a frame buffer in order to access image data.|
|VdUnlockFrameBuffer||Unlocks a buffer that was previously locked.|
Memory INI functions read data that the client engine reads from the Configuration Storage in the registry and stored in a memory INI structure. These functions must be used because some client devices store this information in ROM, and only the engine has access to this INI data. The Memory INI functions read values from this Memory INI structure as if the values are being read from the Configuration Storage. The Configuration Storage for specifying virtual channels is in Software\Citrix\ICA Client\Configuration\Advanced\Modules.
Important: Access to configuration data might be limited depending on security restrictions . In particular, the virtual channel might not have access to all contents of the ICA file. This is controlled by registry keys in HKEY_LOCAL_MACHINE\SOFTWARE\Citrix\ICA Client\Engine\Lock down Profiles\All Regions\Lock down. You can use Memory INI functions read data from the client engine configuration files stored in both the client installation directory for system wide settings and $HOME/.ICAClient for user specific settings.
For each entry in appsrv.ini and wfclient.ini, there must be a corresponding entry in All_Regions.ini for the setting to take effect. For more information, refer to All_Regions.ini file in the $ICAROOT/config directory.
|miGetPrivateProfileBool||Returns a boolean value.|
|miGetPrivateProfileInt||Returns an integer value.|
|miGetPrivateProfileLong||Returns a long value.|
|miGetPrivateProfileString||Returns a string value.|