movewin 1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a8e944a1c1c53e792e0702a75bcaa935e1f40d11
4
+ data.tar.gz: a810a3fbf0e569a772fef44fdad80eb2ba48eac8
5
+ SHA512:
6
+ metadata.gz: 76f97acb90936fb4b3bca5ffeb6c5879e0a184974e9bae82f26b4ed2ac7ede1f263566104e3a0e359570365ca3426d15c1dc66a7f1f195c6d904497fcd18c28b
7
+ data.tar.gz: 1a313507d82594ec1ce688515dcd61066dc048dd41f40598dbcec801288fa8d1da45659cc9404a43b897446227aa0f573771321a663b603c9970d01591b448e9
@@ -0,0 +1,13 @@
1
+ require 'mkmf'
2
+
3
+ # No have_framework() in mkmf that ships with Ruby versions earlier than 1.9
4
+ RUBY_VERSIONS = RUBY_VERSION.split('.').collect { |s| s.to_i }
5
+ unless RUBY_VERSIONS[0] < 1 || (RUBY_VERSIONS[0] == 1 && RUBY_VERSIONS[1] < 9)
6
+ have_framework('Carbon')
7
+ end
8
+ have_header('Carbon/Carbon.h')
9
+
10
+ $CFLAGS = '-Wall'
11
+ $LDFLAGS = '-Wall -framework Carbon'
12
+
13
+ create_makefile('movewin/movewin_ext')
@@ -0,0 +1,295 @@
1
+ /* ========================================================================
2
+ * movewin_ext.c - Ruby extension to list and move OS X windows
3
+ * Andrew Ho (andrew@zeuscat.com)
4
+ *
5
+ * Copyright (c) 2014, Andrew Ho.
6
+ * All rights reserved.
7
+ *
8
+ * Redistribution and use in source and binary forms, with or without
9
+ * modification, are permitted provided that the following conditions
10
+ * are met:
11
+ *
12
+ * Redistributions of source code must retain the above copyright notice,
13
+ * this list of conditions and the following disclaimer.
14
+ *
15
+ * Redistributions in binary form must reproduce the above copyright
16
+ * notice, this list of conditions and the following disclaimer in the
17
+ * documentation and/or other materials provided with the distribution.
18
+ *
19
+ * Neither the name of the author nor the names of its contributors may
20
+ * be used to endorse or promote products derived from this software
21
+ * without specific prior written permission.
22
+ *
23
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
26
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
27
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
28
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
29
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
30
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
31
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
32
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
33
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34
+ * ========================================================================
35
+ */
36
+
37
+ #include <ruby.h>
38
+ #include "winutils.h"
39
+
40
+ /* Global MoveWin::Window handle, so StoreWindows() can create objects */
41
+ static VALUE MW_WindowClass;
42
+
43
+ /* Ruby extension setup code */
44
+ void Init_movewin_ext();
45
+
46
+ /* Ruby method implementations */
47
+ VALUE MW_is_authorized(VALUE module); /* MoveWin.authorized? */
48
+ VALUE MW_display_size(VALUE module); /* MoveWin.display_size */
49
+ VALUE MW_windows(VALUE module); /* MoveWin.windows */
50
+ VALUE MW_Window_app_name(VALUE self); /* MoveWin::Window.app_name */
51
+ VALUE MW_Window_title(VALUE self); /* MoveWin::Window.title */
52
+ VALUE MW_Window_position(VALUE self); /* MoveWin::Window.position */
53
+ VALUE MW_Window_size(VALUE self); /* MoveWin::Window.size */
54
+ VALUE MW_Window_set_position(VALUE self, VALUE args); /* position=, move! */
55
+ VALUE MW_Window_set_size(VALUE self, VALUE args); /* size=, resize! */
56
+ VALUE MW_Window_to_string(VALUE self); /* MoveWin::Window.to_s */
57
+
58
+ /* MW_Window is a structure that holds CFDictionaryRef and AXUIElementRefs */
59
+ typedef struct {
60
+ CFDictionaryRef cgWindow;
61
+ AXUIElementRef axWindow;
62
+ } MW_Window;
63
+
64
+ /* EnumerateWindows callback creates and stores MW_Windows in Ruby array */
65
+ void StoreWindows(CFDictionaryRef cgWindow, void *rb_ary_ptr);
66
+
67
+ /* Callback for Data_Wrap_Struct(), frees internal refs with CFRelease() */
68
+ void MW_Window_destroy(void *ref);
69
+
70
+ /* Copy contents of a CFStringRef into a Ruby string */
71
+ static VALUE convertStringRef(CFStringRef str);
72
+
73
+
74
+ /* ------------------------------------------------------------------------
75
+ * Public implementation
76
+ */
77
+
78
+ /* Define Ruby MoveWin module, MoveWin::Window class, and related methods */
79
+ void Init_movewin_ext() {
80
+ VALUE MW_Module;
81
+
82
+ /* Define module MoveWin and its methods */
83
+ MW_Module = rb_define_module("MoveWin");
84
+ rb_define_singleton_method(MW_Module, "authorized?", MW_is_authorized, 0);
85
+ rb_define_singleton_method(MW_Module, "display_size", MW_display_size, 0);
86
+ rb_define_singleton_method(MW_Module, "windows", MW_windows, 0);
87
+
88
+ /* Define class MoveWin::Window and its methods */
89
+ MW_WindowClass = rb_define_class_under(MW_Module, "Window", rb_cObject);
90
+ rb_define_method(MW_WindowClass, "app_name", MW_Window_app_name, 0);
91
+ rb_define_method(MW_WindowClass, "title", MW_Window_title, 0);
92
+ rb_define_method(MW_WindowClass, "position", MW_Window_position, 0);
93
+ rb_define_method(MW_WindowClass, "size", MW_Window_size, 0);
94
+ rb_define_method(MW_WindowClass, "position=", MW_Window_set_position, -2);
95
+ rb_define_method(MW_WindowClass, "size=", MW_Window_set_size, -2);
96
+ rb_define_method(MW_WindowClass, "move!", MW_Window_set_position, -2);
97
+ rb_define_method(MW_WindowClass, "resize!", MW_Window_set_size, -2);
98
+ rb_define_method(MW_WindowClass, "to_s", MW_Window_to_string, 0);
99
+ }
100
+
101
+ /* Return true if we are authorized to use OS X accessibility APIs */
102
+ VALUE MW_is_authorized(VALUE module) {
103
+ return isAuthorized() ? Qtrue : Qfalse;
104
+ }
105
+
106
+ /* Return dimensions of current main display as an array of two integers */
107
+ VALUE MW_display_size(VALUE module) {
108
+ CGRect bounds;
109
+ VALUE retval;
110
+
111
+ bounds = CGDisplayBounds(CGMainDisplayID());
112
+ retval = rb_ary_new();
113
+ rb_ary_push(retval, INT2NUM((int)CGRectGetMaxX(bounds)));
114
+ rb_ary_push(retval, INT2NUM((int)CGRectGetMaxY(bounds)));
115
+
116
+ return retval;
117
+ }
118
+
119
+ /* Return an array of MoveWin::Window objects (wrapped MW_Window structures) */
120
+ VALUE MW_windows(VALUE module) {
121
+ VALUE retval = rb_ary_new();
122
+ EnumerateWindows(NULL, StoreWindows, (void *)retval);
123
+ return retval;
124
+ }
125
+
126
+ /* Return application name (owner) of a MoveWin::Window as a Ruby string */
127
+ VALUE MW_Window_app_name(VALUE self) {
128
+ void *mwWindow;
129
+ CFStringRef app_name;
130
+
131
+ Data_Get_Struct(self, MW_Window, mwWindow);
132
+ app_name = CFDictionaryGetValue(
133
+ ((MW_Window *)mwWindow)->cgWindow, kCGWindowOwnerName
134
+ );
135
+
136
+ return convertStringRef(app_name);
137
+ }
138
+
139
+ /* Return title of a MoveWin::Window as a Ruby string */
140
+ VALUE MW_Window_title(VALUE self) {
141
+ void *mwWindow;
142
+ CFStringRef title;
143
+
144
+ Data_Get_Struct(self, MW_Window, mwWindow);
145
+ AXUIElementCopyAttributeValue(
146
+ ((MW_Window *)mwWindow)->axWindow, kAXTitleAttribute, (CFTypeRef *)&title
147
+ );
148
+
149
+ return convertStringRef(title);
150
+ }
151
+
152
+ /* Return position of a MoveWin::Window as array of two integer coordinates */
153
+ VALUE MW_Window_position(VALUE self) {
154
+ void *mwWindow;
155
+ CGPoint position;
156
+ VALUE retval;
157
+
158
+ Data_Get_Struct(self, MW_Window, mwWindow);
159
+ position = AXWindowGetPosition(((MW_Window *)mwWindow)->axWindow);
160
+ retval = rb_ary_new();
161
+ rb_ary_push(retval, INT2NUM((int)position.x));
162
+ rb_ary_push(retval, INT2NUM((int)position.y));
163
+
164
+ return retval;
165
+ }
166
+
167
+ /* Return position of a MoveWin::Window as array of two integer dimensions */
168
+ VALUE MW_Window_size(VALUE self) {
169
+ void *mwWindow;
170
+ CGSize size;
171
+ VALUE retval;
172
+
173
+ Data_Get_Struct(self, MW_Window, mwWindow);
174
+ size = AXWindowGetSize(((MW_Window *)mwWindow)->axWindow);
175
+ retval = rb_ary_new();
176
+ rb_ary_push(retval, INT2NUM((int)size.width));
177
+ rb_ary_push(retval, INT2NUM((int)size.height));
178
+
179
+ return retval;
180
+ }
181
+
182
+ /* Given two integers, or array of two integers, set position of window */
183
+ VALUE MW_Window_set_position(VALUE self, VALUE args) {
184
+ CGPoint position;
185
+ void *mwWindow;
186
+
187
+ if(RARRAY_LEN(args) == 1 && TYPE(rb_ary_entry(args, 0)) == T_ARRAY) {
188
+ args = rb_ary_entry(args, 0);
189
+ }
190
+ if( RARRAY_LEN(args) == 2 &&
191
+ TYPE(rb_ary_entry(args, 0)) == T_FIXNUM &&
192
+ TYPE(rb_ary_entry(args, 1)) == T_FIXNUM )
193
+ {
194
+ position.x = NUM2INT(rb_ary_entry(args, 0));
195
+ position.y = NUM2INT(rb_ary_entry(args, 1));
196
+ } else {
197
+ rb_raise(rb_eArgError, "wrong number of arguments");
198
+ }
199
+ Data_Get_Struct(self, MW_Window, mwWindow);
200
+ AXWindowSetPosition(((MW_Window *)mwWindow)->axWindow, position);
201
+
202
+ return MW_Window_position(self);
203
+ }
204
+
205
+ /* Given two integers, or array of two integers, set size of window */
206
+ VALUE MW_Window_set_size(VALUE self, VALUE args) {
207
+ CGSize size;
208
+ void *mwWindow;
209
+
210
+ if(RARRAY_LEN(args) == 1 && TYPE(rb_ary_entry(args, 0)) == T_ARRAY) {
211
+ args = rb_ary_entry(args, 0);
212
+ }
213
+ if( RARRAY_LEN(args) == 2 &&
214
+ TYPE(rb_ary_entry(args, 0)) == T_FIXNUM &&
215
+ TYPE(rb_ary_entry(args, 1)) == T_FIXNUM )
216
+ {
217
+ size.width = NUM2INT(rb_ary_entry(args, 0));
218
+ size.height = NUM2INT(rb_ary_entry(args, 1));
219
+ } else {
220
+ rb_raise(rb_eArgError, "wrong number of arguments");
221
+ }
222
+ Data_Get_Struct(self, MW_Window, mwWindow);
223
+ AXWindowSetSize(((MW_Window *)mwWindow)->axWindow, size);
224
+
225
+ return MW_Window_size(self);
226
+ }
227
+
228
+ /* Return "Application Name - Window Title" string to identify a window */
229
+ VALUE MW_Window_to_string(VALUE self) {
230
+ return rb_str_plus(
231
+ MW_Window_app_name(self),
232
+ rb_str_plus(rb_str_new2(" - "), MW_Window_title(self))
233
+ );
234
+ }
235
+
236
+
237
+ /* ------------------------------------------------------------------------
238
+ * Internal utility functions
239
+ */
240
+
241
+ /* Given window CFDictionaryRef and Ruby array, push MW_Window to array */
242
+ void StoreWindows(CFDictionaryRef cgWindow, void *rb_ary_ptr) {
243
+ int i;
244
+ AXUIElementRef axWindow;
245
+ MW_Window *mwWindow;
246
+ VALUE wrappedMwWindow;
247
+ VALUE mwWindows;
248
+
249
+ mwWindows = (VALUE)rb_ary_ptr;
250
+
251
+ i = 0;
252
+ while(1) {
253
+ axWindow = AXWindowFromCGWindow(cgWindow, i);
254
+ if(!axWindow) break;
255
+ mwWindow = (MW_Window *)malloc(sizeof(MW_Window));
256
+ mwWindow->cgWindow = cgWindow;
257
+ mwWindow->axWindow = axWindow;
258
+ wrappedMwWindow = Data_Wrap_Struct(
259
+ MW_WindowClass, NULL, MW_Window_destroy, (void *)mwWindow
260
+ );
261
+ rb_ary_push(mwWindows, wrappedMwWindow);
262
+ i++;
263
+ }
264
+ }
265
+
266
+ /* Free up MW_Window resources, for Ruby finalizer in Data_Wrap_Struct() */
267
+ void MW_Window_destroy(void *ref) {
268
+ MW_Window *mwWindow = (MW_Window *)ref;
269
+
270
+ if(mwWindow->cgWindow) CFRelease(mwWindow->cgWindow);
271
+ if(mwWindow->axWindow) CFRelease(mwWindow->axWindow);
272
+ free(mwWindow);
273
+ }
274
+
275
+ /* Given a CFStringRef, copy its contents into a Ruby string */
276
+ static VALUE convertStringRef(CFStringRef str) {
277
+ CFRange range;
278
+ CFStringEncoding encoding;
279
+ CFIndex byteCount;
280
+ UInt8 *buffer;
281
+ VALUE retval;
282
+
283
+ range = CFRangeMake(0, CFStringGetLength(str));
284
+ encoding = kCFStringEncodingUTF8;
285
+ CFStringGetBytes(str, range, encoding, 0, false, NULL, 0, &byteCount);
286
+ buffer = ALLOC_N(UInt8, byteCount);
287
+ CFStringGetBytes(str, range, encoding, 0, false, buffer, byteCount, NULL);
288
+ retval = rb_str_new((char *)buffer, (long)byteCount);
289
+ free(buffer);
290
+
291
+ return retval;
292
+ }
293
+
294
+
295
+ /* ======================================================================== */
@@ -0,0 +1,259 @@
1
+ /* ========================================================================
2
+ * winutils.c - utility functions for listing and moving windows
3
+ * Andrew Ho (andrew@zeuscat.com)
4
+ *
5
+ * Copyright (c) 2014, Andrew Ho.
6
+ * All rights reserved.
7
+ *
8
+ * Redistribution and use in source and binary forms, with or without
9
+ * modification, are permitted provided that the following conditions
10
+ * are met:
11
+ *
12
+ * Redistributions of source code must retain the above copyright notice,
13
+ * this list of conditions and the following disclaimer.
14
+ *
15
+ * Redistributions in binary form must reproduce the above copyright
16
+ * notice, this list of conditions and the following disclaimer in the
17
+ * documentation and/or other materials provided with the distribution.
18
+ *
19
+ * Neither the name of the author nor the names of its contributors may
20
+ * be used to endorse or promote products derived from this software
21
+ * without specific prior written permission.
22
+ *
23
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
26
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
27
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
28
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
29
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
30
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
31
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
32
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
33
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34
+ * ========================================================================
35
+ */
36
+
37
+ #include <fnmatch.h>
38
+ #include "winutils.h"
39
+
40
+ /* Search windows for match (NULL for all), run function (NULL for none) */
41
+ int EnumerateWindows(
42
+ char *pattern,
43
+ void(*callback)(CFDictionaryRef window, void *callback_data),
44
+ void *callback_data
45
+ ) {
46
+ int patternLen, subPatternLen, count, i, layer, titleSize;
47
+ char *subPattern, *starL, *starR, *appName, *windowName, *title;
48
+ CFArrayRef windowList;
49
+ CFDictionaryRef window;
50
+
51
+ /* Add asterisks to left/right of pattern, if they are not already there */
52
+ if(pattern && *pattern) {
53
+ patternLen = strlen(pattern);
54
+ starL = (*pattern == '*') ? "" : "*";
55
+ starR = (*pattern + (patternLen - 1) == '*') ? "" : "*";
56
+ subPatternLen = patternLen + strlen(starL) + strlen(starR) + 1;
57
+ subPattern = (char *)malloc(subPatternLen);
58
+ snprintf(subPattern, subPatternLen, "%s%s%s", starL, pattern, starR);
59
+ } else {
60
+ subPattern = pattern;
61
+ }
62
+
63
+ /* Iterate through list of all windows, run callback on pattern matches */
64
+ windowList = CGWindowListCopyWindowInfo(
65
+ (kCGWindowListOptionOnScreenOnly|kCGWindowListExcludeDesktopElements),
66
+ kCGNullWindowID
67
+ );
68
+ count = 0;
69
+ for(i = 0; i < CFArrayGetCount(windowList); i++) {
70
+ window = CFArrayGetValueAtIndex(windowList, i);
71
+
72
+ /* Skip windows that are not on the desktop layer */
73
+ layer = CFDictionaryGetInt(window, kCGWindowLayer);
74
+ if(layer > 0) continue;
75
+
76
+ /* Turn application name and title into string to match against */
77
+ appName = CFDictionaryCopyCString(window, kCGWindowOwnerName);
78
+ windowName = CFDictionaryCopyCString(window, kCGWindowName);
79
+ titleSize = strlen(appName) + strlen(" - ") + strlen(windowName) + 1;
80
+ title = (char *)malloc(titleSize);
81
+ snprintf(title, titleSize, "%s - %s", appName, windowName);
82
+
83
+ /* If no pattern, or pattern matches, run callback */
84
+ if(!pattern || fnmatch(subPattern, title, 0) == 0) {
85
+ if(callback) (*callback)(window, callback_data);
86
+ count++;
87
+ }
88
+
89
+ free(title);
90
+ free(windowName);
91
+ free(appName);
92
+ }
93
+ if(subPattern != pattern) free(subPattern);
94
+
95
+ return count;
96
+ }
97
+
98
+ /* Fetch an integer value from a CFDictionary */
99
+ int CFDictionaryGetInt(CFDictionaryRef dict, const void *key) {
100
+ int isSuccess, value;
101
+
102
+ isSuccess = CFNumberGetValue(
103
+ CFDictionaryGetValue(dict, key), kCFNumberIntType, &value
104
+ );
105
+
106
+ return isSuccess ? value : 0;
107
+ }
108
+
109
+ /* Copy a string value from a CFDictionary into a newly allocated string */
110
+ char *CFDictionaryCopyCString(CFDictionaryRef dict, const void *key) {
111
+ const void *dictValue;
112
+ CFIndex length;
113
+ int maxSize, isSuccess;
114
+ char *value;
115
+
116
+ dictValue = CFDictionaryGetValue(dict, key);
117
+ if(dictValue == NULL) return NULL;
118
+
119
+ /* If empty value, allocate and return empty string */
120
+ length = CFStringGetLength(dictValue);
121
+ maxSize = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8);
122
+ if(length == 0 || maxSize == 0) {
123
+ value = (char *)malloc(1);
124
+ *value = '\0';
125
+ return value;
126
+ }
127
+
128
+ /* Otherwise, allocate string and copy value into it */
129
+ value = (char *)malloc(maxSize);
130
+ isSuccess = CFStringGetCString(
131
+ dictValue, value, maxSize, kCFStringEncodingUTF8
132
+ );
133
+
134
+ return isSuccess ? value : NULL;
135
+ }
136
+
137
+ /* Given window dictionary from CGWindowList, return position */
138
+ CGPoint CGWindowGetPosition(CFDictionaryRef window) {
139
+ CFDictionaryRef bounds = CFDictionaryGetValue(window, kCGWindowBounds);
140
+ int x = CFDictionaryGetInt(bounds, CFSTR("X"));
141
+ int y = CFDictionaryGetInt(bounds, CFSTR("Y"));
142
+ return CGPointMake(x, y);
143
+ }
144
+
145
+ /* Given window dictionary from CGWindowList, return size */
146
+ CGSize CGWindowGetSize(CFDictionaryRef window) {
147
+ CFDictionaryRef bounds = CFDictionaryGetValue(window, kCGWindowBounds);
148
+ int width = CFDictionaryGetInt(bounds, CFSTR("Width"));
149
+ int height = CFDictionaryGetInt(bounds, CFSTR("Height"));
150
+ return CGSizeMake(width, height);
151
+ }
152
+
153
+ /* Return true if and only if we are authorized to call accessibility APIs */
154
+ bool isAuthorized() {
155
+ #if MAC_OS_X_VERSION_MIN_REQUIRED < 1090
156
+ return AXAPIEnabled() || AXIsProcessTrusted();
157
+ #else
158
+ /* Mavericks and later have only per-process accessibility permissions */
159
+ return AXIsProcessTrusted();
160
+ #endif
161
+ }
162
+
163
+ /* Given window dictionary from CGWindowList, return accessibility object */
164
+ AXUIElementRef AXWindowFromCGWindow(CFDictionaryRef window, int minIdx) {
165
+ CFStringRef targetWindowName, actualWindowTitle;
166
+ CGPoint targetPosition, actualPosition;
167
+ CGSize targetSize, actualSize;
168
+ pid_t pid;
169
+ AXUIElementRef app, appWindow, foundAppWindow;
170
+ CFArrayRef appWindowList;
171
+ int matchIdx, i;
172
+
173
+ /* Save the window name, position, and size we are looking for */
174
+ targetWindowName = CFDictionaryGetValue(window, kCGWindowName);
175
+ targetPosition = CGWindowGetPosition(window);
176
+ targetSize = CGWindowGetSize(window);
177
+
178
+ /* Load accessibility application from window PID */
179
+ pid = CFDictionaryGetInt(window, kCGWindowOwnerPID);
180
+ app = AXUIElementCreateApplication(pid);
181
+ AXUIElementCopyAttributeValue(
182
+ app, kAXWindowsAttribute, (CFTypeRef *)&appWindowList
183
+ );
184
+
185
+ /* Search application windows for first matching title, position, size:
186
+ * http://stackoverflow.com/questions/6178860/getting-window-number-through-osx-accessibility-api
187
+ */
188
+ matchIdx = 0;
189
+ foundAppWindow = NULL;
190
+ for(i = 0; i < CFArrayGetCount(appWindowList); i++) {
191
+ appWindow = CFArrayGetValueAtIndex(appWindowList, i);
192
+
193
+ /* Window name must match */
194
+ AXUIElementCopyAttributeValue(
195
+ appWindow, kAXTitleAttribute, (CFTypeRef *)&actualWindowTitle
196
+ );
197
+ if(CFStringCompare(targetWindowName, actualWindowTitle, 0) != 0) continue;
198
+
199
+ /* Position and size must match */
200
+ actualPosition = AXWindowGetPosition(appWindow);
201
+ if(!CGPointEqualToPoint(targetPosition, actualPosition)) continue;
202
+ actualSize = AXWindowGetSize(appWindow);
203
+ if(!CGSizeEqualToSize(targetSize, actualSize)) continue;
204
+
205
+ /* Multiple windows may match, caller chooses which match to return */
206
+ if(matchIdx >= minIdx) {
207
+ /* Found the first matching window, save it and break */
208
+ foundAppWindow = appWindow;
209
+ break;
210
+ } else {
211
+ matchIdx++;
212
+ }
213
+ }
214
+
215
+ return foundAppWindow;
216
+ }
217
+
218
+ /* Get a value from an accessibility object */
219
+ void AXWindowGetValue(
220
+ AXUIElementRef window,
221
+ CFStringRef attrName,
222
+ void *valuePtr
223
+ ) {
224
+ AXValueRef attrValue;
225
+ AXUIElementCopyAttributeValue(window, attrName, (CFTypeRef *)&attrValue);
226
+ AXValueGetValue(attrValue, AXValueGetType(attrValue), valuePtr);
227
+ CFRelease(attrValue);
228
+ }
229
+
230
+ /* Get position of window via accessibility object */
231
+ CGPoint AXWindowGetPosition(AXUIElementRef window) {
232
+ CGPoint position;
233
+ AXWindowGetValue(window, kAXPositionAttribute, &position);
234
+ return position;
235
+ }
236
+
237
+ /* Set position of window via accessibility object */
238
+ void AXWindowSetPosition(AXUIElementRef window, CGPoint position) {
239
+ AXValueRef attrValue = AXValueCreate(kAXValueCGPointType, &position);
240
+ AXUIElementSetAttributeValue(window, kAXPositionAttribute, attrValue);
241
+ CFRelease(attrValue);
242
+ }
243
+
244
+ /* Get size of window via accessibility object */
245
+ CGSize AXWindowGetSize(AXUIElementRef window) {
246
+ CGSize size;
247
+ AXWindowGetValue(window, kAXSizeAttribute, &size);
248
+ return size;
249
+ }
250
+
251
+ /* Set size of window via accessibility object */
252
+ void AXWindowSetSize(AXUIElementRef window, CGSize size) {
253
+ AXValueRef attrValue = AXValueCreate(kAXValueCGSizeType, &size);
254
+ AXUIElementSetAttributeValue(window, kAXSizeAttribute, attrValue);
255
+ CFRelease(attrValue);
256
+ }
257
+
258
+
259
+ /* ======================================================================== */
@@ -0,0 +1,91 @@
1
+ /* ========================================================================
2
+ * winutils.h - utility functions for listing and moving windows
3
+ * Andrew Ho (andrew@zeuscat.com)
4
+ *
5
+ * Copyright (c) 2014, Andrew Ho.
6
+ * All rights reserved.
7
+ *
8
+ * Redistribution and use in source and binary forms, with or without
9
+ * modification, are permitted provided that the following conditions
10
+ * are met:
11
+ *
12
+ * Redistributions of source code must retain the above copyright notice,
13
+ * this list of conditions and the following disclaimer.
14
+ *
15
+ * Redistributions in binary form must reproduce the above copyright
16
+ * notice, this list of conditions and the following disclaimer in the
17
+ * documentation and/or other materials provided with the distribution.
18
+ *
19
+ * Neither the name of the author nor the names of its contributors may
20
+ * be used to endorse or promote products derived from this software
21
+ * without specific prior written permission.
22
+ *
23
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
26
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
27
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
28
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
29
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
30
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
31
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
32
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
33
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34
+ * ========================================================================
35
+ */
36
+
37
+ #ifndef WINUTILS_H
38
+ #define WINUTILS_H
39
+
40
+ #ifdef __cplusplus
41
+ extern "C" {
42
+ #endif
43
+
44
+ #include <Carbon/Carbon.h>
45
+
46
+ /* Search windows for match (NULL for all), run function (NULL for none) */
47
+ int EnumerateWindows(
48
+ char *pattern,
49
+ void(*callback)(CFDictionaryRef window, void *callback_data),
50
+ void *callback_data
51
+ );
52
+
53
+ /* Fetch an integer value from a CFDictionary */
54
+ int CFDictionaryGetInt(CFDictionaryRef dict, const void *key);
55
+
56
+ /* Copy a string value from a CFDictionary into a newly allocated string */
57
+ char *CFDictionaryCopyCString(CFDictionaryRef dict, const void *key);
58
+
59
+ /* Given window dictionary from CGWindowList, return position, size */
60
+ CGPoint CGWindowGetPosition(CFDictionaryRef window);
61
+ CGSize CGWindowGetSize(CFDictionaryRef window);
62
+
63
+ /* Return true if and only if we are authorized to call accessibility APIs */
64
+ bool isAuthorized();
65
+
66
+ /* Given window dictionary from CGWindowList, return accessibility object */
67
+ AXUIElementRef AXWindowFromCGWindow(CFDictionaryRef window, int minIdx);
68
+
69
+ /* Get a value from an accessibility object */
70
+ void AXWindowGetValue(
71
+ AXUIElementRef window,
72
+ CFStringRef attrName,
73
+ void *valuePtr
74
+ );
75
+
76
+ /* Get or set position of window via accessibility object */
77
+ CGPoint AXWindowGetPosition(AXUIElementRef window);
78
+ void AXWindowSetPosition(AXUIElementRef window, CGPoint position);
79
+
80
+ /* Get or set size of window via accessibility object */
81
+ CGSize AXWindowGetSize(AXUIElementRef window);
82
+ void AXWindowSetSize(AXUIElementRef window, CGSize size);
83
+
84
+ #ifdef __cplusplus
85
+ }
86
+ #endif
87
+
88
+ #endif /* !WINUTILS_H */
89
+
90
+
91
+ /* ======================================================================== */
data/lib/movewin.rb ADDED
@@ -0,0 +1,61 @@
1
+ # ========================================================================
2
+ # movewin.rb - Ruby native code that augments movewin_ext Ruby extension
3
+ # Andrew Ho (andrew@zeuscat.com)
4
+ #
5
+ # Copyright (c) 2014, Andrew Ho.
6
+ # All rights reserved.
7
+ #
8
+ # Redistribution and use in source and binary forms, with or without
9
+ # modification, are permitted provided that the following conditions
10
+ # are met:
11
+ #
12
+ # Redistributions of source code must retain the above copyright notice,
13
+ # this list of conditions and the following disclaimer.
14
+ #
15
+ # Redistributions in binary form must reproduce the above copyright
16
+ # notice, this list of conditions and the following disclaimer in the
17
+ # documentation and/or other materials provided with the distribution.
18
+ #
19
+ # Neither the name of the author nor the names of its contributors may
20
+ # be used to endorse or promote products derived from this software
21
+ # without specific prior written permission.
22
+ #
23
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
26
+ # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
27
+ # HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
28
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
29
+ # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
30
+ # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
31
+ # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
32
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
33
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34
+ # ========================================================================
35
+
36
+ require 'movewin/movewin_ext'
37
+
38
+ module MoveWin
39
+ VERSION = '1.0'
40
+ class Window
41
+ def bounds
42
+ self.position + self.size
43
+ end
44
+ def bounds=(*args)
45
+ x, y, width, height = args.flatten
46
+ if x.nil? || y.nil?
47
+ raise ArgumentError,
48
+ 'wrong number of arguments (2 coordinates required)'
49
+ elsif width && height.nil?
50
+ raise ArgumentError,
51
+ 'wrong number of arguments (height required if width specified)'
52
+ end
53
+ self.move!(x, y)
54
+ self.resize!(width, height) if width && height
55
+ end
56
+ alias_method :set_bounds!, :bounds=
57
+ end
58
+ end
59
+
60
+
61
+ # ========================================================================
metadata ADDED
@@ -0,0 +1,49 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: movewin
3
+ version: !ruby/object:Gem::Version
4
+ version: '1.0'
5
+ platform: ruby
6
+ authors:
7
+ - Andrew Ho
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-08-31 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: List and move OS X windows from Ruby via the OS X accessibility APIs.
14
+ email: andrew@zeuscat.com
15
+ executables: []
16
+ extensions:
17
+ - ext/movewin/extconf.rb
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ext/movewin/extconf.rb
21
+ - ext/movewin/movewin_ext.c
22
+ - ext/movewin/winutils.c
23
+ - ext/movewin/winutils.h
24
+ - lib/movewin.rb
25
+ homepage: https://github.com/andrewgho/movewin-ruby
26
+ licenses:
27
+ - BSD-3-Clause
28
+ metadata: {}
29
+ post_install_message:
30
+ rdoc_options: []
31
+ require_paths:
32
+ - lib
33
+ required_ruby_version: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - ">="
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ required_rubygems_version: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ requirements: []
44
+ rubyforge_project:
45
+ rubygems_version: 2.2.2
46
+ signing_key:
47
+ specification_version: 4
48
+ summary: List and move OS X windows from Ruby
49
+ test_files: []