/***************************************************************************** * pce * *****************************************************************************/ /***************************************************************************** * File name: src/drivers/video/terminal.c * * Created: 2003-04-18 by Hampa Hug * * Copyright: (C) 2003-2025 Hampa Hug * *****************************************************************************/ /***************************************************************************** * This program is free software. You can redistribute it and / or modify it * * under the terms of the GNU General Public License version 2 as published * * by the Free Software Foundation. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY, without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * * Public License for more details. * *****************************************************************************/ #include #include #include #include #include #include #define TRM_ESC_ESC 1 #define TRM_ESC_OK 2 #define TRM_ESC_ALT 4 #define TRM_ESC_CTRL 8 #define TRM_ESC_SUPER 16 #if defined(PCE_HOST_MACOS) #define TRM_ESC_KEYS (TRM_ESC_SUPER | TRM_ESC_CTRL) #else #define TRM_ESC_KEYS (TRM_ESC_ALT | TRM_ESC_CTRL) #endif void trm_init (terminal_t *trm, void *ext) { trm->ext = ext; trm->set_msg_emu_ext = NULL; trm->set_msg_emu = NULL; trm->set_key_ext = NULL; trm->set_key = NULL; trm->set_mouse_ext = NULL; trm->set_mouse = NULL; trm->get_mouse_ext = NULL; trm->get_mouse = NULL; trm->del = NULL; trm->set_msg_trm = NULL; trm->update = NULL; trm->check = NULL; trm->is_open = 0; trm->escape_key = PCE_KEY_ESC; trm->escape = 0; trm->w = 0; trm->h = 0; trm->buf_cnt = 0; trm->buf = NULL; trm->scale = 1; trm->aspect_x = 4; trm->aspect_y = 3; trm->min_w = 320; trm->min_h = 240; trm->mouse_scale_x[0] = 1; trm->mouse_scale_x[1] = 1; trm->mouse_scale_x[2] = 0; trm->mouse_scale_y[0] = 1; trm->mouse_scale_y[1] = 1; trm->mouse_scale_y[2] = 0; trm->scale_buf_cnt = 0; trm->scale_buf = NULL; trm->update_x = 0; trm->update_y = 0; trm->update_w = 0; trm->update_h = 0; trm->pict_index = 0; } void trm_free (terminal_t *trm) { trm_close (trm); free (trm->buf); free (trm->scale_buf); } void trm_del (terminal_t *trm) { if (trm == NULL) { return; } trm_free (trm); if (trm->del != NULL) { trm->del (trm->ext); } } int trm_open (terminal_t *trm, unsigned w, unsigned h) { if (trm->is_open) { return (0); } trm_set_size (trm, w, h); if (trm->open != NULL) { if (trm->open (trm->ext, w, h)) { return (1); } } trm->is_open = 1; return (0); } int trm_close (terminal_t *trm) { if (trm->is_open == 0) { return (0); } if (trm->close != NULL) { if (trm->close (trm->ext)) { return (1); } } trm->is_open = 0; return (0); } void trm_set_msg_fct (terminal_t *trm, void *ext, void *fct) { trm->set_msg_emu_ext = ext; trm->set_msg_emu = fct; } void trm_set_key_fct (terminal_t *trm, void *ext, void *fct) { trm->set_key_ext = ext; trm->set_key = fct; } void trm_set_mouse_fct (terminal_t *trm, void *ext, void *fct) { trm->set_mouse_ext = ext; trm->set_mouse = fct; } void trm_set_get_mouse_fct (terminal_t *trm, void *ext, void *fct) { trm->get_mouse_ext = ext; trm->get_mouse = fct; } static int trm_screenshot_get_name (terminal_t *trm, char *str) { unsigned i; for (i = 0; i < 256; i++) { sprintf (str, "pce%04u.ppm", trm->pict_index++); if (pce_file_exists (str) == 0) { return (0); } } return (1); } int trm_screenshot (terminal_t *trm, const char *fname) { FILE *fp; char str[256]; unsigned long cnt; if ((fname == NULL) || (fname[0] == 0)) { if (trm_screenshot_get_name (trm, str)) { return (1); } fname = str; } if ((fp = fopen (fname, "wb")) == NULL) { return (1); } cnt = 3 * (unsigned long) trm->w * (unsigned long) trm->h; fprintf (fp, "P6\n%u %u\n%u\x0a", trm->w, trm->h, 255); if (fwrite (trm->buf, 1, cnt, fp) != cnt) { fclose (fp); return (1); } fclose (fp); return (0); } int trm_set_msg_trm (terminal_t *trm, const char *msg, const char *val) { if (strcmp (msg, "term.escape") == 0) { trm_set_escape_str (trm, val); return (0); } if (strcmp (msg, "term.screenshot") == 0) { trm_screenshot (trm, val); return (0); } if (trm->set_msg_trm != NULL) { return (trm->set_msg_trm (trm->ext, msg, val)); } return (-1); } void trm_set_mouse_scale (terminal_t *trm, int mul_x, int div_x, int mul_y, int div_y) { trm->mouse_scale_x[0] = mul_x; trm->mouse_scale_x[1] = div_x; trm->mouse_scale_x[2] = 0; trm->mouse_scale_y[0] = mul_y; trm->mouse_scale_y[1] = div_y; trm->mouse_scale_y[2] = 0; } void trm_set_size (terminal_t *trm, unsigned w, unsigned h) { unsigned long cnt; if ((w == 0) || (h == 0)) { free (trm->buf); trm->buf_cnt = 0; trm->buf = NULL; trm->w = 0; trm->h = 0; return; } if ((trm->w == w) && (trm->h == h)) { return; } cnt = 3 * (unsigned long) w * (unsigned long) h; if (trm->buf_cnt != cnt) { unsigned char *tmp; tmp = realloc (trm->buf, cnt); if (tmp == NULL) { return; } trm->buf = tmp; trm->buf_cnt = cnt; } trm->w = w; trm->h = h; trm->update_x = 0; trm->update_y = 0; trm->update_w = w; trm->update_h = h; } void trm_set_min_size (terminal_t *trm, unsigned w, unsigned h) { trm->min_w = w; trm->min_h = h; } void trm_set_scale (terminal_t *trm, unsigned v) { trm->scale = (v < 1) ? 1 : v; trm->update_x = 0; trm->update_y = 0; trm->update_w = trm->w; trm->update_h = trm->h; } void trm_set_aspect_ratio (terminal_t *trm, unsigned x, unsigned y) { trm->aspect_x = x; trm->aspect_y = y; } void trm_set_pixel (terminal_t *trm, unsigned x, unsigned y, const unsigned char *col) { unsigned char *buf; buf = trm->buf + 3 * (trm->w * y + x); buf[0] = col[0]; buf[1] = col[1]; buf[2] = col[2]; if (trm->update_w == 0) { trm->update_x = x; trm->update_w = 1; } else { if (x < trm->update_x) { trm->update_w += trm->update_x - x; trm->update_x = x; } if (x >= (trm->update_x + trm->update_w)) { trm->update_w = x - trm->update_x + 1; } } if (trm->update_h == 0) { trm->update_y = y; trm->update_h = 1; } else { if (y < trm->update_y) { trm->update_h += trm->update_y - y; trm->update_y = y; } if (y >= (trm->update_y + trm->update_h)) { trm->update_h = y - trm->update_y + 1; } } } void trm_set_lines (terminal_t *trm, const void *buf, unsigned y, unsigned cnt) { unsigned long w3, tmp; const unsigned char *src; unsigned char *dst; w3 = 3UL * trm->w; src = buf; dst = trm->buf + w3 * y; while (cnt > 0) { if (memcmp (dst, src, w3) != 0) { break; } src += w3; dst += w3; y += 1; cnt -= 1; } tmp = cnt * w3; while (cnt > 0) { tmp -= w3; if (memcmp (dst + tmp, src + tmp, w3) != 0) { break; } cnt -= 1; } if (cnt == 0) { return; } memcpy (dst, src, w3 * cnt); trm->update_x = 0; trm->update_w = trm->w; if (trm->update_h == 0) { trm->update_y = y; trm->update_h = cnt; } else { if (y < trm->update_y) { trm->update_h += trm->update_y - y; trm->update_y = y; } if ((y + cnt) > (trm->update_y + trm->update_h)) { trm->update_h = (y + cnt) - trm->update_y; } } } void trm_update (terminal_t *trm) { if ((trm->update_w == 0) || (trm->update_h == 0)) { return; } if (trm->update != NULL) { trm->update (trm->ext); } trm->update_x = 0; trm->update_y = 0; trm->update_w = 0; trm->update_h = 0; } void trm_check (terminal_t *trm) { if (trm->check != NULL) { trm->check (trm->ext); } } int trm_set_msg_emu (terminal_t *trm, const char *msg, const char *val) { if (trm->set_msg_emu != NULL) { return (trm->set_msg_emu (trm->set_msg_emu_ext, msg, val)); } return (1); } int trm_set_escape_str (terminal_t *trm, const char *str) { pce_key_t key; key = pce_key_from_string (str); if (key == PCE_KEY_NONE) { return (1); } trm->escape_key = key; return (0); } void trm_set_escape_key (terminal_t *trm, pce_key_t key) { trm->escape_key = key; } static int trm_set_key_magic (terminal_t *trm, pce_key_t key) { switch (key) { case PCE_KEY_0: trm_set_msg_emu (trm, "emu.cpu.speed", "0"); return (0); case PCE_KEY_1: trm_set_msg_emu (trm, "emu.cpu.speed", "1"); return (0); case PCE_KEY_2: trm_set_msg_emu (trm, "emu.cpu.speed", "2"); return (0); case PCE_KEY_3: trm_set_msg_emu (trm, "emu.cpu.speed", "3"); return (0); case PCE_KEY_4: trm_set_msg_emu (trm, "emu.cpu.speed", "4"); return (0); case PCE_KEY_5: trm_set_msg_emu (trm, "emu.cpu.speed", "5"); return (0); case PCE_KEY_6: trm_set_msg_emu (trm, "emu.cpu.speed", "6"); return (0); case PCE_KEY_7: trm_set_msg_emu (trm, "emu.cpu.speed", "7"); return (0); case PCE_KEY_8: trm_set_msg_emu (trm, "emu.cpu.speed", "8"); return (0); case PCE_KEY_A: trm_set_msg_trm (trm, "term.autosize", ""); return (0); case PCE_KEY_F: trm_set_msg_trm (trm, "term.fullscreen.toggle", ""); return (0); case PCE_KEY_G: trm_set_msg_trm (trm, "term.grab", ""); return (0); case PCE_KEY_M: trm_set_msg_trm (trm, "term.release", ""); trm_set_msg_trm (trm, "term.fullscreen", "0"); trm_set_msg_emu (trm, "emu.stop", "1"); return (0); case PCE_KEY_P: trm_set_msg_emu (trm, "emu.pause.toggle", ""); return (0); case PCE_KEY_Q: trm_set_msg_trm (trm, "term.release", ""); trm_set_msg_trm (trm, "term.fullscreen", "0"); trm_set_msg_emu (trm, "emu.exit", "1"); return (0); case PCE_KEY_R: trm_set_msg_emu (trm, "emu.reset", "1"); return (0); case PCE_KEY_S: trm_screenshot (trm, NULL); return (0); case PCE_KEY_UP: case PCE_KEY_KP_8: trm_set_scale (trm, trm->scale + 1); return (0); case PCE_KEY_DOWN: case PCE_KEY_KP_2: if (trm->scale > 1) { trm_set_scale (trm, trm->scale - 1); } return (0); case PCE_KEY_LEFT: case PCE_KEY_KP_4: trm_set_msg_emu (trm, "emu.cpu.speed.step", "-1"); return (0); case PCE_KEY_RIGHT: case PCE_KEY_KP_6: trm_set_msg_emu (trm, "emu.cpu.speed.step", "+1"); return (0); default: return (1); } return (1); } static void trm_set_key_emu (terminal_t *trm, unsigned event, pce_key_t key) { if (event == PCE_KEY_EVENT_MAGIC) { if (trm_set_key_magic (trm, key) == 0) { return; } } if (trm->set_key != NULL) { trm->set_key (trm->set_key_ext, event, key); } } void trm_set_key (terminal_t *trm, unsigned event, pce_key_t key) { if (event == PCE_KEY_EVENT_DOWN) { if (key == trm->escape_key) { trm->escape ^= TRM_ESC_ESC; } if (trm->escape & TRM_ESC_ESC) { return; } if (key == PCE_KEY_LALT || key == PCE_KEY_RALT) { trm->escape |= TRM_ESC_ALT; } else if (key == PCE_KEY_LSUPER || key == PCE_KEY_RSUPER) { trm->escape |= TRM_ESC_SUPER; } else if (key == PCE_KEY_LCTRL || key == PCE_KEY_RCTRL) { trm->escape |= TRM_ESC_CTRL; } else if (key == PCE_KEY_PAUSE) { if ((trm->escape & TRM_ESC_KEYS) == 0) { trm_set_msg_trm (trm, "term.release", ""); trm_set_msg_trm (trm, "term.fullscreen", "0"); trm_set_msg_emu (trm, "emu.exit", "1"); return; } } if ((trm->escape & TRM_ESC_KEYS) == TRM_ESC_KEYS) { trm_set_msg_trm (trm, "term.release", ""); trm->escape &= ~TRM_ESC_KEYS; } } else if (event == PCE_KEY_EVENT_UP) { if (trm->escape & TRM_ESC_ESC) { if (key != trm->escape_key) { trm_set_key_emu (trm, PCE_KEY_EVENT_MAGIC, key); trm->escape &= ~TRM_ESC_ESC; } return; } if (key == PCE_KEY_LALT || key == PCE_KEY_RALT) { trm->escape &= ~TRM_ESC_ALT; } else if (key == PCE_KEY_LSUPER || key == PCE_KEY_RSUPER) { trm->escape &= ~TRM_ESC_SUPER; } else if (key == PCE_KEY_LCTRL || key == PCE_KEY_RCTRL) { trm->escape &= ~TRM_ESC_CTRL; } else if (key == PCE_KEY_PAUSE) { return; } } trm_set_key_emu (trm, event, key); } void trm_set_mouse (terminal_t *trm, int dx, int dy, unsigned but) { int tx, ty; dx = trm->mouse_scale_x[0] * dx + trm->mouse_scale_x[2]; tx = dx; dx = dx / trm->mouse_scale_x[1]; trm->mouse_scale_x[2] = tx - trm->mouse_scale_x[1] * dx; dy = trm->mouse_scale_y[0] * dy + trm->mouse_scale_y[2]; ty = dy; dy = dy / trm->mouse_scale_y[1]; trm->mouse_scale_y[2] = ty - trm->mouse_scale_y[1] * dy; if (trm->set_mouse != NULL) { trm->set_mouse (trm->set_mouse_ext, dx, dy, but); } } void trm_get_mouse (terminal_t *trm, int *x, int *y) { if (trm->get_mouse != NULL) { trm->get_mouse (trm->get_mouse_ext, x, y); } else { *x = 0; *y = 0; } } void trm_get_scale (terminal_t *trm, unsigned w, unsigned h, unsigned *fx, unsigned *fy) { unsigned long f; unsigned long w2, h2; unsigned long maxw, maxh; if ((w == 0) || (h == 0)) { *fx = 0; *fy = 0; return; } f = 1; while (((f * w) < trm->min_w) && ((f * h) < trm->min_h)) { f += 1; } *fx = trm->scale + f - 1; *fy = trm->scale + f - 1; if ((trm->aspect_x > 0) && (trm->aspect_y > 0)) { w2 = *fx * w; h2 = *fy * h; maxw = (trm->aspect_x * h2) / trm->aspect_y; maxh = (trm->aspect_y * w2) / trm->aspect_x; while ((h2 + h) <= maxh) { h2 += h; *fy += 1; } while ((w2 + w) <= maxw) { w2 += w; *fx += 1; } if (w2 < maxw) { if ((trm->aspect_y * (w2 + w - maxw)) < (trm->aspect_x * (maxw - w2))) { w2 += w; *fx += 1; } } } } static unsigned char *trm_scale_get_buf (terminal_t *trm, unsigned w, unsigned h) { unsigned long cnt; unsigned char *tmp; cnt = 3 * (unsigned long) w * (unsigned long) h; if (cnt > trm->scale_buf_cnt) { tmp = realloc (trm->scale_buf, cnt); if (tmp == NULL) { return (NULL); } trm->scale_buf = tmp; trm->scale_buf_cnt = cnt; } return (trm->scale_buf); } static void trm_scale_w1 (unsigned char *dst, const unsigned char *src, unsigned w, unsigned h, unsigned fy) { unsigned i, n, y; n = 3 * w; for (y = 0; y < h; y++) { memcpy (dst, src, n); dst += n; src += n; for (i = 1; i < fy; i++) { memcpy (dst, dst - n, n); dst += n; } } } static void trm_scale_w2 (unsigned char *dst, const unsigned char *src, unsigned w, unsigned h, unsigned fy) { unsigned i, n, x, y; n = 6 * w; for (y = 0; y < h; y++) { for (x = 0; x < w; x++) { dst[0] = src[0]; dst[1] = src[1]; dst[2] = src[2]; dst[3] = src[0]; dst[4] = src[1]; dst[5] = src[2]; dst += 6; src += 3; } for (i = 1; i < fy; i++) { memcpy (dst, dst - n, n); dst += n; } } } static void trm_scale_wx (unsigned char *dst, const unsigned char *src, unsigned w, unsigned h, unsigned fx, unsigned fy) { unsigned i, j, n, x, y; n = 3 * fx * w; for (y = 0; y < h; y++) { for (x = 0; x < w; x++) { for (i = 0; i < fx; i++) { dst[0] = src[0]; dst[1] = src[1]; dst[2] = src[2]; dst += 3; } src += 3; } for (j = 1; j < fy; j++) { memcpy (dst, dst - n, n); dst += n; } } } const unsigned char *trm_scale (terminal_t *trm, const unsigned char *src, unsigned w, unsigned h, unsigned fx, unsigned fy) { unsigned dw, dh; unsigned char *dst; if ((fx == 1) && (fy == 1)) { return (src); } dw = fx * w; dh = fy * h; dst = trm_scale_get_buf (trm, dw, dh); if (dst == NULL) { return (NULL); } if (fx == 1) { trm_scale_w1 (dst, src, w, h, fy); } else if (fx == 2) { trm_scale_w2 (dst, src, w, h, fy); } else { trm_scale_wx (dst, src, w, h, fx, fy); } return (dst); }