Skip to content

use POSIX feature macro to check for clock_gettime#348

Open
GitMensch wants to merge 1 commit intomasterfrom
posix-feature
Open

use POSIX feature macro to check for clock_gettime#348
GitMensch wants to merge 1 commit intomasterfrom
posix-feature

Conversation

@GitMensch
Copy link
Collaborator

@GitMensch GitMensch commented Aug 13, 2025

note: while _POSIX_TIMERS may be "indirectly" defined by including time.h on some systems, they come from unistd.h.

tested to work on Debian and MSYS2 (gcc + clang)

fixes #345 (C part)

to be checked:
Is there an environment where this does not work (it seems that on non-released mingwrt we do have the feature with clock_gettime since Dec 2017 but no posix-feature-macros at all; not sure if this is something to care for)?

Should we apply something similar to other places? See https://linux.die.net/man/7/posixoptions.

note: while _POSIX_TIMERS is "directly" defined by including time.h on some systems, it may only be done if the internal macros are defined, which is commonly done by including unistd.h, so that is done before

tested to work on Debian and MSYS2 (gcc + clang)

fixes #345 (C part)
@GitMensch GitMensch requested a review from Bill-Gray August 13, 2025 07:23
@GitMensch GitMensch marked this pull request as draft August 13, 2025 07:24
@GitMensch GitMensch changed the title use POSIX feature macro do check for clock_gettime use POSIX feature macro to check for clock_gettime Aug 13, 2025
@GitMensch GitMensch marked this pull request as ready for review September 15, 2025 06:38
@GitMensch
Copy link
Collaborator Author

ping @Bill-Gray for review/merge

@Bill-Gray
Copy link
Owner

I just tried building WinGUI and WinCon, cross-compiling on my Linux box. For both, I got :

x86_64-w64-mingw32-gcc -Wl,--out-implib,pdcurses.a -static-libgcc -shared -o pdcurses.dll addch.o addchstr.o addstr.o attr.o beep.o bkgd.o border.o clear.o color.o debug.o delch.o deleteln.o getch.o getstr.o getyx.o inch.o inchstr.o initscr.o inopts.o insch.o insstr.o instr.o kernel.o keyname.o mouse.o move.o outopts.o overlay.o pad.o panel.o printw.o refresh.o scanw.o scr_dump.o scroll.o slk.o termattr.o terminfo.o touch.o util.o window.o pdcclip.o pdcdisp.o pdcgetsc.o pdckbd.o pdcscrn.o pdcsetsc.o pdcutil.o -lgdi32 -lcomdlg32 -lwinmm
/usr/bin/x86_64-w64-mingw32-ld: getch.o:getch.c:(.text+0x61e): undefined reference to `clock_gettime'
/usr/bin/x86_64-w64-mingw32-ld: getch.o:getch.c:(.text+0xe8c): undefined reference to `clock_gettime'
collect2: error: ld returned 1 exit status
make: *** [Makefile:155: pdcurses.dll] Error 1

So... compiled without trouble, but couldn't link. Presumably, there's something a little more subtle about when the various timing functions are available to us... any ideas?

I also built with Digital Mars and OpenWATCOM 1.9, each for DOS and WinCon, and it all worked and the resulting testcurs.exe files worked in DOSBox and the Wine console, respectively.

I also compiled everything with both of those compilers for OS/2. I mention this just for completeness, since I don't actually have an OS/2-capable machine on which to test the results. I got build errors with DMC, but I've had those before (don't know why); it's not related to the current issue. With OpenWATCOM, everything compiled without warnings or errors.

So I think if we can figure out a fix for the above error message, we'd be good.

@GitMensch
Copy link
Collaborator Author

For clock_gettime we'd nee to add -lrt (for glibc only necessary before 2.17, see https://www.man7.org/linux/man-pages/man3/clock_gettime.3.html - but likely no problem to do so in any case), no?

Can you please test if adding it to the Makefile(s) works for your other build environments?

@Bill-Gray
Copy link
Owner

Sorry, should have just given this a go right away... especially since the only change required on my machine was adding -lrt to Makefile.

Unfortunately, it didn't work, because (with my MinGW version 9.3-win32 20200320)
there's no rt library available. I see the original comment in the code said that

   /* only newer MinGW environments have clock_gettime and
      those have CLOCK_REALTIME as a macro */

So the question becomes : how do we reliably determine that a given version of MinGW actually has clock_gettime()? I'm assuming the original method (if CLOCK_REALTIME is #defined, we've got it) failed on your system. Your new method (if _POSIX_TIMERS is #defined and is positive) fails on mine.

It appears that there are portability problems with clock_gettime(); in particular,

This function is missing on some platforms: Mac OS X 10.11, Minix 3.1.8, mingw, MSVC 14.
This function leaves the upper 32 bits of the tv_sec field of the result uninitialized on some platforms: mingw in 32-bit mode.

This is a little puzzling to me, since it seems to say that the function is missing for MinGW, and it also doesn't work. Seems to me it should be one or the other. (Maybe they mean "missing on old MinGW and broken on newer MinGW"?)

The implication is that we need to initialize tv_sec ourselves before calling clock_gettime(), presumably to zero. That does introduce a Y2038 problem, of course.

The following patch, which simply says "don't use clock_gettime() on MinGW because we can't really tell if it'll be available", compiles and Works On My Machine™ :

diff --git a/pdcurses/getch.c b/pdcurses/getch.c
index 78d0dbbb..a19ac8e5 100644
--- a/pdcurses/getch.c
+++ b/pdcurses/getch.c
@@ -419,9 +419,8 @@ use clock_gettime() or gettimeofday() when available. */
 #endif
 
 #if defined( _POSIX_C_SOURCE) && (_POSIX_C_SOURCE >= 199309L) \
-     && (!defined( __MINGW32__) || defined( CLOCK_REALTIME))
-   /* only newer MinGW environments have clock_gettime and
-      those have CLOCK_REALTIME as a macro */
+                    && !defined( __MINGW32__)
+   /* only newer MinGW environments have clock_gettime */
    #define HAVE_CLOCK_GETTIME
 #elif defined( _DEFAULT_SOURCE) || defined( _BSD_SOURCE) \
      || defined( __FreeBSD__) || defined( __MINGW32__)

However (as it says just below that patch) "POSIX.1-2008 marks gettimeofday() as obsolete, recommending the use of clock_gettime() instead." I would think that on some suitably modern version of MinGW, the above might get us a deprecation warning.

So ideally, we'd find out with which version of MinGW the rt library (and the clock_gettime() function) were added, and would choose the timing function based off that. (Well, that's not true. Ideally, the MinGW constants wouldn't make us think clock_gettime() was available, when it isn't.)

@GitMensch
Copy link
Collaborator Author

Your new method (if _POSIX_TIMERS is #defined and is positive) fails on mine.

That's... strange. can you run what we effectively do with this PR please?

echo | gcc -dM -E --include unistd.h --include time.h --include sys/time.h - | grep -i "time\|clock"

?

Note: we still may have to add -lrt for non !WIN32 compilations to the Makefile though.

@Bill-Gray
Copy link
Owner

Figured it out... clock_gettime() is defined in pthread_time.h. That caused me to try -lpthread, which worked. Yes, it's supposed to be in the rt library. But it appears it's not.

So I am reasonably confident that your fix will work (works here, anyway). I am just sufficiently worried by these problems that I'd rather hold off on having it in a release version, though.

The other possibility (maybe safer) is to say that we only use clock_gettime() if the glibc version is 2.17 or later (that is to say, if we can use it without having to add in rt or pthread).

I'm going to go ahead with the release "as is" (basically just change version constants and, in HISTORY.md, replace "Current PDCursesMod - 2026 January 06" with "PDCursesMod 4.5.4 - 2026 January 07"). That may lead to 4.5.5 coming out rather shortly afterward, after we've got a change we both like and it passes the GitHub Actions and Appveyor tests.

@GitMensch
Copy link
Collaborator Author

Sounds good to me.

@GitMensch
Copy link
Collaborator Author

GitMensch commented Feb 18, 2026

friendly ping @Bill-Gray "shortly after the release" seems to be ... not much later ;-)

@GitMensch
Copy link
Collaborator Author

another ping @Bill-Gray :-)

@Bill-Gray
Copy link
Owner

Bill-Gray commented Mar 19, 2026

I returned to this tonight, and... I think we might want to try something completely different.

I wrestled with ensuring that -lpthread is included for MinGW builds for WinCon, WinGUI, VT, SDLn, and GL. This tends to be a bit ugly, and of course, means you need the pthread library and some not entirely reliable logic for determining which function to use. It then occurred to me that we could, and probably should, just use the Win32 API for timing when building for Windows.

Specifically, that API provides a GetSystemTimeAsFileTime() function. That function goes back at least as far as MSVC 5.0 on my old WinME box (oldest case I can test), has no deprecation issues, and doesn't require added libraries.

It does return the time in a manner that is a little odd for our purposes (described in comments in the patch). A little arithmetic trickery is required to get it to work in situations where we don't have 64-bit integers (for example, Digital Mars). You can click here for the modified getch.c; the patch is as follows (I'll probably skip the mathematical exegesis in the committed patch, or at least put it elsewhere).

diff --git a/pdcurses/getch.c b/pdcurses/getch.c
index fb3a306a..d20ca2a0 100644
--- a/pdcurses/getch.c
+++ b/pdcurses/getch.c
@@ -407,6 +407,67 @@ static int _mouse_key(void)
     return key;
 }
 
+#ifdef _WIN32
+#undef MOUSE_MOVED
+#include <windows.h>
+
+/* GetSystemTimeAsFileTime( ) returns the time in units of 0.1 microsecond.
+We want milliseconds,  i.e.,  one ten-thousandth of that.  Also,  it returns
+the time as a 64-bit integer,  spread across two unsigned 32-bit ints.  So
+the time is given as
+
+decimicroseconds = A * 2^32 + B     (A=high 32-bit word,  B=low)
+
+   If 64-bit long integers are supported,  we can just shift A,  add B,
+cast to a long,  and divide by 10000.  On older systems/compilers,
+further logic is needed.  We actually want
+
+milliseconds = (A * 2^32 + B) / 10000      (note : 2^32 = 4294967296)
+
+   With integer division (i.e.,  truncation),
+
+A = 10000 * (A / 10000) + (A % 10000)
+
+milliseconds =
+    10000 * 2^32 * (A/10000) + ((A % 10000) * 2^32 + B) / 10000
+   =   2^32 * (A/10000)     +  ((A % 10000) * 2^32 + B) / 10000
+    < High word of result>    <-- Low word of result --------->
+
+   Since we're only doing this for 32-bit systems,  we ignore the high
+word.  (This does mean our count overflows every 49.7 days.  Care must
+be taken.)
+
+rval = ((A % 10000) * 4294967296 + B) / 10000
+     = ((A % 10000) * 4294960000 + (A % 10000) * 7296 + B) / 10000
+     = 429496 * (A % 10000) + ((A % 10000) * 7296 + B) / 10000
+
+   The addition within the last bit could overflow.  Fortunately,  10000 and
+7296 are both multiples of 16;  reducing the fraction gets us :
+
+     = 429496 * (A % 10000) + ((A % 10000) * 456 + B >> 4) / 625
+*/
+
+long PDC_millisecs( void)
+{
+   FILETIME ft;
+   long rval;
+#if LONG_MAX < 9223372036854775807L
+   DWORD tval;
+
+   GetSystemTimeAsFileTime( &ft);
+   tval = ft.dwHighDateTime % 10000u;
+   rval = 429496L * tval + (456L * tval + (long)(ft.dwLowDateTime >> 4)) / 625L;
+#else
+   uint64_t tval;
+
+   GetSystemTimeAsFileTime( &ft);
+   tval = ((uint64_t)ft.dwHighDateTime << 32) | (uint64_t)ft.dwLowDateTime;
+   rval = (long)( tval / 10000u);
+#endif
+   return( rval);
+}
+#else       /* Non-Microsoft Windows cases */
+
 /* ftime() is considered obsolete.  But it's all we have for
 millisecond precision on older compilers/systems.  We'll
 use clock_gettime() or gettimeofday() when available. */
@@ -460,6 +521,7 @@ long PDC_millisecs( void)
     return( (long)t.time * 1000L + (long)t.millitm);
 }
 #endif
+#endif      /* #ifndef _WIN32 */
 
 /* On many systems,  checking for a key hit is quite slow.  If
 PDC_check_key( ) returns FALSE,  we can safely stop checking for

@GitMensch
Copy link
Collaborator Author

GitMensch commented Mar 19, 2026

Interesting.

If we can get away with only a subset for 64 bit integers - let's add a check of INT_MAX or similar to limit the calculation in this case.

Note: After some back and forth we added the following logic and compatibility notes to GnuCOBOL:

#if defined (_MSC_VER)

/* Get function pointer for most precise time function
   GetSystemTimePreciseAsFileTime is available since OS-version Windows 2000
   GetSystemTimeAsFileTime        is available since OS-version Windows 8 / Server 2012
*/
static VOID		(WINAPI *time_as_filetime_func) (LPFILETIME) = NULL;

static void
get_function_ptr_for_precise_time (void)
{
	HMODULE		kernel32_handle;

	kernel32_handle = GetModuleHandle (TEXT ("kernel32.dll"));
	if (kernel32_handle != NULL) {
		time_as_filetime_func = (VOID (WINAPI *) (LPFILETIME))
			GetProcAddress (kernel32_handle, "GetSystemTimePreciseAsFileTime");
	}
	if (time_as_filetime_func == NULL) {
		time_as_filetime_func = GetSystemTimeAsFileTime;
	}
}
#endif

	[...]

	/* Get nanoseconds with highest precision possible */
#if defined (_MSC_VER)
	if (!time_as_filetime_func) {
		get_function_ptr_for_precise_time ();
	}
#pragma warning(suppress: 6011) /* the function pointer is always set by get_function_ptr_for_precise_time */
	(time_as_filetime_func) (&filetime);
	/* fallback to GetLocalTime if one of the following does not work */
	if (FileTimeToSystemTime (&filetime, &utc_time)
	 && SystemTimeToTzSpecificLocalTime (NULL, &utc_time, &local_time)) {
		set_cob_time_ns_from_filetime (filetime, &cb_time);
		return cb_time;
	}
#endif
	GetLocalTime (&local_time);
	cb_time.nanosecond = local_time.wMilliseconds * 1000000;
	return cb_time;

but from the code flow and your test result the compatibility comment seems wrong and should be the following instead - do you agree?

   GetSystemTimePreciseAsFileTime is available since OS-version Windows 8 / Server 2012
   GetSystemTimeAsFileTime        is available since OS-version Windows 2000 / ME

@Bill-Gray
Copy link
Owner

@GitMensch - an interesting thought, and I do have some projects where nanosecond accuracy might be useful.

For this, though, we're looking for time to milliseconds. We're already tossing out four digits; GetSystemTimePreciseAsFileName() would just enable us to toss out more digits.

Yes, the "imprecise" function definitely goes back at least as far as WinME. I've sometimes found it a little difficult to learn exactly when a Windows API call was added; Microsoft's documentation doesn't go back before Win2000. (I can understand them wanting everyone to forget that WinME ever existed.) But I did find a site that says that GetSystemTimePreciseAsFileName() goes back to Win95.

However... there may be a still better way, involving use of the GetTickCount() function. That would cause the code to contract to the following almost trivial patch. It's not as arithmetically interesting, but it's a lot simpler. And it, too, works for Win95.

diff --git a/pdcurses/getch.c b/pdcurses/getch.c
index 78d0dbbb..28ff1d37 100644
--- a/pdcurses/getch.c
+++ b/pdcurses/getch.c
@@ -407,6 +407,15 @@ static int _mouse_key(void)
     return key;
 }
 
+#ifdef _WIN32
+#include <windows.h>
+
+long PDC_millisecs( void)
+{
+   return( (long)GetTickCount( ));
+}
+#else
+
 /* ftime() is considered obsolete.  But it's all we have for
 millisecond precision on older compilers/systems.  We'll
 use clock_gettime() or gettimeofday() when available. */
@@ -460,6 +469,7 @@ long PDC_millisecs( void)
     return( (long)t.time * 1000L + (long)t.millitm);
 }
 #endif
+#endif      /* #ifndef _WIN32 */
 ```patch

@GitMensch
Copy link
Collaborator Author

The patch is too trivial as it needs to cater for overflow between calls.
But otherwise - if a prevision of 10-16 miliseconds is fine then it seems to be the ideal way to go for this implementation.

@Bill-Gray
Copy link
Owner

The patch is too trivial as it needs to cater for overflow between calls

Well, we have to do that anyway on any system where long integers are four bytes. And I was going to tell you that the code already does that correctly; the trick is to treat PDC_millisec() as a relative measure, not an absolute one. The difference between two calls to the function is meaningful and will avoid wraparound issues.

However, it's a little more complicated than that, and there are oddities depending on whether long integers are 64 or 32 bits. Blinking of text and the cursor can be disabled at the overflow point, and (less likely) if you pressed the mouse just before the overflow time and released it afterward, the press and release might not be properly combined into a click on some platforms.

Shouldn't be too difficult to fix, though.

Bill-Gray added a commit that referenced this pull request Mar 22, 2026
…n PDC_millisecs().

This addresses pull request #348 and issue #345.  Standard timekeeping
functions are poorly and unevenly supported on MS Windows;  it seems
simplest just to use the Windows API functions.

The function in question returns the time in units of 0.1 microseconds,
in a 64-bit integer that is broken into two 32-bit integers (DWORDs,
in Microsoftese).  On 64-bit systems,  we shift the two halves into
a single 64-bit integer and divide by 10000.

If 64-bit long integers are not available,  some 32-bit math must be
done to simulate the division and compute the lower 32 bits of the
result.  If A is the high 32 bits and B the low 32 bits,  then

decimicrosecs = A * 2^32 + B
millisecs = (A * 2^32 + B) / 10000

Since integer division truncates,  A = 10000 * (A/10000) + (A%10000),

ms = (10000*(A/10000) * 2^32 + (A%10000) + 2^32 + B) / 10000
   = 2^32 * (A/10000) + ((A % 10000) * 2^32 + B) / 10000
   < High word result> <Low word of result... part we want>

Tossing that high word of the result,  our return value is

rval = ((A % 10000) * 2^32 + B) / 10000

except that would overflow.  Using 2^32 = 4294967296,

rval = 429496 * (A % 10000) + ((A % 10000) * 7296 + B) / 10000

The addition within the last part could still overflow.  However,
7296 and 10000 have a common factor of 16;  division gets us

rval = 429496 * (A % 10000) + ((A % 10000) * 456 + B >> 4) / 625
@Bill-Gray
Copy link
Owner

Okay, I think we've got this one fixed.

Three platforms using PDC_millisecs() (framebuffer/DRM, x11new, VT) to handle timing on mouse clicks didn't handle overflow properly. Commit e23eb34 fixed that part of the puzzle.

I considered modified use of GetTickCount() to get around its wraparound issue. It's not difficult :

long PDC_millisecs( void)
{
   static long rval = 0;
   static DWORD prev_t = 0;
   const DWORD t = GetTickCount( );

   if( rval || prev_t)
      rval += (long)( t - prev_t);
   prev_t = t;
   return( rval);
}

However... we've got the GetSystemTimeAsFileTime() version. We don't really need its higher precision at present (we're using this to time blinking and mouse clicks), but we may someday, and it's not much more code. It is the patch I posted on 2026 Mar 18 above, with mathematical details mostly snipped and moved to the commit message.

Once the AppVeyor builds complete successfully, I'll close this and issue #345.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MinGW: getch.c does not build, no option to work around via Makefile

2 participants