如何区分用户交互式应用程序窗口和非交互式应用程序窗口?

问题描述 投票:0回答:1

这个 Windows 程序应该是一个小型终端实用程序,可以整齐地堆叠正在运行的应用程序的窗口。

用户传入他们想要执行此操作的可执行应用程序的名称,程序会查找可执行名称与用户输入紧密匹配的可能进程。用户在询问时确认他们选择的应用程序,并且程序堆叠这些窗口(不给它们焦点)

问题

该程序的问题在于,它会调出并堆叠应用程序的所有窗口,包括用户不打算与之交互的窗口(有时,这些窗口在应用程序启动时会隐藏)。这会导致桌面出现问题,并且我不确定如何区分面向用户的窗口和其他窗口。是否有一组常规窗口通常使用的属性标志需要测试?

见下图。这些窗口并不像普通窗口那样真正具有交互性,并且具有奇怪的属性。

请原谅代码的长度,但它会在Windows环境下编译。

enter image description here

// C++ standard library
#include <cassert>
#include <cstdio>
#include <fcntl.h>
#include <io.h>
#include <iostream>
#include <set>
#include <string>
#include <vector>

// Windows API
#include <Windows.h>

// Windows API helpers
#include <TlHelp32.h>

/**
 * @brief Takes a snapshot of all processes in the system and returns a vector containing each process entry.
 *
 */
static std::vector<PROCESSENTRY32> getAllProcesses( ) {
    // Take the snapshot.
    HANDLE snapshotHandle = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 ); // @Todo Handle errors.
    assert( snapshotHandle != INVALID_HANDLE_VALUE );

    // Get all the processes in the snapshot.
    PROCESSENTRY32 processEntry;
    processEntry.dwSize = sizeof( PROCESSENTRY32 );                  // Must initialize this member before iterating.
    bool noErrors = Process32First( snapshotHandle, &processEntry ); // @Todo Handle errors.
    assert( noErrors ); // For now, assume this always succeeds the first time.
    std::vector<PROCESSENTRY32> result;
    result.push_back( processEntry ); // Append the first process entry.
    // Query for the next process entry.
    DWORD errorCode = ERROR_SUCCESS;
    if ( !Process32Next( snapshotHandle, &processEntry ) ) {
        errorCode = GetLastError( );
    }
    while ( errorCode != ERROR_NO_MORE_FILES ) {
        result.push_back( processEntry );
        errorCode = ERROR_SUCCESS;
        if ( !Process32Next( snapshotHandle, &processEntry ) ) {
            errorCode = GetLastError( );
        }
    }

    CloseHandle( snapshotHandle );
    return result;
}

/**
 * @brief Returns the name of the executable file for a process.
 *
 */
static std::wstring getProcessName( PROCESSENTRY32 const& p ) {
    std::wstring result;
    size_t const nameLength = wcslen( p.szExeFile );
    for ( size_t i = 0; i < nameLength; ++i ) {
        result.push_back( p.szExeFile[i] );
    }
    return result;
}

/**
 * @brief Returns the ID of the process that opened a given window.
 *
 */
static DWORD getWindowProcessID( HWND windowHandle ) {
    DWORD processID = 0;
    DWORD threadID = GetWindowThreadProcessId( windowHandle, &processID );
    if ( threadID == 0 ) {
        std::abort( );
    }
    return processID;
}

/**
 * @brief Determines whether a given window was opened by a given process.
 *
 * @return true if the process opened the window.
 * @return false if the process did not open the window.
 */
static bool windowOpenedByProcess( HWND windowHandle, DWORD processID ) {
    return getWindowProcessID( windowHandle ) == processID;
}

/**
 * @brief Returns a vector containing the IDs of the given processes.
 *
 */
static std::vector<DWORD> getProcessIDs( std::vector<PROCESSENTRY32> const& processes ) {
    std::vector<DWORD> ids;
    for ( const PROCESSENTRY32& i : processes ) {
        ids.push_back( i.th32ProcessID );
    }
    return ids;
}

/**
 * @brief Determines whether a given window was opened by any of the given processes.
 *
 * @return true if the window was opened by one of the processes.
 * @return false if the window was not opened by any of the processes.
 */
static bool windowOpenedByProcessAnyOf( HWND windowHandle, std::vector<PROCESSENTRY32> const& processes ) {
    std::vector<DWORD> processIDs = getProcessIDs( processes );
    for ( const DWORD& i : processIDs ) {
        if ( windowOpenedByProcess( windowHandle, i ) ) {
            return true;
        }
    }
    return false;
}

/**
 * @brief Returns a string where all alphabetic characters in the given string have been demoted to lowercase.
 *
 */
template <class Container, class Element> Container toLower( Container const& c ) {
    Container result;
    for ( Element const& e : c ) {
        if ( isalpha( e ) ) {
            int const lowerAsInt = tolower( e );
            // assert((lowerAsInt >= CHAR_MIN) && (lowerAsInt <= CHAR_MAX));
            result.append( 1, static_cast<Element>( lowerAsInt ) );
        } else {
            result.append( 1, e );
        }
    }
    return result;
}

/**
 * @brief Takes a vector of processes and removes the ones whose executable name does not contain the search key as a
 * substring.
 *
 */
static void filterProcessesNotContainingName( std::vector<PROCESSENTRY32>& processes, std::wstring const& searchFor ) {
    std::wstring searching = toLower<std::wstring, wchar_t>( searchFor ); // For case-insensitive search.
    size_t       count = processes.size( );
    for ( size_t i = 0; count > 0; --count ) {
        std::wstring processName = toLower<std::wstring, wchar_t>( getProcessName( processes.at( i ) ) );
        size_t       foundAt = processName.find( searching );
        if ( foundAt != std::wstring::npos ) {
            ++i; // Found a loose match. Move on to the next one.
        } else {
            processes.erase( processes.begin( ) + i ); // No match. Remove this one.
        }
    }
}

/**
 * @brief Takes a vector of processes and removes the ones whose executable name does not exactly match the filtering
 * key.
 *
 */
static void filterProcessesNotMatchingName( std::vector<PROCESSENTRY32>& processes, std::wstring const& searchFor ) {
    size_t count = processes.size( );
    for ( size_t i = 0; count > 0; --count ) {
        if ( getProcessName( processes.at( i ) ) == searchFor ) {
            ++i;
        } else {
            processes.erase( processes.begin( ) + i );
        }
    }
}

/**
 * @brief Returns a vector containing the executable names of all given processes.
 *
 */
static std::vector<std::wstring> getProcessNames( std::vector<PROCESSENTRY32> const& allProcesses ) {
    std::vector<std::wstring> result;
    for ( PROCESSENTRY32 const& i : allProcesses ) {
        result.push_back( getProcessName( i ) );
    }
    return result;
}

/**
 * @brief Converts a narrow string to a wide string.
 *
 */
static inline std::wstring narrowToWideString( std::string const& as ) {
    // deal with trivial case of empty string
    if ( as.empty( ) )
        return std::wstring( );

    // determine required length of new string
    size_t reqLength = ::MultiByteToWideChar( CP_UTF8, 0, as.c_str( ), ( int )as.length( ), 0, 0 );

    // construct new string of required length
    std::wstring ret( reqLength, L'\0' );

    // convert old string to new string
    ::MultiByteToWideChar( CP_UTF8, 0, as.c_str( ), ( int )as.length( ), &ret[0], ( int )ret.length( ) );

    // return new string ( compiler should optimize this away )
    return ret;
}

/**
 * @brief Prints a container type as a numbered list to standard output.
 *
 */
static void printNumberedList( std::set<std::wstring> const& container ) {
    int n = 1;
    for ( auto const& i : container ) {
        std::wcout << " [" << n << "]\t" << i << "\n";
        ++n;
    }
}

/**
 * @brief Queries the user to select a running application from a list of possible applications.
 *
 * @note This function makes the assumption that similarly named executable names all belong to the same application.
 */
static void userSelectApplication( std::vector<PROCESSENTRY32>& processes ) {
    std::set<std::wstring> uniqueProcessNames;
    for ( std::wstring const& i : getProcessNames( processes ) ) {
        uniqueProcessNames.insert( i );
    }
    std::wcout << "Found " << uniqueProcessNames.size( ) << " possible application(s)..." << std::endl << std::endl;
    printNumberedList( uniqueProcessNames );
    std::wcout << std::endl;
    std::wcout << "Please select the desired application..." << std::endl;
    std::wcout << "> ";

    // @Todo Handle out-of-bounds access.
    size_t selection = 0;
    std::cin >> selection;
    std::vector<std::wstring> uniqueProcessNamesAsVector{ uniqueProcessNames.begin( ), uniqueProcessNames.end( ) };
    filterProcessesNotMatchingName( processes, uniqueProcessNamesAsVector.at( selection - 1 ) );
}

/**
 * @brief Given an executable name as a loose search key, returns a vector of processes whose executable name matches.
 *
 * @details First, a loose search is formed. If the loose succeeds, the user is prompted to select the exact executable.
 * If the loose search fails, then there were no possible search results for the given executable name.
 *
 */
static std::vector<PROCESSENTRY32> findApplication( std::wstring const& executableName ) {
    std::vector<PROCESSENTRY32> processes = getAllProcesses( );
    filterProcessesNotContainingName( processes, executableName );
    if ( !processes.empty( ) ) {
        userSelectApplication( processes );
    }
    return processes;
}

/**
 * @brief Returns the title of a window.
 *
 * @note The returned string may be empty if the window has no text for the title or for other reasons.
 */
static std::wstring getWindowTitle( HWND windowHandle ) {
    SetLastError( 0 );
    int   titleLength = GetWindowTextLength( windowHandle );
    DWORD error = GetLastError( );
    if ( titleLength == 0 && GetLastError( ) != 0 ) {
        std::abort( );
    }
    wchar_t* title = new wchar_t[titleLength + 1];
    int      copied = GetWindowText( windowHandle, title, titleLength + 1 );
    if ( copied == 0 && GetLastError( ) != 0 ) {
        delete[] title;
        std::abort( );
    }
    assert( copied == titleLength );
    std::wstring result{ title };
    delete[] title;
    return result;
}

/**
 * @brief Returns a vector of window titles as strings for a vector of windows.
 *
 */
static std::vector<std::wstring> getWindowTitles( std::vector<HWND> const& windowHandles ) {
    std::vector<std::wstring> result;
    for ( auto const& i : windowHandles ) {
        result.push_back( getWindowTitle( i ) );
    }
    return result;
}

/**
 * @brief Callback used to enumerate all top-level windows.
 *
 */
static BOOL CALLBACK enumWindowsCallback( HWND windowHandle, LPARAM parameter ) {
    std::vector<HWND>* pHandles = reinterpret_cast<std::vector<HWND>*>( parameter );
    ( *pHandles ).push_back( windowHandle );
    return true;
}

/**
 * @brief Takes a vector of handles to windows and removes those whose window was not opened by a given process.
 *
 */
static void filterWindowsNotOpenedByProcesses( std::vector<HWND>&                 windowHandles,
                                               std::vector<PROCESSENTRY32> const& processes ) {
    size_t count = windowHandles.size( );
    for ( size_t i = 0; count > 0; --count ) {
        if ( windowOpenedByProcessAnyOf( windowHandles.at( i ), processes ) ) {
            ++i;
        } else {
            windowHandles.erase( windowHandles.begin( ) + i );
        }
    }
}

/**
 * @brief Takes a vector of handles to windows and removes those that have no text for their title.
 *
 */
static void filterUnnamedWindows( std::vector<HWND>& windowHandles ) {
    size_t count = windowHandles.size( );
    for ( size_t i = 0; count > 0; --count ) {
        if ( !getWindowTitle( windowHandles.at( i ) ).empty( ) ) {
            ++i;
        } else {
            windowHandles.erase( windowHandles.begin( ) + i );
        }
    }
}

/**
 * @brief Takes a vector of handles to windows and puts each owned window in a "show" state, but does not activate them.
 *
 */
static void showAllWindows( std::vector<HWND>& windowHandles ) {
    for ( auto& i : windowHandles ) {
        ShowWindow( i, SW_SHOWNOACTIVATE );
    }
}

/**
 * @brief Takes a vector of handles to windows and stacks all owned windows over top one another.
 *
 */
static BOOL stackWindows( std::vector<HWND>& windowHandles ) {
    int const windowLength = 500;
    int const windowHeight = 300;
    // POINT     windowSize = { windowLength, windowHeight };

    int x = 10;
    int y = 10;
    // POINT startPosition = { x, y };

    int const incLeft = 30;
    int const incDown = 20;

    UINT const options = SWP_NOZORDER | SWP_SHOWWINDOW | SWP_NOACTIVATE;
    BOOL       noErrors = true;
    for ( auto& i : windowHandles ) {
        noErrors = noErrors && SetWindowPos( i, HWND_TOP, x, y, windowLength, windowHeight, options );
        x += incLeft;
        y += incDown;
    }

    return noErrors;
}

int main( int argc, char* argv[] ) {
    if ( argc == 1 ) {
        std::cout << "Not enough arguments.\n";
        return EXIT_SUCCESS;
    } else if ( argc > 2 ) {
        std::cout << "Too many arguments.\n";
        return EXIT_SUCCESS;
    }
    _setmode( _fileno( stdout ), _O_U16TEXT ); // Set output to unicode mode for wide text strings.
    std::string const           userInput{ argv[1] };
    std::wstring const          executableName = narrowToWideString( userInput );
    std::vector<PROCESSENTRY32> processes = findApplication( executableName );

    if ( processes.empty( ) ) {
        std::cout << "No applications found.\n";
        return EXIT_SUCCESS;
    }

    // Enumerate all top-level windows.
    std::vector<HWND> windowHandles;
    EnumWindows( &enumWindowsCallback, reinterpret_cast<LPARAM>( &windowHandles ) );

    filterWindowsNotOpenedByProcesses( windowHandles, processes );
    filterUnnamedWindows( windowHandles ); // Weed out windows not likely to be user-facing, interactive windows.
    showAllWindows( windowHandles );
    stackWindows( windowHandles );

    return 0;
}
c++ windows winapi
1个回答
0
投票

正如其他人评论的那样,解决方案是检查属于不可见进程的任何窗口并忽略这些窗口,同时显示其他窗口。

这是通过使用函数

IsWindowVisible(...)
实现的。

© www.soinside.com 2019 - 2024. All rights reserved.