/*
* Copyright (c) 2023-2026 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "WindowView.h"
#include "hilog.h"
#include <__nullptr>
#include <atomic>
#include <cstddef>
#include <map>
#include <memory>
#include <vector>
#include "base/utils/time_util.h"
#include "adapter/ios/entrance/interaction/interaction_impl.h"
#include "adapter/ios/capability/editing/iOSTxtInputManager.h"
#include "ace_pointer_data_packet.h"
#include "virtual_rs_window.h"
#include "UINavigationController+StatusBar.h"
#include "core/event/key_event.h"
#import <Foundation/Foundation.h>
#import "AceWebResourcePlugin.h"
#import "AcePlatformViewPlugin.h"
#import "AceWeb.h"
#import "StageContainerView.h"
#import <QuartzCore/CAMetalLayer.h>
#define HIT_TEST_TARGET_WEB @"Web"
#define HIT_TEST_TARGET_PLATFORMVIEW @"PlatformView"
#define ACE_ENABLE_GL
static bool g_isPointInsideWebForceResult = false;
static bool g_isPointInsideWebForceEnable = false;
static const uint64_t KEY_REPEAT_INITIAL_DELAY_MS = 500;
static const uint64_t KEY_REPEAT_INTERVAL_MS = 100;
static const uint64_t KEY_REPEAT_LEEWAY_MS = 5;
static const int64_t MILLISECONDS_PER_SECOND = 1000;
static const char* KEY_REPEAT_QUEUE_NAME = "com.ohos.ace.keyrepeat";
extern "C" void SetIsPointInsideWebForceResult(bool enable, bool result)
{
g_isPointInsideWebForceEnable = enable;
g_isPointInsideWebForceResult = result;
}
@interface WindowView()
@property (nonatomic, strong) CADisplayLink *displayLinkTouch;
@end
@implementation WindowView
{
std::weak_ptr<OHOS::Rosen::Window> _windowDelegate;
int32_t _width;
int32_t _height;
float _density;
BOOL _needNotifySurfaceChangedWithWidth;
BOOL _needCreateSurfaceNode;
BOOL _needNotifyForground;
BOOL _needNotifyFocus;
std::map<int64_t, int32_t> _deviceMap;
std::map<int64_t, int32_t> _pointerMap;
int32_t _deviceId;
int32_t _pointerId;
BOOL _firstTouchFlag;
std::vector<CGRect> hotAreas_;
float _oldBrightness;
NSTimer *_autoPausedTimer;
dispatch_source_t _keyRepeatTimer;
dispatch_queue_t _keyRepeatQueue;
int32_t _currentKeyCode;
int32_t _currentModifierKeys;
int32_t _keyRepeatCount;
BOOL _isInitialDelay;
std::atomic<uint64_t> _keyRepeatGeneration;
}
+(Class)layerClass{
#ifdef ACE_IOS_USE_CAMETAL_LAYER
return [CAMetalLayer class];
#elif defined(ACE_ENABLE_GL)
return [CAEAGLLayer class];
#else
return [CALayer class];
#endif
}
- (instancetype)init {
if (self = [super init]) {
LOGI("windowView init");
_width = 0;
_height = 0;
self.multipleTouchEnabled = YES;
_needNotifySurfaceChangedWithWidth = NO;
_needCreateSurfaceNode = NO;
_focusable = YES;
_isFocused = NO;
_firstTouchFlag = NO;
[self setupNotificationCenterObservers];
self.backgroundColor = [UIColor clearColor];
_deviceMap = std::map<int64_t, int32_t>{};
_pointerMap = std::map<int64_t, int32_t>{};
_deviceId = 0;
_pointerId = 0;
_oldBrightness = - 1;
_brightness = [UIScreen mainScreen].brightness;
_keyRepeatQueue = dispatch_queue_create(KEY_REPEAT_QUEUE_NAME, DISPATCH_QUEUE_SERIAL);
_keyRepeatTimer = NULL;
_keyRepeatCount = 0;
_isInitialDelay = NO;
_keyRepeatGeneration = 0;
}
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
UIScreen *screen = [UIScreen mainScreen];
CGFloat scale = screen.scale;
HILOG_INFO("layoutSubviews : bounds.width/height=%{public}u/%{public}u",
static_cast<int32_t>(self.bounds.size.width),
static_cast<int32_t>(self.bounds.size.height));
int32_t width = static_cast<int32_t>(self.bounds.size.width * scale);
int32_t height = static_cast<int32_t>(self.bounds.size.height * scale);
#ifdef ACE_IOS_USE_CAMETAL_LAYER
if ([self.layer isKindOfClass:[CAMetalLayer class]]) {
CAMetalLayer* metalLayer = static_cast<CAMetalLayer*>(self.layer);
metalLayer.contentsScale = scale;
metalLayer.framebufferOnly = NO;
}
#elif defined(ACE_ENABLE_GL)
if ([self.layer isKindOfClass:[CAEAGLLayer class]]) {
CAEAGLLayer* layer = reinterpret_cast<CAEAGLLayer*>(self.layer);
layer.allowsGroupOpacity = YES;
CGFloat screenScale = scale;
layer.contentsScale = screenScale;
layer.rasterizationScale = screenScale;
layer.drawableProperties = @{
kEAGLDrawablePropertyColorFormat : kEAGLColorFormatRGBA8,
kEAGLDrawablePropertyRetainedBacking : @(NO),
};
}
#endif
[self notifySurfaceChangedWithWidth:width height:height density:scale];
}
- (void)setFullScreen:(BOOL)fullScreen {
_fullScreen = fullScreen;
if (fullScreen) {
self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
} else {
self.autoresizingMask = UIViewAutoresizingNone;
}
}
- (BOOL)requestFocus {
if (self.focusable) {
[((StageContainerView*)self.superview) setActiveWindow:self];
return YES;
}
return NO;
}
- (void)setTouchHotAreas:(CGRect[])rect size:(NSInteger)size{
hotAreas_.clear();
for (int i = 0; i < size; ++i) {
hotAreas_.push_back(*(rect + i));
}
}
- (BOOL)showOnView:(UIView*)rootView {
if (rootView && [rootView isKindOfClass:[StageContainerView class]]) {
if (self.fullScreen) {
self.frame = rootView.bounds;
}
[((StageContainerView*)rootView) showWindow:self];
return YES;
}
return NO;
}
- (BOOL)hide {
[self stopKeyRepeatTimer];
if (self.superview && [self.superview isKindOfClass:[StageContainerView class]]) {
[((StageContainerView*)self.superview) hiddenWindow:self];
return YES;
}
return NO;
}
//invoked twice when hit,so need a flag to ensure only one touch outside callback
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
_firstTouchFlag = !_firstTouchFlag;
BOOL inside = NO;
BOOL inHotArea = NO;
if (hotAreas_.empty()) {
inHotArea = YES;
}
for (std::vector<CGRect>::iterator it = hotAreas_.begin(); it != hotAreas_.end(); ++it) {
if (!CGRectIsEmpty(*it) && CGRectContainsPoint(*it, point)) {
inHotArea = YES;
}
}
inside = [self.layer containsPoint:point] && inHotArea;
if (!inside && _firstTouchFlag){
[self touchOutside];
}
return inside;
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView *view = [super hitTest:point withEvent:event];
BOOL isTouchArkWeb = [self isPointInsideWeb:point withEvent:event];
BOOL isTouchPlatfomrview = [self isPointInsidePlatformview: point withEvent:event];
return (isTouchArkWeb || isTouchPlatfomrview)? nil : view;
}
- (BOOL)isPointInsidePlatformview:(CGPoint)point withEvent:(UIEvent *)event {
__block bool isPointPlatformView = false;
[AcePlatformViewPlugin.getObjectMap enumerateKeysAndObjectsUsingBlock:^(
NSString * _Nonnull key, AcePlatformView * _Nonnull aceplatformview, BOOL * _Nonnull stop) {
UIView *uiview = [aceplatformview getPlatformView];
CGPoint platformPoint = [self convertPoint:point toView:uiview];
if ([uiview pointInside:platformPoint withEvent:event]) {
isPointPlatformView = true;
}
}];
bool isTouchPlatfomrview = false;
if (isPointPlatformView) {
isTouchPlatfomrview = [self touchHitTestTarget:point targetName:HIT_TEST_TARGET_PLATFORMVIEW];
}
return isTouchPlatfomrview;
}
- (BOOL)isPointInsideWeb:(CGPoint)point withEvent:(UIEvent *)event {
__block BOOL isNeedTouchTestArkWeb = NO;
[AceWebResourcePlugin.getObjectMap enumerateKeysAndObjectsUsingBlock:^(
NSString * _Nonnull key, AceWeb * _Nonnull aceWeb, BOOL * _Nonnull stop) {
UIView *uiview = [aceWeb getWeb];
CGPoint webPoint = [self convertPoint:point toView:uiview];
if ([uiview pointInside:webPoint withEvent:event]) {
isNeedTouchTestArkWeb = YES;
}
}];
BOOL isTouchArkWeb = NO;
if (isNeedTouchTestArkWeb) {
isTouchArkWeb = [self touchHitTestTarget:point targetName:HIT_TEST_TARGET_WEB];
}
return isTouchArkWeb && g_isPointInsideWebForceEnable && !g_isPointInsideWebForceResult;
}
- (BOOL)touchHitTestTarget:(CGPoint)point targetName:(NSString*)target{
std::unique_ptr<OHOS::Ace::Platform::AcePointerDataPacket> packet =
std::make_unique<OHOS::Ace::Platform::AcePointerDataPacket>(1);
OHOS::Ace::Platform::AcePointerData pointer_data;
const CGFloat scale = [UIScreen mainScreen].scale;
CGPoint windowCoordinates = point;
pointer_data.window_x = windowCoordinates.x * scale;
pointer_data.window_y = windowCoordinates.y * scale;
pointer_data.pointer_id = 0;
pointer_data.device_id = 0;
auto now = std::chrono::high_resolution_clock::now();
pointer_data.time_stamp = std::chrono::duration_cast<std::chrono::nanoseconds>(now.time_since_epoch()).count();
pointer_data.pointer_action = OHOS::Ace::Platform::AcePointerData::PointerAction::kDowned;
if (_windowDelegate.lock() != nullptr) {
packet->SetPointerData(0, pointer_data);
bool isTouchTarget = _windowDelegate.lock()->ProcessPointerEventTargetHitTest(packet->data(),
std::string([target UTF8String]));
return isTouchTarget;
}
return false;
}
- (void)setWindowDelegate:(std::shared_ptr<OHOS::Rosen::Window>)window {
_windowDelegate = window;
if (_needCreateSurfaceNode) {
_needCreateSurfaceNode = NO;
[self createSurfaceNode];
}
if (_needNotifySurfaceChangedWithWidth) {
_needNotifySurfaceChangedWithWidth = NO;
[self notifySurfaceChangedWithWidth:_width height:_height density:_density];
}
if (_needNotifyForground) {
_needNotifyForground = NO;
[self notifyForeground];
}
if (_needNotifyFocus) {
_needNotifyFocus = NO;
[self notifyFocusChanged:_isFocused];
}
}
- (std::shared_ptr<OHOS::Rosen::Window>)getWindow {
return _windowDelegate.lock();
}
- (void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event {
[self dispatchKeys:presses];
}
- (void)pressesChanged:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event {
[self dispatchKeys:presses];
}
- (void)pressesEnded:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event {
[self dispatchKeys:presses];
}
- (void)pressesCancelled:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event {
[self dispatchKeys:presses];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if (self.displayLinkTouch) {
self.displayLinkTouch.paused = NO;
[self stopPausedTimer];
}
[self dispatchTouches:touches];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
if (self.displayLinkTouch) {
self.displayLinkTouch.paused = NO;
[self stopPausedTimer];
}
[self dispatchTouches:touches];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
if (self.displayLinkTouch) {
[self startPausedTimer];
}
[self dispatchTouches:touches];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
if (self.displayLinkTouch) {
[self startPausedTimer];
}
[self dispatchTouches:touches];
}
- (void)setBrightness:(float)brightness {
_oldBrightness = _brightness;
_brightness = brightness;
}
- (float)getBrightness {
return _brightness;
}
- (void)updateBrightness:(BOOL)isShow {
if (_oldBrightness == -1) {
return;
}
float brightness = _oldBrightness;
if (isShow) {
brightness = _brightness;
}
[UIScreen mainScreen].brightness = brightness;
}
- (void)touchOutside {
if (_windowDelegate.lock() != nullptr) {
_windowDelegate.lock()->NotifyTouchOutside();
}
}
- (void)setIsFocused:(BOOL)isFocused {
if (self.focusable && _isFocused != isFocused) {
_isFocused = isFocused;
[self notifyFocusChanged:isFocused];
}
}
#pragma mark - Touch event handling
static OHOS::Ace::Platform::AcePointerData::PointerAction PointerDataChangeFromUITouchPhase(UITouchPhase phase) {
auto action = OHOS::Ace::Platform::AcePointerData::PointerAction::kCanceled;
switch (phase) {
case UITouchPhaseBegan:
action = OHOS::Ace::Platform::AcePointerData::PointerAction::kDowned;
break;
case UITouchPhaseMoved:
case UITouchPhaseStationary:
// There is no EVENT_TYPE_POINTER_STATIONARY. So we just pass a move type
// with the same coordinates
action = OHOS::Ace::Platform::AcePointerData::PointerAction::kMoved;
break;
case UITouchPhaseEnded:
action = OHOS::Ace::Platform::AcePointerData::PointerAction::kUped;
break;
case UITouchPhaseCancelled:
action = OHOS::Ace::Platform::AcePointerData::PointerAction::kCanceled;
break;
default:
action = OHOS::Ace::Platform::AcePointerData::PointerAction::kCanceled;
break;
}
return action;
}
static const char* SyntheticPhaseToString(UITouchPhase phase)
{
switch (phase) {
case UITouchPhaseBegan:
return "BEGAN";
case UITouchPhaseMoved:
return "MOVED";
case UITouchPhaseStationary:
return "STATIONARY";
case UITouchPhaseEnded:
return "ENDED";
case UITouchPhaseCancelled:
return "CANCELLED";
default:
return "UNKNOWN";
}
}
static OHOS::Ace::Platform::AcePointerData::ToolType DeviceKindFromTouchType(UITouch *touch) {
switch (touch.type) {
case UITouchTypeDirect:
case UITouchTypeIndirect:
return OHOS::Ace::Platform::AcePointerData::ToolType::Touch;
case UITouchTypeStylus:
return OHOS::Ace::Platform::AcePointerData::ToolType::Stylus;
case UITouchTypeIndirectPointer:
return OHOS::Ace::Platform::AcePointerData::ToolType::Mouse;
default:
break;
}
return OHOS::Ace::Platform::AcePointerData::ToolType::Touch;
}
static bool IsSyntheticTouchActive(UITouchPhase phase)
{
return phase == UITouchPhaseBegan || phase == UITouchPhaseMoved || phase == UITouchPhaseStationary;
}
static void UpdateSyntheticTouchDisplayLink(WindowView *view, UITouchPhase phase)
{
if (!view.displayLinkTouch) {
return;
}
if (IsSyntheticTouchActive(phase)) {
view.displayLinkTouch.paused = NO;
[view stopPausedTimer];
return;
}
if (phase == UITouchPhaseEnded || phase == UITouchPhaseCancelled) {
[view startPausedTimer];
}
}
struct SyntheticPointerDataParams {
UITouchPhase phase;
CGFloat scale;
CGPoint windowCoordinates;
CGPoint displayCoordinates;
int32_t pointerId;
int64_t timeStamp;
};
static OHOS::Ace::Platform::AcePointerData CreateSyntheticPointerData(
const SyntheticPointerDataParams& params)
{
OHOS::Ace::Platform::AcePointerData pointerData;
pointerData.Clear();
pointerData.pointer_id = params.pointerId >= 0 ? params.pointerId : 0;
pointerData.device_id = 0;
pointerData.time_stamp =
params.timeStamp > 0 ? params.timeStamp : OHOS::Ace::GetMicroTickCount();
pointerData.finger_count = 1;
pointerData.pointer_action = PointerDataChangeFromUITouchPhase(params.phase);
pointerData.tool_type = OHOS::Ace::Platform::AcePointerData::ToolType::Touch;
pointerData.display_x = params.displayCoordinates.x * params.scale;
pointerData.display_y = params.displayCoordinates.y * params.scale;
pointerData.window_x = params.windowCoordinates.x * params.scale;
pointerData.window_y = params.windowCoordinates.y * params.scale;
pointerData.pressure =
(params.phase == UITouchPhaseEnded || params.phase == UITouchPhaseCancelled) ? 0.0f : 1.0f;
pointerData.radius_major = 0.0;
pointerData.radius_min = 0.0;
pointerData.radius_max = 0.0;
pointerData.tilt = 0.0;
pointerData.orientation = 0.0;
pointerData.actionPoint = true;
return pointerData;
}
- (int32_t)getTouchDevice:(UITouch *)touch {
UITouchPhase phase = touch.phase;
int64_t device = reinterpret_cast<int64_t>(touch);
int32_t deviceId;
auto iter = _deviceMap.find(device);
if (iter == _deviceMap.end()) {
if (phase == UITouchPhaseBegan) {
_deviceMap[device] = _deviceId;
deviceId = _deviceId;
_deviceId++;
} else {
return -1;
}
} else {
deviceId = _deviceMap[device];
if (phase == UITouchPhaseEnded || phase == UITouchPhaseCancelled) {
_deviceMap.erase(iter);
}
if (_deviceMap.size() == 0) {
_deviceId = 0;
}
}
return deviceId;
}
- (int32_t)getTouchPointer:(UITouch *)touch {
UITouchPhase phase = touch.phase;
int64_t pointer = reinterpret_cast<int64_t>(touch);
int32_t pointerId;
auto iter = _pointerMap.find(pointer);
if (iter == _pointerMap.end()) {
if (phase == UITouchPhaseBegan) {
_pointerMap[pointer] = _pointerId;
pointerId = _pointerId;
_pointerId++;
} else {
return -1;
}
} else {
pointerId = _pointerMap[pointer];
if (phase == UITouchPhaseEnded || phase == UITouchPhaseCancelled) {
_pointerMap.erase(iter);
}
if (_pointerMap.size() == 0) {
_pointerId = 0;
}
}
return pointerId;
}
- (BOOL)dispatchSyntheticTouchWithPhase:(UITouchPhase)phase
pixelX:(CGFloat)pixelX
pixelY:(CGFloat)pixelY
pointerId:(int32_t)pointerId
timeStamp:(int64_t)timeStamp
{
UpdateSyntheticTouchDisplayLink(self, phase);
auto window = [self getWindow];
if (window == nullptr) {
return NO;
}
CGFloat scale = [UIScreen mainScreen].scale;
if (scale <= 0.0) {
scale = 1.0;
}
UIView *displayView = self.superview ?: self;
CGPoint windowCoordinates = CGPointMake(pixelX / scale, pixelY / scale);
CGPoint displayCoordinates = displayView == self ? windowCoordinates :
[self convertPoint:windowCoordinates toView:displayView];
const SyntheticPointerDataParams pointerParams {
phase, scale, windowCoordinates, displayCoordinates, pointerId, timeStamp
};
auto pointerData = CreateSyntheticPointerData(pointerParams);
const bool syntheticActive = IsSyntheticTouchActive(phase);
OHOS::Ace::UpdateSyntheticDragTouchState(pointerData.pointer_id, syntheticActive);
OHOS::Ace::PrepareSyntheticDragCompensationContext(pointerData.pointer_id, syntheticActive,
phase == UITouchPhaseBegan, pointerData.time_stamp);
OHOS::Ace::Platform::AcePointerDataPacket packet(1);
packet.SetPointerData(0, pointerData);
const bool result = window->ProcessSyntheticPointerEvent(packet.data());
OHOS::Ace::CompleteSyntheticDragTouchState(pointerData.pointer_id, syntheticActive);
return result;
}
- (void)dispatchTouches:(NSSet *)touches {
const CGFloat scale = [UIScreen mainScreen].scale;
std::unique_ptr<OHOS::Ace::Platform::AcePointerDataPacket> packet =
std::make_unique<OHOS::Ace::Platform::AcePointerDataPacket>(touches.count);
size_t pointer_index = 0;
UIView *rootView = self.superview;
for (UITouch *touch in touches) {
CGPoint windowCoordinates = [touch locationInView:self];
CGPoint screenCoordinates = [self convertPoint:windowCoordinates toView:rootView];
OHOS::Ace::Platform::AcePointerData pointer_data;
pointer_data.Clear();
pointer_data.pointer_id = [self getTouchPointer:touch];
pointer_data.device_id = [self getTouchDevice:touch];
pointer_data.time_stamp = OHOS::Ace::GetMicroTickCount();
pointer_data.finger_count = touches.count;
pointer_data.pointer_action = PointerDataChangeFromUITouchPhase(touch.phase);
pointer_data.tool_type = DeviceKindFromTouchType(touch);
pointer_data.display_x = screenCoordinates.x * scale;
pointer_data.display_y = screenCoordinates.y * scale;
pointer_data.window_x = windowCoordinates.x * scale;
pointer_data.window_y = windowCoordinates.y * scale;
pointer_data.pressure = touch.force;
pointer_data.radius_major = touch.majorRadius;
pointer_data.radius_min = touch.majorRadius - touch.majorRadiusTolerance;
pointer_data.radius_max = touch.majorRadius + touch.majorRadiusTolerance;
pointer_data.actionPoint = true;
pointer_data.tilt = M_PI_2 - touch.altitudeAngle;
pointer_data.orientation = [touch azimuthAngleInView:nil] - M_PI_2;
packet->SetPointerData(pointer_index++, pointer_data);
}
if (_windowDelegate.lock() != nullptr) {
_windowDelegate.lock()->ProcessPointerEvent(packet->data());
}
}
#pragma mark - Key event handling
static OHOS::Ace::KeyAction KeyActionChangeFromUIPressPhase(UIPressPhase phase) {
switch (phase) {
case UIPressPhaseBegan:
return OHOS::Ace::KeyAction::DOWN;
case UIPressPhaseChanged:
case UIPressPhaseStationary:
case UIPressPhaseEnded:
case UIPressPhaseCancelled:
return OHOS::Ace::KeyAction::UP;
}
return OHOS::Ace::KeyAction::UNKNOWN;
}
static int32_t GetModifierKeys(UIKeyModifierFlags modifierFlags) {
int32_t ctrlKeysBit = 0;
static enum CtrlKeysBit {
ctrl = 1,
shift = 2,
alt = 4,
meta = 8,
};
if (modifierFlags & UIKeyModifierControl) {
ctrlKeysBit |= CtrlKeysBit::ctrl;
}
if (modifierFlags & UIKeyModifierShift) {
ctrlKeysBit |= CtrlKeysBit::shift;
}
if (modifierFlags & UIKeyModifierAlternate) {
ctrlKeysBit |= CtrlKeysBit::alt;
}
if (modifierFlags & UIKeyModifierCommand) {
ctrlKeysBit |= CtrlKeysBit::meta;
}
return ctrlKeysBit;
}
- (void)stopKeyRepeatTimer {
_keyRepeatGeneration.fetch_add(1, std::memory_order_relaxed);
if (_keyRepeatTimer) {
dispatch_source_cancel(_keyRepeatTimer);
dispatch_release(_keyRepeatTimer);
_keyRepeatTimer = NULL;
}
_keyRepeatCount = 0;
_isInitialDelay = NO;
}
- (void)handleKeyRepeatForGeneration:(uint64_t)generation {
if (_keyRepeatGeneration.load(std::memory_order_relaxed) != generation) {
return;
}
int32_t repeatCount = ++_keyRepeatCount;
int32_t keyCode = _currentKeyCode;
int32_t modifierKeys = _currentModifierKeys;
int64_t timestamp =
static_cast<int64_t>([[NSDate date] timeIntervalSince1970] * MILLISECONDS_PER_SECOND);
dispatch_async(dispatch_get_main_queue(), ^{
if (self->_keyRepeatGeneration.load(std::memory_order_relaxed) != generation) {
return;
}
auto window = self->_windowDelegate.lock();
if (window != nullptr) {
window->ProcessKeyEvent(
keyCode,
static_cast<int32_t>(OHOS::Ace::KeyAction::DOWN),
repeatCount,
timestamp,
timestamp,
modifierKeys);
}
});
if (_keyRepeatGeneration.load(std::memory_order_relaxed) != generation) {
return;
}
if (_isInitialDelay) {
_isInitialDelay = NO;
[self stopKeyRepeatTimer];
[self startKeyRepeatTimerWithInterval:KEY_REPEAT_INTERVAL_MS];
}
}
- (void)startKeyRepeatTimerWithInterval:(uint64_t)intervalMs {
if (_keyRepeatTimer) {
[self stopKeyRepeatTimer];
}
_keyRepeatTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, _keyRepeatQueue);
if (!_keyRepeatTimer) {
return;
}
uint64_t intervalNs = intervalMs * NSEC_PER_MSEC;
dispatch_source_set_timer(_keyRepeatTimer,
dispatch_time(DISPATCH_TIME_NOW, intervalNs),
intervalNs,
KEY_REPEAT_LEEWAY_MS * NSEC_PER_MSEC);
uint64_t generation = _keyRepeatGeneration.load(std::memory_order_relaxed);
__weak WindowView *weakSelf = self;
dispatch_source_set_event_handler(_keyRepeatTimer, ^{
WindowView *strongSelf = weakSelf;
if (strongSelf) {
[strongSelf handleKeyRepeatForGeneration:generation];
}
});
dispatch_resume(_keyRepeatTimer);
}
- (void)dispatchKeys:(NSSet<UIPress *> *)presses {
for (UIPress *press in presses) {
UIKey *pressKey = press.key;
if (!pressKey) {
continue;
}
UIKeyboardHIDUsage pressKeyCode = [pressKey keyCode];
OHOS::Ace::KeyAction keyAction = KeyActionChangeFromUIPressPhase(press.phase);
UIKeyModifierFlags modifierFlags = [pressKey modifierFlags];
int32_t modifierKeys = GetModifierKeys(modifierFlags);
int32_t keyCode = static_cast<int32_t>(pressKeyCode);
int64_t timestamp = static_cast<int64_t>(press.timestamp * MILLISECONDS_PER_SECOND);
auto window = _windowDelegate.lock();
if (press.phase == UIPressPhaseBegan) {
[self stopKeyRepeatTimer];
if (window != nullptr) {
window->ProcessKeyEvent(
keyCode, static_cast<int32_t>(keyAction), 0, timestamp, timestamp, modifierKeys);
}
_currentKeyCode = keyCode;
_currentModifierKeys = modifierKeys;
_keyRepeatCount = 0;
_isInitialDelay = YES;
[self startKeyRepeatTimerWithInterval:KEY_REPEAT_INITIAL_DELAY_MS];
} else if (press.phase == UIPressPhaseChanged) {
if (window != nullptr) {
window->ProcessKeyEvent(
keyCode, static_cast<int32_t>(keyAction), 0, timestamp, timestamp, modifierKeys);
}
} else if (press.phase == UIPressPhaseEnded || press.phase == UIPressPhaseCancelled) {
[self stopKeyRepeatTimer];
if (window != nullptr) {
window->ProcessKeyEvent(
keyCode, static_cast<int32_t>(keyAction), 0, timestamp, timestamp, modifierKeys);
}
}
}
}
- (void)createSurfaceNode {
if (_windowDelegate.lock() != nullptr) {
_windowDelegate.lock()->CreateSurfaceNode(self.layer);
} else {
_needCreateSurfaceNode = YES;
}
}
- (void)notifySurfaceChangedWithWidth:(int32_t)width height:(int32_t)height density:(float)density {
_width = width;
_height = height;
_density = density;
if (_windowDelegate.lock() != nullptr) {
_windowDelegate.lock()->NotifySurfaceChanged(width, height, density);
} else {
_needNotifySurfaceChangedWithWidth = YES;
}
}
- (void)notifySurfaceDestroyed {
if (_windowDelegate.lock() != nullptr) {
_windowDelegate.lock()->NotifySurfaceDestroyed();
}
}
- (void)notifyWindowDestroyed {
if (_windowDelegate.lock() != nullptr) {
_windowDelegate.lock()->Destroy();
}
if (self.displayLinkTouch) {
LOGI("WindowView notifyWindowDestroyed in");
[self stopPausedTimer];
[self.displayLinkTouch invalidate];
self.displayLinkTouch = nil;
}
}
- (void)notifySafeAreaChanged {
if (_windowDelegate.lock() != nullptr) {
_windowDelegate.lock()->NotifySafeAreaChanged();
}
}
- (void)setupNotificationCenterObservers {
NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
[center addObserver:self
selector:@selector(keyboardWillChangeFrame:)
name:UIKeyboardWillChangeFrameNotification
object:nil];
[center addObserver:self
selector:@selector(keyboardWillBeHidden:)
name:UIKeyboardWillHideNotification
object:nil];
}
// #pragma mark - Application lifecycle notifications
- (void)notifyForeground {
if (_windowDelegate.lock() != nullptr) {
_windowDelegate.lock()->Foreground();
} else {
_needNotifyForground = YES;
}
}
- (void)notifyBackground {
if (_windowDelegate.lock() != nullptr) {
_windowDelegate.lock()->Background();
}
}
- (void)notifyActiveChanged:(BOOL)isActive {
[self updateBrightness:isActive];
if (_windowDelegate.lock() != nullptr) {
_windowDelegate.lock()->WindowActiveChanged(isActive);
}
}
- (void)notifyFocusChanged:(BOOL)focus {
if (_windowDelegate.lock() != nullptr) {
_windowDelegate.lock()->WindowFocusChanged(focus);
} else {
_needNotifyFocus = YES;
}
}
- (void)notifyApplicationForeground:(BOOL)isForeground {
if (_windowDelegate.lock() != nullptr) {
_windowDelegate.lock()->NotifyApplicationForeground(isForeground);
}
}
- (void)keyboardWillChangeFrame:(NSNotification*)notification {
NSDictionary* info = [notification userInfo];
CGFloat keyboardHeight = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height;
CGFloat scale = [UIScreen mainScreen].scale;
keyboardHeight = keyboardHeight * scale;
if (_windowDelegate.lock() != nullptr) {
_windowDelegate.lock()->NotifyKeyboardHeightChanged(keyboardHeight);
}
}
- (void)safeAreaInsetsDidChange {
[super safeAreaInsetsDidChange];
[self notifySafeAreaChanged];
}
- (void)keyboardWillBeHidden:(NSNotification*)notification {
if (_windowDelegate.lock() != nullptr) {
_windowDelegate.lock()->NotifyKeyboardHeightChanged(0);
}
}
- (void)notifyTraitCollectionDidChange:(BOOL)isSplitScreen {
if (_windowDelegate.lock() != nullptr) {
_windowDelegate.lock()->NotifyTraitCollectionDidChange(isSplitScreen);
}
}
- (void)notifyHandleWillTerminate {
if (_windowDelegate.lock() != nullptr) {
_windowDelegate.lock()->NotifyWillTeminate();
}
}
- (BOOL)processBackPressed {
if (_windowDelegate.lock() != nullptr) {
return _windowDelegate.lock()->ProcessBackPressed();
}
return false;
}
- (void)dealloc {
LOGI("WindowView dealloc");
[[NSNotificationCenter defaultCenter] removeObserver:self];
[self stopKeyRepeatTimer];
if (_keyRepeatQueue) {
dispatch_release(_keyRepeatQueue);
_keyRepeatQueue = NULL;
}
[super dealloc];
}
- (UIViewController*)getViewController {
UIResponder *responder = self;
while ((responder = [responder nextResponder])) {
if ([responder isKindOfClass:[UIViewController class]]) {
return (UIViewController *)responder;
}
}
return nil;
}
- (void)startBaseDisplayLink {
float mainMaxFrameRate = [UIScreen mainScreen].maximumFramesPerSecond;
const double epsilon = 0.1;
if (mainMaxFrameRate < 60.0 + epsilon) {
return;
}
self.displayLinkTouch = [CADisplayLink displayLinkWithTarget:self selector:@selector(onDisplayLinkTouch:)];
self.displayLinkTouch.paused = YES;
if (@available(iOS 15.0,*)) {
float maxFrameRate = fmax(mainMaxFrameRate, 60);
LOGI("startBaseDisplayLink maxFrameRate = %{public}f", maxFrameRate);
self.displayLinkTouch.preferredFrameRateRange = CAFrameRateRangeMake(maxFrameRate, maxFrameRate, maxFrameRate);
} else {
self.displayLinkTouch.preferredFramesPerSecond = mainMaxFrameRate;
}
[self.displayLinkTouch addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}
- (void)onDisplayLinkTouch:(CADisplayLink*)link {}
- (void)startPausedTimer {
if (_autoPausedTimer) {
[_autoPausedTimer invalidate];
_autoPausedTimer = nil;
}
_autoPausedTimer = [NSTimer scheduledTimerWithTimeInterval:5.0
target:self
selector:@selector(autoPausedTimerFun)
userInfo:nil
repeats:NO];
}
- (void)stopPausedTimer {
if (_autoPausedTimer) {
[_autoPausedTimer invalidate];
_autoPausedTimer = nil;
}
}
- (void)autoPausedTimerFun {
self.displayLinkTouch.paused = YES;
if (_autoPausedTimer) {
[_autoPausedTimer invalidate];
_autoPausedTimer = nil;
}
}
@end