diff --git a/Source/Installer/Installer.wixproj b/Source/Installer/Installer.wixproj
index b622f0a6..328c48c3 100644
--- a/Source/Installer/Installer.wixproj
+++ b/Source/Installer/Installer.wixproj
@@ -39,6 +39,20 @@
INSTALLFOLDER
+
+
+ $(WixExtDir)\WixUIExtension.dll
+ WixUIExtension
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Source/Installer/Resources/License.rtf b/Source/Installer/Resources/License.rtf
new file mode 100644
index 00000000..5e8043ab
Binary files /dev/null and b/Source/Installer/Resources/License.rtf differ
diff --git a/Source/Installer/Resources/banner.png b/Source/Installer/Resources/banner.png
new file mode 100644
index 00000000..832be823
Binary files /dev/null and b/Source/Installer/Resources/banner.png differ
diff --git a/Source/Installer/Resources/dialog.png b/Source/Installer/Resources/dialog.png
new file mode 100644
index 00000000..f3776290
Binary files /dev/null and b/Source/Installer/Resources/dialog.png differ
diff --git a/Source/Monitorian.Core/AppControllerCore.cs b/Source/Monitorian.Core/AppControllerCore.cs
index 87d9ce03..ed1b53aa 100644
--- a/Source/Monitorian.Core/AppControllerCore.cs
+++ b/Source/Monitorian.Core/AppControllerCore.cs
@@ -60,7 +60,7 @@ public AppControllerCore(AppKeeper keeper, SettingsCore settings)
public virtual async Task InitiateAsync()
{
- Settings.Initiate();
+ await Settings.InitiateAsync();
Settings.MonitorCustomizations.AbsoluteCapacity = MaxKnownMonitorsCount;
Settings.PropertyChanged += OnSettingsChanged;
@@ -145,13 +145,13 @@ protected virtual void ShowMenuWindow(Point pivot)
window.Show();
}
- protected virtual void OnSettingsInitiated()
+ protected virtual async void OnSettingsInitiated()
{
if (Settings.MakesOperationLog)
- Recorder = new("Initiated");
+ Recorder = await OperationRecorder.CreateAsync("Initiated");
}
- protected virtual void OnSettingsChanged(object sender, PropertyChangedEventArgs e)
+ protected virtual async void OnSettingsChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
@@ -160,7 +160,7 @@ protected virtual void OnSettingsChanged(object sender, PropertyChangedEventArgs
break;
case nameof(Settings.MakesOperationLog):
- Recorder = Settings.MakesOperationLog ? new("Enabled") : null;
+ Recorder = Settings.MakesOperationLog ? await OperationRecorder.CreateAsync("Enabled") : null;
break;
}
}
@@ -169,7 +169,7 @@ protected virtual void OnSettingsChanged(object sender, PropertyChangedEventArgs
protected virtual async void OnMonitorsChangeInferred(object sender = null, PowerModes mode = default, int? count = null)
{
- Recorder?.Record($"{nameof(OnMonitorsChangeInferred)} ({sender}{(mode == default ? string.Empty : $"- {mode} {count}")})");
+ await (Recorder?.RecordAsync($"{nameof(OnMonitorsChangeInferred)} ({sender}{(mode == default ? string.Empty : $"- {mode} {count}")})") ?? Task.CompletedTask);
if (count == 0)
return;
@@ -177,11 +177,18 @@ protected virtual async void OnMonitorsChangeInferred(object sender = null, Powe
await ScanAsync(TimeSpan.FromSeconds(3));
}
- protected internal virtual void OnMonitorsChangeFound()
+ protected internal virtual async void OnMonitorAccessFailed(AccessResult result)
+ {
+ await (Recorder?.RecordAsync($"{nameof(OnMonitorAccessFailed)}" + Environment.NewLine
+ + $"Status: {result.Status}" + Environment.NewLine
+ + $"Message: {result.Message}") ?? Task.CompletedTask);
+ }
+
+ protected internal virtual async void OnMonitorsChangeFound()
{
if (Monitors.Any())
{
- Recorder?.Record($"{nameof(OnMonitorsChangeFound)}");
+ await (Recorder?.RecordAsync($"{nameof(OnMonitorsChangeFound)}") ?? Task.CompletedTask);
_displayWatcher.RaiseDisplaySettingsChanged();
}
@@ -270,7 +277,7 @@ await Task.Run(async () =>
var maxMonitorsCount = await GetMaxMonitorsCountAsync();
var updateResults = await Task.WhenAll(Monitors
- .Where(x => x.IsLikelyControllable)
+ .Where(x => x.IsReachable)
.Select((x, index) =>
{
if (index < maxMonitorsCount)
diff --git a/Source/Monitorian.Core/Helper/ArraySearch.cs b/Source/Monitorian.Core/Helper/ArraySearch.cs
index 8904a360..7abaa87a 100644
--- a/Source/Monitorian.Core/Helper/ArraySearch.cs
+++ b/Source/Monitorian.Core/Helper/ArraySearch.cs
@@ -26,7 +26,7 @@ public static int GetNearestIndex(byte[] array, byte target) =>
public static int GetNearestIndex(T[] array, T target, Func measure) where T : IComparable
{
- if ((array is null) || (array.Length == 0))
+ if (array is null or { Length: 0 })
throw new ArgumentNullException(nameof(array));
// The source array must be sorted beforehand.
diff --git a/Source/Monitorian.Core/Models/AppDataService.cs b/Source/Monitorian.Core/Models/AppDataService.cs
index f38c2eb6..d49eff09 100644
--- a/Source/Monitorian.Core/Models/AppDataService.cs
+++ b/Source/Monitorian.Core/Models/AppDataService.cs
@@ -30,7 +30,56 @@ public static void AssureFolder()
Directory.CreateDirectory(FolderPath);
}
- public static void Load(T instance, string fileName) where T : class
+ #region Access
+
+ public static IEnumerable EnumerateFileNames(string searchPattern)
+ {
+ if (!Directory.Exists(FolderPath))
+ return Enumerable.Empty();
+
+ return Directory.EnumerateFiles(FolderPath, searchPattern)
+ .Select(x => Path.GetFileName(x));
+ }
+
+ public static async Task ReadAsync(string fileName)
+ {
+ var filePath = Path.Combine(FolderPath, fileName);
+
+ if (!File.Exists(filePath))
+ return null;
+
+ using var sr = new StreamReader(filePath, Encoding.UTF8);
+ return await sr.ReadToEndAsync();
+ }
+
+ public static async Task WriteAsync(string fileName, bool append, string content)
+ {
+ AssureFolder();
+
+ var filePath = Path.Combine(FolderPath, fileName);
+
+ using var sw = new StreamWriter(filePath, append, Encoding.UTF8); // BOM will be emitted.
+ await sw.WriteAsync(content);
+ }
+
+ public static void Delete(string fileName)
+ {
+ var filePath = Path.Combine(FolderPath, fileName);
+ File.Delete(filePath);
+ }
+
+ public static void Rename(string oldFileName, string newFileName)
+ {
+ var oldFilePath = Path.Combine(FolderPath, oldFileName);
+ var newFilePath = Path.Combine(FolderPath, newFileName);
+ File.Move(oldFilePath, newFilePath);
+ }
+
+ #endregion
+
+ #region Load/Save
+
+ public static void Load(T instance, string fileName, IEnumerable knownTypes = null) where T : class
{
var filePath = Path.Combine(FolderPath, fileName);
var fileInfo = new FileInfo(filePath);
@@ -43,7 +92,7 @@ public static void Load(T instance, string fileName) where T : class
using var xr = XmlReader.Create(sr);
var type = instance.GetType(); // GetType method works in derived class.
- var serializer = new DataContractSerializer(type);
+ var serializer = new DataContractSerializer(type, knownTypes);
var loaded = (T)serializer.ReadObject(xr);
type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
@@ -61,7 +110,7 @@ public static void Load(T instance, string fileName) where T : class
}
}
- public static void Save(T instance, string fileName) where T : class
+ public static void Save(T instance, string fileName, IEnumerable knownTypes = null) where T : class
{
AssureFolder();
@@ -71,9 +120,11 @@ public static void Save(T instance, string fileName) where T : class
using var xw = XmlWriter.Create(sw, new XmlWriterSettings { Indent = true });
var type = instance.GetType(); // GetType method works in derived class.
- var serializer = new DataContractSerializer(type);
+ var serializer = new DataContractSerializer(type, knownTypes);
serializer.WriteObject(xw, instance);
xw.Flush();
}
+
+ #endregion
}
}
\ No newline at end of file
diff --git a/Source/Monitorian.Core/Models/LogService.cs b/Source/Monitorian.Core/Models/LogService.cs
index e46c723c..ed046d7b 100644
--- a/Source/Monitorian.Core/Models/LogService.cs
+++ b/Source/Monitorian.Core/Models/LogService.cs
@@ -1,9 +1,11 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
+using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
+using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows;
@@ -14,13 +16,13 @@ namespace Monitorian.Core.Models
{
public class LogService
{
- private const string ProbeFileName = "probe.log";
- private const string OperationFileName = "operation.log";
- private const string ExceptionFileName = "exception.log";
-
private const string HeaderStart = "[Date:";
private static string ComposeHeader() => $"{HeaderStart} {DateTime.Now} Ver: {ProductInfo.Version}]";
+ #region Probe
+
+ private const string ProbeFileName = "probe.log";
+
///
/// Records probe log to Desktop.
///
@@ -44,32 +46,122 @@ public static void RecordProbe(string content)
RecordToDesktop(ProbeFileName, content);
}
+ #endregion
+
+ #region Operation
+
+ private const string OperationFileName = "operation.log";
+ private const string DateFormat = "yyyyMMdd";
+ private static string GetOperationDateFileName(DateTimeOffset date) => $"operation{date.ToString(DateFormat)}.log";
+
+ ///
+ /// The number of days before expiration of operation log file
+ ///
+ public static byte ExpiryDays { get; set; } = 6;
+
+ public static Task PrepareOperationAsync()
+ {
+ try
+ {
+ return GetOperationFileNamesAsync();
+ }
+ catch (Exception ex)
+ {
+ return Task.FromException(ex);
+ }
+ }
+
+ private static async Task GetOperationFileNamesAsync()
+ {
+ var expiryDate = DateTimeOffset.Now.Date.AddDays(-ExpiryDays);
+ var fileNamePattern = new Regex(@"^operation(?[0-9]{8}).log$");
+ var fileNames = new List();
+
+ foreach (var fileName in AppDataService.EnumerateFileNames("*.log").OrderBy(x => x))
+ {
+ var match = fileNamePattern.Match(fileName);
+ if (match.Success)
+ {
+ if (DateTimeOffset.TryParseExact(match.Groups["date"].Value,
+ DateFormat,
+ null,
+ DateTimeStyles.None,
+ out DateTimeOffset fileDate)
+ && (expiryDate <= fileDate))
+ {
+ fileNames.Add(fileName);
+ continue;
+ }
+
+ AppDataService.Delete(fileName);
+ }
+ }
+
+ #region Conversion
+
+ var content = await AppDataService.ReadAsync(OperationFileName);
+ if (content is not null)
+ {
+ if (fileNames.Any())
+ {
+ content += await AppDataService.ReadAsync(fileNames.First());
+ await AppDataService.WriteAsync(fileNames.First(), append: false, content);
+ AppDataService.Delete(OperationFileName);
+ }
+ else
+ {
+ var fileName = GetOperationDateFileName(DateTimeOffset.Now);
+ AppDataService.Rename(OperationFileName, fileName);
+ fileNames.Add(fileName);
+ }
+ }
+
+ #endregion
+
+ return fileNames.ToArray();
+ }
+
///
/// Records operation log to AppData.
///
/// Content
- /// The number of entries that the log file can contain
- ///
- /// The log file will be appended with new content as long as one day has not yet passed
- /// since last write. Otherwise, the log file will be overwritten.
- ///
- public static void RecordOperation(string content, int capacity = 128)
+ public static async Task RecordOperationAsync(string content)
{
- if (capacity <= 0)
- throw new ArgumentOutOfRangeException(nameof(capacity), capacity, "The capacity must be positive.");
-
content = ComposeHeader() + Environment.NewLine
+ content + Environment.NewLine + Environment.NewLine;
- RecordToAppData(OperationFileName, content, capacity);
+ var fileName = GetOperationDateFileName(DateTimeOffset.Now);
+
+ try
+ {
+ await AppDataService.WriteAsync(fileName, append: true, content);
+ }
+ catch (Exception ex)
+ {
+ Trace.WriteLine("Failed to record log to AppData." + Environment.NewLine
+ + ex);
+ }
}
///
- /// Copies operation log to Desktop.
+ /// Copies operation log from AppData to Desktop.
///
- public static void CopyOperation()
+ public static async Task CopyOperationAsync()
{
- if (!TryReadFromAppData(OperationFileName, out string content))
+ var buffer = new StringBuilder();
+
+ try
+ {
+ foreach (var fileName in await GetOperationFileNamesAsync())
+ buffer.Append(await AppDataService.ReadAsync(fileName));
+ }
+ catch (Exception ex)
+ {
+ Trace.WriteLine("Failed to read log from AppData." + Environment.NewLine
+ + ex);
+ }
+
+ if (buffer.Length == 0)
return;
if (MessageBox.Show(
@@ -80,9 +172,15 @@ public static void CopyOperation()
MessageBoxResult.OK) != MessageBoxResult.OK)
return;
- RecordToDesktop(OperationFileName, content);
+ RecordToDesktop(OperationFileName, buffer.ToString());
}
+ #endregion
+
+ #region Exception
+
+ private const string ExceptionFileName = "exception.log";
+
///
/// Records exception log to AppData and Desktop.
///
@@ -115,6 +213,8 @@ public static void RecordException(Exception exception, int capacity = 8)
RecordToDesktop(ExceptionFileName, content, capacity);
}
+ #endregion
+
#region Helper
private static void RecordToTemp(string fileName, string content, int capacity = 1)
@@ -123,7 +223,7 @@ private static void RecordToTemp(string fileName, string content, int capacity =
{
var tempFilePath = Path.Combine(Path.GetTempPath(), fileName);
- UpdateText(tempFilePath, content, capacity);
+ UpdateContent(tempFilePath, content, capacity);
}
catch (Exception ex)
{
@@ -138,11 +238,9 @@ private static void RecordToAppData(string fileName, string content, int capacit
{
AppDataService.AssureFolder();
- var appDataFilePath = Path.Combine(
- AppDataService.FolderPath,
- fileName);
+ var appDataFilePath = Path.Combine(AppDataService.FolderPath, fileName);
- UpdateText(appDataFilePath, content, capacity);
+ UpdateContent(appDataFilePath, content, capacity);
}
catch (Exception ex)
{
@@ -151,32 +249,6 @@ private static void RecordToAppData(string fileName, string content, int capacit
}
}
- private static bool TryReadFromAppData(string fileName, out string content)
- {
- var appDataFilePath = Path.Combine(
- AppDataService.FolderPath,
- fileName);
-
- if (File.Exists(appDataFilePath))
- {
- try
- {
- using (var sr = new StreamReader(appDataFilePath, Encoding.UTF8))
- {
- content = sr.ReadToEnd();
- return true;
- }
- }
- catch (Exception ex)
- {
- Trace.WriteLine("Failed to read log from AppData." + Environment.NewLine
- + ex);
- }
- }
- content = null;
- return false;
- }
-
private static void RecordToDesktop(string fileName, string content, int capacity = 1)
{
try
@@ -185,7 +257,7 @@ private static void RecordToDesktop(string fileName, string content, int capacit
Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory),
fileName);
- UpdateText(desktopFilePath, content, capacity);
+ UpdateContent(desktopFilePath, content, capacity);
}
catch (Exception ex)
{
@@ -194,42 +266,40 @@ private static void RecordToDesktop(string fileName, string content, int capacit
}
}
- private static void SaveText(string filePath, string content)
- {
- using (var sw = new StreamWriter(filePath, false, Encoding.UTF8)) // BOM will be emitted.
- sw.Write(content);
- }
-
- private static void UpdateText(string filePath, string newContent, int capacity)
+ ///
+ /// Updates content of a log file.
+ ///
+ /// File path
+ /// New content
+ /// The number of entries that the log file can contain
+ ///
+ /// The log file will be appended with new content as long as one day has not yet passed
+ /// since last write. Otherwise, the log file will be overwritten.
+ ///
+ private static void UpdateContent(string filePath, string newContent, int capacity)
{
string oldContent = null;
if ((1 < capacity) && File.Exists(filePath) && (File.GetLastWriteTime(filePath) > DateTime.Now.AddDays(-1)))
{
- using (var sr = new StreamReader(filePath, Encoding.UTF8))
- oldContent = sr.ReadToEnd();
+ using var sr = new StreamReader(filePath, Encoding.UTF8);
+ oldContent = sr.ReadToEnd();
- oldContent = TruncateSections(oldContent, HeaderStart, capacity - 1);
+ if (!string.IsNullOrEmpty(oldContent))
+ oldContent = TruncateSections(oldContent, HeaderStart, capacity - 1);
}
- SaveText(filePath, oldContent + newContent);
- }
-
- private static string TruncateSections(string source, string sectionHeader, int sectionCount)
- {
- if (string.IsNullOrEmpty(sectionHeader))
- throw new ArgumentNullException(nameof(sectionHeader));
- if (sectionCount <= 0)
- throw new ArgumentOutOfRangeException(nameof(sectionCount), sectionCount, "The count must be greater than 0.");
+ using var sw = new StreamWriter(filePath, false, Encoding.UTF8); // BOM will be emitted.
+ sw.Write(oldContent + newContent);
- if (string.IsNullOrEmpty(source))
- return string.Empty;
-
- var firstIndex = source.StartsWith(sectionHeader, StringComparison.Ordinal) ? new[] { 0 } : Enumerable.Empty();
- var secondIndices = source.IndicesOf('\n' /* either CR+Lf or Lf */ + sectionHeader, StringComparison.Ordinal).Select(x => x + 1);
- var indices = firstIndex.Concat(secondIndices).ToArray();
+ static string TruncateSections(string content, string sectionHeader, int sectionCount)
+ {
+ var firstIndex = content.StartsWith(sectionHeader, StringComparison.Ordinal) ? new[] { 0 } : Enumerable.Empty();
+ var secondIndices = content.IndicesOf('\n' /* either CR+Lf or Lf */ + sectionHeader, StringComparison.Ordinal).Select(x => x + 1);
+ var indices = firstIndex.Concat(secondIndices).ToArray();
- return source.Substring(indices[Math.Max(0, indices.Length - sectionCount)]);
+ return content.Substring(indices[Math.Max(0, indices.Length - sectionCount)]);
+ }
}
#endregion
diff --git a/Source/Monitorian.Core/Models/Monitor/DdcMonitorItem.cs b/Source/Monitorian.Core/Models/Monitor/DdcMonitorItem.cs
index 21b4489a..97a84921 100644
--- a/Source/Monitorian.Core/Models/Monitor/DdcMonitorItem.cs
+++ b/Source/Monitorian.Core/Models/Monitor/DdcMonitorItem.cs
@@ -13,7 +13,7 @@ namespace Monitorian.Core.Models.Monitor
internal class DdcMonitorItem : MonitorItem
{
private readonly SafePhysicalMonitorHandle _handle;
- private readonly bool _useLowLevel;
+ private readonly bool _useHighLevel;
public DdcMonitorItem(
string deviceInstanceId,
@@ -21,7 +21,7 @@ public DdcMonitorItem(
byte displayIndex,
byte monitorIndex,
SafePhysicalMonitorHandle handle,
- bool useLowLevel = false) : base(
+ bool useHighLevel = true) : base(
deviceInstanceId: deviceInstanceId,
description: description,
displayIndex: displayIndex,
@@ -29,7 +29,7 @@ public DdcMonitorItem(
isReachable: true)
{
this._handle = handle ?? throw new ArgumentNullException(nameof(handle));
- this._useLowLevel = useLowLevel;
+ this._useHighLevel = useHighLevel;
}
private uint _minimum = 0; // Raw minimum brightness (not always 0)
@@ -37,9 +37,9 @@ public DdcMonitorItem(
public override AccessResult UpdateBrightness(int brightness = -1)
{
- var (result, minimum, current, maximum) = MonitorConfiguration.GetBrightness(_handle, _useLowLevel);
+ var (result, minimum, current, maximum) = MonitorConfiguration.GetBrightness(_handle, _useHighLevel);
- if ((result == AccessResult.Succeeded) && (minimum < maximum) && (minimum <= current) && (current <= maximum))
+ if ((result.Status == AccessStatus.Succeeded) && (minimum < maximum) && (minimum <= current) && (current <= maximum))
{
this.Brightness = (int)Math.Round((double)(current - minimum) / (maximum - minimum) * 100D, MidpointRounding.AwayFromZero);
this._minimum = minimum;
@@ -47,7 +47,7 @@ public override AccessResult UpdateBrightness(int brightness = -1)
}
else
{
- this.Brightness = -1;
+ this.Brightness = -1; // Default
}
return result;
}
@@ -59,9 +59,9 @@ public override AccessResult SetBrightness(int brightness)
var buffer = (uint)Math.Round(brightness / 100D * (_maximum - _minimum) + _minimum, MidpointRounding.AwayFromZero);
- var result = MonitorConfiguration.SetBrightness(_handle, buffer, _useLowLevel);
+ var result = MonitorConfiguration.SetBrightness(_handle, buffer, _useHighLevel);
- if (result == AccessResult.Succeeded)
+ if (result.Status == AccessStatus.Succeeded)
{
this.Brightness = brightness;
}
diff --git a/Source/Monitorian.Core/Models/Monitor/DisplayConfig.cs b/Source/Monitorian.Core/Models/Monitor/DisplayConfig.cs
index 88a5f1d6..42070df8 100644
--- a/Source/Monitorian.Core/Models/Monitor/DisplayConfig.cs
+++ b/Source/Monitorian.Core/Models/Monitor/DisplayConfig.cs
@@ -262,36 +262,41 @@ private enum DISPLAYCONFIG_DEVICE_INFO_TYPE : uint
#region Type
[DataContract]
- public class ConfigItem
+ public class DisplayItem : IDisplayItem
{
[DataMember(Order = 0)]
public string DeviceInstanceId { get; }
[DataMember(Order = 1)]
- public string FriendlyName { get; }
+ public string DisplayName { get; }
[DataMember(Order = 2)]
public bool IsInternal { get; }
[DataMember(Order = 3)]
+ public string ConnectionDescription { get; }
+
+ [DataMember(Order = 4)]
public bool IsAvailable { get; }
- public ConfigItem(
+ public DisplayItem(
string deviceInstanceId,
- string friendlyName,
+ string displayName,
bool isInternal,
+ string connectionDescription,
bool isAvailable)
{
this.DeviceInstanceId = deviceInstanceId;
- this.FriendlyName = friendlyName;
+ this.DisplayName = displayName;
this.IsInternal = isInternal;
+ this.ConnectionDescription = connectionDescription;
this.IsAvailable = isAvailable;
}
}
#endregion
- public static IEnumerable EnumerateDisplayConfigs()
+ public static IEnumerable EnumerateDisplayConfigs()
{
if (GetDisplayConfigBufferSizes(
QDC_ONLY_ACTIVE_PATHS,
@@ -330,12 +335,40 @@ public static IEnumerable EnumerateDisplayConfigs()
var deviceInstanceId = DeviceConversion.ConvertToDeviceInstanceId(deviceName.monitorDevicePath);
- yield return new ConfigItem(
+ yield return new DisplayItem(
deviceInstanceId: deviceInstanceId,
- friendlyName: deviceName.monitorFriendlyDeviceName,
+ displayName: deviceName.monitorFriendlyDeviceName,
isInternal: (deviceName.outputTechnology == DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY.DISPLAYCONFIG_OUTPUT_TECHNOLOGY_INTERNAL),
+ connectionDescription: GetConnectionDescription(deviceName.outputTechnology),
isAvailable: displayPath.targetInfo.targetAvailable);
}
}
+
+ private static string GetConnectionDescription(DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY outputTechnology)
+ {
+ return outputTechnology switch
+ {
+ DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY.DISPLAYCONFIG_OUTPUT_TECHNOLOGY_OTHER => "Other",
+ DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY.DISPLAYCONFIG_OUTPUT_TECHNOLOGY_HD15 => "VGA",
+ DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY.DISPLAYCONFIG_OUTPUT_TECHNOLOGY_SVIDEO or
+ DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY.DISPLAYCONFIG_OUTPUT_TECHNOLOGY_COMPOSITE_VIDEO or
+ DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY.DISPLAYCONFIG_OUTPUT_TECHNOLOGY_COMPONENT_VIDEO or
+ DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY.DISPLAYCONFIG_OUTPUT_TECHNOLOGY_D_JPN => "AnalogTV",
+ DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY.DISPLAYCONFIG_OUTPUT_TECHNOLOGY_DVI => "DVI",
+ DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY.DISPLAYCONFIG_OUTPUT_TECHNOLOGY_HDMI => "HDMI",
+ DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY.DISPLAYCONFIG_OUTPUT_TECHNOLOGY_LVDS => "LVDS",
+ DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY.DISPLAYCONFIG_OUTPUT_TECHNOLOGY_SDI => "SDI",
+ DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY.DISPLAYCONFIG_OUTPUT_TECHNOLOGY_DISPLAYPORT_EXTERNAL or
+ DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY.DISPLAYCONFIG_OUTPUT_TECHNOLOGY_DISPLAYPORT_EMBEDDED => "DisplayPort",
+ DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY.DISPLAYCONFIG_OUTPUT_TECHNOLOGY_UDI_EXTERNAL or
+ DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY.DISPLAYCONFIG_OUTPUT_TECHNOLOGY_UDI_EMBEDDED => "UDI",
+ DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY.DISPLAYCONFIG_OUTPUT_TECHNOLOGY_SDTVDONGLE => "SDTV",
+ DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY.DISPLAYCONFIG_OUTPUT_TECHNOLOGY_MIRACAST => "Miracast",
+ DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY.DISPLAYCONFIG_OUTPUT_TECHNOLOGY_INDIRECT_WIRED => "Wired",
+ DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY.DISPLAYCONFIG_OUTPUT_TECHNOLOGY_INDIRECT_VIRTUAL => "Virtual",
+ DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY.DISPLAYCONFIG_OUTPUT_TECHNOLOGY_INTERNAL => "Internal",
+ _ => null
+ };
+ }
}
}
\ No newline at end of file
diff --git a/Source/Monitorian.Core/Models/Monitor/DisplayMonitor.cs b/Source/Monitorian.Core/Models/Monitor/DisplayMonitor.cs
new file mode 100644
index 00000000..13e224ed
--- /dev/null
+++ b/Source/Monitorian.Core/Models/Monitor/DisplayMonitor.cs
@@ -0,0 +1,58 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.Serialization;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Monitorian.Core.Models.Monitor
+{
+ internal interface IDisplayItem
+ {
+ public string DeviceInstanceId { get; }
+ public string DisplayName { get; }
+ public bool IsInternal { get; }
+ public string ConnectionDescription { get; }
+ }
+
+ internal class DisplayMonitor
+ {
+ #region Type
+
+ [DataContract]
+ public class DisplayItem : IDisplayItem
+ {
+ [DataMember(Order = 0)]
+ public string DeviceInstanceId { get; }
+
+ [DataMember(Order = 1)]
+ public string DisplayName { get; }
+
+ [DataMember(Order = 2)]
+ public bool IsInternal { get; }
+
+ [DataMember(Order = 3)]
+ public string ConnectionDescription { get; }
+
+ [DataMember(Order = 4)]
+ public float PhysicalSize { get; }
+
+ public DisplayItem(Monitorian.Supplement.DisplayInformation.DisplayItem item)
+ {
+ this.DeviceInstanceId = item.DeviceInstanceId;
+ this.DisplayName = item.DisplayName;
+ this.IsInternal = item.IsInternal;
+ this.ConnectionDescription = item.ConnectionDescription;
+ this.PhysicalSize = item.PhysicalSize;
+ }
+ }
+
+ #endregion
+
+ public static Task GetDisplayMonitorsAsync()
+ {
+ return Monitorian.Supplement.DisplayInformation.GetDisplayMonitorsAsync()
+ .ContinueWith(task => task.Result.Select(x => new DisplayItem(x)).ToArray());
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/Monitorian.Core/Models/Monitor/Error.cs b/Source/Monitorian.Core/Models/Monitor/Error.cs
index d2cbc19d..ecf734b5 100644
--- a/Source/Monitorian.Core/Models/Monitor/Error.cs
+++ b/Source/Monitorian.Core/Models/Monitor/Error.cs
@@ -25,10 +25,10 @@ private static extern uint FormatMessage(
#endregion
- public static (string message, int errorCode) GetMessageCode()
+ public static (int errorCode, string message) GetCodeMessage()
{
var errorCode = Marshal.GetLastWin32Error();
- return (GetMessage(errorCode), errorCode);
+ return (errorCode, GetMessage(errorCode));
}
public static string GetMessage() => GetMessage(Marshal.GetLastWin32Error());
diff --git a/Source/Monitorian.Core/Models/Monitor/IMonitor.cs b/Source/Monitorian.Core/Models/Monitor/IMonitor.cs
index fca9d625..4cd7f677 100644
--- a/Source/Monitorian.Core/Models/Monitor/IMonitor.cs
+++ b/Source/Monitorian.Core/Models/Monitor/IMonitor.cs
@@ -21,7 +21,7 @@ public interface IMonitor : IDisposable
AccessResult SetBrightness(int brightness);
}
- public enum AccessResult
+ public enum AccessStatus
{
None = 0,
Succeeded,
@@ -29,4 +29,15 @@ public enum AccessResult
DdcFailed,
NoLongerExist
}
+
+ public class AccessResult
+ {
+ public AccessStatus Status { get; }
+ public string Message { get; }
+
+ public AccessResult(AccessStatus status, string message) => (this.Status, this.Message) = (status, message);
+
+ public static readonly AccessResult Succeeded = new(AccessStatus.Succeeded, null);
+ public static readonly AccessResult Failed = new(AccessStatus.Failed, null);
+ }
}
\ No newline at end of file
diff --git a/Source/Monitorian.Core/Models/Monitor/MonitorConfiguration.cs b/Source/Monitorian.Core/Models/Monitor/MonitorConfiguration.cs
index e753b4fe..9c38d8e2 100644
--- a/Source/Monitorian.Core/Models/Monitor/MonitorConfiguration.cs
+++ b/Source/Monitorian.Core/Models/Monitor/MonitorConfiguration.cs
@@ -348,7 +348,7 @@ private static IEnumerable EnumerateVcpCodes(string source)
/// Gets raw brightnesses not represented in percentage.
///
/// Physical monitor handle
- /// Whether to use low level function
+ /// Whether to use high level function
///
/// result: Result
/// minimum: Raw minimum brightness (not always 0)
@@ -361,7 +361,7 @@ private static IEnumerable EnumerateVcpCodes(string source)
/// in percentage using those values. They are used to convert brightness in percentage
/// back to raw brightness when settings brightness as well.
///
- public static (AccessResult result, uint minimum, uint current, uint maximum) GetBrightness(SafePhysicalMonitorHandle physicalMonitorHandle, bool useLowLevel = false)
+ public static (AccessResult result, uint minimum, uint current, uint maximum) GetBrightness(SafePhysicalMonitorHandle physicalMonitorHandle, bool useHighLevel = true)
{
if (physicalMonitorHandle is null)
throw new ArgumentNullException(nameof(physicalMonitorHandle));
@@ -372,7 +372,7 @@ public static (AccessResult result, uint minimum, uint current, uint maximum) Ge
return (result: AccessResult.Failed, 0, 0, 0);
}
- if (!useLowLevel)
+ if (useHighLevel)
{
if (GetMonitorBrightness(
physicalMonitorHandle,
@@ -385,9 +385,9 @@ public static (AccessResult result, uint minimum, uint current, uint maximum) Ge
current: currentBrightness,
maximum: maximumBrightness);
}
- var (message, errorCode) = Error.GetMessageCode();
+ var (errorCode, message) = Error.GetCodeMessage();
Debug.WriteLine($"Failed to get brightnesses (High level). {message}");
- return (result: GetResult(errorCode), 0, 0, 0);
+ return (result: new AccessResult(GetStatus(errorCode), $"High level, {message}"), 0, 0, 0);
}
else
{
@@ -403,9 +403,9 @@ public static (AccessResult result, uint minimum, uint current, uint maximum) Ge
current: currentValue,
maximum: maximumValue);
}
- var (message, errorCode) = Error.GetMessageCode();
+ var (errorCode, message) = Error.GetCodeMessage();
Debug.WriteLine($"Failed to get brightnesses (Low level). {message}");
- return (result: GetResult(errorCode), 0, 0, 0);
+ return (result: new AccessResult(GetStatus(errorCode), $"Low level, {message}"), 0, 0, 0);
}
}
@@ -414,9 +414,9 @@ public static (AccessResult result, uint minimum, uint current, uint maximum) Ge
///
/// Physical monitor handle
/// Raw brightness (not always 0 to 100)
- /// Whether to use low level function
+ /// Whether to use high level function
/// Result
- public static AccessResult SetBrightness(SafePhysicalMonitorHandle physicalMonitorHandle, uint brightness, bool useLowLevel = false)
+ public static AccessResult SetBrightness(SafePhysicalMonitorHandle physicalMonitorHandle, uint brightness, bool useHighLevel = true)
{
if (physicalMonitorHandle is null)
throw new ArgumentNullException(nameof(physicalMonitorHandle));
@@ -427,7 +427,7 @@ public static AccessResult SetBrightness(SafePhysicalMonitorHandle physicalMonit
return AccessResult.Failed;
}
- if (!useLowLevel)
+ if (useHighLevel)
{
// SetMonitorBrightness function may return true even when it actually failed.
if (SetMonitorBrightness(
@@ -436,9 +436,9 @@ public static AccessResult SetBrightness(SafePhysicalMonitorHandle physicalMonit
{
return AccessResult.Succeeded;
}
- var (message, errorCode) = Error.GetMessageCode();
+ var (errorCode, message) = Error.GetCodeMessage();
Debug.WriteLine($"Failed to set brightness (High level). {message}");
- return GetResult(errorCode);
+ return new AccessResult(GetStatus(errorCode), $"High level {message}");
}
else
{
@@ -449,9 +449,9 @@ public static AccessResult SetBrightness(SafePhysicalMonitorHandle physicalMonit
{
return AccessResult.Succeeded;
}
- var (message, errorCode) = Error.GetMessageCode();
+ var (errorCode, message) = Error.GetCodeMessage();
Debug.WriteLine($"Failed to set brightness (Low level). {message}");
- return GetResult(errorCode);
+ return new AccessResult(GetStatus(errorCode), $"Low level, {message}");
}
}
@@ -465,7 +465,7 @@ public static AccessResult SetBrightness(SafePhysicalMonitorHandle physicalMonit
private const uint ERROR_GRAPHICS_DDCCI_INVALID_MESSAGE_CHECKSUM = 0xC026258B;
private const uint ERROR_GRAPHICS_MONITOR_NO_LONGER_EXISTS = 0xC026258D;
- private static AccessResult GetResult(int errorCode)
+ private static AccessStatus GetStatus(int errorCode)
{
return unchecked((uint)errorCode) switch
{
@@ -473,9 +473,9 @@ ERROR_GRAPHICS_DDCCI_VCP_NOT_SUPPORTED or
ERROR_GRAPHICS_DDCCI_INVALID_DATA or
ERROR_GRAPHICS_DDCCI_INVALID_MESSAGE_COMMAND or
ERROR_GRAPHICS_DDCCI_INVALID_MESSAGE_LENGTH or
- ERROR_GRAPHICS_DDCCI_INVALID_MESSAGE_CHECKSUM => AccessResult.DdcFailed,
- ERROR_GRAPHICS_MONITOR_NO_LONGER_EXISTS => AccessResult.NoLongerExist,
- _ => AccessResult.Failed
+ ERROR_GRAPHICS_DDCCI_INVALID_MESSAGE_CHECKSUM => AccessStatus.DdcFailed,
+ ERROR_GRAPHICS_MONITOR_NO_LONGER_EXISTS => AccessStatus.NoLongerExist,
+ _ => AccessStatus.Failed
};
}
diff --git a/Source/Monitorian.Core/Models/Monitor/MonitorManager.cs b/Source/Monitorian.Core/Models/Monitor/MonitorManager.cs
index d5afbec9..f1cbeb59 100644
--- a/Source/Monitorian.Core/Models/Monitor/MonitorManager.cs
+++ b/Source/Monitorian.Core/Models/Monitor/MonitorManager.cs
@@ -11,7 +11,6 @@
using System.Threading.Tasks;
using Monitorian.Core.Helper;
-using Monitorian.Supplement;
namespace Monitorian.Core.Models.Monitor
{
@@ -54,9 +53,9 @@ public static async Task> EnumerateMonitorsAsync()
private static async Task> GetMonitorDevicesAsync()
{
- var displayItems = OsVersion.Is10Redstone4OrNewer
- ? await DisplayInformation.GetDisplayMonitorsAsync()
- : Array.Empty();
+ IDisplayItem[] displayItems = OsVersion.Is10Redstone4OrNewer
+ ? await DisplayMonitor.GetDisplayMonitorsAsync()
+ : DisplayConfig.EnumerateDisplayConfigs().ToArray();
var deviceItems = DeviceContext.EnumerateMonitorDevices().ToArray();
_ids = new HashSet(deviceItems.Select(x => x.DeviceInstanceId));
@@ -123,7 +122,7 @@ private static IEnumerable EnumerateMonitors(List devi
displayIndex: deviceItem.DisplayIndex,
monitorIndex: deviceItem.MonitorIndex,
handle: physicalItem.Handle,
- useLowLevel: physicalItem.IsLowLevelSupported);
+ useHighLevel: physicalItem.IsHighLevelSupported);
deviceItems.RemoveAt(index);
if (deviceItems.Count == 0)
@@ -132,35 +131,29 @@ private static IEnumerable EnumerateMonitors(List devi
}
// Obtained by WMI
- var installedItems = DeviceInformation.EnumerateInstalledMonitors().ToArray();
-
foreach (var desktopItem in MSMonitor.EnumerateDesktopMonitors())
{
- foreach (var installedItem in installedItems)
+ int index = -1;
+ if (desktopItem.BrightnessLevels.Any())
{
- int index = -1;
- if (desktopItem.BrightnessLevels.Any())
- {
- index = deviceItems.FindIndex(x =>
- string.Equals(x.DeviceInstanceId, desktopItem.DeviceInstanceId, StringComparison.OrdinalIgnoreCase) &&
- string.Equals(x.DeviceInstanceId, installedItem.DeviceInstanceId, StringComparison.OrdinalIgnoreCase));
- }
- if (index < 0)
- continue;
+ index = deviceItems.FindIndex(x =>
+ string.Equals(x.DeviceInstanceId, desktopItem.DeviceInstanceId, StringComparison.OrdinalIgnoreCase));
+ }
+ if (index < 0)
+ continue;
- var deviceItem = deviceItems[index];
- yield return new WmiMonitorItem(
- deviceInstanceId: deviceItem.DeviceInstanceId,
- description: deviceItem.AlternateDescription,
- displayIndex: deviceItem.DisplayIndex,
- monitorIndex: deviceItem.MonitorIndex,
- brightnessLevels: desktopItem.BrightnessLevels,
- isRemovable: installedItem.IsRemovable);
+ var deviceItem = deviceItems[index];
+ yield return new WmiMonitorItem(
+ deviceInstanceId: deviceItem.DeviceInstanceId,
+ description: deviceItem.AlternateDescription,
+ displayIndex: deviceItem.DisplayIndex,
+ monitorIndex: deviceItem.MonitorIndex,
+ isInternal: deviceItem.IsInternal,
+ brightnessLevels: desktopItem.BrightnessLevels);
- deviceItems.RemoveAt(index);
- if (deviceItems.Count == 0)
- yield break;
- }
+ deviceItems.RemoveAt(index);
+ if (deviceItems.Count == 0)
+ yield break;
}
// Unreachable neither by DDC/CI nor by WMI
@@ -225,8 +218,8 @@ public PhysicalItemPlus(
private void TestBrightness()
{
- var (getResult, minimum, current, maximum) = MonitorConfiguration.GetBrightness(Handle, IsLowLevelSupported);
- var isGetSuccess = (getResult == AccessResult.Succeeded);
+ var (getResult, minimum, current, maximum) = MonitorConfiguration.GetBrightness(Handle, IsHighLevelSupported);
+ var isGetSuccess = (getResult.Status == AccessStatus.Succeeded);
var isValid = (minimum < maximum) && (minimum <= current) && (current <= maximum);
GetBrightness = $"Success: {isGetSuccess}" + (isGetSuccess ? $", Valid: {isValid} (Minimum: {minimum}, Current: {current}, Maximum: {maximum})" : string.Empty);
@@ -238,13 +231,13 @@ private void TestBrightness()
expected = Math.Min(maximum, Math.Max(minimum, expected));
}
- var setResult = MonitorConfiguration.SetBrightness(Handle, expected, IsLowLevelSupported);
- var isSetSuccess = (setResult == AccessResult.Succeeded);
- var (_, _, actual, _) = MonitorConfiguration.GetBrightness(Handle, IsLowLevelSupported);
+ var setResult = MonitorConfiguration.SetBrightness(Handle, expected, IsHighLevelSupported);
+ var isSetSuccess = (setResult.Status == AccessStatus.Succeeded);
+ var (_, _, actual, _) = MonitorConfiguration.GetBrightness(Handle, IsHighLevelSupported);
SetBrightness = $"Success: {isSetSuccess}" + (isSetSuccess ? $", Match: {expected == actual} (Expected: {expected}, Actual: {actual})" : string.Empty);
if (isSetSuccess)
- MonitorConfiguration.SetBrightness(Handle, current, IsLowLevelSupported);
+ MonitorConfiguration.SetBrightness(Handle, current, IsHighLevelSupported);
}
}
@@ -260,20 +253,20 @@ private class MonitorData
[DataMember(Order = 1, Name = "Device Context - DeviceItems")]
public DeviceContext.DeviceItem[] DeviceItems { get; private set; }
- [DataMember(Order = 2, Name = "Display Config - ConfigItems")]
- public DisplayConfig.ConfigItem[] ConfigItems { get; private set; }
+ [DataMember(Order = 2, Name = "DisplayMonitor - DisplayItems")]
+ public DisplayMonitor.DisplayItem[] DisplayMonitorItems { get; private set; }
- [DataMember(Order = 3, Name = "Monitor Configuration - PhysicalItems")]
- public Dictionary PhysicalItems { get; private set; }
+ [DataMember(Order = 3, Name = "Display Config - DisplayItems")]
+ public DisplayConfig.DisplayItem[] DisplayConfigItems { get; private set; }
[DataMember(Order = 4, Name = "Device Installation - InstalledItems")]
public DeviceInformation.InstalledItem[] InstalledItems { get; private set; }
- [DataMember(Order = 5, Name = "MSMonitorClass - DesktopItems")]
- public MSMonitor.DesktopItem[] DesktopItems { get; private set; }
+ [DataMember(Order = 5, Name = "Monitor Configuration - PhysicalItems")]
+ public Dictionary PhysicalItems { get; private set; }
- [DataMember(Order = 6, Name = "DisplayMonitor - DisplayItems")]
- public DisplayInformation.DisplayItem[] DisplayItems { get; private set; }
+ [DataMember(Order = 6, Name = "MSMonitorClass - DesktopItems")]
+ public MSMonitor.DesktopItem[] DesktopItems { get; private set; }
[DataMember(Order = 7)]
public string[] ElapsedTime { get; private set; }
@@ -292,8 +285,17 @@ public async Task PopulateAsync()
GetTask(nameof(DeviceItems), () =>
DeviceItems = DeviceContext.EnumerateMonitorDevices().ToArray()),
- GetTask(nameof(ConfigItems), () =>
- ConfigItems = DisplayConfig.EnumerateDisplayConfigs().ToArray()),
+ GetTask(nameof(DisplayMonitorItems), async () =>
+ {
+ if (OsVersion.Is10Redstone4OrNewer)
+ DisplayMonitorItems = await DisplayMonitor.GetDisplayMonitorsAsync();
+ }),
+
+ GetTask(nameof(DisplayConfigItems), () =>
+ DisplayConfigItems = DisplayConfig.EnumerateDisplayConfigs().ToArray()),
+
+ GetTask(nameof(InstalledItems), () =>
+ InstalledItems = DeviceInformation.EnumerateInstalledMonitors().ToArray()),
GetTask(nameof(PhysicalItems), () =>
PhysicalItems = DeviceContext.GetMonitorHandles().ToDictionary(
@@ -302,17 +304,8 @@ public async Task PopulateAsync()
.Select(x => new PhysicalItemPlus(x))
.ToArray())),
- GetTask(nameof(InstalledItems), () =>
- InstalledItems = DeviceInformation.EnumerateInstalledMonitors().ToArray()),
-
GetTask(nameof(DesktopItems), () =>
- DesktopItems = MSMonitor.EnumerateDesktopMonitors().ToArray()),
-
- GetTask(nameof(DisplayItems), async () =>
- {
- if (OsVersion.Is10Redstone4OrNewer)
- DisplayItems = await DisplayInformation.GetDisplayMonitorsAsync();
- })
+ DesktopItems = MSMonitor.EnumerateDesktopMonitors().ToArray())
};
sw.Start();
diff --git a/Source/Monitorian.Core/Models/Monitor/UnreachableMonitorItem.cs b/Source/Monitorian.Core/Models/Monitor/UnreachableMonitorItem.cs
index c527053c..8fb70c25 100644
--- a/Source/Monitorian.Core/Models/Monitor/UnreachableMonitorItem.cs
+++ b/Source/Monitorian.Core/Models/Monitor/UnreachableMonitorItem.cs
@@ -25,7 +25,7 @@ public UnreachableMonitorItem(
this.IsInternal = isInternal;
}
- public override AccessResult UpdateBrightness(int brightness = -1) => default;
- public override AccessResult SetBrightness(int brightness) => default;
+ public override AccessResult UpdateBrightness(int brightness = -1) => AccessResult.Failed;
+ public override AccessResult SetBrightness(int brightness) => AccessResult.Failed;
}
}
\ No newline at end of file
diff --git a/Source/Monitorian.Core/Models/Monitor/WmiMonitorItem.cs b/Source/Monitorian.Core/Models/Monitor/WmiMonitorItem.cs
index d64e3b65..3c3a5380 100644
--- a/Source/Monitorian.Core/Models/Monitor/WmiMonitorItem.cs
+++ b/Source/Monitorian.Core/Models/Monitor/WmiMonitorItem.cs
@@ -13,35 +13,29 @@ namespace Monitorian.Core.Models.Monitor
///
internal class WmiMonitorItem : MonitorItem
{
+ private readonly bool _isInternal;
private readonly byte[] _brightnessLevels;
- private readonly bool _isRemovable;
public WmiMonitorItem(
string deviceInstanceId,
string description,
byte displayIndex,
byte monitorIndex,
- IEnumerable brightnessLevels,
- bool isRemovable) : base(
+ bool isInternal,
+ IEnumerable brightnessLevels) : base(
deviceInstanceId: deviceInstanceId,
description: description,
displayIndex: displayIndex,
monitorIndex: monitorIndex,
isReachable: true)
{
+ this._isInternal = isInternal;
this._brightnessLevels = brightnessLevels?.ToArray() ?? throw new ArgumentNullException(nameof(brightnessLevels));
- this._isRemovable = isRemovable;
}
public override AccessResult UpdateBrightness(int brightness = -1)
{
- if (_isRemovable)
- {
- this.Brightness = (0 <= brightness)
- ? brightness
- : MSMonitor.GetBrightness(DeviceInstanceId);
- }
- else
+ if (_isInternal)
{
this.Brightness = PowerManagement.GetActiveSchemeBrightness();
@@ -51,6 +45,12 @@ public override AccessResult UpdateBrightness(int brightness = -1)
? brightness
: MSMonitor.GetBrightness(DeviceInstanceId);
}
+ else
+ {
+ this.Brightness = (0 <= brightness)
+ ? brightness
+ : MSMonitor.GetBrightness(DeviceInstanceId);
+ }
return (0 <= this.Brightness) ? AccessResult.Succeeded : AccessResult.Failed;
}
@@ -59,11 +59,9 @@ public override AccessResult SetBrightness(int brightness)
if (brightness is < 0 or > 100)
throw new ArgumentOutOfRangeException(nameof(brightness), brightness, "The brightness must be within 0 to 100.");
- if (_isRemovable)
+ if (_isInternal)
{
- brightness = ArraySearch.GetNearest(_brightnessLevels, (byte)brightness);
-
- if (MSMonitor.SetBrightness(DeviceInstanceId, brightness))
+ if (PowerManagement.SetActiveSchemeBrightness(brightness))
{
this.Brightness = brightness;
return AccessResult.Succeeded;
@@ -71,7 +69,9 @@ public override AccessResult SetBrightness(int brightness)
}
else
{
- if (PowerManagement.SetActiveSchemeBrightness(brightness))
+ brightness = ArraySearch.GetNearest(_brightnessLevels, (byte)brightness);
+
+ if (MSMonitor.SetBrightness(DeviceInstanceId, brightness))
{
this.Brightness = brightness;
return AccessResult.Succeeded;
diff --git a/Source/Monitorian.Core/Models/OperationRecorder.cs b/Source/Monitorian.Core/Models/OperationRecorder.cs
index ae85f4d4..ab9182a0 100644
--- a/Source/Monitorian.Core/Models/OperationRecorder.cs
+++ b/Source/Monitorian.Core/Models/OperationRecorder.cs
@@ -11,20 +11,24 @@ namespace Monitorian.Core.Models
{
public class OperationRecorder
{
- ///
- /// The number of entries that operation log file can contain
- ///
- public static int Capacity { get; set; } = 128;
+ private OperationRecorder()
+ { }
- public OperationRecorder(string message) => LogService.RecordOperation(message, Capacity);
+ public static async Task CreateAsync(string message)
+ {
+ await LogService.PrepareOperationAsync();
+ await LogService.RecordOperationAsync(message);
+
+ return new();
+ }
- public void Record(string content) => LogService.RecordOperation(content, Capacity);
+ public Task RecordAsync(string content) => LogService.RecordOperationAsync(content);
#region Line
private readonly Lazy> _record = new(() => new(
TimeSpan.FromSeconds(1),
- async queue => await Task.Run(() => LogService.RecordOperation(string.Join(Environment.NewLine, queue), Capacity))));
+ async queue => await LogService.RecordOperationAsync(string.Join(Environment.NewLine, queue))));
private readonly Lazy>> _actionLines = new(() => new());
@@ -76,7 +80,7 @@ public async Task EndGroupRecordAsync()
{
var groupsStrings = _actionGroups.Value.GroupBy(x => x.groupName).Select(x => (x.Key, (object)x.Select(y => y.item))).ToArray();
- await Task.Run(() => LogService.RecordOperation($"{_actionName}{Environment.NewLine}{SimpleSerialization.Serialize(groupsStrings)}", Capacity));
+ await LogService.RecordOperationAsync($"{_actionName}{Environment.NewLine}{SimpleSerialization.Serialize(groupsStrings)}");
_actionName = null;
_actionGroups.Value.Clear();
diff --git a/Source/Monitorian.Core/Models/SettingsCore.cs b/Source/Monitorian.Core/Models/SettingsCore.cs
index c123ddf2..27cb8bfc 100644
--- a/Source/Monitorian.Core/Models/SettingsCore.cs
+++ b/Source/Monitorian.Core/Models/SettingsCore.cs
@@ -99,6 +99,8 @@ public bool MakesOperationLog
#endregion
+ protected Type[] KnownTypes { get; set; }
+
private const string SettingsFileName = "settings.xml";
private readonly string _fileName;
@@ -112,9 +114,9 @@ protected SettingsCore(string fileName)
private Throttle _save;
- protected internal virtual void Initiate()
+ protected internal virtual async Task InitiateAsync()
{
- Load(this);
+ await Task.Run(() => Load(this));
_save = new Throttle(
TimeSpan.FromMilliseconds(100),
@@ -130,7 +132,7 @@ private void Load(T instance) where T : class
{
try
{
- AppDataService.Load(instance, _fileName);
+ AppDataService.Load(instance, _fileName, KnownTypes);
}
catch (Exception ex)
{
@@ -143,7 +145,7 @@ private void Save(T instance) where T : class
{
try
{
- AppDataService.Save(instance, _fileName);
+ AppDataService.Save(instance, _fileName, KnownTypes);
}
catch (Exception ex)
{
diff --git a/Source/Monitorian.Core/Models/Watcher/PowerWatcher.cs b/Source/Monitorian.Core/Models/Watcher/PowerWatcher.cs
index fa0ae7b0..291d6a29 100644
--- a/Source/Monitorian.Core/Models/Watcher/PowerWatcher.cs
+++ b/Source/Monitorian.Core/Models/Watcher/PowerWatcher.cs
@@ -41,7 +41,7 @@ public PowerWatcher()
{
// Conform invocation timings so that the action would be executed efficiently when
// multiple and different events are fired almost simultaneously.
- _resumeWatcher = new PowerModeWatcher(this, 5, 5, 10, 10, 30, 30);
+ _resumeWatcher = new PowerModeWatcher(this, 5, 5, 10, 10, 30);
_statusWatcher = new PowerModeWatcher(this, 1, 4);
}
diff --git a/Source/Monitorian.Core/Models/Watcher/SystemEventsComplement.cs b/Source/Monitorian.Core/Models/Watcher/SystemEventsComplement.cs
index 670f6de5..2dcc5c5e 100644
--- a/Source/Monitorian.Core/Models/Watcher/SystemEventsComplement.cs
+++ b/Source/Monitorian.Core/Models/Watcher/SystemEventsComplement.cs
@@ -152,6 +152,6 @@ public class PowerSettingChangedEventArgs : EventArgs
public Guid Guid { get; }
public int Data { get; }
- public PowerSettingChangedEventArgs(Guid guid, int data) => (Guid, Data) = (guid, data);
+ public PowerSettingChangedEventArgs(Guid guid, int data) => (this.Guid, this.Data) = (guid, data);
}
}
\ No newline at end of file
diff --git a/Source/Monitorian.Core/Monitorian.Core.csproj b/Source/Monitorian.Core/Monitorian.Core.csproj
index 6c486ec2..45b7c194 100644
--- a/Source/Monitorian.Core/Monitorian.Core.csproj
+++ b/Source/Monitorian.Core/Monitorian.Core.csproj
@@ -69,6 +69,7 @@
+
diff --git a/Source/Monitorian.Core/Properties/AssemblyInfo.cs b/Source/Monitorian.Core/Properties/AssemblyInfo.cs
index a2927dbc..8ce02d01 100644
--- a/Source/Monitorian.Core/Properties/AssemblyInfo.cs
+++ b/Source/Monitorian.Core/Properties/AssemblyInfo.cs
@@ -33,8 +33,8 @@
// 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.13.3.0")]
-[assembly: AssemblyFileVersion("2.13.3.0")]
+[assembly: AssemblyVersion("2.14.0.0")]
+[assembly: AssemblyFileVersion("2.14.0.0")]
[assembly: NeutralResourcesLanguage("en-US")]
// For unit test
diff --git a/Source/Monitorian.Core/ViewModels/MonitorViewModel.cs b/Source/Monitorian.Core/ViewModels/MonitorViewModel.cs
index 75eeec58..fe00d82d 100644
--- a/Source/Monitorian.Core/ViewModels/MonitorViewModel.cs
+++ b/Source/Monitorian.Core/ViewModels/MonitorViewModel.cs
@@ -28,17 +28,17 @@ public MonitorViewModel(AppControllerCore controller, IMonitor monitor)
internal void Replace(IMonitor monitor)
{
- if (monitor is null)
- return;
-
- lock (_lock)
+ if (monitor?.IsReachable is true)
{
- // If IsReachable property is changed to true, reset _controllableCount.
- if (!this._monitor.IsReachable && monitor.IsReachable)
- _controllableCount = InitialCount;
-
- this._monitor.Dispose();
- this._monitor = monitor;
+ lock (_lock)
+ {
+ this._monitor.Dispose();
+ this._monitor = monitor;
+ }
+ }
+ else
+ {
+ monitor?.Dispose();
}
}
@@ -146,20 +146,24 @@ public bool UpdateBrightness(int brightness = -1)
result = _monitor.UpdateBrightness(brightness);
}
- switch (result)
+ switch (result.Status)
{
- case AccessResult.Succeeded:
+ case AccessStatus.Succeeded:
RaisePropertyChanged(nameof(BrightnessSystemChanged)); // This must be prior to Brightness.
RaisePropertyChanged(nameof(Brightness));
RaisePropertyChanged(nameof(BrightnessSystemAdjusted));
OnSucceeded();
return true;
- case AccessResult.NoLongerExist:
- _controller.OnMonitorsChangeFound();
- goto default;
-
default:
+ _controller.OnMonitorAccessFailed(result);
+
+ switch (result.Status)
+ {
+ case AccessStatus.NoLongerExist:
+ _controller.OnMonitorsChangeFound();
+ break;
+ }
OnFailed();
return false;
}
@@ -223,18 +227,23 @@ private bool SetBrightness(int brightness)
result = _monitor.SetBrightness(brightness);
}
- switch (result)
+ switch (result.Status)
{
- case AccessResult.Succeeded:
+ case AccessStatus.Succeeded:
RaisePropertyChanged(nameof(Brightness));
OnSucceeded();
return true;
- case AccessResult.NoLongerExist:
- _controller.OnMonitorsChangeFound();
- goto default;
-
default:
+ _controller.OnMonitorAccessFailed(result);
+
+ switch (result.Status)
+ {
+ case AccessStatus.DdcFailed:
+ case AccessStatus.NoLongerExist:
+ _controller.OnMonitorsChangeFound();
+ break;
+ }
OnFailed();
return false;
}
@@ -244,10 +253,10 @@ private bool SetBrightness(int brightness)
#region Controllable
- public bool IsControllable => _monitor.IsReachable && (0 < _controllableCount);
+ public bool IsReachable => _monitor.IsReachable;
- public bool IsLikelyControllable => IsControllable || _hasSucceeded;
- private bool _hasSucceeded;
+ public bool IsControllable => IsReachable && ((0 < _controllableCount) || _isConfirmed);
+ private bool _isConfirmed;
// This count is for determining IsControllable property.
// To set this count, the following points need to be taken into account:
@@ -257,13 +266,13 @@ private bool SetBrightness(int brightness)
// - The initial count is intended to give allowance for failures before the first success.
// If the count has been consumed without any success, the monitor will be regarded as
// uncontrollable at all.
- // - _hasSucceeded field indicates that the monitor has succeeded at least once. It will be
+ // - _isConfirmed field indicates that the monitor has succeeded at least once. It will be
// set true at the first success and at a succeeding success after a failure.
// - The normal count gives allowance for failures after the first and succeeding successes.
// As long as the monitor continues to succeed, the count will stay at the normal count.
// Each time the monitor fails, the count decreases. The decreased count will be reverted
// to the normal count when the monitor succeeds again.
- // - The initial count must be smaller than the normal count so that _hasSucceeded field
+ // - The initial count must be smaller than the normal count so that _isConfirmed field
// will be set at the first success while reducing unnecessary access to the field.
private short _controllableCount = InitialCount;
private const short InitialCount = 3;
@@ -278,10 +287,10 @@ private void OnSucceeded()
if (formerCount <= 0)
{
RaisePropertyChanged(nameof(IsControllable));
- RaisePropertyChanged(nameof(Status));
+ RaisePropertyChanged(nameof(Message));
}
- _hasSucceeded = true;
+ _isConfirmed = true;
}
}
@@ -290,23 +299,23 @@ private void OnFailed()
if (--_controllableCount == 0)
{
RaisePropertyChanged(nameof(IsControllable));
- RaisePropertyChanged(nameof(Status));
+ RaisePropertyChanged(nameof(Message));
}
}
- public string Status
+ public string Message
{
get
{
- if (IsControllable)
+ if (0 < _controllableCount)
return null;
LanguageService.Switch();
var reason = _monitor switch
{
- DdcMonitorItem _ => Resources.StatusReasonDdcFailing,
- UnreachableMonitorItem { IsInternal: false } _ => Resources.StatusReasonDdcNotEnabled,
+ DdcMonitorItem => Resources.StatusReasonDdcFailing,
+ UnreachableMonitorItem { IsInternal: false } => Resources.StatusReasonDdcNotEnabled,
_ => null,
};
@@ -358,7 +367,7 @@ public override string ToString()
(nameof(Name), Name),
(nameof(IsUnison), IsUnison),
(nameof(IsControllable), IsControllable),
- (nameof(IsLikelyControllable), IsLikelyControllable),
+ ("IsConfirmed", _isConfirmed),
("ControllableCount", _controllableCount),
(nameof(IsByKey), IsByKey),
(nameof(IsSelected), IsSelected),
diff --git a/Source/Monitorian.Core/ViewModels/ProbeSectionViewModel.cs b/Source/Monitorian.Core/ViewModels/ProbeSectionViewModel.cs
index 11fce4eb..6d83e551 100644
--- a/Source/Monitorian.Core/ViewModels/ProbeSectionViewModel.cs
+++ b/Source/Monitorian.Core/ViewModels/ProbeSectionViewModel.cs
@@ -40,7 +40,7 @@ public void PerformProbe()
public void PerformCopy()
{
- Task.Run(() => LogService.CopyOperation());
+ Task.Run(() => LogService.CopyOperationAsync());
}
public void PerformRescan()
diff --git a/Source/Monitorian.Core/Views/MainWindow.xaml b/Source/Monitorian.Core/Views/MainWindow.xaml
index 649918c7..77618e4a 100644
--- a/Source/Monitorian.Core/Views/MainWindow.xaml
+++ b/Source/Monitorian.Core/Views/MainWindow.xaml
@@ -581,6 +581,7 @@
+
@@ -592,7 +593,6 @@
@@ -670,15 +670,15 @@
Visibility="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Settings.ChangesRange, Converter={StaticResource BooleanToVisibilityConverterKey}}"/>
-
-
-
+
+
+ Text="{Binding Message, Mode=OneWay}"/>
diff --git a/Source/Monitorian.Supplement/DisplayInformation.cs b/Source/Monitorian.Supplement/DisplayInformation.cs
index c92704ca..a3850f41 100644
--- a/Source/Monitorian.Supplement/DisplayInformation.cs
+++ b/Source/Monitorian.Supplement/DisplayInformation.cs
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
using Windows.Devices.Display;
@@ -24,37 +23,31 @@ public class DisplayInformation
///
/// Display monitor information
///
- [DataContract]
public class DisplayItem
{
///
/// Device ID (Not device interface ID)
///
- [DataMember(Order = 0)]
public string DeviceInstanceId { get; }
///
/// Display name
///
- [DataMember(Order = 1)]
public string DisplayName { get; }
///
/// Whether the display is connected internally
///
- [DataMember(Order = 2)]
public bool IsInternal { get; }
///
/// Connection description
///
- [DataMember(Order = 3)]
public string ConnectionDescription { get; }
///
/// Physical size (diagonal) in inches
///
- [DataMember(Order = 4)]
public float PhysicalSize { get; }
internal DisplayItem(
@@ -111,7 +104,6 @@ public static async Task GetDisplayMonitorsAsync()
continue;
//Debug.WriteLine($"DeviceInstanceId: {deviceInstanceId}");
- //Debug.WriteLine($"DeviceName: {device.Name}");
//Debug.WriteLine($"DisplayName: {displayMonitor.DisplayName}");
//Debug.WriteLine($"ConnectionKind: {displayMonitor.ConnectionKind}");
//Debug.WriteLine($"PhysicalConnector: {displayMonitor.PhysicalConnector}");
@@ -148,6 +140,9 @@ private static string GetConnectionDescription(DisplayMonitorConnectionKind conn
case DisplayMonitorConnectionKind.Wired:
switch (connectorKind)
{
+ case DisplayMonitorPhysicalConnectorKind.HD15:
+ return "VGA";
+
case DisplayMonitorPhysicalConnectorKind.AnalogTV:
case DisplayMonitorPhysicalConnectorKind.DisplayPort:
return connectorKind.ToString();
@@ -157,9 +152,6 @@ private static string GetConnectionDescription(DisplayMonitorConnectionKind conn
case DisplayMonitorPhysicalConnectorKind.Lvds:
case DisplayMonitorPhysicalConnectorKind.Sdi:
return connectorKind.ToString().ToUpper();
-
- case DisplayMonitorPhysicalConnectorKind.HD15:
- return "VGA";
}
break;
}
diff --git a/Source/Monitorian.Supplement/Monitorian.Supplement.csproj b/Source/Monitorian.Supplement/Monitorian.Supplement.csproj
index 4948e6a2..0947c08d 100644
--- a/Source/Monitorian.Supplement/Monitorian.Supplement.csproj
+++ b/Source/Monitorian.Supplement/Monitorian.Supplement.csproj
@@ -41,7 +41,6 @@
-
False
$(MSBuildProgramFiles32)\Reference Assemblies\Microsoft\Framework\.NETCore\v4.5\System.Runtime.WindowsRuntime.dll
diff --git a/Source/Monitorian.Supplement/Properties/AssemblyInfo.cs b/Source/Monitorian.Supplement/Properties/AssemblyInfo.cs
index 38bc5592..2da2a264 100644
--- a/Source/Monitorian.Supplement/Properties/AssemblyInfo.cs
+++ b/Source/Monitorian.Supplement/Properties/AssemblyInfo.cs
@@ -33,6 +33,6 @@
// 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.13.0.0")]
-[assembly: AssemblyFileVersion("2.13.0.0")]
+[assembly: AssemblyVersion("2.14.0.0")]
+[assembly: AssemblyFileVersion("2.14.0.0")]
[assembly: NeutralResourcesLanguage("en-US")]
diff --git a/Source/Monitorian.Supplement/UIInformation.cs b/Source/Monitorian.Supplement/UIInformation.cs
index ecda68d9..0c856b52 100644
--- a/Source/Monitorian.Supplement/UIInformation.cs
+++ b/Source/Monitorian.Supplement/UIInformation.cs
@@ -49,8 +49,7 @@ public static bool IsLightThemeUsed()
using var key = Registry.CurrentUser.OpenSubKey(keyName);
- return (key?.GetValue(valueName) is int value)
- && (value == 1);
+ return (key?.GetValue(valueName) is 1);
}
private static readonly object _lock = new object();
diff --git a/Source/Monitorian/Models/Settings.cs b/Source/Monitorian/Models/Settings.cs
index 8c5a0ef6..5933a0f9 100644
--- a/Source/Monitorian/Models/Settings.cs
+++ b/Source/Monitorian/Models/Settings.cs
@@ -15,9 +15,9 @@ public class Settings : SettingsCore
public Settings() : base(null)
{ }
- protected override void Initiate()
+ protected override Task InitiateAsync()
{
- base.Initiate();
+ return base.InitiateAsync();
}
}
}
\ No newline at end of file
diff --git a/Source/Monitorian/Properties/AssemblyInfo.cs b/Source/Monitorian/Properties/AssemblyInfo.cs
index ee2c9412..c3050779 100644
--- a/Source/Monitorian/Properties/AssemblyInfo.cs
+++ b/Source/Monitorian/Properties/AssemblyInfo.cs
@@ -51,7 +51,7 @@
// 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.13.3.0")]
-[assembly: AssemblyFileVersion("2.13.3.0")]
+[assembly: AssemblyVersion("2.14.0.0")]
+[assembly: AssemblyFileVersion("2.14.0.0")]
[assembly: Guid("a4cc5362-9b08-465b-ad64-5cfabc72a4c7")]
[assembly: NeutralResourcesLanguage("en-US")]
diff --git a/Source/ScreenFrame/Helper/CultureInfoAddition.cs b/Source/ScreenFrame/Helper/CultureInfoAddition.cs
new file mode 100644
index 00000000..d2ffda3c
--- /dev/null
+++ b/Source/ScreenFrame/Helper/CultureInfoAddition.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ScreenFrame.Helper
+{
+ ///
+ /// Additional methods for
+ ///
+ internal static class CultureInfoAddition
+ {
+ #region Win32
+
+ [DllImport("Kernel32.dll")]
+ private static extern ushort GetUserDefaultUILanguage();
+
+ #endregion
+
+ public static CultureInfo UserDefaultUICulture => _userDefaultUICulture.Value;
+ private static readonly Lazy _userDefaultUICulture = new(() => new(GetUserDefaultUILanguage()));
+ }
+}
\ No newline at end of file
diff --git a/Source/ScreenFrame/Movers/BasicWindowMover.cs b/Source/ScreenFrame/Movers/BasicWindowMover.cs
index e7b3dba4..c48c903b 100644
--- a/Source/ScreenFrame/Movers/BasicWindowMover.cs
+++ b/Source/ScreenFrame/Movers/BasicWindowMover.cs
@@ -44,7 +44,7 @@ protected override void HandleWindowPosChanging(IntPtr hwnd, int msg, IntPtr wPa
if ((0 < width) && (0 < height) &&
TryGetAdjacentLocation(width, height, out Rect adjacentLocation) &&
- TryConfirmLocation(adjacentLocation))
+ TryConfirmLocation(ref adjacentLocation))
{
position.x = (int)adjacentLocation.X;
position.y = (int)adjacentLocation.Y;
@@ -71,9 +71,7 @@ protected override void HandleWindowPosChanged(IntPtr hwnd, int msg, IntPtr wPar
/// Handles Display change event.
///
protected override void HandleDisplayChange(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
- {
- _monitorRects = null;
- }
+ { }
///
/// Attempts to get the adjacent location using specified window width and height.
@@ -84,8 +82,6 @@ protected override void HandleDisplayChange(IntPtr hwnd, int msg, IntPtr wParam,
/// True if successfully gets
protected abstract bool TryGetAdjacentLocation(double windowWidth, double windowHeight, out Rect location);
- private static Rect[] _monitorRects; // Static field
-
///
/// Attempts to confirm that a specified location is not completely outside of monitors.
///
@@ -94,18 +90,24 @@ protected override void HandleDisplayChange(IntPtr hwnd, int msg, IntPtr wParam,
///
/// The specified location and the current location are not necessarily in the same monitor.
///
- protected virtual bool TryConfirmLocation(Rect location)
+ protected virtual bool TryConfirmLocation(ref Rect location)
{
- return (_monitorRects ??= WindowHelper.GetMonitorRects()).Any(x =>
- {
- // Rect.IntersectsWith method is not enough because it will return true when two rectangles
- // share only the outline.
- var intersection = Rect.Intersect(x, location);
+ if (!WindowHelper.TryGetMonitorRect(location, out Rect monitorRect, out _))
+ return false;
+
+ if (monitorRect.Contains(location))
+ return true;
+
+ // Rect.IntersectsWith method is not enough to check intersection because it will return
+ // true when two rectangles share only the outline.
+ // If intersection is Rect.Empty, its width and height will be double.NegativeInfinity.
+ if (Rect.Intersect(location, monitorRect).IsEmpty)
+ return false;
- // If intersection is Rect.Empty, its width and height will be double.NegativeInfinity and
- // its square will be double.PositiveInfinity.
- return (intersection.Width > 0D) && (intersection.Height > 0D);
- });
+ var left = Math.Max(monitorRect.Left, Math.Min(monitorRect.Right, location.Right) - location.Width);
+ var top = Math.Max(monitorRect.Top, Math.Min(monitorRect.Bottom, location.Bottom) - location.Height);
+ location = new Rect(left, top, location.Width, location.Height);
+ return true;
}
}
}
\ No newline at end of file
diff --git a/Source/ScreenFrame/Movers/FloatWindowMover.cs b/Source/ScreenFrame/Movers/FloatWindowMover.cs
index 89c2b95d..1b6e66d8 100644
--- a/Source/ScreenFrame/Movers/FloatWindowMover.cs
+++ b/Source/ScreenFrame/Movers/FloatWindowMover.cs
@@ -5,6 +5,8 @@
using System.Threading.Tasks;
using System.Windows;
+using ScreenFrame.Helper;
+
namespace ScreenFrame.Movers
{
///
@@ -54,31 +56,39 @@ protected bool TryGetAdjacentLocationToPivot(double windowWidth, double windowHe
return false;
}
+ var isLeftToRight = !CultureInfoAddition.UserDefaultUICulture.TextInfo.IsRightToLeft;
+
+ PivotAlignment = (taskbarAlignment, isLeftToRight) switch
+ {
+ (TaskbarAlignment.Top, true) => PivotAlignment.TopRight,
+ (TaskbarAlignment.Top, false) => PivotAlignment.TopLeft,
+ (TaskbarAlignment.Bottom, true) => PivotAlignment.BottomRight,
+ (TaskbarAlignment.Bottom, false) => PivotAlignment.BottomLeft,
+ (TaskbarAlignment.Left, { }) => PivotAlignment.BottomLeft,
+ (TaskbarAlignment.Right, { }) => PivotAlignment.BottomRight,
+ _ => default
+ };
+
var x = _pivot.X;
var y = _pivot.Y;
- switch (taskbarAlignment)
+ switch (PivotAlignment)
{
- case TaskbarAlignment.Left:
- // Place this window at the top-right of the pivot.
+ case PivotAlignment.TopLeft:
x += 1;
- y += -windowHeight - 1;
- PivotAlignment = PivotAlignment.BottomLeft;
+ y += 1;
break;
-
- case TaskbarAlignment.Top:
- // Place this window at the bottom-left of the pivot.
- x += -windowWidth - 1;
+ case PivotAlignment.TopRight:
+ x -= (windowWidth + 1);
y += 1;
- PivotAlignment = PivotAlignment.TopRight;
break;
-
- case TaskbarAlignment.Right:
- case TaskbarAlignment.Bottom:
- // Place this window at the top-left of the pivot.
- x += -windowWidth - 1;
- y += -windowHeight - 1;
- PivotAlignment = PivotAlignment.BottomRight;
+ case PivotAlignment.BottomLeft:
+ x += 1;
+ y -= (windowHeight + 1);
+ break;
+ case PivotAlignment.BottomRight:
+ x -= (windowWidth + 1);
+ y -= (windowHeight + 1);
break;
}
location = new Rect(x, y, windowWidth, windowHeight);
diff --git a/Source/ScreenFrame/Movers/StickWindowMover.cs b/Source/ScreenFrame/Movers/StickWindowMover.cs
index 858a2311..9fc17970 100644
--- a/Source/ScreenFrame/Movers/StickWindowMover.cs
+++ b/Source/ScreenFrame/Movers/StickWindowMover.cs
@@ -6,6 +6,8 @@
using System.Windows;
using System.Windows.Forms;
+using ScreenFrame.Helper;
+
namespace ScreenFrame.Movers
{
///
@@ -78,11 +80,14 @@ protected bool TryGetAdjacentLocationToTaskbar(double windowWidth, double window
if (NotifyIconHelper.TryGetNotifyIconRect(_notifyIcon, out Rect iconRect))
{
- if (taskbarRect.Contains(iconRect))
+ if (taskbarRect.Contains(
+ iconRect.X + iconRect.Width / 2D,
+ iconRect.Y + iconRect.Height / 2D))
{
iconPlacement = IconPlacement.InTaskbar;
}
- else if (WindowHelper.TryGetOverflowAreaRect(out overflowAreaRect))
+ else if (WindowHelper.TryGetOverflowAreaRect(out overflowAreaRect)
+ && overflowAreaRect.Contains(iconRect))
{
iconPlacement = IconPlacement.InOverflowArea;
}
@@ -91,6 +96,8 @@ protected bool TryGetAdjacentLocationToTaskbar(double windowWidth, double window
if (!WindowHelper.TryGetDwmWindowMargin(_window, out Thickness windowMargin))
windowMargin = new Thickness(0); // Fallback
+ var isLeftToRight = !CultureInfoAddition.UserDefaultUICulture.TextInfo.IsRightToLeft;
+
double x = 0, y = 0;
switch (taskbarAlignment)
@@ -99,21 +106,21 @@ protected bool TryGetAdjacentLocationToTaskbar(double windowWidth, double window
case TaskbarAlignment.Bottom:
x = iconPlacement switch
{
- IconPlacement.InTaskbar => iconRect.Right,
- IconPlacement.InOverflowArea => overflowAreaRect.Left,
- _ => taskbarRect.Right,// Fallback
+ IconPlacement.InTaskbar => isLeftToRight ? iconRect.Right : iconRect.Left,
+ IconPlacement.InOverflowArea => isLeftToRight ? overflowAreaRect.Left : overflowAreaRect.Right,
+ _ => isLeftToRight ? taskbarRect.Right : taskbarRect.Left, // Fallback
};
- x -= (windowWidth - windowMargin.Right);
+ x -= isLeftToRight ? (windowWidth - windowMargin.Right) : windowMargin.Left;
switch (taskbarAlignment)
{
case TaskbarAlignment.Top:
y = taskbarRect.Bottom - windowMargin.Top;
- PivotAlignment = PivotAlignment.TopRight;
+ PivotAlignment = isLeftToRight ? PivotAlignment.TopRight : PivotAlignment.TopLeft;
break;
case TaskbarAlignment.Bottom:
y = taskbarRect.Top - (windowHeight - windowMargin.Bottom);
- PivotAlignment = PivotAlignment.BottomRight;
+ PivotAlignment = isLeftToRight ? PivotAlignment.BottomRight : PivotAlignment.BottomLeft;
break;
}
break;
@@ -135,7 +142,7 @@ protected bool TryGetAdjacentLocationToTaskbar(double windowWidth, double window
{
IconPlacement.InTaskbar => iconRect.Bottom,
IconPlacement.InOverflowArea => overflowAreaRect.Top,
- _ => taskbarRect.Bottom,// Fallback
+ _ => taskbarRect.Bottom, // Fallback
};
y -= (windowHeight - windowMargin.Bottom);
break;
diff --git a/Source/ScreenFrame/Properties/AssemblyInfo.cs b/Source/ScreenFrame/Properties/AssemblyInfo.cs
index 10248444..aae732c6 100644
--- a/Source/ScreenFrame/Properties/AssemblyInfo.cs
+++ b/Source/ScreenFrame/Properties/AssemblyInfo.cs
@@ -33,6 +33,6 @@
// 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.13.3.0")]
-[assembly: AssemblyFileVersion("2.13.3.0")]
+[assembly: AssemblyVersion("2.14.0.0")]
+[assembly: AssemblyFileVersion("2.14.0.0")]
[assembly: NeutralResourcesLanguage("en-US")]
diff --git a/Source/ScreenFrame/ScreenFrame.csproj b/Source/ScreenFrame/ScreenFrame.csproj
index 49dd6101..f07b47dd 100644
--- a/Source/ScreenFrame/ScreenFrame.csproj
+++ b/Source/ScreenFrame/ScreenFrame.csproj
@@ -48,6 +48,7 @@
+
diff --git a/Source/ScreenFrame/WindowHelper.cs b/Source/ScreenFrame/WindowHelper.cs
index 645254be..cb92ec76 100644
--- a/Source/ScreenFrame/WindowHelper.cs
+++ b/Source/ScreenFrame/WindowHelper.cs
@@ -259,7 +259,7 @@ private static extern int GetClassName(
#region Monitor
- public static bool TryGetMonitorRect(Rect windowRect, out Rect monitorRect)
+ public static bool TryGetMonitorRect(Rect windowRect, out Rect monitorRect, out Rect workRect)
{
RECT rect = windowRect;
var monitorHandle = MonitorFromRect(
@@ -274,10 +274,12 @@ public static bool TryGetMonitorRect(Rect windowRect, out Rect monitorRect)
ref monitorInfo))
{
monitorRect = monitorInfo.rcMonitor;
+ workRect = monitorInfo.rcWork;
return true;
}
}
monitorRect = Rect.Empty;
+ workRect = Rect.Empty;
return false;
}
@@ -305,8 +307,8 @@ bool Proc(IntPtr monitorHandle, IntPtr hdcMonitor, IntPtr lprcMonitor, IntPtr dw
{
if (Convert.ToBoolean(monitorInfo.dwFlags & MONITORINFOF_PRIMARY))
{
- // Store the primary monitor at the beginning of the collection because in most cases,
- // the primary monitor should be checked first.
+ // Store the primary monitor at the beginning of the collection because in
+ // most cases, the primary monitor should be checked first.
monitorRects.Insert(0, monitorInfo.rcMonitor);
}
else
@@ -542,6 +544,9 @@ private static TaskbarAlignment GetTaskbarAlignment(Rect monitorRect, Rect taskb
public static bool TryGetOverflowAreaRect(out Rect overflowAreaRect)
{
+ // This combination of functions will not produce current location of overflow area
+ // until it is shown in the monitor where primary taskbar currently locates. Thus
+ // the location must be verified by other means.
var overflowAreaHandle = FindWindowEx(
IntPtr.Zero,
IntPtr.Zero,