sbMacWindowMoveService.mm
Go to the documentation of this file.
1 /*
2  *=BEGIN SONGBIRD GPL
3  *
4  * This file is part of the Songbird web player.
5  *
6  * Copyright(c) 2005-2009 POTI, Inc.
7  * http://www.songbirdnest.com
8  *
9  * This file may be licensed under the terms of of the
10  * GNU General Public License Version 2 (the ``GPL'').
11  *
12  * Software distributed under the License is distributed
13  * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either
14  * express or implied. See the GPL for the specific language
15  * governing rights and limitations.
16  *
17  * You should have received a copy of the GPL along with this
18  * program. If not, go to http://www.gnu.org/licenses/gpl.html
19  * or write to the Free Software Foundation, Inc.,
20  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21  *
22  *=END SONGBIRD GPL
23  */
24 
25 #include "sbMacWindowMoveService.h"
26 #include "../NativeWindowFromNode.h"
27 
28 #include <nsIDOMWindow.h>
29 #include <nsCOMPtr.h>
30 #include <nsComponentManagerUtils.h>
31 #include <nsServiceManagerUtils.h>
32 
33 #import <objc/objc-class.h>
34 
35 
36 //==============================================================================
37 // ObjC class wrapper for owning nsISupports derivatives.
38 //==============================================================================
39 
40 @interface SBISupportsOwner : NSObject
41 {
42  nsISupports *mISupports; // AddRef'd
43 }
44 
45 - (id)initWithValue:(nsISupports *)aValue;
46 
47 - (nsISupports *)value; // return is not add-ref'd
48 
49 @end
50 
51 
52 @implementation SBISupportsOwner
53 
54 - (id)initWithValue:(nsISupports *)aValue;
55 {
56  if ((self = [super init])) {
58  NS_IF_ADDREF(mISupports);
59  }
60 
61  return self;
62 }
63 
64 - (void)dealloc
65 {
66  NS_IF_RELEASE(mISupports);
67  [super dealloc];
68 }
69 
71 {
72  return mISupports;
73 }
74 
75 @end
76 
77 
78 //==============================================================================
79 // Method swizzle method.
80 //==============================================================================
81 
82 void MethodSwizzle(Class aClass, SEL orig_sel, SEL alt_sel)
83 {
84  Method orig_method = nil, alt_method = nil;
85 
86  // First, look for methods
87  orig_method = class_getInstanceMethod(aClass, orig_sel);
88  alt_method = class_getInstanceMethod(aClass, alt_sel);
89 
90  // If both are found, swizzle them.
91  if ((orig_method != nil) && (alt_method != nil)) {
92  char *temp_type;
93  IMP temp_imp;
94 
95  temp_type = orig_method->method_types;
96  orig_method->method_types = alt_method->method_types;
97  alt_method->method_types = temp_type;
98 
99  temp_imp = orig_method->method_imp;
100  orig_method->method_imp = alt_method->method_imp;
101  alt_method->method_imp = temp_imp;;
102  }
103 }
104 
105 
106 //==============================================================================
107 // NSWindow category entry for swizzled method.
108 //==============================================================================
109 
110 static NSString *kSBWindowStoppedMovingNotification = @"SBWindowStoppedMoving";
111 
112 
114 
115 - (void)swizzledSendEvent:(NSEvent *)aEvent;
116 
117 @end
118 
119 @implementation NSWindow (SBSwizzleSupport)
120 
121 - (void)swizzledSendEvent:(NSEvent *)aEvent
122 {
123  // Swizzle back to the original method.
124  MethodSwizzle([self class],
125  @selector(swizzledSendEvent:),
126  @selector(sendEvent:));
127 
128  [self sendEvent:aEvent];
129 
130  // Check for the mouse up event.
131  BOOL shouldReswizzle = YES;
132  if (NSLeftMouseUp == [aEvent type]) {
133  NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
134  [defaultCenter postNotificationName:kSBWindowStoppedMovingNotification
135  object:self];
136  shouldReswizzle = NO;
137  }
138 
139  // If this event was the mouse up event, don't reswizzle. The method will be
140  // reswizzled when this interface gets notified of a window move start in
141  // |onWindowWillMove|.
142  if (shouldReswizzle) {
143  MethodSwizzle([self class],
144  @selector(sendEvent:),
145  @selector(swizzledSendEvent:));
146  }
147 }
148 
149 @end
150 
151 
152 //==============================================================================
153 // Window Listener Context Utility Class.
154 //==============================================================================
155 
156 @interface SBWinMoveListenerContext : NSObject
157 {
159  NSView *mWatchedView; // strong
160 }
161 
162 - (id)initWithListener:(sbIWindowMoveListener *)aListener
163  view:(NSView *)aView;
164 
165 - (void)onWindowWillMove;
166 - (void)onWindowDidStopMoving:(NSNotification *)aNotification;
167 - (void)notifyListenerMoveStoppedTimeout;
168 
169 @end
170 
171 @implementation SBWinMoveListenerContext
172 
173 - (id)initWithListener:(sbIWindowMoveListener *)aListener
174  view:(NSView *)aView
175 {
176  if ((self = [super init])) {
177  mListener = [[SBISupportsOwner alloc] initWithValue:aListener];
178  mWatchedView = [aView retain];
179  }
180 
181  return self;
182 }
183 
184 - (void)dealloc
185 {
186  [mListener release];
187  [mWatchedView release];
188  [super dealloc];
189 }
190 
192 {
193  // First, notify our listener
194  nsresult rv;
195  nsCOMPtr<sbIWindowMoveListener> listener =
196  do_QueryInterface([mListener value], &rv);
197  NS_ENSURE_SUCCESS(rv, /* void */);
198  listener->OnMoveStarted();
199 
200  // Next, to avoid hacking on XR for bug XXX simply method swizzle the
201  // |mouseUp:| event handler for the window class. This can be avoided by
202  // patching XR to post an event when this event happens in the |NSWindow|
203  // subclasses in the cocoa widget stuff.
205  @selector(sendEvent:),
206  @selector(swizzledSendEvent:));
207 
208  NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
209  [defaultCenter addObserver:self
210  selector:@selector(onWindowDidStopMoving:)
211  name:kSBWindowStoppedMovingNotification
212  object:nil];
213 }
214 
215 - (void)onWindowDidStopMoving:(NSNotification *)aNotification
216 {
217  // Hack, notify after a short timeout to ensure the listener will actually
218  // be able to look at the window frame.
219  [self performSelector:@selector(notifyListenerMoveStoppedTimeout)
220  withObject:nil
221  afterDelay:0.1];
222 
223  // No longer need to listen to events for now.
224  [[NSNotificationCenter defaultCenter] removeObserver:self];
225 }
226 
228 {
229  // Notify our listener of the move stop
230  nsresult rv;
231  nsCOMPtr<sbIWindowMoveListener> listener =
232  do_QueryInterface([mListener value], &rv);
233  NS_ENSURE_SUCCESS(rv, /* void */);
234  listener->OnMoveStopped();
235 }
236 
237 @end
238 
239 
240 //==============================================================================
241 // SBMacCocoaWindowListener
242 //==============================================================================
243 
245 
246 - (void)startListening;
247 - (void)stopListening;
248 - (void)windowWillMove:(NSNotification *)aNotification;
249 
250 @end
251 
252 
253 @implementation SBMacCocoaWindowListener
254 
255 - (id)initWithService:(sbMacWindowMoveService *)aService
256 {
257  if ((self = [super init])) {
258  mListenerWinDict = [[NSMutableDictionary alloc] init];
259  mService = aService;
260  mIsObserving = NO;
261  }
262 
263  return self;
264 }
265 
266 - (void)dealloc
267 {
268  [mListenerWinDict release];
269  mService = nsnull;
270  [super dealloc];
271 }
272 
273 - (void)beginObservingWindow:(NSWindow *)aWindow
274  forListener:(sbIWindowMoveListener *)aListener
275 {
276  if (!mIsObserving) {
277  [self startListening];
278  }
279 
280  // The NSWindow goes away when we go fullscreen. Retain the contentView
281  // instead since that persists.
282  NSView *contentView = [aWindow contentView];
283 
284  SBWinMoveListenerContext *listenerContext =
285  [[SBWinMoveListenerContext alloc] initWithListener:aListener
286  view:contentView];
287 
288  [mListenerWinDict setObject:listenerContext
289  forKey:[NSNumber numberWithInt:[contentView hash]]];
290 }
291 
292 - (void)stopObservingWindow:(NSWindow *)aWindow
293  forListener:(sbIWindowMoveListener *)aListener
294 {
295  NSView *contentView = [aWindow contentView];
296 
297  // If this was the last window in the watch list, stop listening.
298  NSNumber *viewHash = [NSNumber numberWithInt:[contentView hash]];
299  [mListenerWinDict removeObjectForKey:viewHash];
300 
301  // If this was the last listener in the dictionary, stop listening to window
302  // events for the app.
303  if ([mListenerWinDict count] == 0) {
304  [self stopListening];
305  }
306 }
307 
308 - (void)startListening
309 {
310  if (mIsObserving) {
311  return;
312  }
313 
314  // Listen to window move events.
315  NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
316  [defaultCenter addObserver:self
317  selector:@selector(windowWillMove:)
318  name:NSWindowWillMoveNotification
319  object:nil];
320 
321  mIsObserving = YES;
322 }
323 
324 - (void)stopListening
325 {
326  if (!mIsObserving) {
327  return;
328  }
329 
330  [[NSNotificationCenter defaultCenter] removeObserver:self];
331  mIsObserving = NO;
332 }
333 
334 - (void)windowWillMove:(NSNotification *)aNotification
335 {
336  NSWindow *eventWindow = (NSWindow *)[aNotification object];
337 
338  NSView *contentView = [eventWindow contentView];
339 
340  SBWinMoveListenerContext *listenerContext =
341  (SBWinMoveListenerContext *)[mListenerWinDict objectForKey:
342  [NSNumber numberWithInt:[contentView hash]]];
343  if (listenerContext) {
344  [listenerContext onWindowWillMove];
345  }
346 }
347 
348 @end
349 
350 
351 //==============================================================================
352 // sbMacWindowMoveResizeService
353 //==============================================================================
354 
356 
358 {
359 }
360 
362 {
363  [mWinListener release];
364 }
365 
366 nsresult
368 {
369  mWinListener = [[SBMacCocoaWindowListener alloc] initWithService:this];
370  NS_ENSURE_TRUE(mWinListener, NS_ERROR_OUT_OF_MEMORY);
371 
372  return NS_OK;
373 }
374 
375 //------------------------------------------------------------------------------
376 // sbIWindowMoveService
377 
378 NS_IMETHODIMP
379 sbMacWindowMoveService::StartWatchingWindow(
380  nsISupports *aWindow,
381  sbIWindowMoveListener *aListener)
382 {
383  NS_ENSURE_ARG_POINTER(aWindow);
384  NS_ENSURE_ARG_POINTER(aListener);
385 
386  nsresult rv;
387  nsCOMPtr<nsISupports> supports = do_QueryInterface(aWindow, &rv);
388  NS_ENSURE_SUCCESS(rv, rv);
389 
390  NSWindow *window = NativeWindowFromNode::get(supports);
391  NS_ENSURE_TRUE(window, NS_ERROR_UNEXPECTED);
392 
393  [mWinListener beginObservingWindow:window forListener:aListener];
394  return NS_OK;
395 }
396 
397 NS_IMETHODIMP
398 sbMacWindowMoveService::StopWatchingWindow(
399  nsISupports *aWindow,
400  sbIWindowMoveListener *aListener)
401 {
402  NS_ENSURE_ARG_POINTER(aWindow);
403  NS_ENSURE_ARG_POINTER(aListener);
404 
405  nsresult rv;
406  nsCOMPtr<nsISupports> supports = do_QueryInterface(aWindow, &rv);
407  NS_ENSURE_SUCCESS(rv, rv);
408 
409  NSWindow *window = NativeWindowFromNode::get(supports);
410  NS_ENSURE_TRUE(window, NS_ERROR_UNEXPECTED);
411 
412  [mWinListener stopObservingWindow:window forListener:aListener];
413  return NS_OK;
414 }
415 
return NS_OK
menuItem id
Definition: FeedWriter.js:971
onPageChanged aValue
Definition: FeedWriter.js:1395
NS_IMPL_ISUPPORTS1(sbDeviceCapabilitiesUtils, sbIDeviceCapabilitiesUtils) sbDeviceCapabilitiesUtils
let window
_window init
Definition: FeedWriter.js:1144
var count
Definition: test_bug7406.js:32
static NSString * kSBWindowStoppedMovingNotification
static void * get(nsISupports *window)
function hash(str)
Definition: sbAboutDRM.js:40
NS_DECL_ISUPPORTS NS_DECL_SBIWINDOWMOVESERVICE nsresult Init()
var Class
countRef value
Definition: FeedWriter.js:1423
void MethodSwizzle(Class aClass, SEL orig_sel, SEL alt_sel)