From: Michael McMaster Date: Sat, 23 Feb 2019 05:48:58 +0000 (+1000) Subject: Prepare for 1.0-BETA release X-Git-Tag: 1.0BETA~1 X-Git-Url: http://git.codesrc.com/gitweb.cgi?a=commitdiff_plain;h=376fa42a62bd3d728c41c42cb96be48d82c466d0;p=scsi2sd-util.git Prepare for 1.0-BETA release --- diff --git a/doc/COPYING b/doc/COPYING deleted file mode 100644 index ef7a877..0000000 --- a/doc/COPYING +++ /dev/null @@ -1,63 +0,0 @@ -SCSI2SD is distributed under the GNU General Public License version 3. Please -find the license in the file gplv3.txt - -The additional license terms apply: - -//////////////////////////////////////////////////////////////////////////////////// -Code generated via STM32CubeMX: - -COPYRIGHT(c) 2015 STMicroelectronics - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - 3. Neither the name of STMicroelectronics nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -//////////////////////////////////////////////////////////////////////////////////// -ARM CMSIS, distributed with STM32CubeMX: - -Copyright (c) 2009 - 2015 ARM LIMITED - -All rights reserved. -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: -- Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -- Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. -- Neither the name of ARM nor the names of its contributors may be used - to endorse or promote products derived from this software without - specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDERS AND CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - - diff --git a/pom.xml b/pom.xml old mode 100644 new mode 100755 index d73eda0..c65d6b4 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,11 @@ com.codesrc scsi2sd-util 1.0-SNAPSHOT - jar + + scsi2sd.io + scsi2sd.ui + + pom SCSI2SD Utility http://www.codesrc.com @@ -23,93 +27,5 @@ UTF-8 - - target - target/classes - ${project.artifactId}-${project.version} - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.3 - - 1.8 - 1.8 - - - - com.zenjava - javafx-maven-plugin - 8.8.3 - - com.codesrc.scsi2sd.App - com.codesrc - true - SCSI2SD Utility - doc - - - - - create-jfxjar - package - - build-jar - - - - create-native - package - - build-native - - - - - - - - - - - - org.springframework - spring-context - 4.3.8.RELEASE - - - commons-logging - commons-logging - - - - - - org.controlsfx - controlsfx - 8.40.12 - - - - - ch.qos.logback - logback-classic - 1.2.3 - - - org.slf4j - jcl-over-slf4j - 1.7.25 - - diff --git a/scsi2sd.io/pom.xml b/scsi2sd.io/pom.xml new file mode 100755 index 0000000..684beb1 --- /dev/null +++ b/scsi2sd.io/pom.xml @@ -0,0 +1,179 @@ + + + 4.0.0 + + + com.codesrc + scsi2sd-util + 1.0-SNAPSHOT + + + scsi2sd.io + jar + + scsi2sd.io + http://www.codesrc.com + + + Michael McMaster + michael@codesrc.com + + + + + UTF-8 + + + + target + target/classes + ${project.artifactId}-${project.version} + + + + doc + + + src/main/resources + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + 10 + 10 + + + + + + + + org.moditect + moditect-maven-plugin + 1.0.0.Beta2 + + + add-module-infos + generate-resources + + add-module-info + + + + ${project.build.directory}/install + + + + org.hid4java + hid4java + 0.5.0 + + + open module hid4java { + requires jna; + exports org.hid4java; + exports org.hid4java.jna to jna; + exports org.hid4java.event to com.codesrc.scsi2sd.io; + + // native libraries. Use jimage on the jlink'd module file + // to list the relevant directories (which get + // turned into packages) + // (disabled and open'd entire module because the + // hyphen in the directory names causes parse problems) + //exports darwin; + //exports win32-amd64; + //exports linux-amd64; + //exports linux-x86-64; + } + + + + + + net.java.dev.jna + jna + 5.5.0 + + + // Open is required to access the native library resources + open module jna { + exports com.sun.jna; + requires java.logging; + } + + + + + + + + + + + + + + + org.slf4j + slf4j-api + 1.8.0-beta2 + + + + net.java.dev.jna + jna + 5.2.0 + + + + org.hid4java + hid4java + 0.5.0 + + + net.java.dev.jna + jna + + + + + + + junit + junit + 4.12 + test + + + + diff --git a/scsi2sd.io/scsi2sd.io.iml b/scsi2sd.io/scsi2sd.io.iml new file mode 100644 index 0000000..129903a --- /dev/null +++ b/scsi2sd.io/scsi2sd.io.iml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/Commands.java b/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/Commands.java new file mode 100644 index 0000000..edd93de --- /dev/null +++ b/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/Commands.java @@ -0,0 +1,92 @@ +// Copyright (C) 2019 Michael McMaster +// +// This file is part of scsi2sd-util. +// +// scsi2sd-util is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// scsi2sd-util is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with scsi2sd-util. If not, see . + +package com.codesrc.scsi2sd.io; + +public enum Commands { + NONE, // Invalid + + // Command content: + // uint8_t S2S_CFG_PING + // Response: + // S2S_CFG_STATUS + PING, + + // Command content: + // uint8_t S2S_CFG_WRITEFLASH + // uint8_t[256] flashData + // uint8_t flashArray + // uint8_t flashRow + // Response: + // S2S_CFG_STATUS + WRITEFLASH, + + // Command content: + // uint8_t S2S_CFG_READFLASH + // uint8_t flashArray + // uint8_t flashRow + // Response: + // 256 bytes of flash + READFLASH, + + // Command content: + // uint8_t S2S_CFG_REBOOT + // Response: None. + REBOOT, + + // Command content: + // uint8_t S2S_CFG_INFO + // Response: + // uint8_t[16] CSD + // uint8_t[16] CID + SDINFO, + + // Command content: + // uint8_t S2S_CFG_SCSITEST + // Response: + // S2S_CFG_STATUS + // uint8_t result code (0 = passed) + SCSITEST, + + // Command content: + // uint8_t S2S_CFG_DEVINFO + // Response: + // uint16_t protocol version (MSB) + // uint16_t firmware version (MSB) + // uint32_t SD capacity(MSB) + DEVINFO, + + // Command content: + // uint8_t S2S_CFG_SD_WRITE + // uint32_t Sector Number (MSB) + // uint8_t[512] data + // Response: + // S2S_CFG_STATUS + SD_WRITE, + + // Command content: + // uint8_t S2S_CFG_SD_READ + // uint32_t Sector Number (MSB) + // Response: + // 512 bytes of data + SD_READ, + + // Command content: + // uint8_t S2S_CFG_DEBUG + // Response: + DEBUG, +} diff --git a/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/HidPacketProtocol.java b/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/HidPacketProtocol.java new file mode 100644 index 0000000..ef5d68b --- /dev/null +++ b/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/HidPacketProtocol.java @@ -0,0 +1,160 @@ +// Copyright (C) 2019 Michael McMaster +// +// This file is part of scsi2sd-util. +// +// scsi2sd-util is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// scsi2sd-util is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with scsi2sd-util. If not, see . + +package com.codesrc.scsi2sd.io; + +import java.util.Arrays; + +// scsi2sd custom protocol for sending packet data over a USB HID +// interface, +class HidPacketProtocol { + static final int USBHID_LEN = 64; + + // Maximum packet payload length. Must be large enough to support a SD sector + // + sector number + static final int HIDPACKET_MAX_LEN = 520; + + private enum State { IDLE, PARTIAL, COMPLETE }; + private class PacketState { + PacketState() { + this.reset(); + } + + void reset() { + this.state = State.IDLE; + this.chunk = 0; + this.offset = 0; + } + + byte[] buffer = new byte[HIDPACKET_MAX_LEN]; + State state; + int chunk; + int offset; // Current offset into buffer. + } + + private PacketState rx = new PacketState(); + private PacketState tx = new PacketState(); + + // Process data received over USB HID + public void receiveHIDData(byte[] bytes) { + if (bytes.length < 2) { + // Invalid. We need at least a chunk number and payload length. + this.rx.reset(); + return; + } + + int chunk = bytes[0] & 0x7F; + boolean isFinal = (bytes[0] & 0x80) == 0x80; + int payloadLen = uint8ToInt(bytes[1]); + if ((payloadLen > (bytes.length - 2)) || // short packet + (payloadLen + this.rx.offset > this.rx.buffer.length)) { // buffer overflow + this.rx.reset(); + return; + } + + if (chunk == 0) { + // Initial chunk + this.rx.reset(); + System.arraycopy(bytes, 2, this.rx.buffer, 0, payloadLen); + this.rx.offset = payloadLen; + this.rx.state = State.PARTIAL; + } + else if ((this.rx.state == State.PARTIAL) && (chunk == this.rx.chunk + 1)) { + System.arraycopy(bytes, 2, this.rx.buffer, this.rx.offset, payloadLen); + this.rx.offset += payloadLen; + this.rx.chunk++; + } + else if (chunk == this.rx.chunk) { + // duplicated packet. ignore. + } + else { + // invalid. Maybe we missed some data. + this.rx.reset(); + } + + if ((this.rx.state == State.PARTIAL) && isFinal) + { + this.rx.state = State.COMPLETE; + } + } + + public byte[] getPacket() { + if (this.rx.state == State.COMPLETE) { + byte[] result = Arrays.copyOf(this.rx.buffer, this.rx.offset); + this.rx.reset(); + return result; + } else { + return null; + } + } + + public void sendPacket(byte[] packet) + { + if (packet.length <= this.tx.buffer.length) { + this.tx.state = State.PARTIAL; + this.tx.chunk = 0; + this.tx.offset = packet.length; + System.arraycopy(packet, 0, this.tx.buffer, 0, packet.length); + } + else { + this.tx.reset(); + } + } + + public byte[] getHIDBytes() { + if ((this.tx.state != State.PARTIAL) || (this.tx.offset <= 0)) + { + return null; + } + + byte[] hidBuffer = new byte[USBHID_LEN]; + hidBuffer[0] = intToUint8(this.tx.chunk); + this.tx.chunk++; + int payload; + if (this.tx.offset <= USBHID_LEN - 2) + { + hidBuffer[0] = intToUint8(hidBuffer[0] | 0x80); + payload = this.tx.offset; + this.tx.state = State.IDLE; + } + else + { + payload = USBHID_LEN - 2; + } + + this.tx.offset -= payload; + hidBuffer[1] = intToUint8(payload); + System.arraycopy(this.tx.buffer, 0, hidBuffer, 2, payload); + + // memmove + for (int i = payload; i < this.tx.buffer.length; ++i) + { + this.tx.buffer[i - payload] = this.tx.buffer[i]; + } + + return hidBuffer; + } + + + public static int uint8ToInt(byte data) { + return (int) data & 0xFF; + } + + public static byte intToUint8(int data) { + return (byte) (data & 0xFF); + } +} diff --git a/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/HidPacketProtocolDevice.java b/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/HidPacketProtocolDevice.java new file mode 100644 index 0000000..5c1b416 --- /dev/null +++ b/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/HidPacketProtocolDevice.java @@ -0,0 +1,69 @@ +// Copyright (C) 2019 Michael McMaster +// +// This file is part of scsi2sd-util. +// +// scsi2sd-util is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// scsi2sd-util is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with scsi2sd-util. If not, see . + +package com.codesrc.scsi2sd.io; + +import org.hid4java.HidDevice; + +public class HidPacketProtocolDevice { + + private HidDevice hidDevice; + private HidPacketProtocol protocol; + + public HidPacketProtocolDevice(HidDevice hidDevice) { + this.hidDevice = hidDevice; + this.protocol = new HidPacketProtocol(); + } + + public byte[] sendHIDPacket(byte[] cmd, int responseLength) + { + assert(cmd.length <= HidPacketProtocol.HIDPACKET_MAX_LEN); + + this.protocol.sendPacket(cmd); + + byte[] chunk = this.protocol.getHIDBytes(); + while (chunk != null) + { + int result = -1; + for (int retry = 0; retry < 10 && result <= 0; ++retry) + { + result = this.hidDevice.write(chunk, chunk.length, (byte)0x00); + } + + if (result <= 0) + { + throw new RuntimeException("USB HID write failure: " + this.hidDevice.getLastErrorMessage()); + } + chunk = this.protocol.getHIDBytes(); + } + + byte[] readBuffer = new byte[HidPacketProtocol.HIDPACKET_MAX_LEN]; + byte[] resp = this.protocol.getPacket(); + for (int retry = 0; retry < responseLength * 2 && resp == null; ++retry) + { + this.hidDevice.read(readBuffer); // Will block + this.protocol.receiveHIDData(readBuffer); + resp = this.protocol.getPacket(); + } + + if (resp == null) + { + throw new RuntimeException("SCSI2SD USB protocol error"); + } + return resp; + } +} diff --git a/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/SimpleEvent.java b/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/SimpleEvent.java new file mode 100644 index 0000000..0a2569b --- /dev/null +++ b/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/SimpleEvent.java @@ -0,0 +1,39 @@ +// Copyright (C) 2019 Michael McMaster +// +// This file is part of scsi2sd-util. +// +// scsi2sd-util is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// scsi2sd-util is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with scsi2sd-util. If not, see . + +package com.codesrc.scsi2sd.io; + +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.function.Consumer; + +public class SimpleEvent { + private ConcurrentLinkedQueue> observers = new ConcurrentLinkedQueue>(); + + public void subscribe(Consumer observer) { + observers.add(observer); + } + + public void unsubscribe(Consumer observer) { + observers.remove(observer); + } + + public void trigger(EventParamT param) { + for (Consumer observer : observers) { + observer.accept(param); + } + } +} diff --git a/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/UsbDevice.java b/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/UsbDevice.java new file mode 100644 index 0000000..34d2118 --- /dev/null +++ b/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/UsbDevice.java @@ -0,0 +1,41 @@ +// Copyright (C) 2019 Michael McMaster +// +// This file is part of scsi2sd-util. +// +// scsi2sd-util is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// scsi2sd-util is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with scsi2sd-util. If not, see . + +package com.codesrc.scsi2sd.io; + +/** + * Created by michael on 27/08/18. + */ +public interface UsbDevice { + String getHardwareDescription(); + + int getFirmwareVersion(); + String getFirmwareDescription(); + int getSdCapacity(); + + int getMaxDisks(); + byte[] readBoardConfig(); + + byte[] readDiskConfig(int index); + + void saveBoardConfig(byte[] data); + void saveDiskConfig(int index, byte[] data); + void saveCommit(); + + boolean supportsTerminator(); + boolean supportsSynchronous(); +} diff --git a/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/UsbDeviceConnectedEvent.java b/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/UsbDeviceConnectedEvent.java new file mode 100644 index 0000000..dbcc786 --- /dev/null +++ b/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/UsbDeviceConnectedEvent.java @@ -0,0 +1,35 @@ +// Copyright (C) 2019 Michael McMaster +// +// This file is part of scsi2sd-util. +// +// scsi2sd-util is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// scsi2sd-util is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with scsi2sd-util. If not, see . + +package com.codesrc.scsi2sd.io; + +/** + * Created by michael on 27/08/18. + */ +public class UsbDeviceConnectedEvent { + private UsbDevice usbDevice; + + public UsbDeviceConnectedEvent(UsbDevice usbDevice) { + this.usbDevice = usbDevice; + } + + public UsbDevice getUsbDevice() { + return usbDevice; + } +} + + diff --git a/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/UsbDeviceDisconnectedEvent.java b/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/UsbDeviceDisconnectedEvent.java new file mode 100644 index 0000000..e905404 --- /dev/null +++ b/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/UsbDeviceDisconnectedEvent.java @@ -0,0 +1,34 @@ +// Copyright (C) 2019 Michael McMaster +// +// This file is part of scsi2sd-util. +// +// scsi2sd-util is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// scsi2sd-util is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with scsi2sd-util. If not, see . + +package com.codesrc.scsi2sd.io; + + +/** + * Created by michael on 27/08/18. + */ +public class UsbDeviceDisconnectedEvent { + private UsbDevice usbDevice; + + public UsbDeviceDisconnectedEvent(UsbDevice device) { + this.usbDevice = usbDevice; + } + + public UsbDevice getUsbDevice() { + return usbDevice; + } +} diff --git a/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/UsbDeviceFailedEvent.java b/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/UsbDeviceFailedEvent.java new file mode 100644 index 0000000..45e0797 --- /dev/null +++ b/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/UsbDeviceFailedEvent.java @@ -0,0 +1,41 @@ +// Copyright (C) 2019 Michael McMaster +// +// This file is part of scsi2sd-util. +// +// scsi2sd-util is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// scsi2sd-util is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with scsi2sd-util. If not, see . + +package com.codesrc.scsi2sd.io; + +/** + * Created by michael on 27/08/18. + */ +public class UsbDeviceFailedEvent { + private String devicePath; + private String message; + + public UsbDeviceFailedEvent(String devicePath, String message) { + this.devicePath = devicePath; + this.message = message; + } + + public String getUsbDevicePath() { + return devicePath; + } + + public String getMessage() { + return message; + } +} + + diff --git a/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/UsbDeviceManager.java b/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/UsbDeviceManager.java new file mode 100644 index 0000000..725a52f --- /dev/null +++ b/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/UsbDeviceManager.java @@ -0,0 +1,39 @@ +// Copyright (C) 2019 Michael McMaster +// +// This file is part of scsi2sd-util. +// +// scsi2sd-util is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// scsi2sd-util is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with scsi2sd-util. If not, see . + +package com.codesrc.scsi2sd.io; + +import java.util.List; + +/** + * Created by michael on 27/08/18. + */ +public interface UsbDeviceManager { + + List getDevices(); + void shutdown(); + + SimpleEvent getDeviceConnectedEvent(); + SimpleEvent getDeviceDisconnectedEvent(); + + SimpleEvent getDeviceFailedEvent(); + + static UsbDeviceManager getInstance() { + return UsbDeviceManagerImpl.getInstance(); + } + +} diff --git a/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/UsbDeviceManagerImpl.java b/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/UsbDeviceManagerImpl.java new file mode 100755 index 0000000..7ce1af1 --- /dev/null +++ b/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/UsbDeviceManagerImpl.java @@ -0,0 +1,223 @@ +// Copyright (C) 2019 Michael McMaster +// +// This file is part of scsi2sd-util. +// +// scsi2sd-util is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// scsi2sd-util is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with scsi2sd-util. If not, see . + +package com.codesrc.scsi2sd.io; + +import org.hid4java.*; +import org.hid4java.event.HidServicesEvent; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.*; + +class UsbDeviceManagerImpl implements UsbDeviceManager, HidServicesListener { + private static org.slf4j.Logger LOGGER = LoggerFactory.getLogger(UsbDeviceManagerImpl.class.getPackageName()); + private static UsbDeviceManagerImpl INSTANCE = new UsbDeviceManagerImpl(); + + private SimpleEvent deviceConnectedEvent = new SimpleEvent<>(); + private SimpleEvent deviceDisconnectedEvent = new SimpleEvent<>(); + private SimpleEvent deviceFailedEvent = new SimpleEvent<>(); + + private final ScheduledExecutorService scheduler; + + + private HidServices hidServices; + private Map devices = new ConcurrentHashMap<>(); + + public static UsbDeviceManager getInstance() { + return INSTANCE; + } + + public UsbDeviceManagerImpl() + { + scheduler = Executors.newScheduledThreadPool(1); + + LOGGER.debug("Initialising UsbDeviceManagerImpl"); + + HidServicesSpecification spec = new HidServicesSpecification(); + spec.setScanMode(ScanMode.NO_SCAN); + + hidServices = HidManager.getHidServices(spec); + hidServices.getAttachedHidDevices().forEach(d -> this.attached(d)); + hidServices.addHidServicesListener(this); + + scheduler.scheduleWithFixedDelay(() -> this.poll(), 1, 1, TimeUnit.SECONDS); + + LOGGER.debug("Initialised UsbDeviceManagerImpl"); + } + + @Override + public void shutdown() + { + scheduler.shutdown(); + scheduler.shutdownNow(); // Cancel currently executing tasks + + // Wait a while for tasks to respond to being cancelled + try { + if (!scheduler.awaitTermination(60, TimeUnit.SECONDS)) { + LOGGER.error("Scheduler did not terminate"); + } + hidServices.shutdown(); + } + catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + } + } + + @Override + public List getDevices() { + return new ArrayList<>(devices.values()); + } + + private void poll() { + LOGGER.trace("Polling Usb Devices"); + + try { + hidServices.scan(); + } catch (Exception e) + { + LOGGER.error("Failure scanning USB devices", e); + } + + LOGGER.trace("Polling Complete"); + } + + public SimpleEvent getDeviceConnectedEvent() { + return deviceConnectedEvent; + } + + public SimpleEvent getDeviceDisconnectedEvent() { + return deviceDisconnectedEvent; + } + + public SimpleEvent getDeviceFailedEvent() { + return deviceFailedEvent; + } + + @Override + public void hidDeviceAttached(HidServicesEvent hidServicesEvent) { + try { + LOGGER.debug("Usb Device attached {}", hidServicesEvent.getHidDevice().getPath()); + + this.attached(hidServicesEvent.getHidDevice()); + } catch (Exception e) { + LOGGER.error("Exception in UsbDeviceManagerImpl.hidDeviceAttached", e); + } + } + + @Override + public void hidDeviceDetached(HidServicesEvent hidServicesEvent) { + try { + LOGGER.debug("Usb Device detached {}", hidServicesEvent.getHidDevice().getPath()); + this.detached(hidServicesEvent.getHidDevice()); + } catch (Exception e) { + LOGGER.error("Exception in UsbDeviceManagerImpl.hidDeviceDettached", e); + } + + } + + @Override + public void hidFailure(HidServicesEvent hidServicesEvent) { + LOGGER.warn("Usb Device failure {}", hidServicesEvent.toString()); + } + + private void detached(HidDevice hidDevice) { + UsbDevice attachedDevice = devices.get(hidDevice); + if (attachedDevice != null) { + devices.remove(attachedDevice); + + LOGGER.info("Detached SCSI2SD"); + + this.deviceDisconnectedEvent.trigger(new UsbDeviceDisconnectedEvent(attachedDevice)); + } + if (hidDevice.isOpen()) { + hidDevice.close(); + } + } + + private void attached(HidDevice hidDevice) { + if (devices.containsKey(hidDevice)) { + // Why are we getting this again ? + LOGGER.warn("Usb Device duplicate attach {}", hidDevice.getPath()); + + return; + } + + if (!devices.isEmpty()) { + // UI only supports one device for now + return; + } + + UsbDevice attachedDevice = null; + + try { + if (hidDevice.getVendorId() == V6FirmwareUsbDevice.VENDOR_ID && + hidDevice.getProductId() == V6FirmwareUsbDevice.PRODUCT_ID) { + + attachedDevice = new V6FirmwareUsbDevice(hidDevice); + this.devices.put(hidDevice, attachedDevice); + } else if (hidDevice.getVendorId() == V3FirmwareUsbDevice.VENDOR_ID && + hidDevice.getProductId() == V3FirmwareUsbDevice.PRODUCT_ID) { + + if (hidDevice.getInterfaceNumber() == 0 + || hidDevice.getUsagePage() == (short) 0xFF00) { + + // Search for the 2nd interface. + for (HidDevice dbgDev : hidServices.getAttachedHidDevices()) { + if (dbgDev.getVendorId() == V3FirmwareUsbDevice.VENDOR_ID && + dbgDev.getProductId() == V3FirmwareUsbDevice.PRODUCT_ID && + (dbgDev.getInterfaceNumber() == 1 || hidDevice.getUsagePage() == (short) 0xFF01) && + !this.devices.containsKey(dbgDev)) { + attachedDevice = new V3FirmwareUsbDevice(hidDevice, dbgDev); + this.devices.put(dbgDev, attachedDevice); + this.devices.put(hidDevice, attachedDevice); + } + } + + } else if (hidDevice.getInterfaceNumber() == 1 + || hidDevice.getUsagePage() == (short) 0xFF01) { + + // Search for the 1st interface. + for (HidDevice cfgDev : hidServices.getAttachedHidDevices()) { + if (cfgDev.getVendorId() == V3FirmwareUsbDevice.VENDOR_ID && + cfgDev.getProductId() == V3FirmwareUsbDevice.PRODUCT_ID && + (cfgDev.getInterfaceNumber() == 0 || hidDevice.getUsagePage() == (short) 0xFF00) && + !this.devices.containsKey(cfgDev)) { + attachedDevice = new V3FirmwareUsbDevice(cfgDev, hidDevice); + this.devices.put(cfgDev, attachedDevice); + this.devices.put(hidDevice, attachedDevice); + } + } + } + } + + if (attachedDevice != null) { + LOGGER.info("Attached SCSI2SD.\n\tHardware: {}\n\tFirmware: {}", + attachedDevice.getHardwareDescription(), + attachedDevice.getFirmwareDescription()); + + deviceConnectedEvent.trigger((new UsbDeviceConnectedEvent(attachedDevice))); + } + } catch (RuntimeException e) { + deviceFailedEvent.trigger(new UsbDeviceFailedEvent(hidDevice.getPath(), e.getMessage())); + throw e; + } + } +} diff --git a/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/V3FirmwareUsbDevice.java b/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/V3FirmwareUsbDevice.java new file mode 100644 index 0000000..02700f8 --- /dev/null +++ b/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/V3FirmwareUsbDevice.java @@ -0,0 +1,261 @@ +// Copyright (C) 2019 Michael McMaster +// +// This file is part of scsi2sd-util. +// +// scsi2sd-util is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// scsi2sd-util is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with scsi2sd-util. If not, see . + +package com.codesrc.scsi2sd.io; + +import org.hid4java.HidDevice; +import org.hid4java.HidServices; +import org.slf4j.LoggerFactory; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +public class V3FirmwareUsbDevice implements UsbDevice { + private static org.slf4j.Logger LOGGER = LoggerFactory.getLogger(V3FirmwareUsbDevice.class.getPackageName()); + + public static final short VENDOR_ID = 0x04B4; // Cypress + public static final short PRODUCT_ID = 0x1337; // SCSI2SD + + private static final int MAX_SCSI_TARGETS = 4; + private static final int SCSI_CONFIG_ARRAY = 1; + private static final int SCSI_CONFIG_ROWS = 16; + +// 256 bytes data, 32 bytes ECC + private static final int SCSI_CONFIG_ROW_SIZE = 256; + private static final int SCSI_CONFIG_ROW_ECC = 288; + private static final int SCSI_CONFIG_BOARD_ROW = 187; + private static final int SCSI_CONFIG_0_ROW = 188; + private static final int SCSI_CONFIG_1_ROW = 204; + private static final int SCSI_CONFIG_2_ROW = 220; + private static final int SCSI_CONFIG_3_ROW = 236; + private static final int SCSI_CONFIG_4_ROW = 124; + private static final int SCSI_CONFIG_5_ROW = 140; + private static final int SCSI_CONFIG_6_ROW = 156; + + private HidDevice configHidDevice; + private HidDevice debugHidDevice; + private HidPacketProtocolDevice protocolDevice; + + private int firmwareVersion; + private int sDCapacity; + + private byte[] boardConfigSector; + private Map diskConfigSector = new HashMap<>(); + + + public V3FirmwareUsbDevice(HidDevice configHidDevice, HidDevice debugHidDevice) { + this.configHidDevice = configHidDevice; + this.debugHidDevice = debugHidDevice; + this.protocolDevice = new HidPacketProtocolDevice(configHidDevice); + + if (!configHidDevice.isOpen()) + { + if (!configHidDevice.open()) { + LOGGER.error("Failed to open USB device {}\n{}", configHidDevice.getPath(), configHidDevice.getLastErrorMessage()); + throw new RuntimeException("Failed to open USB device."); + } + } + if (!debugHidDevice.isOpen()) + { + if (!debugHidDevice.open()) { + LOGGER.error("Failed to open USB device {}\n{}", debugHidDevice.getPath(), debugHidDevice.getLastErrorMessage()); + throw new RuntimeException("Failed to open USB device."); + } + } + this.readDebugData(); + } + + @Override + public String getHardwareDescription() { + switch (this.configHidDevice.getReleaseNumber()) { + case 0x3001: + return "3.5\" SCSI2SD V3"; + //info.version = "V3.0"; + //info.firmwareName = "SCSI2SD-V3.cyacd"; + case 0x3002: + return "3.5\" SCSI2SD V4.1/V4.2/V5.0 or 2.5\" SCSI2SD for Apple Powerbook"; + //info.version = "V4.1/V4.2/V5.0"; + //info.firmwareName = "SCSI2SD-V4.cyacd"; + case 0x3003: + return "3.5\" SCSI2SD V5.1"; + //info.version = "V5.1"; + //info.firmwareName = "SCSI2SD-V51.cyacd"; + case 0x0055: + return "External DB25 SCSI2SD"; + //info.version = "V5.5"; + //info.firmwareName = "SCSI2SD-V55.cyacd"; + + } + return "Unknown"; + } + + @Override + public int getFirmwareVersion() { + return this.firmwareVersion; + } + + @Override + public String getFirmwareDescription() { + StringBuilder out = new StringBuilder(); + out.append(this.firmwareVersion >> 8) + .append('.') + .append((this.firmwareVersion & 0xF0) >> 4); + + int rev = this.firmwareVersion & 0xF; + if (rev != 0) + { + out.append(".").append(rev); + } + return out.toString(); + } + + @Override + public int getSdCapacity() { + return this.sDCapacity; + } + + @Override + public boolean supportsTerminator() { + switch (this.configHidDevice.getReleaseNumber()) { + case 0x3001: + return false; //"3.5\" SCSI2SD V3"; + case 0x3002: + return false; //"3.5\" SCSI2SD V4.1/V4.2/V5.0 or 2.5\" SCSI2SD for Apple Powerbook"; + case 0x3003: + return true; //"3.5\" SCSI2SD V5.1"; + case 0x0055: + return false; //"External DB25 SCSI2SD"; + } + return false; + } + + @Override + public boolean supportsSynchronous() { + return false; + } + + + private void readDebugData() + { + byte[] data = new byte[64]; + data[0] = 0; // report ID + this.debugHidDevice.read(data, 5000); + + this.firmwareVersion = (data[62] << 8) | data[63]; + this.sDCapacity = + (HidPacketProtocol.uint8ToInt(data[58]) << 24) | + (HidPacketProtocol.uint8ToInt(data[59]) << 16) | + (HidPacketProtocol.uint8ToInt(data[60]) << 8) | + (HidPacketProtocol.uint8ToInt(data[61])); + } + + @Override + public int getMaxDisks() + { + return MAX_SCSI_TARGETS; + } + + @Override + public byte[] readBoardConfig() + { + if (boardConfigSector == null) { + boardConfigSector = readFlash(SCSI_CONFIG_ARRAY, SCSI_CONFIG_BOARD_ROW); + } + + return Arrays.copyOfRange(boardConfigSector, 0, 128); + } + + @Override + public void saveBoardConfig(byte[] data) { + if (boardConfigSector == null) { + boardConfigSector = readFlash(SCSI_CONFIG_ARRAY, SCSI_CONFIG_BOARD_ROW); + } + + System.arraycopy(data, 0, boardConfigSector, 0, 128); + + } + + @Override + public byte[] readDiskConfig(int index) + { + int flashRow = this.getFlashRow(index); + + var data = this.diskConfigSector.computeIfAbsent(flashRow, (k) -> this.readFlash(SCSI_CONFIG_ARRAY, flashRow)); + return Arrays.copyOfRange(data, 0, 128); + } + + @Override + public void saveDiskConfig(int index, byte[] data) { + int flashRow = this.getFlashRow(index); + + var cachedData = this.diskConfigSector.computeIfAbsent(flashRow, (k) -> this.readFlash(SCSI_CONFIG_ARRAY, flashRow)); + System.arraycopy(data, 0, cachedData, 0, 128); + } + + + @Override + public void saveCommit() { + if (boardConfigSector != null) { + writeFlash(SCSI_CONFIG_ARRAY, SCSI_CONFIG_BOARD_ROW, boardConfigSector); + } + + for (int i = 0; i < MAX_SCSI_TARGETS; ++i) { + int flashRow = this.getFlashRow(i); + if (this.diskConfigSector.containsKey(flashRow)) { + var data = this.diskConfigSector.get(flashRow); + writeFlash(SCSI_CONFIG_ARRAY, flashRow, data); + } + } + } + + private byte[] readFlash(int flashArray, int flashRow) + { + byte[] cmd = { + (byte)Commands.READFLASH.ordinal(), + HidPacketProtocol.intToUint8(flashArray), + HidPacketProtocol.intToUint8(flashRow) + }; + + return protocolDevice.sendHIDPacket(cmd, 256); + } + + + private byte[] writeFlash(int flashArray, int flashRow, byte[] data) + { + var cmd = new byte[256+3]; + cmd[0] =(byte)Commands.WRITEFLASH.ordinal(); + cmd[257] = HidPacketProtocol.intToUint8(flashArray); + cmd[258] = HidPacketProtocol.intToUint8(flashRow); + + System.arraycopy(data, 0, cmd, 1, 256); + + return protocolDevice.sendHIDPacket(cmd, 1); + } + + private int getFlashRow(int index) { + int flashRow; + switch (index) { + case 0: flashRow = SCSI_CONFIG_0_ROW; break; + case 1: flashRow = SCSI_CONFIG_1_ROW; break; + case 2: flashRow = SCSI_CONFIG_2_ROW; break; + case 3: flashRow = SCSI_CONFIG_3_ROW; break; + default: throw new IndexOutOfBoundsException(); + } + return flashRow; + } +} diff --git a/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/V6FirmwareUsbDevice.java b/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/V6FirmwareUsbDevice.java new file mode 100644 index 0000000..7177637 --- /dev/null +++ b/scsi2sd.io/src/main/java/com/codesrc/scsi2sd/io/V6FirmwareUsbDevice.java @@ -0,0 +1,223 @@ +// Copyright (C) 2019 Michael McMaster +// +// This file is part of scsi2sd-util. +// +// scsi2sd-util is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// scsi2sd-util is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with scsi2sd-util. If not, see . + +package com.codesrc.scsi2sd.io; + +import org.hid4java.HidDevice; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class V6FirmwareUsbDevice implements UsbDevice { + private static org.slf4j.Logger LOGGER = LoggerFactory.getLogger(V6FirmwareUsbDevice.class.getPackageName()); + + public static final short VENDOR_ID = 0x16D0; // MCS + public static final short PRODUCT_ID = 0x0BD4; // SCSI2SD + + private HidDevice hidDevice; + private HidPacketProtocolDevice protocolDevice; + + private int firmwareVersion; + private int sDCapacity; + + private byte[] configSector0; + private byte[] configSector1; + + public V6FirmwareUsbDevice(HidDevice hidDevice) { + this.hidDevice = hidDevice; + this.protocolDevice = new HidPacketProtocolDevice(hidDevice); + + if (!hidDevice.isOpen()) { + if (!hidDevice.open()) { + LOGGER.error("Failed to open USB device {}\n{}", hidDevice.getPath(), hidDevice.getLastErrorMessage()); + throw new RuntimeException("Failed to open USB device."); + } + } + this.readDebugData(); + } + + @Override + public String getHardwareDescription() { + return "3.5\" SCSI2SD V6"; + } + + @Override + public int getFirmwareVersion() { + return this.firmwareVersion; + } + + @Override + public String getFirmwareDescription() { + StringBuilder out = new StringBuilder(); + out.append(this.firmwareVersion >> 8) + .append('.') + .append((this.firmwareVersion & 0xF0) >> 4); + + int rev = this.firmwareVersion & 0xF; + if (rev != 0) + { + out.append(".").append(rev); + } + return out.toString(); + } + + @Override + public int getSdCapacity() { + return this.sDCapacity; + } + + @Override + public boolean supportsTerminator() { + return true; + } + + @Override + public boolean supportsSynchronous() { + return true; + } + + private void readDebugData() + { + // Newer devices only have a single HID interface, and present + // a command to obtain the data + byte[] cmd = { (byte)Commands.DEVINFO.ordinal(), (byte)0xDE, (byte)0xAD, (byte)0xBE, (byte)0xEF }; + + byte[] data = protocolDevice.sendHIDPacket(cmd, 6); + + this.firmwareVersion = (data[0] << 8) | data[1]; + this.sDCapacity = + (HidPacketProtocol.uint8ToInt(data[2]) << 24) | + (HidPacketProtocol.uint8ToInt(data[3]) << 16) | + (HidPacketProtocol.uint8ToInt(data[4]) << 8) | + (HidPacketProtocol.uint8ToInt(data[5])); + } + + @Override + public int getMaxDisks() + { + return 7; + } + + @Override + public byte[] readBoardConfig() + { + if (configSector0 == null) { + configSector0 = readSector(this.sDCapacity - 2); + } + + return Arrays.copyOfRange(configSector0, 0, 128); + } + + @Override + public void saveBoardConfig(byte[] data) + { + if (configSector0 == null) { + configSector0 = readSector(this.sDCapacity - 2); + } + + System.arraycopy(data, 0, configSector0, 0, 128); + } + + @Override + public void saveDiskConfig(int index, byte[] data) + { + if (configSector0 == null) { + configSector0 = readSector(this.sDCapacity - 2); + } + if (configSector1 == null) { + configSector1 = readSector(this.sDCapacity - 1); + } + + if (index <= 2) { + var from = 128 + (index * 128); + + System.arraycopy(data, 0, configSector0, from, 128); + } + else { + var from = (index - 3) * 128; + System.arraycopy(data, 0, configSector1, from, 128); + } + + } + + @Override + public void saveCommit() { + if (configSector0 != null) { + writeSector(this.sDCapacity - 2, configSector0); + } + if (configSector1 != null) { + writeSector(this.sDCapacity - 1, configSector1); + } + } + + @Override + public byte[] readDiskConfig(int index) + { + if (configSector0 == null) { + configSector0 = readSector(this.sDCapacity - 2); + } + if (configSector1 == null) { + configSector1 = readSector(this.sDCapacity - 1); + } + + if (index < 0 || index > this.getMaxDisks()) { + throw new IllegalArgumentException("Request for invalid disk index"); + } + + if (index <= 2) { + var from = 128 + (index * 128); + return Arrays.copyOfRange(configSector0, from, from + 128); + } + else { + var from = (index - 3) * 128; + return Arrays.copyOfRange(configSector1, from, from + 128); + } + } + + private byte[] readSector(int sector) + { + byte[] cmd = { + (byte)Commands.SD_READ.ordinal(), + HidPacketProtocol.intToUint8(sector >> 24), + HidPacketProtocol.intToUint8(sector >> 16), + HidPacketProtocol.intToUint8(sector >> 8), + HidPacketProtocol.intToUint8(sector), + }; + + return protocolDevice.sendHIDPacket(cmd, 512); + } + + + private byte[] writeSector(int sector, byte[] data) + { + var cmd = new byte[512+5]; + cmd[0] =(byte)Commands.SD_WRITE.ordinal(); + cmd[1] = HidPacketProtocol.intToUint8(sector >> 24); + cmd[2] = HidPacketProtocol.intToUint8(sector >> 16); + cmd[3] = HidPacketProtocol.intToUint8(sector >> 8); + cmd[4] = HidPacketProtocol.intToUint8(sector); + + for (int i = 0; i < data.length; ++i) + { + cmd[5 + i] = data[i]; + } + + return protocolDevice.sendHIDPacket(cmd, 1); + } +} diff --git a/scsi2sd.io/src/main/java/module-info.java b/scsi2sd.io/src/main/java/module-info.java new file mode 100644 index 0000000..25319fd --- /dev/null +++ b/scsi2sd.io/src/main/java/module-info.java @@ -0,0 +1,23 @@ +// Copyright (C) 2019 Michael McMaster +// +// This file is part of scsi2sd-util. +// +// scsi2sd-util is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// scsi2sd-util is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with scsi2sd-util. If not, see . + +module com.codesrc.scsi2sd.io { + requires hid4java; + requires org.slf4j; + + exports com.codesrc.scsi2sd.io; +} diff --git a/scsi2sd.io/src/test/java/com/codesrc/scsi2sd/io/HidPacketProtocolTest.java b/scsi2sd.io/src/test/java/com/codesrc/scsi2sd/io/HidPacketProtocolTest.java new file mode 100644 index 0000000..723b576 --- /dev/null +++ b/scsi2sd.io/src/test/java/com/codesrc/scsi2sd/io/HidPacketProtocolTest.java @@ -0,0 +1,120 @@ +// Copyright (C) 2019 Michael McMaster +// +// This file is part of scsi2sd-util. +// +// scsi2sd-util is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// scsi2sd-util is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with scsi2sd-util. If not, see . + +package com.codesrc.scsi2sd.io; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.Arrays; + +public class HidPacketProtocolTest { + + private void test(HidPacketProtocol protocol, byte[] in) + { + System.out.println(String.format("Testing packet of size %d", in.length)); + protocol.sendPacket(in); + byte[] toSend = protocol.getHIDBytes(); + while (toSend != null) + { + System.out.println("Transferring packet"); + protocol.receiveHIDData(toSend); + toSend = protocol.getHIDBytes(); + } + + byte[] receivedPacket = protocol.getPacket(); + System.out.println(String.format("Received length = %d", receivedPacket.length)); + Assert.assertTrue(receivedPacket != null); + Assert.assertTrue(receivedPacket.length == in.length); + Assert.assertTrue(Arrays.equals(in, receivedPacket)); + } + + @Test + public void testSmall() { + byte[] small = {1,2,3,4,5,6,7,8,9,0}; + HidPacketProtocol protocol = new HidPacketProtocol(); + this.test(protocol, small); + } + + @Test + public void testMed() { + byte[] med = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, + 1, 2, 3, 4 + }; + + HidPacketProtocol protocol = new HidPacketProtocol(); + this.test(protocol, med); + } + + @Test + public void testBig() { + byte[] big = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 + }; + HidPacketProtocol protocol = new HidPacketProtocol(); + this.test(protocol, big); + } + + @Test + public void testHuge() { + byte[] huge = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, + 1, 2, 3, 4, 5, 6 + }; + HidPacketProtocol protocol = new HidPacketProtocol(); + this.test(protocol, huge); + } +} diff --git a/scsi2sd.ui/doc/COPYING b/scsi2sd.ui/doc/COPYING new file mode 100644 index 0000000..9c9006b --- /dev/null +++ b/scsi2sd.ui/doc/COPYING @@ -0,0 +1,80 @@ +SCSI2SD Utility +Copyright (C) 2019 Michael McMaster + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +//////////////////////////////////////////////////////////////////////////////////// + +The additional license terms apply for software distributed with this program: + +//////////////////////////////////////////////////////////////////////////////////// +http://fxexperience.com/controlsfx/ + +/** + * Copyright (c) 2013, 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +//////////////////////////////////////////////////////////////////////////////////// +https://github.com/gary-rowe/hid4java/blob/develop/LICENSE + +The MIT License (MIT) + +Copyright (c) 2014 Gary Rowe + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +//////////////////////////////////////////////////////////////////////////////////// +https://github.com/frexy/glyph-iconset +Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) +http://creativecommons.org/licenses/by-sa/4.0/ +//////////////////////////////////////////////////////////////////////////////////// diff --git a/doc/gplv3.txt b/scsi2sd.ui/doc/gplv3.txt similarity index 100% rename from doc/gplv3.txt rename to scsi2sd.ui/doc/gplv3.txt diff --git a/scsi2sd.ui/pom.xml b/scsi2sd.ui/pom.xml new file mode 100755 index 0000000..6b5a785 --- /dev/null +++ b/scsi2sd.ui/pom.xml @@ -0,0 +1,261 @@ + + + + scsi2sd-util + com.codesrc + 1.0-SNAPSHOT + + 4.0.0 + + scsi2sd.ui + jar + + scsi2sd.ui + http://www.codesrc.com + + + Michael McMaster + michael@codesrc.com + + + + + UTF-8 + + + + + linux + + + unix + Linux + + + + ${project.build.directory}/../../build/linux/javafx-jmods-11.0.2 + + + + mac + + + mac + + + + ${project.build.directory}/../../build/mac/javafx-jmods-11.0.2 + + + + windows + + + windows + + + + ${project.build.directory}/../../build/windows/javafx-jmods-11.0.2 + + + + + + target + target/classes + ${project.artifactId}-${project.version} + + + + doc + + + src/main/resources + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + 10 + 10 + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.1.1 + + + + + com.codesrc.scsi2sd.App + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + 3.1.1 + + + copy-dependencies + package + + copy-dependencies + + + ${project.build.directory}/install + false + true + true + org.openjfx,org.hid4java,net.java.dev.jna + + + + + + + + + org.moditect + moditect-maven-plugin + 1.0.0.Beta2 + + + add-module-infos + generate-resources + + add-module-info + + + + ${project.build.directory}/install + + + + net.sf.jopt-simple + jopt-simple + 6.0-alpha-3 + + + module joptsimple { + exports joptsimple; + } + + + + + + + + + create-runtime-image + package + + create-runtime-image + + + + ${project.build.directory}/scsi2sd.ui-1.0-SNAPSHOT.jar + ${jmodPath} + ${project.build.directory}/../../scsi2sd.io/target/install + ${project.build.directory}/install + + + com.codesrc.scsi2sd.ui + + + + + + scsi2sd-util + com.codesrc.scsi2sd.ui/com.codesrc.scsi2sd.App + + + ${project.build.directory}/jlink-image + + true + + + + + + + + + + + + ${project.groupId} + scsi2sd.io + ${project.version} + + + + + org.openjfx + javafx-base + 11.0.2 + + + + + org.openjfx + javafx-controls + 11.0.2 + + + + org.openjfx + javafx-media + 11.0.2 + + + + org.openjfx + javafx-web + 11.0.2 + + + + + org.openjfx + javafx-fxml + 11.0.2 + + + + org.controlsfx + controlsfx + 11.0.0 + + + + org.slf4j + slf4j-jdk14 + 1.8.0-beta2 + + + + net.sf.jopt-simple + jopt-simple + 6.0-alpha-3 + + + + org.hid4java + hid4java + 0.5.0 + + + diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/App.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/App.java new file mode 100644 index 0000000..7240daf --- /dev/null +++ b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/App.java @@ -0,0 +1,185 @@ +// Copyright (C) 2019 Michael McMaster +// +// This file is part of scsi2sd-util. +// +// scsi2sd-util is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// scsi2sd-util is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with scsi2sd-util. If not, see . + +package com.codesrc.scsi2sd; + +import com.codesrc.scsi2sd.io.UsbDeviceManager; +import com.codesrc.scsi2sd.presentation.FXWindow; +import javafx.application.Application; +import javafx.concurrent.Task; +import javafx.concurrent.Worker; +import javafx.scene.Scene; +import javafx.scene.control.ProgressBar; +import javafx.scene.effect.DropShadow; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.Pane; +import javafx.stage.Stage; +import javafx.stage.StageStyle; +import joptsimple.OptionParser; +import joptsimple.OptionSet; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Arrays; +import java.util.Properties; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class App extends Application { + private static AppOptions appOptions; + + // Need to keep a reference or else weak-reference is garbage-collected and our debug + // log setting gets lost. + private static Logger usbLogger; + + @Override + public void start(Stage stage) throws Exception { + final Stage splashStage = appOptions.isShowSplashScreen() ? showSplash(stage) : null; + + Task initTask = new Task() { + @Override + protected ScreensConfiguration call() throws Exception { + // Spring framework startup + long startMs = System.currentTimeMillis(); + + AppContext appContext = new AppContext(appOptions); + ScreensConfiguration screens = new ScreensConfiguration(appContext); + screens.setPrimaryStage(stage); + long endMs = System.currentTimeMillis(); + long sleepMs = 1200 - (endMs - startMs); + + if (appOptions.isShowSplashScreen() && sleepMs > 0) { + Thread.sleep(sleepMs); + } + return screens; + } + }; + + new Thread(initTask).start(); + initTask.stateProperty().addListener((observableValue, oldState, newState) -> { + if (newState == Worker.State.SUCCEEDED) { + FXWindow main = initTask.getValue().mainWindow(); + main.show(); + if (splashStage != null) { + splashStage.hide(); + } + } + }); + } + + private Stage showSplash(Stage primaryStage) { + ImageView logo = new ImageView(); + logo.setX(1); + logo.setY(1); + logo.setImage(new Image(App.class.getResourceAsStream("/img/splash.png"))); + + Pane splashRoot = new Pane(); + splashRoot.setPrefWidth(logo.getImage().getWidth() + 2); + splashRoot.setPrefHeight(logo.getImage().getHeight() + 2); + splashRoot.getChildren().add(logo); + + ProgressBar pb = new ProgressBar(); + pb.setLayoutX(logo.getImage().getWidth() * 0.33); + pb.setLayoutY(logo.getImage().getHeight() * 0.90); + pb.setPrefWidth(logo.getImage().getWidth() * 0.33); + splashRoot.getChildren().add(pb); + + splashRoot.setEffect(new DropShadow()); + + Scene splash = new Scene(splashRoot); + splash.getStylesheets().add(App.class.getResource("/css/splash.css").toString()); + Stage splashStage = new Stage(); + splashStage.initOwner(primaryStage); + splashStage.setScene(splash); + splashStage.initStyle(StageStyle.UNDECORATED); + splashStage.setTitle("codesrc SCSI2SD"); + + splashStage.show(); + return splashStage; + } + + private static void parseCommandLineOptions(String[] args) { + OptionParser parser = new OptionParser(); + parser.acceptsAll(Arrays.asList("n", "nosplash"), "No splash screen delay"); + parser.acceptsAll(Arrays.asList("h", "?", "help"), "Show help").forHelp(); + parser.acceptsAll(Arrays.asList("d", "debug"), "Debug logging"); + + + OptionSet options = parser.parse(args); + if (options.has("help")) { + try { + parser.printHelpOn(System.out); + System.exit(0); + } catch (IOException e) { + System.exit(1); + } + } + + appOptions = new AppOptions(); + appOptions.setShowSplashScreen(!options.has("n")); + appOptions.setDebugLogging(options.has("d")); + + } + + public static void main(String[] args) { + + Properties appProperties = new Properties(); + try (InputStream is = App.class.getResourceAsStream("/app.properties")) { + appProperties.load(is); + } catch (IOException e) + { + System.err.println("Could not load resources"); + System.exit(1); + } + + System.out.println( + "SCSI2SD Utility v" + appProperties.getProperty("com.codesrc.scsi2sd.app.version") + "\n" + + "Copyright (C) 2019 Michael McMaster \n" + + "\n" + + "This program is free software: you can redistribute it and/or modify\n" + + "it under the terms of the GNU General Public License as published by\n" + + "the Free Software Foundation, either version 3 of the License, or\n" + + "(at your option) any later version.\n\n" + + "This program is distributed in the hope that it will be useful,\n" + + "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" + + "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" + + "GNU General Public License for more details.\n\n" + + "You should have received a copy of the GNU General Public License\n" + + "along with this program. If not, see .\n"); + + parseCommandLineOptions(args); + + if (appOptions.isDebugLogging()) + { + setDebugLevel(); + } + + launch(args); + } + + public static void setDebugLevel() { + Level targetLevel = Level.ALL; + usbLogger = Logger.getLogger(UsbDeviceManager.class.getPackageName()); + usbLogger.setLevel(targetLevel); + for (Handler handler : usbLogger.getHandlers()) { + handler.setLevel(targetLevel); + } + } +} diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/AppContext.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/AppContext.java new file mode 100644 index 0000000..15c9eb2 --- /dev/null +++ b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/AppContext.java @@ -0,0 +1,78 @@ +// Copyright (C) 2019 Michael McMaster +// +// This file is part of scsi2sd-util. +// +// scsi2sd-util is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// scsi2sd-util is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with scsi2sd-util. If not, see . + +package com.codesrc.scsi2sd; + +import com.codesrc.scsi2sd.document.ActiveDocument; +import com.codesrc.scsi2sd.io.UsbDeviceManager; +import com.codesrc.scsi2sd.presentation.Desktop; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +public class AppContext { + private static String appPropertyResource = "app.properties"; + private AppOptions appOptions; + + private Properties appProperties; + private UsbDeviceManager usbDeviceManager; + private Desktop desktop; + private ActiveDocument activeDocument; + + public AppContext(AppOptions appOptions) { + this.appOptions = appOptions; + } + + public synchronized Properties getAppProperties() { + if (this.appProperties == null) { + this.appProperties = new Properties(); + + try (InputStream inputStream = getClass().getResourceAsStream("/app.properties")) { + + if (inputStream == null) + System.err.println("null stream"); + this.appProperties.load(inputStream); + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + return this.appProperties; + } + + public synchronized Desktop getDesktop() { + if (this.desktop == null) { + this.desktop = new Desktop(); + } + return this.desktop; + } + + public synchronized UsbDeviceManager getUsbDeviceManager() { + if (this.usbDeviceManager == null) { + this.usbDeviceManager = UsbDeviceManager.getInstance(); + } + return this.usbDeviceManager; + } + + public synchronized ActiveDocument getActiveDocument() { + if (this.activeDocument == null) { + this.activeDocument = new ActiveDocument(); + } + return this.activeDocument; + } +} diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/AppOptions.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/AppOptions.java new file mode 100644 index 0000000..bfa1eda --- /dev/null +++ b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/AppOptions.java @@ -0,0 +1,45 @@ +// Copyright (C) 2019 Michael McMaster +// +// This file is part of scsi2sd-util. +// +// scsi2sd-util is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// scsi2sd-util is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with scsi2sd-util. If not, see . + +package com.codesrc.scsi2sd; + +/** + * Created by michael on 4/06/17. + */ +public class AppOptions { + + private boolean showSplashScreen = true; + private boolean debugLogging = false; + + public AppOptions() { + + } + + public boolean isShowSplashScreen() { + return showSplashScreen; + } + + public void setShowSplashScreen(boolean showSplashScreen) { + this.showSplashScreen = showSplashScreen; + } + + public boolean isDebugLogging() { return this.debugLogging; } + + public void setDebugLogging(boolean debugLogging) { + this.debugLogging = debugLogging; + } +} diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/ScreensConfiguration.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/ScreensConfiguration.java new file mode 100644 index 0000000..57f77f2 --- /dev/null +++ b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/ScreensConfiguration.java @@ -0,0 +1,92 @@ +// Copyright (C) 2019 Michael McMaster +// +// This file is part of scsi2sd-util. +// +// scsi2sd-util is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// scsi2sd-util is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with scsi2sd-util. If not, see . + +package com.codesrc.scsi2sd; + +import com.codesrc.scsi2sd.presentation.*; +import javafx.stage.Stage; +import javafx.stage.StageStyle; +import javafx.util.Callback; + +import java.lang.reflect.InvocationTargetException; + +/** + * Suitable for use as a FXMLLoader ControllerFactory + */ +public class ScreensConfiguration implements Callback,Object> { + + private AppContext appContext; + private Stage primaryStage; + + public ScreensConfiguration(AppContext appContext) { + this.appContext = appContext; + } + + public void setPrimaryStage(Stage primaryStage) { + this.primaryStage = primaryStage; + } + + @Override + public Object call(Class aClass) { + String methodName = "provide" + aClass.getSimpleName(); + try { + return getClass().getMethod(methodName).invoke(this); + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + e.printStackTrace(); + return null; + } + } + + public FXWindow mainWindow() { + return new FXWindow(this, getClass().getResource("/fx/main.fxml"), MainController.Title, primaryStage, StageStyle.DECORATED); + } + + public FXWindow aboutWindow(FXWindow parentWindow) { + return new FXWindow(this, getClass().getResource("/fx/about.fxml"), "About SCSI2SD", parentWindow, StageStyle.DECORATED); + } + + public MainController provideMainController() { + return new MainController( + this, + this.appContext.getUsbDeviceManager(), + this.appContext.getActiveDocument()); + } + + public AboutController provideAboutController() { + return new AboutController( + this, + this.appContext.getDesktop(), + this.appContext.getAppProperties()); + } + + public ConfigController provideConfigController() { + return new ConfigController(this, this.appContext.getActiveDocument()); + } + + public StatusTabController provideStatusTabController() { + return new StatusTabController(this, this.appContext.getUsbDeviceManager()); + } + + public DebugLogController provideDebugLogController() { + return new DebugLogController(this, this.appContext.getUsbDeviceManager()); + } + + public DiskConfigController provideDiskConfigController() { + return new DiskConfigController(this, this.appContext.getActiveDocument(), this.appContext.getUsbDeviceManager()); + } + +} diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/document/ActiveDocument.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/document/ActiveDocument.java new file mode 100644 index 0000000..2ac0a1d --- /dev/null +++ b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/document/ActiveDocument.java @@ -0,0 +1,145 @@ +// Copyright (C) 2019 Michael McMaster +// +// This file is part of scsi2sd-util. +// +// scsi2sd-util is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// scsi2sd-util is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with scsi2sd-util. If not, see . + +package com.codesrc.scsi2sd.document; + +import com.codesrc.scsi2sd.model.ConfigRoot; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; + +/** + * Created by michael on 28/05/17. + */ +public class ActiveDocument { + + private ConcurrentLinkedQueue observers = new ConcurrentLinkedQueue(); + + private Map isModified = new ConcurrentHashMap(); + private Map isValid = new ConcurrentHashMap(); + + private ConfigRoot originalDocument; + private ConfigRoot activeDocument; + + private String originalDocumentName; + private boolean inLoad = false; + + public void addObserver(DocumentObserver observer) { + observers.add(observer); + } + + public void removeObserver(DocumentObserver observer) { + observers.remove(observer); + } + + public synchronized boolean isModified() { + for (Boolean value : isModified.values()) + { + if (value) { + return true; + } + } + return false; + } + + public void setModified(String source, boolean modified) + { + boolean wasModified; + boolean isNowModified; + + synchronized(this) { + if (inLoad) { + return; + } + + wasModified = this.isModified(); + + isModified.put(source, modified); + + isNowModified = this.isModified(); + } + + if (wasModified != isNowModified) { + for (DocumentObserver observer : this.observers) { + observer.documentModified(this, isNowModified); + } + } + } + + public synchronized boolean isValid() { + for (Boolean value : isValid.values()) + { + if (!value) { + return false; + } + } + return true; + } + + public void setValid(String source, boolean valid) + { + boolean wasValid; + boolean isNowValid; + + synchronized(this) { + wasValid = this.isValid(); + + isValid.put(source, valid); + + isNowValid = this.isValid(); + } + + if (wasValid != isNowValid) { + for (DocumentObserver observer : this.observers) { + observer.documentValid(this, isNowValid); + } + } + } + + public ConfigRoot getOriginalDocument() { + return this.originalDocument; + } + + public String getOriginalDocumentName() { + return this.originalDocumentName; + } + + public ConfigRoot getActiveDocument() { + return this.activeDocument; + } + + public void loadDocument(ConfigRoot document, String originalDocumentName) { + synchronized (this) { + this.originalDocument = new ConfigRoot(document); + this.originalDocumentName = originalDocumentName; + this.activeDocument = document; + this.isModified.clear(); + this.isValid.clear(); + this.inLoad = true; + } + + for (DocumentObserver observer : this.observers) { + observer.documentLoaded(this); + } + + synchronized (this) { + this.inLoad = false; + } + } +} + diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/document/DocumentObserver.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/document/DocumentObserver.java new file mode 100644 index 0000000..44c1067 --- /dev/null +++ b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/document/DocumentObserver.java @@ -0,0 +1,27 @@ +// Copyright (C) 2019 Michael McMaster +// +// This file is part of scsi2sd-util. +// +// scsi2sd-util is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// scsi2sd-util is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with scsi2sd-util. If not, see . + +package com.codesrc.scsi2sd.document; + +/** + * Created by michael on 28/05/17. + */ +public interface DocumentObserver { + void documentLoaded(ActiveDocument document); + void documentModified(ActiveDocument document, boolean isModified); + void documentValid(ActiveDocument document, boolean isValid); +} diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/BitEnum.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/BitEnum.java new file mode 100644 index 0000000..8d57d73 --- /dev/null +++ b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/BitEnum.java @@ -0,0 +1,22 @@ +// Copyright (C) 2019 Michael McMaster +// +// This file is part of scsi2sd-util. +// +// scsi2sd-util is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// scsi2sd-util is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with scsi2sd-util. If not, see . + +package com.codesrc.scsi2sd.model; + +public interface BitEnum { + int getBit(); +} diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/BitUtil.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/BitUtil.java new file mode 100644 index 0000000..d9c7fa2 --- /dev/null +++ b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/BitUtil.java @@ -0,0 +1,61 @@ +// Copyright (C) 2019 Michael McMaster +// +// This file is part of scsi2sd-util. +// +// scsi2sd-util is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// scsi2sd-util is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with scsi2sd-util. If not, see . + +package com.codesrc.scsi2sd.model; + +import java.util.Set; + +public class BitUtil +{ + + public static long getMask(Set flags) + { + long result = 0; + for (T item : flags) { + result |= item.getBit(); + } + return result; + } + + public static long letohl(byte[] in, int offset) + { + return (((int) in[offset + 3] & 0xFF) << 24) | + (((int) in[offset + 2] & 0xFF) << 16) | + (((int) in[offset + 1] & 0xFF) << 8) | + ((int) in[offset] & 0xFF); + } + + public static int letohs(byte[] in, int offset) { + return (((int) in[offset + 1] & 0xFF) << 8) | + ((int) in[offset] & 0xFF); + } + + + public static int uint8ToInt(byte data) { + return (int) data & 0xFF; + } + + public static byte intToUint8(int data) + { + return (byte) (data & 0xFF); + } + + public static byte longToUint8(long data) + { + return (byte) (data & 0xFF); + } +} diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/BoardConfig.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/BoardConfig.java new file mode 100644 index 0000000..cb25a3f --- /dev/null +++ b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/BoardConfig.java @@ -0,0 +1,319 @@ +// Copyright (C) 2019 Michael McMaster +// +// This file is part of scsi2sd-util. +// +// scsi2sd-util is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// scsi2sd-util is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with scsi2sd-util. If not, see . + +package com.codesrc.scsi2sd.model; + +import org.w3c.dom.Node; + +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +public class BoardConfig { + public static final int LENGTH = 128; + + private Set flags; + + private int startupDelay; // Seconds + + private int selectionDelay; // milliseconds + + private Set flags6; + + private Speed scsiSpeed; + + public BoardConfig(BoardConfig other) + { + this.flags = new HashSet<>(other.flags); + this.startupDelay = other.startupDelay; + this.selectionDelay = other.selectionDelay; + this.flags6 = new HashSet<>(other.flags6); + this.scsiSpeed = other.scsiSpeed; + } + + public BoardConfig() { + this.flags = new HashSet<>(); + this.flags6 = new HashSet<>(); + this.flags6.add(Flag6.S2S_CFG_ENABLE_TERMINATOR); + + this.startupDelay = 0; + this.selectionDelay = 255; // "Auto". + this.scsiSpeed = Speed.S2S_CFG_SPEED_NoLimit; + } + + + public BoardConfig(byte[] in) { + if (in.length != LENGTH) { + throw new IllegalArgumentException("BoardConfig size incorrect"); + } + + flags = Flag.fromBitmask(in[4]); + startupDelay = (int)in[5] & 0xFF; + selectionDelay = (int)in[6] & 0xFF; + flags6 = Flag6.fromBitmask(in[7]); + scsiSpeed = Speed.fromBits(in[8]); + } + + public byte[] asBytes() { + byte[] out = new byte[LENGTH]; + + out[0] = 'B'; out[1] = 'C'; out[2] = 'F'; out[3] = 'G'; + + out[4] = BitUtil.longToUint8(BitUtil.getMask(flags)); + out[5] = BitUtil.intToUint8(startupDelay); + out[6] = BitUtil.intToUint8(selectionDelay); + out[7] = BitUtil.longToUint8(BitUtil.getMask(flags6)); + out[8] = BitUtil.intToUint8(scsiSpeed.getValue()); + return out; + } + + public Set getFlags() { + return flags; + } + + public void setFlags(Set flags) { + this.flags = flags; + } + + public void setFlag(Flag flag, boolean value) + { + if (value) + { + this.flags.add(flag); + } + else + { + this.flags.remove(flag); + } + } + + public void setFlag6(Flag6 flag, boolean value) + { + if (value) + { + this.flags6.add(flag); + } + else + { + this.flags6.remove(flag); + } + } + + public int getStartupDelay() { + return startupDelay; + } + + public void setStartupDelay(int startupDelay) { + this.startupDelay = startupDelay; + } + + public int getSelectionDelay() { + return selectionDelay; + } + + public void setSelectionDelay(int selectionDelay) { + this.selectionDelay = selectionDelay; + } + + public Set getFlags6() { + return flags6; + } + + public void setFlag6(Set flags6) { + this.flags6 = flags6; + } + + public Speed getScsiSpeed() { + return scsiSpeed; + } + + public void setScsiSpeed(Speed scsiSpeed) { + this.scsiSpeed = scsiSpeed; + } + + public boolean isEnableTerminator() { + return flags6.contains(Flag6.S2S_CFG_ENABLE_TERMINATOR); + } + + public void setEnableTerminator(boolean enableTerminator) { + this.flags6.add(Flag6.S2S_CFG_ENABLE_TERMINATOR); + } + + public BoardConfig(Node node) { + var childNodes = node.getChildNodes(); + var children = IntStream.range(0, childNodes.getLength()) + .mapToObj(childNodes::item) + .filter(n -> n.getNodeType() == Node.ELEMENT_NODE) + .filter(n -> n.hasChildNodes() && + n.getFirstChild().getNodeType() == Node.TEXT_NODE) + .collect(Collectors.toMap(n -> n.getNodeName(), n -> n.getFirstChild().getNodeValue().trim())); + + this.flags = new HashSet<>(); + + if (children.getOrDefault("unitAttention", "false").equalsIgnoreCase("true")) { + flags.add(Flag.S2S_CFG_ENABLE_UNIT_ATTENTION); + } + + if (children.getOrDefault("parity", "false").equalsIgnoreCase("true")) { + + flags.add(Flag.S2S_CFG_ENABLE_PARITY); + } + + if (children.getOrDefault("enableScsi2", "false").equalsIgnoreCase("true")) { + flags.add(Flag.S2S_CFG_ENABLE_SCSI2); + } + + if (children.getOrDefault("selLatch", "false").equalsIgnoreCase("true")) { + flags.add(Flag.S2S_CFG_ENABLE_SEL_LATCH); + } + + if (children.getOrDefault("mapLunsToIds", "false").equalsIgnoreCase("true")) { + flags.add(Flag.S2S_CFG_MAP_LUNS_TO_IDS); + } + + this.flags6 = new HashSet<>(); + if (children.getOrDefault("enableTerminator", "false").equalsIgnoreCase("true")) { + this.flags6.add(Flag6.S2S_CFG_ENABLE_TERMINATOR); + } + + this.selectionDelay = 255; + var selDelayText = children.getOrDefault("selectionDelay", "255"); + try { + var intValue = Integer.valueOf(selDelayText); + if (intValue >= 0 && intValue <= 255) { + this.selectionDelay = intValue; + } + } catch (NumberFormatException e) { + // Ignore rubbish data. + } + + this.startupDelay = 0; + var startDelayText = children.getOrDefault("startupDelay", "0"); + try { + var intValue = Integer.valueOf(startDelayText); + if (intValue >= 0 && intValue <= 255) { + this.startupDelay = intValue; + } + } catch (NumberFormatException e) { + // Ignore rubbish data. + } + + this.scsiSpeed = Speed.S2S_CFG_SPEED_NoLimit; + var speedNodeText = children.getOrDefault("scsiSpeed", "0"); + try { + var intValue = Integer.valueOf(speedNodeText); + switch (intValue) { + case 0: this.scsiSpeed = Speed.S2S_CFG_SPEED_NoLimit; break; + case 1: this.scsiSpeed = Speed.S2S_CFG_SPEED_ASYNC_15; break; + case 2: this.scsiSpeed = Speed.S2S_CFG_SPEED_ASYNC_33; break; + case 3: this.scsiSpeed = Speed.S2S_CFG_SPEED_ASYNC_50; break; + case 4: this.scsiSpeed = Speed.S2S_CFG_SPEED_SYNC_5; break; + case 5: this.scsiSpeed = Speed.S2S_CFG_SPEED_SYNC_10; break; + case 6: this.scsiSpeed = Speed.S2S_CFG_SPEED_TURBO; break; + default: this.scsiSpeed = Speed.S2S_CFG_SPEED_NoLimit; break; + } + } catch (NumberFormatException e) { + // Ignore rubbish data. + } + } + + public String toXML() + { + var out = new StringBuilder(); + + out.append("\n") + .append(" \n") + .append( " ") + .append(this.flags6.contains(Flag6.S2S_CFG_ENABLE_TERMINATOR) ? "true" : "false") + .append( "\n") + + .append(" ") + .append(this.flags.contains(Flag.S2S_CFG_ENABLE_UNIT_ATTENTION) ? "true" : "false") + .append("\n") + + .append(" ") + .append(this.flags.contains(Flag.S2S_CFG_ENABLE_PARITY) ? "true" : "false") + .append("\n") + + .append(" \n") + .append(" ") + .append(this.flags.contains(Flag.S2S_CFG_ENABLE_SCSI2) ? "true" : "false") + .append("\n") + + .append(" \n") + .append(" ") + .append(this.flags.contains(Flag.S2S_CFG_ENABLE_SEL_LATCH) ? "true" : "false") + .append("\n") + + + .append(" \n") + .append(" ") + .append(this.flags.contains(Flag.S2S_CFG_MAP_LUNS_TO_IDS) ? "true" : "false") + .append("\n") + + + .append(" \n") + .append(" ").append(this.selectionDelay).append("\n") + + .append(" \n") + .append(" ").append(this.startupDelay).append("\n") + + .append(" \n") + .append(" ").append(this.scsiSpeed.getValue()).append("\n") + .append("\n"); + + return out.toString(); + } +} diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/ConfigRoot.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/ConfigRoot.java new file mode 100644 index 0000000..874d116 --- /dev/null +++ b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/ConfigRoot.java @@ -0,0 +1,54 @@ +// Copyright (C) 2019 Michael McMaster +// +// This file is part of scsi2sd-util. +// +// scsi2sd-util is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// scsi2sd-util is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with scsi2sd-util. If not, see . + +package com.codesrc.scsi2sd.model; + +public class ConfigRoot { + private BoardConfig boardConfig; + private DiskList diskList; + + public ConfigRoot() { + } + + // Deep copy ctor + public ConfigRoot(ConfigRoot other) { + if (other.boardConfig != null) { + this.boardConfig = new BoardConfig(other.boardConfig); + } + + if (other.diskList != null) { + this.diskList = new DiskList(other.diskList); + } + } + + public BoardConfig getBoardConfig() { + return boardConfig; + } + + public void setBoardConfig(BoardConfig boardConfig) { + this.boardConfig = boardConfig; + } + + + public DiskList getDiskList() { + return diskList; + } + + public void setDiskList(DiskList diskList) { + this.diskList = diskList; + } +} diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/DeviceTypeEnum.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/DeviceTypeEnum.java new file mode 100644 index 0000000..dd76ccd --- /dev/null +++ b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/DeviceTypeEnum.java @@ -0,0 +1,56 @@ +// Copyright (C) 2019 Michael McMaster +// +// This file is part of scsi2sd-util. +// +// scsi2sd-util is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// scsi2sd-util is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with scsi2sd-util. If not, see . + +package com.codesrc.scsi2sd.model; + +import java.util.*; + +public enum DeviceTypeEnum { + S2S_CFG_FIXED(0, "Hard Drive"), + S2S_CFG_REMOVEABLE(1, "Removable"), + S2S_CFG_OPTICAL(2, "CDROM"), + S2S_CFG_FLOPPY_14MB(3, "3.5\" Floppy"), + S2S_CFG_MO(4, "Magneto Optical"), + S2S_CFG_SEQUENTIAL(5, "Tape"); + + static DeviceTypeEnum fromValue(byte value) { + for (DeviceTypeEnum deviceType : EnumSet.allOf(DeviceTypeEnum.class)) { + if (deviceType.value == value) + { + return deviceType; + } + } + return S2S_CFG_FIXED; + } + + + private int value; + private String desc; + + DeviceTypeEnum(int value, String desc) { + this.value = value; + this.desc = desc; + } + + public int getValue() { return this.value; } + + @Override + public String toString() + { + return this.desc; + } +} diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/DiskConfig.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/DiskConfig.java new file mode 100644 index 0000000..83b4a8e --- /dev/null +++ b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/DiskConfig.java @@ -0,0 +1,596 @@ +// Copyright (C) 2019 Michael McMaster +// +// This file is part of scsi2sd-util. +// +// scsi2sd-util is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// scsi2sd-util is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with scsi2sd-util. If not, see . + +package com.codesrc.scsi2sd.model; + +import org.w3c.dom.Node; + +import java.nio.charset.StandardCharsets; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +public class DiskConfig { + public static final int LENGTH = 128; + public static final int SD_SECTOR_SIZE = 512; + + public enum Type { + RESERVED, + EMPTY, + DISK + } + + private boolean active; + private int scsiId; + private Type type; + private long sdSectorStart; + private long scsiSectors; + private int bytesPerSector; + private DeviceTypeEnum deviceType; + private int deviceTypeModifier; + + // Max allowed by legacy IBM-PC bios is 6 bits (63) + private int sectorsPerTrack; + + // MS-Dos up to 7.10 will crash on >= 256 heads. + private int headsPerCylinder; + + private String vendor; //[8] + private String prodId; //[16]; + private String revision; //[4]; + private String serial; //[16]; + + private Set quirks; + + public DiskConfig( + Type type, + long sdSectorStart, + long sdSectors) { + + this(type, sdSectorStart, sdSectors, SD_SECTOR_SIZE); + } + + public DiskConfig(DiskConfig other) { + this.active = other.active; + this.scsiId = other.scsiId; + this.type = other.type; + this.sdSectorStart = other.sdSectorStart; + this.scsiSectors = other.scsiSectors; + this.bytesPerSector = other.bytesPerSector; + this.deviceType = other.deviceType; + this.deviceTypeModifier = other.deviceTypeModifier; + this.sectorsPerTrack = other.sectorsPerTrack; + this.headsPerCylinder = other.headsPerCylinder; + this.vendor = other.vendor; + this.prodId = other.prodId; + this.revision = other.revision; + this.serial = other.serial; + this.quirks = new HashSet<>(other.quirks); + } + + public DiskConfig( + Type type, + long sdSectorStart, + long scsiSectors, + int bytesPerSector) { + + this.scsiId = 0; + this.active = true; + this.type = type; + this.sdSectorStart = sdSectorStart; + this.scsiSectors = scsiSectors; + this.bytesPerSector = bytesPerSector; + this.deviceType = DeviceTypeEnum.S2S_CFG_FIXED; + this.deviceTypeModifier = 0; + + this.sectorsPerTrack = 63; + this.headsPerCylinder = 255; + + this.vendor = this.pad("codesrc", 8); + this.prodId = this.pad("SCSI2SD", 16); + this.revision = this.pad("1.0", 4); + this.serial = this.pad("12345678", 16); + + this.quirks = new HashSet(); + } + + public DiskConfig(byte[] in) { + if (in.length != LENGTH) { + throw new IllegalArgumentException("DiskConfig size incorrect"); + } + + this.active = (in[0] & 0x80) != 0; + this.scsiId = in[0] & 0x7; + this.deviceType = DeviceTypeEnum.fromValue(in[1]); + this.deviceTypeModifier = BitUtil.uint8ToInt(in[3]); + this.type = Type.DISK; + this.sdSectorStart = BitUtil.letohl(in, 4); + this.scsiSectors = BitUtil.letohl(in, 8); + this.bytesPerSector = BitUtil.letohs(in, 12); + + this.sectorsPerTrack = BitUtil.letohs(in, 14); + this.headsPerCylinder = BitUtil.letohs(in, 16); + + this.vendor = new String(in, 18, 8, StandardCharsets.US_ASCII); + this.prodId = new String(in, 26, 16, StandardCharsets.US_ASCII); + this.revision = new String(in, 42, 4, StandardCharsets.US_ASCII); + this.serial = new String(in, 46, 16, StandardCharsets.US_ASCII); + + this.quirks = Quirks.fromBitmask(BitUtil.letohs(in, 52)); + } + + + public byte[] asBytes() { + byte[] out = new byte[LENGTH]; + + out[0] = BitUtil.intToUint8((this.scsiId & 0x07) | (this.active ? 0 : 0x80)); + + out[1] = BitUtil.intToUint8(this.deviceType.getValue()); + out[2] = 0; // flagsDeprecated + out[3] = BitUtil.intToUint8(this.deviceTypeModifier); + + out[4] = BitUtil.longToUint8(this.sdSectorStart); + out[5] = BitUtil.longToUint8(this.sdSectorStart >> 8); + out[6] = BitUtil.longToUint8(this.sdSectorStart >> 16); + out[7] = BitUtil.longToUint8(this.sdSectorStart >> 24); + + out[8] = BitUtil.longToUint8(this.scsiSectors); + out[9] = BitUtil.longToUint8(this.scsiSectors >> 8); + out[10] = BitUtil.longToUint8(this.scsiSectors >> 16); + out[11] = BitUtil.longToUint8(this.scsiSectors >> 24); + + out[12] = BitUtil.intToUint8(this.bytesPerSector); + out[13] = BitUtil.intToUint8(this.bytesPerSector >> 8); + + out[14] = BitUtil.intToUint8(this.sectorsPerTrack); + out[15] = BitUtil.intToUint8(this.sectorsPerTrack >> 8); + + out[16] = BitUtil.intToUint8(this.headsPerCylinder); + out[17] = BitUtil.intToUint8(this.headsPerCylinder >> 8); + + System.arraycopy(this.vendor.getBytes(StandardCharsets.US_ASCII), 0, out, 18, 8); + System.arraycopy(this.prodId.getBytes(StandardCharsets.US_ASCII), 0, out, 26, 16); + System.arraycopy(this.revision.getBytes(StandardCharsets.US_ASCII), 0, out, 42, 4); + System.arraycopy(this.serial.getBytes(StandardCharsets.US_ASCII), 0, out, 46, 16); + + var quirksMask = BitUtil.getMask(this.quirks); + out[52] = BitUtil.longToUint8(quirksMask); + out[53] = BitUtil.longToUint8(quirksMask >> 8); + + return out; + } + + public int getScsiId() + { + return this.scsiId; + } + + public void setScsiId(int value) + { + this.scsiId = value & 0x7; + } + + public Type getType() { + return this.type; + } + + public boolean isActive() { + return active; + } + + public void setActive(boolean active) { + this.active = active; + } + + public DeviceTypeEnum getDeviceType() + { + return this.deviceType; + } + + public void setDeviceType(DeviceTypeEnum deviceType) + { + this.deviceType = deviceType; + } + + public int getDeviceTypeModifier() { + return this.deviceTypeModifier; + } + + public void setDeviceTypeModifier(int deviceTypeModifier) { + this.deviceTypeModifier = deviceTypeModifier; + } + + public long getSdSectorStart() { + return this.sdSectorStart; + } + + public long getSdSectorEnd() { + return this.sdSectorStart + this.getSdSectors(); + } + + public void setSdSectorStart(long value) { + this.sdSectorStart = value; + } + + public long getScsiSectors() { + return this.scsiSectors; + } + + public void setScsiSectors(long value) { + this.scsiSectors = value; + } + + public long getSdSectors() { + return ((this.scsiSectors * this.bytesPerSector) + (SD_SECTOR_SIZE - 1)) / SD_SECTOR_SIZE; + } + + public int getBytesPerSector() { + return this.bytesPerSector; + } + + public void setBytesPerSector(int value) { + this.bytesPerSector = value; + } + + public Quirks getQuirksMode() { + if (this.quirks.isEmpty()) { + return Quirks.S2S_CFG_QUIRKS_NONE; + } else { + return this.quirks.iterator().next(); + } + } + + public void setQuirksMode(Quirks quirksMode) { + this.quirks.clear(); + if (quirksMode != Quirks.S2S_CFG_QUIRKS_NONE) { + this.quirks.add(quirksMode); + } + } + + public DiskUnit getBestUnit() { + var scsiSize = this.scsiSectors * this.bytesPerSector; + + double base; + String suffix; + if (scsiSize >= 0.9 * 1024*1024*1024) { + return DiskUnit.GB; + } + else if (scsiSize >= 0.9 * 1024*1024) { + return DiskUnit.MB; + } + else + { + return DiskUnit.SECTOR; + } + } + + public String getSizeString(DiskUnit unit, boolean showSuffix) { + var scsiSize = this.scsiSectors * this.bytesPerSector; + + double base; + String suffix; + if (unit == DiskUnit.GB) { + base = scsiSize / (1024.0*1024*1024); + suffix = "GB"; + } + else if (unit == DiskUnit.MB) { + base = scsiSize / (1024.0*1024); + suffix = "MB"; + } + else { + base = this.scsiSectors; + suffix = "Sectors"; + } + + if (showSuffix) { + return String.format("%.2f %s", base, suffix); + } + else if (unit != DiskUnit.SECTOR) + { + return String.format("%.2f", base); + } + else + { + return String.format("%d", (long)base); + } + } + + public String getVendor() { + return vendor; + } + + public void setVendor(String vendor) { + this.vendor = this.pad(vendor, 8); + } + + public String getProdId() { + return prodId; + } + + public void setProdId(String prodId) { + this.prodId = this.pad(prodId, 16); + } + + public String getRevision() { + return revision; + } + + public void setRevision(String revision) { + this.revision = this.pad(revision, 4); + } + + public String getSerial() { + return serial; + } + + public void setSerial(String serial) { + this.serial = this.pad(serial, 16); + } + + public int getSectorsPerTrack() { + return sectorsPerTrack; + } + + public void setSectorsPerTrack(int sectorsPerTrack) { + this.sectorsPerTrack = sectorsPerTrack; + } + + public int getHeadsPerCylinder() { + return headsPerCylinder; + } + + public void setHeadsPerCylinder(int headsPerCylinder) { + this.headsPerCylinder = headsPerCylinder; + } + + private String pad(String input, int count) + { + if (input.length() >= count) + { + return input.substring(0, count); + } + else + { + return " ".repeat(count - input.length()) + input; + } + } + + public DiskConfig(Node node) { + this.type = Type.DISK; + + var childNodes = node.getChildNodes(); + var children = IntStream.range(0, childNodes.getLength()) + .mapToObj(childNodes::item) + .filter(n -> n.getNodeType() == Node.ELEMENT_NODE) + .filter(n -> n.hasChildNodes() && + n.getFirstChild().getNodeType() == Node.TEXT_NODE) + .collect(Collectors.toMap(n -> n.getNodeName(), n -> n.getFirstChild().getNodeValue().trim())); + + this.scsiId = 0; + var idAttr = node.getAttributes().getNamedItem("id"); + if (idAttr != null) { + try { + var id = Integer.valueOf(idAttr.getNodeValue()); + if (id >= 0 && id <= 7) { + this.scsiId = id; + } + } catch (NumberFormatException e) { + // Ignore + } + } + + if (children.getOrDefault("enabled", "true").equalsIgnoreCase("true")) { + this.active = true; + } else { + this.active = false; + } + + this.quirks = new HashSet<>(); + switch (children.getOrDefault("quirks", "").toLowerCase()) { + case "apple": + this.quirks.add(Quirks.S2S_CFG_QUIRKS_APPLE); + break; + case "omti": + this.quirks.add(Quirks.S2S_CFG_QUIRKS_OMTI); + break; + case "xebec": + this.quirks.add(Quirks.S2S_CFG_QUIRKS_XEBEC); + break; + case "vms": + this.quirks.add(Quirks.S2S_CFG_QUIRKS_VMS); + break; + } + + this.deviceType = DeviceTypeEnum.S2S_CFG_FIXED; + var devTypeText = children.getOrDefault("deviceType", "0"); + try { + var radix = 10; + if (devTypeText.toLowerCase().startsWith("0x")) { + radix = 16; + devTypeText = devTypeText.substring(2); + } + var byteValue = Byte.valueOf(devTypeText, radix); + this.deviceType = DeviceTypeEnum.fromValue(byteValue); + } catch (NumberFormatException e) { + } + + this.deviceTypeModifier = 0; + var devTypeModText = children.getOrDefault("deviceTypeModifier", "0"); + try { + var radix = 10; + if (devTypeModText.toLowerCase().startsWith("0x")) { + radix = 16; + devTypeModText = devTypeModText.substring(2); + } + this.deviceTypeModifier = Byte.valueOf(devTypeModText, radix); + } catch (NumberFormatException e) { + } + + + this.sdSectorStart = 0; + var sdSectorStartText = children.getOrDefault("sdSectorStart", "0"); + try { + var longValue = Long.valueOf(sdSectorStartText); + if (longValue >= 0) { + this.sdSectorStart = longValue; + } + } catch (NumberFormatException e) { + } + + + this.scsiSectors = 1; + var scsiSectorsText = children.getOrDefault("scsiSectors", "1"); + try { + var longValue = Long.valueOf(scsiSectorsText); + if (longValue >= 0) { + this.scsiSectors = longValue; + } + } catch (NumberFormatException e) { + } + + this.bytesPerSector = 512; + var bytesPerSectorText = children.getOrDefault("bytesPerSector", "512"); + try { + var intValue = Integer.valueOf(bytesPerSectorText); + if (intValue > 0 && intValue <= 65535) { + this.bytesPerSector = intValue; + } + } catch (NumberFormatException e) { + } + + this.sectorsPerTrack = 63; + var sectorsPerTrackText = children.getOrDefault("sectorsPerTrack", "63"); + try { + var intValue = Integer.valueOf(sectorsPerTrackText); + if (intValue > 0 && intValue <= 65535) { + this.sectorsPerTrack = intValue; + } + } catch (NumberFormatException e) { + } + + this.headsPerCylinder = 255; + var headsPerCylinderText = children.getOrDefault("headsPerCylinder", "255"); + try { + var intValue = Integer.valueOf(headsPerCylinderText); + if (intValue > 0 && intValue <= 65535) { + this.headsPerCylinder = intValue; + } + } catch (NumberFormatException e) { + } + + this.vendor = this.pad(children.getOrDefault("vendor", "codesrc"), 8); + this.prodId = this.pad(children.getOrDefault("prodId", "SCSI2SD"), 16); + this.revision = this.pad(children.getOrDefault("revision", "1.0"), 4); + this.serial = this.pad(children.getOrDefault("serial", "12345678"), 16); + } + + public String toXML() { + var out = new StringBuilder(); + + out.append("\n") + .append(" ") + .append(this.active ? "true" : "false") + .append("\n") + + .append("\n") + .append(" \n") + .append(" "); + + if (this.quirks.contains(Quirks.S2S_CFG_QUIRKS_APPLE)) { + out.append("apple"); + } + else if (this.quirks.contains(Quirks.S2S_CFG_QUIRKS_OMTI)) { + out.append("omti"); + } + else if (this.quirks.contains(Quirks.S2S_CFG_QUIRKS_XEBEC)) { + out.append("xebec"); + } + else if (this.quirks.contains(Quirks.S2S_CFG_QUIRKS_VMS)) { + out.append("vms"); + } + + out.append("\n") + .append("\n\n") + .append(" \n") + .append(" 0x") + .append(Integer.toString(this.deviceType.getValue(), 16)) + .append("\n") + + .append("\n\n") + .append(" \n") + .append(" 0x") + .append(Integer.toString(this.deviceTypeModifier, 16)) + .append("\n") + + .append("\n\n") + .append(" \n") + .append(" ").append(this.sdSectorStart).append("\n") + .append("\n\n") + .append(" \n") + .append("\n") + .append(" ").append(this.scsiSectors).append("\n") + .append(" ").append(this.bytesPerSector).append("\n") + .append(" ").append(this.sectorsPerTrack).append("\n") + .append(" ").append(this.headsPerCylinder).append("\n") + .append("\n\n") + .append(" \n") + .append("\n") + .append(" \n") + .append(" \n") + .append(" ").append(this.vendor).append("\n") + .append("\n") + .append(" \n") + .append(" \n") + .append(" ").append(this.prodId).append("\n") + .append("\n") + .append(" \n") + .append(" \n") + .append(" ").append(this.revision).append("\n") + .append("\n") + .append(" \n") + .append(" ").append(this.serial).append("\n") + .append("\n") + .append("\n"); + + return out.toString(); + } +} diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/DiskList.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/DiskList.java new file mode 100644 index 0000000..9590e05 --- /dev/null +++ b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/DiskList.java @@ -0,0 +1,252 @@ +// Copyright (C) 2019 Michael McMaster +// +// This file is part of scsi2sd-util. +// +// scsi2sd-util is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// scsi2sd-util is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with scsi2sd-util. If not, see . + +package com.codesrc.scsi2sd.model; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +// List of disk configurations sorted by SD Starting Sector. +public class DiskList { + + public static final long reservedSize = 1024*1024 / 512; // oneMb + + private long sdCardSize; + private LinkedList disks; + + // Deep copy ctor + public DiskList(DiskList other) { + this.sdCardSize = other.sdCardSize; + this.disks = new LinkedList<>(); + for (var disk : other.disks) { + this.disks.add(new DiskConfig(disk)); + } + } + + public DiskList(long sdCardSize) { + this.sdCardSize = sdCardSize; + disks = new LinkedList<>(); + + // Always reserve the first 1Mb for partitioning info + disks.add(new DiskConfig(DiskConfig.Type.RESERVED, 0, reservedSize)); + disks.add(new DiskConfig(DiskConfig.Type.EMPTY, reservedSize, sdCardSize - reservedSize - reservedSize)); + + // Also reserve 1Mb at the end for config info + disks.add(new DiskConfig(DiskConfig.Type.RESERVED, sdCardSize - reservedSize, reservedSize)); + } + + public List getDisks() { + return new ArrayList<>(this.disks); + } + + public String getSdCardSizeGb() { + return String.format("%.2f", (this.sdCardSize * 512.0) / (1024l*1024*1024)); + } + + public long getSdCardSize() { + return this.sdCardSize; + } + + + public void resetSizeGb(double gb) { + var newSize = (long) (gb * (1024l * 1024 * 1024 / 512)); + + if (newSize > sdCardSize) { + var diffSectors = newSize - sdCardSize; + if (disks.getLast().getType() == DiskConfig.Type.RESERVED) { + var reserved = disks.getLast(); + var oldStart = reserved.getSdSectorStart(); + reserved.setSdSectorStart(oldStart + diffSectors); + disks.add(disks.size() - 1, new DiskConfig(DiskConfig.Type.EMPTY, oldStart, diffSectors)); + } + else + { + disks.add(new DiskConfig(DiskConfig.Type.EMPTY, sdCardSize, diffSectors)); + } + + + } else if (newSize < sdCardSize){ + var lastNonReserved = newSize - reservedSize; + while (!disks.isEmpty() && disks.getLast().getSdSectorEnd() > lastNonReserved) { + disks.removeLast(); + } + + if (!disks.isEmpty()) { + var lastNotRemoved = disks.getLast().getSdSectorEnd(); + if (lastNotRemoved < lastNonReserved) { + disks.add(new DiskConfig(DiskConfig.Type.EMPTY, lastNotRemoved, lastNonReserved - lastNotRemoved)); + } + } + + disks.add(new DiskConfig(DiskConfig.Type.RESERVED, lastNonReserved, reservedSize)); + } + this.mergeEmptySections(); + + sdCardSize = newSize; + } + + public boolean add(DiskConfig config) { + var found = false; + + this.removeReservedSections(config); + this.mergeEmptySections(); + + var it = disks.listIterator(); + while (it.hasNext()) + { + var entry = it.next(); + if (entry.getType() == DiskConfig.Type.EMPTY && + entry.getSdSectors() >= config.getSdSectors()) { + if (config.getSdSectorStart() < 0 || + config.getSdSectorStart() == entry.getSdSectorStart()) + { + config.setSdSectorStart(entry.getSdSectorStart()); + entry.setSdSectorStart(entry.getSdSectorStart() + config.getSdSectors()); + entry.setScsiSectors(entry.getSdSectors() - config.getSdSectors()); + + if (entry.getSdSectors() == 0) + { + it.remove(); + } + else + { + it.previous(); + } + + it.add(config); + + found = true; + break; + } + else if (config.getSdSectorStart() > entry.getSdSectorStart() && + config.getSdSectorEnd() <= entry.getSdSectorEnd()) + { + var remaining = new DiskConfig( + DiskConfig.Type.EMPTY, + config.getSdSectorEnd(), + entry.getSdSectorEnd() - config.getSdSectorEnd()); + + entry.setScsiSectors(config.getSdSectorStart() - entry.getSdSectorStart()); + + it.add(entry); + + if (remaining.getSdSectors() > 0) { + it.add(remaining); + } + + found = true; + break; + } + } + } + + return found; + } + + public void remove(DiskConfig config) { + if (config.getType() != DiskConfig.Type.DISK) { + // Don't delete reserved or empty space. + return; + } + + for (var i = 0; i < disks.size(); ++i) { + var entry = disks.get(i); + + if (entry == config) + { + var startSector = config.getSdSectorStart(); + var endSector = config.getSdSectorEnd(); + disks.remove(i); + + var empty = new DiskConfig(DiskConfig.Type.EMPTY, startSector, endSector - startSector); + if (empty.getSdSectors() > 0) + { + disks.add(i, empty); + } + + break; + } + } + + this.mergeEmptySections(); + } + + // Convert RESERVED into EMPTY if needed. + // Can't do this with the UI but can edit XML file or load an old config. + private void removeReservedSections(DiskConfig config) + { + for (var i = 0; i < disks.size(); ++i) + { + var entry = disks.get(i); + + if (entry.getType() == DiskConfig.Type.RESERVED && + config.getSdSectorStart() < entry.getSdSectorEnd() && + config.getSdSectorEnd() > entry.getSdSectorStart()) + { + var reservedStartSector = entry.getSdSectorStart(); + var reservedEndSector = entry.getSdSectorEnd(); + disks.remove(i); + + var stillReservedAtStartSize = config.getSdSectorStart() - reservedStartSector; + if (stillReservedAtStartSize > 0) + { + disks.add(i, new DiskConfig(DiskConfig.Type.RESERVED, reservedStartSector, stillReservedAtStartSize)); + ++i; + } + + var emptyStart = reservedStartSector > config.getSdSectorStart() ? reservedStartSector : config.getSdSectorStart(); + var emptyEnd = reservedEndSector > config.getSdSectorEnd() ? config.getSdSectorEnd() : reservedEndSector; + disks.add(i, new DiskConfig(DiskConfig.Type.EMPTY, emptyStart, emptyEnd - emptyStart)); + ++i; + + var stillReservedAtEndSize = reservedEndSector - config.getSdSectorEnd(); + if (stillReservedAtEndSize > 0) + { + disks.add(i, new DiskConfig(DiskConfig.Type.RESERVED, config.getSdSectorEnd(), stillReservedAtEndSize)); + ++i; + } + } + } + } + + private void mergeEmptySections() + { + var i = 0; + while (i < disks.size() - 1) + { + var entry = disks.get(i); + if (entry.getType() != DiskConfig.Type.EMPTY) + { + ++i; + continue; + } + + var next = disks.get(i + 1); + if (next.getType() != DiskConfig.Type.EMPTY) + { + ++i; + continue; + } + + var endSector = next.getSdSectorEnd(); + disks.remove(i + 1); + disks.remove(i); + disks.add(i, new DiskConfig(DiskConfig.Type.EMPTY, entry.getSdSectorStart(), endSector- entry.getSdSectorStart())); + } + } +} diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/DiskUnit.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/DiskUnit.java new file mode 100644 index 0000000..012edfb --- /dev/null +++ b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/DiskUnit.java @@ -0,0 +1,22 @@ +// Copyright (C) 2019 Michael McMaster +// +// This file is part of scsi2sd-util. +// +// scsi2sd-util is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// scsi2sd-util is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with scsi2sd-util. If not, see . + +package com.codesrc.scsi2sd.model; + +public enum DiskUnit { + MB, GB, SECTOR +} diff --git a/src/main/java/com/codesrc/scsi2sd/model/Flag.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/Flag.java similarity index 50% rename from src/main/java/com/codesrc/scsi2sd/model/Flag.java rename to scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/Flag.java index 37c6d3b..30e63ea 100644 --- a/src/main/java/com/codesrc/scsi2sd/model/Flag.java +++ b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/Flag.java @@ -1,19 +1,19 @@ -// Copyright (C) 2017 Michael McMaster +// Copyright (C) 2019 Michael McMaster // -// This file is part of SCSI2SD. +// This file is part of scsi2sd-util. // -// SCSI2SD is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. +// scsi2sd-util is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. // -// SCSI2SD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. +// scsi2sd-util is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. // -// You should have received a copy of the GNU General Public License -// along with SCSI2SD. If not, see . +// You should have received a copy of the GNU General Public License +// along with scsi2sd-util. If not, see . package com.codesrc.scsi2sd.model; diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/Flag6.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/Flag6.java new file mode 100644 index 0000000..56faddc --- /dev/null +++ b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/Flag6.java @@ -0,0 +1,44 @@ +// Copyright (C) 2019 Michael McMaster +// +// This file is part of scsi2sd-util. +// +// scsi2sd-util is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// scsi2sd-util is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with scsi2sd-util. If not, see . + +package com.codesrc.scsi2sd.model; + +import java.util.EnumSet; +import java.util.HashSet; +import java.util.Set; + +public enum Flag6 implements BitEnum { + S2S_CFG_ENABLE_TERMINATOR(1); + + static Set fromBitmask(byte bitmask) { + Set result = new HashSet<>(); + for (Flag6 flag : EnumSet.allOf(Flag6.class)) { + if ((((int) bitmask & 0xFF) & flag.getBit()) != 0) { + result.add(flag); + } + } + return result; + } + + private int bit; + Flag6(int bit) { + this.bit = bit; + } + + @Override + public int getBit() { return bit; } +} diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/Quirks.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/Quirks.java new file mode 100644 index 0000000..8ebb841 --- /dev/null +++ b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/Quirks.java @@ -0,0 +1,57 @@ +// Copyright (C) 2019 Michael McMaster +// +// This file is part of scsi2sd-util. +// +// scsi2sd-util is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// scsi2sd-util is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with scsi2sd-util. If not, see . + +package com.codesrc.scsi2sd.model; + +import java.util.*; + +public enum Quirks implements BitEnum { + S2S_CFG_QUIRKS_NONE(0, "None"), + S2S_CFG_QUIRKS_APPLE(1, "Apple"), + S2S_CFG_QUIRKS_OMTI(2, "OMTI"), + S2S_CFG_QUIRKS_XEBEC(4, "XEBEC"), + S2S_CFG_QUIRKS_VMS(8, "VMS"); + + static Set fromBitmask(int bitmask) { + Set result = new HashSet<>(); + for (Quirks flag : EnumSet.allOf(Quirks.class)) { + if ((bitmask & flag.getBit()) != 0) { + result.add(flag); + } + } + return result; + } + + + private int bit; + private String desc; + + Quirks(int bit, String desc) { + this.bit = bit; + this.desc = desc; + } + + @Override + public int getBit() { return bit; } + + @Override + public String toString() + { + return this.desc; + } + +} diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/Speed.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/Speed.java new file mode 100644 index 0000000..59710e5 --- /dev/null +++ b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/model/Speed.java @@ -0,0 +1,60 @@ +// Copyright (C) 2019 Michael McMaster +// +// This file is part of scsi2sd-util. +// +// scsi2sd-util is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// scsi2sd-util is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with scsi2sd-util. If not, see . + +package com.codesrc.scsi2sd.model; + +import java.util.EnumSet; + +public enum Speed { + S2S_CFG_SPEED_NoLimit(0, "No limit"), + S2S_CFG_SPEED_ASYNC_15(1, "Async, 1.5MB/s"), + S2S_CFG_SPEED_ASYNC_33(2, "Async, 3.3MB/s"), + S2S_CFG_SPEED_ASYNC_50(3, "Async, 5.0MB/s"), + S2S_CFG_SPEED_SYNC_5(4, "Sync, 5.0MB/s"), + S2S_CFG_SPEED_SYNC_10(5, "Sync, 10MB/s"), + S2S_CFG_SPEED_TURBO(6, "Turbo"); + + static Speed fromBits(byte bits) { + + for (Speed speed : EnumSet.allOf(Speed.class)) { + if (speed.getValue() == ((int) bits & 0xFF)) { + return speed; + } + } + return S2S_CFG_SPEED_NoLimit; + } + + + private int value; + private String desc; + + Speed(int value, String desc) { + this.value = value; + this.desc = desc; + } + + public int getValue() { + return value; + } + + @Override + public String toString() { + return this.desc; + } +} + + diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/AboutController.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/AboutController.java new file mode 100644 index 0000000..4ee445f --- /dev/null +++ b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/AboutController.java @@ -0,0 +1,90 @@ +// Copyright (C) 2019 Michael McMaster +// +// This file is part of scsi2sd-util. +// +// scsi2sd-util is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// scsi2sd-util is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with scsi2sd-util. If not, see . + +package com.codesrc.scsi2sd.presentation; + +import com.codesrc.scsi2sd.ScreensConfiguration; +import javafx.fxml.FXML; +import javafx.scene.control.*; +import javafx.scene.Scene; +import javafx.scene.image.Image; +import org.slf4j.LoggerFactory; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.URL; +import java.util.Properties; +import java.util.ResourceBundle; +import java.util.stream.Collectors; + +/** + * Created by michael on 27/05/17. + */ +public class AboutController implements FXController { + private static org.slf4j.Logger LOGGER = LoggerFactory.getLogger(AboutController.class); + + private ScreensConfiguration screens; + private FXWindow window; + + @FXML private TextArea aboutText; + @FXML private Label labelVersion; + @FXML private Hyperlink licenceLink; + @FXML private Button buttonClose; + + private Desktop desktop; + + private String licenseURL; + + private String contactURL; + + private String appVersion; + + public AboutController( + ScreensConfiguration screens, + Desktop desktop, + Properties appProperties) { + + this.screens = screens; + this.desktop = desktop; + this.licenseURL = appProperties.getProperty("com.codesrc.scsi2sd.app.licence.url"); + this.appVersion = appProperties.getProperty("com.codesrc.scsi2sd.app.version"); + } + + @Override + public void setWindow(FXWindow window) { + this.window = window; + } + + @Override + public void onSceneCreating(Scene scene) + { + } + + public void initialize(URL url, ResourceBundle resourceBundle) { + String about = new BufferedReader(new InputStreamReader(getClass().getResourceAsStream("/COPYING"))) + .lines().collect(Collectors.joining("\n")); + + aboutText.setText(about); + labelVersion.setText(appVersion); + + licenceLink.setTooltip(new Tooltip(licenseURL)); + licenceLink.setOnAction(h -> desktop.openBrowser(licenseURL)); + + buttonClose.setOnAction(h -> window.close()); + } + +} diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/ConfigController.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/ConfigController.java new file mode 100644 index 0000000..27ccb1a --- /dev/null +++ b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/ConfigController.java @@ -0,0 +1,221 @@ +// Copyright (C) 2019 Michael McMaster +// +// This file is part of scsi2sd-util. +// +// scsi2sd-util is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// scsi2sd-util is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with scsi2sd-util. If not, see . + +package com.codesrc.scsi2sd.presentation; + +import com.codesrc.scsi2sd.ScreensConfiguration; +import com.codesrc.scsi2sd.document.ActiveDocument; +import com.codesrc.scsi2sd.document.DocumentObserver; +import com.codesrc.scsi2sd.model.*; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.CheckBox; +import javafx.scene.control.ChoiceBox; +import javafx.scene.control.TextField; +import org.controlsfx.control.ToggleSwitch; + +import java.net.URL; +import java.util.ResourceBundle; + +/** + * Created by michael on 28/05/17. + */ +public class ConfigController implements Initializable, DocumentObserver { + + private ScreensConfiguration screens; + private ActiveDocument activeDocument; + + @FXML private TextField cfgSelectionDelay; + + @FXML private TextField cfgStartupDelay; + + @FXML private CheckBox cfgEnableTerminator; + + @FXML private ToggleSwitch cfgCheckParity; + @FXML private ToggleSwitch cfgUnitAtt; + @FXML private ToggleSwitch cfgScsi2; + @FXML private ToggleSwitch cfgSelPulses; + @FXML private ToggleSwitch cfgMapLuns; + + @FXML private ChoiceBox speedLimit; + + private BoardConfig boardConfig; + + public ConfigController(ScreensConfiguration screens, ActiveDocument document) { + this.screens = screens; + this.activeDocument = document; + document.addObserver(this); + } + + @Override + public void initialize(URL url, ResourceBundle resourceBundle) { + this.cfgSelectionDelay.setOnAction(e -> this.setSelectionDelay()); + this.cfgSelectionDelay.focusedProperty().addListener((obs, wasFocussed, isNowFocussed) -> {if (!isNowFocussed) this.setSelectionDelay();}); + + this.cfgStartupDelay.setOnAction(e -> this.setStartupDelay()); + this.cfgStartupDelay.focusedProperty().addListener((obs, wasFocussed, isNowFocussed) -> {if (!isNowFocussed) this.setStartupDelay();}); + + this.cfgEnableTerminator.setOnAction(e -> this.setTerminator()); + this.cfgCheckParity.selectedProperty().addListener(it -> this.setFlag(Flag.S2S_CFG_ENABLE_PARITY, this.cfgCheckParity)); + this.cfgUnitAtt.selectedProperty().addListener(it -> this.setFlag(Flag.S2S_CFG_ENABLE_UNIT_ATTENTION, this.cfgUnitAtt)); + this.cfgScsi2.selectedProperty().addListener(it -> this.setFlag(Flag.S2S_CFG_ENABLE_SCSI2, this.cfgScsi2)); + this.cfgSelPulses.selectedProperty().addListener(it -> this.setFlag(Flag.S2S_CFG_ENABLE_SEL_LATCH, this.cfgSelPulses)); + this.cfgMapLuns.selectedProperty().addListener(it -> this.setFlag(Flag.S2S_CFG_MAP_LUNS_TO_IDS, this.cfgMapLuns)); + + this.speedLimit.getItems().addAll(Speed.values()); + this.speedLimit.setOnAction(e -> this.setSpeedLimit()); + + } + public void documentLoaded(ActiveDocument document) + { + ConfigRoot root = document.getActiveDocument(); + this.boardConfig = root.getBoardConfig(); + + if (this.boardConfig.getSelectionDelay() == 255) + { + cfgSelectionDelay.setText(null); + } + else { + cfgSelectionDelay.setText(String.valueOf(this.boardConfig.getSelectionDelay())); + } + + if (this.boardConfig.getStartupDelay() == 0) + { + cfgStartupDelay.setText(null); + } + else { + cfgStartupDelay.setText(String.valueOf(this.boardConfig.getStartupDelay())); + } + + cfgEnableTerminator.setSelected(this.boardConfig.isEnableTerminator()); + // TODO check board type. cfgEnableTerminator.setDisable(this.get); + + cfgCheckParity.setSelected(this.boardConfig.getFlags().contains(Flag.S2S_CFG_ENABLE_PARITY)); + cfgUnitAtt.setSelected(this.boardConfig.getFlags().contains(Flag.S2S_CFG_ENABLE_UNIT_ATTENTION)); + cfgScsi2.setSelected(this.boardConfig.getFlags().contains(Flag.S2S_CFG_ENABLE_SCSI2)); + cfgSelPulses.setSelected(this.boardConfig.getFlags().contains(Flag.S2S_CFG_ENABLE_SEL_LATCH)); + cfgMapLuns.setSelected(this.boardConfig.getFlags().contains(Flag.S2S_CFG_MAP_LUNS_TO_IDS)); + + this.speedLimit.setValue(this.boardConfig.getScsiSpeed()); + } + + public void documentModified(ActiveDocument document, boolean isModified) + { + + } + public void documentValid(ActiveDocument document, boolean isValid) + { + + } + + private void setFlag(Flag flag, ToggleSwitch control) + { + if (this.boardConfig != null) + { + this.boardConfig.setFlag(flag, control.isSelected()); + this.activeDocument.setModified(this.getClass().getName(), true); + } + } + + private void setTerminator() + { + if (this.boardConfig != null) + { + this.boardConfig.setFlag6(Flag6.S2S_CFG_ENABLE_TERMINATOR, this.cfgEnableTerminator.isSelected()); + this.activeDocument.setModified(this.getClass().getName(), true); + } + } + + private void setStartupDelay() + { + if (this.boardConfig != null) + { + var in = this.cfgStartupDelay.getText(); + int intValue; + + if (in == null || in.isEmpty()) + { + intValue = 0; + } + else + { + var oldValue = this.boardConfig.getStartupDelay(); + + try { + intValue = Integer.valueOf(in); + if (intValue < 0 || intValue > 255) + { + this.cfgStartupDelay.setText(oldValue == 0 ? "" : String.valueOf(oldValue)); + return; + } + + } catch (NumberFormatException e) { + this.cfgStartupDelay.setText(oldValue == 0 ? "" : String.valueOf(oldValue)); + return; + } + } + + this.boardConfig.setStartupDelay(intValue); + this.cfgStartupDelay.setText(intValue == 0 ? "" : String.valueOf(intValue)); + this.activeDocument.setModified(DiskConfigController.class.getName(), true); + } + } + + private void setSelectionDelay() + { + if (this.boardConfig != null) + { + var in = this.cfgSelectionDelay.getText(); + int intValue; + + if (in == null || in.isEmpty()) + { + intValue = 255; + } + else + { + var oldValue = this.boardConfig.getSelectionDelay(); + + try { + intValue = Integer.valueOf(in); + if (intValue < 0 || intValue > 255) + { + this.cfgSelectionDelay.setText(oldValue == 255 ? "" : String.valueOf(oldValue)); + return; + } + + } catch (NumberFormatException e) { + this.cfgSelectionDelay.setText(oldValue == 255 ? "" : String.valueOf(oldValue)); + return; + } + } + + this.boardConfig.setSelectionDelay(intValue); + this.cfgSelectionDelay.setText(intValue == 255 ? "" : String.valueOf(intValue)); + this.activeDocument.setModified(DiskConfigController.class.getName(), true); + } + } + + private void setSpeedLimit() + { + if (this.boardConfig != null) + { + this.boardConfig.setScsiSpeed((Speed)this.speedLimit.getValue()); + this.activeDocument.setModified(this.getClass().getName(), true); + } + } +} diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/DebugLogController.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/DebugLogController.java new file mode 100644 index 0000000..6a918d8 --- /dev/null +++ b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/DebugLogController.java @@ -0,0 +1,73 @@ +// Copyright (C) 2019 Michael McMaster +// +// This file is part of scsi2sd-util. +// +// scsi2sd-util is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// scsi2sd-util is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with scsi2sd-util. If not, see . + +package com.codesrc.scsi2sd.presentation; + +import com.codesrc.scsi2sd.ScreensConfiguration; +import com.codesrc.scsi2sd.io.UsbDeviceManager; +import javafx.fxml.FXML; +import javafx.scene.Scene; +import javafx.scene.control.MenuItem; +import javafx.scene.control.TabPane; +import javafx.scene.control.TextArea; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.URL; +import java.util.ResourceBundle; +import java.util.logging.Level; + + +/** + * Created by michael on 21/05/17. + */ +public class DebugLogController implements FXController { + private static Logger LOGGER = LoggerFactory.getLogger(DebugLogController.class); + + private ScreensConfiguration screens; + private FXWindow window; + + private UsbDeviceManager usbDeviceManager; + private LogHandler logHandler; + + @FXML private TextArea logTextArea; + + public DebugLogController(ScreensConfiguration screens, UsbDeviceManager usbDeviceManager) { + this.screens = screens; + this.usbDeviceManager = usbDeviceManager; + } + + @Override + public void setWindow(FXWindow window) { + this.window = window; + } + + @Override + public void onSceneCreating(Scene scene) + { + } + + @Override + public void initialize(URL url, ResourceBundle resourceBundle) { + + this.logHandler = new LogHandler(this.logTextArea); + logHandler.setLevel(Level.INFO); + java.util.logging.Logger usbLogger = java.util.logging.Logger.getLogger(UsbDeviceManager.class.getPackageName()); + + usbLogger.addHandler(this.logHandler); + } +} diff --git a/src/main/java/com/codesrc/scsi2sd/presentation/Desktop.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/Desktop.java similarity index 57% rename from src/main/java/com/codesrc/scsi2sd/presentation/Desktop.java rename to scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/Desktop.java index 82c35c4..a945019 100644 --- a/src/main/java/com/codesrc/scsi2sd/presentation/Desktop.java +++ b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/Desktop.java @@ -1,39 +1,37 @@ -// Copyright (C) 2017 Michael McMaster +// Copyright (C) 2019 Michael McMaster // -// This file is part of SCSI2SD. +// This file is part of scsi2sd-util. // -// SCSI2SD is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. +// scsi2sd-util is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. // -// SCSI2SD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. +// scsi2sd-util is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. // -// You should have received a copy of the GNU General Public License -// along with SCSI2SD. If not, see . +// You should have received a copy of the GNU General Public License +// along with scsi2sd-util. If not, see . package com.codesrc.scsi2sd.presentation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Component; import java.io.IOException; /** * Created by michael on 28/05/17. */ -@Component public class Desktop { - private static Logger LOGGER = LoggerFactory.getLogger(Desktop.class.getName()); + private static Logger LOGGER = LoggerFactory.getLogger(Desktop.class); public enum Environment { - LINUX, - MAC, - WINDOWS, + Linux, + OSX, + Windows, OTHER } @@ -42,11 +40,11 @@ public class Desktop { public Desktop() { String os = System.getProperty("os.name").toLowerCase(); if (os.indexOf("windows") != -1 || os.indexOf("nt") != -1) { - environment = Environment.WINDOWS; + environment = Environment.Windows; } else if (os.indexOf("mac") != -1) { - environment = Environment.MAC; + environment = Environment.OSX; } else if (os.indexOf("linux") != -1) { - environment = Environment.LINUX; + environment = Environment.Linux; } else { environment = Environment.OTHER; } @@ -58,15 +56,15 @@ public class Desktop { LOGGER.info("Launching " + url); try { switch (environment) { - case MAC: + case OSX: Runtime.getRuntime().exec(new String[]{"open", url}); break; - case WINDOWS: + case Windows: Runtime.getRuntime().exec(new String[]{"cmd", "/c", "start", url}); break; - case LINUX: + case Linux: case OTHER: Runtime.getRuntime().exec(new String[]{"xdg-open", url}); break; diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/DiskConfigController.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/DiskConfigController.java new file mode 100644 index 0000000..bc5f7d2 --- /dev/null +++ b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/DiskConfigController.java @@ -0,0 +1,616 @@ +// Copyright (C) 2019 Michael McMaster +// +// This file is part of scsi2sd-util. +// +// scsi2sd-util is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// scsi2sd-util is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with scsi2sd-util. If not, see . + +package com.codesrc.scsi2sd.presentation; + +import com.codesrc.scsi2sd.ScreensConfiguration; +import com.codesrc.scsi2sd.document.ActiveDocument; +import com.codesrc.scsi2sd.document.DocumentObserver; +import com.codesrc.scsi2sd.io.UsbDeviceConnectedEvent; +import com.codesrc.scsi2sd.io.UsbDeviceManager; +import com.codesrc.scsi2sd.model.*; +import javafx.application.Platform; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.*; +import javafx.scene.layout.Background; +import javafx.stage.StageStyle; + +import java.net.URL; +import java.util.Arrays; +import java.util.Optional; +import java.util.ResourceBundle; + +public class DiskConfigController implements Initializable, DocumentObserver { + + private ScreensConfiguration screens; + private ActiveDocument activeDocument; + private UsbDeviceManager usbDeviceManager; + + @FXML + private TableView diskTableView; + + @FXML + private Button newBtn; + + @FXML + private Button deleteBtn; + + @FXML private Button setSdCardSizeBtn; + + @FXML private TextField scsiId; + + @FXML private CheckBox enabled; + + @FXML + private TextField scsiSizeStr; + + @FXML private ChoiceBox scsiSizeUnit; + + @FXML + private TextField scsiBytesPerSector; + + @FXML + private TextField sdStartingByte; + + @FXML ComboBox scsiDeviceType; + + @FXML private TextField vendor; + @FXML private TextField prodId; + @FXML private TextField revision; + @FXML private TextField serial; + + @FXML private TextField deviceTypeModifier; + @FXML private TextField sectorsPerTrack; + @FXML private TextField headsPerCylinder; + @FXML private ComboBox quirksMode; + + private DiskList diskList; + + private DiskConfig selectedConfig; + + private int maxSupportedDevices = 4; + + // data bound to the table view. + public class DiskListItem + { + private DiskConfig config; + + public DiskListItem(DiskConfig config) { + this.config = config; + } + + public long getSdSector() { + return config.getSdSectorStart(); + } + + public DiskConfig getConfig() { + return this.config; + } + + public DiskConfig.Type getDiskType() { + return this.config.getType(); + } + + public String getSize() { + return this.config.getSizeString(this.config.getBestUnit(), true); + } + + public String getId() { + return this.config.getType() == DiskConfig.Type.DISK ? Integer.toString(this.config.getScsiId()) : ""; + } + + public DeviceTypeEnum getDeviceType() { return this.config.getType() == DiskConfig.Type.DISK ? this.config.getDeviceType() : null; } + + public boolean isEnabled() { + return this.config.isActive(); + } + } + + public DiskConfigController(ScreensConfiguration screens, ActiveDocument document, UsbDeviceManager usbDeviceManager) { + this.screens = screens; + this.activeDocument = document; + this.usbDeviceManager = usbDeviceManager; + document.addObserver(this); + } + + @Override + public void initialize(URL url, ResourceBundle resourceBundle) { + this.usbDeviceManager.getDeviceConnectedEvent().subscribe(e -> Platform.runLater(() -> this.handleUsbConnected(e))); + + this.newBtn.setOnAction((e) -> this.addNew()); + this.deleteBtn.setOnAction((e) -> this.delete()); + this.setSdCardSizeBtn.setOnAction((e) -> this.setSdCardSize()); + + this.scsiId.setOnAction((e) -> this.setScsiId()); + this.scsiId.setTooltip(new Tooltip("SCSI ID 0 to 7")); + this.scsiId.focusedProperty().addListener((obs, wasFocussed, isNowFocussed) -> {if (!isNowFocussed) this.setScsiId();}); + + this.enabled.setOnAction((e) -> this.setEnabled()); + + this.scsiSizeUnit.setOnAction((e) -> this.setSizeUnits()); + this.scsiSizeStr.setOnAction((e) -> this.setSize()); + this.scsiSizeStr.focusedProperty().addListener((obs, wasFocussed, isNowFocussed) -> {if (!isNowFocussed) this.setSize();}); + + this.scsiBytesPerSector.setOnAction((e) -> this.setBytesPerSector()); + this.scsiBytesPerSector.setTooltip(new Tooltip("Normally 512 bytes for hard drives or 2048 bytes for CDROM devices")); + this.scsiBytesPerSector.focusedProperty().addListener((obs, wasFocussed, isNowFocussed) -> {if (!isNowFocussed) this.setBytesPerSector();}); + + this.scsiDeviceType.setOnAction((e) -> this.setDeviceType()); + + this.vendor.setOnAction((e) -> this.setVendor()); + this.vendor.focusedProperty().addListener((obs, wasFocussed, isNowFocussed) -> {if (!isNowFocussed) this.setVendor();}); + + this.prodId.setOnAction((e) -> this.setProdId()); + this.prodId.focusedProperty().addListener((obs, wasFocussed, isNowFocussed) -> {if (!isNowFocussed) this.setProdId();}); + + this.revision.setOnAction((e) -> this.setRevision()); + this.revision.focusedProperty().addListener((obs, wasFocussed, isNowFocussed) -> {if (!isNowFocussed) this.setRevision();}); + + this.serial.setOnAction((e) -> this.setSerial()); + this.serial.focusedProperty().addListener((obs, wasFocussed, isNowFocussed) -> {if (!isNowFocussed) this.setSerial();}); + + this.deviceTypeModifier.setOnAction((e) -> this.setDeviceTypeModifier()); + this.deviceTypeModifier.setTooltip(new Tooltip("SCSI device type modifier field, usually 0.")); + this.deviceTypeModifier.focusedProperty().addListener((obs, wasFocussed, isNowFocussed) -> {if (!isNowFocussed) this.setDeviceTypeModifier();}); + + this.sectorsPerTrack.setOnAction((e) -> this.setSectorsPerTrack()); + this.sectorsPerTrack.setTooltip(new Tooltip("Modify emulated disk geometry")); + this.sectorsPerTrack.focusedProperty().addListener((obs, wasFocussed, isNowFocussed) -> {if (!isNowFocussed) this.setSectorsPerTrack();}); + + this.headsPerCylinder.setOnAction((e) -> this.setHeadsPerCylinder()); + this.headsPerCylinder.setTooltip(new Tooltip("Modify emulated disk geometry")); + this.headsPerCylinder.focusedProperty().addListener((obs, wasFocussed, isNowFocussed) -> {if (!isNowFocussed) this.setHeadsPerCylinder();}); + + this.quirksMode.setOnAction((e) -> this.setQuirksMode()); + + this.newBtn.setDisable(true); + this.deleteBtn.setDisable(true); + this.setSdCardSizeBtn.setDisable(true); + + this.scsiSizeUnit.getItems().addAll(DiskUnit.values()); + this.scsiDeviceType.getItems().addAll(Arrays.stream(DeviceTypeEnum.values()).filter(dt -> dt != DeviceTypeEnum.S2S_CFG_SEQUENTIAL).toArray()); + + this.quirksMode.getItems().addAll(Quirks.values()); + + this.diskTableView.getSelectionModel().selectedItemProperty().addListener((obs, oldSel, newSel) -> { + this.selectRow(newSel); + }); + } + + private void handleUsbConnected(UsbDeviceConnectedEvent usbDeviceConnectedEvent) { + this.maxSupportedDevices = usbDeviceConnectedEvent.getUsbDevice().getMaxDisks(); + } + + public void documentLoaded(ActiveDocument document) + { + ConfigRoot root = document.getActiveDocument(); + this.diskList = root.getDiskList(); + + ObservableList data = diskTableView.getItems(); + data.clear(); + for (var config : diskList.getDisks()) + { + data.add(new DiskListItem(config)); + } + + // this.setSdCardSizeBtn.setDisable(false); + } + + public void documentModified(ActiveDocument document, boolean isModified) + { + + } + + public void documentValid(ActiveDocument document, boolean isValid) + { + + } + + private void selectRow(Object item) { + if (item == null) + { + this.selectedConfig = null; + } + else + { + this.selectedConfig = ((DiskListItem) item).getConfig(); + + var numDevices = this.diskList.getDisks().stream().filter(c -> c.getType() == DiskConfig.Type.DISK).count(); + this.newBtn.setDisable(this.selectedConfig.getType() != DiskConfig.Type.EMPTY || numDevices >= this.maxSupportedDevices); + this.deleteBtn.setDisable(this.selectedConfig.getType() != DiskConfig.Type.DISK); + this.scsiId.setDisable(this.selectedConfig.getType() != DiskConfig.Type.DISK); + this.enabled.setDisable(this.selectedConfig.getType() != DiskConfig.Type.DISK); + this.scsiSizeStr.setDisable(this.selectedConfig.getType() != DiskConfig.Type.DISK); + this.scsiBytesPerSector.setDisable(this.selectedConfig.getType() != DiskConfig.Type.DISK); + this.scsiDeviceType.setDisable(this.selectedConfig.getType() != DiskConfig.Type.DISK); + this.vendor.setDisable(this.selectedConfig.getType() != DiskConfig.Type.DISK); + this.prodId.setDisable(this.selectedConfig.getType() != DiskConfig.Type.DISK); + this.revision.setDisable(this.selectedConfig.getType() != DiskConfig.Type.DISK); + this.serial.setDisable(this.selectedConfig.getType() != DiskConfig.Type.DISK); + this.deviceTypeModifier.setDisable(this.selectedConfig.getType() != DiskConfig.Type.DISK); + this.sectorsPerTrack.setDisable(this.selectedConfig.getType() != DiskConfig.Type.DISK); + this.headsPerCylinder.setDisable(this.selectedConfig.getType() != DiskConfig.Type.DISK); + this.quirksMode.setDisable(this.selectedConfig.getType() != DiskConfig.Type.DISK); + + this.scsiId.setText(this.selectedConfig.getType() == DiskConfig.Type.DISK ? Integer.toString(this.selectedConfig.getScsiId()) : ""); + this.enabled.setSelected(this.selectedConfig.isActive()); + var bestUnit = this.selectedConfig.getBestUnit(); + this.scsiSizeUnit.setValue(bestUnit); + this.scsiSizeStr.setText(this.selectedConfig.getSizeString(bestUnit, false)); + this.scsiBytesPerSector.setText(Integer.toString(this.selectedConfig.getBytesPerSector())); + this.sdStartingByte.setText(Long.toString(this.selectedConfig.getSdSectorStart() * DiskConfig.SD_SECTOR_SIZE)); + this.scsiDeviceType.setValue(this.selectedConfig.getDeviceType()); + this.deviceTypeModifier.setText(String.valueOf(this.selectedConfig.getDeviceTypeModifier())); + this.vendor.setText(this.selectedConfig.getVendor()); + this.prodId.setText(this.selectedConfig.getProdId()); + this.revision.setText(this.selectedConfig.getRevision()); + this.serial.setText(this.selectedConfig.getSerial()); + this.sectorsPerTrack.setText(String.valueOf(this.selectedConfig.getSectorsPerTrack())); + this.headsPerCylinder.setText(String.valueOf(this.selectedConfig.getHeadsPerCylinder())); + this.quirksMode.setValue(this.selectedConfig.getQuirksMode()); + + } + } + + private void addNew() + { + if (this.selectedConfig != null && this.selectedConfig.getType() == DiskConfig.Type.EMPTY) + { + var newConfig = new DiskConfig(DiskConfig.Type.DISK, this.selectedConfig.getSdSectorStart(), this.selectedConfig.getSdSectors()); + this.diskList.add(newConfig); + } + + this.activeDocument.setModified(DiskConfigController.class.getName(), true); + this.refreshList(); + } + + private void delete() + { + if (this.selectedConfig != null && this.selectedConfig.getType() == DiskConfig.Type.DISK) + { + this.diskList.remove(this.selectedConfig); + } + + this.activeDocument.setModified(DiskConfigController.class.getName(), true); + this.refreshList(); + } + + private void refreshList() + { + var oldSelectedConfig = this.selectedConfig; + ObservableList data = diskTableView.getItems(); + data.clear(); // Will call the row selection handler and set this.selectedConfig to null + + for (var config : diskList.getDisks()) + { + var tableItem = new DiskListItem(config); + data.add(tableItem); + + if (oldSelectedConfig == config) { + diskTableView.getSelectionModel().select(tableItem); + } + } + } + + private void setScsiId() + { + if (this.selectedConfig != null && this.selectedConfig.getType() == DiskConfig.Type.DISK) { + int value; + try { + value = Integer.parseUnsignedInt(this.scsiId.getText()); + if (value >= 0 && value <= 7) { + this.selectedConfig.setScsiId(value); + } + } catch (NumberFormatException e) { + } + + this.activeDocument.setModified(DiskConfigController.class.getName(), true); + this.refreshList(); + } + } + + private void setEnabled() + { + if (this.selectedConfig != null && this.selectedConfig.getType() == DiskConfig.Type.DISK) { + this.selectedConfig.setActive(this.enabled.isSelected()); + this.activeDocument.setModified(DiskConfigController.class.getName(), true); + this.refreshList(); + } + } + + + private void setSizeUnits() + { + if (this.selectedConfig != null) + { + this.scsiSizeStr.setText(this.selectedConfig.getSizeString((DiskUnit)this.scsiSizeUnit.getValue(), false)); + } + } + + private void setSize() + { + if (this.selectedConfig != null && this.selectedConfig.getType() == DiskConfig.Type.DISK) + { + double value; + try + { + value = Double.parseDouble(this.scsiSizeStr.getText()); + if (value <= 0) + { + this.setSizeUnits(); + return; + } + } + catch (NumberFormatException e) + { + this.setSizeUnits(); + return; + } + + var units = (DiskUnit)this.scsiSizeUnit.getValue(); + var bytes = 0l; + switch (units) + { + case SECTOR: bytes = (long)value * this.selectedConfig.getBytesPerSector(); break; + case MB: bytes = (long)(value * 1024 * 1024); break; + case GB: bytes = (long)(value * 1024 * 1024 * 1024); break; + } + + this.diskList.remove(this.selectedConfig); + var origScsiSectors = this.selectedConfig.getScsiSectors(); + this.selectedConfig.setScsiSectors(bytes / this.selectedConfig.getBytesPerSector()); + if (!this.diskList.add(this.selectedConfig)) + { + this.selectedConfig.setScsiSectors(origScsiSectors); + this.diskList.add(this.selectedConfig); + } + + this.activeDocument.setModified(DiskConfigController.class.getName(), true); + this.refreshList(); + } + } + + private void setBytesPerSector() + { + if (this.selectedConfig != null && this.selectedConfig.getType() == DiskConfig.Type.DISK) + { + int value; + try + { + value = Integer.parseUnsignedInt(this.scsiBytesPerSector.getText()); + + if (value <= 0) + { + this.scsiBytesPerSector.setText(Integer.toString(this.selectedConfig.getBytesPerSector())); + return; + } + } + catch (NumberFormatException e) + { + this.scsiBytesPerSector.setText(Integer.toString(this.selectedConfig.getBytesPerSector())); + return; + } + + this.diskList.remove(this.selectedConfig); + + var origBytesPerSector = this.selectedConfig.getBytesPerSector(); + var origScsiSectors = this.selectedConfig.getScsiSectors(); + var bytes = origBytesPerSector * origScsiSectors; + + this.selectedConfig.setScsiSectors(bytes / value); + this.selectedConfig.setBytesPerSector(value); + + if (!this.diskList.add(this.selectedConfig)) + { + this.selectedConfig.setScsiSectors(origScsiSectors); + this.selectedConfig.setBytesPerSector(origBytesPerSector); + + this.diskList.add(this.selectedConfig); + } + + this.activeDocument.setModified(DiskConfigController.class.getName(), true); + this.refreshList(); + } + } + + private void setDeviceType() + { + if (this.selectedConfig != null) + { + this.selectedConfig.setDeviceType((DeviceTypeEnum)this.scsiDeviceType.getValue()); + + this.activeDocument.setModified(DiskConfigController.class.getName(), true); + this.refreshList(); + } + } + + private void setVendor() + { + if (this.selectedConfig != null) + { + this.selectedConfig.setVendor(this.vendor.getText()); + this.vendor.setText(this.selectedConfig.getVendor()); // Fix padding + this.activeDocument.setModified(DiskConfigController.class.getName(), true); + } + } + + private void setProdId() + { + if (this.selectedConfig != null) + { + this.selectedConfig.setProdId(this.prodId.getText()); + this.prodId.setText(this.selectedConfig.getProdId()); // Fix padding + this.activeDocument.setModified(DiskConfigController.class.getName(), true); + } + } + + private void setRevision() + { + if (this.selectedConfig != null) + { + this.selectedConfig.setRevision(this.revision.getText()); + this.revision.setText(this.selectedConfig.getRevision()); // Fix padding + this.activeDocument.setModified(DiskConfigController.class.getName(), true); + } + } + + private void setSerial() + { + if (this.selectedConfig != null) + { + this.selectedConfig.setSerial(this.serial.getText()); + this.serial.setText(this.selectedConfig.getSerial()); // Fix padding + this.activeDocument.setModified(DiskConfigController.class.getName(), true); + } + } + + private void setSdCardSize() { + if (this.diskList != null) { + TextInputDialog dialog = new TextInputDialog(String.valueOf(this.diskList.getSdCardSizeGb())); + dialog.setTitle("SD Card Size"); + dialog.setHeaderText(null); + dialog.setContentText("Set SD card Size (GB):"); + dialog.initStyle(StageStyle.UTILITY); // remove icons + + Optional result = dialog.showAndWait(); + if (result.isPresent()) { + double value; + try + { + value = Double.parseDouble(result.get()); + + if (value <= 0 || value > 1999) + { + return; + } + } + catch (NumberFormatException e) + { + return; + } + + this.diskList.resetSizeGb(value); + this.activeDocument.setModified(DiskConfigController.class.getName(), true); + this.refreshList(); + } + } + } + + private void setDeviceTypeModifier() + { + if (this.selectedConfig != null) + { + var in = this.deviceTypeModifier.getText(); + int intValue; + + if (in == null || in.isEmpty()) + { + intValue = 0; + } + else + { + var oldValue = this.selectedConfig.getDeviceTypeModifier(); + + try { + intValue = Integer.valueOf(in); + if (intValue < 0 || intValue > 255) + { + this.deviceTypeModifier.setText(String.valueOf(oldValue)); + return; + } + + } catch (NumberFormatException e) { + this.deviceTypeModifier.setText(String.valueOf(oldValue)); + return; + } + } + + this.selectedConfig.setDeviceTypeModifier(intValue); + this.deviceTypeModifier.setText(String.valueOf(intValue)); + this.activeDocument.setModified(DiskConfigController.class.getName(), true); + } + } + + private void setSectorsPerTrack() + { + if (this.selectedConfig != null) + { + var in = this.sectorsPerTrack.getText(); + int intValue; + + var oldValue = this.selectedConfig.getSectorsPerTrack(); + + try { + intValue = Integer.valueOf(in); + if (intValue < 1 || intValue > 65535) + { + this.sectorsPerTrack.setText(String.valueOf(oldValue)); + return; + } + + } catch (NumberFormatException e) { + this.sectorsPerTrack.setText(String.valueOf(oldValue)); + return; + } + + this.selectedConfig.setSectorsPerTrack(intValue); + this.sectorsPerTrack.setText(String.valueOf(intValue)); + this.activeDocument.setModified(DiskConfigController.class.getName(), true); + } + } + + private void setHeadsPerCylinder() + { + if (this.selectedConfig != null) + { + var in = this.headsPerCylinder.getText(); + int intValue; + + var oldValue = this.selectedConfig.getHeadsPerCylinder(); + + try { + intValue = Integer.valueOf(in); + if (intValue < 1 || intValue > 65535) + { + this.headsPerCylinder.setText(String.valueOf(oldValue)); + return; + } + + } catch (NumberFormatException e) { + this.headsPerCylinder.setText(String.valueOf(oldValue)); + return; + } + + this.selectedConfig.setHeadsPerCylinder(intValue); + this.headsPerCylinder.setText(String.valueOf(intValue)); + this.activeDocument.setModified(DiskConfigController.class.getName(), true); + } + } + + + private void setQuirksMode() + { + if (this.selectedConfig != null) + { + this.selectedConfig.setQuirksMode((Quirks)this.quirksMode.getValue()); + this.activeDocument.setModified(DiskConfigController.class.getName(), true); + } + } + +} diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/FXController.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/FXController.java new file mode 100644 index 0000000..3978bcc --- /dev/null +++ b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/FXController.java @@ -0,0 +1,29 @@ +// Copyright (C) 2019 Michael McMaster +// +// This file is part of scsi2sd-util. +// +// scsi2sd-util is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// scsi2sd-util is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with scsi2sd-util. If not, see . + +package com.codesrc.scsi2sd.presentation; + +import javafx.fxml.Initializable; +import javafx.scene.Scene; + +/** + * Created by michael on 27/05/17. + */ +public interface FXController extends Initializable { + void setWindow(FXWindow window); + void onSceneCreating(Scene scene); +} diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/FXWindow.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/FXWindow.java new file mode 100644 index 0000000..61decd3 --- /dev/null +++ b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/FXWindow.java @@ -0,0 +1,71 @@ +// Copyright (C) 2019 Michael McMaster +// +// This file is part of scsi2sd-util. +// +// scsi2sd-util is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// scsi2sd-util is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with scsi2sd-util. If not, see . + +package com.codesrc.scsi2sd.presentation; + +import com.codesrc.scsi2sd.App; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.image.Image; +import javafx.stage.Modality; +import javafx.stage.Stage; +import javafx.stage.StageStyle; +import javafx.stage.Window; +import javafx.util.Callback; + +import java.io.IOException; +import java.net.URL; + +/** + * Created by michael on 27/05/17. + */ +public class FXWindow extends Stage { + + public FXWindow(Callback,Object> controllerFactory, URL fxml, String title, Window owner, StageStyle style) { + super(style); + initOwner(owner); + initModality(Modality.WINDOW_MODAL); + getIcons().add(new Image(App.class.getResourceAsStream("/icons/icon-16.png"))); + getIcons().add(new Image(App.class.getResourceAsStream("/icons/icon-32.png"))); + getIcons().add(new Image(App.class.getResourceAsStream("/icons/icon-256.png"))); + setTitle(title); + + + FXMLLoader loader = new FXMLLoader(fxml); + try { + loader.setControllerFactory(controllerFactory); + Parent root = loader.load(); + Object controller = loader.getController(); + if (controller instanceof FXController) + { + ((FXController)controller).setWindow(this); + } + + Scene scene = new Scene(root); + + if (controller instanceof FXController) + { + ((FXController)controller).onSceneCreating(scene); + } + + setScene(scene); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/LogHandler.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/LogHandler.java new file mode 100644 index 0000000..c7d4962 --- /dev/null +++ b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/LogHandler.java @@ -0,0 +1,50 @@ +// Copyright (C) 2019 Michael McMaster +// +// This file is part of scsi2sd-util. +// +// scsi2sd-util is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// scsi2sd-util is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with scsi2sd-util. If not, see . + +package com.codesrc.scsi2sd.presentation; + +import javafx.application.Platform; +import javafx.scene.control.TextArea; + +import java.time.format.DateTimeFormatter; +import java.util.logging.Handler; +import java.util.logging.LogRecord; + +public class LogHandler extends Handler { + private TextArea logTextArea; + + public LogHandler(TextArea logTextArea) { + this.logTextArea = logTextArea; + } + + @Override + public void publish(LogRecord logRecord) { + Platform.runLater(() -> { + logTextArea.appendText(DateTimeFormatter.ISO_INSTANT.format(logRecord.getInstant()) + " " + logRecord.getMessage() + "\n"); + }); + } + + @Override + public void flush() { + + } + + @Override + public void close() throws SecurityException { + + } +} diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/MainController.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/MainController.java new file mode 100644 index 0000000..a83bf96 --- /dev/null +++ b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/MainController.java @@ -0,0 +1,455 @@ +// Copyright (C) 2019 Michael McMaster +// +// This file is part of scsi2sd-util. +// +// scsi2sd-util is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// scsi2sd-util is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with scsi2sd-util. If not, see . + +package com.codesrc.scsi2sd.presentation; + +import com.codesrc.scsi2sd.ScreensConfiguration; +import com.codesrc.scsi2sd.document.ActiveDocument; +import com.codesrc.scsi2sd.document.DocumentObserver; +import com.codesrc.scsi2sd.io.UsbDevice; +import com.codesrc.scsi2sd.io.UsbDeviceManager; +import com.codesrc.scsi2sd.model.BoardConfig; +import com.codesrc.scsi2sd.model.ConfigRoot; +import com.codesrc.scsi2sd.model.DiskConfig; +import com.codesrc.scsi2sd.model.DiskList; +import javafx.application.Platform; +import javafx.fxml.FXML; +import javafx.scene.control.*; +import javafx.scene.Scene; +import javafx.scene.layout.AnchorPane; +import javafx.stage.FileChooser; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.File; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.logging.Level; +import java.util.prefs.Preferences; +import java.util.stream.Collectors; + + +/** + * Created by michael on 21/05/17. + */ +public class MainController implements FXController, DocumentObserver { + public static final String Title = "codesrc SCSI2SD"; + private static Logger LOGGER = LoggerFactory.getLogger(MainController.class); + private final String RECENT = "recent_items"; + private final int RECENT_MAX = 10; + + private ScreensConfiguration screens; + private FXWindow window; + private ActiveDocument activeDocument; + + private UsbDeviceManager usbDeviceManager; + + private Preferences prefs; + private Deque recent; + + @FXML private Menu menuRecent; + + @FXML private MenuItem menuAbout; + @FXML private MenuItem menuQuit; + + @FXML private MenuItem menuOpenDevice; + @FXML private MenuItem menuSaveDevice; + @FXML private MenuItem menuOpenFile; + @FXML private MenuItem toolbarOpenFile; + @FXML private MenuItem menuSaveFile; + @FXML private MenuItem toolbarSaveFile; + @FXML private MenuItem menuRevert; + @FXML private Button buttonRevert; + + @FXML private TabPane mainTabPane; + + @FXML private AnchorPane debugLog; + @FXML private DebugLogController debugLogController; + + public MainController( + ScreensConfiguration screens, + UsbDeviceManager usbDeviceManager, + ActiveDocument activeDocument) { + this.screens = screens; + this.usbDeviceManager = usbDeviceManager; + this.activeDocument = activeDocument; + this.activeDocument.addObserver(this); + + this.recent = new ArrayDeque<>(); + } + + @Override + public void setWindow(FXWindow window) { + this.window = window; + this.window.setOnCloseRequest(e -> this.exit()); + } + + @Override + public void onSceneCreating(Scene scene) + { + URL stylesheet = MainController.class.getResource("/css/main.css"); + scene.getStylesheets().add(stylesheet.toString()); + } + + @Override + public void initialize(URL url, ResourceBundle resourceBundle) { + menuQuit.setOnAction(h -> this.exit()); + menuAbout.setOnAction(h -> screens.aboutWindow(window).showAndWait()); + menuOpenDevice.setOnAction(h -> loadConfigFromDevice()); + menuSaveDevice.setOnAction(h -> saveConfigToDevice()); + menuOpenFile.setOnAction(h -> loadConfigFromFile()); + toolbarOpenFile.setOnAction(h -> loadConfigFromFile()); + menuSaveFile.setOnAction(h -> saveConfigToFile()); + toolbarSaveFile.setOnAction(h -> saveConfigToFile()); + menuRevert.setOnAction(h -> revert()); + buttonRevert.setOnAction(h -> revert()); + + menuOpenDevice.setDisable(true); + menuSaveDevice.setDisable(true); + + this.prefs = Preferences.userNodeForPackage(MainController.class); + this.populateRecentFromPrefs(); + + this.loadDefault(); + + this.usbDeviceManager.getDeviceConnectedEvent().subscribe(e -> Platform.runLater(() -> this.handleDeviceConnectedEvent(e.getUsbDevice()))); + this.usbDeviceManager.getDeviceDisconnectedEvent().subscribe(e -> Platform.runLater(() -> this.handleDeviceDisconnectedEvent(e.getUsbDevice()))); + + List deviceList = usbDeviceManager.getDevices(); + if (!deviceList.isEmpty()) { + handleDeviceConnectedEvent(deviceList.get(0)); + } + } + + private void exit() + { + usbDeviceManager.shutdown(); + System.exit(0); + } + + private void loadConfigFromDevice() { + List deviceList = this.usbDeviceManager.getDevices(); + if (!deviceList.isEmpty()) + { + this.loadConfigFromDevice(deviceList.get(0)); + } + } + + private void handleDeviceDisconnectedEvent(UsbDevice device) + { + this.menuOpenDevice.setDisable(true); + this.menuSaveDevice.setDisable(true); + } + + private void handleDeviceConnectedEvent(UsbDevice device) + { + this.menuOpenDevice.setDisable(false); + this.loadConfigFromDevice(device); + this.menuSaveDevice.setDisable(false); + } + + private void loadConfigFromDevice(UsbDevice device) { + BoardConfig boardConfig = new BoardConfig(device.readBoardConfig()); + ConfigRoot root = new ConfigRoot(); + root.setBoardConfig(boardConfig); + + var lastSectorUsed = 0l; + var configs = new ArrayList(); + for (var i = 0; i < device.getMaxDisks(); ++i) { + var disk = new DiskConfig(device.readDiskConfig(i)); + if (!disk.isActive() && disk.getSdSectorStart() == 0 && disk.getScsiSectors() == 0) + { + continue; // dummy entry. + } + + lastSectorUsed = Math.max(lastSectorUsed, disk.getSdSectorEnd()); + configs.add(disk); + } + + var sdCapacity = device.getSdCapacity(); + var fallbackSize = Math.max(2l*1024*1024*1024/512, lastSectorUsed + DiskList.reservedSize); + var diskList = new DiskList(sdCapacity > 0 ? sdCapacity : fallbackSize); + configs.forEach(c -> diskList.add(c)); + + root.setDiskList(diskList); + + activeDocument.loadDocument(root, null); + } + + private void saveConfigToDevice() + { + var device = this.getDevice(); + if (device == null) + { + return; + } + + var root = this.activeDocument.getActiveDocument(); + device.saveBoardConfig(root.getBoardConfig().asBytes()); + + var configs = root.getDiskList().getDisks().stream() + .filter(d -> d.getType() == DiskConfig.Type.DISK) + .collect(Collectors.toList()); + + for (var i = 0; i < device.getMaxDisks(); ++i) + { + if (i < configs.size()) + { + device.saveDiskConfig(i, configs.get(i).asBytes()); + } + else + { + // Save a 0 byte dummy + var dummy = new DiskConfig(DiskConfig.Type.DISK, 0, 0); + dummy.setActive(false); + device.saveDiskConfig(i, dummy.asBytes()); + } + } + device.saveCommit(); + this.window.setTitle(Title); + } + + @Override + public void documentLoaded(ActiveDocument document) + { + if (this.window != null) { + this.window.setTitle(Title); + } + this.enableDisableSave(); + } + + @Override + public void documentModified(ActiveDocument document, boolean isModified) + { + this.window.setTitle("*" + Title); + this.enableDisableSave(); + } + + @Override + public void documentValid(ActiveDocument document, boolean isValid) + { + this.enableDisableSave(); + } + + private void enableDisableSave() + { + var device = this.getDevice(); + this.menuSaveDevice.setDisable(!this.activeDocument.isValid() || device == null || !this.activeDocument.isModified()); + } + + private UsbDevice getDevice() + { + List deviceList = usbDeviceManager.getDevices(); + if (deviceList.isEmpty()) + { + return null; + } + return deviceList.get(0); + } + + private void loadConfigFromFile() { + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("Open Config File"); + fileChooser.getExtensionFilters().addAll( + new FileChooser.ExtensionFilter("XML", "*.xml"), + new FileChooser.ExtensionFilter("All Files", "*.*") + ); + + File theFile = fileChooser.showOpenDialog(this.window); + if (theFile == null) { + return; // User canceled. + } + this.loadConfigFromFile(theFile); + } + + private void loadConfigFromFile(File theFile) + { + try { + var builder = DocumentBuilderFactory.newDefaultInstance().newDocumentBuilder(); + var doc = builder.parse(theFile); + BoardConfig boardConfig; + + if (doc.hasChildNodes() + && doc.getFirstChild().getNodeName() == "SCSI2SD") + { + var boardConfigNode = doc.getElementsByTagName("S2S_BoardCfg"); + if (boardConfigNode != null && boardConfigNode.getLength() > 0) + { + // do stuff + boardConfig = new BoardConfig(boardConfigNode.item(0)); + } + else + { + throw new IllegalArgumentException("Missing S2S_BoardCfg node"); + } + + ConfigRoot root = new ConfigRoot(); + root.setBoardConfig(boardConfig); + + int maxDisks = 7; + var sdCapacity = 0; + List deviceList = this.usbDeviceManager.getDevices(); + if (!deviceList.isEmpty()) + { + maxDisks = deviceList.get(0).getMaxDisks(); + sdCapacity = deviceList.get(0).getSdCapacity(); + } + + var lastSectorUsed = 0l; + var configs = new ArrayList(); + var diskNodeList = doc.getElementsByTagName("SCSITarget"); + for (var i = 0; i < maxDisks && i < diskNodeList.getLength(); ++i) { + var disk = new DiskConfig(diskNodeList.item(i)); + lastSectorUsed = Math.max(lastSectorUsed, disk.getSdSectorEnd()); + configs.add(disk); + } + + var fallbackSize = Math.max(2l*1024*1024*1024/512, lastSectorUsed + DiskList.reservedSize); + var diskList = new DiskList(sdCapacity > 0 ? sdCapacity : fallbackSize); + configs.forEach(c -> diskList.add(c)); + + root.setDiskList(diskList); + + activeDocument.loadDocument(root, theFile.getCanonicalPath()); + this.saveRecent(theFile.getCanonicalPath()); + } + else { + throw new IllegalArgumentException("Missing SCSI2SD node"); + } + + } catch (Exception e) { + Alert alert = new Alert(Alert.AlertType.ERROR); + alert.setTitle("Load failed"); + alert.setContentText(e.getLocalizedMessage()); + alert.show(); + } + } + + private void saveConfigToFile() + { + var root = this.activeDocument.getActiveDocument(); + + var builder = new StringBuilder(); + builder.append("\n"); + builder.append(root.getBoardConfig().toXML()); + + root.getDiskList().getDisks().stream() + .filter(c -> c.getType() == DiskConfig.Type.DISK) + .forEach(c -> builder.append(c.toXML())); + + builder.append("\n"); + + + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("Save Config File"); + fileChooser.getExtensionFilters().addAll( + new FileChooser.ExtensionFilter("XML", "*.xml"), + new FileChooser.ExtensionFilter("All Files", "*.*") + ); + + var filename = this.activeDocument.getOriginalDocumentName(); + if (filename != null) { + Path path = Path.of(filename); + fileChooser.setInitialDirectory(path.getParent().toFile()); + fileChooser.setInitialFileName(path.getFileName().toFile().toString()); + } + + File theFile = fileChooser.showSaveDialog(this.window); + if (theFile == null) + { + return; // User cancelled. + } + + try { + var path = theFile.toPath(); + if (fileChooser.getSelectedExtensionFilter().getExtensions().contains("*.xml") && + !path.getFileName().toString().toLowerCase().endsWith(".xml")) + { + path = Path.of(path.toString() + ".xml"); + } + + Files.write(path, builder.toString().getBytes(StandardCharsets.US_ASCII)); + + this.window.setTitle(Title); + this.saveRecent(path.toString()); + + } catch (Exception e) { + Alert alert = new Alert(Alert.AlertType.ERROR); + alert.setTitle("Save failed"); + alert.setContentText(e.getLocalizedMessage()); + alert.show(); + } + } + + private void revert() { + var orig = this.activeDocument.getOriginalDocument(); + if (orig != null) { + var revertedDocument = new ConfigRoot(orig); + this.activeDocument.loadDocument(revertedDocument, this.activeDocument.getOriginalDocumentName()); + } + } + + private void loadDefault() + { + var root = new ConfigRoot(); + root.setBoardConfig(new BoardConfig()); + root.setDiskList(new DiskList(2l*1024*1024*1024/512)); + this.activeDocument.loadDocument(root, null); + } + + private void populateRecentFromPrefs() { + this.recent.clear(); + for (int i = 0; i < RECENT_MAX; ++i) { + var prop = prefs.get(RECENT + i, null); + if (prop != null) { + this.recent.addLast(prop); + } + } + + this.setRecentMenuItems(); + } + + private void saveRecent(String newFile) { + this.recent.remove(newFile); + this.recent.addFirst(newFile); + + while (recent.size() > RECENT_MAX) { + this.recent.removeLast(); + } + + var i = 0; + for (var file : this.recent) { + prefs.put(RECENT + i, file); + ++i; + } + + this.setRecentMenuItems(); + } + + private void setRecentMenuItems() { + this.menuRecent.getItems().clear(); + for (var file : this.recent) { + var menuItem = new MenuItem(file); + menuItem.setOnAction(h -> this.loadConfigFromFile(new File(file))); + this.menuRecent.getItems().add(menuItem); + } + } +} diff --git a/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/StatusTabController.java b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/StatusTabController.java new file mode 100644 index 0000000..7248fdd --- /dev/null +++ b/scsi2sd.ui/src/main/java/com/codesrc/scsi2sd/presentation/StatusTabController.java @@ -0,0 +1,103 @@ +// Copyright (C) 2019 Michael McMaster +// +// This file is part of scsi2sd-util. +// +// scsi2sd-util is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// scsi2sd-util is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with scsi2sd-util. If not, see . + +package com.codesrc.scsi2sd.presentation; + +import com.codesrc.scsi2sd.ScreensConfiguration; +import com.codesrc.scsi2sd.io.*; +import javafx.application.Platform; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.Scene; +import org.slf4j.LoggerFactory; + +import java.net.URL; +import java.util.List; +import java.util.ResourceBundle; + +public class StatusTabController implements FXController { + private static org.slf4j.Logger LOGGER = LoggerFactory.getLogger(StatusTabController.class); + + private ScreensConfiguration screens; + private FXWindow window; + + private UsbDeviceManager usbDeviceManager; + + @FXML private Label status; + @FXML private Label hwVersion; + @FXML private Label fwVersion; + @FXML private Label sdCapacity; + + public StatusTabController(ScreensConfiguration screens, UsbDeviceManager usbDeviceManager) { + this.screens = screens; + this.usbDeviceManager = usbDeviceManager; + } + + @Override + public void setWindow(FXWindow window) { + this.window = window; + } + + @Override + public void onSceneCreating(Scene scene) + { + } + + @Override + public void initialize(URL url, ResourceBundle resourceBundle) { + status.setText("No device"); + + usbDeviceManager.getDeviceConnectedEvent().subscribe(e -> Platform.runLater(() -> this.handleUsbConnected(e))); + usbDeviceManager.getDeviceDisconnectedEvent().subscribe(e -> Platform.runLater(() -> this.handleUsbDisconnected(e))); + usbDeviceManager.getDeviceFailedEvent().subscribe(e -> Platform.runLater(() -> this.handleUsbFailedEvent(e))); + + List deviceList = usbDeviceManager.getDevices(); + if (!deviceList.isEmpty()) { + updateStatus(deviceList.get(0)); + } + } + + public void handleUsbConnected(UsbDeviceConnectedEvent usbDeviceConnectedEvent) { + updateStatus(usbDeviceConnectedEvent.getUsbDevice()); + } + + private void updateStatus(UsbDevice device) { + + status.setText("Connected"); + + hwVersion.setText(device.getHardwareDescription()); + fwVersion.setText(device.getFirmwareDescription()); + + StringBuilder sdSizeStr = new StringBuilder(); + long sectors = device.getSdCapacity(); + float gbSize = sectors * 512.0f / 1024 / 1024 / 1024; + sdSizeStr.append(String.format("%.1f", gbSize)).append("GiB") + .append(" (").append(sectors).append(" sectors)"); + if (sectors < (2 * 1024 * 1024 *1024 / 512)) { + sdSizeStr.append(" ! Obsolete Card"); + } + sdCapacity.setText(sdSizeStr.toString()); + } + + public void handleUsbDisconnected(UsbDeviceDisconnectedEvent usbDeviceDisconnectedEvent) { + status.setText("Disconnected"); + } + + private void handleUsbFailedEvent(UsbDeviceFailedEvent usbDeviceFailedEvent) { + status.setText("Failed: " + usbDeviceFailedEvent.getUsbDevicePath() + ". Error: " + usbDeviceFailedEvent.getMessage()); + } +} diff --git a/scsi2sd.ui/src/main/java/module-info.java b/scsi2sd.ui/src/main/java/module-info.java new file mode 100644 index 0000000..07130b8 --- /dev/null +++ b/scsi2sd.ui/src/main/java/module-info.java @@ -0,0 +1,36 @@ +// Copyright (C) 2019 Michael McMaster +// +// This file is part of scsi2sd-util. +// +// scsi2sd-util is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// scsi2sd-util is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with scsi2sd-util. If not, see . + +module com.codesrc.scsi2sd.ui { + exports com.codesrc.scsi2sd; + + opens com.codesrc.scsi2sd.presentation to javafx.fxml, javafx.base; + + requires java.logging; + requires org.controlsfx.controls; + requires javafx.controls; + requires joptsimple; + requires org.slf4j; + requires org.slf4j.jul; + requires java.xml; + requires java.prefs; + + requires transitive com.codesrc.scsi2sd.io; + requires transitive javafx.base; + requires transitive javafx.fxml; + requires transitive javafx.graphics; +} diff --git a/scsi2sd.ui/src/main/resources/app.properties b/scsi2sd.ui/src/main/resources/app.properties new file mode 100644 index 0000000..25a7b50 --- /dev/null +++ b/scsi2sd.ui/src/main/resources/app.properties @@ -0,0 +1,5 @@ + +com.codesrc.scsi2sd.app.version = 1.0-BETA + +com.codesrc.scsi2sd.app.licence.type = GPLv3 +com.codesrc.scsi2sd.app.licence.url = https://www.gnu.org/licenses/gpl-3.0.html diff --git a/scsi2sd.ui/src/main/resources/css/main.css b/scsi2sd.ui/src/main/resources/css/main.css new file mode 100644 index 0000000..69a8267 --- /dev/null +++ b/scsi2sd.ui/src/main/resources/css/main.css @@ -0,0 +1,3 @@ +.root { + -fx-background-color: #FFFFFF; + } \ No newline at end of file diff --git a/scsi2sd.ui/src/main/resources/css/splash.css b/scsi2sd.ui/src/main/resources/css/splash.css new file mode 100644 index 0000000..152752f --- /dev/null +++ b/scsi2sd.ui/src/main/resources/css/splash.css @@ -0,0 +1,6 @@ + +.root { + -fx-border-style: solid; + -fx-border-color: #AAAAAA; + -fx-border-width: 1; + } \ No newline at end of file diff --git a/src/main/resources/fx/about.fxml b/scsi2sd.ui/src/main/resources/fx/about.fxml similarity index 81% rename from src/main/resources/fx/about.fxml rename to scsi2sd.ui/src/main/resources/fx/about.fxml index 8faaec1..e3c148f 100644 --- a/src/main/resources/fx/about.fxml +++ b/scsi2sd.ui/src/main/resources/fx/about.fxml @@ -7,8 +7,6 @@ - - @@ -16,7 +14,7 @@ - + @@ -28,9 +26,9 @@ -