Sign in Join
Wiki copytemporary

Cocoa Internals/Application: what changed

Revision 3 to 4. 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-```
13+```text
1414*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason:
1515'Cannot update for observer <NSFrontmostDocumentWindowObserver 0x7fff8ec52208> for the key path "mainWindow.representedURL"
1616from <TCocoaApplication 0x100d089a0>, most likely because the value for the key "mainWindow" has changed without an appropriate
1717KVO notification being sent. Check the KVO-compliance of the TCocoaApplication class.'
1818```
1919
2020Crash 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.
2121
2222Error 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)
2323
2424NSFrontmostDocumentWindowObserver 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'.
2525
2626The 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.
2727
2828Suggested fix (patch attached) is to run through NSApplication.run first and then jump into our aloop().
2929
3030Accomplished 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.
3131
3232### Native Loop (COCOALOOPNATIVE)
3333
3434The 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.
3535
3636**Downside**
3737
3838- 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).
3939
4040**todo:**
4141
4242- test application termination with Modal loops involved. (**stop:** needs to be called for EVERY modal loop started) passed
4343- check exception catching for "sync" methods. (as those are not really events) passed
4444
4545### Manual Event Loop (COCOALOOPOVERRIDE)
4646
4747
4848**WARNING** the method described below is no longer used.
4949
5050Cocoa Widgetset implements even loops manually. Apple documentation advises that it's possible, though could be complicated.
5151
5252The actual event processing code could be found at AppWaitMessage and AppProcessMessage.
5353
5454```delphi
5555procedure TCocoaWidgetSet.AppWaitMessage;
5656var
5757 event : NSEvent;
5858 pool:NSAutoReleasePool;
5959begin
6060 pool := NSAutoreleasePool.alloc.init;
6161 event := NSApp.nextEventMatchingMask_untilDate_inMode_dequeue(NSAnyEventMask, NSDate.distantFuture, NSDefaultRunLoopMode, true);
6262 if event <> nil then
6363 begin
6464 if (event.type_ = NSApplicationDefined) and (event.subtype = LCLEventSubTypeMessage) and (event.data1 = LM_NULL) and (event.data2 = AppHandle) then
6565 CheckSynchronize
6666 else
6767 NSApp.sendEvent(event);
6868 NSApp.updateWindows;
6969 end;
7070 pool.release;
7171end;
7272```
7373
7474### Sub-classing NSApplication
7575
7676
7777**WARNING** the method described below is no longer used.
7878
7979With a manual event loop implementation, there's another problem showed up.
8080
8181NSWindows 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:
8282
8383
8484
8585**Important**
8686
8787Many AppKit classes rely on the NSApplication class and may not work properly until this class is fully initialized.
8888
8989As a result, you should not, for example, attempt to invoke methods of other AppKit classes from an initialization method of an NSApplication subclass.
9090
9191If 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))
9292
9393There are two options to make NSApplication [running](https://developer.apple.com/documentation/appkit/nsapplication/1428759-running?language=objc) flag to set to true:
9494
9595- 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)
9696- subclass NSApplication, override "isRunning" (getter of "running" property)
9797
9898The sub-classing option is used, introducing TCocoaApplication class. It overrides two methods:
9999
100100- **run** - the code is actually LCL Loop procedure, which would process ObjC events loop manually
101101- **isRunning** - returns true, if **run** method was actually called.
102102
103103## Termination
104104
105105Normally, **NSApplication.run:** method never returns after being called.
106106
107107NSApplication 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.
108108
109109The 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.
110110
111111## Sync (MainThreadWake), PostMessage and SendMessage
112112
113113Originally 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**
114114
115115Posting 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.
116116
117117Each tracking loop however, is written differently. And some places reacting on "unexpected" events differently.
118118
119119Trying to hide "unexpected" events from a loop might work for one implementation, but not the other.
120120
121121The only solution that satisfies every loop is NOT to post any custom events at all.
122122
123123The implementation was changed from using a custom event to **performSelectorOnMainThread** method.
124124
125125For syncing with the main thread there's **lclSyncCheck:** method introduced for **TCocoaApplication**.
126126
127127For 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).
128128
129129### Clogging with Dialogs
130130
131131There'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.
132132
133133## Post Event Processing
134134
135135There a number of routines performed after processing each event.
136136
137137### Clipboard
138138
139139In Cocoa, clipboard is named Pasteboard (class NSPasteboard);
140140
141141The global (system-wide) pasteboard is available via NSPasteboard.generalPasteboard method. Primary and Secondary selection boards are just unique named NSPasteboards allocated at start.
142142
143143The whole implementation of API is in TCocoaWidgetSet class. (todo: could/should be separated)
144144
145145The 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)
146146
147147There'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)
148148
149149### ToCollect
150150
151151On 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).
152152
153153If 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).
154154
155155In 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.
156156
157157Keep 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.
158158
159159### Focus Change
160160
161161The change if control focus is made post event basis to prevent endless loops of focus-fight.
162162
163163Focus-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.
164164
165165To avoid that, the focus switch is verified after event processing.
166166
167167## System Wide Events
168168
169169There's a number of system-wide events that are reported via Application object.
170170
171171### End of User Session
172172
173173Usually triggered by a user logging out or the entire system shutdown.
174174
175175Supported since <https://gitlab.com/freepascal.org/lazarus/lazarus/-/issues/34798>
176176
177177- **IntfQueryEndSession**(var Cancel: Boolean);
178178- **IntfEndSession**;
179179
180180### Application Active Status
181181
182182- **IntfAppActivate**(const Async: Boolean = False); - the event when application gets "active" by gaining the keyboard focus.
183183
184184
185185
186186supported by processing of **applicationDidBecomeActive** message
187187
188188- **IntfAppDeactivate**(const Async: Boolean = False); - the even when application gets "deactivated" by losing keybaord focus.
189189
190190
191191
192192supported by processing of **applicationDidResignActive** message
193193
194194### Application maximization
195195
196196- **IntfAppMinimize**;
197197- **IntfAppRestore**;
198198
199199### Files Drop
200200
201201Issued by the system file exchange (drag/drop mechanism). Files can be dropped on the Application icon (shown in the system Dock)
202202
203203- **IntfDropFiles**(const FileNames: Array of String);
204204
205205
206206
207207supported by processing **application:openFiles** files messages.
208208
209209the list of files is also passed to the main form of the application (similar IntfDropFiles event), by TApplication method is called first.
210210
211211### System Settings Changes
212212
213213- **IntfSettingsChange**
214214
215215
216216
217217not supported
218218
219219- **IntfThemeOptionChange**
220220
221221
222222
223223not supported (need to be supported for Light/Dark theme changes?)
224224
225225## See Also
226226
227227- [[Cocoa Internals]]
228228- [NSApplication](https://developer.apple.com/documentation/appkit/nsapplication?language=objc) - apple reference documentation for NSApplication class.
229229
230230---
231231*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).*
232232Category: Cocoa
233233Category: macOS

Back to history