Skip to content

Commit

Permalink
Merge pull request #16 from Goddchen/feature/clipboard
Browse files Browse the repository at this point in the history
feat: implement clipboard support
  • Loading branch information
Goddchen authored Dec 23, 2022
2 parents 3e80175 + 66a1b60 commit 2d32e3a
Show file tree
Hide file tree
Showing 9 changed files with 507 additions and 13 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,7 @@
## 0.5.0

- Add key event support

## 0.6.0

- Add clipboard support
53 changes: 41 additions & 12 deletions lib/src/client/remote_frame_buffer_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import 'package:dart_rfb/src/constants.dart';
import 'package:dart_rfb/src/extensions/byte_data_extensions.dart';
import 'package:dart_rfb/src/extensions/int_extensions.dart';
import 'package:dart_rfb/src/extensions/raw_socket_extensions.dart';
import 'package:dart_rfb/src/protocol/client_cut_text_message.dart';
import 'package:dart_rfb/src/protocol/client_init_message.dart';
import 'package:dart_rfb/src/protocol/encoding_type.dart';
import 'package:dart_rfb/src/protocol/frame_buffer_update_message.dart';
Expand All @@ -24,6 +25,7 @@ import 'package:dart_rfb/src/protocol/protocol_version_handshake_message.dart';
import 'package:dart_rfb/src/protocol/security_handshake_message.dart';
import 'package:dart_rfb/src/protocol/security_result_handshake_message.dart';
import 'package:dart_rfb/src/protocol/security_type.dart';
import 'package:dart_rfb/src/protocol/server_cut_text_message.dart';
import 'package:dart_rfb/src/protocol/server_init_message.dart';
import 'package:dart_rfb/src/protocol/set_encodings_message.dart';
import 'package:dart_rfb/src/protocol/set_pixel_format_message.dart';
Expand All @@ -35,25 +37,32 @@ import 'package:logging/logging.dart';
class RemoteFrameBufferClient {
static final Logger logger = Logger('RemoteFrameBufferClient');

final StreamController<RemoteFrameBufferClientUpdate>
_updateStreamController =
StreamController<RemoteFrameBufferClientUpdate>.broadcast();

Option<Config> _config = none();

Option<RawSocket> _socket = none();
Option<String> _password = none();

bool _readLoopRunning = false;

Option<String> _password = none();

Option<RemoteFrameBufferProtocolVersion> _selectedProtocolVersion = none();

Option<RemoteFrameBufferSecurityType> _selectedSecurityType = none();

Option<RawSocket> _socket = none();

final StreamController<String> _serverClipBoardStreamController =
StreamController<String>();

final StreamController<RemoteFrameBufferClientUpdate>
_updateStreamController =
StreamController<RemoteFrameBufferClientUpdate>.broadcast();

/// The config used by the underlying session.
Option<Config> get config => _config;

/// A [Stream] that will give access to the server's clipboard updates.
Stream<String> get serverClipBoardStream =>
_serverClipBoardStreamController.stream;

/// A [Stream] that will give access to all incoming framebuffer updates.
Stream<RemoteFrameBufferClientUpdate> get updateStream =>
_updateStreamController.stream;
Expand Down Expand Up @@ -116,6 +125,19 @@ class RemoteFrameBufferClient {
),
);

void sendClientCutText({
required final String text,
}) =>
_socket.match(
() {},
(final RawSocket socket) {
final RemoteFrameBufferClientCutTextMessage message =
RemoteFrameBufferClientCutTextMessage(text: text);
logger.info('> $message');
socket.write(message.toBytes().buffer.asUint8List());
},
);

void sendKeyEvent({
required final RemoteFrameBufferClientKeyEvent keyEvent,
}) =>
Expand Down Expand Up @@ -234,13 +256,20 @@ class RemoteFrameBufferClient {
// no data, just ignore for now
break;
case 3: // ServerCutText
final int length =
(await socket.readSync(length: 7).run()).getUint32(3);
socket.readSync(length: length);
final RemoteFrameBufferServerCutTextMessage message =
(await RemoteFrameBufferServerCutTextMessage
.readFromSocket(socket: socket)
.run())
.getOrElse(
(final Object error) => throw Exception(
'Error reading server cut text: $error',
),
);
logger.info('< $message');
_serverClipBoardStreamController.add(message.text);
break;
default:
logger.log(
Level.INFO,
logger.info(
'Receive unsupported message type: $messageType',
);
break;
Expand Down
32 changes: 32 additions & 0 deletions lib/src/protocol/client_cut_text_message.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import 'dart:convert';
import 'dart:typed_data';

import 'package:freezed_annotation/freezed_annotation.dart';

part 'client_cut_text_message.freezed.dart';

@freezed
class RemoteFrameBufferClientCutTextMessage
with _$RemoteFrameBufferClientCutTextMessage {
const factory RemoteFrameBufferClientCutTextMessage({
required final String text,
}) = _RemoteFrameBufferClientCutTextMessage;

const RemoteFrameBufferClientCutTextMessage._();

ByteData toBytes() {
final String text = this.text.replaceAll('\r', '');
return ByteData.sublistView(
(BytesBuilder()
..add(
(ByteData(8)
..setUint8(0, 6)
..setUint32(4, text.length))
.buffer
.asUint8List(),
)
..add(latin1.encode(text)))
.toBytes(),
);
}
}
147 changes: 147 additions & 0 deletions lib/src/protocol/client_cut_text_message.freezed.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark

part of 'client_cut_text_message.dart';

// **************************************************************************
// FreezedGenerator
// **************************************************************************

T _$identity<T>(T value) => value;

final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');

/// @nodoc
mixin _$RemoteFrameBufferClientCutTextMessage {
String get text => throw _privateConstructorUsedError;

@JsonKey(ignore: true)
$RemoteFrameBufferClientCutTextMessageCopyWith<
RemoteFrameBufferClientCutTextMessage>
get copyWith => throw _privateConstructorUsedError;
}

/// @nodoc
abstract class $RemoteFrameBufferClientCutTextMessageCopyWith<$Res> {
factory $RemoteFrameBufferClientCutTextMessageCopyWith(
RemoteFrameBufferClientCutTextMessage value,
$Res Function(RemoteFrameBufferClientCutTextMessage) then) =
_$RemoteFrameBufferClientCutTextMessageCopyWithImpl<$Res,
RemoteFrameBufferClientCutTextMessage>;
@useResult
$Res call({String text});
}

/// @nodoc
class _$RemoteFrameBufferClientCutTextMessageCopyWithImpl<$Res,
$Val extends RemoteFrameBufferClientCutTextMessage>
implements $RemoteFrameBufferClientCutTextMessageCopyWith<$Res> {
_$RemoteFrameBufferClientCutTextMessageCopyWithImpl(this._value, this._then);

// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;

@pragma('vm:prefer-inline')
@override
$Res call({
Object? text = null,
}) {
return _then(_value.copyWith(
text: null == text
? _value.text
: text // ignore: cast_nullable_to_non_nullable
as String,
) as $Val);
}
}

/// @nodoc
abstract class _$$_RemoteFrameBufferClientCutTextMessageCopyWith<$Res>
implements $RemoteFrameBufferClientCutTextMessageCopyWith<$Res> {
factory _$$_RemoteFrameBufferClientCutTextMessageCopyWith(
_$_RemoteFrameBufferClientCutTextMessage value,
$Res Function(_$_RemoteFrameBufferClientCutTextMessage) then) =
__$$_RemoteFrameBufferClientCutTextMessageCopyWithImpl<$Res>;
@override
@useResult
$Res call({String text});
}

/// @nodoc
class __$$_RemoteFrameBufferClientCutTextMessageCopyWithImpl<$Res>
extends _$RemoteFrameBufferClientCutTextMessageCopyWithImpl<$Res,
_$_RemoteFrameBufferClientCutTextMessage>
implements _$$_RemoteFrameBufferClientCutTextMessageCopyWith<$Res> {
__$$_RemoteFrameBufferClientCutTextMessageCopyWithImpl(
_$_RemoteFrameBufferClientCutTextMessage _value,
$Res Function(_$_RemoteFrameBufferClientCutTextMessage) _then)
: super(_value, _then);

@pragma('vm:prefer-inline')
@override
$Res call({
Object? text = null,
}) {
return _then(_$_RemoteFrameBufferClientCutTextMessage(
text: null == text
? _value.text
: text // ignore: cast_nullable_to_non_nullable
as String,
));
}
}

/// @nodoc
class _$_RemoteFrameBufferClientCutTextMessage
extends _RemoteFrameBufferClientCutTextMessage {
const _$_RemoteFrameBufferClientCutTextMessage({required this.text})
: super._();

@override
final String text;

@override
String toString() {
return 'RemoteFrameBufferClientCutTextMessage(text: $text)';
}

@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$_RemoteFrameBufferClientCutTextMessage &&
(identical(other.text, text) || other.text == text));
}

@override
int get hashCode => Object.hash(runtimeType, text);

@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$_RemoteFrameBufferClientCutTextMessageCopyWith<
_$_RemoteFrameBufferClientCutTextMessage>
get copyWith => __$$_RemoteFrameBufferClientCutTextMessageCopyWithImpl<
_$_RemoteFrameBufferClientCutTextMessage>(this, _$identity);
}

abstract class _RemoteFrameBufferClientCutTextMessage
extends RemoteFrameBufferClientCutTextMessage {
const factory _RemoteFrameBufferClientCutTextMessage(
{required final String text}) = _$_RemoteFrameBufferClientCutTextMessage;
const _RemoteFrameBufferClientCutTextMessage._() : super._();

@override
String get text;
@override
@JsonKey(ignore: true)
_$$_RemoteFrameBufferClientCutTextMessageCopyWith<
_$_RemoteFrameBufferClientCutTextMessage>
get copyWith => throw _privateConstructorUsedError;
}
50 changes: 50 additions & 0 deletions lib/src/protocol/server_cut_text_message.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';

import 'package:dart_rfb/src/extensions/raw_socket_extensions.dart';
import 'package:fpdart/fpdart.dart';
import 'package:freezed_annotation/freezed_annotation.dart';

part 'server_cut_text_message.freezed.dart';

@freezed
class RemoteFrameBufferServerCutTextMessage
with _$RemoteFrameBufferServerCutTextMessage {
const factory RemoteFrameBufferServerCutTextMessage({
required final String text,
}) = _RemoteFrameBufferServerCutTextMessage;

const RemoteFrameBufferServerCutTextMessage._();

ByteData toBytes() => ByteData.sublistView(
(BytesBuilder()
..add(
(ByteData(8)
..setUint8(0, 3)
..setUint32(4, text.length))
.buffer
.asUint8List(),
)
..add(latin1.encode(text.replaceAll('\r', ''))))
.toBytes(),
);

static TaskEither<Object, RemoteFrameBufferServerCutTextMessage>
readFromSocket({
required final RawSocket socket,
}) =>
TaskEither<Object, RemoteFrameBufferServerCutTextMessage>.tryCatch(
() async {
final int length =
(await socket.readSync(length: 7).run()).getUint32(3);
final String text = latin1.decode(
(await socket.readSync(length: length).run())
.buffer
.asUint8List(),
);
return RemoteFrameBufferServerCutTextMessage(text: text);
},
(final Object error, final _) => error,
);
}
Loading

0 comments on commit 2d32e3a

Please sign in to comment.