From 4b93eaca0d2cc83b29045f51deced350d0c7b9b0 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Sun, 15 Dec 2024 23:39:51 +0100 Subject: [PATCH] Initial commit --- .gitattributes | 2 + .gitignore | 398 ++++++++++++++++++ InvLock.sln | 22 + InvLock/App.xaml | 15 + InvLock/App.xaml.cs | 140 ++++++ InvLock/AssemblyInfo.cs | 10 + InvLock/Controls/OutlinedTextBlock.cs | 294 +++++++++++++ InvLock/DataContexts/MainWindowDataContext.cs | 16 + InvLock/GlobalSuppressions.cs | 9 + InvLock/InvLock.csproj | 19 + InvLock/LockWindow.xaml | 21 + InvLock/LockWindow.xaml.cs | 236 +++++++++++ InvLock/MainWindow.xaml | 148 +++++++ InvLock/MainWindow.xaml.cs | 85 ++++ InvLock/Settings.cs | 8 + InvLock/Utilities/WindowsApi.cs | 105 +++++ LICENSE | 21 + README.md | 2 + 18 files changed, 1551 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 InvLock.sln create mode 100644 InvLock/App.xaml create mode 100644 InvLock/App.xaml.cs create mode 100644 InvLock/AssemblyInfo.cs create mode 100644 InvLock/Controls/OutlinedTextBlock.cs create mode 100644 InvLock/DataContexts/MainWindowDataContext.cs create mode 100644 InvLock/GlobalSuppressions.cs create mode 100644 InvLock/InvLock.csproj create mode 100644 InvLock/LockWindow.xaml create mode 100644 InvLock/LockWindow.xaml.cs create mode 100644 InvLock/MainWindow.xaml create mode 100644 InvLock/MainWindow.xaml.cs create mode 100644 InvLock/Settings.cs create mode 100644 InvLock/Utilities/WindowsApi.cs create mode 100644 LICENSE create mode 100644 README.md diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8a30d25 --- /dev/null +++ b/.gitignore @@ -0,0 +1,398 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml diff --git a/InvLock.sln b/InvLock.sln new file mode 100644 index 0000000..4bf0a51 --- /dev/null +++ b/InvLock.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.12.35527.113 d17.12 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InvLock", "InvLock\InvLock.csproj", "{CE36EA45-CC69-468A-8550-EF8912236277}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CE36EA45-CC69-468A-8550-EF8912236277}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CE36EA45-CC69-468A-8550-EF8912236277}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CE36EA45-CC69-468A-8550-EF8912236277}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CE36EA45-CC69-468A-8550-EF8912236277}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/InvLock/App.xaml b/InvLock/App.xaml new file mode 100644 index 0000000..22dc1ea --- /dev/null +++ b/InvLock/App.xaml @@ -0,0 +1,15 @@ + + + + + + + + + + \ No newline at end of file diff --git a/InvLock/App.xaml.cs b/InvLock/App.xaml.cs new file mode 100644 index 0000000..cec9155 --- /dev/null +++ b/InvLock/App.xaml.cs @@ -0,0 +1,140 @@ +using CuteUtils.Logging; + +using System.IO; +using System.Windows; + +namespace InvLock; + +/// +/// Interaction logic for App.xaml +/// +public partial class App : Application +{ + public const string AppGuid = "4B2411E5-1AE9-450C-AF2F-9A515ECBB9DF"; + + public const string AppName = "InvLock"; + + // This is the previous name of the application, used to migrate the application data folder + private const string PreviousAppName = "InvLock"; + + private static readonly string logFilePath = Path.Combine(ApplicationDataPath, $"{AppName}.log"); + private readonly Thread? eventThread; + private readonly EventWaitHandle eventWaitHandle; + public static string ApplicationDataPath => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "StoneRed", AppName); + + public static Logger Logger { get; } = new Logger() + { + Config = new() + { + FatalConfig = new OutputConfig() + { + ConsoleColor = ConsoleColor.DarkRed, + LogTarget = LogTarget.DebugConsole | LogTarget.File, + FilePath = logFilePath + }, + ErrorConfig = new OutputConfig() + { + ConsoleColor = ConsoleColor.Red, + LogTarget = LogTarget.DebugConsole | LogTarget.File, + FilePath = logFilePath + }, + WarnConfig = new OutputConfig() + { + ConsoleColor = ConsoleColor.Yellow, + LogTarget = LogTarget.DebugConsole | LogTarget.File, + FilePath = logFilePath + }, + InfoConfig = new OutputConfig() + { + ConsoleColor = ConsoleColor.White, + LogTarget = LogTarget.DebugConsole | LogTarget.File, + FilePath = logFilePath + }, + DebugConfig = new OutputConfig() + { + ConsoleColor = ConsoleColor.Gray, + LogTarget = LogTarget.DebugConsole, + }, + FormatConfig = new FormatConfig() + { + DebugConsoleFormat = $"> {{{LogFormatType.DateTime}:HH:mm:ss}} | {{{LogFormatType.LogSeverity},-5}} | {{{LogFormatType.Message}}}\nat {{{LogFormatType.LineNumber}}} | {{{LogFormatType.FilePath}}}" + } + } + }; + + private static string PreviousApplicationDataPath => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "StoneRed", PreviousAppName); + + public App() + { + // Setup global event handler + eventWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset, AppGuid, out bool createdNew); + + // Check if creating new was successful + if (!createdNew) + { + Setup(false); + Logger.LogWarn("Shutting down because other instance already running.", source: "Setup"); + // Shutdown Application + _ = eventWaitHandle.Set(); + Current.Shutdown(); + } + else + { + eventThread = new Thread(() => + { + while (eventWaitHandle.WaitOne()) + { + _ = Current.Dispatcher.BeginInvoke(() => ((MainWindow)Current.MainWindow).RestoreWindow()); + } + }); + eventThread.Start(); + + Setup(true); + Exit += CloseHandler; + } + } + + protected void CloseHandler(object sender, EventArgs e) + { + eventWaitHandle.Close(); + eventThread?.Interrupt(); + } + + private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) + { + Exception exception = (Exception)e.ExceptionObject; + Logger.LogFatal(exception + (e.IsTerminating ? "\t Process terminating!" : ""), source: exception.Source ?? "Unknown"); + } + + private static void Setup(bool clearLogFile) + { + AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; + + try + { + if (Directory.Exists(PreviousApplicationDataPath) && !Directory.Exists(ApplicationDataPath)) + { + Directory.Move(PreviousApplicationDataPath, ApplicationDataPath); + Logger.LogInfo("Migrated ApplicationData folder", source: "Setup"); + } + + if (!Directory.Exists(ApplicationDataPath)) + { + _ = Directory.CreateDirectory(ApplicationDataPath); + Logger.LogInfo("Created ApplicationData folder", source: "Setup"); + } + } + catch (Exception ex) + { + _ = MessageBox.Show(ex.ToString(), AppName, MessageBoxButton.OK, MessageBoxImage.Error); + Logger.Log(ex.Message, "Setup", LogSeverity.Error); + } + + if (clearLogFile) + { + Logger.ClearLogFile(LogSeverity.Info); + } + + Logger.LogInfo("Setup complete", source: "Setup"); + } +} \ No newline at end of file diff --git a/InvLock/AssemblyInfo.cs b/InvLock/AssemblyInfo.cs new file mode 100644 index 0000000..b0ec827 --- /dev/null +++ b/InvLock/AssemblyInfo.cs @@ -0,0 +1,10 @@ +using System.Windows; + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] diff --git a/InvLock/Controls/OutlinedTextBlock.cs b/InvLock/Controls/OutlinedTextBlock.cs new file mode 100644 index 0000000..d603efb --- /dev/null +++ b/InvLock/Controls/OutlinedTextBlock.cs @@ -0,0 +1,294 @@ +using System.Globalization; +using System.Windows; +using System.Windows.Markup; +using System.Windows.Media; + +namespace InvLock.Controls; + +[ContentProperty("Text")] +public class OutlinedTextBlock : FrameworkElement, IAddChild +{ + #region Private Fields + + private Geometry _textGeometry; + + #endregion Private Fields + + #region Private Methods + + /// + /// Invoked when a dependency property has changed. Generate a new FormattedText object to display. + /// + /// OutlineText object whose property was updated. + /// Event arguments for the dependency property. + private static void OnOutlineTextInvalidated(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((OutlinedTextBlock)d).CreateText(); + } + + #endregion Private Methods + + #region FrameworkElement Overrides + + /// + /// Create the outline geometry based on the formatted text. + /// + public void CreateText() + { + FontStyle fontStyle = FontStyles.Normal; + FontWeight fontWeight = FontWeights.Medium; + + if (Bold == true) + { + fontWeight = FontWeights.Bold; + } + + if (Italic == true) + { + fontStyle = FontStyles.Italic; + } + + // Create the formatted text based on the properties set. + FormattedText formattedText = new FormattedText( + Text, + CultureInfo.GetCultureInfo("en-us"), + FlowDirection.LeftToRight, + new Typeface(Font, fontStyle, fontWeight, FontStretches.Normal), + FontSize, + Brushes.Black // This brush does not matter since we use the geometry of the text. + ); + + // Build the geometry object that represents the text. + _textGeometry = formattedText.BuildGeometry(new Point(0, 0)); + + //set the size of the custome control based on the size of the text + MinWidth = formattedText.Width; + MinHeight = formattedText.Height; + } + + /// + /// OnRender override draws the geometry of the text and optional highlight. + /// + /// Drawing context of the OutlineText control. + protected override void OnRender(DrawingContext drawingContext) + { + CreateText(); + // Draw the outline based on the properties that are set. + drawingContext.DrawGeometry(Fill, new Pen(Stroke, StrokeThickness), _textGeometry); + } + + #endregion FrameworkElement Overrides + + #region DependencyProperties + + /// + /// Identifies the Bold dependency property. + /// + public static readonly DependencyProperty BoldProperty = DependencyProperty.Register( + "Bold", + typeof(bool), + typeof(OutlinedTextBlock), + new FrameworkPropertyMetadata( + false, + FrameworkPropertyMetadataOptions.AffectsRender, + new PropertyChangedCallback(OnOutlineTextInvalidated), + null + ) + ); + + /// + /// Identifies the Fill dependency property. + /// + public static readonly DependencyProperty FillProperty = DependencyProperty.Register( + "Fill", + typeof(Brush), + typeof(OutlinedTextBlock), + new FrameworkPropertyMetadata( + new SolidColorBrush(Colors.LightSteelBlue), + FrameworkPropertyMetadataOptions.AffectsRender, + new PropertyChangedCallback(OnOutlineTextInvalidated), + null + ) + ); + + /// + /// Identifies the Font dependency property. + /// + public static readonly DependencyProperty FontProperty = DependencyProperty.Register( + "Font", + typeof(FontFamily), + typeof(OutlinedTextBlock), + new FrameworkPropertyMetadata( + new FontFamily("Arial"), + FrameworkPropertyMetadataOptions.AffectsRender, + new PropertyChangedCallback(OnOutlineTextInvalidated), + null + ) + ); + + /// + /// Identifies the FontSize dependency property. + /// + public static readonly DependencyProperty FontSizeProperty = DependencyProperty.Register( + "FontSize", + typeof(double), + typeof(OutlinedTextBlock), + new FrameworkPropertyMetadata( + 48.0, + FrameworkPropertyMetadataOptions.AffectsRender, + new PropertyChangedCallback(OnOutlineTextInvalidated), + null + ) + ); + + /// + /// Identifies the Italic dependency property. + /// + public static readonly DependencyProperty ItalicProperty = DependencyProperty.Register( + "Italic", + typeof(bool), + typeof(OutlinedTextBlock), + new FrameworkPropertyMetadata( + false, + FrameworkPropertyMetadataOptions.AffectsRender, + new PropertyChangedCallback(OnOutlineTextInvalidated), + null + ) + ); + + /// + /// Identifies the Stroke dependency property. + /// + public static readonly DependencyProperty StrokeProperty = DependencyProperty.Register( + "Stroke", + typeof(Brush), + typeof(OutlinedTextBlock), + new FrameworkPropertyMetadata( + new SolidColorBrush(Colors.Teal), + FrameworkPropertyMetadataOptions.AffectsRender, + new PropertyChangedCallback(OnOutlineTextInvalidated), + null + ) + ); + + /// + /// Identifies the StrokeThickness dependency property. + /// + public static readonly DependencyProperty StrokeThicknessProperty = DependencyProperty.Register( + "StrokeThickness", + typeof(ushort), + typeof(OutlinedTextBlock), + new FrameworkPropertyMetadata( + (ushort)0, + FrameworkPropertyMetadataOptions.AffectsRender, + new PropertyChangedCallback(OnOutlineTextInvalidated), + null + ) + ); + + /// + /// Identifies the Text dependency property. + /// + public static readonly DependencyProperty TextProperty = DependencyProperty.Register( + "Text", + typeof(string), + typeof(OutlinedTextBlock), + new FrameworkPropertyMetadata( + "", + FrameworkPropertyMetadataOptions.AffectsRender, + new PropertyChangedCallback(OnOutlineTextInvalidated), + null + ) + ); + + /// + /// Specifies whether the font should display Bold font weight. + /// + public bool Bold + { + get => (bool)GetValue(BoldProperty); + + set => SetValue(BoldProperty, value); + } + + /// + /// Specifies the brush to use for the fill of the formatted text. + /// + public Brush Fill + { + get => (Brush)GetValue(FillProperty); + + set => SetValue(FillProperty, value); + } + + /// + /// The font to use for the displayed formatted text. + /// + public FontFamily Font + { + get => (FontFamily)GetValue(FontProperty); + + set => SetValue(FontProperty, value); + } + + /// + /// The current font size. + /// + public double FontSize + { + get => (double)GetValue(FontSizeProperty); + + set => SetValue(FontSizeProperty, value); + } + + /// + /// Specifies whether the font should display Italic font style. + /// + public bool Italic + { + get => (bool)GetValue(ItalicProperty); + + set => SetValue(ItalicProperty, value); + } + + /// + /// Specifies the brush to use for the stroke and optional hightlight of the formatted text. + /// + public Brush Stroke + { + get => (Brush)GetValue(StrokeProperty); + + set => SetValue(StrokeProperty, value); + } + + /// + /// The stroke thickness of the font. + /// + public ushort StrokeThickness + { + get => (ushort)GetValue(StrokeThicknessProperty); + + set => SetValue(StrokeThicknessProperty, value); + } + + /// + /// Specifies the text string to display. + /// + public string Text + { + get => (string)GetValue(TextProperty); + + set => SetValue(TextProperty, value); + } + + public void AddChild(object value) + { + } + + public void AddText(string value) + { + Text = value; + } + + #endregion DependencyProperties +} \ No newline at end of file diff --git a/InvLock/DataContexts/MainWindowDataContext.cs b/InvLock/DataContexts/MainWindowDataContext.cs new file mode 100644 index 0000000..a23cc3d --- /dev/null +++ b/InvLock/DataContexts/MainWindowDataContext.cs @@ -0,0 +1,16 @@ +using System.Reflection; + +namespace InvLock.DataContexts; + +internal class MainWindowDataContext +{ +#if DEBUG + public string Title => $"{App.AppName} - Dev {AppVersion}"; +#else + public string Title => $"{App.AppName} - {AppVersion}"; +#endif + + public string AppName => App.AppName; + + public string AppVersion => Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? "Unknown"; +} \ No newline at end of file diff --git a/InvLock/GlobalSuppressions.cs b/InvLock/GlobalSuppressions.cs new file mode 100644 index 0000000..0404127 --- /dev/null +++ b/InvLock/GlobalSuppressions.cs @@ -0,0 +1,9 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Minor Code Smell", "S2325:Methods and properties that don't access instance data should be static", Justification = "Required for bindings", Scope = "namespaceanddescendants", Target = "~N:InvLock.DataContexts")] +[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Required for bindings", Scope = "namespaceanddescendants", Target = "~N:InvLock.DataContexts")] \ No newline at end of file diff --git a/InvLock/InvLock.csproj b/InvLock/InvLock.csproj new file mode 100644 index 0000000..e303716 --- /dev/null +++ b/InvLock/InvLock.csproj @@ -0,0 +1,19 @@ + + + + WinExe + net9.0-windows7.0 + enable + enable + true + true + + + + + + + + + + diff --git a/InvLock/LockWindow.xaml b/InvLock/LockWindow.xaml new file mode 100644 index 0000000..2a8ae6c --- /dev/null +++ b/InvLock/LockWindow.xaml @@ -0,0 +1,21 @@ + + + + \ No newline at end of file diff --git a/InvLock/LockWindow.xaml.cs b/InvLock/LockWindow.xaml.cs new file mode 100644 index 0000000..ab76645 --- /dev/null +++ b/InvLock/LockWindow.xaml.cs @@ -0,0 +1,236 @@ +using InvLock.Utilities; + +using SharpHook; +using SharpHook.Native; + +using System.Windows; +using System.Windows.Interop; +using System.Windows.Media; +using System.Windows.Media.Animation; + +namespace InvLock; + +/// +/// Interaction logic for LockWindow.xaml +/// +public partial class LockWindow : Window +{ + private const bool HideWindows = false; + private readonly SimpleGlobalHook hook = new SimpleGlobalHook(); + private readonly Dictionary pressedKeys = []; + private List windows = []; + private bool isOpen = true; + private bool suppressInput = false; + + public LockWindow() + { + InitializeComponent(); + + Window w = new() + { + Left = -100, + Top = -100, + Width = 0, + Height = 0, + + WindowStyle = WindowStyle.ToolWindow, + ShowInTaskbar = false + }; + + WindowInteropHelper helper = new WindowInteropHelper(w); + _ = helper.EnsureHandle(); + + Owner = w; + + hook.MouseMoved += Hook_Mouse; + hook.MouseDragged += Hook_Mouse; + hook.MousePressed += Hook_Mouse; + hook.MouseReleased += Hook_Mouse; + hook.MouseClicked += Hook_Mouse; + hook.MouseWheel += Hook_MouseWheel; + + hook.KeyReleased += Hook_KeyReleased; + hook.KeyTyped += Hook_Key; + hook.KeyPressed += Hook_KeyPressed; + Microsoft.Win32.SystemEvents.SessionSwitch += SystemEvents_SessionSwitch; + + _ = Task.Run(hook.Run); + } + + private void ActivateLockScreen() + { + if (HideWindows) + { + MinimizeWindows(); + } + + isOpen = true; + suppressInput = true; + + Dispatcher.Invoke(() => + { + _ = Activate(); + + textBlock.Text = "Lock Screen Active"; + textBlock.Stroke = (Brush)FindResource("PaletteRedBrush"); + + Animation(); + }); + } + + private void DeactivateLockScreen() + { + isOpen = false; + Dispatcher.Invoke(Close); + } + + private void Hook_KeyPressed(object? sender, KeyboardHookEventArgs e) + { + foreach (KeyCode key in pressedKeys.Keys) + { + if (DateTime.UtcNow - pressedKeys[key] > TimeSpan.FromSeconds(1)) + { + _ = pressedKeys.Remove(key); + } + } + _ = pressedKeys[e.Data.KeyCode] = DateTime.UtcNow; + + if (pressedKeys.ContainsKey(KeyCode.VcL) && pressedKeys.ContainsKey(KeyCode.VcLeftShift) && pressedKeys.ContainsKey(KeyCode.VcLeftControl) && pressedKeys.Count == 3) + { + if (suppressInput) + { + DeactivateLockScreen(); + } + else + { + ActivateLockScreen(); + } + } + + e.SuppressEvent = suppressInput; + } + + private void Hook_KeyReleased(object? sender, KeyboardHookEventArgs e) + { + _ = pressedKeys.Remove(e.Data.KeyCode); + e.SuppressEvent = suppressInput; + } + + private async void SystemEvents_SessionSwitch(object sender, Microsoft.Win32.SessionSwitchEventArgs e) + { + if (e.Reason == Microsoft.Win32.SessionSwitchReason.SessionUnlock) + { + if (HideWindows) + { + RestoreWindows(); + } + + isOpen = true; + + _ = Activate(); + + await Task.Delay(500); + + textBlock.Text = "Lock Screen Inactive"; + textBlock.Stroke = (Brush)FindResource("PaletteGreenBrush"); + + Animation(); + } + } + + private void MinimizeWindows() + { + windows = WindowsApi.GetOpenWindows(); + + WindowsApi.ShowTaskbar(false); + + IntPtr thisWindowHandle = Dispatcher.Invoke(() => new WindowInteropHelper(this).Handle); + + foreach (IntPtr handle in windows) + { + if (handle == thisWindowHandle) + { + continue; + } + + _ = WindowsApi.ShowWindow(handle, WindowsApi.SW_MINIMIZE); + } + } + + private void RestoreWindows() + { + WindowsApi.ShowTaskbar(true); + + foreach (IntPtr handle in windows) + { + _ = WindowsApi.ShowWindow(handle, WindowsApi.SW_RESTORE); + } + } + + private void Animation() + { + Storyboard storyboard = new Storyboard(); + TimeSpan duration = TimeSpan.FromMilliseconds(500); + + DoubleAnimation fadeInAnimation = new DoubleAnimation() + { From = 0.0, To = 1.0, Duration = new Duration(duration) }; + + DoubleAnimation fadeOutAnimation = new DoubleAnimation + { + From = 1.0, + To = 0.0, + Duration = new Duration(duration), + BeginTime = TimeSpan.FromSeconds(3) + }; + + Storyboard.SetTargetName(fadeInAnimation, name: textBlock.Name); + Storyboard.SetTargetProperty(fadeInAnimation, new PropertyPath("Opacity", 1)); + storyboard.Children.Add(fadeInAnimation); + storyboard.Begin(textBlock); + + Storyboard.SetTargetName(fadeOutAnimation, textBlock.Name); + Storyboard.SetTargetProperty(fadeOutAnimation, new PropertyPath("Opacity", 0)); + storyboard.Children.Add(fadeOutAnimation); + storyboard.Begin(textBlock); + } + + private void Hook_MouseWheel(object? sender, MouseWheelHookEventArgs e) + { + e.SuppressEvent = suppressInput; + } + + private void Hook_Mouse(object? sender, MouseHookEventArgs e) + { + e.SuppressEvent = suppressInput; + } + + private void Hook_Key(object? sender, KeyboardHookEventArgs e) + { + e.SuppressEvent = suppressInput; + } + + private void Window_Deactivated(object sender, EventArgs e) + { + if (isOpen) + { + isOpen = false; + Close(); + } + } + + private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) + { + if (suppressInput) + { + _ = WindowsApi.LockWorkStation(); + suppressInput = false; + } + + e.Cancel = true; + } + + private void Window_Loaded(object sender, RoutedEventArgs e) + { + _ = Activate(); + } +} \ No newline at end of file diff --git a/InvLock/MainWindow.xaml b/InvLock/MainWindow.xaml new file mode 100644 index 0000000..0a978fb --- /dev/null +++ b/InvLock/MainWindow.xaml @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + InvLock is a simple lock screen application for Windows 10/11. + It is designed to be lightweight and easy to use. + + If you have any issues or feature requests, please report them on GitHub. + + + To lock and unlock your screen, press + WIN + SHIFT + L + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/InvLock/MainWindow.xaml.cs b/InvLock/MainWindow.xaml.cs new file mode 100644 index 0000000..856c523 --- /dev/null +++ b/InvLock/MainWindow.xaml.cs @@ -0,0 +1,85 @@ +using InvLock.DataContexts; + +using System.Windows; + +using Wpf.Ui.Appearance; +using Wpf.Ui.Controls; + +namespace InvLock; + +/// +/// Interaction logic for MainWindow.xaml +/// +public partial class MainWindow : FluentWindow +{ + private LockWindow? lockWindow; + private bool blockWindowClosing = true; + + public MainWindow() + { + DataContext = new MainWindowDataContext(); + + InitializeComponent(); + + SystemThemeWatcher.Watch(this); + } + + internal void RestoreWindow() + { + for (int i = 0; i < 10; i++) + { + ShowInTaskbar = true; + Visibility = Visibility.Visible; + SystemCommands.RestoreWindow(this); + Topmost = true; + _ = Activate(); + Topmost = false; + } + } + + private void SettingsMenuItem_Click(object sender, RoutedEventArgs e) + { + RestoreWindow(); + } + + private void QuitMenuItem_Click(object sender, RoutedEventArgs e) + { + Quit(); + } + + private void Window_Loaded(object sender, RoutedEventArgs e) + { + Close(); + + lockWindow = new LockWindow(); + lockWindow.Show(); + } + + private void NotifyIcon_LeftClick(Wpf.Ui.Tray.Controls.NotifyIcon sender, RoutedEventArgs e) + { + RestoreWindow(); + } + + private void FluentWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e) + { + if (blockWindowClosing) + { + e.Cancel = true; + + ShowInTaskbar = false; + Visibility = Visibility.Collapsed; + } + } + + private void Quit() + { + blockWindowClosing = false; + Close(); + } + + private void FluentWindow_Closed(object sender, EventArgs e) + { + lockWindow?.Close(); + Environment.Exit(0); + } +} \ No newline at end of file diff --git a/InvLock/Settings.cs b/InvLock/Settings.cs new file mode 100644 index 0000000..c0ef541 --- /dev/null +++ b/InvLock/Settings.cs @@ -0,0 +1,8 @@ +namespace InvLock; + +internal class Settings +{ + public bool HideWindows { get; set; } = false; + public string LockText { get; set; } = "Lock Screen Active"; + public string UnlockText { get; set; } = "Lock Screen Inactive"; +} \ No newline at end of file diff --git a/InvLock/Utilities/WindowsApi.cs b/InvLock/Utilities/WindowsApi.cs new file mode 100644 index 0000000..e8cd4db --- /dev/null +++ b/InvLock/Utilities/WindowsApi.cs @@ -0,0 +1,105 @@ +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; + +namespace InvLock.Utilities; + +public static class WindowsApi +{ + public const int SW_HIDE = 0; + public const int SW_SHOWNORMAL = 1; + public const int SW_NORMAL = 1; + public const int SW_SHOWMINIMIZED = 2; + public const int SW_SHOWMAXIMIZED = 3; + public const int SW_MAXIMIZE = 3; + public const int SW_SHOWNOACTIVATE = 4; + public const int SW_SHOW = 5; + public const int SW_MINIMIZE = 6; + public const int SW_SHOWMINNOACTIVE = 7; + public const int SW_SHOWNA = 8; + public const int SW_RESTORE = 9; + public const int SW_SHOWDEFAULT = 10; + public const int SW_FORCEMINIMIZE = 11; + + private delegate bool EnumWindowsProc(IntPtr hWnd, int lParam); + + public static List GetOpenWindows() + { + IntPtr shellWindow = GetShellWindow(); + List windows = []; + + _ = EnumWindows(delegate (IntPtr hWnd, int lParam) + { + StringBuilder title = new StringBuilder(200); + + _ = GetWindowText(hWnd, title, title.Capacity); + + Debug.WriteLine($"{hWnd} | {title}"); + + if (hWnd == shellWindow) + { + return true; + } + + if (!IsWindowVisible(hWnd) || IsIconic(hWnd)) + { + return true; + } + + if (GetWindowTextLength(hWnd) == 0) + { + return true; + } + + windows.Add(hWnd); + return true; + }, 0); + + windows.Reverse(); + + return windows; + } + + public static void ShowTaskbar(bool show) + { + IntPtr hWnd = FindWindow("Shell_TrayWnd", null); + _ = ShowWindow(hWnd, show ? SW_SHOW : SW_HIDE); + + // Taskbars on other monitors + hWnd = FindWindow("Shell_SecondaryTrayWnd", null); + _ = ShowWindow(hWnd, show ? SW_SHOW : SW_HIDE); + } + + [DllImport("user32.dll")] + internal static extern bool LockWorkStation(); + + [DllImport("user32.dll")] + internal static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); + + [DllImport("user32.dll")] + private static extern bool EnumWindows(EnumWindowsProc enumFunc, int lParam); + + [DllImport("user32.dll")] + private static extern bool IsWindowVisible(IntPtr hWnd); + + [DllImport("USER32.DLL")] + private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount); + + [DllImport("USER32.DLL")] + private static extern int GetWindowTextLength(IntPtr hWnd); + + [DllImport("user32.dll")] + private static extern IntPtr GetShellWindow(); + + [DllImport("user32.dll", SetLastError = true)] + private static extern uint GetWindowLong(IntPtr hWnd, int nIndex); + + [DllImport("user32.dll")] + private static extern int SetWindowLong(IntPtr hWnd, int nIndex, uint dwNewLong); + + [DllImport("user32.dll", SetLastError = true)] + private static extern IntPtr FindWindow(string lpClassName, string? lpWindowName); + + [DllImport("user32.dll")] + private static extern bool IsIconic(IntPtr hWnd); +} \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..db33ee8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Stone_Red + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..59c07ca --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# InvLock + Transparent lockscreen for windows.