[vlc-commits] [Git][videolan/vlc][master] 4 commits: qt: intercept closeEvent for Macos

Felix Paul Kühne (@fkuehne) gitlab at videolan.org
Sat Jun 21 12:57:56 UTC 2025



Felix Paul Kühne pushed to branch master at VideoLAN / VLC


Commits:
31a8877f by Alexandre Janniaux at 2025-06-21T12:34:39+00:00
qt: intercept closeEvent for Macos

On MacOS, the Quit shortcut (Command+Q/Ctrl+Q) is intercepted directly
by AppKit which emits an event on the NSApplicationDelegate bound to
NSApp.delegate. The event is caught by Qt Cocoa integration which sends
a closeEvent to the QApplication, and stops the Qt eventloop if the
closeEvent pass through.

This is problematic since we don't want to kill the eventloop right
away, and it provides a different behaviour when closing than other
ways (SIGINT/closing the window) where we redirect to libvlc_Quit() and
handle the termination of interfaces from their Deactivation callback
directly.

Instead this commit install a handler to catch Qt's closeEvent, on
every platforms, so as to redirect it to libvlc_Quit();

Co-authored-by: Pierre Lamot <pierre at videolabs.io>

- - - - -
025ea2fd by Alexandre Janniaux at 2025-06-21T12:34:39+00:00
macosx: handle termination through libvlc_Quit

Note: this doesn't break quitting from the interface but SIGINT now
requires two attempts to trigger quitting. This will be fixed in
following commit.

- - - - -
48f2b7f3 by Alexandre Janniaux at 2025-06-21T12:34:39+00:00
darwinvlc: fix libvlc termination and SIGTERM/SIGINT handling

Two issues were present with the previous implementation for interface
handling.

 - Contrary to bin/vlc.c, darwinvlc.m needs to handle the CFRunLoop in
   the main thread, but libvlc was released only after the runloop was
   terminated, meaning that interfaces could not use the runloop on
   close.

 - Some application-specific runloop stop code was present to stop the
   NSApp for the main macosx.m interface, with conditionals to check
   whether the NSApp was started or not. darwinvlc.m shouldn't need to
   know whether it needs to do that and should only handle the
   termination of its own CFRunLoopRun() call, even if an interface
   nests the runloop afterwards. As with the point above, the interface
   should terminate its NSApp before darwinvlc terminates its runloop.

This commit reorder the resource deallocation to first remove the
interfaces, by releasing the libvlc instance, and only then destroy the
runloop. It uses previous changes in the macosx interface to handle the
stopping through libvlc_Quit.

By reordering this way, it's now possible to use SIGINT to stop the Qt
interface also.

- - - - -
3db080cb by Alexandre Janniaux at 2025-06-21T12:34:39+00:00
qt: adapt exit code after libvlc_Quit() fix on Darwin

The Abort() function was set as a handler for the libvlc_Quit()
callback, through libvlc_SetExitHandler, and was used to provide Qt
runloop termination when the application was exiting.

But terminating the runloop and Qt resources was not correct since it
didn't account for the internal resource reference counting. Instead,
now that darwinvlc.m correctly handles libvlc termination by releasing
the interfaces before killing the runloop, we can just use the normal
code path for interfaces, which in the case of Qt already handles the
reference counting, and wait for the Qt runloop to exit.

Fix #28917

- - - - -


8 changed files:

- bin/darwinvlc.m
- modules/gui/macosx/main/VLCApplication.h
- modules/gui/macosx/main/VLCApplication.m
- modules/gui/macosx/main/VLCMain.m
- modules/gui/macosx/menus/VLCStatusBarIcon.h
- modules/gui/macosx/menus/VLCStatusBarIcon.m
- modules/gui/qt/qt.cpp
- modules/gui/qt/qt.hpp


Changes:

=====================================
bin/darwinvlc.m
=====================================
@@ -43,42 +43,46 @@
 
 #include "../lib/libvlc_internal.h"
 
+struct vlc_context {
+    libvlc_instance_t *vlc;
+    dispatch_queue_t intf_queue;
+    vlc_sem_t wait_quit;
+
+    bool quitting;
+};
+
 /**
  * Handler called when VLC asks to terminate the program.
  */
 static void vlc_terminate(void *data)
 {
-    (void)data;
-
-    dispatch_async(dispatch_get_main_queue(), ^{
-        /*
-         * Stop the main loop. When using the CoreFoundation mainloop, simply
-         * CFRunLoopStop can be used.
-         *
-         * But this does not work when having an interface.
-         * In this case, [NSApp stop:nil] needs to be used, but the used flag is only
-         * evaluated at the end of main loop event processing. This is always true
-         * in the case of code inside a action method. But here, this is
-         * not true and thus we need to send an dummy event to make sure the stop
-         * flag is actually processed by the main loop.
-         */
-        if (NSApp == nil) {
-            CFRunLoopStop(CFRunLoopGetCurrent());
-        } else {
-
-            [NSApp stop:nil];
-            NSEvent* event = [NSEvent otherEventWithType:NSApplicationDefined
-                                                location:NSMakePoint(0,0)
-                                           modifierFlags:0
-                                               timestamp:0.0
-                                            windowNumber:0
-                                                 context:nil
-                                                 subtype:0
-                                                   data1:0
-                                                   data2:0];
-            [NSApp postEvent:event atStart:YES];
-        }
+    struct vlc_context *context = data;
+
+    /* vlc_terminate can be called multiple times, for instance:
+     * - once when an interface like Qt (DialogProvider::quit) is exiting
+     * - once when libvlc_release() is called.
+     * `context` can only be guaranteed to be valid for the first call to
+     * vlc_terminate, but others will have no more effects than the first
+     * one, so we can ignore them. */
+    __block bool quitting = false;
+    static dispatch_once_t quitToken = 0;
+    dispatch_once(&quitToken, ^{
+        quitting = true;
+    });
+
+    if (!quitting)
+        return;
+
+    /* Release the libvlc instance to clean up the interfaces. */
+    dispatch_async(context->intf_queue, ^{
+        libvlc_release(context->vlc);
+        context->vlc = NULL;
+        vlc_sem_post(&context->wait_quit);
 
+        dispatch_async(dispatch_get_main_queue(), ^{
+            /* Stop the main loop started in main(). */
+            CFRunLoopStop(CFRunLoopGetCurrent());
+        });
     });
 }
 
@@ -208,13 +212,6 @@ int main(int i_argc, const char *ppsz_argv[])
     if (!sigIntSource || !sigTermSource)
         abort();
 
-    dispatch_source_set_event_handler(sigIntSource, ^{
-        vlc_terminate(nil);
-    });
-    dispatch_source_set_event_handler(sigTermSource, ^{
-        vlc_terminate(nil);
-    });
-
     dispatch_resume(sigIntSource);
     dispatch_resume(sigTermSource);
 
@@ -259,12 +256,27 @@ int main(int i_argc, const char *ppsz_argv[])
     argv[argc] = NULL;
 
     dispatch_queue_t intf_queue = dispatch_queue_create("org.videolan.vlc", NULL);
+
+    __block struct vlc_context context = {
+        .vlc = NULL,
+        .intf_queue = intf_queue,
+    };
+    vlc_sem_init(&context.wait_quit, 0);
+
+    dispatch_source_set_event_handler(sigIntSource, ^{
+        vlc_terminate(&context);
+    });
+    dispatch_source_set_event_handler(sigTermSource, ^{
+        vlc_terminate(&context);
+    });
+
     __block bool intf_started = true;
     __block libvlc_instance_t *vlc = NULL;
     int ret = 1;
     dispatch_async(intf_queue, ^{
         /* Initialize libvlc */
-        vlc = libvlc_new(argc, argv);
+        vlc = context.vlc
+            = libvlc_new(argc, argv);
         if (vlc == NULL)
         {
             dispatch_sync(dispatch_get_main_queue(), ^{
@@ -274,13 +286,14 @@ int main(int i_argc, const char *ppsz_argv[])
             return;
         }
 
-        libvlc_SetExitHandler(vlc->p_libvlc_int, vlc_terminate, NULL);
+        libvlc_SetExitHandler(vlc->p_libvlc_int, vlc_terminate, &context);
         libvlc_set_app_id(vlc, "org.VideoLAN.VLC", PACKAGE_VERSION, PACKAGE_NAME);
         libvlc_set_user_agent(vlc, "VLC media player", "VLC/"PACKAGE_VERSION);
 
 
         if (libvlc_InternalAddIntf(vlc->p_libvlc_int, NULL)) {
             fprintf(stderr, "VLC cannot start any interface. Exiting.\n");
+            libvlc_SetExitHandler(vlc->p_libvlc_int, NULL, NULL);
             dispatch_sync(dispatch_get_main_queue(), ^{
                 intf_started = false;
                 CFRunLoopStop(CFRunLoopGetMain());
@@ -305,15 +318,19 @@ int main(int i_argc, const char *ppsz_argv[])
 
     ret = 0;
     /* Cleanup */
+    vlc_sem_wait(&context.wait_quit);
+
 out:
-    dispatch_release(intf_queue);
-    free(argv);
 
     dispatch_release(sigIntSource);
     dispatch_release(sigTermSource);
 
-    if (vlc)
-        libvlc_release(vlc);
+    dispatch_release(intf_queue);
+    free(argv);
+
+    /* If no interface were created, we release libvlc here instead. */
+    if (context.vlc)
+        libvlc_release(context.vlc);
 
 #ifdef HAVE_BREAKPAD
     if (breakpad)


=====================================
modules/gui/macosx/main/VLCApplication.h
=====================================
@@ -25,6 +25,8 @@
 
 #import <Cocoa/Cocoa.h>
 
+typedef struct intf_thread_t intf_thread_t;
+
 /*****************************************************************************
  * VLCApplication interface
  *****************************************************************************/
@@ -45,4 +47,5 @@
 @property(strong, readonly) NSImage *vlcAppIconImage;
 @property(assign, readonly) BOOL winterHolidaysTheming;
 
+- (void)setIntf:(intf_thread_t *)intf;
 @end


=====================================
modules/gui/macosx/main/VLCApplication.m
=====================================
@@ -31,6 +31,7 @@
 #import "extensions/NSString+Helpers.h"
 
 #import <vlc_configuration.h>
+#import <vlc_interface.h>
 
 /*****************************************************************************
  * VLCApplication implementation
@@ -40,6 +41,7 @@
 {
     NSURL *_appLocationURL;
     NSImage *_vlcAppIconImage;
+    intf_thread_t *_intf;
 }
 
 @end
@@ -74,6 +76,11 @@
     return self;
 }
 
+- (void)setIntf:(intf_thread_t*)intf
+{
+    _intf = intf;
+}
+
 - (void)dealloc
 {
     [NSNotificationCenter.defaultCenter removeObserver:self];
@@ -140,19 +147,7 @@
 - (void)terminate:(id)sender
 {
     [self activateIgnoringOtherApps:YES];
-    [self stop:sender];
-
-    // Trigger event in loop to force evaluating the stop flag
-    NSEvent* event = [NSEvent otherEventWithType:NSApplicationDefined
-                                        location:NSMakePoint(0,0)
-                                   modifierFlags:0
-                                       timestamp:0.0
-                                    windowNumber:0
-                                         context:nil
-                                         subtype:0
-                                           data1:0
-                                           data2:0];
-    [NSApp postEvent:event atStart:YES];
+    libvlc_Quit(vlc_object_instance(_intf));
 }
 
 @end


=====================================
modules/gui/macosx/main/VLCMain.m
=====================================
@@ -141,6 +141,7 @@ NSString * const kVLCPreferencesVersion = @"VLCPreferencesVersion";
 
 static intf_thread_t *p_interface_thread;
 static vlc_preparser_t *p_network_preparser;
+vlc_sem_t g_wait_quit;
 
 intf_thread_t *getIntf()
 {
@@ -178,6 +179,7 @@ int OpenIntf (vlc_object_t *p_this)
             @try {
                 VLCApplication * const application = VLCApplication.sharedApplication;
                 NSCAssert(application != nil, @"VLCApplication must not be nil");
+                [application setIntf:p_intf];
 
                 VLCMain * const main = VLCMain.sharedInstance;
                 NSCAssert(main != nil, @"VLCMain must not be nil");
@@ -192,6 +194,7 @@ int OpenIntf (vlc_object_t *p_this)
             dispatch_semaphore_signal(sem);
             [NSApp run];
         }
+        vlc_sem_post(&g_wait_quit);
     });
     CFRunLoopWakeUp(CFRunLoopGetMain());
 
@@ -208,15 +211,24 @@ void CloseIntf (vlc_object_t *p_this)
             [VLCMain.sharedInstance applicationWillTerminate:nil];
             [VLCMain killInstance];
         }
-        p_interface_thread = nil;
-
         vlc_preparser_Delete(p_network_preparser);
-        p_network_preparser = nil;
+        [NSApp stop:nil];
+        NSEvent* event = [NSEvent otherEventWithType:NSApplicationDefined
+            location:NSMakePoint(0,0)
+            modifierFlags:0
+            timestamp:0.0
+            windowNumber:0
+            context:nil
+            subtype:0
+            data1:0
+            data2:0];
+        [NSApp postEvent:event atStart:YES];
     };
     if (CFRunLoopGetCurrent() == CFRunLoopGetMain())
         release_intf();
     else
         dispatch_sync(dispatch_get_main_queue(), release_intf);
+    vlc_sem_wait(&g_wait_quit);
 }
 
 /*****************************************************************************
@@ -337,7 +349,7 @@ static VLCMain *sharedInstance = nil;
         [self migrateOldPreferences];
     }
 
-    _statusBarIcon = [[VLCStatusBarIcon alloc] init];
+    _statusBarIcon = [[VLCStatusBarIcon alloc] init:_p_intf];
 
     /* on macOS 11 and later, check whether the user attempts to deploy
      * the x86_64 binary on ARM-64 - if yes, log it */


=====================================
modules/gui/macosx/menus/VLCStatusBarIcon.h
=====================================
@@ -21,6 +21,7 @@
  *****************************************************************************/
 
 #import <Cocoa/Cocoa.h>
+#include <vlc_interface.h>
 
 @interface VLCStatusBarIcon : NSObject <NSMenuDelegate>
 
@@ -30,6 +31,7 @@
 @property (strong) IBOutlet NSView *playbackInfoView;
 @property (strong) IBOutlet NSView *controlsView;
 
+- (instancetype)init:(intf_thread_t *)intf;
 - (IBAction)statusBarIconShowMainWindow:(id)sender;
 - (IBAction)statusBarIconTogglePlayPause:(id)sender;
 - (IBAction)statusBarIconNext:(id)sender;


=====================================
modules/gui/macosx/menus/VLCStatusBarIcon.m
=====================================
@@ -34,6 +34,8 @@
 
 @interface VLCStatusBarIcon ()
 {
+    intf_thread_t *_intf;
+
     NSMenuItem *_vlcStatusBarMenuItem;
 
     /* Outlets for Now Playing labels */
@@ -71,10 +73,11 @@
 #pragma mark -
 #pragma mark Init
 
-- (instancetype)init
+- (instancetype)init:(intf_thread_t *)intf;
 {
     self = [super init];
     if (self) {
+        _intf = intf;
         [[NSBundle mainBundle] loadNibNamed:@"VLCStatusBarIconMainMenu" owner:self topLevelObjects:nil];
     }
     return self;
@@ -489,7 +492,7 @@
 // Action: Quit VLC
 - (IBAction)quitAction:(id)sender
 {
-    [NSApplication.sharedApplication terminate:nil];
+    libvlc_Quit(vlc_object_instance(_intf));
 }
 
 - (IBAction)statusBarIconShowMiniAudioPlayer:(id)sender


=====================================
modules/gui/qt/qt.cpp
=====================================
@@ -546,13 +546,7 @@ static void *ThreadCleanup( qt_intf_t *p_intf, CleanupReason cleanupReason );
 
 #ifdef Q_OS_MAC
 /* Used to abort the app.exec() on OSX after libvlc_Quit is called */
-#include "../../../lib/libvlc_internal.h" /* libvlc_SetExitHandler */
 #include <CoreFoundation/CFRunLoop.h>
-static void Abort( void *obj )
-{
-    (void)obj;
-    triggerQuit();
-}
 #endif
 
 /* Open Interface */
@@ -607,9 +601,9 @@ static int OpenInternal( qt_intf_t *p_intf )
     /* */
 #ifdef Q_OS_MAC
     /* Run mainloop on the main thread as Cocoa requires */
-    libvlc_SetExitHandler( vlc_object_instance(p_intf), Abort, p_intf );
     CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopDefaultMode, ^{
         Thread(static_cast<void*>(p_intf));
+        vlc_sem_post(&p_intf->wait_quit);
     });
     CFRunLoopWakeUp(CFRunLoopGetMain());
 #else
@@ -648,8 +642,11 @@ static void CloseInternal( qt_intf_t *p_intf )
     msg_Dbg( p_intf, "waiting for UI thread..." );
 #ifndef Q_OS_MAC
     vlc_join (p_intf->thread, NULL);
+#else
+    vlc_sem_wait(&p_intf->wait_quit);
 #endif
 
+
     //mutex scope
     {
         vlc::threads::mutex_locker locker (lock);
@@ -678,6 +675,7 @@ static int OpenIntfCommon( vlc_object_t *p_this, bool dialogProvider )
         vlc_object_delete(p_intf);
         return VLC_EGENERIC;
     }
+    vlc_sem_init(&p_intf->wait_quit, 0);
     p_intf->intf = intfThread;
     p_intf->b_isDialogProvider = dialogProvider;
     p_intf->isShuttingDown = false;
@@ -917,6 +915,34 @@ static void *Thread( void *obj )
         assert(ret);
     }
 
+    /* Ctrl+Q, or more specifically Command+Q on MacOS is going
+     * through the NSApplication/NSApplicationDelegate selector
+     * applicationShouldTerminate:, which is catched by Qt and
+     * trigger termination of the app.exec() runloop.
+     * We don't want to quit right now and instead trigger
+     * libvlc_Quit() to unload the interface from Close and avoid
+     * racing the window by removing the eventloop before it's
+     * destroyed. */
+    class QuitSpy : public QObject
+    {
+    public:
+        QuitSpy(QObject *parent) : QObject(parent)
+        {
+            qGuiApp->installEventFilter(this);
+        }
+        bool eventFilter(QObject *o, QEvent *e) override
+        {
+            (void)o;
+            if (e->type() == QEvent::Quit)
+            {
+                THEDP->quit();
+                return true;
+            }
+            return false;
+        }
+    };
+    QuitSpy quitSpy(&app);
+
     registerMetaTypes();
 
     //app.setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);


=====================================
modules/gui/qt/qt.hpp
=====================================
@@ -82,6 +82,7 @@ struct qt_intf_t
                                intf_dialog_args_t * );
 
     vlc_thread_t thread;
+    vlc_sem_t wait_quit;
 
     class MainCtx *p_mi;     /* Main Interface, NULL if DialogProvider Mode */
     class QSettings *mainSettings; /* Qt State settings not messing main VLC ones */



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/c52d819d60090ac41d78604e6455e3deaeffb736...3db080cbac1140e86b5acb5f9164e41f4fa34fc5

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/c52d819d60090ac41d78604e6455e3deaeffb736...3db080cbac1140e86b5acb5f9164e41f4fa34fc5
You're receiving this email because of your account on code.videolan.org.


VideoLAN code repository instance


More information about the vlc-commits mailing list