rum 0.0.1-universal-darwin-10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. data/CHANGELOG +3 -0
  2. data/README +30 -0
  3. data/Rakefile +134 -0
  4. data/bin/rum-client +124 -0
  5. data/doc/basic.rb +10 -0
  6. data/doc/doc.html +602 -0
  7. data/doc/example.rb +59 -0
  8. data/doc/reference.rb +415 -0
  9. data/doc/resources/bg.png +0 -0
  10. data/doc/resources/bottom.png +0 -0
  11. data/doc/resources/build.rb +235 -0
  12. data/doc/resources/doc.haml +167 -0
  13. data/doc/resources/emacs-auto-completion.png +0 -0
  14. data/doc/resources/flash.png +0 -0
  15. data/doc/resources/highlight.css +94 -0
  16. data/doc/resources/intro.rb +17 -0
  17. data/doc/resources/left.png +0 -0
  18. data/doc/resources/logo.png +0 -0
  19. data/doc/resources/screen.css +420 -0
  20. data/doc/resources/screenshot.png +0 -0
  21. data/doc/resources/top.png +0 -0
  22. data/ext/mac/keyboard_hook/English.lproj/InfoPlist.strings +0 -0
  23. data/ext/mac/keyboard_hook/Event.h +17 -0
  24. data/ext/mac/keyboard_hook/Event.m +18 -0
  25. data/ext/mac/keyboard_hook/EventTap.h +11 -0
  26. data/ext/mac/keyboard_hook/EventTap.m +77 -0
  27. data/ext/mac/keyboard_hook/Info.plist +26 -0
  28. data/ext/mac/keyboard_hook/KeyboardHook.xcodeproj/TemplateIcon.icns +0 -0
  29. data/ext/mac/keyboard_hook/KeyboardHook.xcodeproj/project.pbxproj +323 -0
  30. data/ext/mac/keyboard_hook/KeyboardHook_Prefix.pch +7 -0
  31. data/ext/mac/keyboard_hook/version.plist +16 -0
  32. data/ext/windows/keyboard_hook/extconf.rb +2 -0
  33. data/ext/windows/keyboard_hook/keyboard_hook.c +126 -0
  34. data/ext/windows/system/autohotkey_stuff.c +255 -0
  35. data/ext/windows/system/autohotkey_stuff.h +2 -0
  36. data/ext/windows/system/clipboard_watcher.c +58 -0
  37. data/ext/windows/system/clipboard_watcher.h +2 -0
  38. data/ext/windows/system/extconf.rb +3 -0
  39. data/ext/windows/system/input_box.c +239 -0
  40. data/ext/windows/system/input_box.h +4 -0
  41. data/ext/windows/system/system.c +273 -0
  42. data/lib/rum.rb +4 -0
  43. data/lib/rum/apps.rb +4 -0
  44. data/lib/rum/barrel.rb +157 -0
  45. data/lib/rum/barrel/emacs.rb +44 -0
  46. data/lib/rum/barrel/emacs_client.rb +74 -0
  47. data/lib/rum/core.rb +125 -0
  48. data/lib/rum/dsl.rb +109 -0
  49. data/lib/rum/gui.rb +93 -0
  50. data/lib/rum/help.rb +128 -0
  51. data/lib/rum/hotkey_core.rb +479 -0
  52. data/lib/rum/mac.rb +18 -0
  53. data/lib/rum/mac/app.rb +4 -0
  54. data/lib/rum/mac/apps.rb +19 -0
  55. data/lib/rum/mac/gui.rb +26 -0
  56. data/lib/rum/mac/gui/CocoaDialog.app/Contents/Info.plist +28 -0
  57. data/lib/rum/mac/gui/CocoaDialog.app/Contents/MacOS/CocoaDialog +0 -0
  58. data/lib/rum/mac/gui/CocoaDialog.app/Contents/Resources/Info.plist +28 -0
  59. data/lib/rum/mac/gui/CocoaDialog.app/Contents/Resources/InfoPlist.strings +0 -0
  60. data/lib/rum/mac/gui/CocoaDialog.app/Contents/Resources/Inputbox.nib/classes.nib +51 -0
  61. data/lib/rum/mac/gui/CocoaDialog.app/Contents/Resources/Inputbox.nib/info.nib +16 -0
  62. data/lib/rum/mac/gui/CocoaDialog.app/Contents/Resources/Inputbox.nib/keyedobjects.nib +0 -0
  63. data/lib/rum/mac/gui/CocoaDialog.app/Contents/Resources/MainMenu.nib/classes.nib +7 -0
  64. data/lib/rum/mac/gui/CocoaDialog.app/Contents/Resources/MainMenu.nib/info.nib +21 -0
  65. data/lib/rum/mac/gui/CocoaDialog.app/Contents/Resources/MainMenu.nib/info.nib.orig +21 -0
  66. data/lib/rum/mac/gui/CocoaDialog.app/Contents/Resources/MainMenu.nib/objects.nib +0 -0
  67. data/lib/rum/mac/gui/CocoaDialog.app/Contents/Resources/MainMenu.nib/objects.nib.orig +0 -0
  68. data/lib/rum/mac/gui/CocoaDialog.app/Contents/Resources/Msgbox.nib/classes.nib +27 -0
  69. data/lib/rum/mac/gui/CocoaDialog.app/Contents/Resources/Msgbox.nib/info.nib +16 -0
  70. data/lib/rum/mac/gui/CocoaDialog.app/Contents/Resources/Msgbox.nib/keyedobjects.nib +0 -0
  71. data/lib/rum/mac/gui/Growl.framework/Growl +0 -0
  72. data/lib/rum/mac/gui/Growl.framework/Versions/A/Growl +0 -0
  73. data/lib/rum/mac/gui/Growl.framework/Versions/A/Headers/Growl.h +6 -0
  74. data/lib/rum/mac/gui/Growl.framework/Versions/A/Headers/GrowlApplicationBridge-Carbon.h +780 -0
  75. data/lib/rum/mac/gui/Growl.framework/Versions/A/Headers/GrowlApplicationBridge.h +575 -0
  76. data/lib/rum/mac/gui/Growl.framework/Versions/A/Headers/GrowlDefines.h +348 -0
  77. data/lib/rum/mac/gui/Growl.framework/Versions/A/Resources/Info.plist +24 -0
  78. data/lib/rum/mac/gui/growl.rb +54 -0
  79. data/lib/rum/mac/irb/completion.rb +207 -0
  80. data/lib/rum/mac/keyboard_hook.rb +73 -0
  81. data/lib/rum/mac/keyboard_hook/KeyboardHook.framework/KeyboardHook +0 -0
  82. data/lib/rum/mac/keyboard_hook/KeyboardHook.framework/Versions/A/KeyboardHook +0 -0
  83. data/lib/rum/mac/keyboard_hook/KeyboardHook.framework/Versions/A/Resources/English.lproj/InfoPlist.strings +0 -0
  84. data/lib/rum/mac/keyboard_hook/KeyboardHook.framework/Versions/A/Resources/Info.plist +22 -0
  85. data/lib/rum/mac/layouts.rb +146 -0
  86. data/lib/rum/mac/system.rb +45 -0
  87. data/lib/rum/remote.rb +48 -0
  88. data/lib/rum/server.rb +92 -0
  89. data/lib/rum/windows.rb +23 -0
  90. data/lib/rum/windows/app.rb +72 -0
  91. data/lib/rum/windows/apps.rb +25 -0
  92. data/lib/rum/windows/gui.rb +116 -0
  93. data/lib/rum/windows/keyboard.rb +80 -0
  94. data/lib/rum/windows/keyboard_hook.rb +20 -0
  95. data/lib/rum/windows/layouts.rb +232 -0
  96. data/lib/rum/windows/system.rb +310 -0
  97. data/lib/rum/windows/system_foreign_functions.rb +129 -0
  98. data/rum.gemspec +14 -0
  99. metadata +166 -0
@@ -0,0 +1,72 @@
1
+ module Rum
2
+ class App
3
+ include System
4
+
5
+ @@apps = {}
6
+
7
+ def self.add(app)
8
+ @@apps[app.binary] = app
9
+ end
10
+
11
+ def self.remove(app)
12
+ @@apps.delete(app.binary)
13
+ end
14
+
15
+ def self.apps
16
+ @@apps
17
+ end
18
+
19
+ def self.for_exe(exe)
20
+ @@apps[exe.downcase]
21
+ end
22
+
23
+ attr_accessor :path, :binary, :matcher
24
+
25
+ def initialize(path, *matcher)
26
+ @path = append_path_to_programfiles_if_relative(path)
27
+ @binary = File.basename(@path).sub(/\.exe$/, '').downcase
28
+ @matcher = WindowMatcher.new(*matcher)
29
+ App.add(self)
30
+ end
31
+
32
+ def append_path_to_programfiles_if_relative(path)
33
+ if path !~ /^\w:|^%/
34
+ path = File.join(ENV['PROGRAMFILES'].gsub("\\", '/'), path)
35
+ end
36
+ path
37
+ end
38
+
39
+ def binary=(bin)
40
+ App.remove(self)
41
+ @binary = bin.downcase
42
+ App.add(self)
43
+ end
44
+
45
+ # Returns 'true' if the application window could be activated instantly.
46
+ def activate
47
+ window = @matcher.find
48
+ if window
49
+ window.show
50
+ else
51
+ run
52
+ false
53
+ end
54
+ end
55
+
56
+ def run
57
+ start @path
58
+ end
59
+
60
+ def to_matcher
61
+ @matcher
62
+ end
63
+
64
+ def active?
65
+ @matcher.active?
66
+ end
67
+
68
+ def window
69
+ @matcher.find
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,25 @@
1
+ # encoding: utf-8
2
+ Emacs = App.new('', nil, 'Emacs')
3
+ Emacs.binary = 'emacs'
4
+ require 'rum/barrel/emacs'
5
+
6
+ Firefox = App.new('mozilla firefox/firefox', /firefox/, 'MozillaWindowClass')
7
+ def Firefox.activate_and_focus_address_bar
8
+ Keyboard.type '(ctrl l)' if activate
9
+ end
10
+
11
+ Photoshop = App.new('Adobe/Adobe Photoshop CS5/Photoshop', nil, 'Photoshop')
12
+ class << Photoshop
13
+ def next_blend_mode
14
+ if Rum.layout['ö'] # german layout
15
+ type '(shift 0)'
16
+ else
17
+ type '(shift +)'
18
+ end
19
+ end
20
+
21
+ def previous_blend_mode
22
+ type '(shift -)'
23
+ end
24
+ end
25
+
@@ -0,0 +1,116 @@
1
+ module Rum
2
+ module Gui
3
+ def goto path
4
+ if File.directory? path
5
+ System.start path
6
+ else
7
+ Gui.message "Can't visit a file in Windows Explorer."
8
+ end
9
+ end
10
+
11
+ def open_file file, line=nil
12
+ System.start file
13
+ end
14
+
15
+ private
16
+
17
+ def browse_backend url
18
+ System.start url
19
+ end
20
+
21
+ module WindowsDialogs
22
+ private
23
+ def alert_backend text, title
24
+ System.message_box text, title
25
+ end
26
+
27
+ def message_backend text, title, sticky, callback
28
+ alert text, title
29
+ end
30
+
31
+ def read_backend text, title, initial_value
32
+ System.input_box text, title, initial_value
33
+ end
34
+ end
35
+
36
+ use WindowsDialogs
37
+
38
+ module Growl
39
+ Message = 'message'
40
+
41
+ private
42
+
43
+ def message_backend(text, title, sticky, callback)
44
+ unless title
45
+ title = text
46
+ text = nil
47
+ end
48
+ # ruby_gntp raises an error when 'title' is nil or pure whitespace.
49
+ title = 'empty text' if title.to_s.strip.empty?
50
+ if callback
51
+ on_click = lambda { |event| callback.call if event[:callback_result] == "CLICK" }
52
+ end
53
+
54
+ Growl.client.notify(name: Message, title: title,
55
+ text: text, sticky: sticky, &on_click)
56
+ end
57
+
58
+ module_function
59
+
60
+ def auto_setup
61
+ require 'ruby_gntp'
62
+ @growl = GNTP.new("Rum")
63
+ @growl.register(notifications: [{name: Message, enabled: true}])
64
+ true
65
+ rescue Errno::ECONNREFUSED # Growl not running
66
+ @growl = nil
67
+ end
68
+
69
+ def client
70
+ @growl or auto_setup and @growl
71
+ end
72
+ end
73
+
74
+ module EmacsInteraction
75
+ private
76
+
77
+ def interaction(*args)
78
+ emacs = Emacs.window
79
+ return '' unless emacs
80
+ unless (emacs_already_active = emacs.active?)
81
+ old = System.active_window
82
+ emacs.move(400, 400, 800, 300)
83
+ emacs.show
84
+ end
85
+ result = unpack(Emacs.funcall(*args))
86
+ unless emacs_already_active
87
+ wait { old.active? } unless old.show
88
+ emacs.maximize
89
+ end
90
+ result
91
+ end
92
+
93
+ def unpack(output)
94
+ # If we don't get a string back something has gone wrong.
95
+ raise 'emacs returned no string: ' << output unless output[0] == ?"
96
+ # The first line contains the status (1 or 0),
97
+ # the second line contains additional output.
98
+ # See telemacs-format-output.
99
+ output = Emacs.unquote(output)
100
+ if output =~ /\n$/
101
+ ''
102
+ else
103
+ output = output.split(/\n/)
104
+ output[1] or (output[0] == '1' ? true : nil)
105
+ end
106
+ end
107
+
108
+ def choose_backend prompt, choices
109
+ prompt = prompt ? Emacs.quote(prompt) : 'nil'
110
+ fn = 'selekt-external-fast'
111
+ result = interaction(fn, prompt, *choices.map { |item| Emacs.quote(item.to_s) })
112
+ choices[result.to_i] if result
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,80 @@
1
+ require 'strscan'
2
+
3
+ module Rum
4
+ module Keyboard
5
+ extend self
6
+
7
+ def type(key_sequence, *args)
8
+ release_core_modifiers unless args.include? :blind
9
+ type_sequence(key_sequence, args.include?(:slow))
10
+ end
11
+
12
+ def type!(key_sequence, *args)
13
+ release_core_modifiers unless args.include? :blind
14
+ type_sequence_literally(key_sequence, args.include?(:slow))
15
+ end
16
+
17
+ def release_core_modifiers
18
+ pressed = Rum.hotkey_processor.pressed_modifiers
19
+ to_release = Rum.layout.core_modifiers.keys.select { |mod| pressed[mod] }
20
+ to_release.each { |key| System.send_key_event key, false }
21
+ end
22
+
23
+ private
24
+
25
+ def type_sequence_literally(key_sequence, slow)
26
+ key_sequence.chars.each do |char|
27
+ if (key = Rum.layout[char])
28
+ System.send_keypress key
29
+ sleep 0.01 if slow
30
+ elsif (translation = Rum.layout.translations[char])
31
+ type_sequence(translation, slow)
32
+ end
33
+ end
34
+ end
35
+
36
+ def type_sequence(key_sequence, slow)
37
+ s = StringScanner.new(key_sequence)
38
+ pressed_keys = []
39
+ while (char = s.getch)
40
+ case char
41
+ when '\\'
42
+ down_and_up(s.getch, slow)
43
+ when '('
44
+ key = s.scan(/[^() ]+/)
45
+ s.skip /\ /
46
+ down key
47
+ pressed_keys << key
48
+ when ')'
49
+ up pressed_keys.pop
50
+ else
51
+ down_and_up(char, slow)
52
+ end
53
+ sleep 0.01 if slow
54
+ end
55
+ pressed_keys.reverse_each { |key| up key }
56
+ end
57
+
58
+ def send_key(key, down)
59
+ if id = Rum.layout[key]
60
+ System.send_key_event id, down
61
+ end
62
+ end
63
+
64
+ def down(key)
65
+ send_key key, true
66
+ end
67
+
68
+ def up(key)
69
+ send_key key, false
70
+ end
71
+
72
+ def down_and_up(char, slow)
73
+ if (key = Rum.layout[char])
74
+ System.send_keypress key
75
+ elsif (key_sequence = Rum.layout.translations[char])
76
+ type_sequence(key_sequence, slow)
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,20 @@
1
+ module KeyboardHook
2
+ class Event
3
+ attr_reader :id, :scancode
4
+ def initialize(id, scancode, down)
5
+ @id = id
6
+ @scancode = scancode
7
+ @down = down
8
+ end
9
+
10
+ def down?
11
+ @down
12
+ end
13
+
14
+ def to_s
15
+ "#{@down ? 'Down' : 'Up'}: VKCode #@id, Scancode #@scancode"
16
+ end
17
+ end
18
+ end
19
+
20
+ require_relative 'keyboard_hook.so'
@@ -0,0 +1,232 @@
1
+ # encoding: utf-8
2
+
3
+ module Rum
4
+ module Layouts
5
+ module_function
6
+
7
+ def default_layout
8
+ us
9
+ end
10
+
11
+ def core
12
+ # http://msdn.microsoft.com/en-us/library/dd375731%28VS.85%29.aspx
13
+ core = Layout.new
14
+ # 0-9
15
+ 10.times { |i| core.add i.to_s, 48+i }
16
+ # a-z
17
+ 26.times { |i| core.add (97+i).chr, 65+i }
18
+ # f1-f24
19
+ 1.upto(24) { |i| core.add "f#{i}", 111+i }
20
+
21
+ [['lbutton', 0x01],
22
+ ['rbutton', 0x02],
23
+ ['cancel', 0x03],
24
+ ['mbutton', 0x04],
25
+ ['back', 0x08],
26
+ ['tab', 0x09],
27
+ ['clear', 0x0C],
28
+ ['return', "\n", 0x0D],
29
+ ['shift', 0x10],
30
+ ['control', 0x11],
31
+ ['menu', 0x12],
32
+ ['pause', 0x13],
33
+ ['capital', 0x14],
34
+ ['hangul', 0x15],
35
+ ['junja', 0x17],
36
+ ['final', 0x18],
37
+ ['hanja', 0x19],
38
+ ['kanji', 0x19],
39
+ ['escape', 0x1B],
40
+ ['convert', 0x1C],
41
+ ['nonconvert', 0x1D],
42
+ ['accept', 0x1E],
43
+ ['modechange', 0x1F],
44
+ ['space', ' ', 0x20],
45
+ ['prior', 0x21],
46
+ ['next', 0x22],
47
+ ['end', 0x23],
48
+ ['home', 0x24],
49
+ ['left', 0x25],
50
+ ['up', 0x26],
51
+ ['right', 0x27],
52
+ ['down', 0x28],
53
+ ['select', 0x29],
54
+ ['print', 0x2A],
55
+ ['execute', 0x2B],
56
+ ['snapshot', 0x2C],
57
+ ['insert', 0x2D],
58
+ ['delete', 0x2E],
59
+ ['help', 0x2F],
60
+ ['lwin', 0x5B],
61
+ ['rwin', 0x5C],
62
+ ['apps', 0x5D],
63
+ ['numpad0', 0x60],
64
+ ['numpad1', 0x61],
65
+ ['numpad2', 0x62],
66
+ ['numpad3', 0x63],
67
+ ['numpad4', 0x64],
68
+ ['numpad5', 0x65],
69
+ ['numpad6', 0x66],
70
+ ['numpad7', 0x67],
71
+ ['numpad8', 0x68],
72
+ ['numpad9', 0x69],
73
+ ['multiply', 0x6A],
74
+ ['add', 0x6B],
75
+ ['separator', 0x6C],
76
+ ['subtract', 0x6D],
77
+ ['decimal', 0x6E],
78
+ ['divide', 0x6F],
79
+ ['numlock', 0x90],
80
+ ['scroll', 0x91],
81
+ ['lshift', 0xA0],
82
+ ['rshift', 0xA1],
83
+ ['lcontrol', 0xA2],
84
+ ['rcontrol', 0xA3],
85
+ ['lmenu', 0xA4],
86
+ ['rmenu', 0xA5],
87
+ ['processkey', 0xE5],
88
+ ['attn', 0xF6],
89
+ ['crsel', 0xF7],
90
+ ['exsel', 0xF8],
91
+ ['ereof', 0xF9],
92
+ ['play', 0xFA],
93
+ ['zoom', 0xFB],
94
+ ['noname', 0xFC],
95
+ ['pa1', 0xFD],
96
+ ['oem_clear', 0xFE],
97
+ ['browser_back', 0xA6],
98
+ ['browser_forward', 0xA7],
99
+ ['browser_refresh', 0xA8],
100
+ ['browser_stop', 0xA9],
101
+ ['browser_search', 0xAA],
102
+ ['browser_favorites', 0xAB],
103
+ ['browser_home', 0xAC],
104
+ ['volume_mute', 0xAD],
105
+ ['volume_down', 0xAE],
106
+ ['volume_up', 0xAF],
107
+ ['media_next_track', 0xB0],
108
+ ['media_prev_track', 0xB1],
109
+ ['media_stop', 0xB2],
110
+ ['media_play_pause', 0xB3],
111
+ ['launch_mail', 0xB4],
112
+ ['launch_media_select', 0xB5],
113
+ ['launch_app1', 0xB6],
114
+ ['launch_app2', 0xB7],
115
+ ['oem_plus', 0xBB],
116
+ [',', 'oem_comma', 0xBC],
117
+ ['-', 'oem_minus', 0xBD],
118
+ ['.', 'oem_period', 0xBE],
119
+ ['oem_1', 0xBA],
120
+ ['oem_2', 0xBF],
121
+ ['oem_3', 0xC0],
122
+ ['oem_4', 0xDB],
123
+ ['oem_5', 0xDC],
124
+ ['oem_6', 0xDD],
125
+ ['oem_7', 0xDE],
126
+ ['oem_8', 0xDF],
127
+ ['oem_102', 0xE2],
128
+ ['processkey', 0xE5],
129
+ ['packet', 0xE]].each { |key| core.add *key }
130
+
131
+ core
132
+ end
133
+
134
+ def basic
135
+ basic = core
136
+
137
+ basic.remap 'lcontrol', 'rcontrol', 'control'
138
+ basic.remap 'rmenu', 'lmenu'
139
+ basic.remap 'lshift', 'rshift', 'shift'
140
+ basic.remap 'rwin', 'lwin'
141
+
142
+ basic.alias 'control', 'ctrl'
143
+ basic.alias 'control', 'c'
144
+ basic.rename 'lmenu', 'alt'
145
+ basic.alias 'alt', 'a'
146
+ basic.alias 'shift', 's'
147
+ basic.rename 'lwin', 'win'
148
+ basic.rename 'back', 'backspace'
149
+ basic.rename 'return', 'enter'
150
+ basic.alias 'enter', 'return'
151
+ basic.rename 'capital', 'caps'
152
+ basic.rename 'prior', 'pageup'
153
+ basic.rename 'next', 'pagedown'
154
+ basic.rename 'snapshot', 'print'
155
+
156
+
157
+ %w{shift control alt win}.each { |key| basic.core_modifier key }
158
+
159
+ basic.action_modifier 'alt'
160
+ basic.action_modifier 'win'
161
+
162
+ basic.add :extended, 'alt-extended', basic['alt'].id
163
+ basic.add :extended, 'left-extended', basic['left'].id
164
+ basic.add :extended, 'right-extended', basic['right'].id
165
+
166
+ shift_translations = ('a'..'z').map { |key| [key.upcase, key] }
167
+ shift_translations.each do |from, to|
168
+ basic.translations[from] = "(shift #{to})"
169
+ end
170
+ basic
171
+ end
172
+
173
+ def us
174
+ us = basic
175
+ us.rename 'oem_plus', '='
176
+ us.rename 'oem_1', ';'
177
+ us.rename 'oem_2', '/'
178
+ us.rename 'oem_3', '`'
179
+ us.rename 'oem_4', '['
180
+ us.rename 'oem_5', '\\'
181
+ us.rename 'oem_6', ']'
182
+ us.rename 'oem_7', "'"
183
+ us.rename 'oem_102', '\\'
184
+
185
+ shift_translations = <<END.scan(/(\S) (\S)/)
186
+ ~ ` $ 4 * 8 + = " '
187
+ ! 1 % 5 ( 9 } ] < ,
188
+ @ 2 ^ 6 ) 0 | \ > .
189
+ # 3 & 7 _ - : ; ? /
190
+ END
191
+ shift_translations.each do |from, to|
192
+ us.translations[from] = "(shift #{to})"
193
+ end
194
+ us
195
+ end
196
+
197
+ def german
198
+ german = basic
199
+ german.rename 'oem_plus', '+'
200
+ german.rename 'oem_1', 'ü'
201
+ german.rename 'oem_2', '#'
202
+ german.rename 'oem_3', 'ö'
203
+ german.rename 'oem_4', 'ß'
204
+ german.rename 'oem_5', '^'
205
+ german.rename 'oem_6', '´'
206
+ german.rename 'oem_7', 'ä'
207
+ german.rename 'oem_102', '<'
208
+
209
+ shift_translations = <<END.scan(/(\S) (\S)/)
210
+ ° ^ $ 4 ( 8 ` ´ ; ,
211
+ ! 1 % 5 ) 9 * + : .
212
+ " 2 & 6 = 0 ' # _ -
213
+ § 3 / 7 ? ß > <
214
+ END
215
+ shift_translations.each do |from, to|
216
+ german.translations[from] = "(shift #{to})"
217
+ end
218
+
219
+ altgr_translations = <<END.scan(/(\S) (\S)/)
220
+ ² 2 ] 9 € e
221
+ ³ 3 } 0 ~ +
222
+ { 7 \\ ß | <
223
+ [ 8 @ q µ m
224
+ END
225
+ altgr_translations.each do |from, to|
226
+ german.translations[from] = "(ctrl (alt-extended #{to}))"
227
+ end
228
+
229
+ german
230
+ end
231
+ end
232
+ end