Compare commits
10 Commits
578aeb5662
...
234fcdc5f5
Author | SHA1 | Date |
---|---|---|
Seth Morabito | 234fcdc5f5 | |
Seth Morabito | 969214c6d5 | |
Seth Morabito | 07988707ef | |
Seth Morabito | 157ac0f57e | |
Seth Morabito | bd74df94a0 | |
Seth Morabito | c3eab0fc4e | |
Seth Morabito | a8ee31b219 | |
Seth Morabito | db99836fc2 | |
Seth Morabito | 825aada63a | |
Seth Morabito | dec45ac9a1 |
|
@ -1,3 +1,3 @@
|
|||
[submodule "dmd_core"]
|
||||
path = dmd_core
|
||||
url = git@github.com:sethm/dmd_core.git
|
||||
url = https://github.com/sethm/dmd_core.git
|
||||
|
|
17
README.md
17
README.md
|
@ -4,7 +4,7 @@ This is a GTK+ 3.0 implementation of an AT&T / Teletype DMD 5620 emulator.
|
|||
|
||||
## Status
|
||||
|
||||
Version: 2.0.1
|
||||
Version: 2.1.0
|
||||
|
||||
This is an actively developed project.
|
||||
|
||||
|
@ -32,12 +32,13 @@ The executable has the following dependencies:
|
|||
### Running the Terminal
|
||||
|
||||
```
|
||||
Usage: dmd5620 [-h] [-v] [-d DEV|-s SHELL] \
|
||||
Usage: dmd5620 [-h] [-v] [-i] [-d DEV|-s SHELL] \
|
||||
[-f VER] [-n FILE] [-- <gtk_options> ...]
|
||||
AT&T DMD 5620 Terminal emulator.
|
||||
|
||||
-h, --help display help and exit
|
||||
-v, --version display version and exit
|
||||
-i, --inherit inherit parent environment
|
||||
-f, --firmware VER Firmware version ("8;7;3" or "8;7;5")
|
||||
-d, --device DEV serial port name
|
||||
-s, --shell SHELL execute SHELL instead of default user shell
|
||||
|
@ -46,6 +47,7 @@ AT&T DMD 5620 Terminal emulator.
|
|||
|
||||
- `--help` displays the help shown above, and exits.
|
||||
- `--version` displays the executable version number, and exits.
|
||||
- `--inherit` inherit parent shell environment.
|
||||
- `--firmware VER` selects the firmware version to use. Older DMD
|
||||
terminals used firmware version "8;7;3". Newer terminals used firmware
|
||||
version "8;7;5". The version must be specified as a string, and is
|
||||
|
@ -89,6 +91,17 @@ Certain keys are mapped to special DMD5620 function keys.
|
|||
|
||||
## Changelog
|
||||
|
||||
### Version 2.1.0
|
||||
|
||||
* Sub-processes now inherit the user's environment.
|
||||
* Maximum execution rate is limited to prevent runaway starvation of
|
||||
resources and slow performance.
|
||||
|
||||
### Version 2.0.1
|
||||
|
||||
* Several major bug fixes to the UART code on the back end.
|
||||
* Now supports the ability to run firmware version 8;7;3 and 8;7;5.
|
||||
|
||||
### Version 1.4.1
|
||||
|
||||
* Did away with multi-threaded execution and significantly improved timing.
|
||||
|
|
2
dmd_core
2
dmd_core
|
@ -1 +1 @@
|
|||
Subproject commit 03d0d36061e1dad2f3b0e3d1d670c817f0cf056f
|
||||
Subproject commit 73ea34e619a2a726213682f6c498c9abc3a29902
|
214
src/dmd_5620.c
214
src/dmd_5620.c
|
@ -40,6 +40,7 @@
|
|||
#else
|
||||
#include <pty.h>
|
||||
#endif
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <utmp.h>
|
||||
#include <fcntl.h>
|
||||
|
@ -62,6 +63,7 @@
|
|||
|
||||
#define PCHAR(p) (((p) >= 0x20 && (p) < 0x7f) ? (p) : '.')
|
||||
#define TX_BUF_LEN 64
|
||||
#define MAX_STEPS 350000
|
||||
|
||||
char VERSION_STRING[64];
|
||||
GtkWidget *main_window;
|
||||
|
@ -69,14 +71,13 @@ cairo_surface_t *surface = NULL;
|
|||
GdkPixbuf *pixbuf = NULL;
|
||||
int pty_master, pty_slave;
|
||||
char *nvram = NULL;
|
||||
gint64 previous_clock = -1;
|
||||
uint8_t previous_vram[VIDRAM_SIZE];
|
||||
size_t previous_clock = 0;
|
||||
struct pollfd fds[2];
|
||||
pid_t shell_pid;
|
||||
volatile int window_beep = FALSE;
|
||||
volatile int dmd_thread_run = TRUE;
|
||||
volatile bool window_beep = true;
|
||||
int sigint_count = 0;
|
||||
int tty_fd = -1;
|
||||
bool debug = false;
|
||||
|
||||
void
|
||||
int_handler(int signal)
|
||||
|
@ -114,7 +115,6 @@ close_window()
|
|||
cairo_surface_destroy(surface);
|
||||
}
|
||||
|
||||
dmd_thread_run = 0;
|
||||
gtk_main_quit();
|
||||
}
|
||||
|
||||
|
@ -139,8 +139,8 @@ gboolean
|
|||
draw_handler(GtkWidget *widget, cairo_t *cr, gpointer data)
|
||||
{
|
||||
cairo_set_source_surface(cr, surface, 0, 0);
|
||||
gdk_cairo_set_source_pixbuf(cr, pixbuf, 0, 0);
|
||||
cairo_paint(cr);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
@ -148,6 +148,9 @@ gboolean
|
|||
refresh_display(GtkWidget *widget, gpointer data)
|
||||
{
|
||||
uint8_t oport;
|
||||
uint8_t *vram;
|
||||
guchar *pixel_data;
|
||||
uint32_t pixel_data_index;
|
||||
GdkWindow *window;
|
||||
const struct color *fg_color;
|
||||
const struct color *bg_color;
|
||||
|
@ -157,20 +160,18 @@ refresh_display(GtkWidget *widget, gpointer data)
|
|||
|
||||
if (window_beep) {
|
||||
gdk_window_beep(window);
|
||||
window_beep = FALSE;
|
||||
window_beep = false;
|
||||
}
|
||||
|
||||
uint8_t *vram = dmd_video_ram();
|
||||
|
||||
if (memcmp(previous_vram, vram, VIDRAM_SIZE) == 0) {
|
||||
/* If the video RAM hasn't been updated, there's nothing to do */
|
||||
if (!dmd_video_ram_dirty()) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
memcpy(previous_vram, vram, VIDRAM_SIZE);
|
||||
vram = dmd_video_ram();
|
||||
|
||||
guchar *p = gdk_pixbuf_get_pixels(pixbuf);
|
||||
uint32_t p_index = 0;
|
||||
int byte_width = WIDTH / 8;
|
||||
pixel_data = gdk_pixbuf_get_pixels(pixbuf);
|
||||
pixel_data_index = 0;
|
||||
|
||||
if (vram == NULL) {
|
||||
fprintf(stderr, "ERROR: Unable to access video ram!\n");
|
||||
|
@ -190,33 +191,27 @@ refresh_display(GtkWidget *widget, gpointer data)
|
|||
}
|
||||
|
||||
for (int y = 0; y < HEIGHT; y++) {
|
||||
for (int x = 0; x < byte_width; x++) {
|
||||
uint8_t b = vram[y*byte_width + x];
|
||||
for (int x = 0; x < WIDTH_IN_BYTES; x++) {
|
||||
uint8_t b = vram[y*WIDTH_IN_BYTES + x];
|
||||
for (int i = 0; i < 8; i++) {
|
||||
int bit = (b >> (7 - i)) & 1;
|
||||
if (bit) {
|
||||
p[p_index++] = fg_color->r;
|
||||
p[p_index++] = fg_color->g;
|
||||
p[p_index++] = fg_color->b;
|
||||
p[p_index++] = fg_color->a;
|
||||
pixel_data[pixel_data_index++] = fg_color->r;
|
||||
pixel_data[pixel_data_index++] = fg_color->g;
|
||||
pixel_data[pixel_data_index++] = fg_color->b;
|
||||
pixel_data[pixel_data_index++] = fg_color->a;
|
||||
} else {
|
||||
p[p_index++] = bg_color->r;
|
||||
p[p_index++] = bg_color->g;
|
||||
p[p_index++] = bg_color->b;
|
||||
p[p_index++] = bg_color->a;
|
||||
pixel_data[pixel_data_index++] = bg_color->r;
|
||||
pixel_data[pixel_data_index++] = bg_color->g;
|
||||
pixel_data[pixel_data_index++] = bg_color->b;
|
||||
pixel_data[pixel_data_index++] = bg_color->a;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cairo_t *cr;
|
||||
cr = cairo_create(surface);
|
||||
gdk_cairo_set_source_pixbuf(cr, pixbuf, 0, 0);
|
||||
cairo_paint(cr);
|
||||
cairo_fill(cr);
|
||||
cairo_destroy(cr);
|
||||
|
||||
/* Notify the widget that it should repaint itself */
|
||||
gtk_widget_queue_draw(widget);
|
||||
|
||||
return TRUE;
|
||||
|
@ -226,8 +221,7 @@ gboolean
|
|||
simulation_main_loop(GtkWidget *widget, GdkFrameClock *clock, gpointer data)
|
||||
{
|
||||
uint8_t kbc;
|
||||
gint64 now;
|
||||
size_t steps;
|
||||
size_t now, steps;
|
||||
|
||||
/*
|
||||
* Poll for simulator I/O
|
||||
|
@ -256,14 +250,26 @@ simulation_main_loop(GtkWidget *widget, GdkFrameClock *clock, gpointer data)
|
|||
*/
|
||||
now = gdk_frame_clock_get_frame_time(clock);
|
||||
|
||||
if (previous_clock >= 0) {
|
||||
/* We take 10 simulated steps per microsecond of wall clock
|
||||
* time, based on a 10 MHz WE 32100 CPU */
|
||||
steps = MIN(10 * (now - previous_clock), 10000000);
|
||||
if (previous_clock > 0) {
|
||||
/* We take 7.2 simulated steps per microsecond of wall clock
|
||||
* time, based on a 7.2 MHz WE 32100 CPU. The maximum number of
|
||||
* steps allowed is limited in order to prevent the CPU
|
||||
* simulation from stealing too much processing time if
|
||||
* running on a system with a slower main GTK thread refresh
|
||||
* rate. */
|
||||
size_t delta = now - previous_clock;
|
||||
steps = MIN((size_t)(7.2 * delta), MAX_STEPS);
|
||||
if (debug) {
|
||||
printf("[MAIN LOOP] executing %lu steps in %lu us. rate ~= %.2f MHz\n",
|
||||
steps,
|
||||
delta,
|
||||
(float)steps / (float)delta);
|
||||
}
|
||||
} else {
|
||||
steps = 100000;
|
||||
steps = MAX_STEPS;
|
||||
}
|
||||
|
||||
|
||||
previous_clock = now;
|
||||
|
||||
/* Actually call the core CPU library */
|
||||
|
@ -271,8 +277,6 @@ simulation_main_loop(GtkWidget *widget, GdkFrameClock *clock, gpointer data)
|
|||
|
||||
/* Now refresh the display */
|
||||
return refresh_display(widget, data);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
|
@ -309,7 +313,7 @@ mouse_button(GtkWidget *widget, GdkEventButton *event, gpointer data)
|
|||
* Initialize a shell PTY
|
||||
*/
|
||||
void
|
||||
pty_init(const char *shell)
|
||||
pty_init(const char *shell, char *envp[])
|
||||
{
|
||||
char pty_name[64];
|
||||
|
||||
|
@ -333,7 +337,6 @@ pty_init(const char *shell)
|
|||
exit(-1);
|
||||
} else if (shell_pid == 0) {
|
||||
/* Child */
|
||||
char *const env[] = {"TERM=dmd", NULL};
|
||||
int retval;
|
||||
close(pty_master);
|
||||
|
||||
|
@ -350,9 +353,9 @@ pty_init(const char *shell)
|
|||
close(pty_slave);
|
||||
|
||||
if (shell) {
|
||||
retval = execle(shell, "-", NULL, env);
|
||||
retval = execle(shell, "-", NULL, envp);
|
||||
} else {
|
||||
retval = execle("/bin/sh", "-", NULL, env);
|
||||
retval = execle("/bin/sh", "-", NULL, envp);
|
||||
}
|
||||
|
||||
/* Child process is now replaced, nothing beyond this point
|
||||
|
@ -361,6 +364,7 @@ pty_init(const char *shell)
|
|||
perror("Could not start shell process: ");
|
||||
exit(-1);
|
||||
}
|
||||
close(pty_master);
|
||||
}
|
||||
|
||||
close(pty_slave);
|
||||
|
@ -471,11 +475,6 @@ tty_io_poll()
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
tty_set_blocking(int fd, int should_block)
|
||||
{
|
||||
}
|
||||
|
||||
gboolean
|
||||
keydown(GtkWidget *widget, GdkEventKey *event, gpointer data)
|
||||
{
|
||||
|
@ -667,29 +666,115 @@ keydown(GtkWidget *widget, GdkEventKey *event, gpointer data)
|
|||
return TRUE;
|
||||
}
|
||||
|
||||
void
|
||||
show_about()
|
||||
{
|
||||
GtkWidget *dialog;
|
||||
GtkWidget *label;
|
||||
GtkWidget *content_area;
|
||||
GtkDialogFlags flags = GTK_DIALOG_DESTROY_WITH_PARENT;
|
||||
|
||||
const char *message = "Copyright (c) 2018-2022, Seth J. Morabito <web@loomcom.com>\n"
|
||||
"More information can be found on https://loomcom.com/\n";
|
||||
|
||||
dialog = gtk_dialog_new_with_buttons("About",
|
||||
GTK_WINDOW(main_window),
|
||||
flags,
|
||||
"Close",
|
||||
GTK_RESPONSE_NONE,
|
||||
NULL);
|
||||
|
||||
content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
|
||||
|
||||
gtk_widget_set_margin_start(content_area, 20);
|
||||
gtk_widget_set_margin_end(content_area, 20);
|
||||
gtk_widget_set_margin_top(content_area, 20);
|
||||
gtk_widget_set_margin_bottom(content_area, 20);
|
||||
|
||||
label = gtk_label_new(message);
|
||||
|
||||
g_signal_connect_swapped(dialog, "response", G_CALLBACK(gtk_widget_destroy), dialog);
|
||||
|
||||
gtk_container_add(GTK_CONTAINER(content_area), label);
|
||||
gtk_widget_show_all(dialog);
|
||||
}
|
||||
|
||||
void
|
||||
build_menu(GtkWidget *menu_bar)
|
||||
{
|
||||
GtkWidget *file_menu;
|
||||
GtkWidget *help_menu;
|
||||
|
||||
GtkWidget *file_mi;
|
||||
GtkWidget *quit_mi;
|
||||
|
||||
GtkWidget *help_mi;
|
||||
GtkWidget *about_mi;
|
||||
|
||||
|
||||
file_menu = gtk_menu_new();
|
||||
help_menu = gtk_menu_new();
|
||||
|
||||
file_mi = gtk_menu_item_new_with_label("File");
|
||||
quit_mi = gtk_menu_item_new_with_label("Quit");
|
||||
|
||||
help_mi = gtk_menu_item_new_with_label("Help");
|
||||
about_mi = gtk_menu_item_new_with_label("About");
|
||||
|
||||
gtk_menu_item_set_submenu(GTK_MENU_ITEM(file_mi), file_menu);
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(file_menu), quit_mi);
|
||||
|
||||
gtk_menu_item_set_submenu(GTK_MENU_ITEM(help_mi), help_menu);
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(help_menu), about_mi);
|
||||
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(menu_bar), file_mi);
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(menu_bar), help_mi);
|
||||
|
||||
/* Exit when user selects "Quit" from menu */
|
||||
g_signal_connect(quit_mi, "activate", G_CALLBACK(close_window), NULL);
|
||||
g_signal_connect(about_mi, "activate", G_CALLBACK(show_about), NULL);
|
||||
}
|
||||
|
||||
void
|
||||
gtk_setup(int *argc, char ***argv)
|
||||
{
|
||||
GtkWidget *drawing_area;
|
||||
GtkWidget *menu_bar;
|
||||
GtkWidget *box;
|
||||
|
||||
gtk_init(argc, argv);
|
||||
|
||||
/* Create the main window */
|
||||
main_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
||||
|
||||
/* Be sure to exit cleanly when the user closes the window! */
|
||||
g_signal_connect(main_window, "destroy", G_CALLBACK(close_window), NULL);
|
||||
|
||||
/* Set some properties on the main window */
|
||||
gtk_window_set_icon_name(GTK_WINDOW(main_window), "dmd5620");
|
||||
gtk_window_set_title(GTK_WINDOW(main_window), "AT&T DMD 5620");
|
||||
gtk_window_set_resizable(GTK_WINDOW(main_window), FALSE);
|
||||
|
||||
g_signal_connect(main_window, "destroy", G_CALLBACK(close_window), NULL);
|
||||
|
||||
gtk_container_set_border_width(GTK_CONTAINER(main_window), 0);
|
||||
|
||||
/* Create a GTK Box to contain menu and drawing area */
|
||||
box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||
|
||||
/* TODO: Make border width configurable */
|
||||
gtk_container_set_border_width(GTK_CONTAINER(box), 5);
|
||||
|
||||
/* Build the menu */
|
||||
menu_bar = gtk_menu_bar_new();
|
||||
build_menu(menu_bar);
|
||||
|
||||
/* Stuff the menu into the container. */
|
||||
gtk_box_pack_start(GTK_BOX(box), menu_bar, FALSE, FALSE, 0);
|
||||
|
||||
drawing_area = gtk_drawing_area_new();
|
||||
|
||||
gtk_widget_set_size_request(drawing_area, 800, 1024);
|
||||
gtk_box_pack_end(GTK_BOX(box), drawing_area, FALSE, FALSE, 0);
|
||||
|
||||
gtk_window_set_resizable(GTK_WINDOW(main_window), FALSE);
|
||||
|
||||
gtk_container_add(GTK_CONTAINER(main_window), drawing_area);
|
||||
gtk_container_add(GTK_CONTAINER(main_window), box);
|
||||
|
||||
/* Set up the animation handler, which will step the simulation
|
||||
and draw the display in an infinite loop */
|
||||
|
@ -719,24 +804,28 @@ gtk_setup(int *argc, char ***argv)
|
|||
| GDK_POINTER_MOTION_MASK);
|
||||
|
||||
gtk_widget_show_all(main_window);
|
||||
gtk_window_present(GTK_WINDOW(main_window));
|
||||
}
|
||||
|
||||
struct option long_options[] = {
|
||||
{"help", no_argument, 0, 'h'},
|
||||
{"version", no_argument, 0, 'v'},
|
||||
{"inherit", no_argument, 0, 'i'},
|
||||
{"firmware", required_argument, 0, 'f'},
|
||||
{"shell", required_argument, 0, 's'},
|
||||
{"device", required_argument, 0, 'd'},
|
||||
{"nvram", required_argument, 0, 'n'},
|
||||
{"debug", no_argument, 0, 'b'}, /* Hidden and undocumented */
|
||||
{0, 0, 0, 0}};
|
||||
|
||||
void usage()
|
||||
{
|
||||
printf("Usage: dmd5620 [-h] [-v] [-d DEV|-s SHELL] \\\n"
|
||||
printf("Usage: dmd5620 [-h] [-v] [-i] [-d DEV|-s SHELL] \\\n"
|
||||
" [-f VER] [-n FILE] [-- <gtk_options> ...]\n");
|
||||
printf("AT&T DMD 5620 Terminal emulator.\n\n");
|
||||
printf("-h, --help display help and exit\n");
|
||||
printf("-v, --version display version and exit\n");
|
||||
printf("-i, --inherit inherit parent environment\n");
|
||||
printf("-f, --firmware VER Firmware version (\"8;7;3\" or \"8;7;5\")\n");
|
||||
printf("-d, --device DEV serial port name\n");
|
||||
printf("-s, --shell SHELL execute SHELL instead of default user shell\n");
|
||||
|
@ -747,7 +836,7 @@ const char *FIRMWARE_873 = "8;7;3";
|
|||
const char *FIRMWARE_875 = "8;7;5";
|
||||
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
main(int argc, char *argv[], char *envp[])
|
||||
{
|
||||
int c, errflg = 0;
|
||||
char *shell = NULL;
|
||||
|
@ -757,6 +846,7 @@ main(int argc, char *argv[])
|
|||
uint8_t nvram_buf[NVRAM_SIZE];
|
||||
FILE *fp;
|
||||
struct stat sb;
|
||||
bool inherit = false; /* Inherit parent environment */
|
||||
|
||||
snprintf(VERSION_STRING, 64, "%d.%d.%d",
|
||||
VERSION_MAJOR, VERSION_MINOR, VERSION_BUILD);
|
||||
|
@ -769,7 +859,7 @@ main(int argc, char *argv[])
|
|||
|
||||
int option_index = 0;
|
||||
|
||||
while ((c = getopt_long(argc, argv, "vhd:n:t:p:s:f:",
|
||||
while ((c = getopt_long(argc, argv, "hivbd:n:t:p:s:f:",
|
||||
long_options, &option_index)) != -1) {
|
||||
switch(c) {
|
||||
case 0:
|
||||
|
@ -777,9 +867,15 @@ main(int argc, char *argv[])
|
|||
case 'h':
|
||||
usage();
|
||||
exit(0);
|
||||
case 'i':
|
||||
inherit = true;
|
||||
break;
|
||||
case 'v':
|
||||
printf("Version: %s\n", VERSION_STRING);
|
||||
exit(0);
|
||||
case 'b': /* Hidden and undocumented */
|
||||
debug = true;
|
||||
break;
|
||||
case 'n':
|
||||
nvram = optarg;
|
||||
break;
|
||||
|
@ -819,7 +915,7 @@ main(int argc, char *argv[])
|
|||
fprintf(stderr, "Cannot open %s as shell, or file is not executable.\n", shell);
|
||||
return -1;
|
||||
}
|
||||
pty_init(shell);
|
||||
pty_init(shell, inherit ? envp : NULL);
|
||||
} else {
|
||||
if (stat(device, &sb) != 0) {
|
||||
fprintf(stderr, "Cannot open device %s.\n", device);
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
#include <gtk/gtk.h>
|
||||
|
||||
#define WIDTH 800
|
||||
#define WIDTH_IN_BYTES 100
|
||||
#define HEIGHT 1024
|
||||
#define NVRAM_SIZE 2<<12
|
||||
#define VIDRAM_SIZE 1024 * 100
|
||||
|
@ -50,6 +51,7 @@ const struct color COLOR_DARK = { 0, 0, 0, 255 };
|
|||
|
||||
/* dmd_core exported functions */
|
||||
extern uint8_t *dmd_video_ram();
|
||||
extern int dmd_video_ram_dirty();
|
||||
extern int dmd_init(uint8_t version);
|
||||
extern int dmd_step();
|
||||
extern int dmd_step_loop(size_t steps);
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
#define __VERSION_H__
|
||||
|
||||
#define VERSION_MAJOR 2
|
||||
#define VERSION_MINOR 0
|
||||
#define VERSION_BUILD 1
|
||||
#define VERSION_MINOR 1
|
||||
#define VERSION_BUILD 0
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Reference in New Issue