diff --git a/README.md b/README.md index 671358e..a1b8863 100644 --- a/README.md +++ b/README.md @@ -1,45 +1,81 @@

- Logo + Logo

Taskbar Groups

-Issues open -Last commit -Latest version -Latest version +Issues open +Last commit +Latest version +Latest version

Taskbar groups is a lightweight utility for Windows that lets the users create groups of shortcuts in the taskbar.

- Download & Release Notes + Download & Release Notes


-[![-----------------------------------------------------](https://user-images.githubusercontent.com/56088716/103312593-8a37ff80-49eb-11eb-91d3-75488e21a0a9.png) ](#table-of-contents) +# Taskbar Groups – Unofficial Maintained Build -## 📖 Table of Contents +This is an unofficial maintained fork of the original Taskbar Groups project by **tjackenpacken**. -* [➤ Installation](#-how-to-download-taskbar-groups) -* [➤ Creating your first group](#%EF%B8%8F-creating-your-first-group) -* [➤ Screen/Window Documentation](#%EF%B8%8F-screenwindow-documentation) - * [Main Screen](#main-screen-) - * [Group Creation Screen](#group-creation-screen-) - * [Extra Notes](#extra-notes-) -* [➤ Image/Icon Caching](#-imageicon-caching) -* [➤ Program Shortcuts](#%EF%B8%8F-program-shortcuts) -* [➤ File/Folder Structure](#-folder-structure-documentation) -* [➤ License](#-License) +Original project: +https://github.com/tjackenpacken/taskbar-groups - [![-----------------------------------------------------](https://user-images.githubusercontent.com/56088716/103312593-8a37ff80-49eb-11eb-91d3-75488e21a0a9.png)](#how-to-download-taskbar-groups) +Maintained fork: +https://github.com/hummbugg/taskbar-groups + +--- + +## 📦 Download Ready-to-Run Build + +Download the latest compiled release here: + +https://github.com/hummbugg/taskbar-groups/releases + +No Visual Studio is required to use the release ZIP. + +--- + +## ⚠️ Windows Security Notice (Important) + +Because this application is distributed as a ZIP downloaded from the internet, Windows may block the files. + +### Recommended: + +1. Right-click the downloaded ZIP file +2. Click **Properties** +3. Check **Unblock** +4. Click **Apply** +5. Extract the ZIP +6. Run `TaskbarGroups.exe` + +### If already extracted: +If you accidently extract the zip file before unblocking the zip, you will have to delete the extracted folder, unblock the zip and extract the zip again. +Alternately if you are familiar with PowerShell you can run the command below to unblock the files, just change the path "C:\Path\To\TaskbarGroups" to match the folder you extracted TaskbarGroups into. + +```powershell +Get-ChildItem -Path "C:\Path\To\TaskbarGroups" -Recurse -File | Unblock-File +``` + +[![-----------------------------------------------------](https://user-images.githubusercontent.com/56088716/103312593-8a37ff80-49eb-11eb-91d3-75488e21a0a9.png)](#demo-video) + +## 🎬 Demo Video + +

+ + Taskbar Groups Demo Video + +

+ +

+ Click the thumbnail above to watch the Taskbar Groups v1.1.0-unofficial demo video on YouTube. +

-## 🔽 How to download Taskbar groups: - 1. Download the .zip-file from the latest release (link above) - 2. Unpack the .zip-file at a desired location - 3. Run the TaskbarGroups.exe file in the extracted folder [ ![-----------------------------------------------------](https://user-images.githubusercontent.com/56088716/103312593-8a37ff80-49eb-11eb-91d3-75488e21a0a9.png)](#creating-your-first-group) ## 🛠️ Creating your first group @@ -52,14 +88,24 @@ enter image description here5. Left click on the group 6. In the folder that opens up, right click on the highlighted shortcut 7. Select "Pin to taskbar" + + ## Runtime Requirement +Taskbar Groups targets Microsoft .NET Framework 4.7.2. + +Windows 11 normally already includes a newer compatible .NET Framework 4.x runtime, so no separate runtime installation should be needed. + +If you are using an older or unsupported version of Windows and the app does not start, install the Microsoft .NET Framework 4.7.2 Runtime: + +https://dotnet.microsoft.com/en-us/download/dotnet-framework/thank-you/net472-web-installer + [![-----------------------------------------------------](https://user-images.githubusercontent.com/56088716/103312593-8a37ff80-49eb-11eb-91d3-75488e21a0a9.png)](#screenwindow-documentation) ## 🖥️ Screen/Window Documentation Below will be some documentation for each of the screens with explaining the functionality of each of the components. #### Main screen [](#main-screen) -![Group overview screen](https://user-images.githubusercontent.com/56088716/103317856-81025f00-49fa-11eb-907b-99623babf315.PNG)Here is the main group configuration screen. You get here by executing the TaskbarGroups.exe file. Here you can add groups and see what groups you have created. +![Group overview screen](taskbar_groups.jpg)Here is the main group configuration screen. You get here by executing the TaskbarGroups.exe file. Here you can add groups and see what groups you have created. #### Group Creation Screen [](#group-creation) -![Group creation screen](https://user-images.githubusercontent.com/56088716/103452967-36efd680-4ca3-11eb-8244-2aed6fc5af97.PNG) +![Group creation screen](create_group.jpg) Here is the group creation screen. Here you can start customizing and configuring your group. Here is the quick rundown of the features of this window. **Name the new group** - You can insert any group name (no special characters) that you would like with a maximum character limit of 49 characters in total. @@ -125,3 +171,40 @@ Here is where all of your shortcuts to activate your group will go. All groups c This project is licensed under the [MIT License](https://github.com/tjackenpacken/taskbar-groups/blob/master/LICENSE). ![-----------------------------------------------------](https://user-images.githubusercontent.com/56088716/103312593-8a37ff80-49eb-11eb-91d3-75488e21a0a9.png) + +## Developer Information + +### Supported Operating Systems + +Taskbar Groups v1.1.0-unofficial targets Microsoft .NET Framework 4.7.2. + +Windows 11 already includes a newer compatible .NET Framework 4.x runtime, so no additional runtime installation is normally required for end users. + +The project has been tested primarily on: +- Windows 11 +- Windows 10 + +Older unsupported versions of Windows may require manual installation of the .NET Framework 4.7.2 runtime. + +### Supported Visual Studio Versions + +Because this project remains based on the classic .NET Framework WinForms project system, it can still be opened and edited using multiple generations of Microsoft Visual Studio, including: + +- Visual Studio 2017 +- Visual Studio 2019 +- Visual Studio 2022 +- Visual Studio 2026 + +This was intentionally preserved to maximize compatibility for developers who wish to maintain, enhance, or fork the project without requiring migration to newer SDK-style .NET project formats. + +### Project Dependencies + +The current release uses the following external libraries: + +- ChinhDo.Transactions.FileManager.dll +- Microsoft.WindowsAPICodePack.dll +- Microsoft.WindowsAPICodePack.Shell.dll + +These dependencies are included in the binary release ZIP. + +Future releases may reduce or eliminate some external dependencies in favor of native .NET implementations while preserving compatibility with classic Visual Studio versions and Windows-native deployment. diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..1ffc5e4 --- /dev/null +++ b/README.txt @@ -0,0 +1,161 @@ +Taskbar Groups – Unofficial Build (hummbugg) +Version: v1.1.0-unofficial + +Overview +-------- +Taskbar Groups lets you create custom groups of shortcuts and pin them to the Windows taskbar +for quick access. + +This is an unofficial build maintained by hummbugg, based on the original project by tjackenpacken. + +Original Project: +https://github.com/tjackenpacken/taskbar-groups + +Maintained Fork (this build): +https://github.com/hummbugg/taskbar-groups + + +What’s New in This Version +------------------------- +- Fixed .ico image loading issues +- Fixed multiple-instance behavior (single instance enforced) +- Fixed New Group dialog (now modal; no hidden duplicates) +- Improved Release build stability (x64 fix) +- Added version check against GitHub +- Added “update available” link when a newer version exists +- Added “Original Author” attribution in UI +- Improved dialogs to remember last used folder: + - Select Group Icon dialog + - Create New Shortcut dialog +- Improved usability: + - ESC closes dialog + - “Exit” renamed to “Cancel” +- Locked window sizes to prevent layout issues + + +Installation +------------ +1. Extract all files from the ZIP to a folder of your choice. +2. Run TaskbarGroups.exe + +Runtime Requirement +------------------- +Taskbar Groups targets Microsoft .NET Framework 4.7.2. + +Windows 11 normally already includes a newer compatible .NET Framework 4.x runtime, so no separate runtime installation should be needed. + +If you are using an older or unsupported version of Windows and the app does not start, install the Microsoft .NET Framework 4.7.2 Runtime: + +https://dotnet.microsoft.com/en-us/download/dotnet-framework/thank-you/net472-web-installer + + +IMPORTANT – Windows Security Warning +----------------------------------- +If Windows blocks the app because it was downloaded from the internet: + +OPTION 1 (Recommended): +1. Right-click the downloaded ZIP file +2. Click Properties +3. Check "Unblock" +4. Click Apply +5. Extract the ZIP +6. Run TaskbarGroups.exe + +OPTION 2 (If already extracted): +Run PowerShell and execute: +If you accidently extract the zip file before unblocking the zip, you will have to delete the extracted folder, unblock the zip and extract the zip again. +Alternately if you are familiar with PowerShell you can run the command below to unblock the files, just change the path "C:\Path\To\TaskbarGroups" to match the folder you extracted TaskbarGroups into. + +Get-ChildItem -Path "C:\Path\To\TaskbarGroups" -Recurse -File | Unblock-File + +Then run TaskbarGroups.exe again. + +Demo Video +----------- +A demonstration video for Taskbar Groups v1.1.0-unofficial is available on YouTube: +https://youtu.be/TzaTYnm7Ua8 + +Usage Tips +---------- +- Create a new group using "Add Taskbar Group" +- Add shortcuts and choose an icon +- Pin the generated shortcut to your taskbar +- The app remembers the last folders used for selecting icons and shortcuts + + +Version Information +------------------- +- Current Version: version of the EXE you are running +- Latest Version: latest release available on GitHub + +If a newer version is available, the Latest Version will appear as a clickable link. + + +Credits +------- +Original Author: +tjackenpacken +https://github.com/tjackenpacken/taskbar-groups + +Maintained and enhanced by: +hummbugg +https://github.com/hummbugg/taskbar-groups + + +Disclaimer +---------- +This is an unofficial build. It is not affiliated with or endorsed by the original author. + +Use at your own risk. + + +Support / Issues +---------------- +Please report issues or suggestions here: +https://github.com/hummbugg/taskbar-groups/issues + +Developer Information +--------------------- + +Supported Operating Systems +--------------------------- +Taskbar Groups v1.1.0-unofficial targets Microsoft .NET Framework 4.7.2. + +Windows 11 already includes a newer compatible .NET Framework 4.x runtime, +so no additional runtime installation is normally required for end users. + +The project has been tested primarily on: +- Windows 11 +- Windows 10 + +Older unsupported versions of Windows may require manual installation of the +.NET Framework 4.7.2 runtime. + +Supported Visual Studio Versions +-------------------------------- +Because this project remains based on the classic .NET Framework WinForms +project system, it can still be opened and edited using multiple generations +of Microsoft Visual Studio, including: + +- Visual Studio 2017 +- Visual Studio 2019 +- Visual Studio 2022 +- Visual Studio 2026 + +This was intentionally preserved to maximize compatibility for developers +who wish to maintain, enhance, or fork the project without requiring +migration to newer SDK-style .NET project formats. + +Project Dependencies +-------------------- +The current release uses the following external libraries: + +- ChinhDo.Transactions.FileManager.dll +- Microsoft.WindowsAPICodePack.dll +- Microsoft.WindowsAPICodePack.Shell.dll + +These dependencies are included in the binary release ZIP. + +Future releases may reduce or eliminate some external dependencies in favor +of native .NET implementations while preserving compatibility with classic +Visual Studio versions and Windows-native deployment. diff --git a/create_group.jpg b/create_group.jpg new file mode 100644 index 0000000..f4d062b Binary files /dev/null and b/create_group.jpg differ diff --git a/main/Classes/Category.cs b/main/Classes/Category.cs index 4dc92b9..6d9f161 100644 --- a/main/Classes/Category.cs +++ b/main/Classes/Category.cs @@ -1,4 +1,4 @@ -using client.User_controls; +using client.User_controls; using System; using System.Collections.Generic; using System.Drawing; @@ -104,9 +104,11 @@ public void CreateConfig(Image groupImage) // Through shellLink.cs class, pass through into the function information on how to construct the icon // Needed due to needing to set a unique AppUserModelID so the shortcut applications don't stack on the taskbar with the main application // Tricks Windows to think they are from different applications even though they are from the same .exe + // CHANGED: Use hummbugg AppUserModelID for maintained fork. ShellLink.InstallShortcut( Path.GetFullPath(@System.AppDomain.CurrentDomain.FriendlyName), - "tjackenpacken.taskbarGroup.menu." + this.Name, + //"tjackenpacken.taskbarGroup.menu." + this.Name, + "hummbugg.taskbarGroup.menu." + this.Name, path + " shortcut", Path.GetFullPath(@path), Path.GetFullPath(path + @"\GroupIcon.ico"), diff --git a/main/Forms/frmClient.cs b/main/Forms/frmClient.cs index bbf9d67..1ce41b0 100644 --- a/main/Forms/frmClient.cs +++ b/main/Forms/frmClient.cs @@ -1,4 +1,4 @@ -using client.Classes; +using client.Classes; using client.User_controls; using System; using System.Drawing; @@ -13,17 +13,77 @@ namespace client.Forms public partial class frmClient : Form { private static readonly HttpClient client = new HttpClient(); + // ADDED: Link label used to credit the original Taskbar Groups author. + private LinkLabel originalAuthorLink; + + // ADDED: Link Label used only when a newer GitHub release is available. + private LinkLabel githubVersionUpdateLink; + public frmClient() { System.Runtime.ProfileOptimization.StartProfile("frmClient.Profile"); InitializeComponent(); + + // ADDED: Lock main window size to prevent non-responsive layout issues. + this.FormBorderStyle = FormBorderStyle.FixedSingle; + this.MaximizeBox = false; + + // ADDED: Add original author credit/link above the issues/bugs section. + AddOriginalAuthorLink(); + this.MaximumSize = new Size(Screen.PrimaryScreen.WorkingArea.Width, Screen.PrimaryScreen.WorkingArea.Height); Reload(); - currentVersion.Text = "v" + System.Reflection.Assembly.GetEntryAssembly().GetName().Version.ToString(); + // CHANGED: Store current/local version and latest GitHub version so display can be normalized. + string currentAssemblyVersion = System.Reflection.Assembly.GetEntryAssembly().GetName().Version.ToString(); + string latestGitHubVersion = Task.Run(() => getVersionData()).Result; + + // ADDED: Normalize display text and show Latest Version as a link only when an update is available. + UpdateVersionDisplay(currentAssemblyVersion, latestGitHubVersion); - githubVersion.Text = Task.Run(() => getVersionData()).Result; } + + // ADDED: Adds an original author credit link above the existing "Have Issues or bugs?" section. + private void AddOriginalAuthorLink() + { + const int addedHeight = 28; + + // ADDED: Keep the bottom of pnlVersionInfo anchored in the same place + // while making room above the existing contents. + pnlVersionInfo.Top -= addedHeight; + pnlVersionInfo.Height += addedHeight; + + // ADDED: Move existing controls down to make room for the original author link. + foreach (Control control in pnlVersionInfo.Controls) + { + control.Top += addedHeight; + } + + originalAuthorLink = new LinkLabel(); + + originalAuthorLink.Text = "Original Author: tjackenpacken"; + originalAuthorLink.AutoSize = false; + originalAuthorLink.Width = 243; + originalAuthorLink.Height = 22; + originalAuthorLink.Left = label4.Left; + originalAuthorLink.Top = 1; + originalAuthorLink.TextAlign = ContentAlignment.MiddleCenter; + originalAuthorLink.LinkArea = new LinkArea(0, originalAuthorLink.Text.Length); + + originalAuthorLink.Font = new Font("Segoe UI", 9.75F); + originalAuthorLink.LinkColor = Color.White; + originalAuthorLink.ActiveLinkColor = Color.FromArgb(120, 170, 255); + originalAuthorLink.VisitedLinkColor = Color.White; + originalAuthorLink.BackColor = Color.Transparent; + originalAuthorLink.Cursor = Cursors.Hand; + + originalAuthorLink.LinkClicked += originalAuthorLink_LinkClicked; + + // ADDED: Add to pnlVersionInfo, which is the actual container for the GitHub/issues section. + pnlVersionInfo.Controls.Add(originalAuthorLink); + originalAuthorLink.BringToFront(); + } + public void Reload() { // flush and reload existing groups @@ -82,9 +142,12 @@ public void Reset() private void cmdAddGroup_Click(object sender, EventArgs e) { - frmGroup newGroup = new frmGroup(this); - newGroup.Show(); - newGroup.BringToFront(); + // CHANGED: Open the New Group form as a modal dialog. + // This prevents multiple hidden New Group windows from being created. + using (frmGroup newGroup = new frmGroup(this)) + { + newGroup.ShowDialog(this); + } } private void pnlAddGroup_MouseLeave(object sender, EventArgs e) @@ -112,20 +175,193 @@ private static async Task getVersionData() { HttpClient client = new HttpClient(); client.DefaultRequestHeaders.Add("User-Agent", "taskbar-groups"); - var res = await client.GetAsync("https://api.github.com/repos/tjackenpacken/taskbar-groups/releases"); + + // CHANGED: Point to hummbugg fork instead of original repo + var res = await client.GetAsync("https://api.github.com/repos/hummbugg/taskbar-groups/releases"); res.EnsureSuccessStatusCode(); + string responseBody = await res.Content.ReadAsStringAsync(); JsonArray responseJSON = JsonArray.Parse(responseBody); - JsonObject jsonObjectData = responseJSON[0].GetObject(); - return jsonObjectData["tag_name"].GetString(); - } catch {return "Not found";} + // ADDED: Safely find the latest non-prerelease version + foreach (var item in responseJSON) + { + JsonObject jsonObjectData = item.GetObject(); + + // Skip prereleases if present + if (!jsonObjectData.GetNamedBoolean("prerelease", false)) + { + return jsonObjectData["tag_name"].GetString(); + } + } + + // ADDED: Fallback if all releases are prerelease (unlikely) + if (responseJSON.Count > 0) + { + return responseJSON[0].GetObject()["tag_name"].GetString(); + } + + return "Not found"; + } + catch + { + return "Not found"; + } + } + + // ADDED: Updates Current Version and Latest Version display. + // Latest Version becomes a clickable link only when GitHub has a newer version. + private void UpdateVersionDisplay(string currentAssemblyVersion, string latestGitHubVersion) + { + Version currentNumericVersion = ParseNumericVersion(currentAssemblyVersion); + Version latestNumericVersion = ParseNumericVersion(latestGitHubVersion); + + bool latestVersionFound = latestNumericVersion > new Version(0, 0, 0, 0); + string latestSuffix = latestVersionFound ? GetVersionSuffix(latestGitHubVersion) : ""; + + // ADDED: Display current version using GitHub's suffix for visual consistency. + currentVersion.Text = BuildDisplayVersion(currentNumericVersion, latestSuffix); + + if (latestVersionFound && currentNumericVersion < latestNumericVersion) + { + // ADDED: Newer GitHub version exists, so show Latest Version as a hyperlink. + ShowLatestVersionAsLink(latestGitHubVersion); + } + else + { + // ADDED: No update available, so show Latest Version as plain text. + ShowLatestVersionAsPlainText(latestGitHubVersion); + } + } + + // ADDED: Extracts numeric version from values like "1.1.0.0", "v1.1.0-unofficial", or "v1.1.0". + private Version ParseNumericVersion(string versionText) + { + try + { + if (string.IsNullOrWhiteSpace(versionText)) + return new Version(0, 0, 0, 0); + + string cleaned = versionText.Trim(); + + if (cleaned.StartsWith("v", StringComparison.OrdinalIgnoreCase)) + cleaned = cleaned.Substring(1); + + int endIndex = 0; + + while (endIndex < cleaned.Length && + (char.IsDigit(cleaned[endIndex]) || cleaned[endIndex] == '.')) + { + endIndex++; + } + + if (endIndex == 0) + return new Version(0, 0, 0, 0); + + string numericPart = cleaned.Substring(0, endIndex).TrimEnd('.'); + + return new Version(numericPart); + } + catch + { + return new Version(0, 0, 0, 0); + } + } + + // ADDED: Gets suffix from GitHub tag, such as "-unofficial" or "-official". + private string GetVersionSuffix(string latestGitHubVersion) + { + if (string.IsNullOrWhiteSpace(latestGitHubVersion)) + return ""; + + string cleaned = latestGitHubVersion.Trim(); + + if (cleaned.StartsWith("v", StringComparison.OrdinalIgnoreCase)) + cleaned = cleaned.Substring(1); + + int endIndex = 0; + + while (endIndex < cleaned.Length && + (char.IsDigit(cleaned[endIndex]) || cleaned[endIndex] == '.')) + { + endIndex++; + } + + if (endIndex >= cleaned.Length) + return ""; + + return cleaned.Substring(endIndex); + } + + // ADDED: Builds display version like "v1.1.0-unofficial" from EXE version plus GitHub suffix. + private string BuildDisplayVersion(Version version, string suffix) + { + if (version == null) + return "v0.0.0"; + + string numericVersion; + + if (version.Build >= 0) + numericVersion = $"{version.Major}.{version.Minor}.{version.Build}"; + else + numericVersion = $"{version.Major}.{version.Minor}"; + + return "v" + numericVersion + suffix; + } + + // ADDED: Shows Latest Version as plain non-clickable text. + private void ShowLatestVersionAsPlainText(string latestGitHubVersion) + { + if (githubVersionUpdateLink != null) + githubVersionUpdateLink.Visible = false; + + githubVersion.Visible = true; + githubVersion.Text = latestGitHubVersion; + } + + // ADDED: Shows Latest Version as a clickable hyperlink when an update is available. + private void ShowLatestVersionAsLink(string latestGitHubVersion) + { + githubVersion.Visible = false; + + if (githubVersionUpdateLink == null) + { + githubVersionUpdateLink = new LinkLabel(); + + githubVersionUpdateLink.AutoSize = true; + githubVersionUpdateLink.Font = githubVersion.Font; + githubVersionUpdateLink.BackColor = Color.Transparent; + githubVersionUpdateLink.LinkColor = Color.White; + githubVersionUpdateLink.ActiveLinkColor = Color.FromArgb(120, 170, 255); + githubVersionUpdateLink.VisitedLinkColor = Color.White; + githubVersionUpdateLink.Location = githubVersion.Location; + githubVersionUpdateLink.LinkClicked += githubVersionUpdateLink_LinkClicked; + + pnlVersionInfo.Controls.Add(githubVersionUpdateLink); + githubVersionUpdateLink.BringToFront(); + } + + githubVersionUpdateLink.Text = latestGitHubVersion; + githubVersionUpdateLink.Visible = true; + } + + // ADDED: Opens hummbugg release page when Latest Version indicates an update is available. + private void githubVersionUpdateLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) + { + System.Diagnostics.Process.Start("https://github.com/hummbugg/taskbar-groups/releases"); + } + + // ADDED: Opens the original Taskbar Groups project page for attribution/reference. + private void originalAuthorLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) + { + System.Diagnostics.Process.Start("https://github.com/tjackenpacken/taskbar-groups"); } private void githubLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { - System.Diagnostics.Process.Start("https://github.com/tjackenpacken/taskbar-groups/releases"); + // CHANGED: Point to hummbugg releases page + System.Diagnostics.Process.Start("https://github.com/hummbugg/taskbar-groups/releases"); } private void frmClient_Resize(object sender, EventArgs e) diff --git a/main/Forms/frmGroup.Designer.cs b/main/Forms/frmGroup.Designer.cs index ad82445..a77d90c 100644 --- a/main/Forms/frmGroup.Designer.cs +++ b/main/Forms/frmGroup.Designer.cs @@ -1,4 +1,4 @@ -namespace client.Forms +namespace client.Forms { partial class frmGroup { @@ -170,7 +170,7 @@ private void InitializeComponent() this.cmdExit.Name = "cmdExit"; this.cmdExit.Size = new System.Drawing.Size(129, 30); this.cmdExit.TabIndex = 35; - this.cmdExit.Text = "Exit"; + this.cmdExit.Text = "Cancel"; this.cmdExit.UseVisualStyleBackColor = false; this.cmdExit.Click += new System.EventHandler(this.cmdExit_Click); // @@ -735,4 +735,4 @@ private void InitializeComponent() private System.Windows.Forms.Label label6; private System.Windows.Forms.Label label7; } -} \ No newline at end of file +} diff --git a/main/Forms/frmGroup.cs b/main/Forms/frmGroup.cs index be43763..607f869 100644 --- a/main/Forms/frmGroup.cs +++ b/main/Forms/frmGroup.cs @@ -1,4 +1,4 @@ -using ChinhDo.Transactions; +using ChinhDo.Transactions; using client.Classes; using client.User_controls; using IWshRuntimeLibrary; @@ -11,7 +11,9 @@ using System.Transactions; using System.Windows.Forms; using Microsoft.WindowsAPICodePack.Shell; -using Microsoft.WindowsAPICodePack.Dialogs; +using Microsoft.WindowsAPICodePack.Dialogs; +using System.Runtime.InteropServices; // ADDED: Used for ExtractIconEx indexed icon extraction. + namespace client.Forms { @@ -31,7 +33,39 @@ public partial class frmGroup : Form private List shortcutChanged = new List(); + // ADDED: File used to persist last selected icon directory across sessions. + private static string LastIconDirectoryFile + { + get + { + return Path.Combine(MainPath.path, "config", "last_icon_directory.txt"); + } + } + // ADDED: File used to persist last selected shortcut directory across sessions. + private static string LastShortcutDirectoryFile + { + get + { + return Path.Combine(MainPath.path, "config", "last_shortcut_directory.txt"); + } + } + + // ADDED: Extracts icons by index from EXE/DLL/ICO resources. + // Needed for shortcuts whose IconLocation includes an index, such as "SomeIcons.exe,1". + [DllImport("shell32.dll", CharSet = CharSet.Auto)] + private static extern uint ExtractIconEx( + string szFileName, + int nIconIndex, + IntPtr[] phiconLarge, + IntPtr[] phiconSmall, + uint nIcons + ); + + // ADDED: Releases icon handles returned by ExtractIconEx. + [DllImport("user32.dll", SetLastError = true)] + private static extern bool DestroyIcon(IntPtr hIcon); + //-------------------------------------- // CTOR AND LOAD //-------------------------------------- @@ -43,6 +77,15 @@ public frmGroup(frmClient client) System.Runtime.ProfileOptimization.StartProfile("frmGroup.Profile"); InitializeComponent(); + // ADDED: Allow ESC key to close the dialog like a standard modal. + this.CancelButton = cmdExit; + + // ADDED: Set dialog to fixed size for consistent modal behavior. + this.FormBorderStyle = FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + // OPTIONAL: Prevent resizing via dragging edges completely. + this.AutoSizeMode = AutoSizeMode.GrowAndShrink; // Setting default category properties newExt = imageExt.Concat(specialImageExt).ToArray(); @@ -155,7 +198,8 @@ private void pnlAddShortcut_Click(object sender, EventArgs e) OpenFileDialog openFileDialog = new OpenFileDialog // ask user to select exe file { - InitialDirectory = @"C:\ProgramData\Microsoft\Windows\Start Menu\Programs", + // CHANGED: Use last-used shortcut directory instead of always defaulting to Start Menu\Programs. + InitialDirectory = GetLastShortcutDirectory(), Title = "Create New Shortcut", CheckFileExists = true, CheckPathExists = true, @@ -169,6 +213,12 @@ private void pnlAddShortcut_Click(object sender, EventArgs e) if (openFileDialog.ShowDialog() == DialogResult.OK) { + // ADDED: Persist the last used shortcut directory for future dialog opens. + if (openFileDialog.FileNames.Length > 0) + { + SaveLastShortcutDirectory(openFileDialog.FileNames[0]); + } + foreach (String file in openFileDialog.FileNames) { addShortcut(file); @@ -313,7 +363,8 @@ private void cmdAddGroupIcon_Click(object sender, EventArgs e) OpenFileDialog openFileDialog = new OpenFileDialog // ask user to select img as group icon { - InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), + // CHANGED: Use last-used directory instead of always defaulting to Pictures. + InitialDirectory = GetLastIconDirectory(), Title = "Select Group Icon", CheckFileExists = true, CheckPathExists = true, @@ -331,6 +382,10 @@ private void cmdAddGroupIcon_Click(object sender, EventArgs e) String imageExtension = Path.GetExtension(openFileDialog.FileName).ToLower(); handleIcon(openFileDialog.FileName, imageExtension); + + // ADDED: Persist the last used directory for future dialog opens. + SaveLastIconDirectory(openFileDialog.FileName); + } } @@ -352,15 +407,23 @@ private void pnlDragDropImg(object sender, DragEventArgs e) private void handleIcon(String file, String imageExtension) { - // Checks if the files being added/dropped are an .exe or .lnk in which tye icons need to be extracted/processed + // Checks if the files being added/dropped are an .exe or .lnk in which type icons need to be extracted/processed if (specialImageExt.Contains(imageExtension)) { if (imageExtension == ".lnk") { cmdAddGroupIcon.BackgroundImage = handleLnkExt(file); } + // ADDED: Load the actual .ico image selected by the user. + // Icon.ExtractAssociatedIcon(file) returns the Windows file-type icon for .ico files, + // which can display as a generic document/text icon instead of the icon contents. + else if (imageExtension == ".ico") + { + cmdAddGroupIcon.BackgroundImage = new Bitmap(file); + } else { + //exe cmdAddGroupIcon.BackgroundImage = Icon.ExtractAssociatedIcon(file).ToBitmap(); } } @@ -371,27 +434,161 @@ private void handleIcon(String file, String imageExtension) lblAddGroupIcon.Text = "Change group icon"; } + + // ADDED: Gets the last folder used for selecting group icons. + private string GetLastIconDirectory() + { + try + { + if (System.IO.File.Exists(LastIconDirectoryFile)) + { + string savedPath = System.IO.File.ReadAllText(LastIconDirectoryFile).Trim(); + + if (Directory.Exists(savedPath)) + return savedPath; + } + } + catch + { + // Ignore errors and fall back to default + } + + return Environment.GetFolderPath(Environment.SpecialFolder.MyPictures); + } + + // ADDED: Saves the last folder used for selecting group icons. + private void SaveLastIconDirectory(string filePath) + { + try + { + string directory = Path.GetDirectoryName(filePath); + + if (Directory.Exists(directory)) + System.IO.File.WriteAllText(LastIconDirectoryFile, directory); + } + catch + { + // Ignore save errors (non-critical) + } + } + + // ADDED: Gets the last folder used for selecting program shortcuts. + private string GetLastShortcutDirectory() + { + try + { + if (System.IO.File.Exists(LastShortcutDirectoryFile)) + { + string savedPath = System.IO.File.ReadAllText(LastShortcutDirectoryFile).Trim(); + + if (Directory.Exists(savedPath)) + return savedPath; + } + } + catch + { + // Ignore errors and fall back to default + } + + return @"C:\ProgramData\Microsoft\Windows\Start Menu\Programs"; + } + + // ADDED: Saves the last folder used for selecting program shortcuts. + private void SaveLastShortcutDirectory(string filePath) + { + try + { + string directory = Path.GetDirectoryName(filePath); + + if (Directory.Exists(directory)) + System.IO.File.WriteAllText(LastShortcutDirectoryFile, directory); + } + catch + { + // Ignore save errors (non-critical) + } + } + + // ADDED: Extracts an icon from a file using an explicit icon index. + // This fixes shortcuts like Camtasia where IconLocation points to "CamtasiaIcons.exe,1". + private static Bitmap ExtractIconByIndex(string iconPath, int iconIndex) + { + IntPtr[] largeIcons = new IntPtr[1]; + IntPtr[] smallIcons = new IntPtr[1]; + + uint extractedCount = ExtractIconEx(iconPath, iconIndex, largeIcons, smallIcons, 1); + + try + { + if (extractedCount > 0 && largeIcons[0] != IntPtr.Zero) + { + using (Icon icon = (Icon)Icon.FromHandle(largeIcons[0]).Clone()) + { + return icon.ToBitmap(); + } + } + + if (extractedCount > 0 && smallIcons[0] != IntPtr.Zero) + { + using (Icon icon = (Icon)Icon.FromHandle(smallIcons[0]).Clone()) + { + return icon.ToBitmap(); + } + } + + return null; + } + finally + { + if (largeIcons[0] != IntPtr.Zero) + DestroyIcon(largeIcons[0]); + + if (smallIcons[0] != IntPtr.Zero) + DestroyIcon(smallIcons[0]); + } + } + // Handle returning images of icon files (.lnk) public static Bitmap handleLnkExt(String file) { IWshShortcut lnkIcon = (IWshShortcut)new WshShell().CreateShortcut(file); String[] icLocation = lnkIcon.IconLocation.Split(','); + + // ADDED: Resolve shortcut IconLocation path and index separately. + // Some shortcuts store the correct icon in an indexed resource file, e.g. "CamtasiaIcons.exe,1". + string iconPath = ""; + int iconIndex = 0; + + if (icLocation.Length > 0) + iconPath = Environment.ExpandEnvironmentVariables(icLocation[0]); + + if (icLocation.Length > 1) + int.TryParse(icLocation[1], out iconIndex); + // Check if iconLocation exists to get an .ico from; if not then take the image from the .exe it is referring to // Checks for link iconLocations as those are used by some applications - if (icLocation[0] != "" && !lnkIcon.IconLocation.Contains("http")) + if (iconPath != "" && !lnkIcon.IconLocation.Contains("http") && System.IO.File.Exists(iconPath)) { - return Icon.ExtractAssociatedIcon(Path.GetFullPath(Environment.ExpandEnvironmentVariables(icLocation[0]))).ToBitmap(); + // CHANGED: Extract icon using IconLocation index instead of ignoring the index. + // Fixes shortcuts such as Camtasia where the desired icon is not index 0. + Bitmap indexedIcon = ExtractIconByIndex(Path.GetFullPath(iconPath), iconIndex); + + if (indexedIcon != null) + return indexedIcon; + + // ADDED: Fallback to original behavior if indexed extraction fails. + return Icon.ExtractAssociatedIcon(Path.GetFullPath(iconPath)).ToBitmap(); } else if (icLocation[0] == "" && lnkIcon.TargetPath == "") { return handleWindowsApp.getWindowsAppIcon(file); - } else + } + else { return Icon.ExtractAssociatedIcon(Path.GetFullPath(Environment.ExpandEnvironmentVariables(lnkIcon.TargetPath))).ToBitmap(); } } - public static String handleExtName(String file) { string fileName = Path.GetFileName(file); @@ -468,9 +665,9 @@ private Boolean checkExtensions(DragEventArgs e, String[] exts) // Exit editor private void cmdExit_Click(object sender, EventArgs e) { - this.Hide(); - this.Dispose(); - Client.Reload(); //flush and reload category panels + // CHANGED: Modal Cancel should simply close the dialog. + // Do not Hide/Dispose/Reload here because ShowDialog() and the using block handle cleanup. + this.Close(); } // Save group diff --git a/main/Forms/frmMain.Designer.cs b/main/Forms/frmMain.Designer.cs index a8c4812..acd431c 100644 --- a/main/Forms/frmMain.Designer.cs +++ b/main/Forms/frmMain.Designer.cs @@ -1,4 +1,4 @@ -namespace client +namespace client { partial class frmMain { @@ -28,6 +28,7 @@ protected override void Dispose(bool disposing) /// private void InitializeComponent() { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(frmMain)); this.SuspendLayout(); // // frmMain @@ -37,8 +38,11 @@ private void InitializeComponent() this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(33)))), ((int)(((byte)(33)))), ((int)(((byte)(33))))); this.ClientSize = new System.Drawing.Size(120, 0); this.DoubleBuffered = true; + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); this.KeyPreview = true; this.Name = "frmMain"; + this.ShowIcon = false; + this.ShowInTaskbar = false; this.Text = "Group"; this.Deactivate += new System.EventHandler(this.frmMain_Deactivate); this.Load += new System.EventHandler(this.frmMain_Load); diff --git a/main/Forms/frmMain.cs b/main/Forms/frmMain.cs index 19d67ba..d5e2754 100644 --- a/main/Forms/frmMain.cs +++ b/main/Forms/frmMain.cs @@ -1,4 +1,4 @@ -using client.Classes; +using client.Classes; using client.User_controls; using System; using System.Collections.Generic; @@ -7,6 +7,8 @@ using System.IO; using System.Threading.Tasks; using System.Windows.Forms; +using System.Runtime.InteropServices; // ADDED: Used to bring existing window to the front. +using System.Threading; // ADDED: Used for single-instance mutex. namespace client { @@ -342,6 +344,7 @@ public void OpenFile(string arguments, string path, string workingDirec) proc.Arguments = arguments; proc.FileName = path; proc.WorkingDirectory = workingDirec; + proc.UseShellExecute = true; // Required for launching .lnk and shell-based apps (e.g., Office, UWP); fixes shortcuts not opening /* proc.EnableRaisingEvents = false; diff --git a/main/Properties/AssemblyInfo.cs b/main/Properties/AssemblyInfo.cs index 26b671a..1e895f3 100644 --- a/main/Properties/AssemblyInfo.cs +++ b/main/Properties/AssemblyInfo.cs @@ -1,4 +1,4 @@ -using System.Resources; +using System.Resources; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -33,6 +33,6 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("0.2.0.0")] -[assembly: AssemblyFileVersion("0.2.0.0")] +[assembly: AssemblyVersion("1.1.0.0")] +[assembly: AssemblyFileVersion("1.1.0.0")] [assembly: NeutralResourcesLanguage("en")] diff --git a/main/User controls/ucShortcut.cs b/main/User controls/ucShortcut.cs index d3273b4..7432317 100644 --- a/main/User controls/ucShortcut.cs +++ b/main/User controls/ucShortcut.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; @@ -19,6 +19,10 @@ public partial class ucShortcut : UserControl public ProgramShortcut Psc { get; set; } public frmMain MotherForm { get; set; } public Category ThisCategory { get; set; } + + // ADDED: Tooltip used to show the shortcut/app name when hovering over the icon. + private ToolTip shortcutToolTip = new ToolTip(); + public ucShortcut() { InitializeComponent(); @@ -30,21 +34,33 @@ private void ucShortcut_Load(object sender, EventArgs e) this.BringToFront(); this.BackColor = MotherForm.BackColor; picIcon.BackgroundImage = ThisCategory.loadImageCache(Psc); // Use the local icon cache for the file specified as the icon image + + // ADDED: Show a hover tooltip so identical icons can be distinguished. + string displayName = !string.IsNullOrWhiteSpace(Psc.name) + ? Psc.name + : Path.GetFileNameWithoutExtension(Psc.FilePath); + + // ADDED: Attach tooltip to both the UserControl and the actual PictureBox. + // The mouse is usually over picIcon, so setting only "this" may not be enough. + shortcutToolTip.SetToolTip(this, displayName); + shortcutToolTip.SetToolTip(picIcon, displayName); } public void ucShortcut_Click(object sender, EventArgs e) { if (Psc.isWindowsApp) { - Process p = new Process() {StartInfo = new ProcessStartInfo() { UseShellExecute = true, FileName = $@"shell:appsFolder\{Psc.FilePath}" }}; + Process p = new Process() { StartInfo = new ProcessStartInfo() { UseShellExecute = true, FileName = $@"shell:appsFolder\{Psc.FilePath}" } }; p.Start(); - } else + } + else { - if(Path.GetExtension(Psc.FilePath).ToLower() == ".lnk" && Psc.FilePath == MainPath.exeString) + if (Path.GetExtension(Psc.FilePath).ToLower() == ".lnk" && Psc.FilePath == MainPath.exeString) { MotherForm.OpenFile(Psc.Arguments, Psc.FilePath, MainPath.path); - } else + } + else { MotherForm.OpenFile(Psc.Arguments, Psc.FilePath, Psc.WorkingDirectory); } diff --git a/main/client.cs b/main/client.cs index 36903ce..2547515 100644 --- a/main/client.cs +++ b/main/client.cs @@ -1,10 +1,11 @@ -using client.Forms; +using client.Classes; +using client.Forms; using System; +using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; +using System.Threading; using System.Windows.Forms; -using client.Classes; -using System.Diagnostics; namespace client { @@ -19,9 +20,19 @@ static class client // Define functions to set AppUserModelID [DllImport("shell32.dll", SetLastError = true)] static extern void SetCurrentProcessExplicitAppUserModelID([MarshalAs(UnmanagedType.LPWStr)] string AppID); - - [STAThread] + // ADDED: Win32 calls used to restore and focus the existing TaskbarGroups window. + [DllImport("user32.dll")] + private static extern bool SetForegroundWindow(IntPtr hWnd); + + [DllImport("user32.dll")] + private static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow); + + // ADDED: Restores minimized windows. + private const int SW_RESTORE = 9; + + + [STAThread] static void Main() { // Use existing methods to obtain cursor already imported as to not import any extra functions @@ -70,14 +81,48 @@ static void Main() { // Sets the AppUserModelID to tjackenpacken.taskbarGroup.menu.groupName // Distinguishes each shortcut process from one another to prevent them from stacking with the main application - SetCurrentProcessExplicitAppUserModelID("tjackenpacken.taskbarGroup.menu."+ arguments[1]); + //SetCurrentProcessExplicitAppUserModelID("tjackenpacken.taskbarGroup.menu."+ arguments[1]); + + // CHANGED: Use hummbugg AppUserModelID for maintained fork. + SetCurrentProcessExplicitAppUserModelID("hummbugg.taskbarGroup.menu." + arguments[1]); + Application.Run(new frmMain(arguments[1], cursorX, cursorY)); - } else + + + } + else { - // See comment above - SetCurrentProcessExplicitAppUserModelID("tjackenpacken.taskbarGroup.main"); - Application.Run(new frmClient()); + // ADDED: Prevent multiple main TaskbarGroups configuration windows from running. + // This is only applied to the main app window, not the taskbar drawer/group windows. + bool createdNew; + using (Mutex singleInstanceMutex = new Mutex(true, "TaskbarGroups_Main_SingleInstance_Mutex", out createdNew)) + { + if (!createdNew) + { + // ADDED: If the main app is already running, bring an existing TaskbarGroups window forward. + Process currentProcess = Process.GetCurrentProcess(); + Process[] runningProcesses = Process.GetProcessesByName(currentProcess.ProcessName); + + foreach (Process process in runningProcesses) + { + if (process.Id != currentProcess.Id && process.MainWindowHandle != IntPtr.Zero) + { + ShowWindowAsync(process.MainWindowHandle, SW_RESTORE); + SetForegroundWindow(process.MainWindowHandle); + break; + } + } + + return; + } + + // See comment above + //SetCurrentProcessExplicitAppUserModelID("tjackenpacken.taskbarGroup.main"); + // CHANGED: Use hummbugg AppUserModelID for maintained fork. + SetCurrentProcessExplicitAppUserModelID("hummbugg.taskbarGroup.main"); + Application.Run(new frmClient()); + } } } } diff --git a/main/client.csproj b/main/client.csproj index 137d4c9..890d995 100644 --- a/main/client.csproj +++ b/main/client.csproj @@ -1,4 +1,4 @@ - + @@ -13,6 +13,8 @@ true true false + + publish\ true Disk @@ -27,8 +29,6 @@ 1.0.0.%2a false true - - AnyCPU @@ -42,7 +42,7 @@ false - AnyCPU + x64 pdbonly true bin\Release\ @@ -253,4 +253,4 @@ Microsoft.WindowsAPICodePack.xml - \ No newline at end of file + diff --git a/taskbar_groups.jpg b/taskbar_groups.jpg new file mode 100644 index 0000000..567f443 Binary files /dev/null and b/taskbar_groups.jpg differ diff --git a/taskbar_groups_video_thumbnail.jpg b/taskbar_groups_video_thumbnail.jpg new file mode 100644 index 0000000..289b1c2 Binary files /dev/null and b/taskbar_groups_video_thumbnail.jpg differ