Sign in Join
Wiki copytemporary

Cocoa Internals/Application: what changed

Revision 2 to 3. Removed lines have a minus sign; added lines have a plus sign.

OldNewLine
11Cocoa's Application API pretty much matches what LCL provides. It could be possible to simply call NSApp apis, that correspond to LCL APIs. However, there's a major difference, that makes a simple solution not possible. LCL Application provides a methods **HandleMessages** and **ProcessMessagess** allowing a manual control over the event-loop.
22
33NSApp doesn't provide any corresponding counter parts, thus it's necessary to process events loops manually.
44
55## Event Loop
66
77The technical decisions over TCocoaApplication implementation are described below.
88
99### HiJacking (COCOALOOPHIJACK)
1010
1111macOS 10.15 beta is throwing a KVO inconsistency error. The issue <https://bugs.freepascal.org/view.php?id=35702>
1212
13-`*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: `\
14-`'Cannot update for observer <NSFrontmostDocumentWindowObserver 0x7fff8ec52208> for the key path "mainWindow.representedURL" `\
15-`from <TCocoaApplication 0x100d089a0>, most likely because the value for the key "mainWindow" has changed without an appropriate `\
16-`KVO notification being sent. Check the KVO-compliance of the TCocoaApplication class.'`
13+```
14+*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason:
15+'Cannot update for observer <NSFrontmostDocumentWindowObserver 0x7fff8ec52208> for the key path "mainWindow.representedURL"
16+from <TCocoaApplication 0x100d089a0>, most likely because the value for the key "mainWindow" has changed without an appropriate
17+KVO notification being sent. Check the KVO-compliance of the TCocoaApplication class.'
18+```
1719
1820Crash can occur on on startup (for our app) or also reported to happen when a two form app when opening second form as modal dialog.
1921
2022Error is caused because Cocoa currently doesn't run through base level NSApplication.run and thus appropriate ._isrunning flag is not set (notice that this also required the override of NSApplication.isRunning done for Mantis 32177)
2123
2224NSFrontmostDocumentWindowObserver registers for all changes to mainWindow.representedURL. During initialization of NSApplication (down inside Cocoa code) calls to willChangeValueForKey() and didChangevalueForKey() are made to '_mainWindow' or 'mainWindow' depending on state of _isRunning flag (not from calls to .isRunning but from the memory locaiton where it is stored) and NSapplication.isActive. Because we don't go through NSApplication.run the bit for .isRunning is never set and calls to willChangevalueForKey(), didChangevalueForKey() do not happen appropriately for 'mainWindow'.
2325
2426The crash occurs when _handleApplicationActivated calls removeObserverForKeyPath() on an observer object that is supposed to be set up for 'mainWindow' but it was never actually set up i.e. addObserverForKeyPath() was never called. Documentation for removeObserverForKeyPath() indicates that removing an object that hasn't been set up with addObserverForKeyPath() is an error condition. That is the condition we are hitting.
2527
2628Suggested fix (patch attached) is to run through NSApplication.run first and then jump into our aloop().
2729
2830Accomplished by removing TCocoaApplication.isRunning and TCocoaApplication.run methods thus allowing NSApplication.run to be called and set up NSRunloop and make first call to nextEventMatching:unitDate, etc. On first pass through nextEventMatching. (as caught by isRun = false) we grab control and call aloop.
2931
3032### Native Loop (COCOALOOPNATIVE)
3133
3234The approach doesn't interfere with Cocoa loop processing. The approach also DOES NOT use LCL event processing loop (as LCL event loop blocks Cocoa loop from running). Instead approach is catching FPC exceptions and handles them through LCL routines. Also end of event processing should cause Idle event to be performed. That lets Cocoa loop to run without interruptions.
3335
3436**Downside**
3537
3638- Since **Application.RunLoop** is not used, every time it's updated, CocoaWS side needs to be updated also. It's possible however, to Modify LCL to make the task easier. (which might look like a ultimate solution).
3739
3840**todo:**
3941
4042- test application termination with Modal loops involved. (**stop:** needs to be called for EVERY modal loop started) passed
4143- check exception catching for "sync" methods. (as those are not really events) passed
4244
4345### Manual Event Loop (COCOALOOPOVERRIDE)
4446
4547
4648**WARNING** the method described below is no longer used.
4749
4850Cocoa Widgetset implements even loops manually. Apple documentation advises that it's possible, though could be complicated.
4951
5052The actual event processing code could be found at AppWaitMessage and AppProcessMessage.
5153
5254```delphi
5355procedure TCocoaWidgetSet.AppWaitMessage;
5456var
5557 event : NSEvent;
5658 pool:NSAutoReleasePool;
5759begin
5860 pool := NSAutoreleasePool.alloc.init;
5961 event := NSApp.nextEventMatchingMask_untilDate_inMode_dequeue(NSAnyEventMask, NSDate.distantFuture, NSDefaultRunLoopMode, true);
6062 if event <> nil then
6163 begin
6264 if (event.type_ = NSApplicationDefined) and (event.subtype = LCLEventSubTypeMessage) and (event.data1 = LM_NULL) and (event.data2 = AppHandle) then
6365 CheckSynchronize
6466 else
6567 NSApp.sendEvent(event);
6668 NSApp.updateWindows;
6769 end;
6870 pool.release;
6971end;
7072```
7173
7274### Sub-classing NSApplication
7375
7476
7577**WARNING** the method described below is no longer used.
7678
7779With a manual event loop implementation, there's another problem showed up.
7880
7981NSWindows are behaving differently depending if NSApplication has started or not. (running property returning TRUE or FALSE). The Apple documentation doesn't mention that specifically, except for the following note:
8082
8183
8284
8385**Important**
8486
8587Many AppKit classes rely on the NSApplication class and may not work properly until this class is fully initialized.
8688
8789As a result, you should not, for example, attempt to invoke methods of other AppKit classes from an initialization method of an NSApplication subclass.
8890
8991If NSApplication is not running, then NSWindows would not pass the focus to another Window with the application. Instead they would keep the focus (if hidden) or make no window a focused window (if closed). (see [\#32177](https://bugs.freepascal.org/view.php?id=32177))
9092
9193There are two options to make NSApplication [running](https://developer.apple.com/documentation/appkit/nsapplication/1428759-running?language=objc) flag to set to true:
9294
9395- use NSApp default [run](https://developer.apple.com/documentation/appkit/nsapplication/1428631-run?language=objc) method (which could not be used, due to the need of manual event processing)
9496- subclass NSApplication, override "isRunning" (getter of "running" property)
9597
9698The sub-classing option is used, introducing TCocoaApplication class. It overrides two methods:
9799
98100- **run** - the code is actually LCL Loop procedure, which would process ObjC events loop manually
99101- **isRunning** - returns true, if **run** method was actually called.
100102
101103## Termination
102104
103105Normally, **NSApplication.run:** method never returns after being called.
104106
105107NSApplication provides a special **terminate:** method to terminate Cocoa App. The method terminates the running process (allowing **run:** never to return). It should **NEVER** be used for FPC environment, as FreePascal runtime should call unit finalization.
106108
107109The way to make **run:** method to return is use **stop:** method. Which **stops** *the current* event loop, and should be called multiple times with multiple event loops (such as modal) are used.
108110
109111## Sync (MainThreadWake), PostMessage and SendMessage
110112
111113Originally the syncing with the main thread, as well as Post and SendMessage implementations were based on creating a custom **NSApplicationDefined** event with sub type set to **LCLEventSubTypeMessage**
112114
113115Posting the custom event into an event loop on Cocoa might have a certain undesired consequences. The major problem is with event processing for tracking mouse actions. Unlike WinAPI, where it is expected for to process standalone events, Cocoa developer should actually run a while loop. The loop should catch mouse events and stop as soon as mouse is released.
114116
115117Each tracking loop however, is written differently. And some places reacting on "unexpected" events differently.
116118
117119Trying to hide "unexpected" events from a loop might work for one implementation, but not the other.
118120
119121The only solution that satisfies every loop is NOT to post any custom events at all.
120122
121123The implementation was changed from using a custom event to **performSelectorOnMainThread** method.
122124
123125For syncing with the main thread there's **lclSyncCheck:** method introduced for **TCocoaApplication**.
124126
125127For post and send events, there's a message **TLCLEventMessage** class, which **performSelectorOnMainThread:** method is called. The class provides fields to store WParam, LParam and Result (for SendMessage).
126128
127129### Clogging with Dialogs
128130
129131There's an issue with a clogged sync processing. If there are two messages sent to the main thread, where both are causing a modal window to be shown. (i.e. an error message). Then if the second one arrives, while the first one is still showing the modal window, the application terminates.
130132
131133## Post Event Processing
132134
133135There a number of routines performed after processing each event.
134136
135137### Clipboard
136138
137139In Cocoa, clipboard is named Pasteboard (class NSPasteboard);
138140
139141The global (system-wide) pasteboard is available via NSPasteboard.generalPasteboard method. Primary and Secondary selection boards are just unique named NSPasteboards allocated at start.
140142
141143The whole implementation of API is in TCocoaWidgetSet class. (todo: could/should be separated)
142144
143145The older (10.5 and earlier) APIs are used for operations. (starting with 10.6 Apple suggests using the new API, but the old one has not been deprecated so far)
144146
145147There's not NSPasteboard change notification in place (unlike Windows), thus the only way to track, if the content got changed (in LCL/Windows terms - clipboard ownership was lost) is through checking "changeCount" property. The property is checked on **EVERY** event processed (which might impact efficiency). The only way to get rid of that is to rewrite Clipboard LCL object. (instead of relying on a notification - it should check for the change at "Get" data calls)
146148
147149### ToCollect
148150
149151On certain conditions a control can destroy itself while processing its own event. (For example an Edit field of a "authentication" dialog. If enter is pressed, the OnKeyDown event to close the form, causing its all children controls, including the edit field itself).
150152
151153If such things occur, the **callback** object (linking NSObject and LCL Target) can be released (in DestoryHandle call). However, the **callback** object must not be freed, as Key processing routine (the one that started OnKeyDown) is not yet over. The same concept applies for other processing routines (i.e. Mouse handling, etc).
152154
153155In order to prevent crashed due to access of freed object, the destruction of Callbacks objects is delayed until the end of event processing. Cocoa widgetset interface provides following method **AddToCollect** to add an object to be released after the event processing.
154156
155157Keep in mind that events handling might be recursive, thus a destruction of an object only occurs when the processing of the event that added object to the collection is finished.
156158
157159### Focus Change
158160
159161The change if control focus is made post event basis to prevent endless loops of focus-fight.
160162
161163Focus-fight occurs, a focus change notification is sent at the time when the focus is actually switched. It's typical for Cocoa controls to temporary switch focus to Window. (in Cocoa world, Window is used a "back-up" focus target. As it always allows to be focused... even with canBecomeKeyWindow false?). If LCL is notified by Windows getting the focus, it would attempt to switch the focus to one of its child control. This would typically be the same control as it's now. Eventually Cocoa would try to switch the focus back to Window, causing the recursive focus-fight with application responsiveness and eventual crash.
162164
163165To avoid that, the focus switch is verified after event processing.
164166
165167## System Wide Events
166168
167169There's a number of system-wide events that are reported via Application object.
168170
169171### End of User Session
170172
171173Usually triggered by a user logging out or the entire system shutdown.
172174
173175Supported since <https://gitlab.com/freepascal.org/lazarus/lazarus/-/issues/34798>
174176
175177- **IntfQueryEndSession**(var Cancel: Boolean);
176178- **IntfEndSession**;
177179
178180### Application Active Status
179181
180182- **IntfAppActivate**(const Async: Boolean = False); - the event when application gets "active" by gaining the keyboard focus.
181183
182184
183185
184186supported by processing of **applicationDidBecomeActive** message
185187
186188- **IntfAppDeactivate**(const Async: Boolean = False); - the even when application gets "deactivated" by losing keybaord focus.
187189
188190
189191
190192supported by processing of **applicationDidResignActive** message
191193
192194### Application maximization
193195
194196- **IntfAppMinimize**;
195197- **IntfAppRestore**;
196198
197199### Files Drop
198200
199201Issued by the system file exchange (drag/drop mechanism). Files can be dropped on the Application icon (shown in the system Dock)
200202
201203- **IntfDropFiles**(const FileNames: Array of String);
202204
203205
204206
205207supported by processing **application:openFiles** files messages.
206208
207209the list of files is also passed to the main form of the application (similar IntfDropFiles event), by TApplication method is called first.
208210
209211### System Settings Changes
210212
211213- **IntfSettingsChange**
212214
213215
214216
215217not supported
216218
217219- **IntfThemeOptionChange**
218220
219221
220222
221223not supported (need to be supported for Light/Dark theme changes?)
222224
223225## See Also
224226
225227- [[Cocoa Internals]]
226228- [NSApplication](https://developer.apple.com/documentation/appkit/nsapplication?language=objc) - apple reference documentation for NSApplication class.
227229
228230---
229231*From the Free Pascal wiki, page "Cocoa Internals/Application", as it stood on 2022-02-21 after a change by Skalogryz, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*
230232Category: Cocoa
231233Category: macOS

Back to history