Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Check if my QMainWindow is currently visible in Qt

Tags:

c++

qt

I would like to know if my QMainWindow is currently visible and not overlapped by another windows of another applications.

I need to achieve this for Windows, Linux, and Mac.

like image 524
Didac Perez Parera Avatar asked Aug 12 '13 09:08

Didac Perez Parera


1 Answers

I wrote a small library a little while back for reading the foreground or topmost window information, mainly the window title, across Windows, Mac OS X, and Linux. You can find the source code here: https://github.com/pcmantinker/Qt-Window-Title-Reader

I use the native Windows API for Windows, X11 libraries for Linux, and Cocoa on Mac OS X.

Here is a small sample of how to get active windows in Mac OS X using objective-C++:
Mac.h

/*
  Mac/Cocoa specific code for obtaining information about the frontmost window
*/

#ifndef MAC_H
#define MAC_H
#include <QtCore>
#include "windowinfo.h"

class Mac {
public:
    Mac();
    QList<WindowInfo> getActiveWindows();
};

#endif // MAC_H

Mac.mm

/*
  Mac/Cocoa specific code for obtaining information about the frontmost window
*/

#include "mac.h"
#include "Cocoa/Cocoa.h"

Mac::Mac()
{

}

QList<WindowInfo> Mac::getActiveWindows()
{
    QList<WindowInfo> windowTitles;

    // get frontmost process for currently active application
    ProcessSerialNumber psn = { 0L, 0L };
    OSStatus err = GetFrontProcess(&psn);

    CFStringRef processName = NULL;
    err = CopyProcessName(&psn, &processName);

    NSString *pname = (NSString *)processName;

    // loop through all application windows
    CFArrayRef windowList = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, kCGNullWindowID);
    for (NSMutableDictionary* entry in (NSArray*)windowList)
    {
        NSString* ownerName = [entry objectForKey:(id)kCGWindowOwnerName];
        NSString *name = [entry objectForKey:@"kCGWindowName" ];
        NSInteger ownerPID = [[entry objectForKey:(id)kCGWindowOwnerPID] integerValue];
        NSInteger layer = [[entry objectForKey:@"kCGWindowLayer"] integerValue];
        if(layer == 0)
        {
            if([ownerName isEqualToString:pname])
            {
                NSRange range;
                range.location = 0;
                range.length = [ownerName length];

                unichar *chars = new unichar[range.length];
                [ownerName getCharacters:chars range:range];
                QString owner = QString::fromUtf16(chars, range.length);

                range.length = [name length];

                chars = new unichar[range.length];
                [name getCharacters:chars range:range];
                QString windowTitle = QString::fromUtf16(chars, range.length);
                delete[] chars;

                long pid = (long)ownerPID;

                WindowInfo wi;
                wi.setProcessName(owner);
                wi.setWindowTitle(windowTitle);
                wi.setPID(pid);
                windowTitles.append(wi);
            }
        }
    }
    CFRelease(windowList);
    CFRelease(processName);

    return windowTitles;
}

Please note that in Cocoa, there isn't a direct way for getting only the topmost window. You get a collection of windows and loop through them to find the one you want.
Here is the code for the Windows API:
win.h

/*
  Windows API specific code for obtaining information about the frontmost window
*/

#ifndef WIN_H
#define WIN_H
#include <QtCore>
#include "qt_windows.h"
#include "psapi.h"
#include "windowinfo.h"

class win : public QObject
{
    Q_OBJECT

public:
    win();
    QList<WindowInfo> getActiveWindows();

private:
    TCHAR buf[255];
};

#endif // WIN_H

win.cpp

#include "win.h"

win::win()
{
}

QList<WindowInfo> win::getActiveWindows()
{
    QList<WindowInfo> windowTitles;
    HWND foregroundWindow = GetForegroundWindow();
    DWORD* processID = new DWORD;
    GetWindowText(foregroundWindow, buf, 255);
    GetWindowThreadProcessId(foregroundWindow, processID);
    DWORD p = *processID;
    HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION |
                                  PROCESS_VM_READ,
                                  FALSE, p);
    TCHAR szProcessName[MAX_PATH];

    if (NULL != hProcess )
    {
        HMODULE hMod;
        DWORD cbNeeded;

        if ( EnumProcessModules( hProcess, &hMod, sizeof(hMod),
                                 &cbNeeded) )
        {
            GetModuleBaseName( hProcess, hMod, szProcessName,
                               sizeof(szProcessName)/sizeof(TCHAR) );
        }
    }
    CloseHandle(hProcess);
    long pid = (long)p;
    QString windowTitle, processName;
#ifdef UNICODE
    windowTitle = QString::fromUtf16((ushort*)buf);
    processName = QString::fromUtf16((ushort*)szProcessName);
#else
    windowTitle = QString::fromLocal8Bit(buf);
    processName = QString::fromLocal8Bit(szProcessName);
#endif

    WindowInfo wi;
    wi.setPID(pid);
    wi.setWindowTitle(windowTitle);
    wi.setProcessName(processName);
    windowTitles.append(wi);
    return windowTitles;
}

Here's the code for Linux/X11:
linux_x11.h

/*
  Linux/X11 specific code for obtaining information about the frontmost window
*/

#ifndef LINUX_X11_H
#define LINUX_X11_H

#include <QtCore>
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include "windowinfo.h"

class linux_x11
{
public:
    linux_x11();
    QList<WindowInfo> getActiveWindows();

private:
    Window* active(Display *disp, unsigned long *len);
    char *name (Display *disp, Window win);
    int *pid(Display *disp, Window win);
    QString processName(long pid);
};

#endif // LINUX_X11_H

linux_x11.cpp

/*
  Linux/X11 specific code for obtaining information about the frontmost window
*/

#include "linux_x11.h"
#include <sstream>
#include <stdlib.h>
#include <stdio.h>

linux_x11::linux_x11()
{
}

/**
  * Returns the window name for a specific window on a display
***/
char *linux_x11::name (Display *disp, Window win) {
    Atom prop = XInternAtom(disp,"WM_NAME",False), type;
    int form;
    unsigned long remain, len;
    unsigned char *list;

    if (XGetWindowProperty(disp,win,prop,0,1024,False,AnyPropertyType, &type,&form,&len,&remain,&list) != Success)
        return NULL;

    return (char*)list;
}

/**
  * Returns the pid for a specific window on a display
***/
int* linux_x11::pid(Display *disp, Window win) {
    Atom prop = XInternAtom(disp,"_NET_WM_PID",False), type;
    int form;
    unsigned long remain, len;
    unsigned char *list;

    if (XGetWindowProperty(disp,win,prop,0,1024,False,AnyPropertyType, &type,&form,&len,&remain,&list) != Success)
        return NULL;

    return (int*)list;
}

/**
  * Returns the active window on a specific display
***/
Window * linux_x11::active (Display *disp, unsigned long *len) {
    Atom prop = XInternAtom(disp,"_NET_ACTIVE_WINDOW",False), type;
    int form;
    unsigned long remain;
    unsigned char *list;

    if (XGetWindowProperty(disp,XDefaultRootWindow(disp),prop,0,1024,False,XA_WINDOW, &type,&form,len,&remain,&list) != Success)
        return NULL;

    return (Window*)list;
}

/**
  * Returns process name from pid (processes output from /proc/<pid>/status)
***/
QString linux_x11::processName(long pid)
{
    // construct command string
    QString command = "cat /proc/" + QString("%1").arg(pid) + "/status";
    // capture output in a FILE pointer returned from popen
    FILE * output = popen(command.toStdString().c_str(), "r");
    // initialize a buffer for storing the first line of the output
    char buffer[1024];
    // put the contents of the buffer into a QString
    QString line = QString::fromUtf8(fgets(buffer, sizeof(buffer), output));
    // close the process pipe
    pclose(output);
    // take right substring of line returned to get process name
    return line.right(line.length() - 6).replace("\n", "");
}

QList<WindowInfo> linux_x11::getActiveWindows()
{
    QList<WindowInfo> windowTitles;
    unsigned long len;
    Display *disp = XOpenDisplay(NULL);
    Window *list;
    char *n;
    int* p;

    list = (Window*)active(disp,&len);
    if((int)len > 0)
    {
        for (int i=0;i<(int)len;i++) {
            n = name(disp,list[i]);
            p = pid(disp, list[i]);
            long p_id = 0;
            QString pName;
            QString windowTitle;

            if(p!=NULL)
            {
                p_id = *p; // dereference pointer for obtaining pid
                pName = processName(p_id);
            }

            if(n!=NULL)
                windowTitle = QString::fromUtf8(n);

            WindowInfo wi;
            wi.setWindowTitle(windowTitle);
            wi.setProcessName(pName);
            wi.setPID(p_id);
            windowTitles.append(wi);
            delete n;
            delete p;
        }
    }
    delete list;
    XCloseDisplay (disp);
    return windowTitles;
}

X11 code can get quite ugly and difficult to understand, but this should get you started. It's been quite a while since I've dealt with X11 directly so I can't exactly tell you what each helper method does right now.

I abstract the code such that each platform specific piece of the code has the same method signature. Then I check if it was compiled on Mac OS X, Windows, or Linux and instantiate the correct classes. Here's how it's all tied together:

#include "windowtitlereader.h"

WindowTitleReader::WindowTitleReader()
{
    qDebug() << "WindowTitleReader::WindowTitleReader()";
    // refresh window reading every 10ms
    timer = new QTimer(this);
    timer->setInterval(10);
    timer->start();
    connect(timer, SIGNAL(timeout()), this, SLOT(getWindowTitle()));
}

WindowTitleReader::~WindowTitleReader()
{
    delete timer;
    delete m_pid;
    delete m_processName;
}
void WindowTitleReader::getWindowTitle()
{
    qDebug() << "WindowTitleReader::getWindowTitle()";
#ifdef Q_WS_WIN
    win w;
    m_activeWindows = w.getActiveWindows();
#endif

#ifdef Q_WS_MACX
    Mac m;
    m_activeWindows = m.getActiveWindows();
#endif

#ifdef Q_WS_X11
    linux_x11 l;
    m_activeWindows = l.getActiveWindows();
#endif
    for(int i = 0; i < m_activeWindows.count(); i++)
        qDebug() << "PID: " << m_activeWindows[i].getPID() << " Process Name: " << m_activeWindows[i].getProcessName() << " Window Title: " << m_activeWindows[i].getWindowTitle();
}

You can change the refresh rate to a slower refresh rate if you want, but I'm running the updates every 10ms to get a near real time update on the window focusing.

I mainly wrote this library to read the window titles of web browsers and video games so I could get an estimate of how long people play certain games on their computers. I wrote this as part of a game play metrics app that I'm building.

like image 52
Cameron Tinker Avatar answered Oct 27 '22 00:10

Cameron Tinker