/** * Copyright 2012 Tobias Gierke <tobias.gierke@code-sourcery.de> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package de.codesourcery.jasm16.emulator.devices.impl; import java.awt.Component; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.log4j.Logger; import de.codesourcery.jasm16.Address; import de.codesourcery.jasm16.AddressRange; import de.codesourcery.jasm16.Register; import de.codesourcery.jasm16.Size; import de.codesourcery.jasm16.emulator.EmulationListener; import de.codesourcery.jasm16.emulator.ICPU; import de.codesourcery.jasm16.emulator.IEmulationListener; import de.codesourcery.jasm16.emulator.IEmulator; import de.codesourcery.jasm16.emulator.IEmulatorInvoker; import de.codesourcery.jasm16.emulator.devices.DeviceDescriptor; import de.codesourcery.jasm16.emulator.devices.HardwareInterrupt; import de.codesourcery.jasm16.emulator.devices.IDevice; import de.codesourcery.jasm16.emulator.memory.IMemory; import de.codesourcery.jasm16.emulator.memory.MemoryRegion; import de.codesourcery.jasm16.utils.Misc; /** * Default keyboard device. * * @author tobias.gierke@code-sourcery.de */ public class DefaultKeyboard implements IDevice { private static final Logger LOG = Logger.getLogger(DefaultKeyboard.class); private volatile Component inputComponent; private final Object BUFFER_LOCK = new Object(); // @GuardedBy( BUFFER_LOCK ) private final List<Integer> keysTyped = new ArrayList<Integer>(); // @GuardedBy( BUFFER_LOCK ) private final List<Integer> keysPressed = new ArrayList<Integer>(); private volatile IEmulator emulator; private final boolean useLegacyMemoryBuffer; // use keyboard buffer at 0x9000 private volatile LegacyKeyboardBuffer legacyKeyboardBuffer; private volatile Integer interruptMessage = null; private volatile boolean receivedAtLeastOneInterrupt = false; public static final DeviceDescriptor DESC = new DeviceDescriptor( "Generic keyboard" , "Generic keyboard (compatible)", 0x30cf7406 , 0x01 , Constants.JASM16_MANUFACTURER ); private final AtomicBoolean emulationRunning = new AtomicBoolean(false); private final IEmulationListener myEmulationListener = new EmulationListener() { public boolean belongsToHardwareDevice() { return true; } public void afterReset(IEmulator emulator) { clearKeyboardBuffers(); }; protected void beforeContinuousExecutionHook() { emulationRunning.set( true ); } public void onStopHook(IEmulator emulator, Address previousPC, Throwable emulationError) { clearKeyboardBuffers(); emulationRunning.set( false); } }; protected final class LegacyKeyboardBuffer extends MemoryRegion { public LegacyKeyboardBuffer(Address range) { super("keyboard buffer (legacy)", TYPE_KEYBOARD_BUFFER , new AddressRange( range , Size.words( 1 ) ) , MemoryRegion.Flag.MEMORY_MAPPED_HW); } public void writeKeyEvent(final int keyCode) { emulator.doWithEmulator( new IEmulatorInvoker<Void>() { @Override public Void doWithEmulator(IEmulator emulator, ICPU cpu, IMemory mem) { final Address address = getStartAddress(); if ( mem.read( address ) == 0 ) { mem.write( address , keyCode ); } return null; } }); } private Address getStartAddress() { return getAddressRange().getStartAddress(); } public void reset() { emulator.doWithEmulator( new IEmulatorInvoker<Void>() { @Override public Void doWithEmulator(IEmulator emulator, ICPU cpu, IMemory mem) { mem.write( getStartAddress() , 0 ); return null; } }); } } private final KeyListener keyListener = new KeyListener() { @Override public void keyTyped(KeyEvent e) { if ( ! emulationRunning.get() ) { return; } final int c = e.getKeyChar(); if ( c >= 0x20 && c <= 0x7f ) { keyTyped( c , true ); } } private void keyTyped(int mappedKeyCode,boolean sendInterrupt) { if ( useLegacyMemoryBuffer && legacyKeyboardBuffer != null ) { legacyKeyboardBuffer.writeKeyEvent( mappedKeyCode ); } if ( ! receivedAtLeastOneInterrupt ) { return; } synchronized( BUFFER_LOCK ) { keysTyped.add( mappedKeyCode ); } if ( sendInterrupt ) { sendInterrupt(); } } private int mapKeyCode(KeyEvent e) { final int result = internalMapKeyCode( e ); return result; } private int internalMapKeyCode(KeyEvent e) { /* Key numbers are: * 0x10: Backspace * 0x11: Return * 0x12: Insert * 0x13: Delete * 0x20-0x7f: ASCII characters * 0x80: Arrow up * 0x81: Arrow down * 0x82: Arrow left * 0x83: Arrow right * 0x90: Shift * 0x91: Control */ switch( e.getKeyCode() ) { case KeyEvent.VK_BACK_SPACE: return 0x10; case KeyEvent.VK_ENTER: return 0x11; case KeyEvent.VK_INSERT: return 0x12; case KeyEvent.VK_DELETE: return 0x13; case KeyEvent.VK_UP: return 0x80; case KeyEvent.VK_DOWN: return 0x81; case KeyEvent.VK_LEFT: return 0x82; case KeyEvent.VK_RIGHT: return 0x83; case KeyEvent.VK_SHIFT: return 0x90; case KeyEvent.VK_CONTROL: return 0x91; } final int c = e.getKeyChar(); if ( c >= 0x20 && c <= 0x7f ) { return c; } return -1; } private boolean isSpecialKey(int mappedKey) { switch(mappedKey) { case 0x10: // Backspace case 0x11: // Return case 0x12: // Insert case 0x13: // Delete case 0x80: // Arrow up case 0x81: // Arrow down case 0x82: // Arrow left case 0x83: // Arrow right case 0x90: // Shift case 0x91: // Control */ return true; default: return false; } } @Override public void keyReleased(KeyEvent e) { if ( ! emulationRunning.get() ) { return; } final int mapped = mapKeyCode(e); if ( mapped == -1 ) { return; } if ( isSpecialKey( mapped ) ) { keyTyped( mapped , false ); } if ( ! receivedAtLeastOneInterrupt ) { return; } synchronized(BUFFER_LOCK ) { keysPressed.remove( Integer.valueOf( mapped ) ); } sendInterrupt(); } @Override public void keyPressed(KeyEvent e) { if ( ! emulationRunning.get() ) { return; } if ( ! receivedAtLeastOneInterrupt ) { return; } synchronized( BUFFER_LOCK ) { final int mapped = mapKeyCode(e); if ( mapped != -1 ) { keysPressed.add( mapped ); } } sendInterrupt(); } private void sendInterrupt() { if ( interruptMessage != null && emulationRunning.get() ) { emulator.triggerInterrupt( new HardwareInterrupt( DefaultKeyboard.this , interruptMessage ) ); } } }; public DefaultKeyboard(boolean useLegacyMemoryBuffer) { this.useLegacyMemoryBuffer = useLegacyMemoryBuffer; } @Override public void reset() { synchronized (BUFFER_LOCK) { keysTyped.clear(); keysPressed.clear(); interruptMessage = null; receivedAtLeastOneInterrupt = false; if ( legacyKeyboardBuffer != null ) { legacyKeyboardBuffer.reset(); } } } public void attach(Component comp) { if ( this.inputComponent != null ) { this.inputComponent.removeKeyListener( keyListener ); } this.inputComponent = comp; if ( this.inputComponent != null ) { this.inputComponent.addKeyListener( keyListener ); } } public void detach() { if ( this.inputComponent != null ) { this.inputComponent.removeKeyListener( keyListener ); } this.inputComponent = null; } private void clearKeyboardBuffers() { synchronized ( BUFFER_LOCK ) { keysPressed.clear(); keysTyped.clear(); } } private Integer readTypedKey() { synchronized (BUFFER_LOCK) { if ( keysTyped.isEmpty() ) { return null; } return keysTyped.remove(0); } } private boolean isKeyPressed(int keyCode) { synchronized (BUFFER_LOCK) { return keysPressed.contains( keyCode ); } } /* * Name: Generic Keyboard (compatible) * ID: 0x30cf7406 * Version: 1 * * Interrupts do different things depending on contents of the A register: * * A | BEHAVIOR * ---+---------------------------------------------------------------------------- * 0 | Clear keyboard buffer * 1 | Store next key typed in C register, or 0 if the buffer is empty * 2 | Set C register to 1 if the key specified by the B register is pressed, or * | 0 if it's not pressed * 3 | If register B is non-zero, turn on interrupts with message B. If B is zero, * | disable interrupts * ---+---------------------------------------------------------------------------- * * When interrupts are enabled, the keyboard will trigger an interrupt when one or * more keys have been pressed, released, or typed. */ @Override public void afterAddDevice(IEmulator emulator) { if ( this.emulator != null ) { throw new IllegalStateException("Device "+this+" is already added to emulator "+this.emulator); } this.emulator = emulator; this.emulator.addEmulationListener( myEmulationListener ); if ( useLegacyMemoryBuffer ) { legacyKeyboardBuffer = new LegacyKeyboardBuffer(Address.wordAddress( 0x9000 ) ); emulator.mapRegion( legacyKeyboardBuffer ); } } @Override public boolean supportsMultipleInstances() { return false; } @Override public void beforeRemoveDevice(IEmulator emulator) { if ( inputComponent != null ) { inputComponent.removeKeyListener( keyListener ); this.inputComponent = null; } if ( legacyKeyboardBuffer != null ) { emulator.unmapRegion( legacyKeyboardBuffer ); } this.emulator.removeEmulationListener( myEmulationListener ); this.emulator = null; } @Override public DeviceDescriptor getDeviceDescriptor() { return DESC; } @Override public int handleInterrupt(IEmulator emulator, ICPU cpu, IMemory memory) { receivedAtLeastOneInterrupt = true; final int value = cpu.getRegisterValue( Register.A ); /* * Interrupts do different things depending on contents of the A register: * * A | BEHAVIOR * ---+---------------------------------------------------------------------------- * 0 | Clear keyboard buffer * 1 | Store next key typed in C register, or 0 if the buffer is empty * 2 | Set C register to 1 if the key specified by the B register is pressed, or * | 0 if it's not pressed * 3 | If register B is non-zero, turn on interrupts with message B. If B is zero, * | disable interrupts * ---+---------------------------------------------------------------------------- */ switch( value ) { case 0: clearKeyboardBuffers(); return 0; case 1: Integer keyCode = readTypedKey(); final int msg = keyCode != null ? keyCode.intValue() : 0; cpu.setRegisterValue( Register.C , msg ); return 0; case 2: final int key = cpu.getRegisterValue( Register.B ); cpu.setRegisterValue( Register.C , isKeyPressed( key ) ? 1 : 0 ); return 0; case 3: final int irqMsg = cpu.getRegisterValue( Register.B ); interruptMessage = irqMsg != 0 ? irqMsg : null; return 0; default: LOG.warn("handleInterrupt(): Received unknown interrupt msg "+Misc.toHexString( value ) ); return 0; } } @Override public String toString() { return "'"+DESC.getDescription()+"'"; } }