mirror of
https://github.com/KDE/kdeconnect-android
synced 2025-09-02 15:15:09 +00:00
Compare commits
3 Commits
master
...
work/apol/
Author | SHA1 | Date | |
---|---|---|---|
|
4d4f63723e | ||
|
568a8e623b | ||
|
c3e51d13fe |
@@ -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>
|
||||||
|
@@ -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;
|
||||||
}
|
}
|
||||||
|
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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(); }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -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 {
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
|
@@ -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,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)
|
||||||
|
}
|
Reference in New Issue
Block a user