WinLIRC Client

This script receives notifications from WinLIRC whenever you press a button on your remote control. It can be used to automate Winamp, Windows Media Player, etc. It's easy to configure. For example, if WinLIRC recognizes a button named "VolUp" on your remote control, create a label named VolUp and beneath it use the command "SoundSet +5" to increase the soundcard's volume by 5%.

Download This Script  |  Other Sample Scripts  |  Home


; Here are the steps to use this script:
; 1) Configure WinLIRC to recognize your remote control and its buttons.
;    WinLIRC is at http://winlirc.sourceforge.net
; 2) Edit the WinLIRC path, address, and port in the CONFIG section below.
; 3) Launch this script. It will start the WinLIRC server if needed.
; 4) Press some buttons on your remote control. A small window will
;    appear showing the name of each button as you press it.
; 5) Configure your buttons to send keystrokes and mouse clicks to
;    windows such as Winamp, Media Player, etc. See the examples below.

; This script requires AutoHotkey 1.0.38 or later.
; HISTORY OF CHANGES
; October 5, 2005:
; - Eliminated Winsock warning dialog "10054" upon system shutdown/logoff.
; - Added option "DelayBetweenButtonRepeats" to throttle the repeat speed.

; -------------------------------------------------
; CONFIGURATION SECTION: Set your preferences here.
; -------------------------------------------------
; Some remote controls repeat the signal rapidly while you're holding down
; a button. This makes it difficult to get the remote to send only a single
; signal. The following setting solves this by ignoring repeated signals
; until the specified time has passed. 200 is often a good setting.  Set it
; to 0 to disable this feature.
DelayBetweenButtonRepeats = 200

; Specify the path to WinLIRC, such as C:\WinLIRC\winlirc.exe
WinLIRC_Path = %ProgramFiles%\WinLIRC\winlirc.exe

; Specify WinLIRC's address and port. The most common are 127.0.0.1 (localhost) and 8765.
WinLIRC_Address = 127.0.0.1
WinLIRC_Port = 8765

; Do not change the following two lines. Skip them and continue below.
Gosub WinLIRC_Init
return

; --------------------------------------------
; ASSIGN ACTIONS TO THE BUTTONS ON YOUR REMOTE
; --------------------------------------------
; Configure your remote control's buttons below. Use WinLIRC's names
; for the buttons, which can be seen in your WinLIRC config file
; (.cf file) -- or you can press any button on your remote and the
; script will briefly display the button's name in a small window.
; 
; Below are some examples. Feel free to revise or delete them to suit
; your preferences.

VolUp:
SoundSet +5  ; Increase master volume by 5%.
return

VolDown:
SoundSet -5  ; Reduce master volume by 5%.
return

ChUp:
WinGetClass, ActiveClass, A
if ActiveClass in Winamp v1.x,Winamp PE  ; Winamp is active.
	Send {right}  ; Send a right-arrow keystroke.
else  ; Some other type of window is active.
	Send {WheelUp}  ; Rotate the mouse wheel up by one notch.
return

ChDown:
WinGetClass, ActiveClass, A
if ActiveClass in Winamp v1.x,Winamp PE  ; Winamp is active.
	Send {left}  ; Send a left-arrow keystroke.
else  ; Some other type of window is active.
	Send {WheelDown}  ; Rotate the mouse wheel down by one notch.
return

Menu:
IfWinExist, Untitled - Notepad
{
	WinActivate
}
else
{
	Run, Notepad
	WinWait, Untitled - Notepad
	WinActivate
}
Send Some keystrokes sent to Notepad.{Enter}
return

; The examples above give a feel for how to accomplish common tasks.
; To learn the basics of AutoHotkey, check out the Quick-start Tutorial
; at http://www.autohotkey.com/docs/Tutorial.htm

; ----------------------------
; END OF CONFIGURATION SECTION
; ----------------------------
; Do not make changes below this point unless you want to change the core
; functionality of the script.

WinLIRC_Init:
OnExit, ExitSub  ; For connection cleanup purposes.

; Launch WinLIRC if it isn't already running:
Process, Exist, winlirc.exe
if not ErrorLevel  ; No PID for WinLIRC was found.
{
	IfNotExist, %WinLIRC_Path%
	{
		MsgBox The file "%WinLIRC_Path%" does not exist. Please edit this script to specify its location.
		ExitApp
	}
	Run %WinLIRC_Path%
	Sleep 200  ; Give WinLIRC a little time to initialize (probably never needed, just for peace of mind).
}

; Connect to WinLIRC (or any type of server for that matter):
socket := ConnectToAddress(WinLIRC_Address, WinLIRC_Port)
if socket = -1  ; Connection failed (it already displayed the reason).
	ExitApp

; Find this script's main window:
Process, Exist  ; This sets ErrorLevel to this script's PID (it's done this way to support compiled scripts).
DetectHiddenWindows On
ScriptMainWindowId := WinExist("ahk_class AutoHotkey ahk_pid " . ErrorLevel)
DetectHiddenWindows Off

; When the OS notifies the script that there is incoming data waiting to be received,
; the following causes a function to be launched to read the data:
NotificationMsg = 0x5555  ; An arbitrary message number, but should be greater than 0x1000.
OnMessage(NotificationMsg, "ReceiveData")

; Set up the connection to notify this script via message whenever new data has arrived.
; This avoids the need to poll the connection and thus cuts down on resource usage.
FD_READ = 1     ; Received when data is available to be read.
FD_CLOSE = 32   ; Received when connection has been closed.
if DllCall("Ws2_32\WSAAsyncSelect", UInt, socket, UInt, ScriptMainWindowId, UInt, NotificationMsg, Int, FD_READ|FD_CLOSE)
{
	MsgBox % "WSAAsyncSelect() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError")
	ExitApp
}
return



ConnectToAddress(IPAddress, Port)
; This can connect to most types of TCP servers, not just WinLIRC.
; Returns -1 (INVALID_SOCKET) on failure or the socket ID upon success.
{
	VarSetCapacity(wsaData, 32)  ; The struct is only about 14 in size, so 32 is conservative.
	result := DllCall("Ws2_32\WSAStartup", UShort, 0x0002, UInt, &wsaData) ; Request Winsock 2.0 (0x0002)
	; Since WSAStartup() will likely be the first Winsock function called by this script,
	; check ErrorLevel to see if the OS has Winsock 2.0 available:
	if ErrorLevel
	{
		MsgBox WSAStartup() could not be called due to error %ErrorLevel%. Winsock 2.0 or higher is required.
		return -1
	}
	if result  ; Non-zero, which means it failed (most Winsock functions return 0 on success).
	{
		MsgBox % "WSAStartup() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError")
		return -1
	}

	AF_INET = 2
	SOCK_STREAM = 1
	IPPROTO_TCP = 6
	socket := DllCall("Ws2_32\socket", Int, AF_INET, Int, SOCK_STREAM, Int, IPPROTO_TCP)
	if socket = -1
	{
		MsgBox % "socket() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError")
		return -1
	}

	; Prepare for connection:
	SizeOfSocketAddress = 16
	VarSetCapacity(SocketAddress, SizeOfSocketAddress)
	InsertInteger(2, SocketAddress, 0, AF_INET)   ; sin_family
	InsertInteger(DllCall("Ws2_32\htons", UShort, Port), SocketAddress, 2, 2)   ; sin_port
	InsertInteger(DllCall("Ws2_32\inet_addr", str, IPAddress), SocketAddress, 4, 4)   ; sin_addr.s_addr

	; Attempt connection:
	if DllCall("Ws2_32\connect", UInt, socket, UInt, &SocketAddress, Int, SizeOfSocketAddress)
	{
		MsgBox % "connect() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError") . ". Is WinLIRC running?"
		return -1
	}
	return socket  ; Indicate success by returning a valid socket ID rather than -1.
}



ReceiveData(wParam, lParam)
; By means of OnMessage(), this function has been set up to be called automatically whenever new data
; arrives on the connection.  It reads the data from WinLIRC and takes appropriate action depending
; on the contents.
{
	socket := wParam
	ReceivedDataSize = 4096  ; Large in case a lot of data gets buffered due to delay in processing previous data.
	Loop  ; This loop solves the issue of the notification message being discarded due to thread-already-running.
	{
		VarSetCapacity(ReceivedData, ReceivedDataSize, 0)  ; 0 for last param terminates string for use with recv().
		ReceivedDataLength := DllCall("Ws2_32\recv", UInt, socket, Str, ReceivedData, Int, ReceivedDataSize, Int, 0)
		if ReceivedDataLength = 0  ; The connection was gracefully closed, probably due to exiting WinLIRC.
			ExitApp  ; The OnExit routine will call WSACleanup() for us.
		if ReceivedDataLength = -1
		{
			WinsockError := DllCall("Ws2_32\WSAGetLastError")
			if WinsockError = 10035  ; WSAEWOULDBLOCK, which means "no more data to be read".
				return 1
			if WinsockError <> 10054 ; WSAECONNRESET, which happens when WinLIRC closes via system shutdown/logoff.
				; Since it's an unexpected error, report it.  Also exit to avoid infinite loop.
				MsgBox % "recv() indicated Winsock error " . WinsockError
			ExitApp  ; The OnExit routine will call WSACleanup() for us.
		}
		; Otherwise, process the data received. Testing shows that it's possible to get more than one line
		; at a time (even for explicitly-sent IR signals), which the following method handles properly.
		; Data received from WinLIRC looks like the following example (see the WinLIRC docs for details):
		; 0000000000eab154 00 NameOfButton NameOfRemote
		Loop, parse, ReceivedData, `n, `r
		{
			if A_LoopField in ,BEGIN,SIGHUP,END  ; Ignore blank lines and WinLIRC's start-up messages.
				continue
			ButtonName =  ; Init to blank in case there are less than 3 fields found below.
			Loop, parse, A_LoopField, %A_Space%  ; Extract the button name, which is the third field.
				if A_Index = 3
					ButtonName := A_LoopField
			global DelayBetweenButtonRepeats  ; Declare globals to make them available to this function.
			static PrevButtonName, PrevButtonTime, RepeatCount  ; These variables remember their values between calls.
			if (ButtonName != PrevButtonName || A_TickCount - PrevButtonTime > DelayBetweenButtonRepeats)
			{
				if IsLabel(ButtonName)  ; There is a subroutine associated with this button.
					Gosub %ButtonName%  ; Launch the subroutine.
				else ; Since there is no associated subroutine, briefly display which button was pressed.
				{
					if (ButtonName == PrevButtonName)
						RepeatCount += 1
					else
						RepeatCount = 1
					SplashTextOn, 150, 20, Button from WinLIRC, %ButtonName% (%RepeatCount%)
					SetTimer, SplashOff, 3000  ; This allows more signals to be processed while displaying the window.
				}
				PrevButtonName := ButtonName
				PrevButtonTime := A_TickCount
			}
		}
	}
	return 1  ; Tell the program that no further processing of this message is needed.
}



SplashOff:
SplashTextOff
SetTimer, SplashOff, Off
return



InsertInteger(pInteger, ByRef pDest, pOffset = 0, pSize = 4)
; To preserve any existing contents in pDest, only pSize number of bytes starting at pOffset
; are altered in it. The caller must ensure that pDest has sufficient capacity.
{
	mask := 0xFF  ; This serves to isolate each byte, one by one.
	Loop %pSize%  ; Copy each byte in the integer into the structure as raw binary data.
	{
		DllCall("RtlFillMemory", UInt, &pDest + pOffset + A_Index - 1, UInt, 1  ; Write one byte.
			, UChar, (pInteger & mask) >> 8 * (A_Index - 1))
		mask := mask << 8  ; Set it up for isolation of the next byte.
	}
}



ExitSub:  ; This subroutine is called automatically when the script exits for any reason.
; MSDN: "Any sockets open when WSACleanup is called are reset and automatically
; deallocated as if closesocket was called."
DllCall("Ws2_32\WSACleanup")
ExitApp