commit fc9e7b7598ae5f29b4aa5c860a5336cc5d9187b3 Author: rdavidek Date: Sun Jan 25 19:06:08 2026 +0100 first commit diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..79608c8 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,16 @@ +# Workspace Instructions +- Project: Java 11 SSH Terminal +- Language: Java 11 +- UI Framework: Swing +- SSH Library: JSch (mwiede fork) +- Security: AES Encryption with Master Password + +## Steps Completed +- [x] Initialize project structure and pom.xml +- [x] Implement Encryption Service +- [x] Implement Connection Model and Storage +- [x] Implement SSH Terminal UI component +- [x] Implement Main UI with SplitPane and Tabs +- [x] Add Connection Management (Add/Remove) +- [x] Compile and Test +- [x] README and Documentation diff --git a/.master_key b/.master_key new file mode 100644 index 0000000..828713f --- /dev/null +++ b/.master_key @@ -0,0 +1 @@ +ByBYvcAXjSA51UXD76DrxA== \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..e0f15db --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "java.configuration.updateBuildConfiguration": "automatic" +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..a7e66a9 --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# Java SSH Terminal + +A basic SSH terminal application built with Java 11 and Swing. + +## Features +- Connection management in the left side panel. +- Multiple SSH terminals in a tabbed view. +- Master password protection for stored credentials (AES encrypted). +- Toolbar for quick actions (Add connection, Disconnect). + +## Prerequisites +- Java 11 +- Maven + +## How to Run +1. Build the project: + ```bash + mvn clean package + ``` +2. Run the application: + ```bash + java -jar target/java-ssh-terminal-1.0-SNAPSHOT.jar + ``` + +## Note on Terminal Emulation +This is a base application. The terminal component uses a basic `JTextArea` which does not support full ANSI escape sequences (colors, cursor positioning). For production use, consider integrating a library like `JediTerm`. diff --git a/connections.json b/connections.json new file mode 100644 index 0000000..db1b25c --- /dev/null +++ b/connections.json @@ -0,0 +1 @@ +[{"name":"server01","host":"server01","port":64022,"username":"kamma","encryptedPassword":"cT7BoqH9BONVY740RjdkFQ\u003d\u003d"},{"name":"kamma.cz","host":"kamma.cz","port":22,"username":"root","encryptedPassword":"DmErsKpA1Jf4az0cc2lrgA\u003d\u003d"}] \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..4c95f76 --- /dev/null +++ b/pom.xml @@ -0,0 +1,78 @@ + + + 4.0.0 + + cz.kamma.term + java-ssh-terminal + 1.0-SNAPSHOT + + + 11 + 11 + UTF-8 + + + + + + com.github.mwiede + jsch + 0.2.17 + + + + + com.google.code.gson + gson + 2.10.1 + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + + org.apache.maven.plugins + maven-jar-plugin + 3.2.0 + + + + cz.kamma.term.App + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.4 + + + package + + shade + + + false + + + cz.kamma.term.App + + + + + + + + + diff --git a/settings.json b/settings.json new file mode 100644 index 0000000..0d49e58 --- /dev/null +++ b/settings.json @@ -0,0 +1 @@ +{"fontName":"Monospaced","fontSize":12,"backgroundColor":-16777216,"foregroundColor":-16711936} \ No newline at end of file diff --git a/src/main/java/cz/kamma/term/App.java b/src/main/java/cz/kamma/term/App.java new file mode 100644 index 0000000..d098fee --- /dev/null +++ b/src/main/java/cz/kamma/term/App.java @@ -0,0 +1,18 @@ +package cz.kamma.term; + +import cz.kamma.term.ui.MainFrame; + +import javax.swing.*; + +public class App { + public static void main(String[] args) { + SwingUtilities.invokeLater(() -> { + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (Exception e) { + e.printStackTrace(); + } + new MainFrame().setVisible(true); + }); + } +} diff --git a/src/main/java/cz/kamma/term/model/AppSettings.java b/src/main/java/cz/kamma/term/model/AppSettings.java new file mode 100644 index 0000000..c03fab5 --- /dev/null +++ b/src/main/java/cz/kamma/term/model/AppSettings.java @@ -0,0 +1,20 @@ +package cz.kamma.term.model; + +import java.awt.Color; + +public class AppSettings { + private String fontName = "Monospaced"; + private int fontSize = 14; + private int backgroundColor = Color.BLACK.getRGB(); + private int foregroundColor = Color.GREEN.getRGB(); + + // Getters and Setters + public String getFontName() { return fontName; } + public void setFontName(String fontName) { this.fontName = fontName; } + public int getFontSize() { return fontSize; } + public void setFontSize(int fontSize) { this.fontSize = fontSize; } + public int getBackgroundColor() { return backgroundColor; } + public void setBackgroundColor(int backgroundColor) { this.backgroundColor = backgroundColor; } + public int getForegroundColor() { return foregroundColor; } + public void setForegroundColor(int foregroundColor) { this.foregroundColor = foregroundColor; } +} diff --git a/src/main/java/cz/kamma/term/model/ConnectionInfo.java b/src/main/java/cz/kamma/term/model/ConnectionInfo.java new file mode 100644 index 0000000..1852396 --- /dev/null +++ b/src/main/java/cz/kamma/term/model/ConnectionInfo.java @@ -0,0 +1,34 @@ +package cz.kamma.term.model; + +public class ConnectionInfo { + private String name; + private String host; + private int port = 22; + private String username; + private String encryptedPassword; + + public ConnectionInfo(String name, String host, int port, String username, String encryptedPassword) { + this.name = name; + this.host = host; + this.port = port; + this.username = username; + this.encryptedPassword = encryptedPassword; + } + + // Getters and Setters + public String getName() { return name; } + public void setName(String name) { this.name = name; } + public String getHost() { return host; } + public void setHost(String host) { this.host = host; } + public int getPort() { return port; } + public void setPort(int port) { this.port = port; } + public String getUsername() { return username; } + public void setUsername(String username) { this.username = username; } + public String getEncryptedPassword() { return encryptedPassword; } + public void setEncryptedPassword(String encryptedPassword) { this.encryptedPassword = encryptedPassword; } + + @Override + public String toString() { + return name + " (" + host + ")"; + } +} diff --git a/src/main/java/cz/kamma/term/service/ConnectionManager.java b/src/main/java/cz/kamma/term/service/ConnectionManager.java new file mode 100644 index 0000000..d6c171f --- /dev/null +++ b/src/main/java/cz/kamma/term/service/ConnectionManager.java @@ -0,0 +1,55 @@ +package cz.kamma.term.service; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import cz.kamma.term.model.ConnectionInfo; + +import java.io.*; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; + +public class ConnectionManager { + private static final String FILE_NAME = "connections.json"; + private final Gson gson = new Gson(); + private List connections = new ArrayList<>(); + + public ConnectionManager() { + load(); + } + + public List getConnections() { + return connections; + } + + public void addConnection(ConnectionInfo connection) { + connections.add(connection); + save(); + } + + public void removeConnection(ConnectionInfo connection) { + connections.remove(connection); + save(); + } + + public void save() { + try (Writer writer = new FileWriter(FILE_NAME)) { + gson.toJson(connections, writer); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void load() { + File file = new File(FILE_NAME); + if (file.exists()) { + try (Reader reader = new FileReader(file)) { + Type listType = new TypeToken>(){}.getType(); + connections = gson.fromJson(reader, listType); + if (connections == null) connections = new ArrayList<>(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } +} diff --git a/src/main/java/cz/kamma/term/service/EncryptionService.java b/src/main/java/cz/kamma/term/service/EncryptionService.java new file mode 100644 index 0000000..a7b1e3d --- /dev/null +++ b/src/main/java/cz/kamma/term/service/EncryptionService.java @@ -0,0 +1,37 @@ +package cz.kamma.term.service; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; +import java.security.spec.KeySpec; +import java.util.Base64; + +public class EncryptionService { + private static final String ALGORITHM = "AES"; + private static final String SALT = "some_random_salt_123"; // Should be stored properly, but for base app this is okay + + public static String encrypt(String password, String masterPassword) throws Exception { + SecretKey key = deriveKey(masterPassword); + Cipher cipher = Cipher.getInstance(ALGORITHM); + cipher.init(Cipher.ENCRYPT_MODE, key); + byte[] encryptedBytes = cipher.doFinal(password.getBytes()); + return Base64.getEncoder().encodeToString(encryptedBytes); + } + + public static String decrypt(String encryptedPassword, String masterPassword) throws Exception { + SecretKey key = deriveKey(masterPassword); + Cipher cipher = Cipher.getInstance(ALGORITHM); + cipher.init(Cipher.DECRYPT_MODE, key); + byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedPassword)); + return new String(decryptedBytes); + } + + private static SecretKey deriveKey(String masterPassword) throws Exception { + SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); + KeySpec spec = new PBEKeySpec(masterPassword.toCharArray(), SALT.getBytes(), 65536, 128); + SecretKey tmp = factory.generateSecret(spec); + return new SecretKeySpec(tmp.getEncoded(), ALGORITHM); + } +} diff --git a/src/main/java/cz/kamma/term/service/HistoryManager.java b/src/main/java/cz/kamma/term/service/HistoryManager.java new file mode 100644 index 0000000..6375645 --- /dev/null +++ b/src/main/java/cz/kamma/term/service/HistoryManager.java @@ -0,0 +1,36 @@ +package cz.kamma.term.service; + +import java.io.*; +import java.util.ArrayList; +import java.util.List; + +public class HistoryManager { + private List history = new ArrayList<>(); + + public HistoryManager() { + // Don't load local history - will be loaded from server + } + + public void addRemoteCommand(String command) { + if (command == null || command.trim().isEmpty()) return; + + // Remove duplicate if it was the last command + if (!history.isEmpty() && history.get(history.size() - 1).equals(command)) { + return; + } + + history.add(command); + // Don't save locally - server handles it via .history + } + + public void loadRemoteHistory(List remoteCommands) { + if (remoteCommands != null) { + history.clear(); + history.addAll(remoteCommands); + } + } + + public List getHistory() { + return history; + } +} diff --git a/src/main/java/cz/kamma/term/service/SettingsManager.java b/src/main/java/cz/kamma/term/service/SettingsManager.java new file mode 100644 index 0000000..57d4ee2 --- /dev/null +++ b/src/main/java/cz/kamma/term/service/SettingsManager.java @@ -0,0 +1,42 @@ +package cz.kamma.term.service; + +import com.google.gson.Gson; +import cz.kamma.term.model.AppSettings; + +import java.io.*; + +public class SettingsManager { + private static final String FILE_NAME = "settings.json"; + private final Gson gson = new Gson(); + private AppSettings settings; + + public SettingsManager() { + load(); + } + + public AppSettings getSettings() { + return settings; + } + + public void save() { + try (Writer writer = new FileWriter(FILE_NAME)) { + gson.toJson(settings, writer); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void load() { + File file = new File(FILE_NAME); + if (file.exists()) { + try (Reader reader = new FileReader(file)) { + settings = gson.fromJson(reader, AppSettings.class); + } catch (IOException e) { + e.printStackTrace(); + } + } + if (settings == null) { + settings = new AppSettings(); + } + } +} diff --git a/src/main/java/cz/kamma/term/ui/MainFrame.java b/src/main/java/cz/kamma/term/ui/MainFrame.java new file mode 100644 index 0000000..56009cd --- /dev/null +++ b/src/main/java/cz/kamma/term/ui/MainFrame.java @@ -0,0 +1,313 @@ +package cz.kamma.term.ui; + +import cz.kamma.term.model.ConnectionInfo; +import cz.kamma.term.model.AppSettings; +import cz.kamma.term.service.ConnectionManager; +import cz.kamma.term.service.EncryptionService; +import cz.kamma.term.service.SettingsManager; +import cz.kamma.term.util.SystemIdUtil; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.io.File; +import java.nio.file.Files; + +public class MainFrame extends JFrame { + private static final String MASTER_PASS_FILE = ".master_key"; + private ConnectionManager connectionManager; + private SettingsManager settingsManager; + private JTabbedPane tabbedPane; + private DefaultListModel listModel; + private JList connectionList; + private String masterPassword; + + public MainFrame() { + setTitle("Java SSH Terminal"); + setSize(1000, 700); + setDefaultCloseOperation(EXIT_ON_CLOSE); + setLocationRelativeTo(null); + + connectionManager = new ConnectionManager(); + settingsManager = new SettingsManager(); + + if (!tryLoadMasterPassword()) { + showMasterPasswordDialog(); + } + + initUI(); + } + + private boolean tryLoadMasterPassword() { + File file = new File(MASTER_PASS_FILE); + if (file.exists()) { + try { + String encryptedMaster = Files.readString(file.toPath()).trim(); + masterPassword = EncryptionService.decrypt(encryptedMaster, SystemIdUtil.getMachineId()); + return true; + } catch (Exception e) { + file.delete(); // Corrupted or wrong machine + } + } + return false; + } + + private void showMasterPasswordDialog() { + JPasswordField passF = new JPasswordField(); + JCheckBox rememberCb = new JCheckBox("Remember on this computer"); + Object[] message = { + "Enter Master Password:", passF, + rememberCb + }; + + int option = JOptionPane.showConfirmDialog(this, message, "Security", JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE); + + if (option == JOptionPane.OK_OPTION) { + masterPassword = new String(passF.getPassword()); + if (masterPassword.isEmpty()) { + System.exit(0); + } + if (rememberCb.isSelected()) { + saveMasterPassword(); + } + } else { + System.exit(0); + } + } + + private void saveMasterPassword() { + try { + String encryptedMaster = EncryptionService.encrypt(masterPassword, SystemIdUtil.getMachineId()); + Files.writeString(new File(MASTER_PASS_FILE).toPath(), encryptedMaster); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void initUI() { + // Toolbar + JToolBar toolBar = new JToolBar(); + JButton addButton = new JButton("Add Connection"); + JButton settingsButton = new JButton("Settings"); + toolBar.add(addButton); + toolBar.add(settingsButton); + add(toolBar, BorderLayout.NORTH); + + // Left Panel (Connections) + listModel = new DefaultListModel<>(); + connectionManager.getConnections().forEach(listModel::addElement); + connectionList = new JList<>(listModel); + connectionList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + + JScrollPane leftScroll = new JScrollPane(connectionList); + leftScroll.setPreferredSize(new Dimension(200, 0)); + + // Main Panel (Tabs) + tabbedPane = new JTabbedPane(); + + JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftScroll, tabbedPane); + add(splitPane, BorderLayout.CENTER); + + // Listeners + addButton.addActionListener(e -> showAddConnectionDialog(null)); + settingsButton.addActionListener(e -> showSettingsDialog()); + + connectionList.addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + checkPopup(e); + } + + @Override + public void mouseReleased(MouseEvent e) { + checkPopup(e); + } + + private void checkPopup(MouseEvent e) { + if (e.isPopupTrigger()) { + int index = connectionList.locationToIndex(e.getPoint()); + if (index != -1) { + connectionList.setSelectedIndex(index); + showContextMenu(e.getComponent(), e.getX(), e.getY(), connectionList.getSelectedValue()); + } + } + } + + @Override + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { + ConnectionInfo selected = connectionList.getSelectedValue(); + if (selected != null) { + openTerminal(selected); + } + } + } + }); + } + + private void openTerminal(ConnectionInfo info) { + TerminalPanel terminal = new TerminalPanel(info, masterPassword, settingsManager.getSettings()); + tabbedPane.addTab(info.getName(), terminal); + int index = tabbedPane.indexOfComponent(terminal); + + // Custom Tab Component with (X) button + JPanel pnlTab = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 0)); + pnlTab.setOpaque(false); + JLabel lblTitle = new JLabel(info.getName()); + JButton btnClose = new JButton("x"); + + // Style the close button + btnClose.setMargin(new Insets(0, 2, 0, 2)); + btnClose.setBorderPainted(false); + btnClose.setContentAreaFilled(false); + btnClose.setFocusable(false); + btnClose.setFont(new Font("Monospaced", Font.BOLD, 12)); + + btnClose.addActionListener(e -> { + terminal.disconnect(); + tabbedPane.remove(terminal); + }); + + pnlTab.add(lblTitle); + pnlTab.add(btnClose); + + tabbedPane.setTabComponentAt(index, pnlTab); + tabbedPane.setSelectedIndex(index); + } + + private void showContextMenu(Component invoker, int x, int y, ConnectionInfo selected) { + JPopupMenu menu = new JPopupMenu(); + + JMenuItem connectItem = new JMenuItem("Connect in new tab"); + JMenuItem editItem = new JMenuItem("Edit"); + JMenuItem deleteItem = new JMenuItem("Delete"); + + connectItem.addActionListener(e -> openTerminal(selected)); + editItem.addActionListener(e -> showAddConnectionDialog(selected)); + deleteItem.addActionListener(e -> { + int confirm = JOptionPane.showConfirmDialog(this, "Delete " + selected.getName() + "?", "Confirm", JOptionPane.YES_NO_OPTION); + if (confirm == JOptionPane.YES_OPTION) { + connectionManager.removeConnection(selected); + listModel.removeElement(selected); + } + }); + + menu.add(connectItem); + menu.addSeparator(); + menu.add(editItem); + menu.add(deleteItem); + menu.show(invoker, x, y); + } + + private void showAddConnectionDialog(ConnectionInfo existing) { + JTextField nameF = new JTextField(existing != null ? existing.getName() : ""); + JTextField hostF = new JTextField(existing != null ? existing.getHost() : ""); + JTextField portF = new JTextField(existing != null ? String.valueOf(existing.getPort()) : "22"); + JTextField userF = new JTextField(existing != null ? existing.getUsername() : ""); + JPasswordField passF = new JPasswordField(); + + Object[] message = { + "Name:", nameF, + "Host:", hostF, + "Port:", portF, + "User:", userF, + "Pass (leave empty to keep existing):", passF + }; + + int option = JOptionPane.showConfirmDialog(null, message, + existing == null ? "Add New Connection" : "Edit Connection", JOptionPane.OK_CANCEL_OPTION); + + if (option == JOptionPane.OK_OPTION) { + try { + String encryptedPass; + if (passF.getPassword().length > 0) { + encryptedPass = EncryptionService.encrypt(new String(passF.getPassword()), masterPassword); + } else if (existing != null) { + encryptedPass = existing.getEncryptedPassword(); + } else { + throw new Exception("Password is required for new connection."); + } + + if (existing != null) { + existing.setName(nameF.getText()); + existing.setHost(hostF.getText()); + existing.setPort(Integer.parseInt(portF.getText())); + existing.setUsername(userF.getText()); + existing.setEncryptedPassword(encryptedPass); + connectionManager.save(); + connectionList.repaint(); + } else { + ConnectionInfo info = new ConnectionInfo( + nameF.getText(), + hostF.getText(), + Integer.parseInt(portF.getText()), + userF.getText(), + encryptedPass + ); + connectionManager.addConnection(info); + listModel.addElement(info); + } + } catch (Exception ex) { + JOptionPane.showMessageDialog(this, "Error: " + ex.getMessage()); + } + } + } + + private void showSettingsDialog() { + AppSettings settings = settingsManager.getSettings(); + + String[] fonts = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames(); + JComboBox fontCmbo = new JComboBox<>(fonts); + fontCmbo.setSelectedItem(settings.getFontName()); + + JSpinner sizeSpin = new JSpinner(new SpinnerNumberModel(settings.getFontSize(), 8, 72, 1)); + + JButton bgBtn = new JButton("Choose Background"); + bgBtn.setBackground(new Color(settings.getBackgroundColor())); + final int[] bgArr = {settings.getBackgroundColor()}; + bgBtn.addActionListener(e -> { + Color c = JColorChooser.showDialog(this, "Select Background", new Color(bgArr[0])); + if (c != null) { + bgArr[0] = c.getRGB(); + bgBtn.setBackground(c); + } + }); + + JButton fgBtn = new JButton("Choose Foreground"); + fgBtn.setBackground(new Color(settings.getForegroundColor())); + final int[] fgArr = {settings.getForegroundColor()}; + fgBtn.addActionListener(e -> { + Color c = JColorChooser.showDialog(this, "Select Foreground", new Color(fgArr[0])); + if (c != null) { + fgArr[0] = c.getRGB(); + fgBtn.setBackground(c); + } + }); + + Object[] message = { + "Font:", fontCmbo, + "Size:", sizeSpin, + "Background:", bgBtn, + "Foreground:", fgBtn + }; + + int option = JOptionPane.showConfirmDialog(this, message, "Settings", JOptionPane.OK_CANCEL_OPTION); + if (option == JOptionPane.OK_OPTION) { + settings.setFontName((String) fontCmbo.getSelectedItem()); + settings.setFontSize((Integer) sizeSpin.getValue()); + settings.setBackgroundColor(bgArr[0]); + settings.setForegroundColor(fgArr[0]); + + settingsManager.save(); + + // Apply to all tabs + for (int i = 0; i < tabbedPane.getTabCount(); i++) { + Component comp = tabbedPane.getComponentAt(i); + if (comp instanceof TerminalPanel) { + ((TerminalPanel) comp).updateSettings(settings); + } + } + } + } +} diff --git a/src/main/java/cz/kamma/term/ui/TerminalPanel.java b/src/main/java/cz/kamma/term/ui/TerminalPanel.java new file mode 100644 index 0000000..a5554cd --- /dev/null +++ b/src/main/java/cz/kamma/term/ui/TerminalPanel.java @@ -0,0 +1,347 @@ +package cz.kamma.term.ui; + +import com.jcraft.jsch.*; +import cz.kamma.term.model.ConnectionInfo; +import cz.kamma.term.model.AppSettings; +import cz.kamma.term.service.EncryptionService; +import cz.kamma.term.service.HistoryManager; +import cz.kamma.term.util.AnsiHelper; + +import javax.swing.*; +import javax.swing.text.DefaultCaret; +import javax.swing.text.NavigationFilter; +import javax.swing.text.Position; +import javax.swing.text.StyledDocument; +import java.awt.*; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.io.*; +import java.util.ArrayList; +import java.util.List; + +public class TerminalPanel extends JPanel { + private JTextPane textPane; + private Session session; + private ChannelShell channel; + private PrintStream out; + private String masterPassword; + private ConnectionInfo connectionInfo; + private AppSettings settings; + private HistoryManager historyManager; + private StringBuilder commandLineBuffer = new StringBuilder(); + private int historyIndex = -1; + private int commandStartPosition = 0; + + public TerminalPanel(ConnectionInfo info, String masterPassword, AppSettings settings) { + this.connectionInfo = info; + this.masterPassword = masterPassword; + this.settings = settings; + this.historyManager = new HistoryManager(); // Initialize without loading any history + setLayout(new BorderLayout()); + + textPane = new JTextPane(); + textPane.setFocusTraversalKeysEnabled(false); + applySettings(); + textPane.setCaretColor(Color.WHITE); + + // Auto-scroll to bottom + DefaultCaret caret = (DefaultCaret) textPane.getCaret(); + caret.setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE); + + // Prevent moving caret into protected prompt area + textPane.setNavigationFilter(new NavigationFilter() { + @Override + public void setDot(FilterBypass fb, int dot, Position.Bias bias) { + if (dot < commandStartPosition) dot = commandStartPosition; + fb.setDot(dot, bias); + } + + @Override + public void moveDot(FilterBypass fb, int dot, Position.Bias bias) { + if (dot < commandStartPosition) dot = commandStartPosition; + fb.moveDot(dot, bias); + } + }); + + JScrollPane scrollPane = new JScrollPane(textPane); + add(scrollPane, BorderLayout.CENTER); + + textPane.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (out == null) return; + + // UP arrow - previous command in history + if (e.getKeyCode() == KeyEvent.VK_UP) { + if (historyManager.getHistory().isEmpty()) { + e.consume(); + return; + } + if (historyIndex < historyManager.getHistory().size() - 1) { + historyIndex++; + String command = historyManager.getHistory().get(historyManager.getHistory().size() - 1 - historyIndex); + displayCommand(command); + } + e.consume(); + return; + } + + // DOWN arrow - next command in history + if (e.getKeyCode() == KeyEvent.VK_DOWN) { + if (historyIndex > 0) { + historyIndex--; + String command = historyManager.getHistory().get(historyManager.getHistory().size() - 1 - historyIndex); + displayCommand(command); + } else if (historyIndex == 0) { + historyIndex = -1; + clearCurrentCommand(); + } + e.consume(); + return; + } + + String seq = null; + switch (e.getKeyCode()) { + case KeyEvent.VK_TAB: + out.print("\t"); + out.flush(); + e.consume(); + return; + case KeyEvent.VK_RIGHT: seq = "\u001B[C"; break; + case KeyEvent.VK_LEFT: seq = "\u001B[D"; break; + case KeyEvent.VK_HOME: seq = "\u001B[1~"; break; + case KeyEvent.VK_END: seq = "\u001B[4~"; break; + case KeyEvent.VK_DELETE: + // Protect command prompt - only allow delete if there's command content + if (canDeleteCharacter()) { + seq = "\u001B[3~"; + } + e.consume(); + return; + } + + if (seq != null) { + out.print(seq); + out.flush(); + e.consume(); + } + } + + @Override + public void keyTyped(KeyEvent e) { + if (out != null) { + char c = e.getKeyChar(); + + // Handle backspace - protect command prompt + if (c == '\b') { + if (canDeleteCharacter()) { + out.print(c); + out.flush(); + if (commandLineBuffer.length() > 0) { + commandLineBuffer.setLength(commandLineBuffer.length() - 1); + } + } + // Don't send backspace if it would delete the prompt + e.consume(); + return; + } + + // Send all other characters to server + out.print(c); + out.flush(); + + if (c == '\r' || c == '\n') { + // Command sent to server, server will save it to .history + // Add to local memory only for history browsing + String cmd = commandLineBuffer.toString(); + if (!cmd.trim().isEmpty()) { + historyManager.addRemoteCommand(cmd); + } + commandLineBuffer.setLength(0); + historyIndex = -1; + } else if (!Character.isISOControl(c)) { + commandLineBuffer.append(c); + } + } + e.consume(); + } + }); + + connect(); + } + + private void connect() { + new Thread(() -> { + try { + JSch jsch = new JSch(); + String password = EncryptionService.decrypt(connectionInfo.getEncryptedPassword(), masterPassword); + + session = jsch.getSession(connectionInfo.getUsername(), connectionInfo.getHost(), connectionInfo.getPort()); + session.setPassword(password); + session.setConfig("StrictHostKeyChecking", "no"); + session.connect(); + + channel = (ChannelShell) session.openChannel("shell"); + channel.setPtyType("xterm"); + + // Set up input from our pipe + PipedInputStream pin = new PipedInputStream(65536); + PipedOutputStream pout = new PipedOutputStream(pin); + out = new PrintStream(pout, true); + channel.setInputStream(pin); + + // Connect first, then read output from channel + channel.connect(); + + // Get the output stream from the channel and read it + InputStream channelInput = channel.getInputStream(); + + // Read from channel in a separate thread to ensure we get all output + new Thread(() -> { + try { + byte[] buffer = new byte[1024]; + int len; + while ((len = channelInput.read(buffer)) > 0) { + String output = new String(buffer, 0, len); + SwingUtilities.invokeLater(() -> { + AnsiHelper.appendAnsi(textPane, output, commandStartPosition); + // Update prompt boundary only when not typing/navigating history + if (commandLineBuffer.length() == 0) { + commandStartPosition = textPane.getDocument().getLength(); + } + }); + } + } catch (Exception e) { + // Connection closed + } + }).start(); + + // Load command history from remote server in a separate thread + // Delay to ensure shell initializes first + new Thread(() -> { + try { + Thread.sleep(500); // Shorter delay - 500ms should be enough + loadRemoteHistory(); + } catch (InterruptedException ignored) {} + }).start(); + + // Focus on textPane after successful connection + SwingUtilities.invokeLater(() -> { + textPane.requestFocusInWindow(); + textPane.setCaretPosition(textPane.getDocument().getLength()); + }); + + } catch (Exception e) { + SwingUtilities.invokeLater(() -> { + AnsiHelper.appendAnsi(textPane, "\nConnection failed: " + e.getMessage(), 0); + }); + } + }).start(); + } + + private void loadRemoteHistory() { + try { + ChannelSftp sftpChannel = (ChannelSftp) session.openChannel("sftp"); + sftpChannel.connect(); + + try { + // Try to get home directory first + String homeDir = sftpChannel.pwd(); + + // Try .bash_history first (bash shell), then .history (zsh/generic) + InputStream is = null; + + try { + is = sftpChannel.get(homeDir + "/.bash_history"); + } catch (com.jcraft.jsch.SftpException e1) { + try { + is = sftpChannel.get(homeDir + "/.history"); + } catch (com.jcraft.jsch.SftpException e2) { + throw e1; // throw original exception + } + } + + String historyContent = new String(is.readAllBytes()); + is.close(); + + List commands = new ArrayList<>(); + for (String line : historyContent.split("\n")) { + String trimmed = line.trim(); + if (!trimmed.isEmpty()) { + commands.add(trimmed); + } + } + historyManager.loadRemoteHistory(commands); + } catch (com.jcraft.jsch.SftpException sftpEx) { + if (sftpEx.id == 2) { + // File not found - that's okay, history is empty + historyManager.loadRemoteHistory(new ArrayList<>()); + } + } catch (Exception e) { + // Ignore errors loading history + } + + sftpChannel.disconnect(); + } catch (Exception e) { + // SFTP not available, continue without history + } + } + + public void disconnect() { + if (channel != null && channel.isConnected()) channel.disconnect(); + if (session != null && session.isConnected()) session.disconnect(); + } + + public void updateSettings(AppSettings newSettings) { + this.settings = newSettings; + applySettings(); + } + + private void applySettings() { + textPane.setBackground(new Color(settings.getBackgroundColor())); + textPane.setForeground(new Color(settings.getForegroundColor())); + textPane.setFont(new Font(settings.getFontName(), Font.PLAIN, settings.getFontSize())); + } + + private void displayCommand(String command) { + try { + StyledDocument doc = (StyledDocument) textPane.getDocument(); + int docLength = doc.getLength(); + + // Remove everything after the prompt boundary + if (docLength > commandStartPosition) { + doc.remove(commandStartPosition, docLength - commandStartPosition); + } + + // Put the history command into the buffer so it can be edited + commandLineBuffer.setLength(0); + commandLineBuffer.append(command); + + // Add command from history to display + AnsiHelper.appendAnsi(textPane, command, commandStartPosition); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void clearCurrentCommand() { + try { + StyledDocument doc = (StyledDocument) textPane.getDocument(); + int docLength = doc.getLength(); + + // Remove everything after the prompt boundary + if (docLength > commandStartPosition) { + doc.remove(commandStartPosition, docLength - commandStartPosition); + } + commandLineBuffer.setLength(0); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private boolean canDeleteCharacter() { + // Allow deletion only if the caret is beyond the protected prompt boundary + // Standard backspace deletes character at caret - 1. + return textPane.getCaretPosition() > commandStartPosition; + } +} diff --git a/src/main/java/cz/kamma/term/util/AnsiHelper.java b/src/main/java/cz/kamma/term/util/AnsiHelper.java new file mode 100644 index 0000000..466c21a --- /dev/null +++ b/src/main/java/cz/kamma/term/util/AnsiHelper.java @@ -0,0 +1,311 @@ +package cz.kamma.term.util; + +import javax.swing.*; +import javax.swing.text.*; +import java.awt.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class AnsiHelper { + // Comprehensive ANSI escape sequence pattern + // Matches: ESC [ params command, ESC ] OSC sequence, and other escape sequences + private static final Pattern ANSI_PATTERN = Pattern.compile( + "\u001B" + // ESC character + "(?:" + + "\\[([?0-9;]*)([@-~])|" + // CSI sequences: ESC [ params char + "\\].*?(?:\u0007|\u001B\\\\)|" + // OSC sequences + "[()][B0UK]" + // Charset selection + ")" + ); + + public static void appendAnsi(JTextPane textPane, String text, int protectedLength) { + if (text == null || text.isEmpty()) return; + + StyledDocument doc = textPane.getStyledDocument(); + MutableAttributeSet currentAttrs = new SimpleAttributeSet(); + StyleConstants.setForeground(currentAttrs, new Color(0, 200, 0)); // Default green + StyleConstants.setBackground(currentAttrs, Color.BLACK); + + Matcher matcher = ANSI_PATTERN.matcher(text); + int lastEnd = 0; + + try { + while (matcher.find()) { + // Insert text before the sequence + if (matcher.start() > lastEnd) { + processPlainText(doc, text.substring(lastEnd, matcher.start()), currentAttrs, protectedLength); + } + + String params = matcher.group(1); + String command = matcher.group(2); + + if (command != null && !command.isEmpty()) { + processCsiSequence(command, params, currentAttrs, doc); + } + + lastEnd = matcher.end(); + } + + // Insert remaining text + if (lastEnd < text.length()) { + processPlainText(doc, text.substring(lastEnd), currentAttrs, protectedLength); + } + + textPane.setCaretPosition(doc.getLength()); + } catch (BadLocationException e) { + e.printStackTrace(); + } + } + + private static void processPlainText(StyledDocument doc, String text, AttributeSet attrs, int protectedLength) throws BadLocationException { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); + + if (c == '\r') { + // Check if next char is \n (standard Windows/SSH line ending) + if (i + 1 < text.length() && text.charAt(i + 1) == '\n') { + // \r\n sequence - treat as single newline + if (sb.length() > 0) { + sb.append('\n'); + } else { + doc.insertString(doc.getLength(), "\n", attrs); + } + i++; // Skip the \n + } else { + // Standalone \r - carriage return (move to beginning of line) + if (sb.length() > 0) { + doc.insertString(doc.getLength(), sb.toString(), attrs); + sb.setLength(0); + } + String content = doc.getText(0, doc.getLength()); + int lastNewline = content.lastIndexOf('\n'); + int startToDelete = lastNewline == -1 ? 0 : lastNewline + 1; + + // Respect protected boundary + if (startToDelete < protectedLength) { + startToDelete = protectedLength; + } + + int endToDelete = doc.getLength(); + if (endToDelete > startToDelete) { + doc.remove(startToDelete, endToDelete - startToDelete); + } + } + } else if (c == '\b') { + // Backspace: delete previous character + if (sb.length() > 0) { + doc.insertString(doc.getLength(), sb.toString(), attrs); + sb.setLength(0); + } + // NEVER delete before or at protectedLength + if (doc.getLength() > protectedLength) { + doc.remove(doc.getLength() - 1, 1); + } + } else if (c == '\t') { + // Tab: convert to spaces (4 spaces) + sb.append(" "); + } else if (!Character.isISOControl(c)) { + sb.append(c); + } + } + if (sb.length() > 0) { + doc.insertString(doc.getLength(), sb.toString(), attrs); + } + } + + private static void processCsiSequence(String command, String params, MutableAttributeSet attrs, StyledDocument doc) { + if (params == null) params = ""; + + switch (command) { + case "m": // SGR - Select Graphic Rendition (colors, styles) + processSgr(params, attrs); + break; + case "K": // EL - Erase in Line + eraseInLine(doc, params); + break; + case "J": // ED - Erase in Display + eraseInDisplay(doc, params); + break; + case "H": + case "f": // CUP/HVP - Cursor Position + // Ignore - JTextPane doesn't support cursor repositioning + break; + case "A": // CUU - Cursor Up + case "B": // CUD - Cursor Down + case "C": // CUF - Cursor Forward (Right) + case "D": // CUB - Cursor Backward (Left) + case "E": // CNL - Cursor Next Line + case "F": // CPL - Cursor Previous Line + case "G": // CHA - Cursor Horizontal Absolute + // Ignore cursor movement - JTextPane handles cursor differently + break; + case "s": // SCP - Save Cursor Position + case "u": // RCP - Restore Cursor Position + // Ignore - not needed for output display + break; + case "h": // SM - Set Mode + case "l": // RM - Reset Mode + // Handle mode settings (like cursor visibility, alt screen) + if (params.contains("25")) { + // ?25h = show cursor, ?25l = hide cursor - we'll just display normally + } + break; + case "c": // DA - Device Attributes + case "n": // DSR - Device Status Report + // Device info requests - ignore + break; + case "1" : + case "2" : + case "3" : + case "4" : + // These look like incomplete sequences, ignore + break; + } + } + + private static void processSgr(String params, MutableAttributeSet attrs) { + if (params == null || params.isEmpty()) { + resetAttributes(attrs); + return; + } + + String[] codes = params.split(";"); + for (String code : codes) { + if (code.isEmpty()) continue; + + try { + int c = Integer.parseInt(code); + + if (c == 0) { + // Reset all attributes + resetAttributes(attrs); + } else if (c == 1) { + // Bold + StyleConstants.setBold(attrs, true); + } else if (c == 2) { + // Dim (reduced intensity) + StyleConstants.setBold(attrs, false); + } else if (c == 3) { + // Italic + StyleConstants.setItalic(attrs, true); + } else if (c == 4) { + // Underline + StyleConstants.setUnderline(attrs, true); + } else if (c == 5) { + // Blink (we'll just show it normally) + StyleConstants.setBold(attrs, true); + } else if (c == 7) { + // Reverse video + Color fg = (Color) attrs.getAttribute(StyleConstants.Foreground); + Color bg = (Color) attrs.getAttribute(StyleConstants.Background); + if (fg != null && bg != null) { + StyleConstants.setForeground(attrs, bg); + StyleConstants.setBackground(attrs, fg); + } + } else if (c == 22) { + // Normal intensity + StyleConstants.setBold(attrs, false); + } else if (c == 23) { + // No italic + StyleConstants.setItalic(attrs, false); + } else if (c == 24) { + // No underline + StyleConstants.setUnderline(attrs, false); + } else if (c >= 30 && c <= 37) { + // Foreground color (standard) + StyleConstants.setForeground(attrs, getAnsiColor(c - 30, false)); + } else if (c == 39) { + // Default foreground + StyleConstants.setForeground(attrs, new Color(0, 200, 0)); + } else if (c >= 40 && c <= 47) { + // Background color (standard) + StyleConstants.setBackground(attrs, getAnsiColor(c - 40, false)); + } else if (c == 49) { + // Default background + StyleConstants.setBackground(attrs, Color.BLACK); + } else if (c >= 90 && c <= 97) { + // Foreground color (bright) + StyleConstants.setForeground(attrs, getAnsiColor(c - 90, true)); + } else if (c >= 100 && c <= 107) { + // Background color (bright) + StyleConstants.setBackground(attrs, getAnsiColor(c - 100, true)); + } + } catch (NumberFormatException ignored) { + // Ignore invalid codes + } + } + } + + private static void resetAttributes(MutableAttributeSet attrs) { + StyleConstants.setForeground(attrs, new Color(0, 200, 0)); // Default green + StyleConstants.setBackground(attrs, Color.BLACK); + StyleConstants.setBold(attrs, false); + StyleConstants.setItalic(attrs, false); + StyleConstants.setUnderline(attrs, false); + } + + private static void eraseInLine(StyledDocument doc, String param) { + try { + // EL 0 = erase from cursor to end of line (default) + // EL 1 = erase from start of line to cursor + // EL 2 = erase entire line + + String content = doc.getText(0, doc.getLength()); + int lastNewline = content.lastIndexOf('\n'); + int lineStart = lastNewline == -1 ? 0 : lastNewline + 1; + int lineEnd = doc.getLength(); + + if ("2".equals(param)) { + // Erase entire line + doc.remove(lineStart, lineEnd - lineStart); + } else { + // Default: erase from cursor to end of line + doc.remove(lineStart, lineEnd - lineStart); + } + } catch (BadLocationException ignored) {} + } + + private static void eraseInDisplay(StyledDocument doc, String param) { + try { + // ED 0 = erase from cursor to end of display (default) + // ED 1 = erase from start of display to cursor + // ED 2 = erase entire display + // ED 3 = erase scrollback buffer + + if ("2".equals(param) || "3".equals(param)) { + // For terminal apps: don't clear entire display, + // just leave the content as is + // Clearing would remove important command output + } + } catch (Exception ignored) {} + } + + private static Color getAnsiColor(int index, boolean bright) { + if (bright) { + switch (index) { + case 0: return new Color(128, 128, 128); // Bright Black (Gray) + case 1: return new Color(255, 85, 85); // Bright Red + case 2: return new Color(85, 255, 85); // Bright Green + case 3: return new Color(255, 255, 85); // Bright Yellow + case 4: return new Color(85, 85, 255); // Bright Blue + case 5: return new Color(255, 85, 255); // Bright Magenta + case 6: return new Color(85, 255, 255); // Bright Cyan + case 7: return new Color(255, 255, 255); // Bright White + default: return Color.WHITE; + } + } else { + switch (index) { + case 0: return new Color(0, 0, 0); // Black + case 1: return new Color(170, 0, 0); // Red + case 2: return new Color(0, 170, 0); // Green + case 3: return new Color(170, 85, 0); // Yellow + case 4: return new Color(0, 0, 170); // Blue + case 5: return new Color(170, 0, 170); // Magenta + case 6: return new Color(0, 170, 170); // Cyan + case 7: return new Color(170, 170, 170); // White + default: return new Color(0, 200, 0); + } + } + } +} diff --git a/src/main/java/cz/kamma/term/util/SystemIdUtil.java b/src/main/java/cz/kamma/term/util/SystemIdUtil.java new file mode 100644 index 0000000..e37915e --- /dev/null +++ b/src/main/java/cz/kamma/term/util/SystemIdUtil.java @@ -0,0 +1,30 @@ +package cz.kamma.term.util; + +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.util.Enumeration; + +public class SystemIdUtil { + public static String getMachineId() { + try { + StringBuilder sb = new StringBuilder(); + sb.append(System.getProperty("os.name")); + sb.append(System.getProperty("user.name")); + + Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces(); + while (networkInterfaces.hasMoreElements()) { + NetworkInterface ni = networkInterfaces.nextElement(); + byte[] hardwareAddress = ni.getHardwareAddress(); + if (hardwareAddress != null) { + for (byte b : hardwareAddress) { + sb.append(String.format("%02X", b)); + } + break; // Just use the first MAC address found + } + } + return sb.toString(); + } catch (Exception e) { + return "fallback-machine-id-12345"; + } + } +} diff --git a/target/classes/cz/kamma/term/App.class b/target/classes/cz/kamma/term/App.class new file mode 100644 index 0000000..1544ead Binary files /dev/null and b/target/classes/cz/kamma/term/App.class differ diff --git a/target/classes/cz/kamma/term/model/AppSettings.class b/target/classes/cz/kamma/term/model/AppSettings.class new file mode 100644 index 0000000..9b786c2 Binary files /dev/null and b/target/classes/cz/kamma/term/model/AppSettings.class differ diff --git a/target/classes/cz/kamma/term/model/ConnectionInfo.class b/target/classes/cz/kamma/term/model/ConnectionInfo.class new file mode 100644 index 0000000..8a47209 Binary files /dev/null and b/target/classes/cz/kamma/term/model/ConnectionInfo.class differ diff --git a/target/classes/cz/kamma/term/service/ConnectionManager$1.class b/target/classes/cz/kamma/term/service/ConnectionManager$1.class new file mode 100644 index 0000000..e3a52be Binary files /dev/null and b/target/classes/cz/kamma/term/service/ConnectionManager$1.class differ diff --git a/target/classes/cz/kamma/term/service/ConnectionManager.class b/target/classes/cz/kamma/term/service/ConnectionManager.class new file mode 100644 index 0000000..f003480 Binary files /dev/null and b/target/classes/cz/kamma/term/service/ConnectionManager.class differ diff --git a/target/classes/cz/kamma/term/service/EncryptionService.class b/target/classes/cz/kamma/term/service/EncryptionService.class new file mode 100644 index 0000000..69f4c4c Binary files /dev/null and b/target/classes/cz/kamma/term/service/EncryptionService.class differ diff --git a/target/classes/cz/kamma/term/service/HistoryManager.class b/target/classes/cz/kamma/term/service/HistoryManager.class new file mode 100644 index 0000000..3ce70f9 Binary files /dev/null and b/target/classes/cz/kamma/term/service/HistoryManager.class differ diff --git a/target/classes/cz/kamma/term/service/SettingsManager.class b/target/classes/cz/kamma/term/service/SettingsManager.class new file mode 100644 index 0000000..570eec1 Binary files /dev/null and b/target/classes/cz/kamma/term/service/SettingsManager.class differ diff --git a/target/classes/cz/kamma/term/ui/MainFrame$1.class b/target/classes/cz/kamma/term/ui/MainFrame$1.class new file mode 100644 index 0000000..adbe05b Binary files /dev/null and b/target/classes/cz/kamma/term/ui/MainFrame$1.class differ diff --git a/target/classes/cz/kamma/term/ui/MainFrame.class b/target/classes/cz/kamma/term/ui/MainFrame.class new file mode 100644 index 0000000..29f9ab6 Binary files /dev/null and b/target/classes/cz/kamma/term/ui/MainFrame.class differ diff --git a/target/classes/cz/kamma/term/ui/TerminalPanel$1.class b/target/classes/cz/kamma/term/ui/TerminalPanel$1.class new file mode 100644 index 0000000..842ee30 Binary files /dev/null and b/target/classes/cz/kamma/term/ui/TerminalPanel$1.class differ diff --git a/target/classes/cz/kamma/term/ui/TerminalPanel$2.class b/target/classes/cz/kamma/term/ui/TerminalPanel$2.class new file mode 100644 index 0000000..a89808d Binary files /dev/null and b/target/classes/cz/kamma/term/ui/TerminalPanel$2.class differ diff --git a/target/classes/cz/kamma/term/ui/TerminalPanel.class b/target/classes/cz/kamma/term/ui/TerminalPanel.class new file mode 100644 index 0000000..9c3c323 Binary files /dev/null and b/target/classes/cz/kamma/term/ui/TerminalPanel.class differ diff --git a/target/classes/cz/kamma/term/util/AnsiHelper.class b/target/classes/cz/kamma/term/util/AnsiHelper.class new file mode 100644 index 0000000..30066a9 Binary files /dev/null and b/target/classes/cz/kamma/term/util/AnsiHelper.class differ diff --git a/target/classes/cz/kamma/term/util/SystemIdUtil.class b/target/classes/cz/kamma/term/util/SystemIdUtil.class new file mode 100644 index 0000000..f3e478c Binary files /dev/null and b/target/classes/cz/kamma/term/util/SystemIdUtil.class differ diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst new file mode 100644 index 0000000..7042337 --- /dev/null +++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -0,0 +1,15 @@ +cz/kamma/term/service/ConnectionManager.class +cz/kamma/term/service/HistoryManager.class +cz/kamma/term/ui/MainFrame$1.class +cz/kamma/term/model/AppSettings.class +cz/kamma/term/util/SystemIdUtil.class +cz/kamma/term/service/EncryptionService.class +cz/kamma/term/model/ConnectionInfo.class +cz/kamma/term/ui/TerminalPanel$2.class +cz/kamma/term/ui/TerminalPanel.class +cz/kamma/term/service/SettingsManager.class +cz/kamma/term/ui/TerminalPanel$1.class +cz/kamma/term/service/ConnectionManager$1.class +cz/kamma/term/ui/MainFrame.class +cz/kamma/term/util/AnsiHelper.class +cz/kamma/term/App.class diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst new file mode 100644 index 0000000..96f3838 --- /dev/null +++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -0,0 +1,11 @@ +/home/kamma/projects/terminal/src/main/java/cz/kamma/term/util/SystemIdUtil.java +/home/kamma/projects/terminal/src/main/java/cz/kamma/term/ui/MainFrame.java +/home/kamma/projects/terminal/src/main/java/cz/kamma/term/service/HistoryManager.java +/home/kamma/projects/terminal/src/main/java/cz/kamma/term/service/EncryptionService.java +/home/kamma/projects/terminal/src/main/java/cz/kamma/term/service/SettingsManager.java +/home/kamma/projects/terminal/src/main/java/cz/kamma/term/service/ConnectionManager.java +/home/kamma/projects/terminal/src/main/java/cz/kamma/term/model/AppSettings.java +/home/kamma/projects/terminal/src/main/java/cz/kamma/term/ui/TerminalPanel.java +/home/kamma/projects/terminal/src/main/java/cz/kamma/term/model/ConnectionInfo.java +/home/kamma/projects/terminal/src/main/java/cz/kamma/term/util/AnsiHelper.java +/home/kamma/projects/terminal/src/main/java/cz/kamma/term/App.java