Compare commits

...

26 commits
0.2.2 ... main

Author SHA1 Message Date
Caleb Connolly
080fcf60ca
line split mid-word instead of hanging
The line splitting implementation would look behind to find a space to
split on. This does not work if the line is one long word (like a URL),
and this implementation would hang as a result.

Add support for splitting mid-word in this case and prevent pbsplash
from hanging.
2023-03-06 01:12:31 +00:00
Caleb Connolly
52cb0ae649
nanosvgrast: small cleanup 2023-03-06 00:28:35 +00:00
Caleb Connolly
706888caf4
last few cleanups 2023-02-20 02:31:55 +00:00
Caleb Connolly
3c96de11dd
add timespec lib, decouple animation from frambuffer rate 2023-02-20 02:25:27 +00:00
Caleb Connolly
4a98f0a0cf
finish refactor, zero allocs to make valgrind happy 2023-02-20 01:25:37 +00:00
Caleb Connolly
7b714e433e
factor out message printing, use const where possible 2023-02-19 16:54:25 +00:00
Caleb Connolly
d7e9eca325
filled circles, factor out physical size calculations, slightly adjust bottom text 2023-02-19 16:32:31 +00:00
Caleb Connolly
22f07eb624
increase logo size slightly, fix text getting cut off 2023-02-06 17:17:19 +00:00
Caleb Connolly
83ab7e731d
limit logo to 25mm, move text to lower 2022-08-07 14:49:21 +01:00
Caleb Connolly
9d9633e1f3
terrible logo scaling hacks
Probably breaks landscape devices?
2022-08-06 20:08:26 +01:00
Caleb Connolly
cc37eea120
text: half space char width 2022-08-06 19:35:04 +01:00
Oliver Smith
85fc1c0a41
Add argument to disable animation
For displaying error messages, let's not show the loading animation.
2022-08-06 19:31:45 +01:00
Caleb Connolly
1ab85c2a33
TEST: animate: thicker circle outline 2022-08-06 19:30:23 +01:00
Caleb Connolly
435f8a7977
adjust sizes/positions 2022-08-06 19:30:11 +01:00
Oliver Smith
e2366e9d4f
Support message line at the bottom 2022-08-06 18:50:59 +01:00
Oliver Smith
a1df67dfce
nsvgGetTextShapes: fix uninitialized values
Use calloc to zero ret, because ret[i] does not get written if
NSVG_FLAGS_VISIBLE is unset. Found with valgrind.
2022-08-06 18:45:51 +01:00
Oliver Smith
fc5f065269
nanosvg: build implementation in extra c file
Speed up development iterations by building the nanosvg implementation
only once. Especially on the PinePhone it takes a bit.
2022-08-06 18:45:22 +01:00
Oliver Smith
36b0e6780b
include: add missing declarations
Add nsvgGetTextShapes() and nsvgRasterizeText() declarations outside of
the _IMPLEMENTATION areas, so the headerfiles can be used without
having _IMPLEMENTATION set.
2022-08-06 18:44:53 +01:00
Oliver Smith
2d79d8ad6a
Tweak the animation and placement
* Move the animation between the logo and the text
* Move text further below
* Change circles to 3 (like ...) and increase speed to 5
* Change circles radius, distance, amplitude
* Draw empty circles instead of full circles, to be used with a logo
  that also has an outline
2022-08-06 18:44:29 +01:00
Oliver Smith
817f988022
animate: fix fill_rect height 2022-08-06 18:44:29 +01:00
Oliver Smith
f94f3269da
getopt: make logo_size_max configurable with -q 2022-08-06 18:44:27 +01:00
Oliver Smith
67c9a10db4
usage: fix argument line
Add missing arguments, order them the same as listed below.
2022-08-06 18:36:45 +01:00
Oliver Smith
99ce784c28
getopt: remove unneeded check for -d
Remove the extra check for a value after specifying -d, as getopt() is
already taking care of it:

  pbsplash: option requires an argument: d
2022-08-06 18:36:04 +01:00
Oliver Smith
883ce77d1e
getopt: fix error msg for invalid dpi 2022-08-06 18:35:57 +01:00
Caleb Connolly
9c05d2c7f3
gitignore: ignore more stuff 2022-08-06 18:35:43 +01:00
Caleb Connolly
3c25e1ba44
support newlines + automatic line splitting 2022-08-06 18:34:10 +01:00
11 changed files with 1517 additions and 260 deletions

View file

@ -1,119 +1,10 @@
# BasedOnStyle: LLVM
# clang-format configuration file.
#
# For more information, see:
#
# Documentation/process/clang-format.rst
# https://clang.llvm.org/docs/ClangFormat.html
# https://clang.llvm.org/docs/ClangFormatStyleOptions.html
#
# Copied from Linux @ 5.18.0
---
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignConsecutiveMacros: AcrossEmptyLines
#AlignEscapedNewlines: Left # Unknown to clang-format-4.0
AlignOperands: true
AlignTrailingComments: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: None
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: false
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
AfterClass: false
AfterControlStatement: false
AfterEnum: false
AfterFunction: true
AfterNamespace: true
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
#AfterExternBlock: false # Unknown to clang-format-5.0
BeforeCatch: false
BeforeElse: false
IndentBraces: false
#SplitEmptyFunction: true # Unknown to clang-format-4.0
#SplitEmptyRecord: true # Unknown to clang-format-4.0
#SplitEmptyNamespace: true # Unknown to clang-format-4.0
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Custom
#BreakBeforeInheritanceComma: false # Unknown to clang-format-4.0
BreakBeforeTernaryOperators: false
BreakConstructorInitializersBeforeComma: false
#BreakConstructorInitializers: BeforeComma # Unknown to clang-format-4.0
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: false
ColumnLimit: 80
CommentPragmas: '^ IWYU pragma:'
#CompactNamespaces: false # Unknown to clang-format-4.0
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 8
ContinuationIndentWidth: 8
Cpp11BracedListStyle: false
DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
#FixNamespaceComments: false # Unknown to clang-format-4.0
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '.*'
Priority: 1
IncludeIsMainRegex: '(Test)?$'
IndentCaseLabels: false
#IndentPPDirectives: None # Unknown to clang-format-5.0
IndentWidth: 8 IndentWidth: 8
IndentWrappedFunctionNames: false
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: false
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
#ObjCBinPackProtocolList: Auto # Unknown to clang-format-5.0
ObjCBlockIndentWidth: 8
ObjCSpaceAfterProperty: true
ObjCSpaceBeforeProtocolList: true
# Taken from git's rules
#PenaltyBreakAssignment: 10 # Unknown to clang-format-4.0
PenaltyBreakBeforeFirstCallParameter: 30
PenaltyBreakComment: 10
PenaltyBreakFirstLessLess: 0
PenaltyBreakString: 10
PenaltyExcessCharacter: 100
PenaltyReturnTypeOnItsOwnLine: 60
PointerAlignment: Right
ReflowComments: false
SortIncludes: false
#SortUsingDeclarations: false # Unknown to clang-format-4.0
SpaceAfterCStyleCast: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
#SpaceBeforeCtorInitializerColon: true # Unknown to clang-format-5.0
#SpaceBeforeInheritanceColon: true # Unknown to clang-format-5.0
SpaceBeforeParens: ControlStatements
#SpaceBeforeRangeBasedForLoopColon: true # Unknown to clang-format-5.0
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInContainerLiterals: false
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Cpp03
TabWidth: 8
UseTab: Always UseTab: Always
... BreakBeforeBraces: Linux
AllowShortIfStatementsOnASingleLine: false
AlignConsecutiveMacros:
Enabled: true
AcrossEmptyLines: true
AcrossComments: false
IndentCaseLabels: false

3
.gitignore vendored
View file

@ -2,3 +2,6 @@ builddir/
build/ build/
*.json *.json
*.jsonc *.jsonc
*.bak
art/
.vscode*

View file

@ -182,6 +182,8 @@ NSVGpath* nsvgDuplicatePath(NSVGpath* p);
// Deletes an image. // Deletes an image.
void nsvgDelete(NSVGimage* image); void nsvgDelete(NSVGimage* image);
NSVGshape** nsvgGetTextShapes(const NSVGimage* image, const char* text, int textLen);
#ifndef NANOSVG_CPLUSPLUS #ifndef NANOSVG_CPLUSPLUS
#ifdef __cplusplus #ifdef __cplusplus
} }
@ -2961,15 +2963,19 @@ static void nsvg__scaleToViewbox(NSVGparser* p, const char* units)
} }
} }
NSVGshape** nsvgGetTextShapes(NSVGimage* image, char* text, int textLen) NSVGshape** nsvgGetTextShapes(const NSVGimage* image, const char* text, int textLen)
{ {
NSVGshape *shape = NULL; NSVGshape *shape = NULL;
NSVGshape **ret = malloc(sizeof(NSVGshape*)*textLen); // array of paths, text to render NSVGshape **ret = calloc(textLen, sizeof(shape)); // array of paths, text to render
int i; int i;
// make list of paths representing glyphs to render // make list of paths representing glyphs to render
for (i = 0; i < textLen; i++) for (i = 0; i < textLen; i++)
{ {
if (text[i] == ' ' || text[i] == '\n') {
ret[i] = NULL;
continue;
}
for (shape = image->shapes; shape != NULL; shape = shape->next) { for (shape = image->shapes; shape != NULL; shape = shape->next) {
if (!(shape->flags & NSVG_FLAGS_VISIBLE)) if (!(shape->flags & NSVG_FLAGS_VISIBLE))
continue; continue;
@ -3084,4 +3090,4 @@ void nsvgDelete(NSVGimage* image)
free(image); free(image);
} }
#endif #endif

View file

@ -66,6 +66,12 @@ void nsvgRasterize(NSVGrasterizer* r,
void nsvgDeleteRasterizer(NSVGrasterizer*); void nsvgDeleteRasterizer(NSVGrasterizer*);
void nsvgRasterizeText(NSVGrasterizer* r,
const NSVGimage* font, float tx, float ty, float scale,
unsigned char* dst, int w, int h, int stride,
const char* text);
#ifndef NANOSVGRAST_CPLUSPLUS #ifndef NANOSVGRAST_CPLUSPLUS
#ifdef __cplusplus #ifdef __cplusplus
} }
@ -1363,15 +1369,22 @@ static void dumpEdges(NSVGrasterizer* r, const char* name)
*/ */
void nsvgRasterizeText(NSVGrasterizer* r, void nsvgRasterizeText(NSVGrasterizer* r,
NSVGimage* font, float tx, float ty, float scale, const NSVGimage* font, float tx, float ty, float scale,
unsigned char* dst, int w, int h, int stride, unsigned char* dst, int w, int h, int stride,
char* text) const char* text)
{ {
NSVGshape *shape = NULL; NSVGshape *shape = NULL;
NSVGedge *e = NULL; NSVGedge *e = NULL;
NSVGcachedPaint cache; NSVGcachedPaint cache;
NSVGshape **shapes = nsvgGetTextShapes(font, text, strlen(text)); NSVGshape **shapes = nsvgGetTextShapes(font, text, strlen(text));
int i = 0, j = 0, textLen = strlen(text); int i = 0, j = 0, textLen = strlen(text);
int fontHeight = (font->fontAscent - font->fontDescent) * scale;
int xStart = tx;
int charWidth = font->defaultHorizAdv * scale;
// Hack because for some reason this has Y increase UP and we
// need to go DOWN every line
ty = ty + h - fontHeight;
r->bitmap = dst; r->bitmap = dst;
r->width = w; r->width = w;
@ -1391,17 +1404,23 @@ void nsvgRasterizeText(NSVGrasterizer* r,
} }
for (i = 0; i < textLen; i++) { for (i = 0; i < textLen; i++) {
if (text[i] == '\n') {
ty -= fontHeight;
// No clue why this is needed
tx = xStart - charWidth;
continue;
}
shape = shapes[i]; shape = shapes[i];
if (!shape) { if (!shape) {
if (text[i] == ' ') if (text[i] == ' ')
tx += font->defaultHorizAdv * scale; tx += charWidth / 2.f;
continue; continue;
} }
if (!(shape->flags & NSVG_FLAGS_VISIBLE)) if (!(shape->flags & NSVG_FLAGS_VISIBLE))
continue; continue;
if (i == 0 && strcmp(shape->id, "OpenSansRegular") == 0) if (i == 0 && strcmp(shape->id, "OpenSansRegular") == 0)
tx -= shape->horizAdvX * scale; tx = xStart - charWidth;
if (shape->fill.type != NSVG_PAINT_NONE) { if (shape->fill.type != NSVG_PAINT_NONE) {
nsvg__resetPool(r); nsvg__resetPool(r);
@ -1554,4 +1573,4 @@ void nsvgRasterize(NSVGrasterizer* r,
r->stride = 0; r->stride = 0;
} }
#endif #endif

View file

@ -1,6 +1,8 @@
#ifndef __pbsplash_h__ #ifndef __pbsplash_h__
#define __pbsplash_h__ #define __pbsplash_h__
#define MM_TO_PX(dpi, mm) (dpi / 25.4) * (mm)
struct col { struct col {
union { union {
unsigned int rgba; unsigned int rgba;
@ -10,6 +12,6 @@ struct col {
}; };
}; };
void animate_frame(int frame, int w, int h, long dpi); void animate_frame(int frame, int w, int y_off, long dpi);
#endif #endif

73
include/timespec.h Normal file
View file

@ -0,0 +1,73 @@
/* Functions for working with timespec structures
* Written by Daniel Collins (2017-2021)
* timespec_mod by Alex Forencich (2019)
* Various contributions by Ingo Albrecht (2021)
*
* This is free and unencumbered software released into the public domain.
*
* Anyone is free to copy, modify, publish, use, compile, sell, or
* distribute this software, either in source code form or as a compiled
* binary, for any purpose, commercial or non-commercial, and by any
* means.
*
* In jurisdictions that recognize copyright laws, the author or authors
* of this software dedicate any and all copyright interest in the
* software to the public domain. We make this dedication for the benefit
* of the public at large and to the detriment of our heirs and
* successors. We intend this dedication to be an overt act of
* relinquishment in perpetuity of all present and future rights to this
* software under copyright law.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* For more information, please refer to <http://unlicense.org/>
*/
#ifndef DAN_TIMESPEC_H
#define DAN_TIMESPEC_H
#include <stdbool.h>
#include <sys/time.h>
#include <time.h>
#ifdef __cplusplus
extern "C" {
#endif
struct timespec timespec_add(struct timespec ts1, struct timespec ts2);
struct timespec timespec_sub(struct timespec ts1, struct timespec ts2);
struct timespec timespec_mod(struct timespec ts1, struct timespec ts2);
struct timespec timespec_min(struct timespec ts1, struct timespec ts2);
struct timespec timespec_max(struct timespec ts1, struct timespec ts2);
struct timespec timespec_clamp(struct timespec ts1, struct timespec min, struct timespec max);
int timespec_cmp(struct timespec ts1, struct timespec ts2);
bool timespec_eq(struct timespec ts1, struct timespec ts2);
bool timespec_gt(struct timespec ts1, struct timespec ts2);
bool timespec_ge(struct timespec ts1, struct timespec ts2);
bool timespec_lt(struct timespec ts1, struct timespec ts2);
bool timespec_le(struct timespec ts1, struct timespec ts2);
struct timespec timespec_from_double(double s);
double timespec_to_double(struct timespec ts);
struct timespec timespec_from_timeval(struct timeval tv);
struct timeval timespec_to_timeval(struct timespec ts);
struct timespec timespec_from_ms(long milliseconds);
long timespec_to_ms(struct timespec ts);
struct timespec timespec_normalise(struct timespec ts);
struct timespec timespec_now();
#ifdef __cplusplus
}
#endif
#endif /* !DAN_TIMESPEC_H */

View file

@ -1,38 +1,37 @@
#include "pbsplash.h"
#include <math.h> #include <math.h>
#include <stdio.h> #include <stdio.h>
#include <tfblib/tfblib.h>
#include <tfblib/tfb_colors.h> #include <tfblib/tfb_colors.h>
#include "pbsplash.h" #include <tfblib/tfblib.h>
struct col color = { .r = 255, .g = 255, .b = 255, .a = 255 }; struct col color = {.r = 255, .g = 255, .b = 255, .a = 255};
#define PI 3.1415926535897932384626433832795 #define PI 3.1415926535897932384626433832795
#define n_circles 3
#define speed 2.5
#define n_circles 5 static void circles_wave(int frame, int w, int y_off, long dpi)
#define speed 3
void circles_wave(int frame, int w, int y_off, long dpi)
{ {
unsigned int t_col = tfb_make_color(color.r, color.g, color.b); unsigned int t_col = tfb_make_color(color.r, color.g, color.b);
int f = frame * speed; int f = round(frame * speed);
int rad = (int)(dpi * 4 / 96.0); int rad = MM_TO_PX(dpi, 1);
int dist = rad * 4; int dist = rad * 3.5;
int amplitude = rad * 2; int amplitude = rad * 1;
int left = (w / 2) - (dist * (n_circles - 1) / 2.0); int left = ((float)w / 2) - (dist * (n_circles - 1) / 2.0);
for (unsigned int i = 0; i < n_circles; i++) { for (unsigned int i = 0; i < n_circles; i++) {
int x = left + (i * dist); int x = left + (i * dist);
double offset = sin(f / 60.0 * PI + i); double offset = sin(f / 60.0 * PI + i);
int y = y_off + offset * amplitude; int y = y_off + offset * amplitude;
tfb_fill_rect(x - rad - 1, y_off - amplitude - rad, rad * 2 + 2, tfb_fill_rect(x - rad - 3, y_off - amplitude - rad - 3,
400 + rad * 2, tfb_black); rad * 2 + 6, amplitude * 2 + rad * 2 + 6,
tfb_black);
tfb_fill_circle(x, y, rad, t_col); tfb_fill_circle(x, y, rad, t_col);
} }
} }
void animate_frame(int frame, int w, int h, long dpi) void animate_frame(int frame, int w, int y_off, long dpi)
{ {
circles_wave(frame, w, h * 0.75, dpi); circles_wave(frame, w, y_off, dpi);
} }

View file

@ -1,6 +1,8 @@
src = [ src = [
'pbsplash.c',
'animate.c', 'animate.c',
'nanosvg.c',
'timespec.c',
'pbsplash.c',
] ]
executable('pbsplash', src, executable('pbsplash', src,

9
src/nanosvg.c Normal file
View file

@ -0,0 +1,9 @@
/* Build the nanosvg implementation here, to build it only once and have other
* C files build fast during development. */
#include <stdio.h>
#define NANOSVG_IMPLEMENTATION
#include "nanosvg.h"
#define NANOSVGRAST_IMPLEMENTATION
#include "nanosvgrast.h"

View file

@ -1,3 +1,4 @@
#include <errno.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <signal.h> #include <signal.h>
@ -13,54 +14,62 @@
#include <string.h> #include <string.h>
#include <math.h> #include <math.h>
#define NANOSVG_ALL_COLOR_KEYWORDS // Include full list of color keywords. #define NANOSVG_ALL_COLOR_KEYWORDS // Include full list of color keywords.
#define NANOSVG_IMPLEMENTATION // Expands implementation
#include "nanosvg.h" #include "nanosvg.h"
#define NANOSVGRAST_IMPLEMENTATION
#include "nanosvgrast.h" #include "nanosvgrast.h"
#include "timespec.h"
#include "pbsplash.h" #include "pbsplash.h"
#define MSG_MAX_LEN 4096 #define MSG_MAX_LEN 4096
#define DEFAULT_FONT_PATH "/usr/share/pbsplash/OpenSans-Regular.svg" #define DEFAULT_FONT_PATH "/usr/share/pbsplash/OpenSans-Regular.svg"
#define LOGO_SIZE_MAX_MM 90 #define LOGO_SIZE_MAX_MM 45
#define FONT_SIZE_PT 9 #define FONT_SIZE_PT 9
#define FONT_SIZE_B_PT 6
#define B_MESSAGE_OFFSET_MM 3
#define PT_TO_MM 0.38f #define PT_TO_MM 0.38f
#define TTY_PATH_LEN 11 #define TTY_PATH_LEN 11
#define DEBUGRENDER 0 #define DEBUGRENDER 0
#define MM_TO_PX(dpi, mm) (dpi / 25.4) * mm
volatile sig_atomic_t terminate = 0; volatile sig_atomic_t terminate = 0;
bool debug = false; bool debug = false;
struct col background_color = { .r = 0, .g = 0, .b = 0, .a = 255 }; struct col background_color = { .r = 0, .g = 0, .b = 0, .a = 255 };
static int screenWidth, screenHeight;
#define zalloc(size) calloc(1, size)
#define LOG(fmt, ...) \ #define LOG(fmt, ...) \
do { \ do { \
if (debug) \ if (debug) \
printf(fmt, ##__VA_ARGS__); \ printf(fmt, ##__VA_ARGS__); \
} while (0) } while (0)
int usage() static int usage()
{ {
// clang-format off // clang-format off
fprintf(stderr, "pbsplash: postmarketOS bootsplash generator\n"); fprintf(stderr, "pbsplash: postmarketOS bootsplash generator\n");
fprintf(stderr, "-------------------------------------------\n"); fprintf(stderr, "-------------------------------------------\n");
fprintf(stderr, "pbsplash [-h] [-d] [-f font] [-s splash image] [-m message]\n\n"); fprintf(stderr, "pbsplash [-v] [-h] [-f font] [-s splash image] [-m message]\n");
fprintf(stderr, " [-b message bottom] [-o font size bottom]\n");
fprintf(stderr, " [-p font size] [-q max logo size] [-d] [-e]\n\n");
fprintf(stderr, " -v enable verbose logging\n"); fprintf(stderr, " -v enable verbose logging\n");
fprintf(stderr, " -h show this help\n"); fprintf(stderr, " -h show this help\n");
fprintf(stderr, " -f path to SVG font file (default: %s)\n", DEFAULT_FONT_PATH); fprintf(stderr, " -f path to SVG font file (default: %s)\n", DEFAULT_FONT_PATH);
fprintf(stderr, " -s path to splash image to display\n"); fprintf(stderr, " -s path to splash image to display\n");
fprintf(stderr, " -m message to show under the splash image\n"); fprintf(stderr, " -m message to show under the splash image\n");
fprintf(stderr, " -b message to show at the bottom\n");
fprintf(stderr, " -o font size bottom in pt (default: %d)\n", FONT_SIZE_B_PT);
fprintf(stderr, " -p font size in pt (default: %d)\n", FONT_SIZE_PT); fprintf(stderr, " -p font size in pt (default: %d)\n", FONT_SIZE_PT);
fprintf(stderr, " -q max logo size in mm (default: %d)\n", LOGO_SIZE_MAX_MM);
fprintf(stderr, " -d custom DPI (for testing)\n"); fprintf(stderr, " -d custom DPI (for testing)\n");
fprintf(stderr, " -e error (no loading animation)\n");
// clang-format on // clang-format on
return 1; return 1;
} }
void term(int signum) static void term(int signum)
{ {
terminate = 1; terminate = 1;
} }
@ -117,9 +126,9 @@ static void blit_buf(unsigned char *buf, int x, int y, int w, int h, bool vflip,
static void draw_svg(NSVGimage *image, int x, int y, int w, int h) static void draw_svg(NSVGimage *image, int x, int y, int w, int h)
{ {
float sz = (int)((float)w / (float)image->width * 100.f) / 100.f; float sz = (int)((float)w / (float)image->width * 100.f) / 100.f;
LOG("draw_svg: %dx%d, %dx%d, %f\n", x, y, w, h, sz); LOG("draw_svg: (%d, %d), %dx%d, %f\n", x, y, w, h, sz);
NSVGrasterizer *rast = nsvgCreateRasterizer(); NSVGrasterizer *rast = nsvgCreateRasterizer();
unsigned char *img = malloc(w * h * 4); unsigned char *img = zalloc(w * h * 4);
nsvgRasterize(rast, image, 0, 0, sz, img, w, h, w * 4); nsvgRasterize(rast, image, 0, 0, sz, img, w, h, w * 4);
blit_buf(img, x, y, w, h, false, false); blit_buf(img, x, y, w, h, false, false);
@ -128,13 +137,13 @@ static void draw_svg(NSVGimage *image, int x, int y, int w, int h)
nsvgDeleteRasterizer(rast); nsvgDeleteRasterizer(rast);
} }
static void draw_text(NSVGimage *font, char *text, int x, int y, int width, static void draw_text(const NSVGimage *font, const char *text, int x, int y, int width,
int height, float scale, unsigned int tfb_col) int height, float scale, unsigned int tfb_col)
{ {
LOG("text '%s': fontsz=%f, x=%d, y=%d, dimensions: %d x %d\n", text, LOG("text '%s': fontsz=%f, x=%d, y=%d, dimensions: %d x %d\n", text,
scale, x, y, width, height); scale, x, y, width, height);
NSVGshape **shapes = nsvgGetTextShapes(font, text, strlen(text)); NSVGshape **shapes = nsvgGetTextShapes(font, text, strlen(text));
unsigned char *img = malloc(width * height * 4); unsigned char *img = zalloc(width * height * 4);
NSVGrasterizer *rast = nsvgCreateRasterizer(); NSVGrasterizer *rast = nsvgCreateRasterizer();
nsvgRasterizeText(rast, font, 0, 0, scale, img, width, height, nsvgRasterizeText(rast, font, 0, 0, scale, img, width, height,
@ -147,48 +156,295 @@ static void draw_text(NSVGimage *font, char *text, int x, int y, int width,
nsvgDeleteRasterizer(rast); nsvgDeleteRasterizer(rast);
} }
static inline float getShapeWidth(const NSVGimage *font, const NSVGshape *shape)
{
if (shape) {
return shape->horizAdvX;
} else {
return font->defaultHorizAdv;
}
}
/* /*
* Get the dimensions of a string in pixels. * Get the dimensions of a string in pixels.
* based on the font size and the font SVG file. * based on the font size and the font SVG file.
*/ */
static void getTextDimensions(NSVGimage *font, char *text, float scale, static const char *getTextDimensions(const NSVGimage *font, const char *text, float scale,
int *width, int *height) int *width, int *height)
{ {
int i = 0; int i, j;
int fontHeight = (font->fontAscent - font->fontDescent) * scale;
int maxWidth = 0;
*width = 0;
// The height is simply the height of the font * the scale factor
*height = (font->fontAscent - font->fontDescent) * scale;
if (text == NULL) if (text == NULL)
return; return text;
// Pre-allocate 3x the size to account for any word splitting
char *out_text = zalloc(strlen(text) * 3 + 1);
*width = 2; // font->defaultHorizAdv * scale;
// The height is simply the height of the font * the scale factor
*height = fontHeight;
NSVGshape **shapes = nsvgGetTextShapes(font, text, strlen(text)); NSVGshape **shapes = nsvgGetTextShapes(font, text, strlen(text));
bool line_has_space = false;
// Iterate over every glyph in the string to get the total width // Iterate over every glyph in the string to get the total width
for (i = 0; i < strlen(text); i++) { // and handle line-splitting
for (i = 0, j = 0; text[i] != '\0'; i++, j++) {
NSVGshape *shape = shapes[i]; NSVGshape *shape = shapes[i];
if (shape) { out_text[j] = text[i];
*width += (float)shapes[i]->horizAdvX * scale + 0.5; if (*width > screenWidth * 0.95) {
if (!line_has_space)
i--;
if (!line_has_space) {
if (i < 1) {
fprintf(stderr,
"ERROR: Text is too long to fit on screen!");
goto out;
}
} else {
int old_j = j;
while (out_text[j] != ' ' && j > 0) {
j--;
}
i = i - (old_j - j);
if (i <= 0) {
line_has_space = false;
fprintf(stderr,
"ERROR: Text is too long to fit on screen!");
goto out;
}
}
out_text[j] = '\n';
}
if (out_text[j] == '\n') {
printf("LINE SPLIT, %d %s\n", i, out_text);
line_has_space = false;
*height += fontHeight;
maxWidth = *width > maxWidth ? *width : maxWidth;
*width = 0;
continue;
} else if (text[i] == ' ') {
printf("SPACE! %s\n", out_text);
line_has_space = true;
}
*width += round(getShapeWidth(font, shape) * scale);
}
*width = *width > maxWidth ? *width : maxWidth;
out:
free(shapes);
return out_text;
}
struct dpi_info {
long dpi;
int pixels_per_milli;
float logo_size_px;
int logo_size_max_mm;
};
static void calculate_dpi_info(struct dpi_info *dpi_info)
{
int w_mm = tfb_screen_width_mm();
int h_mm = tfb_screen_height_mm();
if ((w_mm < 1 || h_mm < 1) && !dpi_info->dpi) {
fprintf(stderr, "ERROR!!!: Invalid screen size: %dx%d\n", w_mm, h_mm);
// Assume a dpi of 300
// This should be readable everywhere
// Except ridiculous HiDPI displays
// which honestly should expose their physical
// dimensions....
dpi_info->dpi = 300;
}
// If DPI is specified on cmdline then calculate display size from it
// otherwise calculate the dpi based on the display size.
if (dpi_info->dpi > 0) {
w_mm = screenWidth / (float)dpi_info->dpi * 25.4;
h_mm = screenHeight / (float)dpi_info->dpi * 25.4;
} else {
dpi_info->dpi = (float)screenWidth / (float)w_mm * 25.4;
}
dpi_info->pixels_per_milli = (float)screenWidth / (float)w_mm;
if (dpi_info->logo_size_max_mm * dpi_info->pixels_per_milli > screenWidth)
dpi_info->logo_size_max_mm = (screenWidth * 0.75f) / dpi_info->pixels_per_milli;
dpi_info->logo_size_px =
(float)(screenWidth < screenHeight ? screenWidth :
screenHeight) *
0.75f;
if (w_mm > 0 && h_mm > 0) {
if (w_mm < h_mm) {
if (w_mm > dpi_info->logo_size_max_mm * 1.2f)
dpi_info->logo_size_px = dpi_info->logo_size_max_mm * dpi_info->pixels_per_milli;
} else { } else {
*width += font->defaultHorizAdv * scale; if (h_mm > dpi_info->logo_size_max_mm * 1.2f)
dpi_info->logo_size_px = dpi_info->logo_size_max_mm * dpi_info->pixels_per_milli;
} }
} }
free(shapes); printf("%dx%d @ %dx%dmm, dpi=%ld, logo_size_px=%f\n", screenWidth,
screenHeight, w_mm, h_mm, dpi_info->dpi, dpi_info->logo_size_px);
}
struct msg_info {
const char *src_message;
const char *message;
int x;
int y;
int width;
int height;
float fontsz;
};
static void load_message(struct msg_info *msg_info, const struct dpi_info *dpi_info, float font_size_pt, const NSVGimage *font)
{
msg_info->fontsz = (font_size_pt * PT_TO_MM) /
(font->fontAscent - font->fontDescent) *
dpi_info->pixels_per_milli;
msg_info->message = getTextDimensions(font, msg_info->src_message, msg_info->fontsz, &msg_info->width, &msg_info->height);
msg_info->x = (screenWidth - msg_info->width) / 2;
// Y coordinate is set later
}
static void free_message(struct msg_info *msg_info)
{
if (!msg_info)
return;
if (msg_info->message && msg_info->message != msg_info->src_message)
free((void *)msg_info->message);
}
struct messages {
const char *font_path;
NSVGimage *font;
int font_size_pt;
int font_size_b_pt;
struct msg_info *msg;
struct msg_info *bottom_msg;
};
static inline void show_message(const struct msg_info *msg_info, const NSVGimage *font)
{
draw_text(font, msg_info->message, msg_info->x, msg_info->y, msg_info->width,
msg_info->height, msg_info->fontsz, tfb_gray);
}
static void show_messages(struct messages *msgs, const struct dpi_info *dpi_info)
{
static bool font_failed = false;
if (font_failed)
return;
if (!msgs->font)
msgs->font = nsvgParseFromFile(msgs->font_path, "px", 512);
if (!msgs->font || !msgs->font->shapes) {
font_failed = true;
fprintf(stderr, "failed to load SVG font, can't render messages\n");
fprintf(stderr, " font_path: %s\n", msgs->font_path);
fprintf(stderr, "msg: %s\n\nbottom_message: %s\n", msgs->msg->src_message, msgs->bottom_msg->src_message);
return;
}
if (msgs->bottom_msg) {
if (!msgs->bottom_msg->message) {
load_message(msgs->bottom_msg, dpi_info, msgs->font_size_b_pt, msgs->font);
msgs->bottom_msg->y = screenHeight - msgs->bottom_msg->height - MM_TO_PX(dpi_info->dpi, B_MESSAGE_OFFSET_MM);
}
show_message(msgs->bottom_msg, msgs->font);
}
if (msgs->msg) {
if (!msgs->msg->message) {
load_message(msgs->msg, dpi_info, msgs->font_size_pt, msgs->font);
if (msgs->bottom_msg)
msgs->msg->y = msgs->bottom_msg->y - msgs->msg->height - (MM_TO_PX(dpi_info->dpi, msgs->font_size_b_pt * PT_TO_MM) * 0.6);
else
msgs->msg->y = screenHeight - msgs->msg->height - (MM_TO_PX(dpi_info->dpi, msgs->font_size_pt * PT_TO_MM) * 2);
}
show_message(msgs->msg, msgs->font);
}
}
struct image_info {
const char *path;
NSVGimage *image;
float width;
float height;
float x;
float y;
};
static int load_image(const struct dpi_info *dpi_info, struct image_info *image_info)
{
int logo_size_px = dpi_info->logo_size_px;
image_info->image = nsvgParseFromFile(image_info->path, "", logo_size_px);
if (!image_info->image) {
fprintf(stderr, "failed to load SVG image\n");
fprintf(stderr, " image path: %s\n", image_info->path);
return 1;
}
// For taller images make sure they don't get too wide
if (image_info->image->width < image_info->image->height * 1.1)
logo_size_px = MM_TO_PX(dpi_info->dpi, 25);
float sz =
(float)logo_size_px /
(image_info->image->width > image_info->image->height ? image_info->image->height : image_info->image->width);
image_info->width = image_info->image->width * sz + 0.5;
image_info->height = image_info->image->height * sz + 0.5;
if (image_info->width > (dpi_info->logo_size_max_mm * dpi_info->pixels_per_milli)) {
float scalefactor =
((float)(dpi_info->logo_size_max_mm * dpi_info->pixels_per_milli) / image_info->width);
//printf("Got scale factor: %f\n", scalefactor);
image_info->width = dpi_info->logo_size_max_mm * dpi_info->pixels_per_milli;
image_info->height *= scalefactor;
}
image_info->x = (float)screenWidth / 2 - image_info->width * 0.5f;
image_info->y = (float)screenHeight / 2 - image_info->height * 0.5f;
return 0;
} }
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
int rc = 0; int rc = 0;
char *message = NULL; char *message = NULL;
char *splash_image = NULL; char *message_bottom = NULL;
char *font_path = DEFAULT_FONT_PATH;
char active_tty[TTY_PATH_LEN + 1]; char active_tty[TTY_PATH_LEN + 1];
NSVGimage *image = NULL;
NSVGimage *font = NULL;
struct sigaction action; struct sigaction action;
float font_size = FONT_SIZE_PT; struct messages msgs = {
.font_path = DEFAULT_FONT_PATH,
.font_size_pt = FONT_SIZE_PT,
.font_size_b_pt = FONT_SIZE_B_PT,
.msg = NULL,
.bottom_msg = NULL,
};
struct dpi_info dpi_info = {
.dpi = 0,
.pixels_per_milli = 0,
.logo_size_px = 0,
.logo_size_max_mm = LOGO_SIZE_MAX_MM,
};
struct image_info image_info = {
.path = NULL,
.image = NULL,
.width = 0,
.height = 0,
.x = 0,
.y = 0,
};
int optflag; int optflag;
long dpi = 0; bool animation = true;
memset(active_tty, '\0', TTY_PATH_LEN); memset(active_tty, '\0', TTY_PATH_LEN);
strcat(active_tty, "/dev/"); strcat(active_tty, "/dev/");
@ -198,7 +454,7 @@ int main(int argc, char **argv)
sigaction(SIGTERM, &action, NULL); sigaction(SIGTERM, &action, NULL);
sigaction(SIGINT, &action, NULL); sigaction(SIGINT, &action, NULL);
while ((optflag = getopt(argc, argv, "hvf:s:m:p:d:")) != -1) { while ((optflag = getopt(argc, argv, "hvf:s:m:b:o:p:q:d:e")) != -1) {
char *end = NULL; char *end = NULL;
switch (optflag) { switch (optflag) {
case 'h': case 'h':
@ -207,34 +463,53 @@ int main(int argc, char **argv)
debug = true; debug = true;
break; break;
case 'f': case 'f':
font_path = optarg; msgs.font_path = optarg;
break; break;
case 's': case 's':
splash_image = optarg; image_info.path = optarg;
break; break;
case 'm': case 'm':
message = optarg; message = malloc(strlen(optarg) + 1);
strcpy(message, optarg);
break; break;
case 'p': case 'b':
font_size = strtof(optarg, &end); message_bottom = malloc(strlen(optarg) + 1);
strcpy(message_bottom, optarg);
break;
case 'o':
msgs.font_size_b_pt = strtof(optarg, &end);
if (end == optarg) { if (end == optarg) {
fprintf(stderr, "Invalid font size: %s\n", fprintf(stderr, "Invalid font size: %s\n",
optarg); optarg);
return usage(); return usage();
} }
break; break;
case 'p':
msgs.font_size_pt = strtof(optarg, &end);
if (end == optarg) {
fprintf(stderr, "Invalid font size: %s\n",
optarg);
return usage();
}
break;
case 'q':
dpi_info.logo_size_max_mm = strtof(optarg, &end);
if (end == optarg) {
fprintf(stderr, "Invalid max logo size: %s\n",
optarg);
return usage();
}
break;
case 'd': case 'd':
if (!optarg) { dpi_info.dpi = strtol(optarg, &end, 10);
fprintf(stderr, "--dpi requires an argument\n"); if (end == optarg || dpi_info.dpi < 0) {
return usage(); fprintf(stderr, "Invalid dpi: %s\n", optarg);
}
dpi = strtol(optarg, &end, 10);
if (end == optarg) {
fprintf(stderr, "Invalid font size: %s\n",
optarg);
return usage(); return usage();
} }
break; break;
case 'e':
animation = false;
break;
default: default:
return usage(); return usage();
} }
@ -253,7 +528,7 @@ int main(int argc, char **argv)
LOG("active tty: '%s'\n", active_tty); LOG("active tty: '%s'\n", active_tty);
if ((rc = tfb_acquire_fb(/*TFB_FL_NO_TTY_KD_GRAPHICS */0, "/dev/fb0", if ((rc = tfb_acquire_fb(/*TFB_FL_NO_TTY_KD_GRAPHICS */ 0, "/dev/fb0",
active_tty)) != TFB_SUCCESS) { active_tty)) != TFB_SUCCESS) {
fprintf(stderr, "tfb_acquire_fb() failed with error code: %d\n", fprintf(stderr, "tfb_acquire_fb() failed with error code: %d\n",
rc); rc);
@ -261,103 +536,94 @@ int main(int argc, char **argv)
return rc; return rc;
} }
int w = (int)tfb_screen_width(); screenWidth = (int)tfb_screen_width();
int h = (int)tfb_screen_height(); screenHeight = (int)tfb_screen_height();
int w_mm = tfb_screen_width_mm(); calculate_dpi_info(&dpi_info);
int h_mm = tfb_screen_height_mm();
// If DPI is specified on cmdline then calculate display size from it
// otherwise calculate the dpi based on the display size.
if (dpi > 0) {
w_mm = w / (float)dpi * 25.4;
h_mm = h / (float)dpi * 25.4;
} else {
dpi = (float)w / (float)w_mm * 25.4;
}
int pixels_per_milli = (float)w / (float)w_mm;
float logo_size_px = (float)(w < h ? w : h) * 0.75f; rc = load_image(&dpi_info, &image_info);
if (w_mm > 0 && h_mm > 0) { if (rc)
if (w_mm < h_mm) {
if (w_mm > (float)LOGO_SIZE_MAX_MM * 1.2f)
logo_size_px = (float)LOGO_SIZE_MAX_MM *
pixels_per_milli;
} else {
if (h_mm > (float)LOGO_SIZE_MAX_MM * 1.2f)
logo_size_px = (float)LOGO_SIZE_MAX_MM *
pixels_per_milli;
}
}
LOG("%dx%d @ %dx%dmm, dpi=%ld, logo_size_px=%f\n", w, h, w_mm, h_mm,
dpi, logo_size_px);
image = nsvgParseFromFile(splash_image, "", logo_size_px);
if (!image) {
fprintf(stderr, "failed to load SVG image\n");
rc = 1;
goto out; goto out;
}
float sz = float animation_y = image_info.y + image_info.height + MM_TO_PX(dpi_info.dpi, 5);
(float)logo_size_px /
(image->width > image->height ? image->width : image->height);
int image_w = image->width * sz + 0.5;
int image_h = image->height * sz + 0.5;
float x = (float)w / 2;
float y = (float)h / 2;
// Center the image
x -= image_w * 0.5f;
y -= image_h * 0.5f;
tfb_clear_screen(tfb_make_color(background_color.r, background_color.g, tfb_clear_screen(tfb_make_color(background_color.r, background_color.g,
background_color.b)); background_color.b));
draw_svg(image, x, y, image_w, image_h); draw_svg(image_info.image, image_info.x, image_info.y, image_info.width, image_info.height);
if (message) { if (!message && !message_bottom)
int textWidth, textHeight; goto no_messages;
font = nsvgParseFromFile(font_path, "px", 512); struct msg_info bottom_msg, msg;
if (!font || !font->shapes) {
fprintf(stderr, "failed to load SVG font\n");
rc = 1;
goto out;
}
float fontsz = ((float)font_size * PT_TO_MM) / memset(&bottom_msg, 0, sizeof(bottom_msg));
(font->fontAscent - font->fontDescent) * memset(&msg, 0, sizeof(msg));
pixels_per_milli;
getTextDimensions(font, message, fontsz, &textWidth, bottom_msg.src_message = message_bottom;
&textHeight); msg.src_message = message;
int tx = w / 2.f - textWidth / 2.f; if (message_bottom)
int ty = y + image_h + textHeight * 0.5f + MM_TO_PX(dpi, 2); msgs.bottom_msg = &bottom_msg;
if (message)
msgs.msg = &msg;
draw_text(font, message, tx, ty, textWidth, textHeight, fontsz, show_messages(&msgs, &dpi_info);
tfb_gray);
}
no_messages:
tfb_flush_window(); tfb_flush_window();
tfb_flush_fb(); tfb_flush_fb();
int frame = 0; int tick = 0;
int tty = open(active_tty, O_RDWR); int tty = open(active_tty, O_RDWR);
if (!tty) {
fprintf(stderr, "Failed to open tty %s (%d)\n", active_tty, errno);
goto out;
}
struct timespec epoch, start, end, diff;
int target_fps = 60;
float tickrate = 60.0;
clock_gettime(CLOCK_REALTIME, &epoch);
while (!terminate) { while (!terminate) {
animate_frame(frame++, w, h, dpi); if (!animation) {
sleep(1);
continue;
}
clock_gettime(CLOCK_REALTIME, &start);
tick = timespec_to_double(timespec_sub(start, epoch)) * tickrate;
animate_frame(tick, screenWidth, animation_y, dpi_info.dpi);
tfb_flush_fb(); tfb_flush_fb();
clock_gettime(CLOCK_REALTIME, &end);
diff = timespec_sub(end, start);
//printf("%05d: %09ld\n", tick, diff.tv_nsec);
if (diff.tv_nsec < 1000000000 / target_fps) {
struct timespec sleep_time = {
.tv_sec = 0,
.tv_nsec = 1000000000 / target_fps - diff.tv_nsec,
};
nanosleep(&sleep_time, NULL);
}
} }
out: out:
// Before we exit print the logo so it will persist // Before we exit print the logo so it will persist
if (image) { if (image_info.image) {
ioctl(tty, KDSETMODE, KD_TEXT); ioctl(tty, KDSETMODE, KD_TEXT);
draw_svg(image, x, y, image_w, image_h); draw_svg(image_info.image, image_info.x, image_info.y, image_info.width, image_info.height);
} }
nsvgDelete(font); // Draw the messages again so they will persist
nsvgDelete(image); show_messages(&msgs, &dpi_info);
nsvgDelete(image_info.image);
nsvgDelete(msgs.font);
free_message(msgs.msg);
free_message(msgs.bottom_msg);
if (message)
free(message);
if (message_bottom)
free(message_bottom);
// The TTY might end up in a weird state if this // The TTY might end up in a weird state if this
// is not called! // is not called!
tfb_release_fb(); tfb_release_fb();

987
src/timespec.c Normal file
View file

@ -0,0 +1,987 @@
/* Functions for working with timespec structures
* Written by Daniel Collins (2017-2021)
* timespec_mod by Alex Forencich (2019)
* Various contributions by Ingo Albrecht (2021)
*
* This is free and unencumbered software released into the public domain.
*
* Anyone is free to copy, modify, publish, use, compile, sell, or
* distribute this software, either in source code form or as a compiled
* binary, for any purpose, commercial or non-commercial, and by any
* means.
*
* In jurisdictions that recognize copyright laws, the author or authors
* of this software dedicate any and all copyright interest in the
* software to the public domain. We make this dedication for the benefit
* of the public at large and to the detriment of our heirs and
* successors. We intend this dedication to be an overt act of
* relinquishment in perpetuity of all present and future rights to this
* software under copyright law.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* For more information, please refer to <http://unlicense.org/>
*/
/** \file timespec.c
* \brief Functions for working with timespec structures.
*
* This library aims to provide a comprehensive set of functions with
* well-defined behaviour that handle all edge cases (e.g. negative values) in
* a sensible manner.
*
* Negative values are allowed in the tv_sec and/or tv_usec field of timespec
* structures, tv_usec is always relative to tv_sec, so mixing positive and
* negative values will produce consistent results:
*
* <PRE>
* { tv_sec = 1, tv_nsec = 500000000 } == 1.5 seconds
* { tv_sec = 1, tv_nsec = 0 } == 1.0 seconds
* { tv_sec = 1, tv_nsec = -500000000 } == 0.5 seconds
* { tv_sec = 0, tv_nsec = 500000000 } == 0.5 seconds
* { tv_sec = 0, tv_nsec = 0 } == 0.0 seconds
* { tv_sec = 0, tv_nsec = -500000000 } == -0.5 seconds
* { tv_sec = -1, tv_nsec = 500000000 } == -0.5 seconds
* { tv_sec = -1, tv_nsec = 0 } == -1.0 seconds
* { tv_sec = -1, tv_nsec = -500000000 } == -1.5 seconds
* </PRE>
*
* Furthermore, any timespec structure processed or returned by library functions
* is normalised according to the rules in timespec_normalise().
*/
#include <limits.h>
#include <stdbool.h>
#include <sys/time.h>
#include <time.h>
#include <inttypes.h>
#include "timespec.h"
#define NSEC_PER_SEC 1000000000
/** \fn struct timespec timespec_add(struct timespec ts1, struct timespec ts2)
* \brief Returns the result of adding two timespec structures.
*/
struct timespec timespec_add(struct timespec ts1, struct timespec ts2)
{
/* Normalise inputs to prevent tv_nsec rollover if whole-second values
* are packed in it.
*/
ts1 = timespec_normalise(ts1);
ts2 = timespec_normalise(ts2);
ts1.tv_sec += ts2.tv_sec;
ts1.tv_nsec += ts2.tv_nsec;
return timespec_normalise(ts1);
}
/** \fn struct timespec timespec_sub(struct timespec ts1, struct timespec ts2)
* \brief Returns the result of subtracting ts2 from ts1.
*/
struct timespec timespec_sub(struct timespec ts1, struct timespec ts2)
{
/* Normalise inputs to prevent tv_nsec rollover if whole-second values
* are packed in it.
*/
ts1 = timespec_normalise(ts1);
ts2 = timespec_normalise(ts2);
ts1.tv_sec -= ts2.tv_sec;
ts1.tv_nsec -= ts2.tv_nsec;
return timespec_normalise(ts1);
}
/** \fn struct timespec timespec_mod(struct timespec ts1, struct timespec ts2)
* \brief Returns the remainder left over after dividing ts1 by ts2 (ts1%ts2).
*/
struct timespec timespec_mod(struct timespec ts1, struct timespec ts2)
{
int i = 0;
bool neg1 = false;
bool neg2 = false;
/* Normalise inputs to prevent tv_nsec rollover if whole-second values
* are packed in it.
*/
ts1 = timespec_normalise(ts1);
ts2 = timespec_normalise(ts2);
/* If ts2 is zero, just return ts1
*/
if (ts2.tv_sec == 0 && ts2.tv_nsec == 0)
{
return ts1;
}
/* If inputs are negative, flip and record sign
*/
if (ts1.tv_sec < 0 || ts1.tv_nsec < 0)
{
neg1 = true;
ts1.tv_sec = -ts1.tv_sec;
ts1.tv_nsec = -ts1.tv_nsec;
}
if (ts2.tv_sec < 0 || ts2.tv_nsec < 0)
{
neg2 = true;
ts2.tv_sec = -ts2.tv_sec;
ts2.tv_nsec = -ts2.tv_nsec;
}
/* Shift ts2 until it is larger than ts1 or is about to overflow
*/
while ((ts2.tv_sec < (LONG_MAX >> 1)) && timespec_ge(ts1, ts2))
{
i++;
ts2.tv_nsec <<= 1;
ts2.tv_sec <<= 1;
if (ts2.tv_nsec > NSEC_PER_SEC)
{
ts2.tv_nsec -= NSEC_PER_SEC;
ts2.tv_sec++;
}
}
/* Division by repeated subtraction
*/
while (i >= 0)
{
if (timespec_ge(ts1, ts2))
{
ts1 = timespec_sub(ts1, ts2);
}
if (i == 0)
{
break;
}
i--;
if (ts2.tv_sec & 1)
{
ts2.tv_nsec += NSEC_PER_SEC;
}
ts2.tv_nsec >>= 1;
ts2.tv_sec >>= 1;
}
/* If signs differ and result is nonzero, subtract once more to cross zero
*/
if (neg1 ^ neg2 && (ts1.tv_sec != 0 || ts1.tv_nsec != 0))
{
ts1 = timespec_sub(ts1, ts2);
}
/* Restore sign
*/
if (neg1)
{
ts1.tv_sec = -ts1.tv_sec;
ts1.tv_nsec = -ts1.tv_nsec;
}
return ts1;
}
/** \fn struct timespec timespec_min(struct timespec ts1, struct timespec ts2)
* \brief Return the lesser one of the two given timespec values.
*/
struct timespec timespec_min(struct timespec ts1, struct timespec ts2) {
if(timespec_le(ts1, ts2)) {
return ts1;
} else {
return ts2;
}
}
/** \fn struct timespec timespec_max(struct timespec ts1, struct timespec ts2)
* \brief Return the greater one of the two given timespec values.
*/
struct timespec timespec_max(struct timespec ts1, struct timespec ts2) {
if(timespec_ge(ts1, ts2)) {
return ts1;
} else {
return ts2;
}
}
/** \fn struct timespec timespec_clamp(struct timespec ts, struct timespec min, struct timespec max)
* \brief Clamp the value of TS between MIN and MAX.
*/
struct timespec timespec_clamp(struct timespec ts, struct timespec min, struct timespec max) {
if(timespec_gt(ts, max)) {
return max;
}
if(timespec_lt(ts, min)) {
return min;
}
return ts;
}
/** \fn int timespec_cmp(struct timespec ts1, struct timespec ts2)
* \brief Returns (1, 0, -1) if ts1 is (greater than, equal to, less than) to ts2.
*/
int timespec_cmp(struct timespec ts1, struct timespec ts2)
{
ts1 = timespec_normalise(ts1);
ts2 = timespec_normalise(ts2);
if(ts1.tv_sec == ts2.tv_sec && ts1.tv_nsec == ts2.tv_nsec)
{
return 0;
}
else if((ts1.tv_sec > ts2.tv_sec)
|| (ts1.tv_sec == ts2.tv_sec && ts1.tv_nsec > ts2.tv_nsec))
{
return 1;
}
else {
return -1;
}
}
/** \fn bool timespec_eq(struct timespec ts1, struct timespec ts2)
* \brief Returns true if the two timespec structures are equal.
*/
bool timespec_eq(struct timespec ts1, struct timespec ts2)
{
ts1 = timespec_normalise(ts1);
ts2 = timespec_normalise(ts2);
return (ts1.tv_sec == ts2.tv_sec && ts1.tv_nsec == ts2.tv_nsec);
}
/** \fn bool timespec_gt(struct timespec ts1, struct timespec ts2)
* \brief Returns true if ts1 is greater than ts2.
*/
bool timespec_gt(struct timespec ts1, struct timespec ts2)
{
ts1 = timespec_normalise(ts1);
ts2 = timespec_normalise(ts2);
return (ts1.tv_sec > ts2.tv_sec || (ts1.tv_sec == ts2.tv_sec && ts1.tv_nsec > ts2.tv_nsec));
}
/** \fn bool timespec_ge(struct timespec ts1, struct timespec ts2)
* \brief Returns true if ts1 is greater than or equal to ts2.
*/
bool timespec_ge(struct timespec ts1, struct timespec ts2)
{
ts1 = timespec_normalise(ts1);
ts2 = timespec_normalise(ts2);
return (ts1.tv_sec > ts2.tv_sec || (ts1.tv_sec == ts2.tv_sec && ts1.tv_nsec >= ts2.tv_nsec));
}
/** \fn bool timespec_lt(struct timespec ts1, struct timespec ts2)
* \brief Returns true if ts1 is less than ts2.
*/
bool timespec_lt(struct timespec ts1, struct timespec ts2)
{
ts1 = timespec_normalise(ts1);
ts2 = timespec_normalise(ts2);
return (ts1.tv_sec < ts2.tv_sec || (ts1.tv_sec == ts2.tv_sec && ts1.tv_nsec < ts2.tv_nsec));
}
/** \fn bool timespec_le(struct timespec ts1, struct timespec ts2)
* \brief Returns true if ts1 is less than or equal to ts2.
*/
bool timespec_le(struct timespec ts1, struct timespec ts2)
{
ts1 = timespec_normalise(ts1);
ts2 = timespec_normalise(ts2);
return (ts1.tv_sec < ts2.tv_sec || (ts1.tv_sec == ts2.tv_sec && ts1.tv_nsec <= ts2.tv_nsec));
}
/** \fn struct timespec timespec_from_double(double s)
* \brief Converts a fractional number of seconds to a timespec.
*/
struct timespec timespec_from_double(double s)
{
struct timespec ts = {
.tv_sec = s,
.tv_nsec = (s - (long)(s)) * NSEC_PER_SEC,
};
return timespec_normalise(ts);
}
/** \fn double timespec_to_double(struct timespec ts)
* \brief Converts a timespec to a fractional number of seconds.
*/
double timespec_to_double(struct timespec ts)
{
return ((double)(ts.tv_sec) + ((double)(ts.tv_nsec) / NSEC_PER_SEC));
}
/** \fn struct timespec timespec_from_timeval(struct timeval tv)
* \brief Converts a timeval to a timespec.
*/
struct timespec timespec_from_timeval(struct timeval tv)
{
struct timespec ts = {
.tv_sec = tv.tv_sec,
.tv_nsec = tv.tv_usec * 1000
};
return timespec_normalise(ts);
}
/** \fn struct timeval timespec_to_timeval(struct timespec ts)
* \brief Converts a timespec to a timeval.
*/
struct timeval timespec_to_timeval(struct timespec ts)
{
ts = timespec_normalise(ts);
struct timeval tv = {
.tv_sec = ts.tv_sec,
.tv_usec = ts.tv_nsec / 1000,
};
return tv;
}
/** \fn struct timespec timespec_from_ms(long milliseconds)
* \brief Converts an integer number of milliseconds to a timespec.
*/
struct timespec timespec_from_ms(long milliseconds)
{
struct timespec ts = {
.tv_sec = (milliseconds / 1000),
.tv_nsec = (milliseconds % 1000) * 1000000,
};
return timespec_normalise(ts);
}
/** \fn long timespec_to_ms(struct timespec ts)
* \brief Converts a timespec to an integer number of milliseconds.
*/
long timespec_to_ms(struct timespec ts)
{
return (ts.tv_sec * 1000) + (ts.tv_nsec / 1000000);
}
/** \fn struct timespec timespec_normalise(struct timespec ts)
* \brief Normalises a timespec structure.
*
* Returns a normalised version of a timespec structure, according to the
* following rules:
*
* 1) If tv_nsec is >=1,000,000,00 or <=-1,000,000,000, flatten the surplus
* nanoseconds into the tv_sec field.
*
* 2) If tv_nsec is negative, decrement tv_sec and roll tv_nsec up to represent
* the same value attainable by ADDING nanoseconds to tv_sec.
*/
struct timespec timespec_normalise(struct timespec ts)
{
while(ts.tv_nsec >= NSEC_PER_SEC)
{
++(ts.tv_sec);
ts.tv_nsec -= NSEC_PER_SEC;
}
while(ts.tv_nsec <= -NSEC_PER_SEC)
{
--(ts.tv_sec);
ts.tv_nsec += NSEC_PER_SEC;
}
if(ts.tv_nsec < 0)
{
/* Negative nanoseconds isn't valid according to POSIX.
* Decrement tv_sec and roll tv_nsec over.
*/
--(ts.tv_sec);
ts.tv_nsec = (NSEC_PER_SEC + ts.tv_nsec);
}
return ts;
}
struct timespec timespec_now()
{
struct timespec t;
clock_gettime(CLOCK_REALTIME, &t);
return t;
}
#ifdef TEST
#include <stdio.h>
#define TEST_NORMALISE(ts_sec, ts_nsec, expect_sec, expect_nsec) { \
struct timespec in = { .tv_sec = ts_sec, .tv_nsec = ts_nsec }; \
struct timespec got = timespec_normalise(in); \
if(got.tv_sec != expect_sec || got.tv_nsec != expect_nsec) \
{ \
printf("%s:%d: timespec_normalise({%ld, %ld}) returned wrong values\n", __FILE__, __LINE__, \
(long)(ts_sec), (long)(ts_nsec)); \
printf(" Expected: {%ld, %ld}\n", (long)(expect_sec), (long)(expect_nsec)); \
printf(" Got: {%ld, %ld}\n", (long)(got.tv_sec), (long)(got.tv_nsec)); \
++result; \
} \
}
#define TEST_BINOP(func, ts1_sec, ts1_nsec, ts2_sec, ts2_nsec, expect_sec, expect_nsec) { \
struct timespec ts1 = { .tv_sec = ts1_sec, .tv_nsec = ts1_nsec }; \
struct timespec ts2 = { .tv_sec = ts2_sec, .tv_nsec = ts2_nsec }; \
struct timespec got = func(ts1, ts2); \
if(got.tv_sec != expect_sec || got.tv_nsec != expect_nsec) \
{ \
printf(#func "({%ld, %ld}, {%ld, %ld}) returned wrong values\n", \
(long)(ts1_sec), (long)(ts1_nsec), (long)(ts2_sec), (long)(ts2_nsec)); \
printf(" Expected: {%ld, %ld}\n", (long)(expect_sec), (long)(expect_nsec)); \
printf(" Got: {%ld, %ld}\n", (long)(got.tv_sec), (long)(got.tv_nsec)); \
++result; \
} \
}
#define TEST_TRINOP(func, ts1_sec, ts1_nsec, ts2_sec, ts2_nsec, ts3_sec, ts3_nsec, expect_sec, expect_nsec) { \
struct timespec ts1 = { .tv_sec = ts1_sec, .tv_nsec = ts1_nsec }; \
struct timespec ts2 = { .tv_sec = ts2_sec, .tv_nsec = ts2_nsec }; \
struct timespec ts3 = { .tv_sec = ts3_sec, .tv_nsec = ts3_nsec }; \
struct timespec got = func(ts1, ts2, ts3); \
if(got.tv_sec != expect_sec || got.tv_nsec != expect_nsec) \
{ \
printf(#func "({%ld, %ld}, {%ld, %ld}, {%ld, %ld}) returned wrong values\n", \
(long)(ts1_sec), (long)(ts1_nsec), \
(long)(ts2_sec), (long)(ts2_nsec), \
(long)(ts3_sec), (long)(ts3_nsec)); \
printf(" Expected: {%ld, %ld}\n", (long)(expect_sec), (long)(expect_nsec)); \
printf(" Got: {%ld, %ld}\n", (long)(got.tv_sec), (long)(got.tv_nsec)); \
++result; \
} \
}
#define TEST_TEST_FUNC(func, ts1_sec, ts1_nsec, ts2_sec, ts2_nsec, expect) { \
struct timespec ts1 = { .tv_sec = ts1_sec, .tv_nsec = ts1_nsec }; \
struct timespec ts2 = { .tv_sec = ts2_sec, .tv_nsec = ts2_nsec }; \
int got = func(ts1, ts2); \
if(got != expect) \
{ \
printf("%s:%d: " #func "({%ld, %ld}, {%ld, %ld}) returned %d, expected %s\n", __FILE__, __LINE__, \
(long)(ts1_sec), (long)(ts1_nsec), (long)(ts2_sec), (long)(ts2_nsec), \
got, #expect); \
++result; \
} \
}
#define TEST_FROM_DOUBLE(d_secs, expect_sec, expect_nsec) { \
struct timespec got = timespec_from_double(d_secs); \
if(got.tv_sec != expect_sec || got.tv_nsec != expect_nsec) \
{ \
printf("%s:%d: timespec_from_double(%f) returned wrong values\n", __FILE__, __LINE__, (double)(d_secs)); \
printf(" Expected: {%ld, %ld}\n", (long)(expect_sec), (long)(expect_nsec)); \
printf(" Got: {%ld, %ld}\n", (long)(got.tv_sec), (long)(got.tv_nsec)); \
++result; \
} \
}
#define TEST_TO_DOUBLE(ts_sec, ts_nsec, expect) { \
struct timespec ts = { .tv_sec = ts_sec, .tv_nsec = ts_nsec }; \
double got = timespec_to_double(ts); \
if(got != expect) { \
printf("%s:%d: timespec_to_double({%ld, %ld}) returned wrong value\n", __FILE__, __LINE__, \
(long)(ts_sec), (long)(ts_nsec)); \
printf(" Expected: %f\n", (double)(expect)); \
printf(" Got: %f\n", got); \
++result; \
} \
}
#define TEST_FROM_TIMEVAL(in_sec, in_usec, expect_sec, expect_nsec) { \
struct timeval tv = { .tv_sec = in_sec, .tv_usec = in_usec }; \
struct timespec got = timespec_from_timeval(tv); \
if(got.tv_sec != expect_sec || got.tv_nsec != expect_nsec) \
{ \
printf("%s:%d: timespec_from_timeval({%ld, %ld}) returned wrong values\n", __FILE__, __LINE__, \
(long)(in_sec), (long)(in_usec)); \
printf(" Expected: {%ld, %ld}\n", (long)(expect_sec), (long)(expect_nsec)); \
printf(" Got: {%ld, %ld}\n", (long)(got.tv_sec), (long)(got.tv_nsec)); \
++result; \
} \
}
#define TEST_TO_TIMEVAL(ts_sec, ts_nsec, expect_sec, expect_usec) { \
struct timespec ts = { .tv_sec = ts_sec, .tv_nsec = ts_nsec }; \
struct timeval got = timespec_to_timeval(ts); \
if(got.tv_sec != expect_sec || got.tv_usec != expect_usec) \
{ \
printf("%s:%d: timespec_to_timeval({%ld, %ld}) returned wrong values\n", __FILE__, __LINE__, \
(long)(ts_sec), (long)(ts_nsec)); \
printf(" Expected: {%ld, %ld}\n", (long)(expect_sec), (long)(expect_usec)); \
printf(" Got: {%ld, %ld}\n", (long)(got.tv_sec), (long)(got.tv_usec)); \
++result; \
} \
}
#define TEST_FROM_MS(msecs, expect_sec, expect_nsec) { \
struct timespec got = timespec_from_ms(msecs); \
if(got.tv_sec != expect_sec || got.tv_nsec != expect_nsec) \
{ \
printf("%s:%d: timespec_from_ms(%ld) returned wrong values\n", __FILE__, __LINE__, (long)(msecs)); \
printf(" Expected: {%ld, %ld}\n", (long)(expect_sec), (long)(expect_nsec)); \
printf(" Got: {%ld, %ld}\n", (long)(got.tv_sec), (long)(got.tv_nsec)); \
++result; \
} \
}
#define TEST_TO_MS(ts_sec, ts_nsec, expect) { \
struct timespec ts = { .tv_sec = ts_sec, .tv_nsec = ts_nsec }; \
long got = timespec_to_ms(ts); \
if(got != expect) { \
printf("%s:%d: timespec_to_ms({%ld, %ld}) returned wrong value\n", __FILE__, __LINE__, \
(long)(ts_sec), (long)(ts_nsec)); \
printf(" Expected: %ld\n", (long)(expect)); \
printf(" Got: %ld\n", got); \
++result; \
} \
}
int main()
{
int result = 0;
// timespec_add
TEST_BINOP(timespec_add, 0,0, 0,0, 0,0);
TEST_BINOP(timespec_add, 0,0, 1,0, 1,0);
TEST_BINOP(timespec_add, 1,0, 0,0, 1,0);
TEST_BINOP(timespec_add, 1,0, 1,0, 2,0);
TEST_BINOP(timespec_add, 1,500000000, 1,0, 2,500000000);
TEST_BINOP(timespec_add, 1,0, 1,500000000, 2,500000000);
TEST_BINOP(timespec_add, 1,500000000, 1,500000000, 3,0);
TEST_BINOP(timespec_add, 1,500000000, 1,499999999, 2,999999999);
TEST_BINOP(timespec_add, 1,500000000, 1,500000000, 3,0);
TEST_BINOP(timespec_add, 1,999999999, 1,999999999, 3,999999998);
TEST_BINOP(timespec_add, 0,500000000, 1,500000000, 2,0);
TEST_BINOP(timespec_add, 1,500000000, 0,500000000, 2,0);
// timespec_sub
TEST_BINOP(timespec_sub, 0,0, 0,0, 0,0);
TEST_BINOP(timespec_sub, 1,0, 0,0, 1,0);
TEST_BINOP(timespec_sub, 1,0, 1,0, 0,0);
TEST_BINOP(timespec_sub, 1,500000000, 0,500000000, 1,0);
TEST_BINOP(timespec_sub, 5,500000000, 2,999999999, 2,500000001);
TEST_BINOP(timespec_sub, 0,0, 1,0, -1,0);
TEST_BINOP(timespec_sub, 0,500000000, 1,500000000, -1,0);
TEST_BINOP(timespec_sub, 0,0, 1,500000000, -2,500000000);
TEST_BINOP(timespec_sub, 1,0, 1,500000000, -1,500000000);
TEST_BINOP(timespec_sub, 1,0, 1,499999999, -1,500000001);
// timespec_mod
TEST_BINOP(timespec_mod, 0,0, 0,0, 0,0);
TEST_BINOP(timespec_mod, 0,0, 1,0, 0,0);
TEST_BINOP(timespec_mod, 1,0, 0,0, 1,0);
TEST_BINOP(timespec_mod, 1,0, 1,0, 0,0);
TEST_BINOP(timespec_mod, 10,0, 1,0, 0,0);
TEST_BINOP(timespec_mod, 10,0, 3,0, 1,0);
TEST_BINOP(timespec_mod, 10,0, -3,0, -2,0);
TEST_BINOP(timespec_mod, -10,0, 3,0, 2,0);
TEST_BINOP(timespec_mod, -10,0, -3,0, -1,0);
TEST_BINOP(timespec_mod, 10,0, 5,0, 0,0);
TEST_BINOP(timespec_mod, 10,0, -5,0, 0,0);
TEST_BINOP(timespec_mod, -10,0, 5,0, 0,0);
TEST_BINOP(timespec_mod, -10,0, -5,0, 0,0);
TEST_BINOP(timespec_mod, 1,500000000, 0,500000000, 0,0);
TEST_BINOP(timespec_mod, 5,500000000, 2,999999999, 2,500000001);
TEST_BINOP(timespec_mod, 0,500000000, 1,500000000, 0,500000000);
TEST_BINOP(timespec_mod, 0,0, 1,500000000, 0,0);
TEST_BINOP(timespec_mod, 1,0, 1,500000000, 1,0);
TEST_BINOP(timespec_mod, 1,0, 0,1, 0,0);
TEST_BINOP(timespec_mod, 1,123456789, 0,1000, 0,789);
TEST_BINOP(timespec_mod, 1,0, 0,9999999, 0,100);
TEST_BINOP(timespec_mod, 12345,54321, 0,100001, 0,5555);
TEST_BINOP(timespec_mod, LONG_MAX,0, 0,1, 0,0);
TEST_BINOP(timespec_mod, LONG_MAX,0, LONG_MAX,1, LONG_MAX,0);
// timespec_clamp
TEST_TRINOP(timespec_clamp, 0,0, 0,0, 0,0, 0,0);
TEST_TRINOP(timespec_clamp, 1000,0, 2000,0, 3000,0, 2000,0);
TEST_TRINOP(timespec_clamp, 1500,0, 2000,0, 3000,0, 2000,0);
TEST_TRINOP(timespec_clamp, 1999,0, 2000,0, 3000,0, 2000,0);
TEST_TRINOP(timespec_clamp, 2000,0, 2000,0, 3000,0, 2000,0);
TEST_TRINOP(timespec_clamp, 2001,0, 2000,0, 3000,0, 2001,0);
TEST_TRINOP(timespec_clamp, 2250,0, 2000,0, 3000,0, 2250,0);
TEST_TRINOP(timespec_clamp, 2500,0, 2000,0, 3000,0, 2500,0);
TEST_TRINOP(timespec_clamp, 2750,0, 2000,0, 3000,0, 2750,0);
TEST_TRINOP(timespec_clamp, 2999,0, 2000,0, 3000,0, 2999,0);
TEST_TRINOP(timespec_clamp, 3000,0, 2000,0, 3000,0, 3000,0);
TEST_TRINOP(timespec_clamp, 3001,0, 2000,0, 3000,0, 3000,0);
TEST_TRINOP(timespec_clamp, 3500,0, 2000,0, 3000,0, 3000,0);
TEST_TRINOP(timespec_clamp, 4000,0, 2000,0, 3000,0, 3000,0);
TEST_TRINOP(timespec_clamp, 0,1000, 0,2000, 0,3000, 0,2000);
TEST_TRINOP(timespec_clamp, 0,1500, 0,2000, 0,3000, 0,2000);
TEST_TRINOP(timespec_clamp, 0,1999, 0,2000, 0,3000, 0,2000);
TEST_TRINOP(timespec_clamp, 0,2000, 0,2000, 0,3000, 0,2000);
TEST_TRINOP(timespec_clamp, 0,2001, 0,2000, 0,3000, 0,2001);
TEST_TRINOP(timespec_clamp, 0,2250, 0,2000, 0,3000, 0,2250);
TEST_TRINOP(timespec_clamp, 0,2500, 0,2000, 0,3000, 0,2500);
TEST_TRINOP(timespec_clamp, 0,2750, 0,2000, 0,3000, 0,2750);
TEST_TRINOP(timespec_clamp, 0,2999, 0,2000, 0,3000, 0,2999);
TEST_TRINOP(timespec_clamp, 0,3000, 0,2000, 0,3000, 0,3000);
TEST_TRINOP(timespec_clamp, 0,3001, 0,2000, 0,3000, 0,3000);
TEST_TRINOP(timespec_clamp, 0,3500, 0,2000, 0,3000, 0,3000);
TEST_TRINOP(timespec_clamp, 0,4000, 0,2000, 0,3000, 0,3000);
TEST_TRINOP(timespec_clamp,0,-1000, 0,-3000, 0,-2000, 0,-2000);
TEST_TRINOP(timespec_clamp,0,-1500, 0,-3000, 0,-2000, 0,-2000);
TEST_TRINOP(timespec_clamp,0,-1999, 0,-3000, 0,-2000, 0,-2000);
TEST_TRINOP(timespec_clamp,0,-3000, 0,-3000, 0,-2000, 0,-3000);
TEST_TRINOP(timespec_clamp,0,-2001, 0,-3000, 0,-2000, 0,-2001);
TEST_TRINOP(timespec_clamp,0,-2250, 0,-3000, 0,-2000, 0,-2250);
TEST_TRINOP(timespec_clamp,0,-2500, 0,-3000, 0,-2000, 0,-2500);
TEST_TRINOP(timespec_clamp,0,-2750, 0,-3000, 0,-2000, 0,-2750);
TEST_TRINOP(timespec_clamp,0,-2999, 0,-3000, 0,-2000, 0,-2999);
TEST_TRINOP(timespec_clamp,0,-2000, 0,-3000, 0,-2000, 0,-2000);
TEST_TRINOP(timespec_clamp,0,-3001, 0,-3000, 0,-2000, 0,-3000);
TEST_TRINOP(timespec_clamp,0,-3500, 0,-3000, 0,-2000, 0,-3000);
TEST_TRINOP(timespec_clamp,0,-2000, 0,-3000, 0,-2000, 0,-2000);
TEST_TRINOP(timespec_clamp,0,-4000, 0,-3000, 0,3000, 0,-3000);
TEST_TRINOP(timespec_clamp,0,-3001, 0,-3000, 0,3000, 0,-3000);
TEST_TRINOP(timespec_clamp,0,-3000, 0,-3000, 0,3000, 0,-3000);
TEST_TRINOP(timespec_clamp,0,-2999, 0,-3000, 0,3000, 0,-2999);
TEST_TRINOP(timespec_clamp,0,-1500, 0,-3000, 0,3000, 0,-1500);
TEST_TRINOP(timespec_clamp,0, -1, 0,-3000, 0,3000, 0, -1);
TEST_TRINOP(timespec_clamp,0, 0, 0,-3000, 0,3000, 0, 0);
TEST_TRINOP(timespec_clamp,0, 1, 0,-3000, 0,3000, 0, 1);
TEST_TRINOP(timespec_clamp,0, 1500, 0,-3000, 0,3000, 0, 1500);
TEST_TRINOP(timespec_clamp,0, 2999, 0,-3000, 0,3000, 0, 2999);
TEST_TRINOP(timespec_clamp,0, 3000, 0,-3000, 0,3000, 0, 3000);
TEST_TRINOP(timespec_clamp,0, 3001, 0,-3000, 0,3000, 0, 3000);
TEST_TRINOP(timespec_clamp,0, 4000, 0,-3000, 0,3000, 0, 3000);
// timespec_min
TEST_BINOP(timespec_min, 0,0, 0,0, 0,0);
TEST_BINOP(timespec_min, 0,0, 1,0, 0,0);
TEST_BINOP(timespec_min, 1,0, 0,0, 0,0);
TEST_BINOP(timespec_min, 1,0, 1,0, 1,0);
TEST_BINOP(timespec_min, 10,0, 1,0, 1,0);
TEST_BINOP(timespec_min, 10,0, 3,0, 3,0);
TEST_BINOP(timespec_min, 10,0, -3,0, -3,0);
TEST_BINOP(timespec_min, -10,0, 3,0, -10,0);
TEST_BINOP(timespec_min, -10,0, -3,0, -10,0);
TEST_BINOP(timespec_min, 10,0, 5,0, 5,0);
TEST_BINOP(timespec_min, 10,0, -5,0, -5,0);
TEST_BINOP(timespec_min, -10,0, 5,0, -10,0);
TEST_BINOP(timespec_min, -10,0, -5,0, -10,0);
TEST_BINOP(timespec_min, 1,500000000, 0,500000000, 0,500000000);
TEST_BINOP(timespec_min, 5,500000000, 2,999999999, 2,999999999);
TEST_BINOP(timespec_min, 0,500000000, 1,500000000, 0,500000000);
TEST_BINOP(timespec_min, 0,0, 1,500000000, 0,0);
TEST_BINOP(timespec_min, 1,0, 1,500000000, 1,0);
TEST_BINOP(timespec_min, 1,0, 0,1, 0,1);
TEST_BINOP(timespec_min, 1,123456789, 0,1000, 0,1000);
TEST_BINOP(timespec_min, 1,0, 0,9999999, 0,9999999);
TEST_BINOP(timespec_min, 12345,54321, 0,100001, 0,100001);
TEST_BINOP(timespec_min, LONG_MIN,0, 0,1, LONG_MIN,0);
TEST_BINOP(timespec_min, LONG_MIN,0, 0,-1, LONG_MIN,0);
TEST_BINOP(timespec_min, LONG_MIN,0, LONG_MAX,0, LONG_MIN,0);
TEST_BINOP(timespec_min, LONG_MIN,0, LONG_MIN,0, LONG_MIN,0);
TEST_BINOP(timespec_min, LONG_MAX,0, 0,1, 0,1);
TEST_BINOP(timespec_min, LONG_MAX,0, 0,-1, 0,-1);
TEST_BINOP(timespec_min, LONG_MAX,0, LONG_MAX,0, LONG_MAX,0);
TEST_BINOP(timespec_min, LONG_MAX,0, LONG_MIN,0, LONG_MIN,0);
// timespec_max
TEST_BINOP(timespec_max, 0,0, 0,0, 0,0);
TEST_BINOP(timespec_max, 0,0, 1,0, 1,0);
TEST_BINOP(timespec_max, 1,0, 0,0, 1,0);
TEST_BINOP(timespec_max, 1,0, 1,0, 1,0);
TEST_BINOP(timespec_max, 10,0, 1,0, 10,0);
TEST_BINOP(timespec_max, 10,0, 3,0, 10,0);
TEST_BINOP(timespec_max, 10,0, -3,0, 10,0);
TEST_BINOP(timespec_max, -10,0, 3,0, 3,0);
TEST_BINOP(timespec_max, -10,0, -3,0, -3,0);
TEST_BINOP(timespec_max, 10,0, 5,0, 10,0);
TEST_BINOP(timespec_max, 10,0, -5,0, 10,0);
TEST_BINOP(timespec_max, -10,0, 5,0, 5,0);
TEST_BINOP(timespec_max, -10,0, -5,0, -5,0);
TEST_BINOP(timespec_max, 1,500000000, 0,500000000, 1,500000000);
TEST_BINOP(timespec_max, 5,500000000, 2,999999999, 5,500000000);
TEST_BINOP(timespec_max, 0,500000000, 1,500000000, 1,500000000);
TEST_BINOP(timespec_max, 0,0, 1,500000000, 1,500000000);
TEST_BINOP(timespec_max, 1,0, 1,500000000, 1,500000000);
TEST_BINOP(timespec_max, 1,0, 0,1, 1,0);
TEST_BINOP(timespec_max, 1,123456789, 0,1000, 1,123456789);
TEST_BINOP(timespec_max, 1,0, 0,9999999, 1,0);
TEST_BINOP(timespec_max, 12345,54321, 0,100001, 12345,54321);
TEST_BINOP(timespec_max, LONG_MIN,0, 0,1, 0,1);
TEST_BINOP(timespec_max, LONG_MIN,0, 0,-1, 0,-1);
TEST_BINOP(timespec_max, LONG_MIN,0, LONG_MAX,0, LONG_MAX,0);
TEST_BINOP(timespec_max, LONG_MIN,0, LONG_MIN,0, LONG_MIN,0);
TEST_BINOP(timespec_max, LONG_MAX,0, 0,1, LONG_MAX,0);
TEST_BINOP(timespec_max, LONG_MAX,0, 0,-1, LONG_MAX,0);
TEST_BINOP(timespec_max, LONG_MAX,0, LONG_MAX,0, LONG_MAX,0);
TEST_BINOP(timespec_max, LONG_MAX,0, LONG_MIN,0, LONG_MAX,0);
// timespec_cmp
TEST_TEST_FUNC(timespec_cmp, 0,0, 0,0, 0);
TEST_TEST_FUNC(timespec_cmp, 100,0, 100,0, 0);
TEST_TEST_FUNC(timespec_cmp, -100,0, -100,0, 0);
TEST_TEST_FUNC(timespec_cmp, 1,0, 0,0, 1);
TEST_TEST_FUNC(timespec_cmp, 0,0, 1,0, -1);
TEST_TEST_FUNC(timespec_cmp, 0,1, 0,0, 1);
TEST_TEST_FUNC(timespec_cmp, 0,0, 0,1, -1);
TEST_TEST_FUNC(timespec_cmp, 1,0, 0,100, 1);
TEST_TEST_FUNC(timespec_cmp, 0,100 , 1,0, -1);
TEST_TEST_FUNC(timespec_cmp, -0,-0, 0,0, 0);
TEST_TEST_FUNC(timespec_cmp, -10,-500000000, -11,500000000, 0);
TEST_TEST_FUNC(timespec_cmp, -10,-500000001, -11,499999999, 0);
TEST_TEST_FUNC(timespec_cmp, -10,-500000001, -11,500000001, -1);
TEST_TEST_FUNC(timespec_cmp, -11,500000001, -10,-500000001, 1);
// timespec_eq
TEST_TEST_FUNC(timespec_eq, 0,0, 0,0, true);
TEST_TEST_FUNC(timespec_eq, 100,0, 100,0, true);
TEST_TEST_FUNC(timespec_eq, -200,0, -200,0, true);
TEST_TEST_FUNC(timespec_eq, 0,300, 0,300, true);
TEST_TEST_FUNC(timespec_eq, 0,-400, 0,-400, true);
TEST_TEST_FUNC(timespec_eq, 100,1, 100,0, false);
TEST_TEST_FUNC(timespec_eq, 101,0, 100,0, false);
TEST_TEST_FUNC(timespec_eq, -100,0, 100,0, false);
TEST_TEST_FUNC(timespec_eq, 0,10, 0,-10, false);
TEST_TEST_FUNC(timespec_eq, -0,-0, 0,0, true);
TEST_TEST_FUNC(timespec_eq, -10,-500000000, -11,500000000, true);
TEST_TEST_FUNC(timespec_eq, -10,-500000001, -11,499999999, true);
TEST_TEST_FUNC(timespec_eq, -10,-500000001, -11,500000001, false);
// timespec_gt
TEST_TEST_FUNC(timespec_gt, 1,0, 0,0, true);
TEST_TEST_FUNC(timespec_gt, 0,0, -1,0, true);
TEST_TEST_FUNC(timespec_gt, 0,1, 0,0, true);
TEST_TEST_FUNC(timespec_gt, 0,0, 0,-1, true);
TEST_TEST_FUNC(timespec_gt, 1,0, 1,0, false);
TEST_TEST_FUNC(timespec_gt, 1,1, 1,1, false);
TEST_TEST_FUNC(timespec_gt, -1,0, 0,0, false);
TEST_TEST_FUNC(timespec_gt, 0,-1, 0,0, false);
TEST_TEST_FUNC(timespec_gt, 0,0, -0,-0, false);
TEST_TEST_FUNC(timespec_gt, -10,-500000000, -11,500000000, false);
TEST_TEST_FUNC(timespec_gt, -11,500000000, -10,-500000000, false);
TEST_TEST_FUNC(timespec_gt, -10,-500000001, -11,499999999, false);
TEST_TEST_FUNC(timespec_gt, -11,499999999, -11,499999999, false);
TEST_TEST_FUNC(timespec_gt, -10,-500000001, -11,500000001, false);
TEST_TEST_FUNC(timespec_gt, -11,500000001, -10,-500000001, true);
// timespec_ge
TEST_TEST_FUNC(timespec_ge, 1,0, 0,0, true);
TEST_TEST_FUNC(timespec_ge, 0,0, -1,0, true);
TEST_TEST_FUNC(timespec_ge, 0,1, 0,0, true);
TEST_TEST_FUNC(timespec_ge, 0,0, 0,-1, true);
TEST_TEST_FUNC(timespec_ge, 1,0, 1,0, true);
TEST_TEST_FUNC(timespec_ge, 1,1, 1,1, true);
TEST_TEST_FUNC(timespec_ge, -1,0, 0,0, false);
TEST_TEST_FUNC(timespec_ge, 0,-1, 0,0, false);
TEST_TEST_FUNC(timespec_ge, 0,0, -0,-0, true);
TEST_TEST_FUNC(timespec_ge, -10,-500000000, -11,500000000, true);
TEST_TEST_FUNC(timespec_ge, -11,500000000, -10,-500000000, true);
TEST_TEST_FUNC(timespec_ge, -10,-500000001, -11,499999999, true);
TEST_TEST_FUNC(timespec_ge, -11,499999999, -11,499999999, true);
TEST_TEST_FUNC(timespec_ge, -10,-500000001, -11,500000001, false);
TEST_TEST_FUNC(timespec_ge, -11,500000001, -10,-500000001, true);
// timespec_lt
TEST_TEST_FUNC(timespec_lt, 0,0, 1,0, true);
TEST_TEST_FUNC(timespec_lt, -1,0, 0,0, true);
TEST_TEST_FUNC(timespec_lt, 0,0, 0,1, true);
TEST_TEST_FUNC(timespec_lt, 0,-1, 0,0, true);
TEST_TEST_FUNC(timespec_lt, 1,0, 1,0, false);
TEST_TEST_FUNC(timespec_lt, 1,1, 1,1, false);
TEST_TEST_FUNC(timespec_lt, 0,0, -1,0, false);
TEST_TEST_FUNC(timespec_lt, 0,0, 0,-1, false);
TEST_TEST_FUNC(timespec_lt, 0,0, -0,-0, false);
TEST_TEST_FUNC(timespec_lt, -10,-500000000, -11,500000000, false);
TEST_TEST_FUNC(timespec_lt, -11,500000000, -10,-500000000, false);
TEST_TEST_FUNC(timespec_lt, -10,-500000001, -11,499999999, false);
TEST_TEST_FUNC(timespec_lt, -11,499999999, -11,499999999, false);
TEST_TEST_FUNC(timespec_lt, -10,-500000001, -11,500000001, true);
TEST_TEST_FUNC(timespec_lt, -11,500000001, -10,-500000001, false);
// timespec_le
TEST_TEST_FUNC(timespec_le, 0,0, 1,0, true);
TEST_TEST_FUNC(timespec_le, -1,0, 0,0, true);
TEST_TEST_FUNC(timespec_le, 0,0, 0,1, true);
TEST_TEST_FUNC(timespec_le, 0,-1, 0,0, true);
TEST_TEST_FUNC(timespec_le, 1,0, 1,0, true);
TEST_TEST_FUNC(timespec_le, 1,1, 1,1, true);
TEST_TEST_FUNC(timespec_le, 0,0, -1,0, false);
TEST_TEST_FUNC(timespec_le, 0,0, 0,-1, false);
TEST_TEST_FUNC(timespec_le, 0,0, -0,-0, true);
TEST_TEST_FUNC(timespec_le, -10,-500000000, -11,500000000, true);
TEST_TEST_FUNC(timespec_le, -11,500000000, -10,-500000000, true);
TEST_TEST_FUNC(timespec_le, -10,-500000001, -11,499999999, true);
TEST_TEST_FUNC(timespec_le, -11,499999999, -11,499999999, true);
TEST_TEST_FUNC(timespec_le, -10,-500000001, -11,500000001, true);
TEST_TEST_FUNC(timespec_le, -11,500000001, -10,-500000001, false);
// timespec_from_double
TEST_FROM_DOUBLE(0.0, 0,0);
TEST_FROM_DOUBLE(10.0, 10,0);
TEST_FROM_DOUBLE(-10.0, -10,0);
TEST_FROM_DOUBLE(0.5, 0,500000000);
TEST_FROM_DOUBLE(-0.5, -1,500000000);
TEST_FROM_DOUBLE(10.5, 10,500000000);
TEST_FROM_DOUBLE(-10.5, -11,500000000);
// timespec_to_double
TEST_TO_DOUBLE(0,0, 0.0);
TEST_TO_DOUBLE(10,0, 10.0);
TEST_TO_DOUBLE(-10,0, -10.0);
TEST_TO_DOUBLE(0,500000000, 0.5);
TEST_TO_DOUBLE(0,-500000000, -0.5);
TEST_TO_DOUBLE(10,500000000, 10.5);
TEST_TO_DOUBLE(10,-500000000, 9.5);
TEST_TO_DOUBLE(-10,500000000, -9.5);
TEST_TO_DOUBLE(-10,-500000000, -10.5);
// timespec_from_timeval
TEST_FROM_TIMEVAL(0,0, 0,0);
TEST_FROM_TIMEVAL(1,0, 1,0);
TEST_FROM_TIMEVAL(1000,0, 1000,0);
TEST_FROM_TIMEVAL(0,0, 0,0);
TEST_FROM_TIMEVAL(-1,0, -1,0);
TEST_FROM_TIMEVAL(-1000,0, -1000,0);
TEST_FROM_TIMEVAL(1,1, 1,1000);
TEST_FROM_TIMEVAL(1,1000, 1,1000000);
TEST_FROM_TIMEVAL(1,-1, 0,999999000);
TEST_FROM_TIMEVAL(1,-1000, 0,999000000);
TEST_FROM_TIMEVAL(-1,-1, -2,999999000);
TEST_FROM_TIMEVAL(-1,-1000, -2,999000000);
// timespec_to_timeval
TEST_TO_TIMEVAL(0,0, 0,0);
TEST_TO_TIMEVAL(1,0, 1,0);
TEST_TO_TIMEVAL(10,0, 10,0);
TEST_TO_TIMEVAL(-1,0, -1,0);
TEST_TO_TIMEVAL(-10,0, -10,0);
TEST_TO_TIMEVAL(1,1, 1,0);
TEST_TO_TIMEVAL(1,999, 1,0);
TEST_TO_TIMEVAL(1,1000, 1,1);
TEST_TO_TIMEVAL(1,1001, 1,1);
TEST_TO_TIMEVAL(1,2000, 1,2);
TEST_TO_TIMEVAL(1,2000000, 1,2000);
TEST_TO_TIMEVAL(1,-1, 0,999999);
TEST_TO_TIMEVAL(1,-999, 0,999999);
TEST_TO_TIMEVAL(1,-1000, 0,999999);
TEST_TO_TIMEVAL(1,-1001, 0,999998);
TEST_TO_TIMEVAL(1,-2000, 0,999998);
TEST_TO_TIMEVAL(1,-2000000, 0,998000);
TEST_TO_TIMEVAL(-1,-1, -2,999999);
TEST_TO_TIMEVAL(-1,-999, -2,999999);
TEST_TO_TIMEVAL(-1,-1000, -2,999999);
TEST_TO_TIMEVAL(-1,-1001, -2,999998);
TEST_TO_TIMEVAL(-1,-2000, -2,999998);
TEST_TO_TIMEVAL(-1,-2000000, -2,998000);
TEST_TO_TIMEVAL(1,1500000000, 2,500000);
TEST_TO_TIMEVAL(1,-1500000000, -1,500000);
TEST_TO_TIMEVAL(-1,-1500000000, -3,500000);
// timespec_from_ms
TEST_FROM_MS(0, 0,0);
TEST_FROM_MS(1, 0,1000000);
TEST_FROM_MS(-1, -1,999000000);
TEST_FROM_MS(1500, 1,500000000);
TEST_FROM_MS(-1000, -1,0);
TEST_FROM_MS(-1500, -2,500000000);
// timespec_to_ms
TEST_TO_MS(0,0, 0);
TEST_TO_MS(10,0, 10000);
TEST_TO_MS(-10,0, -10000);
TEST_TO_MS(0,500000000, 500);
TEST_TO_MS(0,-500000000, -500);
TEST_TO_MS(10,500000000, 10500);
TEST_TO_MS(10,-500000000, 9500);
TEST_TO_MS(-10,500000000, -9500);
TEST_TO_MS(-10,-500000000, -10500);
// timespec_normalise
TEST_NORMALISE(0,0, 0,0);
TEST_NORMALISE(0,1000000000, 1,0);
TEST_NORMALISE(0,1500000000, 1,500000000);
TEST_NORMALISE(0,-1000000000, -1,0);
TEST_NORMALISE(0,-1500000000, -2,500000000);
TEST_NORMALISE(5,1000000000, 6,0);
TEST_NORMALISE(5,1500000000, 6,500000000);
TEST_NORMALISE(-5,-1000000000, -6,0);
TEST_NORMALISE(-5,-1500000000, -7,500000000);
TEST_NORMALISE(0,2000000000, 2,0);
TEST_NORMALISE(0,2100000000, 2,100000000);
TEST_NORMALISE(0,-2000000000, -2,0);
TEST_NORMALISE(0,-2100000000, -3,900000000);
TEST_NORMALISE(1,-500000001, 0,499999999);
TEST_NORMALISE(1,-500000000, 0,500000000);
TEST_NORMALISE(1,-499999999, 0,500000001);
TEST_NORMALISE(0,-499999999, -1,500000001);
TEST_NORMALISE(-1,500000000, -1,500000000);
TEST_NORMALISE(-1,499999999, -1,499999999);
if(result > 0)
{
printf("%d tests failed\n", result);
}
else{
printf("All tests passed\n");
}
return !!result; /* Don't overflow the exit status */
}
#endif