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 +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: []
|