Ruby Xocolatl

Ruby, Cocoa, Mac OS X and things of my not so exciting life.

Lucecita: RubyCocoa Example

UPDATE: Version 2.0 of Lucecita is out. Lucecita 2.0

There are some applications like Mouseposé, OmniDazzle or RemoteBuddy that allow you to have a rally nice light effect with your mouse. But as I have stated before, I am a poor student :) and since I am saving money to buy a new laptop(by the way, please support me with the ads and with donations!!) I have written a little app called “Lucecita”. For the impatient: Lucecita.app.

DarkWindow.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
require 'osx/cocoa'
include OSX

class DarkWindow < NSWindow

  def initWithContentRect_styleMask_backing_defer(contentRect, aStyle, bufferingType, flag)
    result = super_initWithContentRect_styleMask_backing_defer(NSScreen.mainScreen.frame,
                  NSBorderlessWindowMask,
                  bufferingType,
                  flag)
    if result
      result.setBackgroundColor(NSColor.clearColor)
      result.setOpaque(false)
      result.setIgnoresMouseEvents(true)
      result.setLevel(NSScreenSaverWindowLevel)
      result.setCollectionBehavior(NSWindowCollectionBehaviorCanJoinAllSpaces)
    end

    result
  end

end

LightView.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
require 'osx/cocoa'
include OSX

class LuzView <  OSX::NSView

  attr_accessor :center, :rect, :radius, :transparency, :enabled

  def initialize
    @radius = 50
    @transparency = 0.5
    @enabled = false
  end

  def initWithFrame(frame)
    self.setNeedsDisplay true
    return self
  end

  def drawRect(rect)
    if @enabled
    NSGraphicsContext.currentContext.setCompositingOperation NSCompositeSourceOut
    context = NSGraphicsContext.currentContext.graphicsPort
    @center = NSEvent.mouseLocation
    CGContextSetRGBFillColor(context, 0.0, 0.0, 0.0, @transparency)
    CGContextFillRect(context, rect)

    drawLight
    end
  end

  def drawLight
    context = NSGraphicsContext.currentContext.graphicsPort
    NSColor.colorWithCalibratedRed_green_blue_alpha(1.0,1.0,1.0,0.0).set

    CGContextSetGrayStrokeColor(context, 0, 1)
    CGContextSetLineWidth(context, 0)

    @rect = NSRect.new(@center.x - @radius, @center.y - @radius, @radius*2, @radius*2)
    CGContextAddEllipseInRect(context, @rect)
    CGContextDrawPath(context, KCGPathFill)
  end

end

LightController.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
require 'osx/cocoa'

class LuzController < OSX::NSObject

  ib_outlets :luz_view
  ib_outlets :menu, :size, :size_lbl, :alpha, :alpha_lbl, :enabled

  def awakeFromNib
    activateStatusMenu()
    @callback = lambda { |p,t,e,r|
      if (t == KCGEventOtherMouseDown &&
        CGEventGetFlags(e) & KCGEventFlagMaskControl == KCGEventFlagMaskControl)
        toggle(self)
        return
      end
      if @luz_view.enabled
        @luz_view.center = CGEventGetLocation(e)
        @luz_view.setNeedsDisplayInRect NSInsetRect(@luz_view.rect, -30, -30)
      end
      e
    }
    start_tapping()
  end

  ib_action :change_alpha do |sender|
    @luz_view.transparency = @alpha.floatValue
    @alpha_lbl.setStringValue "#{(@alpha.floatValue*100).to_i}%"
    @luz_view.setNeedsDisplay true
  end

  ib_action :change_size do |sender|
    @luz_view.radius = @size.floatValue
    @size_lbl.setStringValue "#{(@size.intValue)}px"
    @luz_view.setNeedsDisplay true
  end

  ib_action :toggle do |sender|
    @luz_view.enabled = !@luz_view.enabled
    @enabled.setState(@luz_view.enabled ? 1 : 0)
    @luz_view.setNeedsDisplay true
  end

  def activateStatusMenu()
      statusItem = NSStatusBar.systemStatusBar.statusItemWithLength(NSVariableStatusItemLength)
      statusItem.retain
      icon = NSImage.alloc.initWithContentsOfFile("#{NSBundle.mainBundle.resourcePath}/Lucecita.png")
      statusItem.setImage icon
      statusItem.setHighlightMode true
      statusItem.setMenu @menu
  end

  def start_tapping
    eventMask = 167772384 # Magic Number: LeftMouseDragged MouseMoved RightMouseDragged OtherMouseDragged OtherMouseDown
    eventTap = CGEventTapCreate(KCGSessionEventTap, KCGHeadInsertEventTap,
                                0, eventMask, @callback, nil)
    eventSrc = CFMachPortCreateRunLoopSource(nil, eventTap, 0)
    CFRelease(eventTap)
    CFRunLoopAddSource(CFRunLoopGetCurrent(),  eventSrc, KCFRunLoopCommonModes)
    CFRelease(eventSrc)
  end

  def applicationShouldTerminate
    CFRunLoopRemoveSource(CFRunLoopGetCurrent(), mEventSrc, KCFRunLoopCommonModes)
  end

end

And the application is here: Lucecita.app

You can activate it with the menu or with ctrl-Button3Click or ctrl-Button4Click if you have a three button mouse or a Mighty Mouse.

I am preparing a screencast to show yo how I coded it but that will be later, until then you can see inside the app package and check the ruby code.