Xcode and Curses.h with Error opening terminal

archieoi picture archieoi · Feb 7, 2011 · Viewed 8.6k times · Source

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!

Answer

C0DEF52 picture C0DEF52 · Aug 12, 2015

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. enter image description here

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' &

enter image description here

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'

enter image description here 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.