// Source: https://gist.githubusercontent.com/s-ilent/067d4e477041d52d2f8eabd78927d1a4/raw/568c222044a8ce3c33426c495cccbfe7c3a0c733/LinuxTabFix.cs
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
///
/// Linux Editor Tab Navigation Patch for Unity (2022.3+, Unity 6).
/// Restores missing ASCII '\t' character codes stripped by Linux window backend
/// and manages focus handshakes between IMGUIContainers in UI Toolkit windows.
///
[InitializeOnLoad]
public static class LinuxTabFix
{
private static readonly ConditionalWeakTable patchedContainers
= new ConditionalWeakTable();
static LinuxTabFix()
{
// Ensure idempotent event subscription across domain reloads
EditorApplication.update -= ApplyTabPatchToFocusedWindow;
EditorApplication.update += ApplyTabPatchToFocusedWindow;
}
private static void ApplyTabPatchToFocusedWindow()
{
EditorWindow focusedWindow = EditorWindow.focusedWindow;
if (focusedWindow == null) return;
VisualElement root = focusedWindow.rootVisualElement;
if (root == null) return;
List containers = root.Query().ToList();
int count = containers.Count;
for (int i = 0; i < count; i++)
{
IMGUIContainer container = containers[i];
if (container == null) continue;
int currentIndex = i;
// Patch unhandled containers without modifying container properties
if (!patchedContainers.TryGetValue(container, out _))
{
patchedContainers.Add(container, null);
Action originalHandler = container.onGUIHandler;
container.onGUIHandler = () =>
{
Event e = Event.current;
if (e != null && e.type == EventType.KeyDown && e.keyCode == KeyCode.Tab)
{
// Restore character code stripped by Linux native window backend
if (e.character == 0)
{
e.character = '\t';
}
bool isShift = e.shift;
// Execute original IMGUI pass
originalHandler?.Invoke();
// When boundary is reached, transfer focus to adjacent container
if (GUIUtility.keyboardControl == 0)
{
int targetIndex = isShift ? currentIndex - 1 : currentIndex + 1;
if (targetIndex >= 0 && targetIndex < count && targetIndex < containers.Count)
{
IMGUIContainer targetContainer = containers[targetIndex];
if (targetContainer != null)
{
targetContainer.Focus();
focusedWindow.Repaint();
}
}
}
return;
}
originalHandler?.Invoke();
};
}
}
}
}
#endif