vnc_viewer 0.0.6
vnc_viewer: ^0.0.6 copied to clipboard
Flutter plugin for embedding an Android VNC client that renders remote desktops and sends keyboard and pointer input to VNC/RFB servers.
vnc_viewer #
vnc_viewer is a Flutter plugin for embedding an Android VNC client inside your app.
It connects to a VNC/RFB server, renders the remote desktop through a Flutter Texture, and exposes APIs for sending keyboard and pointer events from Dart.
Features #
- Connect to a VNC server with
hostName,port, andpassword - Configure a connection timeout from Dart, with a built-in default timeout
- Render the remote framebuffer inside Flutter with
VncViewerWidget - Receive connection lifecycle callbacks such as
onStart,onClose, andonError - React to framebuffer size changes with
onImageResize - Send keyboard and pointer events with
VncViewerHandel - Includes an example app for quick local verification
Platform Support #
| Platform | Status |
|---|---|
| Android | Supported |
| iOS | Not supported |
| macOS | Not supported |
| Windows | Not supported |
| Linux | Not supported |
| Web | Not supported |
Requirements #
- Flutter
>=3.3.0 - Dart SDK
>=3.3.4 <4.0.0 - Android
minSdkVersion 19 - Android app must have network access permission
The plugin currently builds native libraries for:
armeabi-v7aarm64-v8a
Installation #
Add the dependency to your pubspec.yaml:
dependencies:
vnc_viewer: ^0.0.6
Then run:
flutter pub get
Android Configuration #
Your host app must declare Internet permission in android/app/src/main/AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:label="your_app_name">
...
</application>
</manifest>
If you plan to test a release build, add the permission to the main manifest, not only the debug manifest.
Quick Start #
Use VncViewerWidget when you want a ready-to-embed viewer widget:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:vnc_viewer/vnc_viewer_handel.dart';
import 'package:vnc_viewer/vnc_viewer_widget.dart';
class VncPage extends StatefulWidget {
const VncPage({
super.key,
required this.hostName,
required this.port,
required this.password,
});
final String hostName;
final int port;
final String password;
@override
State<VncPage> createState() => _VncPageState();
}
class _VncPageState extends State<VncPage> {
final VncViewerHandel _handle = VncViewerHandel();
int? _clientId;
String _status = 'Connecting...';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('${widget.hostName}:${widget.port}'),
actions: [
IconButton(
onPressed: _clientId == null ? null : _sendEnter,
icon: const Icon(Icons.keyboard_return),
),
],
),
body: Column(
children: [
Expanded(
child: VncViewerWidget(
hostName: widget.hostName,
port: widget.port,
password: widget.password,
onStart: (clientId) {
setState(() {
_clientId = clientId;
_status = 'Connected';
});
},
onClose: (_) {
setState(() {
_clientId = null;
_status = 'Closed';
});
},
onImageResize: () {
setState(() {
_status = 'Rendering';
});
},
onError: (message) {
setState(() {
_status = 'Error: $message';
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message)),
);
},
),
),
Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Expanded(child: Text(_status)),
FilledButton(
onPressed: _clientId == null ? null : _sendEnter,
child: const Text('Send Enter'),
),
],
),
),
],
),
);
}
Future<void> _sendEnter() async {
final clientId = _clientId;
if (clientId == null) {
return;
}
_handle.sendKey(clientId, 0xff0d, true);
await Future.delayed(const Duration(milliseconds: 80));
_handle.sendKey(clientId, 0xff0d, false);
}
}
API Overview #
VncViewerWidget #
The widget manages VNC client initialization, event subscription, rendering, and cleanup for you.
Constructor:
const VncViewerWidget({
Key? key,
required String hostName,
required String password,
int port = 5900,
Duration connectTimeout = VncViewerWidget.defaultConnectTimeout,
void Function(int clientId)? onStart,
void Function(int clientId)? onClose,
VoidCallback? onImageResize,
void Function(String msg)? onError,
})
Parameters:
hostName: Hostname or IP address of the VNC serverport: TCP port of the VNC server, default is5900password: VNC password, pass an empty string if the server does not require oneconnectTimeout: Timeout used while establishing the native VNC connection. Defaults toDuration(seconds: 10)onStart: Called after the native VNC connection is established successfullyonClose: Called when the VNC session is closedonImageResize: Called when the remote framebuffer size changesonError: Called when initialization or runtime errors occur
Behavior:
- Shows a loading state while connecting
- Shows an error or closed state instead of staying on the loading indicator when the session fails or ends
- Scales the remote frame to fit the available viewport
- Wraps the frame with
InteractiveViewerfor zooming and panning - Closes the native client automatically on widget disposal
Logging #
Android-side logs use the tag libvncviewer_flutter.
The plugin now logs key lifecycle steps including:
- Init request and validation
- Event stream registration
- Native start, connect success, framebuffer resize, and close
- Error and cleanup paths
VncViewerHandel #
VncViewerHandel exposes low-level methods for manual control:
final handle = VncViewerHandel();
Available methods:
getPlatformVersion()initVncClient(String hostName, int port, String password, {Duration? connectTimeout})startVncClient(int clientId)closeVncClient(int clientId)sendPointer(int clientId, int x, int y, int mask)sendKey(int clientId, int key, bool down)
In most cases, you should let VncViewerWidget manage initialization and lifecycle, and only use VncViewerHandel for sending input events after onStart gives you a clientId.
Timeout example:
final clientId = await handle.initVncClient(
'192.168.1.20',
5900,
'secret',
connectTimeout: const Duration(seconds: 5),
);
Sending Input Events #
Keyboard events #
sendKey expects VNC/X11 keysym values.
Example: send Enter
handle.sendKey(clientId, 0xff0d, true);
handle.sendKey(clientId, 0xff0d, false);
Pointer events #
sendPointer expects remote coordinates and a VNC button mask.
Common button mask values:
0: release all buttons1: left button2: middle button4: right button
Example:
handle.sendPointer(clientId, 120, 80, 1);
handle.sendPointer(clientId, 120, 80, 0);
Note that VncViewerWidget currently focuses on rendering. If you want full remote mouse or touch control, map your own Flutter gestures to sendPointer.
Example App #
The bundled example supports --dart-define so you can launch a server directly:
cd example
flutter run \
--dart-define=VNC_HOST=192.168.1.100 \
--dart-define=VNC_PORT=5900 \
--dart-define=VNC_PASSWORD=secret \
--dart-define=VNC_AUTO_CONNECT=true
If VNC_AUTO_CONNECT is omitted or set to false, the example app shows a form where you can enter the connection details manually.
Error Handling #
Recommended practice:
- Always provide
onError - Validate host and port before opening the viewer
- Treat
clientId == nullor0as initialization failure - Clean up any custom input controllers when the page is disposed
Typical failure reasons:
- Wrong host, port, or password
- The VNC server is unreachable
- The server requires an authentication flow not covered by the current API
- Missing
INTERNETpermission in the Android app
Limitations #
- Android only at the moment
- Current Dart API only accepts
hostName,port, andpassword - No built-in username field for auth flows that require a non-empty username
- No clipboard, file transfer, or audio API exposed in Dart
- No built-in desktop control overlay; custom gesture mapping is up to the app
Testing #
The package includes Dart-side unit tests for:
VncViewerHandeldelegationMethodChannelVncViewermethod channel calls
Run tests with:
flutter test
License #
This package is distributed under the GNU General Public License, version 2 or later (GPL-2.0-or-later). See LICENSE.