# -*- coding: utf-8 -*-
from pygame import *
+import pydub
import sys
+import time
+import threading
+
+draw_lock = threading.RLock()
class Action:
action_types = [
'pause',
'play',
'stop',
+ 'stop_all_actions',
'volume',
'wait',
]
self.arguments = kwargs
- def run(self, callback):
- getattr(self, self.action)(callback, **self.arguments)
+ def ready(self):
+ if 'music' in self.arguments:
+ return self.arguments['music'].loaded
+ else:
+ return True
- def command(self, callback, command = "", **kwargs):
- pass
+ def run(self):
+ print(getattr(self, self.action + "_print")(**self.arguments))
+ return getattr(self, self.action)(**self.arguments)
- def pause(self, callback, music = None, **kwargs):
+ def command(self, command = "", **kwargs):
pass
- def play(self, callback,
- music = None,
- fade_in = 0,
- restart_if_running = False,
- volume = 100,
- **kwargs):
- pass
+ def pause(self, music = None, **kwargs):
+ if music is not None:
+ music.pause()
+ else:
+ mixer.pause()
- def stop(self, callback, music = None, fade_out = 0, **kwargs):
- print('stopping')
- return callback()
+ def play(self, music = None, fade_in = 0, start_at = 0,
+ restart_if_running = False, volume = 100, **kwargs):
+ if music is not None:
+ music.play()
+ else:
+ mixer.unpause()
- def volume(self, callback, music = None, value = 100, **kwargs):
- pass
+ def stop(self, music = None, fade_out = 0, **kwargs):
+ if music is not None:
+ music.stop()
+ else:
+ mixer.stop()
+
+ def stop_all_actions(self, **kwargs):
+ Key.running = []
- def wait(self, callback, time = 0, **kwargs):
+ def volume(self, music = None, value = 100, **kwargs):
pass
+ def wait(self, duration = 0, **kwargs):
+ time.sleep(duration)
+
+ def command_print(self, command = "", **kwargs):
+ return "running command {}".format(command)
+
+ def pause_print(self, music = None, **kwargs):
+ if music is not None:
+ return "pausing {}".format(music.filename)
+ else:
+ return "pausing all musics"
+
+ def play_print(self, music = None, fade_in = 0, start_at = 0,
+ restart_if_running = False, volume = 100, **kwargs):
+ message = "starting "
+ if music is not None:
+ message += music.filename
+ else:
+ message += "music"
+
+ if start_at != 0:
+ message += " at {}s".format(start_at)
+
+ if fade_in != 0:
+ message += " with {}s fade_in".format(fade_in)
+
+ message += " at volume {}%".format(volume)
+
+ if restart_if_running:
+ message += " (restarting if already running)"
+
+ return message
+
+ def stop_print(self, music = None, fade_out = 0, **kwargs):
+ if music is not None:
+ if fade_out == 0:
+ return "stopping music {}".format(music.filename)
+ else:
+ return "stopping music {} with {}s fadeout".format(music.filename, fade_out)
+ else:
+ if fade_out == 0:
+ return "stopping all musics"
+ else:
+ return "stopping all musics with {}s fadeout".format(fade_out)
+
+ def stop_all_actions_print(self):
+ return "stopping all actions"
+
+ def volume_print(self, music = None, value = 100, *kwargs):
+ if music is not None:
+ return "setting volume of {} to {}%".format(music.filename, value)
+ else:
+ return "setting volume to {}%".format(value)
+
+ def wait_print(self, duration, **kwargs):
+ return "waiting {}s".format(duration)
+
class Key:
row_positions = {
- 'first': 5,
- 'second': 55,
- 'third': 105,
- 'fourth': 155,
- 'fifth': 205,
- 'sixth': 255,
+ 'first': 0,
+ 'second': 50,
+ 'third': 100,
+ 'fourth': 150,
+ 'fifth': 200,
+ 'sixth': 250,
}
default_outer_color = (120, 120, 120)
lighter_outer_color = (200, 200, 200)
default_inner_color = (255, 255, 255)
mapped_inner_color = ( 0, 255, 0)
+ mapped_unready_inner_color = (255, 165, 0)
+ running = []
- def __init__(self, key_name, key_sym, top, left, width = 48, height = 48, disabled = False):
+ def __init__(self, mapping, key_name, key_sym, top, left, width = 48, height = 48, disabled = False):
+ self.mapping = mapping
self.key_name = key_name
self.key_sym = key_sym
self.inner_color = self.default_inner_color
self.actions = []
- def square(self):
- if self.has_action():
- self.inner_color = self.mapped_inner_color
+ def square(self, all_actions_ready):
+ if self.has_actions():
+ if all_actions_ready:
+ self.inner_color = self.mapped_inner_color
+ else:
+ self.inner_color = self.mapped_unready_inner_color
return RoundedRect((0, 0, self.width, self.height),
self.outer_color, self.inner_color, self.linewidth)
)
def draw(self, background_surface):
- self.surface = self.square().surface()
+ draw_lock.acquire()
+ all_actions_ready = self.all_actions_ready()
+
+ self.surface = self.square(all_actions_ready).surface()
if getattr(sys, 'frozen', False):
police = font.Font(sys._MEIPASS + "/Ubuntu-Regular.ttf", 14)
text = police.render(self.key_sym, True, (0,0,0))
self.surface.blit(text, (5,5))
background_surface.blit(self.surface, self.position)
+ draw_lock.release()
+
+ return not all_actions_ready
- def has_action(self):
+ def poll_redraw(self, background):
+ while True:
+ time.sleep(1)
+ if self.all_actions_ready():
+ self.draw(background)
+ self.mapping.blit()
+ break
+
+ def has_actions(self):
return len(self.actions) > 0
+ def all_actions_ready(self):
+ return all(action.ready() for action in self.actions)
+
def add_action(self, action_name, **arguments):
self.actions.append(Action(action_name, **arguments))
- def next_action(self):
- print("running next action")
-
def do_actions(self):
- self.current_action = 0
print("running actions for {}".format(self.key_sym))
- if len(self.actions) > 0:
- self.actions[0].run(self.next_action)
+ Key.running.append(self)
+ for action in self.actions:
+ #FIXME: si on stop_all_actions et qu'on relance, "self" est de
+ #nouveau dans Key.running
+ if self in Key.running:
+ action.run()
+
+ if self in Key.running:
+ Key.running.remove(self)
def list_actions(self, surface):
+ # FIXME: Todo
print("bouh", self.key_sym)
surface.fill((255, 0, 0))
- def find_by_key_num(key_num):
- if key_num in Mapping.KEYS:
- return Mapping.KEYS[key_num]
+
+class Mapping:
+ WIDTH = 903
+ HEIGHT = 298
+ SIZE = WIDTH, HEIGHT
+
+ KEYS = [
+ (K_ESCAPE, 'ESC', 'first', 0, {}),
+
+ (K_F1, 'F1', 'first', 100, {}),
+ (K_F2, 'F2', 'first', 150, {}),
+ (K_F3, 'F3', 'first', 200, {}),
+ (K_F4, 'F4', 'first', 250, {}),
+
+ (K_F5, 'F5', 'first', 325, {}),
+ (K_F6, 'F6', 'first', 375, {}),
+ (K_F7, 'F7', 'first', 425, {}),
+ (K_F8, 'F8', 'first', 475, {}),
+
+ (K_F9, 'F9', 'first', 550, {}),
+ (K_F10, 'F10', 'first', 600, {}),
+ (K_F11, 'F11', 'first', 650, {}),
+ (K_F12, 'F12', 'first', 700, {}),
+
+
+ (178, '²', 'second', 0, {}),
+ (K_AMPERSAND, '&', 'second', 50, {}),
+ (233, 'é', 'second', 100, {}),
+ (K_QUOTEDBL, '"', 'second', 150, {}),
+ (K_QUOTE, "'", 'second', 200, {}),
+ (K_LEFTPAREN, '(', 'second', 250, {}),
+ (K_MINUS, '-', 'second', 300, {}),
+ (232, 'è', 'second', 350, {}),
+ (K_UNDERSCORE, '_', 'second', 400, {}),
+ (231, 'ç', 'second', 450, {}),
+ (224, 'à', 'second', 500, {}),
+ (K_RIGHTPAREN, ')', 'second', 550, {}),
+ (K_EQUALS, '=', 'second', 600, {}),
+
+ (K_BACKSPACE, '<-', 'second', 650, { 'width': 98 }),
+
+
+ (K_TAB, 'tab', 'third', 0, { 'width' : 73 }),
+ (K_a, 'a', 'third', 75, {}),
+ (K_z, 'z', 'third', 125, {}),
+ (K_e, 'e', 'third', 175, {}),
+ (K_r, 'r', 'third', 225, {}),
+ (K_t, 't', 'third', 275, {}),
+ (K_y, 'y', 'third', 325, {}),
+ (K_u, 'u', 'third', 375, {}),
+ (K_i, 'i', 'third', 425, {}),
+ (K_o, 'o', 'third', 475, {}),
+ (K_p, 'p', 'third', 525, {}),
+ (K_CARET, '^', 'third', 575, {}),
+ (K_DOLLAR, '$', 'third', 625, {}),
+
+ (K_RETURN, 'Enter', 'third', 692, { 'width': 56, 'height': 98 }),
+
+ (K_CAPSLOCK, 'CAPS', 'fourth', 0, { 'width': 88, 'disabled': True }),
+
+ (K_q, 'q', 'fourth', 90, {}),
+ (K_s, 's', 'fourth', 140, {}),
+ (K_d, 'd', 'fourth', 190, {}),
+ (K_f, 'f', 'fourth', 240, {}),
+ (K_g, 'g', 'fourth', 290, {}),
+ (K_h, 'h', 'fourth', 340, {}),
+ (K_j, 'j', 'fourth', 390, {}),
+ (K_k, 'k', 'fourth', 440, {}),
+ (K_l, 'l', 'fourth', 490, {}),
+ (K_m, 'm', 'fourth', 540, {}),
+ (249, 'ù', 'fourth', 590, {}),
+ (K_ASTERISK, '*', 'fourth', 640, {}),
+
+
+ (K_LSHIFT, 'LShift', 'fifth', 0, { 'width': 63, 'disabled': True }),
+
+ (K_LESS, '<', 'fifth', 65, {}),
+ (K_w, 'w', 'fifth', 115, {}),
+ (K_x, 'x', 'fifth', 165, {}),
+ (K_c, 'c', 'fifth', 215, {}),
+ (K_v, 'v', 'fifth', 265, {}),
+ (K_b, 'b', 'fifth', 315, {}),
+ (K_n, 'n', 'fifth', 365, {}),
+ (K_COMMA, ',', 'fifth', 415, {}),
+ (K_SEMICOLON, ';', 'fifth', 465, {}),
+ (K_COLON, ':', 'fifth', 515, {}),
+ (K_EXCLAIM, '!', 'fifth', 565, {}),
+
+ (K_RSHIFT, 'RShift', 'fifth', 615, { 'width': 133, 'disabled': True }),
+
+ (K_LCTRL, 'LCtrl', 'sixth', 0, { 'width': 63, 'disabled': True }),
+ (K_LSUPER, 'LSuper', 'sixth', 115, { 'disabled': True }),
+ (K_LALT, 'LAlt', 'sixth', 165, { 'disabled': True }),
+ (K_SPACE, 'Espace', 'sixth', 215, { 'width': 248 }),
+ (K_MODE, 'AltGr', 'sixth', 465, { 'disabled': True }),
+ (314, 'Compose', 'sixth', 515, { 'disabled': True }),
+ (K_RCTRL, 'RCtrl', 'sixth', 565, { 'width': 63, 'disabled': True }),
+
+
+ (K_INSERT, 'ins', 'second', 755, {}),
+ (K_HOME, 'home', 'second', 805, {}),
+ (K_PAGEUP, 'pg_u', 'second', 855, {}),
+ (K_DELETE, 'del', 'third', 755, {}),
+ (K_END, 'end', 'third', 805, {}),
+ (K_PAGEDOWN, 'pg_d', 'third', 855, {}),
+
+
+ (K_UP, 'up', 'fifth', 805, {}),
+ (K_DOWN, 'down', 'sixth', 805, {}),
+ (K_LEFT, 'left', 'sixth', 755, {}),
+ (K_RIGHT, 'right', 'sixth', 855, {}),
+ ]
+
+ def __init__(self, screen):
+ self.screen = screen
+ self.background = Surface(self.SIZE).convert()
+ self.background.fill((250, 250, 250))
+ self.keys = {}
+ for key in self.KEYS:
+ self.keys[key[0]] = Key(self, *key[0:4], **key[4])
+
+ def draw(self):
+ for key_name in self.keys:
+ key = self.keys[key_name]
+ should_redraw_key = key.draw(self.background)
+
+ if should_redraw_key:
+ threading.Thread(target = key.poll_redraw, args = [self.background]).start()
+ self.blit()
+
+ def blit(self):
+ draw_lock.acquire()
+ self.screen.blit(self.background, (5, 5))
+ display.flip()
+ draw_lock.release()
+
+ def find_by_key_num(self, key_num):
+ if key_num in self.keys:
+ return self.keys[key_num]
return None
- def find_by_collidepoint(position):
- for key in Mapping.KEYS:
- if Mapping.KEYS[key].collidepoint(position):
- return Mapping.KEYS[key]
+ def find_by_collidepoint(self, position):
+ for key in self.keys:
+ if self.keys[key].collidepoint(position):
+ return self.keys[key]
return None
- def find_by_unicode(key_sym):
- for key in Mapping.KEYS:
- if Mapping.KEYS[key].key_sym == key_sym:
- return Mapping.KEYS[key]
+ def find_by_unicode(self, key_sym):
+ for key in self.keys:
+ if self.keys[key].key_sym == key_sym:
+ return self.keys[key]
return None
-class Mapping:
- KEYS = {
- K_ESCAPE: Key(K_ESCAPE, 'ESC', 'first', 0),
-
- K_F1: Key(K_F1, 'F1', 'first', 100),
- K_F2: Key(K_F2, 'F2', 'first', 150),
- K_F3: Key(K_F3, 'F3', 'first', 200),
- K_F4: Key(K_F4, 'F4', 'first', 250),
-
- K_F5: Key(K_F5, 'F5', 'first', 325),
- K_F6: Key(K_F6, 'F6', 'first', 375),
- K_F7: Key(K_F7, 'F7', 'first', 425),
- K_F8: Key(K_F8, 'F8', 'first', 475),
-
- K_F9: Key(K_F9, 'F9', 'first', 550),
- K_F10: Key(K_F10, 'F10', 'first', 600),
- K_F11: Key(K_F11, 'F11', 'first', 650),
- K_F12: Key(K_F12, 'F12', 'first', 700),
-
-
- 178: Key(178, '²', 'second', 0),
- K_AMPERSAND: Key(K_AMPERSAND, '&', 'second', 50),
- 233: Key(233, 'é', 'second', 100),
- K_QUOTEDBL: Key(K_QUOTEDBL, '"', 'second', 150),
- K_QUOTE: Key(K_QUOTE, "'", 'second', 200),
- K_LEFTPAREN: Key(K_LEFTPAREN, '(', 'second', 250),
- K_MINUS: Key(K_MINUS, '-', 'second', 300),
- 232: Key(232, 'è', 'second', 350),
- K_UNDERSCORE: Key(K_UNDERSCORE, '_', 'second', 400),
- 231: Key(231, 'ç', 'second', 450),
- 224: Key(224, 'à', 'second', 500),
- K_RIGHTPAREN: Key(K_RIGHTPAREN, ')', 'second', 550),
- K_EQUALS: Key(K_EQUALS, '=', 'second', 600),
-
- K_BACKSPACE: Key(K_BACKSPACE, '<-', 'second', 650, width = 98),
-
-
- K_TAB: Key(K_TAB, 'tab', 'third', 0, width = 73),
- K_a: Key(K_a, 'a', 'third', 75),
- K_z: Key(K_z, 'z', 'third', 125),
- K_e: Key(K_e, 'e', 'third', 175),
- K_r: Key(K_r, 'r', 'third', 225),
- K_t: Key(K_t, 't', 'third', 275),
- K_y: Key(K_y, 'y', 'third', 325),
- K_u: Key(K_u, 'u', 'third', 375),
- K_i: Key(K_i, 'i', 'third', 425),
- K_o: Key(K_o, 'o', 'third', 475),
- K_p: Key(K_p, 'p', 'third', 525),
- K_CARET: Key(K_CARET, '^', 'third', 575),
- K_DOLLAR: Key(K_DOLLAR, '$', 'third', 625),
-
- K_RETURN: Key(K_RETURN, 'Enter', 'third', 692, width = 56, height = 98),
-
- K_CAPSLOCK: Key(K_CAPSLOCK, 'CAPS', 'fourth', 0, width = 88, disabled = True),
-
- K_q: Key(K_q, 'q', 'fourth', 90),
- K_s: Key(K_s, 's', 'fourth', 140),
- K_d: Key(K_d, 'd', 'fourth', 190),
- K_f: Key(K_f, 'f', 'fourth', 240),
- K_g: Key(K_g, 'g', 'fourth', 290),
- K_h: Key(K_h, 'h', 'fourth', 340),
- K_j: Key(K_j, 'j', 'fourth', 390),
- K_k: Key(K_k, 'k', 'fourth', 440),
- K_l: Key(K_l, 'l', 'fourth', 490),
- K_m: Key(K_m, 'm', 'fourth', 540),
- 249: Key(249, 'ù', 'fourth', 590),
- K_ASTERISK: Key(K_ASTERISK, '*', 'fourth', 640),
-
-
- K_LSHIFT: Key(K_LSHIFT, 'LShift', 'fifth', 0, width = 63, disabled = True),
-
- K_LESS: Key(K_LESS, '<', 'fifth', 65),
- K_w: Key(K_w, 'w', 'fifth', 115),
- K_x: Key(K_x, 'x', 'fifth', 165),
- K_c: Key(K_c, 'c', 'fifth', 215),
- K_v: Key(K_v, 'v', 'fifth', 265),
- K_b: Key(K_b, 'b', 'fifth', 315),
- K_n: Key(K_n, 'n', 'fifth', 365),
- K_COMMA: Key(K_COMMA, ',', 'fifth', 415),
- K_SEMICOLON: Key(K_SEMICOLON, ';', 'fifth', 465),
- K_COLON: Key(K_COLON, ':', 'fifth', 515),
- K_EXCLAIM: Key(K_EXCLAIM, '!', 'fifth', 565),
-
- K_RSHIFT: Key(K_RSHIFT, 'RShift', 'fifth', 615, width = 133, disabled = True),
-
- K_LCTRL: Key(K_LCTRL, 'LCtrl', 'sixth', 0, width = 63, disabled = True),
- K_LSUPER: Key(K_LSUPER, 'LSuper', 'sixth', 115, disabled = True),
- K_LALT: Key(K_LALT, 'LAlt', 'sixth', 165, disabled = True),
- K_SPACE: Key(K_SPACE, 'Espace', 'sixth', 215, width = 248),
- K_MODE: Key(K_MODE, 'AltGr', 'sixth', 465, disabled = True),
- 314: Key(314, 'Compose', 'sixth', 515, disabled = True),
- K_RCTRL: Key(K_RCTRL, 'RCtrl', 'sixth', 565, width = 63, disabled = True),
-
-
- K_INSERT: Key(K_INSERT, 'ins', 'second', 755),
- K_HOME: Key(K_HOME, 'home', 'second', 805),
- K_PAGEUP: Key(K_PAGEUP, 'pg_u', 'second', 855),
- K_DELETE: Key(K_DELETE, 'del', 'third', 755),
- K_END: Key(K_END, 'end', 'third', 805),
- K_PAGEDOWN: Key(K_PAGEDOWN, 'pg_d', 'third', 855),
-
-
- K_UP: Key(K_UP, 'up', 'fifth', 805),
- K_DOWN: Key(K_DOWN, 'down', 'sixth', 805),
- K_LEFT: Key(K_LEFT, 'left', 'sixth', 755),
- K_RIGHT: Key(K_RIGHT, 'right', 'sixth', 855),
- }
+
class MusicFile:
- def __init__(self, filename):
+ def __init__(self, filename, lock):
self.filename = filename
+ self.channel = None
+ self.raw_data = None
+ self.sound = None
+
+ self.loaded = False
+ threading.Thread(target = self.load_sound, args = [lock]).start()
+
+ def load_sound(self, lock):
+ lock.acquire()
+ print("Loading {}".format(self.filename))
+ self.raw_data = pydub.AudioSegment.from_file(self.filename).raw_data
+ self.sound = mixer.Sound(self.raw_data)
+ print("Loaded {}".format(self.filename))
+ self.loaded = True
+ lock.release()
+
+ def play(self):
+ self.channel = self.sound.play()
+
+ def pause(self):
+ if self.channel is not None:
+ self.channel.pause()
+
+ def stop(self):
+ self.channel = None
+ self.sound.stop()
+
class RoundedRect:
def __init__(self, rect, outer_color, inner_color, linewidth = 2, radius = 0.4):
return rectangle
-def parse_config():
+def parse_config(mapping):
import yaml
stream = open("config.yml", "r")
config = yaml.load(stream)
aliases = config['aliases']
seen_files = {}
+ file_lock = threading.RLock()
+
for mapped_key in config['keys']:
- key = Key.find_by_unicode(mapped_key)
+ key = mapping.find_by_unicode(mapped_key)
if key is None:
continue
if action[action_name] is None:
action[action_name] = []
+ if 'include' in action[action_name]:
+ included = action[action_name]['include']
+ del(action[action_name]['include'])
+
+ if isinstance(included, str):
+ action[action_name].update(aliases[included], **action[action_name])
+ else:
+ for included_ in included:
+ action[action_name].update(aliases[included_], **action[action_name])
+
for argument in action[action_name]:
- if argument == 'include':
- included = action[action_name]['include']
- if isinstance(included, str):
- action_args.update(aliases[included])
- else:
- for included_ in included:
- action_args.update(aliases[included_])
- elif argument == 'file':
+ if argument == 'file':
filename = action[action_name]['file']
if filename not in seen_files:
- seen_files[filename] = MusicFile.new(filename)
+ seen_files[filename] = MusicFile(filename, file_lock)
action_args['music'] = seen_files[filename]