From ccca02a0bcc2716f62662fcbc14bfa25692ddc60 Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Thu, 24 Sep 2026 11:03:16 -0400 Subject: [PATCH] Separate out GomuksListenerController from ClientController Fixes #89 --- gomuks | 2 +- lib/controllers/client.dart | 119 +++---------------------- lib/controllers/gomuks_listener.dart | 124 +++++++++++++++++++++++++++ lib/controllers/unified_push.dart | 38 ++------ lib/main.dart | 4 +- linux/nix/pkg/gomuks.nix | 2 +- 6 files changed, 146 insertions(+), 143 deletions(-) create mode 100644 lib/controllers/gomuks_listener.dart diff --git a/gomuks b/gomuks index b11eedc..480938f 160000 --- a/gomuks +++ b/gomuks @@ -1 +1 @@ -Subproject commit b11eedca121a7081d3ee48165ba363e722d18d87 +Subproject commit 480938f9e01e7eae2cf249657efae03df76599d6 diff --git a/lib/controllers/client.dart b/lib/controllers/client.dart index 90f8896..8cada03 100644 --- a/lib/controllers/client.dart +++ b/lib/controllers/client.dart @@ -6,17 +6,9 @@ import "dart:math"; import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:ffi/ffi.dart"; -import "package:flutter/foundation.dart"; +import "package:flutter/cupertino.dart"; import "package:intl/intl.dart"; -import "package:nexus/controllers/account_data.dart"; -import "package:nexus/controllers/client_state.dart"; -import "package:nexus/controllers/init_complete.dart"; -import "package:nexus/controllers/rooms.dart"; -import "package:nexus/controllers/space_edges.dart"; -import "package:nexus/controllers/sync_status.dart"; -import "package:nexus/controllers/top_level_spaces.dart"; import "package:nexus/helpers/extensions/gomuks_buffer.dart"; -import "package:nexus/main.dart"; import "package:nexus/models/capabilities.dart"; import "package:nexus/models/content/message.dart"; import "package:nexus/models/event.dart"; @@ -46,9 +38,9 @@ import "package:nexus/models/requests/set_membership.dart"; import "package:nexus/models/requests/set_state.dart"; import "package:nexus/models/requests/upload_media.dart"; import "package:nexus/models/room.dart"; +import "package:nexus/models/room_metadata.dart"; import "package:nexus/models/room_summary.dart"; import "package:nexus/models/spec_versions_response.dart"; -import "package:nexus/models/sync_data.dart"; import "package:nexus/src/third_party/gomuks.g.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:path_provider/path_provider.dart"; @@ -56,6 +48,7 @@ import "package:path_provider/path_provider.dart"; class ClientController extends AsyncNotifier { @override Future build() async { + debugPrint("Setting Gomuks env..."); if (Platform.isAndroid || Platform.isIOS) { final env = { "GOMUKS_ROOT": (await getApplicationSupportDirectory()).path, @@ -70,6 +63,8 @@ class ClientController extends AsyncNotifier { ..free(valuePtr); } } + + debugPrint("Initializing Gomuks..."); final handle = await Isolate.run(() { final bufferPointer = GomuksConfig( matrix: .new( @@ -85,99 +80,9 @@ class ClientController extends AsyncNotifier { } }); - final callable = - NativeCallable< - Void Function(Pointer, Int64, GomuksOwnedBuffer) - >.listener(( - Pointer command, - int requestId, - GomuksOwnedBuffer data, - ) { - try { - final muksEventType = command.cast().toDartString(); - debugPrint("Handling $muksEventType..."); - final decodedMuksEvent = data.toJson(); - - switch (muksEventType) { - case "client_state": - ref - .watch(ClientStateController.provider.notifier) - .set(.fromJson(decodedMuksEvent)); - break; - case "sync_status": - ref - .watch(SyncStatusController.provider.notifier) - .set(.fromJson(decodedMuksEvent)); - break; - case "init_complete": - ref.watch(InitCompleteController.provider.notifier).complete(); - break; - case "send_complete": - final event = Event.fromJson(decodedMuksEvent["event"]); - ref - .watch(RoomsController.provider.notifier) - .update( - .new({ - event.roomId: .new(events: .new({event.rowId: event})), - }), - .new(), - ); - - break; - case "sync_complete": - final syncData = SyncData.fromJson(decodedMuksEvent); - final roomProvider = RoomsController.provider; - final accountDataProvider = AccountDataController.provider; - - if (syncData.clearState) { - ref.invalidate(roomProvider); - ref.invalidate(accountDataProvider); - } - - ref - .watch(roomProvider.notifier) - .update(syncData.rooms, syncData.leftRooms); - ref - .watch(accountDataProvider.notifier) - .update(syncData.accountData); - - if (syncData.topLevelSpaces != null) { - ref - .watch(TopLevelSpacesController.provider.notifier) - .set(syncData.topLevelSpaces!); - } - - if (syncData.spaceEdges != null) { - ref - .watch(SpaceEdgesController.provider.notifier) - .set(syncData.spaceEdges!); - } - - // ref - // .watch(SyncStatusController.provider.notifier) - // .set(SyncStatus.fromJson(decodedMuksEvent)); - break; - default: - debugPrint("Unhandled event: $muksEventType"); - } - debugPrint("Finished handling $muksEventType..."); - } catch (error, stackTrace) { - if (kDebugMode) { - debugPrintStack(stackTrace: stackTrace, label: error.toString()); - rethrow; - } else { - showError(error, stackTrace); - } - } - }); - ref.onDispose(() => GomuksDestroy(handle)); - ref.onDispose(callable.close); - final errorCode = GomuksStart(handle, callable.nativeFunction); - - if (errorCode == 0) return handle; - throw Exception("GomuksStart returned error code $errorCode"); + return handle; } Future callGomuksMethod( @@ -201,13 +106,16 @@ class ClientController extends AsyncNotifier { return json; } - Future handlePush(Map data) async { + Future<(Event, RoomMetadata)> handlePush(Map data) async { final response = await callGomuksMethod( data, (handle, data) async => GomuksHandlePush(handle, data), ); - return response == null ? null : .fromJson(response); + return ( + Event.fromJson(response["event"]), + RoomMetadata.fromJson(response["room"]), + ); } dynamic _sendCommand( @@ -270,11 +178,6 @@ class ClientController extends AsyncNotifier { await _sendCommand("leave_room", {"room_id": room.metadata!.id}); } - // (await _sendCommand("get_event_context", { - // "room_id": request.roomId, - // "event_id": r"$OqZT4NuTj0J1-771IOEEWRI4XdumRNu6ighlvO3K3gc", - // })); - Future> getRoomState(GetRoomStateRequest request) async { Future getState(GetRoomStateRequest request) async => (await _sendCommand("get_room_state", request.toJson())) as List?; diff --git a/lib/controllers/gomuks_listener.dart b/lib/controllers/gomuks_listener.dart new file mode 100644 index 0000000..dd75cf3 --- /dev/null +++ b/lib/controllers/gomuks_listener.dart @@ -0,0 +1,124 @@ +import "dart:async"; +import "dart:ffi"; + +import "package:flutter/foundation.dart"; +import "package:nexus/controllers/account_data.dart"; +import "package:nexus/controllers/client.dart"; +import "package:nexus/controllers/client_state.dart"; +import "package:nexus/controllers/init_complete.dart"; +import "package:nexus/controllers/rooms.dart"; +import "package:nexus/controllers/space_edges.dart"; +import "package:nexus/controllers/sync_status.dart"; +import "package:nexus/controllers/top_level_spaces.dart"; +import "package:nexus/helpers/extensions/gomuks_buffer.dart"; +import "package:nexus/main.dart"; +import "package:ffi/ffi.dart"; +import "package:nexus/models/event.dart"; +import "package:nexus/models/sync_data.dart"; +import "package:nexus/src/third_party/gomuks.g.dart"; +import "package:flutter_riverpod/flutter_riverpod.dart"; + +class GomuksListenerController extends AsyncNotifier { + @override + Future build() async { + debugPrint("Starting gomuks..."); + final handle = await ref.watch(ClientController.provider.future); + + final callable = + NativeCallable< + Void Function(Pointer, Int64, GomuksOwnedBuffer) + >.listener(( + Pointer command, + int requestId, + GomuksOwnedBuffer data, + ) { + try { + final muksEventType = command.cast().toDartString(); + debugPrint("Handling $muksEventType..."); + final decodedMuksEvent = data.toJson(); + + switch (muksEventType) { + case "client_state": + ref + .watch(ClientStateController.provider.notifier) + .set(.fromJson(decodedMuksEvent)); + break; + case "sync_status": + ref + .watch(SyncStatusController.provider.notifier) + .set(.fromJson(decodedMuksEvent)); + break; + case "init_complete": + ref.watch(InitCompleteController.provider.notifier).complete(); + break; + case "send_complete": + final event = Event.fromJson(decodedMuksEvent["event"]); + ref + .watch(RoomsController.provider.notifier) + .update( + .new({ + event.roomId: .new(events: .new({event.rowId: event})), + }), + .new(), + ); + + break; + case "sync_complete": + final syncData = SyncData.fromJson(decodedMuksEvent); + final roomProvider = RoomsController.provider; + final accountDataProvider = AccountDataController.provider; + + if (syncData.clearState) { + ref.invalidate(roomProvider); + ref.invalidate(accountDataProvider); + } + + ref + .watch(roomProvider.notifier) + .update(syncData.rooms, syncData.leftRooms); + ref + .watch(accountDataProvider.notifier) + .update(syncData.accountData); + + if (syncData.topLevelSpaces != null) { + ref + .watch(TopLevelSpacesController.provider.notifier) + .set(syncData.topLevelSpaces!); + } + + if (syncData.spaceEdges != null) { + ref + .watch(SpaceEdgesController.provider.notifier) + .set(syncData.spaceEdges!); + } + + // ref + // .watch(SyncStatusController.provider.notifier) + // .set(SyncStatus.fromJson(decodedMuksEvent)); + break; + default: + debugPrint("Unhandled event: $muksEventType"); + } + debugPrint("Finished handling $muksEventType..."); + } catch (error, stackTrace) { + if (kDebugMode) { + debugPrintStack(stackTrace: stackTrace, label: error.toString()); + rethrow; + } else { + showError(error, stackTrace); + } + } + }); + + ref.onDispose(callable.close); + + final errorCode = GomuksStart(handle, callable.nativeFunction); + if (errorCode != 0) { + throw Exception("GomuksStart returned error code $errorCode"); + } + } + + static final provider = AsyncNotifierProvider( + GomuksListenerController.new, + ); +} diff --git a/lib/controllers/unified_push.dart b/lib/controllers/unified_push.dart index 1b52895..db99868 100644 --- a/lib/controllers/unified_push.dart +++ b/lib/controllers/unified_push.dart @@ -8,7 +8,6 @@ import "package:intl/intl.dart"; import "package:nexus/controllers/key.dart"; import "package:nexus/controllers/notification.dart"; import "package:nexus/controllers/push_key.dart"; -import "package:nexus/controllers/rooms.dart"; import "package:nexus/main.dart"; import "package:nexus/controllers/client.dart"; import "package:nexus/controllers/client_state.dart"; @@ -63,12 +62,11 @@ class UnifiedPushController extends AsyncNotifier { "Failed to decrypt notification. Try toggling off and on UnifiedPush in settings.", ); } - final event = await ref + final (event, roomMetadata) = await ref .read(ClientController.provider.notifier) .handlePush(json.decode(String.fromCharCodes(message.content))); - if (event == null || - event.unreadType?.shouldNotify() != true || + if (event.unreadType?.shouldNotify() != true || (!isInBackground && await windowManager.isFocused().onError((_, _) => true) && await ref.read( @@ -79,41 +77,19 @@ class UnifiedPushController extends AsyncNotifier { return; } - final provider = RoomsController.provider.select( - (rooms) => rooms[event.roomId], - ); - - if (ref.read(provider)?.metadata == null) { - final completer = Completer(); - - final subscription = ref.listen(provider, (previous, next) { - if (next?.metadata != null && !completer.isCompleted) { - completer.complete(); - } - }, fireImmediately: true); - - try { - await completer.future.timeout(.new(seconds: 10)); - } on TimeoutException { - // metadata didn't show up in time - } finally { - subscription.close(); - } - } - - final room = ref.read(provider); - final avatar = room?.metadata?.avatar; - final icon = avatar == null + final icon = roomMetadata.avatar == null ? null : await ref .read(ClientController.provider.notifier) - .downloadMedia(.new(mxc: avatar, isAvatar: true)); + .downloadMedia( + .new(mxc: roomMetadata.avatar!, isAvatar: true), + ); await ref .read(NotificationController.provider.notifier) .send( id: event.eventId.hashCode & 0x7fffffff, - title: room?.metadata?.name ?? "New Event", + title: roomMetadata.name ?? "New Event", icon: icon, payload: event.eventId, body: switch (event.content) { diff --git a/lib/main.dart b/lib/main.dart index 98d9a92..61a7d20 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,8 +4,8 @@ import "package:dynamic_color/dynamic_color.dart"; import "package:flutter/foundation.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:media_kit/media_kit.dart"; -import "package:nexus/controllers/client.dart"; import "package:nexus/controllers/client_state.dart"; +import "package:nexus/controllers/gomuks_listener.dart"; import "package:nexus/controllers/key.dart"; import "package:nexus/controllers/member_list_opened.dart"; import "package:nexus/controllers/multi_provider.dart"; @@ -152,7 +152,7 @@ class const App({super.key}) extends StatelessWidget { builder: (_, ref, _) => switch (ref.watch( MultiProviderController.provider( .new([ - ClientController.provider, + GomuksListenerController.provider, NotificationController.provider, UnifiedPushController.provider, MemberListOpenedController.provider, diff --git a/linux/nix/pkg/gomuks.nix b/linux/nix/pkg/gomuks.nix index ed6d8aa..64e0faa 100644 --- a/linux/nix/pkg/gomuks.nix +++ b/linux/nix/pkg/gomuks.nix @@ -17,7 +17,7 @@ buildGoModule ( src = "${src}/gomuks"; - vendorHash = "sha256-e3+bUe7IURUUaLy905a3xlRgKbkHpVfMuGlZCNSxOyU="; + vendorHash = "sha256-rLLDbNrYf5HqG3Y6zihRi1v+GqGeNAtZUL+8QHdCz7w="; buildPhase = '' runHook preBuild