movewin 1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/ext/movewin/extconf.rb +13 -0
- data/ext/movewin/movewin_ext.c +295 -0
- data/ext/movewin/winutils.c +259 -0
- data/ext/movewin/winutils.h +91 -0
- data/lib/movewin.rb +61 -0
- metadata +49 -0
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: []
|