1 # -*- coding: utf-8 -*-
8 draw_lock
= threading
.RLock()
21 def __init__(self
, action
, key
, **kwargs
):
22 if action
in self
.action_types
:
25 raise Exception("Unknown action {}".format(action
))
28 self
.arguments
= kwargs
31 if 'music' in self
.arguments
:
32 return self
.arguments
['music'].loaded
37 print(getattr(self
, self
.action
+ "_print")(**self
.arguments
))
38 return getattr(self
, self
.action
)(**self
.arguments
)
40 def command(self
, command
= "", **kwargs
):
43 def pause(self
, music
= None, **kwargs
):
49 def play(self
, music
= None, fade_in
= 0, start_at
= 0,
50 restart_if_running
= False, volume
= 100, **kwargs
):
56 def stop(self
, music
= None, fade_out
= 0, **kwargs
):
62 def stop_all_actions(self
, **kwargs
):
63 self
.key
.mapping
.stop_all_running()
65 def volume(self
, music
= None, value
= 100, **kwargs
):
68 def wait(self
, duration
= 0, **kwargs
):
71 def command_print(self
, command
= "", **kwargs
):
72 return "running command {}".format(command
)
74 def pause_print(self
, music
= None, **kwargs
):
76 return "pausing {}".format(music
.filename
)
78 return "pausing all musics"
80 def play_print(self
, music
= None, fade_in
= 0, start_at
= 0,
81 restart_if_running
= False, volume
= 100, **kwargs
):
84 message
+= music
.filename
89 message
+= " at {}s".format(start_at
)
92 message
+= " with {}s fade_in".format(fade_in
)
94 message
+= " at volume {}%".format(volume
)
96 if restart_if_running
:
97 message
+= " (restarting if already running)"
101 def stop_print(self
, music
= None, fade_out
= 0, **kwargs
):
102 if music
is not None:
104 return "stopping music {}".format(music
.filename
)
106 return "stopping music {} with {}s fadeout".format(music
.filename
, fade_out
)
109 return "stopping all musics"
111 return "stopping all musics with {}s fadeout".format(fade_out
)
113 def stop_all_actions_print(self
):
114 return "stopping all actions"
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
)
120 return "setting volume to {}%".format(value
)
122 def wait_print(self
, duration
, **kwargs
):
123 return "waiting {}s".format(duration
)
135 default_outer_color
= (120, 120, 120)
136 lighter_outer_color
= (200, 200, 200)
137 default_inner_color
= (255, 255, 255)
138 mapped_inner_color
= ( 0, 255, 0)
139 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
, self
, **arguments
))
222 def do_actions(self
):
223 print("running actions for {}".format(self
.key_sym
))
224 start_time
= time
.time()
225 self
.mapping
.start_running(self
, start_time
)
226 for action
in self
.actions
:
227 if self
.mapping
.keep_running(self
, start_time
):
230 self
.mapping
.finished_running(self
, start_time
)
232 def list_actions(self
, surface
):
234 print("bouh", self
.key_sym
)
235 surface
.fill((255, 0, 0))
244 (K_ESCAPE
, 'ESC', 'first', 0, {}),
246 (K_F1
, 'F1', 'first', 100, {}),
247 (K_F2
, 'F2', 'first', 150, {}),
248 (K_F3
, 'F3', 'first', 200, {}),
249 (K_F4
, 'F4', 'first', 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, {}),
256 (K_F9
, 'F9', 'first', 550, {}),
257 (K_F10
, 'F10', 'first', 600, {}),
258 (K_F11
, 'F11', 'first', 650, {}),
259 (K_F12
, 'F12', 'first', 700, {}),
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, {}),
276 (K_BACKSPACE
, '<-', 'second', 650, { 'width': 98 }
),
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, {}),
293 (K_RETURN
, 'Enter', 'third', 692, { 'width': 56, 'height': 98 }
),
295 (K_CAPSLOCK
, 'CAPS', 'fourth', 0, { 'width': 88, 'disabled': True }
),
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, {}),
311 (K_LSHIFT
, 'LShift', 'fifth', 0, { 'width': 63, 'disabled': True }
),
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, {}),
325 (K_RSHIFT
, 'RShift', 'fifth', 615, { 'width': 133, 'disabled': True }
),
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 }
),
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, {}),
344 (K_UP
, 'up', 'fifth', 805, {}),
345 (K_DOWN
, 'down', 'sixth', 805, {}),
346 (K_LEFT
, 'left', 'sixth', 755, {}),
347 (K_RIGHT
, 'right', 'sixth', 855, {}),
350 def __init__(self
, screen
):
352 self
.background
= Surface(self
.SIZE
).convert()
353 self
.background
.fill((250, 250, 250))
356 for key
in self
.KEYS
:
357 self
.keys
[key
[0]] = Key(self
, *key
[0:4], **key
[4])
360 for key_name
in self
.keys
:
361 key
= self
.keys
[key_name
]
362 should_redraw_key
= key
.draw(self
.background
)
364 if should_redraw_key
:
365 threading
.Thread(target
= key
.poll_redraw
, args
= [self
.background
]).start()
370 self
.screen
.blit(self
.background
, (5, 5))
374 def find_by_key_num(self
, key_num
):
375 if key_num
in self
.keys
:
376 return self
.keys
[key_num
]
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
]
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
]
391 def stop_all_running(self
):
394 def start_running(self
, key
, start_time
):
395 self
.running
.append((key
, start_time
))
397 def keep_running(self
, key
, start_time
):
398 return (key
, start_time
) in self
.running
400 def finished_running(self
, key
, start_time
):
401 if (key
, start_time
) in self
.running
:
402 self
.running
.remove((key
, start_time
))
406 def __init__(self
, filename
, lock
):
407 self
.filename
= filename
413 threading
.Thread(target
= self
.load_sound
, args
= [lock
]).start()
415 def load_sound(self
, lock
):
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
))
425 self
.channel
= self
.sound
.play()
428 if self
.channel
is not None:
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
445 rectangle
= self
.filledRoundedRect(self
.rect
, self
.outer_color
, self
.radius
)
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
454 inner_rectangle
= self
.filledRoundedRect(inner_rect
, self
.inner_color
, self
.radius
)
456 rectangle
.blit(inner_rectangle
, (self
.linewidth
, self
.linewidth
))
460 def filledRoundedRect(self
, rect
, color
, radius
=0.4):
462 filledRoundedRect(rect,color,radius=0.4)
466 radius : 0 <= radius <= 1
473 rectangle
= Surface(rect
.size
,SRCALPHA
)
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)
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
)
487 rectangle
.fill((0,0,0),rect
.inflate(-radius
.w
,0))
488 rectangle
.fill((0,0,0),rect
.inflate(0,-radius
.h
))
490 rectangle
.fill(color
,special_flags
=BLEND_RGBA_MAX
)
491 rectangle
.fill((255,255,255,alpha
),special_flags
=BLEND_RGBA_MIN
)
496 def parse_config(mapping
):
498 stream
= open("config.yml", "r")
499 config
= yaml
.load(stream
)
502 aliases
= config
['aliases']
505 file_lock
= threading
.RLock()
507 for mapped_key
in config
['keys']:
508 key
= mapping
.find_by_unicode(mapped_key
)
512 for action
in config
['keys'][mapped_key
]:
513 action_name
= list(action
)[0]
515 if action
[action_name
] is None:
516 action
[action_name
] = []
518 if 'include' in action
[action_name
]:
519 included
= action
[action_name
]['include']
520 del(action
[action_name
]['include'])
522 if isinstance(included
, str):
523 action
[action_name
].update(aliases
[included
], **action
[action_name
])
525 for included_
in included
:
526 action
[action_name
].update(aliases
[included_
], **action
[action_name
])
528 for argument
in action
[action_name
]:
529 if argument
== 'file':
530 filename
= action
[action_name
]['file']
531 if filename
not in seen_files
:
532 seen_files
[filename
] = MusicFile(filename
, file_lock
)
534 action_args
['music'] = seen_files
[filename
]
537 action_args
[argument
] = action
[action_name
][argument
]
539 key
.add_action(action_name
, **action_args
)