aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIsmaël Bouya <ismael.bouya@normalesup.org>2016-06-25 23:20:34 +0200
committerIsmaël Bouya <ismael.bouya@normalesup.org>2016-06-25 23:24:19 +0200
commit8612c5f8bb0fc9529bc489a6719654d4474db6d5 (patch)
treed335fa08bd49f155d1f9159a96904905e9b01777
parent9d505ec9accd9a84bc6f22f4118bed9669c32fc8 (diff)
downloadMusicSampler-8612c5f8bb0fc9529bc489a6719654d4474db6d5.tar.gz
MusicSampler-8612c5f8bb0fc9529bc489a6719654d4474db6d5.tar.zst
MusicSampler-8612c5f8bb0fc9529bc489a6719654d4474db6d5.zip
Use kivy instead of pygame
-rw-r--r--fonts/Ubuntu-B.ttfbin0 -> 333612 bytes
-rw-r--r--helpers/__init__.py76
-rw-r--r--helpers/action.py4
-rw-r--r--keyboard.py222
-rw-r--r--musicsampler.kv743
5 files changed, 1042 insertions, 3 deletions
diff --git a/fonts/Ubuntu-B.ttf b/fonts/Ubuntu-B.ttf
new file mode 100644
index 0000000..b173da2
--- /dev/null
+++ b/fonts/Ubuntu-B.ttf
Binary files 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 *
5from .font import * 5from .font import *
6import yaml 6import yaml
7 7
8def parse_config2():
9 stream = open("config.yml", "r")
10 config = yaml.load(stream)
11 stream.close()
12
13 aliases = config['aliases']
14 seen_files = {}
15
16 file_lock = Lock("file")
17
18 channel_id = 0
19
20 key_properties = {}
21
22 for key in config['key_properties']:
23 if key not in key_properties:
24 key_properties[key] = {
25 "actions": [],
26 "properties": config['key_properties'][key],
27 "files": []
28 }
29
30 for mapped_key in config['keys']:
31 if mapped_key not in key_properties:
32 key_properties[mapped_key] = {
33 "actions": [],
34 "properties": {},
35 "files": []
36 }
37 for action in config['keys'][mapped_key]:
38 action_name = list(action)[0]
39 action_args = {}
40 if action[action_name] is None:
41 action[action_name] = []
42
43 if 'include' in action[action_name]:
44 included = action[action_name]['include']
45 del(action[action_name]['include'])
46
47 if isinstance(included, str):
48 action[action_name].update(aliases[included], **action[action_name])
49 else:
50 for included_ in included:
51 action[action_name].update(aliases[included_], **action[action_name])
52
53 for argument in action[action_name]:
54 if argument == 'file':
55 filename = action[action_name]['file']
56 if filename not in seen_files:
57 if filename in config['music_properties']:
58 seen_files[filename] = MusicFile(
59 filename,
60 file_lock,
61 channel_id,
62 **config['music_properties'][filename])
63 else:
64 seen_files[filename] = MusicFile(
65 filename,
66 file_lock,
67 channel_id)
68 channel_id = channel_id + 1
69
70 if filename not in key_properties[mapped_key]['files']:
71 key_properties[mapped_key]['files'].append(seen_files[filename])
72
73 action_args['music'] = seen_files[filename]
74
75 else:
76 action_args[argument] = action[action_name][argument]
77
78 key_properties[mapped_key]['actions'].append([action_name, action_args])
79
80 return (key_properties, channel_id + 1, seen_files)
81
8def parse_config(mapping): 82def parse_config(mapping):
9 stream = open("config.yml", "r") 83 stream = open("config.yml", "r")
10 config = yaml.load(stream) 84 config = yaml.load(stream)
@@ -46,7 +120,7 @@ def parse_config(mapping):
46 seen_files[filename] = MusicFile( 120 seen_files[filename] = MusicFile(
47 filename, 121 filename,
48 file_lock, 122 file_lock,
49 channel_id, 123 channel_id,
50 **config['music_properties'][filename]) 124 **config['music_properties'][filename])
51 else: 125 else:
52 seen_files[filename] = MusicFile( 126 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:
31 def run(self): 31 def run(self):
32 print(self.description()) 32 print(self.description())
33 getattr(self, self.action)(**self.arguments) 33 getattr(self, self.action)(**self.arguments)
34 pygame.event.post(pygame.event.Event(pygame.USEREVENT)) 34 #pygame.event.post(pygame.event.Event(pygame.USEREVENT))
35 35
36 def description(self): 36 def description(self):
37 return getattr(self, self.action + "_print")(**self.arguments) 37 return getattr(self, self.action + "_print")(**self.arguments)
@@ -74,7 +74,7 @@ class Action:
74 pygame.mixer.stop() 74 pygame.mixer.stop()
75 75
76 def stop_all_actions(self, **kwargs): 76 def stop_all_actions(self, **kwargs):
77 self.key.mapping.stop_all_running() 77 self.key.parent.stop_all_running()
78 78
79 def volume(self, music = None, value = 100, **kwargs): 79 def volume(self, music = None, value = 100, **kwargs):
80 if music is not None: 80 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 @@
1from kivy.app import App
2from kivy.uix.widget import Widget
3from kivy.uix.floatlayout import FloatLayout
4from kivy.uix.relativelayout import RelativeLayout
5from kivy.properties import AliasProperty, ReferenceListProperty, BooleanProperty, NumericProperty, ListProperty, StringProperty, ObjectProperty
6from kivy.vector import Vector
7from kivy.clock import Clock
8from kivy.uix.behaviors import ButtonBehavior
9from kivy.uix.label import Label
10from kivy.core.window import Window
11
12from helpers.action import *
13import time
14import sys
15import math
16import pygame
17import helpers
18import threading
19
20class KeyDescription(Label):
21 pass
22
23class Key(ButtonBehavior, Widget):
24 key_sym = StringProperty(None)
25 custom_color = ListProperty([0, 1, 0, 1])
26 custom_unready_color = ListProperty([0, 1, 0, 100/255])
27 description_title = StringProperty("")
28 description = ListProperty([])
29 is_key_ready = BooleanProperty(True)
30
31 def get_color(self):
32 if not self.has_actions:
33 return [1, 1, 1, 1]
34 elif self.all_actions_ready:
35 return self.custom_color
36 else:
37 return self.custom_unready_color
38 def set_color(self):
39 pass
40
41 color = AliasProperty(get_color, set_color, bind=['is_key_ready'])
42
43 def __init__(self, **kwargs):
44 super(Key, self).__init__(**kwargs)
45 self.actions = []
46
47 def on_key_sym(self, key, key_sym):
48 if key_sym in self.parent.key_config:
49 self.is_key_ready = False
50
51 self.config = self.parent.key_config[key_sym]
52
53 self.actions = []
54 for key_action in self.config['actions']:
55 self.add_action(key_action[0], **key_action[1])
56
57 if 'description' in self.config['properties']:
58 key.set_description(self.config['properties']['description'])
59 if 'color' in self.config['properties']:
60 key.set_color(self.config['properties']['color'])
61
62 Clock.schedule_interval(self.check_all_active, 1)
63
64 def check_all_active(self, dt):
65 if self.all_actions_ready:
66 self.is_key_ready = True
67 return False
68
69 def set_description(self, description):
70 if description[0] is not None:
71 self.description_title = str(description[0])
72 for desc in description[1:]:
73 if desc is None:
74 self.description.append("")
75 else:
76 self.description.append(str(desc).replace(" ", " "))
77
78 def set_color(self, color):
79 color = [x / 255 for x in color]
80 color.append(1)
81 self.custom_color = color
82 color[3] = 100 / 255
83 self.custom_unready_color = tuple(color)
84
85 @property
86 def has_actions(self):
87 return len(self.actions) > 0
88
89 @property
90 def all_actions_ready(self):
91 return all(action.ready() for action in self.actions)
92
93 def add_action(self, action_name, **arguments):
94 self.actions.append(Action(action_name, self, **arguments))
95
96 def do_actions(self):
97 print("running actions for {}".format(self.key_sym))
98 start_time = time.time()
99 self.parent.start_running(self, start_time)
100 action_number = 0
101 for action in self.actions:
102 if self.parent.keep_running(self, start_time):
103 self.list_actions(action_number = action_number + 0.5)
104 action.run()
105 action_number += 1
106 self.list_actions(action_number = action_number)
107
108 self.parent.finished_running(self, start_time)
109
110 def list_actions(self, action_number = 0):
111 self.parent.parent.ids['ActionList'].update_list(self, action_number)
112
113 def on_press(self):
114 self.list_actions()
115 pass
116
117class PlayList(RelativeLayout):
118 playlist = ListProperty([])
119
120 def __init__(self, **kwargs):
121 super(PlayList, self).__init__(**kwargs)
122 Clock.schedule_interval(self.update_playlist, 0.5)
123
124 def update_playlist(self, dt):
125 if self.parent is None or 'Mapping' not in self.parent.ids:
126 return True
127
128 open_files = self.parent.ids['Mapping'].open_files
129 self.playlist = []
130 for music_file in open_files.values():
131 if not music_file.is_playing():
132 continue
133 if music_file.is_paused():
134 self.playlist.append(["⏸", music_file.name, False])
135 else:
136 self.playlist.append(["⏵", music_file.name, True])
137
138
139class ActionList(RelativeLayout):
140 action_title = StringProperty("")
141 action_list = ListProperty([])
142
143 def update_list(self, key, action_number = 0):
144 self.action_title = "actions linked to key {}:".format(key.key_sym)
145 self.action_list = []
146
147 action_descriptions = [action.description() for action in key.actions]
148
149 for index, description in enumerate(action_descriptions):
150 if index < int(action_number):
151 icon = "✓"
152 elif index + 0.5 == action_number:
153 icon = "✅"
154 else:
155 icon = " "
156
157 self.action_list.append([icon, description])
158
159class Mapping(RelativeLayout):
160 expected_keys = NumericProperty(0)
161
162 def __init__(self, **kwargs):
163 self.key_config, self.channel_number, self.open_files = helpers.parse_config2()
164 super(Mapping, self).__init__(**kwargs)
165 self._keyboard = Window.request_keyboard(self._keyboard_closed, self)
166 self._keyboard.bind(on_key_down=self._on_keyboard_down)
167 self.running = []
168
169
170 pygame.mixer.init(frequency = 44100)
171 pygame.mixer.set_num_channels(self.channel_number)
172
173 def _keyboard_closed(self):
174 self._keyboard.unbind(on_key_down=self._on_keyboard_down)
175 self._keyboard = None
176
177 def _on_keyboard_down(self, keyboard, keycode, text, modifiers):
178 key = self.find_by_key_code(keycode)
179 if key is not None:
180 threading.Thread(name = "MSKeyAction", target=key.do_actions).start()
181 return True
182
183 def find_by_key_code(self, key_code):
184 if "Key_" + str(key_code[0]) in self.ids:
185 return self.ids["Key_" + str(key_code[0])]
186 return None
187
188 def find_by_unicode(self, key_sym):
189 for key in self.children:
190 if not type(key).__name__ == "Key":
191 continue
192 print(key.key_sym, key_sym)
193 if key.key_sym == key_sym:
194 print("found")
195 return key
196 return None
197
198 def stop_all_running(self):
199 self.running = []
200
201 def start_running(self, key, start_time):
202 self.running.append((key, start_time))
203
204 def keep_running(self, key, start_time):
205 return (key, start_time) in self.running
206
207 def finished_running(self, key, start_time):
208 if (key, start_time) in self.running:
209 self.running.remove((key, start_time))
210
211
212class Screen(FloatLayout):
213 pass
214
215class MusicSamplerApp(App):
216 def build(self):
217 Window.size = (913, 563)
218
219 return Screen()
220
221if __name__ == '__main__':
222 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 @@
1#:import math math
2
3<Key>:
4 pad_col_sep: 0 if not self.pad_cols else self.parent.pad_x
5 pad_cols: False
6
7 y: (self.parent.top-self.parent.y) - (self.row) * self.parent.key_size - (self.row - 1) * self.parent.key_sep
8 x: (self.col - 1) * self.parent.key_size + int(self.col - 1) * self.parent.key_sep + self.pad_col_sep
9 size_hint: None, None
10 line_color: 120/255, 120/255, 120/255, 1
11 enabled: True
12 line_width: 2
13 row: 1
14 col: 0
15 key_code: 0
16 key_sym: ""
17 key_width: 1
18 key_height: 1
19 width: self.key_width * (self.parent.key_size + self.parent.key_sep) - self.parent.key_sep
20 height: self.key_height * (self.parent.key_size + self.parent.key_sep) - self.parent.key_sep
21 canvas.before:
22 Color:
23 rgba: self.color
24 RoundedRectangle:
25 pos: self.x, self.y
26 size: self.size
27 canvas:
28 Color:
29 rgba: self.line_color
30 Line:
31 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
32 width: self.line_width
33 Label:
34 id: key_label
35 font_name: "fonts/Ubuntu-B.ttf"
36 font_size: math.ceil(2 * math.sqrt(self.parent.parent.key_size))
37 color: 0, 0, 0, 1
38 text: self.parent.key_sym
39 text_size: self.parent.width,self.font_size
40 shorten: True
41 shorten_from: "right"
42 split_str: ""
43 center_x: self.parent.x + self.texture_size[0] /2 + 5
44 center_y: self.parent.y + self.parent.height - self.texture_size[1] /2 - 5
45 Label:
46 id: key_description_title
47 font_name: "fonts/Ubuntu-Regular.ttf"
48 font_size: math.ceil(2 * math.sqrt(self.parent.parent.key_size / 2))
49 color: 0, 0, 0, 1
50 text: self.parent.description_title
51 text_size: self.parent.width - 2*self.parent.line_width, self.font_size
52 halign: "right"
53 valign: "middle"
54 center_x: self.parent.x + self.texture_size[0] /2
55 center_y: self.parent.y + self.parent.height - self.texture_size[1] /2 - 5
56 Label:
57 id: key_description
58 font_name: "fonts/Ubuntu-Regular.ttf"
59 font_size: math.ceil(2 * math.sqrt(self.parent.parent.key_size / 2))
60 color: 0, 0, 0, 1
61 text: "\n".join(self.parent.description)
62 text_size: 2 * self.parent.width,self.parent.height - key_label.font_size
63 halign: "left"
64 valign: "middle"
65 pos: self.parent.x + 2 * self.parent.line_width + 2, self.parent.y
66 size_hint: None, None
67 size: 2 * self.parent.width - 2 * self.parent.line_width, self.parent.height - key_label.font_size
68
69<Screen>:
70 key_size: int( (3 * self.width - 16) / 56)
71 key_sep: int( self.key_size / 24)
72 key_pad_sep: int( self.key_size / 7) + 1
73
74 border: (self.width - self.key_size * 18 - self.key_sep * 16 - self.key_pad_sep)/ 2
75
76 mapping_height: self.key_size * 6 + self.key_sep * 5
77 mapping_width: self.key_size * 18 + self.key_sep * 16 + self.key_pad_sep
78 mapping_x: self.border
79 mapping_y: self.top - self.mapping_height - self.border
80 action_list_height: self.height - self.mapping_height - 3 * self.border
81 action_list_width: 3 * self.width / 4
82 action_list_x: self.border
83 action_list_y: self.border
84 play_list_height: self.action_list_height
85 play_list_width: self.width - self.action_list_width - 3* self.border
86 play_list_y: self.border
87 play_list_x: self.action_list_width + 2 * self.border
88
89 Mapping:
90 id: Mapping
91 pos: self.parent.mapping_x, self.parent.mapping_y
92 size: self.parent.mapping_width, self.parent.mapping_height
93
94 key_size: self.parent.key_size
95 key_sep: self.parent.key_sep
96 key_pad_sep: self.parent.key_pad_sep
97 pad_x: self.key_size * 15 + 14 * self.key_sep + self.key_pad_sep
98 ActionList:
99 id: ActionList
100 pos: self.parent.action_list_x, self.parent.action_list_y
101 size: self.parent.action_list_width, self.parent.action_list_height
102 PlayList:
103 id: PlayList
104 pos: self.parent.play_list_x, self.parent.play_list_y
105 size: self.parent.play_list_width, self.parent.play_list_height
106
107<ActionList>:
108 size_hint: None, None
109 canvas:
110 Color:
111 rgba: 250./255, 250./255, 250./255, 1
112 Rectangle:
113 pos: 0, 0
114 size: self.width, self.height
115 Label:
116 id: action_list_title
117 font_name: "fonts/Ubuntu-B.ttf"
118 font_size: math.ceil(2 * math.sqrt(self.parent.parent.key_size or 10))
119 color: 0, 0, 0, 1
120 text: self.parent.action_title
121 text_size: None, self.parent.height
122 halign: "left"
123 valign: "top"
124 size_hint: None, None
125 size: self.texture_size[0], self.parent.height
126 Label:
127 id: action_list_icons
128 font_name: "fonts/Symbola.ttf"
129 font_size: math.ceil(2 * math.sqrt(self.parent.parent.key_size or 10))
130 line_height: 1.2 # FIXME: Donner la bonne taille de label
131 color: 0, 0, 0, 1
132 text: "\n".join(map(lambda x: x[0], self.parent.action_list))
133 text_size: None, self.parent.height
134 halign: "left"
135 valign: "top"
136 size_hint: None, None
137 size: self.texture_size[0], self.parent.height - 3 * self.line_height * self.font_size
138 Label:
139 id: action_list_names
140 font_name: "fonts/Ubuntu-Regular.ttf"
141 font_size: math.ceil(2 * math.sqrt(self.parent.parent.key_size or 10))
142 color: 0, 0, 0, 1
143 text: "\n".join(map(lambda x: x[1], self.parent.action_list))
144 text_size: None, self.parent.height
145 halign: "left"
146 valign: "top"
147 size_hint: None, None
148 pos: 15, self.y
149 size: self.texture_size[0], self.parent.height - 3 * self.line_height * self.font_size
150
151<PlayList>:
152 size_hint: None, None
153 canvas:
154 Color:
155 rgba: 250./255, 250./255, 250./255, 1
156 Rectangle:
157 pos: 0, 0
158 size: self.width, self.height
159 Label:
160 id: playlist_icons
161 font_name: "fonts/Symbola.ttf"
162 font_size: math.ceil(2 * math.sqrt(self.parent.parent.key_size or 10))
163 line_height: 1.2 # FIXME: Donner la bonne taille de label
164 color: 0, 0, 0, 1
165 text: "\n".join(map(lambda x: x[0], self.parent.playlist))
166 text_size: None, self.parent.height
167 halign: "left"
168 valign: "top"
169 size_hint: None, None
170 size: self.texture_size[0], self.parent.height
171 Label:
172 id: playlist_names
173 font_name: "fonts/Ubuntu-Regular.ttf" # FIXME: Mettre en gras quand c'est en cours
174 font_size: math.ceil(2 * math.sqrt(self.parent.parent.key_size or 10))
175 color: 0, 0, 0, 1
176 text: "\n".join(map(lambda x: x[1], self.parent.playlist))
177 text_size: None, self.parent.height
178 halign: "left"
179 valign: "top"
180 size_hint: None, None
181 pos: 15, self.y
182 size: self.texture_size[0], self.parent.height
183
184<Mapping>:
185 size_hint: None, None
186 key_size: 48
187 key_sep: 2
188 key_pad_sep: 7
189 pad_x: 755
190 canvas:
191 Color:
192 rgba: 250./255, 250./255, 250./255, 1
193 Rectangle:
194 pos: 0, 0
195 size: self.width, self.height
196 Key:
197 id: Key_27
198 key_code: 27
199 key_sym: "ESC"
200 row: 1
201 col: 1
202 Key:
203 id: Key_282
204 key_code: 282
205 key_sym: "F1"
206 row: 1
207 col: 3
208 Key:
209 id: Key_283
210 key_code: 283
211 key_sym: "F2"
212 row: 1
213 col: 4
214 Key:
215 id: Key_284
216 key_code: 284
217 key_sym: "F3"
218 row: 1
219 col: 5
220 Key:
221 id: Key_285
222 key_code: 285
223 key_sym: "F4"
224 row: 1
225 col: 6
226
227 Key:
228 id: Key_286
229 key_code: 286
230 key_sym: "F5"
231 row: 1
232 col: 7.5
233 Key:
234 id: Key_287
235 key_code: 287
236 key_sym: "F6"
237 row: 1
238 col: 8.5
239 Key:
240 id: Key_288
241 key_code: 288
242 key_sym: "F7"
243 row: 1
244 col: 9.5
245 Key:
246 id: Key_289
247 key_code: 289
248 key_sym: "F8"
249 row: 1
250 col: 10.5
251
252 Key:
253 id: Key_290
254 key_code: 290
255 key_sym: "F9"
256 row: 1
257 col: 12
258 Key:
259 id: Key_291
260 key_code: 291
261 key_sym: "F10"
262 row: 1
263 col: 13
264 Key:
265 id: Key_292
266 key_code: 292
267 key_sym: "F11"
268 row: 1
269 col: 14
270 Key:
271 id: Key_293
272 key_code: 293
273 key_sym: "F12"
274 row: 1
275 col: 15
276
277 Key:
278 id: Key_178
279 key_code: 178
280 key_sym: "²"
281 row: 2
282 col: 1
283 Key:
284 id: Key_38
285 key_code: 38
286 key_sym: "&"
287 row: 2
288 col: 2
289 Key:
290 id: Key_233
291 key_code: 233
292 key_sym: "é"
293 row: 2
294 col: 3
295 Key:
296 id: Key_34
297 key_code: 34
298 key_sym: '"'
299 row: 2
300 col: 4
301 Key:
302 id: Key_39
303 key_code: 39
304 key_sym: "'"
305 row: 2
306 col: 5
307 Key:
308 id: Key_40
309 key_code: 40
310 key_sym: "("
311 row: 2
312 col: 6
313 Key:
314 id: Key_45
315 key_code: 45
316 key_sym: "-"
317 row: 2
318 col: 7
319 Key:
320 id: Key_232
321 key_code: 232
322 key_sym: "è"
323 row: 2
324 col: 8
325 Key:
326 id: Key_95
327 key_code: 95
328 key_sym: "_"
329 row: 2
330 col: 9
331 Key:
332 id: Key_231
333 key_code: 231
334 key_sym: "ç"
335 row: 2
336 col: 10
337 Key:
338 id: Key_224
339 key_code: 224
340 key_sym: "à"
341 row: 2
342 col: 11
343 Key:
344 id: Key_41
345 key_code: 41
346 key_sym: ")"
347 row: 2
348 col: 12
349 Key:
350 id: Key_61
351 key_code: 61
352 key_sym: "="
353 row: 2
354 col: 13
355 Key:
356 id: Key_8
357 key_code: 8
358 key_sym: "<-"
359 row: 2
360 col: 14
361 key_width: 2
362 Key:
363 id: Key_9
364 key_code: 9
365 key_sym: "tab"
366 row: 3
367 col: 1
368 key_width: 1.48
369 Key:
370 id: Key_97
371 key_code: 97
372 key_sym: "a"
373 row: 3
374 col: 2.5
375 Key:
376 id: Key_122
377 key_code: 122
378 key_sym: "z"
379 row: 3
380 col: 3.5
381 Key:
382 id: Key_101
383 key_code: 101
384 key_sym: "e"
385 row: 3
386 col: 4.5
387 Key:
388 id: Key_114
389 key_code: 114
390 key_sym: "r"
391 row: 3
392 col: 5.5
393 Key:
394 id: Key_116
395 key_code: 116
396 key_sym: "t"
397 row: 3
398 col: 6.5
399 Key:
400 id: Key_121
401 key_code: 121
402 key_sym: "y"
403 row: 3
404 col: 7.5
405 Key:
406 id: Key_117
407 key_code: 117
408 key_sym: "u"
409 row: 3
410 col: 8.5
411 Key:
412 id: Key_105
413 key_code: 105
414 key_sym: "i"
415 row: 3
416 col: 9.5
417 Key:
418 id: Key_111
419 key_code: 111
420 key_sym: "o"
421 row: 3
422 col: 10.5
423 Key:
424 id: Key_112
425 key_code: 112
426 key_sym: "p"
427 row: 3
428 col: 11.5
429 Key:
430 id: Key_94
431 key_code: 94
432 key_sym: "^"
433 row: 3
434 col: 12.5
435 Key:
436 id: Key_36
437 key_code: 36
438 key_sym: "$"
439 row: 3
440 col: 13.5
441 Key:
442 id: Key_13
443 key_code: 13
444 key_sym: "Enter"
445 row: 4
446 col: 14.8
447 key_width: 1.23
448 key_height: 2
449 Key:
450 id: Key_301
451 key_code: 301
452 key_sym: "CAPS"
453 row: 4
454 col: 1
455 key_width: 1.75
456 line_width: 1
457 enabled: False
458
459 Key:
460 id: Key_113
461 key_code: 113
462 key_sym: "q"
463 row: 4
464 col: 2.8
465 Key:
466 id: Key_115
467 key_code: 115
468 key_sym: "s"
469 row: 4
470 col: 3.8
471 Key:
472 id: Key_100
473 key_code: 100
474 key_sym: "d"
475 row: 4
476 col: 4.8
477 Key:
478 id: Key_102
479 key_code: 102
480 key_sym: "f"
481 row: 4
482 col: 5.8
483 Key:
484 id: Key_103
485 key_code: 103
486 key_sym: "g"
487 row: 4
488 col: 6.8
489 Key:
490 id: Key_104
491 key_code: 104
492 key_sym: "h"
493 row: 4
494 col: 7.8
495 Key:
496 id: Key_106
497 key_code: 106
498 key_sym: "j"
499 row: 4
500 col: 8.8
501 Key:
502 id: Key_107
503 key_code: 107
504 key_sym: "k"
505 row: 4
506 col: 9.8
507 Key:
508 id: Key_108
509 key_code: 108
510 key_sym: "l"
511 row: 4
512 col: 10.8
513 Key:
514 id: Key_109
515 key_code: 109
516 key_sym: "m"
517 row: 4
518 col: 11.8
519 Key:
520 id: Key_249
521 key_code: 249
522 key_sym: "ù"
523 row: 4
524 col: 12.8
525 Key:
526 id: Key_42
527 key_code: 42
528 key_sym: "*"
529 row: 4
530 col: 13.8
531 Key:
532 id: Key_304
533 key_code: 304
534 key_sym: "LShift"
535 row: 5
536 col: 1
537 key_width: 1.3
538 line_width: 1
539 enabled: False
540 Key:
541 id: Key_60
542 key_code: 60
543 key_sym: "<"
544 row: 5
545 col: 2.3
546 Key:
547 id: Key_119
548 key_code: 119
549 key_sym: "w"
550 row: 5
551 col: 3.3
552 Key:
553 id: Key_120
554 key_code: 120
555 key_sym: "x"
556 row: 5
557 col: 4.3
558 Key:
559 id: Key_99
560 key_code: 99
561 key_sym: "c"
562 row: 5
563 col: 5.3
564 Key:
565 id: Key_118
566 key_code: 118
567 key_sym: "v"
568 row: 5
569 col: 6.3
570 Key:
571 id: Key_98
572 key_code: 98
573 key_sym: "b"
574 row: 5
575 col: 7.3
576 Key:
577 id: Key_110
578 key_code: 110
579 key_sym: "n"
580 row: 5
581 col: 8.3
582 Key:
583 id: Key_44
584 key_code: 44
585 key_sym: ","
586 row: 5
587 col: 9.3
588 Key:
589 id: Key_59
590 key_code: 59
591 key_sym: ";"
592 row: 5
593 col: 10.3
594 Key:
595 id: Key_58
596 key_code: 58
597 key_sym: ":"
598 row: 5
599 col: 11.3
600 Key:
601 id: Key_33
602 key_code: 33
603 key_sym: "!"
604 row: 5
605 col: 12.3
606 Key:
607 id: Key_303
608 key_code: 303
609 key_sym: "RShift"
610 row: 5
611 col: 13.3
612 key_width: 2.7
613 line_width: 1
614 enabled: False
615 Key:
616 id: Key_306
617 key_code: 306
618 key_sym: "LCtrl"
619 row: 6
620 col: 1
621 key_width: 1.3
622 line_width: 1
623 enabled: False
624 Key:
625 id: Key_311
626 key_code: 311
627 key_sym: "LSuper"
628 row: 6
629 col: 3.3
630 line_width: 1
631 enabled: False
632 Key:
633 id: Key_308
634 key_code: 308
635 key_sym: "LAlt"
636 row: 6
637 col: 4.3
638 line_width: 1
639 enabled: False
640 Key:
641 id: Key_32
642 key_code: 32
643 key_sym: "Espace"
644 row: 6
645 col: 5.3
646 key_width: 5
647 Key:
648 id: Key_313
649 key_code: 313
650 key_sym: "AltGr"
651 row: 6
652 col: 10.3
653 line_width: 1
654 enabled: False
655 Key:
656 id: Key_314
657 key_code: 314
658 key_sym: "Compose"
659 row: 6
660 col: 11.3
661 line_width: 1
662 enabled: False
663 Key:
664 id: Key_305
665 key_code: 305
666 key_sym: "RCtrl"
667 row: 6
668 col: 12.3
669 key_width: 1.3
670 line_width: 1
671 enabled: False
672
673
674 Key:
675 id: Key_277
676 key_code: 277
677 key_sym: "ins"
678 row: 2
679 col: 1
680 pad_cols: True
681 Key:
682 id: Key_278
683 key_code: 278
684 key_sym: "home"
685 row: 2
686 col: 2
687 pad_cols: True
688 Key:
689 id: Key_280
690 key_code: 280
691 key_sym: "pg_u"
692 row: 2
693 col: 3
694 pad_cols: True
695 Key:
696 id: Key_127
697 key_code: 127
698 key_sym: "del"
699 row: 3
700 col: 1
701 pad_cols: True
702 Key:
703 id: Key_279
704 key_code: 279
705 key_sym: "end"
706 row: 3
707 col: 2
708 pad_cols: True
709 Key:
710 id: Key_281
711 key_code: 281
712 key_sym: "pg_d"
713 row: 3
714 col: 3
715 pad_cols: True
716 Key:
717 id: Key_273
718 key_code: 273
719 key_sym: "up"
720 row: 5
721 col: 2
722 pad_cols: True
723 Key:
724 id: Key_274
725 key_code: 274
726 key_sym: "down"
727 row: 6
728 col: 2
729 pad_cols: True
730 Key:
731 id: Key_276
732 key_code: 276
733 key_sym: "left"
734 row: 6
735 col: 1
736 pad_cols: True
737 Key:
738 id: Key_275
739 key_code: 275
740 key_sym: "right"
741 row: 6
742 col: 3
743 pad_cols: True