diff --git a/display/Makefile b/display/Makefile
index 990348c8359..824fc00c01f 100644
--- a/display/Makefile
+++ b/display/Makefile
@@ -27,6 +27,7 @@ SUBDIRS = \
d.rast \
d.rast.arrow \
d.rast.num \
+ d.redraw \
d.rgb \
d.rhumbline \
d.text \
diff --git a/display/d.redraw/Makefile b/display/d.redraw/Makefile
new file mode 100644
index 00000000000..00038c24a1e
--- /dev/null
+++ b/display/d.redraw/Makefile
@@ -0,0 +1,11 @@
+
+MODULE_TOPDIR = ../..
+
+PGM = d.redraw
+
+LIBES = $(DISPLAYLIB) $(GISLIB)
+DEPENDENCIES = $(DISPLAYDEP) $(GISDEP)
+
+include $(MODULE_TOPDIR)/include/Make/Module.make
+
+default: cmd
diff --git a/display/d.redraw/d.redraw.html b/display/d.redraw/d.redraw.html
new file mode 100644
index 00000000000..923cbbde44f
--- /dev/null
+++ b/display/d.redraw/d.redraw.html
@@ -0,0 +1,18 @@
+
DESCRIPTION
+
+d.redraw redraws the content of the currently selected monitor. The
+active monitor can be selected with d.mon.
+
+SEE ALSO
+
+
+ d.erase,
+ d.rast,
+ d.vect,
+ d.mon
+
+
+AUTHOR
+
+Huidae Cho, New Mexico State University
+Based on the d.redraw script by Martin Landa, Czech Republic
diff --git a/display/d.redraw/main.c b/display/d.redraw/main.c
new file mode 100644
index 00000000000..1f52757e315
--- /dev/null
+++ b/display/d.redraw/main.c
@@ -0,0 +1,108 @@
+/****************************************************************************
+ *
+ * MODULE: d.redraw
+ * AUTHOR(S): Huidae Cho
+ * Based on scripts/d.redraw/d.redraw.py by Martin Landa
+ * PURPOSE: Redraws the content of currently selected monitor
+ * COPYRIGHT: (C) 2024 by the GRASS Development Team
+ *
+ * This program is free software under the GNU General
+ * Public License (>=v2). Read the file COPYING that
+ * comes with GRASS for details.
+ *
+ *****************************************************************************/
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#define LINES_SIZE_INC 1024
+#define LINE_LEN 1024
+
+int main(int argc, char **argv)
+{
+ struct GModule *module;
+ const char *mon;
+ char element[GPATH_MAX], cmd_file[GPATH_MAX];
+ FILE *fp;
+ int lines_size, num_lines, num_comment_lines;
+ char **lines, line[LINE_LEN];
+ char **cmd_argv;
+ int i;
+
+ G_gisinit(argv[0]);
+
+ module = G_define_module();
+ G_add_keyword(_("display"));
+ G_add_keyword(_("graphics"));
+ G_add_keyword(_("monitors"));
+ module->description =
+ _("Redraws the content of currently selected monitor.");
+
+ if (G_parser(argc, argv))
+ exit(EXIT_FAILURE);
+
+ if (!(mon = G_getenv_nofatal("MONITOR")))
+ G_fatal_error(_("No graphics device selected. Use d.mon to select "
+ "graphics device."));
+
+ D_open_driver();
+ D_close_driver();
+
+ G_temp_element(element);
+ strcat(element, "/MONITORS/");
+ strcat(element, mon);
+ G_file_name(cmd_file, element, "cmd", G_mapset());
+
+ if (!(fp = fopen(cmd_file, "r")))
+ G_fatal_error(_("Unable to open file '%s' for reading."), cmd_file);
+
+ lines_size = num_lines = num_comment_lines = 0;
+ lines = NULL;
+
+ /* read and save cmd lines; run display commands now */
+ while (G_getl2(line, LINE_LEN, fp)) {
+ /* don't add d.redraw this time */
+ if (strcmp(line, "d.redraw") == 0) {
+ /* remove its comment lines above */
+ num_lines -= num_comment_lines;
+ continue;
+ }
+ if (lines_size == num_lines) {
+ lines_size += LINES_SIZE_INC;
+ lines = G_realloc(lines, sizeof(char *) * lines_size);
+ }
+ lines[num_lines++] = G_store(line);
+
+ if (*line == '#') {
+ num_comment_lines++;
+ /* render next command into the same existing file */
+ if (strstr(line, "# GRASS_RENDER_") == line)
+ putenv(G_store(line + 2));
+ continue;
+ }
+ num_comment_lines = 0;
+
+ /* split line by space; double-quote delimiters protect spaces */
+ cmd_argv = G_tokenize2(line, " ", "\"");
+ /* run display command */
+ G_vspawn_ex(cmd_argv[0], (const char **)cmd_argv);
+ G_free_tokens(cmd_argv);
+ }
+
+ fclose(fp);
+
+ /* write out cmd file without d.redraw */
+ if (!(fp = fopen(cmd_file, "w")))
+ G_fatal_error(_("Unable to open file '%s' for writing."), cmd_file);
+
+ for (i = 0; i < num_lines; i++)
+ fprintf(fp, "%s\n", lines[i]);
+
+ fclose(fp);
+
+ exit(EXIT_SUCCESS);
+}
diff --git a/scripts/Makefile b/scripts/Makefile
index 2b898941463..84a9ad21e26 100644
--- a/scripts/Makefile
+++ b/scripts/Makefile
@@ -9,7 +9,6 @@ SUBDIRS = \
d.polar \
d.rast.edit \
d.rast.leg \
- d.redraw \
d.shade \
d.what.rast \
d.what.vect \
diff --git a/scripts/d.redraw/Makefile b/scripts/d.redraw/Makefile
deleted file mode 100644
index 28799bdcdb4..00000000000
--- a/scripts/d.redraw/Makefile
+++ /dev/null
@@ -1,7 +0,0 @@
-MODULE_TOPDIR = ../..
-
-PGM = d.redraw
-
-include $(MODULE_TOPDIR)/include/Make/Script.make
-
-default: script
diff --git a/scripts/d.redraw/d.redraw.html b/scripts/d.redraw/d.redraw.html
deleted file mode 100644
index 00481bc1f16..00000000000
--- a/scripts/d.redraw/d.redraw.html
+++ /dev/null
@@ -1,19 +0,0 @@
-DESCRIPTION
-
-d.redraw redraws the content of the currently selected
-monitor. The active monitor can be selected
-with d.mon.
-
-SEE ALSO
-
-
-
- d.erase,
- d.rast,
- d.vect,
- d.mon
-
-
-AUTHOR
-
-Martin Landa, Czech Republic
diff --git a/scripts/d.redraw/d.redraw.py b/scripts/d.redraw/d.redraw.py
deleted file mode 100755
index 297471cf5f2..00000000000
--- a/scripts/d.redraw/d.redraw.py
+++ /dev/null
@@ -1,72 +0,0 @@
-#!/usr/bin/env python3
-
-############################################################################
-#
-# MODULE: d.redraw
-# AUTHOR(S): Martin Landa
-# PURPOSE: Redraws the content of currently selected monitor
-# COPYRIGHT: (C) 2011-2015 by the GRASS Development Team
-#
-# This program is free software under the GNU General
-# Public License (>=v2). Read the file COPYING that
-# comes with GRASS for details.
-#
-#############################################################################
-
-# %module
-# % description: Redraws the content of currently selected monitor.
-# % keyword: display
-# % keyword: graphics
-# % keyword: monitors
-# %end
-
-import os
-import sys
-
-from grass.script import core as grass
-from grass.script.utils import split
-
-
-def main():
- mon = grass.gisenv().get("MONITOR", None)
- if not mon:
- grass.fatal(
- _("No graphics device selected. Use d.mon to select graphics device.")
- )
-
- monCmd = grass.parse_command("d.mon", flags="g").get("cmd", None)
- if not monCmd or not os.path.isfile(monCmd):
- grass.fatal(_("Unable to open file '%s'") % monCmd)
-
- try:
- fd = open(monCmd, "r")
- cmdList = fd.readlines()
-
- grass.run_command("d.erase")
-
- for cmd in cmdList:
- if cmd.startswith("#"):
- continue
- grass.call(split(cmd))
- except OSError as e:
- grass.fatal(
- _("Unable to open file '%s' for reading. Details: %s") % (monCmd, e)
- )
-
- fd.close()
-
- # restore cmd file
- try:
- fd = open(monCmd, "w")
- fd.writelines(cmdList)
- except OSError as e:
- grass.fatal(
- _("Unable to open file '%s' for writing. Details: %s") % (monCmd, e)
- )
-
- return 0
-
-
-if __name__ == "__main__":
- options, flags = grass.parser()
- sys.exit(main())