liuyuqi-dellpc 8 months ago
parent
commit
640ffd3d72

+ 9 - 0
lib/main.dart

@@ -1,6 +1,15 @@
+import 'dart:io';
+
 import 'package:flutter/material.dart';
+import 'package:window_size/window_size.dart';
 
 void main() {
+  WidgetsFlutterBinding.ensureInitialized();
+  if (Platform.isWindows) {
+    setWindowMinSize(Size(600, 400));
+    setWindowMaxSize(Size.infinite);
+  }
+
   runApp(const MyApp());
 }
 

+ 4 - 0
linux/flutter/generated_plugin_registrant.cc

@@ -6,6 +6,10 @@
 
 #include "generated_plugin_registrant.h"
 
+#include <window_size/window_size_plugin.h>
 
 void fl_register_plugins(FlPluginRegistry* registry) {
+  g_autoptr(FlPluginRegistrar) window_size_registrar =
+      fl_plugin_registry_get_registrar_for_plugin(registry, "WindowSizePlugin");
+  window_size_plugin_register_with_registrar(window_size_registrar);
 }

+ 1 - 0
linux/flutter/generated_plugins.cmake

@@ -3,6 +3,7 @@
 #
 
 list(APPEND FLUTTER_PLUGIN_LIST
+  window_size
 )
 
 set(PLUGIN_BUNDLED_LIBRARIES)

+ 7 - 0
pubspec.lock

@@ -163,5 +163,12 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "2.1.0"
+  window_size:
+    dependency: "direct main"
+    description:
+      path: window_size
+      relative: true
+    source: path
+    version: "0.1.0"
 sdks:
   dart: ">=2.12.0 <3.0.0"

+ 2 - 1
pubspec.yaml

@@ -10,7 +10,8 @@ dependencies:
   flutter:
     sdk: flutter
   cupertino_icons: ^1.0.3
-
+  window_size:
+    path: ./window_size
 dev_dependencies:
   flutter_test:
     sdk: flutter

+ 5 - 0
window_size/.gitignore

@@ -0,0 +1,5 @@
+.dart_tool
+.packages
+.flutter-plugins
+.flutter-plugins-dependencies
+pubspec.lock

+ 202 - 0
window_size/LICENSE

@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.

+ 40 - 0
window_size/README.md

@@ -0,0 +1,40 @@
+# window_size
+
+This plugin allows resizing and repositioning the window containing the Flutter
+content, as well as querying screen information.
+
+This is a prototype, and in the long term is expected to be replaced by
+[functionality within the Flutter
+framework](https://flutter.dev/go/desktop-multi-window-support).
+
+## Scope
+
+There are currently no plans to add new functionality, such as window
+minimization and maximization, to this plugin. The goals of this plugin were to:
+- unblock certain core use cases among very early adopters, and
+- validate plugin APIs in Flutter itself during early development of the desktop
+  plugin APIs.
+
+Now that those goals have been met, and the plugin APIs have been stabilized
+such that anyone can create and publish desktop Flutter plugins, new functionality
+will likely only be added here if unblocks a [Flutter top-tier
+customer](https://github.com/flutter/flutter/wiki/Issue-hygiene#customers).
+The community is encouraged to create their own plugins for other window
+manipulation features.
+
+## Supported Platforms
+
+- [x] macOS
+- [x] Windows
+- [x] Linux
+
+Not all operations have been implemented on all platforms, but the core functionality
+of resizing and repositioning is available for all three.
+
+## Use
+
+See [the plugin README](../README.md) for general instructions on using FDE plugins.
+
+### Linux
+
+Requires GTK 3.22 or later.

+ 1 - 0
window_size/analysis_options.yaml

@@ -0,0 +1 @@
+include: ../../analysis_options.yaml

+ 32 - 0
window_size/lib/src/platform_window.dart

@@ -0,0 +1,32 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+import 'dart:ui';
+
+import 'screen.dart';
+
+/// Represents a window, containing information about its size, position, and
+/// properties.
+class PlatformWindow {
+  /// Create a new window.
+  PlatformWindow(this.frame, this.scaleFactor, this.screen);
+
+  /// The frame of the screen, in screen coordinates.
+  final Rect frame;
+
+  /// The number of pixels per screen coordinate for this screen.
+  final double scaleFactor;
+
+  /// The (or a) screen containing this window, if any.
+  final Screen? screen;
+}

+ 31 - 0
window_size/lib/src/screen.dart

@@ -0,0 +1,31 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+import 'dart:ui';
+
+/// Represents a screen, containing information about its size, position, and
+/// properties.
+class Screen {
+  /// Create a new screen.
+  Screen(this.frame, this.visibleFrame, this.scaleFactor);
+
+  /// The frame of the screen, in screen coordinates.
+  final Rect frame;
+
+  /// The portion of the screen's frame that is available for use by application
+  /// windows. E.g., on macOS, this excludes the menu bar.
+  final Rect visibleFrame;
+
+  /// The number of pixels per screen coordinate for this screen.
+  final double scaleFactor;
+}

+ 249 - 0
window_size/lib/src/window_size_channel.dart

@@ -0,0 +1,249 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+import 'dart:async';
+import 'dart:ui';
+
+import 'package:flutter/services.dart';
+
+import 'platform_window.dart';
+import 'screen.dart';
+
+/// The name of the plugin's platform channel.
+const String _windowSizeChannelName = 'flutter/windowsize';
+
+/// The method name to request information about the available screens.
+///
+/// Returns a list of screen info maps; see keys below.
+const String _getScreenListMethod = 'getScreenList';
+
+/// The method name to request information about the window containing the
+/// Flutter instance.
+///
+/// Returns a list of window info maps; see keys below.
+const String _getWindowInfoMethod = 'getWindowInfo';
+
+/// The method name to set the frame of a window.
+///
+/// Takes a frame array, as documented for the value of _frameKey.
+const String _setWindowFrameMethod = 'setWindowFrame';
+
+/// The method name to set the minimum size of a window.
+///
+/// Takes a window size array, with the value is a list of two doubles:
+///   [width, height].
+///
+/// A value of zero for width or height is to be interpreted as
+/// unconstrained in that dimension.
+const String _setWindowMinimumSizeMethod = 'setWindowMinimumSize';
+
+/// The method name to set the maximum size of a window.
+///
+/// Takes a window size array, with the value is a list of two doubles:
+///   [width, height].
+///
+/// A value of `-1` for width or height is to be interpreted as
+/// unconstrained in that dimension.
+const String _setWindowMaximumSizeMethod = 'setWindowMaximumSize';
+
+/// The method name to set the window title of a window.
+const String _setWindowTitleMethod = 'setWindowTitle';
+
+/// The method name to set the window title's represented URL.
+///
+/// Only implemented for macOS. If the URL is a file URL, the
+/// window shows an icon in its title bar.
+const String _setWindowTitleRepresentedUrlMethod =
+    'setWindowTitleRepresentedUrl';
+
+/// The method name to get the minimum size of a window.
+///
+/// Returns a window size array, with the value is a list of two doubles:
+///   [width, height].
+///
+/// A value of zero for width or height is to be interpreted as
+/// unconstrained in that dimension.
+const String _getWindowMinimumSizeMethod = 'getWindowMinimumSize';
+
+/// The method name to get the maximum size of a window.
+///
+/// Returns a window size array, with the value is a list of two doubles:
+///   [width, height].
+///
+/// A value of `-1` for width or height is to be interpreted as
+/// unconstrained in that dimension.
+const String _getWindowMaximumSizeMethod = 'getWindowMaximumSize';
+
+/// The method name to set the window visibility.
+///
+/// The argument will be a boolean controlling whether or not the window should
+/// be visible.
+const String _setWindowVisibilityMethod = 'setWindowVisibility';
+
+// Keys for screen and window maps returned by _getScreenListMethod.
+
+/// The frame of a screen or window. The value is a list of four doubles:
+///   [left, top, width, height]
+const String _frameKey = 'frame';
+
+/// The frame of a screen available for use by applications. The value format
+/// is the same as _frameKey's.
+///
+/// Only used for screens.
+const String _visibleFrameKey = 'visibleFrame';
+
+/// The scale factor for a screen or window, as a double.
+///
+/// This is the number of pixels per screen coordinate, and thus the ratio
+/// between sizes as seen by Flutter and sizes in native screen coordinates.
+const String _scaleFactorKey = 'scaleFactor';
+
+/// The screen containing this window, if any. The value is a screen map, or
+/// null if the window is not visible on a screen.
+///
+/// Only used for windows.
+///
+/// If a window is on multiple screens, it is up to the platform to decide which
+/// screen to report.
+const String _screenKey = 'screen';
+
+/// A singleton object that handles the interaction with the platform channel.
+class WindowSizeChannel {
+  /// Private constructor.
+  WindowSizeChannel._();
+
+  final MethodChannel _platformChannel =
+      const MethodChannel(_windowSizeChannelName);
+
+  /// The static instance of the menu channel.
+  static final WindowSizeChannel instance = new WindowSizeChannel._();
+
+  /// Returns a list of screens.
+  Future<List<Screen>> getScreenList() async {
+    final screenList = <Screen>[];
+    final response = await _platformChannel.invokeMethod(_getScreenListMethod);
+
+    for (final screenInfo in response) {
+      screenList.add(_screenFromInfoMap(screenInfo));
+    }
+    return screenList;
+  }
+
+  /// Returns information about the window containing this Flutter instance.
+  Future<PlatformWindow> getWindowInfo() async {
+    final response = await _platformChannel.invokeMethod(_getWindowInfoMethod);
+
+    final screenInfo = response[_screenKey];
+    final screen = screenInfo == null ? null : _screenFromInfoMap(screenInfo);
+    return PlatformWindow(_rectFromLTWHList(response[_frameKey].cast<double>()),
+        response[_scaleFactorKey], screen);
+  }
+
+  /// Sets the frame of the window containing this Flutter instance, in
+  /// screen coordinates.
+  ///
+  /// The platform may adjust the frame as necessary if the provided frame would
+  /// cause significant usability issues (e.g., a window with no visible portion
+  /// that can be used to move the window).
+  void setWindowFrame(Rect frame) async {
+    assert(!frame.isEmpty, 'Cannot set window frame to an empty rect.');
+    assert(frame.isFinite, 'Cannot set window frame to a non-finite rect.');
+    await _platformChannel.invokeMethod(_setWindowFrameMethod,
+        [frame.left, frame.top, frame.width, frame.height]);
+  }
+
+  /// Sets the minimum size of the window containing this Flutter instance.
+  void setWindowMinSize(Size size) async {
+    await _platformChannel
+        .invokeMethod(_setWindowMinimumSizeMethod, [size.width, size.height]);
+  }
+
+  /// Sets the visibility of the window.
+  void setWindowVisibility({required bool visible}) async {
+    await _platformChannel.invokeMethod(_setWindowVisibilityMethod, visible);
+  }
+
+  // Window maximum size unconstrained is passed over the channel as -1.
+  double _channelRepresentationForMaxDimension(double size) {
+    return size == double.infinity ? -1 : size;
+  }
+
+  /// Sets the maximum size of the window containing this Flutter instance.
+  void setWindowMaxSize(Size size) async {
+    await _platformChannel.invokeMethod(_setWindowMaximumSizeMethod, [
+      _channelRepresentationForMaxDimension(size.width),
+      _channelRepresentationForMaxDimension(size.height),
+    ]);
+  }
+
+  /// Sets the title of the window containing this Flutter instance.
+  void setWindowTitle(String title) async {
+    await _platformChannel.invokeMapMethod(_setWindowTitleMethod, title);
+  }
+
+  /// Sets the title's represented URL of the window containing this Flutter instance.
+  void setWindowTitleRepresentedUrl(Uri file) async {
+    await _platformChannel.invokeMapMethod(
+        _setWindowTitleRepresentedUrlMethod, file.toString());
+  }
+
+  /// Gets the minimum size of the window containing this Flutter instance.
+  Future<Size> getWindowMinSize() async {
+    final response =
+        await _platformChannel.invokeMethod(_getWindowMinimumSizeMethod);
+    return _sizeFromWHList(List<double>.from(response.cast<double>()));
+  }
+
+  // Window maximum size unconstrained is passed over the channel as -1.
+  double _maxDimensionFromChannelRepresentation(double size) {
+    return size == -1 ? double.infinity : size;
+  }
+
+  /// Gets the maximum size of the window containing this Flutter instance.
+  Future<Size> getWindowMaxSize() async {
+    final response =
+        await _platformChannel.invokeMethod(_getWindowMaximumSizeMethod);
+    return _sizeFromWHList(
+      List<double>.from(
+        response.cast<double>().map(_maxDimensionFromChannelRepresentation),
+      ),
+    );
+  }
+
+  /// Given an array of the form [left, top, width, height], return the
+  /// corresponding [Rect].
+  ///
+  /// Used for frame deserialization in the platform channel.
+  Rect _rectFromLTWHList(List<double> ltwh) {
+    return Rect.fromLTWH(ltwh[0], ltwh[1], ltwh[2], ltwh[3]);
+  }
+
+  /// Given an array of the form [width, height], return the corresponding
+  /// [Size].
+  ///
+  /// Used for window size deserialization in the platform channel.
+  Size _sizeFromWHList(List<double> wh) {
+    return Size(wh[0], wh[1]);
+  }
+
+  /// Given a map of information about a screen, return the corresponding
+  /// [Screen] object.
+  ///
+  /// Used for screen deserialization in the platform channel.
+  Screen _screenFromInfoMap(Map<dynamic, dynamic> map) {
+    return Screen(
+        _rectFromLTWHList(map[_frameKey].cast<double>()),
+        _rectFromLTWHList(map[_visibleFrameKey].cast<double>()),
+        map[_scaleFactorKey]);
+  }
+}

+ 89 - 0
window_size/lib/src/window_size_utils.dart

@@ -0,0 +1,89 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+import 'dart:async';
+import 'dart:ui';
+
+import 'platform_window.dart';
+import 'screen.dart';
+import 'window_size_channel.dart';
+
+/// Returns a list of [Screen]s for the current screen configuration.
+///
+/// It is possible for this list to be empty, if the machine is running in
+/// a headless mode.
+Future<List<Screen>> getScreenList() async {
+  return await WindowSizeChannel.instance.getScreenList();
+}
+
+/// Returns the [Screen] showing the window that contains this Flutter instance.
+///
+/// If the window is not being displayed, returns null. If the window is being
+/// displayed on multiple screens, the platform can return any of those screens.
+Future<Screen?> getCurrentScreen() async {
+  final windowInfo = await WindowSizeChannel.instance.getWindowInfo();
+  return windowInfo.screen;
+}
+
+/// Returns information about the window containing this Flutter instance.
+Future<PlatformWindow> getWindowInfo() async {
+  return await WindowSizeChannel.instance.getWindowInfo();
+}
+
+/// Sets the frame of the window containing this Flutter instance, in
+/// screen coordinates.
+///
+/// The platform may adjust the frame as necessary if the provided frame would
+/// cause significant usability issues (e.g., a window with no visible portion
+/// that can be used to move the window).
+void setWindowFrame(Rect frame) async {
+  WindowSizeChannel.instance.setWindowFrame(frame);
+}
+
+/// Sets the minimum [Size] of the window containing this Flutter instance.
+void setWindowMinSize(Size size) async {
+  WindowSizeChannel.instance.setWindowMinSize(size);
+}
+
+/// Sets the maximum [Size] of the window containing this Flutter instance.
+void setWindowMaxSize(Size size) async {
+  WindowSizeChannel.instance.setWindowMaxSize(size);
+}
+
+/// Sets the window title, as a [String], of the window containing this Flutter instance.
+void setWindowTitle(String title) async {
+  WindowSizeChannel.instance.setWindowTitle(title);
+}
+
+/// Shows or hides the window.
+void setWindowVisibility({required bool visible}) async {
+  WindowSizeChannel.instance.setWindowVisibility(visible: visible);
+}
+
+/// Sets the window title's represented [Uri], of the window containing this Flutter instance.
+///
+/// Only implemented for macOS. If the URL is a file URL, the
+/// window shows an icon in its title bar.
+void setWindowTitleRepresentedUrl(Uri url) async {
+  WindowSizeChannel.instance.setWindowTitleRepresentedUrl(url);
+}
+
+/// Gets the minimum [Size] of the window containing this Flutter instance.
+Future<Size> getWindowMinSize() async {
+  return WindowSizeChannel.instance.getWindowMinSize();
+}
+
+/// Gets the maximum [Size] of the window containing this Flutter instance.
+Future<Size> getWindowMaxSize() async {
+  return WindowSizeChannel.instance.getWindowMaxSize();
+}

+ 16 - 0
window_size/lib/window_size.dart

@@ -0,0 +1,16 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+export 'src/platform_window.dart';
+export 'src/screen.dart';
+export 'src/window_size_utils.dart';

+ 17 - 0
window_size/linux/CMakeLists.txt

@@ -0,0 +1,17 @@
+cmake_minimum_required(VERSION 3.10)
+set(PROJECT_NAME "window_size")
+project(${PROJECT_NAME} LANGUAGES CXX)
+
+set(PLUGIN_NAME "${PROJECT_NAME}_plugin")
+
+add_library(${PLUGIN_NAME} SHARED
+  "${PLUGIN_NAME}.cc"
+)
+apply_standard_settings(${PLUGIN_NAME})
+set_target_properties(${PLUGIN_NAME} PROPERTIES
+  CXX_VISIBILITY_PRESET hidden)
+target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL)
+target_include_directories(${PLUGIN_NAME} INTERFACE
+  "${CMAKE_CURRENT_SOURCE_DIR}/include")
+target_link_libraries(${PLUGIN_NAME} PRIVATE flutter)
+target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::GTK)

+ 40 - 0
window_size/linux/include/window_size/window_size_plugin.h

@@ -0,0 +1,40 @@
+// Copyright 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#ifndef PLUGINS_WINDOW_SIZE_LINUX_WINDOW_SIZE_PLUGIN_H_
+#define PLUGINS_WINDOW_SIZE_LINUX_WINDOW_SIZE_PLUGIN_H_
+
+// A plugin to allow resizing the window.
+
+#include <flutter_linux/flutter_linux.h>
+
+G_BEGIN_DECLS
+
+#ifdef FLUTTER_PLUGIN_IMPL
+#define FLUTTER_PLUGIN_EXPORT __attribute__((visibility("default")))
+#else
+#define FLUTTER_PLUGIN_EXPORT
+#endif
+
+G_DECLARE_FINAL_TYPE(FlWindowSizePlugin, fl_window_size_plugin, FL,
+                     WINDOW_SIZE_PLUGIN, GObject)
+
+FLUTTER_PLUGIN_EXPORT FlWindowSizePlugin* fl_window_size_plugin_new(
+    FlPluginRegistrar* registrar);
+
+FLUTTER_PLUGIN_EXPORT void window_size_plugin_register_with_registrar(
+    FlPluginRegistrar* registrar);
+
+G_END_DECLS
+
+#endif  // PLUGINS_WINDOW_SIZE_LINUX_WINDOW_SIZE_PLUGIN_H_

+ 411 - 0
window_size/linux/window_size_plugin.cc

@@ -0,0 +1,411 @@
+// Copyright 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#include "include/window_size/window_size_plugin.h"
+
+#include <flutter_linux/flutter_linux.h>
+#include <gtk/gtk.h>
+
+// See window_size_channel.dart for documentation.
+const char kChannelName[] = "flutter/windowsize";
+const char kBadArgumentsError[] = "Bad Arguments";
+const char kNoScreenError[] = "No Screen";
+const char kGetScreenListMethod[] = "getScreenList";
+const char kGetWindowInfoMethod[] = "getWindowInfo";
+const char kSetWindowFrameMethod[] = "setWindowFrame";
+const char kSetWindowMinimumSizeMethod[] = "setWindowMinimumSize";
+const char kSetWindowMaximumSizeMethod[] = "setWindowMaximumSize";
+const char kSetWindowTitleMethod[] = "setWindowTitle";
+const char ksetWindowVisibilityMethod[] = "setWindowVisibility";
+const char kGetWindowMinimumSizeMethod[] = "getWindowMinimumSize";
+const char kGetWindowMaximumSizeMethod[] = "getWindowMaximumSize";
+const char kFrameKey[] = "frame";
+const char kVisibleFrameKey[] = "visibleFrame";
+const char kScaleFactorKey[] = "scaleFactor";
+const char kScreenKey[] = "screen";
+
+struct _FlWindowSizePlugin {
+  GObject parent_instance;
+
+  FlPluginRegistrar* registrar;
+
+  // Connection to Flutter engine.
+  FlMethodChannel* channel;
+
+  // Requested window geometry.
+  GdkGeometry window_geometry;
+};
+
+G_DEFINE_TYPE(FlWindowSizePlugin, fl_window_size_plugin, g_object_get_type())
+
+// Gets the window being controlled.
+GtkWindow* get_window(FlWindowSizePlugin* self) {
+  FlView* view = fl_plugin_registrar_get_view(self->registrar);
+  if (view == nullptr) return nullptr;
+
+  return GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(view)));
+}
+
+// Gets the display connection.
+GdkDisplay* get_display(FlWindowSizePlugin* self) {
+  FlView* view = fl_plugin_registrar_get_view(self->registrar);
+  if (view == nullptr) return nullptr;
+
+  return gtk_widget_get_display(GTK_WIDGET(view));
+}
+
+// Converts frame dimensions into the Flutter representation.
+FlValue* make_frame_value(gint x, gint y, gint width, gint height) {
+  g_autoptr(FlValue) value = fl_value_new_list();
+
+  fl_value_append_take(value, fl_value_new_float(x));
+  fl_value_append_take(value, fl_value_new_float(y));
+  fl_value_append_take(value, fl_value_new_float(width));
+  fl_value_append_take(value, fl_value_new_float(height));
+
+  return fl_value_ref(value);
+}
+
+// Converts monitor information into the Flutter representation.
+FlValue* make_monitor_value(GdkMonitor* monitor) {
+  g_autoptr(FlValue) value = fl_value_new_map();
+
+  GdkRectangle frame;
+  gdk_monitor_get_geometry(monitor, &frame);
+  fl_value_set_string_take(
+      value, kFrameKey,
+      make_frame_value(frame.x, frame.y, frame.width, frame.height));
+
+  gdk_monitor_get_workarea(monitor, &frame);
+  fl_value_set_string_take(
+      value, kVisibleFrameKey,
+      make_frame_value(frame.x, frame.y, frame.width, frame.height));
+
+  gint scale_factor = gdk_monitor_get_scale_factor(monitor);
+  fl_value_set_string_take(value, kScaleFactorKey,
+                           fl_value_new_float(scale_factor));
+
+  return fl_value_ref(value);
+}
+
+// Gets the list of current screens.
+static FlMethodResponse* get_screen_list(FlWindowSizePlugin* self) {
+  g_autoptr(FlValue) screens = fl_value_new_list();
+
+  GdkDisplay* display = get_display(self);
+  if (display == nullptr) {
+    return FL_METHOD_RESPONSE(
+        fl_method_error_response_new(kNoScreenError, nullptr, nullptr));
+  }
+
+  gint n_monitors = gdk_display_get_n_monitors(display);
+  for (gint i = 0; i < n_monitors; i++) {
+    GdkMonitor* monitor = gdk_display_get_monitor(display, i);
+    fl_value_append_take(screens, make_monitor_value(monitor));
+  }
+
+  return FL_METHOD_RESPONSE(fl_method_success_response_new(screens));
+}
+
+// Gets information about the Flutter window.
+static FlMethodResponse* get_window_info(FlWindowSizePlugin* self) {
+  GtkWindow* window = get_window(self);
+  if (window == nullptr) {
+    return FL_METHOD_RESPONSE(
+        fl_method_error_response_new(kNoScreenError, nullptr, nullptr));
+  }
+
+  g_autoptr(FlValue) window_info = fl_value_new_map();
+
+  gint x, y, width, height;
+  gtk_window_get_position(window, &x, &y);
+  gtk_window_get_size(window, &width, &height);
+  fl_value_set_string_take(window_info, kFrameKey,
+                           make_frame_value(x, y, width, height));
+
+  // Get the monitor this window is inside, or the primary monitor if doesn't
+  // appear to be in any.
+  GdkDisplay* display = get_display(self);
+  GdkMonitor* monitor_with_window = gdk_display_get_primary_monitor(display);
+  int n_monitors = gdk_display_get_n_monitors(display);
+  for (int i = 0; i < n_monitors; i++) {
+    GdkMonitor* monitor = gdk_display_get_monitor(display, i);
+
+    GdkRectangle frame;
+    gdk_monitor_get_geometry(monitor, &frame);
+    if ((x >= frame.x && x <= frame.x + frame.width) &&
+        (y >= frame.y && y <= frame.y + frame.width)) {
+      monitor_with_window = monitor;
+      break;
+    }
+  }
+  fl_value_set_string_take(window_info, kScreenKey,
+                           make_monitor_value(monitor_with_window));
+
+  gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(window));
+  fl_value_set_string_take(window_info, kScaleFactorKey,
+                           fl_value_new_float(scale_factor));
+
+  return FL_METHOD_RESPONSE(fl_method_success_response_new(window_info));
+}
+
+// Sets the window position and dimensions.
+static FlMethodResponse* set_window_frame(FlWindowSizePlugin* self,
+                                          FlValue* args) {
+  if (fl_value_get_type(args) != FL_VALUE_TYPE_LIST ||
+      fl_value_get_length(args) != 4) {
+    return FL_METHOD_RESPONSE(fl_method_error_response_new(
+        kBadArgumentsError, "Expected 4-element list", nullptr));
+  }
+  double x = fl_value_get_float(fl_value_get_list_value(args, 0));
+  double y = fl_value_get_float(fl_value_get_list_value(args, 1));
+  double width = fl_value_get_float(fl_value_get_list_value(args, 2));
+  double height = fl_value_get_float(fl_value_get_list_value(args, 3));
+
+  GtkWindow* window = get_window(self);
+  if (window == nullptr) {
+    return FL_METHOD_RESPONSE(
+        fl_method_error_response_new(kNoScreenError, nullptr, nullptr));
+  }
+
+  gtk_window_move(window, static_cast<gint>(x), static_cast<gint>(y));
+  gtk_window_resize(window, static_cast<gint>(width),
+                    static_cast<gint>(height));
+
+  return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
+}
+
+// Send updated window geometry to GTK.
+static void update_window_geometry(FlWindowSizePlugin* self) {
+  gtk_window_set_geometry_hints(
+      get_window(self), nullptr, &self->window_geometry,
+      static_cast<GdkWindowHints>(GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE));
+}
+
+// Sets the window minimum size.
+static FlMethodResponse* set_window_minimum_size(FlWindowSizePlugin* self,
+                                                 FlValue* args) {
+  if (fl_value_get_type(args) != FL_VALUE_TYPE_LIST ||
+      fl_value_get_length(args) != 2) {
+    return FL_METHOD_RESPONSE(fl_method_error_response_new(
+        kBadArgumentsError, "Expected 2-element list", nullptr));
+  }
+  double width = fl_value_get_float(fl_value_get_list_value(args, 0));
+  double height = fl_value_get_float(fl_value_get_list_value(args, 1));
+
+  if (get_window(self) == nullptr) {
+    return FL_METHOD_RESPONSE(
+        fl_method_error_response_new(kNoScreenError, nullptr, nullptr));
+  }
+
+  if (width >= 0 && height >= 0) {
+    self->window_geometry.min_width = static_cast<gint>(width);
+    self->window_geometry.min_height = static_cast<gint>(height);
+  }
+
+  update_window_geometry(self);
+
+  return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
+}
+
+// Sets the window maximum size.
+static FlMethodResponse* set_window_maximum_size(FlWindowSizePlugin* self,
+                                                 FlValue* args) {
+  if (fl_value_get_type(args) != FL_VALUE_TYPE_LIST ||
+      fl_value_get_length(args) != 2) {
+    return FL_METHOD_RESPONSE(fl_method_error_response_new(
+        kBadArgumentsError, "Expected 2-element list", nullptr));
+  }
+  double width = fl_value_get_float(fl_value_get_list_value(args, 0));
+  double height = fl_value_get_float(fl_value_get_list_value(args, 1));
+
+  if (get_window(self) == nullptr) {
+    return FL_METHOD_RESPONSE(
+        fl_method_error_response_new(kNoScreenError, nullptr, nullptr));
+  }
+
+  self->window_geometry.max_width = static_cast<gint>(width);
+  self->window_geometry.max_height = static_cast<gint>(height);
+
+  // Flutter uses -1 as unconstrained, GTK doesn't have an unconstrained value.
+  if (self->window_geometry.max_width < 0) {
+    self->window_geometry.max_width = G_MAXINT;
+  }
+  if (self->window_geometry.max_height < 0) {
+    self->window_geometry.max_height = G_MAXINT;
+  }
+
+  update_window_geometry(self);
+
+  return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
+}
+
+// Sets the window title.
+static FlMethodResponse* set_window_title(FlWindowSizePlugin* self,
+                                          FlValue* args) {
+  if (fl_value_get_type(args) != FL_VALUE_TYPE_STRING) {
+    return FL_METHOD_RESPONSE(fl_method_error_response_new(
+        kBadArgumentsError, "Expected string", nullptr));
+  }
+
+  GtkWindow* window = get_window(self);
+  if (window == nullptr) {
+    return FL_METHOD_RESPONSE(
+        fl_method_error_response_new(kNoScreenError, nullptr, nullptr));
+  }
+  gtk_window_set_title(window, fl_value_get_string(args));
+
+  return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
+}
+
+// Sets the window visibility.
+static FlMethodResponse* set_window_visible(FlWindowSizePlugin* self,
+                                          FlValue* args) {
+  if (fl_value_get_type(args) != FL_VALUE_TYPE_BOOL) {
+    return FL_METHOD_RESPONSE(fl_method_error_response_new(
+        kBadArgumentsError, "Expected bool", nullptr));
+  }
+
+  GtkWindow* window = get_window(self);
+  if (window == nullptr) {
+    return FL_METHOD_RESPONSE(
+        fl_method_error_response_new(kNoScreenError, nullptr, nullptr));
+  }
+  if (fl_value_get_bool(args)) {
+    gtk_widget_show(GTK_WIDGET(window));
+  } else {
+    gtk_widget_hide(GTK_WIDGET(window));
+  }
+
+  return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
+}
+
+// Gets the window minimum size.
+static FlMethodResponse* get_window_minimum_size(FlWindowSizePlugin* self) {
+  g_autoptr(FlValue) size = fl_value_new_list();
+
+  gint min_width = self->window_geometry.min_width;
+  gint min_height = self->window_geometry.min_height;
+
+  // GTK uses -1 for the requisition size (the size GTK has calculated).
+  // Report this as zero (smallest possible) so this doesn't look like Size(-1, -1).
+  if (min_width < 0) {
+    min_width = 0;
+  }
+  if (min_height < 0) {
+    min_height = 0;
+  }
+
+  fl_value_append_take(size, fl_value_new_float(min_width));
+  fl_value_append_take(size, fl_value_new_float(min_height));
+
+  return FL_METHOD_RESPONSE(fl_method_success_response_new(size));
+}
+
+// Gets the window maximum size.
+static FlMethodResponse* get_window_maximum_size(FlWindowSizePlugin* self) {
+  g_autoptr(FlValue) size = fl_value_new_list();
+
+  gint max_width = self->window_geometry.max_width;
+  gint max_height = self->window_geometry.max_height;
+
+  // Flutter uses -1 as unconstrained, GTK doesn't have an unconstrained value.
+  if (max_width == G_MAXINT) {
+    max_width = -1;
+  }
+  if (max_height == G_MAXINT) {
+    max_height = -1;
+  }
+
+  fl_value_append_take(size, fl_value_new_float(max_width));
+  fl_value_append_take(size, fl_value_new_float(max_height));
+
+  return FL_METHOD_RESPONSE(fl_method_success_response_new(size));
+}
+
+// Called when a method call is received from Flutter.
+static void method_call_cb(FlMethodChannel* channel, FlMethodCall* method_call,
+                           gpointer user_data) {
+  FlWindowSizePlugin* self = FL_WINDOW_SIZE_PLUGIN(user_data);
+
+  const gchar* method = fl_method_call_get_name(method_call);
+  FlValue* args = fl_method_call_get_args(method_call);
+
+  g_autoptr(FlMethodResponse) response = nullptr;
+  if (strcmp(method, kGetScreenListMethod) == 0) {
+    response = get_screen_list(self);
+  } else if (strcmp(method, kGetWindowInfoMethod) == 0) {
+    response = get_window_info(self);
+  } else if (strcmp(method, kSetWindowFrameMethod) == 0) {
+    response = set_window_frame(self, args);
+  } else if (strcmp(method, kSetWindowMinimumSizeMethod) == 0) {
+    response = set_window_minimum_size(self, args);
+  } else if (strcmp(method, kSetWindowMaximumSizeMethod) == 0) {
+    response = set_window_maximum_size(self, args);
+  } else if (strcmp(method, kSetWindowTitleMethod) == 0) {
+    response = set_window_title(self, args);
+  } else if (strcmp(method, ksetWindowVisibilityMethod) == 0) {
+    response = set_window_visible(self, args);
+  } else if (strcmp(method, kGetWindowMinimumSizeMethod) == 0) {
+    response = get_window_minimum_size(self);
+  } else if (strcmp(method, kGetWindowMaximumSizeMethod) == 0) {
+    response = get_window_maximum_size(self);
+  } else {
+    response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new());
+  }
+
+  g_autoptr(GError) error = nullptr;
+  if (!fl_method_call_respond(method_call, response, &error))
+    g_warning("Failed to send method call response: %s", error->message);
+}
+
+static void fl_window_size_plugin_dispose(GObject* object) {
+  FlWindowSizePlugin* self = FL_WINDOW_SIZE_PLUGIN(object);
+
+  g_clear_object(&self->registrar);
+  g_clear_object(&self->channel);
+
+  G_OBJECT_CLASS(fl_window_size_plugin_parent_class)->dispose(object);
+}
+
+static void fl_window_size_plugin_class_init(FlWindowSizePluginClass* klass) {
+  G_OBJECT_CLASS(klass)->dispose = fl_window_size_plugin_dispose;
+}
+
+static void fl_window_size_plugin_init(FlWindowSizePlugin* self) {
+  self->window_geometry.min_width = -1;
+  self->window_geometry.min_height = -1;
+  self->window_geometry.max_width = G_MAXINT;
+  self->window_geometry.max_height = G_MAXINT;
+}
+
+FlWindowSizePlugin* fl_window_size_plugin_new(FlPluginRegistrar* registrar) {
+  FlWindowSizePlugin* self = FL_WINDOW_SIZE_PLUGIN(
+      g_object_new(fl_window_size_plugin_get_type(), nullptr));
+
+  self->registrar = FL_PLUGIN_REGISTRAR(g_object_ref(registrar));
+
+  g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
+  self->channel =
+      fl_method_channel_new(fl_plugin_registrar_get_messenger(registrar),
+                            kChannelName, FL_METHOD_CODEC(codec));
+  fl_method_channel_set_method_call_handler(self->channel, method_call_cb,
+                                            g_object_ref(self), g_object_unref);
+
+  return self;
+}
+
+void window_size_plugin_register_with_registrar(FlPluginRegistrar* registrar) {
+  FlWindowSizePlugin* plugin = fl_window_size_plugin_new(registrar);
+  g_object_unref(plugin);
+}

+ 25 - 0
window_size/macos/Classes/FLEWindowSizePlugin.h

@@ -0,0 +1,25 @@
+// Copyright 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#import <Foundation/Foundation.h>
+
+#import <FlutterMacOS/FlutterMacOS.h>
+
+/**
+ * A FlutterPlugin to manage macOS's shared NSColorPanel singleton.
+ * Responsible for managing the panel's display state and sending selected color data to Flutter.
+ */
+@interface FLEWindowSizePlugin : NSObject <FlutterPlugin, NSWindowDelegate>
+
+@end

+ 214 - 0
window_size/macos/Classes/FLEWindowSizePlugin.mm

@@ -0,0 +1,214 @@
+// Copyright 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#import "FLEWindowSizePlugin.h"
+
+#import <AppKit/AppKit.h>
+
+namespace {
+
+// See window_size_channel.dart for documentation.
+NSString *const kChannelName = @"flutter/windowsize";
+NSString *const kGetScreenListMethod = @"getScreenList";
+NSString *const kGetWindowInfoMethod = @"getWindowInfo";
+NSString *const kSetWindowFrameMethod = @"setWindowFrame";
+NSString *const kSetWindowMinimumSizeMethod = @"setWindowMinimumSize";
+NSString *const kSetWindowMaximumSizeMethod = @"setWindowMaximumSize";
+NSString *const kSetWindowTitleMethod = @"setWindowTitle";
+NSString *const kSetWindowTitleRepresentedUrlMethod = @"setWindowTitleRepresentedUrl";
+NSString *const kSetWindowVisibilityMethod = @"setWindowVisibility";
+NSString *const kGetWindowMinimumSizeMethod = @"getWindowMinimumSize";
+NSString *const kGetWindowMaximumSizeMethod = @"getWindowMaximumSize";
+NSString *const kFrameKey = @"frame";
+NSString *const kVisibleFrameKey = @"visibleFrame";
+NSString *const kScaleFactorKey = @"scaleFactor";
+NSString *const kScreenKey = @"screen";
+
+/**
+ * Returns the max Y coordinate across all screens.
+ */
+CGFloat GetMaxScreenY() {
+  CGFloat maxY = 0;
+  for (NSScreen *screen in [NSScreen screens]) {
+    maxY = MAX(maxY, CGRectGetMaxY(screen.frame));
+  }
+  return maxY;
+}
+
+/**
+ * Given |frame| in screen coordinates, returns a frame flipped relative to
+ * GetMaxScreenY().
+ */
+NSRect GetFlippedRect(NSRect frame) {
+  CGFloat maxY = GetMaxScreenY();
+  return NSMakeRect(frame.origin.x, maxY - frame.origin.y - frame.size.height, frame.size.width,
+                    frame.size.height);
+}
+
+/**
+ * Converts the channel representation for unconstrained maximum size `-1` to Cocoa's specific
+ * maximum size of `FLT_MAX`.
+ */
+double MaxDimensionFromChannelRepresentation(double size) { return size == -1.0 ? FLT_MAX : size; }
+
+/**
+ * Converts Cocoa's specific maximum size of `FLT_MAX` to channel representation for unconstrained
+ * maximum size `-1`.
+ */
+double ChannelRepresentationForMaxDimension(double size) { return size == FLT_MAX ? -1 : size; }
+
+}  // namespace
+
+@interface FLEWindowSizePlugin ()
+
+/// The view displaying Flutter content.
+@property(nonatomic, readonly) NSView *flutterView;
+
+/**
+ * Extracts information from |screen| and returns the serializable form expected
+ * by the platform channel.
+ */
+- (NSDictionary *)platformChannelRepresentationForScreen:(NSScreen *)screen;
+
+/**
+ * Extracts information from |window| and returns the serializable form expected
+ * by the platform channel.
+ */
+- (NSDictionary *)platformChannelRepresentationForWindow:(NSWindow *)window;
+
+/**
+ * Returns the serializable form of |frame| expected by the platform channel.
+ */
+- (NSArray *)platformChannelRepresentationForFrame:(NSRect)frame;
+
+@end
+
+@implementation FLEWindowSizePlugin {
+  // The channel used to communicate with Flutter.
+  FlutterMethodChannel *_channel;
+
+  // A reference to the registrar holding the NSView used by the plugin. Holding a reference
+  // since the view might be nil at the time the plugin is created.
+  id<FlutterPluginRegistrar> _registrar;
+}
+
+- (NSView *)flutterView {
+  return _registrar.view;
+}
+
++ (void)registerWithRegistrar:(id<FlutterPluginRegistrar>)registrar {
+  FlutterMethodChannel *channel = [FlutterMethodChannel methodChannelWithName:kChannelName
+                                                              binaryMessenger:registrar.messenger];
+  FLEWindowSizePlugin *instance = [[FLEWindowSizePlugin alloc] initWithChannel:channel
+                                                                     registrar:registrar];
+  [registrar addMethodCallDelegate:instance channel:channel];
+}
+
+- (instancetype)initWithChannel:(FlutterMethodChannel *)channel
+                      registrar:(id<FlutterPluginRegistrar>)registrar {
+  self = [super init];
+  if (self) {
+    _channel = channel;
+    _registrar = registrar;
+  }
+  return self;
+}
+
+/**
+ * Handles platform messages generated by the Flutter framework on the platform channel.
+ */
+- (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result {
+  id methodResult = nil;
+  if ([call.method isEqualToString:kGetScreenListMethod]) {
+    NSMutableArray<NSDictionary *> *screenList =
+        [NSMutableArray arrayWithCapacity:[NSScreen screens].count];
+    for (NSScreen *screen in [NSScreen screens]) {
+      [screenList addObject:[self platformChannelRepresentationForScreen:screen]];
+    }
+    methodResult = screenList;
+  } else if ([call.method isEqualToString:kGetWindowInfoMethod]) {
+    methodResult = [self platformChannelRepresentationForWindow:self.flutterView.window];
+  } else if ([call.method isEqualToString:kSetWindowFrameMethod]) {
+    NSArray<NSNumber *> *arguments = call.arguments;
+    [self.flutterView.window
+        setFrame:GetFlippedRect(NSMakeRect(arguments[0].doubleValue, arguments[1].doubleValue,
+                                           arguments[2].doubleValue, arguments[3].doubleValue))
+         display:YES];
+    methodResult = nil;
+  } else if ([call.method isEqualToString:kSetWindowMinimumSizeMethod]) {
+    NSArray<NSNumber *> *arguments = call.arguments;
+    self.flutterView.window.minSize =
+        NSMakeSize(arguments[0].doubleValue, arguments[1].doubleValue);
+    methodResult = nil;
+  } else if ([call.method isEqualToString:kSetWindowMaximumSizeMethod]) {
+    NSArray<NSNumber *> *arguments = call.arguments;
+    self.flutterView.window.maxSize =
+        NSMakeSize(MaxDimensionFromChannelRepresentation(arguments[0].doubleValue),
+                   MaxDimensionFromChannelRepresentation(arguments[1].doubleValue));
+    methodResult = nil;
+  } else if ([call.method isEqualToString:kGetWindowMinimumSizeMethod]) {
+    NSSize size = self.flutterView.window.minSize;
+    methodResult = @[ @(size.width), @(size.height) ];
+  } else if ([call.method isEqualToString:kGetWindowMaximumSizeMethod]) {
+    NSSize size = self.flutterView.window.maxSize;
+    methodResult = @[
+      @(ChannelRepresentationForMaxDimension(size.width)),
+      @(ChannelRepresentationForMaxDimension(size.height))
+    ];
+  } else if ([call.method isEqualToString:kSetWindowTitleMethod]) {
+    NSString *title = call.arguments;
+    self.flutterView.window.title = title;
+    methodResult = nil;
+  } else if ([call.method isEqualToString:kSetWindowTitleRepresentedUrlMethod]) {
+    NSURL *representedURL = [NSURL URLWithString:call.arguments];
+    self.flutterView.window.representedURL = representedURL;
+    methodResult = nil;
+  } else if ([call.method isEqualToString:kSetWindowVisibilityMethod]) {
+    bool visible = [call.arguments boolValue];
+    if (visible) {
+      [self.flutterView.window makeKeyAndOrderFront:self];
+    } else {
+      [self.flutterView.window orderOut:self];
+    }
+    methodResult = nil;
+  } else {
+    methodResult = FlutterMethodNotImplemented;
+  }
+  result(methodResult);
+}
+
+#pragma mark - Private methods
+
+- (NSDictionary *)platformChannelRepresentationForScreen:(NSScreen *)screen {
+  return @{
+    kFrameKey : [self platformChannelRepresentationForFrame:GetFlippedRect(screen.frame)],
+    kVisibleFrameKey :
+        [self platformChannelRepresentationForFrame:GetFlippedRect(screen.visibleFrame)],
+    kScaleFactorKey : @(screen.backingScaleFactor),
+  };
+}
+
+- (NSDictionary *)platformChannelRepresentationForWindow:(NSWindow *)window {
+  return @{
+    kFrameKey : [self platformChannelRepresentationForFrame:GetFlippedRect(window.frame)],
+    kScreenKey : [self platformChannelRepresentationForScreen:window.screen],
+    kScaleFactorKey : @(window.backingScaleFactor),
+  };
+}
+
+- (NSArray *)platformChannelRepresentationForFrame:(NSRect)frame {
+  return @[ @(frame.origin.x), @(frame.origin.y), @(frame.size.width), @(frame.size.height) ];
+}
+
+@end

+ 22 - 0
window_size/macos/Classes/WindowSizePlugin.swift

@@ -0,0 +1,22 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import FlutterMacOS
+import Foundation
+
+public class WindowSizePlugin: NSObject, FlutterPlugin {
+  public static func register(with registrar: FlutterPluginRegistrar) {
+    FLEWindowSizePlugin.register(with: registrar)
+  }
+}

+ 21 - 0
window_size/macos/window_size.podspec

@@ -0,0 +1,21 @@
+#
+# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html
+#
+Pod::Spec.new do |s|
+  s.name             = 'window_size'
+  s.version          = '0.0.2'
+  s.summary          = 'Allows resizing and repositioning the window containing Flutter.'
+  s.description      = <<-DESC
+Allows resizing and repositioning the window containing Flutter.
+                       DESC
+  s.homepage         = 'https://github.com/google/flutter-desktop-embedding/tree/master/plugins/window_size'
+  s.license          = { :file => '../LICENSE' }
+  s.author           = { 'Flutter Desktop Embedding Developers' => 'flutter-desktop-embedding-dev@googlegroups.com' }
+  s.source           = { :path => '.' }
+  s.source_files     = 'Classes/**/*'
+  s.dependency 'FlutterMacOS'
+
+  s.platform = :osx
+  s.osx.deployment_target = '10.11'
+end
+

+ 24 - 0
window_size/pubspec.yaml

@@ -0,0 +1,24 @@
+name: window_size
+description: Allows resizing and repositioning the window containing Flutter.
+version: 0.1.0
+
+# Do not publish this plugin. See:
+# https://github.com/google/flutter-desktop-embedding/blob/master/plugins/README.md#using-plugins
+publish_to: none
+
+flutter:
+  plugin:
+    platforms:
+      linux:
+        pluginClass: WindowSizePlugin
+      macos:
+        pluginClass: WindowSizePlugin
+      windows:
+        pluginClass: WindowSizePlugin
+
+environment:
+  sdk: '>=2.12.0-0 <3.0.0'
+
+dependencies:
+  flutter:
+    sdk: flutter

+ 17 - 0
window_size/windows/.gitignore

@@ -0,0 +1,17 @@
+flutter/
+
+# Visual Studio user-specific files.
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# Visual Studio build-related files.
+x64/
+x86/
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/

+ 23 - 0
window_size/windows/CMakeLists.txt

@@ -0,0 +1,23 @@
+cmake_minimum_required(VERSION 3.10)
+set(PROJECT_NAME "window_size")
+project(${PROJECT_NAME} LANGUAGES CXX)
+
+set(PLUGIN_NAME "${PROJECT_NAME}_plugin")
+
+add_library(${PLUGIN_NAME} SHARED
+  "${PLUGIN_NAME}.cpp"
+)
+apply_standard_settings(${PLUGIN_NAME})
+set_target_properties(${PLUGIN_NAME} PROPERTIES
+  CXX_VISIBILITY_PRESET hidden)
+target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL)
+target_compile_definitions(${PLUGIN_NAME} PRIVATE _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING)
+target_include_directories(${PLUGIN_NAME} INTERFACE
+  "${CMAKE_CURRENT_SOURCE_DIR}/include")
+target_link_libraries(${PLUGIN_NAME} PRIVATE flutter flutter_wrapper_plugin)
+
+# List of absolute paths to libraries that should be bundled with the plugin
+set(window_size_bundled_libraries
+  ""
+  PARENT_SCOPE
+)

+ 38 - 0
window_size/windows/include/window_size/window_size_plugin.h

@@ -0,0 +1,38 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#ifndef PLUGINS_WINDOW_SIZE_WINDOWS_WINDOW_SIZE_PLUGIN_H_
+#define PLUGINS_WINDOW_SIZE_WINDOWS_WINDOW_SIZE_PLUGIN_H_
+
+// A plugin to allow resizing the window.
+
+#include <flutter_plugin_registrar.h>
+
+#ifdef FLUTTER_PLUGIN_IMPL
+#define FLUTTER_PLUGIN_EXPORT __declspec(dllexport)
+#else
+#define FLUTTER_PLUGIN_EXPORT __declspec(dllimport)
+#endif
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+FLUTTER_PLUGIN_EXPORT void WindowSizePluginRegisterWithRegistrar(
+    FlutterDesktopPluginRegistrarRef registrar);
+
+#if defined(__cplusplus)
+}  // extern "C"
+#endif
+
+#endif  // PLUGINS_WINDOW_SIZE_WINDOWS_WINDOW_SIZE_PLUGIN_H_

+ 279 - 0
window_size/windows/window_size_plugin.cpp

@@ -0,0 +1,279 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#include "include/window_size/window_size_plugin.h"
+
+#include <Windows.h>
+#include <flutter/flutter_view.h>
+#include <flutter/method_channel.h>
+#include <flutter/plugin_registrar_windows.h>
+#include <flutter/standard_method_codec.h>
+#include <flutter_windows.h>
+
+#include <codecvt>
+#include <memory>
+#include <optional>
+#include <sstream>
+
+namespace {
+
+using flutter::EncodableList;
+using flutter::EncodableMap;
+using flutter::EncodableValue;
+
+// See window_size_channel.dart for documentation.
+const char kChannelName[] = "flutter/windowsize";
+const char kGetScreenListMethod[] = "getScreenList";
+const char kGetWindowInfoMethod[] = "getWindowInfo";
+const char kSetWindowFrameMethod[] = "setWindowFrame";
+const char kSetWindowMinimumSize[] = "setWindowMinimumSize";
+const char kSetWindowMaximumSize[] = "setWindowMaximumSize";
+const char kSetWindowTitleMethod[] = "setWindowTitle";
+const char ksetWindowVisibilityMethod[] = "setWindowVisibility";
+const char kFrameKey[] = "frame";
+const char kVisibleFrameKey[] = "visibleFrame";
+const char kScaleFactorKey[] = "scaleFactor";
+const char kScreenKey[] = "screen";
+
+const double kBaseDpi = 96.0;
+
+// Returns a POINT corresponding to channel representation of a size.
+POINT GetPointForPlatformChannelRepresentationSize(const EncodableList &size) {
+  POINT point = {};
+  point.x = static_cast<LONG>(std::get<double>(size[0]));
+  point.y = static_cast<LONG>(std::get<double>(size[1]));
+  return point;
+}
+
+// Returns the serializable form of |frame| expected by the platform channel.
+EncodableValue GetPlatformChannelRepresentationForRect(const RECT &rect) {
+  return EncodableValue(EncodableList{
+      EncodableValue(static_cast<double>(rect.left)),
+      EncodableValue(static_cast<double>(rect.top)),
+      EncodableValue(static_cast<double>(rect.right) -
+                     static_cast<double>(rect.left)),
+      EncodableValue(static_cast<double>(rect.bottom) -
+                     static_cast<double>(rect.top)),
+  });
+}
+
+// Extracts information from monitor |monitor| and returns the
+// serializable form expected by the platform channel.
+EncodableValue GetPlatformChannelRepresentationForMonitor(HMONITOR monitor) {
+  if (!monitor) {
+    return EncodableValue();
+  }
+
+  MONITORINFO info;
+  info.cbSize = sizeof(MONITORINFO);
+  ::GetMonitorInfo(monitor, &info);
+  UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
+  double scale_factor = dpi / kBaseDpi;
+  return EncodableValue(EncodableMap{
+      {EncodableValue(kFrameKey),
+       GetPlatformChannelRepresentationForRect(info.rcMonitor)},
+      {EncodableValue(kVisibleFrameKey),
+       GetPlatformChannelRepresentationForRect(info.rcWork)},
+      {EncodableValue(kScaleFactorKey), EncodableValue(scale_factor)},
+  });
+}
+
+BOOL CALLBACK MonitorRepresentationEnumProc(HMONITOR monitor, HDC hdc,
+                                            LPRECT clip, LPARAM list_ref) {
+  EncodableValue *monitors = reinterpret_cast<EncodableValue *>(list_ref);
+  std::get<EncodableList>(*monitors).push_back(
+      GetPlatformChannelRepresentationForMonitor(monitor));
+  return TRUE;
+}
+
+// Extracts information from |window| and returns the serializable form expected
+// by the platform channel.
+EncodableValue GetPlatformChannelRepresentationForWindow(HWND window) {
+  if (!window) {
+    return EncodableValue();
+  }
+  RECT frame;
+  ::GetWindowRect(window, &frame);
+  HMONITOR window_monitor =
+      ::MonitorFromWindow(window, MONITOR_DEFAULTTOPRIMARY);
+  double scale_factor = FlutterDesktopGetDpiForHWND(window) / kBaseDpi;
+
+  return EncodableValue(EncodableMap{
+      {EncodableValue(kFrameKey),
+       GetPlatformChannelRepresentationForRect(frame)},
+      {EncodableValue(kScreenKey),
+       GetPlatformChannelRepresentationForMonitor(window_monitor)},
+      {EncodableValue(kScaleFactorKey), EncodableValue(scale_factor)},
+  });
+}
+
+HWND GetRootWindow(flutter::FlutterView *view) {
+  return ::GetAncestor(view->GetNativeWindow(), GA_ROOT);
+}
+
+class WindowSizePlugin : public flutter::Plugin {
+ public:
+  static void RegisterWithRegistrar(flutter::PluginRegistrarWindows *registrar);
+
+  // Creates a plugin that communicates on the given channel.
+  WindowSizePlugin(flutter::PluginRegistrarWindows *registrar);
+
+  virtual ~WindowSizePlugin();
+
+ private:
+  // Called when a method is called on the plugin channel;
+  void HandleMethodCall(const flutter::MethodCall<> &method_call,
+                        std::unique_ptr<flutter::MethodResult<>> result);
+
+  // Called for top-level WindowProc delegation.
+  std::optional<LRESULT> HandleWindowProc(HWND hwnd, UINT message,
+                                          WPARAM wparam, LPARAM lparam);
+
+  // The registrar for this plugin, for accessing the window.
+  flutter::PluginRegistrarWindows *registrar_;
+
+  // The ID of the WindowProc delegate registration.
+  int window_proc_id_ = -1;
+
+  // The minimum size set by the platform channel.
+  POINT min_size_ = {0, 0};
+
+  // The maximum size set by the platform channel.
+  POINT max_size_ = {-1, -1};
+};
+
+// static
+void WindowSizePlugin::RegisterWithRegistrar(
+    flutter::PluginRegistrarWindows *registrar) {
+  auto channel = std::make_unique<flutter::MethodChannel<>>(
+      registrar->messenger(), kChannelName,
+      &flutter::StandardMethodCodec::GetInstance());
+
+  auto plugin = std::make_unique<WindowSizePlugin>(registrar);
+
+  channel->SetMethodCallHandler(
+      [plugin_pointer = plugin.get()](const auto &call, auto result) {
+        plugin_pointer->HandleMethodCall(call, std::move(result));
+      });
+
+  registrar->AddPlugin(std::move(plugin));
+}
+
+WindowSizePlugin::WindowSizePlugin(flutter::PluginRegistrarWindows *registrar)
+    : registrar_(registrar) {
+  window_proc_id_ = registrar_->RegisterTopLevelWindowProcDelegate(
+      [this](HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) {
+        return HandleWindowProc(hwnd, message, wparam, lparam);
+      });
+}
+
+WindowSizePlugin::~WindowSizePlugin() {
+  registrar_->UnregisterTopLevelWindowProcDelegate(window_proc_id_);
+}
+
+void WindowSizePlugin::HandleMethodCall(
+    const flutter::MethodCall<> &method_call,
+    std::unique_ptr<flutter::MethodResult<>> result) {
+  if (method_call.method_name().compare(kGetScreenListMethod) == 0) {
+    EncodableValue screens(std::in_place_type<EncodableList>);
+    ::EnumDisplayMonitors(nullptr, nullptr, MonitorRepresentationEnumProc,
+                          reinterpret_cast<LPARAM>(&screens));
+    result->Success(screens);
+  } else if (method_call.method_name().compare(kGetWindowInfoMethod) == 0) {
+    result->Success(GetPlatformChannelRepresentationForWindow(
+        GetRootWindow(registrar_->GetView())));
+  } else if (method_call.method_name().compare(kSetWindowFrameMethod) == 0) {
+    const auto *frame_list =
+        std::get_if<EncodableList>(method_call.arguments());
+    if (!frame_list || frame_list->size() != 4) {
+      result->Error("Bad arguments", "Expected 4-element list");
+      return;
+    }
+    // Frame validity (e.g., non-zero size) is assumed to be checked on the Dart
+    // side of the call.
+    int x = static_cast<int>(std::get<double>((*frame_list)[0]));
+    int y = static_cast<int>(std::get<double>((*frame_list)[1]));
+    int width = static_cast<int>(std::get<double>((*frame_list)[2]));
+    int height = static_cast<int>(std::get<double>((*frame_list)[3]));
+    ::SetWindowPos(GetRootWindow(registrar_->GetView()), nullptr, x, y, width,
+                   height, SWP_NOACTIVATE | SWP_NOOWNERZORDER);
+    result->Success();
+  } else if (method_call.method_name().compare(kSetWindowMinimumSize) == 0) {
+    const auto *size = std::get_if<EncodableList>(method_call.arguments());
+    if (!size || size->size() != 2) {
+      result->Error("Bad arguments", "Expected 2-element list");
+      return;
+    }
+    min_size_ = GetPointForPlatformChannelRepresentationSize(*size);
+    result->Success();
+  } else if (method_call.method_name().compare(kSetWindowMaximumSize) == 0) {
+    const auto *size = std::get_if<EncodableList>(method_call.arguments());
+    if (!size || size->size() != 2) {
+      result->Error("Bad arguments", "Expected 2-element list");
+      return;
+    }
+    max_size_ = GetPointForPlatformChannelRepresentationSize(*size);
+    result->Success();
+  } else if (method_call.method_name().compare(kSetWindowTitleMethod) == 0) {
+    const auto *title = std::get_if<std::string>(method_call.arguments());
+    if (!title) {
+      result->Error("Bad arguments", "Expected string");
+      return;
+    }
+    std::wstring wstr =
+        std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t>{}
+            .from_bytes(*title);
+    ::SetWindowText(GetRootWindow(registrar_->GetView()), wstr.c_str());
+    result->Success();
+  } else if (method_call.method_name().compare(ksetWindowVisibilityMethod) ==
+             0) {
+    const bool *visible = std::get_if<bool>(method_call.arguments());
+    if (visible == nullptr) {
+      result->Error("Bad arguments", "Expected bool");
+      return;
+    }
+    ::ShowWindow(GetRootWindow(registrar_->GetView()),
+                 *visible ? SW_SHOW : SW_HIDE);
+    result->Success();
+  } else {
+    result->NotImplemented();
+  }
+}
+
+std::optional<LRESULT> WindowSizePlugin::HandleWindowProc(HWND hwnd,
+                                                          UINT message,
+                                                          WPARAM wparam,
+                                                          LPARAM lparam) {
+  std::optional<LRESULT> result;
+  switch (message) {
+    case WM_GETMINMAXINFO:
+      MINMAXINFO *info = reinterpret_cast<MINMAXINFO *>(lparam);
+      // For the special "unconstrained" values, leave the defaults.
+      if (min_size_.x != 0) info->ptMinTrackSize.x = min_size_.x;
+      if (min_size_.y != 0) info->ptMinTrackSize.y = min_size_.y;
+      if (max_size_.x != -1) info->ptMaxTrackSize.x = max_size_.x;
+      if (max_size_.y != -1) info->ptMaxTrackSize.y = max_size_.y;
+      result = 0;
+      break;
+  }
+  return result;
+}
+
+}  // namespace
+
+void WindowSizePluginRegisterWithRegistrar(
+    FlutterDesktopPluginRegistrarRef registrar) {
+  WindowSizePlugin::RegisterWithRegistrar(
+      flutter::PluginRegistrarManager::GetInstance()
+          ->GetRegistrar<flutter::PluginRegistrarWindows>(registrar));
+}

+ 3 - 0
windows/flutter/generated_plugin_registrant.cc

@@ -6,6 +6,9 @@
 
 #include "generated_plugin_registrant.h"
 
+#include <window_size/window_size_plugin.h>
 
 void RegisterPlugins(flutter::PluginRegistry* registry) {
+  WindowSizePluginRegisterWithRegistrar(
+      registry->GetRegistrarForPlugin("WindowSizePlugin"));
 }

+ 1 - 0
windows/flutter/generated_plugins.cmake

@@ -3,6 +3,7 @@
 #
 
 list(APPEND FLUTTER_PLUGIN_LIST
+  window_size
 )
 
 set(PLUGIN_BUNDLED_LIBRARIES)