Wait for macOS focus restoration to land

This commit is contained in:
firat
2026-08-18 18:26:15 +02:00
parent 45b18722be
commit 0be64c2ef9
2 changed files with 50 additions and 5 deletions
+15 -3
View File
@@ -637,12 +637,24 @@ class Dikte:
# inactive: at forty the title bar visibly blinks, at ten it does not.
# Two messages to AppKit per tick, for at most a second and a half.
watch.setInterval(10)
# activateWithOptions: answers whether macOS accepted the request, not
# whether the other application is already back in front. Keep the
# watch alive until that asynchronous handoff is observable; on Intel
# Macs it can take hundreds of milliseconds after the call returned.
restore_requested = False
def look():
if mac_window.is_frontmost():
mac_window.activate(was_in_front)
nonlocal restore_requested
if time.monotonic() > deadline:
self._stop_watching_the_front()
elif time.monotonic() > deadline:
return
if mac_window.is_frontmost():
if not restore_requested:
restore_requested = mac_window.activate(was_in_front)
elif restore_requested:
# The request has landed. Stop only now, rather than as soon
# as AppKit accepted it, so a delayed or failed handoff stays
# under observation until the deadline guard above.
self._stop_watching_the_front()
watch.timeout.connect(look)
+35 -2
View File
@@ -604,7 +604,7 @@ class GivingTheFrontBack(DikteTest):
from dikte import mac_window
self.dikte = dikte_module
self.activated = []
self.patch_attr(mac_window, "activate", self.activated.append)
self.patch_attr(mac_window, "activate", self.activate)
self.ticks = []
outer = self
@@ -645,6 +645,10 @@ class GivingTheFrontBack(DikteTest):
self.bare = BareDikte
def activate(self, pid):
self.activated.append(pid)
return True
def watching(self, was_in_front, dikte_in_front, on=None):
from dikte import mac_window
self.patch_attr(mac_window, "is_frontmost", lambda: dikte_in_front)
@@ -656,7 +660,36 @@ class GivingTheFrontBack(DikteTest):
watch = self.watching(4242, dikte_in_front=True)
watch.tick()
self.assertEqual(self.activated, [4242])
self.assertFalse(watch.running) # one is enough; it stops watching
self.assertTrue(watch.running) # accepted is not the same as landed
self.patch_attr(self.mac_window_module(), "is_frontmost", lambda: False)
watch.tick()
self.assertFalse(watch.running)
def test_an_accepted_restore_is_not_sent_again_while_it_is_landing(self):
watch = self.watching(4242, dikte_in_front=True)
watch.tick()
watch.tick()
self.assertEqual(self.activated, [4242])
self.assertTrue(watch.running)
def test_an_accepted_restore_that_never_lands_still_times_out(self):
watch = self.watching(4242, dikte_in_front=True)
watch.tick()
with mock.patch.object(self.dikte.time, "monotonic",
return_value=self.dikte.time.monotonic() + 60):
watch.tick()
self.assertFalse(watch.running)
self.assertEqual(self.activated, [4242])
def test_a_restore_the_system_refused_is_retried(self):
from dikte import mac_window
self.patch_attr(mac_window, "activate",
lambda pid: self.activated.append(pid) or False)
watch = self.watching(4242, dikte_in_front=True)
watch.tick()
watch.tick()
self.assertEqual(self.activated, [4242, 4242])
self.assertTrue(watch.running)
def test_a_front_that_was_never_taken_is_left_where_it_is(self):
"""The microphone does not always take it, and pulling an application