source: rgbkbd/trunk/rgbkbd/core.py @ 71

Last change on this file since 71 was 71, checked in by retracile, 9 years ago

Initial import of source code

  • Property svn:executable set to *
File size: 7.4 KB
Line 
1#!/usr/bin/python
2import os
3import time
4
5from color import Colors
6from geometry import Keys
7
8
9class Keyboard(object):
10    """Object representing an attached keyboard; communicates with the
11    ckb-daemon process.
12    """
13
14    def __init__(self, num=None, device=None):
15        if device is not None:
16            self.num = int(device[-1])
17            self.cmdfile = '%s/cmd' % device
18        elif num is not None:
19            self.num = num
20            self.cmdfile = '/dev/input/ckb%s/cmd' % num
21
22    def _cmd(self, command):
23        # FIXME: handle the driver daemon shutting down and eliminating this pipe
24        open(self.cmdfile, 'w').write(command + '\n')
25
26    def clear(self, color=Colors.BLACK):
27        """Clears the colors of the keyboard to a solid color; black by
28        default.
29        """
30        self._cmd("rgb %s" % color)
31
32    def lights(self, on=True):
33        """Turns the keyboard lights on or off"""
34        if on:
35            self._cmd("rgb on")
36        else:
37            self._cmd("rgb off")
38
39    def unbind(self, keys="all"):
40        self._cmd("unbind %s" % keys)
41
42    def rebind(self, keys="all"):
43        self._cmd("rebind %s" % keys)
44
45    def set_keys(self, keys, color):
46        """Sets the color of a set of keys"""
47        self._cmd('rgb %s:%s' % (keys, color))
48
49    def set_many_keys(self, key_colors):
50        """Sets the colors of multiple set of keys"""
51        self._cmd('rgb %s' % ' '.join('%s:%s' % c for c in key_colors))
52
53    def alloc_notify(self):
54        """Allocate a notification pipe from ckb-daemon"""
55        n = 1
56        while os.path.exists('/dev/input/ckb%s/notify%s' % (self.num, n)):
57            n += 1
58        if n > 9: # Catch the driver limitation
59            raise Exception("Too many notification nodes in use")
60        notify_filename = '/dev/input/ckb%s/notify%s' % (self.num, n)
61        self._cmd('notifyon %s' % n)
62        self._cmd('@%s notify all:on' % n)
63        timeout = time.time() + 2
64        while not os.path.exists(notify_filename) and time.time() < timeout:
65            time.sleep(0.01)
66        if not os.path.exists(notify_filename):
67            raise Exception("Failed to create notification pipe")
68        return (n, notify_filename)
69
70    def free_notify(self, num):
71        """Release a notification pipe from ckb-daemon"""
72        self._cmd('notifyoff %s' % num)
73
74
75# Lighting stuff
76class StaticLighting(object):
77    """Keys have a set color"""
78
79    def __init__(self, keyboard, profile=None):
80        self.keyboard = keyboard
81        if profile is None:
82            profile = []
83        self.profile = profile
84
85    def start(self):
86        """Initializes key colors"""
87        if self.profile is not None:
88            self.keyboard.set_many_keys(self.profile)
89
90
91class Monochrome(StaticLighting):
92    """Static lighting where all keys are one color"""
93    def __init__(self, keyboard, color=Colors.WHITE):
94        super(Monochrome, self).__init__(keyboard, profile =[(Keys.ALL, color)])
95
96
97class ColorPattern(object):
98    """Object which returns a color given a time
99
100    Abstract base class; subclasses must implement color(time).
101    """
102
103    def __init__(self, colors, period=5):
104        self.colors = colors
105        self.period = period
106
107    def foreground(self):
108        """Utility function to get the foreground color"""
109        return self.colors[0]
110
111    def background(self):
112        """Utility function to get the background color, defaulting to black"""
113        if len(self.colors) > 1:
114            background = self.colors[1]
115        else:
116            background = Colors.BLACK
117        return background
118
119    def phase(self, t):
120        """Returns a float value in [0, 1] indicating where in the period the
121        given time is.
122        """
123        return (t % self.period) / self.period
124
125    def color(self, t):
126        """Given a time t, return a color"""
127        raise NotImplementedError
128
129
130class MotionPattern(object):
131    """Object which returns a time offset given an x,y position
132
133    Abstract base class; subclasses must implement phase_offset(x, y).
134    """
135
136    def __init__(self, period=5):
137        self.period = period
138
139    def t_offset(self, x, y):
140        """Turns a phase offset into a time offset"""
141        return self.phase_offset(x, y) * self.period
142
143    def phase_offset(self, x, y):
144        raise NotImplementedError
145
146
147class EventHandler(object):
148    """Object to receive keyboard events from the Manager object
149
150    By default does nothing with events.
151    """
152
153    tick_rate = 0.03 # seconds
154
155    def __init__(self, keyboard):
156        self.keyboard = keyboard
157
158    def tick(self):
159        """Override this method for periodic events"""
160        pass
161
162    def event(self, key, state):
163        """Override this method for key up/down events"""
164        pass
165
166    def chord_event(self, chord):
167        """Override this method for key-chord events
168
169        chord is a list of keys pressed in the chord, in order
170        single keys are sent as a single element list
171        """
172        pass
173
174
175# Keyboard Modes
176class KeyboardMode(object):
177    """A keyboard mode represents a set of behaviors for the keyboard,
178    including lighting schemes and reactions to key presses.
179    """
180
181    def __init__(self, manager, keyboard, static_lighting, animations=None):
182        self.manager = manager
183        self.keyboard = keyboard
184        self.static_lighting = static_lighting
185        if animations is None:
186            animations = []
187        self.animations = animations
188
189        self.lights_on = True
190
191        # Use fastest tick rate of the provided animations
192        tick_rate = None
193        for animation in animations:
194            if animation.tick_rate is not None:
195                if tick_rate is None:
196                    tick_rate = animation.tick_rate
197                else:
198                    tick_rate = min(tick_rate, animation.tick_rate)
199        if not tick_rate:
200            tick_rate = None
201        self.tick_rate = tick_rate
202        #print "Tick rate of animations is: %r" % tick_rate # DEBUG
203
204    def start(self, previous_mode=None):
205        """Initializes the lighting effects and resets the key bindings."""
206        self.keyboard._cmd("rebind all")
207        self.static_lighting.start()
208        for animation in reversed(self.animations):
209            animation.tick()
210
211    def tick(self):
212        """Passes tick events on to each animation in its stack of animations.
213        """
214        for animation in reversed(self.animations):
215            animation.tick()
216
217    def is_command_toggle(self, chord):
218        """Utility function to check if we're supposed to break out to command
219        mode.
220        """
221        value = sorted(chord) == ['light', 'lock']
222        #print "is_command_toggle: %s" % value # DEBUG
223        if value:
224            #print "active modes: %r" % self.manager.active_modes # DEBUG
225            pass
226        return value
227
228    def event(self, key, state):
229        """Passes key events on to each animation in its stack of animations.
230        """
231        for animation in reversed(self.animations):
232            animation.event(key, state)
233
234    def chord_event(self, chord):
235        """Passes chord events on to each animation in its stack of animations.
236
237        Also handles the light toggle and breaking out to command mode
238        """
239        #print "chord %s" % chord # DEBUG
240        for animation in reversed(self.animations):
241            animation.chord_event(chord)
242        if chord == ["light"]:
243            self.lights_on = not self.lights_on
244            self.keyboard.lights(self.lights_on)
245        elif self.is_command_toggle(chord):
246            self.manager.mode_return()
247
Note: See TracBrowser for help on using the repository browser.