]>
Commit | Line | Data |
---|---|---|
1df30f07 | 1 | # -*- coding: utf-8 -*- |
189bf90c | 2 | from pygame import * |
e7f8dab4 | 3 | import pydub |
1b794736 | 4 | import sys |
e7f8dab4 IB |
5 | import time |
6 | import threading | |
7 | ||
8 | draw_lock = threading.RLock() | |
8f88a3e4 IB |
9 | |
10 | class Action: | |
11 | action_types = [ | |
d8ab67c7 | 12 | 'command', |
8f88a3e4 IB |
13 | 'pause', |
14 | 'play', | |
8f88a3e4 | 15 | 'stop', |
e7f8dab4 | 16 | 'stop_all_actions', |
8f88a3e4 IB |
17 | 'volume', |
18 | 'wait', | |
19 | ] | |
20 | ||
98fee7ff | 21 | def __init__(self, action, key, **kwargs): |
8f88a3e4 IB |
22 | if action in self.action_types: |
23 | self.action = action | |
24 | else: | |
25 | raise Exception("Unknown action {}".format(action)) | |
26 | ||
98fee7ff | 27 | self.key = key |
8f88a3e4 IB |
28 | self.arguments = kwargs |
29 | ||
e7f8dab4 IB |
30 | def ready(self): |
31 | if 'music' in self.arguments: | |
32 | return self.arguments['music'].loaded | |
33 | else: | |
34 | return True | |
8f88a3e4 | 35 | |
e7f8dab4 IB |
36 | def run(self): |
37 | print(getattr(self, self.action + "_print")(**self.arguments)) | |
38 | return getattr(self, self.action)(**self.arguments) | |
d8ab67c7 | 39 | |
e7f8dab4 | 40 | def command(self, command = "", **kwargs): |
d8ab67c7 IB |
41 | pass |
42 | ||
e7f8dab4 IB |
43 | def pause(self, music = None, **kwargs): |
44 | if music is not None: | |
45 | music.pause() | |
46 | else: | |
47 | mixer.pause() | |
d8ab67c7 | 48 | |
e7f8dab4 IB |
49 | def play(self, music = None, fade_in = 0, start_at = 0, |
50 | restart_if_running = False, volume = 100, **kwargs): | |
51 | if music is not None: | |
52 | music.play() | |
53 | else: | |
54 | mixer.unpause() | |
189bf90c | 55 | |
e7f8dab4 IB |
56 | def stop(self, music = None, fade_out = 0, **kwargs): |
57 | if music is not None: | |
58 | music.stop() | |
59 | else: | |
60 | mixer.stop() | |
61 | ||
62 | def stop_all_actions(self, **kwargs): | |
98fee7ff | 63 | self.key.mapping.stop_all_running() |
d8ab67c7 | 64 | |
e7f8dab4 | 65 | def volume(self, music = None, value = 100, **kwargs): |
d8ab67c7 IB |
66 | pass |
67 | ||
e7f8dab4 IB |
68 | def wait(self, duration = 0, **kwargs): |
69 | time.sleep(duration) | |
70 | ||
71 | def command_print(self, command = "", **kwargs): | |
72 | return "running command {}".format(command) | |
73 | ||
74 | def pause_print(self, music = None, **kwargs): | |
75 | if music is not None: | |
76 | return "pausing {}".format(music.filename) | |
77 | else: | |
78 | return "pausing all musics" | |
79 | ||
80 | def play_print(self, music = None, fade_in = 0, start_at = 0, | |
81 | restart_if_running = False, volume = 100, **kwargs): | |
82 | message = "starting " | |
83 | if music is not None: | |
84 | message += music.filename | |
85 | else: | |
86 | message += "music" | |
87 | ||
88 | if start_at != 0: | |
89 | message += " at {}s".format(start_at) | |
90 | ||
91 | if fade_in != 0: | |
92 | message += " with {}s fade_in".format(fade_in) | |
93 | ||
94 | message += " at volume {}%".format(volume) | |
95 | ||
96 | if restart_if_running: | |
97 | message += " (restarting if already running)" | |
98 | ||
99 | return message | |
100 | ||
101 | def stop_print(self, music = None, fade_out = 0, **kwargs): | |
102 | if music is not None: | |
103 | if fade_out == 0: | |
104 | return "stopping music {}".format(music.filename) | |
105 | else: | |
106 | return "stopping music {} with {}s fadeout".format(music.filename, fade_out) | |
107 | else: | |
108 | if fade_out == 0: | |
109 | return "stopping all musics" | |
110 | else: | |
111 | return "stopping all musics with {}s fadeout".format(fade_out) | |
112 | ||
113 | def stop_all_actions_print(self): | |
114 | return "stopping all actions" | |
115 | ||
116 | def volume_print(self, music = None, value = 100, *kwargs): | |
117 | if music is not None: | |
118 | return "setting volume of {} to {}%".format(music.filename, value) | |
119 | else: | |
120 | return "setting volume to {}%".format(value) | |
121 | ||
122 | def wait_print(self, duration, **kwargs): | |
123 | return "waiting {}s".format(duration) | |
124 | ||
189bf90c IB |
125 | class Key: |
126 | row_positions = { | |
e7f8dab4 IB |
127 | 'first': 0, |
128 | 'second': 50, | |
129 | 'third': 100, | |
130 | 'fourth': 150, | |
131 | 'fifth': 200, | |
132 | 'sixth': 250, | |
189bf90c IB |
133 | } |
134 | ||
135 | default_outer_color = (120, 120, 120) | |
136 | lighter_outer_color = (200, 200, 200) | |
137 | default_inner_color = (255, 255, 255) | |
8f88a3e4 | 138 | mapped_inner_color = ( 0, 255, 0) |
e7f8dab4 | 139 | mapped_unready_inner_color = (255, 165, 0) |
189bf90c | 140 | |
e7f8dab4 IB |
141 | def __init__(self, mapping, key_name, key_sym, top, left, width = 48, height = 48, disabled = False): |
142 | self.mapping = mapping | |
189bf90c IB |
143 | self.key_name = key_name |
144 | self.key_sym = key_sym | |
145 | ||
146 | if isinstance(top, str): | |
147 | self.top = self.row_positions[top] | |
148 | else: | |
149 | self.top = top | |
150 | ||
151 | self.left = left | |
152 | self.width = width | |
153 | self.height = height | |
154 | ||
155 | self.bottom = self.top + self.height | |
156 | self.right = self.left + self.width | |
157 | ||
158 | self.rect = (self.left, self.top, self.right, self.bottom) | |
159 | self.position = (self.left, self.top) | |
160 | ||
161 | if disabled: | |
162 | self.outer_color = self.lighter_outer_color | |
163 | self.linewidth = 1 | |
164 | else: | |
165 | self.outer_color = self.default_outer_color | |
166 | self.linewidth = 3 | |
167 | ||
168 | self.inner_color = self.default_inner_color | |
8f88a3e4 | 169 | self.actions = [] |
189bf90c | 170 | |
e7f8dab4 IB |
171 | def square(self, all_actions_ready): |
172 | if self.has_actions(): | |
173 | if all_actions_ready: | |
174 | self.inner_color = self.mapped_inner_color | |
175 | else: | |
176 | self.inner_color = self.mapped_unready_inner_color | |
8f88a3e4 | 177 | |
189bf90c IB |
178 | return RoundedRect((0, 0, self.width, self.height), |
179 | self.outer_color, self.inner_color, self.linewidth) | |
180 | ||
181 | def collidepoint(self, position): | |
182 | return self.surface.get_rect().collidepoint( | |
183 | position[0] - self.position[0], | |
184 | position[1] - self.position[1] | |
185 | ) | |
186 | ||
187 | def draw(self, background_surface): | |
e7f8dab4 IB |
188 | draw_lock.acquire() |
189 | all_actions_ready = self.all_actions_ready() | |
190 | ||
191 | self.surface = self.square(all_actions_ready).surface() | |
189bf90c | 192 | |
1b794736 IB |
193 | if getattr(sys, 'frozen', False): |
194 | police = font.Font(sys._MEIPASS + "/Ubuntu-Regular.ttf", 14) | |
195 | else: | |
196 | police = font.Font("Ubuntu-Regular.ttf", 14) | |
197 | ||
189bf90c IB |
198 | text = police.render(self.key_sym, True, (0,0,0)) |
199 | self.surface.blit(text, (5,5)) | |
200 | background_surface.blit(self.surface, self.position) | |
e7f8dab4 IB |
201 | draw_lock.release() |
202 | ||
203 | return not all_actions_ready | |
189bf90c | 204 | |
e7f8dab4 IB |
205 | def poll_redraw(self, background): |
206 | while True: | |
207 | time.sleep(1) | |
208 | if self.all_actions_ready(): | |
209 | self.draw(background) | |
210 | self.mapping.blit() | |
211 | break | |
212 | ||
213 | def has_actions(self): | |
8f88a3e4 IB |
214 | return len(self.actions) > 0 |
215 | ||
e7f8dab4 IB |
216 | def all_actions_ready(self): |
217 | return all(action.ready() for action in self.actions) | |
218 | ||
8f88a3e4 | 219 | def add_action(self, action_name, **arguments): |
98fee7ff | 220 | self.actions.append(Action(action_name, self, **arguments)) |
8f88a3e4 | 221 | |
189bf90c | 222 | def do_actions(self): |
8f88a3e4 | 223 | print("running actions for {}".format(self.key_sym)) |
98fee7ff IB |
224 | start_time = time.time() |
225 | self.mapping.start_running(self, start_time) | |
e7f8dab4 | 226 | for action in self.actions: |
98fee7ff | 227 | if self.mapping.keep_running(self, start_time): |
e7f8dab4 IB |
228 | action.run() |
229 | ||
98fee7ff | 230 | self.mapping.finished_running(self, start_time) |
8f88a3e4 | 231 | |
d8ab67c7 | 232 | def list_actions(self, surface): |
e7f8dab4 | 233 | # FIXME: Todo |
d8ab67c7 IB |
234 | print("bouh", self.key_sym) |
235 | surface.fill((255, 0, 0)) | |
236 | ||
e7f8dab4 IB |
237 | |
238 | class Mapping: | |
239 | WIDTH = 903 | |
240 | HEIGHT = 298 | |
241 | SIZE = WIDTH, HEIGHT | |
242 | ||
243 | KEYS = [ | |
244 | (K_ESCAPE, 'ESC', 'first', 0, {}), | |
245 | ||
246 | (K_F1, 'F1', 'first', 100, {}), | |
247 | (K_F2, 'F2', 'first', 150, {}), | |
248 | (K_F3, 'F3', 'first', 200, {}), | |
249 | (K_F4, 'F4', 'first', 250, {}), | |
250 | ||
251 | (K_F5, 'F5', 'first', 325, {}), | |
252 | (K_F6, 'F6', 'first', 375, {}), | |
253 | (K_F7, 'F7', 'first', 425, {}), | |
254 | (K_F8, 'F8', 'first', 475, {}), | |
255 | ||
256 | (K_F9, 'F9', 'first', 550, {}), | |
257 | (K_F10, 'F10', 'first', 600, {}), | |
258 | (K_F11, 'F11', 'first', 650, {}), | |
259 | (K_F12, 'F12', 'first', 700, {}), | |
260 | ||
261 | ||
262 | (178, '²', 'second', 0, {}), | |
263 | (K_AMPERSAND, '&', 'second', 50, {}), | |
264 | (233, 'é', 'second', 100, {}), | |
265 | (K_QUOTEDBL, '"', 'second', 150, {}), | |
266 | (K_QUOTE, "'", 'second', 200, {}), | |
267 | (K_LEFTPAREN, '(', 'second', 250, {}), | |
268 | (K_MINUS, '-', 'second', 300, {}), | |
269 | (232, 'è', 'second', 350, {}), | |
270 | (K_UNDERSCORE, '_', 'second', 400, {}), | |
271 | (231, 'ç', 'second', 450, {}), | |
272 | (224, 'à', 'second', 500, {}), | |
273 | (K_RIGHTPAREN, ')', 'second', 550, {}), | |
274 | (K_EQUALS, '=', 'second', 600, {}), | |
275 | ||
276 | (K_BACKSPACE, '<-', 'second', 650, { 'width': 98 }), | |
277 | ||
278 | ||
279 | (K_TAB, 'tab', 'third', 0, { 'width' : 73 }), | |
280 | (K_a, 'a', 'third', 75, {}), | |
281 | (K_z, 'z', 'third', 125, {}), | |
282 | (K_e, 'e', 'third', 175, {}), | |
283 | (K_r, 'r', 'third', 225, {}), | |
284 | (K_t, 't', 'third', 275, {}), | |
285 | (K_y, 'y', 'third', 325, {}), | |
286 | (K_u, 'u', 'third', 375, {}), | |
287 | (K_i, 'i', 'third', 425, {}), | |
288 | (K_o, 'o', 'third', 475, {}), | |
289 | (K_p, 'p', 'third', 525, {}), | |
290 | (K_CARET, '^', 'third', 575, {}), | |
291 | (K_DOLLAR, '$', 'third', 625, {}), | |
292 | ||
293 | (K_RETURN, 'Enter', 'third', 692, { 'width': 56, 'height': 98 }), | |
294 | ||
295 | (K_CAPSLOCK, 'CAPS', 'fourth', 0, { 'width': 88, 'disabled': True }), | |
296 | ||
297 | (K_q, 'q', 'fourth', 90, {}), | |
298 | (K_s, 's', 'fourth', 140, {}), | |
299 | (K_d, 'd', 'fourth', 190, {}), | |
300 | (K_f, 'f', 'fourth', 240, {}), | |
301 | (K_g, 'g', 'fourth', 290, {}), | |
302 | (K_h, 'h', 'fourth', 340, {}), | |
303 | (K_j, 'j', 'fourth', 390, {}), | |
304 | (K_k, 'k', 'fourth', 440, {}), | |
305 | (K_l, 'l', 'fourth', 490, {}), | |
306 | (K_m, 'm', 'fourth', 540, {}), | |
307 | (249, 'ù', 'fourth', 590, {}), | |
308 | (K_ASTERISK, '*', 'fourth', 640, {}), | |
309 | ||
310 | ||
311 | (K_LSHIFT, 'LShift', 'fifth', 0, { 'width': 63, 'disabled': True }), | |
312 | ||
313 | (K_LESS, '<', 'fifth', 65, {}), | |
314 | (K_w, 'w', 'fifth', 115, {}), | |
315 | (K_x, 'x', 'fifth', 165, {}), | |
316 | (K_c, 'c', 'fifth', 215, {}), | |
317 | (K_v, 'v', 'fifth', 265, {}), | |
318 | (K_b, 'b', 'fifth', 315, {}), | |
319 | (K_n, 'n', 'fifth', 365, {}), | |
320 | (K_COMMA, ',', 'fifth', 415, {}), | |
321 | (K_SEMICOLON, ';', 'fifth', 465, {}), | |
322 | (K_COLON, ':', 'fifth', 515, {}), | |
323 | (K_EXCLAIM, '!', 'fifth', 565, {}), | |
324 | ||
325 | (K_RSHIFT, 'RShift', 'fifth', 615, { 'width': 133, 'disabled': True }), | |
326 | ||
327 | (K_LCTRL, 'LCtrl', 'sixth', 0, { 'width': 63, 'disabled': True }), | |
328 | (K_LSUPER, 'LSuper', 'sixth', 115, { 'disabled': True }), | |
329 | (K_LALT, 'LAlt', 'sixth', 165, { 'disabled': True }), | |
330 | (K_SPACE, 'Espace', 'sixth', 215, { 'width': 248 }), | |
331 | (K_MODE, 'AltGr', 'sixth', 465, { 'disabled': True }), | |
332 | (314, 'Compose', 'sixth', 515, { 'disabled': True }), | |
333 | (K_RCTRL, 'RCtrl', 'sixth', 565, { 'width': 63, 'disabled': True }), | |
334 | ||
335 | ||
336 | (K_INSERT, 'ins', 'second', 755, {}), | |
337 | (K_HOME, 'home', 'second', 805, {}), | |
338 | (K_PAGEUP, 'pg_u', 'second', 855, {}), | |
339 | (K_DELETE, 'del', 'third', 755, {}), | |
340 | (K_END, 'end', 'third', 805, {}), | |
341 | (K_PAGEDOWN, 'pg_d', 'third', 855, {}), | |
342 | ||
343 | ||
344 | (K_UP, 'up', 'fifth', 805, {}), | |
345 | (K_DOWN, 'down', 'sixth', 805, {}), | |
346 | (K_LEFT, 'left', 'sixth', 755, {}), | |
347 | (K_RIGHT, 'right', 'sixth', 855, {}), | |
348 | ] | |
349 | ||
350 | def __init__(self, screen): | |
351 | self.screen = screen | |
352 | self.background = Surface(self.SIZE).convert() | |
353 | self.background.fill((250, 250, 250)) | |
354 | self.keys = {} | |
98fee7ff | 355 | self.running = [] |
e7f8dab4 IB |
356 | for key in self.KEYS: |
357 | self.keys[key[0]] = Key(self, *key[0:4], **key[4]) | |
358 | ||
359 | def draw(self): | |
360 | for key_name in self.keys: | |
361 | key = self.keys[key_name] | |
362 | should_redraw_key = key.draw(self.background) | |
363 | ||
364 | if should_redraw_key: | |
365 | threading.Thread(target = key.poll_redraw, args = [self.background]).start() | |
366 | self.blit() | |
367 | ||
368 | def blit(self): | |
369 | draw_lock.acquire() | |
370 | self.screen.blit(self.background, (5, 5)) | |
371 | display.flip() | |
372 | draw_lock.release() | |
373 | ||
374 | def find_by_key_num(self, key_num): | |
375 | if key_num in self.keys: | |
376 | return self.keys[key_num] | |
d8ab67c7 IB |
377 | return None |
378 | ||
e7f8dab4 IB |
379 | def find_by_collidepoint(self, position): |
380 | for key in self.keys: | |
381 | if self.keys[key].collidepoint(position): | |
382 | return self.keys[key] | |
d8ab67c7 IB |
383 | return None |
384 | ||
e7f8dab4 IB |
385 | def find_by_unicode(self, key_sym): |
386 | for key in self.keys: | |
387 | if self.keys[key].key_sym == key_sym: | |
388 | return self.keys[key] | |
8f88a3e4 IB |
389 | return None |
390 | ||
98fee7ff IB |
391 | def stop_all_running(self): |
392 | self.running = [] | |
393 | ||
394 | def start_running(self, key, start_time): | |
395 | self.running.append((key, start_time)) | |
396 | ||
397 | def keep_running(self, key, start_time): | |
398 | return (key, start_time) in self.running | |
399 | ||
400 | def finished_running(self, key, start_time): | |
401 | if (key, start_time) in self.running: | |
402 | self.running.remove((key, start_time)) | |
e7f8dab4 | 403 | |
8f88a3e4 IB |
404 | |
405 | class MusicFile: | |
e7f8dab4 | 406 | def __init__(self, filename, lock): |
8f88a3e4 | 407 | self.filename = filename |
e7f8dab4 IB |
408 | self.channel = None |
409 | self.raw_data = None | |
410 | self.sound = None | |
411 | ||
412 | self.loaded = False | |
413 | threading.Thread(target = self.load_sound, args = [lock]).start() | |
414 | ||
415 | def load_sound(self, lock): | |
416 | lock.acquire() | |
417 | print("Loading {}".format(self.filename)) | |
418 | self.raw_data = pydub.AudioSegment.from_file(self.filename).raw_data | |
419 | self.sound = mixer.Sound(self.raw_data) | |
420 | print("Loaded {}".format(self.filename)) | |
421 | self.loaded = True | |
422 | lock.release() | |
423 | ||
424 | def play(self): | |
425 | self.channel = self.sound.play() | |
426 | ||
427 | def pause(self): | |
428 | if self.channel is not None: | |
429 | self.channel.pause() | |
430 | ||
431 | def stop(self): | |
432 | self.channel = None | |
433 | self.sound.stop() | |
434 | ||
189bf90c IB |
435 | |
436 | class RoundedRect: | |
437 | def __init__(self, rect, outer_color, inner_color, linewidth = 2, radius = 0.4): | |
438 | self.rect = Rect(rect) | |
439 | self.outer_color = Color(*outer_color) | |
440 | self.inner_color = Color(*inner_color) | |
441 | self.linewidth = linewidth | |
442 | self.radius = radius | |
443 | ||
444 | def surface(self): | |
1df30f07 | 445 | rectangle = self.filledRoundedRect(self.rect, self.outer_color, self.radius) |
189bf90c IB |
446 | |
447 | inner_rect = Rect(( | |
448 | self.rect.left + 2 * self.linewidth, | |
449 | self.rect.top + 2 * self.linewidth, | |
450 | self.rect.right - 2 * self.linewidth, | |
451 | self.rect.bottom - 2 * self.linewidth | |
452 | )) | |
453 | ||
454 | inner_rectangle = self.filledRoundedRect(inner_rect, self.inner_color, self.radius) | |
455 | ||
456 | rectangle.blit(inner_rectangle, (self.linewidth, self.linewidth)) | |
457 | ||
458 | return rectangle | |
459 | ||
460 | def filledRoundedRect(self, rect, color, radius=0.4): | |
461 | """ | |
462 | filledRoundedRect(rect,color,radius=0.4) | |
463 | ||
464 | rect : rectangle | |
465 | color : rgb or rgba | |
466 | radius : 0 <= radius <= 1 | |
467 | """ | |
468 | ||
469 | alpha = color.a | |
470 | color.a = 0 | |
471 | pos = rect.topleft | |
472 | rect.topleft = 0,0 | |
473 | rectangle = Surface(rect.size,SRCALPHA) | |
474 | ||
475 | circle = Surface([min(rect.size)*3]*2,SRCALPHA) | |
476 | draw.ellipse(circle,(0,0,0),circle.get_rect(),0) | |
477 | circle = transform.smoothscale(circle,[int(min(rect.size)*radius)]*2) | |
478 | ||
479 | radius = rectangle.blit(circle,(0,0)) | |
480 | radius.bottomright = rect.bottomright | |
481 | rectangle.blit(circle,radius) | |
482 | radius.topright = rect.topright | |
483 | rectangle.blit(circle,radius) | |
484 | radius.bottomleft = rect.bottomleft | |
485 | rectangle.blit(circle,radius) | |
486 | ||
487 | rectangle.fill((0,0,0),rect.inflate(-radius.w,0)) | |
488 | rectangle.fill((0,0,0),rect.inflate(0,-radius.h)) | |
489 | ||
490 | rectangle.fill(color,special_flags=BLEND_RGBA_MAX) | |
491 | rectangle.fill((255,255,255,alpha),special_flags=BLEND_RGBA_MIN) | |
492 | ||
493 | return rectangle | |
8f88a3e4 IB |
494 | |
495 | ||
e7f8dab4 | 496 | def parse_config(mapping): |
8f88a3e4 IB |
497 | import yaml |
498 | stream = open("config.yml", "r") | |
499 | config = yaml.load(stream) | |
500 | stream.close() | |
501 | ||
502 | aliases = config['aliases'] | |
503 | seen_files = {} | |
504 | ||
e7f8dab4 IB |
505 | file_lock = threading.RLock() |
506 | ||
8f88a3e4 | 507 | for mapped_key in config['keys']: |
e7f8dab4 | 508 | key = mapping.find_by_unicode(mapped_key) |
8f88a3e4 IB |
509 | if key is None: |
510 | continue | |
511 | ||
512 | for action in config['keys'][mapped_key]: | |
513 | action_name = list(action)[0] | |
514 | action_args = {} | |
515 | if action[action_name] is None: | |
516 | action[action_name] = [] | |
517 | ||
e7f8dab4 IB |
518 | if 'include' in action[action_name]: |
519 | included = action[action_name]['include'] | |
520 | del(action[action_name]['include']) | |
521 | ||
522 | if isinstance(included, str): | |
523 | action[action_name].update(aliases[included], **action[action_name]) | |
524 | else: | |
525 | for included_ in included: | |
526 | action[action_name].update(aliases[included_], **action[action_name]) | |
527 | ||
8f88a3e4 | 528 | for argument in action[action_name]: |
e7f8dab4 | 529 | if argument == 'file': |
8f88a3e4 IB |
530 | filename = action[action_name]['file'] |
531 | if filename not in seen_files: | |
e7f8dab4 | 532 | seen_files[filename] = MusicFile(filename, file_lock) |
8f88a3e4 | 533 | |
d8ab67c7 | 534 | action_args['music'] = seen_files[filename] |
8f88a3e4 IB |
535 | |
536 | else: | |
537 | action_args[argument] = action[action_name][argument] | |
538 | ||
539 | key.add_action(action_name, **action_args) |