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">Find remote device</string>
|
||||||
<string name="pref_plugin_findremotedevice_desc">Ring your remote device</string>
|
<string name="pref_plugin_findremotedevice_desc">Ring your remote device</string>
|
||||||
<string name="ring">Ring</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">System volume</string>
|
||||||
<string name="pref_plugin_systemvolume_desc">Control the system volume of the remote device</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 org.kde.kdeconnect.NetworkPacket;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.SocketAddress;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
|
||||||
@ -43,6 +45,8 @@ public abstract class BaseLink {
|
|||||||
return getDeviceInfo().id;
|
return getDeviceInfo().id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public InetAddress getDeviceIp() { return null; }
|
||||||
|
|
||||||
public BaseLinkProvider getLinkProvider() {
|
public BaseLinkProvider getLinkProvider() {
|
||||||
return linkProvider;
|
return linkProvider;
|
||||||
}
|
}
|
||||||
|
@ -27,9 +27,11 @@ import java.io.IOException;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.net.InetAddress;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.ServerSocket;
|
import java.net.ServerSocket;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
|
import java.net.SocketAddress;
|
||||||
import java.net.SocketTimeoutException;
|
import java.net.SocketTimeoutException;
|
||||||
import java.nio.channels.NotYetConnectedException;
|
import java.nio.channels.NotYetConnectedException;
|
||||||
|
|
||||||
@ -256,4 +258,7 @@ public class LanLink extends BaseLink {
|
|||||||
packetReceived(np);
|
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.ConcurrentMap
|
||||||
import java.util.concurrent.CopyOnWriteArrayList
|
import java.util.concurrent.CopyOnWriteArrayList
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
|
import java.net.InetAddress
|
||||||
|
import java.net.SocketAddress
|
||||||
|
|
||||||
class Device : PacketReceiver {
|
class Device : PacketReceiver {
|
||||||
|
|
||||||
@ -647,6 +649,15 @@ class Device : PacketReceiver {
|
|||||||
|
|
||||||
fun removePluginsChangedListener(listener: PluginsChangedListener) = pluginsChangedListeners.remove(listener)
|
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() {
|
fun disconnect() {
|
||||||
links.forEach(BaseLink::disconnect)
|
links.forEach(BaseLink::disconnect)
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ object NetworkHelper {
|
|||||||
//
|
//
|
||||||
// If we run across an interface that has this, we can safely
|
// 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
|
// 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
|
@JvmStatic
|
||||||
val localIpAddress: InetAddress?
|
val localIpAddress: InetAddress?
|
||||||
get() {
|
get() {
|
||||||
@ -34,7 +34,7 @@ object NetworkHelper {
|
|||||||
//
|
//
|
||||||
// If we run across an interface that has this, we can safely
|
// 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
|
// 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")) {
|
if (intf.displayName.contains("rmnet")) {
|
||||||
continue
|
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