Initial commit

This commit is contained in:
Alcaro 2016-07-13 18:44:41 +02:00
commit 9335f0b024
149 changed files with 107169 additions and 0 deletions

6
Makefile Normal file
View File

@ -0,0 +1,6 @@
PROGRAM = floating
ARGUI = 1
ARWUTF = 1
EXTRAOBJ += obj/divsufsort-c$(OBJSUFFIX).o
include arlib/Makefile

226
arlib/Makefile Normal file
View File

@ -0,0 +1,226 @@
all: $(PROGRAM)_dummy
ifeq ($(OS),Windows_NT)
OS = windows
#$(error objdump something and check which sections can be nuked)
else
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Linux)
OS = linux
endif
ifeq ($(UNAME_S),Darwin)
OS = osx
endif
endif
ARGUI ?= 0
ARTHREAD ?= 0
ARSANDBOX ?= 0
ARWUTF ?= 0
ARSOCKET ?= 0
ARSOCKET_SSL ?= openssl
DEBUG ?= 1
CC = gcc
CFLAGS =
CXX = g++
CXXFLAGS = $(CFLAGS)
LD = g++
LFLAGS =
OBJSUFFIX =
EXESUFFIX =
EXTRAOBJ ?=
CONF_CXXFLAGS += $(CONF_CFLAGS)
ifeq ($(OS),linux)
CONF_LFLAGS += -ldl
ifeq ($(ARTHREADS),1)
CONF_CFLAGS += -pthread
CONF_LFLAGS += -pthread
endif
OBJSUFFIX = -linux
endif
## function rwildcard(directory, pattern)
## mostly stolen from bsnes, but slightly improved (can use . as a directory)
#rwildcard = \
# $(strip \
# $(warning 1 1 $1 : 2 $2 : c $(if $(strip $1),$1,.)) \
# $(filter $(if $2,$2,%), \
# $(foreach f, \
# $(wildcard $(if $(strip $1),$1,.)/*), \
# $(eval t = $(call rwildcard,$f)) \
# $(warning 2 t $t : f $f : 1 $1 : 2 $2) \
# $(if $t,$t,$f) \
# ) \
# ) \
# )
ifeq ($(OS),windows)
EXESUFFIX = .exe
# EXTRAOBJ = obj/resource$(OBJSUFFIX).o
# RC = windres
# RCFLAGS =
#obj/resource$(OBJSUFFIX).o: ico/*
# $(RC) $(RCFLAGS) ico/minir.rc obj/resource$(OBJSUFFIX).o
OBJSUFFIX = -windows
endif
OPTFLAGS := -Os -fomit-frame-pointer -fmerge-all-constants -fvisibility=hidden
OPTFLAGS += -fno-exceptions -fno-unwind-tables -fno-asynchronous-unwind-tables
OPTFLAGS += -ffunction-sections -fdata-sections
OPTFLAGS += -Werror
ifeq ($(OPT),1)
CFLAGS += $(OPTFLAGS)
LFLAGS += -Wl,--gc-sections -s
DEBUG = 0
endif
ifeq ($(DEBUG),1)
CFLAGS += -g -DDEBUG
endif
ifeq ($(PROFILE),gen)
CONF_CFLAGS += -fprofile-generate
CONF_LFLAGS += -lgcov
endif
ifeq ($(PROFILE),use)
CONF_CFLAGS += -fprofile-use -fprofile-correction
endif
OUTNAME = $(PROGRAM)$(EXESUFFIX)
#stolen from http://stackoverflow.com/questions/22586084/makefile-with-multiple-rules-sharing-same-recipe-with-patternrules
define ADDDIR_CORE
$(eval OBJPREFIX := obj/_arlib_$(subst /,_,$(1))_)
OBJS += $(patsubst $(1)/%.cpp,$(OBJPREFIX)%$(OBJSUFFIX).o,$(wildcard $(1)/*.cpp))
$(OBJPREFIX)%$(OBJSUFFIX).o: $(1)/%.cpp | obj
$$(CXX) $$(TRUE_CXXFLAGS) -c $$< -o $$@
endef
define ADDDIR
$(eval $(call ADDDIR_CORE,$(1)))
endef
OBJS := $(patsubst %.cpp,obj/%$(OBJSUFFIX).o,$(wildcard *.cpp)) $(EXTRAOBJ)
# obj/miniz$(OBJSUFFIX).o
$(call ADDDIR,arlib)
ifeq ($(ARGUI),1)
$(call ADDDIR,arlib/gui)
ifeq ($(OS),windows)
CONF_CFLAGS += -DARGUI_WINDOWS
CONF_LFLAGS += -lgdi32 -lcomctl32 -lcomdlg32 -ldinput8 -ldxguid -lopengl32
endif
ifeq ($(OS),linux)
CONF_CFLAGS += $(shell pkg-config --cflags gtk+-3.0) -DARGUI_GTK3 -DARGUIPROT_X11
CONF_LFLAGS += -ldl -lX11 -lGL -lXi -lXext $(shell pkg-config --libs gtk+-3.0)
endif
else
CONF_CFLAGS += -DARGUI_NONE
endif
ifeq ($(ARTHREAD),1)
$(call ADDDIR,arlib/thread)
CONF_CFLAGS += -DARLIB_THREAD
ifeq ($(OS),linux)
CONF_CFLAGS += -pthread
CONF_LFLAGS += -pthread
endif
endif
ifeq ($(ARSANDBOX),1)
$(call ADDDIR,arlib/sandbox)
CONF_CFLAGS += -DARLIB_SANDBOX
#not true since the windows sandbox isn't a real sandbox
#ifeq ($(OS),windows)
# #both sandbox and WuTF need to redirect functions; this implementation is in WuTF
# #if linux ends up needing it too, I'll split out the redirector to a top-level Arlib file
# #Arlib is designed for use with -Wl,--gc-sections anyways
# ARWUTF = 1
#endif
endif
ifeq ($(ARWUTF),1)
$(call ADDDIR,arlib/wutf)
CONF_CFLAGS += -DARLIB_WUTF
endif
ifeq ($(ARSOCKET),1)
$(call ADDDIR,arlib/socket)
CONF_CFLAGS += -DARLIB_SOCKET
ifeq ($(OS),windows)
CONF_LFLAGS += -lws2_32
endif
ifeq ($(ARSOCKET_SSL),no)
#no SSL
#socketssl will still be available at compile time, but fails linking
else ifeq ($(OS),windows)
CONF_CFLAGS += -DARLIB_SSL_SCHANNEL
CONF_LFLAGS += -lcrypt32 -lsecur32
#not sure if these are needed, looks like mingw bug workarounds that were probably relevant four years ago
#CONF_LFLAGS += lib/crypt32.exp -l:lib/crypt32.lib -Wl,--enable-stdcall-fixup
else ifeq ($(ARSOCKET_SSL),wolfssl)
WOLFSSL_DIR = arlib/socket/wolfssl-3.9.0
CONF_CFLAGS += -DARLIB_SSL_WOLFSSL -I$(WOLFSSL_DIR)
OBJS += obj/_arlib_sp_wolfssl$(OBJSUFFIX).o
#CFLAGS += $(OPTFLAGS)
else ifeq ($(ARSOCKET_SSL),openssl)
CONF_CFLAGS += -DARLIB_SSL_OPENSSL
CONF_LFLAGS += -lssl -lcrypto
else ifeq ($(ARSOCKET_SSL),tlse)
CONF_CFLAGS += -DARLIB_SSL_TLSE
OBJS += obj/_arlib_sp_tlse$(OBJSUFFIX).o
else
$(error unknown SSL library)
endif
endif
CCXXFLAGS = -fvisibility=hidden -fno-exceptions -Wall -Wno-comment
TRUE_CFLAGS = -std=c99 $(CCXXFLAGS) $(CFLAGS) $(CONF_CFLAGS)
TRUE_CXXFLAGS =-std=c++11 -fno-rtti $(CCXXFLAGS) $(CXXFLAGS) $(CONF_CXXFLAGS)
TRUE_LFLAGS = $(LFLAGS) -fvisibility=hidden $(CONF_LFLAGS)
#double gcc bug combo:
#(1) GCC hates this pattern:
#//define foo(a,b,c) \
#// bar(a) \
#// bar(b) \
#// bar(c)
#(2) https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53431 says '#pragma GCC diagnostic ignored "-Wcomment"' does nothing
TRUE_CFLAGS += -Wno-comment
TRUE_CXXFLAGS += -Wno-comment
#On Windows, cleaning up the object directory is expected to be done with 'del /q obj\*' in a batch script.
clean:
rm obj/* || true
clean-prof:
rm obj/*.o || true
obj:
mkdir obj
obj/miniz$(OBJSUFFIX).o: miniz.c | obj
$(CC) $(TRUE_CFLAGS) -c $< -o $@
obj/_arlib_sp_wolfssl$(OBJSUFFIX).o: arlib/socket/wolfssl-lib.c | obj
$(CC) -DARLIB_SSL_WOLFSSL_SP $(TRUE_CFLAGS) $(OPTFLAGS) -c $< -o $@
obj/_arlib_sp_tlse$(OBJSUFFIX).o: arlib/socket/tlse.c | obj
$(CC) -DTLSE_IMPL -DTLS_AMALGAMATION -D_XOPEN_SOURCE=600 $(TRUE_CFLAGS) -w -c $< -o $@
obj/%-c$(OBJSUFFIX).o: %.c | obj
$(CC) $(TRUE_CFLAGS) -c $< -o $@
obj/%$(OBJSUFFIX).o: %.cpp | obj
$(CXX) $(TRUE_CXXFLAGS) -c $< -o $@
$(OUTNAME): $(OBJS)
$(LD) $+ $(TRUE_LFLAGS) -o $@ -lm
$(PROGRAM)_dummy: $(OUTNAME)

27
arlib/arlib.h Normal file
View File

@ -0,0 +1,27 @@
#pragma once
#include "containers.h"
#include "endian.h"
#include "file.h"
#include "function.h"
#include "intwrap.h"
#include "os.h"
#include "string.h"
//not in #ifdef, there's a check inside that header
#include "thread/thread.h"
#if !defined(ARGUI_NONE) && !defined(ARGUI_WIN32) && !defined(ARGUI_GTK3)
#define ARGUI_NONE
#endif
#ifndef ARGUI_NONE
#include "gui/window.h"
#endif
#ifdef ARLIB_WUTF
#include "wutf.h"
#endif
#ifdef ARLIB_SANDBOX
#include "sandbox.h"
#endif
#ifdef ARLIB_SOCKET
#include "socket.h"
#endif

270
arlib/array.h Normal file
View File

@ -0,0 +1,270 @@
#pragma once
#include "global.h"
#include <new>
#include <string.h>
//as much fun as it is to optimize the hell out of random stuff, I want to get things done as well
//size: two pointers
//this object does not own its storage, it's just a pointer wrapper
template<typename T> class arrayview {
protected:
class null_only;
T * items;
size_t count;
//void clone(const arrayview<T>& other)
//{
// this->count=other.count;
// this->items=other.items;
//}
public:
const T& operator[](size_t n) const { return items[n]; }
const T* ptr() const { return items; }
size_t len() const { return count; }
operator bool() { return items; }
arrayview()
{
this->items=NULL;
this->count=0;
}
arrayview(const null_only*)
{
this->items=NULL;
this->count=0;
}
arrayview(const T * ptr, size_t count)
{
this->items = (T*)ptr;
this->count = count;
}
template<size_t N> arrayview(const T (&ptr)[N])
{
this->items = (T*)ptr;
this->count = N;
}
//arrayview(const arrayview<T>& other)
//{
// clone(other);
//}
//arrayview<T> operator=(const arrayview<T>& other)
//{
// clone(other);
// return *this;
//}
};
//size: two pointers, plus one T per item
//this one owns its storage
template<typename T> class array : public arrayview<T> {
//T * items;
//size_t count;
void clone(const array<T>& other)
{
this->count=other.count;
this->items=malloc(sizeof(T)*bitround(this->count));
for (size_t i=0;i<this->count;i++) new(&this->items[i]) T(other.items[i]);
}
void swap(array<T>& other)
{
T* newitems = other.items;
size_t newcount = other.count;
other.items = this->items;
other.count = this->count;
this->items = newitems;
this->count = newcount;
}
void resize_grow(size_t count)
{
if (this->count >= count) return;
size_t bufsize_pre=bitround(this->count);
size_t bufsize_post=bitround(count);
if (bufsize_pre != bufsize_post) this->items=realloc(this->items, sizeof(T)*bufsize_post);
for (size_t i=this->count;i<count;i++)
{
new(&this->items[i]) T();
}
this->count=count;
}
void resize_shrink(size_t count)
{
if (this->count < count) return;
for (size_t i=count;i<this->count;i++)
{
this->items[i].~T();
}
size_t bufsize_pre=bitround(this->count);
size_t bufsize_post=bitround(count);
if (bufsize_pre != bufsize_post) this->items=realloc(this->items, sizeof(T)*bufsize_post);
this->count=count;
}
void resize_to(size_t count)
{
if (count > this->count) resize_grow(count);
else resize_shrink(count);
}
public:
T& operator[](size_t n) { resize_grow(n+1); return this->items[n]; }
const T& operator[](size_t n) const { return this->items[n]; }
T* ptr() { return this->items; }
void resize(size_t len) { resize_to(len); }
T join() const
{
T out = this->items[0];
for (size_t n=1;n<this->count;n++)
{
out += this->items[n];
}
return out;
}
T join(T between) const
{
T out = this->items[0];
for (size_t n=1;n < this->count;n++)
{
out += between;
out += this->items[n];
}
return out;
}
T join(char between) const
{
T out = this->items[0];
for (size_t n=1;n<this->count;n++)
{
out += between;
out += this->items[n];
}
return out;
}
void append(const T& item) { size_t pos = this->count; resize_grow(pos+1); this->items[pos] = item; }
void reset() { resize_shrink(0); }
arrayview<T> slice(size_t first, size_t count) { return arrayview<T>(this->items+first, this->count); }
array()
{
this->items=NULL;
this->count=0;
}
array(const array<T>& other)
{
clone(other);
}
#ifdef HAVE_MOVE
array(array<T>&& other)
{
swap(other);
}
#endif
array<T> operator=(array<T> other)
{
swap(other);
return *this;
}
static array<T> create_from(T* ptr, size_t count)
{
array<T> ret;
ret.items = ptr;
ret.count = count;
return ret;
}
~array()
{
for (size_t i=0;i<this->count;i++) this->items[i].~T();
free(this->items);
}
};
template<> class array<bool> {
protected:
class null_only;
uint8_t* bits;
size_t nbits;
class entry {
array<bool>& parent;
size_t index;
public:
operator bool() const { return parent.get(index); }
entry& operator=(bool val) { parent.set(index, val); return *this; }
entry(array<bool>& parent, size_t index) : parent(parent), index(index) {}
};
friend class entry;
bool get(size_t n) const
{
if (n >= nbits) return false;
return bits[n/8]>>(n&7) & 1;
}
void set(size_t n, bool val)
{
if (n >= nbits)
{
size_t prevbytes = bitround((nbits+7)/8);
size_t newbytes = bitround((n+8)/8);
if (newbytes > prevbytes)
{
bits = realloc(bits, newbytes);
memset(bits+prevbytes, 0, newbytes-prevbytes);
}
nbits = n+1;
}
uint8_t& byte = bits[n/8];
byte &=~ (1<<(n&7));
byte |= (val<<(n&7));
}
public:
bool operator[](size_t n) const { return get(n); }
entry operator[](size_t n) { return entry(*this, n); }
size_t len() const { return nbits; }
void reset()
{
free(this->bits);
this->bits = NULL;
this->nbits = 0;
}
array()
{
this->bits = NULL;
this->nbits = 0;
}
~array()
{
free(this->bits);
}
};

9
arlib/containers.h Normal file
View File

@ -0,0 +1,9 @@
#pragma once
//#include "global.h"
//#include <string.h> // strdup
//#include <new>
#include "array.h"
//#include "fifo.h"
//#include "hashmap.h"
//#include "multiint.h"

21
arlib/crc32.cpp Normal file
View File

@ -0,0 +1,21 @@
#include "crc32.h"
static const uint32_t crctable_4bits[]={
0x00000000, 0x1DB71064, 0x3B6E20C8, 0x26D930AC, 0x76DC4190, 0x6B6B51F4, 0x4DB26158, 0x5005713C,
0xEDB88320, 0xF00F9344, 0xD6D6A3E8, 0xCB61B38C, 0x9B64C2B0, 0x86D3D2D4, 0xA00AE278, 0xBDBDF21C,
};
uint32_t crc32_update(const uint8_t* data, size_t len, uint32_t crc)
{
crc = ~crc;
for (size_t i=0;i<len;i++)
{
crc = crctable_4bits[(crc^ data[i] )&0x0F] ^ (crc>>4);
crc = crctable_4bits[(crc^(data[i]>>4))&0x0F] ^ (crc>>4);
}
return ~crc;
}
uint32_t crc32(const uint8_t* data, size_t len)
{
return crc32_update(data, len, 0);
}

4
arlib/crc32.h Normal file
View File

@ -0,0 +1,4 @@
#include "global.h"
uint32_t crc32(const uint8_t* data, size_t len);
uint32_t crc32_update(const uint8_t* data, size_t len, uint32_t crc);

99
arlib/dylib.cpp Normal file
View File

@ -0,0 +1,99 @@
#include "os.h"
#include "thread.h"
#include <stdlib.h>
#ifdef __unix__
#include <dlfcn.h>
static mutex dylib_lock;
dylib* dylib::create(const char * filename, bool * owned)
{
dylib_lock.lock();
dylib* ret=NULL;
if (owned)
{
ret=(dylib*)dlopen(filename, RTLD_LAZY|RTLD_NOLOAD);
*owned=(!ret);
if (ret) return ret;
}
if (!ret) ret=(dylib*)dlopen(filename, RTLD_LAZY);
dylib_lock.unlock();
return ret;
}
void* dylib::sym_ptr(const char * name)
{
return dlsym((void*)this, name);
}
funcptr dylib::sym_func(const char * name)
{
funcptr ret;
*(void**)(&ret)=dlsym((void*)this, name);
return ret;
}
void dylib::release()
{
dlclose((void*)this);
}
#endif
#ifdef _WIN32
#undef bind
#include <windows.h>
#define bind bind_func
static mutex dylib_lock;
dylib* dylib::create(const char * filename, bool * owned)
{
dylib_lock.lock();
dylib* ret=NULL;
if (owned)
{
if (!GetModuleHandleEx(0, filename, (HMODULE*)&ret)) ret=NULL;
*owned=(!ret);
//Windows may be able to force load a DLL twice using ntdll!LdrLoadDll
// <https://github.com/wine-mirror/wine/blob/master/dlls/ntdll/loader.c#L2324>
//but Linux can't, and calling ntdll is generally discouraged, so I'm not using that.
}
if (!ret)
{
//this is so weird dependencies, for example winpthread-1.dll, can be placed beside the dll where they belong
char * filename_copy=strdup(filename);
char * filename_copy_slash=strrchr(filename_copy, '/');
if (!filename_copy_slash) filename_copy_slash=strrchr(filename_copy, '\0');
filename_copy_slash[0]='\0';
SetDllDirectory(filename_copy);
free(filename_copy);
ret=(dylib*)LoadLibrary(filename);
SetDllDirectory(NULL);
}
dylib_lock.unlock();
return ret;
}
void* dylib::sym_ptr(const char * name)
{
return (void*)GetProcAddress((HMODULE)this, name);
}
funcptr dylib::sym_func(const char * name)
{
return (funcptr)GetProcAddress((HMODULE)this, name);
}
void dylib::release()
{
FreeLibrary((HMODULE)this);
}
#endif

178
arlib/endian.h Normal file
View File

@ -0,0 +1,178 @@
#pragma once
#include "global.h"
#include "intwrap.h"
#include <stdint.h>
//This one defines:
//Macros END_LITTLE, END_BIG and ENDIAN; ENDIAN is equal to one of the other two. The test is borrowed from byuu's nall.
//end_swap() - Byteswaps an integer.
//end_nat_to_le(), end_le_to_nat(), end_nat_to_be(), end_be_to_nat() - Byteswaps an integer or returns it unmodified, depending on the host endianness.
//Class litend<> and bigend<> - Acts like the given integer type, but is stored by the named endianness internally. Safe to memcpy() and fwrite().
#define END_LITTLE 0x04030201
#define END_BIG 0x01020304
#if (defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && __BYTE_ORDER == __LITTLE_ENDIAN) || defined(__LITTLE_ENDIAN__) || \
defined(__i386__) || defined(__amd64__) || \
defined(_M_IX86) || defined(_M_AMD64) || \
defined(__ARM_EABI__) || defined(__arm__)
#define ENDIAN END_LITTLE
#define BIGEND_SWAP1(a) a // pointless, but for consistency
#define BIGEND_SWAP2(a,b) a b
#define BIGEND_SWAP3(a,b,c) a b c
#define BIGEND_SWAP4(a,b,c,d) a b c d
#define BIGEND_SWAP5(a,b,c,d,e) a b c d e
#define BIGEND_SWAP6(a,b,c,d,e,f) a b c d e f
#define BIGEND_SWAP7(a,b,c,d,e,f,g) a b c d e f g
#define BIGEND_SWAP8(a,b,c,d,e,f,g,h) a b c d e f g h
#elif (defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) && __BYTE_ORDER == __BIG_ENDIAN) || defined(__BIG_ENDIAN__) || \
defined(__powerpc__) || defined(_M_PPC)
#define ENDIAN END_BIG
#define BIGEND_SWAP1(a) a
#define BIGEND_SWAP2(a,b) b a
#define BIGEND_SWAP3(a,b,c) c b a
#define BIGEND_SWAP4(a,b,c,d) d c b a
#define BIGEND_SWAP5(a,b,c,d,e) e d c b a
#define BIGEND_SWAP6(a,b,c,d,e,f) f e d c b a
#define BIGEND_SWAP7(a,b,c,d,e,f,g) g f e d c b a
#define BIGEND_SWAP8(a,b,c,d,e,f,g,h) h g f e d c b a
#else
#error please define your endianness
#endif
#if defined(__GNUC__)
//This one is mostly useless (GCC detects the pattern and optimizes it).
//However, MSVC doesn't, so I need the intrinsics. Might as well use both sets.
static inline uint8_t end_swap(uint8_t n) { return n; }
static inline uint16_t end_swap(uint16_t n) { return __builtin_bswap16(n); }
static inline uint32_t end_swap(uint32_t n) { return __builtin_bswap32(n); }
static inline uint64_t end_swap(uint64_t n) { return __builtin_bswap64(n); }
#elif defined(_MSC_VER)
static inline uint8_t end_swap(uint8_t n) { return n; }
static inline uint16_t end_swap(uint16_t n) { return _byteswap_ushort(n); }
static inline uint32_t end_swap(uint32_t n) { return _byteswap_ulong(n); }
static inline uint64_t end_swap(uint64_t n) { return _byteswap_uint64(n); }
#else
static inline uint8_t end_swap(uint8_t n) { return n; }
static inline uint16_t end_swap(uint16_t n) { return n>>8 | n<<8; }
static inline uint32_t end_swap(uint32_t n)
{
n = n>>16 | n<<16;
n = (n&0x00FF00FF)<<8 | (n&0xFF00FF00)>>8;
return n;
}
static inline uint64_t end_swap(uint64_t n)
{
n = n>>32 | n<<32;
n = (n&0x0000FFFF0000FFFF)<<16 | (n&0xFFFF0000FFFF0000)>>16;
n = (n&0x00FF00FF00FF00FF)<<8 | (n&0xFF00FF00FF00FF00)>>8 ;
return n;
}
#endif
static inline int8_t end_swap(int8_t n) { return (int8_t )end_swap((uint8_t )n); }
static inline int16_t end_swap(int16_t n) { return (int16_t)end_swap((uint16_t)n); }
static inline int32_t end_swap(int32_t n) { return (int32_t)end_swap((uint32_t)n); }
static inline int64_t end_swap(int64_t n) { return (int64_t)end_swap((uint64_t)n); }
#ifdef ENDIAN
#if ENDIAN == END_LITTLE
template<typename T> static inline T end_nat_to_le(T val) { return val; }
template<typename T> static inline T end_nat_to_be(T val) { return end_swap(val); }
template<typename T> static inline T end_le_to_nat(T val) { return val; }
template<typename T> static inline T end_be_to_nat(T val) { return end_swap(val); }
#elif ENDIAN == END_BIG
template<typename T> static inline T end_nat_to_le(T val) { return end_swap(val); }
template<typename T> static inline T end_nat_to_be(T val) { return val; }
template<typename T> static inline T end_le_to_nat(T val) { return end_swap(val); }
template<typename T> static inline T end_be_to_nat(T val) { return val; }
#endif
template<typename T, bool little> class endian_core
{
T val;
public:
operator T()
{
if (little == (ENDIAN==END_LITTLE)) return val;
else return end_swap(val);
}
void operator=(T newval)
{
if (little == (ENDIAN==END_LITTLE)) val = newval;
else val = end_swap(newval);
}
};
#else
//This one doesn't optimize properly. While it does get unrolled, it remains as four byte loads, and some shift/or.
template<typename T, bool little> class endian_core
{
union {
T align;
uint8_t bytes[sizeof(T)];
};
public:
operator T()
{
if (little)
{
T ret=0;
for (size_t i=0;i<sizeof(T);i++)
{
ret = (ret<<8) | bytes[i];
}
return ret;
}
else
{
T ret=0;
for (size_t i=0;i<sizeof(T);i++)
{
ret = (ret<<8) | bytes[sizeof(T)-1-i];
}
return ret;
}
}
void operator=(T newval)
{
if ((little && ENDIAN==END_LITTLE) || (!little && ENDIAN==END_BIG))
{
val = newval;
return;
}
if (!little)
{
for (size_t i=0;i<sizeof(T);i++)
{
bytes[sizeof(T)-1-i]=(newval&0xFF);
newval>>=8;
}
}
else
{
for (size_t i=0;i<sizeof(T);i++)
{
bytes[i]=(newval&0xFF);
newval>>=8;
}
}
}
};
#endif
template<typename T> class bigend : public intwrap<endian_core<T, false>, T> {
public:
bigend() {}
bigend(T i) : intwrap<endian_core<T, false>, T>(i) {} // why does C++ need so much irritating cruft
};
template<typename T> class litend : public intwrap<endian_core<T, true>, T> {
public:
litend() {}
litend(T i) : intwrap<endian_core<T, true>, T>(i) {}
};

302
arlib/file-posix.cpp Normal file
View File

@ -0,0 +1,302 @@
#include "file.h"
#include "os.h"
#include "thread.h"
#define MMAP_THRESHOLD 128*1024
#ifdef __unix__
#include <unistd.h>
//#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/mman.h>
//other platforms: http://stackoverflow.com/questions/1023306/finding-current-executables-path-without-proc-self-exe
const char * window_get_proc_path()
{
//we could lstat it, but apparently that just returns zero on /proc on Linux.
ssize_t bufsize=64;
static char * linkname=NULL;
if (linkname) return linkname;
while (true)
{
linkname=malloc(bufsize);
ssize_t r=readlink("/proc/self/exe", linkname, bufsize);
if (r<0 || r>=bufsize)
{
free(linkname);
if (r<0) return NULL;
bufsize*=2;
continue;
}
linkname[r]='\0';
char * end=strrchr(linkname, '/');
if (end) *end='\0';
return linkname;
}
}
static void window_cwd_enter(const char * dir);
static void window_cwd_leave();
char * _window_native_get_absolute_path(const char * basepath, const char * path, bool allow_up)
{
if (!basepath || !path) return NULL;
const char * filepart=strrchr(basepath, '/');
if (!filepart) return NULL;
char * basedir=strndup(basepath, filepart+1-basepath);
window_cwd_enter(basedir);
char * ret=realpath(path, NULL);
window_cwd_leave();
if (!allow_up && ret && strncasecmp(basedir, ret, filepart+1-basepath)!=0)
{
free(ret);
ret=NULL;
}
free(basedir);
return ret;
}
static const char * cwd_init;
static const char * cwd_bogus;
static mutex cwd_mutex;
static void window_cwd_enter(const char * dir)
{
cwd_mutex.lock();
char * cwd_bogus_check=getcwd(NULL, 0);
if (strcmp(cwd_bogus, cwd_bogus_check)!=0) abort();//if this fires, someone changed the directory without us knowing - not allowed. cwd belongs to the frontend.
free(cwd_bogus_check);
ignore(chdir(dir));
}
static void window_cwd_leave()
{
ignore(chdir(cwd_bogus));
cwd_mutex.unlock();
}
const char * window_get_cwd()
{
return cwd_init;
}
void _window_init_file()
{
char * cwd_init_tmp=getcwd(NULL, 0);
char * cwdend=strrchr(cwd_init_tmp, '/');
if (!cwdend) cwd_init="/";
else if (cwdend[1]=='/') cwd_init=cwd_init_tmp;
else
{
size_t cwdlen=strlen(cwd_init_tmp);
char * cwd_init_fixed=malloc(cwdlen+1+1);
memcpy(cwd_init_fixed, cwd_init_tmp, cwdlen);
cwd_init_fixed[cwdlen+0]='/';
cwd_init_fixed[cwdlen+1]='\0';
cwd_init=cwd_init_fixed;
free(cwd_init_tmp);
}
//try a couple of useless directories and hope one of them works
//this seems to be the best one:
//- even root can't create files here
//- it contains no files with a plausible name on a standard Ubuntu box (I have an ath9k-phy0, nothing will ever want that filename)
//- a wild write will not do anything dangerous except turn on some lamps
!chdir("/sys/class/leds/") ||
//the rest are in case it's not accessible (weird chroot? not linux?), so try some random things
!chdir("/sys/") ||
!chdir("/dev/") ||
!chdir("/home/") ||
!chdir("/tmp/") ||
!chdir("/");
cwd_bogus = getcwd(NULL, 0);//POSIX does not specify getcwd(NULL), it's Linux-specific
}
/*
static void* file_alloc(int fd, size_t len, bool writable)
{
if (len <= MMAP_THRESHOLD)
{
uint8_t* data=malloc(len+1);
pread(fd, data, len, 0);
data[len]='\0';
return data;
}
else
{
void* data=mmap(NULL, len+1, writable ? (PROT_READ|PROT_WRITE) : PROT_READ, MAP_SHARED, fd, 0);
if (data==MAP_FAILED) return NULL;
if (len % sysconf(_SC_PAGESIZE) == 0)
{
mmap((char*)data + len, 1, PROT_READ, MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, -1, 0);
}
return data;
}
}
*/
static long pagesize() { return sysconf(_SC_PAGESIZE); }
namespace {
class file_fs : public file {
int fd;
public:
//do not use the file pointer, dup() doesn't clone that one
file_fs(const char * filename, int fd, size_t len) : file(filename) { this->fd=fd; this->len=len; }
file* clone() { return new file_fs(this->filename, dup(this->fd), this->len); }
size_t read(void* target, size_t start, size_t len)
{
ssize_t ret = pread(fd, target, len, start);
if (ret < 0) return 0;
else return ret;
}
void* mmap(size_t start, size_t len)
{
size_t offset = start % pagesize();
void* data=::mmap(NULL, len+offset, PROT_READ, MAP_SHARED, this->fd, start-offset);
if (data==MAP_FAILED) return NULL;
return (char*)data+offset;
}
void unmap(const void* data, size_t len)
{
size_t offset = (uintptr_t)data % pagesize();
munmap((char*)data-offset, len+offset);
}
~file_fs() { close(fd); }
};
}
file* file::create_fs(const char * filename)
{
int fd=open(filename, O_RDONLY);
if (fd<0) return NULL;
struct stat st;
if (fstat(fd, &st)<0) goto fail;
return new file_fs(filename, fd, st.st_size);
fail:
close(fd);
return NULL;
}
#ifdef ARGUI_NONE
file* file::create(const char * filename)
{
return create_fs(filename);
}
#endif
#if 0
namespace {
class file_fs_wr : public filewrite {
public:
int fd;
bool truncate;
file_fs_wr(int fd) : fd(fd) {}
/*private*/ void alloc(size_t size)
{
this->data=file_alloc(this->fd, size, true);
this->len=size;
if (this->data==NULL) abort();
}
/*private*/ void dealloc()
{
//no msync - munmap is guaranteed to do that already (and linux tracks dirty pages anyways)
if (this->len <= MMAP_THRESHOLD)
{
pwrite(this->fd, this->data, this->len, 0);
free(this->data);
}
else
{
munmap(this->data, this->len+1);
}
}
bool resize(size_t newsize)
{
if (ftruncate(this->fd, newsize) < 0) return false;
if (this->len < MMAP_THRESHOLD && newsize < MMAP_THRESHOLD)
{
this->len=newsize;
uint8_t* data=realloc(this->data, newsize+1);
data[newsize]='\0';
this->data=data;
return true;
}
dealloc();
alloc(newsize);
return true;
}
void sync()
{
if (this->truncate)
{
ftruncate(this->fd, this->len);
this->truncate=false;
}
msync(this->data, this->len, MS_SYNC);//no MS_INVALIDATE because I can't figure out what it's supposed to do
//on linux, it does nothing whatsoever, except in some EINVAL handlers
}
~file_fs_wr()
{
sync();
dealloc();
close(this->fd);
}
};
};
filewrite* filewrite::create_fs(const char * filename, bool truncate)
{
static const int oflags[]={ O_RDWR|O_CREAT, O_WRONLY|O_CREAT };
int fd=open(filename, oflags[truncate], 0666);//umask defaults to turning this to 644
if (fd<0) return NULL;
if (truncate)
{
file_fs_wr* f=new file_fs_wr(fd);
f->truncate=true;
return f;
}
else
{
struct stat st;
if (fstat(fd, &st)<0) goto fail;
file_fs_wr* f; f=new file_fs_wr(fd);
f->alloc(st.st_size);
return f;
}
fail:
close(fd);
return NULL;
}
#endif
#endif

243
arlib/file-win32.cpp Normal file
View File

@ -0,0 +1,243 @@
#include "file.h"
#include "os.h"
#include "thread.h"
#define MMAP_THRESHOLD 128*1024
#ifdef _WIN32
#undef bind
#include <windows.h>
#define bind bind_func
#include <string.h>
static void window_cwd_enter(const char * dir);
static void window_cwd_leave();
//other platforms: http://stackoverflow.com/questions/1023306/finding-current-executables-path-without-proc-self-exe
const char * window_get_proc_path()
{
//TODO: not thread safe
static char path[MAX_PATH];
GetModuleFileName(NULL, path, MAX_PATH);
for (int i=0;path[i];i++)
{
if (path[i]=='\\') path[i]='/';
}
char * end=strrchr(path, '/');
if (end) end[1]='\0';
return path;
}
char * _window_native_get_absolute_path(const char * basepath, const char * path, bool allow_up)
{
if (!path || !basepath) return NULL;
DWORD len=GetFullPathName(basepath, 0, NULL, NULL);
char * matchdir=malloc(len);
char * filepart;
GetFullPathName(basepath, len, matchdir, &filepart);
if (filepart) *filepart='\0';
window_cwd_enter(matchdir);
for (unsigned int i=0;matchdir[i];i++)
{
if (matchdir[i]=='\\') matchdir[i]='/';
}
len=GetFullPathName(path, 0, NULL, NULL);
char * ret=malloc(len);
GetFullPathName(path, len, ret, NULL);
window_cwd_leave();
for (unsigned int i=0;i<len;i++)
{
if (ret[i]=='\\') ret[i]='/';
}
if (!allow_up)
{
if (strncasecmp(matchdir, ret, strlen(matchdir))!=0)
{
free(matchdir);
free(ret);
return NULL;
}
}
free(matchdir);
return ret;
}
static char * cwd_init;
static char * cwd_bogus;
static char * cwd_bogus_check;
static DWORD cwd_bogus_check_len;
static mutex cwd_lock;
static void window_cwd_enter(const char * dir)
{
cwd_lock.lock();
GetCurrentDirectory(cwd_bogus_check_len, cwd_bogus_check);
if (strcmp(cwd_bogus, cwd_bogus_check)!=0) abort();//if this fires, someone changed the directory without us knowing - not allowed. cwd belongs to the frontend.
SetCurrentDirectory(dir);
}
static void window_cwd_leave()
{
SetCurrentDirectory(cwd_bogus);
cwd_lock.unlock();
}
const char * window_get_cwd()
{
return cwd_init;
}
void _window_init_file()
{
DWORD len=GetCurrentDirectory(0, NULL);
cwd_init=malloc(len+1);
GetCurrentDirectory(len, cwd_init);
len=strlen(cwd_init);
for (unsigned int i=0;i<len;i++)
{
if (cwd_init[i]=='\\') cwd_init[i]='/';
}
if (cwd_init[len-1]!='/')
{
cwd_init[len+0]='/';
cwd_init[len+1]='\0';
}
//try a couple of useless directories and hope one of them works
//(this code is downright Perl-like, but the alternative is a pile of ugly nesting)
SetCurrentDirectory("\\Users") ||
SetCurrentDirectory("\\Documents and Settings") ||
SetCurrentDirectory("\\Windows") ||
(SetCurrentDirectory("C:\\") && false) ||
SetCurrentDirectory("\\Users") ||
SetCurrentDirectory("\\Documents and Settings") ||
SetCurrentDirectory("\\Windows") ||
SetCurrentDirectory("\\");
len=GetCurrentDirectory(0, NULL);
cwd_bogus=malloc(len);
cwd_bogus_check=malloc(len);
cwd_bogus_check_len=len;
GetCurrentDirectory(len, cwd_bogus);
}
//static void* file_alloc(int fd, size_t len, bool writable)
//{
//}
//static size_t pagesize()
//{
// SYSTEM_INFO inf;
// GetSystemInfo(&inf);
// return inf.dwPageSize;
//}
//static size_t allocgran()
//{
// SYSTEM_INFO inf;
// GetSystemInfo(&inf);
// return inf.dwAllocationGranularity;
//}
namespace {
class file_fs : public file {
public:
HANDLE handle;
file_fs(const char * filename, HANDLE handle, size_t len) : file(filename, len), handle(handle) {}
file* clone()
{
HANDLE newhandle;
DuplicateHandle(GetCurrentProcess(), this->handle, GetCurrentProcess(), &newhandle, 0, FALSE, DUPLICATE_SAME_ACCESS);
return new file_fs(this->filename, newhandle, this->len);
}
size_t read(void* target, size_t start, size_t len)
{
char* target_c=(char*)target;
//TODO: use OVERLAPPED to nuke the race condition
LARGE_INTEGER pos;
pos.QuadPart=start;
SetFilePointerEx(this->handle, pos, NULL, FILE_BEGIN);
DWORD actual;
ReadFile(this->handle, target_c, len, &actual, NULL);
return actual;
//>4GB lengths die if entered into this, but you shouldn't read() that at all. Use mmap().
}
void* mmap(size_t start, size_t len)
{
HANDLE mem=CreateFileMapping(handle, NULL, PAGE_READONLY, 0, 0, NULL);
void* ptr=MapViewOfFile(mem, FILE_MAP_READ, start>>16>>16, start&0xFFFFFFFF, len);
CloseHandle(mem);
return ptr;
}
void unmap(const void* data, size_t len) { UnmapViewOfFile((void*)data); }
~file_fs() { CloseHandle(handle); }
};
}
file* file::create_fs(const char * filename)
{
HANDLE file=CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (file == INVALID_HANDLE_VALUE) return NULL;
LARGE_INTEGER size;
GetFileSizeEx(file, &size);
return new file_fs(filename, file, size.QuadPart);
}
#ifdef ARGUI_NONE
file* file::create(const char * filename)
{
return create_fs(filename);
}
#endif
//namespace {
// class file_fs_wr : public filewrite {
// public:
// int fd;
// file_fs_wr(int fd) : fd(fd) {}
// /*private*/ void alloc(size_t size)
// {
// }
// /*private*/ void dealloc()
// {
// }
// bool resize(size_t newsize)
// {
// }
// void sync()
// {
// }
// ~file_fs_wr()
// {
// sync();
// dealloc();
// close(this->fd);
// }
// };
//};
//filewrite* filewrite::create_fs(const char * filename, bool truncate)
//{
//}
#endif

162
arlib/file.h Normal file
View File

@ -0,0 +1,162 @@
#pragma once
#include "global.h"
#include <string.h>
//These are implemented by the window manager; however, due to file operations being far more common than GUI, they're split off.
//Returns the working directory at the time of process launch.
//The true working directory is set to something unusable, and the program may not change or use it.
const char * window_get_cwd();
//Returns the process path, without the filename. Multiple calls will return the same pointer.
const char * window_get_proc_path();
//Converts a relative path (../roms/mario.smc) to an absolute path (/home/admin/roms/mario.smc).
// Implemented by the window manager, so gvfs can be supported. If the file doesn't exist, it is
// implementation defined whether the return value is a nonexistent path, or if it's NULL.
//basepath is the directory you want to use as base, or a file in this directory. NULL means current
// directory as set by window_cwd_enter, or that 'path' is expected absolute.
//If allow_up is false, NULL will be returned if 'path' attempts to go up the directory tree (for example ../../../../../etc/passwd).
//If path is absolute already, it will be returned (possibly canonicalized) if allow_up is true, or rejected otherwise.
//Send it to free() once it's done.
char * window_get_absolute_path(const char * basepath, const char * path, bool allow_up);
//Converts any file path to something accessible on the local file system. The resulting path can
// be both ugly and temporary, so only use it for file I/O, and store the absolute path instead.
//It is not guaranteed that window_get_absolute_path can return the original path, or anything useful at all, if given the output of this.
//It can return NULL, even for paths which file_read understands. If it doesn't, use free() when you're done.
char * window_get_native_path(const char * path);
class file : nocopy {
private:
file(){}
protected:
file(const char * filename) : filename(strdup(filename)) {}
file(const char * filename, size_t(len)) : filename(strdup(filename)), len(len) {}
//This one will create the file from the filesystem.
//create() can simply return create_fs(filename), or can additionally support stuff like gvfs.
static file* create_fs(const char * filename);
class mem;
public:
static file* create(const char * filename);
char* filename;
size_t len;
//The returned object is guaranteed equivalent to the given one, assuming the file is not changed or removed in the meanwhile.
virtual file* clone() { return file::create(this->filename); }
virtual size_t read(void* target, size_t start, size_t len) = 0;
size_t read(void* target, size_t len) { return this->read(target, 0, len); }
virtual void* mmap(size_t start, size_t len) = 0;
void* mmap() { return this->mmap(0, this->len); }
virtual void unmap(const void* data, size_t len) = 0;
virtual ~file() { free(filename); }
};
class file::mem : public file {
void* data;
public:
mem(const char * filename, void* data, size_t len) : file(filename) { this->data=data; this->len=len; }
file* clone()
{
void* newdat=malloc(this->len);
memcpy(newdat, this->data, this->len);
return new file::mem(this->filename, newdat, this->len);
}
size_t read(void* target, size_t start, size_t len) { memcpy(target, (char*)data+start, len); return len; }
void* mmap(size_t start, size_t len) { return (char*)data+start; }
void unmap(const void* data, size_t len) {}
~mem() { free(data); }
};
//virtual bool resize(size_t newsize) { return false; }
////Sends all the data to the disk. Does not return until it's there.
////The destructor also sends the data to disk, but does not guarantee that it's done immediately.
////There may be more ways to send the file to disk, but this is not guaranteed either.
//virtual void sync(){}
//These are implemented by the window manager, despite looking somewhat unrelated.
//Support for absolute filenames is present.
//Support for relative filenames will be rejected as much as possible. However, ../../../../../etc/passwd may work.
//Other things, for example http://example.com/roms/snes/smw.sfc, may work too.
//Directory separator is '/', extension separator is '.'.
//file_read appends a '\0' to the output (whether the file is text or binary); this is not reported in the length.
//Use free() on the return value from file_read().
bool file_read(const char * filename, void* * data, size_t * len);
bool file_write(const char * filename, const anyptr data, size_t len);
bool file_read_to(const char * filename, anyptr data, size_t len);//If size differs, this one fails.
//Some simple wrappers for the above three.
inline bool file_read_rel(const char * basepath, bool allow_up, const char * filename, void* * data, size_t * len)
{
char* path=window_get_absolute_path(basepath, filename, allow_up);
if (!path) return false;
bool ret=file_read(path, data, len);
free(path);
return ret;
}
inline bool file_write_rel(const char * basepath, bool allow_up, const char * filename, const anyptr data, size_t len)
{
char* path=window_get_absolute_path(basepath, filename, allow_up);
if (!path) return false;
bool ret=file_write(path, data, len);
free(path);
return ret;
}
inline bool file_read_to_rel(const char * basepath, bool allow_up, const char * filename, anyptr data, size_t len)
{
char* path=window_get_absolute_path(basepath, filename, allow_up);
if (!path) return false;
bool ret=file_read_to(path, data, len);
free(path);
return ret;
}
//These will list the contents of a directory. The returned paths from window_find_next should be
// sent to free(). The . and .. components will not be included; however, symlinks and other loops
// are not guarded against. It is implementation defined whether hidden files are included. The
// returned filenames are relative to the original path and contain no path information nor leading
// or trailing slashes.
void* file_find_create(const char * path);
bool file_find_next(void* find, char* * path, bool * isdir);
void file_find_close(void* find);
//If the window manager does not implement any non-native paths (like gvfs), it can use this one;
// it's implemented by something that knows the local file system, but not the window manager.
//There is no _window_native_get_native_path; since the local file system doesn't understand
// anything except the local file system, it would only be able to return the input, or be
// equivalent to _window_native_get_absolute_path, making it redundant and therefore useless.
char * _window_native_get_absolute_path(const char * basepath, const char * path, bool allow_up);
void _window_init_file();
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
//The above was defined before Arlib was split off from minir, and is unlikely to still work.
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
//TODO before all of this: create string class
//TODO before all of this: create container classes (let API roughly mirror C#)
//TODO: file_find should return an array
//TODO: create a path sanitizer that canonicalizes a path
//TODO: create a path verifier that checks if a path is within a specified directory
//TODO: create a path policy that checks if a path is within one of many allowed directories

291
arlib/function.h Normal file
View File

@ -0,0 +1,291 @@
//This is <http://www.codeproject.com/Articles/136799/> Lightweight Generic C++ Callbacks (or, Yet Another Delegate Implementation)
//with a number of changes:
//- Callback was renamed to function, and the namespace was removed.
//- BIND_FREE_CB/BIND_MEM_CB were combined to a single bind(), by using the C99 preprocessor's __VA_ARGS__.
//- Instead of the thousand lines of copypasta, the implementations were merged by using some preprocessor macros and having the file include itself.
//- The Arity, ReturnType and ParamNType constants/typedefs were removed.
//- NullCallback was replaced with support for plain NULL, by implicitly casting the NULL to a pointer to a private class (default construction also exists).
//- BoundCallbackFactory and bind_ptr were added. It's useful for legacy C-like code, and some other cases.
//- Made it safe to call an unassigned object. (Unassigned objects are still false.)
//List of libraries that do roughly the same thing:
//http://www.codeproject.com/Articles/7150/ Member Function Pointers and the Fastest Possible C++ Delegates
// rejected because it uses ugly hacks which defeat the optimizer, unknown compilers, and my brain
//http://www.codeproject.com/Articles/11015/ The Impossibly Fast C++ Delegates
// rejected because creation syntax is ugly
//http://www.codeproject.com/Articles/13287/ Fast C++ Delegate
// rejected because it's huge and can allocate
//http://www.codeproject.com/Articles/18886/ A new way to implement Delegate in C++
// rejected because it depends on sizeof in creepy ways and can throw
//http://www.codeproject.com/Articles/136799/ Lightweight Generic C++ Callbacks (or, Yet Another Delegate Implementation)
// chosen because it gives the slimmest function objects - unlike the others, it's just two pointers
#ifndef UTIL_CALLBACK_HPP
#define UTIL_CALLBACK_HPP
#include <stddef.h>
#define UTIL_CALLBACK_HPP_INSIDE
#define bind_free(func) (GetFreeCallbackFactory(func).Bind<func>())
#define bind_ptr(func, ptr) (GetCallbackFactory(func, ptr).Bind<func>(ptr))
#define bind bind_free
#define bind_this(func) bind_ptr(func, this) // reminder: bind_this(&classname::function), not bind_this(function)
template<typename FuncSignature> class function;
#define JOIN2(a,b) a##b
#define JOIN(a,b) JOIN2(a,b)
#define FreeCallbackFactory JOIN(FreeCallbackFactory,COUNT)
#define MemberCallbackFactory JOIN(MemberCallbackFactory,COUNT)
#define ConstMemberCallbackFactory JOIN(ConstMemberCallbackFactory,COUNT)
#define BoundCallbackFactory JOIN(BoundCallbackFactory,COUNT)
#define ARG_TYPES_I(n) JOIN(P,n)
#define ARG_TYPES LOOP(ARG_TYPES_I)
#define ARG_NAMES_I(n) JOIN(a,n)
#define ARG_NAMES LOOP(ARG_NAMES_I)
#define ARG_TYPES_AND_NAMES_I(n) JOIN(P,n) JOIN(a,n)
#define ARG_TYPES_AND_NAMES LOOP(ARG_TYPES_AND_NAMES_I)
#define TYPENAMES_I(n) typename JOIN(P,n)
#define TYPENAMES LOOP(TYPENAMES_I)
#define TYPENAMES2_I(n) typename JOIN(FP,n)
#define TYPENAMES2 LOOP(TYPENAMES2_I)
#define COUNT 0
#define LOOP(macro) /* */
#define C /* */
#include "function.h"
#undef C
#define C ,
#define COUNT 1
#define LOOP(macro) macro(1)
#include "function.h"
#define COUNT 2
#define LOOP(macro) macro(1), macro(2)
#include "function.h"
#define COUNT 3
#define LOOP(macro) macro(1), macro(2), macro(3)
#include "function.h"
#define COUNT 4
#define LOOP(macro) macro(1), macro(2), macro(3), macro(4)
#include "function.h"
#define COUNT 5
#define LOOP(macro) macro(1), macro(2), macro(3), macro(4), macro(5)
#include "function.h"
#define COUNT 6
#define LOOP(macro) macro(1), macro(2), macro(3), macro(4), macro(5), macro(6)
#include "function.h"
#undef C
#undef JOIN2
#undef JOIN
#undef FreeCallbackFactory
#undef MemberCallbackFactory
#undef ConstMemberCallbackFactory
#undef BoundCallbackFactory
#undef ARG_TYPES_I
#undef ARG_TYPES
#undef ARG_NAMES_I
#undef ARG_NAMES
#undef ARG_TYPES_AND_NAMES_I
#undef ARG_TYPES_AND_NAMES
#undef TYPENAMES_I
#undef TYPENAMES
#undef TYPENAMES2_I
#undef TYPENAMES2
#undef UTIL_CALLBACK_HPP_INSIDE
#endif
#ifdef UTIL_CALLBACK_HPP_INSIDE
template<typename R C TYPENAMES>
class function<R (ARG_TYPES)>
{
private:
class null_only;
typedef R (*FuncType)(const void* C ARG_TYPES);
function(FuncType f, const void* o) : func(f), obj(o) {}
FuncType func;
const void* obj;
public:
//to make null objects callable, 'func' must be a valid function
//I can not:
//- use the lowest bits - requires mask at call time, and confuses the optimizer
//- compare it to a static null function, I don't trust the compiler to merge it correctly
//nor can I use NULL/whatever in 'obj', because foreign code can find that argument just as easily as this one can
//solution: set obj=func=EmptyFunction for null functions
//- EmptyFunction doesn't use obj, it can be whatever
//- it is not sensitive to false negatives - even if the address of EmptyFunction changes, obj==func does not
//- it is not sensitive to false positives - EmptyFunction is private and it is impossible for foreign code to know where it is, and luck can not hit it
//- it is sensitive to hostile callers, but if you call bind_ptr(func, (void*)func), you're asking for bugs.
function() : func(EmptyHandler), obj((void*)EmptyHandler) {}
function(const function& rhs) : func(rhs.func), obj(rhs.obj) {}
~function() {}
function(const null_only*) : func(EmptyHandler), obj((void*)EmptyHandler) {}
function& operator=(const function& rhs)
{ obj = rhs.obj; func = rhs.func; return *this; }
inline R operator()(ARG_TYPES_AND_NAMES) const
{
return (*func)(obj C ARG_NAMES);
}
private:
typedef const void* function::*SafeBoolType;
bool isTrue() const
{
return ((void*)func != obj);
}
public:
inline operator SafeBoolType() const
{ return isTrue() ? &function::obj : NULL; }
inline bool operator!() const
{ return !isTrue(); }
private:
static R EmptyHandler(const void* o C ARG_TYPES_AND_NAMES) { return R(); }
template<typename FR C TYPENAMES2>
friend class FreeCallbackFactory;
template<typename FR, class FT C TYPENAMES2>
friend class MemberCallbackFactory;
template<typename FR, class FT C TYPENAMES2>
friend class ConstMemberCallbackFactory;
template<typename FR C TYPENAMES2, typename PTR>
friend class BoundCallbackFactory;
};
template<typename R C TYPENAMES>
void operator==(const function<R (ARG_TYPES)>&,
const function<R (ARG_TYPES)>&);
template<typename R C TYPENAMES>
void operator!=(const function<R (ARG_TYPES)>&,
const function<R (ARG_TYPES)>&);
template<typename R C TYPENAMES>
class FreeCallbackFactory
{
private:
template<R (*Func)(ARG_TYPES)>
static R Wrapper(const void* C ARG_TYPES_AND_NAMES)
{
return (*Func)(ARG_NAMES);
}
public:
template<R (*Func)(ARG_TYPES)>
inline static function<R (ARG_TYPES)> Bind()
{
return function<R (ARG_TYPES)>
(&FreeCallbackFactory::Wrapper<Func>, 0);
}
};
template<typename R C TYPENAMES>
inline FreeCallbackFactory<R C ARG_TYPES>
GetFreeCallbackFactory(R (*)(ARG_TYPES))
{
return FreeCallbackFactory<R C ARG_TYPES>();
}
template<typename R, class T C TYPENAMES>
class MemberCallbackFactory
{
private:
template<R (T::*Func)(ARG_TYPES)>
static R Wrapper(const void* o C ARG_TYPES_AND_NAMES)
{
T* obj = const_cast<T*>(static_cast<const T*>(o));
return (obj->*Func)(ARG_NAMES);
}
public:
template<R (T::*Func)(ARG_TYPES)>
inline static function<R (ARG_TYPES)> Bind(T* o)
{
return function<R (ARG_TYPES)>
(&MemberCallbackFactory::Wrapper<Func>,
static_cast<const void*>(o));
}
};
template<typename R, class T C TYPENAMES>
inline MemberCallbackFactory<R, T C ARG_TYPES>
GetCallbackFactory(R (T::*)(ARG_TYPES), T*)
{
return MemberCallbackFactory<R, T C ARG_TYPES>();
}
template<typename R, class T C TYPENAMES>
class ConstMemberCallbackFactory
{
private:
template<R (T::*Func)(ARG_TYPES) const>
static R Wrapper(const void* o C ARG_TYPES_AND_NAMES)
{
const T* obj = static_cast<const T*>(o);
return (obj->*Func)(ARG_NAMES);
}
public:
template<R (T::*Func)(ARG_TYPES) const>
inline static function<R (ARG_TYPES)> Bind(const T* o)
{
return function<R (ARG_TYPES)>
(&ConstMemberCallbackFactory::Wrapper<Func>,
static_cast<const void*>(o));
}
};
template<typename R, class T C TYPENAMES>
inline ConstMemberCallbackFactory<R, T C ARG_TYPES>
GetCallbackFactory(R (T::*)(ARG_TYPES) const, const T*)
{
return ConstMemberCallbackFactory<R, T C ARG_TYPES>();
}
template<typename R C TYPENAMES, typename PTR>
class BoundCallbackFactory
{
private:
template<R (*Func)(PTR* C ARG_TYPES)>
static R Wrapper(const void* o C ARG_TYPES_AND_NAMES)
{
return (*Func)((PTR*)o C ARG_NAMES);
}
public:
template<R (*Func)(PTR* C ARG_TYPES)>
inline static function<R (ARG_TYPES)> Bind(PTR* o)
{
return function<R (ARG_TYPES)>
(&BoundCallbackFactory::Wrapper<Func>, o);
}
};
template<typename R C TYPENAMES, typename PTR>
inline BoundCallbackFactory<R C ARG_TYPES, PTR>
GetCallbackFactory(R (*)(PTR* C ARG_TYPES), PTR*)
{
return BoundCallbackFactory<R C ARG_TYPES, PTR>();
}
#undef COUNT
#undef LOOP
#endif

320
arlib/global.h Normal file
View File

@ -0,0 +1,320 @@
#pragma once
#ifdef _WIN32
# ifndef _WIN32_WINNT
# define _WIN32_WINNT 0x0600
# define NTDDI_VERSION NTDDI_VISTA
# elif _WIN32_WINNT < 0x0600
# undef _WIN32_WINNT
# define _WIN32_WINNT 0x0502//0x0501 excludes SetDllDirectory, so I need to put it at 0x0502
# define NTDDI_VERSION NTDDI_WS03 // actually NTDDI_WINXPSP2, but MinGW sddkddkver.h gets angry about that
# endif
# define _WIN32_IE 0x0600
//the namespace pollution this causes is massive, but without it, there's a bunch of functions that
// just tail call kernel32.dll. With it, they can be inlined.
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
# undef interface // screw that, I'm not interested in COM shittery
#endif
#ifndef _GNU_SOURCE
#define _GNU_SOURCE //strdup, realpath, asprintf
#endif
#define _strdup strdup //and windows is being windows as usual
#define __STDC_LIMIT_MACROS //how many of these stupid things exist
#define __STDC_FORMAT_MACROS//if I include a header, it's because I want to use its contents
#define __STDC_CONSTANT_MACROS
#include <stdint.h>
#include <stddef.h>
#include <stdio.h>
#include <inttypes.h>
#include "function.h"
typedef void(*funcptr)();
//Note to anyone interested in reusing these objects:
//Many, if not most, of them will likely change their interface, likely quite fundamentally, in the future.
//No attempt is made to keep any kind of backwards or forwards compatibility.
#define using(obj) for(bool FIRST=true;FIRST;FIRST=false)for(obj;FIRST;FIRST=false)
#define JOIN_(x, y) x ## y
#define JOIN(x, y) JOIN_(x, y)
//some magic stolen from http://blogs.msdn.com/b/the1/archive/2004/05/07/128242.aspx
//C++ can be so messy sometimes...
template<typename T, size_t N> char(&ARRAY_SIZE_CORE(T(&x)[N]))[N];
#define ARRAY_SIZE(x) (sizeof(ARRAY_SIZE_CORE(x)))
#ifdef __GNUC__
#define GCC_VERSION (__GNUC__ * 10000 \
+ __GNUC_MINOR__ * 100 \
+ __GNUC_PATCHLEVEL__)
#define LIKELY(expr) __builtin_expect(!!(expr), true)
#define UNLIKELY(expr) __builtin_expect(!!(expr), false)
#else
#define GCC_VERSION 0
#define LIKELY(expr) (expr)
#define UNLIKELY(expr) (expr)
#endif
//requirements:
//- static_assert(false) throws something at compile time
//- multiple static_assert(true) works
//- does not require unique names for each assertion
//- zero traces left in the object files, assuming no debug info
//- zero warnings under any compiler
//- static_assert(2+2 < 5); works at the global scope
//- static_assert(2+2 < 5); works as a class member
//- static_assert(2+2 < 5); works inside a function
//- static_assert(2+2 < 5); works in all of the above when templates are involved
//- works on all compilers
//optional:
//- (PASS) works in a template, even if the template isn't instantiated, if the condition isn't dependent on the types
//- (FAIL) works if compiled as C (can fix with an ifdef, but I'm lazy)
//- (FAIL) can name assertions, if desired
#ifdef __GNUC__
#define MAYBE_UNUSED __attribute__((__unused__)) // shut up, stupid warnings
#define TYPENAME_IF_GCC typename // gcc requires this. msvc rejects this.
#else
#define MAYBE_UNUSED
#define TYPENAME_IF_GCC
#endif
template<bool x> struct static_assert_t;
template<> struct static_assert_t<true> { struct STATIC_ASSERTION_FAILED {}; };
template<> struct static_assert_t<false> {};
//#define static_assert(expr)
// typedef TYPENAME_IF_NEEDED static_assert_t<(bool)(expr)>::STATIC_ASSERTION_FAILED
// JOIN(static_assertion_, __COUNTER__) MAYBE_UNUSED;
#define static_assert(expr) \
enum { \
JOIN(static_assertion_, __COUNTER__) = \
sizeof(TYPENAME_IF_GCC static_assert_t<(bool)(expr)>::STATIC_ASSERTION_FAILED) \
} MAYBE_UNUSED
//almost C version (fails inside structs):
//#define static_assert(expr) \
// typedef char JOIN(static_assertion_, __COUNTER__)[(expr)?1:-1]
#ifdef __GNUC__
#define ALIGN(n) __attribute__((aligned(n)))
#endif
#ifdef _MSC_VER
#define ALIGN(n) __declspec(align(n))
#endif
#ifdef __cplusplus
class anyptr {
void* data;
public:
template<typename T> anyptr(T* data_) { data=(void*)data_; }
template<typename T> operator T*() { return (T*)data; }
template<typename T> operator const T*() const { return (const T*)data; }
};
#else
typedef void* anyptr;
#endif
#include <stdlib.h> // needed because otherwise I get errors from malloc_check being redeclared.
anyptr malloc_check(size_t size);
anyptr try_malloc(size_t size);
#define malloc malloc_check
anyptr realloc_check(anyptr ptr, size_t size);
anyptr try_realloc(anyptr ptr, size_t size);
#define realloc realloc_check
anyptr calloc_check(size_t size, size_t count);
anyptr try_calloc(size_t size, size_t count);
#define calloc calloc_check
//if I cast it to void, that means I do not care, so shut the hell up about warn_unused_result.
template<typename T> static inline void ignore(T t) {}
//too reliant on non-ancient compilers
////some SFINAE shenanigans to call T::create if it exists, otherwise new() - took an eternity to google up
////don't use this template yourself, use generic_create/destroy instead
//template<typename T> class generic_create_core {
// template<int G> class int_eater {};
//public:
// template<typename T2> static T* create(T2*, int_eater<sizeof(&T2::create)>*) { return T::create(); }
// static T* create(T*, ...) { return new T(); }
//
// template<typename T2> static void destroy(T* obj, T2*, int_eater<sizeof(&T2::release)>*) { obj->release(); }
// static void destroy(T* obj, T*, ...) { delete obj; }
//};
//template<typename T> T* generic_create() { return generic_create_core<T>::create((T*)NULL, NULL); }
//template<typename T> void generic_delete(T* obj) { generic_create_core<T>::destroy(obj, (T*)NULL, NULL); }
template<typename T> T* generic_create() { return T::create(); }
template<typename T> T* generic_new() { return new T; }
template<typename T> void generic_delete(T* obj) { delete obj; }
template<typename T> void generic_release(T* obj) { obj->release(); }
template<typename T> void* generic_create_void() { return (void*)generic_create<T>(); }
template<typename T> void* generic_new_void() { return (void*)generic_new<T>(); }
template<typename T> void generic_delete_void(void* obj) { generic_delete((T*)obj); }
template<typename T> void generic_release_void(void* obj) { generic_release((T*)obj); }
class empty {
int x[];
};
class nocopy : private empty {
protected:
nocopy() {}
~nocopy() {}
//#ifdef HAVE_MOVE
// nocopy(nocopy&&) = default;
// const nocopy& operator=(nocopy&&) = default;
//#endif
private:
nocopy(const nocopy&);
const nocopy& operator=(const nocopy&);
};
/*
template<typename T> class autoptr : nocopy {
T* obj;
#ifdef HAVE_MOVE
public:
autoptr(T* obj) : obj(obj) {}
autoptr(map&& other) : obj(other.obj) { other.obj=NULL; }
~map() { delete obj; }
#else
unsigned int* refcount;
public:
autoptr(T* obj) : obj(obj)
{
this->refcount=new unsigned int;
this->refcount[0]=1;
}
autoptr(const autoptr& other) : obj(other.obj)
{
this->refcount=other.refcount;
this->refcount[0]++;
}
~autoptr()
{
this->refcount[0]--;
if (this->refcount[0]==0)
{
delete this->refcount;
delete this->obj;
}
}
#endif
T& operator*() { return *obj; }
T* operator->() { return obj; }
};
*/
#ifdef HAVE_MOVE
#define autoref nocopy
#else
template<typename T> class autoref {
unsigned int* refcount;
public:
autoref()
{
this->refcount=new unsigned int;
this->refcount[0]=1;
}
autoref(const autoref& other)
{
this->refcount=other.refcount;
this->refcount[0]++;
}
~autoref()
{
this->refcount[0]--;
if (this->refcount[0]==0)
{
((T*)this) -> release();
}
}
};
#endif
template<typename T> class autoptr : autoref<T> {
T* obj;
public:
autoptr(T* obj) : obj(obj) {}
void release() { delete obj; }
T& operator*() { return *obj; }
T* operator->() { return obj; }
};
#if defined(__linux__) || GCC_VERSION >= 40900
#define asprintf(...) ignore(asprintf(__VA_ARGS__))
#else
void asprintf(char * * ptr, const char * fmt, ...);
#endif
//msvc:
//typedef unsigned long uint32_t;
//typedef unsigned __int64 uint64_t;
//typedef unsigned int size_t;
template<typename T> static inline T bitround(T in)
{
in--;
in|=in>>1;
in|=in>>2;
in|=in>>4;
in|=in>>8;
if (sizeof(T)>2) in|=in>>8>>8;
if (sizeof(T)>4) in|=in>>8>>8>>8>>8;
in++;
return in;
}
//If any interface defines a callback, free() is banned while inside this callback; other functions
// are allowed, unless otherwise specified. Other instances of the same interface may be used and
// freed, and other interfaces may be called.
//If an interface defines a function to set some state, and a callback for when this state changes,
// calling that function will not trigger the state callback.
//Unless otherwise specified, an interface may only be used from its owner thread (the creator).
// However, it is safe for any thread to create an interface, including having different threads
// use multiple instances of the same interface.
//Don't depend on any pointer being unique; for example, the None interfaces are static. However,
// even if they are (potentially) non-unique, following the instructed method to free them is safe;
// either they're owned by the one one giving them to you, or their free() handlers are empty, or
// they could even be refcounted.
//If a pointer is valid until anything from a set of functions is called (including if the set
// contains only one listed function), free() will also invalidate that pointer.
//"Implementation" means the implementation of the interfaces; the blocks of code that are called
// when a function is called on an interface.
//"User" means the one using the interfaces. Some interface implementations are users of other
// interfaces; for example, an implementation of libretro is also the user of a dylib.
//An implementation may, at its sole discretion, choose to define any implementation of undefined
// behaviour. After all, any result, including something well defined, is a valid interpretation of
// undefined behaviour. The user may, of course, not rely on that.
//Any function that starts with an underscore may only be called by the module that implements that
// function. ("Module" is defined as "anything whose compilation is controlled by the same #ifdef,
// or the file implementing an interface, whichever makes sense"; for example, window-win32-* is the
// same module.) The arguments and return values of these private functions may change meaning
// between modules, and the functions are not guaranteed to exist at all, or closely correspond to
// their name. For example, _window_init_misc on GTK+ instead initializes a component needed by the
// listboxes.
//This file, and many other parts of Arlib, uses a weird mix between Windows- and Linux-style
// filenames and paths. This is intentional; the author prefers Linux-style paths and directory
// structures, but Windows file extensions. .exe is less ambigous than no extension, and Windows'
// insistence on overloading the escape character is irritating. Since this excludes following
// any single OS, the rest is personal preference.

687
arlib/gui/gtk3-inner.cpp Normal file
View File

@ -0,0 +1,687 @@
#include "window.h"
#include "../file.h"
#ifdef ARGUI_GTK3
#include <gtk/gtk.h>
#include <stdlib.h>
#include <string.h>
#ifdef ARGUIPROT_X11
#include <gdk/gdkx.h>
#endif
//BUG - The viewport widget does not resize the parent's status bar if resize()d.
static bool in_callback=false;
widget_padding::widget_padding(bool vertical)
{
widget=GTK_DRAWING_AREA(gtk_drawing_area_new());
widthprio=(vertical ? 0 : 2);
heightprio=(vertical ? 2 : 0);
}
widget_padding::~widget_padding() {}
struct widget_layout::impl {
widget_base* * children;
unsigned int numchildren;
};
void widget_layout::construct(unsigned int numchildren, widget_base* * children,
unsigned int totwidth, unsigned int * widths, bool uniformwidths,
unsigned int totheight, unsigned int * heights, bool uniformheights)
{
m=new impl;
GtkGrid* grid=GTK_GRID(gtk_grid_new());
widget=grid;
m->numchildren=numchildren;
m->children=malloc(sizeof(struct widget_base*)*numchildren);
memcpy(m->children, children, sizeof(struct widget_base*)*numchildren);
widthprio=0;
heightprio=0;
bool posused[totheight*totwidth];
memset(posused, 0, sizeof(posused));
unsigned int firstempty=0;
for (unsigned int i=0;i<numchildren;i++)
{
while (posused[firstempty]) firstempty++;
unsigned int width=(widths ? widths[i] : 1);
unsigned int height=(heights ? heights[i] : 1);
gtk_grid_attach(grid, GTK_WIDGET(children[i]->widget),
firstempty%totwidth, firstempty/totwidth,
width, height);
for (unsigned int x=0;x<width ;x++)
for (unsigned int y=0;y<height;y++)
{
posused[firstempty + y*totwidth + x]=true;
}
if (children[i]->widthprio > widthprio) widthprio =children[i]->widthprio;
if (children[i]->heightprio > heightprio) heightprio=children[i]->heightprio;
}
for (unsigned int i=0;i<numchildren;i++)
{
gtk_widget_set_hexpand(GTK_WIDGET(children[i]->widget), (children[i]->widthprio == widthprio));
gtk_widget_set_vexpand(GTK_WIDGET(children[i]->widget), (children[i]->heightprio == heightprio));
}
}
widget_layout::~widget_layout()
{
for (unsigned int i=0;i<m->numchildren;i++)
{
delete m->children[i];
}
free(m->children);
delete m;
}
widget_label::widget_label(const char * text)
{
widget=gtk_label_new(text);
widthprio=1;
heightprio=1;
}
widget_label::~widget_label() {}
widget_label* widget_label::set_enabled(bool enable)
{
gtk_widget_set_sensitive(GTK_WIDGET(widget), enable);
return this;
}
widget_label* widget_label::set_text(const char * text)
{
gtk_label_set_text(GTK_LABEL(widget), text);
return this;
}
widget_label* widget_label::set_ellipsize(bool ellipsize)
{
if (ellipsize)
{
gtk_label_set_ellipsize(GTK_LABEL(widget), PANGO_ELLIPSIZE_END);
gtk_label_set_max_width_chars(GTK_LABEL(widget), 1);//why does this work
}
else
{
gtk_label_set_ellipsize(GTK_LABEL(widget), PANGO_ELLIPSIZE_NONE);
gtk_label_set_max_width_chars(GTK_LABEL(widget), -1);
}
return this;
}
widget_label* widget_label::set_alignment(int alignment)
{
gtk_misc_set_alignment(GTK_MISC(widget), ((float)alignment)/2, 0.5);
return this;
}
struct widget_button::impl {
function<void()> onclick;
};
widget_button::widget_button(const char * text) : m(new impl)
{
widget=gtk_button_new_with_label(text);
widthprio=1;
heightprio=1;
}
widget_button::~widget_button()
{
delete m;
}
widget_button* widget_button::set_enabled(bool enable)
{
gtk_widget_set_sensitive(GTK_WIDGET(widget), enable);
return this;
}
widget_button* widget_button::set_text(const char * text)
{
gtk_button_set_label(GTK_BUTTON(widget), text);
return this;
}
static void widget_button_onclick(GtkButton* button, gpointer user_data)
{
widget_button* obj=(widget_button*)user_data;
obj->m->onclick();
}
widget_button* widget_button::set_onclick(function<void()> onclick)
{
g_signal_connect(widget, "clicked", G_CALLBACK(widget_button_onclick), this);
m->onclick=onclick;
return this;
}
struct widget_checkbox::impl {
function<void(bool checked)> onclick;
};
widget_checkbox::widget_checkbox(const char * text) : m(new impl)
{
widget=gtk_check_button_new_with_label(text);
widthprio=1;
heightprio=1;
}
widget_checkbox::~widget_checkbox()
{
delete m;
}
widget_checkbox* widget_checkbox::set_enabled(bool enable)
{
gtk_widget_set_sensitive(GTK_WIDGET(widget), enable);
return this;
}
widget_checkbox* widget_checkbox::set_text(const char * text)
{
gtk_button_set_label(GTK_BUTTON(widget), text);
return this;
}
bool widget_checkbox::get_state()
{
return gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
}
widget_checkbox* widget_checkbox::set_state(bool checked)
{
in_callback=true;
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), checked);
in_callback=false;
return this;
}
static void widget_checkbox_onclick(GtkButton* button, gpointer user_data)
{
if (in_callback) return;
widget_checkbox* obj=(widget_checkbox*)user_data;
obj->m->onclick(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(obj->widget)));
}
widget_checkbox* widget_checkbox::set_onclick(function<void(bool checked)> onclick)
{
g_signal_connect(widget, "clicked", G_CALLBACK(widget_checkbox_onclick), this);
m->onclick=onclick;
return this;
}
struct widget_radio::impl {
GtkLabel* label;
unsigned int grouplen;
widget_radio * * group;
unsigned int id;//if state is set before grouping, this is used as state
widget_radio * parent;
function<void(unsigned int state)> onclick;
};
static void widget_radio_onclick(GtkToggleButton* togglebutton, gpointer user_data);
widget_radio::widget_radio(const char * text) : m(new impl)
{
widget=gtk_radio_button_new(NULL);
g_signal_connect(widget, "toggled", G_CALLBACK(widget_radio_onclick), this);
m->label=GTK_LABEL(gtk_label_new(text));
gtk_container_add(GTK_CONTAINER(widget), GTK_WIDGET(m->label));
widthprio=1;
heightprio=1;
m->group=NULL;
m->id=0;
}
widget_radio::~widget_radio()
{
free(m->group);
delete m;
}
widget_radio* widget_radio::set_enabled(bool enable)
{
gtk_widget_set_sensitive(GTK_WIDGET(widget), enable);
return this;
}
widget_radio* widget_radio::set_text(const char * text)
{
gtk_label_set_text(m->label, text);
return this;
}
widget_radio* widget_radio::group(unsigned int numitems, widget_radio * * group)
{
m->parent=this;
for (unsigned int i=1;i<numitems;i++)
{
group[i]->m->parent=this;
group[i]->m->id=i;
gtk_radio_button_join_group(GTK_RADIO_BUTTON(group[i]->widget), GTK_RADIO_BUTTON(group[i-1]->widget));
}
m->group=malloc(sizeof(widget_radio*)*numitems);
memcpy(m->group, group, sizeof(widget_radio*)*numitems);
m->grouplen=numitems;
in_callback=true;
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(group[m->id]->widget), true);
m->id=0;
in_callback=false;
return this;
}
unsigned int widget_radio::get_state()
{
unsigned int i=0;
while (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m->group[i]->widget))) i++;
return i;
}
widget_radio* widget_radio::set_state(unsigned int state)
{
if (m->group)
{
in_callback=true;
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m->group[state]->widget), true);
in_callback=false;
}
else
{
m->id=state;
}
return this;
}
static void widget_radio_onclick(GtkToggleButton* togglebutton, gpointer user_data)
{
if (in_callback) return;
widget_radio* obj=(widget_radio*)user_data;
if (!gtk_toggle_button_get_active(togglebutton)) return;
if (obj->m->parent->m->onclick) obj->m->parent->m->onclick(obj->m->id);
}
widget_radio* widget_radio::set_onclick(function<void(unsigned int state)> onclick)
{
m->onclick=onclick;
return this;
}
struct widget_textbox::impl {
function<void(const char * text)> onchange;
function<void(const char * text)> onactivate;
};
static void widget_textbox_onchange(GtkEntry* entry, gpointer user_data);
widget_textbox::widget_textbox() : m(new impl)
{
widget=gtk_entry_new();
widthprio=3;
heightprio=1;
gtk_entry_set_width_chars(GTK_ENTRY(widget), 5);
g_signal_connect(widget, "changed", G_CALLBACK(widget_textbox_onchange), this);
m->onchange=NULL;
}
widget_textbox::~widget_textbox()
{
delete m;
}
widget_textbox* widget_textbox::set_enabled(bool enable)
{
gtk_widget_set_sensitive(GTK_WIDGET(widget), enable);
return this;
}
widget_textbox* widget_textbox::focus()
{
gtk_widget_grab_focus(GTK_WIDGET(widget));
return this;
}
widget_textbox* widget_textbox::set_text(const char * text)
{
gtk_entry_set_text(GTK_ENTRY(widget), text);
return this;
}
widget_textbox* widget_textbox::set_length(unsigned int maxlen)
{
gtk_entry_set_max_length(GTK_ENTRY(widget), maxlen);
return this;
}
widget_textbox* widget_textbox::set_width(unsigned int xs)
{
gtk_entry_set_width_chars(GTK_ENTRY(widget), xs);
return this;
}
widget_textbox* widget_textbox::set_invalid(bool invalid)
{
if (invalid)
{
static GtkCssProvider* cssprovider = NULL;
if (!cssprovider)
{
cssprovider = gtk_css_provider_new();
gtk_css_provider_load_from_data(cssprovider,
"GtkEntry#invalid { background-image: none; background-color: #F66; color: #FFF; }"
"GtkEntry#invalid:selected { background-color: #3465A4; color: #FFF; }"
//this selection doesn't look too good, but not terrible either.
, -1, NULL);
}
GtkStyleContext* context=gtk_widget_get_style_context(GTK_WIDGET(widget));
gtk_style_context_add_provider(context, GTK_STYLE_PROVIDER(cssprovider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
gtk_widget_set_name(GTK_WIDGET(widget), "invalid");
gtk_widget_grab_focus(GTK_WIDGET(widget));
}
else
{
gtk_widget_set_name(GTK_WIDGET(widget), "x");
}
return this;
}
const char * widget_textbox::get_text()
{
return gtk_entry_get_text(GTK_ENTRY(widget));
}
static void widget_textbox_onchange(GtkEntry* entry, gpointer user_data)
{
widget_textbox* obj=(widget_textbox*)user_data;
gtk_widget_set_name(GTK_WIDGET(obj->widget), "x");
if (obj->m->onchange)
{
obj->m->onchange(gtk_entry_get_text(GTK_ENTRY(obj->widget)));
}
}
widget_textbox* widget_textbox::set_onchange(function<void(const char * text)> onchange)
{
m->onchange=onchange;
return this;
}
static void widget_textbox_onactivate(GtkEntry* entry, gpointer user_data)
{
widget_textbox* obj=(widget_textbox*)user_data;
obj->m->onactivate(gtk_entry_get_text(GTK_ENTRY(obj->widget)));
}
widget_textbox* widget_textbox::set_onactivate(function<void(const char * text)> onactivate)
{
g_signal_connect(widget, "activate", G_CALLBACK(widget_textbox_onactivate), this);
m->onactivate=onactivate;
return this;
}
class widget_canvas;
struct widget_viewport::impl {
bool hide_mouse;
bool hide_mouse_timer_active;
guint32 hide_mouse_at;
GdkCursor* hidden_cursor;
function<void(const char * const * filenames)> on_file_drop;
};
widget_viewport::widget_viewport(unsigned int width, unsigned int height) : m(new impl)
{
widget=gtk_drawing_area_new();
widthprio=0;
heightprio=0;
m->hide_mouse_at=0;
m->hidden_cursor=NULL;
gtk_widget_set_size_request(GTK_WIDGET(widget), width, height);
}
widget_viewport::~widget_viewport()
{
if (m->hidden_cursor) g_object_unref(m->hidden_cursor);
delete m;
}
widget_viewport* widget_viewport::resize(unsigned int width, unsigned int height)
{
gtk_widget_set_size_request(GTK_WIDGET(widget), width, height);
//GtkWindow* window=GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(widget)));
//gtk_window_resize(window, 1, 1);
//printf("S=%i,%i\n",width,height);
return this;
}
uintptr_t widget_viewport::get_window_handle()
{
gtk_widget_realize(GTK_WIDGET(widget));
//this won't work on anything except X11, but should be trivial to create an equivalent for.
return gdk_x11_window_get_xid(gtk_widget_get_window(GTK_WIDGET(widget)));
}
void widget_viewport::get_position(int * x, int * y, unsigned int * width, unsigned int * height)
{
gtk_widget_realize(GTK_WIDGET(widget));
GdkWindow* window=gtk_widget_get_window(GTK_WIDGET(widget));
gdk_window_get_origin(window, x, y);
if (width) *width=gdk_window_get_width(window);
if (height) *height=gdk_window_get_height(window);
//if (x) *height=gdk_window_get_height(window);
//if (y) *height=gdk_window_get_height(window);
}
static void viewport_set_hide_cursor_now(widget_viewport* obj, bool hide)
{
GdkWindow* gdkwindow=gtk_widget_get_window(GTK_WIDGET(obj->widget));
if (gdkwindow) gdk_window_set_cursor(gdkwindow, hide ? obj->m->hidden_cursor : NULL);
}
static gboolean viewport_mouse_timeout(gpointer user_data)
{
widget_viewport* obj=(widget_viewport*)user_data;
guint32 now=g_get_monotonic_time()/1000;
if (now >= obj->m->hide_mouse_at)
{
obj->m->hide_mouse_timer_active=false;
viewport_set_hide_cursor_now(obj, obj->m->hide_mouse);
}
else
{
guint32 remaining=obj->m->hide_mouse_at-now+10;
g_timeout_add(remaining, viewport_mouse_timeout, obj);
}
return G_SOURCE_REMOVE;
}
static gboolean viewport_mouse_move_handler(GtkWidget* widget, GdkEvent* event, gpointer user_data)
{
widget_viewport* obj=(widget_viewport*)user_data;
obj->m->hide_mouse_at=g_get_monotonic_time()/1000 + 990;
if (!obj->m->hide_mouse_timer_active)
{
obj->m->hide_mouse_timer_active=true;
g_timeout_add(1000, viewport_mouse_timeout, obj);
viewport_set_hide_cursor_now(obj, false);
}
return G_SOURCE_CONTINUE;
}
widget_viewport* widget_viewport::set_hide_cursor(bool hide)
{
if (m->hide_mouse_at && g_get_monotonic_time()/1000 >= m->hide_mouse_at)
{
viewport_set_hide_cursor_now(this, hide);
}
m->hide_mouse=hide;
if (!hide || m->hide_mouse_at) return this;
if (!m->hidden_cursor) m->hidden_cursor=gdk_cursor_new(GDK_BLANK_CURSOR);
gtk_widget_add_events(GTK_WIDGET(widget), GDK_POINTER_MOTION_MASK);
g_signal_connect(widget, "motion-notify-event", G_CALLBACK(viewport_mouse_move_handler), this);
//seems to not exist in gtk+ 3.8
//and gdk_event_request_motions does nothing, either - am I building for an older GTK+ than I'm using?
//gdk_window_set_event_compression(gtk_widget_get_window(this->i._base.widget), false);
m->hide_mouse_timer_active=false;
viewport_mouse_move_handler(NULL, NULL, this);
return this;
}
/*
void (*keyboard_cb)(struct window * subject, unsigned int keycode, void* userdata);
void* keyboard_ud;
//static gboolean kb_signal(GtkWidget* widget, GdkEvent* event, gpointer user_data)
//{
// struct window_gtk3 * this=(struct window_gtk3*)user_data;
// return FALSE;
//}
static void set_kb_callback(struct window * this_,
void (*keyboard_cb)(struct window * subject, unsigned int keycode, void* userdata), void* userdata)
{
struct window_gtk3 * this=(struct window_gtk3*)this_;
gtk_widget_add_events(GTK_WIDGET(this->wndw), GDK_KEY_PRESS_MASK);
//g_signal_connect(this->contents, "key-press-event", G_CALLBACK(kb_signal), this);
this->keyboard_cb=keyboard_cb;
this->keyboard_ud=userdata;
}
*/
static void viewport_drop_handler(GtkWidget* widget, GdkDragContext* drag_context, gint x, gint y,
GtkSelectionData* selection_data, guint info, guint time, gpointer user_data)
{
widget_viewport* obj=(widget_viewport*)user_data;
if (!selection_data || !gtk_selection_data_get_length(selection_data))
{
gtk_drag_finish(drag_context, FALSE, FALSE, time);
return;
}
const char * data=(gchar*)gtk_selection_data_get_data(selection_data);
int numstr=0;
for (int i=0;data[i];i++)
{
if (data[i]=='\n') numstr++;
}
char* datacopy=strdup(data);
char** strings=malloc(sizeof(char*)*(numstr+1));
char* last=datacopy;
int strnum=0;
for (int i=0;datacopy[i];i++)
{
if (datacopy[i]=='\r') datacopy[i]='\0';//where did those come from? this isn't Windows, we shouldn't be getting Windows-isms.
if (datacopy[i]=='\n')
{
datacopy[i]='\0';
strings[strnum]=window_get_absolute_path(NULL, last, true);
last=datacopy+i+1;
strnum++;
}
}
strings[numstr]=NULL;
free(datacopy);
obj->m->on_file_drop(strings);
for (int i=0;strings[i];i++) free(strings[i]);
free(strings);
gtk_drag_finish(drag_context, TRUE, FALSE, time);
}
widget_viewport* widget_viewport::set_support_drop(function<void(const char * const * filenames)> on_file_drop)
{
GtkTargetList* list=gtk_target_list_new(NULL, 0);
gtk_target_list_add_uri_targets(list, 0);
int n_targets;
GtkTargetEntry* targets=gtk_target_table_new_from_list(list, &n_targets);
//GTK_DEST_DEFAULT_MOTION|GTK_DEST_DEFAULT_DROP
gtk_drag_dest_set(GTK_WIDGET(widget), GTK_DEST_DEFAULT_ALL, targets,n_targets, GDK_ACTION_COPY);
gtk_target_table_free(targets, n_targets);
gtk_target_list_unref(list);
g_signal_connect(widget, "drag-data-received", G_CALLBACK(viewport_drop_handler), this);
m->on_file_drop=on_file_drop;
return this;
}
class widget_listbox;
struct widget_frame::impl {
struct widget_base* child;
};
widget_frame::widget_frame(const char * text, widget_base* child) : m(new impl)
{
widget=gtk_frame_new(text);
widthprio=child->widthprio;
heightprio=child->heightprio;
m->child=child;
gtk_container_add(GTK_CONTAINER(widget), GTK_WIDGET(child->widget));
}
widget_frame::~widget_frame()
{
delete m->child;
delete m;
}
widget_frame* widget_frame::set_text(const char * text)
{
gtk_frame_set_label(GTK_FRAME(m->child), text);
return this;
}
#endif

503
arlib/gui/gtk3-listbox.cpp Normal file
View File

@ -0,0 +1,503 @@
//#include<stdint.h>
//#include<time.h>
//static uint64_t nanotime(){
//struct timespec tv;
//clock_gettime(CLOCK_MONOTONIC, &tv);
//return tv.tv_sec*(uint64_t)1000000000 + tv.tv_nsec;}
#include "window.h"
#ifdef ARGUI_GTK3
#include <gtk/gtk.h>
#include <stdlib.h>
#include <string.h>
//there is a gtk_tree_view_insert_column_with_data_func, but while it can get rid of
// virtual_list_get_value, it can not get rid of virtual_list_iter_n_children
//because GtkTreeView insists on iterating the entire list and building some silly tree out of it
//there's no need for anything like that; it's a list, you have O(1) to everything
//see gtk_tree_view_build_tree for details
#define MAX_ROWS 100000
//http://scentric.net/tutorial/
static GType M_VIRTUAL_TYPE=0;
#define M_VIRTUAL_LIST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), M_VIRTUAL_TYPE, struct VirtualList))
#define M_IS_VIRTUAL_LIST(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), M_VIRTUAL_TYPE))
struct VirtualList
{
GObject g_parent;//leave this one on top
size_t rows;
unsigned int columns;
bool checkboxes;
struct widget_listbox_virtual * subject;
function<const char * (size_t row, int column)> get_cell;
};
struct VirtualListClass
{
GObjectClass parent_class;
};
static GtkTreeModelFlags virtual_list_get_flags(GtkTreeModel* tree_model)
{
g_return_val_if_fail(M_IS_VIRTUAL_LIST(tree_model), (GtkTreeModelFlags)0);
return (GtkTreeModelFlags)(GTK_TREE_MODEL_LIST_ONLY|GTK_TREE_MODEL_ITERS_PERSIST);
}
static gint virtual_list_get_n_columns(GtkTreeModel* tree_model)
{
g_return_val_if_fail(M_IS_VIRTUAL_LIST(tree_model), 0);
return M_VIRTUAL_LIST(tree_model)->columns;
}
static GType virtual_list_get_column_type(GtkTreeModel* tree_model, gint index)
{
g_return_val_if_fail(M_IS_VIRTUAL_LIST(tree_model), G_TYPE_INVALID);
g_return_val_if_fail(index>=0 && (unsigned int)index<M_VIRTUAL_LIST(tree_model)->columns, G_TYPE_INVALID);
return G_TYPE_STRING;
}
static gboolean virtual_list_get_iter(GtkTreeModel* tree_model, GtkTreeIter* iter, GtkTreePath* path)
{
g_assert(M_IS_VIRTUAL_LIST(tree_model));
g_assert(path!=NULL);
g_assert(gtk_tree_path_get_depth(path)==1);
struct VirtualList* virtual_list=M_VIRTUAL_LIST(tree_model);
uintptr_t n=gtk_tree_path_get_indices(path)[0];
if (n>=virtual_list->rows || n<0) return FALSE;
iter->stamp=0;
iter->user_data=(void*)n;
iter->user_data2=NULL;//we don't need those two
iter->user_data3=NULL;
return TRUE;
}
static GtkTreePath* virtual_list_get_path(GtkTreeModel* tree_model, GtkTreeIter* iter)
{
g_return_val_if_fail(M_IS_VIRTUAL_LIST(tree_model), NULL);
g_return_val_if_fail(iter!=NULL, NULL);
GtkTreePath* path=gtk_tree_path_new();
gtk_tree_path_append_index(path, (uintptr_t)iter->user_data);
return path;
}
static void virtual_list_get_value(GtkTreeModel* tree_model, GtkTreeIter* iter, gint column, GValue* value)
{
g_return_if_fail(M_IS_VIRTUAL_LIST(tree_model));
g_return_if_fail(iter!=NULL);
g_return_if_fail(column>=0);
struct VirtualList* virtual_list=M_VIRTUAL_LIST(tree_model);
uintptr_t row=(uintptr_t)iter->user_data;
unsigned int ucolumn=column;
//if (row == MAX_ROWS)
//{
// if (ucolumn == virtual_list->columns)
// {
// g_value_init(value, G_TYPE_BOOLEAN);
// g_value_set_boolean(value, false);
// return;
// }
// else
// {
// g_value_init(value, G_TYPE_STRING);
// //if(0);
// //else if (virtual_list->columns==1) g_value_set_string(value, "(sorry, not supported)");
// //else if (virtual_list->columns==2 && column==0) g_value_set_string(value, "(sorry, not");
// //else if (virtual_list->columns==2 && column==1) g_value_set_string(value, "supported)");
// //else if (virtual_list->columns==3 && column==0) g_value_set_string(value, "(sorry,");
// //else if (virtual_list->columns==3 && column==1) g_value_set_string(value, "not");
// //else if (virtual_list->columns==3 && column==2) g_value_set_string(value, "supported)");
// //else g_value_set_string(value, "");
// g_value_set_string(value, "...");
// }
// return;
//}
if (ucolumn == virtual_list->columns)
{
g_value_init(value, G_TYPE_BOOLEAN);
g_value_set_boolean(value, virtual_list->get_cell(row, -1) ? true : false);
return;
}
g_return_if_fail(ucolumn<virtual_list->columns);
if (row >= virtual_list->rows) g_return_if_reached();
g_value_init(value, G_TYPE_STRING);
const char * ret=virtual_list->get_cell(row, ucolumn);
g_value_set_string(value, ret);
}
static gboolean virtual_list_iter_next(GtkTreeModel* tree_model, GtkTreeIter* iter)
{
g_return_val_if_fail(M_IS_VIRTUAL_LIST(tree_model), FALSE);
if (!iter) return FALSE;
struct VirtualList* virtual_list=M_VIRTUAL_LIST(tree_model);
uintptr_t id = (uintptr_t)iter->user_data;
if (id >= virtual_list->rows-1) return FALSE;
iter->stamp = 0;
iter->user_data = (void*)(id+1);
return TRUE;
}
static gboolean virtual_list_iter_children(GtkTreeModel* tree_model, GtkTreeIter* iter, GtkTreeIter* parent)
{
g_return_val_if_fail(M_IS_VIRTUAL_LIST(tree_model), FALSE);
struct VirtualList* virtual_list=M_VIRTUAL_LIST(tree_model);
if (parent || virtual_list->rows==0) return FALSE;
iter->stamp=0;
iter->user_data=(void*)(uintptr_t)0;
return TRUE;
}
static gboolean virtual_list_iter_has_child(GtkTreeModel* tree_model, GtkTreeIter* iter)
{
return FALSE;
}
static gint virtual_list_iter_n_children(GtkTreeModel* tree_model, GtkTreeIter* iter)
{
g_return_val_if_fail(M_IS_VIRTUAL_LIST(tree_model), -1);
g_return_val_if_fail(iter==NULL || iter->user_data!=NULL, FALSE);
struct VirtualList* virtual_list=M_VIRTUAL_LIST(tree_model);
if (iter) return 0;
return virtual_list->rows;
}
static gboolean virtual_list_iter_nth_child(GtkTreeModel* tree_model, GtkTreeIter* iter, GtkTreeIter* parent, gint n)
{
g_return_val_if_fail(M_IS_VIRTUAL_LIST(tree_model), FALSE);
struct VirtualList* virtual_list=M_VIRTUAL_LIST(tree_model);
if (parent) return FALSE;
if (n<0) return FALSE;
if ((unsigned int)n >= virtual_list->rows) return FALSE;
iter->stamp = 0;
iter->user_data = (void*)(uintptr_t)n;
return TRUE;
}
static gboolean virtual_list_iter_parent(GtkTreeModel* tree_model, GtkTreeIter* iter, GtkTreeIter* child)
{
return FALSE;
}
static void virtual_list_tree_model_init (GtkTreeModelIface* iface)
{
iface->get_flags = virtual_list_get_flags;
iface->get_n_columns = virtual_list_get_n_columns;
iface->get_column_type = virtual_list_get_column_type;
iface->get_iter = virtual_list_get_iter;
iface->get_path = virtual_list_get_path;
iface->get_value = virtual_list_get_value;
iface->iter_next = virtual_list_iter_next;
iface->iter_children = virtual_list_iter_children;
iface->iter_has_child = virtual_list_iter_has_child;
iface->iter_n_children = virtual_list_iter_n_children;
iface->iter_nth_child = virtual_list_iter_nth_child;
iface->iter_parent = virtual_list_iter_parent;
}
static void init_widget()
{
if (M_VIRTUAL_TYPE != 0) return;
static const GTypeInfo virtual_list_info = {
sizeof(struct VirtualListClass),
NULL, NULL, NULL, NULL, NULL,
sizeof(struct VirtualList), 0, NULL };
static const GInterfaceInfo tree_model_info = { (GInterfaceInitFunc)virtual_list_tree_model_init, NULL, NULL };
M_VIRTUAL_TYPE = g_type_register_static(G_TYPE_OBJECT, "ArlibVirtualList", &virtual_list_info, (GTypeFlags)0);
g_type_add_interface_static(M_VIRTUAL_TYPE, GTK_TYPE_TREE_MODEL, &tree_model_info);
}
struct widget_listbox_virtual::impl {
GtkTreeView* tree;
gint borderheight;
gint checkwidth;
GtkCellRenderer* render;
struct VirtualList* vlist;
function<void(size_t row)> onfocus;
function<void(size_t row)> onactivate;
function<void(size_t row)> ontoggle;
};
void widget_listbox_virtual::construct(unsigned int numcolumns, const char * * columns)
{
init_widget();
m = new impl;
widget = gtk_scrolled_window_new(NULL, NULL);
widthprio = 3;
heightprio = 3;
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(widget), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
m->tree = GTK_TREE_VIEW(gtk_tree_view_new());
gtk_container_add(GTK_CONTAINER(widget), GTK_WIDGET(m->tree));
m->vlist = (VirtualList*)g_object_new(M_VIRTUAL_TYPE, NULL);
m->vlist->subject = this;
m->vlist->columns = numcolumns;
m->vlist->rows = 0;
m->vlist->checkboxes = false;
m->render = gtk_cell_renderer_text_new();
for (unsigned int i=0;i<numcolumns;i++)
{
gtk_tree_view_insert_column_with_attributes(m->tree, -1, columns[i], m->render, "text", i, NULL);
GtkTreeViewColumn* col = gtk_tree_view_get_column(m->tree, i);
gtk_tree_view_column_set_expand(col, true);
gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_FIXED);
}
gtk_widget_set_hexpand(GTK_WIDGET(this->widget), true);
gtk_widget_set_vexpand(GTK_WIDGET(this->widget), true);
gtk_tree_view_set_fixed_height_mode(m->tree, true);
gtk_widget_show_all(GTK_WIDGET(m->tree));
GtkRequisition size;
gtk_widget_get_preferred_size(GTK_WIDGET(m->tree), NULL, &size);
m->borderheight = size.height;
m->checkwidth = 0;
set_size(10, NULL, -1);
}
widget_listbox_virtual::~widget_listbox_virtual()
{
delete m;
}
//static void widget_listbox_virtual_refresh_row(widget_listbox_virtual* obj, size_t row)
//{
// GtkTreeIter iter;
// gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(obj->m->vlist), &iter, NULL, row);
// GtkTreePath* path=gtk_tree_path_new_from_indices(row, -1);
// gtk_tree_model_row_changed(GTK_TREE_MODEL(obj->m->vlist), path, &iter);
// gtk_tree_path_free(path);
//}
widget_listbox_virtual* widget_listbox_virtual::set_enabled(bool enable)
{
gtk_widget_set_sensitive(GTK_WIDGET(m->tree), enable);
return this;
}
widget_listbox_virtual* widget_listbox_virtual::set_contents(function<const char * (size_t row, int column)> get_cell,
function<size_t(const char * prefix, size_t start, bool up)> search)
{
//ignore the search function, there is no valid way for gtk+ to use it
m->vlist->get_cell=get_cell;
return this;
}
widget_listbox_virtual* widget_listbox_virtual::set_num_rows(size_t rows)
{
if (rows > MAX_ROWS) rows = MAX_ROWS;
GtkAdjustment* adj = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(m->tree));
double scrollfrac = gtk_adjustment_get_value(adj) / m->vlist->rows;
//this is piss slow for some reason I can't figure out
gtk_tree_view_set_model(m->tree, GTK_TREE_MODEL(NULL));
m->vlist->rows = rows;
gtk_tree_view_set_model(m->tree, GTK_TREE_MODEL(m->vlist));
if (scrollfrac == scrollfrac)
{
gtk_adjustment_set_upper(adj, scrollfrac * m->vlist->rows + gtk_adjustment_get_page_size(adj));
gtk_adjustment_changed(adj);
gtk_adjustment_set_value(adj, scrollfrac * m->vlist->rows);
gtk_adjustment_value_changed(adj);//shouldn't it do this by itself?
}
return this;
}
size_t widget_listbox_virtual::get_max_rows()
{
return MAX_ROWS;
}
widget_listbox_virtual* widget_listbox_virtual::refresh()
{
gtk_widget_queue_draw(GTK_WIDGET(m->tree));
return this;
}
size_t widget_listbox_virtual::get_active_row()
{
GList* list = gtk_tree_selection_get_selected_rows(gtk_tree_view_get_selection(m->tree), NULL);
if (!list) return (size_t)-1;
size_t ret = gtk_tree_path_get_indices((GtkTreePath*)list->data)[0];
//if (ret == MAX_ROWS) ret = (size_t)-1;
g_list_free_full(list, (GDestroyNotify)gtk_tree_path_free);
return ret;
}
static void listbox_on_focus_change(GtkTreeView* tree_view, gpointer user_data)
{
widget_listbox_virtual* obj=(widget_listbox_virtual*)user_data;
GtkTreePath* path;
gtk_tree_view_get_cursor(obj->m->tree, &path, NULL);
size_t item = (size_t)-1;
if (path) item = gtk_tree_path_get_indices(path)[0];
//if (item == MAX_ROWS) item = (size_t)-1;
obj->m->onfocus(item);
if (path) gtk_tree_path_free(path);
}
widget_listbox_virtual* widget_listbox_virtual::set_on_focus_change(function<void(size_t row)> onchange)
{
m->onfocus = onchange;
g_signal_connect(m->tree, "cursor-changed", G_CALLBACK(listbox_on_focus_change), this);
return this;
}
static void listbox_onactivate(GtkTreeView* tree_view, GtkTreePath* path, GtkTreeViewColumn* column, gpointer user_data)
{
widget_listbox_virtual* obj = (widget_listbox_virtual*)user_data;
int item = gtk_tree_path_get_indices(path)[0];
if (item != MAX_ROWS)
{
obj->m->onactivate(item);
}
}
widget_listbox_virtual* widget_listbox_virtual::set_onactivate(function<void(size_t row)> onactivate)
{
m->onactivate=onactivate;
g_signal_connect(m->tree, "row-activated", G_CALLBACK(listbox_onactivate), this);
return this;
}
widget_listbox_virtual* widget_listbox_virtual::set_size(unsigned int height, const char * const * widths, int expand)
{
gint width_total = 0;
for (unsigned int i=0;i<m->vlist->columns;i++)
{
//figuring out this algorithm took about half an hour of digging in the GTK+ source code.
//it's fairly simple, but it's spread out all over the place so I can't find the parts I want.
GtkTreeViewColumn* col = gtk_tree_view_get_column(m->tree, (m->vlist->checkboxes ? 1 : 0) + i);
gint size1;
gtk_widget_get_preferred_width(gtk_tree_view_column_get_button(col), NULL, &size1);
GtkRequisition size2;
if (widths && widths[i])
{
g_object_set(m->render, "text", widths[i], NULL);
gtk_cell_renderer_get_preferred_size(m->render, GTK_WIDGET(widget), NULL, &size2);
gint horizontal_separator;
gtk_widget_style_get(GTK_WIDGET(m->tree), "horizontal-separator", &horizontal_separator, NULL);
size2.width += horizontal_separator;
}
else size2.width = 0;
//there's also a grid_line_width (gtktreeview.c:6110 or near _gtk_tree_view_column_push_padding),
//but I don't use grid lines, so I'll ignore that.
gint colwidth = (size1 > size2.width ? size1 : size2.width);
gtk_tree_view_column_set_fixed_width(col, colwidth);
width_total += colwidth;
}
gtk_widget_set_size_request(GTK_WIDGET(m->tree), width_total + m->checkwidth, -1);
if (height)
{
GtkRequisition size;
g_object_set(m->render, "text", "A", NULL);
gtk_cell_renderer_get_preferred_size(m->render, GTK_WIDGET(widget), NULL, &size);
gtk_widget_set_size_request(GTK_WIDGET(widget), -1, m->borderheight + (size.height+2) * height);
}
for (unsigned int i=0;i<m->vlist->columns;i++)
{
gtk_tree_view_column_set_expand(gtk_tree_view_get_column(m->tree, i), (expand == -1) || (i == (unsigned int)expand));
}
return this;
}
static void widget_listbox_virtual_checkbox_toggle(GtkCellRendererToggle* cell_renderer, gchar* path, gpointer user_data)
{
widget_listbox_virtual* obj = (widget_listbox_virtual*)user_data;
unsigned int row = atoi(path);
obj->m->ontoggle(row);
obj->refresh();
}
widget_listbox_virtual* widget_listbox_virtual::add_checkboxes(function<void(size_t row)> ontoggle)
{
m->vlist->checkboxes = true;
GtkCellRenderer* render = gtk_cell_renderer_toggle_new();
gtk_tree_view_insert_column_with_attributes(m->tree, 0, "", render, "active", m->vlist->columns, NULL);
//gint checkheight;
//g_object_get(render, "height", &checkheight, NULL);
//if (checkheight>m->cellheight) m->cellheight=checkheight;
m->ontoggle=ontoggle;
g_signal_connect(render, "toggled", G_CALLBACK(widget_listbox_virtual_checkbox_toggle), this);
GtkRequisition checksize;
gtk_cell_renderer_get_preferred_size(render, GTK_WIDGET(widget), NULL, &checksize);
gint horizontal_separator;
gtk_widget_style_get(GTK_WIDGET(m->tree), "horizontal-separator", &horizontal_separator, NULL);
GtkTreeViewColumn* col = gtk_tree_view_get_column(m->tree, 0);
gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_FIXED);
gtk_tree_view_column_set_fixed_width(col, checksize.width + horizontal_separator);
m->checkwidth = checksize.width + horizontal_separator;
gint origwidth;
gtk_widget_get_size_request(GTK_WIDGET(m->tree), &origwidth, NULL);
printf("%i+%i+%i\n",origwidth,checksize.width, horizontal_separator);
gtk_widget_set_size_request(GTK_WIDGET(m->tree), m->checkwidth + origwidth, -1);
gtk_widget_show_all(GTK_WIDGET(widget));
return this;
}
#endif

358
arlib/gui/gtk3-misc.cpp Normal file
View File

@ -0,0 +1,358 @@
#include "window.h"
//#include "../image.h"
//#include "minir.h"
#include "../file.h"
#include "../os.h"
#ifdef ARGUI_GTK3
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>
#include <gtk/gtk.h>
#ifdef ARGUIPROT_X11
#include <gdk/gdkx.h>
#else
#error Only X11 supported.
#endif
//Number of ugly hacks: 8
//The status bar is a GtkGrid with GtkLabel, not a GtkStatusbar, because I couldn't get GtkStatusbar
// to cooperate. While the status bar is a GtkBox, I couldn't find how to get rid of its child.
//The status bar manages layout manually (by resizing the GtkLabels), because a GtkGrid with a large
// number of slots seem to assign pixels 3,3,2,2 if told to split 10 pixels to 4 equally sized
// slots, as opposed to 2,3,2,3 or 3,2,3,2.
//Label widgets that ellipsize, as well as status bar labels, are declared with max width 1, and
// then ellipsizes. Apparently they use more space than maximum if they can. This doesn't seem to be
// documented, and is therefore not guaranteed to continue working.
//gtk_main_iteration_do(false) is called twice, so GTK thinks we're idle and sends out the mouse
// move events. Most of the time is spent waiting for A/V drivers, and our mouse move processor is
// cheap. (Likely fixable in GTK+ 3.12, but I'm on 3.8.)
//Refreshing a listbox is done by telling it to redraw, not by telling it that contents have changed.
// It's either that or send tens of thousands of contents-changed events, and I'd rather not.
//gtk_widget_override_background_color does nothing to GtkTextEntry, due to a background-image
// gradient. I had to throw a bit of their fancy CSS at it.
//GtkTreeView has non-constant performance for creating a pile of rows, and gets terrible around
// 100000. I had to cap it to 65536.
//The size of GtkTreeView is complete nonsense. I haven't found how to get it to give out its real
// row height, nor could I figure out what the nonsense I get from the tell-me-your-height functions
// is, but I am sure that whatever tricks I will need to pull fits here.
//static GdkFilterReturn scanfilter(GdkXEvent* xevent, GdkEvent* event, gpointer data)
//{
// XEvent* ev=(XEvent*)xevent;
// if (ev->type==Expose) printf("ex=%lu\n", ev->xexpose.window);
// return GDK_FILTER_CONTINUE;
//}
//#include<sys/resource.h>
#ifdef ARGUIPROT_X11
struct window_x11_info window_x11;
#endif
void window_init(int * argc, char * * argv[])
{
//struct rlimit core_limits;core_limits.rlim_cur=core_limits.rlim_max=64*1024*1024;setrlimit(RLIMIT_CORE,&core_limits);
#ifdef DEBUG
g_log_set_always_fatal((GLogLevelFlags)(G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING));
#endif
#ifdef ARGUIPROT_X11
XInitThreads();
#endif
gtk_init(argc, argv);
_window_init_file();
//gdk_window_add_filter(NULL,scanfilter,NULL);
//#ifndef NO_ICON
// struct image img;
// png_decode(icon_minir_64x64_png,sizeof(icon_minir_64x64_png), &img, fmt_argb8888);
// //we could tell it how to free this, but it will be used until replaced, and it won't be replaced.
// gtk_window_set_default_icon(gdk_pixbuf_new_from_data((guchar*)img.pixels, GDK_COLORSPACE_RGB, true, 8, 64,64, 64*4, NULL, NULL));
//#endif
#ifdef ARGUIPROT_X11
window_x11.display=gdk_x11_get_default_xdisplay();
window_x11.screen=gdk_x11_get_default_screen();
window_x11.root=gdk_x11_get_default_root_xwindow();//alternatively XRootWindow(window_x11.display, window_x11.screen)
#endif
errno=0;
}
file* file::create(const char * filename)
{
//TODO
return create_fs(filename);
}
static void * mem_from_g_alloc(void * mem, size_t size)
{
if (g_mem_is_system_malloc()) return mem;
if (!size) size=strlen((char*)mem)+1;
void * ret=malloc(size);
memcpy(ret, mem, size);
g_free(ret);
return ret;
}
//enum mbox_sev { mb_info, mb_warn, mb_err };
//enum mbox_btns { mb_ok, mb_okcancel, mb_yesno };
bool window_message_box(const char * text, const char * title, enum mbox_sev severity, enum mbox_btns buttons)
{
//"Please note that GTK_BUTTONS_OK, GTK_BUTTONS_YES_NO and GTK_BUTTONS_OK_CANCEL are discouraged by the GNOME HIG."
//I do not listen to advise without a rationale. Tell me which section it violates, and I'll consider it.
GtkMessageType sev[3]={ GTK_MESSAGE_OTHER, GTK_MESSAGE_WARNING, GTK_MESSAGE_ERROR };
GtkButtonsType btns[3]={ GTK_BUTTONS_OK, GTK_BUTTONS_OK_CANCEL, GTK_BUTTONS_YES_NO };
GtkWidget* dialog=gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, sev[severity], btns[buttons], "%s", text);
gint ret=gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_destroy(dialog);
return (ret==GTK_RESPONSE_ACCEPT || ret==GTK_RESPONSE_OK || ret==GTK_RESPONSE_YES);
}
const char * const * window_file_picker(struct window * parent,
const char * title,
const char * const * extensions,
const char * extdescription,
bool dylib,
bool multiple)
{
static char * * ret=NULL;
if (ret)
{
char * * del=ret;
while (*del)
{
free(*del);
del++;
}
free(ret);
ret=NULL;
}
GtkFileChooser* dialog=GTK_FILE_CHOOSER(
gtk_file_chooser_dialog_new(
title,
GTK_WINDOW(parent?(void*)parent->_get_handle():NULL),
GTK_FILE_CHOOSER_ACTION_OPEN,
"_Cancel",
GTK_RESPONSE_CANCEL,
"_Open",
GTK_RESPONSE_ACCEPT,
NULL));
gtk_file_chooser_set_select_multiple(dialog, multiple);
gtk_file_chooser_set_local_only(dialog, dylib);
GtkFileFilter* filter;
if (*extensions)
{
filter=gtk_file_filter_new();
gtk_file_filter_set_name(filter, extdescription);
char extstr[64];
extstr[0]='*';
extstr[1]='.';
while (*extensions)
{
strcpy(extstr+2, *extensions+(**extensions=='.'));
gtk_file_filter_add_pattern(filter, extstr);
extensions++;
}
gtk_file_chooser_add_filter(dialog, filter);
}
filter=gtk_file_filter_new();
gtk_file_filter_set_name(filter, "All files");
gtk_file_filter_add_pattern(filter, "*");
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
if (gtk_dialog_run(GTK_DIALOG(dialog))!=GTK_RESPONSE_ACCEPT)
{
gtk_widget_destroy(GTK_WIDGET(dialog));
return NULL;
}
GSList * list=gtk_file_chooser_get_uris(dialog);
gtk_widget_destroy(GTK_WIDGET(dialog));
unsigned int listlen=g_slist_length(list);
if (!listlen)
{
g_slist_free(list);
return NULL;
}
ret=malloc(sizeof(char*)*(listlen+1));
char * * retcopy=ret;
GSList * listcopy=list;
while (listcopy)
{
*retcopy=window_get_absolute_path(NULL, (char*)listcopy->data, true);
g_free(listcopy->data);
retcopy++;
listcopy=listcopy->next;
}
ret[listlen]=NULL;
g_slist_free(list);
return (const char * const *)ret;
}
void window_run_iter()
{
gtk_main_iteration_do(false);
//Workaround for GTK thinking we're processing events slower than they come in. We're busy waiting
// for the AV drivers, and waiting less costs us nothing.
gtk_main_iteration_do(false);
}
void window_run_wait()
{
gtk_main_iteration_do(true);
}
char * window_get_absolute_path(const char * basepath, const char * path, bool allow_up)
{
if (!path) return NULL;
GFile* file;
const char * pathend = NULL; // gcc bug: this initialization does nothing, but gcc whines
if (basepath)
{
pathend = strrchr(basepath, '/');
gchar * basepath_dir = g_strndup(basepath, pathend+1-basepath);
file = g_file_new_for_commandline_arg_and_cwd(path, basepath_dir);
g_free(basepath_dir);
}
else
{
if (!allow_up) return NULL;
//not sure if gvfs URIs are absolute or not, so if absolute, let's use the one that works for both
//if not absolute, demand it's an URI
//per glib/gconvert.c g_filename_from_uri, file:// URIs are absolute
if (g_path_is_absolute(path)) file = g_file_new_for_commandline_arg(path);
else file = g_file_new_for_uri(path);
}
gchar * ret;
if (g_file_is_native(file)) ret = g_file_get_path(file);
else ret = g_file_get_uri(file);
g_object_unref(file);
if (!ret) return NULL;
if (!allow_up && strncmp(basepath, ret, pathend+1-basepath) != 0)
{
g_free(ret);
return NULL;
}
return (char*)mem_from_g_alloc(ret, 0);
}
char * window_get_native_path(const char * path)
{
if (!path) return NULL;
GFile* file=g_file_new_for_commandline_arg(path);
gchar * ret=g_file_get_path(file);
g_object_unref(file);
if (!ret) return NULL;
return (char*)mem_from_g_alloc(ret, 0);
}
uint64_t window_get_time()
{
return g_get_monotonic_time();
}
bool file_read(const char * filename, void* * data, size_t * len)
{
if (!filename) return false;
GFile* file=g_file_new_for_commandline_arg(filename);
if (!file) return false;
char* ret;
gsize glen;
if (!g_file_load_contents(file, NULL, &ret, &glen, NULL, NULL))
{
g_object_unref(file);
return false;
}
g_object_unref(file);
if (len) *len=glen;
*data=mem_from_g_alloc(ret, glen);
return true;
}
bool file_write(const char * filename, const anyptr data, size_t len)
{
if (!filename) return false;
if (!len) return true;
GFile* file=g_file_new_for_commandline_arg(filename);
if (!file) return false;
bool success=g_file_replace_contents(file, data, len, NULL, false, G_FILE_CREATE_NONE, NULL, NULL, NULL);
g_object_unref(file);
return success;
}
bool file_read_to(const char * filename, anyptr data, size_t len)
{
if (!filename) return false;
if (!len) return true;
GFile* file=g_file_new_for_commandline_arg(filename);
if (!file) return false;
GFileInputStream* io=g_file_read(file, NULL, NULL);
if (!io)
{
g_object_unref(file);
return false;
}
GFileInfo* info=g_file_input_stream_query_info(io, G_FILE_ATTRIBUTE_STANDARD_SIZE, NULL, NULL);
gsize size=g_file_info_get_size(info);
if (size!=len) return false;
gsize actualsize;
bool success=g_input_stream_read_all(G_INPUT_STREAM(io), data, size, &actualsize, NULL, NULL);
g_input_stream_close(G_INPUT_STREAM(io), NULL, NULL);
g_object_unref(file);
g_object_unref(io);
g_object_unref(info);
if (!success || size!=actualsize)
{
memset(data, 0, len);
return false;
}
return true;
}
void* file_find_create(const char * path)
{
if (!path) return NULL;
GFile* parent=g_file_new_for_path(path);
GFileEnumerator* children=g_file_enumerate_children(parent, G_FILE_ATTRIBUTE_STANDARD_NAME "," G_FILE_ATTRIBUTE_STANDARD_TYPE,
G_FILE_QUERY_INFO_NONE, NULL, NULL);
g_object_unref(parent);
return children;
}
bool file_find_next(void* find, char * * path, bool * isdir)
{
if (!find) return false;
GFileEnumerator* children=(GFileEnumerator*)find;
GFileInfo* child=g_file_enumerator_next_file(children, NULL, NULL);
if (!child) return false;
*path=strdup(g_file_info_get_name(child));
*isdir=(g_file_info_get_file_type(child)==G_FILE_TYPE_DIRECTORY);
g_object_unref(child);
return true;
}
void file_find_close(void* find)
{
if (!find) return;
g_object_unref((GFileEnumerator*)find);
}
#endif

667
arlib/gui/gtk3-shell.cpp Normal file
View File

@ -0,0 +1,667 @@
#include "window.h"
#ifdef ARGUI_GTK3
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <gtk/gtk.h>
#ifdef ARGUIPROT_X11
#include <gdk/gdkx.h>
#endif
static gint get_widget_height(GtkWidget* widget)
{
if (!widget) return 0;
GtkRequisition size;
gtk_widget_get_preferred_size(widget, NULL, &size);
return size.height;
}
static GtkMenuShell* get_menu_bar_widget(windowmenu_menu* menu);
namespace {
static bool in_callback=false;
class window_gtk3 : public window {
public:
GtkWindow* wndw;
GtkGrid* grid;
widget_base* contents;
windowmenu_menu* menu;
GtkGrid* status;
int * status_pos;
int status_count;
bool visible;
//uint8_t delayfree;//0=normal, 1=can't free now, 2=free at next opportunity
//char padding1[2];
function<void(int newwidth, int newheight)> onmove;
function<bool()> onclose;
// /*private*/ void statusbar_resize();
window_gtk3(widget_base* contents_)
{
this->wndw=GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
gtk_window_set_gravity(this->wndw, GDK_GRAVITY_STATIC);
g_signal_connect(this->wndw, "delete-event", G_CALLBACK(onclose_gtk), this);//GtkWidget delete-event maybe
gtk_window_set_has_resize_grip(this->wndw, false);
gtk_window_set_resizable(this->wndw, false);
this->grid=GTK_GRID(gtk_grid_new());
gtk_container_add(GTK_CONTAINER(this->wndw), GTK_WIDGET(this->grid));
this->contents=contents_;
gtk_grid_attach(this->grid, GTK_WIDGET(this->contents->widget), 0,1, 1,1);
this->menu = NULL;
this->status = NULL;
this->status_pos = NULL;
this->status_count = 0;
this->onclose = NULL;
this->visible=false;
//GdkRGBA color={0,0,1,1};
//gtk_widget_override_background_color(GTK_WIDGET(this->wndw),GTK_STATE_FLAG_NORMAL,&color);
}
/*private*/ void resize(unsigned int width, unsigned int height)
{
gtk_window_resize(this->wndw, width,
(this->menu ? get_widget_height(GTK_WIDGET(this->menu->m)) : 0)+
height+
(this->status ? get_widget_height(GTK_WIDGET(this->status)) : 0));
if (this->status) this->statusbar_resize();
}
// /*private*/ static gboolean onclose_gtk(GtkWidget* widget, GdkEvent* event, gpointer user_data);
/*private*/ static gboolean popup_esc_close(GtkWidget* widget, GdkEvent* event, gpointer user_data)
{
if (event->key.keyval==GDK_KEY_Escape)
{
onclose_gtk(widget, event, user_data);
return TRUE;
}
return FALSE;
}
void set_is_dialog()
{
gtk_widget_add_events(GTK_WIDGET(this->wndw), GDK_KEY_PRESS_MASK);
g_signal_connect(this->wndw, "key-press-event", G_CALLBACK(popup_esc_close), this);
gtk_window_set_type_hint(this->wndw, GDK_WINDOW_TYPE_HINT_DIALOG);
}
void set_parent(struct window * parent_)
{
struct window_gtk3 * parent=(struct window_gtk3*)parent_;
gtk_window_set_transient_for(this->wndw, parent->wndw);
}
void set_modal(bool modal)
{
gtk_window_set_modal(this->wndw, modal);
}
void set_resizable(bool resizable, function<void(unsigned int newwidth, unsigned int newheight)> onresize)
{
gtk_window_set_resizable(this->wndw, resizable);
if (this->status) this->statusbar_resize();
}
void get_pos(int * x, int * y)
{
gtk_window_get_position(this->wndw, x, y);
}
void set_pos(int x, int y)
{
in_callback=true;
gdk_window_move(gtk_widget_get_window(GTK_WIDGET(this->wndw)), x, y);
in_callback=false;
}
/*private*/ static gboolean onmove_activate(GtkWidget* widget, GdkEvent* event, gpointer user_data)
{
struct window_gtk3 * p=(struct window_gtk3*)user_data;
if (!in_callback) p->onmove(event->configure.x, event->configure.y);
return FALSE;
}
void set_onmove(function<void(int x, int y)> onmove)
{
this->onmove=onmove;
gtk_widget_add_events(GTK_WIDGET(this->wndw), GDK_STRUCTURE_MASK);
g_signal_connect(this->wndw, "configure-event", G_CALLBACK(onmove_activate), this);
}
void set_title(const char * title)
{
gtk_window_set_title(this->wndw, title);
}
void set_onclose(function<bool()> onclose)
{
this->onclose = onclose;
}
void set_menu(windowmenu_menu* menu)
{
delete this->menu;
this->menu=menu;
gtk_grid_attach(this->grid, GTK_WIDGET(get_menu_bar_widget(this->menu)), 0,0, 1,1);
gtk_widget_show_all(GTK_WIDGET(get_menu_bar_widget(this->menu)));
}
void statusbar_create(int numslots, const int * align, const int * dividerpos)
{
if (this->status) gtk_widget_destroy(GTK_WIDGET(this->status));
this->status = NULL;
//if (this->status_parent) gtk_widget_destroy(GTK_WIDGET(this->status_parent));
//this->status_parent=NULL;
free(this->status_pos);
this->status_pos = NULL;
gtk_window_set_has_resize_grip(this->wndw, (bool)numslots);
if (!numslots) return;
this->status = GTK_GRID(gtk_grid_new());
//gtk_box_set_spacing(box, 2);
for (int i=0;i<numslots;i++)
{
GtkWidget* label = gtk_label_new("");
GValue val = G_VALUE_INIT;
g_value_init(&val, G_TYPE_INT);
g_value_set_int(&val, 2);
g_object_set_property(G_OBJECT(label), "margin", &val);
//gtk_widget_set_margin_top(label, 2);
//gtk_widget_set_margin_bottom(label, 2);
//gtk_widget_set_margin_left(label, 2);
//gtk_widget_set_margin_right(label, 2);
//printf("a=%i\n",align[i]);
//const GtkJustification just[]={ GTK_JUSTIFY_LEFT, GTK_JUSTIFY_CENTER, GTK_JUSTIFY_RIGHT };
//gtk_label_set_justify(label, just[align[i]]);
gtk_label_set_single_line_mode(GTK_LABEL(label), true);
gtk_label_set_ellipsize(GTK_LABEL(label), PANGO_ELLIPSIZE_END);
gtk_label_set_max_width_chars(GTK_LABEL(label), 1);//why does this work
gtk_misc_set_alignment(GTK_MISC(label), ((float)align[i])/2, 0.5);
//const GtkAlign gtkalign[]={ GTK_ALIGN_START, GTK_ALIGN_CENTER, GTK_ALIGN_END };
//gtk_widget_set_halign(label, gtkalign[align[i]]);
gtk_grid_attach(this->status, label, i,0, 1,1);
//GdkRGBA color={1,0,i&1,1};
//gtk_widget_override_background_color(label,GTK_STATE_FLAG_NORMAL,&color);
}
this->status_count = numslots;
this->status_pos = malloc(sizeof(int)*numslots);
memcpy(this->status_pos, dividerpos, sizeof(int)*(numslots-1));
this->status_pos[numslots-1] = 240;
//gtk_widget_set_size_request(GTK_WIDGET(this->status), width, -1);
//this->status_parent=GTK_FRAME(gtk_frame_new(NULL));
//gtk_frame_set_shadow_type(this->status_parent, GTK_SHADOW_IN);
//gtk_container_add(GTK_CONTAINER(this->status_parent), GTK_WIDGET(this->status));
//
//gtk_grid_attach(this->grid, GTK_WIDGET(this->status_parent), 0,2, 1,1);
gtk_grid_attach(this->grid, GTK_WIDGET(this->status), 0,2, 1,1);
gtk_widget_show_all(GTK_WIDGET(this->status));
this->statusbar_resize();
}
void statusbar_set(int slot, const char * text)
{
GtkLabel* label=GTK_LABEL(gtk_grid_get_child_at(this->status, slot, 0));
gtk_label_set_text(label, text);
}
/*private*/ void statusbar_resize()
{
//if (width<0) gtk_widget_get_size_request(GTK_WIDGET(this->contents), &width, NULL);
unsigned int width;
if (gtk_window_get_resizable(this->wndw))
{
//TODO - this probably bans shrinking. And doesn't resize on window resize, either.
GtkAllocation size;
gtk_widget_get_allocation(GTK_WIDGET(this->contents->widget), &size);
width=size.width;
}
else
{
GtkRequisition size;
gtk_widget_get_preferred_size(GTK_WIDGET(this->contents->widget), &size, NULL);
width=size.width;
}
if (width<=1) return;
width-=(this->status_count*2*2);
if (gtk_window_get_resizable(this->wndw))
{
gint gripwidth;
gtk_widget_style_get(GTK_WIDGET(this->wndw), "resize-grip-width", &gripwidth, NULL);
width-=gripwidth;
}
int lastpos=0;
for (int i=0;i<this->status_count;i++)
{
int nextpos=(width*this->status_pos[i] + 120)/240;
GtkWidget* label=gtk_grid_get_child_at(this->status, i, 0);
gtk_widget_set_size_request(label, nextpos-lastpos, -1);
lastpos=nextpos;
}
}
void replace_contents(widget_base* contents)
{
gtk_widget_destroy(GTK_WIDGET(this->contents->widget));
delete this->contents;
gtk_grid_attach(this->grid, GTK_WIDGET(this->contents->widget), 0,1, 1,1);
this->contents = contents;
}
bool is_visible()
{
return this->visible;
}
void set_visible(bool visible)
{
this->visible=visible;
if (visible)
{
gtk_widget_show_all(GTK_WIDGET(this->wndw));
this->statusbar_resize();
}
else gtk_widget_hide(GTK_WIDGET(this->wndw));
}
void focus()
{
gtk_window_present(this->wndw);
}
bool is_active()
{
if (this->menu && gtk_menu_shell_get_selected_item(GTK_MENU_SHELL(get_menu_bar_widget(this->menu)))!=NULL) return false;
return gtk_window_is_active(this->wndw);
}
bool menu_active()
{
if (this->menu && gtk_menu_shell_get_selected_item(GTK_MENU_SHELL(get_menu_bar_widget(this->menu)))!=NULL) return true;
return false;
}
~window_gtk3()
{
//struct window_gtk3 * this=(struct window_gtk3*)this_;
//if (this->delayfree)
//{
// this->delayfree=2;
// return;
//}
delete this->contents;
delete this->menu;
gtk_widget_destroy(GTK_WIDGET(this->wndw));
free(this->status_pos);
//free(this);
}
uintptr_t _get_handle()
{
return (uintptr_t)this->wndw;
}
static gboolean onclose_gtk(GtkWidget* widget, GdkEvent* event, gpointer user_data)
{
struct window_gtk3 * This=(struct window_gtk3*)user_data;
if (This->onclose)
{
//This->delayfree=1;
if (This->onclose()==false)
{
//This->delayfree=0;
return TRUE;
}
//if (This->delayfree==2)
//{
// This->delayfree=0;
// free_((struct window*)This);
// return TRUE;
//}
//This->delayfree=0;
}
This->visible=false;
gtk_widget_hide(GTK_WIDGET(This->wndw));
return TRUE;
}
};
}
window* window_create(widget_base* contents)
{
return new window_gtk3(contents);
}
enum menu_type { mtype_item, mtype_check, mtype_radio, mtype_sep, mtype_sub }; // nothing for 'top' since it doesn't have this struct
struct windowmenu::impl {
GtkMenuItem* widget;
uint8_t type;
uint8_t nativepos;
//char padding[6];
};
static void menu_set_text(GtkMenuItem* item, const char * text)
{
GtkWidget* label=gtk_bin_get_child(GTK_BIN(item));
if (*text=='_') gtk_label_set_text_with_mnemonic(GTK_LABEL(label), text+1);
else gtk_label_set_text(GTK_LABEL(label), text);
}
struct windowmenu_item::impl {
function<void(void)> onactivate;
};
static void menu_activate_normal(GtkMenuItem* menuitem, gpointer user_data)
{
windowmenu_item* menu=(windowmenu_item*)user_data;
menu->mu->onactivate();
}
windowmenu_item* windowmenu_item::create(const char * text, function<void(void)> onactivate)
{
windowmenu_item* menu = new windowmenu_item;
menu->m = new windowmenu::impl;
menu->m->type = mtype_item;
menu->m->widget = GTK_MENU_ITEM(gtk_menu_item_new_with_label(""));
menu->mu = new windowmenu_item::impl;
menu->mu->onactivate = onactivate;
g_signal_connect(menu->m->widget, "activate", G_CALLBACK(menu_activate_normal), menu);
menu_set_text(menu->m->widget, text);
return menu;
}
void windowmenu_item::set_enabled(bool enable)
{
gtk_widget_set_sensitive(GTK_WIDGET(this->m->widget), enable);
}
windowmenu_item::~windowmenu_item() {}
struct windowmenu_check::impl
{
function<void(bool checked)> onactivate;
bool block_signals;
};
static void menu_activate_check(GtkMenuItem* menuitem, gpointer user_data)
{
windowmenu_check* menu=(windowmenu_check*)user_data;
if (menu->mu->block_signals) return;
menu->mu->onactivate(gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menu->m->widget)));
}
void windowmenu_check::set_enabled(bool enable)
{
gtk_widget_set_sensitive(GTK_WIDGET(this->m->widget), enable);
}
bool windowmenu_check::get_checked()
{
return gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(this->m->widget));
}
void windowmenu_check::set_checked(bool checked)
{
this->mu->block_signals=true;
gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(this->m->widget), checked);
this->mu->block_signals=false;
}
windowmenu_check* windowmenu_check::create(const char * text, function<void(bool checked)> onactivate)
{
windowmenu_check* ret = new windowmenu_check;
ret->m = new windowmenu::impl;
ret->m->type=mtype_check;
ret->m->widget = GTK_MENU_ITEM(gtk_check_menu_item_new_with_label(""));
g_signal_connect(ret->m->widget, "activate", G_CALLBACK(menu_activate_check), ret);
ret->mu = new windowmenu_check::impl;
ret->mu->onactivate = onactivate;
ret->mu->block_signals=false;
menu_set_text(ret->m->widget, text);
return ret;
}
windowmenu_check::~windowmenu_check() {}
namespace {
struct windowmenu_radio_child {
GtkRadioMenuItem* widget;
windowmenu_radio* parent;
};
}
struct windowmenu_radio::impl
{
windowmenu_radio_child* children;
unsigned int numchildren;
unsigned int state;
function<void(unsigned int state)> onactivate;
};
static void menu_activate_radio(GtkMenuItem* menuitem, gpointer user_data)
{
windowmenu_radio_child* item = (windowmenu_radio_child*)user_data;
windowmenu_radio* menu = item->parent;
if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item->widget))) return;
unsigned int newstate = item - menu->mu->children;
if (newstate != menu->mu->state)
{
menu->mu->state = newstate;
menu->mu->onactivate(menu->mu->state);
}
}
void windowmenu_radio::set_enabled(bool enable)
{
gtk_widget_set_sensitive(GTK_WIDGET(this->m->widget), enable);
}
unsigned int windowmenu_radio::get_state()
{
return this->mu->state;
}
void windowmenu_radio::set_state(unsigned int state)
{
this->mu->state = state;
gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(this->mu->children[state].widget), true);
}
windowmenu_radio* windowmenu_radio::create(unsigned int numitems, const char * const * texts,
function<void(unsigned int state)> onactivate)
{
windowmenu_radio* ret = new windowmenu_radio;
ret->m = new windowmenu::impl;
ret->m->type = mtype_radio;
ret->m->widget = NULL;
ret->mu = new windowmenu_radio::impl;
ret->mu->numchildren = numitems;
ret->mu->children = malloc(sizeof(windowmenu_radio_child)*numitems);
GSList* group=NULL;
for (unsigned int i=0;i<numitems;i++)
{
GtkRadioMenuItem* widget = GTK_RADIO_MENU_ITEM(gtk_radio_menu_item_new_with_label(group, ""));
group = gtk_radio_menu_item_get_group(widget);
menu_set_text(GTK_MENU_ITEM(widget), texts[i]);
g_signal_connect(widget, "activate", G_CALLBACK(menu_activate_radio), &ret->mu->children[i]);
ret->mu->children[i].widget = widget;
ret->mu->children[i].parent = ret;
}
ret->mu->state = 0;
gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(ret->mu->children[0].widget), true);
ret->mu->onactivate = onactivate;
return ret;
}
windowmenu_radio::~windowmenu_radio()
{
for (unsigned int i=1;i<this->mu->numchildren;i++)
{
gtk_widget_destroy(GTK_WIDGET(this->mu->children[i].widget));
}
free(this->mu->children);
}
struct windowmenu_separator::impl {};
windowmenu_separator* windowmenu_separator::create()
{
windowmenu_separator* ret = new windowmenu_separator;
ret->m = new windowmenu::impl;
ret->m->type = mtype_sep;
ret->m->widget = GTK_MENU_ITEM(gtk_separator_menu_item_new());
return ret;
}
windowmenu_separator::~windowmenu_separator() {}
struct windowmenu_menu::impl
{
public:
GtkMenuShell* container;
struct windowmenu* * children;
uint8_t numchildren;
//char padding[7];
};
static unsigned int menu_get_native_length(windowmenu* menu)
{
return (menu->m->type==mtype_radio ? ((windowmenu_radio*)menu)->mu->numchildren : 1);
}
//can't return children[pos]->nativepos because 'pos' may be the number of entries in the menu
//children[pos]->nativepos can also be wrong because it was recently inserted
static unsigned int menu_get_native_start(windowmenu_menu* menu, unsigned int pos)
{
return (pos ? menu->mu->children[pos-1]->m->nativepos + menu_get_native_length(menu->mu->children[pos-1]) : 0);
}
static void menu_renumber(windowmenu_menu* menu, unsigned int start)
{
unsigned int menupos = menu_get_native_start(menu, start);
for (unsigned int i=start;i<menu->mu->numchildren;i++)
{
windowmenu* child = menu->mu->children[i];
child->m->nativepos = menupos;
menupos += menu_get_native_length(child);
}
}
void windowmenu_menu::insert_child(unsigned int pos, windowmenu* child)
{
unsigned int menupos = menu_get_native_start(this, pos);
if (child->m->type!=mtype_radio)
{
gtk_menu_shell_insert(this->mu->container, GTK_WIDGET(child->m->widget), menupos);
gtk_widget_show_all(GTK_WIDGET(child->m->widget));
}
else
{
windowmenu_radio* rchild = (windowmenu_radio*)child;
for (unsigned int i=0;i<rchild->mu->numchildren;i++)
{
gtk_menu_shell_insert(this->mu->container, GTK_WIDGET(rchild->mu->children[i].widget), menupos);
gtk_widget_show_all(GTK_WIDGET(rchild->mu->children[i].widget));
menupos++;
}
}
if ((this->mu->numchildren&(this->mu->numchildren-1))==0)
{
this->mu->children=realloc(this->mu->children, (this->mu->numchildren+1)*2*sizeof(windowmenu*));
}
memmove(this->mu->children+pos+1, this->mu->children+pos, (this->mu->numchildren - pos)*sizeof(windowmenu*));
this->mu->children[pos]=child;
this->mu->numchildren++;
menu_renumber(this, pos);
}
void windowmenu_menu::remove_child(windowmenu* child)
{
this->mu->numchildren--;
unsigned int pos=0;
while (this->mu->children[pos]!=child) pos++;
memmove(this->mu->children+pos, this->mu->children+pos+1, (this->mu->numchildren - pos)*sizeof(windowmenu*));
delete child;
}
windowmenu_menu* windowmenu_menu::create(const char * text, unsigned int numchildren, windowmenu* const * children)
{
windowmenu_menu* ret = new windowmenu_menu;
ret->m = new windowmenu::impl;
ret->m->type = mtype_sub;
ret->m->widget = GTK_MENU_ITEM(gtk_menu_item_new_with_label(""));
menu_set_text(ret->m->widget, text);
ret->mu = new windowmenu_menu::impl;
ret->mu->container = GTK_MENU_SHELL(gtk_menu_new());
ret->mu->children = NULL;
ret->mu->numchildren = 0;
for (unsigned int i=0;i<numchildren;i++) ret->insert_child(i, children[i]);
gtk_menu_item_set_submenu(ret->m->widget, GTK_WIDGET(ret->mu->container));
return ret;
}
windowmenu_menu* windowmenu_menu::create_top(unsigned int numchildren, windowmenu_menu* const * children)
{
windowmenu_menu* ret = new windowmenu_menu;
ret->m = NULL;
ret->mu = new windowmenu_menu::impl;
ret->mu->container = GTK_MENU_SHELL(gtk_menu_bar_new());
ret->mu->children = NULL;
ret->mu->numchildren = 0;
for (unsigned int i=0;i<numchildren;i++) ret->insert_child(i, children[i]);
return ret;
}
windowmenu_menu::~windowmenu_menu()
{
for (unsigned int i=0;i<this->mu->numchildren;i++) delete this->mu->children[i];
free(this->mu->children);
}
static GtkMenuShell* get_menu_bar_widget(windowmenu_menu* menu) { return menu->mu->container; }
#endif

20
arlib/gui/none.cpp Normal file
View File

@ -0,0 +1,20 @@
#include "window.h"
#include "../file.h"
#include "../os.h"
#ifdef ARGUI_MINIMAL
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
void window_init(int * argc, char * * argv[]) {}
uint64_t window_get_time()
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return ts.tv_sec*1000000 + ts.tv_nsec/1000;
}
file* file::create(const char * filename) { return create_fs(filename); }
#endif

496
arlib/gui/shared.cpp Normal file
View File

@ -0,0 +1,496 @@
#include "window.h"
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#ifndef ARGUI_NONE
#if defined(ARGUI_MANUAL_LAYOUT)
//This is in practice only used on Windows, but it's theoretically usable on other operating systems too. Maybe I'll need it on OSX.
widget_padding::widget_padding(bool vertical)
{
this->width=0;
this->height=0;
this->widthprio=(vertical ? 0 : 2);
this->heightprio=(vertical ? 2 : 0);
}
unsigned int widget_padding::init(struct window * parent, uintptr_t parenthandle) { return 0; }
void widget_padding::measure() {}
void widget_padding::place(void* resizeinf, unsigned int x, unsigned int y, unsigned int width, unsigned int height) {}
widget_padding::~widget_padding() {}
struct widget_layout::impl {
unsigned int numchildren;
bool uniform[2];
//char padding[2];
unsigned int totsize[2];
widget_base* * children;
unsigned int * startpos[2];
unsigned int * extent[2];
};
void widget_layout::construct(unsigned int numchildren, widget_base* * children,
unsigned int totwidth, unsigned int * widths, bool uniformwidths,
unsigned int totheight, unsigned int * heights, bool uniformheights)
{
m=new impl;
m->numchildren=numchildren;
m->uniform[0]=uniformwidths;
m->uniform[1]=uniformheights;
m->totsize[0]=totwidth;
m->totsize[1]=totheight;
m->children=malloc(sizeof(struct widget_base*)*numchildren);
memcpy(m->children, children, sizeof(struct widget_base*)*numchildren);
for (unsigned int dir=0;dir<2;dir++)
{
m->extent[dir]=malloc(sizeof(unsigned int)*numchildren);
unsigned int * sizes=(dir==0 ? widths : heights);
if (sizes)
{
memcpy(m->extent[dir], sizes, sizeof(unsigned int)*numchildren);
}
else
{
for (unsigned int i=0;i<numchildren;i++) m->extent[dir][i]=1;
}
}
m->startpos[0]=malloc(sizeof(unsigned int)*numchildren);
m->startpos[1]=malloc(sizeof(unsigned int)*numchildren);
bool * posused=malloc(sizeof(bool)*(totheight*totwidth));
memset(posused, 0, sizeof(bool)*(totheight*totwidth));
unsigned int firstempty=0;
for (unsigned int i=0;i<numchildren;i++)
{
while (posused[firstempty]) firstempty++;
m->startpos[0][i]=firstempty%m->totsize[0];
m->startpos[1][i]=firstempty/m->totsize[0];
for (unsigned int x=0;x<m->extent[0][i];x++)
for (unsigned int y=0;y<m->extent[1][i];y++)
{
posused[firstempty + y*m->totsize[0] + x]=true;
}
}
free(posused);
}
widget_layout::~widget_layout()
{
for (unsigned int i=0;i<m->numchildren;i++)
{
delete m->children[i];
}
free(m->children);
free(m->extent[0]);
free(m->extent[1]);
free(m->startpos[0]);
free(m->startpos[1]);
delete m;
}
unsigned int widget_layout::init(struct window * parent, uintptr_t parenthandle)
{
unsigned int ret=0;
for (unsigned int i=0;i<m->numchildren;i++)
{
ret+=m->children[i]->init(parent, parenthandle);
}
return ret;
}
static void widget_layout_calc_size(widget_layout* obj, unsigned int * widths, unsigned int * heights)
{
for (unsigned int dir=0;dir<2;dir++)
{
unsigned int * sizes=(dir==0 ? widths : heights);
if (obj->m->uniform[dir])
{
unsigned int maxsize=0;
for (unsigned int i=0;i<obj->m->numchildren;i++)
{
unsigned int thissizepx=(dir==0 ? obj->m->children[i]->width : obj->m->children[i]->height);
unsigned int thissize=((thissizepx+obj->m->extent[dir][i]-1)/obj->m->extent[dir][i]);
if (thissize>maxsize) maxsize=thissize;
}
for (unsigned int i=0;i<obj->m->totsize[dir];i++) sizes[i]=maxsize;
}
else
{
memset(sizes, 0, sizeof(unsigned int)*obj->m->totsize[dir]);
for (unsigned int i=0;i<obj->m->numchildren;i++)
{
unsigned int thissize=(dir==0 ? obj->m->children[i]->width : obj->m->children[i]->height);
if (obj->m->extent[dir][i]==1 && thissize > sizes[obj->m->startpos[dir][i]]) sizes[obj->m->startpos[dir][i]]=thissize;
}
//TODO: This does not grant the desired size to elements covering more than one tile.
//GTK+ does something highly weird, so it'll need to be tested everywhere anyways. I can do whatever I want.
}
}
}
void widget_layout::measure()
{
for (unsigned int i=0;i<m->numchildren;i++)
{
m->children[i]->measure();
}
unsigned int * cellwidths=malloc(sizeof(unsigned int)*m->totsize[0]);
unsigned int * cellheights=malloc(sizeof(unsigned int)*m->totsize[1]);
widget_layout_calc_size(this, cellwidths, cellheights);
unsigned int width=0;
for (unsigned int i=0;i<m->totsize[0];i++) width+=cellwidths[i];
this->width=width;
unsigned int height=0;
for (unsigned int i=0;i<m->totsize[1];i++) height+=cellheights[i];
this->height=height;
free(cellwidths);
free(cellheights);
widthprio=0;
heightprio=0;
for (unsigned int i=0;i<m->numchildren;i++)
{
if (m->children[i]->widthprio > this->widthprio) this->widthprio =m->children[i]->widthprio;
if (m->children[i]->heightprio > this->heightprio) this->heightprio=m->children[i]->heightprio;
}
}
void widget_layout::place(void* resizeinf, unsigned int x, unsigned int y, unsigned int width, unsigned int height)
{
unsigned int * cellwidths=malloc(sizeof(unsigned int)*m->totsize[0]);
unsigned int * cellheights=malloc(sizeof(unsigned int)*m->totsize[1]);
widget_layout_calc_size(this, cellwidths, cellheights);
for (int dir=0;dir<2;dir++)
{
uint32_t * expand=malloc(sizeof(uint32_t)*m->totsize[dir]);
memset(expand, 0, sizeof(uint32_t)*m->totsize[dir]);
unsigned int extrasize_pix;
if (dir==0) extrasize_pix = width - this->width;
else extrasize_pix = height - this->height;
unsigned int extrasize_frac=0;
unsigned int extrasize_split=0;
unsigned int extrasize_max=0;
for (unsigned int i=0;i<m->numchildren;i++)
{
if ((dir==0 && m->children[i]->widthprio ==this->widthprio) ||
(dir==1 && m->children[i]->heightprio==this->heightprio))
{
for (unsigned int j=0;j<m->extent[dir][i];j++)
{
expand[m->startpos[dir][i]+j]++;
if (expand[m->startpos[dir][i]+j] == extrasize_max) extrasize_split++;
if (expand[m->startpos[dir][i]+j] > extrasize_max)
{
extrasize_split=1;
extrasize_max=expand[m->startpos[dir][i]+j];
}
}
}
}
for (unsigned int i=0;i<m->totsize[dir];i++)
{
if (expand[i]==extrasize_max)
{
extrasize_frac+=extrasize_pix;
if (dir==0) cellwidths[i]+=extrasize_frac/extrasize_split;
else cellheights[i]+=extrasize_frac/extrasize_split;
extrasize_frac%=extrasize_split;
}
}
free(expand);
}
unsigned int * cellstartx=malloc(sizeof(unsigned int)*(m->totsize[0]+1));
unsigned int * cellstarty=malloc(sizeof(unsigned int)*(m->totsize[1]+1));
cellstartx[0]=0;
cellstarty[0]=0;
for (unsigned int i=0;i<m->totsize[0];i++) cellstartx[i+1]=cellstartx[i]+cellwidths[i];
for (unsigned int i=0;i<m->totsize[1];i++) cellstarty[i+1]=cellstarty[i]+cellheights[i];
free(cellwidths);
free(cellheights);
for (unsigned int i=0;i<m->numchildren;i++)
{
//printf("pl %u at %u,%u %u,%u\n",i,
//x+cellstartx[m->startpos[0][i]], y+cellstarty[m->startpos[1][i]],
//cellstartx[m->startpos[0][i]+m->extent[0][i]]-cellstartx[m->startpos[0][i]],
//cellstarty[m->startpos[1][i]+m->extent[1][i]]-cellstarty[m->startpos[1][i]]);
m->children[i]->place(resizeinf,
x+cellstartx[m->startpos[0][i]], y+cellstarty[m->startpos[1][i]],
cellstartx[m->startpos[0][i]+m->extent[0][i]]-cellstartx[m->startpos[0][i]],
cellstarty[m->startpos[1][i]+m->extent[1][i]]-cellstarty[m->startpos[1][i]]);
}
free(cellstartx);
free(cellstarty);
}
#endif
//varargs are irritating; no point reimplementing them for all platforms.
windowmenu_radio* windowmenu_radio::create(function<void(unsigned int state)> onactivate, const char * firsttext, ...)
{
unsigned int numitems=1;
va_list args;
va_start(args, firsttext);
while (va_arg(args, const char*)) numitems++;
va_end(args);
const char * * items=malloc(sizeof(const char*)*numitems);
items[0]=firsttext;
va_start(args, firsttext);
for (unsigned int i=1;i<numitems;i++)
{
items[i]=va_arg(args, const char*);
}
va_end(args);
windowmenu_radio* ret = windowmenu_radio::create(numitems, items, onactivate);
free(items);
return ret;
}
windowmenu_menu* windowmenu_menu::create_top(windowmenu_menu* firstchild, ...)
{
unsigned int numitems=1;
va_list args;
if (firstchild)
{
va_start(args, firstchild);
while (va_arg(args, windowmenu_menu*)) numitems++;
va_end(args);
}
else numitems=0;
windowmenu_menu* * items=malloc(sizeof(windowmenu_menu*)*numitems);
items[0]=firstchild;
va_start(args, firstchild);
for (unsigned int i=1;i<numitems;i++)
{
items[i]=va_arg(args, windowmenu_menu*);
}
va_end(args);
windowmenu_menu* ret = windowmenu_menu::create_top(numitems, items);
free(items);
return ret;
}
windowmenu_menu* windowmenu_menu::create(const char * text, windowmenu* firstchild, ...)
{
if (!firstchild) return windowmenu_menu::create(text, 0, NULL);
unsigned int numitems=1;
va_list args;
if (firstchild)
{
va_start(args, firstchild);
while (va_arg(args, windowmenu*)) numitems++;
va_end(args);
}
else numitems=0;
windowmenu* * items=malloc(sizeof(windowmenu*)*numitems);
items[0]=firstchild;
va_start(args, firstchild);
for (unsigned int i=1;i<numitems;i++)
{
items[i]=va_arg(args, windowmenu*);
}
va_end(args);
windowmenu_menu* ret = windowmenu_menu::create(text, numitems, items);
free(items);
return ret;
}
widget_layout* widget_create_radio_group(bool vertical, widget_radio* leader, ...)
{
unsigned int numitems=1;
va_list args;
va_start(args, leader);
while (va_arg(args, widget_radio*)) numitems++;
va_end(args);
widget_radio* * items=malloc(sizeof(widget_radio*)*numitems);
items[0]=leader;
va_start(args, leader);
for (unsigned int i=1;i<numitems;i++)
{
items[i]=va_arg(args, widget_radio*);
}
va_end(args);
items[0]->group(numitems, items);
widget_layout* ret = widget_create_layout(numitems, (widget_base**)items, vertical?1:numitems, NULL, false, vertical?numitems:1, NULL, false);
free(items);
return ret;
}
widget_layout* widget_create_radio_group(bool vertical, widget_radio* * leader, const char * firsttext, ...)
{
unsigned int numitems=1;
va_list args;
va_start(args, firsttext);
while (va_arg(args, const char*)) numitems++;
va_end(args);
widget_radio* * items=malloc(sizeof(widget_radio*)*numitems);
items[0]=widget_create_radio(firsttext);
va_start(args, firsttext);
for (unsigned int i=1;i<numitems;i++)
{
items[i]=widget_create_radio(va_arg(args, const char*));
}
va_end(args);
items[0]->group(numitems, items);
if (leader) *leader=items[0];
widget_layout* ret=widget_create_layout(numitems, (widget_base**)items, vertical?1:numitems, NULL, false, vertical?numitems:1, NULL, false);
free(items);
return ret;
}
widget_listbox_virtual::widget_listbox_virtual(const char * firstcol, ...)
{
unsigned int numcols=1;
va_list args;
va_start(args, firstcol);
while (va_arg(args, const char*)) numcols++;
va_end(args);
const char * * columns=malloc(sizeof(const char*)*numcols);
columns[0]=firstcol;
va_start(args, firstcol);
for (unsigned int i=1;i<numcols;i++)
{
columns[i]=va_arg(args, const char*);
}
va_end(args);
construct(numcols, columns);
free(columns);
}
widget_layout::widget_layout(bool vertical, bool uniform, widget_base* firstchild, ...)
{
unsigned int numchildren=1;
va_list args;
va_start(args, firstchild);
while (va_arg(args, void*)) numchildren++;
va_end(args);
widget_base* * children=malloc(sizeof(widget_base*)*numchildren);
children[0]=firstchild;
va_start(args, firstchild);
for (unsigned int i=1;i<numchildren;i++)
{
children[i]=va_arg(args, widget_base*);
}
va_end(args);
construct(numchildren, (widget_base**)children, vertical?1:numchildren, NULL, uniform, vertical?numchildren:1, NULL, uniform);
free(children);
}
widget_layout::widget_layout(unsigned int totwidth, unsigned int totheight, bool uniformwidths, bool uniformheights,
unsigned int firstwidth, unsigned int firstheight, widget_base* firstchild, ...)
{
unsigned int numchildren=1;
unsigned int boxesleft=totwidth*totheight;
boxesleft-=firstwidth*firstheight;
va_list args;
va_start(args, firstchild);
while (boxesleft)
{
boxesleft-=va_arg(args, unsigned int)*va_arg(args, unsigned int);
widget_base* ignored=va_arg(args, widget_base*); (void)ignored;//ignore this, we only want the sizes right now
numchildren++;
}
va_end(args);
unsigned int * widths=malloc(sizeof(unsigned int)*numchildren);
unsigned int * heights=malloc(sizeof(unsigned int)*numchildren);
widget_base* * children=malloc(sizeof(widget_base*)*numchildren);
widths[0]=firstwidth;
heights[0]=firstheight;
children[0]=firstchild;
va_start(args, firstchild);
for (unsigned int i=1;i<numchildren;i++)
{
widths[i]=va_arg(args, unsigned int);
heights[i]=va_arg(args, unsigned int);
children[i]=va_arg(args, widget_base*);
}
va_end(args);
construct(numchildren, children, totwidth, widths, uniformwidths, totheight, heights, uniformheights);
free(widths);
free(heights);
free(children);
}
widget_layout* widget_create_layout_grid(unsigned int width, unsigned int height, bool uniformsizes, widget_base* firstchild, ...)
{
va_list args;
widget_base* * children=malloc(sizeof(widget_base*)*width*height);
children[0]=firstchild;
va_start(args, firstchild);
for (unsigned int i=1;i<width*height;i++)
{
children[i]=va_arg(args, widget_base*);
}
va_end(args);
return widget_create_layout(width*height, children, width, NULL, uniformsizes, height, NULL, uniformsizes);
}
size_t _widget_listbox_search(function<const char *(size_t row, int column)> get_cell, size_t rows,
const char * prefix, size_t start, bool up)
{
size_t len = strlen(prefix);
size_t pos = start;
for (size_t i=0;i<rows;i++)
{
const char * thisstr = get_cell(pos, 0);
if (!strncasecmp(thisstr, prefix, len)) return pos;
if (!up)
{
pos++;
if (pos == rows) pos = 0;
}
else
{
if (pos == 0) pos = rows;
pos--;
}
}
return (size_t)-1;
}
#endif

1365
arlib/gui/win32-inner.cpp Normal file

File diff suppressed because it is too large Load Diff

299
arlib/gui/win32-misc.cpp Normal file
View File

@ -0,0 +1,299 @@
#include "window.h"
#include "../file.h"
#include "../os.h"
#ifdef ARGUI_WINDOWS
#undef bind
#include <windows.h>
#include <commdlg.h>
#define bind bind_func
//Number of ugly hacks: 5
//If a status bar item is right-aligned, a space is appended.
//The status bar is created with WS_DISABLED.
//WM_SYSCOMMAND is sometimes ignored.
//I have to keep track of the mouse position so I can ignore various bogus instances of WM_MOUSEMOVE.
//I have to undefine 'bind' before including any Windows header. I suspect something is including winsock.
//Incompatibility levels:
//Level 0 - a feature works as intended
//Level 1 - a feature is usable, but behaves weirdly
//Level 2 - attempting to use a feature throws an error box, or reports failure in a way the program can and does handle
//Level 3 - attempting to use a feature reports success internally, but nothing happens
//Level 4 - attempting to use a feature crashes the program
//Level 5 - program won't start
//Maximum allowed incompatibility level:
//XP SP2 and older: 5
//XP SP3:
// 1 after December 8, 2013
// 2 after April 8, 2014
// 3 after August 8, 2014
// 4 after December 8, 2014
// 5 after April 8, 2015
//Vista SP0 and higher: 0
//List:
//Level 0: SetDllDirectory demands XP SP1 or higher. (But anything below SP3 is, for all intents and purposes, dead.)
//Level 1: LVCFMT_FIXED_WIDTH on the listbox is ignored before Vista
//Danger list (likely to hit):
//Level 4: printf dislikes z (size_t) size specifiers; they must be behind #ifdef DEBUG, or turned into "I" via #define
// NOTE: This is present on Vista too. z requires 7 or higher.
//Level 5: 64-bit programs dislike XP (there are 32bit Vista/7/8, but Vista is practically dead, as is 32bit 7+)
//Level 5: SRWLOCK is Vista+.
//static LARGE_INTEGER timer_freq;
void window_init(int * argc, char * * argv[])
{
#ifdef ARLIB_WUTF
WuTF_enable_args(argc, argv);
#endif
for (unsigned int i=0;(*argv)[0][i];i++)
{
if ((*argv)[0][i]=='\\') (*argv)[0][i]='/';
}
_window_init_file();
_window_init_shell();
_window_init_inner();
//QueryPerformanceFrequency(&timer_freq);
}
file* file::create(const char * filename)
{
//sorry Windows, no fancy features for you, you suck
return create_fs(filename);
}
bool window_message_box(const char * text, const char * title, enum mbox_sev severity, enum mbox_btns buttons)
{
UINT sev[3]={ 0, MB_ICONWARNING, MB_ICONERROR };
UINT btns[3]={ 0, MB_OKCANCEL, MB_YESNO };
int ret=MessageBox(NULL, text, title, sev[severity]|btns[buttons]|MB_TASKMODAL);
return (ret==IDOK || ret==IDYES);
}
const char * const * window_file_picker(struct window * parent,
const char * title,
const char * const * extensions,
const char * extdescription,
bool dylib,
bool multiple)
{
//there is no reasonable way to use the dylib flag; windows has nothing gvfs-like (okay, maybe IShellItem, but I can't get that from GetOpenFileName).
static char * * ret=NULL;
if (ret)
{
char * * del=ret;
while (*del)
{
free(*del);
del++;
}
free(ret);
ret=NULL;
}
unsigned int filterlen=strlen(extdescription)+1+0+1-1+strlen("All files")+1+strlen("*.*")+1+1;
for (unsigned int i=0;extensions[i];i++) filterlen+=2+strlen(extensions[i])+1;
char * filter=malloc(filterlen);
char * filterat=filter;
strcpy(filterat, extdescription);
filterat+=strlen(extdescription)+1;
for (unsigned int i=0;extensions[i];i++)
{
unsigned int thislen=strlen(extensions[i]);
filterat[0]='*';
filterat[1]='.';
if (*extensions[i]=='.') filterat--;
memcpy(filterat+2, extensions[i], thislen);
filterat[2+thislen]=';';
filterat+=2+thislen+1;
}
memcpy(filterat-1, "\0All files\0*.*\0", 1+strlen("All files")+1+strlen("*.*")+1+1);
OPENFILENAME ofn;
ZeroMemory(&ofn, sizeof(ofn));
ofn.lStructSize=sizeof(ofn);
ofn.hwndOwner=(parent?(HWND)parent->_get_handle():NULL);
ofn.lpstrFilter=(extensions[0] ? filter : "All files (*.*)\0*.*\0");
char * filenames=malloc(65536);
*filenames='\0';
ofn.lpstrFile=filenames;
ofn.nMaxFile=65536;
ofn.lpstrTitle=title;
ofn.Flags=OFN_HIDEREADONLY|OFN_FILEMUSTEXIST|OFN_PATHMUSTEXIST|OFN_EXPLORER|(multiple?OFN_ALLOWMULTISELECT:0);
ofn.lpstrDefExt=NULL;
WCHAR cwd[MAX_PATH];
GetCurrentDirectoryW(MAX_PATH, cwd);
BOOL ok=GetOpenFileName(&ofn);
SetCurrentDirectoryW(cwd);
free(filter);
if (!ok)
{
free(filenames);
return NULL;
}
bool ismultiple=(ofn.nFileOffset && filenames[ofn.nFileOffset-1]=='\0');
if (!ismultiple)
{
ret=malloc(sizeof(char*)*2);
ret[0]=window_get_absolute_path(window_get_cwd(), filenames, true);
ret[1]=NULL;
return (const char * const *)ret;
}
filenames[ofn.nFileOffset-1]='\\';
unsigned int numfiles=0;
char * filename=filenames+ofn.nFileOffset;
while (*filename)
{
numfiles++;
filename+=strlen(filename)+1;
}
ret=malloc(sizeof(char*)*(numfiles+1));
filename=filenames+ofn.nFileOffset;
char * * retout=ret;
while (*filename)
{
unsigned int thislen=strlen(filename);
memcpy(filenames+ofn.nFileOffset, filename, thislen+1);
*retout=window_get_absolute_path(window_get_cwd(), filenames, true);
retout++;
filename+=thislen+1;
}
free(filenames);
ret[numfiles] = NULL;
return (const char * const *)ret;
}
char * window_get_absolute_path(const char * basepath, const char * path, bool allow_up)
{
return _window_native_get_absolute_path(basepath, path, allow_up);
}
uint64_t window_get_time()
{
//this one has an accuracy of 10ms by default
ULARGE_INTEGER time;
GetSystemTimeAsFileTime((LPFILETIME)&time);
return time.QuadPart/10;//this one is in intervals of 100 nanoseconds, for some insane reason. We want microseconds.
//this one is slow - ~800fps -> ~500fps if called each frame
//LARGE_INTEGER timer_now;
//QueryPerformanceCounter(&timer_now);
//return timer_now.QuadPart/timer_freq.QuadPart;
}
bool file_read(const char * filename, void* * data, size_t * len)
{
if (!filename) return false;
HANDLE file=CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (file==INVALID_HANDLE_VALUE) return false;
DWORD readlen=GetFileSize(file, NULL);
DWORD truelen;
char* truedata=malloc(readlen+1);
ReadFile(file, truedata, readlen, &truelen, NULL);
truedata[readlen]='\0';
*data=truedata;
CloseHandle(file);
if (truelen!=readlen)
{
free(truedata);
return false;
}
if (len) *len=truelen;
return true;
}
bool file_write(const char * filename, const anyptr data, size_t len)
{
if (!filename) return false;
if (!len) return true;
HANDLE file=CreateFile(filename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (file==INVALID_HANDLE_VALUE) return false;
DWORD truelen;
WriteFile(file, data, len, &truelen, NULL);
CloseHandle(file);
return (truelen==len);
}
bool file_read_to(const char * filename, anyptr data, size_t len)
{
if (!filename) return false;
if (!len) return true;
HANDLE file=CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (file==INVALID_HANDLE_VALUE) return false;
DWORD readlen=GetFileSize(file, NULL);
if (readlen!=len)
{
CloseHandle(file);
return false;
}
DWORD truelen;
ReadFile(file, data, len, &truelen, NULL);
CloseHandle(file);
return (len==truelen);
}
//this could be made far cleaner if . or .. was guaranteed to be first.
struct finddata {
HANDLE h;
WIN32_FIND_DATA file;
bool first;
};
void* file_find_create(const char * path)
{
if (!path) return NULL;
int pathlen=strlen(path);
char * pathcopy=malloc(pathlen+3);
memcpy(pathcopy, path, pathlen);
pathcopy[pathlen+0]='\\';
pathcopy[pathlen+1]='*';
pathcopy[pathlen+2]='\0';
struct finddata * find=malloc(sizeof(struct finddata));
find->h=FindFirstFile(pathcopy, &find->file);
free(pathcopy);
find->first=true;
if (find->h==INVALID_HANDLE_VALUE)
{
free(find);
return NULL;
}
return find;
}
bool file_find_next(void* find_, char * * path, bool * isdir)
{
if (!find_) return false;
struct finddata * find=(struct finddata*)find_;
nextfile:;
bool ok=true;
if (find->first) find->first=false;
else ok=FindNextFile(find->h, &find->file);
if (!ok) return false;
if (!strcmp(find->file.cFileName, ".")) goto nextfile;
if (!strcmp(find->file.cFileName, "..")) goto nextfile;
*path=strdup(find->file.cFileName);
*isdir=(find->file.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY);
return true;
}
void file_find_close(void* find_)
{
if (!find_) return;
struct finddata * find=(struct finddata*)find_;
FindClose(find->h);
free(find);
}
#endif

982
arlib/gui/win32-shell.cpp Normal file
View File

@ -0,0 +1,982 @@
#include "window.h"
#ifdef ARGUI_WINDOWS
#undef bind
#include <windows.h>
#define bind bind_func
#include <commctrl.h>
#include <ctype.h>
//TODO: check if DwmEnableMMCSS does anything useful
//force some year-old C code to compile properly as C++ - I decided to switch long ago but still haven't finished.
//TODO:
//menu_create: check where it's resized if size changes
//static bool isxp;
void _window_init_shell()
{
WNDCLASS wc;
wc.style=0;
wc.lpfnWndProc=DefWindowProc;
wc.cbClsExtra=0;
wc.cbWndExtra=0;
wc.hInstance=GetModuleHandle(NULL);
wc.hIcon=LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(1));
wc.hCursor=LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground=GetSysColorBrush(COLOR_3DFACE);
wc.lpszMenuName=NULL;
wc.lpszClassName="minir";
RegisterClass(&wc);
//DWORD version=GetVersion();
//DWORD trueversion=(LOBYTE(LOWORD(version))<<8 | HIBYTE(LOWORD(version)));
//isxp=(trueversion<=0x0501);
}
static HMENU menu_to_hmenu(windowmenu_menu* menu);
//static void menu_delete(struct windowmenu_win32 * This);
static void menu_activate(HMENU menu, DWORD pos);
static LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
namespace {
#define WS_BASE WS_OVERLAPPED|WS_CAPTION|WS_SYSMENU|WS_MINIMIZEBOX // okay microsoft, did I miss anything?
#define WS_RESIZABLE (WS_BASE|WS_MAXIMIZEBOX|WS_THICKFRAME)
#define WS_NONRESIZ (WS_BASE|WS_BORDER)
//static bool _reflow(struct window * this_);
//static void reflow_force(struct window_win32 * this_);
static HWND activedialog;
static struct window_win32 * firstwindow;
static struct window_win32 * modalwindow;
class window_win32 : public window {
public:
//used by modality
struct window_win32 * prev;
struct window_win32 * next;
bool modal;
//char padding[7];
HWND hwnd;
widget_base* contents;
unsigned int numchildwin;
DWORD lastmousepos;
windowmenu_menu* menu;
HWND status;
int * status_align;
int * status_div;
uint16_t status_count;
uint8_t status_resizegrip_width;
bool status_extra_spacing;
bool resizable;
bool isdialog;
bool menuactive;//odd position to reduce padding
//uint8_t delayfree;//0=normal, 1=can't free now, 2=free at next opportunity
function<bool()> onclose;
/*private*/ void getBorderSizes(unsigned int * width, unsigned int * height)
{
RECT inner;
RECT outer;
GetClientRect(this->hwnd, &inner);
GetWindowRect(this->hwnd, &outer);
if (width) *width=(outer.right-outer.left)-(inner.right);
if (height) *height=(outer.bottom-outer.top)-(inner.bottom);
if (height && this->status)
{
RECT statsize;
GetClientRect(this->status, &statsize);
*height+=(statsize.bottom-statsize.top);
}
}
/*private*/ void resize_stbar(unsigned int width)
{
if (this->status)
{
SendMessage(this->status, WM_SIZE, 0,0);
unsigned int statuswidth=width;
if (this->resizable)
{
if (!this->status_resizegrip_width)
{
RECT rect;
SendMessage(this->status, SB_GETRECT, 0, (LPARAM)&rect);
this->status_resizegrip_width=rect.bottom-rect.top-8;//assume the size grip has the same width as height
}
statuswidth-=this->status_resizegrip_width;
}
int * statuspositions=malloc(sizeof(int)*this->status_count);
for (int i=0;i<this->status_count;i++)
{
statuspositions[i]=statuswidth*(this->status_div[i])/240;
}
//statuspositions[this->status_count-1]=width;
SendMessage(this->status, SB_SETPARTS, (WPARAM)this->status_count, (LPARAM)statuspositions);
free(statuspositions);
}
}
void set_is_dialog()
{
this->isdialog=true;
}
void set_parent(struct window * parent_)
{
struct window_win32 * parent=(struct window_win32*)parent_;
SetWindowLongPtr(this->hwnd, GWLP_HWNDPARENT, (LONG_PTR)parent->hwnd);
}
/*private*/ void update_modal()
{
if (this->modal && IsWindowVisible(this->hwnd))
{
//disable all windows
if (!modalwindow)//except if they're already disabled because that's a waste of time.
{
struct window_win32 * wndw=firstwindow;
while (wndw)
{
if (wndw!=this) EnableWindow(wndw->hwnd, false);
wndw=wndw->next;
}
modalwindow=this;
}
}
else
{
//we're gone now - if we're the one holding the windows locked, enable them
if (this == modalwindow)
{
struct window_win32 * wndw=firstwindow;
while (wndw)
{
EnableWindow(wndw->hwnd, true);
wndw=wndw->next;
}
modalwindow=NULL;
}
}
}
void set_modal(bool modal)
{
this->modal=modal;
this->update_modal();
}
void resize(unsigned int width, unsigned int height)
{
unsigned int padx;
unsigned int pady;
this->getBorderSizes(&padx, &pady);
SetWindowPos(this->hwnd, NULL, 0, 0, width+padx, height+pady,
SWP_NOACTIVATE|SWP_NOCOPYBITS|SWP_NOMOVE|SWP_NOOWNERZORDER|SWP_NOZORDER);
this->_reflow();
//because we're setting the window position ourselves, reflow is likely to think the status bar is correct, so we need to fix it ourselves.
RECT size;
GetClientRect(this->hwnd, &size);
this->resize_stbar(size.right);
}
void set_resizable(bool resizable, function<void(unsigned int newwidth, unsigned int newheight)> onresize)
{
if (this->resizable != resizable)
{
this->resizable=resizable;
SetWindowLong(this->hwnd, GWL_STYLE, GetWindowLong(this->hwnd, GWL_STYLE) ^ WS_RESIZABLE^WS_NONRESIZ);
this->_reflow();
}
}
void get_pos(int * x, int * y)
{
//TODO
}
void set_pos(int x, int y)
{
}
void set_onmove(function<void(int x, int y)> onmove)
{
}
void set_title(const char * title)
{
/*if (!isxp)*/ SetWindowText(this->hwnd, title);
}
void set_onclose(function<bool()> onclose)
{
this->onclose = onclose;
}
void set_menu(windowmenu_menu* menu)
{
delete this->menu;
SetMenu(this->hwnd, menu_to_hmenu(menu));
this->menu=menu;
}
void statusbar_create(int numslots, const int * align, const int * dividerpos)
{
if (!numslots)
{
if (this->status)
{
RECT barsize;
GetWindowRect(this->status, &barsize);
RECT mainsize;
GetWindowRect(this->hwnd, &mainsize);
SetWindowPos(this->hwnd, NULL, 0, 0, mainsize.right, mainsize.bottom-(barsize.bottom-barsize.top),
SWP_NOACTIVATE|SWP_NOCOPYBITS|SWP_NOMOVE|SWP_NOOWNERZORDER|SWP_NOZORDER);
DestroyWindow(this->status);
this->status=NULL;
}
return;
}
if (!this->status)
{
INITCOMMONCONTROLSEX initctrls;
initctrls.dwSize=sizeof(initctrls);
initctrls.dwICC=ICC_BAR_CLASSES;
InitCommonControlsEx(&initctrls);
this->status=CreateWindow(STATUSCLASSNAME, "", WS_CHILD|WS_VISIBLE|WS_DISABLED, 0,0,0,0, this->hwnd, NULL, GetModuleHandle(NULL), NULL);
//SetWindowLong(this->status, GWL_STYLE, GetWindowLong(this->status, GWL_STYLE)|WS_DISABLED);//phoenix says this is needed, or it can get tab focus
//EnableWindow(this->status, false);//phoenix says this is needed, or it can get tab focus
}
free(this->status_align);
free(this->status_div);
this->status_count=numslots;
this->status_align=malloc(sizeof(int)*numslots);
this->status_div=malloc(sizeof(int)*numslots);
for (int i=0;i<numslots;i++)
{
this->status_align[i]=align[i];
if (i<numslots-1) this->status_div[i]=dividerpos[i];
else this->status_div[i]=240;
}
RECT barsize;
GetWindowRect(this->status, &barsize);
RECT mainsize;
GetWindowRect(this->hwnd, &mainsize);
SetWindowPos(this->hwnd, NULL, 0, 0, mainsize.right, mainsize.bottom+(barsize.bottom-barsize.top),
SWP_NOACTIVATE|SWP_NOCOPYBITS|SWP_NOMOVE|SWP_NOOWNERZORDER|SWP_NOZORDER);
this->resize_stbar(barsize.right);
}
void statusbar_set(int slot, const char * text)
{
int align=this->status_align[slot];
if (align==0)
{
SendMessage(this->status, SB_SETTEXT, (WPARAM)slot, (LPARAM)text);
}
else
{
int textlen=strlen(text);
char * newtext=malloc(1+1+textlen+1+1);
newtext[0]='\t';
newtext[1]='\t';
memcpy(newtext+2, text, textlen);
newtext[2+textlen]=((align==2)?' ':'\0');
newtext[2+textlen+1]='\0';
SendMessage(this->status, SB_SETTEXT, (WPARAM)slot, (LPARAM)(newtext + ((align==1)?1:0)));
free(newtext);
}
}
void replace_contents(widget_base* contents)
{
delete this->contents;
this->contents=contents;
this->numchildwin=this->contents->init(this, (uintptr_t)this->hwnd);
this->_reflow();
}
void set_visible(bool visible)
{
if (visible)
{
this->reflow_force();
ShowWindow(this->hwnd, SW_SHOWNORMAL);
}
else
{
ShowWindow(this->hwnd, SW_HIDE);
}
this->update_modal();
}
bool is_visible()
{
return IsWindowVisible(this->hwnd);
}
void focus()
{
SetForegroundWindow(this->hwnd);
}
bool is_active()
{
return (GetForegroundWindow()==this->hwnd);
}
bool menu_active()
{
return (this->menuactive);
}
~window_win32()
{
//if (this->delayfree)
//{
// this->delayfree=2;
// return;
//}
if (this->prev) this->prev->next=this->next;
else firstwindow=this->next;
if (this->next) this->next->prev=this->prev;
if (this->modal)
{
this->set_visible(false);
this->update_modal();
}
delete this->contents;
delete this->menu;
DestroyWindow(this->hwnd);
//free(this);
}
uintptr_t _get_handle()
{
return (uintptr_t)this->hwnd;
}
bool _reflow()
{
if (!IsWindowVisible(this->hwnd)) return false;
this->reflow_force();
return true;
}
/*private*/ void reflow_force()
{
//Resizing our window seems to call the resize callback again. We're not interested, it'll just recurse in irritating ways.
static bool recursive=false;
if (recursive) return;
recursive=true;
RECT size;
GetClientRect(this->hwnd, &size);
RECT statsize;
if (this->status)
{
GetClientRect(this->status, &statsize);
size.bottom-=statsize.bottom;
}
this->contents->measure();
bool badx=(this->contents->width > (unsigned int)size.right || (!this->resizable && this->contents->width != (unsigned int)size.right));
bool bady=(this->contents->height > (unsigned int)size.bottom || (!this->resizable && this->contents->height != (unsigned int)size.bottom));
if (badx) size.right=this->contents->width;
if (bady) size.bottom=this->contents->height;
if (badx || bady)
{
unsigned int outerw;
unsigned int outerh;
this->getBorderSizes(&outerw, &outerh);
//we can't defer this, or GetClientRect will get stale data, and we need the actual window size to move the rest of the windows
SetWindowPos(this->hwnd, NULL, 0, 0, size.right+outerw, size.bottom+outerh,
SWP_NOACTIVATE|SWP_NOCOPYBITS|SWP_NOMOVE|SWP_NOOWNERZORDER|SWP_NOZORDER);
unsigned int newouterh;
this->getBorderSizes(NULL, &newouterh);
if (newouterh != outerh)//changing the width may change the menu between one and two lines, so resize again
{
SetWindowPos(this->hwnd, NULL, 0, 0, size.right+outerw, size.bottom+newouterh,
SWP_NOACTIVATE|SWP_NOCOPYBITS|SWP_NOMOVE|SWP_NOOWNERZORDER|SWP_NOZORDER);
}
GetClientRect(this->hwnd, &size);
if (this->status) size.bottom-=statsize.bottom;
this->resize_stbar(size.right);
}
HDWP hdwp=BeginDeferWindowPos(this->numchildwin);
this->contents->place(&hdwp, 0,0, size.right, size.bottom);
EndDeferWindowPos(hdwp);
recursive=false;
}
window_win32(widget_base* contents)
{
this->next=firstwindow;
this->prev=NULL;
if (this->next) this->next->prev=this;
firstwindow=this;
this->contents=(struct widget_base*)contents;
this->contents->measure();
//the 6 and 28 are arbitrary; we'll set ourselves to a better size later. Windows' default placement algorithm sucks, anyways.
//const char * xpmsg="Do not submit bug reports. Windows XP is unsupported by Microsoft, and unsupported by me.";
this->hwnd=CreateWindow("minir", /*isxp?xpmsg:*/"", WS_NONRESIZ, CW_USEDEFAULT, CW_USEDEFAULT,
this->contents->width+6, this->contents->height+28, NULL, NULL, GetModuleHandle(NULL), NULL);
SetWindowLongPtr(this->hwnd, GWLP_USERDATA, (LONG_PTR)this);
SetWindowLongPtr(this->hwnd, GWLP_WNDPROC, (LONG_PTR)WindowProc);
this->numchildwin = this->contents->init((struct window*)this, (uintptr_t)this->hwnd);
this->status=NULL;
this->menu=NULL;
this->menuactive=false;
this->resizable=false;
this->onclose=NULL;
this->lastmousepos=-1;
this->status_resizegrip_width=0;
//this->delayfree=0;
this->resize(this->contents->width, this->contents->height);
}
};
}
window* window_create(widget_base* contents)
{
return new window_win32(contents);
}
static LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
struct window_win32 * This=(struct window_win32*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
switch (uMsg)
{
case WM_CTLCOLOREDIT: return _window_get_widget_color(uMsg, (HWND)lParam, (HDC)wParam, hwnd);
case WM_GETMINMAXINFO:
{
if (This)
{
MINMAXINFO* mmi=(MINMAXINFO*)lParam;
unsigned int padx;
unsigned int pady;
This->getBorderSizes(&padx, &pady);
mmi->ptMinTrackSize.x=padx+This->contents->width;
mmi->ptMinTrackSize.y=pady+This->contents->height;
if (!This->resizable)
{
mmi->ptMaxTrackSize.x=mmi->ptMinTrackSize.x;
mmi->ptMaxTrackSize.y=mmi->ptMinTrackSize.y;
}
}
}
break;
case WM_ACTIVATE:
if (LOWORD(wParam) && This->isdialog) activedialog=hwnd;
else activedialog=NULL;
break;
case WM_CLOSE:
//case WM_ENDSESSION://this isn't really the most elegant solution, but it should work.
//disabling the above because I don't want the possibility of being closed between fopen and fwrite.
CloseWindow:
{
if (This->onclose)
{
//This->delayfree=1;
if (!This->onclose())
{
//This->delayfree=0;
break;
}
//if (This->delayfree==2)
//{
// This->delayfree=0;
// free_((struct window*)This);
// break;
//}
//This->delayfree=0;
}
ShowWindow(hwnd, SW_HIDE);
}
break;
case WM_COMMAND:
{
//printf("COMM=%.8zX,%.8zX\n",wParam,lParam);
if (lParam==0)
{
//what does this 2 mean? It works, but...
if (HIWORD(wParam)==0 && LOWORD(wParam)==2) goto CloseWindow;
}
else
{
NMHDR nmhdr={(HWND)lParam, LOWORD(wParam), HIWORD(wParam)};
return _window_notify_inner(&nmhdr);
}
}
break;
case WM_NOTIFY:
{
return _window_notify_inner((LPNMHDR)lParam);
}
break;
case WM_MENUCOMMAND:
{
menu_activate((HMENU)lParam, wParam);
}
break;
case WM_DESTROY:
break;
case WM_SYSCOMMAND:
{
//printf("SC=%.4X\n",wParam&0xFFFF);
if ((wParam&0xFFF0)==SC_KEYMENU) break;//go away, we don't want automenus. Alt could be hit by accident.
//we could eat WM_MOVE and WM_SIZE to nuke the stupid lockups, but that blocks moving the window entirely.
//We'll have to mess with threads.
goto _default;
}
//check WM_CONTEXTMENU
case WM_SIZE:
{
if (!This) break;//this one seems to hit only on Wine, but whatever, worth checking.
This->_reflow();
if (This->status) PostMessage(This->status, WM_SIZE, wParam, lParam);
}
break;
case WM_ENTERMENULOOP:
{
This->menuactive=true;
}
break;
case WM_EXITMENULOOP:
{
This->menuactive=false;
}
break;
_default:
default:
return DefWindowProcA(hwnd, uMsg, wParam, lParam);
}
return 0;
}
static void handlemessage(MSG * msg)
{
if (activedialog && IsDialogMessage(activedialog, msg)) return;
TranslateMessage(msg);
DispatchMessage(msg);
}
void window_run_iter()
{
MSG msg;
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) handlemessage(&msg);
}
void window_run_wait()
{
MSG msg;
GetMessage(&msg, NULL, 0, 0);
handlemessage(&msg);
window_run_iter();
}
enum menu_type { mtype_item, mtype_check, mtype_radio, mtype_sep, mtype_sub }; // nothing for 'top' since it doesn't have this struct
struct windowmenu::impl {
uint8_t type;
uint8_t nativepos;
//char padding[6];
union {
char * text;
char ** multitext; // used by radio
HMENU menu_in;
};
};
static char * menu_transform_name(const char * name)
{
bool useaccel=(*name=='_');
if (useaccel) name++;
unsigned int accelpos=0;
unsigned int len=0;
for (unsigned int i=0;name[i];i++)
{
if (name[i]=='&') len++;
if (!accelpos && name[i]=='_') accelpos=i;
len++;
}
char * ret=malloc(len+2);//NUL, extra &
char * at=ret;
for (unsigned int i=0;name[i];i++)
{
if (name[i]=='&') *(at++)='&';
if (useaccel && i==accelpos) *(at++)='&';
//This is an intentional bug. If it's reported, the user is known to use XP, and will be slapped with a large trout.
//(Details: The menu entries have randomly glitched names.)
//else if (isxp && rand()%7==0) *(at++)=rand()&255;
else *(at++)=name[i];
}
*at='\0';
return ret;
}
static unsigned int menu_get_native_length(windowmenu* menu);
static unsigned int menu_get_native_start(windowmenu_menu* menu, unsigned int pos);
static void menu_set_enabled(windowmenu* menu, bool enabled)
{
unsigned int pos = menu->m->nativepos;
unsigned int left = menu_get_native_length(menu);
while (left--)
{
MENUITEMINFO info;
info.cbSize = sizeof(MENUITEMINFO);
info.fMask = MIIM_STATE;
GetMenuItemInfo(menu->m->menu_in, pos, TRUE, &info);
if (enabled) info.fState &= ~MFS_GRAYED;
else info.fState |= MFS_GRAYED;
SetMenuItemInfo(menu->m->menu_in, pos, TRUE, &info);
pos++;
}
}
struct windowmenu_item::impl {
function<void(void)> onactivate;
const char * text; // used only before putting it in a menu
};
void windowmenu_item::set_enabled(bool enable)
{
menu_set_enabled(this, enable);
}
windowmenu_item* windowmenu_item::create(const char * text, function<void(void)> onactivate)
{
windowmenu_item* menu = new windowmenu_item;
menu->m = new windowmenu::impl;
menu->m->type = mtype_item;
menu->m->text = menu_transform_name(text);
menu->mu = new windowmenu_item::impl;
menu->mu->onactivate = onactivate;
return menu;
}
windowmenu_item::~windowmenu_item() {}
struct windowmenu_check::impl
{
function<void(bool checked)> onactivate;
};
void windowmenu_check::set_enabled(bool enable)
{
menu_set_enabled(this, enable);
}
bool windowmenu_check::get_checked()
{
MENUITEMINFO info;
info.cbSize=sizeof(MENUITEMINFO);
info.fMask=MIIM_STATE;
GetMenuItemInfo(this->m->menu_in, this->m->nativepos, TRUE, &info);
return (info.fState & MFS_CHECKED);
}
void windowmenu_check::set_checked(bool checked)
{
CheckMenuItem(this->m->menu_in, this->m->nativepos, MF_BYPOSITION | (checked?MF_CHECKED:MF_UNCHECKED));
}
windowmenu_check* windowmenu_check::create(const char * text, function<void(bool checked)> onactivate)
{
windowmenu_check* menu = new windowmenu_check;
menu->m = new windowmenu::impl;
menu->m->type = mtype_check;
menu->m->text = menu_transform_name(text);
menu->mu = new windowmenu_check::impl;
menu->mu->onactivate = onactivate;
return menu;
}
windowmenu_check::~windowmenu_check() {}
struct windowmenu_radio::impl
{
unsigned int numchildren;
unsigned int state;
function<void(unsigned int state)> onactivate;
};
void windowmenu_radio::set_enabled(bool enable)
{
menu_set_enabled(this, enable);
}
unsigned int windowmenu_radio::get_state()
{
return this->mu->state;
}
void windowmenu_radio::set_state(unsigned int state)
{
this->mu->state = state;
CheckMenuRadioItem(this->m->menu_in, this->m->nativepos, this->m->nativepos + this->mu->numchildren-1,
this->m->nativepos + state, MF_BYPOSITION);
}
windowmenu_radio* windowmenu_radio::create(unsigned int numitems, const char * const * texts,
function<void(unsigned int state)> onactivate)
{
windowmenu_radio* menu = new windowmenu_radio;
menu->m = new windowmenu::impl;
menu->mu = new windowmenu_radio::impl;
menu->m->type = mtype_radio;
menu->m->multitext = malloc(sizeof(const char *) * numitems);
for (unsigned int i=0;i<numitems;i++)
{
menu->m->multitext[i] = menu_transform_name(texts[i]);
}
menu->mu->numchildren = numitems;
menu->mu->state = 0;
menu->mu->onactivate = onactivate;
return menu;
}
windowmenu_radio::~windowmenu_radio() {}
struct windowmenu_separator::impl {};
windowmenu_separator* windowmenu_separator::create()
{
windowmenu_separator* menu = new windowmenu_separator;
menu->m = new windowmenu::impl;
menu->m->type = mtype_sep;
return menu;
}
windowmenu_separator::~windowmenu_separator() {}
struct windowmenu_menu::impl
{
HMENU container;
windowmenu* * children;
uint8_t numchildren;
//char padding[7];
};
static unsigned int menu_get_native_length(windowmenu* menu)
{
return (menu->m->type==mtype_radio ? ((windowmenu_radio*)menu)->mu->numchildren : 1);
}
//can't return children[pos]->nativepos because 'pos' may be the number of entries in the menu
//children[pos]->nativepos can also be wrong because it was recently inserted
static unsigned int menu_get_native_start(windowmenu_menu* menu, unsigned int pos)
{
return (pos ? menu->mu->children[pos-1]->m->nativepos + menu_get_native_length(menu->mu->children[pos-1]) : 0);
}
static void menu_add_item(windowmenu_menu* menu, unsigned int pos, windowmenu* child)
{
HMENU hmenu = menu->mu->container;
unsigned int menupos = menu_get_native_start(menu, pos);
child->m->nativepos = menupos;
if (child->m->type == mtype_radio)
{
windowmenu_radio* rchild = (windowmenu_radio*)child;
for (unsigned int i=0;i<rchild->mu->numchildren;i++)
{
char * name = child->m->multitext[i];
InsertMenu(hmenu, menupos, MF_BYPOSITION|MF_STRING, 0, name);
free(name);
menupos++;
}
free(child->m->multitext);
child->m->menu_in = hmenu;
rchild->set_state(0);
}
else if (child->m->type == mtype_sep)
{
InsertMenu(hmenu, menupos, MF_BYPOSITION|MF_SEPARATOR, 0, NULL);
}
else if (child->m->type == mtype_sub)
{
windowmenu_menu* mchild = (windowmenu_menu*)child;
InsertMenu(hmenu, menupos, MF_BYPOSITION|MF_POPUP, (UINT_PTR)mchild->mu->container, mchild->m->text);
free(mchild->m->text);
}
else
{
InsertMenu(hmenu, menupos, MF_BYPOSITION|MF_STRING, 0, child->m->text);
free(child->m->text);
}
child->m->menu_in = hmenu;
}
static void menu_renumber(windowmenu_menu* menu, unsigned int start)
{
unsigned int menupos = menu_get_native_start(menu, start);
for (unsigned int i=start;i<menu->mu->numchildren;i++)
{
windowmenu* child = menu->mu->children[i];
child->m->nativepos = menupos;
menupos += menu_get_native_length(child);
}
}
void windowmenu_menu::insert_child(unsigned int pos, windowmenu* child)
{
this->mu->numchildren++;
this->mu->children=realloc(this->mu->children, sizeof(windowmenu*)*this->mu->numchildren);
memmove(this->mu->children+pos+1, this->mu->children+pos, sizeof(windowmenu*)*(this->mu->numchildren-pos-1));
this->mu->children[pos]=(windowmenu*)child;
menu_add_item(this, pos, child);
//TODO: DrawMenuBar https://msdn.microsoft.com/en-us/library/windows/desktop/ms647633%28v=vs.85%29.aspx
menu_renumber(this, pos);
}
void windowmenu_menu::remove_child(windowmenu* child)
{
unsigned int menupos = child->m->nativepos;
unsigned int remcount = menu_get_native_length(child);
delete child;
while (remcount--) DeleteMenu(this->mu->container, menupos, MF_BYPOSITION);
this->mu->numchildren--;
unsigned int i=0;
while (this->mu->children[i]!=child) i++;
menu_renumber(this, i);
}
windowmenu_menu* windowmenu_create_submenu_shared(bool istop, const char * text, unsigned int numchildren, windowmenu* const * children)
{
windowmenu_menu* menu = new windowmenu_menu;
if (!istop)
{
menu->m = new windowmenu::impl;
menu->m->type = mtype_sub;
menu->m->text = menu_transform_name(text);
}
menu->mu = new windowmenu_menu::impl;
menu->mu->container = (istop ? CreateMenu() : CreatePopupMenu());
menu->mu->numchildren = numchildren;
menu->mu->children = malloc(sizeof(windowmenu*)*numchildren);
memcpy(menu->mu->children, children, sizeof(windowmenu*)*numchildren);
for (unsigned int i=0;i<numchildren;i++)
{
//menu->insert_child(i, children[i]);
menu_add_item(menu, i, children[i]);
}
//MENUINFO menuinf={ .cbSize=sizeof(menuinf), .fMask=MIM_STYLE|MIM_MENUDATA, .dwStyle=MNS_NOTIFYBYPOS/*|MNS_MODELESS*/, .dwMenuData=(DWORD_PTR)This };
MENUINFO menuinf;
menuinf.cbSize = sizeof(menuinf);
menuinf.fMask = MIM_STYLE|MIM_MENUDATA;
menuinf.dwStyle = MNS_NOTIFYBYPOS/*|MNS_MODELESS*/; //MODELESS makes the window border flash in stupid ways when switching between the menus.
menuinf.dwMenuData = (DWORD_PTR)menu;
SetMenuInfo(menu->mu->container, &menuinf);
return menu;
}
windowmenu_menu* windowmenu_menu::create(const char * text, unsigned int numchildren, windowmenu* const * children)
{
return windowmenu_create_submenu_shared(false, text, numchildren, children);
}
windowmenu_menu* windowmenu_menu::create_top(unsigned int numchildren, windowmenu_menu* const * children)
{
return windowmenu_create_submenu_shared(true, NULL, numchildren, (windowmenu**)children);
}
windowmenu_menu::~windowmenu_menu()
{
for (unsigned int i=0;i<this->mu->numchildren;i++) delete this->mu->children[i];
free(this->mu->children);
}
static HMENU menu_to_hmenu(windowmenu_menu* menu)
{
return menu->mu->container;
}
static void menu_activate(HMENU hmenu, DWORD pos)
{
MENUINFO menuinf;
menuinf.cbSize = sizeof(menuinf);
menuinf.fMask = MIM_MENUDATA;
GetMenuInfo(hmenu, &menuinf);
windowmenu_menu* menu = (windowmenu_menu*)menuinf.dwMenuData;
unsigned int i=0;
while (pos >= menu->mu->children[i]->m->nativepos + menu_get_native_length(menu->mu->children[i])) i++;
windowmenu* activate = menu->mu->children[i];
if (activate->m->type == mtype_item)
{
windowmenu_item* item = (windowmenu_item*)activate;
item->mu->onactivate();
}
if (activate->m->type == mtype_check)
{
windowmenu_check* check = (windowmenu_check*)activate;
MENUITEMINFO info;
info.cbSize=sizeof(MENUITEMINFO);
info.fMask=MIIM_STATE;
GetMenuItemInfo(check->m->menu_in, pos, TRUE, &info);
bool state = (info.fState & MFS_CHECKED);
state = !state;
CheckMenuItem(check->m->menu_in, pos, MF_BYPOSITION | (state?MF_CHECKED:MF_UNCHECKED));
check->mu->onactivate(state);
}
if (activate->m->type == mtype_radio)
{
windowmenu_radio* radio = (windowmenu_radio*)activate;
unsigned int newstate = pos - radio->m->nativepos;
if (newstate == radio->mu->state) return;
radio->mu->state = newstate;
CheckMenuRadioItem(radio->m->menu_in, radio->m->nativepos, radio->m->nativepos + radio->mu->numchildren-1,
radio->m->nativepos + newstate, MF_BYPOSITION);
radio->mu->onactivate(newstate);
}
}
#endif

758
arlib/gui/window.h Normal file
View File

@ -0,0 +1,758 @@
#pragma once
#include "../global.h"
#include <string.h>
class window;
class windowmenu_menu;
class widget_base;
#if defined(ARGUI_WINDOWS)
#define ARGUI_MANUAL_LAYOUT
#endif
//This must be called before calling any other window_*, before creating any interface that does any I/O, before calling anything from
// the malloc() family, and before using argc/argv; basically, before doing anything else. It should be the first thing main() does.
//It does the following actions, in whatever order makes sense:
//- Initialize the window system, if needed
//- Read off any arguments it recognizes (if any), and delete them; for example, it takes care of --display and a few others on GTK+
//- Convert argv[0] to the standard path format, if needed (hi Windows)
void window_init(int * argc, char * * argv[]);
//window toolkit is not choosable at runtime
//It is safe to interact with this window while inside its callbacks, with the exception that you may not free it.
//You may also not use window_run_*().
class window {
public:
//Marks the window as a popup dialog. This makes it act differently in some ways.
//For example, poking Escape will close it, and it may or may not get a thinner window border.
//Must be called before the first call to set_visible(). Can't be undone, and can't be called multiple times.
virtual void set_is_dialog() = 0;
//Sets which window created this one. This can, for example, center it on top of the parent.
//Should generally be combined with set_is_popup.
//Must be called before the first call to set_visible(). Can't be undone, and can't be called multiple times.
virtual void set_parent(window* parent) = 0;
//Blocks interacting with other windows in the program while this one is visible.
//It is undefined behaviour to have two modal windows visible simultaneously.
virtual void set_modal(bool modal) = 0;
//newwidth and newheight are the content size, excluding menus/toolbars/etc.
//If there is any widget whose size is unknown inside, then the sizes may only be used in resize(), and for relative measurements.
//It is allowed to call resize() on unresizable windows, but changing the size of
// any contents (changing a label text, for example) will resize it to minimum.
//If resizable, the resize callback is called after the window is resized and everything is set to the new sizes.
virtual void resize(unsigned int width, unsigned int height) = 0;
virtual void set_resizable(bool resizable, function<void(unsigned int newwidth, unsigned int newheight)> onresize) = 0;
virtual void get_pos(int * x, int * y) = 0;
//Do not move to position (0,0) - it puts the window border outside the screen.
virtual void set_pos(int x, int y) = 0;
virtual void set_onmove(function<void(int x, int y)> onmove) = 0;
virtual void set_title(const char * title) = 0;
//The callback tells whether the close request should be honored; true for close, false for keep.
//The window is only hidden, not deleted; you can use show() again later.
//It is safe to free this structure from within this callback; if you do this, return true for close.
virtual void set_onclose(function<bool()> onclose) = 0;
//Appends a menu bar to the top of the window. If the window has a menu already, it's replaced. NULL removes the menu.
//There's no real reason to replace it, though. Just change it.
//Must be created by windowmenu_menu::create_top.
virtual void set_menu(windowmenu_menu* menu) = 0;
//Creates a status bar at the bottom of the window. It is undefined what happens if numslots equals or exceeds 32.
//align is how each string is aligned; 0 means touch the left side, 1 means centered, 2 means touch the right side.
//dividerpos is in 240ths of the window size. Values 0 and 240, as well as
// a divider position to the left of the previous one, yield undefined behaviour.
//dividerpos[numslots-1] is ignored; the status bar always covers the entire width of the window.
//It is implementation defined whether the previous status bar strings remain, or if you must use statusbar_set again.
//It is implementation defined whether dividers will be drawn. However, it is guaranteed
// that the implementation will look like the rest of the operating system, as far as that's feasible.
//It is implementation defined what exactly happens if a string is too
// long to fit; however, it is guaranteed to show as much as it can.
//To remove the status bar, set numslots to 0.
virtual void statusbar_create(int numslots, const int * align, const int * dividerpos) = 0;
//Sets a string on the status bar. The index is zero-based. All strings are initially blank.
virtual void statusbar_set(int slot, const char * text) = 0;
//This replaces the contents of a window.
virtual void replace_contents(widget_base* contents) = 0;
//Setting a window visible while it already is will do nothing.
virtual void set_visible(bool visible) = 0;
virtual bool is_visible() = 0;
//Call only after making the window visible.
virtual void focus() = 0;
//If the menu is active, the window is considered not active.
//If the menu doesn't exist, it is considered not active.
//If the window is hidden, results are undefined.
virtual bool is_active() = 0;
virtual bool menu_active() = 0;
//This will also remove the window from the screen, if it's visible.
virtual ~window() = 0;
//Returns a native handle to the window. It is implementation defined what this native handle is, or if it's implemented at all.
virtual uintptr_t _get_handle() { return 0; }
//Repositions the window contents. May not necessarily be implemented, if reflow requests are detected in other ways.
//If false, the reflow will be done later and the old sizes are still present.
virtual bool _reflow() { return false; };
};
inline window::~window(){}
window* window_create(widget_base* contents);
//Each widget is owned by the layout or window it's put in (layouts can own more layouts). Deleting the parent deletes the children.
//Each widget has a few shared base functions that can be called without knowing what
// type of widget this is. However, they should all be seen as implementation details.
//It is undefined behaviour to query a widget's state before it's placed inside a window.
//Any pointers given during widget creation must be valid until the widget is placed inside a window.
//Most functions return the object it's called on, so object state can be set while creating the object.
class widget_base : nocopy {
public:
#ifdef ARGUI_MANUAL_LAYOUT
//measure() returns no value, but sets the width and height. The sizes are undefined if the last
// function call on the widget was not measure(); widgets may choose to update their sizes in
// response to anything that resizes them, and leave measure() blank.
//If multiple widgets want the space equally much, they get equal fractions, in addition to their base demand.
//If a widget gets extra space and doesn't want it, it should add some padding in any direction.
//The widget should, if needed by the window manager, forward all plausible events to its parent window,
// unless the widget wants the events. (For example, a button will want mouse events, but not file drop events.)
//The window handles passed around are implementation defined.
//The return value from init() is the number of child windows involved, from the window manager's point of view.
virtual unsigned int init(struct window * parent, uintptr_t parenthandle) = 0;
virtual void measure() = 0;
unsigned int width;
unsigned int height;
virtual void place(void* resizeinf, unsigned int x, unsigned int y, unsigned int width, unsigned int height) = 0;
//this one acts roughly like Q_OBJECT
#define WIDGET_BASE \
unsigned int init(struct window * parent, uintptr_t parenthandle); \
void measure(); \
void place(void* resizeinf, unsigned int x, unsigned int y, unsigned int width, unsigned int height);
#else
void * widget;
#define WIDGET_BASE /* */
#endif
//The priorities mean:
//0 - Widget has been assigned a certain size; it must get exactly that. (Canvas, viewport)
//1 - Widget wants a specific size; will only grudgingly accept more. (Most of them)
//2 - Widget has orders to consume extra space if there's any left over and nothing really wants it. (Padding)
//3 - Widget will look better if given extra space. (Textbox, listbox)
//4 - Widget is ordered to be resizable. (Canvas, viewport)
unsigned char widthprio;
unsigned char heightprio;
virtual ~widget_base() {};
};
class widget_padding : public widget_base { WIDGET_BASE
public:
widget_padding(bool vertical);
~widget_padding();
public:
struct impl;
impl * m;
};
static inline widget_padding* widget_create_padding_horz() { return new widget_padding(false); }
static inline widget_padding* widget_create_padding_vert() { return new widget_padding(true); }
class widget_layout : public widget_base { WIDGET_BASE
protected:
void construct(unsigned int numchildren, widget_base* * children,
unsigned int totwidth, unsigned int * widths, bool uniformwidths,
unsigned int totheight, unsigned int * heights, bool uniformheights);
widget_layout() {}
public:
//The lists are terminated with a NULL. It shouldn't be empty.
widget_layout(bool vertical, bool uniform, widget_base* firstchild, ...);
#define widget_create_layout_horz(...) (new widget_layout(false, false, __VA_ARGS__))
#define widget_create_layout_vert(...) (new widget_layout(true, false, __VA_ARGS__))
//This one allows some widgets to take up multiple boxes of the grid. They're still stored row by
// row, except that there is no entry for slots that are already used.
//It is undefined behaviour if a widget does not fit where it belongs, if it overlaps another widget,
// or if it's size 0 in either direction.
widget_layout(unsigned int totwidth, unsigned int totheight, bool uniformwidths, bool uniformheights,
unsigned int firstwidth, unsigned int firstheight, widget_base* firstchild, ...);
#define widget_create_layout(...) (new widget_layout(__VA_ARGS__))
//In this one, the widths/heights arrays can be NULL, which is treated as being filled with 1s.
//But if you want that, you should probably use the grid constructor instead. (Though it's useful for the constructors themselves.)
widget_layout(unsigned int numchildren, widget_base* * children,
unsigned int totwidth, unsigned int * widths, bool uniformwidths,
unsigned int totheight, unsigned int * heights, bool uniformheights)
{
construct(numchildren, children, totwidth, widths, uniformwidths, totheight, heights, uniformheights);
}
~widget_layout();
public:
struct impl;
impl * m;
};
//The widgets are stored row by row. There is no NULL terminator, because the size is known from the arguments already.
//Uniform sizes mean that every row has the same height, and every column has the same width.
widget_layout* widget_create_layout_grid(unsigned int width, unsigned int height, bool uniformsizes, widget_base* firstchild, ...);
class widget_label : public widget_base { WIDGET_BASE
public:
widget_label(const char * text = "");
~widget_label();
//Disabling a label does nothing, but may change how it looks.
//Useful it if it's attached to another widget, and this widget is disabled.
widget_label* set_enabled(bool enable);
widget_label* set_text(const char * text);
widget_label* set_ellipsize(bool ellipsize);//Defaults to false.
//Alignment 0 means touch the left side, 1 means centered, 2 means touch the right side. Defaults to left.
widget_label* set_alignment(int align);
public:
struct impl;
impl * m;
};
#define widget_create_label(...) new widget_label(__VA_ARGS__)
class widget_button : public widget_base { WIDGET_BASE
public:
widget_button(const char * text = "");
~widget_button();
widget_button* set_enabled(bool enable);
widget_button* set_text(const char * text);
widget_button* set_onclick(function<void()> onclick);
public:
struct impl;
impl * m;
};
#define widget_create_button(...) (new widget_button(__VA_ARGS__))
class widget_checkbox : public widget_base { WIDGET_BASE
public:
widget_checkbox(const char * text = "");
~widget_checkbox();
widget_checkbox* set_enabled(bool enable);
widget_checkbox* set_text(const char * text);
bool get_state();
widget_checkbox* set_state(bool checked);
widget_checkbox* set_onclick(function<void(bool checked)> onclick);
public:
struct impl;
impl * m;
};
#define widget_create_checkbox(...) (new widget_checkbox(__VA_ARGS__))
class widget_radio : public widget_base { WIDGET_BASE
public:
widget_radio(const char * text = "");
~widget_radio();
widget_radio* set_enabled(bool enable);
widget_radio* set_text(const char * text);
//The button this function is called on becomes the group leader. The leader must be the first in the group.
//It is undefined behaviour to attempt to redefine a group.
//It is undefined behaviour to set the onclick handler, or set or get the state, for anything except the group leader.
//The window may not be shown before grouping them.
widget_radio* group(unsigned int numitems, widget_radio* * group);
//Returns which one is active. The group leader is 0.
unsigned int get_state();
//State values are the same as get_state().
widget_radio* set_state(unsigned int state);
//Called whenever the state changes. It is allowed to set the state in response to this.
//It is undefined whether the callback can fire for the previously active state, for example due to clicking the button twice.
//Must be set only for the group leader.
widget_radio* set_onclick(function<void(unsigned int state)> onclick);
public:
struct impl;
impl * m;
};
#define widget_create_radio(...) (new widget_radio(__VA_ARGS__))
//This one wraps them in a horizontal or vertical layout, and groups them.
//It's just a convenience; you can create them and group them manually and get the same results.
//The first one will expect a set of radio buttons. The second will expect a set of radio button texts, and will put the group leader in the pointer.
widget_layout* widget_create_radio_group(bool vertical, widget_radio* leader, ...);
widget_layout* widget_create_radio_group(bool vertical, widget_radio* * leader, const char * firsttext, ...);
#define widget_create_radio_group_horz(...) widget_create_radio_group(false, __VA_ARGS__)
#define widget_create_radio_group_vert(...) widget_create_radio_group(true, __VA_ARGS__)
class widget_textbox : public widget_base { WIDGET_BASE
public:
widget_textbox();
~widget_textbox();
widget_textbox* set_enabled(bool enable);
widget_textbox* focus();
//The return value is guaranteed valid until the next call to any function
// on this object, or the next window_run[_iter], whichever comes first.
const char * get_text();
widget_textbox* set_text(const char * text);
//If the length is 0, it's unlimited.
widget_textbox* set_length(unsigned int maxlen);
//How many instances of the letter 'X' should fit in the textbox without scrolling. Defaults to 5.
widget_textbox* set_width(unsigned int xs);
//Highlights the widget as containing invalid data. Can paint the background red, focus it, and various other stuff.
//The invalidity highlight is removed as soon as the contents are changed, but may be restored on the onchange event.
//Making a widget invalid and disabled simultaneously is undefined behaviour.
widget_textbox* set_invalid(bool invalid);
//Called whenever the text changes.
//Note that it is not guaranteed to fire only if the text has changed; it may, for example,
// fire if the user selects an E and types another E on top. Or for no reason at all.
//Also note that 'text' is invalidated under the same conditions as get_text is.
widget_textbox* set_onchange(function<void(const char * text)> onchange);
//Called if the user hits Enter while this widget is focused. [TODO: Doesn't that activate the default button instead?]
widget_textbox* set_onactivate(function<void(const char * text)> onactivate);
public:
struct impl;
impl * m;
};
#define widget_create_textbox(...) (new widget_textbox(__VA_ARGS__))
//A canvas is a simple image. It's easy to work with, but performance is poor and it can't vsync, so it shouldn't be used for video.
class widget_canvas : public widget_base { WIDGET_BASE
public:
widget_canvas(unsigned int width, unsigned int height);
~widget_canvas();
//can't disable this
widget_canvas* resize(unsigned int width, unsigned int height);
uint32_t * (*draw_begin)();
void draw_end();
//Whether to hide the cursor while it's on top of this widget.
//The mouse won't instantly hide; if it's moving, it will be visible. The exact details are up to the implementation,
// but it will be similar to "the mouse is visible if it has moved within the last 1000 milliseconds".
widget_canvas* set_hide_cursor(bool hide);
//This must be called before the window is shown, and only exactly once.
//All given filenames are invalidated once the callback returns.
widget_canvas* set_support_drop(function<void(const char * const * filenames)> on_file_drop);
public:
struct impl;
impl * m;
};
#define widget_create_canvas(width, height) (new widget_canvas(width, height))
//A viewport fills the same purpose as a canvas, but the tradeoffs go the opposite way.
class widget_viewport : public widget_base { WIDGET_BASE
public:
widget_viewport(unsigned int width, unsigned int height);
~widget_viewport();
//can't disable this
widget_viewport* resize(unsigned int width, unsigned int height);
uintptr_t get_window_handle();
//The position is relative to the desktop.
void get_position(int * x, int * y, unsigned int * width, unsigned int * height);
//See documentation of canvas for these.
widget_viewport* set_hide_cursor(bool hide);
widget_viewport* set_support_drop(function<void(const char * const * filenames)> on_file_drop);
//Keycodes are from libretro; 0 if unknown. Scancodes are implementation defined, but if there is no libretro translation, then none is returned.
//widget_viewport* set_kb_callback)(function<void(unsigned int keycode, unsigned int scancode)> keyboard_cb);
public:
struct impl;
impl * m;
};
#define widget_create_viewport(width, height) (new widget_viewport(width, height))
class widget_listbox_virtual : public widget_base { WIDGET_BASE
private:
widget_listbox_virtual() {}
void construct(unsigned int numcolumns, const char * * columns);
public:
widget_listbox_virtual(unsigned int numcolumns, const char * * columns) { construct(numcolumns, columns); }
widget_listbox_virtual(const char * firstcol, ...);
template<typename... Args>
widget_listbox_virtual(Args... cols)
{
const char * cols_up[] = { cols... };
construct(sizeof...(cols), cols_up);
}
~widget_listbox_virtual();
widget_listbox_virtual* set_enabled(bool enable);
//Column -1 is the checkboxes, if they exist; NULL for unchecked, non-NULL (not necessarily a valid pointer) for checked.
//The search callback should return the row ID closest to 'start' in the given direction where the first column starts with 'str'.
//If 'start' itself starts with 'prefix', it should be returned.
//If there is none in that direction, loop around. If still no match, return (size_t)-1.
//It's optional, but recommended for better performance.
//(GTK+ is stupid and doesn't let me use it.)
widget_listbox_virtual* set_contents(function<const char * (size_t row, int column)> get_cell,
function<size_t(const char * prefix, size_t start, bool up)> search);
//On Windows, the limit is 100 million; if more than that, it puts in 0.
// Probably because it's a nice round number, and the listbox row height (19) times 100 million is fairly close to 2^31.
//On GTK+, it's 100000; it's slow on huge lists.
//TODO: figure out why.
static size_t get_max_rows();
//If more than get_max_rows(), it's capped to that.
widget_listbox_virtual* set_num_rows(size_t rows);
//Call this after changing anything. It's fine to change multiple rows at once with only one call.
widget_listbox_virtual* refresh();
//If the active row changes, set_focus_change will fire. However, onactivate will likely not.
//The exact conditions under which a listbox entry is activated is platform dependent, but double
// click and Enter are likely. It is guaranteed to be possible.
//Returns (size_t)-1 if no row is active.
size_t get_active_row();
widget_listbox_virtual* set_on_focus_change(function<void(size_t row)> onchange);
widget_listbox_virtual* set_onactivate(function<void(size_t row)> onactivate);
//This is the size on the screen. The height is how many items show up below the header;
// the widths are the longest string that should comfortably fit (or the column header, whichever is wider).
//0 in height means "use some sensible default"; NULL in widths means "only check the column".
//'expand' is which column should get all extra size, if the widget is given more space than it asked for; -1 for even distribution.
//Defaults to 10, column headers, and -1.
//TODO: do this on Windows.
widget_listbox_virtual* set_size(unsigned int height, const char * const * widths, int expand);
//It is implementation defined how the checkboxes are represented. They can be prepended to the
// first column, on a column of their own, or something weirder. The position relative to the
// other columns is not guaranteed, though it is likely to be the leftmost column.
//The toggle callback does not contain the current nor former state; the user is expected to keep track of that.
widget_listbox_virtual* add_checkboxes(function<void(size_t row)> ontoggle);
//TODO (maybe): make columns editable; for windows, it's LVN_BEGINLABELEDIT
public:
struct impl;
impl * m;
};
#define widget_create_listbox_virtual(...) (new widget_listbox_virtual(__VA_ARGS__))
//If performance is bad, switch to the virtual listbox.
class widget_listbox : public widget_listbox_virtual
{
size_t numcols;
size_t numrows;
char * * * cells; // [row][col] is a string
const char * get_cell(size_t row, int column) { return cells[row][column]; }
void init(int numcols)
{
this->numcols = numcols;
this->numrows = 0;
this->cells = NULL;
widget_listbox_virtual::set_contents(bind_this(&widget_listbox::get_cell), NULL);
}
public:
widget_listbox(unsigned int numcolumns, const char * * columns) : widget_listbox_virtual(numcolumns, columns)
{
init(numcolumns);
}
template<typename... Args>
widget_listbox(Args... cols) : widget_listbox_virtual(cols...)
{
init(sizeof...(cols));
}
~widget_listbox()
{
for (size_t row=0;row<numrows;row++)
{
for (size_t col=0;col<numcols;col++)
{
free(cells[row][col]);
}
free(cells[row]);
}
free(cells);
}
size_t rows() { return numrows; }
widget_listbox* add_row(const char * const * cols) { insert_row(numrows, cols); return this; }
widget_listbox* add_row(const char * * cols) { add_row((const char * const *)cols); return this; }
template<typename... Args>
widget_listbox* add_row(Args... cols)
{
const char * cols_up[] = { cols... };
add_row(cols_up);
return this;
}
widget_listbox* insert_row(size_t before, const char * const * cols)
{
cells = realloc(cells, sizeof(char**)*(numrows+1));
memmove(cells+before+1, cells+before, sizeof(char**)*(numrows-before));
numrows++;
cells[before] = malloc(sizeof(char*)*numcols);
for (size_t col=0;col<numcols;col++)
{
cells[before][col] = strdup(cols[col]);
}
widget_listbox_virtual::set_num_rows(numrows);
return this;
}
widget_listbox* insert_row(size_t before, const char * * cols) { insert_row(before, (const char * const *)cols); return this; }
template<typename... Args>
widget_listbox* insert_row(size_t before, Args... cols)
{
const char * cols_up[] = { cols... };
insert_row(before, cols_up);
return this;
}
widget_listbox* delete_row(size_t row)
{
for (size_t col=0;col<numcols;col++)
{
free(cells[row][col]);
}
numrows--;
memmove(cells+row, cells+row+1, sizeof(char**)*(numrows-row));
widget_listbox_virtual::set_num_rows(numrows);
return this;
}
widget_listbox* replace_row(size_t row, const char * const * cols)
{
for (size_t col=0;col<numcols;col++)
{
free(cells[row][col]);
cells[row][col] = strdup(cols[col]);
}
widget_listbox_virtual::refresh();
return this;
}
widget_listbox* replace_row(size_t row, const char ** cols) { replace_row(row, (const char * const *)cols); return this; }
template<typename... Args>
widget_listbox* replace_row(size_t row, Args... cols)
{
const char * cols_up[] = { cols... };
replace_row(row, cols_up);
return this;
}
widget_listbox* replace_cell(size_t row, size_t col, const char * text)
{
free(cells[row][col]);
cells[row][col] = strdup(text);
widget_listbox_virtual::refresh();
return this;
}
private:
//Calling these will screw up the internal state. Yes, you can do it, but it'll break if you try.
widget_listbox_virtual* set_contents(function<const char * (size_t row, int column)> get_cell,
function<size_t(const char * prefix, size_t start, bool up)> search);
widget_listbox_virtual* set_num_rows(size_t rows);
widget_listbox_virtual* refresh(size_t row);
widget_listbox_virtual* add_checkboxes(function<void(size_t row)> ontoggle);
};
#define widget_create_listbox(...) (new widget_listbox(__VA_ARGS__))
//A decorative frame around a widget, to group them together. The widget can be a layout (and probably should, otherwise you're adding a box to a single widget).
class widget_frame : public widget_base { WIDGET_BASE
public:
widget_frame(const char * text, widget_base* contents);
~widget_frame();
//can't disable this (well okay, we can, but it's pointless to disable a widget that does nothing)
widget_frame* set_text(const char * text);
public:
struct impl;
impl * m;
};
#define widget_create_frame(...) (new widget_frame(__VA_ARGS__))
//The only thing that may be done with a menu item before it's in a window, is passing it into another menu constructor.
//The text pointers, if any, must be also be valid until added to the parent.
//Destruction is not allowed.
class windowmenu : nocopy {
public:
virtual ~windowmenu() = 0;
public:
struct impl;
impl * m;
};
inline windowmenu::~windowmenu(){}
class windowmenu_item : public windowmenu {
public:
void set_enabled(bool enable);
static windowmenu_item* create(const char * text, function<void(void)> onactivate);
~windowmenu_item();
public:
struct impl;
impl * mu;
};
class windowmenu_check : public windowmenu {
public:
void set_enabled(bool enable);
bool get_checked();
void set_checked(bool checked);
static windowmenu_check* create(const char * text, function<void(bool checked)> onactivate);
~windowmenu_check();
public:
struct impl;
impl * mu;
};
class windowmenu_radio : public windowmenu {
public:
void set_enabled(bool enable);
//If the new state is out of range, it's undefined behaviour.
unsigned int get_state();
void set_state(unsigned int state);
static windowmenu_radio* create(function<void(unsigned int state)> onactivate, const char * firsttext, ...);
static windowmenu_radio* create(unsigned int numitems, const char * const * texts, function<void(unsigned int state)> onactivate);
~windowmenu_radio();
public:
struct impl;
impl * mu;
};
class windowmenu_separator : public windowmenu {
public:
static windowmenu_separator* create();
~windowmenu_separator();
public:
struct impl;
impl * mu;
};
class windowmenu_menu : public windowmenu {
public:
void insert_child(unsigned int pos, windowmenu* child);
void remove_child(windowmenu* child);
static windowmenu_menu* create(const char * text, windowmenu* firstchild, ...);
static windowmenu_menu* create(const char * text, unsigned int numchildren, windowmenu* const * children);
//This one goes in a window. Anything else only goes in other menues.
static windowmenu_menu* create_top(windowmenu_menu* firstchild, ...);
static windowmenu_menu* create_top(unsigned int numchildren, windowmenu_menu* const * children);
~windowmenu_menu();
public:
struct impl;
impl * mu;
};
//Tells the window manager to handle recent events and fire whatever callbacks are relevant.
//Neither of them are allowed while inside any callback of any kind.
//Some other functions may call these two.
void window_run_iter();//Returns as soon as possible. Use if you're synchronizing on something else.
void window_run_wait();//Returns only after doing something. Use while idling. It will return if any
// state (other than the time) has changed or if any callback has fired.
// It may also return due to uninteresting events, as often as it wants;
// however, repeatedly calling it will leave the CPU mostly idle.
//Shows a message box. You can do that by creating a label and some buttons, but it gives inferior results.
//Returns true for OK and Yes, and false for Cancel/No/close window.
//The title may or may not be ignored.
enum mbox_sev { mb_info, mb_warn, mb_err };
enum mbox_btns { mb_ok, mb_okcancel, mb_yesno };
bool window_message_box(const char * text, const char * title, enum mbox_sev severity, enum mbox_btns buttons);
//Usable for both ROMs and dylibs. If dylib is true, the returned filenames are for the system's
// dynamic linker; this will disable gvfs-like systems the dynamic linker can't understand, and may
// hide files not marked executable, if this makes sense. If false, only file_read/etc is guaranteed
// to work.
//If multiple is true, multiple files may be picked; if not, only one can be picked. Should
// generally be true for dylibs and false for ROMs, but not guaranteed.
//The parent window will be disabled while the dialog is active.
//Both extensions and return value have the format { "smc", ".sfc", NULL }. Extensions may or may not
// include the dot; if it's not there, it's implied.
//Return value is full paths, zero or more. Duplicates are allowed in both input and output.
//The return value is valid until the next call to window_file_picker() or window_run_*(), whichever comes first.
const char * const * window_file_picker(struct window * parent,
const char * title,
const char * const * extensions,
const char * extdescription,
bool dylib,
bool multiple);
//Returns the number of microseconds since an undefined start time.
//The start point doesn't change while the program is running, but need not be the same across reboots, nor between two processes.
//It can be program launch, system boot, the Unix epoch, or whatever.
uint64_t window_get_time();
//The different components may want to initialize various parts each. All three may not necessarily exist.
void _window_init_inner();
void _window_init_misc();
void _window_init_shell();
//If the window shell is the one told about interaction with a widget, this sends it back to the inner area.
uintptr_t _window_notify_inner(void* notification);
//Because Windows is a douchebag.
uintptr_t _window_get_widget_color(unsigned int type, void* handle, void* draw, void* parent);
//This one can be used if the one calling widget_listbox_virtual->set_contents doesn't provide a search function.
size_t _widget_listbox_search(function<const char *(size_t row, int column)> get_cell, size_t rows,
const char * prefix, size_t start, bool up);
#ifdef ARGUIPROT_X11
//Returns the display and screen we should use.
//The concept of screens only exists on X11, so this should not be used elsewhere.
//Only I/O drivers should have any reason to use this.
struct _XDisplay;
typedef struct _XDisplay Display;
struct window_x11_info {
Display* display;
int screen;
unsigned long root; //The real type is Window aka XID.
};
extern struct window_x11_info window_x11;
#endif
//TODO: If porting to Qt, use https://woboq.com/blog/verdigris-qt-without-moc.html

100
arlib/intarray.h Normal file
View File

@ -0,0 +1,100 @@
#include "global.h"
//Use only with the primitive types.
template<typename T>
class intarray {
public:
//Could be optimized a lot harder, but I don't care, it's not used much.
T* ptr;
size_t len;
static const size_t MIN_SIZE = 128/sizeof(T); // minimum number of objects this one always holds
static const size_t MIN_SIZE_SHRINK = 4096/sizeof(T); // don't shrink smaller than this size
static const size_t MIN_SIZE_FACTOR = 4; // only shrink by this factor or more (must be power of two)
private:
size_t capacity;
static size_t resize_size(size_t oldlen, size_t len)
{
len = bitround(len);
if (len < MIN_SIZE) return MIN_SIZE;
if (len > oldlen) return len;
if (len < oldlen/MIN_SIZE_FACTOR && oldlen>MIN_SIZE_SHRINK)
{
if (oldlen/MIN_SIZE_FACTOR < MIN_SIZE_SHRINK) return MIN_SIZE_SHRINK;
else return oldlen/MIN_SIZE_FACTOR;
}
return oldlen;
}
void resize(size_t newcap)
{
newcap = resize_size(capacity, newcap);
if (newcap == capacity) return;
capacity = newcap;
ptr = realloc(ptr, sizeof(T)*capacity);
}
public:
intarray()
{
ptr = NULL;
len = 0;
capacity = 0;
}
intarray(const intarray<T>& other)
{
ptr = NULL;
capacity = 0;
resize(other.len);
len = other.len;
memcpy(ptr, other.ptr, sizeof(T)*len);
}
~intarray() { free(ptr); }
//Optimization - call this to reserve a chunk of space of 'len' entries. Calling append() with the same data will avoid a copy.
//The length to append() may be smaller than here. If you don't want to append anything at all, it's fine to not call append().
T* append_try(size_t len)
{
resize(this->len + len);
return ptr+len;
}
void append(const T* data, size_t len)
{
if (data == ptr+len)
{
this->len += len;
return;
}
resize(this->len + len);
memcpy(ptr + this->len, data, sizeof(T)*len);
this->len += len;
}
void prepend(const T* data, size_t len)
{
resize(this->len + len);
memmove(ptr+len, ptr, sizeof(T)*this->len);
memcpy(ptr, data, sizeof(T)*len);
this->len += len;
}
void drop(int count)
{
memmove(ptr, ptr+count, sizeof(T)*(len-count));
len -= count;
resize(len);
}
};
using bytearray = intarray<uint8_t>;

44
arlib/intwrap.h Normal file
View File

@ -0,0 +1,44 @@
#pragma once
#include "global.h"
//Given class U, where U supports operator T() and operator=(T), intwrap<U> enables all the integer operators.
//Most are already supported by casting to the integer type, but this one adds the assignment operators too.
template<typename U, typename T = U> class intwrap : public U {
T get() { return *this; }
void set(T val) { this->U::operator=(val); }
public:
//no operator T(), that goes to the parent
T operator++(int) { T r = get(); set(r+1); return r; }
T operator--(int) { T r = get(); set(r-1); return r; }
intwrap<U,T>& operator++() { set(get()+1); return *this; }
intwrap<U,T>& operator--() { set(get()-1); return *this; }
intwrap<U,T>& operator =(const T i) { set( i); return *this; }
intwrap<U,T>& operator +=(const T i) { set(get() + i); return *this; }
intwrap<U,T>& operator -=(const T i) { set(get() - i); return *this; }
intwrap<U,T>& operator *=(const T i) { set(get() * i); return *this; }
intwrap<U,T>& operator /=(const T i) { set(get() / i); return *this; }
intwrap<U,T>& operator %=(const T i) { set(get() % i); return *this; }
intwrap<U,T>& operator &=(const T i) { set(get() & i); return *this; }
intwrap<U,T>& operator |=(const T i) { set(get() | i); return *this; }
intwrap<U,T>& operator ^=(const T i) { set(get() ^ i); return *this; }
intwrap<U,T>& operator<<=(const T i) { set(get()<< i); return *this; }
intwrap<U,T>& operator>>=(const T i) { set(get()>> i); return *this; }
intwrap() {}
intwrap(T i) { set(i); }
template<typename T1> intwrap(T1 v1) : U(v1) {}
template<typename T1, typename T2> intwrap(T1 v1, T2 v2) : U(v1, v2) {}
template<typename T1, typename T2, typename T3> intwrap(T1 v1, T2 v2, T3 v3) : U(v1, v2, v3) {}
};
template<typename T> struct int_inherit_core {
T item;
operator T() { return item; }
void operator=(T newval) { item=newval; }
int_inherit_core(T item) : item(item) {}
};
//This allows inheriting from something that acts like a plain int.
//Why doesn't raw C++ allow that? Would it cause too much pains with people creating unsigned iostreams?
template<typename T> class int_inherit : public intwrap<int_inherit_core<T> > {
int_inherit(T item) : intwrap<int_inherit_core<T> >(item) {}
};

62
arlib/malloc.cpp Normal file
View File

@ -0,0 +1,62 @@
#include "global.h"
#undef malloc
#undef realloc
#undef calloc
#include <stdlib.h>
static void debug(void* ptr)
{
//static unsigned int g=0;
//printf("%i: %p\n",g++,ptr);
//if(g==1000)abort();
}
static void malloc_fail(size_t size)
{
printf("malloc failed, size %" PRIuPTR, size);
abort();
}
anyptr malloc_check(size_t size)
{
void* ret=malloc(size);
debug(ret);
if (size && !ret) malloc_fail(size);
return ret;
}
anyptr try_malloc(size_t size)
{
return malloc(size);
}
anyptr realloc_check(anyptr ptr, size_t size)
{
void* ret=realloc(ptr, size);
debug(ret);
if (size && !ret) malloc_fail(size);
return ret;
}
anyptr try_realloc(anyptr ptr, size_t size)
{
return realloc(ptr, size);
}
anyptr calloc_check(size_t size, size_t count)
{
void* ret=calloc(size, count);
debug(ret);
if (size && count && !ret) malloc_fail(size);
return ret;
}
anyptr try_calloc(size_t size, size_t count)
{
return calloc(size, count);
}
void* operator new(size_t n) { return malloc_check(n); }
void* operator new[](size_t n) { return malloc_check(n); }
void operator delete(void * p) { free(p); }
extern "C" void __cxa_pure_virtual() { puts("__cxa_pure_virtual"); abort(); }

38
arlib/os.h Normal file
View File

@ -0,0 +1,38 @@
#pragma once
#include "global.h"
//this is more thread.h than anything else - dylib is the only non-thread-related part. But there's
// no other place where dylib would fit, so os.h it is.
#ifdef __unix__
#define DYLIB_EXT ".so"
#define DYLIB_MAKE_NAME(name) "lib" name DYLIB_EXT
#endif
#ifdef _WIN32
#define DYLIB_EXT ".dll"
#define DYLIB_MAKE_NAME(name) name DYLIB_EXT
#endif
//Nasty stuff going on here... it's impossible to construct this object.
//The size varies per platform, so I have to allocate the object. This could be done by putting in a void* member,
// but that's a pointless level of indirection - instead, I cast the allocated value and return that!
//It's probably undefined, but the compiler won't be able to prove that, so it has to do what I want.
//Perhaps it would be better to let the configure script declare what the size is so they can have a
// member of type uint32_t data[12] and be constructed normally, but this is good enough for now.
class dylib : private nocopy {
dylib(){}
public:
static dylib* create(const char * filename, bool * owned=NULL);
static const char * ext() { return DYLIB_EXT; }
void* sym_ptr(const char * name);
funcptr sym_func(const char * name);
//per http://chadaustin.me/cppinterface.html - redirect operator delete to a function, this doesn't come from the normal allocator.
static void operator delete(void* p) { if (p) ((dylib*)p)->release(); }
void release();//this is the real destructor, you can use either this one or delete it
};
//If the program is run under a debugger, this triggers a breakpoint. If not, ignored.
void debug_break();
//If the program is run under a debugger, this triggers a breakpoint. The program is then terminated.
void debug_abort();

1
arlib/sandbox.h Normal file
View File

@ -0,0 +1 @@
#include "sandbox/sandbox.h"

View File

View File

View File

@ -0,0 +1,156 @@
#ifdef __linux__
#include "sandbox-internal.h"
#undef bind
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
void sandbox_cross_init(int* pfd, int* cfd)
{
int fds[2];
socketpair(AF_LOCAL, SOCK_DGRAM, 0, fds);
*pfd = fds[0];
*cfd = fds[1];
}
//from http://blog.varunajayasiri.com/passing-file-descriptors-between-processes-using-sendmsg-and-recvmsg
//somewhat reformatted
void sandbox_cross_send(int socket, int fd_to_send, int errno_ret)
{
//need at least one byte of data, otherwise recvmsg gets angry
struct iovec iov = { &errno_ret, sizeof(errno_ret) };
char ctrl_buf[CMSG_SPACE(sizeof(int))] = {};
struct msghdr message = {
.msg_name = NULL, .msg_namelen = 0,
.msg_iov = &iov, .msg_iovlen = 1,
.msg_control = ctrl_buf, .msg_controllen = sizeof(ctrl_buf),
.msg_flags = 0,
};
cmsghdr* ctrl_msg = CMSG_FIRSTHDR(&message);
ctrl_msg->cmsg_level = SOL_SOCKET;
ctrl_msg->cmsg_type = SCM_RIGHTS;
ctrl_msg->cmsg_len = CMSG_LEN(sizeof(int));
*(int*)CMSG_DATA(ctrl_msg) = fd_to_send;
if (fd_to_send >= 0) errno = 0;
if (fd_to_send < 0) message.msg_controllen = 0;
sendmsg(socket, &message, 0);
}
int sandbox_cross_recv(int socket)
{
int errno_ret;
struct iovec iov = { &errno_ret, sizeof(int) };
char ctrl_buf[CMSG_SPACE(sizeof(int))] = {};
struct msghdr message = {
.msg_name = NULL, .msg_namelen = 0,
.msg_iov = &iov, .msg_iovlen = 1,
.msg_control = ctrl_buf, .msg_controllen = sizeof(ctrl_buf),
.msg_flags = 0,
};
if (recvmsg(socket, &message, 0) <= 0) return -1;
for (cmsghdr* ctrl_msg=CMSG_FIRSTHDR(&message);ctrl_msg!=NULL;ctrl_msg=CMSG_NXTHDR(&message, ctrl_msg))
{
if (ctrl_msg->cmsg_level == SOL_SOCKET && ctrl_msg->cmsg_type == SCM_RIGHTS)
{
return *(int*)CMSG_DATA(ctrl_msg);
}
}
errno = errno_ret;
return -1;
}
int sandbox_cross_request(int socket, const char * path, int flags, mode_t mode)
{
struct iovec iov[3] = { { &flags, sizeof(flags) }, { &mode, sizeof(mode) }, { (char*)path, strlen(path) } };
struct msghdr message = {
.msg_name = NULL, .msg_namelen = 0,
.msg_iov = iov, .msg_iovlen = 2,
.msg_control = NULL, .msg_controllen = 0,
.msg_flags = 0,
};
sendmsg(socket, &message, 0);
return sandbox_cross_recv(socket);
}
void sandbox_cross_serve_request_full(int socket,
int (*access)(const char * path, int flags, mode_t mode, void* userdata),
void* userdata)
{
struct {
int flags;
mode_t mode;
char path[1024+1];
} msg;
struct iovec iov = { &msg, sizeof(msg)-1 };
struct msghdr message = {
.msg_name = NULL, .msg_namelen = 0,
.msg_iov = &iov, .msg_iovlen = 1,
.msg_control = NULL, .msg_controllen = 0,
.msg_flags = 0,
};
int bytes = recvmsg(socket, &message, MSG_DONTWAIT);
if (bytes <= 0) return;
if (message.msg_flags & MSG_TRUNC)
{
sandbox_cross_send(socket, -1, ENAMETOOLONG);
return;
}
((char*)&msg)[bytes] = '\0';
int retfd = access(msg.path, msg.flags, msg.mode, userdata);
sandbox_cross_send(socket, retfd, errno);
}
struct req_dat {
bool (*access)(const char * path, bool write, void* userdata);
void* userdata;
};
static int req_sub(const char * path, int flags, mode_t mode, void* userdata)
{
req_dat* dat = (req_dat*)userdata;
if (flags & O_CREAT)
{
if (mode & ~0777) goto deny;
}
else mode=0;
static_assert(O_RDONLY==0);
static_assert(O_WRONLY==1);
static_assert(O_RDWR==2);
static const int flag_ignore = 0; // allow these flags, but ignore them
static const int flag_read = O_CLOEXEC|O_LARGEFILE; // allow these flags
static const int flag_write = O_WRONLY|O_RDWR|O_APPEND|O_CREAT|O_EXCL|O_TRUNC; // allow these flags, but only if write access is fine
flags &= ~flag_ignore;
if ((flags & (O_WRONLY|O_RDWR)) == (O_WRONLY|O_RDWR)) goto deny; // invalid open mode
if (flags & ~(flag_read | flag_write)) goto deny; // unacceptable flags
if (!dat->access(path, (flags & flag_write), dat->userdata)) goto deny;
return open(path, flags, mode);
deny:
errno = EACCES;
return -1;
}
void sandbox_cross_serve_request(int socket, bool (*access)(const char * path, bool write, void* userdata), void* userdata)
{
req_dat dat = { access, userdata };
sandbox_cross_serve_request_full(socket, req_sub, &dat);
}
#endif

View File

@ -0,0 +1,90 @@
#ifdef __linux__
#include "sandbox-internal.h"
#include <errno.h>
#include <limits.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#include <sys/prctl.h>
#include <sys/shm.h>
#include <sys/syscall.h>
#include <linux/futex.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/shm.h>
#include <sys/syscall.h>
#include <linux/futex.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/wait.h>
static bool int_in(int* list, int len, int val)
{
for (int i=0;i<len;i++)
{
if (list[i] == val) return true;
}
return false;
}
static void close_fds(int* allow_fd, int n_allow_fd)
{
//based on http://www.opensource.apple.com/source/sudo/sudo-46/src/closefrom.c, license BSD-2, but fairly rewritten
bool changed;
do {
changed = false;
DIR* dirp = opendir("/proc/self/fd/");
if (!dirp) _exit(0);
while (true)
{
dirent* dent = readdir(dirp);
if (dent == NULL) break;
if (strcmp(dent->d_name, ".") == 0) continue;
if (strcmp(dent->d_name, "..") == 0) continue;
char* endp;
long fd = strtol(dent->d_name, &endp, 10);
if (dent->d_name==endp || *endp!='\0' || fd<0 || fd>=INT_MAX)
{
_exit(0);
}
if (fd != dirfd(dirp) && !int_in(allow_fd, n_allow_fd, (int)fd))
{
close((int)fd);
changed = true;
}
}
closedir(dirp);
} while (changed);
}
void sandbox_lockdown(int* allow_fd, int n_allow_fd)
{
prctl(PR_SET_DUMPABLE, false);
prctl(PR_SET_TSC, PR_TSC_SIGSEGV);
prctl(PR_SET_PDEATHSIG, SIGKILL);
close_fds(allow_fd, n_allow_fd);
//TODO: rlimit?
prctl(PR_SET_NO_NEW_PRIVS, true);
//TODO: seccomp
//TODO: test vsyscall and vdso with seccomp enabled
}
#endif

281
arlib/sandbox/linux.cpp Normal file
View File

@ -0,0 +1,281 @@
#ifdef __linux__
#include "sandbox-internal.h"
#include "../thread/atomic.h"
#include <signal.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/wait.h>
#include <linux/futex.h>
#include<errno.h>
struct sandbox::impl {
int childpid; // pid, or 0 if this is the child
sandbox::impl* childdat;
#define CH_FREE 0
#define CH_LOCKED 1
#define CH_SLEEPER 2 // someone is sleeping on this
int channels[8]; // futex
//they could be merged to one int (16 bits), but there's no point saving 56 bytes. the shmem will be rounded to 4K anyways
//0 - parent's turn
//1 - child's turn
//also used during initialization, with same values
int sh_futex;
int sh_fd[8];
void* sh_ptr[8];
size_t sh_len[8];
int fopensock; // used only in parent
void(*run)(sandbox* box);
};
//waits while *uaddr == val
//non-private because this goes across multiple address spaces
static int futex_wait(int * uaddr, int val, const struct timespec * timeout = NULL)
{
return syscall(__NR_futex, uaddr, FUTEX_WAIT, val, timeout);
}
static int futex_wake(int * uaddr)
{
return syscall(__NR_futex, uaddr, FUTEX_WAKE, 1);
}
//static int futex_wake_all(int * uaddr)
//{
// return syscall(__NR_futex, uaddr, FUTEX_WAKE, INT_MAX);
//}
static void futex_wait_while_eq(int * uaddr, int val)
{
while (lock_read(uaddr)==val) futex_wait(uaddr, val);
}
static void futex_set_and_wake(int * uaddr, int val)
{
lock_write(uaddr, val);
futex_wake(uaddr);
}
void sandbox::enter(int argc, char** argv)
{
if (strcmp(argv[0], "[Arlib sandboxed process]")!=0) return;
sandbox::impl* box = (sandbox::impl*)mmap(NULL, sizeof(sandbox::impl), PROT_READ|PROT_WRITE, MAP_SHARED, atoi(argv[1]), 0);
box->childdat = box; // this simplifies the channel handling
futex_set_and_wake(&box->sh_futex, 1);
sandbox boxw(box);
//don't bother keeping the control data fd, the mem map remains anyways
int keep_fd[8+1+3];
memcpy(&keep_fd[0], box->sh_fd, sizeof(box->sh_fd));
keep_fd[8] = box->fopensock;
keep_fd[9] = 0;
keep_fd[10] = 1;
keep_fd[11] = 2;
sandbox_lockdown(keep_fd, 8+1+3);
int newfd=sandbox_cross_recv(box->fopensock);
printf("recv:%i\n",newfd);
char hhh[42];
if (read(newfd,hhh,41)==666) puts("shut up gcc");
hhh[41]=0;
puts(hhh);
close(newfd);
int newfd2=sandbox_cross_recv(box->fopensock);
printf("recv:%i:%i\n",newfd2,errno);
box->run(&boxw);
exit(0);
}
static int tmp_create()
{
const char * paths[] = { "/run/", "/dev/shm/", "/tmp/", "/home/", "/", NULL };
for (int i=0;paths[i];i++)
{
int fd = open(paths[i], O_TMPFILE|O_EXCL|0666);
if (fd >= 0) return fd;
}
return -1;
}
sandbox* sandbox::create(const params* param)
{
int shmfd = tmp_create();
if (ftruncate(shmfd, sizeof(sandbox::impl)) < 0)
{
close(shmfd);
return NULL;
}
sandbox::impl* par = (sandbox::impl*)malloc(sizeof(sandbox::impl));
sandbox::impl* chi = (sandbox::impl*)mmap(NULL, sizeof(sandbox::impl), PROT_READ|PROT_WRITE, MAP_SHARED, shmfd, 0);
memset(par, 0, sizeof(*par));
memset(chi, 0, sizeof(*chi));
par->childdat = chi;
chi->run = param->run;
for (int i=0;i<8;i++)
{
par->sh_fd[i] = -1;
chi->channels[i] = CH_LOCKED;
}
for (int i=0;i<param->n_shmem;i++)
{
par->sh_fd[i] = tmp_create();
chi->sh_fd[i] = par->sh_fd[i];
}
sandbox_cross_init(&par->fopensock, &chi->fopensock);
int childpid = fork();
if (childpid < 0)
{
close(shmfd);
close(par->fopensock);
close(chi->fopensock);
return NULL;
}
if (childpid == 0)
{
char shmfd_s[16];
sprintf(shmfd_s, "%i", shmfd);
const char * argv[] = { "[Arlib sandboxed process]", shmfd_s, NULL };
execv("/proc/self/exe", (char**)argv);
_exit(0);
}
if (childpid > 0)
{
close(shmfd); // apparently mappings survive even if the fd is closed
close(chi->fopensock);
//<http://man7.org/linux/man-pages/man2/open.2.html> says
// The file descriptor returned by a successful call will be the lowest-numbered file descriptor not currently open for the process.
//and 0 is fairly low, and /dev/null is readable by anything, so I'll just assume it works.
//Worst case, fds 0-2 aren't open in the child.
close(0);
open("/dev/null", O_RDONLY);
if ((param->flags & params::allow_stdout) == 0)
{
close(1);
close(2);
open("/dev/null", O_WRONLY);
open("/dev/null", O_WRONLY);
}
int test = open("a.cpp", O_RDONLY);
sandbox_cross_send(par->fopensock, test, 0);
close(test);
sandbox_cross_send(par->fopensock, -1, 42);
futex_wait_while_eq(&chi->sh_futex, 0);
lock_write(&chi->sh_futex, 0);
par->childpid = childpid;
return new sandbox(par);
}
return NULL; // unreachable, but gcc doesn't know this
}
void sandbox::wait(int chan)
{
if (lock_cmpxchg(&m->childdat->channels[chan], CH_FREE, CH_LOCKED) == CH_FREE) return;
while (true)
{
//already did a barrier above, don't need another one
int prev = lock_xchg_loose(&m->childdat->channels[chan], CH_SLEEPER);
if (prev == CH_FREE) return;
futex_wait(&m->childdat->channels[chan], CH_SLEEPER);
}
}
bool sandbox::try_wait(int chan)
{
int old = lock_cmpxchg(&m->childdat->channels[chan], 0, 1);
return (old == 0);
}
void sandbox::release(int chan)
{
int old = lock_xchg(&m->childdat->channels[chan], 0);
if (LIKELY(old != CH_SLEEPER)) return;
futex_wake(&m->childdat->channels[chan]);
}
void* sandbox::shalloc(int index, size_t bytes)
{
if (m->sh_ptr[index]) munmap(m->sh_ptr[index], m->sh_len[index]);
m->sh_ptr[index] = NULL;
void* ret = NULL;
if (!m->childpid) // child, tell parent we're unmapped (if shrinking, we risk SIGBUS in child if we don't wait)
{
futex_set_and_wake(&m->sh_futex, 1);
}
if (m->childpid) // parent, resize and map
{
futex_wait_while_eq(&m->childdat->sh_futex, 0);
if (ftruncate(m->sh_fd[index], bytes) < 0) goto fail;
ret = mmap(NULL, bytes, PROT_READ|PROT_WRITE, MAP_SHARED, m->sh_fd[index], 0);
if (ret == MAP_FAILED) goto fail;
futex_set_and_wake(&m->childdat->sh_futex, 2);
}
if (!m->childpid) // child, map
{
futex_wait_while_eq(&m->sh_futex, 1);
if (m->sh_futex == 0) goto fail;
ret = mmap(NULL, bytes, PROT_READ|PROT_WRITE, MAP_SHARED, m->sh_fd[index], 0);
if (ret == MAP_FAILED) goto fail;
futex_set_and_wake(&m->sh_futex, 3);
}
if (m->childpid) // parent, check that child succeeded
{
futex_wait_while_eq(&m->childdat->sh_futex, 2);
if (m->childdat->sh_futex == 0) goto fail;
futex_set_and_wake(&m->childdat->sh_futex, 0);
}
m->sh_ptr[index] = ret;
m->sh_len[index] = bytes;
return ret;
fail:
futex_set_and_wake(&m->sh_futex, 0);
return NULL;
}
sandbox::~sandbox()
{
//this can blow up if our caller has a SIGCHLD handler that discards everything, and the PID was reused
//even if the child remains alive here, we could end up waiting for something unrelated created between kill/waitpid
//but the risk of that is very low, so I'll just not care.
//(windows process handles make more sense)
kill(m->childpid, SIGKILL);
waitpid(m->childpid, NULL, WNOHANG);
for (int i=0;i<8;i++)
{
if (m->sh_ptr[i]) munmap(m->sh_ptr[i], m->sh_len[i]);
if (m->sh_fd[i] >= 0) close(m->sh_fd[i]);
}
munmap(m->childdat, sizeof(sandbox::impl));
free(m);
}
#endif

View File

@ -0,0 +1,10 @@
#ifdef __linux__
#include "sandbox.h"
void sandbox_lockdown(int* allow_fd, int n_allow_fd);
void sandbox_cross_init(int* pfd, int* cfd);
void sandbox_cross_send(int socket, int fd_to_send, int errno);
int sandbox_cross_recv(int socket);
#endif

145
arlib/sandbox/sandbox.h Normal file
View File

@ -0,0 +1,145 @@
#pragma once
#include "../global.h"
#ifdef ARLIB_SANDBOX
//Allows safely executing untrusted code.
//
//Exact rules:
// If the child process is running hostile code, the parent may deadlock or _exit(), but may not crash or use an invalid handle.
// Failing an access check in the child yields undefined behavior (subject to the above, of course).
// It can return EACCES, kill the process, or whatever.
//
//WARNING: There is NO SECURITY WHATSOEVER on Windows. A "sandboxed" process can do anything the parent can.
//Windows provides plenty of ways to restrict a process, but
//- Most of them are blacklists, disabling a particular privilege; I want whitelists
//- There are so many resource kinds I can't keep track how to restrict everything, or even list
// them; it will also fail open if a new resource kind is introduced
//- Many lockdown functions temporarily disable privileges, rather than completely delete them
//- There's little or no documentation on which privileges are required for the operations I need
//- The lockdown functions are often annoying to call, involving variable-width arrays in
// structures, and LCIDs that likely vary between reboots
//And I cannot trust such a system. I only trust whitelists, like Linux seccomp.
//Even Chrome couldn't find anything comprehensive; they use everything they can find (restricted token, job object, desktop,
// SetProcessMitigationPolicy, firewall, etc), but some operations, such as accessing FAT32 volumes, still pass through.
//It feels like the Windows sandboxing functions are designed for trusted code operating on untrusted data, rather than untrusted code.
//Since I can't create satisfactory results in such an environment, I won't even try.
//
//The Linux implementation is, of course, secure. It has a solid whitelist mechanism (seccomp),
// denying access to creating any and all unauthorized resources - and even using authorized
// resources in an unauthorized way. I don't even need to drop privileges, it won't get a chance to
// use them.
//Documentation is also excellent; source code is available everywhere, all syscalls (both Linux-only and Unix-global)
// are well documented, and if I miss something, strace quickly tells me what.
//
//On Linux, this requires exclusive control over SIGSYS in the child process.
//<http://lxr.free-electrons.com/ident?i=SIGSYS> (as of kernel version 4.6) shows me about fifty references to SIGSYS,
// but they're all seccomp-related (mostly in seccomp tests), #define SIGSYS 123, or otherwise uninteresting to handle,
// so it's safe to claim this signal for myself.
//
//Chrome sandbox entry points: http://stackoverflow.com/questions/1590337/using-the-google-chrome-sandbox
class sandbox : nocopy {
public:
//Must be the first thing in main(), before window_init() and similar.
//If the process is created via sandbox::create, this doesn't return. Otherwise, it does nothing.
static void enter(int argc, char** argv);
//TODO: half of those aren't used
struct params {
enum flags_t {
//Always allowed: Allocate memory, synchronization operations, terminate self.
//If true, stdout and stderr go to the same places as in the parent. If false, /dev/null.
allow_stdout = 1,
//Creates a sandbox facade; it acts like a normal sandbox, but the child process isn't
// restricted. It could even be a thread in the same process.
no_security = 2,
};
unsigned int flags;
//The operating system decides how memory is counted and how to round this.
//0 means unlimited; anything else is in bytes.
size_t max_mem;
//Tells how many synchronization channels and shared memory regions are available in this sandbox.
//Both must be 8 or less.
int n_channel;
int n_shmem;
//TODO: replace with some kind of stringlist thing
//If the allow_file flag is not set, this is called when the child tries to open a file.
//If it returns true, the request is granted. If false, or if the function is NULL, the child's attempt fails with EACCES.
//Can be called at any time when a function is executed on this sandbox object. The sandbox
// manager creates a thread to handle such requests.
//function<bool(const char *, bool write)> file_access;
//Since this goes cross-process, passing a normal userdata won't work. Instead, it's provided by the sandbox object, via shalloc.
void(*run)(sandbox* box);
};
//The params object is used only during this call. You can free it afterwards.
static sandbox* create(const params* param);
//Used to synchronize the two processes. 'chan' can be any number 0 through 7. Negative channels may be used internally.
//While this may look like a mutex, it's also usable as event; release and wait can be in different processes.
//Each channel may only be used by one thread on each side.
//The channels start in the locked state.
void wait(int chan);
bool try_wait(int chan);
void release(int chan);
//Allows synchronized(box->channel(1)) {}.
struct channel_t
{
sandbox* parent; int id;
void lock() { parent->wait(id); }
bool try_lock() { return parent->try_wait(id); }
void unlock() { parent->release(id); }
};
channel_t channel(int id) { return (channel_t){this, id}; }
//Allocates memory shared between the two processes. At least 8 memory areas are supported. It's
// up to the user how to use them; a recommendation is to put a fixed-size control data block in
// area 0, and put variable-size stuff in other areas.
//Rules (optional if you make reasonable assumptions):
// Both processes must call this; don't share pointers directly, they may vary between the processes.
// The function is synchronous; neither process will return until the other has entered it. If one
// fails, the other does too. For this reason, the two processes must perform the same sequence
// of calls; they must also agree on the sizes.
// You can resize a memory area by calling this function again with the same index. However,
// unlike realloc, the new contents are implementation defined. Whether it fails or succeeds, the
// old shared area is deleted.
// The implementation must ensure the parent does not crash even if the child's internal
// structures are corrupt, including but not limited to size mismatch.
// This function is not thread safe.
// It is implementation defined which processes are charged for these bytes. It could be parent,
// child, a little of each, both, or something weirder.
void* shalloc(int index, size_t bytes);
//Convenience function, just calls the above.
template<typename T> T* shalloc(int index, size_t count=1) { return (T*)this->shalloc(index, sizeof(T)*count); }
//This will be called if the child process attempts to open a file, but the attempt is rejected by policy.
//If the return value from this callback is not equal to -1, that will be returned as a file handle.
//It is safe to call accept_fd() from this function.
//On Windows, the intptr_t is a casted HANDLE. On Linux, int.
void set_fopen_fallback(function<intptr_t(const char * path, bool write)> callback); // Child only.
//Clones a file handle into the child. The handle remains open in the parent. The child may get another ID.
//Like shalloc(), neither process returns until the other enters.
void give_fd(intptr_t fd); // Parent only.
intptr_t accept_fd(); // Child only.
//This forcibly terminates the child process. If called from the child, undefined behaviour; instead, call exit() or return from run().
//Also unmaps all shared memory.
~sandbox();
private:
struct impl;
impl * m;
private:
sandbox(){}
sandbox(impl* m) : m(m) {}
};
#endif

196
arlib/sandbox/win32.cpp Normal file
View File

@ -0,0 +1,196 @@
#ifdef _WIN32
#include "sandbox.h"
#include <windows.h>
#include <stdlib.h>
struct sandbox::impl {
//handle type annotations per https://msdn.microsoft.com/en-us/library/windows/desktop/ms724515%28v=vs.85%29.aspx
//child process control block is in shared memory, so parent can update it
//parent control block is unshared (child isn't trusted, and handle values vary between processes)
HANDLE child_handle; // process
sandbox::impl* child_struct;
void* shalloc_ptr[8];
HANDLE shalloc_handle; // file mapping
HANDLE shalloc_wake_parent; // event
HANDLE shalloc_wake_child; // event
HANDLE channel_handle[8]; // event
void(*run)(sandbox* box);
//if a sandbox is ever added, hijack ntdll!NtOpenFile and ntdll!NtCreateFile and send requests to a thread on the parent
//use GetFinalPathNameByHandle if ObjectAttributes->RootDirectory is non-NULL
//there are \??\ prefixes sometimes; according to <https://msdn.microsoft.com/en-us/library/windows/hardware/ff565384%28v=vs.85%29.aspx>,
// it means "this is an absolute file path", somewhat like \\?\; I think \\?\ is only used in userspace
};
#define DupRaw(process, in, out) DuplicateHandle(GetCurrentProcess(), in, process, &out, 0, FALSE, DUPLICATE_SAME_ACCESS)
#define Dup(impl, name) DupRaw(impl->child_handle, impl->name, impl->child_struct->name)
static HANDLE shmem_par = NULL;
void sandbox::enter(int argc, char** argv)
{
if (!shmem_par) return;
sandbox::impl* box = (sandbox::impl*)MapViewOfFile(shmem_par, FILE_MAP_WRITE, 0,0, sizeof(sandbox::impl));
sandbox boxw(box);
//TODO: lockdown
box->run(&boxw);
exit(0);
}
static WCHAR* selfpathw()
{
static WCHAR* ret = NULL;
if (ret) return ret;
DWORD len = MAX_PATH;
again:
WCHAR* ptr = malloc(sizeof(WCHAR)*len);
DWORD gmfnret = GetModuleFileNameW(NULL, ptr, len);
if (!gmfnret || gmfnret==len)
{
free(ptr);
len*=2;
goto again;
}
//ensure thread safety
WCHAR* prevret = (WCHAR*)InterlockedCompareExchangePointer((void**)&ret, ptr, NULL);
if (prevret == NULL)
{
return ptr;
}
else
{
free(ptr);
return prevret;
}
}
sandbox* sandbox::create(const params* param)
{
STARTUPINFOW sti;
memset(&sti, 0, sizeof(sti));
sti.cb=sizeof(sti);
PROCESS_INFORMATION pi;
CreateProcessW(selfpathw(), NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &sti, &pi);
HANDLE shmem = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0,sizeof(sandbox::impl), NULL);
HANDLE shmem_tar;
DupRaw(pi.hProcess, shmem, shmem_tar);
SIZE_T ignore;
WriteProcessMemory(pi.hProcess, &shmem_par, &shmem_tar, sizeof(HANDLE), &ignore);
sandbox::impl* par = new sandbox::impl;
memset(par, 0, sizeof(*par));
sandbox::impl* chi = (sandbox::impl*)MapViewOfFile(shmem, FILE_MAP_WRITE, 0,0, sizeof(sandbox::impl));
//<https://msdn.microsoft.com/en-us/library/windows/desktop/aa366537%28v=vs.85%29.aspx> says
// The initial contents of the pages in a file mapping object backed by the operating system paging file are 0 (zero).
//so no need to clear chi
CloseHandle(shmem);
par->child_handle = pi.hProcess;
par->child_struct = chi;
chi->run = param->run;
for (int i=0;i<param->n_channel;i++)
{
par->channel_handle[i] = CreateEvent(NULL, FALSE, FALSE, NULL);
Dup(par, channel_handle[i]);
}
par->shalloc_wake_parent = CreateEvent(NULL, FALSE, FALSE, NULL);
Dup(par, shalloc_wake_parent);
par->shalloc_wake_child = CreateEvent(NULL, FALSE, FALSE, NULL);
Dup(par, shalloc_wake_child);
ResumeThread(pi.hThread);
CloseHandle(pi.hThread);
return new sandbox(par);
}
void sandbox::wait(int chan)
{
WaitForSingleObject(m->channel_handle[chan], INFINITE);
}
bool sandbox::try_wait(int chan)
{
return WaitForSingleObject(m->channel_handle[chan], 0)==WAIT_OBJECT_0;
}
void sandbox::release(int chan)
{
SetEvent(m->channel_handle[chan]);
}
void* sandbox::shalloc(int index, size_t bytes)
{
if (m->shalloc_ptr[index]) UnmapViewOfFile(m->shalloc_ptr[index]);
void* ret;
if (m->child_handle) // parent
{
m->shalloc_handle = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0,bytes, NULL);
ret = MapViewOfFile(m->shalloc_handle, FILE_MAP_WRITE, 0,0, bytes);
Dup(m, shalloc_handle);
//<https://msdn.microsoft.com/en-us/library/windows/desktop/aa366882%28v=vs.85%29.aspx> says
// Although an application may close the file handle used to create a file mapping object,
// the system holds the corresponding file open until the last view of the file is unmapped.
// Files for which the last view has not yet been unmapped are held open with no sharing restrictions.
//making use of that will simplify stuff
CloseHandle(m->shalloc_handle);
SetEvent(m->shalloc_wake_child);
WaitForSingleObject(m->shalloc_wake_parent, INFINITE);
if (!m->child_struct->shalloc_handle)
{
UnmapViewOfFile(ret);
ret=NULL;
}
}
else // child
{
WaitForSingleObject(m->shalloc_wake_child, INFINITE);
ret = MapViewOfFile(m->shalloc_handle, FILE_MAP_WRITE, 0,0, bytes);
CloseHandle(m->shalloc_handle);
if (!ret) m->shalloc_handle = NULL;
SetEvent(m->shalloc_wake_parent);
}
m->shalloc_ptr[index] = ret;
return ret;
}
sandbox::~sandbox()
{
TerminateProcess(m->child_handle, 0);
CloseHandle(m->child_handle);
for (int i=0;i<8;i++)
{
if (m->channel_handle[i]) CloseHandle(m->channel_handle[i]);
}
for (int i=0;i<8;i++)
{
if (m->shalloc_ptr[i]) UnmapViewOfFile(m->shalloc_ptr[i]);
}
UnmapViewOfFile(m->child_struct);
free(m);
}
#endif

1
arlib/socket.h Normal file
View File

@ -0,0 +1 @@
#include "socket/socket.h"

34375
arlib/socket/libtomcrypt.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,64 @@
#ifdef ARLIB_TEST_SERVER
//Shitty Server: a buggy echo server
//after the first 32 bytes, it drops your connection on the floor, without FIN or anything
//probably somewhat useful to test resilience against network failure
//it would be more useful to make it ignore the pings too, but I can't do that without fiddling with the firewall, and I'd rather not
//linux and root only because TCP_REPAIR requires that
//http://oroboro.com/dealing-with-network-port-abuse-in-sockets-in-c
//if you need to test a windows program against dropped sockets, run this on another machine, possibly a virtual machine
//most of the code stolen from http://www.thegeekstuff.com/2011/12/c-socket-programming/ because I'm lazy
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <time.h>
#include <netinet/tcp.h>
int main()
{
int listenfd = 0, connfd = 0;
struct sockaddr_in serv_addr;
char sendBuff[1025];
time_t ticks;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
memset(&serv_addr, '0', sizeof(serv_addr));
memset(sendBuff, '0', sizeof(sendBuff));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(168);
bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
perror("bind");
listen(listenfd, 10);
perror("listen");
while(1)
{
connfd = accept(listenfd, (struct sockaddr*)NULL, NULL);
perror("accept");
memset(sendBuff, 0, 32);
read(connfd, sendBuff, 32);
write(connfd, sendBuff, 32);
sleep(1); // otherwise the ACK gives a RST
int yes = 1;
setsockopt(connfd, SOL_TCP, TCP_REPAIR, &yes, sizeof(yes));
perror("TCP_REPAIR");
close(connfd);
}
}
#endif

View File

@ -0,0 +1,342 @@
#include "socket.h"
#ifdef ARLIB_SSL_OPENSSL
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/x509v3.h>
static SSL_CTX * ctx;
static void initialize()
{
static bool initialized = false;
if (initialized) return;
initialized = true;
//SSL_load_error_strings(); // TODO
SSL_library_init();
ctx = SSL_CTX_new(SSLv23_client_method());
SSL_CTX_set_default_verify_paths(ctx);
SSL_CTX_set_cipher_list(ctx, "HIGH:!DSS:!aNULL@STRENGTH");
}
static bool validate_hostname(const char *hostname, const X509 *server_cert);
class socketssl_impl : public socketssl {
public:
socket* sock;
SSL* ssl;
//bool nonblock;
static socketssl_impl* create(socket* parent, const char * domain, bool permissive)
{
if (!parent) return NULL;
socketssl_impl* ret = new socketssl_impl();
ret->sock = parent;
ret->fd = parent->get_fd();
ret->ssl = SSL_new(ctx);
//ret->nonblock = false;
SSL_set_fd(ret->ssl, ret->fd);
//TODO: set fd to nonblock
if (!permissive)
{
SSL_set_verify(ret->ssl, SSL_VERIFY_PEER, NULL);
}
//plausible cert failure cases: unrooted (including self-signed), expired, wrong domain
//permissive should allow the former two, but still block the third
#if OPENSSL_VERSION_NUMBER >= 0x10100000 // >= 1.1.0
#error test, especially set0 vs set1
SSL_set1_host(ssl, "example.com");
#endif
#if OPENSSL_VERSION_NUMBER >= 0x10002000 && OPENSSL_VERSION_NUMBER < 0x10100000 // >= 1.0.2, < 1.1.0
#error test, especially [gs]et0 vs [gs]et1
X509_VERIFY_PARAM* param = SSL_get0_param(ssl);
//optional?
//X509_VERIFY_PARAM_set_hostflags(param, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
X509_VERIFY_PARAM_set1_host(param, "example.com", 0);
#endif
bool ok = (SSL_connect(ret->ssl)==1);
#if OPENSSL_VERSION_NUMBER < 0x10002000 // < 1.0.2
if (ok && !validate_hostname(domain, SSL_get_peer_certificate(ret->ssl)))
{
ok=false;
}
#endif
if (!ok)
{
delete ret;
return 0;
}
return ret;
}
/*private*/ int fixret(int ret)
{
if (ret > 0) return ret;
int sslerror = SSL_get_error(ssl, ret);
if (sslerror==SSL_ERROR_WANT_READ || sslerror==SSL_ERROR_WANT_WRITE) return 0;
//printf("ERR=%i\n",sslerror);
//ERR_print_errors();
return e_ssl_failure;
}
//only supports nonblocking
int recv(uint8_t* data, unsigned int len, bool block = false)
{
return fixret(SSL_read(ssl, data, len));
}
int sendp(const uint8_t* data, unsigned int len, bool block = true)
{
return fixret(SSL_write(ssl, data, len));
}
~socketssl_impl()
{
SSL_shutdown(ssl);
SSL_free(ssl);
delete sock;
}
};
socketssl* socketssl::create(socket* parent, const char * domain, bool permissive)
{
initialize();
if (!ctx) return NULL;
return socketssl_impl::create(parent, domain, permissive);
}
#if OPENSSL_VERSION_NUMBER < 0x10002000
//from TLSe https://github.com/eduardsui/tlse/blob/90bdc5d/tlse.c#L2519
#define bad_certificate -1
static int tls_certificate_valid_subject_name(const unsigned char *cert_subject, const char *subject) {
// no subjects ...
if (((!cert_subject) || (!cert_subject[0])) && ((!subject) || (!subject[0])))
return 0;
if ((!subject) || (!subject[0]))
return bad_certificate;
if ((!cert_subject) || (!cert_subject[0]))
return bad_certificate;
// exact match
if (!strcmp((const char *)cert_subject, subject))
return 0;
const char *wildcard = strchr((const char *)cert_subject, '*');
if (wildcard) {
// 6.4.3 (1) The client SHOULD NOT attempt to match a presented identifier in
// which the wildcard character comprises a label other than the left-most label
if (!wildcard[1]) {
// subject is [*]
// or
// subject is [something*] .. invalid
return bad_certificate;
}
wildcard++;
const char *match = strstr(subject, wildcard);
if ((!match) && (wildcard[0] == '.')) {
// check *.domain.com agains domain.com
wildcard++;
if (!strcasecmp(subject, wildcard))
return 0;
}
if (match) {
unsigned long offset = (unsigned long)match - (unsigned long)subject;
if (offset) {
// check for foo.*.domain.com against *.domain.com (invalid)
if (memchr(subject, '.', offset))
return bad_certificate;
}
// check if exact match
if (!strcasecmp(match, wildcard))
return 0;
}
}
return bad_certificate;
}
//copypasted from https://wiki.openssl.org/index.php/Hostname_validation
//and modified a bit (for example to add a missing cast)
/*
Copyright (C) 2012, iSEC Partners.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
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 OR COPYRIGHT HOLDERS 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.
*/
/*
* Helper functions to perform basic hostname validation using OpenSSL.
*
* Please read "everything-you-wanted-to-know-about-openssl.pdf" before
* attempting to use this code. This whitepaper describes how the code works,
* how it should be used, and what its limitations are.
*
* Author: Alban Diquet
* License: See LICENSE
*
*/
// Get rid of OSX 10.7 and greater deprecation warnings.
#if defined(__APPLE__) && defined(__clang__)
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#endif
//#include <openssl/ssl.h>
//#include "openssl_hostname_validation.h"
//#include "hostcheck.h"
#define HOSTNAME_MAX_SIZE 255
enum HostnameValidationResult { Error, MalformedCertificate, NoSANPresent, MatchFound, MatchNotFound };
/**
* Tries to find a match for hostname in the certificate's Common Name field.
*
* Returns MatchFound if a match was found.
* Returns MatchNotFound if no matches were found.
* Returns MalformedCertificate if the Common Name had a NUL character embedded in it.
* Returns Error if the Common Name could not be extracted.
*/
static HostnameValidationResult matches_common_name(const char *hostname, const X509 *server_cert) {
int common_name_loc = -1;
X509_NAME_ENTRY *common_name_entry = NULL;
ASN1_STRING *common_name_asn1 = NULL;
char *common_name_str = NULL;
// Find the position of the CN field in the Subject field of the certificate
common_name_loc = X509_NAME_get_index_by_NID(X509_get_subject_name((X509 *) server_cert), NID_commonName, -1);
if (common_name_loc < 0) {
return Error;
}
// Extract the CN field
common_name_entry = X509_NAME_get_entry(X509_get_subject_name((X509 *) server_cert), common_name_loc);
if (common_name_entry == NULL) {
return Error;
}
// Convert the CN field to a C string
common_name_asn1 = X509_NAME_ENTRY_get_data(common_name_entry);
if (common_name_asn1 == NULL) {
return Error;
}
common_name_str = (char *) ASN1_STRING_data(common_name_asn1);
// Make sure there isn't an embedded NUL character in the CN
if ((size_t)ASN1_STRING_length(common_name_asn1) != strlen(common_name_str)) {
return MalformedCertificate;
}
// Compare expected hostname with the CN
if (tls_certificate_valid_subject_name((uint8_t*)common_name_str, hostname)==0) {
return MatchFound;
}
else {
return MatchNotFound;
}
}
/**
* Tries to find a match for hostname in the certificate's Subject Alternative Name extension.
*
* Returns MatchFound if a match was found.
* Returns MatchNotFound if no matches were found.
* Returns MalformedCertificate if any of the hostnames had a NUL character embedded in it.
* Returns NoSANPresent if the SAN extension was not present in the certificate.
*/
static HostnameValidationResult matches_subject_alternative_name(const char *hostname, const X509 *server_cert) {
HostnameValidationResult result = MatchNotFound;
int i;
int san_names_nb = -1;
STACK_OF(GENERAL_NAME) *san_names = NULL;
// Try to extract the names within the SAN extension from the certificate
san_names = (STACK_OF(GENERAL_NAME)*)X509_get_ext_d2i((X509 *) server_cert, NID_subject_alt_name, NULL, NULL);
if (san_names == NULL) {
return NoSANPresent;
}
san_names_nb = sk_GENERAL_NAME_num(san_names);
// Check each name within the extension
for (i=0; i<san_names_nb; i++) {
const GENERAL_NAME *current_name = sk_GENERAL_NAME_value(san_names, i);
if (current_name->type == GEN_DNS) {
// Current name is a DNS name, let's check it
char *dns_name = (char *) ASN1_STRING_data(current_name->d.dNSName);
// Make sure there isn't an embedded NUL character in the DNS name
if ((size_t)ASN1_STRING_length(current_name->d.dNSName) != strlen(dns_name)) {
result = MalformedCertificate;
break;
}
else { // Compare expected hostname with the DNS name
if (tls_certificate_valid_subject_name((uint8_t*)dns_name, hostname)==0) {
result = MatchFound;
break;
}
}
}
}
sk_GENERAL_NAME_pop_free(san_names, GENERAL_NAME_free);
return result;
}
/**
* Validates the server's identity by looking for the expected hostname in the
* server's certificate. As described in RFC 6125, it first tries to find a match
* in the Subject Alternative Name extension. If the extension is not present in
* the certificate, it checks the Common Name instead.
*
* Returns MatchFound if a match was found.
* Returns MatchNotFound if no matches were found.
* Returns MalformedCertificate if any of the hostnames had a NUL character embedded in it.
* Returns Error if there was an error.
*/
static bool validate_hostname(const char *hostname, const X509 *server_cert) {
HostnameValidationResult result;
if((hostname == NULL) || (server_cert == NULL))
return false;
// First try the Subject Alternative Names extension
result = matches_subject_alternative_name(hostname, server_cert);
if (result == NoSANPresent) {
// Extension was not found: try the Common Name
result = matches_common_name(hostname, server_cert);
}
return (result==MatchFound);
}
#endif
#endif

View File

@ -0,0 +1,339 @@
#include "socket.h"
//based on http://wayback.archive.org/web/20100528130307/http://www.coastrd.com/c-schannel-smtp
//but heavily rewritten for stability and compactness
#ifdef ARLIB_SSL_SCHANNEL
#ifndef _WIN32
#error SChannel only exists on Windows
#endif
#define SECURITY_WIN32
#undef bind
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <winsock.h>
#include <wincrypt.h>
#include <wintrust.h>
#include <schannel.h>
#include <security.h>
#include <sspi.h>
namespace {
static SecurityFunctionTable* SSPI;
static CredHandle cred;
#define SSPIFlags \
(ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONFIDENTIALITY | \
ISC_RET_EXTENDED_ERROR | ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM)
//my mingw headers are outdated
#ifndef SCH_USE_STRONG_CRYPTO
#define SCH_USE_STRONG_CRYPTO 0x00400000
#endif
#ifndef SP_PROT_TLS1_2_CLIENT
#define SP_PROT_TLS1_2_CLIENT 0x00000800
#endif
#ifndef SEC_Entry
#define SEC_Entry WINAPI
#endif
static void initialize()
{
if (SSPI) return;
//linking a DLL is easy, but when there's only one exported function, spending the extra effort is worth it
HMODULE secur32 = LoadLibraryA("secur32.dll");
typedef PSecurityFunctionTableA SEC_Entry (*InitSecurityInterfaceA_t)(void);
InitSecurityInterfaceA_t InitSecurityInterfaceA = (InitSecurityInterfaceA_t)GetProcAddress(secur32, SECURITY_ENTRYPOINT_ANSIA);
SSPI = InitSecurityInterfaceA();
SCHANNEL_CRED SchannelCred = {};
SchannelCred.dwVersion = SCHANNEL_CRED_VERSION;
SchannelCred.dwFlags = SCH_CRED_NO_DEFAULT_CREDS | SCH_USE_STRONG_CRYPTO;
// fun fact: IE11 doesn't use SCH_USE_STRONG_CRYPTO. I guess it favors accepting outdated servers over rejecting evil ones.
SchannelCred.grbitEnabledProtocols = SP_PROT_TLS1_2_CLIENT; // Microsoft recommends setting this to zero, but that makes it use TLS 1.0, which sucks.
//howsmyssl expects session ticket support for the Good rating, but that's only supported on windows 8, according to
// https://connect.microsoft.com/IE/feedback/details/997136/internet-explorer-11-on-windows-7-does-not-support-tls-session-tickets
//and I can't find which flag enables that, anyways
SSPI->AcquireCredentialsHandleA(NULL, (char*)UNISP_NAME_A, SECPKG_CRED_OUTBOUND,
NULL, &SchannelCred, NULL, NULL, &cred, NULL);
}
class socketssl_impl : public socketssl {
public:
socket* sock;
CtxtHandle ssl;
SecPkgContext_StreamSizes bufsizes;
BYTE* recv_buf;
size_t recv_buf_len;
BYTE* ret_buf;
size_t ret_buf_len;
bool in_handshake;
void fetch(bool block)
{
int bytes = sock->recv(recv_buf+recv_buf_len, 1024, block);
if (bytes < 0)
{
delete sock;
sock = NULL;
}
if (bytes > 0)
{
recv_buf_len += bytes;
if (recv_buf_len > 1024)
{
recv_buf = realloc(recv_buf, recv_buf_len + 1024);
}
}
}
void fetch() { fetch(true); }
void fetchnb() { fetch(false); }
void ret_realloc(int bytes)
{
if (bytes > 0)
{
ret_buf_len += bytes;
if (ret_buf_len > 1024)
{
ret_buf = realloc(ret_buf, ret_buf_len + 1024);
}
}
}
BYTE* tmpptr()
{
return recv_buf + recv_buf_len;
}
void error()
{
SSPI->DeleteSecurityContext(&ssl);
delete sock;
sock = NULL;
}
void handshake()
{
if (!in_handshake) return;
SecBuffer InBuffers[2] = { { recv_buf_len, SECBUFFER_TOKEN, recv_buf }, { 0, SECBUFFER_EMPTY, NULL } };
SecBufferDesc InBufferDesc = { SECBUFFER_VERSION, 2, InBuffers };
SecBuffer OutBuffer = { 0, SECBUFFER_TOKEN, NULL };
SecBufferDesc OutBufferDesc = { SECBUFFER_VERSION, 1, &OutBuffer };
DWORD ignore;
SECURITY_STATUS scRet;
scRet = SSPI->InitializeSecurityContextA(&cred, &ssl, NULL, SSPIFlags, 0, SECURITY_NATIVE_DREP,
&InBufferDesc, 0, NULL, &OutBufferDesc, &ignore, NULL);
// according to the original program, extended errors are success
// but they also hit the error handler below, so I guess it just sends an error to the server?
// either way, ignore
if (scRet == SEC_E_OK || scRet == SEC_I_CONTINUE_NEEDED)
{
if (OutBuffer.cbBuffer != 0 && OutBuffer.pvBuffer != NULL)
{
if (sock->send((BYTE*)OutBuffer.pvBuffer, OutBuffer.cbBuffer) < 0)
{
SSPI->FreeContextBuffer(OutBuffer.pvBuffer);
error();
return;
}
SSPI->FreeContextBuffer(OutBuffer.pvBuffer);
}
}
if (scRet == SEC_E_INCOMPLETE_MESSAGE) return;
if (scRet == SEC_E_OK)
{
in_handshake = false;
}
if (FAILED(scRet))
{
error();
return;
}
// SEC_I_INCOMPLETE_CREDENTIALS is possible and means server requested client authentication
// we don't support that, just ignore it
if (InBuffers[1].BufferType == SECBUFFER_EXTRA)
{
memmove(recv_buf, recv_buf + (recv_buf_len - InBuffers[1].cbBuffer), InBuffers[1].cbBuffer);
recv_buf_len = InBuffers[1].cbBuffer;
}
else recv_buf_len = 0;
}
bool handshake_first(const char * domain)
{
SecBuffer OutBuffer = { 0, SECBUFFER_TOKEN, NULL };
SecBufferDesc OutBufferDesc = { SECBUFFER_VERSION, 1, &OutBuffer };
DWORD ignore;
if (SSPI->InitializeSecurityContextA(&cred, NULL, (char*)domain, SSPIFlags, 0, SECURITY_NATIVE_DREP,
NULL, 0, &ssl, &OutBufferDesc, &ignore, NULL)
!= SEC_I_CONTINUE_NEEDED)
{
return false;
}
if (OutBuffer.cbBuffer != 0)
{
if (sock->send((BYTE*)OutBuffer.pvBuffer, OutBuffer.cbBuffer) < 0)
{
SSPI->FreeContextBuffer(OutBuffer.pvBuffer);
error();
return false;
}
SSPI->FreeContextBuffer(OutBuffer.pvBuffer); // Free output buffer.
}
in_handshake = true;
while (in_handshake) { fetch(); handshake(); }
return true;
}
bool init(socket* parent, const char * domain, bool permissive)
{
if (!parent) return false;
sock = parent;
fd = parent->get_fd();
recv_buf = malloc(2048);
recv_buf_len = 0;
ret_buf = malloc(2048);
ret_buf_len = 0;
if (!handshake_first(domain)) return false;
SSPI->QueryContextAttributes(&ssl, SECPKG_ATTR_STREAM_SIZES, &bufsizes);
return (sock);
}
void process()
{
handshake();
bool again = true;
while (again)
{
again = false;
SecBuffer Buffers[4] = {
{ recv_buf_len, SECBUFFER_DATA, recv_buf },
{ 0, SECBUFFER_EMPTY, NULL },
{ 0, SECBUFFER_EMPTY, NULL },
{ 0, SECBUFFER_EMPTY, NULL },
};
SecBufferDesc Message = { SECBUFFER_VERSION, 4, Buffers };
SECURITY_STATUS scRet = SSPI->DecryptMessage(&ssl, &Message, 0, NULL);
if (scRet == SEC_E_INCOMPLETE_MESSAGE) return;
else if (scRet == SEC_I_RENEGOTIATE)
{
in_handshake = true;
}
else if (scRet != SEC_E_OK)
{
error();
return;
}
recv_buf_len = 0;
// Locate data and (optional) extra buffers.
for (int i=0;i<4;i++)
{
if (Buffers[i].BufferType == SECBUFFER_DATA)
{
memcpy(ret_buf+ret_buf_len, Buffers[i].pvBuffer, Buffers[i].cbBuffer);
ret_realloc(Buffers[i].cbBuffer);
again = true;
}
if (Buffers[i].BufferType == SECBUFFER_EXTRA)
{
memmove(recv_buf, Buffers[i].pvBuffer, Buffers[i].cbBuffer);
recv_buf_len = Buffers[i].cbBuffer;
}
}
}
}
int recv(uint8_t* data, unsigned int len, bool block = false)
{
if (!sock) return -1;
fetch(block);
process();
if (!ret_buf_len) return 0;
unsigned ulen = len;
int ret = (ulen < ret_buf_len ? ulen : ret_buf_len);
memcpy(data, ret_buf, ret);
memmove(ret_buf, ret_buf+ret, ret_buf_len-ret);
ret_buf_len -= ret;
return ret;
}
int sendp(const uint8_t* data, unsigned int len, bool block = true)
{
if (!sock) return -1;
fetchnb();
process();
BYTE* sendbuf = tmpptr(); // let's reuse this
unsigned int maxmsglen = 0x1000 - bufsizes.cbHeader - bufsizes.cbTrailer;
if (len > maxmsglen) len = maxmsglen;
memcpy(sendbuf+bufsizes.cbHeader, data, len);
SecBuffer Buffers[4] = {
{ bufsizes.cbHeader, SECBUFFER_STREAM_HEADER, sendbuf },
{ len, SECBUFFER_DATA, sendbuf+bufsizes.cbHeader },
{ bufsizes.cbTrailer, SECBUFFER_STREAM_TRAILER, sendbuf+bufsizes.cbHeader+len },
{ 0, SECBUFFER_EMPTY, NULL },
};
SecBufferDesc Message = { SECBUFFER_VERSION, 4, Buffers };
if (FAILED(SSPI->EncryptMessage(&ssl, 0, &Message, 0))) { error(); return -1; }
if (sock->send(sendbuf, Buffers[0].cbBuffer + Buffers[1].cbBuffer + Buffers[2].cbBuffer) < 0) error();
return len;
}
~socketssl_impl()
{
error();
free(recv_buf);
free(ret_buf);
}
};
}
socketssl* socketssl::create(socket* parent, const char * domain, bool permissive)
{
initialize();
socketssl_impl* ret = new socketssl_impl();
if (!ret->init(parent, domain, permissive)) { delete ret; return NULL; }
else return ret;
}
#endif

View File

@ -0,0 +1,5 @@
//TODO:
//- fetch howsmyssl, ensure the only failure is the session cache
//- ensure Subject Name is verified: fetch https://172.217.18.142/ (IP of google.com)
//- ensure bad roots are rejected: fetch https://badfish.filippo.io/
//- ensure bad certs are accepted with verification off

View File

@ -0,0 +1,260 @@
#include "socket.h"
#ifdef ARLIB_SSL_TLSE
extern "C" {
#include "tlse.h"
}
#include <sys/stat.h>
#include <dirent.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
//TLSe flaws and limitations:
//- can't share root certs between contexts (with possible exception of tls_accept, didn't check)
//- lack of const on some functions
//- have to load root certs myself
//- had to implement Subject Alternative Name myself
//- turns out tls_consume_stream(buf_len=0) throws an error - and no debug output
//- tls_export_context return value seems be 'bytes expected' for inputlen=0 and inputlen>=expected,
// but 'additional bytes expected' for inputlen=1
//- lacks extern "C" on header
//- lack of documentation
// separate context here to ensure they're not loaded multiple times, saves memory and time
static TLSContext* rootcerts;
static void initialize()
{
if (rootcerts) return;
rootcerts = tls_create_context(false, TLS_V12);
#ifdef __unix__
DIR* dir = opendir("/etc/ssl/certs/");
uint8_t* cert = NULL;
off_t cert_buf_len = 0;
if (dir)
{
while (true)
{
struct dirent* entry = readdir(dir);
if (!entry) break;
char name[256];
snprintf(name, sizeof(name), "%s%s", "/etc/ssl/certs/", entry->d_name);
if (!strstr(name, "DST_Root")) continue;
struct stat s;
if (stat(name, &s) == 0 && (s.st_mode & S_IFREG))
{
if (s.st_size > cert_buf_len)
{
free(cert);
cert_buf_len = s.st_size;
cert = (uint8_t*)malloc(cert_buf_len);
}
int fd = open(name, O_RDONLY);
if (fd >= 0)
{
off_t actualsize = read(fd, cert, s.st_size);
tls_load_root_certificates(rootcerts, cert, actualsize);
}
}
}
closedir(dir);
}
free(cert);
#else
#error unsupported
#endif
}
class socketssl_impl : public socketssl {
public:
socket* sock;
TLSContext* ssl;
//same as tls_default_verify, except tls_certificate_chain_is_valid_root is given another context
static int verify(TLSContext* context, TLSCertificate* * certificate_chain, int len) {
int err;
if (certificate_chain) {
for (int i = 0; i < len; i++) {
TLSCertificate* certificate = certificate_chain[i];
// check validity date
err = tls_certificate_is_valid(certificate);
if (err)
return err;
// check certificate in certificate->bytes of length certificate->len
// the certificate is in ASN.1 DER format
}
}
// check if chain is valid
err = tls_certificate_chain_is_valid(certificate_chain, len);
if (err)
return err;
const char * sni = tls_sni(context);
if (len>0 && sni) {
err = tls_certificate_valid_subject(certificate_chain[0], sni);
if (err)
return err;
}
// Perform certificate validation agains ROOT CA
err = tls_certificate_chain_is_valid_root(rootcerts, certificate_chain, len);
if (err)
return err;
//return certificate_expired;
//return certificate_revoked;
//return certificate_unknown;
return no_error;
}
void process(bool block)
{
if (!sock) return;
unsigned int outlen = 0;
const uint8_t * out = tls_get_write_buffer(ssl, &outlen);
if (out && outlen)
{
if (sock->send(out, outlen) < 0) { error(); return; }
tls_buffer_clear(ssl);
}
uint8_t in[0x2000];
int inlen = sock->recv(in, sizeof(in), block);
if (inlen<0) { error(); return; }
if (inlen>0) tls_consume_stream(ssl, in, inlen, verify);
}
void error()
{
delete sock;
sock = NULL;
}
static socketssl_impl* create(socket* parent, const char * domain, bool permissive)
{
if (!parent) return NULL;
socketssl_impl* ret = new socketssl_impl();
ret->sock = parent;
ret->fd = parent->get_fd();
ret->ssl = tls_create_context(false, TLS_V12);
tls_make_exportable(ret->ssl, true);
tls_sni_set(ret->ssl, domain);
tls_client_connect(ret->ssl);
while (!tls_established(ret->ssl))
{
ret->process(true);
}
return ret;
}
int recv(uint8_t* data, unsigned int len, bool block = false)
{
process(block);
int ret = tls_read(ssl, data, len);
if (ret==0 && !sock) return e_broken;
return ret;
}
int sendp(const uint8_t* data, unsigned int len, bool block = true)
{
if (!sock) return -1;
int ret = tls_write(ssl, (uint8_t*)data, len);
process(false);
return ret;
}
~socketssl_impl()
{
if (ssl && sock)
{
tls_close_notify(ssl);
process(false);
}
if (ssl) tls_destroy_context(ssl);
if (sock) delete sock;
}
void q()
{
uint8_t data[4096];
int len = tls_export_context(ssl, NULL, 0, false);
int len2 = tls_export_context(ssl, data, len, false);
printf("len=%i len2=%i\n", len, len2);
//tls_destroy_context(ssl);
TLSContext* ssl2 = tls_import_context(data, len);
uint8_t* p1 = (uint8_t*)ssl;
uint8_t* p2 = (uint8_t*)ssl2;
for (int i=0;i<140304;i++)
{
//if (p1[i] != p2[i]) printf("%i: g=%.2X b=%.2X\n", i, p1[i], p2[i]);
}
//ssl = ssl2;
}
size_t serialize_size()
{
return tls_export_context(ssl, NULL, 0, false);
}
int serialize(uint8_t* data, size_t len)
{
process(true);
tls_export_context(ssl, data, len, false);
tls_destroy_context(this->ssl);
this->ssl = NULL;
int ret = decompose(this->sock);
this->sock = NULL;
delete this;
return ret;
}
static socketssl_impl* unserialize(int fd, const uint8_t* data, size_t len)
{
socketssl_impl* ret = new socketssl_impl();
ret->sock = socket::create_from_fd(fd);
ret->fd = fd;
ret->ssl = tls_import_context((uint8_t*)data, len);
if (!ret->ssl) { delete ret; return NULL; }
return ret;
}
};
socketssl* socketssl::create(socket* parent, const char * domain, bool permissive)
{
initialize();
return socketssl_impl::create(parent, domain, permissive);
}
socketssl* socketssl::unserialize(int fd, const uint8_t* data, size_t len)
{
initialize();
return socketssl_impl::unserialize(fd, data, len);
}
#endif

View File

@ -0,0 +1,241 @@
#include "socket.h"
#ifdef ARLIB_SSL_WOLFSSL
//#define HAVE_SNI
#define HAVE_SUPPORTED_CURVES
#include <wolfssl/ssl.h>
#ifdef __unix__
#include <sys/socket.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <errno.h>
#endif
#include <stdlib.h>
#error "this thing is broken. try again once wolfSSL > 3.9 is released and see if that fixes the alert 40 handshake failed errors"
static WOLFSSL_CTX* ctx;
class socketssl_impl : public socketssl {
public:
socket* sock;
WOLFSSL* ssl;
bool nonblock;
socketssl_impl(socket* parent, const char * domain, bool permissive)
{
sock = parent;
fd = get_fd(parent);
//ssl = wolfSSL_new(ctx);
nonblock = 0;
wolfSSL_Init();
wolfSSL_Debugging_ON();
ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method());
wolfSSL_SetIORecv(ctx, socketssl_impl::recv_raw);
wolfSSL_SetIOSend(ctx, socketssl_impl::send_raw);
wolfSSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, 0);
#define err_sys puts
ssl = wolfSSL_new(ctx);
wolfSSL_SetIOReadCtx(ssl, this);
wolfSSL_SetIOWriteCtx(ssl, this);
if (ssl == NULL)
err_sys("unable to get SSL object");
if (wolfSSL_UseSupportedCurve(ssl, WOLFSSL_ECC_SECP256R1)
!= SSL_SUCCESS) {
err_sys("unable to set curve secp256r1");
}
if (wolfSSL_UseSupportedCurve(ssl, WOLFSSL_ECC_SECP384R1)
!= SSL_SUCCESS) {
err_sys("unable to set curve secp384r1");
}
if (wolfSSL_UseSupportedCurve(ssl, WOLFSSL_ECC_SECP521R1)
!= SSL_SUCCESS) {
err_sys("unable to set curve secp521r1");
}
if (wolfSSL_UseSupportedCurve(ssl, WOLFSSL_ECC_SECP224R1)
!= SSL_SUCCESS) {
err_sys("unable to set curve secp224r1");
}
if (wolfSSL_UseSupportedCurve(ssl, WOLFSSL_ECC_SECP192R1)
!= SSL_SUCCESS) {
err_sys("unable to set curve secp192r1");
}
if (wolfSSL_UseSupportedCurve(ssl, WOLFSSL_ECC_SECP160R1)
!= SSL_SUCCESS) {
err_sys("unable to set curve secp160r1");
}
//printf("fd=%i ret=%i ok=%i f=%i\n", fd,
//wolfSSL_set_fd(ssl, fd),
//SSL_SUCCESS, SSL_FAILURE);
puts("AAAAAAA");
if (wolfSSL_connect(ssl) != SSL_SUCCESS) {puts("NOOO");}
//wolfSSL_check_domain_name(ssl, domain);
//#define err_sys puts
// if (wolfSSL_UseSupportedCurve(ssl, WOLFSSL_ECC_SECP256R1)
// != SSL_SUCCESS) {
// err_sys("unable to set curve secp256r1");
// }
// if (wolfSSL_UseSupportedCurve(ssl, WOLFSSL_ECC_SECP384R1)
// != SSL_SUCCESS) {
// err_sys("unable to set curve secp384r1");
// }
// if (wolfSSL_UseSupportedCurve(ssl, WOLFSSL_ECC_SECP521R1)
// != SSL_SUCCESS) {
// err_sys("unable to set curve secp521r1");
// }
// if (wolfSSL_UseSupportedCurve(ssl, WOLFSSL_ECC_SECP224R1)
// != SSL_SUCCESS) {
// err_sys("unable to set curve secp224r1");
// }
// if (wolfSSL_UseSupportedCurve(ssl, WOLFSSL_ECC_SECP192R1)
// != SSL_SUCCESS) {
// err_sys("unable to set curve secp192r1");
// }
// if (wolfSSL_UseSupportedCurve(ssl, WOLFSSL_ECC_SECP160R1)
// != SSL_SUCCESS) {
// err_sys("unable to set curve secp160r1");
// }
//
// wolfSSL_set_fd(ssl, fd);
// puts("cactus");
// if (wolfSSL_connect(ssl) != SSL_SUCCESS) {
// /* see note at top of README */
// int err = wolfSSL_get_error(ssl, 0);
// char buffer[80];
// printf("err = %d, %s\n", err,
// wolfSSL_ERR_error_string(err, buffer));
// err_sys("SSL_connect failed");
// /* if you're getting an error here */
// }
exit(0);
}
/*private*/ static int recv_raw(WOLFSSL* ssl, char* buf, int sz, void* ctx)
{
socketssl_impl* this_ = (socketssl_impl*)ctx;
int ret = this_->sock->recv((uint8_t*)buf, sz);
printf("SSLDATRAW_R=%i\n",ret);
for(int i=0;i<ret;i++)printf("%.2X ",(uint8_t)buf[i]);
puts("");
if (ret==0) return WOLFSSL_CBIO_ERR_WANT_READ;
if (ret<0) return WOLFSSL_CBIO_ERR_GENERAL;
return ret;
}
/*private*/ static int send_raw(WOLFSSL* ssl, char* buf, int sz, void* ctx)
{
socketssl_impl* this_ = (socketssl_impl*)ctx;
int ret;
if (this_->nonblock) ret = this_->sock->send0((uint8_t*)buf, sz);
else ret = this_->sock->send1((uint8_t*)buf, sz);
printf("SSLDATRAW_S=%i\n",ret);
for(int i=0;i<ret;i++)printf("%.2X ",(uint8_t)buf[i]);
puts("");
if (ret==0) return WOLFSSL_CBIO_ERR_WANT_WRITE;
if (ret<0) return WOLFSSL_CBIO_ERR_GENERAL;
return ret;
}
/*private*/ int fixret(int ret)
{
printf("SSLDAT=%i\n",ret);
if (ret > 0) return ret;
int err = wolfSSL_get_error(ssl, ret);
if (err==SSL_ERROR_WANT_READ || err==SSL_ERROR_WANT_WRITE) return 0;
printf("SSLERR=%i\n",err);
return e_broken;
}
int recv(uint8_t* data, int len)
{
nonblock = false;
return fixret(wolfSSL_read(ssl, data, len));
}
int send0(const uint8_t* data, int len)
{
nonblock = true;
return fixret(wolfSSL_write(ssl, data, len));
}
int send1(const uint8_t* data, int len)
{
nonblock = false;
return fixret(wolfSSL_write(ssl, data, len));
}
~socketssl_impl()
{
wolfSSL_free(ssl);
delete sock;
}
};
static void initialize()
{
static bool initialized = false;
if (initialized) return;
initialized = true;
//wolfSSL_Init();
//
//ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method());
//if (!ctx) return;
//wolfSSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, 0);
//wolfSSL_SetIORecv(ctx, socketssl_impl::recv_raw);
//wolfSSL_SetIOSend(ctx, socketssl_impl::send_raw);
#ifdef __unix__
//mostly copypasta from wolfSSL_CTX_load_verify_locations, minus the abort-on-first-error thingy
//there's some random weirdo files in my /etc/ssl/certs/, possibly duplicates?
//DIR* dir = opendir("/etc/ssl/certs/");
//if (dir)
//{
//while (true)
//{
//struct dirent* entry = readdir(dir);
//if (!entry) break;
//char name[256];
//snprintf(name, sizeof(name), "%s%s", "/etc/ssl/certs/", entry->d_name);
//
//struct stat s;
//if (stat(name, &s) == 0 && (s.st_mode & S_IFREG))
//{
//printf("cert=%s\n",name);
//wolfSSL_CTX_load_verify_locations(ctx, name, NULL);
//}
//}
//closedir(dir);
//}
#else
#error unsupported
#endif
//wolfSSL_Debugging_ON();
}
socketssl* socketssl::create(socket* parent, const char * domain, bool permissive)
{
initialize();
//if (!ctx) return NULL;
return new socketssl_impl(parent, domain, permissive);
}
#endif

160
arlib/socket/socket.cpp Normal file
View File

@ -0,0 +1,160 @@
#include "socket.h"
#include <stdio.h>
#undef socket
#undef bind
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#define MSG_NOSIGNAL 0
#define MSG_DONTWAIT 0
#define close closesocket
#define usleep(n) Sleep(n/1000)
#ifdef _MSC_VER
#pragma comment(lib, "ws2_32.lib")
#endif
#else
#include <netdb.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <netinet/tcp.h>
static int setsockopt(int socket, int level, int option_name, int option_value)
{
return setsockopt(socket, level, option_name, &option_value, sizeof(option_value));
}
#endif
namespace {
static void initialize()
{
#ifdef _WIN32 // lol
static bool initialized = false;
if (initialized) return;
initialized = true;
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
#endif
}
static int connect(const char * domain, int port)
{
initialize();
char portstr[16];
sprintf(portstr, "%i", port);
addrinfo hints;
memset(&hints, 0, sizeof(addrinfo));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = 0;
addrinfo * addr = NULL;
getaddrinfo(domain, portstr, &hints, &addr);
if (!addr) return -1;
int fd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
#ifndef _WIN32
//because 30 second pauses are unequivocally detestable
timeval timeout;
timeout.tv_sec = 4;
timeout.tv_usec = 0;
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (char*)&timeout, sizeof(timeout));
#endif
if (connect(fd, addr->ai_addr, addr->ai_addrlen) != 0)
{
freeaddrinfo(addr);
close(fd);
return -1;
}
#ifndef _WIN32
setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, 1); // enable
setsockopt(fd, SOL_TCP, TCP_KEEPCNT, 3); // ping count before the kernel gives up
setsockopt(fd, SOL_TCP, TCP_KEEPIDLE, 30); // seconds idle until it starts pinging
setsockopt(fd, SOL_TCP, TCP_KEEPINTVL, 10); // seconds per ping once the pings start
#else
u_long yes = 1;
ioctlsocket(fd, FIONBIO, &yes);
struct tcp_keepalive keepalive = {
1, // SO_KEEPALIVE
30*1000, // TCP_KEEPIDLE in milliseconds
3*1000, // TCP_KEEPINTVL
//On Windows Vista and later, the number of keep-alive probes (data retransmissions) is set to 10 and cannot be changed.
//https://msdn.microsoft.com/en-us/library/windows/desktop/dd877220(v=vs.85).aspx
//so no TCP_KEEPCNT; I'll reduce INTVL instead. And a polite server will RST anyways.
};
u_long ignore;
WSAIoctl(fd, SIO_KEEPALIVE_VALS, &keepalive, sizeof(keepalive), NULL, 0, &ignore, NULL, NULL);
#endif
freeaddrinfo(addr);
return fd;
}
#define socket socket_t
class socket_impl : public socket {
public:
socket_impl(int fd) { this->fd = fd; }
/*private*/ int fixret(int ret)
{
//printf("r=%i e=%i wb=%i\n", ret, WSAGetLastError(), WSAEWOULDBLOCK);
if (ret > 0) return ret;
if (ret == 0) return e_closed;
#ifdef __unix__
if (ret < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) return 0;
#endif
#ifdef _WIN32
if (ret < 0 && WSAGetLastError() == WSAEWOULDBLOCK) return 0;
#endif
return e_broken;
}
int recv(uint8_t* data, unsigned int len, bool block = false)
{
return fixret(::recv(fd, (char*)data, len, MSG_NOSIGNAL | (block ? 0 : MSG_DONTWAIT)));
}
int sendp(const uint8_t* data, unsigned int len, bool block = true)
{
//printf("snd=%i\n",len);
return fixret(::send(fd, (char*)data, len, MSG_NOSIGNAL | (block ? 0 : MSG_DONTWAIT)));
}
~socket_impl()
{
close(fd);
}
};
static socket* socket_wrap(int fd)
{
if (fd<0) return NULL;
return new socket_impl(fd);
}
}
socket* socket::create_from_fd(int fd)
{
return socket_wrap(fd);
}
socket* socket::create(const char * domain, int port)
{
return socket_wrap(connect(domain, port));
}
//static socket* create_async(const char * domain, int port);
//static socket* create_udp(const char * domain, int port);
//int socket::select(socket* * socks, int nsocks, int timeout_ms)
//{
// return -1;
//}

99
arlib/socket/socket.h Normal file
View File

@ -0,0 +1,99 @@
#include "../global.h"
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#define socket socket_t
class socket : nocopy {
protected:
socket(){}
int fd; // Used by select().
//deallocates the socket, returning its fd, while letting the fd remain valid
static int decompose(socket* sock) { int ret = sock->fd; sock->fd=-1; delete sock; return ret; }
public:
//Returns NULL on connection failure.
static socket* create(const char * domain, int port);
//Always succeeds. If the server can't be contacted, returns failure on first write or read.
static socket* create_async(const char * domain, int port);
static socket* create_udp(const char * domain, int port);
enum {
e_lazy_dev = -1, // Whoever implemented this socket layer was lazy and just returned -1. Treat it as e_broken or an unknown error.
e_closed = -2, // Remote host chose to gracefully close the connection.
e_broken = -3, // Connection was forcibly torn down.
e_udp_too_big = -4, // Attempted to process an unacceptably large UDP packet.
e_ssl_failure = -5, // Certificate validation failed, no algorithms in common, or other SSL error.
};
//Negative means error, see above.
//Positive is number of bytes handled.
//WARNING: Unlike most socket layers, zero does not mean graceful close!
// It means success, zero bytes processed, and is a valid byte count. Socket closed is in the error list above.
//The first two functions will process at least one byte, or if block is false, at least zero. send() sends all bytes before returning.
//block is ignored on Windows (always false), due to lack of MSG_NOWAIT and I don't want to do another syscall every time.
//For UDP sockets, partial reads or writes aren't possible; you always get one or zero packets.
virtual int recv(uint8_t* data, unsigned int len, bool block = true) = 0;
virtual int sendp(const uint8_t* data, unsigned int len, bool block = true) = 0;
int send(const uint8_t* data, unsigned int len)
{
unsigned int sent = 0;
while (sent < len)
{
int here = sendp(data+sent, len-sent);
if (here<0) return here;
sent += here;
}
return len;
}
//Convenience functions for handling textual data.
int recv(char* data, unsigned int len, bool block = false)
{
int ret = recv((uint8_t*)data, len-1, block);
if (ret >= 0) data[ret]='\0';
else data[0]='\0';
return ret;
}
int sendp(const char * data, bool block = true) { return sendp((uint8_t*)data, strlen(data), block); }
int send (const char * data) { return send((uint8_t*)data, strlen(data)); }
//Returns an index to the sockets array, or negative if timeout expires.
//Negative timeouts mean wait forever.
//It's possible that an active socket returns zero bytes.
//However, this is guaranteed to happen rarely enough that repeatedly select()ing will leave the CPU mostly idle.
//(It may be caused by packets with wrong checksum, SSL renegotiation, or whatever.)
static int select(socket* * socks, unsigned int nsocks, int timeout_ms = -1);
virtual ~socket() {}
//Can be used to keep a socket alive across exec(). Don't use for an SSL socket.
static socket* create_from_fd(int fd);
int get_fd() { return fd; }
};
class socketssl : public socket {
protected:
socketssl(){}
public:
//If 'permissive' is true, expired and self-signed server certificates will be accepted.
//Other invalid certs, such as ones for a different domain, may or may not be accepted.
static socketssl* create(const char * domain, int port, bool permissive=false)
{
return socketssl::create(socket::create(domain, port), domain, permissive);
}
//On entry, this takes ownership of the socket. Even if connection fails, the socket may not be used anymore.
//The socket must be a normal TCP socket. UDP and nested SSL is not supported.
static socketssl* create(socket* parent, const char * domain, bool permissive=false);
virtual void q(){}
//Can be used to keep a socket alive across exec().
//If successful, serialize() returns the the file descriptor needed to unserialize, and the socket is deleted.
//If failure, negative return and nothing happens.
virtual size_t serialize_size() { return 0; }
virtual int serialize(uint8_t* data, size_t len) { return -1; }
static socketssl* unserialize(int fd, const uint8_t* data, size_t len);
};

109
arlib/socket/test.c Normal file
View File

@ -0,0 +1,109 @@
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
#ifdef _WIN32
#include <winsock2.h>
#define socklen_t int
#define sleep(x) Sleep(x*1000)
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#endif
#include "tlse.c"
void error(char *msg) {
perror(msg);
exit(0);
}
int send_pending(int client_sock, struct TLSContext *context) {
unsigned int out_buffer_len = 0;
const unsigned char *out_buffer = tls_get_write_buffer(context, &out_buffer_len);
unsigned int out_buffer_index = 0;
int send_res = 0;
while ((out_buffer) && (out_buffer_len > 0)) {
int res = send(client_sock, (char *)&out_buffer[out_buffer_index], out_buffer_len, 0);
if (res <= 0) {
send_res = res;
break;
}
out_buffer_len -= res;
out_buffer_index += res;
}
tls_buffer_clear(context);
return send_res;
}
int validate_certificate(struct TLSContext *context, struct TLSCertificate **certificate_chain, int len) {
int i;
if (certificate_chain) {
for (i = 0; i < len; i++) {
struct TLSCertificate *certificate = certificate_chain[i];
// check certificate ...
}
}
//return certificate_expired;
//return certificate_revoked;
//return certificate_unknown;
return no_error;
}
int main(int argc, char *argv[]) {
int sockfd, portno, n;
//tls_print_certificate("testcert/server.certificate");
//tls_print_certificate("000.certificate");
//exit(0);
struct sockaddr_in serv_addr;
struct hostent *server;
char buffer[256];
char *ref_argv[] = {"", "google.com", "443"};
if (argc < 3) {
argv = ref_argv;
//fprintf(stderr,"usage %s hostname port\n", argv[0]);
//exit(0);
}
#ifdef _WIN32
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
#else
signal(SIGPIPE, SIG_IGN);
#endif
portno = atoi(argv[2]);
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
error("ERROR opening socket");
server = gethostbyname(argv[1]);
if (server == NULL) {
fprintf(stderr,"ERROR, no such host\n");
exit(0);
}
memset((char *) &serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
memcpy((char *)&serv_addr.sin_addr.s_addr, (char *)server->h_addr, server->h_length);
serv_addr.sin_port = htons(portno);
if (connect(sockfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr)) < 0)
error("ERROR connecting");
struct TLSContext *context = tls_create_context(0, TLS_V12);
tls_client_connect(context);
send_pending(sockfd, context);
unsigned char client_message[0xFFFF];
int read_size;
while ((read_size = recv(sockfd, client_message, sizeof(client_message) , 0)) > 0) {
tls_consume_stream(context, client_message, read_size, validate_certificate);
send_pending(sockfd, context);
if (tls_established(context)) {
const char * out = "GET / HTTP/1.1\nHost: example.com\nConnection: close\n\n";
tls_write(context, out, strlen(out));
send_pending(sockfd, context);
unsigned char read_buffer[0xFFFF];
int read_size = tls_read(context, read_buffer, 0xFFFF - 1);
if (read_size > 0)
fwrite(read_buffer, read_size, 1, stdout);
}
}
return 0;
}

7974
arlib/socket/tlse.c Normal file

File diff suppressed because it is too large Load Diff

276
arlib/socket/tlse.h Normal file
View File

@ -0,0 +1,276 @@
// from https://github.com/eduardsui/tlse
#ifndef TLSE_H
#define TLSE_H
// #define DEBUG
// define TLS_LEGACY_SUPPORT to support TLS 1.1/1.0 (legacy)
// legacy support it will use an additional 272 bytes / context
#define TLS_LEGACY_SUPPORT
// SSL_* style blocking APIs
#define SSL_COMPATIBLE_INTERFACE
// support forward secrecy (Diffie-Hellman ephemeral)
#define TLS_FORWARD_SECRECY
// support client-side ECDHE
#define TLS_CLIENT_ECDHE
// suport ecdsa
#define TLS_ECDSA_SUPPORTED
// TLS renegotiation is disabled by default (secured or not)
// do not uncomment next line!
// #define TLS_ACCEPT_SECURE_RENEGOTIATION
#define TLS_V10 0x0301
#define TLS_V11 0x0302
#define TLS_V12 0x0303
#define DTLS_V10 0xFEFF
#define DTLS_V12 0xFEFD
#define TLS_NEED_MORE_DATA 0
#define TLS_GENERIC_ERROR -1
#define TLS_BROKEN_PACKET -2
#define TLS_NOT_UNDERSTOOD -3
#define TLS_NOT_SAFE -4
#define TLS_NO_COMMON_CIPHER -5
#define TLS_UNEXPECTED_MESSAGE -6
#define TLS_CLOSE_CONNECTION -7
#define TLS_COMPRESSION_NOT_SUPPORTED -8
#define TLS_NO_MEMORY -9
#define TLS_NOT_VERIFIED -10
#define TLS_INTEGRITY_FAILED -11
#define TLS_ERROR_ALERT -12
#define TLS_BROKEN_CONNECTION -13
#define TLS_BAD_CERTIFICATE -14
#define TLS_UNSUPPORTED_CERTIFICATE -15
#define TLS_NO_RENEGOTIATION -16
#define TLS_FEATURE_NOT_SUPPORTED -17
#define TLS_RSA_WITH_AES_128_CBC_SHA 0x002F
#define TLS_RSA_WITH_AES_256_CBC_SHA 0x0035
#define TLS_RSA_WITH_AES_128_CBC_SHA256 0x003C
#define TLS_RSA_WITH_AES_256_CBC_SHA256 0x003D
#define TLS_RSA_WITH_AES_128_GCM_SHA256 0x009C
#define TLS_RSA_WITH_AES_256_GCM_SHA384 0x009D
// forward secrecy
#define TLS_DHE_RSA_WITH_AES_128_CBC_SHA 0x0033
#define TLS_DHE_RSA_WITH_AES_256_CBC_SHA 0x0039
#define TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 0x0067
#define TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 0x006B
#define TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 0x009E
#define TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 0x009F
#define TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA 0xC013
#define TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA 0xC014
#define TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 0xC027
#define TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 0xC02F
#define TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 0xC030
#define TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA 0xC009
#define TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA 0xC00A
#define TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 0xC023
#define TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 0xC024
#define TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 0xC02B
#define TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 0xC02C
#define TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 0xCCA8
#define TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 0xCCA9
#define TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 0xCCAA
#define TLS_FALLBACK_SCSV 0x5600
#define TLS_UNSUPPORTED_ALGORITHM 0x00
#define TLS_RSA_SIGN_RSA 0x01
#define TLS_RSA_SIGN_MD5 0x04
#define TLS_RSA_SIGN_SHA1 0x05
#define TLS_RSA_SIGN_SHA256 0x0B
#define TLS_RSA_SIGN_SHA384 0x0C
#define TLS_RSA_SIGN_SHA512 0x0D
#define TLS_EC_PUBLIC_KEY 0x11
#define TLS_EC_prime192v1 0x12
#define TLS_EC_prime192v2 0x13
#define TLS_EC_prime192v3 0x14
#define TLS_EC_prime239v1 0x15
#define TLS_EC_prime239v2 0x16
#define TLS_EC_prime239v3 0x17
#define TLS_EC_prime256v1 0x18
#define TLS_EC_secp224r1 21
#define TLS_EC_secp256r1 23
#define TLS_EC_secp384r1 24
#define TLS_EC_secp521r1 25
#define TLS_ALERT_WARNING 0x01
#define TLS_ALERT_CRITICAL 0x02
typedef enum {
close_notify = 0,
unexpected_message = 10,
bad_record_mac = 20,
decryption_failed_RESERVED = 21,
record_overflow = 22,
decompression_failure = 30,
handshake_failure = 40,
no_certificate_RESERVED = 41,
bad_certificate = 42,
unsupported_certificate = 43,
certificate_revoked = 44,
certificate_expired = 45,
certificate_unknown = 46,
illegal_parameter = 47,
unknown_ca = 48,
access_denied = 49,
decode_error = 50,
decrypt_error = 51,
export_restriction_RESERVED = 60,
protocol_version = 70,
insufficient_security = 71,
internal_error = 80,
inappropriate_fallback = 86,
user_canceled = 90,
no_renegotiation = 100,
unsupported_extension = 110,
no_error = 255
} TLSAlertDescription;
// forward declarations
struct TLSPacket;
struct TLSCertificate;
struct TLSContext;
struct ECCCurveParameters;
typedef struct TLSContext TLS;
typedef struct TLSCertificate Certificate;
typedef int (*tls_validation_function)(struct TLSContext *context, struct TLSCertificate **certificate_chain, int len);
unsigned char *tls_pem_decode(const unsigned char *data_in, unsigned int input_length, int cert_index, unsigned int *output_len);
struct TLSCertificate *tls_create_certificate();
int tls_certificate_valid_subject(struct TLSCertificate *cert, const char *subject);
int tls_certificate_valid_subject_name(const unsigned char *cert_subject, const char *subject);
int tls_certificate_is_valid(struct TLSCertificate *cert);
void tls_certificate_set_copy(unsigned char **member, const unsigned char *val, int len);
void tls_certificate_set_copy_date(unsigned char **member, const unsigned char *val, int len);
void tls_certificate_set_key(struct TLSCertificate *cert, const unsigned char *val, int len);
void tls_certificate_set_priv(struct TLSCertificate *cert, const unsigned char *val, int len);
void tls_certificate_set_sign_key(struct TLSCertificate *cert, const unsigned char *val, int len);
char *tls_certificate_to_string(struct TLSCertificate *cert, char *buffer, int len);
void tls_certificate_set_exponent(struct TLSCertificate *cert, const unsigned char *val, int len);
void tls_certificate_set_serial(struct TLSCertificate *cert, const unsigned char *val, int len);
void tls_certificate_set_algorithm(unsigned int *algorithm, const unsigned char *val, int len);
void tls_destroy_certificate(struct TLSCertificate *cert);
struct TLSPacket *tls_create_packet(struct TLSContext *context, unsigned char type, unsigned short version, int payload_size_hint);
void tls_destroy_packet(struct TLSPacket *packet);
void tls_packet_update(struct TLSPacket *packet);
int tls_packet_append(struct TLSPacket *packet, unsigned char *buf, unsigned int len);
int tls_packet_uint8(struct TLSPacket *packet, unsigned char i);
int tls_packet_uint16(struct TLSPacket *packet, unsigned short i);
int tls_packet_uint32(struct TLSPacket *packet, unsigned int i);
int tls_packet_uint24(struct TLSPacket *packet, unsigned int i);
int tls_random(unsigned char *key, int len);
const unsigned char *tls_get_write_buffer(struct TLSContext *context, unsigned int *outlen);
void tls_buffer_clear(struct TLSContext *context);
int tls_established(struct TLSContext *context);
void tls_read_clear(struct TLSContext *context);
int tls_read(struct TLSContext *context, unsigned char *buf, unsigned int size);
struct TLSContext *tls_create_context(unsigned char is_server, unsigned short version);
const struct ECCCurveParameters *tls_set_curve(struct TLSContext *context, const struct ECCCurveParameters *curve);
struct TLSContext *tls_accept(struct TLSContext *context);
int tls_set_default_dhe_pg(struct TLSContext *context, const char *p_hex_str, const char *g_hex_str);
void tls_destroy_context(struct TLSContext *context);
int tls_cipher_supported(struct TLSContext *context, unsigned short cipher);
int tls_cipher_is_fs(struct TLSContext *context, unsigned short cipher);
int tls_choose_cipher(struct TLSContext *context, const unsigned char *buf, int buf_len, int *scsv_set);
int tls_cipher_is_ephemeral(struct TLSContext *context);
const char *tls_cipher_name(struct TLSContext *context);
int tls_is_ecdsa(struct TLSContext *context);
struct TLSPacket *tls_build_client_key_exchange(struct TLSContext *context);
struct TLSPacket *tls_build_server_key_exchange(struct TLSContext *context, int method);
struct TLSPacket *tls_build_hello(struct TLSContext *context);
struct TLSPacket *tls_certificate_request(struct TLSContext *context);
struct TLSPacket *tls_build_verify_request(struct TLSContext *context);
int tls_parse_hello(struct TLSContext *context, const unsigned char *buf, int buf_len, unsigned int *write_packets, unsigned int *dtls_verified);
int tls_parse_certificate(struct TLSContext *context, const unsigned char *buf, int buf_len, int is_client);
int tls_parse_server_key_exchange(struct TLSContext *context, const unsigned char *buf, int buf_len);
int tls_parse_client_key_exchange(struct TLSContext *context, const unsigned char *buf, int buf_len);
int tls_parse_server_hello_done(struct TLSContext *context, const unsigned char *buf, int buf_len);
int tls_parse_finished(struct TLSContext *context, const unsigned char *buf, int buf_len, unsigned int *write_packets);
int tls_parse_verify(struct TLSContext *context, const unsigned char *buf, int buf_len);
int tls_parse_payload(struct TLSContext *context, const unsigned char *buf, int buf_len, tls_validation_function certificate_verify);
int tls_parse_message(struct TLSContext *context, unsigned char *buf, int buf_len, tls_validation_function certificate_verify);
int tls_certificate_verify_signature(struct TLSCertificate *cert, struct TLSCertificate *parent);
int tls_certificate_chain_is_valid(struct TLSCertificate **certificates, int len);
int tls_certificate_chain_is_valid_root(struct TLSContext *context, struct TLSCertificate **certificates, int len);
int tls_load_certificates(struct TLSContext *context, const unsigned char *pem_buffer, int pem_size);
int tls_load_private_key(struct TLSContext *context, const unsigned char *pem_buffer, int pem_size);
struct TLSPacket *tls_build_certificate(struct TLSContext *context);
struct TLSPacket *tls_build_finished(struct TLSContext *context);
struct TLSPacket *tls_build_change_cipher_spec(struct TLSContext *context);
struct TLSPacket *tls_build_done(struct TLSContext *context);
struct TLSPacket *tls_build_message(struct TLSContext *context, unsigned char *data, unsigned int len);
int tls_client_connect(struct TLSContext *context);
int tls_write(struct TLSContext *context, unsigned char *data, unsigned int len);
struct TLSPacket *tls_build_alert(struct TLSContext *context, char critical, unsigned char code);
int tls_consume_stream(struct TLSContext *context, const unsigned char *buf, int buf_len, tls_validation_function certificate_verify);
void tls_close_notify(struct TLSContext *context);
void tls_alert(struct TLSContext *context, unsigned char critical, int code);
int tls_pending(struct TLSContext *context);
void tls_make_exportable(struct TLSContext *context, unsigned char exportable_flag);
int tls_export_context(struct TLSContext *context, unsigned char *buffer, unsigned int buf_len, unsigned char small_version);
struct TLSContext *tls_import_context(unsigned char *buffer, unsigned int buf_len);
int tls_is_broken(struct TLSContext *context);
int tls_request_client_certificate(struct TLSContext *context);
int tls_client_verified(struct TLSContext *context);
const char *tls_sni(struct TLSContext *context);
int tls_sni_set(struct TLSContext *context, const char *sni);
int tls_load_root_certificates(struct TLSContext *context, const unsigned char *pem_buffer, int pem_size);
int tls_default_verify(struct TLSContext *context, struct TLSCertificate **certificate_chain, int len);
void tls_print_certificate(const char *fname);
#ifdef SSL_COMPATIBLE_INTERFACE
#define SSL_SERVER_RSA_CERT 1
#define SSL_SERVER_RSA_KEY 2
typedef struct TLSContext SSL_CTX;
typedef struct TLSContext SSL;
#define SSL_FILETYPE_PEM 1
#define SSL_VERIFY_NONE 0
#define SSL_VERIFY_PEER 1
#define SSL_VERIFY_FAIL_IF_NO_PEER_CERT 2
#define SSL_VERIFY_CLIENT_ONCE 3
typedef struct {
int fd;
tls_validation_function certificate_verify;
void *user_data;
} SSLUserData;
int SSL_library_init();
void SSL_load_error_strings();
void OpenSSL_add_all_algorithms();
void OpenSSL_add_all_ciphers();
void OpenSSL_add_all_digests();
void EVP_cleanup();
int SSLv3_server_method();
int SSLv3_client_method();
struct TLSContext *SSL_new(struct TLSContext *context);
int SSL_CTX_use_certificate_file(struct TLSContext *context, const char *filename, int dummy);
int SSL_CTX_use_PrivateKey_file(struct TLSContext *context, const char *filename, int dummy);
int SSL_CTX_check_private_key(struct TLSContext *context);
struct TLSContext *SSL_CTX_new(int method);
void SSL_free(struct TLSContext *context);
void SSL_CTX_free(struct TLSContext *context);
int SSL_get_error(struct TLSContext *context, int ret);
int SSL_set_fd(struct TLSContext *context, int socket);
void *SSL_set_userdata(struct TLSContext *context, void *data);
void *SSL_userdata(struct TLSContext *context);
int SSL_CTX_root_ca(struct TLSContext *context, const char *pem_filename);
void SSL_CTX_set_verify(struct TLSContext *context, int mode, tls_validation_function verify_callback);
int SSL_accept(struct TLSContext *context);
int SSL_connect(struct TLSContext *context);
int SSL_shutdown(struct TLSContext *context);
int SSL_write(struct TLSContext *context, void *buf, unsigned int len);
int SSL_read(struct TLSContext *context, void *buf, unsigned int len);
int SSL_pending(struct TLSContext *context);
#endif
#endif

1555
arlib/socket/uuu.cpp Normal file

File diff suppressed because it is too large Load Diff

141
arlib/socket/wolfssl-lib.c Normal file
View File

@ -0,0 +1,141 @@
#ifdef ARLIB_SSL_WOLFSSL_SP
//I'll have to #include the parts of WolfSSL I need
//it's easier in preprocessor than in makefile
#define DEBUG_WOLFSSL
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
//#define NO_WOLFSSL_MEMORY // use malloc like a sane program
//#define NO_WOLFSSL_DIR // we scan the directories ourelves
//#define WOLFSSL_USER_IO // we set our own read/write callbacks
#ifdef _WIN32
#define USE_WINDOWS_API
#else
#define WOLFSSL_PTHREADS
#endif
//#ifndef ARLIB_THREAD
//#define SINGLE_THREADED
//#endif
//got these from ./configure
#define HAVE_THREAD_LS
#define HAVE_AESGCM
#define WOLFSSL_SHA512
#define WOLFSSL_SHA384
#define NO_DSA
#define HAVE_ECC
#define TFM_ECC256
#define ECC_SHAMIR
#define NO_RC4
#define NO_HC128
#define NO_RABBIT
#define HAVE_POLY1305
#define HAVE_ONE_TIME_AUTH
#define HAVE_CHACHA
#define HAVE_HASHDRBG
#define HAVE_TLS_EXTENSIONS
#define HAVE_SUPPORTED_CURVES
#define NO_PSK
#define NO_MD4
#define NO_PWDBASED
#define USE_FAST_MATH
#define WOLFSSL_X86_64_BUILD
#define HAVE___UINT128_T
#include "wolfssl-3.9.0/src/crl.c"
#include "wolfssl-3.9.0/src/internal.c"
#define c16toa c16toa_b // these functions are copypasted. should be in a header
#define c32toa c32toa_b
#define ato16 ato16_b
#define c24to32 c24to32_b
#define GetSEQIncrement GetSEQIncrement_b
#include "wolfssl-3.9.0/src/io.c"
#include "wolfssl-3.9.0/src/keys.c"
#include "wolfssl-3.9.0/src/ocsp.c"
#include "wolfssl-3.9.0/src/sniffer.c"
#include "wolfssl-3.9.0/src/ssl.c"
#include "wolfssl-3.9.0/src/tls.c"
#include "wolfssl-3.9.0/wolfcrypt/src/aes.c"
#include "wolfssl-3.9.0/wolfcrypt/src/arc4.c"
//#include "wolfssl-3.9.0/wolfcrypt/src/asm.c"
#include "wolfssl-3.9.0/wolfcrypt/src/asn.c"
#include "wolfssl-3.9.0/wolfcrypt/src/blake2b.c"
#include "wolfssl-3.9.0/wolfcrypt/src/camellia.c"
#include "wolfssl-3.9.0/wolfcrypt/src/chacha20_poly1305.c"
#include "wolfssl-3.9.0/wolfcrypt/src/chacha.c"
#include "wolfssl-3.9.0/wolfcrypt/src/coding.c"
#include "wolfssl-3.9.0/wolfcrypt/src/compress.c"
#include "wolfssl-3.9.0/wolfcrypt/src/curve25519.c"
#include "wolfssl-3.9.0/wolfcrypt/src/des3.c"
#include "wolfssl-3.9.0/wolfcrypt/src/dh.c"
#include "wolfssl-3.9.0/wolfcrypt/src/dsa.c"
#include "wolfssl-3.9.0/wolfcrypt/src/ecc.c"
#include "wolfssl-3.9.0/wolfcrypt/src/ecc_fp.c"
#include "wolfssl-3.9.0/wolfcrypt/src/ed25519.c"
#include "wolfssl-3.9.0/wolfcrypt/src/error.c"
#include "wolfssl-3.9.0/wolfcrypt/src/fe_low_mem.c"
#include "wolfssl-3.9.0/wolfcrypt/src/fe_operations.c"
#include "wolfssl-3.9.0/wolfcrypt/src/ge_low_mem.c"
#include "wolfssl-3.9.0/wolfcrypt/src/ge_operations.c"
#include "wolfssl-3.9.0/wolfcrypt/src/hash.c"
#include "wolfssl-3.9.0/wolfcrypt/src/hc128.c"
#include "wolfssl-3.9.0/wolfcrypt/src/hmac.c"
#include "wolfssl-3.9.0/wolfcrypt/src/idea.c"
#include "wolfssl-3.9.0/wolfcrypt/src/integer.c"
#include "wolfssl-3.9.0/wolfcrypt/src/logging.c"
#include "wolfssl-3.9.0/wolfcrypt/src/md2.c"
#define Transform Transform_md4 // several functions and macros exist multiple times
#define AddLength AddLength_md4
#include "wolfssl-3.9.0/wolfcrypt/src/md4.c"
#undef Transform
#undef AddLength
#define Transform Transform_md5
#define AddLength AddLength_md5
#include "wolfssl-3.9.0/wolfcrypt/src/md5.c"
#undef Transform
#undef AddLength
#undef XTRANSFORM
#include "wolfssl-3.9.0/wolfcrypt/src/memory.c"
#include "wolfssl-3.9.0/wolfcrypt/src/misc.c"
#include "wolfssl-3.9.0/wolfcrypt/src/pkcs7.c"
#include "wolfssl-3.9.0/wolfcrypt/src/poly1305.c"
#undef LO
#include "wolfssl-3.9.0/wolfcrypt/src/pwdbased.c"
#include "wolfssl-3.9.0/wolfcrypt/src/rabbit.c"
#include "wolfssl-3.9.0/wolfcrypt/src/random.c"
#include "wolfssl-3.9.0/wolfcrypt/src/ripemd.c"
#include "wolfssl-3.9.0/wolfcrypt/src/rsa.c"
#define Transform Transform_sha256
#define AddLength AddLength_sha256
#include "wolfssl-3.9.0/wolfcrypt/src/sha256.c"
#undef Ch
#undef Maj
#undef R
#undef R2
#undef blk0
#undef Transform
#undef AddLength
#include "wolfssl-3.9.0/wolfcrypt/src/sha512.c"
#undef Ch
#undef Maj
#undef R
#undef R2
#undef blk0
#undef XTRANSFORM
#define _Transform _Transform_sha
#define AddLength AddLength_sha
#include "wolfssl-3.9.0/wolfcrypt/src/sha.c"
#undef Transform
#undef AddLength
#include "wolfssl-3.9.0/wolfcrypt/src/signature.c"
#include "wolfssl-3.9.0/wolfcrypt/src/srp.c"
#include "wolfssl-3.9.0/wolfcrypt/src/tfm.c"
#include "wolfssl-3.9.0/wolfcrypt/src/wc_encrypt.c"
#include "wolfssl-3.9.0/wolfcrypt/src/wc_port.c"
#endif

349
arlib/string.h Normal file
View File

@ -0,0 +1,349 @@
#pragma once
#include "global.h"
//A cstring does not own its memory; it only borrows it from someone else. A string does own its memory.
//Public members shall be named after their counterpart in std::string, if one exists; if not, look in .NET System.String.
//If neither have a good counterpart, any name is acceptable.
//Due to COW optimizations, strings are not thread safe. If you need to share strings across threads,
// call .unshare() after storing it.
//Strings are always NUL terminated. It is safe to overwrite the NUL on a string; that will extend the string.
class string {
private:
static const int obj_size = 24; // maximum 32, or len_inline overflows
static const int max_inline = obj_size-1-1; // -1 for length, -1 for NUL
friend class cstring;
friend class wstring;
union {
struct { // .inlined = 1 (checking .inlined is always allowed)
//ensure .inlined is in the first byte; don't care if it's top or bottom bit
//GCC orders bitfields according to <http://stackoverflow.com/a/1490135>
// With GCC, big endian machines lay out the bits big end first and little endian machines lay out the bits little end first.
//while MSVC follows <https://msdn.microsoft.com/en-us/library/ewwyfdbe.aspx>
// Microsoft Specific: The ordering of data declared as bit fields is from low to high bit
//so I need the low bit first on little endian, MSVC or both; and high bit first on bigend GCC.
//Luckily, Windows doesn't operate on bigend, per <https://support.microsoft.com/en-us/kb/102025>
// Windows NT was designed around Little Endian architecture and was not designed to be compatible with Big Endian
//so I can ignore that combination, and swapping based on endian gives what I want.
BIGEND_SWAP2(
uint8_t inlined : 1;
uint8_t owning : 1;
mutable uint8_t wcache : 1;
,
uint8_t len_inline : 5;
)
//it would be possible to use the last byte of the inlined data as both length indicator and NUL terminator
//(0x00 = length 23, other = outlined or shorter)
//but the extra effort required makes it not worth it. 22 is a perfectly fine SSO length, I don't need 23.
char data_inline[max_inline+1];
};
struct { // .inlined = 0
BIGEND_SWAP2(
uint32_t inlined32 : 1;
uint32_t owning32 : 1;
mutable uint32_t wcache32 : 1;
,
uint32_t len_outline : 29;
)
//char pad[4];
char* data_outline; // if owning, there's also a int32 refcount before this pointer; if not owning, no such thing
//char pad2[8];
};
};
static size_t bytes_for(uint32_t len)
{
return bitround(sizeof(int)+len+1);
}
static char * clone_sized(const char * in, uint32_t len, uint32_t alloclen)
{
int* refcount = malloc(bytes_for(alloclen));
*refcount = 1;
char* ret = (char*)(refcount+1);
memcpy(ret, in, len);
ret[len] = '\0';
return ret;
}
static char * clone(const char * in, uint32_t len)
{
return clone_sized(in, len, len);
}
static char * clone(const char * in)
{
return clone(in, strlen(in));
}
int* refcount() // yields garbage if not inlined or not owning
{
return (int*)(data_outline-sizeof(int));
}
void addref()
{
if (inlined) return;
if (!owning) return;
++*refcount();
}
void release()
{
if (inlined) return;
if (!owning) return;
if (--*refcount() == 0) free(data_outline - sizeof(int));
}
public:
//Detaches a string object from anything it's COWed with. Normally not needed, but if you need to
// share a string across threads, it can be useful.
void unshare()
{
wcache = 0;
if (inlined) return;
if (owning && *refcount() == 1) return;
//use the string after releasing our reference - ugly, but we lose the old refcount if we change data_outline, and we're not thread safe anyways
release();
owning = 1;
data_outline = clone(data_outline, len_outline);
}
private:
void init_from(const char * str)
{
uint32_t len = strlen(str);
if (len <= max_inline)
{
inlined = 1;
owning = 1;
wcache = 0;
len_inline = len;
memcpy(data_inline, str, len+1);
}
else
{
inlined32 = 0;
owning32 = 1;
wcache32 = 0;
len_outline = len;
data_outline = clone(str, len_outline);
}
}
void init_from(const string& other)
{
memcpy(this, &other, sizeof(string));
if (!inlined)
{
if (owning) addref();
else data_outline = clone(data_outline, len_outline);
}
}
void resize(uint32_t newsize)
{
uint32_t oldsize = size();
if (oldsize == newsize) return;
unshare();
if (newsize > max_inline)
{
if (inlined)
{
data_outline = clone_sized(data_inline, oldsize, newsize);
}
else if (bytes_for(oldsize) != bytes_for(newsize))
{
data_outline = realloc(data_outline-sizeof(int), bytes_for(newsize));
}
inlined32 = 0;
owning32 = 1; // set this unconditionally, it allows the compiler to merge the writed
wcache32 = 0;
len_outline = newsize;
data_outline[newsize] = '\0';
}
else
{
if (!inlined) memcpy(data_inline, data(), oldsize);
data_inline[newsize] = '\0';
inlined = 1;
owning = 1;
wcache = 0;
len_inline = newsize;
}
}
//Ignored if the new size is smaller.
void resize_grow(uint32_t newsize)
{
uint32_t oldsize = size();
if (oldsize >= newsize) return;
resize(newsize);
}
//Ignored if the new size is larger.
void resize_shrink(uint32_t newsize)
{
uint32_t oldsize = size();
if (oldsize <= newsize) return;
resize(newsize);
}
char getchar(uint32_t index) const { return data()[index]; }
void setchar(uint32_t index, char val) { unshare(); resize_grow(index+1); data()[index] = val; }
char * data() { return inlined ? data_inline : data_outline; }
class noinit {};
string(noinit) {}
public:
string() { inlined=1; owning=1; wcache=0; len_inline=0; data_inline[0] = '\0'; }
string(const string& other) { init_from(other); }
string(const char * str) { init_from(str); }
string& operator=(const string& other) { release(); init_from(other); return *this; }
string& operator=(const char * str) { release(); init_from(str); return *this; }
~string() { release(); }
const char * data() const { return inlined ? data_inline : data_outline; }
uint32_t size() const { return inlined ? len_inline : len_outline; }
operator const char * () const { return data(); }
private:
class charref {
string* parent;
uint32_t index;
public:
charref& operator=(char ch) { parent->setchar(index, ch); return *this; }
operator char() { return parent->getchar(index); }
charref(string* parent, uint32_t index) : parent(parent), index(index) {}
};
friend class charref;
public:
charref operator[](uint32_t index) { return charref(this, index); }
charref operator[](int index) { return charref(this, index); }
char operator[](uint32_t index) const { return getchar(index); }
char operator[](int index) const { return getchar(index); }
void replace(uint32_t pos, uint32_t len, string newdat)
{
unshare();
uint32_t newlen = newdat.size();
if (newlen > len) resize(size()-len+newlen);
uint32_t mylen = size();
char* dat = data();
if (newlen != len) memmove(dat+pos+newlen, dat+pos+len, mylen-len-pos);
memcpy(dat+pos, newdat.data(), newlen);
if (newlen < len) resize(mylen-len+newlen);
}
};
class cstring : public string {
public:
cstring() : string() {}
cstring(const string& other) : string(other) {}
cstring(const cstring& other) : string(noinit())
{
memcpy(this, &other, sizeof(cstring));
owning = 0;
}
cstring(const char * str)
{
inlined32 = 0;
owning32 = 0;
wcache32 = 0;
len_outline = strlen(str);
data_outline = (char*)str;
}
};
//TODO
class wstring : public string {
mutable uint32_t pos_bytes;
mutable uint32_t pos_chars;
mutable uint32_t wsize;
//char pad[4];
const uint32_t WSIZE_UNKNOWN = -1;
void clearcache() const
{
pos_bytes = 0;
pos_chars = 0;
wsize = WSIZE_UNKNOWN;
wcache = 1;
}
void checkcache() const
{
if (!wcache) clearcache();
}
uint32_t findcp(uint32_t index) const
{
checkcache();
if (pos_chars > index)
{
pos_bytes=0;
pos_chars=0;
}
uint8_t* scan = (uint8_t*)data() + pos_bytes;
uint32_t chars = pos_chars;
while (chars != index)
{
if ((*scan&0xC0) != 0x80) chars++;
scan++;
}
pos_bytes = scan - (uint8_t*)data();
pos_chars = index;
return pos_bytes;
}
uint32_t getcp(uint32_t index) const { return 42; }
void setcp(uint32_t index, uint32_t val) { }
class charref {
wstring* parent;
uint32_t index;
public:
charref& operator=(char ch) { parent->setcp(index, ch); return *this; }
operator uint32_t() { return parent->getcp(index); }
charref(wstring* parent, uint32_t index) : parent(parent), index(index) {}
};
friend class charref;
public:
wstring() : string() { clearcache(); }
wstring(const string& other) : string(other) { clearcache(); }
wstring(const char * str) : string(str) { clearcache(); }
charref operator[](uint32_t index) { return charref(this, index); }
charref operator[](int index) { return charref(this, index); }
uint32_t operator[](uint32_t index) const { return getcp(index); }
uint32_t operator[](int index) const { return getcp(index); }
uint32_t size() const
{
checkcache();
if (wsize == WSIZE_UNKNOWN)
{
uint8_t* scan = (uint8_t*)data() + pos_bytes;
uint32_t chars = pos_chars;
while (*scan)
{
if ((*scan&0xC0) != 0x80) chars++;
scan++;
}
wsize = chars;
}
return wsize;
}
};

2
arlib/test.cpp Normal file
View File

@ -0,0 +1,2 @@
//TODO
//should use

1
arlib/thread.h Normal file
View File

@ -0,0 +1 @@
#include "thread/thread.h"

128
arlib/thread/atomic.h Normal file
View File

@ -0,0 +1,128 @@
#pragma once
//This header defines several functions for atomically operating on integers or pointers.
//You can use int32_t, uint32_t, any typedef thereof, and any pointer.
//The following functions exist:
//lock_read(T*)
//lock_write(T*, T)
//lock_incr(T*)
//lock_decr(T*)
//lock_xchg(T*, T)
//lock_cmpxchg(T*, T, T)
//All of them use aquire-release ordering. If you know what you're doing, you can append _acq, _rel or _loose.
//All of these functions (except store) return the value before the operation.
//(cmp)xchg obviously does, so to ease memorization, the others do too.
#if GCC_VERSION > 0
#if GCC_VERSION >= 40700
//https://gcc.gnu.org/onlinedocs/gcc-4.7.0/gcc/_005f_005fatomic-Builtins.html
#define LOCKD_LOCKS_MODEL(type, model, modelname) \
inline type lock_incr ## modelname(type * val) { return __atomic_fetch_add(val, 1, model); } \
inline type lock_decr ## modelname(type * val) { return __atomic_fetch_sub(val, 1, model); } \
inline type lock_xchg ## modelname(type * val, type newval) { return __atomic_exchange_n(val, newval, model); } \
inline type lock_cmpxchg ## modelname(type * val, type old, type newval) { return __sync_val_compare_and_swap(val, old, newval); } \
//there is a modern version of cmpxchg, but it adds another move instruction for whatever reason and otherwise gives the same binary.
//__atomic_compare_exchange_n(val, &old, newval, false, __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE);
#define LOCKD_LOCKS(type) \
inline type lock_read(type * val) { return __atomic_load_n(val, __ATOMIC_ACQUIRE); } \
inline type lock_read_acq(type * val) { return __atomic_load_n(val, __ATOMIC_ACQUIRE); } \
inline type lock_read_loose(type * val) { return __atomic_load_n(val, __ATOMIC_RELAXED); } \
inline void lock_write(type * val, type newval) { __atomic_store_n(val, newval, __ATOMIC_RELEASE); } \
inline void lock_write_rel(type * val, type newval) { __atomic_store_n(val, newval, __ATOMIC_RELEASE); } \
inline void lock_write_loose(type * val, type newval) { __atomic_store_n(val, newval, __ATOMIC_RELAXED); } \
LOCKD_LOCKS_MODEL(type, __ATOMIC_ACQ_REL, ) \
LOCKD_LOCKS_MODEL(type, __ATOMIC_ACQUIRE, _acq) \
LOCKD_LOCKS_MODEL(type, __ATOMIC_RELEASE, _rel) \
LOCKD_LOCKS_MODEL(type, __ATOMIC_RELAXED, _loose) \
#else
//https://gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Atomic-Builtins.html
//the memory model remains unused, but all functions must still be defined.
#define LOCKD_LOCKS_MODEL(type, modelname) \
inline type lock_incr ## modelname(type * val) { __sync_fetch_and_add(val, 1); } \
inline type lock_decr ## modelname(type * val) { __sync_fetch_and_sub(val, 1); } \
inline type lock_cmpxchg ## modelname(type * val, type old, type newval) { return __sync_val_compare_and_swap(val, old, newval); } \
inline type lock_xchg ## modelname(type * val, type newval) \
{ \
type prev = lock_read(val); \
while (true) \
{ \
type prev2 = lock_cmpxchg(val, prev, newval); \
if (prev == prev2) break; \
else prev = prev2; \
} \
}
#define LOCKD_LOCKS(type) \
inline type lock_read(type * val) { return __sync_fetch_and_add(val, 0); } \
inline type lock_read_acq(type * val) { return __sync_fetch_and_add(val, 0); } \
inline type lock_read_loose(type * val) { return __sync_fetch_and_add(val, 0); } \
LOCKD_LOCKS_MODEL(type, ) \
LOCKD_LOCKS_MODEL(type, _acq) \
LOCKD_LOCKS_MODEL(type, _rel) \
LOCKD_LOCKS_MODEL(type, _loose) \
inline void lock_write(type * val, type newval) { lock_xchg(val, newval); } \
inline void lock_write_rel(type * val, type newval) { lock_xchg(val, newval); } \
inline void lock_write_loose(type * val, type newval) { lock_xchg(val, newval); } \
#endif
LOCKD_LOCKS(uint32_t)
LOCKD_LOCKS(int32_t)
LOCKD_LOCKS(void*)
#elif defined(_WIN32)
#define LOCKD_LOCKS_MODEL(type, wintype, suffix, modelname) \
inline type lock_incr##modelname(type* val) { return (type)(InterlockedIncrement##suffix((wintype*)val)-1); } \
inline type lock_decr##modelname(type* val) { return (type)(InterlockedDecrement##suffix((wintype*)val)+1); } \
inline type lock_xchg##modelname(type* val, type newval) { return (type)InterlockedExchange##suffix((wintype*)val, (wintype)newval); } \
inline type lock_cmpxchg##modelname(type* val, type old, type newval) \
{ return (type)InterlockedCompareExchange##suffix((wintype*)val, (wintype)old, (wintype)newval); } \
//MSVC doesn't know what half of the memory model thingies do. Substitute in the strong ones.
#define LOCKD_LOCKS(type, wintype, suffix) \
LOCKD_LOCKS_MODEL(type, wintype, suffix, ) \
LOCKD_LOCKS_MODEL(type, wintype, suffix, _acq) \
LOCKD_LOCKS_MODEL(type, wintype, suffix, _rel) \
LOCKD_LOCKS_MODEL(type, wintype, suffix, _loose) \
\
inline type lock_read(type * val) { return (type)InterlockedCompareExchange##suffix((wintype*)val, (wintype)0, (wintype)0); } \
inline type lock_read_acq(type * val) { return (type)InterlockedCompareExchange##suffix((wintype*)val, (wintype)0, (wintype)0); } \
inline type lock_read_loose(type * val) { return (type)InterlockedCompareExchange##suffix((wintype*)val, (wintype)0, (wintype)0); } \
inline void lock_write(type * val, type value) { (void)InterlockedExchange##suffix((wintype*)val, (wintype)value); }\
inline void lock_write_rel(type * val, type value) { (void)InterlockedExchange##suffix((wintype*)val, (wintype)value); }\
inline void lock_write_loose(type * val, type value) { (void)InterlockedExchange##suffix((wintype*)val, (wintype)value); }\
#ifdef _M_IX86
LOCKD_LOCKS(int32_t, LONG, )
LOCKD_LOCKS(uint32_t, LONG, )
LOCKD_LOCKS(void*, LONG, )
#elif defined(_M_X64)
LOCKD_LOCKS(int32_t, LONG, )
LOCKD_LOCKS(uint32_t, LONG, )
LOCKD_LOCKS(void*, LONGLONG, 64)
#endif
#endif
template<typename T> T* lock_read(T** val) { return (T*)lock_read((void**)val); }
template<typename T> void lock_write(T** val, T* newval) { lock_write((void**)val, (void*)newval); }
template<typename T> T* lock_cmpxchg(T** val, T* old, T* newval) { return (T*)lock_cmpxchg((void**)val, (void*)old, (void*)newval); }
template<typename T> T* lock_xchg(T** val, T* newval) { return (T*)lock_xchg((void**)val, (void*)newval); }
#if NULL==0
//the NULL/0 duality is one of the dumbest things I have ever seen. at least C++11 somewhat fixes that garbage
class null_only;
template<typename T> void lock_write(T** val, null_only* newval) { lock_write((void**)val, NULL); }
template<typename T> T* lock_cmpxchg(T** val, null_only* old, T* newval) { return (T*)lock_cmpxchg((void**)val, NULL, (void*)newval); }
template<typename T> T* lock_cmpxchg(T** val, T* old, null_only* newval) { return (T*)lock_cmpxchg((void**)val, (void*)old, NULL); }
template<typename T> T* lock_cmpxchg(T** val, null_only* old, null_only* newval) { return (T*)lock_cmpxchg((void**)val, NULL, NULL); }
template<typename T> T* lock_xchg(T** val, null_only* newval) { return (T*)lock_xchg((void**)val, NULL); }
#endif

250
arlib/thread/linux.cpp Normal file
View File

@ -0,0 +1,250 @@
#include "../endian.h"
#include "thread.h"
#ifdef __linux__
//I could try to rewrite all of this without pthread, but I'd rather not set up TLS stuff myself, that'd require replacing half of libc.
//However, I can remove everything except pthread_create.
//Minimum kernel version: 2.6.22 (FUTEX_PRIVATE_FLAG), released in 8 July, 2007 (source: http://kernelnewbies.org/LinuxVersions)
//Dropping the private mutex flag would drop requirements to 2.5.40, October 1, 2002.
#include <pthread.h>
#include <unistd.h>
#include <limits.h>
#include <linux/futex.h>
#include <sys/syscall.h>
#include "endian.h"
//list of synchronization points: http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap04.html#tag_04_10
struct threaddata_pthread {
function<void()> func;
};
static void * threadproc(void * userdata)
{
struct threaddata_pthread * thdat=(struct threaddata_pthread*)userdata;
thdat->func();
free(thdat);
return NULL;
}
void thread_create(function<void()> start)
{
struct threaddata_pthread * thdat=malloc(sizeof(struct threaddata_pthread));
thdat->func=start;
pthread_t thread;
if (pthread_create(&thread, NULL, threadproc, thdat)) abort();
pthread_detach(thread);
}
unsigned int thread_num_cores()
{
//for more OSes: https://qt.gitorious.org/qt/qt/source/HEAD:src/corelib/thread/qthread_unix.cpp#L411, idealThreadCount()
//or http://stackoverflow.com/questions/150355/programmatically-find-the-number-of-cores-on-a-machine
return sysconf(_SC_NPROCESSORS_ONLN);
}
void thread_sleep(unsigned int usec)
{
usleep(usec);
}
//spurious wakeups are possible
//return can tell if the wakeup is bogus, but I don't really need that
static int futex_wait(int * uaddr, int val, const struct timespec * timeout = NULL)
{
return syscall(__NR_futex, uaddr, FUTEX_WAIT_PRIVATE, val, timeout);
}
static int futex_wake(int * uaddr)
{
return syscall(__NR_futex, uaddr, FUTEX_WAKE_PRIVATE, 1);
}
static int futex_wake_all(int * uaddr)
{
return syscall(__NR_futex, uaddr, FUTEX_WAKE_PRIVATE, INT_MAX);
}
//futexes. complex threading code. fun
#define MUT_UNLOCKED 0
#define MUT_LOCKED 1
#define MUT_CONTENDED 2
void mutex::lock()
{
int result = lock_cmpxchg_acq(&fut, MUT_UNLOCKED, MUT_LOCKED);
if (LIKELY(result == MUT_UNLOCKED))
{
return; // unlocked, fast path
}
//If it was locked, mark it contended and force whoever to wake us.
//In the common contended case, it was previously MUT_LOCKED, so the futex would instantly return.
//Therefore, the xchg should be run first.
//loose is fine, since we already did an acquire above (and futex() probably performs a memory barrier).
while (true)
{
result = lock_xchg_loose(&fut, MUT_CONTENDED);
//results:
//MUT_UNLOCKED - we got it, continue
//MUT_CONTENDED - didn't get it, sleep for a while
//MUT_LOCKED - someone else got it and locked it, thinking it's empty, while we're here. force it to wake us.
if (result == MUT_UNLOCKED) break;
futex_wait(&fut, MUT_CONTENDED);
}
}
bool mutex::try_lock()
{
return (lock_cmpxchg_acq(&fut, MUT_UNLOCKED, MUT_LOCKED) == MUT_UNLOCKED);
}
void mutex::unlock()
{
int result = lock_xchg_rel(&fut, MUT_UNLOCKED);
if (UNLIKELY(result == MUT_CONTENDED))
{
futex_wake(&fut);
}
}
#define ONCE_NEW_I 0
#define ONCE_ONE_I 1
#define ONCE_CONTENDED_I 2
#define ONCE_NEW (void*)ONCE_NEW_I
#define ONCE_ONE (void*)ONCE_ONE_I
#define ONCE_CONTENDED (void*)ONCE_CONTENDED_I
//This is a fair bit shorter than the generic thread_once. And it doesn't have the objects-holding-up-each-other bug either.
//I could use Windows 8 WaitOnAddress for this, but I still (1) don't want to make 8-only binaries (2) don't have an 8.
//
//That is, it would be, if a futex was pointer rather than int. Ah well, at least it loses the bug.
void* thread_once_core(void* * item, function<void*()> calculate)
{
void* rd = *item;
//common case - initialized already
//not using an atomic read because stale values are fine, they're caught by the cmpxchg
if (rd!=ONCE_NEW && rd!=ONCE_ONE && rd!=ONCE_CONTENDED) return rd;
void* old = lock_cmpxchg(item, ONCE_NEW, ONCE_ONE);
if (old == ONCE_NEW)
{
void* result = calculate();
//'item' is either ONE or CONTENDED here.
//It's not NEW because we wrote ONE, and it can't be anything else
// because the other threads know that they're only allowed to replace it with CONTENDED.
if (lock_xchg(item, result) != ONCE_ONE)
{
futex_wake_all((ENDIAN==END_BIG)+(int*)item);
}
return result;
}
else if (old == ONCE_ONE || old == ONCE_CONTENDED)
{
lock_cmpxchg(item, ONCE_ONE, ONCE_CONTENDED);
//the timeout is necessary so we don't risk deadlocks if
//- we're on a 64bit platform
//- calculate() returns (void*)0x????????00000002 (or, on a big endian system, 0x00000002????????)
//- it's swapped in between cmpxchg(NEW->ONE) and the futex checks it
//due to the extremely low likelihood of #2, and #3 also being pretty unlikely, the timeout is
// set high (by computer standards), to 16ms.
//poking ENDIAN like that is necessary for similar reasons.
struct timespec timeout;
timeout.tv_sec = 0;
timeout.tv_nsec = 16*1000*1000;
while (true)
{
futex_wait((ENDIAN==END_BIG)+(int*)item, ONCE_CONTENDED_I, &timeout);
void* val = lock_read(item);
if (val != ONCE_CONTENDED) return val;
}
}
else return old;
}
//stuff I should rewrite follows
#include <semaphore.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
event::event()
{
this->data=malloc(sizeof(sem_t));
sem_init((sem_t*)this->data, 0, 0);
}
event::~event()
{
sem_destroy((sem_t*)this->data);
free(this->data);
}
void event::signal()
{
if (!this->signalled()) sem_post((sem_t*)this->data);
}
void event::wait()
{
sem_wait((sem_t*)this->data);
}
bool event::signalled()
{
int active;
sem_getvalue((sem_t*)this->data, &active);
return (active>0);
}
multievent::multievent()
{
this->data=malloc(sizeof(sem_t));
sem_init((sem_t*)this->data, 0, 0);
}
multievent::~multievent()
{
sem_destroy((sem_t*)this->data);
free(this->data);
}
void multievent::signal(unsigned int count)
{
while (count--) sem_post((sem_t*)this->data);
}
void multievent::wait(unsigned int count)
{
while (count--) sem_wait((sem_t*)this->data);
}
signed int multievent::count()
{
int active;
sem_getvalue((sem_t*)this->data, &active);
return active;
}
uintptr_t thread_get_id()
{
//disassembly:
//jmpq 0x400500 <pthread_self@plt>
//jmpq *0x200b22(%rip) # 0x601028 <pthread_self@got.plt>
//mov %fs:0x10,%rax
//retq
//(it's some big mess the first time, apparently the dependency is dynamically loaded)
return pthread_self();
}
#endif

63
arlib/thread/once.cpp Normal file
View File

@ -0,0 +1,63 @@
#include "thread.h"
//a nonatomic read to an atomic variable is safe only if correct results are guaranteed if any old value is read
//a write of non-NULL and non-tag is guaranteed to be the final write, and if anything else seems to be there, we do an atomic read
void* thread_once_undo_core(void* * item, function<void*()> calculate, function<void(void*)> undo)
{
if (*item) return *item;//nonatomic - if something weird happens, all that happens is that another item is created and deleted.
void* obj = calculate();
void* prev = lock_cmpxchg(item, NULL, obj);
if (prev == NULL) return obj;
else
{
undo(obj);
return prev;
}
}
#ifndef __linux__
static event* contention_unlocker=NULL;
#if 1 //if NULL==0 and points to a permanently reserved area of at least 3 bytes (the limit is 65536 on all modern OSes)
#define MAKE_TAG(n) ((void*)(n+1))
#else //assume sizeof(obj*)>=2 - no other thread can return this, they don't know where it is
#define MAKE_TAG(n) (void*)(((char*)&contention_unlocker)+n)
#endif
#define tag_busy MAKE_TAG(0)
#define tag_contended MAKE_TAG(1)
//Bug: If two objects are simultaneously initialized by two threads each, then one of the objects may hold up the other.
//This is not fixable without borrowing at least one bit from the item, which we don't want to do; alternatively waking all waiters, which can't be done either.
void* thread_once_core(void* * item, function<void*()> calculate)
{
void* check=*item;
//common case - initialized already
//not using an atomic read because stale values are fine, they're caught by the cmpxchg
if (check != NULL && check != tag_busy && check != tag_contended) return check;
void* old = lock_cmpxchg(item, NULL, tag_busy);
if (old == NULL)
{
void* result = calculate();
//'written' is either tag_busy or tag_contended here.
//It's not NULL because we wrote tag_busy, and it can't be anything else
// because the other threads know that they're only allowed to replace it with tag_contended.
if (lock_cmpxchg(item, tag_busy, result) == tag_contended)
{
thread_once_create(&contention_unlocker);
lock_write(item, result);
contention_unlocker->signal();
}
}
else if (old == tag_busy || old == tag_contended)
{
//don't bother optimizing this, contention only happens a few times during program lifetime
lock_cmpxchg(item, tag_busy, tag_contended);
thread_once_create(&contention_unlocker);
while (lock_read(item) == tag_busy) contention_unlocker->wait();
contention_unlocker->signal();
}
//it's possible to hit neither of the above if the object was written between the initial read and the swap
return *item;
}
#endif

195
arlib/thread/pthread.cpp Normal file
View File

@ -0,0 +1,195 @@
#include "thread.h"
#if defined(__unix__) && !defined(__linux__)
#include <pthread.h>
#include <semaphore.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
//list of synchronization points: http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap04.html#tag_04_10
struct threaddata_pthread {
function<void()> func;
};
static void * threadproc(void * userdata)
{
struct threaddata_pthread * thdat=(struct threaddata_pthread*)userdata;
thdat->func();
free(thdat);
return NULL;
}
void thread_create(function<void()> start)
{
struct threaddata_pthread * thdat=malloc(sizeof(struct threaddata_pthread));
thdat->func=start;
pthread_t thread;
if (pthread_create(&thread, NULL, threadproc, thdat)) abort();
pthread_detach(thread);
}
unsigned int thread_num_cores()
{
//for more OSes: https://qt.gitorious.org/qt/qt/source/HEAD:src/corelib/thread/qthread_unix.cpp#L411, idealThreadCount()
//or http://stackoverflow.com/questions/150355/programmatically-find-the-number-of-cores-on-a-machine
return sysconf(_SC_NPROCESSORS_ONLN);
}
mutex* mutex::create()
{
pthread_mutex_t* ret=malloc(sizeof(pthread_mutex_t));
pthread_mutex_init(ret, NULL);
return (mutex*)ret;
}
void mutex::lock()
{
pthread_mutex_lock((pthread_mutex_t*)this);
}
bool mutex::try_lock()
{
return (pthread_mutex_trylock((pthread_mutex_t*)this)==0);
}
void mutex::unlock()
{
pthread_mutex_unlock((pthread_mutex_t*)this);
}
void mutex::release()
{
pthread_mutex_destroy((pthread_mutex_t*)this);
free(this);
}
//now I have to write futex code myself! How fun!
void mutex2::lock()
{
#error not implemented yet
}
bool mutex2::try_lock()
{
}
void mutex2::unlock()
{
}
event::event()
{
this->data=malloc(sizeof(sem_t));
sem_init((sem_t*)this->data, 0, 0);
}
event::~event()
{
sem_destroy((sem_t*)this->data);
free(this->data);
}
void event::signal()
{
if (!this->signalled()) sem_post((sem_t*)this->data);
}
void event::wait()
{
sem_wait((sem_t*)this->data);
}
bool event::signalled()
{
int active;
sem_getvalue((sem_t*)this->data, &active);
return (active>0);
}
multievent::multievent()
{
this->data=malloc(sizeof(sem_t));
sem_init((sem_t*)this->data, 0, 0);
}
multievent::~multievent()
{
sem_destroy((sem_t*)this->data);
free(this->data);
}
void multievent::signal(unsigned int count)
{
while (count--) sem_post((sem_t*)this->data);
}
void multievent::wait(unsigned int count)
{
while (count--) sem_wait((sem_t*)this->data);
}
signed int multievent::count()
{
int active;
sem_getvalue((sem_t*)this->data, &active);
return active;
}
uintptr_t thread_get_id()
{
//disassembly:
//jmpq 0x400500 <pthread_self@plt>
//jmpq *0x200b22(%rip) # 0x601028 <pthread_self@got.plt>
//mov %fs:0x10,%rax
//retq
//(it's some big mess the first time, apparently the dependency is dynamically loaded)
return pthread_self();
}
//pthread doesn't seem to contain anything like this, but gcc is the only supported compiler here, so I can use its builtins.
//or if I get any non-gcc compilers, I can throw in the C++11 threads. That's why these builtins exist, anyways.
//for Clang, if these GCC builtins aren't supported (most are), http://clang.llvm.org/docs/LanguageExtensions.html#c11-atomic-builtins
#if __GNUC__*10000 + __GNUC_MINOR__*100 + __GNUC_PATCHLEVEL__*1 >= 40700
//https://gcc.gnu.org/onlinedocs/gcc-4.7.0/gcc/_005f_005fatomic-Builtins.html
uint32_t lock_incr(uint32_t * val) { return __atomic_add_fetch(val, 1, __ATOMIC_ACQ_REL); }
uint32_t lock_decr(uint32_t * val) { return __atomic_sub_fetch(val, 1, __ATOMIC_ACQ_REL); }
uint32_t lock_read(uint32_t * val) { return __atomic_load_n(val, __ATOMIC_ACQUIRE); }
void* lock_read_i(void* * val) { return __atomic_load_n(val, __ATOMIC_ACQUIRE); }
void lock_write_i(void** val, void* newval) { return __atomic_store_n(val, newval, __ATOMIC_RELEASE); }
//there is a modern version of this, but it adds another move instruction for whatever reason and otherwise gives the same binary.
void* lock_write_eq_i(void** val, void* old, void* newval) { return __sync_val_compare_and_swap(val, old, newval); }
//void* lock_write_eq_i(void** val, void* old, void* newval)
//{
// __atomic_compare_exchange_n(val, &old, newval, false, __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE);
// return old;
//}
void* lock_xchg_i(void** val, void* newval) { return __atomic_exchange_n(val, newval, __ATOMIC_ACQ_REL); }
#else
//https://gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Atomic-Builtins.html
uint32_t lock_incr(uint32_t * val) { return __sync_add_and_fetch(val, 1); }
uint32_t lock_decr(uint32_t * val) { return __sync_sub_and_fetch(val, 1); }
uint32_t lock_read(uint32_t * val) { return __sync_val_compare_and_swap(val, 0, 0); }
inline void* lock_read_i(void* * val) { return __sync_val_compare_and_swap(val, 0, 0); }
void lock_write_i(void** val, void* newval) { *val=newval; __sync_synchronize(); }
void* lock_write_eq_i(void** val, void* old, void* newval) { return __sync_val_compare_and_swap(val, old, newval); }
//no such thing - emulate it
void* lock_xchg_i(void** val, void* newval)
{
void* prev=lock_read(val);
while (true)
{
void* prev2=lock_write_eq(val, prev, newval);
if (prev==prev2) break;
else prev=prev2;
}
}
#endif
#endif

93
arlib/thread/split.cpp Normal file
View File

@ -0,0 +1,93 @@
#include "thread.h"
namespace {
//TODO: there is no procedure for destroying threads
struct threadpool {
mutex lock;
multievent* wake;
multievent* started;
uint32_t numthreads;
uint32_t numidle;
//these vary between each piece of work
function<void(unsigned int id)> work;
uint32_t id;
multievent* done;
};
static struct threadpool * pool;
void threadproc(struct threadpool * This)
{
while (true)
{
This->wake->wait();
lock_decr(&This->numidle);
function<void(unsigned int id)> work = This->work;
unsigned int id = lock_incr(&This->id);
multievent* done = This->done;
This->started->signal();
work(id);
done->signal();
lock_incr(&This->numidle);
}
}
struct threadpool* pool_create()
{
struct threadpool * pool = new threadpool;
pool->wake=new multievent();
pool->started=new multievent();
pool->numthreads=0;
pool->numidle=0;
return pool;
}
void pool_delete(struct threadpool* pool)
{
delete pool->wake;
delete pool->started;
delete pool;
}
}
void thread_split(unsigned int count, function<void(unsigned int id)> work)
{
if (!count) return;
if (count==1)
{
work(0);
return;
}
struct threadpool * This = thread_once_undo(&pool, bind(pool_create), bind(pool_delete));
This->lock.lock();
multievent* done=new multievent();
This->work=work;
This->id=1;
This->done=done;
while (lock_read(&This->numidle) < count-1)
{
This->numthreads++;
lock_incr(&This->numidle);
thread_create(bind_ptr(threadproc, This));
}
This->wake->signal(count-1);
This->started->wait(count-1);
This->lock.unlock();
work(0);
done->wait(count-1);
delete done;
}

201
arlib/thread/thread.h Normal file
View File

@ -0,0 +1,201 @@
#pragma once
#include "../global.h"
#ifdef ARLIB_THREAD
//Any data associated with this thread is freed once the thread procedure returns.
//It is safe to malloc() something in one thread and free() it in another.
//It is not safe to call window_run_*() from a thread other than the one entering main().
//A thread is rather heavy; for short-running jobs, use thread_create_short or thread_split.
void thread_create(function<void()> start);
//Returns the number of threads to create to utilize the system resources optimally.
unsigned int thread_num_cores();
#include "atomic.h"
#include <string.h>
//This is a simple tool that ensures only one thread is doing a certain action at a given moment.
//Memory barriers are inserted as appropriate. Any memory access done while holding a lock is finished while holding this lock.
//This means that if all access to an object is done exclusively while holding the lock, no further synchronization is needed.
//It is not allowed for a thread to call lock() or try_lock() while holding the lock already. It is not allowed
// for a thread to release the lock unless it holds it. It is not allowed to delete the lock while it's held.
//However, it it allowed to hold multiple locks simultaneously.
//lock() is not guaranteed to yield the CPU if it can't grab the lock. It may be implemented as a
// busy loop, or a hybrid scheme that spins a few times and then sleeps.
//Remember to create all relevant mutexes before creating a thread.
class mutex : nocopy {
#if defined(__linux__)
int fut = 0;
public:
//TODO: inline fast path
void lock();
bool try_lock();
void unlock();
#elif defined(__unix__)
#error enable thread/pthread.cpp
#elif _WIN32_WINNT >= 0x0600
#if !defined(_MSC_VER) || _MSC_VER > 1600
SRWLOCK srwlock = SRWLOCK_INIT;
#else
// apparently MSVC2008 doesn't understand struct S item = {0}. let's do something it does understand and hope it's optimized out.
SRWLOCK srwlock;
public:
mutex() { srwlock.Ptr = NULL; } // and let's hope MS doesn't change the definition of RTL_SRWLOCK.
#endif
//I could define a path for Windows 8+ that uses WaitOnAddress to shrink it to one single byte, but
//(1) The more code paths, the more potential for bugs, especially the code paths I don't regularly test
//(2) Saving seven bytes is pointless, a mutex is for protecting other resources and they're bigger
//(3) Microsoft's implementation is probably better optimized
//(4) I can't test it without a machine running 8 or higher, and I don't have that.
public:
void lock() { AcquireSRWLockExclusive(&srwlock); }
bool try_lock() { return TryAcquireSRWLockExclusive(&srwlock); }
void unlock() { ReleaseSRWLockExclusive(&srwlock); }
#elif _WIN32_WINNT >= 0x0501
CRITICAL_SECTION cs;
public:
//yay, initializers. no real way to avoid them here.
mutex() { InitializeCriticalSection(&cs); }
void lock() { EnterCriticalSection(&cs); }
bool try_lock() { return TryEnterCriticalSection(&cs); }
void unlock() { LeaveCriticalSection(&cs); }
~mutex() { DeleteCriticalSection(&cs); }
#endif
};
//Some shenanigans: gcc throws errors about strict-aliasing rules if I don't force its hand, and most
// implementations aren't correctly optimized (they leave copies on the stack).
//This is one of few that confuse the optimizer exactly as much as I want.
template<typename T> char* allow_alias(T* ptr) { return (char*)ptr; }
//Executes 'calculate' exactly once. The return value is stored in 'item'. If multiple threads call
// this simultaneously, none returns until calculate() is done.
//'item' must be initialized to NULL. calculate() must return a valid pointer to an object.
// 'return new mutex;' is valid, as is returning the address of something static.
//Non-pointers, such as (void*)1, are not allowed.
//Returns *item.
void* thread_once_core(void* * item, function<void*()> calculate);
template<typename T> T* thread_once(T* * item, function<T*()> calculate)
{
return (T*)thread_once_core((void**)item, *(function<void*()>*)allow_alias(&calculate));
}
//This is like thread_once, but calculate() can be called multiple times. If this happens, undo()
//will be called for all except one; the last one will be returned.
void* thread_once_undo_core(void* * item, function<void*()> calculate, function<void(void*)> undo);
template<typename T> T* thread_once_undo(T* * item, function<T*()> calculate, function<void(T*)> undo)
{
return (T*)thread_once_undo_core((void**)item,
*(function<void*()>*)allow_alias(&calculate),
*(function<void(void*)>*)allow_alias(&undo));
}
//This function is a workaround for a GCC bug. Don't call it yourself.
template<void*(*create)(), void(*undo)(void*)> void* thread_once_create_gccbug(void* * item)
{
return thread_once_undo(item, bind(create), bind(undo));
}
//Simple convenience function, just calls the above.
template<typename T> T* thread_once_create(T* * item)
{
return (T*)thread_once_create_gccbug<generic_new_void<T>, generic_delete_void<T> >((void**)item);
}
class mutexlocker : nocopy {
mutexlocker();
mutex* m;
public:
mutexlocker(mutex* m) { this->m=m; this->m->lock(); }
~mutexlocker() { this->m->unlock(); }
};
#define synchronized(mutex) with(mutexlocker LOCK(mutex))
//This one lets one thread wake another.
//The conceptual difference between this and a mutex is that while a mutex is intended to protect a
// shared resource from being accessed simultaneously, an event is intended to wait until another
// thread is done with something. A mutex is unlocked on the same thread as it's locked; an event is
// unlocked on a different thread.
//An example would be a producer-consumer scenario; if one thread is producing 200 items per second,
// and another thread processes them at 100 items per second, then there will soon be a lot of
// waiting items. An event allows the consumer to ask the producer to get to work, so it'll spend
// half of its time sleeping, instead of filling the system memory.
//An event is boolean; calling signal() twice will drop the extra signal. It is created in the unsignalled state.
//Can be used by multiple threads, but each of signal(), wait() and signalled() should only be used by one thread.
class event : nocopy {
public:
event();
~event();
void signal();
void wait();
bool signalled();
private:
void* data;
};
//This is like event, but it allows setting the event multiple times.
class multievent {
public:
multievent();
~multievent();
//count is how many times to signal or wait. Calling it multiple times is equivalent to calling it with the sum of the arguments.
void signal(unsigned int count=1);
void wait(unsigned int count=1);
//This is how many signals are waiting to be wait()ed for. Can be below zero if something is currently waiting for this event.
//Alternate explaination: Increased for each entry to signal() and decreased for each entry to wait().
signed int count();
private:
void* data;
signed int n_count;//Not used by all implementations.
};
void thread_sleep(unsigned int usec);
//Returns a value that's unique to the current thread for as long as the process lives. Does not
// necessarily have any relationship to OS-level thread IDs, but usually is.
//This just forwards to somewhere in libc or kernel32 or something, but it's so rarely called it doesn't matter.
size_t thread_get_id();
//This one creates 'count' threads, calls work() in each of them with 'id' from 0 to 'count'-1, and
// returns once each thread has returned.
//Unlike thread_create, thread_split is expected to be called often, for short-running tasks. The threads may be reused.
//It is safe to use the values 0 and 1. However, you should avoid going above thread_ideal_count().
void thread_split(unsigned int count, function<void(unsigned int id)> work);
//It is permitted to define this as (e.g.) QThreadStorage<T> rather than compiler magic.
//However, it must support operator=(T) and operator T(), so QThreadStorage is not directly usable. A subclass may be.
//An implementation must support all stdint.h types, all basic integral types (char, short, etc), and all pointers.
#ifdef __GNUC__
#define THREAD_LOCAL(t) __thread t
#endif
#ifdef _MSC_VER
#define THREAD_LOCAL(t) __declspec(thread) t
#endif
#else
//Some parts of arlib want to work with threads disabled.
class mutex : nocopy {
public:
void lock() {}
bool try_lock() { return true; }
void unlock() { }
};
#endif

102
arlib/thread/win32.cpp Normal file
View File

@ -0,0 +1,102 @@
#include "thread.h"
#ifdef _WIN32
#undef bind
#include <windows.h>
#define bind bind_func
#include <stdlib.h>
#include <string.h>
//list of synchronization points: http://msdn.microsoft.com/en-us/library/windows/desktop/ms686355%28v=vs.85%29.aspx
struct threaddata_win32 {
function<void()> func;
};
static DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
struct threaddata_win32 * thdat=(struct threaddata_win32*)lpParameter;
thdat->func();
free(thdat);
return 0;
}
void thread_create(function<void()> start)
{
struct threaddata_win32 * thdat=malloc(sizeof(struct threaddata_win32));
thdat->func=start;
//CreateThread is not listed as a synchronization point; it probably is, but I'd rather use a pointless
// operation than risk a really annoying bug. It's lightweight compared to creating a thread, anyways.
//MemoryBarrier();//gcc lacks this, and msvc lacks the gcc builtin I could use instead.
//And of course my gcc supports only ten out of the 137 InterlockedXxx functions. Let's pick the simplest one...
LONG ignored=0;
InterlockedIncrement(&ignored);
HANDLE h=CreateThread(NULL, 0, ThreadProc, thdat, 0, NULL);
if (!h) abort();
CloseHandle(h);
}
unsigned int thread_num_cores()
{
SYSTEM_INFO sysinf;
GetSystemInfo(&sysinf);
return sysinf.dwNumberOfProcessors;
}
void thread_sleep(unsigned int usec)
{
Sleep(usec/1000);
}
//rewritables follow
event::event() { data=(void*)CreateEvent(NULL, false, false, NULL); }
void event::signal() { SetEvent((HANDLE)this->data); }
void event::wait() { WaitForSingleObject((HANDLE)this->data, INFINITE); }
bool event::signalled() { if (WaitForSingleObject((HANDLE)this->data, 0)==WAIT_OBJECT_0) { SetEvent((HANDLE)this->data); return true; } else return false; }
event::~event() { CloseHandle((HANDLE)this->data); }
multievent::multievent()
{
this->data=(void*)CreateSemaphore(NULL, 0, 127, NULL);
this->n_count=0;
}
void multievent::signal(unsigned int count)
{
InterlockedExchangeAdd((volatile LONG*)&this->n_count, count);
ReleaseSemaphore((HANDLE)this->data, count, NULL);
}
void multievent::wait(unsigned int count)
{
InterlockedExchangeAdd((volatile LONG*)&this->n_count, -(LONG)count);
while (count)
{
WaitForSingleObject((HANDLE)this->data, INFINITE);
count--;
}
}
signed int multievent::count()
{
return InterlockedCompareExchange((volatile LONG*)&this->n_count, 0, 0);
}
multievent::~multievent() { CloseHandle((HANDLE)this->data); }
uintptr_t thread_get_id()
{
//disassembly:
//call *0x406118
//jmp 0x76c11427 <KERNEL32!GetCurrentThreadId+7>
//jmp *0x76c1085c
//mov %fs:0x10,%eax
//mov 0x24(%eax),%eax
//ret
return GetCurrentThreadId();
}
#endif

1
arlib/wutf.h Normal file
View File

@ -0,0 +1 @@
#include "wutf/wutf.h"

545
arlib/wutf/wutf-conv.cpp Normal file
View File

@ -0,0 +1,545 @@
// MIT License
//
// Copyright (c) 2016 Alfred Agrell
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// 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 OR COPYRIGHT HOLDERS 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.
//The above license applies only to this file, not the entire Arlib.
//See wutf.h for documentation.
#include "wutf.h"
static int decode(uint8_t head, const uint8_t* * tailptr, const uint8_t* end)
{
const int minforlen[] = { 0x7FFFFFFF, 0x80, 0x800, 0x10000, 0x7FFFFFFF };
const uint8_t * tail = *tailptr;
int numtrail = ((head&0xC0)==0xC0) + ((head&0xE0)==0xE0) + ((head&0xF0)==0xF0) + ((head&0xF8)==0xF8);
int codepoint = (head & (0x3F>>numtrail));
int i;
if (tail + numtrail > end) return -1;
for (i=0;i<3;i++)
{
if (numtrail>i)
{
if ((tail[i] & 0xC0) != 0x80) return -1;
codepoint = (codepoint<<6) | (tail[i] & 0x3F);
}
}
if (codepoint < minforlen[numtrail]) return -1;
*tailptr += numtrail;
return codepoint;
}
static int utf8_to_utf16_len(int flags, const uint8_t* ptr, const uint8_t* end)
{
int ret = 0;
const uint8_t* at = ptr;
while (at < end)
{
uint8_t head = *at++;
if (head <= 0x7F)
{
ret++;
continue;
}
uint32_t codepoint = (uint32_t)decode(head, &at, end);
ret++;
if ((codepoint&0xF800) == 0xD800 && !(flags & WUTF_WTF8))
{
if ((flags & WUTF_CESU8) && (codepoint&0xFC00) == 0xD800 &&
at+3 <= end && at[0]==0xED && (at[1]&0xF0)==0xB0 && (at[2]&0xC0)==0x80)
{
at+=3;
ret++;
}
else
{
at-=2;
goto fail;
}
}
if (codepoint>0x10FFFF)
{
if (codepoint != (uint32_t)-1) at-=3; // restore the tail
fail:
if ((flags&WUTF_INVALID_MASK) == WUTF_INVALID_ABORT) return WUTF_E_INVALID;
if ((flags&WUTF_INVALID_MASK) == WUTF_INVALID_DROP) ret--;
}
else if (codepoint>=0x10000) ret++; // surrogate
}
return ret;
}
static const uint8_t* utf8_end(const uint8_t* utf8, int utf8_len)
{
if (utf8_len >= 0)
{
return utf8 + utf8_len;
}
else
{
while (*utf8) utf8++;
utf8++; // go behind the NUL
return utf8;
}
}
int WuTF_utf8_to_utf16(int flags, const char* utf8, int utf8_len, uint16_t* utf16, int utf16_len)
{
if ((flags&WUTF_WTF8) && (flags&WUTF_CESU8)) return WUTF_E_INVALID;
if ((flags&WUTF_WTF8) && (flags&WUTF_INVALID_MASK)==WUTF_INVALID_DCXX) return WUTF_E_INVALID;
//I could run a bunch of special cases depending on whether cbMultiByte<0, etc,
//but there's no need. I'll optimize it if it ends up a bottleneck.
const uint8_t* iat = (const uint8_t*)utf8;
const uint8_t* iend = utf8_end(iat, utf8_len);
if (utf16_len == 0)
{
return utf8_to_utf16_len(flags, iat, iend);
}
uint16_t* oat = utf16;
uint16_t* oend = oat + utf16_len;
while (iat < iend)
{
if (oat+1 > oend) break;
uint8_t head = *iat++;
if (head <= 0x7F)
{
*oat++ = head;
continue;
}
uint32_t codepoint = (uint32_t)decode(head, &iat, iend); // -1 -> 0xFFFFFFFF
if (codepoint <= 0xFFFF)
{
if ((codepoint&0xF800) == 0xD800 && !(flags & WUTF_WTF8))
{
if ((flags & WUTF_CESU8) && (codepoint&0xFC00) == 0xD800 &&
iat+3 <= iend && iat[0]==0xED && (iat[1]&0xF0)==0xB0 && (iat[2]&0xC0)==0x80)
{
*oat++ = codepoint;
codepoint = (uint32_t)decode(*iat++, &iat, iend);
}
else
{
iat-=2;
goto fail;
}
}
*oat++ = codepoint;
}
else
{
if (codepoint > 0x10FFFF)
{
if (codepoint != (uint32_t)-1) iat-=3; // restore the tail
fail:
switch (flags & WUTF_INVALID_MASK)
{
case WUTF_INVALID_ABORT: return WUTF_E_INVALID;
case WUTF_INVALID_DROP: break;
case WUTF_INVALID_FFFD: *oat++ = 0xFFFD; break;
case WUTF_INVALID_DCXX: *oat++ = 0xDC00 + head; break;
}
continue;
}
if (oat+2 > oend) break;
codepoint -= 0x10000;
*oat++ = 0xD800 | (codepoint>>10);
*oat++ = 0xDC00 | (codepoint&0x3FF);
}
}
if (iat != iend)
{
if (!(flags & WUTF_TRUNCATE)) return WUTF_E_TRUNCATE;
while (oat < oend) *oat++ = 0xFFFD;
}
return oat - utf16;
}
static int utf8_to_utf32_len(int flags, const uint8_t* ptr, const uint8_t* end)
{
int ret = 0;
const uint8_t* at = ptr;
while (at < end)
{
uint8_t head = *at++;
if (head <= 0x7F)
{
ret++;
continue;
}
uint32_t codepoint = (uint32_t)decode(head, &at, end);
ret++;
if (codepoint>0x10FFFF)
{
switch (flags & WUTF_INVALID_MASK)
{
case WUTF_INVALID_ABORT: return WUTF_E_INVALID;
case WUTF_INVALID_DROP: ret--; break;
case WUTF_INVALID_FFFD: break;
case WUTF_INVALID_DCXX: break;
}
}
//identical to utf16_len, just discard the surrogate check
}
return ret;
}
int WuTF_utf8_to_utf32(int flags, const char* utf8, int utf8_len, uint32_t* utf32, int utf32_len)
{
if (flags&WUTF_CESU8) return WUTF_E_INVALID;
if ((flags&WUTF_WTF8) && (flags&WUTF_INVALID_MASK)==WUTF_INVALID_DCXX) return WUTF_E_INVALID;
const uint8_t* iat = (const uint8_t*)utf8;
const uint8_t* iend = utf8_end(iat, utf8_len);
if (utf32_len == 0)
{
return utf8_to_utf32_len(flags, iat, iend);
}
uint32_t* oat = utf32;
uint32_t* oend = oat + utf32_len;
while (iat < iend)
{
if (oat+1 > oend) break;
uint8_t head = *iat++;
if (head <= 0x7F)
{
*oat++ = head;
continue;
}
uint32_t codepoint = (uint32_t)decode(head, &iat, iend); // -1 -> 0xFFFFFFFF
if (!(flags&WUTF_WTF8) && (codepoint&0xFFFFF800) == 0xD800)
{
if ((flags & WUTF_INVALID_MASK) == WUTF_INVALID_ABORT) return WUTF_E_INVALID;
if ((flags & WUTF_INVALID_MASK) == WUTF_INVALID_DROP) continue;
if ((flags & WUTF_INVALID_MASK) == WUTF_INVALID_FFFD) {}
if ((flags & WUTF_INVALID_MASK) == WUTF_INVALID_DCXX)
{
if ((head&0xFF00)==0xDC00) {}
else return WUTF_E_INVALID;
}
}
if (codepoint > 0x10FFFF)
{
switch (flags & WUTF_INVALID_MASK)
{
case WUTF_INVALID_ABORT: return WUTF_E_INVALID;
case WUTF_INVALID_DROP: break;
case WUTF_INVALID_FFFD: *oat++ = 0xFFFD; break;
case WUTF_INVALID_DCXX: *oat++ = 0xDC00 + head; break;
}
continue;
}
*oat++ = codepoint;
}
if (iat != iend)
{
if (!(flags & WUTF_TRUNCATE)) return WUTF_E_TRUNCATE;
while (oat < oend) *oat++ = 0xFFFD;
}
return oat - utf32;
}
static int utf16_to_utf8_len(int flags, const uint16_t* ptr, const uint16_t* end)
{
int ret = 0;
const uint16_t* at = ptr;
while (at < end)
{
uint16_t head = *at++;
ret++;
if (head >= 0x80) ret++;
if (head >= 0x0800)
{
ret++;
if ((head&0xF800)==0xD800)
{
if (head<=0xDBFF && at < end && *at >= 0xDC00 && *at <= 0xDFFF)
{
at++;
ret++;
continue;
}
if (!(flags & WUTF_WTF8))
{
if ((flags & WUTF_INVALID_MASK) == WUTF_INVALID_ABORT) return WUTF_E_INVALID;
if ((flags & WUTF_INVALID_MASK) == WUTF_INVALID_DROP) ret--; continue;
if ((flags & WUTF_INVALID_MASK) == WUTF_INVALID_FFFD) continue;
if ((flags & WUTF_INVALID_MASK) == WUTF_INVALID_DCXX)
{
if ((head&0xFF00)==0xDC00) continue;
else return WUTF_E_INVALID;
}
}
}
}
}
return ret;
}
static const uint16_t* utf16_end(const uint16_t* utf16, int utf16_len)
{
if (utf16_len >= 0)
{
return utf16 + utf16_len;
}
else
{
while (*utf16) utf16++;
utf16++; // go behind the NUL
return utf16;
}
}
int WuTF_utf16_to_utf8(int flags, const uint16_t* utf16, int utf16_len, char* utf8, int utf8_len)
{
if (flags&WUTF_CESU8) return WUTF_E_INVALID;
if ((flags&WUTF_WTF8) && (flags&WUTF_INVALID_MASK) == WUTF_INVALID_DCXX) return WUTF_E_INVALID;
const uint16_t* iat = utf16;
const uint16_t* iend = utf16_end(iat, utf16_len);
if (utf8_len == 0)
{
return utf16_to_utf8_len(flags, iat, iend);
}
uint8_t* oat = (uint8_t*)utf8;
uint8_t* oend = oat + utf8_len;
while (iat < iend)
{
uint16_t head = *iat++;
if (head <= 0x7F)
{
if (oat+1 > oend) break;
*oat++ = head;
}
else if (head <= 0x07FF)
{
if (oat+2 > oend) break;
*oat++ = (((head>> 6) )|0xC0);
*oat++ = (((head )&0x3F)|0x80);
}
else
{
if ((head&0xF800)==0xD800)
{
if (head<=0xDBFF && iat < iend)
{
uint16_t tail = *iat;
if (tail >= 0xDC00 && tail <= 0xDFFF)
{
iat++;
if (oat+4 > oend) break;
uint32_t codepoint = 0x10000+((head&0x03FF)<<10)+(tail&0x03FF);
*oat++ = (((codepoint>>18)&0x07)|0xF0);
*oat++ = (((codepoint>>12)&0x3F)|0x80);
*oat++ = (((codepoint>>6 )&0x3F)|0x80);
*oat++ = (((codepoint )&0x3F)|0x80);
continue;
}
}
if (!(flags & WUTF_WTF8))
{
if ((flags & WUTF_INVALID_MASK) == WUTF_INVALID_ABORT) return WUTF_E_INVALID;
if ((flags & WUTF_INVALID_MASK) == WUTF_INVALID_DROP) continue;
if ((flags & WUTF_INVALID_MASK) == WUTF_INVALID_FFFD) { head = 0xFFFD; continue; }
if ((flags & WUTF_INVALID_MASK) == WUTF_INVALID_DCXX)
{
if ((head&0xFF00)==0xDC00)
{
if (oat+1 > oend) break;
*oat++ = (head&0x00FF); // don't bother ensuring that this ends up as invalid utf8, too much effort
continue;
}
else
{
return WUTF_E_INVALID;
}
}
}
}
if (oat+3 > oend) break;
*oat++ = (((head>>12)&0x0F)|0xE0);
*oat++ = (((head>>6 )&0x3F)|0x80);
*oat++ = (((head )&0x3F)|0x80);
}
}
if (iat != iend)
{
if (!(flags & WUTF_TRUNCATE)) return WUTF_E_TRUNCATE;
while (oat < oend) *oat++ = '?'; // (probably) can't fit a U+FFFD, just shove in something
}
return oat - (uint8_t*)utf8;
}
static int utf32_to_utf8_len(int flags, const uint32_t* ptr, const uint32_t* end)
{
int ret = 0;
const uint32_t* at = ptr;
while (at < end)
{
uint32_t head = *at++;
ret++;
if (head >= 0x80) ret++;
if (head >= 0x0800) ret++;
if (head >= 0x10000) ret++;
if (head > 0x10FFFF)
{
switch (flags & WUTF_INVALID_MASK)
{
case WUTF_INVALID_ABORT: return WUTF_E_INVALID;
case WUTF_INVALID_DROP: ret-=4; break;
case WUTF_INVALID_FFFD: ret-=4; ret+=3; break;
case WUTF_INVALID_DCXX: ret-=4; ret+=3; break;
}
}
}
return ret;
}
static const uint32_t* utf32_end(const uint32_t* utf32, int utf32_len)
{
if (utf32_len >= 0)
{
return utf32 + utf32_len;
}
else
{
while (*utf32) utf32++;
utf32++; // go behind the NUL
return utf32;
}
}
int WuTF_utf32_to_utf8(int flags, const uint32_t* utf32, int utf32_len, char* utf8, int utf8_len)
{
if (flags&WUTF_CESU8) return WUTF_E_INVALID;
if ((flags&WUTF_INVALID_MASK) == WUTF_INVALID_DCXX) return WUTF_E_INVALID;
if (flags&WUTF_WTF8) return WUTF_E_INVALID; // TODO
if ((flags&WUTF_INVALID_MASK) != -1) return WUTF_E_INVALID; // TODO
const uint32_t* iat = utf32;
const uint32_t* iend = utf32_end(iat, utf32_len);
if (utf8_len == 0)
{
return utf32_to_utf8_len(flags, iat, iend);
}
uint8_t* oat = (uint8_t*)utf8;
uint8_t* oend = oat + utf8_len;
while (iat < iend)
{
uint32_t head = *iat++;
if (head <= 0x7F)
{
if (oat+1 > oend) break;
*oat++ = head;
}
else if (head <= 0x07FF)
{
if (oat+2 > oend) break;
*oat++ = (((head>> 6) )|0xC0);
*oat++ = (((head )&0x3F)|0x80);
}
else if (head <= 0xFFFF)
{
if (oat+3 > oend) break;
*oat++ = (((head>>12)&0x0F)|0xE0);
*oat++ = (((head>>6 )&0x3F)|0x80);
*oat++ = (((head )&0x3F)|0x80);
}
else if (head <= 0x10FFFF)
{
if (oat+4 > oend) break;
*oat++ = (((head>>18)&0x07)|0xF0);
*oat++ = (((head>>12)&0x3F)|0x80);
*oat++ = (((head>>6 )&0x3F)|0x80);
*oat++ = (((head )&0x3F)|0x80);
}
else
{
switch (flags & WUTF_INVALID_MASK)
{
case WUTF_INVALID_ABORT: return WUTF_E_INVALID;
case WUTF_INVALID_DROP: break;
case WUTF_INVALID_FFFD:
if (oat+3 <= oend)
{
*oat++ = 0xEF; // U+FFFD
*oat++ = 0xBF;
*oat++ = 0xBD;
}
break;
}
}
}
if (iat != iend)
{
if (!(flags & WUTF_TRUNCATE)) return WUTF_E_TRUNCATE;
while (oat < oend) *oat++ = '?';
}
return oat - (uint8_t*)utf8;
}

475
arlib/wutf/wutf-test.cpp Normal file
View File

@ -0,0 +1,475 @@
// MIT License
//
// Copyright (c) 2016 Alfred Agrell
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// 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 OR COPYRIGHT HOLDERS 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.
//The above license applies only to this file, not the entire Arlib.
#if 0
//You don't need this. It's just a bunch of tests for WuTF itself.
//To run: Flip the above #if, then
// $ g++ *.cpp -std=c++11 && ./a.out
// C:\> g++ *.cpp -std=c++11 -lcomdlg32 && a.exe
//The tests require C++11, for char16_t. WuTF itself is plain C.
#include "wutf.h"
#include <stdio.h>
static bool test816_core(int flags, const char* utf8, const char16_t* utf16_exp, int inlen=0, int outlen_e=0)
{
int iflags = flags & WUTF_INVALID_MASK;
if ((flags&WUTF_WTF8) && iflags==WUTF_INVALID_DCXX) return true;
if (!inlen)
{
while (utf8[inlen]) inlen++;
inlen++;
}
if (!outlen_e)
{
while (utf16_exp[outlen_e]) outlen_e++;
outlen_e++;
}
uint16_t utf16_act[128];
int outlen_a = WuTF_utf8_to_utf16(flags, utf8, inlen, utf16_act, 128);
int outpos_a=0;
int outpos_e=0;
int outlen_a2 = WuTF_utf8_to_utf16(flags, utf8, inlen, NULL, 0);
if (outlen_a != outlen_a2) { printf("Expected length %i, got %i\n", outlen_a, outlen_a2); goto fail; }
if (iflags == WUTF_INVALID_ABORT && outlen_a < 0)
{
for (int i=0;i<outlen_e;i++)
{
//printf("[%i]%.4X\n",i,utf16_exp[i]);
if ((utf16_exp[i]&0xFC00) == 0xDC00) return true;
if ((utf16_exp[i]&0xFC00) == 0xD800) i++;
}
}
while (outpos_e < outlen_e && outpos_a < outlen_a)
{
uint16_t exp = utf16_exp[outpos_e++];
uint16_t act = utf16_act[outpos_a++];
if ((flags & WUTF_WTF8) == 0)
{
if ((exp&0xFC00) == 0xDC00 && (utf16_exp[outpos_e-2]&0xFC00) != 0xD800)
{
if (iflags == WUTF_INVALID_FFFD && act == 0xFFFD) continue;
if (iflags == WUTF_INVALID_DROP) { outpos_a--; continue; }
}
}
if (exp!=act) goto fail;
}
if (outpos_e != outlen_e || outpos_a != outlen_a)
{
fail:
puts(utf8);
printf("E "); for (int i=0;i<outlen_e;i++) printf("%.4X ", utf16_exp[i]); puts("");
printf("A "); for (int i=0;i<outlen_a;i++) printf("%.4X ", utf16_act[i]); puts("");
return false;
}
return true;
}
static void test816f(int flags, const char* utf8, const char16_t* utf16_exp, int inlen=0, int outlen_e=0)
{
test816_core(flags|WUTF_INVALID_DCXX, utf8, utf16_exp, inlen, outlen_e) &&
test816_core(flags|WUTF_INVALID_FFFD, utf8, utf16_exp, inlen, outlen_e) &&
test816_core(flags|WUTF_INVALID_DROP, utf8, utf16_exp, inlen, outlen_e) &&
test816_core(flags|WUTF_INVALID_ABORT, utf8, utf16_exp, inlen, outlen_e) &&
false;
}
static void test816(const char* utf8, const char16_t* utf16_exp, int inlen=0, int outlen_e=0)
{
test816f(0, utf8, utf16_exp, inlen, outlen_e);
}
static void test168f(int flags, const char16_t* utf16, const char* utf8_exp, int inlen=0, int outlen_e=0)
{
if (!inlen)
{
while (utf16[inlen]) inlen++;
inlen++;
}
if (!outlen_e)
{
while (utf8_exp[outlen_e]) outlen_e++;
outlen_e++;
}
char utf8_act[128];
int outlen_a = WuTF_utf16_to_utf8(flags, (const uint16_t*)utf16, inlen, utf8_act, 128);
int outpos_a=0;
int outpos_e=0;
int outlen_a2 = WuTF_utf16_to_utf8(flags, (const uint16_t*)utf16, inlen, NULL, 0);
if (outlen_a != outlen_a2) { printf("Expected length %i, got %i\n", outlen_a, outlen_a2); goto fail; }
while (outpos_e < outlen_e && outpos_a < outlen_a)
{
if (utf8_exp[outpos_e++] != utf8_act[outpos_a++]) goto fail;
}
if (outpos_e != outlen_e || outpos_a != outlen_a)
{
fail:
for (int i=0;i<inlen;i++) printf("%.4X ", utf16[i]); puts("");
printf("E "); for (int i=0;i<outlen_e;i++) printf("%.2X ", utf8_exp[i]&255); puts("");
printf("A "); for (int i=0;i<outlen_a;i++) printf("%.2X ", utf8_act[i]&255); puts("");
}
}
static void test168(const char16_t* utf16, const char* utf8_exp, int inlen=0, int outlen_e=0)
{
test168f(0, utf16, utf8_exp, inlen, outlen_e);
}
void WuTF_test_encoder()
{
test816("a", u"a");
test816("smörgåsräka", u"smörgåsräka");
test816("♩♪♫♬", u"♩♪♫♬");
test816("𝄞♩♪♫♬", u"𝄞♩♪♫♬");
//http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt
//bugs found in this implementation:
//- I treated 0xF8 as 0xF0 rather than illegal
//- the test for surrogate or not is <= 0xFFFF, not <
//1 Some correct UTF-8 text
test816("#\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5#", u"#\x03BA\x1F79\x03C3\x03BC\x03B5#");
//2 Boundary condition test cases
test816("#\0#", u"#\0#", 4,4);
test816("#\xc2\x80#", u"#\x0080#");
test816("#\xe0\xa0\x80#", u"#\x0800#");
test816("#\xf0\x90\x80\x80#", u"#\xD800\xDC00#");
test816("#\xf8\x88\x80\x80\x80#", u"#\xDCF8\xDC88\xDC80\xDC80\xDC80#");
test816("#\xfc\x84\x80\x80\x80\x80#", u"#\xDCFC\xDC84\xDC80\xDC80\xDC80\xDC80#");
test816("#\x7f#", u"#\x007F#");
test816("#\xdf\xbf#", u"#\x07FF#");
test816("#\xef\xbf\xbf#", u"#\xFFFF#");
test816("#\xf7\xbf\xbf\xbf#", u"#\xDCF7\xDCBF\xDCBF\xDCBF#");
test816("#\xfb\xbf\xbf\xbf\xbf#", u"#\xDCFB\xDCBF\xDCBF\xDCBF\xDCBF#");
test816("#\xfd\xbf\xbf\xbf\xbf\xbf#", u"#\xDCFD\xDCBF\xDCBF\xDCBF\xDCBF\xDCBF#");
test816("#\xed\x9f\xbf#", u"#\xD7FF#");
test816("#\xee\x80\x80#", u"#\xE000#");
test816("#\xef\xbf\xbd#", u"#\xFFFD#");
test816("#\xf4\x8f\xbf\xbf#", u"#\xDBFF\xDFFF#");
test816("#\xf4\x90\x80\x80#", u"#\xDCF4\xDC90\xDC80\xDC80#");
//3 Malformed sequences
test816("#\x80#", u"#\xDC80#");
test816("#\xbf#", u"#\xDCBF#");
test816("#\x80\xbf#", u"#\xDC80\xDCBF#");
test816("#\x80\xbf\x80#", u"#\xDC80\xDCBF\xDC80#");
test816("#\x80\xbf\x80\xbf#", u"#\xDC80\xDCBF\xDC80\xDCBF#");
test816("#\x80\xbf\x80\xbf\x80#", u"#\xDC80\xDCBF\xDC80\xDCBF\xDC80#");
test816("#\x80\xbf\x80\xbf\x80\xbf#", u"#\xDC80\xDCBF\xDC80\xDCBF\xDC80\xDCBF#");
test816("#\x80\xbf\x80\xbf\x80\xbf\x80#", u"#\xDC80\xDCBF\xDC80\xDCBF\xDC80\xDCBF\xDC80#");
test816("#\x80\x81\x82\x83\x84\x85\x86\x87#", u"#\xDC80\xDC81\xDC82\xDC83\xDC84\xDC85\xDC86\xDC87#");
test816("#\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f#", u"#\xDC88\xDC89\xDC8A\xDC8B\xDC8C\xDC8D\xDC8E\xDC8F#");
test816("#\x90\x91\x92\x93\x94\x95\x96\x97#", u"#\xDC90\xDC91\xDC92\xDC93\xDC94\xDC95\xDC96\xDC97#");
test816("#\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f#", u"#\xDC98\xDC99\xDC9A\xDC9B\xDC9C\xDC9D\xDC9E\xDC9F#");
test816("#\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7#", u"#\xDCA0\xDCA1\xDCA2\xDCA3\xDCA4\xDCA5\xDCA6\xDCA7#");
test816("#\xa8\xa9\xaa\xab\xac\xad\xae\xaf#", u"#\xDCA8\xDCA9\xDCAA\xDCAB\xDCAC\xDCAD\xDCAE\xDCAF#");
test816("#\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7#", u"#\xDCB0\xDCB1\xDCB2\xDCB3\xDCB4\xDCB5\xDCB6\xDCB7#");
test816("#\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf#", u"#\xDCB8\xDCB9\xDCBA\xDCBB\xDCBC\xDCBD\xDCBE\xDCBF#");
test816("#\xc0\x20\xc1\x20\xc2\x20\xc3\x20#", u"#\xDCC0 \xDCC1 \xDCC2 \xDCC3 #");
test816("#\xc4\x20\xc5\x20\xc6\x20\xc7\x20#", u"#\xDCC4 \xDCC5 \xDCC6 \xDCC7 #");
test816("#\xc8\x20\xc9\x20\xca\x20\xcb\x20#", u"#\xDCC8 \xDCC9 \xDCCA \xDCCB #");
test816("#\xcc\x20\xcd\x20\xce\x20\xcf\x20#", u"#\xDCCC \xDCCD \xDCCE \xDCCF #");
test816("#\xd0\x20\xd1\x20\xd2\x20\xd3\x20#", u"#\xDCD0 \xDCD1 \xDCD2 \xDCD3 #");
test816("#\xd4\x20\xd5\x20\xd6\x20\xd7\x20#", u"#\xDCD4 \xDCD5 \xDCD6 \xDCD7 #");
test816("#\xd8\x20\xd9\x20\xda\x20\xdb\x20#", u"#\xDCD8 \xDCD9 \xDCDA \xDCDB #");
test816("#\xdc\x20\xdd\x20\xde\x20\xdf\x20#", u"#\xDCDC \xDCDD \xDCDE \xDCDF #");
test816("#\xe0\x20\xe1\x20\xe2\x20\xe3\x20#", u"#\xDCE0 \xDCE1 \xDCE2 \xDCE3 #");
test816("#\xe4\x20\xe5\x20\xe6\x20\xe7\x20#", u"#\xDCE4 \xDCE5 \xDCE6 \xDCE7 #");
test816("#\xe8\x20\xe9\x20\xea\x20\xeb\x20#", u"#\xDCE8 \xDCE9 \xDCEA \xDCEB #");
test816("#\xec\x20\xed\x20\xee\x20\xef\x20#", u"#\xDCEC \xDCED \xDCEE \xDCEF #");
test816("#\xf0\x20\xf1\x20\xf2\x20\xf3\x20#", u"#\xDCF0 \xDCF1 \xDCF2 \xDCF3 #");
test816("#\xf4\x20\xf5\x20\xf6\x20\xf7\x20#", u"#\xDCF4 \xDCF5 \xDCF6 \xDCF7 #");
test816("#\xf8\x20\xf9\x20\xfa\x20\xfb\x20#", u"#\xDCF8 \xDCF9 \xDCFA \xDCFB #");
test816("#\xfc\x20\xfd\x20#", u"#\xDCFC \xDCFD #");
test816("#\xc0#", u"#\xDCC0#");
test816("#\xe0\x80#", u"#\xDCE0\xDC80#");
test816("#\xf0\x80\x80#", u"#\xDCF0\xDC80\xDC80#");
test816("#\xf8\x80\x80\x80#", u"#\xDCF8\xDC80\xDC80\xDC80#");
test816("#\xfc\x80\x80\x80\x80#", u"#\xDCFC\xDC80\xDC80\xDC80\xDC80#");
test816("#\xdf#", u"#\xDCDF#");
test816("#\xef\xbf#", u"#\xDCEF\xDCBF#");
test816("#\xf7\xbf\xbf#", u"#\xDCF7\xDCBF\xDCBF#");
test816("#\xfb\xbf\xbf\xbf#", u"#\xDCFB\xDCBF\xDCBF\xDCBF#");
test816("#\xfd\xbf\xbf\xbf\xbf#", u"#\xDCFD\xDCBF\xDCBF\xDCBF\xDCBF#");
test816("#\xc0\xe0\x80\xf0\x80\x80\xf8\x80\x80\x80\xfc\x80\x80\x80\x80#",
u"#\xDCC0\xDCE0\xDC80\xDCF0\xDC80\xDC80\xDCF8\xDC80\xDC80\xDC80\xDCFC\xDC80\xDC80\xDC80\xDC80#");
test816("#\xdf\xef\xbf\xf7\xbf\xbf\xfb\xbf\xbf\xbf\xfd\xbf\xbf\xbf\xbf#",
u"#\xDCDF\xDCEF\xDCBF\xDCF7\xDCBF\xDCBF\xDCFB\xDCBF\xDCBF\xDCBF\xDCFD\xDCBF\xDCBF\xDCBF\xDCBF#");
test816("#\xfe#", u"#\xDCFE#");
test816("#\xff#", u"#\xDCFF#");
test816("#\xfe\xfe\xff\xff#", u"#\xDCFE\xDCFE\xDCFF\xDCFF#");
//4 Overlong sequences
test816("#\xc0\xaf#", u"#\xDCC0\xDCAF#");
test816("#\xe0\x80\xaf#", u"#\xDCE0\xDC80\xDCAF#");
test816("#\xf0\x80\x80\xaf#", u"#\xDCF0\xDC80\xDC80\xDCAF#");
test816("#\xf8\x80\x80\x80\xaf#", u"#\xDCF8\xDC80\xDC80\xDC80\xDCAF#");
test816("#\xfc\x80\x80\x80\x80\xaf#", u"#\xDCFC\xDC80\xDC80\xDC80\xDC80\xDCAF#");
test816("#\xc1\xbf#", u"#\xDCC1\xDCBF#");
test816("#\xe0\x9f\xbf#", u"#\xDCE0\xDC9F\xDCBF#");
test816("#\xf0\x8f\xbf\xbf#", u"#\xDCF0\xDC8F\xDCBF\xDCBF#");
test816("#\xf8\x87\xbf\xbf\xbf#", u"#\xDCF8\xDC87\xDCBF\xDCBF\xDCBF#");
test816("#\xfc\x83\xbf\xbf\xbf\xbf#", u"#\xDCFC\xDC83\xDCBF\xDCBF\xDCBF\xDCBF#");
test816("#\xc0\x80#", u"#\xDCC0\xDC80#");
test816("#\xe0\x80\x80#", u"#\xDCE0\xDC80\xDC80#");
test816("#\xf0\x80\x80\x80#", u"#\xDCF0\xDC80\xDC80\xDC80#");
test816("#\xf8\x80\x80\x80\x80#", u"#\xDCF8\xDC80\xDC80\xDC80\xDC80#");
test816("#\xfc\x80\x80\x80\x80\x80#", u"#\xDCFC\xDC80\xDC80\xDC80\xDC80\xDC80#");
//5 Illegal code positions
test816("#\xed\xa0\x80#", u"#\xDCED\xDCA0\xDC80#");
test816("#\xed\xad\xbf#", u"#\xDCED\xDCAD\xDCBF#");
test816("#\xed\xae\x80#", u"#\xDCED\xDCAE\xDC80#");
test816("#\xed\xaf\xbf#", u"#\xDCED\xDCAF\xDCBF#");
test816("#\xed\xb0\x80#", u"#\xDCED\xDCB0\xDC80#");
test816("#\xed\xbe\x80#", u"#\xDCED\xDCBE\xDC80#");
test816("#\xed\xbf\xbf#", u"#\xDCED\xDCBF\xDCBF#");
test816f(WUTF_WTF8, "#\xed\xa0\x80#", u"#\xD800#");
test816f(WUTF_WTF8, "#\xed\xad\xbf#", u"#\xDB7F#");
test816f(WUTF_WTF8, "#\xed\xae\x80#", u"#\xDB80#");
test816f(WUTF_WTF8, "#\xed\xaf\xbf#", u"#\xDBFF#");
test816f(WUTF_WTF8, "#\xed\xb0\x80#", u"#\xDC00#");
test816f(WUTF_WTF8, "#\xed\xbe\x80#", u"#\xDF80#");
test816f(WUTF_WTF8, "#\xed\xbf\xbf#", u"#\xDFFF#");
test816("#\xed\xa0\x80\xed\xb0\x80#", u"#\xDCED\xDCA0\xDC80\xDCED\xDCB0\xDC80#");
test816("#\xed\xa0\x80\xed\xbf\xbf#", u"#\xDCED\xDCA0\xDC80\xDCED\xDCBF\xDCBF#");
test816("#\xed\xad\xbf\xed\xb0\x80#", u"#\xDCED\xDCAD\xDCBF\xDCED\xDCB0\xDC80#");
test816("#\xed\xad\xbf\xed\xbf\xbf#", u"#\xDCED\xDCAD\xDCBF\xDCED\xDCBF\xDCBF#");
test816("#\xed\xae\x80\xed\xb0\x80#", u"#\xDCED\xDCAE\xDC80\xDCED\xDCB0\xDC80#");
test816("#\xed\xae\x80\xed\xbf\xbf#", u"#\xDCED\xDCAE\xDC80\xDCED\xDCBF\xDCBF#");
test816("#\xed\xaf\xbf\xed\xb0\x80#", u"#\xDCED\xDCAF\xDCBF\xDCED\xDCB0\xDC80#");
test816("#\xed\xaf\xbf\xed\xbf\xbf#", u"#\xDCED\xDCAF\xDCBF\xDCED\xDCBF\xDCBF#");
test816f(WUTF_CESU8, "#\xed\xa0\x80\xed\xb0\x80#", u"#\xD800\xDC00#");
test816f(WUTF_CESU8, "#\xed\xa0\x80\xed\xbf\xbf#", u"#\xD800\xDFFF#");
test816f(WUTF_CESU8, "#\xed\xad\xbf\xed\xb0\x80#", u"#\xDB7F\xDC00#");
test816f(WUTF_CESU8, "#\xed\xad\xbf\xed\xbf\xbf#", u"#\xDB7F\xDFFF#");
test816f(WUTF_CESU8, "#\xed\xae\x80\xed\xb0\x80#", u"#\xDB80\xDC00#");
test816f(WUTF_CESU8, "#\xed\xae\x80\xed\xbf\xbf#", u"#\xDB80\xDFFF#");
test816f(WUTF_CESU8, "#\xed\xaf\xbf\xed\xb0\x80#", u"#\xDBFF\xDC00#");
test816f(WUTF_CESU8, "#\xed\xaf\xbf\xed\xbf\xbf#", u"#\xDBFF\xDFFF#");
test816("#\xef\xb7\x90\xef\xb7\x91\xef\xb7\x92\xef\xb7\x93#", u"#\xFDD0\xFDD1\xFDD2\xFDD3#");
test816("#\xef\xb7\x94\xef\xb7\x95\xef\xb7\x96\xef\xb7\x97#", u"#\xFDD4\xFDD5\xFDD6\xFDD7#");
test816("#\xef\xb7\x98\xef\xb7\x99\xef\xb7\x9a\xef\xb7\x9b#", u"#\xFDD8\xFDD9\xFDDA\xFDDB#");
test816("#\xef\xb7\x9c\xef\xb7\x9d\xef\xb7\x9e\xef\xb7\x9f#", u"#\xFDDC\xFDDD\xFDDE\xFDDF#");
test816("#\xef\xb7\xa0\xef\xb7\xa1\xef\xb7\xa2\xef\xb7\xa3#", u"#\xFDE0\xFDE1\xFDE2\xFDE3#");
test816("#\xef\xb7\xa4\xef\xb7\xa5\xef\xb7\xa6\xef\xb7\xa7#", u"#\xFDE4\xFDE5\xFDE6\xFDE7#");
test816("#\xef\xb7\xa8\xef\xb7\xa9\xef\xb7\xaa\xef\xb7\xab#", u"#\xFDE8\xFDE9\xFDEA\xFDEB#");
test816("#\xef\xb7\xac\xef\xb7\xad\xef\xb7\xae\xef\xb7\xaf#", u"#\xFDEC\xFDED\xFDEE\xFDEF#");
test816("#\xf0\x9f\xbf\xbe\xf0\x9f\xbf\xbf\xf0\xaf\xbf\xbe#", u"#\xD83F\xDFFE\xD83F\xDFFF\xD87F\xDFFE#");
test816("#\xf0\xaf\xbf\xbf\xf0\xbf\xbf\xbe\xf0\xbf\xbf\xbf#", u"#\xD87F\xDFFF\xD8BF\xDFFE\xD8BF\xDFFF#");
test816("#\xf1\x8f\xbf\xbe\xf1\x8f\xbf\xbf\xf1\x9f\xbf\xbe#", u"#\xD8FF\xDFFE\xD8FF\xDFFF\xD93F\xDFFE#");
test816("#\xf1\x9f\xbf\xbf\xf1\xaf\xbf\xbe\xf1\xaf\xbf\xbf#", u"#\xD93F\xDFFF\xD97F\xDFFE\xD97F\xDFFF#");
test816("#\xf1\xbf\xbf\xbe\xf1\xbf\xbf\xbf\xf2\x8f\xbf\xbe#", u"#\xD9BF\xDFFE\xD9BF\xDFFF\xD9FF\xDFFE#");
test816("#\xf2\x8f\xbf\xbf\xf2\x9f\xbf\xbe\xf2\x9f\xbf\xbf#", u"#\xD9FF\xDFFF\xDA3F\xDFFE\xDA3F\xDFFF#");
test816("#\xf2\xaf\xbf\xbe\xf2\xaf\xbf\xbf\xf2\xbf\xbf\xbe#", u"#\xDA7F\xDFFE\xDA7F\xDFFF\xDABF\xDFFE#");
test816("#\xf2\xbf\xbf\xbf\xf3\x8f\xbf\xbe\xf3\x8f\xbf\xbf#", u"#\xDABF\xDFFF\xDAFF\xDFFE\xDAFF\xDFFF#");
test816("#\xf3\x9f\xbf\xbe\xf3\x9f\xbf\xbf\xf3\xaf\xbf\xbe#", u"#\xDB3F\xDFFE\xDB3F\xDFFF\xDB7F\xDFFE#");
test816("#\xf3\xaf\xbf\xbf\xf3\xbf\xbf\xbe\xf3\xbf\xbf\xbf#", u"#\xDB7F\xDFFF\xDBBF\xDFFE\xDBBF\xDFFF#");
test816("#\xf4\x8f\xbf\xbe\xf4\x8f\xbf\xbf#", u"#\xDBFF\xDFFE\xDBFF\xDFFF#");
//////////////////////////////////////////////////////////////////////////////////////////////////
//some of these are the above backwards, some are other edge cases
//either way, 16->8 is way less tested than 8->16, and 8<->32 isn't tested at all
test168(u"a", "a");
test168(u"smörgåsräka", "smörgåsräka");
test168(u"♩♪♫♬", "♩♪♫♬");
test168(u"𝄞♩♪♫♬", "𝄞♩♪♫♬");
test168(u"#\x0000#", "#\x00#", 4,4);
test168(u"#\x0080#", "#\xc2\x80#");
test168(u"#\x0800#", "#\xe0\xa0\x80#");
test168(u"#\xD800\xDC00#", "#\xf0\x90\x80\x80#");
test168(u"#\x007F#", "#\x7f#");
test168(u"#\x07FF#", "#\xdf\xbf#");
test168(u"#\xFFFF#", "#\xef\xbf\xbf#");
test168(u"#\xD7FF#", "#\xed\x9f\xbf#");
test168(u"#\xE000#", "#\xee\x80\x80#");
test168(u"#\xFFFD#", "#\xef\xbf\xbd#");
test168(u"#\xDBFF\xDFFF#", "#\xf4\x8f\xbf\xbf#");
test168f(WUTF_WTF8, u"#\xD800#", "#\xed\xa0\x80#");
test168f(WUTF_WTF8, u"#\xDBFF#", "#\xed\xaf\xbf#");
test168f(WUTF_WTF8, u"#\xDC00#", "#\xed\xb0\x80#");
test168f(WUTF_WTF8, u"#\xDFFF#", "#\xed\xbf\xbf#");
}
#ifdef _WIN32
#define SMR "sm\xC3\xB6rg\xC3\xA5sr\xC3\xA4ka"
#define SMR_W L"sm\x00F6rg\x00E5sr\x00E4ka"
#define SMR3 "\xC3\xA5\xC3\xA4\xC3\xB6"
#define SMR3_W L"\x00E5\x00E4\x00F6"
#include <windows.h>
static void testOFN();
//To pass,
//(1) "(1) PASS" must show up in the console.
//(2) "(2) PASS" must show up in the console.
//(3) The five .åäö files must show up in the filename chooser dialog.
//(3) you_shouldnt_see_this.txt must NOT show up.
//(3) The instructions (file type field) must be ungarbled.
//(3) You must follow the filetype instructions.
//(3) "(3) PASS" must show up in the console.
//(4) The text on the button must be correct.
//(4) The text on the button window's title must be correct.
//(5) The message box title must be correct.
//(6) The message box title must be correct.
//Explanation:
//(1) CreateFileA(); a simple thing
//(2) GetWindowTextA(); it returns strings, rather than taking them
//(3) GetOpenFileNameA(); a complex thing
//(4) Test text on a button; also on the window title, but (5) also tests that
//(5) Message box title
//(6) Message box body, and a string that's longer than the 260 limit
//4, 5 and 6 should be verified before dismissing the message box.
void WuTF_test_ansiutf()
{
WuTF_enable();
DWORD ignore;
HANDLE h;
h = CreateFileW(SMR_W L".txt", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
WriteFile(h, "pokemon", 8, &ignore, NULL);
CloseHandle(h);
h = CreateFileA(SMR ".txt", GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
if (h != INVALID_HANDLE_VALUE)
{
char p[8];
ReadFile(h, p, 42, &ignore, NULL);
if (!strcmp(p, "pokemon")) puts("(1) PASS");
else puts("(1) FAIL: Wrong contents");
CloseHandle(h);
}
else
{
printf("(1) FAIL: Couldn't open file (errno %lu)", GetLastError());
}
DeleteFileW(SMR_W L".txt");
HWND wnd = CreateWindowA("BUTTON", "(4) CHECK: " SMR, WS_OVERLAPPEDWINDOW|WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT, 200, 60,
NULL, NULL, NULL, NULL);
WCHAR expect[42];
GetWindowTextW(wnd, expect, 42);
if (!wcscmp(expect, L"(4) CHECK: " SMR_W)) puts("(2) PASS");
else puts("(2) PASS");
testOFN();
//this one takes two string arguments, one of which can be way longer than 260
#define PAD "Stretch string to 260 characters."
#define PAD2 PAD " " PAD
#define PAD8 PAD2 "\r\n" PAD2 "\r\n" PAD2 "\r\n" PAD2
MessageBoxA(NULL, PAD8 "\r\n(6) CHECK: " SMR, "(5) CHECK: " SMR, MB_OK);
}
static void testOFN()
{
CreateDirectoryA(SMR, NULL);
CloseHandle(CreateFileW(SMR_W L"/" SMR_W L"1." SMR3_W, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL));
CloseHandle(CreateFileW(SMR_W L"/" SMR_W L"2." SMR3_W, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL));
CloseHandle(CreateFileW(SMR_W L"/" SMR_W L"3." SMR3_W, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL));
CloseHandle(CreateFileW(SMR_W L"/" SMR_W L"4." SMR3_W, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL));
CloseHandle(CreateFileW(SMR_W L"/" SMR_W L"5." SMR3_W, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL));
CloseHandle(CreateFileW(SMR_W L"/you_shouldnt_see_this.txt", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL));
OPENFILENAME ofn;
char ofnret[65536];
memset(&ofn, 0, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
ofn.lpstrFilter = "Select the " SMR3 " files\0*." SMR3 "\0";
ofn.nFilterIndex = 1;
ofn.lpstrFile = ofnret;
ofn.nMaxFile = 65536;
ofn.lpstrInitialDir = SMR;
ofn.Flags = OFN_ALLOWMULTISELECT|OFN_EXPLORER;
GetOpenFileNameA(&ofn);
char* filenames = ofnret;
int numcorrect = 0;
while (*filenames)
{
puts(filenames);
if (strlen(filenames)==strlen(SMR "?." SMR3))
{
filenames[strlen(SMR)]='?';
if (!strcmp(filenames, SMR "?." SMR3)) numcorrect++;
else numcorrect = -1000;
}
filenames += strlen(filenames)+1;
}
if (numcorrect == 5)
{
puts("(3) PASS");
}
else
{
puts("(3) FAIL");
}
}
#endif
int main()
{
WuTF_test_encoder();
#ifdef _WIN32
WuTF_test_ansiutf();
#endif
}
#endif

247
arlib/wutf/wutf.cpp Normal file
View File

@ -0,0 +1,247 @@
// MIT License
//
// Copyright (c) 2016 Alfred Agrell
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// 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 OR COPYRIGHT HOLDERS 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.
//The above license applies only to this file, not the entire Arlib.
//See wutf.h for documentation.
#ifdef _WIN32
#include "wutf.h"
#include <windows.h>
#ifndef NTSTATUS
#define NTSTATUS LONG
#endif
#ifndef STATUS_SUCCESS
#define STATUS_SUCCESS 0x00000000
#endif
static NTSTATUS WINAPI
RtlMultiByteToUnicodeN_Utf(
PWCH UnicodeString,
ULONG MaxBytesInUnicodeString,
PULONG BytesInUnicodeString,
const CHAR *MultiByteString,
ULONG BytesInMultiByteString)
{
int len = WuTF_utf8_to_utf16(WUTF_TRUNCATE | WUTF_INVALID_DROP,
MultiByteString, BytesInMultiByteString,
(uint16_t*)UnicodeString, MaxBytesInUnicodeString/2);
if (BytesInUnicodeString) *BytesInUnicodeString = len*2;
return STATUS_SUCCESS;
}
static NTSTATUS WINAPI
RtlUnicodeToMultiByteN_Utf(
PCHAR MultiByteString,
ULONG MaxBytesInMultiByteString,
PULONG BytesInMultiByteString,
PCWCH UnicodeString,
ULONG BytesInUnicodeString)
{
int len = WuTF_utf16_to_utf8(WUTF_TRUNCATE | WUTF_INVALID_DROP,
(uint16_t*)UnicodeString, BytesInUnicodeString/2,
MultiByteString, MaxBytesInMultiByteString);
if (BytesInMultiByteString) *BytesInMultiByteString = len;
return STATUS_SUCCESS;
}
static NTSTATUS WINAPI
RtlMultiByteToUnicodeSize_Utf(
PULONG BytesInUnicodeString,
const CHAR *MultiByteString,
ULONG BytesInMultiByteString)
{
int len = WuTF_utf8_to_utf16(WUTF_INVALID_DROP,
MultiByteString, BytesInMultiByteString,
NULL, 0);
*BytesInUnicodeString = len*2;
return STATUS_SUCCESS;
}
static NTSTATUS WINAPI
RtlUnicodeToMultiByteSize_Utf(
PULONG BytesInMultiByteString,
PCWCH UnicodeString,
ULONG BytesInUnicodeString)
{
int len = WuTF_utf16_to_utf8(WUTF_INVALID_DROP,
(uint16_t*)UnicodeString, BytesInUnicodeString/2,
NULL, 0);
*BytesInMultiByteString = len;
return STATUS_SUCCESS;
}
//ignores invalid flags and parameters
static int WINAPI
MultiByteToWideChar_Utf(UINT CodePage, DWORD dwFlags,
LPCSTR lpMultiByteStr, int cbMultiByte,
LPWSTR lpWideCharStr, int cchWideChar)
{
int ret = WuTF_utf8_to_utf16((dwFlags&MB_ERR_INVALID_CHARS) ? WUTF_INVALID_ABORT : WUTF_INVALID_DROP,
lpMultiByteStr, cbMultiByte,
(uint16_t*)lpWideCharStr, cchWideChar);
if (ret<0)
{
if (ret == WUTF_E_INVALID) SetLastError(ERROR_NO_UNICODE_TRANSLATION);
if (ret == WUTF_E_TRUNCATE) SetLastError(ERROR_INSUFFICIENT_BUFFER);
return 0;
}
return ret;
}
static int WINAPI
WideCharToMultiByte_Utf(UINT CodePage, DWORD dwFlags,
LPCWSTR lpWideCharStr, int cchWideChar,
LPSTR lpMultiByteStr, int cbMultiByte,
LPCSTR lpDefaultChar, LPBOOL lpUsedDefaultChar)
{
int ret = WuTF_utf16_to_utf8((dwFlags&MB_ERR_INVALID_CHARS) ? WUTF_INVALID_ABORT : WUTF_INVALID_DROP,
(uint16_t*)lpWideCharStr, cchWideChar,
lpMultiByteStr, cbMultiByte);
if (ret<0)
{
if (ret == WUTF_E_INVALID) SetLastError(ERROR_NO_UNICODE_TRANSLATION);
if (ret == WUTF_E_TRUNCATE) SetLastError(ERROR_INSUFFICIENT_BUFFER);
return 0;
}
return ret;
}
//https://sourceforge.net/p/predef/wiki/Architectures/
#if defined(_M_IX86) || defined(__i386__)
static void redirect_machine(LPBYTE victim, LPBYTE replacement)
{
victim[0] = 0xE9; // jmp <offset from next instruction>
*(LONG_PTR*)(victim+1) = replacement-victim-5;
}
#elif defined(_M_X64) || defined(__x86_64__)
static void redirect_machine(LPBYTE victim, LPBYTE replacement)
{
// this destroys %rax, but that register is caller-saved (and the return value).
// https://msdn.microsoft.com/en-us/library/9z1stfyw.aspx
victim[0] = 0x48; victim[1] = 0xB8; // mov %rax, <64 bit constant>
*(LPBYTE*)(victim+2) = replacement;
victim[10] = 0xFF; victim[11] = 0xE0; // jmp %rax
}
#else
#error Not supported
#endif
void WuTF_redirect_function(WuTF_funcptr victim, WuTF_funcptr replacement)
{
DWORD prot;
//it's usually considered bad to have W+X on the same page, but the alternative is risking
// removing X from VirtualProtect or NtProtectVirtualMemory, and then I can't fix it.
//it doesn't matter, anyways; we (should be) called so early no hostile input has been processed
// yet, and even if hostile code is running, it can just wait until I put back X.
VirtualProtect((void*)victim, 64, PAGE_EXECUTE_READWRITE, &prot);
redirect_machine((LPBYTE)victim, (LPBYTE)replacement);
VirtualProtect((void*)victim, 64, prot, &prot);
}
void WuTF_enable()
{
//it's safe to call this multiple times, that just replaces some bytes with their current values
//if wutf becomes more complex, add a static bool initialized
#define STRINGIFY_(x) #x
#define STRINGIFY(x) STRINGIFY_(x)
#define REDIR(dll, func) WuTF_redirect_function((WuTF_funcptr)GetProcAddress(dll, STRINGIFY(func)), (WuTF_funcptr)func##_Utf)
HMODULE ntdll = GetModuleHandle("ntdll.dll");
//list of possibly relevant functions in ntdll.dll (pulled from 'strings ntdll.dll'):
//some are documented at https://msdn.microsoft.com/en-us/library/windows/hardware/ff553354%28v=vs.85%29.aspx
//many are implemented in terms of other functions, often rooting in the ones I've hijacked
// RtlAnsiCharToUnicodeChar
// RtlAnsiStringToUnicodeSize
// RtlAnsiStringToUnicodeString
// RtlAppendAsciizToString
// RtlAppendPathElement
// RtlAppendStringToString
// RtlAppendUnicodeStringToString
// RtlAppendUnicodeToString
// RtlCreateUnicodeStringFromAsciiz
// RtlMultiAppendUnicodeStringBuffer
REDIR(ntdll, RtlMultiByteToUnicodeN);
REDIR(ntdll, RtlMultiByteToUnicodeSize);
// RtlOemStringToUnicodeSize
// RtlOemStringToUnicodeString
// RtlOemToUnicodeN
// RtlRunDecodeUnicodeString
// RtlRunEncodeUnicodeString
// RtlUnicodeStringToAnsiSize
// RtlUnicodeStringToAnsiString
// RtlUnicodeStringToCountedOemString
// RtlUnicodeStringToInteger
// RtlUnicodeStringToOemSize
// RtlUnicodeStringToOemString
// RtlUnicodeToCustomCPN
REDIR(ntdll, RtlUnicodeToMultiByteN);
REDIR(ntdll, RtlUnicodeToMultiByteSize);
// RtlUnicodeToOemN
// RtlUpcaseUnicodeChar
// RtlUpcaseUnicodeString
// RtlUpcaseUnicodeStringToAnsiString
// RtlUpcaseUnicodeStringToCountedOemString
// RtlUpcaseUnicodeStringToOemString
// RtlUpcaseUnicodeToCustomCPN
// RtlUpcaseUnicodeToMultiByteN
// RtlUpcaseUnicodeToOemN
// RtlxAnsiStringToUnicodeSize
// RtlxOemStringToUnicodeSize
// RtlxUnicodeStringToAnsiSize
// RtlxUnicodeStringToOemSize
HMODULE kernel32 = GetModuleHandle("kernel32.dll");
REDIR(kernel32, MultiByteToWideChar);
REDIR(kernel32, WideCharToMultiByte);
#undef REDIR
}
void WuTF_args(int* argc_p, char** * argv_p)
{
int i;
int argc;
LPWSTR* wargv = CommandLineToArgvW(GetCommandLineW(), &argc);
LPSTR* argv = (LPSTR*)HeapAlloc(GetProcessHeap(), 0, sizeof(LPSTR)*(argc+1));
for (i=0;i<argc;i++)
{
int cb = WuTF_utf16_to_utf8(0, (uint16_t*)wargv[i], -1, NULL, 0);
argv[i] = (char*)HeapAlloc(GetProcessHeap(), 0, cb);
WuTF_utf16_to_utf8(0, (uint16_t*)wargv[i], -1, argv[i], cb);
}
argv[argc]=0;
*argv_p = argv;
*argc_p = argc;
}
#endif

152
arlib/wutf/wutf.h Normal file
View File

@ -0,0 +1,152 @@
// MIT License
//
// Copyright (c) 2016 Alfred Agrell
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// 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 OR COPYRIGHT HOLDERS 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.
//The above license applies only to this file, not the entire Arlib.
// It is well known that Windows supports two flavors of every function that
// takes or returns strings(*): A and W. The A ones take strings in the local
// user's codepage; W uses UTF-16.
// (*) With a few exceptions, for example CommandLineToArgvW, CharNextExA and GetProcAddress.
// It is also fairly well known that the local codepage can not be set to UTF-8,
// despite users' repeated requests.
// It is less well known that the A functions convert their arguments and then
// call the W functions.
// It is even less well known (though easy to guess) that there are only a few
// conversion functions in the entire Windows.
//
// It is not very well known that you can mark memory executable using
// VirtualProtect, and use that to create your own functions.
// It is even less well known that you can mark existing functions writable.
//
// Combining those lead to an evil idea: What if we replace the A->W conversion
// function with an UTF8->UTF16 converter?
// And this is exactly what I did.
//
//Limitations:
//- IMMEDIATELY VOIDS YOUR WARRANTY
//- Possibly makes antivirus software panic.
//- Will crash if this code is in a DLL that's unloaded, possibly including program shutdown.
//- Affects the entire process; don't do it unless you know the process wants it this way.
// I believe most processes actually wants it this way; everything I've seen either expects ASCII
// only, uses the W APIs, or is console output (which is another codepage). I am not aware of
// anything that actually expects the ANSI codepage.
//- Disables support for non-UTF8 code pages in MultiByteToWideChar and WideCharToMultiByte and
// treats them as UTF-8, even if explicitly requested otherwise.
//- Console input and output remains ANSI. Consoles are very strangely implemented in Windows;
// judging by struct CHAR_INFO in WriteConsoleOutput's arguments, the consoles don't support
// UTF-16, but only UCS-2. (The rest of WuTF supports non-BMP characters, of course.)
// A more technical explanation: The most common ways to write to the console (the ones in msvcrt)
// end up in _write, then WriteFile. I can't replace either of them; I need to call them, and
// there's no reasonably sane and reliable way to redirect a function while retaining the ability
// to use the original.
// _setmode(_O_U8TEXT) doesn't help; it makes puts() convert from UTF-16 to UTF-8. I need the
// other direction.
// SetConsoleOutputCP(CP_UTF8) seems more promising, but it reports success and does nothing on
// Windows XP and 7. I'm not sure what it actually does.
//- CharNextA/etc are unchanged and still expect the ANSI code page. (Does anything ever use them?)
//- SetFileApisToOEM is untested. I don't know if it's ignored or if it actually does set them to
// OEM. Either way, the fix is easy: don't use it.
//- Actually uses WTF-8 <https://simonsapin.github.io/wtf-8/>; you may see the surrogate characters
// if you somehow get invalid UTF-16 (it's fairly permissive on UTF8->16, too; it accepts CESU-8)
//- Windows filenames are limited to ~260 characters; but I believe functions that return filenames
// will count the UTF-8 bytes. (The ones taking filename inputs should work up to 260 UTF-16
// codepoints.)
//- According to Larry Osterman <https://blogs.msdn.microsoft.com/larryosterman/2007/03/20/other-fun-things-to-do-with-the-endpointvolume-interfaces/>,
// "all new APIs are unicode only" (aka UTF-16).
//- The UTF8/16 converter is not identical to MultiByteToWideChar(CP_UTF8):
// - While it does support UTF-16 surrogate pairs, it's perfectly happy to encode lone surrogate
// characters, as per WTF-8 <https://simonsapin.github.io/wtf-8/>. MBtWC rejects them.
// - It supports decoding lone surrogates, too - or even paired surrogates (also known as CESU-8).
// - If given invalid input (and MB_ERR_INVALID_CHARS is absent), it emits one or more U+FFFD
// REPLACEMENT CHARACTER, rather than dropping them or creating question marks.
// It does reject overlong encodings, and processes surrogate pairs correctly.
//- Did I mention it voids your warranty?
//
// Keywords:
// Windows UTF-8
// make Windows use UTF-8
// set codepage to UTF-8
// set codepage to CP_UTF8
// convert Windows A functions to UTF-8
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#ifdef _WIN32
//Main function; this one does the actual magic. Call this as early as possible.
void WuTF_enable();
//Converts argc/argv to UTF-8. Uses only documented functions, so it has zero chance of blowing up.
//However, it does leak memory, so don't call it more than once. (The leaks are cleaned up on process exit anyways.)
void WuTF_args(int* argc, char** * argv);
//DO NOT USE THIS UNLESS YOU'RE INSANE
//This replaces the 'victim' function such that all calls instead go to 'replacement'.
//Make sure their signatures, including calling convention, are identical.
typedef void(*WuTF_funcptr)();
void WuTF_redirect_function(WuTF_funcptr victim, WuTF_funcptr replacement);
#else
//Other OSes already use UTF-8.
static inline void WuTF_enable() {}
static inline void WuTF_args(int* argc, char** * argv) {}
#endif
//This one just combines the above.
static inline void WuTF_enable_args(int* argc, char** * argv) { WuTF_enable(); WuTF_args(argc, argv); }
//Lengths are in code units, and include the NUL terminator.
//-1 is valid for the input length, and means 'use strlen()+1'.
//Return value is number of code units emitted.
//If the output parameters are NULL/0, it discards the output, and only returns the required number of code units.
//If input is not valid,
#define WUTF_INVALID_ABORT 0x00 // return error (default)
#define WUTF_INVALID_DROP 0x01 // ignore the bad codepoints
#define WUTF_INVALID_FFFD 0x02 // replace each bad byte with U+FFFD
#define WUTF_INVALID_DCXX 0x03 // encode the invalid bytes as U+DC00 plus the bad byte (lossless)
#define WUTF_INVALID_MASK 0x03 // (used internally)
#define WUTF_TRUNCATE 0x04 // If the output string doesn't fit, truncate it. Without this flag, truncation yields WUTF_E_TRUNCATE.
#define WUTF_CESU8 0x08 // If the input UTF-8 contains paired UTF-16 surrogates, decode it to a single codepoint. utf8_to only.
#define WUTF_WTF8 0x10 // If the input contains unpaired UTF-16 surrogates, treat as normal codepoints. Incompatible with INVALID_DCXX.
#define WUTF_E_TRUNCATE -2
#define WUTF_E_INVALID -1
int WuTF_utf8_to_utf32(int flags, const char* utf8, int utf8_len, uint32_t* utf32, int utf32_len);
int WuTF_utf32_to_utf8(int flags, const uint32_t* utf32, int utf32_len, char* utf8, int utf8_len);
//Used internally in WuTF. It's STRONGLY RECOMMENDED to not use these; use UTF-32 instead.
int WuTF_utf8_to_utf16(int flags, const char* utf8, int utf8_len, uint16_t* utf16, int utf16_len);
int WuTF_utf16_to_utf8(int flags, const uint16_t* utf16, int utf16_len, char* utf8, int utf8_len);
#ifdef __cplusplus
}
#endif

1782
divsufsort.c Normal file

File diff suppressed because it is too large Load Diff

63
divsufsort.h Normal file
View File

@ -0,0 +1,63 @@
/*
* divsufsort.h for libdivsufsort-lite
* Copyright (c) 2003-2008 Yuta Mori All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* 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 OR COPYRIGHT
* HOLDERS 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.
*/
#ifndef _DIVSUFSORT_H
#define _DIVSUFSORT_H 1
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
/*- Prototypes -*/
/**
* Constructs the suffix array of a given string.
* @param T[0..n-1] The input string.
* @param SA[0..n-1] The output array of suffixes.
* @param n The length of the given string.
* @return 0 if no error occurred, -1 or -2 otherwise.
*/
int
divsufsort(const unsigned char *T, int *SA, int n);
/**
* Constructs the burrows-wheeler transformed string of a given string.
* @param T[0..n-1] The input string.
* @param U[0..n-1] The output string. (can be T)
* @param A[0..n-1] The temporary array. (can be NULL)
* @param n The length of the given string.
* @return The primary index if no error occurred, -1 or -2 otherwise.
*/
int
divbwt(const unsigned char *T, unsigned char *U, int *A, int n);
#ifdef __cplusplus
} /* extern "C" */
#endif /* __cplusplus */
#endif /* _DIVSUFSORT_H */

1
flips.cpp Normal file
View File

@ -0,0 +1 @@
int main(){}

21
global.h Normal file
View File

@ -0,0 +1,21 @@
//Module name: Floating IPS, global header
//Author: Alcaro
//Date: June 18, 2015
//Licence: GPL v3.0 or higher
#ifndef struct_mem
#define struct_mem
//the standard library can be assumed to exist
#include <stddef.h>//size_t, SIZE_MAX
#include <stdint.h>//uint8_t
#ifndef SIZE_MAX
#define SIZE_MAX ((size_t)-1)
#endif
struct mem {
uint8_t * ptr;
size_t len;
};
#endif

878
libbps-suf.cpp Normal file
View File

@ -0,0 +1,878 @@
#include "libbps.h"
#include "arlib/crc32.h"
#include "arlib/file.h"
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
//These two give minor performance penalties and will print some random stuff to stdout.
//The former will verify the correctness of the output patch, the latter will print some performance data.
//Can be useful for debugging, but should be disabled for release builds.
#ifdef BPS_STANDALONE
#endif
//#define TEST_CORRECT
//#define TEST_PERF
//If the suffix array of [0, 0, 0, 0] is [3, 2, 1, 0], set to true. If it's [0, 1, 2, 3], this is false.
//If it's [4, 3, 2, 1, 0] or [0, 1, 2, 3, 4], remove the 4 (easily done with some pointer math), and follow the above.
//If it's something else, get a non-broken array calculator.
#define EOF_IS_LAST false
#if defined(TEST_CORRECT) || defined(TEST_PERF)
#include <stdio.h>
#endif
//Algorithm description:
//
//This is heavily built upon suffix sorting; the implementation I use, libdivsufsort, claims
// O(n log n) complexity, so I'll believe that. There is also SA-IS, which claims O(n), but if that
// is true, its constant factors are ridiculously high.
//
//The program starts by taking an equal amount of the source file and target file, concatenates that
// with target first, and suffix sorts it.
//It also calculates a reverse index, such that reverse[sorted[i]]==i.
//
//To find a match, it goes to reverse[outpos], and scans sorted[] up and down for the closest entry
// that either starts before the current output position, or is somewhere in the source file.
//As the source file comes last, the end-of-file marker (whose value is outside the range of a byte)
// is guaranteed to not be in the way for a better match.
//This is called O(n) times, and averages O(1) as at least 50% of sorted[] is in range. However, it
// is worst-case O(n) for sorted inputs, giving a total of O(n^2).
//
//It then checks which of the two candidates are superior, by checking how far they match each
// other, and then checking if the upper one has another correct byte.
//This is potentially O(n), but for each matched byte, another iteration is removed from the outer
// loop, so the sum of all calls is O(n).
//
//When the program approaches the end of the sorted area, it re-sorts twice as much as last time.
// This gives O(log n) calls to the suffix sorter.
//Given O(n log n) for one sorting step, the time taken is O(n/1 log n/1 + n/2 log n/2 +
// n/4 log n/4 + ...), which is strictly less than O(n/1 log n + n/2 log n + n/4 log n + ...), which
// equals O(2n log n), which is O(n log n). (The exact value of that infinite sum is 2n*log(n/2).)
//
//Many details were omitted from the above, but that's the basic setup.
//
//Thus, the program is O(max(n log n, n, n) = n log n) average and O(max(n log n, n^2, n) = n^2)
// worst case.
//
//I conclude that the task of finding, understanding and implementing a sub-O(n^2) algorithm for
// delta patching is resolved.
//Known cases where this function does not emit the optimal encoding:
//If a match in the target file would extend further than target_search_size, it is often skipped.
// Penalty: O(log n), with extremely low constants (it'd require a >256B match to be exactly there).
// Even for big files, the penalty is very likely to remain zero; even hitting double-digit bytes
// would require a file designed exactly for that.
//If multiple matches are equally good, it picks one at random, not the one that's cheaper to encode.
// Penalty: Likely O(n) or O(n log log n), with low constants. I'd guess ~1.4% for my 48MB test file.
//However, due to better heuristics and others' performance optimizations, this one still beats its
// competitors.
//Possible optimizations:
//divsufsort() takes approximately 2/3 of the total time. create_reverse_index() takes roughly a third of the remainder.
//Each iteration takes four times as long as the previous one.
//If each iteration takes 4 times as long as the previous one, then the last one takes 3/4 of the total time.
//Since divsufsort+create_reverse_index doesn't depend on anything else, the last iteration can be split off to its own thread.
//This would split it to
//Search, non-final: 2/9 * 1/4 = 2/36
//Search, final: 2/9 * 3/4 = 6/36
//Sort+rev, non-final: 7/9 * 1/4 = 7/36
//Sort+rev, final: 7/9 * 3/4 = 21/36
//All non-final must be done sequentially. Both Sort Final and non-final must be done before Search Final can start.
//This means the final time, if Sort Final is split off, is
//max(7/36+2/36, 21/36) + 6/36 = 27/36 = 3/4
//of the original time.
//Due to
//- the considerable complexity costs (OpenMP doesn't seem able to represent the "insert a wait in
// the middle of this while loop" I would need)
//- the added memory use, approximately 25% higher - it's already high enough
//- libdivsufsort already using threads, which would make the gains lower
// and would increase complexity, as I have to ensure the big one remains threaded -
// and that the small ones are not, as that'd starve the big one
//I deem a possible 25% boost not worthwhile.
//Both sorting algorithms claim O(1) memory use (in addition to the bytes and the output). In
// addition to that, this algorithm uses (source.len*target.len)*(sizeof(uint8_t)+2*sizeof(off_t))
// bytes of memory, plus the input and output files, plus the patch.
//For most hardware, this is 9*(source.len+target.len), or 5*(source+target) for the slim one.
#include "sais.cpp"
template<typename sais_index_type>
static void sufsort(sais_index_type* SA, const uint8_t* T, sais_index_type n) {
if(n <= 1) { if(n == 1) SA[0] = 0; return; }
sais_main<sais_index_type>(T, SA, 0, n, 256);
}
//According to <https://code.google.com/p/libdivsufsort/wiki/SACA_Benchmarks>, divsufsort achieves
// approximately half the time of SAIS for nearly all files, despite SAIS' promises of linear
// performance (divsufsort claims O(n log n)).
//divsufsort only allocates O(1) for some radix/bucket sorting. SAIS seems constant too.
//I'd prefer to let them allocate from an array I give it, but divsuf doesn't allow that, and there
// are only half a dozen allocations per call anyways.
//This ends up in libdivsufsort if available, otherwise lite.
#include "divsufsort.h"
static void sufsort(int32_t* SA, uint8_t* T, int32_t n)
{
divsufsort(T, SA, n);
}
#ifdef USE_DIVSUFSORT64
#include "divsufsort64.h"
static void sufsort(int64_t* SA, uint8_t* T, int64_t n)
{
divsufsort(T, SA, n);
}
#endif
template<typename T> static T min(T a, T b) { return a<b ? a : b; }
template<typename T> static T max(T a, T b) { return a<b ? b : a; }
namespace {
struct bps_creator {
uint8_t* out;
size_t outlen;
size_t outbuflen;
void reserve(size_t len)
{
if (outlen+len > outbuflen)
{
if (!outbuflen) outbuflen = 128;
while (outlen+len > outbuflen) outbuflen *= 2;
out = (uint8_t*)realloc(out, outbuflen);
}
}
void append(const uint8_t * data, size_t len)
{
reserve(len);
memcpy(out+outlen, data, len);
outlen+=len;
}
void appendnum(size_t num)
{
#ifdef TEST_CORRECT
if (num > 1000000000)
printf("ERROR: Attempt to write %.8lX\n",(unsigned long)num),abort();
#endif
reserve(sizeof(size_t)*8/7+1);
while (num >= 128)
{
out[outlen++]=(num&0x7F);
num>>=7;
num--;
}
out[outlen++]=num|0x80;
}
void appendnum32(uint32_t num)
{
reserve(4);
out[outlen++] = num>>0;
out[outlen++] = num>>8;
out[outlen++] = num>>16;
out[outlen++] = num>>24;
}
static size_t maxsize()
{
return SIZE_MAX>>2; // can be reduced to SIZE_MAX>>1 by amending append_cmd, but the mallocs overflow at that point anyways.
}
size_t sourcelen;
size_t targetlen;
const uint8_t* targetmem;
enum bpscmd { SourceRead, TargetRead, SourceCopy, TargetCopy };
size_t outpos;
size_t sourcecopypos;
size_t targetcopypos;
size_t numtargetread;
bps_creator(file* source, file* target, struct mem metadata)
{
outlen = 0;
outbuflen = 128;
out = (uint8_t*)malloc(outbuflen);
outpos = 0;
sourcecopypos = 0;
targetcopypos = 0;
numtargetread = 0;
append((const uint8_t*)"BPS1", 4);
appendnum(source->len);
appendnum(target->len);
appendnum(metadata.len);
append(metadata.ptr, metadata.len);
setProgress(NULL, NULL);
}
void move_target(const uint8_t* ptr)
{
targetmem = ptr;
}
size_t encode_delta(size_t prev, size_t next)
{
bool negative = (next<prev);
size_t offset = negative ? prev-next : next-prev;
return (negative?1:0) | (offset<<1);
}
void append_delta(size_t prev, size_t next)
{
appendnum(encode_delta(prev, next));
}
void append_cmd(bpscmd command, size_t count)
{
appendnum((count-1)<<2 | command);
}
void flush_target_read()
{
if (!numtargetread) return;
append_cmd(TargetRead, numtargetread);
append(targetmem+outpos-numtargetread, numtargetread);
numtargetread = 0;
}
size_t emit_source_copy(size_t location, size_t count)
{
if (location == outpos) return emit_source_read(location, count);
flush_target_read();
append_cmd(SourceCopy, count);
append_delta(sourcecopypos, location);
sourcecopypos = location+count;
outpos += count;
return count;
}
size_t emit_source_read(size_t location, size_t count)
{
flush_target_read();
#ifdef TEST_CORRECT
if (location != outpos)
puts("ERROR: SourceRead not from source pointer"),abort();
#endif
append_cmd(SourceRead, count);
outpos+=count;
return count;
}
size_t emit_target_copy(size_t location, size_t count)
{
flush_target_read();
append_cmd(TargetCopy, count);
append_delta(targetcopypos, location);
targetcopypos = location+count;
outpos += count;
return count;
}
size_t emit_target_read()
{
numtargetread++;
outpos++;
return 1;
}
size_t abs_diff(size_t a, size_t b)
{
return (b<a) ? (a-b) : (b-a);
}
size_t num_cost(size_t num)
{
if (num<128) return 1;
if (num<128*128) return 2; // 32KB
if (num<128*128*128) return 3; // 2MB
if (num<128*128*128*128) return 4; // 256MB
// 128^5 is 32GB, let's just assume the sizes don't go any higher...
// worst case, a bad match is used. except a 32GB match is by definition good.
return 5;
}
bool use_match(bool hastargetread, size_t cost, size_t len)
{
//numbers calculated via trial and error; checking for each cost, optimizing 'len' for each, and checking what happens
//then a pattern was identified and used
//yes, it looks weird
return len >= 1+cost+hastargetread+(len==1);
}
//Return value is how many bytes were used. If you believe the given one sucks, use TargetRead and return 1.
size_t match(bool is_target, size_t pos, size_t len)
{
if (!use_match(
numtargetread,
(!is_target && pos==outpos) ? 1 : // SourceRead
(num_cost(abs_diff(pos, (is_target ? targetcopypos : sourcecopypos)))+1),
len
))
{
return emit_target_read();
}
if (is_target) return emit_target_copy(pos, len);
else return emit_source_copy(pos, len);
}
bool (*prog_func)(void* userdata, size_t done, size_t total);
void* prog_dat;
static bool prog_func_null(void* userdata, size_t done, size_t total) { return true; }
void setProgress(bool (*progress)(void* userdata, size_t done, size_t total), void* userdata)
{
if (!progress) progress = prog_func_null;
prog_func=progress;
prog_dat=userdata;
}
bool progress(size_t done, size_t total)
{
return prog_func(prog_dat, done, total);
}
void finish(const uint8_t* source, const uint8_t* target)
{
flush_target_read();
#ifdef TEST_CORRECT
if (outpos != targetlen)
puts("ERROR: patch creates wrong ROM size"),abort();
#endif
appendnum32(crc32(source, sourcelen));
appendnum32(crc32(target, targetlen));
appendnum32(crc32(out, outlen));
}
struct mem getpatch()
{
struct mem ret = { out, outlen };
out = NULL;
return ret;
}
~bps_creator() { free(out); }
};
}
#ifdef TEST_PERF
static int match_len_n=0;
static int match_len_tot=0;
#endif
template<typename off_t>
static off_t match_len(const uint8_t* a, const uint8_t* b, off_t len)
{
off_t i;
for (i=0;i<len && a[i]==b[i];i++) {}
#ifdef TEST_PERF
match_len_n++;
match_len_tot+=i;
#endif
return i;
}
//This one assumes that the longest common prefix of 'a' and 'b' is shared also by 'search'.
//In practice, lexographically, a < search < b, which is a stronger guarantee.
template<typename off_t>
static off_t pick_best_of_two(const uint8_t* search, off_t searchlen,
const uint8_t* data, off_t datalen,
off_t a, off_t b,
off_t* bestlen)
{
off_t commonlen = match_len(data+a, data+b, min(datalen-a, datalen-b));
if (commonlen>=searchlen)
{
*bestlen=searchlen;
return a;
}
if (a+commonlen<datalen && search[commonlen]==data[a+commonlen])
{
// a is better
*bestlen = commonlen + match_len(search+commonlen, data+a+commonlen, min(searchlen, datalen-a)-commonlen);
return a;
}
else
{
// b is better, or they're equal
*bestlen = commonlen + match_len(search+commonlen, data+b+commonlen, min(searchlen, datalen-b)-commonlen);
return b;
}
}
//This one takes a match, which is assumed optimal, and looks for the lexographically closest one
// that either starts before 'maxstart', or starts at or after 'minstart'.
template<typename off_t>
static off_t adjust_match(off_t match, const uint8_t* search, off_t searchlen,
const uint8_t* data,off_t datalen, off_t maxstart,off_t minstart,
const off_t* sorted, off_t sortedlen,
off_t* bestlen)
{
off_t match_up = match;
off_t match_dn = match;
while (match_up>=0 && sorted[match_up]>=maxstart && sorted[match_up]<minstart) match_up--;
while (match_dn<sortedlen && sorted[match_dn]>=maxstart && sorted[match_dn]<minstart) match_dn++;
if (match_up<0 || match_dn>=sortedlen)
{
if (match_up<0 && match_dn>=sortedlen)
{
*bestlen=0;
return 0;
}
off_t pos = sorted[match_up<0 ? match_dn : match_up];
*bestlen = match_len(search, data+pos, min(searchlen, datalen-pos));
return pos;
}
return pick_best_of_two(search,searchlen, data,datalen, sorted[match_up],sorted[match_dn], bestlen);
}
static uint16_t read2_uc(const uint8_t* data)
{
return data[0]<<8 | data[1];
}
template<typename off_t>
static uint16_t read2(const uint8_t* data, off_t len)
{
if (len>=2) return read2_uc(data);
else
{
uint16_t out = (EOF_IS_LAST ? 0xFFFF : 0x0000);
if (len==1) out = (data[0]<<8) | (out&0x00FF);
return out;
}
}
template<typename off_t>
static void create_buckets(const uint8_t* data, off_t* index, off_t len, off_t* buckets)
{
off_t low = 0;
off_t high;
for (int n=0;n<65536;n++)
{
//'low' remains from the previous iteration and is a known minimum
high = low+(len/131072)+1; // optimal value: slightly above a third of the distance to the next one
while (true)
{
if (high > len-1) break;
off_t pos = index[high];
uint16_t here = read2(data+pos, len-pos);
if (here >= n) break;
else
{
off_t diff = high-low;
low = high;
high = high+diff*2;
}
}
if (high > len-1) high = len-1;
while (low < high)
{
off_t mid = low + (high-low)/2;
off_t midpos = index[mid];
uint16_t here = read2(data+midpos, len-midpos);
if (here < n) low = mid+1;
else high = mid;
}
buckets[n] = low;
}
buckets[65536] = len;
#ifdef TEST_CORRECT
if (buckets[0]!=0)
{
printf("e: buckets suck, [0]=%i\n", buckets[0]);
abort();
}
for (int n=0;n<65536;n++)
{
off_t low = buckets[n];
off_t high = buckets[n+1];
for (off_t i=low;i<high;i++)
{
if (read2(data+index[i], len-index[i])!=n)
{
printf("e: buckets suck, %i != (%i)[%i]%i [%i-%i]", n, i,index[i],read2(data+index[i],len-index[i]),low,high);
abort();
}
}
//printf("%i:[%i]%i\n",n,low,read2(data+index[low],len-low));
}
#endif
}
template<typename off_t>
static off_t find_index(off_t pos, const uint8_t* data, off_t datalen, const off_t* index, const off_t* reverse, off_t* buckets)
{
if (reverse) return reverse[pos];
//if (datalen<2) return 0;
uint16_t bucket = read2(data+pos, datalen-pos);
//printf("p=%i b=%i\n",pos,bucket);
//TODO
//off_t low = 0;
//off_t high = datalen-1;
off_t low = buckets[bucket];
off_t high = buckets[bucket+1]-1;
off_t lowmatch = 2;
off_t highmatch = 2;
//printf("b=%i r=%i(%i)-%i(%i)\n",bucket,low,read2(data+index[low],datalen-index[low]),high,read2(data+index[high],datalen-index[high]));
//fflush(stdout);
while (true)
{
off_t mid = low + (high-low)/2;
off_t midpos = index[mid];
if (midpos == pos) return mid;
//printf("r=[%i]%i-%i \n",high-low,low,high,);
//fflush(stdout);
#ifdef TEST_CORRECT
if (low >= high)
{
printf("E: [%i](%i): stuck at %i(%i)-%i(%i)\n", pos, read2_uc(data+pos),
low, read2_uc(data+index[low]), high, read2_uc(data+index[high]));
int n=0;
while (index[n]!=pos) n++;
printf("correct one is %i(%i)\n",n, read2_uc(data+index[n]));
abort();
}
#endif
off_t matchlenstart = min(lowmatch, highmatch);
off_t len = datalen - max(pos, midpos) - matchlenstart;
const uint8_t* search = data+pos+matchlenstart;
const uint8_t* here = data+midpos+matchlenstart;
while (len>0 && *search==*here)
{
search++;
here++;
len--;
}
off_t matchlen = search-data-pos;
bool less;
if (len > 0) less = (*here<*search);
else less = (here > search) ^ EOF_IS_LAST;
if (less)
{
low = mid+1;
lowmatch = matchlen;
}
else
{
high = mid-1;
highmatch = matchlen;
}
if (low+256 > high)
{
off_t i=low;
while (true)
{
if (index[i]==pos) return i;
i++;
}
}
}
}
template<typename off_t>
static void create_reverse_index(off_t* index, off_t* reverse, off_t len)
{
//testcase: linux 3.18.14 -> 4.0.4 .xz
//without: real23.544 user32.930
//with: real22.636 user40.168
//'user' jumps up quite a lot, while 'real' only moves a short bit
//I'm not sure why the tradeoff is so bad (do the cachelines bounce THAT badly?), but I deem it not worth it.
//#pragma omp parallel for
for (off_t i=0;i<len;i++) reverse[index[i]]=i;
}
template<typename off_t>
static off_t nextsize(off_t outpos, off_t sortedsize, off_t targetlen)
{
while (outpos >= sortedsize-256 && sortedsize < targetlen)
sortedsize = min(sortedsize*4+3, targetlen);
return sortedsize;
}
template<typename off_t>
off_t lerp(off_t x, off_t y, float frac)
{
return x + (y-x)*frac;
}
template<typename off_t>
static bpserror bps_create_suf_core(file* source, file* target, bool moremem, struct bps_creator * out)
{
#define error(which) do { err = which; goto error; } while(0)
bpserror err;
size_t realsourcelen = source->len;
size_t realtargetlen = target->len;
size_t overflowtest = realsourcelen + realtargetlen;
//source+target length is bigger than size_t
if (overflowtest < realsourcelen) return bps_too_big;
//source+target doesn't fit in unsigned off_t
if ((size_t)(off_t)overflowtest != overflowtest) return bps_too_big;
//source+target doesn't fit in signed off_t
if ((off_t)overflowtest < 0) return bps_too_big;
//the mallocs would overflow
if (realsourcelen+realtargetlen >= SIZE_MAX/sizeof(off_t)) return bps_too_big;
if (realsourcelen+realtargetlen >= out->maxsize()) return bps_too_big;
off_t sourcelen = realsourcelen;
off_t targetlen = realtargetlen;
uint8_t* mem_joined = (uint8_t*)malloc(sizeof(uint8_t)*(realsourcelen+realtargetlen));
off_t* sorted = (off_t*)malloc(sizeof(off_t)*(realsourcelen+realtargetlen));
off_t* sorted_inverse = NULL;
if (moremem) sorted_inverse = (off_t*)malloc(sizeof(off_t)*(realsourcelen+realtargetlen));
off_t* buckets = NULL;
if (!sorted_inverse) buckets = (off_t*)malloc(sizeof(off_t)*65537);
if (!sorted || !mem_joined || (!sorted_inverse && !buckets))
{
free(mem_joined);
free(sorted);
free(sorted_inverse);
free(buckets);
return bps_out_of_mem;
}
//sortedsize is how much of the target file is sorted
off_t sortedsize = targetlen;
//divide by 4 for each iteration, to avoid sorting 50% of the file (the sorter is slow)
while (sortedsize/4 > sourcelen && sortedsize > 1024) sortedsize >>= 2;
off_t prevsortedsize = 0;
off_t outpos = 0;
goto reindex; // jump into the middle so I won't need a special case to enter it
while (outpos < targetlen)
{
if (outpos >= sortedsize-256 && sortedsize < targetlen)
{
sortedsize = nextsize(outpos, sortedsize, targetlen);
reindex:
//this isn't an exact science
const float percSort = sorted_inverse ? 0.67 : 0.50;
const float percInv = sorted_inverse ? 0.11 : 0.10;
//const float percFind = sorted_inverse ? 0.22 : 0.40; // unused
const size_t progPreSort = lerp(prevsortedsize, sortedsize, 0);
const size_t progPreInv = lerp(prevsortedsize, sortedsize, percSort);
const size_t progPreFind = lerp(prevsortedsize, sortedsize, percSort+percInv);
prevsortedsize = sortedsize;
if (!out->progress(progPreSort, targetlen)) error(bps_canceled);
if (target->read(mem_joined, 0, sortedsize) < (size_t)sortedsize) error(bps_io);
if (source->read(mem_joined+sortedsize, 0, sourcelen) < (size_t)sourcelen) error(bps_io);
out->move_target(mem_joined);
sufsort(sorted, mem_joined, sortedsize+sourcelen);
if (!out->progress(progPreInv, targetlen)) error(bps_canceled);
if (sorted_inverse)
create_reverse_index(sorted, sorted_inverse, sortedsize+sourcelen);
else
create_buckets(mem_joined, sorted, sortedsize+sourcelen, buckets);
if (!out->progress(progPreFind, targetlen)) error(bps_canceled);
}
off_t matchlen = 0;
off_t matchpos = adjust_match(find_index(outpos, mem_joined, sortedsize+sourcelen, sorted, sorted_inverse, buckets),
mem_joined+outpos, sortedsize-outpos,
mem_joined,sortedsize+sourcelen, outpos,sortedsize,
sorted, sortedsize+sourcelen,
&matchlen);
#ifdef TEST_CORRECT
if (matchlen && matchpos >= outpos && matchpos < sortedsize) puts("ERROR: found match in invalid location"),abort();
if (memcmp(mem_joined+matchpos, mem_joined+outpos, matchlen)) puts("ERROR: found match doesn't match"),abort();
#endif
off_t taken;
if (matchpos >= sortedsize) taken = out->match(false, matchpos-sortedsize, matchlen);
else taken = out->match(true, matchpos, matchlen);
#ifdef TEST_CORRECT
if (taken < 0) puts("ERROR: match() returned negative"),abort();
if (matchlen >= 7 && taken < matchlen) printf("ERROR: match() took %i bytes, offered %i\n", taken, matchlen),abort();
#endif
outpos += taken;
}
out->finish(mem_joined+sortedsize, mem_joined);
err = bps_ok;
error:
free(buckets);
free(sorted_inverse);
free(sorted);
free(mem_joined);
return err;
}
template<typename T> static bpserror bps_create_suf_pick(file* source, file* target, bool moremem, struct bps_creator * bps);
template<> bpserror bps_create_suf_pick<uint32_t>(file* source, file* target, bool moremem, struct bps_creator * bps)
{
return bps_create_suf_core<int32_t>(source, target, moremem, bps);
}
template<> bpserror bps_create_suf_pick<uint64_t>(file* source, file* target, bool moremem, struct bps_creator * bps)
{
bpserror err = bps_create_suf_core<int32_t>(source, target, moremem, bps);
if (err==bps_too_big) err = bps_create_suf_core<int64_t>(source, target, moremem, bps);
return err;
}
//This one picks a function based on 32-bit integers if that fits. This halves memory use for common inputs.
//It also handles some stuff related to the BPS headers and footers.
extern "C"
bpserror bps_create_delta(file* source, file* target, struct mem metadata, struct mem * patchmem,
bool (*progress)(void* userdata, size_t done, size_t total), void* userdata, bool moremem)
{
bps_creator bps(source, target, metadata);
bps.setProgress(progress, userdata);
size_t maindata = bps.outlen;
//off_t must be signed
bpserror err = bps_create_suf_pick<size_t>(source, target, moremem, &bps);
if (err!=bps_ok) return err;
*patchmem = bps.getpatch();
while ((patchmem->ptr[maindata]&0x80) == 0x00) maindata++;
if (maindata==patchmem->len-12-1) return bps_identical;
return bps_ok;
}
#ifdef BPS_STANDALONE
#include <stdio.h>
static struct mem ReadWholeFile(const char * filename)
{
struct mem null = {NULL, 0};
FILE * file=fopen(filename, "rb");
if (!file) return null;
fseek(file, 0, SEEK_END);
size_t len=ftell(file);
fseek(file, 0, SEEK_SET);
unsigned char * data=(unsigned char*)malloc(len);
size_t truelen=fread(data, 1,len, file);
fclose(file);
if (len!=truelen)
{
free(data);
return null;
}
struct mem ret = { (unsigned char*)data, len };
return ret;
}
static bool WriteWholeFile(const char * filename, struct mem data)
{
FILE * file=fopen(filename, "wb");
if (!file) return false;
unsigned int truelen=fwrite(data.ptr, 1,data.len, file);
fclose(file);
return (truelen==data.len);
}
int main(int argc, char * argv[])
{
//struct mem out = ReadWholeFile(argv[2]);
//printf("check=%.8X\n",crc32(out.ptr, out.len));
struct mem in = ReadWholeFile(argv[1]);
struct mem out = ReadWholeFile(argv[2]);
struct mem null = {NULL, 0};
struct mem p={NULL,0};
//int n=50;
//for(int i=0;i<n;i++)
//printf("%i/%i\n",i,n),
bps_create_delta(in,out,null,&p, NULL,NULL);
printf("len=%lu \n",p.len);
printf("check=%.8X\n",*(uint32_t*)(p.ptr+p.len-4));
WriteWholeFile(argv[3], p);
free(in.ptr);
free(out.ptr);
free(p.ptr);
#ifdef TEST_PERF
printf("%i/%i=%f\n",match_len_tot,match_len_n,(float)match_len_tot/match_len_n);
#endif
}
#endif

614
libbps.cpp Normal file
View File

@ -0,0 +1,614 @@
#include "libbps.h"
#include <stdlib.h>//malloc, realloc, free
#include <string.h>//memcpy, memset
#include <stdint.h>//uint8_t, uint32_t
#include "arlib/crc32.h"//crc32
#include "arlib/file.h"//file
static uint32_t read32(uint8_t * ptr)
{
uint32_t out;
out =ptr[0];
out|=ptr[1]<<8;
out|=ptr[2]<<16;
out|=ptr[3]<<24;
return out;
}
enum { SourceRead, TargetRead, SourceCopy, TargetCopy };
static bool try_add(size_t& a, size_t b)
{
if (SIZE_MAX-a < b) return false;
a+=b;
return true;
}
static bool try_shift(size_t& a, size_t b)
{
if (SIZE_MAX>>b < a) return false;
a<<=b;
return true;
}
static bool decodenum(const uint8_t*& ptr, size_t& out)
{
out=0;
unsigned int shift=0;
while (true)
{
uint8_t next=*ptr++;
size_t addthis=(next&0x7F);
if (shift) addthis++;
if (!try_shift(addthis, shift)) return false;
// unchecked because if it was shifted, the lowest bit is zero, and if not, it's <=0x7F.
if (!try_add(out, addthis)) return false;
if (next&0x80) return true;
shift+=7;
}
}
#define error(which) do { error=which; goto exit; } while(0)
#define assert_sum(a,b) do { if (SIZE_MAX-(a)<(b)) error(bps_too_big); } while(0)
#define assert_shift(a,b) do { if (SIZE_MAX>>(b)<(a)) error(bps_too_big); } while(0)
enum bpserror bps_apply(struct mem patch, struct mem in, struct mem * out, struct mem * metadata, bool accept_wrong_input)
{
enum bpserror error = bps_ok;
out->len=0;
out->ptr=NULL;
if (metadata)
{
metadata->len=0;
metadata->ptr=NULL;
}
if (patch.len<4+3+12) return bps_broken;
if (true)
{
#define read8() (*(patchat++))
#define decodeto(var) \
do { \
if (!decodenum(patchat, var)) error(bps_too_big); \
} while(false)
#define write8(byte) (*(outat++)=byte)
const uint8_t * patchat=patch.ptr;
const uint8_t * patchend=patch.ptr+patch.len-12;
if (read8()!='B') error(bps_broken);
if (read8()!='P') error(bps_broken);
if (read8()!='S') error(bps_broken);
if (read8()!='1') error(bps_broken);
uint32_t crc_in_e = read32(patch.ptr+patch.len-12);
uint32_t crc_out_e = read32(patch.ptr+patch.len-8);
uint32_t crc_patch_e = read32(patch.ptr+patch.len-4);
uint32_t crc_in_a = crc32(in.ptr, in.len);
uint32_t crc_patch_a = crc32(patch.ptr, patch.len-4);
if (crc_patch_a != crc_patch_e) error(bps_broken);
size_t inlen;
decodeto(inlen);
size_t outlen;
decodeto(outlen);
if (inlen!=in.len || crc_in_a!=crc_in_e)
{
if (in.len==outlen && crc_in_a==crc_out_e) error=bps_to_output;
else error=bps_not_this;
if (!accept_wrong_input) goto exit;
}
out->len=outlen;
out->ptr=(uint8_t*)malloc(outlen);
const uint8_t * instart=in.ptr;
const uint8_t * inreadat=in.ptr;
const uint8_t * inend=in.ptr+in.len;
uint8_t * outstart=out->ptr;
uint8_t * outreadat=out->ptr;
uint8_t * outat=out->ptr;
uint8_t * outend=out->ptr+out->len;
size_t metadatalen;
decodeto(metadatalen);
if (metadata && metadatalen)
{
metadata->len=metadatalen;
metadata->ptr=(uint8_t*)malloc(metadatalen+1);
for (size_t i=0;i<metadatalen;i++) metadata->ptr[i]=read8();
metadata->ptr[metadatalen]='\0';//just to be on the safe side - that metadata is assumed to be text, might as well terminate it
}
else
{
for (size_t i=0;i<metadatalen;i++) (void)read8();
}
while (patchat<patchend)
{
size_t thisinstr;
decodeto(thisinstr);
size_t length=(thisinstr>>2)+1;
int action=(thisinstr&3);
if (outat+length>outend) error(bps_broken);
switch (action)
{
case SourceRead:
{
if (outat-outstart+length > in.len) error(bps_broken);
for (size_t i=0;i<length;i++)
{
size_t pos = outat-outstart; // don't inline, write8 changes outat
write8(instart[pos]);
}
}
break;
case TargetRead:
{
if (patchat+length>patchend) error(bps_broken);
for (size_t i=0;i<length;i++) write8(read8());
}
break;
case SourceCopy:
{
size_t encodeddistance;
decodeto(encodeddistance);
size_t distance=encodeddistance>>1;
if ((encodeddistance&1)==0) inreadat+=distance;
else inreadat-=distance;
if (inreadat<instart || inreadat+length>inend) error(bps_broken);
for (size_t i=0;i<length;i++) write8(*inreadat++);
}
break;
case TargetCopy:
{
size_t encodeddistance;
decodeto(encodeddistance);
size_t distance=encodeddistance>>1;
if ((encodeddistance&1)==0) outreadat+=distance;
else outreadat-=distance;
if (outreadat<outstart || outreadat>=outat || outreadat+length>outend) error(bps_broken);
for (size_t i=0;i<length;i++) write8(*outreadat++);
}
break;
}
}
if (patchat!=patchend) error(bps_broken);
if (outat!=outend) error(bps_broken);
uint32_t crc_out_a = crc32(out->ptr, out->len);
if (crc_out_a!=crc_out_e)
{
error=bps_not_this;
if (!accept_wrong_input) goto exit;
}
return error;
#undef read8
#undef decodeto
#undef write8
}
exit:
free(out->ptr);
out->len=0;
out->ptr=NULL;
if (metadata)
{
free(metadata->ptr);
metadata->len=0;
metadata->ptr=NULL;
}
return error;
}
#define write(val) \
do { \
out[outlen++]=(val); \
if (outlen==outbuflen) \
{ \
outbuflen*=2; \
out=(uint8_t*)realloc(out, outbuflen); \
} \
} while(0)
#define write32(val) \
do { \
uint32_t tmp=(val); \
write(tmp); \
write(tmp>>8); \
write(tmp>>16); \
write(tmp>>24); \
} while(0)
#define writenum(val) \
do { \
size_t tmpval=(val); \
while (true) \
{ \
uint8_t tmpbyte=(tmpval&0x7F); \
tmpval>>=7; \
if (!tmpval) \
{ \
write(tmpbyte|0x80); \
break; \
} \
write(tmpbyte); \
tmpval--; \
} \
} while(0)
enum bpserror bps_create_linear(struct mem sourcemem, struct mem targetmem, struct mem metadata, struct mem * patchmem)
{
if (sourcemem.len>=(SIZE_MAX>>2) - 16) return bps_too_big;//the 16 is just to be on the safe side, I don't think it's needed.
if (targetmem.len>=(SIZE_MAX>>2) - 16) return bps_too_big;
const uint8_t * source=sourcemem.ptr;
const uint8_t * sourceend=sourcemem.ptr+sourcemem.len;
if (sourcemem.len>targetmem.len) sourceend=sourcemem.ptr+targetmem.len;
const uint8_t * targetbegin=targetmem.ptr;
const uint8_t * target=targetmem.ptr;
const uint8_t * targetend=targetmem.ptr+targetmem.len;
const uint8_t * targetcopypos=targetbegin;
size_t outbuflen=4096;
uint8_t * out=(uint8_t*)malloc(outbuflen);
size_t outlen=0;
write('B');
write('P');
write('S');
write('1');
writenum(sourcemem.len);
writenum(targetmem.len);
writenum(metadata.len);
for (size_t i=0;i<metadata.len;i++) write(metadata.ptr[i]);
size_t mainContentPos=outlen;
const uint8_t * lastknownchange=targetbegin;
while (target<targetend)
{
size_t numunchanged=0;
while (source+numunchanged<sourceend && source[numunchanged]==target[numunchanged]) numunchanged++;
if (numunchanged>1)
{
//assert_shift((numunchanged-1), 2);
writenum((numunchanged-1)<<2 | 0);//SourceRead
source+=numunchanged;
target+=numunchanged;
}
size_t numchanged=0;
if (lastknownchange>target) numchanged=lastknownchange-target;
while ((source+numchanged>=sourceend ||
source[numchanged]!=target[numchanged] ||
source[numchanged+1]!=target[numchanged+1] ||
source[numchanged+2]!=target[numchanged+2]) &&
target+numchanged<targetend)
{
numchanged++;
if (source+numchanged>=sourceend) numchanged=targetend-target;
}
lastknownchange=target+numchanged;
if (numchanged)
{
//assert_shift((numchanged-1), 2);
size_t rle1start=(target==targetbegin);
while (true)
{
if (
target[rle1start-1]==target[rle1start+0] &&
target[rle1start+0]==target[rle1start+1] &&
target[rle1start+1]==target[rle1start+2] &&
target[rle1start+2]==target[rle1start+3])
{
numchanged=rle1start;
break;
}
if (
target[rle1start-2]==target[rle1start+0] &&
target[rle1start-1]==target[rle1start+1] &&
target[rle1start+0]==target[rle1start+2] &&
target[rle1start+1]==target[rle1start+3] &&
target[rle1start+2]==target[rle1start+4])
{
numchanged=rle1start;
break;
}
if (rle1start+3>=numchanged) break;
rle1start++;
}
if (numchanged)
{
writenum((numchanged-1)<<2 | TargetRead);
for (size_t i=0;i<numchanged;i++)
{
write(target[i]);
}
source+=numchanged;
target+=numchanged;
}
if (target[-2]==target[0] && target[-1]==target[1] && target[0]==target[2])
{
//two-byte RLE
size_t rlelen=0;
while (target+rlelen<targetend && target[0]==target[rlelen+0] && target[1]==target[rlelen+1]) rlelen+=2;
writenum((rlelen-1)<<2 | TargetCopy);
writenum((target-targetcopypos-2)<<1);
source+=rlelen;
target+=rlelen;
targetcopypos=target-2;
}
else if (target[-1]==target[0] && target[0]==target[1])
{
//one-byte RLE
size_t rlelen=0;
while (target+rlelen<targetend && target[0]==target[rlelen]) rlelen++;
writenum((rlelen-1)<<2 | TargetCopy);
writenum((target-targetcopypos-1)<<1);
source+=rlelen;
target+=rlelen;
targetcopypos=target-1;
}
}
}
write32(crc32(sourcemem.ptr, sourcemem.len));
write32(crc32(targetmem.ptr, targetmem.len));
write32(crc32(out, outlen));
patchmem->ptr=out;
patchmem->len=outlen;
//while this may look like it can be fooled by a patch containing one of any other command, it
// can't, because the ones that aren't SourceRead requires an argument.
size_t i;
for (i=mainContentPos;(out[i]&0x80)==0x00;i++) {}
if (i==outlen-12-1) return bps_identical;
return bps_ok;
}
#undef write_nocrc
#undef write
#undef writenum
void bps_free(struct mem mem)
{
free(mem.ptr);
}
#undef error
struct bpsinfo bps_get_info(file* patch, bool changefrac)
{
#define error(why) do { ret.error=why; return ret; } while(0)
struct bpsinfo ret;
size_t len = patch->len;
if (len<4+3+12) error(bps_broken);
uint8_t top[256];
size_t toplen = len>256 ? 256 : len;
if (patch->read(top, 0, toplen) < toplen) error(bps_io);
if (memcmp(top, "BPS1", 4)) error(bps_broken);
const uint8_t* patchdat=top+4;
if (!decodenum(patchdat, ret.size_in)) error(bps_too_big);
if (!decodenum(patchdat, ret.size_out)) error(bps_too_big);
uint8_t checksums[12];
if (patch->read(checksums, len-12, 12) < 12) error(bps_io);
ret.crc_in = read32(checksums+0);
ret.crc_out = read32(checksums+4);
ret.crc_patch=read32(checksums+8);
if (changefrac && ret.size_in>0)
{
//algorithm: each command adds its length to the numerator, unless it's above 32, in which case
// it adds 32; or if it's SourceRead, in which case it adds 0
//denominator is just input length
uint8_t* patchbin=(uint8_t*)malloc(len);
if (patch->read(patchbin, 0, len) < len) error(bps_io);
size_t outpos=0; // position in the output file
size_t changeamt=0; // change score
const uint8_t* patchat=patchbin+(patchdat-top);
size_t metasize;
if (!decodenum(patchat, metasize)) error(bps_too_big);
patchat+=metasize;
const uint8_t* patchend=patchbin+len-12;
while (patchat<patchend && outpos<ret.size_in)
{
size_t thisinstr;
decodenum(patchat, thisinstr);
size_t length=(thisinstr>>2)+1;
int action=(thisinstr&3);
int min_len_32 = (length<32 ? length : 32);
switch (action)
{
case SourceRead:
{
changeamt+=0;
}
break;
case TargetRead:
{
changeamt+=min_len_32;
patchat+=length;
}
break;
case SourceCopy:
case TargetCopy:
{
changeamt+=min_len_32;
size_t ignore;
decodenum(patchat, ignore);
}
break;
}
outpos+=length;
}
if (patchat>patchend || outpos>ret.size_out) error(bps_broken);
ret.change_num = (changeamt<ret.size_in ? changeamt : ret.size_in);
ret.change_denom = ret.size_in;
free(patchbin);
}
else
{
//this also happens if change fraction is not requested, but it's undefined behaviour anyways.
ret.change_num=1;
ret.change_denom=1;
}
ret.error=bps_ok;
return ret;
}
#if 0
#warning Disable this in release versions.
#include <stdio.h>
//Congratulations, you found the undocumented feature! It compares two equivalent BPS patches and
// tells where each one is more compact. (It crashes or gives bogus answers on invalid or
// non-equivalent patches.) Have fun.
void bps_compare(struct mem patch1mem, struct mem patch2mem)
{
const uint8_t * patch[2]={patch1mem.ptr, patch2mem.ptr};
size_t patchpos[2]={0,0};
size_t patchlen[2]={patch1mem.len-12, patch2mem.len-12};
size_t patchoutpos[2]={0,0};
size_t patchcopypos[2][4]={0,0};//[0] and [1] are unused, but this is just debug code, it doesn't need to be neat.
#define read8(id) (patch[id][patchpos[id]++])
#define decodeto(id, var) \
do { \
var=0; \
int shift=0; \
while (true) \
{ \
uint8_t next=read8(id); \
size_t addthis=(next&0x7F)<<shift; \
var+=addthis; \
if (next&0x80) break; \
shift+=7; \
var+=1<<shift; \
} \
} while(false)
size_t lastmatch=0;
size_t patchposatmatch[2]={0,0};
size_t outlen;
patch[0]+=4; patch[1]+=4;//BPS1
size_t tempuint;
decodeto(0, tempuint); decodeto(1, tempuint);//source-size
decodeto(0, outlen); decodeto(1, outlen);//target-size
decodeto(0, tempuint); patch[0]+=tempuint;//metadata
decodeto(1, tempuint); patch[1]+=tempuint;//metadata
bool show=false;
while (patchpos[0]<patchlen[0] && patchpos[1]<patchlen[1])
{
bool step[2]={(patchoutpos[0]<=patchoutpos[1]), (patchoutpos[0]>=patchoutpos[1])};
char describe[2][256];
for (int i=0;i<2;i++)
{
if (step[i])
{
size_t patchposstart=patchpos[i];
decodeto(i, tempuint);
size_t len=(tempuint>>2)+1;
patchoutpos[i]+=len;
int action=(tempuint&3);
//enum { SourceRead, TargetRead, SourceCopy, TargetCopy };
const char * actionnames[]={"SourceRead", "TargetRead", "SourceCopy", "TargetCopy"};
if (action==TargetRead) patchpos[i]+=len;
if (action==SourceCopy || action==TargetCopy)
{
decodeto(i, tempuint);
int delta = tempuint>>1;
if (tempuint&1) delta=-delta;
patchcopypos[i][action]+=delta;
sprintf(describe[i], "%s from %i (%+i) for %i in %i", actionnames[action], patchcopypos[i][action], delta, len, patchpos[i]-patchposstart);
patchcopypos[i][action]+=len;
}
else sprintf(describe[i], "%s from %i for %i in %i", actionnames[action], patchoutpos[i], len, patchpos[i]-patchposstart);
if (!step[i^1])
{
printf("%i: %s\n", i+1, describe[i]);
show=true;
}
}
}
if (step[0] && step[1])
{
if (!strcmp(describe[0], describe[1])) /*printf("3: %s\n", describe[0])*/;
else
{
printf("1: %s\n2: %s\n", describe[0], describe[1]);
show=true;
}
}
if (patchoutpos[0]==patchoutpos[1])
{
size_t used[2]={patchpos[0]-patchposatmatch[0], patchpos[1]-patchposatmatch[1]};
char which='=';
if (used[0]<used[1]) which='+';
if (used[0]>used[1]) which='-';
if (show)
{
printf("%c: %i,%i bytes since last match (%i)\n", which, used[0], used[1], patchoutpos[0]);
show=false;
}
patchposatmatch[0]=patchpos[0];
patchposatmatch[1]=patchpos[1];
lastmatch=patchoutpos[0];
}
}
}
static struct mem ReadWholeFile(const char * filename)
{
struct mem null = {NULL, 0};
FILE * file=fopen(filename, "rb");
if (!file) return null;
fseek(file, 0, SEEK_END);
size_t len=ftell(file);
fseek(file, 0, SEEK_SET);
unsigned char * data=(unsigned char*)malloc(len);
size_t truelen=fread(data, 1,len, file);
fclose(file);
if (len!=truelen)
{
free(data);
return null;
}
struct mem ret = { (unsigned char*)data, len };
return ret;
}
int main(int argc,char**argv)
{
bps_compare(ReadWholeFile(argv[1]),ReadWholeFile(argv[2]));
}
#endif

81
libbps.h Normal file
View File

@ -0,0 +1,81 @@
#include "global.h"
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
struct file; // For C, you can pass this around, but you can't really use it. But you can still use the other functions.
enum bpserror {
bps_ok,//Patch applied or created successfully.
bps_to_output,//You attempted to apply a patch to its output.
bps_not_this, //This is not the intended input file for this patch.
bps_broken, //This is not a BPS patch, or it's malformed somehow.
bps_io, //The patch could not be read.
bps_identical, //The input files are identical.
bps_too_big, //Somehow, you're asking for something a size_t can't represent.
bps_out_of_mem,//Memory allocation failure.
bps_canceled, //The callback returned false.
bps_shut_up_gcc//This one isn't used, it's just to kill a stray comma warning.
};
//Applies the given BPS patch to the given ROM and puts it in 'out'. Metadata, if present and
// requested ('metadata'!=NULL), is also returned. Send both to bps_free when you're done with them.
//If accept_wrong_input is true, it may return bps_to_output or bps_not_this, while putting non-NULL in out/metadata.
enum bpserror bps_apply(struct mem patch, struct mem in, struct mem * out, struct mem * metadata, bool accept_wrong_input);
//Creates a BPS patch that converts source to target and stores it to patch. It is safe to give
// {NULL,0} as metadata.
enum bpserror bps_create_linear(struct mem source, struct mem target, struct mem metadata, struct mem * patch);
//Very similar to bps_create_linear; the difference is that this one takes longer to run, but
// generates smaller patches.
//Because it can take much longer, a progress meter is supplied; total is guaranteed to be constant
// between every call until this function returns, done is guaranteed to increase between each
// call, and done/total is an approximate percentage counter. Anything else is undefined; for
// example, progress may or may not be called for done=0, progress may or may not be called for
// done=total, done may or may not increase by the same amount between each call, and the duration
// between each call may or may not be constant.
//To cancel the patch creation, return false from the callback.
//It is safe to pass in NULL for the progress indicator if you're not interested. If the callback is
// NULL, it can obviously not be canceled that way (though if it's a CLI program, you can always
// Ctrl-C it).
//The 'moremem' flag makes it use about twice as much memory (9*(source+target) instead of 5*), but is usually slightly faster.
enum bpserror bps_create_delta(file* source, file* target, struct mem metadata, struct mem * patch,
bool (*progress)(void* userdata, size_t done, size_t total), void* userdata,
bool moremem);
//Frees the memory returned in the output parameters of the above. Do not call it twice on the same
// input, nor on anything you got from anywhere else. bps_free is guaranteed to be equivalent to
// calling stdlib.h's free() on mem.ptr.
void bps_free(struct mem mem);
struct bpsinfo {
enum bpserror error; // If this is not bps_ok, all other values are undefined.
size_t size_in;
size_t size_out;
uint32_t crc_in;
uint32_t crc_out;
uint32_t crc_patch;
//Tells approximately how much of the input ROM is changed compared to the output ROM.
//It's quite heuristic. The algorithm may change with or without notice.
//As of writing, I believe this is accurate to 2 significant digits in base 10.
//It's also more expensive to calculate than the other data, so it's optional.
//If you don't want it, their values are undefined.
//The denominator is always guaranteed nonzero, even if something else says it's undefined.
//Note that this can return success for invalid patches.
size_t change_num;
size_t change_denom;
};
struct bpsinfo bps_get_info(file* patch, bool changefrac);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,3 @@
-- AUTHORS for libdivsufsort
Yuta Mori <yuta.256@gmail.com>

View File

@ -0,0 +1,101 @@
### cmake file for building libdivsufsort Package ###
cmake_minimum_required(VERSION 2.4)
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules")
include(AppendCompilerFlags)
## SVN revision ##
set(SVN_REVISION "")
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.svn")
execute_process(COMMAND svn info --xml
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
OUTPUT_VARIABLE SVN_INFO ERROR_QUIET)
if(SVN_INFO)
string(REGEX MATCH "<entry[^>]+" SVN_REVISION "${SVN_INFO}")
string(REGEX REPLACE "^.*revision=\"([0-9]+)\".*$" "\\1" SVN_REVISION "${SVN_REVISION}")
endif(SVN_INFO)
endif(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.svn")
## Project information ##
project(libdivsufsort C)
set(PROJECT_VENDOR "Yuta Mori")
set(PROJECT_CONTACT "yuta.256@gmail.com")
set(PROJECT_URL "http://libdivsufsort.googlecode.com/")
set(PROJECT_DESCRIPTION "A lightweight suffix sorting library")
file(READ "${CMAKE_CURRENT_SOURCE_DIR}/VERSION" PROJECT_VERSION_FULL)
string(REGEX REPLACE "[\n\r]" "" PROJECT_VERSION_FULL "${PROJECT_VERSION_FULL}")
string(REGEX REPLACE "^([0-9]+)\\.[0-9]+\\.[0-9]+$" "\\1" PROJECT_VERSION_MAJOR "${PROJECT_VERSION_FULL}")
string(REGEX REPLACE "^[0-9]+\\.([0-9]+)\\.[0-9]+$" "\\1" PROJECT_VERSION_MINOR "${PROJECT_VERSION_FULL}")
string(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.([0-9]+)$" "\\1" PROJECT_VERSION_PATCH "${PROJECT_VERSION_FULL}")
set(PROJECT_VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}")
math(EXPR LIBRARY_VERSION_MAJOR "1 + ${PROJECT_VERSION_MAJOR}")
set(LIBRARY_VERSION_MINOR "${PROJECT_VERSION_MINOR}")
set(LIBRARY_VERSION_PATCH "${PROJECT_VERSION_PATCH}")
set(LIBRARY_VERSION "${LIBRARY_VERSION_MAJOR}.${LIBRARY_VERSION_MINOR}")
set(LIBRARY_VERSION_FULL "${LIBRARY_VERSION}.${LIBRARY_VERSION_PATCH}")
if(SVN_REVISION)
set(PROJECT_VERSION_FULL "svn-r${SVN_REVISION}")
endif(SVN_REVISION)
## CPack configuration ##
set(CPACK_GENERATOR "TGZ;TBZ2;ZIP")
set(CPACK_SOURCE_GENERATOR "TGZ;TBZ2;ZIP")
include(ProjectCPack)
## Project options ##
option(BUILD_SHARED_LIBS "Set to OFF to build static libraries" ON)
option(BUILD_EXAMPLES "Build examples" ON)
option(BUILD_DIVSUFSORT64 "Build libdivsufsort64" OFF)
option(USE_OPENMP "Use OpenMP for parallelization" OFF)
option(WITH_LFS "Enable Large File Support" ON)
## Build type ##
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Release")
elseif(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(CMAKE_VERBOSE_MAKEFILE ON)
endif(NOT CMAKE_BUILD_TYPE)
## Compiler options ##
if(MSVC)
append_c_compiler_flags("/W4" "VC" CMAKE_C_FLAGS)
append_c_compiler_flags("/Oi;/Ot;/Ox;/Oy" "VC" CMAKE_C_FLAGS_RELEASE)
if(USE_OPENMP)
append_c_compiler_flags("/openmp" "VC" CMAKE_C_FLAGS)
endif(USE_OPENMP)
elseif(BORLAND)
append_c_compiler_flags("-w" "BCC" CMAKE_C_FLAGS)
append_c_compiler_flags("-Oi;-Og;-Os;-Ov;-Ox" "BCC" CMAKE_C_FLAGS_RELEASE)
else(MSVC)
if(CMAKE_COMPILER_IS_GNUCC)
append_c_compiler_flags("-Wall" "GCC" CMAKE_C_FLAGS)
append_c_compiler_flags("-fomit-frame-pointer" "GCC" CMAKE_C_FLAGS_RELEASE)
if(USE_OPENMP)
append_c_compiler_flags("-fopenmp" "GCC" CMAKE_C_FLAGS)
endif(USE_OPENMP)
else(CMAKE_COMPILER_IS_GNUCC)
append_c_compiler_flags("-Wall" "UNKNOWN" CMAKE_C_FLAGS)
append_c_compiler_flags("-fomit-frame-pointer" "UNKNOWN" CMAKE_C_FLAGS_RELEASE)
if(USE_OPENMP)
append_c_compiler_flags("-fopenmp;-openmp;-omp" "UNKNOWN" CMAKE_C_FLAGS)
endif(USE_OPENMP)
endif(CMAKE_COMPILER_IS_GNUCC)
endif(MSVC)
## Add definitions ##
add_definitions(-DHAVE_CONFIG_H=1 -D__STDC_LIMIT_MACROS -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS)
## Add subdirectories ##
add_subdirectory(pkgconfig)
add_subdirectory(include)
add_subdirectory(lib)
if(BUILD_EXAMPLES)
add_subdirectory(examples)
endif(BUILD_EXAMPLES)
## Add 'uninstall' target ##
CONFIGURE_FILE(
"${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules/cmake_uninstall.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/CMakeModules/cmake_uninstall.cmake"
IMMEDIATE @ONLY)
ADD_CUSTOM_TARGET(uninstall
"${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/CMakeModules/cmake_uninstall.cmake")

View File

@ -0,0 +1,38 @@
include(CheckCSourceCompiles)
include(CheckCXXSourceCompiles)
macro(append_c_compiler_flags _flags _name _result)
set(SAFE_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS})
string(REGEX REPLACE "[-+/ ]" "_" cname "${_name}")
string(TOUPPER "${cname}" cname)
foreach(flag ${_flags})
string(REGEX REPLACE "^[-+/ ]+(.*)[-+/ ]*$" "\\1" flagname "${flag}")
string(REGEX REPLACE "[-+/ ]" "_" flagname "${flagname}")
string(TOUPPER "${flagname}" flagname)
set(have_flag "HAVE_${cname}_${flagname}")
set(CMAKE_REQUIRED_FLAGS "${flag}")
check_c_source_compiles("int main() { return 0; }" ${have_flag})
if(${have_flag})
set(${_result} "${${_result}} ${flag}")
endif(${have_flag})
endforeach(flag)
set(CMAKE_REQUIRED_FLAGS ${SAFE_CMAKE_REQUIRED_FLAGS})
endmacro(append_c_compiler_flags)
macro(append_cxx_compiler_flags _flags _name _result)
set(SAFE_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS})
string(REGEX REPLACE "[-+/ ]" "_" cname "${_name}")
string(TOUPPER "${cname}" cname)
foreach(flag ${_flags})
string(REGEX REPLACE "^[-+/ ]+(.*)[-+/ ]*$" "\\1" flagname "${flag}")
string(REGEX REPLACE "[-+/ ]" "_" flagname "${flagname}")
string(TOUPPER "${flagname}" flagname)
set(have_flag "HAVE_${cname}_${flagname}")
set(CMAKE_REQUIRED_FLAGS "${flag}")
check_cxx_source_compiles("int main() { return 0; }" ${have_flag})
if(${have_flag})
set(${_result} "${${_result}} ${flag}")
endif(${have_flag})
endforeach(flag)
set(CMAKE_REQUIRED_FLAGS ${SAFE_CMAKE_REQUIRED_FLAGS})
endmacro(append_cxx_compiler_flags)

View File

@ -0,0 +1,15 @@
include(CheckCSourceCompiles)
macro(check_function_keywords _wordlist)
set(${_result} "")
foreach(flag ${_wordlist})
string(REGEX REPLACE "[-+/ ()]" "_" flagname "${flag}")
string(TOUPPER "${flagname}" flagname)
set(have_flag "HAVE_${flagname}")
check_c_source_compiles("${flag} void func(); void func() { } int main() { func(); return 0; }" ${have_flag})
if(${have_flag} AND NOT ${_result})
set(${_result} "${flag}")
# break()
endif(${have_flag} AND NOT ${_result})
endforeach(flag)
endmacro(check_function_keywords)

View File

@ -0,0 +1,109 @@
## Checks for large file support ##
include(CheckIncludeFile)
include(CheckSymbolExists)
include(CheckTypeSize)
macro(check_lfs _isenable)
set(LFS_OFF_T "")
set(LFS_FOPEN "")
set(LFS_FSEEK "")
set(LFS_FTELL "")
set(LFS_PRID "")
if(${_isenable})
set(SAFE_CMAKE_REQUIRED_DEFINITIONS "${CMAKE_REQUIRED_DEFINITIONS}")
set(CMAKE_REQUIRED_DEFINITIONS ${CMAKE_REQUIRED_DEFINITIONS}
-D_LARGEFILE_SOURCE -D_LARGE_FILES -D_FILE_OFFSET_BITS=64
-D__STDC_LIMIT_MACROS -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS)
check_include_file("sys/types.h" HAVE_SYS_TYPES_H)
check_include_file("inttypes.h" HAVE_INTTYPES_H)
check_include_file("stddef.h" HAVE_STDDEF_H)
check_include_file("stdint.h" HAVE_STDINT_H)
# LFS type1: 8 <= sizeof(off_t), fseeko, ftello
check_type_size("off_t" SIZEOF_OFF_T)
if(SIZEOF_OFF_T GREATER 7)
check_symbol_exists("fseeko" "stdio.h" HAVE_FSEEKO)
check_symbol_exists("ftello" "stdio.h" HAVE_FTELLO)
if(HAVE_FSEEKO AND HAVE_FTELLO)
set(LFS_OFF_T "off_t")
set(LFS_FOPEN "fopen")
set(LFS_FSEEK "fseeko")
set(LFS_FTELL "ftello")
check_symbol_exists("PRIdMAX" "inttypes.h" HAVE_PRIDMAX)
if(HAVE_PRIDMAX)
set(LFS_PRID "PRIdMAX")
else(HAVE_PRIDMAX)
check_type_size("long" SIZEOF_LONG)
check_type_size("int" SIZEOF_INT)
if(SIZEOF_OFF_T GREATER SIZEOF_LONG)
set(LFS_PRID "\"lld\"")
elseif(SIZEOF_LONG GREATER SIZEOF_INT)
set(LFS_PRID "\"ld\"")
else(SIZEOF_OFF_T GREATER SIZEOF_LONG)
set(LFS_PRID "\"d\"")
endif(SIZEOF_OFF_T GREATER SIZEOF_LONG)
endif(HAVE_PRIDMAX)
endif(HAVE_FSEEKO AND HAVE_FTELLO)
endif(SIZEOF_OFF_T GREATER 7)
# LFS type2: 8 <= sizeof(off64_t), fopen64, fseeko64, ftello64
if(NOT LFS_OFF_T)
check_type_size("off64_t" SIZEOF_OFF64_T)
if(SIZEOF_OFF64_T GREATER 7)
check_symbol_exists("fopen64" "stdio.h" HAVE_FOPEN64)
check_symbol_exists("fseeko64" "stdio.h" HAVE_FSEEKO64)
check_symbol_exists("ftello64" "stdio.h" HAVE_FTELLO64)
if(HAVE_FOPEN64 AND HAVE_FSEEKO64 AND HAVE_FTELLO64)
set(LFS_OFF_T "off64_t")
set(LFS_FOPEN "fopen64")
set(LFS_FSEEK "fseeko64")
set(LFS_FTELL "ftello64")
check_symbol_exists("PRIdMAX" "inttypes.h" HAVE_PRIDMAX)
if(HAVE_PRIDMAX)
set(LFS_PRID "PRIdMAX")
else(HAVE_PRIDMAX)
check_type_size("long" SIZEOF_LONG)
check_type_size("int" SIZEOF_INT)
if(SIZEOF_OFF64_T GREATER SIZEOF_LONG)
set(LFS_PRID "\"lld\"")
elseif(SIZEOF_LONG GREATER SIZEOF_INT)
set(LFS_PRID "\"ld\"")
else(SIZEOF_OFF64_T GREATER SIZEOF_LONG)
set(LFS_PRID "\"d\"")
endif(SIZEOF_OFF64_T GREATER SIZEOF_LONG)
endif(HAVE_PRIDMAX)
endif(HAVE_FOPEN64 AND HAVE_FSEEKO64 AND HAVE_FTELLO64)
endif(SIZEOF_OFF64_T GREATER 7)
endif(NOT LFS_OFF_T)
# LFS type3: 8 <= sizeof(__int64), _fseeki64, _ftelli64
if(NOT LFS_OFF_T)
check_type_size("__int64" SIZEOF___INT64)
if(SIZEOF___INT64 GREATER 7)
check_symbol_exists("_fseeki64" "stdio.h" HAVE__FSEEKI64)
check_symbol_exists("_ftelli64" "stdio.h" HAVE__FTELLI64)
if(HAVE__FSEEKI64 AND HAVE__FTELLI64)
set(LFS_OFF_T "__int64")
set(LFS_FOPEN "fopen")
set(LFS_FSEEK "_fseeki64")
set(LFS_FTELL "_ftelli64")
set(LFS_PRID "\"I64d\"")
endif(HAVE__FSEEKI64 AND HAVE__FTELLI64)
endif(SIZEOF___INT64 GREATER 7)
endif(NOT LFS_OFF_T)
set(CMAKE_REQUIRED_DEFINITIONS "${SAFE_CMAKE_REQUIRED_DEFINITIONS}")
endif(${_isenable})
if(NOT LFS_OFF_T)
## not found
set(LFS_OFF_T "long")
set(LFS_FOPEN "fopen")
set(LFS_FSEEK "fseek")
set(LFS_FTELL "ftell")
set(LFS_PRID "\"ld\"")
endif(NOT LFS_OFF_T)
endmacro(check_lfs)

View File

@ -0,0 +1,38 @@
# If the cmake version includes cpack, use it
IF(EXISTS "${CMAKE_ROOT}/Modules/CPack.cmake")
SET(CPACK_PACKAGE_DESCRIPTION_SUMMARY "${PROJECT_DESCRIPTION}")
SET(CPACK_PACKAGE_VENDOR "${PROJECT_VENDOR}")
SET(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/COPYING")
SET(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/COPYING")
SET(CPACK_PACKAGE_VERSION_MAJOR "${PROJECT_VERSION_MAJOR}")
SET(CPACK_PACKAGE_VERSION_MINOR "${PROJECT_VERSION_MINOR}")
SET(CPACK_PACKAGE_VERSION_PATCH "${PROJECT_VERSION_PATCH}")
# SET(CPACK_PACKAGE_INSTALL_DIRECTORY "${PROJECT_NAME} ${PROJECT_VERSION}")
SET(CPACK_SOURCE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION_FULL}")
IF(NOT DEFINED CPACK_SYSTEM_NAME)
SET(CPACK_SYSTEM_NAME "${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}")
ENDIF(NOT DEFINED CPACK_SYSTEM_NAME)
IF(${CPACK_SYSTEM_NAME} MATCHES Windows)
IF(CMAKE_CL_64)
SET(CPACK_SYSTEM_NAME win64-${CMAKE_SYSTEM_PROCESSOR})
ELSE(CMAKE_CL_64)
SET(CPACK_SYSTEM_NAME win32-${CMAKE_SYSTEM_PROCESSOR})
ENDIF(CMAKE_CL_64)
ENDIF(${CPACK_SYSTEM_NAME} MATCHES Windows)
IF(NOT DEFINED CPACK_PACKAGE_FILE_NAME)
SET(CPACK_PACKAGE_FILE_NAME "${CPACK_SOURCE_PACKAGE_FILE_NAME}-${CPACK_SYSTEM_NAME}")
ENDIF(NOT DEFINED CPACK_PACKAGE_FILE_NAME)
SET(CPACK_PACKAGE_CONTACT "${PROJECT_CONTACT}")
IF(UNIX)
SET(CPACK_STRIP_FILES "")
SET(CPACK_SOURCE_STRIP_FILES "")
# SET(CPACK_PACKAGE_EXECUTABLES "ccmake" "CMake")
ENDIF(UNIX)
SET(CPACK_SOURCE_IGNORE_FILES "/CVS/" "/build/" "/\\\\.build/" "/\\\\.svn/" "~$")
# include CPack model once all variables are set
INCLUDE(CPack)
ENDIF(EXISTS "${CMAKE_ROOT}/Modules/CPack.cmake")

View File

@ -0,0 +1,36 @@
IF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt")
MESSAGE(FATAL_ERROR "Cannot find install manifest: \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\"")
ENDIF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt")
FILE(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files)
STRING(REGEX REPLACE "\n" ";" files "${files}")
SET(NUM 0)
FOREACH(file ${files})
IF(EXISTS "$ENV{DESTDIR}${file}")
MESSAGE(STATUS "Looking for \"$ENV{DESTDIR}${file}\" - found")
SET(UNINSTALL_CHECK_${NUM} 1)
ELSE(EXISTS "$ENV{DESTDIR}${file}")
MESSAGE(STATUS "Looking for \"$ENV{DESTDIR}${file}\" - not found")
SET(UNINSTALL_CHECK_${NUM} 0)
ENDIF(EXISTS "$ENV{DESTDIR}${file}")
MATH(EXPR NUM "1 + ${NUM}")
ENDFOREACH(file)
SET(NUM 0)
FOREACH(file ${files})
IF(${UNINSTALL_CHECK_${NUM}})
MESSAGE(STATUS "Uninstalling \"$ENV{DESTDIR}${file}\"")
EXEC_PROGRAM(
"@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\""
OUTPUT_VARIABLE rm_out
RETURN_VALUE rm_retval
)
IF(NOT "${rm_retval}" STREQUAL 0)
MESSAGE(FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"")
ENDIF(NOT "${rm_retval}" STREQUAL 0)
ENDIF(${UNINSTALL_CHECK_${NUM}})
MATH(EXPR NUM "1 + ${NUM}")
ENDFOREACH(file)
FILE(REMOVE "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt")

View File

@ -0,0 +1,27 @@
The libdivsufsort copyright is as follows:
Copyright (c) 2003-2008 Yuta Mori All Rights Reserved.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
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 OR COPYRIGHT
HOLDERS 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.
See also the libdivsufsort web site:
http://libdivsufsort.googlecode.com/ for more information.

View File

@ -0,0 +1,329 @@
2010-11-11 Yuta Mori <yuta.256@gmail.com>
Changed paths:
M /trunk/CMakeLists.txt
M /trunk/include/CMakeLists.txt
Fixed some bugs in CMakeLists.txt.
2008-08-24 Yuta Mori <yuta.256@gmail.com>
Changed paths:
M /trunk/lib/divsufsort.c
bug fix in divbwt.
2008-08-23 Yuta Mori <yuta.256@gmail.com>
Changed paths:
M /trunk/INSTALL
M /trunk/NEWS
M /trunk/README
M /trunk/include/divsufsort.h.cmake
M /trunk/pkgconfig/CMakeLists.txt
M /trunk/pkgconfig/libdivsufsort.pc.cmake
Update files for 2.0.0.
2008-08-23 Yuta Mori <yuta.256@gmail.com>
Changed paths:
M /trunk/examples/sasearch.c
M /trunk/lib/sssort.c
A few bug fixes.
2008-07-28 Yuta Mori <yuta.256@gmail.com>
Changed paths:
M /trunk/CMakeLists.txt
M /trunk/include/CMakeLists.txt
M /trunk/include/divsufsort.h.cmake
M /trunk/include/divsufsort_private.h
M /trunk/lib/CMakeLists.txt
M /trunk/lib/divsufsort.c
M /trunk/lib/trsort.c
M /trunk/lib/utils.c
M /trunk/pkgconfig/CMakeLists.txt
M /trunk/pkgconfig/libdivsufsort.pc.cmake
Added 64-bit version of divsufsort.
2008-07-19 Yuta Mori <yuta.256@gmail.com>
Changed paths:
M /trunk/include/divsufsort_private.h
M /trunk/lib/sssort.c
Fixed integer overflow in ss_isqrt().
2008-07-14 Yuta Mori <yuta.256@gmail.com>
Changed paths:
M /trunk/examples/mksary.c
M /trunk/examples/sasearch.c
M /trunk/examples/suftest.c
M /trunk/examples/unbwt.c
Rewrote examples.
2008-07-13 Yuta Mori <yuta.256@gmail.com>
Changed paths:
M /trunk/examples/bwt.c
Rewrote bwt.c.
2008-07-13 Yuta Mori <yuta.256@gmail.com>
Changed paths:
A /trunk/CMakeModules/CheckLFS.cmake
A /trunk/include/lfs.h.cmake
Added files...
2008-07-13 Yuta Mori <yuta.256@gmail.com>
Changed paths:
M /trunk/CMakeLists.txt
M /trunk/include/CMakeLists.txt
M /trunk/include/config.h.cmake
Added LFS (Large File Support) files.
2008-07-11 Yuta Mori <yuta.256@gmail.com>
Changed paths:
M /trunk/CMakeLists.txt
Fix version number.
2008-07-11 Yuta Mori <yuta.256@gmail.com>
Changed paths:
M /trunk/CMakeLists.txt
A /trunk/CMakeModules/ProjectCPack.cmake
M /trunk/COPYING
M /trunk/examples/bwt.c
M /trunk/examples/mksary.c
M /trunk/examples/sasearch.c
M /trunk/examples/suftest.c
M /trunk/examples/unbwt.c
M /trunk/include/config.h.cmake
M /trunk/include/divsufsort.h.cmake
M /trunk/include/divsufsort_private.h
M /trunk/lib/CMakeLists.txt
M /trunk/lib/divsufsort.c
A /trunk/lib/sssort.c (from /trunk/lib/substringsort.c:5)
D /trunk/lib/substringsort.c
M /trunk/lib/trsort.c
M /trunk/lib/utils.c
A /trunk/pkgconfig
A /trunk/pkgconfig/CMakeLists.txt
A /trunk/pkgconfig/libdivsufsort.pc.cmake
Major rewrite of libdivsufsort.
Added CPack support to create the source package.
Added OpenMP support for sssort.
2008-07-03 Yuta Mori <yuta.256@gmail.com>
Changed paths:
A /trunk/CMakeLists.txt
A /trunk/CMakeModules
A /trunk/CMakeModules/AppendCompilerFlags.cmake
A /trunk/CMakeModules/CheckFunctionKeywords.cmake
A /trunk/CMakeModules/cmake_uninstall.cmake.in
M /trunk/INSTALL
D /trunk/Makefile.am
M /trunk/README
A /trunk/VERSION
D /trunk/configure.ac
A /trunk/examples/CMakeLists.txt
D /trunk/examples/Makefile.am
M /trunk/examples/sasearch.c
A /trunk/include/CMakeLists.txt
D /trunk/include/Makefile.am
A /trunk/include/config.h.cmake
A /trunk/include/divsufsort.h.cmake
D /trunk/include/divsufsort.h.in
A /trunk/include/divsufsort_private.h
D /trunk/include/divsufsort_private.h.in
A /trunk/lib/CMakeLists.txt
D /trunk/lib/Makefile.am
M /trunk/lib/divsufsort.c
D /trunk/lib/libdivsufsort.sym
M /trunk/lib/substringsort.c
M /trunk/lib/trsort.c
The build system was changed to CMake. (http://www.cmake.org/)
2008-06-26 Yuta Mori <yuta.256@gmail.com>
Changed paths:
M /trunk/AUTHORS
M /trunk/configure.ac
AUTHORS: Fixed email address.
2008-02-23 Yuta Mori <yiv01157@nifty.com>
* lib/substringsort.c (_merge_backward): Bug fix.
* lib/trsort.c (_tr_introsort): Bug fix.
2007-09-02 Yuta Mori <yiv01157@nifty.com>
* lib/trsort.c (_ls_introsort): Important bug fix.
2007-07-15 Yuta Mori <yiv01157@nifty.com>
A few bug fixes.
* lib/divsufsort.c (divbwt): Bug fix.
* lib/trsort.c (_tr_introsort): Bug fix.
* lib/utils.c (sa_search, sa_simplesearch): New functions.
* lib/libdivsufsort.sym: Update.
* include/divsufsort.h.in: Update.
* examples/sasearch.c: New file.
* examples/Makefile.am: Update.
* configure.ac: Update.
* NEWS: Update.
* README: Update.
2007-04-14 Yuta Mori <yiv01157@nifty.com>
Change license to the MIT/X11 license.
Update all files for 1.2.0.
* lib/libdivsufsort.sym: New file for libtool.
2007-04-07 Yuta Mori <yiv01157@nifty.com>
Update files for 1.1.7.
2007-04-07 Yuta Mori <yiv01157@nifty.com>
Replace drsort with tandem repeat sorting algorithm and Larsson-Sadakane sorting algorithm.
* lib/trsort.c: New file.
* lib/drsort.c: Delete.
* lib/divsufsort.c: Update.
* lib/Makefile.am: Update.
* lib/divsufsort_private.h.in (LS_INSERTIONSORT_THRESHOLD, TR_INSERTIONSORT_THRESHOLD): New constants.
(DR_INSERTIONSORT_THRESHOLD): Delete.
(STACK_PUSH3, STACK_POP3): New macros.
2007-03-31 Yuta Mori <yiv01157@nifty.com>
Update files for 1.1.6.
2007-03-31 Yuta Mori <yiv01157@nifty.com>
Replace _ss_merge with new merge algorithms.
* lib/substringsort.c (_ss_merge): Delete.
* lib/substringsort.c (_block_swap, _merge_forward, _merge_backward, _merge): New functions.
(substringsort): Update.
* lib/divsufsort.c (_sort_typeBstar, divsufsort, divbwt): Update.
* include/divsufsort_private.h.in (LOCALMERGE_BUFFERSIZE): New constant.
(SS_MERGESORT_QUEUESIZE): Delete.
2007-03-24 Yuta Mori <yiv01157@nifty.com>
Update files for 1.1.5.
2007-03-23 Yuta Mori <yiv01157@nifty.com>
Replace breadth-first introsort with new multikey introsort.
* lib/substringsort.c (_compare): Update.
(_substring_partition): Update.
(_multikey_introsort): New function.
(_introsort, _bfintrosort): Delete.
(substringsort): Update.
* lib/divsufsort.c (_sort_typeBstar): Update.
2007-03-21 Yuta Mori <yiv01157@nifty.com>
* lib/substringsort.c (_introsort): Convert introsort to a non-recursive algorithm.
(substringsort): Update.
* lib/divsufsort.c (_sort_typeBstar): Update.
2007-03-21 Yuta Mori <yiv01157@nifty.com>
* include/divsufsort_private.h.in (STACK_SIZE): Rename from SS_STACK_SIZE.
(SS_BLOCKSIZE): Rename from SS_MKQSORT_THRESHOLD.
(SS_MKQSORT_DMAX, SS_DSWAP, SS_STACK_PUSH, SS_STACK_POP): Delete.
(STACK_PUSH, STACK_POP): New macros.
(substringsort): Update prototype.
2007-03-17 Yuta Mori <yiv01157@nifty.com>
Update files for 1.1.4.
2007-03-17 Yuta Mori <yiv01157@nifty.com>
* substringsort.c (_fixdown, _heapsort, _lg): New function.
(_introsort): Rename from _quicksort. Change to use new partitioning algorithm.
(_bfintrosort): Rename from _bfquicksort.
2007-03-10 Yuta Mori <yiv01157@nifty.com>
Update files for 1.1.3.
2007-03-10 Yuta Mori <yiv01157@nifty.com>
Replace depth-first multikey quicksort with new breadth-first ternary quicksort.
* substringsort.c (_ss_compare_lcp, _ss_tqsort, _ss_mkqsort): Remove.
(_median3): Rename from _ss_median and rewrite.
(_pivot): Rename from _ss_pivot and rewrite.
(_median5, _substring_partition, _quicksort, _bfquicksort): New function.
2007-03-03 Yuta Mori <yiv01157@nifty.com>
Update files for 1.1.2.
2007-03-03 Yuta Mori <yiv01157@nifty.com>
* substringsort.c (_compare): Rename from _ss_compare and rewrite.
(_insertionsort): Rename from _ss_insertionsort and rewrite.
2007-02-24 Yuta Mori <yiv01157@nifty.com>
Update files for 1.1.1.
2007-02-24 Yuta Mori <yiv01157@nifty.com>
* lib/substringsort.c (_ss_getc): Remove.
2007-02-17 Yuta Mori <yiv01157@nifty.com>
Update files for 1.1.0.
2007-02-17 Yuta Mori <yiv01157@nifty.com>
* utils.c (bwtcheck): Remove.
2007-02-11 Yuta Mori <yiv01157@nifty.com>
* lib/divsufsort.c,
include/divsufsort.h.in,
include/divsufsort_private.h.in:
Change to use a new improved two-stage sort algorithm (version 070210).
2007-01-28 Yuta Mori <yiv01157@nifty.com>
* lib/divsufsort.c (_sort): Fix a bug that using wrong index.
2007-01-28 Yuta Mori <yiv01157@nifty.com>
* examples/bwt.c: Rename from examples/bwt2.c.
* examples/unbwt.c: Rename from examples/unbwt2.c.
* examples/bwt1.c: Delete.
* examples/unbwt1.c: Delete.
2007-01-28 Yuta Mori <yiv01157@nifty.com>
* lib/divsufsort.c, include/divsufsort_private.h.in:
Change to use new improved two-stage sort algorithm (version 070128).
2007-01-24 Yuta Mori <yiv01157@nifty.com>
Remove use of libtool.
* include/divsufsort_private.h.in: Rename from include/divsufsort_private.h.
2007-01-24 Yuta Mori <yiv01157@nifty.com>
Initial import.
;; Local Variables:
;; coding: utf-8
;; End:

View File

@ -0,0 +1,67 @@
Version 1.0.2 (2006-01-01):
* Release 1.0.2
Version 1.0.2b (2005-12-11):
* lib/divsufsort.c (_construct_typeBstar): Completely rewrite.
Version 1.0.2a (2005-12-04):
* lib/substringsort.c: Completely rewrite.
* lib/drsort.c: Completely rewrite.
* lib/divsufsort.c (_sort_typeBstar): Fix some bugs.
Version 1.0.1 (2005-11-08):
* Release 1.0.1
Version 1.0.1a (2005-11-06):
* configure.ac: Add AM_ENABLE_STATIC, AM_DISABLE_SHARED and AC_LIBTOOL_WIN32_DLL
* Makefile.am (EXTRA_DIST): Add ChangeLog.old.
* lib/divsufsort.c (_construct_typeBstar): Fix.
* AUTHORS: New file.
* ChangeLog: New file.
* ChangeLog.old: New file.
* INSTALL: New file.
* NEWS: New file.
Version 1.0.0 (2005-10-31)
* Introduced autoconf and automake.
* Added new example programs.
Version 0.2.1 (2005-08-27)
* divsufsort.c: Kao's algorithm was replaced with Improved Two-Stage algorithm.
* divsufsort.c: Reduced memory usage.
* substringsort.c: Added mergesort for sorting large groups of suffixes.
Version 0.1.6 (2005-06-10)
* divsufsort.h: Renamed from libdivsufsort.h. (again...)
* divsufsort.c: Renamed from libdivsufsort.c. (again...)
* divsufsort.c: Reduced memory usage.
* substringsort.c, substringsort.h, drsort.c, drsort.h: Modify.
* mksary_mmap/makefile, mksary_mmap/mksary.c,
mksary_mmap/mmap.c, mksary_mmap/mmap.h: Removed.
Version 0.1.5 (2005-04-07)
* libdivsufsort.c: ranksort and doublingsort were replaced with drsort.
* def.h, drsort.c, drsort.h: New file.
* doublingsort.c, doublingsort.h, ranksort.c, ranksort.h: Removed.
Version 0.1.4 (2005-03-27)
* mksary/mksary.c, mksary_mmap/mksary.c, suftest.c: Added error handling.
Version 0.1.3 (2005-01-28)
* mksary/makefile, mksary/mksary.c, mksary_mmap/makefile,
mksary_mmap/mksary.c, mksary_mmap/mmap.c, mksary_mmap/mmap.h: New file.
* libdivsufsort.c: Modify.
Version 0.1.2 (2005-01-01)
* suftest.c: New file.
* libdivsufsort.c: Renamed from divsufsort.c.
* libdivsufsort.h: Renamed from divsufsort.h.

View File

@ -0,0 +1,31 @@
-- INSTALL for libdivsufsort
Requirements:
=============
* CMake version 2.4.2 or newer (http://www.cmake.org/)
* An ANSI C compiler
* GNU Make
Compilation and Installation (with Unix Makefiles):
===================================================
1. Create a 'build' directory in the package source directory.
$ cd libdivsufsort-?.?.?
$ mkdir build
$ cd build
2. Configure the package for your system.
$ cmake -DCMAKE_BUILD_TYPE="Release" -DCMAKE_INSTALL_PREFIX="/usr/local" ..
3. Compile the package.
$ make
4. Install the library and header files.
# make install

View File

@ -0,0 +1,10 @@
# Makefile.am for libdivsufsort
SUBDIRS = include lib examples
EXTRA_DIST = ChangeLog.old CMakeLists.txt VERSION CMakeModules pkgconfig
ACLOCAL_AMFLAGS = -I m4
libtool: $(LIBTOOL_DEPS)
$(SHELL) ./config.status --recheck

View File

@ -0,0 +1,736 @@
# Makefile.in generated by automake 1.11.1 from Makefile.am.
# @configure_input@
# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation,
# Inc.
# This Makefile.in is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
# with or without modifications, as long as this notice is preserved.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE.
@SET_MAKE@
# Makefile.am for libdivsufsort
VPATH = @srcdir@
pkgdatadir = $(datadir)/@PACKAGE@
pkgincludedir = $(includedir)/@PACKAGE@
pkglibdir = $(libdir)/@PACKAGE@
pkglibexecdir = $(libexecdir)/@PACKAGE@
am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
install_sh_DATA = $(install_sh) -c -m 644
install_sh_PROGRAM = $(install_sh) -c
install_sh_SCRIPT = $(install_sh) -c
INSTALL_HEADER = $(INSTALL_DATA)
transform = $(program_transform_name)
NORMAL_INSTALL = :
PRE_INSTALL = :
POST_INSTALL = :
NORMAL_UNINSTALL = :
PRE_UNINSTALL = :
POST_UNINSTALL = :
build_triplet = @build@
host_triplet = @host@
target_triplet = @target@
subdir = .
DIST_COMMON = README $(am__configure_deps) $(srcdir)/Makefile.am \
$(srcdir)/Makefile.in $(top_srcdir)/configure AUTHORS COPYING \
ChangeLog INSTALL NEWS config/config.guess config/config.sub \
config/depcomp config/install-sh config/ltmain.sh \
config/missing
ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
am__aclocal_m4_deps = $(top_srcdir)/m4/libtool.m4 \
$(top_srcdir)/m4/ltoptions.m4 $(top_srcdir)/m4/ltsugar.m4 \
$(top_srcdir)/m4/ltversion.m4 $(top_srcdir)/m4/lt~obsolete.m4 \
$(top_srcdir)/configure.ac
am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
$(ACLOCAL_M4)
am__CONFIG_DISTCLEAN_FILES = config.status config.cache config.log \
configure.lineno config.status.lineno
mkinstalldirs = $(install_sh) -d
CONFIG_HEADER = $(top_builddir)/include/config.h
CONFIG_CLEAN_FILES =
CONFIG_CLEAN_VPATH_FILES =
SOURCES =
DIST_SOURCES =
RECURSIVE_TARGETS = all-recursive check-recursive dvi-recursive \
html-recursive info-recursive install-data-recursive \
install-dvi-recursive install-exec-recursive \
install-html-recursive install-info-recursive \
install-pdf-recursive install-ps-recursive install-recursive \
installcheck-recursive installdirs-recursive pdf-recursive \
ps-recursive uninstall-recursive
RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
distclean-recursive maintainer-clean-recursive
AM_RECURSIVE_TARGETS = $(RECURSIVE_TARGETS:-recursive=) \
$(RECURSIVE_CLEAN_TARGETS:-recursive=) tags TAGS ctags CTAGS \
distdir dist dist-all distcheck
ETAGS = etags
CTAGS = ctags
DIST_SUBDIRS = $(SUBDIRS)
DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
distdir = $(PACKAGE)-$(VERSION)
top_distdir = $(distdir)
am__remove_distdir = \
{ test ! -d "$(distdir)" \
|| { find "$(distdir)" -type d ! -perm -200 -exec chmod u+w {} ';' \
&& rm -fr "$(distdir)"; }; }
am__relativize = \
dir0=`pwd`; \
sed_first='s,^\([^/]*\)/.*$$,\1,'; \
sed_rest='s,^[^/]*/*,,'; \
sed_last='s,^.*/\([^/]*\)$$,\1,'; \
sed_butlast='s,/*[^/]*$$,,'; \
while test -n "$$dir1"; do \
first=`echo "$$dir1" | sed -e "$$sed_first"`; \
if test "$$first" != "."; then \
if test "$$first" = ".."; then \
dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
else \
first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
if test "$$first2" = "$$first"; then \
dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
else \
dir2="../$$dir2"; \
fi; \
dir0="$$dir0"/"$$first"; \
fi; \
fi; \
dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
done; \
reldir="$$dir2"
DIST_ARCHIVES = $(distdir).tar.gz $(distdir).tar.bz2
GZIP_ENV = --best
distuninstallcheck_listfiles = find . -type f -print
distcleancheck_listfiles = find . -type f -print
ACLOCAL = @ACLOCAL@
AMTAR = @AMTAR@
AR = @AR@
AUTOCONF = @AUTOCONF@
AUTOHEADER = @AUTOHEADER@
AUTOMAKE = @AUTOMAKE@
AWK = @AWK@
CC = @CC@
CCDEPMODE = @CCDEPMODE@
CFLAGS = @CFLAGS@
CPP = @CPP@
CPPFLAGS = @CPPFLAGS@
CYGPATH_W = @CYGPATH_W@
DEFS = @DEFS@
DEPDIR = @DEPDIR@
DIVSUFSORT_EXPORT = @DIVSUFSORT_EXPORT@
DIVSUFSORT_IMPORT = @DIVSUFSORT_IMPORT@
DSYMUTIL = @DSYMUTIL@
DUMPBIN = @DUMPBIN@
ECHO_C = @ECHO_C@
ECHO_N = @ECHO_N@
ECHO_T = @ECHO_T@
EGREP = @EGREP@
EXEEXT = @EXEEXT@
FGREP = @FGREP@
GREP = @GREP@
INCFILE = @INCFILE@
INSTALL = @INSTALL@
INSTALL_DATA = @INSTALL_DATA@
INSTALL_PROGRAM = @INSTALL_PROGRAM@
INSTALL_SCRIPT = @INSTALL_SCRIPT@
INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
LD = @LD@
LDFLAGS = @LDFLAGS@
LFS_FOPEN = @LFS_FOPEN@
LFS_FSEEK = @LFS_FSEEK@
LFS_FTELL = @LFS_FTELL@
LFS_OFF_T = @LFS_OFF_T@
LFS_PRID = @LFS_PRID@
LIBOBJS = @LIBOBJS@
LIBS = @LIBS@
LIBTOOL = @LIBTOOL@
LIBTOOL_DEPS = @LIBTOOL_DEPS@
LIPO = @LIPO@
LN_S = @LN_S@
LTLIBOBJS = @LTLIBOBJS@
LT_AGE = @LT_AGE@
LT_CURRENT = @LT_CURRENT@
LT_REVISION = @LT_REVISION@
MAINT = @MAINT@
MAKEINFO = @MAKEINFO@
MKDIR_P = @MKDIR_P@
NM = @NM@
NMEDIT = @NMEDIT@
OBJDUMP = @OBJDUMP@
OBJEXT = @OBJEXT@
OTOOL = @OTOOL@
OTOOL64 = @OTOOL64@
PACKAGE = @PACKAGE@
PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
PACKAGE_NAME = @PACKAGE_NAME@
PACKAGE_STRING = @PACKAGE_STRING@
PACKAGE_TARNAME = @PACKAGE_TARNAME@
PACKAGE_URL = @PACKAGE_URL@
PACKAGE_VERSION = @PACKAGE_VERSION@
PATH_SEPARATOR = @PATH_SEPARATOR@
PROJECT_DESCRIPTION = @PROJECT_DESCRIPTION@
PROJECT_NAME = @PROJECT_NAME@
PROJECT_URL = @PROJECT_URL@
PROJECT_VERSION_FULL = @PROJECT_VERSION_FULL@
RANLIB = @RANLIB@
SAINDEX_PRId = @SAINDEX_PRId@
SAINDEX_TYPE = @SAINDEX_TYPE@
SAINT32_TYPE = @SAINT32_TYPE@
SAINT64_PRId = @SAINT64_PRId@
SAINT64_TYPE = @SAINT64_TYPE@
SAINT_PRId = @SAINT_PRId@
SAUCHAR_TYPE = @SAUCHAR_TYPE@
SED = @SED@
SET_MAKE = @SET_MAKE@
SHELL = @SHELL@
STRIP = @STRIP@
VERSION = @VERSION@
W64BIT = @W64BIT@
abs_builddir = @abs_builddir@
abs_srcdir = @abs_srcdir@
abs_top_builddir = @abs_top_builddir@
abs_top_srcdir = @abs_top_srcdir@
ac_ct_CC = @ac_ct_CC@
ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
am__include = @am__include@
am__leading_dot = @am__leading_dot@
am__quote = @am__quote@
am__tar = @am__tar@
am__untar = @am__untar@
bindir = @bindir@
build = @build@
build_alias = @build_alias@
build_cpu = @build_cpu@
build_os = @build_os@
build_vendor = @build_vendor@
builddir = @builddir@
datadir = @datadir@
datarootdir = @datarootdir@
docdir = @docdir@
dvidir = @dvidir@
exec_prefix = @exec_prefix@
host = @host@
host_alias = @host_alias@
host_cpu = @host_cpu@
host_os = @host_os@
host_vendor = @host_vendor@
htmldir = @htmldir@
includedir = @includedir@
infodir = @infodir@
install_sh = @install_sh@
libdir = @libdir@
libexecdir = @libexecdir@
localedir = @localedir@
localstatedir = @localstatedir@
lt_ECHO = @lt_ECHO@
mandir = @mandir@
mkdir_p = @mkdir_p@
oldincludedir = @oldincludedir@
pdfdir = @pdfdir@
prefix = @prefix@
program_transform_name = @program_transform_name@
psdir = @psdir@
sbindir = @sbindir@
sharedstatedir = @sharedstatedir@
srcdir = @srcdir@
sysconfdir = @sysconfdir@
target = @target@
target_alias = @target_alias@
target_cpu = @target_cpu@
target_os = @target_os@
target_vendor = @target_vendor@
top_build_prefix = @top_build_prefix@
top_builddir = @top_builddir@
top_srcdir = @top_srcdir@
SUBDIRS = include lib examples
EXTRA_DIST = ChangeLog.old CMakeLists.txt VERSION CMakeModules pkgconfig
ACLOCAL_AMFLAGS = -I m4
all: all-recursive
.SUFFIXES:
am--refresh:
@:
$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
@for dep in $?; do \
case '$(am__configure_deps)' in \
*$$dep*) \
echo ' cd $(srcdir) && $(AUTOMAKE) --foreign'; \
$(am__cd) $(srcdir) && $(AUTOMAKE) --foreign \
&& exit 0; \
exit 1;; \
esac; \
done; \
echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign Makefile'; \
$(am__cd) $(top_srcdir) && \
$(AUTOMAKE) --foreign Makefile
.PRECIOUS: Makefile
Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
@case '$?' in \
*config.status*) \
echo ' $(SHELL) ./config.status'; \
$(SHELL) ./config.status;; \
*) \
echo ' cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__depfiles_maybe)'; \
cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__depfiles_maybe);; \
esac;
$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
$(SHELL) ./config.status --recheck
$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
$(am__cd) $(srcdir) && $(AUTOCONF)
$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
$(am__cd) $(srcdir) && $(ACLOCAL) $(ACLOCAL_AMFLAGS)
$(am__aclocal_m4_deps):
mostlyclean-libtool:
-rm -f *.lo
clean-libtool:
-rm -rf .libs _libs
distclean-libtool:
-rm -f libtool config.lt
# This directory's subdirectories are mostly independent; you can cd
# into them and run `make' without going through this Makefile.
# To change the values of `make' variables: instead of editing Makefiles,
# (1) if the variable is set in `config.status', edit `config.status'
# (which will cause the Makefiles to be regenerated when you run `make');
# (2) otherwise, pass the desired values on the `make' command line.
$(RECURSIVE_TARGETS):
@fail= failcom='exit 1'; \
for f in x $$MAKEFLAGS; do \
case $$f in \
*=* | --[!k]*);; \
*k*) failcom='fail=yes';; \
esac; \
done; \
dot_seen=no; \
target=`echo $@ | sed s/-recursive//`; \
list='$(SUBDIRS)'; for subdir in $$list; do \
echo "Making $$target in $$subdir"; \
if test "$$subdir" = "."; then \
dot_seen=yes; \
local_target="$$target-am"; \
else \
local_target="$$target"; \
fi; \
($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
|| eval $$failcom; \
done; \
if test "$$dot_seen" = "no"; then \
$(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
fi; test -z "$$fail"
$(RECURSIVE_CLEAN_TARGETS):
@fail= failcom='exit 1'; \
for f in x $$MAKEFLAGS; do \
case $$f in \
*=* | --[!k]*);; \
*k*) failcom='fail=yes';; \
esac; \
done; \
dot_seen=no; \
case "$@" in \
distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
*) list='$(SUBDIRS)' ;; \
esac; \
rev=''; for subdir in $$list; do \
if test "$$subdir" = "."; then :; else \
rev="$$subdir $$rev"; \
fi; \
done; \
rev="$$rev ."; \
target=`echo $@ | sed s/-recursive//`; \
for subdir in $$rev; do \
echo "Making $$target in $$subdir"; \
if test "$$subdir" = "."; then \
local_target="$$target-am"; \
else \
local_target="$$target"; \
fi; \
($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
|| eval $$failcom; \
done && test -z "$$fail"
tags-recursive:
list='$(SUBDIRS)'; for subdir in $$list; do \
test "$$subdir" = . || ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) tags); \
done
ctags-recursive:
list='$(SUBDIRS)'; for subdir in $$list; do \
test "$$subdir" = . || ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) ctags); \
done
ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
unique=`for i in $$list; do \
if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
done | \
$(AWK) '{ files[$$0] = 1; nonempty = 1; } \
END { if (nonempty) { for (i in files) print i; }; }'`; \
mkid -fID $$unique
tags: TAGS
TAGS: tags-recursive $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
$(TAGS_FILES) $(LISP)
set x; \
here=`pwd`; \
if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
include_option=--etags-include; \
empty_fix=.; \
else \
include_option=--include; \
empty_fix=; \
fi; \
list='$(SUBDIRS)'; for subdir in $$list; do \
if test "$$subdir" = .; then :; else \
test ! -f $$subdir/TAGS || \
set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
fi; \
done; \
list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
unique=`for i in $$list; do \
if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
done | \
$(AWK) '{ files[$$0] = 1; nonempty = 1; } \
END { if (nonempty) { for (i in files) print i; }; }'`; \
shift; \
if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
test -n "$$unique" || unique=$$empty_fix; \
if test $$# -gt 0; then \
$(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
"$$@" $$unique; \
else \
$(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
$$unique; \
fi; \
fi
ctags: CTAGS
CTAGS: ctags-recursive $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
$(TAGS_FILES) $(LISP)
list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
unique=`for i in $$list; do \
if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
done | \
$(AWK) '{ files[$$0] = 1; nonempty = 1; } \
END { if (nonempty) { for (i in files) print i; }; }'`; \
test -z "$(CTAGS_ARGS)$$unique" \
|| $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
$$unique
GTAGS:
here=`$(am__cd) $(top_builddir) && pwd` \
&& $(am__cd) $(top_srcdir) \
&& gtags -i $(GTAGS_ARGS) "$$here"
distclean-tags:
-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
distdir: $(DISTFILES)
$(am__remove_distdir)
test -d "$(distdir)" || mkdir "$(distdir)"
@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
list='$(DISTFILES)'; \
dist_files=`for file in $$list; do echo $$file; done | \
sed -e "s|^$$srcdirstrip/||;t" \
-e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
case $$dist_files in \
*/*) $(MKDIR_P) `echo "$$dist_files" | \
sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
sort -u` ;; \
esac; \
for file in $$dist_files; do \
if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
if test -d $$d/$$file; then \
dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
if test -d "$(distdir)/$$file"; then \
find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
fi; \
if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
fi; \
cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
else \
test -f "$(distdir)/$$file" \
|| cp -p $$d/$$file "$(distdir)/$$file" \
|| exit 1; \
fi; \
done
@list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
if test "$$subdir" = .; then :; else \
test -d "$(distdir)/$$subdir" \
|| $(MKDIR_P) "$(distdir)/$$subdir" \
|| exit 1; \
fi; \
done
@list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
if test "$$subdir" = .; then :; else \
dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
$(am__relativize); \
new_distdir=$$reldir; \
dir1=$$subdir; dir2="$(top_distdir)"; \
$(am__relativize); \
new_top_distdir=$$reldir; \
echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
($(am__cd) $$subdir && \
$(MAKE) $(AM_MAKEFLAGS) \
top_distdir="$$new_top_distdir" \
distdir="$$new_distdir" \
am__remove_distdir=: \
am__skip_length_check=: \
am__skip_mode_fix=: \
distdir) \
|| exit 1; \
fi; \
done
-test -n "$(am__skip_mode_fix)" \
|| find "$(distdir)" -type d ! -perm -755 \
-exec chmod u+rwx,go+rx {} \; -o \
! -type d ! -perm -444 -links 1 -exec chmod a+r {} \; -o \
! -type d ! -perm -400 -exec chmod a+r {} \; -o \
! -type d ! -perm -444 -exec $(install_sh) -c -m a+r {} {} \; \
|| chmod -R a+r "$(distdir)"
dist-gzip: distdir
tardir=$(distdir) && $(am__tar) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).tar.gz
$(am__remove_distdir)
dist-bzip2: distdir
tardir=$(distdir) && $(am__tar) | bzip2 -9 -c >$(distdir).tar.bz2
$(am__remove_distdir)
dist-lzma: distdir
tardir=$(distdir) && $(am__tar) | lzma -9 -c >$(distdir).tar.lzma
$(am__remove_distdir)
dist-xz: distdir
tardir=$(distdir) && $(am__tar) | xz -c >$(distdir).tar.xz
$(am__remove_distdir)
dist-tarZ: distdir
tardir=$(distdir) && $(am__tar) | compress -c >$(distdir).tar.Z
$(am__remove_distdir)
dist-shar: distdir
shar $(distdir) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).shar.gz
$(am__remove_distdir)
dist-zip: distdir
-rm -f $(distdir).zip
zip -rq $(distdir).zip $(distdir)
$(am__remove_distdir)
dist dist-all: distdir
tardir=$(distdir) && $(am__tar) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).tar.gz
tardir=$(distdir) && $(am__tar) | bzip2 -9 -c >$(distdir).tar.bz2
$(am__remove_distdir)
# This target untars the dist file and tries a VPATH configuration. Then
# it guarantees that the distribution is self-contained by making another
# tarfile.
distcheck: dist
case '$(DIST_ARCHIVES)' in \
*.tar.gz*) \
GZIP=$(GZIP_ENV) gzip -dc $(distdir).tar.gz | $(am__untar) ;;\
*.tar.bz2*) \
bzip2 -dc $(distdir).tar.bz2 | $(am__untar) ;;\
*.tar.lzma*) \
lzma -dc $(distdir).tar.lzma | $(am__untar) ;;\
*.tar.xz*) \
xz -dc $(distdir).tar.xz | $(am__untar) ;;\
*.tar.Z*) \
uncompress -c $(distdir).tar.Z | $(am__untar) ;;\
*.shar.gz*) \
GZIP=$(GZIP_ENV) gzip -dc $(distdir).shar.gz | unshar ;;\
*.zip*) \
unzip $(distdir).zip ;;\
esac
chmod -R a-w $(distdir); chmod a+w $(distdir)
mkdir $(distdir)/_build
mkdir $(distdir)/_inst
chmod a-w $(distdir)
test -d $(distdir)/_build || exit 0; \
dc_install_base=`$(am__cd) $(distdir)/_inst && pwd | sed -e 's,^[^:\\/]:[\\/],/,'` \
&& dc_destdir="$${TMPDIR-/tmp}/am-dc-$$$$/" \
&& am__cwd=`pwd` \
&& $(am__cd) $(distdir)/_build \
&& ../configure --srcdir=.. --prefix="$$dc_install_base" \
$(DISTCHECK_CONFIGURE_FLAGS) \
&& $(MAKE) $(AM_MAKEFLAGS) \
&& $(MAKE) $(AM_MAKEFLAGS) dvi \
&& $(MAKE) $(AM_MAKEFLAGS) check \
&& $(MAKE) $(AM_MAKEFLAGS) install \
&& $(MAKE) $(AM_MAKEFLAGS) installcheck \
&& $(MAKE) $(AM_MAKEFLAGS) uninstall \
&& $(MAKE) $(AM_MAKEFLAGS) distuninstallcheck_dir="$$dc_install_base" \
distuninstallcheck \
&& chmod -R a-w "$$dc_install_base" \
&& ({ \
(cd ../.. && umask 077 && mkdir "$$dc_destdir") \
&& $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" install \
&& $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" uninstall \
&& $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" \
distuninstallcheck_dir="$$dc_destdir" distuninstallcheck; \
} || { rm -rf "$$dc_destdir"; exit 1; }) \
&& rm -rf "$$dc_destdir" \
&& $(MAKE) $(AM_MAKEFLAGS) dist \
&& rm -rf $(DIST_ARCHIVES) \
&& $(MAKE) $(AM_MAKEFLAGS) distcleancheck \
&& cd "$$am__cwd" \
|| exit 1
$(am__remove_distdir)
@(echo "$(distdir) archives ready for distribution: "; \
list='$(DIST_ARCHIVES)'; for i in $$list; do echo $$i; done) | \
sed -e 1h -e 1s/./=/g -e 1p -e 1x -e '$$p' -e '$$x'
distuninstallcheck:
@$(am__cd) '$(distuninstallcheck_dir)' \
&& test `$(distuninstallcheck_listfiles) | wc -l` -le 1 \
|| { echo "ERROR: files left after uninstall:" ; \
if test -n "$(DESTDIR)"; then \
echo " (check DESTDIR support)"; \
fi ; \
$(distuninstallcheck_listfiles) ; \
exit 1; } >&2
distcleancheck: distclean
@if test '$(srcdir)' = . ; then \
echo "ERROR: distcleancheck can only run from a VPATH build" ; \
exit 1 ; \
fi
@test `$(distcleancheck_listfiles) | wc -l` -eq 0 \
|| { echo "ERROR: files left in build directory after distclean:" ; \
$(distcleancheck_listfiles) ; \
exit 1; } >&2
check-am: all-am
check: check-recursive
all-am: Makefile
installdirs: installdirs-recursive
installdirs-am:
install: install-recursive
install-exec: install-exec-recursive
install-data: install-data-recursive
uninstall: uninstall-recursive
install-am: all-am
@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
installcheck: installcheck-recursive
install-strip:
$(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
`test -z '$(STRIP)' || \
echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
mostlyclean-generic:
clean-generic:
distclean-generic:
-test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
-test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
maintainer-clean-generic:
@echo "This command is intended for maintainers to use"
@echo "it deletes files that may require special tools to rebuild."
clean: clean-recursive
clean-am: clean-generic clean-libtool mostlyclean-am
distclean: distclean-recursive
-rm -f $(am__CONFIG_DISTCLEAN_FILES)
-rm -f Makefile
distclean-am: clean-am distclean-generic distclean-libtool \
distclean-tags
dvi: dvi-recursive
dvi-am:
html: html-recursive
html-am:
info: info-recursive
info-am:
install-data-am:
install-dvi: install-dvi-recursive
install-dvi-am:
install-exec-am:
install-html: install-html-recursive
install-html-am:
install-info: install-info-recursive
install-info-am:
install-man:
install-pdf: install-pdf-recursive
install-pdf-am:
install-ps: install-ps-recursive
install-ps-am:
installcheck-am:
maintainer-clean: maintainer-clean-recursive
-rm -f $(am__CONFIG_DISTCLEAN_FILES)
-rm -rf $(top_srcdir)/autom4te.cache
-rm -f Makefile
maintainer-clean-am: distclean-am maintainer-clean-generic
mostlyclean: mostlyclean-recursive
mostlyclean-am: mostlyclean-generic mostlyclean-libtool
pdf: pdf-recursive
pdf-am:
ps: ps-recursive
ps-am:
uninstall-am:
.MAKE: $(RECURSIVE_CLEAN_TARGETS) $(RECURSIVE_TARGETS) ctags-recursive \
install-am install-strip tags-recursive
.PHONY: $(RECURSIVE_CLEAN_TARGETS) $(RECURSIVE_TARGETS) CTAGS GTAGS \
all all-am am--refresh check check-am clean clean-generic \
clean-libtool ctags ctags-recursive dist dist-all dist-bzip2 \
dist-gzip dist-lzma dist-shar dist-tarZ dist-xz dist-zip \
distcheck distclean distclean-generic distclean-libtool \
distclean-tags distcleancheck distdir distuninstallcheck dvi \
dvi-am html html-am info info-am install install-am \
install-data install-data-am install-dvi install-dvi-am \
install-exec install-exec-am install-html install-html-am \
install-info install-info-am install-man install-pdf \
install-pdf-am install-ps install-ps-am install-strip \
installcheck installcheck-am installdirs installdirs-am \
maintainer-clean maintainer-clean-generic mostlyclean \
mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
tags tags-recursive uninstall uninstall-am
libtool: $(LIBTOOL_DEPS)
$(SHELL) ./config.status --recheck
# Tell versions [3.59,3.63) of GNU make to not export all variables.
# Otherwise a system limit (for SysV at least) may be exceeded.
.NOEXPORT:

43
libdivsufsort-2.0.1/NEWS Normal file
View File

@ -0,0 +1,43 @@
-- NEWS for libdivsufsort
Changes in release 2.0.0
- The build system was changed to CMake. (http://www.cmake.org/)
- Improved the performance of the suffix-sorting algorithm.
- Added OpenMP support.
- Added 64-bit version of divsufsort.
Changes in release 1.2.3
- Bug fixes.
Changes in release 1.2.2
- An important bug fix.
Changes in release 1.2.1
- A few bug fixes.
- New APIs: sa_search, sa_simplesearch.
Changes in release 1.2.0
- Changed license to the MIT/X11 license, see COPYING.
- Improved the performance of the suffix array construction.
- Change to use a new improved two-stage sorting algorithm.
- Replace substringsort with a new sorting algorithm that uses
multikey introspective sorting algorithm and in-place/recursive
merging algorithm.
- Replace drsort with a new sorting algorithm that uses
multikey introspective sorting algorithm, Maniscalco's tandem
repeat sorting algorithm and Larsson-Sadakane sorting algorithm.
- New API: divbwt.
Changes in release 1.0.2
- The performance of sorting has been improved.
- Fix some bugs.
Changes in release 1.0.1
- The performance of sorting has been improved a little bit.

250
libdivsufsort-2.0.1/README Normal file
View File

@ -0,0 +1,250 @@
libdivsufsort - A lightweight suffix-sorting library.
-----------------------------------------------------
Introduction:
-------------
The libdivsufsort project provides a fast, lightweight, and robust
C API library to construct the suffix array and the Burrows-Wheeler
transformed string for any input string of a constant-size alphabet.
The suffix-sorting algorithm runs in O(n log n) worst-case time
using only 5n+O(1) bytes of memory space, where n is the length of
the input string.
The latest version of libdivsufsort is available at:
http://libdivsufsort.googlecode.com/
License:
--------
libdivsufsort is released under the MIT/X11 license. See the file
COPYING for more details.
APIs:
-----
* Data types
typedef int32_t saint_t;
typedef int32_t saidx_t;
typedef uint8_t sauchar_t;
* Constructs the suffix array of a given string.
* @param T[0..n-1] The input string.
* @param SA[0..n-1] The output array or suffixes.
* @param n The length of the given string.
* @return 0 if no error occurred, -1 or -2 otherwise.
saint_t
divsufsort(const sauchar_t *T, saidx_t *SA, saidx_t n);
* Constructs the burrows-wheeler transformed string of a given string.
* @param T[0..n-1] The input string.
* @param U[0..n-1] The output string. (can be T)
* @param A[0..n-1] The temporary array. (can be NULL)
* @param n The length of the given string.
* @return The primary index if no error occurred, -1 or -2 otherwise.
saidx_t
divbwt(const sauchar_t *T, sauchar_t *U, saidx_t *A, saidx_t n);
* Constructs the burrows-wheeler transformed string of a given string and suffix array.
* @param T[0..n-1] The input string.
* @param U[0..n-1] The output string. (can be T)
* @param SA[0..n-1] The suffix array. (can be NULL)
* @param n The length of the given string.
* @param idx The output primary index.
* @return 0 if no error occurred, -1 or -2 otherwise.
saint_t
bw_transform(const sauchar_t *T, sauchar_t *U, saidx_t *SA,
saidx_t n, saidx_t *idx);
* Inverse BW-transforms a given BWTed string.
* @param T[0..n-1] The input string.
* @param U[0..n-1] The output string. (can be T)
* @param A[0..n-1] The temporary array. (can be NULL)
* @param n The length of the given string.
* @param idx The primary index.
* @return 0 if no error occurred, -1 or -2 otherwise.
saint_t
inverse_bw_transform(const sauchar_t *T, sauchar_t *U,
saidx_t *A, saidx_t n, saidx_t idx);
* Checks the correctness of a given suffix array.
* @param T[0..n-1] The input string.
* @param SA[0..n-1] The input suffix array.
* @param n The length of the given string.
* @param verbose The verbose mode.
* @return 0 if no error occurred.
saint_t
sufcheck(const sauchar_t *T, const saidx_t *SA,
saidx_t n, saint_t verbose);
* Search for the pattern P in the string T.
* @param T[0..Tsize-1] The input string.
* @param Tsize The length of the given string.
* @param P[0..Psize-1] The input pattern string.
* @param Psize The length of the given pattern string.
* @param SA[0..SAsize-1] The input suffix array.
* @param SAsize The length of the given suffix array.
* @param idx The output index.
* @return The count of matches if no error occurred, -1 otherwise.
saidx_t
sa_search(const sauchar_t *T, saidx_t Tsize,
const sauchar_t *P, saidx_t Psize,
const saidx_t *SA, saidx_t SAsize,
saidx_t *idx);
* Search for the character c in the string T.
* @param T[0..Tsize-1] The input string.
* @param Tsize The length of the given string.
* @param SA[0..SAsize-1] The input suffix array.
* @param SAsize The length of the given suffix array.
* @param c The input character.
* @param idx The output index.
* @return The count of matches if no error occurred, -1 otherwise.
saidx_t
sa_simplesearch(const sauchar_t *T, saidx_t Tsize,
const saidx_t *SA, saidx_t SAsize,
saint_t c, saidx_t *idx);
* Returns the version string of libdivsufsort.
* @return the version string.
const char *
divsufsort_version(void);
Benchmark:
------------------
= Specifications =
Processor: 2.66 GHz Intel Core 2 Duo E6750
L1 Cache: (32 Kb + 32 Kb) x 2
L2 Cache: 4 Mb
RAM: 2 Gb main memory
Operating system: Windows XP Home SP 3 (with Cygwin)
Compiler: GCC version 4.3.1
= Programs =
Archon4r0 kvark's sorting algorithm http://forum.compression.ru/viewtopic.php?t=352
BPR Bucket-Pointer Refinement algorithm http://bibiserv.techfak.uni-bielefeld.de/bpr/
DC Difference-Cover algorithm (v = 32) http://www.cs.helsinki.fi/juha.karkkainen/publications/cpm03.tar.gz
DS Deep-Shallow sorting algorithm http://www.mfn.unipmn.it/~manzini/lightweight/
divsufsort1 libdivsufsort version 1.2.3 http://libdivsufsort.googlecode.com/
divsufsort2 libdivsufsort version 2.0.0 http://libdivsufsort.googlecode.com/
KA Ko-Aluru algorithm http://ko.pang.cn.googlepages.com/software2
KS Kärkkäinen-Sanders algorithm http://www.mpi-inf.mpg.de/~sanders/programs/suffix/
MSufSort3 MSufSort version 3.1.1 beta http://www.michael-maniscalco.com/msufsort.htm
qsufsort Larsson-Sadakane algorithm http://www.larsson.dogma.net/research.html
sais Induced Sorting algorithm http://yuta.256.googlepages.com/sais
All programs were compiled with gcc/g++ using '-O3 -fomit-frame-pointer -DNDEBUG'
optimization options. The times are the average of five runs, in seconds, and were
measured using the standard Unix/Cygwin 'time' command. (user + system) The spaces
were measured using the 'memusage' command.
= Testfiles =
Manzini's Large Corpus http://www.mfn.unipmn.it/~manzini/lightweight/corpus/
The Gauntlet http://www.michael-maniscalco.com/testset/gauntlet/
= Running times =
== Manzini's Corpus ==
Files Size Archon4r0 BPR DC DS divsufsort1 divsufsort2 KA KS MSufSort3 qsufsort sais
chr22.dna 34553758 6.030 6.196 22.694 7.514 5.404 5.362 16.980 50.006 7.132 10.642 10.796
etext99 105277340 22.160 32.582 79.872 34.264 18.758 18.064 73.236 202.684 24.106 56.612 38.748
gcc-3.0.tar 86630400 13.856 20.692 61.690 35.822 10.382 10.084 40.908 135.174 14.952 40.766 20.990
howto 39422105 5.806 8.326 25.432 8.288 5.472 5.320 20.694 64.834 5.672 16.366 11.388
jdk13c 69728899 18.106 22.252 61.234 32.182 9.260 9.010 34.172 101.096 11.314 39.792 16.396
linux-2.4.5.tar 116254720 18.174 26.226 82.830 25.912 14.672 14.290 58.586 194.412 19.890 54.054 29.614
rctail96 114711151 32.490 55.826 119.026 62.502 18.500 17.914 70.072 190.562 21.060 70.456 33.248
rfc 116421901 20.736 35.404 91.284 29.666 16.116 15.658 64.390 196.500 17.936 61.436 32.224
sprot34.dat 109617186 22.832 36.720 93.122 32.096 17.894 17.404 68.084 187.594 23.352 56.946 34.092
w3c2 104201579 27.264 29.384 89.352 54.682 13.866 13.486 52.660 162.582 17.090 77.804 25.498
totals 896819039 187.454 273.608 726.536 322.928 130.324 126.592 499.782 1485.444 162.504 484.874 252.994
== The Gauntlet ==
Files Size Archon4r0 BPR DC DS divsufsort1 divsufsort2 KA KS MSufSort3 qsufsort sais
abac 200000 0.044 0.064 0.104 27.914 0.042 0.036 0.058 0.048 0.050 0.062 0.044
abba 10500600 3.270 5.124 10.766 30.702 1.714 1.602 2.570 7.952 3.514 15.272 1.460
book1x20 15375420 4.392 3.530 13.872 97.468 2.312 2.154 7.442 15.756 3.542 22.376 3.912
fib_s14930352 14930352 12.728 10.830 18.524 179.040 3.638 3.588 3.544 10.232 6.700 18.224 2.542
fss10 12078908 11.390 8.974 15.130 85.328 2.828 2.824 3.344 8.646 4.618 14.754 2.076
fss9 2851443 1.002 1.210 1.644 5.256 0.410 0.416 0.618 1.290 0.554 2.836 0.336
houston 3840000 0.344 0.708 2.226 118.960 0.118 0.128 0.520 0.744 0.242 1.230 0.238
paper5x80 981924 0.110 0.154 0.454 0.806 0.092 0.090 0.210 0.256 0.144 0.448 0.110
test1 2097152 0.332 2.132 1.108 8.680 0.268 0.280 0.376 1.066 1.302 2.762 0.202
test2 2097152 0.710 0.616 1.110 8.682 0.180 0.176 0.374 1.076 3.354 2.768 0.206
test3 2097152 0.488 213.154 1.164 1.772 0.220 0.226 0.388 1.082 0.922 3.246 0.212
totals 67050103 34.810 246.496 66.102 564.608 11.822 11.520 19.444 48.148 24.942 83.978 11.338
= Space (in MiBytes) =
== Manzini's Corpus ==
Files Size Archon4r0 BPR DC DS divsufsort1 divsufsort2 KA KS MSufSort3 qsufsort sais
chr22.dna 34553758 174.66 296.88 193.60 165.18 165.02 165.02 289.97 428.39 199.72 263.62 164.77
etext99 105277340 531.13 915.48 589.85 503.23 502.25 502.25 907.34 1305.20 604.45 803.20 502.00
gcc-3.0.tar 86630400 437.14 756.43 485.38 415.87 413.34 413.34 709.50 1074.01 497.79 660.94 413.09
howto 39422105 199.20 367.53 220.88 188.45 188.23 188.23 331.54 488.75 227.67 300.77 187.98
jdk13c 69728899 351.96 603.99 390.68 333.40 332.74 332.74 609.71 864.48 401.04 531.99 332.49
linux-2.4.5.tar 116254720 586.46 1061.83 651.36 555.76 554.60 554.60 977.81 1441.30 667.39 886.95 554.35
rctail96 114711151 578.68 987.64 642.71 548.32 547.24 547.24 1004.98 1422.16 658.43 875.18 546.99
rfc 116421901 587.30 1005.85 652.29 556.53 555.39 555.39 956.52 1443.37 668.26 888.23 555.14
sprot34.dat 109617186 553.01 941.95 614.17 524.03 522.95 522.95 930.06 1359.01 629.26 836.31 522.70
w3c2 104201579 525.71 958.37 583.82 498.09 497.12 497.12 912.00 1291.87 598.82 795.00 496.87
totals 896819039 4525.25 7895.95 5024.74 4288.86 4278.88 4278.88 7629.43 11118.54 5152.83 6842.19 4276.38
mean - 5.29 9.23 5.88 5.01 5.00 5.00 8.92 13.00 6.02 8.00 5.00
== The Gauntlet ==
Files Size Archon4r0 BPR DC DS divsufsort1 divsufsort2 KA KS MSufSort3 qsufsort sais
abac 200000 1.51 1.73 1.12 0.98 1.21 1.20 1.75 2.48 3.15 1.53 0.95
abba 10500600 53.43 90.19 58.83 50.21 50.32 50.32 86.20 130.18 62.09 80.11 50.07
book1x20 15375420 78.00 134.00 86.15 73.52 73.57 73.57 132.42 190.62 89.99 117.31 73.32
fib_s14930352 14930352 75.75 128.15 83.65 71.71 71.44 71.44 117.16 185.10 87.43 113.91 71.19
fss10 12078908 61.38 103.68 67.68 58.05 57.85 57.85 107.05 149.75 71.12 92.16 57.60
fss9 2851443 14.87 24.48 15.98 13.71 13.85 13.85 25.27 35.35 18.32 21.76 13.60
houston 3840000 19.85 36.96 21.52 18.46 18.56 18.56 28.79 47.58 23.98 29.30 18.31
paper5x80 981924 5.45 11.40 5.50 4.72 4.93 4.93 8.59 12.17 7.63 7.49 4.68
test1 2097152 11.07 82.00 11.75 10.10 10.25 10.25 18.34 25.99 14.01 16.00 10.00
test2 2097152 11.07 82.00 11.75 10.10 10.25 10.25 18.34 25.99 14.01 16.00 10.00
test3 2097152 11.07 82.00 11.75 10.05 10.25 10.25 18.34 26.00 14.63 16.00 10.12
totals 67050103 343.45 776.59 375.68 321.61 322.48 322.47 562.25 831.21 406.36 511.57 319.84
mean - 5.37 12.14 5.88 5.03 5.04 5.04 8.79 13.00 6.35 8.00 5.00
Algorithm:
----------
libdivsufsort uses the following algorithms for suffix sorting.
- The improved version of Itho-Tanaka two-stage sorting algorithm. [2][6]
- A substring sorting/encoding technique. [1][3]
- Maniscalco's tandem repeat sorting algorithm. [5]
- Larsson-Sadakane sorting algorithm. [4]
References:
-----------
1. Stefan Burkhardt and Juha K"arkk"ainen. Fast lightweight suffix
array construction and checking. Proceedings of the 14th Annual
Symposium on Combinatorial Pattern Matching, LNCS 2676,
Springer, pp. 55-69, 2003.
2. Hideo Itoh and Hozumi Tanaka, An Efficient Method for in Memory
Construction of Suffix Arrays, Proceedings of the IEEE String
Processing and Information Retrieval Symposium, pp. 81-88, 1999.
3. Pang Ko and Srinivas Aluru, Space-efficient linear time
construction of suffix arrays, Proceedings of the 14th Annual
Symposium on Combinatorial Pattern Matching, pp. 200-210, 2003.
4. Jesper Larsson and Kunihiko Sadakane, Faster suffix sorting.
Technical report LU-CS-TR:99-214, Department of Computer
Science, Lund University, Sweden, 1999.
5. Michael Maniscalco, MSufSort.
http://www.michael-maniscalco.com/msufsort.htm
6. Yuta Mori, Short description of improved two-stage suffix sorting
algorithm, 2005.
http://homepage3.nifty.com/wpage/software/itssort.txt

View File

@ -0,0 +1 @@
2.0.1

996
libdivsufsort-2.0.1/aclocal.m4 vendored Normal file
View File

@ -0,0 +1,996 @@
# generated automatically by aclocal 1.11.1 -*- Autoconf -*-
# Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004,
# 2005, 2006, 2007, 2008, 2009 Free Software Foundation, Inc.
# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
# with or without modifications, as long as this notice is preserved.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE.
m4_ifndef([AC_AUTOCONF_VERSION],
[m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl
m4_if(m4_defn([AC_AUTOCONF_VERSION]), [2.67],,
[m4_warning([this file was generated for autoconf 2.67.
You have another version of autoconf. It may work, but is not guaranteed to.
If you have problems, you may need to regenerate the build system entirely.
To do so, use the procedure documented by the package, typically `autoreconf'.])])
# Copyright (C) 2002, 2003, 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
#
# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
# with or without modifications, as long as this notice is preserved.
# AM_AUTOMAKE_VERSION(VERSION)
# ----------------------------
# Automake X.Y traces this macro to ensure aclocal.m4 has been
# generated from the m4 files accompanying Automake X.Y.
# (This private macro should not be called outside this file.)
AC_DEFUN([AM_AUTOMAKE_VERSION],
[am__api_version='1.11'
dnl Some users find AM_AUTOMAKE_VERSION and mistake it for a way to
dnl require some minimum version. Point them to the right macro.
m4_if([$1], [1.11.1], [],
[AC_FATAL([Do not call $0, use AM_INIT_AUTOMAKE([$1]).])])dnl
])
# _AM_AUTOCONF_VERSION(VERSION)
# -----------------------------
# aclocal traces this macro to find the Autoconf version.
# This is a private macro too. Using m4_define simplifies
# the logic in aclocal, which can simply ignore this definition.
m4_define([_AM_AUTOCONF_VERSION], [])
# AM_SET_CURRENT_AUTOMAKE_VERSION
# -------------------------------
# Call AM_AUTOMAKE_VERSION and AM_AUTOMAKE_VERSION so they can be traced.
# This function is AC_REQUIREd by AM_INIT_AUTOMAKE.
AC_DEFUN([AM_SET_CURRENT_AUTOMAKE_VERSION],
[AM_AUTOMAKE_VERSION([1.11.1])dnl
m4_ifndef([AC_AUTOCONF_VERSION],
[m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl
_AM_AUTOCONF_VERSION(m4_defn([AC_AUTOCONF_VERSION]))])
# AM_AUX_DIR_EXPAND -*- Autoconf -*-
# Copyright (C) 2001, 2003, 2005 Free Software Foundation, Inc.
#
# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
# with or without modifications, as long as this notice is preserved.
# For projects using AC_CONFIG_AUX_DIR([foo]), Autoconf sets
# $ac_aux_dir to `$srcdir/foo'. In other projects, it is set to
# `$srcdir', `$srcdir/..', or `$srcdir/../..'.
#
# Of course, Automake must honor this variable whenever it calls a
# tool from the auxiliary directory. The problem is that $srcdir (and
# therefore $ac_aux_dir as well) can be either absolute or relative,
# depending on how configure is run. This is pretty annoying, since
# it makes $ac_aux_dir quite unusable in subdirectories: in the top
# source directory, any form will work fine, but in subdirectories a
# relative path needs to be adjusted first.
#
# $ac_aux_dir/missing
# fails when called from a subdirectory if $ac_aux_dir is relative
# $top_srcdir/$ac_aux_dir/missing
# fails if $ac_aux_dir is absolute,
# fails when called from a subdirectory in a VPATH build with
# a relative $ac_aux_dir
#
# The reason of the latter failure is that $top_srcdir and $ac_aux_dir
# are both prefixed by $srcdir. In an in-source build this is usually
# harmless because $srcdir is `.', but things will broke when you
# start a VPATH build or use an absolute $srcdir.
#
# So we could use something similar to $top_srcdir/$ac_aux_dir/missing,
# iff we strip the leading $srcdir from $ac_aux_dir. That would be:
# am_aux_dir='\$(top_srcdir)/'`expr "$ac_aux_dir" : "$srcdir//*\(.*\)"`
# and then we would define $MISSING as
# MISSING="\${SHELL} $am_aux_dir/missing"
# This will work as long as MISSING is not called from configure, because
# unfortunately $(top_srcdir) has no meaning in configure.
# However there are other variables, like CC, which are often used in
# configure, and could therefore not use this "fixed" $ac_aux_dir.
#
# Another solution, used here, is to always expand $ac_aux_dir to an
# absolute PATH. The drawback is that using absolute paths prevent a
# configured tree to be moved without reconfiguration.
AC_DEFUN([AM_AUX_DIR_EXPAND],
[dnl Rely on autoconf to set up CDPATH properly.
AC_PREREQ([2.50])dnl
# expand $ac_aux_dir to an absolute path
am_aux_dir=`cd $ac_aux_dir && pwd`
])
# AM_CONDITIONAL -*- Autoconf -*-
# Copyright (C) 1997, 2000, 2001, 2003, 2004, 2005, 2006, 2008
# Free Software Foundation, Inc.
#
# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
# with or without modifications, as long as this notice is preserved.
# serial 9
# AM_CONDITIONAL(NAME, SHELL-CONDITION)
# -------------------------------------
# Define a conditional.
AC_DEFUN([AM_CONDITIONAL],
[AC_PREREQ(2.52)dnl
ifelse([$1], [TRUE], [AC_FATAL([$0: invalid condition: $1])],
[$1], [FALSE], [AC_FATAL([$0: invalid condition: $1])])dnl
AC_SUBST([$1_TRUE])dnl
AC_SUBST([$1_FALSE])dnl
_AM_SUBST_NOTMAKE([$1_TRUE])dnl
_AM_SUBST_NOTMAKE([$1_FALSE])dnl
m4_define([_AM_COND_VALUE_$1], [$2])dnl
if $2; then
$1_TRUE=
$1_FALSE='#'
else
$1_TRUE='#'
$1_FALSE=
fi
AC_CONFIG_COMMANDS_PRE(
[if test -z "${$1_TRUE}" && test -z "${$1_FALSE}"; then
AC_MSG_ERROR([[conditional "$1" was never defined.
Usually this means the macro was only invoked conditionally.]])
fi])])
# Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2009
# Free Software Foundation, Inc.
#
# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
# with or without modifications, as long as this notice is preserved.
# serial 10
# There are a few dirty hacks below to avoid letting `AC_PROG_CC' be
# written in clear, in which case automake, when reading aclocal.m4,
# will think it sees a *use*, and therefore will trigger all it's
# C support machinery. Also note that it means that autoscan, seeing
# CC etc. in the Makefile, will ask for an AC_PROG_CC use...
# _AM_DEPENDENCIES(NAME)
# ----------------------
# See how the compiler implements dependency checking.
# NAME is "CC", "CXX", "GCJ", or "OBJC".
# We try a few techniques and use that to set a single cache variable.
#
# We don't AC_REQUIRE the corresponding AC_PROG_CC since the latter was
# modified to invoke _AM_DEPENDENCIES(CC); we would have a circular
# dependency, and given that the user is not expected to run this macro,
# just rely on AC_PROG_CC.
AC_DEFUN([_AM_DEPENDENCIES],
[AC_REQUIRE([AM_SET_DEPDIR])dnl
AC_REQUIRE([AM_OUTPUT_DEPENDENCY_COMMANDS])dnl
AC_REQUIRE([AM_MAKE_INCLUDE])dnl
AC_REQUIRE([AM_DEP_TRACK])dnl
ifelse([$1], CC, [depcc="$CC" am_compiler_list=],
[$1], CXX, [depcc="$CXX" am_compiler_list=],
[$1], OBJC, [depcc="$OBJC" am_compiler_list='gcc3 gcc'],
[$1], UPC, [depcc="$UPC" am_compiler_list=],
[$1], GCJ, [depcc="$GCJ" am_compiler_list='gcc3 gcc'],
[depcc="$$1" am_compiler_list=])
AC_CACHE_CHECK([dependency style of $depcc],
[am_cv_$1_dependencies_compiler_type],
[if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then
# We make a subdir and do the tests there. Otherwise we can end up
# making bogus files that we don't know about and never remove. For
# instance it was reported that on HP-UX the gcc test will end up
# making a dummy file named `D' -- because `-MD' means `put the output
# in D'.
mkdir conftest.dir
# Copy depcomp to subdir because otherwise we won't find it if we're
# using a relative directory.
cp "$am_depcomp" conftest.dir
cd conftest.dir
# We will build objects and dependencies in a subdirectory because
# it helps to detect inapplicable dependency modes. For instance
# both Tru64's cc and ICC support -MD to output dependencies as a
# side effect of compilation, but ICC will put the dependencies in
# the current directory while Tru64 will put them in the object
# directory.
mkdir sub
am_cv_$1_dependencies_compiler_type=none
if test "$am_compiler_list" = ""; then
am_compiler_list=`sed -n ['s/^#*\([a-zA-Z0-9]*\))$/\1/p'] < ./depcomp`
fi
am__universal=false
m4_case([$1], [CC],
[case " $depcc " in #(
*\ -arch\ *\ -arch\ *) am__universal=true ;;
esac],
[CXX],
[case " $depcc " in #(
*\ -arch\ *\ -arch\ *) am__universal=true ;;
esac])
for depmode in $am_compiler_list; do
# Setup a source with many dependencies, because some compilers
# like to wrap large dependency lists on column 80 (with \), and
# we should not choose a depcomp mode which is confused by this.
#
# We need to recreate these files for each test, as the compiler may
# overwrite some of them when testing with obscure command lines.
# This happens at least with the AIX C compiler.
: > sub/conftest.c
for i in 1 2 3 4 5 6; do
echo '#include "conftst'$i'.h"' >> sub/conftest.c
# Using `: > sub/conftst$i.h' creates only sub/conftst1.h with
# Solaris 8's {/usr,}/bin/sh.
touch sub/conftst$i.h
done
echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf
# We check with `-c' and `-o' for the sake of the "dashmstdout"
# mode. It turns out that the SunPro C++ compiler does not properly
# handle `-M -o', and we need to detect this. Also, some Intel
# versions had trouble with output in subdirs
am__obj=sub/conftest.${OBJEXT-o}
am__minus_obj="-o $am__obj"
case $depmode in
gcc)
# This depmode causes a compiler race in universal mode.
test "$am__universal" = false || continue
;;
nosideeffect)
# after this tag, mechanisms are not by side-effect, so they'll
# only be used when explicitly requested
if test "x$enable_dependency_tracking" = xyes; then
continue
else
break
fi
;;
msvisualcpp | msvcmsys)
# This compiler won't grok `-c -o', but also, the minuso test has
# not run yet. These depmodes are late enough in the game, and
# so weak that their functioning should not be impacted.
am__obj=conftest.${OBJEXT-o}
am__minus_obj=
;;
none) break ;;
esac
if depmode=$depmode \
source=sub/conftest.c object=$am__obj \
depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \
$SHELL ./depcomp $depcc -c $am__minus_obj sub/conftest.c \
>/dev/null 2>conftest.err &&
grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 &&
grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 &&
grep $am__obj sub/conftest.Po > /dev/null 2>&1 &&
${MAKE-make} -s -f confmf > /dev/null 2>&1; then
# icc doesn't choke on unknown options, it will just issue warnings
# or remarks (even with -Werror). So we grep stderr for any message
# that says an option was ignored or not supported.
# When given -MP, icc 7.0 and 7.1 complain thusly:
# icc: Command line warning: ignoring option '-M'; no argument required
# The diagnosis changed in icc 8.0:
# icc: Command line remark: option '-MP' not supported
if (grep 'ignoring option' conftest.err ||
grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else
am_cv_$1_dependencies_compiler_type=$depmode
break
fi
fi
done
cd ..
rm -rf conftest.dir
else
am_cv_$1_dependencies_compiler_type=none
fi
])
AC_SUBST([$1DEPMODE], [depmode=$am_cv_$1_dependencies_compiler_type])
AM_CONDITIONAL([am__fastdep$1], [
test "x$enable_dependency_tracking" != xno \
&& test "$am_cv_$1_dependencies_compiler_type" = gcc3])
])
# AM_SET_DEPDIR
# -------------
# Choose a directory name for dependency files.
# This macro is AC_REQUIREd in _AM_DEPENDENCIES
AC_DEFUN([AM_SET_DEPDIR],
[AC_REQUIRE([AM_SET_LEADING_DOT])dnl
AC_SUBST([DEPDIR], ["${am__leading_dot}deps"])dnl
])
# AM_DEP_TRACK
# ------------
AC_DEFUN([AM_DEP_TRACK],
[AC_ARG_ENABLE(dependency-tracking,
[ --disable-dependency-tracking speeds up one-time build
--enable-dependency-tracking do not reject slow dependency extractors])
if test "x$enable_dependency_tracking" != xno; then
am_depcomp="$ac_aux_dir/depcomp"
AMDEPBACKSLASH='\'
fi
AM_CONDITIONAL([AMDEP], [test "x$enable_dependency_tracking" != xno])
AC_SUBST([AMDEPBACKSLASH])dnl
_AM_SUBST_NOTMAKE([AMDEPBACKSLASH])dnl
])
# Generate code to set up dependency tracking. -*- Autoconf -*-
# Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2008
# Free Software Foundation, Inc.
#
# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
# with or without modifications, as long as this notice is preserved.
#serial 5
# _AM_OUTPUT_DEPENDENCY_COMMANDS
# ------------------------------
AC_DEFUN([_AM_OUTPUT_DEPENDENCY_COMMANDS],
[{
# Autoconf 2.62 quotes --file arguments for eval, but not when files
# are listed without --file. Let's play safe and only enable the eval
# if we detect the quoting.
case $CONFIG_FILES in
*\'*) eval set x "$CONFIG_FILES" ;;
*) set x $CONFIG_FILES ;;
esac
shift
for mf
do
# Strip MF so we end up with the name of the file.
mf=`echo "$mf" | sed -e 's/:.*$//'`
# Check whether this is an Automake generated Makefile or not.
# We used to match only the files named `Makefile.in', but
# some people rename them; so instead we look at the file content.
# Grep'ing the first line is not enough: some people post-process
# each Makefile.in and add a new line on top of each file to say so.
# Grep'ing the whole file is not good either: AIX grep has a line
# limit of 2048, but all sed's we know have understand at least 4000.
if sed -n 's,^#.*generated by automake.*,X,p' "$mf" | grep X >/dev/null 2>&1; then
dirpart=`AS_DIRNAME("$mf")`
else
continue
fi
# Extract the definition of DEPDIR, am__include, and am__quote
# from the Makefile without running `make'.
DEPDIR=`sed -n 's/^DEPDIR = //p' < "$mf"`
test -z "$DEPDIR" && continue
am__include=`sed -n 's/^am__include = //p' < "$mf"`
test -z "am__include" && continue
am__quote=`sed -n 's/^am__quote = //p' < "$mf"`
# When using ansi2knr, U may be empty or an underscore; expand it
U=`sed -n 's/^U = //p' < "$mf"`
# Find all dependency output files, they are included files with
# $(DEPDIR) in their names. We invoke sed twice because it is the
# simplest approach to changing $(DEPDIR) to its actual value in the
# expansion.
for file in `sed -n "
s/^$am__include $am__quote\(.*(DEPDIR).*\)$am__quote"'$/\1/p' <"$mf" | \
sed -e 's/\$(DEPDIR)/'"$DEPDIR"'/g' -e 's/\$U/'"$U"'/g'`; do
# Make sure the directory exists.
test -f "$dirpart/$file" && continue
fdir=`AS_DIRNAME(["$file"])`
AS_MKDIR_P([$dirpart/$fdir])
# echo "creating $dirpart/$file"
echo '# dummy' > "$dirpart/$file"
done
done
}
])# _AM_OUTPUT_DEPENDENCY_COMMANDS
# AM_OUTPUT_DEPENDENCY_COMMANDS
# -----------------------------
# This macro should only be invoked once -- use via AC_REQUIRE.
#
# This code is only required when automatic dependency tracking
# is enabled. FIXME. This creates each `.P' file that we will
# need in order to bootstrap the dependency handling code.
AC_DEFUN([AM_OUTPUT_DEPENDENCY_COMMANDS],
[AC_CONFIG_COMMANDS([depfiles],
[test x"$AMDEP_TRUE" != x"" || _AM_OUTPUT_DEPENDENCY_COMMANDS],
[AMDEP_TRUE="$AMDEP_TRUE" ac_aux_dir="$ac_aux_dir"])
])
# Do all the work for Automake. -*- Autoconf -*-
# Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004,
# 2005, 2006, 2008, 2009 Free Software Foundation, Inc.
#
# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
# with or without modifications, as long as this notice is preserved.
# serial 16
# This macro actually does too much. Some checks are only needed if
# your package does certain things. But this isn't really a big deal.
# AM_INIT_AUTOMAKE(PACKAGE, VERSION, [NO-DEFINE])
# AM_INIT_AUTOMAKE([OPTIONS])
# -----------------------------------------------
# The call with PACKAGE and VERSION arguments is the old style
# call (pre autoconf-2.50), which is being phased out. PACKAGE
# and VERSION should now be passed to AC_INIT and removed from
# the call to AM_INIT_AUTOMAKE.
# We support both call styles for the transition. After
# the next Automake release, Autoconf can make the AC_INIT
# arguments mandatory, and then we can depend on a new Autoconf
# release and drop the old call support.
AC_DEFUN([AM_INIT_AUTOMAKE],
[AC_PREREQ([2.62])dnl
dnl Autoconf wants to disallow AM_ names. We explicitly allow
dnl the ones we care about.
m4_pattern_allow([^AM_[A-Z]+FLAGS$])dnl
AC_REQUIRE([AM_SET_CURRENT_AUTOMAKE_VERSION])dnl
AC_REQUIRE([AC_PROG_INSTALL])dnl
if test "`cd $srcdir && pwd`" != "`pwd`"; then
# Use -I$(srcdir) only when $(srcdir) != ., so that make's output
# is not polluted with repeated "-I."
AC_SUBST([am__isrc], [' -I$(srcdir)'])_AM_SUBST_NOTMAKE([am__isrc])dnl
# test to see if srcdir already configured
if test -f $srcdir/config.status; then
AC_MSG_ERROR([source directory already configured; run "make distclean" there first])
fi
fi
# test whether we have cygpath
if test -z "$CYGPATH_W"; then
if (cygpath --version) >/dev/null 2>/dev/null; then
CYGPATH_W='cygpath -w'
else
CYGPATH_W=echo
fi
fi
AC_SUBST([CYGPATH_W])
# Define the identity of the package.
dnl Distinguish between old-style and new-style calls.
m4_ifval([$2],
[m4_ifval([$3], [_AM_SET_OPTION([no-define])])dnl
AC_SUBST([PACKAGE], [$1])dnl
AC_SUBST([VERSION], [$2])],
[_AM_SET_OPTIONS([$1])dnl
dnl Diagnose old-style AC_INIT with new-style AM_AUTOMAKE_INIT.
m4_if(m4_ifdef([AC_PACKAGE_NAME], 1)m4_ifdef([AC_PACKAGE_VERSION], 1), 11,,
[m4_fatal([AC_INIT should be called with package and version arguments])])dnl
AC_SUBST([PACKAGE], ['AC_PACKAGE_TARNAME'])dnl
AC_SUBST([VERSION], ['AC_PACKAGE_VERSION'])])dnl
_AM_IF_OPTION([no-define],,
[AC_DEFINE_UNQUOTED(PACKAGE, "$PACKAGE", [Name of package])
AC_DEFINE_UNQUOTED(VERSION, "$VERSION", [Version number of package])])dnl
# Some tools Automake needs.
AC_REQUIRE([AM_SANITY_CHECK])dnl
AC_REQUIRE([AC_ARG_PROGRAM])dnl
AM_MISSING_PROG(ACLOCAL, aclocal-${am__api_version})
AM_MISSING_PROG(AUTOCONF, autoconf)
AM_MISSING_PROG(AUTOMAKE, automake-${am__api_version})
AM_MISSING_PROG(AUTOHEADER, autoheader)
AM_MISSING_PROG(MAKEINFO, makeinfo)
AC_REQUIRE([AM_PROG_INSTALL_SH])dnl
AC_REQUIRE([AM_PROG_INSTALL_STRIP])dnl
AC_REQUIRE([AM_PROG_MKDIR_P])dnl
# We need awk for the "check" target. The system "awk" is bad on
# some platforms.
AC_REQUIRE([AC_PROG_AWK])dnl
AC_REQUIRE([AC_PROG_MAKE_SET])dnl
AC_REQUIRE([AM_SET_LEADING_DOT])dnl
_AM_IF_OPTION([tar-ustar], [_AM_PROG_TAR([ustar])],
[_AM_IF_OPTION([tar-pax], [_AM_PROG_TAR([pax])],
[_AM_PROG_TAR([v7])])])
_AM_IF_OPTION([no-dependencies],,
[AC_PROVIDE_IFELSE([AC_PROG_CC],
[_AM_DEPENDENCIES(CC)],
[define([AC_PROG_CC],
defn([AC_PROG_CC])[_AM_DEPENDENCIES(CC)])])dnl
AC_PROVIDE_IFELSE([AC_PROG_CXX],
[_AM_DEPENDENCIES(CXX)],
[define([AC_PROG_CXX],
defn([AC_PROG_CXX])[_AM_DEPENDENCIES(CXX)])])dnl
AC_PROVIDE_IFELSE([AC_PROG_OBJC],
[_AM_DEPENDENCIES(OBJC)],
[define([AC_PROG_OBJC],
defn([AC_PROG_OBJC])[_AM_DEPENDENCIES(OBJC)])])dnl
])
_AM_IF_OPTION([silent-rules], [AC_REQUIRE([AM_SILENT_RULES])])dnl
dnl The `parallel-tests' driver may need to know about EXEEXT, so add the
dnl `am__EXEEXT' conditional if _AM_COMPILER_EXEEXT was seen. This macro
dnl is hooked onto _AC_COMPILER_EXEEXT early, see below.
AC_CONFIG_COMMANDS_PRE(dnl
[m4_provide_if([_AM_COMPILER_EXEEXT],
[AM_CONDITIONAL([am__EXEEXT], [test -n "$EXEEXT"])])])dnl
])
dnl Hook into `_AC_COMPILER_EXEEXT' early to learn its expansion. Do not
dnl add the conditional right here, as _AC_COMPILER_EXEEXT may be further
dnl mangled by Autoconf and run in a shell conditional statement.
m4_define([_AC_COMPILER_EXEEXT],
m4_defn([_AC_COMPILER_EXEEXT])[m4_provide([_AM_COMPILER_EXEEXT])])
# When config.status generates a header, we must update the stamp-h file.
# This file resides in the same directory as the config header
# that is generated. The stamp files are numbered to have different names.
# Autoconf calls _AC_AM_CONFIG_HEADER_HOOK (when defined) in the
# loop where config.status creates the headers, so we can generate
# our stamp files there.
AC_DEFUN([_AC_AM_CONFIG_HEADER_HOOK],
[# Compute $1's index in $config_headers.
_am_arg=$1
_am_stamp_count=1
for _am_header in $config_headers :; do
case $_am_header in
$_am_arg | $_am_arg:* )
break ;;
* )
_am_stamp_count=`expr $_am_stamp_count + 1` ;;
esac
done
echo "timestamp for $_am_arg" >`AS_DIRNAME(["$_am_arg"])`/stamp-h[]$_am_stamp_count])
# Copyright (C) 2001, 2003, 2005, 2008 Free Software Foundation, Inc.
#
# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
# with or without modifications, as long as this notice is preserved.
# AM_PROG_INSTALL_SH
# ------------------
# Define $install_sh.
AC_DEFUN([AM_PROG_INSTALL_SH],
[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl
if test x"${install_sh}" != xset; then
case $am_aux_dir in
*\ * | *\ *)
install_sh="\${SHELL} '$am_aux_dir/install-sh'" ;;
*)
install_sh="\${SHELL} $am_aux_dir/install-sh"
esac
fi
AC_SUBST(install_sh)])
# Copyright (C) 2003, 2005 Free Software Foundation, Inc.
#
# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
# with or without modifications, as long as this notice is preserved.
# serial 2
# Check whether the underlying file-system supports filenames
# with a leading dot. For instance MS-DOS doesn't.
AC_DEFUN([AM_SET_LEADING_DOT],
[rm -rf .tst 2>/dev/null
mkdir .tst 2>/dev/null
if test -d .tst; then
am__leading_dot=.
else
am__leading_dot=_
fi
rmdir .tst 2>/dev/null
AC_SUBST([am__leading_dot])])
# Add --enable-maintainer-mode option to configure. -*- Autoconf -*-
# From Jim Meyering
# Copyright (C) 1996, 1998, 2000, 2001, 2002, 2003, 2004, 2005, 2008
# Free Software Foundation, Inc.
#
# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
# with or without modifications, as long as this notice is preserved.
# serial 5
# AM_MAINTAINER_MODE([DEFAULT-MODE])
# ----------------------------------
# Control maintainer-specific portions of Makefiles.
# Default is to disable them, unless `enable' is passed literally.
# For symmetry, `disable' may be passed as well. Anyway, the user
# can override the default with the --enable/--disable switch.
AC_DEFUN([AM_MAINTAINER_MODE],
[m4_case(m4_default([$1], [disable]),
[enable], [m4_define([am_maintainer_other], [disable])],
[disable], [m4_define([am_maintainer_other], [enable])],
[m4_define([am_maintainer_other], [enable])
m4_warn([syntax], [unexpected argument to AM@&t@_MAINTAINER_MODE: $1])])
AC_MSG_CHECKING([whether to am_maintainer_other maintainer-specific portions of Makefiles])
dnl maintainer-mode's default is 'disable' unless 'enable' is passed
AC_ARG_ENABLE([maintainer-mode],
[ --][am_maintainer_other][-maintainer-mode am_maintainer_other make rules and dependencies not useful
(and sometimes confusing) to the casual installer],
[USE_MAINTAINER_MODE=$enableval],
[USE_MAINTAINER_MODE=]m4_if(am_maintainer_other, [enable], [no], [yes]))
AC_MSG_RESULT([$USE_MAINTAINER_MODE])
AM_CONDITIONAL([MAINTAINER_MODE], [test $USE_MAINTAINER_MODE = yes])
MAINT=$MAINTAINER_MODE_TRUE
AC_SUBST([MAINT])dnl
]
)
AU_DEFUN([jm_MAINTAINER_MODE], [AM_MAINTAINER_MODE])
# Check to see how 'make' treats includes. -*- Autoconf -*-
# Copyright (C) 2001, 2002, 2003, 2005, 2009 Free Software Foundation, Inc.
#
# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
# with or without modifications, as long as this notice is preserved.
# serial 4
# AM_MAKE_INCLUDE()
# -----------------
# Check to see how make treats includes.
AC_DEFUN([AM_MAKE_INCLUDE],
[am_make=${MAKE-make}
cat > confinc << 'END'
am__doit:
@echo this is the am__doit target
.PHONY: am__doit
END
# If we don't find an include directive, just comment out the code.
AC_MSG_CHECKING([for style of include used by $am_make])
am__include="#"
am__quote=
_am_result=none
# First try GNU make style include.
echo "include confinc" > confmf
# Ignore all kinds of additional output from `make'.
case `$am_make -s -f confmf 2> /dev/null` in #(
*the\ am__doit\ target*)
am__include=include
am__quote=
_am_result=GNU
;;
esac
# Now try BSD make style include.
if test "$am__include" = "#"; then
echo '.include "confinc"' > confmf
case `$am_make -s -f confmf 2> /dev/null` in #(
*the\ am__doit\ target*)
am__include=.include
am__quote="\""
_am_result=BSD
;;
esac
fi
AC_SUBST([am__include])
AC_SUBST([am__quote])
AC_MSG_RESULT([$_am_result])
rm -f confinc confmf
])
# Fake the existence of programs that GNU maintainers use. -*- Autoconf -*-
# Copyright (C) 1997, 1999, 2000, 2001, 2003, 2004, 2005, 2008
# Free Software Foundation, Inc.
#
# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
# with or without modifications, as long as this notice is preserved.
# serial 6
# AM_MISSING_PROG(NAME, PROGRAM)
# ------------------------------
AC_DEFUN([AM_MISSING_PROG],
[AC_REQUIRE([AM_MISSING_HAS_RUN])
$1=${$1-"${am_missing_run}$2"}
AC_SUBST($1)])
# AM_MISSING_HAS_RUN
# ------------------
# Define MISSING if not defined so far and test if it supports --run.
# If it does, set am_missing_run to use it, otherwise, to nothing.
AC_DEFUN([AM_MISSING_HAS_RUN],
[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl
AC_REQUIRE_AUX_FILE([missing])dnl
if test x"${MISSING+set}" != xset; then
case $am_aux_dir in
*\ * | *\ *)
MISSING="\${SHELL} \"$am_aux_dir/missing\"" ;;
*)
MISSING="\${SHELL} $am_aux_dir/missing" ;;
esac
fi
# Use eval to expand $SHELL
if eval "$MISSING --run true"; then
am_missing_run="$MISSING --run "
else
am_missing_run=
AC_MSG_WARN([`missing' script is too old or missing])
fi
])
# Copyright (C) 2003, 2004, 2005, 2006 Free Software Foundation, Inc.
#
# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
# with or without modifications, as long as this notice is preserved.
# AM_PROG_MKDIR_P
# ---------------
# Check for `mkdir -p'.
AC_DEFUN([AM_PROG_MKDIR_P],
[AC_PREREQ([2.60])dnl
AC_REQUIRE([AC_PROG_MKDIR_P])dnl
dnl Automake 1.8 to 1.9.6 used to define mkdir_p. We now use MKDIR_P,
dnl while keeping a definition of mkdir_p for backward compatibility.
dnl @MKDIR_P@ is magic: AC_OUTPUT adjusts its value for each Makefile.
dnl However we cannot define mkdir_p as $(MKDIR_P) for the sake of
dnl Makefile.ins that do not define MKDIR_P, so we do our own
dnl adjustment using top_builddir (which is defined more often than
dnl MKDIR_P).
AC_SUBST([mkdir_p], ["$MKDIR_P"])dnl
case $mkdir_p in
[[\\/$]]* | ?:[[\\/]]*) ;;
*/*) mkdir_p="\$(top_builddir)/$mkdir_p" ;;
esac
])
# Helper functions for option handling. -*- Autoconf -*-
# Copyright (C) 2001, 2002, 2003, 2005, 2008 Free Software Foundation, Inc.
#
# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
# with or without modifications, as long as this notice is preserved.
# serial 4
# _AM_MANGLE_OPTION(NAME)
# -----------------------
AC_DEFUN([_AM_MANGLE_OPTION],
[[_AM_OPTION_]m4_bpatsubst($1, [[^a-zA-Z0-9_]], [_])])
# _AM_SET_OPTION(NAME)
# ------------------------------
# Set option NAME. Presently that only means defining a flag for this option.
AC_DEFUN([_AM_SET_OPTION],
[m4_define(_AM_MANGLE_OPTION([$1]), 1)])
# _AM_SET_OPTIONS(OPTIONS)
# ----------------------------------
# OPTIONS is a space-separated list of Automake options.
AC_DEFUN([_AM_SET_OPTIONS],
[m4_foreach_w([_AM_Option], [$1], [_AM_SET_OPTION(_AM_Option)])])
# _AM_IF_OPTION(OPTION, IF-SET, [IF-NOT-SET])
# -------------------------------------------
# Execute IF-SET if OPTION is set, IF-NOT-SET otherwise.
AC_DEFUN([_AM_IF_OPTION],
[m4_ifset(_AM_MANGLE_OPTION([$1]), [$2], [$3])])
# Check to make sure that the build environment is sane. -*- Autoconf -*-
# Copyright (C) 1996, 1997, 2000, 2001, 2003, 2005, 2008
# Free Software Foundation, Inc.
#
# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
# with or without modifications, as long as this notice is preserved.
# serial 5
# AM_SANITY_CHECK
# ---------------
AC_DEFUN([AM_SANITY_CHECK],
[AC_MSG_CHECKING([whether build environment is sane])
# Just in case
sleep 1
echo timestamp > conftest.file
# Reject unsafe characters in $srcdir or the absolute working directory
# name. Accept space and tab only in the latter.
am_lf='
'
case `pwd` in
*[[\\\"\#\$\&\'\`$am_lf]]*)
AC_MSG_ERROR([unsafe absolute working directory name]);;
esac
case $srcdir in
*[[\\\"\#\$\&\'\`$am_lf\ \ ]]*)
AC_MSG_ERROR([unsafe srcdir value: `$srcdir']);;
esac
# Do `set' in a subshell so we don't clobber the current shell's
# arguments. Must try -L first in case configure is actually a
# symlink; some systems play weird games with the mod time of symlinks
# (eg FreeBSD returns the mod time of the symlink's containing
# directory).
if (
set X `ls -Lt "$srcdir/configure" conftest.file 2> /dev/null`
if test "$[*]" = "X"; then
# -L didn't work.
set X `ls -t "$srcdir/configure" conftest.file`
fi
rm -f conftest.file
if test "$[*]" != "X $srcdir/configure conftest.file" \
&& test "$[*]" != "X conftest.file $srcdir/configure"; then
# If neither matched, then we have a broken ls. This can happen
# if, for instance, CONFIG_SHELL is bash and it inherits a
# broken ls alias from the environment. This has actually
# happened. Such a system could not be considered "sane".
AC_MSG_ERROR([ls -t appears to fail. Make sure there is not a broken
alias in your environment])
fi
test "$[2]" = conftest.file
)
then
# Ok.
:
else
AC_MSG_ERROR([newly created file is older than distributed files!
Check your system clock])
fi
AC_MSG_RESULT(yes)])
# Copyright (C) 2001, 2003, 2005 Free Software Foundation, Inc.
#
# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
# with or without modifications, as long as this notice is preserved.
# AM_PROG_INSTALL_STRIP
# ---------------------
# One issue with vendor `install' (even GNU) is that you can't
# specify the program used to strip binaries. This is especially
# annoying in cross-compiling environments, where the build's strip
# is unlikely to handle the host's binaries.
# Fortunately install-sh will honor a STRIPPROG variable, so we
# always use install-sh in `make install-strip', and initialize
# STRIPPROG with the value of the STRIP variable (set by the user).
AC_DEFUN([AM_PROG_INSTALL_STRIP],
[AC_REQUIRE([AM_PROG_INSTALL_SH])dnl
# Installed binaries are usually stripped using `strip' when the user
# run `make install-strip'. However `strip' might not be the right
# tool to use in cross-compilation environments, therefore Automake
# will honor the `STRIP' environment variable to overrule this program.
dnl Don't test for $cross_compiling = yes, because it might be `maybe'.
if test "$cross_compiling" != no; then
AC_CHECK_TOOL([STRIP], [strip], :)
fi
INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s"
AC_SUBST([INSTALL_STRIP_PROGRAM])])
# Copyright (C) 2006, 2008 Free Software Foundation, Inc.
#
# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
# with or without modifications, as long as this notice is preserved.
# serial 2
# _AM_SUBST_NOTMAKE(VARIABLE)
# ---------------------------
# Prevent Automake from outputting VARIABLE = @VARIABLE@ in Makefile.in.
# This macro is traced by Automake.
AC_DEFUN([_AM_SUBST_NOTMAKE])
# AM_SUBST_NOTMAKE(VARIABLE)
# ---------------------------
# Public sister of _AM_SUBST_NOTMAKE.
AC_DEFUN([AM_SUBST_NOTMAKE], [_AM_SUBST_NOTMAKE($@)])
# Check how to create a tarball. -*- Autoconf -*-
# Copyright (C) 2004, 2005 Free Software Foundation, Inc.
#
# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
# with or without modifications, as long as this notice is preserved.
# serial 2
# _AM_PROG_TAR(FORMAT)
# --------------------
# Check how to create a tarball in format FORMAT.
# FORMAT should be one of `v7', `ustar', or `pax'.
#
# Substitute a variable $(am__tar) that is a command
# writing to stdout a FORMAT-tarball containing the directory
# $tardir.
# tardir=directory && $(am__tar) > result.tar
#
# Substitute a variable $(am__untar) that extract such
# a tarball read from stdin.
# $(am__untar) < result.tar
AC_DEFUN([_AM_PROG_TAR],
[# Always define AMTAR for backward compatibility.
AM_MISSING_PROG([AMTAR], [tar])
m4_if([$1], [v7],
[am__tar='${AMTAR} chof - "$$tardir"'; am__untar='${AMTAR} xf -'],
[m4_case([$1], [ustar],, [pax],,
[m4_fatal([Unknown tar format])])
AC_MSG_CHECKING([how to create a $1 tar archive])
# Loop over all known methods to create a tar archive until one works.
_am_tools='gnutar m4_if([$1], [ustar], [plaintar]) pax cpio none'
_am_tools=${am_cv_prog_tar_$1-$_am_tools}
# Do not fold the above two line into one, because Tru64 sh and
# Solaris sh will not grok spaces in the rhs of `-'.
for _am_tool in $_am_tools
do
case $_am_tool in
gnutar)
for _am_tar in tar gnutar gtar;
do
AM_RUN_LOG([$_am_tar --version]) && break
done
am__tar="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - "'"$$tardir"'
am__tar_="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - "'"$tardir"'
am__untar="$_am_tar -xf -"
;;
plaintar)
# Must skip GNU tar: if it does not support --format= it doesn't create
# ustar tarball either.
(tar --version) >/dev/null 2>&1 && continue
am__tar='tar chf - "$$tardir"'
am__tar_='tar chf - "$tardir"'
am__untar='tar xf -'
;;
pax)
am__tar='pax -L -x $1 -w "$$tardir"'
am__tar_='pax -L -x $1 -w "$tardir"'
am__untar='pax -r'
;;
cpio)
am__tar='find "$$tardir" -print | cpio -o -H $1 -L'
am__tar_='find "$tardir" -print | cpio -o -H $1 -L'
am__untar='cpio -i -H $1 -d'
;;
none)
am__tar=false
am__tar_=false
am__untar=false
;;
esac
# If the value was cached, stop now. We just wanted to have am__tar
# and am__untar set.
test -n "${am_cv_prog_tar_$1}" && break
# tar/untar a dummy directory, and stop if the command works
rm -rf conftest.dir
mkdir conftest.dir
echo GrepMe > conftest.dir/file
AM_RUN_LOG([tardir=conftest.dir && eval $am__tar_ >conftest.tar])
rm -rf conftest.dir
if test -s conftest.tar; then
AM_RUN_LOG([$am__untar <conftest.tar])
grep GrepMe conftest.dir/file >/dev/null 2>&1 && break
fi
done
rm -rf conftest.dir
AC_CACHE_VAL([am_cv_prog_tar_$1], [am_cv_prog_tar_$1=$_am_tool])
AC_MSG_RESULT([$am_cv_prog_tar_$1])])
AC_SUBST([am__tar])
AC_SUBST([am__untar])
]) # _AM_PROG_TAR
m4_include([m4/libtool.m4])
m4_include([m4/ltoptions.m4])
m4_include([m4/ltsugar.m4])
m4_include([m4/ltversion.m4])
m4_include([m4/lt~obsolete.m4])

1502
libdivsufsort-2.0.1/config/config.guess vendored Normal file

File diff suppressed because it is too large Load Diff

1714
libdivsufsort-2.0.1/config/config.sub vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,630 @@
#! /bin/sh
# depcomp - compile a program generating dependencies as side-effects
scriptversion=2009-04-28.21; # UTC
# Copyright (C) 1999, 2000, 2003, 2004, 2005, 2006, 2007, 2009 Free
# Software Foundation, Inc.
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# As a special exception to the GNU General Public License, if you
# distribute this file as part of a program that contains a
# configuration script generated by Autoconf, you may include it under
# the same distribution terms that you use for the rest of that program.
# Originally written by Alexandre Oliva <oliva@dcc.unicamp.br>.
case $1 in
'')
echo "$0: No command. Try \`$0 --help' for more information." 1>&2
exit 1;
;;
-h | --h*)
cat <<\EOF
Usage: depcomp [--help] [--version] PROGRAM [ARGS]
Run PROGRAMS ARGS to compile a file, generating dependencies
as side-effects.
Environment variables:
depmode Dependency tracking mode.
source Source file read by `PROGRAMS ARGS'.
object Object file output by `PROGRAMS ARGS'.
DEPDIR directory where to store dependencies.
depfile Dependency file to output.
tmpdepfile Temporary file to use when outputing dependencies.
libtool Whether libtool is used (yes/no).
Report bugs to <bug-automake@gnu.org>.
EOF
exit $?
;;
-v | --v*)
echo "depcomp $scriptversion"
exit $?
;;
esac
if test -z "$depmode" || test -z "$source" || test -z "$object"; then
echo "depcomp: Variables source, object and depmode must be set" 1>&2
exit 1
fi
# Dependencies for sub/bar.o or sub/bar.obj go into sub/.deps/bar.Po.
depfile=${depfile-`echo "$object" |
sed 's|[^\\/]*$|'${DEPDIR-.deps}'/&|;s|\.\([^.]*\)$|.P\1|;s|Pobj$|Po|'`}
tmpdepfile=${tmpdepfile-`echo "$depfile" | sed 's/\.\([^.]*\)$/.T\1/'`}
rm -f "$tmpdepfile"
# Some modes work just like other modes, but use different flags. We
# parameterize here, but still list the modes in the big case below,
# to make depend.m4 easier to write. Note that we *cannot* use a case
# here, because this file can only contain one case statement.
if test "$depmode" = hp; then
# HP compiler uses -M and no extra arg.
gccflag=-M
depmode=gcc
fi
if test "$depmode" = dashXmstdout; then
# This is just like dashmstdout with a different argument.
dashmflag=-xM
depmode=dashmstdout
fi
cygpath_u="cygpath -u -f -"
if test "$depmode" = msvcmsys; then
# This is just like msvisualcpp but w/o cygpath translation.
# Just convert the backslash-escaped backslashes to single forward
# slashes to satisfy depend.m4
cygpath_u="sed s,\\\\\\\\,/,g"
depmode=msvisualcpp
fi
case "$depmode" in
gcc3)
## gcc 3 implements dependency tracking that does exactly what
## we want. Yay! Note: for some reason libtool 1.4 doesn't like
## it if -MD -MP comes after the -MF stuff. Hmm.
## Unfortunately, FreeBSD c89 acceptance of flags depends upon
## the command line argument order; so add the flags where they
## appear in depend2.am. Note that the slowdown incurred here
## affects only configure: in makefiles, %FASTDEP% shortcuts this.
for arg
do
case $arg in
-c) set fnord "$@" -MT "$object" -MD -MP -MF "$tmpdepfile" "$arg" ;;
*) set fnord "$@" "$arg" ;;
esac
shift # fnord
shift # $arg
done
"$@"
stat=$?
if test $stat -eq 0; then :
else
rm -f "$tmpdepfile"
exit $stat
fi
mv "$tmpdepfile" "$depfile"
;;
gcc)
## There are various ways to get dependency output from gcc. Here's
## why we pick this rather obscure method:
## - Don't want to use -MD because we'd like the dependencies to end
## up in a subdir. Having to rename by hand is ugly.
## (We might end up doing this anyway to support other compilers.)
## - The DEPENDENCIES_OUTPUT environment variable makes gcc act like
## -MM, not -M (despite what the docs say).
## - Using -M directly means running the compiler twice (even worse
## than renaming).
if test -z "$gccflag"; then
gccflag=-MD,
fi
"$@" -Wp,"$gccflag$tmpdepfile"
stat=$?
if test $stat -eq 0; then :
else
rm -f "$tmpdepfile"
exit $stat
fi
rm -f "$depfile"
echo "$object : \\" > "$depfile"
alpha=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
## The second -e expression handles DOS-style file names with drive letters.
sed -e 's/^[^:]*: / /' \
-e 's/^['$alpha']:\/[^:]*: / /' < "$tmpdepfile" >> "$depfile"
## This next piece of magic avoids the `deleted header file' problem.
## The problem is that when a header file which appears in a .P file
## is deleted, the dependency causes make to die (because there is
## typically no way to rebuild the header). We avoid this by adding
## dummy dependencies for each header file. Too bad gcc doesn't do
## this for us directly.
tr ' ' '
' < "$tmpdepfile" |
## Some versions of gcc put a space before the `:'. On the theory
## that the space means something, we add a space to the output as
## well.
## Some versions of the HPUX 10.20 sed can't process this invocation
## correctly. Breaking it into two sed invocations is a workaround.
sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' | sed -e 's/$/ :/' >> "$depfile"
rm -f "$tmpdepfile"
;;
hp)
# This case exists only to let depend.m4 do its work. It works by
# looking at the text of this script. This case will never be run,
# since it is checked for above.
exit 1
;;
sgi)
if test "$libtool" = yes; then
"$@" "-Wp,-MDupdate,$tmpdepfile"
else
"$@" -MDupdate "$tmpdepfile"
fi
stat=$?
if test $stat -eq 0; then :
else
rm -f "$tmpdepfile"
exit $stat
fi
rm -f "$depfile"
if test -f "$tmpdepfile"; then # yes, the sourcefile depend on other files
echo "$object : \\" > "$depfile"
# Clip off the initial element (the dependent). Don't try to be
# clever and replace this with sed code, as IRIX sed won't handle
# lines with more than a fixed number of characters (4096 in
# IRIX 6.2 sed, 8192 in IRIX 6.5). We also remove comment lines;
# the IRIX cc adds comments like `#:fec' to the end of the
# dependency line.
tr ' ' '
' < "$tmpdepfile" \
| sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' | \
tr '
' ' ' >> "$depfile"
echo >> "$depfile"
# The second pass generates a dummy entry for each header file.
tr ' ' '
' < "$tmpdepfile" \
| sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' -e 's/$/:/' \
>> "$depfile"
else
# The sourcefile does not contain any dependencies, so just
# store a dummy comment line, to avoid errors with the Makefile
# "include basename.Plo" scheme.
echo "#dummy" > "$depfile"
fi
rm -f "$tmpdepfile"
;;
aix)
# The C for AIX Compiler uses -M and outputs the dependencies
# in a .u file. In older versions, this file always lives in the
# current directory. Also, the AIX compiler puts `$object:' at the
# start of each line; $object doesn't have directory information.
# Version 6 uses the directory in both cases.
dir=`echo "$object" | sed -e 's|/[^/]*$|/|'`
test "x$dir" = "x$object" && dir=
base=`echo "$object" | sed -e 's|^.*/||' -e 's/\.o$//' -e 's/\.lo$//'`
if test "$libtool" = yes; then
tmpdepfile1=$dir$base.u
tmpdepfile2=$base.u
tmpdepfile3=$dir.libs/$base.u
"$@" -Wc,-M
else
tmpdepfile1=$dir$base.u
tmpdepfile2=$dir$base.u
tmpdepfile3=$dir$base.u
"$@" -M
fi
stat=$?
if test $stat -eq 0; then :
else
rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3"
exit $stat
fi
for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3"
do
test -f "$tmpdepfile" && break
done
if test -f "$tmpdepfile"; then
# Each line is of the form `foo.o: dependent.h'.
# Do two passes, one to just change these to
# `$object: dependent.h' and one to simply `dependent.h:'.
sed -e "s,^.*\.[a-z]*:,$object:," < "$tmpdepfile" > "$depfile"
# That's a tab and a space in the [].
sed -e 's,^.*\.[a-z]*:[ ]*,,' -e 's,$,:,' < "$tmpdepfile" >> "$depfile"
else
# The sourcefile does not contain any dependencies, so just
# store a dummy comment line, to avoid errors with the Makefile
# "include basename.Plo" scheme.
echo "#dummy" > "$depfile"
fi
rm -f "$tmpdepfile"
;;
icc)
# Intel's C compiler understands `-MD -MF file'. However on
# icc -MD -MF foo.d -c -o sub/foo.o sub/foo.c
# ICC 7.0 will fill foo.d with something like
# foo.o: sub/foo.c
# foo.o: sub/foo.h
# which is wrong. We want:
# sub/foo.o: sub/foo.c
# sub/foo.o: sub/foo.h
# sub/foo.c:
# sub/foo.h:
# ICC 7.1 will output
# foo.o: sub/foo.c sub/foo.h
# and will wrap long lines using \ :
# foo.o: sub/foo.c ... \
# sub/foo.h ... \
# ...
"$@" -MD -MF "$tmpdepfile"
stat=$?
if test $stat -eq 0; then :
else
rm -f "$tmpdepfile"
exit $stat
fi
rm -f "$depfile"
# Each line is of the form `foo.o: dependent.h',
# or `foo.o: dep1.h dep2.h \', or ` dep3.h dep4.h \'.
# Do two passes, one to just change these to
# `$object: dependent.h' and one to simply `dependent.h:'.
sed "s,^[^:]*:,$object :," < "$tmpdepfile" > "$depfile"
# Some versions of the HPUX 10.20 sed can't process this invocation
# correctly. Breaking it into two sed invocations is a workaround.
sed 's,^[^:]*: \(.*\)$,\1,;s/^\\$//;/^$/d;/:$/d' < "$tmpdepfile" |
sed -e 's/$/ :/' >> "$depfile"
rm -f "$tmpdepfile"
;;
hp2)
# The "hp" stanza above does not work with aCC (C++) and HP's ia64
# compilers, which have integrated preprocessors. The correct option
# to use with these is +Maked; it writes dependencies to a file named
# 'foo.d', which lands next to the object file, wherever that
# happens to be.
# Much of this is similar to the tru64 case; see comments there.
dir=`echo "$object" | sed -e 's|/[^/]*$|/|'`
test "x$dir" = "x$object" && dir=
base=`echo "$object" | sed -e 's|^.*/||' -e 's/\.o$//' -e 's/\.lo$//'`
if test "$libtool" = yes; then
tmpdepfile1=$dir$base.d
tmpdepfile2=$dir.libs/$base.d
"$@" -Wc,+Maked
else
tmpdepfile1=$dir$base.d
tmpdepfile2=$dir$base.d
"$@" +Maked
fi
stat=$?
if test $stat -eq 0; then :
else
rm -f "$tmpdepfile1" "$tmpdepfile2"
exit $stat
fi
for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2"
do
test -f "$tmpdepfile" && break
done
if test -f "$tmpdepfile"; then
sed -e "s,^.*\.[a-z]*:,$object:," "$tmpdepfile" > "$depfile"
# Add `dependent.h:' lines.
sed -ne '2,${
s/^ *//
s/ \\*$//
s/$/:/
p
}' "$tmpdepfile" >> "$depfile"
else
echo "#dummy" > "$depfile"
fi
rm -f "$tmpdepfile" "$tmpdepfile2"
;;
tru64)
# The Tru64 compiler uses -MD to generate dependencies as a side
# effect. `cc -MD -o foo.o ...' puts the dependencies into `foo.o.d'.
# At least on Alpha/Redhat 6.1, Compaq CCC V6.2-504 seems to put
# dependencies in `foo.d' instead, so we check for that too.
# Subdirectories are respected.
dir=`echo "$object" | sed -e 's|/[^/]*$|/|'`
test "x$dir" = "x$object" && dir=
base=`echo "$object" | sed -e 's|^.*/||' -e 's/\.o$//' -e 's/\.lo$//'`
if test "$libtool" = yes; then
# With Tru64 cc, shared objects can also be used to make a
# static library. This mechanism is used in libtool 1.4 series to
# handle both shared and static libraries in a single compilation.
# With libtool 1.4, dependencies were output in $dir.libs/$base.lo.d.
#
# With libtool 1.5 this exception was removed, and libtool now
# generates 2 separate objects for the 2 libraries. These two
# compilations output dependencies in $dir.libs/$base.o.d and
# in $dir$base.o.d. We have to check for both files, because
# one of the two compilations can be disabled. We should prefer
# $dir$base.o.d over $dir.libs/$base.o.d because the latter is
# automatically cleaned when .libs/ is deleted, while ignoring
# the former would cause a distcleancheck panic.
tmpdepfile1=$dir.libs/$base.lo.d # libtool 1.4
tmpdepfile2=$dir$base.o.d # libtool 1.5
tmpdepfile3=$dir.libs/$base.o.d # libtool 1.5
tmpdepfile4=$dir.libs/$base.d # Compaq CCC V6.2-504
"$@" -Wc,-MD
else
tmpdepfile1=$dir$base.o.d
tmpdepfile2=$dir$base.d
tmpdepfile3=$dir$base.d
tmpdepfile4=$dir$base.d
"$@" -MD
fi
stat=$?
if test $stat -eq 0; then :
else
rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" "$tmpdepfile4"
exit $stat
fi
for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" "$tmpdepfile4"
do
test -f "$tmpdepfile" && break
done
if test -f "$tmpdepfile"; then
sed -e "s,^.*\.[a-z]*:,$object:," < "$tmpdepfile" > "$depfile"
# That's a tab and a space in the [].
sed -e 's,^.*\.[a-z]*:[ ]*,,' -e 's,$,:,' < "$tmpdepfile" >> "$depfile"
else
echo "#dummy" > "$depfile"
fi
rm -f "$tmpdepfile"
;;
#nosideeffect)
# This comment above is used by automake to tell side-effect
# dependency tracking mechanisms from slower ones.
dashmstdout)
# Important note: in order to support this mode, a compiler *must*
# always write the preprocessed file to stdout, regardless of -o.
"$@" || exit $?
# Remove the call to Libtool.
if test "$libtool" = yes; then
while test "X$1" != 'X--mode=compile'; do
shift
done
shift
fi
# Remove `-o $object'.
IFS=" "
for arg
do
case $arg in
-o)
shift
;;
$object)
shift
;;
*)
set fnord "$@" "$arg"
shift # fnord
shift # $arg
;;
esac
done
test -z "$dashmflag" && dashmflag=-M
# Require at least two characters before searching for `:'
# in the target name. This is to cope with DOS-style filenames:
# a dependency such as `c:/foo/bar' could be seen as target `c' otherwise.
"$@" $dashmflag |
sed 's:^[ ]*[^: ][^:][^:]*\:[ ]*:'"$object"'\: :' > "$tmpdepfile"
rm -f "$depfile"
cat < "$tmpdepfile" > "$depfile"
tr ' ' '
' < "$tmpdepfile" | \
## Some versions of the HPUX 10.20 sed can't process this invocation
## correctly. Breaking it into two sed invocations is a workaround.
sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' | sed -e 's/$/ :/' >> "$depfile"
rm -f "$tmpdepfile"
;;
dashXmstdout)
# This case only exists to satisfy depend.m4. It is never actually
# run, as this mode is specially recognized in the preamble.
exit 1
;;
makedepend)
"$@" || exit $?
# Remove any Libtool call
if test "$libtool" = yes; then
while test "X$1" != 'X--mode=compile'; do
shift
done
shift
fi
# X makedepend
shift
cleared=no eat=no
for arg
do
case $cleared in
no)
set ""; shift
cleared=yes ;;
esac
if test $eat = yes; then
eat=no
continue
fi
case "$arg" in
-D*|-I*)
set fnord "$@" "$arg"; shift ;;
# Strip any option that makedepend may not understand. Remove
# the object too, otherwise makedepend will parse it as a source file.
-arch)
eat=yes ;;
-*|$object)
;;
*)
set fnord "$@" "$arg"; shift ;;
esac
done
obj_suffix=`echo "$object" | sed 's/^.*\././'`
touch "$tmpdepfile"
${MAKEDEPEND-makedepend} -o"$obj_suffix" -f"$tmpdepfile" "$@"
rm -f "$depfile"
cat < "$tmpdepfile" > "$depfile"
sed '1,2d' "$tmpdepfile" | tr ' ' '
' | \
## Some versions of the HPUX 10.20 sed can't process this invocation
## correctly. Breaking it into two sed invocations is a workaround.
sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' | sed -e 's/$/ :/' >> "$depfile"
rm -f "$tmpdepfile" "$tmpdepfile".bak
;;
cpp)
# Important note: in order to support this mode, a compiler *must*
# always write the preprocessed file to stdout.
"$@" || exit $?
# Remove the call to Libtool.
if test "$libtool" = yes; then
while test "X$1" != 'X--mode=compile'; do
shift
done
shift
fi
# Remove `-o $object'.
IFS=" "
for arg
do
case $arg in
-o)
shift
;;
$object)
shift
;;
*)
set fnord "$@" "$arg"
shift # fnord
shift # $arg
;;
esac
done
"$@" -E |
sed -n -e '/^# [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' \
-e '/^#line [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' |
sed '$ s: \\$::' > "$tmpdepfile"
rm -f "$depfile"
echo "$object : \\" > "$depfile"
cat < "$tmpdepfile" >> "$depfile"
sed < "$tmpdepfile" '/^$/d;s/^ //;s/ \\$//;s/$/ :/' >> "$depfile"
rm -f "$tmpdepfile"
;;
msvisualcpp)
# Important note: in order to support this mode, a compiler *must*
# always write the preprocessed file to stdout.
"$@" || exit $?
# Remove the call to Libtool.
if test "$libtool" = yes; then
while test "X$1" != 'X--mode=compile'; do
shift
done
shift
fi
IFS=" "
for arg
do
case "$arg" in
-o)
shift
;;
$object)
shift
;;
"-Gm"|"/Gm"|"-Gi"|"/Gi"|"-ZI"|"/ZI")
set fnord "$@"
shift
shift
;;
*)
set fnord "$@" "$arg"
shift
shift
;;
esac
done
"$@" -E 2>/dev/null |
sed -n '/^#line [0-9][0-9]* "\([^"]*\)"/ s::\1:p' | $cygpath_u | sort -u > "$tmpdepfile"
rm -f "$depfile"
echo "$object : \\" > "$depfile"
sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s:: \1 \\:p' >> "$depfile"
echo " " >> "$depfile"
sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s::\1\::p' >> "$depfile"
rm -f "$tmpdepfile"
;;
msvcmsys)
# This case exists only to let depend.m4 do its work. It works by
# looking at the text of this script. This case will never be run,
# since it is checked for above.
exit 1
;;
none)
exec "$@"
;;
*)
echo "Unknown depmode $depmode" 1>&2
exit 1
;;
esac
exit 0
# Local Variables:
# mode: shell-script
# sh-indentation: 2
# eval: (add-hook 'write-file-hooks 'time-stamp)
# time-stamp-start: "scriptversion="
# time-stamp-format: "%:y-%02m-%02d.%02H"
# time-stamp-time-zone: "UTC"
# time-stamp-end: "; # UTC"
# End:

View File

@ -0,0 +1,520 @@
#!/bin/sh
# install - install a program, script, or datafile
scriptversion=2009-04-28.21; # UTC
# This originates from X11R5 (mit/util/scripts/install.sh), which was
# later released in X11R6 (xc/config/util/install.sh) with the
# following copyright and license.
#
# Copyright (C) 1994 X Consortium
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# 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
# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC-
# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# Except as contained in this notice, the name of the X Consortium shall not
# be used in advertising or otherwise to promote the sale, use or other deal-
# ings in this Software without prior written authorization from the X Consor-
# tium.
#
#
# FSF changes to this file are in the public domain.
#
# Calling this script install-sh is preferred over install.sh, to prevent
# `make' implicit rules from creating a file called install from it
# when there is no Makefile.
#
# This script is compatible with the BSD install script, but was written
# from scratch.
nl='
'
IFS=" "" $nl"
# set DOITPROG to echo to test this script
# Don't use :- since 4.3BSD and earlier shells don't like it.
doit=${DOITPROG-}
if test -z "$doit"; then
doit_exec=exec
else
doit_exec=$doit
fi
# Put in absolute file names if you don't have them in your path;
# or use environment vars.
chgrpprog=${CHGRPPROG-chgrp}
chmodprog=${CHMODPROG-chmod}
chownprog=${CHOWNPROG-chown}
cmpprog=${CMPPROG-cmp}
cpprog=${CPPROG-cp}
mkdirprog=${MKDIRPROG-mkdir}
mvprog=${MVPROG-mv}
rmprog=${RMPROG-rm}
stripprog=${STRIPPROG-strip}
posix_glob='?'
initialize_posix_glob='
test "$posix_glob" != "?" || {
if (set -f) 2>/dev/null; then
posix_glob=
else
posix_glob=:
fi
}
'
posix_mkdir=
# Desired mode of installed file.
mode=0755
chgrpcmd=
chmodcmd=$chmodprog
chowncmd=
mvcmd=$mvprog
rmcmd="$rmprog -f"
stripcmd=
src=
dst=
dir_arg=
dst_arg=
copy_on_change=false
no_target_directory=
usage="\
Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE
or: $0 [OPTION]... SRCFILES... DIRECTORY
or: $0 [OPTION]... -t DIRECTORY SRCFILES...
or: $0 [OPTION]... -d DIRECTORIES...
In the 1st form, copy SRCFILE to DSTFILE.
In the 2nd and 3rd, copy all SRCFILES to DIRECTORY.
In the 4th, create DIRECTORIES.
Options:
--help display this help and exit.
--version display version info and exit.
-c (ignored)
-C install only if different (preserve the last data modification time)
-d create directories instead of installing files.
-g GROUP $chgrpprog installed files to GROUP.
-m MODE $chmodprog installed files to MODE.
-o USER $chownprog installed files to USER.
-s $stripprog installed files.
-t DIRECTORY install into DIRECTORY.
-T report an error if DSTFILE is a directory.
Environment variables override the default commands:
CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG
RMPROG STRIPPROG
"
while test $# -ne 0; do
case $1 in
-c) ;;
-C) copy_on_change=true;;
-d) dir_arg=true;;
-g) chgrpcmd="$chgrpprog $2"
shift;;
--help) echo "$usage"; exit $?;;
-m) mode=$2
case $mode in
*' '* | *' '* | *'
'* | *'*'* | *'?'* | *'['*)
echo "$0: invalid mode: $mode" >&2
exit 1;;
esac
shift;;
-o) chowncmd="$chownprog $2"
shift;;
-s) stripcmd=$stripprog;;
-t) dst_arg=$2
shift;;
-T) no_target_directory=true;;
--version) echo "$0 $scriptversion"; exit $?;;
--) shift
break;;
-*) echo "$0: invalid option: $1" >&2
exit 1;;
*) break;;
esac
shift
done
if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then
# When -d is used, all remaining arguments are directories to create.
# When -t is used, the destination is already specified.
# Otherwise, the last argument is the destination. Remove it from $@.
for arg
do
if test -n "$dst_arg"; then
# $@ is not empty: it contains at least $arg.
set fnord "$@" "$dst_arg"
shift # fnord
fi
shift # arg
dst_arg=$arg
done
fi
if test $# -eq 0; then
if test -z "$dir_arg"; then
echo "$0: no input file specified." >&2
exit 1
fi
# It's OK to call `install-sh -d' without argument.
# This can happen when creating conditional directories.
exit 0
fi
if test -z "$dir_arg"; then
trap '(exit $?); exit' 1 2 13 15
# Set umask so as not to create temps with too-generous modes.
# However, 'strip' requires both read and write access to temps.
case $mode in
# Optimize common cases.
*644) cp_umask=133;;
*755) cp_umask=22;;
*[0-7])
if test -z "$stripcmd"; then
u_plus_rw=
else
u_plus_rw='% 200'
fi
cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;;
*)
if test -z "$stripcmd"; then
u_plus_rw=
else
u_plus_rw=,u+rw
fi
cp_umask=$mode$u_plus_rw;;
esac
fi
for src
do
# Protect names starting with `-'.
case $src in
-*) src=./$src;;
esac
if test -n "$dir_arg"; then
dst=$src
dstdir=$dst
test -d "$dstdir"
dstdir_status=$?
else
# Waiting for this to be detected by the "$cpprog $src $dsttmp" command
# might cause directories to be created, which would be especially bad
# if $src (and thus $dsttmp) contains '*'.
if test ! -f "$src" && test ! -d "$src"; then
echo "$0: $src does not exist." >&2
exit 1
fi
if test -z "$dst_arg"; then
echo "$0: no destination specified." >&2
exit 1
fi
dst=$dst_arg
# Protect names starting with `-'.
case $dst in
-*) dst=./$dst;;
esac
# If destination is a directory, append the input filename; won't work
# if double slashes aren't ignored.
if test -d "$dst"; then
if test -n "$no_target_directory"; then
echo "$0: $dst_arg: Is a directory" >&2
exit 1
fi
dstdir=$dst
dst=$dstdir/`basename "$src"`
dstdir_status=0
else
# Prefer dirname, but fall back on a substitute if dirname fails.
dstdir=`
(dirname "$dst") 2>/dev/null ||
expr X"$dst" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
X"$dst" : 'X\(//\)[^/]' \| \
X"$dst" : 'X\(//\)$' \| \
X"$dst" : 'X\(/\)' \| . 2>/dev/null ||
echo X"$dst" |
sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
s//\1/
q
}
/^X\(\/\/\)[^/].*/{
s//\1/
q
}
/^X\(\/\/\)$/{
s//\1/
q
}
/^X\(\/\).*/{
s//\1/
q
}
s/.*/./; q'
`
test -d "$dstdir"
dstdir_status=$?
fi
fi
obsolete_mkdir_used=false
if test $dstdir_status != 0; then
case $posix_mkdir in
'')
# Create intermediate dirs using mode 755 as modified by the umask.
# This is like FreeBSD 'install' as of 1997-10-28.
umask=`umask`
case $stripcmd.$umask in
# Optimize common cases.
*[2367][2367]) mkdir_umask=$umask;;
.*0[02][02] | .[02][02] | .[02]) mkdir_umask=22;;
*[0-7])
mkdir_umask=`expr $umask + 22 \
- $umask % 100 % 40 + $umask % 20 \
- $umask % 10 % 4 + $umask % 2
`;;
*) mkdir_umask=$umask,go-w;;
esac
# With -d, create the new directory with the user-specified mode.
# Otherwise, rely on $mkdir_umask.
if test -n "$dir_arg"; then
mkdir_mode=-m$mode
else
mkdir_mode=
fi
posix_mkdir=false
case $umask in
*[123567][0-7][0-7])
# POSIX mkdir -p sets u+wx bits regardless of umask, which
# is incompatible with FreeBSD 'install' when (umask & 300) != 0.
;;
*)
tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$
trap 'ret=$?; rmdir "$tmpdir/d" "$tmpdir" 2>/dev/null; exit $ret' 0
if (umask $mkdir_umask &&
exec $mkdirprog $mkdir_mode -p -- "$tmpdir/d") >/dev/null 2>&1
then
if test -z "$dir_arg" || {
# Check for POSIX incompatibilities with -m.
# HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or
# other-writeable bit of parent directory when it shouldn't.
# FreeBSD 6.1 mkdir -m -p sets mode of existing directory.
ls_ld_tmpdir=`ls -ld "$tmpdir"`
case $ls_ld_tmpdir in
d????-?r-*) different_mode=700;;
d????-?--*) different_mode=755;;
*) false;;
esac &&
$mkdirprog -m$different_mode -p -- "$tmpdir" && {
ls_ld_tmpdir_1=`ls -ld "$tmpdir"`
test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1"
}
}
then posix_mkdir=:
fi
rmdir "$tmpdir/d" "$tmpdir"
else
# Remove any dirs left behind by ancient mkdir implementations.
rmdir ./$mkdir_mode ./-p ./-- 2>/dev/null
fi
trap '' 0;;
esac;;
esac
if
$posix_mkdir && (
umask $mkdir_umask &&
$doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir"
)
then :
else
# The umask is ridiculous, or mkdir does not conform to POSIX,
# or it failed possibly due to a race condition. Create the
# directory the slow way, step by step, checking for races as we go.
case $dstdir in
/*) prefix='/';;
-*) prefix='./';;
*) prefix='';;
esac
eval "$initialize_posix_glob"
oIFS=$IFS
IFS=/
$posix_glob set -f
set fnord $dstdir
shift
$posix_glob set +f
IFS=$oIFS
prefixes=
for d
do
test -z "$d" && continue
prefix=$prefix$d
if test -d "$prefix"; then
prefixes=
else
if $posix_mkdir; then
(umask=$mkdir_umask &&
$doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break
# Don't fail if two instances are running concurrently.
test -d "$prefix" || exit 1
else
case $prefix in
*\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;;
*) qprefix=$prefix;;
esac
prefixes="$prefixes '$qprefix'"
fi
fi
prefix=$prefix/
done
if test -n "$prefixes"; then
# Don't fail if two instances are running concurrently.
(umask $mkdir_umask &&
eval "\$doit_exec \$mkdirprog $prefixes") ||
test -d "$dstdir" || exit 1
obsolete_mkdir_used=true
fi
fi
fi
if test -n "$dir_arg"; then
{ test -z "$chowncmd" || $doit $chowncmd "$dst"; } &&
{ test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } &&
{ test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false ||
test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1
else
# Make a couple of temp file names in the proper directory.
dsttmp=$dstdir/_inst.$$_
rmtmp=$dstdir/_rm.$$_
# Trap to clean up those temp files at exit.
trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0
# Copy the file name to the temp name.
(umask $cp_umask && $doit_exec $cpprog "$src" "$dsttmp") &&
# and set any options; do chmod last to preserve setuid bits.
#
# If any of these fail, we abort the whole thing. If we want to
# ignore errors from any of these, just make sure not to ignore
# errors from the above "$doit $cpprog $src $dsttmp" command.
#
{ test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } &&
{ test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } &&
{ test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } &&
{ test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } &&
# If -C, don't bother to copy if it wouldn't change the file.
if $copy_on_change &&
old=`LC_ALL=C ls -dlL "$dst" 2>/dev/null` &&
new=`LC_ALL=C ls -dlL "$dsttmp" 2>/dev/null` &&
eval "$initialize_posix_glob" &&
$posix_glob set -f &&
set X $old && old=:$2:$4:$5:$6 &&
set X $new && new=:$2:$4:$5:$6 &&
$posix_glob set +f &&
test "$old" = "$new" &&
$cmpprog "$dst" "$dsttmp" >/dev/null 2>&1
then
rm -f "$dsttmp"
else
# Rename the file to the real destination.
$doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null ||
# The rename failed, perhaps because mv can't rename something else
# to itself, or perhaps because mv is so ancient that it does not
# support -f.
{
# Now remove or move aside any old file at destination location.
# We try this two ways since rm can't unlink itself on some
# systems and the destination file might be busy for other
# reasons. In this case, the final cleanup might fail but the new
# file should still install successfully.
{
test ! -f "$dst" ||
$doit $rmcmd -f "$dst" 2>/dev/null ||
{ $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null &&
{ $doit $rmcmd -f "$rmtmp" 2>/dev/null; :; }
} ||
{ echo "$0: cannot unlink or rename $dst" >&2
(exit 1); exit 1
}
} &&
# Now rename the file to the real destination.
$doit $mvcmd "$dsttmp" "$dst"
}
fi || exit 1
trap '' 0
fi
done
# Local variables:
# eval: (add-hook 'write-file-hooks 'time-stamp)
# time-stamp-start: "scriptversion="
# time-stamp-format: "%:y-%02m-%02d.%02H"
# time-stamp-time-zone: "UTC"
# time-stamp-end: "; # UTC"
# End:

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,376 @@
#! /bin/sh
# Common stub for a few missing GNU programs while installing.
scriptversion=2009-04-28.21; # UTC
# Copyright (C) 1996, 1997, 1999, 2000, 2002, 2003, 2004, 2005, 2006,
# 2008, 2009 Free Software Foundation, Inc.
# Originally by Fran,cois Pinard <pinard@iro.umontreal.ca>, 1996.
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# As a special exception to the GNU General Public License, if you
# distribute this file as part of a program that contains a
# configuration script generated by Autoconf, you may include it under
# the same distribution terms that you use for the rest of that program.
if test $# -eq 0; then
echo 1>&2 "Try \`$0 --help' for more information"
exit 1
fi
run=:
sed_output='s/.* --output[ =]\([^ ]*\).*/\1/p'
sed_minuso='s/.* -o \([^ ]*\).*/\1/p'
# In the cases where this matters, `missing' is being run in the
# srcdir already.
if test -f configure.ac; then
configure_ac=configure.ac
else
configure_ac=configure.in
fi
msg="missing on your system"
case $1 in
--run)
# Try to run requested program, and just exit if it succeeds.
run=
shift
"$@" && exit 0
# Exit code 63 means version mismatch. This often happens
# when the user try to use an ancient version of a tool on
# a file that requires a minimum version. In this case we
# we should proceed has if the program had been absent, or
# if --run hadn't been passed.
if test $? = 63; then
run=:
msg="probably too old"
fi
;;
-h|--h|--he|--hel|--help)
echo "\
$0 [OPTION]... PROGRAM [ARGUMENT]...
Handle \`PROGRAM [ARGUMENT]...' for when PROGRAM is missing, or return an
error status if there is no known handling for PROGRAM.
Options:
-h, --help display this help and exit
-v, --version output version information and exit
--run try to run the given command, and emulate it if it fails
Supported PROGRAM values:
aclocal touch file \`aclocal.m4'
autoconf touch file \`configure'
autoheader touch file \`config.h.in'
autom4te touch the output file, or create a stub one
automake touch all \`Makefile.in' files
bison create \`y.tab.[ch]', if possible, from existing .[ch]
flex create \`lex.yy.c', if possible, from existing .c
help2man touch the output file
lex create \`lex.yy.c', if possible, from existing .c
makeinfo touch the output file
tar try tar, gnutar, gtar, then tar without non-portable flags
yacc create \`y.tab.[ch]', if possible, from existing .[ch]
Version suffixes to PROGRAM as well as the prefixes \`gnu-', \`gnu', and
\`g' are ignored when checking the name.
Send bug reports to <bug-automake@gnu.org>."
exit $?
;;
-v|--v|--ve|--ver|--vers|--versi|--versio|--version)
echo "missing $scriptversion (GNU Automake)"
exit $?
;;
-*)
echo 1>&2 "$0: Unknown \`$1' option"
echo 1>&2 "Try \`$0 --help' for more information"
exit 1
;;
esac
# normalize program name to check for.
program=`echo "$1" | sed '
s/^gnu-//; t
s/^gnu//; t
s/^g//; t'`
# Now exit if we have it, but it failed. Also exit now if we
# don't have it and --version was passed (most likely to detect
# the program). This is about non-GNU programs, so use $1 not
# $program.
case $1 in
lex*|yacc*)
# Not GNU programs, they don't have --version.
;;
tar*)
if test -n "$run"; then
echo 1>&2 "ERROR: \`tar' requires --run"
exit 1
elif test "x$2" = "x--version" || test "x$2" = "x--help"; then
exit 1
fi
;;
*)
if test -z "$run" && ($1 --version) > /dev/null 2>&1; then
# We have it, but it failed.
exit 1
elif test "x$2" = "x--version" || test "x$2" = "x--help"; then
# Could not run --version or --help. This is probably someone
# running `$TOOL --version' or `$TOOL --help' to check whether
# $TOOL exists and not knowing $TOOL uses missing.
exit 1
fi
;;
esac
# If it does not exist, or fails to run (possibly an outdated version),
# try to emulate it.
case $program in
aclocal*)
echo 1>&2 "\
WARNING: \`$1' is $msg. You should only need it if
you modified \`acinclude.m4' or \`${configure_ac}'. You might want
to install the \`Automake' and \`Perl' packages. Grab them from
any GNU archive site."
touch aclocal.m4
;;
autoconf*)
echo 1>&2 "\
WARNING: \`$1' is $msg. You should only need it if
you modified \`${configure_ac}'. You might want to install the
\`Autoconf' and \`GNU m4' packages. Grab them from any GNU
archive site."
touch configure
;;
autoheader*)
echo 1>&2 "\
WARNING: \`$1' is $msg. You should only need it if
you modified \`acconfig.h' or \`${configure_ac}'. You might want
to install the \`Autoconf' and \`GNU m4' packages. Grab them
from any GNU archive site."
files=`sed -n 's/^[ ]*A[CM]_CONFIG_HEADER(\([^)]*\)).*/\1/p' ${configure_ac}`
test -z "$files" && files="config.h"
touch_files=
for f in $files; do
case $f in
*:*) touch_files="$touch_files "`echo "$f" |
sed -e 's/^[^:]*://' -e 's/:.*//'`;;
*) touch_files="$touch_files $f.in";;
esac
done
touch $touch_files
;;
automake*)
echo 1>&2 "\
WARNING: \`$1' is $msg. You should only need it if
you modified \`Makefile.am', \`acinclude.m4' or \`${configure_ac}'.
You might want to install the \`Automake' and \`Perl' packages.
Grab them from any GNU archive site."
find . -type f -name Makefile.am -print |
sed 's/\.am$/.in/' |
while read f; do touch "$f"; done
;;
autom4te*)
echo 1>&2 "\
WARNING: \`$1' is needed, but is $msg.
You might have modified some files without having the
proper tools for further handling them.
You can get \`$1' as part of \`Autoconf' from any GNU
archive site."
file=`echo "$*" | sed -n "$sed_output"`
test -z "$file" && file=`echo "$*" | sed -n "$sed_minuso"`
if test -f "$file"; then
touch $file
else
test -z "$file" || exec >$file
echo "#! /bin/sh"
echo "# Created by GNU Automake missing as a replacement of"
echo "# $ $@"
echo "exit 0"
chmod +x $file
exit 1
fi
;;
bison*|yacc*)
echo 1>&2 "\
WARNING: \`$1' $msg. You should only need it if
you modified a \`.y' file. You may need the \`Bison' package
in order for those modifications to take effect. You can get
\`Bison' from any GNU archive site."
rm -f y.tab.c y.tab.h
if test $# -ne 1; then
eval LASTARG="\${$#}"
case $LASTARG in
*.y)
SRCFILE=`echo "$LASTARG" | sed 's/y$/c/'`
if test -f "$SRCFILE"; then
cp "$SRCFILE" y.tab.c
fi
SRCFILE=`echo "$LASTARG" | sed 's/y$/h/'`
if test -f "$SRCFILE"; then
cp "$SRCFILE" y.tab.h
fi
;;
esac
fi
if test ! -f y.tab.h; then
echo >y.tab.h
fi
if test ! -f y.tab.c; then
echo 'main() { return 0; }' >y.tab.c
fi
;;
lex*|flex*)
echo 1>&2 "\
WARNING: \`$1' is $msg. You should only need it if
you modified a \`.l' file. You may need the \`Flex' package
in order for those modifications to take effect. You can get
\`Flex' from any GNU archive site."
rm -f lex.yy.c
if test $# -ne 1; then
eval LASTARG="\${$#}"
case $LASTARG in
*.l)
SRCFILE=`echo "$LASTARG" | sed 's/l$/c/'`
if test -f "$SRCFILE"; then
cp "$SRCFILE" lex.yy.c
fi
;;
esac
fi
if test ! -f lex.yy.c; then
echo 'main() { return 0; }' >lex.yy.c
fi
;;
help2man*)
echo 1>&2 "\
WARNING: \`$1' is $msg. You should only need it if
you modified a dependency of a manual page. You may need the
\`Help2man' package in order for those modifications to take
effect. You can get \`Help2man' from any GNU archive site."
file=`echo "$*" | sed -n "$sed_output"`
test -z "$file" && file=`echo "$*" | sed -n "$sed_minuso"`
if test -f "$file"; then
touch $file
else
test -z "$file" || exec >$file
echo ".ab help2man is required to generate this page"
exit $?
fi
;;
makeinfo*)
echo 1>&2 "\
WARNING: \`$1' is $msg. You should only need it if
you modified a \`.texi' or \`.texinfo' file, or any other file
indirectly affecting the aspect of the manual. The spurious
call might also be the consequence of using a buggy \`make' (AIX,
DU, IRIX). You might want to install the \`Texinfo' package or
the \`GNU make' package. Grab either from any GNU archive site."
# The file to touch is that specified with -o ...
file=`echo "$*" | sed -n "$sed_output"`
test -z "$file" && file=`echo "$*" | sed -n "$sed_minuso"`
if test -z "$file"; then
# ... or it is the one specified with @setfilename ...
infile=`echo "$*" | sed 's/.* \([^ ]*\) *$/\1/'`
file=`sed -n '
/^@setfilename/{
s/.* \([^ ]*\) *$/\1/
p
q
}' $infile`
# ... or it is derived from the source name (dir/f.texi becomes f.info)
test -z "$file" && file=`echo "$infile" | sed 's,.*/,,;s,.[^.]*$,,'`.info
fi
# If the file does not exist, the user really needs makeinfo;
# let's fail without touching anything.
test -f $file || exit 1
touch $file
;;
tar*)
shift
# We have already tried tar in the generic part.
# Look for gnutar/gtar before invocation to avoid ugly error
# messages.
if (gnutar --version > /dev/null 2>&1); then
gnutar "$@" && exit 0
fi
if (gtar --version > /dev/null 2>&1); then
gtar "$@" && exit 0
fi
firstarg="$1"
if shift; then
case $firstarg in
*o*)
firstarg=`echo "$firstarg" | sed s/o//`
tar "$firstarg" "$@" && exit 0
;;
esac
case $firstarg in
*h*)
firstarg=`echo "$firstarg" | sed s/h//`
tar "$firstarg" "$@" && exit 0
;;
esac
fi
echo 1>&2 "\
WARNING: I can't seem to be able to run \`tar' with the given arguments.
You may want to install GNU tar or Free paxutils, or check the
command line arguments."
exit 1
;;
*)
echo 1>&2 "\
WARNING: \`$1' is needed, and is $msg.
You might have modified some files without having the
proper tools for further handling them. Check the \`README' file,
it often tells you about the needed prerequisites for installing
this package. You may also peek at any GNU archive site, in case
some other package would contain this missing \`$1' program."
exit 1
;;
esac
exit 0
# Local variables:
# eval: (add-hook 'write-file-hooks 'time-stamp)
# time-stamp-start: "scriptversion="
# time-stamp-format: "%:y-%02m-%02d.%02H"
# time-stamp-time-zone: "UTC"
# time-stamp-end: "; # UTC"
# End:

13977
libdivsufsort-2.0.1/configure vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,198 @@
## configure.ac for libdivsufsort
AC_PREREQ(2.61)
AC_INIT([libdivsufsort], [2.0.1], [yuta.256@gmail.com])
AC_CONFIG_SRCDIR([include/divsufsort.h.cmake])
AC_CONFIG_HEADER([include/config.h])
AC_CONFIG_AUX_DIR([config])
AC_CONFIG_MACRO_DIR([m4])
AC_CANONICAL_TARGET
AM_INIT_AUTOMAKE([-Wall -Werror foreign 1.10.1 no-define dist-bzip2])
AM_MAINTAINER_MODE
# LT_CURRENT = PROJECT_VERSION_MAJOR + PROJECT_VERSION_MINOR + 1
# LT_AGE = PROJECT_VERSION_MINOR
# LT_REVISION = PROJECT_VERSION_PATCH
AC_SUBST(LT_CURRENT, 3)
AC_SUBST(LT_AGE, 0)
AC_SUBST(LT_REVISION, 1)
AC_SUBST([PROJECT_NAME], [libdivsufsort])
AC_SUBST([PROJECT_DESCRIPTION], "A lightweight suffix sorting library")
AC_SUBST([PROJECT_VERSION_FULL], [2.0.0])
AC_SUBST([PROJECT_URL], "http://libdivsufsort.googlecode.com/")
## Checks for programs.
AC_PROG_CC
AC_PROG_INSTALL
AC_PROG_MAKE_SET
## Checks for compiler output filename suffixes.
AC_OBJEXT
AC_EXEEXT
## Check for build configuration.
#AM_DISABLE_STATIC
AM_DISABLE_SHARED
#AC_LIBTOOL_WIN32_DLL
AC_PROG_LIBTOOL
AC_SUBST([LIBTOOL_DEPS])
case "$target_os" in
cygwin* | mingw*)
LDFLAGS="$LDFLAGS -no-undefined"
;;
esac
## Checks for libraries.
## Checks for header files.
AC_HEADER_STDC
AC_CHECK_HEADERS([inttypes.h memory.h stddef.h stdint.h stdlib.h string.h strings.h sys/types.h io.h fcntl.h])
AS_IF([test "$ac_cv_header_inttypes_h" == "yes"],
[AC_SUBST([INCFILE], ["#include <inttypes.h>"])],
[test "$ac_cv_header_stdint_h" == "yes"],
[AC_SUBST([INCFILE], ["#include <stdint.h>"])],
[AC_SUBST([INCFILE], [""])])
## Checks for typedefs, structures, and compiler characteristics.
# sauchar_t
SAUCHAR_TYPE=""
AC_CHECK_TYPES([uint8_t])
if test "$ac_cv_type_uint8_t" = "yes"; then
SAUCHAR_TYPE="uint8_t"
fi
if test -z "$SAUCHAR_TYPE";then
AC_CHECK_SIZEOF([unsigned char], 1)
if test "$ac_cv_sizeof_unsigned_char" = "1";then SAUCHAR_TYPE="unsigned char"; fi
fi
if test -z "$SAUCHAR_TYPE";then
AC_MSG_ERROR([Cannot find unsigned 8-bit integer type])
fi
AC_SUBST([SAUCHAR_TYPE])
# saint_t and saidx_t
SAINT32_TYPE=""
AC_CHECK_TYPES([int32_t])
if test "$ac_cv_type_int32_t" = "yes"; then
SAINT32_TYPE="int32_t";
SAINT32_PRId="PRId32";
fi
if test -z "$SAINT32_TYPE";then
AC_CHECK_SIZEOF([int], 4)
if test "$ac_cv_sizeof_int" = "4";then
SAINT32_TYPE="int";
SAINT32_PRId="\"d\"";
fi
fi
if test -z "$SAINT32_TYPE";then
AC_CHECK_SIZEOF([long], 4)
if test "$ac_cv_sizeof_long" = "4"; then
SAINT32_TYPE="long";
SAINT32_PRId="\"ld\"";
fi
fi
if test -z "$SAINT32_TYPE";then
AC_CHECK_SIZEOF([__int32], 4)
if test "$ac_cv_sizeof___int32" = "4"; then
SAINT32_TYPE="__int32";
SAINT32_PRId="\"I32d\"";
fi
fi
if test -z "$SAINT32_TYPE";then
AC_CHECK_SIZEOF([short], 4)
if test "$ac_cv_sizeof_short" = "4"; then
SAINT32_TYPE="short";
SAINT32_PRId="\"d\"";
fi
fi
if test -z "$SAINT32_TYPE";then
AC_MSG_ERROR([Could not find 32-bit integer type])
fi
AC_SUBST([SAINT32_TYPE])
AC_SUBST([SAINT_PRId], "$SAINT32_PRId")
AC_ARG_ENABLE(divsufsort64, AC_HELP_STRING([--enable-divsufsort64], [build libdivsufsort64]))
if test "$enable_divsufsort64" = "yes"; then
# saint64_t
SAINT64_TYPE=""
AC_CHECK_TYPES([int64_t])
if test "$ac_cv_type_int64_t" = "yes"; then
SAINT64_TYPE="int64_t";
SAINT64_PRId="PRId64";
fi
if test -z "$SAINT64_TYPE";then
AC_CHECK_SIZEOF([long long], 8)
if test "$ac_cv_sizeof_long_long" = "8";then
SAINT64_TYPE="long long";
SAINT64_PRId="\"lld\"";
fi
fi
if test -z "$SAINT64_TYPE";then
AC_CHECK_SIZEOF([long], 8)
if test "$ac_cv_sizeof_long" = "8";then
SAINT64_TYPE="long";
SAINT64_PRId="\"ld\"";
fi
fi
if test -z "$SAINT64_TYPE";then
AC_CHECK_SIZEOF([int], 8)
if test "$ac_cv_sizeof_int" = "8";then
SAINT64_TYPE="int";
SAINT64_PRId="\"d\"";
fi
fi
if test -z "$SAINT64_TYPE";then
AC_CHECK_SIZEOF([__int64], 8)
if test "$ac_cv_sizeof___int64" = "8";then
SAINT64_TYPE="__int32";
SAINT64_PRId="\"I64d\"";
fi
fi
if test -z "$SAINT64_TYPE";then
AC_MSG_ERROR([Could not find 64-bit integer type])
fi
AC_CONFIG_FILES([include/divsufsort64.h:include/divsufsort64.h.in])
AC_SUBST([SAINT64_TYPE])
AC_SUBST([SAINT64_PRId])
fi
AM_CONDITIONAL([DIVSUFSORT64], test "$enable_divsufsort64" = "yes")
AC_SUBST([SAINDEX_TYPE], "$SAINT32_TYPE")
AC_SUBST([SAINDEX_PRId], "$SAINT32_PRId")
AC_SUBST([W64BIT], [])
AC_SUBST([DIVSUFSORT_EXPORT], [])
AC_SUBST([DIVSUFSORT_IMPORT], [])
AC_SUBST([LFS_OFF_T], [long])
AC_SUBST([LFS_FOPEN], [fopen])
AC_SUBST([LFS_FTELL], [ftell])
AC_SUBST([LFS_FSEEK], [fseek])
AC_SUBST([LFS_PRID], ["\"ld\""])
AC_C_CONST
AC_C_INLINE
AC_DEFINE(INLINE, [inline], [for inline])
AC_DEFINE(PROJECT_VERSION_FULL, [PACKAGE_VERSION], [Define to the version of this package.])
## Checks for library functions.
AC_FUNC_MALLOC
AC_CHECK_FUNCS([fopen_s _setmode setmode _fileno])
if test "$ac_cv_func_setmode" = "yes"; then
if test "$ac_cv_func__setmode" = "no"; then
AC_DEFINE(_setmode, [setmode], [for _setmode])
AC_DEFINE(HAVE__SETMODE, 1, [for _setmode])
fi
fi
AC_CONFIG_FILES([Makefile
include/Makefile
include/divsufsort.h:include/divsufsort.h.cmake
include/lfs.h:include/lfs.h.cmake
lib/Makefile
examples/Makefile])
AC_OUTPUT

View File

@ -0,0 +1,11 @@
## Add definitions ##
add_definitions(-D_LARGEFILE_SOURCE -D_LARGE_FILES -D_FILE_OFFSET_BITS=64)
## Targets ##
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../include"
"${CMAKE_CURRENT_BINARY_DIR}/../include")
link_directories("${CMAKE_CURRENT_BINARY_DIR}/../lib")
foreach(src suftest mksary sasearch bwt unbwt)
add_executable(${src} ${src}.c)
target_link_libraries(${src} divsufsort)
endforeach(src)

Some files were not shown because too many files have changed in this diff Show More