Author Topic: Wait for process window to activate  (Read 5440 times)

Exergist

  • Global Moderator
  • Sr. Member
  • *****
  • Posts: 405
  • Ride the lightning
Wait for process window to activate
« on: January 22, 2018, 02:03:29 PM »
VoiceAttack currently provides functionality to automatically switch between profiles depending on the activated window's title. This title check does not consider the actual Windows process associated with the activate window. Unfortunately under certain circumstances this can lead to "false positive" profile switching. In other words, if you have two windows with the same title VoiceAttack will perform the switch if either is activated, though most likely only one of those windows is tied to the actual process (an application or game) that you want associated with the profile. The way I see it this only becomes an actual problem if:
  • You have separate profiles for each of the same-titled windows.
  • You need to execute a command automatically after profile load and you want the triggering active window to be the desired app or game.
I've already submitted this feature request to allow tokens to be used in the "enable profile switching" field (which would lead to options for eliminating false positive profile switching). In the mean time I came up with the following command actions as a workaround (Calculator app example):

Code: [Select]
Begin Text Compare : [{CMDACTION}] Equals 'Profile'
    Set Text [ProcessName] to 'calculator'
    Begin Text Compare : [ProcessName] Does Not Contain '{ACTIVEWINDOWPROCESSNAME}'
        Write '[Blue] Waiting for "{TXT:ProcessName}" process window to be activated...' to log
        Start Loop While : [ProcessName] Does Not Contain '{ACTIVEWINDOWPROCESSNAME}'
            Pause 1 second
        End Loop
        Write '[Blue] "{TXT:ProcessName}" process window activated' to log
    End Condition
End Condition
// Remainder of command goes here

So basically the above code block will execute if the command is launched due to a profile change (automatic or manual), and the code waits for the active window's process name to match a user-defined process name before continuing.

However if we implement some of the above steps in an inline function we can clean up the action sequence:

Code: [Select]
Begin Text Compare : [{CMDACTION}] Equals 'Profile'
    Set Text [ProcessName] to 'calculator'
    Inline C# Function: Wait for Process Window Activation, wait until execution finishes
End Condition
// Remainder of command goes here

Referenced Assemblies: none required
Code: [Select]
using System;
using System.Threading;

public class VAInline
{
    public void main()
    {
        string ProcessName = VA.GetText("ProcessName"); // Retrieve desired ProcessName from VoiceAttack text variable
        string ActiveProcessName; // Initialize string for storing the ActiveProcessName
        if (ProcessName.IndexOf(VA.ParseTokens("{ACTIVEWINDOWPROCESSNAME}"), StringComparison.OrdinalIgnoreCase) < 0) // Check if the text from VA token {ACTIVEWINDOWPROCESSNAME} is NOT found in ProcessName (case-insensitive)
        {
            VA.WriteToLog(@"Waiting for """ + ProcessName + @""" process window to be activated...", "blue"); // Output information to VoiceAttack event log
            while (ProcessName.IndexOf((ActiveProcessName = VA.ParseTokens("{ACTIVEWINDOWPROCESSNAME}")), StringComparison.OrdinalIgnoreCase) < 0) // Loop while the text from VA token {ACTIVEWINDOWPROCESSNAME} is NOT found in ProcessName (case-insensitive)
            {
                Thread.Sleep(1000); // Pause code execution for 1000 ms
            }
            VA.WriteToLog(@"""" + ProcessName + @""" process window activated", "blue"); // Output information to VoiceAttack event log
        }
    }
}

For both cases the looping runs indefinitely in the background until the window tied to the desired process name is activated or the command is stopped (pressing the "stop commands" button, switching to a different profile, etc). The looping does not prevent VA from going into "sleep mode."

Yes the command action and inline function methods produce the same end result, but I like to simplify my command action sequences whenever possible. I figured I'd share this in case others might also have use for it.



Update: a few revisions to increase the robustness.

VoiceAttack Actions
Code: [Select]
// Define process name (or names) of interest. Separate multiple process names with a ";" (semicolon).
Set Text [~~ProcessNames] to 'notepad; calculator'

// Check if active window is associated with a process of interest
// Command execution will be paused until process window of interest is activated, the command is stopped, or the profile is reset
Inline C# Function: Wait for Process Window Activation, wait until execution finishes
Write '[Orange] Process complete' to log

Inline Function
Referenced Assemblies: System.dll
Code: [Select]
using System;
using System.Threading;

public class VAInline
{
public void main()
{
string ProcessNames = VA.GetText("~~ProcessNames").Trim(); // Retrieve desired ProcessNames from VoiceAttack text variable, trim the text, and store in a string variable
if (ProcessNames.LastIndexOf(";") == ProcessNames.Length-1) // Check if there is a trailing ";" in ProcessNames
ProcessNames = ProcessNames.Remove(ProcessNames.LastIndexOf(";")); // Remove the trailing ";" in ProcessNames
string[] ProcessArray = ProcessNames.Replace(" ", "").Split(';'); // Remove extra spaces, split all text within ProcessNames delimited by ";" character, and store in ProcessArray
bool WindowActivated = false; // Initialize boolean variable for tracking if desired window is activated
string ActiveProcessName = VA.ParseTokens("{ACTIVEWINDOWPROCESSNAME}"); // Initialize string for storing the active window's process name
foreach (string ProcessName in ProcessArray) // Loop through each ProcessName in ProcessArray
{
if (ProcessName.Trim() == ActiveProcessName) // Check if ActiveProcessName matches ProcessName
{
WindowActivated = true; // Set WindowActivated as true
break; // Immediately terminate foreach loop
}
}
if (WindowActivated == false) // Check if WindowActivated is still false (window of interest still hasn't been found)
{
VA.WriteToLog("Waiting for " + (ProcessArray.Length == 1 ? @"""" + string.Join(", ", ProcessArray) + @"""" : "(" + string.Join(", ", ProcessArray) + ")") + " process window to be activated...", "blue"); // Output information to VoiceAttack event log
while (WindowActivated == false) // Loop while WindowActivated is false
{
Thread.Sleep(1000); // Pause code execution for 1000 ms
ActiveProcessName = VA.ParseTokens("{ACTIVEWINDOWPROCESSNAME}"); // Store process name of active window
//VA.WriteToLog("Active Window Process = " + ActiveProcessName, "blue"); // Output the active window's process name
foreach (string ProcessName in ProcessArray) // Loop through each ProcessName in ProcessArray
{
if (ProcessName.Trim() == ActiveProcessName) // Check if ActiveProcessName matches ProcessName
{
WindowActivated = true; // Set WindowActivated as true
break; // Immediately terminate foreach loop
}
}
}
}
VA.WriteToLog(@"""" + ActiveProcessName + @""" process window activated", "blue"); // Output information to VoiceAttack event log
}
}
« Last Edit: March 29, 2018, 11:54:38 AM by Exergist »