Created
March 22, 2025 22:57
-
-
Save mengstr/b3dd327c3f971f026e397e0fd272a89b to your computer and use it in GitHub Desktop.
UTF8 terminal, input and socket for win/macOS/Linux demo
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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