2020-11-19 00:32:46 +08:00
import ' package:ffi/ffi.dart ' ;
import ' package:path_provider/path_provider.dart ' ;
import ' dart:io ' ;
import ' dart:ffi ' ;
import ' dart:convert ' ;
import ' dart:typed_data ' ;
import ' dart:ui ' as ui ;
import ' package:flutter/material.dart ' ;
import ' dart:async ' ;
import ' common.dart ' ;
class RgbaFrame extends Struct {
@ Uint32 ( )
int len ;
Pointer < Uint8 > data ;
}
typedef F1 = void Function ( Pointer < Utf8 > ) ;
typedef F2 = Pointer < Utf8 > Function ( Pointer < Utf8 > , Pointer < Utf8 > ) ;
typedef F3 = void Function ( Pointer < Utf8 > , Pointer < Utf8 > ) ;
typedef F4 = void Function ( Pointer < RgbaFrame > ) ;
typedef F5 = Pointer < RgbaFrame > Function ( ) ;
// https://juejin.im/post/6844903864852807694
class FfiModel with ChangeNotifier {
PeerInfo _pi = PeerInfo ( ) ;
Display _display = Display ( ) ;
bool _decoding = false ;
2020-11-19 00:53:10 +08:00
bool _waitForImage = false ;
2020-11-19 00:32:46 +08:00
FfiModel ( ) {
init ( ) ;
}
Future < Null > init ( ) async {
await FFI . init ( ) ;
notifyListeners ( ) ;
}
void clear ( ) {
_decoding = false ;
}
2020-11-19 21:59:49 +08:00
void update (
String id ,
BuildContext context ,
void Function ( Map < String , dynamic > evt , String id , BuildContext context )
handleMsgbox ) {
2020-11-19 18:22:06 +08:00
for ( ; ; ) {
var evt = FFI . popEvent ( ) ;
if ( evt = = null ) break ;
2020-11-19 00:32:46 +08:00
var name = evt [ ' name ' ] ;
if ( name = = ' msgbox ' ) {
handleMsgbox ( evt , id , context ) ;
} else if ( name = = ' peer_info ' ) {
handlePeerInfo ( evt ) ;
} else if ( name = = ' switch_display ' ) {
handleSwitchDisplay ( evt ) ;
} else if ( name = = ' cursor_data ' ) {
FFI . cursorModel . updateCursorData ( evt ) ;
} else if ( name = = ' cursor_id ' ) {
FFI . cursorModel . updateCursorId ( evt ) ;
} else if ( name = = ' cursor_position ' ) {
FFI . cursorModel . updateCursorPosition ( evt ) ;
}
}
if ( ! _decoding ) {
var rgba = FFI . getRgba ( ) ;
if ( rgba ! = null ) {
2020-11-19 00:53:10 +08:00
if ( _waitForImage ) {
_waitForImage = false ;
dismissLoading ( ) ;
}
2020-11-19 00:32:46 +08:00
_decoding = true ;
ui . decodeImageFromPixels (
rgba , _display . width , _display . height , ui . PixelFormat . bgra8888 ,
( image ) {
FFI . clearRgbaFrame ( ) ;
_decoding = false ;
try {
// my throw exception, because the listener maybe already dispose
FFI . imageModel . update ( image ) ;
} catch ( e ) { }
} ) ;
}
}
}
void handleSwitchDisplay ( Map < String , dynamic > evt ) {
_pi . currentDisplay = int . parse ( evt [ ' display ' ] ) ;
_display . x = double . parse ( evt [ ' x ' ] ) ;
_display . y = double . parse ( evt [ ' y ' ] ) ;
_display . width = int . parse ( evt [ ' width ' ] ) ;
_display . height = int . parse ( evt [ ' height ' ] ) ;
FFI . cursorModel . updateDisplayOrigin ( _display . x , _display . y ) ;
}
void handlePeerInfo ( Map < String , dynamic > evt ) {
dismissLoading ( ) ;
_pi . username = evt [ ' username ' ] ;
_pi . hostname = evt [ ' hostname ' ] ;
_pi . platform = evt [ ' platform ' ] ;
_pi . sasEnabled = evt [ ' sas_enabled ' ] = = " true " ;
_pi . currentDisplay = int . parse ( evt [ ' current_display ' ] ) ;
List < dynamic > displays = json . decode ( evt [ ' displays ' ] ) ;
_pi . displays = List < Display > ( ) ;
for ( int i = 0 ; i < displays . length ; + + i ) {
Map < String , dynamic > d0 = displays [ i ] ;
var d = Display ( ) ;
d . x = d0 [ ' x ' ] . toDouble ( ) ;
d . y = d0 [ ' y ' ] . toDouble ( ) ;
d . width = d0 [ ' width ' ] ;
d . height = d0 [ ' height ' ] ;
_pi . displays . add ( d ) ;
}
if ( _pi . currentDisplay < _pi . displays . length ) {
_display = _pi . displays [ _pi . currentDisplay ] ;
FFI . cursorModel . updateDisplayOrigin ( _display . x , _display . y ) ;
}
2020-11-19 00:53:10 +08:00
if ( displays . length > 1 ) {
showLoading ( ' Waiting for image... ' ) ;
_waitForImage = true ;
}
2020-11-19 00:32:46 +08:00
}
}
class ImageModel with ChangeNotifier {
ui . Image _image ;
ui . Image get image = > _image ;
void update ( ui . Image image ) {
_image = image ;
2020-11-19 18:22:06 +08:00
if ( image ! = null ) notifyListeners ( ) ;
2020-11-19 00:32:46 +08:00
}
}
class CursorModel with ChangeNotifier {
ui . Image _image ;
final _images = Map < int , ui . Image > ( ) ;
2020-11-22 18:29:04 +08:00
double _x = - 10000 ;
double _y = - 10000 ;
2020-11-19 00:32:46 +08:00
double _hotx = 0 ;
double _hoty = 0 ;
double _displayOriginX = 0 ;
double _displayOriginY = 0 ;
ui . Image get image = > _image ;
double get x = > _x - _displayOriginX - _hotx ;
double get y = > _y - _displayOriginY - _hoty ;
void updateCursorData ( Map < String , dynamic > evt ) {
var id = int . parse ( evt [ ' id ' ] ) ;
_hotx = double . parse ( evt [ ' hotx ' ] ) ;
_hoty = double . parse ( evt [ ' hoty ' ] ) ;
var width = int . parse ( evt [ ' width ' ] ) ;
var height = int . parse ( evt [ ' height ' ] ) ;
List < dynamic > colors = json . decode ( evt [ ' colors ' ] ) ;
final rgba = Uint8List . fromList ( colors . map ( ( s ) = > s as int ) . toList ( ) ) ;
ui . decodeImageFromPixels ( rgba , width , height , ui . PixelFormat . rgba8888 ,
( image ) {
_image = image ;
_images [ id ] = image ;
try {
// my throw exception, because the listener maybe already dispose
notifyListeners ( ) ;
} catch ( e ) { }
} ) ;
}
void updateCursorId ( Map < String , dynamic > evt ) {
final tmp = _images [ int . parse ( evt [ ' id ' ] ) ] ;
if ( tmp ! = null ) {
_image = tmp ;
notifyListeners ( ) ;
}
}
void updateCursorPosition ( Map < String , dynamic > evt ) {
_x = double . parse ( evt [ ' x ' ] ) ;
_y = double . parse ( evt [ ' y ' ] ) ;
notifyListeners ( ) ;
}
void updateDisplayOrigin ( double x , double y ) {
_displayOriginX = x ;
_displayOriginY = y ;
notifyListeners ( ) ;
}
void clear ( ) {
2020-11-22 18:29:04 +08:00
_x = - 10000 ;
_x = - 10000 ;
2020-11-19 00:32:46 +08:00
_image = null ;
_images . clear ( ) ;
}
}
class FFI {
static F1 _freeCString ;
static F2 _getByName ;
static F3 _setByName ;
static F4 _freeRgba ;
static F5 _getRgba ;
static Pointer < RgbaFrame > _lastRgbaFrame ;
static final imageModel = ImageModel ( ) ;
static final ffiModel = FfiModel ( ) ;
static final cursorModel = CursorModel ( ) ;
static String getId ( ) {
return getByName ( ' remote_id ' ) ;
}
static List < Peer > peers ( ) {
try {
List < dynamic > peers = json . decode ( getByName ( ' peers ' ) ) ;
return peers
. map ( ( s ) = > s as List < dynamic > )
. map ( ( s ) = >
Peer . fromJson ( s [ 0 ] as String , s [ 1 ] as Map < String , dynamic > ) )
. toList ( ) ;
} catch ( e ) {
print ( e ) ;
}
return [ ] ;
}
static void connect ( String id ) {
setByName ( ' connect ' , id ) ;
}
static void clearRgbaFrame ( ) {
if ( _lastRgbaFrame ! = null & & _lastRgbaFrame ! = nullptr )
_freeRgba ( _lastRgbaFrame ) ;
}
static Uint8List getRgba ( ) {
_lastRgbaFrame = _getRgba ( ) ;
if ( _lastRgbaFrame = = null | | _lastRgbaFrame = = nullptr ) return null ;
final ref = _lastRgbaFrame . ref ;
return Uint8List . sublistView ( ref . data . asTypedList ( ref . len ) ) ;
}
static Map < String , dynamic > popEvent ( ) {
var s = getByName ( ' event ' ) ;
if ( s = = ' ' ) return null ;
try {
Map < String , dynamic > event = json . decode ( s ) ;
return event ;
} catch ( e ) {
print ( e ) ;
}
return null ;
}
static void login ( String password , bool remember ) {
setByName (
' login ' ,
json . encode ( {
' password ' : password ,
' remember ' : remember ? ' true ' : ' false ' ,
} ) ) ;
}
static void close ( ) {
setByName ( ' close ' , ' ' ) ;
FFI . imageModel . update ( null ) ;
FFI . cursorModel . clear ( ) ;
FFI . ffiModel . clear ( ) ;
}
2020-11-20 16:37:48 +08:00
static void setByName ( String name , [ String value = ' ' ] ) {
2020-11-19 00:32:46 +08:00
_setByName ( Utf8 . toUtf8 ( name ) , Utf8 . toUtf8 ( value ) ) ;
}
2020-11-20 02:12:48 +08:00
static String getByName ( String name , [ String arg = ' ' ] ) {
2020-11-19 00:32:46 +08:00
var p = _getByName ( Utf8 . toUtf8 ( name ) , Utf8 . toUtf8 ( arg ) ) ;
assert ( p ! = nullptr & & p ! = null ) ;
var res = Utf8 . fromUtf8 ( p ) ;
// https://github.com/brickpop/flutter-rust-ffi
_freeCString ( p ) ;
return res ;
}
static Future < Null > init ( ) async {
final dylib = Platform . isAndroid
? DynamicLibrary . open ( ' librustdesk.so ' )
: DynamicLibrary . process ( ) ;
_getByName = dylib . lookupFunction < F2 , F2 > ( ' get_by_name ' ) ;
_setByName =
dylib . lookupFunction < Void Function ( Pointer < Utf8 > , Pointer < Utf8 > ) , F3 > (
' set_by_name ' ) ;
_freeCString = dylib
. lookupFunction < Void Function ( Pointer < Utf8 > ) , F1 > ( ' rust_cstr_free ' ) ;
_freeRgba = dylib
. lookupFunction < Void Function ( Pointer < RgbaFrame > ) , F4 > ( ' free_rgba ' ) ;
_getRgba = dylib . lookupFunction < F5 , F5 > ( ' get_rgba ' ) ;
final dir = ( await getApplicationDocumentsDirectory ( ) ) . path ;
setByName ( ' init ' , dir ) ;
}
}
class Peer {
final String id ;
final String username ;
final String hostname ;
final String platform ;
Peer . fromJson ( String id , Map < String , dynamic > json )
: id = id ,
username = json [ ' username ' ] ,
hostname = json [ ' hostname ' ] ,
platform = json [ ' platform ' ] ;
}
class Display {
double x = 0 ;
double y = 0 ;
int width = 0 ;
int height = 0 ;
}
class PeerInfo {
String username ;
String hostname ;
String platform ;
bool sasEnabled ;
int currentDisplay ;
List < Display > displays ;
}