movewin 1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []