diff --git a/README b/README
index bc4e83d3bc2f388362806d9afc39213392a1e656..a0a66bf20e7448aaf0a4607b1fc6f6b97a6be183 100644
--- a/README
+++ b/README
@@ -12,33 +12,41 @@ copy libgstkaldionline2.so to ./asr/ or change GST_PLUGIN_PATH in main.py to poi
 
 Run:
 
-python2 main.py
+- The main program:
+./start.sh
 
-Doc:
+- The osc server:
+python2 osc.py
 
+Doc:
 developing with pygtk3: http://lazka.github.io/pgi-docs/, https://python-gtk-3-tutorial.readthedocs.org/en/latest/
 
 Todo:
 
+DONE configuration for osc
+DONE non intrusive animated scrolling
+DONE make configuration box options persistant
+DONE push words through osc
+DONE global action send which deals with
+DONE - show a warning (optional)
+DONE - send action through osc
+DONE - show an action performed message (message log with timing?)
+
 events = click action or words to resynchronize ?
     click line = synchronize to that line
     click section = select that section
     click action = perform action and synchronize to the next line
 
-non intrusive animated scrolling
-global action send which deals with
-    - show a warning (optional)
-    - sned action through osc
-    - show an action performed message (message log with timing?)
 add global timer which shows elapsed time
 change xml view to reflect already performed actions, already recognized text
-configuration for osc
+move slu to asr
 
 make selector a proper window
-make configuration box options persistant
 
 add thread for slu
 
 allow sequence advance in slu, add UI for that
 
+remove section changer UI
 
+add global keybindings (1-9 for sections, y/n)...
diff --git a/action.py b/action.py
deleted file mode 100644
index 777b865e20855037f53d37813add8237f168f489..0000000000000000000000000000000000000000
--- a/action.py
+++ /dev/null
@@ -1,35 +0,0 @@
-from gi.repository import Gtk
-
-class ActionView(Gtk.HBox):
-    def __init__(self):
-        super(ActionView, self).__init__()
-        self.actions = set(['next', 'previous', 'light-on', 'light-off'])
-
-        self.pack_start(Gtk.Label('Actions:'), False, False, 5)
-        for action in self.actions:
-            button = Gtk.Button(action)
-            button.connect('clicked', self._perform)
-            self.pack_start(button, False, False, 5)
-
-        self.confiermer = None
-
-    def _perform(self, button):
-        action = button.get_label()
-        if self.confirmer:
-            self.confirmer.confirm('Perform action "%s"?' % action, 4, lambda: self.perform(action))
-        else:
-            self.perform(action)
-
-    def perform(self, action):
-        if action in self.actions:
-            print 'PERFORM', action
-            return True
-        return False
-
-    def actions(self):
-        return self.actions
-
-    def set_confirmer(self, confirmer):
-        self.confirmer = confirmer
-
-
diff --git a/actions.py b/actions.py
new file mode 100644
index 0000000000000000000000000000000000000000..89e47bce122578f773cb008908efe2732beed683
--- /dev/null
+++ b/actions.py
@@ -0,0 +1,38 @@
+import osc, log
+
+class Action:
+    def __init__(self, text, **kwargs):
+        self.text = text
+        for key, value in kwargs:
+            setattr(self, key, value)
+
+class ActionManager:
+    def __init__(self, host, port, confirmer, highlighter, logger=log.ConsoleLogger()):
+        #self.client = osc.Client(host, port)
+        self.confirmer = confirmer
+        self.highlighter = highlighter
+        self.logger = logger
+
+    def confirmed_perform(self, action):
+        osc.client.send_action(action)
+        self.highlighter.highlight(action)
+        self.logger.log(action)
+
+    def perform(self, action, confirm=True, timeout=3):
+        if confirm:
+            self.confirmer.confirm('Perform action "%s"?' % action.text, time, lambda: self.confirmed_perform(action))
+        else:
+            self.confirmed_perform(action)
+
+manager = None
+
+def setup(confirmer, highlighter, logger):
+    global manager
+    manager = ActionManager(confirmer, highlighter, logger)
+
+def perform_action(action, confirm=True, timeout=3):
+    global manager
+    manager.perform(action, confirm, timeout)
+        
+
+
diff --git a/animate.py b/animate.py
index 2e76748b6e5faf5333de1b916b3560bd7e390c83..bb71dab024eb2d5f658da5733cf4adadc74c5bae 100644
--- a/animate.py
+++ b/animate.py
@@ -9,13 +9,13 @@ def cancel():
 LINEAR=1
 DECELERATE=2
 
-def animate_value(callback, current, target, policy=DECELERATE):
+def animate_value(callback, current, target, policy=DECELERATE, speed=32):
     global timer
     if current != target:
         if policy == DECELERATE:
             delta = abs(target - current) / 2
         else:
-            delta = 32
+            delta = speed
         if current > target:
             current -= delta
         else:
diff --git a/asr.py b/asr.py
index 2eca0f81b9a2037e31d6a236894256d32bd8ec1d..1299008dafb17f416260fb7d45289db354e24f9b 100644
--- a/asr.py
+++ b/asr.py
@@ -8,6 +8,8 @@ GObject.threads_init()
 Gdk.threads_init()
 Gst.init(None)
 
+import osc
+
 class ASR(Gtk.HBox):
     def __init__(self, asr_config, hyp_callback = None, partial_hyp_callback = None):
         super(ASR, self).__init__()
@@ -110,6 +112,10 @@ class ASR(Gtk.HBox):
         Gdk.threads_enter()
         if len(self.hyp) == 0:
             self.hyp = ['']
+
+        if hyp != self.hyp[-1]:
+            osc.client.send_words(len(self.hyp), hyp)
+
         self.hyp[-1] = hyp
         if self.partial_hyp_callback:
             self.partial_hyp_callback(self.hyp)
diff --git a/confirm.py b/confirm.py
index 26381b0498a22184d7d344b17ca8bff4796419e2..3a022991de3811c8c7753c0d4441a25872a1f598 100644
--- a/confirm.py
+++ b/confirm.py
@@ -31,6 +31,7 @@ class ConfirmationBox(Gtk.HBox):
         self.yes_button.get_child().set_text("YES (%d)" % self.counter)
 
         self.timer = GObject.timeout_add_seconds(1, self.countdown)
+        self.yes_button.grab_focus()
         self.show()
 
     def click_yes(self, button = None):
diff --git a/log.py b/log.py
new file mode 100644
index 0000000000000000000000000000000000000000..5a462e59d5c62da4ba56a8b8bdbde82d7b6138b8
--- /dev/null
+++ b/log.py
@@ -0,0 +1,37 @@
+from gi.repository import Gtk
+import actions
+
+class ConsoleLogger:
+    def __init__(self):
+        pass
+
+    def log(self, action):
+        print 'Performed:', action.text
+
+class GtkLogger(Gtk.HBox):
+    def __init__(self):
+        super(Log, self).__init__()
+        self.text = Gtk.TextView()
+        self.text.set_editable(False)
+        self.text.set_cursor_visible(False)
+        self.buffer = self.text.get_buffer()
+        self.scrolled = Gtk.ScrolledWindow()
+        self.scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.ALWAYS)
+        self.scrolled.add_with_viewport(self.text)
+        #self.scrolled.set_size_request(-1, 100)
+        self.pack_start(self.scrolled, True, True, 5)
+
+    def log(self, action):
+        self.buffer.insert_at_cursor('Performed: %s\n' % action.text)
+        adj = self.scrolled.get_vadjustment()
+        adj.set_value(adj.get_upper() - adj.get_page_size())
+
+if __name__ == '__main__':
+    window = Gtk.Window()
+    window.connect("destroy", Gtk.main_quit)
+    logger = GtkLogger()
+    window.add(logger)
+    window.show_all()
+    logger.log(actions.Action('coucou'))
+    Gtk.main()
+
diff --git a/main.py b/main.py
index 24aaed9447c8762a33ad040d1f50342742eaa188..8417cfc2900a6ad71bc2170b46103c36e9c399b2 100644
--- a/main.py
+++ b/main.py
@@ -5,8 +5,10 @@ import os
 import glob, re
 
 # set to location of libgstkaldionline2.so
-os.environ['GST_PLUGIN_PATH'] = './asr/'
+directory = os.path.dirname(__file__) or '.'
+os.environ['GST_PLUGIN_PATH'] = directory + '/asr/'
 os.environ['GTK_THEME'] = 'light'
+print 'gst plugin path =', os.environ['GST_PLUGIN_PATH']
 
 # import gtk stuff
 from threading import Thread
@@ -22,21 +24,23 @@ import signal
 signal.signal(signal.SIGINT, signal.SIG_DFL)
 
 # import local stuff
-import confirm, asr, action, xmlview_widgets
-import levenstein, SLU
+import confirm, asr, actions, xmlview_widgets
+import sections
+import levenstein, SLU, osc
 
 class ScriptedASR(Gtk.Window):
-    def __init__(self, xml_filename, asr_config_file):
+    def __init__(self, xml_filename, asr_config_file, osc_host, osc_port):
         super(ScriptedASR, self).__init__()
 
+        osc.setup(osc_host, osc_port)
+
         self.connect("destroy", self.quit)
-        self.set_default_size(800,600)
+        self.set_default_size(1024, 768)
         self.set_border_width(10)
-        self.set_title('ScriptedASR [%s]' % xml_filename)
+        self.set_title('ASR Transcript [xml=%s asr=%s osc=%s:%s]' % (xml_filename, asr_config_file, osc_host, osc_port))
         vbox = Gtk.VBox()
 
-        import section
-        self.sections = section.SectionManager()
+        self.sections = sections.SectionManager()
         vbox.pack_start(self.sections, False, True, 5)
 
         self.xmlview = xmlview_widgets.XmlView(xml_filename)
@@ -140,8 +144,8 @@ if __name__ == '__main__':
         xml_filename = sys.argv[1]
     if len(sys.argv) > 2:
         asr_config_file = sys.argv[2]
-    xml_filename, asr_config_file = selector.ModelSelector(xml_filename, asr_config_file).run()
-    if xml_filename == None or asr_config_file == None:
+    xml_filename, asr_config_file, osc_host, osc_port = selector.ModelSelector(xml_filename, asr_config_file).run()
+    if xml_filename == None or asr_config_file == None or osc_host == None or osc_port == None:
         sys.exit(0)
-    app = ScriptedASR(xml_filename, asr_config_file)
+    app = ScriptedASR(xml_filename, asr_config_file, osc_host, osc_port)
     Gtk.main()
diff --git a/osc.py b/osc.py
index 9ffce770476cb51f955bee9488fe0fc30268ba6e..212f3e5e7b1e2892c7fa7c33e010ad25593952b8 100644
--- a/osc.py
+++ b/osc.py
@@ -1,6 +1,6 @@
 import liblo, sys
 
-class ActionSender:
+class Client:
     def __init__(self, host = '127.0.0.1', port = 1234):
         try:
             self.target = liblo.Address(host, port)
@@ -13,24 +13,35 @@ class ActionSender:
         except:
             print >>sys.stderr, 'OSC: failed to send message [%s]' % str(message)
 
-class ActionReceiver:
+    def send_action(self, action):
+        self.send('ACTION: %s' % action.text)
+
+    def send_words(self, start, words):
+        self.send('WORDS(%d): %s' % (start, words))
+
+class Server:
     def __init__(self, host = '127.0.0.1', port = 1234):
         print 'OSC: Creating server at %s:%d' % (host, port)
         self.server = liblo.Server(port)
         self.server.add_method(None, None, self.callback)
 
     def callback(self, message):
-        print 'OSC: Recieved [%s]' % message
+        print 'OSC: Received [%s]' % message
 
     def run(self):
         print 'OSC: Waiting for messages'
         while True:
             self.server.recv(100)
 
+client = None
+def setup(host, port):
+    global client
+    client = Client(host, port)
+
 if __name__ == '__main__':
     if len(sys.argv) > 1:
-        client = ActionSender()
+        client = Client()
         client.send(' '.join(sys.argv[1:]))
     else:
-        server = ActionReceiver()
+        server = Server()
         server.run()
diff --git a/section.py b/sections.py
similarity index 100%
rename from section.py
rename to sections.py
diff --git a/selector.py b/selector.py
index c9bdca656b99b538b4a3621d2b74d41b7582ddc4..c995378c055e89057875183cc2036fb871d5d8c2 100644
--- a/selector.py
+++ b/selector.py
@@ -1,54 +1,60 @@
 from gi.repository import GObject, Gtk, Gdk
-import os, sys, glob
+import os, sys, glob, re
 import config
 
 class ModelSelector(Gtk.Dialog):
     def __init__(self, xml_filename = '', asr_model = ''):
         super(ModelSelector, self).__init__()
+        self.options = {'xml_filename': xml_filename, 'asr_model': asr_model, 'osc_host': '127.0.0.1', 'osc_port': '1234'}
+        self.load_options()
+
         self.set_title('Configuration')
+        self.set_border_width(10)
         self.add_button("Cancel", Gtk.ResponseType.CANCEL)
         self.add_button("OK", Gtk.ResponseType.OK)
         box = self.get_content_area()
 
         xml_box = Gtk.HBox()
-        xml_box.pack_start(Gtk.Label('XML file:'), False, False, 10)
+        xml_box.pack_start(Gtk.Label('XML file:'), False, False, 5)
         xml_entry = Gtk.Entry()
-        xml_entry.set_text(xml_filename)
-        xml_entry.set_width_chars(len(xml_filename))
+        xml_entry.set_text(self.options['xml_filename'])
+        xml_entry.set_width_chars(len(self.options['xml_filename']))
         self.xml_entry = xml_entry
-        xml_box.pack_start(xml_entry, True, True, 10)
+        xml_box.pack_start(xml_entry, True, True, 5)
         xml_button = Gtk.Button("Choose...")
         xml_button.connect('clicked', self.show_filechooser)
-        xml_box.pack_start(xml_button, False, False, 10)
+        xml_box.pack_start(xml_button, False, False, 5)
 
         box.pack_start(xml_box, False, False, 5)
 
         model_box = Gtk.HBox()
-        model_box.pack_start(Gtk.Label('ASR model:'), False, False, 10)
+        model_box.pack_start(Gtk.Label('ASR model:'), False, False, 5)
         model_chooser = Gtk.ComboBoxText()
         model_chooser.set_entry_text_column(0)
         target_index = 0
         for i, model in enumerate(self.list_models()):
             model_chooser.append_text(model)
-            if asr_model == self.models[i]:
+            if self.options['asr_model'] == self.models[i]:
                 target_index = i
         model_chooser.set_active(target_index)
         self.model_chooser = model_chooser
-        model_box.pack_start(model_chooser, True, True, 10)
+        model_box.pack_start(model_chooser, True, True, 5)
 
         box.pack_start(model_box, False, False, 5)
 
         osc_box = Gtk.HBox()
-        osc_box.pack_start(Gtk.Label('OSC host:'), False, False, 10)
+        osc_box.pack_start(Gtk.Label('OSC host:'), False, False, 5)
         osc_host = Gtk.Entry()
-        osc_host.set_text('127.0.0.1')
+        osc_host.set_text(self.options['osc_host'])
         osc_host.set_width_chars(len(osc_host.get_text()))
-        osc_box.pack_start(osc_host, True, True, 10)
-        osc_box.pack_start(Gtk.Label('Port:'), False, False, 10)
+        self.osc_host = osc_host
+        osc_box.pack_start(osc_host, True, True, 5)
+        osc_box.pack_start(Gtk.Label('Port:'), False, False, 5)
         osc_port = Gtk.Entry()
-        osc_port.set_text('1234')
+        osc_port.set_text(self.options['osc_port'])
         osc_port.set_width_chars(len(osc_port.get_text()))
-        osc_box.pack_start(osc_port, True, True, 10)
+        self.osc_port = osc_port
+        osc_box.pack_start(osc_port, True, True, 5)
 
         box.pack_start(osc_box, False, False, 5)
 
@@ -56,16 +62,38 @@ class ModelSelector(Gtk.Dialog):
         okButton = self.get_widget_for_response(response_id=Gtk.ResponseType.OK)
         okButton.set_can_default(True)
         okButton.grab_default()
+        okButton.grab_focus()
 
         self.show_all()
 
+    def save_options(self):
+        try:
+            with open('.options.txt', 'w') as fp:
+                for key, value in self.options.items():
+                    print >>fp, '%s: %s' % (key, str(value))
+        except:
+            print 'Warning: could not save options'
+
+    def load_options(self):
+        try:
+            with open('.options.txt') as fp:
+                for line in fp:
+                    line = line.strip()
+                    found = re.search('^([^:]*):(.*)', line)
+                    if found:
+                        key = found.group(1).strip()
+                        value = found.group(2).strip()
+                        self.options[key] = value
+        except:
+            print 'Warning: could not load options'
+
     def show_filechooser(self, button):
         dialog = Gtk.FileChooserDialog("Please choose a file", self, Gtk.FileChooserAction.OPEN, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK))
         filter_text = Gtk.FileFilter()
         filter_text.set_name("XML files")
         filter_text.add_mime_type("text/xml")
         dialog.add_filter(filter_text)
-        dialog.set_current_folder('%s/data' % os.path.dirname(__file__))
+        dialog.set_current_folder('%s/data' % (os.path.dirname(__file__) or '.'))
 
         response = dialog.run()
         if response == Gtk.ResponseType.OK:
@@ -90,9 +118,12 @@ class ModelSelector(Gtk.Dialog):
     def run(self):
         response = super(ModelSelector, self).run()
         if response != Gtk.ResponseType.OK:
-            return None, None
-        asr_model = self.models[self.model_chooser.get_active()]
-        xml_filename = self.xml_entry.get_text()
+            return None, None, None, None
+        self.options['asr_model'] = self.models[self.model_chooser.get_active()]
+        self.options['xml_filename'] = self.xml_entry.get_text()
+        self.options['osc_host'] = self.osc_host.get_text()
+        self.options['osc_port'] = self.osc_port.get_text()
+        self.save_options()
         self.destroy()
-        return xml_filename, asr_model
+        return self.options['xml_filename'], self.options['asr_model'], self.options['osc_host'], self.options['osc_port']