diff --git a/Flyoobe.ToolSpot/Flyoobe.Spot.sln b/Flyoobe.ToolSpot/Flyoobe.Spot.sln new file mode 100644 index 0000000..cc097f1 --- /dev/null +++ b/Flyoobe.ToolSpot/Flyoobe.Spot.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.36414.22 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flyoobe.Spot", "Flyoobe.ToolSpot\Flyoobe.Spot.csproj", "{1133824D-6F42-48D4-8F18-A93AB7403DB7}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1133824D-6F42-48D4-8F18-A93AB7403DB7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1133824D-6F42-48D4-8F18-A93AB7403DB7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1133824D-6F42-48D4-8F18-A93AB7403DB7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1133824D-6F42-48D4-8F18-A93AB7403DB7}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {B22B36E3-C594-46AF-AF57-3C03B2F97541} + EndGlobalSection +EndGlobal diff --git a/Flyoobe.ToolSpot/Flyoobe.ToolSpot/App.config b/Flyoobe.ToolSpot/Flyoobe.ToolSpot/App.config new file mode 100644 index 0000000..aee9adf --- /dev/null +++ b/Flyoobe.ToolSpot/Flyoobe.ToolSpot/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Flyoobe.ToolSpot/Flyoobe.ToolSpot/Flyoobe.Spot.csproj b/Flyoobe.ToolSpot/Flyoobe.ToolSpot/Flyoobe.Spot.csproj new file mode 100644 index 0000000..b5eda88 --- /dev/null +++ b/Flyoobe.ToolSpot/Flyoobe.ToolSpot/Flyoobe.Spot.csproj @@ -0,0 +1,88 @@ + + + + + Debug + AnyCPU + {1133824D-6F42-48D4-8F18-A93AB7403DB7} + WinExe + Flyoobe.Spot + spot + v4.8.1 + 512 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + app.manifest + + + + + + + + + + + + + + + + + Form + + + MainForm.cs + + + + + MainForm.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + True + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + \ No newline at end of file diff --git a/Flyoobe.ToolSpot/Flyoobe.ToolSpot/MainForm.Designer.cs b/Flyoobe.ToolSpot/Flyoobe.ToolSpot/MainForm.Designer.cs new file mode 100644 index 0000000..5c1d62a --- /dev/null +++ b/Flyoobe.ToolSpot/Flyoobe.ToolSpot/MainForm.Designer.cs @@ -0,0 +1,105 @@ +namespace Flyoobe.ToolSpot +{ + partial class MainForm + { + /// + /// Erforderliche Designervariable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Verwendete Ressourcen bereinigen. + /// + /// True, wenn verwaltete Ressourcen gelöscht werden sollen; andernfalls False. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Vom Windows Form-Designer generierter Code + + /// + /// Erforderliche Methode für die Designerunterstützung. + /// Der Inhalt der Methode darf nicht mit dem Code-Editor geändert werden. + /// + private void InitializeComponent() + { + this.listResults = new System.Windows.Forms.ListBox(); + this.textSearch = new System.Windows.Forms.TextBox(); + this.labelStatus = new System.Windows.Forms.Label(); + this.SuspendLayout(); + // + // listResults + // + this.listResults.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.listResults.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(249)))), ((int)(((byte)(243)))), ((int)(((byte)(240))))); + this.listResults.BorderStyle = System.Windows.Forms.BorderStyle.None; + this.listResults.Font = new System.Drawing.Font("Segoe UI Variable Small Semilig", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.listResults.FormattingEnabled = true; + this.listResults.ItemHeight = 17; + this.listResults.Location = new System.Drawing.Point(25, 72); + this.listResults.Name = "listResults"; + this.listResults.Size = new System.Drawing.Size(504, 170); + this.listResults.TabIndex = 5; + this.listResults.TabStop = false; + this.listResults.DoubleClick += new System.EventHandler(this.listResults_DoubleClick); + this.listResults.KeyDown += new System.Windows.Forms.KeyEventHandler(this.listResults_KeyDown); + // + // textSearch + // + this.textSearch.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.textSearch.Font = new System.Drawing.Font("Segoe UI Variable Small Semilig", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.textSearch.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64))))); + this.textSearch.Location = new System.Drawing.Point(25, 26); + this.textSearch.Name = "textSearch"; + this.textSearch.Size = new System.Drawing.Size(504, 29); + this.textSearch.TabIndex = 1; + this.textSearch.Text = "Search"; + this.textSearch.Click += new System.EventHandler(this.textSearch_Click); + this.textSearch.TextChanged += new System.EventHandler(this.textSearch_TextChanged); + // + // labelStatus + // + this.labelStatus.Dock = System.Windows.Forms.DockStyle.Bottom; + this.labelStatus.Font = new System.Drawing.Font("Segoe UI Variable Small Semilig", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.labelStatus.Location = new System.Drawing.Point(0, 251); + this.labelStatus.Name = "labelStatus"; + this.labelStatus.Size = new System.Drawing.Size(557, 21); + this.labelStatus.TabIndex = 2; + // + // MainForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(242)))), ((int)(((byte)(244)))), ((int)(((byte)(244))))); + this.ClientSize = new System.Drawing.Size(557, 272); + this.Controls.Add(this.labelStatus); + this.Controls.Add(this.textSearch); + this.Controls.Add(this.listResults); + this.Name = "MainForm"; + this.ShowIcon = false; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; + this.Text = "Spot"; + this.Load += new System.EventHandler(this.Form1_Load); + this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.Form1_KeyDown); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.ListBox listResults; + private System.Windows.Forms.TextBox textSearch; + private System.Windows.Forms.Label labelStatus; + } +} + diff --git a/Flyoobe.ToolSpot/Flyoobe.ToolSpot/MainForm.cs b/Flyoobe.ToolSpot/Flyoobe.ToolSpot/MainForm.cs new file mode 100644 index 0000000..87a1a5f --- /dev/null +++ b/Flyoobe.ToolSpot/Flyoobe.ToolSpot/MainForm.cs @@ -0,0 +1,209 @@ +/// This tool is part of my Flyoobe app + +using System; +using System.Collections.Generic; +using System.Data; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Windows.Forms; + +namespace Flyoobe.ToolSpot +{ + + + public partial class MainForm : Form + + { // Path of the text DB + public string DatabasePath { get; set; } = + Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "spot.txt"); + + // In-memory list of all tools + private readonly List _all = new List(); + + public MainForm() + { + InitializeComponent(); + } + + private void Form1_Load(object sender, EventArgs e) + { + LoadDatabase(); + //ApplyFilter(); // show all initially + textSearch.Focus(); + } + + private void SetStatus(string text) + { + if (labelStatus != null) labelStatus.Text = text; + } + + private void Form1_KeyDown(object sender, KeyEventArgs e) + { + if (e.KeyCode == Keys.Escape) + { + // close it + var form = this.FindForm(); + form?.Close(); + } + } + + private void listResults_KeyDown(object sender, KeyEventArgs e) + { + if (e.KeyCode == Keys.Enter) { LaunchSelected(); e.Handled = true; } + } + + private void listResults_DoubleClick(object sender, EventArgs e) => LaunchSelected(); + + private void textSearch_TextChanged(object sender, EventArgs e) => ApplyFilter(); + + /// + /// Reads toolspot.txt (Name;Command). Ignores empty and comment lines. + /// + private void LoadDatabase() + { + _all.Clear(); + + if (!File.Exists(DatabasePath)) + { + SetStatus($"Database not found: {DatabasePath}"); + return; + } + + foreach (var line in File.ReadAllLines(DatabasePath)) + { + var trimmed = line.Trim(); + if (string.IsNullOrEmpty(trimmed)) continue; + if (trimmed.StartsWith("#")) continue; + + var item = ParseLine(trimmed); + if (item != null) _all.Add(item); + } + + SetStatus($"Loaded {_all.Count} tools."); + } + + /// + /// Parses "Name;Command" lines. Returns null if malformed. + /// + private ToolItem ParseLine(string line) + { + var parts = line.Split(new[] { ';' }, 2); + if (parts.Length != 2) return null; + + var name = parts[0].Trim(); + var cmd = parts[1].Trim(); + if (name.Length == 0 || cmd.Length == 0) return null; + + return new ToolItem(name, cmd); + } + + /// + /// Filters list by substring (case-insensitive) from textSearch. + /// + private void ApplyFilter() + { + var q = (textSearch.Text ?? string.Empty).Trim().ToLower(); + var list = string.IsNullOrEmpty(q) + ? _all + : _all.Where(t => t.Name.ToLower().Contains(q)).ToList(); + + listResults.BeginUpdate(); + listResults.Items.Clear(); + foreach (var t in list) listResults.Items.Add(t); + listResults.EndUpdate(); + + if (listResults.Items.Count > 0) listResults.SelectedIndex = 0; + SetStatus(listResults.Items.Count > 0 ? $"{listResults.Items.Count} result(s)" : "No results"); + } + + /// + /// Launches the selected tool's command. Supports URIs, .msc/.cpl, exe + args. + /// + private void LaunchSelected() + { + if (!(listResults.SelectedItem is ToolItem t)) return; + + try + { + // Split command into FileName + Arguments (handles quoted paths) + SplitCommand(t.Command, out var fileName, out var args); + + var psi = new ProcessStartInfo + { + FileName = fileName, + Arguments = args, + UseShellExecute = true // required for ms-settings:, .msc/.cpl + }; + Process.Start(psi); + } + catch (Exception ex) + { + MessageBox.Show("Could not launch: " + ex.Message, "Quick Tools", + MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + /// + /// Splits a command line into executable and arguments. + /// Examples: + /// notepad.exe + /// "C:\Path With Spaces\app.exe" --flag + /// chrome.exe --settings + /// For URIs / shell verbs (e.g., ms-settings:...), no split is needed. + /// + private void SplitCommand(string command, out string fileName, out string args) + { + command = command.Trim(); + + // If it's a URI-like command (ms-settings:, http:, etc.), pass as-is. + if (command.Contains(":") && !command.Contains("\\")) + { + fileName = command; + args = string.Empty; + return; + } + + // Quoted path case: "C:\Program Files\App\app.exe" arg1 arg2 + if (command.StartsWith("\"")) + { + int end = command.IndexOf('"', 1); + if (end > 0) + { + fileName = command.Substring(1, end - 1); + args = command.Substring(end + 1).TrimStart(); + return; + } + } + + // Unquoted: split on first space + int space = command.IndexOf(' '); + if (space > 0) + { + fileName = command.Substring(0, space); + args = command.Substring(space + 1); + } + else + { + fileName = command; + args = string.Empty; + } + } + + private class ToolItem + { + public string Name { get; } + public string Command { get; } + + public ToolItem(string name, string command) + { Name = name; Command = command; } + + public override string ToString() => Name; // shown in ListBox + } + + private void textSearch_Click(object sender, EventArgs e) + { + textSearch.Clear(); + } + } +} \ No newline at end of file diff --git a/Flyoobe.ToolSpot/Flyoobe.ToolSpot/MainForm.resx b/Flyoobe.ToolSpot/Flyoobe.ToolSpot/MainForm.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/Flyoobe.ToolSpot/Flyoobe.ToolSpot/MainForm.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Flyoobe.ToolSpot/Flyoobe.ToolSpot/Program.cs b/Flyoobe.ToolSpot/Flyoobe.ToolSpot/Program.cs new file mode 100644 index 0000000..71b863c --- /dev/null +++ b/Flyoobe.ToolSpot/Flyoobe.ToolSpot/Program.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace Flyoobe.ToolSpot +{ + internal static class Program + { + /// + /// Der Haupteinstiegspunkt für die Anwendung. + /// + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new MainForm()); + } + } +} diff --git a/Flyoobe.ToolSpot/Flyoobe.ToolSpot/Properties/AssemblyInfo.cs b/Flyoobe.ToolSpot/Flyoobe.ToolSpot/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..c2e62bb --- /dev/null +++ b/Flyoobe.ToolSpot/Flyoobe.ToolSpot/Properties/AssemblyInfo.cs @@ -0,0 +1,33 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// Allgemeine Informationen über eine Assembly werden über die folgenden +// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, +// die einer Assembly zugeordnet sind. +[assembly: AssemblyTitle("Spot")] +[assembly: AssemblyDescription("Flyoobe Spot")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Builtbybel")] +[assembly: AssemblyProduct("Flyoobe Spot")] +[assembly: AssemblyCopyright("Copyright © 2025")] +[assembly: AssemblyTrademark("A Belim app creation")] +[assembly: AssemblyCulture("")] + +// Durch Festlegen von ComVisible auf FALSE werden die Typen in dieser Assembly +// für COM-Komponenten unsichtbar. Wenn Sie auf einen Typ in dieser Assembly von +// COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen. +[assembly: ComVisible(false)] + +// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird +[assembly: Guid("1133824d-6f42-48d4-8f18-a93ab7403db7")] + +// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: +// +// Hauptversion +// Nebenversion +// Buildnummer +// Revision +// +[assembly: AssemblyVersion("0.2.0.0")] +[assembly: AssemblyFileVersion("0.2.0.0")] diff --git a/Flyoobe.ToolSpot/Flyoobe.ToolSpot/Properties/Resources.Designer.cs b/Flyoobe.ToolSpot/Flyoobe.ToolSpot/Properties/Resources.Designer.cs new file mode 100644 index 0000000..b8a985c --- /dev/null +++ b/Flyoobe.ToolSpot/Flyoobe.ToolSpot/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// Dieser Code wurde von einem Tool generiert. +// Laufzeitversion:4.0.30319.42000 +// +// Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn +// der Code erneut generiert wird. +// +//------------------------------------------------------------------------------ + +namespace Flyoobe.Spot.Properties { + using System; + + + /// + /// Eine stark typisierte Ressourcenklasse zum Suchen von lokalisierten Zeichenfolgen usw. + /// + // Diese Klasse wurde von der StronglyTypedResourceBuilder automatisch generiert + // -Klasse über ein Tool wie ResGen oder Visual Studio automatisch generiert. + // Um einen Member hinzuzufügen oder zu entfernen, bearbeiten Sie die .ResX-Datei und führen dann ResGen + // mit der /str-Option erneut aus, oder Sie erstellen Ihr VS-Projekt neu. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Gibt die zwischengespeicherte ResourceManager-Instanz zurück, die von dieser Klasse verwendet wird. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Flyoobe.Spot.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Überschreibt die CurrentUICulture-Eigenschaft des aktuellen Threads für alle + /// Ressourcenzuordnungen, die diese stark typisierte Ressourcenklasse verwenden. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/Flyoobe.ToolSpot/Flyoobe.ToolSpot/Properties/Resources.resx b/Flyoobe.ToolSpot/Flyoobe.ToolSpot/Properties/Resources.resx new file mode 100644 index 0000000..af7dbeb --- /dev/null +++ b/Flyoobe.ToolSpot/Flyoobe.ToolSpot/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Flyoobe.ToolSpot/Flyoobe.ToolSpot/Properties/Settings.Designer.cs b/Flyoobe.ToolSpot/Flyoobe.ToolSpot/Properties/Settings.Designer.cs new file mode 100644 index 0000000..6ca8d3d --- /dev/null +++ b/Flyoobe.ToolSpot/Flyoobe.ToolSpot/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// Dieser Code wurde von einem Tool generiert. +// Laufzeitversion:4.0.30319.42000 +// +// Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn +// der Code erneut generiert wird. +// +//------------------------------------------------------------------------------ + +namespace Flyoobe.Spot.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.14.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/Flyoobe.ToolSpot/Flyoobe.ToolSpot/Properties/Settings.settings b/Flyoobe.ToolSpot/Flyoobe.ToolSpot/Properties/Settings.settings new file mode 100644 index 0000000..3964565 --- /dev/null +++ b/Flyoobe.ToolSpot/Flyoobe.ToolSpot/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Flyoobe.ToolSpot/Flyoobe.ToolSpot/app.manifest b/Flyoobe.ToolSpot/Flyoobe.ToolSpot/app.manifest new file mode 100644 index 0000000..f8a144b --- /dev/null +++ b/Flyoobe.ToolSpot/Flyoobe.ToolSpot/app.manifest @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + true + + + + + + + + diff --git a/Flyoobe/AboutForm.cs b/Flyoobe/AboutForm.cs index 3202585..008a570 100644 --- a/Flyoobe/AboutForm.cs +++ b/Flyoobe/AboutForm.cs @@ -180,7 +180,8 @@ namespace Flyoobe // Update label text linkAppVersion.Text = text; - // If clickable, show bold & underline and make cursor a hand + // If clickable, + // show bold ; underline ; make cursor a hand if (clickable) { linkAppVersion.Font = new Font(linkAppVersion.Font, FontStyle.Bold | FontStyle.Underline); diff --git a/Flyoobe/Features/FeatureLoader.cs b/Flyoobe/Features/FeatureLoader.cs index 3efb353..7970f2e 100644 --- a/Flyoobe/Features/FeatureLoader.cs +++ b/Flyoobe/Features/FeatureLoader.cs @@ -1,6 +1,5 @@ using Flyoobe; // for FeatureNode using Settings.Ads; -using Settings.AI; using Settings.Edge; using Settings.Gaming; using Settings.Issues; @@ -118,16 +117,6 @@ namespace Features new FeatureNode(new WelcomeExperienceAds()) } }, - - new FeatureNode("AI") - { - Children = new List - { - new FeatureNode(new CopilotTaskbar()), - new FeatureNode(new Recall()), - new FeatureNode(new ClickToDo()) - } - } }; } } diff --git a/Flyoobe/InstallView/BackupDriversProvider.cs b/Flyoobe/InstallView/BackupDriversProvider.cs index bf5aa85..b5fa785 100644 --- a/Flyoobe/InstallView/BackupDriversProvider.cs +++ b/Flyoobe/InstallView/BackupDriversProvider.cs @@ -5,13 +5,19 @@ using System.Windows.Forms; namespace Flyoobe { - // Native provider that exports all installed drivers to C:\DriversBackup using: - // pnputil.exe /export-driver * C:\DriversBackup + // Provider that exports all installed drivers into a fixed subfolder named "DriversBackup" + // INSIDE the parent folder chosen by the user, using: + // pnputil.exe /export-driver * \DriversBackup + // Behavior: + // - Prompts user to choose a parent folder (defaults to C:\). + // - Always creates/uses "\DriversBackup". + // - Runs pnputil elevated. + // - Shows a completion summary with INF package count. public sealed class BackupDriversProvider : IInstallProvider { public string Id => "drivers-backup"; - public string DisplayName => "Backup installed drivers (exports to C:\\DriversBackup)"; + public string DisplayName => "Backup installed drivers (choose folder)"; public string HomepageUrl => null; public string DirectDownloadUrl => null; public string[] ExactExeNames => new[] { "pnputil.exe" }; @@ -20,18 +26,48 @@ namespace Flyoobe public bool IsExternalTool => false; public string Hint => - "Exports all currently installed device drivers (INF + binaries) to C:\\DriversBackup. " + - "After a clean install, point Device Manager to this folder to restore drivers even without internet."; + "Exports all currently installed device drivers (INF + binaries) to a folder you choose. " + + "After a clean install, point Device Manager to that folder to restore drivers even without internet."; public string ShowOptionsAndBuildArgs(IWin32Window owner, LastSelections last) { - const string target = @"C:\DriversBackup"; + // Default suggestion for the parent, the actual export target will be \DriversBackup + var defaultParent = @"C:\"; + string parentPath; + // pick the PARENT folder where the 'DriversBackup' subfolder will be created + using (var fbd = new FolderBrowserDialog + { + Description = "Choose the PARENT folder. A 'DriversBackup' subfolder will be created inside it.", + ShowNewFolderButton = true, + SelectedPath = defaultParent + }) + { + var dlgResult = fbd.ShowDialog(owner); + if (dlgResult != DialogResult.OK || string.IsNullOrWhiteSpace(fbd.SelectedPath)) + return null; //user cancelled + + parentPath = fbd.SelectedPath.Trim(); + try + { + //Normalize parent path + parentPath = Path.GetFullPath(parentPath); + } + catch + { //Keep raw selection if normalization fails (we'll error out later if invalid) + } + } + + // Build the fixed subfolder path: \DriversBackup (always appended) + string target = Path.Combine(parentPath, "DriversBackup"); + + // Confirm before running the elevated export if (!ToolHelpers.Confirm(owner, "Export all installed drivers to:\r\n\r\n" + target + - "\r\n\r\nThis runs 'pnputil /export-driver *' with administrative rights.")) + "\r\n\r\nThis will run 'pnputil /export-driver *' with administrative rights.")) return null; + // Ensure the target DriversBackup folder exists try { Directory.CreateDirectory(target); @@ -47,7 +83,7 @@ namespace Flyoobe try { - // Start pnputil as elevated process + // Start pnputil as an elevated process targeting the 'DriversBackup' subfolder var psi = new ProcessStartInfo { FileName = "pnputil.exe", @@ -69,15 +105,17 @@ namespace Flyoobe proc.WaitForExit(); } - // Now export is complete > count drivers + // Count exported INF files to provide a quick summary int infCount = 0; try { infCount = Directory.GetFiles(target, "*.inf", SearchOption.AllDirectories).Length; } - catch { /* ignore */ } + catch + { // Ignore counting errors; we can still show success without the count + } - MessageBox.Show(owner, + MessageBox.Show(owner, "Driver export completed.\r\n\r\n" + $"Target: {target}\r\n" + $"Exported driver packages: {infCount}\r\n\r\n" + @@ -94,7 +132,7 @@ namespace Flyoobe MessageBoxButtons.OK, MessageBoxIcon.Error); } - return null; + return null; // returning null indicates no further action needed } } } diff --git a/Flyoobe/InstallView/InstallControlView.cs b/Flyoobe/InstallView/InstallControlView.cs index f75890c..0d60fdc 100644 --- a/Flyoobe/InstallView/InstallControlView.cs +++ b/Flyoobe/InstallView/InstallControlView.cs @@ -282,7 +282,7 @@ namespace Flyoobe return null; } - // Selects the provider in the combo (for UI consistency) and runs it via existing pipeline. + // Selects the provider in the combo (for UI consistency) and runs it via existing pipeline private async System.Threading.Tasks.Task RunQuickAsync(string providerId) { var p = FindProviderById(providerId); diff --git a/Flyoobe/MainForm.cs b/Flyoobe/MainForm.cs index 6a45c76..f74b79e 100644 --- a/Flyoobe/MainForm.cs +++ b/Flyoobe/MainForm.cs @@ -35,6 +35,7 @@ namespace Flyoobe _navigator.RegisterView("Device", () => new DeviceControlView()); _navigator.RegisterView("Personalization", () => new PersonalizationControlView()); _navigator.RegisterView("Browser", () => new DefaultsControlView()); + _navigator.RegisterView("AI", () => new AiControlView()); _navigator.RegisterView("Network", () => new NetworkControlView()); _navigator.RegisterView("Account", () => new AccountControlView()); _navigator.RegisterView("Apps", () => new AppsControlView()); diff --git a/Flyoobe/UpgradeView/EsuEnrollmentHandler.cs b/Flyoobe/UpgradeView/EsuEnrollmentHandler.cs new file mode 100644 index 0000000..cb65cbe --- /dev/null +++ b/Flyoobe/UpgradeView/EsuEnrollmentHandler.cs @@ -0,0 +1,200 @@ + +using System; +using System.Diagnostics; +using System.IO; +using System.Net; +using System.Threading.Tasks; +using System.Windows.Forms; + +public class EsuEnrollmentHandler +{ + /// + /// Keep Windows 10 secure until 2026 (Enroll in ESU) using abbodi1406's ConsumerESU scripts. + /// Downloads from GitHub if missing and executes. + /// + /// + public static async Task HandleEnrollInESU() + { + string repoBase = "https://raw.githubusercontent.com/abbodi1406/ConsumerESU/master/"; + string cmdFile = "Consumer_ESU_Enrollment_run.cmd"; + string psFile = "Consumer_ESU_Enrollment.ps1"; + + string appDir = AppDomain.CurrentDomain.BaseDirectory; + string cmdPath = Path.Combine(appDir, cmdFile); + string psPath = Path.Combine(appDir, psFile); + + // Ensure scripts exist (download if missing) + if (!File.Exists(cmdPath) || !File.Exists(psPath)) + { + var confirm = MessageBox.Show( + "The ESU enrollment scripts are missing.\n\n" + + "They will be downloaded directly from GitHub (abbodi1406/ConsumerESU).\n\n" + + "Do you want to continue?", + "Download Scripts", + MessageBoxButtons.YesNo, + MessageBoxIcon.Question); + + if (confirm != DialogResult.Yes) return; + + using (var wc = new WebClient()) + { + try + { + if (!File.Exists(cmdPath)) + await wc.DownloadFileTaskAsync(new Uri(repoBase + cmdFile), cmdPath); + if (!File.Exists(psPath)) + await wc.DownloadFileTaskAsync(new Uri(repoBase + psFile), psPath); + } + catch (Exception ex) + { + MessageBox.Show("Failed to download ESU scripts:\n" + ex.Message, + "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + } + } + + // Show parameter selection dialog + string esuArgs = ""; + using (var dlg = new EsuParamDialog()) + { + if (dlg.ShowDialog() != DialogResult.OK) + return; + + esuArgs = dlg.SelectedParam; + if (dlg.AddProceed) esuArgs += " -Proceed"; + } + + // Confirm and run + var result = MessageBox.Show( + "This will launch the Consumer ESU Enrollment Tool with parameters:\n\n" + + esuArgs + "\n\nContinue?", + "Enroll in ESU", + MessageBoxButtons.YesNo, + MessageBoxIcon.Information); + + if (result != DialogResult.Yes) return; + + try + { + var psi = new ProcessStartInfo + { + FileName = "cmd.exe", + Arguments = "/c \"" + cmdPath + "\" " + esuArgs, + WorkingDirectory = appDir, + UseShellExecute = true, + Verb = "runas" // run as admin + }; + Process.Start(psi); + } + catch (Exception ex) + { + MessageBox.Show("Failed to run ESU enrollment script:\n" + ex.Message, + "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + /// + /// Dialog form to select ESU enrollment parameter (no-designer) + /// + private class EsuParamDialog : Form + { + private ComboBox combo; + private CheckBox chkProceed; + private Button btnOk; + private Button btnCancel; + private LinkLabel link; + + public string SelectedParam { get; private set; } + public bool AddProceed { get; private set; } + + public EsuParamDialog() + { + Text = "Select ESU Enrollment Parameter"; + Width = 550; + Height = 250; + StartPosition = FormStartPosition.CenterParent; + FormBorderStyle = FormBorderStyle.FixedDialog; + MaximizeBox = false; + MinimizeBox = false; + ShowIcon = false; + ShowInTaskbar = false; + + combo = new ComboBox + { + Left = 20, + Top = 20, + Width = 500, + DropDownStyle = ComboBoxStyle.DropDownList + }; + combo.Items.AddRange(new object[] + { + "-Online : Only enroll using Microsoft user account token (exit if failed)", + "-Store : Only enroll using Microsoft store account token (exit if failed)", + "-Local : Only enroll using Local user account token (exit if failed)", + "-License : Force acquire Consumer ESU License regardless or without enrollment", + "-Remove : Remove Consumer ESU License if exists" + }); + combo.SelectedIndex = 0; + + chkProceed = new CheckBox + { + Left = 20, + Top = 60, + Text = "Add -Proceed (force running enrollment even if already enrolled)", + AutoSize = true + }; + + btnOk = new Button + { + Text = "OK", + Left = 340, + Width = 80, + Top = 150, + DialogResult = DialogResult.OK + }; + btnCancel = new Button + { + Text = "Cancel", + Left = 430, + Width = 80, + Top = 150, + DialogResult = DialogResult.Cancel + }; + + link = new LinkLabel + { + Left = 20, + Top = 100, + AutoSize = true, + Text = "Help && Credits: https://github.com/abbodi1406/ConsumerESU" + }; + link.LinkClicked += (s, e) => + { + try { Process.Start(new ProcessStartInfo { FileName = "https://github.com/abbodi1406/ConsumerESU", UseShellExecute = true }); } + catch { } + }; + + Controls.Add(combo); + Controls.Add(chkProceed); + Controls.Add(link); + Controls.Add(btnOk); + Controls.Add(btnCancel); + + AcceptButton = btnOk; + CancelButton = btnCancel; + } + + protected override void OnClosing(System.ComponentModel.CancelEventArgs e) + { + if (DialogResult == DialogResult.OK) + { + // Extract only the switch (first word, e.g. "-Online") + SelectedParam = combo.SelectedItem.ToString().Split(' ')[0]; + AddProceed = chkProceed.Checked; + } + base.OnClosing(e); + } + } +} + diff --git a/Flyoobe/UpgradeView/IsoHandler.cs b/Flyoobe/UpgradeView/IsoHandler.cs index 8a6885a..51405a6 100644 --- a/Flyoobe/UpgradeView/IsoHandler.cs +++ b/Flyoobe/UpgradeView/IsoHandler.cs @@ -403,81 +403,6 @@ try {{ } } - /// - /// Keep Windows 10 secure until 2026 (Enroll in ESU) using abbodi1406's ConsumerESU scripts. - /// Downloads from GitHub if missing and executes. - /// - public async void HandleEnrollInESU() - { - string repoBase = "https://raw.githubusercontent.com/abbodi1406/ConsumerESU/master/"; - string cmdFile = "Consumer_ESU_Enrollment_run.cmd"; - string psFile = "Consumer_ESU_Enrollment.ps1"; - - string appDir = AppDomain.CurrentDomain.BaseDirectory; - string cmdPath = Path.Combine(appDir, cmdFile); - string psPath = Path.Combine(appDir, psFile); - - // Check if scripts exist, otherwise download - if (!System.IO.File.Exists(cmdPath) || !System.IO.File.Exists(psPath)) - { - var confirm = MessageBox.Show( - "The ESU enrollment scripts are missing.\n\n" + - "They will now be downloaded directly from GitHub (abbodi1406/ConsumerESU).\n\n" + - "Do you want to continue?", - "Download Scripts", - MessageBoxButtons.YesNo, - MessageBoxIcon.Question); - - if (confirm != DialogResult.Yes) - return; - - using (var wc = new System.Net.WebClient()) - { - try - { - if (!System.IO.File.Exists(cmdPath)) - await wc.DownloadFileTaskAsync(new Uri(repoBase + cmdFile), cmdPath); - - if (!System.IO.File.Exists(psPath)) - await wc.DownloadFileTaskAsync(new Uri(repoBase + psFile), psPath); - } - catch (Exception ex) - { - MessageBox.Show("Failed to download ESU scripts:\n" + ex.Message, - "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); - return; - } - } - } - var result = MessageBox.Show( - "📦 This will now launch the Consumer ESU Enrollment Tool by abbodi1406.\n\n" + - "✔ Extends Windows 10 support until October 2026\n" + - "✔ Requires Windows 10 22H2 with KB5039299+ (19045.4598+)\n" + - "✔ Requires Microsoft Account + Internet access\n\n" + - "Do you want to continue?", - "Enroll in ESU", - MessageBoxButtons.YesNo, - MessageBoxIcon.Information); - - if (result != DialogResult.Yes) - return; - - // Start the .cmd (which internally calls the PowerShell) - try - { - Process.Start(new ProcessStartInfo - { - FileName = cmdPath, - Verb = "runas", - UseShellExecute = true - }); - } - catch (Exception ex) - { - MessageBox.Show("Failed to run ESU enrollment script:\n" + ex.Message, - "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); - } - } /// /// Handle unexpected exceptions by logging the error, updating the status, and optionally launching Copilot for help. @@ -508,6 +433,7 @@ try {{ CopilotHelper.LaunchCopilot(); } } + } public static class ProcessExtensions @@ -520,4 +446,5 @@ try {{ await tcs.Task; } } -} \ No newline at end of file +} + diff --git a/Flyoobe/UpgradeView/UpgradeControlView.cs b/Flyoobe/UpgradeView/UpgradeControlView.cs index 1e72221..7e4d9ad 100644 --- a/Flyoobe/UpgradeView/UpgradeControlView.cs +++ b/Flyoobe/UpgradeView/UpgradeControlView.cs @@ -48,7 +48,7 @@ namespace Flyoobe var combo = sender as ComboBox; int selectedIndex = combo.SelectedIndex; - // Let user pick ISO from local disk (compatibility check is now in HandleIsoInput) + // Pick ISO from local disk (compatibility check is now in HandleIsoInput) if (selectedIndex == 1) { await HandleIsoInput(sender, null); @@ -63,7 +63,7 @@ namespace Flyoobe break; case 3: - _isoHandler.HandleEnrollInESU(); + await EsuEnrollmentHandler.HandleEnrollInESU(); break; } }