Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
ec0bcfc
Merge pull request #12 from hbashton/gamebar
hbashton Jun 5, 2026
9a3119c
Fix trigger full pull threshold
hbashton Jun 5, 2026
f9afbb0
Prioritize Game Bar profiles over auto profiles
hbashton Jun 6, 2026
d19d922
Quiet Game Bar profile logging
hbashton Jun 6, 2026
fda9679
Update fork branding and release checks
hbashton Jun 6, 2026
aa516b9
Use hbashton DS4Updater releases
hbashton Jun 6, 2026
2b8b9fb
Update README.md
hbashton Jun 6, 2026
77f7473
Prepare DS4Windows 4.0.0 release
hbashton Jun 6, 2026
da56754
Upload CI builds as DS4Windows folder artifacts
hbashton Jun 6, 2026
9f3351f
fix: preserve interrupt pipe for copycat controllers over BT
anagnorisis2peripeteia Apr 26, 2026
768972a
Merge pull request #13 from hbashton/merge-ds4windowsapp-pr-9
hbashton Jun 6, 2026
5cf4ecf
Bump version and simplify tray tooltip
hbashton Jun 6, 2026
e8db7b6
Fix duplicate auto-profile crash handling
hbashton Jun 7, 2026
25078b6
Reduce profile switch action input stalls
hbashton Jun 7, 2026
b38f649
Merge duplicate auto profile crash fix
hbashton Jun 7, 2026
15f650a
Merge profile switch lag fix
hbashton Jun 7, 2026
6a0ca1a
Bump version to 4.0.2
hbashton Jun 7, 2026
2ef63ca
Fix changelog markdown spacing
hbashton Jun 8, 2026
3bed4a4
Prefer virtual mouse for stick mouse profiles
hbashton Jun 8, 2026
37000d4
Add virtual controller crash diagnostics
hbashton Jun 8, 2026
bd54275
Fix virtual crash diagnostic build
hbashton Jun 8, 2026
5cbaed6
Trace virtual DS4 acceptance decisions
hbashton Jun 8, 2026
760e694
Harden virtual DS4 output registration race
hbashton Jun 9, 2026
32ee486
Remove virtual controller diagnostic logging
hbashton Jun 9, 2026
260ee7f
Merge virtual controller crash fix
hbashton Jun 9, 2026
0e8294f
feat: add OpenRGB lightbar sync via embedded SDK server
Jun 9, 2026
da65d1a
feat: use HidHide session blacklist for automatic device unhiding on …
Jun 9, 2026
2a45afe
Reset touchpad movement state on profile load
hbashton Jun 11, 2026
0aa7571
Merge pull request #21 from anagnorisis2peripeteia/feature/hidhide-se…
hbashton Jun 11, 2026
ba8d6a9
Merge pull request #20 from anagnorisis2peripeteia/feature/openrgb-li…
hbashton Jun 11, 2026
a5fc709
Ignore stale virtual DS4 outputs on startup
hbashton Jun 11, 2026
a6bef75
Bump version to 4.0.2.1
hbashton Jun 11, 2026
c83c516
Harden DS4Windows shortcut relaunch guard
hbashton Jun 11, 2026
6afd8c8
Avoid ViGEm dispose crash during app exit
hbashton Jun 11, 2026
147c313
Add gated startup crash diagnostics
hbashton Jun 11, 2026
bc7a76c
Move Game Bar profile polling off report thread
hbashton Jun 11, 2026
38d94e2
Marshal tray battery updates to UI thread
hbashton Jun 11, 2026
201ce69
Harden virtual output shutdown and relaunch
hbashton Jun 11, 2026
e168c98
Harden Game Bar and auto profile polling
hbashton Jun 11, 2026
3a25a34
Fix auto profile process lookup compile error
hbashton Jun 11, 2026
c763967
Fix tray controller list startup race
hbashton Jun 11, 2026
b57ba5b
Restore nonblocking Game Bar API detection
hbashton Jun 11, 2026
5985a1b
Use MTA Game Bar API polling
hbashton Jun 11, 2026
8d71bc7
Add verbose Game Bar detection diagnostics
hbashton Jun 11, 2026
f9487ca
Isolate Game Bar visibility probe
hbashton Jun 11, 2026
f60e7b3
Suppress Game Bar probe startup cursor
hbashton Jun 11, 2026
4aad1fe
Merge pull request #24 from hbashton/startup-crash-diag
hbashton Jun 11, 2026
3a7e455
Show full launch program path in special actions
hbashton Jun 11, 2026
319190d
Handle missing Xbox Game Bar protocol
hbashton Jun 12, 2026
15a28fe
Bump version to 4.0.2.2
hbashton Jun 14, 2026
bd3b6b4
Ignore beta releases in updater checks
hbashton Jun 14, 2026
9378442
Detect Moonlight virtual DS4 v2 controllers
hbashton Jun 14, 2026
e3a2a19
Harden VIIPER HidHide containment
hbashton Jun 14, 2026
ba4bdcb
Bump version to 4.0.2.3
hbashton Jun 14, 2026
f321b56
Remove unreferenced private members
anagnorisis2peripeteia Jul 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions .github/workflows/ci-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,19 @@ jobs:
- name: Package ${{ matrix.platform }}
run: python .\utils\post-build.py .\bin\${{ matrix.platform }}\Release\output . ${{ env.VERSION }}

- name: Stage ${{ matrix.platform }} artifact folder
shell: pwsh
run: |
$artifactRoot = ".\bin\${{ matrix.platform }}\Release\artifact"
New-Item -ItemType Directory -Path $artifactRoot -Force | Out-Null
Copy-Item -Path ".\bin\${{ matrix.platform }}\Release\DS4Windows" -Destination $artifactRoot -Recurse -Force

- name: Upload ${{ matrix.platform }} artifact
id: upload
uses: actions/upload-artifact@v4
with:
name: DS4Windows-${{ matrix.platform }}
path: .\bin\${{ matrix.platform }}\Release\DS4Windows_${{ env.VERSION }}_${{ matrix.platform }}.zip
name: DS4Windows_${{ env.VERSION }}_${{ matrix.platform }}
path: .\bin\${{ matrix.platform }}\Release\artifact
if-no-files-found: error

- name: Publish artifact link to run summary
Expand All @@ -92,5 +99,5 @@ jobs:
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "Version: \`${{ env.VERSION }}\`" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "[Download DS4Windows-${{ matrix.platform }} (zip)](${{ steps.upload.outputs.artifact-url }})" >> "$GITHUB_STEP_SUMMARY"
echo "[Download DS4Windows_${{ env.VERSION }}_${{ matrix.platform }} (contains DS4Windows folder)](${{ steps.upload.outputs.artifact-url }})" >> "$GITHUB_STEP_SUMMARY"

6 changes: 3 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,18 @@ jobs:
echo "SHORT_VERSION=${VERSION%-*}" >> $GITHUB_ENV
shell: bash
- name: Build X64
run: dotnet publish .\DS4Windows\DS4WinWPF.csproj -c Release /p:platform=x64 /p:AssemblyVersion=${{ env.SHORT_VERSION }} /p:FileVersion=${{ env.SHORT_VERSION }} /p:Version=${{ env.VERSION }} -o .\bin\x64\Release\output
run: dotnet publish .\DS4Windows\DS4WinWPF.csproj -c Release /p:platform=x64 /p:AssemblyVersion=${{ env.SHORT_VERSION }} /p:FileVersion=${{ env.SHORT_VERSION }} /p:Version=${{ env.VERSION }} /p:InformationalVersion=${{ env.VERSION }} -o .\bin\x64\Release\output
- name: Post-Build script X64
run: python .\utils\post-build.py .\bin\x64\Release\output . ${{env.VERSION}}
- name: Publish release Build X64
run: gh release upload ${{github.event.release.tag_name}} .\bin\x64\Release\DS4Windows_${{env.VERSION}}_x64.zip
env:
GITHUB_TOKEN: ${{ github.TOKEN }}
- name: Build X86
run: dotnet publish .\DS4Windows\DS4WinWPF.csproj -c Release /p:platform=x86 /p:AssemblyVersion=${{ env.SHORT_VERSION }} /p:FileVersion=${{ env.SHORT_VERSION }} /p:Version=${{ env.VERSION }} -o .\bin\x86\Release\output
run: dotnet publish .\DS4Windows\DS4WinWPF.csproj -c Release /p:platform=x86 /p:AssemblyVersion=${{ env.SHORT_VERSION }} /p:FileVersion=${{ env.SHORT_VERSION }} /p:Version=${{ env.VERSION }} /p:InformationalVersion=${{ env.VERSION }} -o .\bin\x86\Release\output
- name: Post-Build script X86
run: python .\utils\post-build.py .\bin\x86\Release\output . ${{env.VERSION}}
- name: Publish release Build X86
run: gh release upload ${{github.event.release.tag_name}} .\bin\x86\Release\DS4Windows_${{env.VERSION}}_x86.zip
env:
GITHUB_TOKEN: ${{ github.TOKEN }}
GITHUB_TOKEN: ${{ github.TOKEN }}
136 changes: 126 additions & 10 deletions DS4Windows/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ You should have received a copy of the GNU General Public License
using System.IO.MemoryMappedFiles;
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -89,10 +91,30 @@ private void Application_Startup(object sender, StartupEventArgs e)
runShutdown = true;
skipSave = true;

if (DS4Windows.GameBarIntegration.TryRunProbeCommand(e.Args))
{
runShutdown = false;
Current.Shutdown();
return;
}

ArgumentParser parser = new ArgumentParser();
parser.Parse(e.Args);
CheckOptions(parser);

try
{
string exeDir = Path.GetDirectoryName(DS4Windows.Global.exelocation);
if (!string.IsNullOrEmpty(exeDir))
{
Environment.CurrentDirectory = exeDir;
}
}
catch
{
// Keep startup going. A bad working directory should not block DS4Windows.
}

if (exitApp)
{
return;
Expand All @@ -119,8 +141,8 @@ private void Application_Startup(object sender, StartupEventArgs e)
try
{
if (EventWaitHandleAcl.TryOpenExisting(SingleAppComEventName,
System.Security.AccessControl.EventWaitHandleRights.Synchronize |
System.Security.AccessControl.EventWaitHandleRights.Modify,
EventWaitHandleRights.Synchronize |
EventWaitHandleRights.Modify,
out EventWaitHandle tempComEvent))
{
tempComEvent.Set(); // signal the other instance.
Expand All @@ -133,7 +155,11 @@ private void Application_Startup(object sender, StartupEventArgs e)
}
catch (System.UnauthorizedAccessException)
{
// Ignore exception
// An existing elevated instance can deny this process access if it was
// started by an older build. Do not continue into a second mapper.
runShutdown = false;
Current.Shutdown();
return;
}

// Allow sleep time durations less than 16 ms
Expand All @@ -143,7 +169,18 @@ private void Application_Startup(object sender, StartupEventArgs e)
DS4Windows.Global.RefreshViGEmBusInfo();

// Create the Event handle
threadComEvent = new EventWaitHandle(false, EventResetMode.ManualReset, SingleAppComEventName);
try
{
threadComEvent = CreateSingleAppComEvent();
}
catch (UnauthorizedAccessException)
{
// Another elevated instance can win the race with older event security.
runShutdown = false;
Current.Shutdown();
return;
}

CreateTempWorkerThread();

CreateControlService(parser);
Expand Down Expand Up @@ -181,7 +218,7 @@ private void Application_Startup(object sender, StartupEventArgs e)
DispatcherUnhandledException += App_DispatcherUnhandledException;
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
Logger logger = logHolder.Logger;
string version = DS4Windows.Global.exeversion;
string version = DS4Windows.Global.exeDisplayVersion;
logger.Info($"DS4Windows version {version}");
logger.Info($"DS4Windows exe file: {DS4Windows.Global.exeFileName}");
logger.Info($"DS4Windows Assembly Architecture: {(Environment.Is64BitProcess ? "x64" : "x86")}");
Expand All @@ -190,8 +227,12 @@ private void Application_Startup(object sender, StartupEventArgs e)
logger.Info($"OS Release ID: {DS4Windows.Util.GetOSReleaseId()}");
logger.Info($"System Architecture: {(Environment.Is64BitOperatingSystem ? "x64" : "x86")}");
logger.Info("Logger created");
StartupDiag(logger, $"App bootstrap pid={Environment.ProcessId} admin={DS4Windows.Global.IsAdministrator()} cwd=\"{Environment.CurrentDirectory}\" cmd=\"{Environment.CommandLine}\"");
StartupDiag(logger, $"Exe location=\"{DS4Windows.Global.exelocation}\" configPath=\"{DS4Windows.Global.appdatapath}\" firstRun={firstRun}");

StartupDiag(logger, "Global.Load begin");
bool readAppConfig = DS4Windows.Global.Load();
StartupDiag(logger, $"Global.Load end readAppConfig={readAppConfig}");
if (!firstRun && !readAppConfig)
{
logger.Info($@"Profiles.xml not read at location ${DS4Windows.Global.appdatapath}\Profiles.xml. Using default app settings");
Expand Down Expand Up @@ -224,26 +265,41 @@ private void Application_Startup(object sender, StartupEventArgs e)

skipSave = false;

StartupDiag(logger, "Global.LoadActions begin");
if (!DS4Windows.Global.LoadActions())
{
StartupDiag(logger, "Global.LoadActions failed; CreateStdActions begin");
DS4Windows.Global.CreateStdActions();
StartupDiag(logger, "CreateStdActions end");
}
else
{
StartupDiag(logger, "Global.LoadActions end success");
}

// Have app use selected culture
SetUICulture(DS4Windows.Global.UseLang);
DS4Windows.AppThemeChoice themeChoice = DS4Windows.Global.UseCurrentTheme;
ChangeTheme(DS4Windows.Global.UseCurrentTheme, false);

StartupDiag(logger, "LoadLinkedProfiles begin");
DS4Windows.Global.LoadLinkedProfiles();
StartupDiag(logger, "LoadLinkedProfiles end");
StartupDiag(logger, "MainWindow ctor begin");
DS4Forms.MainWindow window = new DS4Forms.MainWindow(parser);
StartupDiag(logger, "MainWindow ctor end");
MainWindow = window;
window.IsInitialShow = true;
StartupDiag(logger, "MainWindow.Show begin");
window.Show();
StartupDiag(logger, "MainWindow.Show end");
window.IsInitialShow = false;

// Set up hooks for IPC command calls
HwndSource source = PresentationSource.FromVisual(window) as HwndSource;
StartupDiag(logger, "CreateIPCClassNameMMF begin");
CreateIPCClassNameMMF(source.Handle);
StartupDiag(logger, "CreateIPCClassNameMMF end");

window.CheckMinStatus();

Expand All @@ -252,11 +308,28 @@ private void Application_Startup(object sender, StartupEventArgs e)

if (DS4Windows.Global.hidHideInstalled)
{
StartupDiag(logger, "CheckHidHidePresence begin");
rootHub.CheckHidHidePresence();
StartupDiag(logger, "CheckHidHidePresence end");
}

StartupDiag(logger, "LoadPermanentSlotsConfig begin");
rootHub.LoadPermanentSlotsConfig();
StartupDiag(logger, "LoadPermanentSlotsConfig end");
StartupDiag(logger, "MainWindow.LateChecks begin");
window.LateChecks(parser);
StartupDiag(logger, "MainWindow.LateChecks returned");
}

private static void StartupDiag(Logger logger, string message)
{
if (!DS4Windows.Global.VerboseStartupLogging)
{
return;
}

logger.Info($"[StartupDiag][T{Thread.CurrentThread.ManagedThreadId}] {message}");
LogManager.Flush(TimeSpan.FromSeconds(1));
}

private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
Expand Down Expand Up @@ -293,6 +366,29 @@ private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionE
}
}

private static EventWaitHandle CreateSingleAppComEvent()
{
EventWaitHandleSecurity security = new EventWaitHandleSecurity();
EventWaitHandleRights appRights = EventWaitHandleRights.Synchronize |
EventWaitHandleRights.Modify |
EventWaitHandleRights.ReadPermissions;

SecurityIdentifier authenticatedUsersSid = new SecurityIdentifier(
WellKnownSidType.AuthenticatedUserSid, null);
security.AddAccessRule(new EventWaitHandleAccessRule(authenticatedUsersSid,
appRights, AccessControlType.Allow));

SecurityIdentifier currentUserSid = WindowsIdentity.GetCurrent().User;
if (currentUserSid != null)
{
security.AddAccessRule(new EventWaitHandleAccessRule(currentUserSid,
EventWaitHandleRights.FullControl, AccessControlType.Allow));
}

return EventWaitHandleAcl.Create(false, EventResetMode.ManualReset,
SingleAppComEventName, out _, security);
}

private void App_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
{
//Debug.WriteLine("App Crashed");
Expand Down Expand Up @@ -746,16 +842,28 @@ private void CleanShutdown()
{
if (runShutdown)
{
bool shutdownTimedOut = false;
if (rootHub != null)
{
Task.Run(() =>
Task shutdownTask = Task.Run(() =>
{
if (rootHub.running)
{
rootHub.Stop(immediateUnplug: true);
rootHub.Stop(immediateUnplug: true, disposeViGEm: false);
rootHub.ShutDown();
}
}).Wait();
});

if (!shutdownTask.Wait(TimeSpan.FromSeconds(8)))
{
shutdownTimedOut = true;
try
{
logHolder?.Logger?.Warn("Timed out while stopping controller service during shutdown. Forcing process exit to avoid a stale single-instance lock.");
rootHub.PrepareAbort();
}
catch { }
}
}

if (!skipSave)
Expand All @@ -770,15 +878,23 @@ private void CleanShutdown()
if (threadComEvent != null)
{
threadComEvent.Set(); // signal the other instance.
while (testThread.IsAlive)
Thread.SpinWait(500);
if (testThread != null && !testThread.Join(2000))
{
shutdownTimedOut = true;
logHolder?.Logger?.Warn("Timed out waiting for single-instance worker thread to exit.");
}
threadComEvent.Close();
}

if (ipcClassNameMMF != null) ipcClassNameMMF.Dispose();

LogManager.Flush();
LogManager.Shutdown();

if (shutdownTimedOut)
{
Environment.Exit(0);
}
}
}
}
Expand Down
Loading
Loading