Compare commits

...

3 Commits

Author SHA1 Message Date
3c6cc15281 Check for updates when tray menu opens
All checks were successful
Build / build (push) Successful in 9h0m7s
Build and Release / build (push) Successful in 8h0m11s
- Checks for updates in background when user clicks tray icon
- Shows clickable blue banner when update available
- Rate limited to once per hour to avoid spam
- Version 1.1.5

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-10 02:56:53 -05:00
ce3402f1a9 Add About dialog with version info
All checks were successful
Build / build (push) Successful in 9h0m7s
Build and Release / build (push) Successful in 8h0m11s
- About button added next to Exit in header
- Shows app name, version, author, and company
- Version 1.1.4

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-10 02:51:12 -05:00
0e530238f6 Ship ControlMyMonitor.exe alongside instead of embedding
All checks were successful
Build / build (push) Successful in 9h0m7s
Build and Release / build (push) Successful in 8h0m11s
- Remove embedded resource pattern (dropper behavior triggers AV)
- ControlMyMonitor.exe now copied to output directory as content
- Removes extraction to temp folder at runtime
- Should eliminate Wacatac.H!ml false positive

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-10 02:47:54 -05:00
6 changed files with 151 additions and 35 deletions

View File

@ -15,9 +15,9 @@
</PropertyGroup>
<PropertyGroup>
<Version>1.1.2</Version>
<AssemblyVersion>1.1.2.0</AssemblyVersion>
<FileVersion>1.1.2.0</FileVersion>
<Version>1.1.5</Version>
<AssemblyVersion>1.1.5.0</AssemblyVersion>
<FileVersion>1.1.5.0</FileVersion>
</PropertyGroup>
<ItemGroup>

View File

@ -187,9 +187,26 @@
<!-- Header -->
<Border Grid.Row="0" Background="#444" CornerRadius="8,8,0,0" Padding="12,10">
<Grid>
<TextBlock Text="Monitor Control" Foreground="White" FontSize="14" FontWeight="SemiBold" VerticalAlignment="Center"/>
<Button Content="Exit" HorizontalAlignment="Right" Click="ExitButton_Click"
Style="{StaticResource DarkButton}" FontSize="11"/>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<TextBlock Text="Monitor Control" Foreground="White" FontSize="14" FontWeight="SemiBold" VerticalAlignment="Center"/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<Button Content="About" Margin="0,0,6,0" Click="AboutButton_Click"
Style="{StaticResource DarkButton}" FontSize="11"/>
<Button Content="Exit" Click="ExitButton_Click"
Style="{StaticResource DarkButton}" FontSize="11"/>
</StackPanel>
</Grid>
<!-- Update Banner -->
<Border Name="updateBanner" Grid.Row="1" Background="#0078D4" CornerRadius="3"
Padding="8,4" Margin="0,8,0,0" Visibility="Collapsed" Cursor="Hand"
MouseLeftButtonUp="UpdateBanner_Click">
<TextBlock Name="updateText" Text="Update available!" Foreground="White"
FontSize="11" HorizontalAlignment="Center"/>
</Border>
</Grid>
</Border>

View File

@ -18,6 +18,8 @@ public partial class MainWindow : Window
private List<(XMonitor Monitor, List<InputSourceOption> Options)> _loadedMonitors = new();
private Storyboard? _spinnerStoryboard;
private DispatcherTimer? _showLogButtonTimer;
private UpdateInfo? _pendingUpdate;
private DateTime _lastUpdateCheck = DateTime.MinValue;
public MainWindow()
{
@ -53,12 +55,50 @@ public partial class MainWindow : Window
};
_showLogButtonTimer.Start();
// Check for updates in background (max once per hour)
if ((DateTime.Now - _lastUpdateCheck).TotalMinutes > 60)
{
_lastUpdateCheck = DateTime.Now;
_ = CheckForUpdatesAsync();
}
Dispatcher.BeginInvoke(new Action(() =>
{
Top = workArea.Bottom - ActualHeight - 10;
}), DispatcherPriority.Loaded);
}
private async Task CheckForUpdatesAsync()
{
try
{
var update = await UpdateChecker.CheckForUpdateAsync();
if (update != null)
{
_pendingUpdate = update;
await Dispatcher.InvokeAsync(() => ShowUpdateBanner(update));
}
}
catch
{
// Silently ignore update check failures
}
}
private void ShowUpdateBanner(UpdateInfo update)
{
updateBanner.Visibility = Visibility.Visible;
updateText.Text = $"v{update.LatestVersion} available - Click to update";
}
private void UpdateBanner_Click(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (_pendingUpdate != null && !string.IsNullOrEmpty(_pendingUpdate.DownloadUrl))
{
UpdateChecker.OpenDownloadPage(_pendingUpdate.DownloadUrl);
}
}
private void Window_Deactivated(object sender, EventArgs e)
{
Hide();
@ -70,6 +110,85 @@ public partial class MainWindow : Window
Application.Current.Shutdown();
}
private void AboutButton_Click(object sender, RoutedEventArgs e)
{
var version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
var versionStr = $"{version?.Major}.{version?.Minor}.{version?.Build}";
var aboutWindow = new Window
{
Title = "About Monitor Control",
Width = 300,
Height = 180,
WindowStartupLocation = WindowStartupLocation.CenterScreen,
ResizeMode = ResizeMode.NoResize,
WindowStyle = WindowStyle.None,
AllowsTransparency = true,
Background = Brushes.Transparent
};
var border = new Border
{
Background = new SolidColorBrush(Color.FromArgb(0xF0, 0x33, 0x33, 0x33)),
CornerRadius = new CornerRadius(8),
BorderBrush = new SolidColorBrush(Color.FromRgb(0x55, 0x55, 0x55)),
BorderThickness = new Thickness(1),
Padding = new Thickness(20)
};
var stack = new StackPanel { VerticalAlignment = VerticalAlignment.Center };
stack.Children.Add(new TextBlock
{
Text = "Monitor Control",
Foreground = Brushes.White,
FontSize = 18,
FontWeight = FontWeights.SemiBold,
HorizontalAlignment = HorizontalAlignment.Center,
Margin = new Thickness(0, 0, 0, 5)
});
stack.Children.Add(new TextBlock
{
Text = $"Version {versionStr}",
Foreground = Brushes.LightGray,
FontSize = 12,
HorizontalAlignment = HorizontalAlignment.Center,
Margin = new Thickness(0, 0, 0, 10)
});
stack.Children.Add(new TextBlock
{
Text = "by David H. Friedel Jr",
Foreground = Brushes.Gray,
FontSize = 11,
HorizontalAlignment = HorizontalAlignment.Center
});
stack.Children.Add(new TextBlock
{
Text = "MarketAlly",
Foreground = Brushes.Gray,
FontSize = 11,
HorizontalAlignment = HorizontalAlignment.Center,
Margin = new Thickness(0, 0, 0, 15)
});
var closeBtn = new Button
{
Content = "OK",
Width = 80,
HorizontalAlignment = HorizontalAlignment.Center,
Style = (Style)FindResource("DarkButton")
};
closeBtn.Click += (s, args) => aboutWindow.Close();
stack.Children.Add(closeBtn);
border.Child = stack;
aboutWindow.Content = border;
aboutWindow.ShowDialog();
}
private void ShowLogButton_Click(object sender, RoutedEventArgs e)
{
var logWindow = new Window

View File

@ -1,5 +1,5 @@
#define MyAppName "Monitor Control"
#define MyAppVersion "1.1.2"
#define MyAppVersion "1.1.5"
#define MyAppPublisher "MarketAlly"
#define MyAppExeName "DellMonitorControl.exe"
#define MyAppIcon "MonitorIcon.ico"

View File

@ -25,13 +25,10 @@
</PropertyGroup>
<ItemGroup>
<None Remove="Resource\ControlMyMonitor.exe" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resource\ControlMyMonitor.exe">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</EmbeddedResource>
<Content Include="Resource\ControlMyMonitor.exe">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Link>ControlMyMonitor.exe</Link>
</Content>
</ItemGroup>
<ItemGroup>

View File

@ -10,13 +10,14 @@ namespace CMM.Library.Method;
/// </summary>
public static class CMMCommand
{
static readonly string CMMTmpFolder = Path.Combine(Path.GetTempPath(), $"CMM");
static readonly string CMMexe = Path.Combine(CMMTmpFolder, "ControlMyMonitor.exe");
static readonly string CMMTmpFolder = Path.Combine(Path.GetTempPath(), "CMM");
static readonly string CMMexe = Path.Combine(AppContext.BaseDirectory, "ControlMyMonitor.exe");
static readonly string CMMsMonitors = Path.Combine(CMMTmpFolder, "smonitors.tmp");
public static async Task ScanMonitor()
{
await BytesToFileAsync(new(CMMexe));
// Ensure temp folder exists for output files
Directory.CreateDirectory(CMMTmpFolder);
await ConsoleHelper.ExecuteExeAsync(CMMexe, $"/smonitors {CMMsMonitors}");
}
@ -279,22 +280,4 @@ public static class CMMCommand
return monitors;
}
static void BytesToFile(FileInfo fi)
{
fi.Refresh();
if (fi.Exists) return;
if (!fi.Directory.Exists) fi.Directory.Create();
File.WriteAllBytes(fi.FullName, fi.Name.ResourceToByteArray());
}
static async Task BytesToFileAsync(FileInfo fi)
{
fi.Refresh();
if (fi.Exists) return;
if (!fi.Directory.Exists) fi.Directory.Create();
await File.WriteAllBytesAsync(fi.FullName, fi.Name.ResourceToByteArray());
}
}