Página 1 de 1

Control remoto

Publicado: 30 May 2011, 19:37
por olivarra1
Os dejo aqui otra invención mia, hecha medio en autoit, medio en flash.

Es uno de estos programas de control remoto que corren por ahí, que te permiten controlar un ordenador desde otro, pudiendo ver el escritorio del ordenador remoto y hacer clicks por ahí, o enviando teclas.
Este lo optimizé para que me pueda conectar desde cualquier dispositivo (en concreto, un smartphone Android, que aun no lo tengo [mañana me llega :P]).

La parte del cliente, el que se conecta, en el ordenador que queremos controlar, la queria hacer en el lenguaje de Android, pero como que todavia no he podido hacer ningun programa para esta plataforma ni siquiera lo puedo probar [pues el emulador es muy lento], lo hize con el Flex SDK 4, con FlashDevelop [para hacer aplicaciones Flash completamente gratis :D], ya que los .swf con ActionScript 3 tambien funcionan en Android.

La aplicacion principal, el servidor, esta hecho en AutoIt. este es el script:

Código: Seleccionar todo

#include <GDIPlus.au3>
#include <ScreenCapture.au3>

Opt("MouseClickDragDelay", 50)

Dim $deviceScreenWidth = 480
Dim $deviceScreenHeight = 770
Dim $deviceRatio = $deviceScreenWidth / $deviceScreenHeight

Const $DEBUG = False ; If true, waits 1 second before executing any command that depends of the state of the remote machine [window active]
Const $RESIZE_TO_BIGGER = False
		#cs If true, if the area to capture is smaller than the device screen, the image will be resized to fit the
		device screen and then sent. If false, it won't resize it, send it, and the client will resize to fit the image. True requires more
		bandwidth, but the quality of the resized image is a lot better than False.
		#ce

Const $CLICK = "1"
Const $DOWN = "2"
Const $UP = "3"
Const $RIGHT = "4"
Const $LEFT = "5"
Const $SCREEN = "6"
Const $WINDOW = "7"

Const $LOGIN = "a"
Const $RESOLUTION = "b"
Const $PASSWORD = "c"
Const $AUTOIT = "d"
Const $RAW = "e"

Const $storedPassword = "myprivatepassword"

Global $regionStartX
Global $regionStartY
Global $regionEndX
Global $regionEndY

Func getRegionWidth()
	Return $regionEndX - $regionStartX
EndFunc
Func getRegionHeight()
	Return $regionEndY - $regionStartY
EndFunc

_ScreenCapture_SetBMPFormat(0)

TCPStartup()

Dim $srvSocket, $cliSocket
$srvSocket = TCPListen("127.0.0.1", 1045) ; Localhost connection
;$srvSocket = TCPListen("192.168.1.10", 1045) ; LAN connection
;$srvSocket = TCPListen("192.168.0.2", 1045) ; LAN connection
If $srvSocket == -1 Then
	Exit
EndIf

Dim $error = True
Dim $ratio = 0
Dim $allowed
While 1
	If $error Then
		Do
			$cliSocket = TCPAccept($srvSocket)
			$allowed = False
			$regionStartX = 0
			$regionStartY = 0
			$regionEndX = $regionStartX + @DesktopWidth ; @DesktopWidth
			$regionEndY = $regionStartY + @DesktopHeight ; @DesktopHeight
		Until $cliSocket <> -1
	EndIf

	;size(2)code(a-zA-Z0-9)data
	$s = TCPRecv($cliSocket, 2)
	$size = Int($s)
	If $size > 0 Then
		$str = TCPRecv($cliSocket, $size)
		While StringLen($str) < $size
			$str = $str & TCPRecv($cliSocket, $size - StringLen($str))
		WEnd
		$codi = StringLeft($str, 1)
		$dades = StringTrimLeft($str, 1)


		If $codi == $LOGIN Then
			If $dades == "thisismypassword" Then
				If $DEBUG Then sleep(1000)
				$allowed = True
			EndIf
		ElseIf $codi == $RESOLUTION Then
			$str = StringSplit($dades, ",", 2)
			$deviceScreenWidth = Int($str[0])
			$deviceScreenHeight = Int($str[1])
			$deviceRatio = $deviceScreenWidth / $deviceScreenHeight
			;TrayTip("", $deviceScreenWidth & ", " & $deviceScreenHeight, 5)
		ElseIf $codi == $PASSWORD And $allowed Then
			Send($storedPassword)
		ElseIf $codi == $AUTOIT And $allowed Then
			If $DEBUG Then sleep(1000)
			Send($dades, 0)
		ElseIf $codi == $RAW And $allowed Then
			If $DEBUG Then sleep(1000)
			Send($dades, 1)
		ElseIf $codi == $CLICK And $allowed Then
			$str = StringSplit($dades, ",", 2)
			$x0 = $str[1] / $ratio + $regionStartX
			$y0 = $str[2] / $ratio + $regionStartY
			$xf = $str[3] / $ratio + $regionStartX
			$yf = $str[4] / $ratio + $regionStartY
			If $str[0] == "r" Then ; right
				MouseClickDrag("secondary", $x0, $y0, $xf, $yf, 3)
			ElseIf $str[0] == "l" Then ; left
				MouseClickDrag("primary", $x0, $y0, $xf, $yf, 3)
			EndIf
		ElseIf $codi == $DOWN Then
			$val = Int($dades)
			$regionEndY = $regionEndY + $val / $ratio
		ElseIf $codi == $UP Then
			$val = Int($dades)
			$regionStartY = $regionStartY + $val / $ratio
		ElseIf $codi == $RIGHT Then
			$val = Int($dades)
			$regionEndX = $regionEndX + $val / $ratio
		ElseIf $codi == $LEFT Then
			$val = Int($dades)
			$regionStartX = $regionStartX + $val / $ratio
		ElseIf $codi == $SCREEN Then
			$regionStartX = 0
			$regionStartY = 0
			$regionEndX = $regionStartX + @DesktopWidth
			$regionEndY = $regionStartY + @DesktopHeight
		ElseIf $codi == $WINDOW Then
			If $DEBUG Then sleep(1000)
			$arr = WinGetPos("[ACTIVE]")
			$regionStartX = $arr[0]
			$regionStartY = $arr[1]
			$regionEndX = $regionStartX + $arr[2]
			$regionEndY = $regionStartY + $arr[3]
		EndIf
	EndIf

	$w = getRegionWidth()
	$h = getRegionHeight()
	$regionRatio = $w / $h

	If ($deviceRatio > 1 And $regionRatio > 1) Or ($deviceRatio < 1 And $regionRatio < 1) Then ; The device won't flip the image
		$ratio = $deviceScreenWidth / $w
		if $ratio * $h > $deviceScreenHeight Then $ratio = $deviceScreenHeight / $h
	Else ; The device will flip the image
		$ratio = $deviceScreenWidth / $h
		if $ratio * $w > $deviceScreenHeight Then $ratio = $deviceScreenHeight / $w
	EndIf
	If $ratio < 1 Or $RESIZE_TO_BIGGER Then
		$deviceW = $w * $ratio
		$deviceH = $h * $ratio
	Else
		$deviceW = $w
		$deviceH = $h
	EndIf

	If $allowed Then
		$img = _ScreenCapture_Capture("", $regionStartX, $regionStartY, $regionEndX, $regionEndY)
		_ImageResize($img, @ScriptDir & "\tmp.jpg", $deviceW, $deviceH)
		_WinAPI_DeleteObject($img)
	Else
		$img = _ScreenCapture_Capture("", 0, 0, 0, 0)
		_ImageResize($img, @ScriptDir & "\tmp.jpg", $deviceScreenWidth, $deviceScreenHeight)
		_WinAPI_DeleteObject($img)
	EndIf

	$size = FileGetSize(@ScriptDir & "\tmp.jpg")
	$oFile = FileOpen(@ScriptDir & "\tmp.jpg", 0)
	$bin = FileRead($oFile)
	FileClose($oFile)

	SetError(0)
	$str = to10chars(String($size))
	TCPSend($cliSocket, $str)
	TCPSend($cliSocket, $bin)
	If @error Then
		$error = True
	Else
		$error = False
	EndIf
WEnd

Func to10chars($str)
	$num = 9 - StringLen($str)
	For $i = 1 To $num
		$str = "0" & $str
	Next
	$str = "s" & $str
	Return $str
EndFunc

Func _ImageResize($sInImage, $sOutImage, $iW, $iH)
    Local $hWnd, $hDC, $hBMP, $hImage1, $hImage2, $hGraphic, $CLSID, $i = 0


    ;OutFile path, to use later on.
    Local $sOP = StringLeft($sOutImage, StringInStr($sOutImage, "\", 0, -1))

    ;OutFile name, to use later on.
    Local $sOF = StringMid($sOutImage, StringInStr($sOutImage, "\", 0, -1) + 1)

    ;OutFile extension , to use for the encoder later on.
    Local $Ext = StringUpper(StringMid($sOutImage, StringInStr($sOutImage, ".", 0, -1) + 1))

    ; Win api to create blank bitmap at the width and height to put your resized image on.
    $hWnd = _WinAPI_GetDesktopWindow()
    $hDC = _WinAPI_GetDC($hWnd)
    $hBMP = _WinAPI_CreateCompatibleBitmap($hDC, $iW, $iH)
    _WinAPI_ReleaseDC($hWnd, $hDC)

    ;Start GDIPlus
    _GDIPlus_Startup()

    ;Get the handle of blank bitmap you created above as an image
    $hImage1 = _GDIPlus_BitmapCreateFromHBITMAP ($hBMP)

    ;Load the image you want to resize.
	If IsString($sInImage) Then
		$hImage2 = _GDIPlus_ImageLoadFromFile($sInImage)
	Else
		$hImage2 = _GDIPlus_BitmapCreateFromHBITMAP($sInImage) ; This function is modified here, so we don't have to write the bmp to the hard disk
	EndIf

    ;Get the graphic context of the blank bitmap
    $hGraphic = _GDIPlus_ImageGetGraphicsContext ($hImage1)

    ;Draw the loaded image onto the blank bitmap at the size you want
    _GDIPLus_GraphicsDrawImageRect($hGraphic, $hImage2, 0, 0, $iW, $iH)

    ;Get the encoder of to save the resized image in the format you want.
    $CLSID = _GDIPlus_EncodersGetCLSID($Ext)

    ;Generate a number for out file that doesn't already exist, so you don't overwrite an existing image.
    Do
        $i += 1
    Until (Not FileExists($sOP & $i & "_" & $sOF))

    ;Prefix the number to the begining of the output filename
    ;$sOutImage = $sOP & $i & "_" & $sOF
	$sOutImage = $sOP & $sOF

    ;Save the new resized image.
    _GDIPlus_ImageSaveToFileEx($hImage1, $sOutImage, $CLSID)

    ;Clean up and shutdown GDIPlus.
    _GDIPlus_ImageDispose($hImage1)
    _GDIPlus_ImageDispose($hImage2)
    _GDIPlus_GraphicsDispose ($hGraphic)
    _WinAPI_DeleteObject($hBMP)
    _GDIPlus_Shutdown()
EndFunc
Aqui tienen el post que hice en la pagina oficial donde se puede ver el codigo coloreado.

Lo que hace es:
1 -> Monta el servidor en la ip que se le ponga [ahora esta puesta para localhost, en el puerto 1045]
2 -> Acepta un cliente
3 -> Mira si el cliente le envia algun comando [estos son hacer clic en algun lugar, cambiar la area de captura, etc]
4 -> Luego procede a la captura de la zona de la pantalla que se necesite
5 -> Redimensiona la imagen [para que ocupe menos] y la comprime en jpg, guardandola en el disco duro
6 -> Abre la imagen en formato binario y se la manda al cliente
7 -> Si el cliente se desconectó, vuelve al paso 2.
8 -> Sino, vuelve al paso 3.

Alguna optimizacion que no se hacer, a ver si alguien me puede ayudar, es que una vez redimensionada y comprimida la imagen, es como hacerlo para no tener que guardarla y abrirla en el disco duro para obtener los datos en binario.

Otras cosas... por el principio os encontrareis estas dos constantes de opcion:
$DEBUG -> esta hace que se espere 1 segundo antes de enviar algun comando. Esto es para debugar el programa si haces una conexion a localhost [porque estas controlando tu propia maquina, que no tiene mucho sentido] y que te de tiempo a canviar de ventana, etc
$RESIZE_TO_BIGGER -> esta hace que si la area de captura es más pequeña que la pantalla del dispositivo, la redimensiona para hacerla mas grande. Si esta a True, la redimensiona, y queda una imagen mucho mas bonita, con mucha mas resolucion, pero necesita mas ancho de banda. Si esta a False, no la redimensiona, y el cliente ya se encargará de hacerla mas grande. Esta opcion consume menos ancho de banda, pero la resolucion es bastante menor.
Yo recomiendo que lo pongan a True. Tienen unas capturas en el .zip de las dos opciones, para que comparen :P

Luego, flash, cuando se conecta en algun sitio, necesita una cosa que se llama policy file, para saber si se puede conectar o no. Asi que tambien he montado un pequeño servidor, llamado policyServer, que lo que hace es escuchar en el puerto 843 por peticiones de policy file, y le envia el archivo policy.xml, tambien incluido en el .zip

Tienen que configurarlo para que escuche en su IP, sino no se les va a poder abrir.

Y para acabar, tengo que advertir que esta aplicación no es segura, si haces que el servidor acepte conexiones de internet. En un principio, cualquier podia entrar en vuestra maquina con este servidor, y controlarla [pudiendo acceder a datos personales, borrar archivos, etc]. Para evitar esto he hecho una medida de seguridad muy debil, que es hechar un password. Hasta que el cliente no se loguee el servidor no hace caso de lo que le diga el cliente. Asi nos evitamos que cualquiera pueda entrar, pero ahora cualquiera que ponga un packet sniffer entre tu dispositivo y tu ordenador [para poder cojer la contraseña que mandas] podra entrar.

Esto solo se podria solucionar con:
-> Criptografia de clave publica.... muy complicada de implementar
-> enviar la informacion encriptada con un AES con una clave fijada, que solo tu sepas. Pero tampoco tengo el tiempo para implementar AES para autoit.

Es possible que yo lo use, porque no creo que haya alguien tan paranoico como para ponerme un packet sniffer por ahí y acceder a mi ordenador... que ya ves, lo uso solo para programar y poco mas xD

O sino tienen otras alternativas, como usando este servidor con este cliente para android, pero bueno, esto ya va a gusto personal, ya no es AutoIt.

Almenos me pareció interesante desenvolupar esto. No lo he podido provar en un Android [todavia!, queda poco :P], pero lo he provado de PC a PC, uno conectado con mi internet, y otro conectado por wifi con una red de un vecino que la tiene abierta, y la veolcidad era mas que aceptable. Algo mas de una imagen por segundo.

Espero que les guste :D
olivarra1

Re: Control remoto

Publicado: 01 Jun 2011, 08:27
por Ximorro
¡Uau, menudo trabajo!

Cuando tenga tiempo miraré el código, seguro que aprendo algo interesante.

Lo de la compresión en memoria la verdad es puñetero, al menos con GDI+ no encontré manera de generar jpg o png en memoria, sólo hay funciones para guardarlo en disco (que yo sepa).
Puedes probar a comprimir a PNG de 8 bits (a los enconders se les puede poner parámetros ¿no?), la pérdida de color no se notará tanto pero típicamente estas imágenes se comprimirán mucho, quizás más que jpg a 24bits (otra cosa serían fotos a todo color, ahí lógicamente jpg es el rey).

Otra opción es usar compresores normales, con gzip.exe, lzma.dll o RTLCompression en ntdll.dll se puede comprimir en memoria directamente. No son compresores dedicados a imágenes, claro, pero se podría probar.
Yo hice experimentos con estas cosas, si te hacen falta los ficheros o ejemplos te puedo buscar a ver dónde tengo las pruebas.

Re: Control remoto

Publicado: 02 Jun 2011, 13:20
por BasicOs
Fantástico aporte, :smt008 :smt008

Algo supersencillo y funcional. Si le deseas incluir/crearte un minilenguaje de script-movil para programarlo si puedes usar algo para redireccionar los comandos, o incluso con botones que incluyan código: Un ejemplo de un motor es Autoexecute:

http://www.emesn.com/autoitforum/viewforum.php?f=8

Si puedes sustituir el flash por HTML5 puede que sea compatible con casi todos los móviles no solo con Flash. Ya que Html5 espera sustituir al Flash¿Ya se verá?

Salu22:)