August, 2003

  • The Old New Thing

    Setup could not verify integrity of file

    When you try to install a patch, you may get the error message "Setup could not verify integrity of file. Make sure the cryptographic service is running."

    This typically means that the signature catalog has been corrupted. There was a patch to fix this problem, but of course if you haven't been keeping up with your patches you probably don't have that patch yet either, and of course you can't install that patch yet either because the signature catalog is corrupted...

    Fortunately, the nice people at http://www.updatexp.com/ have written up a web page describing how to fix it.

  • The Old New Thing

    Painting only when your window is visible on the screen

    • 6 Comments

    Sometimes you want to perform an activity, such as updating a status window, only as long as the window is not covered by another window.

    The easiest way to determine this is by not actually trying to determine it. For example, here's how the taskbar clock updates itself:

    1. It computes how much time will elapse before the next minute ticks over.
    2. It calls SetTimer with the amount of time it needs to wait.
    3. When the timer fires, it does an InvalidateRect of itself and the kills the timer.
    4. The WM_PAINT handler draws the current time, then returns to step 1.

    If the taskbar clock is not visible, because it got auto-hidden or because somebody covered it, Windows will not deliver a WM_PAINT message, so the taskbar clock will simply go idle and consume no CPU time at all. Here's how we can make our scratch program do the same thing:

    Our scratch program displays the current time. It also puts the time into the title bar so we can see the painting action (or lack thereof) when the window is covered or minimized, by watching the taskbar.

    void
    PaintContent(HWND hwnd, PAINTSTRUCT *pps)
    {
        TCHAR szTime[100];
        if (GetTimeFormat(LOCALE_USER_DEFAULT, 0, NULL, NULL,
                          szTime, 100)) {
            SetWindowText(hwnd, szTime);
            TextOut(pps->hdc, 0, 0, szTime, lstrlen(szTime));
        }
    }
    

    Here is the timer callback that fires once we decide it's time to update. It merely kills the timer and invalidates the rectangle. The next time the window becomes uncovered, we will get a WM_PAINT message. (And if the window is uncovered right now, then we'll get one almost immediately.)

    void CALLBACK
    InvalidateAndKillTimer(HWND hwnd, UINT uMsg,
                           UINT_PTR idTimer, DWORD dwTime)
    {
        KillTimer(hwnd, idTimer);
        InvalidateRect(hwnd, NULL, TRUE);
    }
    

    Finally, we add some code to our WM_PAINT handler to restart the timer each time we paint a nonempty rectangle.

    void
    OnPaint(HWND hwnd)
    {
        PAINTSTRUCT ps;
        BeginPaint(hwnd, &ps);
        if (!IsRectEmpty(&ps.rcPaint)) {
            // compute time to next update - we update once a second
            SYSTEMTIME st;
            GetSystemTime(&st);
            DWORD dwTimeToNextTick = 1000 - st.wMilliseconds;
            SetTimer(hwnd, 1, dwTimeToNextTick, InvalidateAndKillTimer);
        }
    
        PaintContent(hwnd,&ps);
        EndPaint(hwnd, &ps);
    }
    

    Compile and run this program, and watch it update the time. When you minimize the window or cover it with another window, the time stops updating. If you take the window and drag it to the bottom of the screen so only the caption is visible, it also stops updating: The WM_PAINT message is used to paint the client area, and the client area is no longer on-screen.

    This method also stops updating the clock when you switch to another user or lock the workstation, though you can't really tell because there's no taskbar you can consult to verify. But you can use your speakers: Stick a call to MessageBeep(-1); in the PaintContent() function, so you will get an annoying beep each time the time is repainted. When you switch to another user or lock the workstation, the beeping will stop.

    This technique of invalidation can be extended to cover the case where only one section of the screen is interesting: Instead of invalidating the entire client area, invalidate only the area that you want to update, and restart the timer only if that rectangle is part of the update region. Here are the changes we need to make.

    // The magic updating rectangle
    RECT g_rcTrigger = { 50, 50, 200, 100 };
    

    When the timer fires, we invalidate only the magic rectangle instead of the entire client area. (As an optimization, I disabled background erasure for reasons you'll see later.)

    void CALLBACK
    InvalidateAndKillTimer(HWND hwnd, UINT uMsg,
                           UINT_PTR idTimer, DWORD dwTime) {
        KillTimer(hwnd, idTimer);
        InvalidateRect(hwnd, &g_rcTrigger, FALSE);
    }
    

    To make it more obvious where the magic rectangle is, we draw it in the highlight color and put the time inside it. By using the ETO_OPAQUE flag, we draw both the foreground and background simultaneously. Consequently, we don't need to have it erased for us.

    void
    PaintContent(HWND hwnd, PAINTSTRUCT *pps)
    {
        TCHAR szTime[100];
        if (GetTimeFormat(LOCALE_USER_DEFAULT, 0, NULL, NULL,
                          szTime, 100)) {
            SetWindowText(hwnd, szTime);
            COLORREF clrTextPrev = SetTextColor(pps->hdc,
                                GetSysColor(COLOR_HIGHLIGHTTEXT));
            COLORREF clrBkPrev = SetBkColor(pps->hdc,
                                    GetSysColor(COLOR_HIGHLIGHT));
            ExtTextOut(pps->hdc, g_rcTrigger.left, g_rcTrigger.top,
                       ETO_CLIPPED | ETO_OPAQUE, &g_rcTrigger,
                       szTime, lstrlen(szTime), NULL);
            SetBkColor(pps->hdc, clrBkPrev);
            SetTextColor(pps->hdc, clrTextPrev);
        }
    }
    

    Finally, the code in the WM_PAINT handler needs to check the magic rectangle for visibility instead of using the entire client area.

    void
    OnPaint(HWND hwnd)
    {
        PAINTSTRUCT ps;
        BeginPaint(hwnd, &ps);
        if (RectVisible(ps.hdc, &g_rcTrigger)) {
            // compute time to next update - we update once a second
            SYSTEMTIME st;
            GetSystemTime(&st);
            DWORD dwTimeToNextTick = 1000 - st.wMilliseconds;
            SetTimer(hwnd, 1, dwTimeToNextTick, InvalidateAndKillTimer);
        }
        PaintContent(hwnd,&ps);
        EndPaint(hwnd, &ps);
    }
    

    Run this program and do various things to cover up or otherwise prevent the highlight box from painting. Observe that once you cover it up, the title stops updating.

    As I noted above, this technique is usually enough for most applications. There's an even more complicated (and more expensive) method, too, which I'll cover next week.

  • The Old New Thing

    Hardware backwards compatibility

    • 11 Comments

    Backwards compatibility applies not only to software. It also applies to hardware. And when hardware goes bad, the software usually takes the blame.

    The HLT instruction tells the CPU to shut itself down until the next hardware interrupt. This is a big win on laptops since it reduces power consumption and thereby saves your lap from third-degree burns.

    We (well, specifically, Jeff) had this implemented and working in Windows 95 but discovered to our dismay that there were many laptops (some from a major manufacturer) which would lock up unrecoverably if you issued a HLT instruction.

    So we had to back it out.

    Then the aftermarket HLT programs came out and people wrote, "Stupid Microsoft. Why did they leave this feature out of Windows." I had to sit quietly while people accused Microsoft of being stupid and/or lazy and/or selfish.

    But now the statute of limitations has expired so at least I can say something (though I'm still not going to name that major manufacturer, nice try).

    My favorite bad hardware, though, was a system which would crash if the video card was put in an expansion slot too far away from the power supply. Manufacturers will do anything to save a nickel.

    And yet Windows 95 ran on almost all of this bad hardware. Why did we go to all this effort to accomodate bad hardware? Consider:

    • You have a computer that works okay.
    • You go to the store and buy Windows 95.
    • You take it home and install it.
    • Your computer crashes.

    Whom do you blame? Hint: Not your computer manufacturer.

    I have more hardware stories, but I have a meeting soon so I'll have to stop here for today.

  • The Old New Thing

    Why are these unwanted files/folders opening when I log on?

    • 5 Comments

    I get called on frequently to do troubleshooting, so I figure I'd share some entries from my private bag of tricks.  (And there are some remarks for programmers hidden here too.)

    Problem 1. A folder like C:\Program Files\LitWare opens each time you log on.

    Reason: Your system contains two sibling directories where one is a strict prefix of the second. For example,

    C:\Program Files\LitWare
    C:\Program Files\LitWare Deluxe
    

    If you go to regedit, you will likely find under

    HKEY_LOCAL_MACHINE\Microsoft\Windows\CurrentVersion\Run
    

    or

    HKEY_CURRENT_USER\Microsoft\Windows\CurrentVersion\Run
    

    an entry that refers to a program in the longer directory, like

    Reminder=REG_SZ:C:\Program Files\LitWare Deluxe\reminder.exe
    

    What's more, the reference such as the one above will not have quotation marks to protect the embedded spaces in the name.

    What's going on is that LitWare Deluxe wants to run C:\Program Files\LitWare Deluxe\reminder.exe, but due to the spaces, this first gets parsed as

    app = C:\Program
    command line=Files\LitWare Deluxe\reminder.exe 

    This fails, so the system tries again with

    app = C:\Program Files\LitWare
    command line=Deluxe\reminder.exe 

    and this succeeds because you have a folder called C:\Program Files\LitWare. Edit the string and add the quotation marks.

    Note to programmers: This is why it's important to quote your filenames if they contain spaces.

    Problem 2. A desktop.ini file opens when you log on.

    Reason: The System and Hidden attributes for the file desktop.ini in the directory

    C:\Documents and Settings\All Users\Start Menu\Startup

    or

    C:\Documents and Settings\yourname\Start Menu\Startup

    have been lost. Alternatively, you went to the advanced Folder Options and disabled "Hide protected operating system files (Recommended)".

    If a file is marked with both the System and Hidden attributes, Explorer will not enumerate it, thereby hiding it from the user. If you disable "Hide protected operating system files", then this rule is suppressed.

    When you log on, one of the things that Explorer does is enumerate the contents of your Startup folders and runs each file it finds. If the desktop.ini is not marked System+Hidden (or you disabled the rule that filters them out), then it will be opened.

    What is this file for?

    This file is used to support Windows XP's Multilingual User Interface, which allows you to change the language you use to interact with Windows, so for example you could say, "I want everything to be in French" and Windows will translate all its menus, shortcuts, dialog boxes, etc. into French. Specifically, this file instructs Windows how to translate the word "Startup" into French, German, Spanish, etc.

    Programmatically, you use the SHSetLocalizedName function to set the multilingual name for a file or folder.

  • The Old New Thing

    What are all these files in my C:\WINDOWS\CSC directory?

    • 12 Comments
    This is where Windows keeps the files that you have marked for being available offline.  (CSC was the working name for the feature now called Offline Files. It stands for Client-Side Caching.)
  • The Old New Thing

    What are those little overlay icons?

    • 3 Comments

    Windows XP ships with a number of icon overlays.

    • A small arrow. Everybody knows this one: It's the shortcut overlay.
    • A hand, palm up. This is the "sharing" overlay. A folder with this overlay is the root of a file share.
    • A downward-pointing blue arrow. This is the "to be written to CD" overlay.
    • A pair of blue swirling arrows. This sometimes baffles people. This means that the item is available offline. (You have to enable offline folders to get this.)
    • A black clock. This really baffles people. This means that the file has been archived to tape and will take a very long time to access.

    The black clock is particularly baffling because you sometimes see it even if your system is not equipped with Hierarchical Storage Management. When this happens, it's because some program (typically a setup program) didn't check error codes properly.

    CopyFileAttributes(LPCTSTR pszSrc, LPTSTR pszDst)
    {
        SetFileAttributes(pszDst, GetFileAttributes(pszSrc));
    }
    

    The above code fragment fails to check for an error code from GetFileAttributes. It so happens that GetFileAttributes fails by returning the value 0xFFFFFFFF. If you fail to check this error code, you end up setting every possible attribute on the destination, including FILE_ATTRIBUTE_OFFLINE. FILE_ATTRIBUTE_OFFLINE is the flag that tells Explorer that the file has been archived to tape.

  • The Old New Thing

    Rotating the Z-order

    • 1 Comments

    Sometimes you run into a conflict where you need the controls laid out in one Z-order to get hit-testing to work, but need a different order to get keyboard TAB order working.

    For example, consider this dialog template, which has been simplified for expository purposes.

    ////////////////////////////////////////////////////////////////////////
    //
    //  Note: The ListView control has to be listed BEFORE the tab control in
    //  order for drag-drop to work properly.
    //
    
    IDD_MAIN DIALOGEX  0, 0, 335, 270
    STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
                        | DS_CONTEXTHELP | DS_SHELLFONT
    CAPTION "Blah blah"
    FONT 8, "MS Shell Dlg"
    BEGIN
        CONTROL         "List",IDC_LIST,WC_LISTVIEW,LVS_REPORT | 
                        WS_BORDER | WS_TABSTOP |
                        LVS_SHOWSELALWAYS,15,46,305,111
        CONTROL         "Tab",IDC_TAB,WC_TABCONTROL,
                        WS_TABSTOP,7,24,321,141
        PUSHBUTTON      "&Import...",IDC_IMPORT,7,172,51,14
        PUSHBUTTON      "&Export...",IDC_EXPORT,62,172,51,14
        PUSHBUTTON      "&Remove",IDC_REMOVE,117,172,51,14
        DEFPUSHBUTTON   "&Close",IDOK,277,249,51,14
    END
    

    The dialog looks like this:

      _______  _______
     / Tab   \/ Tab   \_____________________
    |                                       \
    | +-[List]-----------------------------+ |
    | |                                    | |
    | |                                    | |
    | |                                    | |
    | |                                    | |
    | +------------------------------------+ |
    |________________________________________|
    
    
    [ Import ]   [ Export ]  [ Remove ]
    
                                     [ Close ]
    

    Drag/drop will call WindowFromPoint() to determine which window receives the drop. The window closest to the top of the Z-order (towards the beginning of the dialog template) will be chosen.

    We want drops to go to the listview, not the tab control, so the ListView (IDC_LIST) needs to go ahead of the Tab control (IDC_TAB).

    However, doing this breaks accessibility, because the tab order follows the Z-order, too. Consequently, the Tab order on the dialog is

    List -> Tab -> Import -> Export -> Remove -> Close
    

    This is an undesirable tab order, since it causes focus to jump upwards (from List to Tab). To get the tab order right, you need to put Tab before List.

    This is where rotation comes in.

    Start by putting the controls on the page in the order necessary for tabbing.

    Tab -> List -> Import -> Export -> Remove -> Close
    

    Recall that the tab order is circular. When you are on the last control and hit TAB, you go to the first one. So you really should view the tab order as a circle:

       /-->  Tab  ---\
       |             |
      Close          v
       ^            List
       |             |
      Remove         v
       ^            Import
       |             |
       \-- Export <--/
    

    Once you view it this way, you realize that you can rotate the circle and the tab order remains unchanged. So let's rotate it so List comes first, since we need List to come first in the Z-order.

       /-->  List ---\
       |             |
      Tab            v
       ^            Import
       |             |
      Close          v
       ^            Export
       |             |
       \-- Remove <--/
    

    Now cut the circle open, yielding the linear diagram:

    List -> Import -> Export -> Remove -> Close -> Tab
    

    This gives us our final dialog template:

    ////////////////////////////////////////////////////////////////////////
    //
    //  Note: The ListView control has to be listed BEFORE the tab control in
    //  order for drag-drop to work properly.
    //
    
    IDD_MAIN DIALOGEX  0, 0, 335, 270
    STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
                        | DS_CONTEXTHELP | DS_SHELLFONT
    CAPTION "Blah blah"
    FONT 8, "MS Shell Dlg"
    BEGIN
        CONTROL         "List",IDC_LIST,WC_LISTVIEW,LVS_REPORT | 
                        WS_BORDER | WS_TABSTOP |
                        LVS_SHOWSELALWAYS,15,46,305,111
        PUSHBUTTON      "&Import...",IDC_IMPORT,7,172,51,14
        PUSHBUTTON      "&Export...",IDC_EXPORT,62,172,51,14
        PUSHBUTTON      "&Remove",IDC_REMOVE,117,172,51,14
        DEFPUSHBUTTON   "&Close",IDOK,277,249,51,14
        CONTROL         "Tab",IDC_TAB,WC_TABCONTROL,
                        WS_TABSTOP,7,24,321,141
    END
    

    Now you get the best of both worlds. List is at the top of the Z-order, and the tab order is still correct.

  • The Old New Thing

    Windows brings out the Rorschach test in everyone

    • 10 Comments

    It seems that no matter what you do, somebody will get offended.

    Every Windows 95 box has an anti-piracy hologram on the side. The photographer chose his infant son as his model, since the human face is very hard to copy accurately. The baby sits next to a computer, and as you turn the hologram, his arm rises and points at the computer monitor, which bursts into a Windows 95 logo.

    How cute. And everybody loves babies.

    Until we got a complaint from a government (who shall remain nameless for obvious reasons) that was upset with Windows 95 because it depicted naked children.

    "Naked children!?" we all thought to ourselves.

    They were complaining about the hologram on the box. The baby wasn't wearing a shirt. Even though the baby was visible only from the waist up, the offended government assumed that he wasn't wearing pants either.

    We had to produce a new hologram. In the new hologram, the baby is wearing a shirt and overalls. But since this was a rush job, we didn't have time to do the arm animation.

    So if you still have your copy of Windows 95, go look at the hologram. If the baby in your hologram isn't wearing a shirt, you have a genuine collector's item. I have seen the "naked baby" hologram but unfortunately my copy of Windows 95 has a clothed baby.

    If you hunt around the web, you can find lots of other people who claim to have found subliminal messages in Windows 95. My favorite is the one who claims to have found images in the clouds bitmap. Hey, they're clouds. They're Nature's Rorschach Test.

    Windows XP had its own share of complaints. The original wallpaper for Windows XP was Red Moon Desert, until people claimed that Red Moon Desert looked like a pair of buttocks. People also thought that one of the generic people used in the User Accounts control panel people looked like Hitler. And one government claimed the cartoon character in the original Switch Users dialog looked like an obscene body part. We had to change them all. But it makes me wonder about the mental state of our beta testers...

  • The Old New Thing

    Knitting a pumpkin

    • 3 Comments
    I've decided to change the color scheme to match my latest knitting project:  A pumpkin hat. (Previous food-themed hats were raspberry and eggplant.) I just got a call from the yarn store that the yarn for my sweater has come in, and I can't wait.
  • The Old New Thing

    Why isn't my time zone highlighted on the world map?

    • 35 Comments

    In the original release of Windows 95, you could change your time zone by clicking on the map, and the time zone you selected would highlight. Similarly, you could change your Region Settings by clicking on the world map. This was one of those little touches that made Windows 95 that much more fun to use.

    But we had to remove those features within months of release, even though we based both of the maps on the borders officially recognized by the United Nations.

    In early 1995, a border war broke out between Peru and Ecuador and the Peruvian government complained to Microsoft that the border was incorrectly placed. Of course, if we complied and moved the border northward, we'd get an equally angry letter from the Ecuadorian government demanding that we move it back. So we removed the feature altogether.

    The time zone map met a similar fate. The Indian government threatened to ban all Microsoft software from the country because we assigned a disputed region to Pakistan in the time zone map. (Any map that depicts an unfavorable border must bear a government stamp warning the end-user that the borders are incorrect. You can't stamp software.) We had to make a special version of Windows 95 for them.

    Geopolitics is a very sensitive subject.

Page 1 of 3 (29 items) 123