Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Used GdkMonitor to set default window size #1743

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
69 changes: 63 additions & 6 deletions packages/app_center/linux/my_application.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include <flutter_linux/flutter_linux.h>
#include <handy.h>
#include <gdk/gdk.h>

#include "flutter/generated_plugin_registrant.h"

Expand All @@ -19,6 +20,66 @@ struct _MyApplication {

G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)

// Callback function to set the window size based on the monitor it's located on
void on_window_realize(GtkWidget* widget, gpointer user_data) {
GdkRectangle monitor_geometry;
GdkWindow* gdk_window = gtk_widget_get_window(widget);
if (gdk_window == nullptr) {
return;
}

GdkDisplay* display = gdk_window_get_display(gdk_window);
GdkSeat* seat = gdk_display_get_default_seat(display);
GdkDevice* pointer = gdk_seat_get_pointer(seat);

// Get the current cursor position
int x, y;
gdk_device_get_position(pointer, nullptr, &x, &y);

// Get the monitor at the cursor position
GdkMonitor* monitor = gdk_display_get_monitor_at_point(display, x, y);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is not reliable: if the user moves the pointer in the right moment, it will fail.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if user opens(clicks) app in external monitor and then moves cursor to different screen(let's say laptop) before app is opened then the app will open in the laptop screen and size will adjust according to the display where cursor is

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But it's sensitive to race conditions: If the mouse cursor crosses the boundary after the window has been mapped but before the code gets the position, we can have a wrong value. I personally think that we should use gdk_display_get_monitor_at_window.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay that is possible , the issue with gdk_display_get_monitor_at_window is that it is setting the screen_width and screen_height of the primary display & opens app on primary display not the one the user is interacting with

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but as I commented, in the second MAP event, the data returned is the right one, so I think that the solution is to get the current size in the MAP event, compare it with the last one to see if it has changed (the first time, of course, you must consider it as changed), and if changed, resize the window. Also, in my opinion, it would be even better to just launch an idle task from the MAP callback if the size has changed, and in that idle task do the size change, to avoid doing the change inside the MAP event processing.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay i'll try that 👍

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't make it work other than current solution, i think we sould just open the window in primary display and let the user move and resize, if user want to move to external monitor.

Copy link
Contributor

@sergio-costas sergio-costas Jul 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My fault: it was the draw signal, not the map one. Anyway, here is my test code. I tested it and it works fine.

#include "my_application.h"

#define APPLICATION_FLAGS G_APPLICATION_NON_UNIQUE

struct _MyApplication {
  GtkApplication parent_instance;
};

G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)

gboolean set_new_size(gpointer user_data) {
  // assigning it to a g_autofree to ensure that it is automagically
  // freed at the end of the function
  g_autofree GdkRectangle *geometry = user_data;
  g_print("New geometry detected: %d, %d, %d, %d\n", geometry->x, geometry->y, geometry->width, geometry->height);
  // Ensures that this function isn't called again. It must be called only once per change.
  return G_SOURCE_REMOVE;
}

void on_window_realize(GtkWidget* widget, gpointer user_data) {
  static int x = 0, y = 0, width = 0, height = 0;

  GdkWindow* gdk_window = gtk_widget_get_window(widget);
  if (gdk_window == NULL) {
    g_print("No Gdk window\n");
    return;
  }

  GdkDisplay* display = gdk_window_get_display(gdk_window);
  GdkMonitor* monitor = gdk_display_get_monitor_at_window(display, gdk_window);
  g_autofree GdkRectangle *geometry = g_malloc(sizeof(GdkRectangle));
  gdk_monitor_get_geometry(monitor, geometry);
  g_print("Called geometry callback with geometry: %d, %d, %d, %d\n", geometry->x, geometry->y, geometry->width, geometry->height);

  // If the geometry has changed since the last signal, launch an idle task to update the size of the window
  if ((geometry->x != x) || (geometry->y != y) || (geometry->width != width) || (geometry->height != height)) {
    x = geometry->x;
    y = geometry->y;
    width = geometry->width;
    height = geometry->height;
    g_idle_add(set_new_size, g_steal_pointer(&geometry));
  }
}

static void button_clicked(GtkButton *button, gpointer data) {
  g_print("Button clicked\n");
  on_window_realize(GTK_WIDGET(data), NULL);
}

static gboolean draw_event(GtkWidget *self, cairo_t *cr, gpointer data) {
  g_print("MAP event\n");
  on_window_realize(GTK_WIDGET(data), NULL);
  return TRUE;
}

// Implements GApplication::activate.
static void my_application_activate(GApplication* application) {
  MyApplication* self = MY_APPLICATION(application);

  GtkWindow* window = GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
  gtk_window_set_application(window, GTK_APPLICATION(application));

  GtkButton *button = GTK_BUTTON(gtk_button_new_with_label("Check monitor"));

  gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(button));
  g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(button_clicked), window);
  g_signal_connect_after(window, "realize", G_CALLBACK(on_window_realize), NULL);
  // Pass the window in the data pointer
  g_signal_connect_after(window, "draw", G_CALLBACK(draw_event), window);
  gtk_widget_show_all(GTK_WIDGET(window));
}

// Implements GObject::dispose.
static void my_application_dispose(GObject* object) {
  MyApplication* self = MY_APPLICATION(object);
  G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
}

static void my_application_class_init(MyApplicationClass* klass) {
  G_APPLICATION_CLASS(klass)->activate = my_application_activate;
  G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
}

static void my_application_init(MyApplication* self) {}

MyApplication* my_application_new() {
  return MY_APPLICATION(g_object_new(my_application_get_type(),
                                     "application-id", "com.rastersoft.testapp", "flags",
                                     APPLICATION_FLAGS, NULL));
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Huzaifa-code are you planning to continue working on this?


if (monitor == nullptr) {
return;
}

gdk_monitor_get_geometry(monitor, &monitor_geometry);
gint screen_width = monitor_geometry.width;
gint screen_height = monitor_geometry.height;

// Predefined sizes
const gint min_width = 800;
const gint min_height = 600;
const gint max_width = 1280;
const gint max_height = 800;

// Determine default window size based on screen size
gint default_width = screen_width;
gint default_height = screen_height;

g_print("Default width: %d, Default height: %d\n", default_width, default_height);


if (screen_width <= max_width || screen_height <= max_height) {
default_width = min_width;
default_height = min_height;
} else {
default_width = max_width;
default_height = max_height;
}

g_print("Default width: %d, Default height: %d\n", default_width, default_height);


GdkGeometry geometry;
geometry.min_width = min_width;
geometry.min_height = min_height;

// gtk_window_set_geometry_hints(GTK_WINDOW(widget), nullptr, &geometry, GDK_HINT_MIN_SIZE);
gtk_window_set_default_size(GTK_WINDOW(widget), default_width, default_height);
}


// Implements GApplication::activate.
static void my_application_activate(GApplication* application) {
MyApplication* self = MY_APPLICATION(application);
Expand All @@ -34,14 +95,10 @@ static void my_application_activate(GApplication* application) {
GtkWindow* window = GTK_WINDOW(hdy_application_window_new());
gtk_window_set_application(window, GTK_APPLICATION(application));

GdkGeometry geometry;
// Connect to the "realize" signal to get the monitor size after the window is realized
g_signal_connect(window, "realize", G_CALLBACK(on_window_realize), nullptr);

// TODO: find better solution; set default window size based on available space
geometry.min_width = 800 + 52; // account for shadow from libhandy
geometry.min_height = 600 + 52;
gtk_window_set_geometry_hints(window, nullptr, &geometry, GDK_HINT_MIN_SIZE);

gtk_window_set_default_size(window, 1280 + 52, 800 + 52);
gtk_widget_show(GTK_WIDGET(window));

g_autoptr(FlDartProject) project = fl_dart_project_new();
Expand Down