Commanding Rainmeter with AHK (and vice versa)

Post your working scripts, libraries and tools for AHK v1.1 and older
ori0n
Posts: 5
Joined: 24 Jul 2015, 13:35

Commanding Rainmeter with AHK (and vice versa)

30 Dec 2015, 18:35

I get a lot of use out of the very excellent Rainmeter, and it's even more useful thanks to integration with AHK. But while Rainmeter has docs describing messaging to Rainmeter using AutoIt, there's no AHK-specific stuff there, nor is there anything I could find here on communicating the other way: from Rainmeter to AHK. Here's what I know on the topic:

Why bother?
  • Rainmeter is a simple way to put up attractive UI on the desktop, and can make a quick and effective way to provide interfacing with AHK's capabilities
  • Allow keyboard control of desktop widgets (start a timer widget, turn widgets transparent)
  • Use AHK's better integration tools and APIs to augment Rainmeter skins (e.g. getting play count of the current track from my music player)
  • Use Rainmeter to show status of something that's easier to track with AHK (is a certain background process running?)
  • Tying together tools == synergy!
Communicating AHK -> Rainmeter
Here are the two ways I know of to send Rainmeter commands (the unfortunately-named bangs) from AHK:

First is to call its .exe directly with parameters. This works well, is simple, and depends on knowing where the .exe lives:

Code: Select all

RainmeterPath := "C:\Program Files\Rainmeter\rainmeter.exe"

SendRainmeterCommand(Command) {
  global RainmeterPath
  Run, %RainmeterPath% %Command%
}
The other way is through direct window messaging. This is more complex, but seems just as reliable, doesn't rely on knowing the .exe's location, and is generally awesome:

Code: Select all

SendRainmeterCommand(command) {
  Send_WM_COPYDATA(command, "DummyRainWClass")
}

Send_WM_COPYDATA(ByRef StringToSend, ByRef TargetWindowClass)  ; ByRef saves a little memory in this case.
; This function sends the specified string to the specified window and returns the reply.
; Cribbed from https://www.autohotkey.com/docs/commands/OnMessage.htm
{
    VarSetCapacity(CopyDataStruct, 3*A_PtrSize, 0)  ; Set up the structure's memory area.
    ; First set the structure's cbData member to the size of the string, including its zero terminator:
    SizeInBytes := (StrLen(StringToSend) + 1) * (A_IsUnicode ? 2 : 1)
    NumPut(1, CopyDataStruct) ; Per example at https://docs.rainmeter.net/developers/
    NumPut(SizeInBytes, CopyDataStruct, A_PtrSize)  ; OS requires that this be done.
    NumPut(&StringToSend, CopyDataStruct, 2*A_PtrSize)  ; Set lpData to point to the string itself.
    SendMessage, 0x4a, 0, &CopyDataStruct,, ahk_class %TargetWindowClass%  ; 0x4a is WM_COPYDATA. Must use Send not Post.
    return ErrorLevel  ; Return SendMessage's reply back to our caller.
}
Communicating Rainmeter -> AHK
Going the other way, using window messaging again, requires a couple things set up. In the Rainmeter skin, you need to have a measure dedicated to sending out window messages to AHK, like so:

Code: Select all

[MeasureAhkWindowMessaging]
Measure=Plugin
Plugin=WindowMessagePlugin
WindowClass=AutoHotkey
...which you then invoke using an action option like this:

Code: Select all

LeftMouseUpAction=[!CommandMeasure MeasureAhkWindowMessaging "SendMessage 16687 2 0"]
Meanwhile, the AHK script needs a message handler set up to listen for these messages. It looks something like this:

Code: Select all

OnMessage(16687, "RainmeterWindowMessage")

RainmeterWindowMessage(wParam, lParam) { 
  If (wParam = 2) {
    CheckPlayCount()
  }
}
(The 16687 is arbitrary; just needs to match between the two. Same for the wParam of 2; they just need to match up to make sure the right things happen.)

Anyone else here using these great tools together?

(Note: Sorry for not linking, apparently I'm new enough to the forums that links are disabled for me.)
User avatar
joedf
Posts: 8953
Joined: 29 Sep 2013, 17:08
Location: Canada
Contact:

Re: Commanding Rainmeter with AHK (and vice versa)

31 Dec 2015, 19:13

interesting idea :)
Image Image Image Image Image
Windows 10 x64 Professional, Intel i5-8500, NVIDIA GTX 1060 6GB, 2x16GB Kingston FURY Beast - DDR4 3200 MHz | [About Me] | [About the AHK Foundation] | [Courses on AutoHotkey]
[ASPDM - StdLib Distribution] | [Qonsole - Quake-like console emulator] | [LibCon - Autohotkey Console Library]
robhayes
Posts: 19
Joined: 28 Dec 2015, 15:35

Re: Commanding Rainmeter with AHK (and vice versa)

02 Jan 2016, 18:03

Would love to see/hear about some practical use cases of yours.
User avatar
joedf
Posts: 8953
Joined: 29 Sep 2013, 17:08
Location: Canada
Contact:

Re: Commanding Rainmeter with AHK (and vice versa)

02 Jan 2016, 21:27

We can use rainmeter as the awesome GUI or Frontend and use AHK as the backend :D
Image Image Image Image Image
Windows 10 x64 Professional, Intel i5-8500, NVIDIA GTX 1060 6GB, 2x16GB Kingston FURY Beast - DDR4 3200 MHz | [About Me] | [About the AHK Foundation] | [Courses on AutoHotkey]
[ASPDM - StdLib Distribution] | [Qonsole - Quake-like console emulator] | [LibCon - Autohotkey Console Library]
ori0n
Posts: 5
Joined: 24 Jul 2015, 13:35

Re: Commanding Rainmeter with AHK (and vice versa)

04 Jan 2016, 13:10

Here's what I use this technique for:

Get play count for a playing song

I use MusicBee for a media player, which works well with the default Rainmeter NowPlaying plugin, except for one thing: play count. Now, there's a plugin (MusicBeeIPC) that allows getting it through window messages, but doing that through Rainmeter is more difficult than I've been able to manage (because the return values from the plugin aren't just simple ints like Rainmeter expects). Fortunately, there is an AHK version of the API, so that's easy. So to make my skin show the play count, I just send a message to AHK each time the track changes (which is visible via the default NowPlaying plugin), and AHK queries MusicBeeIPC, then returns the result back to Rainmeter. It's a little convoluted, but works reliably.

Control a timer skin via keyboard shortcuts

I have a timer skin that I use to track Pomodoro-esque productivity bursts, but moving my mouse all the way over to my status monitor to click it feels silly. So I have AHK hotkeys to:
  • Set a new productivity (15-min) timer
  • Stop a timer in progress
  • Set a rest (5-min) timer
Since I'm usually busy on the keyboard anyway, setting timers like this is a cinch.

Show quick status of Plover (on/off) without constantly polling

AHK has better capabilities for being event-driven than Rainmeter, which is almost exclusively polling-based. For example, I use the open-source stenography project Plover, and made a skin that shows whether Plover is active (which is handy because it's super-modal). The best I could come up with using just Rainmeter was to set a WindowMessagePlugin measure that returns the title of the window, then keys off that and updates accordingly. However, to make it responsive requires polling tens of times a second for that title, which feels wasteful since it doesn't change more than once or twice a minute. (I didn't see any CPU overusage doing this, just saying.) This is a common Rainmeter trade-off: either poll rapidly all the time, or get slow updates. (The Rainmeter guidebook suggests one-second update times in general, which feel sluggish for something like this.)

The AHK-powered alternative is to set up a shell hook in AHK, which then gets messaged any time a window's updated. So it gets the messages, and when it sees one from Plover, it instantly updates a variable in Rainmeter and repaints it. Less CPU usage than polling, and far better reaction time to boot.
User avatar
joedf
Posts: 8953
Joined: 29 Sep 2013, 17:08
Location: Canada
Contact:

Re: Commanding Rainmeter with AHK (and vice versa)

04 Jan 2016, 14:15

Hmmm, thats very cool!
And i never knew about musicbee :D
Image Image Image Image Image
Windows 10 x64 Professional, Intel i5-8500, NVIDIA GTX 1060 6GB, 2x16GB Kingston FURY Beast - DDR4 3200 MHz | [About Me] | [About the AHK Foundation] | [Courses on AutoHotkey]
[ASPDM - StdLib Distribution] | [Qonsole - Quake-like console emulator] | [LibCon - Autohotkey Console Library]
ori0n
Posts: 5
Joined: 24 Jul 2015, 13:35

What if I run AHK with UI access?

01 Feb 2016, 17:15

One addendum: If you run your AHK with UI Access to allow keyboard shortcuts with admin-level processes active, Rainmeter's WindowMessagePlugin (which isn't elevated) won't be able to send messages to it anymore, thanks to UIPI. But worry not! You can still communicate with Rainmeter via window messages by using ChangeWindowMessageFilterEx on the AHK window, like so:

Code: Select all

hWnd := WinExist("ahk_class AutoHotkey")
DllCall("ChangeWindowMessageFilterEx", Ptr, hWnd, Uint, 16687, Uint, MSGFLT_ALLOW := 1, ptr, 0)
This sets the window filter for the AHK window to allow the message 16687, even from processes with low level privileges.
User avatar
F4Jonatas
Posts: 45
Joined: 22 Oct 2015, 06:35
Contact:

Re: Commanding Rainmeter with AHK (and vice versa)

02 Feb 2017, 08:37

Thanks ori0n :bravo:
Last edited by F4Jonatas on 02 Feb 2017, 09:18, edited 2 times in total.
User avatar
F4Jonatas
Posts: 45
Joined: 22 Oct 2015, 06:35
Contact:

Re: Commanding Rainmeter with AHK (and vice versa)

02 Feb 2017, 08:38

Can someone implement this code for AHK_L?

Code: Select all

// Usage: GetSkinWindow(L"illustro\\Clock");
// Return value will be NULL if config not found or a valid HWND otherwise
#include <Windows.h>
HWND GetSkinWindow(const WCHAR* configName)
{
	HWND trayWnd = FindWindow(L"RainmeterTrayClass", NULL);
	if (trayWnd)
	{
		COPYDATASTRUCT cds;
		cds.dwData = 5101;
		cds.cbData = (DWORD)(wcslen(configName) + 1) * sizeof(WCHAR);
		cds.lpData = (void*)configName;
		return (HWND)SendMessage(trayWnd, WM_COPYDATA, 0, (LPARAM)&cds);
	}
	return NULL;
}
User avatar
jNizM
Posts: 3183
Joined: 30 Sep 2013, 01:33
Contact:

Re: Commanding Rainmeter with AHK (and vice versa)

02 Feb 2017, 09:20

@F4Jonatas
untested and cannot test it

Code: Select all

GetSkinWindow(ConfigName)
{
    static WM_COPYDATA := 0x004A
    if (hTrayWnd := DllCall("user32\FindWindow", "str", "RainmeterTrayClass", "ptr", 0, "ptr"))
    {
        VarSetCapacity(COPYDATASTRUCT, A_PtrSize * 3, 0)
        NumPut(5101, COPYDATASTRUCT, 0, "uptr")
        NumPut((StrLen(ConfigName) + 1) << 1, COPYDATASTRUCT, A_PtrSize, "uint")
        NumPut(&ConfigName, COPYDATASTRUCT, A_PtrSize * 2, "ptr*")
        return DllCall("user32\SendMessage", "ptr", hTrayWnd, "uint", WM_COPYDATA, "ptr", 0, "ptr", &COPYDATASTRUCT)
    }
    return false
}
[AHK] v2.0.5 | [WIN] 11 Pro (Version 22H2) | [GitHub] Profile
treashunter

Re: Commanding Rainmeter with AHK (and vice versa)

02 Feb 2017, 09:20

I like this, very cool! Will be checking it out.

Thanks for sharing!
User avatar
F4Jonatas
Posts: 45
Joined: 22 Oct 2015, 06:35
Contact:

Re: Commanding Rainmeter with AHK (and vice versa)

02 Feb 2017, 09:30

Solved!
Thanks @jNizM

Code: Select all

; Require DetectHiddenWindows, On
; GetSkinWindow( "illustro\Clock" )
GetSkinWindow( ConfigName ) {
	Local Hwnd := WinExist( "ahk_class RainmeterTrayClass" )
	Local SizeInBytes := ( StrLen( ConfigName ) + 1 ) * ( A_IsUnicode ? 2 : 1 )
	Local DataStruct

	If Not ( Hwnd == 0 ) {
		VarSetCapacity( DataStruct, 3 * A_PtrSize, 0 )
		NumPut( 5101, DataStruct )
		NumPut( SizeInBytes, DataStruct, A_PtrSize )
		NumPut( &ConfigName, DataStruct, 2 * A_PtrSize )
		SendMessage, 0x4a, 0, &DataStruct,, % "ahk_class RainmeterTrayClass"
		Return ErrorLevel
	}
}
User avatar
F4Jonatas
Posts: 45
Joined: 22 Oct 2015, 06:35
Contact:

Re: Commanding Rainmeter with AHK (and vice versa)

02 Feb 2017, 12:19

So. This code can not pass to AHK_L...

Code: Select all

#include <SendMessage.au3>
#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>
#include <Array.au3>

$RAINMETER_QUERY_WINDOW = WinGetHandle("[CLASS:RainmeterTrayClass]")

$WM_QUERY_RAINMETER = $WM_APP + 1000
$RAINMETER_QUERY_ID_SKINS_PATH = 4101
$RAINMETER_QUERY_ID_SETTINGS_PATH = 4102
$RAINMETER_QUERY_ID_PROGRAM_PATH = 4104
$RAINMETER_QUERY_ID_CONFIG_EDITOR = 4106
$WM_QUERY_RAINMETER_RETURN = ""

Dim $PathsArray[ 4 ]
$hGUI = GUICreate( "", 1, 1, -1, -1 )
If Not ProcessExists( "Rainmeter.exe" ) Then
   MsgBox(16, "GetPath Error", "Rainmeter must be running to use GetPath." )
   Exit
EndIf

GUIRegisterMsg( $WM_COPYDATA, "_ReadMessage" )
_SendMessage( $RAINMETER_QUERY_WINDOW, $WM_QUERY_RAINMETER, $RAINMETER_QUERY_ID_PROGRAM_PATH, $hGUI )
$PathsArray[ 0 ] = $WM_QUERY_RAINMETER_RETURN
_SendMessage( $RAINMETER_QUERY_WINDOW, $WM_QUERY_RAINMETER, $RAINMETER_QUERY_ID_SETTINGS_PATH, $hGUI )
$PathsArray[ 1 ] = $WM_QUERY_RAINMETER_RETURN
_SendMessage( $RAINMETER_QUERY_WINDOW, $WM_QUERY_RAINMETER, $RAINMETER_QUERY_ID_SKINS_PATH, $hGUI )
$PathsArray[ 2 ] = $WM_QUERY_RAINMETER_RETURN
_SendMessage( $RAINMETER_QUERY_WINDOW, $WM_QUERY_RAINMETER, $RAINMETER_QUERY_ID_CONFIG_EDITOR, $hGUI )
$PathsArray[ 3 ] = $WM_QUERY_RAINMETER_RETURN
_ArrayDisplay( $PathsArray )

Func _ReadMessage($hWnd, $uiMsg, $wParam, $lParam )
   $pCds = DllStructCreate("dword;dword;ptr", $lParam ) 
   $pData = DllStructGetData($pCds, 3 )
   $pMem = DllStructCreate("wchar[" & DllStructGetData($pCds, 2) & "]", $pData )
   $WM_QUERY_RAINMETER_RETURN = DllStructGetData($pMem, 1 )
EndFunc
User avatar
joedf
Posts: 8953
Joined: 29 Sep 2013, 17:08
Location: Canada
Contact:

Re: Commanding Rainmeter with AHK (and vice versa)

02 Feb 2017, 19:33

That's vbscript no?
Image Image Image Image Image
Windows 10 x64 Professional, Intel i5-8500, NVIDIA GTX 1060 6GB, 2x16GB Kingston FURY Beast - DDR4 3200 MHz | [About Me] | [About the AHK Foundation] | [Courses on AutoHotkey]
[ASPDM - StdLib Distribution] | [Qonsole - Quake-like console emulator] | [LibCon - Autohotkey Console Library]
User avatar
jNizM
Posts: 3183
Joined: 30 Sep 2013, 01:33
Contact:

Re: Commanding Rainmeter with AHK (and vice versa)

03 Feb 2017, 02:39

F4Jonatas wrote:So. This code can not pass to AHK_L...
Every code in AutoIt can be rewritten in AutoHotkey
[AHK] v2.0.5 | [WIN] 11 Pro (Version 22H2) | [GitHub] Profile
User avatar
spaceowl
Posts: 22
Joined: 16 May 2021, 06:46

Re: Commanding Rainmeter with AHK (and vice versa)

30 Jul 2021, 08:07

ori0n wrote:
04 Jan 2016, 13:10
...then returns the result back to Rainmeter...
But how? How can I return or submit a string or a variable from a AHK script and then recive it in rainmeter skin? :) :salute:
User avatar
joedf
Posts: 8953
Joined: 29 Sep 2013, 17:08
Location: Canada
Contact:

Re: Commanding Rainmeter with AHK (and vice versa)

30 Jul 2021, 08:37

@spaceowl i think you can use what they call bangs: https://docs.rainmeter.net/manual/bangs/#Options

There's a !SetVariable and !SetOption in there...
Image Image Image Image Image
Windows 10 x64 Professional, Intel i5-8500, NVIDIA GTX 1060 6GB, 2x16GB Kingston FURY Beast - DDR4 3200 MHz | [About Me] | [About the AHK Foundation] | [Courses on AutoHotkey]
[ASPDM - StdLib Distribution] | [Qonsole - Quake-like console emulator] | [LibCon - Autohotkey Console Library]
User avatar
spaceowl
Posts: 22
Joined: 16 May 2021, 06:46

Re: Commanding Rainmeter with AHK (and vice versa)

31 Jul 2021, 13:38

joedf wrote:
30 Jul 2021, 08:37
@spaceowl i think you can use what they call bangs: https://docs.rainmeter.net/manual/bangs/#Options

There's a !SetVariable and !SetOption in there...
Thank you! That worked great! :dance: :dance: :dance:

Return to “Scripts and Functions (v1)”

Who is online

Users browsing this forum: burque505 and 190 guests