source: trunk/Cocoa/AntiRSI/AntiRSI.m@ 329

Last change on this file since 329 was 329, checked in by Nicholas Riley, 17 years ago

AntiRSI changes for "something" called 1.4, with much of the code but not the same UI as Onne Gorter's released 1.4.

Info.plist, English.lproj/InfoPlist.strings: Updated for 1.4.

AntiRSI.[hm]: Some of Onne Gorter's changes, update checking, "go to Web site" and crediting idle time to work break, and "AntiRSI Help". Most of these are not hooked up in the UI as above. Default to smooth sampling.

AntiRSI.xcodeproj: Build fat (i386/ppc).

English.lproj/MainMenu.nib: Some changes...

File size: 20.1 KB
Line 
1/*
2 author: Onne Gorter
3
4 This file is part of AntiRSI.
5
6 AntiRSI is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10
11 AntiRSI is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with AntiRSI; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20
21#import "AntiRSI.h"
22
23#include <math.h>
24#include <ApplicationServices/ApplicationServices.h>
25
26extern CFTimeInterval CGSSecondsSinceLastInputEvent(unsigned long eventType);
27
28@implementation AntiRSI
29
30// bindings methods
31- (void)setMicro_pause_duration:(float)f
32{
33 micro_pause_duration = round(f);
34 if (s_taking_micro_pause == state) {
35 [progress setMaxValue:micro_pause_duration];
36 [progress setDoubleValue:micro_pause_taking_t];
37 }
38}
39
40- (void)setMicro_pause_period:(float)f
41{ micro_pause_period = 60 * round(f); }
42
43- (void)setWork_break_duration:(float)f
44{
45 work_break_duration = 60 * round(f);
46 if (s_taking_work_break == state) {
47 [progress setMaxValue:work_break_duration / 60];
48 [progress setDoubleValue:work_break_taking_t / 60 - 0.5];
49 }
50}
51
52- (void)setWork_break_period:(float)f
53{ work_break_period = 60 * round(f); }
54
55- (void)installTimer:(double)interval
56{
57 if (mtimer != nil) {
58 [mtimer invalidate];
59 [mtimer autorelease];
60 }
61 mtimer = [[NSTimer scheduledTimerWithTimeInterval:interval target:self selector:@selector(tick:)
62 userInfo:nil repeats:YES] retain];
63}
64
65- (void)setSample_interval:(NSString *)s
66{
67 sample_interval = 1;
68 if ([s isEqualToString:@"Super Smooth"]) sample_interval = 0.1;
69 if ([s isEqualToString:@"Smooth"]) sample_interval = 0.33;
70 if ([s isEqualToString:@"Normal"]) sample_interval = 1;
71 if ([s isEqualToString:@"Low"]) sample_interval = 2;
72
73 [self installTimer:sample_interval];
74}
75
76- (void)setDraw_dock_image:(BOOL)b
77{
78 draw_dock_image=b;
79 if (!b) {
80 [NSApp setApplicationIconImage:[NSImage imageNamed:@"AntiRSI"]];
81 } else {
82 [self drawDockImage];
83 }
84}
85
86- (void)setBackground:(NSColor *)c
87{
88 [background autorelease];
89 background=[c retain];
90
91 // make new darkbackground color
92 float r,g,b,a;
93 [background getRed:&r green:&g blue:&b alpha:&a];
94 [darkbackground autorelease];
95 darkbackground=[[NSColor colorWithCalibratedRed:r*0.35 green:g*0.35 blue:b*0.35 alpha:a+0.2] retain];
96
97 [self drawDockImage];
98}
99
100- (void)setElapsed:(NSColor *)c
101{
102 [elapsed autorelease];
103 elapsed=[c retain];
104 [self drawDockImage];
105}
106
107- (void)setTaking:(NSColor *)c
108{
109 [taking autorelease];
110 taking=[c retain];
111 [self drawDockImage];
112}
113
114// end of bindings
115
116- (void)awakeFromNib
117{
118 // want transparancy
119 [NSColor setIgnoresAlpha:NO];
120
121 // initial colors
122 elapsed = [[NSColor colorWithCalibratedRed:0.3 green:0.3 blue:0.9 alpha:0.95] retain];
123 taking = [[NSColor colorWithCalibratedRed:0.3 green:0.9 blue:0.3 alpha:0.90] retain];
124 background = [NSColor colorWithCalibratedRed:0.9 green:0.9 blue:0.9 alpha:0.7];
125
126 //initial values
127 micro_pause_period = 4*60;
128 micro_pause_duration = 13;
129 work_break_period = 50*60;
130 work_break_duration = 8*60;
131 sample_interval = 1;
132
133 // set current state
134 state = s_normal;
135
136 // set timers to 0
137 micro_pause_t = 0;
138 work_break_t = 0;
139 micro_pause_taking_t = 0;
140 work_break_taking_t = 0;
141 work_break_taking_cached_t = 0;
142 work_break_taking_cached_date = 0;
143
144 // initialize dock image
145 dock_image = [[NSImage alloc] initWithSize:NSMakeSize(128,128)];
146 [dock_image setCacheMode:NSImageCacheNever];
147 original_dock_image = [NSImage imageNamed:@"AntiRSI"];
148 draw_dock_image_q = YES;
149
150 // setup main window that will show either micropause or workbreak
151 main_window = [[NSWindow alloc] initWithContentRect:[view frame]
152 styleMask:NSBorderlessWindowMask
153 backing:NSBackingStoreBuffered defer:YES];
154 [main_window setBackgroundColor:[NSColor clearColor]];
155 [main_window setLevel:NSStatusWindowLevel];
156 [main_window setAlphaValue:0.85];
157 [main_window setOpaque:NO];
158 [main_window setHasShadow:NO];
159 [main_window setMovableByWindowBackground:YES];
160 [main_window center];
161 [main_window setContentView:view];
162 [progress setEnabled:NO];
163
164 // initialze history filter
165 h0 = 0;
166 h1 = 0;
167 h2 = 0;
168
169 // initialize ticks
170 date = [NSDate timeIntervalSinceReferenceDate];
171
172 // set background now
173 [self setBackground:background];
174
175 // create initial values
176 NSMutableDictionary* initial = [NSMutableDictionary dictionaryWithCapacity:10];
177 [initial setObject:[NSNumber numberWithFloat:4] forKey:@"micro_pause_period"];
178 [initial setObject:[NSNumber numberWithFloat:13] forKey:@"micro_pause_duration"];
179 [initial setObject:[NSNumber numberWithFloat:50] forKey:@"work_break_period"];
180 [initial setObject:[NSNumber numberWithFloat:8] forKey:@"work_break_duration"];
181 [initial setObject:@"Smooth" forKey:@"sample_interval"];
182 [initial setObject:[NSNumber numberWithBool:YES] forKey:@"draw_dock_image"];
183 [initial setObject:[NSNumber numberWithBool:NO] forKey:@"lock_focus"];
184 [initial setObject:[NSArchiver archivedDataWithRootObject:elapsed] forKey:@"elapsed"];
185 [initial setObject:[NSArchiver archivedDataWithRootObject:taking] forKey:@"taking"];
186 [initial setObject:[NSArchiver archivedDataWithRootObject:background] forKey:@"background"];
187 [[NSUserDefaultsController sharedUserDefaultsController] setInitialValues:initial];
188
189 // bind to defauls controller
190 id dc = [NSUserDefaultsController sharedUserDefaultsController];
191 [self bind:@"micro_pause_period" toObject:dc withKeyPath:@"values.micro_pause_period" options:nil];
192 [self bind:@"micro_pause_duration" toObject:dc withKeyPath:@"values.micro_pause_duration" options:nil];
193 [self bind:@"work_break_period" toObject:dc withKeyPath:@"values.work_break_period" options:nil];
194 [self bind:@"work_break_duration" toObject:dc withKeyPath:@"values.work_break_duration" options:nil];
195 [self bind:@"sample_interval" toObject:dc withKeyPath:@"values.sample_interval" options:nil];
196 [self bind:@"draw_dock_image" toObject:dc withKeyPath:@"values.draw_dock_image" options:nil];
197 [self bind:@"lock_focus" toObject:dc withKeyPath:@"values.lock_focus" options:nil];
198 NSDictionary* unarchive = [NSDictionary dictionaryWithObject:NSUnarchiveFromDataTransformerName forKey:@"NSValueTransformerName"];
199 [self bind:@"elapsed" toObject:dc withKeyPath:@"values.elapsed" options:unarchive];
200 [self bind:@"taking" toObject:dc withKeyPath:@"values.taking" options:unarchive];
201 [self bind:@"background" toObject:dc withKeyPath:@"values.background" options:unarchive];
202
203 // alert every binding
204 [[NSUserDefaultsController sharedUserDefaultsController] revert:self];
205
206 // start the timer
207 [self installTimer:sample_interval];
208
209 // about dialog
210 [version setStringValue:[NSString stringWithFormat:@"Version %@", sVersion]];
211}
212
213// tick every second and update status
214- (void)tick:(NSTimer *)timer
215{
216 // calculate time since last tick
217 double new_date = [NSDate timeIntervalSinceReferenceDate];
218 double tick_time = new_date - date;
219 date = new_date;
220
221 // check if we are still on track of normal time, otherwise we might have slept or something
222 if (tick_time > work_break_duration) {
223 // set timers to 0
224 micro_pause_t = 0;
225 work_break_t = 0;
226 micro_pause_taking_t = micro_pause_duration;
227 work_break_taking_t = work_break_duration;
228 if (s_normal != state) {
229 [self endBreak];
230 }
231 // and do stuff on next tick
232 return;
233 }
234
235 // just did a whole micropause beyond normal time
236 if (tick_time > micro_pause_duration && s_taking_work_break != state) {
237 // set micro_pause timers to 0
238 micro_pause_t = 0;
239 micro_pause_taking_t = micro_pause_duration;
240 if (s_normal != state) {
241 [self endBreak];
242 }
243 // and do stuff on next tick
244 return;
245 }
246
247 // get idle time in seconds
248 CFTimeInterval idle_time = CGSSecondsSinceLastInputEvent(kCGAnyInputEventType);
249 // CFTimeInterval cgs_idle_time = idle_time;
250 // from other people's reverse engineering of this function, on MDD G4s this can return a large positive number when input is in progress
251 if (idle_time >= 18446744000.0) {
252 idle_time = 0;
253 } else if (CGEventSourceSecondsSinceLastEventType != NULL) {
254 CGEventType eventTypes[] = { kCGEventLeftMouseDown, kCGEventLeftMouseUp, kCGEventRightMouseDown, kCGEventRightMouseUp, kCGEventMouseMoved, kCGEventLeftMouseDragged, kCGEventRightMouseDragged, kCGEventKeyDown, kCGEventKeyUp, kCGEventFlagsChanged, kCGEventScrollWheel, kCGEventTabletPointer, kCGEventTabletProximity, kCGEventOtherMouseDown, kCGEventOtherMouseUp, kCGEventOtherMouseDragged, kCGEventNull };
255 CFTimeInterval event_idle_time;
256 idle_time = DBL_MAX;
257 for (CGEventType *eventType = eventTypes ; *eventType != kCGEventNull ; eventType++) {
258 event_idle_time = CGEventSourceSecondsSinceLastEventType(kCGEventSourceStateCombinedSessionState, *eventType);
259 if (event_idle_time < idle_time) idle_time = event_idle_time;
260 }
261 }
262 // NSLog(@"CGEventSource %.2f, CGS %.2f", idle_time, cgs_idle_time);
263
264 // calculate slack, this gives a sort of 3 history filtered idea.
265 BOOL slack = (h2 + h1 + h0 > 15);
266
267 // if new event comes in history bumps up
268 if (h0 >= idle_time || idle_time < sample_interval) {
269 h2 = h1;
270 h1 = h0;
271 }
272 h0 = idle_time;
273
274 switch (state) {
275 case s_normal:
276 // idle_time needs to be at least 0.3 * micro_pause_duration before kicking in
277 // but we cut the user some slack based on previous idle_times
278 if (idle_time <= micro_pause_duration * 0.3 && !slack) {
279 micro_pause_t += tick_time;
280 work_break_t += tick_time;
281 micro_pause_taking_t = 0;
282 if (work_break_taking_t > 0) {
283 work_break_taking_cached_t = work_break_taking_t;
284 work_break_taking_cached_date = date;
285 }
286 work_break_taking_t = 0;
287 } else if (micro_pause_t > 0) {
288 // oke, leaway is over, increase micro_pause_taking_t unless micro_pause is already over
289 //micro_pause_t stays put
290 work_break_t += tick_time;
291 micro_pause_taking_t += tick_time;
292 work_break_taking_t = 0;
293 }
294
295 // if micro_pause_taking_t is above micro_pause_duration, then micro pause is over,
296 // if still idleing workbreak_taking_t kicks in unless it is already over
297 if (micro_pause_taking_t >= micro_pause_duration && work_break_t > 0) {
298 work_break_taking_t += tick_time;
299 micro_pause_t = 0;
300 }
301
302 // if work_break_taking_t is above work_break_duration, then work break is over
303 if (work_break_taking_t >= work_break_duration) {
304 micro_pause_t = 0;
305 work_break_t = 0;
306 // micro_pause_taking_t stays put
307 // work_break_taking_t stays put
308 }
309
310 // if user needs to take a micro pause
311 if (micro_pause_t >= micro_pause_period) {
312 // anticipate next workbreak by not issuing this micro_pause ...
313 if (work_break_t > work_break_period - (micro_pause_period / 2)) {
314 work_break_t = work_break_period;
315 [self doWorkBreak];
316 } else {
317 [self doMicroPause];
318 }
319 }
320
321 // if user needs to take a work break
322 if (work_break_t >= work_break_period) {
323 // stop micro_pause stuff
324 micro_pause_t = 0;
325 micro_pause_taking_t = micro_pause_duration;
326 // and display window
327 [self doWorkBreak];
328 }
329 break;
330
331 // taking a micro pause with window
332 case s_taking_micro_pause:
333 // continue updating timers
334 micro_pause_taking_t += tick_time;
335 work_break_t += tick_time;
336
337 // if we don't break, or interrupt the break, reset it
338 if (idle_time < 1 && !slack) {
339 micro_pause_taking_t = 0;
340 }
341
342 // update window
343 [progress setDoubleValue:micro_pause_taking_t];
344 [self drawTimeLeft:micro_pause_duration - micro_pause_taking_t];
345 [self drawNextBreak:work_break_period - work_break_t];
346
347 // if user likes to be interrupted
348 if (lock_focus) {
349 [NSApp activateIgnoringOtherApps:YES];
350 [main_window makeKeyAndOrderFront:self];
351 }
352
353 // check if we done enough
354 if (micro_pause_taking_t > micro_pause_duration) {
355 micro_pause_t = 0;
356 [self endBreak];
357 }
358
359 // if workbreak must be run ...
360 if (work_break_t >= work_break_period) {
361 // stop micro_pause stuff
362 micro_pause_t = 0;
363 micro_pause_taking_t = micro_pause_duration;
364 // and display window
365 [self doWorkBreak];
366 } else {
367 double slip = (micro_pause_duration - micro_pause_taking_t) - (int)(micro_pause_duration - micro_pause_taking_t);
368 [self installTimer: slip < 0.1 ? 1 : slip];
369 }
370 break;
371
372 // taking a work break with window
373 case s_taking_work_break:
374 // increase work_break_taking_t
375 if (idle_time >= 2 || work_break_taking_t < 3) {
376 work_break_taking_t += tick_time;
377 }
378
379 // draw window
380 [progress setDoubleValue:work_break_taking_t / 60 - 0.5];
381 [self drawTimeLeft:work_break_duration - work_break_taking_t];
382 [self drawNextBreak:work_break_period + work_break_duration - work_break_taking_t];
383
384 // if user likes to be interrupted
385 if (lock_focus) {
386 [NSApp activateIgnoringOtherApps:YES];
387 [main_window makeKeyAndOrderFront:self];
388 }
389
390 // and check if we done enough
391 if (work_break_taking_t > work_break_duration) {
392 micro_pause_t = 0;
393 micro_pause_taking_t = micro_pause_duration;
394 work_break_t = 0;
395 work_break_taking_t = work_break_duration;
396 [self endBreak];
397 } else {
398 double slip = (work_break_duration - work_break_taking_t) - (int)(work_break_duration - work_break_taking_t);
399 [self installTimer: slip < 0.1 ? 1 : slip];
400 }
401 break;
402 }
403
404 // draw dock image
405 if (draw_dock_image) [self drawDockImage];
406}
407
408// draw the dock icon
409- (void)drawDockImage
410{
411 [dock_image lockFocus];
412
413 // clear all
414 [[NSColor clearColor] set];
415 NSRectFill(NSMakeRect(0,0,127,127));
416
417 NSBezierPath* p;
418 float end;
419
420 //draw background circle
421 [darkbackground set];
422 p =[NSBezierPath bezierPathWithOvalInRect:NSMakeRect(6,6,115,115)];
423 [p setLineWidth:4];
424 [p stroke];
425
426 //fill
427 [background set];
428 [[NSBezierPath bezierPathWithOvalInRect:NSMakeRect(8,8,111,111)] fill];
429
430 //put dot in middle
431 [darkbackground set];
432 [[NSBezierPath bezierPathWithOvalInRect:NSMakeRect(59,59,9,9)] fill];
433
434 // reuse this one
435 p = [NSBezierPath bezierPath];
436
437 // draw work_break
438 [elapsed set];
439 end = 360 - (360.0 / work_break_period * work_break_t - 90);
440 if (end <= 90) end=90.1;
441 [p appendBezierPathWithArcWithCenter:NSMakePoint(63.5, 63.5) radius:40 startAngle:90 endAngle:end clockwise:YES];
442 [p setLineWidth:22];
443 [p stroke];
444
445 // draw work break taking
446 [taking set];
447 [p removeAllPoints];
448 end = 360 - (360.0 / work_break_duration * work_break_taking_t - 90);
449 if (end <= 90) end=90.1;
450 [p appendBezierPathWithArcWithCenter:NSMakePoint(63.5, 63.5) radius:40 startAngle:90 endAngle:end clockwise:YES];
451 [p setLineWidth:18];
452 [p stroke];
453
454 // draw micro pause
455 [elapsed set];
456 [p removeAllPoints];
457 end = 360 - (360.0 / micro_pause_period * micro_pause_t - 90);
458 if (end <= 90) end = 90.1;
459 [p appendBezierPathWithArcWithCenter:NSMakePoint(63.5, 63.5) radius:17 startAngle:90 endAngle:end clockwise:YES];
460 [p setLineWidth:22];
461 [p stroke];
462
463 // draw micro pause taking
464 [taking set];
465 [p removeAllPoints];
466 end = 360 - (360.0 / micro_pause_duration * micro_pause_taking_t - 90);
467 if (end <= 90) end = 90.1;
468 [p appendBezierPathWithArcWithCenter:NSMakePoint(63.5, 63.5) radius:17 startAngle:90 endAngle:end clockwise:YES];
469 [p setLineWidth:18];
470 [p stroke];
471
472 [dock_image unlockFocus];
473
474 // and set it in the dock check draw_dock_image one last time ...
475 if (draw_dock_image_q) [NSApp setApplicationIconImage:dock_image];
476}
477
478// done with micro pause or work break
479- (void)endBreak
480{
481 [main_window orderOut:NULL];
482 state = s_normal;
483 // reset time interval to user's choice
484 [self installTimer:sample_interval];
485}
486
487// display micro_pause window with appropriate widgets and progress bar
488- (void)doMicroPause
489{
490 micro_pause_taking_t = 0;
491 [status setStringValue:@"Micro Pause"];
492 [progress setMaxValue:micro_pause_duration];
493 [progress setDoubleValue:micro_pause_taking_t];
494 [progress setWarningValue: 1];
495 [progress setCriticalValue: micro_pause_duration];
496 [postpone setHidden:YES];
497 state = s_taking_micro_pause;
498 [self tick: nil];
499 [main_window center];
500 [main_window orderFrontRegardless];
501}
502
503// display work_break window with appropriate widgets and progress bar
504- (void)doWorkBreak
505{
506 work_break_taking_t = 0;
507 // incase you were already having an implicit work break and clicked the take work break now button
508 // not more then 20 seconds ago we took a natural break longer then 0.2 * normal work break duration
509 if (date - work_break_taking_cached_date < 20 && work_break_taking_cached_t > work_break_duration * 0.2) {
510 work_break_taking_t = work_break_taking_cached_t;
511 }
512 [status setStringValue:@"Work Break"];
513 [progress setMaxValue:work_break_duration / 60];
514 [progress setDoubleValue:work_break_taking_t / 60 - 0.5];
515 [progress setWarningValue: 0];
516 [progress setCriticalValue: 0.4];
517 [postpone setHidden:NO];
518 state = s_taking_work_break;
519 [self tick: nil];
520 [main_window center];
521 [main_window orderFrontRegardless];
522}
523
524// diplays time left
525- (void)drawTimeLeft:(double)seconds
526{
527 [time setStringValue:[NSString stringWithFormat:@"%d:%02d", lrint(seconds) / 60, lrint(seconds) % 60]];
528}
529
530// displays next break
531- (void)drawNextBreak:(int)seconds
532{
533 int minutes = round(seconds / 60.0) ;
534
535 // nice hours, minutes ...
536 if (minutes > 60) {
537 [next_break setStringValue:[NSString stringWithFormat:@"next break in %d:%02d hours",
538 minutes / 60, minutes % 60]];
539 } else {
540 [next_break setStringValue:[NSString stringWithFormat:@"next break in %d minutes", minutes]];
541 }
542}
543
544// goto website
545- (IBAction)gotoWebsite:(id)sender
546{
547 [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:sURL]];
548}
549
550// check for update
551- (IBAction)checkForUpdate:(id)sender
552{
553 NSString *latest_version =
554 [NSString stringWithContentsOfURL: [NSURL URLWithString:sLatestVersionURL]];
555
556 if (latest_version == Nil) latest_version = @"";
557 latest_version = [latest_version stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
558
559 if ([latest_version length] == 0) {
560 NSRunInformationalAlertPanel(
561 @"Unable to Determine",
562 @"Unable to determine the latest AntiRSI version number.",
563 @"Ok", nil, nil);
564 } else if ([latest_version compare:sVersion] == NSOrderedDescending) {
565 int r = NSRunInformationalAlertPanel(
566 @"New Version",
567 [NSString stringWithFormat:@"A new version (%@) of AntiRSI is available; would you like to go to the website now?", latest_version],
568 @"Goto Website", @"Cancel", nil);
569 if (r == NSOKButton) {
570 [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:sURL]];
571 }
572 } else {
573 NSRunInformationalAlertPanel(
574 @"No Update Available",
575 @"This is the latest version of AntiRSI.",
576 @"OK", nil, nil);
577 }
578}
579
580// stop work break and postpone by 10 minutes
581- (IBAction)postpone:(id)sender
582{
583 if (s_taking_work_break == state) {
584 micro_pause_t = 0;
585 micro_pause_taking_t = 0;
586 work_break_taking_t = 0;
587 work_break_taking_cached_t = 0;
588 work_break_t -= 10*60; // decrease with 10 minutes
589 if (work_break_t < 0) work_break_t = 0;
590 [self endBreak];
591 }
592}
593
594- (IBAction)breakNow:(id)sender
595{
596 [self doWorkBreak];
597}
598
599// validate menu items
600- (BOOL)validateMenuItem:(NSMenuItem *)anItem
601{
602 if ([[anItem title] isEqualToString:@"Take Break Now"] && state == s_normal) {
603 return YES;
604 }
605
606 if ([[anItem title] isEqualToString:@"Postpone Break"] && state == s_taking_work_break) {
607 return YES;
608 }
609
610 if ([[anItem title] isEqualToString:@"AntiRSI Help"]) {
611 return YES;
612 }
613
614 return NO;
615}
616
617// we are delegate of NSApplication, so we can restore the icon on quit.
618- (void)applicationWillTerminate:(NSNotification *)aNotification
619{
620 // make sure timer doesn't tick once more ...
621 draw_dock_image_q = NO;
622 [mtimer invalidate];
623 [mtimer autorelease];
624 mtimer = nil;
625 [dock_image release];
626 // stupid fix for icon beeing restored ... it is not my fault,
627 // the dock or NSImage or setApplicationIconImage seem to be caching or taking
628 // snapshot or something ... !
629 [NSApp setApplicationIconImage:original_dock_image];
630 [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
631 [NSApp setApplicationIconImage:original_dock_image];
632
633}
634
635@end
636
Note: See TracBrowser for help on using the repository browser.