Compare commits

...
19 Commits
Author SHA1 Message Date
RuiFPB fb097a0c49 Fix scaling issues on some windows 2026-10-02 19:30:07 +01:00
RuiFPB 3ee3a7e300 Add release action 2026-09-29 13:44:53 +01:00
RuiFPB b2810fa062 Add missing mike42 machine flag to run options 2026-09-29 12:15:37 +01:00
RuiFPB c9f9121477 Add install maven step (gitea actions 2026-09-29 01:05:01 +01:00
RuiFPB 91ef70fe4e Disable maven cache on jdk setup (gitea actions) 2026-09-29 01:02:24 +01:00
RuiFPB 69bf418f43 Set container image to use to run the pipeline 2026-09-29 00:51:56 +01:00
RuiFPB 540f0dff58 Add gitea action build pipeline 2026-09-29 00:46:42 +01:00
RuiFPB 17668e2c3c Only run X11 resource reader if on Linux 2026-09-28 10:24:02 +01:00
RuiFPB 3353b8436c Implement UI for the VIA/PIA 2026-09-26 19:47:15 +01:00
RuiFPB e635d0a608 Add binary converter to Utils 2026-09-26 19:46:54 +01:00
RuiFPB 558af36cd5 Start implementation of the VIA6522
Only PA and PB implemented, not tested
2026-09-26 19:14:20 +01:00
RuiFPB 5fa6fa0fc0 Clean up imports on the Mike42 machine 2026-09-26 19:13:24 +01:00
RuiFPB f152ed9df5 Fix scaling issues on Linux 2026-09-25 00:38:28 +01:00
RuiFPB 87217afa6e Add Mike42 Machine Type 2026-09-25 00:37:52 +01:00
Retrograde b9997e3edc Use HTTPS instead of HTTP in Schema referencs in the Maven POM file. (#25)
Use HTTPS instead of HTTP in Schema referencs in the Maven POM file.

This fixes cvc-elt.1.a: Cannot find the declaration of element 'project'. errors when building fx from VS.code.

Reviewed-on: https://codeberg.org/sethm/symon/pulls/25
Reviewed-by: Seth Morabito <sethm@noreply.codeberg.org>
2026-06-25 00:08:22 +02:00
Retrograde 45c8a69d7b FlatLaf look-and-feel with better Dark mode support. (#23)
This PR configures Symon to use the FlatLaf Swing look-and-feel, giving both a slightly more modern look but also much better support for Dark/Light UI modes. Additional small tweaks to make the UI more readable in both Dark/Light modes.

Detection of system theme also added, selecting the Dark/Light FlatLaf variant accordingly.

Gets rid of need for the hardcoded Metal UI for text fields in StatusPanel workaround as well, as FlatLaf renders these small fields correctly.

Reviewed-on: https://codeberg.org/sethm/symon/pulls/23
Reviewed-by: Seth Morabito <sethm@noreply.codeberg.org>
2026-06-24 23:13:23 +02:00
Retrograde 2e2163fefb Added handling of -h/--help CLI option, to print out available options. (#22)
Small CLI improvement: adding a -h / --help switch, to be able to get a printout of the available options.

Reviewed-on: https://codeberg.org/sethm/symon/pulls/22
Reviewed-by: Seth Morabito <sethm@noreply.codeberg.org>
2026-06-24 20:45:51 +02:00
Seth Morabito 995b372f55 Update README with Codeberg links 2026-03-25 13:16:52 -07:00
Seth Morabito 47c87aac56 Update README for high-dpi 2025-04-13 11:38:33 -07:00
25 changed files with 677 additions and 96 deletions

No files matched your search

+36
View File
@@ -0,0 +1,36 @@
name: Build and Test
on:
push:
branches:
- master
pull_request:
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
container:
image: catthehacker/ubuntu:act-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup JDK
uses: actions/setup-java@v6
with:
distribution: temurin
java-version: '21'
#cache: maven
- name: Install Maven
run: apt-get update && apt-get install -y maven
- name: Test and package
run: mvn -B verify
- name: Upload jar
uses: actions/upload-artifact@v3
with:
name: package
path: target/*.jar
+33
View File
@@ -0,0 +1,33 @@
name: Release
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ubuntu-latest
container:
image: catthehacker/ubuntu:act-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup JDK
uses: actions/setup-java@v6
with:
distribution: temurin
java-version: '21'
- name: Install Maven
run: apt-get update && apt-get install -y maven
- name: Package
run: mvn -B verify
- name: Create release
uses: https://gitea.com/actions/gitea-release-action@v1
with:
files: |-
target/symon*.jar
+33 -15
View File
@@ -1,13 +1,13 @@
SYMON - A 6502 System Simulator kSYMON - A 6502 System Simulator
=============================== ===============================
**Version:** 1.5.0 **Version:** 1.5.1.1k
**Last Updated:** 30 March, 2025 **Last Updated:** 24 September, 2026
See the file COPYING for license. See the file COPYING for license.
![Symon Simulator in Action](https://github.com/sethm/symon/raw/master/screenshots/full.jpg) ![Symon Simulator in Action](https://codeberg.org/sethm/symon/raw/master/screenshots/full.jpg)
## 1.0 About ## 1.0 About
@@ -71,9 +71,16 @@ memory.
- `$6000`--`$600F`: 6522 VIA - `$6000`--`$600F`: 6522 VIA
- `$8000`--`$FFFF`: 16KB ROM - `$8000`--`$FFFF`: 16KB ROM
#### 3.1.5 Mike42 Memory Map
- `$0000`--`$7FFF`: 32KB RAM
- `$8000`--`$800F`: 6522 VIA
- `$8400`--`$8403`: MOS 6551 ACIA (Serial Console)
- `$C000`--`$FFFF`: 16KB ROM
### 3.2 Serial Console and CPU Status ### 3.2 Serial Console and CPU Status
![Serial Console](https://github.com/sethm/symon/raw/master/screenshots/console.png) ![Serial Console](https://codeberg.org/sethm/symon/raw/master/screenshots/console.png)
The main window of the simulator acts as the primary Input/Output The main window of the simulator acts as the primary Input/Output
system through a virtual serial terminal. It also provides CPU status. system through a virtual serial terminal. It also provides CPU status.
@@ -100,13 +107,13 @@ see the official datasheet:
- [MOS 6551 ACIA](http://archive.6502.org/datasheets/mos_6551_acia.pdf) - [MOS 6551 ACIA](http://archive.6502.org/datasheets/mos_6551_acia.pdf)
![Font Selection](https://github.com/sethm/symon/raw/master/screenshots/font_selection.png) ![Font Selection](https://codeberg.org/sethm/symon/raw/master/screenshots/font_selection.png)
The console supports font sizes from 10 to 20 points. The console supports font sizes from 10 to 20 points.
### 3.3 ROM Loading ### 3.3 ROM Loading
![ROM Loading](https://github.com/sethm/symon/raw/master/screenshots/load_rom.png) ![ROM Loading](https://codeberg.org/sethm/symon/raw/master/screenshots/load_rom.png)
Symon can load any appropriately sized ROM image. The Symon Symon can load any appropriately sized ROM image. The Symon
architecture expects as 16KB (16384 byte) ROM image, while the architecture expects as 16KB (16384 byte) ROM image, while the
@@ -117,32 +124,32 @@ address.
### 3.4 Memory Window ### 3.4 Memory Window
![Memory Window](https://github.com/sethm/symon/raw/master/screenshots/memory_window.png) ![Memory Window](https://codeberg.org/sethm/symon/raw/master/screenshots/memory_window.png)
Memory contents can be viewed (and edited) one page at a time through the Memory Window. Memory contents can be viewed (and edited) one page at a time through the Memory Window.
### 3.5 Trace Log ### 3.5 Trace Log
![Trace Log](https://github.com/sethm/symon/raw/master/screenshots/trace_log.png) ![Trace Log](https://codeberg.org/sethm/symon/raw/master/screenshots/trace_log.png)
The last 20,000 execution steps are disassembled and logged to the Trace Log The last 20,000 execution steps are disassembled and logged to the Trace Log
Window. Window.
### 3.6 Simulator Speeds ### 3.6 Simulator Speeds
![Speeds](https://github.com/sethm/symon/raw/master/screenshots/simulator_menu.png) ![Speeds](https://codeberg.org/sethm/symon/raw/master/screenshots/simulator_menu.png)
Simulated speeds may be set from 1MHz to 8MHz. Simulated speeds may be set from 1MHz to 8MHz.
### 3.7 Breakpoints ### 3.7 Breakpoints
![Breakpoints](https://github.com/sethm/symon/raw/master/screenshots/breakpoints.png) ![Breakpoints](https://codeberg.org/sethm/symon/raw/master/screenshots/breakpoints.png)
Breakpoints can be set and removed through the Breakpoints window. Breakpoints can be set and removed through the Breakpoints window.
### 3.8 Experimental 6545 CRTC Video ### 3.8 Experimental 6545 CRTC Video
![Composite Video](https://github.com/sethm/symon/raw/master/screenshots/video_window.png) ![Composite Video](https://codeberg.org/sethm/symon/raw/master/screenshots/video_window.png)
This feature is highly experimental. It's possible to open a video window This feature is highly experimental. It's possible to open a video window
from the "View" menu. This window simulates the output of a MOS 6545 CRT from the "View" menu. This window simulates the output of a MOS 6545 CRT
@@ -229,7 +236,18 @@ to specify machine type and CPU type. The options are:
- `-r`,`-rom <file>`: Use the specified file as the ROM image. - `-r`,`-rom <file>`: Use the specified file as the ROM image.
- `-b`,`-brk`: Halt the simulator on a BRK instruction (default is to continue) - `-b`,`-brk`: Halt the simulator on a BRK instruction (default is to continue)
### 4.2 ROM images ### 4.2 High-DPI UI scaling
In certain environments with high-DPI (e.g. 4K) displays, the UI may
not automatically scale and appear quite tiny. This can be fixed
with the command line argument `-Dsun.java2d.uiScale=<factor>`,
for example:
```
java -Dsun.java2d.uiScale=2.0 -jar symon-1.5.0.jar
```
### 4.3 ROM images
The simulator requires a ROM image loaded into memory to work The simulator requires a ROM image loaded into memory to work
properly. Without a ROM in memory, the simulator will not be able to properly. Without a ROM in memory, the simulator will not be able to
@@ -247,7 +265,7 @@ architecture named 'ehbasic.rom', containing Lee Davison's Enhanced
*Note*: Presently, EhBASIC only works with the Symon machine *Note*: Presently, EhBASIC only works with the Symon machine
architecture, not with MULTICOMP. architecture, not with MULTICOMP.
### 4.3 Loading A Program ### 4.4 Loading A Program
In addition to ROM images, programs in the form of raw binary object files can In addition to ROM images, programs in the form of raw binary object files can
be loaded directly into memory from "Load Program..." in the File menu. be loaded directly into memory from "Load Program..." in the File menu.
@@ -263,7 +281,7 @@ for testing.
- 'hello.prg' will continuously print "Hello, 6502 World!" to the console. - 'hello.prg' will continuously print "Hello, 6502 World!" to the console.
### 4.4 Running ### 4.5 Running
After loading a program or ROM image, clicking "Run" will start the simulator After loading a program or ROM image, clicking "Run" will start the simulator
running. running.
+34 -11
View File
@@ -1,10 +1,11 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <project xmlns="https://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4.0.0.xsd"> xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>com.loomcom.symon</groupId> <groupId>com.loomcom.symon</groupId>
<artifactId>symon</artifactId> <artifactId>symon</artifactId>
<packaging>jar</packaging> <packaging>jar</packaging>
<version>1.5.0</version> <version>1.5.1.1k</version>
<name>symon</name> <name>symon</name>
<url>https://git.loomcom.com/seth/symon</url> <url>https://git.loomcom.com/seth/symon</url>
<properties> <properties>
@@ -15,11 +16,32 @@
UTF-8 UTF-8
</project.reporting.outputEncoding> </project.reporting.outputEncoding>
</properties> </properties>
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>ch.qos.logback</groupId> <groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId> <artifactId>logback-classic</artifactId>
<version>1.5.16</version> <version>1.5.34</version>
</dependency>
<dependency>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
<version>1.5.0</version>
</dependency>
<dependency>
<groupId>com.formdev</groupId>
<artifactId>flatlaf</artifactId>
<version>3.7.1</version>
</dependency>
<dependency>
<groupId>com.github.Dansoftowner</groupId>
<artifactId>jSystemThemeDetector</artifactId>
<version>3.9.1</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
@@ -34,9 +56,9 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>commons-cli</groupId> <groupId>net.java.dev.jna</groupId>
<artifactId>commons-cli</artifactId> <artifactId>jna</artifactId>
<version>1.5.0</version> <version>5.14.0</version>
</dependency> </dependency>
</dependencies> </dependencies>
@@ -48,7 +70,8 @@
<version>3.2.0</version> <version>3.2.0</version>
<configuration> <configuration>
<transformers> <transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.loomcom.symon.Main</mainClass> <mainClass>com.loomcom.symon.Main</mainClass>
</transformer> </transformer>
</transformers> </transformers>
@@ -79,8 +102,8 @@
<version>3.7.0</version> <version>3.7.0</version>
<configuration> <configuration>
<compilerArgument>-Xlint:unchecked</compilerArgument> <compilerArgument>-Xlint:unchecked</compilerArgument>
<source>11</source> <source>16</source>
<target>11</target> <target>16</target>
</configuration> </configuration>
</plugin> </plugin>
@@ -120,4 +143,4 @@
</plugin> </plugin>
</plugins> </plugins>
</build> </build>
</project> </project>
+20 -26
View File
@@ -25,19 +25,20 @@
package com.loomcom.symon; package com.loomcom.symon;
import com.loomcom.symon.machines.*; import com.loomcom.symon.machines.*;
import com.loomcom.symon.ui.Ui;
import org.apache.commons.cli.*; import org.apache.commons.cli.*;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.Locale; import java.util.Locale;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import javax.swing.UIManager;
public class Main { public class Main {
private final static Logger logger = LoggerFactory.getLogger(Main.class.getName()); private final static Logger logger = LoggerFactory.getLogger(Main.class.getName());
/** /**
* Main entry point to the simulator. Creates a simulator and shows the main * Main entry point to the simulator. Creates a simulator and shows the main
* window. * window.
* *
@@ -49,6 +50,7 @@ public class Main {
Options options = new Options(); Options options = new Options();
options.addOption(new Option("h", "help", false, "Print this message."));
options.addOption(new Option("m", "machine", true, "Specify machine type.")); options.addOption(new Option("m", "machine", true, "Specify machine type."));
options.addOption(new Option("c", "cpu", true, "Specify CPU type.")); options.addOption(new Option("c", "cpu", true, "Specify CPU type."));
options.addOption(new Option("r", "rom", true, "Specify ROM file.")); options.addOption(new Option("r", "rom", true, "Specify ROM file."));
@@ -57,11 +59,18 @@ public class Main {
CommandLineParser parser = new DefaultParser(); CommandLineParser parser = new DefaultParser();
try { try {
CommandLine line = parser.parse(options, args); CommandLine line = parser.parse(options, args);
InstructionTable.CpuBehavior cpuBehavior = null; InstructionTable.CpuBehavior cpuBehavior = null;
String romFile = null; String romFile = null;
boolean haltOnBreak = false; boolean haltOnBreak = false;
if (line.hasOption("help")) {
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp("symon", options, false);
return;
}
if (line.hasOption("machine")) { if (line.hasOption("machine")) {
String machine = line.getOptionValue("machine").toLowerCase(Locale.ENGLISH); String machine = line.getOptionValue("machine").toLowerCase(Locale.ENGLISH);
switch (machine) { switch (machine) {
@@ -77,6 +86,9 @@ public class Main {
case "beneater": case "beneater":
machineClass = BenEaterMachine.class; machineClass = BenEaterMachine.class;
break; break;
case "mike42":
machineClass = MichaelBillingtonMachine.class;
break;
default: default:
logger.error("Could not start Symon. Unknown machine type {}", machine); logger.error("Could not start Symon. Unknown machine type {}", machine);
return; return;
@@ -109,28 +121,11 @@ public class Main {
haltOnBreak = true; haltOnBreak = true;
} }
Ui.initUi();
while (true) { while (true) {
if (machineClass == null) { if (machineClass == null) {
Object[] possibilities = {"Symon", "Multicomp", "Simple", "BenEater"}; machineClass = Ui.showMachineSelectionDialog();
String s = (String)JOptionPane.showInputDialog(
null,
"Please choose the machine type to be emulated:",
"Machine selection",
JOptionPane.PLAIN_MESSAGE,
null,
possibilities,
"Symon");
if (s != null && s.equals("Multicomp")) {
machineClass = MulticompMachine.class;
} else if (s != null && s.equals("Simple")) {
machineClass = SimpleMachine.class;
} else if (s != null && s.equals("BenEater")) {
machineClass = BenEaterMachine.class;
} else {
machineClass = SymonMachine.class;
}
} }
if (cpuBehavior == null) { if (cpuBehavior == null) {
@@ -140,9 +135,8 @@ public class Main {
final Simulator simulator = new Simulator(machineClass, cpuBehavior, romFile, haltOnBreak); final Simulator simulator = new Simulator(machineClass, cpuBehavior, romFile, haltOnBreak);
SwingUtilities.invokeLater(() -> { SwingUtilities.invokeLater(() -> {
// Create the main UI window:
try { try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
// Create the main UI window
simulator.createAndShowUi(); simulator.createAndShowUi();
} catch (Exception e) { } catch (Exception e) {
logger.error("Error during Symon UI initialization: {}", e.getMessage()); logger.error("Error during Symon UI initialization: {}", e.getMessage());
@@ -150,7 +144,6 @@ public class Main {
} }
}); });
Simulator.MainCommand cmd = simulator.waitForCommand(); Simulator.MainCommand cmd = simulator.waitForCommand();
if (cmd.equals(Simulator.MainCommand.SELECTMACHINE)) { if (cmd.equals(Simulator.MainCommand.SELECTMACHINE)) {
@@ -163,4 +156,5 @@ public class Main {
logger.error("Could not start Symon. Reason: {}", ex.getMessage()); logger.error("Could not start Symon. Reason: {}", ex.getMessage());
} }
} }
} }
@@ -23,6 +23,7 @@
package com.loomcom.symon; package com.loomcom.symon;
import com.formdev.flatlaf.util.UIScale;
import com.loomcom.symon.devices.Memory; import com.loomcom.symon.devices.Memory;
import com.loomcom.symon.exceptions.*; import com.loomcom.symon.exceptions.*;
import com.loomcom.symon.machines.Machine; import com.loomcom.symon.machines.Machine;
@@ -53,7 +54,7 @@ public class Simulator {
// UI constants // UI constants
private static final int DEFAULT_FONT_SIZE = 12; private static final int DEFAULT_FONT_SIZE = 12;
private static final Font DEFAULT_FONT = new Font(Font.MONOSPACED, Font.PLAIN, DEFAULT_FONT_SIZE); private static final Font DEFAULT_FONT = new Font(Font.MONOSPACED, Font.PLAIN, UIScale.scale(DEFAULT_FONT_SIZE));
private static final int CONSOLE_BORDER_WIDTH = 10; private static final int CONSOLE_BORDER_WIDTH = 10;
// Clock periods, in NS, for each speed. 0MHz, 1MHz, 2MHz, 3MHz, 4MHz, 5MHz, 6MHz, 7MHz, 8MHz. // Clock periods, in NS, for each speed. 0MHz, 1MHz, 2MHz, 3MHz, 4MHz, 5MHz, 6MHz, 7MHz, 8MHz.
@@ -25,6 +25,7 @@ package com.loomcom.symon.devices;
import com.loomcom.symon.exceptions.MemoryAccessException; import com.loomcom.symon.exceptions.MemoryAccessException;
import com.loomcom.symon.exceptions.MemoryRangeException; import com.loomcom.symon.exceptions.MemoryRangeException;
import com.loomcom.symon.util.Utils;
/** /**
* Skeleton for future implementation of a MOS 6522 VIA. * Skeleton for future implementation of a MOS 6522 VIA.
@@ -32,18 +33,82 @@ import com.loomcom.symon.exceptions.MemoryRangeException;
public class Via6522 extends Pia { public class Via6522 extends Pia {
public static final int VIA_SIZE = 16; public static final int VIA_SIZE = 16;
static final int IOB_REG = 0; // Output Register B / Input Register B
static final int IOA_REG = 1; // Output Register A / Input Register A
static final int DDRB_REG = 2; // Data Direction Register B
static final int DDRA_REG = 3; // Data Direction Register A
static final int T1CL_REG = 4; // T1 Low-Order Counter / Latches
static final int T1CH_REG = 5; // T1 High-Order Counter
static final int T1LL_REG = 6; // T1 Low-Order Latches
static final int T1LH_REG = 7; // T1 High-Order Latches
static final int T2CL_REG = 8; // T2 Low-Order Counter / Latches
static final int T2CH_REG = 9; // T2 High-Order Counter
static final int SR_REG = 10; // Shift Register
static final int AuxC_REG = 11; // Auxiliary Control Register
static final int PerC_REG = 12; // Peripheral Control Register
static final int IntF_REG = 13; // Interrupt Flag Register
static final int IntE_REG = 14; // Interrupt Enable Register
static final int IOANH_REG = 15; // Same as Register 1 (IOA), but no Handshake
private int outputRegisterB, inputRegisterB;
private int outputRegisterA, inputRegisterA;
private int dataDirectionB, dataDirectionA;
public Via6522(int address) throws MemoryRangeException { public Via6522(int address) throws MemoryRangeException {
super(address, address + VIA_SIZE - 1, "MOS 6522 VIA"); super(address, address + VIA_SIZE - 1, "MOS 6522 VIA");
outputRegisterA = outputRegisterB = 0;
dataDirectionA = dataDirectionB = 0;
} }
@Override @Override
public void write(int address, int data) throws MemoryAccessException { public void write(int address, int data) throws MemoryAccessException {
// TODO: Implement write. switch (address) {
case IOB_REG -> outputRegisterB = data & 0xFF;
case IOA_REG -> outputRegisterA = data & 0xFF;
case DDRB_REG -> dataDirectionB = data & 0xFF;
case DDRA_REG -> dataDirectionA = data & 0xFF;
case IOANH_REG -> outputRegisterA = data & 0xFF;
default -> throw new MemoryAccessException("No Register or Not Implemented!");
}
} }
@Override @Override
public int read(int address, boolean cpuAccess) throws MemoryAccessException { public int read(int address, boolean cpuAccess) throws MemoryAccessException {
// TODO: Implement read. return switch (address) {
return 0; case IOB_REG -> pbRead();
case IOA_REG -> paRead();
case DDRB_REG -> dataDirectionB;
case DDRA_REG -> dataDirectionA;
case IOANH_REG -> paRead();
default -> throw new MemoryAccessException("No Register or Not Implemented!");
};
}
public void setInputRegisterB(int data) {
inputRegisterB = data & 0xFF;
}
public void setInputRegisterA(int data) {
inputRegisterA = data & 0xFF;
}
public String getPBStatus() {
return "%" + Utils.byteToBin(pbRead());
}
public String getPAStatus() {
return "%" + Utils.byteToBin(paRead());
}
private int pbRead() {
// Return bits of output register where DDRB is 1 and input register where DDRB is 0
return (outputRegisterB & dataDirectionB) | (inputRegisterB & ~dataDirectionB);
}
private int paRead() {
// Return bits of output register where DDRB is 1 and input register where DDRB is 0
return (outputRegisterA & dataDirectionA) | (inputRegisterA & ~dataDirectionA);
} }
} }
@@ -0,0 +1,133 @@
package com.loomcom.symon.machines;
import com.loomcom.symon.Bus;
import com.loomcom.symon.Cpu;
import com.loomcom.symon.devices.*;
import com.loomcom.symon.exceptions.MemoryRangeException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
public class MichaelBillingtonMachine implements Machine{
private static final Logger logger = LoggerFactory.getLogger(MichaelBillingtonMachine.class.getName());
// Constants used by the simulated system
private static final int BUS_BOTTOM = 0x0000;
private static final int BUS_TOP = 0xffff;
// 32k of ram, from $0000 to $7fff
private static final int MEMORY_BASE = 0x0000;
private static final int MEMORY_SIZE = 0x8000;
// VIA at $8000-$800f
private static final int PIA_BASE = 0x8000;
// ACIA at $8400-$8403
private static final int ACIA_BASE = 0x8400;
// 16k ROM from $c000 to $ffff
private static final int ROM_BASE = 0xc000;
private static final int ROM_SIZE = 0x4000;
private final Bus bus;
private final Cpu cpu;
private final Acia acia;
private final Pia pia;
private final Memory ram;
private Memory rom;
public MichaelBillingtonMachine(String romFile) throws Exception {
this.bus = new Bus(BUS_BOTTOM, BUS_TOP);
this.cpu = new Cpu();
this.ram = new Memory(MEMORY_BASE, MEMORY_BASE + MEMORY_SIZE - 1);
this.pia = new Via6522(PIA_BASE);
this.acia = new Acia6551(ACIA_BASE);
bus.addCpu(cpu);
bus.addDevice(ram);
bus.addDevice(pia);
bus.addDevice(acia);
if (romFile != null) {
File romImage = new File(romFile);
if (romImage.canRead()) {
logger.info("Loading ROM image from file {}", romImage);
this.rom = Memory.makeROM(ROM_BASE, ROM_BASE + ROM_SIZE - 1, romImage);
} else {
logger.info("Default ROM file {} not found, loading empty R/W memory image.", romImage);
this.rom = Memory.makeRAM(ROM_BASE, ROM_BASE + ROM_SIZE - 1);
}
} else {
logger.info("No ROM file specified, loading empty R/W memory image.");
this.rom = Memory.makeRAM(ROM_BASE, ROM_BASE + ROM_SIZE - 1);
}
bus.addDevice(rom);
}
@Override
public Bus getBus() {
return bus;
}
@Override
public Cpu getCpu() {
return cpu;
}
@Override
public Memory getRam() {
return ram;
}
@Override
public Acia getAcia() {
return acia;
}
@Override
public Pia getPia() {
return pia;
}
@Override
public Crtc getCrtc() {
return null;
}
@Override
public Memory getRom() {
return rom;
}
@Override
public void setRom(Memory rom) throws MemoryRangeException {
if (this.rom != null) {
bus.removeDevice(this.rom);
}
this.rom = rom;
bus.addDevice(this.rom);
}
@Override
public int getRomBase() {
return ROM_BASE;
}
@Override
public int getRomSize() {
return ROM_SIZE;
}
@Override
public int getMemorySize() {
return MEMORY_SIZE;
}
@Override
public String getName() {
return "mike42";
}
}
@@ -23,6 +23,7 @@
package com.loomcom.symon.ui; package com.loomcom.symon.ui;
import com.formdev.flatlaf.util.UIScale;
import com.loomcom.symon.Breakpoints; import com.loomcom.symon.Breakpoints;
import com.loomcom.symon.util.Utils; import com.loomcom.symon.util.Utils;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -40,7 +41,7 @@ public class BreakpointsWindow extends JFrame {
private static final Logger logger = LoggerFactory.getLogger(BreakpointsWindow.class); private static final Logger logger = LoggerFactory.getLogger(BreakpointsWindow.class);
private static final Dimension FRAME_SIZE = new Dimension(240, 280); private static final Dimension FRAME_SIZE = UIScale.scale(new Dimension(240, 280));
private static final String EMPTY_STRING = ""; private static final String EMPTY_STRING = "";
private final JFrame mainWindow; private final JFrame mainWindow;
@@ -70,7 +71,6 @@ public class BreakpointsWindow extends JFrame {
final JTable breakpointsTable = new JTable(breakpoints); final JTable breakpointsTable = new JTable(breakpoints);
breakpointsTable.setShowGrid(true); breakpointsTable.setShowGrid(true);
breakpointsTable.setGridColor(Color.LIGHT_GRAY);
breakpointsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); breakpointsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
breakpointsTable.getSelectionModel().addListSelectionListener(e -> removeButton.setEnabled(e.getFirstIndex() > -1)); breakpointsTable.getSelectionModel().addListSelectionListener(e -> removeButton.setEnabled(e.getFirstIndex() > -1));
@@ -23,6 +23,7 @@
package com.loomcom.symon.ui; package com.loomcom.symon.ui;
import com.formdev.flatlaf.util.UIScale;
import com.loomcom.symon.Bus; import com.loomcom.symon.Bus;
import com.loomcom.symon.exceptions.MemoryAccessException; import com.loomcom.symon.exceptions.MemoryAccessException;
import com.loomcom.symon.util.Utils; import com.loomcom.symon.util.Utils;
@@ -55,15 +56,15 @@ public class MemoryWindow extends JFrame implements ActionListener {
private JButton previousPageButton; private JButton previousPageButton;
private JButton nextPageButton; private JButton nextPageButton;
private static final Dimension MINIMUM_SIZE = new Dimension(320, 600); private static final Dimension MINIMUM_SIZE = UIScale.scale(new Dimension(400, 600));
// The width of column 0 (address), in pixels // The width of column 0 (address), in pixels
private static final int ADDR_COL_WIDTH = 48; private static final int ADDR_COL_WIDTH = UIScale.scale(64);
private static final int HEX_COL_WIDTH = 32; private static final int HEX_COL_WIDTH = UIScale.scale(32);
// The width of the ASCII cells, in pixels // The width of the ASCII cells, in pixels
private static final int ASCII_COL_WIDTH = 8; private static final int ASCII_COL_WIDTH = UIScale.scale(15);
// The start/end columns of the ASCII view // The start/end columns of the ASCII view
private static final int ASCII_COL_START = 9; private static final int ASCII_COL_START = 9;
@@ -23,17 +23,20 @@
package com.loomcom.symon.ui; package com.loomcom.symon.ui;
import com.formdev.flatlaf.util.UIScale;
import com.loomcom.symon.Cpu; import com.loomcom.symon.Cpu;
import com.loomcom.symon.CpuState; import com.loomcom.symon.CpuState;
import com.loomcom.symon.devices.Pia;
import com.loomcom.symon.devices.Via6522;
import com.loomcom.symon.machines.Machine; import com.loomcom.symon.machines.Machine;
import javax.swing.*; import javax.swing.*;
import javax.swing.border.Border; import javax.swing.border.Border;
import javax.swing.border.EtchedBorder; import javax.swing.border.EtchedBorder;
import javax.swing.plaf.metal.MetalTextFieldUI;
import java.awt.*; import java.awt.*;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import java.util.Objects;
/** /**
* UI component that displays the current state of the simulated CPU. * UI component that displays the current state of the simulated CPU.
@@ -69,12 +72,14 @@ public class StatusPanel extends JPanel {
private JTextField aField; private JTextField aField;
private JTextField xField; private JTextField xField;
private JTextField yField; private JTextField yField;
private JTextField piaBField;
private JTextField piaAField;
private final Machine machine; private final Machine machine;
private static final int EMPTY_BORDER = 10; private static final int EMPTY_BORDER = 10;
private static final Border LABEL_BORDER = BorderFactory.createEmptyBorder(0, 5, 0, 0); private static final Border LABEL_BORDER = BorderFactory.createEmptyBorder(0, 5, 0, 0);
private static final Font LABEL_FONT = new Font(Font.SANS_SERIF, Font.BOLD, 12); private static final Font LABEL_FONT = new Font(Font.SANS_SERIF, Font.BOLD, UIScale.scale(12));
private static final Dimension LARGE_TEXT_FIELD_SIZE = new Dimension(134, 22); private static final Dimension LARGE_TEXT_FIELD_SIZE = new Dimension(134, 22);
private static final Dimension SMALL_TEXT_FIELD_SIZE = new Dimension(65, 22); private static final Dimension SMALL_TEXT_FIELD_SIZE = new Dimension(65, 22);
@@ -86,7 +91,7 @@ public class StatusPanel extends JPanel {
private void createUi() { private void createUi() {
Border emptyBorder = BorderFactory.createEmptyBorder(EMPTY_BORDER, EMPTY_BORDER, Border emptyBorder = BorderFactory.createEmptyBorder(EMPTY_BORDER, EMPTY_BORDER,
EMPTY_BORDER, EMPTY_BORDER); EMPTY_BORDER, EMPTY_BORDER);
Border etchedBorder = BorderFactory.createEtchedBorder(EtchedBorder.LOWERED); Border etchedBorder = BorderFactory.createEtchedBorder(EtchedBorder.LOWERED);
setBorder(BorderFactory.createCompoundBorder(emptyBorder, etchedBorder)); setBorder(BorderFactory.createCompoundBorder(emptyBorder, etchedBorder));
@@ -99,20 +104,20 @@ public class StatusPanel extends JPanel {
JPanel statusFlagsPanel = new JPanel(); JPanel statusFlagsPanel = new JPanel();
statusFlagsPanel.setAlignmentX(LEFT_ALIGNMENT); statusFlagsPanel.setAlignmentX(LEFT_ALIGNMENT);
carryOn = new ImageIcon(this.getClass().getResource("/C_on.png")); carryOn = loadScaledIcon("/C_on.png");
carryOff = new ImageIcon(this.getClass().getResource("/C_off.png")); carryOff = loadScaledIcon("/C_off.png");
zeroOn = new ImageIcon(this.getClass().getResource("/Z_on.png")); zeroOn = loadScaledIcon("/Z_on.png");
zeroOff = new ImageIcon(this.getClass().getResource("/Z_off.png")); zeroOff = loadScaledIcon("/Z_off.png");
irqOn = new ImageIcon(this.getClass().getResource("/I_on.png")); irqOn = loadScaledIcon("/I_on.png");
irqOff = new ImageIcon(this.getClass().getResource("/I_off.png")); irqOff = loadScaledIcon("/I_off.png");
decimalOn = new ImageIcon(this.getClass().getResource("/D_on.png")); decimalOn = loadScaledIcon("/D_on.png");
decimalOff = new ImageIcon(this.getClass().getResource("/D_off.png")); decimalOff = loadScaledIcon("/D_off.png");
breakOn = new ImageIcon(this.getClass().getResource("/B_on.png")); breakOn = loadScaledIcon("/B_on.png");
breakOff = new ImageIcon(this.getClass().getResource("/B_off.png")); breakOff = loadScaledIcon("/B_off.png");
overflowOn = new ImageIcon(this.getClass().getResource("/O_on.png")); overflowOn = loadScaledIcon("/O_on.png");
overflowOff = new ImageIcon(this.getClass().getResource("/O_off.png")); overflowOff = loadScaledIcon("/O_off.png");
negativeOn = new ImageIcon(this.getClass().getResource("/N_on.png")); negativeOn = loadScaledIcon("/N_on.png");
negativeOff = new ImageIcon(this.getClass().getResource("/N_off.png")); negativeOff = loadScaledIcon("/N_off.png");
// Initialize all to off // Initialize all to off
carryFlagLabel = new JLabel(carryOff, JLabel.CENTER); carryFlagLabel = new JLabel(carryOff, JLabel.CENTER);
@@ -125,16 +130,15 @@ public class StatusPanel extends JPanel {
// Add tool-tip text // Add tool-tip text
carryFlagLabel.setToolTipText("Carry: The last operation caused an overflow " + carryFlagLabel.setToolTipText("Carry: The last operation caused an overflow " +
"from bit 7 of the result or an underflow from bit 0"); "from bit 7 of the result or an underflow from bit 0");
zeroFlagLabel.setToolTipText("Zero: The result of the last operation was 0"); zeroFlagLabel.setToolTipText("Zero: The result of the last operation was 0");
irqDisableFlagLabel.setToolTipText("Interrupt Disable: Processor will not respond to IRQ"); irqDisableFlagLabel.setToolTipText("Interrupt Disable: Processor will not respond to IRQ");
decimalModeFlagLabel.setToolTipText("Decimal Mode"); decimalModeFlagLabel.setToolTipText("Decimal Mode");
breakFlagLabel.setToolTipText("Break: BRK instruction occurred"); breakFlagLabel.setToolTipText("Break: BRK instruction occurred");
overflowFlagLabel.setToolTipText("Overflow: The result of the last operation was " + overflowFlagLabel.setToolTipText("Overflow: The result of the last operation was " +
"an invalid 2's complement result"); "an invalid 2's complement result");
negativeFlagLabel.setToolTipText("Negative: The result of the last operation set bit 7"); negativeFlagLabel.setToolTipText("Negative: The result of the last operation set bit 7");
statusFlagsPanel.add(negativeFlagLabel); statusFlagsPanel.add(negativeFlagLabel);
statusFlagsPanel.add(overflowFlagLabel); statusFlagsPanel.add(overflowFlagLabel);
statusFlagsPanel.add(breakFlagLabel); statusFlagsPanel.add(breakFlagLabel);
@@ -152,6 +156,10 @@ public class StatusPanel extends JPanel {
JLabel xLabel = makeLabel("X"); JLabel xLabel = makeLabel("X");
JLabel yLabel = makeLabel("Y"); JLabel yLabel = makeLabel("Y");
// PIA/VIA labels
JLabel piaBLabel = makeLabel("PB");
JLabel piaALabel = makeLabel("PA");
statusFlagsLabel.setToolTipText("6502 Processor Status Flags"); statusFlagsLabel.setToolTipText("6502 Processor Status Flags");
opcodeLabel.setToolTipText("Instruction Register"); opcodeLabel.setToolTipText("Instruction Register");
pcLabel.setToolTipText("Program Counter"); pcLabel.setToolTipText("Program Counter");
@@ -164,6 +172,9 @@ public class StatusPanel extends JPanel {
xField = makeTextField(SMALL_TEXT_FIELD_SIZE, true); xField = makeTextField(SMALL_TEXT_FIELD_SIZE, true);
yField = makeTextField(SMALL_TEXT_FIELD_SIZE, true); yField = makeTextField(SMALL_TEXT_FIELD_SIZE, true);
piaBField = makeTextField(LARGE_TEXT_FIELD_SIZE, false);
piaAField = makeTextField(LARGE_TEXT_FIELD_SIZE, false);
// Make fields editable // Make fields editable
pcField.addActionListener(new ActionListener() { pcField.addActionListener(new ActionListener() {
@Override @Override
@@ -243,7 +254,6 @@ public class StatusPanel extends JPanel {
constraints.gridy = 1; constraints.gridy = 1;
add(statusFlagsPanel, constraints); add(statusFlagsPanel, constraints);
constraints.insets = new Insets(5, 0, 0, 0); constraints.insets = new Insets(5, 0, 0, 0);
constraints.gridy = 2; constraints.gridy = 2;
add(opcodeLabel, constraints); add(opcodeLabel, constraints);
@@ -287,6 +297,25 @@ public class StatusPanel extends JPanel {
add(xField, constraints); add(xField, constraints);
constraints.gridx = 1; constraints.gridx = 1;
add(yField, constraints); add(yField, constraints);
constraints.insets = new Insets(5, 0, 0, 0);
constraints.gridx = 0;
constraints.gridwidth = 2;
constraints.gridy = 10;
add(piaBLabel, constraints);
constraints.insets = new Insets(2, 2, 2, 2);
constraints.gridy = 11;
add(piaBField, constraints);
constraints.insets = new Insets(5, 0, 0, 0);
constraints.gridy = 12;
add(piaALabel, constraints);
constraints.insets = new Insets(2, 2, 2, 2);
constraints.gridy = 13;
add(piaAField, constraints);
} }
/** /**
@@ -296,6 +325,8 @@ public class StatusPanel extends JPanel {
Cpu cpu = machine.getCpu(); Cpu cpu = machine.getCpu();
CpuState cpuState = cpu.getCpuState(); CpuState cpuState = cpu.getCpuState();
Pia pia = machine.getPia();
// Update the Processor Status Flag display // Update the Processor Status Flag display
int status = cpuState.getStatusFlag(); int status = cpuState.getStatusFlag();
@@ -317,6 +348,12 @@ public class StatusPanel extends JPanel {
xField.setText(cpu.getXRegisterStatus()); xField.setText(cpu.getXRegisterStatus());
yField.setText(cpu.getYRegisterStatus()); yField.setText(cpu.getYRegisterStatus());
// Update the Pia Variables
if (pia instanceof Via6522) {
piaBField.setText(((Via6522) pia).getPBStatus());
piaAField.setText(((Via6522) pia).getPAStatus());
}
repaint(); repaint();
} }
@@ -389,16 +426,11 @@ public class StatusPanel extends JPanel {
JTextField textField = new JTextField(""); JTextField textField = new JTextField("");
textField.setAlignmentX(LEFT_ALIGNMENT); textField.setAlignmentX(LEFT_ALIGNMENT);
textField.setEditable(editable); textField.setEditable(editable);
textField.setMinimumSize(size);
textField.setMaximumSize(size); Dimension scaleSize = UIScale.scale(size);
textField.setPreferredSize(size); textField.setMinimumSize(scaleSize);
textField.setBackground(Color.WHITE); textField.setMaximumSize(scaleSize);
// Although we usually defer to the system look-and-feel, for textField.setPreferredSize(scaleSize);
// these small text fields in particular, we use a Metal
// look-and-feel because native look-and-feel breaks very small
// text fields under GTK+ (they are drawn with an inner margin
// even if the margin is set to 0)
textField.setUI(new MetalTextFieldUI());
return textField; return textField;
} }
@@ -407,4 +439,14 @@ public class StatusPanel extends JPanel {
String val = source.getText().replaceAll("[^0-9a-fA-F]", ""); String val = source.getText().replaceAll("[^0-9a-fA-F]", "");
return Integer.parseInt(val, 16); return Integer.parseInt(val, 16);
} }
private ImageIcon loadScaledIcon(String resourcePath) {
ImageIcon icon = new ImageIcon(Objects.requireNonNull(this.getClass().getResource(resourcePath)));
int scaledWidth = UIScale.scale(icon.getIconWidth());
int scaledHeight = UIScale.scale(icon.getIconHeight());
Image scaleImage = icon.getImage().getScaledInstance(scaledWidth, scaledHeight, Image.SCALE_SMOOTH);
return new ImageIcon(scaleImage);
}
} }
@@ -24,6 +24,7 @@
package com.loomcom.symon.ui; package com.loomcom.symon.ui;
import com.formdev.flatlaf.util.UIScale;
import com.loomcom.symon.CpuState; import com.loomcom.symon.CpuState;
import com.loomcom.symon.util.FifoRingBuffer; import com.loomcom.symon.util.FifoRingBuffer;
@@ -39,8 +40,8 @@ public class TraceLog extends JFrame {
private final FifoRingBuffer<CpuState> traceLog; private final FifoRingBuffer<CpuState> traceLog;
private final JTextArea traceLogTextArea; private final JTextArea traceLogTextArea;
private static final Dimension MIN_SIZE = new Dimension(320, 200); private static final Dimension MIN_SIZE = UIScale.scale(new Dimension(320, 200));
private static final Dimension PREFERRED_SIZE = new Dimension(640, 480); private static final Dimension PREFERRED_SIZE = UIScale.scale(new Dimension(640, 480));
private static final int MAX_LOG_LENGTH = 50000; private static final int MAX_LOG_LENGTH = 50000;
public TraceLog() { public TraceLog() {
@@ -53,7 +54,7 @@ public class TraceLog extends JFrame {
setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE); setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
traceLogTextArea = new JTextArea(); traceLogTextArea = new JTextArea();
traceLogTextArea.setFont(new Font(Font.MONOSPACED, Font.BOLD, 12)); traceLogTextArea.setFont(new Font(Font.MONOSPACED, Font.BOLD, UIScale.scale(12)));
traceLogTextArea.setEditable(false); traceLogTextArea.setEditable(false);
JScrollPane scrollableView = new JScrollPane(traceLogTextArea); JScrollPane scrollableView = new JScrollPane(traceLogTextArea);
+153
View File
@@ -0,0 +1,153 @@
/*
* Copyright (c) 2008-2025 Seth J. Morabito <web@loomcom.com>
*
* 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.
*/
package com.loomcom.symon.ui;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.LookAndFeel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import com.loomcom.symon.machines.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.formdev.flatlaf.FlatDarkLaf;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.FlatLightLaf;
import com.formdev.flatlaf.util.SystemInfo;
import com.jthemedetecor.OsThemeDetector;
import java.awt.*;
import java.beans.PropertyChangeListener;
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
public final class Ui {
private final static Logger logger = LoggerFactory.getLogger(Ui.class.getName());
public static void initUi() throws UnsupportedLookAndFeelException {
// Location of properties resources for app-specific UI defaults:
FlatLaf.registerCustomDefaultsSource("com.loomcom.symon.themes");
logger.debug("AWT Toolkit class: {}", Toolkit.getDefaultToolkit().getClass().getName());
// Enable automatic scaling, based on system DPI settings:
//System.setProperty("sun.java2d.uiScale.enabled", "true");
//System.setProperty("sun.java2d.uiScale", "1x");
//System.setProperty("flatlaf.uiScale", "1x");
applyDetectedUiScale();
// OS-specific tweaking:
if (SystemInfo.isLinux) {
logger.debug("Adapting UI for Linux.");
// Enable custom window decorations:
JFrame.setDefaultLookAndFeelDecorated(true);
JDialog.setDefaultLookAndFeelDecorated(true);
}
else if (SystemInfo.isMacOS) {
logger.debug("Adapting UI for MacOS.");
// Move menu bar from JFrame window to top of screen:
System.setProperty("apple.laf.useScreenMenuBar", "true");
// Set name used in screen menu bar (first menu after Apple menu):
System.setProperty("apple.awt.application.name", "Symon");
// Use current macOS appearance (light/dark) for window title bars:
System.setProperty("apple.awt.application.appearance", "system");
}
else if (SystemInfo.isWindows) {
logger.debug("Adapting UI for Windows.");
}
UIManager.setLookAndFeel(createLookAndFeel());
}
public static Class<?> showMachineSelectionDialog() {
Object[] possibilities = { "Symon", "Multicomp", "Simple", "BenEater", "Mike42" };
String s = (String) JOptionPane.showInputDialog(
null,
"Please choose the machine type to be emulated:",
"Machine selection",
JOptionPane.PLAIN_MESSAGE,
null,
possibilities,
"Symon");
if (s != null && s.equals("Multicomp")) {
return MulticompMachine.class;
} else if (s != null && s.equals("Simple")) {
return SimpleMachine.class;
} else if (s != null && s.equals("BenEater")) {
return BenEaterMachine.class;
} else if (s != null && s.equals("Mike42")) {
return MichaelBillingtonMachine.class;
} else {
return SymonMachine.class;
}
}
private static LookAndFeel createLookAndFeel() {
final OsThemeDetector themeDetector = OsThemeDetector.getDetector();
final boolean systemThemeIsDark = themeDetector.isDark();
// Set light/dark mode depending on what can be detected from the system theme:
FlatLaf theme;
if (systemThemeIsDark) {
logger.debug("Using DARK theme.");
theme = new FlatDarkLaf();
} else {
logger.debug("Using LIGHT theme.");
theme = new FlatLightLaf();
}
return theme;
}
private static void applyDetectedUiScale() {
Double rawScale = null;
if (SystemInfo.isLinux) rawScale = X11ResourceReader.readXftDpiScale(); // ex: 1.5, 2.0, 2.5...
if (rawScale != null && rawScale > 1.0) {
// flatlaf.uiScale aceita frações — mantém a precisão original
System.setProperty("flatlaf.uiScale", rawScale + "x");
// sun.java2d.uiScale só aceita inteiros — arredonda para cima
int javaScale = (int) Math.ceil(rawScale);
//System.setProperty("sun.java2d.uiScale", String.valueOf(javaScale));
logger.debug("Detected scale {}x, applying flatlaf.uiScale={}x, sun.java2d.uiScale={}x",
rawScale, rawScale, javaScale);
} else {
logger.debug("No X11 HiDPI scale detected, using default.");
}
}
}
@@ -0,0 +1,54 @@
package com.loomcom.symon.ui;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.PointerByReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class X11ResourceReader {
private final static Logger logger = LoggerFactory.getLogger(X11ResourceReader.class.getName());
public interface X11 extends Library {
X11 INSTANCE = Native.load("X11", X11.class);
Pointer XOpenDisplay(String display);
Pointer XResourceManagerString(Pointer display);
void XCloseDisplay(Pointer display);
}
public static Double readXftDpiScale() {
Pointer display = null;
try {
display = X11.INSTANCE.XOpenDisplay(null);
if (display == null) {
return null; // não conseguimos ligar ao X server (ex: Wayland puro sem XWayland)
}
Pointer resourceString = X11.INSTANCE.XResourceManagerString(display);
if (resourceString == null) {
return null;
}
String resources = resourceString.getString(0);
for (String line : resources.split("\n")) {
if (line.startsWith("Xft.dpi:")) {
int dpi = Integer.parseInt(line.split(":")[1].trim());
return Math.round((dpi / 96.0) * 4) / 4.0;
}
}
return null;
} catch (UnsatisfiedLinkError | Exception e) {
logger.debug("Could not read X11 resource manager (libX11 unavailable?)", e);
return null;
} finally {
if (display != null) {
X11.INSTANCE.XCloseDisplay(display);
}
}
}
}
@@ -76,6 +76,16 @@ public class Utils {
"F0", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F0", "F1", "F2", "F3", "F4", "F5", "F6", "F7",
"F8", "F9", "FA", "FB", "FC", "FD", "FE", "FF"}; "F8", "F9", "FA", "FB", "FC", "FD", "FE", "FF"};
private static final String[] BIN_CONSTANTS = new String[256];
static {
for (int i = 0; i < 256; i++) {
String bin = Integer.toBinaryString(i);
// Left-pad to 8 digits with zeros
BIN_CONSTANTS[i] = "00000000".substring(bin.length()) + bin;
}
}
/** /**
* Very fast 8-bit int to ASCII conversion. * Very fast 8-bit int to ASCII conversion.
* *
@@ -116,4 +126,14 @@ public class Utils {
public static int address(int lowByte, int hiByte) { public static int address(int lowByte, int hiByte) {
return ((hiByte << 8) | lowByte) & 0xffff; return ((hiByte << 8) | lowByte) & 0xffff;
} }
/**
* Very fast 8-bit int to binary conversion, with zero-padded output.
*
* @param val The unsigned 8-bit value to convert to a zero padded binary string.
* @return Eight digit, zero padded binary string.
*/
public static String byteToBin(int val) {
return BIN_CONSTANTS[val & 0xff];
}
} }
Binary file not shown.

Before

Width:  |  Height:  |  Size: 191 B

After

Width:  |  Height:  |  Size: 220 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 196 B

After

Width:  |  Height:  |  Size: 232 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 B

After

Width:  |  Height:  |  Size: 224 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 183 B

After

Width:  |  Height:  |  Size: 202 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 198 B

After

Width:  |  Height:  |  Size: 238 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 B

After

Width:  |  Height:  |  Size: 221 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 198 B

After

Width:  |  Height:  |  Size: 238 B

@@ -0,0 +1,3 @@
# Loaded for FlatDarkLaf theme only.
Colors.accent.default = #4B6EAF
@@ -0,0 +1 @@
# Loaded for all FlatLaf themes.
@@ -0,0 +1,3 @@
# Loaded for FlatLightLaf theme only.
Colors.accent.default = #2675BF