Skip to content

Commit 5433f32

Browse files
authored
Merge pull request #284 from winnerspiros/copilot/check-vulkan-black-screen-issue
Fix S Pen / tablet cursor stuck at top-left + upstream merge (ranked play duels, beatmap strip)
2 parents c00beca + 0697f41 commit 5433f32

20 files changed

Lines changed: 375 additions & 37 deletions

osu.Android.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@
9999
</PropertyGroup>
100100

101101
<ItemGroup>
102-
<PackageReference Include="ppy.osu.Framework.Android" Version="2026.428.4" />
102+
<PackageReference Include="ppy.osu.Framework.Android" Version="2026.501.1" />
103103
<!-- `ppy.osu.Framework.NativeLibs` is a transitive dependency of `ppy.osu.Framework`
104104
that ships desktop-only natives (Linux/macOS/Windows) under `runtimes/<rid>/native/`
105105
— including a bare Linux `libbass.so`/`libbass_fx.so`/`libbassmix.so` for linux-arm64.

osu.Android/Input/AndroidStylusHandler.cs

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,37 @@ public override bool Initialize(GameHost host)
137137

138138
AreaSize.BindValueChanged(_ => updateCachedTransform());
139139
AreaOffset.BindValueChanged(_ => updateCachedTransform());
140-
OutputAreaSize.BindValueChanged(_ => updateCachedTransform());
141-
OutputAreaOffset.BindValueChanged(_ => updateCachedTransform());
140+
141+
// OutputAreaSize and OutputAreaOffset need a guard against ScalingContainer's
142+
// normalised-coordinate writes. ScalingContainer assumes desktop tablet handlers
143+
// use a [0..1] normalised output space and writes Vector2.One / (0.5, 0.5) when
144+
// game scaling mode is not "Everything". AndroidStylusHandler works in *pixel*
145+
// space, so (1, 1) means a 1×1 pixel output area — which collapses every mapped
146+
// cursor position to ≈(0, 0) and keeps the pointer stuck at the top-left corner
147+
// regardless of where the S Pen physically is. When we detect a sub-pixel write
148+
// (both components ≤ 2) and we already know the real screen size (> 10 px), we
149+
// restore the pixel-space output area immediately.
150+
OutputAreaSize.BindValueChanged(e =>
151+
{
152+
if (e.NewValue.X <= 2f && e.NewValue.Y <= 2f && cachedTabletSizeX > 10f)
153+
{
154+
restorePixelOutputArea();
155+
return;
156+
}
157+
158+
updateCachedTransform();
159+
});
160+
OutputAreaOffset.BindValueChanged(e =>
161+
{
162+
if (e.NewValue.X <= 1f && e.NewValue.Y <= 1f && cachedTabletSizeX > 10f)
163+
{
164+
restorePixelOutputArea();
165+
return;
166+
}
167+
168+
updateCachedTransform();
169+
});
170+
142171
Rotation.BindValueChanged(_ => updateCachedTransform());
143172
PressureThreshold.BindValueChanged(v => cachedPressureThreshold = v.NewValue, true);
144173

@@ -150,6 +179,23 @@ public override bool Initialize(GameHost host)
150179
return base.Initialize(host);
151180
}
152181

182+
/// <summary>
183+
/// Restores <see cref="OutputAreaSize"/> and <see cref="OutputAreaOffset"/> to the
184+
/// actual pixel dimensions of the screen. Called when we detect that
185+
/// <see cref="osu.Game.Graphics.Containers.ScalingContainer"/> has overwritten the
186+
/// pixel-space output area with its normalised-coordinate sentinel values.
187+
/// </summary>
188+
private void restorePixelOutputArea()
189+
{
190+
float w = cachedTabletSizeX;
191+
float h = cachedTabletSizeY;
192+
193+
if (w <= 10f || h <= 10f) return;
194+
195+
OutputAreaSize.Value = new Vector2(w, h);
196+
OutputAreaOffset.Value = new Vector2(w / 2f, h / 2f);
197+
}
198+
153199
/// <summary>
154200
/// Sets the digitizer/display dimensions. Must be called after the display is known,
155201
/// and re-called from <see cref="OsuGameAndroid.RefreshStylusDisplaySize"/> on each
@@ -185,14 +231,18 @@ public void SetDisplaySize(int width, int height)
185231
// legacy 1920x1080 ctor default seeded in Initialize, or still at the
186232
// auto-default we installed on a previous SetDisplaySize call (so a phone
187233
// rotation re-syncs the area mapping rather than leaving the user pinned to
188-
// the previous orientation's bounds).
234+
// the previous orientation's bounds). Also reset if ScalingContainer has
235+
// previously written its normalised-space sentinel (≤ 2 px) — those are
236+
// not user-configured values and must not be preserved.
189237
if (AreaSize.Value == default || AreaSize.Value == legacy_default_size || AreaSize.Value == previousAuto)
190238
{
191239
AreaSize.Value = size;
192240
AreaOffset.Value = size / 2;
193241
}
194242

195-
if (OutputAreaSize.Value == default || OutputAreaSize.Value == legacy_default_size || OutputAreaSize.Value == previousAuto)
243+
bool outputIsNormalisedSentinel = OutputAreaSize.Value.X <= 2f && OutputAreaSize.Value.Y <= 2f;
244+
245+
if (OutputAreaSize.Value == default || OutputAreaSize.Value == legacy_default_size || OutputAreaSize.Value == previousAuto || outputIsNormalisedSentinel)
196246
{
197247
OutputAreaSize.Value = size;
198248
OutputAreaOffset.Value = size / 2;

osu.Android/OsuGameAndroid.cs

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2000,13 +2000,19 @@ private void registerAndroidInputHandlers(GameHost host)
20002000
gameActivity.MouseHandler = mouseHandler;
20012001
gameActivity.KeyboardHandler = keyboardHandler;
20022002

2003-
// Match the screen / digitiser dimensions for tablet area mapping.
2004-
applyStylusDisplaySize(stylusHandler);
2005-
20062003
// Initialize each handler the same way the framework would in
20072004
// CreateAvailableInputHandlers — sets the protected Host field on the base
20082005
// class and runs handler-specific bindable wiring. Skip-on-failure: a single
20092006
// misbehaving handler must not knock out the other two.
2007+
//
2008+
// NOTE: applyStylusDisplaySize is called AFTER Initialize so that:
2009+
// 1. base.Initialize(host) has finished setting up the Host field and any
2010+
// framework config bindings, avoiding a race where config-loaded values
2011+
// overwrite the display size we push in SetDisplaySize.
2012+
// 2. The OutputAreaSize BindValueChanged guard installed in Initialize is
2013+
// already in place before SetDisplaySize fires the first write, ensuring
2014+
// the normalised-sentinel detector can intercept subsequent ScalingContainer
2015+
// writes on the very first updateSize() call.
20102016
var newHandlers = new osu.Framework.Input.Handlers.InputHandler[] { stylusHandler, mouseHandler, keyboardHandler };
20112017

20122018
foreach (var h in newHandlers)
@@ -2022,6 +2028,11 @@ private void registerAndroidInputHandlers(GameHost host)
20222028
}
20232029
}
20242030

2031+
// Match the screen / digitiser dimensions for tablet area mapping.
2032+
// Called after Initialize so the display-size write lands on top of any
2033+
// framework config-restore and the OutputAreaSize guard is already armed.
2034+
applyStylusDisplaySize(stylusHandler);
2035+
20252036
// Reflectively replace AvailableInputHandlers with the union of the host's
20262037
// existing immutable array and our three handlers. The property has a private
20272038
// setter; we use reflection because the framework does not expose a public
@@ -2105,10 +2116,13 @@ private void registerAndroidInputHandlers(GameHost host)
21052116
/// </para>
21062117
///
21072118
/// <para>
2108-
/// As a final defensive guard the resolved bounds are normalised to landscape
2109-
/// (<c>max(W,H) × min(W,H)</c>) since the activity is landscape-locked on phones
2119+
/// As a final defensive guard on phones, the resolved bounds are normalised to landscape
2120+
/// (<c>max(W,H) × min(W,H)</c>) since the activity is landscape-locked there
21102121
/// this neutralises the residual case where an OEM still hands back portrait
2111-
/// bounds for the current metrics on certain Android skins.
2122+
/// bounds for the current metrics on certain Android skins. Tablets and DeX are
2123+
/// excluded because they run in <see cref="ScreenOrientation.FullUser"/> / external
2124+
/// display orientation, where forcing landscape makes portrait tablet S Pen
2125+
/// coordinates divide by the wrong axis and pins the pointer near the origin.
21122126
/// </para>
21132127
/// </summary>
21142128
private void applyStylusDisplaySize(AndroidStylusHandler handler)
@@ -2178,13 +2192,18 @@ private void applyStylusDisplaySize(AndroidStylusHandler handler)
21782192
if (width <= 0 || height <= 0)
21792193
return;
21802194

2181-
// Canonicalise to landscape since the phone activity is landscape-locked
2195+
// Canonicalise to landscape only when the activity is actually landscape-locked
21822196
// (see [Activity(ScreenOrientation = ScreenOrientation.Landscape)] on
2183-
// OsuGameActivity). Tablets / DeX run in FullUser orientation so the
2184-
// canonicalisation is harmless — we still get a (W, H) pair whose major
2185-
// axis matches MotionEvent.GetX's range.
2186-
int w = Math.Max(width, height);
2187-
int h = Math.Min(width, height);
2197+
// OsuGameActivity). Tablets / DeX run in FullUser / external-display
2198+
// orientation, so preserve the current window metrics exactly there.
2199+
int w = width;
2200+
int h = height;
2201+
2202+
if (!gameActivity.IsTablet && !gameActivity.IsDeX)
2203+
{
2204+
w = Math.Max(width, height);
2205+
h = Math.Min(width, height);
2206+
}
21882207

21892208
handler.SetDisplaySize(w, h);
21902209
}

osu.Game/Beatmaps/Drawables/Cards/CollapsibleButtonContainer.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,8 @@ private void updateState()
170170

171171
// By limiting the width we avoid this box showing up as an outline around the drawables that are on top of it.
172172
background.ResizeWidthTo(buttonAreaWidth + BeatmapCard.CORNER_RADIUS, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint);
173-
background.FadeTo(ShowDetails.Value ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint);
173+
if (ButtonsCollapsedWidth == 0)
174+
background.FadeTo(ShowDetails.Value ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint);
174175

175176
background.FadeColour(downloadTracker.State.Value == DownloadState.LocallyAvailable ? colours.Lime0 : colourProvider.Background3, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint);
176177
buttons.FadeTo(ShowDetails.Value ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint);

osu.Game/Online/Matchmaking/IMatchmakingClient.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ public interface IMatchmakingClient : IStatefulUserHubClient
3838
/// </summary>
3939
Task MatchmakingRoomInvitedWithParams(MatchmakingRoomInvitationParams invitation);
4040

41+
/// <summary>
42+
/// Signals that the user has been issued a duel by another user.
43+
/// </summary>
44+
/// <param name="issue">Contains the parameters for the duel.</param>
45+
Task MatchmakingDuelIssued(MatchmakingDuelIssuedParams issue);
46+
4147
/// <summary>
4248
/// Signals that the matchmaking room is ready to be opened.
4349
/// </summary>

osu.Game/Online/Matchmaking/IMatchmakingServer.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,18 @@ public interface IMatchmakingServer
4040
/// </summary>
4141
Task MatchmakingAcceptInvitation();
4242

43+
/// <summary>
44+
/// Issues a matchmaking duel.
45+
/// </summary>
46+
/// <param name="request">Describes the duel.</param>
47+
Task<MatchmakingIssueDuelResponse> MatchmakingIssueDuel(MatchmakingIssueDuelRequest request);
48+
49+
/// <summary>
50+
/// Accepts a matchmaking duel invitation.
51+
/// </summary>
52+
/// <param name="request">Describes the duel.</param>
53+
Task<MatchmakingAcceptDuelResponse> MatchmakingAcceptDuel(MatchmakingAcceptDuelRequest request);
54+
4355
/// <summary>
4456
/// Declines a matchmaking room invitation.
4557
/// </summary>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
2+
// See the LICENCE file in the repository root for full licence text.
3+
4+
using System;
5+
using MessagePack;
6+
7+
namespace osu.Game.Online.Matchmaking
8+
{
9+
[MessagePackObject]
10+
[Serializable]
11+
public class MatchmakingDuelIssuedParams
12+
{
13+
[Key(0)]
14+
public Guid Id { get; set; }
15+
16+
[Key(1)]
17+
public int UserId { get; set; }
18+
19+
[Key(2)]
20+
public MatchmakingPool Pool { get; set; } = new MatchmakingPool();
21+
}
22+
}

osu.Game/Online/Matchmaking/MatchmakingPool.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,31 @@ public class MatchmakingPool : IEquatable<MatchmakingPool>
2626
[Key(4)]
2727
public MatchmakingPoolType Type { get; set; } = MatchmakingPoolType.QuickPlay;
2828

29+
[IgnoreMember]
30+
public string DisplayName
31+
{
32+
get
33+
{
34+
switch (RulesetId)
35+
{
36+
case 0:
37+
return $"osu! ({Name})";
38+
39+
case 1:
40+
return $"osu!taiko ({Name})";
41+
42+
case 2:
43+
return $"osu!catch ({Name})";
44+
45+
case 3:
46+
return $"osu!mania {Variant}K ({Name})";
47+
48+
default:
49+
return Name;
50+
}
51+
}
52+
}
53+
2954
public bool Equals(MatchmakingPool? other)
3055
=> other != null
3156
&& Id == other.Id
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
2+
// See the LICENCE file in the repository root for full licence text.
3+
4+
using System;
5+
using MessagePack;
6+
7+
namespace osu.Game.Online.Matchmaking.Requests
8+
{
9+
[MessagePackObject]
10+
[Serializable]
11+
public class MatchmakingAcceptDuelRequest
12+
{
13+
[Key(0)]
14+
public Guid Id { get; set; }
15+
}
16+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
2+
// See the LICENCE file in the repository root for full licence text.
3+
4+
using System;
5+
using MessagePack;
6+
7+
namespace osu.Game.Online.Matchmaking.Requests
8+
{
9+
[MessagePackObject]
10+
[Serializable]
11+
public class MatchmakingIssueDuelRequest
12+
{
13+
[Key(0)]
14+
public int UserId { get; set; }
15+
16+
[Key(1)]
17+
public int PoolId { get; set; }
18+
}
19+
}

0 commit comments

Comments
 (0)