diff --git a/compositor_dart/lib/compositor_dart.dart b/compositor_dart/lib/compositor_dart.dart index bc97510..5a5e3f2 100644 --- a/compositor_dart/lib/compositor_dart.dart +++ b/compositor_dart/lib/compositor_dart.dart @@ -1,3 +1,4 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first library compositor_dart; import 'dart:async'; @@ -11,6 +12,18 @@ import 'package:logging/logging.dart'; enum KeyStatus { released, pressed } +enum WindowEventType { maximize, minimize } + +class WindowEvent { + final WindowEventType windowEventType; + final int handle; + + WindowEvent({ + required this.windowEventType, + required this.handle, + }); +} + class Surface { // This is actually used as a pointer on the compositor side. // It should always be returned exactly as is to the compositiors, @@ -20,16 +33,42 @@ class Surface { final int pid; final int gid; final int uid; - - final Compositor compositor; + final bool isPopup; + final int parentHandle; + final int surfaceWidth; + final int surfaceHeight; + final int offsetTop; + final int offsetLeft; + final int offsetRight; + final int offsetBottom; + final int contentWidth; + final int contentHeight; + bool isMaximized; + bool isMinimized; Surface({ required this.handle, required this.pid, required this.gid, required this.uid, - required this.compositor, + required this.isPopup, + required this.parentHandle, + required this.surfaceWidth, + required this.surfaceHeight, + required this.offsetTop, + required this.offsetLeft, + required this.offsetRight, + required this.offsetBottom, + required this.contentWidth, + required this.contentHeight, + this.isMaximized = false, + this.isMinimized = false, }); + + @override + String toString() { + return 'Surface(handle: $handle, pid: $pid, gid: $gid, uid: $uid, isPopup: $isPopup, parentHandle: $parentHandle, isMaximized: $isMaximized, isMinimized: $isMinimized)'; + } } class CompositorSockets { @@ -41,7 +80,8 @@ class CompositorSockets { class _CompositorPlatform { final MethodChannel channel = const MethodChannel("wlroots"); - final HashMap Function(MethodCall)> handlers = HashMap(); + final HashMap Function(MethodCall)> handlers = + HashMap(); _CompositorPlatform() { channel.setMethodCallHandler((call) async { @@ -62,15 +102,18 @@ class _CompositorPlatform { handlers[method] = handler; } - Future surfaceToplevelSetSize(Surface surface, int width, int height) async { - await channel.invokeListMethod("surface_toplevel_set_size", [surface.handle, width, height]); + Future surfaceToplevelSetSize( + Surface surface, int width, int height) async { + await channel.invokeListMethod( + "surface_toplevel_set_size", [surface.handle, width, height]); } Future clearFocus(Surface surface) async { await channel.invokeMethod("surface_clear_focus", [surface.handle]); } - Future surfaceSendKey(Surface surface, int keycode, KeyStatus status, Duration timestamp) async { + Future surfaceSendKey(Surface surface, int keycode, KeyStatus status, + Duration timestamp) async { await channel.invokeListMethod( "surface_keyboard_key", [ @@ -82,8 +125,13 @@ class _CompositorPlatform { ); } + Future surfaceFocusViewWithHandle(int handle) async { + await channel.invokeMethod("surface_focus_from_handle", [handle]); + } + Future getSocketPaths() async { - var response = await channel.invokeMethod("get_socket_paths") as Map; + var response = + await channel.invokeMethod("get_socket_paths") as Map; return CompositorSockets( wayland: response["wayland"] as String, x: response["x"] as String, @@ -113,6 +161,7 @@ class Compositor { // Emits an event when a surface has been added and is ready to be presented on the screen. StreamController surfaceMapped = StreamController.broadcast(); StreamController surfaceUnmapped = StreamController.broadcast(); + StreamController windowEvents = StreamController.broadcast(); int? keyToXkb(int physicalKey) => physicalToXkbMap[physicalKey]; @@ -123,40 +172,66 @@ class Compositor { pid: call.arguments["client_pid"], gid: call.arguments["client_gid"], uid: call.arguments["client_uid"], - compositor: this, + isPopup: call.arguments["is_popup"], + parentHandle: call.arguments["parent_handle"], + surfaceHeight: call.arguments['surface_height'], + surfaceWidth: call.arguments['surface_width'], + offsetTop: call.arguments['offset_top'], + offsetLeft: call.arguments['offset_left'], + offsetRight: call.arguments['offset_right'], + offsetBottom: call.arguments['offset_bottom'], + contentWidth: call.arguments['content_width'], + contentHeight: call.arguments['content_height'], ); - surfaces[surface.handle] = surface; + surfaces.putIfAbsent(surface.handle, () => surface); + surfaceMapped.add(surface); }); platform.addHandler("surface_unmap", (call) async { int handle = call.arguments["handle"]; - Surface surface = surfaces[handle]!; - surfaces.remove(handle); - surfaceUnmapped.add(surface); + if (surfaces.containsKey(handle)) { + Surface surface = surfaces[handle]!; + surfaces.remove(handle); + surfaceUnmapped.add(surface); + } }); platform.addHandler("flutter/keyevent", (call) async {}); - } - /// Returns `true` if we are currently running in the compositor embedder. - /// If so, all functionality in this library is available. - /// - /// Returns `false` in all other cases. If so, no funcitonality in this - /// library should be used. - Future isCompositor() async { - if (_isCompositor != null) return _isCompositor!; - - try { - await platform.channel.invokeMethod("is_compositor"); - _isCompositor = true; - } on MissingPluginException { - _isCompositor = false; + platform.addHandler('window_maximize', (call) async { + int handle = call.arguments['handle']; + + windowEvents.add(WindowEvent( + windowEventType: WindowEventType.maximize, handle: handle)); + }); + + platform.addHandler('window_minimize', (call) async { + int handle = call.arguments['handle']; + + windowEvents.add(WindowEvent( + windowEventType: WindowEventType.minimize, handle: handle)); + }); + + /// Returns `true` if we are currently running in the compositor embedder. + /// If so, all functionality in this library is available. + /// + /// Returns `false` in all other cases. If so, no funcitonality in this + /// library should be used. + Future isCompositor() async { + if (_isCompositor != null) return _isCompositor!; + + try { + await platform.channel.invokeMethod("is_compositor"); + _isCompositor = true; + } on MissingPluginException { + _isCompositor = false; + } + + return _isCompositor!; } - return _isCompositor!; + /// Will return the paths of the compositor sockets. + Future getSocketPaths() => platform.getSocketPaths(); } - - /// Will return the paths of the compositor sockets. - Future getSocketPaths() => platform.getSocketPaths(); } diff --git a/compositor_dart/lib/platform/interceptor_widgets_binding.dart b/compositor_dart/lib/platform/interceptor_widgets_binding.dart index 1549455..f571d3a 100644 --- a/compositor_dart/lib/platform/interceptor_widgets_binding.dart +++ b/compositor_dart/lib/platform/interceptor_widgets_binding.dart @@ -5,22 +5,27 @@ import 'package:flutter/services.dart'; class InterceptorWidgetsBinding extends WidgetsFlutterBinding { InterceptorBinaryMessenger? _binaryMessenger; + static WidgetsBinding? instance; + @override void initInstances() { super.initInstances(); _binaryMessenger = InterceptorBinaryMessenger(super.defaultBinaryMessenger); + instance = this; } @override BinaryMessenger get defaultBinaryMessenger { - return _binaryMessenger == null ? super.defaultBinaryMessenger : _binaryMessenger!; + return _binaryMessenger == null + ? super.defaultBinaryMessenger + : _binaryMessenger!; } static WidgetsBinding ensureInitialized() { - if (WidgetsBinding.instance == null) { + if (InterceptorWidgetsBinding.instance == null) { InterceptorWidgetsBinding(); } - return WidgetsBinding.instance!; + return InterceptorWidgetsBinding.instance!; } static void runApp(Widget app) { diff --git a/compositor_dart/lib/surface.dart b/compositor_dart/lib/surface.dart index 22bfeae..9e3ca17 100644 --- a/compositor_dart/lib/surface.dart +++ b/compositor_dart/lib/surface.dart @@ -23,7 +23,7 @@ class _MeasureSizeRenderObject extends RenderProxyBox { if (oldSize == newSize) return; oldSize = newSize; - WidgetsBinding.instance?.addPostFrameCallback((_) { + WidgetsBinding.instance.addPostFrameCallback((_) { onChange(newSize); }); } @@ -46,8 +46,15 @@ class _MeasureSize extends SingleChildRenderObjectWidget { class SurfaceView extends StatefulWidget { final Surface surface; + final Compositor compositor; + final Function(Surface surface)? onPointerClick; - const SurfaceView({Key? key, required this.surface}) : super(key: key); + const SurfaceView({ + Key? key, + required this.surface, + required this.compositor, + this.onPointerClick, + }) : super(key: key); @override State createState() { @@ -61,7 +68,11 @@ class _SurfaceViewState extends State { @override void initState() { super.initState(); - controller = _CompositorPlatformViewController(surface: widget.surface); + controller = _CompositorPlatformViewController( + surface: widget.surface, + compositor: widget.compositor, + onPointerClick: widget.onPointerClick, + ); } @override @@ -69,7 +80,11 @@ class _SurfaceViewState extends State { super.didUpdateWidget(oldWidget); if (oldWidget.surface != widget.surface) { controller.dispose(); - controller = _CompositorPlatformViewController(surface: widget.surface); + controller = _CompositorPlatformViewController( + surface: widget.surface, + compositor: widget.compositor, + onPointerClick: widget.onPointerClick, + ); } } @@ -90,7 +105,7 @@ class _SurfaceViewState extends State { int? keycode = physicalToXkbMap[event.physicalKey.usbHidUsage]; if (keycode != null) { - controller.surface.compositor.platform.surfaceSendKey( + controller.compositor.platform.surfaceSendKey( widget.surface, keycode, status, @@ -120,18 +135,26 @@ class _SurfaceViewState extends State { } class _CompositorPlatformViewController extends PlatformViewController { - Surface surface; + final Surface surface; + final Compositor compositor; + final Function(Surface surface)? onPointerClick; Size size = const Size(100, 100); - _CompositorPlatformViewController({required this.surface}); + _CompositorPlatformViewController({ + required this.surface, + required this.compositor, + this.onPointerClick, + }); void setSize(Size size) { this.size = size; - Compositor.compositor.platform.surfaceToplevelSetSize(surface, size.width.round(), size.height.round()); + Compositor.compositor.platform.surfaceToplevelSetSize( + surface, size.width.round(), size.height.round()); } @override - Future clearFocus() => Compositor.compositor.platform.clearFocus(surface); + Future clearFocus() => + Compositor.compositor.platform.clearFocus(surface); @override Future dispatchPointerEvent(PointerEvent event) async { @@ -154,6 +177,9 @@ class _CompositorPlatformViewController extends PlatformViewController { Offset scrollAmount = Offset.zero; if (event is PointerDownEvent) { eventType = pointerDownEvent; + if (onPointerClick != null) { + onPointerClick!(surface); + } } else if (event is PointerUpEvent) { eventType = pointerUpEvent; } else if (event is PointerHoverEvent) { diff --git a/demo/lib/constants.dart b/demo/lib/constants.dart new file mode 100644 index 0000000..70df71e --- /dev/null +++ b/demo/lib/constants.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; + +final systemBorderRadius = BorderRadius.circular(6); + +const double windowWidth = 400; +const double windowHeight = 500; +const double initialPositionX = 200; +const double initialPositionY = 70; +const double windowDecorationHeight = 30; +const double windowDecorationControlSize = 14; +const Color windowDecorationColor = Color(0xff1c1c1e); +const Color windowDecorationControlColor = Color(0xffffffff); +const borderWidth = 1; diff --git a/demo/lib/main.dart b/demo/lib/main.dart index cebcef4..dff6588 100644 --- a/demo/lib/main.dart +++ b/demo/lib/main.dart @@ -1,10 +1,12 @@ -import 'dart:io'; - import 'package:compositor_dart/compositor_dart.dart'; import 'package:compositor_dart/keyboard/keyboard_client_controller.dart'; import 'package:compositor_dart/keyboard/platform_keyboard.dart'; import 'package:compositor_dart/platform/interceptor_widgets_binding.dart'; import 'package:compositor_dart/surface.dart'; +import 'package:demo/constants.dart'; +import 'package:demo/window.dart'; +import 'package:demo/window_clipper.dart'; +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; @@ -136,111 +138,130 @@ class MyHomePage extends StatefulWidget { } class _MyHomePageState extends State { - int _counter = 0; - Compositor compositor = Compositor(); - Surface? surface; + late Compositor compositor; + Map surfaces = {}; + + int? focusedSurface; + late double mousePositionX; + late double mousePositionY; + + @override + void initState() { + super.initState(); - _MyHomePageState() { - compositor.surfaceMapped.stream.listen((event) { + mousePositionX = 0; + mousePositionY = 0; + + compositor = Compositor(); + compositor.surfaceMapped.stream.listen((Surface event) { setState(() { - surface = event; + surfaces.putIfAbsent(event.handle, () => event); + focusedSurface = event.handle; }); }); - compositor.surfaceUnmapped.stream.listen((event) { - if (surface == event) { + compositor.surfaceUnmapped.stream.listen((Surface event) { + setState(() { + surfaces.removeWhere((key, value) => key == event.handle); + if (surfaces.isNotEmpty) { + focusedSurface = surfaces.keys.last; + } else { + focusedSurface = null; + } + }); + }); + + compositor.windowEvents.stream.listen((WindowEvent event) { + if (event.windowEventType == WindowEventType.maximize) { setState(() { - surface = null; + surfaces[event.handle]!.isMaximized = + !surfaces[event.handle]!.isMaximized; + }); + } + + if (event.windowEventType == WindowEventType.minimize) { + setState(() { + surfaces[event.handle]!.isMinimized = + !surfaces[event.handle]!.isMinimized; }); } }); } - void _incrementCounter() { - setState(() { - // This call to setState tells the Flutter framework that something has - // changed in this State, which causes it to rerun the build method below - // so that the display can reflect the updated values. If we changed - // _counter without calling setState(), then the build method would not be - // called again, and so nothing would appear to happen. - _counter++; - }); + void focusView(int handle) { + if (focusedSurface != handle) { + compositor.platform.surfaceFocusViewWithHandle(handle); + focusedSurface = handle; + } } @override Widget build(BuildContext context) { - Widget? surfaceView; - if (surface != null) { - surfaceView = SurfaceView( - surface: surface!, - ); - } - - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. - // - // The Flutter framework has been optimized to make rerunning build methods - // fast, so that you can just rebuild anything that needs updating rather - // than having to individually change instances of widgets. return Focus( onKeyEvent: (node, KeyEvent event) { int? keycode = compositor.keyToXkb(event.physicalKey.usbHidUsage); - if (keycode != null && surface != null) { + if (keycode != null && focusedSurface != null) { compositor.platform.surfaceSendKey( - surface!, keycode, event is KeyDownEvent ? KeyStatus.pressed : KeyStatus.released, event.timeStamp); + surfaces[focusedSurface]!, + keycode, + event is KeyDownEvent ? KeyStatus.pressed : KeyStatus.released, + event.timeStamp); } return KeyEventResult.handled; }, autofocus: true, child: Scaffold( appBar: AppBar( - // Here we take the value from the MyHomePage object that was created by - // the App.build method, and use it to set our appbar title. title: Text(widget.title), ), - body: Center( - // Center is a layout widget. It takes a single child and positions it - // in the middle of the parent. - child: Column( - // Column is also a layout widget. It takes a list of children and - // arranges them vertically. By default, it sizes itself to fit its - // children horizontally, and tries to be as tall as its parent. - // - // Invoke "debug painting" (press "p" in the console, choose the - // "Toggle Debug Paint" action from the Flutter Inspector in Android - // Studio, or the "Toggle Debug Paint" command in Visual Studio Code) - // to see the wireframe for each widget. - // - // Column has various properties to control how it sizes itself and - // how it positions its children. Here we use mainAxisAlignment to - // center the children vertically; the main axis here is the vertical - // axis because Columns are vertical (the cross axis would be - // horizontal). - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text( - 'You have pushed the button this many timesa:', - ), - Text( - '$_counter', - style: Theme.of(context).textTheme.headline4, - ), - Container( - color: Colors.amber, - padding: const EdgeInsets.all(8.0), - child: SizedBox(width: 500, height: 500, child: surfaceView), + body: LayoutBuilder(builder: (context, constraints) { + return SizedBox.expand( + child: MouseRegion( + onHover: (PointerHoverEvent event) { + mousePositionX = event.position.dx; + mousePositionY = event.position.dy; + }, + child: Stack( + children: surfaces.entries + .skipWhile((entry) => entry.value.isMinimized) + .map((MapEntry entry) { + final isPopup = entry.value.isPopup; + + return Window( + initialX: entry.value.isMaximized + ? 0 + : (isPopup ? mousePositionX : initialPositionX), + initialY: entry.value.isMaximized + ? 0 + : (isPopup ? mousePositionY : initialPositionY), + width: entry.value.isMaximized + ? constraints.maxWidth + : entry.value.contentWidth.toDouble(), + height: entry.value.isMaximized + ? constraints.maxHeight + : entry.value.contentHeight.toDouble(), + shouldDecorate: !isPopup, + isMaximized: entry.value.isMaximized, + onTap: () => focusView(entry.key), + child: SurfaceView( + surface: entry.value, + compositor: compositor, + onPointerClick: (Surface surface) { + focusView(surface.handle); + }, + ), + ); + }).toList(), ), - TestSlider(), - const TextField(), - ], - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, - tooltip: 'Increment', - child: const Icon(Icons.add), - ), // This trailing comma makes auto-formatting nicer for build methods. + ), + ); + }), ), + // floatingActionButton: FloatingActionButton( + // onPressed: _incrementCounter, + // tooltip: 'Increment', + // child: const Icon(Icons.add), + // ), // This trailing comma makes auto-formatting nicer for build methods. ); } } diff --git a/demo/lib/window.dart b/demo/lib/window.dart new file mode 100644 index 0000000..6c0859a --- /dev/null +++ b/demo/lib/window.dart @@ -0,0 +1,131 @@ +import 'package:demo/constants.dart'; +import 'package:demo/window_decoration.dart'; +import 'package:flutter/material.dart'; + +class Window extends StatefulWidget { + final double initialX; + final double initialY; + final Widget child; + final VoidCallback onTap; + final bool shouldDecorate; + final double width; + final double height; + final bool isMaximized; + + const Window({ + Key? key, + required this.initialX, + required this.initialY, + required this.child, + required this.onTap, + this.shouldDecorate = true, + required this.width, + required this.height, + required this.isMaximized, + }) : super(key: key); + + @override + State createState() => _WindowState(); +} + +class _WindowState extends State { + late double initialX; + late double initialY; + late double windowWidth; + late double windowHeight; + late bool isResizingLeft; + late bool isResizingRight; + late bool isResizingTop; + late bool isResizingBottom; + + @override + void initState() { + super.initState(); + initialX = widget.initialX; + initialY = widget.initialY; + if (widget.shouldDecorate) { + windowWidth = widget.width + borderWidth * 2; + windowHeight = widget.height + borderWidth + windowDecorationHeight; + } else { + windowWidth = widget.width + borderWidth * 2; + windowHeight = widget.height + borderWidth; + } + isResizingLeft = false; + isResizingRight = false; + isResizingTop = false; + isResizingBottom = false; + } + + @override + Widget build(BuildContext context) { + final child = widget.shouldDecorate + ? WindowDecoration( + width: windowWidth + borderWidth * 2, + height: windowHeight + borderWidth + windowDecorationHeight, + child: widget.child, + ) + : widget.child; + + return Positioned( + left: widget.isMaximized ? 0 : initialX, + top: widget.isMaximized ? 0 : initialY, + child: GestureDetector( + onTap: widget.onTap, + onPanStart: (details) { + if (details.localPosition.dx <= 10) { + isResizingLeft = true; + } + if (details.localPosition.dx >= windowWidth - 10) { + isResizingRight = true; + } + if (details.localPosition.dy <= 10) { + isResizingTop = true; + } + if (details.localPosition.dy >= windowHeight - 10) { + isResizingBottom = true; + } + setState(() {}); + }, + onPanUpdate: (DragUpdateDetails details) { + setState(() { + if (isResizingLeft) { + initialX += details.delta.dx; + windowWidth += (-details.delta.dx); + } + if (isResizingRight) { + windowWidth += details.delta.dx; + } + if (isResizingTop) { + initialY += details.delta.dy; + windowHeight += (-details.delta.dy); + } + if (isResizingBottom) { + windowHeight += details.delta.dy; + } + + if (!isResizingLeft && + !isResizingRight && + !isResizingTop && + !isResizingBottom) { + initialX += details.delta.dx; + initialY += details.delta.dy; + } + }); + }, + onPanEnd: (details) { + setState(() { + isResizingLeft = false; + isResizingRight = false; + isResizingTop = false; + isResizingBottom = false; + }); + }, + child: SizedBox( + width: windowWidth, + height: windowHeight, + child: child, + ), + ), + ); + } +} diff --git a/demo/lib/window_clipper.dart b/demo/lib/window_clipper.dart new file mode 100644 index 0000000..66ae030 --- /dev/null +++ b/demo/lib/window_clipper.dart @@ -0,0 +1,29 @@ +import 'package:flutter/widgets.dart'; + +class WindowClipper extends CustomClipper { + final int offsetLeft; + final int offsetTop; + final int contentWidth; + final int contentHeight; + + WindowClipper({ + required this.offsetLeft, + required this.offsetTop, + required this.contentWidth, + required this.contentHeight, + }); + + @override + Rect getClip(Size size) { + return Rect.fromLTRB( + offsetLeft.toDouble(), + offsetTop.toDouble(), + contentWidth.toDouble() + offsetLeft, + contentHeight.toDouble() + offsetTop); + } + + @override + bool shouldReclip(covariant CustomClipper oldClipper) { + return true; + } +} diff --git a/demo/lib/window_decoration.dart b/demo/lib/window_decoration.dart new file mode 100644 index 0000000..6eb8c13 --- /dev/null +++ b/demo/lib/window_decoration.dart @@ -0,0 +1,47 @@ +import 'package:demo/constants.dart'; +import 'package:flutter/material.dart'; + +class WindowDecoration extends StatelessWidget { + final Widget child; + final double width; + final double height; + + const WindowDecoration({ + Key? key, + required this.child, + required this.width, + required this.height, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return ClipRRect( + borderRadius: systemBorderRadius, + child: Stack( + children: [ + Container( + color: windowDecorationColor, + height: height, + width: width, + ), + Padding( + padding: const EdgeInsets.only(top: windowDecorationHeight), + child: child, + ), + Positioned( + right: 0, + child: IconButton( + iconSize: windowDecorationControlSize, + icon: const Icon( + Icons.close, + color: windowDecorationControlColor, + size: windowDecorationControlSize, + ), + onPressed: () {}, + ), + ), + ], + ), + ); + } +} diff --git a/demo/pubspec.lock b/demo/pubspec.lock index c8de885..ac6397b 100644 --- a/demo/pubspec.lock +++ b/demo/pubspec.lock @@ -170,13 +170,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.4.9" - typed_data: - dependency: transitive - description: - name: typed_data - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.0" vector_math: dependency: transitive description: diff --git a/include/input.h b/include/input.h index 918c834..dc9f385 100644 --- a/include/input.h +++ b/include/input.h @@ -6,8 +6,14 @@ #include "shaders.h" #include "standard_message_codec.h" + #define FWR_MULTITOUCH_MAX 10 +enum fwr_cursor_mode { + FWR_CURSOR_PASSTHROUGH, + FWR_CURSOR_MOVE, + FWR_CURSOR_RESIZE, +}; struct fwr_input_state { uint32_t mouse_button_mask; uint32_t fl_mouse_button_mask; diff --git a/include/instance.h b/include/instance.h index 1d1a62e..24ce02f 100644 --- a/include/instance.h +++ b/include/instance.h @@ -8,6 +8,7 @@ #include #include #include +#include #include "shaders.h" #include "renderer.h" @@ -95,12 +96,24 @@ struct fwr_view { uint32_t handle; struct fwr_instance *instance; + struct wlr_xdg_toplevel *xdg_toplevel; struct wlr_xdg_surface *surface; + uint32_t parent_handle; + bool is_popup; struct wl_listener map; struct wl_listener unmap; struct wl_listener destroy; struct wl_listener commit; + + struct wl_listener request_move; + struct wl_listener request_resize; + struct wl_listener request_maximize; + struct wl_listener request_fullscreen; + struct wl_listener request_minimize; + + struct wlr_box geometry; + }; struct fwr_keyboard { diff --git a/include/messages.h b/include/messages.h index 7aeb2be..787adc5 100644 --- a/include/messages.h +++ b/include/messages.h @@ -37,4 +37,10 @@ struct surface_keyboard_key_message { int64_t timestamp; }; -bool decode_surface_keyboard_key_message(struct dart_value *value, struct surface_keyboard_key_message *out); \ No newline at end of file +bool decode_surface_keyboard_key_message(struct dart_value *value, struct surface_keyboard_key_message *out); + +struct surface_handle_focus_message { + uint32_t surface_handle; +}; + +bool decode_surface_handle_focus_message(struct dart_value *value, struct surface_handle_focus_message *out); \ No newline at end of file diff --git a/include/surface.h b/include/surface.h index 218a7ef..1071f4b 100644 --- a/include/surface.h +++ b/include/surface.h @@ -7,4 +7,5 @@ #include "instance.h" void fwr_new_xdg_surface(struct wl_listener *listener, void *data); -void fwr_handle_surface_toplevel_set_size(struct fwr_instance *instance, const FlutterPlatformMessageResponseHandle *handle, struct dart_value *args); \ No newline at end of file +void fwr_handle_surface_toplevel_set_size(struct fwr_instance *instance, const FlutterPlatformMessageResponseHandle *handle, struct dart_value *args); +void fwr_handle_surface_focus(struct fwr_instance *instance, const FlutterPlatformMessageResponseHandle *handle, struct dart_value *args); \ No newline at end of file diff --git a/src/flutter_wlroots.c b/src/flutter_wlroots.c index 05e2c32..a4d4d22 100644 --- a/src/flutter_wlroots.c +++ b/src/flutter_wlroots.c @@ -163,6 +163,10 @@ static void engine_cb_platform_message( fwr_handle_surface_toplevel_set_size(instance, engine_message->response_handle, &args); return; } + if (strcmp(method_name, "surface_focus_from_handle") == 0) { + fwr_handle_surface_focus(instance, engine_message->response_handle, &args) ; + return; + } if (strcmp(method_name, "is_compositor") == 0) { // Just send a success response. diff --git a/src/handle_map.cc b/src/handle_map.cc index a06ee96..ceb273e 100644 --- a/src/handle_map.cc +++ b/src/handle_map.cc @@ -42,4 +42,5 @@ extern "C" bool handle_map_get(handle_map *map, uint32_t handle, void **out) { *out = entry->second; return true; } -} \ No newline at end of file +} + diff --git a/src/messages.c b/src/messages.c index 47ae26a..7da86a5 100644 --- a/src/messages.c +++ b/src/messages.c @@ -99,5 +99,18 @@ bool decode_surface_toplevel_set_size_message(struct dart_value *value, struct s DECODE_INTEGER(out->size_x, &value->list.values[1]); DECODE_INTEGER(out->size_y, &value->list.values[2]); + return true; +} + +bool decode_surface_handle_focus_message(struct dart_value *value, struct surface_handle_focus_message *out) { + if (value->type != dvList) { + return false; + } + if (value->list.length != 1) { + return false; + } + + DECODE_INTEGER(out->surface_handle, &value->list.values[0]); + return true; } \ No newline at end of file diff --git a/src/surface.c b/src/surface.c index e53a3c1..ca80884 100644 --- a/src/surface.c +++ b/src/surface.c @@ -21,6 +21,7 @@ static void focus_view(struct fwr_view *view, struct wlr_surface *surface) { return; } struct fwr_instance *instance = view->instance; + instance->current_focused_view = view->handle; struct wlr_seat *seat = instance->seat; struct wlr_surface *prev_surface = seat->keyboard_state.focused_surface; if (prev_surface == surface) { @@ -65,9 +66,20 @@ static void xdg_toplevel_map(struct wl_listener *listener, void *data) { message_builder_segment_push_string(&msg_seg, "surface_map"); message_builder_segment_finish(&msg_seg); + + wlr_xdg_surface_get_geometry(view->surface, &view->geometry); + + // struct wlr_box geometry = view->surface->current.geometry; + + struct wlr_box geometry = view->geometry; + struct wlr_xdg_toplevel_state toplevel_state = view->xdg_toplevel->current; + + int surface_width = view->surface->surface->current.width; + int surface_height = view->surface->surface->current.height; + msg_seg = message_builder_segment(&msg); struct message_builder_segment arg_seg = - message_builder_segment_push_map(&msg_seg, 4); + message_builder_segment_push_map(&msg_seg, 14); message_builder_segment_push_string(&arg_seg, "handle"); wlr_log(WLR_INFO, "viewhandle %d", view->handle); message_builder_segment_push_int64(&arg_seg, view->handle); @@ -77,8 +89,51 @@ static void xdg_toplevel_map(struct wl_listener *listener, void *data) { message_builder_segment_push_int64(&arg_seg, uid); message_builder_segment_push_string(&arg_seg, "client_gid"); message_builder_segment_push_int64(&arg_seg, gid); - message_builder_segment_finish(&arg_seg); + message_builder_segment_push_string(&arg_seg, "is_popup"); + message_builder_segment_push_bool(&arg_seg, view->is_popup); + + message_builder_segment_push_string(&arg_seg, "parent_handle"); + message_builder_segment_push_int64(&arg_seg, view->parent_handle); + + // if(view->is_popup){ + // message_builder_segment_push_string(&arg_seg, "preffered_width"); + // message_builder_segment_push_int64(&arg_seg, toplevel_state.width); + + // message_builder_segment_push_string(&arg_seg, "preffered_height"); + // message_builder_segment_push_int64(&arg_seg, toplevel_state.height); + // } else { + + // this is geometry so content width + // need to add 2 more parameters - toplevel_state.width and height as whole buffer + message_builder_segment_push_string(&arg_seg, "surface_width"); + message_builder_segment_push_int64(&arg_seg, surface_width); + + message_builder_segment_push_string(&arg_seg, "surface_height"); + message_builder_segment_push_int64(&arg_seg, surface_height); + + + message_builder_segment_push_string(&arg_seg, "offset_left"); + message_builder_segment_push_int64(&arg_seg, geometry.x); + + message_builder_segment_push_string(&arg_seg, "offset_top"); + message_builder_segment_push_int64(&arg_seg, geometry.y); + + message_builder_segment_push_string(&arg_seg, "offset_right"); + message_builder_segment_push_int64(&arg_seg, surface_width - (geometry.width + geometry.x)); + + message_builder_segment_push_string(&arg_seg, "offset_bottom"); + message_builder_segment_push_int64(&arg_seg, surface_height - (geometry.height + geometry.y)); + + message_builder_segment_push_string(&arg_seg, "content_width"); + message_builder_segment_push_int64(&arg_seg, geometry.width); + + message_builder_segment_push_string(&arg_seg, "content_height"); + message_builder_segment_push_int64(&arg_seg, geometry.height); + // } + + message_builder_segment_finish(&arg_seg); + message_builder_segment_finish(&msg_seg); uint8_t *msg_buf; size_t msg_buf_len; @@ -146,17 +201,134 @@ static void xdg_toplevel_unmap(struct wl_listener *listener, void *data) { static void xdg_toplevel_destroy(struct wl_listener *listener, void *data) {} + +static void send_window_event(struct fwr_instance *instance, uint32_t handle, const char *event) { + + struct message_builder msg = message_builder_new(); + struct message_builder_segment msg_seg = message_builder_segment(&msg); + message_builder_segment_push_string(&msg_seg, event); + message_builder_segment_finish(&msg_seg); + + msg_seg = message_builder_segment(&msg); + struct message_builder_segment arg_seg = + message_builder_segment_push_map(&msg_seg, 1); + message_builder_segment_push_string(&arg_seg, "handle"); + message_builder_segment_push_int64(&arg_seg, handle); + message_builder_segment_finish(&arg_seg); + + message_builder_segment_finish(&msg_seg); + uint8_t *msg_buf; + size_t msg_buf_len; + message_builder_finish(&msg, &msg_buf, &msg_buf_len); + + FlutterPlatformMessageResponseHandle *response_handle; + instance->fl_proc_table.PlatformMessageCreateResponseHandle( + instance->engine, cb, NULL, &response_handle); + + FlutterPlatformMessage platform_message = {}; + platform_message.struct_size = sizeof(FlutterPlatformMessage); + platform_message.channel = "wlroots"; + platform_message.message = msg_buf; + platform_message.message_size = msg_buf_len; + platform_message.response_handle = response_handle; + instance->fl_proc_table.SendPlatformMessage(instance->engine, + &platform_message); + + + free(msg_buf); + +} + +static void xdg_toplevel_request_move( + struct wl_listener *listener, void *data) { + /* This event is raised when a client would like to begin an interactive + * move, typically because the user clicked on their client-side + * decorations. Note that a more sophisticated compositor should check the + * provided serial against a list of button press serials sent to this + * client, to prevent the client from requesting this whenever they want. */ + struct fwr_view *view = wl_container_of(listener, view, request_move); + // should change cursor in wlroots + // begin_interactive(view, FWR_CURSOR_MOVE, 0); + struct fwr_instance *instance = view->instance; + send_window_event(instance, view->handle, "window_move"); +} + +static void xdg_toplevel_request_resize( + struct wl_listener *listener, void *data) { + /* This event is raised when a client would like to begin an interactive + * resize, typically because the user clicked on their client-side + * decorations. Note that a more sophisticated compositor should check the + * provided serial against a list of button press serials sent to this + * client, to prevent the client from requesting this whenever they want. */ + struct wlr_xdg_toplevel_resize_event *event = data; + struct fwr_view *view = wl_container_of(listener, view, request_resize); + wlr_log(WLR_INFO, "request resize for view %d", view->handle); + // begin_interactive(view, FWR_CURSOR_RESIZE, event->edges); + struct fwr_instance *instance = view->instance; + send_window_event(instance, view->handle, "window_resize"); +} + +static void xdg_toplevel_request_maximize( + struct wl_listener *listener, void *data) { + /* This event is raised when a client would like to maximize itself, + * typically because the user clicked on the maximize button on + * client-side decorations. tinywl doesn't support maximization, but + * to conform to xdg-shell protocol we still must send a configure. + * wlr_xdg_surface_schedule_configure() is used to send an empty reply. */ + struct fwr_view *view = + wl_container_of(listener, view, request_maximize); + wlr_xdg_surface_schedule_configure(view->xdg_toplevel->base); + struct fwr_instance *instance = view->instance; + + send_window_event(instance, view->handle, "window_maximize"); + +} + +static void xdg_toplevel_request_fullscreen( + struct wl_listener *listener, void *data) { + + /* Just as with request_maximize, we must send a configure here. */ + struct fwr_view *view = + wl_container_of(listener, view, request_fullscreen); + wlr_xdg_surface_schedule_configure(view->xdg_toplevel->base); + struct fwr_instance *instance = view->instance; + send_window_event(instance, view->handle, "window_fullscreen"); +} + +static void xdg_toplevel_request_minimize( + struct wl_listener *listener, void *data) { + + /* Just as with request_minimize, we must send a configure here. */ + struct fwr_view *view = + wl_container_of(listener, view, request_minimize); + wlr_xdg_surface_schedule_configure(view->xdg_toplevel->base); + struct fwr_instance *instance = view->instance; + + send_window_event(instance, view->handle, "window_minimize"); +} + + void fwr_new_xdg_surface(struct wl_listener *listener, void *data) { struct fwr_instance *instance = wl_container_of(listener, instance, new_xdg_surface); struct wlr_xdg_surface *xdg_surface = data; + struct fwr_view *view = calloc(1, sizeof(struct fwr_view)); + xdg_surface->data = view; + view->xdg_toplevel = xdg_surface->toplevel; + + struct wlr_xdg_surface *parent = 0; + uint32_t parent_handle = -1; if (xdg_surface->role == WLR_XDG_SURFACE_ROLE_POPUP) { - return; + parent = wlr_xdg_surface_from_wlr_surface(xdg_surface->popup->parent); + struct fwr_view *parent_view = parent->data; + view->is_popup = true; + view->parent_handle = parent_view->handle; + } else { + view->is_popup = false; + view->parent_handle = -1; } - struct fwr_view *view = calloc(1, sizeof(struct fwr_view)); - view->instance = instance; view->surface = xdg_surface; @@ -167,7 +339,32 @@ void fwr_new_xdg_surface(struct wl_listener *listener, void *data) { view->destroy.notify = xdg_toplevel_destroy; wl_signal_add(&xdg_surface->events.destroy, &view->destroy); + + // only set window events if not pop-up, otherwise, exception for qt + if (xdg_surface->role != WLR_XDG_SURFACE_ROLE_POPUP) { + + struct wlr_xdg_toplevel *toplevel = xdg_surface->toplevel; + view->request_move.notify = xdg_toplevel_request_move; + wl_signal_add(&toplevel->events.request_move, &view->request_move); + + view->request_resize.notify = xdg_toplevel_request_resize; + wl_signal_add(&toplevel->events.request_resize, &view->request_resize); + + view->request_maximize.notify = xdg_toplevel_request_maximize; + wl_signal_add(&toplevel->events.request_maximize, + &view->request_maximize); + view->request_fullscreen.notify = xdg_toplevel_request_fullscreen; + wl_signal_add(&toplevel->events.request_fullscreen, + &view->request_fullscreen); + + view->request_minimize.notify = xdg_toplevel_request_minimize; + wl_signal_add(&toplevel->events.request_minimize, + &view->request_minimize); + + } + uint32_t view_handle = handle_map_add(instance->views, (void *)view); + view->handle = view_handle; } @@ -199,4 +396,36 @@ void fwr_handle_surface_toplevel_set_size( wlr_log(WLR_ERROR, "Invalid toplevel set size message"); // Send failure instance->fl_proc_table.SendPlatformMessageResponse(instance->engine, handle, NULL, 0); +} + +void fwr_handle_surface_focus( + struct fwr_instance *instance, + const FlutterPlatformMessageResponseHandle *handle, + struct dart_value *args +){ + + struct surface_handle_focus_message message; + + if (!decode_surface_handle_focus_message(args, &message)) { + goto error; + } + + struct fwr_view *view; + if (!handle_map_get(instance->views, message.surface_handle, (void**) &view)) { + // This implies a race condition of surface removal. + // We return success here. + goto success; + } + + focus_view(view, view->surface->surface); + +success: + instance->fl_proc_table.SendPlatformMessageResponse(instance->engine, handle, method_call_null_success, sizeof(method_call_null_success)); + return; + +error: + wlr_log(WLR_ERROR, "Invalid toplevel set size message"); + // Send failure + instance->fl_proc_table.SendPlatformMessageResponse(instance->engine, handle, NULL, 0); + } \ No newline at end of file diff --git a/subprojects/flutter_embedder/meson_options.txt b/subprojects/flutter_embedder/meson_options.txt index a2fb228..5dede37 100644 --- a/subprojects/flutter_embedder/meson_options.txt +++ b/subprojects/flutter_embedder/meson_options.txt @@ -1,2 +1,2 @@ -option('engine_commit', type: 'string', value: '890a5fca2e34db413be624fc83aeea8e61d42ce6', description: 'Commit of engine build to use') -option('engine_embedder_hash', type: 'string', value: '2ab7c40a7aef8ba4b5766a930e95e3d5bd92c70d8d8d6cc3f28bb690ec030b14', description: 'If present, sha256 hash to validate embedder build') +option('engine_commit', type: 'string', value: 'caaafc5604ee9172293eb84a381be6aadd660317', description: 'Commit of engine build to use') +option('engine_embedder_hash', type: 'string', value: 'e4406385e65883db8389e2e061af5bb465564974adc53aff43b1b2c159b91ec0', description: 'If present, sha256 hash to validate embedder build')