vcl/inc/svdata.hxx           |    1 
 vcl/osx/salframeview.mm      |   92 +++++++++++++++++++++++--------------------
 vcl/osx/salinst.cxx          |    4 +
 vcl/osx/saltimer.cxx         |    4 -
 vcl/source/app/scheduler.cxx |    2 
 5 files changed, 59 insertions(+), 44 deletions(-)

New commits:
commit 62baf23796132d6c0e17ae1ff37c3b9ee0556cf0
Author:     Patrick Luby <guibmac...@gmail.com>
AuthorDate: Tue Nov 5 19:51:55 2024 -0500
Commit:     Patrick Luby <guibomac...@gmail.com>
CommitDate: Mon Nov 11 01:17:25 2024 +0100

    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.wegh...@posteo.de>
    Tested-by: Jenkins
    Reviewed-by: Patrick Luby <guibomac...@gmail.com>

diff --git a/vcl/inc/svdata.hxx b/vcl/inc/svdata.hxx
index a839b4276959..c332894e1fc8 100644
--- a/vcl/inc/svdata.hxx
+++ b/vcl/inc/svdata.hxx
@@ -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;
diff --git a/vcl/osx/salframeview.mm b/vcl/osx/salframeview.mm
index 681567f3a5fa..9bdfa8d9f239 100644
--- a/vcl/osx/salframeview.mm
+++ b/vcl/osx/salframeview.mm
@@ -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
diff --git a/vcl/osx/salinst.cxx b/vcl/osx/salinst.cxx
index 4d078bd96b35..0dbaa428a83a 100644
--- a/vcl/osx/salinst.cxx
+++ b/vcl/osx/salinst.cxx
@@ -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() )
         {
diff --git a/vcl/osx/saltimer.cxx b/vcl/osx/saltimer.cxx
index 8af7de217678..fffd1c63b92c 100644
--- a/vcl/osx/saltimer.cxx
+++ b/vcl/osx/saltimer.cxx
@@ -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();
diff --git a/vcl/source/app/scheduler.cxx b/vcl/source/app/scheduler.cxx
index e9f4057cd47c..eaca69adafa1 100644
--- a/vcl/source/app/scheduler.cxx
+++ b/vcl/source/app/scheduler.cxx
@@ -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];

Reply via email to