February, 2013

  • The Old New Thing

    What's the story of the onestop.mid file in the Media directory?


    If you look in your C:\Windows\Media folder, you'll find a MIDI file called onestop. What's the story behind this odd little MIDI file? Aaron Margosis considers this file a security risk because "if an attacker can cause that file to be played, it will cause lasting mental pain and anguish to everybody within earshot."

    Despite Wikipedia's claims[citation needed], the file is not an Easter Egg. The file was added in in Windows XP with the comment "Add cool MIDI files to replace bad old ones." So as bad as onestop is, the old ones must have been even worse!

    Okay, but why were they added?

    For product support.

    The product support team wants at least one MIDI file present on the system by default for troubleshooting purposes. That way, problems with MIDI playback can be diagnosed without making the customer go to a Web page and download a MIDI file. When asked why the song is so awful, the developer who added the file explained, "Believe it or not, OneStop is 'less bad' than the ones that it replaced. (Dance of the Sugar Plum Fairy, etc.)" Another reason for replacing the old MIDI file is that the new one exercises more instruments.

    The song was composed by David Yackley.

    On the other hand, we lost clock.avi.

  • The Old New Thing

    For the Nitpickers: Enhanced-mode Windows 3.0 didn't exactly run a copy of standard-mode Windows inside the virtual machine


    Generally speaking, Enhanced-mode Windows 3.0 ran a copy of standard-mode Windows inside the virtual machine. This statement isn't exactly true, but it's true enough.

    Commenter Nitpicker objected, "Why are you threatening us with the Nitpicker's Corner for asking about this issue instead of explaining it once and linking it everywhere?"

    Okay, first of all, as far as I can tell, you're the first person to ask about the issue. So you can't say "Everybody who asks about the issue is threatened with the Nitpicker's Corner" because up until you made your comment, nobody ever asked. Okay, well, technically you can say it, because every statement quantified over the empty set is true. But it is equally true that, at the time you made your comment, that "Everybody who asks about the issue is awarded a new car." So it is not a meaningfully true statement.

    I haven't bothered explaining the issue because the issue has never been central to the main point of whatever article happens to bring it up. The statement is true enough for the purpose of discussion, and the various little corners in which the statement breaks down have no bearing on the original topic. Nitpickers would point out that you can't combine velocities by simple addition because of the laws of Special Relativity. Even when the situation under discussion takes place at non-relativistic speeds.

    As for the suggestion, "Explain it once and link it everywhere," you're assuming that I can even explain it once, that doing so is less work than just saying "not exactly true, but true enough," and that I would enjoy explaining it in the first place.

    If you don't like it, you can ask for your money back.

    Okay, I went back and dug through the old Windows 3.0 source code to answer this question. It took me about four hours to study it all, try to understand what the code was doing, and then distill the conclusions into this article. Writing up the results took another two hours. That's six hours I could've spent doing something enjoyable.

    The 16-bit Windows kernel was actually three kernels. One if you were using an 8086 processor, another if you were using an 80286 processor, and a third if you were using an 80386 processor. The 8086 kernel was a completely separate beast, but the 80286 and 80386 kernels shared a lot of code in common. The major difference between the 80286 and 80386 kernels was in how they managed memory, because the descriptor tables on the 80386 were a different format from the descriptor tables on the 80286. The 80386 memory manager could also take advantage of the new 32-bit registers.

    But the difference between the 80286 and 80386 kernels were not based on whether you were running Standard or Enhanced mode. If you're running on an 80386 processor, then you get the 80386 kernel, regardless of whether you're using Standard or Enhanced mode Windows. And since Enhanced mode Windows required an 80386 processor, the behavioral changes between Standard and Enhanced mode were restricted to the 80386 kernel.

    The 80386 kernel was designed to run as a DPMI client. It asked the DPMI host to take it into protected mode, then used the DPMI interface to do things like allocate selectors and allocate memory. If you ran Windows in Standard mode, then the DPMI host was a custom-built DOS extender that was created just for Standard mode Windows. If you ran Windows in Enhanced mode, then the DPMI host was the 32-bit virtual machine manager. Abstracting to the DPMI interface allowed a single 80386 kernel to run in both Standard and Enhanced modes.

    And in fact if you ran Enhanced mode Windows with paging disabled, then the code running in the 80386 kernel was pretty much the same code that ran if you had run the 80386 kernel under Standard mode Windows.

    One obvious place where the behavior changed was in the code to manage MS-DOS applications, because Enhanced mode Windows could multi-task MS-DOS applications, and Standard mode Windows could not.

    Another place where the behavior changed was in in the code to allocate more selectors: The attempt to retry after extending the local descriptor table was skipped if you were running under the Standard mode DOS extender, because the Standard mode DOS extender didn't support extending the local descriptor table.

    And another difference is that the Windows idle loop in Enhanced mode would issue a special call to release its time slice to any multi-tasking MS-DOS applications. (If you were running in Standard mode, there were no multi-tasking MS-DOS applications, so there was nobody to release your time slice to.)

    Another thing special that the 80386 kernel did was register with the virtual machine manager so that it could display an appropriate message when you pressed Ctrl+Alt+Del. For example, you saw this message if you hit Ctrl+Alt+Del while there was a hung Windows application:

    Contoso Deluxe Music Composer

    This Windows application has stopped responding to the system.

    *  Press ESC to cancel and return to Windows.
    *  Press ENTER to close this application that is not responding.
       You will lose any unsaved information in this application.
    *  Press CTRL+ALT+DEL again to restart your computer. You will
       lose any unsaved information in all applications.

    But all these differences are minor in the grand scheme of things. The window manager behaved the same in Standard mode and Enhanced mode. GDI behaved the same in Standard mode and Enhanced mode. Printer drivers behaved the same in Standard mode and Enhanced mode. Only the low-level kernel bits had to change behavior between Standard mode and Enhanced mode, and as you can see, even those behavior changes were relatively minor.

    That's why I said it was "true enough" that what was running inside the virtual machine was a copy of Standard-mode Windows.

  • The Old New Thing

    If you can't find the function, find the caller and see what the caller jumps to


    You're debugging a program and you want to set a breakpoint on some function, say, netapi32!Ds­Address­To­Site­NameW, but when you execute the bp netapi32!Ds­Address­To­Site­NameW command in the debugger, the debugger says that there is no such function.

    The Advanced Windows Debugging book says that the bp command should set a breakpoint on the function, but the debugger says that the symbol cannot be found. I used the x netapi32!* command to see that the debugger did find a whole bunch of symbols, and it says that the symbols were loaded (from the public symbol store), but netapi32!Ds­Address­To­Site­NameW isn't among them. The MSDN documentation says that Ds­Address­To­Site­NameW is in the netapi32.dll, but it's not there! I can't believe you guys stripped that function out of the symbol file, since it's a function that people will want to set a breakpoint on.

    Okay, first let's rule out the conspiracy theory. The symbols were not stripped from the public symbols. And even if they were, that shouldn't stop you, because after all, the loader has to be able to find the function when it loads your program, so it's gotta be obtainable even without symbols.

    Don't be helpless. You already have the tools to figure out where the function is.

    Just write a program that calls the function, then load it into the debugger and see what the destination of the call instruction is. You don't even have to pass valid parameters to the function call, since you're never actually executing the code; you're just looking at it.

    And hey looky-here, you already have a program that calls the function: The program you're trying to debug! So let's see where it goes.

    0:001>u contoso!AwesomeFunction
    00407352 call [contoso!__imp__DsAddressToSiteNameW (0040f104)]
    0:001>u poi 0040f104
    7f014710 push ebp
    7f014711 mov esp, ebp

    There you go. The code for the function is in logoncli.dll.

    What happened? How did you end up in logoncli.dll?

    What you saw was the effect of a DLL forwarder. The code for the function Ds­Address­To­Site­NameW doesn't live in netapi32.dll. Instead, netapi32.dll has an export table entry that says "If anybody comes to me asking for Ds­Address­To­Site­NameW, send them to logoncli!Ds­Address­To­Site­NameW instead."

    Officially, the function is in netapi32.dll for linkage purposes, but internally the function has been forwarded to another DLL for implementation. It's like a telephone call-forwarding service for DLL functions, except that instead of forwarding telephone calls, it forwards function calls. You publish a phone number in all your marketing materials, and behind the scenes, you set up the number to forward to the phone of the person responsible for sales. That way, if that person quits, or the responsibility for selling the product changes, you can just update the call-forwarding table, and all the calls get routed to the new person.

    That's what happenned here. The MSDN phone book lists the function as being in netapi32.dll, and whenever a call comes in, it gets forwarded to wherever the implementation happens to be. And the implementation has moved around over time, so you should continue calling netapi32!Ds­Address­To­Site­NameW and let the call-forwarding do the work of getting you to the implementation.

    Don't start calling logoncli directly, thinking that you're cutting out the middle man, or in a future version of Windows, your program may start failing with a "This number is no longer in service" error, like calling the direct office number for the previous sales representative, only to find that he left the company last month.

  • The Old New Thing

    What does 1#J mean? A strange corner case of the printing of special values


    As a puzzle, commenter nobugz asks, "What kind of infinity is 1.#J?"

    double z = 0;
    printf("%.2f", 1/z);

    Now, the division by zero results in IEEE positive infinity, would would normally be printed as 1#INF. But the catch here is that the print format says "Display at most two places after the decimal point." But where is the decimal point in infinity?

    The Visual C runtime library arbitrarily decided that all of the exceptional values have one digit before the decimal (namely, the "1"). Actually, it turns out that this puzzle might be an answer to Random832's question, "What's the 1 for?" Maybe the 1 is there so that there is a digit at all.

    Okay, so now you have one digit before the decimal (the "1"), and now you need to show at most two places after the decimal. But "#INF" is too long to fit into two characters. The C runtime then says, "Well, then I'd better round it off to two places, then."

    The first character is "#". The second character is "I". Now we need to round it. That's done by inspecting the third character, which is "N". We all learned in grade school that you round up if the next digit is 5 or greater. And it so happens that the code point for "N" is numerically higher than the code point for "5", so the value is rounded up by incrementing the previous digit. Incrementing "I" gives you "J".

    That's why printing IEEE positive infinity to two places gives you the strange-looking "1#J". The J is an I that got rounded up.

    I doubt this behavior was intended; it's just a consequence of taking a rounding algorithm intended for digits and applying it to non-digits.

    Of course, in phonetics, rounding an i produces a ü. Imagine the nerdiness of rounding "1#INF" to two places and producing "1#Ü". That would have been awesome.

  • The Old New Thing

    The curious pattern of pre-emptively rejecting the solution to your problem


    A frustrating pattern that shows up occasionally in customer questions is the case where the customer poses a problem, and pre-emptively rejects the mechanism explicitly designed to solve that problem.

    How can we change the widget color without using IWidget::Set­Color?

    Um, the whole point of IWidget::Set­Color is to change the color of a widget. Why are you rejecting the mechanism whose sole purpose in life is to solve the very problem you are having?

    Usually, if you press hard enough, they will cough up the reason why they think they cannot use the solution specifically designed to do what they want. Various excuses tend to come up over and over.

    One excuse is the belief that the proposed solution does not work in a particular scenario. "We cannot use ACLs because they don't work on network volumes." Um, yes they do. Check it out.

    Or that the proposed solution doesn't fit their choice of technology. "We are programming in a language that does not support COM objects. We can only p/invoke to C-style APIs." Well, you can work around that problem by writing a helper DLL that exposes a C-style API, and implements it by calling the COM method.

    Or that the proposed solution violates some vague corporate policy. "We have a corporate policy that users cannot change widget colors, so the IWidget::Set­Color method returns E_ACCESS­DENIED. We're looking for a way around that policy." Okay, well, now that's something you need to take up with the people who establish your corporate policies. Don't come to us looking for ways to circumvent corporate policy.

    One time, the reason came from our own technical support staff: "We cannot write a C++ program that calls IWidget::Set­Color and provide it to the customer because we are not a developer support team. We are not allowed to send compiled binaries to the customer for liability reasons, and we generally do not send source code because our customers typically do not have the expertise or desire to install Visual Studio and the Platform SDK just to compile and run a five-line C++ program. (Did I mention that we are not a developer support team?) Can it be done from a batch file?"

    Yeah, how about this batch file:

     >changeColor.cs echo using System;
    >>changeColor.cs echo class Program {
    >>changeColor.cs echo public static void Main(string[] args) {
    >>changeColor.cs echo ...
    >>changeColor.cs echo }
    >>changeColor.cs echo }
    %windir%\Microsoft.NET\Framework\v4.0.30319\csc changeColor.cs
    changeColor blue

    Only half-joking.

    The non-joking answer is "The customer can take this information to a developer support team, or at least somebody who will write the program for them, if they don't know how to write a program themselves." Microsoft Consulting Services exists for this, but that is likely overkill for a five-line program.

  • The Old New Thing

    Display a custom thumbnail for your application (and while you're at it, a custom live preview)


    By default, when the taskbar or any other application wants to display a thumbnail for a window, the result is a copy of the window contents shrunk down to the requested size. Today we're going to override that behavior and display a custom thumbnail.

    Take the program from last week and make these changes:

    #include <dwmapi.h>
    OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
      g_hicoAlert = LoadIcon(nullptr, IDI_EXCLAMATION);
      g_wmTaskbarButtonCreated = RegisterWindowMessage(
      BOOL fTrue = TRUE;
      DwmSetWindowAttribute(hwnd, DWMWA_FORCE_ICONIC_REPRESENTATION,
                            &fTrue, sizeof(fTrue));
      DwmSetWindowAttribute(hwnd, DWMWA_HAS_ICONIC_BITMAP,
                            &fTrue, sizeof(fTrue));
      return TRUE;

    We start by enabling custom thumbnails by setting the DWMWA_HAS_ICONIC_BITMAP attribute to TRUE. This overrides the default thumbnail generator and allows us to provide a custom one.

    Next is a helper function that I broke out from this program because it's useful on its own. It simply creates a 32bpp bitmap of the desired size and optionally returns a pointer to the resulting bits.

    HBITMAP Create32bppBitmap(HDC hdc, int cx, int cy,
                              RGBQUAD **pprgbBits = nullptr)
     BITMAPINFO bmi = { 0 };
     bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
     bmi.bmiHeader.biWidth = cx;
     bmi.bmiHeader.biHeight = cy;
     bmi.bmiHeader.biPlanes = 1;
     bmi.bmiHeader.biBitCount = 32;
     bmi.bmiHeader.biCompression = BI_RGB;
     void *pvBits;
     HBITMAP hbm = CreateDIBSection(hdc, &bmi,
               DIB_RGB_COLORS, &pvBits, NULL, 0);
     if (pprgbBits) *pprgbBits = static_cast<RGBQUAD*>(pvBits);
     return hbm;

    Next, we take our Paint­Content function and make it render into a DC instead:

    RenderContent(HDC hdc, LPCRECT prc)
     LOGFONTW lf = { 0 };
     lf.lfHeight = prc->bottom - prc->top;
     wcscpy_s(lf.lfFaceName, L"Verdana");
     HFONT hf = CreateFontIndirectW(&lf);
     HFONT hfPrev = SelectFont(hdc, hf);
     wchar_t wszCount[80];
     swprintf_s(wszCount, L"%d", g_iCounter);
     FillRect(hdc, prc, GetStockBrush(WHITE_BRUSH));
     DrawTextW(hdc, wszCount, -1, const_cast<LPRECT>(prc),
     SelectFont(hdc, hfPrev);

    In our case, we will want to render into a bitmap:

    GenerateContentBitmap(HWND hwnd, int cx, int cy)
     HDC hdc = GetDC(hwnd);
     HDC hdcMem = CreateCompatibleDC(hdc);
     HBITMAP hbm = Create32bppBitmap(hdcMem, cx,cy);
     HBITMAP hbmPrev = SelectBitmap(hdcMem, hbm);
     RECT rc = { 0, 0, cx, cy };
     RenderContent(hdcMem, &rc);
     SelectBitmap(hdcMem, hbmPrev);
     ReleaseDC(hwnd, hdc);
     return hbm;

    We can use this function when DWM asks us to generate a custom thumbnail or a custom live preview bitmap.

    UpdateThumbnailBitmap(HWND hwnd, int cx, int cy)
     HBITMAP hbm = GenerateContentBitmap(hwnd, cx, cy);
     DwmSetIconicThumbnail(hwnd, hbm, 0);
    UpdateLivePreviewBitmap(HWND hwnd)
     RECT rc;
     GetClientRect(hwnd, &rc);
     HBITMAP hbm = GenerateContentBitmap(hwnd, rc.right - rc.left,
                                         rc.bottom - rc.top);
     DwmSetIconicLivePreviewBitmap(hwnd, hbm, nullptr, 0);
     // WndProc
      UpdateThumbnailBitmap(hwnd, HIWORD(lParam), LOWORD(lParam));

    One of the quirks of the WM_DWM­SEND­ICONIC­THUMB­NAIL message is that it passes the x- and y-coordinates backwards. Most window messages put the x-coordinate in the low word and the y-coordinate in the high word, but WM_DWM­SEND­ICONIC­THUMB­NAIL does it the other way around.

    Since we're generating a custom thumbnail and live preview bitmap, we need to let the window manager know that the custom rendering is out of date and needs to be re-rendered: Invalidate the custom bitmaps when the counter changes.

    void OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify)
      switch (id) {
      case IDC_INCREMENT:
        InvalidateRect(hwnd, nullptr, TRUE);

    And finally, just to be interesting, we'll also stop rendering content into our main window.

    PaintContent(HWND hwnd, PAINTSTRUCT *pps)
        // do nothing

    Run this program and observe that the window comes up blank. Ah, but if you hover over the taskbar button, the custom thumbnail will appear, and that custom thumbnail has the number 0 in it. Click on the button in the thumbnail, and the number in the custom thumbnail increments.

    As a bonus, move the mouse over the thumbnail to trigger Aero Peek. The live preview bitmap contains the magic number! Move the mouse away, and the magic number vanishes.

    Now, this was an artificial example, so the effect is kind of weird. However, you can imagine using this in less artificial cases where the result is useful. You application might be a game, and instead of using the default thumbnail which shows a miniature copy of the game window, you can have your thumbnail be a tiny scoreboard or focus on a section of the board. For example, if you are a card game, the thumbnail might show just the cards in your hand.

    I can't think of a useful case for showing a live preview bitmap different from the actual window. The intended use for a custom live preview bitmap is for applications like Web browsers which want to minimize a tab's memory usage when it is not active. When a tab becomes inactive, the browser can destroy all graphics resources except for a bitmap containing the last-known-valid contents of the window, and use that bitmap for the thumbnail and live preview.

  • The Old New Thing

    What does -1.#IND mean?: A survey of how the Visual C runtime library prints special floating point values


    As every computer scientist knows, the IEEE floating point format reserves a number of representations for infinity and non-numeric values (collectively known as NaN, short for not a number). If you try to print one of these special values with the Visual C runtime library, you will get a corresponding special result:

    Output Meaning
    1#INF Positive infinity
    -1#INF Negative infinity
    1#SNAN Positive signaling NaN
    -1#SNAN Negative signaling NaN
    1#QNAN Positive quiet NaN
    -1#QNAN Negative quiet NaN
    1#IND Positive indefinite NaN
    -1#IND Negative indefinite NaN

    Positive and negative infinity are generated by arithmetic overflow, or when the mathematical result of an operation is infinite, such as taking the logarithm of positive zero. (Don't forget that IEEE floating point supports both positive and negative zero.) For math nerds: IEEE arithmetic uses affine infinity, not projective, so there is no point at infinity.

    Signaling and quiet NaNs are not normally generated by computations (with one exception noted below), but you can explicitly create one for a floating-point type by using the std::numeric_limits<T> class, methods signaling_NaN() and quiet_NaN().

    Recall that there is not just one signaling and quiet NaN, but rather a whole collection of them. The C runtime does not distinguish among them when printing, however. All signaling NaNs are reported as 1#SNAN, regardless of the signal bits. The C runtime does report the sign of the NaN, for what little that is worth.

    The weird one is the Indefinite NaN, which is a special type of quiet NaN generated under specific conditions. If you perform an invalid arithmetic operation like add positive infinity and negative infinity, or take the square root of a negative number, then the IEEE standard requires that the result be a quiet NaN, but it doesn't appear to specify what quiet NaN exactly. Different floating point processor manufacturers chose different paths. The term Indefinite NaN refers to this special quiet NaN, whatever the processor ends up choosing it to be.

    Some floating point processors generate a quiet NaN with the signal bits clear but the sign bit set. Setting the sign bit makes the result negative, so on those processors, you will see the indefinite NaN rendered as a negative indefinite NaN. (The x86 is one of these processors.)

    Other floating point processors generate a quiet NaN with the signal bits and the sign bit all clear. Clearing the sign bit makes the result positive, so on those processors, you will see the indefinite NaN rendered as a positive indefinite NaN.

    In practice, the difference is not important, because either way, you have an indefinite NaN.

  • The Old New Thing

    This code would be a lot faster if it weren't for the synchronization


    This is a story from a friend of a friend, which makes it probably untrue, but I still like the story.

    One of my colleagues jokingly suggested that we could speed up our code by adding these lines to our project

    #define EnterCriticalSection(p) ((void)0)
    #define LeaveCriticalSection(p) ((void)0)

    I replied, "You think you're joking, but you're not."

    According to legend, there was a project whose product was running too slow, so they spun off a subteam to see what architectural changes would help them improve their performance. The subteam returned some time later with a fork of the project that they had "tuned". And it was indeed the case that the performance-tuned version ran a lot faster.

    Later, the development team discovered that part of the "tuning" involved simply deleting all the synchronization. They didn't replace it with lock-free algorithms or anything that clever. They just removed all the critical sections.

  • The Old New Thing

    Now that version 4 of the .NET Framework supports in-process side-by-side runtimes, is it now okay to write shell extensions in managed code?


    Many years ago, I wrote, "Do not write in-process shell extensions in managed code." Since I originally wrote that article, version 4 of the .NET Framework was released, and one of the features of that version is that it supports in-process side-by-side runtimes. Does that mean that it's now okay to write shell extensions in managed code?

    The answer is still no.

    The Guidance for implementing in-process extensions has been revised, and it continues the recommendation against writing shell extensions and Internet Explorer extensions (and other types of in-process extensions) in managed code, even if you're using version 4 or higher.

    Although version 4 addresses the side-by-side issue, it is still the case that the .NET Framework is a high-impact runtime, and that there are various part of COM interop in the .NET Framework that are not suitable for use in an extension model designed around native code.

    Note that managed code remains acceptable for out-of-process extensions.

  • The Old New Thing

    Display an overlay on the taskbar button


    Today's "Little Program" displays an overlay on the taskbar button. I've seen some people call this a "badge", but "overlay" is the official term.

    Start with our scratch program and make the following changes:

    #include <comip.h>
    #include <comdef.h>
    #include <shlobj.h>
    #include <shellapi.h>
    _COM_SMARTPTR_TYPEDEF(ITaskbarList3, __uuidof(ITaskbarList3));

    I decided to shake things up and use a different smart pointer library: com_ptr_t. (That'll teach you to complain that I don't use a smart pointer library in my samples. Now you get to complain that I use the wrong smart pointer library.)

    HICON g_hicoAlert;
    UINT g_wmTaskbarButtonCreated;
    OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
      g_hicoAlert = LoadIcon(nullptr, IDI_EXCLAMATION);
      g_wmTaskbarButtonCreated = RegisterWindowMessage(
      return TRUE;

    Our overlay icon is the system exclamation point icon. I chose this because I'm lazy.

    bool g_fHasOverlay = false;
    void UpdateOverlayIcon(HWND hwnd)
      HICON hicon = g_fHasOverlay ? g_hicoAlert : nullptr;
      PCWSTR pszDescription = g_fHasOverlay ?
                            L"Attention required" : nullptr;
      ITaskbarList3Ptr sptb3;
      sptb3->SetOverlayIcon(hwnd, hicon, pszDescription);
    void OnChar(HWND hwnd, TCHAR ch, int cRepeat)
      if (ch == ' ') {
        g_fHasOverlay = !g_fHasOverlay;
        HANDLE_MSG(hwnd, WM_CHAR, OnChar);
          if (uiMsg != 0 && uiMsg == g_wmTaskbarButtonCreated) {

    A real program would have error checking, of course.

    Press the space bar, and the overlay will be toggled on and off.

    If you're really clever, you might generate your overlay icons on the fly, say, if you wanted to report the number of unread messages or something.

    I've heard that there's one program out there that abuses the ITaskbar­List3::Set­Progress­State method by changing its progress state repeatedly, causing its taskbar button to cycle through different colors to get the user's attention.

    Just a reminder: The user interface guidelines say that the way to get the user's attention is to flash your taskbar button. Various parts of the system understand this convention and respond to it. (For example, the taskbar will temporarily unhide if a button starts flashing, and accessibility tools know how to signal the flash state to the user.) As always, the shell reserves the right to block this sort of abusive behavior in the future, just like it has done with abusive notification icons.

Page 1 of 3 (29 items) 123