diff --git a/FluentAutoClicker/App.xaml b/FluentAutoClicker/App.xaml
index 9cb920c..b1603bd 100644
--- a/FluentAutoClicker/App.xaml
+++ b/FluentAutoClicker/App.xaml
@@ -1,9 +1,9 @@
+
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
@@ -14,4 +14,4 @@
-
+
\ No newline at end of file
diff --git a/FluentAutoClicker/App.xaml.cs b/FluentAutoClicker/App.xaml.cs
index 8933114..fcb96d3 100644
--- a/FluentAutoClicker/App.xaml.cs
+++ b/FluentAutoClicker/App.xaml.cs
@@ -25,8 +25,11 @@ namespace FluentAutoClicker;
///
/// Provides application-specific behavior to supplement the default Application class.
///
-public partial class App : Application
+public partial class App
{
+ ///
+ /// The main window of the application.
+ ///
public static readonly MainWindow MainWindow = new();
///
@@ -42,7 +45,7 @@ public App()
/// Invoked when the application is launched.
///
/// Details about the launch request and process.
- protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
+ protected override void OnLaunched(LaunchActivatedEventArgs args)
{
MainWindow.Activate();
}
diff --git a/FluentAutoClicker/Controls/IsEnabledTextBlock/IsEnabledTextBlock.cs b/FluentAutoClicker/Controls/IsEnabledTextBlock/IsEnabledTextBlock.cs
new file mode 100644
index 0000000..a7a56cc
--- /dev/null
+++ b/FluentAutoClicker/Controls/IsEnabledTextBlock/IsEnabledTextBlock.cs
@@ -0,0 +1,89 @@
+// Copyright (C) 2025 Ryan Luu
+//
+// This file is part of Fluent Auto Clicker.
+//
+// Fluent Auto Clicker is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Fluent Auto Clicker is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with Fluent Auto Clicker. If not, see .
+
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using System.ComponentModel;
+
+// To learn more about WinUI, the WinUI project structure,
+// and more about our project templates, see: http://aka.ms/winui-project-info.
+
+namespace FluentAutoClicker.Controls;
+
+///
+/// A custom control that displays text and changes its visual state based on its enabled state.
+///
+[TemplateVisualState(Name = "Normal", GroupName = "CommonStates")]
+[TemplateVisualState(Name = "Disabled", GroupName = "CommonStates")]
+public partial class IsEnabledTextBlock : Control
+{
+ ///
+ /// Identifies the dependency property.
+ ///
+ public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
+ "Text",
+ typeof(string),
+ typeof(IsEnabledTextBlock),
+ null);
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public IsEnabledTextBlock()
+ {
+ DefaultStyleKey = typeof(IsEnabledTextBlock);
+ }
+
+ ///
+ /// Gets or sets the text content of the control.
+ ///
+ [Localizable(true)]
+ public string Text
+ {
+ get => (string)GetValue(TextProperty);
+ set => SetValue(TextProperty, value);
+ }
+
+ ///
+ /// Applies the control template and sets up the initial visual state.
+ ///
+ protected override void OnApplyTemplate()
+ {
+ IsEnabledChanged -= IsEnabledTextBlock_IsEnabledChanged;
+ SetEnabledState();
+ IsEnabledChanged += IsEnabledTextBlock_IsEnabledChanged;
+ base.OnApplyTemplate();
+ }
+
+ ///
+ /// Handles the IsEnabledChanged event and updates the visual state.
+ ///
+ /// The source of the event.
+ /// The event data.
+ private void IsEnabledTextBlock_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
+ {
+ SetEnabledState();
+ }
+
+ ///
+ /// Sets the visual state of the control based on its enabled state.
+ ///
+ private void SetEnabledState()
+ {
+ _ = VisualStateManager.GoToState(this, IsEnabled ? "Normal" : "Disabled", true);
+ }
+}
\ No newline at end of file
diff --git a/FluentAutoClicker/Controls/IsEnabledTextBlock/IsEnabledTextBlock.xaml b/FluentAutoClicker/Controls/IsEnabledTextBlock/IsEnabledTextBlock.xaml
new file mode 100644
index 0000000..ec41958
--- /dev/null
+++ b/FluentAutoClicker/Controls/IsEnabledTextBlock/IsEnabledTextBlock.xaml
@@ -0,0 +1,35 @@
+
+
+
+
\ No newline at end of file
diff --git a/FluentAutoClicker/FluentAutoClicker.csproj b/FluentAutoClicker/FluentAutoClicker.csproj
index 710ab2e..643866b 100644
--- a/FluentAutoClicker/FluentAutoClicker.csproj
+++ b/FluentAutoClicker/FluentAutoClicker.csproj
@@ -8,6 +8,7 @@
app.manifest
x86;x64;ARM64
win-x86;win-x64;win-arm64
+ 8305
win-$(Platform).pubxml
enable
enable
@@ -38,13 +39,14 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
+
-
-
+
+
-
- False
-
-
False
diff --git a/FluentAutoClicker/Helpers/AutoClicker.cs b/FluentAutoClicker/Helpers/AutoClicker.cs
index c296b4c..2bd0dfe 100644
--- a/FluentAutoClicker/Helpers/AutoClicker.cs
+++ b/FluentAutoClicker/Helpers/AutoClicker.cs
@@ -35,7 +35,7 @@ public static class AutoClicker
/// The number of milliseconds to wait before clicks.
/// The number of clicks before stopping the auto clicker thread.
/// The mouse button used to click.
- /// The amount of time in milliseconds to add randomly to the millisecond delay between clicks.
+ /// Milliseconds to add randomly to delay between clicks.
public static void Start(int millisecondsDelay = 100, int clickAmount = 0,
int mouseButtonType = 0, int clickDelayOffset = 0)
{
@@ -82,23 +82,26 @@ private static async void AutoClickerThread(int clickInterval, int repeatAmount,
///
/// Clicks the mouse button.
///
- /// The mouse button to click.
- private static void ClickMouse(int button)
+ /// The mouse button to click.
+ private static void ClickMouse(int mouseButton)
{
- if (button == 0) // Left mouse button
+ switch (mouseButton)
{
- SendMouseInput(MOUSE_EVENT_FLAGS.MOUSEEVENTF_LEFTDOWN);
- SendMouseInput(MOUSE_EVENT_FLAGS.MOUSEEVENTF_LEFTUP);
- }
- else if (button == 1) // Middle mouse button
- {
- SendMouseInput(MOUSE_EVENT_FLAGS.MOUSEEVENTF_MIDDLEDOWN);
- SendMouseInput(MOUSE_EVENT_FLAGS.MOUSEEVENTF_MIDDLEUP);
- }
- else if (button == 2) // Right mouse button
- {
- SendMouseInput(MOUSE_EVENT_FLAGS.MOUSEEVENTF_RIGHTDOWN);
- SendMouseInput(MOUSE_EVENT_FLAGS.MOUSEEVENTF_RIGHTUP);
+ // Left mouse button
+ case 0:
+ SendMouseInput(MOUSE_EVENT_FLAGS.MOUSEEVENTF_LEFTDOWN);
+ SendMouseInput(MOUSE_EVENT_FLAGS.MOUSEEVENTF_LEFTUP);
+ break;
+ // Middle mouse button
+ case 1:
+ SendMouseInput(MOUSE_EVENT_FLAGS.MOUSEEVENTF_MIDDLEDOWN);
+ SendMouseInput(MOUSE_EVENT_FLAGS.MOUSEEVENTF_MIDDLEUP);
+ break;
+ // Right mouse button
+ case 2:
+ SendMouseInput(MOUSE_EVENT_FLAGS.MOUSEEVENTF_RIGHTDOWN);
+ SendMouseInput(MOUSE_EVENT_FLAGS.MOUSEEVENTF_RIGHTUP);
+ break;
}
}
@@ -119,4 +122,4 @@ private static void SendMouseInput(MOUSE_EVENT_FLAGS dwFlags)
_ = PInvoke.SendInput(inputs, Marshal.SizeOf());
}
-}
+}
\ No newline at end of file
diff --git a/FluentAutoClicker/MainPage.xaml b/FluentAutoClicker/MainPage.xaml
index 4cb3a57..d5fa29b 100644
--- a/FluentAutoClicker/MainPage.xaml
+++ b/FluentAutoClicker/MainPage.xaml
@@ -1,13 +1,25 @@
+
-
+
+ 4
+ 8
+ 8
+ 8
+
+
+
@@ -20,9 +32,9 @@
-
-
-
+
+
+
@@ -82,6 +94,7 @@
x:Name="MouseButtonTypeComboBox"
x:Uid="ComboBoxMouseButton"
Grid.Row="1"
+ Grid.Column="0"
SelectedIndex="0">
@@ -89,12 +102,12 @@
-
-
-
+
+
+
@@ -135,15 +148,40 @@
Value="10" />
-
-
+ ColumnSpacing="{StaticResource ButtonColumnSpacing}">
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/FluentAutoClicker/MainPage.xaml.cs b/FluentAutoClicker/MainPage.xaml.cs
index e2b290a..ef4e958 100644
--- a/FluentAutoClicker/MainPage.xaml.cs
+++ b/FluentAutoClicker/MainPage.xaml.cs
@@ -18,98 +18,151 @@
using FluentAutoClicker.Helpers;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
-using Microsoft.UI.Xaml.Media;
+using Microsoft.Windows.BadgeNotifications;
using System.Globalization;
-using System.Runtime.InteropServices;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.UI.Input.KeyboardAndMouse;
-using Windows.Win32.UI.WindowsAndMessaging;
+using WinRT.Interop;
+using WinUIEx.Messaging;
namespace FluentAutoClicker;
///
/// The main page containing all controls displayed on the main window.
///
-public sealed partial class MainPage : Page
+public sealed partial class MainPage
{
+ ///
+ /// The settings page instance.
+ ///
+ private static readonly SettingsPage settingsPage = new();
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
public MainPage()
{
InitializeComponent();
Loaded += MainPage_Loaded;
+
+ // Set tooltip
+ ToolTipService.SetToolTip(ToggleButtonStart, "ToggleButtonStartTooltipStart".GetLocalized());
}
- private WNDPROC? origHotKeyProc;
- private WNDPROC? hotKeyProcD;
+ ///
+ /// Gets or sets a value indicating whether the hotkey is registered.
+ ///
+ private bool IsHotKeyRegistered { get; set; }
- private LRESULT HotKeyProc(HWND hWnd, uint Msg, WPARAM wParam, LPARAM lParam)
+ ///
+ /// Handles the Loaded event of the MainPage control.
+ ///
+ /// The source of the event.
+ /// The event data.
+ private async void MainPage_Loaded(object sender, RoutedEventArgs e)
{
- uint WM_HOTKEY = 0x0312; // HotKey Window Message
+ // Set badge notification
+ SetNotificationBadge(BadgeNotificationGlyph.Paused);
- if (Msg == WM_HOTKEY)
+ // Prevent registering multiple hotkeys
+ if (IsHotKeyRegistered)
{
- if (StartToggleButton.IsEnabled)
- {
- StartToggleButton.IsChecked = !StartToggleButton.IsChecked;
- }
+ return;
}
- return PInvoke.CallWindowProc(origHotKeyProc, hWnd, Msg, wParam, lParam);
- }
+ // Get window handle
+ MainWindow window = App.MainWindow;
+ HWND hWnd = new(WindowNative.GetWindowHandle(window));
- [LibraryImport("user32.dll", EntryPoint = "SetWindowLongW", SetLastError = true)]
- private static partial int SetWindowLong_x86(nint hWnd, int nIndex, int dwNewLong);
+ // Set up window message monitor
+ WindowMessageMonitor monitor = new(window);
+ monitor.WindowMessageReceived += OnWindowMessageReceived;
- [LibraryImport("user32.dll", EntryPoint = "SetWindowLongPtrW", SetLastError = true)]
- private static partial nint SetWindowLongPtr_x64(nint hWnd, int nIndex, nint dwNewLong);
+ // Register hotkey
+ bool success = PInvoke.RegisterHotKey(hWnd, 0x0000, HOT_KEY_MODIFIERS.MOD_NOREPEAT, 0x75); // F6
+ if (success)
+ {
+ IsHotKeyRegistered = true;
+ }
+ else
+ {
+ ContentDialog dialog = new()
+ {
+ // XamlRoot must be set in the case of a ContentDialog running in a Desktop app
+ XamlRoot = XamlRoot,
+ Style = Application.Current.Resources["DefaultContentDialogStyle"] as Style,
+ Title = "Failed to register hotkey",
+ Content = "Please modify the hotkey setting.",
+ CloseButtonText = "OK",
+ DefaultButton = ContentDialogButton.Primary
+ };
- // CsWin32 doesn't allow specifying the calling convention between x86 and x64
- private static nint SetWindowLongPtr(HWND hWnd, int nIndex, nint dwNewLong)
- {
- nint hWndInt = hWnd.Value;
- return IntPtr.Size == 4
- ? SetWindowLong_x86(hWndInt, nIndex, (int)dwNewLong)
- : SetWindowLongPtr_x64(hWndInt, nIndex, dwNewLong);
+ _ = await dialog.ShowAsync();
+ }
}
- private void MainPage_Loaded(object sender, RoutedEventArgs e)
+ ///
+ /// Handles the WindowMessageReceived event of the WindowMessageMonitor control.
+ ///
+ /// The source of the event.
+ /// The event data.
+ private void OnWindowMessageReceived(object? sender, WindowMessageEventArgs e)
{
- // Get window handle
- MainWindow window = App.MainWindow;
- HWND hWnd = new(WinRT.Interop.WindowNative.GetWindowHandle(window));
+ if (e.Message.MessageId == 0x0312) // WM_HOTKEY event
+ {
+ if (ToggleButtonStart.IsEnabled)
+ {
+ ToggleButtonStart.IsChecked = !ToggleButtonStart.IsChecked;
+ }
+ }
+ }
- // Register hotkey
- int id = 0x0000;
- _ = PInvoke.RegisterHotKey(hWnd, id, HOT_KEY_MODIFIERS.MOD_NOREPEAT, 0x75); // F6
-
- // Add hotkey function pointer to window procedure
- int GWL_WNDPROC = -4;
- hotKeyProcD = HotKeyProc;
- IntPtr hotKeyProcPtr = Marshal.GetFunctionPointerForDelegate(hotKeyProcD);
- IntPtr wndPtr = SetWindowLongPtr(hWnd, GWL_WNDPROC, hotKeyProcPtr);
- origHotKeyProc = Marshal.GetDelegateForFunctionPointer(wndPtr);
+ ///
+ /// Sets the notification badge.
+ ///
+ /// The badge notification glyph.
+ private static void SetNotificationBadge(BadgeNotificationGlyph glyph)
+ {
+ if (glyph == BadgeNotificationGlyph.Paused && settingsPage.NotificationBadgePaused)
+ {
+ BadgeNotificationManager.Current.SetBadgeAsGlyph(glyph);
+ }
+ else if (glyph == BadgeNotificationGlyph.Playing && settingsPage.NotificationBadgePlaying)
+ {
+ BadgeNotificationManager.Current.SetBadgeAsGlyph(glyph);
+ }
+ else
+ {
+ BadgeNotificationManager.Current.ClearBadge();
+ }
}
+ ///
+ /// Sets the enabled state of the controls.
+ ///
+ /// if set to true the controls are enabled; otherwise, they are disabled.
private void SetControlsEnabled(bool isEnabled)
{
+ ClickIntervalTextBlock.IsEnabled = isEnabled;
NumberBoxHours.IsEnabled = isEnabled;
NumberBoxMinutes.IsEnabled = isEnabled;
NumberBoxSeconds.IsEnabled = isEnabled;
NumberBoxMilliseconds.IsEnabled = isEnabled;
MouseButtonTypeComboBox.IsEnabled = isEnabled;
+ HotkeyTextBlock.IsEnabled = isEnabled;
ClickRepeatCheckBox.IsEnabled = isEnabled;
ClickOffsetCheckBox.IsEnabled = isEnabled;
-
- ClickOffsetAmount.IsEnabled = ClickOffsetCheckBox.IsChecked == true && isEnabled;
- ClickRepeatAmount.IsEnabled = ClickRepeatCheckBox.IsChecked == true && isEnabled;
-
- // TODO: Change this to use a custom control. See https://github.com/RyanLua/FluentAutoClicker/issues/42
- string brushKey =
- isEnabled ? "SystemControlForegroundBaseHighBrush" : "SystemControlForegroundBaseMediumLowBrush";
- ClickIntervalTextBlock.Foreground = Application.Current.Resources[brushKey] as Brush;
- HotkeyTextBlock.Foreground = Application.Current.Resources[brushKey] as Brush;
+ ClickOffsetAmount.IsEnabled = isEnabled && ClickOffsetCheckBox.IsChecked == true;
+ ClickRepeatAmount.IsEnabled = isEnabled && ClickRepeatCheckBox.IsChecked == true;
}
+ ///
+ /// Gets the value of the specified NumberBox.
+ ///
+ /// The NumberBox control.
+ /// The default value.
+ /// The value of the NumberBox.
private static int GetNumberBoxValue(NumberBox numberBox, int defaultValue)
{
if (!int.TryParse(numberBox.Value.ToString(CultureInfo.InvariantCulture), out int value))
@@ -121,6 +174,10 @@ private static int GetNumberBoxValue(NumberBox numberBox, int defaultValue)
return value;
}
+ ///
+ /// Gets the interval in milliseconds.
+ ///
+ /// The interval in milliseconds.
private int GetIntervalMilliseconds()
{
int hours = GetNumberBoxValue(NumberBoxHours, 0);
@@ -139,35 +196,53 @@ private int GetIntervalMilliseconds()
return totalTimeInMilliseconds;
}
- private async void StartToggleButton_OnChecked(object sender, RoutedEventArgs e)
+ ///
+ /// Handles the Checked event of the ToggleButtonStart control.
+ ///
+ /// The source of the event.
+ /// The event data.
+ private async void ToggleButtonStart_OnChecked(object sender, RoutedEventArgs e)
{
- StartToggleButton.IsEnabled = false;
+ // Update controls
+ ToggleButtonStart.IsEnabled = false;
SetControlsEnabled(false);
+ await Task.Delay(1000);
+ FontIconStart.Glyph = "\uEDB4";
+ BadgeNotificationManager.Current.SetBadgeAsGlyph(BadgeNotificationGlyph.Playing);
+ SetNotificationBadge(BadgeNotificationGlyph.Playing);
+ ToolTipService.SetToolTip(ToggleButtonStart, "ToggleButtonStartTooltipStop".GetLocalized());
- // 3-second countdown
- for (int i = 3; i > 0; i--)
- {
- StartToggleButton.Content = i.ToString();
- await Task.Delay(1000);
- }
-
- StartToggleButton.IsEnabled = true;
- StartToggleButton.Content = "Stop";
-
+ // Start auto clicker
int clickInterval = GetIntervalMilliseconds();
- int repeatAmount = ClickRepeatCheckBox.IsEnabled == true ? Convert.ToInt32(ClickRepeatAmount.Value) : 0;
+ int repeatAmount = ClickRepeatCheckBox.IsChecked == true ? Convert.ToInt32(ClickRepeatAmount.Value) : 0;
int mouseButton = MouseButtonTypeComboBox.SelectedIndex;
int clickOffset = ClickOffsetCheckBox.IsChecked == true ? Convert.ToInt32(ClickOffsetAmount.Value) : 0;
AutoClicker.Start(clickInterval, repeatAmount, mouseButton, clickOffset);
+ ToggleButtonStart.IsEnabled = true;
}
- private void StartToggleButton_OnUnchecked(object sender, RoutedEventArgs e)
+ ///
+ /// Handles the Unchecked event of the ToggleButtonStart control.
+ ///
+ /// The source of the event.
+ /// The event data.
+ private void ToggleButtonStart_OnUnchecked(object sender, RoutedEventArgs e)
{
- StartToggleButton.Content = "Start";
- AutoClicker.Stop();
+ // Update controls
SetControlsEnabled(true);
+ FontIconStart.Glyph = "\uEE4A";
+ SetNotificationBadge(BadgeNotificationGlyph.Paused);
+ ToolTipService.SetToolTip(ToggleButtonStart, "ToggleButtonStartTooltipStart".GetLocalized());
+
+ // Stop auto clicker
+ AutoClicker.Stop();
}
+ ///
+ /// Handles the Click event of the CheckBox controls.
+ ///
+ /// The source of the event.
+ /// The event data.
private void CheckBox_Click(object sender, RoutedEventArgs e)
{
if (sender.Equals(ClickRepeatCheckBox))
@@ -179,4 +254,14 @@ private void CheckBox_Click(object sender, RoutedEventArgs e)
ClickOffsetAmount.IsEnabled = ClickOffsetCheckBox.IsChecked == true;
}
}
-}
+
+ ///
+ /// Handles the Click event of the SettingsButton control.
+ ///
+ /// The source of the event.
+ /// The event data.
+ private void SettingsButton_Click(object sender, RoutedEventArgs e)
+ {
+ _ = Frame.Navigate(typeof(SettingsPage));
+ }
+}
\ No newline at end of file
diff --git a/FluentAutoClicker/MainWindow.xaml b/FluentAutoClicker/MainWindow.xaml
index f56690b..e5d6130 100644
--- a/FluentAutoClicker/MainWindow.xaml
+++ b/FluentAutoClicker/MainWindow.xaml
@@ -1,37 +1,38 @@
+
-
-
-
-
-
+
-
+
-
-
+
+
-
-
+
+
-
+
\ No newline at end of file
diff --git a/FluentAutoClicker/MainWindow.xaml.cs b/FluentAutoClicker/MainWindow.xaml.cs
index f131397..1ef8d1e 100644
--- a/FluentAutoClicker/MainWindow.xaml.cs
+++ b/FluentAutoClicker/MainWindow.xaml.cs
@@ -16,6 +16,8 @@
// along with Fluent Auto Clicker. If not, see .
using FluentAutoClicker.Helpers;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Navigation;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
@@ -23,10 +25,13 @@
namespace FluentAutoClicker;
///
-/// An window that displays a page's contents.
+/// An window that displays a page's contents.
///
public sealed partial class MainWindow
{
+ ///
+ /// Initializes a new instance of the class.
+ ///
public MainWindow()
{
InitializeComponent();
@@ -37,6 +42,31 @@ public MainWindow()
// Set up window title bar
ExtendsContentIntoTitleBar = true;
- AppTitleBar.Title = "AppDisplayName".GetLocalized();
+
+ // Set up frame
+ _ = NavFrame.Navigate(typeof(MainPage));
+ }
+
+ ///
+ /// Handles the BackRequested event of the AppTitleBar control.
+ ///
+ /// The source of the event.
+ /// The event data.
+ private void AppTitleBar_BackRequested(TitleBar sender, object args)
+ {
+ if (NavFrame.CanGoBack)
+ {
+ NavFrame.GoBack();
+ }
+ }
+
+ ///
+ /// Handles the Navigated event of the NavFrame control.
+ ///
+ /// The source of the event.
+ /// The event data.
+ private void NavFrame_Navigated(object sender, NavigationEventArgs e)
+ {
+ AppTitleBar.IsBackButtonVisible = e.SourcePageType != typeof(MainPage);
}
}
\ No newline at end of file
diff --git a/FluentAutoClicker/Package.appxmanifest b/FluentAutoClicker/Package.appxmanifest
index b54ee96..ff9ffa5 100644
--- a/FluentAutoClicker/Package.appxmanifest
+++ b/FluentAutoClicker/Package.appxmanifest
@@ -9,14 +9,14 @@
ms-resource:AppDisplayName
- Ryan Luu
+ ms-resource:AppPublisherDisplayName
Assets\StoreLogo.png
diff --git a/FluentAutoClicker/Program.cs b/FluentAutoClicker/Program.cs
index b830b22..ab91ce8 100644
--- a/FluentAutoClicker/Program.cs
+++ b/FluentAutoClicker/Program.cs
@@ -20,23 +20,31 @@
using Microsoft.Windows.AppLifecycle;
using System.Diagnostics;
using System.Runtime.InteropServices;
+using WinRT;
namespace FluentAutoClicker;
///
-/// Customized Program.cs file to implement single-instancing in a WinUI app with C#. Single-instanced apps only allow one instance of the app running at a time.
+/// Customized Program.cs file to implement
+///
+/// single-instancing
+/// in a WinUI app with C#.
+///
+/// Single-instanced apps only allow one instance of the app running at a time.
///
public partial class Program
{
+ private static IntPtr _redirectEventHandle = IntPtr.Zero;
+
[STAThread]
private static int Main()
{
- WinRT.ComWrappersSupport.InitializeComWrappers();
+ ComWrappersSupport.InitializeComWrappers();
bool isRedirect = DecideRedirection();
if (!isRedirect)
{
- Application.Start((p) =>
+ Application.Start(p =>
{
DispatcherQueueSynchronizationContext context = new(
DispatcherQueue.GetForCurrentThread());
@@ -52,7 +60,6 @@ private static bool DecideRedirection()
{
bool isRedirect = false;
AppActivationArguments args = AppInstance.GetCurrent().GetActivatedEventArgs();
- ExtendedActivationKind kind = args.Kind;
AppInstance keyInstance = AppInstance.FindOrRegisterForKey("FluentAutoClickerApp");
if (keyInstance.IsCurrent)
@@ -80,18 +87,16 @@ private static partial IntPtr CreateEvent(
[LibraryImport("ole32.dll")]
private static partial uint CoWaitForMultipleObjects(
uint dwFlags, uint dwMilliseconds, ulong nHandles,
- [In, Out] IntPtr[] pHandles, out uint dwIndex);
+ [In][Out] IntPtr[] pHandles, out uint dwIndex);
[LibraryImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool SetForegroundWindow(IntPtr hWnd);
- private static IntPtr _redirectEventHandle = IntPtr.Zero;
-
// Do the redirection on another thread, and use a non-blocking
// wait method to wait for the redirection to complete.
- public static void RedirectActivationTo(AppActivationArguments args,
- AppInstance keyInstance)
+ private static void RedirectActivationTo(AppActivationArguments args,
+ AppInstance keyInstance)
{
_redirectEventHandle = CreateEvent(IntPtr.Zero, true, false, null);
_ = Task.Run(() =>
@@ -100,11 +105,11 @@ public static void RedirectActivationTo(AppActivationArguments args,
_ = SetEvent(_redirectEventHandle);
});
- uint cwmoDefault = 0;
- uint infinite = 0xFFFFFFFF;
+ const uint cwmoDefault = 0;
+ const uint infinite = 0xFFFFFFFF;
_ = CoWaitForMultipleObjects(
- cwmoDefault, infinite, 1,
- [_redirectEventHandle], out uint handleIndex);
+ cwmoDefault, infinite, 1,
+ [_redirectEventHandle], out uint _);
// Bring the window to the foreground
Process process = Process.GetProcessById((int)keyInstance.ProcessId);
diff --git a/FluentAutoClicker/SettingsPage.xaml b/FluentAutoClicker/SettingsPage.xaml
new file mode 100644
index 0000000..5677288
--- /dev/null
+++ b/FluentAutoClicker/SettingsPage.xaml
@@ -0,0 +1,120 @@
+
+
+
+
+
+ 4
+ -12,0,0,0
+ 0,8,0,8
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/FluentAutoClicker/SettingsPage.xaml.cs b/FluentAutoClicker/SettingsPage.xaml.cs
new file mode 100644
index 0000000..ee1f085
--- /dev/null
+++ b/FluentAutoClicker/SettingsPage.xaml.cs
@@ -0,0 +1,201 @@
+// Copyright (C) 2025 Ryan Luu
+//
+// This file is part of Fluent Auto Clicker.
+//
+// Fluent Auto Clicker is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Fluent Auto Clicker is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with Fluent Auto Clicker. If not, see .
+
+using FluentAutoClicker.Helpers;
+using Microsoft.UI.Composition.SystemBackdrops;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Media;
+using System.Runtime.InteropServices;
+using Windows.ApplicationModel;
+using Windows.Storage;
+using Windows.System;
+using WinUIEx;
+using SystemBackdrop = Microsoft.UI.Xaml.Media.SystemBackdrop;
+
+// To learn more about WinUI, the WinUI project structure,
+// and more about our project templates, see: http://aka.ms/winui-project-info.
+
+namespace FluentAutoClicker;
+
+///
+/// Represents the settings page of the Fluent Auto Clicker application.
+///
+public sealed partial class SettingsPage
+{
+ ///
+ /// Application settings container in the local app data store.
+ ///
+ private readonly ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public SettingsPage()
+ {
+ InitializeComponent();
+
+ // Initialize saved settings
+ ThemeSelectedIndex = ThemeSelectedIndex;
+ BackdropSelectedIndex = BackdropSelectedIndex;
+ IsAlwaysOnTop = IsAlwaysOnTop;
+
+ // Initialize app name and version
+ AppAboutSettingsExpander.Header = AppName;
+ AppVersionTextBlock.Text = AppVersion;
+ }
+
+ ///
+ /// Gets or sets the if the playing notification badge is enabled.
+ ///
+ public bool NotificationBadgePlaying
+ {
+ get => (bool)(localSettings.Values[nameof(NotificationBadgePlaying)] ?? true);
+ set => localSettings.Values[nameof(NotificationBadgePlaying)] = value;
+ }
+
+ ///
+ /// Gets or sets the if the paused notification badge is enabled.
+ ///
+ public bool NotificationBadgePaused
+ {
+ get => (bool)(localSettings.Values[nameof(NotificationBadgePaused)] ?? true);
+ set => localSettings.Values[nameof(NotificationBadgePaused)] = value;
+ }
+
+ ///
+ /// Gets the localized application name.
+ ///
+ private static string AppName => "AppDisplayName".GetLocalized();
+
+ ///
+ /// Gets the application version.
+ ///
+ private static string AppVersion
+ {
+ get
+ {
+ PackageVersion version = Package.Current.Id.Version;
+ return $"{version.Major}.{version.Minor}.{version.Build}.{version.Revision}";
+ }
+ }
+
+ ///
+ /// Gets the main application window.
+ ///
+ private static WindowEx MainWindow => App.MainWindow;
+
+ ///
+ /// Gets or sets the selected theme index.
+ ///
+ private int ThemeSelectedIndex
+ {
+ get => (int)(localSettings.Values[nameof(ThemeSelectedIndex)] ?? 0);
+ set
+ {
+ localSettings.Values[nameof(ThemeSelectedIndex)] = value;
+ ((FrameworkElement)MainWindow.Content).RequestedTheme = value switch
+ {
+ 1 => ElementTheme.Light,
+ 2 => ElementTheme.Dark,
+ _ => ElementTheme.Default
+ };
+ }
+ }
+
+ ///
+ /// Gets or sets the selected backdrop index.
+ ///
+ private int BackdropSelectedIndex
+ {
+ get => (int)(localSettings.Values[nameof(BackdropSelectedIndex)] ?? 0);
+ set
+ {
+ localSettings.Values[nameof(BackdropSelectedIndex)] = value;
+
+ // HACK: Prevent changing the backdrop so it doesn't flash between pages
+ SystemBackdrop currentBackdrop = MainWindow.SystemBackdrop;
+ bool needsChange = value switch
+ {
+ 1 => currentBackdrop is not MicaBackdrop { Kind: MicaKind.BaseAlt },
+ 2 => currentBackdrop is not DesktopAcrylicBackdrop,
+ _ => currentBackdrop is not MicaBackdrop ||
+ (currentBackdrop is MicaBackdrop mica && mica.Kind != MicaKind.Base)
+ };
+
+ if (needsChange)
+ {
+ MainWindow.SystemBackdrop = value switch
+ {
+ 1 => new MicaBackdrop { Kind = MicaKind.BaseAlt },
+ 2 => new DesktopAcrylicBackdrop(),
+ _ => new MicaBackdrop()
+ };
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the application window is always on top.
+ ///
+ private bool IsAlwaysOnTop
+ {
+ get => (bool)(localSettings.Values[nameof(IsAlwaysOnTop)] ?? true);
+ set
+ {
+ localSettings.Values[nameof(IsAlwaysOnTop)] = value;
+ MainWindow.IsAlwaysOnTop = value;
+ }
+ }
+
+ ///
+ /// Handles the click event of the feedback hyperlink button.
+ ///
+ /// The source of the event.
+ /// The event data.
+ private async void HyperlinkButtonFeedback_Click(object sender, RoutedEventArgs e)
+ {
+ string recipientEmail = "feedback@fluentautoclicker.com";
+ string subject = $"{AppName} App Feedback";
+ string messageBody = $"""
+
+
+ ---------- Add your feedback above ----------
+
+ .NET installation: {RuntimeInformation.FrameworkDescription}
+ App version: {AppVersion}
+ App architecture: {RuntimeInformation.ProcessArchitecture}
+ OS version: {RuntimeInformation.OSDescription}
+ OS architecture: {RuntimeInformation.OSArchitecture}
+ """;
+
+ await ComposeEmailAsync(recipientEmail, subject, messageBody);
+ }
+
+ ///
+ /// Composes and launches an email with the specified recipient, subject, and message body.
+ ///
+ /// The recipient email address.
+ /// The email subject.
+ /// The email message body.
+ private static async Task ComposeEmailAsync(string recipientEmail, string subject, string messageBody)
+ {
+ Uri uri = new(
+ $"mailto:{recipientEmail}?subject={Uri.EscapeDataString(subject)}&body={Uri.EscapeDataString(messageBody)}");
+
+ _ = await Launcher.LaunchUriAsync(uri);
+ }
+}
\ No newline at end of file
diff --git a/FluentAutoClicker/Strings/en-us/Resources.resw b/FluentAutoClicker/Strings/en-us/Resources.resw
index 468f297..727894f 100644
--- a/FluentAutoClicker/Strings/en-us/Resources.resw
+++ b/FluentAutoClicker/Strings/en-us/Resources.resw
@@ -165,11 +165,11 @@
Which button to start the auto clicker
-
- Start
+
+ Settings
-
- Start the auto clicker
+
+ Start
Click repeat
@@ -186,4 +186,112 @@
Description
+
+ Settings
+
+
+ Appearance
+
+
+ App theme
+
+
+ Select which app theme to display
+
+
+ Use system setting
+
+
+ Light
+
+
+ Dark
+
+
+ Backdrop material
+
+
+ System backdrop material for window background
+
+
+ Mica
+
+
+ Mica Alt
+
+
+ Acrylic
+
+
+ Always on top
+
+
+ Always show the app's window on top of others
+
+
+ Behavior
+
+
+ Ryan Luu
+
+
+ Preview
+
+
+ Fluent Auto Clicker
+
+
+ Privacy policy
+
+
+ https://github.com/RyanLua/FluentAutoClicker/blob/main/PRIVACY_POLICY.md
+
+
+ Source code
+
+
+ Website
+
+
+ https://fluentautoclicker.com
+
+
+ https://github.com/RyanLua/FluentAutoClicker
+
+
+ Translate
+
+
+ https://translate.fluentautoclicker.com
+
+
+ Discord
+
+
+ https://discord.gg/32AMskrpFf
+
+
+ Send feedback
+
+
+ License
+
+
+ https://github.com/RyanLua/FluentAutoClicker/blob/main/COPYING.md
+
+
+ Notification badge
+
+
+ Show the status notification badge on the app's taskbar icon
+
+
+ Show badge while clicking
+
+
+ Show badge while paused
+
+
+ Pause
+
\ No newline at end of file
diff --git a/FluentAutoClicker/Styles.xaml b/FluentAutoClicker/Styles.xaml
index e53df05..c0b81db 100644
--- a/FluentAutoClicker/Styles.xaml
+++ b/FluentAutoClicker/Styles.xaml
@@ -1,9 +1,5 @@
-
-
- 4
- 8
- 8
+
16,8,16,16
\ No newline at end of file
diff --git a/FluentAutoClicker/Themes/Generic.xaml b/FluentAutoClicker/Themes/Generic.xaml
new file mode 100644
index 0000000..d7756fc
--- /dev/null
+++ b/FluentAutoClicker/Themes/Generic.xaml
@@ -0,0 +1,6 @@
+
+
+
+
+
+