Instead of trying to detect whether you're using Terminal.app (which could be error-prone if you're e.g. using a different terminal program or running on Linux), you should instead query the terminal to find out whether it supports specific features. This can be done easily with the ncurses
library.
1. Add a CCurses
target to your Swift package
We need to create a system library target so that the Swift Package Manager knows how to find and link against the ncurses library Add the target to your Package.swift
:
let package = Package(
// ...
targets: [
// ...
.systemLibrary(name: "CCurses"),
],
// ...
)
And create a new directory at Sources/CCurses
with the following files:
curses.h
#include <curses.h>
#include <term.h>
module.modulemap
module CCurses [system] {
header "curses.h"
link "curses"
export *
}
2. Check if the terminal supports color output
When you're setting up, query the terminfo database to check for color support:
import CCurses
// ...
var erret: Int32 = 0
if setupterm(nil, 1, &erret) != ERR {
useColor = has_colors()
} else {
useColor = false
}
Based on https://stackoverflow.com/a/7426284.
If you need to check for the existence of another feature, there's almost certainly an ncurses function for it -- just check the man page, search online, or ask here on SO.
The ncurses dylib is available on all platforms, but for some reason the headers only exist on macOS. You shouldn't need it on iOS, watchOS, or tvOS anyway, because those platforms don't have terminals. There are two ways to exclude ncurses from being built on iOS, etc: 1) you can #ifdef
out the headers, or 2) with Swift 5.3 you can declare a conditional target dependency, which is slightly simpler and cleaner.
Approach 1: #ifdef
the headers
Use the following Sources/CCurses/curses.h
file instead:
#ifdef __APPLE__
#include <TargetConditionals.h>
#endif
#if !(defined TARGET_OS_IPHONE || defined TARGET_OS_WATCH || defined TARGET_OS_TV)
#include <curses.h>
#include <term.h>
#endif
Then, whenever you use a function from curses in your Swift code, surround it with a build conditional:
#if os(iOS) || os(watchOS) || os(tvOS)
useColor = false
#else
var erret: Int32 = 0
if setupterm(nil, 1, &erret) != ERR {
useColor = has_colors()
} else {
useColor = false
}
#endif
Approach 2: conditional target dependency (requires Swift 5.3)
No changes are necessary to the CCurses target; you only have to modify your dependency on CCurses
in your Package.swift
:
.target(
name: "MyLib",
dependencies: [
.target(name: "CCurses", condition: .when(platforms: [.macOS, .linux]))
]),
And use a build condition whenever you import CCurses
or use curses functions:
#if canImport(CCurses)
import CCurses
#endif
// ...
#if canImport(CCurses)
var erret: Int32 = 0
if setupterm(nil, 1, &erret) != ERR {
useColor = has_colors()
} else {
useColor = false
}
#else
useColor = false
#endif