mirror of
https://github.com/KDE/kdeconnect-android
synced 2025-08-29 13:17:43 +00:00
virtual-monitor: Make it possible for the device to act as an rdp host
This commit is contained in:
parent
a733433551
commit
c3e51d13fe
@ -393,6 +393,8 @@ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted
|
||||
<string name="pref_plugin_findremotedevice">Find remote device</string>
|
||||
<string name="pref_plugin_findremotedevice_desc">Ring your remote device</string>
|
||||
<string name="ring">Ring</string>
|
||||
<string name="pref_plugin_virtualmonitor">Virtual Monitor</string>
|
||||
<string name="pref_plugin_virtualmonitor_desc">Extend your devices monitors</string>
|
||||
|
||||
<string name="pref_plugin_systemvolume">System volume</string>
|
||||
<string name="pref_plugin_systemvolume_desc">Control the system volume of the remote device</string>
|
||||
|
@ -16,6 +16,8 @@ import org.kde.kdeconnect.DeviceInfo;
|
||||
import org.kde.kdeconnect.NetworkPacket;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.ArrayList;
|
||||
|
||||
|
||||
@ -43,6 +45,8 @@ public abstract class BaseLink {
|
||||
return getDeviceInfo().id;
|
||||
}
|
||||
|
||||
public InetAddress getDeviceIp() { return null; }
|
||||
|
||||
public BaseLinkProvider getLinkProvider() {
|
||||
return linkProvider;
|
||||
}
|
||||
|
@ -27,9 +27,11 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.nio.channels.NotYetConnectedException;
|
||||
|
||||
@ -256,4 +258,7 @@ public class LanLink extends BaseLink {
|
||||
packetReceived(np);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InetAddress getDeviceIp() { return socket.getInetAddress(); }
|
||||
|
||||
}
|
||||
|
@ -46,6 +46,8 @@ import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.ConcurrentMap
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import androidx.core.content.edit
|
||||
import java.net.InetAddress
|
||||
import java.net.SocketAddress
|
||||
|
||||
class Device : PacketReceiver {
|
||||
|
||||
@ -647,6 +649,15 @@ class Device : PacketReceiver {
|
||||
|
||||
fun removePluginsChangedListener(listener: PluginsChangedListener) = pluginsChangedListeners.remove(listener)
|
||||
|
||||
fun ipAddress(): InetAddress? {
|
||||
for (link in links) {
|
||||
if (link.deviceIp != null) {
|
||||
return link.deviceIp
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun disconnect() {
|
||||
links.forEach(BaseLink::disconnect)
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ object NetworkHelper {
|
||||
//
|
||||
// If we run across an interface that has this, we can safely
|
||||
// ignore it. In fact, it's much safer to do. If we don't, we
|
||||
// might get invalid IP adddresses out of it.
|
||||
// might get invalid IP addresses out of it.
|
||||
@JvmStatic
|
||||
val localIpAddress: InetAddress?
|
||||
get() {
|
||||
@ -34,7 +34,7 @@ object NetworkHelper {
|
||||
//
|
||||
// If we run across an interface that has this, we can safely
|
||||
// ignore it. In fact, it's much safer to do. If we don't, we
|
||||
// might get invalid IP adddresses out of it.
|
||||
// might get invalid IP addresses out of it.
|
||||
if (intf.displayName.contains("rmnet")) {
|
||||
continue
|
||||
}
|
||||
|
@ -0,0 +1,114 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Aleix Pol i Gonzalez <aleixpol@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
*/
|
||||
package org.kde.kdeconnect.Plugins.VirtualMonitorPlugin
|
||||
|
||||
import android.content.Intent
|
||||
import android.graphics.Rect
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import android.view.WindowManager
|
||||
import android.view.WindowMetrics
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.net.toUri
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import org.kde.kdeconnect.NetworkPacket
|
||||
import org.kde.kdeconnect.Plugins.Plugin
|
||||
import org.kde.kdeconnect.Plugins.PluginFactory.LoadablePlugin
|
||||
import org.kde.kdeconnect_tp.R
|
||||
|
||||
const val PACKET_TYPE_VIRTUALMONITOR: String = "kdeconnect.virtualmonitor"
|
||||
const val PACKET_TYPE_VIRTUALMONITOR_REQUEST: String = "kdeconnect.virtualmonitor.request"
|
||||
|
||||
@LoadablePlugin
|
||||
class VirtualMonitorPlugin : Plugin() {
|
||||
|
||||
override val displayName: String
|
||||
get() = context.resources.getString(R.string.pref_plugin_virtualmonitor)
|
||||
|
||||
override val description: String
|
||||
get() = context.resources.getString(R.string.pref_plugin_virtualmonitor_desc)
|
||||
|
||||
private fun openUrlExternally(url: Uri): Boolean {
|
||||
val intent = Intent(Intent.ACTION_VIEW, url)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
if (intent.resolveActivity(context.packageManager) != null) {
|
||||
context.startActivity(intent)
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPacketReceived(np: NetworkPacket): Boolean {
|
||||
if (np.type == PACKET_TYPE_VIRTUALMONITOR_REQUEST) {
|
||||
// At least a password is necessary, we have defaults for all other parameters
|
||||
if (!np.has("password")) {
|
||||
Log.e("KDE/VirtualMonitor", "Request invalid, missing password")
|
||||
return false
|
||||
}
|
||||
val addr = device.ipAddress()?.hostAddress
|
||||
if (addr == null) {
|
||||
Log.e("KDE/VirtualMonitor", "Request invalid, no address")
|
||||
return false
|
||||
}
|
||||
val protocol = np.getString("protocol")
|
||||
val username = np.getString("username")
|
||||
val password = np.getString("password")
|
||||
val port = np.getInt("port", -1)
|
||||
|
||||
val url = "$protocol://$username:$password@$addr:$port".toUri()
|
||||
|
||||
Log.i("KDE/VirtualMonitor", "Received request, try connecting to $url")
|
||||
|
||||
if (!openUrlExternally(url)) {
|
||||
Log.e("KDE/VirtualMonitor", "Failed to open $url")
|
||||
val failure = NetworkPacket(PACKET_TYPE_VIRTUALMONITOR).apply {
|
||||
this["failed"] = 0
|
||||
}
|
||||
device.sendPacket(failure)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
|
||||
override fun onCreate() : Boolean
|
||||
{
|
||||
val windowManager = ContextCompat.getSystemService(context, WindowManager::class.java)
|
||||
assert(windowManager != null);
|
||||
val windowMetrics: WindowMetrics = windowManager!!.currentWindowMetrics
|
||||
if (device.ipAddress() == null) {
|
||||
Log.e("KDE/VirtualMonitor", "No IP address for device, pass.")
|
||||
return false
|
||||
}
|
||||
|
||||
val bounds: Rect = windowMetrics.bounds
|
||||
val np = NetworkPacket(PACKET_TYPE_VIRTUALMONITOR).apply {
|
||||
this["resolutions"] = JSONArray().apply { put(JSONObject().apply {
|
||||
put("resolution", bounds.width().toString() + 'x' + bounds.height())
|
||||
put("scale", windowMetrics.density)
|
||||
}) }
|
||||
this["supports_rdp"] = true
|
||||
this["supports_virt_mon"] = false
|
||||
}
|
||||
|
||||
device.sendPacket(np)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onUnpairedDevicePacketReceived(np: NetworkPacket): Boolean {
|
||||
return super.onUnpairedDevicePacketReceived(np)
|
||||
}
|
||||
|
||||
override val supportedPacketTypes: Array<String>
|
||||
get() = arrayOf(PACKET_TYPE_VIRTUALMONITOR, PACKET_TYPE_VIRTUALMONITOR_REQUEST)
|
||||
|
||||
override val outgoingPacketTypes: Array<String>
|
||||
get() = arrayOf(PACKET_TYPE_VIRTUALMONITOR)
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user