Qt Calendar Gadget

A very simple calendar to be displayed on your desktop similar to a Vista or Windows 7 gadget, but also works for Windows XP.

Calendar Widget running on Win 7

Introduction

The application is realized using the Qt framework. Qt provides you with all the functionality needed to develop advanced GUI applications on desktop and embedded platforms. Qt uses the native graphics APIs of each platform it supports, taking full advantage of system resources and ensuring that applications have the native look and feel. Using Qt is very easy and intuitive and, in my opinion, is the next best thing after Microsoft .NET Framework for Windows based applications. It has the advantage that the same code can be compiled for many platforms (Windows, Linux, Mac OS), is written entirely in C++ (some projects may require only C++), and is free (you can choose from LGPL/GPL/Commercial license).

Background

While working, I find it very useful to have a calendar at hand. I havetried to use some existing applications, but none could display thecalendar week number. So I decided to have my own calendar gadget. Theidea was to be able to use my gadget also on Windows XP. If you want tostart the program together with Windows, just create a shortcut to yourStartup folder.

Using the Code

In order to compile the code, you need to have the Qt framework and theQt Visual Studio Add-in (in case you compile using Visual Studio). Youcan download them from https://www.qt.io. Visual Studio or QtCreator canbe used to compile the code.
he Qt framework is compiled without SSL support. This is required inorder to establish a Google connection and to get the authenticationcode for the calendar service. OpenSLL is the best choice since it isfree. You can get a precompiled version from openssl.org.
To make the application installer, I used InnoSetup. This is an OpenSource project used for creating Windows installers. You can downloadthis from http://www.jrsoftware.org/isinfo.php.

Points of Interest

The Gadget class offers the basic functionality of a gadget: a niceframe, some fade in/out animation, and the most important feature isthat it keeps the window on the desktop when you choose “Showdesktop”.
Normally, when the user chooses to show the desktop, all windows arehidden. Unfortunately, there is no flag or style to avoid this behavior,and after a few hours of documentation on the net, I found the way to dothis. All that is required is to have a global event hook by providing acallback function.

Keep the ID for each object.

HWINEVENTHOOK globalHookId;

Create the event hook when the actual window is shown.

globalHookId = SetWinEventHook(EVENT_SYSTEM_FOREGROUND,
                               EVENT_SYSTEM_FOREGROUND, NULL,
                               (WINEVENTPROC)HandleWinEvent,0,0,0);

Remove the event hook from the system when the object is no longer required (in the destructor).

UnhookWinEvent(globalHookId);

HandleWinEvent is a global function that will loop through all the existing gadgets on the desktop and show them.

void CALLBACK HandleWinEvent(HWINEVENTHOOK hook, DWORD event, HWND hwnd,
                             LONG idObject, LONG idChild, DWORD dwEventThread,
                             DWORD dwmsEventTime)
{
    if (event == EVENT_SYSTEM_FOREGROUND)
    {
        TCHAR tmp[255];
        int r = GetClassName(hwnd, tmp, 250);
        if (wcscmp(tmp, L"WorkerW")==0)
        {
            Gadget::assureVisibility();
        }
    }
}

The CalendarService class is used to get all-day-events from your Google Calendar using the Google Calendar API. This is done in three steps: obtain an authentication code using the user name and password, get a session ID, query the calendar for events:

  1. For the authentication, a post needs to be set to "https://www.google.com/accounts/ClientLogin", and in return, the auth code will be received. This is kept and used for all other requests. The authentication part is done in AuthService. Details about the login can be read on http://code.google.com/intl/en/apis/gdata/docs/auth/overview.html#ClientLogin.
  2. For the authentication, a post needs to be set to "https://www.google.com/accounts/ClientLogin", and in return, the auth code will be received. This is kept and used for all other requests. The authentication part is done in AuthService.
  3. Trying to get the data directly will not work because all requests will redirected. Because of this, a get request must be performed at "http://www.google.com/calendar/feeds/default/private/full" using "X-If-No-Redirect" set to "1" in order to get a session ID. "X-Redirect-Location" will be read from the reply. Details about the Calendar API can be read on http://code.google.com/intl/en/apis/calendar/data/2.0/reference.html.
  QNetworkRequest request = QNetworkRequest();
  ...
  request.setRawHeader("X-If-No-Redirect", "1");
  1. In order to limit the results, a query is made using a start time and an end time. The start and end are the values represented by the first and last date shown in the calendar.
  QUrl address("http://www.google.com/calendar/feeds/default/private/full");

  address.addQueryItem("gsessionid", session);
  address.addQueryItem("start-min",
  QString("%1T00:00:00").arg(newStartDate.toString("yyyy-MM-dd")));
  address.addQueryItem("start-max",
  QString("%1T23:59:59").arg(newEndDate.toString("yyyy-MM-dd")));</pre>

I wanted to start the application together with Windows so the password is also saved in the application settings. I did not want to save the password in plain text, so I used the Windows Crypto API to do a simple RC4 encryption. This is by no means safe, but it protects my password from simple view. Here is the basic workflow (check out the full implementation in the Settings class):

// get a CSP handle
HCRYPTPROV CryptoProv = NULL;
if( !CryptAcquireContext(&CryptoProv, NULL, NULL, PROV_RSA_FULL, 0))
  if (!CryptAcquireContext(&CryptoProv, NULL, NULL,
                           PROV_RSA_FULL, CRYPT_NEWKEYSET))
    if (!CryptAcquireContext(&CryptoProv, NULL, NULL,
                             PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
      return "";

// import do-nothing exponent-of-one keypair
HCRYPTKEY ExponentOfOneKey = NULL;
CryptImportKey(CryptoProv, PrivateKeyWithExponentOfOne,
               sizeof(PrivateKeyWithExponentOfOne), 0, 0, &ExponentOfOneKey);

// import known key from RC4 template
HCRYPTKEY RC4Key = NULL;
DWORD cbBlob = sizeof(RC4KeyBlock);
CryptImportKey(CryptoProv, RC4KeyBlock, cbBlob, ExponentOfOneKey, 0, &RC4Key);

// encrypt the data
CryptEncrypt( RC4Key, 0, 1, 0, data, &length, length);

// clean up
CryptDestroyKey(RC4Key);
CryptDestroyKey(ExponentOfOneKey);
CryptReleaseContext(CryptoProv,0);