Repository: dankamongmen/notcurses (language: python)

Issue:
building on macOS fails if `make install` files are not removed before next cmake+make
Per my comments in #1933, in recent days I found myself unable to build notcurses on my macOS laptop, even though it builds successfully in a near identical environment.

I thought again about what could be different between my laptop and macOS builds in GitHub Actions.

The only thing that came to mind is that at one point locally I ran `sudo make install` and never deleted the various notcurses files placed under `/usr/local/`, while in GHA the filesystem is fresh for every build.

I tried deleting the installed files, then tried another round of `cmake` and `make` in my clone of notcurses (current `master`) and it works!

So it seems the files installed under `/usr/local/` by `sudo make install` can interfere with later attempts to build notcurses.

Is this a common gotcha that I didn't know about? A peculiarity of using these tools on macOS? Or does it seem like a bug of some kind?
add libgpm support
@klamonte asked the other day in #1369 whether we supported mouse input in the console through GPM. i feel for sure i've seen this work before, but attempting to demonstrate it with tip of master on my arch laptop failed just now. look into this. we ought be able to work with gpm just fine AFAIK.
building on macOS fails if `make install` files are not removed before next cmake+make
Per my comments in #1933, in recent days I found myself unable to build notcurses on my macOS laptop, even though it builds successfully in a near identical environment.

I thought again about what could be different between my laptop and macOS builds in GitHub Actions.

The only thing that came to mind is that at one point locally I ran `sudo make install` and never deleted the various notcurses files placed under `/usr/local/`, while in GHA the filesystem is fresh for every build.

I tried deleting the installed files, then tried another round of `cmake` and `make` in my clone of notcurses (current `master`) and it works!

So it seems the files installed under `/usr/local/` by `sudo make install` can interfere with later attempts to build notcurses.

Is this a common gotcha that I didn't know about? A peculiarity of using these tools on macOS? Or does it seem like a bug of some kind?


Discussion / hints:
I realize that once notcurses is available via `brew`, few macOS users would have the occasion to run `sudo make install` after building it themselves, but still seems good to understand the nature of the problem.
> Is this a common gotcha that I didn't know about?

it is the bane of one's existence, and the worst possible way to waste several hours. i count on about four a year.

>  A peculiarity of using these tools on macOS? 

a peculiarity of preferring `/usr/local/lib` to `/usr/lib`, which is usually what you want, hence installing them there.

one strategy is to do all temporary development involving writing to `/usr/local` in an OS-level or even system-level VM, but i find that even more annoying than the alternative.

sorry for the timewaste! it happens to the best of us =[. this has never happened to you before?

![You-Are-Fucked_Up](https://user-images.githubusercontent.com/143473/126907762-1a7cf8a0-f437-4ffe-aeac-02f12ac5aedf.jpg)

oh i see what you mean they were installed by the brew system, and you were upgrading? erp, yeah, that seems.....counterproductive if brew doesn't guard against that.
> this has never happened to you before?

Not that I remember, but at least I know to watch out for it now. Thanks!

> oh i see what you mean they were installed by the brew system, and you were upgrading? erp, yeah, that seems.....counterproductive if brew doesn't guard against that.

No, installed with `sudo make install`.

But after installing with `brew`, it will be interesting to check if that results in the same kind of interference when building locally in my clone of the repo. Will leave this issue open for now and report back.

I haven't tried working with the formula you posted in #1962, hope to experiment with it this evening.
been thinking about this and i think we ought be cool? 

btw that was kind of an obnoxious image to post; i hope i didn't offend you at all. just making sure =]
No offense taken. 😄 

Once the package is available via `brew` (or if that homebrew-core PR is up I can try the formula locally before it's published), I was going to install it and then check if there's interference when running `make` in a clone of the repo.
the homebrew PR is up at https://github.com/Homebrew/homebrew-core/pull/82112
After `brew install notcurses`, in my local clone of the notcurses repo (up-to-date with `master`) I did:
```
$ git clean -dfx && mkdir build && cd build && cmake .. -DCMAKE_BUILD_TYPE=Release && make -j16
```

It worked correctly.
https://bbs.archlinux.org/viewtopic.php?id=225660 apparently there's a libgpm?
so there is!
ok, yep, that's what we'd need do. NCURSES programs can get gpm events if NCURSES has been linked against libgpm. we're not an NCURSES program. we thus need link against libgpm ourselves.

```c
/* the following is a heavy thing ... */
extern int gpm_consolefd; /* liblow.c */
```

ahhh this is some solid 90s shit right here. this header has never heard of AIDS.
alright, so it looks like gpm support would require:

* setting up a (linux? linux-and-bsd?)-only optional dep on libgpm in CMake
* documenting the new option
* updating packaging for a ~dozen systems to mention the new optional dep
* code to (when libgpm is present) attempt to connect to gpm *when the mouse is enabled*
* code to read from libgpm, which must be interleaved with regular reading
* code to disconnect when the mouse is disabled
I realize that once notcurses is available via `brew`, few macOS users would have the occasion to run `sudo make install` after building it themselves, but still seems good to understand the nature of the problem.
> Is this a common gotcha that I didn't know about?

it is the bane of one's existence, and the worst possible way to waste several hours. i count on about four a year.

>  A peculiarity of using these tools on macOS? 

a peculiarity of preferring `/usr/local/lib` to `/usr/lib`, which is usually what you want, hence installing them there.

one strategy is to do all temporary development involving writing to `/usr/local` in an OS-level or even system-level VM, but i find that even more annoying than the alternative.

sorry for the timewaste! it happens to the best of us =[. this has never happened to you before?

![You-Are-Fucked_Up](https://user-images.githubusercontent.com/143473/126907762-1a7cf8a0-f437-4ffe-aeac-02f12ac5aedf.jpg)

oh i see what you mean they were installed by the brew system, and you were upgrading? erp, yeah, that seems.....counterproductive if brew doesn't guard against that.
> this has never happened to you before?

Not that I remember, but at least I know to watch out for it now. Thanks!

> oh i see what you mean they were installed by the brew system, and you were upgrading? erp, yeah, that seems.....counterproductive if brew doesn't guard against that.

No, installed with `sudo make install`.

But after installing with `brew`, it will be interesting to check if that results in the same kind of interference when building locally in my clone of the repo. Will leave this issue open for now and report back.

I haven't tried working with the formula you posted in #1962, hope to experiment with it this evening.
been thinking about this and i think we ought be cool? 

btw that was kind of an obnoxious image to post; i hope i didn't offend you at all. just making sure =]
No offense taken. 😄 

Once the package is available via `brew` (or if that homebrew-core PR is up I can try the formula locally before it's published), I was going to install it and then check if there's interference when running `make` in a clone of the repo.
the homebrew PR is up at https://github.com/Homebrew/homebrew-core/pull/82112
After `brew install notcurses`, in my local clone of the notcurses repo (up-to-date with `master`) I did:
```
$ git clean -dfx && mkdir build && cd build && cmake .. -DCMAKE_BUILD_TYPE=Release && make -j16
```

It worked correctly.

Fix patch:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index e3bd6dd6c5..6b63ea09cf 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -24,6 +24,7 @@ cmake_dependent_option(
   "${BUILD_TESTING}" OFF
 )
 option(USE_DOXYGEN "Build HTML cross reference with doxygen" OFF)
+option(USE_GPM "Enable libgpm console mouse support" OFF)
 option(USE_PANDOC "Build man pages and HTML reference with pandoc" ON)
 option(USE_POC "Build small, uninstalled proof-of-concept binaries" ON)
 option(USE_QRCODEGEN "Enable libqrcodegen QR code support" OFF)
@@ -145,6 +146,16 @@ endif()
 find_library(unistring unistring REQUIRED)
 set_property(GLOBAL APPEND PROPERTY PACKAGES_FOUND libunistring)
 set_package_properties(libunistring PROPERTIES TYPE REQUIRED)
+if(${USE_GPM}) # no pkgconfig from gpm
+unset(HAVE_GPM_H CACHE)
+check_include_file("gpm.h" HAVE_GPM_H)
+if(NOT "${HAVE_GPM_H}")
+  message(FATAL_ERROR "Couldn't find gpm.h from libgpm")
+endif()
+find_library(gpm gpm REQUIRED)
+set_property(GLOBAL APPEND PROPERTY PACKAGES_FOUND libgpm)
+set_package_properties(libgpm PROPERTIES TYPE REQUIRED)
+endif()
 unset(HAVE_QRCODEGEN_H CACHE)
 if("${USE_QRCODEGEN}")
 check_include_file("qrcodegen/qrcodegen.h" HAVE_QRCODEGEN_H)
@@ -191,6 +202,7 @@ set_target_properties(notcurses-core-static PROPERTIES
   OUTPUT_NAME notcurses-core
 )
 target_include_directories(notcurses-core
+  BEFORE
   PRIVATE
     include
     src
@@ -200,6 +212,7 @@ target_include_directories(notcurses-core
     "${ZLIB_INCLUDE_DIRS}"
 )
 target_include_directories(notcurses-core-static
+  BEFORE
   PRIVATE
     include
     src
@@ -214,6 +227,7 @@ target_link_libraries(notcurses-core
     "${TERMINFO_LIBRARIES}"
     "${LIBM}"
     "${unistring}"
+    "${gpm}"
   PUBLIC
     Threads::Threads
     "${LIBRT_LIBRARIES}"
@@ -224,6 +238,7 @@ target_link_libraries(notcurses-core-static
     "${TERMINFO_STATIC_LIBRARIES}"
     "${LIBM}"
     "${unistring}"
+    "${gpm}"
   PUBLIC
     Threads::Threads
     "${LIBRT_LIBRARIES}"
@@ -261,19 +276,19 @@ set_target_properties(notcurses-static PROPERTIES
   OUTPUT_NAME notcurses
 )
 target_include_directories(notcurses
+  BEFORE
   PRIVATE
     include
     src
-    src/lib
     "${CMAKE_REQUIRED_INCLUDES}"
     "${PROJECT_BINARY_DIR}/include"
     "${TERMINFO_INCLUDE_DIRS}"
 )
 target_include_directories(notcurses-static
+  BEFORE
   PRIVATE
     include
     src
-    src/lib
     "${CMAKE_REQUIRED_INCLUDES}"
     "${PROJECT_BINARY_DIR}/include"
     "${TERMINFO_INCLUDE_DIRS}"
@@ -399,16 +414,19 @@ set_target_properties(
   OUTPUT_NAME "notcurses++")
 
 set(NCPP_INCLUDE_DIRS
-  include
+    "include"
+    "src"
     "${PROJECT_BINARY_DIR}/include"
     "${TERMINFO_INCLUDE_DIRS}"
   )
 
 target_include_directories(notcurses++
+  BEFORE
   PRIVATE ${NCPP_INCLUDE_DIRS}
   )
 
 target_include_directories(notcurses++-static
+  BEFORE
   PRIVATE ${NCPP_INCLUDE_DIRS}
   )
 
@@ -541,7 +559,6 @@ target_compile_definitions(notcurses-info
 target_include_directories(notcurses-info
   PRIVATE
     src
-    src/lib
     include
     "${CMAKE_REQUIRED_INCLUDES}"
     "${PROJECT_BINARY_DIR}/include"
@@ -650,12 +667,12 @@ if(${USE_DOCTEST})
 file(GLOB TESTSRCS CONFIGURE_DEPENDS src/tests/*.cpp)
 add_executable(notcurses-tester ${TESTSRCS})
 target_include_directories(notcurses-tester
+  BEFORE
   PRIVATE
     include
     src
     "${CMAKE_REQUIRED_INCLUDES}"
     "${PROJECT_BINARY_DIR}/include"
-    src/lib
     "${TERMINFO_INCLUDE_DIRS}"
 )
 target_link_libraries(notcurses-tester
diff --git a/INSTALL.md b/INSTALL.md
index 8807bb48be..15dcb50e9c 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -12,7 +12,7 @@ prepackaged on many distributions. Otherwise, acquire the current source via
 
 Install build dependencies:
 
-`apt-get install build-essential cmake doctest-dev zlib1g-dev libavformat-dev libavutil-dev libncurses-dev libreadline-dev libqrcodegen-dev libswscale-dev libunistring-dev pandoc pkg-config`
+`apt-get install build-essential cmake doctest-dev zlib1g-dev libavformat-dev libavutil-dev libgpm-dev libncurses-dev libreadline-dev libqrcodegen-dev libswscale-dev libunistring-dev pandoc pkg-config`
 
 If you only intend to build core Notcurses (without multimedia support), you
 can omit `libavformat-dev`, `libavutil-dev`, and `libswscale-dev` from this
@@ -30,7 +30,7 @@ If you want to build the Rust wrappers, you'll also need:
 
 Install build dependencies:
 
-`dnf install cmake doctest-devel zlib-devel ncurses-devel readline-devel libqrcodegen-devel libunistring-devel OpenImageIO-devel pandoc`
+`dnf install cmake doctest-devel zlib-devel ncurses-devel gpm-devel readline-devel libqrcodegen-devel libunistring-devel OpenImageIO-devel pandoc`
 
 If you only intend to build core Notcurses (without multimedia support), you
 can omit `OpenImageIO-devel`. If you're building outside Fedora Core (e.g. with
@@ -109,6 +109,7 @@ but must be `Debug` for use of `USE_COVERAGE`.
 * `USE_COVERAGE`: build coverage support (for developers, requires use of Clang)
 * `USE_DOCTEST`: build `notcurses-tester` with Doctest, requires `BUILD_TESTING`
 * `USE_DOXYGEN`: build interlinked HTML documentation with Doxygen
+* `USE_GPM`: build GPM console mouse support via libgpm
 * `USE_MULTIMEDIA`: `ffmpeg` for FFmpeg, `oiio` for OpenImageIO, `none` for none
 * `USE_PANDOC`: build man pages with pandoc
 * `USE_POC`: build small, uninstalled proof-of-concept binaries
diff --git a/README.md b/README.md
index c9d91fe2df..558873c3ab 100644
--- a/README.md
+++ b/README.md
@@ -110,6 +110,7 @@ may well be possible to use still older versions. Let me know of any successes!
 * (OPTIONAL) (OpenImageIO, testing, C++ bindings): A C++17 compiler
 * (build+runtime) From [NCURSES](https://invisible-island.net/ncurses/announce.html): terminfo 6.1+
 * (build+runtime) GNU [libunistring](https://www.gnu.org/software/libunistring/) 0.9.10+
+* (OPTIONAL) (build+runtime) [libgpm](https://www.nico.schottelius.org/software/gpm/) 1.20+
 * (OPTIONAL) (build+runtime) GNU [Readline](https://www.gnu.org/software/readline/) 8.0+
 * (OPTIONAL) (build+runtime) From QR-Code-generator: [libqrcodegen](https://github.com/nayuki/QR-Code-generator) 1.5.0+
 * (OPTIONAL) (build+runtime) From [FFmpeg](https://www.ffmpeg.org/): libswscale 5.0+, libavformat 57.0+, libavutil 56.0+
diff --git a/src/info/main.c b/src/info/main.c
index dab87e4bd2..eb204c486d 100644
--- a/src/info/main.c
+++ b/src/info/main.c
@@ -2,7 +2,7 @@
 #include <unistd.h>
 #include <stdlib.h>
 #include <notcurses/notcurses.h>
-#include "internal.h" // internal headers
+#include "lib/internal.h" // internal headers
 
 static inline wchar_t
 capboolbool(unsigned utf8, bool cap){
diff --git a/src/input/input.cpp b/src/input/input.cpp
index a8144e2458..01de2b1d4c 100644
--- a/src/input/input.cpp
+++ b/src/input/input.cpp
@@ -338,9 +338,7 @@ int main(void){
   notcurses_options nopts{};
   nopts.flags = NCOPTION_INHIBIT_SETLOCALE;
   NotCurses nc(nopts);
-  if(!nc.mouse_enable()){
-    return EXIT_FAILURE;
-  }
+  nc.mouse_enable(); // might fail if no mouse is available
   int ret = input_demo(&nc);
   if(!nc.stop() || ret){
     return EXIT_FAILURE;
diff --git a/src/lib/gpm.c b/src/lib/gpm.c
new file mode 100644
index 0000000000..f80dbe0382
--- /dev/null
+++ b/src/lib/gpm.c
@@ -0,0 +1,52 @@
+#include "internal.h"
+#ifdef USE_GPM
+#undef buttons // defined by both term.h and gpm.h, ugh
+#include <lib/gpm.h>
+#include <gpm.h>
+
+static Gpm_Connect gpmconn;    // gpm server handle
+
+int gpm_connect(tinfo* ti){
+  (void)ti;
+  gpm_zerobased = 1;
+  gpmconn.eventMask = ~0;
+  gpmconn.defaultMask = 0;
+  gpmconn.minMod = 0;
+  gpmconn.maxMod = ~0;
+  if(Gpm_Open(&gpmconn, 0) == -1){
+    logerror("couldn't connect to gpm");
+    return -1;
+  }
+  loginfo("connected to gpm on %d\n", gpm_fd);
+  return gpm_fd;
+}
+
+int gpm_read(tinfo* ti, ncinput* ni){
+  (void)ti;
+  (void)ni;
+  return -1;
+}
+
+int gpm_close(tinfo* ti){
+  (void)ti;
+  Gpm_Close();
+  memset(&gpmconn, 0, sizeof(gpmconn));
+  return 0;
+}
+#else
+int gpm_connect(tinfo* ti){
+  (void)ti;
+  return -1;
+}
+
+int gpm_read(tinfo* ti, ncinput* ni){
+  (void)ti;
+  (void)ni;
+  return -1;
+}
+
+int gpm_close(tinfo* ti){
+  (void)ti;
+  return -1;
+}
+#endif
diff --git a/src/lib/gpm.h b/src/lib/gpm.h
new file mode 100644
index 0000000000..175cef7127
--- /dev/null
+++ b/src/lib/gpm.h
@@ -0,0 +1,30 @@
+#ifndef NOTCURSES_GPM
+#define NOTCURSES_GPM
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// internal header, not installed
+
+struct tinfo;
+struct ncinput;
+
+// GPM ("General Purpose Mouse") provides an interface to mice in the Linux
+// and FreeBSD consoles. The gpm server must be running; we do not attempt to
+// start it. We must have been built with -DUSE_GPM.
+
+// Returns the poll()able file descriptor associated with gpm, or -1 on failure.
+int gpm_connect(struct tinfo* ti);
+
+// Read from the gpm connection, which ought have been poll()ed. Translates
+// the libgpm input to an ncinput.
+int gpm_read(struct tinfo* ti, struct ncinput* ni);
+
+int gpm_close(struct tinfo* ti);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/lib/internal.h b/src/lib/internal.h
index be17065c15..5d19227ffe 100644
--- a/src/lib/internal.h
+++ b/src/lib/internal.h
@@ -32,10 +32,11 @@ extern "C" {
 #ifndef __MINGW64__
 #include <langinfo.h>
 #endif
-#include "termdesc.h"
-#include "egcpool.h"
-#include "sprite.h"
-#include "fbuf.h"
+#include "lib/termdesc.h"
+#include "lib/egcpool.h"
+#include "lib/sprite.h"
+#include "lib/fbuf.h"
+#include "lib/gpm.h"
 
 #define API __attribute__((visibility("default")))
 #define ALLOC __attribute__((malloc)) __attribute__((warn_unused_result))
@@ -1142,7 +1143,10 @@ coerce_styles(fbuf* f, const tinfo* ti, uint16_t* curstyle,
 #define SET_SGR_MODE_MOUSE    "1006"
 
 static inline int
-mouse_enable(FILE* out){
+mouse_enable(tinfo* ti, FILE* out){
+  if(ti->qterm == TERMINAL_LINUX){
+    return gpm_connect(ti);
+  }
 // Sets the shift-escape option, allowing shift+mouse to override the standard
 // mouse protocol (mainly so copy-and-paste can still be performed).
 #define XTSHIFTESCAPE "\x1b[>1s"
@@ -1153,7 +1157,10 @@ mouse_enable(FILE* out){
 }
 
 static inline int
-mouse_disable(fbuf* f){
+mouse_disable(tinfo* ti, fbuf* f){
+  if(ti->qterm == TERMINAL_LINUX){
+    return gpm_close(ti);
+  }
   return fbuf_emit(f, "\x1b[?" SET_BTN_EVENT_MOUSE ";"
                    /*SET_FOCUS_EVENT_MOUSE ";" */SET_SGR_MODE_MOUSE "l");
 }
diff --git a/src/lib/notcurses.c b/src/lib/notcurses.c
index 7f31f074ef..a8b431741f 100644
--- a/src/lib/notcurses.c
+++ b/src/lib/notcurses.c
@@ -90,7 +90,7 @@ notcurses_stop_minimal(void* vnc){
   if(nc->tcache.pixel_shutdown){
     ret |= nc->tcache.pixel_shutdown(f);
   }
-  ret |= mouse_disable(f);
+  ret |= mouse_disable(&nc->tcache, f);
   ret |= reset_term_attributes(&nc->tcache, f);
   if(nc->tcache.ttyfd >= 0){
     if((esc = get_escape(&nc->tcache, ESCAPE_RMCUP))){
@@ -2262,7 +2262,7 @@ ncplane* ncplane_above(ncplane* n){
 }
 
 int notcurses_mouse_enable(notcurses* n){
-  if(mouse_enable(n->ttyfp)){
+  if(mouse_enable(&n->tcache, n->ttyfp)){
     return -1;
   }
   return 0;
@@ -2275,7 +2275,7 @@ int notcurses_mouse_disable(notcurses* n){
   if(fbuf_init_small(&f)){
     return -1;
   }
-  if(mouse_disable(&f)){
+  if(mouse_disable(&n->tcache, &f)){
     fbuf_free(&f);
     return -1;
   }
diff --git a/src/lib/termdesc.c b/src/lib/termdesc.c
index 0b1b7e1976..bdcbbf711c 100644
--- a/src/lib/termdesc.c
+++ b/src/lib/termdesc.c
@@ -664,7 +664,7 @@ macos_early_matches(void){
 int interrogate_terminfo(tinfo* ti, const char* termtype, FILE* out, unsigned utf8,
                          unsigned noaltscreen, unsigned nocbreak, unsigned nonewfonts,
                          int* cursor_y, int* cursor_x, ncsharedstats* stats){
-  queried_terminals_e qterm = TERMINAL_UNKNOWN;
+  ti->qterm = TERMINAL_UNKNOWN;
   memset(ti, 0, sizeof(*ti));
   // we don't need a controlling tty for everything we do; allow a failure here
   ti->ttyfd = get_tty_fd(out);
@@ -672,19 +672,19 @@ int interrogate_terminfo(tinfo* ti, const char* termtype, FILE* out, unsigned ut
   size_t tableused = 0;
   const char* tname = NULL;
 #ifdef __APPLE__
-  qterm = macos_early_matches();
+  ti->qterm = macos_early_matches();
 #elif defined(__MINGW64__)
   if(prepare_windows_terminal(ti, &tablelen, &tableused)){
     return -1;
   }
-  qterm = TERMINAL_MSTERMINAL;
+  ti->qterm = TERMINAL_MSTERMINAL;
   (void)termtype;
 #elif defined(__linux__)
   ti->linux_fb_fd = -1;
   ti->linux_fbuffer = MAP_FAILED;
   // we might or might not program quadrants into the console font
   if(is_linux_console(ti->ttyfd)){
-    qterm = TERMINAL_LINUX;
+    ti->qterm = TERMINAL_LINUX;
   }
 #endif
 #ifndef __MINGW64__
@@ -700,7 +700,7 @@ int interrogate_terminfo(tinfo* ti, const char* termtype, FILE* out, unsigned ut
     }
     // if we already know our terminal (e.g. on the linux console), there's no
     // need to send the identification queries. the controls are sufficient.
-    bool minimal = (qterm != TERMINAL_UNKNOWN);
+    bool minimal = (ti->qterm != TERMINAL_UNKNOWN);
     if(send_initial_queries(ti->ttyfd, minimal)){
       return -1;
     }
@@ -840,7 +840,7 @@ int interrogate_terminfo(tinfo* ti, const char* termtype, FILE* out, unsigned ut
   }
   *cursor_x = *cursor_y = -1;
   unsigned kittygraphs = 0;
-  if(ncinputlayer_init(ti, stdin, &qterm, &appsync_advertised,
+  if(ncinputlayer_init(ti, stdin, &ti->qterm, &appsync_advertised,
                        cursor_y, cursor_x, stats, &kittygraphs)){
     goto err;
   }
@@ -863,7 +863,7 @@ int interrogate_terminfo(tinfo* ti, const char* termtype, FILE* out, unsigned ut
     }
   }
   bool invertsixel = false;
-  if(apply_term_heuristics(ti, tname, qterm, &tablelen, &tableused,
+  if(apply_term_heuristics(ti, tname, ti->qterm, &tablelen, &tableused,
                            &invertsixel, nonewfonts)){
     ncinputlayer_stop(&ti->input);
     goto err;
diff --git a/src/lib/termdesc.h b/src/lib/termdesc.h
index 0f9afca2f9..dde4bc9781 100644
--- a/src/lib/termdesc.h
+++ b/src/lib/termdesc.h
@@ -7,6 +7,8 @@ extern "C" {
 
 // internal header, not installed
 
+#include "version.h"
+#include "builddef.h"
 #include "input.h"
 #include <stdint.h>
 #include <pthread.h>
@@ -185,6 +187,7 @@ typedef struct tinfo {
   int sprixel_scale_height; // sprixel must be a multiple of this many rows
   const char* termname;     // terminal name from environment variables/init
   char* termversion;        // terminal version (freeform) from query responses
+  queried_terminals_e qterm;// detected terminal class
 #ifndef __MINGW64__
   struct termios tpreserved;// terminal state upon entry
 #endif
diff --git a/src/media/ffmpeg.c b/src/media/ffmpeg.c
index a23638f564..a651156b6d 100644
--- a/src/media/ffmpeg.c
+++ b/src/media/ffmpeg.c
@@ -11,8 +11,8 @@
 #include <libswscale/version.h>
 #include <libavformat/version.h>
 #include <libavformat/avformat.h>
-#include "visual-details.h"
-#include "internal.h"
+#include "lib/visual-details.h"
+#include "lib/internal.h"
 
 struct AVFormatContext;
 struct AVCodecContext;
diff --git a/src/media/none.c b/src/media/none.c
index beecfd2f46..2d706a3562 100644
--- a/src/media/none.c
+++ b/src/media/none.c
@@ -1,7 +1,7 @@
 #include "builddef.h"
 #ifndef USE_OIIO
 #ifndef USE_FFMPEG
-#include "internal.h"
+#include "lib/internal.h"
 
 static void
 printbanner(fbuf* f){
diff --git a/src/media/oiio-indep.c b/src/media/oiio-indep.c
index 6d7613c4ab..d7d92fd895 100644
--- a/src/media/oiio-indep.c
+++ b/src/media/oiio-indep.c
@@ -1,7 +1,7 @@
 #include "builddef.h"
 #ifdef USE_OIIO
-#include "visual-details.h"
-#include "internal.h"
+#include "lib/visual-details.h"
+#include "lib/internal.h"
 #include "oiio.h"
 
 int oiio_blit_dispatch(struct ncplane* nc, const struct blitset* bset,
diff --git a/src/media/oiio.cpp b/src/media/oiio.cpp
index 1662d0efeb..f0780b02c2 100644
--- a/src/media/oiio.cpp
+++ b/src/media/oiio.cpp
@@ -5,7 +5,7 @@
 #include <OpenImageIO/imageio.h>
 #include <OpenImageIO/imagebuf.h>
 #include <OpenImageIO/imagebufalgo.h>
-#include "visual-details.h"
+#include "lib/visual-details.h"
 #include "oiio.h"
 
 typedef struct ncvisual_details {
diff --git a/src/media/oiio.h b/src/media/oiio.h
index 0d5d13e039..5ff31419eb 100644
--- a/src/media/oiio.h
+++ b/src/media/oiio.h
@@ -5,7 +5,7 @@
 extern "C" {
 #endif
 
-#include "internal.h"
+#include "lib/internal.h"
 
 int oiio_decode(ncvisual* nc);
 struct ncvisual_details* oiio_details_init(void);
diff --git a/src/media/shim.c b/src/media/shim.c
index 0b615f7e9f..8b17c9dab0 100644
--- a/src/media/shim.c
+++ b/src/media/shim.c
@@ -1,5 +1,5 @@
 #include "notcurses/direct.h"
-#include "internal.h"
+#include "lib/internal.h"
 
 extern const ncvisual_implementation local_visual_implementation;
 
diff --git a/src/poc/multiselect.c b/src/poc/multiselect.c
index 17fb48052a..131f2322f7 100644
--- a/src/poc/multiselect.c
+++ b/src/poc/multiselect.c
@@ -69,10 +69,7 @@ int main(void){
   if(nc == NULL){
     return EXIT_FAILURE;
   }
-  if(notcurses_mouse_enable(nc)){
-    notcurses_stop(nc);
-    return EXIT_FAILURE;
-  }
+  notcurses_mouse_enable(nc);
   ncmultiselector_options sopts;
   memset(&sopts, 0, sizeof(sopts));
   sopts.maxdisplay = 10;
diff --git a/src/poc/selector.c b/src/poc/selector.c
index 2b9fd0e940..a24f334fdb 100644
--- a/src/poc/selector.c
+++ b/src/poc/selector.c
@@ -59,10 +59,7 @@ int main(void){
   if(nc == NULL){
     return EXIT_FAILURE;
   }
-  if(notcurses_mouse_enable(nc)){
-    notcurses_stop(nc);
-    return EXIT_FAILURE;
-  }
+  notcurses_mouse_enable(nc);
   ncselector_options sopts;
   memset(&sopts, 0, sizeof(sopts));
   sopts.maxdisplay = 4;
diff --git a/tools/builddef.h.in b/tools/builddef.h.in
index 8beabbdd07..967e470d05 100644
--- a/tools/builddef.h.in
+++ b/tools/builddef.h.in
@@ -1,5 +1,6 @@
 // Populated by CMake; not installed
 #cmakedefine DFSG_BUILD
+#cmakedefine USE_GPM
 #cmakedefine USE_QRCODEGEN
 #cmakedefine USE_READLINE
 // exclusive with USE_OIIO


Test patch:
diff --git a/src/tests/bitmap.cpp b/src/tests/bitmap.cpp
index 206a3399a5..e0651d2d80 100644
--- a/src/tests/bitmap.cpp
+++ b/src/tests/bitmap.cpp
@@ -1,5 +1,5 @@
 #include "main.h"
-#include "visual-details.h"
+#include "lib/visual-details.h"
 #include <vector>
 
 TEST_CASE("Bitmaps") {
diff --git a/src/tests/cell.cpp b/src/tests/cell.cpp
index 0752e318c2..f470585112 100644
--- a/src/tests/cell.cpp
+++ b/src/tests/cell.cpp
@@ -1,5 +1,5 @@
 #include "main.h"
-#include "egcpool.h"
+#include "lib/egcpool.h"
 
 TEST_CASE("Cell") {
   auto nc_ = testing_notcurses();
diff --git a/src/tests/egcpool.cpp b/src/tests/egcpool.cpp
index b460b0f09a..c8ba03ed73 100644
--- a/src/tests/egcpool.cpp
+++ b/src/tests/egcpool.cpp
@@ -1,6 +1,6 @@
 #include <vector>
 #include "main.h"
-#include "egcpool.h"
+#include "lib/egcpool.h"
 
 TEST_CASE("EGCpool") {
   egcpool pool_{};
diff --git a/src/tests/fade.cpp b/src/tests/fade.cpp
index d5dc397d81..f46f58dd71 100644
--- a/src/tests/fade.cpp
+++ b/src/tests/fade.cpp
@@ -1,7 +1,6 @@
 #include "main.h"
 #include <cstdlib>
 #include <iostream>
-#include "internal.h"
 
 auto pulser(struct notcurses* nc, struct ncplane* ncp __attribute__ ((unused)),
             const struct timespec* ts __attribute__ ((unused)), void* curry) -> int {
diff --git a/src/tests/fbuf.cpp b/src/tests/fbuf.cpp
index 42e8389ca7..510897e7de 100644
--- a/src/tests/fbuf.cpp
+++ b/src/tests/fbuf.cpp
@@ -1,5 +1,5 @@
 #include "main.h"
-#include "fbuf.h"
+#include "lib/fbuf.h"
 
 TEST_CASE("Fbuf") {
   auto nc_ = testing_notcurses();
diff --git a/src/tests/fds.cpp b/src/tests/fds.cpp
index d985300dba..dc11cc66e3 100644
--- a/src/tests/fds.cpp
+++ b/src/tests/fds.cpp
@@ -4,7 +4,6 @@
 #include <cstring>
 #include <fcntl.h>
 #include <unistd.h>
-#include "internal.h"
 #include <condition_variable>
 
 static pthread_cond_t cond;
diff --git a/src/tests/geom.cpp b/src/tests/geom.cpp
index 89b6defae6..b35b3f3609 100644
--- a/src/tests/geom.cpp
+++ b/src/tests/geom.cpp
@@ -1,5 +1,4 @@
 #include "main.h"
-#include "internal.h"
 
 TEST_CASE("Geometry") {
   auto nc_ = testing_notcurses();
diff --git a/src/tests/input.cpp b/src/tests/input.cpp
deleted file mode 100644
index d4b7ec9b29..0000000000
--- a/src/tests/input.cpp
+++ /dev/null
@@ -1,13 +0,0 @@
-#include "main.h"
-
-TEST_CASE("Input") {
-  auto nc_ = testing_notcurses();
-  if(!nc_){
-    return;
-  }
-
-  REQUIRE(0 == notcurses_mouse_enable(nc_));
-  CHECK(0 == notcurses_mouse_disable(nc_));
-
-  CHECK(0 == notcurses_stop(nc_));
-}
diff --git a/src/tests/libunistring.cpp b/src/tests/libunistring.cpp
index a4bdf3f422..0c557ee838 100644
--- a/src/tests/libunistring.cpp
+++ b/src/tests/libunistring.cpp
@@ -1,5 +1,4 @@
 #include "main.h"
-#include "internal.h"
 
 // some simple tests to ensure the libunistring we've compiled/linked against
 // behaves as expected.
diff --git a/src/tests/main.h b/src/tests/main.h
index f5418e493b..3517c577fc 100644
--- a/src/tests/main.h
+++ b/src/tests/main.h
@@ -9,7 +9,7 @@
 #include <notcurses/notcurses.h>
 #include <ncpp/NotCurses.hh>
 #include <ncpp/_exceptions.hh>
-#include "internal.h"
+#include "lib/internal.h"
 
 struct free_deleter{
   template <typename T>
diff --git a/src/tests/media.cpp b/src/tests/media.cpp
index 9821ea17ed..cff8bb3e3d 100644
--- a/src/tests/media.cpp
+++ b/src/tests/media.cpp
@@ -1,5 +1,5 @@
 #include "main.h"
-#include "visual-details.h"
+#include "lib/visual-details.h"
 #include <vector>
 #include <cmath>
 
diff --git a/src/tests/notcurses.cpp b/src/tests/notcurses.cpp
index 1688e951a3..d1c857ea19 100644
--- a/src/tests/notcurses.cpp
+++ b/src/tests/notcurses.cpp
@@ -2,7 +2,6 @@
 #include <cstdlib>
 #include <iostream>
 #include "main.h"
-#include "internal.h"
 
 TEST_CASE("NotcursesBase") {
   auto nc_ = testing_notcurses();
diff --git a/src/tests/plane.cpp b/src/tests/plane.cpp
index af23488b40..be52be002b 100644
--- a/src/tests/plane.cpp
+++ b/src/tests/plane.cpp
@@ -1,6 +1,5 @@
 #include <cstdlib>
 #include "main.h"
-#include "internal.h"
 
 void BoxPermutationsRounded(struct notcurses* nc, struct ncplane* n, unsigned edges) {
   int dimx, dimy;
diff --git a/src/tests/scrolling.cpp b/src/tests/scrolling.cpp
index 7e5e9dfc2f..92073707a3 100644
--- a/src/tests/scrolling.cpp
+++ b/src/tests/scrolling.cpp
@@ -1,7 +1,6 @@
 #include "main.h"
 #include <array>
 #include <cstdlib>
-#include "internal.h"
 
 TEST_CASE("Scrolling") {
   auto nc_ = testing_notcurses();
diff --git a/src/tests/sixel.cpp b/src/tests/sixel.cpp
index 625bff3387..500ec76b16 100644
--- a/src/tests/sixel.cpp
+++ b/src/tests/sixel.cpp
@@ -1,5 +1,5 @@
 #include "main.h"
-#include "visual-details.h"
+#include "lib/visual-details.h"
 #include <vector>
 #include <iostream>
 
diff --git a/src/tests/textlayout.cpp b/src/tests/textlayout.cpp
index d6cab1bf34..24a43df78c 100644
--- a/src/tests/textlayout.cpp
+++ b/src/tests/textlayout.cpp
@@ -1,5 +1,4 @@
 #include "main.h"
-#include "internal.h"
 
 TEST_CASE("TextLayout") {
   auto nc_ = testing_notcurses();
diff --git a/src/tests/visual.cpp b/src/tests/visual.cpp
index 6599d20234..516e180efd 100644
--- a/src/tests/visual.cpp
+++ b/src/tests/visual.cpp
@@ -1,5 +1,5 @@
 #include "main.h"
-#include "visual-details.h"
+#include "lib/visual-details.h"
 #include <vector>
 #include <cmath>
 
diff --git a/src/tests/wide.cpp b/src/tests/wide.cpp
index 63213070f4..6486de38e9 100644
--- a/src/tests/wide.cpp
+++ b/src/tests/wide.cpp
@@ -1,7 +1,6 @@
 #include <array>
 #include <cstdlib>
 #include "main.h"
-#include "internal.h"
 
 const char SNAKE[] = "\xf0\x9f\x90\x8d"; // U+1F40D SNAKE
 const char SCORPION[] = "\xf0\x9f\xa6\x82"; // U+1F982 SCORPION