diff --git a/src/com/maccasoft/tools/SerialTerminal.java b/src/com/maccasoft/tools/SerialTerminal.java index f69e234..03d3a3b 100644 --- a/src/com/maccasoft/tools/SerialTerminal.java +++ b/src/com/maccasoft/tools/SerialTerminal.java @@ -10,16 +10,25 @@ package com.maccasoft.tools; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Arrays; + import org.eclipse.core.databinding.observable.Realm; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jface.databinding.swt.DisplayRealm; -import org.eclipse.jface.window.Window; +import org.eclipse.jface.dialogs.ProgressIndicator; import org.eclipse.swt.SWT; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; @@ -27,7 +36,12 @@ import org.eclipse.swt.widgets.Combo; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.FileDialog; import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Menu; +import org.eclipse.swt.widgets.MenuItem; import org.eclipse.swt.widgets.Shell; import com.maccasoft.tools.internal.ImageRegistry; @@ -39,7 +53,7 @@ import jssc.SerialPortException; import jssc.SerialPortList; import jssc.SerialPortTimeoutException; -public class SerialTerminal extends Window { +public class SerialTerminal { public static final String[] BAUD_RATES = new String[] { String.valueOf(SerialPort.BAUDRATE_9600), @@ -57,6 +71,7 @@ public class SerialTerminal extends Window { public static final byte C = 0x43; // 'C' which use in XModem/CRC Display display; + Shell shell; Composite container; Terminal term; @@ -64,6 +79,8 @@ public class SerialTerminal extends Window { Combo comPort; Combo baudRate; + ProgressIndicator progressBar; + String port; int baud; SerialPort serialPort; @@ -109,13 +126,100 @@ public class SerialTerminal extends Window { } }; + final IProgressMonitor progressMonitor = new IProgressMonitor() { + + @Override + public void beginTask(String name, int totalWork) { + final boolean animated = (totalWork == UNKNOWN || totalWork == 0); + display.syncExec(new Runnable() { + + @Override + public void run() { + if (progressBar == null || progressBar.isDisposed()) { + return; + } + + if (!progressBar.getVisible()) { + progressBar.setVisible(true); + container.layout(); + } + if (!animated) { + progressBar.beginTask(totalWork); + } + else { + progressBar.beginAnimatedTask(); + } + } + }); + } + + @Override + public void done() { + display.syncExec(new Runnable() { + + @Override + public void run() { + if (progressBar == null || progressBar.isDisposed()) { + return; + } + progressBar.sendRemainingWork(); + progressBar.done(); + } + }); + } + + @Override + public void internalWorked(double work) { + display.syncExec(new Runnable() { + + @Override + public void run() { + if (progressBar == null || progressBar.isDisposed()) { + return; + } + progressBar.worked(work); + } + }); + } + + @Override + public boolean isCanceled() { + return false; + } + + @Override + public void setCanceled(boolean value) { + + } + + @Override + public void setTaskName(String name) { + + } + + @Override + public void subTask(String name) { + + } + + @Override + public void worked(int work) { + internalWorked(work); + } + + }; + public SerialTerminal() { - super((Shell) null); + } - @Override - protected void configureShell(Shell newShell) { - super.configureShell(newShell); + public void open() { + display = Display.getDefault(); + preferences = Preferences.getInstance(); + + shell = new Shell(display); + shell.setText(Application.APP_TITLE); + shell.setData(this); Image[] images = new Image[] { ImageRegistry.getImageFromResources("app128.png"), @@ -124,16 +228,146 @@ public class SerialTerminal extends Window { ImageRegistry.getImageFromResources("app32.png"), ImageRegistry.getImageFromResources("app16.png"), }; - newShell.setImages(images); + shell.setImages(images); - newShell.setData(this); + Menu menu = new Menu(shell, SWT.BAR); + createFileMenu(menu); + createEditMenu(menu); + createHelpMenu(menu); + shell.setMenuBar(menu); + + GridLayout layout = new GridLayout(1, false); + layout.marginWidth = layout.marginHeight = 0; + shell.setLayout(layout); + + Control control = createContents(shell); + control.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + + Rectangle screen = display.getClientArea(); + + Point size = control.computeSize(SWT.DEFAULT, SWT.DEFAULT, true); + + Rectangle rect = shell.computeTrim(0, 0, size.x, size.y); + rect.x = (screen.width - rect.width) / 2; + rect.y = (screen.height - rect.height) / 2; + if (rect.y < 0) { + rect.height += rect.y * 2; + rect.y = 0; + } + + shell.setLocation(rect.x, rect.y); + shell.setSize(rect.width, rect.height); + + shell.open(); } - @Override - protected Control createContents(Composite parent) { - display = parent.getDisplay(); + void createFileMenu(Menu parent) { + final Menu menu = new Menu(parent.getParent(), SWT.DROP_DOWN); - preferences = Preferences.getInstance(); + MenuItem item = new MenuItem(parent, SWT.CASCADE); + item.setText("&File"); + item.setMenu(menu); + + item = new MenuItem(menu, SWT.CASCADE); + item.setText("Packed CP/M Binary Upload..."); + item.addListener(SWT.Selection, new Listener() { + + @Override + public void handleEvent(Event e) { + try { + handleUploadPackedBinary(); + } catch (Exception e1) { + e1.printStackTrace(); + } + } + }); + + new MenuItem(menu, SWT.SEPARATOR); + + item = new MenuItem(menu, SWT.CASCADE); + item.setText("XModem Upload..."); + item.addListener(SWT.Selection, new Listener() { + + @Override + public void handleEvent(Event e) { + try { + handleUploadXModem(); + } catch (Exception e1) { + e1.printStackTrace(); + } + } + }); + + item = new MenuItem(menu, SWT.CASCADE); + item.setText("XModem Download..."); + item.addListener(SWT.Selection, new Listener() { + + @Override + public void handleEvent(Event e) { + try { + handleDownloadXModem(); + } catch (Exception e1) { + e1.printStackTrace(); + } + } + }); + + new MenuItem(menu, SWT.SEPARATOR); + + item = new MenuItem(menu, SWT.PUSH); + item.setText("Close"); + item.addListener(SWT.Selection, new Listener() { + + @Override + public void handleEvent(Event e) { + shell.dispose(); + } + }); + } + + void createEditMenu(Menu parent) { + Menu menu = new Menu(parent.getParent(), SWT.DROP_DOWN); + + MenuItem item = new MenuItem(parent, SWT.CASCADE); + item.setText("&Edit"); + item.setMenu(menu); + + item = new MenuItem(menu, SWT.PUSH); + item.setText("Paste\tShift+Ins"); + item.setAccelerator(SWT.MOD2 + SWT.INSERT); + item.addListener(SWT.Selection, new Listener() { + + @Override + public void handleEvent(Event e) { + try { + term.pasteFromClipboard(); + } catch (Exception e1) { + e1.printStackTrace(); + } + } + }); + } + + void createHelpMenu(Menu parent) { + Menu menu = new Menu(parent.getParent(), SWT.DROP_DOWN); + + MenuItem item = new MenuItem(parent, SWT.CASCADE); + item.setText("&Help"); + item.setMenu(menu); + + item = new MenuItem(menu, SWT.PUSH); + item.setText("About " + Application.APP_TITLE); + item.addListener(SWT.Selection, new Listener() { + + @Override + public void handleEvent(Event e) { + AboutDialog dlg = new AboutDialog(shell); + dlg.open(); + } + }); + } + + protected Control createContents(Composite parent) { port = preferences.getSerialPort(); baud = preferences.getSerialBaud(); @@ -229,6 +463,11 @@ public class SerialTerminal extends Window { label = new Label(container, SWT.NONE); label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); + progressBar = new ProgressIndicator(container, SWT.HORIZONTAL); + GridData layoutData = new GridData(GridData.GRAB_VERTICAL); + layoutData.widthHint = 128; + progressBar.setLayoutData(layoutData); + label = new Label(container, SWT.NONE); label.setText("Port"); @@ -291,7 +530,7 @@ public class SerialTerminal extends Window { e.printStackTrace(); } - getShell().setText("Serial Terminal on " + port); + shell.setText("Serial Terminal on " + port); } public void setFocus() { @@ -303,13 +542,129 @@ public class SerialTerminal extends Window { } public void dispose() { - getShell().dispose(); + shell.dispose(); } - public void uploadPackedBinary(String name, byte[] data, IProgressMonitor monitor) throws SerialPortException, SerialPortTimeoutException { - int checksum; + private void handleUploadPackedBinary() { + String s = preferences.getDownloadCommand(); + final File[] fileName = getFileToOpen("Packed-Binary Upload", s != null && !s.equals("")); + if (fileName == null || fileName.length == 0) { + return; + } + + new Thread(new Runnable() { + + @Override + public void run() { + try { + for (int i = 0; i < fileName.length; i++) { + InputStream is = new FileInputStream(fileName[i]); + progressMonitor.beginTask("Upload", (is.available() + 127) / 128); + try { + uploadPackedBinary(fileName[i].getName().toUpperCase(), is, progressMonitor); + } catch (Exception e) { + e.printStackTrace(); + } + progressMonitor.done(); + is.close(); + + if ((i + 1) < fileName.length) { + try { + Thread.sleep(2000); + } catch (Exception e) { + // Do nothing + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + }).start(); + } + + private void handleUploadXModem() { + String s = preferences.getXmodemCommand(); + final File[] fileName = getFileToOpen("XModem Upload", s != null && !s.equals("")); + if (fileName == null || fileName.length == 0) { + return; + } + + new Thread(new Runnable() { + + @Override + public void run() { + try { + for (int i = 0; i < fileName.length; i++) { + InputStream is = new FileInputStream(fileName[i]); + progressMonitor.beginTask("Upload", (is.available() + 127) / 128); + try { + uploadXModem(fileName[i].getName().toUpperCase(), is, progressMonitor); + } catch (Exception e) { + e.printStackTrace(); + } + progressMonitor.done(); + is.close(); + + if ((i + 1) < fileName.length) { + try { + Thread.sleep(2000); + } catch (Exception e) { + // Do nothing + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + }).start(); + } + + private File[] getFileToOpen(String text, boolean multi) { + int style = SWT.OPEN; + if (multi) { + style |= SWT.MULTI; + } + + FileDialog dlg = new FileDialog(shell, style); + dlg.setText(text); + dlg.setFilterNames(new String[] { + "All Files", + "CP/M Programs" + }); + dlg.setFilterExtensions(new String[] { + "*.*", + "*.COM;*.com" + }); + dlg.setFilterIndex(0); + + if (dlg.open() == null) { + return null; + } + + String[] names = dlg.getFileNames(); + + File[] files = new File[names.length]; + for (int i = 0; i < names.length; i++) { + files[i] = new File(dlg.getFilterPath(), names[i]); + } + + return files; + } + + public void uploadPackedBinary(String name, byte[] data, IProgressMonitor monitor) throws Exception { + uploadPackedBinary(name, new ByteArrayInputStream(data), monitor); + } + + public void uploadPackedBinary(String name, InputStream is, IProgressMonitor monitor) throws Exception { + int data, checksum; String s; + int length = is.available(); + try { serialPort.removeEventListener(); } catch (SerialPortException e) { @@ -317,14 +672,16 @@ public class SerialTerminal extends Window { } try { - s = preferences.getDownloadCommand(); - s = s.replace("{0}", name.toUpperCase()); - serialPort.writeString(s); - serialPort.writeInt(13); - flushOutput(); - do { - s = readString(); - } while (s.length() != 0 && !s.contains("DOWNLOAD ")); + String cmd = preferences.getDownloadCommand(); + if (cmd != null && !cmd.equals("")) { + cmd = cmd.replace("{0}", name.toUpperCase()); + serialPort.writeString(cmd); + serialPort.writeInt(13); + flushOutput(); + do { + s = readString(); + } while (s.length() != 0 && !s.contains(cmd)); + } serialPort.writeString("U0\r"); flushOutput(); @@ -343,10 +700,11 @@ public class SerialTerminal extends Window { } checksum = 0; - for (int i = 0; i < data.length; i++) { - serialPort.writeString(String.format("%02X", data[i] & 0xFF)); + for (int i = 0; i < length; i++) { + data = is.read(); + serialPort.writeString(String.format("%02X", data & 0xFF)); flushOutput(); - checksum += data[i] & 0xFF; + checksum += data & 0xFF; if (i > 0 && (i & 127) == 0) { waitDot(); if (monitor != null) { @@ -360,7 +718,7 @@ public class SerialTerminal extends Window { serialPort.writeString(">"); flushOutput(); - if ((data.length & 127) != 0) { + if ((length & 127) != 0) { waitDot(); if (monitor != null) { monitor.worked(1); @@ -374,7 +732,7 @@ public class SerialTerminal extends Window { } } - serialPort.writeString(String.format("%02X", data.length & 0xFF)); + serialPort.writeString(String.format("%02X", length & 0xFF)); flushOutput(); serialPort.writeString(String.format("%02X", checksum & 0xFF)); @@ -426,8 +784,14 @@ public class SerialTerminal extends Window { } } - public void uploadXModem(String name, byte[] data, IProgressMonitor monitor) throws SerialPortException, SerialPortTimeoutException { + public void uploadXModem(String name, byte[] data, IProgressMonitor monitor) throws Exception { + uploadXModem(name, new ByteArrayInputStream(data), monitor); + } + + public void uploadXModem(String name, InputStream is, IProgressMonitor monitor) throws Exception { + int i; String s; + byte[] data = new byte[128]; try { serialPort.removeEventListener(); @@ -436,27 +800,29 @@ public class SerialTerminal extends Window { } try { - s = preferences.getXmodemCommand(); - s = s.replace("{0}", name.toUpperCase()); - serialPort.writeString(s); - serialPort.writeInt(13); - flushOutput(); - do { - s = readString(); - } while (s.length() != 0 && !s.contains("XMODEM ")); + String cmd = preferences.getXmodemCommand(); + if (cmd != null && !cmd.equals("")) { + cmd = cmd.replace("{0}", name.toUpperCase()); + serialPort.writeString(cmd); + serialPort.writeInt(13); + flushOutput(); + do { + s = readString(); + } while (s.length() != 0 && !s.contains(cmd)); - s = ""; - while (true) { - byte[] b = serialPort.readBytes(1, 15000); - term.write(b[0]); - if (b[0] >= ' ') { - s = s + (char) b[0]; - if (s.endsWith("Overwrite (Y/N)?")) { - serialPort.writeString("Y\r"); - s = ""; - } - if (s.endsWith("with CRCs")) { - break; + s = ""; + while (true) { + byte[] b = serialPort.readBytes(1, 15000); + term.write(b[0]); + if (b[0] >= ' ') { + s = s + (char) b[0]; + if (s.endsWith("Overwrite (Y/N)?")) { + serialPort.writeString("Y\r"); + s = ""; + } + if (s.endsWith("with CRCs")) { + break; + } } } } @@ -465,8 +831,12 @@ public class SerialTerminal extends Window { int packet = 1; int errors = 0; - int i = 0; - while (i < data.length) { + Arrays.fill(data, (byte) 0); + if ((i = is.read(data)) < data.length) { + data[i] = 0x1A; + } + + while (true) { try { byte[] b = serialPort.readBytes(1, 15000); @@ -480,12 +850,28 @@ public class SerialTerminal extends Window { } if (b[0] == ACK) { - i += 128; packet++; if (monitor != null) { monitor.worked(1); } - errors++; + errors = 0; + + Arrays.fill(data, (byte) 0); + if ((i = is.read(data)) <= 0) { + serialPort.writeByte(EOT); + b = serialPort.readBytes(1, 5000); + if (b[0] == NAK) { + serialPort.writeByte(EOT); + } + b = serialPort.readBytes(1, 5000); + if (b[0] == NAK) { + serialPort.writeByte(EOT); + } + break; + } + if (i < data.length) { + data[i] = 0x1A; + } } if (b[0] == NAK) { @@ -501,23 +887,10 @@ public class SerialTerminal extends Window { serialPort.writeByte((byte) (packet ^ 0xFF)); int checksum = 0, crc = 0; - int x = 0; - while (x < 128 && (i + x) < data.length) { - serialPort.writeByte(data[i + x]); - checksum += data[i + x] & 0xFF; - crc = updateCrc(crc, data[i + x] & 0xFF); - x++; - } - if (x < 128) { - serialPort.writeByte((byte) 0x1A); - checksum += 0x1A; - crc = updateCrc(crc, 0x1A); - x++; - while (x < 128) { - serialPort.writeByte((byte) 0); - crc = updateCrc(crc, 0); - x++; - } + for (int x = 0; x < data.length; x++) { + serialPort.writeByte(data[x]); + checksum += data[x] & 0xFF; + crc = updateCrc(crc, data[x] & 0xFF); } if (doCrc) { @@ -542,16 +915,145 @@ public class SerialTerminal extends Window { } } } + } finally { + try { + serialPort.addEventListener(serialEventListener); + } catch (SerialPortException e) { + // Do nothing + } + } + } - if (i >= data.length) { - serialPort.writeByte(EOT); - byte[] b = serialPort.readBytes(1, 5000); - if (b[0] == NAK) { - serialPort.writeByte(EOT); + private void handleDownloadXModem() { + FileDialog dlg = new FileDialog(shell, SWT.SAVE); + dlg.setText("XModem Download"); + dlg.setFilterNames(new String[] { + "All Files", + "CP/M Programs" + }); + dlg.setFilterExtensions(new String[] { + "*.*", + "*.COM;*.com" + }); + dlg.setFilterIndex(0); + dlg.setOverwrite(true); + + String fileName = dlg.open(); + if (fileName == null) { + return; + } + + final File file = new File(fileName); + + new Thread(new Runnable() { + + @Override + public void run() { + try { + OutputStream os = new FileOutputStream(file); + progressMonitor.beginTask("Upload", IProgressMonitor.UNKNOWN); + try { + downloadXModem(file.getName().toUpperCase(), os, progressMonitor); + } catch (Exception e) { + e.printStackTrace(); + } + progressMonitor.done(); + os.close(); + } catch (Exception e) { + e.printStackTrace(); } - b = serialPort.readBytes(1, 5000); - if (b[0] == NAK) { - serialPort.writeByte(EOT); + } + + }).start(); + } + + public void downloadXModem(String name, OutputStream os, IProgressMonitor monitor) throws Exception { + byte[] b, data, c; + + try { + serialPort.removeEventListener(); + } catch (SerialPortException e) { + // Do nothing + } + + try { + boolean doCrc = true; + int packet = 1; + int errors = 0; + + serialPort.writeByte(doCrc ? C : NAK); + + while (true) { + try { + b = serialPort.readBytes(1, 15000); + + if (b[0] == SOH) { + b = serialPort.readBytes(2, 15000); + data = serialPort.readBytes(128, 15000); + c = serialPort.readBytes(doCrc ? 2 : 1, 15000); + + if (b[0] != (byte) packet || (b[0] ^ b[1]) != (byte) 0xFF) { + serialPort.writeByte(NAK); + errors++; + if (errors >= 10) { + return; + } + continue; + } + + int checksum = 0, crc = 0; + for (int x = 0; x < data.length; x++) { + checksum += data[x] & 0xFF; + crc = updateCrc(crc, data[x] & 0xFF); + } + + if (doCrc) { + if (c[0] != (byte) (crc >> 8) || c[1] != (byte) (crc & 0xFF)) { + serialPort.writeByte(NAK); + errors++; + if (errors >= 10) { + return; + } + continue; + } + } + else { + if (c[0] != (byte) (checksum & 0xFF)) { + serialPort.writeByte(NAK); + errors++; + if (errors >= 10) { + return; + } + continue; + } + } + + os.write(data); + + serialPort.writeByte(ACK); + packet++; + errors = 0; + } + else if (b[0] == EOT) { + serialPort.writeByte(ACK); + break; + } + if (monitor != null && monitor.isCanceled()) { + serialPort.writeBytes(new byte[] { + CAN, CAN, CAN + }); + return; + } + } catch (SerialPortTimeoutException e) { + errors++; + if (errors >= 10) { + return; + } + if (doCrc && errors >= 5) { + doCrc = false; + errors = 0; + } + serialPort.writeByte(doCrc ? C : NAK); } } } finally { @@ -575,6 +1077,10 @@ public class SerialTerminal extends Window { return crc & 0xFFFF; } + public Shell getShell() { + return shell; + } + static { System.setProperty("SWT_GTK3", "0"); } diff --git a/src/com/maccasoft/tools/Terminal.java b/src/com/maccasoft/tools/Terminal.java index 0569b28..c325032 100644 --- a/src/com/maccasoft/tools/Terminal.java +++ b/src/com/maccasoft/tools/Terminal.java @@ -356,7 +356,7 @@ public class Terminal { } } - void pasteFromClipboard() { + public void pasteFromClipboard() { Clipboard clipboard = new Clipboard(display); try { String s = (String) clipboard.getContents(TextTransfer.getInstance());