Page 1 of 1

[solved] How to detect Calculator on Windows 10

Posted: 09 Feb 2016, 14:57
by mviens
Microsoft has changed Calculator for Windows 10. For all previous version (including the server versions), this used to work just fine:

Code: Select all

WinActive("ahk_class CalcFrame")
I realized it was no longer working on Windows 10, so I used Window Spy and discovered the following:

Code: Select all

ahk_class ApplicationFrameWindow
ahk_exe ApplicationFrameHost.exe
This is not reliable to determine if the window is indeed Calculator because other application also use ApplicationFrameHost.exe. Nor is it acceptable to search for "Calculator" because of various languages. Does anyone have ideas on how to detect if a window is actually Calculator using Windows 10?

Re: How to detect Calculator on Windows 10

Posted: 09 Feb 2016, 16:33
by Exaskryz
Maybe you can poll the names of the Controls with WinGet? Though if it varies from language to language, I don't know if the names for values would be consistent. And you'll get a different number of controls based on using Standard vs Scientific/Programming/Whatever the fourth one is.

If AHK is the one responsible for launching the Calculator, you might be able to use the PID output that can be collected by the Run command.

Re: How to detect Calculator on Windows 10

Posted: 10 Feb 2016, 05:40
by just me
As long as the Calculator app is running Process, Exist, Calculator.exe will retrieve a PID. But it will need more to determine, whether it is active.

Re: How to detect Calculator on Windows 10

Posted: 12 Feb 2016, 16:43
by mviens
Hi "just me",

Thanks for the idea. I have it nearly working, but it doesn't seem to be able to detect when Windows 10 Calculator is actually active. It works for the following:
  • Before Windows 10, calculator is not running
  • Before Windows 10, calculator is running but not active
  • Before Windows 10, calculator is active
  • Windows 10, calculator is not running
  • Windows 10, calculator is running but not active
This is the one for which the script does not work:
  • Windows 10, calculator is active

Code: Select all

#b::
    isCalculatorActive()
    return

isCalculatorActive() {
    Process, Exist, Calc.exe
    cpid := ErrorLevel
    calcFound := false
    if (cpid > 0) {
        calcFound := true
        if (WinActive("ahk_class CalcFrame")) {
            message("Calculator (before Win10) is active...")
        }
        else {
            message("Calculator (before Win10) is running but not active...")
        }
    }
    else {
        Process, Exist, Calculator.exe
        cpid := ErrorLevel
        if (cpid > 0) {
            calcFound := true
            if (WinActive("ahk_pid " cpid)) {
                message("Calculator (Win10+) is active...")
            }
            else {
                message("Calculator (Win10+) is running but not active...")
            }
        }
    }
    if (!calcFound) {
        message("Calculator is not running or active...")
    }
}

Re: How to detect Calculator on Windows 10

Posted: 22 Feb 2016, 15:16
by mviens
Still working on this issue...

I found a post that seems to solve this, but the answer is written using C. I would like to ask for assistance with converting that into usable AHK code but someone who may see this. Here is the link: http://stackoverflow.com/questions/3236 ... ndows-8-10

Code: Select all

#b::
    isCalculatorActive()
    return
 
isCalculatorActive() {
    Process, Exist, Calc.exe
    calcPid := ErrorLevel
    calcFound := false
    if (calcPid > 0) {
        calcFound := true
        if (WinActive("ahk_class CalcFrame")) {
            message("Calculator (before Win10) is active...")
        }
        else {
            message("Calculator (before Win10) is running but not active...")
        }
    }
    else {
        Process, Exist, Calculator.exe
        calcPid := ErrorLevel
        if (calcPid > 0) {
            calcFound := true
            ; this is returning the PID for ApplicationFrameHost.exe,
            ; but I am expecting the PID for Calculator.exe
            ; see - http://stackoverflow.com/questions/32360149/name-of-process-for-active-window-in-windows-8-10
            WinGet, curPid, pid, A
            message("calcPid: " . calcPid . "`ncurPid: " . curPid)
            if (WinActive("ahk_pid " . calcPid)) {
                message("Calculator (Win10+) is active...")
            }
            else {
                message("Calculator (Win10+) is running but not active...")
            }
        }
    }
    if (!calcFound) {
        message("Calculator is not running or active...")
    }
}
 
message(msg) {
    MsgBox % msg
}

Re: How to detect Calculator on Windows 10

Posted: 23 Feb 2016, 07:26
by SifJar
EDIT: Working code at the bottom of post

Code: Select all

MsgBox % getActiveProcess()

getActiveProcess() {
	handle := DllCall("GetForegroundWindow", "Ptr")
	DllCall("GetWindowThreadProcessId", "Int", handle, "int*", pid)
	WinGet, name, ProcessName, ahk_pid %pid%
	return name
}
EDIT: Actually, this returns ApplicationFrameHost.exe for calculator, sorry, guess I should read the post you linked more carefully...

EDIT:I've done a little more work on it, but I'm still having a problem. Here's what I have right now:

Code: Select all

#s::MsgBox % getActiveProcess()
 
getActiveProcess() {
	handle := DllCall("GetForegroundWindow", "Ptr")
	DllCall("GetWindowThreadProcessId", "Int", handle, "int*", pid)
	global true_pid := pid
	callback := RegisterCallback("enumChildCallback")
	DllCall("EnumChildWindows", "Int", handle, "ptr", callback, "int", pid)
	WinGet, name, ProcessName, ahk_pid %true_pid%
	return name
}

enumChildCallback(hwnd, pid) {
	DllCall("GetWindowThreadProcessId", "Int", hwnd, "int*", child_pid)
	msgbox % child_pid " " pid
	if (child_pid != pid)
		global true_pid := child_pid
	return 1
}
This code seems to work for the most part, except that enumChildCallback only seems to be called for the first child window, where I believe it should be called for each child window. I believe this is the main issue with this script right now.

EDIT: Fixed that issue, just had to return 1 from the callback function. Now the only issue is that WinGet doesn't seem to get a process name from the "true" PID, guess WinApi functions are necessary for that too!

EDIT: And it's sorted!! Got it fully working:

Code: Select all

DetectHiddenWindows, on

;#s::MsgBox % getActiveProcess() ; one example of use
 
;"full" example use
#s::
IfWinExist, ahk_exe calculator.exe
	if (getActiveProcess() = "calculator.exe")
		MsgBox Calc active
	else
		MsgBox Calc open, not active
else
	MsgBox Calc not open
return
 
getActiveProcess() {
	handle := DllCall("GetForegroundWindow", "Ptr")
	DllCall("GetWindowThreadProcessId", "Int", handle, "int*", pid)
	global true_pid := pid
	callback := RegisterCallback("enumChildCallback", "Fast")
	DllCall("EnumChildWindows", "Int", handle, "ptr", callback, "int", pid)
	handle := DllCall("OpenProcess", "Int", 0x0400, "Int", 0, "Int", true_pid)
	length := 259 ;max path length in windows
	VarSetCapacity(name, length)
	DllCall("QueryFullProcessImageName", "Int", handle, "Int", 0, "Ptr", &name, "int*", length)
	SplitPath, name, pname
	return pname
}
 
enumChildCallback(hwnd, pid) {
	DllCall("GetWindowThreadProcessId", "Int", hwnd, "int*", child_pid)
	if (child_pid != pid)
		global true_pid := child_pid
	return 1
}
Returns Calculator.exe for me on Windows 10, when calculator is active! (If you want the full path rather than just the process name, you can take out the SplitPath command at the end of the function and change the return to return name instead of pname). Should work with any program, whether the new Windows store style apps or traditional programs.

EDIT: Created a thread for this function: https://autohotkey.com/boards/viewtopic.php?f=6&t=14300

Re: How to detect Calculator on Windows 10

Posted: 23 Feb 2016, 10:36
by CodeKiller
I don't really understand this whole post...

Code: Select all

IfWinExist ahk_exe calculator.exe
Msgbox Ok
This always works...

In my task manager calculator is... calculator.exe...

Re: How to detect Calculator on Windows 10

Posted: 23 Feb 2016, 10:40
by SifJar
CodeKiller wrote:I don't really understand this whole post...

Code: Select all

IfWinExist ahk_exe calculator.exe
Msgbox Ok
This always works...

In my task manager calculator is... calculator.exe...
Try detecting if it is active. (Assuming you're on Windows 10).

Actually, that code doesn't even work for me as-is on Windows 10. You need to add DetectHiddenWindows, on before those lines for that to even work.

(It still shows as calculator.exe in task manager, but AHK can't find a window for that process, because of the way UWP apps work on Windows 10 [and 8, I believe]. The window owned by that process is hidden and inactive, therefore can be detected if DetectHiddenWindows is on, but will never be detected as active)

Re: How to detect Calculator on Windows 10

Posted: 23 Feb 2016, 11:30
by CodeKiller
Ok sorry, miss the "activate" part in your problem and focus on "detect if the windows exist". :-)

Re: How to detect Calculator on Windows 10

Posted: 23 Feb 2016, 11:46
by SifJar
CodeKiller wrote:Ok sorry, miss the "activate" part in your problem and focus on "detect if the windows exist". :-)
Even for that though, your code doesn't work ;) It needs DetectHiddenWindows, on.

Re: How to detect Calculator on Windows 10

Posted: 24 Feb 2016, 03:11
by lexikos
Does this do the job?

Code: Select all

WinGetActiveProcessName() {
    WinGet name, ProcessName, A
    if (name = "ApplicationFrameHost.exe") {
        ControlGet hwnd, Hwnd,, Windows.UI.Core.CoreWindow1, A
        if hwnd {
            WinGet name, ProcessName, ahk_id %hwnd%
        }
    }
    return name
}

Re: How to detect Calculator on Windows 10

Posted: 24 Feb 2016, 05:17
by CodeKiller
I knew an easy way should exist !

Found by the great lexikos ! :-D

Re: How to detect Calculator on Windows 10

Posted: 24 Feb 2016, 06:44
by SifJar
Well, Lexikos' solution is definitely better than mine ;)

For anyone interested, I did a quick timing benchmark of my version, Lexikos' version, and also a version just me sent me via PM, which was similar to my version except using all built-in commands. Ran each function 5000 times, and divided the difference in A_TickCount before & after by 5000, these were the results(time in milliseconds)

Code: Select all

SifJar: 1.012600
just me: 2.156200
Lexkios: 0.793800
So Lexikos' is slightly quicker than mine, but mine is quicker than just me's version ;)

Note: The above is with Calculator. With Chrome my version and just me's are closer, and Lexikos' is even further ahead:

Code: Select all

SifJar: 0.615600
just me: 0.668800
Lexkios: 0.106200
Benchmark script for anyone interested:

Code: Select all

#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
; #Warn  ; Enable warnings to assist with detecting common errors.
SendMode Input  ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir %A_ScriptDir%  ; Ensures a consistent starting directory.

#s::
start := A_TickCount
loop, 5000
	SifJar()
SifJarTime := (A_TickCount - start)/5000

start := A_TickCount
loop, 5000
	just_me()
justmeTime := (A_TickCount - start)/5000

start := A_TickCount
loop, 5000
	lexikos()
lexikosTime := (A_TickCount - start)/5000

times := "SifJar: " . SifJarTime . "`r`njust me: " . justmeTime . "`r`nLexkios: " . lexikosTime
clipboard := times
MsgBox % times

SifJar() {
	handle := DllCall("GetForegroundWindow", "Ptr")
	DllCall("GetWindowThreadProcessId", "Int", handle, "int*", pid)
	global true_pid := pid
	callback := RegisterCallback("enumChildCallback", "Fast")
	DllCall("EnumChildWindows", "Int", handle, "ptr", callback, "int", pid)
	handle := DllCall("OpenProcess", "Int", 0x0400, "Int", 0, "Int", true_pid)
	length := 259 ;max path length in windows
	VarSetCapacity(name, length)
	DllCall("QueryFullProcessImageName", "Int", handle, "Int", 0, "Ptr", &name, "int*", length)
	SplitPath, name, pname
	return pname
}
 
enumChildCallback(hwnd, pid) {
	DllCall("GetWindowThreadProcessId", "Int", hwnd, "int*", child_pid)
	if (child_pid != pid)
		global true_pid := child_pid
	return 1
}

Lexikos() {
    WinGet name, ProcessName, A
    if (name = "ApplicationFrameHost.exe") {
        ControlGet hwnd, Hwnd,, Windows.UI.Core.CoreWindow1, A
        if hwnd {
            WinGet name, ProcessName, ahk_id %hwnd%
        }
    }
    return name
}

just_me() {
   If !WinExist("A")
      Return ""
   WinGet, ActivePID, PID
   WinGet, CtrlList, ControlListHwnd
   Loop, Parse, CtrlList, `n
   {
      WinGet, ChildPID, PID, ahk_id %A_LoopField%
      If (ChildPID <> ActivePID) {
         ActivePID := ChildPID
         Break
      }
 
   }
   DHW := A_DetectHiddenWindows
   DetectHiddenWindows, On
   WinGet, ProcName, ProcessName, ahk_pid %ActivePID%
   DetectHiddenWindows, %DHW%
   Return ProcName
}
(cue Lexikos pointing out that this script is wrong and those numbers aren't accurate :P )

Re: How to detect Calculator on Windows 10

Posted: 24 Feb 2016, 10:50
by just me
Seemingly you run the benchmark on a relative slow machine. You should add SetBatchLines, -1 at least to run with maximum speed. Also, you should add a DllCall("CloseHandle", "Ptr", handle) to your function to free the process handle.

Interestingly, I can confirm that the DllCalls perform better than the AHK commands. But the advantage seems to diminish with the number of child controls which have to be iterated.

Re: How to detect Calculator on Windows 10

Posted: 24 Feb 2016, 13:56
by SifJar
just me wrote:Seemingly you run the benchmark on a relative slow machine.
Yup, mid range laptop from ~5 years ago ;)

Re: How to detect Calculator on Windows 10

Posted: 24 Feb 2016, 17:16
by lexikos
The two scripts are not doing the same things. ControlGet has to retrieve the class name and track the number of controls of each class that it has encountered to find the one I specified, whereas SifJar's code just looks for a control with a different PID. In general, I suppose that some of the built-in commands do more work than strictly necessary to make them easier to use.

Re: How to detect Calculator on Windows 10

Posted: 24 Feb 2016, 19:20
by SifJar
lexikos wrote:The two scripts are not doing the same things. ControlGet has to retrieve the class name and track the number of controls of each class that it has encountered to find the one I specified, whereas SifJar's code just looks for a control with a different PID. In general, I suppose that some of the built-in commands do more work than strictly necessary to make them easier to use.
Which makes sense ;) A little bit more overhead in a command is worth making the commands easier than DllCalls IMO ;)

Re: [solved] How to detect Calculator on Windows 10

Posted: 20 Mar 2016, 00:28
by mviens
Thank you lexicos. Your code was exactly what I needed to solve this.

Re: [solved] How to detect Calculator on Windows 10

Posted: 27 Feb 2018, 13:24
by GollyJer
Sorry to bump an old post but I was messing around with this today and came up with a simpler solution.
Maybe multiple criteria in WinExist is new since this post was created?

This works without the WinGetActiveProcessName function.

Code: Select all

DetectCalculator() {
	calc_id := WinExist("Calculator ahk_class ApplicationFrameWindow")
	if !(calc_id)	{
		MsgBox, % "Calculator is not running."
	}	else {
		if WinActive(ahk_id %calc_id%) {
			MsgBox, % "Calculator is active."
		} else {
			MsgBox, % "Calculator is running but not active."
		}
	}
}
My quest was to override the calculator key on my keyboard so only one copy of calculator is running at all times.

Code: Select all

;Launch_App2 is Calculator Key
Launch_App2:: ActivateCalculator()

ActivateCalculator() {
	if WinExist("Calculator ahk_class ApplicationFrameWindow") {
		WinActivate
	}	else {
		Run, calc
	}
}
EDIT!!!!
I just tried the old tried and true and it works also. Not sure why I made this so hard on myself! :lol:

Code: Select all

ActivateCalculator() {
	IfWinExist Calculator
		WinActivate
	else
		Run, calc
}
Hopefully this helps some future searcher. :)

Re: [solved] How to detect Calculator on Windows 10

Posted: 01 Mar 2018, 14:04
by FanaticGuru
I know this has been "solved" but here is a calculator script that I use many times each day.

Code: Select all

#numpad0::	; <-- Open/Activate/Minimize Windows Calculator
{
	if WinExist("Calculator ahk_class CalcFrame") or WinExist("Calculator ahk_class ApplicationFrameWindow")
		if WinActive()
			WinMinimize
		else
			WinActivate
	else
		Run calc.exe
	return
}
The WinExist is made to work with Windows 10 or earlier Windows.

The script just uses Win+NumPad0 to make the calculator popup or minimize in a toggle like way. Simple but very useful and intuitive.

This script eliminated my physical desktop calculator.

FG