diff --git a/src/main/kotlin/app/termora/Host.kt b/src/main/kotlin/app/termora/Host.kt index 8dfc491..fcb769c 100644 --- a/src/main/kotlin/app/termora/Host.kt +++ b/src/main/kotlin/app/termora/Host.kt @@ -25,6 +25,7 @@ enum class Protocol { SSH, Local, Serial, + RDP, /** * 交互式的 SFTP,此协议只在系统内部交互不应该暴露给用户也不应该持久化 diff --git a/src/main/kotlin/app/termora/HostOptionsPane.kt b/src/main/kotlin/app/termora/HostOptionsPane.kt index 58ffc78..ecab376 100644 --- a/src/main/kotlin/app/termora/HostOptionsPane.kt +++ b/src/main/kotlin/app/termora/HostOptionsPane.kt @@ -320,6 +320,7 @@ open class HostOptionsPane : OptionsPane() { protocolTypeComboBox.addItem(Protocol.SSH) protocolTypeComboBox.addItem(Protocol.Local) protocolTypeComboBox.addItem(Protocol.Serial) + protocolTypeComboBox.addItem(Protocol.RDP) authenticationTypeComboBox.addItem(AuthenticationType.No) authenticationTypeComboBox.addItem(AuthenticationType.Password) diff --git a/src/main/kotlin/app/termora/HostTreeNode.kt b/src/main/kotlin/app/termora/HostTreeNode.kt index 995dcce..21b5c21 100644 --- a/src/main/kotlin/app/termora/HostTreeNode.kt +++ b/src/main/kotlin/app/termora/HostTreeNode.kt @@ -49,6 +49,7 @@ class HostTreeNode(host: Host) : SimpleTreeNode(host) { return when (host.protocol) { Protocol.Folder -> if (expanded) FlatTreeOpenIcon() else FlatTreeClosedIcon() Protocol.Serial -> if (selected && hasFocus) Icons.plugin.dark else Icons.plugin + Protocol.RDP -> if (selected && hasFocus) Icons.microsoftWindows.dark else Icons.microsoftWindows else -> if (selected && hasFocus) Icons.terminal.dark else Icons.terminal } } diff --git a/src/main/kotlin/app/termora/Icons.kt b/src/main/kotlin/app/termora/Icons.kt index 7c1e81b..db10644 100644 --- a/src/main/kotlin/app/termora/Icons.kt +++ b/src/main/kotlin/app/termora/Icons.kt @@ -64,6 +64,7 @@ object Icons { val revert by lazy { DynamicIcon("icons/revert.svg", "icons/revert_dark.svg") } val edit by lazy { DynamicIcon("icons/edit.svg", "icons/edit_dark.svg") } val microsoft by lazy { DynamicIcon("icons/microsoft.svg", "icons/microsoft_dark.svg") } + val microsoftWindows by lazy { DynamicIcon("icons/microsoftWindows.svg", "icons/microsoftWindows_dark.svg") } val tencent by lazy { DynamicIcon("icons/tencent.svg") } val google by lazy { DynamicIcon("icons/google-small.svg") } val aliyun by lazy { DynamicIcon("icons/aliyun.svg") } diff --git a/src/main/kotlin/app/termora/NewHostTree.kt b/src/main/kotlin/app/termora/NewHostTree.kt index bacc870..05b52e6 100644 --- a/src/main/kotlin/app/termora/NewHostTree.kt +++ b/src/main/kotlin/app/termora/NewHostTree.kt @@ -37,6 +37,7 @@ import javax.xml.parsers.DocumentBuilderFactory import javax.xml.xpath.XPathConstants import javax.xml.xpath.XPathFactory +@Suppress("CascadeIf") class NewHostTree : SimpleTree() { companion object { @@ -48,7 +49,7 @@ class NewHostTree : SimpleTree() { private val properties get() = Database.getDatabase().properties private val owner get() = SwingUtilities.getWindowAncestor(this) private val openHostAction get() = ActionManager.getInstance().getAction(OpenHostAction.OPEN_HOST) - private val sftpAction get() = ActionManager.getInstance().getAction(app.termora.Actions.SFTP) + private val sftpAction get() = ActionManager.getInstance().getAction(Actions.SFTP) private var isShowMoreInfo get() = properties.getString("HostTree.showMoreInfo", "false").toBoolean() set(value) = properties.putString("HostTree.showMoreInfo", value.toString()) @@ -97,7 +98,7 @@ class NewHostTree : SimpleTree() { // 是否显示更多信息 if (isShowMoreInfo) { val color = if (sel) { - if (tree.hasFocus()) { + if (tree.hasFocus() || isPopupMenu) { UIManager.getColor("textHighlightText") } else { this.foreground @@ -110,15 +111,15 @@ class NewHostTree : SimpleTree() { """${it}""" } - if (host.protocol == Protocol.SSH) { - text = - "${host.name}    ${fontTag.apply("${host.username}@${host.host}")}" + // @formatter:off + if (host.protocol == Protocol.SSH || host.protocol == Protocol.RDP) { + text = "${host.name}    ${fontTag.apply("${host.username}@${host.host}")}" } else if (host.protocol == Protocol.Serial) { - text = - "${host.name}    ${fontTag.apply(host.options.serialComm.port)}" + text = "${host.name}    ${fontTag.apply(host.options.serialComm.port)}" } else if (host.protocol == Protocol.Folder) { text = "${host.name}${fontTag.apply(" (${node.childCount})")}" } + // @formatter:on } val c = super.getTreeCellRendererComponent(tree, text, sel, expanded, leaf, row, hasFocus) @@ -810,7 +811,7 @@ class NewHostTree : SimpleTree() { var group = bookmarkGroups.find { it.bookmarkIds.contains(id) } while (group != null && group.id != "default") { folderNames.addFirst(group.title) - group = bookmarkGroups.find { it.bookmarkGroupIds.contains(group?.id ?: StringUtils.EMPTY) } + group = bookmarkGroups.find { it.bookmarkGroupIds.contains(group.id) } } printer.printRecord( diff --git a/src/main/kotlin/app/termora/actions/OpenHostAction.kt b/src/main/kotlin/app/termora/actions/OpenHostAction.kt index 02e11bb..ed31bf3 100644 --- a/src/main/kotlin/app/termora/actions/OpenHostAction.kt +++ b/src/main/kotlin/app/termora/actions/OpenHostAction.kt @@ -1,6 +1,19 @@ package app.termora.actions import app.termora.* +import com.formdev.flatlaf.util.SystemInfo +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import org.apache.commons.io.FileUtils +import org.apache.commons.io.IOUtils +import org.apache.commons.lang3.StringUtils +import java.awt.datatransfer.DataFlavor +import java.awt.datatransfer.StringSelection +import java.net.URI +import java.util.* +import javax.swing.JOptionPane +import kotlin.time.Duration.Companion.seconds class OpenHostAction : AnAction() { companion object { @@ -26,10 +39,70 @@ class OpenHostAction : AnAction() { Protocol.SSH -> SSHTerminalTab(windowScope, evt.host) Protocol.Serial -> SerialTerminalTab(windowScope, evt.host) Protocol.SFTPPty -> SFTPPtyTerminalTab(windowScope, evt.host) + Protocol.RDP -> openRDP(windowScope, evt.host) else -> LocalTerminalTab(windowScope, evt.host) } - terminalTabbedManager.addTerminalTab(tab) - tab.start() + if (tab is TerminalTab) { + terminalTabbedManager.addTerminalTab(tab) + if (tab is PtyHostTerminalTab) { + tab.start() + } + } + + } + + private fun openRDP(windowScope: WindowScope, host: Host) { + if (SystemInfo.isLinux) { + OptionPane.showMessageDialog( + windowScope.window, + "Linux cannot connect to Windows Remote Server, Supported only for macOS and Windows", + messageType = JOptionPane.WARNING_MESSAGE + ) + return + } + + if (SystemInfo.isMacOS) { + if (!FileUtils.getFile("/Applications/Windows App.app").exists()) { + val option = OptionPane.showConfirmDialog( + windowScope.window, + "If you want to connect to a Windows Remote Server, You have to install the Windows App", + optionType = JOptionPane.OK_CANCEL_OPTION + ) + if (option == JOptionPane.OK_OPTION) { + Application.browse(URI.create("https://apps.apple.com/app/windows-app/id1295203466")) + } + return + } + } + + val sb = StringBuilder() + sb.append("full address:s:").append(host.host).append(':').append(host.port).appendLine() + sb.append("username:s:").append(host.username).appendLine() + + val file = FileUtils.getFile(Application.getTemporaryDir(), UUID.randomUUID().toSimpleString() + ".rdp") + file.outputStream().use { IOUtils.write(sb.toString(), it, Charsets.UTF_8) } + + if (host.authentication.type == AuthenticationType.Password) { + val systemClipboard = windowScope.window.toolkit.systemClipboard + val password = host.authentication.password + systemClipboard.setContents(StringSelection(password), null) + // clear password + swingCoroutineScope.launch(Dispatchers.IO) { + delay(30.seconds) + if (systemClipboard.isDataFlavorAvailable(DataFlavor.stringFlavor)) { + if (systemClipboard.getData(DataFlavor.stringFlavor) == password) { + systemClipboard.setContents(StringSelection(StringUtils.EMPTY), null) + } + } + } + } + + if (SystemInfo.isMacOS) { + ProcessBuilder("open", file.absolutePath).start() + } else if (SystemInfo.isWindows) { + ProcessBuilder("mstsc", file.absolutePath).start() + } + } } \ No newline at end of file diff --git a/src/main/resources/icons/microsoftWindows.svg b/src/main/resources/icons/microsoftWindows.svg new file mode 100644 index 0000000..c9f28e1 --- /dev/null +++ b/src/main/resources/icons/microsoftWindows.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/main/resources/icons/microsoftWindows_dark.svg b/src/main/resources/icons/microsoftWindows_dark.svg new file mode 100644 index 0000000..aad11ed --- /dev/null +++ b/src/main/resources/icons/microsoftWindows_dark.svg @@ -0,0 +1,4 @@ + + + +