-->

Is there a way to detect changes in Focus Assist (

2020-04-07 12:33发布

问题:

I'd like to change the presence state in an App automatically to DND when Focus Assist is turned on.

So two questions basically:

  • Is it possible to check Focus Assist state through e.g. Windows 10 SDK?

    There is a similar question for Quiet Hours in Windows 8 here: Get windows Quiet hours from Win32 or C# API, though it's not clear whether it also still applies to "Focus Assist" since this is no longer a true or false value. Quiet hours had only ON/OFF state while Focus Assist can be OFF/PRIORITY/ALARMS.

  • The more interesting question though, which is not answered in the post mentioned above: is there an event I could register to, to get notified about state changes?

    The goal is to get notified right away, when Focus Assist status changes in order to not have to query the registry on a regular basis.

回答1:

As far as I know there is no officially documented way of getting the Focus assist status.

It still can be accessed by querying the WNF State of the feature, although this is completely undocumented and not officially supported.

The various states for WNF Data have been reverse engineered, so the one for Focus Assist is WNF_SHEL_QUIETHOURS_ACTIVE_PROFILE_CHANGED.

The C++ sample below shows how to get the state of Focus Assist feature (mostly off, priority_only and alarm_only).

Once again, be wary that this is not officially supported by Microsoft:

#include <Windows.h>
#include <iostream>
#include <string>
#include <map>

// from ntdef.h
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)

// from ntdef.h
typedef struct _WNF_STATE_NAME
{
    ULONG Data[2];
} WNF_STATE_NAME;

typedef struct _WNF_STATE_NAME* PWNF_STATE_NAME;
typedef const struct _WNF_STATE_NAME* PCWNF_STATE_NAME;

typedef struct _WNF_TYPE_ID
{
    GUID TypeId;
} WNF_TYPE_ID, *PWNF_TYPE_ID;

typedef const WNF_TYPE_ID* PCWNF_TYPE_ID;

typedef ULONG WNF_CHANGE_STAMP, *PWNF_CHANGE_STAMP;


enum FocusAssistResult
{
    not_supported = -2,
    failed = -1,
    off = 0,
    priority_only = 1,
    alarms_only = 2
};

std::map<int, std::string> result_map = {
    {-2, "Not Supported"},
    {-1, "Failed"},
    {0, "Off"},
    {1, "Priority Only"},
    {2, "Alarm Only"}
};

typedef NTSTATUS (NTAPI *PNTQUERYWNFSTATEDATA)(
    _In_ PWNF_STATE_NAME StateName,
    _In_opt_ PWNF_TYPE_ID TypeId,
    _In_opt_ const VOID* ExplicitScope,
    _Out_ PWNF_CHANGE_STAMP ChangeStamp,
    _Out_writes_bytes_to_opt_(*BufferSize, *BufferSize) PVOID Buffer,
    _Inout_ PULONG BufferSize);

int main(int argc, CHAR** argv)
{
    // note: ntdll is guaranteed to be in the process address space.
    const auto h_ntdll = GetModuleHandle(_T("ntdll"));

    // get pointer to function
    const auto pNtQueryWnfStateData = PNTQUERYWNFSTATEDATA(GetProcAddress(h_ntdll, "NtQueryWnfStateData"));
    if (!pNtQueryWnfStateData)
    {
        std::cerr << "[-] Error: couldn't get pointer to NtQueryWnfStateData() function." << std::endl;
        return -1;
    }

    // state name for active hours (Focus Assist)
    WNF_STATE_NAME WNF_SHEL_QUIETHOURS_ACTIVE_PROFILE_CHANGED{0xA3BF1C75, 0xD83063E};

    // note: we won't use it but it's required
    WNF_CHANGE_STAMP change_stamp = {0};

    // on output buffer will tell us the status of Focus Assist
    DWORD buffer = 0;
    ULONG buffer_size = sizeof(buffer);

    if (NT_SUCCESS(pNtQueryWnfStateData(&WNF_SHEL_QUIETHOURS_ACTIVE_PROFILE_CHANGED, nullptr, nullptr, &change_stamp, 
        &buffer, &buffer_size)))
    {
        // check if the result is one of FocusAssistResult
        if (result_map.count(buffer) == 0)
        {
            std::cout << "Focus Assist result is unknown." << std::endl;
        }
        else
        {
            std::cout << "Focus assist state: " << result_map[buffer] << std::endl;
        }
    }
    else
    {
        std::cerr << "[-] Error while calling NtQueryWnfStateData." << std::endl;
        return -1;
    }

    return 0;
}