• Please review our updated Terms and Rules here

ZMC: A Dual-Pane File Manager for CP/M & Z80 (Optimized for 32-line terminals)

volney

Member
Joined
May 10, 2024
Messages
15
Hi everyone,

I'd like to share a project I've been working on: ZMC (Z80 Managed Commander). It is a lightweight, "Norton-style" file manager written in C for CP/M 2.2+ systems.

I developed ZMC with a focus on fluidity over serial connections (115200 baud) and larger screens, specifically optimized for VT100/VT102 terminals with 32-line displays.

Key Features:

  • Dual-Pane Interface: Classic side-by-side file management.
  • Surgical Screen Refresh: Minimizes flicker and redraw lag over serial lines.
  • Batch Operations: Multiple file selection (Spacebar) for batch copying (F5).
  • Integrated Tools:
    • F3 (View): Paged text viewer [cite: 2026-01-31].
    • F4 (Dump): Professional Hexadecimal/ASCII binary dump [cite: 2026-01-31].
    • F8 (Delete): Safe file deletion with confirmation [cite: 2026-01-31].
  • Full Drive Support: Quickly switch between drives A: through Z: [cite: 2026-01-31].
I've tested it on my own Z80 hardware kit, and it feels very snappy. I hope it becomes a useful tool for your retro-computing workflow!

GitHub Repository: https://github.com/lu1pvt/zmc-cpm Binary (.COM): Available in the "Releases" section for immediate use.

Feedback and contributions are more than welcome!

Best regards,Volney Torres lu1pvt
 
Last edited by a moderator:
Tested on NABU ROMWBW:

1769927050154.png

I like it, great work. I see it's using hw calls for the Z80 hardware kit but to be compatible with other systems, you'd need to use BDOS to read the keyboard.

Replace this:

Code:
#asm
    loophw_zmc:
        in a, (025h)
        and 01h
        jr z, loophw_zmc
        in a, (020h)
        ld l, a
        ld h, 0
#endasm

With this:
Code:
#asm
    loop_bdos_kbd:
        ld  c, 06h
        ld  e, 0FFh
        call 0005h
        or  a
        jr  z, loop_bdos_kbd
        ld  l, a
        ld  h, 0
#endasm
 
Last edited:
Hardcoded values, i.e. in 'panel.c':
Code:
void draw_panel(Panel *p, int x_offset) {
    int i;
    char title[20];
    int visible_rows = 28; // PANEL_HEIGHT (30) - 2 bordes
should use the defined macros from 'zmc.h':
Code:
void draw_panel(Panel *p, int x_offset) {
    int i;
    char title[20];
    int visible_rows = PANEL_HEIGHT - 2;
to make porting to other screen resolutions (80*25) easier, ESC-codes for the terminal should also be macros ...
 
Hi everyone! Thanks for the amazing reception and for the suggested patches. I've been closely following your feedback and have consolidated all improvements into the new official version 1.1.

Here is a summary of the changes based on your feedback:

  • @Wormetti & @Jenz: I have migrated all keyboard input to BDOS Function 6. The program is now 100% responsive on both real hardware (like my Z80 kit) and emulators (SimH confirmed by @VeryVon).
  • @Jenz: No more hardcoded values. The system now uses the macros defined in 'zmc.h' for panel dimensions and visible rows, making it easier to port to other resolutions.
  • Multi-file Selection (v1.1): Added support for marking files with [Space] or [Insert] (a '*' will appear). Copy (F5) and Delete (F8) functions now operate in batches on all selected files.
  • UI Cleanup: Removed blue color attributes to avoid 'black blocks' on certain terminals. The scheme is now a clean Grey/White on Black with surgical line refreshing to eliminate flickering.
I've cleaned up the official repository, and you can download the stable v1.1 here:GitHub: https://github.com/lu1pvt/zmc-cpm

I hope you enjoy it and keep the suggestions coming!
 
Last edited by a moderator:
If I can work out how to build it (are you using cross-compile tools?) I'll see about adding support for datestamper and CP/M3 stamps.
 
@shirsch Try z88dk and 'make.sh' in the repository ...
@volney Other parts in the code also need to be changed:
Diff:
diff --git a/operaciones.c b/operaciones.c
index 6d20b54..98e3b1d 100644
--- a/operaciones.c
+++ b/operaciones.c
@@ -182,7 +182,7 @@ void view_file(Panel *p) {
                     line_count++;
                    
                     // Pausa cuando se llena la pantalla (aprox 28 líneas)
-                    if (line_count >= 28) {
+                    if (line_count >= VISIBLE_ROWS) {
                         printf("\x1b[7m -- MAS (ESC:Salir) -- \x1b[0m");
                        
                         unsigned char k = wait_key_hw();
@@ -250,7 +250,7 @@ void dump_file(Panel *p) {
                 address += 16;
                 line_count++;
 
-                if (line_count >= 28) {
+                if (line_count >= VISIBLE_ROWS) {
                     printf("\x1b[7m -- MAS (ESC:Salir) -- \x1b[0m");
                     if (wait_key_hw() == 27) goto end_dump;
                     printf("\r                       \r");
diff --git a/panel.c b/panel.c
index 13bff47..3bbe50e 100644
--- a/panel.c
+++ b/panel.c
@@ -20,19 +20,18 @@ void draw_frame(int x, int y, int w, int h, char *title) {
 void draw_panel(Panel *p, int x_offset) {
     int i;
     char title[20];
-    int visible_rows = VISIBLE_ROWS; // macro  Wormetti
     /* Lógica de Scroll para 28 filas visibles */
     if (p->current_idx < p->scroll_offset) {
         p->scroll_offset = p->current_idx;
     }
-    if (p->current_idx >= p->scroll_offset + visible_rows) {
-        p->scroll_offset = p->current_idx - (visible_rows - 1);
+    if (p->current_idx >= p->scroll_offset + VISIBLE_ROWS) {
+        p->scroll_offset = p->current_idx - (VISIBLE_ROWS - 1);
     }
     printf("%s", CLR_PANEL);
     sprintf(title, " Unidad %c: ", p->drive);
     draw_frame(x_offset, 1, PANEL_WIDTH, PANEL_HEIGHT, title);
    
-    for (i = 0; i < visible_rows; i++) {
+    for (i = 0; i < VISIBLE_ROWS; i++) {
         int f_idx = i + p->scroll_offset;
         printf("\x1b[%d;%dH", i + 2, x_offset + 1);
        
@@ -55,7 +54,7 @@ void draw_file_line(Panel *p, int x_offset, int file_idx) {
     // Determinar el carácter de selección: '*' si está marcado, espacio si no
     selector = p->files[file_idx].seleccionado ? '*' : ' ';
 
-    if (file_idx >= p->scroll_offset && file_idx < p->scroll_offset + 28) {
+    if (file_idx >= p->scroll_offset && file_idx < p->scroll_offset + VISIBLE_ROWS) {
         // Posicionar cursor en la fila y columna correspondiente
         printf("\x1b[%d;%dH", screen_row, x_offset + 1);
 
Hi everyone!

@shirsch: To clarify, we are using z88dk for compilation. I've included an updated make.sh in the repository that uses zcc to build the binary [cite: 2026-02-01]. I would absolutely love to see support for DateStamper and CP/M3 stamps!

@Jenz: Thanks for the heads-up! [cite: 2026-02-01] I have just pushed Version 1.1 to GitHub, and I have already implemented the changes you suggested using the z88dk toolchain:

  • Macro Migration: All hardcoded values (like the line counts) have been replaced with VISIBLE_ROWS and other macros from zmc.h to ensure portability across different terminal sizes.
  • BDOS Keyboard: Following your excellent advice, I've fully migrated to BDOS Function 6 using z88dk's bdos() calls. It is now perfectly responsive in SimH and real Z80 hardware.
  • Multi-file Operations: You can now tag multiple files with [Space] or [Insert] for batch copy/delete [cite: 2026-01-31].
  • UI Cleanup: I've removed the blue background attributes to fix the "black block" issues on serial terminals [cite: 2026-01-31].
Official Repository (Licensed under MIT):🔗 https://github.com/lu1pvt/zmc-cpm

Lastly, a special thanks to Peter Norton. [cite: 2026-02-01] This project is a humble tribute to his genius and the original Norton Commander that inspired this Z80 journey. [cite: 2026-02-01]

Enjoy the update!
 
Installation of z88dk looks like way more of a time commitment than I'm prepared to make. I'll pass on setting up for development.
 
Installation of z88dk looks like way more of a time commitment than I'm prepared to make. I'll pass on setting up for development.
Headline: ZMC v1.1.1 - Pre-compiled Binaries Available!

Hi @shirsch,

I totally understand! Setting up the z88dk toolchain can be a bit of a journey. To make things easier for everyone who just wants to use the tool without compiling it themselves, I have uploaded the pre-compiled .COM binary directly to the GitHub repository

You can download the latest stable version here: 👉 [Link a tu sección de Releases o al archivo .COM directo]

Just copy ZMC.COM to your CP/M disk (via serial, Kermit, or by mounting the image in SimH) and you're good to go!

It already includes the latest improvements:

BDOS Function 6 for perfect keyboard response.

Optimized layout for 32-line terminals.

Full support for file tagging and batch operations.

Enjoy the commander!
 
The KC85 community has the KC Commander [here], which runs on top of MicroDOS (aka CP/M 2.2). While quite specific to the KC85 environment, it can also serve as a nice reference/blueprint for modern development?
 
The KC85 community has the KC Commander [here], which runs on top of MicroDOS (aka CP/M 2.2). While quite specific to the KC85 environment, it can also serve as a nice reference/blueprint for modern development?
That KC-Commander screenshot is amazing! It’s a great example of how much UI polish you can squeeze out of CP/M. While ZMC prioritizes portability across different serial terminals (Minicom/VT102), seeing that level of visual detail is definitely inspiring for the next UI refactor.
 
Hi volney,

What a great idea to port mc to CP/M! I like it very much. Under Linux "mc" is the 1st program that I install unless already available from the beginning. My play system and testbed is CP/M3 on Z80-MBC2 HW with some hacks. Terminal is picocom in xterm - xterm got some modifications to translate the function keys to the classical CP/M ^E, ^X, ^S, ^D, ^R, ^C (UP,DOWN,LEFT,RIGHT,PAGEUP,PAGEDOWN) and so on cursor coxmmands. This is easier than to modify WS, TP, ZED, CCP etc. to make them understand the strange <ESC>[nn~ sequences.
Long story short - in my fork I modified the main commando loop to understand the original <ESC> commands as well as the "original" CP/M one-char commands. I also added the possibility to detect and adapt the screen resolution under CP/M3. For other systems there's the cmd line argument --config that shows the hex address in the zmc.com file, where the screen resolution constants are located for easy patching with a hex editor.
The binary zmc.com is available for testing in my repo: https://github.com/Ho-Ro/zmc-cpm/raw/refs/heads/main/zmc.com

Have fun!
Martin

P.S.: I translated the program messages and some comments to English, b/c it's strange for me to switch between different languages in the source (code:English - comments: Spain, German, French etc.).
 
P.P.S.: I do not want to hijack the original project, my effort shall test some new ideas and when ready I will roll a clean PR for volney's repo.
Some thoughts come to my mind:
1. date/time stamp (either BDOS function 102 or direcly by checking dma part3 during load_directory.
2. USER areas - must do some experiments.
I also removed all colour changing ESC-sequences - CP/M on an ancient terminal was only black and white - or amber or green :)
zmc-120x40.png
 
Ok, date/time can be easily retrieved, time display is easy (hour/min are just BCD), date (days since 31.12.1977) must be converted to YYYY.MM.DD.
zmc_time.png
stay tuned ...
Martin
 
Are you aware of the ZSystem SYSLIB suite? There are routines for all kinds of commonly needed operations, including CP/M3 to BCD or ASCII date conversion. Also possible to figure out the time subsystem (CP/M vs. Datestamper) and retrieve the information in a universal manner.

How simple is it for the Z88 compiler to use actual REL files for build and linking?
 
Back
Top