Dieser Artikel ist eine Fortsetzung von Erstellen von Software, die Android-Bildschirme auf einen PC 1 spiegelt. Dort wird erklärt, wie Sie eine Funktion zum Spiegeln des Android-Bildschirms erstellen.
Ermöglicht die Bedienung Ihres Android-Geräts mit der Maus.
Ich hätte gerne nur eine Verbindung zwischen Android und PC, aber ich habe Kompromisse geschlossen, weil das Programm kompliziert zu sein schien.
Zeigt die von FFmpeg fließenden Daten an. Außerdem wird die Mausbedienung in die Berührungsbedienung des Bildschirms umgewandelt und auf die Terminalseite geworfen.
Der Bildschirminhalt wird nach wie vor verschlüsselt und an die PC-Seite gesendet. Es empfängt auch Berührungsereignisse von der PC-Seite und greift in das System ein.
In diesem Abschnitt wird beschrieben, wie Android-Geräte mit Berührungen und Tastenanschlägen umgehen. Sie können Echtzeit-Touch implementieren, ohne es zu wissen. Wenn Sie nicht interessiert sind, fahren Sie mit "Implementierung" fort.
Wie kann ich Android von einem PC aus bedienen? Beginnen wir mit einem einfachen Beispiel.
Android ist ein Linux-basiertes Betriebssystem. Obwohl es sich um eine limitierte Edition handelt, können Sie das Linux-Terminal verwenden.
adb shell
Sie können den Dialog aufrufen, indem Sie dies tun. Dann gibt es einen Befehl namens ** Eingabe **, der Berührungsoperationen und Tastenoperationen sendet. Verwenden Sie diesen Befehl. Ein Beispiel ist unten angegeben.
input touchscreen tap x y
input touchscreen swipe x1 y1 x2 y2
input keyevent Key
input text Text
Durch Ausführen dieser können Sie einfach ein Ereignis an das Terminal senden. Aber wenn ich es starte, dauert es ** Zeit, bis das Ereignis ausgelöst wird **. Dies ist nicht für den Echtzeitbetrieb geeignet. Was ist auch, wenn Sie andere komplizierte Vorgänge als Tippen und Wischen ausführen möchten?
Der Befehl ** getevent ** ist ein Befehl, der die vom Touchpanel oder der physischen Taste des Terminals gesendeten Daten ausgibt. Versuchen Sie, das Touchpanel zu bedienen, nachdem Sie den folgenden Befehl ausgeführt haben.
getevent
Anschließend werden die numerischen Werte in einer Reihe angezeigt (siehe Abbildung unten). Dies sind die vom Touchpanel gesendeten Daten. Das Betriebssystem interpretiert und reflektiert diese Daten. Wie es tatsächlich interpretiert wird, wird in [Android] Berühren des Terminals aus dem Programm [ADB] erklärt. Guck dir das mal bitte an.
** sendevent ** ist ein Befehl, der beliebige Daten senden kann, als ob sie von einem Touchpanel gesendet würden. Mit anderen Worten, die Berührungsoperation kann reproduziert werden, indem die von ** getevent ** erhaltenen Daten erneut an ** sendevent ** gesendet werden. Dieser Befehl ist jedoch auch langsam auszuführen und kann nicht vollständig reproduziert werden.
Da Android auf Linux basiert, werden Gerätedateien verwendet. Es ist möglicherweise nicht bekannt für diejenigen, die Nicht-Unix-Betriebssysteme wie Windows verwenden. Eine Gerätedatei ist eine spezielle Datei, die zur Interaktion mit verschiedenen verbundenen Geräten verwendet wird. Wenn Sie beispielsweise die Gerätedatei des Touchpanels öffnen, können Sie die von getevent erhaltenen Daten zum Betrieb des Touchpanels lesen. Wenn Sie dagegen in eine Gerätedatei schreiben, werden die geschriebenen Daten so behandelt, als stammten sie von diesem Gerät, ähnlich wie bei sendevent. Gerätedatei [Gerätespezialdatei](https://uc2.h2np.net/index.php/%E3%83%87%E3%83%90%E3%82%A4%E3%82%B9%E3%82%B9 % E3% 83% 9A% E3% 82% B7% E3% 83% A3% E3% 83% AB% E3% 83% 95% E3% 82% A1% E3% 82% A4% E3% 83% AB) Ich denke, dieser Bereich wird hilfreich sein.
Lassen Sie uns die Gerätedatei tatsächlich öffnen. Ich denke, Sie können den Pfad / dev / input / event4 im Bild oben sehen. Dies ist der Speicherort der Gerätedatei. Im Verzeichnis / dev / input befinden sich mehrere Gerätedateien wie event0, event1 ...., die Touchpanels, physischen Tasten, Sensoren usw. entsprechen. ** Bitte beachten Sie, dass die entsprechende Nummer je nach Terminal unterschiedlich ist. ** ** **
cat /dev/input/event●
Wenn Sie diesen Befehl ausführen, fließen Daten von diesem Gerät. Es wird jedoch so.
Die aus der Gerätedatei erhaltenen Rohdaten sind Binärdaten, die nicht in Zeichen angezeigt werden. Das zu Beginn eingeführte getEvent, sendEvent und der Eingabebefehl konvertieren zwischen Binär und Zeichen (numerische Werte).
Apropos,
cat /dev/input/event● > /sdcard/event.bin
Auf diese Weise können Sie die Rohdaten in einer Datei speichern.
cat /sdcard/event.bin > /dev/input/event●
Dann können Sie die Berührungsdaten reproduzieren, aber auch hier gibt es eine Falle. Es läuft zu schnell (bitteres Lächeln). Ereignisdaten, die in wenigen Sekunden aufgezeichnet wurden, fließen sofort ein. Ich denke, es ist zu früh, um ordnungsgemäß zu funktionieren. Um die Operation zu reproduzieren, muss der Ruhezustand eingefügt werden, damit die Daten zu einem geeigneten Zeitpunkt gesendet werden können.
Der Punkt ist, dass das von der PC-Seite gesendete Berührungsereignis an die Gerätedatei gesendet werden kann. Sie können es möglicherweise mit einem Shell-Skript schreiben, aber da wir mit Android Studio entwickeln, Es ist einfach, in Java / Kotlin zu schreiben. Früher habe ich auf Gerätedateien zugegriffen, wie ich möchte, aber es ist eine Technik, die nur mit Shell-Berechtigungen erreicht werden kann. Mit anderen Worten, normale Apps haben keine Berechtigung, mit Systemdateien herumzuspielen. (Es sei denn, es ist verwurzelt) Ich wollte gerade aufgeben, aber ich fand diese Frage. How does vysor create touch events on a non rooted device? Wie Vysor ein Echtzeit-Touch-Ereignis auf einem nicht gerooteten Gerät ist Rennst du? Ist die Frage. Und die Antwort ist
What he does is, he then starts his Main class as a separate process using this shell user. Now, the Java code inside that Main class has the same privileges as the shell user (because duh, it's linux).
Er verwendet die Berechtigungen des Shell-Benutzers, um die Hauptklasse in einem separaten Prozess zu starten. Jetzt hat der Java-Code in der Hauptklasse die gleichen Berechtigungen wie der Shell-Benutzer (da Android Linux ist).
Mit anderen Worten, wenn Sie die im apk-Paket enthaltene Klasse über die Shell ausführen, kann das gestartete Programm auch das Shell-Privileg verwenden. Kein Wunder, aber ich habe nicht daran gedacht. Dieses Mal werde ich mit dieser Methode Echtzeit-Touch implementieren.
Wie oben erwähnt, ist die Leistung der Shell erforderlich, um von der Anwendung aus in den Systembereich einzugreifen. Gehen Sie zunächst wie folgt vor, um die im apk-Paket enthaltene Klasse mit Shell-Berechtigungen zu starten.
sh -c "CLASSPATH=[Pfad zur APK-Datei] /system/bin/app_process /system/bin [Paketnamen].[Der Name der Klasse, die die Hauptmethode enthält]"
Der Pfad zur apk-Datei lautet
pm path [Paketnamen]
Sie können es mit bekommen, aber wenn Sie normalerweise mit Android Studio hier debuggen, werden mehrere Pfade angezeigt. Dies ist ein Phänomen, das bei Verwendung von Instant Run auftritt, einer Funktion, die die Erstellungszeit verkürzt und Programmänderungen in Echtzeit widerspiegelt. Durch die Aufteilung von apk in mehrere Teile scheint es, dass zum Zeitpunkt der Änderung nur der Unterschied erstellt und installiert wird, um Zeit zu sparen. Wenn es jedoch aufgeteilt wird, funktioniert der obige Befehl nicht ordnungsgemäß, sodass Instant Run deaktiviert werden muss. Der Ort der Einstellung ist wie folgt.
Noch wichtiger ist, dass die vom Benutzer selbst gestartete App und das mit Shell-Berechtigungen gestartete Programm zum selben Paket gehören. Da die Prozesse jedoch unterschiedlich sind, kann die statische Freigabe überhaupt nicht durchgeführt werden. Wenn Sie also kommunizieren möchten, müssen Sie dies über eine Steckdose usw. tun.
Schauen Sie sich zuerst den folgenden Code an.
InputService.java
public class InputService {
InputManager im;
Method injectInputEventMethod;
public InputService() throws Exception {
//Holen Sie sich eine Instanz von InputManager
im = (InputManager) InputManager.class.getDeclaredMethod("getInstance").invoke(null, new Object[0]);
//Ermöglicht das Aufrufen statischer Methoden, die MotionEvent generieren
MotionEvent.class.getDeclaredMethod(
"obtain",
long.class, long.class, int.class, int.class,
MotionEvent.PointerProperties[].class, MotionEvent.PointerCoords[].class,
int.class, int.class, float.class, float.class, int.class, int.class, int.class, int.class
).setAccessible(true);
//Holen Sie sich eine Methode, um ein Ereignis in das System einzugreifen
injectInputEventMethod = InputManager.class.getDeclaredMethod("injectInputEvent", new Class[]{InputEvent.class, int.class});
}
//Touch-Ereignis generieren
public void injectMotionEvent(int inputSource, int action, float x, float y) throws InvocationTargetException, IllegalAccessException {
MotionEvent.PointerProperties[] pointerProperties = new MotionEvent.PointerProperties[1];
pointerProperties[0] = new MotionEvent.PointerProperties();
pointerProperties[0].id = 0;
MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[1];
pointerCoords[0] = new MotionEvent.PointerCoords();
pointerCoords[0].pressure = 1;
pointerCoords[0].size = 1;
pointerCoords[0].touchMajor = 1;
pointerCoords[0].touchMinor = 1;
pointerCoords[0].x = x;
pointerCoords[0].y = y;
MotionEvent event = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), action, 1, pointerProperties, pointerCoords, 0, 0, 1, 1, 0, 0, inputSource, 0);
injectInputEventMethod.invoke(im, new Object[]{event, 0});
}
//Schlüsselereignis generieren
public void injectKeyEvent(KeyEvent event)throws InvocationTargetException, IllegalAccessException{
injectInputEventMethod.invoke(im, new Object[]{event, 0});
}
}
Dieser Code basiert auf hier und verwendet die neue API. Ich habe es geändert, um es zu verwenden.
Wir verwenden Shell-Berechtigungen, um auf Systemmethoden und Instanzen zuzugreifen, auf die normalerweise durch Reflektion nicht zugegriffen werden kann. Dies wird als "injizierenInputEvent" bezeichnet. Wenn Sie Ereignisdaten an diese Methode übergeben, wird das Ereignis ausgeführt. Ich habe in der AOSP-Quelle nachgesehen, wo sich die Methode tatsächlich befindet, und festgestellt, dass hier Es war in Zeile 914 von /hardware/input/InputManager.java). Auf die Annotation "Ausblenden" kann normalerweise nicht zugegriffen werden. Betrachtung? Für diejenigen, die sagen, kann dieser Artikel hilfreich sein.
Führen Sie das vom Personal Computer gesendete Ereignis mit der obigen InputService-Klasse aus.
InputHost.java
public class InputHost {
static InputService inputService;
static ServerSocket listener;//Server-Socket
static Socket clientSocket;//Buchse zur Client-Seite
static InputStream inputStream;//Zum Empfangen von Nachrichten von Clients
static OutputStream outputStream;//Stream zum Senden von Daten an den Client
static boolean runnning = false;
public static void main(String args[]) {
try {
inputService = new InputService();
} catch (Exception ex) {
ex.printStackTrace();
}
try {
listener = new ServerSocket();
listener.setReuseAddress(true);
listener.bind(new InetSocketAddress(8081));
System.out.println("Server listening on port 8081...");
clientSocket = listener.accept();//Warten Sie bis die Verbindung hergestellt ist
System.out.println("Connected");
inputStream = clientSocket.getInputStream();
outputStream = clientSocket.getOutputStream();
runnning = true;
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
while (runnning) {
String msg = reader.readLine();
String[] data = msg.split(" ");
if (data.length > 0) {
if (data[0].equals("screen")) {//Für Touch-Daten
inputService.injectMotionEvent(InputDeviceCompat.SOURCE_TOUCHSCREEN, Integer.valueOf(data[1]), Integer.valueOf(data[2]), Integer.valueOf(data[3]));
} else if (data[0].equals("key")) {//Für Schlüssel
inputService.injectKeyEvent(new KeyEvent(Integer.valueOf(data[1]), Integer.valueOf(data[2])));
} else if (data[0].equals("exit")) {//Anruf beenden
Disconnect();
}
}
}
} catch (Exception e) {
e.printStackTrace();
Disconnect();
}
}
//Schneidvorgang
private static void Disconnect() {
runnning = false;
try {
listener.close();
if (clientSocket != null)
clientSocket.close();
} catch (Exception ex) {
ex.printStackTrace();
}
System.out.println("Disconnected");
}
}
Es ist ein einfaches Serverprogramm. Ich dachte auch darüber nach, den Datenaustausch mit dem PC mithilfe von json zu modernisieren. Es ist keine große Sache, deshalb habe ich beschlossen, es in einem Format wie CSV zu senden, das durch Leerzeichen getrennt ist.
Dies ist die einzige Implementierung auf der Android-Seite. ** Der gesamte Code ist hier **
Implementieren Sie als Nächstes die PC-Seite.
Ich möchte es mit C # & WPF erstellen. Warum hast du dich nicht für WinForms entschieden? Dies liegt daran, dass es schwierig war, das Bild mit 60 FPS anzuzeigen. Als ich es implementierte, waren es ungefähr 30 FPS. Wenn DoubleBuffered deaktiviert war, wurden es 60 FPS, aber das Flimmern war so groß, dass es nicht praktikabel war.
WPF fühlt sich ebenfalls subtil an, ist aber besser als WinForms, da es GPU zum Zeichnen verwendet. Wenn Sie es ernsthaft tun, verwenden Sie eine Grafik-API wie OpenGL (OpenTK für C #) ... Ich hätte nie gedacht, dass die empfangende Seite eher der Engpass als die sendende Seite ist ^^;
** Klicken Sie hier für den gesamten Client-Code (https://github.com/SIY1121/ScreenCastClient) **
UI
MainWindow.xaml
<Window x:Class="ScreenCastClient.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ScreenCastClient"
mc:Ignorable="d"
Title="ScreenCastClient" Height="819.649" Width="420.611" Loaded="Window_Loaded" Closing="Window_Closing">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="60"/>
</Grid.RowDefinitions>
<Image x:Name="image" MouseDown="image_MouseDown" MouseUp="image_MouseUp" MouseMove="image_MouseMove"/>
<Grid Grid.Row="1" Background="#FF008BFF">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="204*"/>
<ColumnDefinition Width="193*"/>
</Grid.ColumnDefinitions>
<Polygon Points="0,15 25,0 25,30" Fill="White" Margin="30,17,0,0" HorizontalAlignment="Left" Width="36" MouseDown="Polygon_MouseDown" MouseUp="Polygon_MouseUp" />
<Ellipse Fill="White" Margin="186,18,181,12" Width="30" HorizontalAlignment="Center" MouseDown="Ellipse_MouseDown" MouseUp="Ellipse_MouseUp" Grid.ColumnSpan="2"/>
<Rectangle Fill="White" Margin="0,17,30,10" HorizontalAlignment="Right" Width="30" MouseDown="Rectangle_MouseDown" MouseUp="Rectangle_MouseUp" Grid.Column="1"/>
</Grid>
</Grid>
</Window>
Hier finden Sie eine Zusammenfassung der Funktionen, die in Zukunft häufig verwendet werden. Reguläre Ausdrücke sind sehr nützlich, wenn Sie nur die erforderlichen Zahlen aus einigen Daten extrahieren möchten. Darüber hinaus zur Überprüfung Online-Regex-Tester und Debugger: PHP, PCRE, Python, Golang und JavaScript Diese Seiten sind sehr nützlich.
C#:MainWindow.xaml.cs
//Führen Sie einfach den Befehl aus und geben Sie die Standardausgabe zurück
private string Exec(string str)
{
Process process = new Process
{
StartInfo =
{
FileName = "cmd",
Arguments = @"/c " + str,
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardInput = true,
RedirectStandardError = true,
RedirectStandardOutput = true
},
EnableRaisingEvents = true
};
process.Start();
string results = process.StandardOutput.ReadToEnd();
process.WaitForExit();
process.Close();
return results;
}
//Gibt ein Datenarray zurück, das mit einem regulären Ausdruck übereinstimmt
private string[] GetRegexResult(string src, string pattern)
{
Regex regex = new Regex(pattern);
Match match = regex.Match(src);
string[] res = new string[match.Groups.Count - 1];
for (int i = 1; i < match.Groups.Count; i++)
res[i - 1] = match.Groups[i].Value;
return res;
}
Anwendungen jedes Betriebssystems verfügen über Standard-Eingabe- und Ausgabefunktionen.
In der allgemeinen Videokonvertierungssoftware werden Eingabe und Ausgabe von Dateien ausgeführt. Da FFmpeg jedoch die Standardeingabe und -ausgabe verwenden kann, können Sie die aus dem Programm decodierten Daten lesen, indem Sie das Ausgabeziel auf stdout setzen. Im Fall von FFmpeg wird außerdem angegeben, dass das Protokoll von stderr ausgegeben wird. Das folgende Programm startet FFmpeg und stellt eine Verbindung mit stdout und stderr her.
C#:MainWindow.xaml.cs
private void StartFFmpeg()
{
//Porteinstellungen
Exec("adb forward tcp:8080 tcp:8080");
var inputArgs = "-framerate 60 -analyzeduration 100 -i tcp://127.0.0.1:8080";
var outputArgs = "-f rawvideo -pix_fmt bgr24 -r 60 -flags +global_header - ";
Process process = new Process
{
StartInfo =
{
FileName = "ffmpeg.exe",
Arguments = $"{inputArgs} {outputArgs}",
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardInput = true,
RedirectStandardError = true,//Stderr lesbar machen
RedirectStandardOutput=true//Machen Sie stdout lesbar
},
EnableRaisingEvents = true
};
process.ErrorDataReceived += Process_ErrorDataReceived;//Die Protokolle werden von stderr übertragen. Verarbeiten Sie sie daher separat.
process.Start();
rawStream = process.StandardOutput.BaseStream;//Daten fließen von stdout, also holen Sie sich den Stream
process.BeginErrorReadLine();
running = true;
Task.Run(() =>
{
//Beginnen Sie in einem anderen Thread zu lesen
ReadRawData();
});
}
Stellen Sie die erforderlichen Argumente ein und starten Sie FFmpeg. Es gibt zwei Möglichkeiten, Daten aus der Ausgabe abzurufen: Die erste besteht darin, sich für ein Ereignis zu registrieren, und die zweite darin, einen Stream abzurufen und selbst zu lesen. Ersteres kann leicht erhalten werden, kann jedoch nicht zum Austausch von Binärdaten verwendet werden, da es in Zeichendaten konvertiert wird. Letzteres verarbeitet Streams, ist also etwas umständlich, aber eine Feinsteuerung ist möglich. Dieses Mal, da das Protokoll von stderr fließt, wird das erstere gelesen, und da die Binärdaten des Bildes von stdout fließen, werden die Daten durch das letztere Verfahren gelesen.
C#:MainWindow.xaml.cs
//Lesen Sie die Standardfehlerausgabe von FFmpeg
private void Process_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
if (e.Data == null) return;
Console.WriteLine(e.Data);
if (imageWidth == 0 && imageHeight == 0)//Wenn die zu sendende Größe noch nicht bestätigt ist
{
//Grobe Arbeit beim Extrahieren der Größe aus der FFmpeg-Ausgabe
string[] res = GetRegexResult(e.Data, @"([0-9]*?)x([0-9]*?), [0-9]*? fps");
if (res.Length == 2)
{
imageWidth = int.Parse(res[0]);
imageHeight = int.Parse(res[1]);
bytePerframe = imageWidth * imageHeight * 3;
if(imageWidth>imageHeight)//Für Querformat
{
//Tauschen Sie die Maximal- und Minimalwerte der Berührungskoordinaten aus
int tmp = displayWidth;
displayWidth = displayHeight;
displayHeight = tmp;
}
Dispatcher.Invoke(() => {//Wenn Sie im UI-Thread keine Bitmap erstellen, kann diese nicht in der UI wiedergegeben werden
writeableBitmap = new WriteableBitmap(imageWidth, imageHeight, 96, 96, PixelFormats.Bgr24, null);
image.Source = writeableBitmap;
});
}
}
}
Die Größe des Bildes ist wichtig, um die in Zukunft gesendeten Rohdaten wiederherzustellen. Wenn FFmpeg mit der Konvertierung beginnt, gibt es die Informationen des Streams aus, der in das Protokoll ausgegeben werden soll, sodass die Größe des Bilds grob extrahiert werden kann. Eine Variable namens bytePerFrame ist die Anzahl der Bytes, die zum Generieren eines Einzelbilds erforderlich sind. Sie kann anhand der Anzahl der Bytes berechnet werden, die für vertikal x horizontal x 1 Pixel des Bildes verwendet werden. Dieses Mal ist FFmpeg so eingestellt, dass es mit rgb24 ausgegeben wird (8 Bit für jedes RGB sind 24 Bit), sodass die Anzahl der für 1 Pixel verwendeten Bytes 3 beträgt. Wenn Sie sich Sorgen über den Mechanismus von Bildern machen, wissen Sie nicht? Grundkenntnisse in Bild und Dateistruktur. .
C#:MainWindow.xaml.cs
//Lesen Sie rawStream von FFmpeg und schreiben Sie in Bitmap
private void ReadRawData()
{
MemoryStream ms = new MemoryStream();
byte[] buf = new byte[10240];
while (running)
{
int resSize = rawStream.Read(buf, 0, buf.Length);
if (ms.Length + resSize >= bytePerframe)//Wenn die gelesenen Daten dieses Mal die Daten für einen Frame erreichen oder überschreiten
{
int needSize = bytePerframe - (int)ms.Length;//Die Größe der verbleibenden Daten, die für einen Frame erforderlich sind
int remainSize = (int)ms.Length + resSize - bytePerframe;//Größe der überschüssigen Daten
ms.Write(buf, 0, bytePerframe - (int)ms.Length);//Lesen Sie den Rest der benötigten Daten in einem Frame
Dispatcher.Invoke(() =>
{
if (writeableBitmap != null)//Daten schreiben
writeableBitmap.WritePixels(new Int32Rect(0, 0, imageWidth, imageHeight), ms.ToArray(), 3 * imageWidth, 0);
});
ms.Close();
ms = new MemoryStream();
ms.Write(buf, needSize + 1, remainSize);//Schreiben Sie überschüssige Daten
}
else
{
ms.Write(buf, 0, resSize);//Daten akkumulieren
}
}
}
Daten werden aus dem Stream erfasst und in MemoryStream gesammelt. Wenn ein Datenrahmen gesichert ist, werden die Daten in MemoryStream in einem Image wiederhergestellt. WritableBitmap verfügt über eine Methode, die aus einem Array wiederhergestellt wird. Verwenden Sie diese Methode. Der Zugriff auf WritableBitmap muss auch im UI-Thread erfolgen.
C#:MainWindows.xaml.cs
//Starten Sie InputHost und verbinden Sie sich
private void StartInputHost()
{
string inputInfo = Exec("adb shell getevent -i");//Erhalten Sie Daten zur Eingabe des Android-Terminals
//Extrahieren Sie den Maximalwert der Berührungskoordinaten von innen
string[] tmp = GetRegexResult(inputInfo, @"ABS[\s\S]*?35.*?max (.*?),[\s\S]*?max (.*?),");
displayWidth = int.Parse(tmp[0]);
displayHeight = int.Parse(tmp[1]);
//Porteinstellungen
Exec("adb forward tcp:8081 tcp:8081");
//Holen Sie sich den App-Pfad
//Entfernen Sie zusätzliche Zeichen und Zeilenvorschubcode
string pathToPackage = Exec("adb shell pm path space.siy.screencastsample").Replace("package:", "").Replace("\r\n", "");
Process process = new Process
{
StartInfo =
{
FileName = "adb",
Arguments = $"shell",
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardInput = true,
RedirectStandardError = true,
RedirectStandardOutput = true
},
EnableRaisingEvents = true
};
process.Start();
process.OutputDataReceived += (s, e) =>
{
Console.WriteLine(e.Data);//Ich kann in Zukunft etwas tun
};
process.BeginOutputReadLine();
//Starten Sie InputHost mit Shell-Berechtigungen
process.StandardInput.WriteLine($"sh -c \"CLASSPATH={pathToPackage} /system/bin/app_process /system/bin space.siy.screencastsample.InputHost\"");
System.Threading.Thread.Sleep(1000);//Warten Sie, bis es beginnt
TcpClient tcp = new TcpClient("127.0.0.1", 8081);//Stellen Sie eine Verbindung zu InputHost her
streamToInputHost = tcp.GetStream();
}
Zuerst starte ich adb shell getevent -i und lese die Ausgabe, Dieser Befehl zeigt Daten zum Android-Eingabegerät an. Hier wird der Maximalwert der Koordinaten erfasst, die das Touchpanel annehmen kann. Und wir starten InputHost. Bitte beachten Sie, dass wir schwierige Dinge tun, z. B. 1 Sekunde warten, bis es beginnt.
Nachdem die Vorbereitungen für die Verbindung abgeschlossen sind, müssen wir nur noch die Daten an den InputHost senden.
C#:MainWindow.xaml.cs
private void image_MouseDown(object sender, MouseButtonEventArgs e)
{
Point p = GetDisplayPosition(e.GetPosition(image));
byte[] sendByte = Encoding.UTF8.GetBytes($"screen 0 {p.X} {p.Y}\n");
streamToInputHost.Write(sendByte, 0, sendByte.Length);
mouseDown = true;
}
private void image_MouseMove(object sender, MouseEventArgs e)
{
if (mouseDown)
{
Point p = GetDisplayPosition(e.GetPosition(image));
byte[] sendByte = Encoding.UTF8.GetBytes($"screen 2 {p.X} {p.Y}\n");
streamToInputHost.Write(sendByte, 0, sendByte.Length);
}
}
private void image_MouseUp(object sender, MouseButtonEventArgs e)
{
Point p = GetDisplayPosition(e.GetPosition(image));
byte[] sendByte = Encoding.UTF8.GetBytes($"screen 1 {p.X} {p.Y}\n");
streamToInputHost.Write(sendByte, 0, sendByte.Length);
mouseDown = false;
}
//Konvertieren Sie die Mausposition in Terminal-Touch-Koordinaten
private Point GetDisplayPosition(Point p)
{
int x = (int)(p.X / image.ActualWidth * displayWidth);
int y = (int)(p.Y / image.ActualHeight * displayHeight);
return new Point(x, y);
}
Ich sende Daten mit einem Ereignis über die Maus im Bild. Die zweite Zahl 0,1,2 im Leerzeichen bedeutet 0 für Donw, 1 für Up und 2 für Move. Darüber hinaus lautet das Schlüsselereignis wie folgt.
C#:MainWindow.xaml.cs
private void Polygon_MouseDown(object sender, MouseButtonEventArgs e)
{
byte[] sendByte = Encoding.UTF8.GetBytes($"key 0 4\n");
streamToInputHost.Write(sendByte, 0, sendByte.Length);
}
private void Polygon_MouseUp(object sender, MouseButtonEventArgs e)
{
byte[] sendByte = Encoding.UTF8.GetBytes($"key 1 4\n");
streamToInputHost.Write(sendByte, 0, sendByte.Length);
}
private void Ellipse_MouseDown(object sender, MouseButtonEventArgs e)
{
byte[] sendByte = Encoding.UTF8.GetBytes($"key 0 3\n");
streamToInputHost.Write(sendByte, 0, sendByte.Length);
}
private void Ellipse_MouseUp(object sender, MouseButtonEventArgs e)
{
byte[] sendByte = Encoding.UTF8.GetBytes($"key 1 3\n");
streamToInputHost.Write(sendByte, 0, sendByte.Length);
}
private void Rectangle_MouseDown(object sender, MouseButtonEventArgs e)
{
byte[] sendByte = Encoding.UTF8.GetBytes($"key 0 187\n");
streamToInputHost.Write(sendByte, 0, sendByte.Length);
}
private void Rectangle_MouseUp(object sender, MouseButtonEventArgs e)
{
byte[] sendByte = Encoding.UTF8.GetBytes($"key 1 187\n");
streamToInputHost.Write(sendByte, 0, sendByte.Length);
}
Das zweite Leerzeichen hat die gleiche Bedeutung wie oben. Die dritte ist die eindeutige Nummer des Schlüssels, die unter KeyEvent bestätigt werden kann. Sie können auch Schlüssel senden, die nicht auf Ihrem Gerät implementiert sind. Beispielsweise wird 120 eine PrintScreen-Taste zugewiesen. Der Screenshot wird normalerweise durch gleichzeitiges Drücken der Ein- / Aus-Taste und der Lautstärke erstellt. Sie können einen Screenshot machen, indem Sie diesen Schlüssel senden. Wenn Sie es sofort versuchen möchten
adb shell input keyevent 120
Kann mit reproduziert werden.
Portweiterleitung, Starten von InputHost über die Shell usw. Alle lästigen Dinge sind in der Client-Software implementiert, so dass es einfach ist.
Nur das. Der Bildschirm wird jetzt projiziert und Sie können den Bildschirm mit der Maus bedienen.
Ich war zuerst verwirrt, weil das Ausführen der Klasse in apk mit Shell keine normale Anwendungsentwicklung ist, aber ich konnte sie implementieren. Da adb für diese Funktion unverzichtbar geworden ist, muss adb installiert werden, wenn es an normale Benutzer verteilt wird. Vor allem, da jetzt nur noch adb heruntergeladen werden kann, wurde der Schwellenwert für die Einführung gesenkt. (Ist es enthalten?)
Jetzt bin ich Vysor ziemlich nahe. Ich konnte jedoch noch keine Zeichen eingeben oder Dateien übertragen, daher möchte ich dies als Nächstes implementieren. Dann danke, dass du bis zum Ende zugesehen hast.
Recommended Posts