I am trying to compile a simple curse project with Xcode.
The program compiles fine with g++ in terminal with the flag -lcurses, and runs fine.
Started of by creating a Command Line Tool with type c++.
imported curses.h into my main.
In the Target"program"Info -> General -> Linked Libraries, libCurses.dylib has been added.
It compiles fine but the terminal window will not open.
In the Debug Console the output is,
Program loaded.
run
[Switching to process 3424]
Error opening terminal: unknown.
Running…
I can go to build folder and just open the program in terminal but is there any way for xcode to open the terminal?
Thanks for any help!
I had the same problem with ncurses debugging in Xcode. Finally I've found a good way for me to manage debugging with Terminal.app that allows to debug ncurses.
As we know, to initialise and use ncurses we need to run our application in terminal. But Xcode doesn't open terminal when we press run button. So, if we request environment variable TERM
from code, we'll get NULL
. This is why application crashes on initscr()
.
So, it is needed to launch Terminal.app, execute our process there and attach a debugger to it. It can be achieved through Scheme setup. I did it in Xcode 11.4. I created a new macOS Command Line Tool project based on Language:
C++
. Also I added libncurses.tbd
dependency in Frameworks and Libraries
.
Go to Product > Scheme > Edit scheme...
, select Run
scheme and Run
action and navigate to Info
tab. You'll see Launch
set to Automatically
. Change it to Wait for the executable to be launched
.
Select Pre-actions
in the Run
scheme and add New Run Script Action
. Change Provide build settings from
from None
to your build target. Add the following code there:
osascript -e 'tell application "Terminal"' -e 'delay 0.5' -e 'activate' -e "do script (\"$TARGET_BUILD_DIR/$PRODUCT_NAME\")" -e 'end tell' &
To optionally close terminal in the end of debugging session select Post-actions
in the Run
scheme and add New Run Script Action
. Add the following code:
osascript -e 'activate application "Terminal"' -e 'delay 0.5' -e 'tell application "System Events"' -e 'tell process "Terminal"' -e 'keystroke "w" using {command down}' -e 'end tell' -e 'end tell'
Actually osascript will always create at least two terminal windows but if you will leave the first one opened it will create and destroy the second one with your session automatically through Pre and Post actions.
You will probably also meet a problem with debugger attaching. I don't know exact reason why Xcode don't want to attach debugger when process executes externally but I found this issue. Also I found stdin
stream in weird state during debugging session beginning as well. So, I wrote a workaround based on pselect
call. I'm asking stdin
for any data until it doesn't return success. I found that after these manipulations debugger will feel OK and stdin
requests will be OK as well. Here is my code example:
#include <stdlib.h>
#include <string.h>
#include <ncurses.h>
#include <sys/select.h>
#include <unistd.h>
#include <errno.h>
bool g_has_terminal = false; // Check this global variable before ncurses calls
bool ensure_debugger_attached_woraround(int timeout_ms)
{
fd_set fd_stdin;
FD_ZERO(&fd_stdin);
FD_SET(STDIN_FILENO, &fd_stdin);
struct timespec timeout = { timeout_ms / 1000, (timeout_ms % 1000) * 1000000 };
do
{
errno = 0;
}
while (pselect(STDIN_FILENO + 1, &fd_stdin, NULL, NULL, &timeout, NULL) < 0 && errno == EINTR);
if (errno != 0)
{
fprintf(stderr, "Unexpected error %d", errno);
return false;
}
return true;
}
int main(int argc, const char *argv[])
{
if (!ensure_debugger_attached_woraround(700))
return 1;
char *term = getenv("TERM");
g_has_terminal = (term != NULL);
if (g_has_terminal)
g_has_terminal = (initscr() != NULL);
// Some ncurses code. Maybe you should terminate if g_has_terminal is not set
if (g_has_terminal)
{
printw("Press any key to exit...");
refresh();
getch();
endwin();
}
return 0;
}
ensure_debugger_attached_woraround
is called with a timeout 700 milliseconds. I tried different values and found 500 ms is the minimal one to not skip pselect
. Maybe this timeout is machine depended I don't know. You can wrap this call by #ifdef ... #endif
or by some other checks, maybe by special command line argument check to exclude a little wait overhead in the release mode.