Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions Sources/CSystem/include/CSystemDarwin.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
This source file is part of the Swift System open source project

Copyright (c) 2024 Apple Inc. and the Swift System project authors
Licensed under Apache License v2.0 with Runtime Library Exception

See https://swift.org/LICENSE.txt for license information
*/

#if defined(__APPLE__)

#include <sys/ioctl.h>
#include <termios.h>

// Terminal ioctl shims
int _system_ioctl_TIOCGWINSZ(int fd, struct winsize *ws);
int _system_ioctl_TIOCSWINSZ(int fd, const struct winsize *ws);

#endif
7 changes: 7 additions & 0 deletions Sources/CSystem/include/CSystemLinux.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

#include <sys/epoll.h>
#include <sys/eventfd.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/sysinfo.h>
Expand All @@ -20,7 +21,13 @@
#include <fcntl.h>
#include <pthread.h>
#include <sched.h>
#include <termios.h>
#include <unistd.h>
#include "io_uring.h"

// Terminal ioctl shims
int _system_ioctl_TIOCGWINSZ(int fd, struct winsize *ws);
int _system_ioctl_TIOCSWINSZ(int fd, const struct winsize *ws);

#endif

1 change: 1 addition & 0 deletions Sources/CSystem/include/module.modulemap
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
module CSystem {
header "CSystemDarwin.h"
header "CSystemLinux.h"
header "CSystemWASI.h"
header "CSystemWindows.h"
Expand Down
24 changes: 24 additions & 0 deletions Sources/CSystem/shims.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,30 @@

#include <CSystemLinux.h>

// Terminal ioctl shims for Linux
int _system_ioctl_TIOCGWINSZ(int fd, struct winsize *ws) {
return ioctl(fd, TIOCGWINSZ, ws);
}

int _system_ioctl_TIOCSWINSZ(int fd, const struct winsize *ws) {
return ioctl(fd, TIOCSWINSZ, ws);
}

#endif

#if defined(__APPLE__)

#include <CSystemDarwin.h>

// Terminal ioctl shims for Darwin
int _system_ioctl_TIOCGWINSZ(int fd, struct winsize *ws) {
return ioctl(fd, TIOCGWINSZ, ws);
}

int _system_ioctl_TIOCSWINSZ(int fd, const struct winsize *ws) {
return ioctl(fd, TIOCSWINSZ, ws);
}

#endif

#if defined(_WIN32)
Expand Down
55 changes: 55 additions & 0 deletions Sources/Samples/PasswordReader.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
This source file is part of the Swift System open source project

Copyright (c) 2024 Apple Inc. and the Swift System project authors
Licensed under Apache License v2.0 with Runtime Library Exception

See https://swift.org/LICENSE.txt for license information
*/

#if !os(Windows)

import ArgumentParser
import SystemPackage

struct PasswordReader: ParsableCommand {
static let configuration = CommandConfiguration(
abstract: "Securely read a password with echo disabled"
)

@Option(name: .shortAndLong, help: "Prompt to display")
var prompt: String = "Password:"

func run() throws {
// Only works if stdin is a terminal
guard let terminal = TerminalDescriptor(.standardInput) else {
complain("Error: stdin is not a terminal")
throw ExitCode.failure
}

// Display prompt (without newline)
print(prompt, terminator: " ")

// Read password with echo disabled
let password = try terminal.withAttributes({ attrs in
attrs.localFlags.remove(.echo)
}) {
readLine() ?? ""
}

// Print newline since echo was disabled
print()

// Display result (in real app, you'd validate/use the password)
if password.isEmpty {
print("No password entered")
} else {
print("Password read successfully (\(password.count) characters)")

// Show it's actually hidden
print("Password was: \(String(repeating: "*", count: password.count))")
}
}
}

#endif
102 changes: 102 additions & 0 deletions Sources/Samples/RawMode.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
This source file is part of the Swift System open source project

Copyright (c) 2024 Apple Inc. and the Swift System project authors
Licensed under Apache License v2.0 with Runtime Library Exception

See https://swift.org/LICENSE.txt for license information
*/

#if !os(Windows)

import ArgumentParser
import SystemPackage

struct RawMode: ParsableCommand {
static let configuration = CommandConfiguration(
abstract: "Demonstrate raw mode terminal input (character-at-a-time)"
)

func run() throws {
guard let terminal = TerminalDescriptor(.standardInput) else {
complain("Error: stdin is not a terminal")
throw ExitCode.failure
}

print("Raw Mode Key Reader")
print("==================")
print()
print("In raw mode, characters are available immediately without")
print("waiting for Enter. Press 'q' to quit.")
print()
print("Try pressing keys, arrow keys, or special keys...")
print()

try terminal.withRawMode {
var buffer = [UInt8](repeating: 0, count: 16)

while true {
// Read a single character (or escape sequence)
let bytesRead = try buffer.withUnsafeMutableBytes { bufferPtr in
try FileDescriptor.standardInput.read(into: bufferPtr)
}

guard bytesRead > 0 else {
continue
}

let bytes = Array(buffer[..<bytesRead])

// Check for 'q' to quit
if bytesRead == 1 && bytes[0] == UInt8(ascii: "q") {
print("\rQuitting...")
break
}

// Display what was read
print("\rRead \(bytesRead) byte(s): ", terminator: "")

// Show bytes in hex
for byte in bytes {
print(String(format: "0x%02X ", byte), terminator: "")
}

// Try to show as character if printable
if bytesRead == 1 {
// Show printable ASCII characters (space through ~)
if bytes[0] >= 0x20 && bytes[0] <= 0x7E {
let char = Character(UnicodeScalar(bytes[0]))
print(" ('\(char)')", terminator: "")
}
}

// Recognize common escape sequences
if bytesRead == 3 && bytes[0] == 0x1B && bytes[1] == 0x5B {
switch bytes[2] {
case 0x41: print(" [UP ARROW]", terminator: "")
case 0x42: print(" [DOWN ARROW]", terminator: "")
case 0x43: print(" [RIGHT ARROW]", terminator: "")
case 0x44: print(" [LEFT ARROW]", terminator: "")
default: break
}
} else if bytesRead == 1 {
switch bytes[0] {
case 0x1B: print(" [ESC]", terminator: "")
case 0x0D: print(" [ENTER]", terminator: "")
case 0x7F: print(" [DELETE]", terminator: "")
case 0x09: print(" [TAB]", terminator: "")
default: break
}
}

// Clear to end of line and move cursor back
print("\u{1B}[K", terminator: "")
}
}

print()
print("Terminal restored to normal mode.")
}
}

#endif
108 changes: 108 additions & 0 deletions Sources/Samples/TerminalSize.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
This source file is part of the Swift System open source project

Copyright (c) 2024 Apple Inc. and the Swift System project authors
Licensed under Apache License v2.0 with Runtime Library Exception

See https://swift.org/LICENSE.txt for license information
*/

#if !os(Windows)

import ArgumentParser
import SystemPackage

struct TerminalSize: ParsableCommand {
static let configuration = CommandConfiguration(
abstract: "Display terminal window dimensions"
)

@Flag(name: .shortAndLong, help: "Show verbose output")
var verbose: Bool = false

@Flag(name: .long, help: "Draw a border using the terminal dimensions")
var border: Bool = false

@Option(name: .long, help: "Try to set terminal rows (height)")
var setRows: UInt16?

@Option(name: .long, help: "Try to set terminal columns (width)")
var setColumns: UInt16?

func run() throws {
guard let terminal = TerminalDescriptor(.standardOutput) else {
complain("Error: stdout is not a terminal")
throw ExitCode.failure
}

// Try to set size if requested
if let rows = setRows, let cols = setColumns {
print("Attempting to set terminal size to \(cols)x\(rows)...")
let newSize = TerminalDescriptor.WindowSize(rows: rows, columns: cols)
do {
try terminal.setWindowSize(newSize)
print("✓ Successfully set terminal size")
} catch {
print("✗ Failed to set terminal size: \(error)")
print("Note: Setting terminal size may not be supported by your terminal emulator")
}
print()
} else if setRows != nil || setColumns != nil {
complain("Error: Both --set-rows and --set-columns must be specified together")
throw ExitCode.failure
}

let size = try terminal.windowSize()

if verbose {
print("Terminal Window Size:")
print(" Rows (height): \(size.rows)")
print(" Columns (width): \(size.columns)")

// Access underlying C struct for pixel dimensions
let xpixel = size.rawValue.ws_xpixel
let ypixel = size.rawValue.ws_ypixel
if xpixel > 0 || ypixel > 0 {
print(" X pixels: \(xpixel)")
print(" Y pixels: \(ypixel)")
} else {
print(" Pixel dimensions: not available")
}
print()
print("This is useful for:")
print(" - Formatting output to fit the terminal")
print(" - Creating full-screen terminal UIs")
print(" - Responsive command-line applications")
} else {
print("\(size.columns)x\(size.rows)")
}

if border {
print()
drawBorder(width: Int(size.columns), height: Int(size.rows))
}
}

private func drawBorder(width: Int, height: Int) {
// Top border
print("┌" + String(repeating: "─", count: width - 2) + "┐")

// Middle rows
for row in 2..<height {
if row == height / 2 {
// Center text
let text = "\(width)x\(height)"
let leftPad = (width - 2 - text.count) / 2
let rightPad = width - 2 - text.count - leftPad
print("│" + String(repeating: " ", count: leftPad) + text + String(repeating: " ", count: rightPad) + "│")
} else {
print("│" + String(repeating: " ", count: width - 2) + "│")
}
}

// Bottom border
print("└" + String(repeating: "─", count: width - 2) + "┘")
}
}

#endif
5 changes: 5 additions & 0 deletions Sources/Samples/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,15 @@ struct SystemSamples: ParsableCommand {
commandName: "system-samples",
abstract: "A collection of little programs exercising some System features.",
subcommands: [
// Socket samples
Resolve.self,
ReverseResolve.self,
Connect.self,
Listen.self,
// Terminal samples
PasswordReader.self,
TerminalSize.self,
RawMode.self,
])
}

Expand Down
15 changes: 15 additions & 0 deletions Sources/System/Internals/CInterop.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,5 +91,20 @@ extension CInterop {
#if SYSTEM_PACKAGE_DARWIN || os(FreeBSD) || os(OpenBSD)
public typealias FileFlags = UInt32
#endif

/// The C `termios` structure.
public typealias Termios = termios

/// The C `tcflag_t` type for terminal mode flags.
public typealias TerminalFlags = tcflag_t

/// The C `cc_t` type for control character values.
public typealias ControlCharacterValue = cc_t

/// The C `speed_t` type for baud rate values.
public typealias SpeedT = speed_t

/// The C `winsize` structure for terminal window dimensions.
public typealias WinSize = winsize
}
#endif
Loading
Loading