tdf#163764 Force pending timers to run after marked text changes
During native dictation, waiting for the next native event is blocked while dictation runs in a loop within a native callback. Because of this, LibreOffice's painting timers won't fire until dictation is cancelled or the user pauses speaking. So, force any pending timers to fire after the marked text changes. Also, remove the fix for OpenOffice bug 106901 as causes any key down events to cancel dictation and removing the fix does not appear to cause the original crashing bug to reoccur. Note: inserting a character from the system Character Viewer window causes dictation to stop responding and the only way I have found to restart dictation is by toggling dictation off and then back on again. This same behavior occurs in Apple's TextEdit application so this appears to be a macOS bug. Change-Id: Iad2b54870ff1a315f2f71d72bef24af3cea808e6 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/176100 Reviewed-by: Michael Weghorn <m.weghorn@posteo.de> Tested-by: Jenkins Reviewed-by: Patrick Luby <guibomacdev@gmail.com>
This commit is contained in:
parent
e064452b8f
commit
62baf23796
5 changed files with 59 additions and 44 deletions
|
@ -274,6 +274,7 @@ struct ImplSVWinData
|
|||
bool mbNoDeactivate = false; // true: do not execute Deactivate
|
||||
bool mbNoSaveFocus = false; // true: menus must not save/restore focus
|
||||
bool mbIsLiveResize = false; // true: skip waiting for events and low priority timers
|
||||
bool mbIsWaitingForNativeEvent = false; // true: code is executing via a native callback while waiting for the next native event
|
||||
};
|
||||
|
||||
typedef std::vector< std::pair< OUString, FieldUnit > > FieldUnitStringList;
|
||||
|
|
|
@ -212,6 +212,32 @@ static void updateWinDataInLiveResize(bool bInLiveResize)
|
|||
}
|
||||
}
|
||||
|
||||
static void freezeWindowSizeAndReschedule( NSWindow *pWindow )
|
||||
{
|
||||
if ( pWindow )
|
||||
{
|
||||
// Application::Reschedule() can potentially display a modal
|
||||
// dialog which will cause a hang so temporarily disable any
|
||||
// resizing by clamping the window's minimum and maximum sizes
|
||||
// to the current frame size which in Application::Reschedule().
|
||||
bool bIsLiveResize = ImplGetSVData()->mpWinData->mbIsLiveResize;
|
||||
NSSize aMinSize = [pWindow minSize];
|
||||
NSSize aMaxSize = [pWindow maxSize];
|
||||
if ( bIsLiveResize )
|
||||
{
|
||||
NSRect aFrame = [pWindow frame];
|
||||
[pWindow setMinSize:aFrame.size];
|
||||
[pWindow setMaxSize:aFrame.size];
|
||||
}
|
||||
Application::Reschedule( true );
|
||||
if ( bIsLiveResize )
|
||||
{
|
||||
[pWindow setMinSize:aMinSize];
|
||||
[pWindow setMaxSize:aMaxSize];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@interface NSResponder (SalFrameWindow)
|
||||
-(BOOL)accessibilityIsIgnored;
|
||||
@end
|
||||
|
@ -454,18 +480,7 @@ static void updateWinDataInLiveResize(bool bInLiveResize)
|
|||
// not trigger redrawing with the new size.
|
||||
// Instead, force relayout by dispatching all pending internal
|
||||
// events and firing any pending timers.
|
||||
// Also, Application::Reschedule() can potentially display a
|
||||
// modal dialog which will cause a hang so temporarily disable
|
||||
// live resize by clamping the window's minimum and maximum sizes
|
||||
// to the current frame size which in Application::Reschedule().
|
||||
NSRect aFrame = [self frame];
|
||||
NSSize aMinSize = [self minSize];
|
||||
NSSize aMaxSize = [self maxSize];
|
||||
[self setMinSize:aFrame.size];
|
||||
[self setMaxSize:aFrame.size];
|
||||
Application::Reschedule( true );
|
||||
[self setMinSize:aMinSize];
|
||||
[self setMaxSize:aMaxSize];
|
||||
freezeWindowSizeAndReschedule( self );
|
||||
|
||||
if ( ImplGetSVData()->mpWinData->mbIsLiveResize )
|
||||
{
|
||||
|
@ -1931,36 +1946,11 @@ static void updateWinDataInLiveResize(bool bInLiveResize)
|
|||
mbNeedSpecialKeyHandle = true;
|
||||
}
|
||||
|
||||
// FIXME:
|
||||
// #i106901#
|
||||
// if we come here outside of mbInKeyInput, this is likely to be because
|
||||
// of the keyboard viewer. For unknown reasons having no marked range
|
||||
// in this case causes a crash. So we say we have a marked range anyway
|
||||
// This is a hack, since it is not understood what a) causes that crash
|
||||
// and b) why we should have a marked range at this point.
|
||||
if( ! mbInKeyInput )
|
||||
bHasMarkedText = true;
|
||||
|
||||
return bHasMarkedText;
|
||||
}
|
||||
|
||||
- (NSRange)markedRange
|
||||
{
|
||||
// FIXME:
|
||||
// #i106901#
|
||||
// if we come here outside of mbInKeyInput, this is likely to be because
|
||||
// of the keyboard viewer. For unknown reasons having no marked range
|
||||
// in this case causes a crash. So we say we have a marked range anyway
|
||||
// This is a hack, since it is not understood what a) causes that crash
|
||||
// and b) why we should have a marked range at this point. Stop the native
|
||||
// input method popup from appearing in the bottom left corner of the
|
||||
// screen by returning the marked range if is valid when called outside of
|
||||
// mbInKeyInput. If a zero length range is returned, macOS won't call
|
||||
// [self firstRectForCharacterRange:actualRange:] for any newly appended
|
||||
// uncommitted text.
|
||||
if( ! mbInKeyInput )
|
||||
return mMarkedRange.location != NSNotFound ? mMarkedRange : NSMakeRange( 0, 0 );
|
||||
|
||||
return [self hasMarkedText] ? mMarkedRange : NSMakeRange( NSNotFound, 0 );
|
||||
}
|
||||
|
||||
|
@ -1989,6 +1979,7 @@ static void updateWinDataInLiveResize(bool bInLiveResize)
|
|||
[self unmarkText];
|
||||
|
||||
int len = [aString length];
|
||||
bool bReschedule = false;
|
||||
SalExtTextInputEvent aInputEvent;
|
||||
if( len > 0 ) {
|
||||
// Set the marked and selected ranges to the marked text and selected
|
||||
|
@ -2053,16 +2044,35 @@ static void updateWinDataInLiveResize(bool bInLiveResize)
|
|||
aInputEvent.mnCursorPos = nSelectionStart;
|
||||
aInputEvent.mnCursorFlags = 0;
|
||||
aInputEvent.mpTextAttr = aInputFlags.data();
|
||||
mpFrame->CallCallback( SalEvent::ExtTextInput, static_cast<void *>(&aInputEvent) );
|
||||
if( AquaSalFrame::isAlive( mpFrame ) )
|
||||
{
|
||||
bReschedule = true;
|
||||
mpFrame->CallCallback( SalEvent::ExtTextInput, static_cast<void *>(&aInputEvent) );
|
||||
}
|
||||
} else {
|
||||
aInputEvent.maText.clear();
|
||||
aInputEvent.mnCursorPos = 0;
|
||||
aInputEvent.mnCursorFlags = 0;
|
||||
aInputEvent.mpTextAttr = nullptr;
|
||||
mpFrame->CallCallback( SalEvent::ExtTextInput, static_cast<void *>(&aInputEvent) );
|
||||
mpFrame->CallCallback( SalEvent::EndExtTextInput, nullptr );
|
||||
if( AquaSalFrame::isAlive( mpFrame ) )
|
||||
{
|
||||
bReschedule = true;
|
||||
mpFrame->CallCallback( SalEvent::ExtTextInput, static_cast<void *>(&aInputEvent) );
|
||||
if( AquaSalFrame::isAlive( mpFrame ) )
|
||||
mpFrame->CallCallback( SalEvent::EndExtTextInput, nullptr );
|
||||
}
|
||||
}
|
||||
mbKeyHandled= true;
|
||||
|
||||
// tdf#163764 force pending timers to run after marked text changes
|
||||
// During native dictation, waiting for the next native event is
|
||||
// blocked while dictation runs in a loop within a native callback.
|
||||
// Because of this, LibreOffice's painting timers won't fire until
|
||||
// dictation is cancelled or the user pauses speaking. So, force
|
||||
// any pending timers to fire after the marked text changes.
|
||||
if( bReschedule && ImplGetSVData()->mpWinData->mbIsWaitingForNativeEvent )
|
||||
freezeWindowSizeAndReschedule( [self window] );
|
||||
|
||||
mbKeyHandled = true;
|
||||
}
|
||||
|
||||
- (void)unmarkText
|
||||
|
|
|
@ -637,6 +637,8 @@ bool AquaSalInstance::DoYield(bool bWait, bool bHandleAllCurrentEvents)
|
|||
// Some events and timers call Application::Reschedule() or
|
||||
// Application::Yield() so don't block and wait for events when a
|
||||
// window is in live resize
|
||||
bool bOldIsWaitingForNativeEvent = ImplGetSVData()->mpWinData->mbIsWaitingForNativeEvent;
|
||||
ImplGetSVData()->mpWinData->mbIsWaitingForNativeEvent = !o3tl::IsRunningUnitTest();
|
||||
if( bWait && ! bHadEvent && !ImplGetSVData()->mpWinData->mbIsLiveResize )
|
||||
{
|
||||
SolarMutexReleaser aReleaser;
|
||||
|
@ -660,6 +662,8 @@ bool AquaSalInstance::DoYield(bool bWait, bool bHandleAllCurrentEvents)
|
|||
[NSApp updateWindows];
|
||||
}
|
||||
|
||||
ImplGetSVData()->mpWinData->mbIsWaitingForNativeEvent = bOldIsWaitingForNativeEvent;
|
||||
|
||||
// collect update rectangles
|
||||
for( auto pSalFrame : GetSalData()->mpInstance->getFrames() )
|
||||
{
|
||||
|
|
|
@ -83,7 +83,7 @@ void AquaSalTimer::Start( sal_uInt64 nMS )
|
|||
return;
|
||||
}
|
||||
|
||||
m_bDirectTimeout = (0 == nMS) && !ImplGetSVData()->mpWinData->mbIsLiveResize;
|
||||
m_bDirectTimeout = (0 == nMS) && !ImplGetSVData()->mpWinData->mbIsLiveResize && !ImplGetSVData()->mpWinData->mbIsWaitingForNativeEvent;
|
||||
if ( m_bDirectTimeout )
|
||||
Stop();
|
||||
else
|
||||
|
@ -142,7 +142,7 @@ void AquaSalTimer::callTimerCallback()
|
|||
|
||||
void AquaSalTimer::handleTimerElapsed()
|
||||
{
|
||||
if ( m_bDirectTimeout || ImplGetSVData()->mpWinData->mbIsLiveResize )
|
||||
if ( m_bDirectTimeout || ImplGetSVData()->mpWinData->mbIsLiveResize || ImplGetSVData()->mpWinData->mbIsWaitingForNativeEvent )
|
||||
{
|
||||
// Stop the timer, as it is just invalidated after the firing function
|
||||
Stop();
|
||||
|
|
|
@ -389,7 +389,7 @@ void Scheduler::CallbackTaskScheduling()
|
|||
// Only higher priority tasks need to be fired to redraw the window
|
||||
// so skip firing potentially long-running tasks, such as the Writer
|
||||
// idle layout timer, when a window is in live resize
|
||||
if ( ImplGetSVData()->mpWinData->mbIsLiveResize && nTaskPriority == static_cast<int>(TaskPriority::LOWEST) )
|
||||
if ( nTaskPriority == static_cast<int>(TaskPriority::LOWEST) && ( ImplGetSVData()->mpWinData->mbIsLiveResize || ImplGetSVData()->mpWinData->mbIsWaitingForNativeEvent ) )
|
||||
continue;
|
||||
|
||||
pSchedulerData = rSchedCtx.mpFirstSchedulerData[nTaskPriority];
|
||||
|
|
Loading…
Reference in a new issue