Skip to content

Instantly share code, notes, and snippets.

@mengstr
Created March 22, 2025 22:57
Show Gist options
  • Save mengstr/b3dd327c3f971f026e397e0fd272a89b to your computer and use it in GitHub Desktop.
Save mengstr/b3dd327c3f971f026e397e0fd272a89b to your computer and use it in GitHub Desktop.
UTF8 terminal, input and socket for win/macOS/Linux demo
/*
Compilation instructions:
On Windows (using MinGW):
gcc foo.c -lws2_32 -o foo.exe
On macOS/Linux:
gcc foo.c -o foo
*/
#ifdef _WIN32
#include <winsock2.h> // Must come before windows.h
#include <ws2tcpip.h>
#include <windows.h>
#include <io.h>
#include <fcntl.h>
#include <conio.h>
#pragma comment(lib, "ws2_32.lib")
// Redefine PRINTF so that literal format strings become wide literal strings.
#define PRINTF(fmt, ...) wprintf(L"" fmt, ##__VA_ARGS__)
// Windows sleep in milliseconds.
#define SLEEP(ms) Sleep(ms)
typedef SOCKET socket_t;
#define INVALID_SOCKET_VALUE INVALID_SOCKET
#else
#include <unistd.h>
#include <termios.h>
#include <fcntl.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#define PRINTF(fmt, ...) printf(fmt, ##__VA_ARGS__)
// Unix sleep in milliseconds.
#define SLEEP(ms) usleep((ms)*1000)
typedef int socket_t;
#define INVALID_SOCKET_VALUE (-1)
#endif
#include <stdio.h>
#include <stdlib.h>
#include <locale.h>
#include <string.h>
// ========================== Keyboard Handling ===============================
#ifdef _WIN32
// pollKey: returns the character if available, -1 otherwise.
int pollKey(void) {
if (_kbhit())
return _getch();
return -1;
}
#else
// For Unix-like systems, use termios to disable echo and canonical mode.
struct termios orig_termios;
void enableRawMode(void) {
if (tcgetattr(STDIN_FILENO, &orig_termios) == -1) {
perror("tcgetattr");
exit(1);
}
struct termios raw = orig_termios;
raw.c_lflag &= ~(ECHO | ICANON);
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) {
perror("tcsetattr");
exit(1);
}
}
void disableRawMode(void) {
tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios);
}
// pollKey: non-blocking read from stdin.
int pollKey(void) {
fd_set set;
struct timeval timeout;
FD_ZERO(&set);
FD_SET(STDIN_FILENO, &set);
timeout.tv_sec = 0;
timeout.tv_usec = 0;
int rv = select(STDIN_FILENO + 1, &set, NULL, NULL, &timeout);
if (rv > 0) {
char ch;
if (read(STDIN_FILENO, &ch, 1) == 1)
return ch;
}
return -1;
}
#endif
// ============================ Socket Handling ===============================
#ifdef _WIN32
// Windows requires Winsock initialization.
void initWinsock(void) {
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0) {
fprintf(stderr, "WSAStartup failed.\n");
exit(1);
}
}
void cleanupWinsock(void) {
WSACleanup();
}
#else
// No Winsock initialization needed on Unix-like systems.
void initWinsock(void) {}
void cleanupWinsock(void) {}
#endif
// Set a socket to non-blocking mode.
void setNonBlocking(socket_t sock) {
#ifdef _WIN32
u_long mode = 1;
ioctlsocket(sock, FIONBIO, &mode);
#else
int flags = fcntl(sock, F_GETFL, 0);
fcntl(sock, F_SETFL, flags | O_NONBLOCK);
#endif
}
/*
* pollSocket: Checks if data is available on the socket.
* Returns:
* '\r' if a carriage return is found,
* -2 if the client has disconnected,
* -1 if no relevant data is available.
*/
int pollSocket(socket_t client_sock) {
if (client_sock == INVALID_SOCKET_VALUE)
return -1;
fd_set set;
FD_ZERO(&set);
#ifdef _WIN32
FD_SET(client_sock, &set);
struct timeval timeout = { 0, 0 };
#else
FD_SET(client_sock, &set);
struct timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 0;
#endif
int rv = select((int)client_sock + 1, &set, NULL, NULL, &timeout);
if (rv > 0 && FD_ISSET(client_sock, &set)) {
char buf[16];
#ifdef _WIN32
int ret = recv(client_sock, buf, sizeof(buf), 0);
#else
int ret = recv(client_sock, buf, sizeof(buf), 0);
#endif
if (ret > 0) {
// Check for carriage return among received data.
for (int i = 0; i < ret; i++) {
if (buf[i] == '\r')
return '\r';
}
} else if (ret == 0) {
// ret == 0 indicates the client has disconnected.
return -2;
}
}
return -1;
}
// sendCounter: Sends the current counter value as a CRLF-terminated string.
void sendCounter(socket_t client_sock, unsigned int counter) {
if (client_sock == INVALID_SOCKET_VALUE)
return;
char outbuf[64];
snprintf(outbuf, sizeof(outbuf), "%u\r\n", counter);
#ifdef _WIN32
send(client_sock, outbuf, (int)strlen(outbuf), 0);
#else
send(client_sock, outbuf, strlen(outbuf), 0);
#endif
}
// ============================ Main Program ================================
int main(void) {
#ifdef _WIN32
initWinsock();
// Set console for UTF-8 and ANSI escape sequences.
_setmode(_fileno(stdout), _O_U8TEXT);
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
if (hOut == INVALID_HANDLE_VALUE) exit(1);
DWORD dwMode = 0;
if (!GetConsoleMode(hOut, &dwMode)) exit(1);
dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
if (!SetConsoleMode(hOut, dwMode)) exit(1);
#else
enableRawMode();
#endif
// Use the system locale (UTF-8).
setlocale(LC_ALL, "");
// ================== Socket Setup =====================
socket_t listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if (listen_sock == INVALID_SOCKET_VALUE) {
perror("socket");
exit(1);
}
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(7777);
serv_addr.sin_addr.s_addr = INADDR_ANY;
if (bind(listen_sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
perror("bind");
exit(1);
}
if (listen(listen_sock, 1) < 0) {
perror("listen");
exit(1);
}
// Set listening socket to non-blocking mode.
setNonBlocking(listen_sock);
socket_t client_sock = INVALID_SOCKET_VALUE;
// ================== Box Drawing =======================
int start_row = 10; // row where the box starts (1-indexed)
int start_col = 30; // column where the box starts (1-indexed)
int width = 20; // total width of the box
int height = 7; // total height of the box
// Clear screen and draw the box border.
PRINTF("\033[2J\033[H"); // Clear screen & home cursor.
PRINTF("\033[31m"); // Set text color to red.
// Top border.
PRINTF("\033[%d;%dH┌", start_row, start_col);
for (int i = 0; i < width - 2; i++) {
PRINTF("─");
}
PRINTF("┐");
// Sides.
for (int row = 1; row < height - 1; row++) {
PRINTF("\033[%d;%dH│", start_row + row, start_col);
for (int col = 0; col < width - 2; col++) {
PRINTF(" ");
}
PRINTF("│");
}
// Bottom border.
PRINTF("\033[%d;%dH└", start_row + height - 1, start_col);
for (int i = 0; i < width - 2; i++) {
PRINTF("─");
}
PRINTF("┘");
// Reset text attributes.
PRINTF("\033[0m");
// Calculate position to display the counter (roughly centered in the box).
int counter_row = start_row + height / 2;
int counter_col = start_col + (width / 2) - 2; // Adjust for centering
unsigned int counter = 0;
int key;
int sockKey;
// ================== Main Loop ==========================
while (1) {
// Accept a new connection if no client is connected.
if (client_sock == INVALID_SOCKET_VALUE) {
client_sock = accept(listen_sock, NULL, NULL);
if (client_sock != INVALID_SOCKET_VALUE) {
setNonBlocking(client_sock);
}
}
// Poll keyboard.
key = pollKey();
if (key != -1) {
if (key == 'q' || key == 'Q') {
break; // exit the loop.
} else if (key == 'r' || key == 'R') {
counter = 0; // reset counter.
} else if (key == '\r') { // CR from keyboard.
sendCounter(client_sock, counter);
}
}
// Poll socket if connected.
if (client_sock != INVALID_SOCKET_VALUE) {
sockKey = pollSocket(client_sock);
if (sockKey == '\r') { // CR received from socket.
sendCounter(client_sock, counter);
} else if (sockKey == -2) { // Client disconnected.
// Close the client socket and allow a new connection.
#ifdef _WIN32
closesocket(client_sock);
#else
close(client_sock);
#endif
client_sock = INVALID_SOCKET_VALUE;
}
}
// Update the counter display inside the box.
PRINTF("\033[%d;%dH", counter_row, counter_col);
PRINTF("%4u", counter);
fflush(stdout);
SLEEP(100); // wait 100 milliseconds.
counter++;
}
// Cleanup: close sockets and restore terminal.
if (client_sock != INVALID_SOCKET_VALUE) {
#ifdef _WIN32
closesocket(client_sock);
#else
close(client_sock);
#endif
}
#ifdef _WIN32
closesocket(listen_sock);
#else
close(listen_sock);
disableRawMode();
#endif
cleanupWinsock();
// Move the cursor below the box before exiting.
PRINTF("\033[%d;0H", start_row + height + 2);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment