From c1ace0bdd52616fbd0df08fff70aa8692a3234bb Mon Sep 17 00:00:00 2001 From: Vincent Maas Date: Sun, 1 May 2022 10:07:22 +0200 Subject: [PATCH] add sorting of list views, improve refresh rate/color depth apply order, fix manually turned off tv's --- ColorControl/ColorControl.csproj | 4 +- ColorControl/Config.cs | 16 +++- ColorControl/GraphicsService.cs | 4 +- ColorControl/LgDevice.cs | 13 ++- ColorControl/LgService.cs | 18 ++-- ColorControl/ListViewColumnSorter.cs | 118 +++++++++++++++++++++++++++ ColorControl/MainForm.Designer.cs | 3 + ColorControl/MainForm.cs | 101 +++++++++++++++++++++-- ColorControl/MessageForms.cs | 1 + ColorControl/NvService.cs | 52 ++++++------ ColorControl/RemoteControlPanel.cs | 15 +++- ColorControl/Utils.cs | 13 ++- ColorControl/lgtv/LgTvConnection.cs | 5 ++ 13 files changed, 315 insertions(+), 48 deletions(-) create mode 100644 ColorControl/ListViewColumnSorter.cs diff --git a/ColorControl/ColorControl.csproj b/ColorControl/ColorControl.csproj index 2e95144..1006946 100644 --- a/ColorControl/ColorControl.csproj +++ b/ColorControl/ColorControl.csproj @@ -18,8 +18,8 @@ Maassoft Maassoft 0 - 5.7.0.0 - 5.7.0.0 + 5.7.1.0 + 5.7.1.0 false true false diff --git a/ColorControl/Config.cs b/ColorControl/Config.cs index 1ba7865..2b8753d 100644 --- a/ColorControl/Config.cs +++ b/ColorControl/Config.cs @@ -1,5 +1,13 @@ -namespace ColorControl +using System.Windows.Forms; + +namespace ColorControl { + public class ListViewSortState + { + public SortOrder SortOrder { get; set; } + public int SortIndex { get; set; } + } + public class Config { public bool StartMinimized { get; set; } @@ -14,6 +22,9 @@ public int AmdPresetId_ApplyOnStartup { get; set; } public bool FixChromeFonts { get; set; } public bool UseGdiScaling { get; set; } + public ListViewSortState NvPresetsSortState { get; set; } + public ListViewSortState AmdPresetsSortState { get; set; } + public ListViewSortState LgPresetsSortState { get; set; } public Config() { @@ -25,6 +36,9 @@ CheckForUpdates = true; FixChromeFonts = false; UseGdiScaling = true; + LgPresetsSortState = new ListViewSortState(); + NvPresetsSortState = new ListViewSortState(); + AmdPresetsSortState = new ListViewSortState(); } } } diff --git a/ColorControl/GraphicsService.cs b/ColorControl/GraphicsService.cs index ea78a87..7fe4689 100644 --- a/ColorControl/GraphicsService.cs +++ b/ColorControl/GraphicsService.cs @@ -51,7 +51,7 @@ namespace ColorControl } } - protected bool SetRefreshRateInternal(string displayName, uint refreshRate, bool portrait, int horizontal, int vertical) + protected bool SetRefreshRateInternal(string displayName, uint refreshRate, bool portrait, int horizontal, int vertical, bool updateRegistry = false) { uint i = 0; DEVMODEA devMode; @@ -64,7 +64,7 @@ namespace ColorControl { IntPtr bla = Marshal.AllocHGlobal(Marshal.SizeOf(devMode)); Marshal.StructureToPtr(devMode, bla, false); - var result = NativeMethods.ChangeDisplaySettingsExA(displayName, bla, IntPtr.Zero, 0, IntPtr.Zero); + var result = NativeMethods.ChangeDisplaySettingsExA(displayName, bla, IntPtr.Zero, updateRegistry ? (uint)NativeConstants.CDS_UPDATEREGISTRY : 0, IntPtr.Zero); if (result != NativeConstants.DISP_CHANGE_SUCCESSFUL) { Logger.Error($"Could not set refreshrate {refreshRate} on display {displayName} because ChangeDisplaySettingsExA returned a non-zero return code: {result}"); diff --git a/ColorControl/LgDevice.cs b/ColorControl/LgDevice.cs index 915cce2..dbf7d64 100644 --- a/ColorControl/LgDevice.cs +++ b/ColorControl/LgDevice.cs @@ -21,6 +21,7 @@ namespace ColorControl public string Category { get; set; } public string Title { get; set; } public int CurrentValue { get; set; } + public int NumberOfValues { get; set; } public LgPreset Preset { get; set; } } @@ -170,7 +171,10 @@ namespace ColorControl AddGenericPictureAction("hdmiPcMode_hdmi2", typeof(FalseToTrue), category: "other"); AddGenericPictureAction("hdmiPcMode_hdmi3", typeof(FalseToTrue), category: "other"); AddGenericPictureAction("hdmiPcMode_hdmi4", typeof(FalseToTrue), category: "other"); - AddGenericPictureAction("adjustingLuminance", minValue: -50, maxValue: 50); + AddGenericPictureAction("adjustingLuminance", minValue: -50, maxValue: 50, numberOfValues: 22); + AddGenericPictureAction("whiteBalanceBlue", minValue: -50, maxValue: 50, numberOfValues: 22); + AddGenericPictureAction("whiteBalanceGreen", minValue: -50, maxValue: 50, numberOfValues: 22); + AddGenericPictureAction("whiteBalanceRed", minValue: -50, maxValue: 50, numberOfValues: 22); //AddGenericPictureAction("wb20PointsGammaValue", minValue: -50, maxValue: 50); AddInvokableAction("turnScreenOff", new Func, bool>(TurnScreenOffAction)); AddInvokableAction("turnScreenOn", new Func, bool>(TurnScreenOnAction)); @@ -212,7 +216,7 @@ namespace ColorControl _invokableActions.Add(action); } - private void AddGenericPictureAction(string name, Type type = null, decimal minValue = 0, decimal maxValue = 0, string category = "picture", string title = null) + private void AddGenericPictureAction(string name, Type type = null, decimal minValue = 0, decimal maxValue = 0, string category = "picture", string title = null, int numberOfValues = 1) { var action = new InvokableAction { @@ -221,6 +225,7 @@ namespace ColorControl EnumType = type, MinValue = minValue, MaxValue = maxValue, + NumberOfValues = numberOfValues, Category = category, Title = title == null ? Utils.FirstCharUpperCase(name) : title }; @@ -373,6 +378,10 @@ namespace ColorControl PoweredOffBy = PowerOffSource.Unknown; _poweredOffViaAppDateTime = DateTimeOffset.MinValue; } + else if (payload.processing != null && ((string)payload.processing).Equals("Request Power Off", StringComparison.Ordinal)) + { + PoweredOffBy = PoweredOffViaApp ? PowerOffSource.App : PowerOffSource.Manually; + } } else { PoweredOffBy = PoweredOffViaApp ? PowerOffSource.App : PowerOffSource.Manually; diff --git a/ColorControl/LgService.cs b/ColorControl/LgService.cs index fbd49ac..ad35c27 100644 --- a/ColorControl/LgService.cs +++ b/ColorControl/LgService.cs @@ -210,6 +210,7 @@ namespace ColorControl } list.Add(GeneratePreset("Power", step: "POWER")); + list.Add(GeneratePreset("Wol", step: "WOL")); 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")); @@ -867,14 +868,6 @@ namespace ColorControl sleep -= 100; } } - else if (state == PowerOnOffState.StandBy) - { - var standByScript = Path.Combine(Program.DataDir, "StandByScript.bat"); - if (File.Exists(standByScript)) - { - Utils.StartProcess(standByScript, hidden: true); - } - } Logger.Debug("Powering off tv(s)..."); var tasks = new List(); @@ -885,6 +878,15 @@ namespace ColorControl tasks.Add(task); } + if (state == PowerOnOffState.StandBy) + { + var standByScript = Path.Combine(Program.DataDir, "StandByScript.bat"); + if (File.Exists(standByScript)) + { + Utils.StartProcess(standByScript, hidden: true, wait: true); + } + } + var waitTask = Task.WhenAll(tasks.ToArray()); Utils.WaitForTask(waitTask); Logger.Debug("Done powering off tv(s)"); diff --git a/ColorControl/ListViewColumnSorter.cs b/ColorControl/ListViewColumnSorter.cs new file mode 100644 index 0000000..854c4f2 --- /dev/null +++ b/ColorControl/ListViewColumnSorter.cs @@ -0,0 +1,118 @@ +using System.Collections; +using System.Windows.Forms; + +namespace ColorControl +{ + /// + /// This class is an implementation of the 'IComparer' interface. + /// + public class ListViewColumnSorter : IComparer + { + /// + /// Specifies the column to be sorted + /// + private int ColumnToSort; + + /// + /// Specifies the order in which to sort (i.e. 'Ascending'). + /// + private SortOrder OrderOfSort; + + /// + /// Case insensitive comparer object + /// + private CaseInsensitiveComparer ObjectCompare; + + /// + /// Class constructor. Initializes various elements + /// + public ListViewColumnSorter() + { + // Initialize the column to '0' + ColumnToSort = 0; + + // Initialize the sort order to 'none' + OrderOfSort = SortOrder.None; + + // Initialize the CaseInsensitiveComparer object + ObjectCompare = new CaseInsensitiveComparer(); + } + + /// + /// This method is inherited from the IComparer interface. It compares the two objects passed using a case insensitive comparison. + /// + /// First object to be compared + /// Second object to be compared + /// The result of the comparison. "0" if equal, negative if 'x' is less than 'y' and positive if 'x' is greater than 'y' + public int Compare(object x, object y) + { + int compareResult; + ListViewItem listviewX, listviewY; + + // Cast the objects to be compared to ListViewItem objects + listviewX = (ListViewItem)x; + listviewY = (ListViewItem)y; + + if (listviewX.Tag == null) + { + compareResult = OrderOfSort == SortOrder.Ascending ? -1 : 1; + } + else if (listviewY.Tag == null) + { + compareResult = OrderOfSort == SortOrder.Ascending ? 1 : -1; + } + else + { + // Compare the two items + compareResult = ObjectCompare.Compare(listviewX.SubItems[ColumnToSort].Text, listviewY.SubItems[ColumnToSort].Text); + } + + // Calculate correct return value based on object comparison + if (OrderOfSort == SortOrder.Ascending) + { + // Ascending sort is selected, return normal result of compare operation + return compareResult; + } + else if (OrderOfSort == SortOrder.Descending) + { + // Descending sort is selected, return negative result of compare operation + return (-compareResult); + } + else + { + // Return '0' to indicate they are equal + return 0; + } + } + + /// + /// Gets or sets the number of the column to which to apply the sorting operation (Defaults to '0'). + /// + public int SortColumn + { + set + { + ColumnToSort = value; + } + get + { + return ColumnToSort; + } + } + + /// + /// Gets or sets the order of sorting to apply (for example, 'Ascending' or 'Descending'). + /// + public SortOrder Order + { + set + { + OrderOfSort = value; + } + get + { + return OrderOfSort; + } + } + } +} diff --git a/ColorControl/MainForm.Designer.cs b/ColorControl/MainForm.Designer.cs index c378320..489d328 100644 --- a/ColorControl/MainForm.Designer.cs +++ b/ColorControl/MainForm.Designer.cs @@ -655,6 +655,7 @@ this.lvNvPresets.TabIndex = 0; this.lvNvPresets.UseCompatibleStateImageBehavior = false; this.lvNvPresets.View = System.Windows.Forms.View.Details; + this.lvNvPresets.ColumnClick += new System.Windows.Forms.ColumnClickEventHandler(this.lvLgPresets_ColumnClick); this.lvNvPresets.SelectedIndexChanged += new System.EventHandler(this.lvNvPresets_SelectedIndexChanged); this.lvNvPresets.DoubleClick += new System.EventHandler(this.btnApply_Click); // @@ -966,6 +967,7 @@ this.lvAmdPresets.TabIndex = 10; this.lvAmdPresets.UseCompatibleStateImageBehavior = false; this.lvAmdPresets.View = System.Windows.Forms.View.Details; + this.lvAmdPresets.ColumnClick += new System.Windows.Forms.ColumnClickEventHandler(this.lvLgPresets_ColumnClick); this.lvAmdPresets.SelectedIndexChanged += new System.EventHandler(this.lvAmdPresets_SelectedIndexChanged); this.lvAmdPresets.DoubleClick += new System.EventHandler(this.btnApplyAmd_Click); // @@ -1338,6 +1340,7 @@ this.lvLgPresets.TabIndex = 8; this.lvLgPresets.UseCompatibleStateImageBehavior = false; this.lvLgPresets.View = System.Windows.Forms.View.Details; + this.lvLgPresets.ColumnClick += new System.Windows.Forms.ColumnClickEventHandler(this.lvLgPresets_ColumnClick); this.lvLgPresets.SelectedIndexChanged += new System.EventHandler(this.lvLgPresets_SelectedIndexChanged); this.lvLgPresets.DoubleClick += new System.EventHandler(this.lvLgPresets_DoubleClick); // diff --git a/ColorControl/MainForm.cs b/ColorControl/MainForm.cs index 268c3b9..d2ea351 100644 --- a/ColorControl/MainForm.cs +++ b/ColorControl/MainForm.cs @@ -8,6 +8,7 @@ using NvAPIWrapper.Display; using NvAPIWrapper.Native.Display; using NWin32; using System; +using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; @@ -140,6 +141,8 @@ namespace ColorControl FillNvPresets(); Logger.Debug("Initializing NVIDIA...Done."); + InitSortState(lvNvPresets, _config.NvPresetsSortState); + _nvService.AfterApplyPreset += NvServiceAfterApplyPreset; } catch (Exception ex) @@ -162,6 +165,8 @@ namespace ColorControl _amdService = new AmdService(_dataDir); FillAmdPresets(); + InitSortState(lvAmdPresets, _config.AmdPresetsSortState); + _amdService.AfterApplyPreset += AmdServiceAfterApplyPreset; } catch (Exception) @@ -191,6 +196,8 @@ namespace ColorControl edtLgDeviceFilter.Text = _lgService.Config.DeviceSearchKey; chkLgShowAdvancedActions.Checked = _lgService.Config.ShowAdvancedActions; + InitSortState(lvLgPresets, _config.LgPresetsSortState); + var values = Enum.GetValues(typeof(ButtonType)); foreach (var button in values) { @@ -229,6 +236,14 @@ namespace ColorControl } } + private void InitSortState(ListView listView, ListViewSortState sortState) + { + var sorter = new ListViewColumnSorter(); + sorter.SortColumn = sortState.SortIndex; + sorter.Order = sortState.SortOrder; + listView.ListViewItemSorter = sorter; + } + private void Invoke() { BeginInvoke(() => ToggleGameBar()); @@ -494,9 +509,24 @@ namespace ColorControl _amdService?.GlobalSave(); _lgService?.GlobalSave(); + SaveSortState(lvNvPresets.ListViewItemSorter, _config.NvPresetsSortState); + SaveSortState(lvAmdPresets.ListViewItemSorter, _config.AmdPresetsSortState); + SaveSortState(lvLgPresets.ListViewItemSorter, _config.LgPresetsSortState); + SaveConfig(); } + private void SaveSortState(IComparer comparer, ListViewSortState sortState) + { + if (!(comparer is ListViewColumnSorter sorter)) + { + return; + } + + sortState.SortOrder = sorter.Order; + sortState.SortIndex = sorter.SortColumn; + } + private void lvNvPresets_SelectedIndexChanged(object sender, EventArgs e) { var preset = GetSelectedNvPreset(); @@ -1879,22 +1909,46 @@ namespace ColorControl { if (action.MinValue != action.MaxValue) { - var values = MessageForms.ShowDialog("Enter value", new[] { - new MessageForms.FieldDefinition + List fields = new(); + + if (action.NumberOfValues == 1) + { + fields.Add(new MessageForms.FieldDefinition { Label = "Enter desired " + item.Text, FieldType = MessageForms.FieldType.Numeric, MinValue = action.MinValue, - MaxValue = action.MaxValue - } - }); + MaxValue = action.MaxValue, + }); + } + else + { + var array = Enumerable.Range(0, action.NumberOfValues); + + fields.AddRange(array.Select(i => new MessageForms.FieldDefinition + { + Label = "Value for " + i, + FieldType = MessageForms.FieldType.Numeric, + MinValue = action.MinValue, + MaxValue = action.MaxValue, + })); + } + + var values = MessageForms.ShowDialog("Enter value", fields); if (!values.Any()) { return; } - value = values.First().Value.ToString(); + if (values.Count > 1) + { + value = string.Join("; ", values.Select(v => v.Value)); + } + else + { + value = values.First().Value.ToString(); + } } } else @@ -1995,7 +2049,7 @@ namespace ColorControl return; } - if (!(device.PowerOnAfterResume || device.PowerOnAfterStartup || device.PowerSwitchOnScreenSaver)) + if (e.Index is 0 or 1 or 4 or 7 && !(device.PowerOnAfterResume || device.PowerOnAfterStartup || device.PowerSwitchOnScreenSaver || device.PowerByWindows)) { MessageForms.InfoOk( @"Be sure to activate the following setting on the TV, or the app will not be able to wake the TV: @@ -3151,5 +3205,38 @@ The InStart and Software Update items are now visible under the Expert-button." _lgService.SelectedDevice.HDMIPortNumber = cbxLgPcHdmiPort.SelectedIndex; } + + private void lvLgPresets_ColumnClick(object sender, ColumnClickEventArgs e) + { + var listView = (ListView)sender; + var sorter = (ListViewColumnSorter)listView.ListViewItemSorter; + + // Determine if clicked column is already the column that is being sorted. + if (e.Column == sorter.SortColumn) + { + //if (sorter.Order == SortOrder.None) + //{ + // sorter.Order = SortOrder.Ascending; + //} + // Reverse the current sort direction for this column. + if (sorter.Order == SortOrder.Ascending) + { + sorter.Order = SortOrder.Descending; + } + else + { + sorter.Order = SortOrder.Ascending; + } + } + else + { + // Set the column number that is to be sorted; default to ascending. + sorter.SortColumn = e.Column; + sorter.Order = SortOrder.Ascending; + } + + // Perform the sort with these new sort options. + listView.Sort(); + } } } \ No newline at end of file diff --git a/ColorControl/MessageForms.cs b/ColorControl/MessageForms.cs index 6665c87..2ce91b9 100644 --- a/ColorControl/MessageForms.cs +++ b/ColorControl/MessageForms.cs @@ -23,6 +23,7 @@ namespace ColorControl public IEnumerable Values { get; set; } public decimal MinValue { get; set; } public decimal MaxValue { get; set; } + public int NumberOfValues { get; set; } public object Value { get; set; } } diff --git a/ColorControl/NvService.cs b/ColorControl/NvService.cs index 80377b3..9073657 100644 --- a/ColorControl/NvService.cs +++ b/ColorControl/NvService.cs @@ -211,6 +211,13 @@ namespace ColorControl SetCurrentDisplay(preset); + var display = GetCurrentDisplay(); + + if (display == null) + { + return false; + } + var hdrEnabled = IsHDREnabled(); var newHdrEnabled = preset.applyHDR && (preset.HDREnabled || (preset.toggleHDR && !hdrEnabled)); @@ -218,17 +225,20 @@ namespace ColorControl if (preset.applyColorData && (ColorDataDiffers(preset.colorData) || (!newHdrEnabled && preset.applyColorData && preset.colorData.Colorimetry != ColorDataColorimetry.Auto))) { - var display = GetCurrentDisplay(); - if (display == null) - { - return false; - } + //if (hdrEnabled) + //{ + // SetHDRState(display, false); - if (hdrEnabled) - { - SetHDRState(display, false); + // applyHdr = false; + //} - applyHdr = false; + if (preset.applyRefreshRate) + { + var timing = display.DisplayDevice.CurrentTiming; + if (preset.refreshRate < timing.Extra.RefreshRate) + { + SetRefreshRate(preset.refreshRate, true); + } } try @@ -241,22 +251,16 @@ namespace ColorControl result = false; } - if (hdrEnabled && newHdrEnabled) - { - SetHDRState(display, true, useSwitch: appContext.StartUpParams.NoGui); + //if (hdrEnabled && newHdrEnabled) + //{ + // SetHDRState(display, true, useSwitch: appContext.StartUpParams.NoGui); - applyHdr = false; - } + // applyHdr = false; + //} } if (applyHdr) { - var display = GetCurrentDisplay(); - if (display == null) - { - return false; - } - var colorData = preset.applyColorData ? preset.colorData : display.DisplayDevice.CurrentColorData; SetHDRState(display, newHdrEnabled, colorData, appContext.StartUpParams.NoGui); @@ -264,7 +268,9 @@ namespace ColorControl if (preset.applyRefreshRate) { - if (!SetRefreshRate(preset.refreshRate)) + var timing = display.DisplayDevice.CurrentTiming; + + if (preset.refreshRate != timing.Extra.RefreshRate && !SetRefreshRate(preset.refreshRate, true)) { result = false; } @@ -425,7 +431,7 @@ namespace ColorControl return dither; } - public bool SetRefreshRate(uint refreshRate) + public bool SetRefreshRate(uint refreshRate, bool updateRegistry = false) { var display = GetCurrentDisplay(); if (display == null) @@ -442,7 +448,7 @@ namespace ColorControl var portrait = new[] { Rotate.Degree90, Rotate.Degree270 }.Contains(display.DisplayDevice.ScanOutInformation.SourceToTargetRotation); - return SetRefreshRateInternal(display.Name, refreshRate, portrait, timing.HorizontalVisible, timing.VerticalVisible); + return SetRefreshRateInternal(display.Name, refreshRate, portrait, timing.HorizontalVisible, timing.VerticalVisible, updateRegistry); } public List GetAvailableRefreshRates(NvPreset preset = null) diff --git a/ColorControl/RemoteControlPanel.cs b/ColorControl/RemoteControlPanel.cs index 5ef437d..b432230 100644 --- a/ColorControl/RemoteControlPanel.cs +++ b/ColorControl/RemoteControlPanel.cs @@ -1,4 +1,5 @@ -using System; +using NLog; +using System; using System.Collections.Generic; using System.Drawing; using System.Linq; @@ -9,6 +10,7 @@ namespace ColorControl partial class RemoteControlPanel : UserControl { private static int Range = 35; + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); private LgService _lgService; private List _buttons; @@ -65,6 +67,17 @@ namespace ColorControl if (preset != null) { + if (preset.steps.Contains("POWER") && _lgService.SelectedDevice?.CurrentState != LgDevice.PowerState.Active) + { + Logger.Debug("Executing WOL instead of POWER"); + + var wolPreset = _buttons.FirstOrDefault(p => p.name.Equals("Wol")); + if (wolPreset != null) + { + preset = wolPreset; + } + } + var _ = _lgService.ApplyPreset(preset); } } diff --git a/ColorControl/Utils.cs b/ColorControl/Utils.cs index 8b614d6..02c0fe9 100644 --- a/ColorControl/Utils.cs +++ b/ColorControl/Utils.cs @@ -891,6 +891,11 @@ namespace ColorControl } } } + + if (listView.ListViewItemSorter is ListViewColumnSorter sorter && sorter?.Order != SortOrder.None) + { + listView.Sort(); + } } public static bool OpenConsole() @@ -937,9 +942,13 @@ namespace ColorControl return null; } - public static void StartProcess(string fileName, string arguments = null, bool hidden = false) + public static void StartProcess(string fileName, string arguments = null, bool hidden = false, bool wait = false) { - Process.Start(new ProcessStartInfo(fileName, arguments) { UseShellExecute = true, WindowStyle = hidden ? ProcessWindowStyle.Hidden : ProcessWindowStyle.Normal }); + var process = Process.Start(new ProcessStartInfo(fileName, arguments) { UseShellExecute = true, WindowStyle = hidden ? ProcessWindowStyle.Hidden : ProcessWindowStyle.Normal }); + if (wait) + { + process.WaitForExit(); + } } public static string GetResourceFile(string resourceName) diff --git a/ColorControl/lgtv/LgTvConnection.cs b/ColorControl/lgtv/LgTvConnection.cs index 601c33f..a44dd65 100644 --- a/ColorControl/lgtv/LgTvConnection.cs +++ b/ColorControl/lgtv/LgTvConnection.cs @@ -101,6 +101,11 @@ namespace LgTv { try { + if (_messageWriter == null) + { + throw new Exception("SendMessageAsync: no writer"); + } + _messageWriter.WriteString(message); using (var cancellationTokenSource = new CancellationTokenSource(5000))