mirror of
https://github.com/Alcaro/Flips.git
synced 2026-04-16 23:05:55 -05:00
Initial commit
This commit is contained in:
commit
9335f0b024
6
Makefile
Normal file
6
Makefile
Normal 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
226
arlib/Makefile
Normal 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
27
arlib/arlib.h
Normal 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
270
arlib/array.h
Normal 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
9
arlib/containers.h
Normal 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
21
arlib/crc32.cpp
Normal 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
4
arlib/crc32.h
Normal 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
99
arlib/dylib.cpp
Normal 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
178
arlib/endian.h
Normal 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
302
arlib/file-posix.cpp
Normal 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
243
arlib/file-win32.cpp
Normal 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
162
arlib/file.h
Normal 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
291
arlib/function.h
Normal 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
320
arlib/global.h
Normal 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
687
arlib/gui/gtk3-inner.cpp
Normal 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
503
arlib/gui/gtk3-listbox.cpp
Normal 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
358
arlib/gui/gtk3-misc.cpp
Normal 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
667
arlib/gui/gtk3-shell.cpp
Normal 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
20
arlib/gui/none.cpp
Normal 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
496
arlib/gui/shared.cpp
Normal 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
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
299
arlib/gui/win32-misc.cpp
Normal 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
982
arlib/gui/win32-shell.cpp
Normal 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
758
arlib/gui/window.h
Normal 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
100
arlib/intarray.h
Normal 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
44
arlib/intwrap.h
Normal 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
62
arlib/malloc.cpp
Normal 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
38
arlib/os.h
Normal 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
1
arlib/sandbox.h
Normal file
|
|
@ -0,0 +1 @@
|
|||
#include "sandbox/sandbox.h"
|
||||
0
arlib/sandbox/linux-bpf-gen.cpp
Normal file
0
arlib/sandbox/linux-bpf-gen.cpp
Normal file
0
arlib/sandbox/linux-bpf.cpp
Normal file
0
arlib/sandbox/linux-bpf.cpp
Normal file
156
arlib/sandbox/linux-file-pass.cpp
Normal file
156
arlib/sandbox/linux-file-pass.cpp
Normal 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
|
||||
90
arlib/sandbox/linux-lockdown.cpp
Normal file
90
arlib/sandbox/linux-lockdown.cpp
Normal 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
281
arlib/sandbox/linux.cpp
Normal 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
|
||||
10
arlib/sandbox/sandbox-internal.h
Normal file
10
arlib/sandbox/sandbox-internal.h
Normal 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
145
arlib/sandbox/sandbox.h
Normal 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
196
arlib/sandbox/win32.cpp
Normal 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
1
arlib/socket.h
Normal file
|
|
@ -0,0 +1 @@
|
|||
#include "socket/socket.h"
|
||||
34375
arlib/socket/libtomcrypt.c
Normal file
34375
arlib/socket/libtomcrypt.c
Normal file
File diff suppressed because it is too large
Load Diff
64
arlib/socket/shitty-server.cpp
Normal file
64
arlib/socket/shitty-server.cpp
Normal 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
|
||||
342
arlib/socket/socket-openssl.cpp
Normal file
342
arlib/socket/socket-openssl.cpp
Normal 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
|
||||
339
arlib/socket/socket-schannel.cpp
Normal file
339
arlib/socket/socket-schannel.cpp
Normal 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
|
||||
5
arlib/socket/socket-test.cpp
Normal file
5
arlib/socket/socket-test.cpp
Normal 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
|
||||
260
arlib/socket/socket-tlse.cpp
Normal file
260
arlib/socket/socket-tlse.cpp
Normal 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
|
||||
241
arlib/socket/socket-wolfssl.cpp
Normal file
241
arlib/socket/socket-wolfssl.cpp
Normal 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
160
arlib/socket/socket.cpp
Normal 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
99
arlib/socket/socket.h
Normal 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
109
arlib/socket/test.c
Normal 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
7974
arlib/socket/tlse.c
Normal file
File diff suppressed because it is too large
Load Diff
276
arlib/socket/tlse.h
Normal file
276
arlib/socket/tlse.h
Normal 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
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
141
arlib/socket/wolfssl-lib.c
Normal 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
349
arlib/string.h
Normal 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
2
arlib/test.cpp
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
//TODO
|
||||
//should use
|
||||
1
arlib/thread.h
Normal file
1
arlib/thread.h
Normal file
|
|
@ -0,0 +1 @@
|
|||
#include "thread/thread.h"
|
||||
128
arlib/thread/atomic.h
Normal file
128
arlib/thread/atomic.h
Normal 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
250
arlib/thread/linux.cpp
Normal 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
63
arlib/thread/once.cpp
Normal 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
195
arlib/thread/pthread.cpp
Normal 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
93
arlib/thread/split.cpp
Normal 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
201
arlib/thread/thread.h
Normal 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
102
arlib/thread/win32.cpp
Normal 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
1
arlib/wutf.h
Normal file
|
|
@ -0,0 +1 @@
|
|||
#include "wutf/wutf.h"
|
||||
545
arlib/wutf/wutf-conv.cpp
Normal file
545
arlib/wutf/wutf-conv.cpp
Normal 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
475
arlib/wutf/wutf-test.cpp
Normal 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
247
arlib/wutf/wutf.cpp
Normal 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
152
arlib/wutf/wutf.h
Normal 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
1782
divsufsort.c
Normal file
File diff suppressed because it is too large
Load Diff
63
divsufsort.h
Normal file
63
divsufsort.h
Normal 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 */
|
||||
21
global.h
Normal file
21
global.h
Normal 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
878
libbps-suf.cpp
Normal 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
614
libbps.cpp
Normal 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
81
libbps.h
Normal 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
|
||||
3
libdivsufsort-2.0.1/AUTHORS
Normal file
3
libdivsufsort-2.0.1/AUTHORS
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
-- AUTHORS for libdivsufsort
|
||||
|
||||
Yuta Mori <yuta.256@gmail.com>
|
||||
101
libdivsufsort-2.0.1/CMakeLists.txt
Normal file
101
libdivsufsort-2.0.1/CMakeLists.txt
Normal 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")
|
||||
38
libdivsufsort-2.0.1/CMakeModules/AppendCompilerFlags.cmake
Normal file
38
libdivsufsort-2.0.1/CMakeModules/AppendCompilerFlags.cmake
Normal 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)
|
||||
15
libdivsufsort-2.0.1/CMakeModules/CheckFunctionKeywords.cmake
Normal file
15
libdivsufsort-2.0.1/CMakeModules/CheckFunctionKeywords.cmake
Normal 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)
|
||||
109
libdivsufsort-2.0.1/CMakeModules/CheckLFS.cmake
Normal file
109
libdivsufsort-2.0.1/CMakeModules/CheckLFS.cmake
Normal 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)
|
||||
38
libdivsufsort-2.0.1/CMakeModules/ProjectCPack.cmake
Normal file
38
libdivsufsort-2.0.1/CMakeModules/ProjectCPack.cmake
Normal 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")
|
||||
36
libdivsufsort-2.0.1/CMakeModules/cmake_uninstall.cmake.in
Normal file
36
libdivsufsort-2.0.1/CMakeModules/cmake_uninstall.cmake.in
Normal 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")
|
||||
27
libdivsufsort-2.0.1/COPYING
Normal file
27
libdivsufsort-2.0.1/COPYING
Normal 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.
|
||||
329
libdivsufsort-2.0.1/ChangeLog
Normal file
329
libdivsufsort-2.0.1/ChangeLog
Normal 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:
|
||||
67
libdivsufsort-2.0.1/ChangeLog.old
Normal file
67
libdivsufsort-2.0.1/ChangeLog.old
Normal 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.
|
||||
|
||||
31
libdivsufsort-2.0.1/INSTALL
Normal file
31
libdivsufsort-2.0.1/INSTALL
Normal 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
|
||||
10
libdivsufsort-2.0.1/Makefile.am
Normal file
10
libdivsufsort-2.0.1/Makefile.am
Normal 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
|
||||
736
libdivsufsort-2.0.1/Makefile.in
Normal file
736
libdivsufsort-2.0.1/Makefile.in
Normal 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
43
libdivsufsort-2.0.1/NEWS
Normal 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
250
libdivsufsort-2.0.1/README
Normal 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
|
||||
1
libdivsufsort-2.0.1/VERSION
Normal file
1
libdivsufsort-2.0.1/VERSION
Normal file
|
|
@ -0,0 +1 @@
|
|||
2.0.1
|
||||
996
libdivsufsort-2.0.1/aclocal.m4
vendored
Normal file
996
libdivsufsort-2.0.1/aclocal.m4
vendored
Normal 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
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
1714
libdivsufsort-2.0.1/config/config.sub
vendored
Normal file
File diff suppressed because it is too large
Load Diff
630
libdivsufsort-2.0.1/config/depcomp
Normal file
630
libdivsufsort-2.0.1/config/depcomp
Normal 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:
|
||||
520
libdivsufsort-2.0.1/config/install-sh
Normal file
520
libdivsufsort-2.0.1/config/install-sh
Normal 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:
|
||||
8413
libdivsufsort-2.0.1/config/ltmain.sh
Normal file
8413
libdivsufsort-2.0.1/config/ltmain.sh
Normal file
File diff suppressed because it is too large
Load Diff
376
libdivsufsort-2.0.1/config/missing
Normal file
376
libdivsufsort-2.0.1/config/missing
Normal 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
13977
libdivsufsort-2.0.1/configure
vendored
Normal file
File diff suppressed because it is too large
Load Diff
198
libdivsufsort-2.0.1/configure.ac
Normal file
198
libdivsufsort-2.0.1/configure.ac
Normal 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
|
||||
11
libdivsufsort-2.0.1/examples/CMakeLists.txt
Normal file
11
libdivsufsort-2.0.1/examples/CMakeLists.txt
Normal 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
Loading…
Reference in New Issue
Block a user