metasm 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. data/BUGS +11 -0
  2. data/CREDITS +17 -0
  3. data/README +270 -0
  4. data/TODO +114 -0
  5. data/doc/code_organisation.txt +146 -0
  6. data/doc/const_missing.txt +16 -0
  7. data/doc/core_classes.txt +75 -0
  8. data/doc/feature_list.txt +53 -0
  9. data/doc/index.txt +59 -0
  10. data/doc/install_notes.txt +170 -0
  11. data/doc/style.css +3 -0
  12. data/doc/use_cases.txt +18 -0
  13. data/lib/metasm.rb +80 -0
  14. data/lib/metasm/arm.rb +12 -0
  15. data/lib/metasm/arm/debug.rb +39 -0
  16. data/lib/metasm/arm/decode.rb +167 -0
  17. data/lib/metasm/arm/encode.rb +77 -0
  18. data/lib/metasm/arm/main.rb +75 -0
  19. data/lib/metasm/arm/opcodes.rb +177 -0
  20. data/lib/metasm/arm/parse.rb +130 -0
  21. data/lib/metasm/arm/render.rb +55 -0
  22. data/lib/metasm/compile_c.rb +1457 -0
  23. data/lib/metasm/dalvik.rb +8 -0
  24. data/lib/metasm/dalvik/decode.rb +196 -0
  25. data/lib/metasm/dalvik/main.rb +60 -0
  26. data/lib/metasm/dalvik/opcodes.rb +366 -0
  27. data/lib/metasm/decode.rb +213 -0
  28. data/lib/metasm/decompile.rb +2659 -0
  29. data/lib/metasm/disassemble.rb +2068 -0
  30. data/lib/metasm/disassemble_api.rb +1280 -0
  31. data/lib/metasm/dynldr.rb +1329 -0
  32. data/lib/metasm/encode.rb +333 -0
  33. data/lib/metasm/exe_format/a_out.rb +194 -0
  34. data/lib/metasm/exe_format/autoexe.rb +82 -0
  35. data/lib/metasm/exe_format/bflt.rb +189 -0
  36. data/lib/metasm/exe_format/coff.rb +455 -0
  37. data/lib/metasm/exe_format/coff_decode.rb +901 -0
  38. data/lib/metasm/exe_format/coff_encode.rb +1078 -0
  39. data/lib/metasm/exe_format/dex.rb +457 -0
  40. data/lib/metasm/exe_format/dol.rb +145 -0
  41. data/lib/metasm/exe_format/elf.rb +923 -0
  42. data/lib/metasm/exe_format/elf_decode.rb +979 -0
  43. data/lib/metasm/exe_format/elf_encode.rb +1375 -0
  44. data/lib/metasm/exe_format/macho.rb +827 -0
  45. data/lib/metasm/exe_format/main.rb +228 -0
  46. data/lib/metasm/exe_format/mz.rb +164 -0
  47. data/lib/metasm/exe_format/nds.rb +172 -0
  48. data/lib/metasm/exe_format/pe.rb +437 -0
  49. data/lib/metasm/exe_format/serialstruct.rb +246 -0
  50. data/lib/metasm/exe_format/shellcode.rb +114 -0
  51. data/lib/metasm/exe_format/xcoff.rb +167 -0
  52. data/lib/metasm/gui.rb +23 -0
  53. data/lib/metasm/gui/cstruct.rb +373 -0
  54. data/lib/metasm/gui/dasm_coverage.rb +199 -0
  55. data/lib/metasm/gui/dasm_decomp.rb +369 -0
  56. data/lib/metasm/gui/dasm_funcgraph.rb +103 -0
  57. data/lib/metasm/gui/dasm_graph.rb +1354 -0
  58. data/lib/metasm/gui/dasm_hex.rb +543 -0
  59. data/lib/metasm/gui/dasm_listing.rb +599 -0
  60. data/lib/metasm/gui/dasm_main.rb +906 -0
  61. data/lib/metasm/gui/dasm_opcodes.rb +291 -0
  62. data/lib/metasm/gui/debug.rb +1228 -0
  63. data/lib/metasm/gui/gtk.rb +884 -0
  64. data/lib/metasm/gui/qt.rb +495 -0
  65. data/lib/metasm/gui/win32.rb +3004 -0
  66. data/lib/metasm/gui/x11.rb +621 -0
  67. data/lib/metasm/ia32.rb +14 -0
  68. data/lib/metasm/ia32/compile_c.rb +1523 -0
  69. data/lib/metasm/ia32/debug.rb +193 -0
  70. data/lib/metasm/ia32/decode.rb +1167 -0
  71. data/lib/metasm/ia32/decompile.rb +564 -0
  72. data/lib/metasm/ia32/encode.rb +314 -0
  73. data/lib/metasm/ia32/main.rb +233 -0
  74. data/lib/metasm/ia32/opcodes.rb +872 -0
  75. data/lib/metasm/ia32/parse.rb +327 -0
  76. data/lib/metasm/ia32/render.rb +91 -0
  77. data/lib/metasm/main.rb +1193 -0
  78. data/lib/metasm/mips.rb +11 -0
  79. data/lib/metasm/mips/compile_c.rb +7 -0
  80. data/lib/metasm/mips/decode.rb +253 -0
  81. data/lib/metasm/mips/encode.rb +51 -0
  82. data/lib/metasm/mips/main.rb +72 -0
  83. data/lib/metasm/mips/opcodes.rb +443 -0
  84. data/lib/metasm/mips/parse.rb +51 -0
  85. data/lib/metasm/mips/render.rb +43 -0
  86. data/lib/metasm/os/gnu_exports.rb +270 -0
  87. data/lib/metasm/os/linux.rb +1112 -0
  88. data/lib/metasm/os/main.rb +1686 -0
  89. data/lib/metasm/os/remote.rb +527 -0
  90. data/lib/metasm/os/windows.rb +2027 -0
  91. data/lib/metasm/os/windows_exports.rb +745 -0
  92. data/lib/metasm/parse.rb +876 -0
  93. data/lib/metasm/parse_c.rb +3938 -0
  94. data/lib/metasm/pic16c/decode.rb +42 -0
  95. data/lib/metasm/pic16c/main.rb +17 -0
  96. data/lib/metasm/pic16c/opcodes.rb +68 -0
  97. data/lib/metasm/ppc.rb +11 -0
  98. data/lib/metasm/ppc/decode.rb +264 -0
  99. data/lib/metasm/ppc/decompile.rb +251 -0
  100. data/lib/metasm/ppc/encode.rb +51 -0
  101. data/lib/metasm/ppc/main.rb +129 -0
  102. data/lib/metasm/ppc/opcodes.rb +410 -0
  103. data/lib/metasm/ppc/parse.rb +52 -0
  104. data/lib/metasm/preprocessor.rb +1277 -0
  105. data/lib/metasm/render.rb +130 -0
  106. data/lib/metasm/sh4.rb +8 -0
  107. data/lib/metasm/sh4/decode.rb +336 -0
  108. data/lib/metasm/sh4/main.rb +292 -0
  109. data/lib/metasm/sh4/opcodes.rb +381 -0
  110. data/lib/metasm/x86_64.rb +12 -0
  111. data/lib/metasm/x86_64/compile_c.rb +1025 -0
  112. data/lib/metasm/x86_64/debug.rb +59 -0
  113. data/lib/metasm/x86_64/decode.rb +268 -0
  114. data/lib/metasm/x86_64/encode.rb +264 -0
  115. data/lib/metasm/x86_64/main.rb +135 -0
  116. data/lib/metasm/x86_64/opcodes.rb +118 -0
  117. data/lib/metasm/x86_64/parse.rb +68 -0
  118. data/misc/bottleneck.rb +61 -0
  119. data/misc/cheader-findpppath.rb +58 -0
  120. data/misc/hexdiff.rb +74 -0
  121. data/misc/hexdump.rb +55 -0
  122. data/misc/metasm-all.rb +13 -0
  123. data/misc/objdiff.rb +47 -0
  124. data/misc/objscan.rb +40 -0
  125. data/misc/pdfparse.rb +661 -0
  126. data/misc/ppc_pdf2oplist.rb +192 -0
  127. data/misc/tcp_proxy_hex.rb +84 -0
  128. data/misc/txt2html.rb +440 -0
  129. data/samples/a.out.rb +31 -0
  130. data/samples/asmsyntax.rb +77 -0
  131. data/samples/bindiff.rb +555 -0
  132. data/samples/compilation-steps.rb +49 -0
  133. data/samples/cparser_makestackoffset.rb +55 -0
  134. data/samples/dasm-backtrack.rb +38 -0
  135. data/samples/dasmnavig.rb +318 -0
  136. data/samples/dbg-apihook.rb +228 -0
  137. data/samples/dbghelp.rb +143 -0
  138. data/samples/disassemble-gui.rb +102 -0
  139. data/samples/disassemble.rb +133 -0
  140. data/samples/dump_upx.rb +95 -0
  141. data/samples/dynamic_ruby.rb +1929 -0
  142. data/samples/elf_list_needed.rb +46 -0
  143. data/samples/elf_listexports.rb +33 -0
  144. data/samples/elfencode.rb +25 -0
  145. data/samples/exeencode.rb +128 -0
  146. data/samples/factorize-headers-elfimports.rb +77 -0
  147. data/samples/factorize-headers-peimports.rb +109 -0
  148. data/samples/factorize-headers.rb +43 -0
  149. data/samples/gdbclient.rb +583 -0
  150. data/samples/generate_libsigs.rb +102 -0
  151. data/samples/hotfix_gtk_dbg.rb +59 -0
  152. data/samples/install_win_env.rb +78 -0
  153. data/samples/lindebug.rb +924 -0
  154. data/samples/linux_injectsyscall.rb +95 -0
  155. data/samples/machoencode.rb +31 -0
  156. data/samples/metasm-shell.rb +91 -0
  157. data/samples/pe-hook.rb +69 -0
  158. data/samples/pe-ia32-cpuid.rb +203 -0
  159. data/samples/pe-mips.rb +35 -0
  160. data/samples/pe-shutdown.rb +78 -0
  161. data/samples/pe-testrelocs.rb +51 -0
  162. data/samples/pe-testrsrc.rb +24 -0
  163. data/samples/pe_listexports.rb +31 -0
  164. data/samples/peencode.rb +19 -0
  165. data/samples/peldr.rb +494 -0
  166. data/samples/preprocess-flatten.rb +19 -0
  167. data/samples/r0trace.rb +308 -0
  168. data/samples/rubstop.rb +399 -0
  169. data/samples/scan_pt_gnu_stack.rb +54 -0
  170. data/samples/scanpeexports.rb +62 -0
  171. data/samples/shellcode-c.rb +40 -0
  172. data/samples/shellcode-dynlink.rb +146 -0
  173. data/samples/source.asm +34 -0
  174. data/samples/struct_offset.rb +47 -0
  175. data/samples/testpe.rb +32 -0
  176. data/samples/testraw.rb +45 -0
  177. data/samples/win32genloader.rb +132 -0
  178. data/samples/win32hooker-advanced.rb +169 -0
  179. data/samples/win32hooker.rb +96 -0
  180. data/samples/win32livedasm.rb +33 -0
  181. data/samples/win32remotescan.rb +133 -0
  182. data/samples/wintrace.rb +92 -0
  183. data/tests/all.rb +8 -0
  184. data/tests/dasm.rb +39 -0
  185. data/tests/dynldr.rb +35 -0
  186. data/tests/encodeddata.rb +132 -0
  187. data/tests/ia32.rb +82 -0
  188. data/tests/mips.rb +116 -0
  189. data/tests/parse_c.rb +239 -0
  190. data/tests/preprocessor.rb +269 -0
  191. data/tests/x86_64.rb +62 -0
  192. metadata +255 -0
@@ -0,0 +1,884 @@
1
+ # This file is part of Metasm, the Ruby assembly manipulation suite
2
+ # Copyright (C) 2006-2009 Yoann GUILLOT
3
+ #
4
+ # Licence is LGPL, see LICENCE in the top-level directory
5
+
6
+
7
+ require 'gtk2'
8
+
9
+ module Metasm
10
+ module Gui
11
+
12
+ module Protect
13
+ @@lasterror = Time.now
14
+ def protect
15
+ yield
16
+ rescue Object
17
+ puts $!.message, $!.backtrace # also dump on stdout, for c/c
18
+ delay = Time.now-@@lasterror
19
+ sleep 1-delay if delay < 1 # msgbox flood protection
20
+ @@lasterror = Time.now
21
+ messagebox([$!.message, $!.backtrace].join("\n"), $!.class.name)
22
+ end
23
+ end
24
+
25
+ module Msgbox
26
+ include Protect
27
+
28
+ def toplevel
29
+ if self.kind_of? Gtk::Window
30
+ self
31
+ else
32
+ super()
33
+ end
34
+ end
35
+
36
+ # shows a message box (non-modal)
37
+ # args: message, title/optionhash
38
+ def messagebox(*a)
39
+ MessageBox.new(toplevel, *a)
40
+ end
41
+
42
+ # asks for user input, yields the result (unless 'cancel' clicked)
43
+ # args: prompt, :text => default text, :title => title
44
+ def inputbox(*a)
45
+ InputBox.new(toplevel, *a) { |*ya| protect { yield(*ya) } }
46
+ end
47
+
48
+ # asks to chose a file to open, yields filename
49
+ # args: title, :path => path
50
+ def openfile(*a)
51
+ OpenFile.new(toplevel, *a) { |*ya| protect { yield(*ya) } }
52
+ end
53
+
54
+ # same as openfile, but for writing a (new) file
55
+ def savefile(*a)
56
+ SaveFile.new(toplevel, *a) { |*ya| protect { yield(*ya) } }
57
+ end
58
+
59
+ # displays a popup showing a table, yields the selected row
60
+ # args: title, [[col0 title, col1 title...], [col0 val0, col1 val0...], [val1], [val2]...]
61
+ def listwindow(*a)
62
+ ListWindow.new(toplevel, *a) { |*ya| protect { yield(*ya) } }
63
+ end
64
+ end
65
+
66
+ # clipboard = Gtk::Clipboard.get(Gdk::Selection::CLIPBOARD)
67
+ # text = clipboard.wait_for_text
68
+ # clipboard.text = 'foo'
69
+
70
+ # a widget that holds many other widgets, and displays only one of them at a time
71
+ class ContainerChoiceWidget < Gtk::Notebook
72
+ include Msgbox
73
+
74
+ attr_accessor :views, :view_indexes
75
+ def initialize(*a, &b)
76
+ super()
77
+ self.show_border = false
78
+ self.show_tabs = false
79
+ @views = {}
80
+ @view_indexes = []
81
+
82
+ signal_connect('realize') { initialize_visible } if respond_to? :initialize_visible
83
+
84
+ initialize_widget(*a, &b)
85
+
86
+ show_all
87
+ end
88
+
89
+ def view(i)
90
+ @views[i]
91
+ end
92
+
93
+ def showview(i)
94
+ set_page @view_indexes.index(i)
95
+ end
96
+
97
+ def addview(name, w)
98
+ @view_indexes << name
99
+ @views[name] = w
100
+ append_page(w, Gtk::Label.new(name.to_s))
101
+ end
102
+
103
+ def curview
104
+ @views[curview_index]
105
+ end
106
+
107
+ def curview_index
108
+ return if page == -1
109
+ @view_indexes[page]
110
+ end
111
+ end
112
+
113
+ class ContainerVBoxWidget < Gtk::VBox
114
+ include Msgbox
115
+
116
+ def initialize(*a, &b)
117
+ super()
118
+
119
+ signal_connect('realize') { initialize_visible } if respond_to? :initialize_visible
120
+
121
+ signal_connect('size_request') { |w, alloc| resize(*alloc) } if respond_to? :resize
122
+
123
+ self.spacing = 2
124
+
125
+ initialize_widget(*a, &b)
126
+ end
127
+
128
+ def resize_child(cld, w, h)
129
+ pk = query_child_packing(cld)
130
+ if h <= 0
131
+ pk[0] = true
132
+ h = 1
133
+ else
134
+ pk[0] = false
135
+ end
136
+ return if h == cld.allocation.height
137
+ set_child_packing(cld, *pk)
138
+ cld.set_height_request(h)
139
+ end
140
+
141
+ def redraw
142
+ end
143
+ end
144
+
145
+ class DrawableWidget < Gtk::DrawingArea
146
+ include Msgbox
147
+
148
+ attr_accessor :parent_widget, :caret_x, :caret_y, :hl_word
149
+ # this hash is used to determine the colors of the Gui elements (background, caret, ...)
150
+ # modifications to it are only useful before the widget is first rendered (IE before Gui.main)
151
+ attr_accessor :default_color_association
152
+
153
+ # keypress event keyval traduction table
154
+ Keyboard_trad = Gdk::Keyval.constants.grep(/^GDK_/).inject({}) { |h, cst|
155
+ v = Gdk::Keyval.const_get(cst)
156
+ key = cst.to_s.sub(/^GDK_/, '').sub(/^KP_/, '')
157
+ if key.length == 1
158
+ key = key[0] # ?a, ?b etc
159
+ else
160
+ key = key.downcase.to_sym
161
+ key = {
162
+ :page_up => :pgup, :page_down => :pgdown, :next => :pgdown,
163
+ :escape => :esc, :return => :enter, :l1 => :f11, :l2 => :f12,
164
+ :prior => :pgup,
165
+
166
+ :space => ?\ ,
167
+ :asciitilde => ?~, :quoteleft => ?`,
168
+ :exclam => ?!, :at => ?@,
169
+ :numbersign => ?#, :dollar => ?$,
170
+ :percent => ?%, :asciicircum => ?^,
171
+ :ampersand => ?&, :asterisk => ?*,
172
+ :parenleft => ?(, :parenright => ?),
173
+ :bracketleft => ?[, :bracketright => ?],
174
+ :braceleft => ?{, :braceright => ?},
175
+ :less => ?<, :greater => ?>,
176
+ :quotedbl => ?", :quoteright => ?',
177
+ :coma => ?,, :period => ?.,
178
+ :colon => ?:, :semicolon => ?;,
179
+ :slash => ?/, :equal => ?=,
180
+ :plus => ?+, :minus => ?-,
181
+ :question => ??, :backslash => ?\\,
182
+ :underscore => ?_, :bar => ?|,
183
+ :comma => ?,,
184
+ :divide => ?/, :multiply => ?*,
185
+ :subtract => ?-, :add => ?+
186
+ }.fetch(key, key)
187
+ end
188
+
189
+ h.update v => key
190
+ }
191
+
192
+ def initialize(*a, &b)
193
+ @parent_widget = nil
194
+
195
+ @caret_x = @caret_y = 0 # text cursor position
196
+ @oldcaret_x = @oldcaret_y = 1
197
+ @hl_word = nil
198
+
199
+ @layout = Pango::Layout.new Gdk::Pango.context # text rendering
200
+
201
+ @color = {}
202
+ @default_color_association = {:background => :palegrey}
203
+
204
+ super()
205
+
206
+ # events callbacks
207
+ signal_connect('expose_event') {
208
+ @w = window ; @gc = Gdk::GC.new(@w)
209
+ protect { paint }
210
+ @w = @gc = nil
211
+ true
212
+ }
213
+
214
+ signal_connect('size_allocate') { |w, alloc|
215
+ protect { resized(alloc.width, alloc.height) }
216
+ }
217
+
218
+ signal_connect('button_press_event') { |w, ev|
219
+ @last_kb_ev = ev
220
+ if keyboard_state(:control)
221
+ next protect { click_ctrl(ev.x, ev.y) } if ev.event_type == Gdk::Event::Type::BUTTON_PRESS and ev.button == 1 and respond_to? :click_ctrl
222
+ next
223
+ end
224
+ case ev.event_type
225
+ when Gdk::Event::Type::BUTTON_PRESS
226
+ grab_focus
227
+ case ev.button
228
+ when 1; protect { click(ev.x, ev.y) } if respond_to? :click
229
+ when 3; protect { rightclick(ev.x, ev.y) } if respond_to? :rightclick
230
+ end
231
+ when Gdk::Event::Type::BUTTON2_PRESS
232
+ case ev.button
233
+ when 1; protect { doubleclick(ev.x, ev.y) } if respond_to? :doubleclick
234
+ end
235
+ end
236
+ }
237
+
238
+ signal_connect('motion_notify_event') { |w, ev|
239
+ @last_kb_ev = ev
240
+ if keyboard_state(:control)
241
+ protect { mousemove_ctrl(ev.x, ev.y) } if respond_to? :mousemove_ctrl
242
+ else
243
+ protect { mousemove(ev.x, ev.y) }
244
+ end
245
+ } if respond_to? :mousemove
246
+
247
+ signal_connect('button_release_event') { |w, ev|
248
+ protect { mouserelease(ev.x, ev.y) } if ev.button == 1
249
+ } if respond_to? :mouserelease
250
+
251
+ signal_connect('scroll_event') { |w, ev|
252
+ dir = case ev.direction
253
+ when Gdk::EventScroll::Direction::UP; :up
254
+ when Gdk::EventScroll::Direction::DOWN; :down
255
+ else next
256
+ end
257
+ @last_kb_ev = ev
258
+ if keyboard_state(:control)
259
+ protect { mouse_wheel_ctrl(dir, ev.x, ev.y) } if respond_to? :mouse_wheel_ctrl
260
+ else
261
+ protect { mouse_wheel(dir, ev.x, ev.y) }
262
+ end
263
+ } if respond_to? :mouse_wheel
264
+
265
+ signal_connect('key_press_event') { |w, ev|
266
+ @last_kb_ev = ev
267
+ key = Keyboard_trad[ev.keyval]
268
+ if keyboard_state(:control)
269
+ protect { keypress_ctrl(key) or (@parent_widget and @parent_widget.keypress_ctrl(key)) }
270
+ else
271
+ protect { keypress(key) or (@parent_widget and @parent_widget.keypress(key)) }
272
+ end
273
+ }
274
+
275
+ signal_connect('realize') {
276
+ { :white => 'fff', :palegrey => 'ddd', :black => '000', :grey => '444',
277
+ :red => 'f00', :darkred => '800', :palered => 'fcc',
278
+ :green => '0f0', :darkgreen => '080', :palegreen => 'cfc',
279
+ :blue => '00f', :darkblue => '008', :paleblue => 'ccf',
280
+ :yellow => 'ff0', :darkyellow => '440', :paleyellow => 'ffc',
281
+ }.each { |tag, val|
282
+ @color[tag] = color(val)
283
+ }
284
+
285
+ set_color_association @default_color_association
286
+
287
+ initialize_visible if respond_to? :initialize_visible
288
+ }
289
+
290
+ initialize_widget(*a, &b)
291
+
292
+ # receive keyboard/mouse signals
293
+ set_events Gdk::Event::ALL_EVENTS_MASK
294
+ set_can_focus true
295
+ set_font 'courier 10'
296
+ end
297
+
298
+ def initialize_widget
299
+ end
300
+
301
+
302
+ # create a color from a 'rgb' description
303
+ def color(val)
304
+ if not @color[val]
305
+ @color[val] = Gdk::Color.new(*val.unpack('CCC').map { |c| (c.chr*4).hex })
306
+ window.colormap.alloc_color(@color[val], true, true)
307
+ end
308
+ @color[val]
309
+ end
310
+
311
+ def set_caret_from_click(x, y)
312
+ @caret_x = (x-1).to_i / @font_width
313
+ @caret_y = y.to_i / @font_height
314
+ update_caret
315
+ end
316
+
317
+ # change the font of the widget
318
+ # arg is a Gtk Fontdescription string (eg 'courier 10')
319
+ def set_font(descr)
320
+ @layout.font_description = Pango::FontDescription.new(descr)
321
+ @layout.text = 'x'
322
+ @font_width, @font_height = @layout.pixel_size
323
+ gui_update
324
+ end
325
+
326
+ # change the color association
327
+ # arg is a hash function symbol => color symbol
328
+ # color must be allocated
329
+ # check #initialize/sig('realize') for initial function/color list
330
+ def set_color_association(hash)
331
+ hash.each { |k, v| @color[k] = color(v) }
332
+ modify_bg Gtk::STATE_NORMAL, @color[:background]
333
+ gui_update
334
+ end
335
+
336
+ # update @hl_word from a line & offset, return nil if unchanged
337
+ def update_hl_word(line, offset)
338
+ return if not line
339
+ word = line[0...offset].to_s[/\w*$/] << line[offset..-1].to_s[/^\w*/]
340
+ word = nil if word == ''
341
+ @hl_word = word if @hl_word != word
342
+ end
343
+
344
+ def paint
345
+ end
346
+
347
+ # invalidate the whole widget area
348
+ def redraw
349
+ invalidate(0, 0, 1000000, 1000000)
350
+ end
351
+
352
+ def invalidate_caret(cx, cy, x=0, y=0)
353
+ invalidate(x + cx*@font_width, y + cy*@font_height, 2, @font_height)
354
+ end
355
+
356
+ def invalidate(x, y, w, h)
357
+ return if not window
358
+ window.invalidate Gdk::Rectangle.new(x, y, w, h), false
359
+ end
360
+
361
+ def width
362
+ allocation.width
363
+ end
364
+
365
+ def height
366
+ allocation.height
367
+ end
368
+
369
+ def resized(w, h)
370
+ redraw
371
+ end
372
+
373
+ def keypress(key)
374
+ end
375
+
376
+ def keypress_ctrl(key)
377
+ end
378
+
379
+ def gui_update
380
+ redraw
381
+ end
382
+
383
+ def draw_color(col)
384
+ @gc.set_foreground color(col)
385
+ end
386
+
387
+ def draw_rectangle(x, y, w, h)
388
+ @w.draw_rectangle(@gc, true, x, y, w, h)
389
+ end
390
+
391
+ def draw_rectangle_color(col, x, y, w, h)
392
+ draw_color(col)
393
+ draw_rectangle(x, y, w, h)
394
+ end
395
+
396
+ def draw_line(x, y, ex, ey)
397
+ @w.draw_line(@gc, x, y, ex, ey)
398
+ end
399
+
400
+ def draw_line_color(col, x, y, ex, ey)
401
+ draw_color(col)
402
+ draw_line(x, y, ex, ey)
403
+ end
404
+
405
+ def draw_string(x, y, str)
406
+ @layout.text = str
407
+ @w.draw_layout(@gc, x, y, @layout)
408
+ end
409
+
410
+ def draw_string_color(col, x, y, str)
411
+ draw_color(col)
412
+ draw_string(x, y, str)
413
+ end
414
+
415
+ def clipboard_copy(buf)
416
+ clipboard = Gtk::Clipboard.get(Gdk::Selection::PRIMARY)
417
+ clipboard.text = buf
418
+ end
419
+
420
+ def clipboard_paste
421
+ clipboard = Gtk::Clipboard.get(Gdk::Selection::PRIMARY)
422
+ clipboard.wait_for_text
423
+ end
424
+
425
+ def keyboard_state(query=nil)
426
+ case query
427
+ when :control, :ctrl
428
+ ev = @last_kb_ev and ev.state & Gdk::Window::CONTROL_MASK == Gdk::Window::CONTROL_MASK
429
+ when :shift
430
+ ev = @last_kb_ev and ev.state & Gdk::Window::SHIFT_MASK == Gdk::Window::SHIFT_MASK
431
+ when :alt
432
+ ev = @last_kb_ev and ev.state & Gdk::Window::MOD1_MASK == Gdk::Window::MOD1_MASK
433
+ else
434
+ [:control, :shift, :alt].find_all { |s| keyboard_state(s) }
435
+ end
436
+ end
437
+ end
438
+
439
+ module WindowPos
440
+ def x; position[0]; end
441
+ def x=(nx); move(nx, position[1]); end
442
+ def y; position[1]; end
443
+ def y=(ny); move(position[0], ny); end
444
+ def width; size[0] ; end
445
+ def width=(nw); resize(nw, size[1]); end
446
+ def height; size[1] ; end
447
+ def height=(nh); resize(size[0], nh); end
448
+ end
449
+
450
+ class MessageBox < Gtk::MessageDialog
451
+ include WindowPos
452
+ def initialize(owner, str, opts={})
453
+ owner = nil if owner and (not owner.kind_of? Gtk::Window or owner.destroyed?)
454
+ owner ||= Gtk::Window.toplevels.first
455
+ opts = {:title => opts} if opts.kind_of? String
456
+ super(owner, Gtk::Dialog::DESTROY_WITH_PARENT, INFO, BUTTONS_CLOSE, str)
457
+ self.title = opts[:title] if opts[:title]
458
+ signal_connect('response') { destroy }
459
+ show_all
460
+ present # bring the window to the foreground & set focus
461
+ end
462
+ end
463
+
464
+ class InputBox < Gtk::Dialog
465
+ include WindowPos
466
+ attr_accessor :label, :textwidget
467
+
468
+ # shows a simplitic input box (eg window with a 1-line textbox + OK button), yields the text
469
+ # TODO history, dropdown, autocomplete, contexts, 3D stereo surround, etc
470
+ def initialize(owner, str, opts={})
471
+ owner ||= Gtk::Window.toplevels.first
472
+ super(nil, owner, Gtk::Dialog::DESTROY_WITH_PARENT,
473
+ [Gtk::Stock::OK, Gtk::Dialog::RESPONSE_ACCEPT], [Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_REJECT])
474
+ self.title = opts[:title] if opts[:title]
475
+
476
+ @label = Gtk::Label.new(str)
477
+ @textwidget = Gtk::TextView.new
478
+ if opts[:text]
479
+ @textwidget.buffer.text = opts[:text].to_s
480
+ @textwidget.buffer.move_mark('selection_bound', @textwidget.buffer.start_iter)
481
+ @textwidget.buffer.move_mark('insert', @textwidget.buffer.end_iter)
482
+ end
483
+
484
+ @textwidget.signal_connect('key_press_event') { |w, ev|
485
+ key = DrawableWidget::Keyboard_trad[ev.keyval]
486
+ case key
487
+ when :escape; response(RESPONSE_REJECT) ; true
488
+ when :enter; response(RESPONSE_ACCEPT) ; true
489
+ end
490
+ }
491
+
492
+ signal_connect('response') { |win, id|
493
+ resp = @textwidget.buffer.text if id == RESPONSE_ACCEPT
494
+ destroy
495
+ yield resp.strip if resp
496
+ true
497
+ }
498
+
499
+ vbox.pack_start label, false, false, 8
500
+ vbox.pack_start @textwidget, false, false, 8
501
+
502
+ Gtk::Drag.dest_set(self,
503
+ Gtk::Drag::DEST_DEFAULT_MOTION |
504
+ Gtk::Drag::DEST_DEFAULT_DROP,
505
+ [['text/plain', 0, 0], ['text/uri-list', 0, 0]],
506
+ Gdk::DragContext::ACTION_COPY | Gdk::DragContext::ACTION_MOVE)
507
+
508
+ signal_connect('drag_data_received') { |w, dc, x, y, data, info, time|
509
+ dc.targets.each { |target|
510
+ next if target.name != 'text/plain' and target.name != 'text/uri-list'
511
+ data.data.each_line { |l|
512
+ l = l.chomp.sub(%r{^file://}, '')
513
+ self.text = l
514
+ }
515
+ }
516
+ Gtk::Drag.finish(dc, true, false, time)
517
+ }
518
+
519
+
520
+ show_all
521
+ present
522
+ end
523
+
524
+ def text ; @textwidget.buffer.text ; end
525
+ def text=(nt) ; @textwidget.buffer.text = nt ; end
526
+ end
527
+
528
+ class OpenFile < Gtk::FileChooserDialog
529
+ include WindowPos
530
+ @@currentfolder = nil
531
+
532
+ # shows an asynchronous FileChooser window, yields the chosen filename
533
+ # TODO save last path
534
+ def initialize(owner, title, opts={})
535
+ owner ||= Gtk::Window.toplevels.first
536
+ super(title, owner, Gtk::FileChooser::ACTION_OPEN, nil,
537
+ [Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_CANCEL], [Gtk::Stock::OPEN, Gtk::Dialog::RESPONSE_ACCEPT])
538
+ f = opts[:path] || @@currentfolder
539
+ self.current_folder = f if f
540
+ signal_connect('response') { |win, id|
541
+ if id == Gtk::Dialog::RESPONSE_ACCEPT
542
+ file = filename
543
+ @@currentfolder = File.dirname(file)
544
+ end
545
+ destroy
546
+ yield file if file
547
+ true
548
+ }
549
+
550
+ show_all
551
+ present
552
+ end
553
+ end
554
+
555
+ class SaveFile < Gtk::FileChooserDialog
556
+ include WindowPos
557
+ @@currentfolder = nil
558
+
559
+ # shows an asynchronous FileChooser window, yields the chosen filename
560
+ # TODO save last path
561
+ def initialize(owner, title, opts={})
562
+ owner ||= Gtk::Window.toplevels.first
563
+ super(title, owner, Gtk::FileChooser::ACTION_SAVE, nil,
564
+ [Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_CANCEL], [Gtk::Stock::SAVE, Gtk::Dialog::RESPONSE_ACCEPT])
565
+ f = opts[:path] || @@currentfolder
566
+ self.current_folder = f if f
567
+ signal_connect('response') { |win, id|
568
+ if id == Gtk::Dialog::RESPONSE_ACCEPT
569
+ file = filename
570
+ @@currentfolder = File.dirname(file)
571
+ end
572
+ destroy
573
+ yield file if file
574
+ true
575
+ }
576
+
577
+ show_all
578
+ present
579
+ end
580
+ end
581
+
582
+ class ListWindow < Gtk::Dialog
583
+ include WindowPos
584
+ # shows a window with a list of items
585
+ # the list is an array of arrays, displayed as String
586
+ # the first array is the column names
587
+ # each item clicked yields the block with the selected iterator, double-click also close the popup
588
+ def initialize(owner, title, list, h={})
589
+ owner ||= Gtk::Window.toplevels.first
590
+ super(title, owner, Gtk::Dialog::DESTROY_WITH_PARENT)
591
+
592
+ cols = list.shift
593
+
594
+ treeview = Gtk::TreeView.new
595
+ treeview.model = Gtk::ListStore.new(*[String]*cols.length)
596
+ treeview.selection.mode = Gtk::SELECTION_NONE
597
+ if @color_callback = h[:color_callback]
598
+ @drawable = DrawableWidget.new # needed for color()...
599
+ @drawable.signal_emit('realize')
600
+ end
601
+
602
+ cols.each_with_index { |col, i|
603
+ crt = Gtk::CellRendererText.new
604
+ tvc = Gtk::TreeViewColumn.new(col, crt)
605
+ tvc.sort_column_id = i
606
+ tvc.set_cell_data_func(crt) { |_tvc, _crt, model, iter|
607
+ _crt.text = iter[i]
608
+ if @color_callback
609
+ fu = (0...cols.length).map { |ii| iter[ii] }
610
+ fg, bg = @color_callback[fu]
611
+ fg ||= :black
612
+ bg ||= :white
613
+ _crt.foreground = @drawable.color(fg).to_s
614
+ _crt.cell_background = @drawable.color(bg).to_s
615
+ end
616
+ }
617
+ treeview.append_column tvc
618
+ }
619
+
620
+ list.each { |e|
621
+ iter = treeview.model.append
622
+ e.each_with_index { |v, i| iter[i] = v.to_s }
623
+ }
624
+
625
+ treeview.model.set_sort_column_id(0)
626
+
627
+ treeview.signal_connect('cursor_changed') { |x|
628
+ if iter = treeview.selection.selected
629
+ yield iter
630
+ end
631
+ }
632
+ treeview.signal_connect('row_activated') { destroy }
633
+
634
+ signal_connect('destroy') { h[:ondestroy].call } if h[:ondestroy]
635
+
636
+ remove vbox
637
+ add Gtk::ScrolledWindow.new.add(treeview)
638
+ toplevel.set_default_size cols.length*120, 400
639
+
640
+ show if not h[:noshow]
641
+
642
+ # so that the 1st line is not selected by default
643
+ treeview.selection.mode = Gtk::SELECTION_SINGLE
644
+ end
645
+
646
+ def show
647
+ show_all
648
+ present
649
+ end
650
+ end
651
+
652
+ class Window < Gtk::Window
653
+ include WindowPos
654
+ include Msgbox
655
+
656
+ attr_accessor :menu
657
+ def initialize(*a, &b)
658
+ super()
659
+
660
+ signal_connect('destroy') { destroy_window }
661
+
662
+ @vbox = Gtk::VBox.new
663
+ add @vbox
664
+
665
+ @menu = []
666
+ @menubar = Gtk::MenuBar.new
667
+ @accel_group = Gtk::AccelGroup.new
668
+
669
+ @vbox.add @menubar, 'expand' => false
670
+ @child = nil
671
+ s = Gdk::Screen.default
672
+ set_default_size s.width*3/4, s.height*3/4
673
+
674
+ Gtk::Settings.default.gtk_menu_bar_accel = nil # disable F10 -> focus menubar
675
+
676
+ (@@mainwindow_list ||= []) << self
677
+
678
+ initialize_window(*a, &b)
679
+ build_menu
680
+ update_menu
681
+
682
+
683
+ Gtk::Drag.dest_set(self,
684
+ Gtk::Drag::DEST_DEFAULT_MOTION |
685
+ Gtk::Drag::DEST_DEFAULT_DROP,
686
+ [['text/plain', 0, 0], ['text/uri-list', 0, 0]],
687
+ Gdk::DragContext::ACTION_COPY | Gdk::DragContext::ACTION_MOVE)
688
+
689
+ signal_connect('drag_data_received') { |w, dc, x, y, data, info, time|
690
+ dc.targets.each { |target|
691
+ next if target.name != 'text/plain' and target.name != 'text/uri-list'
692
+ data.data.each_line { |l|
693
+ next if not @child or not @child.respond_to? :dragdropfile
694
+ l = l.chomp.sub(%r{^file://}, '')
695
+ protect { @child.dragdropfile(l) }
696
+ }
697
+ }
698
+ Gtk::Drag.finish(dc, true, false, time)
699
+ }
700
+
701
+ show_all
702
+ end
703
+
704
+ def destroy_window
705
+ @@mainwindow_list.delete self
706
+ Gui.main_quit if @@mainwindow_list.empty? # XXX we don't call main_start ourself..
707
+ end
708
+
709
+ def widget=(w)
710
+ @vbox.remove @child if @child
711
+ @child = w
712
+ @vbox.add w if w
713
+ show_all
714
+ end
715
+
716
+ def widget
717
+ @child
718
+ end
719
+
720
+ def build_menu
721
+ end
722
+
723
+ def new_menu
724
+ []
725
+ end
726
+
727
+ # finds a menu by name (recursive)
728
+ # returns a valid arg for addsubmenu(ret)
729
+ def find_menu(name, from=@menu)
730
+ name = name.gsub('_', '')
731
+ if not l = from.find { |e| e.grep(::String).find { |es| es.gsub('_', '') == name } }
732
+ l = from.map { |e| e.grep(::Array).map { |ae| find_menu(name, ae) }.compact.first }.compact.first
733
+ end
734
+ l.grep(::Array).first if l
735
+ end
736
+
737
+ # append stuff to a menu
738
+ # arglist:
739
+ # empty = menu separator
740
+ # string = menu entry display name (use a single '_' keyboard for shortcut, eg 'Sho_rtcut' => 'r')
741
+ # :check = menu entry is a checkbox type, add a true/false argument to specify initial value
742
+ # second string = keyboard shortcut (accelerator) - use '^' for Ctrl, and '<up>' for special keys
743
+ # a menu object = this entry will open a submenu (you must specify a name, and action is ignored)
744
+ # the method takes a block or a Proc argument that will be run whenever the menu item is selected
745
+ #
746
+ # use @menu to reference the top-level menu bar
747
+ # call update_menu when the menu is done
748
+ def addsubmenu(menu, *args, &action)
749
+ args << action if action
750
+ menu << args
751
+ menu.last
752
+ end
753
+
754
+ # make the window's MenuBar reflect the content of @menu
755
+ def update_menu
756
+ # clear
757
+ @menubar.children.dup.each { |mc| @menubar.remove mc }
758
+ # populate the menubar using @menu
759
+ @menu.each { |e| create_menu_item(@menubar, e) }
760
+ @menubar.show_all
761
+ end
762
+
763
+ def create_menu_item(menu, entry)
764
+ args = entry.dup
765
+
766
+ # recognise 'OPEN', 'SAVE' etc, with special icon/localisation
767
+ stock = (Gtk::Stock.constants.map { |c| c.to_s } & args).first
768
+ args.delete stock if stock
769
+ accel = args.grep(/^\^?(\w|<\w+>)$/).first
770
+ args.delete accel if accel
771
+ check = args.delete :check
772
+ action = args.grep(::Proc).first
773
+ args.delete action if action
774
+ if submenu = args.grep(::Array).first
775
+ args.delete submenu
776
+ sm = Gtk::Menu.new
777
+ submenu.each { |e| create_menu_item(sm, e) }
778
+ submenu = sm
779
+ end
780
+ label = args.shift
781
+
782
+ if stock
783
+ item = Gtk::ImageMenuItem.new(Gtk::Stock.const_get(stock))
784
+ begin
785
+ item.label = label if label
786
+ rescue
787
+ # in some version of gtk, no #label=
788
+ item = Gtk::MenuItem.new(label) if label
789
+ end
790
+ elsif check
791
+ item = Gtk::CheckMenuItem.new(label)
792
+ item.active = args.shift
793
+ elsif label
794
+ item = Gtk::MenuItem.new(label)
795
+ else
796
+ item = Gtk::MenuItem.new
797
+ end
798
+ item.set_submenu(submenu) if submenu
799
+
800
+ if accel
801
+ key = accel[-1]
802
+ if key == ?>
803
+ key = accel[/<(.*)>/, 1]
804
+ key = case key
805
+ when 'enter'; Gdk::Keyval::GDK_Return
806
+ when 'esc'; Gdk::Keyval::GDK_Escape
807
+ when 'tab'; Gdk::Keyval::GDK_Tab
808
+ when /^f(\d\d?)$/i; Gdk::Keyval.const_get("GDK_#{key.upcase}")
809
+ else ??
810
+ end
811
+ end
812
+ key = key.unpack('C')[0] if key.kind_of? String # yay rb19
813
+ item.add_accelerator('activate', @accel_group, key, (accel[0] == ?^ ? Gdk::Window::CONTROL_MASK : 0), Gtk::ACCEL_VISIBLE)
814
+ end
815
+ if action
816
+ a = action
817
+ if check
818
+ a = lambda { item.active = action.call(item.active?) }
819
+ end
820
+ item.signal_connect('activate') { protect { a.call(item) } }
821
+ end
822
+ menu.append item
823
+ item
824
+ end
825
+
826
+ def initialize_window
827
+ end
828
+ end
829
+
830
+ class ToolWindow < Gtk::Dialog
831
+ include WindowPos
832
+
833
+ def initialize(parent=nil, *a, &b)
834
+ super('toolwin', parent, Gtk::Dialog::DESTROY_WITH_PARENT)
835
+ set_events Gdk::Event::ALL_EVENTS_MASK
836
+ set_can_focus true
837
+ @child = vbox
838
+ initialize_window(*a, &b)
839
+ show_all
840
+ end
841
+
842
+ def widget=(w)
843
+ remove @child if @child
844
+ @child = w
845
+ add @child
846
+ if @child.respond_to? :initial_size
847
+ resize(*@child.initial_size)
848
+ end
849
+ show_all
850
+ end
851
+
852
+ def widget
853
+ @child
854
+ end
855
+ end
856
+
857
+ # start the Gui main loop
858
+ def self.main
859
+ Gtk.main
860
+ end
861
+
862
+ # ends the Gui main loop
863
+ def self.main_quit
864
+ Gtk.main_quit
865
+ end
866
+
867
+ # register a proc to be run whenever the gui loop is idle
868
+ # if the proc returns nil/false, delete it
869
+ def self.idle_add(&b)
870
+ Gtk.idle_add(&b)
871
+ end
872
+
873
+ # run a single iteration of the main_loop
874
+ # e.g. call this from time to time when doing heavy computation, to keep the UI somewhat responsive
875
+ def self.main_iter
876
+ Gtk.main_iteration_do(false)
877
+ end
878
+
879
+ end
880
+ end
881
+
882
+ require 'metasm/gui/dasm_main'
883
+ require 'metasm/gui/debug'
884
+