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