2
0
mirror of https://github.com/KDE/kdeconnect-android synced 2025-09-02 15:15:09 +00:00

Compare commits

...

3 Commits

Author SHA1 Message Date
Albert Vaca Cintora
4d4f63723e Fix build 2025-07-02 21:14:34 +02:00
Albert Vaca Cintora
568a8e623b Tweaks 2025-07-02 20:31:48 +02:00
Aleix Pol
c3e51d13fe virtual-monitor: Make it possible for the device to act as an rdp host 2025-07-02 19:29:28 +02:00
8 changed files with 136 additions and 3 deletions

View File

@@ -137,6 +137,8 @@ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted
<item>stronger</item> <item>stronger</item>
</string-array> </string-array>
<string name="virtualmonitor_rdp_client_not_installed">No app can handle RDP. Please install aFreeRDP or another client.</string>
<string name="sendkeystrokes_send_to">Send keystrokes to</string> <string name="sendkeystrokes_send_to">Send keystrokes to</string>
<string name="sendkeystrokes_textbox_hint">Send keystrokes to host</string> <string name="sendkeystrokes_textbox_hint">Send keystrokes to host</string>
<string name="sendkeystrokes_disabled_toast">Sending keystrokes is disabled - enable it in the settings</string> <string name="sendkeystrokes_disabled_toast">Sending keystrokes is disabled - enable it in the settings</string>
@@ -393,6 +395,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>

View File

@@ -9,6 +9,7 @@ package org.kde.kdeconnect.Backends;
import android.content.Context; import android.content.Context;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread; import androidx.annotation.WorkerThread;
import org.kde.kdeconnect.Device; import org.kde.kdeconnect.Device;
@@ -16,6 +17,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 +46,8 @@ public abstract class BaseLink {
return getDeviceInfo().id; return getDeviceInfo().id;
} }
public abstract @Nullable InetAddress getDeviceIp();
public BaseLinkProvider getLinkProvider() { public BaseLinkProvider getLinkProvider() {
return linkProvider; return linkProvider;
} }

View File

@@ -21,6 +21,7 @@ import java.io.InputStream
import java.io.InputStreamReader import java.io.InputStreamReader
import java.io.OutputStream import java.io.OutputStream
import java.io.Reader import java.io.Reader
import java.net.InetAddress
import java.util.UUID import java.util.UUID
import kotlin.text.Charsets.UTF_8 import kotlin.text.Charsets.UTF_8
@@ -98,6 +99,10 @@ class BluetoothLink(
return theDeviceInfo return theDeviceInfo
} }
override fun getDeviceIp(): InetAddress? {
return null;
}
override fun disconnect() { override fun disconnect() {
if (connection == null) { if (connection == null) {
return return
@@ -159,4 +164,4 @@ class BluetoothLink(
false false
} }
} }
} }

View File

@@ -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(); }
} }

View File

@@ -13,12 +13,16 @@ import org.kde.kdeconnect.Device
import org.kde.kdeconnect.DeviceInfo import org.kde.kdeconnect.DeviceInfo
import org.kde.kdeconnect.Helpers.DeviceHelper.getDeviceInfo import org.kde.kdeconnect.Helpers.DeviceHelper.getDeviceInfo
import org.kde.kdeconnect.NetworkPacket import org.kde.kdeconnect.NetworkPacket
import java.net.InetAddress
class LoopbackLink : BaseLink { class LoopbackLink : BaseLink {
constructor(context: Context, linkProvider: BaseLinkProvider) : super(context, linkProvider) constructor(context: Context, linkProvider: BaseLinkProvider) : super(context, linkProvider)
override fun getName(): String = "LoopbackLink" override fun getName(): String = "LoopbackLink"
override fun getDeviceInfo(): DeviceInfo = getDeviceInfo(context) override fun getDeviceInfo(): DeviceInfo = getDeviceInfo(context)
override fun getDeviceIp(): InetAddress {
return InetAddress.getLoopbackAddress()
}
@WorkerThread @WorkerThread
override fun sendPacket(packet: NetworkPacket, callback: Device.SendPacketStatusCallback, sendPayloadFromSameThread: Boolean): Boolean { override fun sendPacket(packet: NetworkPacket, callback: Device.SendPacketStatusCallback, sendPayloadFromSameThread: Boolean): Boolean {

View File

@@ -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,10 @@ class Device : PacketReceiver {
fun removePluginsChangedListener(listener: PluginsChangedListener) = pluginsChangedListeners.remove(listener) fun removePluginsChangedListener(listener: PluginsChangedListener) = pluginsChangedListeners.remove(listener)
fun ipAddress(): InetAddress? {
return links.firstNotNullOf { it.deviceIp }
}
fun disconnect() { fun disconnect() {
links.forEach(BaseLink::disconnect) links.forEach(BaseLink::disconnect)
} }

View File

@@ -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
} }

View File

@@ -0,0 +1,104 @@
/*
* 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.net.Uri
import android.util.Log
import android.widget.Toast
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)) {
Toast.makeText(context, R.string.virtualmonitor_rdp_client_not_installed, Toast.LENGTH_LONG).show()
openUrlExternally("https://f-droid.org/en/packages/com.freerdp.afreerdp/".toUri())
val failure = NetworkPacket(PACKET_TYPE_VIRTUALMONITOR).apply {
this["failed"] = 0
}
device.sendPacket(failure)
}
}
return true
}
override fun onCreate() : Boolean
{
if (device.ipAddress() == null) {
Log.e("KDE/VirtualMonitor", "No IP address for device, pass.")
return false
}
val metrics = context.resources.displayMetrics
val np = NetworkPacket(PACKET_TYPE_VIRTUALMONITOR).apply {
this["resolutions"] = JSONArray().apply {
put(JSONObject().apply {
put("resolution", "${metrics.widthPixels}x${metrics.heightPixels}")
put("scale", metrics.density)
})
}
this["supports_rdp"] = true
this["supports_virt_mon"] = false
}
device.sendPacket(np)
return true
}
override val supportedPacketTypes: Array<String>
get() = arrayOf(PACKET_TYPE_VIRTUALMONITOR, PACKET_TYPE_VIRTUALMONITOR_REQUEST)
override val outgoingPacketTypes: Array<String>
get() = arrayOf(PACKET_TYPE_VIRTUALMONITOR)
}