Compare commits

..

6 Commits

Author SHA1 Message Date
e3ecc6a59f Build macOS x86_64 release
Add actions to build a release for macOS x86_64 in addition to the
aarch64 version.
2024-11-26 00:20:25 +01:00
6a2f35e7dc Specify architecture for Linux and macOS releases
Co-authored-by: Genxster1998 <ck.2229.ck@gmail.com>
2024-11-26 00:18:27 +01:00
4f74bf7f2b Rename TARGET to TARGET_DIRNAME 2024-11-26 00:18:03 +01:00
5c7d0717a2 Use FORMAT variable name in package_client.sh
The format is used several times, avoid using "$2" directly.
2024-11-26 00:16:46 +01:00
f7ff391afd Simplify GitHub actions step descriptions
Each step is executed within the context of an action, so there is no
need to mention the name of the action.
2024-11-26 00:16:04 +01:00
b901cb1c40 Remove apt update on GitHub Actions
Assume the image is up-to-date.
2024-11-26 00:13:02 +01:00
8 changed files with 44 additions and 106 deletions

View File

@ -85,15 +85,6 @@ jobs:
build-linux-x86_64: build-linux-x86_64:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check architecture
run: |
arch=$(uname -m)
if [[ "$arch" != x86_64 ]]
then
echo "Unexpected architecture: $arch" >&2
exit 1
fi
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
@ -190,15 +181,6 @@ jobs:
build-macos-aarch64: build-macos-aarch64:
runs-on: macos-latest runs-on: macos-latest
steps: steps:
- name: Check architecture
run: |
arch=$(uname -m)
if [[ "$arch" != arm64 ]]
then
echo "Unexpected architecture: $arch" >&2
exit 1
fi
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
@ -227,15 +209,6 @@ jobs:
build-macos-x86_64: build-macos-x86_64:
runs-on: macos-13 runs-on: macos-13
steps: steps:
- name: Check architecture
run: |
arch=$(uname -m)
if [[ "$arch" != x86_64 ]]
then
echo "Unexpected architecture: $arch" >&2
exit 1
fi
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
@ -486,7 +459,7 @@ jobs:
- name: Download release-macos-x86_64 - name: Download release-macos-x86_64
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
name: release-macos-x86_64 name: release-macos-aarch64
path: release/output/ path: release/output/
- name: Package server - name: Package server

View File

@ -23,20 +23,14 @@ To control the device without mirroring:
scrcpy --no-video --no-audio scrcpy --no-video --no-audio
``` ```
By default, the mouse is disabled when video playback is turned off. By default, mouse mode is switched to UHID if video mirroring is disabled (a
relative mouse mode is required).
To control the device using a relative mouse, enable UHID mouse mode:
```bash
scrcpy --no-video --no-audio --mouse=uhid
scrcpy --no-video --no-audio -M # short version
```
To also use a UHID keyboard, set it explicitly: To also use a UHID keyboard, set it explicitly:
```bash ```bash
scrcpy --no-video --no-audio --mouse=uhid --keyboard=uhid scrcpy --no-video --no-audio --keyboard=uhid
scrcpy --no-video --no-audio -MK # short version scrcpy --no-video --no-audio -K # short version
``` ```
To use AOA instead (over USB only): To use AOA instead (over USB only):

View File

@ -4,13 +4,13 @@ cd "$(dirname ${BASH_SOURCE[0]})"
. build_common . build_common
cd .. # root project dir cd .. # root project dir
ARCH="$1"
if [[ $# != 1 ]] if [[ $# != 1 ]]
then then
echo "Syntax: $0 <arch>" >&2 echo "Syntax: $0 <arch>" >&2
exit 1 exit 1
fi fi
ARCH="$1"
LINUX_BUILD_DIR="$WORK_DIR/build-linux-$ARCH" LINUX_BUILD_DIR="$WORK_DIR/build-linux-$ARCH"
app/deps/adb_linux.sh app/deps/adb_linux.sh

View File

@ -4,13 +4,13 @@ cd "$(dirname ${BASH_SOURCE[0]})"
. build_common . build_common
cd .. # root project dir cd .. # root project dir
ARCH="$1"
if [[ $# != 1 ]] if [[ $# != 1 ]]
then then
echo "Syntax: $0 <arch>" >&2 echo "Syntax: $0 <arch>" >&2
exit 1 exit 1
fi fi
ARCH="$1"
MACOS_BUILD_DIR="$WORK_DIR/build-macos-$ARCH" MACOS_BUILD_DIR="$WORK_DIR/build-macos-$ARCH"
app/deps/adb_macos.sh app/deps/adb_macos.sh

View File

@ -207,15 +207,13 @@ public final class CleanUp {
} }
} }
// Change the power of the main display when mirroring a virtual display if (displayId != Device.DISPLAY_ID_NONE && Device.isScreenOn(displayId)) {
int targetDisplayId = displayId != Device.DISPLAY_ID_NONE ? displayId : 0;
if (Device.isScreenOn(targetDisplayId)) {
if (powerOffScreen) { if (powerOffScreen) {
Ln.i("Power off screen"); Ln.i("Power off screen");
Device.powerOffScreen(targetDisplayId); Device.powerOffScreen(displayId);
} else if (restoreDisplayPower) { } else if (restoreDisplayPower) {
Ln.i("Restoring display power"); Ln.i("Restoring display power");
Device.setDisplayPower(targetDisplayId, true); Device.setDisplayPower(displayId, true);
} }
} }

View File

@ -21,7 +21,6 @@ import android.content.IOnPrimaryClipChangedListener;
import android.content.Intent; import android.content.Intent;
import android.os.Build; import android.os.Build;
import android.os.SystemClock; import android.os.SystemClock;
import android.util.Pair;
import android.view.InputDevice; import android.view.InputDevice;
import android.view.KeyCharacterMap; import android.view.KeyCharacterMap;
import android.view.KeyEvent; import android.view.KeyEvent;
@ -282,7 +281,7 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
setClipboard(msg.getText(), msg.getPaste(), msg.getSequence()); setClipboard(msg.getText(), msg.getPaste(), msg.getSequence());
break; break;
case ControlMessage.TYPE_SET_DISPLAY_POWER: case ControlMessage.TYPE_SET_DISPLAY_POWER:
if (supportsInputEvents) { if (supportsInputEvents && displayId != Device.DISPLAY_ID_NONE) {
setDisplayPower(msg.getOn()); setDisplayPower(msg.getOn());
} }
break; break;
@ -351,47 +350,24 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
return successCount; return successCount;
} }
private Pair<Point, Integer> getEventPointAndDisplayId(Position position) { private boolean injectTouch(int action, long pointerId, Position position, float pressure, int actionButton, int buttons) {
long now = SystemClock.uptimeMillis();
// it hides the field on purpose, to read it with atomic access // it hides the field on purpose, to read it with atomic access
@SuppressWarnings("checkstyle:HiddenField") @SuppressWarnings("checkstyle:HiddenField")
DisplayData displayData = this.displayData.get(); DisplayData displayData = this.displayData.get();
// In scrcpy, displayData should never be null (a touch event can only be generated from the client when a video frame is present). assert displayData != null : "Cannot receive a touch event without a display";
// However, it is possible to send events without video playback when using scrcpy-server alone (except for virtual displays).
assert displayData != null || displayId != Device.DISPLAY_ID_NONE : "Cannot receive a touch event without a display";
Point point; Point point = displayData.positionMapper.map(position);
int targetDisplayId;
if (displayData != null) {
point = displayData.positionMapper.map(position);
if (point == null) { if (point == null) {
if (Ln.isEnabled(Ln.Level.VERBOSE)) { if (Ln.isEnabled(Ln.Level.VERBOSE)) {
Size eventSize = position.getScreenSize(); Size eventSize = position.getScreenSize();
Size currentSize = displayData.positionMapper.getVideoSize(); Size currentSize = displayData.positionMapper.getVideoSize();
Ln.v("Ignore event generated for size " + eventSize + " (current size is " + currentSize + ")"); Ln.v("Ignore touch event generated for size " + eventSize + " (current size is " + currentSize + ")");
} }
return null;
}
targetDisplayId = displayData.virtualDisplayId;
} else {
// No display, use the raw coordinates
point = position.getPoint();
targetDisplayId = displayId;
}
return Pair.create(point, targetDisplayId);
}
private boolean injectTouch(int action, long pointerId, Position position, float pressure, int actionButton, int buttons) {
long now = SystemClock.uptimeMillis();
Pair<Point, Integer> pair = getEventPointAndDisplayId(position);
if (pair == null) {
return false; return false;
} }
Point point = pair.first;
int targetDisplayId = pair.second;
int pointerIndex = pointersState.getPointerIndex(pointerId); int pointerIndex = pointersState.getPointerIndex(pointerId);
if (pointerIndex == -1) { if (pointerIndex == -1) {
Ln.w("Too many pointers for touch event"); Ln.w("Too many pointers for touch event");
@ -445,7 +421,7 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
// First button pressed: ACTION_DOWN // First button pressed: ACTION_DOWN
MotionEvent downEvent = MotionEvent.obtain(lastTouchDown, now, MotionEvent.ACTION_DOWN, pointerCount, pointerProperties, MotionEvent downEvent = MotionEvent.obtain(lastTouchDown, now, MotionEvent.ACTION_DOWN, pointerCount, pointerProperties,
pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source, 0); pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source, 0);
if (!Device.injectEvent(downEvent, targetDisplayId, Device.INJECT_MODE_ASYNC)) { if (!Device.injectEvent(downEvent, displayData.virtualDisplayId, Device.INJECT_MODE_ASYNC)) {
return false; return false;
} }
} }
@ -456,7 +432,7 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
if (!InputManager.setActionButton(pressEvent, actionButton)) { if (!InputManager.setActionButton(pressEvent, actionButton)) {
return false; return false;
} }
if (!Device.injectEvent(pressEvent, targetDisplayId, Device.INJECT_MODE_ASYNC)) { if (!Device.injectEvent(pressEvent, displayData.virtualDisplayId, Device.INJECT_MODE_ASYNC)) {
return false; return false;
} }
@ -470,7 +446,7 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
if (!InputManager.setActionButton(releaseEvent, actionButton)) { if (!InputManager.setActionButton(releaseEvent, actionButton)) {
return false; return false;
} }
if (!Device.injectEvent(releaseEvent, targetDisplayId, Device.INJECT_MODE_ASYNC)) { if (!Device.injectEvent(releaseEvent, displayData.virtualDisplayId, Device.INJECT_MODE_ASYNC)) {
return false; return false;
} }
@ -478,7 +454,7 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
// Last button released: ACTION_UP // Last button released: ACTION_UP
MotionEvent upEvent = MotionEvent.obtain(lastTouchDown, now, MotionEvent.ACTION_UP, pointerCount, pointerProperties, MotionEvent upEvent = MotionEvent.obtain(lastTouchDown, now, MotionEvent.ACTION_UP, pointerCount, pointerProperties,
pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source, 0); pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source, 0);
if (!Device.injectEvent(upEvent, targetDisplayId, Device.INJECT_MODE_ASYNC)) { if (!Device.injectEvent(upEvent, displayData.virtualDisplayId, Device.INJECT_MODE_ASYNC)) {
return false; return false;
} }
} }
@ -489,20 +465,27 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
MotionEvent event = MotionEvent.obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, MotionEvent event = MotionEvent.obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f,
DEFAULT_DEVICE_ID, 0, source, 0); DEFAULT_DEVICE_ID, 0, source, 0);
return Device.injectEvent(event, targetDisplayId, Device.INJECT_MODE_ASYNC); return Device.injectEvent(event, displayData.virtualDisplayId, Device.INJECT_MODE_ASYNC);
} }
private boolean injectScroll(Position position, float hScroll, float vScroll, int buttons) { private boolean injectScroll(Position position, float hScroll, float vScroll, int buttons) {
long now = SystemClock.uptimeMillis(); long now = SystemClock.uptimeMillis();
Pair<Point, Integer> pair = getEventPointAndDisplayId(position); // it hides the field on purpose, to read it with atomic access
if (pair == null) { @SuppressWarnings("checkstyle:HiddenField")
DisplayData displayData = this.displayData.get();
assert displayData != null : "Cannot receive a scroll event without a display";
Point point = displayData.positionMapper.map(position);
if (point == null) {
if (Ln.isEnabled(Ln.Level.VERBOSE)) {
Size eventSize = position.getScreenSize();
Size currentSize = displayData.positionMapper.getVideoSize();
Ln.v("Ignore scroll event generated for size " + eventSize + " (current size is " + currentSize + ")");
}
return false; return false;
} }
Point point = pair.first;
int targetDisplayId = pair.second;
MotionEvent.PointerProperties props = pointerProperties[0]; MotionEvent.PointerProperties props = pointerProperties[0];
props.id = 0; props.id = 0;
@ -514,7 +497,7 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
MotionEvent event = MotionEvent.obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, MotionEvent event = MotionEvent.obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, buttons, 1f, 1f,
DEFAULT_DEVICE_ID, 0, InputDevice.SOURCE_MOUSE, 0); DEFAULT_DEVICE_ID, 0, InputDevice.SOURCE_MOUSE, 0);
return Device.injectEvent(event, targetDisplayId, Device.INJECT_MODE_ASYNC); return Device.injectEvent(event, displayData.virtualDisplayId, Device.INJECT_MODE_ASYNC);
} }
/** /**
@ -708,12 +691,9 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
} }
private void setDisplayPower(boolean on) { private void setDisplayPower(boolean on) {
// Change the power of the main display when mirroring a virtual display boolean setDisplayPowerOk = Device.setDisplayPower(displayId, on);
int targetDisplayId = displayId != Device.DISPLAY_ID_NONE ? displayId : 0;
boolean setDisplayPowerOk = Device.setDisplayPower(targetDisplayId, on);
if (setDisplayPowerOk) { if (setDisplayPowerOk) {
// Do not keep display power off for virtual displays: MOD+p must wake up the physical device keepDisplayPowerOff = !on;
keepDisplayPowerOff = displayId != Device.DISPLAY_ID_NONE && !on;
Ln.i("Device display turned " + (on ? "on" : "off")); Ln.i("Device display turned " + (on ? "on" : "off"));
if (cleanUp != null) { if (cleanUp != null) {
boolean mustRestoreOnExit = !on; boolean mustRestoreOnExit = !on;

View File

@ -40,10 +40,6 @@ public final class Device {
public static final int INJECT_MODE_WAIT_FOR_RESULT = InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT; public static final int INJECT_MODE_WAIT_FOR_RESULT = InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT;
public static final int INJECT_MODE_WAIT_FOR_FINISH = InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH; public static final int INJECT_MODE_WAIT_FOR_FINISH = InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH;
// The new display power method introduced in Android 15 does not work as expected:
// <https://github.com/Genymobile/scrcpy/issues/5530>
private static final boolean USE_ANDROID_15_DISPLAY_POWER = false;
private Device() { private Device() {
// not instantiable // not instantiable
} }
@ -131,7 +127,7 @@ public final class Device {
public static boolean setDisplayPower(int displayId, boolean on) { public static boolean setDisplayPower(int displayId, boolean on) {
assert displayId != Device.DISPLAY_ID_NONE; assert displayId != Device.DISPLAY_ID_NONE;
if (USE_ANDROID_15_DISPLAY_POWER && Build.VERSION.SDK_INT >= AndroidVersions.API_35_ANDROID_15) { if (Build.VERSION.SDK_INT >= AndroidVersions.API_35_ANDROID_15) {
return ServiceManager.getDisplayManager().requestDisplayPower(displayId, on); return ServiceManager.getDisplayManager().requestDisplayPower(displayId, on);
} }

View File

@ -192,9 +192,6 @@ public final class DisplayManager {
if ("onDisplayChanged".equals(method.getName())) { if ("onDisplayChanged".equals(method.getName())) {
listener.onDisplayChanged((int) args[0]); listener.onDisplayChanged((int) args[0]);
} }
if ("toString".equals(method.getName())) {
return "DisplayListener";
}
return null; return null;
}); });
try { try {