Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0d810bbf49 | |||
| 4039e70d0c | |||
| 28c6cad153 | |||
| dda6bf1002 | |||
| 821f119547 | |||
| a33430faa0 | |||
| 401d717adf | |||
| cfac39a838 | |||
| a408b0fd27 | |||
| 4aba0049f5 | |||
| 7ad87a1398 | |||
| db9930f3b6 |
@@ -23,12 +23,51 @@ jobs:
|
||||
- name: Build Release
|
||||
run: dotnet build --configuration Release --no-restore
|
||||
|
||||
- name: Publish
|
||||
run: dotnet publish DellMonitorControl/DellMonitorControl.csproj -c Release -o ./publish --self-contained false
|
||||
|
||||
- name: Create ZIP archive
|
||||
run: Compress-Archive -Path ./publish/* -DestinationPath ./MonitorControl-${{ gitea.ref_name }}.zip
|
||||
- name: Update version in ISS
|
||||
shell: powershell
|
||||
run: |
|
||||
$version = "${{ gitea.ref_name }}".TrimStart("v")
|
||||
$issPath = "DellMonitorControl/MonitorControl.iss"
|
||||
(Get-Content $issPath) -replace '#define MyAppVersion ".*"', "#define MyAppVersion `"$version`"" | Set-Content $issPath
|
||||
|
||||
- name: List build output
|
||||
shell: cmd
|
||||
run: |
|
||||
echo "Current directory:"
|
||||
cd
|
||||
echo "DellMonitorControl contents:"
|
||||
dir DellMonitorControl
|
||||
echo "DellMonitorControl\bin contents:"
|
||||
dir DellMonitorControl\bin
|
||||
echo "DellMonitorControl\bin\Release contents:"
|
||||
dir DellMonitorControl\bin\Release
|
||||
echo "DellMonitorControl\bin\Release\net9.0-windows contents:"
|
||||
dir DellMonitorControl\bin\Release\net9.0-windows
|
||||
|
||||
- name: Build Installer
|
||||
shell: cmd
|
||||
run: |
|
||||
echo Copying to short path to avoid path length issues...
|
||||
mkdir C:\build 2>nul
|
||||
xcopy /E /I /Y DellMonitorControl C:\build\app
|
||||
echo.
|
||||
echo Working from C:\build\app
|
||||
cd /d C:\build\app
|
||||
echo Current directory:
|
||||
cd
|
||||
echo.
|
||||
set TEMP=C:\build\temp
|
||||
set TMP=C:\build\temp
|
||||
mkdir C:\build\temp 2>nul
|
||||
mkdir installer 2>nul
|
||||
echo.
|
||||
echo Running Inno Setup...
|
||||
"C:\Program Files (x86)\Inno Setup 6\ISCC.exe" MonitorControl.iss
|
||||
echo.
|
||||
echo ISCC exit code: %ERRORLEVEL%
|
||||
echo.
|
||||
echo Installer directory contents:
|
||||
dir installer
|
||||
|
||||
- name: Create Release
|
||||
shell: powershell
|
||||
@@ -36,6 +75,7 @@ jobs:
|
||||
GITEA_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
run: |
|
||||
$tag = "${{ gitea.ref_name }}"
|
||||
$version = $tag.TrimStart("v")
|
||||
$repo = "misc/ControlMyMonitorManagement"
|
||||
$baseUrl = "https://git.marketally.com/api/v1"
|
||||
|
||||
@@ -51,7 +91,7 @@ jobs:
|
||||
$body = @{
|
||||
tag_name = $tag
|
||||
name = "Monitor Control $tag"
|
||||
body = "## Monitor Control $tag`n`n### Installation`n1. Download the ZIP file`n2. Extract to a folder`n3. Run DellMonitorControl.exe`n`n### Requirements`n- Windows 10/11`n- .NET 9.0 Runtime"
|
||||
body = "## Monitor Control $tag`n`n### Installation`nDownload and run the installer.`n`n### Features`n- System tray monitor control`n- Brightness/Contrast adjustment`n- Input source switching`n- Quick-switch toolbar`n`n### Requirements`n- Windows 10/11`n- .NET 9.0 Runtime"
|
||||
} | ConvertTo-Json
|
||||
|
||||
$release = Invoke-RestMethod -Uri "$baseUrl/repos/$repo/releases" -Method Post -Headers @{"Authorization"="token $env:GITEA_TOKEN"; "Content-Type"="application/json"} -Body $body
|
||||
@@ -59,11 +99,11 @@ jobs:
|
||||
Write-Host "Release created with ID: $releaseId"
|
||||
}
|
||||
|
||||
# Upload asset using curl (more reliable for file uploads)
|
||||
$filePath = "./MonitorControl-$tag.zip"
|
||||
$fileName = "MonitorControl-$tag.zip"
|
||||
# Upload installer
|
||||
$filePath = "C:\build\app\installer\MonitorControl-Setup-$version.exe"
|
||||
$fileName = "MonitorControl-Setup-$version.exe"
|
||||
Write-Host "Uploading $fileName..."
|
||||
|
||||
curl.exe -X POST -H "Authorization: token $env:GITEA_TOKEN" -F "attachment=@$filePath" "$baseUrl/repos/$repo/releases/$releaseId/assets?name=$fileName"
|
||||
|
||||
Write-Host "Asset uploaded successfully"
|
||||
Write-Host "Installer uploaded successfully"
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -365,5 +365,5 @@ FodyWeavers.xsd
|
||||
# Claude Code
|
||||
.claude/
|
||||
|
||||
# Windows special files
|
||||
nul
|
||||
# Inno Setup output
|
||||
DellMonitorControl/installer/
|
||||
|
||||
77
DellMonitorControl/DebugLogger.cs
Normal file
77
DellMonitorControl/DebugLogger.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace DellMonitorControl;
|
||||
|
||||
public static class DebugLogger
|
||||
{
|
||||
private static readonly List<string> _logs = new();
|
||||
private static readonly object _lock = new();
|
||||
private static readonly string _logFilePath;
|
||||
|
||||
static DebugLogger()
|
||||
{
|
||||
var tempPath = Path.Combine(Path.GetTempPath(), "CMM");
|
||||
Directory.CreateDirectory(tempPath);
|
||||
_logFilePath = Path.Combine(tempPath, "debug.log");
|
||||
}
|
||||
|
||||
public static void Log(string message)
|
||||
{
|
||||
var entry = $"[{DateTime.Now:HH:mm:ss.fff}] {message}";
|
||||
lock (_lock)
|
||||
{
|
||||
_logs.Add(entry);
|
||||
try
|
||||
{
|
||||
File.AppendAllText(_logFilePath, entry + Environment.NewLine);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
public static void LogError(string message, Exception? ex = null)
|
||||
{
|
||||
var entry = ex != null
|
||||
? $"[{DateTime.Now:HH:mm:ss.fff}] ERROR: {message} - {ex.GetType().Name}: {ex.Message}"
|
||||
: $"[{DateTime.Now:HH:mm:ss.fff}] ERROR: {message}";
|
||||
lock (_lock)
|
||||
{
|
||||
_logs.Add(entry);
|
||||
if (ex != null)
|
||||
_logs.Add($" Stack: {ex.StackTrace}");
|
||||
try
|
||||
{
|
||||
File.AppendAllText(_logFilePath, entry + Environment.NewLine);
|
||||
if (ex != null)
|
||||
File.AppendAllText(_logFilePath, $" Stack: {ex.StackTrace}" + Environment.NewLine);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetLogs()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return string.Join(Environment.NewLine, _logs);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Clear()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_logs.Clear();
|
||||
try
|
||||
{
|
||||
File.WriteAllText(_logFilePath, "");
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
public static string LogFilePath => _logFilePath;
|
||||
}
|
||||
@@ -215,6 +215,9 @@
|
||||
</Ellipse>
|
||||
<TextBlock Name="loadingText" Text="Loading..." Foreground="LightGray" FontSize="12"
|
||||
HorizontalAlignment="Center"/>
|
||||
<Button Name="showLogButton" Content="Show Log" Margin="0,10,0,0"
|
||||
Style="{StaticResource DarkButton}" FontSize="10"
|
||||
Click="ShowLogButton_Click" Visibility="Collapsed"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace DellMonitorControl;
|
||||
|
||||
@@ -16,15 +17,18 @@ public partial class MainWindow : Window
|
||||
{
|
||||
private List<(XMonitor Monitor, List<InputSourceOption> Options)> _loadedMonitors = new();
|
||||
private Storyboard? _spinnerStoryboard;
|
||||
private DispatcherTimer? _showLogButtonTimer;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
_spinnerStoryboard = (Storyboard)FindResource("SpinnerAnimation");
|
||||
DebugLogger.Log("MainWindow initialized");
|
||||
}
|
||||
|
||||
public void ShowNearTray()
|
||||
{
|
||||
DebugLogger.Log("ShowNearTray called");
|
||||
var workArea = SystemParameters.WorkArea;
|
||||
Left = workArea.Right - Width - 10;
|
||||
Top = workArea.Bottom - 350;
|
||||
@@ -34,10 +38,25 @@ public partial class MainWindow : Window
|
||||
// Start spinner
|
||||
_spinnerStoryboard?.Begin(this, true);
|
||||
|
||||
// Show log button after 3 seconds if still loading
|
||||
showLogButton.Visibility = Visibility.Collapsed;
|
||||
_showLogButtonTimer?.Stop();
|
||||
_showLogButtonTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(3) };
|
||||
_showLogButtonTimer.Tick += (s, e) =>
|
||||
{
|
||||
_showLogButtonTimer.Stop();
|
||||
if (loadingPanel.Visibility == Visibility.Visible || sp.Children.Contains(loadingPanel))
|
||||
{
|
||||
showLogButton.Visibility = Visibility.Visible;
|
||||
DebugLogger.Log("Show Log button displayed (loading took > 3s)");
|
||||
}
|
||||
};
|
||||
_showLogButtonTimer.Start();
|
||||
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
Top = workArea.Bottom - ActualHeight - 10;
|
||||
}), System.Windows.Threading.DispatcherPriority.Loaded);
|
||||
}), DispatcherPriority.Loaded);
|
||||
}
|
||||
|
||||
private void Window_Deactivated(object sender, EventArgs e)
|
||||
@@ -51,18 +70,86 @@ public partial class MainWindow : Window
|
||||
Application.Current.Shutdown();
|
||||
}
|
||||
|
||||
private void ShowLogButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var logWindow = new Window
|
||||
{
|
||||
Title = "Debug Log",
|
||||
Width = 600,
|
||||
Height = 400,
|
||||
WindowStartupLocation = WindowStartupLocation.CenterScreen,
|
||||
Background = new SolidColorBrush(Color.FromRgb(45, 45, 45))
|
||||
};
|
||||
|
||||
var grid = new Grid();
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
|
||||
|
||||
var textBox = new TextBox
|
||||
{
|
||||
Text = DebugLogger.GetLogs(),
|
||||
IsReadOnly = true,
|
||||
Background = new SolidColorBrush(Color.FromRgb(30, 30, 30)),
|
||||
Foreground = Brushes.LightGray,
|
||||
FontFamily = new FontFamily("Consolas"),
|
||||
FontSize = 11,
|
||||
VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
|
||||
HorizontalScrollBarVisibility = ScrollBarVisibility.Auto,
|
||||
TextWrapping = TextWrapping.NoWrap,
|
||||
Margin = new Thickness(10)
|
||||
};
|
||||
Grid.SetRow(textBox, 0);
|
||||
grid.Children.Add(textBox);
|
||||
|
||||
var buttonPanel = new StackPanel { Orientation = Orientation.Horizontal, HorizontalAlignment = HorizontalAlignment.Right, Margin = new Thickness(10) };
|
||||
|
||||
var copyBtn = new Button { Content = "Copy to Clipboard", Padding = new Thickness(10, 5, 10, 5), Margin = new Thickness(0, 0, 10, 0) };
|
||||
copyBtn.Click += (s, args) =>
|
||||
{
|
||||
Clipboard.SetText(DebugLogger.GetLogs());
|
||||
MessageBox.Show("Log copied to clipboard!", "Debug Log", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
};
|
||||
buttonPanel.Children.Add(copyBtn);
|
||||
|
||||
var openFileBtn = new Button { Content = "Open Log File", Padding = new Thickness(10, 5, 10, 5), Margin = new Thickness(0, 0, 10, 0) };
|
||||
openFileBtn.Click += (s, args) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
System.Diagnostics.Process.Start("explorer.exe", $"/select,\"{DebugLogger.LogFilePath}\"");
|
||||
}
|
||||
catch { }
|
||||
};
|
||||
buttonPanel.Children.Add(openFileBtn);
|
||||
|
||||
var closeBtn = new Button { Content = "Close", Padding = new Thickness(10, 5, 10, 5) };
|
||||
closeBtn.Click += (s, args) => logWindow.Close();
|
||||
buttonPanel.Children.Add(closeBtn);
|
||||
|
||||
Grid.SetRow(buttonPanel, 1);
|
||||
grid.Children.Add(buttonPanel);
|
||||
|
||||
logWindow.Content = grid;
|
||||
logWindow.Show();
|
||||
}
|
||||
|
||||
public async Task LoadMonitors()
|
||||
{
|
||||
DebugLogger.Log("LoadMonitors started");
|
||||
var newChildren = new List<UIElement>();
|
||||
_loadedMonitors.Clear();
|
||||
|
||||
try
|
||||
{
|
||||
DebugLogger.Log("Scanning for monitors...");
|
||||
await CMMCommand.ScanMonitor();
|
||||
DebugLogger.Log("Scan complete, reading monitor data...");
|
||||
var monitors = (await CMMCommand.ReadMonitorsData()).ToList();
|
||||
DebugLogger.Log($"Found {monitors.Count} monitor(s)");
|
||||
|
||||
if (!monitors.Any())
|
||||
{
|
||||
DebugLogger.Log("No DDC/CI monitors detected");
|
||||
newChildren.Add(new TextBlock
|
||||
{
|
||||
Text = "No DDC/CI monitors detected",
|
||||
@@ -76,16 +163,33 @@ public partial class MainWindow : Window
|
||||
{
|
||||
foreach (var m in monitors)
|
||||
{
|
||||
DebugLogger.Log($"Processing monitor: {m.MonitorName} (SN: {m.SerialNumber})");
|
||||
|
||||
DebugLogger.Log($" Getting brightness...");
|
||||
var brightness = await CMMCommand.GetBrightness(m.SerialNumber) ?? 50;
|
||||
DebugLogger.Log($" Brightness: {brightness}");
|
||||
|
||||
DebugLogger.Log($" Getting contrast...");
|
||||
var contrast = await CMMCommand.GetContrast(m.SerialNumber) ?? 50;
|
||||
DebugLogger.Log($" Contrast: {contrast}");
|
||||
|
||||
DebugLogger.Log($" Getting input source...");
|
||||
var inputSource = await CMMCommand.GetInputSource(m.SerialNumber);
|
||||
DebugLogger.Log($" Input source: {inputSource}");
|
||||
|
||||
DebugLogger.Log($" Getting input options...");
|
||||
var inputOptions = await CMMCommand.GetInputSourceOptions(m.SerialNumber);
|
||||
DebugLogger.Log($" Input options count: {inputOptions.Count}");
|
||||
|
||||
DebugLogger.Log($" Getting power status...");
|
||||
var powerStatus = await CMMCommand.GetMonPowerStatus(m.SerialNumber) ?? "Unknown";
|
||||
DebugLogger.Log($" Power status: {powerStatus}");
|
||||
|
||||
_loadedMonitors.Add((m, inputOptions));
|
||||
|
||||
// Apply config to filter hidden ports and use custom labels
|
||||
var filteredOptions = MonitorConfigManager.ApplyConfigToOptions(m.SerialNumber, inputOptions);
|
||||
DebugLogger.Log($" Filtered options count: {filteredOptions.Count}");
|
||||
|
||||
// Monitor name header with Config button
|
||||
newChildren.Add(CreateMonitorHeader(m, inputOptions));
|
||||
@@ -100,6 +204,8 @@ public partial class MainWindow : Window
|
||||
|
||||
// Separator
|
||||
newChildren.Add(new Border { Height = 1, Background = new SolidColorBrush(Color.FromRgb(80, 80, 80)), Margin = new Thickness(0, 10, 0, 2) });
|
||||
|
||||
DebugLogger.Log($" Monitor {m.MonitorName} processing complete");
|
||||
}
|
||||
|
||||
// Remove last separator
|
||||
@@ -108,27 +214,33 @@ public partial class MainWindow : Window
|
||||
}
|
||||
|
||||
// Load quick-switch toolbar
|
||||
DebugLogger.Log("Loading quick-switch toolbar...");
|
||||
LoadQuickSwitchToolbar();
|
||||
DebugLogger.Log("Quick-switch toolbar loaded");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DebugLogger.LogError("LoadMonitors failed", ex);
|
||||
newChildren.Clear();
|
||||
newChildren.Add(new TextBlock { Text = $"Error: {ex.Message}", Foreground = Brushes.OrangeRed, FontSize = 11, TextWrapping = TextWrapping.Wrap });
|
||||
}
|
||||
|
||||
// Stop spinner
|
||||
// Stop spinner and timer
|
||||
_spinnerStoryboard?.Stop(this);
|
||||
_showLogButtonTimer?.Stop();
|
||||
|
||||
sp.VerticalAlignment = VerticalAlignment.Top;
|
||||
sp.Children.Clear();
|
||||
foreach (var child in newChildren)
|
||||
sp.Children.Add(child);
|
||||
|
||||
DebugLogger.Log("LoadMonitors complete, UI updated");
|
||||
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
var workArea = SystemParameters.WorkArea;
|
||||
Top = workArea.Bottom - ActualHeight - 10;
|
||||
}), System.Windows.Threading.DispatcherPriority.Loaded);
|
||||
}), DispatcherPriority.Loaded);
|
||||
}
|
||||
|
||||
private void LoadQuickSwitchToolbar()
|
||||
|
||||
41
DellMonitorControl/MonitorControl.iss
Normal file
41
DellMonitorControl/MonitorControl.iss
Normal file
@@ -0,0 +1,41 @@
|
||||
#define MyAppName "Monitor Control"
|
||||
#define MyAppVersion "1.0.0"
|
||||
#define MyAppPublisher "Dang"
|
||||
#define MyAppExeName "DellMonitorControl.exe"
|
||||
#define SourcePath "bin\Release\net9.0-windows"
|
||||
|
||||
[Setup]
|
||||
SourceDir=.
|
||||
AppId={{8F3E4A2B-1C5D-4E6F-9A8B-7C2D1E3F4A5B}
|
||||
AppName={#MyAppName}
|
||||
AppVersion={#MyAppVersion}
|
||||
AppPublisher={#MyAppPublisher}
|
||||
DefaultDirName={autopf}\{#MyAppName}
|
||||
DefaultGroupName={#MyAppName}
|
||||
AllowNoIcons=yes
|
||||
OutputDir=installer
|
||||
OutputBaseFilename=MonitorControl-Setup-{#MyAppVersion}
|
||||
Compression=lzma
|
||||
SolidCompression=yes
|
||||
WizardStyle=modern
|
||||
PrivilegesRequired=admin
|
||||
UninstallDisplayIcon={app}\{#MyAppExeName}
|
||||
|
||||
[Languages]
|
||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||
|
||||
[Tasks]
|
||||
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
||||
Name: "startupicon"; Description: "Start with Windows"; GroupDescription: "Startup:"
|
||||
|
||||
[Files]
|
||||
Source: "bin\Release\net9.0-windows\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
|
||||
[Icons]
|
||||
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
|
||||
Name: "{group}\Uninstall {#MyAppName}"; Filename: "{uninstallexe}"
|
||||
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
|
||||
Name: "{userstartup}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: startupicon
|
||||
|
||||
[Run]
|
||||
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
|
||||
Reference in New Issue
Block a user