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
32 changes: 30 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,14 +105,42 @@ jobs:
NUnit.ConsoleOut=0

# Attempt to upload results even if test fails.
# https://docs.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#always
# https://docs.github.com/en/actions/reference/workflows-and-actions/expressions#cancelled
- name: Upload Test Results
uses: actions/upload-artifact@v7
if: ${{ always() }}
if: ${{ !cancelled() }}
with:
name: osu-test-results-${{matrix.os.prettyname}}-${{matrix.threadingMode}}
path: ${{github.workspace}}/TestResults/TestResults-${{matrix.os.prettyname}}-${{matrix.threadingMode}}.trx

test-results:
name: Test results
runs-on: ubuntu-latest
# we want to wait for the `test` job to complete, but run regardless of whether it succeeds or fails
# https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#example-not-requiring-successful-dependent-jobs
if: ${{ !cancelled() }}
needs: test
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Download results
uses: actions/download-artifact@v8
with:
pattern: osu-test-results-*
merge-multiple: true

- name: Add test results summary to workflow run
uses: dorny/test-reporter@v3.0.0
with:
name: Results
path: "*.trx"
reporter: dotnet-trx
list-suites: 'failed'
list-tests: 'failed'
use-actions-summary: 'true'

build-only-android:
name: Build only (Android)
runs-on: windows-latest
Expand Down
45 changes: 0 additions & 45 deletions .github/workflows/report-nunit.yml

This file was deleted.

17 changes: 16 additions & 1 deletion build/PatchElfPageSize.targets
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,22 @@ try
return true;
}

System.IO.File.WriteAllBytes(FilePath, newData);
// Even with a lock file, the actual .so may be locked for reading by another MSBuild process.
// Retry writing the actual file.
for (int attempt = 0; attempt < 30; attempt++)
{
try
{
System.IO.File.WriteAllBytes(FilePath, newData);
WasPatched = true;
break;
}
catch (System.IO.IOException)
{
if (attempt == 29) throw;
System.Threading.Thread.Sleep(1000);
}
}
WasPatched = true;
Log.LogMessage(MessageImportance.High,
"PatchElfPageSize: patched {0} (align 0x1000 -> 0x{1:X}, +{2} bytes)",
Expand Down
133 changes: 133 additions & 0 deletions osu.Android/Input/AndroidKeyboardHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using Android.Views;
using osu.Framework.Input.Handlers;
using osu.Framework.Input.StateChanges;
using osu.Framework.Platform;
using osuTK.Input;

namespace osu.Android.Input
{
public class AndroidKeyboardHandler : InputHandler
{
public override string Description => "Keyboard (Low Latency)";
public override bool IsActive => Enabled.Value;

public AndroidKeyboardHandler()
{
Enabled.Default = true;
Enabled.Value = true;
}

public override bool Initialize(GameHost host) => true;

public bool HandleKeyEvent(KeyEvent e)
{
if (!Enabled.Value) return false;

if (e.KeyCode == Keycode.Back || e.KeyCode == Keycode.Home || e.KeyCode == Keycode.Menu || e.KeyCode == Keycode.VolumeUp || e.KeyCode == Keycode.VolumeDown || e.KeyCode == Keycode.VolumeMute)
return false;

if ((e.Source & InputSourceType.Keyboard) != InputSourceType.Keyboard)
return false;

var key = mapKey(e.KeyCode);
if (key == Key.Unknown) return false;

bool isDown = e.Action == KeyEventActions.Down;
if (e.RepeatCount > 0 && isDown) return true;

PendingInputs.Enqueue(new KeyboardKeyInput(key, isDown));
return true;
}

private Key mapKey(Keycode code)
{
switch (code)
{
case Keycode.A: return Key.A;
case Keycode.B: return Key.B;
case Keycode.C: return Key.C;
case Keycode.D: return Key.D;
case Keycode.E: return Key.E;
case Keycode.F: return Key.F;
case Keycode.G: return Key.G;
case Keycode.H: return Key.H;
case Keycode.I: return Key.I;
case Keycode.J: return Key.J;
case Keycode.K: return Key.K;
case Keycode.L: return Key.L;
case Keycode.M: return Key.M;
case Keycode.N: return Key.N;
case Keycode.O: return Key.O;
case Keycode.P: return Key.P;
case Keycode.Q: return Key.Q;
case Keycode.R: return Key.R;
case Keycode.S: return Key.S;
case Keycode.T: return Key.T;
case Keycode.U: return Key.U;
case Keycode.V: return Key.V;
case Keycode.W: return Key.W;
case Keycode.X: return Key.X;
case Keycode.Y: return Key.Y;
case Keycode.Z: return Key.Z;
case Keycode.Num0: return Key.Number0;
case Keycode.Num1: return Key.Number1;
case Keycode.Num2: return Key.Number2;
case Keycode.Num3: return Key.Number3;
case Keycode.Num4: return Key.Number4;
case Keycode.Num5: return Key.Number5;
case Keycode.Num6: return Key.Number6;
case Keycode.Num7: return Key.Number7;
case Keycode.Num8: return Key.Number8;
case Keycode.Num9: return Key.Number9;
case Keycode.DpadUp: return Key.Up;
case Keycode.DpadDown: return Key.Down;
case Keycode.DpadLeft: return Key.Left;
case Keycode.DpadRight: return Key.Right;
case Keycode.Enter: return Key.Enter;
case Keycode.Escape: return Key.Escape;
case Keycode.Space: return Key.Space;
case Keycode.Tab: return Key.Tab;
case Keycode.Del: return Key.BackSpace;
case Keycode.ForwardDel: return Key.Delete;
case Keycode.MoveHome: return Key.Home;
case Keycode.MoveEnd: return Key.End;
case Keycode.PageUp: return Key.PageUp;
case Keycode.PageDown: return Key.PageDown;
case Keycode.ShiftLeft: return Key.ShiftLeft;
case Keycode.ShiftRight: return Key.ShiftRight;
case Keycode.CtrlLeft: return Key.ControlLeft;
case Keycode.CtrlRight: return Key.ControlRight;
case Keycode.AltLeft: return Key.AltLeft;
case Keycode.AltRight: return Key.AltRight;
case Keycode.CapsLock: return Key.CapsLock;
case Keycode.F1: return Key.F1;
case Keycode.F2: return Key.F2;
case Keycode.F3: return Key.F3;
case Keycode.F4: return Key.F4;
case Keycode.F5: return Key.F5;
case Keycode.F6: return Key.F6;
case Keycode.F7: return Key.F7;
case Keycode.F8: return Key.F8;
case Keycode.F9: return Key.F9;
case Keycode.F10: return Key.F10;
case Keycode.F11: return Key.F11;
case Keycode.F12: return Key.F12;
case Keycode.Grave: return Key.Tilde;
case Keycode.Minus: return Key.Minus;
case Keycode.Equals: return Key.Plus;
case Keycode.LeftBracket: return Key.BracketLeft;
case Keycode.RightBracket: return Key.BracketRight;
case Keycode.Backslash: return Key.BackSlash;
case Keycode.Semicolon: return Key.Semicolon;
case Keycode.Apostrophe: return Key.Quote;
case Keycode.Comma: return Key.Comma;
case Keycode.Period: return Key.Period;
case Keycode.Slash: return Key.Slash;
default: return Key.Unknown;
}
}
}
}
71 changes: 71 additions & 0 deletions osu.Android/Input/AndroidMouseHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using Android.Views;
using osu.Framework.Input.Handlers;
using osu.Framework.Input.StateChanges;
using osu.Framework.Platform;
using osuTK;
using osuTK.Input;

namespace osu.Android.Input
{
public class AndroidMouseHandler : InputHandler
{
public override string Description => "Mouse (Low Latency)";
public override bool IsActive => Enabled.Value;

public AndroidMouseHandler()
{
Enabled.Default = true;
Enabled.Value = true;
}

public override bool Initialize(GameHost host) => true;

public void HandleMotionEvent(MotionEvent e)
{
if (!Enabled.Value) return;

if (e.ActionMasked == MotionEventActions.Scroll)
{
float scrollX = e.GetAxisValue(Axis.Hscroll);
float scrollY = e.GetAxisValue(Axis.Vscroll);
if (scrollX != 0 || scrollY != 0)
{
PendingInputs.Enqueue(new MouseScrollRelativeInput { Delta = new Vector2(scrollX, scrollY), IsPrecise = true });
}
return;
}

for (int i = 0; i < e.HistorySize; i++)
{
handlePointer(e, i);
}
handlePointer(e, -1);
}

private void handlePointer(MotionEvent e, int historyIndex)
{
const int pointer_index = 0;
if (e.PointerCount <= pointer_index) return;

float x = historyIndex < 0 ? e.GetX(pointer_index) : e.GetHistoricalX(pointer_index, historyIndex);
float y = historyIndex < 0 ? e.GetY(pointer_index) : e.GetHistoricalY(pointer_index, historyIndex);

PendingInputs.Enqueue(new MousePositionAbsoluteInput { Position = new Vector2(x, y) });

bool left = (e.ButtonState & MotionEventButtonState.Primary) != 0;
bool right = (e.ButtonState & MotionEventButtonState.Secondary) != 0;
bool middle = (e.ButtonState & MotionEventButtonState.Tertiary) != 0;

if (left != lastLeft) { PendingInputs.Enqueue(new MouseButtonInput(MouseButton.Left, left)); lastLeft = left; }
if (right != lastRight) { PendingInputs.Enqueue(new MouseButtonInput(MouseButton.Right, right)); lastRight = right; }
if (middle != lastMiddle) { PendingInputs.Enqueue(new MouseButtonInput(MouseButton.Middle, middle)); lastMiddle = middle; }
}

private bool lastLeft;
private bool lastRight;
private bool lastMiddle;
}
}
Loading
Loading