LibCecSharp: bump client versions to 1.7.1
[deb_libcec.git] / src / cec-config-gui / CecConfigGUI.cs
index 15d3f0de7fb684cd566c1e90ee81bbfa90c4b8de..1bd2f071ba5da0e2eb52546cee3ebc25eb04ffda 100644 (file)
@@ -1,7 +1,5 @@
 using System;
 using System.Collections.Generic;
-using System.ComponentModel;
-using System.Data;
 using System.Drawing;
 using System.Text;
 using System.Windows.Forms;
@@ -10,10 +8,19 @@ using CecSharp;
 using CecConfigGui.actions;
 using System.Globalization;
 using System.IO;
+using System.Xml;
 
 namespace CecConfigGui
 {
-  public partial class CecConfigGUI : Form
+  internal enum ConfigTab
+  {
+    Configuration,
+    KeyConfiguration,
+    Tester,
+    Log
+  }
+
+  public partial class CecConfigGUI : AsyncForm
   {
     public CecConfigGUI()
     {
@@ -21,249 +28,1068 @@ namespace CecConfigGui
       Config.DeviceTypes.Types[0] = CecDeviceType.RecordingDevice;
       Config.DeviceName = "CEC Config";
       Config.GetSettingsFromROM = true;
-      Config.ClientVersion = CecClientVersion.Version1_5_0;
+      Config.ClientVersion = CecClientVersion.Version1_7_1;
       Callbacks = new CecCallbackWrapper(this);
       Config.SetCallbacks(Callbacks);
+      LoadXMLConfiguration(ref Config);
+      Lib = new LibCecSharp(Config);
 
       InitializeComponent();
-      Lib = new LibCecSharp(Config);
+      LoadButtonConfiguration();
 
-      ActiveProcess = new ConnectToDevice(ref Lib);
-      ActiveProcess.EventHandler += new EventHandler<UpdateEvent>(ProcessEventHandler);
-      (new Thread(new ThreadStart(ActiveProcess.Run))).Start();
+      ActiveProcess = new ConnectToDevice(ref Lib, Config);
+      ActiveProcess.EventHandler += ProcessEventHandler;
+      (new Thread(ActiveProcess.Run)).Start();
     }
 
-    public int ReceiveCommand(CecCommand command)
+    private bool LoadXMLConfiguration(ref LibCECConfiguration config)
     {
-      return 1;
+      bool gotConfig = false;
+      string xbmcDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + @"\XBMC\userdata\peripheral_data";
+      string defaultDir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
+      string file = defaultDir + @"\usb_2548_1001.xml";
+      if (File.Exists(xbmcDir + @"\usb_2548_1001.xml"))
+        file = xbmcDir + @"\usb_2548_1001.xml";
+
+      if (File.Exists(file))
+      {
+        XmlTextReader reader = new XmlTextReader(file);
+        while (reader.Read())
+        {
+          gotConfig = true;
+          switch (reader.NodeType)
+          {
+            case XmlNodeType.Element:
+              if (reader.Name.ToLower() == "setting")
+              {
+                string name = string.Empty;
+                string value = string.Empty;
+
+                while (reader.MoveToNextAttribute())
+                {
+                  if (reader.Name.ToLower().Equals("id"))
+                    name = reader.Value.ToLower();
+                  if (reader.Name.ToLower().Equals("value"))
+                    value = reader.Value;
+                }
+
+                switch (name)
+                {
+                  case "cec_hdmi_port":
+                    {
+                      byte iPort;
+                      if (byte.TryParse(value, out iPort))
+                        config.HDMIPort = iPort;
+                    }
+                    break;
+                  case "connected_device":
+                    {
+                      ushort iDevice;
+                      if (ushort.TryParse(value, out iDevice))
+                        config.BaseDevice = (CecLogicalAddress)iDevice;
+                    }
+                    break;
+                  case "cec_power_on_startup":
+                    if (value.Equals("1") || value.ToLower().Equals("true") || value.ToLower().Equals("yes"))
+                    {
+                      config.ActivateSource = true;
+                      config.WakeDevices.Set(CecLogicalAddress.Tv);
+                    }
+                    break;
+                  case "cec_power_off_shutdown":
+                    if (value.Equals("1") || value.ToLower().Equals("true") || value.ToLower().Equals("yes"))
+                      config.PowerOffDevices.Set(CecLogicalAddress.Broadcast);
+                    break;
+                  case "cec_standby_screensaver":
+                    config.PowerOffScreensaver = value.Equals("1") || value.ToLower().Equals("true") || value.ToLower().Equals("yes");
+                    break;
+                  case "standby_pc_on_tv_standby":
+                    config.PowerOffOnStandby = value.Equals("1") || value.ToLower().Equals("true") || value.ToLower().Equals("yes");
+                    break;
+                  case "use_tv_menu_language":
+                    config.UseTVMenuLanguage = value.Equals("1") || value.ToLower().Equals("true") || value.ToLower().Equals("yes");
+                    break;
+                  // 1.5.0+ settings
+                  case "physical_address":
+                    {
+                      ushort physicalAddress = 0;
+                      if (ushort.TryParse(value, NumberStyles.AllowHexSpecifier, null, out physicalAddress))
+                        config.PhysicalAddress = physicalAddress;
+                    }
+                    break;
+                  case "device_type":
+                    {
+                      ushort iType;
+                      if (ushort.TryParse(value, out iType))
+                        config.DeviceTypes.Types[0] = (CecDeviceType)iType;
+                    }
+                    break;
+                  case "tv_vendor":
+                    {
+                      UInt64 iVendor;
+                      if (UInt64.TryParse(value, out iVendor))
+                        config.TvVendor = (CecVendorId)iVendor;
+                    }
+                    break;
+                  case "wake_devices":
+                    {
+                      config.WakeDevices.Clear();
+                      string[] split = value.Split(new[] { ' ' });
+                      foreach (string dev in split)
+                      {
+                        byte iLogicalAddress;
+                        if (byte.TryParse(dev, out iLogicalAddress))
+                          config.WakeDevices.Set((CecLogicalAddress)iLogicalAddress);
+                      }
+                    }
+                    break;
+                  case "standby_devices":
+                    {
+                      config.PowerOffDevices.Clear();
+                      string[] split = value.Split(new char[] { ' ' });
+                      foreach (string dev in split)
+                      {
+                        byte iLogicalAddress;
+                        if (byte.TryParse(dev, out iLogicalAddress))
+                          config.PowerOffDevices.Set((CecLogicalAddress)iLogicalAddress);
+                      }
+                    }
+                    break;
+                  case "enabled":
+                    break;
+                  case "port":
+                    //TODO
+                    break;
+                  // 1.5.1 settings
+                  case "send_inactive_source":
+                    config.SendInactiveSource = value.Equals("1") || value.ToLower().Equals("true") || value.ToLower().Equals("yes");
+                    break;
+                  default:
+                    break;
+                }
+              }
+              break;
+            default:
+              break;
+          }
+        }
+      }
+      return gotConfig;
     }
 
-    public int ReceiveKeypress(CecKeypress key)
+    private void LoadButtonConfiguration()
     {
-      return 1;
+      //TODO load the real configuration
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Select", (new CecKeypress { Keycode = CecUserControlCode.Select }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Up", (new CecKeypress { Keycode = CecUserControlCode.Up }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Down", (new CecKeypress { Keycode = CecUserControlCode.Down }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Left", (new CecKeypress { Keycode = CecUserControlCode.Left }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Right", (new CecKeypress { Keycode = CecUserControlCode.Right }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Right+Up", (new CecKeypress { Keycode = CecUserControlCode.RightUp }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Right+Down", (new CecKeypress { Keycode = CecUserControlCode.RightDown }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Left+Up", (new CecKeypress { Keycode = CecUserControlCode.LeftUp }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Left+Down", (new CecKeypress { Keycode = CecUserControlCode.LeftDown }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Root menu", (new CecKeypress { Keycode = CecUserControlCode.RootMenu }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Setup menu", (new CecKeypress { Keycode = CecUserControlCode.SetupMenu }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Contents menu", (new CecKeypress { Keycode = CecUserControlCode.ContentsMenu }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Favourite menu", (new CecKeypress { Keycode = CecUserControlCode.FavoriteMenu }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Exit", (new CecKeypress { Keycode = CecUserControlCode.Exit }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("0", (new CecKeypress { Keycode = CecUserControlCode.Number0 }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("1", (new CecKeypress { Keycode = CecUserControlCode.Number1 }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("2", (new CecKeypress { Keycode = CecUserControlCode.Number2 }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("3", (new CecKeypress { Keycode = CecUserControlCode.Number3 }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("4", (new CecKeypress { Keycode = CecUserControlCode.Number4 }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("5", (new CecKeypress { Keycode = CecUserControlCode.Number5 }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("6", (new CecKeypress { Keycode = CecUserControlCode.Number6 }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("7", (new CecKeypress { Keycode = CecUserControlCode.Number7 }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("8", (new CecKeypress { Keycode = CecUserControlCode.Number8 }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("9", (new CecKeypress { Keycode = CecUserControlCode.Number9 }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem(".", (new CecKeypress { Keycode = CecUserControlCode.Dot }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Enter", (new CecKeypress { Keycode = CecUserControlCode.Enter }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Clear", (new CecKeypress { Keycode = CecUserControlCode.Clear }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Next favourite", (new CecKeypress { Keycode = CecUserControlCode.NextFavorite }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Channel up", (new CecKeypress { Keycode = CecUserControlCode.ChannelUp }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Channel down", (new CecKeypress { Keycode = CecUserControlCode.ChannelDown }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Previous channel", (new CecKeypress { Keycode = CecUserControlCode.PreviousChannel }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Sound select", (new CecKeypress { Keycode = CecUserControlCode.SoundSelect }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Input select", (new CecKeypress { Keycode = CecUserControlCode.InputSelect }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Display information", (new CecKeypress { Keycode = CecUserControlCode.DisplayInformation }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Help", (new CecKeypress { Keycode = CecUserControlCode.Help }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Page up", (new CecKeypress { Keycode = CecUserControlCode.PageUp }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Page down", (new CecKeypress { Keycode = CecUserControlCode.PageDown }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Power", (new CecKeypress { Keycode = CecUserControlCode.Power }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Volume up", (new CecKeypress { Keycode = CecUserControlCode.VolumeUp }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Volume down", (new CecKeypress { Keycode = CecUserControlCode.VolumeDown }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Mute", (new CecKeypress { Keycode = CecUserControlCode.Mute }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Play", (new CecKeypress { Keycode = CecUserControlCode.Play }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Stop", (new CecKeypress { Keycode = CecUserControlCode.Stop }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Pause", (new CecKeypress { Keycode = CecUserControlCode.Pause }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Record", (new CecKeypress { Keycode = CecUserControlCode.Record }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Rewind", (new CecKeypress { Keycode = CecUserControlCode.Rewind }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Fast forward", (new CecKeypress { Keycode = CecUserControlCode.FastForward }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Eject", (new CecKeypress { Keycode = CecUserControlCode.Eject }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Forward", (new CecKeypress { Keycode = CecUserControlCode.Forward }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Backward", (new CecKeypress { Keycode = CecUserControlCode.Backward }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Stop record", (new CecKeypress { Keycode = CecUserControlCode.StopRecord }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Pause record", (new CecKeypress { Keycode = CecUserControlCode.PauseRecord }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Angle", (new CecKeypress { Keycode = CecUserControlCode.Angle }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Sub picture", (new CecKeypress { Keycode = CecUserControlCode.SubPicture }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Video on demand", (new CecKeypress { Keycode = CecUserControlCode.VideoOnDemand }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Electronic program guide", (new CecKeypress { Keycode = CecUserControlCode.ElectronicProgramGuide }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Timer programming", (new CecKeypress { Keycode = CecUserControlCode.TimerProgramming }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Initial configuration", (new CecKeypress { Keycode = CecUserControlCode.InitialConfiguration }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Play (function)", (new CecKeypress { Keycode = CecUserControlCode.PlayFunction }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Pause play (function)", (new CecKeypress { Keycode = CecUserControlCode.PausePlayFunction }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Record (function)", (new CecKeypress { Keycode = CecUserControlCode.RecordFunction }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Pause record (function)", (new CecKeypress { Keycode = CecUserControlCode .PauseRecordFunction }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Stop (function)", (new CecKeypress { Keycode = CecUserControlCode.StopFunction }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Mute (function)", (new CecKeypress { Keycode = CecUserControlCode.MuteFunction }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Restore volume", (new CecKeypress { Keycode = CecUserControlCode.RestoreVolumeFunction }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Tune", (new CecKeypress { Keycode = CecUserControlCode.TuneFunction }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Select media", (new CecKeypress { Keycode = CecUserControlCode.SelectMediaFunction }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Select AV input", (new CecKeypress { Keycode = CecUserControlCode.SelectAVInputFunction }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Select audio input", (new CecKeypress { Keycode = CecUserControlCode.SelectAudioInputFunction }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Power toggle", (new CecKeypress { Keycode = CecUserControlCode.PowerToggleFunction }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Power off", (new CecKeypress { Keycode = CecUserControlCode.PowerOffFunction }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Power on", (new CecKeypress { Keycode = CecUserControlCode.PowerOnFunction }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("F1 (blue)", (new CecKeypress { Keycode = CecUserControlCode.F1Blue }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("F2 (red)", (new CecKeypress { Keycode = CecUserControlCode.F2Red }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("F3 (green)", (new CecKeypress { Keycode = CecUserControlCode.F3Green }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("F4 (yellow)", (new CecKeypress { Keycode = CecUserControlCode.F4Yellow }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("F5", (new CecKeypress { Keycode = CecUserControlCode.F5 }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Data", (new CecKeypress { Keycode = CecUserControlCode.Data }), string.Empty));
+      cecButtonConfigBindingSource.Add(new CecButtonConfigItem("Return (Samsung)", (new CecKeypress { Keycode = CecUserControlCode.SamsungReturn }), string.Empty));
     }
 
-    delegate void AddLogMessageCallback(CecLogMessage message);
-    private void AddLogMessage(CecLogMessage message)
+    private void ProcessEventHandler(object src, UpdateEvent updateEvent)
     {
-      if (tbLog.InvokeRequired)
+      switch (updateEvent.Type)
       {
-        AddLogMessageCallback d = new AddLogMessageCallback(AddLogMessage);
-        try
-        {
-          this.Invoke(d, new object[] { message });
-        }
-        catch (Exception) { }
+        case UpdateEventType.StatusText:
+          SetControlText(lStatus, updateEvent.StringValue);
+          break;
+        case UpdateEventType.PhysicalAddress:
+          Config.PhysicalAddress = (ushort)updateEvent.IntValue;
+          SetControlText(tbPhysicalAddress, string.Format("{0,4:X}", updateEvent.IntValue));
+          break;
+        case UpdateEventType.ProgressBar:
+          SetControlVisible(pProgress, true);
+          SetProgressValue(pProgress, updateEvent.IntValue);
+          break;
+        case UpdateEventType.TVVendorId:
+          TVVendor = (CecVendorId)updateEvent.IntValue;
+          UpdateSelectedDevice();
+          break;
+        case UpdateEventType.BaseDevicePhysicalAddress:
+          SetControlText(lConnectedPhysicalAddress, string.Format("Address: {0,4:X}", updateEvent.IntValue));
+          break;
+        case UpdateEventType.BaseDevice:
+          Config.BaseDevice = (CecLogicalAddress)updateEvent.IntValue;
+          break;
+        case UpdateEventType.HDMIPort:
+          Config.HDMIPort = (byte)updateEvent.IntValue;
+          break;
+        case UpdateEventType.MenuLanguage:
+          SetControlText(cbUseTVMenuLanguage, "Use the TV's language setting" + (updateEvent.StringValue.Length > 0 ? " (" + updateEvent.StringValue + ")" : ""));
+          break;
+        case UpdateEventType.HasAVRDevice:
+          if (HasAVRDevice != updateEvent.BoolValue)
+          {
+            HasAVRDevice = updateEvent.BoolValue;
+            UpdateSelectedDevice();
+          }
+          break;
+        case UpdateEventType.AVRVendorId:
+          AVRVendor = (CecVendorId)updateEvent.IntValue;
+          UpdateSelectedDevice();
+          break;
+        case UpdateEventType.Configuration:
+          SuppressUpdates = true;
+          ConfigurationChanged(updateEvent.ConfigValue);
+          SuppressUpdates = false;
+          break;
+        case UpdateEventType.PollDevices:
+          CheckActiveDevices();
+          break;
+        case UpdateEventType.ProcessCompleted:
+          ActiveProcess = null;
+          SetControlsEnabled(true);
+          if (UpdatingInfoPanel != null)
+          {
+            UpdatingInfoPanel.SetControlEnabled(UpdatingInfoPanel.bUpdate, true);
+            UpdatingInfoPanel = null;
+          }
+          SetControlVisible(pProgress, false);
+          break;
+        case UpdateEventType.ExitApplication:
+          ActiveProcess = null;
+          SetControlsEnabled(false);
+          SetControlVisible(pProgress, false);
+          Application.Exit();
+          break;
       }
+    }
+
+    private void SetControlsEnabled(bool val)
+    {
+      SetControlEnabled(cbPortNumber, val && !cbOverrideAddress.Checked);
+      SetControlEnabled(cbConnectedDevice, cbConnectedDevice.Items.Count > 1 && !cbOverrideAddress.Checked && val);
+      SetControlEnabled(cbOverrideAddress, val);
+      SetControlEnabled(tbPhysicalAddress, val && !Config.AutodetectAddress && cbOverrideAddress.Checked);
+      SetControlEnabled(cbDeviceType, val);
+      SetControlEnabled(cbUseTVMenuLanguage, val);
+      SetControlEnabled(cbActivateSource, val);
+      SetControlEnabled(cbPowerOffScreensaver, val);
+      SetControlEnabled(cbPowerOffOnStandby, val);
+      SetControlEnabled(cbWakeDevices, val);
+      SetControlEnabled(cbPowerOffDevices, val);
+      SetControlEnabled(cbVendorOverride, val);
+      SetControlEnabled(cbVendorId, val && cbVendorOverride.Checked);
+      SetControlEnabled(cbSendInactiveSource, val);
+      SetControlEnabled(bClose, val);
+      SetControlEnabled(bSaveConfig, val);
+      SetControlEnabled(bReloadConfig, val);
+      SetControlEnabled(bRescanDevices, val);
+
+      SetControlEnabled(bSendImageViewOn, val);
+      SetControlEnabled(bStandby, val);
+      SetControlEnabled(bActivateSource, val);
+      SetControlEnabled(bScan, val);
+
+      bool enableVolumeButtons = (GetTargetDevice() == CecLogicalAddress.AudioSystem) && val;
+      SetControlEnabled(bVolUp, enableVolumeButtons);
+      SetControlEnabled(bVolDown, enableVolumeButtons);
+      SetControlEnabled(bMute, enableVolumeButtons);
+    }
+
+    private void tabControl1_SelectedIndexChanged(object sender, EventArgs e)
+    {
+      switch (tabControl1.SelectedIndex)
+      {
+        case 0:
+          SelectedTab = ConfigTab.Configuration;
+          break;
+        case 1:
+          SelectedTab = ConfigTab.KeyConfiguration;
+          break;
+        case 2:
+          SelectedTab = ConfigTab.Tester;
+          break;
+        case 3:
+          SelectedTab = ConfigTab.Log;
+          UpdateLog();
+          break;
+        default:
+          SelectedTab = ConfigTab.Configuration;
+          break;
+      }
+    }
+
+    protected override void Dispose(bool disposing)
+    {
+      if (disposing)
+      {
+        Lib.DisableCallbacks();
+        Lib.StandbyDevices(CecLogicalAddress.Broadcast);
+        Lib.Close();
+      }
+      if (disposing && (components != null))
+      {
+        components.Dispose();
+      }
+      base.Dispose(disposing);
+    }
+
+    #region Actions
+    public void ReloadXmlConfiguration()
+    {
+      LoadXMLConfiguration(ref Config);
+      Lib.SetConfiguration(Config);
+      ConfigurationChanged(Config);
+    }
+
+    public void UpdateInfoPanel(DeviceInformation panel)
+    {
+      if (!SuppressUpdates && ActiveProcess == null)
+      {
+        SetControlsEnabled(false);
+        UpdatingInfoPanel = panel;
+        panel.SetControlEnabled(panel.bUpdate, false);
+        ActiveProcess = new UpdateDeviceInfo(this, ref Lib, panel);
+        ActiveProcess.EventHandler += ProcessEventHandler;
+        (new Thread(ActiveProcess.Run)).Start();
+      }
+    }
+
+    public void SetPhysicalAddress(ushort physicalAddress)
+    {
+      if (!SuppressUpdates && ActiveProcess == null && cbOverrideAddress.Checked)
+      {
+        SetControlsEnabled(false);
+        SetControlText(cbPortNumber, string.Empty);
+        SetControlText(cbConnectedDevice, string.Empty);
+        ActiveProcess = new UpdatePhysicalAddress(ref Lib, physicalAddress);
+        ActiveProcess.EventHandler += ProcessEventHandler;
+        (new Thread(ActiveProcess.Run)).Start();
+      }
+    }
+
+    public void UpdateConfigurationAsync()
+    {
+      if (!SuppressUpdates && ActiveProcess == null)
+      {
+        SetControlsEnabled(false);
+        ActiveProcess = new UpdateConfiguration(ref Lib, Config);
+        ActiveProcess.EventHandler += ProcessEventHandler;
+        (new Thread(ActiveProcess.Run)).Start();
+      }
+    }
+
+    public void SendImageViewOn(CecLogicalAddress address)
+    {
+      if (!SuppressUpdates && ActiveProcess == null)
+      {
+        SetControlsEnabled(false);
+        ActiveProcess = new SendImageViewOn(ref Lib, address);
+        ActiveProcess.EventHandler += ProcessEventHandler;
+        (new Thread(ActiveProcess.Run)).Start();
+      }
+    }
+
+    public void ActivateSource(CecLogicalAddress address)
+    {
+      if (!SuppressUpdates && ActiveProcess == null)
+      {
+        SetControlsEnabled(false);
+        ActiveProcess = new SendActivateSource(ref Lib, address);
+        ActiveProcess.EventHandler += ProcessEventHandler;
+        (new Thread(ActiveProcess.Run)).Start();
+      }
+    }
+
+    public void SendStandby(CecLogicalAddress address)
+    {
+      if (!SuppressUpdates && ActiveProcess == null)
+      {
+        SetControlsEnabled(false);
+        ActiveProcess = new SendStandby(ref Lib, address);
+        ActiveProcess.EventHandler += ProcessEventHandler;
+        (new Thread(ActiveProcess.Run)).Start();
+      }
+    }
+
+    public void ShowDeviceInfo(CecLogicalAddress address)
+    {
+      if (!SuppressUpdates && ActiveProcess == null)
+      {
+        SetControlsEnabled(false);
+        ActiveProcess = new ShowDeviceInfo(this, ref Lib, address);
+        ActiveProcess.EventHandler += ProcessEventHandler;
+        (new Thread(ActiveProcess.Run)).Start();
+      }
+    }
+    #endregion
+
+    #region Configuration tab
+    private void cbOverrideAddress_CheckedChanged(object sender, EventArgs e)
+    {
+      SetControlEnabled(tbPhysicalAddress, ((CheckBox)sender).Checked);
+      SetControlEnabled(cbPortNumber, !((CheckBox)sender).Checked);
+      SetControlEnabled(cbConnectedDevice, !((CheckBox)sender).Checked && cbConnectedDevice.Items.Count > 1);
+    }
+
+    private void tbPhysicalAddress_TextChanged(object sender, EventArgs e)
+    {
+      if (tbPhysicalAddress.Text.Length != 4 ||
+          cbOverrideAddress.Checked)
+        return;
+      ushort physicalAddress = 0;
+      if (!ushort.TryParse(tbPhysicalAddress.Text, NumberStyles.AllowHexSpecifier, null, out physicalAddress))
+        return;
+
+      SetPhysicalAddress(physicalAddress);
+    }
+
+    private void UpdateSelectedDevice()
+    {
+      if (HasAVRDevice)
+        SetComboBoxItems(cbConnectedDevice, Config.BaseDevice == CecLogicalAddress.AudioSystem ? AVRVendorString : TVVendorString, new object[] { TVVendorString, AVRVendorString });
       else
+        SetComboBoxItems(cbConnectedDevice, TVVendorString, new object[] { TVVendorString });
+    }
+
+    public void SetConnectedDevice(CecLogicalAddress address, int portnumber)
+    {
+      if (!SuppressUpdates && ActiveProcess == null)
+      {
+        SetControlsEnabled(false);
+        ActiveProcess = new UpdateConnectedDevice(ref Lib, address, portnumber);
+        ActiveProcess.EventHandler += ProcessEventHandler;
+        (new Thread(ActiveProcess.Run)).Start();
+      }
+    }
+
+    private void connectedDevice_SelectedIndexChanged(object sender, EventArgs e)
+    {
+      SetConnectedDevice(SelectedConnectedDevice, SelectedPortNumber);
+    }
+
+    private void bCancel_Click(object sender, EventArgs e)
+    {
+      Dispose();
+    }
+
+    private void bSave_Click(object sender, EventArgs e)
+    {
+      SetControlsEnabled(false);
+
+      Config.UseTVMenuLanguage = cbUseTVMenuLanguage.Checked;
+      Config.ActivateSource = cbActivateSource.Checked;
+      Config.PowerOffScreensaver = cbPowerOffScreensaver.Checked;
+      Config.PowerOffOnStandby = cbPowerOffOnStandby.Checked;
+      Config.SendInactiveSource = cbSendInactiveSource.Checked;
+      Config.WakeDevices = WakeDevices;
+      Config.PowerOffDevices = PowerOffDevices;
+
+      /* save settings in the eeprom */
+      Lib.PersistConfiguration(Config);
+
+      /* and in xml */
+      if (ActiveProcess == null)
       {
-        if (((int)message.Level & LogLevel) == (int)message.Level)
+        SetControlsEnabled(false);
+        string xbmcDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + @"\XBMC\userdata\peripheral_data";
+        string defaultDir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
+
+        SaveFileDialog dialog = new SaveFileDialog()
         {
-          string strLevel = "";
-          switch (message.Level)
+          Title = "Where do you want to store the settings?",
+          InitialDirectory = Directory.Exists(xbmcDir) ? xbmcDir : defaultDir,
+          FileName = "usb_2548_1001.xml",
+          Filter = "xml files (*.xml)|*.xml|All files (*.*)|*.*",
+          FilterIndex = 1
+        };
+
+        if (dialog.ShowDialog() == DialogResult.OK)
+        {
+          FileStream fs = null;
+          string error = string.Empty;
+          try
           {
-            case CecLogLevel.Error:
-              strLevel = "ERROR:   ";
-              break;
-            case CecLogLevel.Warning:
-              strLevel = "WARNING: ";
-              break;
-            case CecLogLevel.Notice:
-              strLevel = "NOTICE:  ";
-              break;
-            case CecLogLevel.Traffic:
-              strLevel = "TRAFFIC: ";
-              break;
-            case CecLogLevel.Debug:
-              strLevel = "DEBUG:   ";
-              break;
-            default:
-              break;
+            fs = (FileStream)dialog.OpenFile();
+          }
+          catch (Exception ex)
+          {
+            error = ex.Message;
+          }
+          if (fs == null)
+          {
+            MessageBox.Show("Cannot open '" + dialog.FileName + "' for writing" + (error.Length > 0 ? ": " + error : string.Empty ), "Pulse-Eight USB-CEC Adapter", MessageBoxButtons.OK, MessageBoxIcon.Error);
+          }
+          else
+          {
+            StreamWriter writer = new StreamWriter(fs);
+            StringBuilder output = new StringBuilder();
+            output.AppendLine("<settings>");
+            output.AppendLine("<setting id=\"cec_hdmi_port\" value=\"" + Config.HDMIPort + "\" />");
+            output.AppendLine("<setting id=\"connected_device\" value=\"" + (Config.BaseDevice == CecLogicalAddress.AudioSystem ? 5 : 0) + "\" />");
+            output.AppendLine("<setting id=\"cec_power_on_startup\" value=\"" + (Config.ActivateSource ? 1 : 0) + "\" />");
+            output.AppendLine("<setting id=\"cec_power_off_shutdown\" value=\"" + (Config.PowerOffDevices.IsSet(CecLogicalAddress.Broadcast) ? 1 : 0) + "\" />");
+            output.AppendLine("<setting id=\"cec_standby_screensaver\" value=\"" + (Config.PowerOffScreensaver ? 1 : 0) + "\" />");
+            output.AppendLine("<setting id=\"standby_pc_on_tv_standby\" value=\"" + (Config.PowerOffOnStandby ? 1 : 0) + "\" />");
+            output.AppendLine("<setting id=\"use_tv_menu_language\" value=\"" + (Config.UseTVMenuLanguage ? 1 : 0) + "\" />");
+            output.AppendLine("<setting id=\"enabled\" value=\"1\" />");
+            output.AppendLine("<setting id=\"port\" value=\"\" />");
+
+            // only supported by 1.5.0+ clients
+            output.AppendLine("<!-- the following lines are only supported by v1.5.0+ clients -->");
+            output.AppendLine("<setting id=\"activate_source\" value=\"" + (Config.ActivateSource ? 1 : 0) + "\" />");
+            output.AppendLine("<setting id=\"physical_address\" value=\"" + string.Format("{0,4:X}", cbOverrideAddress.Checked ? Config.PhysicalAddress : 0).Trim() + "\" />");
+            output.AppendLine("<setting id=\"device_type\" value=\"" + (int)Config.DeviceTypes.Types[0] + "\" />");
+            output.AppendLine("<setting id=\"tv_vendor\" value=\"" + string.Format("{0,6:X}", (int)Config.TvVendor).Trim() + "\" />");
+
+            output.Append("<setting id=\"wake_devices\" value=\"");
+            StringBuilder strWakeDevices = new StringBuilder();
+            foreach (CecLogicalAddress addr in Config.WakeDevices.Addresses)
+              if (addr != CecLogicalAddress.Unknown)
+                strWakeDevices.Append(" " + (int)addr);
+            output.Append(strWakeDevices.ToString().Trim());
+            output.AppendLine("\" />");
+
+            output.Append("<setting id=\"standby_devices\" value=\"");
+            StringBuilder strSleepDevices = new StringBuilder();
+            foreach (CecLogicalAddress addr in Config.PowerOffDevices.Addresses)
+              if (addr != CecLogicalAddress.Unknown)
+                strSleepDevices.Append(" " + (int)addr);
+            output.Append(strSleepDevices.ToString().Trim()); 
+            output.AppendLine("\" />");
+
+            // only supported by 1.5.1+ clients
+            output.AppendLine("<!-- the following lines are only supported by v1.5.1+ clients -->");
+            output.AppendLine("<setting id=\"send_inactive_source\" value=\"" + (Config.SendInactiveSource ? 1 : 0) + "\" />");
+
+            output.AppendLine("</settings>");
+            writer.Write(output.ToString());
+            writer.Close();
+            fs.Close();
+            fs.Dispose();
+            MessageBox.Show("Settings are stored.", "Pulse-Eight USB-CEC Adapter", MessageBoxButtons.OK, MessageBoxIcon.Information);
           }
-          string strLog = string.Format("{0} {1,16} {2}", strLevel, message.Time, message.Message) + System.Environment.NewLine;
-          tbLog.Text += strLog;
         }
+        SetControlsEnabled(true);
       }
     }
 
-    public int ReceiveLogMessage(CecLogMessage message)
+    private void bReloadConfig_Click(object sender, EventArgs e)
     {
-      AddLogMessage(message);
-      return 1;
+      if (Lib.CanPersistConfiguration())
+      {
+        Lib.GetCurrentConfiguration(Config);
+        ConfigurationChanged(Config);
+      }
+      else
+      {
+        ReloadXmlConfiguration();
+      }
     }
 
-    delegate void SetControlEnabledCallback(Control control, bool val);
-    private void SetControlEnabled(Control control, bool val)
+    private void cbVendorOverride_CheckedChanged(object sender, EventArgs e)
     {
-      if (control.InvokeRequired)
+      if (cbVendorOverride.Checked)
       {
-        SetControlEnabledCallback d = new SetControlEnabledCallback(SetControlEnabled);
-        try
+        cbVendorId.Enabled = true;
+        switch (cbVendorId.Text)
         {
-          this.Invoke(d, new object[] { control, val });
+          case "LG":
+            Config.TvVendor = CecVendorId.LG;
+            break;
+          case "Onkyo":
+            Config.TvVendor = CecVendorId.Onkyo;
+            break;
+          case "Panasonic":
+            Config.TvVendor = CecVendorId.Panasonic;
+            break;
+          case "Philips":
+            Config.TvVendor = CecVendorId.Philips;
+            break;
+          case "Pioneer":
+            Config.TvVendor = CecVendorId.Pioneer;
+            break;
+          case "Samsung":
+            Config.TvVendor = CecVendorId.Samsung;
+            break;
+          case "Sony":
+            Config.TvVendor = CecVendorId.Sony;
+            break;
+          case "Yamaha":
+            Config.TvVendor = CecVendorId.Yamaha;
+            break;
+          default:
+            Config.TvVendor = CecVendorId.Unknown;
+            break;
         }
-        catch (Exception) { }
       }
       else
       {
-        control.Enabled = val;
+        cbVendorId.Enabled = false;
+        Config.TvVendor = CecVendorId.Unknown;
       }
     }
 
-    private void SetControlsEnabled(bool val)
+    private void cbDeviceType_SelectedIndexChanged(object sender, EventArgs e)
     {
-      SetControlEnabled(cbPortNumber, val);
-      SetControlEnabled(cbConnectedDevice, val);
-      SetControlEnabled(tbPhysicalAddress, val);
-      SetControlEnabled(cbDeviceType, val);
-      SetControlEnabled(cbUseTVMenuLanguage, val);
-      SetControlEnabled(cbPowerOnStartup, val);
-      SetControlEnabled(cbPowerOffShutdown, val);
-      SetControlEnabled(cbPowerOffScreensaver, val);
-      SetControlEnabled(cbPowerOffOnStandby, val);
-      SetControlEnabled(bClose, val);
-      SetControlEnabled(bSave, val);
+      CecDeviceType type = SelectedDeviceType;
+      if (type != Config.DeviceTypes.Types[0])
+      {
+        Config.DeviceTypes.Types[0] = type;
+        if (!DeviceChangeWarningDisplayed)
+        {
+          DeviceChangeWarningDisplayed = true;
+          MessageBox.Show("You have changed the device type. Save the configuration, and restart the application to use the new setting.", "Pulse-Eight USB-CEC Adapter", MessageBoxButtons.OK, MessageBoxIcon.Warning);
+        }
+      }
     }
+    #endregion
 
-    delegate void SetControlTextCallback(Control control, string val);
-    private void SetControlText(Control control, string val)
+    #region Key configuration tab
+    delegate void SelectKeypressRowCallback(CecKeypress key);
+    private void SelectKeypressRow(CecKeypress key)
     {
-      if (control.InvokeRequired)
+      if (dgButtons.InvokeRequired)
       {
-        SetControlTextCallback d = new SetControlTextCallback(SetControlText);
+        SelectKeypressRowCallback d = SelectKeypressRow;
         try
         {
-          this.Invoke(d, new object[] { control, val });
+          Invoke(d, new object[] { key });
         }
         catch (Exception) { }
       }
       else
       {
-        control.Text = val;
+        int rowIndex = -1;
+        foreach (DataGridViewRow row in dgButtons.Rows)
+        {
+          CecButtonConfigItem item = row.DataBoundItem as CecButtonConfigItem;
+          if (item != null && item.Key.Keycode == key.Keycode)
+          {
+            rowIndex = row.Index;
+            row.Selected = true;
+            item.Enabled = true;
+          }
+          else
+          {
+            row.Selected = false;
+          }
+        }
+        if (rowIndex > -1)
+          dgButtons.FirstDisplayedScrollingRowIndex = rowIndex;
+      }
+    }
+
+    private void dataGridView1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
+    {
+      DataGridView grid = sender as DataGridView;
+      CecButtonConfigItem data = grid.Rows[e.RowIndex].DataBoundItem as CecButtonConfigItem;
+      if (data == null || !data.Enabled)
+        e.CellStyle.ForeColor = Color.Gray;
+    }
+    #endregion
+
+    #region CEC Tester tab
+    public void CheckActiveDevices()
+    {
+      CecLogicalAddresses activeDevices = Lib.GetActiveDevices();
+      List<string> deviceList = new List<string>();
+      foreach (CecLogicalAddress activeDevice in activeDevices.Addresses)
+      {
+        if (activeDevice != CecLogicalAddress.Unknown)
+          deviceList.Add(string.Format("{0,1:X} : {1}", (int)activeDevice, Lib.ToString(activeDevice)));
       }
+      deviceList.Add(string.Format("{0,1:X} : {1}", (int)CecLogicalAddress.Broadcast, Lib.ToString(CecLogicalAddress.Broadcast)));
+
+      SetActiveDevices(deviceList.ToArray());
     }
 
-    delegate void SetCheckboxCheckedCallback(CheckBox control, bool val);
-    private void SetCheckboxChecked(CheckBox control, bool val)
+    delegate void SetActiveDevicesCallback(string[] activeDevices);
+    private void SetActiveDevices(string[] activeDevices)
     {
-      if (control.InvokeRequired)
+      if (cbCommandDestination.InvokeRequired)
       {
-        SetCheckboxCheckedCallback d = new SetCheckboxCheckedCallback(SetCheckboxChecked);
+        SetActiveDevicesCallback d = SetActiveDevices;
         try
         {
-          this.Invoke(d, new object[] { control, val });
+          Invoke(d, new object[] { activeDevices });
         }
         catch (Exception) { }
       }
       else
       {
-        control.Checked = val;
+        cbCommandDestination.Items.Clear();
+        foreach (string item in activeDevices)
+          cbCommandDestination.Items.Add(item);
       }
     }
 
-    delegate void SetProgressValueCallback(ProgressBar control, int val);
-    private void SetProgressValue(ProgressBar control, int val)
+    delegate CecLogicalAddress GetTargetDeviceCallback();
+    private CecLogicalAddress GetTargetDevice()
     {
-      if (control.InvokeRequired)
+      if (cbCommandDestination.InvokeRequired)
       {
-        SetProgressValueCallback d = new SetProgressValueCallback(SetProgressValue);
+        GetTargetDeviceCallback d = GetTargetDevice;
+        CecLogicalAddress retval = CecLogicalAddress.Unknown;
         try
         {
-          this.Invoke(d, new object[] { control, val });
+          retval = (CecLogicalAddress)Invoke(d, new object[] { });
         }
         catch (Exception) { }
+        return retval;
       }
-      else
+
+      return GetLogicalAddressFromString(cbCommandDestination.Text);
+    }
+
+    private CecLogicalAddress GetLogicalAddressFromString(string name)
+    {
+      switch (name.Substring(0, 1).ToLower())
       {
-        control.Value = val;
+        case "0":
+          return CecLogicalAddress.Tv;
+        case "1":
+          return CecLogicalAddress.RecordingDevice1;
+        case "2":
+          return CecLogicalAddress.RecordingDevice2;
+        case "3":
+          return CecLogicalAddress.Tuner1;
+        case "4":
+          return CecLogicalAddress.PlaybackDevice1;
+        case "5":
+          return CecLogicalAddress.AudioSystem;
+        case "6":
+          return CecLogicalAddress.Tuner2;
+        case "7":
+          return CecLogicalAddress.Tuner3;
+        case "8":
+          return CecLogicalAddress.PlaybackDevice2;
+        case "9":
+          return CecLogicalAddress.RecordingDevice3;
+        case "a":
+          return CecLogicalAddress.Tuner4;
+        case "b":
+          return CecLogicalAddress.PlaybackDevice3;
+        case "c":
+          return CecLogicalAddress.Reserved1;
+        case "d":
+          return CecLogicalAddress.Reserved2;
+        case "e":
+          return CecLogicalAddress.FreeUse;
+        case "f":
+          return CecLogicalAddress.Broadcast;
+        default:
+          return CecLogicalAddress.Unknown;
       }
     }
 
-    delegate void SetComboBoxItemsCallback(ComboBox control, string selectedText, object[] val);
-    private void SetComboBoxItems(ComboBox control, string selectedText, object[] val)
+    private void bSendImageViewOn_Click(object sender, EventArgs e)
+    {
+      SendImageViewOn(GetTargetDevice());
+    }
+
+    private void bStandby_Click(object sender, EventArgs e)
+    {
+      SendStandby(GetTargetDevice());
+    }
+
+    private void bScan_Click(object sender, EventArgs e)
+    {
+      ShowDeviceInfo(GetTargetDevice());
+    }
+
+    private void bActivateSource_Click(object sender, EventArgs e)
+    {
+      ActivateSource(GetTargetDevice());
+    }
+
+    private void cbCommandDestination_SelectedIndexChanged(object sender, EventArgs e)
+    {
+      bool enableVolumeButtons = (GetTargetDevice() == CecLogicalAddress.AudioSystem);
+      bVolUp.Enabled = enableVolumeButtons;
+      bVolDown.Enabled = enableVolumeButtons;
+      bMute.Enabled = enableVolumeButtons;
+      bActivateSource.Enabled = (GetTargetDevice() != CecLogicalAddress.Broadcast);
+      bScan.Enabled = (GetTargetDevice() != CecLogicalAddress.Broadcast);
+    }
+
+    private void bVolUp_Click(object sender, EventArgs e)
+    {
+      SetControlsEnabled(false);
+      Lib.VolumeUp(true);
+      SetControlsEnabled(true);
+    }
+
+    private void bVolDown_Click(object sender, EventArgs e)
+    {
+      SetControlsEnabled(false);
+      Lib.VolumeDown(true);
+      SetControlsEnabled(true);
+    }
+
+    private void bMute_Click(object sender, EventArgs e)
     {
-      if (control.InvokeRequired)
+      SetControlsEnabled(false);
+      Lib.MuteAudio(true);
+      SetControlsEnabled(true);
+    }
+
+    private void bRescanDevices_Click(object sender, EventArgs e)
+    {
+      if (!SuppressUpdates && ActiveProcess == null)
+      {
+        SetControlsEnabled(false);
+        ActiveProcess = new RescanDevices(ref Lib);
+        ActiveProcess.EventHandler += ProcessEventHandler;
+        (new Thread(ActiveProcess.Run)).Start();
+      }
+    }
+    #endregion
+
+    #region Log tab
+    delegate void UpdateLogCallback();
+    private void UpdateLog()
+    {
+      if (tbLog.InvokeRequired)
       {
-        SetComboBoxItemsCallback d = new SetComboBoxItemsCallback(SetComboBoxItems);
+        UpdateLogCallback d = UpdateLog;
         try
         {
-          this.Invoke(d, new object[] { control, selectedText, val });
+          Invoke(d, new object[] { });
         }
         catch (Exception) { }
       }
       else
       {
-        control.Items.Clear();
-        control.Items.AddRange(val);
-        control.Text = selectedText;
+        tbLog.Text = Log;
+        tbLog.Select(tbLog.Text.Length, 0);
+        tbLog.ScrollToCaret();
       }
     }
 
-    private void ProcessEventHandler(object src, UpdateEvent updateEvent)
+    private void AddLogMessage(CecLogMessage message)
     {
-      switch (updateEvent.Type)
+      string strLevel = "";
+      bool display = false;
+      switch (message.Level)
       {
-        case UpdateEventType.StatusText:
-          SetControlText(lStatus, updateEvent.StringValue);
+        case CecLogLevel.Error:
+          strLevel = "ERROR:   ";
+          display = cbLogError.Checked;
           break;
-        case UpdateEventType.PhysicalAddress:
-          Config.PhysicalAddress = (ushort)updateEvent.IntValue;
-          SetControlText(tbPhysicalAddress, string.Format("{0,4:X}", updateEvent.IntValue));
+        case CecLogLevel.Warning:
+          strLevel = "WARNING: ";
+          display = cbLogWarning.Checked;
           break;
-        case UpdateEventType.ProgressBar:
-          SetProgressValue(pProgress, updateEvent.IntValue);
+        case CecLogLevel.Notice:
+          strLevel = "NOTICE:  ";
+          display = cbLogNotice.Checked;
           break;
-        case UpdateEventType.TVVendorId:
-          TVVendor = (CecVendorId)updateEvent.IntValue;
-          UpdateSelectedDevice();
+        case CecLogLevel.Traffic:
+          strLevel = "TRAFFIC: ";
+          display = cbLogTraffic.Checked;
           break;
-        case UpdateEventType.BaseDevicePhysicalAddress:
-          SetControlText(lConnectedPhysicalAddress, string.Format("Address: {0,4:X}", updateEvent.IntValue));
+        case CecLogLevel.Debug:
+          strLevel = "DEBUG:   ";
+          display = cbLogDebug.Checked;
           break;
-        case UpdateEventType.BaseDevice:
-          Config.BaseDevice = (CecLogicalAddress)updateEvent.IntValue;
-          break;
-        case UpdateEventType.HDMIPort:
-          Config.HDMIPort = (byte)updateEvent.IntValue;
+        default:
           break;
-        case UpdateEventType.HasAVRDevice:
-          if (HasAVRDevice != updateEvent.BoolValue)
-          {
-            HasAVRDevice = updateEvent.BoolValue;
-            UpdateSelectedDevice();
-          }
+      }
+
+      if (display)
+      {
+        string strLog = string.Format("{0} {1,16} {2}", strLevel, message.Time, message.Message) + System.Environment.NewLine;
+        Log += strLog;
+      }
+
+      if (SelectedTab == ConfigTab.Log)
+        UpdateLog();
+    }
+
+    private void bClearLog_Click(object sender, EventArgs e)
+    {
+      Log = string.Empty;
+      UpdateLog();
+    }
+
+    private void bSaveLog_Click(object sender, EventArgs e)
+    {
+      SaveFileDialog dialog = new SaveFileDialog()
+      {
+        Title = "Where do you want to store the log file?",
+        InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
+        FileName = "cec-log.txt",
+        Filter = "txt files (*.txt)|*.txt|All files (*.*)|*.*",
+        FilterIndex = 1
+      };
+
+      if (dialog.ShowDialog() == DialogResult.OK)
+      {
+        FileStream fs = (FileStream)dialog.OpenFile();
+        if (fs == null)
+        {
+          MessageBox.Show("Cannot open '" + dialog.FileName + "' for writing", "Pulse-Eight USB-CEC Adapter", MessageBoxButtons.OK, MessageBoxIcon.Error);
+        }
+        else
+        {
+          StreamWriter writer = new StreamWriter(fs);
+          writer.Write(Log);
+          writer.Close();
+          fs.Close();
+          fs.Dispose();
+          MessageBox.Show("The log file was stored as '" + dialog.FileName + "'.", "Pulse-Eight USB-CEC Adapter", MessageBoxButtons.OK, MessageBoxIcon.Information);
+        }
+      }
+    }
+    #endregion
+
+    #region LibCecSharp callbacks
+    public int ConfigurationChanged(LibCECConfiguration config)
+    {
+      Config = config;
+      SetControlText(tbPhysicalAddress, string.Format("{0,4:X}", Config.PhysicalAddress));
+      SetControlText(cbConnectedDevice, Config.BaseDevice == CecLogicalAddress.AudioSystem ? AVRVendorString : TVVendorString);
+      SetControlText(cbPortNumber, Config.HDMIPort.ToString());
+      switch (config.DeviceTypes.Types[0])
+      {
+        case CecDeviceType.RecordingDevice:
+          SetControlText(cbDeviceType, "Recorder");
           break;
-        case UpdateEventType.AVRVendorId:
-          AVRVendor = (CecVendorId)updateEvent.IntValue;
-          UpdateSelectedDevice();
+        case CecDeviceType.PlaybackDevice:
+          SetControlText(cbDeviceType, "Player");
           break;
-        case UpdateEventType.Configuration:
-          Config = updateEvent.ConfigValue;
-          SetControlText(tbPhysicalAddress, string.Format("{0,4:X}", Config.PhysicalAddress));
-          SetControlText(cbConnectedDevice, Config.BaseDevice == CecLogicalAddress.AudioSystem ? AVRVendorString : TVVendorString);
-          SetControlText(cbPortNumber, Config.HDMIPort.ToString());
-          SetCheckboxChecked(cbUseTVMenuLanguage, Config.UseTVMenuLanguage);
-          SetCheckboxChecked(cbPowerOnStartup, Config.PowerOnStartup);
-          SetCheckboxChecked(cbPowerOffShutdown, Config.PowerOffShutdown);
-          SetCheckboxChecked(cbPowerOffScreensaver, Config.PowerOffScreensaver);
-          SetCheckboxChecked(cbPowerOffOnStandby, Config.PowerOffOnStandby);
-          UpdateSelectedDevice();
+        case CecDeviceType.Tuner:
+          SetControlText(cbDeviceType, "Tuner");
           break;
-        case UpdateEventType.ProcessCompleted:
-          ActiveProcess = null;
-          SetControlsEnabled(true);
+        default:
+          SetControlText(cbDeviceType, "Recorder");
           break;
       }
+      if (config.TvVendor != CecVendorId.Unknown)
+      {
+        SetCheckboxChecked(cbVendorOverride, true);
+        SetControlText(cbVendorId, Lib.ToString(config.TvVendor));
+      }
+      else
+      {
+        SetCheckboxChecked(cbVendorOverride, false);
+        SetControlText(cbVendorId, Lib.ToString(TVVendor));
+      }
+
+      SetCheckboxChecked(cbUseTVMenuLanguage, Config.UseTVMenuLanguage);
+      SetCheckboxChecked(cbActivateSource, Config.ActivateSource);
+      SetCheckboxChecked(cbPowerOffScreensaver, Config.PowerOffScreensaver);
+      SetCheckboxChecked(cbPowerOffOnStandby, Config.PowerOffOnStandby);
+      SetCheckboxChecked(cbSendInactiveSource, Config.SendInactiveSource);
+      UpdateSelectedDevice();
+
+      for (int iPtr = 0; iPtr < 15; iPtr++)
+        SetCheckboxItemChecked(cbWakeDevices, iPtr, Config.WakeDevices.IsSet((CecLogicalAddress)iPtr));
+      for (int iPtr = 0; iPtr < 15; iPtr++)
+        SetCheckboxItemChecked(cbPowerOffDevices, iPtr, Config.PowerOffDevices.IsSet((CecLogicalAddress)iPtr));
+
+      SetControlText(this, "Pulse-Eight USB-CEC Adapter - libCEC " + Lib.ToString(Config.ServerVersion));
+      return 1;
     }
 
-    private void UpdateSelectedDevice()
+    public int ReceiveCommand(CecCommand command)
     {
-      if (HasAVRDevice)
-        SetComboBoxItems(this.cbConnectedDevice, Config.BaseDevice == CecLogicalAddress.AudioSystem ? AVRVendorString : TVVendorString, new object[] { TVVendorString, AVRVendorString });
-      else
-        SetComboBoxItems(this.cbConnectedDevice, TVVendorString, new object[] { TVVendorString });
+      return 1;
+    }
+
+    public int ReceiveKeypress(CecKeypress key)
+    {
+      SelectKeypressRow(key);
+      return 1;
+    }
+
+    public int ReceiveLogMessage(CecLogMessage message)
+    {
+      try
+      {
+        AddLogMessage(message);
+      }
+      catch (Exception) { }
+      return 1;
     }
+    #endregion
 
+    #region Class members
+    public bool HasAVRDevice { get; private set; }
+    #region TV Vendor
+    private CecVendorId _tvVendor = CecVendorId.Unknown;
+    public CecVendorId TVVendor
+    {
+      get { return _tvVendor;}
+      private set { _tvVendor = value; }
+    }
     public string TVVendorString
     {
       get
@@ -273,7 +1099,14 @@ namespace CecConfigGui
           "Television";
       }
     }
-
+    #endregion
+    #region AVR Vendor
+    private CecVendorId _avrVendor = CecVendorId.Unknown;
+    public CecVendorId AVRVendor
+    {
+      get { return _avrVendor; }
+      private set { _avrVendor = value; }
+    }
     public string AVRVendorString
     {
       get
@@ -283,128 +1116,80 @@ namespace CecConfigGui
           "AVR";
       }
     }
-
-    protected bool HasAVRDevice;
-    protected CecVendorId TVVendor = CecVendorId.Unknown;
-    protected CecVendorId AVRVendor = CecVendorId.Unknown;
-    protected CecLogicalAddress SelectedConnectedDevice = CecLogicalAddress.Unknown;
-    protected int LogLevel = (int)CecLogLevel.All;
-
-    protected LibCECConfiguration Config;
-    protected LibCecSharp Lib;
-    private CecCallbackWrapper Callbacks;
-    private UpdateProcess ActiveProcess = null;
-
-    private void connectedDevice_SelectedIndexChanged(object sender, EventArgs e)
+    #endregion
+    public CecLogicalAddress SelectedConnectedDevice
     {
-      if (ActiveProcess == null)
+      get
+      {
+        return (cbConnectedDevice.Text.Equals(AVRVendorString)) ? CecLogicalAddress.AudioSystem : CecLogicalAddress.Tv;
+      }
+    }
+    public CecDeviceType SelectedDeviceType
+    {
+      get
+      {
+        switch (cbDeviceType.Text.ToLower())
+        {
+          case "player":
+            return CecDeviceType.PlaybackDevice;
+          case "tuner":
+            return CecDeviceType.Tuner;
+          default:
+            return CecDeviceType.RecordingDevice;
+        }
+      }
+    }
+    public int SelectedPortNumber
+    {
+      get
       {
-        SetControlsEnabled(false);
-        SelectedConnectedDevice = (this.cbConnectedDevice.Text.Equals(AVRVendorString)) ? CecLogicalAddress.AudioSystem : CecLogicalAddress.Tv;
         int iPortNumber = 0;
         if (!int.TryParse(cbPortNumber.Text, out iPortNumber))
           iPortNumber = 1;
-        ActiveProcess = new UpdateConnectedDevice(ref Lib, cbConnectedDevice.Text.Equals(AVRVendorString) ? CecLogicalAddress.AudioSystem : CecLogicalAddress.Tv, iPortNumber);
-        ActiveProcess.EventHandler += new EventHandler<UpdateEvent>(ProcessEventHandler);
-        (new Thread(new ThreadStart(ActiveProcess.Run))).Start();
+        return iPortNumber;
       }
     }
-
-    private void bCancel_Click(object sender, EventArgs e)
-    {
-      this.Dispose();
-    }
-
-    private void bSave_Click(object sender, EventArgs e)
+    protected LibCECConfiguration Config;
+    protected LibCecSharp Lib;
+    private CecCallbackWrapper Callbacks;
+    private UpdateProcess ActiveProcess = null;
+    private bool SuppressUpdates = true;
+    private ConfigTab SelectedTab = ConfigTab.Configuration;
+    private string Log = string.Empty;
+    private DeviceInformation UpdatingInfoPanel = null;
+    private bool DeviceChangeWarningDisplayed = false;
+    public CecLogicalAddresses WakeDevices
     {
-      SetControlsEnabled(false);
-
-      Config.UseTVMenuLanguage = cbUseTVMenuLanguage.Checked;
-      Config.PowerOnStartup = cbPowerOnStartup.Checked;
-      Config.PowerOffShutdown = cbPowerOffShutdown.Checked;
-      Config.PowerOffScreensaver = cbPowerOffScreensaver.Checked;
-      Config.PowerOffOnStandby = cbPowerOffOnStandby.Checked;
-
-      if (!Lib.CanPersistConfiguration())
+      get
       {
-        if (ActiveProcess == null)
+        CecLogicalAddresses addr = new CecLogicalAddresses();
+        foreach (object item in cbWakeDevices.CheckedItems)
         {
-          SetControlsEnabled(false);
-          string xbmcDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + @"\XBMC\userdata\peripheral_data";
-          string defaultDir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
-
-          SaveFileDialog dialog = new SaveFileDialog()
-          {
-            Title = "Where do you want to store the settings?",
-            InitialDirectory = Directory.Exists(xbmcDir) ? xbmcDir : defaultDir,
-            FileName = "usb_2548_1001.xml",
-            Filter = "xml files (*.xml)|*.xml|All files (*.*)|*.*",
-            FilterIndex = 1
-          };
-
-          if (dialog.ShowDialog() == DialogResult.OK)
-          {
-            FileStream fs = (FileStream)dialog.OpenFile();
-            if (fs == null)
-            {
-              MessageBox.Show("Cannot open '" + dialog.FileName + "' for writing", "Pulse-Eight USB-CEC Adapter", MessageBoxButtons.OK, MessageBoxIcon.Error);
-            }
-            else
-            {
-              StreamWriter writer = new StreamWriter(fs);
-              StringBuilder output = new StringBuilder();
-              output.AppendLine("<settings>");
-              output.AppendLine("<setting id=\"cec_hdmi_port\" value=\"" + Config.HDMIPort + "\" />");
-              output.AppendLine("<setting id=\"connected_device\" value=\"" + (Config.BaseDevice == CecLogicalAddress.AudioSystem ? 5 : 1) + "\" />");
-              output.AppendLine("<setting id=\"physical_address\" value=\"" + string.Format("{0,4:X}", Config.PhysicalAddress) + "\" />");
-              output.AppendLine("<setting id=\"device_type\" value=\"" + (int)Config.DeviceTypes.Types[0] + "\" />");
-              output.AppendLine("<setting id=\"cec_power_on_startup\" value=\"" + (Config.PowerOnStartup ? 1 : 0) + "\" />");
-              output.AppendLine("<setting id=\"cec_power_off_shutdown\" value=\"" + (Config.PowerOffShutdown ? 1 : 0) + "\" />");
-              output.AppendLine("<setting id=\"cec_standby_screensaver\" value=\"" + (Config.PowerOffScreensaver ? 1 : 0) + "\" />");
-              output.AppendLine("<setting id=\"standby_pc_on_tv_standby\" value=\"" + (Config.PowerOffOnStandby ? 1 : 0) + "\" />");
-              output.AppendLine("<setting id=\"use_tv_menu_language\" value=\"" + (Config.UseTVMenuLanguage ? 1 : 0) + "\" />");
-              output.AppendLine("<setting id=\"enabled\" value=\"1\" />");
-              output.AppendLine("<setting id=\"port\" value=\"\" />");
-              output.AppendLine("</settings>");
-              writer.Write(output.ToString());
-              writer.Close();
-              fs.Close();
-              fs.Dispose();
-              MessageBox.Show("Settings are stored.", "Pulse-Eight USB-CEC Adapter", MessageBoxButtons.OK, MessageBoxIcon.Information);
-            }
-          }
-          SetControlsEnabled(true);
+          string c = item as string;
+          addr.Set(GetLogicalAddressFromString(c));
         }
+        return addr;
       }
-      else
-      {
-        if (!Lib.PersistConfiguration(Config))
-          MessageBox.Show("Could not persist the new settings.", "Pulse-Eight USB-CEC Adapter", MessageBoxButtons.OK, MessageBoxIcon.Error);
-        else
-          MessageBox.Show("Settings are stored.", "Pulse-Eight USB-CEC Adapter", MessageBoxButtons.OK, MessageBoxIcon.Information);
-      }
-      SetControlsEnabled(true);
     }
-
-    private void tbPhysicalAddress_TextChanged(object sender, EventArgs e)
+    public CecLogicalAddresses PowerOffDevices
     {
-      if (ActiveProcess == null)
+      get
       {
-        if (tbPhysicalAddress.Text.Length != 4)
-          return;
-        ushort physicalAddress = 0;
-        if (!ushort.TryParse(tbPhysicalAddress.Text, NumberStyles.AllowHexSpecifier, null, out physicalAddress))
-          return;
-        SetControlsEnabled(false);
-        SetControlText(cbPortNumber, string.Empty);
-        SetControlText(cbConnectedDevice, string.Empty);
-        ActiveProcess = new UpdatePhysicalAddress(ref Lib, physicalAddress);
-        ActiveProcess.EventHandler += new EventHandler<UpdateEvent>(ProcessEventHandler);
-        (new Thread(new ThreadStart(ActiveProcess.Run))).Start();
+        CecLogicalAddresses addr = new CecLogicalAddresses();
+        foreach (object item in cbPowerOffDevices.CheckedItems)
+        {
+          string c = item as string;
+          addr.Set(GetLogicalAddressFromString(c));
+        }
+        return addr;
       }
     }
+    #endregion
   }
 
+  /// <summary>
+  /// A little wrapper that is needed because we already inherit form
+  /// </summary>
   internal class CecCallbackWrapper : CecCallbackMethods
   {
     public CecCallbackWrapper(CecConfigGUI gui)
@@ -427,6 +1212,11 @@ namespace CecConfigGui
       return Gui.ReceiveLogMessage(message);
     }
 
+    public override int ConfigurationChanged(LibCECConfiguration config)
+    {
+      return Gui.ConfigurationChanged(config);
+    }
+
     private CecConfigGUI Gui;
   }
 }