Quantcast
Channel: windows mobile – Windows CE Programming
Viewing all 25 articles
Browse latest View live

Mobile Development: Move your Form

$
0
0

Although I do not yet know a use case for this, here comes some code to make your smartdevice forms being moveable.

As default, Windows forms on mobile devices are created always as maximized forms. There may be situations, where you need a moveable form. The trick in compact framework is to use SetWindowLong with WS_CAPTION style and apply that to your form.

  

To enable you to experminet more with Window Styles there is another demo enabling to check all known window stlyes with a form. Be warned, setting WS_DISABLED or some other styles will make your form inaccessible.

  

The above changes or Window Styles are well known to every native C Windows programmer. Possibly you are now curious what else can be done with a good background knowledge of Windows API programming.

Another nice piece of code is how to get a list of an enum type. The below needs a list of options to build the checkboxes in the form:

        //build a list of chk options for WSYTLES
        void buildOptions()
        {
            string[] stylesList = winapi.getStyles();

            int iCount=0;
            foreach (string s in stylesList)
            {
                CheckBox chkBox = new CheckBox();
                chkBox.Left = offsetX;
                chkBox.Top = iCount * offsetY;
                chkBox.Size = new Size(widthX, heightX);
                chkBox.Text = s;
                uint uStyle = (uint)Enum.Parse(typeof(winapi.WINSTYLES),s,false);
                if ((uiCurrentStyle & uStyle) == uStyle)
                    chkBox.Checked = true;
                chkBox.CheckStateChanged += new EventHandler(chkBox_CheckStateChanged);
                tabPage1.Controls.Add(chkBox);
                iCount++;
            }
        }

But an enum can not be enumerated ( great wording :-) ). Although you could build the list by hand, I am a lazy programmer. I already have entered all the values as an enum. But there is a solution:

        public static string[] getStyles()
        {
            List<string> list = new List<string>();
            foreach (WINSTYLES ws in GetValues(new WINSTYLES()))
            {
                list.Add(ws.ToString());
            }
            return list.ToArray();
        }

with the special GetValues() function for the enum (WINSTYLES):

        ...
        [Flags]
        public enum WINSTYLES:uint{
            WS_OVERLAPPED =     0x00000000,    //#define WS_OVERLAPPED       WS_BORDER | WS_CAPTION
...
            WS_MAXIMIZE=        0x01000000,
            WS_CAPTION =        0x00C00000,    //#define WS_CAPTION          0x00C00000L     /* WS_BORDER | WS_DLGFRAME  */
 ...
            WS_MAXIMIZEBOX=     0x00010000,
            WS_POPUPWINDOW=     0x80880000,     //     Creates a pop-up window with WS_BORDER, WS_POPUP, and WS_SYSMENU styles. The WS_CAPTION and WS_POPUPWINDOW styles must be combined to make the window menu visible.
        }        
...
        //great stuff by http://ideas.dalezak.ca/2008/11/enumgetvalues-in-compact-framework.html
        public static IEnumerable<Enum> GetValues(Enum enumeration)
        {
            List<Enum> enumerations = new List<Enum>();
            foreach (FieldInfo fieldInfo in enumeration.GetType().GetFields(
                  BindingFlags.Static | BindingFlags.Public))
            {
                enumerations.Add((Enum)fieldInfo.GetValue(enumeration));
            }
            return enumerations;
        }

Have fun.

DOWNLOAD:MoveableForms Source and Binary - VS2008, WM5 SDK, WM6.5.3 DTK (Hits: 95, size: 98.51 kB)

Source code at code.google.com

Update 18. jan 2013:
added class to enable you to subclass a form and get ALL window messages. Here an example of a WM_MOVE:

MoveableForm_WM_MOVE


Mobile Development: Analyze QRcode content on Windows Mobile

$
0
0

QRreaderWH6

You possibly know these QRcodes that make your phone open a web site or add a contact. Unfortunately there is no such function on Windows Mobile and I just wrote a demo of such a functionality:
A demo app to show how these nifty QR barcodes are encoded to hold URLs, business cards, appointments or other data. Further on, the application shows how to use QR code reading and import or launch the appropiate application for the QRcode content. The demo uses ZXing.Net to encode text into a QRcode barcode image but ZXing or ThoughtWorks code to decode QRcode images as the latter was easier to use. Then there is a list of sample encodings to show you how to specify a vcard or SMS or others to be encoded into a readable QRcode.

After start of the app and then simply click in the type list at the top and select for example “vcard”. In the sample field you can see how a vcard is to be encoded:

BEGIN:VCARD
N:Heinz-Josef Gode
ORG:ACME GmbH
TITLE:Systems Engineer
TEL:0123456789
URL:www.hjgode.de/wp
EMAIL:heinz-josef.gode@somewhere.com
ADR:Musterstrasse 1
NOTE:Support Engineer
END:VCARD

Now click [decode&encode] and you get the QRcode image and the cleaned content:

  

The ‘decoded’ cleaned content is:

Heinz-Josef Gode
Systems Engineer
ACME GmbH
Musterstrasse 1
0123456789
heinz-josef.gode@somewhere.com
www.hjgode.de/wp
Support Engineer

If you click [process] instead, the information is transferred to the right app (if any) on the device. For a vcard, this is Pocket Outlook or say Contacts. You can check the information has arrived in Contacts:

     

Easy! Just using zxing client code and some mobile code. Here is a calendar entry example:

  

You also have the option to save the QRcode image or to load QRcode images. You even can take a photo of a QRcode print. See the File menu with Save Image, Load Image and the Extras menu with Camera.

The code to parse a QRcode text is:

            if (comboBox1.SelectedIndex == -1)
                return;
            testQRcodes = new TestQRcodes();
            TestQRcodes.TestType tt = (TestQRcodes.TestType)comboBox1.SelectedItem;
            string sTest = tt.content;
            QRdecodeClass qr = new QRdecodeClass(sTest);
            txtResultType.Text = qr.resultTypeString;
            txtResultContent.Text = qr.sResult;
            encode2QRcode(txtResultContent.Text);

whereas encode2QRcode encodes the data into a QRcode image:

        void encode2QRcode(string s)
        {
            try
            {
                var writer = new BarcodeWriter();
                writer.Format = BarcodeFormat.QR_CODE;
                writer.Options.Height = 240;
                writer.Options.Width = 240;
                pictureBox1.Image = writer.Write(s);
            }
            catch (Exception exc)
            {
                MessageBox.Show(exc.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation,MessageBoxDefaultButton.Button1);
            }
        }

The code to decode text from a QRcode image is that:

        private void mnuLoadImage_Click(object sender, EventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog();
            if (ofd.ShowDialog() == DialogResult.OK)
            {
                string FileName = ofd.FileName;
                try
                {
                    pictureBox1.Image = new Bitmap(FileName);
                    QRCodeDecoder decoder = new QRCodeDecoder();
                    String decodedString = decoder.decode(new QRCodeBitmapImage(new Bitmap(pictureBox1.Image)));

                    txtResultContent.Text = decodedString;

                    processQRresult.processResult(txtContentSample.Text);
                }
                catch (Exception ex)
                {
                    MessageBox.Show("Sorry, could not load image. Exception: " + ex.Message);
                    txtResultContent.Text = ex.Message;
                }
            }
        }

You see the code is simple.

A list of possible standard texts to encode into a barcode: http://archive.is/20120530/http://code.google.com/p/zxing/wiki/BarcodeContents

Warning: the menu extra offers to try to capture an image to be decoded. Most QRCode decoders I have tested fail in decoding these camera pictures. As I am working with and developing for industrial devices, this is no problem for me. Most of these devices have a barcode reader intergrated and decode QRcode images the fastest.

Download source of QRreaderWH6 incl. ZXing.net binary runtimes

DOWNLOAD:QReaderWH6 - (Hits: 203, size: 1.52 MB)

Mobile Development: A remote cpu monitor and cpu usage analysis

$
0
0

cpumon2 and cpumonRcv

Although this project is not yet finished, the main function works very well: viewing cpu usage remotely and capture data to database for later analysis.

Some of the ideas and code is inspired by a cpu usage article at http://www.codeproject.com/Articles/159461/Mobile-Processor-Usage. Further on I got more questions the last days like “Why is app x running slow?” or “When I start that app, the system gets slow and taskmanager shows a high cpu usage.”.

Here are two tools to capture cpu usage of a windows mobile device remotely via a TCP/IP connection.

cpumon2   cpumonRcv

excel-barchart

Before you read on, the code is by far not perfect and there are still many improvements possible. But if you need a working remote cpumon, here we go…

cpumon on device

Why another cpumon or taskmanager like app? Simply as you may test an application without having to switch between the app and taskmanager to see cpu usage. Switching back and for may also interrupt the workflow in your app.

cpumon creates snapshot of all processes and threads periodically including there used user and kernel times. On windows ce, the kernel times are always zero, but that may change in future and so I included that in the statistics info too.

First, cpumon creates a snapshot of all processes with there process ID’s. Then another snapshot of all threads is created with there owner ProcID, thread ID and usage times. There is a delay between two snpashots further called duration. The threads are assigned to the processes and there times (user and kernel) are summarized. This statistics snapshot is put in a queue. This will repeat all 3 seconds. At the end of a statistics set, another thread is released that dequeues the data and sends it using an UDP socket. I decided to use UDP broadcast as this is fast and requires no client/server connection. At the end of a set, one <EOT> packet is sent to let the host know, that a set has been transfered.

The statistic data is also shown in the GUI of cpumon, but this is only for reference.

cpumonRcv

cpumonRcv is a desktop csharp app that listens for UDP broadcasts of cpumon. It converts the received bytes back to a statistic set of processes and threads and en-queues the data until an EOT packet has arrived. Then the data is saved to a local database and can be used for later analyzes. Additionally every new statistic data packet will update a live view.

The code

On the device

A process has an exe name and a list of threads. Every thread is captured with the time it has spend in user and kernel space. The threads and processes are recorded with a timestamp and the duration of two consecutive snapshots. If you sum the user times of all threads of a process and divide that by the duration and multiply with 100 you get the percentage the process has used of the processors time.

The above is reflected in a thread and a process class. Further on, these classes include a conversion from/to byte arrays. These byte arrays build the packet that is send via UDP broadcasts.
The transmitter (cpumon) and receiver share the same code for process and thread class. So the receiver can easily transform byte packets back to processes and threads.

The main class is ProcInfo. It starts the snapshot (usage) thread and the socket thread.

        public ProcInfo()
        {
            statisticsTimes = new Dictionary<string, ProcessStatistics.process_statistics>();

            eventEnableCapture = new AutoResetEvent(true);
            eventEnableSend = new AutoResetEvent(false);

            //procStatsQueue = new Queue<ProcessStatistics.process_statistics>();
            procStatsQueueBytes = new Queue<byte[]>();

            myThreadSocket = new Thread(socketThread);
            myThreadSocket.Start();

            myThread = new Thread(usageThread);
            myThread.Start();
        }

The snapshot thread captures a snapshot of processes and threads and adds the data to a dictionary and en-queues it for the socket thread. I use a dictionary for the process stats as this automatically inserts or updates process data. So I do not need to look first, if a process is already known or not and if I have to update existing data or add new data. Another reason is that the code has to look up a the thread list for existing data (TryGetValue) to build the new stats.

        /// <summary>
        /// build thread and process list periodically and fire update event and enqueue results for the socket thread
        /// </summary>
        void usageThread()
        {
            try
            {
                int interval = 3000;

                uint start = Process.GetTickCount();
                Dictionary<uint, thread> old_thread_List;// = Process.GetThreadList();

                string exeFile = Process.exefile;
                //read all processes
                Dictionary<uint, process> ProcList = Process.getProcessNameList();
                DateTime dtCurrent = DateTime.Now;

                //######### var declarations
                Dictionary<uint, thread> new_ThreadList;
                uint duration;
                long system_total;
                long user_total, kernel_total;      //total process spend in user/kernel
                long thread_user, thread_kernel;    //times the thread spend in user/kernel
                DWORD dwProc;
                float user_percent;
                float kernel_percent;    
                ProcessStatistics.process_usage usage;
                ProcessStatistics.process_statistics stats = null;

                string sProcessName = "";
                List<thread> processThreadList = new List<thread>();

                //extended list
                List<threadStatistic> processThreadStatsList = new List<threadStatistic>(); //to store thread stats
                while (!bStopMainThread)
                {
                    eventEnableCapture.WaitOne();
                    old_thread_List = Process.GetThreadList();  //build a list of threads with user and kernel times

                    System.Threading.Thread.Sleep(interval);

                    //get a new thread list
                    new_ThreadList = Process.GetThreadList();   //build another list of threads with user and kernel times, to compare

                    duration = Process.GetTickCount() - start;

                    ProcList = Process.getProcessNameList();    //update process list
                    dtCurrent = DateTime.Now;
                    system_total = 0;
                    statisticsTimes.Clear();
                    //look thru all processes
                    foreach (KeyValuePair<uint, process> p2 in ProcList)
                    {
                        //empty the process's thread list
                        processThreadList=new List<thread>();
                        processThreadStatsList = new List<threadStatistic>();

                        user_total     = 0;  //hold sum of thread user times for a process
                        kernel_total   = 0;  //hold sum of thread kernel times for a process
                        sProcessName = p2.Value.sName;

                        //SUM over all threads with that ProcID
                        dwProc = p2.Value.dwProcID;
                        foreach (KeyValuePair<uint, thread> kpNew in new_ThreadList)
                        {
                            thread_user = 0;
                            thread_kernel = 0;
                            //if the thread belongs to the process
                            if (kpNew.Value.dwOwnerProcID == dwProc)
                            {
                                //is there an old thread entry we can use to calc?
                                thread threadOld;
                                if (old_thread_List.TryGetValue(kpNew.Value.dwThreadID, out threadOld))
                                {
                                    thread_user=Process.GetThreadTick(kpNew.Value.thread_times.user) - Process.GetThreadTick(old_thread_List[kpNew.Value.dwThreadID].thread_times.user);
                                    user_total += thread_user;
                                    thread_kernel =Process.GetThreadTick(kpNew.Value.thread_times.kernel) - Process.GetThreadTick(old_thread_List[kpNew.Value.dwThreadID].thread_times.kernel);
                                    kernel_total += thread_kernel;
                                }
                                //simple list
                                thread threadsOfProcess = new thread(kpNew.Value.dwOwnerProcID, kpNew.Value.dwThreadID, kpNew.Value.thread_times);
                                processThreadList.Add(threadsOfProcess);

                                //extended list
                                threadStatistic threadStats = 
                                    new threadStatistic(
                                        kpNew.Value.dwOwnerProcID, 
                                        kpNew.Value.dwThreadID, 
                                        new threadtimes(thread_user, thread_kernel), 
                                        duration, 
                                        dtCurrent.Ticks);
                                processThreadStatsList.Add(threadStats);

                            }//if dwProcID matches
                        }
                        //end of sum for process
                        user_percent      = (float)user_total / (float)duration * 100f;
                        kernel_percent    = (float)kernel_total / (float)duration * 100f;
                        system_total = user_total + kernel_total;

                        // update the statistics with this process' info
                        usage = new ProcessStatistics.process_usage(kernel_total, user_total);
                        // update process statistics
                        stats = new ProcessStatistics.process_statistics(p2.Value.dwProcID, p2.Value.sName, usage, dtCurrent.Ticks, duration, processThreadStatsList);

                        //add or update the proc stats
                        if (exeFile != p2.Value.sName || bIncludeMySelf)
                        {
                            statisticsTimes[p2.Value.sName] = stats;
                                procStatsQueueBytes.Enqueue(stats.ToByte());
                        }

                        start = Process.GetTickCount();
                    }//foreach process

                    onUpdateHandler(new ProcessStatsEventArgs(statisticsTimes, duration));
                    procStatsQueueBytes.Enqueue(ByteHelper.endOfTransferBytes);
                    ((AutoResetEvent)eventEnableSend).Set();
                }//while true
            }
            catch (ThreadAbortException ex)
            {
                System.Diagnostics.Debug.WriteLine("ThreadAbortException: usageThread(): " + ex.Message);
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine("Exception: usageThread(): " + ex.Message);
            }
            System.Diagnostics.Debug.WriteLine("Thread ENDED");
        }

 

The other thread, the socket thread, looks like this:

        /// <summary>
        /// send enqueued objects via UDP broadcast
        /// </summary>
        void socketThread()
        {
            System.Diagnostics.Debug.WriteLine("Entering socketThread ...");
            try
            {
                const int ProtocolPort = 3001;
                sendSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
                sendSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
                sendSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendBuffer, 32768);

                IPAddress sendTo = IPAddress.Broadcast;// IPAddress.Parse("192.168.128.255");  //local broadcast
                EndPoint sendEndPoint = new IPEndPoint(sendTo, ProtocolPort);

                //UdpClient udpC = new UdpClient("255.255.255.255", 1111);
                System.Diagnostics.Debug.WriteLine("Socket ready to send");

                while (!bStopSocketThread)
                {
                    //block until released by capture
                    eventEnableSend.WaitOne();
                    lock (lockQueue)
                    {
                        //if (procStatsQueue.Count > 0)
                        while (procStatsQueueBytes.Count > 0)
                        {
                            //ProcessStatistics.process_statistics pStat = procStatsQueue.Dequeue();
                            //byte[] buf = pStat.ToByte();
                            byte[] buf = procStatsQueueBytes.Dequeue();
                            if (ByteHelper.isEndOfTransfer(buf))
                                System.Diagnostics.Debug.WriteLine("sending <EOT>");

                            sendSocket.SendTo(buf, buf.Length, SocketFlags.None, sendEndPoint);
                            //System.Diagnostics.Debug.WriteLine("Socket send " + buf.Length.ToString() + " bytes");
                            //System.Diagnostics.Debug.WriteLine(pStat.dumpStatistics());
                            System.Threading.Thread.Sleep(2);
                        }
                    }
                    ((AutoResetEvent)eventEnableCapture).Set();
                }

            }
            catch (ThreadAbortException ex)
            {
                System.Diagnostics.Debug.WriteLine("ThreadAbortException: socketThread(): " + ex.Message);
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine("Exception: socketThread(): " + ex.Message);
            }
            System.Diagnostics.Debug.WriteLine("socketThread ENDED");
        }

After setting up the socket for port 3001 the thread waits for the event eventEnableSend. Then it starts the transfer of all en-queued packets. The last packet in the queue should be the <EOT> packet, which has been en-queued by the usageThread. The usageThread is waiting during the transfer of the packets and will be released by the socketThread with setting the event eventEnableCapture.

As we can only send bytes thru the socket, the process statistics class include functions to be converted to or from byte arrays. Here is a snippet of the process_statistics class to show you how I did implement this.

            public byte[] ToByte()
            {
                List<byte> buf = new List<byte>();
                //string length
                Int16 bLen = (Int16)System.Text.Encoding.UTF8.GetByteCount(sName);
                buf.AddRange(BitConverter.GetBytes(bLen));
                byte[] bName = System.Text.Encoding.UTF8.GetBytes(sName);
                //string as byte[]
                buf.AddRange(bName);

                buf.AddRange(BitConverter.GetBytes(dateTime));
                buf.AddRange(BitConverter.GetBytes(duration));
                buf.AddRange(procUsage.ToByte());
                buf.AddRange(BitConverter.GetBytes(processID));

                //list count
                Int16 iCnt = (Int16) ThreadStatList.Count;
                threadStatistic[] threadsArray = ThreadStatList.ToArray();
                buf.AddRange(BitConverter.GetBytes(iCnt));
                //now add the threads of the list
                foreach (threadStatistic th in threadsArray)
                    buf.AddRange(th.ToByte());

                return buf.ToArray();
            }

The packet (byte array) can vary in size as a string (the process name) has to be encoded and decoded. So the function first encodes the string length and then the string.

and here a function to get the object back from a byte array.

            public process_statistics FromByte(byte[] buf)
            {
                int offset = 0;
                Int16 bLen = BitConverter.ToInt16(buf, 0); //2 bytes
                offset += sizeof(System.Int16);
                if (bLen > 0)
                    this.sName = System.Text.Encoding.UTF8.GetString(buf, offset, bLen);
                offset += bLen;
                this.dateTime = BitConverter.ToInt64(buf, offset);
                offset += sizeof(System.Int64);
                this.duration = BitConverter.ToUInt32(buf, offset);
                offset += sizeof(System.Int32);
                this.procUsage = new process_usage(ref buf, ref offset);
                //offset = offset; //has been changed by process_usage

                this.processID = BitConverter.ToUInt32(buf, offset);
                offset += sizeof(System.UInt32);

                //how many thtreads are in the byte stream
                Int16 iCnt = BitConverter.ToInt16(buf, offset);
                offset += sizeof(System.Int16);
                //start reading the threads
                List<threadStatistic> thList = new List<threadStatistic>();
                for (int x = 0; x < iCnt; x++)
                {
                    threadStatistic th = new threadStatistic(buf, ref offset);
                    thList.Add(th);
                }
                this.ThreadStatList = thList;

                return this;
            }

Here the string has to be decoded and so first the length has to be read and then the string.

On the PC

cpumonRcv runs on a PC in the same subnet as the device is running cpumon. The receiver code listens for packets coming on UPD port 3001 and then re-assembles the byte to process statistics. These stats are saved to a local database (currently sqlite) and are send to the GUI for a live view.

The main function is the socket server thread code.

    public void StartReceive()
    {
        receiveSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
        bindEndPoint = new IPEndPoint(IPAddress.Any, 3001);
        recBuffer = new byte[maxBuffer];
        receiveSocket.Bind(bindEndPoint);
        receiveSocket.BeginReceiveFrom(recBuffer, 0, recBuffer.Length, SocketFlags.None, ref bindEndPoint, new AsyncCallback(MessageReceivedCallback), (object)this);
    }

You see, that is not really a thread. Instead of polling the socket for received bytes within a thread, the code uses the asynchronous BeginReceiveFrom socket call.

The callback will be called whenever there are bytes received. As a packet always holds a complete statistic process data, the GUI can be updated after each received packet. But to be fast, the GUI is only updated when a <EOT> packet has arrived. The other stats data are delivered to the GUI thread, where they are recorded for later use and directly update the GUI.

When a packet has received, we must call BeginReceiveFrom again to be ready for the next packet.

    void MessageReceivedCallback(IAsyncResult result)
    {
        EndPoint remoteEndPoint = new IPEndPoint(0, 0);
        try
        {
            //all data should fit in one package!
            int bytesRead = receiveSocket.EndReceiveFrom(result, ref remoteEndPoint);
            byte[] bData = new byte[bytesRead];
            Array.Copy(recBuffer, bData, bytesRead);
            if (ByteHelper.isEndOfTransfer(bData))
                updateEndOfTransfer();// end of transfer
            else
            {
                ProcessStatistics.process_statistics stats = new ProcessStatistics.process_statistics(bData);
                //System.Diagnostics.Debug.WriteLine( stats.dumpStatistics() );
                updateStatus(stats);
            }
        }
        catch (SocketException e)
        {
            System.Diagnostics.Debug.WriteLine(String.Format("MessageReceivedCallback SocketException: {0} {1}", e.ErrorCode, e.Message));
        }
        catch (Exception e)
        {
            System.Diagnostics.Debug.WriteLine(String.Format("MessageReceivedCallback Exception: {0}", e.Message));
        }
        try
        {
            //ready to receive next packet
            receiveSocket.BeginReceiveFrom(recBuffer, 0, recBuffer.Length, SocketFlags.None, ref bindEndPoint, new AsyncCallback(MessageReceivedCallback), (object)this);
        }
        catch (Exception) { }
    }

I placed the data capturing inside the GUI thread. The live view will be updated and a queue is filled with the data.

        delegate void addDataCallback(ProcessStatistics.process_statistics procStats);
        void addData(ProcessStatistics.process_statistics procStats)
        {
            if (this.dataGridView1.InvokeRequired)
            {
                addDataCallback d = new addDataCallback(addData);
                this.Invoke(d, new object[] { procStats });
            }
            else
            {
                //enqueue data to be saved to sqlite
                dataQueue.Enqueue(procStats);

                //dataAccess.addSqlData(procStats);

                dataGridView1.SuspendLayout();
                //dtProcesses.Rows.Clear();

                dataAccess.addData(procStats);

                //dataGridView1.Refresh();
                dataGridView1.ResumeLayout();

                //release queue data
                dataAccess.waitHandle.Set();

                //object[] o = new object[7]{ procUsage.procStatistics. .procStatistics. [i].sApp, eventEntries[i].sArg, eventEntries[i].sEvent, 
                //        eventEntries[i].sStartTime, eventEntries[i].sEndTime, eventEntries[i].sType, eventEntries[i].sHandle };
            }
        }

A queue is used for the background task that saves the data to a file. Saving will need some time and should not interfer with the live view or the data receive function.

        Queue<System.Process.ProcessStatistics.process_statistics> dataQueue;
        Thread myDataThread;

        public EventWaitHandle waitHandle;

The data is ‘written’ to the local database using a separate thread.

        void dataAddThread()
        {
            try
            {
                while (true)
                {
                    waitHandle.WaitOne();
                    if (dataQueue.Count > 10)
                    {
                        while (dataQueue.Count > 0)
                        {
                            System.Process.ProcessStatistics.process_statistics procStats = dataQueue.Dequeue();
                            addSqlData(procStats);
                        }
                    }
                    Thread.Sleep(10);
                }
            }
            catch (ThreadAbortException ex)
            {
                System.Diagnostics.Debug.WriteLine("Exception: " + ex.Message);
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine("Exception: " + ex.Message);
            }
        }

The receiver code has some time (3 seconds) to save the data between the send of two full statistic sets.

The live view shows a list of running processes in a datagridview and a small line plot of the usage times of the selected process.

cpumonRcv

There is another data view that is not yet finished. The detail view shows a list of captured stats with the processes and the timestamp. If you click a line, the threads stats of the process are displayed with there usage. The detail view shows the saved data, where the main view shows live data.

cpumonRcv_detailview

Export and excel analysis

Now I have also finished export functions. From the detail form you can use the menu to export the process or threads table data to CSV files. These can be easily imported into other applications like for example excel or LibreOffice Calc. The below table is the result of a CSV import and adding some calulated fields (the percent ‘of cpu usage’ is calculated by UserTime/Duration*100).

excel01

There is another export which first transforms the data and then exports to a CSV file. This CSV file is much easier to handle for analysis. It contains the recorded times, all processes and the time they spend in user (cpu usage) mode. The export does something like rotating the ‘processes’ table.

        #region Transform
        class PROCESS_USAGE
        {
            public string procname;
            public int user;
            public UInt64 timestamp;
            public PROCESS_USAGE(string sProcessName, int iUserTime, UInt64 iTimeStamp)
            {
                procname = sProcessName;
                user = iUserTime;
                timestamp = iTimeStamp;
            }
        }

        public int export2CSV2(string sFileCSV)
        {
            //### setup
            sql_cmd = new SQLiteCommand();
            sql_con = new SQLiteConnection();
            SQLiteDataReader sql_rdr;
            connectDB();
            if (sql_con.State != ConnectionState.Open)
            {
                sql_con.Close();
                sql_con.Open();
            }
            sql_cmd = sql_con.CreateCommand();
            long lCnt = 0;

            //### Build a List of known processes
            sql_cmd.CommandText = "Select DISTINCT Process from processes order by Process";
            List<string> lProcesses= new List<string>();
            sql_rdr = sql_cmd.ExecuteReader();
            while (sql_rdr.Read())
            {
                lProcesses.Add((string)sql_rdr["Process"]);
            }
            sql_rdr.Close();
            sql_rdr.Dispose();

            //create a new table with the process names as fields
            string sProcField = "";
            foreach (string sProc in lProcesses)
            {
                sProcField += "[" + sProc + "] INTEGER,";
            }
            sProcField = sProcField.TrimEnd(new char[] { ',' });
            sProcField = "[Time] INTEGER, " + sProcField;
            //delete existing table            
            lCnt = executeNonQuery("DROP Table IF EXISTS [ProcUsage] ;");
            //create new one
            lCnt = executeNonQuery("Create Table [ProcUsage] (" + sProcField + ");");

            //### get all process,user,time data
            List<PROCESS_USAGE> lProcessUsages = new List<PROCESS_USAGE>();
            sql_cmd.CommandText = "Select Process,User,Time from processes order by Time";
            sql_rdr = sql_cmd.ExecuteReader();
            while (sql_rdr.Read())
            {
                string sP = (string)sql_rdr["Process"];
                int iUT = Convert.ToInt32(sql_rdr["User"]);
                ulong uTI = Convert.ToUInt64(sql_rdr["Time"]);
                lProcessUsages.Add(new PROCESS_USAGE(sP, iUT, uTI));
            }
            sql_rdr.Close();

            //### get all distinct times
            List<ulong> lTimes = new List<ulong>();
            sql_cmd.CommandText = "Select DISTINCT Time from processes order by Time";
            sql_rdr = sql_cmd.ExecuteReader();
            while (sql_rdr.Read())
            {
                lTimes.Add(Convert.ToUInt64(sql_rdr["Time"]));
            }
            sql_rdr.Close();

            string sUpdateCommand = "";
            //### file the new ProcUsage table
            SQLiteTransaction tr = sql_con.BeginTransaction();
            foreach (ulong uTime in lTimes)
            {
                System.Diagnostics.Debug.WriteLine("Updating for Time=" + uTime.ToString());
                //insert an empty row
                sql_cmd.CommandText = "Insert Into ProcUsage (Time) VALUES(" + uTime.ToString() + ");";
                lCnt = sql_cmd.ExecuteNonQuery();
                foreach (string sPro in lProcesses)
                {
                    //is there already a line?
                    // http://stackoverflow.com/questions/4495698/c-sharp-using-listt-find-with-custom-objects
                    PROCESS_USAGE pu = lProcessUsages.Find(x => x.procname == sPro && x.timestamp == uTime);
                    if (pu != null)
                    {
                        System.Diagnostics.Debug.WriteLine("\tUpdating User="+ pu.user +" for Process=" + sPro);
                        //update values
                        sUpdateCommand = "Update [ProcUsage] SET " +
                            "[" + sPro + "]=" + pu.user +
                            " WHERE Time=" + uTime.ToString() + //" AND Process=" + "'" + sPro + "'"+
                            ";";
                        sql_cmd.CommandText = sUpdateCommand;
                        lCnt = sql_cmd.ExecuteNonQuery();
                    }
                }
            }
            tr.Commit();

            lCnt = 0;
            SQLiteDataReader rdr = null;
            System.IO.StreamWriter sw = null;
            try
            {
                sw = new System.IO.StreamWriter(sFileCSV);
                string sFields = "";
                List<string> lFields = new List<string>();
                lFields.Add("Time");
                lFields.AddRange(lProcesses);
                foreach (string ft in lFields)
                {
                    sFields += ("'" + ft + "'" + ";");
                }
                sFields.TrimEnd(new char[] { ';' });
                sw.Write(sFields + "\r\n");

                sql_cmd.CommandText = "Select * from ProcUsage;";
                rdr = sql_cmd.ExecuteReader(CommandBehavior.CloseConnection);
                while (rdr.Read())
                {
                    lCnt++;
                    sFields = "";
                    //Console.WriteLine(rdr["ProcID"] + " " + rdr["User"]);
                    foreach (string ft in lFields)
                    {
                        sFields += rdr[ft] + ";";
                    }
                    sFields.TrimEnd(new char[] { ';' });
                    sw.Write(sFields + "\r\n");
                    sw.Flush();
                }
            }
            catch (Exception) { }
            finally
            {
                sw.Close();
                rdr.Close();
            }

            return 0;
        }
        #endregion

That is the first time that I used a List<>.Find (PROCESS_USAGE pu = lProcessUsages.Find(x => x.procname == sPro && x.timestamp == uTime);). The ‘rotated’ table looks like this:

proc_usage

Using the above data enables you to create very informative 3D bar charts:

proc_usage02

In the chart above you can see the time running from left to right. Each process has its own bar row. The height of the bar shows the relative cpu usage against the measure interval duration of 3000ms (3 seconds).

Possible extensions and planned enhancements

The code is by far not perfect. If you look closer to the exported transformed data, you will recognize empty user time fields. That happens when the receiver thread is not active for example during a time expensive GUI update. So, there are possible improvements.

  • Improve decoupling of receive and display of data
  • Option to log cpu usage locally on the device (if not to time expensive)
  • Integrate better graphics using mschart?

Source Code

Source code at code.google.com

Downloads

Binaries are available inside the google code dirs for cpumon2 and cpumonRcv. For the PC you need the SQLite .Net runtimes installed.

RDM_Keepbusy has been updated

$
0
0

I fixed a bug in RDM_KeepBusy. It worked only for the first session and not after close/open another session. Please always use the subversion code.

Internet Explorer Mobile – To zoom or not to zoom

$
0
0

With Windows Mobile 6.5.3, sorry, Windows Embedded Handheld 6.5.3 we have the Internet Explorer Mobile 6 engine.

weh-version

Although it may look nice for the one or other, the rendering, especially the zoom in and out is unusable for commercial use. The IT departments of Warehouses and Stores designed there mobile web pages for a fixed size layout of 240×320 (QVGA). Using IEM (Internet Explorer Mobile) these pages do not show well. Either they are to small to read or, if zoomed in, the user has to scroll here and there. Not very usable. Event the Fit-To-Screen option does not help.

The predefined viewport or 1024×768 may be good for browsing non-mobile web sites but I was unable to find a suitable setting for mobile sites with a fixed QVGA or VGA layout.

Here comes the tipp found at xda-developers: change the default viewport size to match your needs:

Default:
HKCU\Software\Microsoft\Internet Explorer\Main
Viewport Height = dword:00000258(600)
Viewport Width = dword:00000320(800)

Landscape optimized (VGA)
HKCU\Software\Microsoft\Internet Explorer\Main
Viewport Height = dword:000001e0(480)
Viewport Width = dword:00000280(640)

Portrait optimized (VGA)
HKCU\Software\Microsoft\Internet Explorer\Main
Viewport Height = dword:00000280(640)
Viewport Width = dword:000001e0(480)

I assume you will find these better for commercial use than the desktop settings.

Unfortunately these settings are not used on every windows embedded device. So, you may give it a try and if it works for you, fine. I tested one device and iexplore.exe did not care about these entries. I checked with RegLoggerCE and found that iexplore.exe on that device does not query the above settings. For anyone interested, here is the log file of registry access of iexplore.exe captured with regLoggerCE: DOWNLOAD:regLoggerCE log file for iexplore within WEH653 - (Hits: 93, size: 10.66 kB)

Internet Explorer Mobile – QVGA web site do not scale well to VGA screen

$
0
0

As described in this post, I was looking for a way to get a proper view of pages designed for QVGA screens on devices with higher resolutions like VGA.

1) SAP ITS mobile screen on VGA Internet Explorer Mobile

1) SAP ITS mobile screen on VGA Internet Explorer Mobile

2) SAP ITS mobile screen on VGA display with viewport width=240 and zoom=2

2) SAP ITS mobile screen on VGA display with viewport width=240 and zoom=2

The trick is to use the non-standard viewport META tag. As described here, here and here, you can use meta-viewport to define the initial viewport, the part of the page you want to see and a zoom level.

Fortunately the meta tag for viewport works within Internet Explorer Mobile (Windows Embedded Handheld 6.5.3).

weh-version

The differences between the html code of image 1) and image 2) is just one additional line in the head section of the code for image 2):

<meta name="viewport" content="width=240, initial-scale=2, maximum-scale=2, minimum-scale=2">

This line makes the viewport equal to the PocketPC QVGA width of 240 pixel and specifies a zoom level of 2 to enlarge the viewport onto the VGA screen with a width of 480.

Now, my final goal is to disable the Zoom bar of Internet Explorer Mobile.

See also this post about QVGA and VGA scaling.

Windows Mobile: watch the memory footstep of running processes

$
0
0

Some times ago I posted my remote cpu usage monitor. Now here is a similar tool but for logging the memory. You can now watch the memory usage of processes remotely for example when you test an application.

There are two tools: vmUsage and vmUsageRecvr. You may use the mobile vmUsage alone and just use its logging. The other tool receives the memory status information on a PC and enables long time logging and export to a csv text.

vmusage   memeater-vm   excel-linechart

vmUsage is the mobile application that shows you a list of bars, one bar for each of the 32 possible process slots. It also shows the process name running in a slot and the memory usage. The memory usage is queried using a KernelIOCtl (IOCTL_KLIB_GETPROCMEMINFO). I found that API call at CodeProject. I first tried with the approach made in VirtualMemory at codeproject. But using VirtualQuery for every 4K block inside 32 pieces of 32MB takes a lot of time (256000 blocks!). The following shows a process memEater that is gaining more and more memory:

memeater-vmusage   memeater-vmusage2

You can also see the total physical and available memory in the first bar and you will recognize irregular memory changes too.

The small tool sends all data using UDP to possible receivers. My receiver is called vmUsageRecvr and receives the data and saves every virtual memory status set it to a SQLite database. The data can then be exported and is re-arranged by known processes. The live view of vmUsageRecvr shows the latest receive memory status and a small line graphic showing how the total used memory changed over time.

You can use the exported data in excel and again produce nice graphics.

excel-vmusage

In the above graph you can see memeater is consuming memory in 1MB steps until it crashes. The other memory peek is produced by pimg.exe, the camera dialog, when I made a photo.

Processes may start and go and so there process ID will be zero when they are not running. If a process is gone, vmUsage will not record it in its log:

20130221 06:17 pimg    569344    VMusage.exe    2105344    MemEater.exe    15175680    tmail.exe    303104    IQueue.exe    679936    total    493723648    free    323395584    vtotal    33554432tvfree    26476544    load    35    
20130221 06:17 pimg    569344    VMusage.exe    2105344    MemEater.exe    16228352    tmail.exe    303104    IQueue.exe    679936    total    493723648    free    322342912    vtotal    33554432tvfree    26476544    load    35    
20130221 06:17 pimg    569344    VMusage.exe    2105344    MemEater.exe    17281024    tmail.exe    303104    IQueue.exe    679936    total    493723648    free    322342912    vtotal    33554432tvfree    26476544    load    35    
20130221 06:17 pimg    569344    VMusage.exe    2105344    MemEater.exe    17281024    tmail.exe    303104    IQueue.exe    679936    total    493723648    free    321282048    vtotal    33554432tvfree    26476544    load    35    
20130221 06:17 pimg    569344    VMusage.exe    2105344    MemEater.exe    18337792    tmail.exe    303104    IQueue.exe    679936    total    493723648    free    320163840    vtotal    33554432tvfree    26476544    load    36    
20130221 06:17 pimg    569344    VMusage.exe    2105344    MemEater.exe    19456000    tmail.exe    303104    IQueue.exe    679936    total    493723648    free    320163840    vtotal    33554432tvfree    26476544    load    36    
20130221 06:17 pimg    569344    VMusage.exe    2105344    MemEater.exe    19456000    tmail.exe    303104    IQueue.exe    679936    total    493723648    free    319111168    vtotal    33554432tvfree    26476544    load    36    
20130221 06:17 pimg    569344    VMusage.exe    2109440    MemEater.exe    20508672    tmail.exe    303104    IQueue.exe    679936    total    493723648    free    318054400    vtotal    33554432tvfree    26476544    load    36    
20130221 06:17 pimg    569344    VMusage.exe    2109440    MemEater.exe    21561344    tmail.exe    303104    IQueue.exe    679936    total    493723648    free    317001728    vtotal    33554432tvfree    26476544    load    36    
20130221 06:17 pimg    569344    VMusage.exe    2109440    MemEater.exe    22614016    tmail.exe    303104    IQueue.exe    679936    total    493723648    free    317001728    vtotal    33554432tvfree    26476544    load    36    
20130221 06:17 pimg    569344    VMusage.exe    2109440    MemEater.exe    22614016    tmail.exe    303104    IQueue.exe    679936    total    493723648    free    315949056    vtotal    33554432tvfree    26476544    load    37    
20130221 06:17 pimg    569344    VMusage.exe    2109440    MemEater.exe    23666688    tmail.exe    303104    IQueue.exe    679936    total    493723648    free    314896384    vtotal    33554432tvfree    26476544    load    37    
20130221 06:17 pimg    569344    VMusage.exe    2109440    MemEater.exe    24719360    tmail.exe    303104    IQueue.exe    679936    total    493723648    free    314896384    vtotal    33554432tvfree    26476544    load    37    
20130221 06:17 pimg    569344    VMusage.exe    2109440    MemEater.exe    24719360    tmail.exe    303104    IQueue.exe    679936    total    493723648    free    313843712    vtotal    33554432tvfree    26476544    load    37    
20130221 06:17 pimg    569344    VMusage.exe    2109440    MemEater.exe    25772032    tmail.exe    303104    IQueue.exe    679936    total    493723648    free    314822656    vtotal    33554432tvfree    27459584    load    37    
20130221 06:18 pimg    569344    VMusage.exe    1191936    MemEater.exe    25772032    tmail.exe    303104    IQueue.exe    679936    total    493723648    free    340561920    vtotal    33554432tvfree    27328512    load    32    
20130221 06:18 pimg    569344    VMusage.exe    1323008    tmail.exe    303104    IQueue.exe    679936    total    493723648    free    340430848    vtotal    33554432tvfree    27197440    load    32    
20130221 06:18 pimg    569344    VMusage.exe    1388544    tmail.exe    303104    IQueue.exe    679936    total    493723648    free    340365312    vtotal    33554432tvfree    27131904    load    32    
20130221 06:18 pimg    569344    VMusage.exe    1519616    tmail.exe    303104    IQueue.exe    679936    total    493723648    free    340234240    vtotal    33554432tvfree    27000832    load    32

 

When a process is gone in vmUsageRecr, the process data is still there. In the following export viewed in excel you can see pimg is first not running. After pimg is started it consumes about 368KB. Then I took a photo and the memory increased to 1.1MB:

excel_process_0

Notes about the code

As said, the code is similar to my cpuMon tool.

A costum panel to simulate a bar graph

vmUsage uses a large panel and places 32 smaller custom panels inside to display a bar graphic. The custom panel’s background is just green and I draw a rectangle on top to visualize the current value. Alternatively I could have used 32  progress bars but I liked to also have text inside the graphic. Here is the OnPaint override code:

        protected override void OnPaint(PaintEventArgs e)
        {
            //draw the background rectangle
            e.Graphics.FillRectangle(new SolidBrush(BackColor), 0, 0, (int)((float)(this.Width / _Maximum) * _Maximum), this.Height);
            //draw foreground rectangle
            if (scaleMode == scaleModeValue.relative)
            {
                e.Graphics.FillRectangle(new SolidBrush(ForeColor), 0, 0, (int)((float)(this.Width / _Maximum) * @Value), this.Height);
            }
            else
            {
                e.Graphics.FillRectangle(new SolidBrush(ForeColor), 0, 0, (int)@Value, this.Height);
            }
            //draw text
            if (Text.Length > 0)
            {
                StringFormat sf = new StringFormat();
                sf.Alignment=StringAlignment.Center;
                RectangleF rect = new RectangleF(0f, 0f, this.Width, this.Height);
                e.Graphics.DrawString(Text, base.Font, new SolidBrush(Color.Black), rect, sf);
            }
            base.OnPaint(e);
        }

When vmUsage is started it starts a background thread that captures the current memory usage data:

            //start the background tasks
            vmiThread = new vmInfoThread();
            vmiThread._iTimeOut = iTimeout*1000;
            vmiThread.updateEvent += new vmInfoThread.updateEventHandler(vmiThread_updateEvent);

In the background thread I am using two events and a queue to sync foreground and background working:

        public vmInfoThread()
        {
            _fileLogger = new Logging.fileLogger(Logging.utils.appPath + "vmusage.log.txt");

            eventEnableCapture = new AutoResetEvent(true);
            eventEnableSend = new AutoResetEvent(false);

            //procStatsQueue = new Queue<ProcessStatistics.process_statistics>();
            procStatsQueueBytes = new Queue<byte[]>();

            myThreadSocket = new Thread(socketThread);
            myThreadSocket.Start();

            myThread = new Thread(usageThread);
            myThread.Start();
        }

The queue is used to decouple the data capture and the data send functions. One thread captures the data into a queue and then releases the socket thread which reads the queued data and releases the data capture thread:

        /// <summary>
        /// build thread and process list periodically and fire update event and enqueue results for the socket thread
        /// </summary>
        void usageThread()
        {
            try
            {
                int interval = 3000;
                //rebuild a new mem usage info
                VMusage.CeGetProcVMusage vmInfo = new CeGetProcVMusage();

                while (!bStopMainThread)
                {
                    eventEnableCapture.WaitOne();
                    List<VMusage.procVMinfo> myList = vmInfo._procVMinfo; //get a list of processes and the VM usage
                    StringBuilder sbLogInfo = new StringBuilder();  //needed to merge infos for log

                    System.Threading.Thread.Sleep(interval);
                    uint _totalMemUse = 0;
                    long lTimeStamp = DateTime.Now.ToFileTimeUtc();

                    //send all data in one block
                    List<byte> buffer = new List<byte>();
                    buffer.AddRange(ByteHelper.LargePacketBytes);
                    foreach (VMusage.procVMinfo pvmi in myList)
                    {
                        pvmi.Time = lTimeStamp;
                        buffer.AddRange(pvmi.toByte());

                        _totalMemUse += pvmi.memusage;

                        if (!pvmi.name.StartsWith("Slot", StringComparison.InvariantCultureIgnoreCase))
                        {
                            //_fileLogger.addLog(pvmi.ToString());    //adds one row for each VM info
                            sbLogInfo.Append(pvmi.name + "\t" + pvmi.memusage.ToString() + "\t");
                        }
                    }                    
                    procStatsQueueBytes.Enqueue(buffer.ToArray());

                    onUpdateHandler(new procVMinfoEventArgs(myList, _totalMemUse));

                    //send MemoryStatusInfo
                    memorystatus.MemoryInfo.MEMORYSTATUS mstat = new memorystatus.MemoryInfo.MEMORYSTATUS();
                    if (memorystatus.MemoryInfo.GetMemoryStatus(ref mstat))
                    {
                        MemoryInfoHelper memoryInfoStat= new MemoryInfoHelper(mstat);

                        //send header
                        procStatsQueueBytes.Enqueue(ByteHelper.meminfostatusBytes);
                        //send data
                        procStatsQueueBytes.Enqueue(memoryInfoStat.toByte());

                        //log global memstatus
                        sbLogInfo.Append(
                            "total\t" + memoryInfoStat.totalPhysical.ToString() +
                            "\tfree\t" + memoryInfoStat.availPhysical.ToString() +
                            "\tvtotal\t" + memoryInfoStat.totalVirtual.ToString() +
                            "tvfree\t" + memoryInfoStat.availVirtual.ToString() +
                            "\tload\t" + memoryInfoStat.memoryLoad + "\t");
                    }

                    //write a log line
                    _fileLogger.addLog(sbLogInfo.ToString()+"\r\n");
                    procStatsQueueBytes.Enqueue(ByteHelper.endOfTransferBytes);
                    ((AutoResetEvent)eventEnableSend).Set();
                }//while true
            }
            catch (ThreadAbortException ex)
            {
                System.Diagnostics.Debug.WriteLine("ThreadAbortException: usageThread(): " + ex.Message);
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine("Exception: usageThread(): " + ex.Message);
            }
            System.Diagnostics.Debug.WriteLine("Thread ENDED");
        }

The call eventEnableCapture.WaitOne(); waits until the event is set by the socket thread. Immediately after this call a new memory usage dataset is loaded. The rest of the code converts the data to bytes and adds the byte buffer to a queue. Then another event is set to release the socketThread.

        /// <summary>
        /// send enqueued objects via UDP broadcast
        /// </summary>
        void socketThread()
        {
            System.Diagnostics.Debug.WriteLine("Entering socketThread ...");
            try
            {
                const int ProtocolPort = 3002;
                sendSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
                sendSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
                sendSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendBuffer, 32768);

                IPAddress sendTo = IPAddress.Broadcast;// IPAddress.Parse("192.168.128.255");  //local broadcast
                EndPoint sendEndPoint = new IPEndPoint(sendTo, ProtocolPort);

                //UdpClient udpC = new UdpClient("255.255.255.255", 1111);
                System.Diagnostics.Debug.WriteLine("Socket ready to send");

                while (!bStopSocketThread)
                {
                    //block until released by capture
                    eventEnableSend.WaitOne();
                    lock (lockQueue)
                    {
                        //if (procStatsQueue.Count > 0)
                        while (procStatsQueueBytes.Count > 0)
                        {
                            byte[] buf = procStatsQueueBytes.Dequeue();
                            if (ByteHelper.isEndOfTransfer(buf))
                                System.Diagnostics.Debug.WriteLine("sending <EOT>");

                            sendSocket.SendTo(buf, buf.Length, SocketFlags.None, sendEndPoint);
                            System.Diagnostics.Debug.WriteLine("Socket send " + buf.Length.ToString() + " bytes");
                            System.Threading.Thread.Sleep(2);
                        }
                    }
                    ((AutoResetEvent)eventEnableCapture).Set();
                }

            }
            catch (ThreadAbortException ex)
            {
                System.Diagnostics.Debug.WriteLine("ThreadAbortException: socketThread(): " + ex.Message);
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine("Exception: socketThread(): " + ex.Message);
            }
            System.Diagnostics.Debug.WriteLine("socketThread ENDED");
        }

The first call after eventEnableSend.WaitOne(); inside the socket thread locks the queue, so no other code is able to access it. This is not necessary here as we sync the access using the events but I left the lock to show an alternative for async queue access. The rest of the code inside socket thread just sends the bytes of the queue using UDP.

Minor protocol between sender and receiver

During the development of the code I found it better to send a large block of bytes instead of small packets sending each memory info separately. After some time I added some special packets to mark the end-of-transfer. These are usefull to let the receiver know about which packets will come. So I added a memoryStatusInfo packet that informs the receiver when a memory status packet is attached in contrast to the virtual memory information block.

    void MessageReceivedCallback(IAsyncResult result)
    {
        EndPoint remoteEndPoint = new IPEndPoint(0, 0);
        try
        {
            //all data should fit in one package!
            int bytesRead = receiveSocket.EndReceiveFrom(result, ref remoteEndPoint);
            //System.Diagnostics.Debug.WriteLine("Remote IP: " + ((IPEndPoint)(remoteEndPoint)).Address.ToString());

            byte[] bData = new byte[bytesRead];
            Array.Copy(recBuffer, bData, bytesRead);
            if (ByteHelper.isEndOfTransfer(bData))
            {
                System.Diagnostics.Debug.WriteLine("isEndOfTransfer");
                updateEndOfTransfer();// end of transfer
            }
            else if (ByteHelper.isMemInfoPacket(bData))
            {
                System.Diagnostics.Debug.WriteLine("isMemInfoPacket");
                try
                {
                    VMusage.MemoryInfoHelper mstat = new VMusage.MemoryInfoHelper();
                    mstat.fromByte(bData);

                    //System.Diagnostics.Debug.WriteLine(mstat.ToString());

                    updateMem(mstat);
                }
                catch (Exception) { }
            }
            else if(ByteHelper.isLargePacket(bData)){
                System.Diagnostics.Debug.WriteLine("isLargePacket");
                try
                {
                    List<procVMinfo> lStats = new List<procVMinfo>();
                    VMusage.procVMinfo stats = new VMusage.procVMinfo();
                    lStats = stats.getprocVmList(bData, ((IPEndPoint)(remoteEndPoint)).Address.ToString());
                    updateStatusBulk(lStats);
                    //foreach (procVMinfo pvmi in lStats)
                    //{
                    //    pvmi.remoteIP = ((IPEndPoint)(remoteEndPoint)).Address.ToString();
                    //    //System.Diagnostics.Debug.WriteLine( stats.dumpStatistics() );
                    //}
                }
                catch (Exception) { }

            }
            else
            {
                System.Diagnostics.Debug.WriteLine("trying vmUsagePacket...");
                try
                {
                    VMusage.procVMinfo stats = new VMusage.procVMinfo(bData);
                    stats.remoteIP = ((IPEndPoint)(remoteEndPoint)).Address.ToString();
                    //System.Diagnostics.Debug.WriteLine( stats.dumpStatistics() );
                    if (stats.Time == 0)
                        stats.Time = DateTime.Now.ToFileTimeUtc();
                    updateStatus(stats);
                }
                catch (Exception) { }
            }
        }
        catch (SocketException e)
        ...

In the above vmUsage Recvr code you can see the different branches for different packet types: EndOfTransfer, MemInfoPacket and isLargePacket.

Network only knows bytes

Using TCP/IP you can only transfer bytes and so my memory info classes all contain code to convert from to bytes – a basic serialization and de-serialization. The class files are shared between the Windows Mobile vmUsage and the Windows vmUsageRecvr code. Following is an example of the VMInfo class.

    /// <summary>
    /// holds the VM data of one process
    /// </summary>
    public class procVMinfo
    {
        public string remoteIP;
        public string name;
        public UInt32 memusage;
        public byte slot;
        public UInt32 procID;
        public long Time;
...
        public byte[] toByte()
        {
            List<byte> buf = new List<byte>();
            //slot
            buf.AddRange(BitConverter.GetBytes((Int16)slot));
            //memusage
            buf.AddRange(BitConverter.GetBytes((UInt32)memusage));
            //name length
            Int16 len = (Int16)name.Length;
            buf.AddRange(BitConverter.GetBytes(len));
            //name string
            buf.AddRange(Encoding.UTF8.GetBytes(name));
            //procID
            buf.AddRange(BitConverter.GetBytes((UInt32)procID));
            //timestamp
            buf.AddRange(BitConverter.GetBytes((UInt64)Time));

            return buf.ToArray();
        }
        public procVMinfo fromBytes(byte[] buf)
        {
            int offset = 0;

            //is magic packet?
            if (ByteHelper.isLargePacket(buf))
                offset += sizeof(UInt64);   //cut first bytes

            //read slot
            this.slot = (byte)BitConverter.ToInt16(buf, offset);
            offset += sizeof(System.Int16);

            UInt32 _memuse = BitConverter.ToUInt32(buf, offset);
            memusage = _memuse;
            offset += sizeof(System.UInt32);

            Int16 bLen = BitConverter.ToInt16(buf, offset);
            offset += sizeof(System.Int16);
            if (bLen > 0)
            {
                this.name = System.Text.Encoding.UTF8.GetString(buf, offset, bLen);
            }
            offset += bLen;
            this.procID = BitConverter.ToUInt32(buf, offset);

            offset += sizeof(System.UInt32);
            this.Time = (long) BitConverter.ToUInt64(buf, offset);

            return this;
        }

You see a lot of BitConverter calls. The fromByte function needs to keep track of the offset for reading following data.

vmUsageRecvr

The code uses also a queue to transfer data between background thread (RecvBroadcast) and the GUI.

        public frmMain()
        {
            InitializeComponent();
            //the plot graph
            c2DPushGraph1.AutoAdjustPeek = true;
            c2DPushGraph1.MaxLabel = "32";
            c2DPushGraph1.MaxPeekMagnitude = 32;
            c2DPushGraph1.MinPeekMagnitude = 0;
            c2DPushGraph1.MinLabel = "0";

            dataQueue = new Queue<VMusage.procVMinfo>();

            dataAccess = new DataAccess(this.dataGridView1, ref dataQueue);

            recvr = new RecvBroadcst();
            recvr.onUpdate += new RecvBroadcst.delegateUpdate(recvr_onUpdate);
            recvr.onUpdateBulk += new RecvBroadcst.delegateUpdateBulk(recvr_onUpdateBulk);
            recvr.onEndOfTransfer += new RecvBroadcst.delegateEndOfTransfer(recvr_onEndOfTransfer);

            recvr.onUpdateMem += new RecvBroadcst.delegateUpdateMem(recvr_onUpdateMem);
        }

As we have different packet types for global memory status, single and bulk virtual memory data, I implemented multiple delegates. One handler of is the bulk updater. It gets its data via the custom event arg which is a list of all virtual memory dat for all ‘slots’:

        void recvr_onUpdateBulk(object sender, List<VMusage.procVMinfo> data)
        {
            foreach (VMusage.procVMinfo pvmi in data)
                addData(pvmi);
        }

The data is then feed into the GUI using the addData call:

        delegate void addDataCallback(VMusage.procVMinfo vmdata);
        void addData(VMusage.procVMinfo vmdata)
        {
            if (this.dataGridView1.InvokeRequired)
            {
                addDataCallback d = new addDataCallback(addData);
                this.Invoke(d, new object[] { vmdata });
            }
            else
            {
                dataGridView1.SuspendLayout();
                //enqueue data to be saved to sqlite
                dataQueue.Enqueue(vmdata);

                if (bAllowGUIupdate)
                {
                    dataAccess.addData(vmdata);
                    //release queue data
                    dataAccess.waitHandle.Set();
                }
                dataGridView1.Refresh();
                dataGridView1.ResumeLayout();
            }
        }

You see we have to prepare for cross event calling. Then we suspend the refreshing of the datagrid. The sql data is updated using a queue to decouple the GUI and SQL data saving. dataAccess.addData adds the data to the dataset that is bound to the datagrid.

There is lot more of code inside, just take a look if you like.

Questions?

Leave me a comment if you have any questions.

Source Code

Source code can be loaded from github

Mobile development: show a small information window using WIN32 API

$
0
0

ShowWin

Sometimes you may need to display a small window to inform the user about what is going on. Although the scripting tools MortScript and nScript provide functions to show dialogs they can not show simple information windows.

ShowWin default colors

ShowWin default colors

ShowWin with a progress bar

ShowWin with a progress bar

ShowWin is nothing special but a nearly full configurable window to be used from cmd line tools. It just uses Win32 API calls, constants and structures as FindWindow, PostMessage, SendMessage, WM_COPYDATA, COPYDATASTRUCT, GetSystemMetrics, GetDesktopWindow, GetWindowRect, CreateWindowEx, ShowWindow, UpdateWindow, INITCOMMONCONTROLSEX, GetDeviceCaps, CreateFontIndirect, GetWindowDC, ReleaseDC, PROGRESS_CLASS, InvalidateRect, BeginPaint, CreatePen, SelectObject, Rectangle, SetBkMode, DrawText, EndPaint, SetTextColor, DeleteObject, GetKeyState and PostQuitMessage.

Basic WIN32 programming

Possibly you never wrote a native C windows application. Come on and dive into the basics. It is always good to know the basics even if one writes DotNet or JAVA code.

Supported arguments

 showWin -t "Text zum Anzeigen" -r 90 -g 80 -b 70 -s 8 -w 200 -h 50 -x 0 -y 0 -rt 200 -gt 20 -bt 20 -ti 10 -progr 30 -align left

 ARGS: 
 option/parameter:                meaning:                default:            limitations:
 -t "Text zum Anzeigen"           text to show            "Installing"        255 chars, no " inside, no line breaks, no tabs
 -r 90                            background color RED    255                    0-255
 -g 80                            background color GREEN    207                    0-255
 -b 70                            background color BLUE    0                    0-255
 -s 8                             font size in points        10                    7-24 points
 -w 200                           window width pixels        460                    100-screenwidth
 -h 50                            window height pixels    40                    menu bar height (ie 26pixels)
 -x 60                            window pos X            12                    0 + system window bordersize
 -y 60                            window pos Y            48                    0 + system taskbar bar height. Using 0;0 does not work nice on WM, win may be below taskbar
 -rt 200                          text color RED            0                    0-255
 -gt 20                           text color GREEN        0                    0-255
 -bt 20                           text color BLUE            0                    0-255

 -align center                    text alignment            left                center|left|right

 -ti 10                           timeout to autoclose    0                    no autoclose, min: 1 (second), max: 3600 = one hour

 -progr 10                        show with progress val    0                    no progressbar, max: 100
                                  the progressbar is appended at bottom of textwindow
 -prval                           update progress bar value                    no default, min=1, max=100

 -kill                            kill existing window, exit app

 -m "new message text"            replace text in window                        see -t

Argument parsing

Fortunately I found some code to split arguments supplied to int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow). But i had to adopt the class to be unicode compatible. Windows CE and Windows Mobile uses Unicode for strings.

You know, that the WinMain is the first function called by the OS when you start an application.

int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPTSTR    lpCmdLine,
                   int       nCmdShow)
{
    LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); 
    LoadString(hInstance, IDC_SHOWWIN, szWindowClass, MAX_LOADSTRING);

    HWND hwndOld=FindWindow(szWindowClass,NULL);
    // kill request?
    if(hwndOld!=NULL){
        if(wcsicmp(lpCmdLine, L"-kill")==0){
            PostMessage(hwndOld, WM_QUIT, 0, 0);
            return 11;
        }
    }

    DEBUGMSG(1, (L"CmdLine parsing #1: \r\n"));
    //command parsing
    struct cmdList *Liste;
    Liste=NULL;
    CmdLineArgs args;
    for (UINT i = 0; i < args.size(); i++){
        DEBUGMSG(1, (L"%20i: '%s'\r\n", i, args[i]));
        append(&Liste, args[i]);
    }
    getOptions(Liste);
    args.~CmdLineArgs();

First the code uses LoadString to load the title and window class from its resources. Then FindWindow is used to look for a previous instance and checks if the only argument is -kill. If so, the previous instance is sent a quit using PostMessage and then the application exits itself.

Now we define a structure (Liste) to hold the arguments. Then we start command line parsing by creating a chained list of arguments. The list is created by using the class CmdLineArgs. We then walk thru the argument list and append each argument to our Liste structure. Using getOptions(Liste) we scan the list for known optional arguments and apply optional values to global variables.

class CmdLineArgs : public std::vector<TCHAR*>
{
public:
    CmdLineArgs ()
    {
        // Save local copy of the command line string, because
        // ParseCmdLine() modifies this string while parsing it.
        TCHAR* cmdline = GetCommandLine();
        m_cmdline = new TCHAR [_tcslen (cmdline) + 1];
        if (m_cmdline)
        {
            _tcscpy (m_cmdline, cmdline);
            ParseCmdLine(); 
        }
    }
    ~CmdLineArgs()
    {
        delete []m_cmdline;
    }
...

getOptions is a chain of if/else if blocks that tests for known options and applies values to global variables:

void getOptions(struct cmdList *l){
    struct cmdList *liste;
    liste=l;
    if(l==NULL)
        return;
    int iVal;
    do{
        DEBUGMSG(1, (L"%s\r\n", liste->value));
        if(wcsicmp(liste->value, L"-t")==0){        // message text
            if(liste->next != NULL){
                liste=liste->next;
                wsprintf(szMessageText, L"%s", liste->value);
            }
        }
        else if(wcsicmp(liste->value, L"-m")==0){        // message text
            if(liste->next != NULL){
                liste=liste->next;
                wsprintf(szMessageTextNew, L"%s", liste->value);
            }
        }
        else if(wcsicmp(liste->value, L"-r")==0){    // rgb r value
            if(liste->next != NULL){
                liste=liste->next;
                iVal=_wtoi(liste->value);
                if(iVal!=0)
                    backcolorRed=iVal;
            }
        }
...
        liste=liste->next;
    }while(liste != NULL);
}

Inter process communication

Now that we have read all arguments, we can test if we need to update the text or progress value of an existing instance:

    // already running?
    if(hwndOld!=NULL){
        //check if new message text?
        if(wcslen(szMessageTextNew) > 0){
            myMsg _mymsg;
            memset(&_mymsg,0,sizeof(myMsg));
            wsprintf( _mymsg.szText, L"%s", szMessageTextNew );
            _mymsg.iVal=0;    //the text message identifier
            //prepare WM_COPYDATA
            COPYDATASTRUCT copyData;
            copyData.dwData=1234;
            copyData.cbData=sizeof(myMsg);
            copyData.lpData=&_mymsg;
            SendMessage(hwndOld, WM_COPYDATA, (WPARAM)NULL, (LPARAM)&copyData);
        }
        if(iProgressValNew!=-1){
            myMsg _mymsg;
            memset(&_mymsg,0,sizeof(myMsg));
            wsprintf( _mymsg.szText, L"%i", iProgressValNew );
            _mymsg.iVal=1;    //the progress message identifier
            //prepare WM_COPYDATA
            COPYDATASTRUCT copyData;
            copyData.dwData=1234;
            copyData.cbData=sizeof(myMsg);
            copyData.lpData=&_mymsg;
            SendMessage(hwndOld, WM_COPYDATA, (WPARAM)NULL, (LPARAM)&copyData);
        }
        ShowWindow(hwndOld, SW_SHOWNORMAL);
        return -1;
    }

To let the ‘old’ window update its text or progress bar we need to use inter-process communication. The simplest one supporting custom data (text, progress value) I found was using the WM_COPYDATA message. To use that, we have to define a structure that holds our data (here myMsg is used) and then assign the filled data structure to lpData of a COPYDATASTRUCT variable. Then we send the data to the windows handle of the existing instance using SendMessage. The asynchronous PostMessage does not work, the data must be available on the sender side when the message is received by the target window. SendMessage will block until the message has been delivered and so the data can be transfered between sender and target. Finally the previous instance will be shown and the actual launched will quit (return -1;).

On the receiver side (same application but second instance) we have to decode the WM_COPYDATA message.

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
...
    switch (message) 
    {
...
        case WM_COPYDATA:
            copyData=(COPYDATASTRUCT*)lParam;
            myMsg _mymsg;
            if(copyData->dwData==1234)    //that's right
            {
                memcpy(&_mymsg, copyData->lpData, sizeof(myMsg));
            }
            if(_mymsg.iVal==0){        //text message
                if(wcslen(_mymsg.szText)>0)
                    wcscpy(szMessageText, _mymsg.szText);
                GetClientRect(hWnd, &rect);
                InvalidateRect(hWnd, &rect, TRUE);
            }
            else if(_mymsg.iVal==1){        //progress message
                if(wcslen(_mymsg.szText)>0)
                    wcscpy(szTemp, _mymsg.szText);
                iProgressVal=_wtoi(szTemp);
                SendMessage(hProgress, PBM_SETPOS, iProgressVal, 0);
            }
            break;

The above code shows how we get the data back from the message lParam parameter. The structure myMsg knows actually two types of data: a progress value or a new text. Depending on the message type we either update the global variable szMessageText or iProgressVal. After changing the text we inform the OS that our window needs to be updated (painted again). If a new progress value has been received we just need to send the progress bar the new value using SendMessage(hProgress, PBM_SETPOS, iProgressVal, 0);.

Adopt to available screen size

Back to our application winMain startup code. The next code lines query the device for screen size and xxx

    //client size
    int maxX = GetSystemMetrics(SM_CXSCREEN);
    int maxY = GetSystemMetrics(SM_CYSCREEN);        //640 ??
    int borderSize = GetSystemMetrics(SM_CXBORDER);
    int minSize = GetSystemMetrics(SM_CYMENU);
    RECT rectMax;
    GetWindowRect(GetDesktopWindow(), &rectMax);
    if(xWidth<100 || xWidth>maxX)    // secure width setting
        xWidth=maxX-2*borderSize;
    if(yHeight<minSize)
        yHeight=minSize+2*borderSize;

    if(xPos<borderSize)    //secure x pos
        xPos=borderSize;
    if(yPos<rectMax.top)    //secure y pos
        yPos=rectMax.top;

    //progressBar is attached to bottom of window
    if(bUseProgress){
        //extend window
        xProgressWidth=xWidth;
        yHeight+=yProgressHeight;
        yProgress=yHeight-yProgressHeight;

    }

SM_CXSCREEN and SM_CYSCREEN let us know the width and height of the screen in pixels. As we want to limit the window creation to usual values, I also query the system value of border width (SM_CXBORDER) and the menu height (SM_CYMENU).
Using GetWindowRect we query for the maximum client area of the ‘desktop’ window. Then we adjust the given width and height value to usable values. We do the same for the x and y position of the window.

If a progressbar is to be used (determined by parsing the command line arguments), we need to extend the specified window at the bottom to reserve place for the bar.
The remainder of winMain is standard and initializes the window and starts the message loop.

    // Perform application initialization:
    if (!InitInstance(hInstance, nCmdShow)) 
    {
        return FALSE;
    }

    HACCEL hAccelTable;
    hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_SHOWWIN));

    // Main message loop:
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0)) 
    {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) 
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    return (int) msg.wParam;

Inside InitInstance() there is a call to myRegisterClass(). As I like to have a ‘backdoor’ to quit the ShowWin app, I added CS_DBLCLKS. Without that style attribute the window will otherwise not get double click messages!:

ATOM MyRegisterClass(HINSTANCE hInstance, LPTSTR szWindowClass)
{
    WNDCLASS wc;

    hBackcolor = CreateSolidBrush(RGB(backcolorRed,backcolorGreen,backcolorBlue));

    wc.style         = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
    wc.lpfnWndProc   = WndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_SHOWWIN));
    wc.hCursor       = 0;
    wc.hbrBackground = (HBRUSH) hBackcolor;// GetStockObject(WHITE_BRUSH);
    wc.lpszMenuName  = 0;
    wc.lpszClassName = szWindowClass;

    return RegisterClass(&wc);
}

In InitInstance we apply some special wishes for the window z-order: WS_EX_ABOVESTARTUP and WS_EX_TOPMOST. The code also does not use window defaults for size and position as these would result in a maximized window but we want to show only a small window.

    hWnd = CreateWindowEx( 
        WS_EX_TOPMOST | WS_EX_ABOVESTARTUP,    //exStyle
        szWindowClass,    //wndClass
        NULL, //L"Installer",    //title
        WS_VISIBLE, //dwStyle
        xPos,    // CW_USEDEFAULT,  //x
        yPos,    //CW_USEDEFAULT,  //y    
        xWidth,  //CW_USEDEFAULT,  //width
        yHeight, //CW_USEDEFAULT,  //height
        NULL,    //hParent
        NULL,    //hMenu
        hInstance,
        NULL
        );

After all this stuff the window class is registered and the window will be created using our settings. Now the magic starts and the windows message proc is called by the message loop. The first message we will see is WM_CREATE.

        case WM_CREATE:
            //do font calculation
            hdc=GetWindowDC(hWnd);
            iDevCap=GetDeviceCaps(hdc, LOGPIXELSY);    //pixels per inch
            lfHeight = -((long)fontHeight * (long)iDevCap) / 72L;
            GetObject (GetStockObject (SYSTEM_FONT), sizeof (LOGFONT), (PTSTR) &logfont) ;
            //    HFONT hf = CreateFontIndirect(&logfont);
            logfont.lfHeight=lfHeight;
            hFont=CreateFontIndirect(&logfont);
            ReleaseDC(NULL,hdc);

            DEBUGMSG(1, (L"Create hWnd=%i\r\n", hWnd));
            if(iTimeOut>0)
                startThread(hWnd);

            if(bUseProgress){
                //progressBar
                hProgress = CreateWindowEx(0, PROGRESS_CLASS, NULL,
                                WS_CHILD | WS_VISIBLE,
                                xProgress, yProgress, xProgressWidth, yProgressHeight,
                                hWnd, NULL, g_hInst, NULL);
                SendMessage(hProgress, PBM_SETRANGE, 0, MAKELPARAM(0, 100));
                SendMessage(hProgress, PBM_SETPOS, iProgressVal, 0);
            }
            break;

The above tries to calculate the right size for a LOGFONT structure for the text drwan in the window. We need to know the number of pixels per inch of the screen – the dots-per-inch resolution. The font size argument you can use on the command line is to be given in points. A point is a 1/72 of an inch (or given at 72 dpi). We calculate the logical font height by relating the screen dpi and the font size dpi. Then the code gets the LOGFONT structure of the system font and applies the new logical font size. Finally the global variable hFont is initialized with the logfont structure.

If an optional timeout value was supplied via the cmd line, the line startThread() will be executed. That starts a background thread that will post a quit message to the message loop when the timeout value is reached:

DWORD myThread(LPVOID lpParam){
    BOOL bExit=FALSE;
    HWND hwndMain=(HWND)lpParam;
    DWORD dwWaitResult=0;
    int iCountSeconds=0;
    DEBUGMSG(1, (L"myThread hWndMain=%i\r\n", hwndMain));
    do{
        dwWaitResult = WaitForSingleObject(hStopThread, 1000);
        switch(dwWaitResult){
            case WAIT_OBJECT_0:
                bExit=TRUE;
                break;
            case WAIT_TIMEOUT:
                iCountSeconds++;
                if(iCountSeconds>=iTimeOut)
                {
                    PostMessage(hwndMain, WM_QUIT, 99, iTimeOut);
                    bExit=TRUE;
                }
                break;
        }
    }while(!bExit);
    return 0;
}

I am using WaitForSingleObject() here to be able to stop the thread by setting a named event.

Back in WM_CREATE the last lines are to create a progressBar, if the optional argument for a progress bar was used.

The next message of importance is WM_PAINT. All drawing of the window is done within the WM_PAINT handler.

        case WM_PAINT:
            hdc = BeginPaint(hWnd, &ps);

            // TODO: Add any drawing code here...
            GetClientRect(hWnd, &rect);
            //shrink text area if progressbar is there
            if(bUseProgress && hProgress!=NULL){
                rect.bottom-=yProgressHeight;
            }

the above resized the drawing rectangle for text if a progress bar is used.

Next we draw some black rectangles to give the window a simple drop shadow effect.

            //draw rectangle
            myPen = CreatePen(PS_SOLID, 1, RGB(0,0,0));
            oldPen = (HPEN)SelectObject(hdc,myPen);
            SelectObject(hdc, hBackcolor);
            Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom);
            //shrinkRect(&rect, 1);
            //Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom);
            //a drop shadow
            rect.right-=1;rect.bottom-=1;
            Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom);
            rect.right-=1;rect.bottom-=1;
            Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom);
            SelectObject(hdc, oldPen);

Then we set the background mode to transparent and draw the text and finally restore font and text color.

            SetBkMode(hdc, TRANSPARENT);
            oldTextColor = SetTextColor(hdc, RGB(fontColorRed, fontColorGreen, fontColorBlue));
            hfOld=(HFONT)SelectObject(hdc, hFont);
            DrawText(hdc, 
                szMessageText,    //text to draw
                -1,                //length of text
                &rect, 
                dwTextalign | DT_END_ELLIPSIS | DT_EXTERNALLEADING | DT_VCENTER // | DT_SINGLELINE        //text formatting
                );

            EndPaint(hWnd, &ps);
            SelectObject(hdc, hfOld);
            SetTextColor(hdc, oldTextColor);

            DeleteObject(hFont);
            break;

the backdoor to quit

You can quit ShowWin by calling it with ‘-kill’. You can also end ShowWin by double clicking inside the window with the CAPS Lock key being toggled:

        case WM_LBUTTONDBLCLK:
            vkShift=GetKeyState(VK_CAPITAL);
            if( (vkShift & 0x80) == 0x80 || (vkShift & 0x01) == 0x01 ){
                if(MessageBox(hWnd, L"Exit?", L"showWin", MB_OKCANCEL)==IDOK)
                    DestroyWindow(hWnd);
            }
            break;

MessageBox shows a verification dialog and DestroyWindow exits the application.

The End

As we are running a background thread it is a good idea to stop the thread before the application ends. The below code shows the SetEvent call that releases the background thread’s WaitForSingleObject() call.

void stopThread(){
    if(hStopThread==NULL)
        SetEvent(hStopThread);
}
.......
        case WM_DESTROY:
            stopThread();
            PostQuitMessage(0);
            break;
...

Source code download

Full source code available at GitHub.

 


Mobile Development: showFullScreen, a tool to change foreign windows attributes

$
0
0

Hello

attached is a small tool to alter foreign windows. You can show/hide, enable/disable and resize windows.

One example is to disable the taskbar window: showFullScreen -disable -class “HHTASKBAR”

How to launch

Here is a list of possible arguments:

Arguments:                default          meaning
-class                    ""               class name of window to change, optional
-title                    ""               title of window to change, optional
            at least -class or -title must be used to change a window

-fullscreen               false            make window fullscreen
-maximized                false            make window normal size

-show                     no change        make window visible
-hide                     no change        make window invisible

-enable                   no change        enable window
-disable                  no change        disable window

-list                    -                 list windows into file

Examples

And here some more examples for usage:

Enable IE6 soft menu button:    -class "IE6on6SoftKeyBar" -enable
Disable IE6 soft menu button:    -class "IE6on6SoftKeyBar" -disable

Enable taskbar:        -class "HHTASKBAR" -enable
Disable taskbar:    -class "HHTASKBAR" -disable

make win fullscreen: -class "WFIcaClient" -fullscreen

List windows (like remote spy does)

Please use -list argument, to get a list of running windows:

0x55889c2a: showFullScreen utility v 1.0 * Date and Time: 07.03.2013 14:55:36
0x55889c2a: Window List
this    nr    hwnd    procID    procName    class    title    pos/size    state
0x55889c2a: 1    0x7c072d30    0x5aaef4a6    ('gwes.exe')    '@MSSCOPE'    'SCOPE'    0;0/480;640 (480x640)    [hidden]
0x55889c2a: 2    0x7c070bb0    0x5aaef4a6    ('gwes.exe')    'static'    'CursorWindow'    200;280/280;360 (80x80)    [hidden]
0x55889c2a: 3    0x7c073680    0x7a451c12    ('shell32.exe')    'HHTaskBar'    ''    0;0/480;36 (480x36)    [visible]
0x55889c2a: 4    0x7c071880    0x3c8a25d2    ('device.exe')    'SipBackDropWndClass'    ''    0;412/480;572 (480x160)    [hidden]
0x55889c2a: 5    0x7c071780    0x3c8a25d2    ('device.exe')    'SipWndClass'    ''    0;412/480;572 (480x160)    [hidden]
0x55889c2a: 6    0x7c073880    0x7a451c12    ('shell32.exe')    'MS_SIPBUTTON'    'MS_SIPBUTTON'    206;572/274;640 (68x68)    [hidden]
0x55889c2a: 7    0x7c076b80    0xdc8eb29a    ('filesys.exe')    'ms_sqlce_se_notify_wndproc'    'ms_sqlce_se_notify_wndproc'    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 8    0x7c075ae0    0x7a451c12    ('shell32.exe')    'tooltips_class32'    ''    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 9    0x7c0758f0    0x7a451c12    ('shell32.exe')    'menu_worker'    ''    0;572/480;640 (480x68)    [visible]
0x55889c2a: 10    0x7c08e930    0x7a451c12    ('shell32.exe')    'WimData'    'WimData'    10;10/20;20 (10x10)    [hidden]
0x55889c2a: 11    0x7c084bf0    0xd9528c2a    ('cprog.exe')    'tooltips_class32'    ''    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 12    0x7c0842d0    0xd9528c2a    ('cprog.exe')    'menu_worker'    ''    0;572/480;640 (480x68)    [hidden]
0x55889c2a: 13    0x7c07db60    0xd9528c2a    ('cprog.exe')    'CEStockWnd-17e492c'    ''    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 14    0x7c07da20    0xd9528c2a    ('cprog.exe')    'CE_UTILWND-17e0908'    ''    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 15    0x7c07ae80    0xd9528c2a    ('cprog.exe')    'XME Notify Window 98B84234'    ''    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 16    0x7c084060    0xd9528c2a    ('cprog.exe')    'Dialog'    'Phone - Incoming'    -4;214/484;572 (488x358)    [hidden]
0x55889c2a: 17    0x7c075670    0x7a451c12    ('shell32.exe')    'DesktopExplorerWindow'    'Desktop'    0;36/480;640 (480x604)    [visible]
0x55889c2a: 18    0x7c0d1c90    0x77404a5a    ('Notes.exe')    'COMPIMEUI'    'COMPIMEUI'    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 19    0x7c09eb80    0x77404a5a    ('Notes.exe')    'Ime'    'Default Ime'    0;0/1;1 (1x1)    [hidden]
0x55889c2a: 20    0x7c0d05a0    0x77404a5a    ('Notes.exe')    'tooltips_class32'    ''    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 21    0x7c0d0030    0x77404a5a    ('Notes.exe')    'tooltips_class32'    ''    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 22    0x7c0d1d80    0x77404a5a    ('Notes.exe')    'menu_worker'    ''    0;572/480;640 (480x68)    [visible]
0x55889c2a: 23    0x7c09e850    0x77404a5a    ('Notes.exe')    'Notes'    'Notes'    0;36/480;572 (480x536)    [visible]
0x55889c2a: 24    0x7c0d1680    0xbb46d6ba    ('remote.exe')    'MyMobiler'    'MyMobiler'    0;36/480;640 (480x604)    [hidden]
0x55889c2a: 25    0x7c07f010    0x7a451c12    ('shell32.exe')    'tooltips_class32'    ''    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 26    0x7c07ac20    0x7a451c12    ('shell32.exe')    'menu_worker'    ''    0;572/480;640 (480x68)    [visible]
0x55889c2a: 27    0x7c07b850    0x7a451c12    ('shell32.exe')    'MSSTARTMENU'    'System'    0;36/480;640 (480x604)    [visible]
0x55889c2a: 28    0x7c09d610    0x3aceae5a    ('rapiclnt')    'OLEAUT32'    ''    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 29    0x7c08dd50    0x96a75c96    ('tmail.exe')    'tooltips_class32'    ''    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 30    0x7c08d900    0x96a75c96    ('tmail.exe')    'menu_worker'    ''    0;572/480;640 (480x68)    [hidden]
0x55889c2a: 31    0x7c09cd70    0x96a75c96    ('tmail.exe')    'tooltips_class32'    ''    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 32    0x7c09c700    0x96a75c96    ('tmail.exe')    'Mapi.MainWnd'    ''    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 33    0x7c09c5b0    0x96a75c96    ('tmail.exe')    'Inbox.MainWnd'    'Messaging'    0;36/480;572 (480x536)    [hidden]
0x55889c2a: 34    0x7c09b9d0    0x3aceae5a    ('rapiclnt')    'RapiClnt'    'Remote API Client'    0;0/1;1 (1x1)    [hidden]
0x55889c2a: 35    0x7c089d10    0x16abe79e    ('IntermecSettings.exe')    'tooltips_class32'    ''    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 36    0x7c07d860    0x16abe79e    ('IntermecSettings.exe')    'menu_worker'    ''    0;572/480;640 (480x68)    [visible]
0x55889c2a: 37    0x7c089ee0    0x16abe79e    ('IntermecSettings.exe')    '#NETCF_AGL_BASE_'    'Intermec Settings'    0;36/480;572 (480x536)    [visible]
0x55889c2a: 38    0x7c08b5b0    0x16abe79e    ('IntermecSettings.exe')    'COMPIMEUI'    'COMPIMEUI'    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 39    0x7c08b4a0    0x16abe79e    ('IntermecSettings.exe')    'Ime'    'Default Ime'    0;0/1;1 (1x1)    [hidden]
0x55889c2a: 40    0x7c089600    0x16abe79e    ('IntermecSettings.exe')    '#NETCF_AGL_PARK_\Windows\IntermecSettings.exe'    ''    -2000;-2000/-2000;-2000 (0x0)    [hidden]
0x55889c2a: 41    0x7c087a60    0xf6b37482    ('repllog.exe')    'PolApp Window'    ''    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 42    0x7c0875e0    0xf6b37482    ('repllog.exe')    'Mapi.MainWnd'    ''    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 43    0x7c084780    0x7a451c12    ('shell32.exe')    'SSUpdate.MainWnd'    ''    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 44    0x7c0873a0    0xf6b37482    ('repllog.exe')    'tooltips_class32'    ''    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 45    0x7c0871b0    0xf6b37482    ('repllog.exe')    'menu_worker'    ''    0;572/480;640 (480x68)    [visible]
0x55889c2a: 46    0x7c087080    0xf6b37482    ('repllog.exe')    'tooltips_class32'    ''    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 47    0x7c086d80    0xf6b37482    ('repllog.exe')    'ActiveSync'    'ActiveSync'    0;36/480;572 (480x536)    [visible]
0x55889c2a: 48    0x7c084880    0xd9528c2a    ('cprog.exe')    'PolApp Window'    ''    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 49    0x7c07f200    0xd9528c2a    ('cprog.exe')    'Worker'    ''    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 50    0x7c07dca0    0xd9528c2a    ('cprog.exe')    'XMEThreadPlayer Wnd Class'    ''    0;36/480;640 (480x604)    [hidden]
0x55889c2a: 51    0x7c07d640    0x7a451c12    ('shell32.exe')    'SIMInit'    ''    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 52    0x7c07cf60    0x7a451c12    ('shell32.exe')    'Mapi.MainWnd'    ''    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 53    0x7c07cb30    0x7a451c12    ('shell32.exe')    'Worker'    ''    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 54    0x7c07ca40    0x7a451c12    ('shell32.exe')    'Worker'    ''    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 55    0x7c07c610    0x7a451c12    ('shell32.exe')    'Worker'    ''    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 56    0x7c07c950    0x1a2de4a6    ('services.exe')    'WLMContactsPlugin.WndClass'    ''    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 57    0x7c07a9b0    0xd9528c2a    ('cprog.exe')    'Worker'    ''    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 58    0x7c07a380    0xd9528c2a    ('cprog.exe')    'tooltips_class32'    ''    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 59    0x7c079940    0xd9528c2a    ('cprog.exe')    'tooltips_class32'    ''    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 60    0x7c079750    0xd9528c2a    ('cprog.exe')    'menu_worker'    ''    0;-100/0;-100 (0x0)    [visible]
0x55889c2a: 61    0x7c079530    0xd9528c2a    ('cprog.exe')    'PolApp Window'    ''    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 62    0x7c0790d0    0xd9528c2a    ('cprog.exe')    'CallPol'    ''    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 63    0x7c078fe0    0xd9528c2a    ('cprog.exe')    'MSCprog'    'Phone '    0;36/480;572 (480x536)    [hidden]
0x55889c2a: 64    0x7c078280    0xb93a2eea    ('connmgr.exe')    'ConnMgrWndClass'    'SchedConnNotify'    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 65    0x7c077fc0    0xb93a2eea    ('connmgr.exe')    'StatStore Worker'    ''    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 66    0x7c077cc0    0xb93a2eea    ('connmgr.exe')    'StatStore Worker'    ''    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 67    0x7c077bc0    0xb93a2eea    ('connmgr.exe')    'ConnMgrWndClass'    ''    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 68    0x7c072950    0xb93a2eea    ('connmgr.exe')    'StatStore Worker'    ''    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 69    0x7c072300    0xb93a2eea    ('connmgr.exe')    'ConnMgrWndClass'    ''    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 70    0x7c0721f0    0xb93a2eea    ('connmgr.exe')    'ConnMgrWndClass'    'ConnMgrSink'    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 71    0x7c076e30    0x5aaef4a6    ('gwes.exe')    'TOUCHSTUBWND_CLASS'    ''    0;0/1;1 (1x1)    [hidden]
0x55889c2a: 72    0x7c076530    0x1a2de4a6    ('services.exe')    'Worker'    ''    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 73    0x7c0761b0    0x395a84c2    ('AAM.exe')    ''    ''    0;36/480;640 (480x604)    [hidden]
0x55889c2a: 74    0x7c074d40    0x1a2de4a6    ('services.exe')    'Worker'    ''    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 75    0x7c0744b0    0x1a2de4a6    ('services.exe')    'WinCENotify'    'WinCENotify'    0;36/480;640 (480x604)    [hidden]
0x55889c2a: 76    0x7c074100    0x3c8a25d2    ('device.exe')    'SinkWiredNetUI'    ''    -10;-10/-5;-5 (5x5)    [hidden]
0x55889c2a: 77    0x7c079e80    0xd9528c2a    ('cprog.exe')    'tooltips_class32'    ''    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 78    0x7c079c90    0xd9528c2a    ('cprog.exe')    'menu_worker'    ''    0;572/480;640 (480x68)    [visible]
0x55889c2a: 79    0x7c079b80    0xd9528c2a    ('cprog.exe')    'MSClog'    'Phone '    0;36/480;572 (480x536)    [hidden]
0x55889c2a: 80    0x7c07b5a0    0x98ffa152    ('poutlook.exe')    'MSPOutlook'    ''    100000;0/100000;0 (0x0)    [visible]
0x55889c2a: 81    0x7c07f980    0xd9528c2a    ('cprog.exe')    'tooltips_class32'    ''    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 82    0x7c07f790    0xd9528c2a    ('cprog.exe')    'menu_worker'    ''    0;572/480;640 (480x68)    [visible]
0x55889c2a: 83    0x7c07f6a0    0xd9528c2a    ('cprog.exe')    'tooltips_class32'    ''    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 84    0x7c07e4a0    0xd9528c2a    ('cprog.exe')    'Dialog'    'Phone '    0;36/480;572 (480x536)    [hidden]
0x55889c2a: 85    0x7c081b60    0xd9528c2a    ('cprog.exe')    'tooltips_class32'    ''    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 86    0x7c081970    0xd9528c2a    ('cprog.exe')    'menu_worker'    ''    0;572/480;640 (480x68)    [visible]
0x55889c2a: 87    0x7c07fce0    0xd9528c2a    ('cprog.exe')    'Dialog'    'Phone '    0;36/480;572 (480x536)    [hidden]
0x55889c2a: 88    0x7c083c90    0xd9528c2a    ('cprog.exe')    'tooltips_class32'    ''    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 89    0x7c083aa0    0xd9528c2a    ('cprog.exe')    'menu_worker'    ''    0;572/480;640 (480x68)    [visible]
0x55889c2a: 90    0x7c082f40    0xd9528c2a    ('cprog.exe')    'tooltips_class32'    ''    0;0/0;0 (0x0)    [hidden]
0x55889c2a: 91    0x7c082120    0xd9528c2a    ('cprog.exe')    'Dialog'    'Phone '    0;36/480;572 (480x536)    [hidden]

Download Visual Studio 2008 sources (Mobile 5 SDK, C/C++):
showFullScreen_v1

Windows Mobile 6.5: Changed Screen Geometry

$
0
0

Screen layout changes from Windows Mobile 6.1 to Windows Mobile Embedded Handheld 6.5

The Windows Mobile screen geometry changed from Windows Mobile 6.1 and before to the actual Windows Mobile 6.5 (also called Windows Embedded Handheld). Not only the geometry changed, the layout also changed. The Start icon is now moved to the bottom whereas before WM65 the start icon was on the left in the taskbar.

wm61screen   wm65screen

The taskbar and the menubar was about 26 pixels in height. With WM65 the taskbar is about 18 pixels in height and the menu bar occupies 34 pixels in height.

QVGA screen geometry

Windows Mobile 6.1

Windows Mobile 6.5

taskbar

26

18

menubar

26

34

client size

240;268

240;268

client size height no taskbar

240;294

240;302

client size height no menubar

240;294

240;286

You can see that assuming a fixed client size will give problems with the layout of the application, especially if menubar and taskbar height are assumed as being 26 pixels all the time.

Applications that only use the client size with the taskbar and menubar visible, will show normally, as the resulting client size does not differ between WM61 and WM65.

Application geometry

Applications that use any code to make there window more or less fullscreen have to adopt for the new layout of the screen. Applications that do not use code to adopt there screen to the new layout may look like this:

geometry_wm61_QVGA_sample1   geometry_wm65_QVGA_sample1

The screen moves slightly outside top assuming 26 pixels for taskbar and menubar and so the ‘fullscreen’ app does not not cover the bottom menu bar completely.

If an app has used correct coding it would display better on WM65 (wrong coded Citrix 11.5 here):

geometry_wm65_VGA__sample5    geometry_wm65_VGA__sample5_showfullscreen

High Resolution awareness

Applications

A long time nearly all Windows Mobile device came with a QVGA screen resolution only. Now you may be faced by a Windows Mobile VGA screen resolution, whereas consumer smartphones come with many different screen resolutions.

Windows Mobile 6 will adopt non-hires aware applications automatically to the screen resolution. The OS looks for a resource named HIRES_AWARE in the application executable and for the major subsystem version number being less than 5.

If an app does not have the HIRES_AWARE and the major version number is less than 5, the Windows Mobile OS automatically scales the app to the screen size.

The ‘auto-scale’ uses the dpi values for scaling. The screen sizes (the screen diagonal) is mostly similar. So a VGA screen has more pixels per inch (ie 192dpi, dots-per-inch). Whereas a QVGA screen has about 96 pixels per inch (96dpi).

Dotnet AutoscaleMode

In Visual Studio you can set the FormFactor and Autscale-Mode (auto, none) for a form:

dotnet_dpi_auto  dotnet_formfactor

If AutoScale is set to dpi, everything is OK, the form ‘scales’ automatically. If set to None, the form content is not scaled:

dotnet_form_dpi_auto   dotnet_form_dpi_none

If you start desiging the form with QVGA form factor and switch then to VGA form factor, you can see the change directly:

dotnet_formfactor_vga

Browser applications and VGA screen

Internet browser applications must avoid using fixed pixel count for there screen layout. You can imagine what happens if a element like a table is specified for 240 pixels wide on a VGA screen supporting 480 pixels:

geometry_wm65_VGA__sample3

More Sample screens

Windows Mobile 6.1 QVGA

without and with menubar/taskbar:

geometry_wm61_QVGA_noMenu   geometry_wm61_QVGA_noTaskbar

Windows Mobile 6.5 QVGA

geometry_wm65_QVGA_noMenu   geometry_wm65_QVGA_noTaskbar

Windows Mobile 6.5 VGA

geometry_wm65_VGA__noMenu   geometry_wm65_VGA__noTaskbar

 

Mobile Programming – Cmd line tools for automated tests

$
0
0

Recently I had to perform an automated stress test for a compact framework application using the camera. I remembered MortScript (original web site down) and wrote a script to automate the test.

Now, as there are sometimes more needs to automate tests, for example for battery performance tests, I decided to mimic the motorola emScript environment. emScript offers a lot of functions you may need on other devices too. Instead of putting all functions into one executable, I decided to have extern command line driven tool set. So I started to develop small tools to perform device functions.

scannerCLI

The first tool is scannerCLI. This tool issues a barcode scan on an intermec handheld device:

Just issue the scanner for a max time of supplied arg default timeout is 1 second:
"ScannerCLI.exe x" will issue a barcode scan with a timeout of x seconds.
x must be > 0 and < 10.
"ScannerCLI.exe" will issue a barcode scan for max 1 second. The scan will be stopped immediately if a barcode is read.
The return code is 0 for no barcode read, 1 for one or more read and negative for an error in barcode scan.

iBacklIghtCLI

This tool is to control or query the backlight settings of intermec devices. As you know the backlight is one of the most battery consuming setting.

arguments  function
on         switch backlight on using current brightness
off        switch backlight off
max        switch backlight brightness to max and on
min        switch backlight brightness to min and on

state      return 0 for is OFF, 1 for ON, -1 for error
level      return current brightness level or -1 for error

1 to max level 
           set new brightness level, returns new level or -1 for 
           error (ie val exceeds max level)

More tools will added here…

The source code for all published tools is be available at code.google.com.

http://code.google.com/p/win-mobile-code/source/browse/#svn%2Ftrunk%2Fcli-tools

Mobile development: pocketHosts-Edit Windows Mobile hosts entries

$
0
0

PocketPC and Windows Mobile does not support a hosts file as desktop windows. As I recently had to add an entry for a virtual machine running Mini SAP (Netweaver 7.01 Trial) I stumbled about how to add a host entry to a windows mobile device.

pocketHosts

The platform builder help gives the details about how host entries are organized:

Host Name

The host name can be configured through the HKEY_LOCAL_MACHINE\Comm\Tcpip\Hosts subkey. When an application calls gethostbyname or getaddrinfo, the registry is queried first, before a DNS or WINS request is sent. If the host name is found in the registry, the registry values are returned.

The following table shows the values for the HKEY_LOCAL_MACHINE\Comm\Tcpip\Hosts\<Host Name> subkey.

Value : type Description
Aliases : REG_MULTI_SZ This value stores the aliases by which this host is known.
ExpireTime : REG_BINARY If the current time, obtained by calling GetCurrentFT, exceeds the value in ExpireTime, the entire Host Name subkey is deleted the next time that gethostbyname is called. The length of this value is 8 bytes.
ipaddr : REG_BINARY This value stores the IPv4 addresses associated with this host name. The length of this value is 4 bytes per address.
ipaddr6 : REG_BINARY This value stores the IPv6 addresses associated with this host name. The length of this value is 20 bytes per address (16 bytes for address and 4 bytes for Scope ID).

So, there is no simple hosts file.

Before struggling all the time with these entries I decided to write a small app to manage these entries: pocketHosts.

I created a class hostsentry to hold each hosts details and a class hostsentries to hold all host entries.

...    
    public class hostsentry
    {

        //##################################################################
        public string sHost { get; set; }
        public List<System.Net.IPAddress> ipAddress { get; set; }
        public List<System.Net.IPAddress> ipAddress6 { get; set; }
        public List<string> aliases { get; set; }
        private ulong _expireTime = 0;
        public ulong expireTime { get { return (uint)_expireTime; } set { _expireTime = (ulong)value; } }
...

The hostsentries class:

...
    public class hostsentries:IDisposable
    {
        const string regSubKey = @"Comm\Tcpip\Hosts";
        RegistryKey rKeyTCPIP;
        public Dictionary<string, hostsentry> allHosts;
        public hostsentries()
        {
            allHosts = new Dictionary<string, hostsentry>();
            init();
        }
...

The registry can hold IP4 and IP6 addresses. pocketHosts shows them in one list:

pocketHostsIPentry

Then there are some helper functions to convert byte arrays of ipaddr and ipaddr6 to single ipAddresses. All ipAddresses are saved to the registry as binary:

...
        //convert 20010db8ac10fe010000000000000000
        //to 2001:0db8:ac10:fe01:0000:0000:0000:0000
        private string getIP6StrfromByte(byte[] bIpAddress6)
        {
            if (bIpAddress6.Length != 16)
                return "";
            string sIP6 = "";
            for (int i = 0; i < 16; i++)
            {
                byte b = bIpAddress6[i];
                sIP6 += b.ToString("x02");
                if (i > 0 && i % 2 != 0 && i != 15)
                    sIP6 += ":";
            }
            return sIP6;
        }

        private System.Net.IPAddress getIP6fromByte(byte[] bIpAddress6)
        {
            System.Net.IPAddress ip6 = new System.Net.IPAddress((long)0);
            string sIP = getIP6StrfromByte(bIpAddress6);
            if (sIP != "")
            {
                try{
                    ip6 = System.Net.IPAddress.Parse(sIP);
                }
                catch(Exception){}
            }
            return ip6;
        }
...

These function are called to convert the byte arrays back to IP addresses. The functions to convert the IP addresses back to bytes are much easier:

...
                List<byte> b = new List<byte>();
                foreach (System.Net.IPAddress ip in he.ipAddress)
                {
                    b.AddRange(ip.GetAddressBytes());
                    iRet++;
                }
                if(b.Count>=4)
                    regWork.SetValue("ipaddr", b.ToArray(), RegistryValueKind.Binary);

                b.Clear();
                foreach (System.Net.IPAddress ip6 in he.ipAddress6)
                {
                    b.AddRange(ip6.GetAddressBytes());
                    iRet++;
                }
                if (b.Count >= 4)
                    regWork.SetValue("ipaddr6", b.ToArray(), RegistryValueKind.Binary);
...

Although the registry holds the IP addresses in ipaddr and ipaddr6 entries. The application can treat both IP address types the same. Only when saving, the list has to be diverted into IP4 and IP6 addresses.

The source code (VS2005, WM5 SDK, CF2) and a binary can be found at my code.google.com repository.

There is one bug I could not fix: you cannot remove a hosts entry. The RegDelKey function always returns an error.

Mobile development: Netstat, know your device’s open ports

$
0
0

On desktop PCs you have the nice tool netstat to see which ports are open on the PC. A customer wanted to know, why his devices do not release there internet connection. The only tool I know, that will show open network connections, is called netstat. Unfortunately I did not find such tool for Windows Mobile and so I wrote one myself:

netstat for windows mobile

netstatCF2

NetstatCF2 is written in C#, Compact Framework. After you started the tool it immediately collects the open ports data. It will also log this data periodically (every 3 seconds) to a log file called “\netstat.log”.

The code makes massive calls to the ipHlp API functions. Here are some sample snippets:

...
    public class IPHlpAPI32Wrapper
    {
        public const byte NO_ERROR = 0;
        public const int FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100;
        public const int FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200;
        public const int FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000;
        public int dwFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER |
            FORMAT_MESSAGE_FROM_SYSTEM |
            FORMAT_MESSAGE_IGNORE_INSERTS;

        [DllImport("iphlpapi.dll", SetLastError = true)]
        public extern static int GetUdpStatistics(ref MIB_UDPSTATS pStats);

        [DllImport("iphlpapi.dll", SetLastError = true)]
        public static extern int GetUdpTable(byte[] UcpTable, out int pdwSize, bool bOrder);

        [DllImport("iphlpapi.dll", SetLastError = true)]
        public extern static int GetTcpStatistics(ref MIB_TCPSTATS pStats);

        [DllImport("iphlpapi.dll", SetLastError = true)]
        public static extern int GetTcpTable(byte[] pTcpTable, out int pdwSize, bool bOrder);

        [DllImport("iphlpapi.dll", SetLastError = true)]
        public static extern int GetIpForwardTable(IntPtr pIpForwardTable, ref int pdwSize, bool bOrder);
        [DllImport("iphlpapi.dll", SetLastError = true)]
        public static extern int GetIpForwardTable(byte[] pIpForwardTable, ref int pdwSize, bool bOrder);
...

        public void GetTcpConnexions()
        {
            byte[] buffer = new byte[20000]; // Start with 20.000 bytes left for information about tcp table
            int pdwSize = 20000;
            int res = IPHlpAPI32Wrapper.GetTcpTable(buffer, out pdwSize, true);
            if (res != NO_ERROR)
            {
                buffer = new byte[pdwSize];
                res = IPHlpAPI32Wrapper.GetTcpTable(buffer, out pdwSize, true);
                if (res != 0)
                    return;     // Error. You should handle it
            }

            TcpConnexion = new IpHlpApidotnet.MIB_TCPTABLE();

            int nOffset = 0;
            // number of entry in the
            TcpConnexion.dwNumEntries = Convert.ToInt32(buffer[nOffset]);
            nOffset += 4;
            TcpConnexion.table = new MIB_TCPROW[TcpConnexion.dwNumEntries];

            for (int i = 0; i < TcpConnexion.dwNumEntries; i++)
            {
                // state
                int st = Convert.ToInt32(buffer[nOffset]);
                // state in string
                //((MIB_TCPROW)
                (TcpConnexion.table[i]).StrgState = convert_state(st);
                // state  by ID
                //((MIB_TCPROW)
                (TcpConnexion.table[i]).iState = st;
                nOffset += 4;
                // local address
                string LocalAdrr = buffer[nOffset].ToString() + "." + buffer[nOffset + 1].ToString() + "." + buffer[nOffset + 2].ToString() + "." + buffer[nOffset + 3].ToString();
                nOffset += 4;
                //local port in decimal
                int LocalPort = (((int)buffer[nOffset]) << 8) + (((int)buffer[nOffset + 1])) +
                    (((int)buffer[nOffset + 2]) << 24) + (((int)buffer[nOffset + 3]) << 16);

                nOffset += 4;
                // store the remote endpoint
                //((MIB_TCPROW)(
                (TcpConnexion.table[i]).Local = new IPEndPoint(IPAddress.Parse(LocalAdrr), LocalPort);

                // remote address
                string RemoteAdrr = buffer[nOffset].ToString() + "." + buffer[nOffset + 1].ToString() + "." + buffer[nOffset + 2].ToString() + "." + buffer[nOffset + 3].ToString();
                nOffset += 4;
                // if the remote address = 0 (0.0.0.0) the remote port is always 0
                // else get the remote port in decimal
                int RemotePort;
                //
                if (RemoteAdrr == "0.0.0.0")
                {
                    RemotePort = 0;
                }
                else
                {
                    RemotePort = (((int)buffer[nOffset]) << 8) + (((int)buffer[nOffset + 1])) +
                        (((int)buffer[nOffset + 2]) << 24) + (((int)buffer[nOffset + 3]) << 16);
                }
                nOffset += 4;
                //((MIB_TCPROW)
                (TcpConnexion.table[i]).Remote = new IPEndPoint(IPAddress.Parse(RemoteAdrr), RemotePort);
            }
        }
...
        public void GetUdpConnexions()
        {
            byte[] buffer = new byte[20000]; // Start with 20.000 bytes left for information about tcp table
            int pdwSize = 20000;
            int res = IPHlpAPI32Wrapper.GetUdpTable(buffer, out pdwSize, true);
            if (res != NO_ERROR)
            {
                buffer = new byte[pdwSize];
                res = IPHlpAPI32Wrapper.GetUdpTable(buffer, out pdwSize, true);
                if (res != 0)
                    return;     // Error. You should handle it
            }

            UdpConnexion = new IpHlpApidotnet.MIB_UDPTABLE();

            int nOffset = 0;
            // number of entry in the
            UdpConnexion.dwNumEntries = Convert.ToInt32(buffer[nOffset]);
            nOffset += 4;
            UdpConnexion.table = new MIB_UDPROW[UdpConnexion.dwNumEntries];
            for (int i = 0; i < UdpConnexion.dwNumEntries; i++)
            {
                string LocalAdrr = buffer[nOffset].ToString() + "." + buffer[nOffset + 1].ToString() + "." + buffer[nOffset + 2].ToString() + "." + buffer[nOffset + 3].ToString();
                nOffset += 4;

                int LocalPort = (((int)buffer[nOffset]) << 8) + (((int)buffer[nOffset + 1])) +
                    (((int)buffer[nOffset + 2]) << 24) + (((int)buffer[nOffset + 3]) << 16);
                nOffset += 4;
                //((MIB_UDPROW)
                (UdpConnexion.table[i]).Local = new IPEndPoint(IPAddress.Parse(LocalAdrr), LocalPort);
            }
        }
...
    public class IPHelper
    {
        private const int NO_ERROR = 0;
        private const int MIB_TCP_STATE_CLOSED = 1;
        private const int MIB_TCP_STATE_LISTEN = 2;
        private const int MIB_TCP_STATE_SYN_SENT = 3;
        private const int MIB_TCP_STATE_SYN_RCVD = 4;
        private const int MIB_TCP_STATE_ESTAB = 5;
        private const int MIB_TCP_STATE_FIN_WAIT1 = 6;
        private const int MIB_TCP_STATE_FIN_WAIT2 = 7;
        private const int MIB_TCP_STATE_CLOSE_WAIT = 8;
        private const int MIB_TCP_STATE_CLOSING = 9;
        private const int MIB_TCP_STATE_LAST_ACK = 10;
        private const int MIB_TCP_STATE_TIME_WAIT = 11;
        private const int MIB_TCP_STATE_DELETE_TCB = 12;

        private const int ERROR_INSUFFICIENT_BUFFER = 122;
...

But there is nothing special except on how to use C/C++ structures in C#.

Here is a sample of the logged data:

xxxxxxxxxxx AM #################
======= TCP table ========
local                       remote
        0.0.0.0:    21         0.0.0.0:     0 LISTEN
        0.0.0.0:  1004         0.0.0.0:     0 LISTEN
        0.0.0.0:  2188         0.0.0.0:     0 LISTEN
        0.0.0.0:  2189         0.0.0.0:     0 LISTEN
        0.0.0.0:  5655         0.0.0.0:     0 LISTEN
        0.0.0.0: 52241         0.0.0.0:     0 LISTEN
      127.0.0.1:  1032       127.0.0.1: 52241 ESTAB
      127.0.0.1:  1034       127.0.0.1: 52241 ESTAB
      127.0.0.1:  1035       127.0.0.1: 52241 ESTAB
      127.0.0.1:  1036       127.0.0.1: 52241 ESTAB
      127.0.0.1: 52241       127.0.0.1:  1032 ESTAB
      127.0.0.1: 52241       127.0.0.1:  1034 ESTAB
      127.0.0.1: 52241       127.0.0.1:  1035 ESTAB
      127.0.0.1: 52241       127.0.0.1:  1036 ESTAB
 192.168.55.101:  1082  192.168.55.100:  7438 ESTAB
 192.168.55.101:  1083  192.168.55.100:   990 ESTAB
 192.168.55.101:  1086  192.168.55.100:   990 ESTAB
 192.168.55.101:  1087  192.168.55.100:   990 ESTAB
 192.168.55.101:  1092  192.168.55.100:   990 ESTAB
 192.168.55.101:  1102  192.168.55.100:  1004 ESTAB
 192.168.55.101:  1103  192.168.55.100:   990 ESTAB
192.168.128.104:  1033   192.168.128.5: 62241 ESTAB
192.168.128.104:  5655   192.168.128.2: 59534 ESTAB
192.168.128.104:  5655   192.168.128.2: 59535 ESTAB
192.168.128.104:  6510   192.168.128.2: 59536 ESTAB
192.168.128.104:  6510   192.168.128.2: 59537 ESTAB
======= UDP table ========
        0.0.0.0:    53
        0.0.0.0:   137
        0.0.0.0:   138
        0.0.0.0:  1088
        0.0.0.0:  9204
        0.0.0.0: 49111
192.168.128.104:    68
======= TCP statistics ========
Retransmission timeout (min/max): Van Jacobson's Algorithm: 300/120000
                max connnections: -1
                     active open: 69
                    passive open: 196
                 failed attempts: 0
              established resets: 243
             current established: 20
                     segments in: 134380
                    segments out: 130900
          retransmitted segments: 175
                       in errors: 0
                      out resets: 861
                 num connections: 26
======= UDP statistics ========
                    in datagrams: 13771
                       in errors: 0
                       num ports: 3353
                   num addresses: 7
                   out datagrams: 887
======= Adapter infos ==========
131074: BCMCF1, 192.168.128.104
262147: USB Cable:, 192.168.55.101
======= Route entries ==========
Network Destination        Netmask          Gateway          Interface     Metric
0.0.0.0                    0.0.0.0          192.168.55.101                   003    0
225.20.0.0                 0.0.0.0          255.255.255.255                  255    0
0.0.0.0                    0.0.0.0          192.168.128.1                    002    0
225.20.0.0                 0.0.0.0          255.255.255.255                  255    0
127.0.0.0                  255.0.0.0        127.0.0.1                        001    0
104.148.1.0                0.0.0.0          255.255.255.255                  255    0
192.168.55.101             255.255.255.255  127.0.0.1                        001    0
225.20.0.0                 0.0.0.0          255.255.255.255                  255    0
192.168.55.255             255.255.255.255  192.168.55.101                   003    0
225.20.0.0                 0.0.0.0          255.255.255.255                  255    0
192.168.128.0              255.255.255.0    192.168.128.104                  002    0
67.148.1.0                 0.0.0.0          255.255.255.255                  255    0

Hopefully you use this small tool at a special time.

Code and sample bin at code.google.com (VS2008, CF2, WM5SDK)

Windows Mobile 6 – Internet Explorer Mobile modes

$
0
0

Since IE6 comes as 6.1.4 or higher, Internet Explorer Mobile can work in IE6 or the older PIE mode.

IE6 means Internet Explorer 6 desktop compatible mode. Which works more or less with known and unknown restrictions. The biggest advantage is that Internet Explorer (IEM) >=6.1.4 supports keyboard event DOM of javascript.

For some web sites you may downgrade to PIE (Pocket Internet Explorer) mode. You will get scroll bars and a standard menu bar and no on-screen zoom option.

To switch to PIE mode in Windows Mobile 6 or Windows Embedded Handheld 6.5 you need to set the following registry key:

[HKEY_LOCAL_MACHINE\Security\Internet Explorer]
MSHTML=0
; 0=PIE mode
; 1=IE6 mode

To control the PIE view mode directly, you can use the following registry key:

[HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Main]
"MakeFit"=dword:00000002
; tested in PIE mode
; 2=OneColumn
; 1=FitToScreen
; 0=Desktop
IE6 automatic mode

IE6 automatic mode

IE6 mobile view

IE6 mobile view

 

IE6 desktop view

IE6 desktop view

 

PIE desktop view

PIE desktop view

 

PIE Fit To Screen view

PIE Fit To Screen view

 

PIE One Column view

PIE One Column view

Windows Mobile – Ctlpnl.exe arguments

$
0
0

Did you know you can launch ctlpnl.exe to open a defined applet directly?

OK, here we go:

ctlpnl.exe cplmain.cpl,26

will open the Wireless Manager. You can use that in a .lnk file if you need a direct option to launch ctlpnl.exe with a specific applet:

255#"\windows\ctlpnl.exe" cplmain.cpl,26

REMEMBER: do NOT add a newline in the .lnk ASCII file!

Here is a list of know arguments:

0  -  Contrast
1  -  Password
2  -  Owner Information
3  -  Power
4  -  Memory
5  -  About
6  -  Backlight
7  -  Align Screen
8  -  Input Method
9  -  Sounds & Reminders
10 -  Remove Programs
11 -  Menus
12 -  Buttons
13 -  Today Settings
14 -  Domain Enrollment
15 -  Beam
16 -  Clock & Alarms
17 -  Configure Wireless Networks
18 -  Regional Settings
19 -  Connections - i.e. My ISP, My Work
20 -  Phone Settings - Network Selection, Call barring etc
21 -  ??
22 -  Manager Certificates
23 -  Bluetooth
24 -  Error Reporting
25 -  GPS Port Settings
26 -  Wireless Manager, WLAN, Bluetooth, Phone
27 -  Encryption
28 -  USB to PC
29 -  Customer Feedback
30 -  Task Manager

Mobile development: rdp_autologin updated to support clipboard redirection

$
0
0

The source code of rdp_autologin has been updated and now default.rdp will now include some more lines:

WorkingDir:s:C:\Program Files (x86)
AlternateShell:s:C:\Program Files (x86)\notepad.exe
EnableClipboardRedirection:i:1
RDPIdleTimeout:i:0

where only EnableClipboardRedirection is working.

Will update the code of rdp_autologin and it’s tools to enable you to control the clipboard setting by the registry. Currently it defaults to ENABLED.

See also previous posts.

Windows Mobile: Some logging tools

$
0
0

If you ever had a windows mobile device that behaved strange you might need some logging to find the cause.

Here is a collection of logging tools I wrote:

BatteryLog: logs the status of battery and power management changes (ie Suspend/Resume etc.)

Instead of just querying the PowerStatusEx the code uses a thread to watch for PowerNotifications

...
    // Request power notifications 
    HANDLE hPowerNotifications = RequestPowerNotifications(hPowerMsgQ,
                                                           PBT_TRANSITION | 
                                                           PBT_RESUME | 
                                                           PBT_POWERINFOCHANGE);
    if (NULL == hPowerNotifications) 
    {
        RETAILMSG(1, (L"RequestPowerNotifications failed: %x\n", GetLastError()));
        goto Error;
    }

    HANDLE rgHandles[2] = {0};
    rgHandles[0] = hPowerMsgQ;
    rgHandles[1] = g_hEventShutDown;

    // Wait for a power notification or for the app to exit
    while(WaitForMultipleObjects(2, rgHandles, FALSE, INFINITE) == WAIT_OBJECT_0)
    {
        DWORD cbRead;
        DWORD dwFlags;
        POWER_BROADCAST *ppb = (POWER_BROADCAST*) new BYTE[cbPowerMsgSize];
        TCHAR* szPPB = new TCHAR[MAX_PATH];  
        memset(szPPB, 0, MAX_PATH * 2);
        TCHAR* szPBtype=new TCHAR[MAX_PATH];
        memset(szPBtype, 0, MAX_PATH * 2);
        TCHAR szOut[MAX_PATH];
        // loop through in case there is more than 1 msg 
        while(ReadMsgQueue(hPowerMsgQ, ppb, cbPowerMsgSize, &cbRead, 
                           0, &dwFlags))
        {
            wsprintf(szPPB, L"");
            wsprintf(szPBtype, L"");
            switch (ppb->Message)
            {
                case PBT_POWERSTATUSCHANGE:
                    RETAILMSG(1,(L"Power Notification Message: PBT_POWERSTATUSCHANGE\n"));
                    wsprintf(szPBtype, L"change: ");
                    //Add2Log(L"Power Notification Message: PBT_POWERSTATUSCHANGE\n",TRUE);
                    break;
                case PBT_SUSPENDKEYPRESSED:
                    RETAILMSG(1,(L"Power Notification Message: PBT_SUSPENDKEYPRESSED\n"));
                    wsprintf(szPBtype, L"keypress: ");
                    //Add2Log(L"Power Notification Message: PBT_SUSPENDKEYPRESSED\n",TRUE);
                    break;
...

So the code does not miss changes. But remember, a suspend is signaled at the next resume!

BTmon: logs BT activity of your device. Handles Device connect/disconnect and other events and logs them to a file.

Fortunately MS provides a message queue for BT events too. So the code uses a similar aproach as batteryLog:

void startMsgQueue(HWND hWnd){
    DEBUGMSG(1, (L"Entering msgQueue with hwnd=%i\n", hWnd));

    MSGQUEUEOPTIONS mqOptions;
    memset (&mqOptions, 0, sizeof(mqOptions));

    mqOptions.dwFlags = 0;
    mqOptions.dwSize = sizeof(mqOptions);
    mqOptions.dwMaxMessages = 10;
    mqOptions.cbMaxMessage = sizeof(BTEVENT);
    mqOptions.bReadAccess = TRUE;

    hMsgQ = CreateMsgQueue(NULL, &mqOptions);
    if (! hMsgQ) {
        nclog(L"Error creating message queue.\n");
        goto exit;
    }
    hBTNotif = RequestBluetoothNotifications(
        BTE_CLASS_CONNECTIONS    |
            BTE_CONNECTION            |
            BTE_DISCONNECTION        |
            BTE_ROLE_SWITCH            |
            BTE_MODE_CHANGE            |
            BTE_PAGE_TIMEOUT        |
            BTE_CONNECTION_AUTH_FAILURE    |
        BTE_CLASS_PAIRING    |
            BTE_KEY_NOTIFY    |
            BTE_KEY_REVOKED    |
        BTE_CLASS_DEVICE    |
            BTE_LOCAL_NAME    |
            BTE_COD            |
        BTE_CLASS_STACK        |
            BTE_STACK_UP    |
            BTE_STACK_DOWN    
        , hMsgQ);

    bStop=FALSE;
    CreateThread(NULL, 0, msgThread, hWnd, 0, &threadID);

exit:
    ;
}
...
DWORD msgThread(LPVOID lpParam){
    HWND hWnd = (HWND)lpParam;
    hwndMsg=hWnd;

    DEBUGMSG(1,(L"Waiting for Bluetooth notifications with hwnd=%i...\n", hWnd));
    nclog(L"%s: Waiting for Bluetooth notifications...\n", logDateTime());
    
    TCHAR szMsg[MAX_PATH];
    wsprintf(szMsg, L"\r\n%s: Waiting for Bluetooth notifications...", logDateTime());
    printMsg(szMsg, hWnd);

    BTEVENT btEvent;
    DWORD dwBytesRead;
    DWORD dwFlags;
    BOOL fRet;

    while (FALSE == bStop) {
        DWORD dwWait = WaitForSingleObject (hMsgQ, 5000);//wait up to 5 seconds INFINITE);
        switch (dwWait){
            case WAIT_OBJECT_0:
                // We have got a Bluetooth event!
                dwFlags = 0;
                dwBytesRead = 0;

                fRet = ReadMsgQueue (hMsgQ, &btEvent, sizeof(BTEVENT), &dwBytesRead, 10, &dwFlags);
                if (! fRet) {
                    DEBUGMSG(1,(L"Error - Failed to read message from queue!\n"));
                    //bStop=TRUE;
                } 
                else {
                    dumpBTevent(btEvent, hWnd);
                }
                break;
            case WAIT_TIMEOUT:
...

The ‘problem’ with that is that the events contain different data and every type has to be handled very differently. Here is just a small example:

...
void dumpBTevent(BTEVENT btEvent, HWND hwnd){
    BT_CONNECT_EVENT* cntEvt = NULL;
    BT_DISCONNECT_EVENT* discntEvt=NULL;
    BT_ROLE_SWITCH_EVENT* rolSwitchEvt=NULL;
    BT_MODE_CHANGE_EVENT* btModeChgEvt=NULL;
    BT_LINK_KEY_EVENT* btLnkKeyEvt=NULL;

    TCHAR btAddress[18];
    TCHAR hConn[12];
    TCHAR encMode[5];
    TCHAR linkType[5];
    TCHAR szTemp[MAX_PATH];
    TCHAR szMsg[MAX_PATH];

    wsprintf(szMsg, L"\r\n");

    switch (btEvent.dwEventId){
        case BTE_KEY_NOTIFY:
            wsprintf(szMsg, L"\r\n%s: BTE_KEY_NOTIFY:", logDateTime());
            break;
        case BTE_KEY_REVOKED:
            btLnkKeyEvt = (BT_LINK_KEY_EVENT*)btEvent.baEventData;
            //XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
            wsprintf(btAddress, L"%s", btAddr2Mac( btLnkKeyEvt->bta, btAddress));
            wsprintf(szMsg, L"\r\n%s: BTE_KEY_REVOKED: mac=%s", logDateTime(), btAddress);
            break;
        case BTE_LOCAL_NAME:
            wsprintf(szMsg, L"\r\n%s: BTE_LOCAL_NAME:", logDateTime());
            break;
...

connMgrLog: log connection manager events and connection state changes.

This logging tool curently does not log to file. The code also uses a notification event supported by the MS ConnMgr API. The background thread receiving the notification message will post another message to update the main window with the current event data:

connStats connectionStates[]={
    {L"CONNMGR_STATUS_UNKNOWN", 0x00},
    {L"CONNMGR_STATUS_CONNECTED", 0x10 },
    {L"CONNMGR_STATUS_SUSPENDED", 0x11 },
    {L"CONNMGR_STATUS_DISCONNECTED", 0x20 },
    {L"CONNMGR_STATUS_CONNECTIONFAILED", 0x21 },
    {L"CONNMGR_STATUS_CONNECTIONCANCELED", 0x22 },
    {L"CONNMGR_STATUS_CONNECTIONDISABLED", 0x23 },
    {L"CONNMGR_STATUS_NOPATHTODESTINATION", 0x24 },
    {L"CONNMGR_STATUS_WAITINGFORPATH", 0x25 },
    {L"CONNMGR_STATUS_WAITINGFORPHONE", 0x26 },
    {L"CONNMGR_STATUS_PHONEOFF", 0x27 },
    {L"CONNMGR_STATUS_EXCLUSIVECONFLICT", 0x28 },
    {L"CONNMGR_STATUS_NORESOURCES", 0x29 },
    {L"CONNMGR_STATUS_CONNECTIONLINKFAILED", 0x2A },
    {L"CONNMGR_STATUS_AUTHENTICATIONFAILED", 0x2B },
    {L"CONNMGR_STATUS_WAITINGCONNECTION", 0x40 },
    {L"CONNMGR_STATUS_WAITINGFORRESOURCE", 0x41 },
    {L"CONNMGR_STATUS_WAITINGFORNETWORK", 0x42 },
    {L"CONNMGR_STATUS_WAITINGDISCONNECTION", 0x80 },
    {L"CONNMGR_STATUS_WAITINGCONNECTIONABORT", 0x81 },
    {NULL, -1},
};

UINT WM_CM_STATUS_CHANGE = WM_USER+1234;
...
void startConnMgrWatch(HWND hWnd){
    HRESULT hr;
    hEdit=hWnd;
    if(WM_CM_STATUS_CHANGE==WM_USER+1234){    // do only once, if 1234 then not registered
        WM_CM_STATUS_CHANGE = RegisterWindowMessage( CONNMGR_STATUS_CHANGE_NOTIFICATION_MSG );
        //DEBUGMSG(1, (L"RegisterWindowMessage =0x%x.\r\n", WM_CM_STATUS_CHANGE));
        logMsg(L"RegisterWindowMessage =0x%x.\r\n", WM_CM_STATUS_CHANGE);
    }
    // after you registered for the CONNMGR_STATUS_CHANGE_NOTIFICATION_MSG and got a constant you can watch for changes
    hr = ConnMgrRegisterForStatusChangeNotification(TRUE, hWnd);
    DEBUGMSG(1, (L"ConnMgrRegisterForStatusChangeNotification =0x%x.\r\n", hr));
    return;
}
...
void dump_details_params(CONNMGR_CONNECTION_DETAILED_STATUS *p){
    //DEBUGMSG(1, (L"\r\n############################################\r\n"));
    logMsg(L"\r\n############################################\r\n");
    //check which flags are valid and dump them
    if(p->dwParams & CONNMGRDETAILEDSTATUS_PARAM_TYPE){            //The dwType member of CONNMGR_CONNECTION_DETAILED_STATUS is valid
        //DEBUGMSG(1,(L"\tConnType= '%s'\r\n", szConnType[p->dwType]));
        logMsg(L"\tConnType= '%s'\r\n", szConnType[p->dwType]);
    }
    if(p->dwParams & CONNMGRDETAILEDSTATUS_PARAM_SUBTYPE){        //dwSubtype member of CONNMGR_CONNECTION_DETAILED_STATUS is valid
        //DEBUGMSG(1,(L"\tConnSubtype= '%s'\r\n", szConnSubtype[p->dwSubtype]));
        logMsg(L"\tConnSubtype= '%s'\r\n", szConnSubtype[p->dwSubtype]);
        switch(p->dwSubtype){
            case CM_CONNSUBTYPE_UNKNOWN:
                break;
        }
    }
    if(p->dwFlags & CONNMGRDETAILEDSTATUS_PARAM_FLAGS){
        if(p->dwFlags && CM_DSF_BILLBYTIME){
            //DEBUGMSG(1, (L"\t cm_flags+=CM_DSF_BILLBYTIME\r\n"));
            logMsg(L"\t cm_flags+=CM_DSF_BILLBYTIME\r\n");
        }
        if(p->dwFlags && CM_DSF_ALWAYSON){
            //DEBUGMSG(1, (L"\t cm_flags+=CM_DSF_ALWAYSON\r\n"));
            logMsg(L"\t cm_flags+=CM_DSF_ALWAYSON\r\n");
        }
...
void dump_details(CONNMGR_CONNECTION_DETAILED_STATUS *p)
{
    dump_details_params(p);
    return;
    TCHAR txt[MAX_PATH];
    if (p->szAdapterName && wcslen(p->szAdapterName)>0){
        //DEBUGMSG(1,(L"adap: %s\r\n", p->szAdapterName));
        logMsg(L"adap: %s\r\n", p->szAdapterName);
        //wsprintf(txt, L"adap: %s\r\n", p->szAdapterName);
        //addText(hWndMain, txt);
    }
    if (p->szDescription){
        //DEBUGMSG(1, (L"desc: %s\r\n", p->szDescription));
        logMsg(L"desc: %s\r\n", p->szDescription);
        //wsprintf(txt, L"desc: %s\r\n", p->szDescription);
        //addText(hWndMain, txt);
    }
...

deviceMon: (work in progress) watches the device for changes in drivers like connect/disconnect etc.

This logging tool will also use a message queue provided by the MS API set. Here is a small sample watching for a disk change:

...
void MonitorForNewDisks()
{
    HANDLE hNotify;
    HANDLE qStore ;
    DWORD flags;
    DWORD size;
    BYTE DevDetail[sizeof(DEVDETAIL) + (MAX_PATH * sizeof( TCHAR ))];
    DEVDETAIL * pDetail = (DEVDETAIL *)DevDetail;
    MSGQUEUEOPTIONS msgopts;

    msgopts.dwSize = sizeof(MSGQUEUEOPTIONS);
    msgopts.dwFlags = 0;
    msgopts.dwMaxMessages = 0;
    msgopts.cbMaxMessage = sizeof(DevDetail);
    msgopts.bReadAccess = TRUE;

    qStore = CreateMsgQueue(NULL, &msgopts);
/*
"IClass"="{f8a6ba98-087a-43ac-a9d8-b7f13c5bae31}"; This is DEVCLASS_STREAM_GUID
In case you follow my suggestion going for CE_DRIVER_SERIAL_PORT_GUID you will need:
"IClass"="{CC5195AC-BA49-48a0-BE17-DF6D1B0173DD}" ; this is CE_DRIVER_SERIAL_PORT_GUID
*/
    hNotify = RequestDeviceNotifications(&BLOCK_DRIVER_GUID, qStore, TRUE);

    do
    {
        if(WaitForSingleObject(qStore, 5000) == WAIT_OBJECT_0)
        {
            while(ReadMsgQueue(qStore, pDetail, sizeof(DevDetail), &size, 1, &flags))
            {
                if(pDetail->fAttached)
                    ;//do something HandleNewDisk(pDetail->szName);
            }
        }
    } while( !StopMonitoringForNewDisks );
...

memuse: logs the memory usage of all running processes on your device. Possibly find a memory leak in an app.

This logging uses a periodic timer to dump the memory use of all processes. Here it usefull to know that there are a max of 32 processes running. Each process has its own slot and we can walk the slots and query the memory use. Then we need to map the process ID to a process name to get a named list:

...
DWORD getVMuse(){
    //build the process list
    int iMax = GetProcessNameList((PROCESSNAMES*) &ProcessNames);

    DWORD total = 0;
    for( int idx = 2; idx < 33; ++idx )
    {
        PROCVMINFO vmi = { 0 };
        if( CeGetProcVMInfo( idx, sizeof( PROCVMINFO ), &vmi ) )
        {
            //DEBUGMSG(1, ( L"%d: 0x08x %d bytes\r\n", idx, vmi.hProc, vmi.cbRwMemUsed ));
            total += vmi.cbRwMemUsed;
            //GetProcessIDFromIndex() and match it to the th32ProcessID member of the PROCESSENTRY32 structure
            HANDLE dwThreaID = GetProcessIDFromIndex(idx);
            for(int i=0; i<iMax; i++){
                if(ProcessNames[i]._dwID == (DWORD)dwThreaID)
                    ProcessNames[i]._memuse = vmi.cbRwMemUsed;
            }
        }
    }
...

All projects can be loaded from logging_ce at github.

Mobile Development: BarcodeLib again ported to Compact Framework

$
0
0

I again ported the codeproject BarcodeLib to Compact Framework. So feel free to use that, if you need one of the supported linear barcode type.

BarcodeLibTestCF

The VS 2008 / WM5 SDK / Full Framework solution is located at github.

A ready to use binary including the lib is at github.

 

Windows Server 2012 RDS and Windows Mobile: connection error

$
0
0

For whatever reason MS decided to make Windows 2012 RDS (former Terminal Services, now Remote Desktop Services) not compatible with Windows Mobile 6.x and other Windows CE 5.0 based handheld devices.

Fortunately, if you activated Remote Desktop License Server using ‘Web Browser’ method, you simply have to change the Collections Security settings and disable ‘Allow only … Network Level Authentication’ (NLA).

The following can also apply for Windows 2008 R2 Terminal Server. Check if you activate the Licensing server via “Web Browser” connection or directly. My 2008 R2 server is running OK for Windows Mobile, as a stand-alone server, with 100 licenses and activated via “Web Browser”.

But let start at the beginning.

Basic RDS setup

When you installed Windows 2012 Server within an existing or new Active Directory and then add the Remote Desktop Server role, you have different choices:

AddRolesAndFeatures

You may go on with “Remote Desktop Services scenario-based installation” and then just follow the wizard after selecting “Quick Start”.

AddRolesAndFeatures_2

The wizard will install everything onto one server.

AddRolesAndFeatures_3

Virtual Desktop Infrastructure makes no sense for Windows Mobile clients. They do not need a full virtual windows machine based on a virtual machine. So we select “Session Virtualization”.

The wizard will then deploy all the services and roles and create one default “Session Collection” and “Remote Apps”. At the end you should get following screen:

AddRolesAndFeatures_4

Now check the setup and look at the RDS Overview:

RemoteDesktopServices-Overview

You see we have RD Web Access (unused by Windows Mobile but cannot be removed), no RD Gateway (not needed here), no RD Licensing (we will install that later), the RD Connection Broker, no RD Virtualization Host (as we do not provide virtual machines here) and a RD Session Host with a QuickSession Collection.

At this stage we can not connect using Windows Mobile client. The NLA setting dis-allows that and we get an error in Remote Desktop Mobile. Just change the NLA setting of the Collection and your Windows Mobile clients can connect.

rdm_cn70   rdm_ck3b

NOTE that there is no License server and we are in the 120-day trial of RDS!

RDS_Collections_EditProperties QuickSessionCollection_Properties

You can access the above Properties using the TASKS menu of Remote Desktop Services-Collections-CollectionName and selecting “Edit Properties”.

If there is no Collection, we can not change the setting! Windows Desktop PCs can connect to that RDS without a Collection installed. We (Windows Mobile client) need a collection to disable NLA.

Setup Remote Desktop License Server

Now setup a Remote Desktop License server, activate it (or better read my later note about the activation method: see “RD License Server Activation Connection Method”) and install some CALs or DALs (licenses per User or Device). Ensure the License Manager shows your License Server without any error. And also check with RD License Diagnoser!

RD_licensing_Manager RD_Licensing_Diagnoser

The licensing mode must match the general Collections properties setting:

Collection_DeploymentProperties

If everything is in place and activated and licensed Windows Mobile Clients can no longer connect!

rdm_security_error

The certificates generated by the License Server are not compatible with Remote Desktop Mobile. They use 4096 bit key-length and SHA256 footprint. The certificates are stored in the registry at [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server\RCM]. Extracted and converted these certs look like this:

Certificate:
     Data:
         Version: 3 (0x2)
         Serial Number:
             0b:1c:04:1c:9c:74:34:af:41:3a:3c:bf:39:f5:56:bf
     Signature Algorithm: sha256WithRSAEncryption
         Issuer: C=US, ST=Washington, L=Redmond, O=Microsoft Corporation, CN=Microsoft Assurance Designation Root 2011
         Validity
             Not Before: Mar 23 17:41:27 2011 GMT
             Not After : Mar 23 17:48:11 2036 GMT
         Subject: C=US, ST=Washington, L=Redmond, O=Microsoft Corporation, CN=Microsoft Assurance Designation Root 2011
         Subject Public Key Info:
             Public Key Algorithm: rsaEncryption
                 Public-Key: (4096 bit)
                 Modulus:
                     00:a8:ef:ce:ef:ec:12:8b:92:94:ed:cf:aa:a5:81:
                     8d:4f:a4:ad:4a:ec:a5:f0:da:a8:3d:b6:e5:61:01:
  ...
                     2b:a9:44:56:83:be:b6:6e:60:b9:16:1a:e1:62:e9:
                     54:9d:bf
                 Exponent: 65537 (0x10001)
         X509v3 extensions:
             X509v3 Key Usage: 
                 Digital Signature, Certificate Sign, CRL Sign
             X509v3 Basic Constraints: critical
                 CA:TRUE
             X509v3 Subject Key Identifier: 
                 1A:A9:53:45:33:8E:D0:6E:22:52:54:76:39:76:43:1E:FF:79:14:41
             1.3.6.1.4.1.311.21.1: 
                 ...
     Signature Algorithm: sha256WithRSAEncryption
          0b:2e:fa:54:de:11:a4:72:e4:13:1d:8b:bc:42:36:7c:fe:76:
 ...
          fa:be:02:5b:1a:c1:d9:58:66:c2:0c:b3:ce:e4:b4:ec:f4:eb:
          56:4f:9a:cc:cc:b2:a0:a4

RD License Server Activation Connection Method

To fix that and get compatible certificates re-activate the RD Licensing Server using the Web method. In RD Licensing Manager right-click the server name and select Properties. Change the Connection Method to “Web Browser”. Close Properties with OK and again right click the server and then Advanced-Reactivate. Follow the process to reactivate the server using the web browser.

RD_license_server_web_activation

After reactivation delete the following registry keys and reboot the server!

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server\RCM
 o Certificate
 o X509 Certificate
 o X509 Certificate ID
 o X509 Certificate2

These registry keys will rebuild with lower security after reboot (see also).

And, surprise, after reboot Remote Desktop Mobile (Windows CE5, Windows Mobile 6.x and Windows Embedded Handheld 6.5.3) can connect!

rdm_cn70_after_reactivation

If you extract and convert the new ‘web-based’ certificates you see the difference:

Certificate:
 Data:
 Version: 3 (0x2)
 Serial Number:
 01:9d:e7:ca:8c:9a:66:80
 Signature Algorithm: sha1WithRSA
 Issuer: L=\x00W\x002\x00K\x001\x002, CN=\x00W\x002\x00K\x001\x002\x00H\x00G\x00O
 Validity
 Not Before: Mar 10 14:50:50 1970 GMT
 Not After : Mar 10 14:50:50 2049 GMT
 Subject: L=\x00W\x002\x00K\x001\x002, CN=\x00W\x002\x00K\x001\x002\x00H\x00G\x00O
 Subject Public Key Info:
 Public Key Algorithm: rsaEncryption
 Public-Key: (2048 bit)
 Modulus:
 00:b6:7e:f2:41:23:f1:f3:cf:44:90:e7:fc:ba:3f:
 ...
 d0:51:d1:55:8c:6b:d0:f6:65:e5:c4:d2:09:1d:d0:
 17:c7
 Exponent: 65537 (0x10001)
 X509v3 extensions:
 X509v3 Basic Constraints:
 CA:TRUE, pathlen:0
 1.3.6.1.4.1.311.18.8:
 Q.G.K.8.V.3.W.2.K.H.P.D.6.W.4.V.M.Q.2.G.3.T.H.3.K.C.8.J.W.K.W.D.M.4.Y...
 Signature Algorithm: sha1WithRSA
 3a:1d:94:36:5d:32:12:6f:5e:e3:76:9f:cb:2b:1c:92:c2:ff:
 ...
 ac:1e:23:b2:a0:73:ff:6f:12:f8:86:24:4b:95:15:54:c0:a2:
 ba:05:00:e3

The key length is only 2048 bits and the security algorithm is SHA1.
If you had activated the “Web browser” Connection method before Activating the server the first time, you do not need to touch the registry and reactivate the server!

Conclusion

Windows Mobile’s Remote Desktop Mobile (RDM) application connects fine if the right certificates are generated when Activating the RD License Server. RDM will not connect, if SHA256 and 4096 bits key are used on the server. RDM does not support NLA nor SSL/TLS!

What MS says

RDS 2008, 2008R2, and 2012 will not allow connections from older RDP 5.x clients.

To get around this add the following registry key to the RDS Session Host

Subkey: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server\RCM
Registry entry: Use512LenPropCert
Data type: REG_DWORD
Value: 0 or 1

This is far less secure (512bit encryption vs 2048bit), and you won't be able to take advantage of the features of later versions of the RDP protocol, but older clients will be able to connect.

What wonders me about that, is that it says ‘no support of 2048 length key’, but the key length is 2048 in my validated tests. Possibly they mean, does not support 4096 key length but 2048 (which is set with the Use512LenPropCert?).

Test your installation

If you like to check the certificates of your installation you may use the attached demo application: “RDS2012_security”. It will just read the registry and show the certificates data.

RDS_Security_CertifcateDump_not_ok RDS_Security_CertifcateDump_ok

The code (included in the attachment) just reads the registry and extracts the certification data. The data is stored binary with some extra data at the beginning. There are twelve bytes to remove to get the raw certificate (all these certs start with 0×30 0×82). So the reg might look like this:

"X509 Certificate"=hex:02,00,00,00,04,00,00,00,f1,05,00,00,30,82,05,ed,30,82,\
  03,d5,a0,03,02,01,02,02,10,0b,1c,04,1c,9c,74,34,af,41,3a,3c,bf,39,f5,56,bf,\
  30,0d,06,09,2a,86,48,86,f7,0d,01,01,0b,05,00,30,81,88,31,0b,30,09,06,03,55,\
...

Then the tool has to remove the first 12 bytes and we get the raw data:

30,82,05,ed,30,82,\
  03,d5,a0,03,02,01,02,02,10,0b,1c,04,1c,9c,74,34,af,41,3a,3c,bf,39,f5,56,bf,\
  30,0d,06,09,2a,86,48,86,f7,0d,01,01,0b,05,00,30,81,88,31,0b,30,09,06,03,55,\
...

Here is the simple code that does this in csharp and then initializes a new X509Certificate2 object:

        const string rd_mainRegKey = @"SYSTEM\CurrentControlSet\Control\Terminal Server\RCM"
        string[] _x509ValueNames = new string[] { "X509 Certificate", "X509 Certificate2" };
...
        byte[] readX509Cert(string sValueName)
        {
            byte[] buf = null;
            using (RegistryKey rk = Registry.LocalMachine.OpenSubKey(rd_mainRegKey, false))
            {
                byte[] bufTemp = (byte[]) rk.GetValue(sValueName);
                //remove first 12 bytes
                buf = new byte[bufTemp.Length - 0x0b];                
                Array.Copy(bufTemp, 0x0c, buf, 0, bufTemp.Length - 0x0c);
            }
            if(sValueName.EndsWith("2"))
                _x509Certificate2=new X509Certificate2(buf);
            else
                _x509Certificate = new X509Certificate2(buf);

            return buf;
        }

Now you can even save the cert or, like the demo does, just show the key length and used algorithm.

DOWNLOAD:rds_security - test the certs of an win 2012 RDS server. (Hits: 5, size: 43.74 kB)

 

Mobile Development: writing today screen plugins the easy way

$
0
0

today Plugins with compact framework

The following today or home screen plugins for Windows Mobile are based on a work of CrisText at codeplex.

The codeplex sources provide a framework to create home screen plugins very easily.

You just start a Form or UserControl and add the attribute “[TodayScreenItem("a unique name")]” before the class. Further on, the framework looks for such plugins dynamically and you can add or remove plugins by just adding or removing DLLs.

I did two plugins, one shows the Intermec device ID and the other enables you to have buttons on the home screen to directly launch applications.

Show some information

The first plugin is very simple. An user control that just shows some static text (see the lower info on the screen shot):

today_date_only today_with_apps_and_deviceID

Simple code:

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

using ChrisTec.WindowsMobile.TodayScreen;

namespace todayDeviceID
{
    [TodayScreenItem("todayScreenDeviceID")]
    public partial class todayDeviceID : UserControl
    {
        private Label label1;
        private PictureBox pictureBox1;
    
        public todayDeviceID()
        {
            InitializeComponent();
            int w = System.Windows.Forms.Screen.PrimaryScreen.WorkingArea.Width;
            int h = System.Windows.Forms.Screen.PrimaryScreen.WorkingArea.Height;
            int factor = w / this.Width;
            this.Width = w;
            this.Height = this.Height * factor;
            
            this.pictureBox1.Height = this.Height - (pictureBox1.Top*2);
            pictureBox1.Width = pictureBox1.Height;    //make a square box

            label1.Width = this.Width - pictureBox1.Width;
            label1.Height = this.Height;

            label1.Text = ITC_DeviceID.getDeviceID();
        }
		...

The above simply has a label and a picturebox on a user control.

To get the above working on your device you must install the ChrisTec.WindowsMobile.TodayScreen cab file or at least the files:

  • ManagedTodayScreenItem.dll
  • ManagedTodayScreenItemHost.exe

And you must register the today plugin framework:

REGEDIT4

[HKEY_LOCAL_MACHINE\Software\Microsoft\Today\Items\Managed Items]
"DLL"="%CE1%\\Managed Today Screen Framework\\ManagedTodayScreenItem.dll"
"Enabled"=dword:00000001
"Options"=dword:00000000
"Selectability"=dword:00000002
"Type"=dword:00000004

All files should be placed inside “\Program Files\Managed Today Screen Framework\”.

Debug and deployment

You should setup your plugins to be deployed to the above directory. So you can easily update and test your plugins. Unfortunately you can not replace any existing plugin as long as Managed Items are enabled in Home screen settings. So I added a project and application that works similar to the plugin framework loader. You may use testPlugin to test and debug your plugin:

testPlugin

Then test your plugin by Start-Settings-Home:Items and enabling “Managed Items”:

settings_home_1 settings_home_2

If everything is correct, you will see your plugin on the home screen.

The other plugin I wrote is a program launcher. I started another Control Library SmartDevice project. Just add the attribute to let the framework know that this is another plugin:

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

using ChrisTec.WindowsMobile.TodayScreen;

namespace todayApp1
{
    [TodayScreenItem("todayScreenApps1")]
    public partial class todayApp1 : UserControl
    {
        theApps apps = new theApps();
        myApps.myApp[] allApps;

        public todayApp1()
        {
            InitializeComponent();
            allApps = apps.myAppList.ToArray();
			...

The above uses a class to determine the application buttons to create. This theApps class uses a xml file to let you define launcher buttons:

<?xml version="1.0" encoding="UTF-8"?>
<myApps>
   <myApp>
     <title>The File Explorer</title>
     <iconFile>\Program Files\managed today screen framework\fexplore.gif</iconFile>
     <exeFile>\Windows\fexplore.exe</exeFile>
     <args>\My Documents</args>
   </myApp>
   <myApp>
     <title>Internet Explorer</title>
     <iconFile>\Program Files\managed today screen framework\iexplore.gif</iconFile>
     <exeFile>\Windows\iexplore.exe</exeFile>
     <args>www.google.de</args>
   </myApp>
</myApps>

You can see that you can define the exe to start, arguments for the exe, a title and a symbol.

The todayApp user control adds buttons dynamically and will show a scroll bar if more than 4 apps are defined.

Now start your own home screen plugin the easy way.

Source code at: https://github.com/hjgode/todayPlugins

Viewing all 25 articles
Browse latest View live