Skip to content

Commit

Permalink
Multiple stability improvements, fixes and a new foreground alogirhtm:
Browse files Browse the repository at this point in the history
* ForegroundProcessManager.cs: Added a new algorithm for detecting the foreground window. Older algorithm will be removed in a future version
* MainForm.cs:  1) added error handler in case of accessing a non-existing process
  2) cleaned up RunMuter() and added additional error checks
  3) added an inline function for muting processes. Fixed bug where processes are sometimes not muted
* VolumeMixer.cs: additional comments and logging
  • Loading branch information
nefares committed Nov 17, 2022
1 parent 57fbe11 commit 8fd91c6
Show file tree
Hide file tree
Showing 5 changed files with 240 additions and 41 deletions.
123 changes: 120 additions & 3 deletions WinBGMute/ForegroundProcessManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,21 +65,100 @@ UInt32 dwmsEventTime
private WinEventProcDelegate m_winEventProc;
private static ConcurrentStack<int> m_JobStack = new ConcurrentStack<int>();

private int m_lastForegroundId = -1;

public (bool, int) GetJobThreadSafe()
{
int pid;
bool success;

/*
* piece of code to output the contents of the stack
*
string output = "";
foreach (int i_pid in m_JobStack)
{
output += $"{i_pid} - ";
}
if (!m_JobStack.IsEmpty)
LoggingEngine.LogLine("" + output);
*/

if (m_JobStack.TryPop(out pid))
{

if (!m_JobStack.IsEmpty)
{
//LoggingEngine.LogLine("[!] discrading previous foreground processes ");
}
m_JobStack.Clear();


success = true;
}
else
{
pid = -1;
success = false;
}

/* EXPERIEMTNAL
* This will evaluate if (in rare cases) the current foreground PID from the event handler (WinEventProc) is equal to the PID provided by
* a new polling function (PollForegroundProcessId).
*
TODO: in the future, remove the whole WinEventProc (and the related stack) and replace them with the following piece of code which does the same job more reliably */
int poll_fpid = PollForegroundProcessId();

if (m_lastForegroundId != poll_fpid)
{
success = true;
m_lastForegroundId = poll_fpid;
}
else
{
success = false;
return (success, poll_fpid);

}


string poll_fname = "";
string fname = "";
if (poll_fpid != pid)
{
try
{
poll_fname = Process.GetProcessById(poll_fpid).ProcessName;

}
catch (Exception e)
{
poll_fname = "<unknown>";
}

try
{
fname = Process.GetProcessById(pid).ProcessName;

}
catch (Exception e)
{
fname = "<unknown>";
}

if (pid != -1)
LoggingEngine.LogLine($"[!] Assertion failed for {fname}({pid}) <> (polled){poll_fname}({poll_fpid}) ", Color.Yellow);

//overwriting pid!
pid = poll_fpid;




}


return (success, pid);

}
Expand All @@ -100,6 +179,20 @@ public void CleanUp()

}

[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();

public int PollForegroundProcessId()
{

uint processID = 0;
IntPtr hWnd = GetForegroundWindow(); // Get foreground window handle
uint threadID = GetWindowThreadProcessId(hWnd, out processID); // Get PID from window handle
//Process fgProc = Process.GetProcessById(Convert.ToInt32(processID)); // Get it as a C# obj.
// NOTE: In some rare cases ProcessID will be NULL. Handle this how you want.
return Convert.ToInt32(processID);
}

private void WinEventProc(
IntPtr hWinEventHook,
UInt32 ev,
Expand All @@ -111,6 +204,11 @@ UInt32 dwmsEventTime

)
{
uint testpid = 0;
GetWindowThreadProcessId(hwnd, out testpid);

int ftestpidpid = (int)testpid;

if (ev == EVENT_SYSTEM_FOREGROUND &&
idObject == OBJID_WINDOW &&
idChild == CHILDID_SELF)
Expand All @@ -119,9 +217,28 @@ UInt32 dwmsEventTime
GetWindowThreadProcessId(hwnd, out pid);

int fpid = (int)pid;
Process foreground = Process.GetProcessById(fpid);
var pname = foreground.ProcessName;
LoggingEngine.LogLine($"> Foreground Window Changed at {hwnd} {fpid} {pname}", Color.Blue);
Process? foreground = null;
try
{
foreground = Process.GetProcessById(fpid);
}
catch(Exception e)
{
LoggingEngine.LogLine($"[-] Foreground Window {hwnd} and/or process {fpid} do not exist => {e.Message}", Color.Red);
}
finally
{

}

string pname = String.Empty;

if (foreground != null)
{
pname = foreground.ProcessName;
}

LoggingEngine.LogLine($"[+] Foreground process changed to {pname} - {fpid}", Color.Cyan);

if ((fpid != 0) && (pname != String.Empty))
{
Expand Down
130 changes: 93 additions & 37 deletions WinBGMute/MainForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ public enum DWMWINDOWATTRIBUTE
private bool m_settingsChanged = false;
private bool m_enableMiniStart = false;
private bool m_enableDemo = false;
private int m_errorCount = 0;

// @todo untested whether this works
private static string m_previous_fname = "wininit";
Expand Down Expand Up @@ -114,40 +115,78 @@ private void InternalLogLine(object olog, object? ocolor = null, object? ofont =
InternalLog(log + Environment.NewLine, color, font);
}

private void HandleError(Exception ex, object? data=null)
{
m_errorCount += 1;
if (ex.InnerException is InvalidOperationException)
{
int pid = (data is int) ? (int)data : -1;
LoggingEngine.LogLine("[-] Process access failed for PID " + pid.ToString() + " @" + ex.Source, Color.Red);
m_volumeMixer.ReloadAudio(true);

}

else
{
LoggingEngine.LogLine("[-] Unknown error! " + ex.ToString(), Color.Red);
m_volumeMixer.ReloadAudio(true);

}

}
// stores previous foreground process name for fallback in case of error
private void RunMuter(int fpid, bool doMute = true)
{
// get a process PID list of processes with an audio channel
int[] pids = m_volumeMixer.GetPIDs();

Dictionary<int, (string, Process[])> dpids = new Dictionary<int, (string, Process[])>();
Dictionary<int, (string, Process[])> audio_procs = new Dictionary<int, (string, Process[])>();

// populate dictionary with KEY=<PID>, VALUE=tuple(<PROCESS_NAME>, <Process>)
// clear process list
ProcessListListBox.Items.Clear();
foreach (var pid in pids)

// get a process PID list of processes with an audio channel
int[] audio_pids = m_volumeMixer.GetPIDs();

// populate dictionary audio_procs for each PID in audio_pids with KEY=<PID>, VALUE=tuple(<PROCESS_NAME>, <Process>)
foreach (var pid in audio_pids)
{
try
{
{
Process proc = Process.GetProcessById(pid);
string pname = proc.ProcessName;

//add proc name to ListBox if it will be muted
if (!NeverMuteListBox.Items.Contains(pname))
{
ProcessListListBox.Items.Add(pname);
}

//gather all processes of the same name as the process of the current pid (workaround for some programs)
Process[] procs_similar = Process.GetProcessesByName(pname);
dpids.Add(pid, (pname, procs_similar));

//add all processes of the same name as pname to the audio_procs corresponding to PID
audio_procs.Add(pid, (pname, procs_similar));
}
catch (Exception ex)
{
LoggingEngine.LogLine("[-] PID access failed at: " + pid.ToString() + ex.ToString());
HandleError(ex, (object)pid);
}
finally
{

if (!audio_procs.ContainsKey(pid))
{
LoggingEngine.LogLine($"[-] PID with audio channel {pid} not found in process list (most likely due to an error)");
//throw new Exception();
Process[] empty_procs = { };
audio_procs.Add(pid, ("", empty_procs));

}
}
}

if (!doMute)
return;

// get foreground process. If failed, revert to last process
// get foreground process object. If failed (e.g. process exited), revert to last process
string fname = "";
try
{
Expand All @@ -158,49 +197,58 @@ private void RunMuter(int fpid, bool doMute = true)
catch(Exception ex)
{
fname = m_previous_fname;
LoggingEngine.LogLine($"[-] Process name not found for pid {fpid}. Reverting to {fname}. {ex.ToString()}");
LoggingEngine.LogLine($"[!] Process name not found for pid {fpid}. Reverting to {fname}. {ex.ToString()}",Color.Orange);
}

/*LoggingEngine.LogLine($"- [PIDs({dpids.Count}] - ");
foreach (var pid in pids)
{
LoggingEngine.Log("#", Color.Brown);
LoggingEngine.Log(dpids[pid].Item1, Color.Red);
LoggingEngine.LogLine($" - {dpids[pid].Item2.Length} - {dpids[pid].Item2[0].MainWindowTitle}");
}*/

foreach (var pid in pids)
//Inline function to mute/unmute a list of processes
Func<Process[], bool, string> InlineMuteProcList = (procs, isMuted) =>
{
if (!dpids.ContainsKey(pid))
string log_output = "";
foreach (var fproc_similar in procs)
{
LoggingEngine.LogLine($"[-] PID with audio channel {pid} not found in process list");
continue;

var fproc_similar_pid = fproc_similar.Id;
m_volumeMixer.SetApplicationMute(fproc_similar_pid, isMuted);
log_output +=".";
}
// unmute all foreground processes with the same name
if (dpids[pid].Item1 == fname)
//log_output += "\r\n";
return log_output;
};

string log_skipped = "";
string log_muted = "";

foreach (var item in audio_procs)
{
var audio_pid = item.Key;
var audio_pname = item.Value.Item1;
var audio_proc_list = item.Value.Item2;

// check if this is the foreground process
// if yes unmute all foreground processes with the same name
if (audio_pname == fname)
{
LoggingEngine.Log($"[Unmuting] {dpids[pid].Item1}({pid}) ", Color.BlueViolet);
foreach (var fproc_similar in dpids[pid].Item2)
{
m_volumeMixer.SetApplicationMute(fproc_similar.Id, false);
LoggingEngine.Log(".");
}
LoggingEngine.Log("\n\r");
string log_output = InlineMuteProcList(audio_proc_list, false);
LoggingEngine.LogLine($"[+] Unmuting foreground process {audio_pname}({audio_pid}) {log_output} ", Color.BlueViolet);
}
// mute all other processes
// mute all other processes (with an audio channel), except the ones on the neverMuteList
else
{
if (m_neverMuteList.Contains(dpids[pid].Item1))
if (m_neverMuteList.Contains(audio_pname))
{

//LoggingEngine.LogLine($" [!] Process {audio_pname}({audio_pid}) skipped ", Color.BlueViolet);
log_skipped += audio_pname + ", ";
}
else
{
m_volumeMixer.SetApplicationMute(pid, true);
m_volumeMixer.SetApplicationMute(audio_pid, true);
InlineMuteProcList(audio_proc_list, true);
log_muted += audio_pname + ", ";
}
}
}

LoggingEngine.LogLine($"[+] Summary: skipped ({log_skipped}) and muted ({log_muted})");
}

private void MuterCallback(object state)
Expand Down Expand Up @@ -397,8 +445,16 @@ private void MainForm_Load(object sender, EventArgs e)
SaveChangesButton.Enabled = false;

System.Reflection.Assembly assembly = System.Reflection.Assembly.GetExecutingAssembly();

if (assembly.Location.Length == 0)
{
MessageBox.Show("Assembly Location not detected. This may be due to a non-standard build process. Beware that this may break some features.");
}

System.Diagnostics.FileVersionInfo fvi = System.Diagnostics.FileVersionInfo.GetVersionInfo(assembly.Location);



this.Text += " - v" + fvi.ProductVersion;

if (m_enableMiniStart)
Expand Down Expand Up @@ -435,7 +491,7 @@ private void MainForm_FormClosed(object sender, FormClosedEventArgs e)

if (m_settingsChanged)
{
var res = MessageBox.Show("Settings changed. Would you like to save?", "Saving...", MessageBoxButtons.YesNo);
var res = MessageBox.Show("Settings changed. Would you like to save?", "Saving...", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
if (res == DialogResult.Yes)
{
SaveChangesButton_Click(sender, e);
Expand Down
8 changes: 8 additions & 0 deletions WinBGMute/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"profiles": {
"WinBGMuter": {
"commandName": "Project",
"nativeDebugging": true
}
}
}
Loading

0 comments on commit 8fd91c6

Please sign in to comment.