Keyboard & Mouse control

Everything else that doesn't fall into one of the other PB categories.
User avatar
Michael Vogel
Addict
Addict
Posts: 2799
Joined: Thu Feb 09, 2006 11:27 pm
Contact:

Keyboard & Mouse control

Post by Michael Vogel »

My old notebook's keyboard is broken and I start thinking to control it using the keyboard of another notebook. Using a program like VNC does not work as expected (horrible video quality and refresh rate). So what about using Purebasic to do that job anyhow there are many questions now...
  • How to connect the two computers (OLD and NEW) after booting the old notebook (bluetooth seems to be complicated, but using WLAN should be a simple job as both computers are in the same network segment)
  • Using a hotkey on the NEW computer toogles between standard behaviour of mouse and keyboard and controlling the remote notebook
  • The latter mode needs to scan all keyboard and mouse activities (on the NEW computer)...
  • ...and suppress all inputs for the NEW computer and simulate them on the OLD computer
Main problem seem to be to block and scan all inputs on the NEW computer, any ideas?
Fred
Administrator
Administrator
Posts: 18162
Joined: Fri May 17, 2002 4:39 pm
Location: France
Contact:

Re: Keyboard & Mouse control

Post by Fred »

User avatar
Michael Vogel
Addict
Addict
Posts: 2799
Joined: Thu Feb 09, 2006 11:27 pm
Contact:

Re: Keyboard & Mouse control

Post by Michael Vogel »

Thanks, will have a look...

My first start are the following two pieces of code (the 'server' for my old notebook with the non working keyboard, the 'client' for the new one):

The client should automatically connect to the server and can switch between active and passive mode (tested only on one PC so maybe it doesn't work with two PC's). And there's no code for controlling the mouse and keyboard for now.

Code: Select all

; Define Server

	#ApplicationTitle="·MYSVR·"

	EnableExplicit

	Structure AppType
		Window.i
		Icon.i;		Network State
		IconSize.i
		IconArcs.i
		DpiScale.i
		ConnectionIn.i
		ConnectionOut.i
		PartnerIP.i
		PartnerX.i
		PartnerY.i
		Quit.i
		Retry.i
	EndStructure

	Global App.AppType

	Enumeration
		#SysTray
		#Window
		#Icon
		#Font
		#PopupMenu
		#PopupQuit
	EndEnumeration

; EndDefine
; Define Include Network & More

	#ServerID=	#Null
	#ServerPort=	5004
	#ClientPort=	5005

	Structure ValueType
		A.l
		B.l
		C.l
		D.l
	EndStructure

	Structure MessageType
		Type.l
		StructureUnion
			Record.l[4]
			Values.ValueType
		EndStructureUnion
		Magic.l
	EndStructure

	Global Message.MessageType

	#Magic=$12345678

	Enumeration
		#CmdUnknown
		#CmdGetIP
		#CmdSetIP
		#CmdSetPassive
		#CmdSetActive
		#CmdMouseXY
		#CmdMouseButton
		#CmdKey
	EndEnumeration

	Enumeration
		#StateNoNetwork
		#StateIdle
		#StateConnected
		#StateActive
	EndEnumeration

	#Ticker=		5
	#RepeatTimer=	1500;	15000 ~ 60s

	#Undefined=	-#True

	#DpiBits=		12+SizeOf(Integer)
	#DpiScale=	1<<#DpiBits

	#DrawOpaque=	$FF000000
	#DrawMilky=	$80000000
	#DarkRed=		$0000A0
	#NiceGray=	$D0D0D0
	#NiceGreen=	$35A77C
	#NiceBlue=	$D3C985

	CompilerIf #PB_Compiler_Version>=600
		Macro InitNetwork()
			#True
		EndMacro
	CompilerEndIf


; EndDefine
Procedure ScaleUp(value)

	ProcedureReturn (((value)*App\DpiScale)/#DpiScale)

EndProcedure
Procedure SysTrayIcon(mode)

	Protected.s s,t
	Protected.i ct,cb

	With App

		If \DpiScale=#Null
			\DpiScale=GetDeviceCaps_(GetDC_(0),#LOGPIXELSX)<<#DpiBits/96
			\IconSize=ScaleUp(20)
			\IconArcs=ScaleUp(3)
			LoadFont(#Font,"Segoe UI",\IconSize*0.55,#PB_Font_Bold)
			CreateImage(#Icon,\IconSize,\IconSize,32)
			\Icon=#Undefined
		EndIf

		If mode<>\Icon

			StartDrawing(ImageOutput(#Icon))
			ct=#White

			Select mode
			Case #StateNoNetwork
				ct=#Black
				cb=#NiceGray
				s="X"
				t="No Network"
			Case #StateIdle
				ct=#Black
				cb=#NiceBlue
				s="I"
				t="Idle"
			Case #StateConnected
				cb=#NiceGreen
				s="C"
				t="Connected"
			Case #StateActive
				cb=#DarkRed
				s="A"
				t="Active"
			EndSelect

			DrawingMode(#PB_2DDrawing_AllChannels)
			Box(0,0,\IconSize,\IconSize,#Null)
			RoundBox(0,0,\IconSize,\IconSize,\IconArcs,\IconArcs,#DrawOpaque|cb)

			DrawingMode(#PB_2DDrawing_Outlined|#PB_2DDrawing_AlphaBlend)
			RoundBox(0,0,\IconSize,\IconSize,\IconArcs,\IconArcs,#DrawMilky)

			DrawingFont(FontID(#Font))
			DrawText((\IconSize-TextWidth(s))/2,(\IconSize-TextHeight(s)*1.1)/2,s,#DrawOpaque|ct,#Null)
			StopDrawing()

			If \Icon=#Undefined
				AddSysTrayIcon(#SysTray,\Window,ImageID(#Icon))
			Else
				ChangeSysTrayIcon(#SysTray,ImageID(#Icon))
			EndIf

			SysTrayIconToolTip(#SysTray,"MyServer by Michael Vogel - "+t)

			\Icon=mode
		EndIf

	EndWith

EndProcedure
Procedure InitServer()

	If initNetwork()

		If CreateNetworkServer(#ServerID,#ServerPort,#PB_Network_UDP)
			SysTrayIcon(#StateIdle)
			ProcedureReturn #True
		EndIf

	EndIf

	App\Retry=#RepeatTimer/#Ticker
	ProcedureReturn #Null

EndProcedure
Procedure ReadMessage()

	With App

		Message\Magic=#Null
		ReceiveNetworkData(\ConnectionIn,@Message,SizeOf(Message))
		ProcedureReturn Bool(Message\Magic=#Magic)

	EndWith

EndProcedure
Procedure SendMessage(type,*Value.ValueType)

	With App

		If \ConnectionOut
			Message\Type=type
			If *Value
				CopyMemory(*Value,@Message\Values,SizeOf(ValueType))
			Else
				FillMemory(@Message\Values,SizeOf(ValueType),#Null,#PB_Long)
			EndIf
			Message\Magic=#Magic
			ProcedureReturn Bool(SendNetworkData(\ConnectionOut,@Message,SizeOf(Message))>=0)

		Else
			ProcedureReturn #Null

		EndIf

	EndWith

EndProcedure
; End of Include Network & More

Procedure Main()

	With App

		If FindWindow_(0,#ApplicationTitle)=0

			\Window=OpenWindow(#Window,0,0,0,0,#ApplicationTitle,#PB_Window_Invisible)
			AddWindowTimer(#Window,#Null,#Ticker)

			SysTrayIcon(#StateNoNetwork)

			CreatePopupMenu(#PopupMenu)
			MenuItem(#PopupQuit,"Quit 'MyServer'...")

			InitServer()

			Repeat

				Select WaitWindowEvent()

				Case #PB_Event_SysTray
					Select EventType()

					Case #PB_EventType_LeftDoubleClick

					Case #PB_EventType_RightClick
						\Quit=#True

					Case #PB_EventType_LeftClick
						DisplayPopupMenu(#PopupMenu,\Window)

					EndSelect

				Case #PB_Event_Timer
					Select \Icon
					Case #StateNoNetwork
						If \Retry
							\Retry-1
							If \Retry=#Null
								InitServer()
							EndIf
						EndIf
					EndSelect

				Case #PB_Event_Menu;#PB_Event_Gadget
					Select EventGadget()

					Case #PopupQuit
						\Quit=#True
					EndSelect

				EndSelect

				Select NetworkServerEvent(#ServerID)

				Case #PB_NetworkEvent_Connect
					Debug "CONN (never happens)"

				Case #PB_NetworkEvent_Data
					\ConnectionIn=EventClient()
					If ReadMessage()
						Debug "MESS "+Str(Message\Type)+" "+Hex(Message\Values\A)+"."+Hex(Message\Values\B)+"."+Hex(Message\Values\C)+"."+Hex(Message\Values\D)
						Select Message\Type
						Case #CmdGetIP
							\PartnerIP=GetClientIP(\ConnectionIn)
							\ConnectionOut=OpenNetworkConnection(IPString(\PartnerIP),#ClientPort,#PB_Network_UDP)
							If SendMessage(#CmdSetIP,#Null)
								SysTrayIcon(#StateConnected)
							EndIf
						Case #CmdSetActive
							\PartnerX=Message\Values\A
							\PartnerY=Message\Values\B
							SysTrayIcon(#StateActive)
						Case #CmdSetPassive
							SysTrayIcon(#StateConnected)
						Case #CmdMouseXY
							Debug "MOUSE "+Str(Message\Values\A)+" | "+Str(Message\Values\B)

						EndSelect
					EndIf

				Case #PB_NetworkEvent_Disconnect
					Debug "DISC (never happens)"

				EndSelect


			Until \Quit

		EndIf

	EndWith

EndProcedure
Main()

Code: Select all

; Define Client

	EnableExplicit

	#ApplicationTitle="·MYCLT·"

	#MonCode=		666
	#MonControl=	#MOD_CONTROL|#MOD_SHIFT
	#MonKey=		#VK_K

	Structure AppType
		Window.i
		Icon.i;		Network State
		IconSize.i
		IconArcs.i
		DpiScale.i
		ConnectionIn.i
		ConnectionOut.i
		PartnerIP.i
		MouseX.i
		MouseY.i
		MouseButton.i
		Quit.i
		Retry.i
	EndStructure

	Global App.AppType

	Enumeration
		#SysTray
		#Window
		#Icon
		#Font
		#PopupMenu
		#PopupToggle
		#PopupQuit
	EndEnumeration

; EndDefine
; Define Include Network & More

	;#ServerID=	#Null
	#ServerPort=	5004
	#ClientID=	#Null
	#ClientPort=	5005

	Structure ValueType
		A.l
		B.l
		C.l
		D.l
	EndStructure

	Structure MessageType
		Type.l
		StructureUnion
			Record.l[4]
			Values.ValueType
		EndStructureUnion
		Magic.l
	EndStructure

	Global Message.MessageType
	Global Value.ValueType

	#Magic=$12345678

	Enumeration
		#CmdUnknown
		#CmdGetIP
		#CmdSetIP
		#CmdSetPassive
		#CmdSetActive
		#CmdMouseXY
		#CmdMouseButton
		#CmdKey
	EndEnumeration

	Enumeration
		#StateNoNetwork
		#StateIdle
		#StateConnected
		#StateActive
	EndEnumeration

	#Ticker=		5
	#RepeatTimer=	1500;	15000 ~ 60s

	#Undefined=	-#True

	#DpiBits=		12+SizeOf(Integer)
	#DpiScale=	1<<#DpiBits

	#DrawOpaque=	$FF000000
	#DrawMilky=	$80000000
	#DarkRed=		$0000A0
	#NiceGray=	$D0D0D0
	#NiceGreen=	$35A77C
	#NiceBlue=	$D3C985

	CompilerIf #PB_Compiler_Version>=600
		Macro InitNetwork()
			#True
		EndMacro
	CompilerEndIf

	Declare ReadMessage()

; EndDefine
Procedure ScaleUp(value)

	ProcedureReturn (((value)*App\DpiScale)/#DpiScale)

EndProcedure
Procedure SysTrayIcon(mode)

	Protected.s s,t
	Protected.i ct,cb

	With App

		If \DpiScale=#Null
			\DpiScale=GetDeviceCaps_(GetDC_(0),#LOGPIXELSX)<<#DpiBits/96
			\IconSize=ScaleUp(20)
			\IconArcs=ScaleUp(3)
			LoadFont(#Font,"Segoe UI",\IconSize*0.55,#PB_Font_Bold)
			CreateImage(#Icon,\IconSize,\IconSize,32)
			\Icon=#Undefined
		EndIf

		If mode<>\Icon

			StartDrawing(ImageOutput(#Icon))
			ct=#White

			Select mode
			Case #StateNoNetwork
				ct=#Black
				cb=#NiceGray
				s="X"
				t="No Network"
			Case #StateIdle
				ct=#Black
				cb=#NiceBlue
				s="N"
				t="Not connected"
			Case #StateConnected
				cb=#NiceGreen
				s="C"
				t="Connected"
			Case #StateActive
				cb=#DarkRed
				s="A"
				t="Active"
			EndSelect

			DrawingMode(#PB_2DDrawing_AllChannels)
			Box(0,0,\IconSize,\IconSize,#Null)
			RoundBox(0,0,\IconSize,\IconSize,\IconArcs,\IconArcs,#DrawOpaque|cb)

			DrawingMode(#PB_2DDrawing_Outlined|#PB_2DDrawing_AlphaBlend)
			RoundBox(0,0,\IconSize,\IconSize,\IconArcs,\IconArcs,#DrawMilky)

			DrawingFont(FontID(#Font))
			DrawText((\IconSize-TextWidth(s))/2,(\IconSize-TextHeight(s)*1.1)/2,s,#DrawOpaque|ct,#Null)
			StopDrawing()

			If \Icon=#Undefined
				AddSysTrayIcon(#SysTray,\Window,ImageID(#Icon))
			Else
				ChangeSysTrayIcon(#SysTray,ImageID(#Icon))
			EndIf

			SysTrayIconToolTip(#SysTray,"My Client by Michael Vogel - "+t)

			\Icon=mode
		EndIf

	EndWith

EndProcedure
Procedure SendUDPBroadcast(type,port)

	Protected sock,one,bytes
	Protected remote.sockaddr_in

	sock=socket_(#AF_INET,#SOCK_DGRAM,#IPPROTO_IP)

	If (sock=#SOCKET_ERROR)
		ProcedureReturn #Null
	EndIf

	one=1
	If (setsockopt_(sock,#SOL_SOCKET,#SO_BROADCAST,@one,SizeOf(one))=#SOCKET_ERROR)
		ProcedureReturn #Null
	EndIf

	remote\sin_family=#AF_INET
	remote\sin_port=htons_(port)
	remote\sin_addr=#INADDR_BROADCAST

	Message\Type=type
	Message\Magic=#Magic

	If (sendto_(sock,@Message,SizeOf(Message),0,@remote,SizeOf(remote))=#SOCKET_ERROR)
		ProcedureReturn #Null
	EndIf

	closesocket_(sock)
	ProcedureReturn #True

EndProcedure
Procedure InitServer()

	If initNetwork()

		If CreateNetworkServer(#ClientID,#ClientPort,#PB_Network_UDP)
			SysTrayIcon(#StateIdle)
			App\Retry=10
			ProcedureReturn #True
		EndIf

	EndIf

	App\Retry=#RepeatTimer/#Ticker
	ProcedureReturn #Null

EndProcedure
Procedure InitConnection()

	Protected timer

	With App

		If SendUDPBroadcast(#CmdGetIP,#ServerPort)

			timer=GetTickCount_()+2000
			Repeat
				Delay(#Ticker)
				If NetworkServerEvent()=#PB_NetworkEvent_Data
					\ConnectionIn=EventClient()
					If ReadMessage()
						If Message\Type=#CmdSetIP
							\PartnerIP=GetClientIP(\ConnectionIn)
							SysTrayIcon(#StateConnected)
							DisableMenuItem(#PopupMenu,#PopupToggle,#Null)
							ProcedureReturn #True
						EndIf
					EndIf
				EndIf
			Until GetTickCount_()>timer

		EndIf

		App\Retry=#RepeatTimer/#Ticker

	EndWith

EndProcedure
Procedure ReadMessage()

	With App

		Message\Magic=#Null
		ReceiveNetworkData(\ConnectionIn,@Message,SizeOf(Message))
		ProcedureReturn Bool(Message\Magic=#Magic)

	EndWith

EndProcedure
Procedure SendMessage(type,*Value.ValueType)

	With App

		\ConnectionOut=OpenNetworkConnection(IPString(\PartnerIP),#ServerPort,#PB_Network_UDP)
		If \ConnectionOut
			Message\Type=type
			If *Value
				CopyMemory(*Value,@Message\Values,SizeOf(ValueType))
			Else
				FillMemory(@Message\Values,SizeOf(ValueType),#Null,#PB_Long)
			EndIf
			Message\Magic=#Magic

			ProcedureReturn Bool(SendNetworkData(\ConnectionOut,@Message,SizeOf(Message))>=0)

		Else
			ProcedureReturn #Null

		EndIf

	EndWith

EndProcedure
; End of Include Network & More

Procedure ToggleMode()


	With App

		Select \Icon
		Case #StateConnected
			Value\A=GetSystemMetrics_(#SM_CXSCREEN)
			Value\B=GetSystemMetrics_(#SM_CYSCREEN)
			Value\C=0
			Value\D=0
			If SendMessage(#CmdSetActive,@Value)
				SysTrayIcon(#StateActive)
			EndIf
		Case #StateActive
			If SendMessage(#CmdSetPassive,#Null)
				SysTrayIcon(#StateConnected)
				\MouseX=#Undefined
				\MouseY=#Undefined
				\MouseButton=#Undefined
			EndIf
		EndSelect

	EndWith

EndProcedure
Procedure Main()

	Protected Cursor.Point

	With App

		If FindWindow_(0,#ApplicationTitle)=0

			\MouseX=#Undefined
			\MouseY=#Undefined
			\MouseButton=#Undefined

			\Window=OpenWindow(#Window,0,0,0,0,#ApplicationTitle,#PB_Window_Invisible)
			RegisterHotKey_(\Window,#MonCode,#MonControl,#MonKey)
			AddWindowTimer(#Window,#Null,#Ticker)

			SysTrayIcon(#StateNoNetwork)

			CreatePopupMenu(#PopupMenu)
			MenuItem(#PopupToggle,"Toggle Control"+#TAB$+"***")
			MenuBar()
			MenuItem(#PopupQuit,"Quit 'MyClient'...")
			DisableMenuItem(#PopupMenu,#PopupToggle,#True)

			InitServer()

			Repeat

				Select WaitWindowEvent()

				Case #PB_Event_SysTray
					Select EventType()

					Case #PB_EventType_LeftDoubleClick

					Case #PB_EventType_RightClick
						\Quit=#True

					Case #PB_EventType_LeftClick
						DisplayPopupMenu(#PopupMenu,\Window)
					EndSelect

				Case #PB_Event_Timer
					Select \Icon
					Case #StateNoNetwork,#StateIdle
						If \Retry
							\Retry-1
							If \Retry=#Null
								If \Icon=#StateNoNetwork
									InitServer()
								Else
									InitConnection()
								EndIf
							EndIf
						EndIf
					EndSelect

				Case #PB_Event_Menu;#PB_Event_Gadget
					Select EventGadget()

					Case #PopupToggle
						ToggleMode()

					Case #PopupQuit
						\Quit=#True
					EndSelect

				Case #WM_HOTKEY
					If EventwParam()=#MonCode
						ToggleMode()
					EndIf

				EndSelect



				If \Icon=#StateActive
					GetCursorPos_(@Cursor)
					If \MouseX<>Cursor\x Or \MouseY<>Cursor\y
						\MouseX=Cursor\x
						\MouseY=Cursor\y
						Value\A=Cursor\x
						Value\B=Cursor\y
						SendMessage(#CmdMouseXY,@Value)
					EndIf
				EndIf



				Select NetworkServerEvent()

				Case #PB_NetworkEvent_Connect
					Debug "CONN (never happens)"

				Case #PB_NetworkEvent_Data
					\ConnectionIn=EventClient()
					If ReadMessage()
						Debug "MESS "+Str(Message\Type)+" "+Hex(Message\Values\A)+"."+Hex(Message\Values\B)+"."+Hex(Message\Values\C)+"."+Hex(Message\Values\D)
						Select Message\Type
						Case #CmdGetIP
							\PartnerIP=GetClientIP(\ConnectionIn)
							\ConnectionOut=OpenNetworkConnection(IPString(\PartnerIP),#ServerPort,#PB_Network_UDP)
							If SendMessage(#CmdSetIP,#Null)
								SysTrayIcon(#StateConnected)
							EndIf

						EndSelect
					EndIf

				Case #PB_NetworkEvent_Disconnect
					Debug "DISC (never happens)"

				EndSelect

			Until \Quit

		EndIf

	EndWith

EndProcedure
Main()

SMaag
Enthusiast
Enthusiast
Posts: 316
Joined: Sat Jan 14, 2023 6:55 pm
Location: Bavaria/Germany

Re: Keyboard & Mouse control

Post by SMaag »

maybe I can give you an idea how to do Keyboard and mouse handling on the receiving Laptop.

Windows proivedes an API called SendInputs() which integrates the data into the Keyboard and mouse stream.f

seems to be a nice remote control Project!


(an other idea: just use an external Keyboard USB or BlueTooth)
User avatar
Kurzer
Enthusiast
Enthusiast
Posts: 671
Joined: Sun Jun 11, 2006 12:07 am
Location: Near Hamburg

Re: Keyboard & Mouse control

Post by Kurzer »

Hello Michael,

in case you also want to use an external program for this task, I can highly recommend the software "Input Director" https://inputdirector.com/.

It shares mouse and keyboard events across multiple computers on the network and has many other nice features.

I operate all my 3 computers with just one keyboard and mouse with this great software.
PB 6.12 x64, OS: Win 11 24H2 x64, Desktopscaling: 150%, CPU: I7 12700 H, RAM: 32 GB, GPU: Intel(R) Iris(R) Xe Graphics | NVIDIA GeForce RTX 3070, User age in 2025: 57y
"Happiness is a pet." | "Never run a changing system!"
User avatar
Michael Vogel
Addict
Addict
Posts: 2799
Joined: Thu Feb 09, 2006 11:27 pm
Contact:

Re: Keyboard & Mouse control

Post by Michael Vogel »

Thanks for all your replies, I was using an external keyboard but the different key layouts were making me small again :?
The InputDirector is BRILLIANT so I don't need to continue working on my solution :lol:


Anyhow here's an updated code which can control the mouse cursor position. There's also a change in broadcasting because my notebook's network is not the primary interface (see \BroadcastAddress in the Client code which is used for address\sin_addr in the procedure SendUDPBroadcast).

Code: Select all

; Define Network Client

	EnableExplicit

	#ApplicationTitle="·MYCLT·"

	#MonCode=		666
	#MonControl=	#MOD_CONTROL|#MOD_SHIFT
	#MonKey=		#VK_K

	Structure AppType
		Window.i
		BroadcastAddress.s
		Icon.i;		Network State
		IconSize.i
		IconArcs.i
		DpiScale.i
		ConnectionIn.i
		ConnectionOut.i
		PartnerIP.i
		MouseX.i
		MouseY.i
		MouseButton.i
		WindowX.i
		WindowY.i
		Quit.i
		Retry.i
		DeadTimer.i
	EndStructure

	Global App.AppType

	Enumeration
		#SysTray
		#Window
		#Icon
		#Font
		#PopupMenu
		#PopupToggle
		#PopupQuit
	EndEnumeration

; EndDefine
; Define Include Network & More

	;#ServerID=	#Null
	#ServerPort=	5004
	#ClientID=	#Null
	#ClientPort=	5005

	Structure ValueType
		A.l
		B.l
		C.l
		D.l
	EndStructure

	Structure MessageType
		Type.l
		StructureUnion
			Record.l[4]
			Values.ValueType
		EndStructureUnion
		Magic.l
	EndStructure

	Global Message.MessageType
	Global Value.ValueType

	#Magic=$12345678

	Enumeration
		#CmdUnknown
		#CmdImAlive
		#CmdBreak
		#CmdGetIP
		#CmdSetIP
		#CmdSetPassive
		#CmdSetActive
		#CmdMouseXY
		#CmdMouseButton
		#CmdKey
	EndEnumeration

	Enumeration
		#StateNoNetwork
		#StateIdle
		#StateConnected
		#StateActive
	EndEnumeration

	#Ticker=		5
	#RepeatTimer=	5000/#Ticker;	5000 ~ 10s
	#AliveTimer=	500/#Ticker
	#DeadTimer=	#AliveTimer*3

	#Undefined=	-#True

	#DpiBits=		12+SizeOf(Integer)
	#DpiScale=	1<<#DpiBits

	#DrawOpaque=	$FF000000
	#DrawMilky=	$80000000
	#DarkRed=		$0000A0
	#NiceGray=	$D0D0D0
	#NiceGreen=	$35A77C
	#NiceBlue=	$D3C985

	CompilerIf #PB_Compiler_Version>=600
		Macro InitNetwork()
			#True
		EndMacro
	CompilerEndIf

	Declare ReadMessage()

; EndDefine
Procedure ScaleUp(value)

	ProcedureReturn (((value)*App\DpiScale)/#DpiScale)

EndProcedure
Procedure SysTrayIcon(mode)

	Protected.s s,t
	Protected.i ct,cb

	With App

		If \DpiScale=#Null
			\DpiScale=GetDeviceCaps_(GetDC_(0),#LOGPIXELSX)<<#DpiBits/96
			\IconSize=ScaleUp(20)
			\IconArcs=ScaleUp(3)
			LoadFont(#Font,"Segoe UI",\IconSize*0.55,#PB_Font_Bold)
			CreateImage(#Icon,\IconSize,\IconSize,32)
			\Icon=#Undefined
		EndIf

		If mode<>\Icon

			DisableMenuItem(#PopupMenu,#PopupToggle,Bool(mode<#StateConnected))

			StartDrawing(ImageOutput(#Icon))
			ct=#White

			Select mode
			Case #StateNoNetwork
				ct=#Black
				cb=#NiceGray
				s="X"
				t="No Network"
			Case #StateIdle
				ct=#Black
				cb=#NiceBlue
				s="N"
				t="Not connected"
			Case #StateConnected
				cb=#NiceGreen
				s="C"
				t="Connected"
				\DeadTimer=#DeadTimer
			Case #StateActive
				cb=#DarkRed
				s="A"
				t="Active"
				\DeadTimer=#DeadTimer
			EndSelect

			DrawingMode(#PB_2DDrawing_AllChannels)
			Box(0,0,\IconSize,\IconSize,#Null)
			RoundBox(0,0,\IconSize,\IconSize,\IconArcs,\IconArcs,#DrawOpaque|cb)

			DrawingMode(#PB_2DDrawing_Outlined|#PB_2DDrawing_AlphaBlend)
			RoundBox(0,0,\IconSize,\IconSize,\IconArcs,\IconArcs,#DrawMilky)

			DrawingFont(FontID(#Font))
			DrawText((\IconSize-TextWidth(s))/2,(\IconSize-TextHeight(s)*1.1)/2,s,#DrawOpaque|ct,#Null)
			StopDrawing()

			If \Icon=#Undefined
				AddSysTrayIcon(#SysTray,\Window,ImageID(#Icon))
			Else
				ChangeSysTrayIcon(#SysTray,ImageID(#Icon))
			EndIf

			SysTrayIconToolTip(#SysTray,"My Client by Michael Vogel - "+t)

			\Icon=mode
		EndIf

	EndWith

EndProcedure
Procedure SendUDPBroadcast(type,ip.s,port)

	Protected socket
	Protected *ip
	Protected address.sockaddr_in

	socket=socket_(#AF_INET,#SOCK_DGRAM,#IPPROTO_IP)

	If (socket<>#SOCKET_ERROR)

		Protected one=1
		;If (setsockopt_(socket,#SOL_SOCKET,#SO_BROADCAST,@one,SizeOf(one))=#SOCKET_ERROR)
		;	ProcedureReturn #Null
		;EndIf

		*ip=Ascii(ip)
		address\sin_family=	#AF_INET
		address\sin_port=	htons_(port)
		address\sin_addr=	inet_addr_(*ip)
		;address\sin_addr=	#INADDR_BROADCAST;	sends to 'first' interface only
		FreeMemory(*ip)

		Message\Type=type
		Message\Magic=#Magic

		If (sendto_(socket,@Message,SizeOf(Message),0,@address,SizeOf(address))=#SOCKET_ERROR)
			ProcedureReturn #Null
		EndIf

		closesocket_(socket)
	EndIf

	ProcedureReturn #True

EndProcedure
Procedure SendUDPBroadcastOld(type,port)

	Protected sock,one,bytes
	Protected remote.sockaddr_in

	sock=socket_(#AF_INET,#SOCK_DGRAM,#IPPROTO_IP)

	If (sock=#SOCKET_ERROR)
		ProcedureReturn #Null
	EndIf

	one=1
	If (setsockopt_(sock,#SOL_SOCKET,#SO_BROADCAST,@one,SizeOf(one))=#SOCKET_ERROR)
		ProcedureReturn #Null
	EndIf

	remote\sin_family=#AF_INET
	remote\sin_port=htons_(port)
	remote\sin_addr=#INADDR_BROADCAST

	Message\Type=type
	Message\Magic=#Magic

	If (sendto_(sock,@Message,SizeOf(Message),0,@remote,SizeOf(remote))=#SOCKET_ERROR)
		ProcedureReturn #Null
	EndIf

	closesocket_(sock)
	ProcedureReturn #True

EndProcedure
Procedure InitServer()

	If initNetwork()

		If CreateNetworkServer(#ClientID,#ClientPort,#PB_Network_UDP)
			SysTrayIcon(#StateIdle)
			App\Retry=10
			ProcedureReturn #True
		EndIf

	EndIf

	App\Retry=#RepeatTimer/#Ticker
	ProcedureReturn #Null

EndProcedure
Procedure InitConnection()

	Protected timer

	With App

		Debug "BROADCAST"
		If SendUDPBroadcast(#CmdGetIP,\BroadcastAddress,#ServerPort)

			timer=GetTickCount_()+2000
			Repeat
				Delay(#Ticker)
				If NetworkServerEvent()=#PB_NetworkEvent_Data
					\ConnectionIn=EventClient()
					If ReadMessage()
						If Message\Type=#CmdSetIP
							\PartnerIP=GetClientIP(\ConnectionIn)
							SysTrayIcon(#StateConnected)
							ProcedureReturn #True
						EndIf
					EndIf
				EndIf
			Until GetTickCount_()>timer

		EndIf

		App\Retry=#RepeatTimer/#Ticker

	EndWith

EndProcedure
Procedure ReadMessage()

	With App

		Message\Magic=#Null
		Message\Type=#Null
		ReceiveNetworkData(\ConnectionIn,@Message,SizeOf(Message))
		; Debug "DATA "+Str(Message\Type)+" "+Hex(Message\Values\A)+":"+Hex(Message\Values\B)+":"+Hex(Message\Values\C)+":"+Hex(Message\Values\D)
		ProcedureReturn Bool(Message\Magic=#Magic)

	EndWith

EndProcedure
Procedure SendMessage(type,*Value.ValueType)

	With App

		\ConnectionOut=OpenNetworkConnection(IPString(\PartnerIP),#ServerPort,#PB_Network_UDP)
		If \ConnectionOut
			Message\Type=type
			If *Value
				CopyMemory(*Value,@Message\Values,SizeOf(ValueType))
			Else
				FillMemory(@Message\Values,SizeOf(ValueType),#Null,#PB_Long)
			EndIf
			Message\Magic=#Magic

			ProcedureReturn Bool(SendNetworkData(\ConnectionOut,@Message,SizeOf(Message))>=0)

		Else
			ProcedureReturn #Null

		EndIf

	EndWith

EndProcedure
; End of Include Network & More

Procedure ToggleMode()

	With App

		Select \Icon

		Case #StateConnected
			\WindowX=GetSystemMetrics_(#SM_CXSCREEN)
			\WindowY=GetSystemMetrics_(#SM_CYSCREEN)
			Value\A=\WindowX
			Value\B=\WindowY
			Value\C=0
			Value\D=0
			If SendMessage(#CmdSetActive,@Value)
				SysTrayIcon(#StateActive)
				;ResizeWindow(#Window,#PB_Ignore,#PB_Ignore,\WindowX,\WindowY)
				;HideWindow(#Window,#Null)
				;SetWindowLong_(WindowID(#Window),#GWL_EXSTYLE,GetWindowLong_(WindowID(#Window),#GWL_EXSTYLE)|#WS_EX_LAYERED)
				;SetLayeredWindowAttributes_(WindowID(#Window), 0, (255*70)/100,#LWA_ALPHA)
			Else
				\Retry=10
				SysTrayIcon(#StateIdle)
			EndIf

		Case #StateActive
			If SendMessage(#CmdSetPassive,#Null)
				SysTrayIcon(#StateConnected)
				\MouseX=#Undefined
				\MouseY=#Undefined
				\MouseButton=#Undefined
				HideWindow(#Window,#True)
			Else
				\Retry=10
				SysTrayIcon(#StateIdle)
			EndIf
		EndSelect

	EndWith

EndProcedure
Procedure Main()

	Protected Cursor.Point

	With App

		If FindWindow_(0,#ApplicationTitle)=0

			\BroadcastAddress="10.0.0.255"

			\MouseX=#Undefined
			\MouseY=#Undefined
			\MouseButton=#Undefined

			CreatePopupMenu(#PopupMenu)
			MenuItem(#PopupToggle,"Toggle Control"+#TAB$+"Ctrl+Shift+K")
			MenuBar()
			MenuItem(#PopupQuit,"Quit 'MyClient'...")

			\Window=OpenWindow(#Window,0,0,0,0,#ApplicationTitle,#PB_Window_Invisible)
			RegisterHotKey_(\Window,#MonCode,#MonControl,#MonKey)
			AddWindowTimer(#Window,#Null,#Ticker)

			SysTrayIcon(#StateNoNetwork)
			InitServer()

			Repeat

				Select WaitWindowEvent()

				Case #PB_Event_SysTray
					Select EventType()

					Case #PB_EventType_LeftDoubleClick
						ToggleMode()

					Case #PB_EventType_RightClick
						\Quit=#True

					Case #PB_EventType_LeftClick
						DisplayPopupMenu(#PopupMenu,\Window)
					EndSelect

				Case #PB_Event_Timer
					Select \Icon
					Case #StateNoNetwork,#StateIdle
						If \Retry
							\Retry-1
							If \Retry=#Null
								If \Icon=#StateNoNetwork
									InitServer()
								Else
									InitConnection()
								EndIf
							EndIf
						EndIf
					Case #StateConnected,#StateActive
						\DeadTimer-1
						If \DeadTimer<0
							Debug "Server is dead..."
							SysTrayIcon(#StateIdle)
							\Retry=10
						EndIf
					EndSelect

				Case #PB_Event_Menu;#PB_Event_Gadget
					Select EventGadget()

					Case #PopupToggle
						ToggleMode()

					Case #PopupQuit
						\Quit=#True
					EndSelect

				Case #WM_HOTKEY
					If EventwParam()=#MonCode
						ToggleMode()
					EndIf

				Case #WM_MOUSEMOVE
					Debug "*"
				EndSelect


				If \Icon=#StateActive
					GetCursorPos_(@Cursor)
					If \MouseX<>Cursor\x Or \MouseY<>Cursor\y
						\MouseX=Cursor\x
						\MouseY=Cursor\y
						Value\A=Cursor\x
						Value\B=Cursor\y
						SendMessage(#CmdMouseXY,@Value)
					EndIf
				EndIf


				Select NetworkServerEvent(#ClientID)

				Case #PB_NetworkEvent_Connect
					Debug "CONN (never happens)"

				Case #PB_NetworkEvent_Data
					\ConnectionIn=EventClient()
					If ReadMessage()
						Debug "MESS "+Str(Message\Type)+" "+Hex(Message\Values\A)+":"+Hex(Message\Values\B)+":"+Hex(Message\Values\C)+":"+Hex(Message\Values\D)
						Select Message\Type
						Case #CmdImAlive
							Debug "Server is alive"
							\DeadTimer=#DeadTimer
						Case #CmdBreak
							If \Icon>#StateIdle
								SysTrayIcon(#StateIdle)
								\Retry=10
							EndIf
						Case #CmdGetIP
							\PartnerIP=GetClientIP(\ConnectionIn)
							\ConnectionOut=OpenNetworkConnection(IPString(\PartnerIP),#ServerPort,#PB_Network_UDP)
							If SendMessage(#CmdSetIP,#Null)
								SysTrayIcon(#StateConnected)
							EndIf

						EndSelect
					EndIf

				Case #PB_NetworkEvent_Disconnect
					Debug "DISC (never happens)"

				EndSelect

			Until \Quit

			;If \Icon>#StateIdle
			SendMessage(#CmdBreak,#Null)
			;EndIf

		EndIf

	EndWith

EndProcedure
Main()

Code: Select all

; Define Network Server

	#ApplicationTitle="·MYSVR·"

	EnableExplicit

	Structure AppType
		Window.i
		Icon.i;		Network State
		IconSize.i
		IconArcs.i
		DpiScale.i
		ConnectionIn.i
		ConnectionOut.i
		PartnerIP.i
		PartnerX.i
		PartnerY.i
		WindowX.i
		WindowY.i
		Quit.i
		Retry.i
		Alive.i
	EndStructure

	Global App.AppType

	Enumeration
		#SysTray
		#Window
		#Icon
		#Font
		#PopupMenu
		#PopupQuit
	EndEnumeration

; EndDefine
; Define Include Network & More

	#ServerID=	#Null
	#ServerPort=	5004
	#ClientPort=	5005

	Structure ValueType
		A.l
		B.l
		C.l
		D.l
	EndStructure

	Structure MessageType
		Type.l
		StructureUnion
			Record.l[4]
			Values.ValueType
		EndStructureUnion
		Magic.l
	EndStructure

	Global Message.MessageType

	#Magic=$12345678

	Enumeration
		#CmdUnknown
		#CmdImAlive
		#CmdBreak
		#CmdGetIP
		#CmdSetIP
		#CmdSetPassive
		#CmdSetActive
		#CmdMouseXY
		#CmdMouseButton
		#CmdKey
	EndEnumeration

	Enumeration
		#StateNoNetwork
		#StateIdle
		#StateConnected
		#StateActive
	EndEnumeration

	#Ticker=		5
	#RepeatTimer=	5000/#Ticker;	5000 ~ 10s
	#AliveTimer=	500/#Ticker

	#Undefined=	-#True

	#DpiBits=		12+SizeOf(Integer)
	#DpiScale=	1<<#DpiBits

	#DrawOpaque=	$FF000000
	#DrawMilky=	$80000000
	#DarkRed=		$0000A0
	#NiceGray=	$D0D0D0
	#NiceGreen=	$35A77C
	#NiceBlue=	$D3C985

	CompilerIf #PB_Compiler_Version>=600
		Macro InitNetwork()
			#True
		EndMacro
	CompilerEndIf


; EndDefine
Procedure ScaleUp(value)

	ProcedureReturn (((value)*App\DpiScale)/#DpiScale)

EndProcedure
Procedure SysTrayIcon(mode)

	Protected.s s,t
	Protected.i ct,cb

	With App

		If \DpiScale=#Null
			\DpiScale=GetDeviceCaps_(GetDC_(0),#LOGPIXELSX)<<#DpiBits/96
			\IconSize=ScaleUp(20)
			\IconArcs=ScaleUp(3)
			LoadFont(#Font,"Segoe UI",\IconSize*0.55,#PB_Font_Bold)
			CreateImage(#Icon,\IconSize,\IconSize,32)
			\Icon=#Undefined
		EndIf

		If mode<>\Icon

			StartDrawing(ImageOutput(#Icon))
			ct=#White

			Select mode
			Case #StateNoNetwork
				ct=#Black
				cb=#NiceGray
				s="X"
				t="No Network"
			Case #StateIdle
				ct=#Black
				cb=#NiceBlue
				s="I"
				t="Idle"
			Case #StateConnected
				cb=#NiceGreen
				s="C"
				t="Connected"
			Case #StateActive
				cb=#DarkRed
				s="A"
				t="Active"
			EndSelect

			DrawingMode(#PB_2DDrawing_AllChannels)
			Box(0,0,\IconSize,\IconSize,#Null)
			RoundBox(0,0,\IconSize,\IconSize,\IconArcs,\IconArcs,#DrawOpaque|cb)

			DrawingMode(#PB_2DDrawing_Outlined|#PB_2DDrawing_AlphaBlend)
			RoundBox(0,0,\IconSize,\IconSize,\IconArcs,\IconArcs,#DrawMilky)

			DrawingFont(FontID(#Font))
			DrawText((\IconSize-TextWidth(s))/2,(\IconSize-TextHeight(s)*1.1)/2,s,#DrawOpaque|ct,#Null)
			StopDrawing()

			If \Icon=#Undefined
				AddSysTrayIcon(#SysTray,\Window,ImageID(#Icon))
			Else
				ChangeSysTrayIcon(#SysTray,ImageID(#Icon))
			EndIf

			SysTrayIconToolTip(#SysTray,"MyServer by Michael Vogel - "+t)

			\Icon=mode
		EndIf

	EndWith

EndProcedure
Procedure InitServer()

	If InitNetwork()

		If CreateNetworkServer(#ServerID,#ServerPort,#PB_Network_UDP)
			SysTrayIcon(#StateIdle)
			ProcedureReturn #True
		EndIf

	EndIf

	App\Retry=#RepeatTimer/#Ticker
	ProcedureReturn #Null

EndProcedure
Procedure ReadMessage()

	With App

		Message\Magic=#Null
		ReceiveNetworkData(\ConnectionIn,@Message,SizeOf(Message))
		; Debug "DATA "+Str(Message\Type)+" "+Hex(Message\Values\A)+"."+Hex(Message\Values\B)+"."+Hex(Message\Values\C)+"."+Hex(Message\Values\D)

		ProcedureReturn Bool(Message\Magic=#Magic)

	EndWith

EndProcedure
Procedure SendMessage(type,*Value.ValueType)

	With App

		\ConnectionOut=OpenNetworkConnection(IPString(\PartnerIP),#ClientPort,#PB_Network_UDP)

		If \ConnectionOut
			Message\Type=type
			If *Value
				CopyMemory(*Value,@Message\Values,SizeOf(ValueType))
			Else
				FillMemory(@Message\Values,SizeOf(ValueType),#Null,#PB_Long)
			EndIf
			Message\Magic=#Magic
			Debug "Send message "+type+" to "+IPString(\PartnerIP)
			ProcedureReturn Bool(SendNetworkData(\ConnectionOut,@Message,SizeOf(Message))>=0)

		Else
			ProcedureReturn #Null

		EndIf

	EndWith

EndProcedure
; End of Include Network & More

Procedure Main()

	With App

		If FindWindow_(0,#ApplicationTitle)=0

			\Window=OpenWindow(#Window,0,0,0,0,#ApplicationTitle,#PB_Window_Invisible)
			AddWindowTimer(#Window,#Null,#Ticker)

			SysTrayIcon(#StateNoNetwork)

			CreatePopupMenu(#PopupMenu)
			MenuItem(#PopupQuit,"Quit 'MyServer'...")

			InitServer()

			Repeat

				Select WaitWindowEvent()

				Case #PB_Event_SysTray
					Select EventType()

					Case #PB_EventType_LeftDoubleClick

					Case #PB_EventType_RightClick
						\Quit=#True

					Case #PB_EventType_LeftClick
						DisplayPopupMenu(#PopupMenu,\Window)

					EndSelect

				Case #PB_Event_Timer
					Select \Icon
					Case #StateNoNetwork
						If \Retry
							\Retry-1
							If \Retry=#Null
								InitServer()
							EndIf
						EndIf
					Case #StateConnected,#StateActive
						\Alive-1
						If \Alive<=#Null
							\Alive=#AliveTimer
							SendMessage(#CmdImAlive,#Null)
						EndIf
					EndSelect

				Case #PB_Event_Menu;#PB_Event_Gadget
					Select EventGadget()

					Case #PopupQuit
						\Quit=#True
					EndSelect

				EndSelect

				Select NetworkServerEvent(#ServerID)

				Case #PB_NetworkEvent_Connect
					Debug "CONN (never happens)"

				Case #PB_NetworkEvent_Data
					\ConnectionIn=EventClient()
					If ReadMessage()
						Debug "MESS "+Str(Message\Type)+" "+Hex(Message\Values\A)+":"+Hex(Message\Values\B)+":"+Hex(Message\Values\C)+":"+Hex(Message\Values\D)
						Select Message\Type
						Case #CmdGetIP
							\PartnerIP=GetClientIP(\ConnectionIn)
							;\ConnectionOut=OpenNetworkConnection(IPString(\PartnerIP),#ClientPort,#PB_Network_UDP)
							If SendMessage(#CmdSetIP,#Null)
								SysTrayIcon(#StateConnected)
							EndIf
						Case #CmdSetActive
							\PartnerX=Message\Values\A
							\PartnerY=Message\Values\B
							\WindowX=GetSystemMetrics_(#SM_CXSCREEN)
							\WindowY=GetSystemMetrics_(#SM_CYSCREEN)

							SysTrayIcon(#StateActive)
						Case #CmdSetPassive
							SysTrayIcon(#StateConnected)
						Case #CmdMouseXY
							; Debug "MOUSE "+Str(Message\Values\A)+" | "+Str(Message\Values\B)
							SetCursorPos_(MulDiv_(Message\Values\A,\WindowX,\PartnerX),MulDiv_(Message\Values\B,\WindowY,\PartnerX))
							
						Case #CmdBreak
							If \Icon>#StateIdle
								SysTrayIcon(#StateIdle)
							EndIf

						EndSelect
					EndIf

				Case #PB_NetworkEvent_Disconnect
					Debug "DISC (never happens)"

				EndSelect


			Until \Quit

			If \Icon>#StateIdle
				SendMessage(#CmdBreak,#Null)
			EndIf

		EndIf

	EndWith

EndProcedure
Main()
Post Reply