window_size_plugin.cc 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. // Copyright 2018 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #include "include/window_size/window_size_plugin.h"
  15. #include <flutter_linux/flutter_linux.h>
  16. #include <gtk/gtk.h>
  17. // See window_size_channel.dart for documentation.
  18. const char kChannelName[] = "flutter/windowsize";
  19. const char kBadArgumentsError[] = "Bad Arguments";
  20. const char kNoScreenError[] = "No Screen";
  21. const char kGetScreenListMethod[] = "getScreenList";
  22. const char kGetWindowInfoMethod[] = "getWindowInfo";
  23. const char kSetWindowFrameMethod[] = "setWindowFrame";
  24. const char kSetWindowMinimumSizeMethod[] = "setWindowMinimumSize";
  25. const char kSetWindowMaximumSizeMethod[] = "setWindowMaximumSize";
  26. const char kSetWindowTitleMethod[] = "setWindowTitle";
  27. const char ksetWindowVisibilityMethod[] = "setWindowVisibility";
  28. const char kGetWindowMinimumSizeMethod[] = "getWindowMinimumSize";
  29. const char kGetWindowMaximumSizeMethod[] = "getWindowMaximumSize";
  30. const char kFrameKey[] = "frame";
  31. const char kVisibleFrameKey[] = "visibleFrame";
  32. const char kScaleFactorKey[] = "scaleFactor";
  33. const char kScreenKey[] = "screen";
  34. struct _FlWindowSizePlugin {
  35. GObject parent_instance;
  36. FlPluginRegistrar* registrar;
  37. // Connection to Flutter engine.
  38. FlMethodChannel* channel;
  39. // Requested window geometry.
  40. GdkGeometry window_geometry;
  41. };
  42. G_DEFINE_TYPE(FlWindowSizePlugin, fl_window_size_plugin, g_object_get_type())
  43. // Gets the window being controlled.
  44. GtkWindow* get_window(FlWindowSizePlugin* self) {
  45. FlView* view = fl_plugin_registrar_get_view(self->registrar);
  46. if (view == nullptr) return nullptr;
  47. return GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(view)));
  48. }
  49. // Gets the display connection.
  50. GdkDisplay* get_display(FlWindowSizePlugin* self) {
  51. FlView* view = fl_plugin_registrar_get_view(self->registrar);
  52. if (view == nullptr) return nullptr;
  53. return gtk_widget_get_display(GTK_WIDGET(view));
  54. }
  55. // Converts frame dimensions into the Flutter representation.
  56. FlValue* make_frame_value(gint x, gint y, gint width, gint height) {
  57. g_autoptr(FlValue) value = fl_value_new_list();
  58. fl_value_append_take(value, fl_value_new_float(x));
  59. fl_value_append_take(value, fl_value_new_float(y));
  60. fl_value_append_take(value, fl_value_new_float(width));
  61. fl_value_append_take(value, fl_value_new_float(height));
  62. return fl_value_ref(value);
  63. }
  64. // Converts monitor information into the Flutter representation.
  65. FlValue* make_monitor_value(GdkMonitor* monitor) {
  66. g_autoptr(FlValue) value = fl_value_new_map();
  67. GdkRectangle frame;
  68. gdk_monitor_get_geometry(monitor, &frame);
  69. fl_value_set_string_take(
  70. value, kFrameKey,
  71. make_frame_value(frame.x, frame.y, frame.width, frame.height));
  72. gdk_monitor_get_workarea(monitor, &frame);
  73. fl_value_set_string_take(
  74. value, kVisibleFrameKey,
  75. make_frame_value(frame.x, frame.y, frame.width, frame.height));
  76. gint scale_factor = gdk_monitor_get_scale_factor(monitor);
  77. fl_value_set_string_take(value, kScaleFactorKey,
  78. fl_value_new_float(scale_factor));
  79. return fl_value_ref(value);
  80. }
  81. // Gets the list of current screens.
  82. static FlMethodResponse* get_screen_list(FlWindowSizePlugin* self) {
  83. g_autoptr(FlValue) screens = fl_value_new_list();
  84. GdkDisplay* display = get_display(self);
  85. if (display == nullptr) {
  86. return FL_METHOD_RESPONSE(
  87. fl_method_error_response_new(kNoScreenError, nullptr, nullptr));
  88. }
  89. gint n_monitors = gdk_display_get_n_monitors(display);
  90. for (gint i = 0; i < n_monitors; i++) {
  91. GdkMonitor* monitor = gdk_display_get_monitor(display, i);
  92. fl_value_append_take(screens, make_monitor_value(monitor));
  93. }
  94. return FL_METHOD_RESPONSE(fl_method_success_response_new(screens));
  95. }
  96. // Gets information about the Flutter window.
  97. static FlMethodResponse* get_window_info(FlWindowSizePlugin* self) {
  98. GtkWindow* window = get_window(self);
  99. if (window == nullptr) {
  100. return FL_METHOD_RESPONSE(
  101. fl_method_error_response_new(kNoScreenError, nullptr, nullptr));
  102. }
  103. g_autoptr(FlValue) window_info = fl_value_new_map();
  104. gint x, y, width, height;
  105. gtk_window_get_position(window, &x, &y);
  106. gtk_window_get_size(window, &width, &height);
  107. fl_value_set_string_take(window_info, kFrameKey,
  108. make_frame_value(x, y, width, height));
  109. // Get the monitor this window is inside, or the primary monitor if doesn't
  110. // appear to be in any.
  111. GdkDisplay* display = get_display(self);
  112. GdkMonitor* monitor_with_window = gdk_display_get_primary_monitor(display);
  113. int n_monitors = gdk_display_get_n_monitors(display);
  114. for (int i = 0; i < n_monitors; i++) {
  115. GdkMonitor* monitor = gdk_display_get_monitor(display, i);
  116. GdkRectangle frame;
  117. gdk_monitor_get_geometry(monitor, &frame);
  118. if ((x >= frame.x && x <= frame.x + frame.width) &&
  119. (y >= frame.y && y <= frame.y + frame.width)) {
  120. monitor_with_window = monitor;
  121. break;
  122. }
  123. }
  124. fl_value_set_string_take(window_info, kScreenKey,
  125. make_monitor_value(monitor_with_window));
  126. gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(window));
  127. fl_value_set_string_take(window_info, kScaleFactorKey,
  128. fl_value_new_float(scale_factor));
  129. return FL_METHOD_RESPONSE(fl_method_success_response_new(window_info));
  130. }
  131. // Sets the window position and dimensions.
  132. static FlMethodResponse* set_window_frame(FlWindowSizePlugin* self,
  133. FlValue* args) {
  134. if (fl_value_get_type(args) != FL_VALUE_TYPE_LIST ||
  135. fl_value_get_length(args) != 4) {
  136. return FL_METHOD_RESPONSE(fl_method_error_response_new(
  137. kBadArgumentsError, "Expected 4-element list", nullptr));
  138. }
  139. double x = fl_value_get_float(fl_value_get_list_value(args, 0));
  140. double y = fl_value_get_float(fl_value_get_list_value(args, 1));
  141. double width = fl_value_get_float(fl_value_get_list_value(args, 2));
  142. double height = fl_value_get_float(fl_value_get_list_value(args, 3));
  143. GtkWindow* window = get_window(self);
  144. if (window == nullptr) {
  145. return FL_METHOD_RESPONSE(
  146. fl_method_error_response_new(kNoScreenError, nullptr, nullptr));
  147. }
  148. gtk_window_move(window, static_cast<gint>(x), static_cast<gint>(y));
  149. gtk_window_resize(window, static_cast<gint>(width),
  150. static_cast<gint>(height));
  151. return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
  152. }
  153. // Send updated window geometry to GTK.
  154. static void update_window_geometry(FlWindowSizePlugin* self) {
  155. gtk_window_set_geometry_hints(
  156. get_window(self), nullptr, &self->window_geometry,
  157. static_cast<GdkWindowHints>(GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE));
  158. }
  159. // Sets the window minimum size.
  160. static FlMethodResponse* set_window_minimum_size(FlWindowSizePlugin* self,
  161. FlValue* args) {
  162. if (fl_value_get_type(args) != FL_VALUE_TYPE_LIST ||
  163. fl_value_get_length(args) != 2) {
  164. return FL_METHOD_RESPONSE(fl_method_error_response_new(
  165. kBadArgumentsError, "Expected 2-element list", nullptr));
  166. }
  167. double width = fl_value_get_float(fl_value_get_list_value(args, 0));
  168. double height = fl_value_get_float(fl_value_get_list_value(args, 1));
  169. if (get_window(self) == nullptr) {
  170. return FL_METHOD_RESPONSE(
  171. fl_method_error_response_new(kNoScreenError, nullptr, nullptr));
  172. }
  173. if (width >= 0 && height >= 0) {
  174. self->window_geometry.min_width = static_cast<gint>(width);
  175. self->window_geometry.min_height = static_cast<gint>(height);
  176. }
  177. update_window_geometry(self);
  178. return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
  179. }
  180. // Sets the window maximum size.
  181. static FlMethodResponse* set_window_maximum_size(FlWindowSizePlugin* self,
  182. FlValue* args) {
  183. if (fl_value_get_type(args) != FL_VALUE_TYPE_LIST ||
  184. fl_value_get_length(args) != 2) {
  185. return FL_METHOD_RESPONSE(fl_method_error_response_new(
  186. kBadArgumentsError, "Expected 2-element list", nullptr));
  187. }
  188. double width = fl_value_get_float(fl_value_get_list_value(args, 0));
  189. double height = fl_value_get_float(fl_value_get_list_value(args, 1));
  190. if (get_window(self) == nullptr) {
  191. return FL_METHOD_RESPONSE(
  192. fl_method_error_response_new(kNoScreenError, nullptr, nullptr));
  193. }
  194. self->window_geometry.max_width = static_cast<gint>(width);
  195. self->window_geometry.max_height = static_cast<gint>(height);
  196. // Flutter uses -1 as unconstrained, GTK doesn't have an unconstrained value.
  197. if (self->window_geometry.max_width < 0) {
  198. self->window_geometry.max_width = G_MAXINT;
  199. }
  200. if (self->window_geometry.max_height < 0) {
  201. self->window_geometry.max_height = G_MAXINT;
  202. }
  203. update_window_geometry(self);
  204. return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
  205. }
  206. // Sets the window title.
  207. static FlMethodResponse* set_window_title(FlWindowSizePlugin* self,
  208. FlValue* args) {
  209. if (fl_value_get_type(args) != FL_VALUE_TYPE_STRING) {
  210. return FL_METHOD_RESPONSE(fl_method_error_response_new(
  211. kBadArgumentsError, "Expected string", nullptr));
  212. }
  213. GtkWindow* window = get_window(self);
  214. if (window == nullptr) {
  215. return FL_METHOD_RESPONSE(
  216. fl_method_error_response_new(kNoScreenError, nullptr, nullptr));
  217. }
  218. gtk_window_set_title(window, fl_value_get_string(args));
  219. return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
  220. }
  221. // Sets the window visibility.
  222. static FlMethodResponse* set_window_visible(FlWindowSizePlugin* self,
  223. FlValue* args) {
  224. if (fl_value_get_type(args) != FL_VALUE_TYPE_BOOL) {
  225. return FL_METHOD_RESPONSE(fl_method_error_response_new(
  226. kBadArgumentsError, "Expected bool", nullptr));
  227. }
  228. GtkWindow* window = get_window(self);
  229. if (window == nullptr) {
  230. return FL_METHOD_RESPONSE(
  231. fl_method_error_response_new(kNoScreenError, nullptr, nullptr));
  232. }
  233. if (fl_value_get_bool(args)) {
  234. gtk_widget_show(GTK_WIDGET(window));
  235. } else {
  236. gtk_widget_hide(GTK_WIDGET(window));
  237. }
  238. return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
  239. }
  240. // Gets the window minimum size.
  241. static FlMethodResponse* get_window_minimum_size(FlWindowSizePlugin* self) {
  242. g_autoptr(FlValue) size = fl_value_new_list();
  243. gint min_width = self->window_geometry.min_width;
  244. gint min_height = self->window_geometry.min_height;
  245. // GTK uses -1 for the requisition size (the size GTK has calculated).
  246. // Report this as zero (smallest possible) so this doesn't look like Size(-1, -1).
  247. if (min_width < 0) {
  248. min_width = 0;
  249. }
  250. if (min_height < 0) {
  251. min_height = 0;
  252. }
  253. fl_value_append_take(size, fl_value_new_float(min_width));
  254. fl_value_append_take(size, fl_value_new_float(min_height));
  255. return FL_METHOD_RESPONSE(fl_method_success_response_new(size));
  256. }
  257. // Gets the window maximum size.
  258. static FlMethodResponse* get_window_maximum_size(FlWindowSizePlugin* self) {
  259. g_autoptr(FlValue) size = fl_value_new_list();
  260. gint max_width = self->window_geometry.max_width;
  261. gint max_height = self->window_geometry.max_height;
  262. // Flutter uses -1 as unconstrained, GTK doesn't have an unconstrained value.
  263. if (max_width == G_MAXINT) {
  264. max_width = -1;
  265. }
  266. if (max_height == G_MAXINT) {
  267. max_height = -1;
  268. }
  269. fl_value_append_take(size, fl_value_new_float(max_width));
  270. fl_value_append_take(size, fl_value_new_float(max_height));
  271. return FL_METHOD_RESPONSE(fl_method_success_response_new(size));
  272. }
  273. // Called when a method call is received from Flutter.
  274. static void method_call_cb(FlMethodChannel* channel, FlMethodCall* method_call,
  275. gpointer user_data) {
  276. FlWindowSizePlugin* self = FL_WINDOW_SIZE_PLUGIN(user_data);
  277. const gchar* method = fl_method_call_get_name(method_call);
  278. FlValue* args = fl_method_call_get_args(method_call);
  279. g_autoptr(FlMethodResponse) response = nullptr;
  280. if (strcmp(method, kGetScreenListMethod) == 0) {
  281. response = get_screen_list(self);
  282. } else if (strcmp(method, kGetWindowInfoMethod) == 0) {
  283. response = get_window_info(self);
  284. } else if (strcmp(method, kSetWindowFrameMethod) == 0) {
  285. response = set_window_frame(self, args);
  286. } else if (strcmp(method, kSetWindowMinimumSizeMethod) == 0) {
  287. response = set_window_minimum_size(self, args);
  288. } else if (strcmp(method, kSetWindowMaximumSizeMethod) == 0) {
  289. response = set_window_maximum_size(self, args);
  290. } else if (strcmp(method, kSetWindowTitleMethod) == 0) {
  291. response = set_window_title(self, args);
  292. } else if (strcmp(method, ksetWindowVisibilityMethod) == 0) {
  293. response = set_window_visible(self, args);
  294. } else if (strcmp(method, kGetWindowMinimumSizeMethod) == 0) {
  295. response = get_window_minimum_size(self);
  296. } else if (strcmp(method, kGetWindowMaximumSizeMethod) == 0) {
  297. response = get_window_maximum_size(self);
  298. } else {
  299. response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new());
  300. }
  301. g_autoptr(GError) error = nullptr;
  302. if (!fl_method_call_respond(method_call, response, &error))
  303. g_warning("Failed to send method call response: %s", error->message);
  304. }
  305. static void fl_window_size_plugin_dispose(GObject* object) {
  306. FlWindowSizePlugin* self = FL_WINDOW_SIZE_PLUGIN(object);
  307. g_clear_object(&self->registrar);
  308. g_clear_object(&self->channel);
  309. G_OBJECT_CLASS(fl_window_size_plugin_parent_class)->dispose(object);
  310. }
  311. static void fl_window_size_plugin_class_init(FlWindowSizePluginClass* klass) {
  312. G_OBJECT_CLASS(klass)->dispose = fl_window_size_plugin_dispose;
  313. }
  314. static void fl_window_size_plugin_init(FlWindowSizePlugin* self) {
  315. self->window_geometry.min_width = -1;
  316. self->window_geometry.min_height = -1;
  317. self->window_geometry.max_width = G_MAXINT;
  318. self->window_geometry.max_height = G_MAXINT;
  319. }
  320. FlWindowSizePlugin* fl_window_size_plugin_new(FlPluginRegistrar* registrar) {
  321. FlWindowSizePlugin* self = FL_WINDOW_SIZE_PLUGIN(
  322. g_object_new(fl_window_size_plugin_get_type(), nullptr));
  323. self->registrar = FL_PLUGIN_REGISTRAR(g_object_ref(registrar));
  324. g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
  325. self->channel =
  326. fl_method_channel_new(fl_plugin_registrar_get_messenger(registrar),
  327. kChannelName, FL_METHOD_CODEC(codec));
  328. fl_method_channel_set_method_call_handler(self->channel, method_call_cb,
  329. g_object_ref(self), g_object_unref);
  330. return self;
  331. }
  332. void window_size_plugin_register_with_registrar(FlPluginRegistrar* registrar) {
  333. FlWindowSizePlugin* plugin = fl_window_size_plugin_new(registrar);
  334. g_object_unref(plugin);
  335. }