robotgo/window/pub.h
Jean Raby 8e7a413ec6
Fix x11 display leak (#590)
* Call XCloseDisplay in all return paths

Add test to confirm XOpenDisplay leak is plugged

* whitespace
2023-06-11 10:17:26 -07:00

288 lines
8.1 KiB
C

// Copyright 2016 The go-vgo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// https://github.com/go-vgo/robotgo/blob/master/LICENSE
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// #include "../base/os.h"
#if defined(IS_MACOSX)
#include <dlfcn.h>
#elif defined(USE_X11)
#include <X11/Xatom.h>
#endif
struct _MData{
#if defined(IS_MACOSX)
CGWindowID CgID; // Handle to a CGWindowID
AXUIElementRef AxID; // Handle to a AXUIElementRef
#elif defined(USE_X11)
Window XWin; // Handle to an X11 window
#elif defined(IS_WINDOWS)
HWND HWnd; // Handle to a window HWND
TCHAR Title[512];
#endif
};
typedef struct _MData MData;
MData mData;
struct _Bounds {
int32_t X; // Top left X coordinate
int32_t Y; // Top left Y coordinate
int32_t W; // bounds width
int32_t H; // bounds height
};
typedef struct _Bounds Bounds;
#if defined(IS_MACOSX)
static Boolean(*gAXIsProcessTrustedWithOptions) (CFDictionaryRef);
static CFStringRef* gkAXTrustedCheckOptionPrompt;
AXError _AXUIElementGetWindow(AXUIElementRef, CGWindowID* out);
static AXUIElementRef GetUIElement(CGWindowID win){
intptr pid = 0;
// double_t pid = 0;
// Create array storing window
CGWindowID window[1] = { win };
CFArrayRef wlist = CFArrayCreate(NULL, (const void**)window, 1, NULL);
// Get window info
CFArrayRef info = CGWindowListCreateDescriptionFromArray(wlist);
CFRelease(wlist);
// Check whether the resulting array is populated
if (info != NULL && CFArrayGetCount(info) > 0) {
// Retrieve description from info array
CFDictionaryRef desc = (CFDictionaryRef)CFArrayGetValueAtIndex(info, 0);
// Get window PID
CFNumberRef data = (CFNumberRef) CFDictionaryGetValue(desc, kCGWindowOwnerPID);
if (data != NULL) {
CFNumberGetValue(data, kCFNumberIntType, &pid);
}
// Return result
CFRelease(info);
}
// Check if PID was retrieved
if (pid <= 0) { return NULL; }
// Create an accessibility object using retrieved PID
AXUIElementRef application = AXUIElementCreateApplication(pid);
if (application == 0) {return NULL;}
CFArrayRef windows = NULL;
// Get all windows associated with the app
AXUIElementCopyAttributeValues(application, kAXWindowsAttribute, 0, 1024, &windows);
// Reference to resulting value
AXUIElementRef result = NULL;
if (windows != NULL) {
int count = CFArrayGetCount(windows);
// Loop all windows in the process
for (CFIndex i = 0; i < count; ++i){
// Get the element at the index
AXUIElementRef element = (AXUIElementRef) CFArrayGetValueAtIndex(windows, i);
CGWindowID temp = 0;
// Use undocumented API to get WindowID
_AXUIElementGetWindow(element, &temp);
if (temp == win) {
// Retain element
CFRetain(element);
result = element;
break;
}
}
CFRelease(windows);
}
CFRelease(application);
return result;
}
#elif defined(USE_X11)
// Error Handling
typedef int (*XErrorHandler) (Display*, XErrorEvent*);
static int XHandleError(Display* dp, XErrorEvent* e) { return 0; }
XErrorHandler mOld;
void XDismissErrors (void) {
Display *rDisplay = XOpenDisplay(NULL);
// Save old handler and dismiss errors
mOld = XSetErrorHandler(XHandleError);
// Flush output buffer
XSync(rDisplay, False);
// Reinstate old handler
XSetErrorHandler(mOld);
XCloseDisplay(rDisplay);
}
// Definitions
struct Hints{
unsigned long Flags;
unsigned long Funcs;
unsigned long Decorations;
signed long Mode;
unsigned long Stat;
};
static Atom WM_STATE = None;
static Atom WM_ABOVE = None;
static Atom WM_HIDDEN = None;
static Atom WM_HMAX = None;
static Atom WM_VMAX = None;
static Atom WM_DESKTOP = None;
static Atom WM_CURDESK = None;
static Atom WM_NAME = None;
static Atom WM_UTF8 = None;
static Atom WM_PID = None;
static Atom WM_ACTIVE = None;
static Atom WM_HINTS = None;
static Atom WM_EXTENTS = None;
////////////////////////////////////////////////////////////////////////////////
static void LoadAtoms (void){
Display *rDisplay = XOpenDisplay(NULL);
WM_STATE = XInternAtom(rDisplay, "_NET_WM_STATE", True);
WM_ABOVE = XInternAtom(rDisplay, "_NET_WM_STATE_ABOVE", True);
WM_HIDDEN = XInternAtom(rDisplay, "_NET_WM_STATE_HIDDEN", True);
WM_HMAX = XInternAtom(rDisplay, "_NET_WM_STATE_MAXIMIZED_HORZ", True);
WM_VMAX = XInternAtom(rDisplay, "_NET_WM_STATE_MAXIMIZED_VERT", True);
WM_DESKTOP = XInternAtom(rDisplay, "_NET_WM_DESKTOP", True);
WM_CURDESK = XInternAtom(rDisplay, "_NET_CURRENT_DESKTOP", True);
WM_NAME = XInternAtom(rDisplay, "_NET_WM_NAME", True);
WM_UTF8 = XInternAtom(rDisplay, "UTF8_STRING", True);
WM_PID = XInternAtom(rDisplay, "_NET_WM_PID", True);
WM_ACTIVE = XInternAtom(rDisplay, "_NET_ACTIVE_WINDOW", True);
WM_HINTS = XInternAtom(rDisplay, "_MOTIF_WM_HINTS", True);
WM_EXTENTS = XInternAtom(rDisplay, "_NET_FRAME_EXTENTS", True);
XCloseDisplay(rDisplay);
}
// Functions
static void* GetWindowProperty(MData win, Atom atom, uint32_t* items) {
// Property variables
Atom type; int format;
unsigned long nItems;
unsigned long bAfter;
unsigned char* result = NULL;
Display *rDisplay = XOpenDisplay(NULL);
// Check the atom
if (atom != None) {
// Retrieve and validate the specified property
if (!XGetWindowProperty(rDisplay, win.XWin, atom, 0,
BUFSIZ, False, AnyPropertyType, &type, &format, &nItems, &bAfter, &result)
&& result && nItems) {
// Copy items result
if (items != NULL) {
*items = (uint32_t) nItems;
}
XCloseDisplay(rDisplay);
return result;
}
}
// Reset the items result if valid
if (items != NULL) { *items = 0; }
if (result != NULL) {
XFree(result);
}
XCloseDisplay(rDisplay);
return NULL;
}
//////
#define STATE_TOPMOST 0
#define STATE_MINIMIZE 1
#define STATE_MAXIMIZE 2
//////
static void SetDesktopForWindow(MData win){
Display *rDisplay = XOpenDisplay(NULL);
// Validate every atom that we want to use
if (WM_DESKTOP != None && WM_CURDESK != None) {
// Get desktop property
long* desktop = (long*)GetWindowProperty(win, WM_DESKTOP,NULL);
// Check result value
if (desktop != NULL) {
// Retrieve the screen number
XWindowAttributes attr = { 0 };
XGetWindowAttributes(rDisplay, win.XWin, &attr);
int s = XScreenNumberOfScreen(attr.screen);
Window root = XRootWindow(rDisplay, s);
// Prepare an event
XClientMessageEvent e = { 0 };
e.window = root; e.format = 32;
e.message_type = WM_CURDESK;
e.display = rDisplay;
e.type = ClientMessage;
e.data.l[0] = *desktop;
e.data.l[1] = CurrentTime;
// Send the message
XSendEvent(rDisplay, root, False, SubstructureNotifyMask | SubstructureRedirectMask,
(XEvent*) &e);
XFree(desktop);
}
}
XCloseDisplay(rDisplay);
}
static Bounds GetFrame(MData win){
Bounds frame;
// Retrieve frame bounds
if (WM_EXTENTS != None) {
long* result; uint32_t nItems = 0;
// Get the window extents property
result = (long*) GetWindowProperty(win, WM_EXTENTS, &nItems);
if (result != NULL) {
if (nItems == 4) {
frame.X = (int32_t) result[0];
frame.Y = (int32_t) result[2];
frame.W = (int32_t) result[0] + (int32_t) result[1];
frame.H = (int32_t) result[2] + (int32_t) result[3];
}
XFree(result);
}
}
return frame;
}
#elif defined(IS_WINDOWS)
HWND getHwnd(uintptr pid, int8_t isPid);
void win_min(HWND hwnd, bool state){
if (state) {
ShowWindow(hwnd, SW_MINIMIZE);
} else {
ShowWindow(hwnd, SW_RESTORE);
}
}
void win_max(HWND hwnd, bool state){
if (state) {
ShowWindow(hwnd, SW_MAXIMIZE);
} else {
ShowWindow(hwnd, SW_RESTORE);
}
}
#endif