Difference between revisions of "Cocoa Internals/Application"

From Free Pascal wiki
Jump to navigationJump to search
Line 3: Line 3:
 
NSApp doesn't provide any corresponding counter parts, thus it's necessary to process events loops manually.
 
NSApp doesn't provide any corresponding counter parts, thus it's necessary to process events loops manually.
 
==Event Loop and Application==
 
==Event Loop and Application==
 +
===Sync (MainThreadWake), PostMessage and SendMessage===
 +
Originally 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'''
 +
 +
Posting 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.
 +
 +
Each tracking loop however, is written differently. And some places reacting on "unexpected" events differently.
 +
 +
Trying to hide "unexpected" events from a loop might work for one implementation, but not the other.
 +
 +
The only solution that satisfies every loop is NOT to post any custom events at all.
 +
 +
The implementation was changed from using a custom event to '''performSelectorOnMainThread''' method.
 +
 +
For syncing with the main thread there's '''lclSyncCheck:''' method introduced for '''TCocoaApplication'''.
 +
 +
For post and send events, there's a message '''TLCLEventMessage''' class.
 +
 +
 
===Manual Event Loop===
 
===Manual Event Loop===
 
WARNING the method described below is no longer used.
 
WARNING the method described below is no longer used.

Revision as of 15:31, 11 June 2019

Cocoa'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.

NSApp doesn't provide any corresponding counter parts, thus it's necessary to process events loops manually.

Event Loop and Application

Sync (MainThreadWake), PostMessage and SendMessage

Originally 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

Posting 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.

Each tracking loop however, is written differently. And some places reacting on "unexpected" events differently.

Trying to hide "unexpected" events from a loop might work for one implementation, but not the other.

The only solution that satisfies every loop is NOT to post any custom events at all.

The implementation was changed from using a custom event to performSelectorOnMainThread method.

For syncing with the main thread there's lclSyncCheck: method introduced for TCocoaApplication.

For post and send events, there's a message TLCLEventMessage class.


Manual Event Loop

WARNING the method described below is no longer used.

Cocoa Widgetset implements even loops manually. Apple documentation advises that it's possible, though could be complicated.

The actual event processing code could be found at AppWaitMessage and AppProcessMessage.

procedure TCocoaWidgetSet.AppWaitMessage;
var
  event : NSEvent;
  pool:NSAutoReleasePool;
begin
  pool := NSAutoreleasePool.alloc.init;
  event := NSApp.nextEventMatchingMask_untilDate_inMode_dequeue(NSAnyEventMask, NSDate.distantFuture, NSDefaultRunLoopMode, true);
  if event <> nil then
  begin
    if (event.type_ = NSApplicationDefined) and (event.subtype = LCLEventSubTypeMessage) and (event.data1 = LM_NULL) and (event.data2 = AppHandle) then
      CheckSynchronize
    else
      NSApp.sendEvent(event);
    NSApp.updateWindows;
  end;
  pool.release;
end;

Sub-classing NSApplication

WARNING the method described below is no longer used.

With a manual event loop implementation, there's another problem showed up.

NSWindows 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:

Important
Many AppKit classes rely on the NSApplication class and may not work properly until this class is fully initialized.
As a result, you should not, for example, attempt to invoke methods of other AppKit classes from an initialization method of an NSApplication subclass.

If 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)

There are two options to make NSApplication running flag to set to true:

  • use NSApp default run method (which could not be used, due to the need of manual event processing)
  • subclass NSApplication, override "isRunning" (getter of "running" property)

The sub-classing option is used, introducing TCocoaApplication class. It overrides two methods:

  • run - the code is actually LCL Loop procedure, which would process ObjC events loop manually
  • isRunning - returns true, if run method was actually called.

Clipboard

In Cocoa, clipboard is named Pasteboard (class NSPasteboard);

The global (system-wide) pasteboard is available via NSPasteboard.generalPasteboard method. Primary and Secondary selection boards are just unique named NSPasteboards allocated at start.

The whole implementation of API is in TCocoaWidgetSet class. (todo: could/should be separated)

The 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)

There'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)

See Also