CitrixBlogger.org ending soon

sunset2Just to let you know, the domain registration for Citrix Blogger expires February 5, 2017. Since I no longer work at Citrix, it is time to drop the current domain name. Have not decided yet whether to create a new name to act as an archive for blog posts.

Will decide what to do before time runs out.

Single Sign On with Linux VDA 1.1

Customers want things to be easier. A common request is to support Single Sign On (SSO).  Instead of having to enter credentials more than once, SSO remembers the username and password. The SSO design is secure yet provides a better user experience. SSO is a common feature for web browsers. Once logged in, the credentials are used for the site without requiring entering them again.

In Citrix XenDesktop/XenApp, the idea is very similar.  Once logged into Receiver, StoreFront, or Web Interface, the user does not need to enter passwords again for desktop sessions and applications.  Users only need to click on the icon of the desired desktop or application and the connection will be made without requiring credentials.  Even though many different hosts are involved, the hosts work together to allow the user entry.

However, this is not considered the ultimate SSO solution.  For users using Windows Receiver, there is a feature that uses the native Windows credentials from the user’s session.  If the user is logged into Active Directory on a Windows domain workstation, they will not be required to login to Citrix if SSO is properly configured.

Windows Receiver

The official title for the Citrix feature is “Pass Thru Authentication” and based on the latest feature chart, is only supported on Windows Receiver.  Setting it up is more involved than a typical installation.  Please review the knowledge article. There is a command line switch called “/includeSSON” when installing CitrixReceiver.exe.

sson

There are several other steps so please follow the knowledge article.

Once everything is working, users only need to sign into Windows in order to use Citrix XenApp and XenDesktop.

In Linux VDA version 1.0, we did not support this feature.  To understand why requires some basic understanding of how this technology works.  We will get to that in a moment, but it is important to announce that we have added this feature in Linux VDA version 1.1.  This version of the Linux VDA can use the user’s domain credentials which are made available by the machine that the user is logged into and is running Windows Receiver.

Inside Citrix, we call this support “WD Credentials”.  WD stands for Winstation Driver, a core module in the Citrix software stack. The concept is that the credentials are provided during the connection from Citrix Receiver. These credentials are provided very early in the HDX/ICA connection between Receiver and the host (Linux VDA). In the original model, the credential data was gathered from tickets in the ICA file. Specifically, the ticket was used to request the credentials from the Citrix DDC (broker).  This was a problem since it is not possible to get Windows Receiver SSO credentials this way.

HTML5 Receiver

Over time, “WD Credentials” has been used by other Receivers as well.  In fact, some Receivers only support providing credential information this way.  For example, the Citrix HTML5 Receiver passes the user’s credentials to the VDA when requested.  If the VDA does not request it (and uses the ICA file only) HTML5 Receiver will never be able to automatically sign in. In order to support Linux VDA 1.0, HTML5 Receiver 16.0 added support for the old credential transfer.  However, this still meant that the older HTML5 Receivers could not connect and login automatically.  With the change in Linux VDA 1.1, this is no longer a problem.

Smart card

Even though the Linux VDA now supports “WD Credentials” it does not yet support smart cards.  There is an additional work item to support the smart card virtual channel.

Linux VDA

One of the hidden benefits of SSO is Active Directory integration. The credentials work for Windows resources accessed from Linux. For example, SSO allows for automatic access to user home directories. Services based on Active Directory Kerberos respect that the user has been authorized and will not request credentials from the user.  This reduces the pain of connecting network shares during login.

Summary

Linux VDA 1.1 now supports Windows Receiver SSO and all Receivers that use “WD Credentials” to exchange credentials.  This includes HTML5 Receivers prior to version 16.0. Smart card support is not there yet. With the Linux VDA 1.1 release, true SSO with Windows is now possible and the Receivers are able to use the latest Citrix credential exchange technology.

To read more from the Linux Virtual Desktop Team, please refer to the Linux Virtual Desktop Team blog here.

Linux VDA 1.1 Internationalization (UTF-8)

In the history of computing, one aspect that had eluded capture is a consistent encoding scheme.  There have been many attempts to standardize a character encoding scheme, but each has had strong downsides until about 20 years ago.  The invention of UTF-8 in 1992 by Ken Thompson based on Unicode was the solution to the original problem of universal encoding. It provided the ability to represent all possible characters in the smallest space possible.  It also preserved compatibility with ASCII. Other solutions did not have full coverage or used too much storage.

Encoding Year Adopted Bytes/Char Description
ASCII 1964 1 Early standard. Made popular by PC
UCS-2 1993 2 First Unicode encoding
UTF-16 1996 2,4 UCS-2 model with more chars
UTF-32 1996 4 Simple, but uses much memory
UTF-8 1998 1-4 Most efficient and compatible

Based on the timing of how things happened, Windows adopted UCS-2 first with Windows NT in 1993. This was seen as an improvement over the traditional single byte character sets of DOS and Windows 3.x. Unfortunately, this added complexity to Windows development and support due to the dual model of Unicode and non-Unicode support. It led to the classic doubling of Windows APIs. One version of the API appended with ‘W’ (Wide/Unicode) and the other ‘A’ (ASCII). A brief explanation can be found here.

Use of A and W in Windows

Citrix XenApp and XenDesktop are built on Windows models. Even Citrix Linux VDA has its roots in the Windows encoding schemes. Overall this makes sense because it is the history of what happened and it made sense to share code between platforms.

UTF8

This philosophy has changed for Linux VDA version 1.1. Instead of trying to preserve the Microsoft encoding schemes, everything is now converted internally to UTF-8. Initially this might not seem to have much relevance to customers. However, this provides some immediate benefits and also some longer term improvements.

Because UTF-8 is now the core encoding in Linux VDA, it no longer has to convert strings internally. This improves performance slightly and also reduces the risk of losing something in translation. It also reduces the footprint of how much space the strings need.

Another benefit is that it allows for a native encoding on Linux. Messages coming from administrators are now allowed to be displayed using full Unicode support. Even though the message arrives in UTF-16 from Studio, the message is converted to UTF-8 and displayed using GTK+.

Yet another reason to use UTF-8 is that it is now possible to support full Unicode text transfers with the clipboard. Again, even though the clipboard is receiving UTF-16 text, it is automatically translated to UTF-8 for the sake of the Linux applications. This is very important for Asian languages that typically have large character sets.

UTF8Clipboard

A side benefit is improved username handling. It is now possible to support usernames that include non-ASCII characters. This was tried as part of the Linux VDA 1.1 tests. The username in that case had characters above the BMP range (>64K) which is considered fairly rare but valid.

Beyond these changes, the logging and tracing components now use UTF-8. This allows for full character set usage for log messages and trace output. There is still more work needed to localize the log messages to non-English languages but at least it is enabled and will display UTF-8 content.

Internally it simplified the code in many places. This will allow for more consistent handling of the strings and less trouble with conversion.

As to the future, it provides a base for Linux VDA to better support any language. It also allows for the possibility of having a server that supports different languages at once with different users. The overall biggest potential is full integration with Linux related to text.

To read more from the Linux Virtual Desktop Team, please refer to the Linux Virtual Desktop Team blog here.

Linux and Citrix

To start with, this is not an official statement from Citrix.  Rather, this is a collection of observations over the length of time at Citrix.

DSCN1637

Citrix Linux sunrise

First thing to note is that in the history of Citrix, the company had respect for Unix/Linux, and the first product in 1991 was effectively an OS/2 version of supporting terminals.  This model matched the classic Unix terminal framework.  The vision was that Citrix would provide a solution with Microsoft-based applications to terminals.  A lofty goal for 1989 when it was first formed.

However, there was a dark cloud present.  Any consideration between 1989 and the last few years to use Linux in a server product was seen as conflict of interest related to the relationship with Microsoft.

In 1998, Citrix acquired Insignia.  This was key to releasing clients that supported Mac, Linux, and Unix.  The dividends for this continue to this day.  This Linux Receiver is seen as one of the more important receivers and is still updated with relevant features.

Also in 1998, I worked as a system engineer for a reseller in Brisbane.  The experience revealed plenty of direct customer exposure and the joys of supporting software that you cannot personally fix.  Anyways, some of the customers were using Linux.  In general, there were two camps.  The government camp wanted to use Linux because it was cheap and they had the talent to support it (or so they thought).  The second camp saw it as a powerful tool for engineers and designers.  There was a bit of mistrust from the government group with any outside opinions.  In my case, I was only supporting a trial of WinFrame for a much larger environment.  Amazingly, they were using Samba with Linux as the backbone of their Windows desktops.  Perhaps you can see the fun in that in the late 90’s.

The second group of people using Linux for workstations for engineers was much more interesting.  Citrix had nothing for that at the time (as a server).  As explained by the customer, they wanted a Linux server that could remote applications to users.  It would not be until 2015, that this would be possible with a Citrix product.

For 1999, the MetaFrame for Unix product started development.  The focus was to create a Unix-based server for Citrix.  The release gained some momentum but avoided Linux.  Even though not declared, the reason was probably to avoid upsetting Microsoft.  In 2014, the core MFU/XAU codebase would form the early basis for the current Citrix Linux VDA.

In 2004, Citrix pursued SSL VPN companies to provide its first VPN solution.  As part of this process, Net6 was acquired.  The secret was that the Net6 appliance was actually running open source Linux.  There were initially concerns about Microsoft’s reaction.  However, the concerns were unfounded due the appliance not being the same model as Microsoft’s client/server environment.

An important 2005 acquisition was Netscaler.  It too was based on a Linux appliance.

In 2007, Citrix acquired XenSource.  The core of Xen virtualizations is Linux.  This introduced the concept of Linux being an acceptable solution for virtualization.

In 2014, Ericsson approached Citrix to produce what was later called Linux VDA.  The Citrix Labs team accepted the task and released the first version in June 2015.  The model follows the design of Windows VDA, but customized to Linux hosts.  Citrix receivers connect to Linux VDA the same way that they connect to Windows VDA.  Given that the receiver is a recent one, it is designed to connect to either server without special treatment.

And to wrap things up, today Microsoft announced that they have developed a Linux network appliance for Azure.  The age of upsetting Microsoft about using Linux is truly over.

Lights, Camera, Action! MobilePicture using the SDK

MobilePictureSample

Taking pictures with mobile devices is common .  The quality of the pictures might not be as good and it might not be always as easy to zoom,  control the flash,  or properly focus, but it does its job well enough in most situations.  The biggest advantage is that you always have your phone so you can capture images that normally would be missed.

When we started working on the Mobile SDK for Windows Apps in 2011, one of the most interesting use cases was being able to take pictures on the mobile device and then using these pictures with the Windows app.  From a customer point of view, this is one of the most compelling reasons to implement an application using the SDK.

What makes this different from usual?  Why would you do it this way?

The answer requires a bit of background information.  The biggest difference is where the picture is first processed.  Instead of being controlled by a local application on the device, the picture is provided to the remote application first.  Since the application is running on XenDesktop/XenApp, it uses the SDK to trigger the picture being taken and collecting the picture when it is done.  This makes it possible to inject a picture directly into a Windows app from a mobile device (phone or tablet).

A number of use cases came to mind early on including maintenance and insurance.  It is possible to not only instantly take pictures at remote locations but to also have these pictures be included in the corporate databases in a very quick way.  The most famous use of this model is from Warren Simondson from Ctrl-Alt-Del.  In this case, the camera is used make sure that miners are where they say they are.

Several months ago, I started working on a project to share on the blog.  This work was meant to extend what was done for MobileHelloWorld. Even though I had made great progress, I never quite got it to the state where I wanted to release it to the world.  The intent was to make it all about pictures and taking new ones.  It uses the SDK more than MobileHelloWorld and has a number of useful features.

  • Enumerates all pictures under My Pictures and puts them in a list for display
  • Navigates backwards and forwards using arrow buttons
  • Rotates picture based on user using rotate buttons
  • Slideshow mode by holding forward or backward buttons for a few seconds
  • Touch the picture will hide/show the controls using a toggle
  • Pictures with Apple orientation are presented correctly
  • Changes to rotation can be saved
  • Pictures are shown as true size if they fit.  If too large, they are zoomed down to the largest fitting size.
  • Label for photo at top right displays name, number, and dimensions
  • New pictures can be taken with the camera button
  • Device rotation will trigger a replacement of controls
  • Control placement is based on screen relative position (top, middle, bottom, left, middle, bottom)
  • Control size is adjusted based on the device type (phone, tablet, PC)
  • The app remembers the last displayed photo and uses it for the next time
  • Icons come from the Visual Studio resource kit and are safe to distribute
  • Delete pictures that are not wanted

My favorite features are the slideshow and being able to capture new pictures.  I also appreciate the ability to correctly display pictures from iOS devices.  Without the code to detect true orientation, it can be quite frustrating.

Instead of dragging you through all the code like I did for HelloWorld, I will instead offer the source code first.

download

There is a lot of potential for using this program as a template for something much more interesting.  This code works fine with the emulator so it is possible to simulate on a Windows 7/8 dev machine with the SDK.  In fact, that is the best place to start since it can be difficult getting a dev server going.

To recap, the program can display pictures from your My Pictures folder and add new ones using the mobile device to capture them.

Mobile WinForms Hello World

In the last post, it discussed how to create a simple Hello World example using WinForms.  The example displays the words “Hello World” in the client area of the window and it calculates how big to make the text based on the area available.  The code is not using any Citrix API extensions to make it run properly on a mobile device.

HelloWorld

There a few things that need to change to get it to be mobile friendly.  Here is the list of things that need to be added:

  1. Reference Mobile SDK for Windows Apps Citrix.CMP.dll assembly
  2. Border style changed to none
  3. Add “Using Citrix.Cmp;” at top
  4. Initialize the SDK object and hook certain events
  5. Add conditional code for mobile and non-mobile paths
  6. Whenever the display changes size, be sure to update the text

There are a few more details to understand but these are the main ideas of what needs to change.

The modified code is included here inline to compare against the previous post.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Citrix.Cmp;
using System.Diagnostics;

namespace MobileHelloWorld
{
    /// <summary>
    /// The MobileHelloWorld form demonstrates how to display "Hello World" with the appropriate font based on the size of the client area.
    /// This work was derived from HelloWorld and extended to use the Mobile SDK for Windows Apps
    /// </summary>
    public partial class MobileHelloWorld : Form
    {
        private CmpApi cmpApi = null;
        private bool mobileDevice = false;

        /// <summary>
        /// Main constructor for MobileHelloWorld Form
        /// </summary>
        public MobileHelloWorld()
        {
            // initialize the designer standard components
            InitializeComponent();

            // initialize the Mobile SDK so we can use it
            InitializeMobileSDK();

            // change how the form looks based on what kind of device it is
            if (IsMobileDevice())
            {
                // do not use the resizable border on mobile devices
                FormBorderStyle = FormBorderStyle.None;
            }
            else
            {
                // make sure that this window is maximized on non-mobile devices
                WindowState = FormWindowState.Maximized;
            }
        }

        /// <summary>
        /// Initialize the Mobile SDK API object
        /// </summary>
        private void InitializeMobileSDK()
        {
            try
            {
                // create an instance of our API (referenced from Citrix.Cmp.dll assembly)
                cmpApi = new CmpApi();

                // hook the revelant events to support our app

                // viewport is the area we can use for our application
                cmpApi.Display.ViewportInfoChanged += new EventHandler<ViewportInfoChangedArgs>(Display_ViewportInfoChanged);

                // request notification for when we are connected and disconnected to properly handle the mobile device
                cmpApi.SessionManager.SessionConnected += new EventHandler<SessionConnectedArgs>(SessionManager_SessionConnected);
                cmpApi.SessionManager.SessionDisconnected += new EventHandler(SessionManager_SessionDisconnected);

                // the mobile device is only connected whe IsCmpAvailable is true
                if (cmpApi.SessionManager.IsCmpAvailable)
                {
                    mobileDevice = true;
                }
            }
            catch
            {
                // we did not get a working instance of CmpApi.
                // fail gracefully to allow it to work fine on non-mobile devices
            }
        }

        /// <summary>
        /// Session disconnected event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void SessionManager_SessionDisconnected(object sender, EventArgs e)
        {
            // the mobile device is now no longer connected
            mobileDevice = false;
        }

        /// <summary>
        /// Notified when session is connected
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void SessionManager_SessionConnected(object sender, SessionConnectedArgs e)
        {
            // if the mobile device has returned, then allow mobile device calls again
            if (cmpApi.SessionManager.IsCmpAvailable)
            {
                mobileDevice = true;
            }

            // resize the form to match the current connected device
            ResizeForm();
        }

        /// <summary>
        /// Viewport changed event - we are interested in the ClientViewport and use it to resize the main form
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void Display_ViewportInfoChanged(object sender, ViewportInfoChangedArgs e)
        {
            ViewportInfo viewport = e.NewState;

            if ((viewport.ClientViewport.HasValue) && (viewport.ClientViewport.Value.Width != 0.0) && (viewport.ClientViewport.Value.Height != 0.0))
            {
                Size = new Size(Convert.ToInt32(viewport.ClientViewport.Value.Width), Convert.ToInt32(viewport.ClientViewport.Value.Height));

                Trace.WriteLine(String.Format("ViewportInfoChanged Rect ClientViewport {0}", viewport.ClientViewport.Value));
            }
            else
            {
                if (viewport.ClientViewport.HasValue)
                {
                    Trace.WriteLine(String.Format("ViewportInfoChanged Rect ClientViewport {0}", viewport.ClientViewport.Value));
                }
                else
                {
                    Trace.WriteLine("ViewportInfoChanged ClientViewport has no value");
                }
            }

            // if the server viewport has changed, make sure our app is at the origin
            if ((viewport.ServerViewport.HasValue) && ((viewport.ServerViewport.Value.X != 0) || (viewport.ServerViewport.Value.Y != 0)))
            {
                Location = new Point(Convert.ToInt32(viewport.ServerViewport.Value.X), Convert.ToInt32(viewport.ServerViewport.Value.Y));
            }
            else
            {
                Location = new Point();
            }
        }

        /// <summary>
        /// Get the current display size for the mobile device.
        /// There are three techniques to attempt in a certain order.
        /// 1. Viewport information
        /// 2. Display state
        /// 3. Actual screen width and height
        /// The viewport is best since it takes into account keyboard and other mobile device areas which are reserved.
        /// The display state is from receiver but does not take into account keyboard or other reserved areas.
        /// The last is the real screen size which comes from Windows.
        /// </summary>
        /// <returns>Size of the display area</returns>
        private Size GetDisplaySize()
        {
            // start with the current size as the default
            Size size = Size;

            if (IsMobileDevice())
            {
                try
                {
                    ViewportInfo viewport = cmpApi.Display.GetViewport();

                    // ViewportInfo.ClientViewport is a nullable value and that means that we have to use HasValue to check it
                    // Also, the iOS Receiver sometimes reports 0,0 we need to check for that too
                    if ((viewport.ClientViewport.HasValue) && (viewport.ClientViewport.Value.Width != 0) && (viewport.ClientViewport.Value.Height != 0))
                    {
                        size.Width = Convert.ToInt32(viewport.ClientViewport.Value.Width);
                        size.Height = Convert.ToInt32(viewport.ClientViewport.Value.Height);
                    }
                    else
                    {
                        // The display state resolution is a width, height measurement in pixels
                        DisplayState displayState = cmpApi.Display.GetDisplayState();

                        if (displayState.Resolution.HasValue)
                        {
                            // the display state is the next best place to go
                            size.Width = Convert.ToInt32(displayState.Resolution.Value.Height);
                            size.Height = Convert.ToInt32(displayState.Resolution.Value.Width);
                        }
                        else
                        {
                            // last resort is the screen width and height
                            size.Width = Convert.ToInt32(System.Windows.SystemParameters.PrimaryScreenWidth);
                            size.Height = Convert.ToInt32(System.Windows.SystemParameters.PrimaryScreenHeight);
                        }
                    }
                }
                catch
                {
                    // fail gracefully
                }
            }

            return (size);
        }

        /// <summary>
        /// Determines if mobile device is on the other side
        /// </summary>
        /// <returns>true - mobile device, false - non-mobile device</returns>
        private bool IsMobileDevice()
        {
            return (mobileDevice);
        }

        /// <summary>
        /// Called when the form is first loaded.  Resize the HelloWorldLabel to fit the new form size
        /// </summary>
        /// <param name="sender">Ignored</param>
        /// <param name="e">Ignored</param>
        private void HelloWorld_Load(object sender, EventArgs e)
        {
            // resize and relocate based on mobile device informtation
            ResizeForm();
        }

        /// <summary>
        /// Resize the Form if it is running on mobile device
        /// </summary>
        private void ResizeForm()
        {
            if (IsMobileDevice())
            {
                // do not use the resizable border on mobile devices
                FormBorderStyle = FormBorderStyle.None;

                // Location is always (0,0)
                Location = new Point();

                // Size is calculated from the Mobile SDK information
                Size = GetDisplaySize();
            }
            else
            {
                WindowState = FormWindowState.Maximized;
            }
        }

        /// <summary>
        /// Called when the form is resized.  Resize the HelloWorldLabel to fit the new form size
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void HelloWorld_Resize(object sender, EventArgs e)
        {
            ResizeHelloWorld();
        }

        /// <summary>
        /// Resize the Hello World to fill the client area
        /// </summary>
        void ResizeHelloWorld()
        {
            // change the font to match the size of the client area
            HelloWorldLabel.Font = GetBestFontFit(ClientSize, HelloWorldLabel.Font, HelloWorldLabel.Text, ClientSize.Height, 8f); ;

            // center the label
            CenterHelloWorld();
        }

        /// <summary>
        /// Center the HelloWorldLabel in the client area of MobileHelloWorld form
        /// </summary>
        void CenterHelloWorld()
        {
            HelloWorldLabel.Location = new Point((ClientSize.Width / 2) - (HelloWorldLabel.Width / 2), (ClientSize.Height / 2) - (HelloWorldLabel.Height / 2));
        }

        /// <summary>
        /// Determine the best font match for the size/text.  The result should be between the maximum and minimum height.
        /// The size is the area to fill.  The new font is based on the old font which is passed in.
        /// </summary>
        /// <returns>Font</returns>
        public static Font GetBestFontFit(Size size, Font currFont, String text, float fontMax, float fontMin)
        {
            float fontPixels = fontMax;
            float minfontPixels = fontMin;

            // find the corresponding font size for the textbox size
            Font font = currFont;

            // make sure that the text is not null or empty before attempting to fit it in the area.
            // if it is null or empty, the current font is used since it does not matter.
            if (!String.IsNullOrEmpty(text))
            {
                Size textsize;

                font = new Font(currFont.FontFamily, fontPixels, currFont.Style, GraphicsUnit.Pixel);

                textsize = TextRenderer.MeasureText(text, font);

                // Check to see if the font fits in the area and if not, find a smaller one
                while ((((textsize.Height) > size.Height) || ((textsize.Width) > size.Width)) && (fontPixels > minfontPixels))
                {
                    // try to speed up the process of finding the right font size by detecting how much it is too big by
                    if (textsize.Height > size.Height)
                    {
                        // reduce the fontPixels based on how far off the font size is
                        fontPixels -= (textsize.Height - size.Height);
                    }
                    else if (textsize.Width > size.Width)
                    {
                        int charPixelWidth = textsize.Width / text.Length;
                        int targetPixelWidth = size.Width / text.Length;

                        // if the characters are too wide, reduce the fontPixels by how different the result is from the desired width
                        if (charPixelWidth > targetPixelWidth)
                        {
                            fontPixels -= (charPixelWidth - targetPixelWidth);
                        }
                        else
                        {
                            fontPixels -= 1.0F;
                        }
                    }
                    else
                    {
                        fontPixels -= 1.0F;
                    }

                    // drop the old one since it did not work
                    font.Dispose();

                    // get a new font based on the smaller font pixels size
                    font = new Font(font.FontFamily, fontPixels, font.Style, GraphicsUnit.Pixel);

                    // recalculate the size based on the new font
                    textsize = TextRenderer.MeasureText(text, font);
                }
            }

            // when all done, return the font to use
            return (font);
        }

    }
}

MobileHelloWorld

The picture above is the result of running the code against the emulator with the iPhone 5 selected. Note that there is no border and that the size matches an iPhone 5 screen.

Now is a good time to break up what the differences are. There are many new lines but these changes are not conceptually very big.

using Citrix.Cmp;

Including a ‘Using’ line just makes it easier to use routines from Citrix.Cmp namespace. It is not required but a “nice to have”.

    /// <summary>
    /// The MobileHelloWorld form demonstrates how to display "Hello World" with the appropriate font based on the size of the client area.
    /// This work was derived from HelloWorld and extended to use the Mobile SDK for Windows Apps
    /// </summary>
    public partial class MobileHelloWorld : Form
    {
        private CmpApi cmpApi = null;
        private bool mobileDevice = false;

        /// <summary>
        /// Main constructor for MobileHelloWorld Form
        /// </summary>
        public MobileHelloWorld()
        {
            // initialize the designer standard components
            InitializeComponent();

            // initialize the Mobile SDK so we can use it
            InitializeMobileSDK();

            // change how the form looks based on what kind of device it is
            if (IsMobileDevice())
            {
                // do not use the resizable border on mobile devices
                FormBorderStyle = FormBorderStyle.None;
            }
            else
            {
                // make sure that this window is maximized on non-mobile devices
                WindowState = FormWindowState.Maximized;
            }
        }

The initialization code for the MobileHelloWorld code has been extended to call InitializeMobileSDK and to change the style of the form based on whether or not the device is mobile. The InitializeMobileSDK function is displayed later in this post and is responsible for creating an instance of the SDK object and hooking events. The MobileHelloWorld form is only created once and this related code is only run once. You only need to create the object for the SDK once so this is a good match. Note also that IsMobileDevice is used to decide between maximizing (non-mobile) and getting rid of the border (mobile).

        /// <summary>
        /// Initialize the Mobile SDK API object
        /// </summary>
        private void InitializeMobileSDK()
        {
            try
            {
                // create an instance of our API (referenced from Citrix.Cmp.dll assembly)
                cmpApi = new CmpApi();

                // hook the revelant events to support our app

                // viewport is the area we can use for our application
                cmpApi.Display.ViewportInfoChanged += new EventHandler<ViewportInfoChangedArgs>(Display_ViewportInfoChanged);

                // request notification for when we are connected and disconnected to properly handle the mobile device
                cmpApi.SessionManager.SessionConnected += new EventHandler<SessionConnectedArgs>(SessionManager_SessionConnected);
                cmpApi.SessionManager.SessionDisconnected += new EventHandler(SessionManager_SessionDisconnected);

                // the mobile device is only connected whe IsCmpAvailable is true
                if (cmpApi.SessionManager.IsCmpAvailable)
                {
                    mobileDevice = true;
                }
            }
            catch
            {
                // we did not get a working instance of CmpApi.
                // fail gracefully to allow it to work fine on non-mobile devices
            }
        }

This is InitializeMobileSDK(). The CmpApi object is used for everything in the SDK. In this code, an CmpApi object is created and stored in a cmpApi variable in the form class. Three event handlers are added to the object to be notified of viewport changes and session connect/disconnect. The viewport changes are important for making sure the application gets notified of viewport size changes (usually from screen keyboard or orientation change). The session connect/disconnect events help the application match the state of the device to the application. Beyond creating the CmpApi object, the function also makes sure that a mobile device is actually there. If it is, it then sets the mobileDevice flag which is used with IsMobileDevice function. If anything fails during this function call with an exception, the try/catch section will prevent the exception from being passed up. This is done to make the application function without exceptions if not connected to a mobile device.

        /// <summary>
        /// Session disconnected event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void SessionManager_SessionDisconnected(object sender, EventArgs e)
        {
            // the mobile device is now no longer connected
            mobileDevice = false;
        }

        /// <summary>
        /// Notified when session is connected
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void SessionManager_SessionConnected(object sender, SessionConnectedArgs e)
        {
            // if the mobile device has returned, then allow mobile device calls again
            if (cmpApi.SessionManager.IsCmpAvailable)
            {
                mobileDevice = true;
            }

            // resize the form to match the current connected device
            ResizeForm();
        }

Session connect/disconnect events correspond to when the session is first connected to the device and when the session is disconnected. The disconnect event is only needed to know that the mobile device is no longer connected. The connect event is used to check for a mobile device and to resize the form to match the screen size.

        /// <summary>
        /// Viewport changed event - we are interested in the ClientViewport and use it to resize the main form
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void Display_ViewportInfoChanged(object sender, ViewportInfoChangedArgs e)
        {
            ViewportInfo viewport = e.NewState;

            if ((viewport.ClientViewport.HasValue) && (viewport.ClientViewport.Value.Width != 0.0) && (viewport.ClientViewport.Value.Height != 0.0))
            {
                Size = new Size(Convert.ToInt32(viewport.ClientViewport.Value.Width), Convert.ToInt32(viewport.ClientViewport.Value.Height));

                Trace.WriteLine(String.Format("ViewportInfoChanged Rect ClientViewport {0}", viewport.ClientViewport.Value));
            }
            else
            {
                if (viewport.ClientViewport.HasValue)
                {
                    Trace.WriteLine(String.Format("ViewportInfoChanged Rect ClientViewport {0}", viewport.ClientViewport.Value));
                }
                else
                {
                    Trace.WriteLine("ViewportInfoChanged ClientViewport has no value");
                }
            }

            // if the server viewport has changed, make sure our app is at the origin
            if ((viewport.ServerViewport.HasValue) && ((viewport.ServerViewport.Value.X != 0) || (viewport.ServerViewport.Value.Y != 0)))
            {
                Location = new Point(Convert.ToInt32(viewport.ServerViewport.Value.X), Convert.ToInt32(viewport.ServerViewport.Value.Y));
            }
            else
            {
                Location = new Point();
            }
        }

The viewport event is very important for knowing the correct form size. The event has both client and server viewport information. The client viewport reveals the width and height of the area on the mobile device that can be used. The server viewport reveals what section of the server display is currently visible. Usually the two viewports match up. However, there are times when they do not. The most obvious cases are when the screen keyboard is displayed or when zooming is used. The event handler above is using tracing since viewport events can be a bit tricky to understand. I added the trace to understand why it was originally not doing what I wanted when using a keyboard. During the development of the SDK, it has been very helpful to understand the various events and what the values can be in different states. The event handler is using the client viewport to resize the form. It also uses the server viewport to reposition the form. Between these two sources of information, the form is correctly sized and positioned.

        /// <summary>
        /// Get the current display size for the mobile device.
        /// There are three techniques to attempt in a certain order.
        /// 1. Viewport information
        /// 2. Display state
        /// 3. Actual screen width and height
        /// The viewport is best since it takes into account keyboard and other mobile device areas which are reserved.
        /// The display state is from receiver but does not take into account keyboard or other reserved areas.
        /// The last is the real screen size which comes from Windows.
        /// </summary>
        /// <returns>Size of the display area</returns>
        private Size GetDisplaySize()
        {
            // start with the current size as the default
            Size size = Size;

            if (IsMobileDevice())
            {
                try
                {
                    ViewportInfo viewport = cmpApi.Display.GetViewport();

                    // ViewportInfo.ClientViewport is a nullable value and that means that we have to use HasValue to check it
                    // Also, the iOS Receiver sometimes reports 0,0 we need to check for that too
                    if ((viewport.ClientViewport.HasValue) && (viewport.ClientViewport.Value.Width != 0) && (viewport.ClientViewport.Value.Height != 0))
                    {
                        size.Width = Convert.ToInt32(viewport.ClientViewport.Value.Width);
                        size.Height = Convert.ToInt32(viewport.ClientViewport.Value.Height);
                    }
                    else
                    {
                        // The display state resolution is a width, height measurement in pixels
                        DisplayState displayState = cmpApi.Display.GetDisplayState();

                        if (displayState.Resolution.HasValue)
                        {
                            // the display state is the next best place to go
                            size.Width = Convert.ToInt32(displayState.Resolution.Value.Height);
                            size.Height = Convert.ToInt32(displayState.Resolution.Value.Width);
                        }
                        else
                        {
                            // last resort is the screen width and height
                            size.Width = Convert.ToInt32(System.Windows.SystemParameters.PrimaryScreenWidth);
                            size.Height = Convert.ToInt32(System.Windows.SystemParameters.PrimaryScreenHeight);
                        }
                    }
                }
                catch
                {
                    // fail gracefully
                }
            }

            return (size);
        }

Being able to get the correct display size is very important to the application. In order to make this more fool-proof, the GetDisplaySize function uses three different techniques to get the information. In priority order, the different sources are used to find the right match. Having a fall-back makes sure that it will always get the best answer in different environments. It is also implemented to gracefully fail and not change the size if all these techniques fail. The main source will always be coming from viewport client viewport.

        /// <summary>
        /// Determines if mobile device is on the other side
        /// </summary>
        /// <returns>true - mobile device, false - non-mobile device</returns>
        private bool IsMobileDevice()
        {
            return (mobileDevice);
        }

Instead of querying the mobile device all the time, the status of the mobile device connection is kept in a boolean that is used in a function called IsMobileDevice.

        /// <summary>
        /// Called when the form is first loaded.  Resize the HelloWorldLabel to fit the new form size
        /// </summary>
        /// <param name="sender">Ignored</param>
        /// <param name="e">Ignored</param>
        private void HelloWorld_Load(object sender, EventArgs e)
        {
            // resize and relocate based on mobile device informtation
            ResizeForm();
        }

        /// <summary>
        /// Resize the Form if it is running on mobile device
        /// </summary>
        private void ResizeForm()
        {
            if (IsMobileDevice())
            {
                // do not use the resizable border on mobile devices
                FormBorderStyle = FormBorderStyle.None;

                // Location is always (0,0)
                Location = new Point();

                // Size is calculated from the Mobile SDK information
                Size = GetDisplaySize();
            }
            else
            {
                WindowState = FormWindowState.Maximized;
            }
        }
        /// <summary>
        /// Called when the form is resized.  Resize the HelloWorldLabel to fit the new form size
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void HelloWorld_Resize(object sender, EventArgs e)
        {
            ResizeHelloWorld();
        }

        /// <summary>
        /// Resize the Hello World to fill the client area
        /// </summary>
        void ResizeHelloWorld()
        {
            // change the font to match the size of the client area
            HelloWorldLabel.Font = GetBestFontFit(ClientSize, HelloWorldLabel.Font, HelloWorldLabel.Text, ClientSize.Height, 8f);

            // center the label
            CenterHelloWorld();
        }

Whenever the form is first loaded or when the session is reconnected, it calls ResizeForm. The ResizeForm then uses GetDisplaySize to resize the actual form size. When the form is resized, it triggers the form resize event when then resizes the text and centers it. The chain of events makes sure that a form resize will lead to a text resize as well. The code for resizing the text is the same as before.

There are things that might help to reduce the number of lines being used to make it a mobile app but I do think that it is more honest to show what the details are. The MobileHelloWorld.cs source file is 343 lines on my development machine. Also keep in mind that the infrastructure of using the SDK is lightweight due to using the object model and the fact that it does not require much to use the API. The original HelloWorld example was 138 lines. That means about 200 lines were added to support a mobile device. Also keep in mind that this is not lines of code but rather actual text file lines include brackets and blank lines. Also keep in mind that I wrote the two examples on the same day and only postponed this example till today since I needed to do a blog post for it. In other words, it took longer to do the post than it did to modify the HelloWorld program to support a mobile device display.

If you would a copy of the source for the entire project, it is available on ShareFile. This time I did not include the binary since it might be considered untrustworthy by virus scanners. If you have any questions, please let me know using the comments.

Simple WinForms Hello World Example

HelloWorld

Not long ago, a member of the team thought it would be a good idea to show the simplest program possible using the Mobile SDK for Windows Apps.  This was an excellent idea since “Hello World” is commonly used to introduce developers to new concepts.  The history of “Hello World” comes from the early 1970s with the C language and has prevailed even though things have changed so much since then.

The first step is to provide you with a really basic version of “Hello World” using WinForms.  Get the code from here.  It is a really small download at around 25K.  The download is a full Visual Studio 2010 project and even includes the bin directory with the HelloWorld.exe binary.  The binary is not signed so if you do not trust it you can build your own copy.

The code that drives this app is short so I will include in full here.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace HelloWorld
{
    /// <summary>
    /// The HelloWorld form demonstrates how to display "Hello World" with the appropriate font based on the size of the client area.
    /// Later, this will be transformed to support the Mobile SDK for Windows Apps.
    /// </summary>
    public partial class HelloWorld : Form
    {
        /// <summary>
        /// Main constructor for HelloWorld Form
        /// </summary>
        public HelloWorld()
        {
            // initialize the designer standard components
            InitializeComponent();

            // make sure that this window is maximized
            WindowState = FormWindowState.Maximized;
        }

        /// <summary>
        /// Called when the form is first loaded.  Resize the HelloWorldLabel to fit the new form size
        /// </summary>
        /// <param name="sender">Ignored</param>
        /// <param name="e">Ignored</param>
        private void HelloWorld_Load(object sender, EventArgs e)
        {
            ResizeHelloWorld();
        }

        /// <summary>
        /// Called when the form is resized.  Resize the HelloWorldLabel to fit the new form size
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void HelloWorld_Resize(object sender, EventArgs e)
        {
            ResizeHelloWorld();
        }

        /// <summary>
        /// Resize the Hello World to fill the client area
        /// </summary>
        void ResizeHelloWorld()
        {
            // change the font to match the size of the client area
            HelloWorldLabel.Font = GetBestFontFit(ClientSize, HelloWorldLabel.Font, HelloWorldLabel.Text, ClientSize.Height, 8f); ;

            // center the label
            CenterHelloWorld();
        }

        /// <summary>
        /// Center the HelloWorldLabel in the client area of HelloWorld form
        /// </summary>
        void CenterHelloWorld()
        {
            HelloWorldLabel.Location = new Point((ClientSize.Width / 2) - (HelloWorldLabel.Width / 2), (ClientSize.Height / 2) - (HelloWorldLabel.Height / 2));
        }

        /// <summary>
        /// Determine the best font match for the size/text.  The result should be between the maximum and minimum height.
        /// The size is the area to fill.  The new font is based on the old font which is passed in.
        /// </summary>
        /// <returns>Font</returns>
        public static Font GetBestFontFit(Size size, Font currFont, String text, float fontMax, float fontMin)
        {
            float fontPixels = fontMax;
            float minfontPixels = fontMin;

            // find the corresponding font size for the textbox size
            Font font = currFont;

            // make sure that the text is not null or empty before attempting to fit it in the area.
            // if it is null or empty, the current font is used since it does not matter.
            if (!String.IsNullOrEmpty(text))
            {
                Size textsize;

                font = new Font(currFont.FontFamily, fontPixels, currFont.Style, GraphicsUnit.Pixel);

                textsize = TextRenderer.MeasureText(text, font);

                // Check to see if the font fits in the area and if not, find a smaller one
                while ((((textsize.Height) > size.Height) || ((textsize.Width) > size.Width)) && (fontPixels > minfontPixels))
                {
                    // try to speed up the process of finding the right font size by detecting how much it is too big by
                    if (textsize.Height > size.Height)
                    {
                        // reduce the fontPixels based on how far off the font size is
                        fontPixels -= (textsize.Height - size.Height);
                    }
                    else if (textsize.Width > size.Width)
                    {
                        int charPixelWidth = textsize.Width / text.Length;
                        int targetPixelWidth = size.Width / text.Length;

                        // if the characters are too wide, reduce the fontPixels by how different the result is from the desired width
                        if (charPixelWidth > targetPixelWidth)
                        {
                            fontPixels -= (charPixelWidth - targetPixelWidth);
                        }
                        else
                        {
                            fontPixels -= 1.0F;
                        }
                    }
                    else
                    {
                        fontPixels -= 1.0F;
                    }

                    // drop the old one since it did not work
                    font.Dispose();

                    // get a new font based on the smaller font pixels size
                    font = new Font(font.FontFamily, fontPixels, font.Style, GraphicsUnit.Pixel);

                    // recalculate the size based on the new font
                    textsize = TextRenderer.MeasureText(text, font);
                }
            }

            // when all done, return the font to use
            return (font);
        }

    }
}

The most complicated part is figuring out the proper font to use to fill the client area. This code was modified from the code that is being used for UserInfo from the previous posts. The idea is that it loops over attempts to find the correct font starting from the biggest size to the minimum size.

The end result is a program that always shows “Hello World” in its biggest font possible to fit in the client area. If you change the size of the window, the text will adjust to fill it again. This is a good introduction to the concept of having text be re-sized to fit a specified rectangle. The effect is quite interesting if you re-size it constantly and see how fast the text adjusts. It almost appears to be an animation.

The next step will be to introduce the Mobile SDK for Windows Apps. This is mostly for the sake of matching the size of the display and the orientation.