Files
iperf3-gui/lib/ui/features/test_run/test_run_screen.dart
T
irene 0dd202291c fix: resolve SIGBUS crash in JNI bridge — use CallVoidMethod instead of function pointer casts
The root cause of the SIGBUS (alignment fault) crash was casting jmethodID
values to function pointers and invoking them directly. jmethodID is an
opaque JNI type that must only be used via CallVoidMethod/CallObjectMethod
etc. Calling it as a function pointer dereferences garbage memory, causing
SIGBUS at address 0xb1.

Changes:
- Replace function pointer typedefs (output_callback_t etc.) with a struct
  storing JNIEnv + callback global ref + jmethodIDs
- All callbacks now go through CallVoidMethod(env, ref, mid, ...)
- Remove getTestProgress() (never used, unused start_time workaround)
- Fix Kotlin arg cast: List<String> -> toTypedArray() (ClassCastException)
- Update UI default server to speedtest.uztelecom.uz
2026-08-25 18:59:42 +09:00

144 lines
4.5 KiB
Dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:iperf3gui/data/services/iperf3_service.dart';
import 'package:iperf3gui/domain/models/test_configuration.dart';
import 'package:iperf3gui/ui/features/test_run/test_run_view_model.dart';
/// Main test run screen.
///
/// iperf3 is now compiled as a native shared library (libiperf3jni.so)
/// and accessed via JNI. No binary extraction or chmod needed.
class TestRunScreen extends StatelessWidget {
const TestRunScreen({super.key});
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => TestRunViewModel(
iperf3Service: Iperf3Service(),
),
child: const _TestRunContent(),
);
}
}
class _TestRunContent extends StatefulWidget {
const _TestRunContent();
@override
State<_TestRunContent> createState() => _TestRunContentState();
}
class _TestRunContentState extends State<_TestRunContent> {
final _serverController = TextEditingController(text: 'speedtest.uztelecom.uz');
final _portController = TextEditingController(text: '5201');
@override
void dispose() {
_serverController.dispose();
_portController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('iperf3-gui'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Server input
TextField(
controller: _serverController,
decoration: const InputDecoration(
labelText: 'Server',
hintText: 'e.g. iperf.es.net or 192.168.1.1',
),
),
const SizedBox(height: 16),
// Port input
TextField(
controller: _portController,
decoration: const InputDecoration(
labelText: 'Port',
hintText: '5201',
),
keyboardType: TextInputType.number,
),
const SizedBox(height: 16),
// Go button
Consumer<TestRunViewModel>(
builder: (context, vm, _) {
return ElevatedButton(
onPressed: vm.state == TestRunState.running
? null
: () {
final config = TestConfiguration(
server: _serverController.text,
port: int.tryParse(_portController.text) ?? 5201,
);
vm.runTest(config);
},
child: vm.state == TestRunState.running
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Text('GO'),
);
},
),
const SizedBox(height: 16),
// Status indicator
Consumer<TestRunViewModel>(
builder: (context, vm, _) {
final statusText = switch (vm.state) {
TestRunState.idle => '',
TestRunState.running => 'Running...',
TestRunState.completed => 'Test complete',
TestRunState.error => 'Error',
};
return Text(
statusText,
style: TextStyle(
color: vm.state == TestRunState.error
? Colors.red
: Colors.grey,
),
);
},
),
const SizedBox(height: 16),
// Raw output text box
Expanded(
child: Consumer<TestRunViewModel>(
builder: (context, vm, _) {
final output = vm.result?.rawOutput ?? '';
return TextField(
controller: TextEditingController(text: output),
decoration: const InputDecoration(
labelText: 'Raw Output',
border: OutlineInputBorder(),
),
maxLines: null,
readOnly: true,
);
},
),
),
],
),
),
);
}
}