Sebastians Blog http://sgaul.de Neues aus den Softwareminen... Tue, 20 May 2014 14:23:59 +0000 de-DE hourly 1 http://wordpress.org/?v=3.9.1 Ruby: Testvollständigkeit testen http://sgaul.de/2014/05/20/ruby-testvollstaendigkeit-testen/ http://sgaul.de/2014/05/20/ruby-testvollstaendigkeit-testen/#comments Tue, 20 May 2014 14:23:59 +0000 http://sgaul.de/?p=2679 Weiterlesen ]]> Eine Wissenschaft für sich im Test-Driven Development ist die Frage, wie jede einzelne Funktion getestet werden muss: Sind die Tests vollständig, hat man jeden Spezialfall bedacht? Hat man es zu gut gemeint und eine schwer überschaubare, redundante Testsuite geschaffen?

Einen recht ungewöhnlichen Weg zur Beantwortung der ersten Frage geht Markus Schirp. Sein Gem Mutant betrachtet Code und die zugehörigen, grünen Tests. Es verändert nun immer wieder bestimmte Teile des Applikations-Codes (nicht die Tests) und prüft, ob die Tests dadurch fehlschlagen. Tun sie dies nicht lässt sich schlussfolgern, dass diese Stellen des Codes nicht vollständig durch Tests abgedeckt oder tot sind.

Die Code-Änderungen sind dabei vollautomatisch. Ifs werden durch Unless ersetzt, boolsche Default-Werte von Argumenten werden negiert oder ganze Statements weggelassen. Solange die Tests dadurch fehlschlagen ist es egal wie viel Unsinn sich hieraus ergibt.

Der Ansatz klingt auf jeden Fall interessant und erscheint um einiges aussagekräftiger als die üblichen Code-Coverage-Statistiken. Ob er wirklich praxistauglich ist kann ich aufgrund mangelnder Erfahrung nicht beurteilen. Im Weg steht leider auch der etwas sperrige Einstieg. Ich konnte etwa keine Beispiele finden wie man Code mit Rails-Abhängigkeiten testet – und von diesem Code habe ich eine Menge. Ein Gem Mutant-Rails gibt es zwar schon, ist aber mit einem entmutigenden „This gem does not yet work“ überschrieben. Aber der Kurs scheint zu stimmen.

Auf der Rails-Conf 2014 gab es einen Vortrag zu Mutant. Dieser kratzt leider nur an der Oberfläche, zeigt aber immerhin ein Live-Beispiel.

]]>
http://sgaul.de/2014/05/20/ruby-testvollstaendigkeit-testen/feed/ 0
Klassen- und Klassenhierarchievariablen in Ruby http://sgaul.de/2014/05/15/klassen-und-klassenhierarchievariablen-in-ruby/ http://sgaul.de/2014/05/15/klassen-und-klassenhierarchievariablen-in-ruby/#comments Thu, 15 May 2014 13:11:48 +0000 http://sgaul.de/?p=2663 Weiterlesen ]]> Sucht man nach einem Klassenvariablenkonzept für Ruby trifft man über kurz oder lang auf das @@-Konzept. Wer sich darauf einlässt kann schnell Probleme bekommen. Der Grund ist relativ einfach: @@-Variablen sind keine Klassenvariablen.

Einfache „Klassenvariablen“

class Cat
  
  @@size = :small
  
  def self.size
    @@size
  end

  def self.size= new_size
    @@size = new_size
  end

  def size
    @@size
  end

end

expect(Cat.size).to be :small
expect(Cat.new.size).to be :small
Cat.size = :medium
expect(Cat.size).to be :medium
expect(Cat.new.size).to be :medium

Vererbung

class HouseCat < Cat
end

class Lion < Cat
end
expect(Lion.size).to be :small
Lion.size = :big
expect(Lion.size).to be :big
expect(HouseCat.size).to be :big

Hauskatze und Löwe teilen die selbe Variable, was zu erwarten war. Lässt sich das trennen?

@@-Variablen sind keine Klassenvariablen

class HouseCat < Cat
end

class Lion < Cat
  @@size = :big
end
expect(Lion.size).to be :big
expect(HouseCat.size).to be :big # !!!

Nein. Die gesetzte Variable gilt für die gesamte Klassenhierarchie aufwärts.

Superklassen haben übrigens keinen Zugriff:

class Cat
  
  def self.size
    @@size
  end

end

class Lion < Cat
  @@size = :big
end
expect{ Lion.size }.to raise_error NameError

Richtige Klassenvariablen

Da in Ruby auch Klassen Objekte sind, sind Instanzvariablen dieser Objekte auch Klassenvariablen. Bemerkenswert ist hier die strenge Sichtbarkeitsbeschränkung: Selbst wenn @small und self.size in der selben Klassen definiert werden, kann self.size nach der Vererbung in die HouseCat nicht länger mit @small auf die Klassenvariable von Cat zurgreifen.

class Cat
  
  @size = :small
  
  def self.size
    @size
  end

end

class HouseCat < Cat
end

class Lion < Cat
  @size = :big
end
expect(Lion.size).to be :big
expect(Cat.size).to be :small
expect(HouseCat.size).to be_nil

Aber hier können ja wieder Klassenhierarchievariablen helfen.

]]>
http://sgaul.de/2014/05/15/klassen-und-klassenhierarchievariablen-in-ruby/feed/ 0
Was beim Unit-Testing getestet werden sollte http://sgaul.de/2014/05/10/was-beim-unit-testing-getestet-werden-sollte/ http://sgaul.de/2014/05/10/was-beim-unit-testing-getestet-werden-sollte/#comments Sat, 10 May 2014 20:30:21 +0000 http://sgaul.de/?p=2634 Weiterlesen ]]> Die zentrale Idee des Unit-Testings ist Isolation. Man testet einzelne Komponenten, Abhängigkeiten werden als gegeben und korrekt angesehen und ggf. gemockt. Dies macht das  Testen der einzelnen Komponente übersichtlicher, da nur ihre Pfade betrachtet werden müssen. In der Regel wird hierbei ein Blackbox-Ansatz verfolgt. Die zu testende Funktionalität (meist eine öffentliche Methode) wird nur von außen betrachtet: Was geht rein, was kommt raus, wie ändert sich der nach außen sichtbare Zustand?

Es gibt hilfreiche theoretische Betrachtungen, welche Funktionen getestet werden sollten und welche nicht. Zudem lässt sich argumentieren, wie ein bestimmer Funktionstyp zu testen ist. Dies kann helfen den Aufwand des Testschreibens zu reduzieren. Zudem sorgt die bessere Isolation dafür, dass eine Änderung in einer Komponente nicht länger das Umschreiben hiervon eigentlich unabhängiger Tests erfordert.

Query und Command

Um zu formalisieren was getestet werden sollte, unterscheidet man zwei Arten von Funktionen: Query-Methoden geben einen Wert zurück, ändern aber nicht den Zustand des Systems (Array#length). Command-Methoden sind das entsprechende Gegenteil. Sie ändern den Zustand der Komponenten oder des Systems, haben aber keinen Rückgabewert (Array#push). Es gilt als guter Stil diese Trennung zu achten, auch wenn es zahlreiche akzeptierte Ausnahmen gibt (Array#pop, ActiveRecord::Base#save uvm.).

Nachrichten

Beim Testen betrachtet man Nachrichten, die die zu testende Komponente erhält oder versendet. Eingehende Nachrichten erteilen der Komponente den Auftrag etwas zu ändern (Command) oder zurückzugeben (Query). In der objektorientierten Welt ist dies der Aufruf einer Methode des zu testenden Objekts von außen. Der zugehörige Test stellt sicher, dass die Query-Methode den korrekten Wert zurück gibt und die Command-Methode die gewünschte Zustandsänderung bewirkt.

Ausgehende Nachrichten treten auf, wenn die zu testende Methode eine Methode eines anderen Objekts aufruft. Schickt eine Command-Methode eine solche Nachricht, muss sichergestellt werden, dass diese auch wirklich verschickt wird. Dies lässt sich komfortabel durch Mocks lösen. Ausgehende Nachrichten einer Query-Methode werden nicht getestet. Treten hier Probleme auf, werden diese durch die Tests der eingehenden Nachrichten deutlich.

Interne Nachrichten beschreiben den Aufruf einer Methode durch eine Methode des selben Objekts. Dies sollte in aller Regel nicht getestet werden, da die beiden vorigen Nachrichtentypen-Tests bereits sicherstellen, dass die Komponente korrekt funktioniert. Dies impliziert, dass auch interne Aufrufe korrekt sind. Solche Tests sind nicht nur unnötig, sondern fixieren zusätzlich Implementierungsdetails und behindern somit ein mögliches Refactoring.

Beispiel

Das folgende Beispiel zeigt mögliche Tests der Klasse Array. #length ist eine Command-Methode, bei der nur das Ergebnis wichtig ist. Bei #push zählt nur, dass das eingefügte Element Teil des Arrays wird. Für #map muss nur sichergestellt werden, dass die angegebene Methode auf dem Array-Element aufgerufen wird:

Nachrichtentyp Query Command
Eingehend
a = [1, [2, [3]]]
expect(a.flatten).to(
  eq [1, 2, 3]
)
a = [1]
a.push(2)
expect(a).to include 2
Ausgehend keine Tests
mock = Object.new
array = [mock]
mock.should_receive(:foo)
array.map(&:foo)
Intern keine Tests keine Tests

Tabelle in Anlehnung an The Magic Tricks of Testing by Sandi Metz

Beim Flatten-Beispiel erscheint es verlockend zu testen, dass auch auf den Elementen des Arrays flatten aufgerufen wird, wenn diese wiederum Arrays sind. Tatsächlich wird dieses rekursive Verhalten aber bereits vom Test der eingehenden Nachricht sichergestellt, ein Test der ausgehenden Nachricht der Query-Methode ist somit unnötig.

]]>
http://sgaul.de/2014/05/10/was-beim-unit-testing-getestet-werden-sollte/feed/ 0
RetroPie über WLAN befüllen und aktualisieren http://sgaul.de/2014/05/05/retropie-ueber-wlan-befuellen-und-steuern/ http://sgaul.de/2014/05/05/retropie-ueber-wlan-befuellen-und-steuern/#comments Mon, 05 May 2014 17:22:48 +0000 http://sgaul.de/?p=2611 Weiterlesen ]]> Nachdem mein Pi für Retrospiele nun weitgehend konfiguriert ist, habe ich die Tastatur durch einen WLAN-Dongle ersetzt. Die Konfiguration habe ich etwas umständlich in die /etc/network/interfaces eintragen müssen, hierfür scheint es keinen Assistenten zu geben. Dafür wird das Romverzeichnis über Samba freigegeben, so dass es ohne jedes Zutun im Ubuntu-Dateimanager auftaucht und befüllt werden kann. Auch via SSH ist der kleine Rechenknecht erreichbar: Der Benutzername ist pi, das Passwort raspberry. Somit ist die Tastatur wirklich obsolet.

Dank der Internetverbindung bietet sich auch ein Update an. Debian lässt sich über Apt aktualisieren, für die Emulatorkomponenten gibt es einen eigenen Assistenten:

 sudo ./RetroPie-Setup/retropie_setup.sh

Hier sollte man zunächst das Setup-Script selbst und anschließend die Retro-Pie-Binaries auf den neuesten Stand bringen. Ich war etwas misstrauisch, ob die von mir gemachten Anpassungen durch das Update verloren gehen könnten. Dies war nicht der Fall. Im Gegenteil: Seit dem Update funktioniert auch der Ton des Osmose-Emulators, so dass ich nun auch zu Game-Gear- und Master-System-Klassikern greifen kann.

]]>
http://sgaul.de/2014/05/05/retropie-ueber-wlan-befuellen-und-steuern/feed/ 0
Vim-Zwischenablage unter Ubuntu 14.04 http://sgaul.de/2014/05/03/vim-zwischenablage-unter-ubuntu-14-04/ http://sgaul.de/2014/05/03/vim-zwischenablage-unter-ubuntu-14-04/#comments Sat, 03 May 2014 09:33:20 +0000 http://sgaul.de/?p=2664 Weiterlesen ]]> Wenn in Vim von Ubuntu 14.04 die Zugriffe auf das System-Clipboard (etwa "+p oder "+y) nicht funktionieren, ist in aller Regel eine Version ohne Clipboard-Modul installiert:

vim --version
VIM - Vi IMproved 7.4 (2013 Aug 10, compiled Jan  2 2014 19:39:32)
...
Riesige Version ohne GUI. Ein- (+) oder ausschließlich (-) der Eigenschaften:
...
-clipboard       +iconv           +path_extra      -toolbar
...

Um dies zu beheben einfach das Paket vim-gtk installieren, was auch gleich Gvim auf den Rechner schaufelt. Wer sich an den drei MB extra stört kann es auch mit dem Paket vim-gui-common versuchen.

sudo apt-get install vim-gtk
vim --version
VIM - Vi IMproved 7.4 (2013 Aug 10, compiled Jan  2 2014 19:39:59)
Inklusive der Korrekturen: 1-52
Verändert von pkg-vim-maintainers@lists.alioth.debian.org
Übersetzt von buildd@
Riesige Version mit GTK2 GUI.
 Ein- (+) oder ausschließlich (-) der Eigenschaften:
+acl             +farsi           +mouse_netterm   +syntax
+arabic          +file_in_path    +mouse_sgr       +tag_binary
+autocmd         +find_in_path    -mouse_sysmouse  +tag_old_static
+balloon_eval    +float           +mouse_urxvt     -tag_any_white
+browse          +folding         +mouse_xterm     +tcl
++builtin_terms  -footer          +multi_byte      +terminfo
+byte_offset     +fork()          +multi_lang      +termresponse
+cindent         +gettext         -mzscheme        +textobjects
+clientserver    -hangul_input    +netbeans_intg   +title
+clipboard       +iconv           +path_extra      +toolbar
+cmdline_compl   +insert_expand   +perl            +user_commands
+cmdline_hist    +jumplist        +persistent_undo +vertsplit
+cmdline_info    +keymap          +postscript      +virtualedit
+comments        +langmap         +printer         +visual
+conceal         +libcall         +profile         +visualextra
+cryptv          +linebreak       +python          +viminfo
+cscope          +lispindent      -python3         +vreplace
+cursorbind      +listcmds        +quickfix        +wildignore
+cursorshape     +localmap        +reltime         +wildmenu
+dialog_con_gui  +lua             +rightleft       +windows
+diff            +menu            +ruby            +writebackup
+digraphs        +mksession       +scrollbind      +X11
+dnd             +modify_fname    +signs           -xfontset
-ebcdic          +mouse           +smartindent     +xim
+emacs_tags      +mouseshape      -sniff           +xsmp_interact
+eval            +mouse_dec       +startuptime     +xterm_clipboard
+ex_extra        +mouse_gpm       +statusline      -xterm_save
+extra_search    -mouse_jsbterm   -sun_workshop    +xpm
]]>
http://sgaul.de/2014/05/03/vim-zwischenablage-unter-ubuntu-14-04/feed/ 0
RetroPie: Master System und Game Gear mit PS3-Controller http://sgaul.de/2014/05/02/retropie-master-system-und-game-gear-mit-ps3-controller/ http://sgaul.de/2014/05/02/retropie-master-system-und-game-gear-mit-ps3-controller/#comments Fri, 02 May 2014 12:35:49 +0000 http://sgaul.de/?p=2621 Weiterlesen ]]> Das Image vom Retro-Pie-Projekt setzt für Sega Master System und Sega Game Gear auf den Emulator Osmose, für den die Tastenbelegung leider nicht durch eine Oberfläche konfiguriert werden kann. Stattdessen sind die Keycodes des Controllers als Argumente zu übergeben. Der folgende Test funktioniert mit meinem Playstation-3-Controller:

./RetroPie/emulators/osmose-0.8.1+rpi20121122/osmose RetroPie/roms/mastersystem/rom.sms -joy -joy1 15 -joy2 14 -joystart 3

Dies belegt die Tasten Quadrat, X und Start. Somit kann dies in Emulationstation hinterlegt werden, damit dies auch aus dem Kioskmodus funktioniert.

~/.emulationstation/es_systems.cfg

DESCNAME=Sega Game Gear
...
COMMAND=/home/pi/RetroPie/emulators/osmose-0.8.1+rpi20121122/osmose %ROM% -joy -joy1 15 -joy2 14 -joystart 3 -tv -fs

...

DESCNAME=Sega Master System II
...
COMMAND=/home/pi/RetroPie/emulators/osmose-0.8.1+rpi20121122/osmose %ROM% -joy -joy1 15 -joy2 14 -tv -fs

Leider bleibt bei mir das doch recht fatale Problem, dass der Emulator keinen Ton ausgibt. In den meisten Foren wird von Osmose auf dem Pi abgeraten, man solle statt Fehler zu beheben einen anderen konfigurieren. Ob mir das ein paar Sega-Klassiker wert sind habe ich noch nicht entschieden.

]]>
http://sgaul.de/2014/05/02/retropie-master-system-und-game-gear-mit-ps3-controller/feed/ 0
RetroPie: Playstation-3-Controller nutzen http://sgaul.de/2014/04/30/retropie-playstation-3-controller-nutzen/ http://sgaul.de/2014/04/30/retropie-playstation-3-controller-nutzen/#comments Wed, 30 Apr 2014 13:20:16 +0000 http://sgaul.de/?p=2614 Weiterlesen ]]> Der Controller der Playstation 3 macht im Retro-Pie-System und insbesondere Emulationstation einige Probleme. So scheint der Controller-Einrichtungsassistent permanent Tastendrücke (-drucke(?), -drucks(!?)) zu registrieren, was wohl auf die Bewegungssensoren zurückzuführen ist. Wird nichts registriert muss man ihn mit einem Druck auf die Playstation-Taste aktivieren.

Nach der verkorksten Kalibrierung sollte man Emulationstation mit F4 verlassen und sich manuell an den Konfigurationsdateien versuchen. Ich hoffe mal dass die Keycodes dieses Controllertyps immer gleich sind und meine Konfigurationen auch anderen helfen können.

Emulationstation

Im „Kiosk“ nutze ich das Steuerkreuz, bestätige meine Auswahl mit der X-Taste und öffne das Hauptmenü (vor allem zum Herunterfahren mit der Playstation-Taste.

/home/pi/.emulationstation/es_input.cfg

<?xml version="1.0"?>
<inputList>
	<inputConfig type="keyboard" />
	<inputConfig type="joystick" deviceName="Sony PLAYSTATION(R)3 Controller">
		<input name="a" type="button" id="14" value="1" />
		<input name="b" type="button" id="15" value="-1" />
		<input name="down" type="button" id="6" value="1" />
		<input name="left" type="button" id="7" value="-1" />
		<input name="menu" type="button" id="16" value="1" />
		<input name="pagedown" type="button" id="10" value="1" />
		<input name="pageup" type="button" id="11" value="-1" />
		<input name="right" type="button" id="5" value="1" />
		<input name="select" type="button" id="12" value="1" />
		<input name="up" type="button" id="4" value="-1" />
	</inputConfig>
</inputList>

Retroarch (die meisten Emulatoren)

Hiermit lassen sich die meisten Emulatoren (wie das SNES) sinnvoll mit dem PS3-Pad steuern:

/home/pi/RetroPie/configs/all/retroarch.cfg

input_player1_joypad_index = "0"
input_player1_b_btn = "15"
input_player1_y_btn = "13"
input_player1_select_btn = "0"
input_player1_start_btn = "3"
input_player1_up_btn = "4"
input_player1_down_btn = "6"
input_player1_left_btn = "7"
input_player1_right_btn = "5"
input_player1_a_btn = "14"
input_player1_x_btn = "16"
input_player1_l_btn = "10"
input_player1_r_btn = "11"
input_player1_l2_btn = "8"
input_player1_r2_btn = "9"
input_player1_l3_btn = "1"
input_player1_r3_btn = "2"
input_player1_l_x_plus_axis = "+0"
input_player1_l_x_minus_axis = "-0"
input_player1_l_y_plus_axis = "+1"
input_player1_l_y_minus_axis = "-1"
input_player1_r_x_plus_axis = "+2"
input_player1_r_x_minus_axis = "-2"
input_player1_r_y_plus_axis = "+3"
input_player1_r_y_minus_axis = "-3"
fps_show = "false"
libretro_path = "/home/pi/RetroPie/emulatorcores/fceu-next/fceumm-code/fceumm_libretro.so"
rewind_enable = "false"
rewind_granularity = "1"
video_shader_enable = "false"
video_aspect_ratio = "-1.000000"
video_xscale = "3.000000"
autosave_interval = "0"
video_yscale = "3.000000"
video_crop_overscan = "true"
video_scale_integer = "false"
video_smooth = "true"
video_threaded = "false"
video_fullscreen = "true"
video_refresh_rate = "59.950001"
video_driver = "gl"
video_vsync = "true"
video_hard_sync = "false"
video_hard_sync_frames = "0"
video_black_frame_insertion = "false"
video_swap_interval = "1"
video_gpu_screenshot = "true"
video_rotation = "0"
screenshot_directory = "default"
aspect_ratio_index = "7"
audio_rate_control = "true"
audio_rate_control_delta = "0.005000"
audio_driver = "alsathread"
audio_out_rate = "48000"
system_directory = "default"
savefile_directory = "default"
savestate_directory = "default"
video_shader_dir = "default"
rgui_browser_directory = "default"
rgui_config_directory = "default"
rgui_show_start_screen = "false"
game_history_size = "100"
overlay_directory = "default"
input_overlay_opacity = "1.000000"
input_overlay_scale = "1.000000"
gamma_correction = "false"
triple_buffering_enable = "false"
soft_filter_enable = "false"
flicker_filter_enable = "false"
flicker_filter_index = "0"
soft_filter_index = "0"
current_resolution_id = "0"
custom_viewport_width = "640"
custom_viewport_height = "478"
custom_viewport_x = "0"
custom_viewport_y = "1"
video_font_size = "48.000000"
config_save_on_exit = "true"
sound_mode = "0"
state_slot = "0"
audio_mute = "0"
custom_bgm_enable = "false"
input_driver = "sdl"
input_device_p1 = "0"
input_libretro_device_p1 = "1"
input_device_p2 = "0"
input_player2_joypad_index = "1"
input_libretro_device_p2 = "1"
input_device_p3 = "0"
input_player3_joypad_index = "2"
input_libretro_device_p3 = "1"
input_device_p4 = "0"
input_player4_joypad_index = "3"
input_libretro_device_p4 = "1"
input_device_p5 = "0"
input_player5_joypad_index = "4"
input_libretro_device_p5 = "1"
input_device_p6 = "0"
input_player6_joypad_index = "5"
input_libretro_device_p6 = "1"
input_device_p7 = "0"
input_player7_joypad_index = "6"
input_libretro_device_p7 = "1"
input_device_p8 = "0"
input_player8_joypad_index = "7"
input_libretro_device_p8 = "1"
input_player1_b = "z"
input_player1_b_axis = "nul"
input_player1_y = "a"
input_player1_y_axis = "nul"
input_player1_select = "rshift"
input_player1_select_axis = "nul"
input_player1_start = "enter"
input_player1_start_axis = "nul"
input_player1_up = "up"
input_player1_up_axis = "nul"
input_player1_down = "down"
input_player1_down_axis = "nul"
input_player1_left = "left"
input_player1_left_axis = "nul"
input_player1_right = "right"
input_player1_right_axis = "nul"
input_player1_a = "x"
input_player1_a_axis = "nul"
input_player1_x = "s"
input_player1_x_axis = "nul"
input_player1_l = "q"
input_player1_l_axis = "nul"
input_player1_r = "w"
input_player1_r_axis = "nul"
input_player1_l2 = "nul"
input_player1_l2_axis = "nul"
input_player1_r2 = "nul"
input_player1_r2_axis = "nul"
input_player1_l3 = "nul"
input_player1_l3_axis = "nul"
input_player1_r3 = "nul"
input_player1_r3_axis = "nul"
input_player1_l_x_plus = "nul"
input_player1_l_x_plus_btn = "nul"
input_player1_l_x_minus = "nul"
input_player1_l_x_minus_btn = "nul"
input_player1_l_y_plus = "nul"
input_player1_l_y_plus_btn = "nul"
input_player1_l_y_minus = "nul"
input_player1_l_y_minus_btn = "nul"
input_player1_r_x_plus = "nul"
input_player1_r_x_plus_btn = "nul"
input_player1_r_x_minus = "nul"
input_player1_r_x_minus_btn = "nul"
input_player1_r_y_plus = "nul"
input_player1_r_y_plus_btn = "nul"
input_player1_r_y_minus = "nul"
input_player1_r_y_minus_btn = "nul"
input_player1_turbo = "nul"
input_player1_turbo_btn = "nul"
input_player1_turbo_axis = "nul"
input_toggle_fast_forward = "space"
input_toggle_fast_forward_btn = "nul"
input_toggle_fast_forward_axis = "nul"
input_hold_fast_forward = "l"
input_hold_fast_forward_btn = "nul"
input_hold_fast_forward_axis = "nul"
input_load_state = "f4"
input_load_state_btn = "nul"
input_load_state_axis = "nul"
input_save_state = "f2"
input_save_state_btn = "nul"
input_save_state_axis = "nul"
input_toggle_fullscreen = "f"
input_toggle_fullscreen_btn = "nul"
input_toggle_fullscreen_axis = "nul"
input_exit_emulator = "escape"
input_exit_emulator_btn = "nul"
input_exit_emulator_axis = "nul"
input_state_slot_increase = "f7"
input_state_slot_increase_btn = "nul"
input_state_slot_increase_axis = "nul"
input_state_slot_decrease = "f6"
input_state_slot_decrease_btn = "nul"
input_state_slot_decrease_axis = "nul"
input_rewind = "r"
input_rewind_btn = "nul"
input_rewind_axis = "nul"
input_movie_record_toggle = "o"
input_movie_record_toggle_btn = "nul"
input_movie_record_toggle_axis = "nul"
input_pause_toggle = "p"
input_pause_toggle_btn = "nul"
input_pause_toggle_axis = "nul"
input_frame_advance = "k"
input_frame_advance_btn = "nul"
input_frame_advance_axis = "nul"
input_reset = "h"
input_reset_btn = "nul"
input_reset_axis = "nul"
input_shader_next = "m"
input_shader_next_btn = "nul"
input_shader_next_axis = "nul"
input_shader_prev = "n"
input_shader_prev_btn = "nul"
input_shader_prev_axis = "nul"
input_cheat_index_plus = "y"
input_cheat_index_plus_btn = "nul"
input_cheat_index_plus_axis = "nul"
input_cheat_index_minus = "t"
input_cheat_index_minus_btn = "nul"
input_cheat_index_minus_axis = "nul"
input_cheat_toggle = "u"
input_cheat_toggle_btn = "nul"
input_cheat_toggle_axis = "nul"
input_screenshot = "f8"
input_screenshot_btn = "nul"
input_screenshot_axis = "nul"
input_dsp_config = "c"
input_dsp_config_btn = "nul"
input_dsp_config_axis = "nul"
input_audio_mute = "f9"
input_audio_mute_btn = "nul"
input_audio_mute_axis = "nul"
input_netplay_flip_players = "i"
input_netplay_flip_players_btn = "nul"
input_netplay_flip_players_axis = "nul"
input_slowmotion = "e"
input_slowmotion_btn = "nul"
input_slowmotion_axis = "nul"
input_enable_hotkey = "nul"
input_enable_hotkey_btn = "nul"
input_enable_hotkey_axis = "nul"
input_volume_up = "add"
input_volume_up_btn = "nul"
input_volume_up_axis = "nul"
input_volume_down = "subtract"
input_volume_down_btn = "nul"
input_volume_down_axis = "nul"
input_overlay_next = "nul"
input_overlay_next_btn = "nul"
input_overlay_next_axis = "nul"
input_disk_eject_toggle = "nul"
input_disk_eject_toggle_btn = "nul"
input_disk_eject_toggle_axis = "nul"
input_disk_next = "nul"
input_disk_next_btn = "nul"
input_disk_next_axis = "nul"
input_grab_mouse_toggle = "f11"
input_grab_mouse_toggle_btn = "nul"
input_grab_mouse_toggle_axis = "nul"
input_menu_toggle = "f1"
input_menu_toggle_btn = "2"
input_menu_toggle_axis = "nul"
input_player2_b = "nul"
input_player2_b_btn = "nul"
input_player2_b_axis = "nul"
input_player2_y = "nul"
input_player2_y_btn = "nul"
input_player2_y_axis = "nul"
input_player2_select = "nul"
input_player2_select_btn = "nul"
input_player2_select_axis = "nul"
input_player2_start = "nul"
input_player2_start_btn = "nul"
input_player2_start_axis = "nul"
input_player2_up = "nul"
input_player2_up_btn = "nul"
input_player2_up_axis = "nul"
input_player2_down = "nul"
input_player2_down_btn = "nul"
input_player2_down_axis = "nul"
input_player2_left = "nul"
input_player2_left_btn = "nul"
input_player2_left_axis = "nul"
input_player2_right = "nul"
input_player2_right_btn = "nul"
input_player2_right_axis = "nul"
input_player2_a = "nul"
input_player2_a_btn = "nul"
input_player2_a_axis = "nul"
input_player2_x = "nul"
input_player2_x_btn = "nul"
input_player2_x_axis = "nul"
input_player2_l = "nul"
input_player2_l_btn = "nul"
input_player2_l_axis = "nul"
input_player2_r = "nul"
input_player2_r_btn = "nul"
input_player2_r_axis = "nul"
input_player2_l2 = "nul"
input_player2_l2_btn = "nul"
input_player2_l2_axis = "nul"
input_player2_r2 = "nul"
input_player2_r2_btn = "nul"
input_player2_r2_axis = "nul"
input_player2_l3 = "nul"
input_player2_l3_btn = "nul"
input_player2_l3_axis = "nul"
input_player2_r3 = "nul"
input_player2_r3_btn = "nul"
input_player2_r3_axis = "nul"
input_player2_l_x_plus = "nul"
input_player2_l_x_plus_btn = "nul"
input_player2_l_x_plus_axis = "nul"
input_player2_l_x_minus = "nul"
input_player2_l_x_minus_btn = "nul"
input_player2_l_x_minus_axis = "nul"
input_player2_l_y_plus = "nul"
input_player2_l_y_plus_btn = "nul"
input_player2_l_y_plus_axis = "nul"
input_player2_l_y_minus = "nul"
input_player2_l_y_minus_btn = "nul"
input_player2_l_y_minus_axis = "nul"
input_player2_r_x_plus = "nul"
input_player2_r_x_plus_btn = "nul"
input_player2_r_x_plus_axis = "nul"
input_player2_r_x_minus = "nul"
input_player2_r_x_minus_btn = "nul"
input_player2_r_x_minus_axis = "nul"
input_player2_r_y_plus = "nul"
input_player2_r_y_plus_btn = "nul"
input_player2_r_y_plus_axis = "nul"
input_player2_r_y_minus = "nul"
input_player2_r_y_minus_btn = "nul"
input_player2_r_y_minus_axis = "nul"
input_player2_turbo = "nul"
input_player2_turbo_btn = "nul"
input_player2_turbo_axis = "nul"
input_player3_b = "nul"
input_player3_b_btn = "nul"
input_player3_b_axis = "nul"
input_player3_y = "nul"
input_player3_y_btn = "nul"
input_player3_y_axis = "nul"
input_player3_select = "nul"
input_player3_select_btn = "nul"
input_player3_select_axis = "nul"
input_player3_start = "nul"
input_player3_start_btn = "nul"
input_player3_start_axis = "nul"
input_player3_up = "nul"
input_player3_up_btn = "nul"
input_player3_up_axis = "nul"
input_player3_down = "nul"
input_player3_down_btn = "nul"
input_player3_down_axis = "nul"
input_player3_left = "nul"
input_player3_left_btn = "nul"
input_player3_left_axis = "nul"
input_player3_right = "nul"
input_player3_right_btn = "nul"
input_player3_right_axis = "nul"
input_player3_a = "nul"
input_player3_a_btn = "nul"
input_player3_a_axis = "nul"
input_player3_x = "nul"
input_player3_x_btn = "nul"
input_player3_x_axis = "nul"
input_player3_l = "nul"
input_player3_l_btn = "nul"
input_player3_l_axis = "nul"
input_player3_r = "nul"
input_player3_r_btn = "nul"
input_player3_r_axis = "nul"
input_player3_l2 = "nul"
input_player3_l2_btn = "nul"
input_player3_l2_axis = "nul"
input_player3_r2 = "nul"
input_player3_r2_btn = "nul"
input_player3_r2_axis = "nul"
input_player3_l3 = "nul"
input_player3_l3_btn = "nul"
input_player3_l3_axis = "nul"
input_player3_r3 = "nul"
input_player3_r3_btn = "nul"
input_player3_r3_axis = "nul"
input_player3_l_x_plus = "nul"
input_player3_l_x_plus_btn = "nul"
input_player3_l_x_plus_axis = "nul"
input_player3_l_x_minus = "nul"
input_player3_l_x_minus_btn = "nul"
input_player3_l_x_minus_axis = "nul"
input_player3_l_y_plus = "nul"
input_player3_l_y_plus_btn = "nul"
input_player3_l_y_plus_axis = "nul"
input_player3_l_y_minus = "nul"
input_player3_l_y_minus_btn = "nul"
input_player3_l_y_minus_axis = "nul"
input_player3_r_x_plus = "nul"
input_player3_r_x_plus_btn = "nul"
input_player3_r_x_plus_axis = "nul"
input_player3_r_x_minus = "nul"
input_player3_r_x_minus_btn = "nul"
input_player3_r_x_minus_axis = "nul"
input_player3_r_y_plus = "nul"
input_player3_r_y_plus_btn = "nul"
input_player3_r_y_plus_axis = "nul"
input_player3_r_y_minus = "nul"
input_player3_r_y_minus_btn = "nul"
input_player3_r_y_minus_axis = "nul"
input_player3_turbo = "nul"
input_player3_turbo_btn = "nul"
input_player3_turbo_axis = "nul"
input_player4_b = "nul"
input_player4_b_btn = "nul"
input_player4_b_axis = "nul"
input_player4_y = "nul"
input_player4_y_btn = "nul"
input_player4_y_axis = "nul"
input_player4_select = "nul"
input_player4_select_btn = "nul"
input_player4_select_axis = "nul"
input_player4_start = "nul"
input_player4_start_btn = "nul"
input_player4_start_axis = "nul"
input_player4_up = "nul"
input_player4_up_btn = "nul"
input_player4_up_axis = "nul"
input_player4_down = "nul"
input_player4_down_btn = "nul"
input_player4_down_axis = "nul"
input_player4_left = "nul"
input_player4_left_btn = "nul"
input_player4_left_axis = "nul"
input_player4_right = "nul"
input_player4_right_btn = "nul"
input_player4_right_axis = "nul"
input_player4_a = "nul"
input_player4_a_btn = "nul"
input_player4_a_axis = "nul"
input_player4_x = "nul"
input_player4_x_btn = "nul"
input_player4_x_axis = "nul"
input_player4_l = "nul"
input_player4_l_btn = "nul"
input_player4_l_axis = "nul"
input_player4_r = "nul"
input_player4_r_btn = "nul"
input_player4_r_axis = "nul"
input_player4_l2 = "nul"
input_player4_l2_btn = "nul"
input_player4_l2_axis = "nul"
input_player4_r2 = "nul"
input_player4_r2_btn = "nul"
input_player4_r2_axis = "nul"
input_player4_l3 = "nul"
input_player4_l3_btn = "nul"
input_player4_l3_axis = "nul"
input_player4_r3 = "nul"
input_player4_r3_btn = "nul"
input_player4_r3_axis = "nul"
input_player4_l_x_plus = "nul"
input_player4_l_x_plus_btn = "nul"
input_player4_l_x_plus_axis = "nul"
input_player4_l_x_minus = "nul"
input_player4_l_x_minus_btn = "nul"
input_player4_l_x_minus_axis = "nul"
input_player4_l_y_plus = "nul"
input_player4_l_y_plus_btn = "nul"
input_player4_l_y_plus_axis = "nul"
input_player4_l_y_minus = "nul"
input_player4_l_y_minus_btn = "nul"
input_player4_l_y_minus_axis = "nul"
input_player4_r_x_plus = "nul"
input_player4_r_x_plus_btn = "nul"
input_player4_r_x_plus_axis = "nul"
input_player4_r_x_minus = "nul"
input_player4_r_x_minus_btn = "nul"
input_player4_r_x_minus_axis = "nul"
input_player4_r_y_plus = "nul"
input_player4_r_y_plus_btn = "nul"
input_player4_r_y_plus_axis = "nul"
input_player4_r_y_minus = "nul"
input_player4_r_y_minus_btn = "nul"
input_player4_r_y_minus_axis = "nul"
input_player4_turbo = "nul"
input_player4_turbo_btn = "nul"
input_player4_turbo_axis = "nul"
input_player5_b = "nul"
input_player5_b_btn = "nul"
input_player5_b_axis = "nul"
input_player5_y = "nul"
input_player5_y_btn = "nul"
input_player5_y_axis = "nul"
input_player5_select = "nul"
input_player5_select_btn = "nul"
input_player5_select_axis = "nul"
input_player5_start = "nul"
input_player5_start_btn = "nul"
input_player5_start_axis = "nul"
input_player5_up = "nul"
input_player5_up_btn = "nul"
input_player5_up_axis = "nul"
input_player5_down = "nul"
input_player5_down_btn = "nul"
input_player5_down_axis = "nul"
input_player5_left = "nul"
input_player5_left_btn = "nul"
input_player5_left_axis = "nul"
input_player5_right = "nul"
input_player5_right_btn = "nul"
input_player5_right_axis = "nul"
input_player5_a = "nul"
input_player5_a_btn = "nul"
input_player5_a_axis = "nul"
input_player5_x = "nul"
input_player5_x_btn = "nul"
input_player5_x_axis = "nul"
input_player5_l = "nul"
input_player5_l_btn = "nul"
input_player5_l_axis = "nul"
input_player5_r = "nul"
input_player5_r_btn = "nul"
input_player5_r_axis = "nul"
input_player5_l2 = "nul"
input_player5_l2_btn = "nul"
input_player5_l2_axis = "nul"
input_player5_r2 = "nul"
input_player5_r2_btn = "nul"
input_player5_r2_axis = "nul"
input_player5_l3 = "nul"
input_player5_l3_btn = "nul"
input_player5_l3_axis = "nul"
input_player5_r3 = "nul"
input_player5_r3_btn = "nul"
input_player5_r3_axis = "nul"
input_player5_l_x_plus = "nul"
input_player5_l_x_plus_btn = "nul"
input_player5_l_x_plus_axis = "nul"
input_player5_l_x_minus = "nul"
input_player5_l_x_minus_btn = "nul"
input_player5_l_x_minus_axis = "nul"
input_player5_l_y_plus = "nul"
input_player5_l_y_plus_btn = "nul"
input_player5_l_y_plus_axis = "nul"
input_player5_l_y_minus = "nul"
input_player5_l_y_minus_btn = "nul"
input_player5_l_y_minus_axis = "nul"
input_player5_r_x_plus = "nul"
input_player5_r_x_plus_btn = "nul"
input_player5_r_x_plus_axis = "nul"
input_player5_r_x_minus = "nul"
input_player5_r_x_minus_btn = "nul"
input_player5_r_x_minus_axis = "nul"
input_player5_r_y_plus = "nul"
input_player5_r_y_plus_btn = "nul"
input_player5_r_y_plus_axis = "nul"
input_player5_r_y_minus = "nul"
input_player5_r_y_minus_btn = "nul"
input_player5_r_y_minus_axis = "nul"
input_player5_turbo = "nul"
input_player5_turbo_btn = "nul"
input_player5_turbo_axis = "nul"
input_player6_b = "nul"
input_player6_b_btn = "nul"
input_player6_b_axis = "nul"
input_player6_y = "nul"
input_player6_y_btn = "nul"
input_player6_y_axis = "nul"
input_player6_select = "nul"
input_player6_select_btn = "nul"
input_player6_select_axis = "nul"
input_player6_start = "nul"
input_player6_start_btn = "nul"
input_player6_start_axis = "nul"
input_player6_up = "nul"
input_player6_up_btn = "nul"
input_player6_up_axis = "nul"
input_player6_down = "nul"
input_player6_down_btn = "nul"
input_player6_down_axis = "nul"
input_player6_left = "nul"
input_player6_left_btn = "nul"
input_player6_left_axis = "nul"
input_player6_right = "nul"
input_player6_right_btn = "nul"
input_player6_right_axis = "nul"
input_player6_a = "nul"
input_player6_a_btn = "nul"
input_player6_a_axis = "nul"
input_player6_x = "nul"
input_player6_x_btn = "nul"
input_player6_x_axis = "nul"
input_player6_l = "nul"
input_player6_l_btn = "nul"
input_player6_l_axis = "nul"
input_player6_r = "nul"
input_player6_r_btn = "nul"
input_player6_r_axis = "nul"
input_player6_l2 = "nul"
input_player6_l2_btn = "nul"
input_player6_l2_axis = "nul"
input_player6_r2 = "nul"
input_player6_r2_btn = "nul"
input_player6_r2_axis = "nul"
input_player6_l3 = "nul"
input_player6_l3_btn = "nul"
input_player6_l3_axis = "nul"
input_player6_r3 = "nul"
input_player6_r3_btn = "nul"
input_player6_r3_axis = "nul"
input_player6_l_x_plus = "nul"
input_player6_l_x_plus_btn = "nul"
input_player6_l_x_plus_axis = "nul"
input_player6_l_x_minus = "nul"
input_player6_l_x_minus_btn = "nul"
input_player6_l_x_minus_axis = "nul"
input_player6_l_y_plus = "nul"
input_player6_l_y_plus_btn = "nul"
input_player6_l_y_plus_axis = "nul"
input_player6_l_y_minus = "nul"
input_player6_l_y_minus_btn = "nul"
input_player6_l_y_minus_axis = "nul"
input_player6_r_x_plus = "nul"
input_player6_r_x_plus_btn = "nul"
input_player6_r_x_plus_axis = "nul"
input_player6_r_x_minus = "nul"
input_player6_r_x_minus_btn = "nul"
input_player6_r_x_minus_axis = "nul"
input_player6_r_y_plus = "nul"
input_player6_r_y_plus_btn = "nul"
input_player6_r_y_plus_axis = "nul"
input_player6_r_y_minus = "nul"
input_player6_r_y_minus_btn = "nul"
input_player6_r_y_minus_axis = "nul"
input_player6_turbo = "nul"
input_player6_turbo_btn = "nul"
input_player6_turbo_axis = "nul"
input_player7_b = "nul"
input_player7_b_btn = "nul"
input_player7_b_axis = "nul"
input_player7_y = "nul"
input_player7_y_btn = "nul"
input_player7_y_axis = "nul"
input_player7_select = "nul"
input_player7_select_btn = "nul"
input_player7_select_axis = "nul"
input_player7_start = "nul"
input_player7_start_btn = "nul"
input_player7_start_axis = "nul"
input_player7_up = "nul"
input_player7_up_btn = "nul"
input_player7_up_axis = "nul"
input_player7_down = "nul"
input_player7_down_btn = "nul"
input_player7_down_axis = "nul"
input_player7_left = "nul"
input_player7_left_btn = "nul"
input_player7_left_axis = "nul"
input_player7_right = "nul"
input_player7_right_btn = "nul"
input_player7_right_axis = "nul"
input_player7_a = "nul"
input_player7_a_btn = "nul"
input_player7_a_axis = "nul"
input_player7_x = "nul"
input_player7_x_btn = "nul"
input_player7_x_axis = "nul"
input_player7_l = "nul"
input_player7_l_btn = "nul"
input_player7_l_axis = "nul"
input_player7_r = "nul"
input_player7_r_btn = "nul"
input_player7_r_axis = "nul"
input_player7_l2 = "nul"
input_player7_l2_btn = "nul"
input_player7_l2_axis = "nul"
input_player7_r2 = "nul"
input_player7_r2_btn = "nul"
input_player7_r2_axis = "nul"
input_player7_l3 = "nul"
input_player7_l3_btn = "nul"
input_player7_l3_axis = "nul"
input_player7_r3 = "nul"
input_player7_r3_btn = "nul"
input_player7_r3_axis = "nul"
input_player7_l_x_plus = "nul"
input_player7_l_x_plus_btn = "nul"
input_player7_l_x_plus_axis = "nul"
input_player7_l_x_minus = "nul"
input_player7_l_x_minus_btn = "nul"
input_player7_l_x_minus_axis = "nul"
input_player7_l_y_plus = "nul"
input_player7_l_y_plus_btn = "nul"
input_player7_l_y_plus_axis = "nul"
input_player7_l_y_minus = "nul"
input_player7_l_y_minus_btn = "nul"
input_player7_l_y_minus_axis = "nul"
input_player7_r_x_plus = "nul"
input_player7_r_x_plus_btn = "nul"
input_player7_r_x_plus_axis = "nul"
input_player7_r_x_minus = "nul"
input_player7_r_x_minus_btn = "nul"
input_player7_r_x_minus_axis = "nul"
input_player7_r_y_plus = "nul"
input_player7_r_y_plus_btn = "nul"
input_player7_r_y_plus_axis = "nul"
input_player7_r_y_minus = "nul"
input_player7_r_y_minus_btn = "nul"
input_player7_r_y_minus_axis = "nul"
input_player7_turbo = "nul"
input_player7_turbo_btn = "nul"
input_player7_turbo_axis = "nul"
input_player8_b = "nul"
input_player8_b_btn = "nul"
input_player8_b_axis = "nul"
input_player8_y = "nul"
input_player8_y_btn = "nul"
input_player8_y_axis = "nul"
input_player8_select = "nul"
input_player8_select_btn = "nul"
input_player8_select_axis = "nul"
input_player8_start = "nul"
input_player8_start_btn = "nul"
input_player8_start_axis = "nul"
input_player8_up = "nul"
input_player8_up_btn = "nul"
input_player8_up_axis = "nul"
input_player8_down = "nul"
input_player8_down_btn = "nul"
input_player8_down_axis = "nul"
input_player8_left = "nul"
input_player8_left_btn = "nul"
input_player8_left_axis = "nul"
input_player8_right = "nul"
input_player8_right_btn = "nul"
input_player8_right_axis = "nul"
input_player8_a = "nul"
input_player8_a_btn = "nul"
input_player8_a_axis = "nul"
input_player8_x = "nul"
input_player8_x_btn = "nul"
input_player8_x_axis = "nul"
input_player8_l = "nul"
input_player8_l_btn = "nul"
input_player8_l_axis = "nul"
input_player8_r = "nul"
input_player8_r_btn = "nul"
input_player8_r_axis = "nul"
input_player8_l2 = "nul"
input_player8_l2_btn = "nul"
input_player8_l2_axis = "nul"
input_player8_r2 = "nul"
input_player8_r2_btn = "nul"
input_player8_r2_axis = "nul"
input_player8_l3 = "nul"
input_player8_l3_btn = "nul"
input_player8_l3_axis = "nul"
input_player8_r3 = "nul"
input_player8_r3_btn = "nul"
input_player8_r3_axis = "nul"
input_player8_l_x_plus = "nul"
input_player8_l_x_plus_btn = "nul"
input_player8_l_x_plus_axis = "nul"
input_player8_l_x_minus = "nul"
input_player8_l_x_minus_btn = "nul"
input_player8_l_x_minus_axis = "nul"
input_player8_l_y_plus = "nul"
input_player8_l_y_plus_btn = "nul"
input_player8_l_y_plus_axis = "nul"
input_player8_l_y_minus = "nul"
input_player8_l_y_minus_btn = "nul"
input_player8_l_y_minus_axis = "nul"
input_player8_r_x_plus = "nul"
input_player8_r_x_plus_btn = "nul"
input_player8_r_x_plus_axis = "nul"
input_player8_r_x_minus = "nul"
input_player8_r_x_minus_btn = "nul"
input_player8_r_x_minus_axis = "nul"
input_player8_r_y_plus = "nul"
input_player8_r_y_plus_btn = "nul"
input_player8_r_y_plus_axis = "nul"
input_player8_r_y_minus = "nul"
input_player8_r_y_minus_btn = "nul"
input_player8_r_y_minus_axis = "nul"
input_player8_turbo = "nul"
input_player8_turbo_btn = "nul"
input_player8_turbo_axis = "nul"
libretro_info_path = ""
cheat_database_path = ""
video_shader = ""
audio_device = ""
game_history_path = ""
joypad_autoconfig_dir = ""
input_overlay = ""
input_joypad_driver = ""

Mir ist das Konzept hinter “all” nicht ganz klar, zur Sicherheit habe ich diese Datei auch in alle emulatorspezifischen Unterverzeichnisse neben all kopiert.

In den Retroarch-Emulatoren funktioniert auch der Einrichtungsassistent, den man im Spiel mit F1 aufrufen und mit Pfeiltasten, y (Abbruch) und x (Bestätigung) bedienen kann. Hier sollte man unter Settings zunächst einstellen, dass die Konfigurationsänderungen auch gespeichert werden. Unter Input-Settings kann man sich nun die Knöpfe entsprechend belegen. So lässt sich statt des Steuerkreuzes auch der Analogstick für die Klassiker nutzen.

]]>
http://sgaul.de/2014/04/30/retropie-playstation-3-controller-nutzen/feed/ 0
RetroPie: Retrospiele auf dem Raspberry Pi http://sgaul.de/2014/04/28/retropie-retrospiele-auf-dem-raspberry-pi/ http://sgaul.de/2014/04/28/retropie-retrospiele-auf-dem-raspberry-pi/#comments Mon, 28 Apr 2014 19:20:08 +0000 http://sgaul.de/?p=2609 Weiterlesen ]]> Das Retro-Pie-Projekt bringt alte Spieleklassiker auf den Raspberry Pi. Ob Nintendo, Sega oder Atari, das fertige Image bringt bereits eine interessante Auswahl an Emulatoren mit. Der Pi schlägt sich dabei bis ins 16-Bit-Zeitalter ohne Probleme. Super-Nintendo-Spiele laufen in der Regel mit 50 Bildern in der Sekunde.

Nach dem Herunterladen des RetroPie Project SD card Image (der Google-Mirror ging für mich am besten) dieses einfach auf eine SD-Karte schieben:

sudo dd if=RetroPieImage_ver1.9.1.img of=/dev/mmcblk0

Um herauszufinden, welches das SD-Device ist kann man `lsblk` nutzen.

Die fertige Karte in den Pi und schon startet Emulationstation, eine Art Kioskmodus für alle enthaltenen Emulatoren und Roms. Nach der Konfiguration kann man hier bequem mit dem Controller alles auswählen, ohne eine Tastatur bemühen zu müssen. Emulationstation sollte man erstmal mit F4 verlassen und raspi-config starten. Hier kann man Sprache und Tastaturlayout wählen und die aktuelle Partition auf die gesamte SD-Karte vergrößern. Es gibt eine Reihe weiterer Optionen, die beiden genannten haben für mich zunächst gereicht.

Um Roms auf die SD zu kriegen, stecke ich die Karte in meinen Laptop und schiebe entpackte Roms in den passenden Unterordner in /home/pi/RetroPie/roms.

]]>
http://sgaul.de/2014/04/28/retropie-retrospiele-auf-dem-raspberry-pi/feed/ 3
YAML-Manipulation mit Python http://sgaul.de/2014/04/13/yaml-manipulation-mit-python/ http://sgaul.de/2014/04/13/yaml-manipulation-mit-python/#comments Sun, 13 Apr 2014 15:23:24 +0000 http://sgaul.de/?p=2597 Weiterlesen ]]> Ich mache es zwanzig mal am Tag: t('admin.users.show.name'), die YAML-Datei öffnen, in denen sich die Übersetzungen befinden, den Pfad absuchen und ggf. fehlende Teile einfügen und zuletzt die Übersetzung hineinschreiben und speichern. Wirklich kreativ ist dabei nur das Formulieren, den Rest würde ich gern automatisieren. Dies sollte jedem Rails-Entwickler eine Hilfe sein.

Mein erster Schritt ist eine simple Kommandozeilen-Applikation, die eine YAML-Datei und einen Pfad liest, den Pfad findet oder erstellt, den aktuellen Wert anzeigt, einen neuen entgegennimmt, einträgt und alles in eine Datei schreibt. Das alles möglichst nah an Vim und Sublime, so dass man das ganze später in ein Plugin überführen kann. Somit bietet sich Python an, schon allein um mal wieder was neues zu nutzen.

YAML parsen und Pfade finden

Für diesen Job kommt PyYAML zum Einsatz. Leider machte mir die wirklich schlechte Dokumentation der Bibliothek zu schaffen, im Endeffekt ist es aber recht einfach:

import yaml

input_file = open(args.input_file, "r")
yaml_doc = yaml.load(input_file)

Die Rückgabe ist ein Dict (oder etwas das sich so verhält). In diesem Dictonary kann man nun den gewünschten Pfad suchen:

def find_or_create_parent(yaml_doc, search_string):
  yaml_component = yaml_doc
  for path_component in search_string.split(".")[:-1]:
    if path_component in yaml_component:
      if type(yaml_component[path_component]) is dict:
        yaml_component = yaml_component[path_component]
      else:
        sys.exit("Error: Component {0} of {1} exists but is not an object".format(
          path_component, search_string
        ))
    else:
      yaml_component[path_component] = dict()
      yaml_component = yaml_component[path_component]
  return yaml_component

Ich suche oder erstelle hier das Vaterobjekt des gesuchten Wertes. Ist ein Zwischenschritt kein Dict, breche ich aus Sicherheitsgründen ab, damit keine einfachen Werte oder aber Listen verloren gehen.

Werte lesen und ändern

Mit dem Vaterelement ist das Lesen und Schreiben recht einfach:

def current_value(yaml_doc, seach_string):
  parent =  find_or_create_parent(yaml_doc, search_string)
  if search_string.split(".")[-1] in parent:
    return parent[search_string.split(".")[-1]]
  else:
    return None

def set_value(yaml_doc, search_string, value):
  parent = find_or_create_parent(yaml_doc, search_string)
  parent[search_string.split(".")[-1]] = value

Mit diesen Funktionen kann man nun eine einfache CLI-Applikation formulieren:

print "Current value of {0}:".format(search_string)
print current_value(yaml_doc, search_string)
new_value = raw_input("Please enter new value: ").decode(sys.stdin.encoding)
set_value(yaml_doc, search_string, new_value)

Hier hat mir vor allem Pythons UTF-8-Handhabung Probleme gemacht, weshalb der Wert aus raw_input hier noch dekodiert werden muss. Keine Ahnung wie das schöner geht, in meinen Augen aber erschreckend dass man sich um sowas noch kümmern muss.

In Datei oder Terminal ausgeben

Für das Dumpen des YAML-Dokuments habe sich für mich folgende Einstellungen bewährt:

def dump(yaml_doc):
  return yaml.dump(yaml_doc, default_flow_style=False, allow_unicode=True, encoding="utf-8")

Auch hier scheint UTF-8 wieder etwas Unerwartetes zu sein. Aber es funktioniert:

if output_file_path is None:
  print ""
  print dump(yaml_doc)
else:
  output_file = open(output_file_path, "wb")
  output_file.write(dump(yaml_doc))
  print "Changes have been written into {0}".format(output_file_path)

Das Ergebnis

Mit Argparse konnte ich eine Demoanwendung erstellen, die im nächsten Schritt als Vorlage für ein Plugin für Vim oder Sublime dienen soll:

usage: test.py [-h] input_file search_string [output_file]

positional arguments:
  input_file
  search_string
  output_file

optional arguments:
  -h, --help     show this help message and exit

Das Skript kann mit einer Datei und einer Pfadangabe aufgerufen werden. Es zeigt den aktuellen Wert, erfragt einen neuen und schreibt dies in eine angegebene Datei oder das Terminal.

Offene Probleme

Mit dieser Lösung habe ich vor allem ein Problem: PyYAML liest und schreibt die Datei vollständig. Auch Zeilen, in denen nichts geändert wurde, werden daher an die präferierte PyYAML-Syntax angepasst. So wird beispielsweise

de:
  users:
    edit:
      text: |
        Hier können Sie Ihr Passwort anpassen.
        Lassen Sie die Felder frei, um keine Änderung vorzunehmen.

zu

de:                                                                                                                                                                                                  
  users:                                                                                                                                                                                             
    edit:
      change_password: Passwort ändern
      text: 'Hier können Sie Ihr Passwort anpassen.\nLassen Sie die Felder frei, um keine Änderung vorzunehmen.'

Das ist immer noch gültiges YAML, macht sich im Git-Diff aber nicht so schön.

Das gesamte Skript

#!/usr/bin/env python

import sys
import argparse
import yaml

parser = argparse.ArgumentParser()
parser.add_argument('input_file')
parser.add_argument('search_string')
parser.add_argument('output_file', default=None, nargs="?")
args = parser.parse_args()

input_file = open(args.input_file, "r")
search_string = args.search_string
output_file_path = args.output_file
yaml_doc = yaml.load(input_file)


def find_or_create_parent(yaml_doc, search_string):
  yaml_component = yaml_doc
  for path_component in search_string.split(".")[:-1]:
    if path_component in yaml_component:
      if type(yaml_component[path_component]) is dict:
        yaml_component = yaml_component[path_component]
      else:
        sys.exit("Error: Component {0} of {1} exists but is not an object".format(
          path_component, search_string
        ))
    else:
      yaml_component[path_component] = dict()
      yaml_component = yaml_component[path_component]
  return yaml_component

def current_value(yaml_doc, seach_string):
  parent =  find_or_create_parent(yaml_doc, search_string)
  if search_string.split(".")[-1] in parent:
    return parent[search_string.split(".")[-1]]
  else:
    return None

def set_value(yaml_doc, search_string, value):
  parent = find_or_create_parent(yaml_doc, search_string)
  parent[search_string.split(".")[-1]] = value

def dump(yaml_doc):
  return yaml.dump(yaml_doc, default_flow_style=False, allow_unicode=True, encoding="utf-8")


print "Current value of {0}:".format(search_string)
print current_value(yaml_doc, search_string)
new_value = raw_input("Please enter new value: ").decode(sys.stdin.encoding)
set_value(yaml_doc, search_string, new_value)

if output_file_path is None:
  print ""
  print dump(yaml_doc)
else:
  output_file = open(output_file_path, "wb")
  output_file.write(dump(yaml_doc))
  print "Changes have been written into {0}".format(output_file_path)

Abhängigkeiten

Natürlich muss Python installiert sein. Wenn sich das Skript über ein fehlendes YAML-Modul beschwert (ImportError: No module named yaml), lässt sich das in Ubuntu folgendermaßen nachrüsten:

sudo apt-get install python-yaml
]]>
http://sgaul.de/2014/04/13/yaml-manipulation-mit-python/feed/ 0
Guard erkennt keine Sublime-3-Änderungen http://sgaul.de/2014/03/27/guard-erkennt-keine-sublime-3-aenderungen/ http://sgaul.de/2014/03/27/guard-erkennt-keine-sublime-3-aenderungen/#comments Thu, 27 Mar 2014 15:30:36 +0000 http://sgaul.de/?p=2583 Weiterlesen ]]> Sublime Text 3 hat das Speichern von Dateien umgestellt, so dass Guard dieses nicht mehr als Änderung registriert. Abhilfe schafft hier eine Rückkehr zum alten Ansatz. Hierfür unter „Preferences“ > „Settings — User“ die folgende Option setzen:

{
  ...
  "atomic_save": false,
  ...
}
]]>
http://sgaul.de/2014/03/27/guard-erkennt-keine-sublime-3-aenderungen/feed/ 0