mirror of
https://github.com/Maassoft/ColorControl.git
synced 2026-10-22 00:22:18 -05:00
586 lines
18 KiB
C#
586 lines
18 KiB
C#
using LgTv;
|
|
using Newtonsoft.Json;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using System.Web.Script.Serialization;
|
|
using System.Windows.Forms;
|
|
|
|
namespace ColorControl
|
|
{
|
|
class LgService
|
|
{
|
|
public static string LgListAppsJson = "listApps.json";
|
|
|
|
private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
|
|
|
|
//public string FriendlyScreenName { get; private set; }
|
|
|
|
public List<PnpDev> Devices { get; private set; }
|
|
public PnpDev SelectedDevice
|
|
{
|
|
get { return _selectedDevice; }
|
|
set
|
|
{
|
|
_selectedDevice = value;
|
|
Config.PreferredMacAddress = _selectedDevice != null ? _selectedDevice.MacAddress : null;
|
|
}
|
|
}
|
|
|
|
public LgServiceConfig Config { get; private set; }
|
|
|
|
private List<LgPreset> _lgPresets;
|
|
private string _lgPresetsFilename;
|
|
|
|
private LgTvApi _lgTvApi;
|
|
private string _dataDir;
|
|
private string _configFilename;
|
|
private bool _allowPowerOn;
|
|
private bool _justWokeUp;
|
|
private PnpDev _selectedDevice;
|
|
private string _rcButtonsFilename;
|
|
private List<LgPreset> _remoteControlButtons;
|
|
private List<LgApp> _lgApps = new List<LgApp>();
|
|
|
|
private JavaScriptSerializer _JsonSerializer = new JavaScriptSerializer();
|
|
private JavaScriptSerializer _JsonDeserializer = new JavaScriptSerializer();
|
|
|
|
private Dictionary<string, Func<bool>> _invokableActions = new Dictionary<string, Func<bool>>();
|
|
|
|
public LgService(string dataDir, bool allowPowerOn)
|
|
{
|
|
_dataDir = dataDir;
|
|
_allowPowerOn = allowPowerOn;
|
|
|
|
_invokableActions.Add("WOL", new Func<bool>(WakeSelectedDevice));
|
|
|
|
LgPreset.LgApps = _lgApps;
|
|
|
|
LoadConfig();
|
|
LoadPresets();
|
|
LoadRemoteControlButtons();
|
|
|
|
//foreach (var screen in Screen.AllScreens)
|
|
//{
|
|
// var name = screen.DeviceFriendlyName();
|
|
// if (name.Contains("LG"))
|
|
// {
|
|
// FriendlyScreenName = name;
|
|
// }
|
|
//}
|
|
|
|
var _ = RefreshDevices(afterStartUp: true);
|
|
}
|
|
|
|
~LgService()
|
|
{
|
|
GlobalSave();
|
|
}
|
|
|
|
public void GlobalSave()
|
|
{
|
|
SaveConfig();
|
|
SavePresets();
|
|
SaveRemoteControlButtons();
|
|
}
|
|
|
|
public List<LgPreset> GetPresets()
|
|
{
|
|
return _lgPresets;
|
|
}
|
|
|
|
public LgPreset CreateNewPreset()
|
|
{
|
|
var preset = new LgPreset();
|
|
var name = "New preset";
|
|
string fullname;
|
|
var number = 1;
|
|
do
|
|
{
|
|
fullname = $"{name} ({number})";
|
|
number++;
|
|
} while (_lgPresets.Any(x => x.name.Equals(fullname)));
|
|
|
|
preset.name = fullname;
|
|
|
|
return preset;
|
|
}
|
|
|
|
private void LoadPresets()
|
|
{
|
|
_lgPresetsFilename = Path.Combine(_dataDir, "LgPresets.json");
|
|
var toCopy = Path.Combine(Directory.GetCurrentDirectory(), "LgPresets.json");
|
|
if (!File.Exists(_lgPresetsFilename) && File.Exists(toCopy))
|
|
{
|
|
try
|
|
{
|
|
File.Copy(toCopy, _lgPresetsFilename);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logger.Error($"Error while copying {toCopy} to {_lgPresetsFilename}: {e.Message}");
|
|
}
|
|
}
|
|
|
|
if (File.Exists(_lgPresetsFilename))
|
|
{
|
|
var json = File.ReadAllText(_lgPresetsFilename);
|
|
|
|
_lgPresets = _JsonDeserializer.Deserialize<List<LgPreset>>(json);
|
|
}
|
|
else
|
|
{
|
|
_lgPresets = new List<LgPreset>();
|
|
}
|
|
}
|
|
|
|
private void LoadConfig()
|
|
{
|
|
_configFilename = Path.Combine(_dataDir, "LgConfig.json");
|
|
if (File.Exists(_configFilename))
|
|
{
|
|
Config = JsonConvert.DeserializeObject<LgServiceConfig>(File.ReadAllText(_configFilename));
|
|
}
|
|
else
|
|
{
|
|
Config = new LgServiceConfig();
|
|
}
|
|
}
|
|
|
|
private void LoadRemoteControlButtons()
|
|
{
|
|
var defaultButtons = GenerateDefaultRemoteControlButtons();
|
|
|
|
_rcButtonsFilename = Path.Combine(_dataDir, "LgRemoteControlButtons.json");
|
|
//if (File.Exists(_rcButtonsFilename))
|
|
//{
|
|
// var json = File.ReadAllText(_rcButtonsFilename);
|
|
|
|
// _remoteControlButtons = _JsonDeserializer.Deserialize<List<LgPreset>>(json);
|
|
|
|
// var missingButtons = defaultButtons.Where(b => !_remoteControlButtons.Any(x => x.name.Equals(b.name)));
|
|
// _remoteControlButtons.AddRange(missingButtons);
|
|
//}
|
|
//else
|
|
{
|
|
_remoteControlButtons = defaultButtons;
|
|
}
|
|
}
|
|
|
|
public List<LgPreset> GetRemoteControlButtons()
|
|
{
|
|
return _remoteControlButtons;
|
|
}
|
|
|
|
public Dictionary<string, Func<bool>> GetInvokableActions()
|
|
{
|
|
return _invokableActions;
|
|
}
|
|
|
|
private List<LgPreset> GenerateDefaultRemoteControlButtons()
|
|
{
|
|
var list = new List<LgPreset>();
|
|
|
|
for (var i = 1; i < 11; i++)
|
|
{
|
|
var button = i < 10 ? i : 0;
|
|
list.Add(GeneratePreset(button.ToString()));
|
|
}
|
|
|
|
list.Add(GeneratePreset("Settings", appId: "com.palm.app.settings", key: Keys.S));
|
|
|
|
list.Add(GeneratePreset("Vol +", step: "VOLUMEUP", key: Keys.VolumeUp));
|
|
list.Add(GeneratePreset("Vol -", step: "VOLUMEDOWN", key: Keys.VolumeDown));
|
|
list.Add(GeneratePreset("Mute", step: "MUTE", key: Keys.VolumeMute));
|
|
|
|
list.Add(GeneratePreset("Up", step: "UP", key: Keys.Up));
|
|
list.Add(GeneratePreset("Down", step: "DOWN", key: Keys.Down));
|
|
list.Add(GeneratePreset("Left", step: "LEFT", key: Keys.Left));
|
|
list.Add(GeneratePreset("Right", step: "RIGHT", key: Keys.Right));
|
|
|
|
list.Add(GeneratePreset("Enter", step: "ENTER", key: Keys.Enter));
|
|
list.Add(GeneratePreset("Back", step: "BACK", key: Keys.Back));
|
|
list.Add(GeneratePreset("Exit", step: "EXIT", key: Keys.Escape));
|
|
|
|
return list;
|
|
}
|
|
|
|
private LgPreset GeneratePreset(string name, string step = null, string appId = null, Keys key = Keys.None)
|
|
{
|
|
var preset = new LgPreset();
|
|
|
|
var cvt = new KeysConverter();
|
|
var shortcut = key != Keys.None ? (string)cvt.ConvertTo(key, typeof(string)) : null;
|
|
|
|
preset.name = name;
|
|
preset.appId = appId;
|
|
preset.shortcut = shortcut;
|
|
if (appId == null)
|
|
{
|
|
preset.steps.Add(step == null ? name : step);
|
|
}
|
|
|
|
return preset;
|
|
}
|
|
|
|
private void SavePresets()
|
|
{
|
|
try
|
|
{
|
|
var json = _JsonSerializer.Serialize(_lgPresets);
|
|
File.WriteAllText(_lgPresetsFilename, json);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logger.Error(e.ToLogString());
|
|
}
|
|
}
|
|
|
|
private void SaveConfig()
|
|
{
|
|
var json = JsonConvert.SerializeObject(Config);
|
|
File.WriteAllText(_configFilename, json);
|
|
}
|
|
|
|
private void SaveRemoteControlButtons()
|
|
{
|
|
var json = JsonConvert.SerializeObject(_remoteControlButtons);
|
|
File.WriteAllText(_rcButtonsFilename, json);
|
|
}
|
|
|
|
public async Task RefreshDevices(bool connect = true, bool afterStartUp = false)
|
|
{
|
|
var devices = await Utils.GetPnpDevices(Config.DeviceSearchKey);
|
|
SetDevices(devices);
|
|
|
|
if (afterStartUp && SelectedDevice == null && Config.PowerOnAfterStartup && !string.IsNullOrEmpty(Config.PreferredMacAddress))
|
|
{
|
|
Logger.Debug("No device has been found, trying to wake it first...");
|
|
|
|
WakeSelectedDevice(Config.PreferredMacAddress);
|
|
|
|
await Task.Delay(4000);
|
|
await RefreshDevices();
|
|
|
|
return;
|
|
}
|
|
|
|
if (connect && SelectedDevice != null)
|
|
{
|
|
if (Config.PowerOnAfterStartup && _allowPowerOn)
|
|
{
|
|
var _ = WakeAndConnectToSelectedDeviceWithRetries();
|
|
}
|
|
else
|
|
{
|
|
var _ = ConnectToSelectedDevice();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void SetDevices(List<PnpDev> devices)
|
|
{
|
|
Devices = devices;
|
|
if (Devices?.Count > 0)
|
|
{
|
|
var preferredDevice = Devices.FirstOrDefault(x => x.MacAddress != null && x.MacAddress.Equals(Config.PreferredMacAddress)) ?? Devices[0];
|
|
|
|
SelectedDevice = preferredDevice;
|
|
}
|
|
}
|
|
|
|
public async Task<bool> ConnectToSelectedDevice(int retries = 3)
|
|
{
|
|
try
|
|
{
|
|
DisposeConnection();
|
|
_lgTvApi = await LgTvApi.CreateLgTvApi(SelectedDevice.IpAddress, retries);
|
|
|
|
//_lgTvApi.GetServiceList();
|
|
return _lgTvApi != null;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
string logMessage = ex.ToLogString(Environment.StackTrace);
|
|
Logger.Error($"Error while connecting to {SelectedDevice.IpAddress}: {logMessage}");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private async Task<bool> Connected(bool reconnect = false)
|
|
{
|
|
if (SelectedDevice == null)
|
|
{
|
|
Logger.Debug("Cannot apply LG-preset: no device has been selected");
|
|
return false;
|
|
}
|
|
|
|
if (reconnect || _lgTvApi == null || _lgTvApi.ConnectionClosed || !string.Equals(_lgTvApi.GetIpAddress(), SelectedDevice.IpAddress))
|
|
{
|
|
if (!await ConnectToSelectedDevice())
|
|
{
|
|
Logger.Debug("Cannot apply LG-preset: no connection could be made");
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
public async Task<bool> ApplyPreset(LgPreset preset, bool reconnect = false)
|
|
{
|
|
var hasApp = !string.IsNullOrEmpty(preset.appId);
|
|
|
|
var hasWOL = preset.steps.Any(s => s.Equals("WOL", StringComparison.OrdinalIgnoreCase));
|
|
|
|
if (hasWOL)
|
|
{
|
|
var connected = await WakeAndConnectToSelectedDevice(0);
|
|
if (!connected)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
for (var tries = 0; tries <= 1; tries++)
|
|
{
|
|
if (!await Connected(reconnect || tries == 1))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (hasApp)
|
|
{
|
|
try
|
|
{
|
|
await _lgTvApi.LaunchApp(preset.appId);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
string logMessage = ex.ToLogString(Environment.StackTrace);
|
|
Logger.Error("Error while launching app: " + logMessage);
|
|
|
|
if (tries == 0)
|
|
{
|
|
continue;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (_justWokeUp)
|
|
{
|
|
_justWokeUp = false;
|
|
await Task.Delay(1000);
|
|
}
|
|
}
|
|
|
|
if (preset.steps.Any())
|
|
{
|
|
if (hasApp)
|
|
{
|
|
await Task.Delay(1500);
|
|
}
|
|
try
|
|
{
|
|
await ExecuteSteps(_lgTvApi, preset);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
string logMessage = ex.ToLogString(Environment.StackTrace);
|
|
Logger.Error("Error while executing steps: " + logMessage);
|
|
|
|
if (tries == 0)
|
|
{
|
|
continue;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public async Task PowerOn()
|
|
{
|
|
var mouse = await _lgTvApi.GetMouse();
|
|
mouse.SendButton(ButtonType.POWER);
|
|
}
|
|
|
|
private async Task ExecuteSteps(LgTvApi api, LgPreset preset)
|
|
{
|
|
var mouse = await api.GetMouse();
|
|
foreach (var step in preset.steps)
|
|
{
|
|
var keySpec = step.Split(':');
|
|
var key = keySpec[0].ToUpper();
|
|
if (_invokableActions.ContainsKey(key))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (keySpec.Length == 2)
|
|
{
|
|
SendKey(mouse, key, int.Parse(keySpec[1]));
|
|
}
|
|
else
|
|
{
|
|
SendKey(mouse, key);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void SendKey(LgWebOsMouseService mouse, string key, int delay = 180)
|
|
{
|
|
if (key.Length == 1 && int.TryParse(key, out _))
|
|
{
|
|
key = "_" + key;
|
|
}
|
|
var button = (ButtonType)Enum.Parse(typeof(ButtonType), key);
|
|
mouse.SendButton(button);
|
|
Thread.Sleep(delay);
|
|
}
|
|
|
|
public async Task<LgWebOsMouseService> GetMouseAsync()
|
|
{
|
|
return await _lgTvApi.GetMouse();
|
|
}
|
|
|
|
public async Task RefreshApps(bool force = false)
|
|
{
|
|
if (SelectedDevice == null)
|
|
{
|
|
Logger.Debug("Cannot refresh apps: no device has been selected");
|
|
}
|
|
|
|
if (!await Connected(force))
|
|
{
|
|
Logger.Debug("Cannot refresh apps: no connection could be made");
|
|
}
|
|
|
|
_lgApps.Clear();
|
|
_lgApps.AddRange(await _lgTvApi.GetApps(force));
|
|
}
|
|
|
|
public List<LgApp> GetApps()
|
|
{
|
|
return _lgApps;
|
|
}
|
|
|
|
internal void DisposeConnection()
|
|
{
|
|
if (_lgTvApi != null)
|
|
{
|
|
_lgTvApi.Dispose();
|
|
_lgTvApi = null;
|
|
}
|
|
}
|
|
|
|
internal async Task<bool> PowerOff()
|
|
{
|
|
if (!await Connected(true))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
await _lgTvApi.TurnOff();
|
|
return true;
|
|
}
|
|
|
|
internal bool WakeSelectedDevice()
|
|
{
|
|
return WakeSelectedDevice(SelectedDevice?.MacAddress ?? Config.PreferredMacAddress);
|
|
}
|
|
|
|
internal bool WakeSelectedDevice(string macAddress)
|
|
{
|
|
var result = false;
|
|
|
|
macAddress = string.IsNullOrEmpty(macAddress) ? SelectedDevice?.MacAddress : macAddress;
|
|
if (macAddress != null)
|
|
{
|
|
result = WOL.WakeFunction(macAddress, Config.UseOldNpcapWol);
|
|
_justWokeUp = true;
|
|
}
|
|
else
|
|
{
|
|
Logger.Debug("Cannot wake device: no device has been selected or there is no preferred MAC-address stored in the configuration");
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
internal void WakeAfterResume()
|
|
{
|
|
if (Config.PowerOnAfterResume)
|
|
{
|
|
var _ = WakeAndConnectToSelectedDeviceWithRetries();
|
|
}
|
|
}
|
|
|
|
private async Task<bool> WakeAndConnectToSelectedDeviceWithRetries()
|
|
{
|
|
var wakeDelay = 0;
|
|
var maxRetries = Config.PowerOnRetries <= 1 ? 5 : Config.PowerOnRetries;
|
|
|
|
var result = false;
|
|
for (var retry = 0; retry < maxRetries && !result; retry++)
|
|
{
|
|
Logger.Debug($"WakeAndConnectToSelectedDeviceWithRetries: attempt {retry + 1} of {maxRetries}...");
|
|
|
|
var ms = DateTimeOffset.Now.ToUnixTimeMilliseconds();
|
|
result = await WakeAndConnectToSelectedDevice(retry == 0 ? wakeDelay : 0);
|
|
ms = DateTimeOffset.Now.ToUnixTimeMilliseconds() - ms;
|
|
if (!result)
|
|
{
|
|
var delay = 2000 - ms;
|
|
if (delay > 0)
|
|
{
|
|
await Task.Delay((int)delay);
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private async Task<bool> WakeAndConnectToSelectedDevice(int wakeDelay = 5000, int connectDelay = 500)
|
|
{
|
|
try
|
|
{
|
|
//if (wakeDelay == 0)
|
|
//{
|
|
// if (await ConnectToSelectedDevice())
|
|
// {
|
|
// Logger.Debug("Already connected, no wake needed?");
|
|
// return;
|
|
// };
|
|
//}
|
|
|
|
await Task.Delay(wakeDelay);
|
|
var result = WakeSelectedDevice();
|
|
if (!result)
|
|
{
|
|
Logger.Debug("WOL failed");
|
|
return false;
|
|
}
|
|
Logger.Debug("WOL succeeded");
|
|
await Task.Delay(connectDelay);
|
|
result = await ConnectToSelectedDevice();
|
|
Logger.Debug("Connect succeeded: " + result);
|
|
return result;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logger.Error("WakeAndConnectToSelectedDevice: " + e.ToLogString());
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|