View Javadoc
1   /*
2    * Copyright 2021-2022 The OSHI Project Contributors
3    * SPDX-License-Identifier: MIT
4    */
5   package oshi.driver.mac;
6   
7   import static oshi.jna.platform.mac.CoreGraphics.kCGNullWindowID;
8   import static oshi.jna.platform.mac.CoreGraphics.kCGWindowListExcludeDesktopElements;
9   import static oshi.jna.platform.mac.CoreGraphics.kCGWindowListOptionAll;
10  import static oshi.jna.platform.mac.CoreGraphics.kCGWindowListOptionOnScreenOnly;
11  
12  import java.awt.Rectangle;
13  import java.util.ArrayList;
14  import java.util.List;
15  
16  import com.sun.jna.Pointer;
17  import com.sun.jna.platform.mac.CoreFoundation.CFArrayRef;
18  import com.sun.jna.platform.mac.CoreFoundation.CFBooleanRef;
19  import com.sun.jna.platform.mac.CoreFoundation.CFDictionaryRef;
20  import com.sun.jna.platform.mac.CoreFoundation.CFNumberRef;
21  import com.sun.jna.platform.mac.CoreFoundation.CFStringRef;
22  
23  import oshi.annotation.concurrent.ThreadSafe;
24  import oshi.jna.platform.mac.CoreGraphics;
25  import oshi.jna.platform.mac.CoreGraphics.CGRect;
26  import oshi.software.os.OSDesktopWindow;
27  import oshi.util.FormatUtil;
28  import oshi.util.platform.mac.CFUtil;
29  
30  /**
31   * Utility to query desktop windows
32   */
33  @ThreadSafe
34  public final class WindowInfo {
35  
36      private WindowInfo() {
37      }
38  
39      /**
40       * Gets windows on the operating system's GUI desktop.
41       *
42       * @param visibleOnly Whether to restrict the list to only windows visible to the user.
43       * @return A list of {@link oshi.software.os.OSDesktopWindow} objects representing the desktop windows.
44       */
45      public static List<OSDesktopWindow> queryDesktopWindows(boolean visibleOnly) {
46          CFArrayRef windowInfo = CoreGraphics.INSTANCE.CGWindowListCopyWindowInfo(
47                  visibleOnly ? kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements
48                          : kCGWindowListOptionAll,
49                  kCGNullWindowID);
50          int numWindows = windowInfo.getCount();
51          // Prepare a list to return
52          List<OSDesktopWindow> windowList = new ArrayList<>();
53          // Set up keys for dictionary lookup
54          CFStringRef kCGWindowIsOnscreen = CFStringRef.createCFString("kCGWindowIsOnscreen");
55          CFStringRef kCGWindowNumber = CFStringRef.createCFString("kCGWindowNumber");
56          CFStringRef kCGWindowOwnerPID = CFStringRef.createCFString("kCGWindowOwnerPID");
57          CFStringRef kCGWindowLayer = CFStringRef.createCFString("kCGWindowLayer");
58          CFStringRef kCGWindowBounds = CFStringRef.createCFString("kCGWindowBounds");
59          CFStringRef kCGWindowName = CFStringRef.createCFString("kCGWindowName");
60          CFStringRef kCGWindowOwnerName = CFStringRef.createCFString("kCGWindowOwnerName");
61          try {
62              // Populate the list
63              for (int i = 0; i < numWindows; i++) {
64                  // For each array element, get the dictionary
65                  Pointer result = windowInfo.getValueAtIndex(i);
66                  CFDictionaryRef windowRef = new CFDictionaryRef(result);
67                  // Now get information from the dictionary.
68                  result = windowRef.getValue(kCGWindowIsOnscreen); // Optional key, check for null
69                  boolean visible = result == null || new CFBooleanRef(result).booleanValue();
70                  if (!visibleOnly || visible) {
71                      result = windowRef.getValue(kCGWindowNumber); // kCFNumberSInt64Type
72                      long windowNumber = new CFNumberRef(result).longValue();
73  
74                      result = windowRef.getValue(kCGWindowOwnerPID); // kCFNumberSInt64Type
75                      long windowOwnerPID = new CFNumberRef(result).longValue();
76  
77                      result = windowRef.getValue(kCGWindowLayer); // kCFNumberIntType
78                      int windowLayer = new CFNumberRef(result).intValue();
79  
80                      result = windowRef.getValue(kCGWindowBounds);
81                      try (CGRect rect = new CGRect()) {
82                          CoreGraphics.INSTANCE.CGRectMakeWithDictionaryRepresentation(new CFDictionaryRef(result), rect);
83                          Rectangle windowBounds = new Rectangle(FormatUtil.roundToInt(rect.origin.x),
84                                  FormatUtil.roundToInt(rect.origin.y), FormatUtil.roundToInt(rect.size.width),
85                                  FormatUtil.roundToInt(rect.size.height));
86                          // Note: the Quartz name returned by this field is rarely used
87                          result = windowRef.getValue(kCGWindowName); // Optional key, check for null
88                          String windowName = CFUtil.cfPointerToString(result, false);
89                          // This is the program running the window, use as name if name blank or add in
90                          // parenthesis
91                          result = windowRef.getValue(kCGWindowOwnerName); // Optional key, check for null
92                          String windowOwnerName = CFUtil.cfPointerToString(result, false);
93                          if (windowName.isEmpty()) {
94                              windowName = windowOwnerName;
95                          } else {
96                              windowName = windowName + "(" + windowOwnerName + ")";
97                          }
98  
99                          windowList.add(new OSDesktopWindow(windowNumber, windowName, windowOwnerName, windowBounds,
100                                 windowOwnerPID, windowLayer, visible));
101                     }
102                 }
103             }
104         } finally {
105             // CF references from "Copy" or "Create" must be released
106             kCGWindowIsOnscreen.release();
107             kCGWindowNumber.release();
108             kCGWindowOwnerPID.release();
109             kCGWindowLayer.release();
110             kCGWindowBounds.release();
111             kCGWindowName.release();
112             kCGWindowOwnerName.release();
113             windowInfo.release();
114         }
115 
116         return windowList;
117     }
118 }