3
\$\begingroup\$

I’ve recently completed my very first library in Zig, SuperZIG IO, designed to simplify input and output handling. The library provides utilities for console input/output, event listening (like key presses), and aims to offer cross-platform support (Windows, Linux, and macOS).

Here are some of the key features:

  • Input Handling: Read user inputs with options for customization.
  • Output Formatting: Output text with formatting capabilities.
  • Event Listening: Listen to system key press events in real-time.

I’d love to hear your feedback on:

  1. The library’s overall design and structure.
  2. Code readability and maintainability.
  3. Performance optimization tips (especially for cross-platform compatibility).
  4. Ideas for additional features or improvements.

You can find the repo (source code) on GitHub :
SuperZIG IO Repository


SOURCE CODE v0.0.1

  • io.zig

      const       std                         = @import("std");
      const       builtin                     = @import("builtin");
      const       typesFile                   = @import("./types/_.zig");
    
    
    
      /// Alias for io library types.
      pub const   types                       = typesFile;
    
      /// Global buffer for General purposes.
      var         g_buff_1024 : [1024]u8      = undefined;
    
    
    
      /// Outputs a simple message followed by a newline.
      pub inline fn out
      ( comptime _msg: []const u8 ) 
      !void 
      {
          try outWith(_msg ++ "\n", .{});
      }
    
      /// Outputs a formatted message to the console.
      pub inline fn outWith
      ( comptime _fmt: []const u8, _args: anytype ) 
      !void 
      {
          try nosuspend std.io.getStdOut().writer().print(_fmt, _args);
      }
    
      /// Outputs a simple message followed by a newline to a specific writer.
      pub inline fn outWriter
      ( comptime _msg: []const u8, _writer: anytype ) 
      !void 
      {
          try _writer.print("{s}", .{_msg});
      }
    
      /// Outputs a formatted message followed by a newline to a specific writer.
      pub inline fn outWriterWith
      ( comptime _fmt: []const u8, _args: anytype, _writer: anytype ) 
      !void 
      {
          try _writer.print(_fmt, _args);
      }
    
    
    
      /// Reads input from the user until a newline character is encountered.
      pub inline fn in
      () 
      ![]const u8 
      {
          const l_res = try std.io.getStdIn().reader().readUntilDelimiterOrEof(&g_buff_1024, '\n');
          return l_res orelse unreachable;
      }
    
      /// Reads input from the user until a newline character is encountered using custom buffer.
      pub inline fn inBuff
      ( _buff: []u8 )
      !void
      {
          // set zeros (maybe set all buffer to zeros using std ?)
          for (_buff) |*l_byte|
          {
              l_byte.* = 0;
          }
    
          _ = try std.io.getStdIn().reader().readUntilDelimiterOrEof(_buff, '\n');
      }
    
      /// Displays a message and waits for the user to respond.
      pub inline fn ask
      ( comptime _msg: []const u8 ) 
      ![]const u8 
      {
          try         out (_msg);
          return try  in  ();
      }
    
      /// Displays a message and waits for the user to respond using custom buffer.
      pub inline fn askBuff
      ( comptime _msg: []const u8, _buff: []u8 ) 
      !void
      {
          try out     (_msg);
          try inBuff  (_buff);
      }
    
    
    
      /// Listens for key input.
      pub inline fn once
      ( _call: anytype ) 
      !void 
      {
          if (builtin.os.tag == .windows) 
          {
              return @import("./func/on/windows.zig").once(_call);
          } 
    
          else if (builtin.os.tag == .linux) 
          {
              return @import("./func/on/linux.zig").once(_call);
          } 
    
          else 
          {
              try outWith("OS not supported : {}\n", .{builtin.os.tag});
              unreachable;
          }
      }
    
      /// Listens for key input until the condition is met.
      pub inline fn on
      ( _cond: anytype, _call: anytype ) 
      !void 
      {
          if (builtin.os.tag == .windows) 
          {
              return @import("./func/on/windows.zig").on(_cond, _call);
          } 
    
          else if (builtin.os.tag == .linux) 
          {
              return @import("./func/on/linux.zig").on(_cond, _call);
          } 
    
          else 
          {
              try outWith("OS not supported : {}\n", .{builtin.os.tag});
              unreachable;
          }
      }
    
    
    
      /// Clears the terminal(Screen).
      pub inline fn cls
      () 
      !void 
      {
          _ = try out("\x1b[2J\x1b[H");
      }
    
  • /types/_.zig

      pub const   MOD_ALT       = 1 << 0;
      pub const   MOD_SHIFT     = 1 << 1;
      pub const   MOD_CTRL      = 1 << 2;
    
      pub const key = @import("./key.zig").key;
    
  • /types/key.zig

      pub const   MOD_ALT       = 1 << 0;
      pub const   MOD_SHIFT     = 1 << 1;
      pub const   MOD_CTRL      = 1 << 2;
    
      /// Struct to represent key press details.
      pub const key = struct
      {
          pub const State = enum
          {
              None,
              SinglePress,
              DoublePress,
          };
    
          m_val   : u8    = 0,            // key code
          m_mod   : u3    = 0,            // modifier keys (ctrl, alt, shift)
          m_state : State = State.None,   // press state 
    
          /// Returns the press state.
          pub inline fn state
          (_self: *const key) 
          State
          {
              if(_self.m_state == State.None)
              {
                  const isSinglePress = _self.m_mod == 0;
    
                  if(isSinglePress)
                  {
                      _self.m_state = State.SinglePress;
                  }
                  else
                  {
                      _self.m_state = State.DoublePress;
                  }
              }
    
              return _self.m_state;
          }
    
          /// Returns the key code.
          pub inline fn get
          (_self: *const key) 
          u8 
          {
              return _self.m_val;
          }
    
          /// Returns a character representation of the key.
          pub inline fn char
          (_self: *const key) 
          u8 
          {
              return switch (_self.m_mod)
              {
                  4      =>
                  {
                      // ctrl 
                      return '@' + _self.m_val;
                  },
    
                  5      =>
                  {
                      // alt + ctrl 
                      return '@' + _self.m_val;
                  },
    
                  else    =>
                  {
                      // others
                      return _self.m_val;
                  },
              };
          }
    
          /// Returns a string representation of the modifiers.
          pub inline fn mod
          (_self: *const key) 
          [] const u8 
          {
              return switch (_self.m_mod)
              {
                  0       => "none"           ,
                  1       => "alt"            ,
                  2       => "shift"          ,
                  3       => "alt + shift"    ,
                  4       => "ctrl"           ,
                  5       => "alt + ctrl"     ,
                  else    => "unknown"        ,
              };
          }
    
          /// Returns true if the Alt modifier key was pressed.
          pub inline fn alt
          (_self: *const key)
          bool
          {
              return (_self.m_mod & MOD_ALT) != 0;
          }
    
          /// Returns true if the Ctrl modifier key was pressed.
          pub inline fn ctrl
          (_self: *const key)
          bool
          {
              return (_self.m_mod & MOD_CTRL) != 0;
          }
    
          // Returns true if the Shift modifier key was pressed.
          pub inline fn 
          shift(_self: *const key)
          bool
          {
              return (_self.m_mod & MOD_SHIFT) != 0;
          }
    
          /// Returns count of pressed keys.
          pub inline fn count
          (_self: *const key)
          u8
          {
              var m_count : u8 = 1;
    
              if (_self.alt())     m_count += 1;
              if (_self.ctrl())    m_count += 1;
              if (_self.shift())   m_count += 1;
    
              return m_count;
          }
      };
    
  • func/on/windows.zig

      const   std                     = @import("std");
      const   loop                    = @import("../../libs/loop.zig");
      const   types                   = @import("../../types/_.zig");
    
      const   windowsH                = @cImport
      ({
          @cInclude("windows.h");                             // Windows API definitions for handling keyboard input and system events.
      });
    
      const g_State                   = types.key.State;      // Alias for key press state enumeration.
    
      // Virtual key codes for common keys
      const VK_LSHIFT                 = 0xA0;
      const VK_RSHIFT                 = 0xA1;
      const VK_LCONTROL               = 0xA2;
      const VK_RCONTROL               = 0xA3;
      const VK_LMENU                  = 0xA4;
      const VK_RMENU                  = 0xA5;
    
      // Flags representing modifier key states (Ctrl, Shift, Alt)
      const LEFT_CTRL_PRESSED         = 0x0008;
      const RIGHT_CTRL_PRESSED        = 0x0004;
      const LEFT_SHIFT_PRESSED        = 0x0010;
      const RIGHT_SHIFT_PRESSED       = 0x0020;
      const LEFT_ALT_PRESSED          = 0x0002;
      const RIGHT_ALT_PRESSED         = 0x0001;
    
      // Maximum allowed delay between key presses to be considered as double press
      const MAX_KEYPRESS_DELAY : f64  = 1.0;
    
    
      /// Listen for a single key press and invoke the provided callback function
      pub inline fn once
      ( _call: anytype ) 
      !void
      {
          try Logic.Core( _call, windowsH.GetStdHandle(windowsH.STD_INPUT_HANDLE) );
      }
    
      /// Listen for key input until the specified condition is met and invoke the callback function
      pub inline fn on
      ( _cond: anytype, _call: anytype ) 
      !void
      {
          try loop.untilWith( _cond, Logic.Core, .{ _call, windowsH.GetStdHandle(windowsH.STD_INPUT_HANDLE) } );
      }
    
    
      const Logic = struct
      {
          var g_lastKeyTime   : f64       = 0.0;              // Stores the timestamp of the last key press
          var g_lastKey       : u8        = 0;                // Stores the last pressed key
          var g_state         : g_State   = g_State.None;     // Stores the current key press state (SinglePress/DoublePress)
    
          /// Core function to handle key input and invoke the callback function
          inline fn Core
          ( _call: anytype, _hConsole: anytype )
          !void
          {
              var l_inputRecord   : windowsH.INPUT_RECORD     = undefined;
              var l_bytesRead     : u32                       = 0;
    
              while(true)
              {
                  // Read the console input event (key press)
                  _ = windowsH.ReadConsoleInputA(_hConsole, &l_inputRecord, 1, &l_bytesRead);
    
                  if (l_inputRecord.EventType == windowsH.KEY_EVENT and l_inputRecord.Event.KeyEvent.bKeyDown != 0)
                  {
                      const l_key         = l_inputRecord.Event.KeyEvent.uChar.AsciiChar;
                      const l_currentTime = @as(f64, @floatFromInt(std.time.timestamp()));
    
                      // Check the time between the current and last key press
                      if (l_currentTime - g_lastKeyTime <= MAX_KEYPRESS_DELAY)
                      {
                          // If the time between presses is less than the allowed threshold, treat as double press
                          g_state         = g_State.DoublePress;
                      }
                      else
                      {
                          // Otherwise, treat as single press
                          g_state         = g_State.SinglePress;
                      }
    
                      g_lastKeyTime       = l_currentTime;        // Update the timestamp of the last key press
                      g_lastKey           = l_key;                // Update the last pressed key
    
                      const l_modifiers   = Help.detectMods();    // Detect the modifier keys (Shift, Ctrl, Alt)
    
                      // If the key pressed is 0 (no valid key), ignore
                      if (l_key == 0)
                      {
                          return;
                      }
    
                      // Create a key object with the key value, modifiers, and key press state
                      const l_res         = types.key
                      {
                          .m_val          = Help.getKeyValue(l_key),
                          .m_mod          = l_modifiers,
                          .m_state        = g_state,
                      };
    
                      // Call the provided callback function with the key object
                      try _call(l_res);
    
                      break;
                  }
              }
          }
    
          const Help = struct
          {
              inline fn
              getKeyValue(key: u8) u8
              {
                  return key;
    
                  // // Printable character
                  // if (std.ascii.isPrint(key))
                  // {
                  //     return key;
                  // }
    
                  // // Handle non-printable key
                  // else
                  // {
                  //     return key;
                  // }
              }
    
              /// Detect the state of modifier keys (Shift, Ctrl, Alt)
              inline fn 
              detectMods
              ( ) 
              u3
              {
                  var l_modifiers : u3 = 0;
    
                  // Check if Shift key (either left or right) is pressed
                  if (checkKeyState(windowsH.VK_LSHIFT) & 0x8000 != 0 or checkKeyState(windowsH.VK_RSHIFT) & 0x8000 != 0)
                  {
                      l_modifiers |= 1 << 1;                  // Set the Shift modifier
                  }
    
                  // Check if Ctrl key (either left or right) is pressed and there's a valid key press
                  if (checkKeyState(windowsH.VK_LCONTROL) & 0x8000 != 0 or checkKeyState(windowsH.VK_RCONTROL) & 0x8000 != 0)
                  {
                      if (g_lastKey != 0)
                      {
                          l_modifiers |= 1 << 2;              // Set the Ctrl modifier
                      }
                  }
    
                  // Check if Alt key (either left or right) is pressed and there's a valid key press
                  if (checkKeyState(windowsH.VK_LMENU) & 0x8000 != 0 or checkKeyState(windowsH.VK_RMENU) & 0x8000 != 0)
                  {
                      if (g_lastKey != 0)
                      {
                          l_modifiers |= 1 << 0;              // Set the Alt modifier
                      }
                  }
    
                  return l_modifiers;                         // Return the detected modifiers
              }
    
              /// Get the current state of a key based on its virtual key code
              inline fn 
              checkKeyState
              ( _keyCode: i32 ) 
              i32
              {
                  return windowsH.GetKeyState(_keyCode);      // Return the key state (pressed or not)
              }
          };
      };
    
  • func/on/linux.zig

      const   std                     = @import("std");
      const   loop                    = @import("../../libs/loop.zig");
      const   types                   = @import("../../types/_.zig");
    
      const   linuxH                  = @cImport
      ({
          @cInclude("termios.h");                             // Termios library for terminal I/O settings
          @cInclude("unistd.h");                              // POSIX operating system API (e.g., read, tcsetattr)
      });
    
      const   g_ts                    = linuxH.termios;
      const   g_tS                    = linuxH.struct_termios;
    
    
      // variables to hold the old/new terminal settings
      var     g_oldSettings : g_tS    = undefined;
      var     g_newSettings : g_ts    = undefined;
    
      // variables to hold the key input
      var     g_keyBuffer   : [3]u8   = undefined;
      var     g_bytesRead   : isize   = undefined;
    
    
      /// Listen for key input
      pub inline fn once
      ( _call: anytype ) 
      !void
      {
          try Logic.Core( _call );
      }
    
      /// Listen for key input until the condition is met
      pub inline fn on
      ( _cond: anytype, _call: anytype ) 
      !void
      {
          try loop.untilWith( _cond, Logic.Core, .{ _call } );
      }
    
    
      const Logic = struct
      {
          /// The entry point of our logic !
          inline fn Core
          ( _call: anytype )
          !void
          {
              Help.init();
    
              // Read key input
              g_bytesRead = linuxH.read(0, &g_keyBuffer, @sizeOf(@TypeOf(g_keyBuffer)));
    
              if (g_bytesRead > 0)
              {
                  const l_res: types.key = Help.detectMods(g_keyBuffer[0..], g_bytesRead);
    
                  try _call(l_res);
              }
              else
              {
                  // try _call(types.key{ .key = 0, .character = 0, .modifiers = 0, });
                  unreachable;
              }
    
              Help.reset();
          }
    
          const Help = struct
          {
              /// Restore old terminal settings to prevent permanent changes.
              inline fn reset
              ()
              void
              {
                  _ = linuxH.tcsetattr(0, linuxH.TCSAFLUSH, &g_oldSettings);
              }
    
              /// Setup the new terminal settings.
              inline fn init
              ()
              void
              {
                  // Cleanup the buffer
                  g_keyBuffer[0..].* = @as([3]u8, std.mem.zeroes([3]u8));
    
                  // Get current terminal settings and store in 'oldSettings'
                  _ = linuxH.tcgetattr(0, &g_oldSettings);
    
                  // Copy the current settings to 'newSettings'
                  g_newSettings = g_oldSettings;
    
    
                  // Disable canonical mode (no buffering, no waiting for Enter key to submit input)
                  g_newSettings.c_lflag &= @as(c_uint, ~@as(c_uint, linuxH.ICANON));  // Clear the ICANON flag to disable canonical mode
    
    
                  // Disable echoing characters (input characters will not be displayed on the terminal)
                  g_newSettings.c_lflag &= @as(c_uint, ~@as(c_uint, linuxH.ECHO));    // Clear the ECHO flag to disable character echoing
    
    
                  // Set minimum number of characters to 1 (we don’t need to wait for Enter key)
                  g_newSettings.c_cc[linuxH.VMIN] = 1;                                // Set the VMIN control character to 1 to require at least 1 character
    
    
                  // Set timeout to 0 (no wait time for input)
                  g_newSettings.c_cc[linuxH.VTIME] = 0;                               // Set the VTIME control character to 0 to disable wait time
    
    
                  // Set the new terminal settings
                  if (linuxH.tcsetattr(0, linuxH.TCSAFLUSH, &g_newSettings) != 0) 
                  {
                      // Register the cleanup function to restore terminal settings on program exit
                      defer reset();
                  }
              }
    
              // Function to detect modifier keys (Ctrl, Alt, Shift)
              inline fn detectMods
              ( _keyBuffer: []const u8, _bytesRead: isize )
              types.key
              {
                  var l_res = types.key
                  {
                      .m_val      = 0,
                      .m_mod      = 0,
                      .m_state    = types.key.State.None
                  };
    
                  if (_bytesRead == 0)
                  {
                      return l_res;
                  }
    
                  // Alt key detection
                  if (_keyBuffer[0] == 0x1B)
                  {
                      l_res.m_mod |= types.MOD_ALT;
    
                      if (_bytesRead > 1) {
                          l_res.m_val = _keyBuffer[1];
    
                          if (std.ascii.isUpper(_keyBuffer[1]))
                          {
                              l_res.m_mod |= types.MOD_SHIFT;
                          }
    
                          if (_keyBuffer[1] >= 0x00 and _keyBuffer[1] <= 0x1F)
                          {
                              l_res.m_mod |= types.MOD_CTRL;
                          }
                      }
    
                      return l_res;
                  }
    
                  // Ctrl key detection
                  if (_keyBuffer[0] >= 0x00 and _keyBuffer[0] <= 0x1F)
                  {
                      l_res.m_mod |= types.MOD_CTRL;
                      l_res.m_val = _keyBuffer[0];
    
                      return l_res;
                  }
    
                  // Shift detection
                  if (std.ascii.isUpper(_keyBuffer[0]))
                  {
                      l_res.m_mod |= types.MOD_SHIFT;
                  }
    
                  l_res.m_val = getKeyValue(_keyBuffer[0]);
    
                  return l_res;
              }  
    
              inline fn
              getKeyValue(key: u8) u8
              {
                  return key;
    
                  // // Printable character
                  // if (std.ascii.isPrint(key))
                  // {
                  //     return key;
                  // }
    
                  // // Handle non-printable key
                  // else
                  // {
                  //     return key;
                  // }
              }
          };
      };
    

Any thoughts, tips, or constructive criticism are greatly appreciated.

SuperZIG IO Repository

\$\endgroup\$

0

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.