1 # -*- coding: utf-8 -*-
8 draw_lock
= threading
.RLock()
21 def __init__(self
, action
, **kwargs
):
22 if action
in self
.action_types
:
25 raise Exception("Unknown action {}".format(action
))
27 self
.arguments
= kwargs
30 if 'music' in self
.arguments
:
31 return self
.arguments
['music'].loaded
36 print(getattr(self
, self
.action
+ "_print")(**self
.arguments
))
37 return getattr(self
, self
.action
)(**self
.arguments
)
39 def command(self
, command
= "", **kwargs
):
42 def pause(self
, music
= None, **kwargs
):
48 def play(self
, music
= None, fade_in
= 0, start_at
= 0,
49 restart_if_running
= False, volume
= 100, **kwargs
):
55 def stop(self
, music
= None, fade_out
= 0, **kwargs
):
61 def stop_all_actions(self
, **kwargs
):
64 def volume(self
, music
= None, value
= 100, **kwargs
):
67 def wait(self
, duration
= 0, **kwargs
):
70 def command_print(self
, command
= "", **kwargs
):
71 return "running command {}".format(command
)
73 def pause_print(self
, music
= None, **kwargs
):
75 return "pausing {}".format(music
.filename
)
77 return "pausing all musics"
79 def play_print(self
, music
= None, fade_in
= 0, start_at
= 0,
80 restart_if_running
= False, volume
= 100, **kwargs
):
83 message
+= music
.filename
88 message
+= " at {}s".format(start_at
)
91 message
+= " with {}s fade_in".format(fade_in
)
93 message
+= " at volume {}%".format(volume
)
95 if restart_if_running
:
96 message
+= " (restarting if already running)"
100 def stop_print(self
, music
= None, fade_out
= 0, **kwargs
):
101 if music
is not None:
103 return "stopping music {}".format(music
.filename
)
105 return "stopping music {} with {}s fadeout".format(music
.filename
, fade_out
)
108 return "stopping all musics"
110 return "stopping all musics with {}s fadeout".format(fade_out
)
112 def stop_all_actions_print(self
):
113 return "stopping all actions"
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
)
119 return "setting volume to {}%".format(value
)
121 def wait_print(self
, duration
, **kwargs
):
122 return "waiting {}s".format(duration
)
134 default_outer_color
= (120, 120, 120)
135 lighter_outer_color
= (200, 200, 200)
136 default_inner_color
= (255, 255, 255)
137 mapped_inner_color
= ( 0, 255, 0)
138 mapped_unready_inner_color
= (255, 165, 0)
141 def __init__(self
, mapping
, key_name
, key_sym
, top
, left
, width
= 48, height
= 48, disabled
= False):
142 self
.mapping
= mapping
143 self
.key_name
= key_name
144 self
.key_sym
= key_sym
146 if isinstance(top
, str):
147 self
.top
= self
.row_positions
[top
]
155 self
.bottom
= self
.top
+ self
.height
156 self
.right
= self
.left
+ self
.width
158 self
.rect
= (self
.left
, self
.top
, self
.right
, self
.bottom
)
159 self
.position
= (self
.left
, self
.top
)
162 self
.outer_color
= self
.lighter_outer_color
165 self
.outer_color
= self
.default_outer_color
168 self
.inner_color
= self
.default_inner_color
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
176 self
.inner_color
= self
.mapped_unready_inner_color
178 return RoundedRect((0, 0, self
.width
, self
.height
),
179 self
.outer_color
, self
.inner_color
, self
.linewidth
)
181 def collidepoint(self
, position
):
182 return self
.surface
.get_rect().collidepoint(
183 position
[0] - self
.position
[0],
184 position
[1] - self
.position
[1]
187 def draw(self
, background_surface
):
189 all_actions_ready
= self
.all_actions_ready()
191 self
.surface
= self
.square(all_actions_ready
).surface()
193 if getattr(sys
, 'frozen', False):
194 police
= font
.Font(sys
._MEIPASS
+ "/Ubuntu-Regular.ttf", 14)
196 police
= font
.Font("Ubuntu-Regular.ttf", 14)
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
)
203 return not all_actions_ready
205 def poll_redraw(self
, background
):
208 if self
.all_actions_ready():
209 self
.draw(background
)
213 def has_actions(self
):
214 return len(self
.actions
) > 0
216 def all_actions_ready(self
):
217 return all(action
.ready() for action
in self
.actions
)
219 def add_action(self
, action_name
, **arguments
):
220 self
.actions
.append(Action(action_name
, **arguments
))
222 def do_actions(self
):
223 print("running actions for {}".format(self
.key_sym
))
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
:
231 if self
in Key
.running
:
232 Key
.running
.remove(self
)
234 def list_actions(self
, surface
):
236 print("bouh", self
.key_sym
)
237 surface
.fill((255, 0, 0))
246 (K_ESCAPE
, 'ESC', 'first', 0, {}),
248 (K_F1
, 'F1', 'first', 100, {}),
249 (K_F2
, 'F2', 'first', 150, {}),
250 (K_F3
, 'F3', 'first', 200, {}),
251 (K_F4
, 'F4', 'first', 250, {}),
253 (K_F5
, 'F5', 'first', 325, {}),
254 (K_F6
, 'F6', 'first', 375, {}),
255 (K_F7
, 'F7', 'first', 425, {}),
256 (K_F8
, 'F8', 'first', 475, {}),
258 (K_F9
, 'F9', 'first', 550, {}),
259 (K_F10
, 'F10', 'first', 600, {}),
260 (K_F11
, 'F11', 'first', 650, {}),
261 (K_F12
, 'F12', 'first', 700, {}),
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, {}),
278 (K_BACKSPACE
, '<-', 'second', 650, { 'width': 98 }
),
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, {}),
295 (K_RETURN
, 'Enter', 'third', 692, { 'width': 56, 'height': 98 }
),
297 (K_CAPSLOCK
, 'CAPS', 'fourth', 0, { 'width': 88, 'disabled': True }
),
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, {}),
313 (K_LSHIFT
, 'LShift', 'fifth', 0, { 'width': 63, 'disabled': True }
),
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, {}),
327 (K_RSHIFT
, 'RShift', 'fifth', 615, { 'width': 133, 'disabled': True }
),
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 }
),
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, {}),
346 (K_UP
, 'up', 'fifth', 805, {}),
347 (K_DOWN
, 'down', 'sixth', 805, {}),
348 (K_LEFT
, 'left', 'sixth', 755, {}),
349 (K_RIGHT
, 'right', 'sixth', 855, {}),
352 def __init__(self
, screen
):
354 self
.background
= Surface(self
.SIZE
).convert()
355 self
.background
.fill((250, 250, 250))
357 for key
in self
.KEYS
:
358 self
.keys
[key
[0]] = Key(self
, *key
[0:4], **key
[4])
361 for key_name
in self
.keys
:
362 key
= self
.keys
[key_name
]
363 should_redraw_key
= key
.draw(self
.background
)
365 if should_redraw_key
:
366 threading
.Thread(target
= key
.poll_redraw
, args
= [self
.background
]).start()
371 self
.screen
.blit(self
.background
, (5, 5))
375 def find_by_key_num(self
, key_num
):
376 if key_num
in self
.keys
:
377 return self
.keys
[key_num
]
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
]
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
]
395 def __init__(self
, filename
, lock
):
396 self
.filename
= filename
402 threading
.Thread(target
= self
.load_sound
, args
= [lock
]).start()
404 def load_sound(self
, lock
):
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
))
414 self
.channel
= self
.sound
.play()
417 if self
.channel
is not None:
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
434 rectangle
= self
.filledRoundedRect(self
.rect
, self
.outer_color
, self
.radius
)
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
443 inner_rectangle
= self
.filledRoundedRect(inner_rect
, self
.inner_color
, self
.radius
)
445 rectangle
.blit(inner_rectangle
, (self
.linewidth
, self
.linewidth
))
449 def filledRoundedRect(self
, rect
, color
, radius
=0.4):
451 filledRoundedRect(rect,color,radius=0.4)
455 radius : 0 <= radius <= 1
462 rectangle
= Surface(rect
.size
,SRCALPHA
)
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)
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
)
476 rectangle
.fill((0,0,0),rect
.inflate(-radius
.w
,0))
477 rectangle
.fill((0,0,0),rect
.inflate(0,-radius
.h
))
479 rectangle
.fill(color
,special_flags
=BLEND_RGBA_MAX
)
480 rectangle
.fill((255,255,255,alpha
),special_flags
=BLEND_RGBA_MIN
)
485 def parse_config(mapping
):
487 stream
= open("config.yml", "r")
488 config
= yaml
.load(stream
)
491 aliases
= config
['aliases']
494 file_lock
= threading
.RLock()
496 for mapped_key
in config
['keys']:
497 key
= mapping
.find_by_unicode(mapped_key
)
501 for action
in config
['keys'][mapped_key
]:
502 action_name
= list(action
)[0]
504 if action
[action_name
] is None:
505 action
[action_name
] = []
507 if 'include' in action
[action_name
]:
508 included
= action
[action_name
]['include']
509 del(action
[action_name
]['include'])
511 if isinstance(included
, str):
512 action
[action_name
].update(aliases
[included
], **action
[action_name
])
514 for included_
in included
:
515 action
[action_name
].update(aliases
[included_
], **action
[action_name
])
517 for argument
in action
[action_name
]:
518 if argument
== 'file':
519 filename
= action
[action_name
]['file']
520 if filename
not in seen_files
:
521 seen_files
[filename
] = MusicFile(filename
, file_lock
)
523 action_args
['music'] = seen_files
[filename
]
526 action_args
[argument
] = action
[action_name
][argument
]
528 key
.add_action(action_name
, **action_args
)