September, 2011

  • The Old New Thing

    Why are the building numbers on Microsoft main campus so erratic?

    • 21 Comments

    Carrie complains that the building numbers on Microsoft main campus are completely random. Why is building 22 near buildings 40 and 41, far, far away from building 24?

    Because the Microsoft campus evolved.

    Many many years ago, the space on which the central Microsoft campus resides was a mill. Eventually it became an office park, and when Microsoft decided to move its headquarters there, it carved out a little wooded area and constructed four buildings, logically numbered 1 through 4.

    Later, the campus expanded, and plans were drawn up for three more buildings, logically numbered (and placed) 5 through 7. Two of those buildings were constructed, but the third was not built for reasons shrouded in mystery. When the campus expanded a third time, the new buildings were numbered 8 through 11. Presumably, at this point, there were still plans to construct Building 7 someday, so the number remained assigned to the planned-but-not-yet-built building. (Even if the Building 7 plans had been abandoned, the number had already been used in the plans submitted to the City of Redmond, and revising them would have entailed additional paperwork for no real benefit aside from satisfying some anal-retentive compulsion to ensure that every number was used. People who worry about this probably are also waiting for DirectX 4.)

    The campus grew, and each time new buildings were added, they received the next available number. The result of this was that buildings with consecutive numbers could very well end up far apart on campus.

    When the Microsoft main campus expanded across highway 520, the people in charge of assigning numbers decided to assign numbers starting at 100 for buildings on the other side of the highway. Mind you, they didn't stick to that plan rigidly, as there are some buildings numbered in the high 90's on that part of the campus.

    Once the idea of assigning non-consecutive numbers was breached, the number-assigning people went to town. There is a cluster of buildings in the 40's, another in the 50's (with Building 50 being an outlier), and another in the 80's.

    So at least the numbers for newer buildings are a bit less crazy. But if you're looking for an older building, you're going to have a rough time of it.

    Maybe if the original building-numbering people had had the foresight to name the buildings after their GPS coordinates.

    Bonus chatter: In 2009, the building-numbering people tried to rename Buildings 116 through 119 to Studios E through H, presumably because they were across the street from Studios A through D. This "Rebranding Project" was largely mocked. (And of course, just to make things confusing, the new names appear to have been assigned randomly.)

    Bonus chatter 2: The original Building 100 was demolished to make way for The Commons. The soon-to-be-displaced residents of Building 100 had a "demolition party" on their last day in the building, wherein they went around spraying graffiti, smashing walls with sledgehammers, that sort of thing.

  • The Old New Thing

    Why waste your money on the car when it's the sound system you care about?

    • 20 Comments

    There is apparently a subculture of people who decide to economize on the car part of the "loud stereo in car" formula (since they really don't care about the car—it's all about the music) and put their loud stereo on the back of a bicycle instead.

    This quotation from the article caught my attention:

    "People say, 'It's the next best thing to having a system in a car.' But it's better because you don't even have to roll down the windows."

    I had been unsure what to think about people who drive down the street with their stereos blaring. Are they audiophiles who prefer their music loud? Or are they jerks who like to annoy other people with loud music? That quotation sort of settles it.

  • The Old New Thing

    The clipboard viewer linked list is no longer the responsibility of applications to maintain, unless they want to

    • 20 Comments

    Commenter Nice Clipboard Manager (with drop->clipboard) wonders why Windows still uses a linked list to inform programs about clipboard modifications. If any clipboard viewer fails to maintain the chain, then some windows won't get informed of the change, and if a clipboard viewer creates a loop in the chain, an infinite loop results.

    Well, sure, that's what happens if you use the old clipboard viewer chain. So don't use it. The old clipboard viewer chain remains for backward compatibility, but it's hardly the best way to monitor the clipboard. (This is another example of people asking for a feature that already exists.)

    Instead of using the clipboard viewer chain, just add yourself as a clipboard format listener via AddClipboardFormatListener. Once you've done that, the system will post you a WM_CLIPBOARDUPDATE message when the contents of the clipboard have changed, and you can respond accordingly. When you're done, call RemoveClipboardFormatListener.

    By using the clipboard format listener model, you let Windows worry about keeping track of all the people who are monitoring the clipboard, as Clipboarder Gadget suggested. (Mind you, Windows doesn't go so far as making each clipboard viewer think that it's the only viewer in the chain, because there may be applications which break the chain on purpose. Changing the chain behavior will break compatibility with those applications.)

    Let's turn our scratch program into a clipboard format listener.

    void
    SniffClipboardContents(HWND hwnd)
    {
     SetWindowText(hwnd, IsClipboardFormatAvailable(CF_TEXT)
                 ? TEXT("Has text") : TEXT("No text"));
    }
    
    BOOL
    OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
    {
     SniffClipboardContents(hwnd); // set initial title
     return AddClipboardFormatListener(hwnd);
    }
    
    void
    OnDestroy(HWND hwnd)
    {
     RemoveClipboardFormatListener(hwnd);
     PostQuitMessage(0);
    }
    
    ... add to window procedure ...
    
     case WM_CLIPBOARDUPDATE: SniffClipboardContents(hwnd); break;
    

    And that's it. Much, much simpler than writing a clipboard viewer, and much more robust since you aren't dependent on other applications not screwing up.

    There's another alternative to registering a clipboard listener and that's using the clipboard sequence number. The window manager increments the clipboard sequence number each time the contents of the clipboard change. You can compare the sequence number from two points in time to determine whether the contents of the clipboard have changed while you weren't looking.

    Now you have a choice. Do you use the notification method (clipboard format listener) or the polling method (clipboard sequence number)? The notification method is recommended if you want to do something as soon as the clipboard contents change. On the other hand, the polling method is more suitable if you perform calculations based on the clipboard contents and cache the results, and then later you want to verify that your cached results are still valid.

    For example, suppose you have a program with a Paste function, and pasting from the clipboard involves creating a complex data structure based on the clipboard contents. The user clicks Paste, you create your complex data structure, and insert it into the document. Your research discovers that a common operation is pasting the same contents several times. To optimize this, you want to cache the complex data structure so that if the user clicks Paste five times in a row, you only have to build the complex data structure the first time and you can just re-use it the other four times.

    void DocumentWindow::OnPaste()
    {
     if (m_CachedClipboardData == NULL ||
         GetClipboardSequenceNumber() != m_SequenceNumberInCache) {
      delete m_CachedClipboardData;
      m_SequenceNumberInCache = GetClipboardSequenceNumber();
      m_CachedClipboardData = CreateComplexDataFromClipboard();
     }
     if (m_CachedClipboardData) Paste(m_CachedClipboardData);
    }
    

    When the OnPaste method is called, we see if we have clipboard data cached from last time. If not, then clearly we need to create our complex data structure from the clipboard. If we do have clipboard data in our cache, we see if the clipboard sequence number has changed. If so, then the cached data is no longer valid and we have to throw it away and create it from scratch. But if we have cached data and the sequence number hasn't changed, then the cache is still valid and we can avoid calling CreateComplexDataFromClipboard.

    The old clipboard viewer is like DDE: please feel free to stop using it.

  • The Old New Thing

    Ah, the exciting world of cross-forest dogfood

    • 17 Comments

    The Windows group has its own domain (known as NTDEV for historical reasons) which operates separately from the domain forest operated by the Microsoft IT department. Various trust relationships need to be set up between them so that people on the Windows team can connect to resources managed by the Microsoft IT department and vice versa, but it generally works out okay.

    There are some glitches, but that's the price of dogfood. What better way to make sure that Windows works well in a cross-forest environment than by putting the entire Windows division in its own domain separate from the rest of the company?

    Subject: Newbie domain join question

    My user account is in the REDMOND domain. Should I join my machine to the REDMOND domain or the NTDEV domain?

    I was much amused by this cynical recommendation:

    Subject: RE: Newbie domain join question

    It's pretty simple, really.

    If you want to have trouble accessing NTDEV resources, then join the REDMOND domain.

    If you want to have trouble accessing REDMOND resources, then join the NTDEV domain.

  • The Old New Thing

    Why does my single-byte write take forever?

    • 15 Comments

    A customer found that a single-byte write was taking several seconds, even though the write was to a file on the local hard drive that was fully spun-up. Here's the pseudocode:

    // Create a new file - returns quickly
    hFile = CreateFile(..., CREATE_NEW, ...);
    
    // make the file 1GB
    SetFilePointer(hFile, 1024*1024*1024, NULL, FILE_BEGIN);
    SetEndOfFile(hFile);
    
    // Write 1 byte into the middle of the file
    SetFilePointer(hFile, 512*1024*1024, NULL, FILE_BEGIN);
    BYTE b = 42;
    / this write call takes several seconds!
    WriteFile(hFile, &b, &nBytesWritten, NULL);
    

    The customer experimented with using asynchronous I/O, but it didn't help. The write still took a long time. Even using FILE_FLAG_NO_BUFFERING (and writing full sectors, naturally) didn't help.

    The reason is that on NTFS, extending a file reserves disk space but does not zero out the data. Instead, NTFS keeps track of the "last byte written", technically known as the valid data length, and only zeroes out up to that point. The data past the valid data length are logically zero but are not physically zero on disk. When you write to a point past the current valid data length, all the bytes between the valid data length and the start of your write need to be zeroed out before the new valid data length can be set to the end of your write operation. (You can manipulate the valid data length directly with the Set­File­Valid­Data function, but be very careful since it comes with serious security implications.)

    Two solutions were proposed to the customer.

    Option 1 is to force the file to be zeroed out immediately after setting the end of file by writing a zero byte to the end. This front-loads the cost so that it doesn't get imposed on subsequent writes at seemingly random points.

    Option 2 is to make the file sparse. Mark the file as sparse with the FSCTL_SET_SPARSE control code, and immediately after setting the end of file, use the FSCTL_SET_ZERO_DATA control code to make the entire file sparse. This logically fills the file with zeroes without committing physical disk space. Anywhere you actually write gets converted from "sparse" to "real". This does open the possibility that a later write into the middle of the file will encounter a disk-full error, so it's not a "just do this and you won't have to worry about anything" solution, and depending on how randomly you convert the file from "sparse" to "real", the file may end up more fragmented than it would have been if you had "kept it real" the whole time.

  • The Old New Thing

    Invoking commands on items in the Recycle Bin

    • 14 Comments

    Once you've found the items you want in the Recycle Bin, you may want to perform some operation on them. This brings us back to our old friend, IContextMenu. At this point, you're just snapping two blocks together. You have one block called Retrieving properties from items in the Recycle Bin and you have another block called Invoking verbs on items.

    For the first block, let's assume you've written a function called WantToRestoreThisItem which studies the properties of a Recycle Bin item and determines whether you want to restore it. I leave this for you to implement, since I don't know what your criteria are. Maybe you want to restore files only if they were deleted from a particular directory. Maybe you want to restore files that were deleted while you were drunk. (This assumes you have some other computer program that tracks when you're drunk.)¹ Whatever. It's your function.

    For the second block, we have a helper function which should look awfully familiar.

    void InvokeVerb(IContextMenu *pcm, PCSTR pszVerb)
    {
     HMENU hmenu = CreatePopupMenu();
     if (hmenu) {
      HRESULT hr = pcm->QueryContextMenu(hmenu, 0, 1, 0x7FFF, CMF_NORMAL);
      if(SUCCEEDED(hr)) {
       CMINVOKECOMMANDINFO info = { 0 };
       info.cbSize = sizeof(info);
       info.lpVerb = pszVerb;
       pcm->InvokeCommand(&info);
      }
      DestroyMenu(hmenu);
     }
    }
    

    And now we snap the two blocks together.

    int __cdecl _tmain(int argc, PTSTR *argv)
    {
     HRESULT hr = CoInitialize(NULL);
     if (SUCCEEDED(hr)) {
      IShellItem *psiRecycleBin;
      hr = SHGetKnownFolderItem(FOLDERID_RecycleBinFolder, KF_FLAG_DEFAULT,
                                NULL, IID_PPV_ARGS(&psiRecycleBin));
      if (SUCCEEDED(hr)) {
       IEnumShellItems *pesi;
       hr = psiRecycleBin->BindToHandler(NULL, BHID_EnumItems,
                                         IID_PPV_ARGS(&pesi));
       if (hr == S_OK) {
        IShellItem *psi;
        while (pesi->Next(1, &psi, NULL) == S_OK) {
         if (WantToRestoreThisItem(psi)) {
          IContextMenu *pcm;
          hr = psi->BindToHandler(NULL, BHID_SFUIObject,
                                  IID_PPV_ARGS(&pcm));
          if (SUCCEEDED(hr)) {
           InvokeVerb(pcm, "undelete");
           pcm->Release();
          }
         }
         psi->Release();
        }
       }
       psiRecycleBin->Release();
      }
      CoUninitialize();
     }
     return 0;
    }
    

    One annoyance of the Recycle Bin is that, at least up until Windows 7, it ignores the CMIC_MASK_FLAG_NO_UI flag. It always displays a confirmation dialog if something dangerous is about to happen (like overwriting an existing file). To mitigate this problem, we can at least reduce the number of confirmations from one-per-file to just one by batching up all the objects we want to operate on into a single context menu. For this, it's easier to go back to the classical version of the program.

    int __cdecl _tmain(int argc, PTSTR *argv)
    {
     HRESULT hr = CoInitialize(NULL);
     if (SUCCEEDED(hr)) {
      IShellFolder2 *psfRecycleBin;
      hr = BindToCsidl(CSIDL_BITBUCKET, IID_PPV_ARGS(&psfRecycleBin));
      if (SUCCEEDED(hr)) {
       IEnumIDList *peidl;
       hr = psfRecycleBin->EnumObjects(NULL,
         SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, &peidl);
       if (hr == S_OK) {
        // in a real program you wouldn't hard-code a fixed limit
        PITEMID_CHILD rgpidlItems[100];
        UINT cpidlItems = 0;
        PITEMID_CHILD pidlItem;
        while (peidl->Next(1, &pidlItem, NULL) == S_OK) {
         if (WantToRestoreThisItem(psfRecycleBin, pidlItem) &&
             cpidlItems < ARRAYSIZE(rgpidlItems)) {
          rgpidlItems[cpidlItems++] = pidlItem;
         } else {
          CoTaskMemFree(pidlItem);
         }
        }
        // restore the items we collected
        if (cpidlItems) {
         IContextMenu *pcm;
         hr = psfRecycleBin->GetUIObjectOf(NULL, cpidlItems,
                         (PCUITEMID_CHILD_ARRAY)rgpidlItems,
                         IID_IContextMenu, NULL, (void**)&pcm);
         if (SUCCEEDED(hr)) {
          InvokeVerb(pcm, "undelete");
          pcm->Release();
         }
         for (UINT i = 0; i < cpidlItems; i++) {
          CoTaskMemFree(rgpidlItems[i]);
         }
        }
       }
       psfRecycleBin->Release();
      }
      CoUninitialize();
     }
     return 0;
    }
    

    In the course of the enumeration, we save the ITEMIDLISTs of all the items we want to restore, then create one giant context menu for all of them. This is the programmatic equivalent of multi-selecting the items from the Recycle Bin and then right-clicking. We then invoke the undelete verb on the entire group.

    Okay, so now suppose you want to restore the files, but instead of restoring them to their original locations, you want to restore them to a special folder. Like, say, C:\Files I deleted while I was drunk.¹ No problem. We just need a different block to snap into: The drag/drop block.

    void DropOnRestoreFolder(IDataObject *pdto)
    {
     IDropTarget *pdt;
     if (SUCCEEDED(GetUIObjectOfFile(NULL,
            L"C:\\Files I deleted while I was drunk",
            IID_PPV_ARGS(&pdt)))) {
      POINTL pt = { 0, 0 };
      DWORD dwEffect = DROPEFFECT_MOVE;
      if (SUCCEEDED(pdt->DragEnter(pdto, MK_LBUTTON,
                                   pt, &dwEffect))) {
       dwEffect &= DROPEFFECT_MOVE;
       if (dwEffect) {
        pdt->Drop(pdto, MK_LBUTTON, pt, &dwEffect);
       } else {
        pdt->DragLeave();
       }
      }
      pdt->Release();
     }
    }
    

    And now it's just a matter of snapping out the undelete block and snapping in the drag/drop block.

    int __cdecl _tmain(int argc, PTSTR *argv)
    {
     HRESULT hr = CoInitialize(NULL);
     if (SUCCEEDED(hr)) {
      IShellFolder2 *psfRecycleBin;
      hr = BindToCsidl(CSIDL_BITBUCKET, IID_PPV_ARGS(&psfRecycleBin));
      if (SUCCEEDED(hr)) {
       IEnumIDList *peidl;
       hr = psfRecycleBin->EnumObjects(NULL,
         SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, &peidl);
       if (hr == S_OK) {
        // in a real program you wouldn't hard-code a fixed limit
        PITEMID_CHILD rgpidlItems[100];
        UINT cpidlItems = 0;
        PITEMID_CHILD pidlItem;
        while (peidl->Next(1, &pidlItem, NULL) == S_OK) {
         if (WantToRestoreThisItem(psfRecycleBin, pidlItem) &&
             cpidlItems < ARRAYSIZE(rgpidlItems)) {
          rgpidlItems[cpidlItems++] = pidlItem;
         } else {
          CoTaskMemFree(pidlItem);
         }
        }
        // restore the items we collected
        if (cpidlItems) {
         IDataObject *pdto;
         hr = psfRecycleBin->GetUIObjectOf(NULL, cpidlItems,
                         (PCUITEMID_CHILD_ARRAY)rgpidlItems,
                         IID_IDataObject, NULL, (void**)&pdto);
         if (SUCCEEDED(hr)) {
          DropOnRestoreFolder(pdto);
          pdto->Release();
         }
         for (UINT i = 0; i < cpidlItems; i++) {
          CoTaskMemFree(rgpidlItems[i]);
         }
        }
       }
       psfRecycleBin->Release();
      }
      CoUninitialize();
     }
     return 0;
    }
    

    Footnotes

    ¹ If being drunk isn't your thing, then substitute some other form of impaired judgment.

  • The Old New Thing

    What happened to that suspicious-looking guy hanging around the entrance?

    • 14 Comments

    One of the fun parts of attending a conference is swapping stories with other professionals. Today's story is in honor of Global Security Week. (And retroactively in honor of the upcoming //build conference.)

    One of the attendees (let's call him Bob) shared with me a story of the time they had to make a change in one of their data centers. This particular change required physical presence at the facility, and to minimize impact on customers, the change was made at night (presumably because that's when demand was lowest).

    When Bob arrived at the data center, he walked past a suspicious-looking guy on his way to the door. Bob said hello to the security guard and swiped his access card through the card reader.

    Access denied.

    He tried again. Still no luck.

    He asked the security guard, "Hi, I'm supposed to be here to do a system upgrade, but my access card isn't working. Maybe they just put my name on a list instead of reprogramming the access system?"

    The security guard said, "Let's go down to the main office and find out."

    The two of them walked down to the main security office, and after some checking, everything got straightened out, and Bob and the security guard headed back to the entrance. As they left, the security guard asked the person at the main security desk, "Oh, there's a suspicious-looking guy hanging around the entrance. Did you see him?"

    The person at the main security desk said, "Yeah, he's hiding in the bushes."

    Bob (and I) found it interesting, amusing, and reassuring that the security guards were all fully aware of the suspicious guy and had been keeping tabs on him the whole time.

  • The Old New Thing

    What's the story with the parameters to the WM_INPUT_DEVICE_CHANGE message?

    • 13 Comments

    A customer found these strange macros in winuser.h:

    #if (_WIN32_WINNT >= 0x0601)
    #define GET_DEVICE_CHANGE_WPARAM(wParam)  (LOWORD(wParam))
    #elif (_WIN32_WINNT >= 0x0501)
    #define GET_DEVICE_CHANGE_LPARAM(lParam)  (LOWORD(lParam))
    #endif /* (_WIN32_WINNT >= 0x0601) */
    

    According to the documentation for the WM_INPUT_DEVICE_CHANGE message, the wParam is the operation code and the lParam is a handle to the device that changed. Given that definition, the correct macro would be GET_DEVICE_CHANGE_WPARAM. What's up with the bogus GET_DEVICE_CHANGE_LPARAM macro?

    The macro was incorrectly defined in Windows Vista. In the Windows 7 version of the Platform SDK, the correct macro was added, but in order to avoid introducing a breaking change to existing code, the old broken macro remains in place in order to retain bug-for-bug compatibility with existing code.

    Even though the macro didn't work, there is a good possibility that code exists which relied on it anyway. For example, people may have read the documentation, read the macro, realized that the macro was wrong, and worked around the bug like this:

    case WM_INPUT_DEVICE_CHANGE:
     return OnInputDeviceChange(GET_DEVICE_CHANGE_LPARAM(wParam),
                                (HANDLE)lParam);
    

    To avoid breaking this code, the old broken definition remains in the header file. But at least it's defined only if you say that you want the Windows Vista version of the header file, so at least people won't use the bad macro going forward.

  • The Old New Thing

    What happens to a sent message when SendMessageTimeout reaches its timeout?

    • 13 Comments

    The Send­Message­Timeout function tries to send a message, but gives up if the timeout elapses. What exactly happens when the timeout elapses?

    It depends.

    The first case is if the receiving thread never received the message at all. (I.e., if during the period the sender is waiting, the receiving thread never called GetMessage, PeekMessage, or a similar message-retrieval function which dispatches inbound sent messages.) In that case, if the timeout is reached, then the entire operation is canceled; the window manager cleans up everything and makes it look as if the call to SendMessageTimeout never took place. The message is removed from the list of the thread's non-queued messages, and when it finally gets around to calling GetMessage (or whatever), the message will not be delivered.

    The second case is if the receiving thread received the message, and the message was delivered to the destination window procedure, but the receiving thread is just slow to process the message and either return from its window procedure or call Reply­Message. In that case, if the timeout is reached, then the sender is released from waiting, but the message is allowed to proceed to completion.

    Since people seem to like tables, here's a timeline showing the two cases.

    Sending thread Case 1 Case 2
    SendMessageTimeout(WM_X) called ... not responding ... ... not responding ...
    ... not responding ... ... not responding ...
    ... not responding ... GetMessage() called
    ... not responding ... WndProc(WM_X) called
    ... not responding ... WndProc(WM_X) still executing
    timeout elapses ... not responding ... WndProc(WM_X) still executing
    SendMessageTimeout(WM_X) returns ... not responding ... WndProc(WM_X) still executing
    ... not responding ... WndProc(WM_X) returns
    GetMessage() called
    (message WM_X not received)

    Notice that in case 2, the window manager has little choice but to let the window procedure continue with the message. After all, time travel has yet to be perfected, so the window manager can't go back in time and tell the younger version of itself, (Possibly with a slow-motion "Nooooooooooooo" for dramatic effect.) "No, don't give him the message; he won't finish processing it in time!"

    If you are in case 2 and the message WM_X is a system-defined message that is subject to marshaling, then the data is not unmarshaled until the window procedure returns. It would be bad to free the memory out from under a window procedure. On the other hand, if the message is a custom message, then you are still on the hook for keeping the values valid until the window procedure is done.

    But wait, how do I know when the window procedure is done? The Send­Message­Timeout function doesn't tell me! Yup, that's right. If you need to do cleanup after message processing is complete, you should use the Send­Message­Callback function, which calls you back when the receiving thread completes message processing. When the callback fires, that's when you do your cleanup.

  • The Old New Thing

    Random notes from //build/ 2011

    • 11 Comments

    Here are some random notes from //build/ 2011, information of no consequence whatesoever.

    • A game we played while walking to and from the convention center was spot the geek. "Hey, there's a guy walking down the street. He's wearing a collared shirt and khakis, with a black bag over his shoulder, staring into his phone. I call geek."
    • One of the stores on Harbor Boulevard has the direct-and-to-the-point name Brand Name Mart, or as it was known at night (due to burnt-out lights) Bra d N    Mart.
    • In the room where the prototype devices were being handed out to attendees, the boxes were stacked in groups. Each group consisted of 512 devices. Why 512? Because the boxes were stack 8 across, 8 high, and 8 wide. Somebody was being way too cute.
    • Nearly all the machines were handed out in the first 55 minutes of availability. During that time, they were distributed at a rate of one machine per second. Kudos to the event staff for managing the enormous onslaught! Also, kudos to my colleagues who flew down a week early for the thankless task of preparing 5,000 computers to be handed out!
    • In the way, way back of the Expo room were a bunch of makeshift private meeting rooms for the various vendors. As you can see from the picture, it was a depressing hallway of what looked like sensory deprivation chambers or interrogation rooms from 1984. All that was missing was the screaming. Upon seeing the photo, one of my friends remarked, "Mental institutions look more cheerful than this," and she should know: She's a professional nurse.
    • The setup at our demo booth consisted of a table with a touch monitor, with the image duplicated onto a wall-mounted display for better visibility. More than once, somebody would walk up to the wall-mounted display and try touching it. The game I played was to surreptitiously manipulate the touch monitor to match what the person was doing on the wall-mounted display, and see how long before they figure out that somebody was messing with them. (It didn't take long.)
    • Two of my colleagues played an even more elaborate trick. One of them stood about ten feet from the wall-mounted display and waved his arms as if he were using a Kinect. The other matched his colleague's actions on the touch monitor. So if you see a media report about seeing a Kinect-enabled Windows 8 machine at the //build/ conference, you'll know that they were pranked.
    • John Sheehan stopped by our booth, and around his neck were so many access passes he could've played solitaire. Security was tight, as you might expect, and any time he needed to go backstage, the security guard would ask to see his pass. "I'd just hold up all of them, saying 'Go ahead, pick one. Whatever pass you're looking for, it's in here somewhere.'"
    • One of my colleagues stopped by our booth, and I made some remark about the backstage passes around her neck. She replied, "You so don't want a backstage pass. Because if you have one, it means that you will be working on three hours' sleep for days on end."
    • Instead of "Hello, world," I think Aleš should have acknowledged that the programming landscape have changed, and the standard first application to write for a new platform is now a fart app. Wouldn't that have been an awesome app to have written on stage at the keynote?
    • You may have noticed that everybody was wearing a white or green T-shirt under their //build/ conference uniform. When we arrived, each staff member was issued two uniform shirts, plus four undershirts. And for people who didn't understand what that meant, there were instructions to wear a different undershirt each day. (The engineer would optimize the solution to two uniform shirts and only two undershirts, with instructions to wear undershirts the first two days and skip them on the last two days.)
    • Ever since PDC sessions started being put online, attending sessions has tended to take a back seat to business networking as a primary goal for coming to the conference, since you can always catch up on sessions later. As a result, the Expo floor tended to remain busy even when breakout sessions were taking place. Also, the last day of the conference tended to be a bit dead, with a lot of people leaving early, and the remaining people just taking it easy. But this year was different: People actually went to the breakout sessions! And despite being held on the final day of the conference, Matt Merry's session was not only well-attended, it overflowed.

Page 2 of 3 (26 items) 123