diff --git a/README.md b/README.md index 16f42b9..9a55cbc 100644 --- a/README.md +++ b/README.md @@ -84,29 +84,35 @@ The client code looks like this: ``` from rdpy.protocol.rdp import rdp + class MyRDPFactory(rdp.ClientFactory): - def buildObserver(self, controller): + + def clientConnectionLost(self, connector, reason): + reactor.stop() + app.exit() + + def clientConnectionFailed(self, connector, reason): + reactor.stop() + app.exit() + + def buildObserver(self, controller, addr): class MyObserver(rdp.RDPClientObserver) - def __init__(self, controller) - rdp.RDPClientObserver.__init__(self, controller) - def onBitmapUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data): + + def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data): #here code handle bitmap pass + def onReady(self): #send 'r' key self._controller.sendKeyEventUnicode(ord(unicode("r".toUtf8(), encoding="UTF-8")), True) #mouse move and click at pixel 200x200 self._controller.sendPointerEvent(200, 200, 1, true) + + def onClode(self): + pass return MyObserver(controller) - def startedConnecting(self, connector): - pass - def clientConnectionLost(self, connector, reason): - pass - def clientConnectionFailed(self, connector, reason): - pass - from twisted.internet import reactor reactor.connectTCP("XXX.XXX.XXX.XXX", 3389), MyRDPFactory()) reactor.run() diff --git a/bin/rdpy-rdpscreenshot b/bin/rdpy-rdpscreenshot new file mode 100755 index 0000000..87d62cf --- /dev/null +++ b/bin/rdpy-rdpscreenshot @@ -0,0 +1,162 @@ +#!/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.rdp import rdp +from rdpy.ui.qt4 import RDPBitmapToQtImage +import rdpy.base.log as log + +#set log level +log._LOG_LEVEL = log.Level.INFO + +class RDPScreenShotFactory(rdp.ClientFactory): + """ + @summary: Factory for screenshot exemple + """ + def __init__(self, width, height, path): + """ + @param width: width of screen + @param height: height of screen + @param path: path of output screenshot + """ + self._width = width + self._height = height + 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 failes : %s"%reason) + reactor.stop() + app.exit() + + + def buildObserver(self, controller, addr): + """ + @summary: build ScreenShot observer + @param controller: RDPClientController + @param addr: address of target + """ + class ScreenShotObserver(rdp.RDPClientObserver): + """ + @summary: observer that connect, cache every image received and save at deconnection + """ + def __init__(self, controller, width, height, path): + """ + @param controller: RDPClientController + @param width: width of screen + @param height: height of screen + @param path: path of output screenshot + """ + rdp.RDPClientObserver.__init__(self, controller) + controller.setScreen(width, height); + 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 + """ + 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) + + return ScreenShotObserver(controller, self._width, self._height, self._path) + +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" + +if __name__ == '__main__': + #default script argument + width = 1024 + height = 800 + path = "/tmp/rdpy-rdpscreenshot.jpg" + + try: + opts, args = getopt.getopt(sys.argv[1:], "hw:l:o:") + 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 + + 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)) + reactor.runReturn() + app.exec_() \ No newline at end of file diff --git a/rdpy/ui/qt4.py b/rdpy/ui/qt4.py index 2430f72..660498f 100644 --- a/rdpy/ui/qt4.py +++ b/rdpy/ui/qt4.py @@ -35,7 +35,6 @@ try: except: log.error("Please build core package before using RLE algorithm : scons -C rdpy/core install") - class QAdaptor(object): """ Adaptor model with link between protocol @@ -135,7 +134,49 @@ class RFBClientQt(RFBClientObserver, QAdaptor): """ self.keyEvent(isPressed, e.nativeVirtualKey()) - + +def RDPBitmapToQtImage(destLeft, width, height, bitsPerPixel, isCompress, data): + """ + Bitmap transformation to Qt object + @param width: width of bitmap + @param height: height of bitmap + @param bitsPerPixel: number of bit per pixel + @param isCompress: use RLE compression + @param data: bitmap data + """ + image = None + if bitsPerPixel == 15: + if isCompress: + image = QtGui.QImage(width, height, QtGui.QImage.Format_RGB555) + rle.bitmap_decompress(image.bits(), width, height, data, len(data), 2) + else: + image = QtGui.QImage(data, width, height, QtGui.QImage.Format_RGB555).transformed(QtGui.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0)) + + elif bitsPerPixel == 16: + if isCompress: + image = QtGui.QImage(width, height, QtGui.QImage.Format_RGB16) + rle.bitmap_decompress(image.bits(), width, height, data, len(data), 2) + else: + image = QtGui.QImage(data, width, height, QtGui.QImage.Format_RGB16).transformed(QtGui.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0)) + + elif bitsPerPixel == 24: + if isCompress: + image = QtGui.QImage(width, height, QtGui.QImage.Format_RGB24) + rle.bitmap_decompress(image.bits(), width, height, data, len(data), 3) + else: + image = QtGui.QImage(data, width, height, QtGui.QImage.Format_RGB24).transformed(QtGui.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0)) + + elif bitsPerPixel == 32: + if isCompress: + image = QtGui.QImage(width, height, QtGui.QImage.Format_RGB32) + rle.bitmap_decompress(image.bits(), width, height, data, len(data), 4) + else: + image = QtGui.QImage(data, width, height, QtGui.QImage.Format_RGB32).transformed(QtGui.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0)) + else: + log.error("Receive image in bad format") + image = QtGui.QImage(width, height, QtGui.QImage.Format_RGB32) + return image + class RDPClientQt(RDPClientObserver, QAdaptor): """ Adaptor for RDP client @@ -201,38 +242,7 @@ class RDPClientQt(RDPClientObserver, QAdaptor): @param isCompress: use RLE compression @param data: bitmap data """ - image = None - if bitsPerPixel == 15: - if isCompress: - image = QtGui.QImage(width, height, QtGui.QImage.Format_RGB555) - rle.bitmap_decompress(image.bits(), width, height, data, len(data), 2) - else: - image = QtGui.QImage(data, width, height, QtGui.QImage.Format_RGB555).transformed(QtGui.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0)) - - elif bitsPerPixel == 16: - if isCompress: - image = QtGui.QImage(width, height, QtGui.QImage.Format_RGB16) - rle.bitmap_decompress(image.bits(), width, height, data, len(data), 2) - else: - image = QtGui.QImage(data, width, height, QtGui.QImage.Format_RGB16).transformed(QtGui.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0)) - - elif bitsPerPixel == 24: - if isCompress: - image = QtGui.QImage(width, height, QtGui.QImage.Format_RGB24) - rle.bitmap_decompress(image.bits(), width, height, data, len(data), 3) - else: - image = QtGui.QImage(data, width, height, QtGui.QImage.Format_RGB24).transformed(QtGui.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0)) - - elif bitsPerPixel == 32: - if isCompress: - image = QtGui.QImage(width, height, QtGui.QImage.Format_RGB32) - rle.bitmap_decompress(image.bits(), width, height, data, len(data), 4) - else: - image = QtGui.QImage(data, width, height, QtGui.QImage.Format_RGB32).transformed(QtGui.QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0)) - else: - log.error("Receive image in bad format") - return - + image = RDPBitmapToQtImage(destLeft, width, height, bitsPerPixel, isCompress, data); #if image need to be cut #For bit alignement server may send more than image pixel self._widget.notifyImage(destLeft, destTop, image, destRight - destLeft + 1, destBottom - destTop + 1)