View Issue Details

IDProjectCategoryView StatusLast Update
0035986LazarusWidgetsetpublic2019-08-22 05:50
ReporterZoë PetersonAssigned ToDmitry Boyarintsev 
PrioritynormalSeverityminorReproducibilityalways
Status resolvedResolutionno change required 
Product Version2.0.4Product Build 
Target VersionFixed in Version 
Summary0035986: Treat Alt/Ctrl+Left Click as Right/Middle Click
DescriptionCocoa apps all treat Ctrl+Left Click as a Right Click and Option+Left Click as a Middle Click. Ctrl+left clicking should bring up the context menu, for example, and Option+Clicking in Safari will open a link in a new tab. LCLCARBON mapped the buttons automatically, and this patch does the same thing for LCLCOCOA.

In LCLCARBON, you could distinguish the fake state by looking at the TShiftState instead of the TMouseButton, and I've kept that behavior here. So:

Left Click => mbLeft + []
Ctrl+Right Click => mbRight + [ssCtrl, ssRight]
Ctrl+Left Click => mbRight + [ssCtrl, ssLeft]

We never use that fact, but it seemed reasonable to keep it.
Steps To ReproduceUsing the attached sample project, left click, right click, and ctrl+left click on the form. The window caption will change to indicate what's currently being passed in, and it will show a context menu on a right click.

In LCLCARBON, Ctrl+Left Click is reported as mbRight + [ssCtrl, ssLeft]. In the current LCLCOCOA, it's reported as mbLeft + [ssCtrl, ssLeft] and it doesn't show the context menu.
TagsNo tags attached.
Fixed in Revision
LazTarget-
WidgetsetCocoa
Attached Files
  • ctrl-click-remap.zip (110,878 bytes)
  • ctrl-click-remap.patch (612 bytes)
    --- a/lcl/interfaces/cocoa/cocoawscommon.pas
    +++ b/lcl/interfaces/cocoa/cocoawscommon.pas
    @@ -947,7 +947,13 @@ begin
         // high word of XButton messages indicate the X button which is pressed
         Msg.Keys := Msg.Keys or (MButton - 2) shl 16;
         MButton := 3;
    -  end;
    +  end
    +  else if MButton = 0 then
    +    // treat ctrl+left click as right click and alt+left click as middle click
    +    if Event.modifierFlags and NSControlKeyMask <> 0 then
    +      MButton := 1
    +    else if Event.modifierFlags and NSAlternateKeyMask <> 0 then
    +      MButton := 2;
     
       lEventType := Event.type_;
       if AForceAsMouseUp then
    
  • ctrl-click-remap-v2.patch (947 bytes)
    --- a/lcl/interfaces/cocoa/cocoawscommon.pas
    +++ b/lcl/interfaces/cocoa/cocoawscommon.pas
    @@ -177,6 +177,7 @@ uses
     
     var
       LastMouse: TLastMouseInfo;
    +  LastMouseLeftButtonAsRight: Boolean;
     
     function ButtonStateToShiftState(BtnState: PtrUInt): TShiftState;
     begin
    @@ -953,6 +954,20 @@ begin
       if AForceAsMouseUp then
         lEventType := NSLeftMouseUp;
     
    +  // treat ctrl+left button as right button
    +  if (lEventType = NSLeftMouseDown) and
    +     (Event.modifierFlags and NSControlKeyMask <> 0) then
    +    LastMouseLeftButtonAsRight := True;
    +  if LastMouseLeftButtonAsRight then
    +  begin
    +    if MButton = 0 then
    +      MButton := 1;
    +    if Msg.Keys and MK_LBUTTON <> 0 then
    +      Msg.Keys := (Msg.Keys or MK_RBUTTON) and not MK_LBUTTON;
    +    if lEventType = NSLeftMouseUp then
    +      LastMouseLeftButtonAsRight := False;
    +  end;
    +
       Result := Result or (BlockCocoaUpDown and not AOverrideBlock);
       case lEventType of
         NSLeftMouseDown,
    

Activities

Zoë Peterson

2019-08-19 23:52

reporter  

ctrl-click-remap.zip (110,878 bytes)
ctrl-click-remap.patch (612 bytes)
--- a/lcl/interfaces/cocoa/cocoawscommon.pas
+++ b/lcl/interfaces/cocoa/cocoawscommon.pas
@@ -947,7 +947,13 @@ begin
     // high word of XButton messages indicate the X button which is pressed
     Msg.Keys := Msg.Keys or (MButton - 2) shl 16;
     MButton := 3;
-  end;
+  end
+  else if MButton = 0 then
+    // treat ctrl+left click as right click and alt+left click as middle click
+    if Event.modifierFlags and NSControlKeyMask <> 0 then
+      MButton := 1
+    else if Event.modifierFlags and NSAlternateKeyMask <> 0 then
+      MButton := 2;
 
   lEventType := Event.type_;
   if AForceAsMouseUp then

Dmitry Boyarintsev

2019-08-20 17:30

developer   ~0117746

note that TextEdit works differently for RightClick vs LeftClick+Control

RightClick simply shows the context menu, where
LeftClick+Control also selects the word under the cursor and shows the context menu.

Dmitry Boyarintsev

2019-08-20 17:45

developer   ~0117747

Last edited: 2019-08-20 17:46

View 2 revisions

Clarification requested.
Option+LeftClicking in Safari tries to download the link. It doesn't open it in the new tab (like a middle click does).
Also, Option+Left clicking in Safari does nothing on the tab. MiddleClick closes the tab.

It doesn't seem that "option+left click" is supposed to do anything.

Neither Apple guidelines define a middle click in any manner:
https://developer.apple.com/design/human-interface-guidelines/macos/user-interaction/mouse-and-trackpad/
(vs Primary and Secondary clicks)

Dmitry Boyarintsev

2019-08-20 17:46

developer   ~0117748

one need to note that "secondary" click is not "double click". it's either Right mouse click or Left mouse click.

Zoë Peterson

2019-08-20 18:03

reporter   ~0117749

In my testing in TextEdit on macOS 10.14 both clicks work identically:

Left click: Move the caret to be under the mouse cursor
Right click / Left click + Ctrl: If the cursor is not over a selection, select the word or whitespace below it. Show the context menu.

The catch is that if there isn't a selection but the mouse cursor is over the caret, it considers that the selection. So if you move the mouse around and randomly right click it will always select what's under the cursor, but if you left click and then immediately right click it shows the menu without expanding the selection.

Zoë Peterson

2019-08-20 18:07

reporter   ~0117750

I'm fine with dropping the Option+Left Clicking part of the patch. I included it because the Carbon version did, but it isn't something I've ever used and the customer who reported the lack of Ctrl+Left click support didn't mention it.

Dmitry Boyarintsev

2019-08-21 03:52

developer   ~0117758

1) if Option+Left <> Middle, then why should we trust Carbon code in the first place?! (and option+left=middle is just wrong)
It might have been applied (r14324) as a work around in order to be able to show a context menu.
(the revision contains the author name, but not the reasoning or a bug report behind the patch)

2) AppKit doesn't substitute Ctrl+Left as mouseRightDown or mouseRightUp.
(for example, "Primary Button" can be switched between "Left" and "Right". If it's down the actual mouseLeft and mouseRight actions will be swapped, within AppKit).
AppKit doesn't need the substitution because it has it a separate method for showing the Context menu.
Which would be called by the AppKit for control+left, and is emulated for control+left by CocoaWS.

Anyway. The patch IS applicable, since it somewhat matches the description of TMouseButton
https://lazarus-ccr.sourceforge.io/docs/lcl/controls/tmousebutton.html
as "LOGICAL" button, (meaning it doesn't have to match the physical button).

But, it might cause issues where the code is "unaware" of macOS specifics.
I.e.
  if buttom - mbRight then begin
     if (ssCtrl in ShiftState) then ...
     else ...;

Do you've an ability to check how Qt for Mac handles Ctrl+Left?

Dmitry Boyarintsev

2019-08-21 04:32

developer   ~0117759

On the other hand, it cannot be used.
For example: a user hold Control and clicks left mouse -> that's expected to be MouseDown mbRight.
But then user released Control.
and only after that release mouse. As a result, there's MouseUp mbLeft.

Dmitry Boyarintsev

2019-08-21 06:11

developer   ~0117760

The proper implementation would, if "OnClick" event had TMouseButton method.
For such method it would make total sense to substitute Ctrl+Left as Right.

Instead of OnClick (or OnRightClick), there's OnContextPopup method. Which is being called on either RightClick or Ctrl+LeftClick.

Zoë Peterson

2019-08-21 20:24

reporter  

ctrl-click-remap-v2.patch (947 bytes)
--- a/lcl/interfaces/cocoa/cocoawscommon.pas
+++ b/lcl/interfaces/cocoa/cocoawscommon.pas
@@ -177,6 +177,7 @@ uses
 
 var
   LastMouse: TLastMouseInfo;
+  LastMouseLeftButtonAsRight: Boolean;
 
 function ButtonStateToShiftState(BtnState: PtrUInt): TShiftState;
 begin
@@ -953,6 +954,20 @@ begin
   if AForceAsMouseUp then
     lEventType := NSLeftMouseUp;
 
+  // treat ctrl+left button as right button
+  if (lEventType = NSLeftMouseDown) and
+     (Event.modifierFlags and NSControlKeyMask <> 0) then
+    LastMouseLeftButtonAsRight := True;
+  if LastMouseLeftButtonAsRight then
+  begin
+    if MButton = 0 then
+      MButton := 1;
+    if Msg.Keys and MK_LBUTTON <> 0 then
+      Msg.Keys := (Msg.Keys or MK_RBUTTON) and not MK_LBUTTON;
+    if lEventType = NSLeftMouseUp then
+      LastMouseLeftButtonAsRight := False;
+  end;
+
   Result := Result or (BlockCocoaUpDown and not AOverrideBlock);
   case lEventType of
     NSLeftMouseDown,

Zoë Peterson

2019-08-21 20:24

reporter   ~0117770

Here's a new version of the patch that matches LCLQT on macOS and drops the Options+Left click behavior.

The remapped state is now maintained until left mouse up, so doing "Ctrl+Left down, Ctrl up, Left up" will send it as a right mouse up.

Both LCLQT and LCLCARBON include ssCtrl if Ctrl is held down, regardless of the remapping occurring. mbRight + [ssRight] is always a plain right click, but mbRight + [ssRight, ssCtrl] could be either an actual Ctrl+Right click or Ctrl+Left click:

Left Click => mbLeft [ssLeft]
Right Click => mbRight [ssRight]
Ctrl+Left Click => mbRight [ssCtrl, ssRight]
Ctrl+Right Click => mbRight [ssCtrl, ssRight]

I also added code to update Shift to include ssRight instead of ssLeft when appropriate, which matches LCLQT but not LCLCARBON. That makes it impossible to detect the remapping, but makes the state consistent and fixes "Ctrl+Left down, Right down" to send "mbRight + [ssCtrl, ssRight]" instead of "mbRight + [ssCtrl, ssLeft, ssRight]" for the second mouse down. That does mean that if you release one of the buttons in that state, the first MouseUp will see "mbRight + [ssRight]" instead of "mbRight + []", which is weird but correct.

Qt does the mapping automatically, so LCLQT doesn't have or need any extra code to behave like this.

I verified that it also works correctly if the System Preferences "Primary mouse button" is set to "Right" instead of "Left". When that's set, the system just switches which physical button corresponds with which button indexes, so clicking on the right mouse button sends an NSLeftMouseDown event. We don't have to do anything special and the resulting output of my tests is identical as long as I swap button presses.

If someone has code that's not macOS aware but runs on macOS and has trouble with this patch, I suggest we let them speak up once this gets merged. The behavior has existed in LCLCARBON for 11 years and LCLQT since its creation, so it doesn't seem like it's causing a lot of problems. They should also update their code to make it macOS aware, since it'll be confusing to anyone used to the default system behavior unless they do.

Here's the full list of tests I've done and the event order and state passed in:

// Left click
Down: mbLeft+[ssLeft]
Click
Up: mbLeft

// Right click
Down: mbRight+[ssRight]
Up: mbRight

// Ctrl+Left click
Down: mbRight+[ssCtrl,ssRight]
Up: mbRight+[ssCtrl]

// Ctrl+Right click
Down: mbRight+[ssCtrl,ssRight]
Up: mbRight+[ssCtrl]

// Ctrl+Left down, Ctrl up, Left up
Down: mbRight+[ssCtrl,ssRight]
Up: mbRight

// Left down, Right click, Left up
Down: mbLeft+[ssLeft]
Down: mbRight+[ssLeft,ssRight]
Up: mbRight+[ssLeft]
Click
Up: mbLeft

// Left down, Right down, Left up, Right up
Down: mbLeft+[ssLeft]
Down: mbRight+[ssLeft,ssRight]
Click
Up: mbLeft+[ssRight]
Up: mbRight

// Ctrl+Left down, Right click, Left up
Down: mbRight+[ssCtrl,ssRight]
Down: mbRight+[ssCtrl,ssRight]
Up: mbRight+[ssCtrl,ssRight]
Up: mbRight+[ssCtrl]

// Ctrl+Left down, Ctrl up, Right click, Left up
Down: mbRight+[ssCtrl,ssRight]
Down: mbRight+[ssRight]
Up: mbRight+[ssRight]
Up: mbRight

Dmitry Boyarintsev

2019-08-21 21:52

developer   ~0117771

Ctrl+Left = mbRight+[ssCtrl,ssRight]
that's not Carbon compatible.
and it cannot be distinguished from Ctrl+Right

Zoë Peterson

2019-08-21 23:52

reporter   ~0117775

> that's not Carbon compatible.

Your previous asked "why should we trust Carbon code in the first place?! (and option+left=middle is just wrong)". IMO, after looking into it, we shouldn't. Exact LCLCARBON compatibility is not desirable. It reports different values depending on whether you check Button or Shift, and doesn't send mbRight on MouseUp. Those are both bad and unexpected, and I believe they were unintentional oversights.

> and it cannot be distinguished from Ctrl+Right

That is true for LCLQT on macOS too.


I don't think there's a right answer here. I first ran in to this because Ctrl+LeftClicking on a selection in our text editor cleared the selection before showing the context menu. SynEdit under LCLCOCOA has the exact same bug. Anything that looks at Button and not Shift will have similar problems. We have a bunch of controls that are affected. To fix it, we have to check for ssCtrl before doing normal mbLeft handling, and while that's easy to do, I don't think most LCL applications will know to do it. It also pushes macOS-specific code up to all of those places. This patch makes it easier for them to work correctly.

On the other hand, a game would need to tell the two clicks apart. The current SVN code will work perfectly for them, because they never show context menus.

Dmitry Boyarintsev

2019-08-22 05:39

developer   ~0117776

In that case, the best solution is not to do the change.
Hiding the information (by substituting Ctrl+Left to Ctrl+Right) is worse.
Having the actual information would at least allow the end application to reliably resolve the mouse event in the way it finds fit.

Example:
"Preview" by Apple - treats Ctrl+Left as just Left. It does allow to setup the selection. (Right click on the image does nothing )
"Chess" by Apple - treats Ctrl+Left as just Left. Right click does nothing.
"SketchBook" by Autodesk - ignores Ctrl+Left completely (even though there's a context menu showing up on the Right Click)

After all Apple declares the Secondary click as "need to show context menu".
Though doesn't explain what needs to happen if context menu is not used (specifically for Ctrl+Left).

Dmitry Boyarintsev

2019-08-22 05:43

developer   ~0117777

Last edited: 2019-08-22 05:44

View 3 revisions

Apple's handling of Ctrl+Left is never = Right click.

Ctrl+Left on a button - is actually pressing the button!
Right on a button - does nothing.

It's to an application to decide what it wants to do. WS should not obscure the system information

Dmitry Boyarintsev

2019-08-22 05:49

developer   ~0117778

Last edited: 2019-08-22 05:50

View 2 revisions

The best thing that a WS could do is to provide the information ahead of time,
IF the selection needs to be reset, on a certain mouse + shiftstate,

"the default" for all widgetset would be to return false for "rightClick"
for macOS it would return false for "rightClick" or "leftClick"+"ctrl"

I don't think such API exists, though.

Issue History

Date Modified Username Field Change
2019-08-19 23:52 Zoë Peterson New Issue
2019-08-19 23:52 Zoë Peterson File Added: ctrl-click-remap.zip
2019-08-19 23:52 Zoë Peterson File Added: ctrl-click-remap.patch
2019-08-20 17:25 Dmitry Boyarintsev Assigned To => Dmitry Boyarintsev
2019-08-20 17:25 Dmitry Boyarintsev Status new => assigned
2019-08-20 17:30 Dmitry Boyarintsev Note Added: 0117746
2019-08-20 17:45 Dmitry Boyarintsev Status assigned => feedback
2019-08-20 17:45 Dmitry Boyarintsev LazTarget => -
2019-08-20 17:45 Dmitry Boyarintsev Note Added: 0117747
2019-08-20 17:46 Dmitry Boyarintsev Note Added: 0117748
2019-08-20 17:46 Dmitry Boyarintsev Note Edited: 0117747 View Revisions
2019-08-20 18:03 Zoë Peterson Note Added: 0117749
2019-08-20 18:03 Zoë Peterson Status feedback => assigned
2019-08-20 18:07 Zoë Peterson Note Added: 0117750
2019-08-21 03:52 Dmitry Boyarintsev Status assigned => feedback
2019-08-21 03:52 Dmitry Boyarintsev Note Added: 0117758
2019-08-21 04:32 Dmitry Boyarintsev Note Added: 0117759
2019-08-21 06:11 Dmitry Boyarintsev Note Added: 0117760
2019-08-21 20:24 Zoë Peterson File Added: ctrl-click-remap-v2.patch
2019-08-21 20:24 Zoë Peterson Note Added: 0117770
2019-08-21 20:24 Zoë Peterson Status feedback => assigned
2019-08-21 21:52 Dmitry Boyarintsev Note Added: 0117771
2019-08-21 23:52 Zoë Peterson Note Added: 0117775
2019-08-22 05:39 Dmitry Boyarintsev Status assigned => resolved
2019-08-22 05:39 Dmitry Boyarintsev Resolution open => no change required
2019-08-22 05:39 Dmitry Boyarintsev Widgetset Cocoa => Cocoa
2019-08-22 05:39 Dmitry Boyarintsev Note Added: 0117776
2019-08-22 05:43 Dmitry Boyarintsev Note Added: 0117777
2019-08-22 05:43 Dmitry Boyarintsev Note Edited: 0117777 View Revisions
2019-08-22 05:44 Dmitry Boyarintsev Note Edited: 0117777 View Revisions
2019-08-22 05:49 Dmitry Boyarintsev Note Added: 0117778
2019-08-22 05:50 Dmitry Boyarintsev Note Edited: 0117778 View Revisions