// 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 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. // #include "../base/os.h" // #include #include "process.h" 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 #endif }; typedef struct _MData MData; MData mData; bool setHandle(uintptr handle); bool IsValid(); bool IsAxEnabled(bool options); MData GetActive (void); void aWindow(); #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); // Check results 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); } // 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); } // Functions static void* GetWindowProperty (MData win, Atom atom, uint32* 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) nItems; return result; } } // Reset the items result if valid if (items != NULL) *items = 0; // Free the result if it got allocated if (result != NULL) XFree (result); 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); } } } #elif defined(IS_WINDOWS) // #endif //int findwindow() uintptr ahandle = 0; void aWindow(uintptr handle){ #if defined(IS_MACOSX) mData.CgID = 0; mData.AxID = 0; #elif defined(USE_X11) Display *rDisplay = XOpenDisplay(NULL); // If atoms loaded if (WM_PID == None){ // Load all necessary atom properties if (rDisplay != NULL) LoadAtoms(); } mData.XWin = 0; #elif defined(IS_WINDOWS) mData.HWnd = 0; #endif setHandle(handle); } bool IsValid (){ aWindow(ahandle); if(!IsAxEnabled(true))printf("%s\n", "Window:Accessibility API is disabled!\nFailed to enable access for assistive devices."); MData actdata=GetActive(); #if defined(IS_MACOSX) mData.CgID=actdata.CgID; mData.AxID=actdata.AxID; if (mData.CgID == 0 || mData.AxID == 0)return false; CFTypeRef r = NULL; // Attempt to get the window role if (AXUIElementCopyAttributeValue(mData.AxID, kAXRoleAttribute,&r) == kAXErrorSuccess && r){ CFRelease (r); return true; } return false; #elif defined(USE_X11) mData.XWin=actdata.XWin; if (mData.XWin == 0)return false; Display *rDisplay = XOpenDisplay(NULL); // Check for a valid X-Window display if (rDisplay == NULL) return false; // Ignore X errors XDismissErrors(); // Get the window PID property void* result = GetWindowProperty(mData, WM_PID,NULL); if (result == NULL) return false; // Free result and return true XFree (result); return true; #elif defined(IS_WINDOWS) mData.HWnd=actdata.HWnd; if (mData.HWnd == 0) return false; return IsWindow (mData.HWnd) != 0; #endif } bool IsAxEnabled (bool options){ #if defined(IS_MACOSX) // Statically load all required functions one time static dispatch_once_t once; dispatch_once (&once, ^{ // Open the framework void* handle = dlopen ("/System/Library/Frameworks/Application" "Services.framework/ApplicationServices", RTLD_LAZY); // Validate the handle if (handle != NULL){ *(void**) (&gAXIsProcessTrustedWithOptions) = dlsym (handle, "AXIsProcessTrustedWithOptions"); gkAXTrustedCheckOptionPrompt = (CFStringRef*) dlsym (handle, "kAXTrustedCheckOptionPrompt"); } }); // Check for new OSX 10.9 function if (gAXIsProcessTrustedWithOptions){ // Check whether to show prompt CFBooleanRef displayPrompt = options ? kCFBooleanTrue : kCFBooleanFalse; // Convert display prompt value into a dictionary const void* k[] = { *gkAXTrustedCheckOptionPrompt }; const void* v[] = { displayPrompt }; CFDictionaryRef o = CFDictionaryCreate (NULL, k, v, 1, NULL, NULL); // Determine whether the process is actually trusted bool result = (*gAXIsProcessTrustedWithOptions) (o); // Free memory CFRelease (o); return result; }else{ // Ignore deprecated warnings #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" // Check whether we have accessibility access return AXAPIEnabled() || AXIsProcessTrusted(); #pragma clang diagnostic pop } #elif defined(USE_X11) return true; #elif defined(IS_WINDOWS) return true; #endif } // int bool setHandle (uintptr handle){ #if defined(IS_MACOSX) // Release the AX element if (mData.AxID != NULL)CFRelease (mData.AxID); // Reset both values mData.CgID = 0; mData.AxID = 0; if (handle == 0)return 0; // return true; // Retrieve the window element CGWindowID cgID = (CGWindowID) handle; AXUIElementRef axID = GetUIElement (cgID); if (axID != NULL){ mData.CgID = cgID; mData.AxID = axID; // return 0; return true; } // return 1; return false; #elif defined(USE_X11) mData.XWin = (Window) handle; if (handle == 0) return true; if (IsValid()) return true; mData.XWin = 0; return false; #elif defined(IS_WINDOWS) mData.HWnd = (HWND) handle; if (handle == 0) return true; if (IsValid()) return true; mData.HWnd = 0; return false; #endif } //uint32 uintptr uintptr getHandle(){ #if defined(IS_MACOSX) return (uintptr) mData.CgID; #elif defined(USE_X11) return (uintptr) mData.XWin; #elif defined(IS_WINDOWS) return (uintptr) mData.HWnd; #endif } bool IsTopMost (void){ // Check the window validity if (!IsValid()) return false; #if defined(IS_MACOSX) return false; // WARNING: Unavailable #elif defined(USE_X11) // Ignore X errors // XDismissErrors (); // return GetState (mData.XWin, STATE_TOPMOST); #elif defined(IS_WINDOWS) return (GetWindowLongPtr (mData.HWnd, GWL_EXSTYLE) & WS_EX_TOPMOST) != 0; #endif } bool IsMinimized(void){ // Check the window validity if (!IsValid()) return false; #if defined(IS_MACOSX) CFBooleanRef data = NULL; // Determine whether the window is minimized if (AXUIElementCopyAttributeValue (mData.AxID, kAXMinimizedAttribute, (CFTypeRef*) &data) == kAXErrorSuccess && data != NULL) { // Convert resulting data into a bool bool result = CFBooleanGetValue(data); CFRelease (data); return result; } return false; #elif defined(USE_X11) // Ignore X errors // XDismissErrors(); // return GetState (mData.XWin, STATE_MINIMIZE); #elif defined(IS_WINDOWS) return (GetWindowLongPtr (mData.HWnd, GWL_STYLE) & WS_MINIMIZE) != 0; #endif } ////// bool IsMaximized(void){ // Check the window validity if (!IsValid()) return false; #if defined(IS_MACOSX) return false; // WARNING: Unavailable #elif defined(USE_X11) // Ignore X errors // XDismissErrors(); // return GetState (mData.XWin, STATE_MAXIMIZE); #elif defined(IS_WINDOWS) return (GetWindowLongPtr (mData.HWnd, GWL_STYLE) & WS_MAXIMIZE) != 0; #endif } void SetActive(const MData win){ // Check if the window is valid if (!IsValid()) return; #if defined(IS_MACOSX) // Attempt to raise the specified window object if (AXUIElementPerformAction (win.AxID, kAXRaiseAction) == kAXErrorSuccess) { pid_t pid = 0; // Attempt to retrieve the PID of the window if (AXUIElementGetPid (win.AxID, &pid) != kAXErrorSuccess || !pid) return; // Ignore deprecated warnings #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" // NOTE: Until Apple actually removes // these functions, there's no real // reason to switch to the NS* flavor ProcessSerialNumber psn; // Attempt to retrieve the process psn if (GetProcessForPID (pid, &psn) == 0){ // Gracefully activate process SetFrontProcessWithOptions (&psn, kSetFrontProcessFrontWindowOnly); } #pragma clang diagnostic pop } #elif defined(USE_X11) // Ignore X errors XDismissErrors(); // Go to the specified window's desktop SetDesktopForWindow(win); Display *rDisplay = XOpenDisplay(NULL); // Check the atom value if (WM_ACTIVE != None){ // Retrieve the screen number XWindowAttributes attr = { 0 }; XGetWindowAttributes (rDisplay, win.XWin, &attr); int s = XScreenNumberOfScreen(attr.screen); // Prepare an event XClientMessageEvent e = { 0 }; e.window = win.XWin; e.format = 32; e.message_type = WM_ACTIVE; e.display = rDisplay; e.type = ClientMessage; e.data.l[0] = 2; e.data.l[1] = CurrentTime; // Send the message XSendEvent(rDisplay, XRootWindow (rDisplay, s), False, SubstructureNotifyMask | SubstructureRedirectMask, (XEvent*) &e); }else{ // Attempt to raise the specified window XRaiseWindow(rDisplay, win.XWin); // Set the specified window's input focus XSetInputFocus(rDisplay, win.XWin, RevertToParent, CurrentTime); } #elif defined(IS_WINDOWS) if (IsMinimized()) ShowWindow(win.HWnd, SW_RESTORE); SetForegroundWindow (win.HWnd); #endif } MData GetActive (void){ #if defined(IS_MACOSX) MData result; // Ignore deprecated warnings #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" ProcessSerialNumber psn; pid_t pid; // Attempt to retrieve the front process if (GetFrontProcess (&psn ) != 0 || GetProcessPID (&psn, &pid) != 0) return result; #pragma clang diagnostic pop // Create accessibility object using focused PID AXUIElementRef focused = AXUIElementCreateApplication (pid); if (focused == NULL) return result; // Verify AXUIElementRef element; // Retrieve the currently focused window if (AXUIElementCopyAttributeValue (focused, kAXFocusedWindowAttribute, (CFTypeRef*) &element) == kAXErrorSuccess && element) { CGWindowID win = 0; // Use undocumented API to get WID if (_AXUIElementGetWindow (element, &win) == kAXErrorSuccess && win) { // Manually set internals result.CgID = win; result.AxID = element; } // Something went wrong else CFRelease (element); } CFRelease (focused); return result; #elif defined(USE_X11) MData result; Display *rDisplay = XOpenDisplay(NULL); // Check X-Window display if (WM_ACTIVE == None || rDisplay == NULL) return result; // Ignore X errors XDismissErrors(); // Get the current active window result.XWin=XDefaultRootWindow (rDisplay); void* active = GetWindowProperty(result,WM_ACTIVE,NULL); // Check result value if (active != NULL) { // Extract window from the result long window = *((long*) active); XFree (active); if (window != 0) { // Set and return the foreground window result.XWin = (Window) window; return result; } } // Use input focus instead Window window = None; int revert = RevertToNone; XGetInputFocus (rDisplay, &window, &revert); // Return foreground window result.XWin = window; return result; #elif defined(IS_WINDOWS) // Attempt to get the foreground window multiple times in case MData result; uint8 times = 0; while (++times < 20) { HWND handle; handle = GetForegroundWindow(); if (handle != NULL){ // mData.HWnd=(uintptr) handle; result.HWnd= (HWND)handle; return result; } Sleep (20); } return result; #endif } void SetTopMost (bool state){ // Check window validity if (!IsValid()) return; #if defined(IS_MACOSX) // WARNING: Unavailable #elif defined(USE_X11) // Ignore X errors // XDismissErrors (); // SetState (mData.XWin, STATE_TOPMOST, state); #elif defined(IS_WINDOWS) SetWindowPos (mData.HWnd, state ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); #endif } //CloseWindow void CloseWin(void){ // Check window validity if (!IsValid()) return; #if defined(IS_MACOSX) AXUIElementRef b = NULL; // Retrieve the close button of this window if (AXUIElementCopyAttributeValue (mData.AxID, kAXCloseButtonAttribute, (CFTypeRef*) &b) == kAXErrorSuccess && b != NULL) { // Simulate button press on the close button AXUIElementPerformAction (b, kAXPressAction); CFRelease (b); } #elif defined(USE_X11) Display *rDisplay = XOpenDisplay(NULL); // Ignore X errors XDismissErrors(); // Close the window XDestroyWindow (rDisplay, mData.XWin); #elif defined(IS_WINDOWS) PostMessage (mData.HWnd, WM_CLOSE, 0, 0); #endif } char *GetTitle(){ // Check if the window is valid if (!IsValid()) return "IsValid failed."; #if defined(IS_MACOSX) CFStringRef data = NULL; // Determine the current title of the window if (AXUIElementCopyAttributeValue (mData.AxID, kAXTitleAttribute, (CFTypeRef*) &data) == kAXErrorSuccess && data != NULL) { char conv[512]; // Convert result to a C-String CFStringGetCString (data, conv, 512, kCFStringEncodingUTF8); CFRelease (data); char* s=(char*)calloc(100,sizeof(char*)); if(s)strcpy(s,conv); // return (char *)&conv; return s; } return "null"; #elif defined(USE_X11) void* result; // Ignore X errors XDismissErrors(); // Get window title (UTF-8) result = GetWindowProperty(mData, WM_NAME,NULL); // Check result value if (result != NULL){ // Convert result to a string char *name=(char*) result; XFree (result); if (name!=NULL)return name; } // Get window title (ASCII) result = GetWindowProperty(mData, XA_WM_NAME,NULL); // Check result value if (result != NULL){ // Convert result to a string char *name =(char*) result; XFree (result); return name; } return "null"; #elif defined(IS_WINDOWS) TCHAR name[512]; return GetWindowText (mData.HWnd, name, 512) > 0 ? name : "null"; // return GetWindowText // (mData.HWnd, name, 512) > 0 ? // _UTF8Encode (name) : "null"; #endif }