diff --git a/client/include/input.h b/client/include/input.h index 3c05adf5..0911777c 100644 --- a/client/include/input.h +++ b/client/include/input.h @@ -29,6 +29,8 @@ void lgInput_init(void); void lgInput_free(void); void lgInput_setFallback(const LG_InputOps * ops, void * opaque); +/* Drop functions remove a dead endpoint without invoking it. */ +void lgInput_dropFallback(void); void lgInput_setTransport(const LG_InputOps * ops, void * opaque); void lgInput_dropTransport(void); void lgInput_useTransport(bool enable); diff --git a/client/src/audio.c b/client/src/audio.c index 2211d640..39b858a9 100644 --- a/client/src/audio.c +++ b/client/src/audio.c @@ -4335,12 +4335,7 @@ void lgAudio_setFallback(const LG_AudioOps * ops, void * opaque) setBinding(&audio.fallback, ops, opaque, fallbackStatusChanged); } -void lgAudio_setTransport(const LG_AudioOps * ops, void * opaque) -{ - setBinding(&audio.transport, ops, opaque, transportStatusChanged); -} - -void lgAudio_dropTransport(void) +static void dropBinding(AudioBinding * target) { LG_LOCK(audio.bindingLock); if (!atomic_load_explicit(&audio.ready, memory_order_acquire)) @@ -4351,16 +4346,27 @@ void lgAudio_dropTransport(void) LG_LOCK(audio.providerLock); LG_LOCK_EXCLUSIVE(audio.activeLock); - const AudioBinding old = audio.transport; - const bool wasActive = bindingActiveNL(&audio.transport); - audio.transport = (AudioBinding) { 0 }; + const bool wasActive = bindingActiveNL(target); + *target = (AudioBinding) { 0 }; LG_UNLOCK_EXCLUSIVE(audio.activeLock); updateActive(wasActive); LG_UNLOCK(audio.providerLock); - - if (old.ops && old.ops->setStatusListener) - old.ops->setStatusListener(old.opaque, NULL, NULL); LG_UNLOCK(audio.bindingLock); } +void lgAudio_dropFallback(void) +{ + dropBinding(&audio.fallback); +} + +void lgAudio_setTransport(const LG_AudioOps * ops, void * opaque) +{ + setBinding(&audio.transport, ops, opaque, transportStatusChanged); +} + +void lgAudio_dropTransport(void) +{ + dropBinding(&audio.transport); +} + #endif diff --git a/client/src/audio.h b/client/src/audio.h index f79f759c..b2aa8441 100644 --- a/client/src/audio.h +++ b/client/src/audio.h @@ -29,6 +29,8 @@ void lgAudio_init(void); void lgAudio_free(void); void lgAudio_setFallback(const LG_AudioOps * ops, void * opaque); +/* Drop functions remove a dead endpoint without invoking it. */ +void lgAudio_dropFallback(void); void lgAudio_setTransport(const LG_AudioOps * ops, void * opaque); void lgAudio_dropTransport(void); @@ -42,6 +44,7 @@ static inline void lgAudio_init(void) {} static inline void lgAudio_free(void) {} static inline void lgAudio_setFallback( const LG_AudioOps * ops, void * opaque) {} +static inline void lgAudio_dropFallback(void) {} static inline void lgAudio_setTransport( const LG_AudioOps * ops, void * opaque) {} static inline void lgAudio_dropTransport(void) {} diff --git a/client/src/clipboard.c b/client/src/clipboard.c index 48a67560..621ec4ed 100644 --- a/client/src/clipboard.c +++ b/client/src/clipboard.c @@ -482,8 +482,8 @@ static const LG_ClipboardEventOps eventOps = .request = eventRequest, }; -/* providerLock must be held. dropActive suppresses remote release when the - * endpoint has already disappeared; detach must always quiesce local events. */ +/* providerLock must be held. dropActive suppresses all calls into an endpoint + * which has already disappeared. */ static void updateActive(bool dropActive) { for (;;) @@ -501,9 +501,9 @@ static void updateActive(bool dropActive) clipboard.active = (ClipboardBinding) { 0 }; LG_UNLOCK_EXCLUSIVE(clipboard.activeLock); - if (old.ops) + if (old.ops && !dropActive) { - if (!dropActive && !old.ops->release(old.opaque)) + if (!old.ops->release(old.opaque)) DEBUG_WARN("Failed to release Clipboard provider: %s", old.ops->name); old.ops->detach(old.opaque); @@ -687,6 +687,24 @@ void lgClipboard_setFallback(const LG_ClipboardOps * ops, void * opaque) ops, opaque, fallbackStatusChanged); } +static void dropBinding(ClipboardBinding * target) +{ + LG_LOCK(clipboard.registrationLock); + LG_LOCK(clipboard.providerLock); + LG_LOCK_EXCLUSIVE(clipboard.activeLock); + const bool wasActive = bindingActiveNL(target); + *target = (ClipboardBinding) { 0 }; + LG_UNLOCK_EXCLUSIVE(clipboard.activeLock); + updateActive(wasActive); + LG_UNLOCK(clipboard.providerLock); + LG_UNLOCK(clipboard.registrationLock); +} + +void lgClipboard_dropFallback(void) +{ + dropBinding(&clipboard.fallback); +} + void lgClipboard_setTransport(const LG_ClipboardOps * ops, void * opaque) { setBinding(&clipboard.transport, @@ -695,22 +713,7 @@ void lgClipboard_setTransport(const LG_ClipboardOps * ops, void * opaque) void lgClipboard_dropTransport(void) { - LG_LOCK(clipboard.registrationLock); - LG_LOCK(clipboard.providerLock); - const ClipboardBinding old = clipboard.transport; - LG_UNLOCK(clipboard.providerLock); - - if (old.ops && old.ops->setStatusListener) - old.ops->setStatusListener(old.opaque, NULL, NULL); - - LG_LOCK(clipboard.providerLock); - LG_LOCK_EXCLUSIVE(clipboard.activeLock); - const bool wasActive = bindingActiveNL(&clipboard.transport); - clipboard.transport = (ClipboardBinding) { 0 }; - LG_UNLOCK_EXCLUSIVE(clipboard.activeLock); - updateActive(wasActive); - LG_UNLOCK(clipboard.providerLock); - LG_UNLOCK(clipboard.registrationLock); + dropBinding(&clipboard.transport); } void lgClipboard_release(void) diff --git a/client/src/clipboard.h b/client/src/clipboard.h index 0a458d7a..a671a780 100644 --- a/client/src/clipboard.h +++ b/client/src/clipboard.h @@ -28,6 +28,8 @@ void lgClipboard_free(void); void lgClipboard_setLocalAvailable(bool available); void lgClipboard_setFallback(const LG_ClipboardOps * ops, void * opaque); +/* Drop functions remove a dead endpoint without invoking it. */ +void lgClipboard_dropFallback(void); void lgClipboard_setTransport(const LG_ClipboardOps * ops, void * opaque); void lgClipboard_dropTransport(void); diff --git a/client/src/input.c b/client/src/input.c index d793b852..c6267c73 100644 --- a/client/src/input.c +++ b/client/src/input.c @@ -32,12 +32,14 @@ struct InputBinding void * opaque; bool available; bool mouseAbsolute; - uint32_t generation; + uint32_t epoch; }; static struct { + LG_Lock bindingLock; LG_RWLock activeLock; + uint32_t nextBindingEpoch; struct InputBinding fallback; struct InputBinding transport; @@ -66,6 +68,14 @@ static struct InputBinding makeBinding(const LG_InputOps * ops, void * opaque) { const bool available = ops && !ops->setStatusListener; + uint32_t epoch = 0; + if (ops) + { + epoch = ++l_input.nextBindingEpoch; + if (!epoch) + epoch = ++l_input.nextBindingEpoch; + } + return (struct InputBinding) { .ops = ops, @@ -73,10 +83,18 @@ static struct InputBinding makeBinding(const LG_InputOps * ops, .available = available, .mouseAbsolute = available && ops->mousePosition && ops->supports(opaque, LG_INPUT_SUPPORT_MOUSE_ABSOLUTE), - .generation = 0, + .epoch = epoch, }; } +static bool bindingEqual(const struct InputBinding * a, + const struct InputBinding * b) +{ + return a->ops == b->ops && + a->opaque == b->opaque && + a->epoch == b->epoch; +} + static void releaseKeysNL(void) { for (int key = 0; key < KEY_MAX; ++key) @@ -114,7 +132,7 @@ static void clearStateNL(void) atomic_store_explicit(&l_input.buttons, 0, memory_order_relaxed); } -static void updateActiveNL(void) +static void updateActiveNL(bool dropActive) { const struct InputBinding next = l_input.useTransport && l_input.transport.available ? @@ -122,14 +140,16 @@ static void updateActiveNL(void) l_input.fallback.available ? l_input.fallback : (struct InputBinding) { 0 }; - if (next.ops == l_input.active.ops && - next.opaque == l_input.active.opaque) + if (bindingEqual(&next, &l_input.active)) { l_input.active = next; return; } - resetActiveNL(); + if (dropActive) + clearStateNL(); + else + resetActiveNL(); l_input.active = next; if (l_input.active.ops) @@ -145,9 +165,7 @@ static void updateActiveNL(void) static void updateStatusNL(struct InputBinding * binding, const LG_InputStatus * status) { - const bool wasActive = - l_input.active.ops == binding->ops && - l_input.active.opaque == binding->opaque; + const bool wasActive = bindingEqual(&l_input.active, binding); if (wasActive && !status->available) { @@ -160,8 +178,7 @@ static void updateStatusNL(struct InputBinding * binding, binding->ops->mousePosition && binding->ops->supports(binding->opaque, LG_INPUT_SUPPORT_MOUSE_ABSOLUTE); - binding->generation = status->generation; - updateActiveNL(); + updateActiveNL(false); } static void fallbackStatusChanged(void * opaque, @@ -171,7 +188,8 @@ static void fallbackStatusChanged(void * opaque, return; LG_LOCK_EXCLUSIVE(l_input.activeLock); - if (l_input.fallback.ops && l_input.fallback.opaque == opaque) + if (l_input.fallback.ops && + l_input.fallback.epoch == (uint32_t)(uintptr_t)opaque) updateStatusNL(&l_input.fallback, status); LG_UNLOCK_EXCLUSIVE(l_input.activeLock); } @@ -183,7 +201,8 @@ static void transportStatusChanged(void * opaque, return; LG_LOCK_EXCLUSIVE(l_input.activeLock); - if (l_input.transport.ops && l_input.transport.opaque == opaque) + if (l_input.transport.ops && + l_input.transport.epoch == (uint32_t)(uintptr_t)opaque) updateStatusNL(&l_input.transport, status); LG_UNLOCK_EXCLUSIVE(l_input.activeLock); } @@ -199,6 +218,7 @@ void lgInput_init(void) atomic_init(&l_input.keys[key], false); atomic_init(&l_input.buttons, 0); + LG_LOCK_INIT(l_input.bindingLock); LG_RWLOCK_INIT(l_input.activeLock); } @@ -207,6 +227,7 @@ void lgInput_free(void) struct InputBinding fallback; struct InputBinding transport; + LG_LOCK(l_input.bindingLock); LG_LOCK_EXCLUSIVE(l_input.activeLock); resetActiveNL(); fallback = l_input.fallback; @@ -221,85 +242,79 @@ void lgInput_free(void) if (transport.ops && transport.ops->setStatusListener) transport.ops->setStatusListener(transport.opaque, NULL, NULL); + LG_UNLOCK(l_input.bindingLock); LG_RWLOCK_FREE(l_input.activeLock); + LG_LOCK_FREE(l_input.bindingLock); +} + +static void setBinding(struct InputBinding * target, + const LG_InputOps * ops, void * opaque, LG_InputStatusFn statusFn) +{ + if (ops && !validOps(ops)) + { + DEBUG_ERROR("Invalid input operations"); + ops = NULL; + opaque = NULL; + } + + struct InputBinding old; + + LG_LOCK(l_input.bindingLock); + LG_LOCK_EXCLUSIVE(l_input.activeLock); + old = *target; + LG_UNLOCK_EXCLUSIVE(l_input.activeLock); + + if (old.ops && old.ops->setStatusListener) + old.ops->setStatusListener(old.opaque, NULL, NULL); + + const struct InputBinding next = makeBinding(ops, opaque); + LG_LOCK_EXCLUSIVE(l_input.activeLock); + *target = next; + updateActiveNL(false); + LG_UNLOCK_EXCLUSIVE(l_input.activeLock); + + if (next.ops && next.ops->setStatusListener) + next.ops->setStatusListener(next.opaque, statusFn, + (void *)(uintptr_t)next.epoch); + LG_UNLOCK(l_input.bindingLock); +} + +static void dropBinding(struct InputBinding * target) +{ + LG_LOCK(l_input.bindingLock); + LG_LOCK_EXCLUSIVE(l_input.activeLock); + const bool wasActive = bindingEqual(&l_input.active, target); + *target = (struct InputBinding) { 0 }; + updateActiveNL(wasActive); + LG_UNLOCK_EXCLUSIVE(l_input.activeLock); + LG_UNLOCK(l_input.bindingLock); } void lgInput_setFallback(const LG_InputOps * ops, void * opaque) { - if (ops && !validOps(ops)) - { - DEBUG_ERROR("Invalid fallback input operations"); - ops = NULL; - opaque = NULL; - } + setBinding(&l_input.fallback, ops, opaque, fallbackStatusChanged); +} - const struct InputBinding next = makeBinding(ops, opaque); - struct InputBinding old; - - LG_LOCK_EXCLUSIVE(l_input.activeLock); - old = l_input.fallback; - l_input.fallback = next; - updateActiveNL(); - LG_UNLOCK_EXCLUSIVE(l_input.activeLock); - - if (old.ops && old.ops->setStatusListener) - old.ops->setStatusListener(old.opaque, NULL, NULL); - if (next.ops && next.ops->setStatusListener) - next.ops->setStatusListener(next.opaque, - fallbackStatusChanged, next.opaque); +void lgInput_dropFallback(void) +{ + dropBinding(&l_input.fallback); } void lgInput_setTransport(const LG_InputOps * ops, void * opaque) { - if (ops && !validOps(ops)) - { - DEBUG_ERROR("Invalid transport input operations"); - ops = NULL; - opaque = NULL; - } - - const struct InputBinding next = makeBinding(ops, opaque); - struct InputBinding old; - - LG_LOCK_EXCLUSIVE(l_input.activeLock); - old = l_input.transport; - l_input.transport = next; - updateActiveNL(); - LG_UNLOCK_EXCLUSIVE(l_input.activeLock); - - if (old.ops && old.ops->setStatusListener) - old.ops->setStatusListener(old.opaque, NULL, NULL); - if (next.ops && next.ops->setStatusListener) - next.ops->setStatusListener(next.opaque, - transportStatusChanged, next.opaque); + setBinding(&l_input.transport, ops, opaque, transportStatusChanged); } void lgInput_dropTransport(void) { - struct InputBinding old; - - LG_LOCK_EXCLUSIVE(l_input.activeLock); - old = l_input.transport; - if (l_input.useTransport && l_input.active.ops == l_input.transport.ops && - l_input.active.opaque == l_input.transport.opaque) - { - l_input.active = (struct InputBinding) { 0 }; - clearStateNL(); - } - - l_input.transport = (struct InputBinding) { 0 }; - updateActiveNL(); - LG_UNLOCK_EXCLUSIVE(l_input.activeLock); - - if (old.ops && old.ops->setStatusListener) - old.ops->setStatusListener(old.opaque, NULL, NULL); + dropBinding(&l_input.transport); } void lgInput_useTransport(bool enable) { LG_LOCK_EXCLUSIVE(l_input.activeLock); l_input.useTransport = enable; - updateActiveNL(); + updateActiveNL(false); LG_UNLOCK_EXCLUSIVE(l_input.activeLock); }