Skip to content

Commit 466967c

Browse files
committed
Attempt to reuse an existing Git Extensions window
1 parent f07f4ef commit 466967c

File tree

3 files changed

+312
-0
lines changed

3 files changed

+312
-0
lines changed

GitExtBar/GitExtBar.cs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,17 @@
77
using System.IO;
88
using System.Reflection;
99
using GitExtBar.Properties;
10+
using System.Text;
1011

1112
namespace GitExtBar
1213
{
1314
public partial class GitExtBar : Form
1415
{
1516
public class GitExtAction
1617
{
18+
[System.Runtime.InteropServices.DllImport("user32.dll")]
19+
public static extern void SwitchToThisWindow(IntPtr hWnd, bool fAltTab);
20+
1721
public string Name;
1822
public string Command;
1923
public Bitmap Icon;
@@ -39,7 +43,39 @@ public void Run()
3943
var fileName = Command.Substring(0, splitIdx);
4044
var arguments = Command.Substring(splitIdx + 1);
4145
var currentDirectory = Directory.GetCurrentDirectory();
46+
var gitRoot = Git("rev-parse --show-toplevel");
47+
if (!string.IsNullOrEmpty(gitRoot))
48+
currentDirectory = Path.GetFullPath(gitRoot.Replace('/', '\\'));
4249
arguments = arguments.Replace("$currentdir$", currentDirectory);
50+
51+
// Attempt to put an existing Git Extensions window in focus
52+
try
53+
{
54+
var processes = Process.GetProcessesByName("gitextensions");
55+
foreach (var process in processes)
56+
{
57+
var mainWindow = process.MainWindowHandle;
58+
if (mainWindow == IntPtr.Zero)
59+
continue;
60+
61+
ProcessCommandLine.Retrieve(process, out var workingDirectory, ProcessCommandLine.Parameter.WorkingDirectory);
62+
workingDirectory = workingDirectory.TrimEnd('\\');
63+
if (workingDirectory.Equals(currentDirectory, StringComparison.OrdinalIgnoreCase))
64+
{
65+
ProcessCommandLine.Retrieve(process, out var commandLine, ProcessCommandLine.Parameter.CommandLine);
66+
if (commandLine.Contains(arguments))
67+
{
68+
SwitchToThisWindow(mainWindow, false);
69+
return;
70+
71+
}
72+
}
73+
}
74+
}
75+
catch
76+
{
77+
}
78+
4379
Process.Start(new ProcessStartInfo
4480
{
4581
FileName = fileName,
@@ -83,6 +119,8 @@ public GitExtBar()
83119
}
84120
else
85121
{
122+
var processes = Process.GetProcesses();
123+
var kurwa = new StringBuilder();
86124
_actions = new GitExtAction[]
87125
{
88126
new GitExtAction("&Commit", "GitExtensions.exe:commit", Resources.IconCommit),

GitExtBar/GitExtBar.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
<Compile Include="GitExtBar.Designer.cs">
5858
<DependentUpon>GitExtBar.cs</DependentUpon>
5959
</Compile>
60+
<Compile Include="ProcessCommandLine.cs" />
6061
<Compile Include="Program.cs" />
6162
<Compile Include="Properties\AssemblyInfo.cs" />
6263
<EmbeddedResource Include="GitExtBar.resx">

GitExtBar/ProcessCommandLine.cs

Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
// Source: https://github.com/sonicmouse/ProcCmdLine (MIT)
2+
3+
using System;
4+
using System.Collections.Generic;
5+
using System.ComponentModel;
6+
using System.Diagnostics;
7+
using System.Linq;
8+
using System.Runtime.InteropServices;
9+
10+
public static class ProcessCommandLine
11+
{
12+
private static class Win32Native
13+
{
14+
public const uint PROCESS_BASIC_INFORMATION = 0;
15+
16+
[Flags]
17+
public enum OpenProcessDesiredAccessFlags : uint
18+
{
19+
PROCESS_VM_READ = 0x0010,
20+
PROCESS_QUERY_INFORMATION = 0x0400,
21+
}
22+
23+
[StructLayout(LayoutKind.Sequential)]
24+
public struct ProcessBasicInformation
25+
{
26+
public IntPtr Reserved1;
27+
public IntPtr PebBaseAddress;
28+
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
29+
public IntPtr[] Reserved2;
30+
public IntPtr UniqueProcessId;
31+
public IntPtr Reserved3;
32+
}
33+
34+
[StructLayout(LayoutKind.Sequential)]
35+
public struct UnicodeString
36+
{
37+
public ushort Length;
38+
public ushort MaximumLength;
39+
public IntPtr Buffer;
40+
}
41+
42+
// This is not the real struct!
43+
// I faked it to get ProcessParameters address.
44+
// Actual struct definition:
45+
// https://docs.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb
46+
[StructLayout(LayoutKind.Sequential)]
47+
public struct PEB
48+
{
49+
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
50+
public IntPtr[] Reserved;
51+
public IntPtr ProcessParameters;
52+
}
53+
54+
[StructLayout(LayoutKind.Sequential)]
55+
public struct RtlUserProcessParameters
56+
{
57+
public uint MaximumLength;
58+
public uint Length;
59+
public uint Flags;
60+
public uint DebugFlags;
61+
public IntPtr ConsoleHandle;
62+
public uint ConsoleFlags;
63+
public IntPtr StandardInput;
64+
public IntPtr StandardOutput;
65+
public IntPtr StandardError;
66+
public UnicodeString CurrentDirectory;
67+
public IntPtr CurrentDirectoryHandle;
68+
public UnicodeString DllPath;
69+
public UnicodeString ImagePathName;
70+
public UnicodeString CommandLine;
71+
}
72+
73+
[DllImport("ntdll.dll")]
74+
public static extern uint NtQueryInformationProcess(
75+
IntPtr ProcessHandle,
76+
uint ProcessInformationClass,
77+
IntPtr ProcessInformation,
78+
uint ProcessInformationLength,
79+
out uint ReturnLength);
80+
81+
[DllImport("kernel32.dll")]
82+
public static extern IntPtr OpenProcess(
83+
OpenProcessDesiredAccessFlags dwDesiredAccess,
84+
[MarshalAs(UnmanagedType.Bool)] bool bInheritHandle,
85+
uint dwProcessId);
86+
87+
[DllImport("kernel32.dll")]
88+
[return: MarshalAs(UnmanagedType.Bool)]
89+
public static extern bool ReadProcessMemory(
90+
IntPtr hProcess, IntPtr lpBaseAddress, IntPtr lpBuffer,
91+
uint nSize, out uint lpNumberOfBytesRead);
92+
93+
[DllImport("kernel32.dll")]
94+
[return: MarshalAs(UnmanagedType.Bool)]
95+
public static extern bool CloseHandle(IntPtr hObject);
96+
97+
[DllImport("shell32.dll", SetLastError = true,
98+
CharSet = CharSet.Unicode, EntryPoint = "CommandLineToArgvW")]
99+
public static extern IntPtr CommandLineToArgv(string lpCmdLine, out int pNumArgs);
100+
}
101+
102+
private static bool ReadStructFromProcessMemory<TStruct>(
103+
IntPtr hProcess, IntPtr lpBaseAddress, out TStruct val)
104+
{
105+
val = default;
106+
var structSize = Marshal.SizeOf<TStruct>();
107+
var mem = Marshal.AllocHGlobal(structSize);
108+
try
109+
{
110+
if (Win32Native.ReadProcessMemory(
111+
hProcess, lpBaseAddress, mem, (uint)structSize, out var len) &&
112+
(len == structSize))
113+
{
114+
val = Marshal.PtrToStructure<TStruct>(mem);
115+
return true;
116+
}
117+
}
118+
finally
119+
{
120+
Marshal.FreeHGlobal(mem);
121+
}
122+
return false;
123+
}
124+
125+
public static string ErrorToString(int error) =>
126+
new string[]
127+
{
128+
"Success",
129+
"Failed to open process for reading",
130+
"Failed to query process information",
131+
"PEB address was null",
132+
"Failed to read PEB information",
133+
"Failed to read process parameters",
134+
"Failed to read parameter from process"
135+
}[Math.Abs(error)];
136+
137+
public enum Parameter
138+
{
139+
CommandLine,
140+
WorkingDirectory,
141+
}
142+
143+
public static int Retrieve(Process process, out string parameterValue, Parameter parameter = Parameter.CommandLine)
144+
{
145+
int rc = 0;
146+
parameterValue = null;
147+
var hProcess = Win32Native.OpenProcess(
148+
Win32Native.OpenProcessDesiredAccessFlags.PROCESS_QUERY_INFORMATION |
149+
Win32Native.OpenProcessDesiredAccessFlags.PROCESS_VM_READ, false, (uint)process.Id);
150+
if (hProcess != IntPtr.Zero)
151+
{
152+
try
153+
{
154+
var sizePBI = Marshal.SizeOf<Win32Native.ProcessBasicInformation>();
155+
var memPBI = Marshal.AllocHGlobal(sizePBI);
156+
try
157+
{
158+
var ret = Win32Native.NtQueryInformationProcess(
159+
hProcess, Win32Native.PROCESS_BASIC_INFORMATION, memPBI,
160+
(uint)sizePBI, out var len);
161+
if (0 == ret)
162+
{
163+
var pbiInfo = Marshal.PtrToStructure<Win32Native.ProcessBasicInformation>(memPBI);
164+
if (pbiInfo.PebBaseAddress != IntPtr.Zero)
165+
{
166+
if (ReadStructFromProcessMemory<Win32Native.PEB>(hProcess,
167+
pbiInfo.PebBaseAddress, out var pebInfo))
168+
{
169+
if (ReadStructFromProcessMemory<Win32Native.RtlUserProcessParameters>(
170+
hProcess, pebInfo.ProcessParameters, out var ruppInfo))
171+
{
172+
string ReadUnicodeString(Win32Native.UnicodeString unicodeString)
173+
{
174+
var clLen = unicodeString.MaximumLength;
175+
var memCL = Marshal.AllocHGlobal(clLen);
176+
try
177+
{
178+
if (Win32Native.ReadProcessMemory(hProcess,
179+
unicodeString.Buffer, memCL, clLen, out len))
180+
{
181+
rc = 0;
182+
return Marshal.PtrToStringUni(memCL);
183+
}
184+
else
185+
{
186+
// couldn't read parameter line buffer
187+
rc = -6;
188+
}
189+
}
190+
finally
191+
{
192+
Marshal.FreeHGlobal(memCL);
193+
}
194+
return null;
195+
}
196+
197+
switch (parameter)
198+
{
199+
case Parameter.CommandLine:
200+
parameterValue = ReadUnicodeString(ruppInfo.CommandLine);
201+
break;
202+
case Parameter.WorkingDirectory:
203+
parameterValue = ReadUnicodeString(ruppInfo.CurrentDirectory);
204+
break;
205+
}
206+
}
207+
else
208+
{
209+
// couldn't read ProcessParameters
210+
rc = -5;
211+
}
212+
}
213+
else
214+
{
215+
// couldn't read PEB information
216+
rc = -4;
217+
}
218+
}
219+
else
220+
{
221+
// PebBaseAddress is null
222+
rc = -3;
223+
}
224+
}
225+
else
226+
{
227+
// NtQueryInformationProcess failed
228+
rc = -2;
229+
}
230+
}
231+
finally
232+
{
233+
Marshal.FreeHGlobal(memPBI);
234+
}
235+
}
236+
finally
237+
{
238+
Win32Native.CloseHandle(hProcess);
239+
}
240+
}
241+
else
242+
{
243+
// couldn't open process for VM read
244+
rc = -1;
245+
}
246+
return rc;
247+
}
248+
249+
public static IReadOnlyList<string> CommandLineToArgs(string commandLine)
250+
{
251+
if (string.IsNullOrEmpty(commandLine)) { return Array.Empty<string>(); }
252+
253+
var argv = Win32Native.CommandLineToArgv(commandLine, out var argc);
254+
if (argv == IntPtr.Zero)
255+
{
256+
throw new Win32Exception(Marshal.GetLastWin32Error());
257+
}
258+
try
259+
{
260+
var args = new string[argc];
261+
for (var i = 0; i < args.Length; ++i)
262+
{
263+
var p = Marshal.ReadIntPtr(argv, i * IntPtr.Size);
264+
args[i] = Marshal.PtrToStringUni(p);
265+
}
266+
return args.ToList().AsReadOnly();
267+
}
268+
finally
269+
{
270+
Marshal.FreeHGlobal(argv);
271+
}
272+
}
273+
}

0 commit comments

Comments
 (0)