提交 ee280771 编写于 作者: V vit9696

OcAppleEventLib: Allow tuning pointer poll list and period

closes #312
closes acidanthera/bugtracker#1899
上级 a5ffbd77
......@@ -14,6 +14,8 @@ OpenCore Changelog
- Implemented multi-channel (e.g. bass+main speaker; speakers+headphones) UEFI sound with `AudioOutMask`
- Fixed AudioDxe startup stalls when Nvidia HDA audio present
- Resolved AudioDxe disabling sound in Windows on some firmware
- Added pointer polling period tuning in the builtin AppleEvent implementation
- Added pointer device list tuning in the builtin AppleEvent implementation
#### v0.7.6
- Fixed stack canary support when compiling with GCC
......
e5664cfe0d0efc9e0b073e4071162e97
b8ee5f959708f44a41c5560f6eeee820
......@@ -6828,6 +6828,65 @@ with the boot menu.
\item \texttt{false} --- Prevent key input mirroring to non-Apple protocols when in graphics mode.
\end{itemize}
\item
\texttt{PointerPollMin}\\
\textbf{Type}: \texttt{plist\ integer}\\
\textbf{Failsafe}: \texttt{0}\\
\textbf{Description}: Configure minimal pointer polling period in ms.
This is the minimal period the OpenCore builtin AppleEvent driver polls
pointer devices (e.g. mice, trackpads) for motion events. The current
implementation defaults to 10 ms. Setting \texttt{0} leaves this
default unchanged.
\emph{Note}: The OEM Apple implementation uses a polling rate of 2 ms.
\item
\texttt{PointerPollMax}\\
\textbf{Type}: \texttt{plist\ integer}\\
\textbf{Failsafe}: \texttt{0}\\
\textbf{Description}: Configure maximum pointer polling period in ms.
This is the maximum period the OpenCore builtin AppleEvent driver polls
pointer devices (e.g. mice, trackpads) for motion events. The period
is increased up to this value as long as the devices do not respond
in time. The current implementation defaults to 80 ms. Setting
\texttt{0} leaves this default unchanged.
Certain trackpad drivers often found in Dell laptops can be very slow
to respond when no physical movement happens. This can affect
OpenCanopy and FileVault 2 user interface responsiveness and loading times.
Increasing the polling periods can reduce the impact.
\emph{Note}: The OEM Apple implementation uses a polling rate of 2 ms.
\item
\texttt{PointerPollMask}\\
\textbf{Type}: \texttt{plist\ integer, 32 bit}\\
\textbf{Failsafe}: \texttt{-1}\\
\textbf{Description}: Configure indices of polled pointers.
Selects pointer devices to poll for AppleEvent motion events.
\texttt{-1} implies all devices. A bit sum is used to determine
particular devices. E.g. to enable devices 0, 2, 3 the value
will be \texttt{1+4+8} (corresponding powers of two). A total
of 32 configurable devices is supported.
Certain pointer devices can be present in the firmware even when
no corresponding physical devices are available. These devices
usually are placeholders, aggregate devices, or proxies.
Gathering information from these devices may result in inaccurate
motion activity in the user interfaces and even cause performance
issues. Disabling such pointer devices is recommended for laptop
setups having issues of this kind.
The amount of pointer devices available in the system can be
found in the log. Refer to \texttt{Found N pointer devices} message
for more details.
\emph{Note}: Has no effect when using the OEM Apple implementation
(see \texttt{AppleEvent} setting).
\item
\texttt{PointerSpeedDiv}\\
\textbf{Type}: \texttt{plist\ integer}\\
......@@ -7213,7 +7272,7 @@ with the boot menu.
The recommended value is \texttt{50000} (\texttt{5} milliseconds) or slightly higher. Select
ASUS Z87 boards use \texttt{60000} for the interface. Apple boards use \texttt{100000}.
In case of issues, this option can be left as \texttt{0}.
In case of issues, this option can be left as \texttt{0} to not change the timer resolution.
\end{enumerate}
......
\documentclass[]{article}
%DIF LATEXDIFF DIFFERENCE FILE
%DIF DEL PreviousConfiguration.tex Sun Dec 12 09:04:19 2021
%DIF ADD ../Configuration.tex Sun Jan 2 10:55:11 2022
%DIF DEL PreviousConfiguration.tex Wed Dec 8 21:04:04 2021
%DIF ADD ../Configuration.tex Mon Jan 3 20:26:21 2022
\usepackage{lmodern}
\usepackage{amssymb,amsmath}
......@@ -6898,7 +6898,78 @@ with the boot menu.
\end{itemize}
\item
\texttt{PointerSpeedDiv}\\
\DIFaddbegin \texttt{\DIFadd{PointerPollMin}}\\
\textbf{\DIFadd{Type}}\DIFadd{: }\texttt{\DIFadd{plist\ integer}}\\
\textbf{\DIFadd{Failsafe}}\DIFadd{: }\texttt{\DIFadd{0}}\\
\textbf{\DIFadd{Description}}\DIFadd{: Configure minimal pointer polling period in ms.
}
\DIFadd{This is the minimal period the OpenCore builtin AppleEvent driver polls
pointer devices (e.g. mice, trackpads) for motion events. The current
implementation defaults to 10 ms. Setting }\texttt{\DIFadd{0}} \DIFadd{leaves this
default unchanged.
}
\emph{\DIFadd{Note}}\DIFadd{: The OEM Apple implementation uses a polling rate of 2 ms.
}
\item
\texttt{\DIFadd{PointerPollMax}}\\
\textbf{\DIFadd{Type}}\DIFadd{: }\texttt{\DIFadd{plist\ integer}}\\
\textbf{\DIFadd{Failsafe}}\DIFadd{: }\texttt{\DIFadd{0}}\\
\textbf{\DIFadd{Description}}\DIFadd{: Configure maximum pointer polling period in ms.
}
\DIFadd{This is the maximum period the OpenCore builtin AppleEvent driver polls
pointer devices (e.g. mice, trackpads) for motion events. The period
is increased up to this value as long as the devices do not respond
in time. The current implementation defaults to 80 ms. Setting
}\texttt{\DIFadd{0}} \DIFadd{leaves this default unchanged.
}
\DIFadd{Certain trackpad drivers often found in Dell laptops can be very slow
to respond when no physical movement happens. This can affect
OpenCanopy and FileVault 2 user interface responsiveness and loading times.
Increasing the polling periods can reduce the impact.
}
\emph{\DIFadd{Note}}\DIFadd{: The OEM Apple implementation uses a polling rate of 2 ms.
}
\item
\texttt{\DIFadd{PointerPollMask}}\\
\textbf{\DIFadd{Type}}\DIFadd{: }\texttt{\DIFadd{plist\ integer, 32 bit}}\\
\textbf{\DIFadd{Failsafe}}\DIFadd{: }\texttt{\DIFadd{-1}}\\
\textbf{\DIFadd{Description}}\DIFadd{: Configure indices of polled pointers.
}
\DIFadd{Selects pointer devices to poll for AppleEvent motion events.
}\texttt{\DIFadd{-1}} \DIFadd{implies all devices. A bit sum is used to determine
particular devices. E.g. to enable devices 0, 2, 3 the value
will be }\texttt{\DIFadd{1+4+8}} \DIFadd{(corresponding powers of two). A total
of 32 configurable devices is supported.
}
\DIFadd{Certain pointer devices can be present in the firmware even when
no corresponding physical devices are available. These devices
usually are placeholders, aggregate devices, or proxies.
Gathering information from these devices may result in inaccurate
motion activity in the user interfaces and even cause performance
issues. Disabling such pointer devices is recommended for laptop
setups having issues of this kind.
}
\DIFadd{The amount of pointer devices available in the system can be
found in the log. Refer to }\texttt{\DIFadd{Found N pointer devices}} \DIFadd{message
for more details.
}
\emph{\DIFadd{Note}}\DIFadd{: Has no effect when using the OEM Apple implementation
(see }\texttt{\DIFadd{AppleEvent}} \DIFadd{setting).
}
\item
\DIFaddend \texttt{PointerSpeedDiv}\\
\textbf{Type}: \texttt{plist\ integer}\\
\textbf{Failsafe}: \texttt{1}\\
\textbf{Description}: Configure pointer speed divisor in the OpenCore re-implementation
......@@ -7029,21 +7100,21 @@ with the boot menu.
Enabling this setting routes audio playback from builtin protocols to \DIFdelbegin \DIFdel{a dedicated
audio port (}\DIFdelend \DIFaddbegin \DIFadd{specified
(}\DIFaddend \texttt{\DIFdelbegin \DIFdel{AudioOut}\DIFdelend \DIFaddbegin \DIFadd{AudioOutMask}\DIFaddend }) \DIFaddbegin \DIFadd{dedicated audio ports }\DIFaddend of the specified codec (\texttt{AudioCodec})\DIFaddbegin \DIFadd{,
}\DIFaddend located on the \DIFaddbegin \DIFadd{specified }\DIFaddend audio controller (\texttt{AudioDevice})\DIFaddbegin \DIFadd{.
}
}\DIFaddend located on the \DIFaddbegin \DIFadd{specified }\DIFaddend audio controller (\texttt{AudioDevice}).
\item
\texttt{\DIFadd{DisconnectHda}}\\
\DIFaddbegin \texttt{\DIFadd{DisconnectHda}}\\
\textbf{\DIFadd{Type}}\DIFadd{: }\texttt{\DIFadd{plist\ boolean}}\\
\textbf{\DIFadd{Failsafe}}\DIFadd{: }\texttt{\DIFadd{false}}\\
\textbf{\DIFadd{Description}}\DIFadd{: Disconnect HDA controller before loading drivers.
}
\DIFadd{May be required on some systems (e.g. Apple hardware, VMware Fusion guest) to allow
a UEFI sound driver (such as }\texttt{\DIFadd{AudioDxe}}\DIFadd{) to take control of the audio hardware}\DIFaddend .
a UEFI sound driver (such as }\texttt{\DIFadd{AudioDxe}}\DIFadd{) to take control of the audio hardware.
}
\item
\texttt{MinimumVolume}\\
\DIFaddend \texttt{MinimumVolume}\\
\textbf{Type}: \texttt{plist\ integer}\\
\textbf{Failsafe}: \texttt{0}\\
\textbf{Description}: Minimal heard volume level from \texttt{0} to \texttt{100}.
......@@ -7296,7 +7367,7 @@ with the boot menu.
The recommended value is \texttt{50000} (\texttt{5} milliseconds) or slightly higher. Select
ASUS Z87 boards use \texttt{60000} for the interface. Apple boards use \texttt{100000}.
In case of issues, this option can be left as \texttt{0}.
In case of issues, this option can be left as \texttt{0} \DIFaddbegin \DIFadd{to not change the timer resolution}\DIFaddend .
\end{enumerate}
......
......@@ -1334,6 +1334,12 @@
<integer>50</integer>
<key>KeySubsequentDelay</key>
<integer>5</integer>
<key>PointerPollMask</key>
<integer>-1</integer>
<key>PointerPollMax</key>
<integer>80</integer>
<key>PointerPollMin</key>
<integer>10</integer>
<key>PointerSpeedDiv</key>
<integer>1</integer>
<key>PointerSpeedMul</key>
......
......@@ -1672,6 +1672,12 @@
<integer>50</integer>
<key>KeySubsequentDelay</key>
<integer>5</integer>
<key>PointerPollMask</key>
<integer>-1</integer>
<key>PointerPollMax</key>
<integer>80</integer>
<key>PointerPollMin</key>
<integer>10</integer>
<key>PointerSpeedDiv</key>
<integer>1</integer>
<key>PointerSpeedMul</key>
......
......@@ -18,6 +18,9 @@
#include <Protocol/AppleEvent.h>
#define POINTER_POLL_DEFAULT 0
#define POINTER_POLL_ALL_MASK (UINT32)(-1)
/**
Install and initialise Apple Event protocol.
......@@ -33,6 +36,9 @@
@param[in] GraphicsInputMirroring If true, disable Apple default behaviour which can
prevent keyboard input reaching non-Apple GUI UEFI apps.
OC builtin AppleEvent only.
@param[in] PointerPollMin Pointer polling minimal period in ms.
@param[in] PointerPollMax Pointer polling maximum period in ms.
@param[in] PointerPollMask Pointer polling mask to choose polled handles.
@param[in] PointerSpeedDiv Pointer speed divisor. If zero, warn and use 1.
@param[in] PointerSpeedMul Pointer speed multiplier.
......@@ -46,6 +52,9 @@ OcAppleEventInstallProtocol (
IN UINT16 KeyInitialDelay,
IN UINT16 KeySubsequentDelay,
IN BOOLEAN GraphicsInputMirroring,
IN UINT32 PointerPollMin,
IN UINT32 PointerPollMax,
IN UINT32 PointerPollMask,
IN UINT16 PointerSpeedDiv,
IN UINT16 PointerSpeedMul
);
......
......@@ -604,6 +604,9 @@ typedef enum {
_(UINT16 , KeyInitialDelay , , 50 , ()) \
_(UINT16 , KeySubsequentDelay , , 5 , ()) \
_(BOOLEAN , GraphicsInputMirroring, , FALSE , ()) \
_(UINT32 , PointerPollMin , , 0 , ()) \
_(UINT32 , PointerPollMax , , 0 , ()) \
_(UINT32 , PointerPollMask , , ((UINT32) (-1)) , ()) \
_(UINT16 , PointerSpeedDiv , , 1 , ()) \
_(UINT16 , PointerSpeedMul , , 1 , ())
OC_DECLARE (OC_UEFI_APPLEINPUT)
......
......@@ -163,6 +163,13 @@ InternalSetKeyBehaviour (
IN BOOLEAN GraphicsInputMirroring
);
VOID
InternalSetPointerPolling (
IN UINT32 PointerPollMin,
IN UINT32 PointerPollMax,
IN UINT32 PointerPollMask
);
VOID
InternalSetPointerSpeed (
IN UINT16 PointerSpeedDiv,
......
......@@ -545,26 +545,6 @@ AppleEventUnload (
return Status;
}
/**
Install and initialise Apple Event protocol.
@param[in] Install If false, do not install even when no suitable OEM version found.
@param[in] Reinstall If true, force overwrite installed protocol.
If false, use Apple OEM protocol where possible.
@param[in] CustomDelays If true, use key delays specified.
If false, use Apple OEM default key delay values.
OC builtin AppleEvent only.
@param[in] KeyInitialDelay Key repeat initial delay in 10ms units.
@param[in] KeySubsequentDelay Key repeat subsequent delay in 10ms units.
If zero, warn and use 1.
@param[in] GraphicsInputMirroring If true, disable Apple default behaviour which can
prevent keyboard input reaching non-Apple GUI UEFI apps.
OC builtin AppleEvent only.
@param[in] PointerSpeedDiv Pointer speed divisor. If zero, warn and use 1.
@param[in] PointerSpeedMul Pointer speed multiplier.
@retval installed or located protocol or NULL.
**/
APPLE_EVENT_PROTOCOL *
OcAppleEventInstallProtocol (
IN BOOLEAN Install,
......@@ -573,6 +553,9 @@ OcAppleEventInstallProtocol (
IN UINT16 KeyInitialDelay,
IN UINT16 KeySubsequentDelay,
IN BOOLEAN GraphicsInputMirroring,
IN UINT32 PointerPollMin,
IN UINT32 PointerPollMax,
IN UINT32 PointerPollMask,
IN UINT16 PointerSpeedDiv,
IN UINT16 PointerSpeedMul
)
......@@ -627,7 +610,8 @@ OcAppleEventInstallProtocol (
KeySubsequentDelay,
GraphicsInputMirroring
);
InternalSetPointerPolling (PointerPollMin, PointerPollMax, PointerPollMask);
InternalSetPointerSpeed (PointerSpeedDiv, PointerSpeedMul);
Status = gBS->InstallMultipleProtocolInterfaces (
......
......@@ -25,6 +25,7 @@ WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
#include <Protocol/SimplePointer.h>
#include <Library/AppleEventLib.h>
#include <Library/OcAppleEventLib.h>
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
......@@ -43,8 +44,8 @@ WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
// most machines. Poll with 10 ms, which matches the keyboard behaviour,
// and also is the minimum for QEMU.
//
#define POINTER_POLL_FREQUENCY EFI_TIMER_PERIOD_MILLISECONDS (10)
#define MAX_POINTER_POLL_FREQUENCY EFI_TIMER_PERIOD_MILLISECONDS (80)
#define MIN_POINTER_POLL_PERIOD 10
#define MAX_POINTER_POLL_PERIOD 80
GLOBAL_REMOVE_IF_UNREFERENCED UINT32 mPointerSpeedDiv = 0;
GLOBAL_REMOVE_IF_UNREFERENCED UINT32 mPointerSpeedMul = 0;
......@@ -91,6 +92,10 @@ STATIC EFI_EVENT mSimplePointerPollEvent = NULL;
// mSimplePointerPollTime
STATIC UINT64 mSimplePointerPollTime;
STATIC UINT64 mSimplePointerMinPollTime = MIN_POINTER_POLL_PERIOD * 10000;
STATIC UINT64 mSimplePointerMaxPollTime = MAX_POINTER_POLL_PERIOD * 10000;
STATIC UINT32 mSimplePointerPollMask = POINTER_POLL_ALL_MASK;
// mUiScale
STATIC UINT8 mUiScale = 1;
......@@ -134,6 +139,28 @@ STATIC UINT64 mMaxPointerResolutionY = 1;
STATIC INT64 mPointerRawX;
STATIC INT64 mPointerRawY;
VOID
InternalSetPointerPolling (
IN UINT32 PointerPollMin,
IN UINT32 PointerPollMax,
IN UINT32 PointerPollMask
)
{
if (PointerPollMin == POINTER_POLL_DEFAULT) {
PointerPollMin = MIN_POINTER_POLL_PERIOD;
}
mSimplePointerMinPollTime = EFI_TIMER_PERIOD_MILLISECONDS (PointerPollMin);
if (PointerPollMax == POINTER_POLL_DEFAULT) {
PointerPollMax = MAX_POINTER_POLL_PERIOD;
}
mSimplePointerMaxPollTime = EFI_TIMER_PERIOD_MILLISECONDS (PointerPollMax);
mSimplePointerPollMask = PointerPollMask;
}
VOID
InternalSetPointerSpeed (
IN UINT16 PointerSpeedDiv,
......@@ -725,6 +752,11 @@ InternalSimplePointerPollNotifyFunction (
CommonStatus = EFI_NOT_READY;
for (Index = 0; Index < mNumberOfPointerProtocols; ++Index) {
if (mSimplePointerPollMask != POINTER_POLL_ALL_MASK
&& (Index >= 32 || ((1U << Index) & mSimplePointerPollMask) == 0)) {
continue;
}
Instance = &mPointerProtocols[Index];
SimplePointer = Instance->Interface;
Status = SimplePointer->GetState (SimplePointer, &State);
......@@ -854,7 +886,7 @@ InternalSimplePointerPollNotifyFunction (
// which is still far from enough. The event system on these laptops is pretty broken,
// and even adding gBS->CheckEvent prior to GetState almost does not reduce the time spent.
//
if (mSimplePointerPollEvent != NULL && mSimplePointerPollTime < MAX_POINTER_POLL_FREQUENCY) {
if (mSimplePointerPollEvent != NULL && mSimplePointerPollTime < mSimplePointerMaxPollTime) {
EndTime = GetPerformanceCounter ();
if (StartTime > EndTime) {
EndTime = StartTime;
......@@ -869,8 +901,8 @@ InternalSimplePointerPollNotifyFunction (
);
mSimplePointerPollTime = DivU64x32 (EndTime, 50);
if (mSimplePointerPollTime > MAX_POINTER_POLL_FREQUENCY) {
mSimplePointerPollTime = MAX_POINTER_POLL_FREQUENCY;
if (mSimplePointerPollTime > mSimplePointerMaxPollTime) {
mSimplePointerPollTime = mSimplePointerMaxPollTime;
}
mMaximumClickDuration = (UINT16) DivU64x64Remainder (
......@@ -930,7 +962,7 @@ EventCreateSimplePointerPollEvent (
InternalGetScreenResolution ();
ZeroMem (&mCursorPosition, sizeof (mCursorPosition));
mSimplePointerPollTime = POINTER_POLL_FREQUENCY;
mSimplePointerPollTime = mSimplePointerMinPollTime;
mSimplePointerPollEvent = EventLibCreateNotifyTimerEvent (
InternalSimplePointerPollNotifyFunction,
NULL,
......
......@@ -741,6 +741,9 @@ mUefiAppleInputSchema[] = {
OC_SCHEMA_BOOLEAN_IN ("GraphicsInputMirroring", OC_GLOBAL_CONFIG, Uefi.AppleInput.GraphicsInputMirroring),
OC_SCHEMA_INTEGER_IN ("KeyInitialDelay", OC_GLOBAL_CONFIG, Uefi.AppleInput.KeyInitialDelay),
OC_SCHEMA_INTEGER_IN ("KeySubsequentDelay", OC_GLOBAL_CONFIG, Uefi.AppleInput.KeySubsequentDelay),
OC_SCHEMA_INTEGER_IN ("PointerPollMask", OC_GLOBAL_CONFIG, Uefi.AppleInput.PointerPollMask),
OC_SCHEMA_INTEGER_IN ("PointerPollMax", OC_GLOBAL_CONFIG, Uefi.AppleInput.PointerPollMax),
OC_SCHEMA_INTEGER_IN ("PointerPollMin", OC_GLOBAL_CONFIG, Uefi.AppleInput.PointerPollMin),
OC_SCHEMA_INTEGER_IN ("PointerSpeedDiv", OC_GLOBAL_CONFIG, Uefi.AppleInput.PointerSpeedDiv),
OC_SCHEMA_INTEGER_IN ("PointerSpeedMul", OC_GLOBAL_CONFIG, Uefi.AppleInput.PointerSpeedMul),
};
......
......@@ -46,6 +46,7 @@
gEfiAudioDecodeProtocolGuid ## SOMETIMES_CONSUMES
gEfiDevicePathProtocolGuid ## CONSUMES
gEfiDevicePathProtocolGuid ## CONSUMES
gEfiSimplePointerProtocolGuid ## SOMETIMES_CONSUMES
gEfiLoadedImageProtocolGuid ## CONSUMES
gEfiSimpleFileSystemProtocolGuid ## CONSUMES
gOcInterfaceProtocolGuid ## SOMETIMES_CONSUMES
......
......@@ -58,6 +58,7 @@ WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
#include <Protocol/GraphicsOutput.h>
#include <Protocol/Security.h>
#include <Protocol/Security2.h>
#include <Protocol/SimplePointer.h>
#define OC_EXIT_BOOT_SERVICES_HANDLER_MAX 5
......@@ -413,6 +414,9 @@ OcReinstallProtocols (
Config->Uefi.AppleInput.KeyInitialDelay,
Config->Uefi.AppleInput.KeySubsequentDelay,
Config->Uefi.AppleInput.GraphicsInputMirroring,
Config->Uefi.AppleInput.PointerPollMin,
Config->Uefi.AppleInput.PointerPollMax,
Config->Uefi.AppleInput.PointerPollMask,
Config->Uefi.AppleInput.PointerSpeedDiv,
Config->Uefi.AppleInput.PointerSpeedMul
) == NULL
......@@ -821,6 +825,8 @@ OcLoadUefiSupport (
{
EFI_STATUS Status;
EFI_HANDLE *DriversToConnect;
EFI_HANDLE *HandleBuffer;
UINTN HandleCount;
EFI_EVENT Event;
BOOLEAN AvxEnabled;
......@@ -927,6 +933,22 @@ OcLoadUefiSupport (
OcLoadDrivers (Storage, Config, NULL);
}
DEBUG_CODE_BEGIN ();
HandleCount = 0;
HandleBuffer = NULL;
Status = gBS->LocateHandleBuffer (
ByProtocol,
&gEfiSimplePointerProtocolGuid,
NULL,
&HandleCount,
&HandleBuffer
);
DEBUG ((DEBUG_INFO, "OC: Found %u pointer devices - %r\n", HandleCount, Status));
if (!EFI_ERROR (Status)) {
FreePool (HandleBuffer);
}
DEBUG_CODE_END ();
if (Config->Uefi.Apfs.EnableJumpstart) {
OcApfsConfigure (
Config->Uefi.Apfs.MinVersion,
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册