Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion osu.Android.props
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="ppy.osu.Framework.Android" Version="2026.501.2" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2026.502.1" />
<!-- `ppy.osu.Framework.NativeLibs` is a transitive dependency of `ppy.osu.Framework`
that ships desktop-only natives (Linux/macOS/Windows) under `runtimes/<rid>/native/`
— including a bare Linux `libbass.so`/`libbass_fx.so`/`libbassmix.so` for linux-arm64.
Expand Down
15 changes: 12 additions & 3 deletions osu.Android/Input/AndroidStylusHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,15 @@ public class AndroidStylusHandler : InputHandler, ITabletHandler
/// </summary>
public volatile bool TreatAsTouch;

/// <summary>
/// Mirrored from <see cref="osu.Game.Configuration.OsuSetting.AndroidStylusDisableClick"/>.
/// When true, no left-button click is ever synthesised from pen tip pressure, so the
/// S Pen can be used purely for cursor positioning without accidentally registering taps.
/// Held as a volatile field so the OS dispatch thread can read it without
/// crossing the managed-config bindable lock on every motion event.
/// </summary>
public volatile bool DisableClick;

// Cached area values for hot path (avoids bindable access per event).
private float areaLeft, areaTop, areaWidth, areaHeight;
private float outLeft, outTop, outWidth, outHeight;
Expand Down Expand Up @@ -532,10 +541,10 @@ private void handlePointer(MotionEvent e, int historyIndex, MotionEventActions a
// are intentionally NOT mapped to right/middle (see comment block below), so a single
// read is unavoidable but bounded.
var buttonState = e.ButtonState;
bool isLeftDown = pressure >= cachedPressureThreshold;
if (actionMasked == MotionEventActions.Down || actionMasked == MotionEventActions.ButtonPress) isLeftDown = true;
bool isLeftDown = !DisableClick && pressure >= cachedPressureThreshold;
if (!DisableClick && (actionMasked == MotionEventActions.Down || actionMasked == MotionEventActions.ButtonPress)) isLeftDown = true;
else if (actionMasked == MotionEventActions.Up || actionMasked == MotionEventActions.ButtonRelease || actionMasked == MotionEventActions.Cancel) isLeftDown = false;
else if (actionMasked == MotionEventActions.Move && (buttonState & MotionEventButtonState.Primary) != 0) isLeftDown = true;
else if (!DisableClick && actionMasked == MotionEventActions.Move && (buttonState & MotionEventButtonState.Primary) != 0) isLeftDown = true;

if (TreatAsTouch)
{
Expand Down
12 changes: 9 additions & 3 deletions osu.Android/Input/AndroidStylusSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,14 @@ public AndroidStylusSettings(AndroidStylusHandler handler)
private void load(OsuConfigManager osuConfig)
{
// Appended after the base TabletSettings.AddRange (the area-selection UI),
// so the toggle appears at the bottom of the section. Settings search
// (FilterTerms below) still surfaces it under "s pen" / "stylus" / "touch".
// so the toggles appear at the bottom of the section. Settings search
// (FilterTerms below) still surfaces them under "s pen" / "stylus" / "touch".
Add(new SettingsItemV2(new FormCheckBox
{
Caption = "Disable pen click",
HintText = "When enabled, pressing the S Pen tip does not register as a left click. Useful if you want to use the pen purely for cursor positioning (e.g. when using a keyboard or gamepad for input).",
Current = osuConfig.GetBindable<bool>(OsuSetting.AndroidStylusDisableClick),
}));
Add(new SettingsItemV2(new FormCheckBox
{
Caption = "Treat S Pen as touch",
Expand All @@ -41,7 +47,7 @@ private void load(OsuConfigManager osuConfig)

public override IEnumerable<LocalisableString> FilterTerms => base.FilterTerms.Concat(new LocalisableString[]
{
@"s pen", @"spen", @"stylus", @"pen", @"touch", @"samsung",
@"s pen", @"spen", @"stylus", @"pen", @"touch", @"samsung", @"click", @"disable",
});
}
}
16 changes: 16 additions & 0 deletions osu.Android/OsuGameActivity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,22 @@ protected override void OnCreate(Bundle? savedInstanceState)

holder.AddCallback(this);
}

// Also hide the pointer icon on the SurfaceView itself.
// Setting it only on DecorView is not enough in DeX mode: Android
// uses the innermost view's pointer icon when the cursor is over
// that view, so the SurfaceView's default arrow would still show.
try
{
var surface = GetSurface();

if (surface != null)
surface.PointerIcon = PointerIcon.GetSystemIcon(this, PointerIconType.Null);
}
catch (Exception e)
{
Logger.Log($"[osu!] Failed to hide SurfaceView pointer icon: {e.Message}", LoggingTarget.Input);
}
}
catch (Exception e)
{
Expand Down
9 changes: 9 additions & 0 deletions osu.Android/OsuGameAndroid.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ public partial class OsuGameAndroid : OsuGame
private readonly Bindable<bool> startupFrameSyncMigrationEnabled = new Bindable<bool>();
private readonly Bindable<bool> verboseLogging = new Bindable<bool>();
private readonly Bindable<bool> stylusAsTouch = new Bindable<bool>();
private readonly Bindable<bool> stylusDisableClick = new Bindable<bool>();

[Cached(typeof(IHighPerformanceSessionManager))]
private readonly IHighPerformanceSessionManager highPerformanceSessionManager = new AndroidHighPerformanceSessionManager();
Expand Down Expand Up @@ -281,6 +282,7 @@ private void load(FrameworkConfigManager frameworkConfig)
LocalConfig.BindWith(OsuSetting.AndroidStartupFrameSyncMigrationEnabled, startupFrameSyncMigrationEnabled);
LocalConfig.BindWith(OsuSetting.AndroidVerboseLogging, verboseLogging);
LocalConfig.BindWith(OsuSetting.AndroidStylusAsTouch, stylusAsTouch);
LocalConfig.BindWith(OsuSetting.AndroidStylusDisableClick, stylusDisableClick);

// Mirror the stylus-as-touch toggle into the volatile flag the OS-thread
// dispatch hot path reads on AndroidStylusHandler. Subscribed (not just
Expand All @@ -294,6 +296,12 @@ private void load(FrameworkConfigManager frameworkConfig)
stylusHandler.TreatAsTouch = e.NewValue;
}, true);

stylusDisableClick.BindValueChanged(e =>
{
if (stylusHandler != null)
stylusHandler.DisableClick = e.NewValue;
}, true);

// sentinelOnDisable=true → presence ⇒ "feature disabled". The
// safety nets default to ON, so the sentinel is created only
// when the user explicitly disables them.
Expand Down Expand Up @@ -1995,6 +2003,7 @@ private void registerAndroidInputHandlers(GameHost host)
// in load() may have fired before this point (when stylusHandler was
// still null) — re-applying the current value here closes that race.
stylusHandler.TreatAsTouch = stylusAsTouch.Value;
stylusHandler.DisableClick = stylusDisableClick.Value;

gameActivity.StylusHandler = stylusHandler;
gameActivity.MouseHandler = mouseHandler;
Expand Down
2 changes: 2 additions & 0 deletions osu.Game/Configuration/OsuConfigManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@ protected override void InitialiseDefaults()
SetDefault(OsuSetting.AndroidStartupFrameSyncMigrationEnabled, false);
SetDefault(OsuSetting.AndroidVerboseLogging, false);
SetDefault(OsuSetting.AndroidStylusAsTouch, false);
SetDefault(OsuSetting.AndroidStylusDisableClick, false);
SetDefault(OsuSetting.ShowFpsAdditionalInfo, false);
}

Expand Down Expand Up @@ -579,6 +580,7 @@ public enum OsuSetting
AndroidStartupFrameSyncMigrationEnabled,
AndroidVerboseLogging,
AndroidStylusAsTouch,
AndroidStylusDisableClick,
ShowFpsAdditionalInfo,
RefreshRateFullscreen,
}
Expand Down
4 changes: 2 additions & 2 deletions osu.Game/osu.Game.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Realm" Version="20.1.0" />
<PackageReference Include="ppy.osu.Framework" Version="2026.501.2" />
<PackageReference Include="ppy.osu.Framework" Version="2026.502.1" />
<!--
Explicitly pin `ppy.Veldrid.SPIRV` to the winnerspiros fork build that
`ppy.osu.Framework 2026.501.1` was compiled against. This version is the only
`ppy.osu.Framework 2026.502.1` was compiled against. This version is the only
one whose `runtimes/android-arm64/native/libveldrid-spirv.so` is aligned to 16 KB
pages (required by Android 16+). It lives only as a release asset on
<https://github.com/winnerspiros/veldrid-spirv/releases/tag/1.0> and is vendored
Expand Down
2 changes: 1 addition & 1 deletion osu.iOS.props
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,6 @@
<SuppressTrimAnalysisWarnings>true</SuppressTrimAnalysisWarnings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Framework.iOS" Version="2026.501.2" />
<PackageReference Include="ppy.osu.Framework.iOS" Version="2026.502.1" />
</ItemGroup>
</Project>
Loading