Compare commits

..

19 Commits

Author SHA1 Message Date
9de3ae43d5 Improve code formatting and comment out TrackWows method for future reference 2025-07-02 09:05:26 +02:00
886258e4c3 Refactor process tracking to handle null slots and compact array 2025-05-25 20:51:29 +02:00
b5446516e4 Add sorting for process list and implement scroll lock toggle 2025-05-24 23:56:23 +02:00
c28bc4ac69 Ignore vs 2025-05-15 07:51:37 +02:00
04cb6c1e38 Base logic off of scroll lock toggle instead of whatever the fuck we had previously 2025-05-15 07:47:17 +02:00
50f7c777b3 Switch end for scroll lock 2025-05-15 07:45:25 +02:00
740d3d8b01 Fix some shit and code format
Also now end is to swap windows
2025-04-27 17:56:43 +02:00
6a766a5441 Update 2025-04-25 16:28:48 +02:00
be2c6f7829 Add support for 19 processes 2025-01-04 12:17:14 +01:00
7beb55e3ce Clean windows when doing anything, to keep tidy 2025-01-04 12:13:18 +01:00
4aaedac17c Implement wrap around for the damn 0 (should be 123..90 not 0123..9) 2025-01-03 20:02:27 +01:00
6a4e25cdb8 Make more processes 2025-01-03 19:51:28 +01:00
a9024539a4 Clean up code a little 2024-12-31 10:31:39 +01:00
6cee7038bd Implement "find all wows" button 2024-12-27 11:00:58 +01:00
197f098f67 Fully implement the tabbing and switching 2024-11-03 14:56:47 +01:00
bd24acde46 Rework almost everything 2024-11-03 14:54:03 +01:00
3872c5181f Add properties... 2024-11-03 14:11:26 +01:00
fd68adaff8 Code format 2024-11-03 14:11:26 +01:00
289f03fbfc Remove pixelcs 2024-11-03 13:03:50 +01:00
19 changed files with 821 additions and 173 deletions

6
.gitignore vendored
View File

@@ -1,2 +1,4 @@
main.log .idea
__debug_bin3523166607.exe obj
bin
.vs

16
DD2Switcher.sln Normal file
View File

@@ -0,0 +1,16 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DD2Switcher", "DD2Switcher\DD2Switcher.csproj", "{2AC26899-8E27-4B96-85A9-C387186EAD27}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{2AC26899-8E27-4B96-85A9-C387186EAD27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2AC26899-8E27-4B96-85A9-C387186EAD27}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2AC26899-8E27-4B96-85A9-C387186EAD27}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2AC26899-8E27-4B96-85A9-C387186EAD27}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,5 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/PencilsConfiguration/ActualSeverity/@EntryValue">INFO</s:String>
<s:String x:Key="/Default/Environment/Hierarchy/Build/BuildTool/CustomBuildToolPath/@EntryValue">C:\Users\Administrator\AppData\Local\JetBrains\Toolbox\apps\Rider\ch-0\231.8109.212\tools\MSBuild\Current\Bin\amd64\MSBuild.exe</s:String>
</wpf:ResourceDictionary>

6
DD2Switcher/App.config Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
</startup>
</configuration>

View File

@@ -0,0 +1,87 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')"/>
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{2AC26899-8E27-4B96-85A9-C387186EAD27}</ProjectGuid>
<OutputType>WinExe</OutputType>
<RootNamespace>DD2Switcher</RootNamespace>
<AssemblyName>DD2Switcher</AssemblyName>
<TargetFrameworkVersion>v4.8.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
<LangVersion>9.0</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>x64</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32bit>false</Prefer32bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>x64</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32bit>false</Prefer32bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="System"/>
<Reference Include="System.Core"/>
<Reference Include="System.Xml.Linq"/>
<Reference Include="System.Data.DataSetExtensions"/>
<Reference Include="Microsoft.CSharp"/>
<Reference Include="System.Data"/>
<Reference Include="System.Deployment"/>
<Reference Include="System.Drawing"/>
<Reference Include="System.Net.Http"/>
<Reference Include="System.Windows.Forms"/>
<Reference Include="System.Xml"/>
</ItemGroup>
<ItemGroup>
<Compile Include="Form1.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Form1.Designer.cs">
<DependentUpon>Form1.cs</DependentUpon>
</Compile>
<Compile Include="HotKeyManager.cs"/>
<Compile Include="Program.cs"/>
<Compile Include="Properties\AssemblyInfo.cs"/>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<SubType>Designer</SubType>
</EmbeddedResource>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
</ItemGroup>
<ItemGroup>
<None Include="App.config"/>
</ItemGroup>
<ItemGroup>
<Content Include="beep.wav"/>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets"/>
</Project>

36
DD2Switcher/Form1.Designer.cs generated Normal file
View File

@@ -0,0 +1,36 @@
namespace DD2Switcher {
partial class Form1 {
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed;
/// otherwise, false.</param>
protected override void Dispose(bool disposing) {
if (disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent() {
this.components = new System.ComponentModel.Container();
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(800, 450);
this.Text = "Form1";
}
#endregion
}
}

7
DD2Switcher/Form1.cs Normal file
View File

@@ -0,0 +1,7 @@
using System.Windows.Forms;
namespace DD2Switcher {
public partial class Form1 : Form {
public Form1() { InitializeComponent(); }
}
}

View File

@@ -0,0 +1,112 @@
using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
namespace DD2Switcher {
public static class HotKeyManager {
private static volatile MessageWindow _wnd;
private static volatile IntPtr _hwnd;
private static readonly ManualResetEvent _windowReadyEvent = new(false);
private static int _id;
static HotKeyManager() {
var messageLoop =
new Thread(delegate() { Application.Run(new MessageWindow()); });
messageLoop.Name = "MessageLoopThread";
messageLoop.IsBackground = true;
messageLoop.Start();
}
public static event EventHandler<HotKeyEventArgs> HotKeyPressed;
public static int RegisterHotKey(Keys key, KeyModifiers modifiers) {
_windowReadyEvent.WaitOne();
var id = Interlocked.Increment(ref _id);
_wnd.Invoke(new RegisterHotKeyDelegate(RegisterHotKeyInternal), _hwnd, id,
(uint)modifiers, (uint)key);
return id;
}
public static void UnregisterHotKey(int id) {
_wnd.Invoke(new UnRegisterHotKeyDelegate(UnRegisterHotKeyInternal), _hwnd,
id);
}
private static void RegisterHotKeyInternal(IntPtr hwnd, int id,
uint modifiers, uint key) {
RegisterHotKey(hwnd, id, modifiers, key);
}
private static void UnRegisterHotKeyInternal(IntPtr hwnd, int id) {
UnregisterHotKey(_hwnd, id);
}
private static void OnHotKeyPressed(HotKeyEventArgs e) {
if (HotKeyPressed != null)
HotKeyPressed(null, e);
}
[DllImport("user32", SetLastError = true)]
private static extern bool RegisterHotKey(IntPtr hWnd, int id,
uint fsModifiers, uint vk);
[DllImport("user32", SetLastError = true)]
private static extern bool UnregisterHotKey(IntPtr hWnd, int id);
private delegate void RegisterHotKeyDelegate(IntPtr hwnd, int id,
uint modifiers, uint key);
private delegate void UnRegisterHotKeyDelegate(IntPtr hwnd, int id);
private class MessageWindow : Form {
private const int WM_HOTKEY = 0x312;
public MessageWindow() {
_wnd = this;
_hwnd = Handle;
_windowReadyEvent.Set();
}
protected override void WndProc(ref Message m) {
if (m.Msg == WM_HOTKEY) {
var e = new HotKeyEventArgs(m.LParam);
OnHotKeyPressed(e);
}
base.WndProc(ref m);
}
protected override void SetVisibleCore(bool value) {
// Ensure the window never becomes visible
base.SetVisibleCore(false);
}
}
}
public class HotKeyEventArgs : EventArgs {
public readonly Keys Key;
public readonly KeyModifiers Modifiers;
public HotKeyEventArgs(Keys key, KeyModifiers modifiers) {
Key = key;
Modifiers = modifiers;
}
public HotKeyEventArgs(IntPtr hotKeyParam) {
var param = (uint)hotKeyParam.ToInt64();
Key = (Keys)((param & 0xffff0000) >> 16);
Modifiers = (KeyModifiers)(param & 0x0000ffff);
}
}
[Flags]
public enum KeyModifiers {
Alt = 1,
Control = 2,
Shift = 4,
Windows = 8,
NoRepeat = 0x4000
}
}

294
DD2Switcher/Program.cs Normal file
View File

@@ -0,0 +1,294 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace DD2Switcher {
internal static class Program {
private static int NumProc = 19;
private static Process[] windows = new Process[NumProc];
private static int ActiveIndex = -1;
private static readonly IntPtr defaultAffinity = new(0xFF000000);
private static readonly IntPtr fullAffinity = new(0xFFFFFFFF);
[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
public static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern short GetKeyState(int nVirtKey);
[DllImport("kernel32.dll", SetLastError = true)]
[return:MarshalAs(UnmanagedType.Bool)]
static extern bool AllocConsole();
[DllImport("user32.dll")]
private static extern void keybd_event(byte bVk, byte bScan, uint dwFlags,
int dwExtraInfo);
private static void CleanWindows() {
for (int i = 0; i < NumProc; i++) {
var window = windows[i];
if (window == null)
continue;
if (window.MainWindowHandle == IntPtr.Zero) {
Console.WriteLine(
$"Window at index {i} has no main window, removing from tracked windows");
windows[i] = null;
}
}
}
private static void AdjustAffinities() {
CleanWindows();
for (int i = 0; i < NumProc; i++) {
var window = windows[i];
if (window == null)
continue;
if (i != ActiveIndex) {
try {
window.ProcessorAffinity = defaultAffinity;
} catch (Exception e) {
windows[i] = null;
}
}
}
var active = windows[ActiveIndex];
if (active != null) {
try {
active.ProcessorAffinity = fullAffinity;
} catch (Exception e) {
windows[ActiveIndex] = null;
}
}
}
private static void AdjustPriorities() {
CleanWindows();
for (int i = 0; i < NumProc; i++) {
var window = windows[i];
if (window == null)
continue;
if (i != ActiveIndex) {
try {
window.PriorityClass = ProcessPriorityClass.Idle;
} catch (Exception e) {
windows[i] = null;
}
}
}
var active = windows[ActiveIndex];
if (active != null) {
try {
active.PriorityClass = ProcessPriorityClass.High;
} catch (Exception e) {
windows[ActiveIndex] = null;
}
}
}
private static Process GetForegroundProcess() {
var foregroundWindow = GetForegroundWindow();
var process = Process.GetProcesses();
Process foregroundProcess = null;
foreach (var p in process)
if (foregroundWindow == p.MainWindowHandle) {
foregroundProcess = p;
break;
}
if (foregroundProcess == null)
return null;
return foregroundProcess;
}
private static Boolean ProcessTracked(int id) {
for (int i = 0; i < NumProc; i++) {
if (windows[i] != null && windows[i].Id == id) {
return true;
}
}
return false;
}
private static void Track(Process process) {
// First find the first null slot
int firstNullIndex = -1;
for (int i = 0; i < NumProc; i++) {
if (windows[i] == null) {
firstNullIndex = i;
break;
}
}
if (firstNullIndex == -1) {
Console.WriteLine("No slots available for tracking");
return;
}
// Compact the array by shifting non-null elements to the left
for (int i = firstNullIndex + 1; i < NumProc; i++) {
if (windows[i] != null) {
windows[firstNullIndex] = windows[i];
windows[i] = null;
firstNullIndex++;
}
}
// Add the new process at the first null slot
windows[firstNullIndex] = process;
Console.WriteLine(
$"Added {process.ProcessName} to tracked windows at index {firstNullIndex}");
}
// private static void TrackWows() {
// CleanWindows();
// var processes = Process.GetProcesses().OrderBy(p => p.Id).ToList();
// foreach (var process in processes) {
// if (process.ProcessName == "UWow-64") {
// Console.WriteLine($"Found UWow-64 at pid {process.Id}");
// if (ProcessTracked(process.Id))
// continue;
// Track(process);
// }
// }
// }
private static void Swap(int index) {
index = (index - 1) % NumProc;
if (index < 0)
index = NumProc - 1;
if (index >= NumProc)
return;
CleanWindows();
Console.WriteLine($"Swapping window at index {index}");
var process = GetForegroundProcess();
if (process == null)
return;
Console.WriteLine($"Foreground process: {process}");
bool found = false;
for (int i = 0; i < NumProc; i++) {
var window = windows[i];
if (window != null && window.Id == process.Id) {
found = true;
break;
}
}
if (!found) {
for (int i = 0; i < NumProc; i++) {
var window = windows[i];
if (window == null) {
Console.WriteLine(
$"Adding foreground window to tracked at index {i}...");
windows[i] = process;
break;
}
}
}
for (int i = 0; i < NumProc; i++) {
var window = windows[i];
if (window != null && window.Id == process.Id) {
windows[i] = windows[index];
windows[index] = window;
Console.WriteLine($"Swapped window at index {i} to {index}");
// Toggle scroll lock off
keybd_event(0x91, 0, 0, 0); // KEYEVENTF_KEYDOWN
keybd_event(0x91, 0, 0x0002, 0); // KEYEVENTF_KEYUP
return;
}
}
}
private static void TabTo(int index) {
index = (index - 1) % NumProc;
if (index < 0)
index = NumProc - 1;
if (index >= NumProc)
return;
CleanWindows();
Console.WriteLine($"Tab to window at index {index}");
var window = windows[index];
if (window == null || window.MainWindowHandle == IntPtr.Zero) {
Console.WriteLine(
$"Window at index {index} does not exist, removing from tracked windows");
windows[index] = null;
} else {
SetForegroundWindow(window.MainWindowHandle);
ActiveIndex = index;
AdjustAffinities();
AdjustPriorities();
}
}
private static bool IsScrollLockOn() { return (GetKeyState(0x91) & 1) == 1; }
[STAThread]
private static void Main() {
// AllocConsole();
var processes = Process.GetProcesses();
var currentProcess = Process.GetCurrentProcess();
foreach (var process in processes)
if (process.Id != currentProcess.Id &&
process.ProcessName == currentProcess.ProcessName) {
process.Kill();
Process.GetCurrentProcess().Kill();
}
HotKeyManager.RegisterHotKey(Keys.Scroll, KeyModifiers.NoRepeat);
// Register main number keys (0-9)
for (int i = 0; i < 10; i++)
HotKeyManager.RegisterHotKey(Keys.D0 + i, KeyModifiers.Alt);
// Register numpad keys (1-9)
for (int i = 0; i < 9; i++)
HotKeyManager.RegisterHotKey(Keys.NumPad1 + i, KeyModifiers.Alt);
HotKeyManager.RegisterHotKey(Keys.Oemtilde, KeyModifiers.Alt);
HotKeyManager.HotKeyPressed += HotKeyManager_HotKeyPressed;
void HotKeyManager_HotKeyPressed(object sender, HotKeyEventArgs e) {
// if (e.Key == Keys.Oemtilde && e.Modifiers == KeyModifiers.Alt) {
// TrackWows();
// return;
// }
int index;
if (e.Key >= Keys.D0 && e.Key <= Keys.D9) {
index = e.Key - Keys.D0;
} else if (e.Key >= Keys.NumPad1 && e.Key <= Keys.NumPad9) {
index = 10 + (e.Key - Keys.NumPad1);
} else {
return;
}
if (e.Modifiers == KeyModifiers.Alt) {
if (IsScrollLockOn()) {
Swap(index);
} else {
TabTo(index);
}
}
}
Console.CancelKeyPress +=
(sender, e) => { Process.GetCurrentProcess().Kill(); };
while (true)
System.Threading.Thread.Sleep(100000);
}
}
}

View File

@@ -0,0 +1,35 @@
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly:AssemblyTitle("DD2Switcher")]
[assembly:AssemblyDescription("")]
[assembly:AssemblyConfiguration("")]
[assembly:AssemblyCompany("")]
[assembly:AssemblyProduct("DD2Switcher")]
[assembly:AssemblyCopyright("Copyright © 2022")]
[assembly:AssemblyTrademark("")]
[assembly:AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly:ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to
// COM
[assembly:Guid("2AC26899-8E27-4B96-85A9-C387186EAD27")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// 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("1.0.0.0")]
[assembly:AssemblyFileVersion("1.0.0.0")]

View File

@@ -0,0 +1,63 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace DD2Switcher.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute(
"System.Resources.Tools.StronglyTypedResourceBuilder", "4.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() {}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[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(
"DD2Switcher.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(
global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get { return resourceCulture; }
set { resourceCulture = value; }
}
}
}

View File

@@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"
id="root"
xmlns="">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string"/>
<xsd:attribute name="type" type="xsd:string"/>
<xsd:attribute name="mimetype" type="xsd:string"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string"/>
<xsd:attribute name="name" type="xsd:string"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1"/>
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"/>
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
</value>
</resheader>
</root>

View File

@@ -0,0 +1,26 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace DD2Switcher.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute(
"Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator",
"11.0.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; }
}
}
}

View File

@@ -0,0 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)"/>
</Profiles>
<Settings/>
</SettingsFile>

BIN
DD2Switcher/beep.wav Normal file

Binary file not shown.

BIN
duck2.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

14
go.mod
View File

@@ -1,14 +0,0 @@
module multiboxer
go 1.23.2
require (
fyne.io/systray v1.11.0
github.com/robotn/gohook v0.41.0
)
require (
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/vcaesar/keycode v0.10.1 // indirect
golang.org/x/sys v0.15.0 // indirect
)

12
go.sum
View File

@@ -1,12 +0,0 @@
fyne.io/systray v1.11.0 h1:D9HISlxSkx+jHSniMBR6fCFOUjk1x/OOOJLa9lJYAKg=
fyne.io/systray v1.11.0/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/robotn/gohook v0.41.0 h1:h1vK3w/UQpq0YkIiGnxm9Awv85W54esL0/NUYGueggo=
github.com/robotn/gohook v0.41.0/go.mod h1:FedpuAkVqzM5t67L5fcf3hSSCUDO9cM5YkWCw1U+nuc=
github.com/vcaesar/keycode v0.10.1 h1:0DesGmMAPWpYTCYddOFiCMKCDKgNnwiQa2QXindVUHw=
github.com/vcaesar/keycode v0.10.1/go.mod h1:JNlY7xbKsh+LAGfY2j4M3znVrGEm5W1R8s/Uv6BJcfQ=
github.com/vcaesar/tt v0.20.0 h1:9t2Ycb9RNHcP0WgQgIaRKJBB+FrRdejuaL6uWIHuoBA=
github.com/vcaesar/tt v0.20.0/go.mod h1:GHPxQYhn+7OgKakRusH7KJ0M5MhywoeLb8Fcffs/Gtg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=

145
main.go
View File

@@ -1,145 +0,0 @@
package main
import (
"fmt"
"io"
"log"
"os"
"slices"
"strconv"
"syscall"
// _ "embed"
"fyne.io/systray"
hook "github.com/robotn/gohook"
)
var (
us32 = syscall.MustLoadDLL("user32.dll")
SetForegroundWindow = us32.MustFindProc("SetForegroundWindow")
GetForegroundWindow = us32.MustFindProc("GetForegroundWindow")
)
// //go:embed duck2.jpg
// var icon []byte
var Error *log.Logger
var Warning *log.Logger
func init() {
log.SetFlags(log.Lmicroseconds | log.Lshortfile)
logFile, err := os.Create("main.log")
if err != nil {
log.Printf("Error creating log file: %v", err)
os.Exit(1)
}
logger := io.MultiWriter(os.Stdout, logFile)
log.SetOutput(logger)
Error = log.New(io.MultiWriter(logFile, os.Stderr, os.Stdout),
fmt.Sprintf("%sERROR:%s ", "\033[0;101m", "\033[0m"),
log.Lmicroseconds|log.Lshortfile)
Warning = log.New(io.MultiWriter(logFile, os.Stdout),
fmt.Sprintf("%sWarning:%s ", "\033[0;93m", "\033[0m"),
log.Lmicroseconds|log.Lshortfile)
}
var windows = make([]uintptr, 10, 10)
func main() {
go systray.Run(onReady, nil)
hook.Register(hook.KeyDown, []string{"alt", "-"}, func(e hook.Event) {
hwnd, _, _ := GetForegroundWindow.Call()
TrackWindow(hwnd)
})
for i := 1; i <= 9; i++ {
tabIndex := i - 1
log.Printf("Registering tab %d", tabIndex)
hook.Register(hook.KeyDown, []string{"alt", strconv.Itoa(i)}, func(e hook.Event) {
TabTo(tabIndex)
})
hook.Register(hook.KeyDown, []string{"alt", "shift", strconv.Itoa(i)}, func(e hook.Event) {
Swap(tabIndex)
})
}
s := hook.Start()
<-hook.Process(s)
}
func TabTo(idx int) {
log.Printf("TabTo: %d", idx)
log.Printf("%+v", windows)
if idx > cap(windows)-1 {
Warning.Printf("idx %d is greater than length of windows %d", idx, cap(windows))
return
}
hwnd := windows[idx]
if hwnd == 0 {
Warning.Printf("hwnd for idx %d is 0", idx)
return
}
SetForegroundWindow.Call(hwnd)
}
func Swap(idx int) {
log.Printf("Swap: %d", idx)
if idx > cap(windows)-1 {
Warning.Printf("idx %d is greater than length of windows %d", idx, cap(windows))
return
}
hwnd, _, _ := GetForegroundWindow.Call()
if !IsTracked(hwnd) {
TrackWindow(hwnd)
}
log.Printf("Swapping %d to %d", hwnd, idx)
for i := 0; i < cap(windows); i++ {
if windows[i] == hwnd {
windows[i] = windows[idx]
windows[idx] = hwnd
log.Printf("Swapped %d to %d at index %d", hwnd, idx, i)
break
}
}
log.Printf("windows swapped: %+v", windows)
}
func TrackWindow(hwnd uintptr) {
if IsTracked(hwnd) {
log.Printf("Untracking window: %d", hwnd)
windows = slices.DeleteFunc(windows, func(i uintptr) bool {
return i == hwnd
})
} else {
log.Printf("Tracking window: %d", hwnd)
for i := 0; i < cap(windows); i++ {
if windows[i] == 0 {
windows[i] = hwnd
log.Printf("Tracked window: %d at index %d", hwnd, i)
return
}
}
}
}
func IsTracked(hwnd uintptr) bool {
return slices.Contains(windows, hwnd)
}
func onReady() {
// Icons no workey... idk why...
// systray error: unable to set icon: The operation completed successfully.
//systray.SetIcon(icon)
systray.SetTitle("Awesome App")
systray.SetTooltip("Pretty awesome超级棒")
mQuit := systray.AddMenuItem("Quit", "Quit the whole app")
go func() {
<-mQuit.ClickedCh
fmt.Println("Requesting quit")
systray.Quit()
os.Exit(0)
// TODO: Quit properly, not this exit bullshit
fmt.Println("Finished quitting")
}()
// Sets the icon of a menu item. Only available on Mac and Windows.
//mQuit.SetIcon(icon)
}