Eine Art VNC Server, realisiert mit AWT's Robot?

Hallo,

folgendes Problem:

Ich hab eine Linux-Kiste im Headless-Mode (kein Monitor, keine Maus und Tastatur). Darauf läuft in X11 eine Anwendung, welche ich gerne über’s Netzwerk benutzen möchte. Prinzipiell funktioniert das mit VNC prima.

Nur würde ich gerne die VNC Funktionalität erweitern, indem ich Drucker-Support etc. einbaue. D.h. ich drucke in der Anwendung auf der Linux-Kiste was aus (in eine File oder PDF), die eigene VNC artige Implementierung schnappt sich die File und schickt sie zum Client, der dann damit den lokalen Standarddrucker befüttert. File upload und download Features wären auch nicht schlecht. Das ganze lässt sich natürlich noch weiter spinnen …

Das ganze würde ich gerne, sowohl auf Client als auch auf Serverseite mit Java implementieren.

Nun hab ich ein bisschen mit AWT’s Robot Klasse rumgespielt. Den Screen capturen mit einer Auflösung von 1024x768 dauert hier im Schnitt 23Millisekunden. Das aktuelle Bild dann mit dem letzten vergleichen und die “dirty rectangles” zum Client übertragen, der sie dann bei sich im Client updatet. Maus und Tastatureingaben sollten ja mit dem Robot ebenfalls kein Problem sein.

Damit die CPU-Last nicht ins unermessliche steigt, müsste man die refresh-rate begrenzen. Hab dazu mal n kleines Testprogrämmchen gebastelt:

public class RobotTest {
	
	public static void main(String[] args) throws AWTException, IOException, InterruptedException {
		// determine current screen size
		Toolkit toolkit = Toolkit.getDefaultToolkit();
		Dimension screenSize = toolkit.getScreenSize();
		Rectangle screenRect = new Rectangle(screenSize);
		long fps = 15; // refresh rate set to 15fps
				
		Robot r = new Robot();
		
		BufferedImage screenScrape = null;
		
		// measure scrape time
		long start = System.currentTimeMillis();
		for (int i=0;i<100;i++) {
			screenScrape = r.createScreenCapture(new Rectangle(0,0,1024,768)); // hardcoded to 1024x768 only
		}
		long stop = System.currentTimeMillis();
		
		// results
		long timePerScrape = (stop-start)/100;
		System.out.println("time per scrape="+timePerScrape);

		// test cpu load
		long fpsSleepTime = (1000 / fps)-timePerScrape;
		System.out.println("fpsSleepTime="+fpsSleepTime);
		for (int i=0;i<1000;i++) {
			screenScrape = r.createScreenCapture(new Rectangle(0,0,1024,768));
			Thread.sleep(fpsSleepTime);
		}
		
//		ImageIO.write(screenScrape, "png", new File("c:/screen.png"));

	}

}```

Auf meiner Maschine (Dualcore, 2.13Ghz), krieg ich mit 15fps eine CPU-Last von ~9% bei 15fps (im "test cpu load block mal in den Taskmanager schauen). Weiß allerdings noch nicht wie rechenaufwendig das berechnen der dirty rectangles ist. 

Soweit die Theorie.  Sehr ihr da irgendwelche "Schwierigkeiten" oder Probleme die sich noch ergeben könnten? Was haltet ihr von der Idee? Bzgl. meinen gewünschten Features (vor allem die Sache mit dem drucken), hab ich bisher nur kommerzielle Software gefunden, die dann meist auch nicht für Linux geeignet war.

- Alex

Hmm, hab gerade noch ein wenig gegoogelt. Scheint so als ob andere auch schon auf die Idee gekommen sind: http://sourceforge.net/projects/vncjlgpl

[update]
Ne, das ist es doch nicht. Da wird nur eine Java anwendung gescraped und nicht der native Desktop… weitersuch

Hab noch mehr zu dem Thema gefunden, auch was die performance des Robots betrifft. Die Klasse ist etwas unschön implementiert. Bei jedem Screenshot wird ein komplett neues Pixelarray angelegt, was natülrich etwas dauert jedesmal neuen Speicher zu allokieren.

Was auch ungemein helfen würde, wenn es eine tief liegende Methode gäbe, an die Änderungen ranzukommen, die seit dem letzten Screenshot aufgetreten sind, so dass man sich das mit dem selbst berechnen der dirty rectangles sparen kann. Aber ich bezeifle dass es hier schon was fertiges, plattformunabhängiges gibt…

  • Alex

Die Idee ist gut die du hast, nur sehe ich mit dem Drucken ein Problem.
Du müsstest einen “Drucker” bauen der in dein Programm integriert ist, wäre es nicht einfacher nen PDF Drucker zu nehmen und die PDF zu verschicken? Weil eine Datei zu übertragen sollte nicht so schwer sein, du musst deinem Programm ja nur sagen schick den Kram.

Ich schlage vor du liest meinen ersten Post nochmal gründlich durch :slight_smile:

D.h. ich drucke in der Anwendung auf der Linux-Kiste was aus (in eine File oder PDF), die eigene VNC artige Implementierung schnappt sich die File und schickt sie zum Client, der dann damit den lokalen Standarddrucker befüttert.

Mal was (fast) anderes:

Das Manko, dass der Robot so langsam ist, liegt ja unter anderem, wie ich schon geschrieben hatte, an der Sache mit dem Pixelarray (Developer Community - Oracle Forums). Wäre ja nicht weiter Wild die Sache selbst in die Hand zu nehmen, und basierend auf SUNs Robot-Klasse, eine eigene zu basteln, die dann etwas performanter ist, weil man ein bereits existierendes Pixel-Array übergeben kann.

Hab mal etwas in den Source geschaut:

 * @(#)WRobotPeer.java	1.15 03/12/19

package sun.awt.windows;

import java.awt.*;
import java.awt.peer.RobotPeer;

class WRobotPeer extends WObjectPeer implements RobotPeer
{
    private Point offset = new Point(0, 0);

    WRobotPeer() {
	create();
    }
    WRobotPeer(GraphicsDevice screen) {
        if ( screen != null ) {
            GraphicsConfiguration conf = screen.getDefaultConfiguration();
            if ( conf != null ) {
                offset = conf.getBounds().getLocation();
            }
        }
        create();
    }

    private synchronized native void _dispose();

    protected void disposeImpl() {
	_dispose();
    }
      
    public native void create();
    public native void mouseMoveImpl(int x, int y);
    public void mouseMove(int x, int y) {
        x += offset.x;
        y += offset.y;
        mouseMoveImpl(x, y);
    }
    public native void mousePress(int buttons);
    public native void mouseRelease(int buttons);
    public native void mouseWheel(int wheelAmt);

    public native void keyPress( int keycode );
    public native void keyRelease( int keycode );

    public int getRGBPixel(int x, int y) {
        x += offset.x;
        y += offset.y;
        return getRGBPixelImpl(x, y);
    }
    public native int getRGBPixelImpl(int x, int y);
    
    public int [] getRGBPixels(Rectangle bounds) {
        bounds.translate(offset.x, offset.y);
	int pixelArray[] = new int[bounds.width*bounds.height];
	getRGBPixels(bounds.x, bounds.y, bounds.width, bounds.height, pixelArray);
	return pixelArray;
    }

    private native void getRGBPixels(int x, int y, int width, int height, int pixelArray[]);
}

Da gibts ja jede Menge native Methoden (eine gleiche/ähnliche Klasse existiert nochmal für X11 Umgebungen). Aber ich find nirgends (auch in der Superklasse) die Stelle, wo die passende DLL dazu geladen wird.
Kennt sich da jemand aus? Wie komm ich an die nativen Methoden dran wenn ich sie in meiner eigenen Klasse direkt benutzen will?!

Gruß
Alex

[update]
yeeehaaa… Hab nen Weg gefunden… Vermutlich muss ich nichtmal die klasse neu implementieren sondern kann einfach die (private) Methode überschreiben :wink: Aber das muss ich erst testen

Ups :smiley:
naja beim Frühstück da kann man schonmal was übersehen :smiley:

So, hab mal etwas gespielt… 1680x1050 mit 10fps sieht noch sehr flüssig aus und hat auf einigermaßen aktuellen CPUs eine vertretbare performance (auf meinem Dualcore 2,13Ghz etwa 30% Last beim nix tun).

Wenn ich den Robot noch optimiert bekomme, dann geht das sicherlich nochmal n bisschen runter da das dumme Pixelarray nicht jedesmal neu erzeugt werden muss.

Die Sache mit dem „dirty rectangle“ algo hab ich jetzt auch schon im Kopf. Mal sehen wann ich dazu komme das umzusetzen.

Ziel ist es übrigens das ganze auf einem MiniITX Rechner mit 1GB RAM und 1Ghz CPU ohne Monitor laufen zu lassen … Geplante Auflösung: 1024x768

Wenn das „flüssig“ und mit akzeptabler CPU-Last übers Internet läuft hab ich mein Ziel erreicht. Als „Protokoll“ werd ich vermutlich SIMON einsetzen (was auch sonst :wink: ).

  • Alex

wenn das läuft hab ich gleich die nächste Sache für dich :wink:
Teste obs auch mit 2 Monitoren läuft, daran scheitern 90% aller VNC Programme die ich kenn, nur realvnc kann das bisher (meine Erfahrung)

Wo hast du denn den quatsch her? Sowohl RealVNC sowie TightVNC hat damit keine Probleme. Hab im Büro sogar ein 4 Monitorsystem laufen mit 4x 1920x1200… Geht astrein.

Und noch krasser: Wenn ich die Monitore abklemme kann ich auf der Grafikkarte eine maximale Auflösung von 8k * 4k einstellen und das mit RealVNC scrapen. Wohl bemerkt: Das ist eine 32 Megapixel Auflösung …

Einzigster Trick bei Multimonitorsystemen: Ohne Spanning-Modus kanns probleme geben (übrigens auch mit anderen Anwendungen).

  • Alex

ich hab doch gesagt Realvnc kann das :wink:
Tightvnc läuft hier glaube aufm Mac und das kann das nicht, dort seh ich nur den Hauptmonitor. Vor einiger Zeit hatte ich auch mal UltraVNC das konnte es auch nicht.
Nachtrag: Teamviewer kann es zb auch nicht :wink:

Scrapst du einen Mac Host oder läuft da nur der Viewer?

Ob mit meiner VNC Variante dann Multimonitoring klappt, kannst du leicht selbst rausfinden: Einfach mal mit der Robot-Klasse einen Screenshot machen und den speichern.

  • Alex

ne der Mac ist der Server

So, erste “dirty rectangle detection” routine ist fertig. Hab nur noch eine gewisse “unschärfe” drin während sich etwas bewegt. Muss das noch verfeinern.

  • Alex

[update]

das unscharfe ist jetzt weg. dafür ist getRGB() auf dem BufferedImage noch eine gehörige Bremse.

Habs mittlerweile soweit, dass ich über’s Netzwerk “live” den anderen Desktop sehe. Allerdings ist die Update-Rate nicht gerade berauschend. Momentan wäre es “ausreichend” für ein Online-Meeting-Tool mit dem man den Desktop sharen will und es nicht auf eine hohe Updaterate ankommt (Powerpoint-Präsentation würde exzellent gehen). Zum einen ist die “dirty rectangle” erkennung zu langsam, zum anderen ist das capturen der Screenshots noch langsam (weil der Robot intern jedesmal ein neues int-Array erstellt welches ja erst allokiert werden muss).

Aber letzteres ist nicht das Nadelöhr #1. Das erkennen der Dirty Rectangles ist einfach noch zu langsam. Dafür muss es aber irgendwie algorithmen geben. weiter suchen geh

  • Alex