/*
NativeProcessTest.as
Lee Felarca
http://www.zeropointnine.com/blog
v0.9 - 2009-04-18
Works in tandem with C# background process via standard I/O
Source code licensed under a Creative Commons Attribution 3.0 License.
http://creativecommons.org/licenses/by/3.0/
Some Rights Reserved. Improvements made to this class are encouraged.
*/
package
{
import flash.desktop.NativeApplication;
import flash.desktop.NativeProcess;
import flash.desktop.NativeProcessStartupInfo;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.NativeWindow;
import flash.display.NativeWindowDisplayState;
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.*;
import flash.filesystem.File;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.system.Capabilities;
import flash.text.*;
import flash.utils.ByteArray;
import flash.utils.Endian;
import flash.utils.setTimeout;
[SWF(frameRate="60", backgroundColor="#ffffff")]
public class NativeProcessTest extends Sprite
{
private static const PROCESS_ENDIANNESS:String = Endian.LITTLE_ENDIAN; // .NET process uses little-endian
private static const PROCESS_PATH:String = "nativeProcess/ScreenCapturer.exe";
private static const PROCESS_GETIMAGECOMMAND:String = "GET";
private static const PROCESS_STARTCODE:uint = 9999999;
private static const PROCESS_ENDCODE:uint = 6666666;
private var _process:NativeProcess;
private var _imageData:ByteArray;
private var _requestedWidth:int = 640;
private var _requestedHeight:int = 480;
private var _requestedOffsetX:int = 100;
private var _requestedOffsetY:int = 100;
private var _expectedImageLength:Number;
private var _imageWidth:int;
private var _imageHeight:int;
private var _isReceivingData:Boolean;
private var _startReceiveTimestamp:Number;
private var _startRequestTimestamp:Number;
private var _mouseDownMousePos:Point;
private var _mouseDownOffsetPos:Point;
private var _timestamps:Array = [];
private var _image:Bitmap;
private var _tf1:TextField;
private var _tf2:TextField;
public function NativeProcessTest()
{
this.stage.frameRate = 30;
this.stage.align = StageAlign.TOP_LEFT;
this.stage.scaleMode = StageScaleMode.NO_SCALE;
this.stage.nativeWindow.x = Capabilities.screenResolutionX - _requestedWidth - 100;
this.stage.nativeWindow.y = 50;
this.stage.nativeWindow.visible = true;
initUi();
var success:Boolean = initProcess();
if (! success) return;
setTimeout(requestImage, 333, _requestedWidth, _requestedHeight, _requestedOffsetX, _requestedOffsetY); // .. for good measure
}
private function initUi():void
{
var s:Sprite = new Sprite();
s.buttonMode = true;
this.addChild(s);
_image = new Bitmap();
s.addChild(_image);
_tf1 = new TextField();
_tf1.width = 640;
_tf1.selectable = false;
_tf1.defaultTextFormat = new TextFormat("_sans", 16, 0xffffff, true);
_tf1.htmlText = "SELECT IMAGE DIMENSIONS: 640x480 800x600 1024x768 1280x1024";
_tf1.addEventListener(TextEvent.LINK, onTextLink);
this.addChild(_tf1);
_tf2 = new TextField();
_tf2.autoSize = TextFieldAutoSize.LEFT;
_tf2.defaultTextFormat = new TextFormat("_sans", 16, 0xffffff, true);
this.addChild(_tf2);
_tf1.height = _tf2.height = 25;
_tf1.background = _tf2.background = true;
_tf1.backgroundColor = _tf2.backgroundColor = 0x00;
this.stage.nativeWindow.addEventListener(NativeWindowDisplayStateEvent.DISPLAY_STATE_CHANGE, onDisplayStateChange);
this.stage.addEventListener(Event.MOUSE_LEAVE, onLeave);
this.addEventListener(MouseEvent.MOUSE_DOWN, onDragStart);
}
public function initProcess():Boolean
{
if (Capabilities.os.toLowerCase().indexOf("win") == -1) {
_tf1.htmlText = "Sorry, Windows only.";
return false;
}
if (! NativeProcess.isSupported) {
_tf1.htmlText = "NativeProcess not supported.";
return false;
}
var np:Class = NativeProcess;
var file:File = File.applicationDirectory;
file = file.resolvePath(PROCESS_PATH);
var nativeProcessStartupInfo:NativeProcessStartupInfo = new NativeProcessStartupInfo();
try {
nativeProcessStartupInfo.executable = file;
}
catch (e:Error) {
_tf1.htmlText = "Couldn't launch executable:
" + e.message + "";
return false;
}
_process = new NativeProcess();
_process.addEventListener(ProgressEvent.STANDARD_OUTPUT_DATA, onStandardOutput);
_process.addEventListener(ProgressEvent.STANDARD_ERROR_DATA, onStandardError);
_process.addEventListener(ProgressEvent.STANDARD_INPUT_PROGRESS, onStandardInputProgress);
_process.standardOutput.endian = PROCESS_ENDIANNESS;
_process.start(nativeProcessStartupInfo);
NativeApplication.nativeApplication.addEventListener(Event.EXITING, onAppExiting);
_process.running
return true;
}
//
private function onDisplayStateChange($e:NativeWindowDisplayStateEvent):void
{
if ($e.afterDisplayState == NativeWindowDisplayState.NORMAL)
requestImage(_requestedWidth, _requestedHeight, _requestedOffsetX, _requestedOffsetY);
}
private function onLeave(e:*):void
{
_tf1.visible = false;
this.stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
}
private function onMouseMove(e:*):void
{
_tf1.visible = true;
this.stage.removeEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
}
private function onTextLink($e:TextEvent):void
{
var a:Array = $e.text.split("_");
_requestedWidth = parseInt(a[0]);
_requestedHeight = parseInt(a[1]);
}
private function onDragStart(e:*):void
{
_mouseDownMousePos = new Point(this.mouseX, this.mouseY);
_mouseDownOffsetPos = new Point(_requestedOffsetX, _requestedOffsetY);
this.addEventListener(Event.ENTER_FRAME, onDragging);
this.stage.addEventListener(MouseEvent.MOUSE_UP, onDragEnd);
this.stage.addEventListener(Event.MOUSE_LEAVE, onDragEnd);
}
private function onDragging(e:*):void
{
var dx:int = this.mouseX - _mouseDownMousePos.x;
var dy:int = this.mouseY - _mouseDownMousePos.y;
_requestedOffsetX = _mouseDownOffsetPos.x - dx;
_requestedOffsetY = _mouseDownOffsetPos.y - dy;
}
private function onDragEnd(e:*):void
{
this.removeEventListener(Event.ENTER_FRAME, onDragging);
this.stage.removeEventListener(MouseEvent.MOUSE_UP, onDragEnd);
this.stage.removeEventListener(Event.MOUSE_LEAVE, onDragEnd);
}
private function onStandardInputProgress(event:ProgressEvent):void
{
// Do nothing
// _process.closeInput();
}
private function onStandardOutput(event:ProgressEvent):void
{
processStandardOut();
}
public function onStandardError(event:ProgressEvent):void
{
trace("Error: " + _process.standardError.readUTFBytes(_process.standardError.bytesAvailable));
}
private function onAppExiting(e:*):void
{
if (_process && _process.running)
{
_process.removeEventListener(ProgressEvent.STANDARD_OUTPUT_DATA, onStandardOutput);
_process.removeEventListener(ProgressEvent.STANDARD_ERROR_DATA, onStandardError);
_process.removeEventListener(ProgressEvent.STANDARD_INPUT_PROGRESS, onStandardInputProgress);
_process.exit(true);
}
}
//
private function requestImage($width:int, $height:int, $offsetX:int, $offsetY:int):Boolean
{
if (! _process || ! _process.running) return false;
if (_isReceivingData) {
trace('Skipping - already trying to receive an image');
return false;
}
benchmarkCalc();
// bounds check
_requestedOffsetX = Math.min(_requestedOffsetX, Capabilities.screenResolutionX - _requestedWidth);
_requestedOffsetY = Math.min(_requestedOffsetY, Capabilities.screenResolutionY - _requestedHeight);
_requestedOffsetX = Math.max(_requestedOffsetX, 0);
_requestedOffsetY = Math.max(_requestedOffsetY, 0);
var s:String = PROCESS_GETIMAGECOMMAND + " " +
_requestedWidth.toString() + " " +
_requestedHeight.toString() + " " +
_requestedOffsetX.toString() + " " +
_requestedOffsetY.toString() + "\n";
_process.standardInput.writeUTFBytes(s);
_startRequestTimestamp = new Date().getTime();
_isReceivingData = true;
return true;
}
private function processStandardOut():void
{
// Check first 4 bytes for start code
var first4:int = _process.standardOutput.readUnsignedInt();
if (first4 == PROCESS_STARTCODE)
{
// error check
if (_process.standardOutput.bytesAvailable < 8) {
trace('Header appears to be split between packets. Ignoring rather than writing extra logic.');
return;
}
_startReceiveTimestamp = new Date().getTime();
// trace('Time waiting for response: ', _startReceiveTimestamp - _startRequestTimestamp);
_imageWidth = _process.standardOutput.readInt();
_imageHeight = _process.standardOutput.readInt();
_expectedImageLength = _imageWidth * _imageHeight * 4;
_imageData = new ByteArray();
_imageData.endian = Endian.LITTLE_ENDIAN;
if (_process.standardOutput.bytesAvailable == 0) return; // (expected behavior)
}
else // ... that first longword is part of the real image data, so write it to our ByteArray
{
_imageData.writeInt(first4);
}
// Write the remainder of the stdout data to the ByteArray
_process.standardOutput.readBytes(_imageData, _imageData.length);
// Check last 4 bytes for termination code
_imageData.position = _imageData.length - 4;
var last4:int = _imageData.readUnsignedInt();
if (last4 == PROCESS_ENDCODE)
{
_imageData.length = _imageData.length - 4; // .. remove endcode from bytearray
_isReceivingData = false;
if (_imageData.length != _expectedImageLength) trace("Image not of expected length: ", _imageData.length, "versus", _expectedImageLength);
// trace('Time taken to transfer data: ', new Date().getTime() - _startReceiveTimestamp);
drawImage(_imageData);
}
}
private function drawImage($b:ByteArray):void
{
// Make new bitmapdata if necessary
if (! _image.bitmapData || _image.bitmapData.width != _imageWidth || _image.bitmapData.height != _imageHeight) {
if (_image.bitmapData) _image.bitmapData.dispose();
_image.bitmapData = new BitmapData(_imageWidth,_imageHeight,false);
}
// Apply image
$b.position = 0;
_image.bitmapData.setPixels(new Rectangle(0,0,_imageWidth,_imageHeight), $b);
// *** This is where you might use or manipulate the BitmapData to have the app do something actually useful...
// If window is minimized, return
if (this.stage.nativeWindow.displayState == NativeWindowDisplayState.MINIMIZED) return;
// Resize window if necessary
var px:Number = this.stage.nativeWindow.width - this.stage.stageWidth;
var py:Number = this.stage.nativeWindow.height - this.stage.stageHeight;
if (this.stage.nativeWindow.width != _imageWidth + px) {
this.stage.nativeWindow.width = _imageWidth + px;
_tf1.width = _imageWidth;
_tf2.x = _imageWidth - 58;
}
if (this.stage.nativeWindow.height != _imageHeight + py) {
this.stage.nativeWindow.height = _imageHeight + py;
}
// Immediately request new image
requestImage(_requestedWidth, _requestedHeight, _requestedOffsetX, _requestedOffsetY);
}
private function benchmarkCalc():void
{
var now:Number = new Date().getTime();
_timestamps.push(now);
if (_timestamps.length > 10) {
var msPerFrame:Number = (now - _timestamps.shift()) / 10;
var framesPerSecond:Number = 1000 / msPerFrame;
_tf2.text = Math.round(framesPerSecond).toString() + " FPS";
}
}
}
}