Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Detecting if laptop lid is closed/integrated screen is off

Is there a Windows API to detect if a laptop lid is closed (= integrated laptop screen is off)?


There's already the "same" question asked:
Get current laptop lid state

Though the (self-)accepted answer relies on an integrated screen "device" being removed, when the lid closes. But that does not happen on all laptops. Some keep the screen "available" to the system (while not displaying anything actually), even when the lid is closed. This means that the Windows desktop still stretches over the closed screen (if the "Multiple Displays" settings is set to "Extend these displays").

I have not determined yet, if this behavior can be configured or if it is driver-specific:
Remove closed laptop screen from Windows desktop

But even on such systems, the OS knows that the lid closes, as it can shutdown/sleep the machine when it does. And it broadcasts a notification (WM_POWERBROADCAST):
Detect laptop lid closure and opening


Background: I have an application that starts on the same display, where it was closed the last time. If it was closed on the integrated laptop screen and the lid is closed the next time the application starts (because the user is now using an external monitor), my application starts on the now-invisible integrated laptop screen.

Hence I want to detect that the lid is closed and force the application onto an external monitor.

So I'm looking either for a way to detect, if lid is closed. Or for a way to detect, that a particular screen is off (what would be a cleaner solution).

like image 471
Martin Prikryl Avatar asked Sep 17 '15 13:09

Martin Prikryl


1 Answers

Sounds like you don't really care if the lid is closed or not and just want to know if the screen area where you are about to launch your application is available or not.

If the OS "still uses the shut off screen for its extended desktop" then that means (from the OS's point of view) that the screen is available to be used for applications. In other words - your application would not be the only one suffering from that issue. Though I have to say I have never observed that particular behavior first-hand.

If you need to move your application while it is running then you can register for the RegisterPowerSettingNotification and act on it.

However if you are launching and need to know if the screen is on or off you have two options:

EnumDisplayDevices

This will provide you with the information on whether your screen is attached to a desktop and is active. This is "system info" that you get from the API in User32.dll

DISPLAY_DEVICE ddi;
ddi.cb = sizeof(ddi);
DWORD iDevNum = 0; // or iterate 0..15
EnumDisplayDevices(NULL, iDevNum, &ddi, /*EDD_GET_DEVICE_INTERFACE_NAME*/0);
if( (ddi.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER) == 0 &&
    (ddi.StateFlags & DISPLAY_DEVICE_ACTIVE) != 0 ){...}

DXGI (DX11)

This gives you basically the same info as above but with a more modern approach (and potentially fewer false positives). Of course that would require you to link-in DXGI for this to work and include the header which will increase your application size:

#include <atltypes.h>

IDXGIAdapter * pAdapter; 
std::vector <IDXGIAdapter*> vAdapters; 
IDXGIFactory* pFactory = NULL; 
// Create a DXGIFactory object.
if(FAILED(CreateDXGIFactory(__uuidof(IDXGIFactory) ,(void**)&pFactory)))
{
    return;
}
for(UINT i = 0; pFactory->EnumAdapters(i, &pAdapter) != DXGI_ERROR_NOT_FOUND; ++i){
    DXGI_ADAPTER_DESC ad = {0};
    if(SUCCEEDED(pAdapter->GetDesc(&ad))){
        UINT j = 0;
        IDXGIOutput * pOutput;
        while(pAdapter->EnumOutputs(j, &pOutput) != DXGI_ERROR_NOT_FOUND)
        {
            DXGI_OUTPUT_DESC od = {0};
            if(SUCCEEDED(pOutput->GetDesc(&od))){
                // in here you can access od.DesktopCoordinates
                // od.AttachedToDesktop tells you if the screen is attached
            }
            pOutput->Release();
            ++j;
        }
    }
    pAdapter->Release();
} 

if(pFactory)
{
    pFactory->Release();
}

Hope that helps.

Direct3D9

This method also provides display information but in a slightly different way - via a list of adapters and monitors attached to those adapters. Remember to link-in d3d9 library for this to work:

void d3d_adapterInfo(IDirect3D9 * _pD3D9, UINT _n)
{
    D3DADAPTER_IDENTIFIER9 id;
    const DWORD flags = 0;
    if(SUCCEEDED(_pD3D9->GetAdapterIdentifier(_n, flags, &id))){
        // id provides info on Driver, Description, Name
        HMONITOR hm = _pD3D9->GetAdapterMonitor(_n);
        // and based on that hm you get the same monitor info as
        // with the first method
    }
}

void d3d_enumDisplays()
{
    cout << endl << "--- Information by Direct3D9 ---" << endl;
    IDirect3D9 * pD3D9 = Direct3DCreate9(D3D_SDK_VERSION);
    const auto nAdapters = pD3D9->GetAdapterCount();
    cout << "A total of " << nAdapters << " adapters are listed by Direct3D9" << endl;
    for(UINT i = 0; i < nAdapters; ++i){
        d3d_adapterInfo(pD3D9, i);
    }
    pD3D9->Release();
}

All 3 code snippets are from some of my projects so you can just copy-paste the code and it should work (baring some minor fixes for missing functions or variables as I was modifying the code on-the-fly to reduce its size when posted it here)

like image 80
YePhIcK Avatar answered Oct 03 '22 21:10

YePhIcK