Romain-p
Romain-p

Reputation: 475

Hide ^C pressing ctrl-c in C

I'm creating my own shell and I would like to disable ^C when any user on any Linux distributation press ctrl+c.

I don't need to handle the signal SIGINT, I already did it for don't stop the program on ctrl+c. I just want to know how to hide these two characters ^C.

Is there any function to call or env variables to set at the start of my program?

EDIT

  int a = fork();
  if (!a) {
    char *cmd[] = {"/bin/stty", 0 };
    char *cmd_args[] = {" ", "-echoctl", 0};
    execve(cmd[0], cmd_args, env);
  }

Tried this. It removes my ^C on ctrl-c but it still displays a square character, like if the character can't be displayed. It seems to be EOT (003 ascii)

Upvotes: 5

Views: 5514

Answers (3)

The ^C comes from the echo on the terminal driver in Linux

Here's an example program in C. It first disables saves the current settings, and registers an atexit handler to restore settings when the program exits, then disables echo on the terminal of standard input. Then it enters an infinite while loop. When you now type anything on the terminal, nothing is displayed, not even that ^C.

The trick that shells use is that they completely replace the input handling on the terminal, switching off the canonical input handling, and reading standard input one character at a time, and handling the echoing on their own - something that requires far more code than is possible in a Stack Overflow answer.

#include <termios.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>


struct termios saved;

void restore(void) {
    tcsetattr(STDIN_FILENO, TCSANOW, &saved);
}


int main() {
    struct termios attributes;

    tcgetattr(STDIN_FILENO, &saved);
    atexit(restore);

    tcgetattr(STDIN_FILENO, &attributes);
    attributes.c_lflag &= ~ ECHO;
    tcsetattr(STDIN_FILENO, TCSAFLUSH, &attributes);

    printf("Entering the loop\n");
    while(1) {};
}

Upvotes: 5

nico
nico

Reputation: 1352

You can use ANSI escape codes to delete ^C characters. In the function that processes the SIGINT signal, print the characters that move the cursor twice to the left, then delete all the characters on the right up to the end of the line.

The example below works on macOS and Raspberry Pi OS.

On Windows this trick is not needed as pressing ctrl+c is silent.

/**
    remove_ctrl_c.c
    gcc remove_ctrl_c.c -o remove_ctrl_c && ./remove_ctrl_c

    Remove the characters ^C with ANSI escape codes.
    (see https://en.wikipedia.org/wiki/ANSI_escape_code)

    \b     : Move cursor one character on the left.
    \b     : Idem
    \033[K : Delete all the characters on the right of
             the cursor until the end of the line.
             You can also use two spaces if you prefer,
             but they will be present in the output although
             they are not visible.
    \n     : Add a new line. This is optional,
             but if you remove it and some characters
             are printed on the last line, the terminal
             will add an extra % character to indicate
             that the new line character was absent.

    Note that, as the printf command is buffered,
    we need to use the fflush command before the end
    of the program to force stdout to be updated.
*/

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

volatile sig_atomic_t KEYBOARD_INTERRUPT = 0;

void handleSignal(int signal)
{
    KEYBOARD_INTERRUPT = 1;
}

int main()
{
    signal(SIGINT, handleSignal);
    printf("Remove ^C on exit!");
    fflush(stdout);
    while (!KEYBOARD_INTERRUPT)
    {
    }
    printf("\b\b\033[K\n");
    fflush(stdout);
    return 0;
}

Upvotes: 0

Running stty -echoctl should hide it. See man stty for more details.

Upvotes: 3

Related Questions