Compare commits

..
14 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
15 changed files with 487 additions and 47 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
+10 -3
View File
@@ -1,9 +1,9 @@
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.
@@ -71,6 +71,13 @@ memory.
- `$6000`--`$600F`: 6522 VIA
- `$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
![Serial Console](https://codeberg.org/sethm/symon/raw/master/screenshots/console.png)
+10 -5
View File
@@ -5,7 +5,7 @@
<groupId>com.loomcom.symon</groupId>
<artifactId>symon</artifactId>
<packaging>jar</packaging>
<version>1.5.0</version>
<version>1.5.1.1k</version>
<name>symon</name>
<url>https://git.loomcom.com/seth/symon</url>
<properties>
@@ -26,7 +26,7 @@
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.5.16</version>
<version>1.5.34</version>
</dependency>
<dependency>
<groupId>commons-cli</groupId>
@@ -55,6 +55,11 @@
<version>4.11.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>5.14.0</version>
</dependency>
</dependencies>
<build>
@@ -66,7 +71,7 @@
<configuration>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.loomcom.symon.Main</mainClass>
</transformer>
</transformers>
@@ -97,8 +102,8 @@
<version>3.7.0</version>
<configuration>
<compilerArgument>-Xlint:unchecked</compilerArgument>
<source>11</source>
<target>11</target>
<source>16</source>
<target>16</target>
</configuration>
</plugin>
@@ -86,6 +86,9 @@ public class Main {
case "beneater":
machineClass = BenEaterMachine.class;
break;
case "mike42":
machineClass = MichaelBillingtonMachine.class;
break;
default:
logger.error("Could not start Symon. Unknown machine type {}", machine);
return;
@@ -23,6 +23,7 @@
package com.loomcom.symon;
import com.formdev.flatlaf.util.UIScale;
import com.loomcom.symon.devices.Memory;
import com.loomcom.symon.exceptions.*;
import com.loomcom.symon.machines.Machine;
@@ -53,7 +54,7 @@ public class Simulator {
// UI constants
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;
// 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.MemoryRangeException;
import com.loomcom.symon.util.Utils;
/**
* 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 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 {
super(address, address + VIA_SIZE - 1, "MOS 6522 VIA");
outputRegisterA = outputRegisterB = 0;
dataDirectionA = dataDirectionB = 0;
}
@Override
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
public int read(int address, boolean cpuAccess) throws MemoryAccessException {
// TODO: Implement read.
return 0;
return switch (address) {
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;
import com.formdev.flatlaf.util.UIScale;
import com.loomcom.symon.Breakpoints;
import com.loomcom.symon.util.Utils;
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 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 final JFrame mainWindow;
@@ -23,6 +23,7 @@
package com.loomcom.symon.ui;
import com.formdev.flatlaf.util.UIScale;
import com.loomcom.symon.Bus;
import com.loomcom.symon.exceptions.MemoryAccessException;
import com.loomcom.symon.util.Utils;
@@ -55,15 +56,15 @@ public class MemoryWindow extends JFrame implements ActionListener {
private JButton previousPageButton;
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
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
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
private static final int ASCII_COL_START = 9;
@@ -23,17 +23,20 @@
package com.loomcom.symon.ui;
import com.formdev.flatlaf.util.UIScale;
import com.loomcom.symon.Cpu;
import com.loomcom.symon.CpuState;
import com.loomcom.symon.devices.Pia;
import com.loomcom.symon.devices.Via6522;
import com.loomcom.symon.machines.Machine;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.border.EtchedBorder;
import javax.swing.plaf.metal.MetalTextFieldUI;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Objects;
/**
* 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 xField;
private JTextField yField;
private JTextField piaBField;
private JTextField piaAField;
private final Machine machine;
private static final int EMPTY_BORDER = 10;
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 SMALL_TEXT_FIELD_SIZE = new Dimension(65, 22);
@@ -99,20 +104,20 @@ public class StatusPanel extends JPanel {
JPanel statusFlagsPanel = new JPanel();
statusFlagsPanel.setAlignmentX(LEFT_ALIGNMENT);
carryOn = new ImageIcon(this.getClass().getResource("/C_on.png"));
carryOff = new ImageIcon(this.getClass().getResource("/C_off.png"));
zeroOn = new ImageIcon(this.getClass().getResource("/Z_on.png"));
zeroOff = new ImageIcon(this.getClass().getResource("/Z_off.png"));
irqOn = new ImageIcon(this.getClass().getResource("/I_on.png"));
irqOff = new ImageIcon(this.getClass().getResource("/I_off.png"));
decimalOn = new ImageIcon(this.getClass().getResource("/D_on.png"));
decimalOff = new ImageIcon(this.getClass().getResource("/D_off.png"));
breakOn = new ImageIcon(this.getClass().getResource("/B_on.png"));
breakOff = new ImageIcon(this.getClass().getResource("/B_off.png"));
overflowOn = new ImageIcon(this.getClass().getResource("/O_on.png"));
overflowOff = new ImageIcon(this.getClass().getResource("/O_off.png"));
negativeOn = new ImageIcon(this.getClass().getResource("/N_on.png"));
negativeOff = new ImageIcon(this.getClass().getResource("/N_off.png"));
carryOn = loadScaledIcon("/C_on.png");
carryOff = loadScaledIcon("/C_off.png");
zeroOn = loadScaledIcon("/Z_on.png");
zeroOff = loadScaledIcon("/Z_off.png");
irqOn = loadScaledIcon("/I_on.png");
irqOff = loadScaledIcon("/I_off.png");
decimalOn = loadScaledIcon("/D_on.png");
decimalOff = loadScaledIcon("/D_off.png");
breakOn = loadScaledIcon("/B_on.png");
breakOff = loadScaledIcon("/B_off.png");
overflowOn = loadScaledIcon("/O_on.png");
overflowOff = loadScaledIcon("/O_off.png");
negativeOn = loadScaledIcon("/N_on.png");
negativeOff = loadScaledIcon("/N_off.png");
// Initialize all to off
carryFlagLabel = new JLabel(carryOff, JLabel.CENTER);
@@ -151,6 +156,10 @@ public class StatusPanel extends JPanel {
JLabel xLabel = makeLabel("X");
JLabel yLabel = makeLabel("Y");
// PIA/VIA labels
JLabel piaBLabel = makeLabel("PB");
JLabel piaALabel = makeLabel("PA");
statusFlagsLabel.setToolTipText("6502 Processor Status Flags");
opcodeLabel.setToolTipText("Instruction Register");
pcLabel.setToolTipText("Program Counter");
@@ -163,6 +172,9 @@ public class StatusPanel extends JPanel {
xField = 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
pcField.addActionListener(new ActionListener() {
@Override
@@ -285,6 +297,25 @@ public class StatusPanel extends JPanel {
add(xField, constraints);
constraints.gridx = 1;
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);
}
/**
@@ -294,6 +325,8 @@ public class StatusPanel extends JPanel {
Cpu cpu = machine.getCpu();
CpuState cpuState = cpu.getCpuState();
Pia pia = machine.getPia();
// Update the Processor Status Flag display
int status = cpuState.getStatusFlag();
@@ -315,6 +348,12 @@ public class StatusPanel extends JPanel {
xField.setText(cpu.getXRegisterStatus());
yField.setText(cpu.getYRegisterStatus());
// Update the Pia Variables
if (pia instanceof Via6522) {
piaBField.setText(((Via6522) pia).getPBStatus());
piaAField.setText(((Via6522) pia).getPAStatus());
}
repaint();
}
@@ -387,9 +426,11 @@ public class StatusPanel extends JPanel {
JTextField textField = new JTextField("");
textField.setAlignmentX(LEFT_ALIGNMENT);
textField.setEditable(editable);
textField.setMinimumSize(size);
textField.setMaximumSize(size);
textField.setPreferredSize(size);
Dimension scaleSize = UIScale.scale(size);
textField.setMinimumSize(scaleSize);
textField.setMaximumSize(scaleSize);
textField.setPreferredSize(scaleSize);
return textField;
}
@@ -398,4 +439,14 @@ public class StatusPanel extends JPanel {
String val = source.getText().replaceAll("[^0-9a-fA-F]", "");
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;
import com.formdev.flatlaf.util.UIScale;
import com.loomcom.symon.CpuState;
import com.loomcom.symon.util.FifoRingBuffer;
@@ -39,8 +40,8 @@ public class TraceLog extends JFrame {
private final FifoRingBuffer<CpuState> traceLog;
private final JTextArea traceLogTextArea;
private static final Dimension MIN_SIZE = new Dimension(320, 200);
private static final Dimension PREFERRED_SIZE = new Dimension(640, 480);
private static final Dimension MIN_SIZE = UIScale.scale(new Dimension(320, 200));
private static final Dimension PREFERRED_SIZE = UIScale.scale(new Dimension(640, 480));
private static final int MAX_LOG_LENGTH = 50000;
public TraceLog() {
@@ -53,7 +54,7 @@ public class TraceLog extends JFrame {
setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
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);
JScrollPane scrollableView = new JScrollPane(traceLogTextArea);
+37 -8
View File
@@ -30,6 +30,7 @@ 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;
@@ -38,10 +39,13 @@ import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.FlatLightLaf;
import com.formdev.flatlaf.util.SystemInfo;
import com.jthemedetecor.OsThemeDetector;
import com.loomcom.symon.machines.BenEaterMachine;
import com.loomcom.symon.machines.MulticompMachine;
import com.loomcom.symon.machines.SimpleMachine;
import com.loomcom.symon.machines.SymonMachine;
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());
@@ -50,10 +54,14 @@ public final class Ui {
// 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");
//System.setProperty("sun.java2d.uiScale.enabled", "true");
//System.setProperty("sun.java2d.uiScale", "1x");
//System.setProperty("flatlaf.uiScale", "1x");
applyDetectedUiScale();
// OS-specific tweaking:
@@ -84,7 +92,7 @@ public final class Ui {
}
public static Class<?> showMachineSelectionDialog() {
Object[] possibilities = { "Symon", "Multicomp", "Simple", "BenEater" };
Object[] possibilities = { "Symon", "Multicomp", "Simple", "BenEater", "Mike42" };
String s = (String) JOptionPane.showInputDialog(
null,
"Please choose the machine type to be emulated:",
@@ -100,6 +108,8 @@ public final class Ui {
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;
}
@@ -121,4 +131,23 @@ public final class Ui {
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",
"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.
*
@@ -116,4 +126,14 @@ public class Utils {
public static int address(int lowByte, int hiByte) {
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];
}
}