aboutsummaryrefslogblamecommitdiff
path: root/helpers/__init__.py
blob: 8570008f265efe7e86ae1f21be28c9c68213cae8 (plain) (tree)
1
2
3
4
5
6
7
8
                       
                    
            
          



                             


                    
                  

                
               
                           



                 
                                              




                                                               
                      

                               




                                                 
 


                                                                      
 
                                              

            




                                            
 





                                                                
 






                                                         
                                           
 
                                                          

            
























































                                                                                            

                     





                      




                                         
                                         
                                              
 

                                                                                                         

























                                                                      
                         
 





                                                                  
 









                                                               



                                                               
 




                                                                        


                                                            


                                    
 








                                        

                                    


                                                             
                                                   
                                                                   
 
                         
                                                            

                                                    
                                   
                                                           

                            
                                                       
 
                                    
                     


                                   





















































































































                                                                                 
                         




















                                                                                            

                   



                                                     

                   



                                                 

                   











                                                  
 

                
                                       
                                


























                                                                            









                                                                                    
                                                                                          















































                                                                                           

 
                          







                                    

                                 
                                     
                                                 








                                                 









                                                                                             
                                                
                                      

                                                          
                                                                             
 
                                                               




                                                                         
# -*- coding: utf-8 -*-
from pygame import *
import pydub
import sys
import time
import threading

draw_lock = threading.RLock()

class Action:
    action_types = [
        'command',
        'pause',
        'play',
        'stop',
        'stop_all_actions',
        'volume',
        'wait',
    ]

    def __init__(self, action, key, **kwargs):
        if action in self.action_types:
            self.action = action
        else:
            raise Exception("Unknown action {}".format(action))

        self.key = key
        self.arguments = kwargs

    def ready(self):
        if 'music' in self.arguments:
            return self.arguments['music'].loaded
        else:
            return True

    def run(self):
        print(getattr(self, self.action + "_print")(**self.arguments))
        return getattr(self, self.action)(**self.arguments)

    def command(self, command = "", **kwargs):
        pass

    def pause(self, music = None, **kwargs):
        if music is not None:
            music.pause()
        else:
            mixer.pause()

    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 stop(self, music = None, fade_out = 0, **kwargs):
        if music is not None:
            music.stop()
        else:
            mixer.stop()

    def stop_all_actions(self, **kwargs):
        self.key.mapping.stop_all_running()

    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':    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)

    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

        if isinstance(top, str):
            self.top = self.row_positions[top]
        else:
            self.top = top

        self.left   = left
        self.width  = width
        self.height = height

        self.bottom = self.top  + self.height
        self.right  = self.left + self.width

        self.rect     = (self.left, self.top, self.right, self.bottom)
        self.position = (self.left, self.top)

        if disabled:
            self.outer_color = self.lighter_outer_color
            self.linewidth = 1
        else:
            self.outer_color = self.default_outer_color
            self.linewidth = 3

        self.inner_color = self.default_inner_color
        self.actions = []

    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 collidepoint(self, position):
        return self.surface.get_rect().collidepoint(
                position[0] - self.position[0],
                position[1] - self.position[1]
                )

    def draw(self, background_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)
        else:
            police = font.Font("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 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, self, **arguments))

    def do_actions(self):
        print("running actions for {}".format(self.key_sym))
        start_time = time.time()
        self.mapping.start_running(self, start_time)
        for action in self.actions:
            if self.mapping.keep_running(self, start_time):
                action.run()

        self.mapping.finished_running(self, start_time)

    def list_actions(self, surface):
        # FIXME: Todo
        print("bouh", self.key_sym)
        surface.fill((255, 0, 0))


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 = {}
        self.running = []
        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(self, position):
        for key in self.keys:
            if self.keys[key].collidepoint(position):
                return self.keys[key]
        return None

    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

    def stop_all_running(self):
        self.running = []

    def start_running(self, key, start_time):
        self.running.append((key, start_time))

    def keep_running(self, key, start_time):
        return (key, start_time) in self.running

    def finished_running(self, key, start_time):
        if (key, start_time) in self.running:
            self.running.remove((key, start_time))


class MusicFile:
    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):
        self.rect        = Rect(rect)
        self.outer_color = Color(*outer_color)
        self.inner_color = Color(*inner_color)
        self.linewidth   = linewidth
        self.radius      = radius

    def surface(self):
        rectangle       = self.filledRoundedRect(self.rect, self.outer_color, self.radius)

        inner_rect      = Rect((
                self.rect.left   + 2 * self.linewidth,
                self.rect.top    + 2 * self.linewidth,
                self.rect.right  - 2 * self.linewidth,
                self.rect.bottom - 2 * self.linewidth
                ))

        inner_rectangle = self.filledRoundedRect(inner_rect, self.inner_color, self.radius)

        rectangle.blit(inner_rectangle, (self.linewidth, self.linewidth))

        return rectangle

    def filledRoundedRect(self, rect, color, radius=0.4):
        """
        filledRoundedRect(rect,color,radius=0.4)

        rect    : rectangle
        color   : rgb or rgba
        radius  : 0 <= radius <= 1
        """

        alpha        = color.a
        color.a      = 0
        pos          = rect.topleft
        rect.topleft = 0,0
        rectangle    = Surface(rect.size,SRCALPHA)

        circle       = Surface([min(rect.size)*3]*2,SRCALPHA)
        draw.ellipse(circle,(0,0,0),circle.get_rect(),0)
        circle       = transform.smoothscale(circle,[int(min(rect.size)*radius)]*2)

        radius              = rectangle.blit(circle,(0,0))
        radius.bottomright  = rect.bottomright
        rectangle.blit(circle,radius)
        radius.topright     = rect.topright
        rectangle.blit(circle,radius)
        radius.bottomleft   = rect.bottomleft
        rectangle.blit(circle,radius)

        rectangle.fill((0,0,0),rect.inflate(-radius.w,0))
        rectangle.fill((0,0,0),rect.inflate(0,-radius.h))

        rectangle.fill(color,special_flags=BLEND_RGBA_MAX)
        rectangle.fill((255,255,255,alpha),special_flags=BLEND_RGBA_MIN)

        return rectangle


def parse_config(mapping):
    import yaml
    stream = open("config.yml", "r")
    config = yaml.load(stream)
    stream.close()

    aliases = config['aliases']
    seen_files = {}

    file_lock = threading.RLock()

    for mapped_key in config['keys']:
        key = mapping.find_by_unicode(mapped_key)
        if key is None:
            continue

        for action in config['keys'][mapped_key]:
            action_name = list(action)[0]
            action_args = {}
            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 == 'file':
                    filename = action[action_name]['file']
                    if filename not in seen_files:
                        seen_files[filename] = MusicFile(filename, file_lock)

                    action_args['music'] = seen_files[filename]

                else:
                    action_args[argument] = action[action_name][argument]

            key.add_action(action_name, **action_args)