manually add device, add device setting per preset, add remote control image

This commit is contained in:
Vincent Maas committed 2021-05-02 17:38:38 +02:00
1 parent cac5180c76
commit 26a91dc7e9
27 files changed
+2027 -862

No files matched your search

+19 -6
View File
@@ -27,7 +27,7 @@
<ProductName>ColorControl</ProductName>
<PublisherName>Maassoft</PublisherName>
<ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>2.2.0.0</ApplicationVersion>
<ApplicationVersion>3.0.0.0</ApplicationVersion>
<UseApplicationTrust>false</UseApplicationTrust>
<PublishWizardCompleted>true</PublishWizardCompleted>
<BootstrapperEnabled>true</BootstrapperEnabled>
@@ -177,6 +177,18 @@
<Compile Include="ADLWrapper.cs" />
<Compile Include="AmdDisplayInfo.cs" />
<Compile Include="AppContext.cs" />
<Compile Include="LgDevice.cs" />
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="RemoteControlPanel.cs">
<SubType>UserControl</SubType>
</Compile>
<Compile Include="RemoteControlPanel.Designer.cs">
<DependentUpon>RemoteControlPanel.cs</DependentUpon>
</Compile>
<Compile Include="UserSessionInfo.cs" />
<Compile Include="ServiceBase.cs" />
<Compile Include="NvDisplayInfo.cs" />
@@ -226,21 +238,22 @@
</Compile>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<EmbeddedResource Include="Resources\RC_coords.txt" />
<None Include="Resources\LG_remote.jpg" />
<EmbeddedResource Include="MainForm.resx">
<DependentUpon>MainForm.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<SubType>Designer</SubType>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<EmbeddedResource Include="RemoteControlForm.resx">
<DependentUpon>RemoteControlForm.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="RemoteControlPanel.resx">
<DependentUpon>RemoteControlPanel.cs</DependentUpon>
</EmbeddedResource>
<None Include="ColorControl_TemporaryKey.pfx" />
<Content Include="LgPresets.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+358
View File
@@ -0,0 +1,358 @@
using LgTv;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace ColorControl
{
class LgDevice
{
protected static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
public string Name { get; private set; }
public string IpAddress { get; private set; }
public string MacAddress { get; private set; }
public bool IsCustom { get; private set; }
[JsonIgnore]
public bool IsDummy { get; private set; }
public bool PowerOnAfterStartup { get; set; }
public bool PowerOnAfterResume { get; set; }
public bool PowerOffOnShutdown { get; set; }
public bool PowerOffOnStandby { get; set; }
public bool PowerSwitchOnScreenSaver { get; set; }
[JsonIgnore]
private LgTvApi _lgTvApi;
[JsonIgnore]
private bool _justWokeUp;
[JsonIgnore]
private Dictionary<string, Func<bool>> _invokableActions = new Dictionary<string, Func<bool>>();
[JsonConstructor]
public LgDevice(string name, string ipAddress, string macAddress, bool isCustom = true, bool isDummy = false)
{
Name = name;
IpAddress = ipAddress;
MacAddress = macAddress;
IsCustom = isCustom;
IsDummy = isDummy;
_invokableActions.Add("WOL", new Func<bool>(Wake));
}
public override string ToString()
{
return (IsDummy ? string.Empty : (IsCustom ? "Custom: " : "Auto detect: ")) + $"{Name}" + (!string.IsNullOrEmpty(IpAddress) ? $" ({IpAddress})" : string.Empty);
}
public async Task<bool> Connect(int retries = 3)
{
try
{
DisposeConnection();
_lgTvApi = await LgTvApi.CreateLgTvApi(IpAddress, retries);
//Test();
return _lgTvApi != null;
}
catch (Exception ex)
{
string logMessage = ex.ToLogString(Environment.StackTrace);
Logger.Error($"Error while connecting to {IpAddress}: {logMessage}");
return false;
}
}
public bool IsConnected()
{
return !_lgTvApi?.ConnectionClosed ?? false;
}
internal void DisposeConnection()
{
if (_lgTvApi != null)
{
_lgTvApi.Dispose();
_lgTvApi = null;
}
}
public async Task<bool> ExecutePreset(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 WakeAndConnect(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;
}
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<IEnumerable<LgApp>> GetApps(bool force = false)
{
if (!await Connected(force))
{
Logger.Debug("Cannot refresh apps: no connection could be made");
return new List<LgApp>();
}
return await _lgTvApi.GetApps(force);
}
internal async Task<bool> PowerOff()
{
if (!await Connected(true))
{
return false;
}
await _lgTvApi.TurnOff();
return true;
}
internal async Task<bool> TestConnection(int retries = 1)
{
if (!await Connected(true, retries))
{
return false;
}
try
{
await _lgTvApi.IsMuted();
return true;
}
catch (Exception ex)
{
Logger.Error("TestConnection: " + ex.ToLogString());
return false;
}
}
internal async Task<bool> WakeAndConnect(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 = Wake();
if (!result)
{
Logger.Debug("WOL failed");
return false;
}
Logger.Debug("WOL succeeded");
await Task.Delay(connectDelay);
result = await Connect();
Logger.Debug("Connect succeeded: " + result);
return result;
}
catch (Exception e)
{
Logger.Error("WakeAndConnectToSelectedDevice: " + e.ToLogString());
return false;
}
}
internal bool Wake()
{
var result = false;
if (MacAddress != null)
{
result = WOL.WakeFunction(MacAddress);
_justWokeUp = true;
}
else
{
Logger.Debug("Cannot wake device: the device has no MAC-address");
}
return result;
}
internal async Task<bool> WakeAndConnectWithRetries(int retries = 5)
{
var wakeDelay = 0;
var maxRetries = retries <= 1 ? 5 : retries;
var result = false;
for (var retry = 0; retry < maxRetries && !result; retry++)
{
Logger.Debug($"WakeAndConnectWithRetries: attempt {retry + 1} of {maxRetries}...");
var ms = DateTimeOffset.Now.ToUnixTimeMilliseconds();
result = await WakeAndConnect(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;
}
public Dictionary<string, Func<bool>> GetInvokableActions()
{
return _invokableActions;
}
public async Task PowerOn()
{
var mouse = await _lgTvApi.GetMouse();
mouse.SendButton(ButtonType.POWER);
}
public void Test()
{
try
{
//_lgTvApi.GetServiceList();
//_lgTvApi?.Test();
}
catch (Exception ex)
{
Logger.Error("TEST: " + ex.ToLogString());
}
}
private async Task<bool> Connected(bool reconnect = false, int retries = 3)
{
if (reconnect || !IsConnected() || !string.Equals(_lgTvApi.GetIpAddress(), IpAddress))
{
if (!await Connect(retries))
{
Logger.Debug("Cannot apply LG-preset: no connection could be made");
return false;
}
}
return true;
}
internal void ConvertToCustom()
{
IsCustom = true;
}
}
}
+34 -2
View File
@@ -1,4 +1,5 @@
using LgTv;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Linq;
@@ -6,10 +7,18 @@ namespace ColorControl
{
class LgPreset : PresetBase
{
public static List<LgApp> LgApps { get; set; }
public static List<LgDevice> LgDevices { get; set; }
public string appId { get; set; }
public List<string> steps { get; set; }
public string DeviceMacAddress { get; set; }
public static List<LgApp> LgApps { get; set; }
[JsonIgnore]
public int X { get; set; }
[JsonIgnore]
public int Y { get; set; }
public LgPreset()
{
@@ -37,7 +46,7 @@ namespace ColorControl
public static List<string> GetColumnNames()
{
return new List<string>() { "Name", "App|200", "Steps|400", "Shortcut" };
return new List<string>() { "Name", "Device|120", "App|200", "Steps|400", "Shortcut" };
}
public override List<string> GetDisplayValues(Config config = null)
@@ -45,6 +54,29 @@ namespace ColorControl
var values = new List<string>();
values.Add(name);
var deviceString = "Global";
if (!string.IsNullOrEmpty(DeviceMacAddress))
{
if (LgDevices != null)
{
var device = LgDevices.FirstOrDefault(d => !string.IsNullOrEmpty(d.MacAddress) && d.MacAddress.Equals(DeviceMacAddress));
if (device != null)
{
deviceString = device.Name;
}
else
{
deviceString = "Unknown: device not found";
}
}
else
{
deviceString = DeviceMacAddress;
}
}
values.Add(deviceString);
var app = appId;
if (LgApps != null)
{
+175 -355
View File
@@ -15,16 +15,21 @@ namespace ColorControl
{
class LgService : ServiceBase<LgPreset>
{
internal enum PowerOnOffState
{
StartUp = 1,
Resume = 2,
ScreenSaver = 3,
ShutDown = 4,
StandBy = 5
}
protected static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
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
public List<LgDevice> Devices { get; private set; }
public LgDevice SelectedDevice
{
get { return _selectedDevice; }
set
@@ -36,17 +41,13 @@ namespace ColorControl
public LgServiceConfig Config { get; private set; }
private LgTvApi _lgTvApi;
private string _configFilename;
private bool _allowPowerOn;
private bool _justWokeUp;
private PnpDev _selectedDevice;
private LgDevice _selectedDevice;
private string _rcButtonsFilename;
private List<LgPreset> _remoteControlButtons;
private List<LgApp> _lgApps = new List<LgApp>();
private Dictionary<string, Func<bool>> _invokableActions = new Dictionary<string, Func<bool>>();
private bool _poweredOffByScreenSaver;
private int _poweredOffByScreenSaverProcessId;
private Task _monitorTask;
@@ -56,22 +57,11 @@ namespace ColorControl
{
_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;
// }
//}
}
~LgService()
@@ -165,6 +155,8 @@ namespace ColorControl
{
Config = new LgServiceConfig();
}
LgPreset.LgDevices = Config.Devices;
}
private void LoadRemoteControlButtons()
@@ -192,11 +184,6 @@ namespace ColorControl
return _remoteControlButtons;
}
public Dictionary<string, Func<bool>> GetInvokableActions()
{
return _invokableActions;
}
private List<LgPreset> GenerateDefaultRemoteControlButtons()
{
var list = new List<LgPreset>();
@@ -207,11 +194,18 @@ namespace ColorControl
list.Add(GeneratePreset(button.ToString()));
}
list.Add(GeneratePreset("Power", step: "POWER"));
list.Add(GeneratePreset("Home", appId: "com.webos.app.home", key: Keys.H));
list.Add(GeneratePreset("Settings", appId: "com.palm.app.settings", key: Keys.S));
list.Add(GeneratePreset("TV Guide", appId: "com.webos.app.livemenu"));
list.Add(GeneratePreset("List", step: "LIST"));
list.Add(GeneratePreset("SAP", step: "SAP"));
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("Channel +", step: "CHANNELUP", key: Keys.MediaNextTrack));
list.Add(GeneratePreset("Channel -", step: "CHANNELDOWN", key: Keys.MediaPreviousTrack));
list.Add(GeneratePreset("Up", step: "UP", key: Keys.Up));
list.Add(GeneratePreset("Down", step: "DOWN", key: Keys.Down));
@@ -222,6 +216,19 @@ namespace ColorControl
list.Add(GeneratePreset("Back", step: "BACK", key: Keys.Back));
list.Add(GeneratePreset("Exit", step: "EXIT", key: Keys.Escape));
list.Add(GeneratePreset("Netflix", appId: "netflix"));
list.Add(GeneratePreset("Inputs", appId: "com.webos.app.homeconnect"));
list.Add(GeneratePreset("Amazon Prime", appId: "amazon"));
list.Add(GeneratePreset("Red", step: "RED"));
list.Add(GeneratePreset("Green", step: "GREEN"));
list.Add(GeneratePreset("Yellow", step: "YELLOW"));
list.Add(GeneratePreset("Blue", step: "BLUE"));
list.Add(GeneratePreset("Rakuten TV", appId: "ui30"));
list.Add(GeneratePreset("Play", step: "PLAY"));
list.Add(GeneratePreset("Pause", step: "PAUSE"));
return list;
}
@@ -258,6 +265,8 @@ namespace ColorControl
private void SaveConfig()
{
Config.PowerOnAfterStartup = Devices.Any(d => d.PowerOnAfterStartup);
var json = JsonConvert.SerializeObject(Config);
File.WriteAllText(_configFilename, json);
}
@@ -270,14 +279,36 @@ namespace ColorControl
public async Task RefreshDevices(bool connect = true, bool afterStartUp = false)
{
var devices = await Utils.GetPnpDevices(Config.DeviceSearchKey);
SetDevices(devices);
Devices = Config.Devices;
var customIpAddresses = Devices.Where(d => d.IsCustom).Select(d => d.IpAddress);
var pnpDevices = await Utils.GetPnpDevices(Config.DeviceSearchKey);
var autoDevices = pnpDevices.Where(p => !customIpAddresses.Contains(p.IpAddress)).Select(d => new LgDevice(d.Name, d.IpAddress, d.MacAddress, false)).ToList();
var autoIpAddresses = pnpDevices.Select(d => d.IpAddress);
Devices.RemoveAll(d => !d.IsCustom && !autoIpAddresses.Contains(d.IpAddress));
var newAutoDevices = autoDevices.Where(ad => !Devices.Any(d => d.IpAddress.Equals(ad.IpAddress)));
Devices.AddRange(newAutoDevices);
if (Devices.Any())
{
var preferredDevice = Devices.FirstOrDefault(x => x.MacAddress != null && x.MacAddress.Equals(Config.PreferredMacAddress)) ?? Devices[0];
SelectedDevice = preferredDevice;
}
else
{
SelectedDevice = null;
}
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);
var tempDevice = new LgDevice("Test", string.Empty, Config.PreferredMacAddress);
tempDevice.Wake();
await Task.Delay(4000);
await RefreshDevices();
@@ -287,70 +318,17 @@ namespace ColorControl
if (connect && SelectedDevice != null)
{
if (Config.PowerOnAfterStartup && _allowPowerOn)
if (_allowPowerOn)
{
var _ = WakeAndConnectToSelectedDeviceWithRetries();
WakeAfterStartupOrResume();
}
else
{
var _ = ConnectToSelectedDevice();
var _ = SelectedDevice.Connect();
}
}
}
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 || !IsConnected() || !string.Equals(_lgTvApi.GetIpAddress(), SelectedDevice.IpAddress))
{
if (!await ConnectToSelectedDevice())
{
Logger.Debug("Cannot apply LG-preset: no connection could be made");
return false;
}
}
return true;
}
private bool IsConnected()
{
return !_lgTvApi?.ConnectionClosed ?? false;
}
public async Task<bool> ApplyPreset(string presetName)
{
var preset = _presets.FirstOrDefault(p => p.name.Equals(presetName, StringComparison.OrdinalIgnoreCase));
@@ -366,123 +344,28 @@ namespace ColorControl
public async Task<bool> ApplyPreset(LgPreset preset, bool reconnect = false)
{
var hasApp = !string.IsNullOrEmpty(preset.appId);
var device = GetPresetDevice(preset);
var hasWOL = preset.steps.Any(s => s.Equals("WOL", StringComparison.OrdinalIgnoreCase));
if (hasWOL)
if (device == null)
{
var connected = await WakeAndConnectToSelectedDevice(0);
if (!connected)
{
return false;
}
Logger.Debug("Cannot apply preset: no device has been selected");
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;
return await device.ExecutePreset(preset, reconnect);
}
public async Task PowerOn()
public LgDevice GetPresetDevice(LgPreset preset)
{
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);
var device = string.IsNullOrEmpty(preset.DeviceMacAddress)
? SelectedDevice
: Devices.FirstOrDefault(d => d.MacAddress?.Equals(preset.DeviceMacAddress, StringComparison.OrdinalIgnoreCase) ?? false);
return device;
}
public async Task<LgWebOsMouseService> GetMouseAsync()
{
return await _lgTvApi.GetMouse();
return await SelectedDevice?.GetMouseAsync();
}
public async Task RefreshApps(bool force = false)
@@ -490,15 +373,16 @@ namespace ColorControl
if (SelectedDevice == null)
{
Logger.Debug("Cannot refresh apps: no device has been selected");
return;
}
if (!await Connected(force))
var apps = await SelectedDevice.GetApps();
if (apps.Any())
{
Logger.Debug("Cannot refresh apps: no connection could be made");
_lgApps.Clear();
_lgApps.AddRange(apps);
}
_lgApps.Clear();
_lgApps.AddRange(await _lgTvApi.GetApps(force));
}
public List<LgApp> GetApps()
@@ -506,138 +390,47 @@ namespace ColorControl
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;
return await SelectedDevice?.PowerOff();
}
internal async Task<bool> TestConnection()
internal Task PowerOffOnShutdownOrResume(PowerOnOffState state = PowerOnOffState.ShutDown)
{
if (!await Connected(true))
var devices = Devices.Where(d => state == PowerOnOffState.ShutDown && d.PowerOffOnShutdown ||
state == PowerOnOffState.StandBy && d.PowerOffOnStandby);
var tasks = new List<Task>();
foreach (var device in devices)
{
return false;
var task = device.PowerOff();
tasks.Add(task);
}
try
{
await _lgTvApi.IsMuted();
return true;
}
catch (Exception ex)
{
Logger.Error("TestConnection: " + ex.ToLogString());
return false;
}
return Task.WhenAll(tasks.ToArray());
}
internal bool WakeSelectedDevice()
{
return WakeSelectedDevice(SelectedDevice?.MacAddress ?? Config.PreferredMacAddress);
return SelectedDevice?.Wake() ?? false;
}
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(bool checkUserSession = true)
internal void WakeAfterStartupOrResume(PowerOnOffState state = PowerOnOffState.StartUp, bool checkUserSession = true)
{
if (checkUserSession && !UserSessionInfo.UserLocalSession)
{
Logger.Debug($"WakeAndConnectToSelectedDeviceWithRetries: not waking because session info indicates no local session");
return false;
Logger.Debug($"WakeAfterStartupOrResume: not waking because session info indicates no local session");
return;
}
var wakeDelay = 0;
var maxRetries = Config.PowerOnRetries <= 1 ? 5 : Config.PowerOnRetries;
var result = false;
for (var retry = 0; retry < maxRetries && !result; retry++)
var wakeDevices = Devices.Where(d => state == PowerOnOffState.StartUp && d.PowerOnAfterStartup ||
state == PowerOnOffState.Resume && d.PowerOnAfterResume ||
state == PowerOnOffState.ScreenSaver && d.PowerSwitchOnScreenSaver);
foreach (var device in wakeDevices)
{
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;
device.DisposeConnection();
var _ = device.WakeAndConnectWithRetries(Config.PowerOnRetries);
}
}
@@ -663,7 +456,7 @@ namespace ColorControl
Logger.Debug("User switched to local and screen was powered off due to screensaver: waking up");
_poweredOffByScreenSaver = false;
_poweredOffByScreenSaverProcessId = 0;
var _ = WakeAndConnectToSelectedDeviceWithRetries();
WakeAfterStartupOrResume();
}
else
{
@@ -680,18 +473,17 @@ namespace ColorControl
if (powerOn)
{
DisposeConnection();
WakeAfterResume();
WakeAfterStartupOrResume(PowerOnOffState.Resume);
return;
}
if (Config.PowerOffOnStandby)
if (Devices.Any(d => d.PowerOffOnStandby))
{
NativeMethods.SetThreadExecutionState(NativeConstants.ES_CONTINUOUS | NativeConstants.ES_SYSTEM_REQUIRED | NativeConstants.ES_AWAYMODE_REQUIRED);
try
{
Logger.Debug("Powering off tv...");
var task = PowerOff();
var task = PowerOffOnShutdownOrResume(PowerOnOffState.StandBy);
Utils.WaitForTask(task);
Logger.Debug("Done powering off tv");
}
@@ -717,11 +509,12 @@ namespace ColorControl
private void MonitorProcesses()
{
if (Config.PowerSwitchOnScreenSaver && _monitorTask == null)
var enableMonitoring = Devices.Any(d => d.PowerSwitchOnScreenSaver);
if (enableMonitoring && _monitorTask == null)
{
_monitorTask = CheckProcesses();
}
else if (!Config.PowerSwitchOnScreenSaver && _monitorTask != null)
else if (!enableMonitoring && _monitorTask != null)
{
_monitorTask = null;
}
@@ -729,12 +522,12 @@ namespace ColorControl
public async Task CheckProcesses()
{
var wasConnected = IsConnected();
var wasConnected = Devices.Any(d => d.PowerSwitchOnScreenSaver && d.IsConnected());
_monitorTaskCounter++;
var validCounter = _monitorTaskCounter;
var lastProcessId = 0;
while (Config.PowerSwitchOnScreenSaver && validCounter == _monitorTaskCounter)
while (Devices.Any(d => d.PowerSwitchOnScreenSaver) && validCounter == _monitorTaskCounter)
{
await Task.Delay(500);
@@ -758,16 +551,18 @@ namespace ColorControl
_poweredOffByScreenSaver = false;
_poweredOffByScreenSaverProcessId = 0;
lastProcessId = 0;
var _ = WakeAndConnectToSelectedDeviceWithRetries();
WakeAfterStartupOrResume(PowerOnOffState.ScreenSaver);
continue;
}
if (!IsConnected())
var devices = Devices.Where(d => d.PowerSwitchOnScreenSaver && d.IsConnected()).ToList();
if (!devices.Any())
{
if (wasConnected)
{
Logger.Debug("TV was connected, but not any longer");
Logger.Debug("Screensaver check: TV(s) where connected, but not any longer");
wasConnected = false;
}
continue;
@@ -775,49 +570,50 @@ namespace ColorControl
if (!wasConnected)
{
Logger.Debug("TV was not connected, but connection has now been established");
Logger.Debug("Screensaver check: TV(s) where not connected, but connection has now been established");
wasConnected = true;
}
foreach (var process in processes)
{
var name = process.ProcessName.ToLowerInvariant();
if (name.EndsWith(".scr"))
{
var parent = process.Parent();
if (parent?.ProcessName.Contains("winlogon") ?? false)
{
Logger.Debug($"Screensaver started: {process.ProcessName}, parent: {parent.ProcessName}");
try
{
Logger.Debug("Test connection...");
var test = await TestConnection();
Logger.Debug("Test connection result: " + test);
var process = processes.FirstOrDefault(p => p.ProcessName.ToLowerInvariant().EndsWith(".scr"));
Logger.Debug("Powering off tv because of screensaver");
_poweredOffByScreenSaver = await PowerOff();
_poweredOffByScreenSaverProcessId = process.Id;
lastProcessId = 0;
}
catch (Exception e)
{
Logger.Error("CheckProcesses: can't power off: " + e.ToLogString());
}
if (!IsConnected())
{
_poweredOffByScreenSaver = false;
}
break;
}
else
if (process == null)
{
continue;
}
var parent = process.Parent();
if (parent?.ProcessName.Contains("winlogon") ?? false)
{
Logger.Debug($"Screensaver started: {process.ProcessName}, parent: {parent.ProcessName}");
try
{
foreach (var device in devices)
{
if (process.Id != lastProcessId)
Logger.Debug($"Screensaver check: test connection with {device.Name}...");
var test = await device.TestConnection();
Logger.Debug("Screensaver check: test connection result: " + test);
if (!test)
{
Logger.Debug($"Screensaver started: {process.ProcessName}, but invalid parent: {parent?.ProcessName ?? "no parent"}");
lastProcessId = process.Id;
continue;
}
Logger.Debug($"Screensaver check: powering off tv {device.Name} because of screensaver");
_poweredOffByScreenSaver = await device.PowerOff();
}
_poweredOffByScreenSaverProcessId = process.Id;
lastProcessId = 0;
}
catch (Exception e)
{
Logger.Error($"Screensaver check: can't power off: " + e.ToLogString());
}
}
else if (process.Id != lastProcessId)
{
Logger.Debug($"Screensaver started: {process.ProcessName}, but invalid parent: {parent?.ProcessName ?? "no parent"}");
lastProcessId = process.Id;
}
}
catch (Exception ex)
@@ -826,5 +622,29 @@ namespace ColorControl
}
}
}
internal void AddCustomDevice(LgDevice device)
{
Devices.Add(device);
}
internal void RemoveCustomDevice(LgDevice device)
{
Devices.Remove(device);
if (!string.IsNullOrEmpty(device.MacAddress))
{
var presets = _presets.Where(p => !string.IsNullOrEmpty(p.DeviceMacAddress) && p.DeviceMacAddress.Equals(device.MacAddress)).ToList();
presets.ForEach(preset =>
{
preset.DeviceMacAddress = null;
});
}
if (SelectedDevice == device)
{
SelectedDevice = Devices.Any() ? Devices.First() : null;
}
}
}
}
+8 -9
View File
@@ -1,17 +1,11 @@
namespace ColorControl
using System.Collections.Generic;
namespace ColorControl
{
class LgServiceConfig
{
public bool PowerOnAfterStartup { get; set; }
public bool PowerOnAfterResume { get; set; }
public bool PowerOffOnShutdown { get; set; }
public bool PowerOffOnStandby { get; set; }
public bool PowerSwitchOnScreenSaver { get; set; }
public int PowerOnDelayAfterResume { get; set; }
public int PowerOnRetries { get; set; }
@@ -22,11 +16,16 @@
public bool UseOldNpcapWol { get; set; }
public List<LgDevice> Devices { get; set; }
public bool ShowRemoteControl { get; set; }
public LgServiceConfig()
{
PowerOnDelayAfterResume = 5000;
DeviceSearchKey = "[LG]";
PowerOnRetries = 10;
Devices = new List<LgDevice>();
}
}
}
+466 -371
View File
File diff suppressed because it is too large. Load diff
+204 -44
View File
@@ -37,12 +37,12 @@ namespace ColorControl
private NotifyIcon _trayIcon;
private bool _initialized = false;
private bool _disableEvents = false;
private Config _config;
private bool _setVisibleCalled = false;
private RestartDetector _restartDetector;
private LgService _lgService;
private RemoteControlForm _remoteControlForm;
private MenuItem _nvTrayMenu;
private MenuItem _amdTrayMenu;
@@ -149,11 +149,6 @@ namespace ColorControl
FillLgPresets();
clbLgPower.SetItemChecked(0, _lgService.Config.PowerOnAfterStartup);
clbLgPower.SetItemChecked(1, _lgService.Config.PowerOnAfterResume);
clbLgPower.SetItemChecked(2, _lgService.Config.PowerOffOnShutdown);
clbLgPower.SetItemChecked(3, _lgService.Config.PowerOffOnStandby);
clbLgPower.SetItemChecked(4, _lgService.Config.PowerSwitchOnScreenSaver);
edtLgMaxPowerOnRetries.Value = _lgService.Config.PowerOnRetries;
edtLgDeviceFilter.Text = _lgService.Config.DeviceSearchKey;
chkLgOldWolMechanism.Checked = _lgService.Config.UseOldNpcapWol;
@@ -171,15 +166,6 @@ namespace ColorControl
item.Click += miLgAddButton_Click;
}
var actions = _lgService.GetInvokableActions();
foreach (var keyValuePair in actions)
{
var text = keyValuePair.Key;
var item = mnuLgActions.DropDownItems.Add(text);
item.Click += miLgAddAction_Click;
}
_lgService.InstallEventHandlers();
}
catch (Exception e)
@@ -403,7 +389,7 @@ namespace ColorControl
GlobalSave();
if (SystemShutdown && _lgService.Config.PowerOffOnShutdown)
if (SystemShutdown && _lgService.Devices.Any(d => d.PowerOffOnShutdown))
{
Logger.Debug($"MainForm_FormClosing: SystemShutdown");
@@ -418,7 +404,7 @@ namespace ColorControl
else
{
Logger.Debug("Powering off tv...");
var task = _lgService.PowerOff();
var task = _lgService.PowerOffOnShutdownOrResume();
Utils.WaitForTask(task);
Logger.Debug("Done powering off tv");
//ExecPowerOffPreset(true);
@@ -1049,6 +1035,7 @@ namespace ColorControl
btnApplyLg.Enabled = enabled;
btnCloneLg.Enabled = enabled;
edtNameLg.Enabled = enabled;
cbxLgPresetDevice.Enabled = enabled;
edtShortcutLg.Enabled = enabled;
btnSetShortcutLg.Enabled = enabled;
edtStepsLg.Enabled = enabled;
@@ -1065,6 +1052,20 @@ namespace ColorControl
cbxLgApps.SelectedIndex = lgApps == null ? -1 : lgApps.FindIndex(x => x.appId.Equals(preset.appId));
edtShortcutLg.Text = preset.shortcut;
edtStepsLg.Text = preset.steps.Aggregate("", (a, b) => (string.IsNullOrEmpty(a) ? "" : a + ", ") + b);
var index = -1;
for (var i = 0; i < cbxLgPresetDevice.Items.Count; i++)
{
var pnpDev = (LgDevice)cbxLgPresetDevice.Items[i];
if ((string.IsNullOrEmpty(preset.DeviceMacAddress) && string.IsNullOrEmpty(pnpDev.MacAddress)) || pnpDev.MacAddress.Equals(preset.DeviceMacAddress, StringComparison.OrdinalIgnoreCase))
{
index = i;
break;
}
}
cbxLgPresetDevice.SelectedIndex = index;
}
else
{
@@ -1072,6 +1073,7 @@ namespace ColorControl
cbxLgApps.SelectedIndex = -1;
edtShortcutLg.Text = string.Empty;
edtStepsLg.Text = string.Empty;
cbxLgPresetDevice.SelectedIndex = -1;
}
}
@@ -1105,6 +1107,16 @@ namespace ColorControl
preset.name = name;
if (cbxLgPresetDevice.SelectedIndex == -1)
{
preset.DeviceMacAddress = string.Empty;
}
else
{
var device = (LgDevice)cbxLgPresetDevice.SelectedItem;
preset.DeviceMacAddress = device.MacAddress;
}
if (cbxLgApps.SelectedIndex == -1)
{
preset.appId = string.Empty;
@@ -1199,6 +1211,15 @@ namespace ColorControl
{
FillLgDevices();
}
if (scLgController.Panel2.Controls.Count == 0)
{
var rcPanel = new RemoteControlPanel(_lgService, _lgService.GetRemoteControlButtons());
rcPanel.Parent = scLgController.Panel2;
rcPanel.Dock = DockStyle.Fill;
}
chkLgRemoteControlShow.Checked = _lgService.Config.ShowRemoteControl;
scLgController.Panel2Collapsed = !_lgService.Config.ShowRemoteControl;
}
}
else if (tcMain.SelectedTab == tabLog)
@@ -1236,15 +1257,21 @@ namespace ColorControl
private void FillLgDevices()
{
cbxLgDevices.Items.Clear();
var devices = _lgService.Devices;
foreach (var device in devices)
cbxLgDevices.Items.Clear();
cbxLgDevices.Items.AddRange(devices.ToArray());
cbxLgPresetDevice.Items.Clear();
cbxLgPresetDevice.Items.AddRange(devices.ToArray());
var globalDevice = new LgDevice("Globally selected device", string.Empty, string.Empty, true, true);
cbxLgPresetDevice.Items.Insert(0, globalDevice);
var device = _lgService.SelectedDevice;
if (device != null)
{
cbxLgDevices.Items.Add(device);
}
if (_lgService.SelectedDevice != null)
{
cbxLgDevices.SelectedIndex = cbxLgDevices.Items.IndexOf(_lgService.SelectedDevice);
cbxLgDevices.SelectedIndex = cbxLgDevices.Items.IndexOf(device);
}
if (!devices.Any())
@@ -1252,10 +1279,32 @@ namespace ColorControl
MessageForms.WarningOk("It seems there's no LG TV available! Please make sure it's connected to the same network as this PC.");
}
if (cbxLgApps.Items.Count == 0 && _lgService.SelectedDevice != null)
if (cbxLgApps.Items.Count == 0 && device != null)
{
_lgService.RefreshApps().ContinueWith((task) => BeginInvoke(new Action(FillLgApps)));
}
SetLgDevicePowerOptions();
}
private void SetLgDevicePowerOptions()
{
var device = _lgService.SelectedDevice;
clbLgPower.Enabled = device != null;
_disableEvents = true;
try
{
clbLgPower.SetItemChecked(0, device?.PowerOnAfterStartup ?? false);
clbLgPower.SetItemChecked(1, device?.PowerOnAfterResume ?? false);
clbLgPower.SetItemChecked(2, device?.PowerOffOnShutdown ?? false);
clbLgPower.SetItemChecked(3, device?.PowerOffOnStandby ?? false);
clbLgPower.SetItemChecked(4, device?.PowerSwitchOnScreenSaver ?? false);
}
finally
{
_disableEvents = false;
}
}
private void FillLgApps()
@@ -1542,9 +1591,13 @@ namespace ColorControl
}
else
{
_lgService.SelectedDevice = (PnpDev)cbxLgDevices.SelectedItem;
_lgService.SelectedDevice = (LgDevice)cbxLgDevices.SelectedItem;
}
btnLGRemoteControl.Enabled = _lgService.SelectedDevice != null;
chkLgRemoteControlShow.Enabled = _lgService.SelectedDevice != null;
btnLgRemoveDevice.Enabled = _lgService.SelectedDevice != null && _lgService.SelectedDevice.IsCustom;
btnLgDeviceConvertToCustom.Enabled = _lgService.SelectedDevice != null && !_lgService.SelectedDevice.IsCustom;
SetLgDevicePowerOptions();
}
private void lvLgPresets_DoubleClick(object sender, EventArgs e)
@@ -1620,12 +1673,18 @@ namespace ColorControl
private void clbLgPower_ItemCheck(object sender, ItemCheckEventArgs e)
{
if (!_initialized)
if (!_initialized || _disableEvents)
{
return;
}
if (!(_lgService.Config.PowerOnAfterResume || _lgService.Config.PowerOnAfterStartup || _lgService.Config.PowerSwitchOnScreenSaver))
var device = _lgService?.SelectedDevice;
if (device == null)
{
return;
}
if (!(device.PowerOnAfterResume || device.PowerOnAfterStartup || device.PowerSwitchOnScreenSaver))
{
MessageForms.InfoOk(
@"Be sure to activate the following setting on the TV, or the app will not be able to wake the TV:
@@ -1638,11 +1697,11 @@ See Options to test this functionality."
BeginInvoke(new Action(() =>
{
_lgService.Config.PowerOnAfterStartup = clbLgPower.GetItemChecked(0);
_lgService.Config.PowerOnAfterResume = clbLgPower.GetItemChecked(1);
_lgService.Config.PowerOffOnShutdown = clbLgPower.GetItemChecked(2);
_lgService.Config.PowerOffOnStandby = clbLgPower.GetItemChecked(3);
_lgService.Config.PowerSwitchOnScreenSaver = clbLgPower.GetItemChecked(4);
device.PowerOnAfterStartup = clbLgPower.GetItemChecked(0);
device.PowerOnAfterResume = clbLgPower.GetItemChecked(1);
device.PowerOffOnShutdown = clbLgPower.GetItemChecked(2);
device.PowerOffOnStandby = clbLgPower.GetItemChecked(3);
device.PowerSwitchOnScreenSaver = clbLgPower.GetItemChecked(4);
_lgService.InstallEventHandlers();
}));
@@ -1810,16 +1869,6 @@ Do you want to continue?";
private void btnLGRemoteControl_Click(object sender, EventArgs e)
{
if (_remoteControlForm == null || _remoteControlForm.IsDisposed)
{
var buttons = _lgService.GetRemoteControlButtons();
_remoteControlForm = new RemoteControlForm(_lgService, buttons);
_remoteControlForm.Show();
}
else
{
_remoteControlForm.Show();
}
}
private void AfterInitialized()
@@ -2273,5 +2322,116 @@ Do you want to continue?";
Clipboard.SetText(preset.id.ToString());
}
}
private void btnLgAddDevice_Click(object sender, EventArgs e)
{
var values = MessageForms.ShowDialog("Add tv", new[] { "Name", "Ip-address", "MAC-address" }, ValidateAddDevice);
if (values.Any())
{
var device = new LgDevice(values[0], values[1], values[2]);
var form = MessageForms.ShowProgress("Connecting to device...");
var result = false;
Enabled = false;
try
{
var task = device.TestConnection();
result = Utils.WaitForTask(task);
}
finally
{
form.Close();
Enabled = true;
}
if (!result && MessageForms.QuestionYesNo("Unable to connect to the device. Are you sure you want to add it?") != DialogResult.Yes)
{
return;
}
_lgService.AddCustomDevice(device);
FillLgDevices();
}
}
private string ValidateAddDevice(List<string> values)
{
if (values.Any(v => string.IsNullOrEmpty(v)))
{
return "Please fill in all the fields";
}
return null;
}
private void btnLgRemoveDevice_Click(object sender, EventArgs e)
{
if (MessageForms.QuestionYesNo("Are you sure you want to remove this device?") != DialogResult.Yes)
{
return;
}
var device = _lgService.SelectedDevice;
if (device != null)
{
_lgService.RemoveCustomDevice(device);
FillLgDevices();
}
}
private void mnuLgButtons_Opening(object sender, CancelEventArgs e)
{
mnuLgActions.DropDownItems.Clear();
var preset = GetSelectedLgPreset();
if (preset == null)
{
return;
}
var device = _lgService.GetPresetDevice(preset);
var actions = device?.GetInvokableActions();
foreach (var keyValuePair in actions)
{
var text = keyValuePair.Key;
var item = mnuLgActions.DropDownItems.Add(text);
item.Click += miLgAddAction_Click;
}
}
private void btnLgDeviceConvertToCustom_Click(object sender, EventArgs e)
{
if (MessageForms.QuestionYesNo(
@"This will convert the automatically detected device to a custom variant.
This means that the device will remain here even if it is not detected anymore.
Do you want to continue?"
) != DialogResult.Yes)
{
return;
}
_lgService.SelectedDevice.ConvertToCustom();
FillLgDevices();
}
private void btnLgRemoteControlShow_Click(object sender, EventArgs e)
{
}
private void chkLgRemoteControlShow_CheckedChanged(object sender, EventArgs e)
{
scLgController.Panel2Collapsed = !chkLgRemoteControlShow.Checked;
_lgService.Config.ShowRemoteControl = chkLgRemoteControlShow.Checked;
}
}
}
+3
View File
@@ -126,6 +126,9 @@
<metadata name="mnuLgButtons.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>147, 17</value>
</metadata>
<metadata name="backgroundWorker1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>419, 17</value>
</metadata>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
+88 -3
View File
@@ -1,8 +1,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ColorControl
@@ -30,5 +29,91 @@ namespace ColorControl
{
return MessageBox.Show(text, MainForm?.Text, MessageBoxButtons.YesNo, MessageBoxIcon.Information);
}
public static List<string> ShowDialog(string caption, IEnumerable<string> labels, Func<List<string>, string> validateFunc = null)
{
var values = new List<string>();
var prompt = new Form()
{
Width = 500,
Height = 90 + (labels.Count() * 55),
FormBorderStyle = FormBorderStyle.FixedDialog,
Text = caption,
StartPosition = FormStartPosition.CenterScreen
};
var top = 10;
var boxes = new List<TextBoxBase>();
foreach (var label in labels)
{
var textLabel = new Label() { Left = 40, Top = top, Text = label };
top += 25;
prompt.Controls.Add(textLabel);
var textBox = new MaskedTextBox() { Left = 40, Top = top, Width = 400 };
if (label.Contains("Ip-address"))
{
textBox.Mask = "###.###.###.###";
textBox.ValidatingType = typeof(System.Net.IPAddress);
textBox.Culture = CultureInfo.InvariantCulture;
}
else if (label.Contains("MAC-address"))
{
textBox.Mask = "AA-AA-AA-AA-AA-AA";
textBox.Culture = CultureInfo.InvariantCulture;
}
top += 30;
prompt.Controls.Add(textBox);
boxes.Add(textBox);
}
var confirmation = new Button() { Text = "Add", Left = 365, Width = 75, Top = prompt.ClientRectangle.Height - 30 };
confirmation.Click += (sender, e) =>
{
values.AddRange(boxes.Select(b => b.Text.Trim()));
if (validateFunc != null)
{
var message = validateFunc(values);
if (!string.IsNullOrEmpty(message))
{
WarningOk(message);
values.Clear();
return;
}
}
prompt.Close();
};
prompt.Controls.Add(confirmation);
prompt.AcceptButton = confirmation;
prompt.ShowDialog(MainForm);
return values;
}
public static Form ShowProgress(string caption)
{
var prompt = new Form()
{
Width = 300,
Height = 100,
FormBorderStyle = FormBorderStyle.FixedToolWindow,
Text = caption,
StartPosition = FormStartPosition.CenterScreen,
ControlBox = false
};
var textLabel = new Label() { Left = 20, Top = 25, Text = caption, AutoSize = true };
prompt.Controls.Add(textLabel);
prompt.Show(MainForm);
return prompt;
}
}
}
}
+17 -5
View File
@@ -1,11 +1,14 @@
using Windows.Devices.Enumeration;
using Newtonsoft.Json;
using Windows.Devices.Enumeration;
using Windows.Devices.Enumeration.Pnp;
namespace ColorControl
{
class PnpDev
{
[JsonIgnore]
public DeviceInformation DevInfo { get; private set; }
[JsonIgnore]
public PnpObject PnpObject { get; private set; }
public string Name { get; private set; }
@@ -13,18 +16,27 @@ namespace ColorControl
public string MacAddress { get; private set; }
public PnpDev(DeviceInformation devInfo, PnpObject pnpObject, string name, string ipAddress, string macAddress)
[JsonConstructor]
public PnpDev(string name, string ipAddress, string macAddress)
{
DevInfo = devInfo;
PnpObject = pnpObject;
Name = name;
IpAddress = ipAddress;
MacAddress = macAddress;
}
public PnpDev(DeviceInformation devInfo, PnpObject pnpObject, string name, string ipAddress, string macAddress) : this(name, ipAddress, macAddress)
{
DevInfo = devInfo;
PnpObject = pnpObject;
}
public override string ToString()
{
return Name + " (" + IpAddress + ")";
return (IsCustom() ? "Custom: " : "Auto detect: ") + $"{Name}" + (!string.IsNullOrEmpty(IpAddress) ? $"({IpAddress})" : string.Empty);
}
public bool IsCustom()
{
return DevInfo == null;
}
}
}
+21
View File
@@ -1,12 +1,33 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
namespace ColorControl
{
[Flags]
enum PresetTrigger
{
[Description("Startup")]
Startup = 1,
[Description("Resume")]
Resume = 2,
[Description("Shutdown")]
Shutdown = 4,
[Description("Standby")]
Standby = 8,
[Description("Screensaver")]
Screensaver = 16,
[Description("Process start")]
ProcessStart = 32,
[Description("Process exit")]
ProcessExit = 64
}
internal abstract class PresetBase
{
public string name { get; set; }
public string shortcut { get; set; }
public PresetTrigger Triggers { get; set; }
private int _id;
+1 -1
View File
@@ -150,7 +150,7 @@ namespace ColorControl
Console.WriteLine("--amdpreset <preset name or id>: execute AMD-preset");
Console.WriteLine("--lgpreset <preset name> : execute LG-preset");
Console.WriteLine("--help : displays this help info");
Console.WriteLine("Options :");
Console.WriteLine("Options:");
Console.WriteLine("--nogui: starts command from the command line and will not open GUI (is forced when GUI is already running)");
result = true;
+2 -2
View File
@@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("2.2.0.0")]
[assembly: AssemblyFileVersion("2.2.0.0")]
[assembly: AssemblyVersion("3.0.0.0")]
[assembly: AssemblyFileVersion("3.0.0.0")]
+55 -26
View File
@@ -8,10 +8,10 @@
// </auto-generated>
//------------------------------------------------------------------------------
namespace ColorControl.Properties
{
namespace ColorControl.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
@@ -19,53 +19,82 @@ namespace ColorControl.Properties
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources
{
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources()
{
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager
{
get
{
if ((resourceMan == null))
{
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ColorControl.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture
{
get
{
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set
{
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap LG_remote {
get {
object obj = ResourceManager.GetObject("LG_remote", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized string similar to Power;120;132
///1;106;228
///2;196;228
///3;286;228
///4;106;296
///5;196;296
///6;286;296
///7;106;362
///8;196;362
///9;286;362
///0;196;425
///Vol +;98;513
///Mute;198;513
///Channel +;298;513
///Vol -;98;622
///Speech;198;633
///Channel -;298;622
///Home;78;695
///Settings;318;695.
/// </summary>
internal static string RC_coords {
get {
return ResourceManager.GetString("RC_coords", resourceCulture);
}
}
}
}
+15 -5
View File
@@ -46,7 +46,7 @@
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
@@ -60,6 +60,7 @@
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
@@ -68,9 +69,10 @@
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
@@ -85,9 +87,10 @@
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
@@ -109,9 +112,16 @@
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="LG_remote" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\LG_remote.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="RC_coords" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\RC_coords.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252</value>
</data>
</root>
+54 -10
View File
@@ -29,21 +29,25 @@ namespace ColorControl
/// </summary>
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(RemoteControlForm));
this.flowPanel = new System.Windows.Forms.FlowLayoutPanel();
this.btnClose = new System.Windows.Forms.Button();
this.lblUC = new System.Windows.Forms.Label();
this.chkDirectMode = new System.Windows.Forms.CheckBox();
this.pnlMouse = new System.Windows.Forms.Panel();
this.lblPos = new System.Windows.Forms.Label();
this.label1 = new System.Windows.Forms.Label();
this.label2 = new System.Windows.Forms.Label();
this.label3 = new System.Windows.Forms.Label();
this.SuspendLayout();
//
// flowPanel
//
this.flowPanel.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.flowPanel.Location = new System.Drawing.Point(12, 16);
this.flowPanel.Location = new System.Drawing.Point(12, 185);
this.flowPanel.Name = "flowPanel";
this.flowPanel.Size = new System.Drawing.Size(787, 164);
this.flowPanel.Size = new System.Drawing.Size(817, 120);
this.flowPanel.TabIndex = 0;
this.flowPanel.MouseDown += new System.Windows.Forms.MouseEventHandler(this.RemoteControlForm_MouseDown);
this.flowPanel.MouseMove += new System.Windows.Forms.MouseEventHandler(this.flowPanel_MouseMove);
@@ -56,7 +60,7 @@ namespace ColorControl
this.btnClose.FlatAppearance.BorderSize = 0;
this.btnClose.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.btnClose.ForeColor = System.Drawing.Color.White;
this.btnClose.Location = new System.Drawing.Point(796, 0);
this.btnClose.Location = new System.Drawing.Point(826, 0);
this.btnClose.Name = "btnClose";
this.btnClose.Size = new System.Drawing.Size(14, 23);
this.btnClose.TabIndex = 1;
@@ -68,7 +72,7 @@ namespace ColorControl
//
this.lblUC.AutoSize = true;
this.lblUC.ForeColor = System.Drawing.Color.Maroon;
this.lblUC.Location = new System.Drawing.Point(130, 0);
this.lblUC.Location = new System.Drawing.Point(12, 0);
this.lblUC.Name = "lblUC";
this.lblUC.Size = new System.Drawing.Size(100, 13);
this.lblUC.TabIndex = 0;
@@ -78,8 +82,8 @@ namespace ColorControl
//
this.chkDirectMode.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.chkDirectMode.AutoSize = true;
this.chkDirectMode.ForeColor = System.Drawing.Color.DarkGray;
this.chkDirectMode.Location = new System.Drawing.Point(12, 643);
this.chkDirectMode.ForeColor = System.Drawing.Color.White;
this.chkDirectMode.Location = new System.Drawing.Point(12, 718);
this.chkDirectMode.Name = "chkDirectMode";
this.chkDirectMode.Size = new System.Drawing.Size(105, 17);
this.chkDirectMode.TabIndex = 2;
@@ -93,9 +97,9 @@ namespace ColorControl
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.pnlMouse.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.pnlMouse.Location = new System.Drawing.Point(12, 191);
this.pnlMouse.Location = new System.Drawing.Point(12, 311);
this.pnlMouse.Name = "pnlMouse";
this.pnlMouse.Size = new System.Drawing.Size(787, 446);
this.pnlMouse.Size = new System.Drawing.Size(817, 401);
this.pnlMouse.TabIndex = 3;
this.pnlMouse.MouseEnter += new System.EventHandler(this.pnlMouse_MouseEnter);
this.pnlMouse.MouseMove += new System.Windows.Forms.MouseEventHandler(this.pnlMouse_MouseMove);
@@ -106,17 +110,53 @@ namespace ColorControl
this.lblPos.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.lblPos.AutoSize = true;
this.lblPos.ForeColor = System.Drawing.Color.Maroon;
this.lblPos.Location = new System.Drawing.Point(719, 643);
this.lblPos.Location = new System.Drawing.Point(749, 718);
this.lblPos.Name = "lblPos";
this.lblPos.Size = new System.Drawing.Size(0, 13);
this.lblPos.TabIndex = 1;
//
// label1
//
this.label1.AutoSize = true;
this.label1.ForeColor = System.Drawing.Color.White;
this.label1.Location = new System.Drawing.Point(12, 26);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(538, 52);
this.label1.TabIndex = 4;
this.label1.Text = resources.GetString("label1.Text");
//
// label2
//
this.label2.AutoSize = true;
this.label2.ForeColor = System.Drawing.Color.White;
this.label2.Location = new System.Drawing.Point(231, 91);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(216, 52);
this.label2.TabIndex = 5;
this.label2.Text = "Mouse:\r\n- left button: activate/open item\r\n- right button: go back\r\n- wheel: scro" +
"ll through menus (only vertically)";
//
// label3
//
this.label3.AutoSize = true;
this.label3.ForeColor = System.Drawing.Color.White;
this.label3.Location = new System.Drawing.Point(12, 91);
this.label3.Name = "label3";
this.label3.Size = new System.Drawing.Size(180, 91);
this.label3.TabIndex = 6;
this.label3.Text = "Keyboard keys:\r\n- arrow keys: navigate the menu\r\n- backspace: go back\r\n- escape: " +
"exit\r\n- S: open picture settings\r\n- H: open Home\r\n- media keys: volume up/down/m" +
"ute";
//
// RemoteControlForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = System.Drawing.Color.Black;
this.ClientSize = new System.Drawing.Size(811, 660);
this.ClientSize = new System.Drawing.Size(841, 735);
this.Controls.Add(this.label3);
this.Controls.Add(this.label2);
this.Controls.Add(this.label1);
this.Controls.Add(this.lblPos);
this.Controls.Add(this.pnlMouse);
this.Controls.Add(this.chkDirectMode);
@@ -132,6 +172,7 @@ namespace ColorControl
this.Text = "RemoteControlForm";
this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.RemoteControlForm_FormClosed);
this.Load += new System.EventHandler(this.RemoteControlForm_Load);
this.Shown += new System.EventHandler(this.RemoteControlForm_Shown);
this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.RemoteControlForm_KeyDown);
this.KeyUp += new System.Windows.Forms.KeyEventHandler(this.RemoteControlForm_KeyUp);
this.MouseDown += new System.Windows.Forms.MouseEventHandler(this.RemoteControlForm_MouseDown);
@@ -149,5 +190,8 @@ namespace ColorControl
private System.Windows.Forms.CheckBox chkDirectMode;
private System.Windows.Forms.Panel pnlMouse;
private System.Windows.Forms.Label lblPos;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.Label label3;
}
}
+20 -15
View File
@@ -116,24 +116,24 @@ namespace ColorControl
{
if (chkDirectMode.Checked)
{
MessageForms.InfoOk(
@"If you enable Direct Mode you will be able to control the TV directly via your keyboard and mouse.
Note that your mouse will be captured in a box and that due to pointer accelaration the control will not be perfect.
// MessageForms.InfoOk(
//@"If you enable Direct Mode you will be able to control the TV directly via your keyboard and mouse.
//Note that your mouse will be captured in a box and that due to pointer accelaration the control will not be perfect.
Keyboard keys:
- arrow keys: navigate the menu
- backspace: go back
- escape: exit
- S: open picture settings
- media keys: volume up/down/mute
//Keyboard keys:
//- arrow keys: navigate the menu
//- backspace: go back
//- escape: exit
//- S: open picture settings
//- media keys: volume up/down/mute
Mouse:
- left button: activate/open item
- right button: go back
- wheel: scroll through menus (only vertically)
//Mouse:
//- left button: activate/open item
//- right button: go back
//- wheel: scroll through menus (only vertically)
Press F1 to stop Direct Mode."
);
//Press F1 to stop Direct Mode."
// );
if (_mouseService == null)
{
@@ -216,6 +216,11 @@ Press F1 to stop Direct Mode."
}
}
private void RemoteControlForm_Shown(object sender, EventArgs e)
{
chkDirectMode.Checked = true;
}
private void pnlMouse_MouseMove(object sender, MouseEventArgs e)
{
HandleMouse(e);
+6
View File
@@ -117,4 +117,10 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="label1.Text" xml:space="preserve">
<value>This form allows you to control the TV directly via your keyboard and mouse.
Note that your mouse will be captured in a box and that due to pointer acceleration the control will not be perfect.
Press F1 to stop Direct Mode.</value>
</data>
</root>
+81
View File
@@ -0,0 +1,81 @@
namespace ColorControl
{
partial class RemoteControlPanel
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.pbRemote = new System.Windows.Forms.PictureBox();
this.btnRcDirect = new System.Windows.Forms.Button();
((System.ComponentModel.ISupportInitialize)(this.pbRemote)).BeginInit();
this.SuspendLayout();
//
// pbRemote
//
this.pbRemote.Dock = System.Windows.Forms.DockStyle.Fill;
this.pbRemote.Image = global::ColorControl.Properties.Resources.LG_remote;
this.pbRemote.Location = new System.Drawing.Point(0, 0);
this.pbRemote.Name = "pbRemote";
this.pbRemote.Size = new System.Drawing.Size(129, 468);
this.pbRemote.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom;
this.pbRemote.TabIndex = 0;
this.pbRemote.TabStop = false;
this.pbRemote.Click += new System.EventHandler(this.pbRemote_Click);
this.pbRemote.MouseMove += new System.Windows.Forms.MouseEventHandler(this.pbRemote_MouseMove);
//
// btnRcDirect
//
this.btnRcDirect.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.btnRcDirect.Location = new System.Drawing.Point(71, 7);
this.btnRcDirect.Name = "btnRcDirect";
this.btnRcDirect.Size = new System.Drawing.Size(55, 23);
this.btnRcDirect.TabIndex = 3;
this.btnRcDirect.Text = "Direct";
this.btnRcDirect.UseVisualStyleBackColor = true;
this.btnRcDirect.Click += new System.EventHandler(this.button1_Click);
//
// RemoteControlPanel
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = System.Drawing.Color.White;
this.Controls.Add(this.btnRcDirect);
this.Controls.Add(this.pbRemote);
this.Name = "RemoteControlPanel";
this.Size = new System.Drawing.Size(129, 468);
this.Load += new System.EventHandler(this.RemoteControlPanel_Load);
((System.ComponentModel.ISupportInitialize)(this.pbRemote)).EndInit();
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.PictureBox pbRemote;
private System.Windows.Forms.Button btnRcDirect;
}
}
+174
View File
@@ -0,0 +1,174 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
namespace ColorControl
{
partial class RemoteControlPanel : UserControl
{
private static int Range = 35;
private LgService _lgService;
private List<LgPreset> _buttons;
private RemoteControlForm _remoteControlForm;
private Bitmap _backupImage;
private LgPreset _lastHoveredPreset;
public RemoteControlPanel(LgService lgService, List<LgPreset> buttons)
{
_lgService = lgService;
_buttons = buttons;
InitializeComponent();
_backupImage = new Bitmap(pbRemote.Image);
var txtCoords = Utils.GetResourceFile("RC_coords.txt");
foreach (var line in txtCoords.Split('\n'))
{
var columns = line.Split(';');
var name = columns[0];
var preset = _buttons.FirstOrDefault(p => p.name.Equals(name));
if (preset != null)
{
preset.X = int.Parse(columns[1]);
preset.Y = int.Parse(columns[2]);
}
}
}
private void RemoteControlPanel_Load(object sender, EventArgs e)
{
}
private void ButtonClick(object sender, EventArgs e)
{
if (e is MouseEventArgs)
{
var preset = (LgPreset)(sender as Button).Tag;
var _ = _lgService.ApplyPreset(preset);
}
}
private void pbRemote_Click(object sender, EventArgs e)
{
if (e is MouseEventArgs mouseArgs)
{
var (posX, posY) = GetCoordsInImage(mouseArgs.X, mouseArgs.Y);
var preset = _buttons.FirstOrDefault(p => posX >= (p.X - Range) && posX <= (p.X + Range) && posY >= (p.Y - Range) && posY <= (p.Y + Range));
if (preset != null)
{
var _ = _lgService.ApplyPreset(preset);
}
}
}
private (int, int) GetCoordsInImage(int curX, int curY)
{
var width = pbRemote.Width;
var aspect = 0.27D;
var currentAspect = (double)pbRemote.Width / pbRemote.Height;
if (currentAspect > aspect)
{
width = (int)(aspect * pbRemote.Height);
curX = curX - ((pbRemote.Width - width) / 2);
}
else
{
var height = (int)(pbRemote.Width / aspect);
curY = curY - ((pbRemote.Height - height) / 2);
}
var factor = 405D / width;
var posX = (int)(curX * factor);
var posY = (int)(curY * factor);
return (posX, posY);
}
private void btnRcDirect_Click(object sender, EventArgs e)
{
}
private void pbRemote_MouseMove(object sender, MouseEventArgs mouseArgs)
{
var (posX, posY) = GetCoordsInImage(mouseArgs.X, mouseArgs.Y);
var preset = _buttons.FirstOrDefault(p => posX >= (p.X - Range) && posX <= (p.X + Range) && posY >= (p.Y - Range) && posY <= (p.Y + Range));
var refresh = false;
if (_lastHoveredPreset != null && (preset == null || preset != _lastHoveredPreset))
{
var image = (Bitmap)pbRemote.Image;
for (var y = Math.Max(0, _lastHoveredPreset.Y - Range); y < _lastHoveredPreset.Y + Range; y++)
{
for (var x = Math.Max(0, _lastHoveredPreset.X - Range); x < _lastHoveredPreset.X + Range; x++)
{
image.SetPixel(x, y, _backupImage.GetPixel(x, y));
}
}
refresh = true;
}
if (preset != null && preset != _lastHoveredPreset)
{
var image = (Bitmap)pbRemote.Image;
var rec = new Rectangle(Math.Max(0, preset.X - Range), Math.Max(0, preset.Y - Range), Range * 2, Range * 2);
using (var g = Graphics.FromImage(image))
{
using (var cloud_brush = new SolidBrush(Color.FromArgb(128, Color.White)))
{
g.FillEllipse(cloud_brush, rec);
}
}
//for (var y = preset.Y - 30; y < preset.Y + 30; y++)
//{
// for (var x = preset.X - 30; x < preset.X + 30; x++)
// {
// var pixel = image.GetPixel(x, y);
// image.SetPixel(x, y, Color.FromArgb(150, 0, 0));
// }
//}
refresh = true;
}
if (refresh)
{
pbRemote.Refresh();
}
_lastHoveredPreset = preset;
}
private void button1_Click(object sender, EventArgs e)
{
if (_remoteControlForm == null || _remoteControlForm.IsDisposed)
{
var buttons = _lgService.GetRemoteControlButtons();
_remoteControlForm = new RemoteControlForm(_lgService, buttons);
_remoteControlForm.Show();
}
else
{
_remoteControlForm.Show();
}
}
}
}
+120
View File
@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>
Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

+37
View File
@@ -0,0 +1,37 @@
Power;120;136
1;106;228
2;196;228
3;286;228
4;106;296
5;196;296
6;286;296
7;106;362
8;196;362
9;286;362
List;106;425
0;196;425
SAP;286;425
Vol +;98;513
Mute;198;513
Channel +;298;513
Vol -;98;622
Channel -;298;622
Home;78;695
Settings;318;695
Up;200;748
Left;98;852
Enter;200;852
Right;304;852
Down;200;952
Back;80;1000
TV Guide;320;1000
Netflix;92;1076
Inputs;200;1066
Amazon Prime;312;1076
Red;90;1150
Green;164;1150
Yellow;240;1150
Blue;316;1150
Rakuten TV;116;1218
Play;202;1218
Pause;291;1218
+31 -2
View File
@@ -353,16 +353,25 @@ namespace ColorControl
return devices;
}
public static T WaitForTask<T>(System.Threading.Tasks.Task<T> task)
public static T WaitForTask<T>(Task<T> task)
{
while (task != null && (task.Status < TaskStatus.WaitingForChildrenToComplete))
{
Thread.Sleep(100);
Thread.Sleep(10);
Application.DoEvents();
}
return task.Result;
}
public static void WaitForTask(System.Threading.Tasks.Task task)
{
while (task != null && (task.Status < TaskStatus.WaitingForChildrenToComplete))
{
Thread.Sleep(10);
Application.DoEvents();
}
}
public static void SetNotifyIconText(NotifyIcon ni, string text)
{
text = text.Substring(0, Math.Min(text.Length, 127));
@@ -686,6 +695,26 @@ namespace ColorControl
return null;
}
public static void StartProcess(string fileName, string arguments)
{
var process = Process.Start(fileName, arguments);
}
public static string GetResourceFile(string resourceName)
{
resourceName = "ColorControl.Resources." + resourceName;
var assembly = Assembly.GetExecutingAssembly();
using (Stream stream = assembly.GetManifestResourceStream(resourceName))
using (StreamReader reader = new StreamReader(stream))
{
var result = reader.ReadToEnd();
return result;
}
}
}
public static class ProcessExtensions
+7 -1
View File
@@ -91,7 +91,7 @@ namespace LgTv
public async Task<bool> Connect()
{
var ctx = _connection.Connect(new Uri(webSocketUri));
var ctx = _connection.Connect(new Uri(webSocketUri));
return await ctx;
}
@@ -365,6 +365,12 @@ namespace LgTv
return (string)response.sessionId;
}
public async Task Test()
{
var msg = JObject.Parse(@"{ ""configs"": { ""tv.model.motionProMode"": ""OLED Motion Pro"" } }");
var requestMessage = new RequestMessage("luna://com.webos.service.config/setConfigs", msg);
var response = await _connection.SendCommandAsync(requestMessage);
}
public void Close()
{
+22 -4
View File
@@ -44,12 +44,30 @@ namespace LgTv
_connection.MessageReceived += Connection_MessageReceived;
_connection.Closed += Connection_Closed;
await _connection.ConnectAsync(uri);
var cancellationTokenSource = new CancellationTokenSource();
var connectTask = _connection.ConnectAsync(uri).AsTask(cancellationTokenSource.Token);
IsConnected?.Invoke(true);
cancellationTokenSource.CancelAfter(5000);
_messageWriter = new DataWriter(_connection.OutputStream);
return true;
var result = await connectTask.ContinueWith((antecedent) =>
{
if (antecedent.Status == TaskStatus.RanToCompletion)
{
// connectTask ran to completion, so we know that the MessageWebSocket is connected.
// Add additional code here to use the MessageWebSocket.
IsConnected?.Invoke(true);
_messageWriter = new DataWriter(_connection.OutputStream);
return true;
}
else
{
// connectTask timed out, or faulted.
return false;
}
});
return result;
}
catch (Exception e)
{
+9 -1
View File
@@ -33,7 +33,15 @@ namespace LgTv
MUTE,
MENU,
CC,
DASH
DASH,
CHANNELUP,
CHANNELDOWN,
LIST,
AD,
SAP,
PROGRAM,
PLAY,
PAUSE
}
public class LgWebOsMouseService : IDisposable