Bug 579056 - [GTK4] FocusIn/Out events not working

FocusIn/FocusOut events are now correctly sent in all cases.

- Implemented a new callback for GTKWindow's is-active property
- windowActiveProc now allows SWT to replicate GTK3 behaviour for
FocusIn and FocusOut events
- Created firstFixedHandle to cache the first SWTFixed container to
prevent extra calls (mostly relevant when no other text input based
widgets are in a shell)

Tested by using Snippet 1 to test default behaviour when no other text
input based widgets present. Then, snippet 1 was modified to contain two
Text widgets to allow the testing of switching focus back and forth. In
both cases FocusIn and FocusOut listeners were added to the relevant
widgets (Shell + Text) to ensure events were being sent correctly.

Change-Id: Idd924a60bf3110bf6e2328f826768b911b2a7911
Signed-off-by: Joel Majano <jmajano@redhat.com>
Reviewed-on: https://git.eclipse.org/r/c/platform/eclipse.platform.swt/+/191748
Tested-by: Alexander Kurtakov <akurtako@redhat.com>
Reviewed-by: Alexander Kurtakov <akurtako@redhat.com>
diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk/OS.java b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk/OS.java
index 179aaf2..a234d7d 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk/OS.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk/OS.java
@@ -377,6 +377,7 @@
 	public static final byte[] notify_default_height = ascii("notify::default-height");
 	public static final byte[] notify_default_width = ascii("notify::default-width");
 	public static final byte[] notify_maximized = ascii("notify::maximized");
+	public static final byte[] notify_is_active = ascii("notify::is-active");
 	public static final byte[] notify_theme_change = ascii("notify::gtk-application-prefer-dark-theme");
 	public static final byte[] response = ascii("response");
 	public static final byte[] compute_size = ascii("compute-size");
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Control.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Control.java
index ffdbf48..0d82404 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Control.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Control.java
@@ -57,6 +57,7 @@
 	static final boolean DISABLE_EMOJI = Boolean.getBoolean("SWT_GTK_INPUT_HINT_NO_EMOJI");
 
 	long fixedHandle;
+	long firstFixedHandle = 0;
 	long redrawWindow, enableWindow, provider;
 	int drawCount, backgroundAlpha = 255;
 	long dragGesture, zoomGesture, rotateGesture, panGesture;
@@ -3926,6 +3927,25 @@
 }
 
 @Override
+void gtk4_focus_window_event(long handle, long event) {
+	super.gtk4_focus_window_event(handle, event);
+
+	if(firstFixedHandle == 0) {
+		long child = handle;
+		//3rd child of shell will be SWTFixed
+		for(int i = 0; i<3; i++) {
+			child = GTK4.gtk_widget_get_first_child(child);
+		}
+		firstFixedHandle = child != 0 ? child:0;
+	}
+
+	if(firstFixedHandle !=0 && GTK.gtk_widget_has_focus(firstFixedHandle)) {
+		if(event == SWT.FocusIn)sendFocusEvent(SWT.FocusIn);
+		else sendFocusEvent(SWT.FocusOut);
+	}
+}
+
+@Override
 long gtk_focus_out_event (long widget, long event) {
 	// widget could be disposed at this point
 	if (handle != 0) {
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Display.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Display.java
index 4dfc47f..f59a441 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Display.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Display.java
@@ -132,13 +132,13 @@
 	Callback eventCallback;
 	long eventProc, windowProc2, windowProc3, windowProc4, windowProc5, windowProc6;
 	long changeValueProc;
-	long snapshotDrawProc, keyPressReleaseProc, focusProc, enterMotionProc, leaveProc,
+	long snapshotDrawProc, keyPressReleaseProc, focusProc, windowActiveProc, enterMotionProc, leaveProc,
 		 scrollProc, resizeProc, activateProc, gesturePressReleaseProc;
 	long notifyProc;
 	long computeSizeProc;
 	Callback windowCallback2, windowCallback3, windowCallback4, windowCallback5, windowCallback6;
 	Callback changeValue;
-	Callback snapshotDraw, keyPressReleaseCallback, focusCallback, enterMotionCallback, computeSizeCallback,
+	Callback snapshotDraw, keyPressReleaseCallback, focusCallback, windowActiveCallback, enterMotionCallback, computeSizeCallback,
 			 scrollCallback, leaveCallback, resizeCallback, activateCallback, gesturePressReleaseCallback;
 	Callback notifyCallback;
 	EventTable eventTable, filterTable;
@@ -3573,6 +3573,9 @@
 		focusCallback = new Callback(this, "focusProc", void.class, new Type[] {long.class, long.class}); //$NON-NLS-1$
 		focusProc = focusCallback.getAddress();
 
+		windowActiveCallback = new Callback(this, "windowActiveProc", void.class, new Type[] {long.class, long.class}); //$NON-NLS-1$
+		windowActiveProc = windowActiveCallback.getAddress();
+
 		enterMotionCallback = new Callback(this, "enterMotionProc", void.class, new Type[] {
 				long.class, double.class, double.class, long.class}); //$NON-NLS-1$
 		enterMotionProc = enterMotionCallback.getAddress ();
@@ -4701,6 +4704,10 @@
 		focusCallback = null;
 		focusProc = 0;
 
+		windowActiveCallback.dispose();
+		windowActiveCallback = null;
+		windowActiveProc = 0;
+
 		enterMotionCallback.dispose();
 		enterMotionCallback = null;
 		enterMotionProc = 0;
@@ -6130,13 +6137,18 @@
 	return false;
 }
 
-void focusProc(long controller, long user_data) {
+void focusProc(long controller, long user_data) {;
 	long handle = GTK.gtk_event_controller_get_widget(controller);
 	Widget widget = getWidget(handle);
 
 	if (widget != null) widget.focusProc(controller, user_data);
 }
 
+void windowActiveProc(long handle, long user_data) {;
+	Widget widget = getWidget(handle);
+	if (widget != null) widget.windowActiveProc(handle, user_data);
+}
+
 boolean keyPressReleaseProc(long controller, int keyval, int keycode, int state, long user_data) {
 	long handle = GTK.gtk_event_controller_get_widget(controller);
 	Widget widget = getWidget(handle);
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Shell.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Shell.java
index 23db4e2..224734a 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Shell.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Shell.java
@@ -947,6 +947,7 @@
 	}
 	if (GTK.GTK4) {
 		OS.g_signal_connect_closure (shellHandle, OS.close_request, display.getClosure (CLOSE_REQUEST), false);
+		OS.g_signal_connect(shellHandle, OS.notify_is_active, display.windowActiveProc, FOCUS_IN);
 		long keyController = GTK4.gtk_event_controller_key_new();
 		GTK4.gtk_widget_add_controller(shellHandle, keyController);
 		GTK.gtk_event_controller_set_propagation_phase(keyController, GTK.GTK_PHASE_TARGET);
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Widget.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Widget.java
index 8a1b3ab..81f11f0 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Widget.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Widget.java
@@ -796,6 +796,12 @@
 void gtk4_focus_enter_event(long controller, long event) {}
 
 /**
+ * @param handle the handle of the window that caused the event
+ * @param event the type of event, should be FocusIn or FocusOut
+ */
+void gtk4_focus_window_event(long handle, long event) {}
+
+/**
  * @param controller the corresponding controller responsible for capturing the event
  * @param event the GdkEvent captured
  */
@@ -2299,6 +2305,12 @@
 	}
 }
 
+void windowActiveProc(long handle, long user_data) {
+	long eventType = GTK.gtk_window_is_active(handle) ? SWT.FocusIn:SWT.FocusOut;
+
+	gtk4_focus_window_event(handle, eventType);
+}
+
 boolean keyPressReleaseProc(long controller, int keyval, int keycode, int state, long user_data) {
 	long event = GTK4.gtk_event_controller_get_current_event(controller);