2023-04-13 13:16:14 +06:00

439 lines
12 KiB
C

/** @file
This file is part of OpenCanopy, OpenCore GUI.
Copyright (c) 2018-2019, Download-Fritz. All rights reserved.<BR>
SPDX-License-Identifier: BSD-3-Clause
**/
#include <Uefi.h>
#include <Protocol/AppleEvent.h>
#include <Protocol/AbsolutePointer.h>
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/BaseOverflowLib.h>
#include <Library/DebugLib.h>
#include <Library/AppleEventLib.h>
#include <Library/OcCpuLib.h>
#include <Library/OcMiscLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiLib.h>
#include "../OpenCanopy.h"
#include "../GuiIo.h"
#define ABS_DOUBLE_CLICK_RADIUS 25U
#define IS_POWER_2(x) (((x) & ((x) - 1)) == 0 && (x) != 0)
#define POINTER_SCALE 1U
struct GUI_POINTER_CONTEXT_ {
APPLE_EVENT_PROTOCOL *AppleEvent;
EFI_ABSOLUTE_POINTER_PROTOCOL *AbsPointer;
APPLE_EVENT_HANDLE AppleEventHandle;
EFI_EVENT AbsPollEvent;
UINT32 MaxXPlus1;
UINT32 MaxYPlus1;
GUI_PTR_POSITION CurPos;
GUI_PTR_POSITION AbsLastDownPos;
UINT32 OldEventExScale;
BOOLEAN AbsPrimaryDown;
BOOLEAN AbsDoubleClick;
UINT8 UiScale;
UINT8 LockedBy;
UINT8 EventQueueHead;
UINT8 EventQueueTail;
GUI_PTR_EVENT EventQueue[16];
};
enum {
PointerUnlocked,
PointerLockedSimple,
PointerLockedAbsolute
};
STATIC
VOID
InternalQueuePointerEvent (
IN OUT GUI_POINTER_CONTEXT *Context,
IN UINT8 Type,
IN UINT32 X,
IN UINT32 Y
)
{
UINT32 Tail;
//
// Due to the modulus, wraparounds do not matter. The size of the queue must
// be a power of two for this to hold.
//
STATIC_ASSERT (
IS_POWER_2 (ARRAY_SIZE (Context->EventQueue)),
"The pointer event queue must have a power of two length."
);
Tail = Context->EventQueueTail % ARRAY_SIZE (Context->EventQueue);
Context->EventQueue[Tail].Type = Type;
Context->EventQueue[Tail].Pos.Pos.X = X;
Context->EventQueue[Tail].Pos.Pos.Y = Y;
++Context->EventQueueTail;
}
BOOLEAN
GuiPointerGetEvent (
IN OUT GUI_POINTER_CONTEXT *Context,
OUT GUI_PTR_EVENT *Event
)
{
UINT32 Head;
if (Context->EventQueueHead == Context->EventQueueTail) {
return FALSE;
}
//
// Due to the modulus, wraparounds do not matter. The size of the queue must
// be a power of two for this to hold.
//
STATIC_ASSERT (
IS_POWER_2 (ARRAY_SIZE (Context->EventQueue)),
"The pointer event queue must have a power of two length."
);
Head = Context->EventQueueHead % ARRAY_SIZE (Context->EventQueue);
Event->Type = Context->EventQueue[Head].Type;
Event->Pos.Pos.X = Context->EventQueue[Head].Pos.Pos.X;
Event->Pos.Pos.Y = Context->EventQueue[Head].Pos.Pos.Y;
++Context->EventQueueHead;
return TRUE;
}
STATIC
VOID
EFIAPI
InternalAppleEventNotification (
IN APPLE_EVENT_INFORMATION *Information,
IN VOID *NotifyContext
)
{
APPLE_POINTER_EVENT_TYPE EventType;
GUI_POINTER_CONTEXT *Context;
Context = NotifyContext;
ASSERT ((Information->EventType & APPLE_ALL_MOUSE_EVENTS) != 0);
//
// Discard simple pointer updates when absolute pointer locked.
//
if (Context->LockedBy == PointerLockedAbsolute) {
return;
}
EventType = Information->EventData.PointerEventType;
if ((EventType & APPLE_EVENT_TYPE_MOUSE_MOVED) != 0) {
Context->CurPos.Pos.X = (UINT32)Information->PointerPosition.Horizontal;
Context->CurPos.Pos.Y = (UINT32)Information->PointerPosition.Vertical;
}
if ((EventType & APPLE_EVENT_TYPE_LEFT_BUTTON) != 0) {
if ((EventType & APPLE_EVENT_TYPE_MOUSE_DOWN) != 0) {
ASSERT (Context->LockedBy == PointerUnlocked);
InternalQueuePointerEvent (
Context,
GuiPointerPrimaryDown,
(UINT32)Information->PointerPosition.Horizontal,
(UINT32)Information->PointerPosition.Vertical
);
Context->LockedBy = PointerLockedSimple;
} else if ((EventType & APPLE_EVENT_TYPE_MOUSE_UP) != 0) {
ASSERT (Context->LockedBy == PointerLockedSimple);
InternalQueuePointerEvent (
Context,
GuiPointerPrimaryUp,
(UINT32)Information->PointerPosition.Horizontal,
(UINT32)Information->PointerPosition.Vertical
);
Context->LockedBy = PointerUnlocked;
} else if ((EventType & APPLE_EVENT_TYPE_MOUSE_DOUBLE_CLICK) != 0) {
InternalQueuePointerEvent (
Context,
GuiPointerPrimaryDoubleClick,
(UINT32)Information->PointerPosition.Horizontal,
(UINT32)Information->PointerPosition.Vertical
);
}
}
}
STATIC
VOID
EFIAPI
InternalUpdateContextAbsolute (
IN EFI_EVENT Event,
IN OUT VOID *NotifyContext
)
{
GUI_POINTER_CONTEXT *Context;
EFI_STATUS Status;
EFI_ABSOLUTE_POINTER_STATE PointerState;
UINT64 NewX;
UINT64 NewY;
GUI_PTR_POSITION NewPos;
ASSERT (NotifyContext != NULL);
Context = NotifyContext;
ASSERT (Context->AbsPointer != NULL);
//
// Discard absolute pointer updates when simple pointer locked.
//
if (Context->LockedBy == PointerLockedSimple) {
return;
}
Status = Context->AbsPointer->GetState (Context->AbsPointer, &PointerState);
if (EFI_ERROR (Status)) {
return;
}
NewX = PointerState.CurrentX - Context->AbsPointer->Mode->AbsoluteMinX;
NewX *= Context->MaxXPlus1;
NewPos.Pos.X = (UINT32)DivU64x32 (
NewX,
(UINT32)(Context->AbsPointer->Mode->AbsoluteMaxX - Context->AbsPointer->Mode->AbsoluteMinX)
);
NewY = PointerState.CurrentY - Context->AbsPointer->Mode->AbsoluteMinY;
NewY *= Context->MaxYPlus1;
NewPos.Pos.Y = (UINT32)DivU64x32 (
NewY,
(UINT32)(Context->AbsPointer->Mode->AbsoluteMaxY - Context->AbsPointer->Mode->AbsoluteMinY)
);
Context->CurPos.Uint64 = NewPos.Uint64;
//
// Cancel double click when the finger is moved too far away.
//
ASSERT (Context->UiScale > 0);
if (Context->AbsDoubleClick) {
if ( (ABS ((INT64)NewPos.Pos.X - Context->AbsLastDownPos.Pos.X) > ABS_DOUBLE_CLICK_RADIUS * Context->UiScale)
|| (ABS ((INT64)NewPos.Pos.Y - Context->AbsLastDownPos.Pos.Y) > ABS_DOUBLE_CLICK_RADIUS * Context->UiScale))
{
Context->AbsDoubleClick = FALSE;
}
}
if (Context->AbsPrimaryDown != ((PointerState.ActiveButtons & EFI_ABSP_TouchActive) != 0)) {
Context->AbsPrimaryDown = ((PointerState.ActiveButtons & EFI_ABSP_TouchActive) != 0);
if (Context->AbsPrimaryDown) {
ASSERT (Context->LockedBy == PointerUnlocked);
InternalQueuePointerEvent (
Context,
GuiPointerPrimaryDown,
NewPos.Pos.X,
NewPos.Pos.Y
);
Context->LockedBy = PointerLockedAbsolute;
Context->AbsLastDownPos.Uint64 = NewPos.Uint64;
Context->AbsDoubleClick = TRUE;
} else {
ASSERT (Context->LockedBy == PointerLockedAbsolute);
InternalQueuePointerEvent (
Context,
GuiPointerPrimaryUp,
NewPos.Pos.X,
NewPos.Pos.Y
);
Context->LockedBy = PointerUnlocked;
if (Context->AbsDoubleClick) {
InternalQueuePointerEvent (
Context,
GuiPointerPrimaryDoubleClick,
NewPos.Pos.X,
NewPos.Pos.Y
);
Context->AbsDoubleClick = FALSE;
}
}
}
}
VOID
GuiPointerReset (
IN OUT GUI_POINTER_CONTEXT *Context
)
{
ASSERT (Context != NULL);
Context->EventQueueHead = 0;
Context->EventQueueTail = 0;
Context->LockedBy = PointerUnlocked;
}
VOID
GuiPointerGetPosition (
IN OUT GUI_POINTER_CONTEXT *Context,
OUT GUI_PTR_POSITION *Position
)
{
EFI_TPL OldTpl;
ASSERT (Context != NULL);
ASSERT (Position != NULL);
//
// Prevent pointer updates during state retrieval.
// On 64+-bit systems, the operation is atomic.
//
if (sizeof (UINTN) < sizeof (UINT64)) {
OldTpl = gBS->RaiseTPL (TPL_NOTIFY);
}
//
// Return the current pointer position.
//
Position->Uint64 = Context->CurPos.Uint64;
if (sizeof (UINTN) < sizeof (UINT64)) {
gBS->RestoreTPL (OldTpl);
}
}
VOID
GuiPointerSetPosition (
IN OUT GUI_POINTER_CONTEXT *Context,
IN CONST GUI_PTR_POSITION *Position
)
{
EFI_TPL OldTpl;
DIMENSION ApplePosition;
ASSERT (Context != NULL);
ASSERT (Position != NULL);
OldTpl = gBS->RaiseTPL (TPL_NOTIFY);
ApplePosition.Horizontal = (INT32)Position->Pos.X;
ApplePosition.Vertical = (INT32)Position->Pos.Y;
Context->AppleEvent->SetCursorPosition (
&ApplePosition
);
//
// Return the current pointer position.
//
Context->CurPos.Uint64 = Position->Uint64;
gBS->RestoreTPL (OldTpl);
}
GUI_POINTER_CONTEXT *
GuiPointerConstruct (
IN UINT32 DefaultX,
IN UINT32 DefaultY,
IN UINT32 Width,
IN UINT32 Height,
IN UINT8 UiScale
)
{
// TODO: alloc on the fly?
STATIC GUI_POINTER_CONTEXT Context;
EFI_STATUS Status;
EFI_STATUS Status2;
DIMENSION Dimension;
ASSERT (DefaultX <= MAX_INT32);
ASSERT (DefaultY <= MAX_INT32);
Context.MaxXPlus1 = Width;
Context.MaxYPlus1 = Height;
Context.CurPos.Pos.X = DefaultX;
Context.CurPos.Pos.Y = DefaultY;
Context.UiScale = UiScale;
Context.AppleEvent = OcGetProtocol (
&gAppleEventProtocolGuid,
DEBUG_WARN,
"GuiPointerConstruct",
"AppleEvent"
);
Status = EFI_UNSUPPORTED;
if (Context.AppleEvent != NULL) {
if (Context.AppleEvent->Revision >= APPLE_EVENT_PROTOCOL_REVISION) {
Dimension.Horizontal = (INT32)DefaultX;
Dimension.Vertical = (INT32)DefaultY;
Context.AppleEvent->SetCursorPosition (
&Dimension
);
//
// Do not register 'Click' as its behaviour does not follow OS behaviour.
//
Status = Context.AppleEvent->RegisterHandler (
APPLE_ALL_MOUSE_EVENTS & ~APPLE_EVENT_TYPE_MOUSE_CLICK,
InternalAppleEventNotification,
&Context.AppleEventHandle,
&Context
);
}
if (EFI_ERROR (Status)) {
DEBUG ((
DEBUG_WARN,
"OCUI: AppleEvent %u is unsupported - %r\n",
Context.AppleEvent->Revision,
Status
));
return NULL;
}
}
Status2 = OcHandleProtocolFallback (
gST->ConsoleInHandle,
&gEfiAbsolutePointerProtocolGuid,
(VOID **)&Context.AbsPointer
);
if (!EFI_ERROR (Status2)) {
Context.AbsPollEvent = EventLibCreateNotifyTimerEvent (
InternalUpdateContextAbsolute,
&Context,
EFI_TIMER_PERIOD_MILLISECONDS (10),
TRUE
);
if (Context.AbsPollEvent == NULL) {
Status2 = EFI_UNSUPPORTED;
}
}
if (EFI_ERROR (Status) && EFI_ERROR (Status2)) {
return NULL;
}
return &Context;
}
VOID
GuiPointerDestruct (
IN GUI_POINTER_CONTEXT *Context
)
{
ASSERT (Context != NULL);
ASSERT (Context->AppleEvent != NULL);
Context->AppleEvent->UnregisterHandler (Context->AppleEventHandle);
if (Context->AbsPollEvent != NULL) {
EventLibCancelEvent (Context->AbsPollEvent);
}
ZeroMem (Context, sizeof (*Context));
}