diff --git a/src/TED.sln b/src/TED.sln
new file mode 100644
index 0000000..10d06ef
--- /dev/null
+++ b/src/TED.sln
@@ -0,0 +1,28 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.2.32630.192
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TED", "TED\TED.csproj", "{BD69CE02-1B63-4949-B34B-AA6E875018DA}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Open|Any CPU = Open|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {BD69CE02-1B63-4949-B34B-AA6E875018DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BD69CE02-1B63-4949-B34B-AA6E875018DA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BD69CE02-1B63-4949-B34B-AA6E875018DA}.Open|Any CPU.ActiveCfg = Open|Any CPU
+ {BD69CE02-1B63-4949-B34B-AA6E875018DA}.Open|Any CPU.Build.0 = Open|Any CPU
+ {BD69CE02-1B63-4949-B34B-AA6E875018DA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BD69CE02-1B63-4949-B34B-AA6E875018DA}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {CEDB2C21-E30F-4966-9C5F-7A936F90E393}
+ EndGlobalSection
+EndGlobal
diff --git a/src/TED/TED.DrawModes/DrawModeBase.cs b/src/TED/TED.DrawModes/DrawModeBase.cs
new file mode 100644
index 0000000..5748717
--- /dev/null
+++ b/src/TED/TED.DrawModes/DrawModeBase.cs
@@ -0,0 +1,33 @@
+using System;
+using TED.Program;
+
+namespace TED.DrawModes
+{
+ ///
+ /// Serves as the base class for different drawing modes.
+ ///
+ internal abstract class DrawModeBase
+ {
+ // The device context to be drawn on
+ protected IntPtr DeviceContext;
+
+ // The options to be used when drawing
+ protected readonly Options Options;
+
+ ///
+ /// Initializes a new instance of the DrawModeBase class with the specified device context and options.
+ ///
+ /// The device context to be drawn on.
+ /// The options to be used when drawing.
+ internal DrawModeBase(IntPtr deviceContext, Options options)
+ {
+ DeviceContext = deviceContext;
+ Options = options;
+ }
+
+ ///
+ /// When overridden in a derived class, performs the drawing action.
+ ///
+ public abstract void Draw();
+ }
+}
diff --git a/src/TED/TED.DrawModes/OneTimeDrawMode.cs b/src/TED/TED.DrawModes/OneTimeDrawMode.cs
new file mode 100644
index 0000000..fff28bd
--- /dev/null
+++ b/src/TED/TED.DrawModes/OneTimeDrawMode.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Drawing;
+using System.Linq;
+using System.Windows.Forms;
+using TED.Program;
+using TED.Utils;
+
+namespace TED.DrawModes
+{
+ ///
+ /// Represents a drawing mode that operates once and does not update.
+ ///
+ internal class OneTimeDrawMode : DrawModeBase
+ {
+ ///
+ /// Initializes a new instance of the OneTimeDrawMode class with the specified device context and options.
+ ///
+ /// The device context to be drawn on.
+ /// The options to be used when drawing.
+ public OneTimeDrawMode(IntPtr deviceContext, Options options) : base(deviceContext, options)
+ {
+ }
+
+ ///
+ /// Draws the image and text on the given device context based on the options provided.
+ ///
+ public override void Draw()
+ {
+ // Calculate the luminance of the wallpaper
+ var wallpaperLuminance = ImageUtilities.CalculateWallpaperLuminance();
+ var primaryAreaRect = SystemUtilities.GetPrimaryScreenRect();
+
+ if (DeviceContext != IntPtr.Zero)
+ {
+ using (var graphics = Graphics.FromHdc(DeviceContext))
+ {
+ // Get the path of the image based on the luminance of the wallpaper
+ var imagePath = Options.GetImagePath(wallpaperLuminance);
+
+ // Set the text color based on the luminance of the wallpaper
+ var textColor = wallpaperLuminance > 0.5 ? Color.Black : Color.White;
+
+ using (var font = new Font(Options.FontName, Options.FontSize, FontStyle.Bold))
+ {
+ // Calculate the scaling factors
+ var scaleX = graphics.DpiX / 96.0f;
+ var scaleY = graphics.DpiY / 96.0f;
+
+ // Calculate the working area dimensions
+ var scaledWorkingAreaWidth = primaryAreaRect.X / scaleX;
+ var scaledWorkingAreaHeight = primaryAreaRect.Y / scaleY;
+
+ // Calculate the maximum width of all lines
+ var maxWidth = graphics.MeasureString(Options.Lines.Max(l => l), font).Width;
+
+ // Calculate the positions of the text and the image
+ var textX = scaledWorkingAreaWidth + Screen.PrimaryScreen.WorkingArea.Width - maxWidth - Options.PaddingHorizontal;
+ var textY = scaledWorkingAreaHeight + Screen.PrimaryScreen.WorkingArea.Height - Options.Lines.Sum(line => graphics.MeasureString(line, font).Height) - (Options.LineSpacing * (Options.Lines.Count - 1)) - Options.PaddingVertical;
+
+ if (!string.IsNullOrEmpty(imagePath))
+ {
+ using (var overlayImage = Image.FromFile(imagePath))
+ {
+ // Scale the image while maintaining its aspect ratio
+ ImageUtilities.ScaleImageAndMaintainAspectRatio(overlayImage.Width, overlayImage.Height, maxWidth, int.MaxValue, out int newWidth, out int newHeight);
+
+ var imageX = scaledWorkingAreaWidth + Screen.PrimaryScreen.WorkingArea.Width - newWidth - Options.PaddingHorizontal;
+ var imageY = scaledWorkingAreaHeight + Screen.PrimaryScreen.WorkingArea.Height - newHeight - Options.Lines.Sum(line => graphics.MeasureString(line, font).Height) - (Options.LineSpacing * (Options.Lines.Count)) - Options.PaddingVertical;
+
+ textX = imageX;
+ textY = imageY + newHeight + Options.LineSpacing;
+
+ // Draw the image
+ graphics.DrawImage(overlayImage, new RectangleF(imageX, imageY, newWidth, newHeight));
+ }
+ }
+
+ // Draw each line of text
+ for (var i = 0; i < Options.Lines.Count; i++)
+ {
+ var line = Options.Lines[i];
+
+ // Draw the line
+ graphics.DrawString(line, font, new SolidBrush(textColor), new PointF(textX, textY));
+
+ // Move the text cursor down to the next line
+ textY += graphics.MeasureString(line, font).Height;
+
+ // If there are more lines, add additional spacing
+ if (i < Options.Lines.Count)
+ {
+ textY += Options.LineSpacing;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/TED/TED.Program/Options.cs b/src/TED/TED.Program/Options.cs
new file mode 100644
index 0000000..38ff6ca
--- /dev/null
+++ b/src/TED/TED.Program/Options.cs
@@ -0,0 +1,91 @@
+using System.Collections.Generic;
+using TED.Utils;
+
+namespace TED.Program
+{
+ ///
+ /// Container for the various options utilized within TED.
+ ///
+ internal class Options
+ {
+ internal readonly int PaddingHorizontal;
+ internal readonly int PaddingVertical;
+ internal readonly int LineSpacing;
+ internal readonly int FontSize;
+ internal readonly string FontName;
+ internal readonly string ImagePath;
+ internal readonly string LightImagePath;
+ internal readonly string DarkImagePath;
+ internal readonly List Lines;
+ internal readonly bool Debug;
+ internal readonly bool AdaptiveImageMode;
+
+ ///
+ /// Gets default options
+ ///
+ private static Options? _default;
+ internal static Options Default
+ {
+ get
+ {
+ _default ??= new Options(
+ 10, 10, 8, 8, "Arial",
+ "", "", "",
+ new List()
+ {
+ Tokenizer.ReplaceTokens("USERNAME: @userName"),
+ Tokenizer.ReplaceTokens("DEVICE NAME: @machineName"),
+ Tokenizer.ReplaceTokens("OS: @os"),
+ },
+ false);
+
+ return _default;
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the Options class with the specified settings.
+ ///
+ internal Options(int paddingHorizontal, int paddingVertical,
+ int lineSpacing, int fontSize, string fontName,
+ string imagePath, string lightImagePath,
+ string darkImagePath, List lines,
+ bool debug)
+ {
+ PaddingHorizontal = paddingHorizontal;
+ PaddingVertical = paddingVertical;
+ LineSpacing = lineSpacing;
+ FontSize = fontSize;
+ FontName = fontName;
+ ImagePath = imagePath;
+ LightImagePath = lightImagePath;
+ DarkImagePath = darkImagePath;
+ Lines = lines;
+ Debug = debug;
+ AdaptiveImageMode = !string.IsNullOrEmpty(LightImagePath) && !string.IsNullOrEmpty(DarkImagePath);
+ }
+
+ ///
+ /// Determines the image path based on the wallpaper's luminance.
+ /// When in AdaptiveImageMode, chooses the light or dark image path based on the provided luminance.
+ ///
+ /// Note: AdaptiveImageMode requires both a light image and dark image to be set. If only one is provided, the regular image
+ /// path will be used as a fallback.
+ ///
+ /// The luminance of the wallpaper.
+ ///
+ /// The appropriate image path considering the luminance and the AdaptiveImageMode.
+ ///
+ internal string GetImagePath(double wallpaperLuminance)
+ {
+ if (AdaptiveImageMode && !string.IsNullOrEmpty(LightImagePath) && !string.IsNullOrEmpty(DarkImagePath))
+ {
+ return wallpaperLuminance > 0.5 ? DarkImagePath : LightImagePath;
+ }
+ else
+ {
+ return ImagePath;
+ }
+ }
+ }
+}
diff --git a/src/TED/TED.Program/Program.cs b/src/TED/TED.Program/Program.cs
new file mode 100644
index 0000000..7d71d09
--- /dev/null
+++ b/src/TED/TED.Program/Program.cs
@@ -0,0 +1,159 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net.Http;
+using TED.Utils;
+
+namespace TED.Program
+{
+ ///
+ /// The main class for the program.
+ ///
+ public class Program
+ {
+
+ private static Tagger? tagger;
+
+ ///
+ /// Application entry point
+ ///
+ /// An array of command-line arguments.
+ public static void Main(string[] args)
+ {
+ tagger = new Tagger(ParseArgsIntoOptions(args));
+ tagger.Tag();
+ }
+
+ ///
+ /// Parses the command-line arguments into an Options object.
+ ///
+ /// An array of command-line arguments.
+ /// An Options object that encapsulates application settings.
+ private static Options ParseArgsIntoOptions(string[] args)
+ {
+ var fontName = GetArgument(args, new string[] { "-font", "-f" }, Options.Default.FontName);
+ var imagePath = GetArgument(args, new string[] { "-image", "-i" }, Options.Default.ImagePath);
+ var darkImagePath = GetArgument(args, new string[] { "-darkimage", "-di" }, Options.Default.DarkImagePath);
+ var lightImagePath = GetArgument(args, new string[] { "-lightimage", "-li" }, Options.Default.LightImagePath);
+ var lines = Options.Default.Lines;
+
+ if (!bool.TryParse(GetArgument(args, new string[] { "-debug", "-d" }, Options.Default.Debug.ToString()), out bool debug))
+ {
+ debug = Options.Default.Debug;
+ }
+
+ if (!int.TryParse(GetArgument(args, new string[] { "-fontsize", "-fs" }, Options.Default.FontSize.ToString()), out int fontSize))
+ {
+ fontSize = Options.Default.FontSize;
+ }
+
+ if (!int.TryParse(GetArgument(args, new string[] { "-linespacing", "-ls" }, Options.Default.LineSpacing.ToString()), out int margin))
+ {
+ margin = Options.Default.LineSpacing;
+ }
+
+ if (!int.TryParse(GetArgument(args, new string[] { "-hpad", "-hp" }, Options.Default.PaddingHorizontal.ToString()), out int paddingHorizontal))
+ {
+ paddingHorizontal = Options.Default.PaddingHorizontal;
+ }
+
+ if (!int.TryParse(GetArgument(args, new string[] { "-vpad", "-vp" }, Options.Default.PaddingHorizontal.ToString()), out int paddingVertical))
+ {
+ paddingVertical = Options.Default.PaddingVertical;
+ }
+
+ if (args.Any(arg => arg.Contains("-line")))
+ {
+ lines.Clear();
+ }
+
+ for (int i = 0; i < args.Length; i++)
+ {
+ if (args[i] == "-line")
+ {
+
+ for (int j = i + 1; j < args.Length; j++)
+ {
+ if (args[j].StartsWith("-"))
+ {
+ break;
+ }
+
+ lines.Add(Tokenizer.ReplaceTokens(args[j]));
+ }
+ }
+ }
+
+ imagePath = string.IsNullOrEmpty(imagePath) ? imagePath : EnsureImageExists(imagePath);
+ lightImagePath = string.IsNullOrEmpty(lightImagePath) ? lightImagePath : EnsureImageExists(lightImagePath);
+ darkImagePath = string.IsNullOrEmpty(darkImagePath) ? darkImagePath : EnsureImageExists(darkImagePath);
+
+ return new Options(
+ paddingHorizontal,
+ paddingVertical,
+ margin,
+ fontSize,
+ fontName,
+ imagePath,
+ lightImagePath,
+ darkImagePath,
+ lines,
+ debug
+ );
+ }
+
+ ///
+ /// Validates the existence of the image file at the specified path.
+ ///
+ /// Path of the image file.
+ ///
+ /// The path of the image. If the image path points to a URL, the method attempts to download
+ /// the image, cache it, and then return the local path of the downloaded image.
+ ///
+ ///
+ /// Thrown if the image path points to a URL and the image fails to download.
+ ///
+ ///
+ /// Thrown if the image path does not point to a valid local file or URL.
+ ///
+ private static string EnsureImageExists(string imagePath)
+ {
+ if (!FileUtilities.PathIsLocalFile(imagePath))
+ {
+ if (FileUtilities.PathIsUrl(imagePath))
+ {
+ var path = string.Empty;
+
+ try
+ {
+ path = FileUtilities.DownloadAndCacheFileAsync(imagePath).GetAwaiter().GetResult();
+ }
+ catch
+ {
+ throw new HttpRequestException("ERROR: Failed to acquire image from provided URL");
+ }
+
+ return path;
+ }
+ else
+ {
+ throw new FileNotFoundException("ERROR: Image filepath or URL is invalid.");
+ }
+ }
+
+ return imagePath;
+ }
+
+ ///
+ /// Retrieves the value of a specific command-line argument.
+ ///
+ /// A collection of command-line arguments.
+ /// An array of options to match against the command-line arguments.
+ /// The default value to return if the option is not found.
+ /// The value of the specified command-line argument, or the default value if the option is not found.
+ private static string GetArgument(IEnumerable args, string[] options, string defaultValue) =>
+ args.SkipWhile(i => !options.Contains(i)).Skip(1).Take(1).DefaultIfEmpty(defaultValue).First();
+ }
+}
+
diff --git a/src/TED/TED.Program/Tagger.cs b/src/TED/TED.Program/Tagger.cs
new file mode 100644
index 0000000..a53bb73
--- /dev/null
+++ b/src/TED/TED.Program/Tagger.cs
@@ -0,0 +1,40 @@
+using TED.DrawModes;
+using TED.Utils;
+
+namespace TED.Program
+{
+ ///
+ /// Provides functionality for tagging windows in the system.
+ ///
+ internal class Tagger
+ {
+ ///
+ /// Provides functionality for tagging windows in the system.
+ ///
+ internal Options Options;
+
+ ///
+ /// Initializes a new instance of the class with the specified options.
+ ///
+ /// The options to be used while tagging.
+ public Tagger(Options options)
+ {
+ Options = options;
+
+ }
+
+ ///
+ /// Tags the window based on the given .
+ ///
+ public void Tag()
+ {
+ var progman = Win32Native.GetProgmanWindow();
+ Win32Native.ClearWindow(progman);
+ var workerWindow = Win32Native.CreateWorkerW(progman);
+ var deviceContext = Win32Native.GetWorkerWindowDeviceContext(workerWindow);
+ DrawModeBase drawer = new OneTimeDrawMode(deviceContext, Options);
+ drawer.Draw();
+ Win32Native.ReleaseWorkerWindowDeviceContext(workerWindow, deviceContext);
+ }
+ }
+}
diff --git a/src/TED/TED.Utils/FileUtilities.cs b/src/TED/TED.Utils/FileUtilities.cs
new file mode 100644
index 0000000..d9acb99
--- /dev/null
+++ b/src/TED/TED.Utils/FileUtilities.cs
@@ -0,0 +1,77 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Net.Http;
+using System.Threading.Tasks;
+
+namespace TED.Utils
+{
+ ///
+ /// Provides utility methods for working with files
+ ///
+ internal class FileUtilities
+ {
+ ///
+ /// Downloads a file from a specified URL and saves it to a cache.
+ /// If the file already exists in the cache, the function returns the path to the cached file.
+ /// If the file does not exist in the cache, the function downloads the file, saves it to the cache, and then returns the path to the cached file.
+ ///
+ /// The URL of the file to download.
+ /// A task that represents the asynchronous operation. The task result is the path to the downloaded (or cached) file.
+ public static async Task DownloadAndCacheFileAsync(string url)
+ {
+ using (var client = new HttpClient()
+ {
+ Timeout = TimeSpan.FromSeconds(10)
+ })
+ {
+ var response = await client.GetAsync(url);
+ response.EnsureSuccessStatusCode();
+
+ var etag = response.Headers.ETag?.Tag.Replace("\"", string.Empty) ?? "untagged";
+ if (!Directory.Exists(Path.Combine(Path.GetTempPath(), "TED")))
+ {
+ Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), "TED"));
+ }
+ var path = Path.Combine(Path.GetTempPath(), "TED", $"{etag}.png");
+
+ if (File.Exists(path))
+ {
+ return path;
+ }
+
+ var filesToDelete = Directory.GetFiles(Path.Combine(Path.GetTempPath(), "TED"), "*.png")
+ .Where(filePath => Path.GetFileNameWithoutExtension(filePath) != etag);
+ foreach (var fileToDelete in filesToDelete)
+ {
+ File.Delete(fileToDelete);
+ }
+
+ using (var stream = await response.Content.ReadAsStreamAsync())
+ {
+ using (var fileStream = new FileStream(path, FileMode.CreateNew))
+ {
+ await stream.CopyToAsync(fileStream);
+ return path;
+ }
+ }
+ }
+ }
+
+ ///
+ /// Downloads a file from a specified URL and saves it to a cache.
+ /// If the file already exists in the cache, the function returns the path to the cached file.
+ /// If the file does not exist in the cache, the function downloads the file, saves it to the cache, and then returns the path to the cached file.
+ ///
+ /// The URL of the file to download.
+ /// A task that represents the asynchronous operation. The task result is the path to the downloaded (or cached) file.
+ public static bool PathIsLocalFile(string path) => Path.IsPathFullyQualified(path) && File.Exists(path);
+
+ ///
+ /// Determines whether a specified path represents a URL.
+ ///
+ /// The path to check.
+ /// True if the path represents a URL; otherwise, false.
+ public static bool PathIsUrl(string path) => !Path.IsPathFullyQualified(path) && Uri.TryCreate(path, UriKind.Absolute, out _);
+ }
+}
diff --git a/src/TED/TED.Utils/ImageUtilities.cs b/src/TED/TED.Utils/ImageUtilities.cs
new file mode 100644
index 0000000..610fe4c
--- /dev/null
+++ b/src/TED/TED.Utils/ImageUtilities.cs
@@ -0,0 +1,88 @@
+using System;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.Runtime.InteropServices;
+
+namespace TED.Utils
+{
+ ///
+ /// Provides utility methods for working with images
+ ///
+ public class ImageUtilities
+ {
+ ///
+ /// Scales the image dimensions to fit within the specified maximum width and height, maintaining the original aspect ratio.
+ ///
+ /// The original image width.
+ /// The original image height.
+ /// The maximum allowable width for the scaled image.
+ /// The maximum allowable height for the scaled image.
+ /// The calculated width for the scaled image.
+ /// The calculated height for the scaled image.
+ public static void ScaleImageAndMaintainAspectRatio(int srcWidth, int srcHeight, float maxWidth, float maxHeight, out int destWidth, out int destHeight)
+ {
+ var ratioX = (double)maxWidth / srcWidth;
+ var ratioY = (double)maxHeight / srcHeight;
+ var ratio = Math.Min(ratioX, ratioY);
+
+ destWidth = (int)(srcWidth * ratio);
+ destHeight = (int)(srcHeight * ratio);
+ }
+
+ ///
+ /// Calculates the perceived luminance of the current desktop wallpaper.
+ ///
+ /// A value representing the calculated perceived luminance of the wallpaper. Returns 0.0 if the wallpaper path is not found.
+ public static double CalculateWallpaperLuminance()
+ {
+ // Get the wallpaper path from the registry
+ string wallpaperPath = SystemUtilities.GetWallpaperPathFromRegistry();
+ double luminance = 0.0;
+
+ if (!string.IsNullOrEmpty(wallpaperPath))
+ {
+ using (var bmp = new Bitmap(wallpaperPath))
+ {
+ luminance = CalculateImageLuminance01(bmp);
+ }
+ }
+
+ return luminance;
+ }
+
+ ///
+ /// Calculates the perceived luminance of a Bitmap image.
+ ///
+ /// The Bitmap image to calculate the luminance for.
+ /// A value representing the calculated luminance of the image.
+ public static double CalculateImageLuminance01(Bitmap bm)
+ {
+ var lum = 0.0;
+ var width = bm.Width;
+ var height = bm.Height;
+ var bytesPerPixel = Image.GetPixelFormatSize(bm.PixelFormat) / 8;
+
+ var srcData = bm.LockBits(new Rectangle(0, 0, bm.Width, bm.Height), ImageLockMode.ReadOnly, bm.PixelFormat);
+ var stride = srcData.Stride;
+ IntPtr scan0 = srcData.Scan0;
+
+ // Luminance (standard, objective): (0.2126*R) + (0.7152*G) + (0.0722*B)
+ // Luminance (perceived option 1): (0.299*R + 0.587*G + 0.114*B)
+ // Luminance (perceived option 2, slower to calculate): sqrt( 0.299*R^2 + 0.587*G^2 + 0.114*B^2 )
+
+ for (var y = 0; y < height; y++)
+ {
+ for (var x = 0; x < width; x++)
+ {
+ var idx = y * stride + x * bytesPerPixel;
+ lum += (0.299 * Marshal.ReadByte(scan0, idx + 2) + 0.587 * Marshal.ReadByte(scan0, idx + 1) + 0.114 * Marshal.ReadByte(scan0, idx)) / 255.0;
+ }
+ }
+
+ bm.UnlockBits(srcData);
+ var normalized = lum / (width * height);
+ return normalized;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/TED/TED.Utils/SystemUtilities.cs b/src/TED/TED.Utils/SystemUtilities.cs
new file mode 100644
index 0000000..5e043df
--- /dev/null
+++ b/src/TED/TED.Utils/SystemUtilities.cs
@@ -0,0 +1,45 @@
+using Microsoft.Win32;
+using System.Drawing;
+using System.Windows.Forms;
+
+namespace TED.Utils
+{
+ ///
+ /// Provides utility functions related to system configurations.
+ ///
+ public class SystemUtilities
+ {
+ ///
+ /// Get's the Rectangle of the Primary Screen in Virtual Screen space.
+ ///
+ /// The Rectangle of the Primary Screen in Virtual Screen space.
+ public static Rectangle GetPrimaryScreenRect()
+ {
+ return new Rectangle(
+ Screen.PrimaryScreen.Bounds.X - SystemInformation.VirtualScreen.Left,
+ Screen.PrimaryScreen.Bounds.Y - SystemInformation.VirtualScreen.Top,
+ Screen.PrimaryScreen.WorkingArea.Width,
+ Screen.PrimaryScreen.WorkingArea.Height
+ );
+ }
+
+ ///
+ /// Fetches the current wallpaper path from the system registry.
+ ///
+ /// The full path of the current desktop wallpaper.
+ public static string GetWallpaperPathFromRegistry()
+ {
+ string result = string.Empty;
+ using (var key = Registry.CurrentUser.OpenSubKey(@"Control Panel\Desktop"))
+ {
+ if (key != null)
+ {
+ result = key.GetValue("Wallpaper").ToString();
+ key.Close();
+ }
+ }
+
+ return result;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/TED/TED.Utils/Tokenizer.cs b/src/TED/TED.Utils/Tokenizer.cs
new file mode 100644
index 0000000..5a2af27
--- /dev/null
+++ b/src/TED/TED.Utils/Tokenizer.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using System.Security.Principal;
+
+namespace TED.Utils
+{
+ ///
+ /// Provides functionality for replacing tokens in a string.
+ ///
+ internal class Tokenizer
+ {
+ ///
+ /// A dictionary that maps token names to their corresponding values.
+ ///
+ static Dictionary> TokenLookup = new Dictionary>()
+ {
+ { "@userName", () => WindowsIdentity.GetCurrent().Name },
+ { "@machineName", () => Environment.MachineName },
+ { "@os", () => Environment.OSVersion.ToString() },
+ };
+
+ ///
+ /// Replaces tokens in the input string with their corresponding values.
+ ///
+ /// The string that may contain tokens to be replaced.
+ /// The input string with all tokens replaced by their corresponding values.
+ public static string ReplaceTokens(string input)
+ {
+ foreach (var kvp in TokenLookup)
+ {
+ var token = kvp.Key;
+ var value = kvp.Value();
+
+ input = input.Replace(token, value);
+ }
+
+ return input;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/TED/TED.Utils/Win32Native.cs b/src/TED/TED.Utils/Win32Native.cs
new file mode 100644
index 0000000..5af941b
--- /dev/null
+++ b/src/TED/TED.Utils/Win32Native.cs
@@ -0,0 +1,169 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace TED.Utils
+{
+ ///
+ ///
+ /// The information and methods stored within this class are largely attributed to
+ /// It has been a fantastic resource for tinkering with and learning how native methods work.
+ ///
+ ///
+
+ public class Win32Native
+ {
+ public delegate bool EnumWindowsProc(IntPtr hwnd, IntPtr lParam);
+
+ [Flags()]
+ public enum DeviceContextValues : uint
+ {
+ /// DCX_WINDOW: Returns a DC that corresponds to the window rectangle rather
+ /// than the client rectangle.
+ Window = 0x00000001,
+
+ /// DCX_CACHE: Returns a DC from the cache, rather than the OWNDC or CLASSDC
+ /// window. Essentially overrides CS_OWNDC and CS_CLASSDC.
+ Cache = 0x00000002,
+
+ /// DCX_NORESETATTRS: Does not reset the attributes of this DC to the
+ /// default attributes when this DC is released.
+ NoResetAttrs = 0x00000004,
+
+ /// DCX_CLIPCHILDREN: Excludes the visible regions of all child windows
+ /// below the window identified by hWnd.
+ ClipChildren = 0x00000008,
+
+ /// DCX_CLIPSIBLINGS: Excludes the visible regions of all sibling windows
+ /// above the window identified by hWnd.
+ ClipSiblings = 0x00000010,
+
+ /// DCX_PARENTCLIP: Uses the visible region of the parent window. The
+ /// parent's WS_CLIPCHILDREN and CS_PARENTDC style bits are ignored. The origin is
+ /// set to the upper-left corner of the window identified by hWnd.
+ ParentClip = 0x00000020,
+
+ /// DCX_EXCLUDERGN: The clipping region identified by hrgnClip is excluded
+ /// from the visible region of the returned DC.
+ ExcludeRgn = 0x00000040,
+
+ /// DCX_INTERSECTRGN: The clipping region identified by hrgnClip is
+ /// intersected with the visible region of the returned DC.
+ IntersectRgn = 0x00000080,
+
+ /// DCX_EXCLUDEUPDATE: Unknown...Undocumented
+ ExcludeUpdate = 0x00000100,
+
+ /// DCX_INTERSECTUPDATE: Unknown...Undocumented
+ IntersectUpdate = 0x00000200,
+
+ /// DCX_LOCKWINDOWUPDATE: Allows drawing even if there is a LockWindowUpdate
+ /// call in effect that would otherwise exclude this window. Used for drawing during
+ /// tracking.
+ LockWindowUpdate = 0x00000400,
+
+ /// DCX_USESTYLE: Undocumented, something related to WM_NCPAINT message.
+ UseStyle = 0x00010000,
+
+ /// DCX_VALIDATE When specified with DCX_INTERSECTUPDATE, causes the DC to
+ /// be completely validated. Using this function with both DCX_INTERSECTUPDATE and
+ /// DCX_VALIDATE is identical to using the BeginPaint function.
+ Validate = 0x00200000,
+ }
+
+ [Flags]
+ public enum SendMessageTimeoutFlags : uint
+ {
+ SMTO_NORMAL = 0x0,
+ SMTO_BLOCK = 0x1,
+ SMTO_ABORTIFHUNG = 0x2,
+ SMTO_NOTIMEOUTIFNOTHUNG = 0x8,
+ SMTO_ERRORONEXIT = 0x20
+ }
+
+ [DllImport("user32.dll")]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ public static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
+
+ [DllImport("user32.dll", SetLastError = true)]
+ public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
+
+ //Keep
+ [DllImport("user32.dll", SetLastError = true)]
+ public static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr childAfter, string className, IntPtr windowTitle);
+
+ [DllImport("user32.dll")]
+ public static extern IntPtr GetDCEx(IntPtr hWnd, IntPtr hrgnClip, DeviceContextValues flags);
+
+ [DllImport("user32.dll", EntryPoint = "ReleaseDC")]
+ public static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDc);
+
+ [DllImport("user32.dll", CharSet = CharSet.Auto)]
+ public static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, int wParam, IntPtr lParam);
+
+ [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
+ public static extern IntPtr SendMessageTimeout(IntPtr windowHandle, uint Msg, IntPtr wParam, IntPtr lParam, SendMessageTimeoutFlags flags, uint timeout, out IntPtr result);
+
+ public static IntPtr GetProgmanWindow()
+ {
+ // We're going to render the watermark over the wallpaper, but behind desktop icons.
+ // We can do so with some trickery using the Progman window
+ // We're going to spawn a WorkerW window behind the desktop icons, and then render our watermark to that
+ return FindWindow("Progman", null);
+ }
+
+ public static void ClearWindow(IntPtr windowHandle)
+ {
+ // The below command clears any existing watermark we've drawn
+ // The Thread.Sleep() is necessary because the command has a minor delay to it
+ // Without it, the clear will end up running after we've generated a watermark,
+ // removing our newly generated watermark
+ SendMessage(windowHandle, 0x0034, 4, IntPtr.Zero);
+ //Thread.Sleep(2000);
+ }
+
+ public static IntPtr CreateWorkerW(IntPtr progman)
+ {
+ IntPtr result = IntPtr.Zero;
+
+ // Send 0x052C to Progman. This message directs Progman to spawn a
+ // WorkerW behind the desktop icons. If it is already there, nothing
+ // happens.
+ SendMessageTimeout(progman,
+ 0x052C,
+ new IntPtr(0),
+ IntPtr.Zero,
+ SendMessageTimeoutFlags.SMTO_NORMAL,
+ 1000,
+ out result);
+
+ IntPtr workerw = IntPtr.Zero;
+
+ // We enumerate all Windows, until we find one, that has the SHELLDLL_DefView
+ // as a child. These should be our desktop icons.
+ // If we found that window, we take its next sibling and assign it to workerw.
+ EnumWindows(new EnumWindowsProc((tophandle, topparamhandle) =>
+ {
+ IntPtr p = FindWindowEx(tophandle,
+ IntPtr.Zero,
+ "SHELLDLL_DefView",
+ IntPtr.Zero);
+
+ if (p != IntPtr.Zero)
+ {
+ // Gets the WorkerW Window after the current one.
+ workerw = FindWindowEx(IntPtr.Zero,
+ tophandle,
+ "WorkerW",
+ IntPtr.Zero);
+ }
+
+ return true;
+ }), IntPtr.Zero);
+
+ return workerw;
+ }
+
+ public static IntPtr GetWorkerWindowDeviceContext(IntPtr workerw) => GetDCEx(workerw, IntPtr.Zero, (DeviceContextValues)0x403);
+ public static void ReleaseWorkerWindowDeviceContext(IntPtr workerw, IntPtr deviceContext) => ReleaseDC(workerw, deviceContext);
+ }
+}
\ No newline at end of file
diff --git a/src/TED/TED.csproj b/src/TED/TED.csproj
new file mode 100644
index 0000000..ff02c98
--- /dev/null
+++ b/src/TED/TED.csproj
@@ -0,0 +1,40 @@
+
+
+ true
+ WinExe
+ net7.0-windows10.0.22621.0
+ true
+ true
+ win-x64
+ true
+ true
+ Link
+ TED
+ enable
+ Debug;Release;Open
+ 1.0.4.1-RC1
+ Prerelease
+ true
+ True
+ app.manifest
+ true
+ Health IT
+ TED - Tag Every Desktop
+ Health IT
+
+
+ True
+
+
+ False
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/TED/app.manifest b/src/TED/app.manifest
new file mode 100644
index 0000000..1aa0b81
--- /dev/null
+++ b/src/TED/app.manifest
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ true
+
+
+