diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..87f85c9
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,16 @@
+Copyright (c) 2014 Sylvain Peyrefitte
+
+This file is part of rdpy.
+
+rdpy is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
diff --git a/README.md b/README.md
index 7e2ec5c..cf7dcd0 100644
--- a/README.md
+++ b/README.md
@@ -80,7 +80,7 @@ RDPY can also be used as Qt widget throw rdpy.ui.qt4.QRemoteDesktop class. It ca
In a nutshell the RDPY can be used as a protocol library with a twisted engine.
-The client code looks like this:
+The RDP client code looks like this:
```
from rdpy.protocol.rdp import rdp
@@ -106,7 +106,7 @@ class MyRDPFactory(rdp.ClientFactory):
#mouse move and click at pixel 200x200
self._controller.sendPointerEvent(200, 200, 1, true)
- def onClode(self):
+ def onClose(self):
pass
return MyObserver(controller)
diff --git a/bin/rdpy-vncclient b/bin/rdpy-vncclient
index b20aef0..b726fa9 100755
--- a/bin/rdpy-vncclient
+++ b/bin/rdpy-vncclient
@@ -42,16 +42,12 @@ class RFBClientQtFactory(rfb.ClientFactory):
@param controller: build by factory
"""
#create client observer
- client = RFBClientQt(controller)
+ client = RFBClientQt(controller, 1024, 800)
#create qt widget
self._w = client.getWidget()
- self._w.resize(1024, 800)
self._w.setWindowTitle('rdpy-vncclient')
self._w.show()
return client
-
- def startedConnecting(self, connector):
- pass
def clientConnectionLost(self, connector, reason):
"""
diff --git a/bin/rdpy-vncscreenshot b/bin/rdpy-vncscreenshot
new file mode 100755
index 0000000..8c54ded
--- /dev/null
+++ b/bin/rdpy-vncscreenshot
@@ -0,0 +1,167 @@
+#!/usr/bin/python
+#
+# Copyright (c) 2014 Sylvain Peyrefitte
+#
+# This file is part of rdpy.
+#
+# rdpy is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+
+"""
+example of use rdpy
+take screenshot of login page
+"""
+
+import sys, os, getopt
+
+# Change path so we find rdpy
+sys.path.insert(1, os.path.join(sys.path[0], '..'))
+
+from PyQt4 import QtCore, QtGui
+from rdpy.protocol.rfb import rfb
+import rdpy.base.log as log
+from twisted.internet import task
+
+#set log level
+log._LOG_LEVEL = log.Level.INFO
+
+class RFBScreenShotFactory(rfb.ClientFactory):
+ """
+ @summary: Factory for screenshot exemple
+ """
+ def __init__(self, path):
+ """
+ @param path: path of output screenshot
+ """
+ self._path = path
+
+ def clientConnectionLost(self, connector, reason):
+ """
+ @summary: Connection lost event
+ @param connector: twisted connector use for rdp connection (use reconnect to restart connection)
+ @param reason: str use to advertise reason of lost connection
+ """
+ log.info("connection lost : %s"%reason)
+ reactor.stop()
+ app.exit()
+
+ def clientConnectionFailed(self, connector, reason):
+ """
+ @summary: Connection failed event
+ @param connector: twisted connector use for rdp connection (use reconnect to restart connection)
+ @param reason: str use to advertise reason of lost connection
+ """
+ log.info("connection failed : %s"%reason)
+ reactor.stop()
+ app.exit()
+
+
+ def buildObserver(self, controller, addr):
+ """
+ @summary: build ScreenShot observer
+ @param controller: RFBClientController
+ @param addr: address of target
+ """
+ class ScreenShotObserver(rfb.RFBClientObserver):
+ """
+ @summary: observer that connect, cache every image received and save at deconnection
+ """
+ def __init__(self, controller, path):
+ """
+ @param controller: RFBClientController
+ @param path: path of output screenshot
+ """
+ rdp.RDPClientObserver.__init__(self, controller)
+ self._buffer = QtGui.QImage(width, height, QtGui.QImage.Format_RGB32)
+ self._path = path
+
+ def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
+ """
+ @summary: callback use when bitmap is received
+ """
+ self._hasUpdated = True
+ image = RDPBitmapToQtImage(destLeft, width, height, bitsPerPixel, isCompress, data);
+ with QtGui.QPainter(self._buffer) as qp:
+ #draw image
+ qp.drawImage(destLeft, destTop, image, 0, 0, destRight - destLeft + 1, destBottom - destTop + 1)
+
+ def onReady(self):
+ """
+ @summary: callback use when RDP stack is connected (just before received bitmap)
+ """
+ log.info("connected %s"%addr)
+
+ def onClose(self):
+ """
+ @summary: callback use when RDP stack is closed
+ """
+ log.info("save screenshot into %s"%self._path)
+ self._buffer.save(self._path)
+
+ def checkUpdate(self):
+ if not self._hasUpdated:
+ log.info("close connection on timeout without updating orders")
+ self._controller.close();
+ return
+ self._hasUpdated = False
+
+ return ScreenShotObserver(controller, self._width, self._height, self._path, self._timeout)
+
+def help():
+ print "Usage: rdpy-rdpscreenshot [options] ip[:port]"
+ print "\t-w: width of screen default value is 1024"
+ print "\t-l: height of screen default value is 800"
+ print "\t-o: file path of screenshot default(/tmp/rdpy-rdpscreenshot.jpg)"
+ print "\t-t: timeout of connection without any updating order (default is 2s)"
+
+if __name__ == '__main__':
+ #default script argument
+ width = 1024
+ height = 800
+ path = "/tmp/rdpy-rdpscreenshot.jpg"
+ timeout = 2.0
+
+ try:
+ opts, args = getopt.getopt(sys.argv[1:], "hw:l:o:t:")
+ except getopt.GetoptError:
+ help()
+ for opt, arg in opts:
+ if opt == "-h":
+ help()
+ sys.exit()
+ elif opt == "-w":
+ width = int(arg)
+ elif opt == "-l":
+ height = int(arg)
+ elif opt == "-o":
+ path = arg
+ elif opt == "-t":
+ timeout = float(arg)
+
+ if ':' in args[0]:
+ ip, port = args[0].split(':')
+ else:
+ ip, port = args[0], "3389"
+
+ #create application
+ app = QtGui.QApplication(sys.argv)
+
+ #add qt4 reactor
+ import qt4reactor
+ qt4reactor.install()
+
+ from twisted.internet import reactor
+ reactor.connectTCP(ip, int(port), RDPScreenShotFactory(width, height, path, timeout))
+ reactor.runReturn()
+ app.exec_()
\ No newline at end of file
diff --git a/rdpy/protocol/rfb/rfb.py b/rdpy/protocol/rfb/rfb.py
index d809a2b..bd8e1d4 100644
--- a/rdpy/protocol/rfb/rfb.py
+++ b/rdpy/protocol/rfb/rfb.py
@@ -26,10 +26,10 @@ Implement Remote FrameBuffer protocol use in VNC client and server
@todo: more encoding rectangle
"""
-from twisted.internet import protocol
-from rdpy.network.layer import RawLayer
+from rdpy.network.layer import RawLayer, RawLayerClientFactory
from rdpy.network.type import UInt8, UInt16Be, UInt32Be, SInt32Be, String, CompositeType
from rdpy.base.error import InvalidValue, CallPureVirtualFuntion
+import rdpy.base.log as log
class ProtocolVersion(object):
"""
@@ -223,7 +223,7 @@ class RFB(RawLayer):
elif data.len == 4:
bodyLen = UInt32Be()
else:
- print "invalid header length"
+ log.error("invalid header length")
return
data.readType(bodyLen)
self.expect(bodyLen.value, self._callbackBody)
@@ -253,7 +253,7 @@ class RFB(RawLayer):
"""
self.readProtocolVersion(data)
if self._version.value == ProtocolVersion.UNKNOWN:
- print "Unknown protocol version %s send 003.008"%data.getvalue()
+ log.info("Unknown protocol version %s send 003.008"%data.getvalue())
#protocol version is unknown try best version we can handle
self._version.value = ProtocolVersion.RFB003008
#send same version of
@@ -302,11 +302,11 @@ class RFB(RawLayer):
result = UInt32Be()
data.readType(result)
if result == UInt32Be(1):
- print "Authentification failed"
+ log.info("Authentification failed")
if self._version.value == ProtocolVersion.RFB003008:
self.expectWithHeader(4, self.recvSecurityFailed)
else:
- print "Authentification OK"
+ log.debug("Authentification OK")
self.sendClientInit()
def recvSecurityFailed(self, data):
@@ -314,7 +314,7 @@ class RFB(RawLayer):
Send by server to inform reason of why it's refused client
@param data: Stream that contains well formed packet
"""
- print "Security failed cause to %s"%data.getvalue()
+ log.info("Security failed cause to %s"%data.getvalue())
def recvServerInit(self, data):
"""
@@ -330,7 +330,7 @@ class RFB(RawLayer):
@param data: Stream that contains well formed packet
"""
data.readType(self._serverName)
- print "Server name %s"%str(self._serverName)
+ log.info("Server name %s"%str(self._serverName))
#end of handshake
#send pixel format
self.sendPixelFormat(self._pixelFormat)
@@ -454,19 +454,16 @@ class RFBClientListener(object):
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "recvRectangle", "RFBListener"))
-class RFBController(RFBClientListener):
+class RFBClientController(RFBClientListener):
"""
- Class use to manage RFB order and dispatch throw observers
+ @summary: Class use to manage RFB order and dispatch throw observers for client side
"""
- def __init__(self, mode):
- """
- @param mode: mode of inner RFB layer
- """
+ def __init__(self):
self._clientObservers = []
#rfb layer to send client orders
self._rfbLayer = RFB(self)
- def getRFBLayer(self):
+ def getProtocol(self):
"""
@return: RFB layer build by controller
"""
@@ -498,7 +495,7 @@ class RFBController(RFBClientListener):
@param key: ASCII code of key
"""
if not self._rfbLayer._ready:
- print "Try to send key event on non ready layer"
+ log.info("Try to send key event on non ready layer")
return
try:
event = KeyEvent()
@@ -507,7 +504,7 @@ class RFBController(RFBClientListener):
self._rfbLayer.sendKeyEvent(event)
except InvalidValue:
- print "Try to send an invalid key event"
+ log.debug("Try to send an invalid key event")
def sendPointerEvent(self, mask, x, y):
"""
@@ -517,7 +514,7 @@ class RFBController(RFBClientListener):
@param y: y pointer of mouse pointer
"""
if not self._rfbLayer._ready:
- print "Try to send pointer event on non ready layer"
+ log.info("Try to send pointer event on non ready layer")
return
try:
event = PointerEvent()
@@ -527,10 +524,16 @@ class RFBController(RFBClientListener):
self._rfbLayer.sendPointerEvent(event)
except InvalidValue:
- print "Try to send an invalid pointer event"
+ log.debug("Try to send an invalid pointer event")
+
+ def close(self):
+ """
+ @summary: close rfb stack
+ """
+ self._rfbLayer.close()
-class ClientFactory(protocol.Factory):
+class ClientFactory(RawLayerClientFactory):
"""
Twisted Factory of RFB protocol
"""
@@ -539,9 +542,9 @@ class ClientFactory(protocol.Factory):
Function call by twisted on connection
@param addr: address where client try to connect
"""
- controller = RFBController()
+ controller = RFBClientController()
self.buildObserver(controller)
- return controller.getRFBLayer()
+ return controller.getProtocol()
def buildObserver(self, controller):
"""
@@ -575,7 +578,7 @@ class RFBClientObserver(object):
def mouseEvent(self, button, x, y):
"""
Send a mouse event to RFB Layer
- @param button: button number which is pressed (0,1,2,3,4,5,6,7,8)
+ @param button: button number which is pressed (0,1,2,3,4,5,6,7)
@param x: x coordinate of mouse pointer
@param y: y coordinate of mouse pointer
"""
diff --git a/rdpy/ui/qt4.py b/rdpy/ui/qt4.py
index ccb4659..83b7d1a 100644
--- a/rdpy/ui/qt4.py
+++ b/rdpy/ui/qt4.py
@@ -70,6 +70,15 @@ class QAdaptor(object):
"""
raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "closeEvent", "QAdaptor"))
+def qtImageFormatFromRFBPixelFormat(pixelFormat):
+ """
+ @summary: convert RFB pixel format to QtGui.QImage format
+ """
+ if pixelFormat.BitsPerPixel.value == 32:
+ return QtGui.QImage.Format_RGB32
+ elif pixelFormat.BitsPerPixel.value == 16:
+ return QtGui.QImage.Format_RGB16
+
class RFBClientQt(RFBClientObserver, QAdaptor):
"""
QAdaptor for specific RFB protocol stack
@@ -101,15 +110,13 @@ class RFBClientQt(RFBClientObserver, QAdaptor):
@param encoding: encoding type rfb.message.Encoding
@param data: image data in accordance with pixel format and encoding
"""
- imageFormat = None
- if pixelFormat.BitsPerPixel.value == 32 and pixelFormat.RedShift.value == 16:
- imageFormat = QtGui.QImage.Format_RGB32
- else:
+ imageFormat = qtImageFormatFromRFBPixelFormat(pixelFormat)
+ if imageFormat is None:
log.error("Receive image in bad format")
return
image = QtGui.QImage(data, width, height, imageFormat)
- self._widget.notifyImage(x, y, image)
+ self._widget.notifyImage(x, y, image, width, height)
def sendMouseEvent(self, e, isPressed):
"""
@@ -134,7 +141,13 @@ class RFBClientQt(RFBClientObserver, QAdaptor):
@param isPressed: event come from press or release action
"""
self.keyEvent(isPressed, e.nativeVirtualKey())
-
+
+ def closeEvent(self, e):
+ """
+ Call when you want to close connection
+ @param: QCloseEvent
+ """
+ self._controller.close()
def RDPBitmapToQtImage(destLeft, width, height, bitsPerPixel, isCompress, data):
"""