From 470b95cc426ce31e2d67c9e0f77e80154ac28aae Mon Sep 17 00:00:00 2001 From: hstyi Date: Thu, 2 Jan 2025 10:51:54 +0800 Subject: [PATCH] Init Commit --- .gitignore | 45 + README.md | 45 + THIRDPARTY | 231 ++++ build.gradle.kts | 293 +++++ docs/findeverywhere.png | Bin 0 -> 84932 bytes docs/readme.png | Bin 0 -> 93326 bytes gradle.properties | 3 + gradle/libs.versions.toml | 98 ++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 60756 bytes gradle/wrapper/gradle-wrapper.properties | 5 + gradlew | 234 ++++ gradlew.bat | 89 ++ settings.gradle.kts | 5 + src/main/java/app/termora/Disposable.java | 14 + src/main/java/app/termora/Disposer.java | 192 +++ src/main/java/app/termora/ObjectNode.java | 128 ++ src/main/java/app/termora/ObjectTree.java | 246 ++++ src/main/java/zmodem/FileCopyStreamEvent.kt | 34 + src/main/java/zmodem/XModem.java | 24 + src/main/java/zmodem/YModem.java | 207 +++ src/main/java/zmodem/ZModem.java | 46 + src/main/java/zmodem/package-info.java | 4 + src/main/java/zmodem/util/CustomFile.java | 65 + src/main/java/zmodem/util/EmptyFileAdapter.kt | 42 + src/main/java/zmodem/util/FileAdapter.java | 25 + .../zmodem/xfer/io/ObjectInputStream.java | 7 + .../zmodem/xfer/io/ObjectOutputStream.java | 7 + src/main/java/zmodem/xfer/util/ASCII.java | 27 + src/main/java/zmodem/xfer/util/Arrays.java | 91 ++ src/main/java/zmodem/xfer/util/Buffer.java | 38 + .../java/zmodem/xfer/util/ByteBuffer.java | 193 +++ src/main/java/zmodem/xfer/util/CRC.java | 232 ++++ src/main/java/zmodem/xfer/util/CRC16.java | 58 + src/main/java/zmodem/xfer/util/CRC8.java | 21 + src/main/java/zmodem/xfer/util/HexBuffer.java | 235 ++++ .../xfer/util/InvalidChecksumException.java | 6 + .../zmodem/xfer/util/TimeoutException.java | 7 + src/main/java/zmodem/xfer/util/XCRC.java | 10 + .../java/zmodem/xfer/zm/packet/Cancel.java | 29 + .../zmodem/xfer/zm/packet/DataPacket.java | 92 ++ .../java/zmodem/xfer/zm/packet/Finish.java | 27 + .../java/zmodem/xfer/zm/packet/Format.java | 48 + .../java/zmodem/xfer/zm/packet/Header.java | 130 ++ .../zm/packet/InvalidPacketException.java | 9 + .../java/zmodem/xfer/zm/proto/Action.java | 5 + .../java/zmodem/xfer/zm/proto/Escape.java | 99 ++ src/main/java/zmodem/xfer/zm/util/Modem.java | 401 ++++++ .../java/zmodem/xfer/zm/util/ZDLEEncoder.java | 58 + .../java/zmodem/xfer/zm/util/ZMOptions.java | 49 + .../java/zmodem/xfer/zm/util/ZMPacket.java | 9 + .../zmodem/xfer/zm/util/ZMPacketFactory.java | 39 + .../zmodem/xfer/zm/util/ZModemCharacter.java | 64 + .../zmodem/xfer/zm/util/ZModemReceive.java | 259 ++++ .../java/zmodem/xfer/zm/util/ZModemSend.java | 213 +++ .../zmodem/zm/io/ZMPacketInputStream.java | 145 +++ .../zmodem/zm/io/ZMPacketOutputStream.java | 67 + src/main/kotlin/app/termora/Actions.kt | 50 + src/main/kotlin/app/termora/AnAction.kt | 16 + src/main/kotlin/app/termora/Application.kt | 146 +++ .../app/termora/ApplicationDisposable.kt | 10 + .../kotlin/app/termora/ApplicationRunner.kt | 254 ++++ src/main/kotlin/app/termora/BannerPanel.kt | 68 + .../app/termora/ChannelShellPtyConnector.kt | 41 + src/main/kotlin/app/termora/Crypto.kt | 155 +++ src/main/kotlin/app/termora/DialogWrapper.kt | 200 +++ .../kotlin/app/termora/DocumentAdaptor.kt | 18 + src/main/kotlin/app/termora/Doorman.kt | 85 ++ src/main/kotlin/app/termora/DoormanDialog.kt | 309 +++++ src/main/kotlin/app/termora/DynamicColor.kt | 63 + src/main/kotlin/app/termora/DynamicIcon.kt | 10 + .../kotlin/app/termora/EditHostOptionsPane.kt | 54 + src/main/kotlin/app/termora/Host.kt | 236 ++++ src/main/kotlin/app/termora/HostDialog.kt | 46 + src/main/kotlin/app/termora/HostManager.kt | 54 + .../kotlin/app/termora/HostOptionsPane.kt | 842 ++++++++++++ .../kotlin/app/termora/HostTerminalTab.kt | 63 + src/main/kotlin/app/termora/HostTree.kt | 583 +++++++++ src/main/kotlin/app/termora/HostTreeModel.kt | 159 +++ src/main/kotlin/app/termora/Hyperlink.kt | 22 + src/main/kotlin/app/termora/I18n.kt | 57 + src/main/kotlin/app/termora/Icons.kt | 83 ++ src/main/kotlin/app/termora/InputDialog.kt | 74 ++ src/main/kotlin/app/termora/Laf.kt | 951 ++++++++++++++ .../kotlin/app/termora/LocalTerminalTab.kt | 20 + .../kotlin/app/termora/LogicCustomTitleBar.kt | 109 ++ src/main/kotlin/app/termora/Main.kt | 6 + .../app/termora/MultiplePtyConnector.kt | 45 + .../app/termora/MultipleTerminalListener.kt | 44 + src/main/kotlin/app/termora/MyTabbedPane.kt | 11 + .../kotlin/app/termora/OpenHostActionEvent.kt | 5 + src/main/kotlin/app/termora/OptionPane.kt | 165 +++ src/main/kotlin/app/termora/OptionsPane.kt | 136 ++ .../kotlin/app/termora/PropertyTerminalTab.kt | 32 + .../kotlin/app/termora/PtyConnectorFactory.kt | 70 + .../kotlin/app/termora/PtyConnectorReader.kt | 27 + .../kotlin/app/termora/PtyHostTerminalTab.kt | 121 ++ .../kotlin/app/termora/ResponseException.kt | 15 + src/main/kotlin/app/termora/SSHTerminalTab.kt | 229 ++++ .../app/termora/SearchableHostTreeModel.kt | 66 + src/main/kotlin/app/termora/SettingsDialog.kt | 61 + .../kotlin/app/termora/SettingsOptionsPane.kt | 1154 +++++++++++++++++ src/main/kotlin/app/termora/SshClients.kt | 112 ++ .../kotlin/app/termora/TerminalFactory.kt | 106 ++ .../app/termora/TerminalPanelFactory.kt | 48 + src/main/kotlin/app/termora/TerminalTab.kt | 41 + .../kotlin/app/termora/TerminalTabDialog.kt | 45 + src/main/kotlin/app/termora/TerminalTabbed.kt | 404 ++++++ .../app/termora/TerminalTabbedManager.kt | 7 + src/main/kotlin/app/termora/TermoraFrame.kt | 455 +++++++ src/main/kotlin/app/termora/TextField.kt | 194 +++ src/main/kotlin/app/termora/ThemeManager.kt | 139 ++ src/main/kotlin/app/termora/TreeUtils.kt | 77 ++ src/main/kotlin/app/termora/UpdaterManager.kt | 149 +++ src/main/kotlin/app/termora/WelcomePanel.kt | 262 ++++ .../zmodem/ZModemPtyConnectorAdaptor.kt | 271 ++++ src/main/kotlin/app/termora/db/Database.kt | 577 +++++++++ .../ActionFindEverywhereResult.kt | 48 + .../BasicFilterFindEverywhereProvider.kt | 22 + .../termora/findeverywhere/FindEverywhere.kt | 244 ++++ .../findeverywhere/FindEverywhereProvider.kt | 19 + .../findeverywhere/FindEverywhereResult.kt | 12 + .../findeverywhere/FindEverywhereXList.kt | 139 ++ .../GroupFindEverywhereResult.kt | 13 + .../QuickActionsFindEverywhereProvider.kt | 27 + .../QuickCommandFindEverywhereProvider.kt | 49 + .../SettingsFindEverywhereProvider.kt | 17 + .../highlight/ChooseColorTemplateDialog.kt | 75 ++ .../app/termora/highlight/ColorPanel.kt | 19 + .../app/termora/highlight/KeywordHighlight.kt | 66 + .../highlight/KeywordHighlightDialog.kt | 247 ++++ .../highlight/KeywordHighlightManager.kt | 49 + .../KeywordHighlightPaintListener.kt | 91 ++ .../highlight/KeywordHighlightTableModel.kt | 29 + .../termora/highlight/KeywordHighlightView.kt | 97 ++ .../termora/highlight/MyColorPickerDialog.kt | 30 + .../highlight/NewKeywordHighlightDialog.kt | 236 ++++ .../kotlin/app/termora/keymgr/KeyManager.kt | 45 + .../app/termora/keymgr/KeyManagerDialog.kt | 70 + .../app/termora/keymgr/KeyManagerPanel.kt | 583 +++++++++ .../app/termora/keymgr/KeyPairTableModel.kt | 28 + .../kotlin/app/termora/keymgr/OhKeyPair.kt | 37 + .../keymgr/OhKeyPairKeyPairProvider.kt | 50 + src/main/kotlin/app/termora/macro/Macro.kt | 24 + .../kotlin/app/termora/macro/MacroAction.kt | 111 ++ .../kotlin/app/termora/macro/MacroDialog.kt | 181 +++ .../macro/MacroFindEverywhereProvider.kt | 73 ++ .../kotlin/app/termora/macro/MacroManager.kt | 43 + .../app/termora/macro/MacroPlaybackAction.kt | 28 + .../app/termora/macro/MacroPtyConnector.kt | 32 + .../macro/MacroStartRecordingAction.kt | 25 + .../termora/macro/MacroStopRecordingAction.kt | 24 + .../kotlin/app/termora/native/FileChooser.kt | 163 +++ .../kotlin/app/termora/native/KeyStorage.kt | 14 + .../app/termora/native/osx/DispatchNative.kt | 33 + .../app/termora/native/osx/MacOSKeyStorage.kt | 248 ++++ .../kotlin/app/termora/sync/GitHubSyncer.kt | 84 ++ .../kotlin/app/termora/sync/GitLabSyncer.kt | 115 ++ src/main/kotlin/app/termora/sync/GitSyncer.kt | 380 ++++++ .../kotlin/app/termora/sync/GiteeSyncer.kt | 83 ++ .../kotlin/app/termora/sync/SyncConfig.kt | 32 + src/main/kotlin/app/termora/sync/Syncer.kt | 7 + .../kotlin/app/termora/sync/SyncerProvider.kt | 19 + .../app/termora/terminal/AbstractProcessor.kt | 3 + .../app/termora/terminal/CharacterSet.kt | 436 +++++++ .../app/termora/terminal/ColorPalette.kt | 70 + .../app/termora/terminal/ColorPaletteImpl.kt | 121 ++ .../app/termora/terminal/ControlCharacters.kt | 69 + .../ControlSequenceIntroducerProcessor.kt | 984 ++++++++++++++ .../app/termora/terminal/CursorModel.kt | 82 ++ .../app/termora/terminal/CursorModelImpl.kt | 146 +++ .../app/termora/terminal/CursorStore.kt | 27 + .../kotlin/app/termora/terminal/DataKey.kt | 186 +++ .../terminal/DeviceControlProcessor.kt | 36 + .../kotlin/app/termora/terminal/Document.kt | 83 ++ .../app/termora/terminal/DocumentImpl.kt | 146 +++ .../EscapeDesignateCharacterSetProcessor.kt | 170 +++ .../terminal/EscapeSequenceProcessor.kt | 497 +++++++ .../kotlin/app/termora/terminal/FindModel.kt | 18 + .../app/termora/terminal/FindModelImpl.kt | 29 + src/main/kotlin/app/termora/terminal/KTerm.kt | 223 ++++ .../kotlin/app/termora/terminal/KeyEncoder.kt | 20 + .../app/termora/terminal/KeyEncoderImpl.kt | 143 ++ .../app/termora/terminal/MarkupModel.kt | 141 ++ .../app/termora/terminal/MarkupModelImpl.kt | 71 + .../kotlin/app/termora/terminal/MouseMode.kt | 9 + .../OperatingSystemCommandProcessor.kt | 120 ++ .../kotlin/app/termora/terminal/Position.kt | 41 + .../kotlin/app/termora/terminal/Processor.kt | 5 + .../app/termora/terminal/PtyConnector.kt | 46 + .../termora/terminal/PtyConnectorDelegate.kt | 30 + .../termora/terminal/PtyProcessConnector.kt | 40 + .../app/termora/terminal/ScrollingModel.kt | 64 + .../termora/terminal/ScrollingModelImpl.kt | 135 ++ .../app/termora/terminal/ScrollingRegion.kt | 3 + .../app/termora/terminal/SelectionModel.kt | 58 + .../termora/terminal/SelectionModelImpl.kt | 187 +++ .../termora/terminal/StreamPtyConnector.kt | 10 + .../app/termora/terminal/SubstrFinder.kt | 91 ++ .../app/termora/terminal/TCharArrayList.kt | 72 + .../termora/terminal/TTextStyleArrayList.kt | 88 ++ .../kotlin/app/termora/terminal/Tabulator.kt | 54 + .../app/termora/terminal/TabulatorImpl.kt | 72 + .../kotlin/app/termora/terminal/Terminal.kt | 81 ++ .../app/termora/terminal/TerminalEvent.kt | 29 + .../app/termora/terminal/TerminalLine.kt | 263 ++++ .../termora/terminal/TerminalLineBuffer.kt | 773 +++++++++++ .../app/termora/terminal/TerminalListener.kt | 11 + .../app/termora/terminal/TerminalModel.kt | 149 +++ .../app/termora/terminal/TerminalModelImpl.kt | 143 ++ .../app/termora/terminal/TerminalMouse.kt | 129 ++ .../app/termora/terminal/TerminalReader.kt | 66 + .../app/termora/terminal/TerminalState.kt | 47 + .../app/termora/terminal/TextProcessor.kt | 37 + .../kotlin/app/termora/terminal/TextStyle.kt | 125 ++ .../app/termora/terminal/VisualTerminal.kt | 258 ++++ .../terminal/panel/AWTTerminalEvent.kt | 51 + .../termora/terminal/panel/TerminalAction.kt | 11 + .../terminal/panel/TerminalCopyAction.kt | 59 + .../termora/terminal/panel/TerminalDisplay.kt | 490 +++++++ .../terminal/panel/TerminalFindAction.kt | 15 + .../terminal/panel/TerminalFindPanel.kt | 189 +++ .../panel/TerminalHyperlinkPaintListener.kt | 90 ++ .../terminal/panel/TerminalInputMethodData.kt | 17 + .../terminal/panel/TerminalPaintListener.kt | 31 + .../termora/terminal/panel/TerminalPanel.kt | 477 +++++++ .../panel/TerminalPanelComponentAdapter.kt | 40 + .../terminal/panel/TerminalPanelKeyAdapter.kt | 58 + .../TerminalPanelMouseHyperlinkAdapter.kt | 29 + .../TerminalPanelMouseSelectionAdapter.kt | 327 +++++ .../TerminalPanelMouseTrackingAdapter.kt | 152 +++ .../panel/TerminalPanelRepaintListener.kt | 30 + .../terminal/panel/TerminalPasteAction.kt | 37 + .../terminal/panel/TerminalPredicateAction.kt | 9 + .../terminal/panel/TerminalScrollBar.kt | 65 + .../terminal/panel/TerminalSelectAllAction.kt | 21 + .../terminal/panel/TerminalSelectionAction.kt | 20 + .../terminal/panel/TerminalZoomAction.kt | 17 + .../terminal/panel/TerminalZoomInAction.kt | 20 + .../terminal/panel/TerminalZoomOutAction.kt | 21 + .../terminal/panel/TerminalZoomResetAction.kt | 24 + src/main/resources/i18n/messages.properties | 198 +++ .../resources/i18n/messages_zh_CN.properties | 188 +++ .../resources/i18n/messages_zh_TW.properties | 180 +++ src/main/resources/icons/add.svg | 4 + src/main/resources/icons/add_dark.svg | 4 + src/main/resources/icons/aliyun.svg | 6 + src/main/resources/icons/aws.svg | 9 + src/main/resources/icons/aws_dark.svg | 9 + src/main/resources/icons/azure.svg | 4 + src/main/resources/icons/azure_dark.svg | 4 + src/main/resources/icons/baiduyun.svg | 9 + src/main/resources/icons/baiduyun_dark.svg | 9 + src/main/resources/icons/chevronDownLarge.svg | 4 + .../resources/icons/chevronDownLarge_dark.svg | 4 + src/main/resources/icons/close.svg | 4 + src/main/resources/icons/close_dark.svg | 4 + src/main/resources/icons/cloud.svg | 5 + src/main/resources/icons/cloud_dark.svg | 5 + src/main/resources/icons/clusterRole.svg | 4 + src/main/resources/icons/clusterRole_dark.svg | 4 + src/main/resources/icons/collapse.svg | 4 + src/main/resources/icons/collapseAll.svg | 5 + src/main/resources/icons/collapseAll_dark.svg | 5 + src/main/resources/icons/collapse_dark.svg | 4 + src/main/resources/icons/colorPicker.svg | 5 + src/main/resources/icons/colorPicker_dark.svg | 5 + src/main/resources/icons/colors.svg | 7 + src/main/resources/icons/colors_dark.svg | 7 + src/main/resources/icons/daemonSets.svg | 8 + src/main/resources/icons/daemonSets_dark.svg | 8 + src/main/resources/icons/dbPrimitive.svg | 7 + src/main/resources/icons/dbPrimitive_dark.svg | 7 + src/main/resources/icons/digitalocean.svg | 1 + .../resources/icons/digitalocean_dark.svg | 1 + src/main/resources/icons/down.svg | 4 + src/main/resources/icons/down_dark.svg | 4 + src/main/resources/icons/download.svg | 7 + src/main/resources/icons/download_dark.svg | 7 + src/main/resources/icons/dumpThreads.svg | 6 + src/main/resources/icons/dumpThreads_dark.svg | 6 + src/main/resources/icons/edit.svg | 4 + src/main/resources/icons/edit_dark.svg | 4 + src/main/resources/icons/empty.svg | 3 + src/main/resources/icons/expand.svg | 4 + src/main/resources/icons/expandAll.svg | 5 + src/main/resources/icons/expandAll_dark.svg | 5 + src/main/resources/icons/expand_dark.svg | 4 + src/main/resources/icons/export.svg | 5 + src/main/resources/icons/export_dark.svg | 5 + src/main/resources/icons/externalLink.svg | 4 + .../resources/icons/externalLink_dark.svg | 4 + src/main/resources/icons/find.svg | 5 + src/main/resources/icons/find_dark.svg | 5 + src/main/resources/icons/folder.svg | 4 + src/main/resources/icons/folder_dark.svg | 4 + src/main/resources/icons/forwardPorts.svg | 5 + .../resources/icons/forwardPorts_dark.svg | 5 + src/main/resources/icons/google-small.svg | 14 + src/main/resources/icons/greyKey.svg | 4 + src/main/resources/icons/greyKey_dark.svg | 4 + src/main/resources/icons/help.svg | 5 + src/main/resources/icons/help_dark.svg | 5 + src/main/resources/icons/homeFolder.svg | 4 + src/main/resources/icons/homeFolder_dark.svg | 4 + src/main/resources/icons/huawei.svg | 1 + src/main/resources/icons/i18n.svg | 11 + src/main/resources/icons/i18n_dark.svg | 11 + src/main/resources/icons/ideUpdate.svg | 12 + src/main/resources/icons/ideUpdate_dark.svg | 12 + src/main/resources/icons/import.svg | 5 + src/main/resources/icons/import_dark.svg | 5 + src/main/resources/icons/infoOutline.svg | 6 + src/main/resources/icons/infoOutline_dark.svg | 6 + src/main/resources/icons/keyboard.svg | 14 + src/main/resources/icons/keyboard_dark.svg | 14 + src/main/resources/icons/lightning.svg | 4 + src/main/resources/icons/lightning_dark.svg | 4 + src/main/resources/icons/linux.svg | 9 + src/main/resources/icons/linux_dark.svg | 9 + src/main/resources/icons/listKey.svg | 13 + src/main/resources/icons/listKey_dark.svg | 13 + src/main/resources/icons/locked.svg | 4 + src/main/resources/icons/locked_dark.svg | 4 + src/main/resources/icons/matchCase.svg | 5 + src/main/resources/icons/matchCase_dark.svg | 5 + src/main/resources/icons/microsoft.svg | 9 + src/main/resources/icons/microsoft_dark.svg | 7 + src/main/resources/icons/moreVertical.svg | 6 + .../resources/icons/moreVertical_dark.svg | 6 + src/main/resources/icons/network.svg | 8 + src/main/resources/icons/networkPolicy.svg | 7 + .../resources/icons/networkPolicy_dark.svg | 7 + src/main/resources/icons/network_dark.svg | 8 + src/main/resources/icons/openNewTab.svg | 5 + src/main/resources/icons/openNewTab_dark.svg | 5 + src/main/resources/icons/pin.svg | 5 + src/main/resources/icons/pin_dark.svg | 5 + src/main/resources/icons/rec.svg | 5 + src/main/resources/icons/rec_dark.svg | 5 + src/main/resources/icons/regex.svg | 5 + src/main/resources/icons/regex_dark.svg | 5 + src/main/resources/icons/revert.svg | 4 + src/main/resources/icons/revert_dark.svg | 4 + src/main/resources/icons/role.svg | 8 + src/main/resources/icons/role_dark.svg | 8 + src/main/resources/icons/runAnything.svg | 6 + src/main/resources/icons/runAnything_dark.svg | 6 + src/main/resources/icons/searchHistory.svg | 5 + .../resources/icons/searchHistory_dark.svg | 5 + src/main/resources/icons/server.svg | 14 + src/main/resources/icons/server_dark.svg | 14 + src/main/resources/icons/serviceAccount.svg | 6 + .../resources/icons/serviceAccount_dark.svg | 6 + src/main/resources/icons/setKey.svg | 13 + src/main/resources/icons/setKey_dark.svg | 13 + src/main/resources/icons/settings.svg | 4 + src/main/resources/icons/settings_dark.svg | 4 + src/main/resources/icons/showWriteAccess.svg | 5 + .../resources/icons/showWriteAccess_dark.svg | 5 + src/main/resources/icons/sortedSet.svg | 7 + src/main/resources/icons/sortedSet_dark.svg | 7 + src/main/resources/icons/split.svg | 7 + src/main/resources/icons/split_dark.svg | 7 + src/main/resources/icons/stop.svg | 4 + src/main/resources/icons/stop_dark.svg | 4 + src/main/resources/icons/success.svg | 5 + src/main/resources/icons/success_dark.svg | 5 + src/main/resources/icons/supertypes.svg | 5 + src/main/resources/icons/supertypes_dark.svg | 5 + src/main/resources/icons/tencent.svg | 5 + src/main/resources/icons/terminal.svg | 6 + src/main/resources/icons/terminalClosed.svg | 7 + .../resources/icons/terminalClosed_dark.svg | 7 + src/main/resources/icons/terminalUnread.svg | 7 + .../resources/icons/terminalUnread_dark.svg | 7 + src/main/resources/icons/terminal_dark.svg | 6 + src/main/resources/icons/termora.icns | Bin 0 -> 238520 bytes src/main/resources/icons/termora.ico | Bin 0 -> 28870 bytes src/main/resources/icons/termora.png | Bin 0 -> 24703 bytes src/main/resources/icons/termora_16x16.png | Bin 0 -> 1099 bytes src/main/resources/icons/termora_20x20.png | Bin 0 -> 1304 bytes src/main/resources/icons/termora_24x24.png | Bin 0 -> 1473 bytes src/main/resources/icons/termora_28x28.png | Bin 0 -> 1792 bytes src/main/resources/icons/termora_32x32.png | Bin 0 -> 1760 bytes src/main/resources/icons/termora_48x48.png | Bin 0 -> 2510 bytes src/main/resources/icons/termora_64x64.png | Bin 0 -> 3000 bytes src/main/resources/icons/tianyiyun.svg | 5 + src/main/resources/icons/uiForm.svg | 4 + src/main/resources/icons/uiForm_dark.svg | 11 + src/main/resources/icons/unlocked.svg | 4 + src/main/resources/icons/unlocked_dark.svg | 4 + src/main/resources/icons/up.svg | 4 + src/main/resources/icons/up_dark.svg | 4 + src/main/resources/icons/upload.svg | 7 + src/main/resources/icons/upload_dark.svg | 7 + src/main/resources/icons/user.svg | 7 + src/main/resources/icons/user_dark.svg | 7 + src/main/resources/icons/vcs.svg | 7 + src/main/resources/icons/vcs_dark.svg | 7 + src/main/resources/icons/warning.svg | 6 + src/main/resources/icons/warningDialog.svg | 6 + .../resources/icons/warningDialog_dark.svg | 13 + src/main/resources/icons/warning_dark.svg | 6 + src/main/resources/icons/web.svg | 7 + src/main/resources/icons/web_dark.svg | 7 + src/main/resources/icons/yandexCloud.svg | 15 + src/main/resources/tinylog.properties | 16 + src/test/kotlin/app/termora/AESTest.kt | 25 + .../kotlin/app/termora/BreakIteratorTest.kt | 50 + src/test/kotlin/app/termora/HostTest.kt | 22 + src/test/kotlin/app/termora/I18nTest.kt | 26 + src/test/kotlin/app/termora/KeyUtilsTest.kt | 13 + src/test/kotlin/app/termora/LocaleTest.kt | 15 + src/test/kotlin/app/termora/PBKDF2Test.kt | 17 + src/test/kotlin/app/termora/RSA2048Test.kt | 23 + .../app/termora/terminal/FindModelImplTest.kt | 32 + .../app/termora/terminal/TextStyleTest.kt | 27 + src/test/resources/sshd/Dockerfile | 6 + 418 files changed, 29687 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 THIRDPARTY create mode 100644 build.gradle.kts create mode 100644 docs/findeverywhere.png create mode 100644 docs/readme.png create mode 100644 gradle.properties create mode 100644 gradle/libs.versions.toml create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle.kts create mode 100644 src/main/java/app/termora/Disposable.java create mode 100644 src/main/java/app/termora/Disposer.java create mode 100644 src/main/java/app/termora/ObjectNode.java create mode 100644 src/main/java/app/termora/ObjectTree.java create mode 100644 src/main/java/zmodem/FileCopyStreamEvent.kt create mode 100644 src/main/java/zmodem/XModem.java create mode 100644 src/main/java/zmodem/YModem.java create mode 100644 src/main/java/zmodem/ZModem.java create mode 100644 src/main/java/zmodem/package-info.java create mode 100644 src/main/java/zmodem/util/CustomFile.java create mode 100644 src/main/java/zmodem/util/EmptyFileAdapter.kt create mode 100644 src/main/java/zmodem/util/FileAdapter.java create mode 100644 src/main/java/zmodem/xfer/io/ObjectInputStream.java create mode 100644 src/main/java/zmodem/xfer/io/ObjectOutputStream.java create mode 100644 src/main/java/zmodem/xfer/util/ASCII.java create mode 100644 src/main/java/zmodem/xfer/util/Arrays.java create mode 100644 src/main/java/zmodem/xfer/util/Buffer.java create mode 100644 src/main/java/zmodem/xfer/util/ByteBuffer.java create mode 100644 src/main/java/zmodem/xfer/util/CRC.java create mode 100644 src/main/java/zmodem/xfer/util/CRC16.java create mode 100644 src/main/java/zmodem/xfer/util/CRC8.java create mode 100644 src/main/java/zmodem/xfer/util/HexBuffer.java create mode 100644 src/main/java/zmodem/xfer/util/InvalidChecksumException.java create mode 100644 src/main/java/zmodem/xfer/util/TimeoutException.java create mode 100644 src/main/java/zmodem/xfer/util/XCRC.java create mode 100644 src/main/java/zmodem/xfer/zm/packet/Cancel.java create mode 100644 src/main/java/zmodem/xfer/zm/packet/DataPacket.java create mode 100644 src/main/java/zmodem/xfer/zm/packet/Finish.java create mode 100644 src/main/java/zmodem/xfer/zm/packet/Format.java create mode 100644 src/main/java/zmodem/xfer/zm/packet/Header.java create mode 100644 src/main/java/zmodem/xfer/zm/packet/InvalidPacketException.java create mode 100644 src/main/java/zmodem/xfer/zm/proto/Action.java create mode 100644 src/main/java/zmodem/xfer/zm/proto/Escape.java create mode 100644 src/main/java/zmodem/xfer/zm/util/Modem.java create mode 100644 src/main/java/zmodem/xfer/zm/util/ZDLEEncoder.java create mode 100644 src/main/java/zmodem/xfer/zm/util/ZMOptions.java create mode 100644 src/main/java/zmodem/xfer/zm/util/ZMPacket.java create mode 100644 src/main/java/zmodem/xfer/zm/util/ZMPacketFactory.java create mode 100644 src/main/java/zmodem/xfer/zm/util/ZModemCharacter.java create mode 100644 src/main/java/zmodem/xfer/zm/util/ZModemReceive.java create mode 100644 src/main/java/zmodem/xfer/zm/util/ZModemSend.java create mode 100644 src/main/java/zmodem/zm/io/ZMPacketInputStream.java create mode 100644 src/main/java/zmodem/zm/io/ZMPacketOutputStream.java create mode 100644 src/main/kotlin/app/termora/Actions.kt create mode 100644 src/main/kotlin/app/termora/AnAction.kt create mode 100644 src/main/kotlin/app/termora/Application.kt create mode 100644 src/main/kotlin/app/termora/ApplicationDisposable.kt create mode 100644 src/main/kotlin/app/termora/ApplicationRunner.kt create mode 100644 src/main/kotlin/app/termora/BannerPanel.kt create mode 100644 src/main/kotlin/app/termora/ChannelShellPtyConnector.kt create mode 100644 src/main/kotlin/app/termora/Crypto.kt create mode 100644 src/main/kotlin/app/termora/DialogWrapper.kt create mode 100644 src/main/kotlin/app/termora/DocumentAdaptor.kt create mode 100644 src/main/kotlin/app/termora/Doorman.kt create mode 100644 src/main/kotlin/app/termora/DoormanDialog.kt create mode 100644 src/main/kotlin/app/termora/DynamicColor.kt create mode 100644 src/main/kotlin/app/termora/DynamicIcon.kt create mode 100644 src/main/kotlin/app/termora/EditHostOptionsPane.kt create mode 100644 src/main/kotlin/app/termora/Host.kt create mode 100644 src/main/kotlin/app/termora/HostDialog.kt create mode 100644 src/main/kotlin/app/termora/HostManager.kt create mode 100644 src/main/kotlin/app/termora/HostOptionsPane.kt create mode 100644 src/main/kotlin/app/termora/HostTerminalTab.kt create mode 100644 src/main/kotlin/app/termora/HostTree.kt create mode 100644 src/main/kotlin/app/termora/HostTreeModel.kt create mode 100644 src/main/kotlin/app/termora/Hyperlink.kt create mode 100644 src/main/kotlin/app/termora/I18n.kt create mode 100644 src/main/kotlin/app/termora/Icons.kt create mode 100644 src/main/kotlin/app/termora/InputDialog.kt create mode 100644 src/main/kotlin/app/termora/Laf.kt create mode 100644 src/main/kotlin/app/termora/LocalTerminalTab.kt create mode 100644 src/main/kotlin/app/termora/LogicCustomTitleBar.kt create mode 100644 src/main/kotlin/app/termora/Main.kt create mode 100644 src/main/kotlin/app/termora/MultiplePtyConnector.kt create mode 100644 src/main/kotlin/app/termora/MultipleTerminalListener.kt create mode 100644 src/main/kotlin/app/termora/MyTabbedPane.kt create mode 100644 src/main/kotlin/app/termora/OpenHostActionEvent.kt create mode 100644 src/main/kotlin/app/termora/OptionPane.kt create mode 100644 src/main/kotlin/app/termora/OptionsPane.kt create mode 100644 src/main/kotlin/app/termora/PropertyTerminalTab.kt create mode 100644 src/main/kotlin/app/termora/PtyConnectorFactory.kt create mode 100644 src/main/kotlin/app/termora/PtyConnectorReader.kt create mode 100644 src/main/kotlin/app/termora/PtyHostTerminalTab.kt create mode 100644 src/main/kotlin/app/termora/ResponseException.kt create mode 100644 src/main/kotlin/app/termora/SSHTerminalTab.kt create mode 100644 src/main/kotlin/app/termora/SearchableHostTreeModel.kt create mode 100644 src/main/kotlin/app/termora/SettingsDialog.kt create mode 100644 src/main/kotlin/app/termora/SettingsOptionsPane.kt create mode 100644 src/main/kotlin/app/termora/SshClients.kt create mode 100644 src/main/kotlin/app/termora/TerminalFactory.kt create mode 100644 src/main/kotlin/app/termora/TerminalPanelFactory.kt create mode 100644 src/main/kotlin/app/termora/TerminalTab.kt create mode 100644 src/main/kotlin/app/termora/TerminalTabDialog.kt create mode 100644 src/main/kotlin/app/termora/TerminalTabbed.kt create mode 100644 src/main/kotlin/app/termora/TerminalTabbedManager.kt create mode 100644 src/main/kotlin/app/termora/TermoraFrame.kt create mode 100644 src/main/kotlin/app/termora/TextField.kt create mode 100644 src/main/kotlin/app/termora/ThemeManager.kt create mode 100644 src/main/kotlin/app/termora/TreeUtils.kt create mode 100644 src/main/kotlin/app/termora/UpdaterManager.kt create mode 100644 src/main/kotlin/app/termora/WelcomePanel.kt create mode 100644 src/main/kotlin/app/termora/addons/zmodem/ZModemPtyConnectorAdaptor.kt create mode 100644 src/main/kotlin/app/termora/db/Database.kt create mode 100644 src/main/kotlin/app/termora/findeverywhere/ActionFindEverywhereResult.kt create mode 100644 src/main/kotlin/app/termora/findeverywhere/BasicFilterFindEverywhereProvider.kt create mode 100644 src/main/kotlin/app/termora/findeverywhere/FindEverywhere.kt create mode 100644 src/main/kotlin/app/termora/findeverywhere/FindEverywhereProvider.kt create mode 100644 src/main/kotlin/app/termora/findeverywhere/FindEverywhereResult.kt create mode 100644 src/main/kotlin/app/termora/findeverywhere/FindEverywhereXList.kt create mode 100644 src/main/kotlin/app/termora/findeverywhere/GroupFindEverywhereResult.kt create mode 100644 src/main/kotlin/app/termora/findeverywhere/QuickActionsFindEverywhereProvider.kt create mode 100644 src/main/kotlin/app/termora/findeverywhere/QuickCommandFindEverywhereProvider.kt create mode 100644 src/main/kotlin/app/termora/findeverywhere/SettingsFindEverywhereProvider.kt create mode 100644 src/main/kotlin/app/termora/highlight/ChooseColorTemplateDialog.kt create mode 100644 src/main/kotlin/app/termora/highlight/ColorPanel.kt create mode 100644 src/main/kotlin/app/termora/highlight/KeywordHighlight.kt create mode 100644 src/main/kotlin/app/termora/highlight/KeywordHighlightDialog.kt create mode 100644 src/main/kotlin/app/termora/highlight/KeywordHighlightManager.kt create mode 100644 src/main/kotlin/app/termora/highlight/KeywordHighlightPaintListener.kt create mode 100644 src/main/kotlin/app/termora/highlight/KeywordHighlightTableModel.kt create mode 100644 src/main/kotlin/app/termora/highlight/KeywordHighlightView.kt create mode 100644 src/main/kotlin/app/termora/highlight/MyColorPickerDialog.kt create mode 100644 src/main/kotlin/app/termora/highlight/NewKeywordHighlightDialog.kt create mode 100644 src/main/kotlin/app/termora/keymgr/KeyManager.kt create mode 100644 src/main/kotlin/app/termora/keymgr/KeyManagerDialog.kt create mode 100644 src/main/kotlin/app/termora/keymgr/KeyManagerPanel.kt create mode 100644 src/main/kotlin/app/termora/keymgr/KeyPairTableModel.kt create mode 100644 src/main/kotlin/app/termora/keymgr/OhKeyPair.kt create mode 100644 src/main/kotlin/app/termora/keymgr/OhKeyPairKeyPairProvider.kt create mode 100644 src/main/kotlin/app/termora/macro/Macro.kt create mode 100644 src/main/kotlin/app/termora/macro/MacroAction.kt create mode 100644 src/main/kotlin/app/termora/macro/MacroDialog.kt create mode 100644 src/main/kotlin/app/termora/macro/MacroFindEverywhereProvider.kt create mode 100644 src/main/kotlin/app/termora/macro/MacroManager.kt create mode 100644 src/main/kotlin/app/termora/macro/MacroPlaybackAction.kt create mode 100644 src/main/kotlin/app/termora/macro/MacroPtyConnector.kt create mode 100644 src/main/kotlin/app/termora/macro/MacroStartRecordingAction.kt create mode 100644 src/main/kotlin/app/termora/macro/MacroStopRecordingAction.kt create mode 100644 src/main/kotlin/app/termora/native/FileChooser.kt create mode 100644 src/main/kotlin/app/termora/native/KeyStorage.kt create mode 100644 src/main/kotlin/app/termora/native/osx/DispatchNative.kt create mode 100644 src/main/kotlin/app/termora/native/osx/MacOSKeyStorage.kt create mode 100644 src/main/kotlin/app/termora/sync/GitHubSyncer.kt create mode 100644 src/main/kotlin/app/termora/sync/GitLabSyncer.kt create mode 100644 src/main/kotlin/app/termora/sync/GitSyncer.kt create mode 100644 src/main/kotlin/app/termora/sync/GiteeSyncer.kt create mode 100644 src/main/kotlin/app/termora/sync/SyncConfig.kt create mode 100644 src/main/kotlin/app/termora/sync/Syncer.kt create mode 100644 src/main/kotlin/app/termora/sync/SyncerProvider.kt create mode 100644 src/main/kotlin/app/termora/terminal/AbstractProcessor.kt create mode 100644 src/main/kotlin/app/termora/terminal/CharacterSet.kt create mode 100644 src/main/kotlin/app/termora/terminal/ColorPalette.kt create mode 100644 src/main/kotlin/app/termora/terminal/ColorPaletteImpl.kt create mode 100644 src/main/kotlin/app/termora/terminal/ControlCharacters.kt create mode 100644 src/main/kotlin/app/termora/terminal/ControlSequenceIntroducerProcessor.kt create mode 100644 src/main/kotlin/app/termora/terminal/CursorModel.kt create mode 100644 src/main/kotlin/app/termora/terminal/CursorModelImpl.kt create mode 100644 src/main/kotlin/app/termora/terminal/CursorStore.kt create mode 100644 src/main/kotlin/app/termora/terminal/DataKey.kt create mode 100644 src/main/kotlin/app/termora/terminal/DeviceControlProcessor.kt create mode 100644 src/main/kotlin/app/termora/terminal/Document.kt create mode 100644 src/main/kotlin/app/termora/terminal/DocumentImpl.kt create mode 100644 src/main/kotlin/app/termora/terminal/EscapeDesignateCharacterSetProcessor.kt create mode 100644 src/main/kotlin/app/termora/terminal/EscapeSequenceProcessor.kt create mode 100644 src/main/kotlin/app/termora/terminal/FindModel.kt create mode 100644 src/main/kotlin/app/termora/terminal/FindModelImpl.kt create mode 100644 src/main/kotlin/app/termora/terminal/KTerm.kt create mode 100644 src/main/kotlin/app/termora/terminal/KeyEncoder.kt create mode 100644 src/main/kotlin/app/termora/terminal/KeyEncoderImpl.kt create mode 100644 src/main/kotlin/app/termora/terminal/MarkupModel.kt create mode 100644 src/main/kotlin/app/termora/terminal/MarkupModelImpl.kt create mode 100644 src/main/kotlin/app/termora/terminal/MouseMode.kt create mode 100644 src/main/kotlin/app/termora/terminal/OperatingSystemCommandProcessor.kt create mode 100644 src/main/kotlin/app/termora/terminal/Position.kt create mode 100644 src/main/kotlin/app/termora/terminal/Processor.kt create mode 100644 src/main/kotlin/app/termora/terminal/PtyConnector.kt create mode 100644 src/main/kotlin/app/termora/terminal/PtyConnectorDelegate.kt create mode 100644 src/main/kotlin/app/termora/terminal/PtyProcessConnector.kt create mode 100644 src/main/kotlin/app/termora/terminal/ScrollingModel.kt create mode 100644 src/main/kotlin/app/termora/terminal/ScrollingModelImpl.kt create mode 100644 src/main/kotlin/app/termora/terminal/ScrollingRegion.kt create mode 100644 src/main/kotlin/app/termora/terminal/SelectionModel.kt create mode 100644 src/main/kotlin/app/termora/terminal/SelectionModelImpl.kt create mode 100644 src/main/kotlin/app/termora/terminal/StreamPtyConnector.kt create mode 100644 src/main/kotlin/app/termora/terminal/SubstrFinder.kt create mode 100644 src/main/kotlin/app/termora/terminal/TCharArrayList.kt create mode 100644 src/main/kotlin/app/termora/terminal/TTextStyleArrayList.kt create mode 100644 src/main/kotlin/app/termora/terminal/Tabulator.kt create mode 100644 src/main/kotlin/app/termora/terminal/TabulatorImpl.kt create mode 100644 src/main/kotlin/app/termora/terminal/Terminal.kt create mode 100644 src/main/kotlin/app/termora/terminal/TerminalEvent.kt create mode 100644 src/main/kotlin/app/termora/terminal/TerminalLine.kt create mode 100644 src/main/kotlin/app/termora/terminal/TerminalLineBuffer.kt create mode 100644 src/main/kotlin/app/termora/terminal/TerminalListener.kt create mode 100644 src/main/kotlin/app/termora/terminal/TerminalModel.kt create mode 100644 src/main/kotlin/app/termora/terminal/TerminalModelImpl.kt create mode 100644 src/main/kotlin/app/termora/terminal/TerminalMouse.kt create mode 100644 src/main/kotlin/app/termora/terminal/TerminalReader.kt create mode 100644 src/main/kotlin/app/termora/terminal/TerminalState.kt create mode 100644 src/main/kotlin/app/termora/terminal/TextProcessor.kt create mode 100644 src/main/kotlin/app/termora/terminal/TextStyle.kt create mode 100644 src/main/kotlin/app/termora/terminal/VisualTerminal.kt create mode 100644 src/main/kotlin/app/termora/terminal/panel/AWTTerminalEvent.kt create mode 100644 src/main/kotlin/app/termora/terminal/panel/TerminalAction.kt create mode 100644 src/main/kotlin/app/termora/terminal/panel/TerminalCopyAction.kt create mode 100644 src/main/kotlin/app/termora/terminal/panel/TerminalDisplay.kt create mode 100644 src/main/kotlin/app/termora/terminal/panel/TerminalFindAction.kt create mode 100644 src/main/kotlin/app/termora/terminal/panel/TerminalFindPanel.kt create mode 100644 src/main/kotlin/app/termora/terminal/panel/TerminalHyperlinkPaintListener.kt create mode 100644 src/main/kotlin/app/termora/terminal/panel/TerminalInputMethodData.kt create mode 100644 src/main/kotlin/app/termora/terminal/panel/TerminalPaintListener.kt create mode 100644 src/main/kotlin/app/termora/terminal/panel/TerminalPanel.kt create mode 100644 src/main/kotlin/app/termora/terminal/panel/TerminalPanelComponentAdapter.kt create mode 100644 src/main/kotlin/app/termora/terminal/panel/TerminalPanelKeyAdapter.kt create mode 100644 src/main/kotlin/app/termora/terminal/panel/TerminalPanelMouseHyperlinkAdapter.kt create mode 100644 src/main/kotlin/app/termora/terminal/panel/TerminalPanelMouseSelectionAdapter.kt create mode 100644 src/main/kotlin/app/termora/terminal/panel/TerminalPanelMouseTrackingAdapter.kt create mode 100644 src/main/kotlin/app/termora/terminal/panel/TerminalPanelRepaintListener.kt create mode 100644 src/main/kotlin/app/termora/terminal/panel/TerminalPasteAction.kt create mode 100644 src/main/kotlin/app/termora/terminal/panel/TerminalPredicateAction.kt create mode 100644 src/main/kotlin/app/termora/terminal/panel/TerminalScrollBar.kt create mode 100644 src/main/kotlin/app/termora/terminal/panel/TerminalSelectAllAction.kt create mode 100644 src/main/kotlin/app/termora/terminal/panel/TerminalSelectionAction.kt create mode 100644 src/main/kotlin/app/termora/terminal/panel/TerminalZoomAction.kt create mode 100644 src/main/kotlin/app/termora/terminal/panel/TerminalZoomInAction.kt create mode 100644 src/main/kotlin/app/termora/terminal/panel/TerminalZoomOutAction.kt create mode 100644 src/main/kotlin/app/termora/terminal/panel/TerminalZoomResetAction.kt create mode 100644 src/main/resources/i18n/messages.properties create mode 100644 src/main/resources/i18n/messages_zh_CN.properties create mode 100644 src/main/resources/i18n/messages_zh_TW.properties create mode 100644 src/main/resources/icons/add.svg create mode 100644 src/main/resources/icons/add_dark.svg create mode 100644 src/main/resources/icons/aliyun.svg create mode 100644 src/main/resources/icons/aws.svg create mode 100644 src/main/resources/icons/aws_dark.svg create mode 100644 src/main/resources/icons/azure.svg create mode 100644 src/main/resources/icons/azure_dark.svg create mode 100644 src/main/resources/icons/baiduyun.svg create mode 100644 src/main/resources/icons/baiduyun_dark.svg create mode 100644 src/main/resources/icons/chevronDownLarge.svg create mode 100644 src/main/resources/icons/chevronDownLarge_dark.svg create mode 100644 src/main/resources/icons/close.svg create mode 100644 src/main/resources/icons/close_dark.svg create mode 100644 src/main/resources/icons/cloud.svg create mode 100644 src/main/resources/icons/cloud_dark.svg create mode 100644 src/main/resources/icons/clusterRole.svg create mode 100644 src/main/resources/icons/clusterRole_dark.svg create mode 100644 src/main/resources/icons/collapse.svg create mode 100644 src/main/resources/icons/collapseAll.svg create mode 100644 src/main/resources/icons/collapseAll_dark.svg create mode 100644 src/main/resources/icons/collapse_dark.svg create mode 100644 src/main/resources/icons/colorPicker.svg create mode 100644 src/main/resources/icons/colorPicker_dark.svg create mode 100644 src/main/resources/icons/colors.svg create mode 100644 src/main/resources/icons/colors_dark.svg create mode 100644 src/main/resources/icons/daemonSets.svg create mode 100644 src/main/resources/icons/daemonSets_dark.svg create mode 100644 src/main/resources/icons/dbPrimitive.svg create mode 100644 src/main/resources/icons/dbPrimitive_dark.svg create mode 100644 src/main/resources/icons/digitalocean.svg create mode 100644 src/main/resources/icons/digitalocean_dark.svg create mode 100644 src/main/resources/icons/down.svg create mode 100644 src/main/resources/icons/down_dark.svg create mode 100644 src/main/resources/icons/download.svg create mode 100644 src/main/resources/icons/download_dark.svg create mode 100644 src/main/resources/icons/dumpThreads.svg create mode 100644 src/main/resources/icons/dumpThreads_dark.svg create mode 100644 src/main/resources/icons/edit.svg create mode 100644 src/main/resources/icons/edit_dark.svg create mode 100644 src/main/resources/icons/empty.svg create mode 100644 src/main/resources/icons/expand.svg create mode 100644 src/main/resources/icons/expandAll.svg create mode 100644 src/main/resources/icons/expandAll_dark.svg create mode 100644 src/main/resources/icons/expand_dark.svg create mode 100644 src/main/resources/icons/export.svg create mode 100644 src/main/resources/icons/export_dark.svg create mode 100644 src/main/resources/icons/externalLink.svg create mode 100644 src/main/resources/icons/externalLink_dark.svg create mode 100644 src/main/resources/icons/find.svg create mode 100644 src/main/resources/icons/find_dark.svg create mode 100644 src/main/resources/icons/folder.svg create mode 100644 src/main/resources/icons/folder_dark.svg create mode 100644 src/main/resources/icons/forwardPorts.svg create mode 100644 src/main/resources/icons/forwardPorts_dark.svg create mode 100644 src/main/resources/icons/google-small.svg create mode 100644 src/main/resources/icons/greyKey.svg create mode 100644 src/main/resources/icons/greyKey_dark.svg create mode 100644 src/main/resources/icons/help.svg create mode 100644 src/main/resources/icons/help_dark.svg create mode 100644 src/main/resources/icons/homeFolder.svg create mode 100644 src/main/resources/icons/homeFolder_dark.svg create mode 100644 src/main/resources/icons/huawei.svg create mode 100644 src/main/resources/icons/i18n.svg create mode 100644 src/main/resources/icons/i18n_dark.svg create mode 100644 src/main/resources/icons/ideUpdate.svg create mode 100644 src/main/resources/icons/ideUpdate_dark.svg create mode 100644 src/main/resources/icons/import.svg create mode 100644 src/main/resources/icons/import_dark.svg create mode 100644 src/main/resources/icons/infoOutline.svg create mode 100644 src/main/resources/icons/infoOutline_dark.svg create mode 100644 src/main/resources/icons/keyboard.svg create mode 100644 src/main/resources/icons/keyboard_dark.svg create mode 100644 src/main/resources/icons/lightning.svg create mode 100644 src/main/resources/icons/lightning_dark.svg create mode 100644 src/main/resources/icons/linux.svg create mode 100644 src/main/resources/icons/linux_dark.svg create mode 100644 src/main/resources/icons/listKey.svg create mode 100644 src/main/resources/icons/listKey_dark.svg create mode 100644 src/main/resources/icons/locked.svg create mode 100644 src/main/resources/icons/locked_dark.svg create mode 100644 src/main/resources/icons/matchCase.svg create mode 100644 src/main/resources/icons/matchCase_dark.svg create mode 100644 src/main/resources/icons/microsoft.svg create mode 100644 src/main/resources/icons/microsoft_dark.svg create mode 100644 src/main/resources/icons/moreVertical.svg create mode 100644 src/main/resources/icons/moreVertical_dark.svg create mode 100644 src/main/resources/icons/network.svg create mode 100644 src/main/resources/icons/networkPolicy.svg create mode 100644 src/main/resources/icons/networkPolicy_dark.svg create mode 100644 src/main/resources/icons/network_dark.svg create mode 100644 src/main/resources/icons/openNewTab.svg create mode 100644 src/main/resources/icons/openNewTab_dark.svg create mode 100644 src/main/resources/icons/pin.svg create mode 100644 src/main/resources/icons/pin_dark.svg create mode 100644 src/main/resources/icons/rec.svg create mode 100644 src/main/resources/icons/rec_dark.svg create mode 100644 src/main/resources/icons/regex.svg create mode 100644 src/main/resources/icons/regex_dark.svg create mode 100644 src/main/resources/icons/revert.svg create mode 100644 src/main/resources/icons/revert_dark.svg create mode 100644 src/main/resources/icons/role.svg create mode 100644 src/main/resources/icons/role_dark.svg create mode 100644 src/main/resources/icons/runAnything.svg create mode 100644 src/main/resources/icons/runAnything_dark.svg create mode 100644 src/main/resources/icons/searchHistory.svg create mode 100644 src/main/resources/icons/searchHistory_dark.svg create mode 100644 src/main/resources/icons/server.svg create mode 100644 src/main/resources/icons/server_dark.svg create mode 100644 src/main/resources/icons/serviceAccount.svg create mode 100644 src/main/resources/icons/serviceAccount_dark.svg create mode 100644 src/main/resources/icons/setKey.svg create mode 100644 src/main/resources/icons/setKey_dark.svg create mode 100644 src/main/resources/icons/settings.svg create mode 100644 src/main/resources/icons/settings_dark.svg create mode 100644 src/main/resources/icons/showWriteAccess.svg create mode 100644 src/main/resources/icons/showWriteAccess_dark.svg create mode 100644 src/main/resources/icons/sortedSet.svg create mode 100644 src/main/resources/icons/sortedSet_dark.svg create mode 100644 src/main/resources/icons/split.svg create mode 100644 src/main/resources/icons/split_dark.svg create mode 100644 src/main/resources/icons/stop.svg create mode 100644 src/main/resources/icons/stop_dark.svg create mode 100644 src/main/resources/icons/success.svg create mode 100644 src/main/resources/icons/success_dark.svg create mode 100644 src/main/resources/icons/supertypes.svg create mode 100644 src/main/resources/icons/supertypes_dark.svg create mode 100644 src/main/resources/icons/tencent.svg create mode 100644 src/main/resources/icons/terminal.svg create mode 100644 src/main/resources/icons/terminalClosed.svg create mode 100644 src/main/resources/icons/terminalClosed_dark.svg create mode 100644 src/main/resources/icons/terminalUnread.svg create mode 100644 src/main/resources/icons/terminalUnread_dark.svg create mode 100644 src/main/resources/icons/terminal_dark.svg create mode 100644 src/main/resources/icons/termora.icns create mode 100644 src/main/resources/icons/termora.ico create mode 100644 src/main/resources/icons/termora.png create mode 100644 src/main/resources/icons/termora_16x16.png create mode 100644 src/main/resources/icons/termora_20x20.png create mode 100644 src/main/resources/icons/termora_24x24.png create mode 100644 src/main/resources/icons/termora_28x28.png create mode 100644 src/main/resources/icons/termora_32x32.png create mode 100644 src/main/resources/icons/termora_48x48.png create mode 100644 src/main/resources/icons/termora_64x64.png create mode 100644 src/main/resources/icons/tianyiyun.svg create mode 100644 src/main/resources/icons/uiForm.svg create mode 100644 src/main/resources/icons/uiForm_dark.svg create mode 100644 src/main/resources/icons/unlocked.svg create mode 100644 src/main/resources/icons/unlocked_dark.svg create mode 100644 src/main/resources/icons/up.svg create mode 100644 src/main/resources/icons/up_dark.svg create mode 100644 src/main/resources/icons/upload.svg create mode 100644 src/main/resources/icons/upload_dark.svg create mode 100644 src/main/resources/icons/user.svg create mode 100644 src/main/resources/icons/user_dark.svg create mode 100644 src/main/resources/icons/vcs.svg create mode 100644 src/main/resources/icons/vcs_dark.svg create mode 100644 src/main/resources/icons/warning.svg create mode 100644 src/main/resources/icons/warningDialog.svg create mode 100644 src/main/resources/icons/warningDialog_dark.svg create mode 100644 src/main/resources/icons/warning_dark.svg create mode 100644 src/main/resources/icons/web.svg create mode 100644 src/main/resources/icons/web_dark.svg create mode 100644 src/main/resources/icons/yandexCloud.svg create mode 100644 src/main/resources/tinylog.properties create mode 100644 src/test/kotlin/app/termora/AESTest.kt create mode 100644 src/test/kotlin/app/termora/BreakIteratorTest.kt create mode 100644 src/test/kotlin/app/termora/HostTest.kt create mode 100644 src/test/kotlin/app/termora/I18nTest.kt create mode 100644 src/test/kotlin/app/termora/KeyUtilsTest.kt create mode 100644 src/test/kotlin/app/termora/LocaleTest.kt create mode 100644 src/test/kotlin/app/termora/PBKDF2Test.kt create mode 100644 src/test/kotlin/app/termora/RSA2048Test.kt create mode 100644 src/test/kotlin/app/termora/terminal/FindModelImplTest.kt create mode 100644 src/test/kotlin/app/termora/terminal/TextStyleTest.kt create mode 100644 src/test/resources/sshd/Dockerfile diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a273c0c --- /dev/null +++ b/.gitignore @@ -0,0 +1,45 @@ +.gradle +build/ +data/ +dist/ +certs/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### Kotlin ### +.kotlin + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..79d000b --- /dev/null +++ b/README.md @@ -0,0 +1,45 @@ +# Termora + +**Termora** 是一个终端模拟器和 SSH 客户端,支持 Windows,macOS 和 Linux。 + +
+ termora +
+ +**Termora** 采用 [Kotlin/JVM](https://kotlinlang.org/) 开发并实现了 [XTerm](https://invisible-island.net/xterm/ctlseqs/ctlseqs.html) 协议(尚未完全实现),它的最终目标是通过 [Kotlin Multiplatform](https://kotlinlang.org/docs/multiplatform.html) 实现全平台(含 Android、iOS、iPadOS 等)。 + +## 功能特性 + +- 支持 SSH 和本地终端 +- 支持 Windows、macOS、Linux 平台 +- 支持 Zmodem 协议 +- 支持 SSH 端口转发 +- 支持配置同步到 [Gist](https://gist.github.com) +- 支持宏(录制脚本并回放) +- 支持关键词高亮 +- 支持密钥管理器 +- 支持将命令发送到多个会话 +- 支持 [Find Everywhere](./docs/findeverywhere.png) 快速跳转 +- 支持数据加密 +- ... + +## 下载 + +- [releases](https://github.com/TermoraDev/termora/releases/latest) + +### macOS + +由于苹果开发者证书正在申请中,所以 macOS 用户需要执行 `sudo xattr -r -d com.apple.quarantine /Applications/Termora.app` 后才可以运行程序。 + +## 开发 + +建议使用 [JetBrainsRuntime](https://github.com/JetBrains/JetBrainsRuntime) 的 JDK 版本,通过 `./gradlew :run`即可运行程序。 + +通过 `./gradlew dist` 可以自动构建适用于本机的版本。在 macOS 上是:`dmg`,在 Windows 上是:`zip`,在 Linux 上是:`tar.gz`。 + +## 协议 + +本软件采用双重许可模式,您可以选择以下任意一种许可方式: + +- AGPL-3.0:根据 [AGPL-3.0](https://opensource.org/license/agpl-v3) 的条款,您可以自由使用、分发和修改本软件。 +- 专有许可:如果希望在闭源或专有环境中使用,请联系作者获取许可。 diff --git a/THIRDPARTY b/THIRDPARTY new file mode 100644 index 0000000..2178ef8 --- /dev/null +++ b/THIRDPARTY @@ -0,0 +1,231 @@ +annotations 24.0.1 +Apache License 2.0 +https://github.com/JetBrains/java-annotations/blob/master/LICENSE.txt + +bip39-lib-jvm 1.0.8 +MIT License +https://github.com/Electric-Coin-Company/kotlin-bip39/blob/main/LICENSE + +colorpicker 2.0.1 +BSD 3-Clause "New" or "Revised" License +https://github.com/dheid/colorpicker/blob/main/LICENSE + +commonmark 0.24.0 +BSD 2-Clause "Simplified" License +https://github.com/commonmark/commonmark-java/blob/main/LICENSE.txt + +commons-codec 1.17.1 +Apache License 2.0 +https://github.com/apache/commons-codec/blob/master/LICENSE.txt + +commons-compress 1.27.1 +Apache License 2.0 +https://github.com/apache/commons-compress/blob/master/LICENSE.txt + +commons-io 2.18.0 +Apache License 2.0 +https://github.com/apache/commons-io/blob/master/LICENSE.txt + +commons-lang3 3.17.0 +Apache License 2.0 +https://github.com/apache/commons-lang/blob/master/LICENSE.txt + +commons-net 3.11.1 +Apache License 2.0 +https://github.com/apache/commons-net/blob/master/LICENSE.txt + +commons-text 1.12.0 +Apache License 2.0 +https://github.com/apache/commons-text/blob/master/LICENSE.txt + +eddsa 0.3.0 +Creative Commons Zero v1.0 Universal +https://github.com/str4d/ed25519-java/blob/master/LICENSE.txt + +flatlaf 3.5.4 +Apache License 2.0 +https://github.com/JFormDesigner/FlatLaf/blob/main/LICENSE + +flatlaf-extras 3.5.4 +Apache License 2.0 +https://github.com/JFormDesigner/FlatLaf/blob/main/LICENSE + +flatlaf-swingx 3.5.4 +Apache License 2.0 +https://github.com/JFormDesigner/FlatLaf/blob/main/LICENSE + +JavaEWAH 1.2.3 +Apache License 2.0 +https://github.com/lemire/javaewah/blob/master/LICENSE + +jbr-api 17.1.10.1 +Apache License 2.0 +https://github.com/JetBrains/JetBrainsRuntimeApi/blob/main/LICENSE + +jcl-over-slf4j 1.7.36 +Apache License 2.0 +https://www.apache.org/licenses/LICENSE-2.0.txt + +jfa 1.2.0 +Apache License 2.0 +https://github.com/0x4a616e/jfa/blob/main/LICENSE + +jgoodies-common 1.8.1 +BSD-2-Clause License +http://www.opensource.org/licenses/bsd-license.html + +jgoodies-forms 1.9.0 +BSD-2-Clause License +http://www.opensource.org/licenses/bsd-license.html + +jna 5.16.0 +Apache License 2.0 +https://github.com/java-native-access/jna/blob/master/AL2.0 + +jna-platform 5.16.0 +Apache License 2.0 +https://github.com/java-native-access/jna/blob/master/AL2.0 + +jnafilechooser-api 1.1.2 +BSD 3-Clause "New" or "Revised" License +https://github.com/steos/jnafilechooser/blob/master/LICENSE + +jnafilechooser-win32 1.1.2 +BSD 3-Clause "New" or "Revised" License +https://github.com/steos/jnafilechooser/blob/master/LICENSE + +jsvg 1.4.0 +MIT License +https://github.com/weisJ/jsvg/blob/master/LICENSE + +jSystemThemeDetector 3.9.1 +Apache License 2.0 +https://github.com/Dansoftowner/jSystemThemeDetector/blob/master/LICENSE + +kotlin-logging 1.7.9 +Apache License 2.0 +https://github.com/oshai/kotlin-logging/blob/master/LICENSE + +kotlin-stdlib 2.1.0 +Apache License 2.0 +https://github.com/JetBrains/kotlin/blob/master/license/LICENSE.txt + +kotlin-stdlib-jdk7 1.9.10 +Apache License 2.0 +https://github.com/JetBrains/kotlin/blob/master/license/LICENSE.txt + +kotlin-stdlib-jdk8 1.9.10 +Apache License 2.0 +https://github.com/JetBrains/kotlin/blob/master/license/LICENSE.txt + +kotlin-stdlib-jdk8 1.9.10 +Apache License 2.0 +https://github.com/JetBrains/kotlin/blob/master/license/LICENSE.txt + +kotlinx-coroutines-core-jvm 1.10.1 +Apache License 2.0 +https://www.apache.org/licenses/LICENSE-2.0 + +kotlinx-coroutines-swing 1.10.1 +Apache License 2.0 +https://www.apache.org/licenses/LICENSE-2.0 + +kotlinx-serialization-core-jvm 1.7.3 +Apache License 2.0 +https://github.com/Kotlin/kotlinx.serialization/blob/master/LICENSE.txt + +kotlinx-serialization-json-jvm 1.7.3 +Apache License 2.0 +https://github.com/Kotlin/kotlinx.serialization/blob/master/LICENSE.txt + +logging-interceptor 4.12.0 +Apache License 2.0 +https://www.apache.org/licenses/LICENSE-2.0 + +okhttp 4.12.0 +Apache License 2.0 +https://www.apache.org/licenses/LICENSE-2.0 + +okio-jvm 3.6.0 +Apache License 2.0 +https://www.apache.org/licenses/LICENSE-2.0 + +org.eclipse.jgit.ssh.apache 7.1.0.202411261347-r +Eclipse Distribution License +https://www.eclipse.org/org/documents/edl-v10.php + +org.eclipse.jgit 7.1.0.202411261347-r +Eclipse Distribution License +https://www.eclipse.org/org/documents/edl-v10.php + +oshi-core 6.6.5 +MIT License +https://github.com/oshi/oshi/blob/master/LICENSE + +pty4j 0.13.2 +Eclipse Public License 1.0 +https://github.com/JetBrains/pty4j/blob/master/LICENSE + +slf4j-api 2.0.16 +MIT License +https://github.com/qos-ch/slf4j/blob/master/LICENSE.txt + +slf4j-tinylog 2.7.0 +Apache License 2.0 +https://github.com/tinylog-org/tinylog/blob/v2.7/license.txt + +sshd-common 2.14.0 +Apache License 2.0 +https://www.apache.org/licenses/LICENSE-2.0 + +sshd-core 2.14.0 +Apache License 2.0 +https://www.apache.org/licenses/LICENSE-2.0 + +sshd-osgi 2.14.0 +Apache License 2.0 +https://www.apache.org/licenses/LICENSE-2.0 + +sshd-sftp 2.14.0 +Apache License 2.0 +https://www.apache.org/licenses/LICENSE-2.0 + +swingx-all 1.6.5-1 +GNU LESSER GENERAL PUBLIC LICENSE v3 +https://www.gnu.org/licenses/lgpl-3.0 + +tinylog-api 2.7.0 +Apache License 2.0 +https://github.com/tinylog-org/tinylog/blob/v2.7/license.txt + +tinylog-impl 2.7.0 +Apache License 2.0 +https://github.com/tinylog-org/tinylog/blob/v2.7/license.txt + +versioncompare 1.4.1 +Apache License 2.0 +https://github.com/G00fY2/version-compare/blob/main/LICENSE + +xodus-compress 2.0.1 +Apache License 2.0 +https://github.com/JetBrains/xodus/blob/master/LICENSE.txt + +xodus-environment 2.0.1 +Apache License 2.0 +https://github.com/JetBrains/xodus/blob/master/LICENSE.txt + +xodus-openAPI 2.0.1 +Apache License 2.0 +https://github.com/JetBrains/xodus/blob/master/LICENSE.txt + +xodus-utils 2.0.1 +Apache License 2.0 +https://github.com/JetBrains/xodus/blob/master/LICENSE.txt + +xodus-vfs 2.0.1 +Apache License 2.0 +https://github.com/JetBrains/xodus/blob/master/LICENSE.txt + +jediterm +Apache License 2.0 +https://github.com/JetBrains/jediterm/blob/master/LICENSE-APACHE-2.0.txt \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..56e76f0 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,293 @@ +import org.gradle.internal.jvm.Jvm +import org.gradle.kotlin.dsl.support.uppercaseFirstChar +import org.gradle.nativeplatform.platform.internal.ArchitectureInternal +import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform +import org.gradle.nativeplatform.platform.internal.DefaultOperatingSystem +import org.jetbrains.kotlin.org.apache.commons.lang3.StringUtils + +plugins { + java + application + alias(libs.plugins.kotlin.jvm) + alias(libs.plugins.kotlinx.serialization) +} + + +group = "app.termora" +version = "1.0.0" + +val os: DefaultOperatingSystem = DefaultNativePlatform.getCurrentOperatingSystem() +var arch: ArchitectureInternal = DefaultNativePlatform.getCurrentArchitecture() + + +repositories { + mavenCentral() + maven("https://packages.jetbrains.team/maven/p/ij/intellij-dependencies") + maven("https://www.jitpack.io") +} + +dependencies { + testImplementation(kotlin("test")) + testImplementation(libs.hutool) + testImplementation(libs.sshj) + testImplementation(platform(libs.koin.bom)) + testImplementation(libs.koin.core) + testImplementation(libs.jsch) + testImplementation(libs.rhino) + testImplementation(libs.delight.rhino.sandbox) + + implementation(libs.slf4j.api) + implementation(libs.pty4j) + implementation(libs.slf4j.tinylog) + implementation(libs.tinylog.impl) + implementation(libs.commons.codec) + implementation(libs.commons.io) + implementation(libs.commons.lang3) + implementation(libs.commons.net) + implementation(libs.commons.text) + implementation(libs.commons.compress) + implementation(libs.kotlinx.coroutines.swing) + implementation(libs.kotlinx.coroutines.core) + implementation(libs.flatlaf) + implementation(libs.flatlaf.extras) + implementation(libs.flatlaf.swingx) + implementation(libs.kotlinx.serialization.json) + implementation(libs.swingx) + implementation(libs.jgoodies.forms) + implementation(libs.jna) + implementation(libs.jna.platform) + implementation(libs.versioncompare) + implementation(libs.oshi.core) + implementation(libs.jSystemThemeDetector) { exclude(group = "*", module = "*") } + implementation(libs.jfa) { exclude(group = "*", module = "*") } + implementation(libs.jbr.api) + implementation(libs.okhttp) + implementation(libs.okhttp.logging) + implementation(libs.sshd.core) + implementation(libs.commonmark) + implementation(libs.jgit) + implementation(libs.jgit.sshd) + implementation(libs.jnafilechooser) + implementation(libs.xodus.vfs) + implementation(libs.xodus.openAPI) + implementation(libs.xodus.environment) + implementation(libs.bip39) + implementation(libs.colorpicker) +} + +application { + val args = mutableListOf( + "--add-exports java.base/sun.nio.ch=ALL-UNNAMED", + ) + + if (os.isMacOsX) { + args.add("--add-opens java.desktop/sun.lwawt.macosx.concurrent=ALL-UNNAMED") + args.add("-Dsun.java2d.metal=true") + args.add("-Dapple.awt.application.appearance=system") + } + + args.add("-Dapp-version=${project.version}") + + if (os.isLinux) { + args.add("-Dsun.java2d.opengl=true") + } + + applicationDefaultJvmArgs = args + mainClass = "app.termora.MainKt" +} + +tasks.test { + useJUnitPlatform() +} + +tasks.register("copy-dependencies") { + from(configurations.runtimeClasspath) + .into("${layout.buildDirectory.get()}/libs") +} + +tasks.register("jlink") { + val modules = listOf( + "java.base", + "java.desktop", + "java.logging", + "java.management", + "java.rmi", + "java.security.jgss", + "jdk.crypto.ec", + "jdk.unsupported", + ) + + commandLine( + "${Jvm.current().javaHome}/bin/jlink", + "--verbose", + "--strip-java-debug-attributes", + "--strip-native-commands", + "--strip-debug", + "--compress=zip-9", + "--no-header-files", + "--no-man-pages", + "--add-modules", + modules.joinToString(","), + "--output", + "${layout.buildDirectory.get()}/jlink" + ) +} + +tasks.register("jpackage") { + val buildDir = layout.buildDirectory.get() + val options = mutableListOf( + "--add-exports java.base/sun.nio.ch=ALL-UNNAMED", + "-Xmx2g", + "-XX:+HeapDumpOnOutOfMemoryError", + "-Dlogger.console.level=off", + "-Dkotlinx.coroutines.debug=off", + "-Dapp-version=${project.version}", + ) + + if (os.isMacOsX) { + options.add("-Dsun.java2d.metal=true") + options.add("-Dapple.awt.application.appearance=system") + options.add("--add-opens java.desktop/sun.lwawt.macosx.concurrent=ALL-UNNAMED") + } else { + options.add("-Dsun.java2d.opengl=true") + } + + val arguments = mutableListOf("${Jvm.current().javaHome}/bin/jpackage", "--verbose") + arguments.addAll(listOf("--runtime-image", "${buildDir}/jlink")) + arguments.addAll(listOf("--name", project.name.uppercaseFirstChar())) + arguments.addAll(listOf("--app-version", "${project.version}")) + arguments.addAll(listOf("--main-jar", tasks.jar.get().archiveFileName.get())) + arguments.addAll(listOf("--main-class", application.mainClass.get())) + arguments.addAll(listOf("--input", "$buildDir/libs")) + arguments.addAll(listOf("--temp", "$buildDir/jpackage")) + arguments.addAll(listOf("--dest", "$buildDir/distributions")) + arguments.addAll(listOf("--java-options", options.joinToString(StringUtils.SPACE))) + + + if (os.isMacOsX) { + arguments.addAll(listOf("--mac-package-name", project.name.uppercaseFirstChar())) + arguments.addAll(listOf("--mac-app-category", "developer-tools")) + arguments.addAll(listOf("--mac-package-identifier", "${project.group}")) + arguments.addAll(listOf("--icon", "${projectDir.absolutePath}/src/main/resources/icons/termora.icns")) + } + + if (os.isWindows) { + arguments.add("--win-dir-chooser") + arguments.add("--win-shortcut") + arguments.add("--win-shortcut-prompt") + arguments.addAll(listOf("--win-upgrade-uuid", "E1D93CAD-5BF8-442E-93BA-6E90DE601E4C")) + arguments.addAll(listOf("--icon", "${projectDir.absolutePath}/src/main/resources/icons/termora.ico")) + } + + + arguments.add("--type") + if (os.isMacOsX) { + arguments.add("dmg") + } else if (os.isWindows) { + arguments.add("msi") + } else if (os.isLinux) { + arguments.add("app-image") + } else { + throw UnsupportedOperationException() + } + + commandLine(arguments) + +} + +tasks.register("dist") { + doLast { + val vendor = Jvm.current().vendor ?: StringUtils.EMPTY + @Suppress("UnstableApiUsage") + if (!JvmVendorSpec.JETBRAINS.matches(vendor)) { + throw GradleException("JVM: $vendor is not supported") + } + + val distributionDir = layout.buildDirectory.dir("distributions").get() + val gradlew = File(projectDir, if (os.isWindows) "gradlew.bat" else "gradlew").absolutePath + + // 清空目录 + exec { commandLine(gradlew, "clean") } + + // 打包并复制依赖 + exec { commandLine(gradlew, "jar", "copy-dependencies") } + + // 检查依赖的开源协议 + exec { commandLine(gradlew, "check-license") } + + // jlink + exec { commandLine(gradlew, "jlink") } + + // 打包 + exec { commandLine(gradlew, "jpackage") } + + // pack + exec { + if (os.isWindows) { // zip + commandLine( + "tar", "-vacf", + distributionDir.file("${project.name}-${project.version}-windows-${arch.name}.zip").asFile.absolutePath, + project.name.uppercaseFirstChar() + ) + workingDir = layout.buildDirectory.dir("jpackage/images/win-msi.image/").get().asFile + } else if (os.isLinux) { // tar.gz + commandLine( + "tar", "-czvf", + distributionDir.file("${project.name}-${project.version}-linux-${arch.name}.tar.gz").asFile.absolutePath, + project.name.uppercaseFirstChar() + ) + workingDir = distributionDir.asFile + } else if (os.isMacOsX) { // rename + commandLine( + "mv", + distributionDir.file("${project.name.uppercaseFirstChar()}-${project.version}.dmg").asFile.absolutePath, + distributionDir.file("${project.name}-${project.version}-osx-${arch.name}.dmg").asFile.absolutePath, + ) + } else { + throw GradleException("${os.name} is not supported") + } + } + } +} + +tasks.register("check-license") { + doLast { + val thirdParty = mutableMapOf() + val iterator = File(projectDir, "THIRDPARTY").readLines().iterator() + val thirdPartyNames = mutableSetOf() + + while (iterator.hasNext()) { + val nameWithVersion = iterator.next() + if (nameWithVersion.isBlank()) { + continue + } + + // ignore license name + iterator.next() + + val license = iterator.next() + thirdParty[nameWithVersion.replace(StringUtils.SPACE, "-")] = license + thirdPartyNames.add(nameWithVersion.split(StringUtils.SPACE).first()) + } + + for (file in configurations.runtimeClasspath.get()) { + val name = file.nameWithoutExtension + if (!thirdParty.containsKey(name)) { + if (logger.isWarnEnabled) { + logger.warn("$name does not exist in third-party") + } + if (!thirdPartyNames.contains(name)) { + throw GradleException("$name No license found") + } + } + } + } +} + +kotlin { + jvmToolchain { + languageVersion = JavaLanguageVersion.of(21) + @Suppress("UnstableApiUsage") + vendor = JvmVendorSpec.JETBRAINS + } +} \ No newline at end of file diff --git a/docs/findeverywhere.png b/docs/findeverywhere.png new file mode 100644 index 0000000000000000000000000000000000000000..90237e08d9f17fcba009c9a433b7c861f0ccafa0 GIT binary patch literal 84932 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 literal 0 HcmV?d00001 diff --git a/docs/readme.png b/docs/readme.png new file mode 100644 index 0000000000000000000000000000000000000000..0971a141a768e6ce6f5202ac31ae99a783c8a86d GIT binary patch literal 93326 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 literal 0 HcmV?d00001 diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..25b09e3 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.caching=true +org.gradle.parallel=true +kotlin.code.style=official \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..f02b792 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,98 @@ +[versions] +kotlin = "2.1.0" +slf4j = "2.0.16" +pty4j = "0.13.2" +tinylog = "2.7.0" +kotlinx-coroutines = "1.10.1" +flatlaf = "3.5.4" +trove4j = "1.0.20200330" +kotlinx-serialization-json = "1.7.3" +commons-codec = "1.17.1" +commons-lang3 = "3.17.0" +commons-net = "3.11.1" +commons-text = "1.12.0" +commons-compress = "1.27.1" +koin-bom = "4.0.0" +swingx = "1.6.5-1" +jgoodies-forms = "1.9.0" +jfa = "1.2.0" +oshi = "6.6.5" +versioncompare = "1.4.1" +jna = "5.16.0" +jSystemThemeDetector = "3.9.1" +commons-io = "2.18.0" +jbr-api = "17.1.10.1" +leveldb = "0.12" +guava = "33.3.1-jre" +credential-secure-storage = "1.0.3" +hutool = "5.8.34" +jsch = "0.2.21" +okhttp = "4.12.0" +bcprov = "1.79" +sshj = "0.39.0" +sshd-core = "2.14.0" +jgit = "7.1.0.202411261347-r" +commonmark = "0.24.0" +jnafilechooser = "1.1.2" +xodus = "2.0.1" +bip39 = "1.0.8" +colorpicker = "2.0.1" +rhino = "1.7.15" +delight-rhino-sandbox = "0.0.17" + +[libraries] +kotlinx-coroutines-swing = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" } +kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" } +kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinx-serialization-json" } +slf4j-api = { group = "org.slf4j", name = "slf4j-api", version.ref = "slf4j" } +slf4j-tinylog = { group = "org.tinylog", name = "slf4j-tinylog", version.ref = "tinylog" } +tinylog-impl = { group = "org.tinylog", name = "tinylog-impl", version.ref = "tinylog" } +commons-codec = { group = "commons-codec", name = "commons-codec", version.ref = "commons-codec" } +commons-net = { group = "commons-net", name = "commons-net", version.ref = "commons-net" } +commons-lang3 = { group = "org.apache.commons", name = "commons-lang3", version.ref = "commons-lang3" } +commons-text = { group = "org.apache.commons", name = "commons-text", version.ref = "commons-text" } +commons-compress = { group = "org.apache.commons", name = "commons-compress", version.ref = "commons-compress" } +pty4j = { group = "org.jetbrains.pty4j", name = "pty4j", version.ref = "pty4j" } +flatlaf = { group = "com.formdev", name = "flatlaf", version.ref = "flatlaf" } +flatlaf-extras = { group = "com.formdev", name = "flatlaf-extras", version.ref = "flatlaf" } +trove4j = { group = "org.jetbrains.intellij.deps", name = "trove4j", version.ref = "trove4j" } +koin-bom = { module = "io.insert-koin:koin-bom", version.ref = "koin-bom" } +koin-core = { module = "io.insert-koin:koin-core" } +swingx = { module = "org.swinglabs.swingx:swingx-all", version.ref = "swingx" } +jgoodies-forms = { module = "com.jgoodies:jgoodies-forms", version.ref = "jgoodies-forms" } +jna = { module = "net.java.dev.jna:jna", version.ref = "jna" } +jna-platform = { module = "net.java.dev.jna:jna-platform", version.ref = "jna" } +jSystemThemeDetector = { module = "com.github.Dansoftowner:jSystemThemeDetector", version.ref = "jSystemThemeDetector" } +versioncompare = { module = "io.github.g00fy2:versioncompare", version.ref = "versioncompare" } +jfa = { module = "de.jangassen:jfa", version.ref = "jfa" } +oshi-core = { module = "com.github.oshi:oshi-core", version.ref = "oshi" } +commons-io = { module = "commons-io:commons-io", version.ref = "commons-io" } +jbr-api = { module = "com.jetbrains:jbr-api", version.ref = "jbr-api" } +flatlaf-swingx = { module = "com.formdev:flatlaf-swingx", version.ref = "flatlaf" } +leveldb = { module = "org.iq80.leveldb:leveldb", version.ref = "leveldb" } +guava = { module = "com.google.guava:guava", version.ref = "guava" } +hutool = { module = "cn.hutool:hutool-all", version.ref = "hutool" } +credential-secure-storage = { module = "com.microsoft:credential-secure-storage", version.ref = "credential-secure-storage" } +jsch = { module = "com.github.mwiede:jsch", version.ref = "jsch" } +okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } +okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" } +bcprov = { module = "org.bouncycastle:bcprov-jdk18on", version.ref = "bcprov" } +sshj = { module = "com.hierynomus:sshj", version.ref = "sshj" } +sshd-core = { module = "org.apache.sshd:sshd-core", version.ref = "sshd-core" } +jgit = { module = "org.eclipse.jgit:org.eclipse.jgit", version.ref = "jgit" } +commonmark = { module = "org.commonmark:commonmark", version.ref = "commonmark" } +jgit-sshd = { module = "org.eclipse.jgit:org.eclipse.jgit.ssh.apache", version.ref = "jgit" } +xodus-openAPI = { module = "org.jetbrains.xodus:xodus-openAPI", version.ref = "xodus" } +xodus-entity-store = { module = "org.jetbrains.xodus:xodus-entity-store", version.ref = "xodus" } +xodus-environment = { module = "org.jetbrains.xodus:xodus-environment", version.ref = "xodus" } +xodus-crypto = { module = "org.jetbrains.xodus:xodus-crypto", version.ref = "xodus" } +xodus-vfs = { module = "org.jetbrains.xodus:xodus-vfs", version.ref = "xodus" } +jnafilechooser = { module = "com.github.steos.jnafilechooser:jnafilechooser-api", version.ref = "jnafilechooser" } +bip39 = { module = "cash.z.ecc.android:kotlin-bip39-jvm", version.ref = "bip39" } +rhino = { module = "org.mozilla:rhino", version.ref = "rhino" } +delight-rhino-sandbox = { module = "org.javadelight:delight-rhino-sandbox", version.ref = "delight-rhino-sandbox" } +colorpicker = { module = "org.drjekyll:colorpicker", version.ref = "colorpicker" } + +[plugins] +kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } +kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..249e5832f090a2944b7473328c07c9755baa3196 GIT binary patch literal 60756 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 '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..ac1b06f --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..d908e4b --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,5 @@ +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "0.9.0" +} +rootProject.name = "termora" + diff --git a/src/main/java/app/termora/Disposable.java b/src/main/java/app/termora/Disposable.java new file mode 100644 index 0000000..f4287db --- /dev/null +++ b/src/main/java/app/termora/Disposable.java @@ -0,0 +1,14 @@ +// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package app.termora; + +public interface Disposable { + + default void dispose() { + + } + + interface Parent extends Disposable { + void beforeTreeDispose(); + } + +} diff --git a/src/main/java/app/termora/Disposer.java b/src/main/java/app/termora/Disposer.java new file mode 100644 index 0000000..1727871 --- /dev/null +++ b/src/main/java/app/termora/Disposer.java @@ -0,0 +1,192 @@ +// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package app.termora; + +import org.jetbrains.annotations.*; + +import java.util.Collections; +import java.util.Map; +import java.util.WeakHashMap; +import java.util.function.Predicate; + +/** + *

Manages a parent-child relation of chained objects requiring cleanup.

+ * + *

A root node can be created via {@link #newDisposable()}, to which children are attached via subsequent calls to {@link #register(Disposable, Disposable)}. + * Invoking {@link #dispose(Disposable)} will process all its registered children's {@link Disposable#dispose()} method.

+ *

+ * See Disposer and Disposable in SDK Docs. + * + * @see Disposable + */ +public final class Disposer { + private static final ObjectTree ourTree = new ObjectTree(); + + public static boolean isDebugDisposerOn() { + return "on".equals(System.getProperty("idea.disposer.debug")); + } + + private static boolean ourDebugMode; + + private Disposer() { + } + + @NotNull + @Contract(pure = true, value = "->new") + public static Disposable newDisposable() { + // must not be lambda because we care about identity in ObjectTree.myObject2NodeMap + return newDisposable(""); + } + + @NotNull + @Contract(pure = true, value = "_->new") + public static Disposable newDisposable(@NotNull @NonNls String debugName) { + // must not be lambda because we care about identity in ObjectTree.myObject2NodeMap + return new Disposable() { + @Override + public void dispose() { + } + + @Override + public String toString() { + return debugName; + } + }; + } + + @Contract(pure = true, value = "_,_->new") + public static @NotNull Disposable newDisposable(@NotNull Disposable parentDisposable, @NotNull String debugName) { + Disposable result = newDisposable(debugName); + register(parentDisposable, result); + return result; + } + + private static final Map ourKeyDisposables = Collections.synchronizedMap(new WeakHashMap<>()); + + + public static void register(@NotNull Disposable parent, @NotNull Disposable child) { + RuntimeException e = ourTree.register(parent, child); + if (e != null) throw e; + } + + /** + * Registers child disposable under parent unless the parent has already been disposed + * + * @return whether the registration succeeded + */ + public static boolean tryRegister(@NotNull Disposable parent, @NotNull Disposable child) { + return ourTree.register(parent, child) == null; + } + + public static void register(@NotNull Disposable parent, @NotNull Disposable child, @NonNls @NotNull final String key) { + register(parent, child); + Disposable v = get(key); + if (v != null) throw new IllegalArgumentException("Key " + key + " already registered: " + v); + ourKeyDisposables.put(key, child); + register(child, new KeyDisposable(key)); + } + + private static final class KeyDisposable implements Disposable { + @NotNull + private final String myKey; + + KeyDisposable(@NotNull String key) { + myKey = key; + } + + @Override + public void dispose() { + ourKeyDisposables.remove(myKey); + } + + @Override + public String toString() { + return "KeyDisposable (" + myKey + ")"; + } + } + + /** + * @return true if {@code disposable} is disposed or being disposed (i.e. its {@link Disposable#dispose()} method is executing). + */ + public static boolean isDisposed(@NotNull Disposable disposable) { + return ourTree.getDisposalInfo(disposable) != null; + } + + /** + * @deprecated use {@link #isDisposed(Disposable)} instead + */ + @Deprecated + public static boolean isDisposing(@NotNull Disposable disposable) { + return isDisposed(disposable); + } + + public static Disposable get(@NotNull String key) { + return ourKeyDisposables.get(key); + } + + public static void dispose(@NotNull Disposable disposable) { + dispose(disposable, true); + } + + /** + * {@code predicate} is used only for direct children. + */ + @ApiStatus.Internal + public static void disposeChildren(@NotNull Disposable disposable, @Nullable Predicate predicate) { + ourTree.executeAllChildren(disposable, predicate); + } + + public static void dispose(@NotNull Disposable disposable, boolean processUnregistered) { + ourTree.executeAll(disposable, processUnregistered); + } + + @NotNull + public static ObjectTree getTree() { + return ourTree; + } + + public static void assertIsEmpty() { + assertIsEmpty(false); + } + + public static void assertIsEmpty(boolean throwError) { + if (ourDebugMode) { + ourTree.assertIsEmpty(throwError); + } + } + + /** + * @return old value + */ + public static boolean setDebugMode(boolean debugMode) { + if (debugMode) { + debugMode = !"off".equals(System.getProperty("idea.disposer.debug")); + } + boolean oldValue = ourDebugMode; + ourDebugMode = debugMode; + return oldValue; + } + + public static boolean isDebugMode() { + return ourDebugMode; + } + + /** + * @return object registered on {@code parentDisposable} which is equal to object, or {@code null} if not found + */ + @Nullable + public static T findRegisteredObject(@NotNull Disposable parentDisposable, @NotNull T object) { + return ourTree.findRegisteredObject(parentDisposable, object); + } + + public static Throwable getDisposalTrace(@NotNull Disposable disposable) { + if (getTree().getDisposalInfo(disposable) instanceof Throwable) { + return (Throwable) disposable; + } + return null; + } + + @ApiStatus.Internal + public static void clearDisposalTraces() { + ourTree.clearDisposedObjectTraces(); + } +} \ No newline at end of file diff --git a/src/main/java/app/termora/ObjectNode.java b/src/main/java/app/termora/ObjectNode.java new file mode 100644 index 0000000..ffb6441 --- /dev/null +++ b/src/main/java/app/termora/ObjectNode.java @@ -0,0 +1,128 @@ +// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package app.termora; + +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.TestOnly; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; + +final class ObjectNode { + private final ObjectTree myTree; + + ObjectNode myParent; // guarded by myTree.treeLock + private final Disposable myObject; + + private List myChildren; // guarded by myTree.treeLock + private Throwable myTrace; // guarded by myTree.treeLock + + ObjectNode(@NotNull ObjectTree tree, + @Nullable ObjectNode parentNode, + @NotNull Disposable object) { + myTree = tree; + myParent = parentNode; + myObject = object; + + } + + void addChild(@NotNull ObjectNode child) { + List children = myChildren; + if (children == null) { + myChildren = new ArrayList<>(); + myChildren.add(child); + } else { + children.add(child); + } + child.myParent = this; + } + + void removeChild(@NotNull ObjectNode child) { + List children = myChildren; + if (children != null) { + // optimisation: iterate backwards + for (int i = children.size() - 1; i >= 0; i--) { + ObjectNode node = children.get(i); + if (node.equals(child)) { + children.remove(i); + break; + } + } + } + child.myParent = null; + } + + ObjectNode getParent() { + return myParent; + } + + void getAndRemoveRecursively(@NotNull List result) { + getAndRemoveChildrenRecursively(result, null); + myTree.removeObjectFromTree(this); + // already disposed. may happen when someone does `register(obj, ()->Disposer.dispose(t));` abomination + if (myTree.rememberDisposedTrace(myObject) == null) { + result.add(myObject); + } + myChildren = null; + myParent = null; + } + + /** + * {@code predicate} is used only for direct children. + */ + void getAndRemoveChildrenRecursively(@NotNull List result, @Nullable Predicate predicate) { + if (myChildren != null) { + for (int i = myChildren.size() - 1; i >= 0; i--) { + ObjectNode childNode = myChildren.get(i); + if (predicate == null || predicate.test(childNode.getObject())) { + childNode.getAndRemoveRecursively(result); + } + } + } + } + + @NotNull + Disposable getObject() { + return myObject; + } + + @Override + @NonNls + public String toString() { + return "Node: " + myObject; + } + + Throwable getTrace() { + return myTrace; + } + + void clearTrace() { + myTrace = null; + } + + @TestOnly + void assertNoReferencesKept(@NotNull Disposable aDisposable) { + assert getObject() != aDisposable; + if (myChildren != null) { + for (ObjectNode node : myChildren) { + node.assertNoReferencesKept(aDisposable); + } + } + } + + D findChildEqualTo(@NotNull D object) { + List children = myChildren; + if (children != null) { + for (ObjectNode node : children) { + Disposable nodeObject = node.getObject(); + if (nodeObject.equals(object)) { + //noinspection unchecked + return (D) nodeObject; + } + } + } + return null; + } +} diff --git a/src/main/java/app/termora/ObjectTree.java b/src/main/java/app/termora/ObjectTree.java new file mode 100644 index 0000000..79104e9 --- /dev/null +++ b/src/main/java/app/termora/ObjectTree.java @@ -0,0 +1,246 @@ +// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package app.termora; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.TestOnly; + +import java.util.*; +import java.util.function.Predicate; +import java.util.function.Supplier; + +final class ObjectTree { + private static final ThreadLocal ourTopmostDisposeTrace = new ThreadLocal<>(); + + private final Set myRootObjects = new HashSet<>(); + // guarded by treeLock + private final Map myObject2NodeMap = new HashMap<>(); + // Disposable -> trace or boolean marker (if trace unavailable) + private final Map myDisposedObjects = new WeakHashMap<>(); // guarded by treeLock + + private final Object treeLock = new Object(); + + private ObjectNode getNode(@NotNull Disposable object) { + return myObject2NodeMap.get(object); + } + + private void putNode(@NotNull Disposable object, @Nullable("null means remove") ObjectNode node) { + if (node == null) { + myObject2NodeMap.remove(object); + } else { + myObject2NodeMap.put(object, node); + } + } + + @Nullable + final RuntimeException register(@NotNull Disposable parent, @NotNull Disposable child) { + if (parent == child) return new IllegalArgumentException("Cannot register to itself: " + parent); + synchronized (treeLock) { + Object wasDisposed = getDisposalInfo(parent); + if (wasDisposed != null) { + return new IllegalStateException("Sorry but parent: " + parent + " has already been disposed " + + "(see the cause for stacktrace) so the child: " + child + " will never be disposed", + wasDisposed instanceof Throwable ? (Throwable) wasDisposed : null); + } + + myDisposedObjects.remove(child); // if we dispose thing and then register it back it means it's not disposed anymore + ObjectNode parentNode = getNode(parent); + if (parentNode == null) parentNode = createNodeFor(parent, null); + + ObjectNode childNode = getNode(child); + if (childNode == null) { + childNode = createNodeFor(child, parentNode); + } else { + ObjectNode oldParent = childNode.getParent(); + if (oldParent != null) { + oldParent.removeChild(childNode); + } + } + myRootObjects.remove(child); + + RuntimeException e = checkWasNotAddedAlready(parentNode, childNode); + if (e != null) return e; + + parentNode.addChild(childNode); + } + return null; + } + + Object getDisposalInfo(@NotNull Disposable object) { + synchronized (treeLock) { + return myDisposedObjects.get(object); + } + } + + private static RuntimeException checkWasNotAddedAlready(@NotNull ObjectNode childNode, @NotNull ObjectNode parentNode) { + for (ObjectNode node = childNode; node != null; node = node.getParent()) { + if (node == parentNode) { + return new IllegalStateException("'" + childNode.getObject() + "' was already added as a child of '" + parentNode.getObject() + "'"); + } + } + return null; + } + + @NotNull + private ObjectNode createNodeFor(@NotNull Disposable object, @Nullable ObjectNode parentNode) { + final ObjectNode newNode = new ObjectNode(this, parentNode, object); + if (parentNode == null) { + myRootObjects.add(object); + } + putNode(object, newNode); + return newNode; + } + + private void runWithTrace(@NotNull Supplier> removeFromTreeAction) { + boolean needTrace = Disposer.isDebugMode() && ourTopmostDisposeTrace.get() == null; + if (needTrace) { + ourTopmostDisposeTrace.set(new Throwable()); + } + + // first, atomically remove disposables from the tree to avoid "register during dispose" race conditions + List disposables; + synchronized (treeLock) { + disposables = removeFromTreeAction.get(); + } + + // second, call "beforeTreeDispose" in pre-order (some clients are hardcoded to see parents-then-children order in "beforeTreeDispose") + List exceptions = null; + for (int i = disposables.size() - 1; i >= 0; i--) { + Disposable disposable = disposables.get(i); + if (disposable instanceof Disposable.Parent) { + try { + ((Disposable.Parent) disposable).beforeTreeDispose(); + } catch (Throwable t) { + if (exceptions == null) exceptions = new ArrayList<>(); + exceptions.add(t); + } + } + } + + // third, dispose in post-order (bottom-up) + for (Disposable disposable : disposables) { + try { + //noinspection SSBasedInspection + disposable.dispose(); + } catch (Throwable e) { + if (exceptions == null) exceptions = new ArrayList<>(); + exceptions.add(e); + } + } + + if (needTrace) { + ourTopmostDisposeTrace.remove(); + } + if (exceptions != null) { + handleExceptions(exceptions); + } + } + + void executeAllChildren(@NotNull Disposable object, @Nullable Predicate predicate) { + runWithTrace(() -> { + ObjectNode node = getNode(object); + if (node == null) { + return Collections.emptyList(); + } + + List disposables = new ArrayList<>(); + node.getAndRemoveChildrenRecursively(disposables, predicate); + return disposables; + }); + } + + void executeAll(@NotNull Disposable object, boolean processUnregistered) { + runWithTrace(() -> { + ObjectNode node = getNode(object); + if (node == null && !processUnregistered) { + return Collections.emptyList(); + } + List disposables = new ArrayList<>(); + if (node == null) { + if (rememberDisposedTrace(object) == null) { + disposables.add(object); + } + } else { + node.getAndRemoveRecursively(disposables); + } + return disposables; + }); + } + + private static void handleExceptions(@NotNull List exceptions) { + + } + + @TestOnly + void assertNoReferenceKeptInTree(@NotNull Disposable disposable) { + synchronized (treeLock) { + for (Map.Entry entry : myObject2NodeMap.entrySet()) { + Disposable key = entry.getKey(); + assert key != disposable; + ObjectNode node = entry.getValue(); + node.assertNoReferencesKept(disposable); + } + } + } + + void assertIsEmpty(boolean throwError) { + synchronized (treeLock) { + for (Disposable object : myRootObjects) { + if (object == null) continue; + ObjectNode objectNode = getNode(object); + if (objectNode == null) continue; + while (objectNode.getParent() != null) { + objectNode = objectNode.getParent(); + } + final Throwable trace = objectNode.getTrace(); + RuntimeException exception = new RuntimeException("Memory leak detected: '" + object + "' of " + object.getClass() + + "\nSee the cause for the corresponding Disposer.register() stacktrace:\n", + trace); + if (throwError) { + throw exception; + } + } + } + } + + + // return old value + Object rememberDisposedTrace(@NotNull Disposable object) { + synchronized (treeLock) { + Throwable trace = ourTopmostDisposeTrace.get(); + return myDisposedObjects.put(object, trace != null ? trace : Boolean.TRUE); + } + } + + void clearDisposedObjectTraces() { + synchronized (treeLock) { + myDisposedObjects.clear(); + for (ObjectNode value : myObject2NodeMap.values()) { + value.clearTrace(); + } + } + } + + @Nullable + D findRegisteredObject(@NotNull Disposable parentDisposable, @NotNull D object) { + synchronized (treeLock) { + ObjectNode parentNode = getNode(parentDisposable); + if (parentNode == null) return null; + return parentNode.findChildEqualTo(object); + } + } + + void removeObjectFromTree(@NotNull ObjectNode node) { + synchronized (treeLock) { + Disposable myObject = node.getObject(); + putNode(myObject, null); + ObjectNode parent = node.getParent(); + if (parent == null) { + myRootObjects.remove(myObject); + } else { + parent.removeChild(node); + } + node.myParent = null; + } + } +} diff --git a/src/main/java/zmodem/FileCopyStreamEvent.kt b/src/main/java/zmodem/FileCopyStreamEvent.kt new file mode 100644 index 0000000..0dfc092 --- /dev/null +++ b/src/main/java/zmodem/FileCopyStreamEvent.kt @@ -0,0 +1,34 @@ +package zmodem + +import org.apache.commons.net.io.CopyStreamEvent + +/** + * 如果一共两个文件,并且传输第一个文件时: + * + * remaining = 1 + * index = 1 + */ +class FileCopyStreamEvent( + source: Any, + // 本次传输的文件名 + val filename: String, + // 剩余未传输的文件数量 + val remaining: Int, + // 第几个文件 + val index: Int, + // 总字节数 + totalBytesTransferred: Long, + // 已经传输完成的字节数 + bytesTransferred: Int, + // 本次传输的字节数 + streamSize: Long, + /** + * 这个文件被跳过了 + */ + val skip: Boolean = false, +) : + CopyStreamEvent( + source, totalBytesTransferred, + bytesTransferred, + streamSize + ) \ No newline at end of file diff --git a/src/main/java/zmodem/XModem.java b/src/main/java/zmodem/XModem.java new file mode 100644 index 0000000..e3acd6f --- /dev/null +++ b/src/main/java/zmodem/XModem.java @@ -0,0 +1,24 @@ +package zmodem; + +import zmodem.xfer.zm.util.Modem; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Path; + +public class XModem { + private Modem modem; + + public XModem(InputStream inputStream, OutputStream outputStream) { + this.modem = new Modem(inputStream, outputStream); + } + + public void send(Path file, boolean useBlock1K) throws IOException, InterruptedException { + modem.send(file, useBlock1K); + } + + public void receive(Path file) throws IOException { + modem.receive(file, false); + } +} diff --git a/src/main/java/zmodem/YModem.java b/src/main/java/zmodem/YModem.java new file mode 100644 index 0000000..c65788d --- /dev/null +++ b/src/main/java/zmodem/YModem.java @@ -0,0 +1,207 @@ +package zmodem; + +import zmodem.xfer.util.CRC16; +import zmodem.xfer.util.CRC8; +import zmodem.xfer.util.XCRC; +import zmodem.xfer.zm.util.Modem; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Arrays; + +/** + * YModem.
+ * Block 0 contain minimal file information (only filename)
+ *

+ * Created by Anton Sirotinkin (aesirot@mail.ru), Moscow 2014
+ * I hope you will find this program useful.
+ * You are free to use/modify the code for any purpose, but please leave a reference to me.
+ *
+ */ +public class YModem { + private Modem modem; + + /** + * Constructor + * + * @param inputStream stream for reading received data from other side + * @param outputStream stream for writing data to other side + */ + public YModem(InputStream inputStream, OutputStream outputStream) { + this.modem = new Modem(inputStream, outputStream); + } + + /** + * Send a file.
+ *

+ * This method support correct thread interruption, when thread is interrupted "cancel of transmission" will be send. + * So you can move long transmission to other thread and interrupt it according to your algorithm. + * + * @param file + * @throws IOException + */ + public void send(Path file) throws IOException { + //check filename +// if (!file.getFileName().toString().matches("\\w{1,8}\\.\\w{1,3}")) { +// throw new IOException("Filename must be in DOS style (no spaces, max 8.3)"); +// } + + //open file + try (DataInputStream dataStream = new DataInputStream(Files.newInputStream(file))) { + + boolean useCRC16 = modem.waitReceiverRequest(); + XCRC crc; + if (useCRC16) + crc = new CRC16(); + else + crc = new CRC8(); + + //send block 0 + BasicFileAttributes readAttributes = Files.readAttributes(file, BasicFileAttributes.class); + String fileNameString = file.getFileName().toString() + (char) 0 + ((Long) Files.size(file)).toString() + " " + Long.toOctalString(readAttributes.lastModifiedTime().toMillis() / 1000); + byte[] fileNameBytes = Arrays.copyOf(fileNameString.getBytes(), 128); + modem.sendBlock(0, Arrays.copyOf(fileNameBytes, 128), 128, crc); + + modem.waitReceiverRequest(); + //send data + byte[] block = new byte[1024]; + modem.sendDataBlocks(dataStream, 1, crc, block); + + modem.sendEOT(); + } + } + + /** + * Send files in batch mode.
+ *

+ * This method support correct thread interruption, when thread is interrupted "cancel of transmission" will be send. + * So you can move long transmission to other thread and interrupt it according to your algorithm. + * + * @param files + * @throws IOException + */ + public void batchSend(Path... files) throws IOException { + for (Path file : files) { + send(file); + } + + sendBatchStop(); + } + + private void sendBatchStop() throws IOException { + boolean useCRC16 = modem.waitReceiverRequest(); + XCRC crc; + if (useCRC16) + crc = new CRC16(); + else + crc = new CRC8(); + + //send block 0 + byte[] bytes = new byte[128]; + modem.sendBlock(0, bytes, bytes.length, crc); + } + + /** + * Receive single file
+ *

+ * This method support correct thread interruption, when thread is interrupted "cancel of transmission" will be send. + * So you can move long transmission to other thread and interrupt it according to your algorithm. + * + * @param directory directory where file will be saved + * @return path to created file + * @throws IOException + */ + public Path receiveSingleFileInDirectory(Path directory) throws IOException { + return receive(directory, true); + } + + /** + * Receive files in batch mode
+ *

+ * This method support correct thread interruption, when thread is interrupted "cancel of transmission" will be send. + * So you can move long transmission to other thread and interrupt it according to your algorithm. + * + * @param directory directory where files will be saved + * @throws IOException + */ + public void receiveFilesInDirectory(Path directory) throws IOException { + while (receive(directory, true) != null) { + } + } + + /** + * Receive path
+ *

+ * This method support correct thread interruption, when thread is interrupted "cancel of transmission" will be send. + * So you can move long transmission to other thread and interrupt it according to your algorithm. + * + * @param path path to file where data will be saved + * @return path to file + * @throws IOException + */ + public Path receive(Path path) throws IOException { + return receive(path, false); + } + + private Path receive(Path path, boolean inDirectory) throws IOException { + DataOutputStream dataOutput = null; + Path filePath; + try { + XCRC crc = new CRC16(); + int errorCount = 0; + + // process block 0 + byte[] block; + int character; + while (true) { + character = modem.requestTransmissionStart(true); + try { + // read file name from zero block + block = modem.readBlock(0, (character == Modem.SOH), crc); + + if (inDirectory) { + StringBuilder sb = new StringBuilder(); + if (block[0] == 0) { + //this is stop block of batch file transfer + modem.sendByte(Modem.ACK); + return null; + } + for (int i = 0; i < block.length; i++) { + if (block[i] == 0) { + break; + } + sb.append((char) block[i]); + } + filePath = path.resolve(sb.toString()); + } else { + filePath = path; + } + dataOutput = new DataOutputStream(Files.newOutputStream(filePath)); + modem.sendByte(Modem.ACK); + break; + } catch (Modem.InvalidBlockException e) { + errorCount++; + if (errorCount == Modem.MAXERRORS) { + modem.interruptTransmission(); + throw new IOException("Transmission aborted, error count exceeded max"); + } + modem.sendByte(Modem.NAK); + } catch (Modem.RepeatedBlockException | Modem.SynchronizationLostException e) { + //fatal transmission error + modem.interruptTransmission(); + throw new IOException("Fatal transmission error", e); + } + } + + //receive data blocks + modem.receive(filePath, true); + } finally { + if (dataOutput != null) { + dataOutput.close(); + } + } + return filePath; + } +} diff --git a/src/main/java/zmodem/ZModem.java b/src/main/java/zmodem/ZModem.java new file mode 100644 index 0000000..49335cc --- /dev/null +++ b/src/main/java/zmodem/ZModem.java @@ -0,0 +1,46 @@ +package zmodem; + + +import org.apache.commons.net.io.CopyStreamListener; +import zmodem.util.FileAdapter; +import zmodem.xfer.zm.util.ZModemReceive; +import zmodem.xfer.zm.util.ZModemSend; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Supplier; + + +public class ZModem { + + private final InputStream netIs; + private final OutputStream netOs; + private final AtomicBoolean isCancelled = new AtomicBoolean(false); + + + public ZModem(InputStream netin, OutputStream netout) { + netIs = netin; + netOs = netout; + } + + public void receive(Supplier destDir, CopyStreamListener listener) throws IOException { + ZModemReceive sender = new ZModemReceive(destDir, netIs, netOs); + sender.addCopyStreamListener(listener); + sender.receive(isCancelled::get); + netOs.flush(); + } + + public void send(Supplier> filesSupplier, CopyStreamListener listener) throws IOException { + ZModemSend sender = new ZModemSend(filesSupplier, netIs, netOs); + sender.addCopyStreamListener(listener); + sender.send(isCancelled::get); + netOs.flush(); + } + + public void cancel() { + isCancelled.compareAndSet(false, true); + } +} diff --git a/src/main/java/zmodem/package-info.java b/src/main/java/zmodem/package-info.java new file mode 100644 index 0000000..2ecbc76 --- /dev/null +++ b/src/main/java/zmodem/package-info.java @@ -0,0 +1,4 @@ +/** + * https://github.com/scraymer/Zmodem-in-Java + */ +package zmodem; \ No newline at end of file diff --git a/src/main/java/zmodem/util/CustomFile.java b/src/main/java/zmodem/util/CustomFile.java new file mode 100644 index 0000000..8cc7d0f --- /dev/null +++ b/src/main/java/zmodem/util/CustomFile.java @@ -0,0 +1,65 @@ +package zmodem.util; + +import org.apache.commons.io.input.RandomAccessFileInputStream; + +import java.io.*; + +public class CustomFile implements FileAdapter { + File file = null; + + public CustomFile(File file) { + super(); + this.file = file; + } + + @Override + public String getName() { + return file.getName(); + } + + @Override + public InputStream getInputStream() throws IOException { + return RandomAccessFileInputStream.builder() + .setCloseOnClose(true) + .setRandomAccessFile(new RandomAccessFile(file, "r")) + .setBufferSize(1024 * 8) + .get(); + } + + @Override + public OutputStream getOutputStream() throws IOException { + return getOutputStream(false); + } + + @Override + public OutputStream getOutputStream(boolean append) throws IOException { + return new BufferedOutputStream(new FileOutputStream(file, append)); + } + + @Override + public FileAdapter getChild(String name) { + if (name.equals(file.getName())) { + return this; + } else if (file.isDirectory()) { + return new CustomFile(new File(file.getAbsolutePath(), name)); + } + return null; + + } + + @Override + public long length() { + return file.length(); + } + + @Override + public boolean isDirectory() { + return file.isDirectory(); + } + + @Override + public boolean exists() { + return file.exists(); + } + +} diff --git a/src/main/java/zmodem/util/EmptyFileAdapter.kt b/src/main/java/zmodem/util/EmptyFileAdapter.kt new file mode 100644 index 0000000..3d76fb9 --- /dev/null +++ b/src/main/java/zmodem/util/EmptyFileAdapter.kt @@ -0,0 +1,42 @@ +package zmodem.util + +import java.io.InputStream +import java.io.OutputStream + +class EmptyFileAdapter private constructor() : FileAdapter { + companion object { + val instance by lazy { EmptyFileAdapter() } + } + + override fun getName(): String { + TODO("Not yet implemented") + } + + override fun getInputStream(): InputStream { + TODO("Not yet implemented") + } + + override fun getOutputStream(): OutputStream { + TODO("Not yet implemented") + } + + override fun getOutputStream(append: Boolean): OutputStream { + TODO("Not yet implemented") + } + + override fun getChild(name: String?): FileAdapter { + TODO("Not yet implemented") + } + + override fun length(): Long { + TODO("Not yet implemented") + } + + override fun isDirectory(): Boolean { + return true + } + + override fun exists(): Boolean { + return true + } +} \ No newline at end of file diff --git a/src/main/java/zmodem/util/FileAdapter.java b/src/main/java/zmodem/util/FileAdapter.java new file mode 100644 index 0000000..f00e60b --- /dev/null +++ b/src/main/java/zmodem/util/FileAdapter.java @@ -0,0 +1,25 @@ +package zmodem.util; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public interface FileAdapter { + public String getName(); + + public InputStream getInputStream() throws IOException; + + public OutputStream getOutputStream() throws IOException; + + public OutputStream getOutputStream(boolean append) throws IOException; + + public FileAdapter getChild(String name); + + public long length(); + + public boolean isDirectory(); + + public boolean exists(); + + public String toString(); +} diff --git a/src/main/java/zmodem/xfer/io/ObjectInputStream.java b/src/main/java/zmodem/xfer/io/ObjectInputStream.java new file mode 100644 index 0000000..374ab85 --- /dev/null +++ b/src/main/java/zmodem/xfer/io/ObjectInputStream.java @@ -0,0 +1,7 @@ +package zmodem.xfer.io; + +import java.io.IOException; + +public abstract class ObjectInputStream { + public abstract T read() throws IOException; +} diff --git a/src/main/java/zmodem/xfer/io/ObjectOutputStream.java b/src/main/java/zmodem/xfer/io/ObjectOutputStream.java new file mode 100644 index 0000000..5358fd9 --- /dev/null +++ b/src/main/java/zmodem/xfer/io/ObjectOutputStream.java @@ -0,0 +1,7 @@ +package zmodem.xfer.io; + +import java.io.IOException; + +public abstract class ObjectOutputStream { + public abstract void write(T o) throws IOException; +} diff --git a/src/main/java/zmodem/xfer/util/ASCII.java b/src/main/java/zmodem/xfer/util/ASCII.java new file mode 100644 index 0000000..aca91bd --- /dev/null +++ b/src/main/java/zmodem/xfer/util/ASCII.java @@ -0,0 +1,27 @@ +package zmodem.xfer.util; + +public enum ASCII { + + SOH((byte) 0x01), + STX((byte) 0x02), + EOT((byte) 0x04), + ENQ((byte) 0x05), + ACK((byte) 0x06), + BS((byte) 0x08), + LF((byte) 0x0a), + CR((byte) 0x0d), + XON((byte) 0x11), + XOFF((byte) 0x13), + NAK((byte) 0x15), + CAN((byte) 0x18); + + private byte value; + + private ASCII(byte b) { + value = b; + } + + public byte value() { + return value; + } +} diff --git a/src/main/java/zmodem/xfer/util/Arrays.java b/src/main/java/zmodem/xfer/util/Arrays.java new file mode 100644 index 0000000..e35b50a --- /dev/null +++ b/src/main/java/zmodem/xfer/util/Arrays.java @@ -0,0 +1,91 @@ +package zmodem.xfer.util; + +/** + * copyOf is not in java 5 java.util.Array + * + * @author justin + */ +public class Arrays { + public enum Endianness {Little, Big;} + + public static byte[] copyOf(byte[] original, int newLength) { + byte[] copy = new byte[newLength]; + System.arraycopy(original, 0, copy, 0, + Math.min(original.length, newLength)); + return copy; + } + + public static boolean equals(byte[] a, byte[] a2) { + return java.util.Arrays.equals(a, a2); + } + + public static long toInteger(byte[] array, int size, Endianness endian) { + long n = 0; + int offset = 0, increment = 1; + switch (endian) { + case Little: + increment = 1; + offset = 0; + break; + case Big: + increment = -1; + offset = size - 1; + break; + + } + + for (int i = 0; i < size; i++) { + n += (0xff & array[offset]) * (0x1 << i * 8); + offset += increment; + } + + return n; + } + + public static short toShort(byte[] array, Endianness endian) { + return (short) toInteger(array, 2, endian); + } + + public static int toInt(byte[] array, Endianness endian) { + return (int) toInteger(array, 4, endian); + } + + public static long toLong(byte[] array, Endianness endian) { + return toInteger(array, 8, endian); + } + + public static byte[] fromInteger(long n, int size, Endianness endian) { + byte[] ret = new byte[size]; + int offset = 0, increment = 1; + + switch (endian) { + case Big: + increment = -1; + offset = size - 1; + break; + case Little: + increment = 1; + offset = 0; + break; + } + + for (int i = 0; i < size; i++) { + ret[offset] = (byte) ((n >> (i * 8)) & 0xFF); + offset += increment; + } + + return ret; + } + + public static byte[] fromShort(short s, Endianness endian) { + return fromInteger(s, 2, endian); + } + + public static byte[] fromInt(int i, Endianness endian) { + return fromInteger(i, 4, endian); + } + + public static byte[] fromLong(long i, Endianness endian) { + return fromInteger(i, 8, endian); + } +} diff --git a/src/main/java/zmodem/xfer/util/Buffer.java b/src/main/java/zmodem/xfer/util/Buffer.java new file mode 100644 index 0000000..551504f --- /dev/null +++ b/src/main/java/zmodem/xfer/util/Buffer.java @@ -0,0 +1,38 @@ +package zmodem.xfer.util; + +public interface Buffer { + public byte get(); + + public Buffer get(byte[] dst, int offset, int len); + + public Buffer get(byte[] dst); + + public Buffer put(byte b); + + public Buffer put(byte[] dst, int offset, int len); + + public Buffer put(byte[] dst); + + public byte get(int index); + + public Buffer get(int index, byte[] dst, int offset, int len); + + public Buffer get(int index, byte[] dst); + + public Buffer put(int index, byte b); + + public Buffer put(int index, byte[] dst, int offset, int len); + + public Buffer put(int index, byte[] dst); + + public void flip(); + + public int remaining(); + + public boolean hasRemaining(); + + public HexBuffer asHexBuffer(); + + public ByteBuffer asByteBuffer(); + +} diff --git a/src/main/java/zmodem/xfer/util/ByteBuffer.java b/src/main/java/zmodem/xfer/util/ByteBuffer.java new file mode 100644 index 0000000..3af58d9 --- /dev/null +++ b/src/main/java/zmodem/xfer/util/ByteBuffer.java @@ -0,0 +1,193 @@ +package zmodem.xfer.util; + + +public class ByteBuffer implements Buffer { + + private java.nio.ByteBuffer _wrapped; + + public ByteBuffer(java.nio.ByteBuffer b) { + _wrapped = b; + } + + public static ByteBuffer allocate(int capacity) { + return new ByteBuffer(java.nio.ByteBuffer.allocate(capacity)); + } + + public static ByteBuffer allocateDirect(int capacity) { + return new ByteBuffer(java.nio.ByteBuffer.allocateDirect(capacity)); + } + + public Buffer slice() { + return new ByteBuffer(_wrapped.slice()); + } + + public Buffer duplicate() { + return new ByteBuffer(_wrapped.duplicate()); + } + + public Buffer asReadOnlyBuffer() { + return new ByteBuffer(_wrapped.asReadOnlyBuffer()); + } + + public byte get() { + return _wrapped.get(); + } + + public Buffer get(byte[] dst, int offset, int len) { + for (; offset < len; offset++) + dst[offset] = get(); + + return this; + } + + public Buffer get(byte[] dst) { + return get(dst, 0, dst.length); + } + + public Buffer put(byte b) { + _wrapped.put(b); + return this; + } + + public Buffer put(byte[] dst, int offset, int len) { + for (; offset < len; offset++) + put(dst[offset]); + + return this; + } + + public Buffer put(byte[] dst) { + return put(dst, 0, dst.length); + } + + public byte get(int index) { + return _wrapped.get(index); + } + + public Buffer get(int index, byte[] dst, int offset, int len) { + for (; offset < len; offset++) + dst[offset] = get(index++); + + return this; + } + + public Buffer get(int index, byte[] dst) { + return get(index, dst, 0, dst.length); + } + + public Buffer put(int index, byte b) { + _wrapped.put(index, b); + return this; + } + + public Buffer put(int index, byte[] dst, int offset, int len) { + for (; offset < len; offset++) + put(index++, dst[offset]); + + return this; + } + + public Buffer put(int index, byte[] dst) { + return put(index, dst, 0, dst.length); + } + + public Buffer compact() { + _wrapped.compact(); + return this; + } + + public boolean isDirect() { + return _wrapped.isDirect(); + } + + public char getChar() { + return (char) get(); + } + + public Buffer putChar(char value) { + return put((byte) value); + } + + public char getChar(int index) { + return (char) get(index); + } + + public Buffer putChar(int index, char value) { + return put(index, (byte) value); + } + + public HexBuffer asHexBuffer() { + return new HexBuffer(_wrapped); + } + + public short getShort() { + return Arrays.toShort(new byte[]{get(), get()}, Arrays.Endianness.Little); + } + + public Buffer putShort(short value) { + return put(Arrays.fromShort(value, Arrays.Endianness.Little)); + } + + public short getShort(int index) { + return Arrays.toShort(new byte[]{get(index), get(index + 1)}, Arrays.Endianness.Little); + } + + public Buffer putShort(int index, short value) { + return put(index, Arrays.fromShort(value, Arrays.Endianness.Little)); + } + + public int getInt() { + return Arrays.toInt(new byte[]{get(), get(), get(), get()}, Arrays.Endianness.Little); + } + + public Buffer putInt(int value) { + return put(Arrays.fromInt(value, Arrays.Endianness.Little)); + } + + public int getInt(int index) { + return Arrays.toInt(new byte[]{get(index), get(index + 1), get(index + 2), get(index + 3)}, Arrays.Endianness.Little); + } + + public Buffer putInt(int index, int value) { + return put(index, Arrays.fromInt(value, Arrays.Endianness.Little)); + } + + public long getLong() { + return Arrays.toLong(new byte[]{get(), get(), get(), get(), get(), get(), get(), get()}, Arrays.Endianness.Little); + } + + public Buffer putLong(long value) { + return put(Arrays.fromLong(value, Arrays.Endianness.Little)); + } + + public long getLong(int index) { + return Arrays.toLong(new byte[]{get(index), get(index + 1), get(index + 2), get(index + 3), get(index + 4), get(index + 5), get(index + 6), get(index + 7)}, Arrays.Endianness.Little); + } + + public Buffer putLong(int index, long value) { + return put(index, Arrays.fromLong(value, Arrays.Endianness.Little)); + } + + public boolean isReadOnly() { + return _wrapped.isReadOnly(); + } + + public void flip() { + _wrapped.flip(); + } + + public int remaining() { + return _wrapped.remaining(); + } + + public boolean hasRemaining() { + return remaining() > 0; + } + + public ByteBuffer asByteBuffer() { + return this; + } + +} + + diff --git a/src/main/java/zmodem/xfer/util/CRC.java b/src/main/java/zmodem/xfer/util/CRC.java new file mode 100644 index 0000000..99b7f2b --- /dev/null +++ b/src/main/java/zmodem/xfer/util/CRC.java @@ -0,0 +1,232 @@ +package zmodem.xfer.util; + + +public class CRC { + + public static enum Type { + CRC16(2, 0), + CRC32(4, 0xffffffff); + private int numbytes; + private int initial; + + private Type(int s, int i) { + numbytes = s; + initial = i; + } + + public int size() { + return numbytes; + } + + public int initial() { + return initial; + } + } + + /** + * Copied from the original zmodem source + */ + private static final int crctab[] = { + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, + 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, + 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, + 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, + 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, + 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, + 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, + 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, + 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, + 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, + 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, + 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, + 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, + 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, + 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, + 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, + 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, + 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, + 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, + 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, + 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, + 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, + 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 + }; + /** + * Copied from the original zmodem source + */ + private static final int cr3tab[] = { /* CRC polynomial 0xedb88320 */ + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, + 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, + 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, + 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, + 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, + 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, + 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, + 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, + 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, + 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, + 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, + 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, + 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, + 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, + 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, + 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, + 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, + 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, + 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, + 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, + 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, + 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d + }; + + private static int updcrc(int cp, int crc) { + //System.out.printf("updcrc: %08x / %08x\n",cp,crc); + return (crctab[(crc >> 8) & 0xff] ^ (crc << 8) ^ cp); + } + + private static int updcrc32(int b, int c) { + return (cr3tab[(c ^ b) & 0xff] ^ ((c >> 8) & 0x00FFFFFF)); + } + + + public static byte[] arrayCRC(Type t, byte[] bytes) { + switch (t) { + case CRC16: + return arrayCRC16(bytes); + case CRC32: + return arrayCRC32(bytes); + } + return new byte[0]; + } + + /** + * @param bytes + * @return + */ + public static byte[] arrayCRC16(byte[] bytes) { + byte[] bb = new byte[2]; + int value = CRC16(bytes); + bb[1] = (byte) (value & 0xFF); + bb[0] = (byte) ((value >> 8) & 0xFF); + return bb; + + } + + public static int CRC16(byte[] bytes) { + int crc = 0; + + for (byte b : bytes) + crc = updcrc(0xff & b, crc); + + crc = updcrc(0, updcrc(0, crc)); + + return crc; + } + + /** + * @param bytes + * @return + */ + public static byte[] arrayCRC32(byte[] bytes) { + byte[] bb = new byte[4]; + long value = CRC32(bytes); + bb[3] = (byte) (value & 0xFF); + bb[2] = (byte) ((value >> 8) & 0xFF); + bb[1] = (byte) ((value >> 16) & 0xFF); + bb[0] = (byte) ((value >> 24) & 0xFF); + return bb; + + } + + public static int CRC32(byte[] bytes) { + int crc = 0xFFFFFFFF; + + for (byte b : bytes) + crc = updcrc32(b, crc); + + return ~crc; + } + + private Type type; + private int crc; + + public CRC(Type t) { + type = t; + crc = type.initial(); + } + + public void update(byte b) { + switch (type) { + case CRC16: + crc = updcrc(0xff & b, crc); + break; + case CRC32: + crc = updcrc32((0xff & b), crc); + break; + } + } + + public void update(byte[] array) { + for (byte b : array) + update(b); + } + + public void finalized() { + switch (type) { + case CRC16: + crc = 0xffff & updcrc(0, updcrc(0, crc)); + break; + case CRC32: + crc = ~crc; + break; + } + //System.out.printf("crc: %08x\n",crc); + } + + public int size() { + return type.size(); + } + + public byte[] getBytes() { + byte[] bb; + switch (type) { + case CRC16: + bb = new byte[2]; + bb[1] = (byte) (crc & 0xFF); + bb[0] = (byte) ((crc >> 8) & 0xFF); + return bb; + case CRC32: + bb = new byte[4]; + bb[3] = (byte) (crc & 0xFF); + bb[2] = (byte) ((crc >> 8) & 0xFF); + bb[1] = (byte) ((crc >> 16) & 0xFF); + bb[0] = (byte) ((crc >> 24) & 0xFF); + return bb; + } + return new byte[0]; + } + + public Type type() { + return type; + } + +} diff --git a/src/main/java/zmodem/xfer/util/CRC16.java b/src/main/java/zmodem/xfer/util/CRC16.java new file mode 100644 index 0000000..bb2ffed --- /dev/null +++ b/src/main/java/zmodem/xfer/util/CRC16.java @@ -0,0 +1,58 @@ +package zmodem.xfer.util; + +/** + * Uses table for irreducible polynomial: 1 + x^2 + x^15 + x^16 + */ + +public class CRC16 implements XCRC { + + private static int[] table = { + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, + 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, + 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, + 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, + 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, + 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, + 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, + 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, + 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, + 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, + 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, + 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, + 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, + 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, + 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, + 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, + 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, + 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, + 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, + 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, + 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, + 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, + 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0, + }; + + @Override + public int getCRCLength() { + return 2; + } + + @Override + public long calcCRC(byte[] block) { + int crc = 0x0000; + for (byte b : block) { + crc = ((crc << 8) ^ table[((crc >> 8) ^ (0xff & b))]) & 0xFFFF; + } + + return crc; + } +} diff --git a/src/main/java/zmodem/xfer/util/CRC8.java b/src/main/java/zmodem/xfer/util/CRC8.java new file mode 100644 index 0000000..a05b9f2 --- /dev/null +++ b/src/main/java/zmodem/xfer/util/CRC8.java @@ -0,0 +1,21 @@ +package zmodem.xfer.util; + +/** + * Created by asirotinkin on 11.11.2014. + */ +public class CRC8 implements XCRC { + @Override + public int getCRCLength() { + return 1; + } + + @Override + public long calcCRC(byte[] block) { + byte checkSumma = 0; + for (int i = 0; i < block.length; i++) { + checkSumma += block[i]; + } + return checkSumma; + } + +} diff --git a/src/main/java/zmodem/xfer/util/HexBuffer.java b/src/main/java/zmodem/xfer/util/HexBuffer.java new file mode 100644 index 0000000..6b17620 --- /dev/null +++ b/src/main/java/zmodem/xfer/util/HexBuffer.java @@ -0,0 +1,235 @@ +package zmodem.xfer.util; + + +import zmodem.xfer.util.Arrays.Endianness; + +public class HexBuffer implements Buffer { + + private static final byte[] hx = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + + public static byte[] binToHex(byte[] bin) { + byte[] hex = new byte[bin.length * 2]; + for (int i = 0; i < bin.length; i++) + System.arraycopy(toHex(bin[i]), 0, hex, i * 2, 2); + + return hex; + } + + public static byte[] hexToBin(byte[] hex) { + byte[] bin = new byte[hex.length / 2]; + for (int i = 0; i < bin.length; i++) { + byte[] bn = new byte[2]; + System.arraycopy(hex, i * 2, bn, 0, 2); + bin[i] = toByte(bn); + } + + return bin; + } + + private static byte toByte(byte[] array) { + int d; + + d = java.util.Arrays.binarySearch(hx, array[0]) * 16; + d += java.util.Arrays.binarySearch(hx, array[1]); + + return (byte) d; + } + + private static byte[] toHex(byte b) { + byte[] array = new byte[2]; + + array[0] = hx[((b >> 4) & 0xF)]; + array[1] = hx[(b & 0xF)]; + + return array; + } + + private java.nio.ByteBuffer _wrapped; + + protected HexBuffer(java.nio.ByteBuffer b) { + _wrapped = b; + } + + public static HexBuffer allocate(int capacity) { + return new HexBuffer(java.nio.ByteBuffer.allocate(capacity * 2)); + } + + public static HexBuffer allocateDirect(int capacity) { + return new HexBuffer(java.nio.ByteBuffer.allocateDirect(capacity * 2)); + } + + public Buffer slice() { + return new HexBuffer(_wrapped.slice()); + } + + public Buffer duplicate() { + return new HexBuffer(_wrapped.duplicate()); + } + + public Buffer asReadOnlyBuffer() { + return new HexBuffer(_wrapped.asReadOnlyBuffer()); + } + + public byte get() { + return toByte(new byte[]{_wrapped.get(), _wrapped.get()}); + } + + public Buffer get(byte[] dst, int offset, int len) { + for (; offset < len; offset++) + dst[offset] = get(); + + return this; + } + + public Buffer get(byte[] dst) { + return get(dst, 0, dst.length); + } + + public Buffer put(byte b) { + _wrapped.put(toHex(b)); + return this; + } + + public Buffer put(byte[] dst, int offset, int len) { + for (; offset < len; offset++) + put(dst[offset]); + + return this; + } + + public Buffer put(byte[] dst) { + return put(dst, 0, dst.length); + } + + public byte get(int index) { + return toByte(new byte[]{_wrapped.get(index * 2), _wrapped.get(index * 2 + 1)}); + } + + public Buffer get(int index, byte[] dst, int offset, int len) { + for (; offset < len; offset++) + dst[offset] = get(index++); + + return this; + } + + public Buffer get(int index, byte[] dst) { + return get(index, dst, 0, dst.length); + } + + public Buffer put(int index, byte b) { + byte[] array = toHex(b); + _wrapped.put(index * 2, array[0]); + _wrapped.put(index * 2 + 1, array[1]); + return this; + } + + public Buffer put(int index, byte[] dst, int offset, int len) { + for (; offset < len; offset++) + put(index++, dst[offset]); + + return this; + } + + public Buffer put(int index, byte[] dst) { + return put(index, dst, 0, dst.length); + } + + public Buffer compact() { + _wrapped.compact(); + return this; + } + + public boolean isDirect() { + return _wrapped.isDirect(); + } + + public char getChar() { + return (char) get(); + } + + public Buffer putChar(char value) { + return put((byte) value); + } + + public char getChar(int index) { + return (char) get(index); + } + + public Buffer putChar(int index, char value) { + return put(index, (byte) value); + } + + public ByteBuffer asByteBuffer() { + return new ByteBuffer(_wrapped); + } + + public short getShort() { + return Arrays.toShort(new byte[]{get(), get()}, Endianness.Little); + } + + public Buffer putShort(short value) { + return put(Arrays.fromShort(value, Endianness.Little)); + } + + public short getShort(int index) { + return Arrays.toShort(new byte[]{get(index), get(index + 1)}, Endianness.Little); + } + + public Buffer putShort(int index, short value) { + return put(index, Arrays.fromShort(value, Endianness.Little)); + } + + public int getInt() { + return Arrays.toInt(new byte[]{get(), get(), get(), get()}, Endianness.Little); + } + + public Buffer putInt(int value) { + return put(Arrays.fromInt(value, Endianness.Little)); + } + + public int getInt(int index) { + return Arrays.toInt(new byte[]{get(index), get(index + 1), get(index + 2), get(index + 3)}, Endianness.Little); + } + + public Buffer putInt(int index, int value) { + return put(index, Arrays.fromInt(value, Endianness.Little)); + } + + public long getLong() { + return Arrays.toLong(new byte[]{get(), get(), get(), get(), get(), get(), get(), get()}, Endianness.Little); + } + + public Buffer putLong(long value) { + return put(Arrays.fromLong(value, Endianness.Little)); + } + + public long getLong(int index) { + return Arrays.toLong(new byte[]{get(index), get(index + 1), get(index + 2), get(index + 3), get(index + 4), get(index + 5), get(index + 6), get(index + 7)}, Endianness.Little); + } + + public Buffer putLong(int index, long value) { + return put(index, Arrays.fromLong(value, Endianness.Little)); + } + + public boolean isReadOnly() { + return _wrapped.isReadOnly(); + } + + public void flip() { + _wrapped.flip(); + } + + public int remaining() { + double rem = ((double) _wrapped.remaining() / 2.0d); + return (int) Math.floor(rem); + } + + public boolean hasRemaining() { + return (_wrapped.remaining() > 1); + } + + public HexBuffer asHexBuffer() { + return this; + } + +} diff --git a/src/main/java/zmodem/xfer/util/InvalidChecksumException.java b/src/main/java/zmodem/xfer/util/InvalidChecksumException.java new file mode 100644 index 0000000..d590bfe --- /dev/null +++ b/src/main/java/zmodem/xfer/util/InvalidChecksumException.java @@ -0,0 +1,6 @@ +package zmodem.xfer.util; + +public class InvalidChecksumException extends RuntimeException { + private static final long serialVersionUID = 3864874377147160043L; + +} diff --git a/src/main/java/zmodem/xfer/util/TimeoutException.java b/src/main/java/zmodem/xfer/util/TimeoutException.java new file mode 100644 index 0000000..7b0ccff --- /dev/null +++ b/src/main/java/zmodem/xfer/util/TimeoutException.java @@ -0,0 +1,7 @@ +package zmodem.xfer.util; + +/** + * Created by asirotinkin on 12.11.2014. + */ +class TimeoutException extends Exception { +} diff --git a/src/main/java/zmodem/xfer/util/XCRC.java b/src/main/java/zmodem/xfer/util/XCRC.java new file mode 100644 index 0000000..4120007 --- /dev/null +++ b/src/main/java/zmodem/xfer/util/XCRC.java @@ -0,0 +1,10 @@ +package zmodem.xfer.util; + +/** + * Created by Muzeffer on 2016/6/30. + */ +public interface XCRC { + int getCRCLength(); + + long calcCRC(byte[] block); +} diff --git a/src/main/java/zmodem/xfer/zm/packet/Cancel.java b/src/main/java/zmodem/xfer/zm/packet/Cancel.java new file mode 100644 index 0000000..e626312 --- /dev/null +++ b/src/main/java/zmodem/xfer/zm/packet/Cancel.java @@ -0,0 +1,29 @@ +package zmodem.xfer.zm.packet; + + +import zmodem.xfer.util.ASCII; +import zmodem.xfer.util.Buffer; +import zmodem.xfer.util.ByteBuffer; +import zmodem.xfer.zm.util.ZMPacket; + +public class Cancel extends ZMPacket { + + @Override + public Buffer marshall() { + ByteBuffer buff = ByteBuffer.allocate(16); + + for (int i = 0; i < 8; i++) + buff.put(ASCII.CAN.value()); + for (int i = 0; i < 8; i++) + buff.put(ASCII.BS.value()); + + buff.flip(); + + return buff; + } + + @Override + public String toString() { + return "Cancel: CAN * 8 + BS * 8"; + } +} diff --git a/src/main/java/zmodem/xfer/zm/packet/DataPacket.java b/src/main/java/zmodem/xfer/zm/packet/DataPacket.java new file mode 100644 index 0000000..a307de9 --- /dev/null +++ b/src/main/java/zmodem/xfer/zm/packet/DataPacket.java @@ -0,0 +1,92 @@ +package zmodem.xfer.zm.packet; + + +import zmodem.xfer.util.*; +import zmodem.xfer.zm.util.ZDLEEncoder; +import zmodem.xfer.zm.util.ZMPacket; +import zmodem.xfer.zm.util.ZModemCharacter; + +import java.io.ByteArrayOutputStream; + +public class DataPacket extends ZMPacket { + + public static DataPacket unmarshall(Buffer buff, CRC crc) { + byte[] data = new byte[buff.remaining() - crc.size() - 1]; + + buff.get(data); + + ZModemCharacter type; + type = ZModemCharacter.forbyte(buff.get()); + + + byte[] netCrc = new byte[crc.size()]; + buff.get(netCrc); + + if (!Arrays.equals(netCrc, crc.getBytes())) + throw new InvalidChecksumException(); + + return new DataPacket(type, data); + } + + + private final ZModemCharacter type; + private byte[] data = new byte[0]; + + public DataPacket(ZModemCharacter fe) { + type = fe; + } + + public DataPacket(ZModemCharacter fr, byte[] d) { + this(fr); + data = d; + } + + public ZModemCharacter type() { + return type; + } + + public byte[] data() { + return data; + } + + public void setData(byte[] d) { + data = d; + } + + public void copyData(byte[] d) { + data = Arrays.copyOf(d, d.length); + } + + @Override + public Buffer marshall() { + ZDLEEncoder encoder; + ByteBuffer buff = ByteBuffer.allocate(data.length * 2 + 64); + + CRC crc = new CRC(CRC.Type.CRC16); + + encoder = new ZDLEEncoder(data); + + crc.update(data); + buff.put(encoder.zdle(), 0, encoder.zdleLen()); + + buff.put(ZModemCharacter.ZDLE.value()); + + crc.update(type.value()); + buff.put(type.value()); + + crc.finalized(); + + encoder = new ZDLEEncoder(crc.getBytes()); + buff.put(encoder.zdle(), 0, encoder.zdleLen()); + + buff.flip(); + + return buff; + } + + + @Override + public String toString() { + return type + ":" + data.length + " bytes"; + } +} diff --git a/src/main/java/zmodem/xfer/zm/packet/Finish.java b/src/main/java/zmodem/xfer/zm/packet/Finish.java new file mode 100644 index 0000000..96c9df6 --- /dev/null +++ b/src/main/java/zmodem/xfer/zm/packet/Finish.java @@ -0,0 +1,27 @@ +package zmodem.xfer.zm.packet; + + +import zmodem.xfer.util.Buffer; +import zmodem.xfer.util.ByteBuffer; +import zmodem.xfer.zm.util.ZMPacket; + +public class Finish extends ZMPacket { + + @Override + public Buffer marshall() { + ByteBuffer buff = ByteBuffer.allocate(16); + + for (int i = 0; i < 2; i++) + buff.put((byte) 'O'); + + buff.flip(); + + return buff; + } + + @Override + public String toString() { + return "Finish: OO"; + } + +} diff --git a/src/main/java/zmodem/xfer/zm/packet/Format.java b/src/main/java/zmodem/xfer/zm/packet/Format.java new file mode 100644 index 0000000..8e35a81 --- /dev/null +++ b/src/main/java/zmodem/xfer/zm/packet/Format.java @@ -0,0 +1,48 @@ +package zmodem.xfer.zm.packet; + + +import zmodem.xfer.util.CRC; +import zmodem.xfer.zm.util.ZModemCharacter; + +public enum Format { + + BIN32(1, CRC.Type.CRC32, ZModemCharacter.ZBIN32), + BIN(1, CRC.Type.CRC16, ZModemCharacter.ZBIN), + HEX(2, CRC.Type.CRC16, ZModemCharacter.ZHEX); + + + public static Format fromByte(byte b) { + for (Format ft : values()) { + if (ft.character() == b) + return ft; + } + return null; + } + + private int width; + private CRC.Type crc; + private ZModemCharacter character; + + private Format(int bw, CRC.Type crct, ZModemCharacter fmt) { + width = bw; + crc = crct; + character = fmt; + } + + public CRC.Type crc() { + return crc; + } + + public byte character() { + return character.value(); + } + + public int width() { + return width; + } + + public boolean hex() { + return (this == HEX); + } + +} \ No newline at end of file diff --git a/src/main/java/zmodem/xfer/zm/packet/Header.java b/src/main/java/zmodem/xfer/zm/packet/Header.java new file mode 100644 index 0000000..105875c --- /dev/null +++ b/src/main/java/zmodem/xfer/zm/packet/Header.java @@ -0,0 +1,130 @@ +package zmodem.xfer.zm.packet; + + +import zmodem.xfer.util.*; +import zmodem.xfer.zm.util.ZDLEEncoder; +import zmodem.xfer.zm.util.ZMPacket; +import zmodem.xfer.zm.util.ZModemCharacter; + +public class Header extends ZMPacket { + + public static Header unmarshall(Buffer buff) { + + Format fmt = null; + + while (fmt == null) + fmt = Format.fromByte(buff.get()); + + if (fmt.hex()) + buff = buff.asHexBuffer(); + + CRC crc = new CRC(fmt.crc()); + byte b; + + b = buff.get(); + crc.update(b); + ZModemCharacter type = ZModemCharacter.forbyte(b); + + byte[] data = new byte[4]; + for (int i = 0; i < data.length; i++) { + b = buff.get(); + crc.update(b); + data[i] = b; + } + crc.finalized(); + + byte[] netCrc = new byte[crc.size()]; + buff.get(netCrc); + + if (!Arrays.equals(netCrc, crc.getBytes())) + throw new InvalidChecksumException(); + + return new Header(fmt, type, data); + } + + + private Format format; + private ZModemCharacter type; + private byte[] data = {0, 0, 0, 0}; + + private Header(Format fFmt) { + format = fFmt; + } + + public Header(Format fFmt, ZModemCharacter fType) { + this(fFmt); + type = fType; + } + + public Header(Format fFmt, ZModemCharacter fType, byte[] flags) { + this(fFmt, fType); + setFlags(flags); + } + + public Header(Format fFmt, ZModemCharacter fType, int pos) { + this(fFmt, fType); + setPos(pos); + } + + + public ZModemCharacter type() { + return type; + } + + public Format format() { + return format; + } + + public void setFlags(byte[] flags) { + data = Arrays.copyOf(flags, flags.length); + } + + public byte[] getFlags() { + return data; + } + + public void setPos(int num) { + data = Arrays.fromInt(num, Arrays.Endianness.Little); + } + + public int getPos() { + return Arrays.toInt(data, Arrays.Endianness.Little); + } + + @Override + public Buffer marshall() { + ZDLEEncoder encoder; + + Buffer buff; + if (format.hex()) + buff = HexBuffer.allocate(16); + else + buff = ByteBuffer.allocate(32); + + CRC crc = new CRC(format.crc()); + + crc.update(type.value()); + buff.put(type.value()); + + crc.update(data); + encoder = new ZDLEEncoder(data, format); + buff.put(encoder.zdle(), 0, encoder.zdleLen()); + + + crc.finalized(); + + encoder = new ZDLEEncoder(crc.getBytes(), format); + buff.put(encoder.zdle(), 0, encoder.zdleLen()); + + buff.flip(); + + return buff.asByteBuffer(); + + } + + @Override + public String toString() { + return type + ", " + format + ", " + "{" + data[0] + "," + data[1] + "," + data[2] + "," + data[3] + "}"; + } + +} diff --git a/src/main/java/zmodem/xfer/zm/packet/InvalidPacketException.java b/src/main/java/zmodem/xfer/zm/packet/InvalidPacketException.java new file mode 100644 index 0000000..87d1075 --- /dev/null +++ b/src/main/java/zmodem/xfer/zm/packet/InvalidPacketException.java @@ -0,0 +1,9 @@ +package zmodem.xfer.zm.packet; + +import java.io.IOException; + +public class InvalidPacketException extends IOException { + + private static final long serialVersionUID = 6436104259898858243L; + +} diff --git a/src/main/java/zmodem/xfer/zm/proto/Action.java b/src/main/java/zmodem/xfer/zm/proto/Action.java new file mode 100644 index 0000000..10bdb7f --- /dev/null +++ b/src/main/java/zmodem/xfer/zm/proto/Action.java @@ -0,0 +1,5 @@ +package zmodem.xfer.zm.proto; + +public enum Action { + ESCAPE, DATA, HEADER, CANCEL, FINISH; +} diff --git a/src/main/java/zmodem/xfer/zm/proto/Escape.java b/src/main/java/zmodem/xfer/zm/proto/Escape.java new file mode 100644 index 0000000..55a30df --- /dev/null +++ b/src/main/java/zmodem/xfer/zm/proto/Escape.java @@ -0,0 +1,99 @@ +package zmodem.xfer.zm.proto; + +import zmodem.xfer.zm.util.ZModemCharacter; + +import java.util.HashMap; +import java.util.Map; + + +public class Escape { + + private int len = 0; + private Action action = Action.ESCAPE; + + + public Escape(Action a) { + this(a, 0); + } + + + public Escape(Action a, int l) { + len = l; + action = a; + } + + public Action action() { + return action; + } + + + public int len() { + return len; + } + + + private static Map _specials = new HashMap(); + + static { + _specials.put(ZModemCharacter.ZBIN.value(), new Escape(Action.HEADER, 7)); + _specials.put(ZModemCharacter.ZHEX.value(), new Escape(Action.HEADER, 16)); + _specials.put(ZModemCharacter.ZBIN32.value(), new Escape(Action.HEADER, 9)); + _specials.put(ZModemCharacter.ZCRCE.value(), new Escape(Action.DATA, 2)); + _specials.put(ZModemCharacter.ZCRCG.value(), new Escape(Action.DATA, 2)); + _specials.put(ZModemCharacter.ZCRCQ.value(), new Escape(Action.DATA, 2)); + _specials.put(ZModemCharacter.ZCRCW.value(), new Escape(Action.DATA, 2)); + } + + + public static Escape detect(byte b, boolean acceptsHeader) { + Escape r = _specials.get(b); + + + if (r == null || ((!acceptsHeader) && r.action() == Action.HEADER)) + return new Escape(Action.ESCAPE); + + return r; + } + + public static boolean mustEscape(byte b, byte previous, boolean escapeCtl) { + switch (b) { + case 0xd: + case (byte) 0x8d: + if (escapeCtl && previous == '@') + return true; + break; + case 0x18: + case 0x10: + case 0x11: + case 0x13: + case (byte) 0x7f: + case (byte) 0x90: + case (byte) 0x91: + case (byte) 0x93: + case (byte) 0xff: + return true; + default: + if (escapeCtl && ((b & 0x60) == 0)) + return true; + } + return false; + } + + public static byte escapeIt(byte b) { + if (b == (byte) 0x7f) + return ZModemCharacter.ZRUB0.value(); + if (b == (byte) 0xff) + return ZModemCharacter.ZRUB1.value(); + if (b == (byte) ZModemCharacter.ZRUB0.value()) + return 0x7f; + if (b == (byte) ZModemCharacter.ZRUB1.value()) + return (byte) 0xff; + + return (byte) (b ^ 0x40); + } + + @Override + public String toString() { + return "Action=" + action + ", len=" + len; + } +} diff --git a/src/main/java/zmodem/xfer/zm/util/Modem.java b/src/main/java/zmodem/xfer/zm/util/Modem.java new file mode 100644 index 0000000..dc996fa --- /dev/null +++ b/src/main/java/zmodem/xfer/zm/util/Modem.java @@ -0,0 +1,401 @@ +package zmodem.xfer.zm.util; + +import zmodem.xfer.util.CRC16; +import zmodem.xfer.util.CRC8; +import zmodem.xfer.util.XCRC; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; + +/** + * This is core Modem class supporting XModem (and some extensions XModem-1K, XModem-CRC), and YModem.
+ * YModem support is limited (currently block 0 is ignored).
+ *
+ * Created by Anton Sirotinkin (aesirot@mail.ru), Moscow 2014
+ * I hope you will find this program useful.
+ * You are free to use/modify the code for any purpose, but please leave a reference to me.
+ */ +public class Modem { + + public static final byte SOH = 0x01; /* Start Of Header */ + public static final byte STX = 0x02; /* Start Of Text (used like SOH but means 1024 block size) */ + public static final byte EOT = 0x04; /* End Of Transmission */ + public static final byte ACK = 0x06; /* ACKnowlege */ + public static final byte NAK = 0x15; /* Negative AcKnowlege */ + public static final byte CAN = 0x18; /* CANcel character */ + + public static final byte CPMEOF = 0x1A; + public static final byte ST_C = 'C'; + + public static final int MAXERRORS = 10; + + public static final int BLOCK_TIMEOUT = 1000; + public static final int REQUEST_TIMEOUT = 3000; + public static final int WAIT_FOR_RECEIVER_TIMEOUT = 60_000; + public static final int SEND_BLOCK_TIMEOUT = 10_000; + + private final InputStream inputStream; + private final OutputStream outputStream; + + private final byte[] shortBlockBuffer; + private final byte[] longBlockBuffer; + + /** + * Constructor + * + * @param inputStream stream for reading received data from other side + * @param outputStream stream for writing data to other side + */ + public Modem(InputStream inputStream, OutputStream outputStream) { + this.inputStream = inputStream; + this.outputStream = outputStream; + shortBlockBuffer = new byte[128]; + longBlockBuffer = new byte[1024]; + } + + /** + * Wait for receiver request for transmission + * + * @return TRUE if receiver requested CRC-16 checksum, FALSE if 8bit checksum + * @throws IOException + */ + public boolean waitReceiverRequest() throws IOException { + int character; + while (true) { + character = readByte(); + if (character == NAK) + return false; + if (character == ST_C) { + return true; + } + } + } + + /** + * Send a file.
+ *

+ * This method support correct thread interruption, when thread is interrupted "cancel of transmission" will be send. + * So you can move long transmission to other thread and interrupt it according to your algorithm. + * + * @param file + * @param useBlock1K + * @throws IOException + */ + public void send(Path file, boolean useBlock1K) throws IOException { + //open file + try (DataInputStream dataStream = new DataInputStream(Files.newInputStream(file))) { + + boolean useCRC16 = waitReceiverRequest(); + XCRC crc; + if (useCRC16) + crc = new CRC16(); + else + crc = new CRC8(); + + byte[] block; + if (useBlock1K) + block = new byte[1024]; + else + block = new byte[128]; + sendDataBlocks(dataStream, 1, crc, block); + + sendEOT(); + } + } + + public void sendDataBlocks(DataInputStream dataStream, int blockNumber, XCRC crc, byte[] block) throws IOException { + int dataLength; + while ((dataLength = dataStream.read(block)) != -1) { + sendBlock(blockNumber++, block, dataLength, crc); + } + } + + public void sendEOT() throws IOException { + int errorCount = 0; + int character; + while (errorCount < 10) { + sendByte(EOT); + character = readByte(); + + if (character == ACK) { + return; + } else if (character == CAN) { + throw new IOException("Transmission terminated"); + } + errorCount++; + } + } + + public void sendBlock(int blockNumber, byte[] block, int dataLength, XCRC crc) throws IOException { + int errorCount; + int character; + + if (dataLength < block.length) { + block[dataLength] = CPMEOF; + } + errorCount = 0; + + while (errorCount < MAXERRORS) { + + if (block.length == 1024) + outputStream.write(STX); + else //128 + outputStream.write(SOH); + outputStream.write(blockNumber); + outputStream.write(~blockNumber); + + outputStream.write(block); + writeCRC(block, crc); + outputStream.flush(); + + while (true) { + character = readByte(); + if (character == ACK) { + return; + } else if (character == NAK) { + errorCount++; + break; + } else if (character == CAN) { + throw new IOException("Transmission terminated"); + } + } + + } + + throw new IOException("Too many errors caught, abandoning transfer"); + } + + private void writeCRC(byte[] block, XCRC crc) throws IOException { + byte[] crcBytes = new byte[crc.getCRCLength()]; + long crcValue = crc.calcCRC(block); + for (int i = 0; i < crc.getCRCLength(); i++) { + crcBytes[crc.getCRCLength() - i - 1] = (byte) ((crcValue >> (8 * i)) & 0xFF); + } + outputStream.write(crcBytes); + } + + /** + * Receive file
+ *

+ * This method support correct thread interruption, when thread is interrupted "cancel of transmission" will be send. + * So you can move long transmission to other thread and interrupt it according to your algorithm. + * + * @param file file path for storing + * @throws IOException + */ + public void receive(Path file, boolean useCRC16) throws IOException { + try (DataOutputStream dataOutput = new DataOutputStream(Files.newOutputStream(file))) { + int available; + // clean input stream + if ((available = inputStream.available()) > 0) { + inputStream.skip(available); + } + + int character = requestTransmissionStart(useCRC16); + + XCRC crc; + if (useCRC16) + crc = new CRC16(); + else + crc = new CRC8(); + + + processDataBlocks(crc, 1, character, dataOutput); + } + } + + public void processDataBlocks(XCRC crc, int blockNumber, int blockInitialCharacter, DataOutputStream dataOutput) throws IOException { + // read blocks until EOT + boolean result = false; + boolean shortBlock; + byte[] block; + while (true) { + int errorCount = 0; + if (blockInitialCharacter == EOT) { + // end of transmission + sendByte(ACK); + return; + } + + //read and process block + shortBlock = (blockInitialCharacter == SOH); + try { + block = readBlock(blockNumber, shortBlock, crc); + dataOutput.write(block); + blockNumber++; + errorCount = 0; + result = true; + sendByte(ACK); + } catch (InvalidBlockException e) { + errorCount++; + if (errorCount == MAXERRORS) { + interruptTransmission(); + throw new IOException("Transmission aborted, error count exceeded max"); + } + sendByte(NAK); + result = false; + } catch (RepeatedBlockException e) { + //thats ok, accept and wait for next block + sendByte(ACK); + } catch (SynchronizationLostException e) { + //fatal transmission error + interruptTransmission(); + throw new IOException("Fatal transmission error", e); + } + + //wait for next block + blockInitialCharacter = readNextBlockStart(result); + } + } + + public void sendByte(byte b) throws IOException { + outputStream.write(b); + outputStream.flush(); + } + + /** + * Request transmission start and return first byte of "first" block from sender (block 1 for XModem, block 0 for YModem) + * + * @param useCRC16 + * @return + * @throws IOException + */ + public int requestTransmissionStart(boolean useCRC16) throws IOException { + int character; + int errorCount = 0; + byte requestStartByte; + if (!useCRC16) { + requestStartByte = NAK; + } else { + requestStartByte = ST_C; + } + + // wait for first block start + // request transmission start (will be repeated after 10 second timeout for 10 times) + sendByte(requestStartByte); + while (true) { + character = readByte(); + + if (character == SOH || character == STX) { + return character; + } + } + } + + public int readNextBlockStart(boolean lastBlockResult) throws IOException { + int character; + int errorCount = 0; + while (true) { + while (true) { + character = readByte(); + if (character == SOH || character == STX || character == EOT) { + return character; + } + } + // repeat last block result and wait for next block one more time +// if (++errorCount < MAXERRORS) { +// sendByte(lastBlockResult ? ACK : NAK); +// } else { +// interruptTransmission(); +// throw new RuntimeException("Timeout, no data received from transmitter"); +// } + } + } + + private void shortSleep() { + try { + Thread.sleep(10); + } catch (InterruptedException e) { + try { + interruptTransmission(); + } catch (IOException ignore) { + } + throw new RuntimeException("Transmission was interrupted", e); + } + } + + /** + * send CAN to interrupt seance + * + * @throws IOException + */ + public void interruptTransmission() throws IOException { + sendByte(CAN); + sendByte(CAN); + } + + public byte[] readBlock(int blockNumber, boolean shortBlock, XCRC crc) throws IOException, RepeatedBlockException, SynchronizationLostException, InvalidBlockException { + byte[] block; + + if (shortBlock) { + block = shortBlockBuffer; + } else { + block = longBlockBuffer; + } + byte character; + + character = readByte(); + + if (character == blockNumber - 1) { + // this is repeating of last block, possible ACK lost + throw new RepeatedBlockException(); + } + if (character != blockNumber) { + // wrong block - fatal loss of synchronization + throw new SynchronizationLostException(); + } + + character = readByte(); + + if (character != ~blockNumber) { + throw new InvalidBlockException(); + } + + // data + for (int i = 0; i < block.length; i++) { + block[i] = readByte(); + } + + while (true) { + if (inputStream.available() >= crc.getCRCLength()) { + if (crc.calcCRC(block) != readCRC(crc)) { + throw new InvalidBlockException(); + } + break; + } + + shortSleep(); + + } + + return block; + } + + private long readCRC(XCRC crc) throws IOException { + long checkSumma = 0; + for (int j = 0; j < crc.getCRCLength(); j++) { + checkSumma = (checkSumma << 8) + inputStream.read(); + } + return checkSumma; + } + + private byte readByte() throws IOException { + while (true) { + if (inputStream.available() > 0) { + int b = inputStream.read(); + return (byte) b; + } + shortSleep(); + } + } + + public class RepeatedBlockException extends Exception { + } + + public class SynchronizationLostException extends Exception { + } + + public class InvalidBlockException extends Exception { + } +} diff --git a/src/main/java/zmodem/xfer/zm/util/ZDLEEncoder.java b/src/main/java/zmodem/xfer/zm/util/ZDLEEncoder.java new file mode 100644 index 0000000..e352793 --- /dev/null +++ b/src/main/java/zmodem/xfer/zm/util/ZDLEEncoder.java @@ -0,0 +1,58 @@ +package zmodem.xfer.zm.util; + + +import zmodem.xfer.zm.packet.Format; +import zmodem.xfer.zm.proto.Escape; + +public class ZDLEEncoder { + + private byte[] raw; + private byte[] zdle; + private int zdleLen; + private Format format; + + + public ZDLEEncoder(byte[] data) { + this(data, Format.BIN); + } + + public ZDLEEncoder(byte[] data, Format fmt) { + raw = data; + format = fmt; + zdle = new byte[raw.length * 2]; + encode(); + } + + private void putZdle(byte b) { + zdle[zdleLen] = b; + zdleLen++; + } + + private void encode() { + byte previous = 0; + for (byte b : raw) { + + if ((!format.hex()) && Escape.mustEscape(b, previous, false)) { + putZdle(ZModemCharacter.ZDLE.value()); + b = Escape.escapeIt(b); + } + + putZdle(b); + previous = b; + } + } + + public byte[] raw() { + return raw; + } + + public int zdleLen() { + return zdleLen; + } + + public byte[] zdle() { + return zdle; + + } + +} diff --git a/src/main/java/zmodem/xfer/zm/util/ZMOptions.java b/src/main/java/zmodem/xfer/zm/util/ZMOptions.java new file mode 100644 index 0000000..cf9476a --- /dev/null +++ b/src/main/java/zmodem/xfer/zm/util/ZMOptions.java @@ -0,0 +1,49 @@ +package zmodem.xfer.zm.util; + +public enum ZMOptions { + + CANFDX(0x01), /* Rx can send and receive true FDX */ + CANOVIO(0x02), /* Rx can receive data during disk I/O */ + CANBRK(0x04), /* Rx can send a break signal */ + CANCRY(0x08), /* Receiver can decrypt */ + CANLZW(0x10), /* Receiver can uncompress */ + CANFC32(0x20), /* Receiver can use 32 bit Frame Check */ + ESCCTL(0x40), /* Receiver expects ctl chars to be escaped */ + ESC8(0x80), /* Receiver expects 8th bit to be escaped */ + ZCBIN(0x01); + + private byte value; + + private ZMOptions(char b) { + value = (byte) b; + } + + private ZMOptions(int b) { + value = (byte) b; + } + + private ZMOptions(byte b) { + value = b; + } + + + public byte value() { + return value; + } + + public static byte with(ZMOptions... oo) { + byte r = 0; + for (ZMOptions o : oo) + r = (byte) (r | o.value()); + return r; + } + + public static ZMOptions forbyte(byte b) { + for (ZMOptions zb : values()) { + if (zb.value() == b) + return zb; + } + return null; + } + +} diff --git a/src/main/java/zmodem/xfer/zm/util/ZMPacket.java b/src/main/java/zmodem/xfer/zm/util/ZMPacket.java new file mode 100644 index 0000000..c236eda --- /dev/null +++ b/src/main/java/zmodem/xfer/zm/util/ZMPacket.java @@ -0,0 +1,9 @@ +package zmodem.xfer.zm.util; + + +import zmodem.xfer.util.Buffer; + +public abstract class ZMPacket { + public abstract Buffer marshall(); + +} diff --git a/src/main/java/zmodem/xfer/zm/util/ZMPacketFactory.java b/src/main/java/zmodem/xfer/zm/util/ZMPacketFactory.java new file mode 100644 index 0000000..0401785 --- /dev/null +++ b/src/main/java/zmodem/xfer/zm/util/ZMPacketFactory.java @@ -0,0 +1,39 @@ +package zmodem.xfer.zm.util; + + +import zmodem.xfer.zm.packet.DataPacket; + +public class ZMPacketFactory { + + public ZMPacketFactory() { + } + + public DataPacket createZFilePacket(String pathname, long flen) { + return createZFilePacket(pathname, flen, 0, "0", 0, 0); + } + + public DataPacket createZFilePacket(String pathname, long flen, long ts, String mode/*octal*/ + , int remainingfiles, long remainingBytes) { + + StringBuilder builder = new StringBuilder(); + + builder.append(pathname); + builder.append('\0'); + builder.append(flen); + builder.append(' '); + builder.append(ts); + builder.append(' '); + builder.append(mode); + builder.append(' '); + builder.append('0'); + builder.append(' '); + builder.append(remainingfiles); + builder.append(' '); + builder.append(remainingBytes); + builder.append('0'); + + return new DataPacket(ZModemCharacter.ZCRCW, builder.toString().getBytes()); + } + + +} diff --git a/src/main/java/zmodem/xfer/zm/util/ZModemCharacter.java b/src/main/java/zmodem/xfer/zm/util/ZModemCharacter.java new file mode 100644 index 0000000..8beb22b --- /dev/null +++ b/src/main/java/zmodem/xfer/zm/util/ZModemCharacter.java @@ -0,0 +1,64 @@ +package zmodem.xfer.zm.util; + +public enum ZModemCharacter { + ZPAD('*'), + ZDLE(0x18), + ZDLEE(ZDLE.value() ^ 0x40), + ZBIN('A'), + ZHEX('B'), + ZBIN32('C'), + ZCRCE('h'), + ZCRCG('i'), + ZCRCQ('j'), + ZCRCW('k'), + ZRUB0('l'), + ZRUB1('m'), + ZRQINIT(0), + ZRINIT(1), + ZSINIT(2), + ZACK(3), + ZFILE(4), + ZSKIP(5), + ZNAK(6), + ZABORT(7), + ZFIN(8), + ZRPOS(9), + ZDATA(10), + ZEOF(11), + ZFERR(12), + ZCRC(13), + ZCHALLENGE(14), + ZCOMPL(15), + ZCAN(16), + ZFREECNT(17), + ZCOMMAND(18), + ZSTDERR(19); + + private byte value; + + private ZModemCharacter(char b) { + value = (byte) b; + } + + private ZModemCharacter(int b) { + value = (byte) b; + } + + private ZModemCharacter(byte b) { + value = b; + } + + + public byte value() { + return value; + } + + public static ZModemCharacter forbyte(byte b) { + for (ZModemCharacter zb : values()) { + if (zb.value() == b) + return zb; + } + return null; + } + +} diff --git a/src/main/java/zmodem/xfer/zm/util/ZModemReceive.java b/src/main/java/zmodem/xfer/zm/util/ZModemReceive.java new file mode 100644 index 0000000..0131449 --- /dev/null +++ b/src/main/java/zmodem/xfer/zm/util/ZModemReceive.java @@ -0,0 +1,259 @@ +package zmodem.xfer.zm.util; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.math.NumberUtils; +import org.apache.commons.net.io.CopyStreamAdapter; +import org.apache.commons.net.io.CopyStreamListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import zmodem.FileCopyStreamEvent; +import zmodem.util.EmptyFileAdapter; +import zmodem.util.FileAdapter; +import zmodem.xfer.util.InvalidChecksumException; +import zmodem.xfer.zm.packet.*; +import zmodem.zm.io.ZMPacketInputStream; +import zmodem.zm.io.ZMPacketOutputStream; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.function.Supplier; + + +public class ZModemReceive { + + private static final Logger log = LoggerFactory.getLogger(ZModemReceive.class); + + private final CopyStreamAdapter adapter = new CopyStreamAdapter(); + private final Supplier destinationSupplier; + private FileAdapter destination; + + private FileAdapter file; + private int fOffset = 0; + private Long filesize; + private int remaining = 0; + private int index = 0; + + private OutputStream fileOs = null; + + private final InputStream netIs; + private final OutputStream netOs; + + private enum Expect { + FILENAME, DATA, NOTHING; + } + + + public ZModemReceive(Supplier destDir, InputStream netin, OutputStream netout) throws IOException { + destinationSupplier = destDir; + netIs = netin; + netOs = netout; + } + + private void open(int offset) throws IOException { + boolean append = false; + + if (offset != 0) { + if (file.exists() && file.length() == offset) + append = true; + else + offset = 0; + } + + IOUtils.closeQuietly(fileOs); + + fileOs = file.getOutputStream(append); + fOffset = offset; + + } + + private void decodeFileNameData(DataPacket p) { + ByteArrayOutputStream filename = new ByteArrayOutputStream(); + StringBuilder extract = new StringBuilder(); + byte[] data = p.data(); + for (int i = 0; i < data.length; i++) { + byte b = data[i]; + if (b == 0) { + for (int j = i + 1; j < data.length; j++) { + b = data[j]; + if (b == 0) { + break; + } + extract.append((char) b); + } + break; + } + filename.write(b); + } + + final String[] segments = extract.toString().split(StringUtils.SPACE); + if (ArrayUtils.isNotEmpty(segments)) { + // filesize + if (segments.length >= 1) { + this.filesize = NumberUtils.toLong(segments[0]); + } + // remaining + if (segments.length >= 5) { + this.remaining = NumberUtils.toInt(segments[4]); + } + } + + file = destination.getChild(filename.toString()); + fOffset = 0; + + index++; + + adapter.bytesTransferred(new FileCopyStreamEvent(this, file.getName(), remaining - index, index, + this.filesize, fOffset, 0, false)); + } + + public void addCopyStreamListener(CopyStreamListener listener) { + adapter.addCopyStreamListener(listener); + } + + public void removeCopyStreamListener(CopyStreamListener listener) { + adapter.removeCopyStreamListener(listener); + } + + private void writeData(DataPacket p) throws IOException { + final byte[] data = p.data(); + + fileOs.write(data); + fOffset += data.length; + + // 开始传输 + adapter.bytesTransferred(new FileCopyStreamEvent(this, file.getName(), remaining, index, + this.filesize, fOffset, 0, false)); + } + + private boolean initDestination() { + if (destination != null) { + return true; + } + destination = destinationSupplier.get(); + return !(destination instanceof EmptyFileAdapter); + } + + public void receive(Supplier isCancelled) { + ZMPacketInputStream is = new ZMPacketInputStream(netIs); + ZMPacketOutputStream os = new ZMPacketOutputStream(netOs); + + Expect expect = Expect.NOTHING; + + byte[] recvOpt = {0, 4, 0, ZMOptions.with(ZMOptions.ESCCTL, ZMOptions.ESC8)}; + + try { + + boolean end = false; + int errorCount = 0; + ZMPacket packet = null; + while (!end) { + try { + packet = is.read(); + } catch (InvalidChecksumException ice) { + if (log.isErrorEnabled()) { + log.error(ice.getMessage(), ice); + } + ++errorCount; + if (errorCount >= 3) { + os.write(new Cancel()); + end = true; + } + } + + if (packet instanceof Cancel) { + end = true; + } else if (packet instanceof Finish) { + end = true; + } + + if (isCancelled.get()) { + break; + } + + // 如果重定向为空,则终止传输 + if (destination instanceof EmptyFileAdapter) { + os.write(new Cancel()); + break; + } + + if (packet instanceof Header header) { + switch (header.type()) { + case ZRQINIT: + os.write(new Header(Format.HEX, ZModemCharacter.ZRINIT, recvOpt)); + break; + case ZFILE: + expect = Expect.FILENAME; + break; + case ZEOF: + os.write(new Header(Format.HEX, ZModemCharacter.ZRINIT, recvOpt)); + expect = Expect.NOTHING; + file = null; + fileOs.flush(); + IOUtils.closeQuietly(fileOs); + fileOs = null; + break; + case ZDATA: + open(header.getPos()); + expect = Expect.DATA; + break; + case ZFIN: + os.write(new Header(Format.HEX, ZModemCharacter.ZFIN)); + end = true; + break; + default: + end = true; + os.write(new Cancel()); + break; + } + } + + if (packet instanceof DataPacket data) { + switch (expect) { + case NOTHING: + os.write(new Header(Format.HEX, ZModemCharacter.ZRINIT, recvOpt)); + break; + case FILENAME: + if (!initDestination()) { + end = true; + os.write(new Cancel()); + break; + } + decodeFileNameData(data); + if (file.length() == filesize) { + os.write(new Header(Format.HEX, ZModemCharacter.ZSKIP)); + adapter.bytesTransferred(new FileCopyStreamEvent(this, file.getName(), remaining, index, + this.filesize, fOffset, 0, true)); + } else { + os.write(new Header(Format.HEX, ZModemCharacter.ZRPOS, (int) file.length())); + } + expect = Expect.NOTHING; + break; + case DATA: + writeData(data); + switch (data.type()) { + case ZCRCW: + expect = Expect.NOTHING; + case ZCRCQ: + os.write(new Header(Format.HEX, ZModemCharacter.ZACK, fOffset)); + break; + case ZCRCE: + expect = Expect.NOTHING; + break; + } + } + } + } + } catch (IOException e) { + if (log.isErrorEnabled()) { + log.error(e.getMessage(), e); + } + } finally { + IOUtils.closeQuietly(fileOs); + } + + } +} diff --git a/src/main/java/zmodem/xfer/zm/util/ZModemSend.java b/src/main/java/zmodem/xfer/zm/util/ZModemSend.java new file mode 100644 index 0000000..4f5d71f --- /dev/null +++ b/src/main/java/zmodem/xfer/zm/util/ZModemSend.java @@ -0,0 +1,213 @@ +package zmodem.xfer.zm.util; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.net.io.CopyStreamAdapter; +import org.apache.commons.net.io.CopyStreamListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import zmodem.FileCopyStreamEvent; +import zmodem.util.FileAdapter; +import zmodem.xfer.util.InvalidChecksumException; +import zmodem.xfer.zm.packet.*; +import zmodem.zm.io.ZMPacketInputStream; +import zmodem.zm.io.ZMPacketOutputStream; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Iterator; +import java.util.List; +import java.util.function.Supplier; + + +public class ZModemSend { + + private static final int packLen = 1024 * 8; + private static final Logger log = LoggerFactory.getLogger(ZModemSend.class); + + private final byte[] data = new byte[packLen]; + private final CopyStreamAdapter adapter = new CopyStreamAdapter(); + private final Supplier> destinationSupplier; + private final InputStream netIs; + private final OutputStream netOs; + + private List files; + private Iterator iter; + + private FileAdapter file; + private int fOffset = 0; + private int index = 0; + private int filesize = 0; + private boolean atEof = false; + private InputStream fileIs; + + + public ZModemSend(Supplier> destinationSupplier, InputStream netin, OutputStream netout) throws IOException { + this.destinationSupplier = destinationSupplier; + netIs = netin; + netOs = netout; + } + + public boolean nextFile() throws IOException { + + IOUtils.closeQuietly(fileIs); + + if (files == null) { + files = destinationSupplier.get(); + iter = files.iterator(); + } + + if (!iter.hasNext()) + return false; + + + file = iter.next(); + fileIs = file.getInputStream(); + filesize = fileIs.available(); + fOffset = 0; + atEof = false; + index++; + + return true; + } + + + public void addCopyStreamListener(CopyStreamListener listener) { + adapter.addCopyStreamListener(listener); + } + + public void removeCopyStreamListener(CopyStreamListener listener) { + adapter.removeCopyStreamListener(listener); + } + + + private void position(int offset) throws IOException { + if (offset != fOffset) { + fileIs.skipNBytes(offset); + fOffset = offset; + } + } + + private byte[] getNextBlock() throws IOException { + final int len = fileIs.read(data); + + /* we know it is a file: all the data is locally available.*/ + if (len < data.length) + atEof = true; + else if (fileIs.available() == 0) + atEof = true; + + if (len == -1) { + return null; + } + + fOffset += len; + + if (len != data.length) + return ArrayUtils.subarray(data, 0, len); + else + return data; + } + + private DataPacket getNextDataPacket() throws IOException { + byte[] data = getNextBlock(); + + ZModemCharacter fe = ZModemCharacter.ZCRCW; + if (atEof) { + fe = ZModemCharacter.ZCRCE; + fileIs.close(); + } + + if (data == null) { + return new DataPacket(fe); + } + + return new DataPacket(fe, data); + } + + public void send(Supplier isCancelled) { + ZMPacketFactory factory = new ZMPacketFactory(); + + ZMPacketInputStream is = new ZMPacketInputStream(netIs); + ZMPacketOutputStream os = new ZMPacketOutputStream(netOs); + + + try { + + boolean end = false; + int errorCount = 0; + ZMPacket packet = null; + + while (!end) { + try { + packet = is.read(); + } catch (InvalidChecksumException ice) { + ++errorCount; + if (errorCount > 20) { + os.write(new Cancel()); + end = true; + } + } + + if (packet instanceof Cancel) { + end = true; + } else if (isCancelled.get()) { + os.write(new Cancel()); + continue; + } + + if (packet instanceof Header header) { + switch (header.type()) { + case ZSKIP: + fireBytesTransferred(true); + case ZRINIT: + if (!nextFile()) { + os.write(new Header(Format.BIN, ZModemCharacter.ZFIN)); + } else { + os.write(new Header(Format.BIN, ZModemCharacter.ZFILE, new byte[]{0, 0, 0, ZMOptions.with(ZMOptions.ZCBIN)})); + os.write(factory.createZFilePacket(file.getName(), filesize)); + fireBytesTransferred(false); + } + break; + case ZRPOS: + if (!atEof) + position(header.getPos()); + case ZACK: + os.write(new Header(Format.BIN, ZModemCharacter.ZDATA, fOffset)); + os.write(getNextDataPacket()); + if (atEof) { + os.write(new Header(Format.HEX, ZModemCharacter.ZEOF, fOffset)); + } + fireBytesTransferred(false); + break; + case ZFIN: + end = true; + os.write(new Finish()); + break; + default: + end = true; + os.write(new Cancel()); + break; + } + + } + } + } catch (IOException e) { + if (log.isErrorEnabled()) { + log.error(e.getMessage(), e); + } + } finally { + IOUtils.closeQuietly(fileIs); + } + + } + + private void fireBytesTransferred(boolean skip) { + if (this.filesize == fOffset) { + System.out.println(); + } + adapter.bytesTransferred(new FileCopyStreamEvent(this, file.getName(), files.size() - index + 1, index, + this.filesize, fOffset, 0, skip)); + } +} diff --git a/src/main/java/zmodem/zm/io/ZMPacketInputStream.java b/src/main/java/zmodem/zm/io/ZMPacketInputStream.java new file mode 100644 index 0000000..78dccae --- /dev/null +++ b/src/main/java/zmodem/zm/io/ZMPacketInputStream.java @@ -0,0 +1,145 @@ +package zmodem.zm.io; + +import zmodem.xfer.io.ObjectInputStream; +import zmodem.xfer.util.ByteBuffer; +import zmodem.xfer.util.CRC; +import zmodem.xfer.zm.packet.*; +import zmodem.xfer.zm.proto.Action; +import zmodem.xfer.zm.proto.Escape; +import zmodem.xfer.zm.util.ZMPacket; +import zmodem.xfer.zm.util.ZModemCharacter; + +import java.io.IOException; +import java.io.InputStream; + + +public class ZMPacketInputStream extends ObjectInputStream { + + private final InputStream netIs; + private CRC dataCRC = new CRC(CRC.Type.CRC16); + private boolean gotFIN = false; + private boolean acceptsHeader = true; + + public ZMPacketInputStream(InputStream is) { + netIs = is; + } + + private boolean ignored(int b) { + return b == 0x11 || b == 0x13 || b == 0x91 || b == 0x93; + } + + private byte implRead() throws IOException { + int n; + do { + n = netIs.read(); + } while (ignored(n)); + + if (n == -1) { + throw new IOException("Closed"); + } + + return (byte) n; + } + + @Override + public ZMPacket read() throws IOException { + ByteBuffer zbuff = ByteBuffer.allocate(1024 * 10); + boolean doread = true; + Action action = Action.ESCAPE; + + int beforeStop = -1; + int countCan = 0; + + while (doread) { + byte n = implRead(); + + if (gotFIN && n == 'O') { + n = implRead(); + if (n == 'O') { + return new Finish(); + } + } + + if (n == ZModemCharacter.ZDLE.value()) { + n = (byte) netIs.read(); + + if (n == ZModemCharacter.ZDLE.value()) + countCan += 2; + else + countCan = 0; + + Escape escape = Escape.detect(n, acceptsHeader); + + if (escape.action() != Action.ESCAPE && beforeStop < 0) { + action = escape.action(); + + if (escape.action() == Action.DATA) + beforeStop = dataCRC.size(); + else + beforeStop = escape.len(); + + dataCRC.update(n); + } else { + n = Escape.escapeIt(n); + } + + } + zbuff.put(n); + + if (beforeStop < 0) + dataCRC.update(n); + + if (beforeStop == 0) + doread = false; + + if (beforeStop > 0) + beforeStop--; + + if (countCan >= 5) { + doread = false; + action = Action.CANCEL; + } + + } + zbuff.flip(); + + ZMPacket r = null; + switch (action) { + case HEADER: + r = Header.unmarshall(zbuff); + + + if (((Header) r).format() == Format.BIN32) + dataCRC = new CRC(CRC.Type.CRC32); + else + dataCRC = new CRC(CRC.Type.CRC16); + + if (((Header) r).type() == ZModemCharacter.ZFIN) + gotFIN = true; + if (((Header) r).type() == ZModemCharacter.ZDATA || ((Header) r).type() == ZModemCharacter.ZFILE) + acceptsHeader = false; + + break; + case DATA: + dataCRC.finalized(); + + r = DataPacket.unmarshall(zbuff, dataCRC); + + dataCRC = new CRC(dataCRC.type()); + + if (((DataPacket) r).type() == ZModemCharacter.ZCRCG) + acceptsHeader = false; + else + acceptsHeader = true; + + break; + case CANCEL: + r = new Cancel(); + dataCRC = new CRC(dataCRC.type()); + break; + } + + return r; + } + +} diff --git a/src/main/java/zmodem/zm/io/ZMPacketOutputStream.java b/src/main/java/zmodem/zm/io/ZMPacketOutputStream.java new file mode 100644 index 0000000..cbc70c4 --- /dev/null +++ b/src/main/java/zmodem/zm/io/ZMPacketOutputStream.java @@ -0,0 +1,67 @@ +package zmodem.zm.io; + +import zmodem.xfer.io.ObjectOutputStream; +import zmodem.xfer.util.ASCII; +import zmodem.xfer.util.Buffer; +import zmodem.xfer.zm.packet.DataPacket; +import zmodem.xfer.zm.packet.Format; +import zmodem.xfer.zm.packet.Header; +import zmodem.xfer.zm.util.ZMPacket; +import zmodem.xfer.zm.util.ZModemCharacter; + +import java.io.IOException; +import java.io.OutputStream; + + +public class ZMPacketOutputStream extends ObjectOutputStream { + + private final OutputStream os; + + public ZMPacketOutputStream(OutputStream netOs) { + os = netOs; + } + + public void implWrite(byte b) throws IOException { + //System.out.printf("%02x",b); + os.write(b); + } + + @Override + public void write(ZMPacket o) throws IOException { + Buffer buff = o.marshall(); + Format fmt = null; + + if (o instanceof Header) + fmt = ((Header) o).format(); + + + if (fmt != null) { + for (int i = 0; i < fmt.width(); i++) + implWrite(ZModemCharacter.ZPAD.value()); + + implWrite(ZModemCharacter.ZDLE.value()); + implWrite(fmt.character()); + } + + + if (buff.hasRemaining()) { + byte[] buf = new byte[buff.remaining()]; + buff.get(buf); + os.write(buf); + } + + if (fmt != null) if (fmt.hex()) { + implWrite(ASCII.CR.value()); + implWrite(ASCII.LF.value()); + implWrite(ASCII.XON.value()); + } + + if (o instanceof DataPacket) if (((DataPacket) o).type() == ZModemCharacter.ZCRCW) + implWrite(ASCII.XON.value()); + + + os.flush(); + + } + +} diff --git a/src/main/kotlin/app/termora/Actions.kt b/src/main/kotlin/app/termora/Actions.kt new file mode 100644 index 0000000..962268f --- /dev/null +++ b/src/main/kotlin/app/termora/Actions.kt @@ -0,0 +1,50 @@ +package app.termora + +object Actions { + + /** + * 打开设置 + */ + const val SETTING = "SettingAction" + + /** + * 将命令发送到多个会话 + */ + const val MULTIPLE = "MultipleAction" + + /** + * 查找 + */ + const val FIND_EVERYWHERE = "FindEverywhereAction" + + /** + * 关键词高亮 + */ + const val KEYWORD_HIGHLIGHT_EVERYWHERE = "KeywordHighlightAction" + + /** + * Key manager + */ + const val KEY_MANAGER = "KeyManagerAction" + + /** + * 更新 + */ + const val APP_UPDATE = "AppUpdateAction" + + + /** + * 宏 + */ + const val MACRO = "MacroAction" + + /** + * 添加主机对话框 + */ + const val ADD_HOST = "AddHostAction" + + /** + * 打开一个主机 + */ + const val OPEN_HOST = "OpenHostAction" +} \ No newline at end of file diff --git a/src/main/kotlin/app/termora/AnAction.kt b/src/main/kotlin/app/termora/AnAction.kt new file mode 100644 index 0000000..6f05563 --- /dev/null +++ b/src/main/kotlin/app/termora/AnAction.kt @@ -0,0 +1,16 @@ +package app.termora + +import org.jdesktop.swingx.action.BoundAction +import javax.swing.Icon + +abstract class AnAction : BoundAction { + + constructor() : super() + constructor(icon: Icon) : super() { + super.putValue(SMALL_ICON, icon) + } + + constructor(name: String?) : super(name) + constructor(name: String?, icon: Icon?) : super(name, icon) + +} \ No newline at end of file diff --git a/src/main/kotlin/app/termora/Application.kt b/src/main/kotlin/app/termora/Application.kt new file mode 100644 index 0000000..2042d37 --- /dev/null +++ b/src/main/kotlin/app/termora/Application.kt @@ -0,0 +1,146 @@ +package app.termora + +import com.formdev.flatlaf.util.SystemInfo +import com.jthemedetecor.util.OsInfo +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import kotlinx.serialization.json.Json +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import org.apache.commons.io.FileUtils +import org.apache.commons.lang3.StringUtils +import org.apache.commons.lang3.SystemUtils +import org.slf4j.LoggerFactory +import java.awt.Desktop +import java.io.File +import java.net.URI +import java.time.Duration +import java.util.* +import kotlin.reflect.KClass + +object Application { + private val services = Collections.synchronizedMap(mutableMapOf, Any>()) + private lateinit var baseDataDir: File + + + val ohMyJson = Json { + ignoreUnknownKeys = true + // 默认值不输出 + encodeDefaults = false + } + + + val httpClient by lazy { + OkHttpClient.Builder() + .connectTimeout(Duration.ofSeconds(10)) + .callTimeout(Duration.ofSeconds(60)) + .writeTimeout(Duration.ofSeconds(60)) + .readTimeout(Duration.ofSeconds(60)) + .addInterceptor( + HttpLoggingInterceptor(object : HttpLoggingInterceptor.Logger { + private val log = LoggerFactory.getLogger(HttpLoggingInterceptor::class.java) + override fun log(message: String) { + if (log.isDebugEnabled) log.debug(message) + } + }).setLevel(HttpLoggingInterceptor.Level.BASIC) + ) + .build() + } + + fun getDefaultShell(): String { + if (SystemInfo.isWindows) { + return "cmd.exe" + } else { + val shell = System.getenv("SHELL") + if (shell != null && shell.isNotBlank()) { + return shell + } + } + return "/bin/bash" + } + + fun getBaseDataDir(): File { + if (::baseDataDir.isInitialized) { + return baseDataDir + } + + // 从启动参数取 + var baseDataDir = System.getProperty("${getName()}.base-data-dir".lowercase()) + // 取不到从环境取 + if (StringUtils.isBlank(baseDataDir)) { + baseDataDir = System.getenv("${getName()}-BASE-DATA-DIR".uppercase()) + } + + var dir = File(SystemUtils.getUserHome(), ".${getName()}".lowercase()) + if (StringUtils.isNotBlank(baseDataDir)) { + dir = File(baseDataDir) + } + + + FileUtils.forceMkdir(dir) + Application.baseDataDir = dir + + return dir + } + + fun getDatabaseFile(): File { + return FileUtils.getFile(getBaseDataDir(), "storage") + } + + fun getVersion(): String { + var version = System.getProperty("jpackage.app-version") + if (version.isNullOrBlank()) { + version = System.getProperty("app-version") + } + if (version.isNullOrBlank()) { + version = "unknown" + } + return version + } + + fun getAppPath(): String { + return StringUtils.defaultString(System.getProperty("jpackage.app-path")) + } + + fun getName(): String { + return "Termora" + } + + fun browse(uri: URI, async: Boolean = true) { + if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) { + Desktop.getDesktop().browse(uri) + } else if (async) { + @Suppress("OPT_IN_USAGE") + GlobalScope.launch(Dispatchers.IO) { tryBrowse(uri) } + } else { + tryBrowse(uri) + } + } + + @Suppress("UNCHECKED_CAST") + fun getService(clazz: KClass): T { + if (services.containsKey(clazz)) { + return services[clazz] as T + } + throw IllegalStateException("$clazz does not exist") + } + + @Synchronized + fun registerService(clazz: KClass<*>, service: Any) { + if (services.containsKey(clazz)) { + throw IllegalStateException("$clazz already registered") + } + services[clazz] = service + } + + private fun tryBrowse(uri: URI) { + if (SystemInfo.isWindows) { + ProcessBuilder("explorer", uri.toString()).start() + } else if (SystemInfo.isMacOS) { + ProcessBuilder("open", uri.toString()).start() + } else if (SystemInfo.isLinux && OsInfo.isGnome()) { + ProcessBuilder("xdg-open", uri.toString()).start() + } + } +} diff --git a/src/main/kotlin/app/termora/ApplicationDisposable.kt b/src/main/kotlin/app/termora/ApplicationDisposable.kt new file mode 100644 index 0000000..7de6de4 --- /dev/null +++ b/src/main/kotlin/app/termora/ApplicationDisposable.kt @@ -0,0 +1,10 @@ +package app.termora + +/** + * 将在 JVM 进程退出时释放 + */ +class ApplicationDisposable : Disposable { + companion object { + val instance by lazy { ApplicationDisposable() } + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/termora/ApplicationRunner.kt b/src/main/kotlin/app/termora/ApplicationRunner.kt new file mode 100644 index 0000000..945da7c --- /dev/null +++ b/src/main/kotlin/app/termora/ApplicationRunner.kt @@ -0,0 +1,254 @@ +package app.termora + +import app.termora.db.Database +import com.formdev.flatlaf.FlatClientProperties +import com.formdev.flatlaf.FlatSystemProperties +import com.formdev.flatlaf.extras.FlatInspector +import com.formdev.flatlaf.util.SystemInfo +import com.jthemedetecor.OsThemeDetector +import com.sun.jna.platform.WindowUtils +import com.sun.jna.platform.win32.User32 +import com.sun.jna.ptr.IntByReference +import org.apache.commons.io.FileUtils +import org.apache.commons.lang3.LocaleUtils +import org.apache.commons.lang3.SystemUtils +import org.apache.commons.lang3.math.NumberUtils +import org.slf4j.LoggerFactory +import org.tinylog.configuration.Configuration +import java.io.File +import java.io.RandomAccessFile +import java.nio.channels.FileChannel +import java.nio.channels.FileLock +import java.nio.file.StandardOpenOption +import java.util.* +import javax.swing.* +import javax.swing.WindowConstants.DISPOSE_ON_CLOSE +import kotlin.system.exitProcess + +class ApplicationRunner { + private lateinit var singletonLock: FileLock + private val log by lazy { + if (!::singletonLock.isInitialized) { + throw UnsupportedOperationException("Singleton lock is not initialized") + } + LoggerFactory.getLogger("Main") + } + + fun run() { + // 覆盖 tinylog 配置 + setupTinylog() + + // 是否单例 + checkSingleton() + + // 打印系统信息 + printSystemInfo() + + SwingUtilities.invokeAndWait { + // 打开数据库 + openDatabase() + + // 加载设置 + loadSettings() + + // 设置 LAF + setupLaf() + + // 解密数据 + openDoor() + + // 启动主窗口 + startMainFrame() + } + } + + + private fun openDoor() { + if (Doorman.instance.isWorking()) { + if (!DoormanDialog(null).open()) { + exitProcess(1) + } + } + } + + private fun startMainFrame() { + val frame = TermoraFrame() + frame.title = if (SystemInfo.isLinux) null else Application.getName() + frame.defaultCloseOperation = DISPOSE_ON_CLOSE + frame.setSize(1280, 800) + frame.setLocationRelativeTo(null) + frame.isVisible = true + } + + + private fun loadSettings() { + val language = Database.instance.appearance.language + val locale = runCatching { LocaleUtils.toLocale(language) }.getOrElse { Locale.getDefault() } + if (log.isInfoEnabled) { + log.info("Language: {} , Locale: {}", language, locale) + } + Locale.setDefault(locale) + } + + + private fun setupLaf() { + + System.setProperty(FlatSystemProperties.USE_WINDOW_DECORATIONS, "${SystemInfo.isLinux}") + System.setProperty(FlatSystemProperties.USE_ROUNDED_POPUP_BORDER, "false") + + if (SystemInfo.isLinux) { + JFrame.setDefaultLookAndFeelDecorated(true) + JDialog.setDefaultLookAndFeelDecorated(true) + } + + val themeManager = ThemeManager.instance + val settings = Database.instance + var theme = settings.appearance.theme + + // 如果是跟随系统或者不存在样式,那么使用默认的 + if (settings.appearance.followSystem || !themeManager.themes.containsKey(theme)) { + theme = if (OsThemeDetector.getDetector().isDark) { + "Dark" + } else { + "Light" + } + } + + themeManager.change(theme, true) + + FlatInspector.install("ctrl shift alt X"); + + UIManager.put(FlatClientProperties.FULL_WINDOW_CONTENT, true) + UIManager.put(FlatClientProperties.USE_WINDOW_DECORATIONS, false) + UIManager.put("TitlePane.useWindowDecorations", false) + + UIManager.put("Component.arc", 5) + UIManager.put("TextComponent.arc", UIManager.getInt("Component.arc")) + UIManager.put("Component.hideMnemonics", false) + + UIManager.put("TitleBar.height", 36) + + UIManager.put("Dialog.width", 650) + UIManager.put("Dialog.height", 550) + + + if (SystemInfo.isMacOS) { + UIManager.put("TabbedPane.tabHeight", UIManager.getInt("TitleBar.height")) + } else if (SystemInfo.isLinux) { + UIManager.put("TabbedPane.tabHeight", UIManager.getInt("TitleBar.height") - 4) + } else { + UIManager.put("TabbedPane.tabHeight", UIManager.getInt("TitleBar.height") - 6) + } + + if (SystemInfo.isLinux) { + UIManager.put("TitlePane.centerTitle", true) + UIManager.put("TitlePane.showIcon", false) + UIManager.put("TitlePane.showIconInDialogs", false) + } + + UIManager.put("Table.rowHeight", 24) + UIManager.put("Table.cellNoFocusBorder", BorderFactory.createEmptyBorder()) + UIManager.put("Table.focusCellHighlightBorder", BorderFactory.createEmptyBorder()) + UIManager.put("Table.focusSelectedCellHighlightBorder", BorderFactory.createEmptyBorder()) + UIManager.put("Table.selectionArc", UIManager.getInt("Component.arc")) + + UIManager.put("Tree.rowHeight", 24) + UIManager.put("Tree.background", DynamicColor("window")) + UIManager.put("Tree.selectionArc", UIManager.getInt("Component.arc")) + UIManager.put("Tree.showCellFocusIndicator", false) + UIManager.put("Tree.repaintWholeRow", true) + + UIManager.put("List.selectionArc", UIManager.getInt("Component.arc")) + + + } + + private fun printSystemInfo() { + if (log.isInfoEnabled) { + log.info("Welcome to ${Application.getName()} ${Application.getVersion()}!") + log.info( + "JVM name: {} , vendor: {} , version: {}", + SystemUtils.JAVA_VM_NAME, + SystemUtils.JAVA_VM_VENDOR, + SystemUtils.JAVA_VM_VERSION, + ) + log.info( + "OS name: {} , version: {} , arch: {}", + SystemUtils.OS_NAME, + SystemUtils.OS_VERSION, + SystemUtils.OS_ARCH + ) + log.info("Base config dir: ${Application.getBaseDataDir().absolutePath}") + } + } + + + /** + * Windows 情况覆盖 + */ + private fun setupTinylog() { + if (SystemInfo.isWindows) { + val dir = File(Application.getBaseDataDir(), "logs") + FileUtils.forceMkdir(dir) + Configuration.set("writer_file.latest", "${dir.absolutePath}/${Application.getName().lowercase()}.log") + Configuration.set("writer_file.file", "${dir.absolutePath}/{date:yyyy}-{date:MM}-{date:dd}.log") + } + } + + + private fun checkSingleton() { + val file = File(Application.getBaseDataDir(), "lock") + val pidFile = File(Application.getBaseDataDir(), "pid") + + + val raf = RandomAccessFile(file, "rw") + val lock = raf.channel.tryLock() + + if (lock != null) { + pidFile.writeText(ProcessHandle.current().pid().toString()) + pidFile.deleteOnExit() + file.deleteOnExit() + } else { + if (SystemInfo.isWindows && pidFile.exists()) { + val pid = NumberUtils.toLong(pidFile.readText()) + for (window in WindowUtils.getAllWindows(false)) { + if (pid > 0) { + val processId = IntByReference() + User32.INSTANCE.GetWindowThreadProcessId(window.hwnd, processId) + if (processId.value.toLong() != pid) { + continue + } + } else if (window.title != Application.getName() || window.filePath.endsWith("explorer.exe")) { + continue + } + User32.INSTANCE.ShowWindow(window.hwnd, User32.SW_SHOWNOACTIVATE) + User32.INSTANCE.SetForegroundWindow(window.hwnd) + break + } + } + + System.err.println("Program is already running") + exitProcess(1) + } + + singletonLock = lock + } + + + private fun openDatabase() { + val dir = Application.getDatabaseFile() + try { + Database.open(dir) + } catch (e: Exception) { + if (log.isErrorEnabled) { + log.error(e.message, e) + } + JOptionPane.showMessageDialog( + null, "Unable to open database", + I18n.getString("termora.title"), JOptionPane.ERROR_MESSAGE + ) + exitProcess(1) + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/app/termora/BannerPanel.kt b/src/main/kotlin/app/termora/BannerPanel.kt new file mode 100644 index 0000000..627f413 --- /dev/null +++ b/src/main/kotlin/app/termora/BannerPanel.kt @@ -0,0 +1,68 @@ +package app.termora + +import com.formdev.flatlaf.FlatLaf +import org.apache.commons.lang3.RandomUtils +import java.awt.* +import javax.swing.JComponent +import javax.swing.UIManager + +class BannerPanel(fontSize: Int = 11, val beautiful: Boolean = false) : JComponent() { + private val banner = """ + ______ + /_ __/__ _________ ___ ____ _________ _ + / / / _ \/ ___/ __ `__ \/ __ \/ ___/ __ `/ + / / / __/ / / / / / / / /_/ / / / /_/ / +/_/ \___/_/ /_/ /_/ /_/\____/_/ \__,_/ +""".trimIndent().lines() + + private val colors = mutableListOf() + + init { + font = Font("JetBrains Mono", Font.PLAIN, fontSize) + preferredSize = Dimension(width, getFontMetrics(font).height * banner.size) + size = preferredSize + } + + override fun paintComponent(g: Graphics) { + if (g is Graphics2D) { + g.setRenderingHints( + RenderingHints( + RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON + ) + ) + } + + g.font = font + g.color = UIManager.getColor("TextField.placeholderForeground") + + val height = g.fontMetrics.height + val descent = g.fontMetrics.descent + val offset = width / 2 - g.fontMetrics.stringWidth(banner.maxBy { it.length }) / 2 + val insecure = RandomUtils.insecure() + var index = 0 + + for (i in banner.indices) { + var x = offset + val y = height * (i + 1) - descent + val chars = banner[i].toCharArray() + for (j in chars.indices) { + if (beautiful) { + if (colors.size <= index) { + colors.add( + Color( + insecure.randomInt(0, 255), + insecure.randomInt(0, 255), + insecure.randomInt(0, 255) + ) + ) + } + val color = colors[index++] + g.color = if (FlatLaf.isLafDark()) color.brighter() else color.darker() + } + g.drawChars(chars, j, 1, x, y) + x += g.fontMetrics.charWidth(chars[j]) + } + } + + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/termora/ChannelShellPtyConnector.kt b/src/main/kotlin/app/termora/ChannelShellPtyConnector.kt new file mode 100644 index 0000000..ddd11a7 --- /dev/null +++ b/src/main/kotlin/app/termora/ChannelShellPtyConnector.kt @@ -0,0 +1,41 @@ +package app.termora + +import app.termora.terminal.StreamPtyConnector +import org.apache.sshd.client.channel.ChannelShell +import org.apache.sshd.client.channel.ClientChannelEvent +import java.io.InputStreamReader +import java.nio.charset.Charset + +class ChannelShellPtyConnector( + private val channel: ChannelShell, + private val charset: Charset = Charsets.UTF_8 +) : StreamPtyConnector(channel.invertedOut, channel.invertedIn) { + + private val reader = InputStreamReader(input, charset) + + override fun read(buffer: CharArray): Int { + return reader.read(buffer) + } + + override fun write(buffer: ByteArray, offset: Int, len: Int) { + output.write(buffer, offset, len) + output.flush() + } + + override fun write(buffer: String) { + write(buffer.toByteArray(charset)) + } + + override fun resize(rows: Int, cols: Int) { + channel.sendWindowChange(cols, rows) + } + + override fun waitFor(): Int { + channel.waitFor(listOf(ClientChannelEvent.CLOSED), Long.MAX_VALUE) + return channel.exitStatus + } + + override fun close() { + channel.close(true) + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/termora/Crypto.kt b/src/main/kotlin/app/termora/Crypto.kt new file mode 100644 index 0000000..f419e9b --- /dev/null +++ b/src/main/kotlin/app/termora/Crypto.kt @@ -0,0 +1,155 @@ +package app.termora + +import org.apache.commons.codec.binary.Base64 +import org.apache.commons.lang3.RandomUtils +import org.slf4j.LoggerFactory +import java.security.* +import java.security.spec.PKCS8EncodedKeySpec +import java.security.spec.X509EncodedKeySpec +import javax.crypto.Cipher +import javax.crypto.SecretKeyFactory +import javax.crypto.spec.IvParameterSpec +import javax.crypto.spec.PBEKeySpec +import javax.crypto.spec.SecretKeySpec +import kotlin.time.measureTime + +object AES { + private const val ALGORITHM = "AES" + + /** + * ECB 没有 IV + */ + object ECB { + private const val TRANSFORMATION = "AES/ECB/PKCS5Padding" + + fun encrypt(key: ByteArray, data: ByteArray): ByteArray { + val cipher = Cipher.getInstance(TRANSFORMATION) + cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(key, ALGORITHM)) + return cipher.doFinal(data) + } + + fun decrypt(key: ByteArray, data: ByteArray): ByteArray { + val cipher = Cipher.getInstance(TRANSFORMATION) + cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(key, ALGORITHM)) + return cipher.doFinal(data) + } + + } + + /** + * 携带 IV + */ + object CBC { + private const val TRANSFORMATION = "AES/CBC/PKCS5Padding" + + fun encrypt(key: ByteArray, iv: ByteArray, data: ByteArray): ByteArray { + val cipher = Cipher.getInstance(TRANSFORMATION) + cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(key, ALGORITHM), IvParameterSpec(iv)) + return cipher.doFinal(data) + } + + fun decrypt(key: ByteArray, iv: ByteArray, data: ByteArray): ByteArray { + val cipher = Cipher.getInstance(TRANSFORMATION) + cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(key, ALGORITHM), IvParameterSpec(iv)) + return cipher.doFinal(data) + } + + + fun String.aesCBCEncrypt(key: ByteArray, iv: ByteArray): ByteArray { + return encrypt(key, iv, toByteArray()) + } + + fun ByteArray.aesCBCEncrypt(key: ByteArray, iv: ByteArray): ByteArray { + return encrypt(key, iv, this) + } + + fun ByteArray.aesCBCDecrypt(key: ByteArray, iv: ByteArray): ByteArray { + return decrypt(key, iv, this) + } + + } + + fun randomBytes(size: Int = 32): ByteArray { + return RandomUtils.secureStrong().randomBytes(size) + } + + fun ByteArray.encodeBase64String(): String { + return Base64.encodeBase64String(this) + } + + fun String.decodeBase64(): ByteArray { + return Base64.decodeBase64(this) + } +} + + +object PBKDF2 { + + private const val ALGORITHM = "PBKDF2WithHmacSHA512" + private val log = LoggerFactory.getLogger(PBKDF2::class.java) + + fun generateSecret( + password: CharArray, + salt: ByteArray, + iterationCount: Int = 150000, + keyLength: Int = 256 + ): ByteArray { + val bytes: ByteArray + val time = measureTime { + bytes = SecretKeyFactory.getInstance(ALGORITHM) + .generateSecret(PBEKeySpec(password, salt, iterationCount, keyLength)) + .encoded + } + if (log.isDebugEnabled) { + log.debug("Secret generated $time") + } + return bytes + } + +} + + +object RSA { + + private const val TRANSFORMATION = "RSA" + + fun encrypt(publicKey: PublicKey, data: ByteArray): ByteArray { + val cipher = Cipher.getInstance(TRANSFORMATION) + cipher.init(Cipher.ENCRYPT_MODE, publicKey) + return cipher.doFinal(data) + } + + fun decrypt(privateKey: PrivateKey, data: ByteArray): ByteArray { + val cipher = Cipher.getInstance(TRANSFORMATION) + cipher.init(Cipher.DECRYPT_MODE, privateKey) + return cipher.doFinal(data) + } + + fun encrypt(privateKey: PrivateKey, data: ByteArray): ByteArray { + val cipher = Cipher.getInstance(TRANSFORMATION) + cipher.init(Cipher.ENCRYPT_MODE, privateKey) + return cipher.doFinal(data) + } + + fun decrypt(publicKey: PublicKey, data: ByteArray): ByteArray { + val cipher = Cipher.getInstance(TRANSFORMATION) + cipher.init(Cipher.DECRYPT_MODE, publicKey) + return cipher.doFinal(data) + } + + fun generatePublic(publicKey: ByteArray): PublicKey { + return KeyFactory.getInstance(TRANSFORMATION) + .generatePublic(X509EncodedKeySpec(publicKey)) + } + + fun generatePrivate(privateKey: ByteArray): PrivateKey { + return KeyFactory.getInstance(TRANSFORMATION) + .generatePrivate(PKCS8EncodedKeySpec(privateKey)) + } + + fun generateKeyPair(keySize: Int = 2048): KeyPair { + val generator = KeyPairGenerator.getInstance(TRANSFORMATION) + generator.initialize(keySize) + return generator.generateKeyPair() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/termora/DialogWrapper.kt b/src/main/kotlin/app/termora/DialogWrapper.kt new file mode 100644 index 0000000..f674aa8 --- /dev/null +++ b/src/main/kotlin/app/termora/DialogWrapper.kt @@ -0,0 +1,200 @@ +package app.termora + +import com.formdev.flatlaf.FlatClientProperties +import com.formdev.flatlaf.FlatLaf +import com.formdev.flatlaf.util.SystemInfo +import com.jetbrains.JBR +import java.awt.BorderLayout +import java.awt.Dimension +import java.awt.Window +import java.awt.event.ActionEvent +import java.awt.event.KeyEvent +import java.awt.event.WindowAdapter +import java.awt.event.WindowEvent +import javax.swing.* + +abstract class DialogWrapper(owner: Window?) : JDialog(owner) { + private val rootPanel = JPanel(BorderLayout()) + private val titleLabel = JLabel() + private val titleBar by lazy { LogicCustomTitleBar.createCustomTitleBar(this) } + val disposable = Disposer.newDisposable() + + companion object { + const val DEFAULT_ACTION = "DEFAULT_ACTION" + } + + + protected var controlsVisible = true + set(value) { + field = value + titleBar.putProperty("controls.visible", value) + } + + protected var titleBarHeight = UIManager.getInt("TabbedPane.tabHeight").toFloat() + set(value) { + titleBar.height = value + field = value + } + + protected var lostFocusDispose = false + protected var escapeDispose = true + + protected fun init() { + + defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE + + initTitleBar() + initEvents() + + if (JBR.isWindowDecorationsSupported()) { + if (rootPane.getClientProperty(FlatClientProperties.TITLE_BAR_SHOW_TITLE) != false) { + val titlePanel = createTitlePanel() + if (titlePanel != null) { + rootPanel.add(titlePanel, BorderLayout.NORTH) + } + } + } + + rootPanel.add(createCenterPanel(), BorderLayout.CENTER) + + val southPanel = createSouthPanel() + if (southPanel != null) { + rootPanel.add(southPanel, BorderLayout.SOUTH) + } + + rootPane.contentPane = rootPanel + } + + protected open fun createSouthPanel(): JComponent? { + val box = Box.createHorizontalBox() + box.border = BorderFactory.createCompoundBorder( + BorderFactory.createMatteBorder(1, 0, 0, 0, DynamicColor.BorderColor), + BorderFactory.createEmptyBorder(8, 12, 8, 12) + ) + + val okButton = createJButtonForAction(createOkAction()) + box.add(Box.createHorizontalGlue()) + box.add(createJButtonForAction(CancelAction())) + box.add(Box.createHorizontalStrut(8)) + box.add(okButton) + + + return box + } + + protected open fun createOkAction(): AbstractAction { + return OkAction() + } + + protected open fun createJButtonForAction(action: Action): JButton { + val button = JButton(action) + val value = action.getValue(DEFAULT_ACTION) + if (value is Boolean && value) { + rootPane.defaultButton = button + } + return button + } + + protected open fun createTitlePanel(): JPanel? { + titleLabel.horizontalAlignment = SwingConstants.CENTER + titleLabel.verticalAlignment = SwingConstants.CENTER + titleLabel.text = title + titleLabel.putClientProperty("FlatLaf.style", "font: bold") + + val panel = JPanel(BorderLayout()) + panel.add(titleLabel, BorderLayout.CENTER) + panel.preferredSize = Dimension(-1, titleBar.height.toInt()) + + + return panel + } + + override fun setTitle(title: String?) { + super.setTitle(title) + titleLabel.text = title + } + + protected abstract fun createCenterPanel(): JComponent + + private fun initEvents() { + + val inputMap = rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW) + if (escapeDispose) { + inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "close") + } + + inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, toolkit.menuShortcutKeyMaskEx), "close") + + rootPane.actionMap.put("close", object : AnAction() { + override fun actionPerformed(e: ActionEvent) { + doCancelAction() + } + }) + + addWindowFocusListener(object : WindowAdapter() { + override fun windowLostFocus(e: WindowEvent) { + if (lostFocusDispose) { + SwingUtilities.invokeLater { doCancelAction() } + } + } + }) + + addWindowListener(object : WindowAdapter() { + override fun windowClosed(e: WindowEvent) { + Disposer.dispose(disposable) + } + }) + + if (SystemInfo.isWindows) { + addWindowListener(object : WindowAdapter(), ThemeChangeListener { + override fun windowClosed(e: WindowEvent) { + ThemeManager.instance.removeThemeChangeListener(this) + } + + override fun windowOpened(e: WindowEvent) { + onChanged() + ThemeManager.instance.addThemeChangeListener(this) + } + + override fun onChanged() { + titleBar.putProperty("controls.dark", FlatLaf.isLafDark()) + } + }) + } + } + + private fun initTitleBar() { + titleBar.height = titleBarHeight + titleBar.putProperty("controls.visible", controlsVisible) + if (JBR.isWindowDecorationsSupported()) { + JBR.getWindowDecorations().setCustomTitleBar(this, titleBar) + } + } + + protected open fun doOKAction() { + dispose() + } + + protected open fun doCancelAction() { + dispose() + } + + protected inner class OkAction(text: String = I18n.getString("termora.confirm")) : AnAction(text) { + init { + putValue(DEFAULT_ACTION, true) + } + + override fun actionPerformed(e: ActionEvent) { + doOKAction() + } + + } + + protected inner class CancelAction : AnAction(I18n.getString("termora.cancel")) { + + override fun actionPerformed(e: ActionEvent) { + doCancelAction() + } + + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/termora/DocumentAdaptor.kt b/src/main/kotlin/app/termora/DocumentAdaptor.kt new file mode 100644 index 0000000..2dcf61f --- /dev/null +++ b/src/main/kotlin/app/termora/DocumentAdaptor.kt @@ -0,0 +1,18 @@ +package app.termora + +import javax.swing.event.DocumentEvent +import javax.swing.event.DocumentListener + +abstract class DocumentAdaptor : DocumentListener { + override fun insertUpdate(e: DocumentEvent) { + changedUpdate(e) + } + + override fun removeUpdate(e: DocumentEvent) { + changedUpdate(e) + } + + override fun changedUpdate(e: DocumentEvent) { + + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/termora/Doorman.kt b/src/main/kotlin/app/termora/Doorman.kt new file mode 100644 index 0000000..89314da --- /dev/null +++ b/src/main/kotlin/app/termora/Doorman.kt @@ -0,0 +1,85 @@ +package app.termora + +import app.termora.AES.decodeBase64 +import app.termora.AES.encodeBase64String +import app.termora.db.Database + +class PasswordWrongException : RuntimeException() + +class Doorman private constructor() { + private val properties get() = Database.instance.properties + private var key = byteArrayOf() + + companion object { + val instance by lazy { Doorman() } + } + + fun isWorking(): Boolean { + return properties.getString("doorman", "false").toBoolean() + } + + fun encrypt(text: String): String { + checkIsWorking() + return AES.ECB.encrypt(key, text.toByteArray()).encodeBase64String() + } + + + fun decrypt(text: String): String { + checkIsWorking() + return AES.ECB.decrypt(key, text.decodeBase64()).decodeToString() + } + + /** + * @return 返回钥匙 + */ + fun work(password: CharArray): ByteArray { + if (key.isNotEmpty()) { + throw IllegalStateException("Working") + } + return work(convertKey(password)) + } + + fun work(key: ByteArray): ByteArray { + val verify = properties.getString("doorman-verify") + if (verify == null) { + properties.putString( + "doorman-verify", + AES.ECB.encrypt(key, factor()).encodeBase64String() + ) + } else { + try { + if (!AES.ECB.decrypt(key, verify.decodeBase64()).contentEquals(factor())) { + throw PasswordWrongException() + } + } catch (e: Exception) { + throw PasswordWrongException() + } + } + + this.key = key + properties.putString("doorman", "true") + + return this.key + } + + + private fun convertKey(password: CharArray): ByteArray { + return PBKDF2.generateSecret(password, factor()) + } + + + private fun checkIsWorking() { + if (key.isEmpty() || !isWorking()) { + throw UnsupportedOperationException("Doorman is not working") + } + } + + private fun factor(): ByteArray { + return Application.getName().toByteArray() + } + + fun test(password: CharArray): Boolean { + checkIsWorking() + return key.contentEquals(convertKey(password)) + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/termora/DoormanDialog.kt b/src/main/kotlin/app/termora/DoormanDialog.kt new file mode 100644 index 0000000..7795dd7 --- /dev/null +++ b/src/main/kotlin/app/termora/DoormanDialog.kt @@ -0,0 +1,309 @@ +package app.termora + +import app.termora.AES.decodeBase64 +import app.termora.db.Database +import app.termora.terminal.ControlCharacters +import cash.z.ecc.android.bip39.Mnemonics +import com.formdev.flatlaf.FlatClientProperties +import com.formdev.flatlaf.extras.FlatSVGIcon +import com.formdev.flatlaf.extras.components.FlatButton +import com.formdev.flatlaf.extras.components.FlatLabel +import com.formdev.flatlaf.util.SystemInfo +import com.jgoodies.forms.builder.FormBuilder +import com.jgoodies.forms.layout.FormLayout +import org.apache.commons.lang3.StringUtils +import org.jdesktop.swingx.JXHyperlink +import org.slf4j.LoggerFactory +import java.awt.Dimension +import java.awt.Window +import java.awt.datatransfer.DataFlavor +import java.awt.event.ActionEvent +import java.awt.event.KeyAdapter +import java.awt.event.KeyEvent +import javax.imageio.ImageIO +import javax.swing.* +import kotlin.math.max + +class DoormanDialog(owner: Window?) : DialogWrapper(owner) { + companion object { + private val log = LoggerFactory.getLogger(DoormanDialog::class.java) + } + + private val formMargin = "7dlu" + private val label = FlatLabel() + private val icon = JLabel() + private val passwordTextField = OutlinePasswordField() + private val tip = FlatLabel() + private val safeBtn = FlatButton() + + var isOpened = false + + init { + size = Dimension(UIManager.getInt("Dialog.width") - 200, UIManager.getInt("Dialog.height") - 150) + isModal = true + isResizable = false + controlsVisible = false + + if (SystemInfo.isWindows || SystemInfo.isLinux) { + title = I18n.getString("termora.doorman.safe") + rootPane.putClientProperty(FlatClientProperties.TITLE_BAR_SHOW_TITLE, false) + } + + + if (SystemInfo.isWindows || SystemInfo.isLinux) { + val sizes = listOf(16, 20, 24, 28, 32, 48, 64) + val loader = TermoraFrame::class.java.classLoader + val images = sizes.mapNotNull { e -> + loader.getResourceAsStream("icons/termora_${e}x${e}.png")?.use { ImageIO.read(it) } + } + iconImages = images + } + + setLocationRelativeTo(null) + init() + } + + override fun createCenterPanel(): JComponent { + label.text = I18n.getString("termora.doorman.safe") + tip.text = I18n.getString("termora.doorman.unlock-data") + icon.icon = FlatSVGIcon(Icons.role.name, 80, 80) + safeBtn.icon = Icons.unlocked + + + label.labelType = FlatLabel.LabelType.h2 + label.horizontalAlignment = SwingConstants.CENTER + safeBtn.isFocusable = false + tip.foreground = UIManager.getColor("TextField.placeholderForeground") + icon.horizontalAlignment = SwingConstants.CENTER + + + safeBtn.addActionListener { doOKAction() } + passwordTextField.addActionListener { doOKAction() } + + var rows = 2 + val step = 2 + return FormBuilder.create().debug(false) + .layout( + FormLayout( + "$formMargin, default:grow, 4dlu, pref, $formMargin", + "${if (SystemInfo.isWindows) "20dlu" else "0dlu"}, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin" + ) + ) + .add(icon).xyw(2, rows, 4).apply { rows += step } + .add(label).xyw(2, rows, 4).apply { rows += step } + .add(passwordTextField).xy(2, rows) + .add(safeBtn).xy(4, rows).apply { rows += step } + .add(tip).xyw(2, rows, 4, "center, fill").apply { rows += step } + .add(JXHyperlink(object : AnAction(I18n.getString("termora.doorman.forget-password")) { + override fun actionPerformed(e: ActionEvent) { + val option = OptionPane.showConfirmDialog( + this@DoormanDialog, I18n.getString("termora.doorman.forget-password-message"), + options = arrayOf( + I18n.getString("termora.doorman.have-a-mnemonic"), + I18n.getString("termora.doorman.dont-have-a-mnemonic"), + ), + optionType = JOptionPane.YES_NO_OPTION, + messageType = JOptionPane.INFORMATION_MESSAGE, + initialValue = I18n.getString("termora.doorman.have-a-mnemonic") + ) + if (option == JOptionPane.YES_OPTION) { + showMnemonicsDialog() + } else if (option == JOptionPane.NO_OPTION) { + OptionPane.showMessageDialog( + this@DoormanDialog, + I18n.getString("termora.doorman.delete-data"), + messageType = JOptionPane.WARNING_MESSAGE + ) + Application.browse(Application.getDatabaseFile().toURI()) + } + } + }).apply { isFocusable = false }).xyw(2, rows, 4, "center, fill") + .build() + } + + private fun showMnemonicsDialog() { + val dialog = MnemonicsDialog(this@DoormanDialog) + dialog.isVisible = true + val entropy = dialog.entropy + if (entropy.isEmpty()) { + return + } + + try { + val keyBackup = Database.instance.properties.getString("doorman-key-backup") + ?: throw IllegalStateException("doorman-key-backup is null") + val key = AES.ECB.decrypt(entropy, keyBackup.decodeBase64()) + Doorman.instance.work(key) + } catch (e: Exception) { + OptionPane.showMessageDialog( + this, I18n.getString("termora.doorman.mnemonic-data-corrupted"), + messageType = JOptionPane.ERROR_MESSAGE + ) + passwordTextField.outline = "error" + passwordTextField.requestFocus() + return + } + + isOpened = true + super.doOKAction() + + } + + override fun doOKAction() { + if (passwordTextField.password.isEmpty()) { + passwordTextField.outline = "error" + passwordTextField.requestFocus() + return + } + + try { + Doorman.instance.work(passwordTextField.password) + } catch (e: Exception) { + if (e is PasswordWrongException) { + OptionPane.showMessageDialog( + this, I18n.getString("termora.doorman.password-wrong"), + messageType = JOptionPane.ERROR_MESSAGE + ) + } + passwordTextField.outline = "error" + passwordTextField.requestFocus() + return + } + + isOpened = true + + super.doOKAction() + } + + fun open(): Boolean { + isModal = true + isVisible = true + return isOpened + } + + + private class MnemonicsDialog(owner: Window) : DialogWrapper(owner) { + + private val textFields = (1..12).map { PasteTextField(it) } + var entropy = byteArrayOf() + private set + + init { + isModal = true + isResizable = true + controlsVisible = false + title = I18n.getString("termora.doorman.mnemonic.title") + init() + pack() + size = Dimension(max(size.width, UIManager.getInt("Dialog.width") - 250), size.height) + setLocationRelativeTo(null) + } + + fun getWords(): List { + val words = mutableListOf() + for (e in textFields) { + if (e.text.isBlank()) { + return emptyList() + } + words.add(e.text) + } + return words + } + + override fun createCenterPanel(): JComponent { + val formMargin = "4dlu" + val layout = FormLayout( + "default:grow, $formMargin, default:grow, $formMargin, default:grow, $formMargin, default:grow", + "pref, $formMargin, pref, $formMargin, pref" + ) + + val builder = FormBuilder.create().padding("0, $formMargin, $formMargin, $formMargin") + .layout(layout).debug(true) + val iterator = textFields.iterator() + for (i in 1..5 step 2) { + for (j in 1..7 step 2) { + builder.add(iterator.next()).xy(j, i) + } + } + + return builder.build() + } + + override fun doOKAction() { + for (textField in textFields) { + if (textField.text.isBlank()) { + textField.outline = "error" + textField.requestFocusInWindow() + return + } + } + + try { + Mnemonics.MnemonicCode(getWords().joinToString(StringUtils.SPACE)).use { + it.validate() + entropy = it.toEntropy() + } + } catch (e: Exception) { + OptionPane.showMessageDialog( + this, + I18n.getString("termora.doorman.mnemonic.incorrect"), + messageType = JOptionPane.ERROR_MESSAGE + ) + return + } + + + super.doOKAction() + } + + override fun doCancelAction() { + entropy = byteArrayOf() + super.doCancelAction() + } + + private inner class PasteTextField(private val index: Int) : OutlineTextField() { + init { + addKeyListener(object : KeyAdapter() { + override fun keyPressed(e: KeyEvent) { + if (e.keyCode == KeyEvent.VK_BACK_SPACE) { + if (text.isEmpty() && index != 1) { + textFields[index - 2].requestFocusInWindow() + } + } + } + }) + } + + override fun paste() { + if (!toolkit.systemClipboard.isDataFlavorAvailable(DataFlavor.stringFlavor)) { + return + } + + val text = toolkit.systemClipboard.getData(DataFlavor.stringFlavor)?.toString() ?: return + if (text.isBlank()) { + return + } + val words = mutableListOf() + if (text.count { it == ControlCharacters.SP } > text.count { it == ControlCharacters.LF }) { + words.addAll(text.split(StringUtils.SPACE)) + } else { + words.addAll(text.split(ControlCharacters.LF)) + } + val iterator = words.iterator() + for (i in index..textFields.size) { + if (iterator.hasNext()) { + textFields[i - 1].text = iterator.next() + textFields[i - 1].requestFocusInWindow() + } else { + break + } + } + } + } + } + + + override fun createSouthPanel(): JComponent? { + return null + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/termora/DynamicColor.kt b/src/main/kotlin/app/termora/DynamicColor.kt new file mode 100644 index 0000000..31faa2a --- /dev/null +++ b/src/main/kotlin/app/termora/DynamicColor.kt @@ -0,0 +1,63 @@ +package app.termora + +import com.formdev.flatlaf.FlatLaf +import java.awt.Color +import javax.swing.UIManager + +open class DynamicColor : Color { + private var regular: Color? + private val dark: Color? + private var colorKey: String? = null + private val color: Color + get() { + val r = regular + val d = dark + if (r == null || d == null) { + return UIManager.getColor(colorKey) + } + return if (FlatLaf.isLafDark()) d else r + } + + constructor(regular: Color, dark: Color) : super(regular.rgb, regular.alpha != 255) { + this.regular = regular + this.dark = dark + } + + companion object { + val BorderColor = DynamicColor("Component.borderColor") + } + + constructor(key: String) : super(0) { + this.regular = null + this.dark = null + this.colorKey = key + } + + override fun getRed(): Int { + return color.red + } + + override fun getGreen(): Int { + return color.green + } + + override fun getBlue(): Int { + return color.blue + } + + override fun getAlpha(): Int { + return color.alpha + } + + override fun getRGB(): Int { + return color.rgb + } + + override fun brighter(): Color { + return color.brighter() + } + + override fun darker(): Color { + return color.darker() + } +} diff --git a/src/main/kotlin/app/termora/DynamicIcon.kt b/src/main/kotlin/app/termora/DynamicIcon.kt new file mode 100644 index 0000000..2ef2cb7 --- /dev/null +++ b/src/main/kotlin/app/termora/DynamicIcon.kt @@ -0,0 +1,10 @@ +package app.termora + +import com.formdev.flatlaf.extras.FlatSVGIcon + +open class DynamicIcon(name: String, private val darkName: String) : FlatSVGIcon(name) { + constructor(name: String) : this(name, name) + + val dark by lazy { DynamicIcon(darkName, name) } + +} diff --git a/src/main/kotlin/app/termora/EditHostOptionsPane.kt b/src/main/kotlin/app/termora/EditHostOptionsPane.kt new file mode 100644 index 0000000..8a41fbb --- /dev/null +++ b/src/main/kotlin/app/termora/EditHostOptionsPane.kt @@ -0,0 +1,54 @@ +package app.termora + +import app.termora.keymgr.KeyManager +import app.termora.keymgr.OhKeyPair + +class EditHostOptionsPane(private val host: Host) : HostOptionsPane() { + init { + generalOption.portTextField.value = host.port + generalOption.nameTextField.text = host.name + generalOption.protocolTypeComboBox.selectedItem = host.protocol + generalOption.usernameTextField.text = host.username + generalOption.hostTextField.text = host.host + generalOption.passwordTextField.text = host.authentication.password + generalOption.remarkTextArea.text = host.remark + generalOption.authenticationTypeComboBox.selectedItem = host.authentication.type + if (host.authentication.type == AuthenticationType.PublicKey) { + val ohKeyPair = KeyManager.instance.getOhKeyPair(host.authentication.password) + if (ohKeyPair != null) { + generalOption.publicKeyTextField.text = ohKeyPair.name + generalOption.publicKeyTextField.putClientProperty(OhKeyPair::class, ohKeyPair) + } + } + + proxyOption.proxyTypeComboBox.selectedItem = host.proxy.type + proxyOption.proxyHostTextField.text = host.proxy.host + proxyOption.proxyPasswordTextField.text = host.proxy.password + proxyOption.proxyUsernameTextField.text = host.proxy.username + proxyOption.proxyPortTextField.value = host.proxy.port + proxyOption.proxyAuthenticationTypeComboBox.selectedItem = host.proxy.authenticationType + + terminalOption.charsetComboBox.selectedItem = host.options.encoding + terminalOption.environmentTextArea.text = host.options.env + terminalOption.startupCommandTextField.text = host.options.startupCommand + + tunnelingOption.tunnelings.addAll(host.tunnelings) + } + + override fun getHost(): Host { + val newHost = super.getHost() + return host.copy( + name = newHost.name, + protocol = newHost.protocol, + host = newHost.host, + port = newHost.port, + username = newHost.username, + authentication = newHost.authentication, + proxy = newHost.proxy, + remark = newHost.remark, + updateDate = System.currentTimeMillis(), + options = newHost.options, + tunnelings = newHost.tunnelings, + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/termora/Host.kt b/src/main/kotlin/app/termora/Host.kt new file mode 100644 index 0000000..54911c4 --- /dev/null +++ b/src/main/kotlin/app/termora/Host.kt @@ -0,0 +1,236 @@ +package app.termora + +import kotlinx.serialization.Serializable +import org.apache.commons.lang3.StringUtils +import java.util.* + + +fun UUID.toSimpleString(): String { + return toString().replace("-", StringUtils.EMPTY) +} + +enum class Protocol { + Folder, + SSH, + Local, +} + + +enum class AuthenticationType { + No, + Password, + PublicKey, + KeyboardInteractive, +} + +enum class ProxyType { + No, + HTTP, + SOCKS5, +} + +@Serializable +data class Authentication( + val type: AuthenticationType, + val password: String, +) { + companion object { + val No = Authentication(AuthenticationType.No, String()) + } +} + + +@Serializable +data class Options( + /** + * 跳板机 + */ + val jumpHosts: List = mutableListOf(), + /** + * 编码 + */ + val encoding: String = "UTF-8", + /** + * 环境变量 + */ + val env: String = StringUtils.EMPTY, + /** + * 连接成功后立即发送命令 + */ + val startupCommand: String = StringUtils.EMPTY, +) { + companion object { + val Default = Options() + } + + fun envs(): Map { + if (env.isBlank()) return emptyMap() + val envs = mutableMapOf() + for (line in env.lines()) { + if (line.isBlank()) continue + val vars = line.split("=", limit = 2) + if (vars.size != 2) continue + envs[vars[0]] = vars[1] + } + return envs + } +} + +@Serializable +data class Proxy( + val type: ProxyType, + val host: String, + val port: Int, + val authenticationType: AuthenticationType = AuthenticationType.No, + val username: String, + val password: String, +) { + companion object { + val No = Proxy( + ProxyType.No, + host = StringUtils.EMPTY, + port = 7890, + username = StringUtils.EMPTY, + password = StringUtils.EMPTY + ) + } +} + +enum class TunnelingType { + Local, + Remote, + Dynamic +} + +@Serializable +data class Tunneling( + val name: String = StringUtils.EMPTY, + val type: TunnelingType = TunnelingType.Local, + val sourceHost: String = StringUtils.EMPTY, + val sourcePort: Int = 0, + val destinationHost: String = StringUtils.EMPTY, + val destinationPort: Int = 0, +) + + +@Serializable +data class EncryptedHost( + var id: String = StringUtils.EMPTY, + var name: String = StringUtils.EMPTY, + var protocol: String = StringUtils.EMPTY, + var host: String = StringUtils.EMPTY, + var port: String = StringUtils.EMPTY, + var username: String = StringUtils.EMPTY, + var remark: String = StringUtils.EMPTY, + var authentication: String = StringUtils.EMPTY, + var proxy: String = StringUtils.EMPTY, + var options: String = StringUtils.EMPTY, + var tunnelings: String = StringUtils.EMPTY, + var sort: Long = 0L, + var deleted: Boolean = false, + var parentId: String = StringUtils.EMPTY, + var ownerId: String = StringUtils.EMPTY, + var creatorId: String = StringUtils.EMPTY, + var createDate: Long = 0L, + var updateDate: Long = 0L, +) + + +@Serializable +data class Host( + /** + * 唯一ID + */ + val id: String = UUID.randomUUID().toSimpleString(), + /** + * 名称 + */ + val name: String, + /** + * 协议 + */ + val protocol: Protocol, + /** + * 主机 + */ + val host: String = StringUtils.EMPTY, + /** + * 端口 + */ + val port: Int = 0, + /** + * 用户名 + */ + val username: String = StringUtils.EMPTY, + /** + * 备注 + */ + val remark: String = StringUtils.EMPTY, + /** + * 认证信息 + */ + val authentication: Authentication = Authentication.No, + /** + * 代理 + */ + val proxy: Proxy = Proxy.No, + + /** + * 选项,备用字段 + */ + val options: Options = Options.Default, + + /** + * 隧道 + */ + val tunnelings: List = emptyList(), + + /** + * 排序 + */ + val sort: Long = 0, + /** + * 父ID + */ + val parentId: String = "0", + /** + * 所属者 + */ + val ownerId: String = "0", + /** + * 创建者 + */ + val creatorId: String = "0", + /** + * 创建时间 + */ + val createDate: Long = System.currentTimeMillis(), + /** + * 更新时间 + */ + val updateDate: Long = System.currentTimeMillis(), + + /** + * 是否已经删除 + */ + val deleted: Boolean = false +) { + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Host + + if (id != other.id) return false + if (ownerId != other.ownerId) return false + + return true + } + + override fun hashCode(): Int { + var result = id.hashCode() + result = 31 * result + ownerId.hashCode() + return result + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/termora/HostDialog.kt b/src/main/kotlin/app/termora/HostDialog.kt new file mode 100644 index 0000000..5c2989c --- /dev/null +++ b/src/main/kotlin/app/termora/HostDialog.kt @@ -0,0 +1,46 @@ +package app.termora + +import java.awt.BorderLayout +import java.awt.Dimension +import java.awt.Window +import javax.swing.BorderFactory +import javax.swing.JComponent +import javax.swing.JPanel +import javax.swing.UIManager + +class HostDialog(owner: Window, host: Host? = null) : DialogWrapper(owner) { + private val pane = if (host != null) EditHostOptionsPane(host) else HostOptionsPane() + var host: Host? = host + private set + + init { + size = Dimension(UIManager.getInt("Dialog.width"), UIManager.getInt("Dialog.height")) + isModal = true + title = I18n.getString("termora.new-host.title") + setLocationRelativeTo(null) + + init() + } + + override fun createCenterPanel(): JComponent { + pane.background = UIManager.getColor("window") + + val panel = JPanel(BorderLayout()) + panel.add(pane, BorderLayout.CENTER) + panel.background = UIManager.getColor("window") + panel.border = BorderFactory.createMatteBorder(1, 0, 0, 0, DynamicColor.BorderColor) + + return panel + } + + + override fun doOKAction() { + if (!pane.validateFields()) { + return + } + host = pane.getHost() + super.doOKAction() + } + + +} \ No newline at end of file diff --git a/src/main/kotlin/app/termora/HostManager.kt b/src/main/kotlin/app/termora/HostManager.kt new file mode 100644 index 0000000..4abd1d6 --- /dev/null +++ b/src/main/kotlin/app/termora/HostManager.kt @@ -0,0 +1,54 @@ +package app.termora + +import app.termora.db.Database +import java.util.* + +interface HostListener : EventListener { + fun hostAdded(host: Host) {} + fun hostRemoved(id: String) {} + fun hostsChanged() {} +} + + +class HostManager private constructor() { + companion object { + val instance by lazy { HostManager() } + } + + private val database get() = Database.instance + private val listeners = mutableListOf() + + fun addHost(host: Host, notify: Boolean = true) { + assertEventDispatchThread() + database.addHost(host) + if (notify) listeners.forEach { it.hostAdded(host) } + } + + fun removeHost(id: String) { + assertEventDispatchThread() + database.removeHost(id) + listeners.forEach { it.hostRemoved(id) } + + } + + fun hosts(): List { + return database.getHosts() + .sortedWith(compareBy { if (it.protocol == Protocol.Folder) 0 else 1 }.thenBy { it.sort }) + } + + fun removeAll() { + assertEventDispatchThread() + database.removeAllHost() + listeners.forEach { it.hostsChanged() } + } + + fun addHostListener(listener: HostListener) { + listeners.add(listener) + } + + fun removeHostListener(listener: HostListener) { + listeners.remove(listener) + } + + +} \ No newline at end of file diff --git a/src/main/kotlin/app/termora/HostOptionsPane.kt b/src/main/kotlin/app/termora/HostOptionsPane.kt new file mode 100644 index 0000000..7238c29 --- /dev/null +++ b/src/main/kotlin/app/termora/HostOptionsPane.kt @@ -0,0 +1,842 @@ +package app.termora + +import app.termora.keymgr.KeyManagerDialog +import app.termora.keymgr.OhKeyPair +import com.formdev.flatlaf.FlatClientProperties +import com.formdev.flatlaf.extras.components.FlatComboBox +import com.formdev.flatlaf.ui.FlatTextBorder +import com.jgoodies.forms.builder.FormBuilder +import com.jgoodies.forms.layout.FormLayout +import org.apache.commons.lang3.StringUtils +import java.awt.* +import java.awt.event.* +import java.nio.charset.Charset +import javax.swing.* +import javax.swing.table.DefaultTableModel + + +open class HostOptionsPane : OptionsPane() { + protected val tunnelingOption = TunnelingOption() + protected val generalOption = GeneralOption() + protected val proxyOption = ProxyOption() + protected val terminalOption = TerminalOption() + protected val owner: Window? get() = SwingUtilities.getWindowAncestor(this) + + init { + addOption(generalOption) + addOption(proxyOption) + addOption(tunnelingOption) + addOption(terminalOption) + + setContentBorder(BorderFactory.createEmptyBorder(6, 8, 6, 8)) + } + + + open fun getHost(): Host { + val name = generalOption.nameTextField.text + val protocol = generalOption.protocolTypeComboBox.selectedItem as Protocol + val host = generalOption.hostTextField.text + val port = (generalOption.portTextField.value ?: 22) as Int + var authentication = Authentication.No + var proxy = Proxy.No + + if (generalOption.authenticationTypeComboBox.selectedItem == AuthenticationType.Password) { + authentication = authentication.copy( + type = AuthenticationType.Password, + password = String(generalOption.passwordTextField.password) + ) + } else if (generalOption.authenticationTypeComboBox.selectedItem == AuthenticationType.PublicKey) { + val keyPair = generalOption.publicKeyTextField.getClientProperty(OhKeyPair::class) as OhKeyPair? + authentication = authentication.copy( + type = AuthenticationType.PublicKey, + password = keyPair?.id ?: StringUtils.EMPTY + ) + } + + if (proxyOption.proxyTypeComboBox.selectedItem != ProxyType.No) { + proxy = proxy.copy( + type = proxyOption.proxyTypeComboBox.selectedItem as ProxyType, + host = proxyOption.proxyHostTextField.text, + username = proxyOption.proxyUsernameTextField.text, + password = String(proxyOption.proxyPasswordTextField.password), + port = proxyOption.proxyPortTextField.value as Int, + authenticationType = proxyOption.proxyAuthenticationTypeComboBox.selectedItem as AuthenticationType, + ) + } + + val options = Options.Default.copy( + encoding = terminalOption.charsetComboBox.selectedItem as String, + env = terminalOption.environmentTextArea.text, + startupCommand = terminalOption.startupCommandTextField.text + ) + + return Host( + name = name, + protocol = protocol, + host = host, + port = port, + username = generalOption.usernameTextField.text, + authentication = authentication, + proxy = proxy, + sort = System.currentTimeMillis(), + remark = generalOption.remarkTextArea.text, + options = options, + tunnelings = tunnelingOption.tunnelings + ) + } + + fun validateFields(): Boolean { + val host = getHost() + + // general + if (validateField(generalOption.nameTextField) + || validateField(generalOption.hostTextField) + ) { + return false + } + + if (host.protocol == Protocol.SSH) { + if (validateField(generalOption.usernameTextField)) { + return false + } + } + + if (host.authentication.type == AuthenticationType.Password) { + if (validateField(generalOption.passwordTextField)) { + return false + } + } else if (host.authentication.type == AuthenticationType.PublicKey) { + if (validateField(generalOption.publicKeyTextField)) { + return false + } + } + + // proxy + if (host.proxy.type != ProxyType.No) { + if (validateField(proxyOption.proxyHostTextField) + ) { + return false + } + + if (host.proxy.authenticationType != AuthenticationType.No) { + if (validateField(proxyOption.proxyUsernameTextField) + || validateField(proxyOption.proxyPasswordTextField) + ) { + return false + } + } + } + + + return true + } + + /** + * 返回 true 表示有错误 + */ + private fun validateField(textField: JTextField): Boolean { + if (textField.isEnabled && textField.text.isBlank()) { + selectOptionJComponent(textField) + textField.putClientProperty(FlatClientProperties.OUTLINE, FlatClientProperties.OUTLINE_ERROR) + textField.requestFocusInWindow() + return true + } + return false + } + + protected inner class GeneralOption : JPanel(BorderLayout()), Option { + val portTextField = PortSpinner() + val nameTextField = OutlineTextField(128) + val protocolTypeComboBox = FlatComboBox() + val usernameTextField = OutlineTextField(128) + val hostTextField = OutlineTextField(255) + private val passwordPanel = JPanel(BorderLayout()) + private val chooseKeyBtn = JButton(Icons.greyKey) + val passwordTextField = OutlinePasswordField(255) + val publicKeyTextField = OutlineTextField() + val remarkTextArea = FixedLengthTextArea(512) + val authenticationTypeComboBox = FlatComboBox() + + init { + initView() + initEvents() + } + + private fun initView() { + add(getCenterComponent(), BorderLayout.CENTER) + + publicKeyTextField.isEditable = false + chooseKeyBtn.isFocusable = false + + protocolTypeComboBox.renderer = object : DefaultListCellRenderer() { + override fun getListCellRendererComponent( + list: JList<*>?, + value: Any?, + index: Int, + isSelected: Boolean, + cellHasFocus: Boolean + ): Component { + return super.getListCellRendererComponent( + list, + value.toString().uppercase(), + index, + isSelected, + cellHasFocus + ) + } + } + + authenticationTypeComboBox.renderer = object : DefaultListCellRenderer() { + override fun getListCellRendererComponent( + list: JList<*>?, + value: Any?, + index: Int, + isSelected: Boolean, + cellHasFocus: Boolean + ): Component { + var text = value?.toString() ?: "" + when (value) { + AuthenticationType.Password -> { + text = "Password" + } + + AuthenticationType.PublicKey -> { + text = "Public Key" + } + + AuthenticationType.KeyboardInteractive -> { + text = "Keyboard Interactive" + } + } + return super.getListCellRendererComponent( + list, + text, + index, + isSelected, + cellHasFocus + ) + } + } + + protocolTypeComboBox.addItem(Protocol.SSH) + protocolTypeComboBox.addItem(Protocol.Local) + + authenticationTypeComboBox.addItem(AuthenticationType.No) + authenticationTypeComboBox.addItem(AuthenticationType.Password) + authenticationTypeComboBox.addItem(AuthenticationType.PublicKey) + + authenticationTypeComboBox.selectedItem = AuthenticationType.Password + + refreshStates() + } + + private fun initEvents() { + protocolTypeComboBox.addItemListener { + if (it.stateChange == ItemEvent.SELECTED) { + refreshStates() + } + } + + authenticationTypeComboBox.addItemListener { + if (it.stateChange == ItemEvent.SELECTED) { + refreshStates() + switchPasswordComponent() + } + } + + chooseKeyBtn.addActionListener { + chooseKeyPair() + } + + addComponentListener(object : ComponentAdapter() { + override fun componentResized(e: ComponentEvent) { + SwingUtilities.invokeLater { nameTextField.requestFocusInWindow() } + removeComponentListener(this) + } + }) + } + + private fun chooseKeyPair() { + val dialog = KeyManagerDialog( + SwingUtilities.getWindowAncestor(this), + selectMode = true, + ) + dialog.pack() + dialog.setLocationRelativeTo(null) + dialog.isVisible = true + if (dialog.ok) { + val lastKeyPair = dialog.getLasOhKeyPair() + if (lastKeyPair != null) { + publicKeyTextField.putClientProperty(OhKeyPair::class, lastKeyPair) + publicKeyTextField.text = lastKeyPair.name + publicKeyTextField.outline = null + } + } + } + + private fun refreshStates() { + hostTextField.isEnabled = true + portTextField.isEnabled = true + usernameTextField.isEnabled = true + authenticationTypeComboBox.isEnabled = true + passwordTextField.isEnabled = true + chooseKeyBtn.isEnabled = true + + if (protocolTypeComboBox.selectedItem == Protocol.Local) { + hostTextField.isEnabled = false + portTextField.isEnabled = false + usernameTextField.isEnabled = false + authenticationTypeComboBox.isEnabled = false + passwordTextField.isEnabled = false + chooseKeyBtn.isEnabled = false + } + + } + + override fun getIcon(isSelected: Boolean): Icon { + return Icons.settings + } + + override fun getTitle(): String { + return I18n.getString("termora.new-host.general") + } + + override fun getJComponent(): JComponent { + return this + } + + private fun getCenterComponent(): JComponent { + val layout = FormLayout( + "left:pref, $formMargin, default:grow, $formMargin, pref, $formMargin, default, default:grow", + "pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref" + ) + remarkTextArea.setFocusTraversalKeys( + KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, + KeyboardFocusManager.getCurrentKeyboardFocusManager() + .getDefaultFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS) + ) + remarkTextArea.setFocusTraversalKeys( + KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, + KeyboardFocusManager.getCurrentKeyboardFocusManager() + .getDefaultFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS) + ) + + remarkTextArea.rows = 8 + remarkTextArea.lineWrap = true + remarkTextArea.border = BorderFactory.createEmptyBorder(4, 4, 4, 4) + + switchPasswordComponent() + + var rows = 1 + val step = 2 + val panel = FormBuilder.create().layout(layout) + .add("${I18n.getString("termora.new-host.general.name")}:").xy(1, rows) + .add(nameTextField).xyw(3, rows, 5).apply { rows += step } + + .add("${I18n.getString("termora.new-host.general.protocol")}:").xy(1, rows) + .add(protocolTypeComboBox).xyw(3, rows, 5).apply { rows += step } + + .add("${I18n.getString("termora.new-host.general.host")}:").xy(1, rows) + .add(hostTextField).xy(3, rows) + .add("${I18n.getString("termora.new-host.general.port")}:").xy(5, rows) + .add(portTextField).xy(7, rows).apply { rows += step } + + .add("${I18n.getString("termora.new-host.general.username")}:").xy(1, rows) + .add(usernameTextField).xyw(3, rows, 5).apply { rows += step } + + .add("${I18n.getString("termora.new-host.general.authentication")}:").xy(1, rows) + .add(authenticationTypeComboBox).xyw(3, rows, 5).apply { rows += step } + + .add("${I18n.getString("termora.new-host.general.password")}:").xy(1, rows) + .add(passwordPanel).xyw(3, rows, 5).apply { rows += step } + + .add("${I18n.getString("termora.new-host.general.remark")}:").xy(1, rows) + .add(JScrollPane(remarkTextArea).apply { border = FlatTextBorder() }) + .xyw(3, rows, 5).apply { rows += step } + + .build() + + + return panel + } + + private fun switchPasswordComponent() { + passwordPanel.removeAll() + + if (authenticationTypeComboBox.selectedItem == AuthenticationType.PublicKey) { + passwordPanel.add( + FormBuilder.create() + .layout(FormLayout("default:grow, 4dlu, left:pref", "pref")) + .add(publicKeyTextField).xy(1, 1) + .add(chooseKeyBtn).xy(3, 1) + .build(), BorderLayout.CENTER + ) + } else { + passwordPanel.add(passwordTextField, BorderLayout.CENTER) + } + passwordPanel.revalidate() + passwordPanel.repaint() + } + } + + protected inner class ProxyOption : JPanel(BorderLayout()), Option { + val proxyTypeComboBox = FlatComboBox() + val proxyHostTextField = OutlineTextField() + val proxyPasswordTextField = OutlinePasswordField() + val proxyUsernameTextField = OutlineTextField() + val proxyPortTextField = PortSpinner(1080) + val proxyAuthenticationTypeComboBox = FlatComboBox() + + + init { + initView() + initEvents() + } + + private fun initView() { + add(getCenterComponent(), BorderLayout.CENTER) + proxyAuthenticationTypeComboBox.renderer = object : DefaultListCellRenderer() { + override fun getListCellRendererComponent( + list: JList<*>?, + value: Any?, + index: Int, + isSelected: Boolean, + cellHasFocus: Boolean + ): Component { + var text = value?.toString() ?: "" + when (value) { + AuthenticationType.Password -> { + text = "Password" + } + + AuthenticationType.PublicKey -> { + text = "Public Key" + } + + AuthenticationType.KeyboardInteractive -> { + text = "Keyboard Interactive" + } + } + return super.getListCellRendererComponent( + list, + text, + index, + isSelected, + cellHasFocus + ) + } + } + + proxyTypeComboBox.addItem(ProxyType.No) + proxyTypeComboBox.addItem(ProxyType.HTTP) + proxyTypeComboBox.addItem(ProxyType.SOCKS5) + + proxyAuthenticationTypeComboBox.addItem(AuthenticationType.No) + proxyAuthenticationTypeComboBox.addItem(AuthenticationType.Password) + + proxyUsernameTextField.text = "root" + + refreshStates() + } + + private fun initEvents() { + proxyTypeComboBox.addItemListener { + if (it.stateChange == ItemEvent.SELECTED) { + refreshStates() + } + } + proxyAuthenticationTypeComboBox.addItemListener { + if (it.stateChange == ItemEvent.SELECTED) { + refreshStates() + } + } + } + + private fun refreshStates() { + proxyHostTextField.isEnabled = proxyTypeComboBox.selectedItem != ProxyType.No + proxyPortTextField.isEnabled = proxyHostTextField.isEnabled + + proxyAuthenticationTypeComboBox.isEnabled = proxyHostTextField.isEnabled + proxyUsernameTextField.isEnabled = proxyAuthenticationTypeComboBox.selectedItem != AuthenticationType.No + proxyPasswordTextField.isEnabled = proxyUsernameTextField.isEnabled + } + + override fun getIcon(isSelected: Boolean): Icon { + return Icons.network + } + + override fun getTitle(): String { + return I18n.getString("termora.new-host.proxy") + } + + override fun getJComponent(): JComponent { + return this + } + + private fun getCenterComponent(): JComponent { + val layout = FormLayout( + "left:pref, $formMargin, default:grow, $formMargin, pref, $formMargin, default, default:grow", + "pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref" + ) + + var rows = 1 + val step = 2 + val panel = FormBuilder.create().layout(layout) + .add("${I18n.getString("termora.new-host.general.protocol")}:").xy(1, rows) + .add(proxyTypeComboBox).xyw(3, rows, 5).apply { rows += step } + + .add("${I18n.getString("termora.new-host.general.host")}:").xy(1, rows) + .add(proxyHostTextField).xy(3, rows) + .add("${I18n.getString("termora.new-host.general.port")}:").xy(5, rows) + .add(proxyPortTextField).xy(7, rows).apply { rows += step } + + .add("${I18n.getString("termora.new-host.general.authentication")}:").xy(1, rows) + .add(proxyAuthenticationTypeComboBox).xyw(3, rows, 5).apply { rows += step } + + .add("${I18n.getString("termora.new-host.general.username")}:").xy(1, rows) + .add(proxyUsernameTextField).xyw(3, rows, 5).apply { rows += step } + .add("${I18n.getString("termora.new-host.general.password")}:").xy(1, rows) + .add(proxyPasswordTextField).xyw(3, rows, 5).apply { rows += step } + + .build() + + + return panel + } + } + + protected inner class TerminalOption : JPanel(BorderLayout()), Option { + val charsetComboBox = JComboBox() + val startupCommandTextField = OutlineTextField() + val environmentTextArea = FixedLengthTextArea(2048) + + + init { + initView() + initEvents() + } + + private fun initView() { + add(getCenterComponent(), BorderLayout.CENTER) + + + environmentTextArea.setFocusTraversalKeys( + KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, + KeyboardFocusManager.getCurrentKeyboardFocusManager() + .getDefaultFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS) + ) + environmentTextArea.setFocusTraversalKeys( + KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, + KeyboardFocusManager.getCurrentKeyboardFocusManager() + .getDefaultFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS) + ) + + environmentTextArea.rows = 8 + environmentTextArea.lineWrap = true + environmentTextArea.border = BorderFactory.createEmptyBorder(4, 4, 4, 4) + + for (e in Charset.availableCharsets()) { + charsetComboBox.addItem(e.key) + } + + charsetComboBox.selectedItem = "UTF-8" + + } + + private fun initEvents() { + + } + + + override fun getIcon(isSelected: Boolean): Icon { + return Icons.terminal + } + + override fun getTitle(): String { + return I18n.getString("termora.new-host.terminal") + } + + override fun getJComponent(): JComponent { + return this + } + + private fun getCenterComponent(): JComponent { + val layout = FormLayout( + "left:pref, $formMargin, default:grow, $formMargin, default:grow", + "pref, $formMargin, pref, $formMargin, pref, $formMargin" + ) + + var rows = 1 + val step = 2 + val panel = FormBuilder.create().layout(layout) + .add("${I18n.getString("termora.new-host.terminal.encoding")}:").xy(1, rows) + .add(charsetComboBox).xy(3, rows).apply { rows += step } + .add("${I18n.getString("termora.new-host.terminal.startup-commands")}:").xy(1, rows) + .add(startupCommandTextField).xy(3, rows).apply { rows += step } + .add("${I18n.getString("termora.new-host.terminal.env")}:").xy(1, rows) + .add(JScrollPane(environmentTextArea).apply { border = FlatTextBorder() }).xy(3, rows) + .apply { rows += step } + .build() + + + return panel + } + } + + protected inner class TunnelingOption : JPanel(BorderLayout()), Option { + val tunnelings = mutableListOf() + + private val model = object : DefaultTableModel() { + override fun getRowCount(): Int { + return tunnelings.size + } + + override fun isCellEditable(row: Int, column: Int): Boolean { + return false + } + + fun addRow(tunneling: Tunneling) { + val rowCount = super.getRowCount() + tunnelings.add(tunneling) + super.fireTableRowsInserted(rowCount, rowCount + 1) + } + + override fun getValueAt(row: Int, column: Int): Any { + val tunneling = tunnelings[row] + return when (column) { + 0 -> tunneling.name + 1 -> tunneling.type + 2 -> "${tunneling.sourceHost}:${tunneling.sourcePort}" + 3 -> "${tunneling.destinationHost}:${tunneling.destinationPort}" + else -> super.getValueAt(row, column) + } + } + } + private val table = JTable(model) + private val addBtn = JButton(I18n.getString("termora.new-host.tunneling.add")) + private val editBtn = JButton(I18n.getString("termora.new-host.tunneling.edit")) + private val deleteBtn = JButton(I18n.getString("termora.new-host.tunneling.delete")) + + init { + initView() + initEvents() + } + + private fun initView() { + val scrollPane = JScrollPane(table) + + model.addColumn(I18n.getString("termora.new-host.tunneling.table.name")) + model.addColumn(I18n.getString("termora.new-host.tunneling.table.type")) + model.addColumn(I18n.getString("termora.new-host.tunneling.table.source")) + model.addColumn(I18n.getString("termora.new-host.tunneling.table.destination")) + + + table.autoResizeMode = JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS + table.border = BorderFactory.createEmptyBorder() + table.fillsViewportHeight = true + scrollPane.border = BorderFactory.createCompoundBorder( + BorderFactory.createEmptyBorder(4, 0, 4, 0), + BorderFactory.createMatteBorder(1, 1, 1, 1, DynamicColor.BorderColor) + ) + + deleteBtn.isFocusable = false + addBtn.isFocusable = false + editBtn.isFocusable = false + + editBtn.isEnabled = false + deleteBtn.isEnabled = false + + val box = Box.createHorizontalBox() + box.add(addBtn) + box.add(Box.createHorizontalStrut(4)) + box.add(editBtn) + box.add(Box.createHorizontalStrut(4)) + box.add(deleteBtn) + + add(JLabel("TCP/IP Forwarding:"), BorderLayout.NORTH) + add(scrollPane, BorderLayout.CENTER) + add(box, BorderLayout.SOUTH) + + } + + private fun initEvents() { + addBtn.addActionListener(object : AbstractAction() { + override fun actionPerformed(e: ActionEvent?) { + val dialog = PortForwardingDialog(SwingUtilities.getWindowAncestor(this@HostOptionsPane)) + dialog.isVisible = true + val tunneling = dialog.tunneling ?: return + model.addRow(tunneling) + } + }) + + + editBtn.addActionListener(object : AbstractAction() { + override fun actionPerformed(e: ActionEvent?) { + val row = table.selectedRow + if (row < 0) { + return + } + val dialog = PortForwardingDialog( + SwingUtilities.getWindowAncestor(this@HostOptionsPane), + tunnelings[row] + ) + dialog.isVisible = true + tunnelings[row] = dialog.tunneling ?: return + model.fireTableRowsUpdated(row, row) + } + }) + + deleteBtn.addActionListener(object : AbstractAction() { + override fun actionPerformed(e: ActionEvent) { + val rows = table.selectedRows + if (rows.isEmpty()) return + rows.sortDescending() + for (row in rows) { + tunnelings.removeAt(row) + model.fireTableRowsDeleted(row, row) + } + } + }) + + table.selectionModel.addListSelectionListener { + editBtn.isEnabled = table.selectedRowCount > 0 + deleteBtn.isEnabled = editBtn.isEnabled + } + + table.addMouseListener(object : MouseAdapter() { + override fun mouseClicked(e: MouseEvent) { + if (e.clickCount % 2 == 0 && SwingUtilities.isLeftMouseButton(e)) { + editBtn.actionListeners.forEach { + it.actionPerformed( + ActionEvent( + editBtn, + ActionEvent.ACTION_PERFORMED, + StringUtils.EMPTY + ) + ) + } + } + } + }) + } + + override fun getIcon(isSelected: Boolean): Icon { + return Icons.showWriteAccess + } + + override fun getTitle(): String { + return I18n.getString("termora.new-host.tunneling") + } + + override fun getJComponent(): JComponent { + return this + } + + private inner class PortForwardingDialog( + owner: Window, + var tunneling: Tunneling? = null + ) : DialogWrapper(owner) { + private val formMargin = "4dlu" + private val typeComboBox = FlatComboBox() + private val nameTextField = OutlineTextField(32) + private val localHostTextField = OutlineTextField() + private val localPortSpinner = PortSpinner() + private val remoteHostTextField = OutlineTextField() + private val remotePortSpinner = PortSpinner() + + init { + isModal = true + title = I18n.getString("termora.new-host.tunneling") + controlsVisible = false + + typeComboBox.addItem(TunnelingType.Local) + typeComboBox.addItem(TunnelingType.Remote) + typeComboBox.addItem(TunnelingType.Dynamic) + + localHostTextField.text = "127.0.0.1" + localPortSpinner.value = 1080 + + remoteHostTextField.text = "127.0.0.1" + + typeComboBox.addItemListener { + if (it.stateChange == ItemEvent.SELECTED) { + remoteHostTextField.isEnabled = typeComboBox.selectedItem != TunnelingType.Dynamic + remotePortSpinner.isEnabled = remoteHostTextField.isEnabled + } + } + + tunneling?.let { + localHostTextField.text = it.sourceHost + localPortSpinner.value = it.sourcePort + remoteHostTextField.text = it.destinationHost + remotePortSpinner.value = it.destinationPort + nameTextField.text = it.name + typeComboBox.selectedItem = it.type + } + + init() + pack() + size = Dimension(UIManager.getInt("Dialog.width") - 300, size.height) + setLocationRelativeTo(null) + + } + + override fun doOKAction() { + if (nameTextField.text.isBlank()) { + nameTextField.outline = "error" + nameTextField.requestFocusInWindow() + return + } else if (localHostTextField.text.isBlank()) { + localHostTextField.outline = "error" + localHostTextField.requestFocusInWindow() + return + } else if (remoteHostTextField.text.isBlank()) { + remoteHostTextField.outline = "error" + remoteHostTextField.requestFocusInWindow() + return + } + + tunneling = Tunneling( + name = nameTextField.text, + type = typeComboBox.selectedItem as TunnelingType, + sourceHost = localHostTextField.text, + sourcePort = localPortSpinner.value as Int, + destinationHost = remoteHostTextField.text, + destinationPort = remotePortSpinner.value as Int, + ) + + super.doOKAction() + } + + override fun doCancelAction() { + tunneling = null + super.doCancelAction() + } + + override fun createCenterPanel(): JComponent { + val layout = FormLayout( + "left:pref, $formMargin, default:grow, $formMargin, pref", + "pref, $formMargin, pref, $formMargin, pref, $formMargin, pref" + ) + + var rows = 1 + val step = 2 + return FormBuilder.create().layout(layout).padding("0dlu, $formMargin, $formMargin, $formMargin") + .add("${I18n.getString("termora.new-host.tunneling.table.name")}:").xy(1, rows) + .add(nameTextField).xyw(3, rows, 3).apply { rows += step } + .add("${I18n.getString("termora.new-host.tunneling.table.type")}:").xy(1, rows) + .add(typeComboBox).xyw(3, rows, 3).apply { rows += step } + .add("${I18n.getString("termora.new-host.tunneling.table.source")}:").xy(1, rows) + .add(localHostTextField).xy(3, rows) + .add(localPortSpinner).xy(5, rows).apply { rows += step } + .add("${I18n.getString("termora.new-host.tunneling.table.destination")}:").xy(1, rows) + .add(remoteHostTextField).xy(3, rows) + .add(remotePortSpinner).xy(5, rows).apply { rows += step } + .build() + } + + + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/app/termora/HostTerminalTab.kt b/src/main/kotlin/app/termora/HostTerminalTab.kt new file mode 100644 index 0000000..02525dd --- /dev/null +++ b/src/main/kotlin/app/termora/HostTerminalTab.kt @@ -0,0 +1,63 @@ +package app.termora + +import app.termora.terminal.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.cancel +import kotlinx.coroutines.swing.Swing +import java.beans.PropertyChangeEvent +import javax.swing.Icon + +abstract class HostTerminalTab(val host: Host) : PropertyTerminalTab() { + protected val coroutineScope by lazy { CoroutineScope(Dispatchers.Swing) } + protected val terminal = TerminalFactory.instance.createTerminal() + protected val terminalModel get() = terminal.getTerminalModel() + protected var unread = false + set(value) { + field = value + firePropertyChange(PropertyChangeEvent(this, "icon", null, null)) + } + + + /* visualTerminal */ + protected fun Terminal.clearScreen() { + this.write("${ControlCharacters.ESC}[3J") + } + + init { + terminal.getTerminalModel().addDataListener(object : DataListener { + override fun onChanged(key: DataKey<*>, data: Any) { + if (key == VisualTerminal.Written) { + if (hasFocus || unread) { + return + } + unread = true + } + } + }) + } + + open fun start() {} + + override fun getTitle(): String { + return host.name + } + + override fun getIcon(): Icon { + if (host.protocol == Protocol.Local || host.protocol == Protocol.SSH) { + return if (unread) Icons.terminalUnread else Icons.terminal + } + return Icons.terminal + } + + override fun dispose() { + coroutineScope.cancel() + } + + override fun onGrabFocus() { + super.onGrabFocus() + if (!unread) return + unread = false + } + +} \ No newline at end of file diff --git a/src/main/kotlin/app/termora/HostTree.kt b/src/main/kotlin/app/termora/HostTree.kt new file mode 100644 index 0000000..519a41d --- /dev/null +++ b/src/main/kotlin/app/termora/HostTree.kt @@ -0,0 +1,583 @@ +package app.termora + +import app.termora.db.Database +import com.formdev.flatlaf.extras.components.FlatPopupMenu +import com.formdev.flatlaf.icons.FlatTreeClosedIcon +import com.formdev.flatlaf.icons.FlatTreeOpenIcon +import org.jdesktop.swingx.action.ActionManager +import org.jdesktop.swingx.tree.DefaultXTreeCellRenderer +import java.awt.Component +import java.awt.Dimension +import java.awt.datatransfer.DataFlavor +import java.awt.datatransfer.Transferable +import java.awt.event.ActionEvent +import java.awt.event.MouseAdapter +import java.awt.event.MouseEvent +import java.util.* +import javax.swing.* +import javax.swing.event.CellEditorListener +import javax.swing.event.ChangeEvent +import javax.swing.event.PopupMenuEvent +import javax.swing.event.PopupMenuListener +import javax.swing.tree.TreePath +import javax.swing.tree.TreeSelectionModel + + +class HostTree : JTree(), Disposable { + private val hostManager get() = HostManager.instance + private val editor = OutlineTextField(64) + + val model = HostTreeModel() + val searchableModel = SearchableHostTreeModel(model) + + init { + initView() + initEvents() + } + + + private fun initView() { + setModel(model) + isEditable = true + dropMode = DropMode.ON_OR_INSERT + dragEnabled = true + selectionModel.selectionMode = TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION + editor.preferredSize = Dimension(220, 0) + + setCellRenderer(object : DefaultXTreeCellRenderer() { + override fun getTreeCellRendererComponent( + tree: JTree, + value: Any, + sel: Boolean, + expanded: Boolean, + leaf: Boolean, + row: Int, + hasFocus: Boolean + ): Component { + val host = value as Host + val c = super.getTreeCellRendererComponent(tree, host, sel, expanded, leaf, row, hasFocus) + if (host.protocol == Protocol.Folder) { + icon = if (expanded) FlatTreeOpenIcon() else FlatTreeClosedIcon() + } else if (host.protocol == Protocol.SSH || host.protocol == Protocol.Local) { + icon = if (sel && this@HostTree.hasFocus()) Icons.terminal.dark else Icons.terminal + } + return c + } + }) + + setCellEditor(object : DefaultCellEditor(editor) { + override fun isCellEditable(e: EventObject?): Boolean { + if (e is MouseEvent) { + return false + } + return super.isCellEditable(e) + } + + }) + + + val state = Database.instance.properties.getString("HostTreeExpansionState") + if (state != null) { + TreeUtils.loadExpansionState(this@HostTree, state) + } + } + + override fun convertValueToText( + value: Any?, + selected: Boolean, + expanded: Boolean, + leaf: Boolean, + row: Int, + hasFocus: Boolean + ): String { + if (value is Host) { + return value.name + } + return super.convertValueToText(value, selected, expanded, leaf, row, hasFocus) + } + + private fun initEvents() { + // 右键选中 + addMouseListener(object : MouseAdapter() { + override fun mousePressed(e: MouseEvent) { + if (!SwingUtilities.isRightMouseButton(e)) { + return + } + + requestFocusInWindow() + + val selectionRows = selectionModel.selectionRows + + val selRow = getClosestRowForLocation(e.x, e.y) + if (selRow < 0) { + selectionModel.clearSelection() + return + } else if (selectionRows != null && selectionRows.contains(selRow)) { + return + } + + selectionPath = getPathForLocation(e.x, e.y) + + setSelectionRow(selRow) + } + + override fun mouseClicked(e: MouseEvent) { + if (SwingUtilities.isLeftMouseButton(e) && e.clickCount % 2 == 0) { + val host = lastSelectedPathComponent + if (host is Host && host.protocol != Protocol.Folder) { + ActionManager.getInstance().getAction(Actions.OPEN_HOST) + ?.actionPerformed(OpenHostActionEvent(this, host)) + } + } + } + }) + + + // contextmenu + addMouseListener(object : MouseAdapter() { + override fun mousePressed(e: MouseEvent) { + if (!(SwingUtilities.isRightMouseButton(e))) { + return + } + + if (Objects.isNull(lastSelectedPathComponent)) { + return + } + + SwingUtilities.invokeLater { showContextMenu(e) } + } + }) + + + // rename + getCellEditor().addCellEditorListener(object : CellEditorListener { + override fun editingStopped(e: ChangeEvent) { + val lastHost = lastSelectedPathComponent + if (lastHost !is Host || editor.text.isBlank() || editor.text == lastHost.name) { + return + } + runCatchingHost(lastHost.copy(name = editor.text)) + } + + override fun editingCanceled(e: ChangeEvent) { + } + + }) + + // drag + transferHandler = object : TransferHandler() { + + override fun createTransferable(c: JComponent): Transferable { + val nodes = selectionModel.selectionPaths + .map { it.lastPathComponent } + .filterIsInstance() + .toMutableList() + + val iterator = nodes.iterator() + while (iterator.hasNext()) { + val node = iterator.next() + val parents = model.getPathToRoot(node).filter { it != node } + if (parents.any { nodes.contains(it) }) { + iterator.remove() + } + } + + return MoveHostTransferable(nodes) + } + + override fun getSourceActions(c: JComponent?): Int { + return MOVE + } + + override fun canImport(support: TransferSupport): Boolean { + if (!support.isDrop) { + return false + } + val dropLocation = support.dropLocation + if (dropLocation !is JTree.DropLocation || support.component != this@HostTree + || dropLocation.childIndex != -1 + ) { + return false + } + + val lastNode = dropLocation.path.lastPathComponent + if (lastNode !is Host || lastNode.protocol != Protocol.Folder) { + return false + } + + if (support.isDataFlavorSupported(MoveHostTransferable.dataFlavor)) { + val nodes = support.transferable.getTransferData(MoveHostTransferable.dataFlavor) as List<*> + if (nodes.any { it == lastNode }) { + return false + } + for (parent in model.getPathToRoot(lastNode).filter { it != lastNode }) { + if (nodes.any { it == parent }) { + return false + } + } + } + support.setShowDropLocation(true) + return support.isDataFlavorSupported(MoveHostTransferable.dataFlavor) + } + + override fun importData(support: TransferSupport): Boolean { + if (!support.isDrop) { + return false + } + + val dropLocation = support.dropLocation + if (dropLocation !is JTree.DropLocation) { + return false + } + + val lastNode = dropLocation.path.lastPathComponent + if (lastNode !is Host || lastNode.protocol != Protocol.Folder) { + return false + } + + if (!support.isDataFlavorSupported(MoveHostTransferable.dataFlavor)) { + return false + } + + val hosts = (support.transferable.getTransferData(MoveHostTransferable.dataFlavor) as List<*>) + .filterIsInstance().toMutableList() + if (hosts.isEmpty()) { + return false + } + + // 记录展开的节点 + val expandedHosts = mutableListOf() + for (host in hosts) { + model.visit(host) { + if (it.protocol == Protocol.Folder) { + if (isExpanded(TreePath(model.getPathToRoot(it)))) { + expandedHosts.addFirst(it.id) + } + } + } + } + + var now = System.currentTimeMillis() + for (host in hosts) { + model.removeNodeFromParent(host) + val newHost = host.copy( + parentId = lastNode.id, + sort = ++now, + updateDate = now + ) + runCatchingHost(newHost) + } + + expandNode(lastNode) + + // 展开 + for (id in expandedHosts) { + model.getHost(id)?.let { expandNode(it) } + } + + return true + } + } + + } + + override fun isPathEditable(path: TreePath?): Boolean { + if (path == null) return false + if (path.lastPathComponent == model.root) return false + return super.isPathEditable(path) + } + + override fun getLastSelectedPathComponent(): Any? { + val last = super.getLastSelectedPathComponent() ?: return null + if (last is Host) { + return model.getHost(last.id) ?: last + } + return last + } + + private fun showContextMenu(event: MouseEvent) { + val lastHost = lastSelectedPathComponent + if (lastHost !is Host) { + return + } + + val popupMenu = FlatPopupMenu() + val newMenu = JMenu(I18n.getString("termora.welcome.contextmenu.new")) + val newFolder = newMenu.add(I18n.getString("termora.welcome.contextmenu.new.folder")) + val newHost = newMenu.add(I18n.getString("termora.welcome.contextmenu.new.host")) + + val open = popupMenu.add(I18n.getString("termora.welcome.contextmenu.open")) + popupMenu.addSeparator() + val copy = popupMenu.add(I18n.getString("termora.welcome.contextmenu.copy")) + val remove = popupMenu.add(I18n.getString("termora.welcome.contextmenu.remove")) + val rename = popupMenu.add(I18n.getString("termora.welcome.contextmenu.rename")) + popupMenu.addSeparator() + val expandAll = popupMenu.add(I18n.getString("termora.welcome.contextmenu.expand-all")) + val colspanAll = popupMenu.add(I18n.getString("termora.welcome.contextmenu.collapse-all")) + popupMenu.addSeparator() + popupMenu.add(newMenu) + popupMenu.addSeparator() + val property = popupMenu.add(I18n.getString("termora.welcome.contextmenu.property")) + + open.addActionListener { + getSelectionNodes() + .filter { it.protocol != Protocol.Folder } + .forEach { + ActionManager.getInstance() + .getAction(Actions.OPEN_HOST) + ?.actionPerformed(OpenHostActionEvent(this, it)) + } + } + + rename.addActionListener { + startEditingAtPath(TreePath(model.getPathToRoot(lastHost))) + } + + expandAll.addActionListener { + getSelectionNodes().forEach { expandNode(it, true) } + } + + + colspanAll.addActionListener { + selectionModel.selectionPaths.map { it.lastPathComponent } + .filterIsInstance() + .filter { it.protocol == Protocol.Folder } + .forEach { collapseNode(it) } + } + + copy.addActionListener(object : AbstractAction() { + override fun actionPerformed(e: ActionEvent) { + val parent = model.getParent(lastHost) ?: return + val node = copyNode(parent, lastHost) + selectionPath = TreePath(model.getPathToRoot(node)) + } + }) + + remove.addActionListener { + if (OptionPane.showConfirmDialog( + SwingUtilities.getWindowAncestor(this), + "删除后无法恢复,你确定要删除吗?", + I18n.getString("termora.remove"), + JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE + ) == JOptionPane.YES_OPTION + ) { + var lastParent: Host? = null + while (!selectionModel.isSelectionEmpty) { + val host = lastSelectedPathComponent ?: break + if (host !is Host) { + break + } else { + lastParent = model.getParent(host) + } + model.visit(host) { hostManager.removeHost(it.id) } + } + if (lastParent != null) { + selectionPath = TreePath(model.getPathToRoot(lastParent)) + } + } + } + + newFolder.addActionListener(object : AbstractAction() { + override fun actionPerformed(e: ActionEvent) { + if (lastHost.protocol != Protocol.Folder) { + return + } + + val host = Host( + id = UUID.randomUUID().toSimpleString(), + protocol = Protocol.Folder, + name = I18n.getString("termora.welcome.contextmenu.new.folder.name"), + sort = System.currentTimeMillis(), + parentId = lastHost.id + ) + + runCatchingHost(host) + + expandNode(lastHost) + selectionPath = TreePath(model.getPathToRoot(host)) + startEditingAtPath(selectionPath) + + } + }) + + newHost.addActionListener(object : AbstractAction() { + override fun actionPerformed(e: ActionEvent) { + showAddHostDialog() + } + }) + + property.addActionListener(object : AbstractAction() { + override fun actionPerformed(e: ActionEvent) { + val dialog = HostDialog(SwingUtilities.getWindowAncestor(this@HostTree), lastHost) + dialog.isVisible = true + val host = dialog.host ?: return + runCatchingHost(host) + } + }) + + // 初始化状态 + newFolder.isEnabled = lastHost.protocol == Protocol.Folder + newHost.isEnabled = newFolder.isEnabled + remove.isEnabled = !getSelectionNodes().any { it == model.root } + copy.isEnabled = remove.isEnabled + rename.isEnabled = remove.isEnabled + property.isEnabled = lastHost.protocol != Protocol.Folder + + popupMenu.addPopupMenuListener(object : PopupMenuListener { + override fun popupMenuWillBecomeVisible(e: PopupMenuEvent) { + this@HostTree.grabFocus() + } + + override fun popupMenuWillBecomeInvisible(e: PopupMenuEvent) { + this@HostTree.requestFocusInWindow() + } + + override fun popupMenuCanceled(e: PopupMenuEvent) { + } + + }) + + + popupMenu.show(this, event.x, event.y) + } + + fun showAddHostDialog() { + var lastHost = lastSelectedPathComponent + if (lastHost !is Host) { + return + } + + if (lastHost.protocol != Protocol.Folder) { + val p = model.getParent(lastHost) ?: return + lastHost = p + } + + val dialog = HostDialog(SwingUtilities.getWindowAncestor(this)) + dialog.isVisible = true + val host = (dialog.host ?: return).copy(parentId = lastHost.id) + + runCatchingHost(host) + + expandNode(lastHost) + selectionPath = TreePath(model.getPathToRoot(host)) + + } + + + private fun expandNode(node: Host, including: Boolean = false) { + expandPath(TreePath(model.getPathToRoot(node))) + if (including) { + model.getChildren(node).forEach { expandNode(it, true) } + } + } + + + private fun copyNode( + parent: Host, + host: Host, + idGenerator: () -> String = { UUID.randomUUID().toSimpleString() } + ): Host { + val now = System.currentTimeMillis() + val newHost = host.copy( + name = "${host.name} ${I18n.getString("termora.welcome.contextmenu.copy")}", + id = idGenerator.invoke(), + parentId = parent.id, + updateDate = now, + createDate = now, + sort = now + ) + + runCatchingHost(newHost) + + if (host.protocol == Protocol.Folder) { + for (child in model.getChildren(host)) { + copyNode(newHost, child, idGenerator) + } + if (isExpanded(TreePath(model.getPathToRoot(host)))) { + expandNode(newHost) + } + } + + return newHost + + } + + private fun runCatchingHost(host: Host) { + hostManager.addHost(host) + } + + private fun collapseNode(node: Host) { + model.getChildren(node).forEach { collapseNode(it) } + collapsePath(TreePath(model.getPathToRoot(node))) + } + + private fun getSelectionNodes(): List { + val selectionNodes = selectionModel.selectionPaths.map { it.lastPathComponent } + .filterIsInstance() + + if (selectionNodes.isEmpty()) { + return emptyList() + } + + val nodes = mutableListOf() + val parents = mutableListOf() + + for (node in selectionNodes) { + if (node.protocol == Protocol.Folder) { + parents.add(node) + } + nodes.add(node) + } + + while (parents.isNotEmpty()) { + val p = parents.removeFirst() + for (i in 0 until model.getChildCount(p)) { + val child = model.getChild(p, i) as Host + nodes.add(child) + parents.add(child) + } + } + + return nodes + } + + override fun dispose() { + Database.instance.properties.putString( + "HostTreeExpansionState", + TreeUtils.saveExpansionState(this) + ) + } + + private abstract class HostTreeNodeTransferable(val hosts: List) : + Transferable { + + override fun getTransferDataFlavors(): Array { + return arrayOf(getDataFlavor()) + } + + override fun isDataFlavorSupported(flavor: DataFlavor): Boolean { + return getDataFlavor() == flavor + } + + override fun getTransferData(flavor: DataFlavor): Any { + return hosts + } + + abstract fun getDataFlavor(): DataFlavor + } + + private class MoveHostTransferable(hosts: List) : HostTreeNodeTransferable(hosts) { + companion object { + val dataFlavor = + DataFlavor("${DataFlavor.javaJVMLocalObjectMimeType};class=${MoveHostTransferable::class.java.name}") + } + + override fun getDataFlavor(): DataFlavor { + return dataFlavor + } + + } + + +} \ No newline at end of file diff --git a/src/main/kotlin/app/termora/HostTreeModel.kt b/src/main/kotlin/app/termora/HostTreeModel.kt new file mode 100644 index 0000000..163e221 --- /dev/null +++ b/src/main/kotlin/app/termora/HostTreeModel.kt @@ -0,0 +1,159 @@ +package app.termora + +import org.apache.commons.lang3.StringUtils +import javax.swing.event.TreeModelEvent +import javax.swing.event.TreeModelListener +import javax.swing.tree.TreeModel +import javax.swing.tree.TreePath + +class HostTreeModel : TreeModel { + + val listeners = mutableListOf() + + private val hostManager get() = HostManager.instance + private val hosts = mutableMapOf() + private val myRoot by lazy { + Host( + id = "0", + protocol = Protocol.Folder, + name = I18n.getString("termora.welcome.my-hosts"), + host = StringUtils.EMPTY, + port = 0, + remark = StringUtils.EMPTY, + username = StringUtils.EMPTY + ) + } + + init { + + for (host in hostManager.hosts()) { + hosts[host.id] = host + } + + hostManager.addHostListener(object : HostListener { + override fun hostRemoved(id: String) { + val host = hosts[id] ?: return + removeNodeFromParent(host) + } + + override fun hostAdded(host: Host) { + // 如果已经存在,那么是修改 + if (hosts.containsKey(host.id)) { + val oldHost = hosts.getValue(host.id) + // 父级结构变了 + if (oldHost.parentId != host.parentId) { + hostRemoved(host.id) + hostAdded(host) + } else { + hosts[host.id] = host + val event = TreeModelEvent(this, getPathToRoot(host)) + listeners.forEach { it.treeStructureChanged(event) } + } + + } else { + hosts[host.id] = host + val parent = getParent(host) ?: return + val path = TreePath(getPathToRoot(parent)) + val event = TreeModelEvent(this, path, intArrayOf(getIndexOfChild(parent, host)), arrayOf(host)) + listeners.forEach { it.treeNodesInserted(event) } + } + } + + override fun hostsChanged() { + hosts.clear() + for (host in hostManager.hosts()) { + hosts[host.id] = host + } + val event = TreeModelEvent(this, getPathToRoot(root), null, null) + listeners.forEach { it.treeStructureChanged(event) } + } + + }) + } + + override fun getRoot(): Host { + return myRoot + } + + override fun getChild(parent: Any?, index: Int): Any { + return getChildren(parent)[index] + } + + override fun getChildCount(parent: Any?): Int { + return getChildren(parent).size + } + + override fun isLeaf(node: Any?): Boolean { + return getChildCount(node) == 0 + } + + fun getParent(node: Host): Host? { + if (node.parentId == root.id || root.id == node.id) { + return root + } + return hosts.values.firstOrNull { it.id == node.parentId } + } + + override fun valueForPathChanged(path: TreePath?, newValue: Any?) { + + } + + override fun getIndexOfChild(parent: Any?, child: Any?): Int { + return getChildren(parent).indexOf(child) + } + + override fun addTreeModelListener(listener: TreeModelListener) { + listeners.add(listener) + } + + override fun removeTreeModelListener(listener: TreeModelListener) { + listeners.remove(listener) + } + + /** + * 仅从结构中删除 + */ + fun removeNodeFromParent(host: Host) { + val parent = getParent(host) ?: return + val index = getIndexOfChild(parent, host) + val event = TreeModelEvent(this, TreePath(getPathToRoot(parent)), intArrayOf(index), arrayOf(host)) + hosts.remove(host.id) + listeners.forEach { it.treeNodesRemoved(event) } + } + + fun visit(host: Host, visitor: (host: Host) -> Unit) { + if (host.protocol == Protocol.Folder) { + getChildren(host).forEach { visit(it, visitor) } + visitor.invoke(host) + } else { + visitor.invoke(host) + } + } + + fun getHost(id: String): Host? { + return hosts[id] + } + + fun getPathToRoot(host: Host): Array { + + if (host.id == root.id) { + return arrayOf(root) + } + + val parents = mutableListOf(host) + var pId = host.parentId + while (pId != root.id) { + val e = hosts[(pId)] ?: break + parents.addFirst(e) + pId = e.parentId + } + parents.addFirst(root) + return parents.toTypedArray() + } + + fun getChildren(parent: Any?): List { + val pId = if (parent is Host) parent.id else root.id + return hosts.values.filter { it.parentId == pId } + .sortedWith(compareBy { if (it.protocol == Protocol.Folder) 0 else 1 }.thenBy { it.sort }) + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/termora/Hyperlink.kt b/src/main/kotlin/app/termora/Hyperlink.kt new file mode 100644 index 0000000..0146711 --- /dev/null +++ b/src/main/kotlin/app/termora/Hyperlink.kt @@ -0,0 +1,22 @@ +package app.termora + +import com.formdev.flatlaf.extras.FlatSVGIcon +import com.formdev.flatlaf.extras.FlatSVGIcon.ColorFilter +import org.jdesktop.swingx.JXHyperlink +import java.awt.Color +import javax.swing.SwingConstants +import javax.swing.UIManager + +class Hyperlink(action: AnAction, focusable: Boolean = true) : JXHyperlink(action) { + init { + val myIcon = FlatSVGIcon(Icons.externalLink.name) + myIcon.colorFilter = object : ColorFilter() { + override fun filter(color: Color?): Color { + return UIManager.getColor("Hyperlink.linkColor") + } + } + isFocusable = focusable + icon = myIcon + horizontalTextPosition = SwingConstants.LEFT + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/termora/I18n.kt b/src/main/kotlin/app/termora/I18n.kt new file mode 100644 index 0000000..0720912 --- /dev/null +++ b/src/main/kotlin/app/termora/I18n.kt @@ -0,0 +1,57 @@ +package app.termora + +import org.apache.commons.lang3.LocaleUtils +import org.apache.commons.text.StringSubstitutor +import org.slf4j.LoggerFactory +import java.text.MessageFormat +import java.util.* + +object I18n { + private val log = LoggerFactory.getLogger(I18n::class.java) + private val bundle by lazy { + val bundle = ResourceBundle.getBundle("i18n/messages", Locale.getDefault()) + if (log.isInfoEnabled) { + log.info("I18n: {}", bundle.baseBundleName ?: "null") + } + return@lazy bundle + } + + private val substitutor by lazy { StringSubstitutor { key -> getString(key) } } + private val supportedLanguages = sortedMapOf( + "en_US" to "English", + "zh_CN" to "简体中文", + "zh_TW" to "繁體中文", + ) + + fun containsLanguage(locale: Locale): String? { + for (key in supportedLanguages.keys) { + val e = LocaleUtils.toLocale(key) + if (LocaleUtils.toLocale(key) == locale || + (e.language.equals(locale.language, true) && e.country.equals(locale.country, true)) + ) { + return key + } + } + return null + } + + fun getLanguages(): Map { + return supportedLanguages + } + + fun getString(key: String, vararg args: Any): String { + try { + val text = substitutor.replace(bundle.getString(key)) + if (args.isNotEmpty()) { + return MessageFormat.format(text, *args) + } + return text + } catch (e: MissingResourceException) { + if (log.isWarnEnabled) { + log.warn(e.message, e) + } + return key + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/app/termora/Icons.kt b/src/main/kotlin/app/termora/Icons.kt new file mode 100644 index 0000000..042653c --- /dev/null +++ b/src/main/kotlin/app/termora/Icons.kt @@ -0,0 +1,83 @@ +package app.termora + +object Icons { + val up by lazy { DynamicIcon("icons/up.svg", "icons/up_dark.svg") } + val down by lazy { DynamicIcon("icons/down.svg", "icons/down_dark.svg") } + val close by lazy { DynamicIcon("icons/close.svg", "icons/close_dark.svg") } + val searchHistory by lazy { DynamicIcon("icons/searchHistory.svg", "icons/searchHistory_dark.svg") } + val matchCase by lazy { DynamicIcon("icons/matchCase.svg", "icons/matchCase_dark.svg") } + val regex by lazy { DynamicIcon("icons/regex.svg", "icons/regex_dark.svg") } + val vcs by lazy { DynamicIcon("icons/vcs.svg", "icons/vcs_dark.svg") } + val dumpThreads by lazy { DynamicIcon("icons/dumpThreads.svg", "icons/dumpThreads_dark.svg") } + val supertypes by lazy { DynamicIcon("icons/supertypes.svg", "icons/supertypes_dark.svg") } + val settings by lazy { DynamicIcon("icons/settings.svg", "icons/settings_dark.svg") } + val pin by lazy { DynamicIcon("icons/pin.svg", "icons/pin_dark.svg") } + val empty by lazy { DynamicIcon("icons/empty.svg") } + val add by lazy { DynamicIcon("icons/add.svg", "icons/add_dark.svg") } + val networkPolicy by lazy { DynamicIcon("icons/networkPolicy.svg", "icons/networkPolicy_dark.svg") } + val clusterRole by lazy { DynamicIcon("icons/clusterRole.svg", "icons/clusterRole_dark.svg") } + val daemonSets by lazy { DynamicIcon("icons/daemonSets.svg", "icons/daemonSets_dark.svg") } + val role by lazy { DynamicIcon("icons/role.svg", "icons/role_dark.svg") } + val locked by lazy { DynamicIcon("icons/locked.svg", "icons/locked_dark.svg") } + val warning by lazy { DynamicIcon("icons/warning.svg", "icons/warning_dark.svg") } + val warningDialog by lazy { DynamicIcon("icons/warningDialog.svg", "icons/warningDialog_dark.svg") } + val unlocked by lazy { DynamicIcon("icons/unlocked.svg", "icons/unlocked_dark.svg") } + val i18n by lazy { DynamicIcon("icons/i18n.svg", "icons/i18n_dark.svg") } + val rec by lazy { DynamicIcon("icons/rec.svg", "icons/rec_dark.svg") } + val stop by lazy { DynamicIcon("icons/stop.svg", "icons/stop_dark.svg") } + val find by lazy { DynamicIcon("icons/find.svg", "icons/find_dark.svg") } + val keyboard by lazy { DynamicIcon("icons/keyboard.svg", "icons/keyboard_dark.svg") } + val moreVertical by lazy { DynamicIcon("icons/moreVertical.svg", "icons/moreVertical_dark.svg") } + val colors by lazy { DynamicIcon("icons/colors.svg", "icons/colors_dark.svg") } + val chevronDown by lazy { DynamicIcon("icons/chevronDownLarge.svg", "icons/chevronDownLarge_dark.svg") } + val homeFolder by lazy { DynamicIcon("icons/homeFolder.svg", "icons/homeFolder_dark.svg") } + val openNewTab by lazy { DynamicIcon("icons/openNewTab.svg", "icons/openNewTab_dark.svg") } + val import by lazy { DynamicIcon("icons/import.svg", "icons/import_dark.svg") } + val export by lazy { DynamicIcon("icons/export.svg", "icons/export_dark.svg") } + val terminal by lazy { DynamicIcon("icons/terminal.svg", "icons/terminal_dark.svg") } + val azure by lazy { DynamicIcon("icons/azure.svg", "icons/azure_dark.svg") } + 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 tencent by lazy { DynamicIcon("icons/tencent.svg") } + val google by lazy { DynamicIcon("icons/google-small.svg") } + val aliyun by lazy { DynamicIcon("icons/aliyun.svg") } + val yandexCloud by lazy { DynamicIcon("icons/yandexCloud.svg") } + val aws by lazy { DynamicIcon("icons/aws.svg","icons/aws_dark.svg") } + val huawei by lazy { DynamicIcon("icons/huawei.svg") } + val baidu by lazy { DynamicIcon("icons/baiduyun.svg") } + val tianyi by lazy { DynamicIcon("icons/tianyiyun.svg") } + val digitalocean by lazy { DynamicIcon("icons/digitalocean.svg","icons/digitalocean_dark.svg") } + val terminalUnread by lazy { DynamicIcon("icons/terminalUnread.svg", "icons/terminalUnread_dark.svg") } + val dbPrimitive by lazy { DynamicIcon("icons/dbPrimitive.svg", "icons/dbPrimitive_dark.svg") } + val linux by lazy { DynamicIcon("icons/linux.svg", "icons/linux_dark.svg") } + val success by lazy { DynamicIcon("icons/success.svg", "icons/success_dark.svg") } + val network by lazy { DynamicIcon("icons/network.svg", "icons/network_dark.svg") } + val server by lazy { DynamicIcon("icons/server.svg", "icons/server_dark.svg") } + val runAnything by lazy { DynamicIcon("icons/runAnything.svg", "icons/runAnything_dark.svg") } + val uiForm by lazy { DynamicIcon("icons/uiForm.svg", "icons/uiForm_dark.svg") } + val cloud by lazy { DynamicIcon("icons/cloud.svg", "icons/cloud_dark.svg") } + val externalLink by lazy { DynamicIcon("icons/externalLink.svg", "icons/externalLink_dark.svg") } + val user by lazy { DynamicIcon("icons/user.svg", "icons/user_dark.svg") } + val infoOutline by lazy { DynamicIcon("icons/infoOutline.svg", "icons/infoOutline_dark.svg") } + val lightning by lazy { DynamicIcon("icons/lightning.svg", "icons/lightning_dark.svg") } + val split by lazy { DynamicIcon("icons/split.svg", "icons/split_dark.svg") } + val setKey by lazy { DynamicIcon("icons/setKey.svg", "icons/setKey_dark.svg") } + val greyKey by lazy { DynamicIcon("icons/greyKey.svg", "icons/greyKey_dark.svg") } + val sortedSet by lazy { DynamicIcon("icons/sortedSet.svg", "icons/sortedSet_dark.svg") } + val colorPicker by lazy { DynamicIcon("icons/colorPicker.svg", "icons/colorPicker_dark.svg") } + val folder by lazy { DynamicIcon("icons/folder.svg", "icons/folder_dark.svg") } + val help by lazy { DynamicIcon("icons/help.svg", "icons/help_dark.svg") } + val expand by lazy { DynamicIcon("icons/expand.svg", "icons/expand_dark.svg") } + val collapse by lazy { DynamicIcon("icons/collapse.svg", "icons/collapse_dark.svg") } + val expandAll by lazy { DynamicIcon("icons/expandAll.svg", "icons/expandAll_dark.svg") } + val collapseAll by lazy { DynamicIcon("icons/collapseAll.svg", "icons/collapseAll_dark.svg") } + val web by lazy { DynamicIcon("icons/web.svg", "icons/web_dark.svg") } + val download by lazy { DynamicIcon("icons/download.svg", "icons/download_dark.svg") } + val upload by lazy { DynamicIcon("icons/upload.svg", "icons/upload_dark.svg") } + val ideUpdate by lazy { DynamicIcon("icons/ideUpdate.svg", "icons/ideUpdate_dark.svg") } + val listKey by lazy { DynamicIcon("icons/listKey.svg", "icons/listKey_dark.svg") } + val forwardPorts by lazy { DynamicIcon("icons/forwardPorts.svg", "icons/forwardPorts_dark.svg") } + val showWriteAccess by lazy { DynamicIcon("icons/showWriteAccess.svg", "icons/showWriteAccess_dark.svg") } + +} \ No newline at end of file diff --git a/src/main/kotlin/app/termora/InputDialog.kt b/src/main/kotlin/app/termora/InputDialog.kt new file mode 100644 index 0000000..56f7503 --- /dev/null +++ b/src/main/kotlin/app/termora/InputDialog.kt @@ -0,0 +1,74 @@ +package app.termora + +import com.formdev.flatlaf.extras.components.FlatTextField +import org.apache.commons.lang3.StringUtils +import java.awt.Window +import java.awt.event.KeyAdapter +import java.awt.event.KeyEvent +import javax.swing.BorderFactory +import javax.swing.JComponent +import javax.swing.UIManager + +class InputDialog( + owner: Window, + title: String, + text: String = StringUtils.EMPTY, + placeholderText: String = StringUtils.EMPTY +) : DialogWrapper(owner) { + private val textField = FlatTextField() + private var text: String? = null + + init { + setSize(340, 60) + setLocationRelativeTo(owner) + + super.setTitle(title) + + isResizable = false + isModal = true + controlsVisible = false + titleBarHeight = UIManager.getInt("TabbedPane.tabHeight") * 0.8f + + + textField.placeholderText = placeholderText + textField.text = text + textField.addKeyListener(object : KeyAdapter() { + override fun keyPressed(e: KeyEvent) { + if (e.keyCode == KeyEvent.VK_ENTER) { + if (textField.text.isBlank()) { + return + } + doOKAction() + } + } + }) + + init() + } + + override fun createCenterPanel(): JComponent { + textField.background = UIManager.getColor("window") + textField.border = BorderFactory.createEmptyBorder(0, 13, 0, 13) + + return textField + } + + fun getText(): String? { + isVisible = true + return text + } + + override fun doCancelAction() { + text = null + super.doCancelAction() + } + + override fun doOKAction() { + text = textField.text + super.doOKAction() + } + + override fun createSouthPanel(): JComponent? { + return null + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/termora/Laf.kt b/src/main/kotlin/app/termora/Laf.kt new file mode 100644 index 0000000..64e2e2d --- /dev/null +++ b/src/main/kotlin/app/termora/Laf.kt @@ -0,0 +1,951 @@ +package app.termora + +import app.termora.terminal.ColorTheme +import app.termora.terminal.TerminalColor +import com.formdev.flatlaf.FlatDarkLaf +import com.formdev.flatlaf.FlatLightLaf +import com.formdev.flatlaf.FlatPropertiesLaf +import com.formdev.flatlaf.util.SystemInfo +import java.util.* + + +class LightLaf : FlatLightLaf(), ColorTheme { + override fun getColor(color: TerminalColor): Int { + return when (color) { + TerminalColor.Normal.BLACK -> 0 + TerminalColor.Normal.RED -> 13501701 + TerminalColor.Normal.GREEN -> 425239 + TerminalColor.Normal.YELLOW -> 11701248 + TerminalColor.Normal.BLUE -> 409563 + TerminalColor.Normal.MAGENTA -> 11733427 + TerminalColor.Normal.CYAN -> 167566 + TerminalColor.Normal.WHITE -> 9605778 + + TerminalColor.Bright.BLACK -> 0x4c4c4c + TerminalColor.Bright.RED -> 0xff0000 + TerminalColor.Bright.GREEN -> 0x00ff00 + TerminalColor.Bright.YELLOW -> if (SystemInfo.isWindows) 0xC18301 else 0xffff00 + TerminalColor.Bright.BLUE -> 0x4682b4 + TerminalColor.Bright.MAGENTA -> 0xff00ff + TerminalColor.Bright.CYAN -> 0x00ffff + TerminalColor.Bright.WHITE -> 0xffffff + + else -> Int.MAX_VALUE + } + } +} + + +class DarkLaf : FlatDarkLaf(), ColorTheme { + override fun getColor(color: TerminalColor): Int { + return when (color) { + TerminalColor.Normal.BLACK -> 0 + TerminalColor.Normal.RED -> 15749711 + TerminalColor.Normal.GREEN -> 6067756 + TerminalColor.Normal.YELLOW -> 10914317 + TerminalColor.Normal.BLUE -> 3773396 + TerminalColor.Normal.MAGENTA -> 10973631 + TerminalColor.Normal.CYAN -> 41891 + TerminalColor.Normal.WHITE -> 8421504 + + + TerminalColor.Bright.BLACK -> 0x676767 + TerminalColor.Bright.RED -> 0xef766d + TerminalColor.Bright.GREEN -> 0x8cf67a + TerminalColor.Bright.YELLOW -> 0xfefb7e + TerminalColor.Bright.BLUE -> 0x6a71f6 + TerminalColor.Bright.MAGENTA -> 0xf07ef8 + TerminalColor.Bright.CYAN -> 0x8ef9fd + TerminalColor.Bright.WHITE -> 0xfeffff + +// TerminalColor.Basic.BACKGROUND -> 1974050 + + else -> Int.MAX_VALUE + } + } +} + +class iTerm2DarkLaf : FlatDarkLaf(), ColorTheme { + override fun getColor(color: TerminalColor): Int { + return when (color) { + + TerminalColor.Basic.BACKGROUND -> 0 + TerminalColor.Basic.FOREGROUND -> 0xc7c7c7 + TerminalColor.Basic.SELECTION_BACKGROUND -> 0xc6dcfc + TerminalColor.Basic.SELECTION_FOREGROUND -> 0x000000 + TerminalColor.Basic.HYPERLINK -> 0x255ab4 + TerminalColor.Find.BACKGROUND -> 0xffff00 + TerminalColor.Find.FOREGROUND -> 0 + + TerminalColor.Cursor.BACKGROUND -> 0xc7c7c7 + + TerminalColor.Normal.BLACK -> 0 + TerminalColor.Normal.RED -> 0xb83019 + TerminalColor.Normal.GREEN -> 0x51bf37 + TerminalColor.Normal.YELLOW -> 0xc6c43d + TerminalColor.Normal.BLUE -> 0x0c24bf + TerminalColor.Normal.MAGENTA -> 0xb93ec1 + TerminalColor.Normal.CYAN -> 0x53c2c5 + TerminalColor.Normal.WHITE -> 0xc7c7c7 + + + TerminalColor.Bright.BLACK -> 0x676767 + TerminalColor.Bright.RED -> 0xef766d + TerminalColor.Bright.GREEN -> 0x8cf67a + TerminalColor.Bright.YELLOW -> 0xfefb7e + TerminalColor.Bright.BLUE -> 0x6a71f6 + TerminalColor.Bright.MAGENTA -> 0xf07ef8 + TerminalColor.Bright.CYAN -> 0x8ef9fd + TerminalColor.Bright.WHITE -> 0xfeffff + + + else -> Int.MAX_VALUE + } + } +} + + +class TermiusLightLaf : FlatPropertiesLaf("Termius Light", Properties().apply { + putAll( + mapOf( + "@baseTheme" to "light", + "@background" to "#d5dde0", + "@windowText" to "#32364a", + ) + ) +}), ColorTheme { + + override fun getColor(color: TerminalColor): Int { + + return when (color) { + TerminalColor.Basic.SELECTION_BACKGROUND, + TerminalColor.Cursor.BACKGROUND -> 0x32364a + + TerminalColor.Basic.FOREGROUND -> 0x32364a + + TerminalColor.Normal.BLACK -> 0x141729 + TerminalColor.Normal.RED -> 0xf24e50 + TerminalColor.Normal.GREEN -> 0x198c51 + TerminalColor.Normal.YELLOW -> 0xf8aa4b + TerminalColor.Normal.BLUE -> 0x004878 + TerminalColor.Normal.MAGENTA -> 0x8f3c91 + TerminalColor.Normal.CYAN -> 0x2091f6 + TerminalColor.Normal.WHITE -> 0xeeeeee + + TerminalColor.Bright.BLACK -> 0x3e4257 + TerminalColor.Bright.RED -> 0xff7375 + TerminalColor.Bright.GREEN -> 0x21b568 + TerminalColor.Bright.YELLOW -> 0xfdc47d + TerminalColor.Bright.BLUE -> 0x1d6da2 + TerminalColor.Bright.MAGENTA -> 0xff7dc5 + TerminalColor.Bright.CYAN -> 0x44a7ff + TerminalColor.Bright.WHITE -> 0xffffff + + + else -> Int.MAX_VALUE + } + } +} + + +class TermiusDarkLaf : FlatPropertiesLaf("Termius Dark", Properties().apply { + putAll( + mapOf( + "@baseTheme" to "dark", + "@background" to "#141729", + "@windowText" to "#21b568", + ) + ) +}), ColorTheme { + + override fun getColor(color: TerminalColor): Int { + return when (color) { + TerminalColor.Basic.SELECTION_BACKGROUND, + TerminalColor.Cursor.BACKGROUND -> 0x21b568 + + TerminalColor.Basic.SELECTION_FOREGROUND ->0 + + TerminalColor.Basic.FOREGROUND -> 0x21b568 + + TerminalColor.Normal.BLACK -> 0x343851 + TerminalColor.Normal.RED -> 0xf24e50 + TerminalColor.Normal.GREEN -> 0x008463 + TerminalColor.Normal.YELLOW -> 0xeca855 + TerminalColor.Normal.BLUE -> 0x08639f + TerminalColor.Normal.MAGENTA -> 0xc13282 + TerminalColor.Normal.CYAN -> 0x2091f6 + TerminalColor.Normal.WHITE -> 0xe2e3e8 + + TerminalColor.Bright.BLACK -> 0x8d91a5 + TerminalColor.Bright.RED -> 0xff7375 + TerminalColor.Bright.GREEN -> 0x3ed7be + TerminalColor.Bright.YELLOW -> 0xfdc47d + TerminalColor.Bright.BLUE -> 0x6ba0c3 + TerminalColor.Bright.MAGENTA -> 0xff7dc5 + TerminalColor.Bright.CYAN -> 0x44a7ff + TerminalColor.Bright.WHITE -> 0xffffff + + else -> Int.MAX_VALUE + } + } +} + +class NovelLaf : FlatPropertiesLaf("Novel", Properties().apply { + putAll( + mapOf( + "@baseTheme" to "light", + "@background" to "#dfdbc3", + "@windowText" to "#3b2322", + ) + ) +}), ColorTheme { + override fun getColor(color: TerminalColor): Int { + return when (color) { + TerminalColor.Normal.BLACK -> 0x000000 + TerminalColor.Normal.RED -> 0xd30f0f + TerminalColor.Normal.GREEN -> 0x00933b + TerminalColor.Normal.YELLOW -> 0xd38b40 + TerminalColor.Normal.BLUE -> 0x00528e + TerminalColor.Normal.MAGENTA -> 0xcc32cf + TerminalColor.Normal.CYAN -> 0x26c3e6 + TerminalColor.Normal.WHITE -> 0xa6a6a6 + + TerminalColor.Bright.BLACK -> 0x5c5c5c + TerminalColor.Bright.RED -> 0xe0692f + TerminalColor.Bright.GREEN -> 0x00b400 + TerminalColor.Bright.YELLOW -> 0xfff284 + TerminalColor.Bright.BLUE -> 0x3ba6f3 + TerminalColor.Bright.MAGENTA -> 0xec88c2 + TerminalColor.Bright.CYAN -> 0x38daff + TerminalColor.Bright.WHITE -> 0xf2f2f2 + + TerminalColor.Basic.SELECTION_BACKGROUND, + TerminalColor.Cursor.BACKGROUND -> 0x73635a + + + else -> Int.MAX_VALUE + } + } +} + + +class AtomOneDarkLaf : FlatPropertiesLaf("Atom One Dark", Properties().apply { + putAll( + mapOf( + "@baseTheme" to "dark", + "@background" to "#1e2127", + "@windowText" to "#abb2bf", + ) + ) +}), ColorTheme { + override fun getColor(color: TerminalColor): Int { + return when (color) { + TerminalColor.Normal.BLACK -> 0x000000 + TerminalColor.Normal.RED -> 0xca6169 + TerminalColor.Normal.GREEN -> 0x82a568 + TerminalColor.Normal.YELLOW -> 0xbf8c5d + TerminalColor.Normal.BLUE -> 0x56a2e1 + TerminalColor.Normal.MAGENTA -> 0xb76ccd + TerminalColor.Normal.CYAN -> 0x4e9aa3 + TerminalColor.Normal.WHITE -> 0xc5cbd6 + + TerminalColor.Bright.BLACK -> 0x5c6370 + TerminalColor.Bright.RED -> 0xe77c84 + TerminalColor.Bright.GREEN -> 0xb4e294 + TerminalColor.Bright.YELLOW -> 0xe9b17b + TerminalColor.Bright.BLUE -> 0x7ec5ff + TerminalColor.Bright.MAGENTA -> 0xdb8df2 + TerminalColor.Bright.CYAN -> 0x64cfdd + TerminalColor.Bright.WHITE -> 0xffffff + + TerminalColor.Basic.SELECTION_BACKGROUND, + TerminalColor.Cursor.BACKGROUND -> 0xabb2bf + + else -> Int.MAX_VALUE + } + } +} + + +class AtomOneLightLaf : FlatPropertiesLaf("Atom One Light", Properties().apply { + putAll( + mapOf( + "@baseTheme" to "light", + "@background" to "#f9f9f9", + "@windowText" to "#383a42", + ) + ) +}), ColorTheme { + override fun getColor(color: TerminalColor): Int { + return when (color) { + TerminalColor.Normal.BLACK -> 0x000000 + TerminalColor.Normal.RED -> 0xe45649 + TerminalColor.Normal.GREEN -> 0x4c9b4b + TerminalColor.Normal.YELLOW -> 0xc99525 + TerminalColor.Normal.BLUE -> 0x4078f2 + TerminalColor.Normal.MAGENTA -> 0xa626a4 + TerminalColor.Normal.CYAN -> 0x0184bc + TerminalColor.Normal.WHITE -> 0xb8b9bf + + TerminalColor.Bright.BLACK -> 0x474747 + TerminalColor.Bright.RED -> 0xff7468 + TerminalColor.Bright.GREEN -> 0x74ca72 + TerminalColor.Bright.YELLOW -> 0xdba633 + TerminalColor.Bright.BLUE -> 0x6a99ff + TerminalColor.Bright.MAGENTA -> 0xc142bf + TerminalColor.Bright.CYAN -> 0x00b1fd + TerminalColor.Bright.WHITE -> 0xffffff + + TerminalColor.Basic.SELECTION_BACKGROUND, + TerminalColor.Cursor.BACKGROUND -> 0x383a42 + + else -> Int.MAX_VALUE + } + } +} + + +class EverforestDarkLaf : FlatPropertiesLaf("Everforest Dark", Properties().apply { + putAll( + mapOf( + "@baseTheme" to "dark", + "@background" to "#282e32", + "@windowText" to "#d3c6aa", + ) + ) +}), ColorTheme { + override fun getColor(color: TerminalColor): Int { + return when (color) { + TerminalColor.Normal.BLACK -> 0x42494e + TerminalColor.Normal.RED -> 0xa1484a + TerminalColor.Normal.GREEN -> 0x778e54 + TerminalColor.Normal.YELLOW -> 0xba9e68 + TerminalColor.Normal.BLUE -> 0x388084 + TerminalColor.Normal.MAGENTA -> 0x906378 + TerminalColor.Normal.CYAN -> 0x6ca37a + TerminalColor.Normal.WHITE -> 0xc0dac6 + TerminalColor.Bright.BLACK -> 0x575656 + TerminalColor.Bright.RED -> 0xe67e80 + TerminalColor.Bright.GREEN -> 0xa7c080 + TerminalColor.Bright.YELLOW -> 0xdbbc7f + TerminalColor.Bright.BLUE -> 0x7fbbb3 + TerminalColor.Bright.MAGENTA -> 0xd699b6 + TerminalColor.Bright.CYAN -> 0x83c092 + TerminalColor.Bright.WHITE -> 0xe8f4eb + + TerminalColor.Basic.SELECTION_BACKGROUND, + TerminalColor.Cursor.BACKGROUND -> 0xd3c6aa + + else -> Int.MAX_VALUE + } + } +} + + +class EverforestLightLaf : FlatPropertiesLaf("Everforest Light", Properties().apply { + putAll( + mapOf( + "@baseTheme" to "light", + "@background" to "#fefbf1", + "@windowText" to "#5c6a72", + ) + ) +}), ColorTheme { + override fun getColor(color: TerminalColor): Int { + return when (color) { + TerminalColor.Normal.BLACK -> 0x42494e + TerminalColor.Normal.RED -> 0xd2413e + TerminalColor.Normal.GREEN -> 0x919d45 + TerminalColor.Normal.YELLOW -> 0xd89902 + TerminalColor.Normal.BLUE -> 0x2b7ba7 + TerminalColor.Normal.MAGENTA -> 0xbc72a5 + TerminalColor.Normal.CYAN -> 0x50b08c + TerminalColor.Normal.WHITE -> 0xc8d0c9 + TerminalColor.Bright.BLACK -> 0x575656 + TerminalColor.Bright.RED -> 0xe67e80 + TerminalColor.Bright.GREEN -> 0xa7c080 + TerminalColor.Bright.YELLOW -> 0xdbbc7f + TerminalColor.Bright.BLUE -> 0x7fbbb3 + TerminalColor.Bright.MAGENTA -> 0xd699b6 + TerminalColor.Bright.CYAN -> 0x83c092 + TerminalColor.Bright.WHITE -> 0xd7e2d8 + + TerminalColor.Basic.SELECTION_BACKGROUND, + TerminalColor.Cursor.BACKGROUND -> 0x5c6a72 + + else -> Int.MAX_VALUE + } + } +} + + +class NightOwlLaf : FlatPropertiesLaf("Night Owl", Properties().apply { + putAll( + mapOf( + "@baseTheme" to "dark", + "@background" to "#011627", + "@windowText" to "#d6deeb", + ) + ) +}), ColorTheme { + override fun getColor(color: TerminalColor): Int { + return when (color) { + TerminalColor.Normal.BLACK -> 0x072945 + TerminalColor.Normal.RED -> 0xef5350 + TerminalColor.Normal.GREEN -> 0x22da6e + TerminalColor.Normal.YELLOW -> 0xc5e478 + TerminalColor.Normal.BLUE -> 0x82aaff + TerminalColor.Normal.MAGENTA -> 0xc792ea + TerminalColor.Normal.CYAN -> 0x21c7a8 + TerminalColor.Normal.WHITE -> 0xe1f1ff + TerminalColor.Bright.BLACK -> 0x575656 + TerminalColor.Bright.RED -> 0xff7472 + TerminalColor.Bright.GREEN -> 0x40fa8d + TerminalColor.Bright.YELLOW -> 0xffeb95 + TerminalColor.Bright.BLUE -> 0xa0beff + TerminalColor.Bright.MAGENTA -> 0xdaa4ff + TerminalColor.Bright.CYAN -> 0x7fdbca + TerminalColor.Bright.WHITE -> 0xffffff + + TerminalColor.Basic.SELECTION_BACKGROUND, + TerminalColor.Cursor.BACKGROUND -> 0x80a4c2 + + else -> Int.MAX_VALUE + } + } +} + + +class LightOwlLaf : FlatPropertiesLaf("Light Owl", Properties().apply { + putAll( + mapOf( + "@baseTheme" to "light", + "@background" to "#fbfbfb", + "@windowText" to "#403f53", + ) + ) +}), ColorTheme { + override fun getColor(color: TerminalColor): Int { + return when (color) { + TerminalColor.Normal.BLACK -> 0x403f53 + TerminalColor.Normal.RED -> 0xde3d3b + TerminalColor.Normal.GREEN -> 0x08916a + TerminalColor.Normal.YELLOW -> 0xe0af02 + TerminalColor.Normal.BLUE -> 0x288ed7 + TerminalColor.Normal.MAGENTA -> 0xd6438a + TerminalColor.Normal.CYAN -> 0x2aa298 + TerminalColor.Normal.WHITE -> 0xe8e5e5 + TerminalColor.Bright.BLACK -> 0x57566d + TerminalColor.Bright.RED -> 0xfa5d5b + TerminalColor.Bright.GREEN -> 0x1abf90 + TerminalColor.Bright.YELLOW -> 0xf4c315 + TerminalColor.Bright.BLUE -> 0x3ca3ec + TerminalColor.Bright.MAGENTA -> 0xf559a4 + TerminalColor.Bright.CYAN -> 0x39c6ba + TerminalColor.Bright.WHITE -> 0xf6f6f6 + + TerminalColor.Basic.SELECTION_BACKGROUND, + TerminalColor.Cursor.BACKGROUND -> 0x90a7b2 + + else -> Int.MAX_VALUE + } + } +} + + +class AuraLaf : FlatPropertiesLaf("Aura", Properties().apply { + putAll( + mapOf( + "@baseTheme" to "dark", + "@background" to "#21202e", + "@windowText" to "#edecee", + ) + ) +}), ColorTheme { + override fun getColor(color: TerminalColor): Int { + return when (color) { + TerminalColor.Normal.BLACK -> 0x1c1b22 + TerminalColor.Normal.RED -> 0xff6767 + TerminalColor.Normal.GREEN -> 0x4deeb8 + TerminalColor.Normal.YELLOW -> 0xf4be77 + TerminalColor.Normal.BLUE -> 0x5b72ee + TerminalColor.Normal.MAGENTA -> 0xa277ff + TerminalColor.Normal.CYAN -> 0x51fafa + TerminalColor.Normal.WHITE -> 0xdddbfa + TerminalColor.Bright.BLACK -> 0x4d4d4d + TerminalColor.Bright.RED -> 0xffa285 + TerminalColor.Bright.GREEN -> 0x99ffdd + TerminalColor.Bright.YELLOW -> 0xffd49d + TerminalColor.Bright.BLUE -> 0x8296ff + TerminalColor.Bright.MAGENTA -> 0xb592ff + TerminalColor.Bright.CYAN -> 0x8cffff + TerminalColor.Bright.WHITE -> 0xffffff + + TerminalColor.Basic.SELECTION_BACKGROUND, + TerminalColor.Cursor.BACKGROUND -> 0xedecee + + else -> Int.MAX_VALUE + } + } +} + + +class Cobalt2Laf : FlatPropertiesLaf("Cobalt2", Properties().apply { + putAll( + mapOf( + "@baseTheme" to "dark", + "@background" to "#132738", + "@windowText" to "#ffffff", + ) + ) +}), ColorTheme { + override fun getColor(color: TerminalColor): Int { + return when (color) { + TerminalColor.Normal.BLACK -> 0x000000 + TerminalColor.Normal.RED -> 0xff0000 + TerminalColor.Normal.GREEN -> 0x38de21 + TerminalColor.Normal.YELLOW -> 0xffe50a + TerminalColor.Normal.BLUE -> 0x1460d2 + TerminalColor.Normal.MAGENTA -> 0xff4387 + TerminalColor.Normal.CYAN -> 0x00bbbb + TerminalColor.Normal.WHITE -> 0xcfcfcf + TerminalColor.Bright.BLACK -> 0x555555 + TerminalColor.Bright.RED -> 0xff757a + TerminalColor.Bright.GREEN -> 0x69fb79 + TerminalColor.Bright.YELLOW -> 0xfff285 + TerminalColor.Bright.BLUE -> 0x77adff + TerminalColor.Bright.MAGENTA -> 0xff92cc + TerminalColor.Bright.CYAN -> 0x6bffff + TerminalColor.Bright.WHITE -> 0xffffff + + TerminalColor.Basic.SELECTION_BACKGROUND, + TerminalColor.Cursor.BACKGROUND -> 0xf0cc09 + + else -> Int.MAX_VALUE + } + } +} + + +class OctocatDarkLaf : FlatPropertiesLaf("Octocat Dark", Properties().apply { + putAll( + mapOf( + "@baseTheme" to "dark", + "@background" to "#101216", + "@windowText" to "#8b949e", + ) + ) +}), ColorTheme { + override fun getColor(color: TerminalColor): Int { + return when (color) { + TerminalColor.Normal.BLACK -> 0x000000 + TerminalColor.Normal.RED -> 0xf78166 + TerminalColor.Normal.GREEN -> 0x56d364 + TerminalColor.Normal.YELLOW -> 0xe3b341 + TerminalColor.Normal.BLUE -> 0x6ca4f8 + TerminalColor.Normal.MAGENTA -> 0xdb61a2 + TerminalColor.Normal.CYAN -> 0x2b7489 + TerminalColor.Normal.WHITE -> 0xDADADA + TerminalColor.Bright.BLACK -> 0x4d4d4d + TerminalColor.Bright.RED -> 0xffb5a5 + TerminalColor.Bright.GREEN -> 0x69fb79 + TerminalColor.Bright.YELLOW -> 0xffcf5f + TerminalColor.Bright.BLUE -> 0xb0d0ff + TerminalColor.Bright.MAGENTA -> 0xff92cc + TerminalColor.Bright.CYAN -> 0x54d8ff + TerminalColor.Bright.WHITE -> 0xffffff + + TerminalColor.Basic.SELECTION_BACKGROUND, + TerminalColor.Cursor.BACKGROUND -> 0xc9d1d9 + + else -> Int.MAX_VALUE + } + } +} + + +class OctocatLightLaf : FlatPropertiesLaf("Octocat Light", Properties().apply { + putAll( + mapOf( + "@baseTheme" to "light", + "@background" to "#f4f4f4", + "@windowText" to "#3e3e3e", + ) + ) +}), ColorTheme { + override fun getColor(color: TerminalColor): Int { + return when (color) { + TerminalColor.Normal.BLACK -> 0x000000 + TerminalColor.Normal.RED -> 0xff0000 + TerminalColor.Normal.GREEN -> 0x38de21 + TerminalColor.Normal.YELLOW -> 0xffe50a + TerminalColor.Normal.BLUE -> 0x1460d2 + TerminalColor.Normal.MAGENTA -> 0xff4387 + TerminalColor.Normal.CYAN -> 0x00bbbb + TerminalColor.Normal.WHITE -> 0xcfcfcf + TerminalColor.Bright.BLACK -> 0x555555 + TerminalColor.Bright.RED -> 0xff757a + TerminalColor.Bright.GREEN -> 0x69fb79 + TerminalColor.Bright.YELLOW -> 0xfff285 + TerminalColor.Bright.BLUE -> 0x77adff + TerminalColor.Bright.MAGENTA -> 0xff92cc + TerminalColor.Bright.CYAN -> 0x6bffff + TerminalColor.Bright.WHITE -> 0xffffff + + TerminalColor.Basic.SELECTION_BACKGROUND, + TerminalColor.Cursor.BACKGROUND -> 0x3f3f3f + + else -> Int.MAX_VALUE + } + } +} + + +class AyuDarkLaf : FlatPropertiesLaf("Ayu Dark", Properties().apply { + putAll( + mapOf( + "@baseTheme" to "dark", + "@background" to "#0f1419", + "@windowText" to "#e6e1cf", + ) + ) +}), ColorTheme { + override fun getColor(color: TerminalColor): Int { + return when (color) { + TerminalColor.Normal.BLACK -> 0x000000 + TerminalColor.Normal.RED -> 0xff3333 + TerminalColor.Normal.GREEN -> 0xb8cc52 + TerminalColor.Normal.YELLOW -> 0xdbb012 + TerminalColor.Normal.BLUE -> 0x36a3d9 + TerminalColor.Normal.MAGENTA -> 0xdf7a80 + TerminalColor.Normal.CYAN -> 0x6ceedf + TerminalColor.Normal.WHITE -> 0xababab + TerminalColor.Bright.BLACK -> 0x323232 + TerminalColor.Bright.RED -> 0xff8181 + TerminalColor.Bright.GREEN -> 0xeafe84 + TerminalColor.Bright.YELLOW -> 0xffe174 + TerminalColor.Bright.BLUE -> 0x68d5ff + TerminalColor.Bright.MAGENTA -> 0xffa3aa + TerminalColor.Bright.CYAN -> 0x94fff1 + TerminalColor.Bright.WHITE -> 0xffffff + + TerminalColor.Basic.SELECTION_BACKGROUND, + TerminalColor.Cursor.BACKGROUND -> 0xf29718 + + else -> Int.MAX_VALUE + } + } +} + + +class AyuLightLaf : FlatPropertiesLaf("Ayu Light", Properties().apply { + putAll( + mapOf( + "@baseTheme" to "light", + "@background" to "#fafafa", + "@windowText" to "#5c6773", + ) + ) +}), ColorTheme { + override fun getColor(color: TerminalColor): Int { + return when (color) { + TerminalColor.Normal.BLACK -> 0x000000 + TerminalColor.Normal.RED -> 0xff3333 + TerminalColor.Normal.GREEN -> 0x319900 + TerminalColor.Normal.YELLOW -> 0xf29718 + TerminalColor.Normal.BLUE -> 0x41a6d9 + TerminalColor.Normal.MAGENTA -> 0xe07ead + TerminalColor.Normal.CYAN -> 0x1dd1b0 + TerminalColor.Normal.WHITE -> 0xdfdddd + TerminalColor.Bright.BLACK -> 0x323232 + TerminalColor.Bright.RED -> 0xff5959 + TerminalColor.Bright.GREEN -> 0xb8e532 + TerminalColor.Bright.YELLOW -> 0xffc94a + TerminalColor.Bright.BLUE -> 0x73d8ff + TerminalColor.Bright.MAGENTA -> 0xffa3aa + TerminalColor.Bright.CYAN -> 0x7ff1cb + TerminalColor.Bright.WHITE -> 0xffffff + + TerminalColor.Basic.SELECTION_BACKGROUND, + TerminalColor.Cursor.BACKGROUND -> 0xff6a00 + + else -> Int.MAX_VALUE + } + } +} + + +class HomebrewLaf : FlatPropertiesLaf("Homebrew", Properties().apply { + putAll( + mapOf( + "@baseTheme" to "dark", + "@background" to "#000000", + "@windowText" to "#00ff00", + ) + ) +}), ColorTheme { + override fun getColor(color: TerminalColor): Int { + return when (color) { + TerminalColor.Normal.BLACK -> 0x2e2e2e + TerminalColor.Normal.RED -> 0xc93434 + TerminalColor.Normal.GREEN -> 0x348e48 + TerminalColor.Normal.YELLOW -> 0xe09e00 + TerminalColor.Normal.BLUE -> 0x0031e0 + TerminalColor.Normal.MAGENTA -> 0xe235ff + TerminalColor.Normal.CYAN -> 0x3fc1dd + TerminalColor.Normal.WHITE -> 0xd0cfcf + TerminalColor.Bright.BLACK -> 0x5b5b5b + TerminalColor.Bright.RED -> 0xff6767 + TerminalColor.Bright.GREEN -> 0x31ff31 + TerminalColor.Bright.YELLOW -> 0xffdca8 + TerminalColor.Bright.BLUE -> 0x4465da + TerminalColor.Bright.MAGENTA -> 0xff5fc8 + TerminalColor.Bright.CYAN -> 0x8debff + TerminalColor.Bright.WHITE -> 0xe6e6e6 + + TerminalColor.Basic.SELECTION_BACKGROUND, + TerminalColor.Cursor.BACKGROUND -> 0x23ff18 + + TerminalColor.Basic.FOREGROUND -> 0x00ff00 + + else -> Int.MAX_VALUE + } + } +} + + +class ProLaf : FlatPropertiesLaf("Pro", Properties().apply { + putAll( + mapOf( + "@baseTheme" to "dark", + "@background" to "#000000", + "@windowText" to "#f2f2f2", + ) + ) +}), ColorTheme { + override fun getColor(color: TerminalColor): Int { + return when (color) { + TerminalColor.Normal.BLACK -> 0x2e2e2e + TerminalColor.Normal.RED -> 0xc93434 + TerminalColor.Normal.GREEN -> 0x348e48 + TerminalColor.Normal.YELLOW -> 0xe09e00 + TerminalColor.Normal.BLUE -> 0x002bc7 + TerminalColor.Normal.MAGENTA -> 0xe235ff + TerminalColor.Normal.CYAN -> 0x3fc1dd + TerminalColor.Normal.WHITE -> 0xd0cfcf + TerminalColor.Bright.BLACK -> 0x5b5b5b + TerminalColor.Bright.RED -> 0xff6767 + TerminalColor.Bright.GREEN -> 0x31ff31 + TerminalColor.Bright.YELLOW -> 0xffdca8 + TerminalColor.Bright.BLUE -> 0x4465da + TerminalColor.Bright.MAGENTA -> 0xff5fc8 + TerminalColor.Bright.CYAN -> 0x8debff + TerminalColor.Bright.WHITE -> 0xe6e6e6 + + TerminalColor.Basic.SELECTION_BACKGROUND, + TerminalColor.Cursor.BACKGROUND -> 0x4d4d4d + + TerminalColor.Basic.FOREGROUND -> 0xf2f2f2 + + else -> Int.MAX_VALUE + } + } +} + + +class NordLightLaf : FlatPropertiesLaf("Nord Light", Properties().apply { + putAll( + mapOf( + "@baseTheme" to "light", + "@background" to "#e5e9f0", + "@windowText" to "#414858", + ) + ) +}), ColorTheme { + override fun getColor(color: TerminalColor): Int { + return when (color) { + TerminalColor.Normal.BLACK -> 0x2c3344 + TerminalColor.Normal.RED -> 0xae545d + TerminalColor.Normal.GREEN -> 0x8ca377 + TerminalColor.Normal.YELLOW -> 0xdabe84 + TerminalColor.Normal.BLUE -> 0x718fae + TerminalColor.Normal.MAGENTA -> 0x95728e + TerminalColor.Normal.CYAN -> 0x78acbb + TerminalColor.Normal.WHITE -> 0xd8dee9 + TerminalColor.Bright.BLACK -> 0x4c556a + TerminalColor.Bright.RED -> 0xd97982 + TerminalColor.Bright.GREEN -> 0xa3be8b + TerminalColor.Bright.YELLOW -> 0xeacb8a + TerminalColor.Bright.BLUE -> 0xa4c7e9 + TerminalColor.Bright.MAGENTA -> 0xb48dac + TerminalColor.Bright.CYAN -> 0x8fbcbb + TerminalColor.Bright.WHITE -> 0xeceff4 + + TerminalColor.Basic.SELECTION_BACKGROUND, + TerminalColor.Cursor.BACKGROUND -> 0x88c0d0 + + TerminalColor.Basic.FOREGROUND -> 0x414858 + + else -> Int.MAX_VALUE + } + } +} + + +class NordDarkLaf : FlatPropertiesLaf("Nord Dark", Properties().apply { + putAll( + mapOf( + "@baseTheme" to "dark", + "@background" to "#2e3440", + "@windowText" to "#d8dee9", + ) + ) +}), ColorTheme { + override fun getColor(color: TerminalColor): Int { + return when (color) { + TerminalColor.Normal.BLACK -> 0x3b4252 + TerminalColor.Normal.RED -> 0xae545d + TerminalColor.Normal.GREEN -> 0x8ca377 + TerminalColor.Normal.YELLOW -> 0xdabe84 + TerminalColor.Normal.BLUE -> 0x718fae + TerminalColor.Normal.MAGENTA -> 0x95728e + TerminalColor.Normal.CYAN -> 0x78acbb + TerminalColor.Normal.WHITE -> 0xd8dee9 + TerminalColor.Bright.BLACK -> 0x4c556a + TerminalColor.Bright.RED -> 0xd97982 + TerminalColor.Bright.GREEN -> 0xa3be8b + TerminalColor.Bright.YELLOW -> 0xeacb8a + TerminalColor.Bright.BLUE -> 0xa4c7e9 + TerminalColor.Bright.MAGENTA -> 0xb48dac + TerminalColor.Bright.CYAN -> 0x8fbcbb + TerminalColor.Bright.WHITE -> 0xeceff4 + + TerminalColor.Basic.SELECTION_BACKGROUND, + TerminalColor.Cursor.BACKGROUND -> 0xeceff4 + + TerminalColor.Basic.FOREGROUND -> 0xd8dee9 + + + else -> Int.MAX_VALUE + } + } +} + + +class GitHubLightLaf : FlatPropertiesLaf("GitHub Light", Properties().apply { + putAll( + mapOf( + "@baseTheme" to "light", + "@background" to "#f4f4f4", + "@windowText" to "#3e3e3e", + ) + ) +}), ColorTheme { + override fun getColor(color: TerminalColor): Int { + return when (color) { + TerminalColor.Normal.BLACK -> 0x3e3e3e + TerminalColor.Normal.RED -> 0x970b16 + TerminalColor.Normal.GREEN -> 0x07962a + TerminalColor.Normal.YELLOW -> 0xf8eec7 + TerminalColor.Normal.BLUE -> 0x003e8a + TerminalColor.Normal.MAGENTA -> 0xe94691 + TerminalColor.Normal.CYAN -> 0x89d1ec + TerminalColor.Normal.WHITE -> 0x3e3e3e + TerminalColor.Bright.BLACK -> 0x666666 + TerminalColor.Bright.RED -> 0xde0000 + TerminalColor.Bright.GREEN -> 0x87d5a2 + TerminalColor.Bright.YELLOW -> 0xf1d007 + TerminalColor.Bright.BLUE -> 0x2e6cba + TerminalColor.Bright.MAGENTA -> 0xffa29f + TerminalColor.Bright.CYAN -> 0x1cfafe + TerminalColor.Bright.WHITE -> 0xffffff + + TerminalColor.Basic.SELECTION_BACKGROUND, + TerminalColor.Cursor.BACKGROUND -> 0x3f3f3f + + TerminalColor.Basic.FOREGROUND -> 0x3e3e3e + + else -> Int.MAX_VALUE + } + } +} + + +class GitHubDarkLaf : FlatPropertiesLaf("GitHub Dark", Properties().apply { + putAll( + mapOf( + "@baseTheme" to "dark", + "@background" to "#101216", + "@windowText" to "#8b949e", + ) + ) +}), ColorTheme { + override fun getColor(color: TerminalColor): Int { + return when (color) { + TerminalColor.Normal.BLACK -> 0x000000 + TerminalColor.Normal.RED -> 0xf78166 + TerminalColor.Normal.GREEN -> 0x56d364 + TerminalColor.Normal.YELLOW -> 0xe3b341 + TerminalColor.Normal.BLUE -> 0x6ca4f8 + TerminalColor.Normal.MAGENTA -> 0xdb61a2 + TerminalColor.Normal.CYAN -> 0x2b7489 + TerminalColor.Normal.WHITE -> 0x8b949e + TerminalColor.Bright.BLACK -> 0x4d4d4d + TerminalColor.Bright.RED -> 0xf78166 + TerminalColor.Bright.GREEN -> 0x56d364 + TerminalColor.Bright.YELLOW -> 0xe3b341 + TerminalColor.Bright.BLUE -> 0x6ca4f8 + TerminalColor.Bright.MAGENTA -> 0xdb61a2 + TerminalColor.Bright.CYAN -> 0x2b7489 + TerminalColor.Bright.WHITE -> 0xffffff + + TerminalColor.Basic.SELECTION_BACKGROUND, + TerminalColor.Cursor.BACKGROUND -> 0xc9d1d9 + + TerminalColor.Basic.FOREGROUND -> 0x8b949e + + + else -> Int.MAX_VALUE + } + } +} + + +class ChalkLaf : FlatPropertiesLaf("Chalk", Properties().apply { + putAll( + mapOf( + "@baseTheme" to "dark", + "@background" to "#2b2d2e", + "@windowText" to "#d2d8d9", + ) + ) +}), ColorTheme { + override fun getColor(color: TerminalColor): Int { + return when (color) { + TerminalColor.Normal.BLACK -> 0x7d8b8f + TerminalColor.Normal.RED -> 0xb23a52 + TerminalColor.Normal.GREEN -> 0x789b6a + TerminalColor.Normal.YELLOW -> 0xb9ac4a + TerminalColor.Normal.BLUE -> 0x2a7fac + TerminalColor.Normal.MAGENTA -> 0xbd4f5a + TerminalColor.Normal.CYAN -> 0x44a799 + TerminalColor.Normal.WHITE -> 0xd2d8d9 + TerminalColor.Bright.BLACK -> 0x888888 + TerminalColor.Bright.RED -> 0xf24840 + TerminalColor.Bright.GREEN -> 0x80c470 + TerminalColor.Bright.YELLOW -> 0xffeb62 + TerminalColor.Bright.BLUE -> 0x4196ff + TerminalColor.Bright.MAGENTA -> 0xfc5275 + TerminalColor.Bright.CYAN -> 0x53cdbd + TerminalColor.Bright.WHITE -> 0xd2d8d9 + + TerminalColor.Basic.SELECTION_BACKGROUND, + TerminalColor.Cursor.BACKGROUND -> 0x708284 + + TerminalColor.Basic.FOREGROUND -> 0xd2d8d9 + + + else -> Int.MAX_VALUE + } + } +} diff --git a/src/main/kotlin/app/termora/LocalTerminalTab.kt b/src/main/kotlin/app/termora/LocalTerminalTab.kt new file mode 100644 index 0000000..a1b92c9 --- /dev/null +++ b/src/main/kotlin/app/termora/LocalTerminalTab.kt @@ -0,0 +1,20 @@ +package app.termora + +import app.termora.terminal.PtyConnector +import org.apache.commons.io.Charsets +import java.nio.charset.StandardCharsets + +class LocalTerminalTab(host: Host) : PtyHostTerminalTab(host) { + + override suspend fun openPtyConnector(): PtyConnector { + val winSize = terminalPanel.winSize() + val ptyConnector = PtyConnectorFactory.instance.createPtyConnector( + winSize.rows, winSize.cols, + host.options.envs(), + Charsets.toCharset(host.options.encoding, StandardCharsets.UTF_8), + ) + + return ptyConnector + } + +} \ No newline at end of file diff --git a/src/main/kotlin/app/termora/LogicCustomTitleBar.kt b/src/main/kotlin/app/termora/LogicCustomTitleBar.kt new file mode 100644 index 0000000..fa38c65 --- /dev/null +++ b/src/main/kotlin/app/termora/LogicCustomTitleBar.kt @@ -0,0 +1,109 @@ +package app.termora + +import com.formdev.flatlaf.FlatClientProperties +import com.jetbrains.JBR +import com.jetbrains.WindowDecorations.CustomTitleBar +import java.awt.Rectangle +import java.awt.Window +import javax.swing.RootPaneContainer + +class LogicCustomTitleBar(private val titleBar: CustomTitleBar) : CustomTitleBar { + companion object { + fun createCustomTitleBar(rootPaneContainer: RootPaneContainer): CustomTitleBar { + if (!JBR.isWindowDecorationsSupported()) { + return LogicCustomTitleBar(object : CustomTitleBar { + override fun getHeight(): Float { + val bounds = rootPaneContainer.rootPane + .getClientProperty(FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_BOUNDS) + if (bounds is Rectangle) { + return bounds.height.toFloat() + } + return 0f + } + + override fun setHeight(height: Float) { + rootPaneContainer.rootPane.putClientProperty( + FlatClientProperties.TITLE_BAR_HEIGHT, + height.toInt() + ) + } + + override fun getProperties(): MutableMap { + return mutableMapOf() + } + + override fun putProperties(m: MutableMap?) { + + } + + override fun putProperty(key: String?, value: Any?) { + if (key == "controls.visible" && value is Boolean) { + rootPaneContainer.rootPane.putClientProperty( + FlatClientProperties.TITLE_BAR_SHOW_CLOSE, + value + ) + } + } + + override fun getLeftInset(): Float { + return 0f + } + + override fun getRightInset(): Float { + val bounds = rootPaneContainer.rootPane + .getClientProperty(FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_BOUNDS) + if (bounds is Rectangle) { + return bounds.width.toFloat() + } + return 0f + } + + override fun forceHitTest(client: Boolean) { + + } + + override fun getContainingWindow(): Window { + return rootPaneContainer as Window + } + }) + } + return JBR.getWindowDecorations().createCustomTitleBar() + } + } + + override fun getHeight(): Float { + return titleBar.height + } + + override fun setHeight(height: Float) { + titleBar.height = height + } + + override fun getProperties(): MutableMap { + return titleBar.properties + } + + override fun putProperties(m: MutableMap?) { + titleBar.putProperties(m) + } + + override fun putProperty(key: String?, value: Any?) { + titleBar.putProperty(key, value) + } + + override fun getLeftInset(): Float { + return titleBar.leftInset + } + + override fun getRightInset(): Float { + return titleBar.rightInset + } + + override fun forceHitTest(client: Boolean) { + titleBar.forceHitTest(client) + } + + override fun getContainingWindow(): Window { + return titleBar.containingWindow + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/termora/Main.kt b/src/main/kotlin/app/termora/Main.kt new file mode 100644 index 0000000..6486990 --- /dev/null +++ b/src/main/kotlin/app/termora/Main.kt @@ -0,0 +1,6 @@ +package app.termora + +fun main() { + ApplicationRunner().run() +} + diff --git a/src/main/kotlin/app/termora/MultiplePtyConnector.kt b/src/main/kotlin/app/termora/MultiplePtyConnector.kt new file mode 100644 index 0000000..cddec2c --- /dev/null +++ b/src/main/kotlin/app/termora/MultiplePtyConnector.kt @@ -0,0 +1,45 @@ +package app.termora + +import app.termora.terminal.PtyConnector +import app.termora.terminal.PtyConnectorDelegate +import org.jdesktop.swingx.action.ActionManager + +/** + * 当开启转发时,会获取到所有的 [PtyConnector] 然后跳过中间层,直接找到最近的一个 [MultiplePtyConnector],如果找不到那就以最后一个匹配不到的为准 [getMultiplePtyConnector]。 + */ +class MultiplePtyConnector(private val myConnector: PtyConnector) : PtyConnectorDelegate(myConnector) { + + private val isMultiple get() = ActionManager.getInstance().isSelected(Actions.MULTIPLE) + private val ptyConnectors get() = PtyConnectorFactory.instance.getPtyConnectors() + + override fun write(buffer: ByteArray, offset: Int, len: Int) { + if (isMultiple) { + for (connector in ptyConnectors) { + getMultiplePtyConnector(connector).write(buffer, offset, len) + } + } else { + myConnector.write(buffer, offset, len) + } + } + + + private fun getMultiplePtyConnector(connector: PtyConnector): PtyConnector { + if (connector is MultiplePtyConnector) { + val c = connector.myConnector + if (c is MultiplePtyConnector) { + return getMultiplePtyConnector(c) + } + return c + } + + if (connector is PtyConnectorDelegate) { + val c = connector.ptyConnector + if (c != null) { + return getMultiplePtyConnector(c) + } + } + + return connector + } + +} \ No newline at end of file diff --git a/src/main/kotlin/app/termora/MultipleTerminalListener.kt b/src/main/kotlin/app/termora/MultipleTerminalListener.kt new file mode 100644 index 0000000..8ffa3bc --- /dev/null +++ b/src/main/kotlin/app/termora/MultipleTerminalListener.kt @@ -0,0 +1,44 @@ +package app.termora + +import app.termora.terminal.Terminal +import app.termora.terminal.TerminalColor +import app.termora.terminal.TextStyle +import app.termora.terminal.panel.TerminalDisplay +import app.termora.terminal.panel.TerminalPaintListener +import app.termora.terminal.panel.TerminalPanel +import org.jdesktop.swingx.action.ActionManager +import java.awt.Color +import java.awt.Graphics + +class MultipleTerminalListener : TerminalPaintListener { + override fun after( + offset: Int, + count: Int, + g: Graphics, + terminalPanel: TerminalPanel, + terminalDisplay: TerminalDisplay, + terminal: Terminal + ) { + if (!ActionManager.getInstance().isSelected(Actions.MULTIPLE)) { + return + } + + val oldFont = g.font + val colorPalette = terminal.getTerminalModel().getColorPalette() + val text = I18n.getString("termora.tools.multiple") + val font = terminalDisplay.getDisplayFont(text, TextStyle.Default) + val width = g.getFontMetrics(font).stringWidth(text) + // 正在搜索那么需要下移 + val finding = terminal.getTerminalModel().getData(TerminalPanel.Finding, false) + + g.font = font + g.color = Color(colorPalette.getColor(TerminalColor.Normal.RED)) + g.drawString( + text, + terminalDisplay.width - width - terminalPanel.getAverageCharWidth() / 2, + g.fontMetrics.ascent + if (finding) + g.fontMetrics.height + g.fontMetrics.ascent / 2 else 0 + ) + g.font = oldFont + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/termora/MyTabbedPane.kt b/src/main/kotlin/app/termora/MyTabbedPane.kt new file mode 100644 index 0000000..d88c83b --- /dev/null +++ b/src/main/kotlin/app/termora/MyTabbedPane.kt @@ -0,0 +1,11 @@ +package app.termora + +import com.formdev.flatlaf.extras.components.FlatTabbedPane + +class MyTabbedPane : FlatTabbedPane() { + override fun setSelectedIndex(index: Int) { + val oldIndex = selectedIndex + super.setSelectedIndex(index) + firePropertyChange("selectedIndex", oldIndex,index) + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/termora/OpenHostActionEvent.kt b/src/main/kotlin/app/termora/OpenHostActionEvent.kt new file mode 100644 index 0000000..e78e54d --- /dev/null +++ b/src/main/kotlin/app/termora/OpenHostActionEvent.kt @@ -0,0 +1,5 @@ +package app.termora + +import java.awt.event.ActionEvent + +class OpenHostActionEvent(source: Any, val host: Host) : ActionEvent(source, ACTION_PERFORMED, String()) \ No newline at end of file diff --git a/src/main/kotlin/app/termora/OptionPane.kt b/src/main/kotlin/app/termora/OptionPane.kt new file mode 100644 index 0000000..813ab48 --- /dev/null +++ b/src/main/kotlin/app/termora/OptionPane.kt @@ -0,0 +1,165 @@ +package app.termora + +import com.formdev.flatlaf.FlatClientProperties +import com.formdev.flatlaf.extras.components.FlatTextPane +import com.formdev.flatlaf.util.SystemInfo +import com.jetbrains.JBR +import kotlinx.coroutines.* +import kotlinx.coroutines.swing.Swing +import org.jdesktop.swingx.JXLabel +import java.awt.BorderLayout +import java.awt.Component +import java.awt.Desktop +import java.awt.Dimension +import java.awt.event.WindowAdapter +import java.awt.event.WindowEvent +import java.io.File +import javax.swing.* +import kotlin.time.Duration +import kotlin.time.Duration.Companion.milliseconds + +object OptionPane { + fun showConfirmDialog( + parentComponent: Component?, + message: Any, + title: String = UIManager.getString("OptionPane.messageDialogTitle"), + optionType: Int = JOptionPane.YES_NO_OPTION, + messageType: Int = JOptionPane.QUESTION_MESSAGE, + icon: Icon? = null, + options: Array? = null, + initialValue: Any? = null, + ): Int { + + val panel = if (message is JComponent) { + message + } else { + val label = FlatTextPane() + label.contentType = "text/html" + label.text = "$message" + label.isEditable = false + label.background = null + label.border = BorderFactory.createEmptyBorder() + label + } + + val pane = object : JOptionPane(panel, messageType, optionType, icon, options, initialValue) { + override fun selectInitialValue() { + super.selectInitialValue() + if (message is JComponent) { + message.requestFocusInWindow() + } + } + } + val dialog = initDialog(pane.createDialog(parentComponent, title)) + dialog.addWindowListener(object : WindowAdapter() { + override fun windowOpened(e: WindowEvent) { + pane.selectInitialValue() + } + }) + dialog.isVisible = true + dialog.dispose() + val selectedValue = pane.value + + + if (selectedValue == null) { + return -1 + } else if (pane.options == null) { + return if (selectedValue is Int) selectedValue else -1 + } else { + var counter = 0 + + val maxCounter: Int = pane.options.size + while (counter < maxCounter) { + if (pane.options[counter] == selectedValue) { + return counter + } + ++counter + } + + return -1 + } + } + + fun showMessageDialog( + parentComponent: Component?, + message: String, + title: String = UIManager.getString("OptionPane.messageDialogTitle"), + messageType: Int = JOptionPane.INFORMATION_MESSAGE, + duration: Duration = 0.milliseconds, + ) { + val label = JTextPane() + label.contentType = "text/html" + label.text = "$message" + label.isEditable = false + label.background = null + label.border = BorderFactory.createEmptyBorder() + val pane = JOptionPane(label, messageType, JOptionPane.DEFAULT_OPTION) + val dialog = initDialog(pane.createDialog(parentComponent, title)) + if (duration.inWholeMilliseconds > 0) { + dialog.addWindowListener(object : WindowAdapter() { + @OptIn(DelicateCoroutinesApi::class) + override fun windowOpened(e: WindowEvent) { + GlobalScope.launch(Dispatchers.Swing) { + delay(duration.inWholeMilliseconds) + if (dialog.isVisible) { + dialog.isVisible = false + } + } + } + }) + } + pane.selectInitialValue() + dialog.isVisible = true + dialog.dispose() + } + + fun openFileInFolder( + parentComponent: Component, + file: File, + yMessage: String, + nMessage: String? = null, + ) { + if (Desktop.isDesktopSupported() && Desktop.getDesktop() + .isSupported(Desktop.Action.BROWSE_FILE_DIR) + ) { + if (JOptionPane.YES_OPTION == showConfirmDialog( + parentComponent, + yMessage, + optionType = JOptionPane.YES_NO_OPTION + ) + ) { + Desktop.getDesktop().browseFileDirectory(file) + } + } else if (nMessage != null) { + showMessageDialog( + parentComponent, + nMessage, + messageType = JOptionPane.INFORMATION_MESSAGE + ) + } + } + + private fun initDialog(dialog: JDialog): JDialog { + + if (JBR.isWindowDecorationsSupported()) { + + val windowDecorations = JBR.getWindowDecorations() + val titleBar = windowDecorations.createCustomTitleBar() + titleBar.putProperty("controls.visible", false) + titleBar.height = UIManager.getInt("TabbedPane.tabHeight") - if (SystemInfo.isMacOS) 10f else 6f + windowDecorations.setCustomTitleBar(dialog, titleBar) + + val label = JLabel(dialog.title) + label.putClientProperty(FlatClientProperties.STYLE, "font: bold") + val box = Box.createHorizontalBox() + box.add(Box.createHorizontalGlue()) + box.add(label) + box.add(Box.createHorizontalGlue()) + box.preferredSize = Dimension(-1, titleBar.height.toInt()) + + dialog.contentPane.add(box, BorderLayout.NORTH) + } + + return dialog + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/termora/OptionsPane.kt b/src/main/kotlin/app/termora/OptionsPane.kt new file mode 100644 index 0000000..0d5da1f --- /dev/null +++ b/src/main/kotlin/app/termora/OptionsPane.kt @@ -0,0 +1,136 @@ +package app.termora + +import com.formdev.flatlaf.FlatLaf +import java.awt.* +import javax.swing.* +import javax.swing.border.Border + + +open class OptionsPane : JPanel(BorderLayout()) { + protected val formMargin = "7dlu" + + protected val tabListModel = DefaultListModel


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%0AG7t)#Ze4#poGu8d}|5dXx0UB`0T1ZpW$l! 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/icons/termora.ico b/src/main/resources/icons/termora.ico new file mode 100644 index 0000000000000000000000000000000000000000..a6ba728e943083e06ae7d3f39806b94f201e14a0 GIT binary patch literal 28870 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/icons/termora.png b/src/main/resources/icons/termora.png new file mode 100644 index 0000000000000000000000000000000000000000..aadea1e7beff82bba3d243e186ab7a6e060ef988 GIT binary patch literal 24703 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

=gSuxe_hHdmPFs@Qv5v8P6!`%T6=O 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 N@O1TaS?83{1OPpe-5CG? literal 0 HcmV?d00001 diff --git a/src/main/resources/icons/termora_24x24.png b/src/main/resources/icons/termora_24x24.png new file mode 100644 index 0000000000000000000000000000000000000000..98f685edbb1d5607f9b4152a5977c5bf9174fe19 GIT binary patch literal 1473 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 bZP7>AHSN(6>g-sb@;6U+*-I;T3QqV39)Zb? literal 0 HcmV?d00001 diff --git a/src/main/resources/icons/termora_48x48.png b/src/main/resources/icons/termora_48x48.png new file mode 100644 index 0000000000000000000000000000000000000000..1dbb7b6df6cc88a5a0aabefd96b11e40579b309f GIT binary patch literal 2510 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 C5*`)+ literal 0 HcmV?d00001 diff --git a/src/main/resources/icons/termora_64x64.png b/src/main/resources/icons/termora_64x64.png new file mode 100644 index 0000000000000000000000000000000000000000..c53b692b981318d96a3ee4356efa91a0c09d5f15 GIT binary patch literal 3000 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 Vn^BbKVmSGIy>!7uw_MvX;y(bXHx2** literal 0 HcmV?d00001 diff --git a/src/main/resources/icons/tianyiyun.svg b/src/main/resources/icons/tianyiyun.svg new file mode 100644 index 0000000..793ee91 --- /dev/null +++ b/src/main/resources/icons/tianyiyun.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/src/main/resources/icons/uiForm.svg b/src/main/resources/icons/uiForm.svg new file mode 100644 index 0000000..23a796e --- /dev/null +++ b/src/main/resources/icons/uiForm.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/main/resources/icons/uiForm_dark.svg b/src/main/resources/icons/uiForm_dark.svg new file mode 100644 index 0000000..caf70e0 --- /dev/null +++ b/src/main/resources/icons/uiForm_dark.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/main/resources/icons/unlocked.svg b/src/main/resources/icons/unlocked.svg new file mode 100644 index 0000000..305bcde --- /dev/null +++ b/src/main/resources/icons/unlocked.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/main/resources/icons/unlocked_dark.svg b/src/main/resources/icons/unlocked_dark.svg new file mode 100644 index 0000000..a53f822 --- /dev/null +++ b/src/main/resources/icons/unlocked_dark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/main/resources/icons/up.svg b/src/main/resources/icons/up.svg new file mode 100644 index 0000000..a884232 --- /dev/null +++ b/src/main/resources/icons/up.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/main/resources/icons/up_dark.svg b/src/main/resources/icons/up_dark.svg new file mode 100644 index 0000000..db56b61 --- /dev/null +++ b/src/main/resources/icons/up_dark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/main/resources/icons/upload.svg b/src/main/resources/icons/upload.svg new file mode 100644 index 0000000..b26837f --- /dev/null +++ b/src/main/resources/icons/upload.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/main/resources/icons/upload_dark.svg b/src/main/resources/icons/upload_dark.svg new file mode 100644 index 0000000..2241155 --- /dev/null +++ b/src/main/resources/icons/upload_dark.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/main/resources/icons/user.svg b/src/main/resources/icons/user.svg new file mode 100644 index 0000000..ddbd1d5 --- /dev/null +++ b/src/main/resources/icons/user.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/main/resources/icons/user_dark.svg b/src/main/resources/icons/user_dark.svg new file mode 100644 index 0000000..49e4dd6 --- /dev/null +++ b/src/main/resources/icons/user_dark.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/main/resources/icons/vcs.svg b/src/main/resources/icons/vcs.svg new file mode 100644 index 0000000..37f8363 --- /dev/null +++ b/src/main/resources/icons/vcs.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/main/resources/icons/vcs_dark.svg b/src/main/resources/icons/vcs_dark.svg new file mode 100644 index 0000000..fb3435c --- /dev/null +++ b/src/main/resources/icons/vcs_dark.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/main/resources/icons/warning.svg b/src/main/resources/icons/warning.svg new file mode 100644 index 0000000..336eae2 --- /dev/null +++ b/src/main/resources/icons/warning.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/main/resources/icons/warningDialog.svg b/src/main/resources/icons/warningDialog.svg new file mode 100644 index 0000000..4d4740b --- /dev/null +++ b/src/main/resources/icons/warningDialog.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/main/resources/icons/warningDialog_dark.svg b/src/main/resources/icons/warningDialog_dark.svg new file mode 100644 index 0000000..69b5c4e --- /dev/null +++ b/src/main/resources/icons/warningDialog_dark.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/main/resources/icons/warning_dark.svg b/src/main/resources/icons/warning_dark.svg new file mode 100644 index 0000000..56f4a92 --- /dev/null +++ b/src/main/resources/icons/warning_dark.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/main/resources/icons/web.svg b/src/main/resources/icons/web.svg new file mode 100644 index 0000000..9ed5e6c --- /dev/null +++ b/src/main/resources/icons/web.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/main/resources/icons/web_dark.svg b/src/main/resources/icons/web_dark.svg new file mode 100644 index 0000000..889c74d --- /dev/null +++ b/src/main/resources/icons/web_dark.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/main/resources/icons/yandexCloud.svg b/src/main/resources/icons/yandexCloud.svg new file mode 100644 index 0000000..f9915bf --- /dev/null +++ b/src/main/resources/icons/yandexCloud.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + diff --git a/src/main/resources/tinylog.properties b/src/main/resources/tinylog.properties new file mode 100644 index 0000000..584b5c5 --- /dev/null +++ b/src/main/resources/tinylog.properties @@ -0,0 +1,16 @@ +writer_file=rolling file +writer_file.file=#{user.home}/.termora/logs/{date:yyyy}-{date:MM}-{date:dd}.log +writer_file.latest=#{user.home}/.termora/logs/termora.log +writer_file.level=#{logger.file.level:info} +writer_file.format={date: HH:mm:ss.SSS} {level}: {message} +writer_file.policies=daily +writer_file.backups=3 +writer_file.append=true + +writer_console=console +writer_console.level=#{logger.console.level:info} +writer_console.format={date: HH:mm:ss.SSS} {level}: {message} + +level@org.apache.sshd=warn + + diff --git a/src/test/kotlin/app/termora/AESTest.kt b/src/test/kotlin/app/termora/AESTest.kt new file mode 100644 index 0000000..d56db3e --- /dev/null +++ b/src/test/kotlin/app/termora/AESTest.kt @@ -0,0 +1,25 @@ +package app.termora + +import kotlin.test.Test +import kotlin.test.assertContentEquals + + +class AESTest { + @Test + fun test() { + val data = "hello world. 中国 😄".toByteArray() + val key = AES.randomBytes() + val iv = AES.randomBytes(16) + assertContentEquals(AES.CBC.decrypt(key, iv, AES.CBC.encrypt(key, iv, data)), data) + } + + + @Test + fun testECB() { + val data = "hello world. 中国 😄".toByteArray() + val key = AES.randomBytes() + assertContentEquals(AES.ECB.decrypt(key, AES.ECB.encrypt(key, data)), data) + } + + +} \ No newline at end of file diff --git a/src/test/kotlin/app/termora/BreakIteratorTest.kt b/src/test/kotlin/app/termora/BreakIteratorTest.kt new file mode 100644 index 0000000..7c30c12 --- /dev/null +++ b/src/test/kotlin/app/termora/BreakIteratorTest.kt @@ -0,0 +1,50 @@ +package app.termora + +import java.text.BreakIterator +import kotlin.test.Test + +class BreakIteratorTest { + @Test + fun test() { + val text = "Hello World" + val breakIterator = BreakIterator.getCharacterInstance() + breakIterator.setText(text) + var start = breakIterator.first() + var end = breakIterator.next() + while (end != BreakIterator.DONE) { + println(text.substring(start, end)) + start = end + end = breakIterator.next() + } + + } + + + @Test + fun testChinese() { + val text = "你好中国 hello 123 @!。" + val breakIterator = BreakIterator.getCharacterInstance() + breakIterator.setText(text) + var start = breakIterator.first() + var end = breakIterator.next() + while (end != BreakIterator.DONE) { + println(text.substring(start, end)) + start = end + end = breakIterator.next() + } + } + + @Test + fun testEmoji() { + val text = "⌚️哈哈😂123🀄️" + val breakIterator = BreakIterator.getCharacterInstance() + breakIterator.setText(text) + var start = breakIterator.first() + var end = breakIterator.next() + while (end != BreakIterator.DONE) { + println(text.substring(start, end)) + start = end + end = breakIterator.next() + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/app/termora/HostTest.kt b/src/test/kotlin/app/termora/HostTest.kt new file mode 100644 index 0000000..c6aaef3 --- /dev/null +++ b/src/test/kotlin/app/termora/HostTest.kt @@ -0,0 +1,22 @@ +package app.termora + +import app.termora.Application.ohMyJson +import kotlinx.serialization.encodeToString +import kotlin.test.Test + +class HostTest { + @Test + fun test() { + val host = ohMyJson.decodeFromString( + """ + { + "name": "test", + "protocol": "SSH", + "test": "" + } + """.trimIndent() + ) + + println(ohMyJson.encodeToString(host)) + } +} \ No newline at end of file diff --git a/src/test/kotlin/app/termora/I18nTest.kt b/src/test/kotlin/app/termora/I18nTest.kt new file mode 100644 index 0000000..df66020 --- /dev/null +++ b/src/test/kotlin/app/termora/I18nTest.kt @@ -0,0 +1,26 @@ +package app.termora + +import org.apache.commons.lang3.LocaleUtils +import java.io.BufferedOutputStream +import java.nio.file.Files +import java.nio.file.Paths +import java.util.* +import kotlin.test.Test +import kotlin.test.assertEquals + +class I18nTest { + + + @Test + fun test_zh_CN() { + val bundle = ResourceBundle.getBundle("i18n/messages", LocaleUtils.toLocale("zh_CN")) + assertEquals(bundle.getString("termora.confirm"), "确认") + } + + + @Test + fun test_zh_TW() { + val bundle = ResourceBundle.getBundle("i18n/messages", LocaleUtils.toLocale("zh_TW")) + assertEquals(bundle.getString("termora.confirm"), "確定") + } +} \ No newline at end of file diff --git a/src/test/kotlin/app/termora/KeyUtilsTest.kt b/src/test/kotlin/app/termora/KeyUtilsTest.kt new file mode 100644 index 0000000..f23b269 --- /dev/null +++ b/src/test/kotlin/app/termora/KeyUtilsTest.kt @@ -0,0 +1,13 @@ +package app.termora + +import org.apache.sshd.common.config.keys.KeyUtils +import kotlin.test.Test +import kotlin.test.assertEquals + +class KeyUtilsTest { + @Test + fun test() { + assertEquals(KeyUtils.getKeySize(KeyUtils.generateKeyPair("ssh-rsa", 1024).private), 1024) + assertEquals(KeyUtils.getKeySize(KeyUtils.generateKeyPair("ssh-rsa", 1024).public), 1024) + } +} \ No newline at end of file diff --git a/src/test/kotlin/app/termora/LocaleTest.kt b/src/test/kotlin/app/termora/LocaleTest.kt new file mode 100644 index 0000000..e1193be --- /dev/null +++ b/src/test/kotlin/app/termora/LocaleTest.kt @@ -0,0 +1,15 @@ +package app.termora + +import org.apache.commons.lang3.LocaleUtils +import java.util.* +import kotlin.test.Test +import kotlin.test.assertEquals + +class LocaleTest { + @Test + fun test() { + assertEquals(Locale.of("zh_CN").toString(), "zh_cn") + assertEquals(Locale.of("zh", "CN").toString(), "zh_CN") + assertEquals(LocaleUtils.toLocale("zh_CN").toString(), "zh_CN") + } +} \ No newline at end of file diff --git a/src/test/kotlin/app/termora/PBKDF2Test.kt b/src/test/kotlin/app/termora/PBKDF2Test.kt new file mode 100644 index 0000000..6041485 --- /dev/null +++ b/src/test/kotlin/app/termora/PBKDF2Test.kt @@ -0,0 +1,17 @@ +package app.termora + +import org.apache.commons.codec.binary.Hex +import kotlin.test.Test +import kotlin.test.assertEquals + +class PBKDF2Test { + @Test + fun test() { + val password = "password" + assertEquals( + "72629a41b076e588fba8c71ca37fadc9acdc8e7321b9cb4ea55fd0bf9fe8ed72", Hex.encodeHexString( + PBKDF2.generateSecret(password.toCharArray(), "salt".toByteArray(), 10000, 256) + ) + ) + } +} \ No newline at end of file diff --git a/src/test/kotlin/app/termora/RSA2048Test.kt b/src/test/kotlin/app/termora/RSA2048Test.kt new file mode 100644 index 0000000..2bdf873 --- /dev/null +++ b/src/test/kotlin/app/termora/RSA2048Test.kt @@ -0,0 +1,23 @@ +package app.termora + +import org.apache.commons.codec.binary.Base64 +import kotlin.test.Test +import kotlin.test.assertContentEquals + + +class RSA2048Test { + @Test + fun test() { + val data = "hello world. 中国 😄".toByteArray() + + val pair = RSA.generateKeyPair() + + println("publicKey: ${Base64.encodeBase64String(pair.public.encoded)}") + println("privateKey: ${Base64.encodeBase64String(pair.private.encoded)}") + + assertContentEquals(RSA.decrypt(pair.private, RSA.encrypt(pair.public, data)), data) + assertContentEquals(RSA.decrypt(pair.public, RSA.encrypt(pair.private, data)), data) + } + + +} \ No newline at end of file diff --git a/src/test/kotlin/app/termora/terminal/FindModelImplTest.kt b/src/test/kotlin/app/termora/terminal/FindModelImplTest.kt new file mode 100644 index 0000000..348b569 --- /dev/null +++ b/src/test/kotlin/app/termora/terminal/FindModelImplTest.kt @@ -0,0 +1,32 @@ +package app.termora.terminal + +import kotlin.test.Test +import kotlin.test.assertEquals + +class FindModelImplTest { + @Test + fun test() { + val terminal = VisualTerminal() + terminal.write("hello world") + assertEquals(terminal.getFindModel().find("o").size, 2) + } + + + @Test + fun testMultiline() { + val terminal = VisualTerminal() + terminal.write("hello world hello world hello world hello world hello world hello world world 123456789") + val kind = terminal.getFindModel().find("123456789").first() + assertEquals(kind.startPosition, Position(1, 79)) + assertEquals(kind.endPosition, Position(2, 7)) + } + + @Test + fun testChinese() { + val terminal = VisualTerminal() + terminal.write("aaaaa.txt") + val kind = terminal.getFindModel().find("aa.txt").first() + assertEquals(kind.startPosition, Position(1, 4)) + assertEquals(kind.endPosition, Position(1, 9)) + } +} \ No newline at end of file diff --git a/src/test/kotlin/app/termora/terminal/TextStyleTest.kt b/src/test/kotlin/app/termora/terminal/TextStyleTest.kt new file mode 100644 index 0000000..4a2ad79 --- /dev/null +++ b/src/test/kotlin/app/termora/terminal/TextStyleTest.kt @@ -0,0 +1,27 @@ +package app.termora.terminal + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class TextStyleTest { + @Test + fun test() { + var textStyle = TextStyle() + assertFalse(textStyle.bold) + assertTrue(textStyle.bold(true).bold) + + + textStyle = textStyle.foreground(255 * 255 * 2) + textStyle = textStyle.background(255 * 2) + + assertEquals(textStyle.foreground, 255 * 255 * 2) + assertEquals(textStyle.background, 255 * 2) + assertFalse(textStyle.italic) + + textStyle = textStyle.italic(true) + assertTrue(textStyle.italic) + + } +} \ No newline at end of file diff --git a/src/test/resources/sshd/Dockerfile b/src/test/resources/sshd/Dockerfile new file mode 100644 index 0000000..3c1b0a3 --- /dev/null +++ b/src/test/resources/sshd/Dockerfile @@ -0,0 +1,6 @@ +FROM linuxserver/openssh-server +RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories \ + && apk update && apk add wget gcc g++ git make zsh htop && wget https://ohse.de/uwe/releases/lrzsz-0.12.20.tar.gz \ + && tar -xf lrzsz-0.12.20.tar.gz && cd lrzsz-0.12.20 && ./configure && make && make install \ + && ln -s /usr/local/bin/lrz /usr/local/bin/rz && ln -s /usr/local/bin/lsz /usr/local/bin/sz +