Compare commits

...

10 Commits

Author SHA1 Message Date
Seth Morabito 234fcdc5f5 Allow shell environment to not be inherited
The parent shell environment can now be inherited by passing the "-i,
--inherit" argument, but by default will NOT be inherited.
2023-07-02 10:00:23 -07:00
Seth Morabito 969214c6d5 Revert last dmd_core change, fix submodule
The submodule was pointing at a private-SSH URL instead of the public
github URL for dmd_core. This commit fixes that.

It also reverts the last change to dmd_core, which apparently introduced
a bug.
2022-12-04 17:05:30 -08:00
Seth Morabito 07988707ef Update dmd_core 2022-11-20 10:28:04 -08:00
Seth Morabito 157ac0f57e Cairo painting fix
The screen refresh callback was creating a cairo surface and calling
cairo_paint() directly, when it should not have been. Instead, it now
simply calls gtk_widget_queue_draw() to request that the drawing
callback do the work it needs to do.
2022-09-14 10:42:58 -07:00
Seth Morabito bd74df94a0 Attempt to execute at 7.2MHz, not 10MHz
I am a dunderhead. The Bellmac 32 CPU of the DMD 5620 did not execute
at 10MHz. It executed at 7.2MHz, derived from a 28.8MHz clock source.
2022-09-14 09:42:47 -07:00
Seth Morabito c3eab0fc4e GTK menu and Video Dirty Bit
- Adds a new menu that will eventually become more useful.

- Introduces the video RAM dirty bit to help reduce memcpy()
  calls.
2022-09-13 20:01:53 -07:00
Seth Morabito a8ee31b219 Update dmd_core 2022-09-13 11:42:39 -07:00
Seth Morabito db99836fc2 Release version 2.1.0 2022-09-12 16:02:19 -07:00
Seth Morabito 825aada63a Pass environment to subprocess
This change passes the user environment to the forked shell.

Additionally, the maximum number of steps allowed per loop has been
increased from 250000 to 350000. I will continue to experiment with
this for best performance.
2022-09-12 15:57:43 -07:00
Seth Morabito dec45ac9a1 Limit the number of steps per loop
It was possible for the code to attempt to run up to 10,000,000
simulated steps each time through the main simulation loop. This was
much too many simulated steps, and could result in starving the other
GTK threads of resources.

This change drops that maximum value way way down to 150,000 steps. This
should guarantee that the GTK tick callback can always complete in a
timely fashion and not steal resources from the other threads, while
still giving excellent CPU performance.
2022-09-12 13:28:43 -07:00
6 changed files with 176 additions and 65 deletions

2
.gitmodules vendored
View File

@ -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

View File

@ -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.

@ -1 +1 @@
Subproject commit 03d0d36061e1dad2f3b0e3d1d670c817f0cf056f
Subproject commit 73ea34e619a2a726213682f6c498c9abc3a29902

View File

@ -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);

View File

@ -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);

View File

@ -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