From: Ismaël Bouya Date: Sat, 25 Jun 2016 21:20:34 +0000 (+0200) Subject: Use kivy instead of pygame X-Git-Tag: 1.0.0~81 X-Git-Url: https://git.immae.eu/?p=perso%2FImmae%2FProjets%2FPython%2FMusicSampler.git;a=commitdiff_plain;h=8612c5f8bb0fc9529bc489a6719654d4474db6d5 Use kivy instead of pygame --- diff --git a/fonts/Ubuntu-B.ttf b/fonts/Ubuntu-B.ttf new file mode 100644 index 0000000..b173da2 Binary files /dev/null and b/fonts/Ubuntu-B.ttf differ diff --git a/helpers/__init__.py b/helpers/__init__.py index 7fe9c35..eb948f2 100644 --- a/helpers/__init__.py +++ b/helpers/__init__.py @@ -5,6 +5,80 @@ from .lock import * from .font import * import yaml +def parse_config2(): + stream = open("config.yml", "r") + config = yaml.load(stream) + stream.close() + + aliases = config['aliases'] + seen_files = {} + + file_lock = Lock("file") + + channel_id = 0 + + key_properties = {} + + for key in config['key_properties']: + if key not in key_properties: + key_properties[key] = { + "actions": [], + "properties": config['key_properties'][key], + "files": [] + } + + for mapped_key in config['keys']: + if mapped_key not in key_properties: + key_properties[mapped_key] = { + "actions": [], + "properties": {}, + "files": [] + } + 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: + if filename in config['music_properties']: + seen_files[filename] = MusicFile( + filename, + file_lock, + channel_id, + **config['music_properties'][filename]) + else: + seen_files[filename] = MusicFile( + filename, + file_lock, + channel_id) + channel_id = channel_id + 1 + + if filename not in key_properties[mapped_key]['files']: + key_properties[mapped_key]['files'].append(seen_files[filename]) + + action_args['music'] = seen_files[filename] + + else: + action_args[argument] = action[action_name][argument] + + key_properties[mapped_key]['actions'].append([action_name, action_args]) + + return (key_properties, channel_id + 1, seen_files) + def parse_config(mapping): stream = open("config.yml", "r") config = yaml.load(stream) @@ -46,7 +120,7 @@ def parse_config(mapping): seen_files[filename] = MusicFile( filename, file_lock, - channel_id, + channel_id, **config['music_properties'][filename]) else: seen_files[filename] = MusicFile( diff --git a/helpers/action.py b/helpers/action.py index d4c6252..aff61e7 100644 --- a/helpers/action.py +++ b/helpers/action.py @@ -31,7 +31,7 @@ class Action: def run(self): print(self.description()) getattr(self, self.action)(**self.arguments) - pygame.event.post(pygame.event.Event(pygame.USEREVENT)) + #pygame.event.post(pygame.event.Event(pygame.USEREVENT)) def description(self): return getattr(self, self.action + "_print")(**self.arguments) @@ -74,7 +74,7 @@ class Action: pygame.mixer.stop() def stop_all_actions(self, **kwargs): - self.key.mapping.stop_all_running() + self.key.parent.stop_all_running() def volume(self, music = None, value = 100, **kwargs): if music is not None: diff --git a/keyboard.py b/keyboard.py new file mode 100644 index 0000000..0c1115f --- /dev/null +++ b/keyboard.py @@ -0,0 +1,222 @@ +from kivy.app import App +from kivy.uix.widget import Widget +from kivy.uix.floatlayout import FloatLayout +from kivy.uix.relativelayout import RelativeLayout +from kivy.properties import AliasProperty, ReferenceListProperty, BooleanProperty, NumericProperty, ListProperty, StringProperty, ObjectProperty +from kivy.vector import Vector +from kivy.clock import Clock +from kivy.uix.behaviors import ButtonBehavior +from kivy.uix.label import Label +from kivy.core.window import Window + +from helpers.action import * +import time +import sys +import math +import pygame +import helpers +import threading + +class KeyDescription(Label): + pass + +class Key(ButtonBehavior, Widget): + key_sym = StringProperty(None) + custom_color = ListProperty([0, 1, 0, 1]) + custom_unready_color = ListProperty([0, 1, 0, 100/255]) + description_title = StringProperty("") + description = ListProperty([]) + is_key_ready = BooleanProperty(True) + + def get_color(self): + if not self.has_actions: + return [1, 1, 1, 1] + elif self.all_actions_ready: + return self.custom_color + else: + return self.custom_unready_color + def set_color(self): + pass + + color = AliasProperty(get_color, set_color, bind=['is_key_ready']) + + def __init__(self, **kwargs): + super(Key, self).__init__(**kwargs) + self.actions = [] + + def on_key_sym(self, key, key_sym): + if key_sym in self.parent.key_config: + self.is_key_ready = False + + self.config = self.parent.key_config[key_sym] + + self.actions = [] + for key_action in self.config['actions']: + self.add_action(key_action[0], **key_action[1]) + + if 'description' in self.config['properties']: + key.set_description(self.config['properties']['description']) + if 'color' in self.config['properties']: + key.set_color(self.config['properties']['color']) + + Clock.schedule_interval(self.check_all_active, 1) + + def check_all_active(self, dt): + if self.all_actions_ready: + self.is_key_ready = True + return False + + def set_description(self, description): + if description[0] is not None: + self.description_title = str(description[0]) + for desc in description[1:]: + if desc is None: + self.description.append("") + else: + self.description.append(str(desc).replace(" ", " ")) + + def set_color(self, color): + color = [x / 255 for x in color] + color.append(1) + self.custom_color = color + color[3] = 100 / 255 + self.custom_unready_color = tuple(color) + + @property + def has_actions(self): + return len(self.actions) > 0 + + @property + 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.parent.start_running(self, start_time) + action_number = 0 + for action in self.actions: + if self.parent.keep_running(self, start_time): + self.list_actions(action_number = action_number + 0.5) + action.run() + action_number += 1 + self.list_actions(action_number = action_number) + + self.parent.finished_running(self, start_time) + + def list_actions(self, action_number = 0): + self.parent.parent.ids['ActionList'].update_list(self, action_number) + + def on_press(self): + self.list_actions() + pass + +class PlayList(RelativeLayout): + playlist = ListProperty([]) + + def __init__(self, **kwargs): + super(PlayList, self).__init__(**kwargs) + Clock.schedule_interval(self.update_playlist, 0.5) + + def update_playlist(self, dt): + if self.parent is None or 'Mapping' not in self.parent.ids: + return True + + open_files = self.parent.ids['Mapping'].open_files + self.playlist = [] + for music_file in open_files.values(): + if not music_file.is_playing(): + continue + if music_file.is_paused(): + self.playlist.append(["⏸", music_file.name, False]) + else: + self.playlist.append(["⏵", music_file.name, True]) + + +class ActionList(RelativeLayout): + action_title = StringProperty("") + action_list = ListProperty([]) + + def update_list(self, key, action_number = 0): + self.action_title = "actions linked to key {}:".format(key.key_sym) + self.action_list = [] + + action_descriptions = [action.description() for action in key.actions] + + for index, description in enumerate(action_descriptions): + if index < int(action_number): + icon = "✓" + elif index + 0.5 == action_number: + icon = "✅" + else: + icon = " " + + self.action_list.append([icon, description]) + +class Mapping(RelativeLayout): + expected_keys = NumericProperty(0) + + def __init__(self, **kwargs): + self.key_config, self.channel_number, self.open_files = helpers.parse_config2() + super(Mapping, self).__init__(**kwargs) + self._keyboard = Window.request_keyboard(self._keyboard_closed, self) + self._keyboard.bind(on_key_down=self._on_keyboard_down) + self.running = [] + + + pygame.mixer.init(frequency = 44100) + pygame.mixer.set_num_channels(self.channel_number) + + def _keyboard_closed(self): + self._keyboard.unbind(on_key_down=self._on_keyboard_down) + self._keyboard = None + + def _on_keyboard_down(self, keyboard, keycode, text, modifiers): + key = self.find_by_key_code(keycode) + if key is not None: + threading.Thread(name = "MSKeyAction", target=key.do_actions).start() + return True + + def find_by_key_code(self, key_code): + if "Key_" + str(key_code[0]) in self.ids: + return self.ids["Key_" + str(key_code[0])] + return None + + def find_by_unicode(self, key_sym): + for key in self.children: + if not type(key).__name__ == "Key": + continue + print(key.key_sym, key_sym) + if key.key_sym == key_sym: + print("found") + return 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 Screen(FloatLayout): + pass + +class MusicSamplerApp(App): + def build(self): + Window.size = (913, 563) + + return Screen() + +if __name__ == '__main__': + MusicSamplerApp().run() diff --git a/musicsampler.kv b/musicsampler.kv new file mode 100644 index 0000000..3c1964b --- /dev/null +++ b/musicsampler.kv @@ -0,0 +1,743 @@ +#:import math math + +: + pad_col_sep: 0 if not self.pad_cols else self.parent.pad_x + pad_cols: False + + y: (self.parent.top-self.parent.y) - (self.row) * self.parent.key_size - (self.row - 1) * self.parent.key_sep + x: (self.col - 1) * self.parent.key_size + int(self.col - 1) * self.parent.key_sep + self.pad_col_sep + size_hint: None, None + line_color: 120/255, 120/255, 120/255, 1 + enabled: True + line_width: 2 + row: 1 + col: 0 + key_code: 0 + key_sym: "" + key_width: 1 + key_height: 1 + width: self.key_width * (self.parent.key_size + self.parent.key_sep) - self.parent.key_sep + height: self.key_height * (self.parent.key_size + self.parent.key_sep) - self.parent.key_sep + canvas.before: + Color: + rgba: self.color + RoundedRectangle: + pos: self.x, self.y + size: self.size + canvas: + Color: + rgba: self.line_color + Line: + rounded_rectangle: self.x + self.line_width, self.y + self.line_width, self.width - 2 * self.line_width, self.height - 2 * self.line_width, 10 + width: self.line_width + Label: + id: key_label + font_name: "fonts/Ubuntu-B.ttf" + font_size: math.ceil(2 * math.sqrt(self.parent.parent.key_size)) + color: 0, 0, 0, 1 + text: self.parent.key_sym + text_size: self.parent.width,self.font_size + shorten: True + shorten_from: "right" + split_str: "" + center_x: self.parent.x + self.texture_size[0] /2 + 5 + center_y: self.parent.y + self.parent.height - self.texture_size[1] /2 - 5 + Label: + id: key_description_title + font_name: "fonts/Ubuntu-Regular.ttf" + font_size: math.ceil(2 * math.sqrt(self.parent.parent.key_size / 2)) + color: 0, 0, 0, 1 + text: self.parent.description_title + text_size: self.parent.width - 2*self.parent.line_width, self.font_size + halign: "right" + valign: "middle" + center_x: self.parent.x + self.texture_size[0] /2 + center_y: self.parent.y + self.parent.height - self.texture_size[1] /2 - 5 + Label: + id: key_description + font_name: "fonts/Ubuntu-Regular.ttf" + font_size: math.ceil(2 * math.sqrt(self.parent.parent.key_size / 2)) + color: 0, 0, 0, 1 + text: "\n".join(self.parent.description) + text_size: 2 * self.parent.width,self.parent.height - key_label.font_size + halign: "left" + valign: "middle" + pos: self.parent.x + 2 * self.parent.line_width + 2, self.parent.y + size_hint: None, None + size: 2 * self.parent.width - 2 * self.parent.line_width, self.parent.height - key_label.font_size + +: + key_size: int( (3 * self.width - 16) / 56) + key_sep: int( self.key_size / 24) + key_pad_sep: int( self.key_size / 7) + 1 + + border: (self.width - self.key_size * 18 - self.key_sep * 16 - self.key_pad_sep)/ 2 + + mapping_height: self.key_size * 6 + self.key_sep * 5 + mapping_width: self.key_size * 18 + self.key_sep * 16 + self.key_pad_sep + mapping_x: self.border + mapping_y: self.top - self.mapping_height - self.border + action_list_height: self.height - self.mapping_height - 3 * self.border + action_list_width: 3 * self.width / 4 + action_list_x: self.border + action_list_y: self.border + play_list_height: self.action_list_height + play_list_width: self.width - self.action_list_width - 3* self.border + play_list_y: self.border + play_list_x: self.action_list_width + 2 * self.border + + Mapping: + id: Mapping + pos: self.parent.mapping_x, self.parent.mapping_y + size: self.parent.mapping_width, self.parent.mapping_height + + key_size: self.parent.key_size + key_sep: self.parent.key_sep + key_pad_sep: self.parent.key_pad_sep + pad_x: self.key_size * 15 + 14 * self.key_sep + self.key_pad_sep + ActionList: + id: ActionList + pos: self.parent.action_list_x, self.parent.action_list_y + size: self.parent.action_list_width, self.parent.action_list_height + PlayList: + id: PlayList + pos: self.parent.play_list_x, self.parent.play_list_y + size: self.parent.play_list_width, self.parent.play_list_height + +: + size_hint: None, None + canvas: + Color: + rgba: 250./255, 250./255, 250./255, 1 + Rectangle: + pos: 0, 0 + size: self.width, self.height + Label: + id: action_list_title + font_name: "fonts/Ubuntu-B.ttf" + font_size: math.ceil(2 * math.sqrt(self.parent.parent.key_size or 10)) + color: 0, 0, 0, 1 + text: self.parent.action_title + text_size: None, self.parent.height + halign: "left" + valign: "top" + size_hint: None, None + size: self.texture_size[0], self.parent.height + Label: + id: action_list_icons + font_name: "fonts/Symbola.ttf" + font_size: math.ceil(2 * math.sqrt(self.parent.parent.key_size or 10)) + line_height: 1.2 # FIXME: Donner la bonne taille de label + color: 0, 0, 0, 1 + text: "\n".join(map(lambda x: x[0], self.parent.action_list)) + text_size: None, self.parent.height + halign: "left" + valign: "top" + size_hint: None, None + size: self.texture_size[0], self.parent.height - 3 * self.line_height * self.font_size + Label: + id: action_list_names + font_name: "fonts/Ubuntu-Regular.ttf" + font_size: math.ceil(2 * math.sqrt(self.parent.parent.key_size or 10)) + color: 0, 0, 0, 1 + text: "\n".join(map(lambda x: x[1], self.parent.action_list)) + text_size: None, self.parent.height + halign: "left" + valign: "top" + size_hint: None, None + pos: 15, self.y + size: self.texture_size[0], self.parent.height - 3 * self.line_height * self.font_size + +: + size_hint: None, None + canvas: + Color: + rgba: 250./255, 250./255, 250./255, 1 + Rectangle: + pos: 0, 0 + size: self.width, self.height + Label: + id: playlist_icons + font_name: "fonts/Symbola.ttf" + font_size: math.ceil(2 * math.sqrt(self.parent.parent.key_size or 10)) + line_height: 1.2 # FIXME: Donner la bonne taille de label + color: 0, 0, 0, 1 + text: "\n".join(map(lambda x: x[0], self.parent.playlist)) + text_size: None, self.parent.height + halign: "left" + valign: "top" + size_hint: None, None + size: self.texture_size[0], self.parent.height + Label: + id: playlist_names + font_name: "fonts/Ubuntu-Regular.ttf" # FIXME: Mettre en gras quand c'est en cours + font_size: math.ceil(2 * math.sqrt(self.parent.parent.key_size or 10)) + color: 0, 0, 0, 1 + text: "\n".join(map(lambda x: x[1], self.parent.playlist)) + text_size: None, self.parent.height + halign: "left" + valign: "top" + size_hint: None, None + pos: 15, self.y + size: self.texture_size[0], self.parent.height + +: + size_hint: None, None + key_size: 48 + key_sep: 2 + key_pad_sep: 7 + pad_x: 755 + canvas: + Color: + rgba: 250./255, 250./255, 250./255, 1 + Rectangle: + pos: 0, 0 + size: self.width, self.height + Key: + id: Key_27 + key_code: 27 + key_sym: "ESC" + row: 1 + col: 1 + Key: + id: Key_282 + key_code: 282 + key_sym: "F1" + row: 1 + col: 3 + Key: + id: Key_283 + key_code: 283 + key_sym: "F2" + row: 1 + col: 4 + Key: + id: Key_284 + key_code: 284 + key_sym: "F3" + row: 1 + col: 5 + Key: + id: Key_285 + key_code: 285 + key_sym: "F4" + row: 1 + col: 6 + + Key: + id: Key_286 + key_code: 286 + key_sym: "F5" + row: 1 + col: 7.5 + Key: + id: Key_287 + key_code: 287 + key_sym: "F6" + row: 1 + col: 8.5 + Key: + id: Key_288 + key_code: 288 + key_sym: "F7" + row: 1 + col: 9.5 + Key: + id: Key_289 + key_code: 289 + key_sym: "F8" + row: 1 + col: 10.5 + + Key: + id: Key_290 + key_code: 290 + key_sym: "F9" + row: 1 + col: 12 + Key: + id: Key_291 + key_code: 291 + key_sym: "F10" + row: 1 + col: 13 + Key: + id: Key_292 + key_code: 292 + key_sym: "F11" + row: 1 + col: 14 + Key: + id: Key_293 + key_code: 293 + key_sym: "F12" + row: 1 + col: 15 + + Key: + id: Key_178 + key_code: 178 + key_sym: "²" + row: 2 + col: 1 + Key: + id: Key_38 + key_code: 38 + key_sym: "&" + row: 2 + col: 2 + Key: + id: Key_233 + key_code: 233 + key_sym: "é" + row: 2 + col: 3 + Key: + id: Key_34 + key_code: 34 + key_sym: '"' + row: 2 + col: 4 + Key: + id: Key_39 + key_code: 39 + key_sym: "'" + row: 2 + col: 5 + Key: + id: Key_40 + key_code: 40 + key_sym: "(" + row: 2 + col: 6 + Key: + id: Key_45 + key_code: 45 + key_sym: "-" + row: 2 + col: 7 + Key: + id: Key_232 + key_code: 232 + key_sym: "è" + row: 2 + col: 8 + Key: + id: Key_95 + key_code: 95 + key_sym: "_" + row: 2 + col: 9 + Key: + id: Key_231 + key_code: 231 + key_sym: "ç" + row: 2 + col: 10 + Key: + id: Key_224 + key_code: 224 + key_sym: "à" + row: 2 + col: 11 + Key: + id: Key_41 + key_code: 41 + key_sym: ")" + row: 2 + col: 12 + Key: + id: Key_61 + key_code: 61 + key_sym: "=" + row: 2 + col: 13 + Key: + id: Key_8 + key_code: 8 + key_sym: "<-" + row: 2 + col: 14 + key_width: 2 + Key: + id: Key_9 + key_code: 9 + key_sym: "tab" + row: 3 + col: 1 + key_width: 1.48 + Key: + id: Key_97 + key_code: 97 + key_sym: "a" + row: 3 + col: 2.5 + Key: + id: Key_122 + key_code: 122 + key_sym: "z" + row: 3 + col: 3.5 + Key: + id: Key_101 + key_code: 101 + key_sym: "e" + row: 3 + col: 4.5 + Key: + id: Key_114 + key_code: 114 + key_sym: "r" + row: 3 + col: 5.5 + Key: + id: Key_116 + key_code: 116 + key_sym: "t" + row: 3 + col: 6.5 + Key: + id: Key_121 + key_code: 121 + key_sym: "y" + row: 3 + col: 7.5 + Key: + id: Key_117 + key_code: 117 + key_sym: "u" + row: 3 + col: 8.5 + Key: + id: Key_105 + key_code: 105 + key_sym: "i" + row: 3 + col: 9.5 + Key: + id: Key_111 + key_code: 111 + key_sym: "o" + row: 3 + col: 10.5 + Key: + id: Key_112 + key_code: 112 + key_sym: "p" + row: 3 + col: 11.5 + Key: + id: Key_94 + key_code: 94 + key_sym: "^" + row: 3 + col: 12.5 + Key: + id: Key_36 + key_code: 36 + key_sym: "$" + row: 3 + col: 13.5 + Key: + id: Key_13 + key_code: 13 + key_sym: "Enter" + row: 4 + col: 14.8 + key_width: 1.23 + key_height: 2 + Key: + id: Key_301 + key_code: 301 + key_sym: "CAPS" + row: 4 + col: 1 + key_width: 1.75 + line_width: 1 + enabled: False + + Key: + id: Key_113 + key_code: 113 + key_sym: "q" + row: 4 + col: 2.8 + Key: + id: Key_115 + key_code: 115 + key_sym: "s" + row: 4 + col: 3.8 + Key: + id: Key_100 + key_code: 100 + key_sym: "d" + row: 4 + col: 4.8 + Key: + id: Key_102 + key_code: 102 + key_sym: "f" + row: 4 + col: 5.8 + Key: + id: Key_103 + key_code: 103 + key_sym: "g" + row: 4 + col: 6.8 + Key: + id: Key_104 + key_code: 104 + key_sym: "h" + row: 4 + col: 7.8 + Key: + id: Key_106 + key_code: 106 + key_sym: "j" + row: 4 + col: 8.8 + Key: + id: Key_107 + key_code: 107 + key_sym: "k" + row: 4 + col: 9.8 + Key: + id: Key_108 + key_code: 108 + key_sym: "l" + row: 4 + col: 10.8 + Key: + id: Key_109 + key_code: 109 + key_sym: "m" + row: 4 + col: 11.8 + Key: + id: Key_249 + key_code: 249 + key_sym: "ù" + row: 4 + col: 12.8 + Key: + id: Key_42 + key_code: 42 + key_sym: "*" + row: 4 + col: 13.8 + Key: + id: Key_304 + key_code: 304 + key_sym: "LShift" + row: 5 + col: 1 + key_width: 1.3 + line_width: 1 + enabled: False + Key: + id: Key_60 + key_code: 60 + key_sym: "<" + row: 5 + col: 2.3 + Key: + id: Key_119 + key_code: 119 + key_sym: "w" + row: 5 + col: 3.3 + Key: + id: Key_120 + key_code: 120 + key_sym: "x" + row: 5 + col: 4.3 + Key: + id: Key_99 + key_code: 99 + key_sym: "c" + row: 5 + col: 5.3 + Key: + id: Key_118 + key_code: 118 + key_sym: "v" + row: 5 + col: 6.3 + Key: + id: Key_98 + key_code: 98 + key_sym: "b" + row: 5 + col: 7.3 + Key: + id: Key_110 + key_code: 110 + key_sym: "n" + row: 5 + col: 8.3 + Key: + id: Key_44 + key_code: 44 + key_sym: "," + row: 5 + col: 9.3 + Key: + id: Key_59 + key_code: 59 + key_sym: ";" + row: 5 + col: 10.3 + Key: + id: Key_58 + key_code: 58 + key_sym: ":" + row: 5 + col: 11.3 + Key: + id: Key_33 + key_code: 33 + key_sym: "!" + row: 5 + col: 12.3 + Key: + id: Key_303 + key_code: 303 + key_sym: "RShift" + row: 5 + col: 13.3 + key_width: 2.7 + line_width: 1 + enabled: False + Key: + id: Key_306 + key_code: 306 + key_sym: "LCtrl" + row: 6 + col: 1 + key_width: 1.3 + line_width: 1 + enabled: False + Key: + id: Key_311 + key_code: 311 + key_sym: "LSuper" + row: 6 + col: 3.3 + line_width: 1 + enabled: False + Key: + id: Key_308 + key_code: 308 + key_sym: "LAlt" + row: 6 + col: 4.3 + line_width: 1 + enabled: False + Key: + id: Key_32 + key_code: 32 + key_sym: "Espace" + row: 6 + col: 5.3 + key_width: 5 + Key: + id: Key_313 + key_code: 313 + key_sym: "AltGr" + row: 6 + col: 10.3 + line_width: 1 + enabled: False + Key: + id: Key_314 + key_code: 314 + key_sym: "Compose" + row: 6 + col: 11.3 + line_width: 1 + enabled: False + Key: + id: Key_305 + key_code: 305 + key_sym: "RCtrl" + row: 6 + col: 12.3 + key_width: 1.3 + line_width: 1 + enabled: False + + + Key: + id: Key_277 + key_code: 277 + key_sym: "ins" + row: 2 + col: 1 + pad_cols: True + Key: + id: Key_278 + key_code: 278 + key_sym: "home" + row: 2 + col: 2 + pad_cols: True + Key: + id: Key_280 + key_code: 280 + key_sym: "pg_u" + row: 2 + col: 3 + pad_cols: True + Key: + id: Key_127 + key_code: 127 + key_sym: "del" + row: 3 + col: 1 + pad_cols: True + Key: + id: Key_279 + key_code: 279 + key_sym: "end" + row: 3 + col: 2 + pad_cols: True + Key: + id: Key_281 + key_code: 281 + key_sym: "pg_d" + row: 3 + col: 3 + pad_cols: True + Key: + id: Key_273 + key_code: 273 + key_sym: "up" + row: 5 + col: 2 + pad_cols: True + Key: + id: Key_274 + key_code: 274 + key_sym: "down" + row: 6 + col: 2 + pad_cols: True + Key: + id: Key_276 + key_code: 276 + key_sym: "left" + row: 6 + col: 1 + pad_cols: True + Key: + id: Key_275 + key_code: 275 + key_sym: "right" + row: 6 + col: 3 + pad_cols: True