commit da7d9f2662c21c11a9d50f8425354d898d9b6e52 Author: Tim Allen Date: Tue Dec 28 12:53:15 2010 +1100 Initial commit of bgameboy v000. The source tarball also included empty obj/ and out/ directories which git does not support. byuu says: Project started, so basically everything is new. It's basically a rough skeleton that mimics bsnes project structure. Eventually the src/gameboy folder will be copied into bsnes-official and used by the chip/supergameboy core. The middleware layer (supergameboy/interface) will be merged into a new chip/icd2 folder that will represent direct Super Game Boy emulation in the future. At least, if all goes according to plan. There is a simple GUI that can load ROMs, but do nothing after it. It's not hooked up to ruby yet. There is a basic system class and interface to expose the video/audio/input functions. There is a basic memory bus that doesn't support any MBCs yet. There is a CPU skeleton that only handles easy read/write access to the CPU registers (AF is a really fucked up register.) The core is not hooked up to libco yet, but I intend for it to be, so that I can run the CPU + LCD how I like. If it turns out the LCD+audio is easily enslavable, then I'll probably drop libco and just run it like a regular emulator, using a thread wrapper around it in bsnes only. We'll see. The CPU doesn't actually support any opcodes, and loading a ROM won't actually execute anything. diff --git a/Makefile b/Makefile new file mode 100755 index 00000000..4788f548 --- /dev/null +++ b/Makefile @@ -0,0 +1,78 @@ +include nall/Makefile +gameboy := gameboy +ui := ui + +# compiler +c := $(compiler) -std=gnu99 +cpp := $(subst cc,++,$(compiler)) -std=gnu++0x +flags := -O3 -fomit-frame-pointer -I. -I$(gameboy) +link := +objects := + +# profile-guided instrumentation +# flags += -fprofile-generate +# link += -lgcov + +# profile-guided optimization +# flags += -fprofile-use + +# platform +ifeq ($(platform),x) + link += -s -ldl -lX11 -lXext +else ifeq ($(platform),osx) +else ifeq ($(platform),win) + link += -mwindows +# link += -mconsole + link += -mthreads -s -luuid -lkernel32 -luser32 -lgdi32 -lcomctl32 -lcomdlg32 -lshell32 -lole32 + link += -enable-stdcall-fixup -Wl,-enable-auto-import -Wl,-enable-runtime-pseudo-reloc +else + unknown_platform: help; +endif + +# implicit rules +compile = \ + $(strip \ + $(if $(filter %.c,$<), \ + $(c) $(flags) $1 -c $< -o $@, \ + $(if $(filter %.cpp,$<), \ + $(cpp) $(flags) $1 -c $< -o $@ \ + ) \ + ) \ + ) + +%.o: $<; $(call compile) + +all: build; + +include $(gameboy)/Makefile +include $(ui)/Makefile + +objects := $(patsubst %,obj/%.o,$(objects)) + +# targets +build: ui_build $(objects) +ifeq ($(platform),osx) + test -d ../bgameboy.app || mkdir -p ../bgameboy.app/Contents/MacOS + $(strip $(cpp) -o ../bgameboy.app/Contents/MacOS/bgameboy $(objects) $(link)) +else + $(strip $(cpp) -o out/bgameboy $(objects) $(link)) +endif + +install: +ifeq ($(platform),x) + install -D -m 755 out/bgameboy $(DESTDIR)$(prefix)/bin/bgameboy + test -d ~/.bgameboy || mkdir ~/.bgameboy +endif + +uninstall: +ifeq ($(platform),x) + rm $(DESTDIR)$(prefix)/bin/bgameboy +endif + +clean: ui_clean + -@$(call delete,obj/*) + +archive-all: + tar -cjf bgameboy.tar.bz2 gameboy libco nall obj out phoenix ruby ui Makefile sync.sh + +help:; diff --git a/gameboy/Makefile b/gameboy/Makefile new file mode 100755 index 00000000..d368bc29 --- /dev/null +++ b/gameboy/Makefile @@ -0,0 +1,10 @@ +gameboy_objects := libco +gameboy_objects += gameboy-system gameboy-cartridge gameboy-memory gameboy-cpu +objects += $(gameboy_objects) + +obj/libco.o: libco/libco.c libco/* + +obj/gameboy-system.o: $(gameboy)/system/system.cpp $(call rwildcard,$(gameboy)/system/) +obj/gameboy-cartridge.o: $(gameboy)/cartridge/cartridge.cpp $(call rwildcard,$(gameboy)/cartridge/) +obj/gameboy-memory.o: $(gameboy)/memory/memory.cpp $(call rwildcard,$(gameboy)/memory/) +obj/gameboy-cpu.o: $(gameboy)/cpu/cpu.cpp $(call rwildcard,$(gameboy)/cpu/) diff --git a/gameboy/cartridge/cartridge.cpp b/gameboy/cartridge/cartridge.cpp new file mode 100755 index 00000000..4b7bbfdf --- /dev/null +++ b/gameboy/cartridge/cartridge.cpp @@ -0,0 +1,58 @@ +#include + +#define CARTRIDGE_CPP +namespace GameBoy { + +Cartridge cartridge; + +void Cartridge::load(uint8_t *data, unsigned size) { + bus.cartrom.copy(data, size); + + char name[17]; + memcpy(name, bus.cartrom.data + 0x0134, 16); + name[16] = 0; + info.name = name; + info.name.rtrim(); + + info.cgbflag = bus.cartrom[0x0143]; + info.sgbflag = bus.cartrom[0x0146]; + info.type = bus.cartrom[0x0147]; + + switch(bus.cartrom[0x0148]) { default: + case 0x00: info.romsize = 2 * 16 * 1024; break; + case 0x01: info.romsize = 4 * 16 * 1024; break; + case 0x02: info.romsize = 8 * 16 * 1024; break; + case 0x03: info.romsize = 16 * 16 * 1024; break; + case 0x04: info.romsize = 32 * 16 * 1024; break; + case 0x05: info.romsize = 64 * 16 * 1024; break; + case 0x06: info.romsize = 128 * 16 * 1024; break; + case 0x07: info.romsize = 256 * 16 * 1024; break; + case 0x52: info.romsize = 72 * 16 * 1024; break; + case 0x53: info.romsize = 80 * 16 * 1024; break; + case 0x54: info.romsize = 96 * 16 * 1024; break; + } + + //TODO: MBC2 always stores 0x00 here; yet it has 512x4-bits RAM + switch(bus.cartrom[0x0149]) { default: + case 0x00: info.ramsize = 0 * 1024; break; + case 0x01: info.ramsize = 2 * 1024; break; + case 0x02: info.ramsize = 8 * 1024; break; + case 0x03: info.ramsize = 32 * 1024; break; + } + + loaded = true; + system.interface->message({ "Loaded:\n", info.name }); +} + +void Cartridge::unload() { + if(loaded == false) return; + + bus.cartrom.free(); + loaded = false; +} + +Cartridge::Cartridge() { + loaded = false; +} + +} diff --git a/gameboy/cartridge/cartridge.hpp b/gameboy/cartridge/cartridge.hpp new file mode 100755 index 00000000..a19b3b7d --- /dev/null +++ b/gameboy/cartridge/cartridge.hpp @@ -0,0 +1,19 @@ +class Cartridge : property { +public: + struct Information { + string name; + uint8 cgbflag; + uint8 sgbflag; + uint8 type; + unsigned romsize; + unsigned ramsize; + } info; + + readonly loaded; + + void load(uint8_t *data, unsigned size); + void unload(); + Cartridge(); +}; + +extern Cartridge cartridge; diff --git a/gameboy/cpu/cpu.cpp b/gameboy/cpu/cpu.cpp new file mode 100755 index 00000000..2a986aec --- /dev/null +++ b/gameboy/cpu/cpu.cpp @@ -0,0 +1,27 @@ +//Sharp LR35902 @ 4.19MHz + +#include + +#define CPU_CPP +namespace GameBoy { + +CPU cpu; + +void CPU::main() { + while(true) { + uint8 opcode = bus.read(r.pc++); + switch(opcode) { + case 0x00: break; //NOP + } + } +} + +void CPU::power() { + reset(); +} + +void CPU::reset() { + r.pc = 0x0100; +} + +} diff --git a/gameboy/cpu/cpu.hpp b/gameboy/cpu/cpu.hpp new file mode 100755 index 00000000..073d12c9 --- /dev/null +++ b/gameboy/cpu/cpu.hpp @@ -0,0 +1,10 @@ +class CPU { +public: + #include "registers.hpp" + + void main(); + void power(); + void reset(); +}; + +extern CPU cpu; diff --git a/gameboy/cpu/registers.hpp b/gameboy/cpu/registers.hpp new file mode 100755 index 00000000..45c8cbf7 --- /dev/null +++ b/gameboy/cpu/registers.hpp @@ -0,0 +1,79 @@ +//register base class +//the idea here is to have all registers derive from a single base class. +//this allows construction of opcodes that can take any register as input or output, +//despite the fact that behind-the-scenes, special handling is done for eg: F, AF, HL, etc. +//registers can also be chained together: eg af = 0x0000 writes both a and f. +struct Register { + virtual operator unsigned() const = 0; + virtual unsigned operator=(unsigned x) = 0; + + unsigned operator++(int) { unsigned r = *this; operator=(*this + 1); return r; } + unsigned operator--(int) { unsigned r = *this; operator=(*this - 1); return r; } + unsigned operator++() { return operator=(*this + 1); } + unsigned operator--() { return operator=(*this - 1); } + + unsigned operator |=(unsigned x) { return operator=(*this | x); } + unsigned operator ^=(unsigned x) { return operator=(*this ^ x); } + unsigned operator &=(unsigned x) { return operator=(*this & x); } + + unsigned operator<<=(unsigned x) { return operator=(*this << x); } + unsigned operator>>=(unsigned x) { return operator=(*this >> x); } + + unsigned operator +=(unsigned x) { return operator=(*this + x); } + unsigned operator -=(unsigned x) { return operator=(*this - x); } + unsigned operator *=(unsigned x) { return operator=(*this * x); } + unsigned operator /=(unsigned x) { return operator=(*this / x); } + unsigned operator %=(unsigned x) { return operator=(*this % x); } +}; + +struct Register8 : Register { + uint8 data; + operator unsigned() const { return data; } + unsigned operator=(unsigned x) { return data = x; } +}; + +struct RegisterF : Register { + bool z, n, h, c; + operator unsigned() const { return (z << 7) | (n << 6) | (h << 5) | (c << 4); } + unsigned operator=(unsigned x) { z = x & 0x80; n = x & 0x40; h = x & 0x20; c = x & 0x10; return *this; } +}; + +struct Register16 : Register { + uint16 data; + operator unsigned() const { return data; } + unsigned operator=(unsigned x) { return data = x; } +}; + +struct RegisterAF : Register { + Register8 &hi; + RegisterF &lo; + operator unsigned() const { return (hi << 8) | (lo << 0); } + unsigned operator=(unsigned x) { hi = x >> 8; lo = x >> 0; return *this; } + RegisterAF(Register8 &hi, RegisterF &lo) : hi(hi), lo(lo) {} +}; + +struct RegisterW : Register { + Register8 &hi, &lo; + operator unsigned() const { return (hi << 8) | (lo << 0); } + unsigned operator=(unsigned x) { hi = x >> 8; lo = x >> 0; return *this; } + RegisterW(Register8 &hi, Register8 &lo) : hi(hi), lo(lo) {} +}; + +struct Registers { + Register8 a; + RegisterF f; + RegisterAF af; + Register8 b; + Register8 c; + RegisterW bc; + Register8 d; + Register8 e; + RegisterW de; + Register8 h; + Register8 l; + RegisterW hl; + Register16 sp; + Register16 pc; + + Registers() : af(a, f), bc(b, c), de(d, e), hl(h, l) {} +} r; diff --git a/gameboy/gameboy.hpp b/gameboy/gameboy.hpp new file mode 100755 index 00000000..917ea17c --- /dev/null +++ b/gameboy/gameboy.hpp @@ -0,0 +1,35 @@ +//bgameboy +//author: byuu +//project started: 2010-12-27 + +namespace GameBoy { + namespace Info { + static const char Name[] = "bgameboy"; + static const char Version[] = "000"; + } +} + +#include + +#include +#include +#include +#include +using namespace nall; + +namespace GameBoy { + typedef int8_t int8; + typedef int16_t int16; + typedef int32_t int32; + typedef int64_t int64; + + typedef uint8_t uint8; + typedef uint16_t uint16; + typedef uint32_t uint32; + typedef uint64_t uint64; + + #include + #include + #include + #include +}; diff --git a/gameboy/interface/interface.hpp b/gameboy/interface/interface.hpp new file mode 100755 index 00000000..e43a4a16 --- /dev/null +++ b/gameboy/interface/interface.hpp @@ -0,0 +1,9 @@ +class Interface { +public: + virtual void video_refresh(const uint8_t *data) {} + virtual void audio_sample(signed left, signed right) {} + virtual void input_poll() {} + virtual bool input_poll(unsigned id) {} + + virtual void message(const string &text) { print(text, "\n"); } +}; diff --git a/gameboy/memory/memory.cpp b/gameboy/memory/memory.cpp new file mode 100755 index 00000000..9bc3bc98 --- /dev/null +++ b/gameboy/memory/memory.cpp @@ -0,0 +1,140 @@ +#include + +#define MEMORY_CPP +namespace GameBoy { + +Bus bus; + +uint8_t& Memory::operator[](unsigned addr) { + return data[addr]; +} + +void Memory::allocate(unsigned size_) { + free(); + size = size_; + data = new uint8_t[size](); +} + +void Memory::copy(const uint8_t *data_, unsigned size_) { + free(); + size = size_; + data = new uint8_t[size]; + memcpy(data, data_, size); +} + +void Memory::free() { + if(data) { + delete[] data; + data = 0; + } +} + +Memory::Memory() { + data = 0; + size = 0; +} + +Memory::~Memory() { + free(); +} + +// + +uint8 Bus::read(uint16 addr) { + if(/* addr >= 0x0000 && */ addr <= 0x7fff) { //Cartridge ROM + return cartrom[addr]; + } + + if(/* addr >= 0x8000 && */ addr <= 0x9fff) { //VRAM + return vram[addr & 0x1fff]; + } + + if(/* addr >= 0xa000 && */ addr <= 0xbfff) { //Cartridge RAM + if(cartram.size == 0) return 0x00; + return cartram[addr & 0x1fff]; + } + + if(/* addr >= 0xc000 && */ addr <= 0xdfff) { //WRAM + return wram[addr & 0x1fff]; + } + + if(/* addr >= 0xe000 && */ addr <= 0xfdff) { //WRAM (mirror) + return wram[addr & 0x1fff]; + } + + if(/* addr >= 0xfe00 && */ addr <= 0xfe9f) { //OAM + return oam[addr & 0xff]; + } + + if(/* addr >= 0xfea0 && */ addr <= 0xfeff) { //unmapped + return 0x00; + } + + if(/* addr >= 0xff00 && */ addr <= 0xff7f) { //MMIO + return 0x00; + } + + if(/* addr >= 0xff80 && */ addr <= 0xfffe) { //HRAM + return hram[addr & 0x7f]; + } + + //addr == 0xffff (interrupt enable register) + return 0x00; +} + +void Bus::write(uint16 addr, uint8 data) { + if(/* addr >= 0x0000 && */ addr <= 0x7fff) { //Cartridge ROM + return; + } + + if(/* addr >= 0x8000 && */ addr <= 0x9fff) { //VRAM + vram[addr & 0x1fff] = data; + return; + } + + if(/* addr >= 0xa000 && */ addr <= 0xbfff) { //Cartridge RAM + if(cartram.size == 0) return; + cartram[addr & 0x1fff] = data; + return; + } + + if(/* addr >= 0xc000 && */ addr <= 0xdfff) { //WRAM + wram[addr & 0x1fff] = data; + return; + } + + if(/* addr >= 0xe000 && */ addr <= 0xfdff) { //WRAM (mirror) + wram[addr & 0x1fff] = data; + return; + } + + if(/* addr >= 0xfe00 && */ addr <= 0xfe9f) { //OAM + oam[addr & 0xff] = data; + return; + } + + if(/* addr >= 0xfea0 && */ addr <= 0xfeff) { //unmapped + return; + } + + if(/* addr >= 0xff00 && */ addr <= 0xff7f) { //MMIO + return; + } + + if(/* addr >= 0xff80 && */ addr <= 0xfffe) { //HRAM + hram[addr & 0x7f] = data; + return; + } + + //addr == 0xffff (interrupt enable register) + return; +} + +Bus::Bus() { + vram.allocate(8192); + wram.allocate(8192); + oam.allocate ( 160); + hram.allocate( 128); +} + +} diff --git a/gameboy/memory/memory.hpp b/gameboy/memory/memory.hpp new file mode 100755 index 00000000..4c94bb8d --- /dev/null +++ b/gameboy/memory/memory.hpp @@ -0,0 +1,28 @@ +struct Memory { + uint8_t *data; + unsigned size; + + uint8_t& operator[](unsigned addr); + void allocate(unsigned size); + void copy(const uint8_t *data, unsigned size); + void free(); + Memory(); + ~Memory(); +}; + +class Bus { +public: + Memory cartrom; + Memory cartram; + Memory vram; + Memory wram; + Memory oam; + Memory hram; + + uint8 read(uint16 addr); + void write(uint16 addr, uint8 data); + + Bus(); +}; + +extern Bus bus; diff --git a/gameboy/system/system.cpp b/gameboy/system/system.cpp new file mode 100755 index 00000000..bebae611 --- /dev/null +++ b/gameboy/system/system.cpp @@ -0,0 +1,20 @@ +#include + +#define SYSTEM_CPP +namespace GameBoy { + +System system; + +void System::init(Interface *interface_) { + interface = interface_; +} + +void System::power() { + cpu.power(); +} + +void System::reset() { + cpu.reset(); +} + +} diff --git a/gameboy/system/system.hpp b/gameboy/system/system.hpp new file mode 100755 index 00000000..ccc9b9c0 --- /dev/null +++ b/gameboy/system/system.hpp @@ -0,0 +1,15 @@ +class Interface; + +class System { +public: + void init(Interface*); + void power(); + void reset(); + +//private: + Interface *interface; +}; + +#include + +extern System system; diff --git a/libco/amd64.c b/libco/amd64.c new file mode 100755 index 00000000..5f1cfca9 --- /dev/null +++ b/libco/amd64.c @@ -0,0 +1,104 @@ +/* + libco.amd64 (2009-10-12) + author: byuu + license: public domain +*/ + +#define LIBCO_C +#include "libco.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +static thread_local long long co_active_buffer[64]; +static thread_local cothread_t co_active_handle = 0; +static void (*co_swap)(cothread_t, cothread_t) = 0; + +#ifdef _WIN32 + //ABI: Win64 + static unsigned char co_swap_function[] = { + 0x48, 0x89, 0x22, 0x48, 0x8B, 0x21, 0x58, 0x48, 0x89, 0x6A, 0x08, 0x48, 0x89, 0x72, 0x10, 0x48, + 0x89, 0x7A, 0x18, 0x48, 0x89, 0x5A, 0x20, 0x4C, 0x89, 0x62, 0x28, 0x4C, 0x89, 0x6A, 0x30, 0x4C, + 0x89, 0x72, 0x38, 0x4C, 0x89, 0x7A, 0x40, 0x48, 0x81, 0xC2, 0x80, 0x00, 0x00, 0x00, 0x48, 0x83, + 0xE2, 0xF0, 0x0F, 0x29, 0x32, 0x0F, 0x29, 0x7A, 0x10, 0x44, 0x0F, 0x29, 0x42, 0x20, 0x44, 0x0F, + 0x29, 0x4A, 0x30, 0x44, 0x0F, 0x29, 0x52, 0x40, 0x44, 0x0F, 0x29, 0x5A, 0x50, 0x44, 0x0F, 0x29, + 0x62, 0x60, 0x44, 0x0F, 0x29, 0x6A, 0x70, 0x44, 0x0F, 0x29, 0xB2, 0x80, 0x00, 0x00, 0x00, 0x44, + 0x0F, 0x29, 0xBA, 0x90, 0x00, 0x00, 0x00, 0x48, 0x8B, 0x69, 0x08, 0x48, 0x8B, 0x71, 0x10, 0x48, + 0x8B, 0x79, 0x18, 0x48, 0x8B, 0x59, 0x20, 0x4C, 0x8B, 0x61, 0x28, 0x4C, 0x8B, 0x69, 0x30, 0x4C, + 0x8B, 0x71, 0x38, 0x4C, 0x8B, 0x79, 0x40, 0x48, 0x81, 0xC1, 0x80, 0x00, 0x00, 0x00, 0x48, 0x83, + 0xE1, 0xF0, 0x0F, 0x29, 0x31, 0x0F, 0x29, 0x79, 0x10, 0x44, 0x0F, 0x29, 0x41, 0x20, 0x44, 0x0F, + 0x29, 0x49, 0x30, 0x44, 0x0F, 0x29, 0x51, 0x40, 0x44, 0x0F, 0x29, 0x59, 0x50, 0x44, 0x0F, 0x29, + 0x61, 0x60, 0x44, 0x0F, 0x29, 0x69, 0x70, 0x44, 0x0F, 0x29, 0xB1, 0x80, 0x00, 0x00, 0x00, 0x44, + 0x0F, 0x29, 0xB9, 0x90, 0x00, 0x00, 0x00, 0xFF, 0xE0, + }; + + #include + + void co_init() { + DWORD old_privileges; + VirtualProtect(co_swap_function, sizeof co_swap_function, PAGE_EXECUTE_READWRITE, &old_privileges); + } +#else + //ABI: SystemV + static unsigned char co_swap_function[] = { + 0x48, 0x89, 0x26, 0x48, 0x8B, 0x27, 0x58, 0x48, 0x89, 0x6E, 0x08, 0x48, 0x89, 0x5E, 0x10, 0x4C, + 0x89, 0x66, 0x18, 0x4C, 0x89, 0x6E, 0x20, 0x4C, 0x89, 0x76, 0x28, 0x4C, 0x89, 0x7E, 0x30, 0x48, + 0x8B, 0x6F, 0x08, 0x48, 0x8B, 0x5F, 0x10, 0x4C, 0x8B, 0x67, 0x18, 0x4C, 0x8B, 0x6F, 0x20, 0x4C, + 0x8B, 0x77, 0x28, 0x4C, 0x8B, 0x7F, 0x30, 0xFF, 0xE0, + }; + + #include + #include + + void co_init() { + unsigned long long addr = (unsigned long long)co_swap_function; + unsigned long long base = addr - (addr % sysconf(_SC_PAGESIZE)); + unsigned long long size = (addr - base) + sizeof co_swap_function; + mprotect((void*)base, size, PROT_READ | PROT_WRITE | PROT_EXEC); + } +#endif + +static void crash() { + assert(0); /* called only if cothread_t entrypoint returns */ +} + +cothread_t co_active() { + if(!co_active_handle) co_active_handle = &co_active_buffer; + return co_active_handle; +} + +cothread_t co_create(unsigned int size, void (*entrypoint)(void)) { + cothread_t handle; + if(!co_swap) { + co_init(); + co_swap = (void (*)(cothread_t, cothread_t))co_swap_function; + } + if(!co_active_handle) co_active_handle = &co_active_buffer; + size += 512; /* allocate additional space for storage */ + size &= ~15; /* align stack to 16-byte boundary */ + + if(handle = (cothread_t)malloc(size)) { + long long *p = (long long*)((char*)handle + size); /* seek to top of stack */ + *--p = (long long)crash; /* crash if entrypoint returns */ + *--p = (long long)entrypoint; /* start of function */ + *(long long*)handle = (long long)p; /* stack pointer */ + } + + return handle; +} + +void co_delete(cothread_t handle) { + free(handle); +} + +void co_switch(cothread_t handle) { + register cothread_t co_previous_handle = co_active_handle; + co_swap(co_active_handle = handle, co_previous_handle); +} + +#ifdef __cplusplus +} +#endif diff --git a/libco/fiber.c b/libco/fiber.c new file mode 100755 index 00000000..02ef5bc7 --- /dev/null +++ b/libco/fiber.c @@ -0,0 +1,51 @@ +/* + libco.win (2008-01-28) + authors: Nach, byuu + license: public domain +*/ + +#define LIBCO_C +#include "libco.h" +#define WINVER 0x0400 +#define _WIN32_WINNT 0x0400 +#define WIN32_LEAN_AND_MEAN +#include + +#ifdef __cplusplus +extern "C" { +#endif + +static thread_local cothread_t co_active_ = 0; + +static void __stdcall co_thunk(void *coentry) { + ((void (*)(void))coentry)(); +} + +cothread_t co_active() { + if(!co_active_) { + ConvertThreadToFiber(0); + co_active_ = GetCurrentFiber(); + } + return co_active_; +} + +cothread_t co_create(unsigned int heapsize, void (*coentry)(void)) { + if(!co_active_) { + ConvertThreadToFiber(0); + co_active_ = GetCurrentFiber(); + } + return (cothread_t)CreateFiber(heapsize, co_thunk, (void*)coentry); +} + +void co_delete(cothread_t cothread) { + DeleteFiber(cothread); +} + +void co_switch(cothread_t cothread) { + co_active_ = cothread; + SwitchToFiber(cothread); +} + +#ifdef __cplusplus +} +#endif diff --git a/libco/libco.c b/libco/libco.c new file mode 100755 index 00000000..55676263 --- /dev/null +++ b/libco/libco.c @@ -0,0 +1,23 @@ +/* + libco + auto-selection module + license: public domain +*/ + +#if defined(__GNUC__) && defined(__i386__) + #include "x86.c" +#elif defined(__GNUC__) && defined(__amd64__) + #include "amd64.c" +#elif defined(__GNUC__) && defined(_ARCH_PPC) + #include "ppc.c" +#elif defined(__GNUC__) + #include "sjlj.c" +#elif defined(_MSC_VER) && defined(_M_IX86) + #include "x86.c" +#elif defined(_MSC_VER) && defined(_M_AMD64) + #include "amd64.c" +#elif defined(_MSC_VER) + #include "fiber.c" +#else + #error "libco: unsupported processor, compiler or operating system" +#endif diff --git a/libco/libco.h b/libco/libco.h new file mode 100755 index 00000000..deb954fb --- /dev/null +++ b/libco/libco.h @@ -0,0 +1,34 @@ +/* + libco + version: 0.16 (2010-12-24) + license: public domain +*/ + +#ifndef LIBCO_H +#define LIBCO_H + +#ifdef LIBCO_C + #ifdef LIBCO_MP + #define thread_local __thread + #else + #define thread_local + #endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void* cothread_t; + +cothread_t co_active(); +cothread_t co_create(unsigned int, void (*)(void)); +void co_delete(cothread_t); +void co_switch(cothread_t); + +#ifdef __cplusplus +} +#endif + +/* ifndef LIBCO_H */ +#endif diff --git a/libco/ppc.c b/libco/ppc.c new file mode 100755 index 00000000..a6028fdb --- /dev/null +++ b/libco/ppc.c @@ -0,0 +1,407 @@ +/* + libco.ppc (2010-10-17) + author: blargg + license: public domain +*/ + +/* PowerPC 32/64 using embedded or external asm, with optional +floating-point and AltiVec save/restore */ + +#define LIBCO_C +#include "libco.h" +#include +#include +#include + +#define LIBCO_MPROTECT (__unix__ && !LIBCO_PPC_ASM) + +#if LIBCO_MPROTECT + #include + #include +#endif + +/* State format (offsets in 32-bit words) + ++0 Pointer to swap code + Rest of function descriptor for entry function ++8 PC ++10 SP + Special regs + GPRs + FPRs + VRs + stack +*/ + +enum { state_size = 1024 }; +enum { above_stack = 2048 }; +enum { stack_align = 256 }; + +static thread_local cothread_t co_active_handle = 0; + +/**** Determine environment ****/ + +#define LIBCO_PPC64 (_ARCH_PPC64 || __PPC64__ || __ppc64__ || __powerpc64__) + +/* Whether function calls are indirect through a descriptor, +or are directly to function */ +#ifndef LIBCO_PPCDESC + #if !_CALL_SYSV && (_CALL_AIX || _CALL_AIXDESC || LIBCO_PPC64) + #define LIBCO_PPCDESC 1 + #endif +#endif + +#ifdef LIBCO_PPC_ASM + + #ifdef __cplusplus + extern "C" + #endif + + /* Swap code is in ppc.S */ + void co_swap_asm( cothread_t, cothread_t ); + #define CO_SWAP_ASM( x, y ) co_swap_asm( x, y ) + +#else + +/* Swap code is here in array. Please leave dieassembly comments, +as they make it easy to see what it does, and reorder instructions +if one wants to see whether that improves performance. */ +static const uint32_t libco_ppc_code [] = { +#if LIBCO_PPC64 + 0x7d000026, /* mfcr r8 */ + 0xf8240028, /* std r1,40(r4) */ + 0x7d2802a6, /* mflr r9 */ + 0xf9c40048, /* std r14,72(r4) */ + 0xf9e40050, /* std r15,80(r4) */ + 0xfa040058, /* std r16,88(r4) */ + 0xfa240060, /* std r17,96(r4) */ + 0xfa440068, /* std r18,104(r4) */ + 0xfa640070, /* std r19,112(r4) */ + 0xfa840078, /* std r20,120(r4) */ + 0xfaa40080, /* std r21,128(r4) */ + 0xfac40088, /* std r22,136(r4) */ + 0xfae40090, /* std r23,144(r4) */ + 0xfb040098, /* std r24,152(r4) */ + 0xfb2400a0, /* std r25,160(r4) */ + 0xfb4400a8, /* std r26,168(r4) */ + 0xfb6400b0, /* std r27,176(r4) */ + 0xfb8400b8, /* std r28,184(r4) */ + 0xfba400c0, /* std r29,192(r4) */ + 0xfbc400c8, /* std r30,200(r4) */ + 0xfbe400d0, /* std r31,208(r4) */ + 0xf9240020, /* std r9,32(r4) */ + 0xe8e30020, /* ld r7,32(r3) */ + 0xe8230028, /* ld r1,40(r3) */ + 0x48000009, /* bl 1 */ + 0x7fe00008, /* trap */ + 0x91040030,/*1:stw r8,48(r4) */ + 0x80c30030, /* lwz r6,48(r3) */ + 0x7ce903a6, /* mtctr r7 */ + 0xe9c30048, /* ld r14,72(r3) */ + 0xe9e30050, /* ld r15,80(r3) */ + 0xea030058, /* ld r16,88(r3) */ + 0xea230060, /* ld r17,96(r3) */ + 0xea430068, /* ld r18,104(r3) */ + 0xea630070, /* ld r19,112(r3) */ + 0xea830078, /* ld r20,120(r3) */ + 0xeaa30080, /* ld r21,128(r3) */ + 0xeac30088, /* ld r22,136(r3) */ + 0xeae30090, /* ld r23,144(r3) */ + 0xeb030098, /* ld r24,152(r3) */ + 0xeb2300a0, /* ld r25,160(r3) */ + 0xeb4300a8, /* ld r26,168(r3) */ + 0xeb6300b0, /* ld r27,176(r3) */ + 0xeb8300b8, /* ld r28,184(r3) */ + 0xeba300c0, /* ld r29,192(r3) */ + 0xebc300c8, /* ld r30,200(r3) */ + 0xebe300d0, /* ld r31,208(r3) */ + 0x7ccff120, /* mtcr r6 */ +#else + 0x7d000026, /* mfcr r8 */ + 0x90240028, /* stw r1,40(r4) */ + 0x7d2802a6, /* mflr r9 */ + 0x91a4003c, /* stw r13,60(r4) */ + 0x91c40040, /* stw r14,64(r4) */ + 0x91e40044, /* stw r15,68(r4) */ + 0x92040048, /* stw r16,72(r4) */ + 0x9224004c, /* stw r17,76(r4) */ + 0x92440050, /* stw r18,80(r4) */ + 0x92640054, /* stw r19,84(r4) */ + 0x92840058, /* stw r20,88(r4) */ + 0x92a4005c, /* stw r21,92(r4) */ + 0x92c40060, /* stw r22,96(r4) */ + 0x92e40064, /* stw r23,100(r4) */ + 0x93040068, /* stw r24,104(r4) */ + 0x9324006c, /* stw r25,108(r4) */ + 0x93440070, /* stw r26,112(r4) */ + 0x93640074, /* stw r27,116(r4) */ + 0x93840078, /* stw r28,120(r4) */ + 0x93a4007c, /* stw r29,124(r4) */ + 0x93c40080, /* stw r30,128(r4) */ + 0x93e40084, /* stw r31,132(r4) */ + 0x91240020, /* stw r9,32(r4) */ + 0x80e30020, /* lwz r7,32(r3) */ + 0x80230028, /* lwz r1,40(r3) */ + 0x48000009, /* bl 1 */ + 0x7fe00008, /* trap */ + 0x91040030,/*1:stw r8,48(r4) */ + 0x80c30030, /* lwz r6,48(r3) */ + 0x7ce903a6, /* mtctr r7 */ + 0x81a3003c, /* lwz r13,60(r3) */ + 0x81c30040, /* lwz r14,64(r3) */ + 0x81e30044, /* lwz r15,68(r3) */ + 0x82030048, /* lwz r16,72(r3) */ + 0x8223004c, /* lwz r17,76(r3) */ + 0x82430050, /* lwz r18,80(r3) */ + 0x82630054, /* lwz r19,84(r3) */ + 0x82830058, /* lwz r20,88(r3) */ + 0x82a3005c, /* lwz r21,92(r3) */ + 0x82c30060, /* lwz r22,96(r3) */ + 0x82e30064, /* lwz r23,100(r3) */ + 0x83030068, /* lwz r24,104(r3) */ + 0x8323006c, /* lwz r25,108(r3) */ + 0x83430070, /* lwz r26,112(r3) */ + 0x83630074, /* lwz r27,116(r3) */ + 0x83830078, /* lwz r28,120(r3) */ + 0x83a3007c, /* lwz r29,124(r3) */ + 0x83c30080, /* lwz r30,128(r3) */ + 0x83e30084, /* lwz r31,132(r3) */ + 0x7ccff120, /* mtcr r6 */ +#endif + +#ifndef LIBCO_PPC_NOFP + 0xd9c400e0, /* stfd f14,224(r4) */ + 0xd9e400e8, /* stfd f15,232(r4) */ + 0xda0400f0, /* stfd f16,240(r4) */ + 0xda2400f8, /* stfd f17,248(r4) */ + 0xda440100, /* stfd f18,256(r4) */ + 0xda640108, /* stfd f19,264(r4) */ + 0xda840110, /* stfd f20,272(r4) */ + 0xdaa40118, /* stfd f21,280(r4) */ + 0xdac40120, /* stfd f22,288(r4) */ + 0xdae40128, /* stfd f23,296(r4) */ + 0xdb040130, /* stfd f24,304(r4) */ + 0xdb240138, /* stfd f25,312(r4) */ + 0xdb440140, /* stfd f26,320(r4) */ + 0xdb640148, /* stfd f27,328(r4) */ + 0xdb840150, /* stfd f28,336(r4) */ + 0xdba40158, /* stfd f29,344(r4) */ + 0xdbc40160, /* stfd f30,352(r4) */ + 0xdbe40168, /* stfd f31,360(r4) */ + 0xc9c300e0, /* lfd f14,224(r3) */ + 0xc9e300e8, /* lfd f15,232(r3) */ + 0xca0300f0, /* lfd f16,240(r3) */ + 0xca2300f8, /* lfd f17,248(r3) */ + 0xca430100, /* lfd f18,256(r3) */ + 0xca630108, /* lfd f19,264(r3) */ + 0xca830110, /* lfd f20,272(r3) */ + 0xcaa30118, /* lfd f21,280(r3) */ + 0xcac30120, /* lfd f22,288(r3) */ + 0xcae30128, /* lfd f23,296(r3) */ + 0xcb030130, /* lfd f24,304(r3) */ + 0xcb230138, /* lfd f25,312(r3) */ + 0xcb430140, /* lfd f26,320(r3) */ + 0xcb630148, /* lfd f27,328(r3) */ + 0xcb830150, /* lfd f28,336(r3) */ + 0xcba30158, /* lfd f29,344(r3) */ + 0xcbc30160, /* lfd f30,352(r3) */ + 0xcbe30168, /* lfd f31,360(r3) */ +#endif + +#ifdef __ALTIVEC__ + 0x7ca042a6, /* mfvrsave r5 */ + 0x39040180, /* addi r8,r4,384 */ + 0x39240190, /* addi r9,r4,400 */ + 0x70a00fff, /* andi. r0,r5,4095 */ + 0x90a40034, /* stw r5,52(r4) */ + 0x4182005c, /* beq- 2 */ + 0x7e8041ce, /* stvx v20,r0,r8 */ + 0x39080020, /* addi r8,r8,32 */ + 0x7ea049ce, /* stvx v21,r0,r9 */ + 0x39290020, /* addi r9,r9,32 */ + 0x7ec041ce, /* stvx v22,r0,r8 */ + 0x39080020, /* addi r8,r8,32 */ + 0x7ee049ce, /* stvx v23,r0,r9 */ + 0x39290020, /* addi r9,r9,32 */ + 0x7f0041ce, /* stvx v24,r0,r8 */ + 0x39080020, /* addi r8,r8,32 */ + 0x7f2049ce, /* stvx v25,r0,r9 */ + 0x39290020, /* addi r9,r9,32 */ + 0x7f4041ce, /* stvx v26,r0,r8 */ + 0x39080020, /* addi r8,r8,32 */ + 0x7f6049ce, /* stvx v27,r0,r9 */ + 0x39290020, /* addi r9,r9,32 */ + 0x7f8041ce, /* stvx v28,r0,r8 */ + 0x39080020, /* addi r8,r8,32 */ + 0x7fa049ce, /* stvx v29,r0,r9 */ + 0x39290020, /* addi r9,r9,32 */ + 0x7fc041ce, /* stvx v30,r0,r8 */ + 0x7fe049ce, /* stvx v31,r0,r9 */ + 0x80a30034,/*2:lwz r5,52(r3) */ + 0x39030180, /* addi r8,r3,384 */ + 0x39230190, /* addi r9,r3,400 */ + 0x70a00fff, /* andi. r0,r5,4095 */ + 0x7ca043a6, /* mtvrsave r5 */ + 0x4d820420, /* beqctr */ + 0x7e8040ce, /* lvx v20,r0,r8 */ + 0x39080020, /* addi r8,r8,32 */ + 0x7ea048ce, /* lvx v21,r0,r9 */ + 0x39290020, /* addi r9,r9,32 */ + 0x7ec040ce, /* lvx v22,r0,r8 */ + 0x39080020, /* addi r8,r8,32 */ + 0x7ee048ce, /* lvx v23,r0,r9 */ + 0x39290020, /* addi r9,r9,32 */ + 0x7f0040ce, /* lvx v24,r0,r8 */ + 0x39080020, /* addi r8,r8,32 */ + 0x7f2048ce, /* lvx v25,r0,r9 */ + 0x39290020, /* addi r9,r9,32 */ + 0x7f4040ce, /* lvx v26,r0,r8 */ + 0x39080020, /* addi r8,r8,32 */ + 0x7f6048ce, /* lvx v27,r0,r9 */ + 0x39290020, /* addi r9,r9,32 */ + 0x7f8040ce, /* lvx v28,r0,r8 */ + 0x39080020, /* addi r8,r8,32 */ + 0x7fa048ce, /* lvx v29,r0,r9 */ + 0x39290020, /* addi r9,r9,32 */ + 0x7fc040ce, /* lvx v30,r0,r8 */ + 0x7fe048ce, /* lvx v31,r0,r9 */ +#endif + + 0x4e800420, /* bctr */ +}; + + #if LIBCO_PPCDESC + /* Function call goes through indirect descriptor */ + #define CO_SWAP_ASM( x, y ) \ + ((void (*)( cothread_t, cothread_t )) (uintptr_t) x)( x, y ) + #else + /* Function call goes directly to code */ + #define CO_SWAP_ASM( x, y ) \ + ((void (*)( cothread_t, cothread_t )) (uintptr_t) libco_ppc_code)( x, y ) + #endif + +#endif + +static uint32_t* co_create_( unsigned size, uintptr_t entry ) +{ + uint32_t* t = (uint32_t*) malloc( size ); + + (void) entry; + + #if LIBCO_PPCDESC + if ( t ) + { + /* Copy entry's descriptor */ + memcpy( t, (void*) entry, sizeof (void*) * 3 ); + + /* Set function pointer to swap routine */ + #ifdef LIBCO_PPC_ASM + *(const void**) t = *(void**) &co_swap_asm; + #else + *(const void**) t = libco_ppc_code; + #endif + } + #endif + + return t; +} + +cothread_t co_create( unsigned int size, void (*entry_)( void ) ) +{ + uintptr_t entry = (uintptr_t) entry_; + uint32_t* t = NULL; + + /* Be sure main thread was successfully allocated */ + if ( co_active() ) + { + size += state_size + above_stack + stack_align; + t = co_create_( size, entry ); + } + + if ( t ) + { + uintptr_t sp; + int shift; + + /* Save current registers into new thread, so that any special ones will + have proper values when thread is begun */ + CO_SWAP_ASM( t, t ); + + #if LIBCO_PPCDESC + /* Get real address */ + entry = (uintptr_t) *(void**) entry; + #endif + + /* Put stack near end of block, and align */ + sp = (uintptr_t) t + size - above_stack; + sp -= sp % stack_align; + + /* On PPC32, we save and restore GPRs as 32 bits. For PPC64, we + save and restore them as 64 bits, regardless of the size the ABI + uses. So, we manually write pointers at the proper size. We always + save and restore at the same address, and since PPC is big-endian, + we must put the low byte first on PPC32. */ + + /* If uintptr_t is 32 bits, >>32 is undefined behavior, so we do two shifts + and don't have to care how many bits uintptr_t is. */ + #if LIBCO_PPC64 + shift = 16; + #else + shift = 0; + #endif + + /* Set up so entry will be called on next swap */ + t [8] = (uint32_t) (entry >> shift >> shift); + t [9] = (uint32_t) entry; + + t [10] = (uint32_t) (sp >> shift >> shift); + t [11] = (uint32_t) sp; + } + + return t; +} + +void co_delete( cothread_t t ) +{ + free( t ); +} + +static void co_init_( void ) +{ + #if LIBCO_MPROTECT + /* TODO: pre- and post-pad PPC code so that this doesn't make other + data executable and writable */ + long page_size = sysconf( _SC_PAGESIZE ); + if ( page_size > 0 ) + { + uintptr_t align = page_size; + uintptr_t begin = (uintptr_t) libco_ppc_code; + uintptr_t end = begin + sizeof libco_ppc_code; + + /* Align beginning and end */ + end += align - 1; + end -= end % align; + begin -= begin % align; + + mprotect( (void*) begin, end - begin, PROT_READ | PROT_WRITE | PROT_EXEC ); + } + #endif + + co_active_handle = co_create_( state_size, (uintptr_t) &co_switch ); +} + +cothread_t co_active() +{ + if ( !co_active_handle ) + co_init_(); + + return co_active_handle; +} + +void co_switch( cothread_t t ) +{ + cothread_t old = co_active_handle; + co_active_handle = t; + + CO_SWAP_ASM( t, old ); +} diff --git a/libco/sjlj.c b/libco/sjlj.c new file mode 100755 index 00000000..8b72b614 --- /dev/null +++ b/libco/sjlj.c @@ -0,0 +1,102 @@ +/* + libco.sjlj (2008-01-28) + author: Nach + license: public domain +*/ + +/* + * Note this was designed for UNIX systems. Based on ideas expressed in a paper + * by Ralf Engelschall. + * For SJLJ on other systems, one would want to rewrite springboard() and + * co_create() and hack the jmb_buf stack pointer. + */ + +#define LIBCO_C +#include "libco.h" +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + sigjmp_buf context; + void (*coentry)(void); + void *stack; +} cothread_struct; + +static thread_local cothread_struct co_primary; +static thread_local cothread_struct *creating, *co_running = 0; + +static void springboard(int ignored) { + if(sigsetjmp(creating->context, 0)) { + co_running->coentry(); + } +} + +cothread_t co_active() { + if(!co_running) co_running = &co_primary; + return (cothread_t)co_running; +} + +cothread_t co_create(unsigned int size, void (*coentry)(void)) { + if(!co_running) co_running = &co_primary; + + cothread_struct *thread = (cothread_struct*)malloc(sizeof(cothread_struct)); + if(thread) { + struct sigaction handler; + struct sigaction old_handler; + + stack_t stack; + stack_t old_stack; + + thread->coentry = thread->stack = 0; + + stack.ss_flags = 0; + stack.ss_size = size; + thread->stack = stack.ss_sp = malloc(size); + if(stack.ss_sp && !sigaltstack(&stack, &old_stack)) { + handler.sa_handler = springboard; + handler.sa_flags = SA_ONSTACK; + sigemptyset(&handler.sa_mask); + creating = thread; + + if(!sigaction(SIGUSR1, &handler, &old_handler)) { + if(!raise(SIGUSR1)) { + thread->coentry = coentry; + } + sigaltstack(&old_stack, 0); + sigaction(SIGUSR1, &old_handler, 0); + } + } + + if(thread->coentry != coentry) { + co_delete(thread); + thread = 0; + } + } + + return (cothread_t)thread; +} + +void co_delete(cothread_t cothread) { + if(cothread) { + if(((cothread_struct*)cothread)->stack) { + free(((cothread_struct*)cothread)->stack); + } + free(cothread); + } +} + +void co_switch(cothread_t cothread) { + if(!sigsetjmp(co_running->context, 0)) { + co_running = (cothread_struct*)cothread; + siglongjmp(co_running->context, 1); + } +} + +#ifdef __cplusplus +} +#endif diff --git a/libco/ucontext.c b/libco/ucontext.c new file mode 100755 index 00000000..17472f6b --- /dev/null +++ b/libco/ucontext.c @@ -0,0 +1,67 @@ +/* + libco.ucontext (2008-01-28) + author: Nach + license: public domain +*/ + +/* + * WARNING: the overhead of POSIX ucontext is very high, + * assembly versions of libco or libco_sjlj should be much faster + * + * This library only exists for two reasons: + * 1 - as an initial test for the viability of a ucontext implementation + * 2 - to demonstrate the power and speed of libco over existing implementations, + * such as pth (which defaults to wrapping ucontext on unix targets) + * + * Use this library only as a *last resort* + */ + +#define LIBCO_C +#include "libco.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +static thread_local ucontext_t co_primary; +static thread_local ucontext_t *co_running = 0; + +cothread_t co_active() { + if(!co_running) co_running = &co_primary; + return (cothread_t)co_running; +} + +cothread_t co_create(unsigned int heapsize, void (*coentry)(void)) { + if(!co_running) co_running = &co_primary; + ucontext_t *thread = (ucontext_t*)malloc(sizeof(ucontext_t)); + if(thread) { + if((!getcontext(thread) && !(thread->uc_stack.ss_sp = 0)) && (thread->uc_stack.ss_sp = malloc(heapsize))) { + thread->uc_link = co_running; + thread->uc_stack.ss_size = heapsize; + makecontext(thread, coentry, 0); + } else { + co_delete((cothread_t)thread); + thread = 0; + } + } + return (cothread_t)thread; +} + +void co_delete(cothread_t cothread) { + if(cothread) { + if(((ucontext_t*)cothread)->uc_stack.ss_sp) { free(((ucontext_t*)cothread)->uc_stack.ss_sp); } + free(cothread); + } +} + +void co_switch(cothread_t cothread) { + ucontext_t *old_thread = co_running; + co_running = (ucontext_t*)cothread; + swapcontext(old_thread, co_running); +} + +#ifdef __cplusplus +} +#endif diff --git a/libco/x86.c b/libco/x86.c new file mode 100755 index 00000000..d8f820b0 --- /dev/null +++ b/libco/x86.c @@ -0,0 +1,93 @@ +/* + libco.x86 (2009-10-12) + author: byuu + license: public domain +*/ + +#define LIBCO_C +#include "libco.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(_MSC_VER) + #define fastcall __fastcall +#elif defined(__GNUC__) + #define fastcall __attribute__((fastcall)) +#else + #error "libco: please define fastcall macro" +#endif + +static thread_local long co_active_buffer[64]; +static thread_local cothread_t co_active_handle = 0; +static void (fastcall *co_swap)(cothread_t, cothread_t) = 0; + +//ABI: fastcall +static unsigned char co_swap_function[] = { + 0x89, 0x22, 0x8B, 0x21, 0x58, 0x89, 0x6A, 0x04, 0x89, 0x72, 0x08, 0x89, 0x7A, 0x0C, 0x89, 0x5A, + 0x10, 0x8B, 0x69, 0x04, 0x8B, 0x71, 0x08, 0x8B, 0x79, 0x0C, 0x8B, 0x59, 0x10, 0xFF, 0xE0, +}; + +#ifdef _WIN32 + #include + + void co_init() { + DWORD old_privileges; + VirtualProtect(co_swap_function, sizeof co_swap_function, PAGE_EXECUTE_READWRITE, &old_privileges); + } +#else + #include + #include + + void co_init() { + unsigned long addr = (unsigned long)co_swap_function; + unsigned long base = addr - (addr % sysconf(_SC_PAGESIZE)); + unsigned long size = (addr - base) + sizeof co_swap_function; + mprotect((void*)base, size, PROT_READ | PROT_WRITE | PROT_EXEC); + } +#endif + +static void crash() { + assert(0); /* called only if cothread_t entrypoint returns */ +} + +cothread_t co_active() { + if(!co_active_handle) co_active_handle = &co_active_buffer; + return co_active_handle; +} + +cothread_t co_create(unsigned int size, void (*entrypoint)(void)) { + cothread_t handle; + if(!co_swap) { + co_init(); + co_swap = (void (fastcall*)(cothread_t, cothread_t))co_swap_function; + } + if(!co_active_handle) co_active_handle = &co_active_buffer; + size += 256; /* allocate additional space for storage */ + size &= ~15; /* align stack to 16-byte boundary */ + + if(handle = (cothread_t)malloc(size)) { + long *p = (long*)((char*)handle + size); /* seek to top of stack */ + *--p = (long)crash; /* crash if entrypoint returns */ + *--p = (long)entrypoint; /* start of function */ + *(long*)handle = (long)p; /* stack pointer */ + } + + return handle; +} + +void co_delete(cothread_t handle) { + free(handle); +} + +void co_switch(cothread_t handle) { + register cothread_t co_previous_handle = co_active_handle; + co_swap(co_active_handle = handle, co_previous_handle); +} + +#ifdef __cplusplus +} +#endif diff --git a/nall/Makefile b/nall/Makefile new file mode 100755 index 00000000..9a93bd23 --- /dev/null +++ b/nall/Makefile @@ -0,0 +1,109 @@ +# Makefile +# author: byuu +# license: public domain + +[A-Z] = A B C D E F G H I J K L M N O P Q R S T U V W X Y Z +[a-z] = a b c d e f g h i j k l m n o p q r s t u v w x y z +[0-9] = 0 1 2 3 4 5 6 7 8 9 +[markup] = ` ~ ! @ \# $$ % ^ & * ( ) - _ = + [ { ] } \ | ; : ' " , < . > / ? +[all] = $([A-Z]) $([a-z]) $([0-9]) $([markup]) +[space] := +[space] += + +##### +# platform detection +##### + +ifeq ($(platform),) + uname := $(shell uname -a) + ifeq ($(uname),) + platform := win + delete = del $(subst /,\,$1) + else ifneq ($(findstring Darwin,$(uname)),) + platform := osx + delete = rm -f $1 + else + platform := x + delete = rm -f $1 + endif +endif + +ifeq ($(compiler),) + ifeq ($(platform),win) + compiler := gcc + else ifeq ($(platform),osx) + compiler := gcc-mp-4.5 + else + compiler := gcc-4.5 + endif +endif + +ifeq ($(prefix),) + prefix := /usr/local +endif + +##### +# function rwildcard(directory, pattern) +##### +rwildcard = \ + $(strip \ + $(filter $(if $2,$2,%), \ + $(foreach f, \ + $(wildcard $1*), \ + $(eval t = $(call rwildcard,$f/)) \ + $(if $t,$t,$f) \ + ) \ + ) \ + ) + +##### +# function strtr(source, from, to) +##### +strtr = \ + $(eval __temp := $1) \ + $(strip \ + $(foreach c, \ + $(join $(addsuffix :,$2),$3), \ + $(eval __temp := \ + $(subst $(word 1,$(subst :, ,$c)),$(word 2,$(subst :, ,$c)),$(__temp)) \ + ) \ + ) \ + $(__temp) \ + ) + +##### +# function strupper(source) +##### +strupper = $(call strtr,$1,$([a-z]),$([A-Z])) + +##### +# function strlower(source) +##### +strlower = $(call strtr,$1,$([A-Z]),$([a-z])) + +##### +# function strlen(source) +##### +strlen = \ + $(eval __temp := $(subst $([space]),_,$1)) \ + $(words \ + $(strip \ + $(foreach c, \ + $([all]), \ + $(eval __temp := \ + $(subst $c,$c ,$(__temp)) \ + ) \ + ) \ + $(__temp) \ + ) \ + ) + +##### +# function streq(source) +##### +streq = $(if $(filter-out xx,x$(subst $1,,$2)$(subst $2,,$1)x),,1) + +##### +# function strne(source) +##### +strne = $(if $(filter-out xx,x$(subst $1,,$2)$(subst $2,,$1)x),1,) diff --git a/nall/algorithm.hpp b/nall/algorithm.hpp new file mode 100755 index 00000000..037f0bb7 --- /dev/null +++ b/nall/algorithm.hpp @@ -0,0 +1,17 @@ +#ifndef NALL_ALGORITHM_HPP +#define NALL_ALGORITHM_HPP + +#undef min +#undef max + +namespace nall { + template T min(const T &t, const U &u) { + return t < u ? t : u; + } + + template T max(const T &t, const U &u) { + return t > u ? t : u; + } +} + +#endif diff --git a/nall/any.hpp b/nall/any.hpp new file mode 100755 index 00000000..b31cff3c --- /dev/null +++ b/nall/any.hpp @@ -0,0 +1,74 @@ +#ifndef NALL_ANY_HPP +#define NALL_ANY_HPP + +#include +#include +#include + +namespace nall { + class any { + public: + bool empty() const { return container; } + const std::type_info& type() const { return container ? container->type() : typeid(void); } + + template any& operator=(const T& value_) { + typedef typename static_if< + std::is_array::value, + typename std::remove_extent::type>::type*, + T + >::type auto_t; + + if(type() == typeid(auto_t)) { + static_cast*>(container)->value = (auto_t)value_; + } else { + if(container) delete container; + container = new holder((auto_t)value_); + } + + return *this; + } + + any() : container(0) {} + template any(const T& value_) : container(0) { operator=(value_); } + + private: + struct placeholder { + virtual const std::type_info& type() const = 0; + } *container; + + template struct holder : placeholder { + T value; + const std::type_info& type() const { return typeid(T); } + holder(const T& value_) : value(value_) {} + }; + + template friend T any_cast(any&); + template friend T any_cast(const any&); + template friend T* any_cast(any*); + template friend const T* any_cast(const any*); + }; + + template T any_cast(any &value) { + typedef typename std::remove_reference::type nonref; + if(value.type() != typeid(nonref)) throw; + return static_cast*>(value.container)->value; + } + + template T any_cast(const any &value) { + typedef const typename std::remove_reference::type nonref; + if(value.type() != typeid(nonref)) throw; + return static_cast*>(value.container)->value; + } + + template T* any_cast(any *value) { + if(!value || value->type() != typeid(T)) return 0; + return &static_cast*>(value->container)->value; + } + + template const T* any_cast(const any *value) { + if(!value || value->type() != typeid(T)) return 0; + return &static_cast*>(value->container)->value; + } +} + +#endif diff --git a/nall/array.hpp b/nall/array.hpp new file mode 100755 index 00000000..9cfe7758 --- /dev/null +++ b/nall/array.hpp @@ -0,0 +1,141 @@ +#ifndef NALL_ARRAY_HPP +#define NALL_ARRAY_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace nall { + //dynamic vector array + //neither constructor nor destructor is ever invoked; + //thus, this should only be used for POD objects. + template class array { + protected: + T *pool; + unsigned poolsize, buffersize; + + public: + unsigned size() const { return buffersize; } + unsigned capacity() const { return poolsize; } + + void reset() { + if(pool) free(pool); + pool = 0; + poolsize = 0; + buffersize = 0; + } + + void reserve(unsigned newsize) { + if(newsize == poolsize) return; + + pool = (T*)realloc(pool, newsize * sizeof(T)); + poolsize = newsize; + buffersize = min(buffersize, newsize); + } + + void resize(unsigned newsize) { + if(newsize > poolsize) reserve(bit::round(newsize)); //round reserve size up to power of 2 + buffersize = newsize; + } + + T* get(unsigned minsize = 0) { + if(minsize > buffersize) resize(minsize); + if(minsize > buffersize) throw "array[] out of bounds"; + return pool; + } + + void append(const T data) { + operator[](buffersize) = data; + } + + template void insert(unsigned index, const U list) { + unsigned listsize = container_size(list); + resize(buffersize + listsize); + memmove(pool + index + listsize, pool + index, (buffersize - index) * sizeof(T)); + foreach(item, list) pool[index++] = item; + } + + void insert(unsigned index, const T item) { + insert(index, array{ item }); + } + + void remove(unsigned index, unsigned count = 1) { + for(unsigned i = index; count + i < buffersize; i++) { + pool[i] = pool[count + i]; + } + if(count + index >= buffersize) resize(index); //every element >= index was removed + else resize(buffersize - count); + } + + optional find(const T data) { + for(unsigned i = 0; i < size(); i++) if(pool[i] == data) return { true, i }; + return { false, 0 }; + } + + void clear() { + memset(pool, 0, buffersize * sizeof(T)); + } + + array() : pool(0), poolsize(0), buffersize(0) { + } + + array(std::initializer_list list) : pool(0), poolsize(0), buffersize(0) { + for(const T *p = list.begin(); p != list.end(); ++p) append(*p); + } + + ~array() { + reset(); + } + + //copy + array& operator=(const array &source) { + if(pool) free(pool); + buffersize = source.buffersize; + poolsize = source.poolsize; + pool = (T*)malloc(sizeof(T) * poolsize); //allocate entire pool size, + memcpy(pool, source.pool, sizeof(T) * buffersize); //... but only copy used pool objects + return *this; + } + + array(const array &source) : pool(0), poolsize(0), buffersize(0) { + operator=(source); + } + + //move + array& operator=(array &&source) { + if(pool) free(pool); + pool = source.pool; + poolsize = source.poolsize; + buffersize = source.buffersize; + source.pool = 0; + source.reset(); + return *this; + } + + array(array &&source) : pool(0), poolsize(0), buffersize(0) { + operator=(std::move(source)); + } + + //index + inline T& operator[](unsigned index) { + if(index >= buffersize) resize(index + 1); + if(index >= buffersize) throw "array[] out of bounds"; + return pool[index]; + } + + inline const T& operator[](unsigned index) const { + if(index >= buffersize) throw "array[] out of bounds"; + return pool[index]; + } + }; + + template struct has_size> { enum { value = true }; }; +} + +#endif diff --git a/nall/base64.hpp b/nall/base64.hpp new file mode 100755 index 00000000..e41c87b7 --- /dev/null +++ b/nall/base64.hpp @@ -0,0 +1,90 @@ +#ifndef NALL_BASE64_HPP +#define NALL_BASE64_HPP + +#include +#include + +namespace nall { + class base64 { + public: + static bool encode(char *&output, const uint8_t* input, unsigned inlength) { + output = new char[inlength * 8 / 6 + 6](); + + unsigned i = 0, o = 0; + while(i < inlength) { + switch(i % 3) { + case 0: { + output[o++] = enc(input[i] >> 2); + output[o] = enc((input[i] & 3) << 4); + } break; + + case 1: { + uint8_t prev = dec(output[o]); + output[o++] = enc(prev + (input[i] >> 4)); + output[o] = enc((input[i] & 15) << 2); + } break; + + case 2: { + uint8_t prev = dec(output[o]); + output[o++] = enc(prev + (input[i] >> 6)); + output[o++] = enc(input[i] & 63); + } break; + } + + i++; + } + + return true; + } + + static bool decode(uint8_t *&output, unsigned &outlength, const char *input) { + unsigned inlength = strlen(input), infix = 0; + output = new uint8_t[inlength](); + + unsigned i = 0, o = 0; + while(i < inlength) { + uint8_t x = dec(input[i]); + + switch(i++ & 3) { + case 0: { + output[o] = x << 2; + } break; + + case 1: { + output[o++] |= x >> 4; + output[o] = (x & 15) << 4; + } break; + + case 2: { + output[o++] |= x >> 2; + output[o] = (x & 3) << 6; + } break; + + case 3: { + output[o++] |= x; + } break; + } + } + + outlength = o; + return true; + } + + private: + static char enc(uint8_t n) { + static char lookup_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + return lookup_table[n & 63]; + } + + static uint8_t dec(char n) { + if(n >= 'A' && n <= 'Z') return n - 'A'; + if(n >= 'a' && n <= 'z') return n - 'a' + 26; + if(n >= '0' && n <= '9') return n - '0' + 52; + if(n == '-') return 62; + if(n == '_') return 63; + return 0; + } + }; +} + +#endif diff --git a/nall/bit.hpp b/nall/bit.hpp new file mode 100755 index 00000000..169fc144 --- /dev/null +++ b/nall/bit.hpp @@ -0,0 +1,51 @@ +#ifndef NALL_BIT_HPP +#define NALL_BIT_HPP + +namespace nall { + template inline unsigned uclamp(const unsigned x) { + enum { y = (1U << bits) - 1 }; + return y + ((x - y) & -(x < y)); //min(x, y); + } + + template inline unsigned uclip(const unsigned x) { + enum { m = (1U << bits) - 1 }; + return (x & m); + } + + template inline signed sclamp(const signed x) { + enum { b = 1U << (bits - 1), m = (1U << (bits - 1)) - 1 }; + return (x > m) ? m : (x < -b) ? -b : x; + } + + template inline signed sclip(const signed x) { + enum { b = 1U << (bits - 1), m = (1U << bits) - 1 }; + return ((x & m) ^ b) - b; + } + + namespace bit { + //lowest(0b1110) == 0b0010 + template inline T lowest(const T x) { + return x & -x; + } + + //clear_lowest(0b1110) == 0b1100 + template inline T clear_lowest(const T x) { + return x & (x - 1); + } + + //set_lowest(0b0101) == 0b0111 + template inline T set_lowest(const T x) { + return x | (x + 1); + } + + //round up to next highest single bit: + //round(15) == 16, round(16) == 16, round(17) == 32 + inline unsigned round(unsigned x) { + if((x & (x - 1)) == 0) return x; + while(x & (x - 1)) x &= x - 1; + return x << 1; + } + } +} + +#endif diff --git a/nall/concept.hpp b/nall/concept.hpp new file mode 100755 index 00000000..47167e21 --- /dev/null +++ b/nall/concept.hpp @@ -0,0 +1,34 @@ +#ifndef NALL_CONCEPT_HPP +#define NALL_CONCEPT_HPP + +#include +#include + +namespace nall { + //unsigned count() const; + template struct has_count { enum { value = false }; }; + + //unsigned length() const; + template struct has_length { enum { value = false }; }; + + //unsigned size() const; + template struct has_size { enum { value = false }; }; + + template unsigned container_size(const T& object, typename mp_enable_if>::type = 0) { + return object.count(); + } + + template unsigned container_size(const T& object, typename mp_enable_if>::type = 0) { + return object.length(); + } + + template unsigned container_size(const T& object, typename mp_enable_if>::type = 0) { + return object.size(); + } + + template unsigned container_size(const T& object, typename mp_enable_if>::type = 0) { + return sizeof(T) / sizeof(typename std::remove_extent::type); + } +} + +#endif diff --git a/nall/config.hpp b/nall/config.hpp new file mode 100755 index 00000000..b8381b16 --- /dev/null +++ b/nall/config.hpp @@ -0,0 +1,123 @@ +#ifndef NALL_CONFIG_HPP +#define NALL_CONFIG_HPP + +#include +#include +#include + +namespace nall { + namespace configuration_traits { + template struct is_boolean { enum { value = false }; }; + template<> struct is_boolean { enum { value = true }; }; + + template struct is_signed { enum { value = false }; }; + template<> struct is_signed { enum { value = true }; }; + + template struct is_unsigned { enum { value = false }; }; + template<> struct is_unsigned { enum { value = true }; }; + + template struct is_double { enum { value = false }; }; + template<> struct is_double { enum { value = true }; }; + + template struct is_string { enum { value = false }; }; + template<> struct is_string { enum { value = true }; }; + } + + class configuration { + public: + enum type_t { boolean_t, signed_t, unsigned_t, double_t, string_t, unknown_t }; + struct item_t { + uintptr_t data; + string name; + string desc; + type_t type; + + string get() const { + switch(type) { + case boolean_t: return string() << *(bool*)data; + case signed_t: return string() << *(signed*)data; + case unsigned_t: return string() << *(unsigned*)data; + case double_t: return string() << *(double*)data; + case string_t: return string() << "\"" << *(string*)data << "\""; + } + return "???"; + } + + void set(string s) { + switch(type) { + case boolean_t: *(bool*)data = (s == "true"); break; + case signed_t: *(signed*)data = integer(s); break; + case unsigned_t: *(unsigned*)data = decimal(s); break; + case double_t: *(double*)data = fp(s); break; + case string_t: s.trim("\""); *(string*)data = s; break; + } + } + }; + linear_vector list; + + template + void attach(T &data, const char *name, const char *desc = "") { + unsigned n = list.size(); + list[n].data = (uintptr_t)&data; + list[n].name = name; + list[n].desc = desc; + + if(configuration_traits::is_boolean::value) list[n].type = boolean_t; + else if(configuration_traits::is_signed::value) list[n].type = signed_t; + else if(configuration_traits::is_unsigned::value) list[n].type = unsigned_t; + else if(configuration_traits::is_double::value) list[n].type = double_t; + else if(configuration_traits::is_string::value) list[n].type = string_t; + else list[n].type = unknown_t; + } + + virtual bool load(const char *filename) { + string data; + if(data.readfile(filename) == true) { + data.replace("\r", ""); + lstring line; + line.split("\n", data); + + for(unsigned i = 0; i < line.size(); i++) { + if(auto position = qstrpos(line[i], "#")) line[i][position()] = 0; + if(!qstrpos(line[i], " = ")) continue; + + lstring part; + part.qsplit(" = ", line[i]); + part[0].trim(); + part[1].trim(); + + for(unsigned n = 0; n < list.size(); n++) { + if(part[0] == list[n].name) { + list[n].set(part[1]); + break; + } + } + } + + return true; + } else { + return false; + } + } + + virtual bool save(const char *filename) const { + file fp; + if(fp.open(filename, file::mode::write)) { + for(unsigned i = 0; i < list.size(); i++) { + string output; + output << list[i].name << " = " << list[i].get(); + if(list[i].desc != "") output << " # " << list[i].desc; + output << "\r\n"; + fp.print(output); + } + + fp.close(); + return true; + } else { + return false; + } + } + }; +} + +#endif diff --git a/nall/crc32.hpp b/nall/crc32.hpp new file mode 100755 index 00000000..ad36fbf6 --- /dev/null +++ b/nall/crc32.hpp @@ -0,0 +1,66 @@ +#ifndef NALL_CRC32_HPP +#define NALL_CRC32_HPP + +#include + +namespace nall { + const uint32_t crc32_table[256] = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, + 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, + 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, + 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, + 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, + 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, + 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, + 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, + 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, + 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, + 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, + 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, + 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, + 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, + 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, + 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, + 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, + 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, + 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, + 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, + 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, + 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d + }; + + inline uint32_t crc32_adjust(uint32_t crc32, uint8_t input) { + return ((crc32 >> 8) & 0x00ffffff) ^ crc32_table[(crc32 ^ input) & 0xff]; + } + + inline uint32_t crc32_calculate(const uint8_t *data, unsigned length) { + uint32_t crc32 = ~0; + for(unsigned i = 0; i < length; i++) { + crc32 = crc32_adjust(crc32, data[i]); + } + return ~crc32; + } +} + +#endif diff --git a/nall/detect.hpp b/nall/detect.hpp new file mode 100755 index 00000000..b4991aaf --- /dev/null +++ b/nall/detect.hpp @@ -0,0 +1,30 @@ +#ifndef NALL_DETECT_HPP +#define NALL_DETECT_HPP + +/* Compiler detection */ + +#if defined(__GNUC__) + #define COMPILER_GCC +#elif defined(_MSC_VER) + #define COMPILER_VISUALC +#endif + +/* Platform detection */ + +#if defined(_WIN32) + #define PLATFORM_WIN +#elif defined(__APPLE__) + #define PLATFORM_OSX +#elif defined(linux) || defined(__sun__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) + #define PLATFORM_X +#endif + +/* Endian detection */ + +#if defined(__i386__) || defined(__amd64__) || defined(_M_IX86) || defined(_M_AMD64) + #define ARCH_LSB +#elif defined(__powerpc__) || defined(_M_PPC) || defined(__BIG_ENDIAN__) + #define ARCH_MSB +#endif + +#endif diff --git a/nall/dictionary.hpp b/nall/dictionary.hpp new file mode 100755 index 00000000..dcb04151 --- /dev/null +++ b/nall/dictionary.hpp @@ -0,0 +1,75 @@ +#ifndef NALL_DICTIONARY_HPP +#define NALL_DICTIONARY_HPP + +#include +#include +#include + +namespace nall { + class dictionary { + public: + string operator[](const char *input) { + for(unsigned i = 0; i < index_input.size(); i++) { + if(index_input[i] == input) return index_output[i]; + } + + //no match, use input; remove input identifier, if one exists + if(strbegin(input, "{{")) { + if(auto pos = strpos(input, "}}")) { + string temp = substr(input, pos() + 2); + return temp; + } + } + + return input; + } + + bool import(const char *filename) { + string data; + if(data.readfile(filename) == false) return false; + data.ltrim<1>("\xef\xbb\xbf"); //remove UTF-8 marker, if it exists + data.replace("\r", ""); + + lstring line; + line.split("\n", data); + for(unsigned i = 0; i < line.size(); i++) { + lstring part; + //format: "Input" = "Output" + part.qsplit("=", line[i]); + if(part.size() != 2) continue; + + //remove whitespace + part[0].trim(); + part[1].trim(); + + //remove quotes + part[0].trim<1>("\""); + part[1].trim<1>("\""); + + unsigned n = index_input.size(); + index_input[n] = part[0]; + index_output[n] = part[1]; + } + + return true; + } + + void reset() { + index_input.reset(); + index_output.reset(); + } + + ~dictionary() { + reset(); + } + + dictionary& operator=(const dictionary&) = delete; + dictionary(const dictionary&) = delete; + + protected: + lstring index_input; + lstring index_output; + }; +} + +#endif diff --git a/nall/directory.hpp b/nall/directory.hpp new file mode 100755 index 00000000..df0bf086 --- /dev/null +++ b/nall/directory.hpp @@ -0,0 +1,151 @@ +#ifndef NALL_DIRECTORY_HPP +#define NALL_DIRECTORY_HPP + +#include +#include +#include + +#if defined(_WIN32) + #include +#else + #include + #include + #include +#endif + +namespace nall { + +struct directory { + static bool exists(const string &pathname); + static lstring folders(const string &pathname, const string &pattern = "*"); + static lstring files(const string &pathname, const string &pattern = "*"); + static lstring contents(const string &pathname, const string &pattern = "*"); +}; + +#if defined(_WIN32) + inline bool directory::exists(const string &pathname) { + DWORD result = GetFileAttributes(utf16_t(pathname)); + if(result == INVALID_FILE_ATTRIBUTES) return false; + return (result & FILE_ATTRIBUTE_DIRECTORY); + } + + inline lstring directory::folders(const string &pathname, const string &pattern) { + lstring list; + string path = pathname; + path.transform("/", "\\"); + if(!strend(path, "\\")) path.append("\\"); + path.append("*"); + HANDLE handle; + WIN32_FIND_DATA data; + handle = FindFirstFile(utf16_t(path), &data); + if(handle != INVALID_HANDLE_VALUE) { + if(wcscmp(data.cFileName, L".") && wcscmp(data.cFileName, L"..")) { + if(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + string name = utf8_t(data.cFileName); + if(wildcard(name, pattern)) list.append(string(name, "/")); + } + } + while(FindNextFile(handle, &data) != false) { + if(wcscmp(data.cFileName, L".") && wcscmp(data.cFileName, L"..")) { + if(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + string name = utf8_t(data.cFileName); + if(wildcard(name, pattern)) list.append(string(name, "/")); + } + } + } + FindClose(handle); + } + if(list.size() > 0) sort(&list[0], list.size()); + return list; + } + + inline lstring directory::files(const string &pathname, const string &pattern) { + lstring list; + string path = pathname; + path.transform("/", "\\"); + if(!strend(path, "\\")) path.append("\\"); + path.append("*"); + HANDLE handle; + WIN32_FIND_DATA data; + handle = FindFirstFile(utf16_t(path), &data); + if(handle != INVALID_HANDLE_VALUE) { + if((data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) { + string name = utf8_t(data.cFileName); + if(wildcard(name, pattern)) list.append(name); + } + while(FindNextFile(handle, &data) != false) { + if((data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) { + string name = utf8_t(data.cFileName); + if(wildcard(name, pattern)) list.append(name); + } + } + FindClose(handle); + } + if(list.size() > 0) sort(&list[0], list.size()); + return list; + } + + inline lstring directory::contents(const string &pathname, const string &pattern) { + lstring folders = directory::folders(pathname); //pattern search of contents() should only filter files + lstring files = directory::files(pathname, pattern); + foreach(file, files) folders.append(file); + return folders; + } +#else + inline bool directory::exists(const string &pathname) { + DIR *dp = opendir(pathname); + if(!dp) return false; + closedir(dp); + return true; + } + + inline lstring directory::folders(const string &pathname, const string &pattern) { + lstring list; + DIR *dp; + struct dirent *ep; + dp = opendir(pathname); + if(dp) { + while(ep = readdir(dp)) { + if(!strcmp(ep->d_name, ".")) continue; + if(!strcmp(ep->d_name, "..")) continue; + if(ep->d_type & DT_DIR) { + if(wildcard(ep->d_name, pattern)) list.append(string(ep->d_name, "/")); + } + } + closedir(dp); + } + if(list.size() > 0) sort(&list[0], list.size()); + return list; + + } + + inline lstring directory::files(const string &pathname, const string &pattern) { + lstring list; + DIR *dp; + struct dirent *ep; + dp = opendir(pathname); + if(dp) { + while(ep = readdir(dp)) { + if(!strcmp(ep->d_name, ".")) continue; + if(!strcmp(ep->d_name, "..")) continue; + if((ep->d_type & DT_DIR) == 0) { + if(wildcard(ep->d_name, pattern)) list.append(ep->d_name); + } + } + closedir(dp); + } + if(list.size() > 0) sort(&list[0], list.size()); + return list; + } + + inline lstring directory::contents(const string &pathname, const string &pattern) { + lstring folders = directory::folders(pathname); //pattern search of contents() should only filter files + lstring files = directory::files(pathname, pattern); + foreach(file, files) folders.append(file); + return folders; + } +#endif + +} + +#endif diff --git a/nall/dl.hpp b/nall/dl.hpp new file mode 100755 index 00000000..ebfa5585 --- /dev/null +++ b/nall/dl.hpp @@ -0,0 +1,115 @@ +#ifndef NALL_DL_HPP +#define NALL_DL_HPP + +//dynamic linking support + +#include +#include +#include +#include + +#if defined(PLATFORM_X) || defined(PLATFORM_OSX) + #include +#elif defined(PLATFORM_WIN) + #include + #include +#endif + +namespace nall { + struct library { + bool opened() const { return handle; } + bool open(const char*, const char* = ""); + bool open_absolute(const char*); + void* sym(const char*); + void close(); + + library() : handle(0) {} + ~library() { close(); } + + library& operator=(const library&) = delete; + library(const library&) = delete; + + private: + uintptr_t handle; + }; + + #if defined(PLATFORM_X) + inline bool library::open(const char *name, const char *path) { + if(handle) close(); + handle = (uintptr_t)dlopen(string(path, *path && !strend(path, "/") ? "/" : "", "lib", name, ".so"), RTLD_LAZY); + if(!handle) handle = (uintptr_t)dlopen(string("/usr/local/lib/lib", name, ".so"), RTLD_LAZY); + return handle; + } + + inline bool library::open_absolute(const char *name) { + if(handle) close(); + handle = (uintptr_t)dlopen(name, RTLD_LAZY); + return handle; + } + + inline void* library::sym(const char *name) { + if(!handle) return 0; + return dlsym((void*)handle, name); + } + + inline void library::close() { + if(!handle) return; + dlclose((void*)handle); + handle = 0; + } + #elif defined(PLATFORM_OSX) + inline bool library::open(const char *name, const char *path) { + if(handle) close(); + handle = (uintptr_t)dlopen(string(path, *path && !strend(path, "/") ? "/" : "", "lib", name, ".dylib"), RTLD_LAZY); + if(!handle) handle = (uintptr_t)dlopen(string("/usr/local/lib/lib", name, ".dylib"), RTLD_LAZY); + return handle; + } + + inline bool library::open_absolute(const char *name) { + if(handle) close(); + handle = (uintptr_t)dlopen(name, RTLD_LAZY); + return handle; + } + + inline void* library::sym(const char *name) { + if(!handle) return 0; + return dlsym((void*)handle, name); + } + + inline void library::close() { + if(!handle) return; + dlclose((void*)handle); + handle = 0; + } + #elif defined(PLATFORM_WIN) + inline bool library::open(const char *name, const char *path) { + if(handle) close(); + string filepath(path, *path && !strend(path, "/") && !strend(path, "\\") ? "\\" : "", name, ".dll"); + handle = (uintptr_t)LoadLibraryW(utf16_t(filepath)); + return handle; + } + + inline bool library::open_absolute(const char *name) { + if(handle) close(); + handle = (uintptr_t)LoadLibraryW(utf16_t(name)); + return handle; + } + + inline void* library::sym(const char *name) { + if(!handle) return 0; + return (void*)GetProcAddress((HMODULE)handle, name); + } + + inline void library::close() { + if(!handle) return; + FreeLibrary((HMODULE)handle); + handle = 0; + } + #else + inline bool library::open(const char*, const char*) { return false; } + inline void* library::sym(const char*) { return 0; } + inline void library::close() {} + #endif +}; + +#endif diff --git a/nall/endian.hpp b/nall/endian.hpp new file mode 100755 index 00000000..40d15633 --- /dev/null +++ b/nall/endian.hpp @@ -0,0 +1,38 @@ +#ifndef NALL_ENDIAN_HPP +#define NALL_ENDIAN_HPP + +#if !defined(ARCH_MSB) + //little-endian: uint8_t[] { 0x01, 0x02, 0x03, 0x04 } == 0x04030201 + #define order_lsb2(a,b) a,b + #define order_lsb3(a,b,c) a,b,c + #define order_lsb4(a,b,c,d) a,b,c,d + #define order_lsb5(a,b,c,d,e) a,b,c,d,e + #define order_lsb6(a,b,c,d,e,f) a,b,c,d,e,f + #define order_lsb7(a,b,c,d,e,f,g) a,b,c,d,e,f,g + #define order_lsb8(a,b,c,d,e,f,g,h) a,b,c,d,e,f,g,h + #define order_msb2(a,b) b,a + #define order_msb3(a,b,c) c,b,a + #define order_msb4(a,b,c,d) d,c,b,a + #define order_msb5(a,b,c,d,e) e,d,c,b,a + #define order_msb6(a,b,c,d,e,f) f,e,d,c,b,a + #define order_msb7(a,b,c,d,e,f,g) g,f,e,d,c,b,a + #define order_msb8(a,b,c,d,e,f,g,h) h,g,f,e,d,c,b,a +#else + //big-endian: uint8_t[] { 0x01, 0x02, 0x03, 0x04 } == 0x01020304 + #define order_lsb2(a,b) b,a + #define order_lsb3(a,b,c) c,b,a + #define order_lsb4(a,b,c,d) d,c,b,a + #define order_lsb5(a,b,c,d,e) e,d,c,b,a + #define order_lsb6(a,b,c,d,e,f) f,e,d,c,b,a + #define order_lsb7(a,b,c,d,e,f,g) g,f,e,d,c,b,a + #define order_lsb8(a,b,c,d,e,f,g,h) h,g,f,e,d,c,b,a + #define order_msb2(a,b) a,b + #define order_msb3(a,b,c) a,b,c + #define order_msb4(a,b,c,d) a,b,c,d + #define order_msb5(a,b,c,d,e) a,b,c,d,e + #define order_msb6(a,b,c,d,e,f) a,b,c,d,e,f + #define order_msb7(a,b,c,d,e,f,g) a,b,c,d,e,f,g + #define order_msb8(a,b,c,d,e,f,g,h) a,b,c,d,e,f,g,h +#endif + +#endif diff --git a/nall/file.hpp b/nall/file.hpp new file mode 100755 index 00000000..103c7d4a --- /dev/null +++ b/nall/file.hpp @@ -0,0 +1,261 @@ +#ifndef NALL_FILE_HPP +#define NALL_FILE_HPP + +#include +#include + +#if !defined(_WIN32) + #include +#else + #include +#endif + +#include +#include +#include +#include + +namespace nall { + inline FILE* fopen_utf8(const char *utf8_filename, const char *mode) { + #if !defined(_WIN32) + return fopen(utf8_filename, mode); + #else + return _wfopen(utf16_t(utf8_filename), utf16_t(mode)); + #endif + } + + class file { + public: + enum class mode : unsigned { read, write, readwrite, writeread }; + enum class index : unsigned { absolute, relative }; + + uint8_t read() { + if(!fp) return 0xff; //file not open + if(file_mode == mode::write) return 0xff; //reads not permitted + if(file_offset >= file_size) return 0xff; //cannot read past end of file + buffer_sync(); + return buffer[(file_offset++) & buffer_mask]; + } + + uintmax_t readl(unsigned length = 1) { + uintmax_t data = 0; + for(int i = 0; i < length; i++) { + data |= (uintmax_t)read() << (i << 3); + } + return data; + } + + uintmax_t readm(unsigned length = 1) { + uintmax_t data = 0; + while(length--) { + data <<= 8; + data |= read(); + } + return data; + } + + void read(uint8_t *buffer, unsigned length) { + while(length--) *buffer++ = read(); + } + + void write(uint8_t data) { + if(!fp) return; //file not open + if(file_mode == mode::read) return; //writes not permitted + buffer_sync(); + buffer[(file_offset++) & buffer_mask] = data; + buffer_dirty = true; + if(file_offset > file_size) file_size = file_offset; + } + + void writel(uintmax_t data, unsigned length = 1) { + while(length--) { + write(data); + data >>= 8; + } + } + + void writem(uintmax_t data, unsigned length = 1) { + for(int i = length - 1; i >= 0; i--) { + write(data >> (i << 3)); + } + } + + void write(const uint8_t *buffer, unsigned length) { + while(length--) write(*buffer++); + } + + template void print(Args... args) { + string data(args...); + const char *p = data; + while(*p) write(*p++); + } + + void flush() { + buffer_flush(); + fflush(fp); + } + + void seek(int offset, index index_ = index::absolute) { + if(!fp) return; //file not open + buffer_flush(); + + uintmax_t req_offset = file_offset; + switch(index_) { + case index::absolute: req_offset = offset; break; + case index::relative: req_offset += offset; break; + } + + if(req_offset < 0) req_offset = 0; //cannot seek before start of file + if(req_offset > file_size) { + if(file_mode == mode::read) { //cannot seek past end of file + req_offset = file_size; + } else { //pad file to requested location + file_offset = file_size; + while(file_size < req_offset) write(0x00); + } + } + + file_offset = req_offset; + } + + int offset() { + if(!fp) return -1; //file not open + return file_offset; + } + + int size() { + if(!fp) return -1; //file not open + return file_size; + } + + bool truncate(unsigned size) { + if(!fp) return false; //file not open + #if !defined(_WIN32) + return ftruncate(fileno(fp), size) == 0; + #else + return _chsize(fileno(fp), size) == 0; + #endif + } + + bool end() { + if(!fp) return true; //file not open + return file_offset >= file_size; + } + + static bool exists(const char *fn) { + #if !defined(_WIN32) + FILE *fp = fopen(fn, "rb"); + #else + FILE *fp = _wfopen(utf16_t(fn), L"rb"); + #endif + if(fp) { + fclose(fp); + return true; + } + return false; + } + + static unsigned size(const char *fn) { + #if !defined(_WIN32) + FILE *fp = fopen(fn, "rb"); + #else + FILE *fp = _wfopen(utf16_t(fn), L"rb"); + #endif + unsigned filesize = 0; + if(fp) { + fseek(fp, 0, SEEK_END); + filesize = ftell(fp); + fclose(fp); + } + return filesize; + } + + bool open() { + return fp; + } + + bool open(const char *fn, mode mode_) { + if(fp) return false; + + switch(file_mode = mode_) { + #if !defined(_WIN32) + case mode::read: fp = fopen(fn, "rb"); break; + case mode::write: fp = fopen(fn, "wb+"); break; //need read permission for buffering + case mode::readwrite: fp = fopen(fn, "rb+"); break; + case mode::writeread: fp = fopen(fn, "wb+"); break; + #else + case mode::read: fp = _wfopen(utf16_t(fn), L"rb"); break; + case mode::write: fp = _wfopen(utf16_t(fn), L"wb+"); break; + case mode::readwrite: fp = _wfopen(utf16_t(fn), L"rb+"); break; + case mode::writeread: fp = _wfopen(utf16_t(fn), L"wb+"); break; + #endif + } + if(!fp) return false; + buffer_offset = -1; //invalidate buffer + file_offset = 0; + fseek(fp, 0, SEEK_END); + file_size = ftell(fp); + fseek(fp, 0, SEEK_SET); + return true; + } + + void close() { + if(!fp) return; + buffer_flush(); + fclose(fp); + fp = 0; + } + + file() { + memset(buffer, 0, sizeof buffer); + buffer_offset = -1; + buffer_dirty = false; + fp = 0; + file_offset = 0; + file_size = 0; + file_mode = mode::read; + } + + ~file() { + close(); + } + + file& operator=(const file&) = delete; + file(const file&) = delete; + + private: + enum { buffer_size = 1 << 12, buffer_mask = buffer_size - 1 }; + char buffer[buffer_size]; + int buffer_offset; + bool buffer_dirty; + FILE *fp; + unsigned file_offset; + unsigned file_size; + mode file_mode; + + void buffer_sync() { + if(!fp) return; //file not open + if(buffer_offset != (file_offset & ~buffer_mask)) { + buffer_flush(); + buffer_offset = file_offset & ~buffer_mask; + fseek(fp, buffer_offset, SEEK_SET); + unsigned length = (buffer_offset + buffer_size) <= file_size ? buffer_size : (file_size & buffer_mask); + if(length) unsigned unused = fread(buffer, 1, length, fp); + } + } + + void buffer_flush() { + if(!fp) return; //file not open + if(file_mode == mode::read) return; //buffer cannot be written to + if(buffer_offset < 0) return; //buffer unused + if(buffer_dirty == false) return; //buffer unmodified since read + fseek(fp, buffer_offset, SEEK_SET); + unsigned length = (buffer_offset + buffer_size) <= file_size ? buffer_size : (file_size & buffer_mask); + if(length) unsigned unused = fwrite(buffer, 1, length, fp); + buffer_offset = -1; //invalidate buffer + buffer_dirty = false; + } + }; +} + +#endif diff --git a/nall/filemap.hpp b/nall/filemap.hpp new file mode 100755 index 00000000..52acb2fa --- /dev/null +++ b/nall/filemap.hpp @@ -0,0 +1,200 @@ +#ifndef NALL_FILEMAP_HPP +#define NALL_FILEMAP_HPP + +#include +#include + +#include +#include +#if defined(_WIN32) + #include +#else + #include + #include + #include + #include + #include +#endif + +namespace nall { + class filemap { + public: + enum class mode : unsigned { read, write, readwrite, writeread }; + + bool opened() const { return p_opened(); } + bool open(const char *filename, mode mode_) { return p_open(filename, mode_); } + void close() { return p_close(); } + unsigned size() const { return p_size; } + uint8_t* data() { return p_handle; } + const uint8_t* data() const { return p_handle; } + filemap() : p_size(0), p_handle(0) { p_ctor(); } + filemap(const char *filename, mode mode_) : p_size(0), p_handle(0) { p_ctor(); p_open(filename, mode_); } + ~filemap() { p_dtor(); } + + private: + unsigned p_size; + uint8_t *p_handle; + + #if defined(_WIN32) + //============= + //MapViewOfFile + //============= + + HANDLE p_filehandle, p_maphandle; + + bool p_opened() const { + return p_handle; + } + + bool p_open(const char *filename, mode mode_) { + int desired_access, creation_disposition, flprotect, map_access; + + switch(mode_) { + default: return false; + case mode::read: + desired_access = GENERIC_READ; + creation_disposition = OPEN_EXISTING; + flprotect = PAGE_READONLY; + map_access = FILE_MAP_READ; + break; + case mode::write: + //write access requires read access + desired_access = GENERIC_WRITE; + creation_disposition = CREATE_ALWAYS; + flprotect = PAGE_READWRITE; + map_access = FILE_MAP_ALL_ACCESS; + break; + case mode::readwrite: + desired_access = GENERIC_READ | GENERIC_WRITE; + creation_disposition = OPEN_EXISTING; + flprotect = PAGE_READWRITE; + map_access = FILE_MAP_ALL_ACCESS; + break; + case mode::writeread: + desired_access = GENERIC_READ | GENERIC_WRITE; + creation_disposition = CREATE_NEW; + flprotect = PAGE_READWRITE; + map_access = FILE_MAP_ALL_ACCESS; + break; + } + + p_filehandle = CreateFileW(utf16_t(filename), desired_access, FILE_SHARE_READ, NULL, + creation_disposition, FILE_ATTRIBUTE_NORMAL, NULL); + if(p_filehandle == INVALID_HANDLE_VALUE) return false; + + p_size = GetFileSize(p_filehandle, NULL); + + p_maphandle = CreateFileMapping(p_filehandle, NULL, flprotect, 0, p_size, NULL); + if(p_maphandle == INVALID_HANDLE_VALUE) { + CloseHandle(p_filehandle); + p_filehandle = INVALID_HANDLE_VALUE; + return false; + } + + p_handle = (uint8_t*)MapViewOfFile(p_maphandle, map_access, 0, 0, p_size); + return p_handle; + } + + void p_close() { + if(p_handle) { + UnmapViewOfFile(p_handle); + p_handle = 0; + } + + if(p_maphandle != INVALID_HANDLE_VALUE) { + CloseHandle(p_maphandle); + p_maphandle = INVALID_HANDLE_VALUE; + } + + if(p_filehandle != INVALID_HANDLE_VALUE) { + CloseHandle(p_filehandle); + p_filehandle = INVALID_HANDLE_VALUE; + } + } + + void p_ctor() { + p_filehandle = INVALID_HANDLE_VALUE; + p_maphandle = INVALID_HANDLE_VALUE; + } + + void p_dtor() { + close(); + } + + #else + //==== + //mmap + //==== + + int p_fd; + + bool p_opened() const { + return p_handle; + } + + bool p_open(const char *filename, mode mode_) { + int open_flags, mmap_flags; + + switch(mode_) { + default: return false; + case mode::read: + open_flags = O_RDONLY; + mmap_flags = PROT_READ; + break; + case mode::write: + open_flags = O_RDWR | O_CREAT; //mmap() requires read access + mmap_flags = PROT_WRITE; + break; + case mode::readwrite: + open_flags = O_RDWR; + mmap_flags = PROT_READ | PROT_WRITE; + break; + case mode::writeread: + open_flags = O_RDWR | O_CREAT; + mmap_flags = PROT_READ | PROT_WRITE; + break; + } + + p_fd = ::open(filename, open_flags, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); + if(p_fd < 0) return false; + + struct stat p_stat; + fstat(p_fd, &p_stat); + p_size = p_stat.st_size; + + p_handle = (uint8_t*)mmap(0, p_size, mmap_flags, MAP_SHARED, p_fd, 0); + if(p_handle == MAP_FAILED) { + p_handle = 0; + ::close(p_fd); + p_fd = -1; + return false; + } + + return p_handle; + } + + void p_close() { + if(p_handle) { + munmap(p_handle, p_size); + p_handle = 0; + } + + if(p_fd >= 0) { + ::close(p_fd); + p_fd = -1; + } + } + + void p_ctor() { + p_fd = -1; + } + + void p_dtor() { + p_close(); + } + + #endif + }; +} + +#endif diff --git a/nall/foreach.hpp b/nall/foreach.hpp new file mode 100755 index 00000000..00a039f3 --- /dev/null +++ b/nall/foreach.hpp @@ -0,0 +1,12 @@ +#ifndef NALL_FOREACH_HPP +#define NALL_FOREACH_HPP + +#include +#include + +#undef foreach +#define foreach(iter, object) \ + for(unsigned foreach_counter = 0, foreach_limit = container_size(object), foreach_once = 0, foreach_broken = 0; foreach_counter < foreach_limit && foreach_broken == 0; foreach_counter++, foreach_once = 0) \ + for(auto &iter = object[foreach_counter]; foreach_once == 0 && (foreach_broken = 1); foreach_once++, foreach_broken = 0) + +#endif diff --git a/nall/function.hpp b/nall/function.hpp new file mode 100755 index 00000000..645991fb --- /dev/null +++ b/nall/function.hpp @@ -0,0 +1,60 @@ +#ifndef NALL_FUNCTION_HPP +#define NALL_FUNCTION_HPP + +namespace nall { + template class function; + + template class function { + struct container { + virtual R operator()(P... p) const = 0; + virtual container* copy() const = 0; + virtual ~container() {} + } *callback; + + struct global : container { + R (*function)(P...); + R operator()(P... p) const { return function(std::forward

(p)...); } + container* copy() const { return new global(function); } + global(R (*function)(P...)) : function(function) {} + }; + + template struct member : container { + R (C::*function)(P...); + C *object; + R operator()(P... p) const { return (object->*function)(std::forward

(p)...); } + container* copy() const { return new member(function, object); } + member(R (C::*function)(P...), C *object) : function(function), object(object) {} + }; + + template struct lambda : container { + L object; + R operator()(P... p) const { return object(std::forward

(p)...); } + container* copy() const { return new lambda(object); } + lambda(const L& object) : object(object) {} + }; + + public: + operator bool() const { return callback; } + R operator()(P... p) const { return (*callback)(std::forward

(p)...); } + void reset() { if(callback) { delete callback; callback = 0; } } + + function& operator=(const function &source) { + if(this != &source) { + if(callback) { delete callback; callback = 0; } + if(source.callback) callback = source.callback->copy(); + } + return *this; + } + + function(const function &source) { operator=(source); } + function() : callback(0) {} + function(void *function) : callback(0) { if(function) callback = new global((R (*)(P...))function); } + function(R (*function)(P...)) { callback = new global(function); } + template function(R (C::*function)(P...), C *object) { callback = new member(function, object); } + template function(R (C::*function)(P...) const, C *object) { callback = new member((R (C::*)(P...))function, object); } + template function(const L& object) { callback = new lambda(object); } + ~function() { if(callback) delete callback; } + }; +} + +#endif diff --git a/nall/input.hpp b/nall/input.hpp new file mode 100755 index 00000000..1fd680f4 --- /dev/null +++ b/nall/input.hpp @@ -0,0 +1,386 @@ +#ifndef NALL_INPUT_HPP +#define NALL_INPUT_HPP + +#include +#include +#include + +#include +#include + +namespace nall { + +struct Keyboard; +Keyboard& keyboard(unsigned = 0); + +static const char KeyboardScancodeName[][64] = { + "Escape", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", + "PrintScreen", "ScrollLock", "Pause", "Tilde", + "Num1", "Num2", "Num3", "Num4", "Num5", "Num6", "Num7", "Num8", "Num9", "Num0", + "Dash", "Equal", "Backspace", + "Insert", "Delete", "Home", "End", "PageUp", "PageDown", + "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", + "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", + "LeftBracket", "RightBracket", "Backslash", "Semicolon", "Apostrophe", "Comma", "Period", "Slash", + "Keypad1", "Keypad2", "Keypad3", "Keypad4", "Keypad5", "Keypad6", "Keypad7", "Keypad8", "Keypad9", "Keypad0", + "Point", "Enter", "Add", "Subtract", "Multiply", "Divide", + "NumLock", "CapsLock", + "Up", "Down", "Left", "Right", + "Tab", "Return", "Spacebar", "Menu", + "Shift", "Control", "Alt", "Super", +}; + +struct Keyboard { + const unsigned ID; + enum { Base = 1 }; + enum { Count = 8, Size = 128 }; + + enum Scancode { + Escape, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, + PrintScreen, ScrollLock, Pause, Tilde, + Num1, Num2, Num3, Num4, Num5, Num6, Num7, Num8, Num9, Num0, + Dash, Equal, Backspace, + Insert, Delete, Home, End, PageUp, PageDown, + A, B, C, D, E, F, G, H, I, J, K, L, M, + N, O, P, Q, R, S, T, U, V, W, X, Y, Z, + LeftBracket, RightBracket, Backslash, Semicolon, Apostrophe, Comma, Period, Slash, + Keypad1, Keypad2, Keypad3, Keypad4, Keypad5, Keypad6, Keypad7, Keypad8, Keypad9, Keypad0, + Point, Enter, Add, Subtract, Multiply, Divide, + NumLock, CapsLock, + Up, Down, Left, Right, + Tab, Return, Spacebar, Menu, + Shift, Control, Alt, Super, + Limit, + }; + + static signed numberDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(keyboard(i).belongsTo(scancode)) return i; + } + return -1; + } + + static signed keyDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(keyboard(i).isKey(scancode)) return scancode - keyboard(i).key(Escape); + } + return -1; + } + + static signed modifierDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(keyboard(i).isModifier(scancode)) return scancode - keyboard(i).key(Shift); + } + return -1; + } + + static bool isAnyKey(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(keyboard(i).isKey(scancode)) return true; + } + return false; + } + + static bool isAnyModifier(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(keyboard(i).isModifier(scancode)) return true; + } + return false; + } + + static uint16_t decode(const char *name) { + string s(name); + if(!strbegin(name, "KB")) return 0; + s.ltrim("KB"); + unsigned id = decimal(s); + auto pos = strpos(s, "::"); + if(!pos) return 0; + s = substr(s, pos() + 2); + for(unsigned i = 0; i < Limit; i++) { + if(s == KeyboardScancodeName[i]) return Base + Size * id + i; + } + return 0; + } + + string encode(uint16_t code) const { + unsigned index = 0; + for(unsigned i = 0; i < Count; i++) { + if(code >= Base + Size * i && code < Base + Size * (i + 1)) { + index = code - (Base + Size * i); + break; + } + } + return string() << "KB" << ID << "::" << KeyboardScancodeName[index]; + } + + uint16_t operator[](Scancode code) const { return Base + ID * Size + code; } + uint16_t key(unsigned id) const { return Base + Size * ID + id; } + bool isKey(unsigned id) const { return id >= key(Escape) && id <= key(Menu); } + bool isModifier(unsigned id) const { return id >= key(Shift) && id <= key(Super); } + bool belongsTo(uint16_t scancode) const { return isKey(scancode) || isModifier(scancode); } + + Keyboard(unsigned ID_) : ID(ID_) {} +}; + +inline Keyboard& keyboard(unsigned id) { + static Keyboard kb0(0), kb1(1), kb2(2), kb3(3), kb4(4), kb5(5), kb6(6), kb7(7); + switch(id) { default: + case 0: return kb0; case 1: return kb1; case 2: return kb2; case 3: return kb3; + case 4: return kb4; case 5: return kb5; case 6: return kb6; case 7: return kb7; + } +} + +static const char MouseScancodeName[][64] = { + "Xaxis", "Yaxis", "Zaxis", + "Button0", "Button1", "Button2", "Button3", "Button4", "Button5", "Button6", "Button7", +}; + +struct Mouse; +Mouse& mouse(unsigned = 0); + +struct Mouse { + const unsigned ID; + enum { Base = Keyboard::Base + Keyboard::Size * Keyboard::Count }; + enum { Count = 8, Size = 16 }; + enum { Axes = 3, Buttons = 8 }; + + enum Scancode { + Xaxis, Yaxis, Zaxis, + Button0, Button1, Button2, Button3, Button4, Button5, Button6, Button7, + Limit, + }; + + static signed numberDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(mouse(i).belongsTo(scancode)) return i; + } + return -1; + } + + static signed axisDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(mouse(i).isAxis(scancode)) return scancode - mouse(i).axis(0); + } + return -1; + } + + static signed buttonDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(mouse(i).isButton(scancode)) return scancode - mouse(i).button(0); + } + return -1; + } + + static bool isAnyAxis(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(mouse(i).isAxis(scancode)) return true; + } + return false; + } + + static bool isAnyButton(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(mouse(i).isButton(scancode)) return true; + } + return false; + } + + static uint16_t decode(const char *name) { + string s(name); + if(!strbegin(name, "MS")) return 0; + s.ltrim("MS"); + unsigned id = decimal(s); + auto pos = strpos(s, "::"); + if(!pos) return 0; + s = substr(s, pos() + 2); + for(unsigned i = 0; i < Limit; i++) { + if(s == MouseScancodeName[i]) return Base + Size * id + i; + } + return 0; + } + + string encode(uint16_t code) const { + unsigned index = 0; + for(unsigned i = 0; i < Count; i++) { + if(code >= Base + Size * i && code < Base + Size * (i + 1)) { + index = code - (Base + Size * i); + break; + } + } + return string() << "MS" << ID << "::" << MouseScancodeName[index]; + } + + uint16_t operator[](Scancode code) const { return Base + ID * Size + code; } + uint16_t axis(unsigned id) const { return Base + Size * ID + Xaxis + id; } + uint16_t button(unsigned id) const { return Base + Size * ID + Button0 + id; } + bool isAxis(unsigned id) const { return id >= axis(0) && id <= axis(2); } + bool isButton(unsigned id) const { return id >= button(0) && id <= button(7); } + bool belongsTo(uint16_t scancode) const { return isAxis(scancode) || isButton(scancode); } + + Mouse(unsigned ID_) : ID(ID_) {} +}; + +inline Mouse& mouse(unsigned id) { + static Mouse ms0(0), ms1(1), ms2(2), ms3(3), ms4(4), ms5(5), ms6(6), ms7(7); + switch(id) { default: + case 0: return ms0; case 1: return ms1; case 2: return ms2; case 3: return ms3; + case 4: return ms4; case 5: return ms5; case 6: return ms6; case 7: return ms7; + } +} + +static const char JoypadScancodeName[][64] = { + "Hat0", "Hat1", "Hat2", "Hat3", "Hat4", "Hat5", "Hat6", "Hat7", + "Axis0", "Axis1", "Axis2", "Axis3", "Axis4", "Axis5", "Axis6", "Axis7", + "Axis8", "Axis9", "Axis10", "Axis11", "Axis12", "Axis13", "Axis14", "Axis15", + "Button0", "Button1", "Button2", "Button3", "Button4", "Button5", "Button6", "Button7", + "Button8", "Button9", "Button10", "Button11", "Button12", "Button13", "Button14", "Button15", + "Button16", "Button17", "Button18", "Button19", "Button20", "Button21", "Button22", "Button23", + "Button24", "Button25", "Button26", "Button27", "Button28", "Button29", "Button30", "Button31", +}; + +struct Joypad; +Joypad& joypad(unsigned = 0); + +struct Joypad { + const unsigned ID; + enum { Base = Mouse::Base + Mouse::Size * Mouse::Count }; + enum { Count = 8, Size = 64 }; + enum { Hats = 8, Axes = 16, Buttons = 32 }; + + enum Scancode { + Hat0, Hat1, Hat2, Hat3, Hat4, Hat5, Hat6, Hat7, + Axis0, Axis1, Axis2, Axis3, Axis4, Axis5, Axis6, Axis7, + Axis8, Axis9, Axis10, Axis11, Axis12, Axis13, Axis14, Axis15, + Button0, Button1, Button2, Button3, Button4, Button5, Button6, Button7, + Button8, Button9, Button10, Button11, Button12, Button13, Button14, Button15, + Button16, Button17, Button18, Button19, Button20, Button21, Button22, Button23, + Button24, Button25, Button26, Button27, Button28, Button29, Button30, Button31, + Limit, + }; + + enum Hat { HatCenter = 0, HatUp = 1, HatRight = 2, HatDown = 4, HatLeft = 8 }; + + static signed numberDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(joypad(i).belongsTo(scancode)) return i; + } + return -1; + } + + static signed hatDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(joypad(i).isHat(scancode)) return scancode - joypad(i).hat(0); + } + return -1; + } + + static signed axisDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(joypad(i).isAxis(scancode)) return scancode - joypad(i).axis(0); + } + return -1; + } + + static signed buttonDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(joypad(i).isButton(scancode)) return scancode - joypad(i).button(0); + } + return -1; + } + + static bool isAnyHat(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(joypad(i).isHat(scancode)) return true; + } + return false; + } + + static bool isAnyAxis(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(joypad(i).isAxis(scancode)) return true; + } + return false; + } + + static bool isAnyButton(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(joypad(i).isButton(scancode)) return true; + } + return false; + } + + static uint16_t decode(const char *name) { + string s(name); + if(!strbegin(name, "JP")) return 0; + s.ltrim("JP"); + unsigned id = decimal(s); + auto pos = strpos(s, "::"); + if(!pos) return 0; + s = substr(s, pos() + 2); + for(unsigned i = 0; i < Limit; i++) { + if(s == JoypadScancodeName[i]) return Base + Size * id + i; + } + return 0; + } + + string encode(uint16_t code) const { + unsigned index = 0; + for(unsigned i = 0; i < Count; i++) { + if(code >= Base + Size * i && code < Base + Size * (i + 1)) { + index = code - (Base + Size * i); + } + } + return string() << "JP" << ID << "::" << JoypadScancodeName[index]; + } + + uint16_t operator[](Scancode code) const { return Base + ID * Size + code; } + uint16_t hat(unsigned id) const { return Base + Size * ID + Hat0 + id; } + uint16_t axis(unsigned id) const { return Base + Size * ID + Axis0 + id; } + uint16_t button(unsigned id) const { return Base + Size * ID + Button0 + id; } + bool isHat(unsigned id) const { return id >= hat(0) && id <= hat(7); } + bool isAxis(unsigned id) const { return id >= axis(0) && id <= axis(15); } + bool isButton(unsigned id) const { return id >= button(0) && id <= button(31); } + bool belongsTo(uint16_t scancode) const { return isHat(scancode) || isAxis(scancode) || isButton(scancode); } + + Joypad(unsigned ID_) : ID(ID_) {} +}; + +inline Joypad& joypad(unsigned id) { + static Joypad jp0(0), jp1(1), jp2(2), jp3(3), jp4(4), jp5(5), jp6(6), jp7(7); + switch(id) { default: + case 0: return jp0; case 1: return jp1; case 2: return jp2; case 3: return jp3; + case 4: return jp4; case 5: return jp5; case 6: return jp6; case 7: return jp7; + } +} + +struct Scancode { + enum { None = 0, Limit = Joypad::Base + Joypad::Size * Joypad::Count }; + + static uint16_t decode(const char *name) { + uint16_t code; + code = Keyboard::decode(name); + if(code) return code; + code = Mouse::decode(name); + if(code) return code; + code = Joypad::decode(name); + if(code) return code; + return None; + } + + static string encode(uint16_t code) { + for(unsigned i = 0; i < Keyboard::Count; i++) { + if(keyboard(i).belongsTo(code)) return keyboard(i).encode(code); + } + for(unsigned i = 0; i < Mouse::Count; i++) { + if(mouse(i).belongsTo(code)) return mouse(i).encode(code); + } + for(unsigned i = 0; i < Joypad::Count; i++) { + if(joypad(i).belongsTo(code)) return joypad(i).encode(code); + } + return "None"; + } +}; + +} + +#endif diff --git a/nall/lzss.hpp b/nall/lzss.hpp new file mode 100755 index 00000000..202bc814 --- /dev/null +++ b/nall/lzss.hpp @@ -0,0 +1,81 @@ +#ifndef NALL_LZSS_HPP +#define NALL_LZSS_HPP + +#include +#include +#include + +namespace nall { + class lzss { + public: + static bool encode(uint8_t *&output, unsigned &outlength, const uint8_t *input, unsigned inlength) { + output = new(zeromemory) uint8_t[inlength * 9 / 8 + 9]; + + unsigned i = 0, o = 0; + while(i < inlength) { + unsigned flagoffset = o++; + uint8_t flag = 0x00; + + for(unsigned b = 0; b < 8 && i < inlength; b++) { + unsigned longest = 0, pointer; + for(unsigned index = 1; index < 4096; index++) { + unsigned count = 0; + while(true) { + if(count >= 15 + 3) break; //verify pattern match is not longer than max length + if(i + count >= inlength) break; //verify pattern match does not read past end of input + if(i + count < index) break; //verify read is not before start of input + if(input[i + count] != input[i + count - index]) break; //verify pattern still matches + count++; + } + + if(count > longest) { + longest = count; + pointer = index; + } + } + + if(longest < 3) output[o++] = input[i++]; + else { + flag |= 1 << b; + uint16_t x = ((longest - 3) << 12) + pointer; + output[o++] = x; + output[o++] = x >> 8; + i += longest; + } + } + + output[flagoffset] = flag; + } + + outlength = o; + return true; + } + + static bool decode(uint8_t *&output, const uint8_t *input, unsigned length) { + output = new(zeromemory) uint8_t[length]; + + unsigned i = 0, o = 0; + while(o < length) { + uint8_t flag = input[i++]; + + for(unsigned b = 0; b < 8 && o < length; b++) { + if(!(flag & (1 << b))) output[o++] = input[i++]; + else { + uint16_t offset = input[i++]; + offset += input[i++] << 8; + uint16_t lookuplength = (offset >> 12) + 3; + offset &= 4095; + for(unsigned index = 0; index < lookuplength && o + index < length; index++) { + output[o + index] = output[o + index - offset]; + } + o += lookuplength; + } + } + } + + return true; + } + }; +} + +#endif diff --git a/nall/moduloarray.hpp b/nall/moduloarray.hpp new file mode 100755 index 00000000..be549ae9 --- /dev/null +++ b/nall/moduloarray.hpp @@ -0,0 +1,40 @@ +#ifndef NALL_MODULO_HPP +#define NALL_MODULO_HPP + +#include + +namespace nall { + template class modulo_array { + public: + inline T operator[](int index) const { + return buffer[size + index]; + } + + inline T read(int index) const { + return buffer[size + index]; + } + + inline void write(unsigned index, const T value) { + buffer[index] = + buffer[index + size] = + buffer[index + size + size] = value; + } + + void serialize(serializer &s) { + s.array(buffer, size * 3); + } + + modulo_array() { + buffer = new T[size * 3](); + } + + ~modulo_array() { + delete[] buffer; + } + + private: + T *buffer; + }; +} + +#endif diff --git a/nall/platform.hpp b/nall/platform.hpp new file mode 100755 index 00000000..72eeec09 --- /dev/null +++ b/nall/platform.hpp @@ -0,0 +1,122 @@ +#ifndef NALL_PLATFORM_HPP +#define NALL_PLATFORM_HPP + +#include + +//========================= +//standard platform headers +//========================= + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(_WIN32) + #include + #include + #include + #undef interface + #define dllexport __declspec(dllexport) +#else + #include + #include + #include + #define dllexport +#endif + +//================== +//warning supression +//================== + +//Visual C++ +#if defined(_MSC_VER) + //disable libc "deprecation" warnings + #pragma warning(disable:4996) +#endif + +//================ +//POSIX compliance +//================ + +#if defined(_MSC_VER) + #define PATH_MAX _MAX_PATH + #define va_copy(dest, src) ((dest) = (src)) +#endif + +#if defined(_WIN32) + #define getcwd _getcwd + #define ftruncate _chsize + #define putenv _putenv + #define mkdir(n, m) _wmkdir(nall::utf16_t(n)) + #define rmdir _rmdir + #define vsnprintf _vsnprintf + #define usleep(n) Sleep(n / 1000) +#endif + +//================ +//inline expansion +//================ + +#if defined(__GNUC__) + #define noinline __attribute__((noinline)) + #define inline inline + #define alwaysinline inline __attribute__((always_inline)) +#elif defined(_MSC_VER) + #define noinline __declspec(noinline) + #define inline inline + #define alwaysinline inline __forceinline +#else + #define noinline + #define inline inline + #define alwaysinline inline +#endif + +//========================= +//file system functionality +//========================= + +#if defined(_WIN32) + inline char* realpath(const char *filename, char *resolvedname) { + wchar_t fn[_MAX_PATH] = L""; + _wfullpath(fn, nall::utf16_t(filename), _MAX_PATH); + strcpy(resolvedname, nall::utf8_t(fn)); + return resolvedname; + } + + inline char* userpath(char *path) { + wchar_t fp[_MAX_PATH] = L""; + SHGetFolderPathW(0, CSIDL_APPDATA | CSIDL_FLAG_CREATE, 0, 0, fp); + strcpy(path, nall::utf8_t(fp)); + return path; + } + + inline char* getcwd(char *path) { + wchar_t fp[_MAX_PATH] = L""; + _wgetcwd(fp, _MAX_PATH); + strcpy(path, nall::utf8_t(fp)); + return path; + } +#else + //realpath() already exists + + inline char* userpath(char *path) { + *path = 0; + struct passwd *userinfo = getpwuid(getuid()); + if(userinfo) strcpy(path, userinfo->pw_dir); + return path; + } + + inline char *getcwd(char *path) { + return getcwd(path, PATH_MAX); + } +#endif + +#endif + diff --git a/nall/priorityqueue.hpp b/nall/priorityqueue.hpp new file mode 100755 index 00000000..7104e791 --- /dev/null +++ b/nall/priorityqueue.hpp @@ -0,0 +1,109 @@ +#ifndef NALL_PRIORITYQUEUE_HPP +#define NALL_PRIORITYQUEUE_HPP + +#include +#include +#include +#include + +namespace nall { + template void priority_queue_nocallback(type_t) {} + + //priority queue implementation using binary min-heap array; + //does not require normalize() function. + //O(1) find (tick) + //O(log n) insert (enqueue) + //O(log n) remove (dequeue) + template class priority_queue { + public: + inline void tick(unsigned ticks) { + basecounter += ticks; + while(heapsize && gte(basecounter, heap[0].counter)) callback(dequeue()); + } + + //counter is relative to current time (eg enqueue(64, ...) fires in 64 ticks); + //counter cannot exceed std::numeric_limits::max() >> 1. + void enqueue(unsigned counter, type_t event) { + unsigned child = heapsize++; + counter += basecounter; + + while(child) { + unsigned parent = (child - 1) >> 1; + if(gte(counter, heap[parent].counter)) break; + + heap[child].counter = heap[parent].counter; + heap[child].event = heap[parent].event; + child = parent; + } + + heap[child].counter = counter; + heap[child].event = event; + } + + type_t dequeue() { + type_t event(heap[0].event); + unsigned parent = 0; + unsigned counter = heap[--heapsize].counter; + + while(true) { + unsigned child = (parent << 1) + 1; + if(child >= heapsize) break; + if(child + 1 < heapsize && gte(heap[child].counter, heap[child + 1].counter)) child++; + if(gte(heap[child].counter, counter)) break; + + heap[parent].counter = heap[child].counter; + heap[parent].event = heap[child].event; + parent = child; + } + + heap[parent].counter = counter; + heap[parent].event = heap[heapsize].event; + return event; + } + + void reset() { + basecounter = 0; + heapsize = 0; + } + + void serialize(serializer &s) { + s.integer(basecounter); + s.integer(heapsize); + for(unsigned n = 0; n < heapcapacity; n++) { + s.integer(heap[n].counter); + s.integer(heap[n].event); + } + } + + priority_queue(unsigned size, function callback_ = &priority_queue_nocallback) + : callback(callback_) { + heap = new heap_t[size]; + heapcapacity = size; + reset(); + } + + ~priority_queue() { + delete[] heap; + } + + priority_queue& operator=(const priority_queue&) = delete; + priority_queue(const priority_queue&) = delete; + + private: + function callback; + unsigned basecounter; + unsigned heapsize; + unsigned heapcapacity; + struct heap_t { + unsigned counter; + type_t event; + } *heap; + + //return true if x is greater than or equal to y + inline bool gte(unsigned x, unsigned y) { + return x - y < (std::numeric_limits::max() >> 1); + } + }; +} + +#endif diff --git a/nall/property.hpp b/nall/property.hpp new file mode 100755 index 00000000..6fd33acd --- /dev/null +++ b/nall/property.hpp @@ -0,0 +1,91 @@ +#ifndef NALL_PROPERTY_HPP +#define NALL_PROPERTY_HPP + +//nall::property implements ownership semantics into container classes +//example: property::readonly implies that only owner has full +//access to type; and all other code has readonly access. +// +//this code relies on extended friend semantics from C++0x to work, as it +//declares a friend class via a template paramter. it also exploits a bug in +//G++ 4.x to work even in C++98 mode. +// +//if compiling elsewhere, simply remove the friend class and private semantics + +//property can be used either of two ways: +//struct foo { +// property::readonly x; +// property::readwrite y; +//}; +//-or- +//struct foo : property { +// readonly x; +// readwrite y; +//}; + +//return types are const T& (byref) instead fo T (byval) to avoid major speed +//penalties for objects with expensive copy constructors + +//operator-> provides access to underlying object type: +//readonly foo; +//foo->bar(); +//... will call Object::bar(); + +//operator='s reference is constant so as to avoid leaking a reference handle +//that could bypass access restrictions + +//both constant and non-constant operators are provided, though it may be +//necessary to cast first, for instance: +//struct foo : property { readonly bar; } object; +//int main() { int value = const_cast(object); } + +//writeonly is useful for objects that have non-const reads, but const writes. +//however, to avoid leaking handles, the interface is very restricted. the only +//way to write is via operator=, which requires conversion via eg copy +//constructor. example: +//struct foo { +// foo(bool value) { ... } +//}; +//writeonly bar; +//bar = true; + +namespace nall { + template struct property { + template struct traits { typedef T type; }; + + template struct readonly { + const T* operator->() const { return &value; } + const T& operator()() const { return value; } + operator const T&() const { return value; } + private: + T* operator->() { return &value; } + operator T&() { return value; } + const T& operator=(const T& value_) { return value = value_; } + T value; + friend class traits::type; + }; + + template struct writeonly { + void operator=(const T& value_) { value = value_; } + private: + const T* operator->() const { return &value; } + const T& operator()() const { return value; } + operator const T&() const { return value; } + T* operator->() { return &value; } + operator T&() { return value; } + T value; + friend class traits::type; + }; + + template struct readwrite { + const T* operator->() const { return &value; } + const T& operator()() const { return value; } + operator const T&() const { return value; } + T* operator->() { return &value; } + operator T&() { return value; } + const T& operator=(const T& value_) { return value = value_; } + T value; + }; + }; +} + +#endif diff --git a/nall/random.hpp b/nall/random.hpp new file mode 100755 index 00000000..74ebc2d2 --- /dev/null +++ b/nall/random.hpp @@ -0,0 +1,20 @@ +#ifndef NALL_RANDOM_HPP +#define NALL_RANDOM_HPP + +namespace nall { + //pseudo-random number generator + inline unsigned prng() { + static unsigned n = 0; + return n = (n >> 1) ^ (((n & 1) - 1) & 0xedb88320); + } + + struct random_cyclic { + unsigned seed; + inline unsigned operator()() { + return seed = (seed >> 1) ^ (((seed & 1) - 1) & 0xedb88320); + } + random_cyclic() : seed(0) {} + }; +} + +#endif diff --git a/nall/serial.hpp b/nall/serial.hpp new file mode 100755 index 00000000..9ac8451a --- /dev/null +++ b/nall/serial.hpp @@ -0,0 +1,85 @@ +#ifndef NALL_SERIAL_HPP +#define NALL_SERIAL_HPP + +#include +#include +#include +#include + +#include + +namespace nall { + class serial { + public: + //-1 on error, otherwise return bytes read + int read(uint8_t *data, unsigned length) { + if(port_open == false) return -1; + return ::read(port, (void*)data, length); + } + + //-1 on error, otherwise return bytes written + int write(const uint8_t *data, unsigned length) { + if(port_open == false) return -1; + return ::write(port, (void*)data, length); + } + + bool open(const char *portname, unsigned rate, bool flowcontrol) { + close(); + + port = ::open(portname, O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK); + if(port == -1) return false; + + if(ioctl(port, TIOCEXCL) == -1) { close(); return false; } + if(fcntl(port, F_SETFL, 0) == -1) { close(); return false; } + if(tcgetattr(port, &original_attr) == -1) { close(); return false; } + + termios attr = original_attr; + cfmakeraw(&attr); + cfsetspeed(&attr, rate); + + attr.c_lflag &=~ (ECHO | ECHONL | ISIG | ICANON | IEXTEN); + attr.c_iflag &=~ (BRKINT | PARMRK | INPCK | ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXOFF | IXANY); + attr.c_iflag |= (IGNBRK | IGNPAR); + attr.c_oflag &=~ (OPOST); + attr.c_cflag &=~ (CSIZE | CSTOPB | PARENB | CLOCAL); + attr.c_cflag |= (CS8 | CREAD); + if(flowcontrol == false) { + attr.c_cflag &= ~CRTSCTS; + } else { + attr.c_cflag |= CRTSCTS; + } + attr.c_cc[VTIME] = attr.c_cc[VMIN] = 0; + + if(tcsetattr(port, TCSANOW, &attr) == -1) { close(); return false; } + return port_open = true; + } + + void close() { + if(port != -1) { + tcdrain(port); + if(port_open == true) { + tcsetattr(port, TCSANOW, &original_attr); + port_open = false; + } + ::close(port); + port = -1; + } + } + + serial() { + port = -1; + port_open = false; + } + + ~serial() { + close(); + } + + private: + int port; + bool port_open; + termios original_attr; + }; +} + +#endif diff --git a/nall/serializer.hpp b/nall/serializer.hpp new file mode 100755 index 00000000..ff2337ab --- /dev/null +++ b/nall/serializer.hpp @@ -0,0 +1,146 @@ +#ifndef NALL_SERIALIZER_HPP +#define NALL_SERIALIZER_HPP + +#include +#include +#include +#include + +namespace nall { + //serializer: a class designed to save and restore the state of classes. + // + //benefits: + //- data() will be portable in size (it is not necessary to specify type sizes.) + //- data() will be portable in endianness (always stored internally as little-endian.) + //- one serialize function can both save and restore class states. + // + //caveats: + //- only plain-old-data can be stored. complex classes must provide serialize(serializer&); + //- floating-point usage is not portable across platforms + + class serializer { + public: + enum mode_t { Load, Save, Size }; + + mode_t mode() const { + return imode; + } + + const uint8_t* data() const { + return idata; + } + + unsigned size() const { + return isize; + } + + unsigned capacity() const { + return icapacity; + } + + template void floatingpoint(T &value) { + enum { size = sizeof(T) }; + //this is rather dangerous, and not cross-platform safe; + //but there is no standardized way to export FP-values + uint8_t *p = (uint8_t*)&value; + if(imode == Save) { + for(unsigned n = 0; n < size; n++) idata[isize++] = p[n]; + } else if(imode == Load) { + for(unsigned n = 0; n < size; n++) p[n] = idata[isize++]; + } else { + isize += size; + } + } + + template void integer(T &value) { + enum { size = std::is_same::value ? 1 : sizeof(T) }; + if(imode == Save) { + for(unsigned n = 0; n < size; n++) idata[isize++] = value >> (n << 3); + } else if(imode == Load) { + value = 0; + for(unsigned n = 0; n < size; n++) value |= idata[isize++] << (n << 3); + } else if(imode == Size) { + isize += size; + } + } + + template void array(T &array) { + enum { size = sizeof(T) / sizeof(typename std::remove_extent::type) }; + for(unsigned n = 0; n < size; n++) integer(array[n]); + } + + template void array(T array, unsigned size) { + for(unsigned n = 0; n < size; n++) integer(array[n]); + } + + //copy + serializer& operator=(const serializer &s) { + if(idata) delete[] idata; + + imode = s.imode; + idata = new uint8_t[s.icapacity]; + isize = s.isize; + icapacity = s.icapacity; + + memcpy(idata, s.idata, s.icapacity); + return *this; + } + + serializer(const serializer &s) : idata(0) { + operator=(s); + } + + //move + serializer& operator=(serializer &&s) { + if(idata) delete[] idata; + + imode = s.imode; + idata = s.idata; + isize = s.isize; + icapacity = s.icapacity; + + s.idata = 0; + return *this; + } + + serializer(serializer &&s) { + operator=(std::move(s)); + } + + //construction + serializer() { + imode = Size; + idata = 0; + isize = 0; + icapacity = 0; + } + + serializer(unsigned capacity) { + imode = Save; + idata = new uint8_t[capacity](); + isize = 0; + icapacity = capacity; + } + + serializer(const uint8_t *data, unsigned capacity) { + imode = Load; + idata = new uint8_t[capacity]; + isize = 0; + icapacity = capacity; + memcpy(idata, data, capacity); + } + + ~serializer() { + if(idata) delete[] idata; + } + + private: + mode_t imode; + uint8_t *idata; + unsigned isize; + unsigned icapacity; + }; + +}; + +#endif diff --git a/nall/sha256.hpp b/nall/sha256.hpp new file mode 100755 index 00000000..7f41f04e --- /dev/null +++ b/nall/sha256.hpp @@ -0,0 +1,143 @@ +#ifndef NALL_SHA256_HPP +#define NALL_SHA256_HPP + +//author: vladitx + +namespace nall { + #define PTR(t, a) ((t*)(a)) + + #define SWAP32(x) ((uint32_t)( \ + (((uint32_t)(x) & 0x000000ff) << 24) | \ + (((uint32_t)(x) & 0x0000ff00) << 8) | \ + (((uint32_t)(x) & 0x00ff0000) >> 8) | \ + (((uint32_t)(x) & 0xff000000) >> 24) \ + )) + + #define ST32(a, d) *PTR(uint32_t, a) = (d) + #define ST32BE(a, d) ST32(a, SWAP32(d)) + + #define LD32(a) *PTR(uint32_t, a) + #define LD32BE(a) SWAP32(LD32(a)) + + #define LSL32(x, n) ((uint32_t)(x) << (n)) + #define LSR32(x, n) ((uint32_t)(x) >> (n)) + #define ROR32(x, n) (LSR32(x, n) | LSL32(x, 32 - (n))) + + //first 32 bits of the fractional parts of the square roots of the first 8 primes 2..19 + static const uint32_t T_H[8] = { + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, + }; + + //first 32 bits of the fractional parts of the cube roots of the first 64 primes 2..311 + static const uint32_t T_K[64] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, + }; + + struct sha256_ctx { + uint8_t in[64]; + unsigned inlen; + + uint32_t w[64]; + uint32_t h[8]; + uint64_t len; + }; + + void sha256_init(sha256_ctx *p) { + memset(p, 0, sizeof(sha256_ctx)); + memcpy(p->h, T_H, sizeof(T_H)); + } + + static void sha256_block(sha256_ctx *p) { + unsigned i; + uint32_t s0, s1; + uint32_t a, b, c, d, e, f, g, h; + uint32_t t1, t2, maj, ch; + + for(i = 0; i < 16; i++) p->w[i] = LD32BE(p->in + i * 4); + + for(i = 16; i < 64; i++) { + s0 = ROR32(p->w[i - 15], 7) ^ ROR32(p->w[i - 15], 18) ^ LSR32(p->w[i - 15], 3); + s1 = ROR32(p->w[i - 2], 17) ^ ROR32(p->w[i - 2], 19) ^ LSR32(p->w[i - 2], 10); + p->w[i] = p->w[i - 16] + s0 + p->w[i - 7] + s1; + } + + a = p->h[0]; b = p->h[1]; c = p->h[2]; d = p->h[3]; + e = p->h[4]; f = p->h[5]; g = p->h[6]; h = p->h[7]; + + for(i = 0; i < 64; i++) { + s0 = ROR32(a, 2) ^ ROR32(a, 13) ^ ROR32(a, 22); + maj = (a & b) ^ (a & c) ^ (b & c); + t2 = s0 + maj; + s1 = ROR32(e, 6) ^ ROR32(e, 11) ^ ROR32(e, 25); + ch = (e & f) ^ (~e & g); + t1 = h + s1 + ch + T_K[i] + p->w[i]; + + h = g; g = f; f = e; e = d + t1; + d = c; c = b; b = a; a = t1 + t2; + } + + p->h[0] += a; p->h[1] += b; p->h[2] += c; p->h[3] += d; + p->h[4] += e; p->h[5] += f; p->h[6] += g; p->h[7] += h; + + //next block + p->inlen = 0; + } + + void sha256_chunk(sha256_ctx *p, const uint8_t *s, unsigned len) { + unsigned l; + p->len += len; + + while(len) { + l = 64 - p->inlen; + l = (len < l) ? len : l; + + memcpy(p->in + p->inlen, s, l); + s += l; + p->inlen += l; + len -= l; + + if(p->inlen == 64) sha256_block(p); + } + } + + void sha256_final(sha256_ctx *p) { + uint64_t len; + p->in[p->inlen++] = 0x80; + + if(p->inlen > 56) { + memset(p->in + p->inlen, 0, 64 - p->inlen); + sha256_block(p); + } + + memset(p->in + p->inlen, 0, 56 - p->inlen); + + len = p->len << 3; + ST32BE(p->in + 56, len >> 32); + ST32BE(p->in + 60, len); + sha256_block(p); + } + + void sha256_hash(sha256_ctx *p, uint8_t *s) { + uint32_t *t = (uint32_t*)s; + for(unsigned i = 0; i < 8; i++) ST32BE(t++, p->h[i]); + } + + #undef PTR + #undef SWAP32 + #undef ST32 + #undef ST32BE + #undef LD32 + #undef LD32BE + #undef LSL32 + #undef LSR32 + #undef ROR32 +} + +#endif diff --git a/nall/snes/cartridge.hpp b/nall/snes/cartridge.hpp new file mode 100755 index 00000000..485e91eb --- /dev/null +++ b/nall/snes/cartridge.hpp @@ -0,0 +1,868 @@ +#ifndef NALL_SNES_CARTRIDGE_HPP +#define NALL_SNES_CARTRIDGE_HPP + +namespace nall { + +class SNESCartridge { +public: + string xmlMemoryMap; + inline SNESCartridge(const uint8_t *data, unsigned size); + +//private: + inline void read_header(const uint8_t *data, unsigned size); + inline unsigned find_header(const uint8_t *data, unsigned size); + inline unsigned score_header(const uint8_t *data, unsigned size, unsigned addr); + inline unsigned gameboy_ram_size(const uint8_t *data, unsigned size); + inline bool gameboy_has_rtc(const uint8_t *data, unsigned size); + + enum HeaderField { + CartName = 0x00, + Mapper = 0x15, + RomType = 0x16, + RomSize = 0x17, + RamSize = 0x18, + CartRegion = 0x19, + Company = 0x1a, + Version = 0x1b, + Complement = 0x1c, //inverse checksum + Checksum = 0x1e, + ResetVector = 0x3c, + }; + + enum Mode { + ModeNormal, + ModeBsxSlotted, + ModeBsx, + ModeSufamiTurbo, + ModeSuperGameBoy, + }; + + enum Type { + TypeNormal, + TypeBsxSlotted, + TypeBsxBios, + TypeBsx, + TypeSufamiTurboBios, + TypeSufamiTurbo, + TypeSuperGameBoy1Bios, + TypeSuperGameBoy2Bios, + TypeGameBoy, + TypeUnknown, + }; + + enum Region { + NTSC, + PAL, + }; + + enum MemoryMapper { + LoROM, + HiROM, + ExLoROM, + ExHiROM, + SuperFXROM, + SA1ROM, + SPC7110ROM, + BSCLoROM, + BSCHiROM, + BSXROM, + STROM, + }; + + enum DSP1MemoryMapper { + DSP1Unmapped, + DSP1LoROM1MB, + DSP1LoROM2MB, + DSP1HiROM, + }; + + bool loaded; //is a base cartridge inserted? + unsigned crc32; //crc32 of all cartridges (base+slot(s)) + unsigned rom_size; + unsigned ram_size; + + Mode mode; + Type type; + Region region; + MemoryMapper mapper; + DSP1MemoryMapper dsp1_mapper; + + bool has_bsx_slot; + bool has_superfx; + bool has_sa1; + bool has_srtc; + bool has_sdd1; + bool has_spc7110; + bool has_spc7110rtc; + bool has_cx4; + bool has_dsp1; + bool has_dsp2; + bool has_dsp3; + bool has_dsp4; + bool has_obc1; + bool has_st010; + bool has_st011; + bool has_st018; +}; + +SNESCartridge::SNESCartridge(const uint8_t *data, unsigned size) { + read_header(data, size); + + string xml = "\n"; + + if(type == TypeBsx) { + xml << ""; + xmlMemoryMap = xml; + return; + } + + if(type == TypeSufamiTurbo) { + xml << ""; + xmlMemoryMap = xml; + return; + } + + if(type == TypeGameBoy) { + xml << "\n"; + if(gameboy_ram_size(data, size) > 0) { + xml << " \n"; + } + xml << "\n"; + xmlMemoryMap = xml; + return; + } + + xml << "\n"; + + if(type == TypeSuperGameBoy1Bios) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + } else if(type == TypeSuperGameBoy2Bios) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + } else if(has_spc7110) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + if(has_spc7110rtc) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + } + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + } else if(mapper == LoROM) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + + if(ram_size > 0) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + if((rom_size > 0x200000) || (ram_size > 32 * 1024)) { + xml << " \n"; + xml << " \n"; + } else { + xml << " \n"; + xml << " \n"; + } + xml << " \n"; + } + } else if(mapper == HiROM) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + + if(ram_size > 0) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + if((rom_size > 0x200000) || (ram_size > 32 * 1024)) { + xml << " \n"; + } else { + xml << " \n"; + } + xml << " \n"; + } + } else if(mapper == ExLoROM) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + + if(ram_size > 0) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + } + } else if(mapper == ExHiROM) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + + if(ram_size > 0) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + if((rom_size > 0x200000) || (ram_size > 32 * 1024)) { + xml << " \n"; + } else { + xml << " \n"; + } + xml << " \n"; + } + } else if(mapper == SuperFXROM) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + } else if(mapper == SA1ROM) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + } else if(mapper == BSCLoROM) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + } else if(mapper == BSCHiROM) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + } else if(mapper == BSXROM) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + } else if(mapper == STROM) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + } + + if(has_srtc) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + } + + if(has_sdd1) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + } + + if(has_cx4) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + } + + if(has_dsp1) { + xml << " \n"; + if(dsp1_mapper == DSP1LoROM1MB) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + } else if(dsp1_mapper == DSP1LoROM2MB) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + } else if(dsp1_mapper == DSP1HiROM) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + } + xml << " \n"; + } + + if(has_dsp2) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + } + + if(has_dsp3) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + } + + if(has_dsp4) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + } + + if(has_obc1) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + } + + if(has_st010) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + } + + if(has_st011) { + //ST-0011 addresses not verified; chip is unsupported + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + } + + if(has_st018) { + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + xml << " \n"; + } + + xml << "\n"; + xmlMemoryMap = xml; +} + +void SNESCartridge::read_header(const uint8_t *data, unsigned size) { + type = TypeUnknown; + mapper = LoROM; + dsp1_mapper = DSP1Unmapped; + region = NTSC; + rom_size = size; + ram_size = 0; + + has_bsx_slot = false; + has_superfx = false; + has_sa1 = false; + has_srtc = false; + has_sdd1 = false; + has_spc7110 = false; + has_spc7110rtc = false; + has_cx4 = false; + has_dsp1 = false; + has_dsp2 = false; + has_dsp3 = false; + has_dsp4 = false; + has_obc1 = false; + has_st010 = false; + has_st011 = false; + has_st018 = false; + + //===================== + //detect Game Boy carts + //===================== + + if(size >= 0x0140) { + if(data[0x0104] == 0xce && data[0x0105] == 0xed && data[0x0106] == 0x66 && data[0x0107] == 0x66 + && data[0x0108] == 0xcc && data[0x0109] == 0x0d && data[0x010a] == 0x00 && data[0x010b] == 0x0b) { + type = TypeGameBoy; + return; + } + } + + if(size < 32768) { + type = TypeUnknown; + return; + } + + const unsigned index = find_header(data, size); + const uint8_t mapperid = data[index + Mapper]; + const uint8_t rom_type = data[index + RomType]; + const uint8_t rom_size = data[index + RomSize]; + const uint8_t company = data[index + Company]; + const uint8_t regionid = data[index + CartRegion] & 0x7f; + + ram_size = 1024 << (data[index + RamSize] & 7); + if(ram_size == 1024) ram_size = 0; //no RAM present + + //0, 1, 13 = NTSC; 2 - 12 = PAL + region = (regionid <= 1 || regionid >= 13) ? NTSC : PAL; + + //======================= + //detect BS-X flash carts + //======================= + + if(data[index + 0x13] == 0x00 || data[index + 0x13] == 0xff) { + if(data[index + 0x14] == 0x00) { + const uint8_t n15 = data[index + 0x15]; + if(n15 == 0x00 || n15 == 0x80 || n15 == 0x84 || n15 == 0x9c || n15 == 0xbc || n15 == 0xfc) { + if(data[index + 0x1a] == 0x33 || data[index + 0x1a] == 0xff) { + type = TypeBsx; + mapper = BSXROM; + region = NTSC; //BS-X only released in Japan + return; + } + } + } + } + + //========================= + //detect Sufami Turbo carts + //========================= + + if(!memcmp(data, "BANDAI SFC-ADX", 14)) { + if(!memcmp(data + 16, "SFC-ADX BACKUP", 14)) { + type = TypeSufamiTurboBios; + } else { + type = TypeSufamiTurbo; + } + mapper = STROM; + region = NTSC; //Sufami Turbo only released in Japan + return; //RAM size handled outside this routine + } + + //========================== + //detect Super Game Boy BIOS + //========================== + + if(!memcmp(data + index, "Super GAMEBOY2", 14)) { + type = TypeSuperGameBoy2Bios; + return; + } + + if(!memcmp(data + index, "Super GAMEBOY", 13)) { + type = TypeSuperGameBoy1Bios; + return; + } + + //===================== + //detect standard carts + //===================== + + //detect presence of BS-X flash cartridge connector (reads extended header information) + if(data[index - 14] == 'Z') { + if(data[index - 11] == 'J') { + uint8_t n13 = data[index - 13]; + if((n13 >= 'A' && n13 <= 'Z') || (n13 >= '0' && n13 <= '9')) { + if(company == 0x33 || (data[index - 10] == 0x00 && data[index - 4] == 0x00)) { + has_bsx_slot = true; + } + } + } + } + + if(has_bsx_slot) { + if(!memcmp(data + index, "Satellaview BS-X ", 21)) { + //BS-X base cart + type = TypeBsxBios; + mapper = BSXROM; + region = NTSC; //BS-X only released in Japan + return; //RAM size handled internally by load_cart_bsx() -> BSXCart class + } else { + type = TypeBsxSlotted; + mapper = (index == 0x7fc0 ? BSCLoROM : BSCHiROM); + region = NTSC; //BS-X slotted cartridges only released in Japan + } + } else { + //standard cart + type = TypeNormal; + + if(index == 0x7fc0 && size >= 0x401000) { + mapper = ExLoROM; + } else if(index == 0x7fc0 && mapperid == 0x32) { + mapper = ExLoROM; + } else if(index == 0x7fc0) { + mapper = LoROM; + } else if(index == 0xffc0) { + mapper = HiROM; + } else { //index == 0x40ffc0 + mapper = ExHiROM; + } + } + + if(mapperid == 0x20 && (rom_type == 0x13 || rom_type == 0x14 || rom_type == 0x15 || rom_type == 0x1a)) { + has_superfx = true; + mapper = SuperFXROM; + ram_size = 1024 << (data[index - 3] & 7); + if(ram_size == 1024) ram_size = 0; + } + + if(mapperid == 0x23 && (rom_type == 0x32 || rom_type == 0x34 || rom_type == 0x35)) { + has_sa1 = true; + mapper = SA1ROM; + } + + if(mapperid == 0x35 && rom_type == 0x55) { + has_srtc = true; + } + + if(mapperid == 0x32 && (rom_type == 0x43 || rom_type == 0x45)) { + has_sdd1 = true; + } + + if(mapperid == 0x3a && (rom_type == 0xf5 || rom_type == 0xf9)) { + has_spc7110 = true; + has_spc7110rtc = (rom_type == 0xf9); + mapper = SPC7110ROM; + } + + if(mapperid == 0x20 && rom_type == 0xf3) { + has_cx4 = true; + } + + if((mapperid == 0x20 || mapperid == 0x21) && rom_type == 0x03) { + has_dsp1 = true; + } + + if(mapperid == 0x30 && rom_type == 0x05 && company != 0xb2) { + has_dsp1 = true; + } + + if(mapperid == 0x31 && (rom_type == 0x03 || rom_type == 0x05)) { + has_dsp1 = true; + } + + if(has_dsp1 == true) { + if((mapperid & 0x2f) == 0x20 && size <= 0x100000) { + dsp1_mapper = DSP1LoROM1MB; + } else if((mapperid & 0x2f) == 0x20) { + dsp1_mapper = DSP1LoROM2MB; + } else if((mapperid & 0x2f) == 0x21) { + dsp1_mapper = DSP1HiROM; + } + } + + if(mapperid == 0x20 && rom_type == 0x05) { + has_dsp2 = true; + } + + if(mapperid == 0x30 && rom_type == 0x05 && company == 0xb2) { + has_dsp3 = true; + } + + if(mapperid == 0x30 && rom_type == 0x03) { + has_dsp4 = true; + } + + if(mapperid == 0x30 && rom_type == 0x25) { + has_obc1 = true; + } + + if(mapperid == 0x30 && rom_type == 0xf6 && rom_size >= 10) { + has_st010 = true; + } + + if(mapperid == 0x30 && rom_type == 0xf6 && rom_size < 10) { + has_st011 = true; + } + + if(mapperid == 0x30 && rom_type == 0xf5) { + has_st018 = true; + } +} + +unsigned SNESCartridge::find_header(const uint8_t *data, unsigned size) { + unsigned score_lo = score_header(data, size, 0x007fc0); + unsigned score_hi = score_header(data, size, 0x00ffc0); + unsigned score_ex = score_header(data, size, 0x40ffc0); + if(score_ex) score_ex += 4; //favor ExHiROM on images > 32mbits + + if(score_lo >= score_hi && score_lo >= score_ex) { + return 0x007fc0; + } else if(score_hi >= score_ex) { + return 0x00ffc0; + } else { + return 0x40ffc0; + } +} + +unsigned SNESCartridge::score_header(const uint8_t *data, unsigned size, unsigned addr) { + if(size < addr + 64) return 0; //image too small to contain header at this location? + int score = 0; + + uint16_t resetvector = data[addr + ResetVector] | (data[addr + ResetVector + 1] << 8); + uint16_t checksum = data[addr + Checksum ] | (data[addr + Checksum + 1] << 8); + uint16_t complement = data[addr + Complement ] | (data[addr + Complement + 1] << 8); + + uint8_t resetop = data[(addr & ~0x7fff) | (resetvector & 0x7fff)]; //first opcode executed upon reset + uint8_t mapper = data[addr + Mapper] & ~0x10; //mask off irrelevent FastROM-capable bit + + //$00:[000-7fff] contains uninitialized RAM and MMIO. + //reset vector must point to ROM at $00:[8000-ffff] to be considered valid. + if(resetvector < 0x8000) return 0; + + //some images duplicate the header in multiple locations, and others have completely + //invalid header information that cannot be relied upon. + //below code will analyze the first opcode executed at the specified reset vector to + //determine the probability that this is the correct header. + + //most likely opcodes + if(resetop == 0x78 //sei + || resetop == 0x18 //clc (clc; xce) + || resetop == 0x38 //sec (sec; xce) + || resetop == 0x9c //stz $nnnn (stz $4200) + || resetop == 0x4c //jmp $nnnn + || resetop == 0x5c //jml $nnnnnn + ) score += 8; + + //plausible opcodes + if(resetop == 0xc2 //rep #$nn + || resetop == 0xe2 //sep #$nn + || resetop == 0xad //lda $nnnn + || resetop == 0xae //ldx $nnnn + || resetop == 0xac //ldy $nnnn + || resetop == 0xaf //lda $nnnnnn + || resetop == 0xa9 //lda #$nn + || resetop == 0xa2 //ldx #$nn + || resetop == 0xa0 //ldy #$nn + || resetop == 0x20 //jsr $nnnn + || resetop == 0x22 //jsl $nnnnnn + ) score += 4; + + //implausible opcodes + if(resetop == 0x40 //rti + || resetop == 0x60 //rts + || resetop == 0x6b //rtl + || resetop == 0xcd //cmp $nnnn + || resetop == 0xec //cpx $nnnn + || resetop == 0xcc //cpy $nnnn + ) score -= 4; + + //least likely opcodes + if(resetop == 0x00 //brk #$nn + || resetop == 0x02 //cop #$nn + || resetop == 0xdb //stp + || resetop == 0x42 //wdm + || resetop == 0xff //sbc $nnnnnn,x + ) score -= 8; + + //at times, both the header and reset vector's first opcode will match ... + //fallback and rely on info validity in these cases to determine more likely header. + + //a valid checksum is the biggest indicator of a valid header. + if((checksum + complement) == 0xffff && (checksum != 0) && (complement != 0)) score += 4; + + if(addr == 0x007fc0 && mapper == 0x20) score += 2; //0x20 is usually LoROM + if(addr == 0x00ffc0 && mapper == 0x21) score += 2; //0x21 is usually HiROM + if(addr == 0x007fc0 && mapper == 0x22) score += 2; //0x22 is usually ExLoROM + if(addr == 0x40ffc0 && mapper == 0x25) score += 2; //0x25 is usually ExHiROM + + if(data[addr + Company] == 0x33) score += 2; //0x33 indicates extended header + if(data[addr + RomType] < 0x08) score++; + if(data[addr + RomSize] < 0x10) score++; + if(data[addr + RamSize] < 0x08) score++; + if(data[addr + CartRegion] < 14) score++; + + if(score < 0) score = 0; + return score; +} + +unsigned SNESCartridge::gameboy_ram_size(const uint8_t *data, unsigned size) { + if(size < 512) return 0; + switch(data[0x0149]) { + case 0x00: return 0 * 1024; + case 0x01: return 8 * 1024; + case 0x02: return 8 * 1024; + case 0x03: return 32 * 1024; + case 0x04: return 128 * 1024; + case 0x05: return 128 * 1024; + default: return 128 * 1024; + } +} + +bool SNESCartridge::gameboy_has_rtc(const uint8_t *data, unsigned size) { + if(size < 512) return false; + if(data[0x0147] == 0x0f ||data[0x0147] == 0x10) return true; + return false; +} + +} + +#endif diff --git a/nall/snes/cpu.hpp b/nall/snes/cpu.hpp new file mode 100755 index 00000000..28f5ddb5 --- /dev/null +++ b/nall/snes/cpu.hpp @@ -0,0 +1,458 @@ +#ifndef NALL_SNES_CPU_HPP +#define NALL_SNES_CPU_HPP + +namespace nall { + +struct SNESCPU { + enum : unsigned { + Implied, // + Constant, //#$00 + AccumConstant, //#$00 + IndexConstant, //#$00 + Direct, //$00 + DirectX, //$00,x + DirectY, //$00,y + IDirect, //($00) + IDirectX, //($00,x) + IDirectY, //($00),y + ILDirect, //[$00] + ILDirectY, //[$00],y + Address, //$0000 + AddressX, //$0000,x + AddressY, //$0000,y + IAddressX, //($0000,x) + ILAddress, //[$0000] + PAddress, //PBR:$0000 + PIAddress, //PBR:($0000) + Long, //$000000 + LongX, //$000000,x + Stack, //$00,s + IStackY, //($00,s),y + BlockMove, //$00,$00 + RelativeShort, //+/- $00 + RelativeLong, //+/- $0000 + }; + + struct OpcodeInfo { + char name[4]; + unsigned mode; + }; + + static const OpcodeInfo opcodeInfo[256]; + + static unsigned getOpcodeLength(bool accum, bool index, uint8_t opcode); + static string disassemble(unsigned pc, bool accum, bool index, uint8_t opcode, uint8_t pl, uint8_t ph, uint8_t pb); +}; + +const SNESCPU::OpcodeInfo SNESCPU::opcodeInfo[256] = { + //0x00 - 0x0f + { "brk", Constant }, + { "ora", IDirectX }, + { "cop", Constant }, + { "ora", Stack }, + + { "tsb", Direct }, + { "ora", Direct }, + { "asl", Direct }, + { "ora", ILDirect }, + + { "php", Implied }, + { "ora", AccumConstant }, + { "asl", Implied }, + { "phd", Implied }, + + { "tsb", Address }, + { "ora", Address }, + { "asl", Address }, + { "ora", Long }, + + //0x10 - 0x1f + { "bpl", RelativeShort }, + { "ora", IDirectY }, + { "ora", IDirect }, + { "ora", IStackY }, + + { "trb", Direct }, + { "ora", DirectX }, + { "asl", DirectX }, + { "ora", ILDirectY }, + + { "clc", Implied }, + { "ora", AddressY }, + { "inc", Implied }, + { "tcs", Implied }, + + { "trb", Address }, + { "ora", AddressX }, + { "asl", AddressX }, + { "ora", LongX }, + + //0x20 - 0x2f + { "jsr", Address }, + { "and", IDirectX }, + { "jsl", Long }, + { "and", Stack }, + + { "bit", Direct }, + { "and", Direct }, + { "rol", Direct }, + { "and", ILDirect }, + + { "plp", Implied }, + { "and", AccumConstant }, + { "rol", Implied }, + { "pld", Implied }, + + { "bit", Address }, + { "and", Address }, + { "rol", Address }, + { "and", Long }, + + //0x30 - 0x3f + { "bmi", RelativeShort }, + { "and", IDirectY }, + { "and", IDirect }, + { "and", IStackY }, + + { "bit", DirectX }, + { "and", DirectX }, + { "rol", DirectX }, + { "and", ILDirectY }, + + { "sec", Implied }, + { "and", AddressY }, + { "dec", Implied }, + { "tsc", Implied }, + + { "bit", AddressX }, + { "and", AddressX }, + { "rol", AddressX }, + { "and", LongX }, + + //0x40 - 0x4f + { "rti", Implied }, + { "eor", IDirectX }, + { "wdm", Constant }, + { "eor", Stack }, + + { "mvp", BlockMove }, + { "eor", Direct }, + { "lsr", Direct }, + { "eor", ILDirect }, + + { "pha", Implied }, + { "eor", AccumConstant }, + { "lsr", Implied }, + { "phk", Implied }, + + { "jmp", PAddress }, + { "eor", Address }, + { "lsr", Address }, + { "eor", Long }, + + //0x50 - 0x5f + { "bvc", RelativeShort }, + { "eor", IDirectY }, + { "eor", IDirect }, + { "eor", IStackY }, + + { "mvn", BlockMove }, + { "eor", DirectX }, + { "lsr", DirectX }, + { "eor", ILDirectY }, + + { "cli", Implied }, + { "eor", AddressY }, + { "phy", Implied }, + { "tcd", Implied }, + + { "jml", Long }, + { "eor", AddressX }, + { "lsr", AddressX }, + { "eor", LongX }, + + //0x60 - 0x6f + { "rts", Implied }, + { "adc", IDirectX }, + { "per", Address }, + { "adc", Stack }, + + { "stz", Direct }, + { "adc", Direct }, + { "ror", Direct }, + { "adc", ILDirect }, + + { "pla", Implied }, + { "adc", AccumConstant }, + { "ror", Implied }, + { "rtl", Implied }, + + { "jmp", PIAddress }, + { "adc", Address }, + { "ror", Address }, + { "adc", Long }, + + //0x70 - 0x7f + { "bvs", RelativeShort }, + { "adc", IDirectY }, + { "adc", IDirect }, + { "adc", IStackY }, + + { "stz", DirectX }, + { "adc", DirectX }, + { "ror", DirectX }, + { "adc", ILDirectY }, + + { "sei", Implied }, + { "adc", AddressY }, + { "ply", Implied }, + { "tdc", Implied }, + + { "jmp", IAddressX }, + { "adc", AddressX }, + { "ror", AddressX }, + { "adc", LongX }, + + //0x80 - 0x8f + { "bra", RelativeShort }, + { "sta", IDirectX }, + { "brl", RelativeLong }, + { "sta", Stack }, + + { "sty", Direct }, + { "sta", Direct }, + { "stx", Direct }, + { "sta", ILDirect }, + + { "dey", Implied }, + { "bit", AccumConstant }, + { "txa", Implied }, + { "phb", Implied }, + + { "sty", Address }, + { "sta", Address }, + { "stx", Address }, + { "sta", Long }, + + //0x90 - 0x9f + { "bcc", RelativeShort }, + { "sta", IDirectY }, + { "sta", IDirect }, + { "sta", IStackY }, + + { "sty", DirectX }, + { "sta", DirectX }, + { "stx", DirectY }, + { "sta", ILDirectY }, + + { "tya", Implied }, + { "sta", AddressY }, + { "txs", Implied }, + { "txy", Implied }, + + { "stz", Address }, + { "sta", AddressX }, + { "stz", AddressX }, + { "sta", LongX }, + + //0xa0 - 0xaf + { "ldy", IndexConstant }, + { "lda", IDirectX }, + { "ldx", IndexConstant }, + { "lda", Stack }, + + { "ldy", Direct }, + { "lda", Direct }, + { "ldx", Direct }, + { "lda", ILDirect }, + + { "tay", Implied }, + { "lda", AccumConstant }, + { "tax", Implied }, + { "plb", Implied }, + + { "ldy", Address }, + { "lda", Address }, + { "ldx", Address }, + { "lda", Long }, + + //0xb0 - 0xbf + { "bcs", RelativeShort }, + { "lda", IDirectY }, + { "lda", IDirect }, + { "lda", IStackY }, + + { "ldy", DirectX }, + { "lda", DirectX }, + { "ldx", DirectY }, + { "lda", ILDirectY }, + + { "clv", Implied }, + { "lda", AddressY }, + { "tsx", Implied }, + { "tyx", Implied }, + + { "ldy", AddressX }, + { "lda", AddressX }, + { "ldx", AddressY }, + { "lda", LongX }, + + //0xc0 - 0xcf + { "cpy", IndexConstant }, + { "cmp", IDirectX }, + { "rep", Constant }, + { "cmp", Stack }, + + { "cpy", Direct }, + { "cmp", Direct }, + { "dec", Direct }, + { "cmp", ILDirect }, + + { "iny", Implied }, + { "cmp", AccumConstant }, + { "dex", Implied }, + { "wai", Implied }, + + { "cpy", Address }, + { "cmp", Address }, + { "dec", Address }, + { "cmp", Long }, + + //0xd0 - 0xdf + { "bne", RelativeShort }, + { "cmp", IDirectY }, + { "cmp", IDirect }, + { "cmp", IStackY }, + + { "pei", IDirect }, + { "cmp", DirectX }, + { "dec", DirectX }, + { "cmp", ILDirectY }, + + { "cld", Implied }, + { "cmp", AddressY }, + { "phx", Implied }, + { "stp", Implied }, + + { "jmp", ILAddress }, + { "cmp", AddressX }, + { "dec", AddressX }, + { "cmp", LongX }, + + //0xe0 - 0xef + { "cpx", IndexConstant }, + { "sbc", IDirectX }, + { "sep", Constant }, + { "sbc", Stack }, + + { "cpx", Direct }, + { "sbc", Direct }, + { "inc", Direct }, + { "sbc", ILDirect }, + + { "inx", Implied }, + { "sbc", AccumConstant }, + { "nop", Implied }, + { "xba", Implied }, + + { "cpx", Address }, + { "sbc", Address }, + { "inc", Address }, + { "sbc", Long }, + + //0xf0 - 0xff + { "beq", RelativeShort }, + { "sbc", IDirectY }, + { "sbc", IDirect }, + { "sbc", IStackY }, + + { "pea", Address }, + { "sbc", DirectX }, + { "inc", DirectX }, + { "sbc", ILDirectY }, + + { "sed", Implied }, + { "sbc", AddressY }, + { "plx", Implied }, + { "xce", Implied }, + + { "jsr", IAddressX }, + { "sbc", AddressX }, + { "inc", AddressX }, + { "sbc", LongX }, +}; + +inline unsigned SNESCPU::getOpcodeLength(bool accum, bool index, uint8_t opcode) { + switch(opcodeInfo[opcode].mode) { default: + case Implied: return 1; + case Constant: return 2; + case AccumConstant: return 3 - accum; + case IndexConstant: return 3 - index; + case Direct: return 2; + case DirectX: return 2; + case DirectY: return 2; + case IDirect: return 2; + case IDirectX: return 2; + case IDirectY: return 2; + case ILDirect: return 2; + case ILDirectY: return 2; + case Address: return 3; + case AddressX: return 3; + case AddressY: return 3; + case IAddressX: return 3; + case ILAddress: return 3; + case PAddress: return 3; + case PIAddress: return 3; + case Long: return 4; + case LongX: return 4; + case Stack: return 2; + case IStackY: return 2; + case BlockMove: return 3; + case RelativeShort: return 2; + case RelativeLong: return 3; + } +} + +inline string SNESCPU::disassemble(unsigned pc, bool accum, bool index, uint8_t opcode, uint8_t pl, uint8_t ph, uint8_t pb) { + string name = opcodeInfo[opcode].name; + unsigned mode = opcodeInfo[opcode].mode; + + if(mode == Implied) return name; + if(mode == Constant) return { name, " #$", hex<2>(pl) }; + if(mode == AccumConstant) return { name, " #$", accum ? "" : hex<2>(ph), hex<2>(pl) }; + if(mode == IndexConstant) return { name, " #$", index ? "" : hex<2>(ph), hex<2>(pl) }; + if(mode == Direct) return { name, " $", hex<2>(pl) }; + if(mode == DirectX) return { name, " $", hex<2>(pl), ",x" }; + if(mode == DirectY) return { name, " $", hex<2>(pl), ",y" }; + if(mode == IDirect) return { name, " ($", hex<2>(pl), ")" }; + if(mode == IDirectX) return { name, " ($", hex<2>(pl), ",x)" }; + if(mode == IDirectY) return { name, " ($", hex<2>(pl), "),y" }; + if(mode == ILDirect) return { name, " [$", hex<2>(pl), "]" }; + if(mode == ILDirectY) return { name, " [$", hex<2>(pl), "],y" }; + if(mode == Address) return { name, " $", hex<2>(ph), hex<2>(pl) }; + if(mode == AddressX) return { name, " $", hex<2>(ph), hex<2>(pl), ",x" }; + if(mode == AddressY) return { name, " $", hex<2>(ph), hex<2>(pl), ",y" }; + if(mode == IAddressX) return { name, " ($", hex<2>(ph), hex<2>(pl), ",x)" }; + if(mode == ILAddress) return { name, " [$", hex<2>(ph), hex<2>(pl), "]" }; + if(mode == PAddress) return { name, " $", hex<2>(ph), hex<2>(pl) }; + if(mode == PIAddress) return { name, " ($", hex<2>(ph), hex<2>(pl), ")" }; + if(mode == Long) return { name, " $", hex<2>(pb), hex<2>(ph), hex<2>(pl) }; + if(mode == LongX) return { name, " $", hex<2>(pb), hex<2>(ph), hex<2>(pl), ",x" }; + if(mode == Stack) return { name, " $", hex<2>(pl), ",s" }; + if(mode == IStackY) return { name, " ($", hex<2>(pl), ",s),y" }; + if(mode == BlockMove) return { name, " $", hex<2>(ph), ",$", hex<2>(pl) }; + if(mode == RelativeShort) { + unsigned addr = (pc + 2) + (int8_t)(pl << 0); + return { name, " $", hex<4>(addr) }; + } + if(mode == RelativeLong) { + unsigned addr = (pc + 3) + (int16_t)((ph << 8) + (pl << 0)); + return { name, " $", hex<4>(addr) }; + } + + return ""; +} + +} + +#endif diff --git a/nall/snes/smp.hpp b/nall/snes/smp.hpp new file mode 100755 index 00000000..7a1ac47b --- /dev/null +++ b/nall/snes/smp.hpp @@ -0,0 +1,639 @@ +#ifndef NALL_SNES_SMP_HPP +#define NALL_SNES_SMP_HPP + +namespace nall { + +struct SNESSMP { + enum : unsigned { + Implied, // + TVector, //0 + Direct, //$00 + DirectRelative, //$00,+/-$00 + ADirect, //a,$00 + AAbsolute, //a,$0000 + AIX, //a,(x) + AIDirectX, //a,($00+x) + AConstant, //a,#$00 + DirectDirect, //$00,$00 + CAbsoluteBit, //c,$0000:0 + Absolute, //$0000 + P, //p + AbsoluteA, //$0000,a + Relative, //+/-$00 + ADirectX, //a,$00+x + AAbsoluteX, //a,$0000+x + AAbsoluteY, //a,$0000+y + AIDirectY, //a,($00)+y + DirectConstant, //$00,#$00 + IXIY, //(x),(y) + DirectX, //$00+x + A, //a + X, //x + XAbsolute, //x,$0000 + IAbsoluteX, //($0000+x) + CNAbsoluteBit, //c,!$0000:0 + XDirect, //x,$00 + PVector, //$ff00 + YaDirect, //ya,$00 + XA, //x,a + YAbsolute, //y,$0000 + Y, //y + AX, //a,x + YDirect, //y,$00 + YConstant, //y,#$00 + XSp, //x,sp + YaX, //ya,x + IXPA, //(x)+,a + SpX, //sp,x + AIXP, //a,(x)+ + DirectA, //$00,a + IXA, //(x),a + IDirectXA, //($00+x),a + XConstant, //x,#$00 + AbsoluteX, //$0000,x + AbsoluteBitC, //$0000:0,c + DirectY, //$00,y + AbsoluteY, //$0000,y + Ya, //ya + DirectXA, //$00+x,a + AbsoluteXA, //$0000+x,a + AbsoluteYA, //$0000+y,a + IDirectYA, //($00)+y,a + DirectYX, //$00+y,x + DirectYa, //$00,ya + DirectXY, //$00+x,y + AY, //a,y + DirectXRelative, //$00+x,+/-$00 + XDirectY, //x,$00+y + YDirectX, //y,$00+x + YA, //y,a + YRelative, //y,+/-$00 + }; + + struct OpcodeInfo { + char name[6]; + unsigned mode; + }; + + static const OpcodeInfo opcodeInfo[256]; + + static unsigned getOpcodeLength(uint8_t opcode); + static string disassemble(uint16_t pc, uint8_t opcode, uint8_t pl, uint8_t ph); + static string disassemble(uint16_t pc, bool p, uint8_t opcode, uint8_t pl, uint8_t ph); +}; + +const SNESSMP::OpcodeInfo SNESSMP::opcodeInfo[256] = { + //0x00 - 0x0f + { "nop ", Implied }, + { "tcall", TVector }, + { "set0 ", Direct }, + { "bbs0 ", DirectRelative }, + + { "or ", ADirect }, + { "or ", AAbsolute }, + { "or ", AIX }, + { "or ", AIDirectX }, + + { "or ", AConstant }, + { "or ", DirectDirect }, + { "or1 ", CAbsoluteBit }, + { "asl ", Direct }, + + { "asl ", Absolute }, + { "push ", P }, + { "tset ", AbsoluteA }, + { "brk ", Implied }, + + //0x10 - 0x1f + { "bpl ", Relative }, + { "tcall", TVector }, + { "clr0 ", Direct }, + { "bbc0 ", DirectRelative }, + + { "or ", ADirectX }, + { "or ", AAbsoluteX }, + { "or ", AAbsoluteY }, + { "or ", AIDirectY }, + + { "or ", DirectConstant }, + { "or ", IXIY }, + { "decw ", Direct }, + { "asl ", DirectX }, + + { "asl ", A }, + { "dec ", X }, + { "cmp ", XAbsolute }, + { "jmp ", IAbsoluteX }, + + //0x20 - 0x2f + { "clrp ", Implied }, + { "tcall", TVector }, + { "set1 ", Direct }, + { "bbs1 ", DirectRelative }, + + { "and ", ADirect }, + { "and ", AAbsolute }, + { "and ", AIX }, + { "and ", AIDirectX }, + + { "and ", AConstant }, + { "and ", DirectDirect }, + { "or1 ", CNAbsoluteBit }, + { "rol ", Direct }, + + { "rol ", Absolute }, + { "push ", A }, + { "cbne ", DirectRelative }, + { "bra ", Relative }, + + //0x30 - 0x3f + { "bmi ", Relative }, + { "tcall", TVector }, + { "clr1 ", Direct }, + { "bbc1 ", DirectRelative }, + + { "and ", ADirectX }, + { "and ", AAbsoluteX }, + { "and ", AAbsoluteY }, + { "and ", AIDirectY }, + + { "and ", DirectConstant }, + { "and ", IXIY }, + { "incw ", Direct }, + { "rol ", DirectX }, + + { "rol ", A }, + { "inc ", X }, + { "cmp ", XDirect }, + { "call ", Absolute }, + + //0x40 - 0x4f + { "setp ", Implied }, + { "tcall", TVector }, + { "set2 ", Direct }, + { "bbs2 ", DirectRelative }, + + { "eor ", ADirect }, + { "eor ", AAbsolute }, + { "eor ", AIX }, + { "eor ", AIDirectX }, + + { "eor ", AConstant }, + { "eor ", DirectDirect }, + { "and1 ", CAbsoluteBit }, + { "lsr ", Direct }, + + { "lsr ", Absolute }, + { "push ", X }, + { "tclr ", AbsoluteA }, + { "pcall", PVector }, + + //0x50 - 0x5f + { "bvc ", Relative }, + { "tcall", TVector }, + { "clr2 ", Direct }, + { "bbc2 ", DirectRelative }, + + { "eor ", ADirectX }, + { "eor ", AAbsoluteX }, + { "eor ", AAbsoluteY }, + { "eor ", AIDirectY }, + + { "eor ", DirectConstant }, + { "eor ", IXIY }, + { "cmpw ", YaDirect }, + { "lsr ", DirectX }, + + { "lsr ", A }, + { "mov ", XA }, + { "cmp ", YAbsolute }, + { "jmp ", Absolute }, + + //0x60 - 0x6f + { "clrc ", Implied }, + { "tcall", TVector }, + { "set3 ", Direct }, + { "bbs3 ", DirectRelative }, + + { "cmp ", ADirect }, + { "cmp ", AAbsolute }, + { "cmp ", AIX }, + { "cmp ", AIDirectX }, + + { "cmp ", AConstant }, + { "cmp ", DirectDirect }, + { "and1 ", CNAbsoluteBit }, + { "ror ", Direct }, + + { "ror ", Absolute }, + { "push ", Y }, + { "dbnz ", DirectRelative }, + { "ret ", Implied }, + + //0x70 - 0x7f + { "bvs ", Relative }, + { "tcall", TVector }, + { "clr3 ", Direct }, + { "bbc3 ", DirectRelative }, + + { "cmp ", ADirectX }, + { "cmp ", AAbsoluteX }, + { "cmp ", AAbsoluteY }, + { "cmp ", AIDirectY }, + + { "cmp ", DirectConstant }, + { "cmp ", IXIY }, + { "addw ", YaDirect }, + { "ror ", DirectX }, + + { "ror ", A }, + { "mov ", AX }, + { "cmp ", YDirect }, + { "reti ", Implied }, + + //0x80 - 0x8f + { "setc ", Implied }, + { "tcall", TVector }, + { "set4 ", Direct }, + { "bbs4 ", DirectRelative }, + + { "adc ", ADirect }, + { "adc ", AAbsolute }, + { "adc ", AIX }, + { "adc ", AIDirectX }, + + { "adc ", AConstant }, + { "adc ", DirectDirect }, + { "eor1 ", CAbsoluteBit }, + { "dec ", Direct }, + + { "dec ", Absolute }, + { "mov ", YConstant }, + { "pop ", P }, + { "mov ", DirectConstant }, + + //0x90 - 0x9f + { "bcc ", Relative }, + { "tcall", TVector }, + { "clr4 ", Direct }, + { "bbc4 ", DirectRelative }, + + { "adc ", ADirectX }, + { "adc ", AAbsoluteX }, + { "adc ", AAbsoluteY }, + { "adc ", AIDirectY }, + + { "adc ", DirectRelative }, + { "adc ", IXIY }, + { "subw ", YaDirect }, + { "dec ", DirectX }, + + { "dec ", A }, + { "mov ", XSp }, + { "div ", YaX }, + { "xcn ", A }, + + //0xa0 - 0xaf + { "ei ", Implied }, + { "tcall", TVector }, + { "set5 ", Direct }, + { "bbs5 ", DirectRelative }, + + { "sbc ", ADirect }, + { "sbc ", AAbsolute }, + { "sbc ", AIX }, + { "sbc ", AIDirectX }, + + { "sbc ", AConstant }, + { "sbc ", DirectDirect }, + { "mov1 ", CAbsoluteBit }, + { "inc ", Direct }, + + { "inc ", Absolute }, + { "cmp ", YConstant }, + { "pop ", A }, + { "mov ", IXPA }, + + //0xb0 - 0xbf + { "bcs ", Relative }, + { "tcall", TVector }, + { "clr5 ", Direct }, + { "bbc5 ", DirectRelative }, + + { "sbc ", ADirectX }, + { "sbc ", AAbsoluteX }, + { "sbc ", AAbsoluteY }, + { "sbc ", AIDirectY }, + + { "sbc ", DirectConstant }, + { "sbc ", IXIY }, + { "movw ", YaDirect }, + { "inc ", DirectX }, + + { "inc ", A }, + { "mov ", SpX }, + { "das ", A }, + { "mov ", AIXP }, + + //0xc0 - 0xcf + { "di ", Implied }, + { "tcall", TVector }, + { "set6 ", Direct }, + { "bbs6 ", DirectRelative }, + + { "mov ", DirectA }, + { "mov ", AbsoluteA }, + { "mov ", IXA }, + { "mov ", IDirectXA }, + + { "cmp ", XConstant }, + { "mov ", AbsoluteX }, + { "mov1 ", AbsoluteBitC }, + { "mov ", DirectY }, + + { "mov ", AbsoluteY }, + { "mov ", XConstant }, + { "pop ", X }, + { "mul ", Ya }, + + //0xd0 - 0xdf + { "bne ", Relative }, + { "tcall", TVector }, + { "clr6 ", Relative }, + { "bbc6 ", DirectRelative }, + + { "mov ", DirectXA }, + { "mov ", AbsoluteXA }, + { "mov ", AbsoluteYA }, + { "mov ", IDirectYA }, + + { "mov ", DirectX }, + { "mov ", DirectYX }, + { "movw ", DirectYa }, + { "mov ", DirectXY }, + + { "dec ", Y }, + { "mov ", AY }, + { "cbne ", DirectXRelative }, + { "daa ", A }, + + //0xe0 - 0xef + { "clrv ", Implied }, + { "tcall", TVector }, + { "set7 ", Direct }, + { "bbs7 ", DirectRelative }, + + { "mov ", ADirect }, + { "mov ", AAbsolute }, + { "mov ", AIX }, + { "mov ", AIDirectX }, + + { "mov ", AConstant }, + { "mov ", XAbsolute }, + { "not1 ", CAbsoluteBit }, + { "mov ", YDirect }, + + { "mov ", YAbsolute }, + { "notc ", Implied }, + { "pop ", Y }, + { "sleep", Implied }, + + //0xf0 - 0xff + { "beq ", Relative }, + { "tcall", TVector }, + { "clr7 ", Direct }, + { "bbc7 ", DirectRelative }, + + { "mov ", ADirectX }, + { "mov ", AAbsoluteX }, + { "mov ", AAbsoluteY }, + { "mov ", AIDirectY }, + + { "mov ", XDirect }, + { "mov ", XDirectY }, + { "mov ", DirectDirect }, + { "mov ", YDirectX }, + + { "inc ", Y }, + { "mov ", YA }, + { "dbz ", YRelative }, + { "stop ", Implied }, +}; + +inline unsigned SNESSMP::getOpcodeLength(uint8_t opcode) { + switch(opcodeInfo[opcode].mode) { default: + case Implied: return 1; // + case TVector: return 1; //0 + case Direct: return 2; //$00 + case DirectRelative: return 3; //$00,+/-$00 + case ADirect: return 2; //a,$00 + case AAbsolute: return 3; //a,$0000 + case AIX: return 1; //a,(x) + case AIDirectX: return 2; //a,($00+x) + case AConstant: return 2; //a,#$00 + case DirectDirect: return 3; //$00,$00 + case CAbsoluteBit: return 3; //c,$0000:0 + case Absolute: return 3; //$0000 + case P: return 1; //p + case AbsoluteA: return 3; //$0000,a + case Relative: return 2; //+/-$00 + case ADirectX: return 2; //a,$00+x + case AAbsoluteX: return 3; //a,$0000+x + case AAbsoluteY: return 3; //a,$0000+y + case AIDirectY: return 2; //a,($00)+y + case DirectConstant: return 3; //$00,#$00 + case IXIY: return 1; //(x),(y) + case DirectX: return 2; //$00+x + case A: return 1; //a + case X: return 1; //x + case XAbsolute: return 3; //x,$0000 + case IAbsoluteX: return 3; //($0000+x) + case CNAbsoluteBit: return 3; //c,!$0000:0 + case XDirect: return 2; //x,$00 + case PVector: return 2; //$ff00 + case YaDirect: return 2; //ya,$00 + case XA: return 1; //x,a + case YAbsolute: return 3; //y,$0000 + case Y: return 1; //y + case AX: return 1; //a,x + case YDirect: return 2; //y,$00 + case YConstant: return 2; //y,#$00 + case XSp: return 1; //x,sp + case YaX: return 1; //ya,x + case IXPA: return 1; //(x)+,a + case SpX: return 1; //sp,x + case AIXP: return 1; //a,(x)+ + case DirectA: return 2; //$00,a + case IXA: return 1; //(x),a + case IDirectXA: return 2; //($00+x),a + case XConstant: return 2; //x,#$00 + case AbsoluteX: return 3; //$0000,x + case AbsoluteBitC: return 3; //$0000:0,c + case DirectY: return 2; //$00,y + case AbsoluteY: return 3; //$0000,y + case Ya: return 1; //ya + case DirectXA: return 2; //$00+x,a + case AbsoluteXA: return 3; //$0000+x,a + case AbsoluteYA: return 3; //$0000+y,a + case IDirectYA: return 2; //($00)+y,a + case DirectYX: return 2; //$00+y,x + case DirectYa: return 2; //$00,ya + case DirectXY: return 2; //$00+x,y + case AY: return 1; //a,y + case DirectXRelative: return 3; //$00+x,+/-$00 + case XDirectY: return 2; //x,$00+y + case YDirectX: return 2; //y,$00+x + case YA: return 1; //y,a + case YRelative: return 2; //y,+/-$00 + } +} + +inline string SNESSMP::disassemble(uint16_t pc, uint8_t opcode, uint8_t pl, uint8_t ph) { + string name = opcodeInfo[opcode].name; + unsigned mode = opcodeInfo[opcode].mode; + unsigned pa = (ph << 8) + pl; + + if(mode == Implied) return name; + if(mode == TVector) return { name, " ", opcode >> 4 }; + if(mode == Direct) return { name, " $", hex<2>(pl) }; + if(mode == DirectRelative) return { name, " $", hex<2>(pl), ",$", hex<4>(pc + 3 + (int8_t)ph) }; + if(mode == ADirect) return { name, " a,$", hex<2>(pl) }; + if(mode == AAbsolute) return { name, " a,$", hex<4>(pa) }; + if(mode == AIX) return { name, "a,(x)" }; + if(mode == AIDirectX) return { name, " a,($", hex<2>(pl), "+x)" }; + if(mode == AConstant) return { name, " a,#$", hex<2>(pl) }; + if(mode == DirectDirect) return { name, " $", hex<2>(ph), ",$", hex<2>(pl) }; + if(mode == CAbsoluteBit) return { name, " c,$", hex<4>(pa & 0x1fff), ":", pa >> 13 }; + if(mode == Absolute) return { name, " $", hex<4>(pa) }; + if(mode == P) return { name, " p" }; + if(mode == AbsoluteA) return { name, " $", hex<4>(pa), ",a" }; + if(mode == Relative) return { name, " $", hex<4>(pc + 2 + (int8_t)pl) }; + if(mode == ADirectX) return { name, " a,$", hex<2>(pl), "+x" }; + if(mode == AAbsoluteX) return { name, " a,$", hex<4>(pa), "+x" }; + if(mode == AAbsoluteY) return { name, " a,$", hex<4>(pa), "+y" }; + if(mode == AIDirectY) return { name, " a,($", hex<2>(pl), ")+y" }; + if(mode == DirectConstant) return { name, " $", hex<2>(ph), ",#$", hex<2>(pl) }; + if(mode == IXIY) return { name, " (x),(y)" }; + if(mode == DirectX) return { name, " $", hex<2>(pl), "+x" }; + if(mode == A) return { name, " a" }; + if(mode == X) return { name, " x" }; + if(mode == XAbsolute) return { name, " x,$", hex<4>(pa) }; + if(mode == IAbsoluteX) return { name, " ($", hex<4>(pa), "+x)" }; + if(mode == CNAbsoluteBit) return { name, " c,!$", hex<4>(pa & 0x1fff), ":", pa >> 13 }; + if(mode == XDirect) return { name, " x,$", hex<2>(pl) }; + if(mode == PVector) return { name, " $ff", hex<2>(pl) }; + if(mode == YaDirect) return { name, " ya,$", hex<2>(pl) }; + if(mode == XA) return { name, " x,a" }; + if(mode == YAbsolute) return { name, " y,$", hex<4>(pa) }; + if(mode == Y) return { name, " y" }; + if(mode == AX) return { name, " a,x" }; + if(mode == YDirect) return { name, " y,$", hex<2>(pl) }; + if(mode == YConstant) return { name, " y,#$", hex<2>(pl) }; + if(mode == XSp) return { name, " x,sp" }; + if(mode == YaX) return { name, " ya,x" }; + if(mode == IXPA) return { name, " (x)+,a" }; + if(mode == SpX) return { name, " sp,x" }; + if(mode == AIXP) return { name, " a,(x)+" }; + if(mode == DirectA) return { name, " $", hex<2>(pl), ",a" }; + if(mode == IXA) return { name, " (x),a" }; + if(mode == IDirectXA) return { name, " ($", hex<2>(pl), "+x),a" }; + if(mode == XConstant) return { name, " x,#$", hex<2>(pl) }; + if(mode == AbsoluteX) return { name, " $", hex<4>(pa), ",x" }; + if(mode == AbsoluteBitC) return { name, " $", hex<4>(pa & 0x1fff), ":", pa >> 13, ",c" }; + if(mode == DirectY) return { name, " $", hex<2>(pl), ",y" }; + if(mode == AbsoluteY) return { name, " $", hex<4>(pa), ",y" }; + if(mode == Ya) return { name, " ya" }; + if(mode == DirectXA) return { name, " $", hex<2>(pl), "+x,a" }; + if(mode == AbsoluteXA) return { name, " $", hex<4>(pa), "+x,a" }; + if(mode == AbsoluteYA) return { name, " $", hex<4>(pa), "+y,a" }; + if(mode == IDirectYA) return { name, " ($", hex<2>(pl), ")+y,a" }; + if(mode == DirectYX) return { name, " $", hex<2>(pl), "+y,x" }; + if(mode == DirectYa) return { name, " $", hex<2>(pl), ",ya" }; + if(mode == DirectXY) return { name, " $", hex<2>(pl), "+x,y" }; + if(mode == AY) return { name, " a,y" }; + if(mode == DirectXRelative) return { name, " $", hex<2>(pl), ",$", hex<4>(pc + 3 + (int8_t)ph) }; + if(mode == XDirectY) return { name, " x,$", hex<2>(pl), "+y" }; + if(mode == YDirectX) return { name, " y,$", hex<2>(pl), "+x" }; + if(mode == YA) return { name, " y,a" }; + if(mode == YRelative) return { name, " y,$", hex<4>(pc + 2 + (int8_t)pl) }; + + return ""; +} + +inline string SNESSMP::disassemble(uint16_t pc, bool p, uint8_t opcode, uint8_t pl, uint8_t ph) { + string name = opcodeInfo[opcode].name; + unsigned mode = opcodeInfo[opcode].mode; + unsigned pdl = (p << 8) + pl; + unsigned pdh = (p << 8) + ph; + unsigned pa = (ph << 8) + pl; + + if(mode == Implied) return name; + if(mode == TVector) return { name, " ", opcode >> 4 }; + if(mode == Direct) return { name, " $", hex<3>(pdl) }; + if(mode == DirectRelative) return { name, " $", hex<3>(pdl), ",$", hex<4>(pc + 3 + (int8_t)ph) }; + if(mode == ADirect) return { name, " a,$", hex<3>(pdl) }; + if(mode == AAbsolute) return { name, " a,$", hex<4>(pa) }; + if(mode == AIX) return { name, "a,(x)" }; + if(mode == AIDirectX) return { name, " a,($", hex<3>(pdl), "+x)" }; + if(mode == AConstant) return { name, " a,#$", hex<2>(pl) }; + if(mode == DirectDirect) return { name, " $", hex<3>(pdh), ",$", hex<3>(pdl) }; + if(mode == CAbsoluteBit) return { name, " c,$", hex<4>(pa & 0x1fff), ":", pa >> 13 }; + if(mode == Absolute) return { name, " $", hex<4>(pa) }; + if(mode == P) return { name, " p" }; + if(mode == AbsoluteA) return { name, " $", hex<4>(pa), ",a" }; + if(mode == Relative) return { name, " $", hex<4>(pc + 2 + (int8_t)pl) }; + if(mode == ADirectX) return { name, " a,$", hex<3>(pdl), "+x" }; + if(mode == AAbsoluteX) return { name, " a,$", hex<4>(pa), "+x" }; + if(mode == AAbsoluteY) return { name, " a,$", hex<4>(pa), "+y" }; + if(mode == AIDirectY) return { name, " a,($", hex<3>(pdl), ")+y" }; + if(mode == DirectConstant) return { name, " $", hex<3>(pdh), ",#$", hex<2>(pl) }; + if(mode == IXIY) return { name, " (x),(y)" }; + if(mode == DirectX) return { name, " $", hex<3>(pdl), "+x" }; + if(mode == A) return { name, " a" }; + if(mode == X) return { name, " x" }; + if(mode == XAbsolute) return { name, " x,$", hex<4>(pa) }; + if(mode == IAbsoluteX) return { name, " ($", hex<4>(pa), "+x)" }; + if(mode == CNAbsoluteBit) return { name, " c,!$", hex<4>(pa & 0x1fff), ":", pa >> 13 }; + if(mode == XDirect) return { name, " x,$", hex<3>(pdl) }; + if(mode == PVector) return { name, " $ff", hex<2>(pl) }; + if(mode == YaDirect) return { name, " ya,$", hex<3>(pdl) }; + if(mode == XA) return { name, " x,a" }; + if(mode == YAbsolute) return { name, " y,$", hex<4>(pa) }; + if(mode == Y) return { name, " y" }; + if(mode == AX) return { name, " a,x" }; + if(mode == YDirect) return { name, " y,$", hex<3>(pdl) }; + if(mode == YConstant) return { name, " y,#$", hex<2>(pl) }; + if(mode == XSp) return { name, " x,sp" }; + if(mode == YaX) return { name, " ya,x" }; + if(mode == IXPA) return { name, " (x)+,a" }; + if(mode == SpX) return { name, " sp,x" }; + if(mode == AIXP) return { name, " a,(x)+" }; + if(mode == DirectA) return { name, " $", hex<3>(pdl), ",a" }; + if(mode == IXA) return { name, " (x),a" }; + if(mode == IDirectXA) return { name, " ($", hex<3>(pdl), "+x),a" }; + if(mode == XConstant) return { name, " x,#$", hex<2>(pl) }; + if(mode == AbsoluteX) return { name, " $", hex<4>(pa), ",x" }; + if(mode == AbsoluteBitC) return { name, " $", hex<4>(pa & 0x1fff), ":", pa >> 13, ",c" }; + if(mode == DirectY) return { name, " $", hex<3>(pdl), ",y" }; + if(mode == AbsoluteY) return { name, " $", hex<4>(pa), ",y" }; + if(mode == Ya) return { name, " ya" }; + if(mode == DirectXA) return { name, " $", hex<3>(pdl), "+x,a" }; + if(mode == AbsoluteXA) return { name, " $", hex<4>(pa), "+x,a" }; + if(mode == AbsoluteYA) return { name, " $", hex<4>(pa), "+y,a" }; + if(mode == IDirectYA) return { name, " ($", hex<3>(pdl), ")+y,a" }; + if(mode == DirectYX) return { name, " $", hex<3>(pdl), "+y,x" }; + if(mode == DirectYa) return { name, " $", hex<3>(pdl), ",ya" }; + if(mode == DirectXY) return { name, " $", hex<3>(pdl), "+x,y" }; + if(mode == AY) return { name, " a,y" }; + if(mode == DirectXRelative) return { name, " $", hex<3>(pdl), ",$", hex<4>(pc + 3 + (int8_t)ph) }; + if(mode == XDirectY) return { name, " x,$", hex<3>(pdl), "+y" }; + if(mode == YDirectX) return { name, " y,$", hex<3>(pdl), "+x" }; + if(mode == YA) return { name, " y,a" }; + if(mode == YRelative) return { name, " y,$", hex<4>(pc + 2 + (int8_t)pl) }; + + return ""; +} + +} + +#endif diff --git a/nall/sort.hpp b/nall/sort.hpp new file mode 100755 index 00000000..23c317a5 --- /dev/null +++ b/nall/sort.hpp @@ -0,0 +1,62 @@ +#ifndef NALL_SORT_HPP +#define NALL_SORT_HPP + +#include + +//class: merge sort +//average: O(n log n) +//worst: O(n log n) +//memory: O(n) +//stack: O(log n) +//stable?: yes + +//notes: +//there are two primary reasons for choosing merge sort +//over the (usually) faster quick sort*: +//1: it is a stable sort. +//2: it lacks O(n^2) worst-case overhead. +//(* which is also O(n log n) in the average case.) + +namespace nall { + template + void sort(T list[], unsigned length) { + if(length <= 1) return; //nothing to sort + + //use insertion sort to quickly sort smaller blocks + if(length < 64) { + for(unsigned i = 0; i < length; i++) { + unsigned min = i; + for(unsigned j = i + 1; j < length; j++) { + if(list[j] < list[min]) min = j; + } + if(min != i) swap(list[i], list[min]); + } + return; + } + + //split list in half and recursively sort both + unsigned middle = length / 2; + sort(list, middle); + sort(list + middle, length - middle); + + //left and right are sorted here; perform merge sort + T *buffer = new T[length]; + unsigned offset = 0; + unsigned left = 0; + unsigned right = middle; + while(left < middle && right < length) { + if(list[left] < list[right]) { + buffer[offset++] = list[left++]; + } else { + buffer[offset++] = list[right++]; + } + } + while(left < middle) buffer[offset++] = list[left++]; + while(right < length) buffer[offset++] = list[right++]; + + for(unsigned i = 0; i < length; i++) list[i] = buffer[i]; + delete[] buffer; + } +} + +#endif diff --git a/nall/static.hpp b/nall/static.hpp new file mode 100755 index 00000000..4acb9fd0 --- /dev/null +++ b/nall/static.hpp @@ -0,0 +1,20 @@ +#ifndef NALL_STATIC_HPP +#define NALL_STATIC_HPP + +namespace nall { + template struct static_if { typedef T type; }; + template struct static_if { typedef F type; }; + template struct mp_static_if { typedef typename static_if::type type; }; + + template struct static_and { enum { value = false }; }; + template<> struct static_and { enum { value = true }; }; + template struct mp_static_and { enum { value = static_and::value }; }; + + template struct static_or { enum { value = false }; }; + template<> struct static_or { enum { value = true }; }; + template<> struct static_or { enum { value = true }; }; + template<> struct static_or { enum { value = true }; }; + template struct mp_static_or { enum { value = static_or::value }; }; +} + +#endif diff --git a/nall/stdint.hpp b/nall/stdint.hpp new file mode 100755 index 00000000..d8b6c788 --- /dev/null +++ b/nall/stdint.hpp @@ -0,0 +1,44 @@ +#ifndef NALL_STDINT_HPP +#define NALL_STDINT_HPP + +#include + +#if defined(_MSC_VER) + typedef signed char int8_t; + typedef signed short int16_t; + typedef signed int int32_t; + typedef signed long long int64_t; + typedef int64_t intmax_t; + #if defined(_WIN64) + typedef int64_t intptr_t; + #else + typedef int32_t intptr_t; + #endif + + typedef unsigned char uint8_t; + typedef unsigned short uint16_t; + typedef unsigned int uint32_t; + typedef unsigned long long uint64_t; + typedef uint64_t uintmax_t; + #if defined(_WIN64) + typedef uint64_t uintptr_t; + #else + typedef uint32_t uintptr_t; + #endif +#else + #include +#endif + +namespace nall { + static_assert(sizeof(int8_t) == 1, "int8_t is not of the correct size" ); + static_assert(sizeof(int16_t) == 2, "int16_t is not of the correct size"); + static_assert(sizeof(int32_t) == 4, "int32_t is not of the correct size"); + static_assert(sizeof(int64_t) == 8, "int64_t is not of the correct size"); + + static_assert(sizeof(uint8_t) == 1, "int8_t is not of the correct size" ); + static_assert(sizeof(uint16_t) == 2, "int16_t is not of the correct size"); + static_assert(sizeof(uint32_t) == 4, "int32_t is not of the correct size"); + static_assert(sizeof(uint64_t) == 8, "int64_t is not of the correct size"); +} + +#endif diff --git a/nall/string.hpp b/nall/string.hpp new file mode 100755 index 00000000..9acc2e9d --- /dev/null +++ b/nall/string.hpp @@ -0,0 +1,32 @@ +#ifndef NALL_STRING_HPP +#define NALL_STRING_HPP + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace nall { + template<> struct has_length { enum { value = true }; }; + template<> struct has_size { enum { value = true }; }; +} + +#endif diff --git a/nall/string/base.hpp b/nall/string/base.hpp new file mode 100755 index 00000000..f2e307c0 --- /dev/null +++ b/nall/string/base.hpp @@ -0,0 +1,159 @@ +#ifndef NALL_STRING_BASE_HPP +#define NALL_STRING_BASE_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace nall { + class string; + template inline string to_string(T); + + class string { + public: + inline void reserve(unsigned); + + inline string& assign(const char*); + inline string& append(const char*); + inline string& append(bool); + inline string& append(signed int value); + inline string& append(unsigned int value); + inline string& append(double value); + + inline bool readfile(const char*); + + inline string& replace (const char*, const char*); + inline string& qreplace(const char*, const char*); + + inline unsigned length() const; + + inline bool equals(const char*) const; + inline bool iequals(const char*) const; + + inline bool wildcard(const char*) const; + inline bool iwildcard(const char*) const; + + inline bool beginswith(const char*) const; + inline bool ibeginswith(const char*) const; + inline bool endswith(const char*) const; + inline bool iendswith(const char*) const; + + inline string& lower(); + inline string& upper(); + inline string& transform(const char *before, const char *after); + + template inline string& ltrim(const char *key = " "); + template inline string& rtrim(const char *key = " "); + template inline string& trim (const char *key = " "); + + inline optional position(const char *key) const; + inline optional qposition(const char *key) const; + + template inline string& operator= (T value); + template inline string& operator<<(T value); + + inline operator const char*() const; + inline char* operator()(); + inline char& operator[](int); + + inline bool operator==(const char*) const; + inline bool operator!=(const char*) const; + inline bool operator< (const char*) const; + inline bool operator<=(const char*) const; + inline bool operator> (const char*) const; + inline bool operator>=(const char*) const; + + inline string& operator=(const string&); + inline string& operator=(string&&); + + template inline string(Args&&... args); + inline string(const string&); + inline string(string&&); + inline ~string(); + + protected: + char *data; + unsigned size; + + #if defined(QSTRING_H) + public: + inline operator QString() const; + #endif + }; + + class lstring : public linear_vector { + public: + template inline lstring& operator<<(T value); + + inline optional find(const char*) const; + template inline void split (const char*, const char*); + template inline void qsplit(const char*, const char*); + + lstring(); + lstring(std::initializer_list); + }; + + //compare.hpp + inline char chrlower(char c); + inline char chrupper(char c); + inline int stricmp(const char *str1, const char *str2); + inline bool wildcard(const char *str, const char *pattern); + inline bool iwildcard(const char *str, const char *pattern); + inline bool strbegin (const char *str, const char *key); + inline bool stribegin(const char *str, const char *key); + inline bool strend (const char *str, const char *key); + inline bool striend(const char *str, const char *key); + + //convert.hpp + inline char* strlower(char *str); + inline char* strupper(char *str); + inline char* strtr(char *dest, const char *before, const char *after); + inline uintmax_t hex (const char *str); + inline intmax_t integer(const char *str); + inline uintmax_t decimal(const char *str); + inline uintmax_t binary (const char *str); + inline double fp (const char *str); + + //math.hpp + inline bool strint (const char *str, int &result); + inline bool strmath(const char *str, int &result); + + //platform.hpp + inline string realpath(const char *name); + inline string userpath(); + inline string currentpath(); + + //strl.hpp + inline unsigned strlcpy(char *dest, const char *src, unsigned length); + inline unsigned strlcat(char *dest, const char *src, unsigned length); + + //strpos.hpp + inline optional strpos(const char *str, const char *key); + inline optional qstrpos(const char *str, const char *key); + + //trim.hpp + template inline char* ltrim(char *str, const char *key = " "); + template inline char* rtrim(char *str, const char *key = " "); + template inline char* trim (char *str, const char *key = " "); + + //utility.hpp + inline unsigned strlcpy(string &dest, const char *src, unsigned length); + inline unsigned strlcat(string &dest, const char *src, unsigned length); + inline string substr(const char *src, unsigned start = 0, unsigned length = 0); + template inline string hex(uintmax_t value); + template inline string integer(intmax_t value); + template inline string decimal(uintmax_t value); + template inline string binary(uintmax_t value); + inline unsigned fp(char *str, double value); + inline string fp(double value); + + //variadic.hpp + template inline void print(Args&&... args); +}; + +#endif diff --git a/nall/string/bsv.hpp b/nall/string/bsv.hpp new file mode 100755 index 00000000..d4b919e0 --- /dev/null +++ b/nall/string/bsv.hpp @@ -0,0 +1,75 @@ +#ifndef NALL_STRING_BSV_HPP +#define NALL_STRING_BSV_HPP + +//BSV parser +//version 0.01 + +namespace nall { + +inline string bsv_decode(const char *input) { + string output; + unsigned offset = 0; + while(*input) { + //illegal characters + if(*input == '}' ) return ""; + if(*input == '\r') return ""; + if(*input == '\n') return ""; + + //normal characters + if(*input != '{') { output[offset++] = *input++; continue; } + + //entities + if(strbegin(input, "{lf}")) { output[offset++] = '\n'; input += 4; continue; } + if(strbegin(input, "{lb}")) { output[offset++] = '{'; input += 4; continue; } + if(strbegin(input, "{rb}")) { output[offset++] = '}'; input += 4; continue; } + + //illegal entities + return ""; + } + output[offset] = 0; + return output; +} + +inline string bsv_encode(const char *input) { + string output; + unsigned offset = 0; + while(*input) { + //illegal characters + if(*input == '\r') return ""; + + if(*input == '\n') { + output[offset++] = '{'; + output[offset++] = 'l'; + output[offset++] = 'f'; + output[offset++] = '}'; + input++; + continue; + } + + if(*input == '{') { + output[offset++] = '{'; + output[offset++] = 'l'; + output[offset++] = 'b'; + output[offset++] = '}'; + input++; + continue; + } + + if(*input == '}') { + output[offset++] = '{'; + output[offset++] = 'r'; + output[offset++] = 'b'; + output[offset++] = '}'; + input++; + continue; + } + + output[offset++] = *input++; + } + output[offset] = 0; + return output; +} + +} + +#endif diff --git a/nall/string/cast.hpp b/nall/string/cast.hpp new file mode 100755 index 00000000..d8503106 --- /dev/null +++ b/nall/string/cast.hpp @@ -0,0 +1,32 @@ +#ifndef NALL_STRING_CAST_HPP +#define NALL_STRING_CAST_HPP + +namespace nall { + +//this is needed, as C++0x does not support explicit template specialization inside classes +template<> inline string to_string (bool v) { return v ? "true" : "false"; } +template<> inline string to_string (signed int v) { return integer(v); } +template<> inline string to_string (unsigned int v) { return decimal(v); } +template<> inline string to_string (double v) { return fp(v); } +template<> inline string to_string (char *v) { return v; } +template<> inline string to_string (const char *v) { return v; } +template<> inline string to_string (string v) { return v; } +template<> inline string to_string(const string &v) { return v; } + +template string& string::operator= (T value) { return assign(to_string(value)); } +template string& string::operator<<(T value) { return append(to_string(value)); } + +template lstring& lstring::operator<<(T value) { + operator[](size()).assign(to_string(value)); + return *this; +} + +#if defined(QSTRING_H) +template<> inline string to_string(QString v) { return v.toUtf8().constData(); } +template<> inline string to_string(const QString &v) { return v.toUtf8().constData(); } +string::operator QString() const { return QString::fromUtf8(*this); } +#endif + +} + +#endif diff --git a/nall/string/compare.hpp b/nall/string/compare.hpp new file mode 100755 index 00000000..bce0895b --- /dev/null +++ b/nall/string/compare.hpp @@ -0,0 +1,110 @@ +#ifndef NALL_STRING_COMPARE_HPP +#define NALL_STRING_COMPARE_HPP + +namespace nall { + +char chrlower(char c) { + return (c >= 'A' && c <= 'Z') ? c + ('a' - 'A') : c; +} + +char chrupper(char c) { + return (c >= 'a' && c <= 'z') ? c - ('a' - 'A') : c; +} + +int stricmp(const char *str1, const char *str2) { + while(*str1) { + if(chrlower(*str1) != chrlower(*str2)) break; + str1++, str2++; + } + return (int)chrlower(*str1) - (int)chrlower(*str2); +} + +bool wildcard(const char *s, const char *p) { + const char *cp = 0, *mp = 0; + while(*s && *p != '*') { + if(*p != '?' && *s != *p) return false; + p++, s++; + } + while(*s) { + if(*p == '*') { + if(!*++p) return true; + mp = p, cp = s + 1; + } else if(*p == '?' || *p == *s) { + p++, s++; + } else { + p = mp, s = cp++; + } + } + while(*p == '*') p++; + return !*p; +} + +bool iwildcard(const char *s, const char *p) { + const char *cp = 0, *mp = 0; + while(*s && *p != '*') { + if(*p != '?' && chrlower(*s) != chrlower(*p)) return false; + p++, s++; + } + while(*s) { + if(*p == '*') { + if(!*++p) return true; + mp = p, cp = s + 1; + } else if(*p == '?' || chrlower(*p) == chrlower(*s)) { + p++, s++; + } else { + p = mp, s = cp++; + } + } + while(*p == '*') p++; + return !*p; +} + +bool strbegin(const char *str, const char *key) { + int i, ssl = strlen(str), ksl = strlen(key); + + if(ksl > ssl) return false; + return (!memcmp(str, key, ksl)); +} + +bool stribegin(const char *str, const char *key) { + int ssl = strlen(str), ksl = strlen(key); + + if(ksl > ssl) return false; + for(int i = 0; i < ksl; i++) { + if(str[i] >= 'A' && str[i] <= 'Z') { + if(str[i] != key[i] && str[i]+0x20 != key[i])return false; + } else if(str[i] >= 'a' && str[i] <= 'z') { + if(str[i] != key[i] && str[i]-0x20 != key[i])return false; + } else { + if(str[i] != key[i])return false; + } + } + return true; +} + +bool strend(const char *str, const char *key) { + int ssl = strlen(str), ksl = strlen(key); + + if(ksl > ssl) return false; + return (!memcmp(str + ssl - ksl, key, ksl)); +} + +bool striend(const char *str, const char *key) { + int ssl = strlen(str), ksl = strlen(key); + + if(ksl > ssl) return false; + for(int i = ssl - ksl, z = 0; i < ssl; i++, z++) { + if(str[i] >= 'A' && str[i] <= 'Z') { + if(str[i] != key[z] && str[i]+0x20 != key[z])return false; + } else if(str[i] >= 'a' && str[i] <= 'z') { + if(str[i] != key[z] && str[i]-0x20 != key[z])return false; + } else { + if(str[i] != key[z])return false; + } + } + return true; +} + +} + +#endif diff --git a/nall/string/convert.hpp b/nall/string/convert.hpp new file mode 100755 index 00000000..9040cb83 --- /dev/null +++ b/nall/string/convert.hpp @@ -0,0 +1,153 @@ +#ifndef NALL_STRING_CONVERT_HPP +#define NALL_STRING_CONVERT_HPP + +namespace nall { + +char* strlower(char *str) { + if(!str) return 0; + int i = 0; + while(str[i]) { + str[i] = chrlower(str[i]); + i++; + } + return str; +} + +char* strupper(char *str) { + if(!str) return 0; + int i = 0; + while(str[i]) { + str[i] = chrupper(str[i]); + i++; + } + return str; +} + +char* strtr(char *dest, const char *before, const char *after) { + if(!dest || !before || !after) return dest; + int sl = strlen(dest), bsl = strlen(before), asl = strlen(after); + + if(bsl != asl || bsl == 0) return dest; //patterns must be the same length for 1:1 replace + for(unsigned i = 0; i < sl; i++) { + for(unsigned l = 0; l < bsl; l++) { + if(dest[i] == before[l]) { + dest[i] = after[l]; + break; + } + } + } + + return dest; +} + +uintmax_t hex(const char *str) { + if(!str) return 0; + uintmax_t result = 0; + + //skip hex identifiers 0x and $, if present + if(*str == '0' && (*(str + 1) == 'X' || *(str + 1) == 'x')) str += 2; + else if(*str == '$') str++; + + while(*str) { + uint8_t x = *str++; + if(x >= '0' && x <= '9') x -= '0'; + else if(x >= 'A' && x <= 'F') x -= 'A' - 10; + else if(x >= 'a' && x <= 'f') x -= 'a' - 10; + else break; //stop at first invalid character + result = result * 16 + x; + } + + return result; +} + +intmax_t integer(const char *str) { + if(!str) return 0; + intmax_t result = 0; + bool negate = false; + + //check for negation + if(*str == '-') { + negate = true; + str++; + } + + while(*str) { + uint8_t x = *str++; + if(x >= '0' && x <= '9') x -= '0'; + else break; //stop at first invalid character + result = result * 10 + x; + } + + return !negate ? result : -result; +} + +uintmax_t decimal(const char *str) { + if(!str) return 0; + uintmax_t result = 0; + + while(*str) { + uint8_t x = *str++; + if(x >= '0' && x <= '9') x -= '0'; + else break; //stop at first invalid character + result = result * 10 + x; + } + + return result; +} + +uintmax_t binary(const char *str) { + if(!str) return 0; + uintmax_t result = 0; + + //skip bin identifiers 0b and %, if present + if(*str == '0' && (*(str + 1) == 'B' || *(str + 1) == 'b')) str += 2; + else if(*str == '%') str++; + + while(*str) { + uint8_t x = *str++; + if(x == '0' || x == '1') x -= '0'; + else break; //stop at first invalid character + result = result * 2 + x; + } + + return result; +} + +double fp(const char *str) { + if(!str) return 0.0; + bool negate = false; + + //check for negation + if(*str == '-') { + negate = true; + str++; + } + + intmax_t result_integral = 0; + while(*str) { + uint8_t x = *str++; + if(x >= '0' && x <= '9') x -= '0'; + else if(x == '.' || x == ',') break; //break loop and read fractional part + else return (double)result_integral; //invalid value, assume no fractional part + result_integral = result_integral * 10 + x; + } + + intmax_t result_fractional = 0; + while(*str) { + uint8_t x = *str++; + if(x >= '0' && x <= '9') x -= '0'; + else break; //stop at first invalid character + result_fractional = result_fractional * 10 + x; + } + + //calculate fractional portion + double result = (double)result_fractional; + while((uintmax_t)result > 0) result /= 10.0; + result += (double)result_integral; + + return !negate ? result : -result; +} + +} + +#endif diff --git a/nall/string/core.hpp b/nall/string/core.hpp new file mode 100755 index 00000000..976ae9b3 --- /dev/null +++ b/nall/string/core.hpp @@ -0,0 +1,139 @@ +#ifndef NALL_STRING_CORE_HPP +#define NALL_STRING_CORE_HPP + +namespace nall { + +void string::reserve(unsigned size_) { + if(size_ > size) { + size = size_; + data = (char*)realloc(data, size + 1); + data[size] = 0; + } +} + +string& string::assign(const char *s) { + unsigned length = strlen(s); + reserve(length); + strcpy(data, s); + return *this; +} + +string& string::append(const char *s) { + unsigned length = strlen(data) + strlen(s); + reserve(length); + strcat(data, s); + return *this; +} + +string& string::append(bool value) { append(value ? "true" : "false"); return *this; } +string& string::append(signed int value) { append(integer(value)); return *this; } +string& string::append(unsigned int value) { append(decimal(value)); return *this; } +string& string::append(double value) { append(fp(value)); return *this; } + +string::operator const char*() const { + return data; +} + +char* string::operator()() { + return data; +} + +char& string::operator[](int index) { + reserve(index); + return data[index]; +} + +bool string::operator==(const char *str) const { return strcmp(data, str) == 0; } +bool string::operator!=(const char *str) const { return strcmp(data, str) != 0; } +bool string::operator< (const char *str) const { return strcmp(data, str) < 0; } +bool string::operator<=(const char *str) const { return strcmp(data, str) <= 0; } +bool string::operator> (const char *str) const { return strcmp(data, str) > 0; } +bool string::operator>=(const char *str) const { return strcmp(data, str) >= 0; } + +string& string::operator=(const string &value) { + assign(value); + return *this; +} + +string& string::operator=(string &&source) { + if(data) free(data); + size = source.size; + data = source.data; + source.data = 0; + source.size = 0; + return *this; +} + +static void istring(string &output) { +} + +template +static void istring(string &output, const T &value, Args&&... args) { + output.append(value); + istring(output, std::forward(args)...); +} + +template string::string(Args&&... args) { + size = 64; + data = (char*)malloc(size + 1); + *data = 0; + istring(*this, std::forward(args)...); +} + +string::string(const string &value) { + size = strlen(value); + data = strdup(value); +} + +string::string(string &&source) { + size = source.size; + data = source.data; + source.data = 0; +} + +string::~string() { + if(data) free(data); +} + +bool string::readfile(const char *filename) { + assign(""); + + #if !defined(_WIN32) + FILE *fp = fopen(filename, "rb"); + #else + FILE *fp = _wfopen(utf16_t(filename), L"rb"); + #endif + if(!fp) return false; + + fseek(fp, 0, SEEK_END); + unsigned size = ftell(fp); + rewind(fp); + char *fdata = new char[size + 1]; + unsigned unused = fread(fdata, 1, size, fp); + fclose(fp); + fdata[size] = 0; + assign(fdata); + delete[] fdata; + + return true; +} + +optional lstring::find(const char *key) const { + for(unsigned i = 0; i < size(); i++) { + if(operator[](i) == key) return { true, i }; + } + return { false, 0 }; +} + +inline lstring::lstring() { +} + +inline lstring::lstring(std::initializer_list list) { + for(const string *s = list.begin(); s != list.end(); ++s) { + operator<<(*s); + } +} + +} + +#endif diff --git a/nall/string/filename.hpp b/nall/string/filename.hpp new file mode 100755 index 00000000..93d605ae --- /dev/null +++ b/nall/string/filename.hpp @@ -0,0 +1,63 @@ +#ifndef NALL_FILENAME_HPP +#define NALL_FILENAME_HPP + +namespace nall { + +// "foo/bar.c" -> "foo/" +// "foo/" -> "foo/" +// "bar.c" -> "./" +inline string dir(char const *name) { + string result = name; + for(signed i = strlen(result); i >= 0; i--) { + if(result[i] == '/' || result[i] == '\\') { + result[i + 1] = 0; + break; + } + if(i == 0) result = "./"; + } + return result; +} + +// "foo/bar.c" -> "bar.c" +inline string notdir(char const *name) { + for(signed i = strlen(name); i >= 0; i--) { + if(name[i] == '/' || name[i] == '\\') { + name += i + 1; + break; + } + } + string result = name; + return result; +} + +// "foo/bar.c" -> "foo/bar" +inline string basename(char const *name) { + string result = name; + for(signed i = strlen(result); i >= 0; i--) { + if(result[i] == '/' || result[i] == '\\') { + //file has no extension + break; + } + if(result[i] == '.') { + result[i] = 0; + break; + } + } + return result; +} + +// "foo/bar.c" -> "c" +inline string extension(char const *name) { + for(signed i = strlen(name); i >= 0; i--) { + if(name[i] == '.') { + name += i + 1; + break; + } + } + string result = name; + return result; +} + +} + +#endif diff --git a/nall/string/math.hpp b/nall/string/math.hpp new file mode 100755 index 00000000..ea8b99c8 --- /dev/null +++ b/nall/string/math.hpp @@ -0,0 +1,164 @@ +#ifndef NALL_STRING_MATH_HPP +#define NALL_STRING_MATH_HPP + +namespace nall { + +static int eval_integer(const char *&s) { + if(!*s) throw "unrecognized_integer"; + int value = 0, x = *s, y = *(s + 1); + + //hexadecimal + if(x == '0' && (y == 'X' || y == 'x')) { + s += 2; + while(true) { + if(*s >= '0' && *s <= '9') { value = value * 16 + (*s++ - '0'); continue; } + if(*s >= 'A' && *s <= 'F') { value = value * 16 + (*s++ - 'A' + 10); continue; } + if(*s >= 'a' && *s <= 'f') { value = value * 16 + (*s++ - 'a' + 10); continue; } + return value; + } + } + + //binary + if(x == '0' && (y == 'B' || y == 'b')) { + s += 2; + while(true) { + if(*s == '0' || *s == '1') { value = value * 2 + (*s++ - '0'); continue; } + return value; + } + } + + //octal (or decimal '0') + if(x == '0') { + s += 1; + while(true) { + if(*s >= '0' && *s <= '7') { value = value * 8 + (*s++ - '0'); continue; } + return value; + } + } + + //decimal + if(x >= '0' && x <= '9') { + while(true) { + if(*s >= '0' && *s <= '9') { value = value * 10 + (*s++ - '0'); continue; } + return value; + } + } + + //char + if(x == '\'' && y != '\'') { + s += 1; + while(true) { + value = value * 256 + *s++; + if(*s == '\'') { s += 1; return value; } + if(!*s) throw "mismatched_char"; + } + } + + throw "unrecognized_integer"; +} + +static int eval(const char *&s, int depth = 0) { + while(*s == ' ' || *s == '\t') s++; //trim whitespace + if(!*s) throw "unrecognized_token"; + int value = 0, x = *s, y = *(s + 1); + + if(*s == '(') { + value = eval(++s, 1); + if(*s++ != ')') throw "mismatched_group"; + } + + else if(x == '!') value = !eval(++s, 13); + else if(x == '~') value = ~eval(++s, 13); + else if(x == '+') value = +eval(++s, 13); + else if(x == '-') value = -eval(++s, 13); + + else if((x >= '0' && x <= '9') || x == '\'') value = eval_integer(s); + + else throw "unrecognized_token"; + + while(true) { + while(*s == ' ' || *s == '\t') s++; //trim whitespace + if(!*s) break; + x = *s, y = *(s + 1); + + if(depth >= 13) break; + if(x == '*') { value *= eval(++s, 13); continue; } + if(x == '/') { value /= eval(++s, 13); continue; } + if(x == '%') { value %= eval(++s, 13); continue; } + + if(depth >= 12) break; + if(x == '+') { value += eval(++s, 12); continue; } + if(x == '-') { value -= eval(++s, 12); continue; } + + if(depth >= 11) break; + if(x == '<' && y == '<') { value <<= eval(++++s, 11); continue; } + if(x == '>' && y == '>') { value >>= eval(++++s, 11); continue; } + + if(depth >= 10) break; + if(x == '<' && y == '=') { value = value <= eval(++++s, 10); continue; } + if(x == '>' && y == '=') { value = value >= eval(++++s, 10); continue; } + if(x == '<') { value = value < eval(++s, 10); continue; } + if(x == '>') { value = value > eval(++s, 10); continue; } + + if(depth >= 9) break; + if(x == '=' && y == '=') { value = value == eval(++++s, 9); continue; } + if(x == '!' && y == '=') { value = value != eval(++++s, 9); continue; } + + if(depth >= 8) break; + if(x == '&' && y != '&') { value = value & eval(++s, 8); continue; } + + if(depth >= 7) break; + if(x == '^' && y != '^') { value = value ^ eval(++s, 7); continue; } + + if(depth >= 6) break; + if(x == '|' && y != '|') { value = value | eval(++s, 6); continue; } + + if(depth >= 5) break; + if(x == '&' && y == '&') { value = eval(++++s, 5) && value; continue; } + + if(depth >= 4) break; + if(x == '^' && y == '^') { value = (!eval(++++s, 4) != !value); continue; } + + if(depth >= 3) break; + if(x == '|' && y == '|') { value = eval(++++s, 3) || value; continue; } + + if(x == '?') { + int lhs = eval(++s, 2); + if(*s != ':') throw "mismatched_ternary"; + int rhs = eval(++s, 2); + value = value ? lhs : rhs; + continue; + } + if(depth >= 2) break; + + if(depth > 0 && x == ')') break; + + throw "unrecognized_token"; + } + + return value; +} + +bool strint(const char *s, int &result) { + try { + result = eval_integer(s); + return true; + } catch(const char*) { + result = 0; + return false; + } +} + +bool strmath(const char *s, int &result) { + try { + result = eval(s); + return true; + } catch(const char*) { + result = 0; + return false; + } +} + +} + +#endif diff --git a/nall/string/platform.hpp b/nall/string/platform.hpp new file mode 100755 index 00000000..42c1a756 --- /dev/null +++ b/nall/string/platform.hpp @@ -0,0 +1,41 @@ +#ifndef NALL_STRING_PLATFORM_HPP +#define NALL_STRING_PLATFORM_HPP + +namespace nall { + +string realpath(const char *name) { + char path[PATH_MAX]; + if(::realpath(name, path)) { + string result(path); + result.transform("\\", "/"); + if(result.endswith("/") == false) result.append("/"); + return result; + } + return ""; +} + +string userpath() { + char path[PATH_MAX]; + if(::userpath(path)) { + string result(path); + result.transform("\\", "/"); + if(result.endswith("/") == false) result.append("/"); + return result; + } + return ""; +} + +string currentpath() { + char path[PATH_MAX]; + if(::getcwd(path)) { + string result(path); + result.transform("\\", "/"); + if(result.endswith("/") == false) result.append("/"); + return result; + } + return ""; +} + +} + +#endif diff --git a/nall/string/replace.hpp b/nall/string/replace.hpp new file mode 100755 index 00000000..db405a9b --- /dev/null +++ b/nall/string/replace.hpp @@ -0,0 +1,103 @@ +#ifndef NALL_STRING_REPLACE_HPP +#define NALL_STRING_REPLACE_HPP + +namespace nall { + +string& string::replace(const char *key, const char *token) { + int i, z, ksl = strlen(key), tsl = strlen(token), ssl = length(); + unsigned int replace_count = 0, size = ssl; + char *buffer; + + if(ksl <= ssl) { + if(tsl > ksl) { //the new string may be longer than the old string... + for(i = 0; i <= ssl - ksl;) { //so let's find out how big of a string we'll need... + if(!memcmp(data + i, key, ksl)) { + replace_count++; + i += ksl; + } else i++; + } + size = ssl + ((tsl - ksl) * replace_count); + reserve(size); + } + + buffer = new char[size + 1]; + for(i = z = 0; i < ssl;) { + if(i <= ssl - ksl) { + if(!memcmp(data + i, key, ksl)) { + memcpy(buffer + z, token, tsl); + z += tsl; + i += ksl; + } else buffer[z++] = data[i++]; + } else buffer[z++] = data[i++]; + } + buffer[z] = 0; + + assign(buffer); + delete[] buffer; + } + + return *this; +} + +string& string::qreplace(const char *key, const char *token) { + int i, l, z, ksl = strlen(key), tsl = strlen(token), ssl = length(); + unsigned int replace_count = 0, size = ssl; + uint8_t x; + char *buffer; + + if(ksl <= ssl) { + if(tsl > ksl) { + for(i = 0; i <= ssl - ksl;) { + x = data[i]; + if(x == '\"' || x == '\'') { + l = i; + i++; + while(data[i++] != x) { + if(i == ssl) { + i = l; + break; + } + } + } + if(!memcmp(data + i, key, ksl)) { + replace_count++; + i += ksl; + } else i++; + } + size = ssl + ((tsl - ksl) * replace_count); + reserve(size); + } + + buffer = new char[size + 1]; + for(i = z = 0; i < ssl;) { + x = data[i]; + if(x == '\"' || x == '\'') { + l = i++; + while(data[i] != x && i < ssl)i++; + if(i >= ssl)i = l; + else { + memcpy(buffer + z, data + l, i - l); + z += i - l; + } + } + if(i <= ssl - ksl) { + if(!memcmp(data + i, key, ksl)) { + memcpy(buffer + z, token, tsl); + z += tsl; + i += ksl; + replace_count++; + } else buffer[z++] = data[i++]; + } else buffer[z++] = data[i++]; + } + buffer[z] = 0; + + assign(buffer); + delete[] buffer; + } + + return *this; +} + +}; + +#endif diff --git a/nall/string/split.hpp b/nall/string/split.hpp new file mode 100755 index 00000000..8d3ca877 --- /dev/null +++ b/nall/string/split.hpp @@ -0,0 +1,58 @@ +#ifndef NALL_STRING_SPLIT_HPP +#define NALL_STRING_SPLIT_HPP + +namespace nall { + +template void lstring::split(const char *key, const char *src) { + unsigned limit = Limit; + reset(); + + int ssl = strlen(src), ksl = strlen(key); + int lp = 0, split_count = 0; + + for(int i = 0; i <= ssl - ksl;) { + if(!memcmp(src + i, key, ksl)) { + strlcpy(operator[](split_count++), src + lp, i - lp + 1); + i += ksl; + lp = i; + if(!--limit) break; + } else i++; + } + + operator[](split_count++) = src + lp; +} + +template void lstring::qsplit(const char *key, const char *src) { + unsigned limit = Limit; + reset(); + + int ssl = strlen(src), ksl = strlen(key); + int lp = 0, split_count = 0; + + for(int i = 0; i <= ssl - ksl;) { + uint8_t x = src[i]; + + if(x == '\"' || x == '\'') { + int z = i++; //skip opening quote + while(i < ssl && src[i] != x) i++; + if(i >= ssl) i = z; //failed match, rewind i + else { + i++; //skip closing quote + continue; //restart in case next char is also a quote + } + } + + if(!memcmp(src + i, key, ksl)) { + strlcpy(operator[](split_count++), src + lp, i - lp + 1); + i += ksl; + lp = i; + if(!--limit) break; + } else i++; + } + + operator[](split_count++) = src + lp; +} + +}; + +#endif diff --git a/nall/string/strl.hpp b/nall/string/strl.hpp new file mode 100755 index 00000000..84c841fa --- /dev/null +++ b/nall/string/strl.hpp @@ -0,0 +1,52 @@ +#ifndef NALL_STRING_STRL_HPP +#define NALL_STRING_STRL_HPP + +namespace nall { + +//strlcpy, strlcat based on OpenBSD implementation by Todd C. Miller + +//return = strlen(src) +unsigned strlcpy(char *dest, const char *src, unsigned length) { + char *d = dest; + const char *s = src; + unsigned n = length; + + if(n) { + while(--n && (*d++ = *s++)); //copy as many bytes as possible, or until null terminator reached + } + + if(!n) { + if(length) *d = 0; + while(*s++); //traverse rest of s, so that s - src == strlen(src) + } + + return (s - src - 1); //return length of copied string, sans null terminator +} + +//return = strlen(src) + min(length, strlen(dest)) +unsigned strlcat(char *dest, const char *src, unsigned length) { + char *d = dest; + const char *s = src; + unsigned n = length; + + while(n-- && *d) d++; //find end of dest + unsigned dlength = d - dest; + n = length - dlength; //subtract length of dest from maximum string length + + if(!n) return dlength + strlen(s); + + while(*s) { + if(n != 1) { + *d++ = *s; + n--; + } + s++; + } + *d = 0; + + return dlength + (s - src); //return length of resulting string, sans null terminator +} + +} + +#endif diff --git a/nall/string/strpos.hpp b/nall/string/strpos.hpp new file mode 100755 index 00000000..1907a2f3 --- /dev/null +++ b/nall/string/strpos.hpp @@ -0,0 +1,41 @@ +#ifndef NALL_STRING_STRPOS_HPP +#define NALL_STRING_STRPOS_HPP + +//usage example: +//if(auto pos = strpos(str, key)) print(pos(), "\n"); +//prints position of key within str, only if it is found + +namespace nall { + +optional strpos(const char *str, const char *key) { + unsigned ssl = strlen(str), ksl = strlen(key); + if(ksl > ssl) return { false, 0 }; + + for(unsigned i = 0; i <= ssl - ksl; i++) { + if(!memcmp(str + i, key, ksl)) return { true, i }; + } + + return { false, 0 }; +} + +optional qstrpos(const char *str, const char *key) { + unsigned ssl = strlen(str), ksl = strlen(key); + if(ksl > ssl) return { false, 0 }; + + for(unsigned i = 0; i <= ssl - ksl;) { + uint8_t x = str[i]; + if(x == '\"' || x == '\'') { + uint8_t z = i++; + while(str[i] != x && i < ssl) i++; + if(i >= ssl) i = z; + } + if(!memcmp(str + i, key, ksl)) return { true, i }; + i++; + } + + return { false, 0 }; +} + +} + +#endif diff --git a/nall/string/trim.hpp b/nall/string/trim.hpp new file mode 100755 index 00000000..f5355d7d --- /dev/null +++ b/nall/string/trim.hpp @@ -0,0 +1,38 @@ +#ifndef NALL_STRING_TRIM_HPP +#define NALL_STRING_TRIM_HPP + +namespace nall { + +//limit defaults to zero, which will underflow on first compare; equivalent to no limit +template char* ltrim(char *str, const char *key) { + unsigned limit = Limit; + if(!key || !*key) return str; + while(strbegin(str, key)) { + char *dest = str, *src = str + strlen(key); + while(true) { + *dest = *src++; + if(!*dest) break; + dest++; + } + if(--limit == 0) break; + } + return str; +} + +template char* rtrim(char *str, const char *key) { + unsigned limit = Limit; + if(!key || !*key) return str; + while(strend(str, key)) { + str[strlen(str) - strlen(key)] = 0; + if(--limit == 0) break; + } + return str; +} + +template char* trim(char *str, const char *key) { + return ltrim(rtrim(str, key), key); +} + +} + +#endif diff --git a/nall/string/utility.hpp b/nall/string/utility.hpp new file mode 100755 index 00000000..779e6c7d --- /dev/null +++ b/nall/string/utility.hpp @@ -0,0 +1,157 @@ +#ifndef NALL_STRING_UTILITY_HPP +#define NALL_STRING_UTILITY_HPP + +namespace nall { + +unsigned strlcpy(string &dest, const char *src, unsigned length) { + dest.reserve(length); + return strlcpy(dest(), src, length); +} + +unsigned strlcat(string &dest, const char *src, unsigned length) { + dest.reserve(length); + return strlcat(dest(), src, length); +} + +string substr(const char *src, unsigned start, unsigned length) { + string dest; + if(length == 0) { + //copy entire string + dest = src + start; + } else { + //copy partial string + strlcpy(dest, src + start, length + 1); + } + return dest; +} + +/* arithmetic <> string */ + +template string hex(uintmax_t value) { + string output; + unsigned offset = 0; + + //render string backwards, as we do not know its length yet + do { + unsigned n = value & 15; + output[offset++] = n < 10 ? '0' + n : 'a' + n - 10; + value >>= 4; + } while(value); + + while(offset < length) output[offset++] = padding; + output[offset--] = 0; + + //reverse the string in-place + for(unsigned i = 0; i < (offset + 1) >> 1; i++) { + char temp = output[i]; + output[i] = output[offset - i]; + output[offset - i] = temp; + } + + return output; +} + +template string integer(intmax_t value) { + string output; + unsigned offset = 0; + + bool negative = value < 0; + if(negative) value = abs(value); + + do { + unsigned n = value % 10; + output[offset++] = '0' + n; + value /= 10; + } while(value); + + while(offset < length) output[offset++] = padding; + if(negative) output[offset++] = '-'; + output[offset--] = 0; + + for(unsigned i = 0; i < (offset + 1) >> 1; i++) { + char temp = output[i]; + output[i] = output[offset - i]; + output[offset - i] = temp; + } + + return output; +} + +template string decimal(uintmax_t value) { + string output; + unsigned offset = 0; + + do { + unsigned n = value % 10; + output[offset++] = '0' + n; + value /= 10; + } while(value); + + while(offset < length) output[offset++] = padding; + output[offset--] = 0; + + for(unsigned i = 0; i < (offset + 1) >> 1; i++) { + char temp = output[i]; + output[i] = output[offset - i]; + output[offset - i] = temp; + } + + return output; +} + +template string binary(uintmax_t value) { + string output; + unsigned offset = 0; + + do { + unsigned n = value & 1; + output[offset++] = '0' + n; + value >>= 1; + } while(value); + + while(offset < length) output[offset++] = padding; + output[offset--] = 0; + + for(unsigned i = 0; i < (offset + 1) >> 1; i++) { + char temp = output[i]; + output[i] = output[offset - i]; + output[offset - i] = temp; + } + + return output; +} + +//using sprintf is certainly not the most ideal method to convert +//a double to a string ... but attempting to parse a double by +//hand, digit-by-digit, results in subtle rounding errors. +unsigned fp(char *str, double value) { + char buffer[256]; + sprintf(buffer, "%f", value); + + //remove excess 0's in fraction (2.500000 -> 2.5) + for(char *p = buffer; *p; p++) { + if(*p == '.') { + char *p = buffer + strlen(buffer) - 1; + while(*p == '0') { + if(*(p - 1) != '.') *p = 0; //... but not for eg 1.0 -> 1. + p--; + } + break; + } + } + + unsigned length = strlen(buffer); + if(str) strcpy(str, buffer); + return length + 1; +} + +string fp(double value) { + string temp; + temp.reserve(fp(0, value)); + fp(temp(), value); + return temp; +} + +} + +#endif diff --git a/nall/string/variadic.hpp b/nall/string/variadic.hpp new file mode 100755 index 00000000..6c027fc8 --- /dev/null +++ b/nall/string/variadic.hpp @@ -0,0 +1,12 @@ +#ifndef NALL_STRING_VARIADIC_HPP +#define NALL_STRING_VARIADIC_HPP + +namespace nall { + +template inline void print(Args&&... args) { + printf("%s", (const char*)string(std::forward(args)...)); +} + +} + +#endif diff --git a/nall/string/wrapper.hpp b/nall/string/wrapper.hpp new file mode 100755 index 00000000..eadf0a10 --- /dev/null +++ b/nall/string/wrapper.hpp @@ -0,0 +1,33 @@ +#ifndef NALL_STRING_WRAPPER_HPP +#define NALL_STRING_WRAPPER_HPP + +namespace nall { + +unsigned string::length() const { return strlen(data); } + +bool string::equals(const char *str) const { return !strcmp(data, str); } +bool string::iequals(const char *str) const { return !stricmp(data, str); } + +bool string::wildcard(const char *str) const { return nall::wildcard(data, str); } +bool string::iwildcard(const char *str) const { return nall::iwildcard(data, str); } + +bool string::beginswith(const char *str) const { return strbegin(data, str); } +bool string::ibeginswith(const char *str) const { return stribegin(data, str); } + +bool string::endswith(const char *str) const { return strend(data, str); } +bool string::iendswith(const char *str) const { return striend(data, str); } + +string& string::lower() { nall::strlower(data); return *this; } +string& string::upper() { nall::strupper(data); return *this; } +string& string::transform(const char *before, const char *after) { nall::strtr(data, before, after); return *this; } + +template string& string::ltrim(const char *key) { nall::ltrim(data, key); return *this; } +template string& string::rtrim(const char *key) { nall::rtrim(data, key); return *this; } +template string& string::trim (const char *key) { nall::trim (data, key); return *this; } + +optional string::position(const char *key) const { return strpos(data, key); } +optional string::qposition(const char *key) const { return qstrpos(data, key); } + +} + +#endif diff --git a/nall/string/xml.hpp b/nall/string/xml.hpp new file mode 100755 index 00000000..185a89f9 --- /dev/null +++ b/nall/string/xml.hpp @@ -0,0 +1,266 @@ +#ifndef NALL_STRING_XML_HPP +#define NALL_STRING_XML_HPP + +//XML subset parser +//version 0.05 + +namespace nall { + +struct xml_attribute { + string name; + string content; + virtual string parse() const; +}; + +struct xml_element : xml_attribute { + string parse() const; + linear_vector attribute; + linear_vector element; + +protected: + void parse_doctype(const char *&data); + bool parse_head(string data); + bool parse_body(const char *&data); + friend xml_element xml_parse(const char *data); +}; + +inline string xml_attribute::parse() const { + string data; + unsigned offset = 0; + + const char *source = content; + while(*source) { + if(*source == '&') { + if(strbegin(source, "<")) { data[offset++] = '<'; source += 4; continue; } + if(strbegin(source, ">")) { data[offset++] = '>'; source += 4; continue; } + if(strbegin(source, "&")) { data[offset++] = '&'; source += 5; continue; } + if(strbegin(source, "'")) { data[offset++] = '\''; source += 6; continue; } + if(strbegin(source, """)) { data[offset++] = '"'; source += 6; continue; } + } + + //reject illegal characters + if(*source == '&') return ""; + if(*source == '<') return ""; + if(*source == '>') return ""; + + data[offset++] = *source++; + } + + data[offset] = 0; + return data; +} + +inline string xml_element::parse() const { + string data; + unsigned offset = 0; + + const char *source = content; + while(*source) { + if(*source == '&') { + if(strbegin(source, "<")) { data[offset++] = '<'; source += 4; continue; } + if(strbegin(source, ">")) { data[offset++] = '>'; source += 4; continue; } + if(strbegin(source, "&")) { data[offset++] = '&'; source += 5; continue; } + if(strbegin(source, "'")) { data[offset++] = '\''; source += 6; continue; } + if(strbegin(source, """)) { data[offset++] = '"'; source += 6; continue; } + } + + if(strbegin(source, "")) { + source += pos() + 3; + continue; + } else { + return ""; + } + } + + if(strbegin(source, "")) { + if(pos() - 9 > 0) { + string cdata = substr(source, 9, pos() - 9); + data << cdata; + offset += strlen(cdata); + } + source += 9 + offset + 3; + continue; + } else { + return ""; + } + } + + //reject illegal characters + if(*source == '&') return ""; + if(*source == '<') return ""; + if(*source == '>') return ""; + + data[offset++] = *source++; + } + + data[offset] = 0; + return data; +} + +inline void xml_element::parse_doctype(const char *&data) { + name = "!DOCTYPE"; + const char *content_begin = data; + + signed counter = 0; + while(*data) { + char value = *data++; + if(value == '<') counter++; + if(value == '>') counter--; + if(counter < 0) { + content = substr(content_begin, 0, data - content_begin - 1); + return; + } + } + throw "..."; +} + +inline bool xml_element::parse_head(string data) { + data.qreplace("\t", " "); + data.qreplace("\r", " "); + data.qreplace("\n", " "); + while(qstrpos(data, " ")) data.qreplace(" ", " "); + data.qreplace(" =", "="); + data.qreplace("= ", "="); + data.rtrim(); + + lstring part; + part.qsplit(" ", data); + + name = part[0]; + if(name == "") throw "..."; + + for(unsigned i = 1; i < part.size(); i++) { + lstring side; + side.qsplit("=", part[i]); + if(side.size() != 2) throw "..."; + + xml_attribute attr; + attr.name = side[0]; + attr.content = side[1]; + if(strbegin(attr.content, "\"") && strend(attr.content, "\"")) attr.content.trim<1>("\""); + else if(strbegin(attr.content, "'") && strend(attr.content, "'")) attr.content.trim<1>("'"); + else throw "..."; + attribute.append(attr); + } +} + +inline bool xml_element::parse_body(const char *&data) { + while(true) { + if(!*data) return false; + if(*data++ != '<') continue; + if(*data == '/') return false; + + if(strbegin(data, "!DOCTYPE") == true) { + parse_doctype(data); + return true; + } + + if(strbegin(data, "!--")) { + if(auto offset = strpos(data, "-->")) { + data += offset() + 3; + continue; + } else { + throw "..."; + } + } + + if(strbegin(data, "![CDATA[")) { + if(auto offset = strpos(data, "]]>")) { + data += offset() + 3; + continue; + } else { + throw "..."; + } + } + + auto offset = strpos(data, ">"); + if(!offset) throw "..."; + + string tag = substr(data, 0, offset()); + data += offset() + 1; + const char *content_begin = data; + + bool self_terminating = false; + + if(strend(tag, "?") == true) { + self_terminating = true; + tag.rtrim<1>("?"); + } else if(strend(tag, "/") == true) { + self_terminating = true; + tag.rtrim<1>("/"); + } + + parse_head(tag); + if(self_terminating) return true; + + while(*data) { + unsigned index = element.size(); + xml_element node; + if(node.parse_body(data) == false) { + if(*data == '/') { + signed length = data - content_begin - 1; + if(length > 0) content = substr(content_begin, 0, length); + + data++; + auto offset = strpos(data, ">"); + if(!offset) throw "..."; + + tag = substr(data, 0, offset()); + data += offset() + 1; + + tag.replace("\t", " "); + tag.replace("\r", " "); + tag.replace("\n", " "); + while(strpos(tag, " ")) tag.replace(" ", " "); + tag.rtrim(); + + if(name != tag) throw "..."; + return true; + } + } else { + element.append(node); + } + } + } +} + +//ensure there is only one root element +inline bool xml_validate(xml_element &document) { + unsigned root_counter = 0; + + for(unsigned i = 0; i < document.element.size(); i++) { + string &name = document.element[i].name; + if(strbegin(name, "?")) continue; + if(strbegin(name, "!")) continue; + if(++root_counter > 1) return false; + } + + return true; +} + +inline xml_element xml_parse(const char *data) { + xml_element self; + + try { + while(*data) { + xml_element node; + if(node.parse_body(data) == false) { + break; + } else { + self.element.append(node); + } + } + + if(xml_validate(self) == false) throw "..."; + return self; + } catch(const char*) { + xml_element empty; + return empty; + } +} + +} + +#endif diff --git a/nall/ups.hpp b/nall/ups.hpp new file mode 100755 index 00000000..ffcdb2d7 --- /dev/null +++ b/nall/ups.hpp @@ -0,0 +1,223 @@ +#ifndef NALL_UPS_HPP +#define NALL_UPS_HPP + +#include +#include +#include +#include + +namespace nall { + +struct ups { + enum class result : unsigned { + unknown, + success, + patch_unwritable, + patch_invalid, + source_invalid, + target_invalid, + target_too_small, + patch_checksum_invalid, + source_checksum_invalid, + target_checksum_invalid, + }; + + function progress; + + result create( + const uint8_t *sourcedata, unsigned sourcelength, + const uint8_t *targetdata, unsigned targetlength, + const char *patchfilename + ) { + source_data = (uint8_t*)sourcedata, target_data = (uint8_t*)targetdata; + source_length = sourcelength, target_length = targetlength; + source_offset = target_offset = 0; + source_checksum = target_checksum = patch_checksum = ~0; + + if(patch_file.open(patchfilename, file::mode::write) == false) return result::patch_unwritable; + + patch_write('U'); + patch_write('P'); + patch_write('S'); + patch_write('1'); + encode(source_length); + encode(target_length); + + unsigned output_length = source_length > target_length ? source_length : target_length; + unsigned relative = 0; + for(unsigned offset = 0; offset < output_length;) { + uint8_t x = source_read(); + uint8_t y = target_read(); + + if(x == y) { + offset++; + continue; + } + + encode(offset++ - relative); + patch_write(x ^ y); + + while(true) { + if(offset >= output_length) { + patch_write(0x00); + break; + } + + x = source_read(); + y = target_read(); + offset++; + patch_write(x ^ y); + if(x == y) break; + } + + relative = offset; + } + + source_checksum = ~source_checksum; + target_checksum = ~target_checksum; + for(unsigned i = 0; i < 4; i++) patch_write(source_checksum >> (i * 8)); + for(unsigned i = 0; i < 4; i++) patch_write(target_checksum >> (i * 8)); + uint32_t patch_result_checksum = ~patch_checksum; + for(unsigned i = 0; i < 4; i++) patch_write(patch_result_checksum >> (i * 8)); + + patch_file.close(); + return result::success; + } + + result apply( + const uint8_t *patchdata, unsigned patchlength, + const uint8_t *sourcedata, unsigned sourcelength, + uint8_t *targetdata, unsigned &targetlength + ) { + patch_data = (uint8_t*)patchdata, source_data = (uint8_t*)sourcedata, target_data = targetdata; + patch_length = patchlength, source_length = sourcelength, target_length = targetlength; + patch_offset = source_offset = target_offset = 0; + patch_checksum = source_checksum = target_checksum = ~0; + + if(patch_length < 18) return result::patch_invalid; + if(patch_read() != 'U') return result::patch_invalid; + if(patch_read() != 'P') return result::patch_invalid; + if(patch_read() != 'S') return result::patch_invalid; + if(patch_read() != '1') return result::patch_invalid; + + unsigned source_read_length = decode(); + unsigned target_read_length = decode(); + + if(source_length != source_read_length && source_length != target_read_length) return result::source_invalid; + targetlength = (source_length == source_read_length ? target_read_length : source_read_length); + if(target_length < targetlength) return result::target_too_small; + target_length = targetlength; + + while(patch_offset < patch_length - 12) { + unsigned length = decode(); + while(length--) target_write(source_read()); + while(true) { + uint8_t patch_xor = patch_read(); + target_write(patch_xor ^ source_read()); + if(patch_xor == 0) break; + } + } + while(source_offset < source_length) target_write(source_read()); + while(target_offset < target_length) target_write(source_read()); + + uint32_t patch_read_checksum = 0, source_read_checksum = 0, target_read_checksum = 0; + for(unsigned i = 0; i < 4; i++) source_read_checksum |= patch_read() << (i * 8); + for(unsigned i = 0; i < 4; i++) target_read_checksum |= patch_read() << (i * 8); + uint32_t patch_result_checksum = ~patch_checksum; + source_checksum = ~source_checksum; + target_checksum = ~target_checksum; + for(unsigned i = 0; i < 4; i++) patch_read_checksum |= patch_read() << (i * 8); + + if(patch_result_checksum != patch_read_checksum) return result::patch_invalid; + if(source_checksum == source_read_checksum && source_length == source_read_length) { + if(target_checksum == target_read_checksum && target_length == target_read_length) return result::success; + return result::target_invalid; + } else if(source_checksum == target_read_checksum && source_length == target_read_length) { + if(target_checksum == source_read_checksum && target_length == source_read_length) return result::success; + return result::target_invalid; + } else { + return result::source_invalid; + } + } + +private: + uint8_t *patch_data, *source_data, *target_data; + unsigned patch_length, source_length, target_length; + unsigned patch_offset, source_offset, target_offset; + unsigned patch_checksum, source_checksum, target_checksum; + file patch_file; + + uint8_t patch_read() { + if(patch_offset < patch_length) { + uint8_t n = patch_data[patch_offset++]; + patch_checksum = crc32_adjust(patch_checksum, n); + return n; + } + return 0x00; + } + + uint8_t source_read() { + if(source_offset < source_length) { + uint8_t n = source_data[source_offset++]; + source_checksum = crc32_adjust(source_checksum, n); + return n; + } + return 0x00; + } + + uint8_t target_read() { + uint8_t result = 0x00; + if(target_offset < target_length) { + result = target_data[target_offset]; + target_checksum = crc32_adjust(target_checksum, result); + } + if(((target_offset++ & 255) == 0) && progress) { + progress(target_offset, source_length > target_length ? source_length : target_length); + } + return result; + } + + void patch_write(uint8_t n) { + patch_file.write(n); + patch_checksum = crc32_adjust(patch_checksum, n); + } + + void target_write(uint8_t n) { + if(target_offset < target_length) { + target_data[target_offset] = n; + target_checksum = crc32_adjust(target_checksum, n); + } + if(((target_offset++ & 255) == 0) && progress) { + progress(target_offset, source_length > target_length ? source_length : target_length); + } + } + + void encode(uint64_t offset) { + while(true) { + uint64_t x = offset & 0x7f; + offset >>= 7; + if(offset == 0) { + patch_write(0x80 | x); + break; + } + patch_write(x); + offset--; + } + } + + uint64_t decode() { + uint64_t offset = 0, shift = 1; + while(true) { + uint8_t x = patch_read(); + offset += (x & 0x7f) * shift; + if(x & 0x80) break; + shift <<= 7; + offset += shift; + } + return offset; + } +}; + +} + +#endif diff --git a/nall/utf8.hpp b/nall/utf8.hpp new file mode 100755 index 00000000..f5597b85 --- /dev/null +++ b/nall/utf8.hpp @@ -0,0 +1,86 @@ +#ifndef NALL_UTF8_HPP +#define NALL_UTF8_HPP + +//UTF-8 <> UTF-16 conversion +//used only for Win32; Linux, etc use UTF-8 internally + +#if defined(_WIN32) + +#undef UNICODE +#undef _WIN32_WINNT +#undef NOMINMAX +#define UNICODE +#define _WIN32_WINNT 0x0501 +#define NOMINMAX +#include +#undef interface + +namespace nall { + //UTF-8 to UTF-16 + class utf16_t { + public: + operator wchar_t*() { + return buffer; + } + + operator const wchar_t*() const { + return buffer; + } + + utf16_t(const char *s = "") { + if(!s) s = ""; + unsigned length = MultiByteToWideChar(CP_UTF8, 0, s, -1, 0, 0); + buffer = new wchar_t[length + 1](); + MultiByteToWideChar(CP_UTF8, 0, s, -1, buffer, length); + } + + ~utf16_t() { + delete[] buffer; + } + + private: + wchar_t *buffer; + }; + + //UTF-16 to UTF-8 + class utf8_t { + public: + operator char*() { + return buffer; + } + + operator const char*() const { + return buffer; + } + + utf8_t(const wchar_t *s = L"") { + if(!s) s = L""; + unsigned length = WideCharToMultiByte(CP_UTF8, 0, s, -1, 0, 0, (const char*)0, (BOOL*)0); + buffer = new char[length + 1](); + WideCharToMultiByte(CP_UTF8, 0, s, -1, buffer, length, (const char*)0, (BOOL*)0); + } + + ~utf8_t() { + delete[] buffer; + } + + utf8_t(const utf8_t&) = delete; + utf8_t& operator=(const utf8_t&) = delete; + + private: + char *buffer; + }; + + inline void utf8_args(int &argc, char **&argv) { + wchar_t **wargv = CommandLineToArgvW(GetCommandLineW(), &argc); + argv = new char*[argc]; + for(unsigned i = 0; i < argc; i++) { + argv[i] = new char[_MAX_PATH]; + strcpy(argv[i], nall::utf8_t(wargv[i])); + } + } +} + +#endif //if defined(_WIN32) + +#endif diff --git a/nall/utility.hpp b/nall/utility.hpp new file mode 100755 index 00000000..60bda562 --- /dev/null +++ b/nall/utility.hpp @@ -0,0 +1,39 @@ +#ifndef NALL_UTILITY_HPP +#define NALL_UTILITY_HPP + +#include +#include + +namespace nall { + template struct enable_if { typedef T type; }; + template struct enable_if {}; + template struct mp_enable_if : enable_if {}; + + template inline void swap(T &x, T &y) { + T temp(std::move(x)); + x = std::move(y); + y = std::move(temp); + } + + template struct base_from_member { + T value; + base_from_member(T value_) : value(value_) {} + }; + + template class optional { + bool valid; + T value; + public: + inline operator bool() const { return valid; } + inline const T& operator()() const { if(!valid) throw; return value; } + inline optional(bool valid, const T &value) : valid(valid), value(value) {} + }; + + template inline T* allocate(unsigned size, const T &value) { + T *array = new T[size]; + for(unsigned i = 0; i < size; i++) array[i] = value; + return array; + } +} + +#endif diff --git a/nall/varint.hpp b/nall/varint.hpp new file mode 100755 index 00000000..fe4732b1 --- /dev/null +++ b/nall/varint.hpp @@ -0,0 +1,92 @@ +#ifndef NALL_VARINT_HPP +#define NALL_VARINT_HPP + +#include +#include +#include + +namespace nall { + template class uint_t { + private: + enum { bytes = (bits + 7) >> 3 }; //minimum number of bytes needed to store value + typedef typename static_if< + sizeof(int) >= bytes, + unsigned int, + typename static_if< + sizeof(long) >= bytes, + unsigned long, + typename static_if< + sizeof(long long) >= bytes, + unsigned long long, + void + >::type + >::type + >::type T; + static_assert(!std::is_same::value, ""); + T data; + + public: + inline operator T() const { return data; } + inline T operator ++(int) { T r = data; data = uclip(data + 1); return r; } + inline T operator --(int) { T r = data; data = uclip(data - 1); return r; } + inline T operator ++() { return data = uclip(data + 1); } + inline T operator --() { return data = uclip(data - 1); } + inline T operator =(const T i) { return data = uclip(i); } + inline T operator |=(const T i) { return data = uclip(data | i); } + inline T operator ^=(const T i) { return data = uclip(data ^ i); } + inline T operator &=(const T i) { return data = uclip(data & i); } + inline T operator<<=(const T i) { return data = uclip(data << i); } + inline T operator>>=(const T i) { return data = uclip(data >> i); } + inline T operator +=(const T i) { return data = uclip(data + i); } + inline T operator -=(const T i) { return data = uclip(data - i); } + inline T operator *=(const T i) { return data = uclip(data * i); } + inline T operator /=(const T i) { return data = uclip(data / i); } + inline T operator %=(const T i) { return data = uclip(data % i); } + + inline uint_t() : data(0) {} + inline uint_t(const T i) : data(uclip(i)) {} + }; + + template class int_t { + private: + enum { bytes = (bits + 7) >> 3 }; //minimum number of bytes needed to store value + typedef typename static_if< + sizeof(int) >= bytes, + signed int, + typename static_if< + sizeof(long) >= bytes, + signed long, + typename static_if< + sizeof(long long) >= bytes, + signed long long, + void + >::type + >::type + >::type T; + static_assert(!std::is_same::value, ""); + T data; + + public: + inline operator T() const { return data; } + inline T operator ++(int) { T r = data; data = sclip(data + 1); return r; } + inline T operator --(int) { T r = data; data = sclip(data - 1); return r; } + inline T operator ++() { return data = sclip(data + 1); } + inline T operator --() { return data = sclip(data - 1); } + inline T operator =(const T i) { return data = sclip(i); } + inline T operator |=(const T i) { return data = sclip(data | i); } + inline T operator ^=(const T i) { return data = sclip(data ^ i); } + inline T operator &=(const T i) { return data = sclip(data & i); } + inline T operator<<=(const T i) { return data = sclip(data << i); } + inline T operator>>=(const T i) { return data = sclip(data >> i); } + inline T operator +=(const T i) { return data = sclip(data + i); } + inline T operator -=(const T i) { return data = sclip(data - i); } + inline T operator *=(const T i) { return data = sclip(data * i); } + inline T operator /=(const T i) { return data = sclip(data / i); } + inline T operator %=(const T i) { return data = sclip(data % i); } + + inline int_t() : data(0) {} + inline int_t(const T i) : data(sclip(i)) {} + }; +} + +#endif diff --git a/nall/vector.hpp b/nall/vector.hpp new file mode 100755 index 00000000..543c7b69 --- /dev/null +++ b/nall/vector.hpp @@ -0,0 +1,281 @@ +#ifndef NALL_VECTOR_HPP +#define NALL_VECTOR_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace nall { + //linear_vector + //memory: O(capacity * 2) + // + //linear_vector uses placement new + manual destructor calls to create a + //contiguous block of memory for all objects. accessing individual elements + //is fast, though resizing the array incurs significant overhead. + //reserve() overhead is reduced from quadratic time to amortized constant time + //by resizing twice as much as requested. + // + //if objects hold memory address references to themselves (introspection), a + //valid copy constructor will be needed to keep pointers valid. + + template class linear_vector { + protected: + T *pool; + unsigned poolsize, objectsize; + + public: + unsigned size() const { return objectsize; } + unsigned capacity() const { return poolsize; } + + void reset() { + if(pool) { + for(unsigned i = 0; i < objectsize; i++) pool[i].~T(); + free(pool); + } + pool = 0; + poolsize = 0; + objectsize = 0; + } + + void reserve(unsigned newsize) { + newsize = bit::round(newsize); //round to nearest power of two (for amortized growth) + + T *poolcopy = (T*)malloc(newsize * sizeof(T)); + for(unsigned i = 0; i < min(objectsize, newsize); i++) new(poolcopy + i) T(pool[i]); + for(unsigned i = 0; i < objectsize; i++) pool[i].~T(); + free(pool); + pool = poolcopy; + poolsize = newsize; + objectsize = min(objectsize, newsize); + } + + void resize(unsigned newsize) { + if(newsize > poolsize) reserve(newsize); + + if(newsize < objectsize) { + //vector is shrinking; destroy excess objects + for(unsigned i = newsize; i < objectsize; i++) pool[i].~T(); + } else if(newsize > objectsize) { + //vector is expanding; allocate new objects + for(unsigned i = objectsize; i < newsize; i++) new(pool + i) T; + } + + objectsize = newsize; + } + + void append(const T data) { + if(objectsize + 1 > poolsize) reserve(objectsize + 1); + new(pool + objectsize++) T(data); + } + + template void insert(unsigned index, const U list) { + linear_vector merged; + for(unsigned i = 0; i < index; i++) merged.append(pool[i]); + foreach(item, list) merged.append(item); + for(unsigned i = index; i < objectsize; i++) merged.append(pool[i]); + operator=(merged); + } + + void insert(unsigned index, const T item) { + insert(index, linear_vector{ item }); + } + + void remove(unsigned index, unsigned count = 1) { + for(unsigned i = index; count + i < objectsize; i++) { + pool[i] = pool[count + i]; + } + if(count + index >= objectsize) resize(index); //every element >= index was removed + else resize(objectsize - count); + } + + inline T& operator[](unsigned index) { + if(index >= objectsize) resize(index + 1); + return pool[index]; + } + + inline const T& operator[](unsigned index) const { + if(index >= objectsize) throw "vector[] out of bounds"; + return pool[index]; + } + + //copy + inline linear_vector& operator=(const linear_vector &source) { + reset(); + reserve(source.capacity()); + resize(source.size()); + for(unsigned i = 0; i < source.size(); i++) operator[](i) = source.operator[](i); + return *this; + } + + linear_vector(const linear_vector &source) : pool(0), poolsize(0), objectsize(0) { + operator=(source); + } + + //move + inline linear_vector& operator=(linear_vector &&source) { + reset(); + pool = source.pool; + poolsize = source.poolsize; + objectsize = source.objectsize; + source.pool = 0; + source.reset(); + return *this; + } + + linear_vector(linear_vector &&source) : pool(0), poolsize(0), objectsize(0) { + operator=(std::move(source)); + } + + //construction + linear_vector() : pool(0), poolsize(0), objectsize(0) { + } + + linear_vector(std::initializer_list list) : pool(0), poolsize(0), objectsize(0) { + for(const T *p = list.begin(); p != list.end(); ++p) append(*p); + } + + ~linear_vector() { + reset(); + } + }; + + //pointer_vector + //memory: O(1) + // + //pointer_vector keeps an array of pointers to each vector object. this adds + //significant overhead to individual accesses, but allows for optimal memory + //utilization. + // + //by guaranteeing that the base memory address of each objects never changes, + //this avoids the need for an object to have a valid copy constructor. + + template class pointer_vector { + protected: + T **pool; + unsigned poolsize, objectsize; + + public: + unsigned size() const { return objectsize; } + unsigned capacity() const { return poolsize; } + + void reset() { + if(pool) { + for(unsigned i = 0; i < objectsize; i++) { if(pool[i]) delete pool[i]; } + free(pool); + } + pool = 0; + poolsize = 0; + objectsize = 0; + } + + void reserve(unsigned newsize) { + newsize = bit::round(newsize); //round to nearest power of two (for amortized growth) + + for(unsigned i = newsize; i < objectsize; i++) { + if(pool[i]) { delete pool[i]; pool[i] = 0; } + } + + pool = (T**)realloc(pool, newsize * sizeof(T*)); + for(unsigned i = poolsize; i < newsize; i++) pool[i] = 0; + poolsize = newsize; + objectsize = min(objectsize, newsize); + } + + void resize(unsigned newsize) { + if(newsize > poolsize) reserve(newsize); + + for(unsigned i = newsize; i < objectsize; i++) { + if(pool[i]) { delete pool[i]; pool[i] = 0; } + } + + objectsize = newsize; + } + + void append(const T data) { + if(objectsize + 1 > poolsize) reserve(objectsize + 1); + pool[objectsize++] = new T(data); + } + + template void insert(unsigned index, const U list) { + pointer_vector merged; + for(unsigned i = 0; i < index; i++) merged.append(*pool[i]); + foreach(item, list) merged.append(item); + for(unsigned i = index; i < objectsize; i++) merged.append(*pool[i]); + operator=(merged); + } + + void insert(unsigned index, const T item) { + insert(index, pointer_vector{ item }); + } + + void remove(unsigned index, unsigned count = 1) { + for(unsigned i = index; count + i < objectsize; i++) { + *pool[i] = *pool[count + i]; + } + if(count + index >= objectsize) resize(index); //every element >= index was removed + else resize(objectsize - count); + } + + inline T& operator[](unsigned index) { + if(index >= objectsize) resize(index + 1); + if(!pool[index]) pool[index] = new T; + return *pool[index]; + } + + inline const T& operator[](unsigned index) const { + if(index >= objectsize || !pool[index]) throw "vector[] out of bounds"; + return *pool[index]; + } + + //copy + inline pointer_vector& operator=(const pointer_vector &source) { + reset(); + reserve(source.capacity()); + resize(source.size()); + for(unsigned i = 0; i < source.size(); i++) operator[](i) = source.operator[](i); + return *this; + } + + pointer_vector(const pointer_vector &source) : pool(0), poolsize(0), objectsize(0) { + operator=(source); + } + + //move + inline pointer_vector& operator=(pointer_vector &&source) { + reset(); + pool = source.pool; + poolsize = source.poolsize; + objectsize = source.objectsize; + source.pool = 0; + source.reset(); + return *this; + } + + pointer_vector(pointer_vector &&source) : pool(0), poolsize(0), objectsize(0) { + operator=(std::move(source)); + } + + //construction + pointer_vector() : pool(0), poolsize(0), objectsize(0) { + } + + pointer_vector(std::initializer_list list) : pool(0), poolsize(0), objectsize(0) { + for(const T *p = list.begin(); p != list.end(); ++p) append(*p); + } + + ~pointer_vector() { + reset(); + } + }; + + template struct has_size> { enum { value = true }; }; + template struct has_size> { enum { value = true }; }; +} + +#endif diff --git a/phoenix/gtk/button.cpp b/phoenix/gtk/button.cpp new file mode 100755 index 00000000..af2a8a61 --- /dev/null +++ b/phoenix/gtk/button.cpp @@ -0,0 +1,13 @@ +static void Button_tick(Button *self) { + if(self->onTick) self->onTick(); +} + +void Button::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + object->widget = gtk_button_new_with_label(text); + widget->parent = &parent; + gtk_widget_set_size_request(object->widget, width, height); + g_signal_connect_swapped(G_OBJECT(object->widget), "clicked", G_CALLBACK(Button_tick), (gpointer)this); + if(parent.window->defaultFont) setFont(*parent.window->defaultFont); + gtk_fixed_put(GTK_FIXED(parent.object->formContainer), object->widget, x, y); + gtk_widget_show(object->widget); +} diff --git a/phoenix/gtk/canvas.cpp b/phoenix/gtk/canvas.cpp new file mode 100755 index 00000000..43913871 --- /dev/null +++ b/phoenix/gtk/canvas.cpp @@ -0,0 +1,59 @@ +static void Canvas_expose(Canvas *self) { + uint32_t *rgb = self->canvas->bufferRGB; + uint32_t *bgr = self->canvas->bufferBGR; + for(unsigned y = self->object->widget->allocation.height; y; y--) { + for(unsigned x = self->object->widget->allocation.width; x; x--) { + uint32_t pixel = *rgb++; + *bgr++ = ((pixel << 16) & 0xff0000) | (pixel & 0x00ff00) | ((pixel >> 16) & 0x0000ff); + } + } + + gdk_draw_rgb_32_image( + self->object->widget->window, + self->object->widget->style->fg_gc[GTK_WIDGET_STATE(self->object->widget)], + 0, 0, self->object->widget->allocation.width, self->object->widget->allocation.height, + GDK_RGB_DITHER_NONE, (guchar*)self->canvas->bufferBGR, self->canvas->pitch + ); +} + +void Canvas::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height) { + canvas->bufferRGB = new uint32_t[width * height](); + canvas->bufferBGR = new uint32_t[width * height](); + canvas->pitch = width * sizeof(uint32_t); + + object->widget = gtk_drawing_area_new(); + widget->parent = &parent; + GdkColor color; + color.pixel = color.red = color.green = color.blue = 0; + gtk_widget_modify_bg(object->widget, GTK_STATE_NORMAL, &color); + gtk_widget_set_double_buffered(object->widget, false); + gtk_widget_add_events(object->widget, GDK_EXPOSURE_MASK); + gtk_widget_set_size_request(object->widget, width, height); + g_signal_connect_swapped(G_OBJECT(object->widget), "expose_event", G_CALLBACK(Canvas_expose), (gpointer)this); + gtk_fixed_put(GTK_FIXED(parent.object->formContainer), object->widget, x, y); + gtk_widget_show(object->widget); +} + +uint32_t* Canvas::buffer() { + return canvas->bufferRGB; +} + +void Canvas::redraw() { + GdkRectangle rect; + rect.x = 0; + rect.y = 0; + rect.width = object->widget->allocation.width; + rect.height = object->widget->allocation.height; + gdk_window_invalidate_rect(object->widget->window, &rect, true); +} + +Canvas::Canvas() { + canvas = new Canvas::Data; + canvas->bufferRGB = 0; + canvas->bufferBGR = 0; +} + +Canvas::~Canvas() { + if(canvas->bufferRGB) delete[] canvas->bufferRGB; + if(canvas->bufferBGR) delete[] canvas->bufferBGR; +} diff --git a/phoenix/gtk/checkbox.cpp b/phoenix/gtk/checkbox.cpp new file mode 100755 index 00000000..f569775c --- /dev/null +++ b/phoenix/gtk/checkbox.cpp @@ -0,0 +1,23 @@ +static void CheckBox_tick(CheckBox *self) { + if(self->onTick && self->object->locked == false) self->onTick(); +} + +void CheckBox::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + object->widget = gtk_check_button_new_with_label(text); + widget->parent = &parent; + gtk_widget_set_size_request(object->widget, width, height); + g_signal_connect_swapped(G_OBJECT(object->widget), "toggled", G_CALLBACK(CheckBox_tick), (gpointer)this); + if(parent.window->defaultFont) setFont(*parent.window->defaultFont); + gtk_fixed_put(GTK_FIXED(parent.object->formContainer), object->widget, x, y); + gtk_widget_show(object->widget); +} + +bool CheckBox::checked() { + return gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(object->widget)); +} + +void CheckBox::setChecked(bool checked) { + object->locked = true; + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(object->widget), checked); + object->locked = false; +} diff --git a/phoenix/gtk/combobox.cpp b/phoenix/gtk/combobox.cpp new file mode 100755 index 00000000..dbabf247 --- /dev/null +++ b/phoenix/gtk/combobox.cpp @@ -0,0 +1,48 @@ +void ComboBox_change(ComboBox *self) { + if(self->object->locked == false && self->onChange) self->onChange(); +} + +void ComboBox::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + object->widget = gtk_combo_box_new_text(); + widget->parent = &parent; + gtk_widget_set_size_request(object->widget, width, height); + g_signal_connect_swapped(G_OBJECT(object->widget), "changed", G_CALLBACK(ComboBox_change), (gpointer)this); + + if(*text) { + lstring list; + list.split("\n", text); + foreach(item, list) addItem(item); + } + + if(parent.window->defaultFont) setFont(*parent.window->defaultFont); + gtk_fixed_put(GTK_FIXED(parent.object->formContainer), object->widget, x, y); + gtk_widget_show(object->widget); +} + +void ComboBox::reset() { + object->locked = true; + for(signed i = counter - 1; i >= 0; i--) { + gtk_combo_box_remove_text(GTK_COMBO_BOX(object->widget), i); + } + object->locked = false; + counter = 0; +} + +void ComboBox::addItem(const string &text) { + gtk_combo_box_append_text(GTK_COMBO_BOX(object->widget), text); + if(counter++ == 0) setSelection(0); +} + +unsigned ComboBox::selection() { + return gtk_combo_box_get_active(GTK_COMBO_BOX(object->widget)); +} + +void ComboBox::setSelection(unsigned item) { + object->locked = true; + gtk_combo_box_set_active(GTK_COMBO_BOX(object->widget), item); + object->locked = false; +} + +ComboBox::ComboBox() { + counter = 0; +} diff --git a/phoenix/gtk/editbox.cpp b/phoenix/gtk/editbox.cpp new file mode 100755 index 00000000..90cddfe9 --- /dev/null +++ b/phoenix/gtk/editbox.cpp @@ -0,0 +1,58 @@ +static void EditBox_change(EditBox *self) { + if(self->object->locked == false && self->onChange) self->onChange(); +} + +void EditBox::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + object->widget = gtk_scrolled_window_new(0, 0); + widget->parent = &parent; + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(object->widget), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(object->widget), GTK_SHADOW_ETCHED_IN); + gtk_widget_set_size_request(object->widget, width, height); + object->subWidget = gtk_text_view_new(); + gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(object->subWidget), GTK_WRAP_WORD_CHAR); + gtk_container_add(GTK_CONTAINER(object->widget), object->subWidget); + object->textBuffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(object->subWidget)); + gtk_text_buffer_set_text(object->textBuffer, text, -1); + g_signal_connect_swapped(G_OBJECT(object->textBuffer), "changed", G_CALLBACK(EditBox_change), (gpointer)this); + if(parent.window->defaultFont) setFont(*parent.window->defaultFont); + gtk_fixed_put(GTK_FIXED(parent.object->formContainer), object->widget, x, y); + gtk_widget_show(object->subWidget); + gtk_widget_show(object->widget); +} + +void EditBox::setFocused() { + gtk_widget_grab_focus(object->subWidget); +} + +void EditBox::setEditable(bool editable) { + gtk_text_view_set_editable(GTK_TEXT_VIEW(object->subWidget), editable); +} + +void EditBox::setWordWrap(bool wordWrap) { + gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(object->subWidget), wordWrap ? GTK_WRAP_WORD_CHAR : GTK_WRAP_NONE); +} + +string EditBox::text() { + GtkTextIter start, end; + gtk_text_buffer_get_start_iter(object->textBuffer, &start); + gtk_text_buffer_get_end_iter(object->textBuffer, &end); + char *temp = gtk_text_buffer_get_text(object->textBuffer, &start, &end, true); + string text = temp; + g_free(temp); + return text; +} + +void EditBox::setText(const string &text) { + object->locked = true; + gtk_text_buffer_set_text(object->textBuffer, text, -1); + object->locked = false; +} + +void EditBox::setCursorPosition(unsigned position) { + GtkTextMark *mark = gtk_text_buffer_get_mark(object->textBuffer, "insert"); + GtkTextIter iter; + gtk_text_buffer_get_end_iter(object->textBuffer, &iter); + gtk_text_iter_set_offset(&iter, min(position, gtk_text_iter_get_offset(&iter))); + gtk_text_buffer_place_cursor(object->textBuffer, &iter); + gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(object->subWidget), mark); +} diff --git a/phoenix/gtk/font.cpp b/phoenix/gtk/font.cpp new file mode 100755 index 00000000..459151e0 --- /dev/null +++ b/phoenix/gtk/font.cpp @@ -0,0 +1,18 @@ +bool Font::create(const string &name, unsigned size, Font::Style style) { + font->font = pango_font_description_new(); + pango_font_description_set_family(font->font, name); + pango_font_description_set_size(font->font, size * PANGO_SCALE); + pango_font_description_set_style(font->font, (style & Style::Italic) == Style::Italic ? PANGO_STYLE_OBLIQUE : PANGO_STYLE_NORMAL); + pango_font_description_set_weight(font->font, (style & Style::Bold) == Style::Bold ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL); + return true; +} + +Font::Font() { + font = new Font::Data; + font->font = 0; +} + +Font::~Font() { + if(font->font) pango_font_description_free(font->font); + delete font; +} diff --git a/phoenix/gtk/gtk.cpp b/phoenix/gtk/gtk.cpp new file mode 100755 index 00000000..3c7cbb60 --- /dev/null +++ b/phoenix/gtk/gtk.cpp @@ -0,0 +1,190 @@ +#include +#include +#include + +#define None X11None +#define Window X11Window + +#include +#include +#include +#include + +#undef None +#undef Window + +using namespace nall; + +namespace phoenix { + +#include "object.cpp" +#include "font.cpp" +#include "menu.cpp" +#include "widget.cpp" +#include "window.cpp" +#include "button.cpp" +#include "canvas.cpp" +#include "checkbox.cpp" +#include "combobox.cpp" +#include "editbox.cpp" +#include "hexeditor.cpp" +#include "horizontalslider.cpp" +#include "label.cpp" +#include "listbox.cpp" +#include "progressbar.cpp" +#include "radiobox.cpp" +#include "textbox.cpp" +#include "verticalslider.cpp" +#include "viewport.cpp" +#include "messagewindow.cpp" + +Window Window::None; + +void OS::initialize() { + static bool initialized = false; + if(initialized == true) return; + initialized = true; + + int argc = 1; + char *argv[2]; + argv[0] = new char[8]; + argv[1] = 0; + strcpy(argv[0], "phoenix"); + char **argvp = argv; + gtk_init(&argc, &argvp); + + gtk_rc_parse_string( + "style \"phoenix-gtk\"\n" + "{\n" + " GtkComboBox::appears-as-list = 1\n" + " GtkTreeView::vertical-separator = 0\n" + "}\n" + "class \"GtkComboBox\" style \"phoenix-gtk\"\n" + "class \"GtkTreeView\" style \"phoenix-gtk\"\n" + ); +} + +bool OS::pending() { + return gtk_events_pending(); +} + +void OS::run() { + while(pending()) gtk_main_iteration_do(false); +} + +void OS::main() { + gtk_main(); +} + +void OS::quit() { + gtk_main_quit(); +} + +unsigned OS::desktopWidth() { + return gdk_screen_get_width(gdk_screen_get_default()); +} + +unsigned OS::desktopHeight() { + return gdk_screen_get_height(gdk_screen_get_default()); +} + +string OS::folderSelect(Window &parent, const string &path) { + string name; + + GtkWidget *dialog = gtk_file_chooser_dialog_new( + "Select Folder", + &parent != &Window::None ? GTK_WINDOW(parent.object->widget) : (GtkWindow*)0, + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, + (const gchar*)0 + ); + + if(path) gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), path); + + if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { + char *temp = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); + name = temp; + g_free(temp); + } + + gtk_widget_destroy(dialog); + if(name.endswith("/") == false) name.append("/"); + return name; +} + +string OS::fileOpen(Window &parent, const string &filter, const string &path) { + string name; + + GtkWidget *dialog = gtk_file_chooser_dialog_new( + "Open File", + &parent != &Window::None ? GTK_WINDOW(parent.object->widget) : (GtkWindow*)0, + GTK_FILE_CHOOSER_ACTION_OPEN, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, + (const gchar*)0 + ); + + if(path) gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), path); + + lstring list; + list.split("\n", filter); + foreach(item, list) { + lstring part; + part.split("\t", item); + GtkFileFilter *filter = gtk_file_filter_new(); + gtk_file_filter_set_name(filter, string(part[0], " (", part[1], ")")); + lstring patterns; + patterns.split(",", part[1]); + foreach(pattern, patterns) gtk_file_filter_add_pattern(filter, pattern); + gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter); + } + + if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { + char *temp = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); + name = temp; + g_free(temp); + } + + gtk_widget_destroy(dialog); + return name; +} + +string OS::fileSave(Window &parent, const string &filter, const string &path) { + string name; + + GtkWidget *dialog = gtk_file_chooser_dialog_new( + "Save File", + &parent != &Window::None ? GTK_WINDOW(parent.object->widget) : (GtkWindow*)0, + GTK_FILE_CHOOSER_ACTION_SAVE, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, + (const gchar*)0 + ); + + if(path) gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), path); + + lstring list; + list.split("\n", filter); + foreach(item, list) { + lstring part; + part.split("\t", item); + GtkFileFilter *filter = gtk_file_filter_new(); + gtk_file_filter_set_name(filter, string(part[0], " (", part[1], ")")); + lstring patterns; + patterns.split(",", part[1]); + foreach(pattern, patterns) gtk_file_filter_add_pattern(filter, pattern); + gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter); + } + + if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { + char *temp = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); + name = temp; + g_free(temp); + } + + gtk_widget_destroy(dialog); + return name; +} + +} diff --git a/phoenix/gtk/gtk.hpp b/phoenix/gtk/gtk.hpp new file mode 100755 index 00000000..5b14e34c --- /dev/null +++ b/phoenix/gtk/gtk.hpp @@ -0,0 +1,287 @@ +namespace phoenix { + +struct Window; + +struct Object { + Object(); + Object& operator=(const Object&) = delete; + Object(const Object&) = delete; +//private: + virtual void unused(); + struct Data; + Data *object; +}; + +struct Geometry { + unsigned x, y; + unsigned width, height; + inline Geometry() : x(0), y(0), width(0), height(0) {} + inline Geometry(unsigned x, unsigned y, unsigned width, unsigned height) : x(x), y(y), width(width), height(height) {} +}; + +struct Font : Object { + enum class Style : unsigned { + None = 0, + Bold = 1, + Italic = 2, + }; + bool create(const nall::string &name, unsigned size, Font::Style style = Style::None); + Font(); + ~Font(); +//private: + struct Data; + Data *font; +}; + +inline Font::Style operator|(Font::Style a, Font::Style b) { return (Font::Style)((unsigned)a | (unsigned)b); } +inline Font::Style operator&(Font::Style a, Font::Style b) { return (Font::Style)((unsigned)a & (unsigned)b); } + +struct Action : Object { + bool visible(); + void setVisible(bool visible = true); + bool enabled(); + void setEnabled(bool enabled = true); + Action(); +//private: + struct Data; + Data *action; +}; + +struct Menu : Action { + void create(Window &parent, const nall::string &text); + void create(Menu &parent, const nall::string &text); +}; + +struct MenuSeparator : Action { + void create(Menu &parent); +}; + +struct MenuItem : Action { + nall::function onTick; + void create(Menu &parent, const nall::string &text); +}; + +struct MenuCheckItem : Action { + nall::function onTick; + void create(Menu &parent, const nall::string &text); + bool checked(); + void setChecked(bool checked = true); +}; + +struct MenuRadioItem : Action { + nall::function onTick; + void create(Menu &parent, const nall::string &text); + void create(MenuRadioItem &parent, const nall::string &text); + bool checked(); + void setChecked(); +private: + MenuRadioItem *first; +}; + +struct Widget : Object { + virtual void setFont(Font &font); + bool visible(); + void setVisible(bool visible = true); + bool enabled(); + void setEnabled(bool enabled = true); + virtual bool focused(); + virtual void setFocused(); + virtual void setGeometry(unsigned x, unsigned y, unsigned width, unsigned height); + Widget(); +//private: + struct Data; + Data *widget; +}; + +struct Window : Widget { + nall::function onClose; + void create(unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + bool focused(); + void setFocused(); + Geometry geometry(); + void setGeometry(unsigned x, unsigned y, unsigned width, unsigned height); + void setDefaultFont(Font &font); + void setFont(Font &font); + void setBackgroundColor(uint8_t red, uint8_t green, uint8_t blue); + void setTitle(const nall::string &text); + void setStatusText(const nall::string &text); + void setMenuVisible(bool visible = true); + void setStatusVisible(bool visible = true); + Window(); +//private: + struct Data; + Data *window; + static Window None; +}; + +struct Button : Widget { + nall::function onTick; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); +}; + +struct Canvas : Widget { + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height); + uint32_t* buffer(); + void redraw(); + Canvas(); + ~Canvas(); +//private: + struct Data; + Data *canvas; +}; + +struct CheckBox : Widget { + nall::function onTick; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + bool checked(); + void setChecked(bool checked = true); +}; + +struct ComboBox : Widget { + nall::function onChange; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + void reset(); + void addItem(const nall::string &text); + unsigned selection(); + void setSelection(unsigned item); + ComboBox(); +private: + unsigned counter; +}; + +struct EditBox : Widget { + nall::function onChange; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + void setFocused(); + void setEditable(bool editable = true); + void setWordWrap(bool wordWrap = true); + nall::string text(); + void setText(const nall::string &text); + void setCursorPosition(unsigned position); +}; + +struct HexEditor : Widget { + nall::function onRead; + nall::function onWrite; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height); + void setSize(unsigned size); + void setOffset(unsigned offset); + void setColumns(unsigned columns); + void setRows(unsigned rows); + void update(); + HexEditor(); +//private: + struct Data; + Data *hexEditor; + bool keyPress(unsigned scancode); + void scroll(unsigned position); + void setScroll(); + void updateScroll(); + unsigned cursorPosition(); + void setCursorPosition(unsigned position); +}; + +struct HorizontalSlider : Widget { + nall::function onChange; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, unsigned length); + unsigned position(); + void setPosition(unsigned position); +}; + +struct Label : Widget { + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + void setText(const nall::string &text); +}; + +struct ListBox : Widget { + nall::function onActivate; + nall::function onChange; + nall::function onTick; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + void setFocused(); + void setHeaderVisible(bool headerVisible = true); + void setCheckable(bool checkable = true); + void setFont(Font &font); + void reset(); + void resizeColumnsToContent(); + void addItem(const nall::string &text); + void setItem(unsigned row, const nall::string &text); + bool checked(unsigned row); + void setChecked(unsigned row, bool checked = true); + nall::optional selection(); + void setSelection(unsigned row); + ListBox(); +//private: + struct Data; + Data *listBox; +}; + +struct ProgressBar : Widget { + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height); + void setPosition(unsigned position); +}; + +struct RadioBox : Widget { + nall::function onTick; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + void create(RadioBox &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + bool checked(); + void setChecked(); +private: + RadioBox *first; +}; + +struct TextBox : Widget { + nall::function onActivate; + nall::function onChange; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + void setEditable(bool editable = true); + nall::string text(); + void setText(const nall::string &text); +}; + +struct VerticalSlider : Widget { + nall::function onChange; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, unsigned length); + unsigned position(); + void setPosition(unsigned position); +}; + +struct Viewport : Widget { + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height); + uintptr_t handle(); +}; + +struct MessageWindow : Object { + enum class Buttons : unsigned { + Ok, + OkCancel, + YesNo, + }; + enum class Response : unsigned { + Ok, + Cancel, + Yes, + No, + }; + static Response information(Window &parent, const nall::string &text, Buttons = Buttons::Ok); + static Response question(Window &parent, const nall::string &text, Buttons = Buttons::YesNo); + static Response warning(Window &parent, const nall::string &text, Buttons = Buttons::Ok); + static Response critical(Window &parent, const nall::string &text, Buttons = Buttons::Ok); +}; + +struct OS : Object { + static bool pending(); + static void run(); + static void main(); + static void quit(); + static unsigned desktopWidth(); + static unsigned desktopHeight(); + static nall::string folderSelect(Window &parent, const nall::string &path = ""); + static nall::string fileOpen(Window &parent, const nall::string &filter, const nall::string &path = ""); + static nall::string fileSave(Window &parent, const nall::string &filter, const nall::string &path = ""); +//private: + static void initialize(); +}; + +} diff --git a/phoenix/gtk/hexeditor.cpp b/phoenix/gtk/hexeditor.cpp new file mode 100755 index 00000000..3da8f3e1 --- /dev/null +++ b/phoenix/gtk/hexeditor.cpp @@ -0,0 +1,266 @@ +static bool HexEditor_keyPress(GtkWidget *widget, GdkEventKey *event, HexEditor *self) { + return self->keyPress(event->keyval); +} + +static bool HexEditor_scroll(GtkRange *range, GtkScrollType scroll, gdouble value, HexEditor *self) { + self->scroll((unsigned)value); + return false; +} + +void HexEditor::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height) { + widget->parent = &parent; + + hexEditor->size = 0; + hexEditor->offset = 0; + hexEditor->columns = 16; + hexEditor->rows = 16; + + object->widget = gtk_hbox_new(false, 0); + gtk_widget_set_size_request(object->widget, width, height); + + hexEditor->container = gtk_scrolled_window_new(0, 0); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(hexEditor->container), GTK_POLICY_NEVER, GTK_POLICY_NEVER); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(hexEditor->container), GTK_SHADOW_ETCHED_IN); + + hexEditor->widget = gtk_text_view_new(); + gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(hexEditor->widget), GTK_WRAP_NONE); + gtk_container_add(GTK_CONTAINER(hexEditor->container), hexEditor->widget); + g_signal_connect(G_OBJECT(hexEditor->widget), "key-press-event", G_CALLBACK(HexEditor_keyPress), (gpointer)this); + + hexEditor->scroll = gtk_vscrollbar_new((GtkAdjustment*)0); + gtk_range_set_range(GTK_RANGE(hexEditor->scroll), 0, 256); + gtk_range_set_increments(GTK_RANGE(hexEditor->scroll), 1, 16); + gtk_widget_set_sensitive(hexEditor->scroll, false); + g_signal_connect(G_OBJECT(hexEditor->scroll), "change-value", G_CALLBACK(HexEditor_scroll), (gpointer)this); + + gtk_box_pack_start(GTK_BOX(object->widget), hexEditor->container, true, true, 0); + gtk_box_pack_start(GTK_BOX(object->widget), hexEditor->scroll, false, false, 1); + + object->textBuffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(hexEditor->widget)); + hexEditor->cursor = gtk_text_buffer_get_mark(object->textBuffer, "insert"); + + gtk_fixed_put(GTK_FIXED(parent.object->formContainer), object->widget, x, y); + if(parent.window->defaultFont) setFont(*parent.window->defaultFont); + + gtk_widget_show(hexEditor->scroll); + gtk_widget_show(hexEditor->widget); + gtk_widget_show(hexEditor->container); + gtk_widget_show(object->widget); +} + +void HexEditor::setSize(unsigned size) { + hexEditor->size = size; + setScroll(); +} + +void HexEditor::setOffset(unsigned offset) { + hexEditor->offset = offset; + setScroll(); + updateScroll(); +} + +void HexEditor::setColumns(unsigned columns) { + hexEditor->columns = columns; + setScroll(); +} + +void HexEditor::setRows(unsigned rows) { + hexEditor->rows = rows; + setScroll(); +} + +void HexEditor::update() { + if(!onRead) { + gtk_text_buffer_set_text(object->textBuffer, "", -1); + return; + } + + unsigned position = cursorPosition(); + + string output; + unsigned offset = hexEditor->offset; + for(unsigned row = 0; row < hexEditor->rows; row++) { + output.append(hex<8>(offset)); + output.append(" "); + + string hexdata; + string ansidata = " "; + for(unsigned column = 0; column < hexEditor->columns; column++) { + if(offset < hexEditor->size) { + uint8_t data = onRead(offset++); + hexdata.append(hex<2>(data)); + hexdata.append(" "); + char buffer[2] = { data >= 0x20 && data <= 0x7e ? (char)data : '.', 0 }; + ansidata.append(buffer); + } else { + hexdata.append(" "); + ansidata.append(" "); + } + } + + output.append(hexdata); + output.append(ansidata); + if(offset >= hexEditor->size) break; + if(row != hexEditor->rows - 1) output.append("\n"); + } + + gtk_text_buffer_set_text(object->textBuffer, output, -1); + if(position == 0) position = 10; //start at first position where hex values can be entered + setCursorPosition(position); +} + +HexEditor::HexEditor() { + hexEditor = new HexEditor::Data; +} + +//internal + +bool HexEditor::keyPress(unsigned scancode) { + if(!onRead && !onWrite) return false; + + unsigned position = cursorPosition(); + unsigned lineWidth = 10 + (hexEditor->columns * 3) + 1 + (hexEditor->columns) + 1; + unsigned cursorY = position / lineWidth; + unsigned cursorX = position % lineWidth; + + if(scancode == GDK_Home) { + setCursorPosition(cursorY * lineWidth + 10); + return true; + } + + if(scancode == GDK_End) { + setCursorPosition(cursorY * lineWidth + 10 + (hexEditor->columns * 3 - 1)); + return true; + } + + if(scancode == GDK_Up) { + if(cursorY != 0) return false; + + signed newOffset = hexEditor->offset - hexEditor->columns; + if(newOffset >= 0) { + setOffset(newOffset); + update(); + } + return true; + } + + if(scancode == GDK_Down) { + if(cursorY != hexEditor->rows - 1) return false; + + signed newOffset = hexEditor->offset + hexEditor->columns; + if(newOffset + hexEditor->columns * hexEditor->rows - (hexEditor->columns - 1) <= hexEditor->size) { + setOffset(newOffset); + update(); + } + return true; + } + + if(scancode == GDK_Page_Up) { + signed newOffset = hexEditor->offset - hexEditor->columns * hexEditor->rows; + if(newOffset >= 0) { + setOffset(newOffset); + update(); + } else { + setOffset(0); + update(); + } + return true; + } + + if(scancode == GDK_Page_Down) { + signed newOffset = hexEditor->offset + hexEditor->columns * hexEditor->rows; + for(unsigned n = 0; n < hexEditor->rows; n++) { + if(newOffset + hexEditor->columns * hexEditor->rows - (hexEditor->columns - 1) <= hexEditor->size) { + setOffset(newOffset); + update(); + break; + } + newOffset -= hexEditor->columns; + } + return true; + } + + //convert scancode to hex nibble + if(scancode >= '0' && scancode <= '9') scancode = scancode - '0'; + else if(scancode >= 'A' && scancode <= 'F') scancode = scancode - 'A' + 10; + else if(scancode >= 'a' && scancode <= 'f') scancode = scancode - 'a' + 10; + else return false; //not a valid hex value + + if(cursorX >= 10) { + //not on an offset + cursorX -= 10; + if((cursorX % 3) != 2) { + //not on a space + bool cursorNibble = (cursorX % 3) == 1; //0 = high, 1 = low + cursorX /= 3; + if(cursorX < hexEditor->columns) { + //not in ANSI region + unsigned offset = hexEditor->offset + (cursorY * hexEditor->columns + cursorX); + + if(offset >= hexEditor->size) return false; //do not edit past end of file + uint8_t data = onRead(offset); + + //write modified value + if(cursorNibble == 1) { + data = (data & 0xf0) | (scancode << 0); + } else { + data = (data & 0x0f) | (scancode << 4); + } + onWrite(offset, data); + + //auto-advance cursor to next nibble/byte + position++; + if(cursorNibble && cursorX != hexEditor->columns - 1) position++; + setCursorPosition(position); + + //refresh output to reflect modified data + update(); + } + } + } + + return true; +} + +void HexEditor::scroll(unsigned position) { + unsigned rows = hexEditor->size / hexEditor->columns; + if(position >= rows) position = rows - 1; + setOffset(position * hexEditor->columns); + update(); +} + +void HexEditor::setScroll() { + unsigned rows = hexEditor->size / hexEditor->columns; + if(rows) rows--; + if(rows) { + gtk_range_set_range(GTK_RANGE(hexEditor->scroll), 0, rows); + gtk_widget_set_sensitive(hexEditor->scroll, true); + } else { + gtk_widget_set_sensitive(hexEditor->scroll, false); + } +} + +void HexEditor::updateScroll() { + unsigned row = hexEditor->offset / hexEditor->columns; + gtk_range_set_value(GTK_RANGE(hexEditor->scroll), row); +} + +unsigned HexEditor::cursorPosition() { + GtkTextIter iter; + gtk_text_buffer_get_iter_at_mark(object->textBuffer, &iter, hexEditor->cursor); + return gtk_text_iter_get_offset(&iter); +} + +void HexEditor::setCursorPosition(unsigned position) { + GtkTextIter iter; + gtk_text_buffer_get_iter_at_mark(object->textBuffer, &iter, hexEditor->cursor); + + //GTK+ will throw a hundred errors on the terminal + //if you set an iterator past the end of the text buffer + GtkTextIter endIter; + gtk_text_buffer_get_end_iter(object->textBuffer, &iter); + unsigned endPosition = gtk_text_iter_get_offset(&iter); + + gtk_text_iter_set_offset(&iter, min(position, endPosition)); + gtk_text_buffer_place_cursor(object->textBuffer, &iter); +} diff --git a/phoenix/gtk/horizontalslider.cpp b/phoenix/gtk/horizontalslider.cpp new file mode 100755 index 00000000..9e6e76c7 --- /dev/null +++ b/phoenix/gtk/horizontalslider.cpp @@ -0,0 +1,25 @@ +static void HorizontalSlider_change(HorizontalSlider *self) { + if(self->object->position == self->position()) return; + self->object->position = self->position(); + if(self->onChange) self->onChange(); +} + +void HorizontalSlider::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, unsigned length) { + object->position = 0; + length += (length == 0); + object->widget = gtk_hscale_new_with_range(0, length - 1, 1); + widget->parent = &parent; + gtk_scale_set_draw_value(GTK_SCALE(object->widget), false); + gtk_widget_set_size_request(object->widget, width, height); + g_signal_connect_swapped(G_OBJECT(object->widget), "value-changed", G_CALLBACK(HorizontalSlider_change), (gpointer)this); + gtk_fixed_put(GTK_FIXED(parent.object->formContainer), object->widget, x, y); + gtk_widget_show(object->widget); +} + +unsigned HorizontalSlider::position() { + return (unsigned)gtk_range_get_value(GTK_RANGE(object->widget)); +} + +void HorizontalSlider::setPosition(unsigned position) { + gtk_range_set_value(GTK_RANGE(object->widget), position); +} diff --git a/phoenix/gtk/label.cpp b/phoenix/gtk/label.cpp new file mode 100755 index 00000000..8321523f --- /dev/null +++ b/phoenix/gtk/label.cpp @@ -0,0 +1,13 @@ +void Label::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + object->widget = gtk_label_new(text); + widget->parent = &parent; + gtk_misc_set_alignment(GTK_MISC(object->widget), 0.0, 0.5); + gtk_widget_set_size_request(object->widget, width, height); + if(parent.window->defaultFont) setFont(*parent.window->defaultFont); + gtk_fixed_put(GTK_FIXED(parent.object->formContainer), object->widget, x, y); + gtk_widget_show(object->widget); +} + +void Label::setText(const string &text) { + gtk_label_set_text(GTK_LABEL(object->widget), text); +} diff --git a/phoenix/gtk/listbox.cpp b/phoenix/gtk/listbox.cpp new file mode 100755 index 00000000..3d390338 --- /dev/null +++ b/phoenix/gtk/listbox.cpp @@ -0,0 +1,195 @@ +static void ListBox_activate(ListBox *self) { + signed selection = -1; + if(auto position = self->selection()) selection = position(); + self->listBox->selection = selection; + if(self->onActivate) self->onActivate(); +} + +static void ListBox_change(ListBox *self) { + signed selection = -1; + if(auto position = self->selection()) selection = position(); + if(selection == self->listBox->selection) return; + self->listBox->selection = selection; + if(self->onChange) self->onChange(); +} + +static void ListBox_tick(GtkCellRendererToggle *cell, gchar *path_string, ListBox *self) { + unsigned index = decimal(path_string); + self->setChecked(index, !self->checked(index)); + if(self->onTick) self->onTick(index); +} + +void ListBox::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + listBox->selection = -1; + object->widget = gtk_scrolled_window_new(0, 0); + widget->parent = &parent; + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(object->widget), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(object->widget), GTK_SHADOW_ETCHED_IN); + gtk_widget_set_size_request(object->widget, width, height); + + lstring list; + list.split("\t", string("\t", text)); + + GType *v = (GType*)malloc(list.size() * sizeof(GType)); + for(unsigned i = 0; i < list.size(); i++) v[i] = (i == 0 ? G_TYPE_BOOLEAN : G_TYPE_STRING); + listBox->store = gtk_list_store_newv(list.size(), v); + free(v); + + object->subWidget = gtk_tree_view_new_with_model(GTK_TREE_MODEL(listBox->store)); + gtk_container_add(GTK_CONTAINER(object->widget), object->subWidget); + g_object_unref(G_OBJECT(listBox->store)); + + for(unsigned i = 0; i < list.size(); i++) { + if(i == 0) { + listBox->column[i].renderer = gtk_cell_renderer_toggle_new(); + listBox->column[i].column = gtk_tree_view_column_new_with_attributes( + "", listBox->column[i].renderer, "active", i, (void*)0 + ); + gtk_tree_view_column_set_resizable(listBox->column[i].column, false); + gtk_tree_view_column_set_visible(listBox->column[i].column, listBox->checkable); + g_signal_connect(listBox->column[i].renderer, "toggled", G_CALLBACK(ListBox_tick), (gpointer)this); + } else { + listBox->column[i].renderer = gtk_cell_renderer_text_new(); + listBox->column[i].column = gtk_tree_view_column_new_with_attributes( + "", listBox->column[i].renderer, "text", i, (void*)0 + ); + gtk_tree_view_column_set_resizable(listBox->column[i].column, true); + } + listBox->column[i].label = gtk_label_new(list[i]); + gtk_tree_view_column_set_widget(GTK_TREE_VIEW_COLUMN(listBox->column[i].column), listBox->column[i].label); + gtk_tree_view_append_column(GTK_TREE_VIEW(object->subWidget), listBox->column[i].column); + gtk_widget_show(listBox->column[i].label); + } + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(object->subWidget), false); + gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(object->subWidget), list.size() >= 3); //>= 2 + one for the checkbox column + gtk_tree_view_set_search_column(GTK_TREE_VIEW(object->subWidget), 1); + + g_signal_connect_swapped(G_OBJECT(object->subWidget), "cursor-changed", G_CALLBACK(ListBox_change), (gpointer)this); + g_signal_connect_swapped(G_OBJECT(object->subWidget), "row-activated", G_CALLBACK(ListBox_activate), (gpointer)this); + + if(parent.window->defaultFont) setFont(*parent.window->defaultFont); + gtk_fixed_put(GTK_FIXED(parent.object->formContainer), object->widget, x, y); + gtk_widget_show(object->subWidget); + gtk_widget_show(object->widget); +} + +void ListBox::setFocused() { + gtk_widget_grab_focus(object->subWidget); +} + +void ListBox::setHeaderVisible(bool visible) { + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(object->subWidget), visible); +} + +void ListBox::setCheckable(bool checkable) { + listBox->checkable = checkable; + if(object->subWidget) gtk_tree_view_column_set_visible(listBox->column[0].column, checkable); +} + +void ListBox::setFont(Font &font) { + Widget::setFont(font); + unsigned columns = 1; + while(true) { + if(gtk_tree_view_get_column(GTK_TREE_VIEW(object->subWidget), columns) == 0) break; + columns++; + } + for(unsigned i = 0; i < columns; i++) { + gtk_widget_modify_font(listBox->column[i].label, font.font->font); + } +} + +void ListBox::reset() { + listBox->selection = -1; + gtk_list_store_clear(GTK_LIST_STORE(listBox->store)); + gtk_tree_view_set_model(GTK_TREE_VIEW(object->subWidget), GTK_TREE_MODEL(listBox->store)); + //reset gtk_scrolled_window scrollbar position to 0,0 (top-left), as ListBox is now empty + gtk_scrolled_window_set_hadjustment(GTK_SCROLLED_WINDOW(object->widget), 0); + gtk_scrolled_window_set_vadjustment(GTK_SCROLLED_WINDOW(object->widget), 0); +} + +void ListBox::resizeColumnsToContent() { + gtk_tree_view_columns_autosize(GTK_TREE_VIEW(object->subWidget)); +} + +void ListBox::addItem(const string &text) { + lstring list; + list.split("\t", text); + GtkTreeIter iter; + gtk_list_store_append(listBox->store, &iter); + unsigned index = 1; + foreach(item, list) gtk_list_store_set(listBox->store, &iter, index++, (const char*)item, -1); +} + +void ListBox::setItem(unsigned row, const string &text) { + GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(object->subWidget)); + GtkTreeIter iter; + for(unsigned i = 0; i <= row; i++) { + if(i == 0) gtk_tree_model_get_iter_first(model, &iter); + else gtk_tree_model_iter_next(model, &iter); + } + + lstring list; + list.split("\t", text); + unsigned index = 1; + foreach(item, list) gtk_list_store_set(listBox->store, &iter, index++, (const char*)item, -1); +} + +bool ListBox::checked(unsigned row) { + GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(object->subWidget)); + GtkTreePath *path = gtk_tree_path_new_from_string(string(row)); + GtkTreeIter iter; + bool state; + gtk_tree_model_get_iter(model, &iter, path); + gtk_tree_model_get(model, &iter, 0, &state, -1); + gtk_tree_path_free(path); + return state; +} + +void ListBox::setChecked(unsigned row, bool checked) { + GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(object->subWidget)); + GtkTreePath *path = gtk_tree_path_new_from_string(string(row)); + GtkTreeIter iter; + gtk_tree_model_get_iter(model, &iter, path); + gtk_list_store_set(GTK_LIST_STORE(model), &iter, 0, checked, -1); + gtk_tree_path_free(path); +} + +optional ListBox::selection() { + GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(object->subWidget)); + GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(object->subWidget)); + GtkTreeIter iter; + if(gtk_tree_model_get_iter_first(model, &iter) == false) return { false, 0 }; + if(gtk_tree_selection_iter_is_selected(selection, &iter) == true) return { true, 0 }; + for(unsigned i = 1;; i++) { + if(gtk_tree_model_iter_next(model, &iter) == false) return { false, 0 }; + if(gtk_tree_selection_iter_is_selected(selection, &iter) == true) return { true, i }; + } + return { false, 0 }; +} + +void ListBox::setSelection(unsigned row) { + signed current = -1; + if(auto position = selection()) current = position(); + GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(object->subWidget)); + GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(object->subWidget)); + gtk_tree_selection_unselect_all(selection); + + GtkTreeIter iter; + if(gtk_tree_model_get_iter_first(model, &iter) == false) return; + if(row == 0) { + gtk_tree_selection_select_iter(selection, &iter); + return; + } + for(unsigned i = 1;; i++) { + if(gtk_tree_model_iter_next(model, &iter) == false) return; + if(row == i) { + gtk_tree_selection_select_iter(selection, &iter); + return; + } + } +} + +ListBox::ListBox() { + listBox = new ListBox::Data; + listBox->checkable = false; +} diff --git a/phoenix/gtk/menu.cpp b/phoenix/gtk/menu.cpp new file mode 100755 index 00000000..fbfc67bc --- /dev/null +++ b/phoenix/gtk/menu.cpp @@ -0,0 +1,129 @@ +static void Action_setFont(GtkWidget *widget, gpointer font) { + if(font) { + gtk_widget_modify_font(widget, (PangoFontDescription*)font); + if(GTK_IS_CONTAINER(widget)) { + gtk_container_foreach(GTK_CONTAINER(widget), (GtkCallback)Action_setFont, (PangoFontDescription*)font); + } + } +} + +bool Action::visible() { + return gtk_widget_get_visible(object->widget); +} + +void Action::setVisible(bool visible) { + gtk_widget_set_visible(object->widget, visible); +} + +bool Action::enabled() { + return gtk_widget_get_sensitive(object->widget); +} + +void Action::setEnabled(bool enabled) { + gtk_widget_set_sensitive(object->widget, enabled); +} + +Action::Action() { + action = new Action::Data; + action->font = 0; +} + +void Menu::create(Window &parent, const string &text) { + action->font = parent.window->defaultFont; + object->menu = gtk_menu_new(); + object->widget = gtk_menu_item_new_with_label(text); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(object->widget), object->menu); + if(action->font) Action_setFont(object->widget, action->font->font->font); + gtk_menu_bar_append(parent.object->menu, object->widget); + gtk_widget_show(object->widget); +} + +void Menu::create(Menu &parent, const string &text) { + action->font = parent.action->font; + object->menu = gtk_menu_new(); + object->widget = gtk_menu_item_new_with_label(text); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(object->widget), object->menu); + if(action->font) Action_setFont(object->widget, action->font->font->font); + gtk_menu_shell_append(GTK_MENU_SHELL(parent.object->menu), object->widget); + gtk_widget_show(object->widget); +} + +void MenuSeparator::create(Menu &parent) { + action->font = parent.action->font; + object->widget = gtk_separator_menu_item_new(); + if(action->font) Action_setFont(object->widget, action->font->font->font); + gtk_menu_shell_append(GTK_MENU_SHELL(parent.object->menu), object->widget); + gtk_widget_show(object->widget); +} + +static void MenuItem_tick(MenuItem *self) { + if(self->onTick) self->onTick(); +} + +void MenuItem::create(Menu &parent, const string &text) { + action->font = parent.action->font; + object->widget = gtk_menu_item_new_with_label(text); + g_signal_connect_swapped(G_OBJECT(object->widget), "activate", G_CALLBACK(MenuItem_tick), (gpointer)this); + if(action->font) Action_setFont(object->widget, action->font->font->font); + gtk_menu_shell_append(GTK_MENU_SHELL(parent.object->menu), object->widget); + gtk_widget_show(object->widget); +} + +static void MenuCheckItem_tick(MenuCheckItem *self) { + if(self->onTick && self->object->locked == false) self->onTick(); +} + +void MenuCheckItem::create(Menu &parent, const string &text) { + action->font = parent.action->font; + object->widget = gtk_check_menu_item_new_with_label(text); + g_signal_connect_swapped(G_OBJECT(object->widget), "toggled", G_CALLBACK(MenuCheckItem_tick), (gpointer)this); + if(action->font) Action_setFont(object->widget, action->font->font->font); + gtk_menu_shell_append(GTK_MENU_SHELL(parent.object->menu), object->widget); + gtk_widget_show(object->widget); +} + +bool MenuCheckItem::checked() { + return gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(object->widget)); +} + +void MenuCheckItem::setChecked(bool state) { + object->locked = true; + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(object->widget), state); + object->locked = false; +} + +static void MenuRadioItem_tick(MenuRadioItem *self) { + if(self->onTick && self->checked() && self->object->locked == false) self->onTick(); +} + +void MenuRadioItem::create(Menu &parent, const string &text) { + first = this; + action->font = parent.action->font; + object->parentMenu = &parent; + object->widget = gtk_radio_menu_item_new_with_label(0, text); + g_signal_connect_swapped(G_OBJECT(object->widget), "toggled", G_CALLBACK(MenuRadioItem_tick), (gpointer)this); + if(action->font) Action_setFont(object->widget, action->font->font->font); + gtk_menu_shell_append(GTK_MENU_SHELL(parent.object->menu), object->widget); + gtk_widget_show(object->widget); +} + +void MenuRadioItem::create(MenuRadioItem &parent, const string &text) { + first = parent.first; + action->font = parent.action->font; + object->parentMenu = parent.object->parentMenu; + object->widget = gtk_radio_menu_item_new_with_label_from_widget(GTK_RADIO_MENU_ITEM(first->object->widget), text); + g_signal_connect_swapped(G_OBJECT(object->widget), "toggled", G_CALLBACK(MenuRadioItem_tick), (gpointer)this); + if(action->font) Action_setFont(object->widget, action->font->font->font); + gtk_menu_shell_append(GTK_MENU_SHELL(object->parentMenu->object->menu), object->widget); + gtk_widget_show(object->widget); +} + +bool MenuRadioItem::checked() { + return gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(object->widget)); +} + +void MenuRadioItem::setChecked() { + object->locked = true; + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(object->widget), true); + object->locked = false; +} diff --git a/phoenix/gtk/messagewindow.cpp b/phoenix/gtk/messagewindow.cpp new file mode 100755 index 00000000..6c41f022 --- /dev/null +++ b/phoenix/gtk/messagewindow.cpp @@ -0,0 +1,65 @@ +static MessageWindow::Response MessageWindow_response(MessageWindow::Buttons buttons, gint response) { + if(response == GTK_RESPONSE_OK) return MessageWindow::Response::Ok; + if(response == GTK_RESPONSE_CANCEL) return MessageWindow::Response::Cancel; + if(response == GTK_RESPONSE_YES) return MessageWindow::Response::Yes; + if(response == GTK_RESPONSE_NO) return MessageWindow::Response::No; + if(buttons == MessageWindow::Buttons::OkCancel) return MessageWindow::Response::Cancel; + if(buttons == MessageWindow::Buttons::YesNo) return MessageWindow::Response::No; + return MessageWindow::Response::Ok; +} + +MessageWindow::Response MessageWindow::information(Window &parent, const string &text, MessageWindow::Buttons buttons) { + GtkButtonsType buttonsType = GTK_BUTTONS_OK; + if(buttons == Buttons::OkCancel) buttonsType = GTK_BUTTONS_OK_CANCEL; + if(buttons == Buttons::YesNo) buttonsType = GTK_BUTTONS_YES_NO; + + GtkWidget *dialog = gtk_message_dialog_new( + &parent != &Window::None ? GTK_WINDOW(parent.object->widget) : (GtkWindow*)0, + GTK_DIALOG_MODAL, GTK_MESSAGE_INFO, buttonsType, "%s", (const char*)text + ); + gint response = gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + return MessageWindow_response(buttons, response); +} + +MessageWindow::Response MessageWindow::question(Window &parent, const string &text, MessageWindow::Buttons buttons) { + GtkButtonsType buttonsType = GTK_BUTTONS_OK; + if(buttons == Buttons::OkCancel) buttonsType = GTK_BUTTONS_OK_CANCEL; + if(buttons == Buttons::YesNo) buttonsType = GTK_BUTTONS_YES_NO; + + GtkWidget *dialog = gtk_message_dialog_new( + &parent != &Window::None ? GTK_WINDOW(parent.object->widget) : (GtkWindow*)0, + GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION, buttonsType, "%s", (const char*)text + ); + gint response = gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + return MessageWindow_response(buttons, response); +} + +MessageWindow::Response MessageWindow::warning(Window &parent, const string &text, MessageWindow::Buttons buttons) { + GtkButtonsType buttonsType = GTK_BUTTONS_OK; + if(buttons == Buttons::OkCancel) buttonsType = GTK_BUTTONS_OK_CANCEL; + if(buttons == Buttons::YesNo) buttonsType = GTK_BUTTONS_YES_NO; + + GtkWidget *dialog = gtk_message_dialog_new( + &parent != &Window::None ? GTK_WINDOW(parent.object->widget) : (GtkWindow*)0, + GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, buttonsType, "%s", (const char*)text + ); + gint response = gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + return MessageWindow_response(buttons, response); +} + +MessageWindow::Response MessageWindow::critical(Window &parent, const string &text, MessageWindow::Buttons buttons) { + GtkButtonsType buttonsType = GTK_BUTTONS_OK; + if(buttons == Buttons::OkCancel) buttonsType = GTK_BUTTONS_OK_CANCEL; + if(buttons == Buttons::YesNo) buttonsType = GTK_BUTTONS_YES_NO; + + GtkWidget *dialog = gtk_message_dialog_new( + &parent != &Window::None ? GTK_WINDOW(parent.object->widget) : (GtkWindow*)0, + GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, buttonsType, "%s", (const char*)text + ); + gint response = gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + return MessageWindow_response(buttons, response); +} diff --git a/phoenix/gtk/object.cpp b/phoenix/gtk/object.cpp new file mode 100755 index 00000000..5b5e04f6 --- /dev/null +++ b/phoenix/gtk/object.cpp @@ -0,0 +1,69 @@ +struct Object::Data { + bool locked; + GtkWidget *widget; + GtkWidget *subWidget; + GtkWidget *menuContainer; + GtkWidget *formContainer; + GtkWidget *statusContainer; + GtkWidget *menu; + GtkWidget *status; + Menu *parentMenu; + Window *parentWindow; + GtkTextBuffer *textBuffer; + unsigned position; +}; + +struct Font::Data { + PangoFontDescription *font; +}; + +struct Action::Data { + Font *font; +}; + +struct Widget::Data { + Window *parent; +}; + +struct Window::Data { + Font *defaultFont; +}; + +struct Canvas::Data { + uint32_t *bufferRGB; + uint32_t *bufferBGR; + unsigned pitch; +}; + +struct HexEditor::Data { + GtkWidget *container; + GtkWidget *widget; + GtkWidget *scroll; + + GtkTextMark *cursor; + unsigned size; + unsigned offset; + unsigned columns; + unsigned rows; +}; + +struct ListBox::Data { + GtkListStore *store; + struct GtkColumn { + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + GtkWidget *label; + }; + linear_vector column; + bool checkable; + signed selection; +}; + +void Object::unused() { +} + +Object::Object() { + OS::initialize(); + object = new Object::Data; + object->locked = false; +} diff --git a/phoenix/gtk/progressbar.cpp b/phoenix/gtk/progressbar.cpp new file mode 100755 index 00000000..193e924d --- /dev/null +++ b/phoenix/gtk/progressbar.cpp @@ -0,0 +1,12 @@ +void ProgressBar::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height) { + object->widget = gtk_progress_bar_new(); + widget->parent = &parent; + gtk_widget_set_size_request(object->widget, width, height); + gtk_fixed_put(GTK_FIXED(parent.object->formContainer), object->widget, x, y); + gtk_widget_show(object->widget); +} + +void ProgressBar::setPosition(unsigned position) { + position = position <= 100 ? position : 0; + gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(object->widget), (double)position / 100.0); +} diff --git a/phoenix/gtk/radiobox.cpp b/phoenix/gtk/radiobox.cpp new file mode 100755 index 00000000..603e199f --- /dev/null +++ b/phoenix/gtk/radiobox.cpp @@ -0,0 +1,36 @@ +static void RadioBox_tick(RadioBox *self) { + if(self->onTick && self->checked() && self->object->locked == false) self->onTick(); +} + +void RadioBox::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + first = this; + object->parentWindow = &parent; + object->widget = gtk_radio_button_new_with_label(0, text); + widget->parent = &parent; + gtk_widget_set_size_request(object->widget, width, height); + g_signal_connect_swapped(G_OBJECT(object->widget), "toggled", G_CALLBACK(RadioBox_tick), (gpointer)this); + if(parent.window->defaultFont) setFont(*parent.window->defaultFont); + gtk_fixed_put(GTK_FIXED(parent.object->formContainer), object->widget, x, y); + gtk_widget_show(object->widget); +} + +void RadioBox::create(RadioBox &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + first = parent.first; + object->parentWindow = parent.object->parentWindow; + object->widget = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(parent.object->widget), text); + gtk_widget_set_size_request(object->widget, width, height); + g_signal_connect_swapped(G_OBJECT(object->widget), "toggled", G_CALLBACK(RadioBox_tick), (gpointer)this); + if(object->parentWindow->window->defaultFont) setFont(*object->parentWindow->window->defaultFont); + gtk_fixed_put(GTK_FIXED(object->parentWindow->object->formContainer), object->widget, x, y); + gtk_widget_show(object->widget); +} + +bool RadioBox::checked() { + return gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(object->widget)); +} + +void RadioBox::setChecked() { + object->locked = true; + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(object->widget), true); + object->locked = false; +} diff --git a/phoenix/gtk/textbox.cpp b/phoenix/gtk/textbox.cpp new file mode 100755 index 00000000..66455f03 --- /dev/null +++ b/phoenix/gtk/textbox.cpp @@ -0,0 +1,33 @@ +static void TextBox_activate(TextBox *self) { + if(self->onActivate) self->onActivate(); +} + +static void TextBox_change(TextBox *self) { + if(self->object->locked == false && self->onChange) self->onChange(); +} + +void TextBox::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + object->widget = gtk_entry_new(); + widget->parent = &parent; + gtk_entry_set_text(GTK_ENTRY(object->widget), text); + gtk_widget_set_size_request(object->widget, width, height); + g_signal_connect_swapped(G_OBJECT(object->widget), "activate", G_CALLBACK(TextBox_activate), (gpointer)this); + g_signal_connect_swapped(G_OBJECT(object->widget), "changed", G_CALLBACK(TextBox_change), (gpointer)this); + if(parent.window->defaultFont) setFont(*parent.window->defaultFont); + gtk_fixed_put(GTK_FIXED(parent.object->formContainer), object->widget, x, y); + gtk_widget_show(object->widget); +} + +void TextBox::setEditable(bool editable) { + gtk_entry_set_editable(GTK_ENTRY(object->widget), editable); +} + +string TextBox::text() { + return gtk_entry_get_text(GTK_ENTRY(object->widget)); +} + +void TextBox::setText(const string &text) { + object->locked = true; + gtk_entry_set_text(GTK_ENTRY(object->widget), text); + object->locked = false; +} diff --git a/phoenix/gtk/verticalslider.cpp b/phoenix/gtk/verticalslider.cpp new file mode 100755 index 00000000..1cca9e4c --- /dev/null +++ b/phoenix/gtk/verticalslider.cpp @@ -0,0 +1,25 @@ +static void VerticalSlider_change(VerticalSlider *self) { + if(self->object->position == self->position()) return; + self->object->position = self->position(); + if(self->onChange) self->onChange(); +} + +void VerticalSlider::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, unsigned length) { + object->position = 0; + length += (length == 0); + object->widget = gtk_vscale_new_with_range(0, length - 1, 1); + widget->parent = &parent; + gtk_scale_set_draw_value(GTK_SCALE(object->widget), false); + gtk_widget_set_size_request(object->widget, width, height); + g_signal_connect_swapped(G_OBJECT(object->widget), "value-changed", G_CALLBACK(VerticalSlider_change), (gpointer)this); + gtk_fixed_put(GTK_FIXED(parent.object->formContainer), object->widget, x, y); + gtk_widget_show(object->widget); +} + +unsigned VerticalSlider::position() { + return (unsigned)gtk_range_get_value(GTK_RANGE(object->widget)); +} + +void VerticalSlider::setPosition(unsigned position) { + gtk_range_set_value(GTK_RANGE(object->widget), position); +} diff --git a/phoenix/gtk/viewport.cpp b/phoenix/gtk/viewport.cpp new file mode 100755 index 00000000..3b407727 --- /dev/null +++ b/phoenix/gtk/viewport.cpp @@ -0,0 +1,20 @@ +void Viewport::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height) { + object->widget = gtk_drawing_area_new(); + widget->parent = &parent; +//gtk_widget_set_double_buffered(object->widget, false); + gtk_widget_set_size_request(object->widget, width, height); + + GdkColor color; + color.pixel = 0; + color.red = 0; + color.green = 0; + color.blue = 0; + gtk_widget_modify_bg(object->widget, GTK_STATE_NORMAL, &color); + + gtk_fixed_put(GTK_FIXED(parent.object->formContainer), object->widget, x, y); + gtk_widget_show(object->widget); +} + +uintptr_t Viewport::handle() { + return GDK_WINDOW_XID(object->widget->window); +} diff --git a/phoenix/gtk/widget.cpp b/phoenix/gtk/widget.cpp new file mode 100755 index 00000000..4f0bf079 --- /dev/null +++ b/phoenix/gtk/widget.cpp @@ -0,0 +1,47 @@ +static void Widget_setFont(GtkWidget *widget, gpointer font) { + gtk_widget_modify_font(widget, (PangoFontDescription*)font); + if(GTK_IS_CONTAINER(widget)) { + gtk_container_foreach(GTK_CONTAINER(widget), (GtkCallback)Widget_setFont, font); + } +} + +void Widget::setFont(Font &font) { + Widget_setFont(object->widget, font.font->font); +} + +bool Widget::visible() { + return gtk_widget_get_visible(object->widget); +} + +void Widget::setVisible(bool visible) { + if(visible) gtk_widget_show(object->widget); + else gtk_widget_hide(object->widget); +} + +bool Widget::enabled() { + return gtk_widget_get_sensitive(object->widget); +} + +void Widget::setEnabled(bool enabled) { + gtk_widget_set_sensitive(object->widget, enabled); +} + +bool Widget::focused() { + return gtk_widget_is_focus(object->widget); +} + +void Widget::setFocused() { + if(visible() == false) setVisible(true); + gtk_widget_grab_focus(object->widget); +} + +void Widget::setGeometry(unsigned x, unsigned y, unsigned width, unsigned height) { + if(widget->parent == 0) return; + gtk_fixed_move(GTK_FIXED(widget->parent->object->formContainer), object->widget, x, y); + gtk_widget_set_size_request(object->widget, width, height); +} + +Widget::Widget() { + widget = new Widget::Data; + widget->parent = 0; +} diff --git a/phoenix/gtk/window.cpp b/phoenix/gtk/window.cpp new file mode 100755 index 00000000..5b289296 --- /dev/null +++ b/phoenix/gtk/window.cpp @@ -0,0 +1,99 @@ +static gint Window_close(Window *window) { + if(window->onClose) { + if(window->onClose()) window->setVisible(false); + return true; + } + window->setVisible(false); + return true; +} + +void Window::create(unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + object->widget = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_move(GTK_WINDOW(object->widget), x, y); + + gtk_window_set_title(GTK_WINDOW(object->widget), text); + gtk_window_set_resizable(GTK_WINDOW(object->widget), false); + gtk_widget_set_app_paintable(object->widget, true); + + g_signal_connect_swapped(G_OBJECT(object->widget), "delete_event", G_CALLBACK(Window_close), (gpointer)this); + + object->menuContainer = gtk_vbox_new(false, 0); + gtk_container_add(GTK_CONTAINER(object->widget), object->menuContainer); + gtk_widget_show(object->menuContainer); + + object->menu = gtk_menu_bar_new(); + gtk_box_pack_start(GTK_BOX(object->menuContainer), object->menu, false, false, 0); + + object->formContainer = gtk_fixed_new(); + gtk_widget_set_size_request(object->formContainer, width, height); + gtk_box_pack_start(GTK_BOX(object->menuContainer), object->formContainer, true, true, 0); + gtk_widget_show(object->formContainer); + + object->statusContainer = gtk_event_box_new(); + object->status = gtk_statusbar_new(); + gtk_statusbar_set_has_resize_grip(GTK_STATUSBAR(object->status), false); + gtk_container_add(GTK_CONTAINER(object->statusContainer), object->status); + gtk_box_pack_start(GTK_BOX(object->menuContainer), object->statusContainer, false, false, 0); + gtk_widget_show(object->statusContainer); + + gtk_widget_realize(object->widget); +} + +bool Window::focused() { + return gtk_window_is_active(GTK_WINDOW(object->widget)); +} + +void Window::setFocused() { + gtk_window_present(GTK_WINDOW(object->widget)); +} + +Geometry Window::geometry() { + gint x, y, width, height; + gtk_window_get_position(GTK_WINDOW(object->widget), &x, &y); + gtk_widget_get_size_request(object->formContainer, &width, &height); + return Geometry(x, y, width, height); +} + +void Window::setGeometry(unsigned x, unsigned y, unsigned width, unsigned height) { + gtk_window_move(GTK_WINDOW(object->widget), x, y); + gtk_widget_set_size_request(object->formContainer, width, height); +} + +void Window::setDefaultFont(Font &font) { + window->defaultFont = &font; +} + +void Window::setFont(Font &font) { + Widget_setFont(object->status, font.font->font); +} + +void Window::setBackgroundColor(uint8_t red, uint8_t green, uint8_t blue) { + GdkColor color; + color.pixel = (red << 16) | (green << 8) | (blue << 0); + color.red = (red << 8) | (red << 0); + color.green = (green << 8) | (green << 0); + color.blue = (blue << 8) | (blue << 0); + gtk_widget_modify_bg(object->widget, GTK_STATE_NORMAL, &color); +} + +void Window::setTitle(const string &text) { + gtk_window_set_title(GTK_WINDOW(object->widget), text); +} + +void Window::setStatusText(const string &text) { + gtk_statusbar_pop(GTK_STATUSBAR(object->status), 1); + gtk_statusbar_push(GTK_STATUSBAR(object->status), 1, text); +} + +void Window::setMenuVisible(bool visible) { + gtk_widget_set_visible(object->menu, visible); +} + +void Window::setStatusVisible(bool visible) { + gtk_widget_set_visible(object->status, visible); +} + +Window::Window() { + window = new Window::Data; + window->defaultFont = 0; +} diff --git a/phoenix/phoenix.cpp b/phoenix/phoenix.cpp new file mode 100755 index 00000000..bf51451b --- /dev/null +++ b/phoenix/phoenix.cpp @@ -0,0 +1,17 @@ +#if defined(PHOENIX_WINDOWS) + #define UNICODE + #define WINVER 0x0501 + #define _WIN32_WINNT 0x0501 + #define _WIN32_IE 0x0600 + #define NOMINMAX +#endif + +#include "phoenix.hpp" + +#if defined(PHOENIX_WINDOWS) + #include "windows/windows.cpp" +#elif defined(PHOENIX_GTK) + #include "gtk/gtk.cpp" +#elif defined(PHOENIX_QT) + #include "qt/qt.cpp" +#endif diff --git a/phoenix/phoenix.hpp b/phoenix/phoenix.hpp new file mode 100755 index 00000000..99c82d15 --- /dev/null +++ b/phoenix/phoenix.hpp @@ -0,0 +1,15 @@ +#include +#include +#include +#include +#include +#include +#include + +#if defined(PHOENIX_WINDOWS) + #include "windows/windows.hpp" +#elif defined(PHOENIX_GTK) + #include "gtk/gtk.hpp" +#elif defined(PHOENIX_QT) + #include "qt/qt.hpp" +#endif diff --git a/phoenix/qt/button.cpp b/phoenix/qt/button.cpp new file mode 100755 index 00000000..e11a14b5 --- /dev/null +++ b/phoenix/qt/button.cpp @@ -0,0 +1,13 @@ +void Button::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + button->setParent(parent.window->container); + button->setGeometry(x, y, width, height); + button->setText(QString::fromUtf8(text)); + if(parent.window->defaultFont) button->setFont(*parent.window->defaultFont); + button->show(); + button->connect(button, SIGNAL(released()), SLOT(onTick())); +} + +Button::Button() { + button = new Button::Data(*this); + widget->widget = button; +} diff --git a/phoenix/qt/canvas.cpp b/phoenix/qt/canvas.cpp new file mode 100755 index 00000000..a0887604 --- /dev/null +++ b/phoenix/qt/canvas.cpp @@ -0,0 +1,39 @@ +void Canvas::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height) { + canvas->image = new QImage(width, height, QImage::Format_RGB32); + canvas->image->fill(0); + canvas->setParent(parent.window->container); + canvas->setGeometry(x, y, width, height); + canvas->show(); +} + +void Canvas::setGeometry(unsigned x, unsigned y, unsigned width, unsigned height) { + delete canvas->image; + canvas->image = new QImage(width, height, QImage::Format_RGB32); + canvas->image->fill(0); + canvas->setGeometry(x, y, width, height); + canvas->update(); +} + +uint32_t* Canvas::buffer() { + return (uint32_t*)canvas->image->bits(); +} + +void Canvas::redraw() { + canvas->update(); +} + +Canvas::Canvas() { + canvas = new Canvas::Data(*this); + canvas->image = 0; + widget->widget = canvas; +} + +Canvas::~Canvas() { + if(canvas->image) delete canvas->image; + delete canvas; +} + +void Canvas::Data::paintEvent(QPaintEvent *event) { + QPainter painter(this); + painter.drawImage(0, 0, *image); +} diff --git a/phoenix/qt/checkbox.cpp b/phoenix/qt/checkbox.cpp new file mode 100755 index 00000000..a0ab121b --- /dev/null +++ b/phoenix/qt/checkbox.cpp @@ -0,0 +1,21 @@ +void CheckBox::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + checkBox->setParent(parent.window->container); + checkBox->setGeometry(x, y, width, height); + checkBox->setText(QString::fromUtf8(text)); + if(parent.window->defaultFont) checkBox->setFont(*parent.window->defaultFont); + checkBox->show(); + checkBox->connect(checkBox, SIGNAL(stateChanged(int)), SLOT(onTick())); +} + +bool CheckBox::checked() { + return checkBox->isChecked(); +} + +void CheckBox::setChecked(bool checked) { + checkBox->setChecked(checked); +} + +CheckBox::CheckBox() { + checkBox = new CheckBox::Data(*this); + widget->widget = checkBox; +} diff --git a/phoenix/qt/combobox.cpp b/phoenix/qt/combobox.cpp new file mode 100755 index 00000000..41bd7d8f --- /dev/null +++ b/phoenix/qt/combobox.cpp @@ -0,0 +1,38 @@ +void ComboBox::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + comboBox->setParent(parent.window->container); + comboBox->setGeometry(x, y, width, height); + + if(*text) { + lstring list; + list.split("\n", text); + foreach(item, list) addItem(item); + } + + comboBox->connect(comboBox, SIGNAL(currentIndexChanged(int)), SLOT(onChange())); + if(parent.window->defaultFont) comboBox->setFont(*parent.window->defaultFont); + comboBox->show(); +} + +void ComboBox::reset() { + while(comboBox->count()) comboBox->removeItem(0); +} + +void ComboBox::addItem(const string &text) { + comboBox->addItem(QString::fromUtf8(text)); +} + +unsigned ComboBox::selection() { + signed index = comboBox->currentIndex(); + return (index >= 0 ? index : 0); +} + +void ComboBox::setSelection(unsigned row) { + object->locked = true; + comboBox->setCurrentIndex(row); + object->locked = false; +} + +ComboBox::ComboBox() { + comboBox = new ComboBox::Data(*this); + widget->widget = comboBox; +} diff --git a/phoenix/qt/editbox.cpp b/phoenix/qt/editbox.cpp new file mode 100755 index 00000000..7f7b744c --- /dev/null +++ b/phoenix/qt/editbox.cpp @@ -0,0 +1,36 @@ +void EditBox::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + editBox->setParent(parent.window->container); + editBox->setGeometry(x, y, width, height); + editBox->setPlainText(QString::fromUtf8(text)); + if(parent.window->defaultFont) editBox->setFont(*parent.window->defaultFont); + editBox->show(); + editBox->connect(editBox, SIGNAL(textChanged()), SLOT(onChange())); +} + +void EditBox::setEditable(bool editable) { + editBox->setReadOnly(editable == false); +} + +void EditBox::setWordWrap(bool wordWrap) { + editBox->setWordWrapMode(wordWrap ? QTextOption::WordWrap : QTextOption::NoWrap); +} + +string EditBox::text() { + return editBox->toPlainText().toUtf8().constData(); +} + +void EditBox::setText(const string &text) { + editBox->setPlainText(QString::fromUtf8(text)); +} + +void EditBox::setCursorPosition(unsigned position) { + QTextCursor cursor = editBox->textCursor(); + unsigned lastchar = strlen(editBox->toPlainText().toUtf8().constData()); + cursor.setPosition(min(position, lastchar)); + editBox->setTextCursor(cursor); +} + +EditBox::EditBox() { + editBox = new EditBox::Data(*this); + widget->widget = editBox; +} diff --git a/phoenix/qt/font.cpp b/phoenix/qt/font.cpp new file mode 100755 index 00000000..01d6bab6 --- /dev/null +++ b/phoenix/qt/font.cpp @@ -0,0 +1,14 @@ +bool Font::create(const string &name, unsigned size, Font::Style style) { + font->setFamily(QString::fromUtf8(name)); + font->setPointSize(size); + font->setBold((style & Style::Bold) == Style::Bold); + font->setItalic((style & Style::Italic) == Style::Italic); +} + +Font::Font() { + font = new Font::Data(*this); +} + +Font::~Font() { + delete font; +} diff --git a/phoenix/qt/horizontalslider.cpp b/phoenix/qt/horizontalslider.cpp new file mode 100755 index 00000000..48f9e18c --- /dev/null +++ b/phoenix/qt/horizontalslider.cpp @@ -0,0 +1,22 @@ +void HorizontalSlider::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, unsigned length) { + length += (length == 0); + horizontalSlider->setParent(parent.window->container); + horizontalSlider->setGeometry(x, y, width, height); + horizontalSlider->setRange(0, length - 1); + horizontalSlider->setPageStep(length >> 3); + horizontalSlider->connect(horizontalSlider, SIGNAL(valueChanged(int)), SLOT(onChange())); + horizontalSlider->show(); +} + +unsigned HorizontalSlider::position() { + return horizontalSlider->value(); +} + +void HorizontalSlider::setPosition(unsigned position) { + horizontalSlider->setValue(position); +} + +HorizontalSlider::HorizontalSlider() { + horizontalSlider = new HorizontalSlider::Data(*this); + widget->widget = horizontalSlider; +} diff --git a/phoenix/qt/label.cpp b/phoenix/qt/label.cpp new file mode 100755 index 00000000..cee55452 --- /dev/null +++ b/phoenix/qt/label.cpp @@ -0,0 +1,16 @@ +void Label::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + label->setParent(parent.window->container); + label->setGeometry(x, y, width, height); + label->setText(QString::fromUtf8(text)); + if(parent.window->defaultFont) label->setFont(*parent.window->defaultFont); + label->show(); +} + +void Label::setText(const string &text) { + label->setText(QString::fromUtf8(text)); +} + +Label::Label() { + label = new Label::Data(*this); + widget->widget = label; +} diff --git a/phoenix/qt/listbox.cpp b/phoenix/qt/listbox.cpp new file mode 100755 index 00000000..ac5e97ee --- /dev/null +++ b/phoenix/qt/listbox.cpp @@ -0,0 +1,102 @@ +void ListBox::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + listBox->setParent(parent.window->container); + listBox->setGeometry(x, y, width, height); + listBox->setAllColumnsShowFocus(true); + listBox->setRootIsDecorated(false); + + lstring list; + list.split("\t", text); + QStringList labels; + foreach(item, list) labels << QString::fromUtf8(item); + listBox->setColumnCount(list.size()); + listBox->setHeaderLabels(labels); + for(unsigned i = 0; i < list.size(); i++) listBox->resizeColumnToContents(i); + + listBox->setHeaderHidden(true); + listBox->setAlternatingRowColors(list.size() >= 2); + listBox->connect(listBox, SIGNAL(itemActivated(QTreeWidgetItem*, int)), SLOT(onActivate())); + listBox->connect(listBox, SIGNAL(itemSelectionChanged()), SLOT(onChange())); + listBox->connect(listBox, SIGNAL(itemChanged(QTreeWidgetItem*, int)), SLOT(onTick(QTreeWidgetItem*))); + if(parent.window->defaultFont) listBox->setFont(*parent.window->defaultFont); + listBox->show(); +} + +void ListBox::setHeaderVisible(bool headerVisible) { + listBox->setHeaderHidden(headerVisible == false); +} + +void ListBox::setCheckable(bool checkable) { + listBox->checkable = checkable; + if(listBox->checkable) { + auto items = listBox->findItems("", Qt::MatchContains); + for(unsigned i = 0; i < items.size(); i++) items[i]->setCheckState(0, Qt::Unchecked); + } +} + +void ListBox::reset() { + listBox->clear(); +} + +void ListBox::resizeColumnsToContent() { + for(unsigned i = 0; i < listBox->columnCount(); i++) listBox->resizeColumnToContents(i); +} + +void ListBox::addItem(const string &text) { + object->locked = true; + auto items = listBox->findItems("", Qt::MatchContains); + QTreeWidgetItem *item = new QTreeWidgetItem(listBox); + if(listBox->checkable) item->setCheckState(0, Qt::Unchecked); + item->setData(0, Qt::UserRole, (unsigned)items.size()); + lstring list; + list.split("\t", text); + for(unsigned i = 0; i < list.size(); i++) item->setText(i, QString::fromUtf8(list[i])); + object->locked = false; +} + +void ListBox::setItem(unsigned row, const string &text) { + object->locked = true; + QTreeWidgetItem *item = listBox->topLevelItem(row); + lstring list; + list.split("\t", text); + for(unsigned i = 0; i < list.size(); i++) item->setText(i, QString::fromUtf8(list[i])); + object->locked = false; +} + +bool ListBox::checked(unsigned row) { + QTreeWidgetItem *item = listBox->topLevelItem(row); + return (item ? item->checkState(0) == Qt::Checked : false); +} + +void ListBox::setChecked(unsigned row, bool checked) { + object->locked = true; + QTreeWidgetItem *item = listBox->topLevelItem(row); + if(item) item->setCheckState(0, checked ? Qt::Checked : Qt::Unchecked); + object->locked = false; +} + +optional ListBox::selection() { + QTreeWidgetItem *item = listBox->currentItem(); + if(item == 0) return { false, 0 }; + if(item->isSelected() == false) return { false, 0 }; + unsigned row = item->data(0, Qt::UserRole).toUInt(); + return { true, row }; +} + +void ListBox::setSelection(unsigned row) { + object->locked = true; + QTreeWidgetItem *item = listBox->currentItem(); + if(item) item->setSelected(false); + auto items = listBox->findItems("", Qt::MatchContains); + for(unsigned i = 0; i < items.size(); i++) { + if(items[i]->data(0, Qt::UserRole).toUInt() == row) { + listBox->setCurrentItem(items[i]); + break; + } + } + object->locked = false; +} + +ListBox::ListBox() { + listBox = new ListBox::Data(*this); + widget->widget = listBox; +} diff --git a/phoenix/qt/menu.cpp b/phoenix/qt/menu.cpp new file mode 100755 index 00000000..8e6fa819 --- /dev/null +++ b/phoenix/qt/menu.cpp @@ -0,0 +1,169 @@ +void Menu::create(Window &parent, const string &text) { + menu->parent = &parent; + if(menu->parent->window->defaultFont) menu->setFont(*menu->parent->window->defaultFont); + menu->setTitle(QString::fromUtf8(text)); + parent.window->menuBar->addMenu(menu); +} + +void Menu::create(Menu &parent, const string &text) { + menu->parent = parent.menu->parent; + if(menu->parent->window->defaultFont) menu->setFont(*menu->parent->window->defaultFont); + menu->setTitle(QString::fromUtf8(text)); + parent.menu->addMenu(menu); +} + +bool Menu::visible() { + return menu->isVisible(); +} + +void Menu::setVisible(bool visible) { + menu->setVisible(visible); +} + +bool Menu::enabled() { + return menu->isEnabled(); +} + +void Menu::setEnabled(bool enabled) { + menu->setEnabled(enabled); +} + +Menu::Menu() { + menu = new Menu::Data(*this); +} + +void MenuSeparator::create(Menu &parent) { + menuSeparator->action = parent.menu->addSeparator(); +} + +bool MenuSeparator::visible() { + return menuSeparator->action->isVisible(); +} + +void MenuSeparator::setVisible(bool visible) { + menuSeparator->action->setVisible(visible); +} + +bool MenuSeparator::enabled() { + return menuSeparator->action->isEnabled(); +} + +void MenuSeparator::setEnabled(bool enabled) { + menuSeparator->action->setEnabled(enabled); +} + +MenuSeparator::MenuSeparator() { + menuSeparator = new MenuSeparator::Data(*this); +} + +void MenuItem::create(Menu &parent, const string &text) { + menuItem->setText(QString::fromUtf8(text)); + menuItem->connect(menuItem, SIGNAL(triggered()), SLOT(onTick())); + parent.menu->addAction(menuItem); +} + +bool MenuItem::visible() { + return menuItem->isVisible(); +} + +void MenuItem::setVisible(bool visible) { + menuItem->setVisible(visible); +} + +bool MenuItem::enabled() { + return menuItem->isEnabled(); +} + +void MenuItem::setEnabled(bool enabled) { + menuItem->setEnabled(enabled); +} + +MenuItem::MenuItem() { + menuItem = new MenuItem::Data(*this); +} + +void MenuCheckItem::create(Menu &parent, const string &text) { + menuCheckItem->setText(QString::fromUtf8(text)); + menuCheckItem->setCheckable(true); + menuCheckItem->connect(menuCheckItem, SIGNAL(triggered()), SLOT(onTick())); + parent.menu->addAction(menuCheckItem); +} + +bool MenuCheckItem::visible() { + return menuCheckItem->isVisible(); +} + +void MenuCheckItem::setVisible(bool visible) { + menuCheckItem->setVisible(visible); +} + +bool MenuCheckItem::enabled() { + return menuCheckItem->isEnabled(); +} + +void MenuCheckItem::setEnabled(bool enabled) { + menuCheckItem->setEnabled(enabled); +} + +bool MenuCheckItem::checked() { + return menuCheckItem->isChecked(); +} + +void MenuCheckItem::setChecked(bool checked) { + menuCheckItem->setChecked(checked); +} + +MenuCheckItem::MenuCheckItem() { + menuCheckItem = new MenuCheckItem::Data(*this); +} + +void MenuRadioItem::create(Menu &parent, const string &text) { + menuRadioItem->parent = &parent; + menuRadioItem->actionGroup = new QActionGroup(0); + menuRadioItem->actionGroup->addAction(menuRadioItem); + menuRadioItem->setText(QString::fromUtf8(text)); + menuRadioItem->setCheckable(true); + menuRadioItem->setChecked(true); + menuRadioItem->connect(menuRadioItem, SIGNAL(changed()), SLOT(onTick())); + menuRadioItem->parent->menu->addAction(menuRadioItem); +} + +void MenuRadioItem::create(MenuRadioItem &parent, const string &text) { + menuRadioItem->parent = parent.menuRadioItem->parent; + menuRadioItem->actionGroup = parent.menuRadioItem->actionGroup; + menuRadioItem->actionGroup->addAction(menuRadioItem); + menuRadioItem->setText(QString::fromUtf8(text)); + menuRadioItem->setCheckable(true); + menuRadioItem->connect(menuRadioItem, SIGNAL(changed()), SLOT(onTick())); + menuRadioItem->parent->menu->addAction(menuRadioItem); +} + +bool MenuRadioItem::visible() { + return menuRadioItem->isVisible(); +} + +void MenuRadioItem::setVisible(bool visible) { + menuRadioItem->setVisible(visible); +} + +bool MenuRadioItem::enabled() { + return menuRadioItem->isEnabled(); +} + +void MenuRadioItem::setEnabled(bool enabled) { + menuRadioItem->setEnabled(enabled); +} + +bool MenuRadioItem::checked() { + return menuRadioItem->isChecked(); +} + +void MenuRadioItem::setChecked() { + object->locked = true; + menuRadioItem->setChecked(true); + object->locked = false; +} + +MenuRadioItem::MenuRadioItem() { + menuRadioItem = new MenuRadioItem::Data(*this); +} diff --git a/phoenix/qt/messagewindow.cpp b/phoenix/qt/messagewindow.cpp new file mode 100755 index 00000000..5d60ea01 --- /dev/null +++ b/phoenix/qt/messagewindow.cpp @@ -0,0 +1,45 @@ +static QMessageBox::StandardButtons MessageWindow_buttons(MessageWindow::Buttons buttons) { + QMessageBox::StandardButtons standardButtons = QMessageBox::NoButton; + if(buttons == MessageWindow::Buttons::Ok) standardButtons = QMessageBox::Ok; + if(buttons == MessageWindow::Buttons::OkCancel) standardButtons = QMessageBox::Ok | QMessageBox::Cancel; + if(buttons == MessageWindow::Buttons::YesNo) standardButtons = QMessageBox::Yes | QMessageBox::No; + return standardButtons; +} + +static MessageWindow::Response MessageWindow_response(MessageWindow::Buttons buttons, QMessageBox::StandardButton response) { + if(response == QMessageBox::Ok) return MessageWindow::Response::Ok; + if(response == QMessageBox::Cancel) return MessageWindow::Response::Cancel; + if(response == QMessageBox::Yes) return MessageWindow::Response::Yes; + if(response == QMessageBox::No) return MessageWindow::Response::No; + if(buttons == MessageWindow::Buttons::OkCancel) return MessageWindow::Response::Cancel; + if(buttons == MessageWindow::Buttons::YesNo) return MessageWindow::Response::No; + return MessageWindow::Response::Ok; +} + +MessageWindow::Response MessageWindow::information(Window &parent, const string &text, MessageWindow::Buttons buttons) { + return MessageWindow_response( + buttons, QMessageBox::information(&parent != &Window::None ? parent.window : 0, " ", + QString::fromUtf8(text), MessageWindow_buttons(buttons)) + ); +} + +MessageWindow::Response MessageWindow::question(Window &parent, const string &text, MessageWindow::Buttons buttons) { + return MessageWindow_response( + buttons, QMessageBox::question(&parent != &Window::None ? parent.window : 0, " ", + QString::fromUtf8(text), MessageWindow_buttons(buttons)) + ); +} + +MessageWindow::Response MessageWindow::warning(Window &parent, const string &text, MessageWindow::Buttons buttons) { + return MessageWindow_response( + buttons, QMessageBox::warning(&parent != &Window::None ? parent.window : 0, " ", + QString::fromUtf8(text), MessageWindow_buttons(buttons)) + ); +} + +MessageWindow::Response MessageWindow::critical(Window &parent, const string &text, MessageWindow::Buttons buttons) { + return MessageWindow_response( + buttons, QMessageBox::critical(&parent != &Window::None ? parent.window : 0, " ", + QString::fromUtf8(text), MessageWindow_buttons(buttons)) + ); +} diff --git a/phoenix/qt/object.cpp b/phoenix/qt/object.cpp new file mode 100755 index 00000000..ae6cc6d9 --- /dev/null +++ b/phoenix/qt/object.cpp @@ -0,0 +1,7 @@ +void Object::unused() { +} + +Object::Object() { + OS::initialize(); + object = new Object::Data(*this); +} diff --git a/phoenix/qt/progressbar.cpp b/phoenix/qt/progressbar.cpp new file mode 100755 index 00000000..ab21f882 --- /dev/null +++ b/phoenix/qt/progressbar.cpp @@ -0,0 +1,16 @@ +void ProgressBar::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height) { + progressBar->setParent(parent.window->container); + progressBar->setGeometry(x, y, width, height); + progressBar->setRange(0, 100); + progressBar->setTextVisible(false); + progressBar->show(); +} + +void ProgressBar::setPosition(unsigned position) { + progressBar->setValue(position); +} + +ProgressBar::ProgressBar() { + progressBar = new ProgressBar::Data(*this); + widget->widget = progressBar; +} diff --git a/phoenix/qt/qt.cpp b/phoenix/qt/qt.cpp new file mode 100755 index 00000000..203b4b6f --- /dev/null +++ b/phoenix/qt/qt.cpp @@ -0,0 +1,128 @@ +#include +#include +using namespace nall; + +namespace phoenix { + +#include "qt.moc.hpp" +#include "qt.moc" + +#include "object.cpp" +#include "font.cpp" +#include "menu.cpp" +#include "widget.cpp" +#include "window.cpp" +#include "button.cpp" +#include "canvas.cpp" +#include "checkbox.cpp" +#include "combobox.cpp" +#include "editbox.cpp" +#include "horizontalslider.cpp" +#include "label.cpp" +#include "listbox.cpp" +#include "progressbar.cpp" +#include "radiobox.cpp" +#include "textbox.cpp" +#include "verticalslider.cpp" +#include "viewport.cpp" +#include "messagewindow.cpp" + +OS::Data *OS::os = 0; +Window Window::None; + +void OS::initialize() { + static bool initialized = false; + if(initialized == true) return; + initialized = true; + + os = new OS::Data; + static int argc = 1; + static char *argv[2]; + argv[0] = new char[8]; + argv[1] = 0; + strcpy(argv[0], "phoenix"); + char **argvp = argv; + os->application = new QApplication(argc, argvp); +} + +bool OS::pending() { + return QApplication::hasPendingEvents(); +} + +void OS::run() { + QApplication::processEvents(); +} + +void OS::main() { + QApplication::exec(); +} + +void OS::quit() { + QApplication::quit(); +} + +unsigned OS::desktopWidth() { + return QApplication::desktop()->screenGeometry().width(); +} + +unsigned OS::desktopHeight() { + return QApplication::desktop()->screenGeometry().height(); +} + +string OS::folderSelect(Window &parent, const string &path) { + QString directory = QFileDialog::getExistingDirectory( + &parent != &Window::None ? parent.window : 0, "Select Directory", + QString::fromUtf8(path), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks + ); + string name = directory.toUtf8().constData(); + if(name.endswith("/") == false) name.append("/"); + return name; +} + +string OS::fileOpen(Window &parent, const string &filter, const string &path) { + string filters; + lstring list; + list.split("\n", filter); + foreach(item, list) { + lstring part; + part.split("\t", item); + if(part.size() != 2) continue; + part[1].replace(",", " "); + filters.append(part[0]); + filters.append(" ("); + filters.append(part[1]); + filters.append(");;"); + } + filters.rtrim(";;"); + + QString filename = QFileDialog::getOpenFileName( + &parent != &Window::None ? parent.window : 0, "Open File", + QString::fromUtf8(path), QString::fromUtf8(filters) + ); + return filename.toUtf8().constData(); +} + +string OS::fileSave(Window &parent, const string &filter, const string &path) { + string filters; + lstring list; + list.split("\n", filter); + foreach(item, list) { + lstring part; + part.split("\t", item); + if(part.size() != 2) continue; + part[1].replace(",", " "); + filters.append(part[0]); + filters.append(" ("); + filters.append(part[1]); + filters.append(");;"); + } + filters.rtrim(";;"); + + QString filename = QFileDialog::getSaveFileName( + &parent != &Window::None ? parent.window : 0, "Save File", + QString::fromUtf8(path), QString::fromUtf8(filters) + ); + return filename.toUtf8().constData(); +} + +} diff --git a/phoenix/qt/qt.hpp b/phoenix/qt/qt.hpp new file mode 100755 index 00000000..9c6abdcc --- /dev/null +++ b/phoenix/qt/qt.hpp @@ -0,0 +1,338 @@ +namespace phoenix { + +struct Window; + +struct Object { + Object(); + Object& operator=(const Object&) = delete; + Object(const Object&) = delete; +//private: + virtual void unused(); + struct Data; + Data *object; +}; + +struct Geometry { + unsigned x, y; + unsigned width, height; + inline Geometry() : x(0), y(0), width(0), height(0) {} + inline Geometry(unsigned x, unsigned y, unsigned width, unsigned height) : x(x), y(y), width(width), height(height) {} +}; + +struct Font : Object { + enum class Style : unsigned { + None = 0, + Bold = 1, + Italic = 2, + }; + bool create(const nall::string &name, unsigned size, Font::Style style = Style::None); + Font(); + ~Font(); +//private: + struct Data; + Data *font; +}; + +inline Font::Style operator|(Font::Style a, Font::Style b) { return (Font::Style)((unsigned)a | (unsigned)b); } +inline Font::Style operator&(Font::Style a, Font::Style b) { return (Font::Style)((unsigned)a & (unsigned)b); } + +struct Action : Object { + virtual bool visible() = 0; + virtual void setVisible(bool visible = true) = 0; + virtual bool enabled() = 0; + virtual void setEnabled(bool enabled = true) = 0; +}; + +struct Menu : Action { + void create(Window &parent, const nall::string &text); + void create(Menu &parent, const nall::string &text); + bool visible(); + void setVisible(bool visible = true); + bool enabled(); + void setEnabled(bool enabled = true); + Menu(); +//private: + struct Data; + Data *menu; +}; + +struct MenuSeparator : Action { + void create(Menu &parent); + bool visible(); + void setVisible(bool visible = true); + bool enabled(); + void setEnabled(bool enabled = true); + MenuSeparator(); +//private: + struct Data; + Data *menuSeparator; +}; + +struct MenuItem : Action { + nall::function onTick; + void create(Menu &parent, const nall::string &text); + bool visible(); + void setVisible(bool visible = true); + bool enabled(); + void setEnabled(bool enabled = true); + MenuItem(); +//private: + struct Data; + Data *menuItem; +}; + +struct MenuCheckItem : Action { + nall::function onTick; + void create(Menu &parent, const nall::string &text); + bool visible(); + void setVisible(bool visible = true); + bool enabled(); + void setEnabled(bool enabled = true); + bool checked(); + void setChecked(bool checked = true); + MenuCheckItem(); +//private: + struct Data; + Data *menuCheckItem; +}; + +struct MenuRadioItem : Action { + nall::function onTick; + void create(Menu &parent, const nall::string &text); + void create(MenuRadioItem &parent, const nall::string &text); + bool visible(); + void setVisible(bool visible = true); + bool enabled(); + void setEnabled(bool enabled = true); + bool checked(); + void setChecked(); + MenuRadioItem(); +//private: + struct Data; + Data *menuRadioItem; +}; + +struct Widget : Object { + virtual void setGeometry(unsigned x, unsigned y, unsigned width, unsigned height); + virtual void setFont(Font &font); + bool visible(); + void setVisible(bool visible = true); + bool enabled(); + void setEnabled(bool enabled = true); + virtual bool focused(); + virtual void setFocused(); + Widget(); +//private: + struct Data; + Data *widget; +}; + +struct Window : Widget { + nall::function onClose; + void create(unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + Geometry geometry(); + void setGeometry(unsigned x, unsigned y, unsigned width, unsigned height); + void setDefaultFont(Font &font); + void setFont(Font &font); + void setBackgroundColor(uint8_t red, uint8_t green, uint8_t blue); + void setTitle(const nall::string &text); + void setStatusText(const nall::string &text); + void setMenuVisible(bool visible = true); + void setStatusVisible(bool visible = true); + bool focused(); + Window(); +//private: + struct Data; + Data *window; + static Window None; +}; + +struct Button : Widget { + nall::function onTick; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + Button(); +//private: + struct Data; + Data *button; +}; + +struct Canvas : Widget { + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height); + void setGeometry(unsigned x, unsigned y, unsigned width, unsigned height); + uint32_t* buffer(); + void redraw(); + Canvas(); + ~Canvas(); +//private: + struct Data; + Data *canvas; +}; + +struct CheckBox : Widget { + nall::function onTick; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + bool checked(); + void setChecked(bool checked = true); + CheckBox(); +//private: + struct Data; + Data *checkBox; +}; + +struct ComboBox : Widget { + nall::function onChange; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + void reset(); + void addItem(const nall::string &text); + unsigned selection(); + void setSelection(unsigned row); + ComboBox(); +//private: + struct Data; + Data *comboBox; +}; + +struct EditBox : Widget { + nall::function onChange; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + void setEditable(bool editable = true); + void setWordWrap(bool wordWrap = true); + nall::string text(); + void setText(const nall::string &text); + void setCursorPosition(unsigned position); + EditBox(); +//private: + struct Data; + Data *editBox; +}; + +struct HorizontalSlider : Widget { + nall::function onChange; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, unsigned length); + unsigned position(); + void setPosition(unsigned position); + HorizontalSlider(); +//private: + struct Data; + Data *horizontalSlider; +}; + +struct Label : Widget { + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + void setText(const nall::string &text); + Label(); +//private: + struct Data; + Data *label; +}; + +struct ListBox : Widget { + nall::function onActivate; + nall::function onChange; + nall::function onTick; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + void setHeaderVisible(bool headerVisible = true); + void setCheckable(bool checkable = true); + void reset(); + void resizeColumnsToContent(); + void addItem(const nall::string &text); + void setItem(unsigned row, const nall::string &text); + bool checked(unsigned row); + void setChecked(unsigned row, bool checked = true); + nall::optional selection(); + void setSelection(unsigned row); + ListBox(); +//private: + struct Data; + Data *listBox; +}; + +struct ProgressBar : Widget { + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height); + void setPosition(unsigned position); + ProgressBar(); +//private: + struct Data; + Data *progressBar; +}; + +struct RadioBox : Widget { + nall::function onTick; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + void create(RadioBox &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + bool checked(); + void setChecked(); + RadioBox(); +//private: + struct Data; + Data *radioBox; +}; + +struct TextBox : Widget { + nall::function onActivate; + nall::function onChange; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + void setEditable(bool editable = true); + nall::string text(); + void setText(const nall::string &text); + TextBox(); +//private: + struct Data; + Data *textBox; +}; + +struct VerticalSlider : Widget { + nall::function onChange; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, unsigned length); + unsigned position(); + void setPosition(unsigned position); + VerticalSlider(); +//private: + struct Data; + Data *verticalSlider; +}; + +struct Viewport : Widget { + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height); + uintptr_t handle(); + Viewport(); +//private: + struct Data; + Data *viewport; +}; + +struct MessageWindow : Object { + enum class Buttons : unsigned { + Ok, + OkCancel, + YesNo, + }; + enum class Response : unsigned { + Ok, + Cancel, + Yes, + No, + }; + static Response information(Window &parent, const nall::string &text, Buttons = Buttons::Ok); + static Response question(Window &parent, const nall::string &text, Buttons = Buttons::YesNo); + static Response warning(Window &parent, const nall::string &text, Buttons = Buttons::Ok); + static Response critical(Window &parent, const nall::string &text, Buttons = Buttons::Ok); +}; + +struct OS : Object { + static bool pending(); + static void run(); + static void main(); + static void quit(); + static unsigned desktopWidth(); + static unsigned desktopHeight(); + static nall::string folderSelect(Window &parent, const nall::string &path = ""); + static nall::string fileOpen(Window &parent, const nall::string &filter, const nall::string &path = ""); + static nall::string fileSave(Window &parent, const nall::string &filter, const nall::string &path = ""); +//private: + struct Data; + static Data *os; + static void initialize(); +}; + +} diff --git a/phoenix/qt/qt.moc b/phoenix/qt/qt.moc new file mode 100755 index 00000000..0d6e7f71 --- /dev/null +++ b/phoenix/qt/qt.moc @@ -0,0 +1,930 @@ +/**************************************************************************** +** Meta object code from reading C++ file 'qt.moc.hpp' +** +** Created: Mon Nov 1 06:26:59 2010 +** by: The Qt Meta Object Compiler version 62 (Qt 4.6.2) +** +** WARNING! All changes made in this file will be lost! +*****************************************************************************/ + +#if !defined(Q_MOC_OUTPUT_REVISION) +#error "The header file 'qt.moc.hpp' doesn't include ." +#elif Q_MOC_OUTPUT_REVISION != 62 +#error "This file was generated using the moc from 4.6.2. It" +#error "cannot be used with the include files from this version of Qt." +#error "(The moc has changed too much.)" +#endif + +QT_BEGIN_MOC_NAMESPACE +static const uint qt_meta_data_MenuItem__Data[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 1, 14, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + // slots: signature, parameters, type, tag, flags + 16, 15, 15, 15, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_MenuItem__Data[] = { + "MenuItem::Data\0\0onTick()\0" +}; + +const QMetaObject MenuItem::Data::staticMetaObject = { + { &QAction::staticMetaObject, qt_meta_stringdata_MenuItem__Data, + qt_meta_data_MenuItem__Data, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &MenuItem::Data::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *MenuItem::Data::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *MenuItem::Data::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_MenuItem__Data)) + return static_cast(const_cast< Data*>(this)); + return QAction::qt_metacast(_clname); +} + +int MenuItem::Data::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QAction::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onTick(); break; + default: ; + } + _id -= 1; + } + return _id; +} +static const uint qt_meta_data_MenuCheckItem__Data[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 1, 14, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + // slots: signature, parameters, type, tag, flags + 21, 20, 20, 20, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_MenuCheckItem__Data[] = { + "MenuCheckItem::Data\0\0onTick()\0" +}; + +const QMetaObject MenuCheckItem::Data::staticMetaObject = { + { &QAction::staticMetaObject, qt_meta_stringdata_MenuCheckItem__Data, + qt_meta_data_MenuCheckItem__Data, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &MenuCheckItem::Data::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *MenuCheckItem::Data::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *MenuCheckItem::Data::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_MenuCheckItem__Data)) + return static_cast(const_cast< Data*>(this)); + return QAction::qt_metacast(_clname); +} + +int MenuCheckItem::Data::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QAction::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onTick(); break; + default: ; + } + _id -= 1; + } + return _id; +} +static const uint qt_meta_data_MenuRadioItem__Data[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 1, 14, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + // slots: signature, parameters, type, tag, flags + 21, 20, 20, 20, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_MenuRadioItem__Data[] = { + "MenuRadioItem::Data\0\0onTick()\0" +}; + +const QMetaObject MenuRadioItem::Data::staticMetaObject = { + { &QAction::staticMetaObject, qt_meta_stringdata_MenuRadioItem__Data, + qt_meta_data_MenuRadioItem__Data, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &MenuRadioItem::Data::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *MenuRadioItem::Data::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *MenuRadioItem::Data::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_MenuRadioItem__Data)) + return static_cast(const_cast< Data*>(this)); + return QAction::qt_metacast(_clname); +} + +int MenuRadioItem::Data::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QAction::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onTick(); break; + default: ; + } + _id -= 1; + } + return _id; +} +static const uint qt_meta_data_Window__Data[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 0, 0, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + 0 // eod +}; + +static const char qt_meta_stringdata_Window__Data[] = { + "Window::Data\0" +}; + +const QMetaObject Window::Data::staticMetaObject = { + { &QWidget::staticMetaObject, qt_meta_stringdata_Window__Data, + qt_meta_data_Window__Data, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &Window::Data::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *Window::Data::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *Window::Data::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_Window__Data)) + return static_cast(const_cast< Data*>(this)); + return QWidget::qt_metacast(_clname); +} + +int Window::Data::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QWidget::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + return _id; +} +static const uint qt_meta_data_Button__Data[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 1, 14, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + // slots: signature, parameters, type, tag, flags + 14, 13, 13, 13, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_Button__Data[] = { + "Button::Data\0\0onTick()\0" +}; + +const QMetaObject Button::Data::staticMetaObject = { + { &QPushButton::staticMetaObject, qt_meta_stringdata_Button__Data, + qt_meta_data_Button__Data, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &Button::Data::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *Button::Data::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *Button::Data::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_Button__Data)) + return static_cast(const_cast< Data*>(this)); + return QPushButton::qt_metacast(_clname); +} + +int Button::Data::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QPushButton::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onTick(); break; + default: ; + } + _id -= 1; + } + return _id; +} +static const uint qt_meta_data_Canvas__Data[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 0, 0, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + 0 // eod +}; + +static const char qt_meta_stringdata_Canvas__Data[] = { + "Canvas::Data\0" +}; + +const QMetaObject Canvas::Data::staticMetaObject = { + { &QWidget::staticMetaObject, qt_meta_stringdata_Canvas__Data, + qt_meta_data_Canvas__Data, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &Canvas::Data::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *Canvas::Data::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *Canvas::Data::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_Canvas__Data)) + return static_cast(const_cast< Data*>(this)); + return QWidget::qt_metacast(_clname); +} + +int Canvas::Data::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QWidget::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + return _id; +} +static const uint qt_meta_data_CheckBox__Data[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 1, 14, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + // slots: signature, parameters, type, tag, flags + 16, 15, 15, 15, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_CheckBox__Data[] = { + "CheckBox::Data\0\0onTick()\0" +}; + +const QMetaObject CheckBox::Data::staticMetaObject = { + { &QCheckBox::staticMetaObject, qt_meta_stringdata_CheckBox__Data, + qt_meta_data_CheckBox__Data, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &CheckBox::Data::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *CheckBox::Data::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *CheckBox::Data::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_CheckBox__Data)) + return static_cast(const_cast< Data*>(this)); + return QCheckBox::qt_metacast(_clname); +} + +int CheckBox::Data::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QCheckBox::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onTick(); break; + default: ; + } + _id -= 1; + } + return _id; +} +static const uint qt_meta_data_ComboBox__Data[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 1, 14, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + // slots: signature, parameters, type, tag, flags + 16, 15, 15, 15, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_ComboBox__Data[] = { + "ComboBox::Data\0\0onChange()\0" +}; + +const QMetaObject ComboBox::Data::staticMetaObject = { + { &QComboBox::staticMetaObject, qt_meta_stringdata_ComboBox__Data, + qt_meta_data_ComboBox__Data, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &ComboBox::Data::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *ComboBox::Data::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *ComboBox::Data::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_ComboBox__Data)) + return static_cast(const_cast< Data*>(this)); + return QComboBox::qt_metacast(_clname); +} + +int ComboBox::Data::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QComboBox::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onChange(); break; + default: ; + } + _id -= 1; + } + return _id; +} +static const uint qt_meta_data_EditBox__Data[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 1, 14, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + // slots: signature, parameters, type, tag, flags + 15, 14, 14, 14, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_EditBox__Data[] = { + "EditBox::Data\0\0onChange()\0" +}; + +const QMetaObject EditBox::Data::staticMetaObject = { + { &QTextEdit::staticMetaObject, qt_meta_stringdata_EditBox__Data, + qt_meta_data_EditBox__Data, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &EditBox::Data::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *EditBox::Data::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *EditBox::Data::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_EditBox__Data)) + return static_cast(const_cast< Data*>(this)); + return QTextEdit::qt_metacast(_clname); +} + +int EditBox::Data::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QTextEdit::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onChange(); break; + default: ; + } + _id -= 1; + } + return _id; +} +static const uint qt_meta_data_HorizontalSlider__Data[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 1, 14, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + // slots: signature, parameters, type, tag, flags + 24, 23, 23, 23, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_HorizontalSlider__Data[] = { + "HorizontalSlider::Data\0\0onChange()\0" +}; + +const QMetaObject HorizontalSlider::Data::staticMetaObject = { + { &QSlider::staticMetaObject, qt_meta_stringdata_HorizontalSlider__Data, + qt_meta_data_HorizontalSlider__Data, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &HorizontalSlider::Data::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *HorizontalSlider::Data::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *HorizontalSlider::Data::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_HorizontalSlider__Data)) + return static_cast(const_cast< Data*>(this)); + return QSlider::qt_metacast(_clname); +} + +int HorizontalSlider::Data::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QSlider::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onChange(); break; + default: ; + } + _id -= 1; + } + return _id; +} +static const uint qt_meta_data_Label__Data[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 0, 0, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + 0 // eod +}; + +static const char qt_meta_stringdata_Label__Data[] = { + "Label::Data\0" +}; + +const QMetaObject Label::Data::staticMetaObject = { + { &QLabel::staticMetaObject, qt_meta_stringdata_Label__Data, + qt_meta_data_Label__Data, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &Label::Data::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *Label::Data::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *Label::Data::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_Label__Data)) + return static_cast(const_cast< Data*>(this)); + return QLabel::qt_metacast(_clname); +} + +int Label::Data::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QLabel::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + return _id; +} +static const uint qt_meta_data_ListBox__Data[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 3, 14, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + // slots: signature, parameters, type, tag, flags + 15, 14, 14, 14, 0x0a, + 28, 14, 14, 14, 0x0a, + 44, 39, 14, 14, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_ListBox__Data[] = { + "ListBox::Data\0\0onActivate()\0onChange()\0" + "item\0onTick(QTreeWidgetItem*)\0" +}; + +const QMetaObject ListBox::Data::staticMetaObject = { + { &QTreeWidget::staticMetaObject, qt_meta_stringdata_ListBox__Data, + qt_meta_data_ListBox__Data, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &ListBox::Data::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *ListBox::Data::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *ListBox::Data::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_ListBox__Data)) + return static_cast(const_cast< Data*>(this)); + return QTreeWidget::qt_metacast(_clname); +} + +int ListBox::Data::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QTreeWidget::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onActivate(); break; + case 1: onChange(); break; + case 2: onTick((*reinterpret_cast< QTreeWidgetItem*(*)>(_a[1]))); break; + default: ; + } + _id -= 3; + } + return _id; +} +static const uint qt_meta_data_RadioBox__Data[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 1, 14, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + // slots: signature, parameters, type, tag, flags + 16, 15, 15, 15, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_RadioBox__Data[] = { + "RadioBox::Data\0\0onTick()\0" +}; + +const QMetaObject RadioBox::Data::staticMetaObject = { + { &QRadioButton::staticMetaObject, qt_meta_stringdata_RadioBox__Data, + qt_meta_data_RadioBox__Data, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &RadioBox::Data::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *RadioBox::Data::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *RadioBox::Data::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_RadioBox__Data)) + return static_cast(const_cast< Data*>(this)); + return QRadioButton::qt_metacast(_clname); +} + +int RadioBox::Data::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QRadioButton::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onTick(); break; + default: ; + } + _id -= 1; + } + return _id; +} +static const uint qt_meta_data_TextBox__Data[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 2, 14, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + // slots: signature, parameters, type, tag, flags + 15, 14, 14, 14, 0x0a, + 28, 14, 14, 14, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_TextBox__Data[] = { + "TextBox::Data\0\0onActivate()\0onChange()\0" +}; + +const QMetaObject TextBox::Data::staticMetaObject = { + { &QLineEdit::staticMetaObject, qt_meta_stringdata_TextBox__Data, + qt_meta_data_TextBox__Data, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &TextBox::Data::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *TextBox::Data::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *TextBox::Data::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_TextBox__Data)) + return static_cast(const_cast< Data*>(this)); + return QLineEdit::qt_metacast(_clname); +} + +int TextBox::Data::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QLineEdit::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onActivate(); break; + case 1: onChange(); break; + default: ; + } + _id -= 2; + } + return _id; +} +static const uint qt_meta_data_VerticalSlider__Data[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 1, 14, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + // slots: signature, parameters, type, tag, flags + 22, 21, 21, 21, 0x0a, + + 0 // eod +}; + +static const char qt_meta_stringdata_VerticalSlider__Data[] = { + "VerticalSlider::Data\0\0onChange()\0" +}; + +const QMetaObject VerticalSlider::Data::staticMetaObject = { + { &QSlider::staticMetaObject, qt_meta_stringdata_VerticalSlider__Data, + qt_meta_data_VerticalSlider__Data, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &VerticalSlider::Data::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *VerticalSlider::Data::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *VerticalSlider::Data::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_VerticalSlider__Data)) + return static_cast(const_cast< Data*>(this)); + return QSlider::qt_metacast(_clname); +} + +int VerticalSlider::Data::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QSlider::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: onChange(); break; + default: ; + } + _id -= 1; + } + return _id; +} +static const uint qt_meta_data_OS__Data[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 0, 0, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + 0 // eod +}; + +static const char qt_meta_stringdata_OS__Data[] = { + "OS::Data\0" +}; + +const QMetaObject OS::Data::staticMetaObject = { + { &QObject::staticMetaObject, qt_meta_stringdata_OS__Data, + qt_meta_data_OS__Data, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &OS::Data::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *OS::Data::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *OS::Data::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_OS__Data)) + return static_cast(const_cast< Data*>(this)); + return QObject::qt_metacast(_clname); +} + +int OS::Data::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QObject::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + return _id; +} +QT_END_MOC_NAMESPACE diff --git a/phoenix/qt/qt.moc.hpp b/phoenix/qt/qt.moc.hpp new file mode 100755 index 00000000..8beaed5e --- /dev/null +++ b/phoenix/qt/qt.moc.hpp @@ -0,0 +1,311 @@ +struct Object::Data { +public: + Object &self; + bool locked; + + Data(Object &self) : self(self) { + locked = false; + } +}; + +struct Font::Data : public QFont { +public: + Font &self; + + Data(Font &self) : self(self) { + } +}; + +struct Menu::Data : public QMenu { +public: + Menu &self; + Window *parent; + + Data(Menu &self) : self(self), parent(0) { + } +}; + +struct MenuSeparator::Data { +public: + MenuSeparator &self; + QAction *action; + + Data(MenuSeparator &self) : self(self) { + } +}; + +struct MenuItem::Data : public QAction { + Q_OBJECT + +public: + MenuItem &self; + + Data(MenuItem &self) : self(self), QAction(0) { + } + +public slots: + void onTick() { + if(self.onTick) self.onTick(); + } +}; + +struct MenuCheckItem::Data : public QAction { + Q_OBJECT + +public: + MenuCheckItem &self; + + Data(MenuCheckItem &self) : self(self), QAction(0) { + } + +public slots: + void onTick() { + if(self.onTick) self.onTick(); + } +}; + +struct MenuRadioItem::Data : public QAction { + Q_OBJECT + +public: + MenuRadioItem &self; + Menu *parent; + QActionGroup *actionGroup; + + Data(MenuRadioItem &self) : self(self), QAction(0) { + } + +public slots: + void onTick() { + if(self.object->locked == false && self.onTick && self.checked()) self.onTick(); + } +}; + +struct Widget::Data { +public: + Widget &self; + QWidget *widget; + + Data(Widget &self) : self(self) { + } +}; + +struct Window::Data : public QWidget { + Q_OBJECT + +public: + Window &self; + QFont *defaultFont; + QVBoxLayout *layout; + QMenuBar *menuBar; + QWidget *container; + QStatusBar *statusBar; + + void closeEvent(QCloseEvent *event) { + if(self.onClose) { + bool result = self.onClose(); + if(result == false) event->ignore(); + } + } + + Data(Window &self) : self(self) { + } +}; + +struct Button::Data : public QPushButton { + Q_OBJECT + +public: + Button &self; + + Data(Button &self) : self(self) { + } + +public slots: + void onTick() { + if(self.onTick) self.onTick(); + } +}; + +struct Canvas::Data : public QWidget { + Q_OBJECT + +public: + Canvas &self; + QImage *image; + void paintEvent(QPaintEvent*); + + Data(Canvas &self) : self(self) { + } +}; + +struct CheckBox::Data : public QCheckBox { + Q_OBJECT + +public: + CheckBox &self; + + Data(CheckBox &self) : self(self) { + } + +public slots: + void onTick() { + if(self.onTick) self.onTick(); + } +}; + +struct ComboBox::Data : public QComboBox { + Q_OBJECT + +public: + ComboBox &self; + + Data(ComboBox &self) : self(self) { + } + +public slots: + void onChange() { + if(self.object->locked == false && self.onChange) self.onChange(); + } +}; + +struct EditBox::Data : public QTextEdit { + Q_OBJECT + +public: + EditBox &self; + + Data(EditBox &self) : self(self) { + } + +public slots: + void onChange() { + if(self.onChange) self.onChange(); + } +}; + +struct HorizontalSlider::Data : public QSlider { + Q_OBJECT + +public: + HorizontalSlider &self; + + Data(HorizontalSlider &self) : self(self), QSlider(Qt::Horizontal) { + } + +public slots: + void onChange() { + if(self.onChange) self.onChange(); + } +}; + +struct Label::Data : public QLabel { + Q_OBJECT + +public: + Label &self; + + Data(Label &self) : self(self) { + } +}; + +struct ListBox::Data : public QTreeWidget { + Q_OBJECT + +public: + ListBox &self; + bool checkable; + + Data(ListBox &self) : self(self) { + checkable = false; + } + +public slots: + void onActivate() { + if(self.object->locked == false && self.onActivate) self.onActivate(); + } + + void onChange() { + if(self.object->locked == false && self.onChange) self.onChange(); + } + + void onTick(QTreeWidgetItem *item) { + if(self.object->locked == false && self.onTick) self.onTick(item->data(0, Qt::UserRole).toUInt()); + } +}; + +struct ProgressBar::Data : public QProgressBar { +public: + ProgressBar &self; + + Data(ProgressBar &self) : self(self) { + } +}; + +struct RadioBox::Data : public QRadioButton { + Q_OBJECT + +public: + RadioBox &self; + Window *parent; + QButtonGroup *buttonGroup; + + Data(RadioBox &self) : self(self) { + } + +public slots: + void onTick() { + if(self.onTick && self.checked()) self.onTick(); + } +}; + +struct TextBox::Data : public QLineEdit { + Q_OBJECT + +public: + TextBox &self; + + Data(TextBox &self) : self(self) { + } + +public slots: + void onActivate() { + if(self.onActivate) self.onActivate(); + } + + void onChange() { + if(self.onChange) self.onChange(); + } +}; + +struct VerticalSlider::Data : public QSlider { + Q_OBJECT + +public: + VerticalSlider &self; + + Data(VerticalSlider &self) : self(self), QSlider(Qt::Vertical) { + } + +public slots: + void onChange() { + if(self.onChange) self.onChange(); + } +}; + +struct Viewport::Data : public QWidget { +public: + Viewport &self; + + Data(Viewport &self) : self(self) { + } +}; + +struct OS::Data : public QObject { + Q_OBJECT + +public: + QApplication *application; + +public slots: +}; diff --git a/phoenix/qt/radiobox.cpp b/phoenix/qt/radiobox.cpp new file mode 100755 index 00000000..b6f3a846 --- /dev/null +++ b/phoenix/qt/radiobox.cpp @@ -0,0 +1,37 @@ +void RadioBox::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + radioBox->parent = &parent; + radioBox->buttonGroup = new QButtonGroup; + radioBox->buttonGroup->addButton(radioBox); + radioBox->setParent(radioBox->parent->window->container); + radioBox->setGeometry(x, y, width, height); + radioBox->setText(QString::fromUtf8(text)); + radioBox->setChecked(true); + if(parent.window->defaultFont) radioBox->setFont(*parent.window->defaultFont); + radioBox->show(); + radioBox->connect(radioBox, SIGNAL(toggled(bool)), SLOT(onTick())); +} + +void RadioBox::create(RadioBox &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + radioBox->parent = parent.radioBox->parent; + radioBox->buttonGroup = parent.radioBox->buttonGroup; + radioBox->buttonGroup->addButton(radioBox); + radioBox->setParent(radioBox->parent->window->container); + radioBox->setGeometry(x, y, width, height); + radioBox->setText(QString::fromUtf8(text)); + if(radioBox->parent->window->defaultFont) radioBox->setFont(*radioBox->parent->window->defaultFont); + radioBox->show(); + radioBox->connect(radioBox, SIGNAL(toggled(bool)), SLOT(onTick())); +} + +bool RadioBox::checked() { + return radioBox->isChecked(); +} + +void RadioBox::setChecked() { + radioBox->setChecked(true); +} + +RadioBox::RadioBox() { + radioBox = new RadioBox::Data(*this); + widget->widget = radioBox; +} diff --git a/phoenix/qt/textbox.cpp b/phoenix/qt/textbox.cpp new file mode 100755 index 00000000..ffe2c82f --- /dev/null +++ b/phoenix/qt/textbox.cpp @@ -0,0 +1,26 @@ +void TextBox::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + textBox->setParent(parent.window->container); + textBox->setGeometry(x, y, width, height); + textBox->setText(QString::fromUtf8(text)); + if(parent.window->defaultFont) textBox->setFont(*parent.window->defaultFont); + textBox->show(); + textBox->connect(textBox, SIGNAL(returnPressed()), SLOT(onActivate())); + textBox->connect(textBox, SIGNAL(textEdited(const QString&)), SLOT(onChange())); +} + +void TextBox::setEditable(bool editable) { + textBox->setReadOnly(editable == false); +} + +string TextBox::text() { + return textBox->text().toUtf8().constData(); +} + +void TextBox::setText(const string &text) { + textBox->setText(QString::fromUtf8(text)); +} + +TextBox::TextBox() { + textBox = new TextBox::Data(*this); + widget->widget = textBox; +} diff --git a/phoenix/qt/verticalslider.cpp b/phoenix/qt/verticalslider.cpp new file mode 100755 index 00000000..98d9f22a --- /dev/null +++ b/phoenix/qt/verticalslider.cpp @@ -0,0 +1,24 @@ +void VerticalSlider::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, unsigned length) { + length += (length == 0); + verticalSlider->setParent(parent.window->container); + verticalSlider->setGeometry(x, y, width, height); + verticalSlider->setInvertedAppearance(true); + verticalSlider->setInvertedControls(true); + verticalSlider->setRange(0, length - 1); + verticalSlider->setPageStep(length >> 3); + verticalSlider->connect(verticalSlider, SIGNAL(valueChanged(int)), SLOT(onChange())); + verticalSlider->show(); +} + +unsigned VerticalSlider::position() { + return verticalSlider->value(); +} + +void VerticalSlider::setPosition(unsigned position) { + verticalSlider->setValue(position); +} + +VerticalSlider::VerticalSlider() { + verticalSlider = new VerticalSlider::Data(*this); + widget->widget = verticalSlider; +} diff --git a/phoenix/qt/viewport.cpp b/phoenix/qt/viewport.cpp new file mode 100755 index 00000000..7c4b0f19 --- /dev/null +++ b/phoenix/qt/viewport.cpp @@ -0,0 +1,16 @@ +void Viewport::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height) { + viewport->setParent(parent.window->container); + viewport->setGeometry(x, y, width, height); + viewport->setAttribute(Qt::WA_PaintOnScreen, true); + viewport->setStyleSheet("background: #000000"); + viewport->show(); +} + +uintptr_t Viewport::handle() { + return (uintptr_t)viewport->winId(); +} + +Viewport::Viewport() { + viewport = new Viewport::Data(*this); + widget->widget = viewport; +} diff --git a/phoenix/qt/widget.cpp b/phoenix/qt/widget.cpp new file mode 100755 index 00000000..9510607a --- /dev/null +++ b/phoenix/qt/widget.cpp @@ -0,0 +1,35 @@ +void Widget::setGeometry(unsigned x, unsigned y, unsigned width, unsigned height) { + widget->widget->setGeometry(x, y, width, height); +} + +void Widget::setFont(Font &font) { + widget->widget->setFont(*font.font); +} + +bool Widget::visible() { + return widget->widget->isVisible(); +} + +void Widget::setVisible(bool visible) { + widget->widget->setVisible(visible); +} + +bool Widget::enabled() { + return widget->widget->isEnabled(); +} + +void Widget::setEnabled(bool enabled) { + widget->widget->setEnabled(enabled); +} + +bool Widget::focused() { + return widget->widget->hasFocus(); +} + +void Widget::setFocused() { + widget->widget->setFocus(Qt::OtherFocusReason); +} + +Widget::Widget() { + widget = new Widget::Data(*this); +} diff --git a/phoenix/qt/window.cpp b/phoenix/qt/window.cpp new file mode 100755 index 00000000..ef209ab0 --- /dev/null +++ b/phoenix/qt/window.cpp @@ -0,0 +1,77 @@ +void Window::create(unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + window->setWindowTitle(QString::fromUtf8(text)); + window->move(x, y); + + window->layout = new QVBoxLayout(window); + window->layout->setMargin(0); + window->layout->setSpacing(0); + window->layout->setSizeConstraint(QLayout::SetFixedSize); + window->setLayout(window->layout); + + window->menuBar = new QMenuBar(window); + window->menuBar->setVisible(false); + window->layout->addWidget(window->menuBar); + + window->container = new QWidget(window); + window->container->setFixedSize(width, height); + window->container->setVisible(true); + window->layout->addWidget(window->container); + + window->statusBar = new QStatusBar(window); + window->statusBar->setSizeGripEnabled(false); + window->statusBar->setVisible(false); + window->layout->addWidget(window->statusBar); +} + +Geometry Window::geometry() { + return Geometry(window->x(), window->y(), window->container->width(), window->container->height()); +} + +void Window::setGeometry(unsigned x, unsigned y, unsigned width, unsigned height) { + window->container->setFixedSize(width, height); + window->move(x, y); +} + +void Window::setDefaultFont(Font &font) { + window->defaultFont = font.font; + window->menuBar->setFont(*font.font); +} + +void Window::setFont(Font &font) { + window->statusBar->setFont(*font.font); +} + +void Window::setBackgroundColor(uint8_t red, uint8_t green, uint8_t blue) { + QPalette palette; + palette.setColor(QPalette::Window, QColor(red, green, blue)); + window->setPalette(palette); + window->setAutoFillBackground(true); +} + +void Window::setTitle(const string &text) { + window->setWindowTitle(QString::fromUtf8(text)); +} + +void Window::setStatusText(const string &text) { + window->statusBar->showMessage(QString::fromUtf8(text), 0); +} + +void Window::setMenuVisible(bool visible) { + if(visible) window->menuBar->show(); + else window->menuBar->hide(); +} + +void Window::setStatusVisible(bool visible) { + if(visible) window->statusBar->show(); + else window->statusBar->hide(); +} + +bool Window::focused() { + return window->isActiveWindow() && !window->isMinimized(); +} + +Window::Window() { + window = new Window::Data(*this); + window->defaultFont = 0; + widget->widget = window; +} diff --git a/phoenix/windows/button.cpp b/phoenix/windows/button.cpp new file mode 100755 index 00000000..267b446e --- /dev/null +++ b/phoenix/windows/button.cpp @@ -0,0 +1,10 @@ +void Button::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + widget->window = CreateWindow( + L"BUTTON", utf16_t(text), + WS_CHILD | WS_TABSTOP | WS_VISIBLE, + x, y, width, height, + parent.widget->window, (HMENU)object->id, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(widget->window, GWLP_USERDATA, (LONG_PTR)this); + SendMessage(widget->window, WM_SETFONT, (WPARAM)(parent.window->defaultFont ? parent.window->defaultFont : OS::os->proportionalFont), 0); +} diff --git a/phoenix/windows/canvas.cpp b/phoenix/windows/canvas.cpp new file mode 100755 index 00000000..783d7042 --- /dev/null +++ b/phoenix/windows/canvas.cpp @@ -0,0 +1,60 @@ +void Canvas::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height) { + canvas->buffer = new uint32_t[width * height](); + canvas->pitch = width * sizeof(uint32_t); + canvas->width = width; + canvas->height = height; + memset(&canvas->bmi, 0, sizeof(BITMAPINFO)); + canvas->bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + canvas->bmi.bmiHeader.biPlanes = 1; + canvas->bmi.bmiHeader.biBitCount = 32; + canvas->bmi.bmiHeader.biCompression = BI_RGB; + canvas->bmi.bmiHeader.biWidth = width; + canvas->bmi.bmiHeader.biHeight = -height; //GDI stores bitmaps upside down; negative height flips bitmap + canvas->bmi.bmiHeader.biSizeImage = canvas->pitch * canvas->height; + + widget->window = CreateWindow( + L"phoenix_canvas", L"", + WS_CHILD | WS_VISIBLE, + x, y, width, height, + parent.widget->window, (HMENU)object->id, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(widget->window, GWLP_USERDATA, (LONG_PTR)this); +} + +uint32_t* Canvas::buffer() { + return canvas->buffer; +} + +void Canvas::redraw() { + PAINTSTRUCT ps; + BeginPaint(widget->window, &ps); + SetDIBitsToDevice(ps.hdc, 0, 0, canvas->width, canvas->height, 0, 0, 0, canvas->height, (void*)canvas->buffer, &canvas->bmi, DIB_RGB_COLORS); + EndPaint(widget->window, &ps); + InvalidateRect(widget->window, 0, false); +} + +Canvas::Canvas() { + canvas = new Canvas::Data; + canvas->buffer = 0; +} + +Canvas::~Canvas() { + delete[] canvas->buffer; + delete canvas; +} + +static LRESULT CALLBACK Canvas_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + switch(msg) { + case WM_PAINT: { + Object *object_ptr = (Object*)GetWindowLongPtr(hwnd, GWLP_USERDATA); + if(object_ptr) { + if(dynamic_cast(object_ptr)) { + Canvas &canvas = (Canvas&)*object_ptr; + canvas.redraw(); + } + } + } + } + + return DefWindowProc(hwnd, msg, wparam, lparam); +} diff --git a/phoenix/windows/checkbox.cpp b/phoenix/windows/checkbox.cpp new file mode 100755 index 00000000..035deb56 --- /dev/null +++ b/phoenix/windows/checkbox.cpp @@ -0,0 +1,18 @@ +void CheckBox::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + widget->window = CreateWindow( + L"BUTTON", utf16_t(text), + WS_CHILD | WS_TABSTOP | WS_VISIBLE | BS_CHECKBOX, + x, y, width, height, + parent.widget->window, (HMENU)object->id, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(widget->window, GWLP_USERDATA, (LONG_PTR)this); + SendMessage(widget->window, WM_SETFONT, (WPARAM)(parent.window->defaultFont ? parent.window->defaultFont : OS::os->proportionalFont), 0); +} + +bool CheckBox::checked() { + return SendMessage(widget->window, BM_GETCHECK, 0, 0); +} + +void CheckBox::setChecked(bool checked) { + SendMessage(widget->window, BM_SETCHECK, (WPARAM)checked, 0); +} diff --git a/phoenix/windows/combobox.cpp b/phoenix/windows/combobox.cpp new file mode 100755 index 00000000..4965a529 --- /dev/null +++ b/phoenix/windows/combobox.cpp @@ -0,0 +1,46 @@ +void ComboBox::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + widget->window = CreateWindowEx( + 0, L"COMBOBOX", L"", + WS_CHILD | WS_TABSTOP | WS_VISIBLE | CBS_DROPDOWNLIST | CBS_HASSTRINGS, + x, y, width, 200, + parent.widget->window, (HMENU)object->id, GetModuleHandle(0), 0 + ); + + SetWindowLongPtr(widget->window, GWLP_USERDATA, (LONG_PTR)this); + SendMessage(widget->window, WM_SETFONT, (WPARAM)(parent.window->defaultFont ? parent.window->defaultFont : OS::os->proportionalFont), 0); + + //CreateWindow height parameter is the height of the expanded list box; + //need additional code to override default ComboBox control height + RECT rc; + GetWindowRect(widget->window, &rc); + unsigned adjustedHeight = height - ((rc.bottom - rc.top) - SendMessage(widget->window, CB_GETITEMHEIGHT, (WPARAM)-1, 0)); + SendMessage(widget->window, CB_SETITEMHEIGHT, (WPARAM)-1, adjustedHeight); + + if(*text) { + lstring list; + list.split("\n", text); + foreach(item, list) addItem(item); + } +} + +void ComboBox::reset() { + SendMessage(widget->window, CB_RESETCONTENT, 0, 0); +} + +void ComboBox::addItem(const string &text) { + SendMessage(widget->window, CB_ADDSTRING, 0, (LPARAM)(wchar_t*)utf16_t(text)); + if(SendMessage(widget->window, CB_GETCOUNT, 0, 0) == 1) setSelection(0); +} + +unsigned ComboBox::selection() { + return SendMessage(widget->window, CB_GETCURSEL, 0, 0); +} + +void ComboBox::setSelection(unsigned row) { + SendMessage(widget->window, CB_SETCURSEL, comboBox->selection = row, 0); +} + +ComboBox::ComboBox() { + comboBox = new ComboBox::Data; + comboBox->selection = 0; +} diff --git a/phoenix/windows/editbox.cpp b/phoenix/windows/editbox.cpp new file mode 100755 index 00000000..2d165b3a --- /dev/null +++ b/phoenix/windows/editbox.cpp @@ -0,0 +1,53 @@ +void EditBox::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + widget->window = CreateWindowEx( + WS_EX_CLIENTEDGE, L"EDIT", L"", + WS_CHILD | WS_VISIBLE | ES_AUTOVSCROLL | ES_MULTILINE | ES_WANTRETURN | + (editBox->wordWrap == false ? ES_AUTOHSCROLL : 0), + editBox->x = x, editBox->y = y, editBox->width = width, editBox->height = height, + parent.widget->window, (HMENU)object->id, GetModuleHandle(0), 0 + ); + setText(text); + SetWindowLongPtr(widget->window, GWLP_USERDATA, (LONG_PTR)this); + SendMessage(widget->window, WM_SETFONT, (WPARAM)(parent.window->defaultFont ? parent.window->defaultFont : OS::os->proportionalFont), 0); +} + +string EditBox::getText() { + unsigned length = GetWindowTextLength(widget->window); + wchar_t buffer[length + 1]; + GetWindowText(widget->window, buffer, length + 1); + buffer[length] = 0; + string text = utf8_t(buffer); + text.replace("\r", ""); + return text; +} + +void EditBox::setText(const string &text) { + string output = text; + output.replace("\r", ""); + output.replace("\n", "\r\n"); + object->locked = true; + SetWindowText(widget->window, utf16_t(output)); + object->locked = false; +} + +void EditBox::setEditable(bool editable) { + SendMessage(widget->window, EM_SETREADONLY, editable == false, (LPARAM)0); +} + +void EditBox::setWordWrap(bool wordWrap) { + editBox->wordWrap = wordWrap; + if(widget->window == 0) return; + + //ES_AUTOSCROLL options cannot be changed after control has been created; + //so destroy the control and recreate it with desired options + HWND hparent = GetParent(widget->window); + Window *parent = (Window*)GetWindowLongPtr(hparent, GWLP_USERDATA); + string text = getText(); + DestroyWindow(widget->window); + create(*parent, editBox->x, editBox->y, editBox->width, editBox->height, text); +} + +EditBox::EditBox() { + editBox = new EditBox::Data; + editBox->wordWrap = true; +} diff --git a/phoenix/windows/font.cpp b/phoenix/windows/font.cpp new file mode 100755 index 00000000..712b928f --- /dev/null +++ b/phoenix/windows/font.cpp @@ -0,0 +1,26 @@ +static HFONT Font_createFont(const string &name, unsigned size, bool bold, bool italic) { + return CreateFont( + -(size * 96.0 / 72.0 + 0.5), + 0, 0, 0, bold == false ? FW_NORMAL : FW_BOLD, italic, 0, 0, 0, 0, 0, 0, 0, + utf16_t(name) + ); +} + +bool Font::create(const string &name, unsigned size, Font::Style style) { + font->font = Font_createFont( + name, size, + (style & Font::Style::Bold) == Font::Style::Bold, + (style & Font::Style::Italic) == Font::Style::Italic + ); + return font->font; +} + +Font::Font() { + font = new Font::Data; + font->font = 0; +} + +Font::~Font() { + if(font->font) DeleteObject(font->font); + delete font; +} diff --git a/phoenix/windows/horizontalslider.cpp b/phoenix/windows/horizontalslider.cpp new file mode 100755 index 00000000..9f5939bd --- /dev/null +++ b/phoenix/windows/horizontalslider.cpp @@ -0,0 +1,25 @@ +void HorizontalSlider::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, unsigned length) { + length += (length == 0); + widget->window = CreateWindow( + TRACKBAR_CLASS, L"", + WS_CHILD | WS_VISIBLE | WS_TABSTOP | TBS_NOTICKS | TBS_BOTH | TBS_HORZ, + x, y, width, height, + parent.widget->window, (HMENU)object->id, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(widget->window, GWLP_USERDATA, (LONG_PTR)this); + SendMessage(widget->window, TBM_SETRANGE, (WPARAM)true, (LPARAM)MAKELONG(0, length - 1)); + SendMessage(widget->window, TBM_SETPAGESIZE, 0, (LPARAM)(length >> 3)); + setPosition(0); +} + +unsigned HorizontalSlider::position() { + return SendMessage(widget->window, TBM_GETPOS, 0, 0); +} + +void HorizontalSlider::setPosition(unsigned position) { + SendMessage(widget->window, TBM_SETPOS, (WPARAM)true, (LPARAM)(horizontalSlider->position = position)); +} + +HorizontalSlider::HorizontalSlider() { + horizontalSlider = new HorizontalSlider::Data; +} diff --git a/phoenix/windows/label.cpp b/phoenix/windows/label.cpp new file mode 100755 index 00000000..e20c7cf0 --- /dev/null +++ b/phoenix/windows/label.cpp @@ -0,0 +1,51 @@ +void Label::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + widget->window = CreateWindow( + L"phoenix_label", L"", + WS_CHILD | WS_VISIBLE, + x, y, width, height, + parent.widget->window, (HMENU)object->id, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(widget->window, GWLP_USERDATA, (LONG_PTR)this); + SendMessage(widget->window, WM_SETFONT, (WPARAM)(parent.window->defaultFont ? parent.window->defaultFont : OS::os->proportionalFont), 0); + setText(text); +} + +void Label::setText(const string &text) { + SetWindowText(widget->window, utf16_t(text)); + InvalidateRect(widget->window, 0, false); +} + +//all of this for want of a STATIC SS_VCENTER flag ... +LRESULT CALLBACK Label_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + Window *window_ptr = (Window*)GetWindowLongPtr(GetParent(hwnd), GWLP_USERDATA); + if(!window_ptr) return DefWindowProc(hwnd, msg, wparam, lparam); + Label *label_ptr = (Label*)GetWindowLongPtr(hwnd, GWLP_USERDATA); + if(!label_ptr) return DefWindowProc(hwnd, msg, wparam, lparam); + Window &window = *window_ptr; + Label &label = *label_ptr; + + switch(msg) { + case WM_PAINT: { + PAINTSTRUCT ps; + RECT rc; + BeginPaint(hwnd, &ps); + GetClientRect(hwnd, &rc); + FillRect(ps.hdc, &rc, window.window->brush ? window.window->brush : GetSysColorBrush(COLOR_3DFACE)); + SetBkColor(ps.hdc, window.window->brush ? window.window->brushColor : GetSysColor(COLOR_3DFACE)); + SelectObject(ps.hdc, label.widget->font); + unsigned length = GetWindowTextLength(hwnd); + wchar_t text[length + 1]; + GetWindowText(hwnd, text, length + 1); + text[length] = 0; + DrawText(ps.hdc, text, -1, &rc, DT_CALCRECT | DT_END_ELLIPSIS); + unsigned height = rc.bottom; + GetClientRect(hwnd, &rc); + rc.top = (rc.bottom - height) / 2; + rc.bottom = rc.top + height; + DrawText(ps.hdc, text, -1, &rc, DT_LEFT | DT_END_ELLIPSIS); + EndPaint(hwnd, &ps); + } + } + + return DefWindowProc(hwnd, msg, wparam, lparam); +} diff --git a/phoenix/windows/listbox.cpp b/phoenix/windows/listbox.cpp new file mode 100755 index 00000000..86d82255 --- /dev/null +++ b/phoenix/windows/listbox.cpp @@ -0,0 +1,114 @@ +void ListBox::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + widget->window = CreateWindowEx( + WS_EX_CLIENTEDGE, WC_LISTVIEW, L"", + WS_CHILD | WS_TABSTOP | WS_VISIBLE | + LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_NOSORTHEADER | LVS_NOCOLUMNHEADER, + x, y, width, height, + parent.widget->window, (HMENU)object->id, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(widget->window, GWLP_USERDATA, (LONG_PTR)this); + SendMessage(widget->window, WM_SETFONT, (WPARAM)(parent.window->defaultFont ? parent.window->defaultFont : OS::os->proportionalFont), 0); + ListView_SetExtendedListViewStyle(widget->window, LVS_EX_FULLROWSELECT); + + lstring list; + list.split("\t", text); + listBox->columns = list.size(); + for(unsigned i = 0; i < list.size(); i++) { + LVCOLUMN column; + column.mask = LVCF_FMT | LVCF_TEXT | LVCF_SUBITEM; + column.fmt = LVCFMT_LEFT; + column.iSubItem = list.size(); + utf16_t text(list[i]); + column.pszText = text; + ListView_InsertColumn(widget->window, i, &column); + } + resizeColumnsToContent(); +} + +void ListBox::setHeaderVisible(bool headerVisible) { + SetWindowLong( + widget->window, + GWL_STYLE, + (GetWindowLong(widget->window, GWL_STYLE) & ~LVS_NOCOLUMNHEADER) | + (headerVisible == false ? LVS_NOCOLUMNHEADER : 0) + ); +} + +void ListBox::setCheckable(bool checkable) { + ListView_SetExtendedListViewStyle(widget->window, LVS_EX_FULLROWSELECT | (checkable ? LVS_EX_CHECKBOXES : 0)); +} + +void ListBox::reset() { + ListView_DeleteAllItems(widget->window); +} + +void ListBox::resizeColumnsToContent() { + for(unsigned i = 0; i < listBox->columns; i++) { + ListView_SetColumnWidth(widget->window, i, LVSCW_AUTOSIZE_USEHEADER); + } +} + +void ListBox::addItem(const string &text) { + lstring list; + list.split("\t", text); + LVITEM item; + unsigned row = ListView_GetItemCount(widget->window); + item.mask = LVIF_TEXT; + item.iItem = row; + item.iSubItem = 0; + utf16_t wtext(list[0]); + item.pszText = wtext; + object->locked = true; + ListView_InsertItem(widget->window, &item); + object->locked = false; + for(unsigned i = 1; i < list.size(); i++) { + utf16_t wtext(list[i]); + ListView_SetItemText(widget->window, row, i, wtext); + } + + //workaround: when there is only one column, the horizontal scrollbar will always appear without this + if(listBox->columns == 1) ListView_SetColumnWidth(widget->window, 0, LVSCW_AUTOSIZE_USEHEADER); +} + +void ListBox::setItem(unsigned row, const string &text) { + lstring list; + list.split("\t", text); + for(unsigned i = 0; i < list.size(); i++) { + utf16_t wtext(list[i]); + ListView_SetItemText(widget->window, row, i, wtext); + } + + //workaround: when there is only one column, the horizontal scrollbar will always appear without this + if(listBox->columns == 1) ListView_SetColumnWidth(widget->window, 0, LVSCW_AUTOSIZE_USEHEADER); +} + +optional ListBox::selection() { + unsigned count = ListView_GetItemCount(widget->window); + for(unsigned i = 0; i < count; i++) { + if(ListView_GetItemState(widget->window, i, LVIS_SELECTED)) return { true, i }; + } + return { false, 0 }; +} + +void ListBox::setSelection(unsigned row) { + unsigned count = ListView_GetItemCount(widget->window); + for(unsigned i = 0; i < count; i++) { + ListView_SetItemState(widget->window, i, LVIS_FOCUSED, (i == row ? LVIS_FOCUSED : 0)); + ListView_SetItemState(widget->window, i, LVIS_SELECTED, (i == row ? LVIS_SELECTED : 0)); + } +} + +bool ListBox::checked(unsigned row) { + return ListView_GetCheckState(widget->window, row); +} + +void ListBox::setChecked(unsigned row, bool checked) { + object->locked = true; + ListView_SetCheckState(widget->window, row, checked); + object->locked = false; +} + +ListBox::ListBox() { + listBox = new ListBox::Data; + listBox->lostFocus = false; +} diff --git a/phoenix/windows/menu.cpp b/phoenix/windows/menu.cpp new file mode 100755 index 00000000..789ed382 --- /dev/null +++ b/phoenix/windows/menu.cpp @@ -0,0 +1,144 @@ +Action::Action() { + OS::os->objects.append(this); + action = new Action::Data; +} + +void Menu::create(Window &parent, const string &text) { + action->parentMenu = parent.window->menu; + action->menu = CreatePopupMenu(); + AppendMenu(parent.window->menu, MF_STRING | MF_POPUP, (UINT_PTR)action->menu, utf16_t(text)); +} + +void Menu::create(Menu &parent, const string &text) { + action->parentMenu = parent.action->menu; + action->menu = CreatePopupMenu(); + AppendMenu(parent.action->menu, MF_STRING | MF_POPUP, (UINT_PTR)action->menu, utf16_t(text)); +} + +bool Menu::enabled() { + MENUITEMINFO info; + memset(&info, 0, sizeof(MENUITEMINFO)); + info.cbSize = sizeof(MENUITEMINFO); + info.fMask = MIIM_STATE; + GetMenuItemInfo(action->parentMenu, (UINT_PTR)action->menu, false, &info); + return (info.fState & MFS_GRAYED) == 0; +} + +void Menu::setEnabled(bool enabled) { + EnableMenuItem(action->parentMenu, (UINT_PTR)action->menu, MF_BYCOMMAND | (enabled ? MF_ENABLED : MF_GRAYED)); +} + +void MenuSeparator::create(Menu &parent) { + action->parent = &parent; + AppendMenu(parent.action->menu, MF_SEPARATOR, object->id, L""); +} + +bool MenuSeparator::enabled() { + MENUITEMINFO info; + memset(&info, 0, sizeof(MENUITEMINFO)); + info.cbSize = sizeof(MENUITEMINFO); + info.fMask = MIIM_STATE; + GetMenuItemInfo(action->parent->action->menu, object->id, false, &info); + return (info.fState & MFS_GRAYED) == 0; +} + +void MenuSeparator::setEnabled(bool enabled) { + EnableMenuItem(action->parent->action->menu, object->id, MF_BYCOMMAND | (enabled ? MF_ENABLED : MF_GRAYED)); +} + +void MenuItem::create(Menu &parent, const string &text) { + action->parent = &parent; + AppendMenu(parent.action->menu, MF_STRING, object->id, utf16_t(text)); +} + +bool MenuItem::enabled() { + MENUITEMINFO info; + memset(&info, 0, sizeof(MENUITEMINFO)); + info.cbSize = sizeof(MENUITEMINFO); + info.fMask = MIIM_STATE; + GetMenuItemInfo(action->parent->action->menu, object->id, false, &info); + return (info.fState & MFS_GRAYED) == 0; +} + +void MenuItem::setEnabled(bool enabled) { + EnableMenuItem(action->parent->action->menu, object->id, MF_BYCOMMAND | (enabled ? MF_ENABLED : MF_GRAYED)); +} + +void MenuCheckItem::create(Menu &parent, const string &text) { + action->parent = &parent; + AppendMenu(parent.action->menu, MF_STRING, object->id, utf16_t(text)); +} + +bool MenuCheckItem::enabled() { + MENUITEMINFO info; + memset(&info, 0, sizeof(MENUITEMINFO)); + info.cbSize = sizeof(MENUITEMINFO); + info.fMask = MIIM_STATE; + GetMenuItemInfo(action->parent->action->menu, object->id, false, &info); + return (info.fState & MFS_GRAYED) == 0; +} + +void MenuCheckItem::setEnabled(bool enabled) { + EnableMenuItem(action->parent->action->menu, object->id, MF_BYCOMMAND | (enabled ? MF_ENABLED : MF_GRAYED)); +} + +bool MenuCheckItem::checked() { + MENUITEMINFO info; + memset(&info, 0, sizeof(MENUITEMINFO)); + info.cbSize = sizeof(MENUITEMINFO); + info.fMask = MIIM_STATE; + GetMenuItemInfo(action->parent->action->menu, object->id, false, &info); + return info.fState & MFS_CHECKED; +} + +void MenuCheckItem::setChecked(bool checked) { + CheckMenuItem(action->parent->action->menu, object->id, checked ? MF_CHECKED : MF_UNCHECKED); +} + +void MenuRadioItem::create(Menu &parent, const string &text) { + action->parent = &parent; + action->radioParent = this; + action->items.append(this); + AppendMenu(parent.action->menu, MF_STRING, object->id, utf16_t(text)); + setChecked(); +} + +void MenuRadioItem::create(MenuRadioItem &parent, const string &text) { + action->parent = parent.action->parent; + action->radioParent = parent.action->radioParent; + action->radioParent->action->items.append(this); + AppendMenu(action->parent->action->menu, MF_STRING, object->id, utf16_t(text)); +} + +bool MenuRadioItem::enabled() { + MENUITEMINFO info; + memset(&info, 0, sizeof(MENUITEMINFO)); + info.cbSize = sizeof(MENUITEMINFO); + info.fMask = MIIM_STATE; + GetMenuItemInfo(action->parent->action->menu, object->id, false, &info); + return (info.fState & MFS_GRAYED) == 0; +} + +void MenuRadioItem::setEnabled(bool enabled) { + EnableMenuItem(action->parent->action->menu, object->id, MF_BYCOMMAND | (enabled ? MF_ENABLED : MF_GRAYED)); +} + +bool MenuRadioItem::checked() { + MENUITEMINFO info; + memset(&info, 0, sizeof(MENUITEMINFO)); + info.cbSize = sizeof(MENUITEMINFO); + info.fMask = MIIM_STATE; + GetMenuItemInfo(action->parent->action->menu, object->id, false, &info); + return info.fState & MFS_CHECKED; +} + +void MenuRadioItem::setChecked() { + MenuRadioItem *parent = action->radioParent; + foreach(item, parent->action->items) { + CheckMenuRadioItem( + action->parent->action->menu, + item->object->id, item->object->id, item->object->id + (item != this), + MF_BYCOMMAND + ); + } +} diff --git a/phoenix/windows/messagewindow.cpp b/phoenix/windows/messagewindow.cpp new file mode 100755 index 00000000..b2439dc3 --- /dev/null +++ b/phoenix/windows/messagewindow.cpp @@ -0,0 +1,41 @@ +static MessageWindow::Response MessageWindow_response(MessageWindow::Buttons buttons, UINT response) { + if(response == IDOK) return MessageWindow::Response::Ok; + if(response == IDCANCEL) return MessageWindow::Response::Cancel; + if(response == IDYES) return MessageWindow::Response::Yes; + if(response == IDNO) return MessageWindow::Response::No; + if(buttons == MessageWindow::Buttons::OkCancel) return MessageWindow::Response::Cancel; + if(buttons == MessageWindow::Buttons::YesNo) return MessageWindow::Response::No; + return MessageWindow::Response::Ok; +} + +MessageWindow::Response MessageWindow::information(Window &parent, const string &text, MessageWindow::Buttons buttons) { + UINT flags = MB_ICONINFORMATION; + if(buttons == Buttons::Ok) flags |= MB_OK; + if(buttons == Buttons::OkCancel) flags |= MB_OKCANCEL; + if(buttons == Buttons::YesNo) flags |= MB_YESNO; + return MessageWindow_response(buttons, MessageBox(&parent != &Window::None ? parent.widget->window : 0, utf16_t(text), L"", flags)); +} + +MessageWindow::Response MessageWindow::question(Window &parent, const string &text, MessageWindow::Buttons buttons) { + UINT flags = MB_ICONQUESTION; + if(buttons == Buttons::Ok) flags |= MB_OK; + if(buttons == Buttons::OkCancel) flags |= MB_OKCANCEL; + if(buttons == Buttons::YesNo) flags |= MB_YESNO; + return MessageWindow_response(buttons, MessageBox(&parent != &Window::None ? parent.widget->window : 0, utf16_t(text), L"", flags)); +} + +MessageWindow::Response MessageWindow::warning(Window &parent, const string &text, MessageWindow::Buttons buttons) { + UINT flags = MB_ICONWARNING; + if(buttons == Buttons::Ok) flags |= MB_OK; + if(buttons == Buttons::OkCancel) flags |= MB_OKCANCEL; + if(buttons == Buttons::YesNo) flags |= MB_YESNO; + return MessageWindow_response(buttons, MessageBox(&parent != &Window::None ? parent.widget->window : 0, utf16_t(text), L"", flags)); +} + +MessageWindow::Response MessageWindow::critical(Window &parent, const string &text, MessageWindow::Buttons buttons) { + UINT flags = MB_ICONERROR; + if(buttons == Buttons::Ok) flags |= MB_OK; + if(buttons == Buttons::OkCancel) flags |= MB_OKCANCEL; + if(buttons == Buttons::YesNo) flags |= MB_YESNO; + return MessageWindow_response(buttons, MessageBox(&parent != &Window::None ? parent.widget->window : 0, utf16_t(text), L"", flags)); +} diff --git a/phoenix/windows/object.cpp b/phoenix/windows/object.cpp new file mode 100755 index 00000000..2a5b8785 --- /dev/null +++ b/phoenix/windows/object.cpp @@ -0,0 +1,87 @@ +struct Object::Data { + unsigned id; + bool locked; +}; + +struct Font::Data { + HFONT font; +}; + +struct Action::Data { + Menu *parent; + HMENU parentMenu; + HMENU menu; + MenuRadioItem *radioParent; + array items; +}; + +struct Widget::Data { + HWND window; + HFONT font; +}; + +struct Window::Data { + HFONT defaultFont; + HBRUSH brush; + COLORREF brushColor; + HMENU menu; + HWND status; + unsigned width; + unsigned height; +}; + +struct Canvas::Data { + uint32_t *buffer; + BITMAPINFO bmi; + unsigned pitch; + unsigned width; + unsigned height; +}; + +struct ComboBox::Data { + unsigned selection; +}; + +struct EditBox::Data { + bool wordWrap; + unsigned x; + unsigned y; + unsigned width; + unsigned height; +}; + +struct HorizontalSlider::Data { + unsigned position; +}; + +struct ListBox::Data { + unsigned columns; + bool lostFocus; +}; + +struct RadioBox::Data { + Window *parentWindow; + RadioBox *parent; + array items; +}; + +struct VerticalSlider::Data { + unsigned position; +}; + +struct OS::Data { + nall::array objects; + HFONT proportionalFont; + HFONT monospaceFont; +}; + +void Object::unused() { +} + +Object::Object() { + OS::initialize(); + static unsigned guid = 100; + object = new Object::Data; + object->id = guid++; + object->locked = false; +} diff --git a/phoenix/windows/phoenix.Manifest b/phoenix/windows/phoenix.Manifest new file mode 100755 index 00000000..71013ffe --- /dev/null +++ b/phoenix/windows/phoenix.Manifest @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/phoenix/windows/phoenix.rc b/phoenix/windows/phoenix.rc new file mode 100755 index 00000000..89fb8dc2 --- /dev/null +++ b/phoenix/windows/phoenix.rc @@ -0,0 +1 @@ +1 24 "phoenix.Manifest" diff --git a/phoenix/windows/progressbar.cpp b/phoenix/windows/progressbar.cpp new file mode 100755 index 00000000..230f12b4 --- /dev/null +++ b/phoenix/windows/progressbar.cpp @@ -0,0 +1,18 @@ +void ProgressBar::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height) { + widget->window = CreateWindow( + PROGRESS_CLASS, L"", + WS_CHILD | WS_VISIBLE | PBS_SMOOTH, + x, y, width, height, + parent.widget->window, (HMENU)object->id, GetModuleHandle(0), 0 + ); + SendMessage(widget->window, PBM_SETRANGE, 0, MAKELPARAM(0, 100)); + SendMessage(widget->window, PBM_SETSTEP, MAKEWPARAM(1, 0), 0); +} + +unsigned ProgressBar::position() { + return SendMessage(widget->window, PBM_GETPOS, 0, 0); +} + +void ProgressBar::setPosition(unsigned position) { + SendMessage(widget->window, PBM_SETPOS, (WPARAM)position, 0); +} diff --git a/phoenix/windows/radiobox.cpp b/phoenix/windows/radiobox.cpp new file mode 100755 index 00000000..9c49297b --- /dev/null +++ b/phoenix/windows/radiobox.cpp @@ -0,0 +1,42 @@ +void RadioBox::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + radioBox->parentWindow = &parent; + radioBox->parent = this; + radioBox->parent->radioBox->items.append(this); + widget->window = CreateWindow( + L"BUTTON", utf16_t(text), + WS_CHILD | WS_TABSTOP | WS_VISIBLE | BS_RADIOBUTTON, + x, y, width, height, + parent.widget->window, (HMENU)object->id, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(widget->window, GWLP_USERDATA, (LONG_PTR)this); + SendMessage(widget->window, WM_SETFONT, (WPARAM)(parent.window->defaultFont ? parent.window->defaultFont : OS::os->proportionalFont), 0); + setChecked(); +} + +void RadioBox::create(RadioBox &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + radioBox->parentWindow = parent.radioBox->parentWindow; + radioBox->parent = parent.radioBox->parent; + radioBox->parent->radioBox->items.append(this); + widget->window = CreateWindow( + L"BUTTON", utf16_t(text), + WS_CHILD | WS_TABSTOP | WS_VISIBLE | BS_RADIOBUTTON, + x, y, width, height, + GetParent(radioBox->parent->widget->window), (HMENU)object->id, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(widget->window, GWLP_USERDATA, (LONG_PTR)this); + SendMessage(widget->window, WM_SETFONT, (WPARAM)(radioBox->parentWindow->window->defaultFont ? radioBox->parentWindow->window->defaultFont : OS::os->proportionalFont), 0); +} + +bool RadioBox::checked() { + return SendMessage(widget->window, BM_GETCHECK, 0, 0); +} + +void RadioBox::setChecked() { + foreach(item, radioBox->parent->radioBox->items) { + SendMessage(item->widget->window, BM_SETCHECK, (WPARAM)(item == this), 0); + } +} + +RadioBox::RadioBox() { + radioBox = new RadioBox::Data; +} diff --git a/phoenix/windows/textbox.cpp b/phoenix/windows/textbox.cpp new file mode 100755 index 00000000..e63f2dac --- /dev/null +++ b/phoenix/windows/textbox.cpp @@ -0,0 +1,28 @@ +void TextBox::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + widget->window = CreateWindowEx( + WS_EX_CLIENTEDGE, L"EDIT", utf16_t(text), + WS_CHILD | WS_TABSTOP | WS_VISIBLE | ES_AUTOHSCROLL | ES_AUTOVSCROLL, + x, y, width, height, + parent.widget->window, (HMENU)object->id, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(widget->window, GWLP_USERDATA, (LONG_PTR)this); + SendMessage(widget->window, WM_SETFONT, (WPARAM)(parent.window->defaultFont ? parent.window->defaultFont : OS::os->proportionalFont), 0); +} + +string TextBox::text() { + unsigned length = GetWindowTextLength(widget->window); + wchar_t text[length + 1]; + GetWindowText(widget->window, text, length + 1); + text[length] = 0; + return utf8_t(text); +} + +void TextBox::setText(const string &text) { + object->locked = true; + SetWindowText(widget->window, utf16_t(text)); + object->locked = false; +} + +void TextBox::setEditable(bool editable) { + SendMessage(widget->window, EM_SETREADONLY, editable == false, 0); +} diff --git a/phoenix/windows/verticalslider.cpp b/phoenix/windows/verticalslider.cpp new file mode 100755 index 00000000..43024432 --- /dev/null +++ b/phoenix/windows/verticalslider.cpp @@ -0,0 +1,25 @@ +void VerticalSlider::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, unsigned length) { + length += (length == 0); + widget->window = CreateWindow( + TRACKBAR_CLASS, L"", + WS_CHILD | WS_VISIBLE | WS_TABSTOP | TBS_NOTICKS | TBS_BOTH | TBS_VERT, + x, y, width, height, + parent.widget->window, (HMENU)object->id, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(widget->window, GWLP_USERDATA, (LONG_PTR)this); + SendMessage(widget->window, TBM_SETRANGE, (WPARAM)true, (LPARAM)MAKELONG(0, length - 1)); + SendMessage(widget->window, TBM_SETPAGESIZE, 0, (LPARAM)(length >> 3)); + setPosition(0); +} + +unsigned VerticalSlider::position() { + return SendMessage(widget->window, TBM_GETPOS, 0, 0); +} + +void VerticalSlider::setPosition(unsigned position) { + SendMessage(widget->window, TBM_SETPOS, (WPARAM)true, (LPARAM)(verticalSlider->position = position)); +} + +VerticalSlider::VerticalSlider() { + verticalSlider = new VerticalSlider::Data; +} diff --git a/phoenix/windows/viewport.cpp b/phoenix/windows/viewport.cpp new file mode 100755 index 00000000..21e38d79 --- /dev/null +++ b/phoenix/windows/viewport.cpp @@ -0,0 +1,17 @@ +void Viewport::create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height) { + widget->window = CreateWindow( + L"phoenix_viewport", L"", + WS_CHILD | WS_VISIBLE | WS_DISABLED, + x, y, width, height, + parent.widget->window, (HMENU)object->id, GetModuleHandle(0), 0 + ); + SetWindowLongPtr(widget->window, GWLP_USERDATA, (LONG_PTR)this); +} + +uintptr_t Viewport::handle() { + return (uintptr_t)widget->window; +} + +static LRESULT CALLBACK Viewport_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + return DefWindowProc(hwnd, msg, wparam, lparam); +} diff --git a/phoenix/windows/widget.cpp b/phoenix/windows/widget.cpp new file mode 100755 index 00000000..70ac5301 --- /dev/null +++ b/phoenix/windows/widget.cpp @@ -0,0 +1,40 @@ +void Widget::setFont(Font &font) { + widget->font = font.font->font; + SendMessage(widget->window, WM_SETFONT, (WPARAM)font.font->font, 0); +} + +bool Widget::visible() { + return GetWindowLong(widget->window, GWL_STYLE) & WS_VISIBLE; +} + +void Widget::setVisible(bool visible) { + ShowWindow(widget->window, visible ? SW_SHOWNORMAL : SW_HIDE); +} + +bool Widget::enabled() { + return IsWindowEnabled(widget->window); +} + +void Widget::setEnabled(bool enabled) { + EnableWindow(widget->window, enabled); +} + +bool Widget::focused() { + return (GetForegroundWindow() == widget->window); +} + +void Widget::setFocused() { + if(visible() == false) setVisible(true); + SetFocus(widget->window); +} + +void Widget::setGeometry(unsigned x, unsigned y, unsigned width, unsigned height) { + SetWindowPos(widget->window, NULL, x, y, width, height, SWP_NOZORDER); +} + +Widget::Widget() { + OS::os->objects.append(this); + widget = new Widget::Data; + widget->window = 0; + widget->font = OS::os->proportionalFont; +} diff --git a/phoenix/windows/window.cpp b/phoenix/windows/window.cpp new file mode 100755 index 00000000..5f86d547 --- /dev/null +++ b/phoenix/windows/window.cpp @@ -0,0 +1,96 @@ +void Window::create(unsigned x, unsigned y, unsigned width, unsigned height, const string &text) { + widget->window = CreateWindowEx( + 0, L"phoenix_window", utf16_t(text), + WS_POPUP | WS_SYSMENU | WS_CAPTION | WS_MINIMIZEBOX, + x, y, width, height, + 0, 0, GetModuleHandle(0), 0 + ); + window->menu = CreateMenu(); + window->status = CreateWindowEx( + 0, STATUSCLASSNAME, L"", + WS_CHILD, + 0, 0, 0, 0, + widget->window, 0, GetModuleHandle(0), 0 + ); + //StatusBar will be capable of receiving tab focus if it is not disabled + SetWindowLongPtr(window->status, GWL_STYLE, GetWindowLong(window->status, GWL_STYLE) | WS_DISABLED); + resize(width, height); + SetWindowLongPtr(widget->window, GWLP_USERDATA, (LONG_PTR)this); +} + +void Window::setDefaultFont(Font &font) { + window->defaultFont = font.font->font; +} + +void Window::setFont(Font &font) { + SendMessage(window->status, WM_SETFONT, (WPARAM)font.font->font, 0); +} + +Geometry Window::geometry() { + RECT position, size; + GetWindowRect(widget->window, &position); + GetClientRect(widget->window, &size); + if(GetWindowLongPtr(window->status, GWL_STYLE) & WS_VISIBLE) { + RECT status; + GetClientRect(window->status, &status); + size.bottom -= status.bottom - status.top; + } + return Geometry(position.left, position.top, size.right, size.bottom); +} + +void Window::setGeometry(unsigned x, unsigned y, unsigned width, unsigned height) { + bool isVisible = visible(); + if(isVisible) setVisible(false); + SetWindowPos(widget->window, NULL, x, y, width, height, SWP_NOZORDER | SWP_FRAMECHANGED); + resize(width, height); + if(isVisible) setVisible(true); +} + +void Window::setBackgroundColor(uint8_t red, uint8_t green, uint8_t blue) { + if(window->brush) DeleteObject(window->brush); + window->brushColor = RGB(red, green, blue); + window->brush = CreateSolidBrush(window->brushColor); +} + +void Window::setTitle(const string &text) { + SetWindowText(widget->window, utf16_t(text)); +} + +void Window::setStatusText(const string &text) { + SendMessage(window->status, SB_SETTEXT, 0, (LPARAM)(wchar_t*)utf16_t(text)); +} + +void Window::setMenuVisible(bool visible) { + SetMenu(widget->window, visible ? window->menu : 0); + resize(window->width, window->height); +} + +void Window::setStatusVisible(bool visible) { + ShowWindow(window->status, visible ? SW_SHOWNORMAL : SW_HIDE); + resize(window->width, window->height); +} + +Window::Window() { + window = new Window::Data; + window->defaultFont = 0; + window->brush = 0; +} + +void Window::resize(unsigned width, unsigned height) { + window->width = width; + window->height = height; + + SetWindowPos(widget->window, NULL, 0, 0, width, height, SWP_NOZORDER | SWP_NOMOVE | SWP_FRAMECHANGED); + RECT rc; + GetClientRect(widget->window, &rc); + width += width - (rc.right - rc.left); + height += height - (rc.bottom - rc.top); + + if(GetWindowLongPtr(window->status, GWL_STYLE) & WS_VISIBLE) { + GetClientRect(window->status, &rc); + height += rc.bottom - rc.top; + } + + SetWindowPos(widget->window, NULL, 0, 0, width, height, SWP_NOZORDER | SWP_NOMOVE | SWP_FRAMECHANGED); + SetWindowPos(window->status, NULL, 0, 0, 0, 0, SWP_NOZORDER | SWP_FRAMECHANGED); +} \ No newline at end of file diff --git a/phoenix/windows/windows.cpp b/phoenix/windows/windows.cpp new file mode 100755 index 00000000..697f8eec --- /dev/null +++ b/phoenix/windows/windows.cpp @@ -0,0 +1,468 @@ +#include +#include +#include +#include +#include +#include +#include +using namespace nall; + +namespace phoenix { + +#include "object.cpp" +#include "font.cpp" +#include "menu.cpp" +#include "widget.cpp" +#include "window.cpp" +#include "button.cpp" +#include "canvas.cpp" +#include "checkbox.cpp" +#include "combobox.cpp" +#include "editbox.cpp" +#include "horizontalslider.cpp" +#include "label.cpp" +#include "listbox.cpp" +#include "progressbar.cpp" +#include "radiobox.cpp" +#include "textbox.cpp" +#include "verticalslider.cpp" +#include "viewport.cpp" +#include "messagewindow.cpp" + +OS::Data *OS::os = 0; +Window Window::None; + +static void OS_keyboardProc(HWND, UINT, WPARAM, LPARAM); +static LRESULT CALLBACK OS_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); + +void OS::initialize() { + static bool initialized = false; + if(initialized == true) return; + initialized = true; + + InitCommonControls(); + CoInitialize(0); + + os = new OS::Data; + os->proportionalFont = Font_createFont("Tahoma", 8, false, false); + os->monospaceFont = Font_createFont("Courier New", 8, false, false); + + WNDCLASS wc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1); + wc.hCursor = LoadCursor(0, IDC_ARROW); + wc.hIcon = LoadIcon(GetModuleHandle(0), MAKEINTRESOURCE(2)); + wc.hInstance = GetModuleHandle(0); + wc.lpfnWndProc = OS_windowProc; + wc.lpszClassName = L"phoenix_window"; + wc.lpszMenuName = 0; + wc.style = CS_HREDRAW | CS_VREDRAW; + RegisterClass(&wc); + + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hbrBackground = CreateSolidBrush(RGB(0, 0, 0)); + wc.hCursor = LoadCursor(0, IDC_ARROW); + wc.hIcon = LoadIcon(0, IDI_APPLICATION); + wc.hInstance = GetModuleHandle(0); + wc.lpfnWndProc = Canvas_windowProc; + wc.lpszClassName = L"phoenix_canvas"; + wc.lpszMenuName = 0; + wc.style = CS_HREDRAW | CS_VREDRAW; + RegisterClass(&wc); + + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1); + wc.hCursor = LoadCursor(0, IDC_ARROW); + wc.hIcon = LoadIcon(0, IDI_APPLICATION); + wc.hInstance = GetModuleHandle(0); + wc.lpfnWndProc = Label_windowProc; + wc.lpszClassName = L"phoenix_label"; + wc.lpszMenuName = 0; + wc.style = CS_HREDRAW | CS_VREDRAW; + RegisterClass(&wc); + + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hbrBackground = CreateSolidBrush(RGB(0, 0, 0)); + wc.hCursor = LoadCursor(0, IDC_ARROW); + wc.hIcon = LoadIcon(0, IDI_APPLICATION); + wc.hInstance = GetModuleHandle(0); + wc.lpfnWndProc = Viewport_windowProc; + wc.lpszClassName = L"phoenix_viewport"; + wc.lpszMenuName = 0; + wc.style = CS_HREDRAW | CS_VREDRAW; + RegisterClass(&wc); +} + +bool OS::pending() { + MSG msg; + return PeekMessage(&msg, 0, 0, 0, PM_NOREMOVE); +} + +void OS::run() { + while(pending()) { + MSG msg; + if(PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) { + if(msg.message == WM_KEYDOWN || msg.message == WM_KEYUP) { + OS_keyboardProc(msg.hwnd, msg.message, msg.wParam, msg.lParam); + } + if(!IsDialogMessage(GetParent(msg.hwnd) ? GetParent(msg.hwnd) : msg.hwnd, &msg)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + } +} + +void OS::main() { + MSG msg; + while(GetMessage(&msg, 0, 0, 0)) { + if(msg.message == WM_KEYDOWN || msg.message == WM_KEYUP) { + OS_keyboardProc(msg.hwnd, msg.message, msg.wParam, msg.lParam); + } + if(!IsDialogMessage(GetParent(msg.hwnd) ? GetParent(msg.hwnd) : msg.hwnd, &msg)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } +} + +void OS::quit() { + PostQuitMessage(0); +} + +unsigned OS::desktopWidth() { + return GetSystemMetrics(SM_CXSCREEN); +} + +unsigned OS::desktopHeight() { + return GetSystemMetrics(SM_CYSCREEN); +} + +string OS::folderSelect(Window &parent, const string &path) { + wchar_t wfilename[PATH_MAX + 1] = L""; + BROWSEINFO bi; + bi.hwndOwner = &parent != &Window::None ? parent.widget->window : 0; + bi.pidlRoot = NULL; + bi.pszDisplayName = wfilename; + bi.lpszTitle = L""; + bi.ulFlags = BIF_NEWDIALOGSTYLE | BIF_RETURNONLYFSDIRS; + bi.lpfn = NULL; + bi.lParam = 0; + bi.iImage = 0; + bool result = false; + LPITEMIDLIST pidl = SHBrowseForFolder(&bi); + if(pidl) { + if(SHGetPathFromIDList(pidl, wfilename)) { + result = true; + IMalloc *imalloc = 0; + if(SUCCEEDED(SHGetMalloc(&imalloc))) { + imalloc->Free(pidl); + imalloc->Release(); + } + } + } + if(result == false) return ""; + string name = utf8_t(wfilename); + name.transform("\\", "/"); + if(name.endswith("/") == false) name.append("/"); + return name; +} + +string OS::fileOpen(Window &parent, const string &filter, const string &path) { + string dir = path; + dir.replace("/", "\\"); + + string filterInfo; + lstring type; + type.split("\n", filter); + for(unsigned i = 0; i < type.size(); i++) { + lstring part; + part.split("\t", type[i]); + if(part.size() != 2) continue; + filterInfo.append(part[0]); + filterInfo.append(" ("); + filterInfo.append(part[1]); + filterInfo.append(")\t"); + part[1].replace(",", ";"); + filterInfo.append(part[1]); + filterInfo.append("\t"); + } + + utf16_t wfilter(filterInfo); + utf16_t wdir(dir); + wchar_t wfilename[PATH_MAX] = L""; + + wchar_t *p = wfilter; + while(*p != L'\0') { + if(*p == L'\t') *p = L'\0'; + p++; + } + + OPENFILENAME ofn; + memset(&ofn, 0, sizeof(OPENFILENAME)); + ofn.lStructSize = sizeof(OPENFILENAME); + ofn.hwndOwner = &parent != &Window::None ? parent.widget->window : 0; + ofn.lpstrFilter = wfilter; + ofn.lpstrInitialDir = wdir; + ofn.lpstrFile = wfilename; + ofn.nMaxFile = PATH_MAX; + ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; + ofn.lpstrDefExt = L""; + + bool result = GetOpenFileName(&ofn); + if(result == false) return ""; + string name = utf8_t(wfilename); + name.transform("\\", "/"); + return name; +} + +string OS::fileSave(Window &parent, const string &filter, const string &path) { + string dir = path; + dir.replace("/", "\\"); + + string filterInfo; + lstring type; + type.split("\n", filter); + for(unsigned i = 0; i < type.size(); i++) { + lstring part; + part.split("\t", type[i]); + if(part.size() != 2) continue; + filterInfo.append(part[0]); + filterInfo.append(" ("); + filterInfo.append(part[1]); + filterInfo.append(")\t"); + part[1].replace(",", ";"); + filterInfo.append(part[1]); + filterInfo.append("\t"); + } + + utf16_t wfilter(filterInfo); + utf16_t wdir(dir); + wchar_t wfilename[PATH_MAX] = L""; + + wchar_t *p = wfilter; + while(*p != L'\0') { + if(*p == L'\t') *p = L'\0'; + p++; + } + + OPENFILENAME ofn; + memset(&ofn, 0, sizeof(OPENFILENAME)); + ofn.lStructSize = sizeof(OPENFILENAME); + ofn.hwndOwner = &parent != &Window::None ? parent.widget->window : 0; + ofn.lpstrFilter = wfilter; + ofn.lpstrInitialDir = wdir; + ofn.lpstrFile = wfilename; + ofn.nMaxFile = PATH_MAX; + ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; + ofn.lpstrDefExt = L""; + + bool result = GetSaveFileName(&ofn); + if(result == false) return ""; + string name = utf8_t(wfilename); + name.transform("\\", "/"); + return name; +} + +static void OS_keyboardProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + switch(msg) { + case WM_KEYDOWN: { + GUITHREADINFO info; + memset(&info, 0, sizeof(GUITHREADINFO)); + info.cbSize = sizeof(GUITHREADINFO); + GetGUIThreadInfo(GetCurrentThreadId(), &info); + Object *object_ptr = (Object*)GetWindowLongPtr(info.hwndFocus, GWLP_USERDATA); + if(object_ptr) { + if(dynamic_cast(object_ptr)) { + ListBox &listBox = (ListBox&)*object_ptr; + if(wparam == VK_RETURN) { + if(listBox.onActivate) listBox.onActivate(); + } + } else if(dynamic_cast(object_ptr)) { + TextBox &textBox = (TextBox&)*object_ptr; + if(wparam == VK_RETURN) { + if(textBox.onActivate) textBox.onActivate(); + } + } + } + } + } +} + +static LRESULT CALLBACK OS_windowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + Object *object_ptr = (Object*)GetWindowLongPtr(hwnd, GWLP_USERDATA); + if(!object_ptr || !dynamic_cast(object_ptr)) return DefWindowProc(hwnd, msg, wparam, lparam); + Window &window = (Window&)*object_ptr; + + switch(msg) { + case WM_CLOSE: { + if(window.onClose) { + if(window.onClose()) window.setVisible(false); + } else { + window.setVisible(false); + } + return TRUE; + } + + case WM_ERASEBKGND: { + if(window.window->brush == 0) break; + RECT rc; + GetClientRect(window.widget->window, &rc); + PAINTSTRUCT ps; + BeginPaint(window.widget->window, &ps); + FillRect(ps.hdc, &rc, window.window->brush); + EndPaint(window.widget->window, &ps); + return TRUE; + } + + case WM_CTLCOLORBTN: + case WM_CTLCOLORSTATIC: { + Object *object_ptr = (Object*)GetWindowLongPtr((HWND)lparam, GWLP_USERDATA); + if(object_ptr && window.window->brush) { + HDC hdc = (HDC)wparam; + SetBkColor((HDC)wparam, window.window->brushColor); + return (INT_PTR)window.window->brush; + } + } + + case WM_COMMAND: { + unsigned id = LOWORD(wparam); + HWND control = GetDlgItem(window.widget->window, id); + if(control == 0) { + Object *object_ptr = (Object*)OS::findObject(id); + if(object_ptr) { + if(dynamic_cast(object_ptr)) { + MenuItem &menuItem = (MenuItem&)*object_ptr; + if(menuItem.onTick) menuItem.onTick(); + } else if(dynamic_cast(object_ptr)) { + MenuCheckItem &menuCheckItem = (MenuCheckItem&)*object_ptr; + menuCheckItem.setChecked(!menuCheckItem.checked()); + if(menuCheckItem.onTick) menuCheckItem.onTick(); + } else if(dynamic_cast(object_ptr)) { + MenuRadioItem &menuRadioItem = (MenuRadioItem&)*object_ptr; + if(menuRadioItem.checked() == false) { + menuRadioItem.setChecked(); + if(menuRadioItem.onTick) menuRadioItem.onTick(); + } + } + } + } else { + Object *object_ptr = (Object*)GetWindowLongPtr(control, GWLP_USERDATA); + if(object_ptr) { + if(dynamic_cast(object_ptr)) { + Button &button = (Button&)*object_ptr; + if(button.onTick) button.onTick(); + } else if(dynamic_cast(object_ptr)) { + CheckBox &checkBox = (CheckBox&)*object_ptr; + checkBox.setChecked(!checkBox.checked()); + if(checkBox.onTick) checkBox.onTick(); + } else if(dynamic_cast(object_ptr)) { + ComboBox &comboBox = (ComboBox&)*object_ptr; + if(HIWORD(wparam) == CBN_SELCHANGE) { + if(comboBox.comboBox->selection != comboBox.selection()) { + comboBox.comboBox->selection = comboBox.selection(); + if(comboBox.onChange) comboBox.onChange(); + } + } + } else if(dynamic_cast(object_ptr)) { + EditBox &editBox = (EditBox&)*object_ptr; + if(HIWORD(wparam) == EN_CHANGE) { + if(editBox.object->locked == false && editBox.onChange) editBox.onChange(); + } + } else if(dynamic_cast(object_ptr)) { + RadioBox &radioBox = (RadioBox&)*object_ptr; + if(radioBox.checked() == false) { + radioBox.setChecked(); + if(radioBox.onTick) radioBox.onTick(); + } + } else if(dynamic_cast(object_ptr)) { + TextBox &textBox = (TextBox&)*object_ptr; + if(HIWORD(wparam) == EN_CHANGE) { + if(textBox.object->locked == false && textBox.onChange) textBox.onChange(); + } + } + } + } + } + + case WM_NOTIFY: { + unsigned id = LOWORD(wparam); + HWND control = GetDlgItem(window.widget->window, id); + if(control) { + Object *object_ptr = (Object*)GetWindowLongPtr(control, GWLP_USERDATA); + if(object_ptr) { + if(dynamic_cast(object_ptr)) { + ListBox &listBox = (ListBox&)*object_ptr; + LPNMHDR nmhdr = (LPNMHDR)lparam; + LPNMLISTVIEW nmlistview = (LPNMLISTVIEW)lparam; + + if(nmhdr->code == LVN_ITEMCHANGED && (nmlistview->uChanged & LVIF_STATE)) { + unsigned imagemask = ((nmlistview->uNewState & LVIS_STATEIMAGEMASK) >> 12) - 1; + if(imagemask == 0 || imagemask == 1) { + if(listBox.object->locked == false && listBox.onTick) listBox.onTick(nmlistview->iItem); + } else if((nmlistview->uOldState & LVIS_FOCUSED) && !(nmlistview->uNewState & LVIS_FOCUSED)) { + listBox.listBox->lostFocus = true; + } else { + if(!(nmlistview->uOldState & LVIS_SELECTED) && (nmlistview->uNewState & LVIS_SELECTED)) { + if(listBox.onChange) listBox.onChange(); + } else if(listBox.listBox->lostFocus == false && listBox.selection() == false) { + if(listBox.onChange) listBox.onChange(); + } + listBox.listBox->lostFocus = false; + } + } else if(nmhdr->code == LVN_ITEMACTIVATE) { + if(listBox.onActivate) listBox.onActivate(); + } + } + } + } + } + + case WM_HSCROLL: { + unsigned id = LOWORD(wparam); + HWND control = GetDlgItem(window.widget->window, id); + if(control) { + Object *object_ptr = (Object*)GetWindowLongPtr(control, GWLP_USERDATA); + if(object_ptr) { + if(dynamic_cast(object_ptr)) { + HorizontalSlider &horizontalSlider = (HorizontalSlider&)*object_ptr; + if(horizontalSlider.horizontalSlider->position != horizontalSlider.position()) { + horizontalSlider.horizontalSlider->position = horizontalSlider.position(); + if(horizontalSlider.onChange) horizontalSlider.onChange(); + } + } + } + } + } + + case WM_VSCROLL: { + unsigned id = LOWORD(wparam); + HWND control = GetDlgItem(window.widget->window, id); + if(control) { + Object *object_ptr = (Object*)GetWindowLongPtr(control, GWLP_USERDATA); + if(object_ptr) { + if(dynamic_cast(object_ptr)) { + VerticalSlider &verticalSlider = (VerticalSlider&)*object_ptr; + if(verticalSlider.verticalSlider->position != verticalSlider.position()) { + verticalSlider.verticalSlider->position = verticalSlider.position(); + if(verticalSlider.onChange) verticalSlider.onChange(); + } + } + } + } + } + } + + return DefWindowProc(hwnd, msg, wparam, lparam); +} + +Object* OS::findObject(unsigned id) { + foreach(object, os->objects) { if(object->object->id == id) return object; } + return 0; +} + +} diff --git a/phoenix/windows/windows.hpp b/phoenix/windows/windows.hpp new file mode 100755 index 00000000..41807a94 --- /dev/null +++ b/phoenix/windows/windows.hpp @@ -0,0 +1,288 @@ +namespace phoenix { + +struct Window; + +struct Object { + Object(); + Object& operator=(const Object&) = delete; + Object(const Object&) = delete; +//private: + struct Data; + Data *object; +private: + virtual void unused(); +}; + +struct Geometry { + unsigned x, y; + unsigned width, height; + inline Geometry() : x(0), y(0), width(0), height(0) {} + inline Geometry(unsigned x, unsigned y, unsigned width, unsigned height) : x(x), y(y), width(width), height(height) {} +}; + +struct Font : Object { + enum class Style : unsigned { + None = 0, + Bold = 1, + Italic = 2, + }; + bool create(const nall::string &name, unsigned size, Font::Style style = Style::None); + Font(); + ~Font(); +//private: + struct Data; + Data *font; +}; + +inline Font::Style operator|(Font::Style a, Font::Style b) { return (Font::Style)((unsigned)a | (unsigned)b); } +inline Font::Style operator&(Font::Style a, Font::Style b) { return (Font::Style)((unsigned)a & (unsigned)b); } + +struct Action : Object { + virtual bool enabled() = 0; + virtual void setEnabled(bool enabled = true) = 0; + Action(); +//private: + struct Data; + Data *action; +}; + +struct Menu : Action { + void create(Window &parent, const nall::string &text); + void create(Menu &parent, const nall::string &text); + bool enabled(); + void setEnabled(bool enabled = true); +}; + +struct MenuSeparator : Action { + void create(Menu &parent); + bool enabled(); + void setEnabled(bool enabled = true); +}; + +struct MenuItem : Action { + nall::function onTick; + void create(Menu &parent, const nall::string &text); + bool enabled(); + void setEnabled(bool enabled = true); +}; + +struct MenuCheckItem : Action { + nall::function onTick; + void create(Menu &parent, const nall::string &text); + bool enabled(); + void setEnabled(bool enabled = true); + bool checked(); + void setChecked(bool checked = true); +}; + +struct MenuRadioItem : Action { + nall::function onTick; + void create(Menu &parent, const nall::string &text); + void create(MenuRadioItem &parent, const nall::string &text); + bool enabled(); + void setEnabled(bool enabled = true); + bool checked(); + void setChecked(); +}; + +struct Widget : Object { + virtual void setFont(Font &font); + bool visible(); + void setVisible(bool visible = true); + bool enabled(); + void setEnabled(bool enabled = true); + bool focused(); + void setFocused(); + virtual void setGeometry(unsigned x, unsigned y, unsigned width, unsigned height); + Widget(); +//private: + struct Data; + Data *widget; +}; + +struct Window : Widget { + nall::function onClose; + void create(unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + void setDefaultFont(Font &font); + void setFont(Font &font); + Geometry geometry(); + void setGeometry(unsigned x, unsigned y, unsigned width, unsigned height); + void setBackgroundColor(uint8_t red, uint8_t green, uint8_t blue); + void setTitle(const nall::string &text); + void setStatusText(const nall::string &text); + void setMenuVisible(bool visible = true); + void setStatusVisible(bool visible = true); + Window(); +//private: + struct Data; + Data *window; + static Window None; + void resize(unsigned width, unsigned height); +}; + +struct Button : Widget { + nall::function onTick; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); +}; + +struct Canvas : Widget { + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height); + uint32_t* buffer(); + void redraw(); + Canvas(); + ~Canvas(); +//private: + struct Data; + Data *canvas; +}; + +struct CheckBox : Widget { + nall::function onTick; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + bool checked(); + void setChecked(bool checked = true); +}; + +struct ComboBox : Widget { + nall::function onChange; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + void reset(); + void addItem(const nall::string &text); + unsigned selection(); + void setSelection(unsigned item); + ComboBox(); +//private: + struct Data; + Data *comboBox; +}; + +struct EditBox : Widget { + nall::function onChange; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + nall::string getText(); + void setText(const nall::string &text); + void setEditable(bool editable = true); + void setWordWrap(bool wordWrap = true); + EditBox(); +//private: + struct Data; + Data *editBox; +}; + +struct HorizontalSlider : Widget { + nall::function onChange; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, unsigned length); + unsigned position(); + void setPosition(unsigned position); + HorizontalSlider(); +//private: + struct Data; + Data *horizontalSlider; +}; + +struct Label : Widget { + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + void setText(const nall::string &text); +}; + +struct ListBox : Widget { + nall::function onActivate; + nall::function onChange; + nall::function onTick; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + void setHeaderVisible(bool headerVisible = true); + void setCheckable(bool checkable = true); + void reset(); + void resizeColumnsToContent(); + void addItem(const nall::string &text); + void setItem(unsigned row, const nall::string &text); + bool checked(unsigned row); + void setChecked(unsigned row, bool checked = true); + nall::optional selection(); + void setSelection(unsigned row); + ListBox(); +//private: + struct Data; + Data *listBox; +}; + +struct ProgressBar : Widget { + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height); + unsigned position(); + void setPosition(unsigned position); +}; + +struct RadioBox : Widget { + nall::function onTick; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + void create(RadioBox &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + bool checked(); + void setChecked(); + RadioBox(); +//private: + struct Data; + Data *radioBox; +}; + +struct TextBox : Widget { + nall::function onActivate; + nall::function onChange; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, const nall::string &text = ""); + nall::string text(); + void setText(const nall::string &text); + void setEditable(bool editable = true); +}; + +struct VerticalSlider : Widget { + nall::function onChange; + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height, unsigned length); + unsigned position(); + void setPosition(unsigned position); + VerticalSlider(); +//private: + struct Data; + Data *verticalSlider; +}; + +struct Viewport : Widget { + void create(Window &parent, unsigned x, unsigned y, unsigned width, unsigned height); + uintptr_t handle(); +}; + +struct MessageWindow : Object { + enum class Buttons : unsigned { + Ok, + OkCancel, + YesNo, + }; + enum class Response : unsigned { + Ok, + Cancel, + Yes, + No, + }; + static Response information(Window &parent, const nall::string &text, Buttons = Buttons::Ok); + static Response question(Window &parent, const nall::string &text, Buttons = Buttons::YesNo); + static Response warning(Window &parent, const nall::string &text, Buttons = Buttons::Ok); + static Response critical(Window &parent, const nall::string &text, Buttons = Buttons::Ok); +}; + +struct OS : Object { + static bool pending(); + static void run(); + static void main(); + static void quit(); + static unsigned desktopWidth(); + static unsigned desktopHeight(); + static nall::string folderSelect(Window &parent, const nall::string &path = ""); + static nall::string fileOpen(Window &parent, const nall::string &filter, const nall::string &path = ""); + static nall::string fileSave(Window &parent, const nall::string &filter, const nall::string &path = ""); +//private: + static void initialize(); + struct Data; + static Data *os; + static Object* findObject(unsigned id); + friend class Object; +}; + +}; diff --git a/ruby/audio.hpp b/ruby/audio.hpp new file mode 100755 index 00000000..aaf43ff0 --- /dev/null +++ b/ruby/audio.hpp @@ -0,0 +1,23 @@ +class Audio { +public: + static const char *Volume; + static const char *Resample; + static const char *ResampleRatio; + + static const char *Handle; + static const char *Synchronize; + static const char *Frequency; + static const char *Latency; + + virtual bool cap(const nall::string& name) { return false; } + virtual nall::any get(const nall::string& name) { return false; } + virtual bool set(const nall::string& name, const nall::any& value) { return false; } + + virtual void sample(uint16_t left, uint16_t right) {} + virtual void clear() {} + virtual bool init() { return true; } + virtual void term() {} + + Audio() {} + virtual ~Audio() {} +}; diff --git a/ruby/audio/alsa.cpp b/ruby/audio/alsa.cpp new file mode 100755 index 00000000..45b4a8bd --- /dev/null +++ b/ruby/audio/alsa.cpp @@ -0,0 +1,240 @@ +//audio.alsa (2009-11-30) +//authors: BearOso, byuu, Nach, RedDwarf + +#include + +namespace ruby { + +class pAudioALSA { +public: + struct { + snd_pcm_t *handle; + snd_pcm_format_t format; + snd_pcm_uframes_t buffer_size; + snd_pcm_uframes_t period_size; + int channels; + const char *name; + } device; + + struct { + uint32_t *data; + unsigned length; + } buffer; + + struct { + bool synchronize; + unsigned frequency; + unsigned latency; + } settings; + + bool cap(const string& name) { + if(name == Audio::Synchronize) return true; + if(name == Audio::Frequency) return true; + if(name == Audio::Latency) return true; + return false; + } + + any get(const string& name) { + if(name == Audio::Synchronize) return settings.synchronize; + if(name == Audio::Frequency) return settings.frequency; + if(name == Audio::Latency) return settings.latency; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Audio::Synchronize) { + if(settings.synchronize != any_cast(value)) { + settings.synchronize = any_cast(value); + if(device.handle) init(); + } + return true; + } + + if(name == Audio::Frequency) { + if(settings.frequency != any_cast(value)) { + settings.frequency = any_cast(value); + if(device.handle) init(); + } + return true; + } + + if(name == Audio::Latency) { + if(settings.latency != any_cast(value)) { + settings.latency = any_cast(value); + if(device.handle) init(); + } + return true; + } + + return false; + } + + void sample(uint16_t left, uint16_t right) { + if(!device.handle) return; + + buffer.data[buffer.length++] = left + (right << 16); + if(buffer.length < device.period_size) return; + + snd_pcm_sframes_t avail; + do { + avail = snd_pcm_avail_update(device.handle); + if(avail < 0) snd_pcm_recover(device.handle, avail, 1); + if(avail < buffer.length) { + if(settings.synchronize == false) { + buffer.length = 0; + return; + } + int error = snd_pcm_wait(device.handle, -1); + if(error < 0) snd_pcm_recover(device.handle, error, 1); + } + } while(avail < buffer.length); + + //below code has issues with PulseAudio sound server + #if 0 + if(settings.synchronize == false) { + snd_pcm_sframes_t avail = snd_pcm_avail_update(device.handle); + if(avail < device.period_size) { + buffer.length = 0; + return; + } + } + #endif + + uint32_t *buffer_ptr = buffer.data; + int i = 4; + + while((buffer.length > 0) && i--) { + snd_pcm_sframes_t written = snd_pcm_writei(device.handle, buffer_ptr, buffer.length); + if(written < 0) { + //no samples written + snd_pcm_recover(device.handle, written, 1); + } else if(written <= buffer.length) { + buffer.length -= written; + buffer_ptr += written; + } + } + + if(i < 0) { + if(buffer.data == buffer_ptr) { + buffer.length--; + buffer_ptr++; + } + memmove(buffer.data, buffer_ptr, buffer.length * sizeof(uint32_t)); + } + } + + void clear() { + } + + bool init() { + term(); + + if(snd_pcm_open(&device.handle, device.name, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK) < 0) { + term(); + return false; + } + + //below code will not work with 24khz frequency rate (ALSA library bug) + #if 0 + if(snd_pcm_set_params(device.handle, device.format, SND_PCM_ACCESS_RW_INTERLEAVED, + device.channels, settings.frequency, 1, settings.latency * 1000) < 0) { + //failed to set device parameters + term(); + return false; + } + + if(snd_pcm_get_params(device.handle, &device.buffer_size, &device.period_size) < 0) { + device.period_size = settings.latency * 1000 * 1e-6 * settings.frequency / 4; + } + #endif + + snd_pcm_hw_params_t *hwparams; + snd_pcm_sw_params_t *swparams; + unsigned rate = settings.frequency; + unsigned buffer_time = settings.latency * 1000; + unsigned period_time = settings.latency * 1000 / 4; + + snd_pcm_hw_params_alloca(&hwparams); + if(snd_pcm_hw_params_any(device.handle, hwparams) < 0) { + term(); + return false; + } + + if(snd_pcm_hw_params_set_access(device.handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0 + || snd_pcm_hw_params_set_format(device.handle, hwparams, device.format) < 0 + || snd_pcm_hw_params_set_channels(device.handle, hwparams, device.channels) < 0 + || snd_pcm_hw_params_set_rate_near(device.handle, hwparams, &rate, 0) < 0 + || snd_pcm_hw_params_set_period_time_near(device.handle, hwparams, &period_time, 0) < 0 + || snd_pcm_hw_params_set_buffer_time_near(device.handle, hwparams, &buffer_time, 0) < 0 + ) { + term(); + return false; + } + + if(snd_pcm_hw_params(device.handle, hwparams) < 0) { + term(); + return false; + } + + if(snd_pcm_get_params(device.handle, &device.buffer_size, &device.period_size) < 0) { + term(); + return false; + } + + snd_pcm_sw_params_alloca(&swparams); + if(snd_pcm_sw_params_current(device.handle, swparams) < 0) { + term(); + return false; + } + + if(snd_pcm_sw_params_set_start_threshold(device.handle, swparams, + (device.buffer_size / device.period_size) * device.period_size) < 0 + ) { + term(); + return false; + } + + if(snd_pcm_sw_params(device.handle, swparams) < 0) { + term(); + return false; + } + + buffer.data = new uint32_t[device.period_size]; + return true; + } + + void term() { + if(device.handle) { + snd_pcm_drain(device.handle); + snd_pcm_close(device.handle); + device.handle = 0; + } + + if(buffer.data) { + delete[] buffer.data; + buffer.data = 0; + } + } + + pAudioALSA() { + device.handle = 0; + device.format = SND_PCM_FORMAT_S16_LE; + device.channels = 2; + device.name = "default"; + + buffer.data = 0; + buffer.length = 0; + + settings.synchronize = false; + settings.frequency = 22050; + settings.latency = 60; + } + + ~pAudioALSA() { + term(); + } +}; + +DeclareAudio(ALSA) + +}; diff --git a/ruby/audio/ao.cpp b/ruby/audio/ao.cpp new file mode 100755 index 00000000..0cfe670d --- /dev/null +++ b/ruby/audio/ao.cpp @@ -0,0 +1,94 @@ +/* + audio.ao (2008-06-01) + authors: Nach, RedDwarf +*/ + +#include + +namespace ruby { + +class pAudioAO { +public: + int driver_id; + ao_sample_format driver_format; + ao_device *audio_device; + + struct { + unsigned frequency; + } settings; + + bool cap(const string& name) { + if(name == Audio::Frequency) return true; + return false; + } + + any get(const string& name) { + if(name == Audio::Frequency) return settings.frequency; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Audio::Frequency) { + settings.frequency = any_cast(value); + if(audio_device) init(); + return true; + } + + return false; + } + + void sample(uint16_t l_sample, uint16_t r_sample) { + uint32_t samp = (l_sample << 0) + (r_sample << 16); + ao_play(audio_device, (char*)&samp, 4); //This may need to be byte swapped for Big Endian + } + + void clear() { + } + + bool init() { + term(); + + driver_id = ao_default_driver_id(); //ao_driver_id((const char*)driver) + if(driver_id < 0) return false; + + driver_format.bits = 16; + driver_format.channels = 2; + driver_format.rate = settings.frequency; + driver_format.byte_format = AO_FMT_LITTLE; + + ao_option *options = 0; + ao_info *di = ao_driver_info(driver_id); + if(!di) return false; + if(!strcmp(di->short_name, "alsa")) { + ao_append_option(&options, "buffer_time", "100000"); //100ms latency (default was 500ms) + } + + audio_device = ao_open_live(driver_id, &driver_format, options); + if(!audio_device) return false; + + return true; + } + + void term() { + if(audio_device) { + ao_close(audio_device); + audio_device = 0; + } + } + + pAudioAO() { + audio_device = 0; + ao_initialize(); + + settings.frequency = 22050; + } + + ~pAudioAO() { + term(); + //ao_shutdown(); //FIXME: this is causing a segfault for some reason when called ... + } +}; + +DeclareAudio(AO) + +}; diff --git a/ruby/audio/directsound.cpp b/ruby/audio/directsound.cpp new file mode 100755 index 00000000..17d09e23 --- /dev/null +++ b/ruby/audio/directsound.cpp @@ -0,0 +1,212 @@ +/* + audio.directsound (2007-12-26) + author: byuu +*/ + +#include + +namespace ruby { + +class pAudioDS { +public: + LPDIRECTSOUND ds; + LPDIRECTSOUNDBUFFER dsb_p, dsb_b; + DSBUFFERDESC dsbd; + WAVEFORMATEX wfx; + + struct { + unsigned rings; + unsigned latency; + + uint32_t *buffer; + unsigned bufferoffset; + + unsigned readring; + unsigned writering; + int distance; + } device; + + struct { + HWND handle; + bool synchronize; + unsigned frequency; + unsigned latency; + } settings; + + bool cap(const string& name) { + if(name == Audio::Handle) return true; + if(name == Audio::Synchronize) return true; + if(name == Audio::Frequency) return true; + if(name == Audio::Latency) return true; + return false; + } + + any get(const string& name) { + if(name == Audio::Handle) return (uintptr_t)settings.handle; + if(name == Audio::Synchronize) return settings.synchronize; + if(name == Audio::Frequency) return settings.frequency; + if(name == Audio::Latency) return settings.latency; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Audio::Handle) { + settings.handle = (HWND)any_cast(value); + return true; + } + + if(name == Audio::Synchronize) { + settings.synchronize = any_cast(value); + if(ds) clear(); + return true; + } + + if(name == Audio::Frequency) { + settings.frequency = any_cast(value); + if(ds) init(); + return true; + } + + if(name == Audio::Latency) { + settings.latency = any_cast(value); + if(ds) init(); + return true; + } + + return false; + } + + void sample(uint16_t left, uint16_t right) { + device.buffer[device.bufferoffset++] = left + (right << 16); + if(device.bufferoffset < device.latency) return; + device.bufferoffset = 0; + + DWORD pos, size; + void *output; + + if(settings.synchronize == true) { + //wait until playback buffer has an empty ring to write new audio data to + while(device.distance >= device.rings - 1) { + dsb_b->GetCurrentPosition(&pos, 0); + unsigned activering = pos / (device.latency * 4); + if(activering == device.readring) { + if(settings.synchronize == false) Sleep(1); + continue; + } + + //subtract number of played rings from ring distance counter + device.distance -= (device.rings + activering - device.readring) % device.rings; + device.readring = activering; + + if(device.distance < 2) { + //buffer underflow; set max distance to recover quickly + device.distance = device.rings - 1; + device.writering = (device.rings + device.readring - 1) % device.rings; + break; + } + } + } + + device.writering = (device.writering + 1) % device.rings; + device.distance = (device.distance + 1) % device.rings; + + if(dsb_b->Lock(device.writering * device.latency * 4, device.latency * 4, &output, &size, 0, 0, 0) == DS_OK) { + memcpy(output, device.buffer, device.latency * 4); + dsb_b->Unlock(output, size, 0, 0); + } + } + + void clear() { + device.readring = 0; + device.writering = device.rings - 1; + device.distance = device.rings - 1; + + device.bufferoffset = 0; + if(device.buffer) memset(device.buffer, 0, device.latency * device.rings * 4); + + if(!dsb_b) return; + dsb_b->Stop(); + dsb_b->SetCurrentPosition(0); + + DWORD size; + void *output; + dsb_b->Lock(0, device.latency * device.rings * 4, &output, &size, 0, 0, 0); + memset(output, 0, size); + dsb_b->Unlock(output, size, 0, 0); + + dsb_b->Play(0, 0, DSBPLAY_LOOPING); + } + + bool init() { + term(); + + device.rings = 8; + device.latency = settings.frequency * settings.latency / device.rings / 1000.0 + 0.5; + device.buffer = new uint32_t[device.latency * device.rings]; + device.bufferoffset = 0; + + DirectSoundCreate(0, &ds, 0); + ds->SetCooperativeLevel((HWND)settings.handle, DSSCL_PRIORITY); + + memset(&dsbd, 0, sizeof(dsbd)); + dsbd.dwSize = sizeof(dsbd); + dsbd.dwFlags = DSBCAPS_PRIMARYBUFFER; + dsbd.dwBufferBytes = 0; + dsbd.lpwfxFormat = 0; + ds->CreateSoundBuffer(&dsbd, &dsb_p, 0); + + memset(&wfx, 0, sizeof(wfx)); + wfx.wFormatTag = WAVE_FORMAT_PCM; + wfx.nChannels = 2; + wfx.nSamplesPerSec = settings.frequency; + wfx.wBitsPerSample = 16; + wfx.nBlockAlign = wfx.wBitsPerSample / 8 * wfx.nChannels; + wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign; + dsb_p->SetFormat(&wfx); + + memset(&dsbd, 0, sizeof(dsbd)); + dsbd.dwSize = sizeof(dsbd); + dsbd.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_CTRLFREQUENCY | DSBCAPS_GLOBALFOCUS | DSBCAPS_LOCSOFTWARE; + dsbd.dwBufferBytes = device.latency * device.rings * sizeof(uint32_t); + dsbd.guid3DAlgorithm = GUID_NULL; + dsbd.lpwfxFormat = &wfx; + ds->CreateSoundBuffer(&dsbd, &dsb_b, 0); + dsb_b->SetFrequency(settings.frequency); + dsb_b->SetCurrentPosition(0); + + clear(); + return true; + } + + void term() { + if(device.buffer) { + delete[] device.buffer; + device.buffer = 0; + } + + if(dsb_b) { dsb_b->Stop(); dsb_b->Release(); dsb_b = 0; } + if(dsb_p) { dsb_p->Stop(); dsb_p->Release(); dsb_p = 0; } + if(ds) { ds->Release(); ds = 0; } + } + + pAudioDS() { + ds = 0; + dsb_p = 0; + dsb_b = 0; + + device.buffer = 0; + device.bufferoffset = 0; + device.readring = 0; + device.writering = 0; + device.distance = 0; + + settings.handle = GetDesktopWindow(); + settings.synchronize = false; + settings.frequency = 22050; + settings.latency = 120; + } +}; + +DeclareAudio(DS) + +}; diff --git a/ruby/audio/openal.cpp b/ruby/audio/openal.cpp new file mode 100755 index 00000000..a5be2aac --- /dev/null +++ b/ruby/audio/openal.cpp @@ -0,0 +1,210 @@ +/* + audio.openal (2007-12-26) + author: Nach + contributors: byuu, wertigon, _willow_ +*/ + +#if defined(PLATFORM_OSX) + #include + #include +#else + #include + #include +#endif + +namespace ruby { + +class pAudioOpenAL { +public: + struct { + ALCdevice *handle; + ALCcontext *context; + ALuint source; + ALenum format; + unsigned latency; + unsigned queue_length; + } device; + + struct { + uint32_t *data; + unsigned length; + unsigned size; + } buffer; + + struct { + bool synchronize; + unsigned frequency; + unsigned latency; + } settings; + + bool cap(const string& name) { + if(name == Audio::Synchronize) return true; + if(name == Audio::Frequency) return true; + if(name == Audio::Latency) return true; + return false; + } + + any get(const string& name) { + if(name == Audio::Synchronize) return settings.synchronize; + if(name == Audio::Frequency) return settings.frequency; + if(name == Audio::Latency) return settings.latency; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Audio::Synchronize) { + settings.synchronize = any_cast(value); + return true; + } + + if(name == Audio::Frequency) { + settings.frequency = any_cast(value); + return true; + } + + if(name == Audio::Latency) { + if(settings.latency != any_cast(value)) { + settings.latency = any_cast(value); + update_latency(); + } + return true; + } + + return false; + } + + void sample(uint16_t sl, uint16_t sr) { + buffer.data[buffer.length++] = sl + (sr << 16); + if(buffer.length < buffer.size) return; + + ALuint albuffer = 0; + int processed = 0; + while(true) { + alGetSourcei(device.source, AL_BUFFERS_PROCESSED, &processed); + while(processed--) { + alSourceUnqueueBuffers(device.source, 1, &albuffer); + alDeleteBuffers(1, &albuffer); + device.queue_length--; + } + //wait for buffer playback to catch up to sample generation if not synchronizing + if(settings.synchronize == false || device.queue_length < 3) break; + } + + if(device.queue_length < 3) { + alGenBuffers(1, &albuffer); + alBufferData(albuffer, device.format, buffer.data, buffer.size * 4, settings.frequency); + alSourceQueueBuffers(device.source, 1, &albuffer); + device.queue_length++; + } + + ALint playing; + alGetSourcei(device.source, AL_SOURCE_STATE, &playing); + if(playing != AL_PLAYING) alSourcePlay(device.source); + buffer.length = 0; + } + + void clear() { + } + + void update_latency() { + if(buffer.data) delete[] buffer.data; + buffer.size = settings.frequency * settings.latency / 1000.0 + 0.5; + buffer.data = new uint32_t[buffer.size]; + } + + bool init() { + update_latency(); + device.queue_length = 0; + + bool success = false; + if(device.handle = alcOpenDevice(NULL)) { + if(device.context = alcCreateContext(device.handle, NULL)) { + alcMakeContextCurrent(device.context); + alGenSources(1, &device.source); + + //alSourcef (device.source, AL_PITCH, 1.0); + //alSourcef (device.source, AL_GAIN, 1.0); + //alSource3f(device.source, AL_POSITION, 0.0, 0.0, 0.0); + //alSource3f(device.source, AL_VELOCITY, 0.0, 0.0, 0.0); + //alSource3f(device.source, AL_DIRECTION, 0.0, 0.0, 0.0); + //alSourcef (device.source, AL_ROLLOFF_FACTOR, 0.0); + //alSourcei (device.source, AL_SOURCE_RELATIVE, AL_TRUE); + + alListener3f(AL_POSITION, 0.0, 0.0, 0.0); + alListener3f(AL_VELOCITY, 0.0, 0.0, 0.0); + ALfloat listener_orientation[] = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }; + alListenerfv(AL_ORIENTATION, listener_orientation); + + success = true; + } + } + + if(success == false) { + term(); + return false; + } + + return true; + } + + void term() { + if(alIsSource(device.source) == AL_TRUE) { + int playing = 0; + alGetSourcei(device.source, AL_SOURCE_STATE, &playing); + if(playing == AL_PLAYING) { + alSourceStop(device.source); + int queued = 0; + alGetSourcei(device.source, AL_BUFFERS_QUEUED, &queued); + while(queued--) { + ALuint albuffer = 0; + alSourceUnqueueBuffers(device.source, 1, &albuffer); + alDeleteBuffers(1, &albuffer); + device.queue_length--; + } + } + + alDeleteSources(1, &device.source); + device.source = 0; + } + + if(device.context) { + alcMakeContextCurrent(NULL); + alcDestroyContext(device.context); + device.context = 0; + } + + if(device.handle) { + alcCloseDevice(device.handle); + device.handle = 0; + } + + if(buffer.data) { + delete[] buffer.data; + buffer.data = 0; + } + } + + pAudioOpenAL() { + device.source = 0; + device.handle = 0; + device.context = 0; + device.format = AL_FORMAT_STEREO16; + device.queue_length = 0; + + buffer.data = 0; + buffer.length = 0; + buffer.size = 0; + + settings.synchronize = true; + settings.frequency = 22050; + settings.latency = 40; + } + + ~pAudioOpenAL() { + term(); + } +}; + +DeclareAudio(OpenAL) + +}; diff --git a/ruby/audio/oss.cpp b/ruby/audio/oss.cpp new file mode 100755 index 00000000..dcb8115c --- /dev/null +++ b/ruby/audio/oss.cpp @@ -0,0 +1,113 @@ +/* + audio.oss (2007-12-26) + author: Nach +*/ + +#include +#include +#include +#include + +//OSS4 soundcard.h includes below SNDCTL defines, but OSS3 does not +//However, OSS4 soundcard.h does not reside in +//Therefore, attempt to manually define SNDCTL values if using OSS3 header +//Note that if the defines below fail to work on any specific platform, one can point soundcard.h +//above to the correct location for OSS4 (usually /usr/lib/oss/include/sys/soundcard.h) +//Failing that, one can disable OSS4 ioctl calls inside init() and remove the below defines + +#ifndef SNDCTL_DSP_COOKEDMODE + #define SNDCTL_DSP_COOKEDMODE _IOW('P', 30, int) +#endif + +#ifndef SNDCTL_DSP_POLICY + #define SNDCTL_DSP_POLICY _IOW('P', 45, int) +#endif + +namespace ruby { + +class pAudioOSS { +public: + struct { + int fd; + int format; + int channels; + const char *name; + } device; + + struct { + unsigned frequency; + } settings; + + bool cap(const string& name) { + if(name == Audio::Frequency) return true; + return false; + } + + any get(const string& name) { + if(name == Audio::Frequency) return settings.frequency; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Audio::Frequency) { + settings.frequency = any_cast(value); + if(device.fd > 0) init(); + return true; + } + + return false; + } + + void sample(uint16_t sl, uint16_t sr) { + uint32_t sample = sl + (sr << 16); + unsigned unused = write(device.fd, &sample, 4); + } + + void clear() { + } + + bool init() { + term(); + + device.fd = open(device.name, O_WRONLY, O_NONBLOCK); + if(device.fd < 0) return false; + + #if 1 //SOUND_VERSION >= 0x040000 + //attempt to enable OSS4-specific features regardless of version + //OSS3 ioctl calls will silently fail, but sound will still work + int cooked = 1, policy = 4; //policy should be 0 - 10, lower = less latency, more CPU usage + ioctl(device.fd, SNDCTL_DSP_COOKEDMODE, &cooked); + ioctl(device.fd, SNDCTL_DSP_POLICY, &policy); + #endif + int freq = settings.frequency; + ioctl(device.fd, SNDCTL_DSP_CHANNELS, &device.channels); + ioctl(device.fd, SNDCTL_DSP_SETFMT, &device.format); + ioctl(device.fd, SNDCTL_DSP_SPEED, &freq); + + return true; + } + + void term() { + if(device.fd > 0) { + close(device.fd); + device.fd = -1; + } + } + + pAudioOSS() { + device.fd = -1; + device.format = AFMT_S16_LE; + device.channels = 2; + device.name = "/dev/dsp"; + + settings.frequency = 22050; + } + + ~pAudioOSS() { + term(); + } +}; + +DeclareAudio(OSS) + +}; diff --git a/ruby/audio/pulseaudio.cpp b/ruby/audio/pulseaudio.cpp new file mode 100755 index 00000000..bdd5f682 --- /dev/null +++ b/ruby/audio/pulseaudio.cpp @@ -0,0 +1,177 @@ +//audio.pulseaudio (2010-01-05) +//author: RedDwarf + +#include + +namespace ruby { + +class pAudioPulseAudio { +public: + struct { + pa_mainloop *mainloop; + pa_context *context; + pa_stream *stream; + pa_sample_spec spec; + pa_buffer_attr buffer_attr; + bool first; + } device; + + struct { + uint32_t *data; + size_t size; + unsigned offset; + } buffer; + + struct { + bool synchronize; + unsigned frequency; + unsigned latency; + } settings; + + bool cap(const string& name) { + if(name == Audio::Synchronize) return true; + if(name == Audio::Frequency) return true; + if(name == Audio::Latency) return true; + } + + any get(const string& name) { + if(name == Audio::Synchronize) return settings.synchronize; + if(name == Audio::Frequency) return settings.frequency; + if(name == Audio::Latency) return settings.latency; + } + + bool set(const string& name, const any& value) { + if(name == Audio::Synchronize) { + settings.synchronize = any_cast(value); + return true; + } + + if(name == Audio::Frequency) { + settings.frequency = any_cast(value); + if(device.stream) { + pa_operation_unref(pa_stream_update_sample_rate(device.stream, settings.frequency, NULL, NULL)); + } + return true; + } + + if(name == Audio::Latency) { + settings.latency = any_cast(value); + if(device.stream) { + device.buffer_attr.tlength = pa_usec_to_bytes(settings.latency * PA_USEC_PER_MSEC, &device.spec); + pa_stream_set_buffer_attr(device.stream, &device.buffer_attr, NULL, NULL); + } + return true; + } + } + + void sample(uint16_t left, uint16_t right) { + pa_stream_begin_write(device.stream, (void**)&buffer.data, &buffer.size); + buffer.data[buffer.offset++] = left + (right << 16); + if((buffer.offset + 1) * pa_frame_size(&device.spec) <= buffer.size) return; + + while(true) { + if(device.first) { + device.first = false; + pa_mainloop_iterate(device.mainloop, 0, NULL); + } else { + pa_mainloop_iterate(device.mainloop, 1, NULL); + } + unsigned length = pa_stream_writable_size(device.stream); + if(length >= buffer.offset * pa_frame_size(&device.spec)) break; + if(settings.synchronize == false) { + buffer.offset = 0; + return; + } + } + + pa_stream_write(device.stream, (const void*)buffer.data, buffer.offset * pa_frame_size(&device.spec), NULL, 0LL, PA_SEEK_RELATIVE); + buffer.data = 0; + buffer.offset = 0; + } + + void clear() { + } + + bool init() { + device.mainloop = pa_mainloop_new(); + + device.context = pa_context_new(pa_mainloop_get_api(device.mainloop), "ruby::pulseaudio"); + pa_context_connect(device.context, NULL, PA_CONTEXT_NOFLAGS, NULL); + + pa_context_state_t cstate; + do { + pa_mainloop_iterate(device.mainloop, 1, NULL); + cstate = pa_context_get_state(device.context); + if(!PA_CONTEXT_IS_GOOD(cstate)) return false; + } while(cstate != PA_CONTEXT_READY); + + device.spec.format = PA_SAMPLE_S16LE; + device.spec.channels = 2; + device.spec.rate = settings.frequency; + device.stream = pa_stream_new(device.context, "audio", &device.spec, NULL); + + device.buffer_attr.maxlength = -1; + device.buffer_attr.tlength = pa_usec_to_bytes(settings.latency * PA_USEC_PER_MSEC, &device.spec); + device.buffer_attr.prebuf = -1; + device.buffer_attr.minreq = -1; + device.buffer_attr.fragsize = -1; + + pa_stream_flags_t flags = (pa_stream_flags_t)(PA_STREAM_ADJUST_LATENCY | PA_STREAM_VARIABLE_RATE); + pa_stream_connect_playback(device.stream, NULL, &device.buffer_attr, flags, NULL, NULL); + + pa_stream_state_t sstate; + do { + pa_mainloop_iterate(device.mainloop, 1, NULL); + sstate = pa_stream_get_state(device.stream); + if(!PA_STREAM_IS_GOOD(sstate)) return false; + } while(sstate != PA_STREAM_READY); + + buffer.size = 960; + buffer.offset = 0; + device.first = true; + + return true; + } + + void term() { + if(buffer.data) { + pa_stream_cancel_write(device.stream); + buffer.data = 0; + } + + if(device.stream) { + pa_stream_disconnect(device.stream); + pa_stream_unref(device.stream); + device.stream = 0; + } + + if(device.context) { + pa_context_disconnect(device.context); + pa_context_unref(device.context); + device.context = 0; + } + + if(device.mainloop) { + pa_mainloop_free(device.mainloop); + device.mainloop = 0; + } + } + + pAudioPulseAudio() { + device.mainloop = 0; + device.context = 0; + device.stream = 0; + buffer.data = 0; + settings.synchronize = false; + settings.frequency = 22050; + settings.latency = 60; + } + + ~pAudioPulseAudio() { + term(); + } +}; + +DeclareAudio(PulseAudio) + +} diff --git a/ruby/audio/pulseaudiosimple.cpp b/ruby/audio/pulseaudiosimple.cpp new file mode 100755 index 00000000..cdd6e438 --- /dev/null +++ b/ruby/audio/pulseaudiosimple.cpp @@ -0,0 +1,115 @@ +//audio.pulseaudiosimple (2010-01-05) +//author: byuu + +#include +#include + +namespace ruby { + +class pAudioPulseAudioSimple { +public: + struct { + pa_simple *handle; + pa_sample_spec spec; + } device; + + struct { + uint32_t *data; + unsigned offset; + } buffer; + + struct { + unsigned frequency; + } settings; + + bool cap(const string& name) { + if(name == Audio::Frequency) return true; + return false; + } + + any get(const string& name) { + if(name == Audio::Frequency) return settings.frequency; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Audio::Frequency) { + settings.frequency = any_cast(value); + if(device.handle) init(); + return true; + } + + return false; + } + + void sample(uint16_t left, uint16_t right) { + if(!device.handle) return; + + buffer.data[buffer.offset++] = left + (right << 16); + if(buffer.offset >= 64) { + int error; + pa_simple_write(device.handle, (const void*)buffer.data, buffer.offset * sizeof(uint32_t), &error); + buffer.offset = 0; + } + } + + void clear() { + } + + bool init() { + term(); + + device.spec.format = PA_SAMPLE_S16LE; + device.spec.channels = 2; + device.spec.rate = settings.frequency; + + int error = 0; + device.handle = pa_simple_new( + 0, //default server + "ruby::pulseaudiosimple", //application name + PA_STREAM_PLAYBACK, //direction + 0, //default device + "audio", //stream description + &device.spec, //sample format + 0, //default channel map + 0, //default buffering attributes + &error //error code + ); + if(!device.handle) { + fprintf(stderr, "ruby::pulseaudiosimple failed to initialize - %s\n", pa_strerror(error)); + return false; + } + + buffer.data = new uint32_t[64]; + buffer.offset = 0; + return true; + } + + void term() { + if(device.handle) { + int error; + pa_simple_flush(device.handle, &error); + pa_simple_free(device.handle); + device.handle = 0; + } + + if(buffer.data) { + delete[] buffer.data; + buffer.data = 0; + } + } + + pAudioPulseAudioSimple() { + device.handle = 0; + buffer.data = 0; + settings.frequency = 22050; + } + + ~pAudioPulseAudioSimple() { + term(); + } +}; + +DeclareAudio(PulseAudioSimple) + +}; diff --git a/ruby/audio/xaudio2.cpp b/ruby/audio/xaudio2.cpp new file mode 100755 index 00000000..d6298593 --- /dev/null +++ b/ruby/audio/xaudio2.cpp @@ -0,0 +1,200 @@ +/* + audio.xaudio2 (2010-08-14) + author: OV2 +*/ + +#include "xaudio2.hpp" +#include + +namespace ruby { + +class pAudioXAudio2: public IXAudio2VoiceCallback { +public: + IXAudio2 *pXAudio2; + IXAudio2MasteringVoice* pMasterVoice; + IXAudio2SourceVoice *pSourceVoice; + + // inherited from IXAudio2VoiceCallback + STDMETHODIMP_(void) OnBufferStart(void *pBufferContext){} + STDMETHODIMP_(void) OnLoopEnd(void *pBufferContext){} + STDMETHODIMP_(void) OnStreamEnd() {} + STDMETHODIMP_(void) OnVoiceError(void *pBufferContext, HRESULT Error) {} + STDMETHODIMP_(void) OnVoiceProcessingPassEnd() {} + STDMETHODIMP_(void) OnVoiceProcessingPassStart(UINT32 BytesRequired) {} + + struct { + unsigned buffers; + unsigned latency; + + uint32_t *buffer; + unsigned bufferoffset; + + volatile long submitbuffers; + unsigned writebuffer; + } device; + + struct { + bool synchronize; + unsigned frequency; + unsigned latency; + } settings; + + bool cap(const string& name) { + if(name == Audio::Synchronize) return true; + if(name == Audio::Frequency) return true; + if(name == Audio::Latency) return true; + return false; + } + + any get(const string& name) { + if(name == Audio::Synchronize) return settings.synchronize; + if(name == Audio::Frequency) return settings.frequency; + if(name == Audio::Latency) return settings.latency; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Audio::Synchronize) { + settings.synchronize = any_cast(value); + if(pXAudio2) clear(); + return true; + } + + if(name == Audio::Frequency) { + settings.frequency = any_cast(value); + if(pXAudio2) init(); + return true; + } + + if(name == Audio::Latency) { + settings.latency = any_cast(value); + if(pXAudio2) init(); + return true; + } + + return false; + } + + void pushbuffer(unsigned bytes,uint32_t *pAudioData) { + XAUDIO2_BUFFER xa2buffer={0}; + xa2buffer.AudioBytes=bytes; + xa2buffer.pAudioData=reinterpret_cast(pAudioData); + xa2buffer.pContext=0; + InterlockedIncrement(&device.submitbuffers); + pSourceVoice->SubmitSourceBuffer(&xa2buffer); + } + + void sample(uint16_t left, uint16_t right) { + device.buffer[device.writebuffer * device.latency + device.bufferoffset++] = left + (right << 16); + if(device.bufferoffset < device.latency) return; + device.bufferoffset = 0; + + if(device.submitbuffers == device.buffers - 1) { + if(settings.synchronize == true) { + //wait until there is at least one other free buffer for the next sample + while(device.submitbuffers == device.buffers - 1) { + //Sleep(0); + } + } else { //we need one free buffer for the next sample, so ignore the current contents + return; + } + } + + pushbuffer(device.latency * 4,device.buffer + device.writebuffer * device.latency); + + device.writebuffer = (device.writebuffer + 1) % device.buffers; + } + + void clear() { + if(!pSourceVoice) return; + pSourceVoice->Stop(0); + pSourceVoice->FlushSourceBuffers(); //calls OnBufferEnd for all currently submitted buffers + + device.writebuffer = 0; + + device.bufferoffset = 0; + if(device.buffer) memset(device.buffer, 0, device.latency * device.buffers * 4); + + pSourceVoice->Start(0); + } + + bool init() { + term(); + + device.buffers = 8; + device.latency = settings.frequency * settings.latency / device.buffers / 1000.0 + 0.5; + device.buffer = new uint32_t[device.latency * device.buffers]; + device.bufferoffset = 0; + device.submitbuffers = 0; + + HRESULT hr; + if(FAILED(hr = XAudio2Create(&pXAudio2, 0 , XAUDIO2_DEFAULT_PROCESSOR))) { + return false; + } + + if(FAILED(hr = pXAudio2->CreateMasteringVoice( &pMasterVoice, 2, + settings.frequency, 0, 0 , NULL))) { + return false; + } + + WAVEFORMATEX wfx; + wfx.wFormatTag = WAVE_FORMAT_PCM; + wfx.nChannels = 2; + wfx.nSamplesPerSec = settings.frequency; + wfx.nBlockAlign = 4; + wfx.wBitsPerSample = 16; + wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign; + wfx.cbSize = 0; + + if(FAILED(hr = pXAudio2->CreateSourceVoice(&pSourceVoice, (WAVEFORMATEX*)&wfx, + XAUDIO2_VOICE_NOSRC , XAUDIO2_DEFAULT_FREQ_RATIO, this, NULL, NULL))) { + return false; + } + + clear(); + return true; + } + + void term() { + if(pSourceVoice) { + pSourceVoice->Stop(0); + pSourceVoice->DestroyVoice(); + pSourceVoice = 0; + } + if(pMasterVoice) { + pMasterVoice->DestroyVoice(); + pMasterVoice = 0; + } + if(pXAudio2) { + pXAudio2->Release(); + pXAudio2 = NULL; + } + if(device.buffer) { + delete[] device.buffer; + device.buffer = 0; + } + } + + STDMETHODIMP_(void) OnBufferEnd(void *pBufferContext) { + InterlockedDecrement(&device.submitbuffers); + } + + pAudioXAudio2() { + pXAudio2 = 0; + pMasterVoice = 0; + pSourceVoice = 0; + + device.buffer = 0; + device.bufferoffset = 0; + device.submitbuffers = 0; + device.writebuffer = 0; + + settings.synchronize = false; + settings.frequency = 22050; + settings.latency = 120; + } +}; + +DeclareAudio(XAudio2) + +}; diff --git a/ruby/audio/xaudio2.hpp b/ruby/audio/xaudio2.hpp new file mode 100755 index 00000000..e283f503 --- /dev/null +++ b/ruby/audio/xaudio2.hpp @@ -0,0 +1,340 @@ +/* + xaudio2.hpp (2010-08-14) + author: OV2 + + ruby-specific header to provide mingw-friendly xaudio2 interfaces +*/ + +#ifndef XAUDIO2_RUBY_H +#define XAUDIO2_RUBY_H + +//64-bit GCC fix +#define GUID_EXT EXTERN_C +#define GUID_SECT + +#include + +#define DEFINE_GUID_X(n,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) GUID_EXT const GUID n GUID_SECT = {l,w1,w2,{b1,b2,b3,b4,b5,b6,b7,b8}} +#define DEFINE_CLSID_X(className, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + DEFINE_GUID_X(CLSID_##className, 0x##l, 0x##w1, 0x##w2, 0x##b1, 0x##b2, 0x##b3, 0x##b4, 0x##b5, 0x##b6, 0x##b7, 0x##b8) +#define DEFINE_IID_X(interfaceName, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + DEFINE_GUID_X(IID_##interfaceName, 0x##l, 0x##w1, 0x##w2, 0x##b1, 0x##b2, 0x##b3, 0x##b4, 0x##b5, 0x##b6, 0x##b7, 0x##b8) +#define X2DEFAULT(x) =x + +DEFINE_CLSID_X(XAudio2, e21a7345, eb21, 468e, be, 50, 80, 4d, b9, 7c, f7, 08); +DEFINE_CLSID_X(XAudio2_Debug, f7a76c21, 53d4, 46bb, ac, 53, 8b, 45, 9c, ae, 46, bd); +DEFINE_IID_X(IXAudio2, 8bcf1f58, 9fe7, 4583, 8a, c6, e2, ad, c4, 65, c8, bb); + +DECLARE_INTERFACE(IXAudio2Voice); + +#define XAUDIO2_COMMIT_NOW 0 +#define XAUDIO2_DEFAULT_CHANNELS 0 +#define XAUDIO2_DEFAULT_SAMPLERATE 0 +#define XAUDIO2_DEFAULT_FREQ_RATIO 4.0f +#define XAUDIO2_DEBUG_ENGINE 0x0001 +#define XAUDIO2_VOICE_NOSRC 0x0004 + +typedef struct +{ + WAVEFORMATEX Format; + union + { + WORD wValidBitsPerSample; + WORD wSamplesPerBlock; + WORD wReserved; + } Samples; + DWORD dwChannelMask; + GUID SubFormat; +} WAVEFORMATEXTENSIBLE, *PWAVEFORMATEXTENSIBLE, *LPPWAVEFORMATEXTENSIBLE; +typedef const WAVEFORMATEXTENSIBLE* LPCWAVEFORMATEXTENSIBLE; + +typedef enum XAUDIO2_DEVICE_ROLE +{ + NotDefaultDevice = 0x0, + DefaultConsoleDevice = 0x1, + DefaultMultimediaDevice = 0x2, + DefaultCommunicationsDevice = 0x4, + DefaultGameDevice = 0x8, + GlobalDefaultDevice = 0xf, + InvalidDeviceRole = ~GlobalDefaultDevice +} XAUDIO2_DEVICE_ROLE; + +typedef struct XAUDIO2_DEVICE_DETAILS +{ + WCHAR DeviceID[256]; + WCHAR DisplayName[256]; + XAUDIO2_DEVICE_ROLE Role; + WAVEFORMATEXTENSIBLE OutputFormat; +} XAUDIO2_DEVICE_DETAILS; + +typedef struct XAUDIO2_VOICE_DETAILS +{ + UINT32 CreationFlags; + UINT32 InputChannels; + UINT32 InputSampleRate; +} XAUDIO2_VOICE_DETAILS; + +typedef enum XAUDIO2_WINDOWS_PROCESSOR_SPECIFIER +{ + Processor1 = 0x00000001, + Processor2 = 0x00000002, + Processor3 = 0x00000004, + Processor4 = 0x00000008, + Processor5 = 0x00000010, + Processor6 = 0x00000020, + Processor7 = 0x00000040, + Processor8 = 0x00000080, + Processor9 = 0x00000100, + Processor10 = 0x00000200, + Processor11 = 0x00000400, + Processor12 = 0x00000800, + Processor13 = 0x00001000, + Processor14 = 0x00002000, + Processor15 = 0x00004000, + Processor16 = 0x00008000, + Processor17 = 0x00010000, + Processor18 = 0x00020000, + Processor19 = 0x00040000, + Processor20 = 0x00080000, + Processor21 = 0x00100000, + Processor22 = 0x00200000, + Processor23 = 0x00400000, + Processor24 = 0x00800000, + Processor25 = 0x01000000, + Processor26 = 0x02000000, + Processor27 = 0x04000000, + Processor28 = 0x08000000, + Processor29 = 0x10000000, + Processor30 = 0x20000000, + Processor31 = 0x40000000, + Processor32 = 0x80000000, + XAUDIO2_ANY_PROCESSOR = 0xffffffff, + XAUDIO2_DEFAULT_PROCESSOR = XAUDIO2_ANY_PROCESSOR +} XAUDIO2_WINDOWS_PROCESSOR_SPECIFIER, XAUDIO2_PROCESSOR; + +typedef struct XAUDIO2_VOICE_SENDS +{ + UINT32 OutputCount; + IXAudio2Voice** pOutputVoices; +} XAUDIO2_VOICE_SENDS; + +typedef struct XAUDIO2_EFFECT_DESCRIPTOR +{ + IUnknown* pEffect; + BOOL InitialState; + UINT32 OutputChannels; +} XAUDIO2_EFFECT_DESCRIPTOR; + +typedef struct XAUDIO2_EFFECT_CHAIN +{ + UINT32 EffectCount; + const XAUDIO2_EFFECT_DESCRIPTOR* pEffectDescriptors; +} XAUDIO2_EFFECT_CHAIN; + +typedef enum XAUDIO2_FILTER_TYPE +{ + LowPassFilter, + BandPassFilter, + HighPassFilter +} XAUDIO2_FILTER_TYPE; + +typedef struct XAUDIO2_FILTER_PARAMETERS +{ + XAUDIO2_FILTER_TYPE Type; + float Frequency; + float OneOverQ; + +} XAUDIO2_FILTER_PARAMETERS; + +typedef struct XAUDIO2_BUFFER +{ + UINT32 Flags; + UINT32 AudioBytes; + const BYTE* pAudioData; + UINT32 PlayBegin; + UINT32 PlayLength; + UINT32 LoopBegin; + UINT32 LoopLength; + UINT32 LoopCount; + void* pContext; +} XAUDIO2_BUFFER; + +typedef struct XAUDIO2_BUFFER_WMA +{ + const UINT32* pDecodedPacketCumulativeBytes; + UINT32 PacketCount; +} XAUDIO2_BUFFER_WMA; + +typedef struct XAUDIO2_VOICE_STATE +{ + void* pCurrentBufferContext; + UINT32 BuffersQueued; + UINT64 SamplesPlayed; +} XAUDIO2_VOICE_STATE; + +typedef struct XAUDIO2_PERFORMANCE_DATA +{ + UINT64 AudioCyclesSinceLastQuery; + UINT64 TotalCyclesSinceLastQuery; + UINT32 MinimumCyclesPerQuantum; + UINT32 MaximumCyclesPerQuantum; + UINT32 MemoryUsageInBytes; + UINT32 CurrentLatencyInSamples; + UINT32 GlitchesSinceEngineStarted; + UINT32 ActiveSourceVoiceCount; + UINT32 TotalSourceVoiceCount; + UINT32 ActiveSubmixVoiceCount; + UINT32 TotalSubmixVoiceCount; + UINT32 ActiveXmaSourceVoices; + UINT32 ActiveXmaStreams; +} XAUDIO2_PERFORMANCE_DATA; + +typedef struct XAUDIO2_DEBUG_CONFIGURATION +{ + UINT32 TraceMask; + UINT32 BreakMask; + BOOL LogThreadID; + BOOL LogFileline; + BOOL LogFunctionName; + BOOL LogTiming; +} XAUDIO2_DEBUG_CONFIGURATION; + +DECLARE_INTERFACE(IXAudio2EngineCallback) +{ + STDMETHOD_(void, OnProcessingPassStart) (THIS) PURE; + STDMETHOD_(void, OnProcessingPassEnd) (THIS) PURE; + STDMETHOD_(void, OnCriticalError) (THIS_ HRESULT Error) PURE; +}; + +DECLARE_INTERFACE(IXAudio2VoiceCallback) +{ + STDMETHOD_(void, OnVoiceProcessingPassStart) (THIS_ UINT32 BytesRequired) PURE; + STDMETHOD_(void, OnVoiceProcessingPassEnd) (THIS) PURE; + STDMETHOD_(void, OnStreamEnd) (THIS) PURE; + STDMETHOD_(void, OnBufferStart) (THIS_ void* pBufferContext) PURE; + STDMETHOD_(void, OnBufferEnd) (THIS_ void* pBufferContext) PURE; + STDMETHOD_(void, OnLoopEnd) (THIS_ void* pBufferContext) PURE; + STDMETHOD_(void, OnVoiceError) (THIS_ void* pBufferContext, HRESULT Error) PURE; +}; + +DECLARE_INTERFACE(IXAudio2Voice) +{ + #define Declare_IXAudio2Voice_Methods() \ + STDMETHOD_(void, GetVoiceDetails) (THIS_ XAUDIO2_VOICE_DETAILS* pVoiceDetails) PURE; \ + STDMETHOD(SetOutputVoices) (THIS_ const XAUDIO2_VOICE_SENDS* pSendList) PURE; \ + STDMETHOD(SetEffectChain) (THIS_ const XAUDIO2_EFFECT_CHAIN* pEffectChain) PURE; \ + STDMETHOD(EnableEffect) (THIS_ UINT32 EffectIndex, \ + UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \ + STDMETHOD(DisableEffect) (THIS_ UINT32 EffectIndex, \ + UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \ + STDMETHOD_(void, GetEffectState) (THIS_ UINT32 EffectIndex, BOOL* pEnabled) PURE; \ + STDMETHOD(SetEffectParameters) (THIS_ UINT32 EffectIndex, \ + const void* pParameters, \ + UINT32 ParametersByteSize, \ + UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \ + STDMETHOD(GetEffectParameters) (THIS_ UINT32 EffectIndex, void* pParameters, \ + UINT32 ParametersByteSize) PURE; \ + STDMETHOD(SetFilterParameters) (THIS_ const XAUDIO2_FILTER_PARAMETERS* pParameters, \ + UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \ + STDMETHOD_(void, GetFilterParameters) (THIS_ XAUDIO2_FILTER_PARAMETERS* pParameters) PURE; \ + STDMETHOD(SetVolume) (THIS_ float Volume, \ + UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \ + STDMETHOD_(void, GetVolume) (THIS_ float* pVolume) PURE; \ + STDMETHOD(SetChannelVolumes) (THIS_ UINT32 Channels, const float* pVolumes, \ + UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \ + STDMETHOD_(void, GetChannelVolumes) (THIS_ UINT32 Channels, float* pVolumes) PURE; \ + STDMETHOD(SetOutputMatrix) (THIS_ IXAudio2Voice* pDestinationVoice, \ + UINT32 SourceChannels, UINT32 DestinationChannels, \ + const float* pLevelMatrix, \ + UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \ + STDMETHOD_(void, GetOutputMatrix) (THIS_ IXAudio2Voice* pDestinationVoice, \ + UINT32 SourceChannels, UINT32 DestinationChannels, \ + float* pLevelMatrix) PURE; \ + STDMETHOD_(void, DestroyVoice) (THIS) PURE + + Declare_IXAudio2Voice_Methods(); +}; + + +DECLARE_INTERFACE_(IXAudio2MasteringVoice, IXAudio2Voice) +{ + Declare_IXAudio2Voice_Methods(); +}; + +DECLARE_INTERFACE_(IXAudio2SubmixVoice, IXAudio2Voice) +{ + Declare_IXAudio2Voice_Methods(); +}; + +DECLARE_INTERFACE_(IXAudio2SourceVoice, IXAudio2Voice) +{ + Declare_IXAudio2Voice_Methods(); + STDMETHOD(Start) (THIS_ UINT32 Flags, UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; + STDMETHOD(Stop) (THIS_ UINT32 Flags, UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; + STDMETHOD(SubmitSourceBuffer) (THIS_ const XAUDIO2_BUFFER* pBuffer, const XAUDIO2_BUFFER_WMA* pBufferWMA X2DEFAULT(NULL)) PURE; + STDMETHOD(FlushSourceBuffers) (THIS) PURE; + STDMETHOD(Discontinuity) (THIS) PURE; + STDMETHOD(ExitLoop) (THIS_ UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; + STDMETHOD_(void, GetState) (THIS_ XAUDIO2_VOICE_STATE* pVoiceState) PURE; + STDMETHOD(SetFrequencyRatio) (THIS_ float Ratio, + UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; + STDMETHOD_(void, GetFrequencyRatio) (THIS_ float* pRatio) PURE; +}; + +DECLARE_INTERFACE_(IXAudio2, IUnknown) +{ + STDMETHOD(QueryInterface) (THIS_ REFIID riid, void** ppvInterface) PURE; + STDMETHOD_(ULONG, AddRef) (THIS) PURE; + STDMETHOD_(ULONG, Release) (THIS) PURE; + STDMETHOD(GetDeviceCount) (THIS_ UINT32* pCount) PURE; + STDMETHOD(GetDeviceDetails) (THIS_ UINT32 Index, XAUDIO2_DEVICE_DETAILS* pDeviceDetails) PURE; + STDMETHOD(Initialize) (THIS_ UINT32 Flags X2DEFAULT(0), + XAUDIO2_PROCESSOR XAudio2Processor X2DEFAULT(XAUDIO2_DEFAULT_PROCESSOR)) PURE; + STDMETHOD(RegisterForCallbacks) (IXAudio2EngineCallback* pCallback) PURE; + STDMETHOD_(void, UnregisterForCallbacks) (IXAudio2EngineCallback* pCallback) PURE; + STDMETHOD(CreateSourceVoice) (THIS_ IXAudio2SourceVoice** ppSourceVoice, + const WAVEFORMATEX* pSourceFormat, + UINT32 Flags X2DEFAULT(0), + float MaxFrequencyRatio X2DEFAULT(XAUDIO2_DEFAULT_FREQ_RATIO), + IXAudio2VoiceCallback* pCallback X2DEFAULT(NULL), + const XAUDIO2_VOICE_SENDS* pSendList X2DEFAULT(NULL), + const XAUDIO2_EFFECT_CHAIN* pEffectChain X2DEFAULT(NULL)) PURE; + STDMETHOD(CreateSubmixVoice) (THIS_ IXAudio2SubmixVoice** ppSubmixVoice, + UINT32 InputChannels, UINT32 InputSampleRate, + UINT32 Flags X2DEFAULT(0), UINT32 ProcessingStage X2DEFAULT(0), + const XAUDIO2_VOICE_SENDS* pSendList X2DEFAULT(NULL), + const XAUDIO2_EFFECT_CHAIN* pEffectChain X2DEFAULT(NULL)) PURE; + STDMETHOD(CreateMasteringVoice) (THIS_ IXAudio2MasteringVoice** ppMasteringVoice, + UINT32 InputChannels X2DEFAULT(XAUDIO2_DEFAULT_CHANNELS), + UINT32 InputSampleRate X2DEFAULT(XAUDIO2_DEFAULT_SAMPLERATE), + UINT32 Flags X2DEFAULT(0), UINT32 DeviceIndex X2DEFAULT(0), + const XAUDIO2_EFFECT_CHAIN* pEffectChain X2DEFAULT(NULL)) PURE; + STDMETHOD(StartEngine) (THIS) PURE; + STDMETHOD_(void, StopEngine) (THIS) PURE; + STDMETHOD(CommitChanges) (THIS_ UINT32 OperationSet) PURE; + STDMETHOD_(void, GetPerformanceData) (THIS_ XAUDIO2_PERFORMANCE_DATA* pPerfData) PURE; + STDMETHOD_(void, SetDebugConfiguration) (THIS_ const XAUDIO2_DEBUG_CONFIGURATION* pDebugConfiguration, + void* pReserved X2DEFAULT(NULL)) PURE; +}; + +__inline HRESULT XAudio2Create(IXAudio2** ppXAudio2, UINT32 Flags X2DEFAULT(0), + XAUDIO2_PROCESSOR XAudio2Processor X2DEFAULT(XAUDIO2_DEFAULT_PROCESSOR)) +{ + IXAudio2* pXAudio2; + HRESULT hr = CoCreateInstance((Flags & XAUDIO2_DEBUG_ENGINE) ? CLSID_XAudio2_Debug : CLSID_XAudio2, + NULL, CLSCTX_INPROC_SERVER, IID_IXAudio2, (void**)&pXAudio2); + if (SUCCEEDED(hr)) + { + hr = pXAudio2->Initialize(Flags, XAudio2Processor); + if (SUCCEEDED(hr)) + { + *ppXAudio2 = pXAudio2; + } + else + { + pXAudio2->Release(); + } + } + return hr; +} +#endif diff --git a/ruby/input.hpp b/ruby/input.hpp new file mode 100755 index 00000000..5334c4da --- /dev/null +++ b/ruby/input.hpp @@ -0,0 +1,22 @@ +class Input { +public: + static const char *Handle; + static const char *KeyboardSupport; + static const char *MouseSupport; + static const char *JoypadSupport; + + virtual bool cap(const nall::string& name) { return false; } + virtual nall::any get(const nall::string& name) { return false; } + virtual bool set(const nall::string& name, const nall::any& value) { return false; } + + virtual bool acquire() { return false; } + virtual bool unacquire() { return false; } + virtual bool acquired() { return false; } + + virtual bool poll(int16_t *table) { return false; } + virtual bool init() { return true; } + virtual void term() {} + + Input() {} + virtual ~Input() {} +}; diff --git a/ruby/input/carbon.cpp b/ruby/input/carbon.cpp new file mode 100755 index 00000000..c5191f4b --- /dev/null +++ b/ruby/input/carbon.cpp @@ -0,0 +1,157 @@ +namespace ruby { + +class pInputCarbon { +public: + bool cap(const string& name) { + return false; + } + + any get(const string& name) { + return false; + } + + bool set(const string& name, const any& value) { + return false; + } + + bool acquire() { return false; } + bool unacquire() { return false; } + bool acquired() { return false; } + + bool poll(int16_t *table) { + memset(table, 0, Scancode::Limit * sizeof(int16_t)); + + KeyMap keys; + GetKeys(keys); + uint8_t *keymap = (uint8_t*)keys; + + #define map(id, name) table[keyboard(0)[name]] = (bool)(keymap[id >> 3] & (1 << (id & 7))) + map(0x35, Keyboard::Escape); + + map(0x7a, Keyboard::F1); + map(0x78, Keyboard::F2); + map(0x63, Keyboard::F3); + map(0x76, Keyboard::F4); + map(0x60, Keyboard::F5); + map(0x61, Keyboard::F6); + map(0x62, Keyboard::F7); + map(0x64, Keyboard::F8); + map(0x65, Keyboard::F9); + map(0x6d, Keyboard::F10); + map(0x67, Keyboard::F11); + //map(0x??, Keyboard::F12); + + map(0x69, Keyboard::PrintScreen); + //map(0x??, Keyboard::ScrollLock); + map(0x71, Keyboard::Pause); + + map(0x32, Keyboard::Tilde); + map(0x12, Keyboard::Num1); + map(0x13, Keyboard::Num2); + map(0x14, Keyboard::Num3); + map(0x15, Keyboard::Num4); + map(0x17, Keyboard::Num5); + map(0x16, Keyboard::Num6); + map(0x1a, Keyboard::Num7); + map(0x1c, Keyboard::Num8); + map(0x19, Keyboard::Num9); + map(0x1d, Keyboard::Num0); + + map(0x1b, Keyboard::Dash); + map(0x18, Keyboard::Equal); + map(0x33, Keyboard::Backspace); + + map(0x72, Keyboard::Insert); + map(0x75, Keyboard::Delete); + map(0x73, Keyboard::Home); + map(0x77, Keyboard::End); + map(0x74, Keyboard::PageUp); + map(0x79, Keyboard::PageDown); + + map(0x00, Keyboard::A); + map(0x0b, Keyboard::B); + map(0x08, Keyboard::C); + map(0x02, Keyboard::D); + map(0x0e, Keyboard::E); + map(0x03, Keyboard::F); + map(0x05, Keyboard::G); + map(0x04, Keyboard::H); + map(0x22, Keyboard::I); + map(0x26, Keyboard::J); + map(0x28, Keyboard::K); + map(0x25, Keyboard::L); + map(0x2e, Keyboard::M); + map(0x2d, Keyboard::N); + map(0x1f, Keyboard::O); + map(0x23, Keyboard::P); + map(0x0c, Keyboard::Q); + map(0x0f, Keyboard::R); + map(0x01, Keyboard::S); + map(0x11, Keyboard::T); + map(0x20, Keyboard::U); + map(0x09, Keyboard::V); + map(0x0d, Keyboard::W); + map(0x07, Keyboard::X); + map(0x10, Keyboard::Y); + map(0x06, Keyboard::Z); + + map(0x21, Keyboard::LeftBracket); + map(0x1e, Keyboard::RightBracket); + map(0x2a, Keyboard::Backslash); + map(0x29, Keyboard::Semicolon); + map(0x27, Keyboard::Apostrophe); + map(0x2b, Keyboard::Comma); + map(0x2f, Keyboard::Period); + map(0x2c, Keyboard::Slash); + + map(0x53, Keyboard::Keypad1); + map(0x54, Keyboard::Keypad2); + map(0x55, Keyboard::Keypad3); + map(0x56, Keyboard::Keypad4); + map(0x57, Keyboard::Keypad5); + map(0x58, Keyboard::Keypad6); + map(0x59, Keyboard::Keypad7); + map(0x5b, Keyboard::Keypad8); + map(0x5c, Keyboard::Keypad9); + map(0x52, Keyboard::Keypad0); + + //map(0x??, Keyboard::Point); + map(0x4c, Keyboard::Enter); + map(0x45, Keyboard::Add); + map(0x4e, Keyboard::Subtract); + map(0x43, Keyboard::Multiply); + map(0x4b, Keyboard::Divide); + + map(0x47, Keyboard::NumLock); + //map(0x39, Keyboard::CapsLock); + + map(0x7e, Keyboard::Up); + map(0x7d, Keyboard::Down); + map(0x7b, Keyboard::Left); + map(0x7c, Keyboard::Right); + + map(0x30, Keyboard::Tab); + map(0x24, Keyboard::Return); + map(0x31, Keyboard::Spacebar); + //map(0x??, Keyboard::Menu); + + map(0x38, Keyboard::Shift); + map(0x3b, Keyboard::Control); + map(0x3a, Keyboard::Alt); + map(0x37, Keyboard::Super); + #undef map + + return true; + } + + bool init() { + return true; + } + + void term() { + } +}; + +DeclareInput(Carbon) + +}; diff --git a/ruby/input/directinput.cpp b/ruby/input/directinput.cpp new file mode 100755 index 00000000..6db107b2 --- /dev/null +++ b/ruby/input/directinput.cpp @@ -0,0 +1,387 @@ +#define DIRECTINPUT_VERSION 0x0800 +#include + +namespace ruby { + +static BOOL CALLBACK DI_EnumJoypadsCallback(const DIDEVICEINSTANCE*, void*); +static BOOL CALLBACK DI_EnumJoypadAxesCallback(const DIDEVICEOBJECTINSTANCE*, void*); + +using namespace nall; + +class pInputDI { +public: + struct { + LPDIRECTINPUT8 context; + LPDIRECTINPUTDEVICE8 keyboard; + LPDIRECTINPUTDEVICE8 mouse; + LPDIRECTINPUTDEVICE8 gamepad[Joypad::Count]; + bool mouseacquired; + } device; + + struct { + HWND handle; + } settings; + + bool cap(const string& name) { + if(name == Input::Handle) return true; + if(name == Input::KeyboardSupport) return true; + if(name == Input::MouseSupport) return true; + if(name == Input::JoypadSupport) return true; + return false; + } + + any get(const string& name) { + if(name == Input::Handle) return (uintptr_t)settings.handle; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Input::Handle) { + settings.handle = (HWND)any_cast(value); + return true; + } + + return false; + } + + bool poll(int16_t *table) { + memset(table, 0, Scancode::Limit * sizeof(int16_t)); + + //======== + //Keyboard + //======== + + if(device.keyboard) { + uint8_t state[256]; + if(FAILED(device.keyboard->GetDeviceState(sizeof state, state))) { + device.keyboard->Acquire(); + if(FAILED(device.keyboard->GetDeviceState(sizeof state, state))) { + memset(state, 0, sizeof state); + } + } + + #define key(id) table[keyboard(0)[id]] + + key(Keyboard::Escape) = (bool)(state[0x01] & 0x80); + key(Keyboard::F1 ) = (bool)(state[0x3b] & 0x80); + key(Keyboard::F2 ) = (bool)(state[0x3c] & 0x80); + key(Keyboard::F3 ) = (bool)(state[0x3d] & 0x80); + key(Keyboard::F4 ) = (bool)(state[0x3e] & 0x80); + key(Keyboard::F5 ) = (bool)(state[0x3f] & 0x80); + key(Keyboard::F6 ) = (bool)(state[0x40] & 0x80); + key(Keyboard::F7 ) = (bool)(state[0x41] & 0x80); + key(Keyboard::F8 ) = (bool)(state[0x42] & 0x80); + key(Keyboard::F9 ) = (bool)(state[0x43] & 0x80); + key(Keyboard::F10 ) = (bool)(state[0x44] & 0x80); + key(Keyboard::F11 ) = (bool)(state[0x57] & 0x80); + key(Keyboard::F12 ) = (bool)(state[0x58] & 0x80); + + key(Keyboard::PrintScreen) = (bool)(state[0xb7] & 0x80); + key(Keyboard::ScrollLock ) = (bool)(state[0x46] & 0x80); + key(Keyboard::Pause ) = (bool)(state[0xc5] & 0x80); + key(Keyboard::Tilde ) = (bool)(state[0x29] & 0x80); + + key(Keyboard::Num1) = (bool)(state[0x02] & 0x80); + key(Keyboard::Num2) = (bool)(state[0x03] & 0x80); + key(Keyboard::Num3) = (bool)(state[0x04] & 0x80); + key(Keyboard::Num4) = (bool)(state[0x05] & 0x80); + key(Keyboard::Num5) = (bool)(state[0x06] & 0x80); + key(Keyboard::Num6) = (bool)(state[0x07] & 0x80); + key(Keyboard::Num7) = (bool)(state[0x08] & 0x80); + key(Keyboard::Num8) = (bool)(state[0x09] & 0x80); + key(Keyboard::Num9) = (bool)(state[0x0a] & 0x80); + key(Keyboard::Num0) = (bool)(state[0x0b] & 0x80); + + key(Keyboard::Dash ) = (bool)(state[0x0c] & 0x80); + key(Keyboard::Equal ) = (bool)(state[0x0d] & 0x80); + key(Keyboard::Backspace) = (bool)(state[0x0e] & 0x80); + + key(Keyboard::Insert ) = (bool)(state[0xd2] & 0x80); + key(Keyboard::Delete ) = (bool)(state[0xd3] & 0x80); + key(Keyboard::Home ) = (bool)(state[0xc7] & 0x80); + key(Keyboard::End ) = (bool)(state[0xcf] & 0x80); + key(Keyboard::PageUp ) = (bool)(state[0xc9] & 0x80); + key(Keyboard::PageDown) = (bool)(state[0xd1] & 0x80); + + key(Keyboard::A) = (bool)(state[0x1e] & 0x80); + key(Keyboard::B) = (bool)(state[0x30] & 0x80); + key(Keyboard::C) = (bool)(state[0x2e] & 0x80); + key(Keyboard::D) = (bool)(state[0x20] & 0x80); + key(Keyboard::E) = (bool)(state[0x12] & 0x80); + key(Keyboard::F) = (bool)(state[0x21] & 0x80); + key(Keyboard::G) = (bool)(state[0x22] & 0x80); + key(Keyboard::H) = (bool)(state[0x23] & 0x80); + key(Keyboard::I) = (bool)(state[0x17] & 0x80); + key(Keyboard::J) = (bool)(state[0x24] & 0x80); + key(Keyboard::K) = (bool)(state[0x25] & 0x80); + key(Keyboard::L) = (bool)(state[0x26] & 0x80); + key(Keyboard::M) = (bool)(state[0x32] & 0x80); + key(Keyboard::N) = (bool)(state[0x31] & 0x80); + key(Keyboard::O) = (bool)(state[0x18] & 0x80); + key(Keyboard::P) = (bool)(state[0x19] & 0x80); + key(Keyboard::Q) = (bool)(state[0x10] & 0x80); + key(Keyboard::R) = (bool)(state[0x13] & 0x80); + key(Keyboard::S) = (bool)(state[0x1f] & 0x80); + key(Keyboard::T) = (bool)(state[0x14] & 0x80); + key(Keyboard::U) = (bool)(state[0x16] & 0x80); + key(Keyboard::V) = (bool)(state[0x2f] & 0x80); + key(Keyboard::W) = (bool)(state[0x11] & 0x80); + key(Keyboard::X) = (bool)(state[0x2d] & 0x80); + key(Keyboard::Y) = (bool)(state[0x15] & 0x80); + key(Keyboard::Z) = (bool)(state[0x2c] & 0x80); + + key(Keyboard::LeftBracket ) = (bool)(state[0x1a] & 0x80); + key(Keyboard::RightBracket) = (bool)(state[0x1b] & 0x80); + key(Keyboard::Backslash ) = (bool)(state[0x2b] & 0x80); + key(Keyboard::Semicolon ) = (bool)(state[0x27] & 0x80); + key(Keyboard::Apostrophe ) = (bool)(state[0x28] & 0x80); + key(Keyboard::Comma ) = (bool)(state[0x33] & 0x80); + key(Keyboard::Period ) = (bool)(state[0x34] & 0x80); + key(Keyboard::Slash ) = (bool)(state[0x35] & 0x80); + + key(Keyboard::Keypad1) = (bool)(state[0x4f] & 0x80); + key(Keyboard::Keypad2) = (bool)(state[0x50] & 0x80); + key(Keyboard::Keypad3) = (bool)(state[0x51] & 0x80); + key(Keyboard::Keypad4) = (bool)(state[0x4b] & 0x80); + key(Keyboard::Keypad5) = (bool)(state[0x4c] & 0x80); + key(Keyboard::Keypad6) = (bool)(state[0x4d] & 0x80); + key(Keyboard::Keypad7) = (bool)(state[0x47] & 0x80); + key(Keyboard::Keypad8) = (bool)(state[0x48] & 0x80); + key(Keyboard::Keypad9) = (bool)(state[0x49] & 0x80); + key(Keyboard::Keypad0) = (bool)(state[0x52] & 0x80); + key(Keyboard::Point ) = (bool)(state[0x53] & 0x80); + + key(Keyboard::Add ) = (bool)(state[0x4e] & 0x80); + key(Keyboard::Subtract) = (bool)(state[0x4a] & 0x80); + key(Keyboard::Multiply) = (bool)(state[0x37] & 0x80); + key(Keyboard::Divide ) = (bool)(state[0xb5] & 0x80); + key(Keyboard::Enter ) = (bool)(state[0x9c] & 0x80); + + key(Keyboard::NumLock ) = (bool)(state[0x45] & 0x80); + key(Keyboard::CapsLock) = (bool)(state[0x3a] & 0x80); + + key(Keyboard::Up ) = (bool)(state[0xc8] & 0x80); + key(Keyboard::Down ) = (bool)(state[0xd0] & 0x80); + key(Keyboard::Left ) = (bool)(state[0xcb] & 0x80); + key(Keyboard::Right) = (bool)(state[0xcd] & 0x80); + + key(Keyboard::Tab ) = (bool)(state[0x0f] & 0x80); + key(Keyboard::Return ) = (bool)(state[0x1c] & 0x80); + key(Keyboard::Spacebar) = (bool)(state[0x39] & 0x80); + key(Keyboard::Menu ) = (bool)(state[0xdd] & 0x80); + + key(Keyboard::Shift ) = (bool)(state[0x2a] & 0x80) || (bool)(state[0x36] & 0x80); + key(Keyboard::Control) = (bool)(state[0x1d] & 0x80) || (bool)(state[0x9d] & 0x80); + key(Keyboard::Alt ) = (bool)(state[0x38] & 0x80) || (bool)(state[0xb8] & 0x80); + key(Keyboard::Super ) = (bool)(state[0xdb] & 0x80) || (bool)(state[0xdc] & 0x80); + + #undef key + } + + //===== + //Mouse + //===== + + if(device.mouse) { + DIMOUSESTATE2 state; + if(FAILED(device.mouse->GetDeviceState(sizeof(DIMOUSESTATE2), (void*)&state))) { + device.mouse->Acquire(); + if(FAILED(device.mouse->GetDeviceState(sizeof(DIMOUSESTATE2), (void*)&state))) { + memset(&state, 0, sizeof(DIMOUSESTATE2)); + } + } + + table[mouse(0).axis(0)] = state.lX; + table[mouse(0).axis(1)] = state.lY; + table[mouse(0).axis(2)] = state.lZ / WHEEL_DELTA; + for(unsigned n = 0; n < Mouse::Buttons; n++) { + table[mouse(0).button(n)] = (bool)state.rgbButtons[n]; + } + + //on Windows, 0 = left, 1 = right, 2 = middle + //swap middle and right buttons for consistency with Linux + int16_t temp = table[mouse(0).button(1)]; + table[mouse(0).button(1)] = table[mouse(0).button(2)]; + table[mouse(0).button(2)] = temp; + } + + //========= + //Joypad(s) + //========= + + for(unsigned i = 0; i < Joypad::Count; i++) { + if(!device.gamepad[i]) continue; + + if(FAILED(device.gamepad[i]->Poll())) { + device.gamepad[i]->Acquire(); + continue; + } + + DIJOYSTATE2 state; + device.gamepad[i]->GetDeviceState(sizeof(DIJOYSTATE2), &state); + + //POV hats + for(unsigned n = 0; n < min((unsigned)Joypad::Hats, 4); n++) { + //POV value is in clockwise-hundredth degree units. + unsigned pov = state.rgdwPOV[n]; + //some drivers report a centered POV hat as -1U, others as 65535U. + //>= 36000 will match both, as well as invalid ranges. + if(pov < 36000) { + if(pov >= 31500 || pov <= 4500) table[joypad(i).hat(n)] |= Joypad::HatUp; + if(pov >= 4500 && pov <= 13500) table[joypad(i).hat(n)] |= Joypad::HatRight; + if(pov >= 13500 && pov <= 22500) table[joypad(i).hat(n)] |= Joypad::HatDown; + if(pov >= 22500 && pov <= 31500) table[joypad(i).hat(n)] |= Joypad::HatLeft; + } + } + + //axes + table[joypad(i).axis(0)] = state.lX; + table[joypad(i).axis(1)] = state.lY; + table[joypad(i).axis(2)] = state.lZ; + table[joypad(i).axis(3)] = state.lRx; + table[joypad(i).axis(4)] = state.lRy; + table[joypad(i).axis(5)] = state.lRz; + + //buttons + for(unsigned n = 0; n < min((unsigned)Joypad::Buttons, 128); n++) { + table[joypad(i).button(n)] = (bool)state.rgbButtons[n]; + } + } + + return true; + } + + bool init_joypad(const DIDEVICEINSTANCE *instance) { + unsigned n; + for(n = 0; n < Joypad::Count; n++) { if(!device.gamepad[n]) break; } + if(n >= Joypad::Count) return DIENUM_STOP; + + if(FAILED(device.context->CreateDevice(instance->guidInstance, &device.gamepad[n], 0))) { + return DIENUM_CONTINUE; //continue and try next gamepad + } + + device.gamepad[n]->SetDataFormat(&c_dfDIJoystick2); + device.gamepad[n]->SetCooperativeLevel(settings.handle, DISCL_NONEXCLUSIVE | DISCL_BACKGROUND); + device.gamepad[n]->EnumObjects(DI_EnumJoypadAxesCallback, (void*)this, DIDFT_ABSAXIS); + + return DIENUM_CONTINUE; + } + + bool init_axis(const DIDEVICEOBJECTINSTANCE *instance) { + signed n; + for(n = Joypad::Count - 1; n >= 0; n--) { if(device.gamepad[n]) break; } + if(n < 0) return DIENUM_STOP; + + DIPROPRANGE range; + range.diph.dwSize = sizeof(DIPROPRANGE); + range.diph.dwHeaderSize = sizeof(DIPROPHEADER); + range.diph.dwHow = DIPH_BYID; + range.diph.dwObj = instance->dwType; + range.lMin = -32768; + range.lMax = +32767; + device.gamepad[n]->SetProperty(DIPROP_RANGE, &range.diph); + + return DIENUM_CONTINUE; + } + + bool init() { + device.context = 0; + device.keyboard = 0; + device.mouse = 0; + for(unsigned i = 0; i < Joypad::Count; i++) device.gamepad[i] = 0; + device.mouseacquired = false; + + DirectInput8Create(GetModuleHandle(0), 0x0800, IID_IDirectInput8, (void**)&device.context, 0); + + device.context->CreateDevice(GUID_SysKeyboard, &device.keyboard, 0); + device.keyboard->SetDataFormat(&c_dfDIKeyboard); + device.keyboard->SetCooperativeLevel(settings.handle, DISCL_NONEXCLUSIVE | DISCL_BACKGROUND); + device.keyboard->Acquire(); + + device.context->CreateDevice(GUID_SysMouse, &device.mouse, 0); + device.mouse->SetDataFormat(&c_dfDIMouse2); + HRESULT hr = device.mouse->SetCooperativeLevel(settings.handle, DISCL_NONEXCLUSIVE | DISCL_BACKGROUND); + device.mouse->Acquire(); + + device.context->EnumDevices(DI8DEVCLASS_GAMECTRL, DI_EnumJoypadsCallback, (void*)this, DIEDFL_ATTACHEDONLY); + + return true; + } + + void term() { + if(device.keyboard) { + device.keyboard->Unacquire(); + device.keyboard->Release(); + device.keyboard = 0; + } + + if(device.mouse) { + device.mouse->Unacquire(); + device.mouse->Release(); + device.mouse = 0; + } + + for(unsigned i = 0; i < Joypad::Count; i++) { + if(device.gamepad[i]) { + device.gamepad[i]->Unacquire(); + device.gamepad[i]->Release(); + device.gamepad[i] = 0; + } + } + + if(device.context) { + device.context->Release(); + device.context = 0; + } + } + + bool acquire() { + if(!device.mouse) return false; + if(acquired() == false) { + device.mouse->Unacquire(); + device.mouse->SetCooperativeLevel(settings.handle, DISCL_EXCLUSIVE | DISCL_FOREGROUND); + device.mouse->Acquire(); + device.mouseacquired = true; + } + return true; + } + + bool unacquire() { + if(!device.mouse) return false; + if(acquired() == true) { + device.mouse->Unacquire(); + device.mouse->SetCooperativeLevel(settings.handle, DISCL_NONEXCLUSIVE | DISCL_BACKGROUND); + device.mouse->Acquire(); + device.mouseacquired = false; + } + return true; + } + + bool acquired() { + return device.mouseacquired; + } + + pInputDI() { + device.context = 0; + device.keyboard = 0; + device.mouse = 0; + for(unsigned i = 0; i < Joypad::Count; i++) device.gamepad[i] = 0; + device.mouseacquired = false; + + settings.handle = 0; + } + + ~pInputDI() { term(); } +}; + +BOOL CALLBACK DI_EnumJoypadsCallback(const DIDEVICEINSTANCE *instance, void *p) { + return ((pInputDI*)p)->init_joypad(instance); +} + +BOOL CALLBACK DI_EnumJoypadAxesCallback(const DIDEVICEOBJECTINSTANCE *instance, void *p) { + return ((pInputDI*)p)->init_axis(instance); +} + +DeclareInput(DI) + +}; diff --git a/ruby/input/rawinput.cpp b/ruby/input/rawinput.cpp new file mode 100755 index 00000000..bba81286 --- /dev/null +++ b/ruby/input/rawinput.cpp @@ -0,0 +1,807 @@ +//RawInput driver +//author: byuu + +//this driver utilizes RawInput (WM_INPUT) to capture keyboard and mouse input. +//although this requires WinXP or newer, it is the only way to uniquely identify +//and independently map multiple keyboards and mice. DirectInput merges all +//keyboards and mice into one device per. +// +//as WM_INPUT lacks specific RAWINPUT structures for gamepads, giving only raw +//data, and because DirectInput supports up to 16 joypads, DirectInput is used +//for joypad mapping. +// +//further, Xbox 360 controllers are explicitly detected and supported through +//XInput. this is because under DirectInput, the LT / RT (trigger) buttons are +//merged into a single Z-axis -- making it impossible to detect both buttons +//being pressed at the same time. with XInput, the state of both trigger +//buttons can be read independently. +// +//so in essence, this is actually more of a hybrid driver. + +#define DIRECTINPUT_VERSION 0x0800 +#include +#include + +namespace ruby { + +static DWORD WINAPI RawInputThreadProc(void*); +static LRESULT CALLBACK RawInputWindowProc(HWND, UINT, WPARAM, LPARAM); + +class RawInput { +public: + HANDLE mutex; + HWND hwnd; + bool initialized; + bool ready; + + struct Device { + HANDLE handle; + }; + + struct Keyboard : Device { + bool state[nall::Keyboard::Size]; + + void update(RAWINPUT *input) { + unsigned code = input->data.keyboard.MakeCode; + unsigned flags = input->data.keyboard.Flags; + + #define map(id, flag, name) if(code == id) state[name] = (bool)(flags == flag); + map(0x0001, 0, nall::Keyboard::Escape) + map(0x003b, 0, nall::Keyboard::F1) + map(0x003c, 0, nall::Keyboard::F2) + map(0x003d, 0, nall::Keyboard::F3) + map(0x003e, 0, nall::Keyboard::F4) + map(0x003f, 0, nall::Keyboard::F5) + map(0x0040, 0, nall::Keyboard::F6) + map(0x0041, 0, nall::Keyboard::F7) + map(0x0042, 0, nall::Keyboard::F8) + map(0x0043, 0, nall::Keyboard::F9) + map(0x0044, 0, nall::Keyboard::F10) + map(0x0057, 0, nall::Keyboard::F11) + map(0x0058, 0, nall::Keyboard::F12) + + map(0x0037, 2, nall::Keyboard::PrintScreen) + map(0x0046, 0, nall::Keyboard::ScrollLock) + map(0x001d, 4, nall::Keyboard::Pause) + map(0x0029, 0, nall::Keyboard::Tilde) + + map(0x0002, 0, nall::Keyboard::Num1) + map(0x0003, 0, nall::Keyboard::Num2) + map(0x0004, 0, nall::Keyboard::Num3) + map(0x0005, 0, nall::Keyboard::Num4) + map(0x0006, 0, nall::Keyboard::Num5) + map(0x0007, 0, nall::Keyboard::Num6) + map(0x0008, 0, nall::Keyboard::Num7) + map(0x0009, 0, nall::Keyboard::Num8) + map(0x000a, 0, nall::Keyboard::Num9) + map(0x000b, 0, nall::Keyboard::Num0) + + map(0x000c, 0, nall::Keyboard::Dash) + map(0x000d, 0, nall::Keyboard::Equal) + map(0x000e, 0, nall::Keyboard::Backspace) + + map(0x0052, 2, nall::Keyboard::Insert) + map(0x0053, 2, nall::Keyboard::Delete) + map(0x0047, 2, nall::Keyboard::Home) + map(0x004f, 2, nall::Keyboard::End) + map(0x0049, 2, nall::Keyboard::PageUp) + map(0x0051, 2, nall::Keyboard::PageDown) + + map(0x001e, 0, nall::Keyboard::A) + map(0x0030, 0, nall::Keyboard::B) + map(0x002e, 0, nall::Keyboard::C) + map(0x0020, 0, nall::Keyboard::D) + map(0x0012, 0, nall::Keyboard::E) + map(0x0021, 0, nall::Keyboard::F) + map(0x0022, 0, nall::Keyboard::G) + map(0x0023, 0, nall::Keyboard::H) + map(0x0017, 0, nall::Keyboard::I) + map(0x0024, 0, nall::Keyboard::J) + map(0x0025, 0, nall::Keyboard::K) + map(0x0026, 0, nall::Keyboard::L) + map(0x0032, 0, nall::Keyboard::M) + map(0x0031, 0, nall::Keyboard::N) + map(0x0018, 0, nall::Keyboard::O) + map(0x0019, 0, nall::Keyboard::P) + map(0x0010, 0, nall::Keyboard::Q) + map(0x0013, 0, nall::Keyboard::R) + map(0x001f, 0, nall::Keyboard::S) + map(0x0014, 0, nall::Keyboard::T) + map(0x0016, 0, nall::Keyboard::U) + map(0x002f, 0, nall::Keyboard::V) + map(0x0011, 0, nall::Keyboard::W) + map(0x002d, 0, nall::Keyboard::X) + map(0x0015, 0, nall::Keyboard::Y) + map(0x002c, 0, nall::Keyboard::Z) + + map(0x001a, 0, nall::Keyboard::LeftBracket) + map(0x001b, 0, nall::Keyboard::RightBracket) + map(0x002b, 0, nall::Keyboard::Backslash) + map(0x0027, 0, nall::Keyboard::Semicolon) + map(0x0028, 0, nall::Keyboard::Apostrophe) + map(0x0033, 0, nall::Keyboard::Comma) + map(0x0034, 0, nall::Keyboard::Period) + map(0x0035, 0, nall::Keyboard::Slash) + + map(0x004f, 0, nall::Keyboard::Keypad1) + map(0x0050, 0, nall::Keyboard::Keypad2) + map(0x0051, 0, nall::Keyboard::Keypad3) + map(0x004b, 0, nall::Keyboard::Keypad4) + map(0x004c, 0, nall::Keyboard::Keypad5) + map(0x004d, 0, nall::Keyboard::Keypad6) + map(0x0047, 0, nall::Keyboard::Keypad7) + map(0x0048, 0, nall::Keyboard::Keypad8) + map(0x0049, 0, nall::Keyboard::Keypad9) + map(0x0052, 0, nall::Keyboard::Keypad0) + + map(0x0053, 0, nall::Keyboard::Point) + map(0x001c, 2, nall::Keyboard::Enter) + map(0x004e, 0, nall::Keyboard::Add) + map(0x004a, 0, nall::Keyboard::Subtract) + map(0x0037, 0, nall::Keyboard::Multiply) + map(0x0035, 2, nall::Keyboard::Divide) + + map(0x0045, 0, nall::Keyboard::NumLock) + map(0x003a, 0, nall::Keyboard::CapsLock) + + //Pause signals 0x1d:4 + 0x45:0, whereas NumLock signals only 0x45:0. + //this makes it impractical to detect both Pause+NumLock independently. + //workaround: always detect Pause; detect NumLock only when Pause is released. + if(state[nall::Keyboard::Pause]) state[nall::Keyboard::NumLock] = false; + + map(0x0048, 2, nall::Keyboard::Up) + map(0x0050, 2, nall::Keyboard::Down) + map(0x004b, 2, nall::Keyboard::Left) + map(0x004d, 2, nall::Keyboard::Right) + + map(0x000f, 0, nall::Keyboard::Tab) + map(0x001c, 0, nall::Keyboard::Return) + map(0x0039, 0, nall::Keyboard::Spacebar) + map(0x005d, 2, nall::Keyboard::Menu) + + //merge left and right modifiers to one ID + if(code == 0x002a && flags == 0) state[nall::Keyboard::Shift] = 1; //left shift + if(code == 0x002a && flags == 1) state[nall::Keyboard::Shift] = 0; + if(code == 0x0036 && flags == 0) state[nall::Keyboard::Shift] = 1; //right shift + if(code == 0x0036 && flags == 1) state[nall::Keyboard::Shift] = 0; + + if(code == 0x001d && flags == 0) state[nall::Keyboard::Control] = 1; //left control + if(code == 0x001d && flags == 1) state[nall::Keyboard::Control] = 0; + if(code == 0x001d && flags == 2) state[nall::Keyboard::Control] = 1; //right control + if(code == 0x001d && flags == 3) state[nall::Keyboard::Control] = 0; + + if(code == 0x0038 && flags == 0) state[nall::Keyboard::Alt] = 1; //left alt + if(code == 0x0038 && flags == 1) state[nall::Keyboard::Alt] = 0; + if(code == 0x0038 && flags == 2) state[nall::Keyboard::Alt] = 1; //right alt + if(code == 0x0038 && flags == 3) state[nall::Keyboard::Alt] = 0; + + if(code == 0x005b && flags == 2) state[nall::Keyboard::Super] = 1; //left super + if(code == 0x005b && flags == 3) state[nall::Keyboard::Super] = 0; + if(code == 0x005c && flags == 2) state[nall::Keyboard::Super] = 1; //right super + if(code == 0x005c && flags == 3) state[nall::Keyboard::Super] = 0; + #undef map + } + + Keyboard() { + for(unsigned i = 0; i < nall::Keyboard::Size; i++) state[i] = false; + } + }; + + struct Mouse : Device { + signed xDistance; + signed yDistance; + signed zDistance; + unsigned buttonState; + + void sync() { + xDistance = 0; + yDistance = 0; + zDistance = 0; + } + + void update(RAWINPUT *input) { + if((input->data.mouse.usFlags & 1) == MOUSE_MOVE_RELATIVE) { + xDistance += input->data.mouse.lLastX; + yDistance += input->data.mouse.lLastY; + } + + if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_1_DOWN) buttonState |= 1 << 0; + if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_1_UP ) buttonState &=~ 1 << 0; + if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_2_DOWN) buttonState |= 1 << 2; //swap middle and right buttons, + if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_2_UP ) buttonState &=~ 1 << 2; //for consistency with Linux: + if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_3_DOWN) buttonState |= 1 << 1; //left = 0, middle = 1, right = 2 + if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_3_UP ) buttonState &=~ 1 << 1; + if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_DOWN) buttonState |= 1 << 3; + if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_UP ) buttonState &=~ 1 << 3; + if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_DOWN) buttonState |= 1 << 4; + if(input->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_UP ) buttonState &=~ 1 << 4; + + if(input->data.mouse.usButtonFlags & RI_MOUSE_WHEEL) { + zDistance += (int16_t)input->data.mouse.usButtonData; + } + } + + Mouse() { + xDistance = yDistance = zDistance = 0; + buttonState = 0; + } + }; + + //keep track of gamepads for the sole purpose of distinguishing XInput devices + //from all other devices. this is necessary, as DirectInput does not provide + //a way to retrieve the necessary RIDI_DEVICENAME string. + struct Gamepad : Device { + bool isXInputDevice; + uint16_t vendorId; + uint16_t productId; + }; + + linear_vector lkeyboard; + linear_vector lmouse; + linear_vector lgamepad; + + LRESULT window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + if(msg == WM_INPUT) { + unsigned size = 0; + GetRawInputData((HRAWINPUT)lparam, RID_INPUT, NULL, &size, sizeof(RAWINPUTHEADER)); + RAWINPUT *input = new RAWINPUT[size]; + GetRawInputData((HRAWINPUT)lparam, RID_INPUT, input, &size, sizeof(RAWINPUTHEADER)); + WaitForSingleObject(mutex, INFINITE); + + if(input->header.dwType == RIM_TYPEKEYBOARD) { + for(unsigned i = 0; i < lkeyboard.size(); i++) { + if(input->header.hDevice == lkeyboard[i].handle) { + lkeyboard[i].update(input); + break; + } + } + } else if(input->header.dwType == RIM_TYPEMOUSE) { + for(unsigned i = 0; i < lmouse.size(); i++) { + if(input->header.hDevice == lmouse[i].handle) { + lmouse[i].update(input); + break; + } + } + } + + ReleaseMutex(mutex); + //allow propogation of WM_INPUT message + LRESULT result = DefRawInputProc(&input, size, sizeof(RAWINPUTHEADER)); + delete[] input; + return result; + } + + return DefWindowProc(hwnd, msg, wparam, lparam); + } + + //this is used to sort device IDs + struct DevicePool { + HANDLE handle; + wchar_t name[4096]; + bool operator<(const DevicePool &pool) const { return wcscmp(name, pool.name) < 0; } + }; + + int main() { + //create an invisible window to act as a sink, capturing all WM_INPUT messages + WNDCLASS wc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hbrBackground = (HBRUSH)COLOR_WINDOW; + wc.hCursor = LoadCursor(0, IDC_ARROW); + wc.hIcon = LoadIcon(0, IDI_APPLICATION); + wc.hInstance = GetModuleHandle(0); + wc.lpfnWndProc = RawInputWindowProc; + wc.lpszClassName = L"RawInputClass"; + wc.lpszMenuName = 0; + wc.style = CS_VREDRAW | CS_HREDRAW; + RegisterClass(&wc); + + hwnd = CreateWindow(L"RawInputClass", L"RawInputClass", WS_POPUP, + 0, 0, 64, 64, 0, 0, GetModuleHandle(0), 0); + + //enumerate all HID devices + unsigned devices = 0; + GetRawInputDeviceList(NULL, &devices, sizeof(RAWINPUTDEVICELIST)); + RAWINPUTDEVICELIST *list = new RAWINPUTDEVICELIST[devices]; + GetRawInputDeviceList(list, &devices, sizeof(RAWINPUTDEVICELIST)); + + //sort all devices by name. this has two important properties: + //1) it consistently orders peripherals, so mapped IDs remain constant + //2) it sorts the virtual keyboard and mouse to the bottom of the list + // (real devices start with \\?\HID#, virtual with \\?\Root#) + DevicePool pool[devices]; + for(unsigned i = 0; i < devices; i++) { + pool[i].handle = list[i].hDevice; + unsigned size = sizeof(pool[i].name) - 1; + GetRawInputDeviceInfo(list[i].hDevice, RIDI_DEVICENAME, &pool[i].name, &size); + } + nall::sort(pool, devices); + delete[] list; + + for(unsigned i = 0; i < devices; i++) { + RID_DEVICE_INFO info; + info.cbSize = sizeof(RID_DEVICE_INFO); + + unsigned size = info.cbSize; + GetRawInputDeviceInfo(pool[i].handle, RIDI_DEVICEINFO, &info, &size); + + if(info.dwType == RIM_TYPEKEYBOARD) { + unsigned n = lkeyboard.size(); + lkeyboard[n].handle = pool[i].handle; + } else if(info.dwType == RIM_TYPEMOUSE) { + unsigned n = lmouse.size(); + lmouse[n].handle = pool[i].handle; + } else if(info.dwType == RIM_TYPEHID) { + //if this is a gamepad or joystick device ... + if(info.hid.usUsagePage == 1 && (info.hid.usUsage == 4 || info.hid.usUsage == 5)) { + //... then cache device information for later use + unsigned n = lgamepad.size(); + lgamepad[n].handle = pool[i].handle; + lgamepad[n].vendorId = (uint16_t)info.hid.dwVendorId; + lgamepad[n].productId = (uint16_t)info.hid.dwProductId; + + //per MSDN: XInput devices have "IG_" in their device strings, + //which is how they should be identified. + string p = utf8_t(pool[i].name); + if(auto position = strpos(p, "IG_")) { + lgamepad[n].isXInputDevice = true; + } else { + lgamepad[n].isXInputDevice = false; + } + } + } + } + + RAWINPUTDEVICE device[2]; + //capture all keyboard input + device[0].usUsagePage = 1; + device[0].usUsage = 6; + device[0].dwFlags = RIDEV_INPUTSINK; + device[0].hwndTarget = hwnd; + //capture all mouse input + device[1].usUsagePage = 1; + device[1].usUsage = 2; + device[1].dwFlags = RIDEV_INPUTSINK; + device[1].hwndTarget = hwnd; + RegisterRawInputDevices(device, 2, sizeof(RAWINPUTDEVICE)); + + WaitForSingleObject(mutex, INFINITE); + ready = true; + ReleaseMutex(mutex); + + while(true) { + MSG msg; + GetMessage(&msg, hwnd, 0, 0); + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + return 0; + } + + RawInput() : initialized(false), ready(false) { + } +}; + +static RawInput rawinput; + +DWORD WINAPI RawInputThreadProc(void*) { + return rawinput.main(); +} + +LRESULT CALLBACK RawInputWindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + return rawinput.window_proc(hwnd, msg, wparam, lparam); +} + +class XInput { +public: + HMODULE libxinput; + DWORD WINAPI (*pXInputGetState)(DWORD, XINPUT_STATE*); + + struct Gamepad { + unsigned id; + + int16_t hat; + int16_t axis[6]; + bool button[10]; + + void poll(XINPUT_STATE &state) { + hat = Joypad::HatCenter; + if(state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP ) hat |= Joypad::HatUp; + if(state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) hat |= Joypad::HatRight; + if(state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN ) hat |= Joypad::HatDown; + if(state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT ) hat |= Joypad::HatLeft; + + axis[0] = (int16_t)state.Gamepad.sThumbLX; + axis[1] = (int16_t)state.Gamepad.sThumbLY; + axis[2] = (int16_t)state.Gamepad.sThumbRX; + axis[3] = (int16_t)state.Gamepad.sThumbRY; + + //transform left and right trigger ranges: + //from: 0 (low, eg released) to 255 (high, eg pressed all the way down) + //to: +32767 (low) to -32768 (high) + uint16_t triggerX = state.Gamepad.bLeftTrigger; + uint16_t triggerY = state.Gamepad.bRightTrigger; + + triggerX = (triggerX << 8) | triggerX; + triggerY = (triggerY << 8) | triggerY; + + axis[4] = (~triggerX) - 32768; + axis[5] = (~triggerY) - 32768; + + button[0] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_A); + button[1] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_B); + button[2] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_X); + button[3] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_Y); + button[4] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_BACK); + button[5] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_START); + button[6] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER); + button[7] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER); + button[8] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_THUMB); + button[9] = (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB); + } + + Gamepad() { + hat = Joypad::HatCenter; + for(unsigned n = 0; n < 6; n++) axis[n] = 0; + for(unsigned n = 0; n < 10; n++) button[n] = false; + } + }; + + linear_vector lgamepad; + + void poll() { + if(!pXInputGetState) return; + + for(unsigned i = 0; i < lgamepad.size(); i++) { + XINPUT_STATE state; + DWORD result = pXInputGetState(lgamepad[i].id, &state); + if(result == ERROR_SUCCESS) lgamepad[i].poll(state); + } + } + + void init() { + if(!pXInputGetState) return; + + //XInput only supports up to four controllers + for(unsigned i = 0; i <= 3; i++) { + XINPUT_STATE state; + DWORD result = pXInputGetState(i, &state); + if(result == ERROR_SUCCESS) { + //valid controller detected, add to gamepad list + unsigned n = lgamepad.size(); + lgamepad[n].id = i; + } + } + } + + XInput() : pXInputGetState(0) { + //bind xinput1 dynamically, as it does not ship with Windows Vista or below + libxinput = LoadLibraryA("xinput1_3.dll"); + if(!libxinput) libxinput = LoadLibraryA("xinput1_2.dll"); + if(!libxinput) libxinput = LoadLibraryA("xinput1_1.dll"); + if(!libxinput) return; + pXInputGetState = (DWORD WINAPI (*)(DWORD, XINPUT_STATE*))GetProcAddress(libxinput, "XInputGetState"); + } + + ~XInput() { + if(libxinput) FreeLibrary(libxinput); + } +}; + +static BOOL CALLBACK DirectInput_EnumJoypadsCallback(const DIDEVICEINSTANCE*, void*); +static BOOL CALLBACK DirectInput_EnumJoypadAxesCallback(const DIDEVICEOBJECTINSTANCE*, void*); + +class DirectInput { +public: + HWND handle; + LPDIRECTINPUT8 context; + struct Gamepad { + LPDIRECTINPUTDEVICE8 handle; + + int16_t hat[4]; + int16_t axis[6]; + bool button[128]; + + void poll(DIJOYSTATE2 &state) { + //POV hats + for(unsigned n = 0; n < 4; n++) { + hat[n] = Joypad::HatCenter; + + //POV value is in clockwise-hundredth degree units + unsigned pov = state.rgdwPOV[n]; + + //some drivers report a centered POV hat as -1U, others as 65535U. + //>= 36000 will match both, as well as invalid ranges. + if(pov >= 36000) continue; + + if(pov >= 31500 || pov <= 4500) hat[n] |= Joypad::HatUp; + if(pov >= 4500 && pov <= 13500) hat[n] |= Joypad::HatRight; + if(pov >= 13500 && pov <= 22500) hat[n] |= Joypad::HatDown; + if(pov >= 22500 && pov <= 31500) hat[n] |= Joypad::HatLeft; + } + + //axes + axis[0] = state.lX; + axis[1] = state.lY; + axis[2] = state.lZ; + axis[3] = state.lRx; + axis[4] = state.lRy; + axis[5] = state.lRz; + + //buttons + for(unsigned n = 0; n < 128; n++) { + button[n] = (bool)state.rgbButtons[n]; + } + } + + Gamepad() { + handle = 0; + for(unsigned n = 0; n < 4; n++) hat[n] = Joypad::HatCenter; + for(unsigned n = 0; n < 6; n++) axis[n] = 0; + for(unsigned n = 0; n < 128; n++) button[n] = false; + } + }; + linear_vector lgamepad; + + void poll() { + for(unsigned i = 0; i < lgamepad.size(); i++) { + if(FAILED(lgamepad[i].handle->Poll())) { + lgamepad[i].handle->Acquire(); + continue; + } + + DIJOYSTATE2 state; + lgamepad[i].handle->GetDeviceState(sizeof(DIJOYSTATE2), &state); + lgamepad[i].poll(state); + } + } + + bool init_joypad(const DIDEVICEINSTANCE *instance) { + //if this is an XInput device, do not acquire it via DirectInput ... + //the XInput driver above will handle said device. + for(unsigned i = 0; i < rawinput.lgamepad.size(); i++) { + uint32_t guid = MAKELONG(rawinput.lgamepad[i].vendorId, rawinput.lgamepad[i].productId); + if(guid == instance->guidProduct.Data1) { + if(rawinput.lgamepad[i].isXInputDevice == true) { + return DIENUM_CONTINUE; + } + } + } + + if(FAILED(context->CreateDevice(instance->guidInstance, &device, 0))) { + return DIENUM_CONTINUE; + } + + device->SetDataFormat(&c_dfDIJoystick2); + device->SetCooperativeLevel(handle, DISCL_NONEXCLUSIVE | DISCL_BACKGROUND); + device->EnumObjects(DirectInput_EnumJoypadAxesCallback, (void*)this, DIDFT_ABSAXIS); + unsigned n = lgamepad.size(); + lgamepad[n].handle = device; + return DIENUM_CONTINUE; + } + + bool init_axis(const DIDEVICEOBJECTINSTANCE *instance) { + DIPROPRANGE range; + range.diph.dwSize = sizeof(DIPROPRANGE); + range.diph.dwHeaderSize = sizeof(DIPROPHEADER); + range.diph.dwHow = DIPH_BYID; + range.diph.dwObj = instance->dwType; + range.lMin = -32768; + range.lMax = +32767; + device->SetProperty(DIPROP_RANGE, &range.diph); + return DIENUM_CONTINUE; + } + + void init(HWND handle_) { + handle = handle_; + DirectInput8Create(GetModuleHandle(0), DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&context, 0); + context->EnumDevices(DI8DEVCLASS_GAMECTRL, DirectInput_EnumJoypadsCallback, (void*)this, DIEDFL_ATTACHEDONLY); + } + + void term() { + for(unsigned i = 0; i < lgamepad.size(); i++) { + lgamepad[i].handle->Unacquire(); + lgamepad[i].handle->Release(); + } + lgamepad.reset(); + + if(context) { + context->Release(); + context = 0; + } + } + +private: + LPDIRECTINPUTDEVICE8 device; +}; + +BOOL CALLBACK DirectInput_EnumJoypadsCallback(const DIDEVICEINSTANCE *instance, void *p) { + return ((DirectInput*)p)->init_joypad(instance); +} + +BOOL CALLBACK DirectInput_EnumJoypadAxesCallback(const DIDEVICEOBJECTINSTANCE *instance, void *p) { + return ((DirectInput*)p)->init_axis(instance); +} + +class pInputRaw { +public: + XInput xinput; + DirectInput dinput; + + bool acquire_mouse; + bool cursor_visible; + + struct { + HWND handle; + } settings; + + bool cap(const string& name) { + if(name == Input::Handle) return true; + if(name == Input::KeyboardSupport) return true; + if(name == Input::MouseSupport) return true; + if(name == Input::JoypadSupport) return true; + return false; + } + + any get(const string& name) { + if(name == Input::Handle) return (uintptr_t)settings.handle; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Input::Handle) { + settings.handle = (HWND)any_cast(value); + return true; + } + + return false; + } + + bool acquire() { + acquire_mouse = true; + if(cursor_visible == true) { + ShowCursor(cursor_visible = false); + } + return acquired(); + } + + bool unacquire() { + acquire_mouse = false; + ReleaseCapture(); + ClipCursor(NULL); + if(cursor_visible == false) { + ShowCursor(cursor_visible = true); + } + return true; + } + + bool acquired() { + if(acquire_mouse == true) { + SetFocus(settings.handle); + SetCapture(settings.handle); + RECT rc; + GetWindowRect(settings.handle, &rc); + ClipCursor(&rc); + } + return GetCapture() == settings.handle; + } + + bool poll(int16_t *table) { + memset(table, 0, Scancode::Limit * sizeof(int16_t)); + + WaitForSingleObject(rawinput.mutex, INFINITE); + + //========= + //Keyboards + //========= + for(unsigned i = 0; i < min(rawinput.lkeyboard.size(), (unsigned)Keyboard::Count); i++) { + for(unsigned n = 0; n < nall::Keyboard::Size; n++) { + //using keyboard(0)|= instead of keyboard(i)= merges all keyboards to KB0 + //this is done to favor ease of mapping over flexibility (eg share laptop+USB keyboard mapping) + table[keyboard(0).key(n)] |= rawinput.lkeyboard[i].state[n]; + } + } + + //==== + //Mice + //==== + for(unsigned i = 0; i < min(rawinput.lmouse.size(), (unsigned)Mouse::Count); i++) { + table[mouse(i).axis(0)] = rawinput.lmouse[i].xDistance; + table[mouse(i).axis(1)] = rawinput.lmouse[i].yDistance; + table[mouse(i).axis(2)] = rawinput.lmouse[i].zDistance; + + for(unsigned n = 0; n < min(5U, (unsigned)Mouse::Buttons); n++) { + table[mouse(i).button(n)] = (bool)(rawinput.lmouse[i].buttonState & (1 << n)); + } + + rawinput.lmouse[i].sync(); + } + + ReleaseMutex(rawinput.mutex); + + unsigned joy = 0; + + //================== + //XInput controllers + //================== + xinput.poll(); + for(unsigned i = 0; i < xinput.lgamepad.size(); i++) { + if(joy >= Joypad::Count) break; + + table[joypad(joy).hat(0)] = xinput.lgamepad[i].hat; + + for(unsigned axis = 0; axis < min(6U, (unsigned)Joypad::Axes); axis++) { + table[joypad(joy).axis(axis)] = xinput.lgamepad[i].axis[axis]; + } + + for(unsigned button = 0; button < min(10U, (unsigned)Joypad::Buttons); button++) { + table[joypad(joy).button(button)] = xinput.lgamepad[i].button[button]; + } + + joy++; + } + + //======================= + //DirectInput controllers + //======================= + dinput.poll(); + for(unsigned i = 0; i < dinput.lgamepad.size(); i++) { + if(joy >= Joypad::Count) break; + + for(unsigned hat = 0; hat < min(4U, (unsigned)Joypad::Hats); hat++) { + table[joypad(joy).hat(hat)] = dinput.lgamepad[i].hat[hat]; + } + + for(unsigned axis = 0; axis < min(6U, (unsigned)Joypad::Axes); axis++) { + table[joypad(joy).axis(axis)] = dinput.lgamepad[i].axis[axis]; + } + + for(unsigned button = 0; button < min(128U, (unsigned)Joypad::Buttons); button++) { + table[joypad(joy).button(button)] = dinput.lgamepad[i].button[button]; + } + + joy++; + } + + return true; + } + + bool init() { + //only spawn RawInput processing thread one time + if(rawinput.initialized == false) { + rawinput.initialized = true; + rawinput.mutex = CreateMutex(NULL, FALSE, NULL); + CreateThread(NULL, 0, RawInputThreadProc, 0, 0, NULL); + + //RawInput device calibration needs to finish before initializing DirectInput; + //as it needs device GUIDs to distinguish XInput devices from ordinary joypads. + bool ready = false; + do { + Sleep(10); + WaitForSingleObject(rawinput.mutex, INFINITE); + ready = rawinput.ready; + ReleaseMutex(rawinput.mutex); + } while(ready == false); + } + + xinput.init(); + dinput.init(settings.handle); + + acquire_mouse = false; + cursor_visible = true; + return true; + } + + void term() { + unacquire(); + dinput.term(); + } + + pInputRaw() { + } +}; + +DeclareInput(Raw) + +}; diff --git a/ruby/input/sdl.cpp b/ruby/input/sdl.cpp new file mode 100755 index 00000000..9986a007 --- /dev/null +++ b/ruby/input/sdl.cpp @@ -0,0 +1,230 @@ +//================ +//SDL input driver +//================ +//Keyboard and mouse are controlled directly via Xlib, +//as SDL cannot capture input from windows it does not create itself. +//SDL is used only to handle joysticks / gamepads. + +#include +#include +#include + +namespace ruby { + +struct pInputSDL { + #include "xlibkeys.hpp" + + struct { + Display *display; + Window rootwindow; + Cursor InvisibleCursor; + SDL_Joystick *gamepad[Joypad::Count]; + + unsigned screenwidth, screenheight; + unsigned relativex, relativey; + bool mouseacquired; + + //mouse device settings + int accel_numerator; + int accel_denominator; + int threshold; + } device; + + struct { + uintptr_t handle; + } settings; + + bool cap(const string& name) { + if(name == Input::Handle) return true; + if(name == Input::KeyboardSupport) return true; + if(name == Input::MouseSupport) return true; + if(name == Input::JoypadSupport) return true; + return false; + } + + any get(const string& name) { + if(name == Input::Handle) return (uintptr_t)settings.handle; + return false; + } + + bool set(const string& name, const any &value) { + if(name == Input::Handle) { + settings.handle = any_cast(value); + return true; + } + + return false; + } + + bool acquire() { + if(acquired()) return true; + + if(XGrabPointer(device.display, settings.handle, True, 0, GrabModeAsync, GrabModeAsync, + device.rootwindow, device.InvisibleCursor, CurrentTime) == GrabSuccess) { + //backup existing cursor acceleration settings + XGetPointerControl(device.display, &device.accel_numerator, &device.accel_denominator, &device.threshold); + + //disable cursor acceleration + XChangePointerControl(device.display, True, False, 1, 1, 0); + + //center cursor (so that first relative poll returns 0, 0 if mouse has not moved) + XWarpPointer(device.display, None, device.rootwindow, 0, 0, 0, 0, device.screenwidth / 2, device.screenheight / 2); + + return device.mouseacquired = true; + } else { + return device.mouseacquired = false; + } + } + + bool unacquire() { + if(acquired()) { + //restore cursor acceleration and release cursor + XChangePointerControl(device.display, True, True, device.accel_numerator, device.accel_denominator, device.threshold); + XUngrabPointer(device.display, CurrentTime); + device.mouseacquired = false; + } + return true; + } + + bool acquired() { + return device.mouseacquired; + } + + bool poll(int16_t *table) { + memset(table, 0, Scancode::Limit * sizeof(int16_t)); + + //======== + //Keyboard + //======== + + x_poll(table); + + //===== + //Mouse + //===== + + Window root_return, child_return; + int root_x_return = 0, root_y_return = 0; + int win_x_return = 0, win_y_return = 0; + unsigned int mask_return = 0; + XQueryPointer(device.display, settings.handle, + &root_return, &child_return, &root_x_return, &root_y_return, + &win_x_return, &win_y_return, &mask_return); + + if(acquired()) { + XWindowAttributes attributes; + XGetWindowAttributes(device.display, settings.handle, &attributes); + + //absolute -> relative conversion + table[mouse(0).axis(0)] = (int16_t)(root_x_return - device.screenwidth / 2); + table[mouse(0).axis(1)] = (int16_t)(root_y_return - device.screenheight / 2); + + if(table[mouse(0).axis(0)] != 0 || table[mouse(0).axis(1)] != 0) { + //if mouse movement occurred, re-center mouse for next poll + XWarpPointer(device.display, None, device.rootwindow, 0, 0, 0, 0, device.screenwidth / 2, device.screenheight / 2); + } + } else { + table[mouse(0).axis(0)] = (int16_t)(root_x_return - device.relativex); + table[mouse(0).axis(1)] = (int16_t)(root_y_return - device.relativey); + + device.relativex = root_x_return; + device.relativey = root_y_return; + } + + //manual device polling is limited to only five buttons ... + table[mouse(0).button(0)] = (bool)(mask_return & Button1Mask); + table[mouse(0).button(1)] = (bool)(mask_return & Button2Mask); + table[mouse(0).button(2)] = (bool)(mask_return & Button3Mask); + table[mouse(0).button(3)] = (bool)(mask_return & Button4Mask); + table[mouse(0).button(4)] = (bool)(mask_return & Button5Mask); + + //========= + //Joypad(s) + //========= + + SDL_JoystickUpdate(); + for(unsigned i = 0; i < Joypad::Count; i++) { + if(!device.gamepad[i]) continue; + + //POV hats + unsigned hats = min((unsigned)Joypad::Hats, SDL_JoystickNumHats(device.gamepad[i])); + for(unsigned hat = 0; hat < hats; hat++) { + uint8_t state = SDL_JoystickGetHat(device.gamepad[i], hat); + if(state & SDL_HAT_UP ) table[joypad(i).hat(hat)] |= Joypad::HatUp; + if(state & SDL_HAT_RIGHT) table[joypad(i).hat(hat)] |= Joypad::HatRight; + if(state & SDL_HAT_DOWN ) table[joypad(i).hat(hat)] |= Joypad::HatDown; + if(state & SDL_HAT_LEFT ) table[joypad(i).hat(hat)] |= Joypad::HatLeft; + } + + //axes + unsigned axes = min((unsigned)Joypad::Axes, SDL_JoystickNumAxes(device.gamepad[i])); + for(unsigned axis = 0; axis < axes; axis++) { + table[joypad(i).axis(axis)] = (int16_t)SDL_JoystickGetAxis(device.gamepad[i], axis); + } + + //buttons + for(unsigned button = 0; button < Joypad::Buttons; button++) { + table[joypad(i).button(button)] = (bool)SDL_JoystickGetButton(device.gamepad[i], button); + } + } + + return true; + } + + bool init() { + x_init(); + SDL_InitSubSystem(SDL_INIT_JOYSTICK); + SDL_JoystickEventState(SDL_IGNORE); + + device.display = XOpenDisplay(0); + device.rootwindow = DefaultRootWindow(device.display); + XWindowAttributes attributes; + XGetWindowAttributes(device.display, device.rootwindow, &attributes); + device.screenwidth = attributes.width; + device.screenheight = attributes.height; + + //Xlib: "because XShowCursor(false) would be too easy." + //create a fully transparent cursor named InvisibleCursor, + //for use while acquire() / XGrabPointer() is active. + Pixmap pixmap; + XColor black, unused; + static char invisible_data[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + Colormap colormap = DefaultColormap(device.display, DefaultScreen(device.display)); + XAllocNamedColor(device.display, colormap, "black", &black, &unused); + pixmap = XCreateBitmapFromData(device.display, settings.handle, invisible_data, 8, 8); + device.InvisibleCursor = XCreatePixmapCursor(device.display, pixmap, pixmap, &black, &black, 0, 0); + XFreePixmap(device.display, pixmap); + XFreeColors(device.display, colormap, &black.pixel, 1, 0); + + device.mouseacquired = false; + device.relativex = 0; + device.relativey = 0; + + unsigned joypads = min((unsigned)Joypad::Count, SDL_NumJoysticks()); + for(unsigned i = 0; i < joypads; i++) device.gamepad[i] = SDL_JoystickOpen(i); + + return true; + } + + void term() { + unacquire(); + XFreeCursor(device.display, device.InvisibleCursor); + + for(unsigned i = 0; i < Joypad::Count; i++) { + if(device.gamepad[i]) SDL_JoystickClose(device.gamepad[i]); + device.gamepad[i] = 0; + } + + SDL_QuitSubSystem(SDL_INIT_JOYSTICK); + XCloseDisplay(device.display); + } + + pInputSDL() { + for(unsigned i = 0; i < Joypad::Count; i++) device.gamepad[i] = 0; + settings.handle = 0; + } +}; + +DeclareInput(SDL) + +}; diff --git a/ruby/input/x.cpp b/ruby/input/x.cpp new file mode 100755 index 00000000..fe1440a8 --- /dev/null +++ b/ruby/input/x.cpp @@ -0,0 +1,50 @@ +#include +#include +#include +#include +#include + +namespace ruby { + +class pInputX { +public: + Display *display; + #include "xlibkeys.hpp" + + bool cap(const string& name) { + if(name == Input::KeyboardSupport) return true; + return false; + } + + any get(const string& name) { + return false; + } + + bool set(const string& name, const any &value) { + return false; + } + + bool acquire() { return false; } + bool unacquire() { return false; } + bool acquired() { return false; } + + bool poll(int16_t *table) { + memset(table, 0, Scancode::Limit * sizeof(int16_t)); + x_poll(table); + return true; + } + + bool init() { + x_init(); + display = XOpenDisplay(0); + return true; + } + + void term() { + XCloseDisplay(display); + } +}; + +DeclareInput(X) + +}; diff --git a/ruby/input/xlibkeys.hpp b/ruby/input/xlibkeys.hpp new file mode 100755 index 00000000..770de16d --- /dev/null +++ b/ruby/input/xlibkeys.hpp @@ -0,0 +1,264 @@ +uint8_t scancode[256]; + +enum XScancode { + Escape, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, + ScrollLock, Pause, Tilde, + Num1, Num2, Num3, Num4, Num5, Num6, Num7, Num8, Num9, Num0, + Dash, Equal, Backspace, + Insert, Delete, Home, End, PageUp, PageDown, + A, B, C, D, E, F, G, H, I, J, K, L, M, + N, O, P, Q, R, S, T, U, V, W, X, Y, Z, + LeftBracket, RightBracket, Backslash, Semicolon, Apostrophe, Comma, Period, Slash, + Keypad1, Keypad2, Keypad3, Keypad4, Keypad5, Keypad6, Keypad7, Keypad8, Keypad9, Keypad0, + Point, Enter, Add, Subtract, Multiply, Divide, + Up, Down, Left, Right, + Tab, Return, Spacebar, Menu, + LeftShift, RightShift, LeftControl, RightControl, LeftAlt, RightAlt, LeftSuper, RightSuper, +}; + +void x_poll(int16_t *table) { + char state[32]; + Display *display = XOpenDisplay(0); + XQueryKeymap(display, state); + XCloseDisplay(display); + + #define key(id) table[keyboard(0)[id]] + #define pressed(id) (bool)(state[scancode[id] >> 3] & (1 << (scancode[id] & 7))) + + key(Keyboard::Escape) = pressed(Escape); + + key(Keyboard::F1) = pressed(F1); + key(Keyboard::F2) = pressed(F2); + key(Keyboard::F3) = pressed(F3); + key(Keyboard::F4) = pressed(F4); + key(Keyboard::F5) = pressed(F5); + key(Keyboard::F6) = pressed(F6); + key(Keyboard::F7) = pressed(F7); + key(Keyboard::F8) = pressed(F8); + key(Keyboard::F9) = pressed(F9); + key(Keyboard::F10) = pressed(F10); + key(Keyboard::F11) = pressed(F11); + key(Keyboard::F12) = pressed(F12); + + key(Keyboard::ScrollLock) = pressed(ScrollLock); + key(Keyboard::Pause) = pressed(Pause); + key(Keyboard::Tilde) = pressed(Tilde); + + key(Keyboard::Num1) = pressed(Num1); + key(Keyboard::Num2) = pressed(Num2); + key(Keyboard::Num3) = pressed(Num3); + key(Keyboard::Num4) = pressed(Num4); + key(Keyboard::Num5) = pressed(Num5); + key(Keyboard::Num6) = pressed(Num6); + key(Keyboard::Num7) = pressed(Num7); + key(Keyboard::Num8) = pressed(Num8); + key(Keyboard::Num9) = pressed(Num9); + key(Keyboard::Num0) = pressed(Num0); + + key(Keyboard::Dash) = pressed(Dash); + key(Keyboard::Equal) = pressed(Equal); + key(Keyboard::Backspace) = pressed(Backspace); + + key(Keyboard::Insert) = pressed(Insert); + key(Keyboard::Delete) = pressed(Delete); + key(Keyboard::Home) = pressed(Home); + key(Keyboard::End) = pressed(End); + key(Keyboard::PageUp) = pressed(PageUp); + key(Keyboard::PageDown) = pressed(PageDown); + + key(Keyboard::A) = pressed(A); + key(Keyboard::B) = pressed(B); + key(Keyboard::C) = pressed(C); + key(Keyboard::D) = pressed(D); + key(Keyboard::E) = pressed(E); + key(Keyboard::F) = pressed(F); + key(Keyboard::G) = pressed(G); + key(Keyboard::H) = pressed(H); + key(Keyboard::I) = pressed(I); + key(Keyboard::J) = pressed(J); + key(Keyboard::K) = pressed(K); + key(Keyboard::L) = pressed(L); + key(Keyboard::M) = pressed(M); + key(Keyboard::N) = pressed(N); + key(Keyboard::O) = pressed(O); + key(Keyboard::P) = pressed(P); + key(Keyboard::Q) = pressed(Q); + key(Keyboard::R) = pressed(R); + key(Keyboard::S) = pressed(S); + key(Keyboard::T) = pressed(T); + key(Keyboard::U) = pressed(U); + key(Keyboard::V) = pressed(V); + key(Keyboard::W) = pressed(W); + key(Keyboard::X) = pressed(X); + key(Keyboard::Y) = pressed(Y); + key(Keyboard::Z) = pressed(Z); + + key(Keyboard::LeftBracket) = pressed(LeftBracket); + key(Keyboard::RightBracket) = pressed(RightBracket); + key(Keyboard::Backslash) = pressed(Backslash); + key(Keyboard::Semicolon) = pressed(Semicolon); + key(Keyboard::Apostrophe) = pressed(Apostrophe); + key(Keyboard::Comma) = pressed(Comma); + key(Keyboard::Period) = pressed(Period); + key(Keyboard::Slash) = pressed(Slash); + + key(Keyboard::Keypad1) = pressed(Keypad1); + key(Keyboard::Keypad2) = pressed(Keypad2); + key(Keyboard::Keypad3) = pressed(Keypad3); + key(Keyboard::Keypad4) = pressed(Keypad4); + key(Keyboard::Keypad5) = pressed(Keypad5); + key(Keyboard::Keypad6) = pressed(Keypad6); + key(Keyboard::Keypad7) = pressed(Keypad7); + key(Keyboard::Keypad8) = pressed(Keypad8); + key(Keyboard::Keypad9) = pressed(Keypad9); + key(Keyboard::Keypad0) = pressed(Keypad0); + + key(Keyboard::Point) = pressed(Point); + key(Keyboard::Enter) = pressed(Enter); + key(Keyboard::Add) = pressed(Add); + key(Keyboard::Subtract) = pressed(Subtract); + key(Keyboard::Multiply) = pressed(Multiply); + key(Keyboard::Divide) = pressed(Divide); + + key(Keyboard::Up) = pressed(Up); + key(Keyboard::Down) = pressed(Down); + key(Keyboard::Left) = pressed(Left); + key(Keyboard::Right) = pressed(Right); + + key(Keyboard::Tab) = pressed(Tab); + key(Keyboard::Return) = pressed(Return); + key(Keyboard::Spacebar) = pressed(Spacebar); + key(Keyboard::Menu) = pressed(Menu); + + key(Keyboard::Shift) = pressed(LeftShift) || pressed(RightShift); + key(Keyboard::Control) = pressed(LeftControl) || pressed(RightControl); + key(Keyboard::Alt) = pressed(LeftAlt) || pressed(RightAlt); + key(Keyboard::Super) = pressed(LeftSuper) || pressed(RightSuper); + + #undef key + #undef pressed +} + +void x_init() { + Display *display = XOpenDisplay(0); + memset(&scancode, 0, sizeof scancode); + + #define assign(x, y) scancode[x] = XKeysymToKeycode(display, y) + assign(Escape, XK_Escape); + + assign(F1, XK_F1); + assign(F2, XK_F2); + assign(F3, XK_F3); + assign(F4, XK_F4); + assign(F5, XK_F5); + assign(F6, XK_F6); + assign(F7, XK_F7); + assign(F8, XK_F8); + assign(F9, XK_F9); + assign(F10, XK_F10); + assign(F11, XK_F11); + assign(F12, XK_F12); + + assign(ScrollLock, XK_Scroll_Lock); + assign(Pause, XK_Pause); + + assign(Tilde, XK_asciitilde); + + assign(Num0, XK_0); + assign(Num1, XK_1); + assign(Num2, XK_2); + assign(Num3, XK_3); + assign(Num4, XK_4); + assign(Num5, XK_5); + assign(Num6, XK_6); + assign(Num7, XK_7); + assign(Num8, XK_8); + assign(Num9, XK_9); + + assign(Dash, XK_minus); + assign(Equal, XK_equal); + assign(Backspace, XK_BackSpace); + + assign(Insert, XK_Insert); + assign(Delete, XK_Delete); + assign(Home, XK_Home); + assign(End, XK_End); + assign(PageUp, XK_Prior); + assign(PageDown, XK_Next); + + assign(A, XK_A); + assign(B, XK_B); + assign(C, XK_C); + assign(D, XK_D); + assign(E, XK_E); + assign(F, XK_F); + assign(G, XK_G); + assign(H, XK_H); + assign(I, XK_I); + assign(J, XK_J); + assign(K, XK_K); + assign(L, XK_L); + assign(M, XK_M); + assign(N, XK_N); + assign(O, XK_O); + assign(P, XK_P); + assign(Q, XK_Q); + assign(R, XK_R); + assign(S, XK_S); + assign(T, XK_T); + assign(U, XK_U); + assign(V, XK_V); + assign(W, XK_W); + assign(X, XK_X); + assign(Y, XK_Y); + assign(Z, XK_Z); + + assign(LeftBracket, XK_bracketleft); + assign(RightBracket, XK_bracketright); + assign(Backslash, XK_backslash); + assign(Semicolon, XK_semicolon); + assign(Apostrophe, XK_apostrophe); + assign(Comma, XK_comma); + assign(Period, XK_period); + assign(Slash, XK_slash); + + assign(Keypad0, XK_KP_0); + assign(Keypad1, XK_KP_1); + assign(Keypad2, XK_KP_2); + assign(Keypad3, XK_KP_3); + assign(Keypad4, XK_KP_4); + assign(Keypad5, XK_KP_5); + assign(Keypad6, XK_KP_6); + assign(Keypad7, XK_KP_7); + assign(Keypad8, XK_KP_8); + assign(Keypad9, XK_KP_9); + + assign(Add, XK_KP_Add); + assign(Subtract, XK_KP_Subtract); + assign(Multiply, XK_KP_Multiply); + assign(Divide, XK_KP_Divide); + assign(Enter, XK_KP_Enter); + + assign(Up, XK_Up); + assign(Down, XK_Down); + assign(Left, XK_Left); + assign(Right, XK_Right); + + assign(Tab, XK_Tab); + assign(Return, XK_Return); + assign(Spacebar, XK_space); + + assign(LeftControl, XK_Control_L); + assign(RightControl, XK_Control_R); + assign(LeftAlt, XK_Alt_L); + assign(RightAlt, XK_Alt_R); + assign(LeftShift, XK_Shift_L); + assign(RightShift, XK_Shift_R); + assign(LeftSuper, XK_Super_L); + assign(RightSuper, XK_Super_R); + assign(Menu, XK_Menu); + + #undef assign + + XCloseDisplay(display); +} diff --git a/ruby/ruby.cpp b/ruby/ruby.cpp new file mode 100755 index 00000000..8b84c4f4 --- /dev/null +++ b/ruby/ruby.cpp @@ -0,0 +1,383 @@ +#include +using namespace nall; + +#undef mkdir +#undef usleep +#include + +namespace ruby { + +VideoInterface video; +AudioInterface audio; +InputInterface input; + +/* VideoInterface */ + +const char *Video::Handle = "Handle"; +const char *Video::Synchronize = "Synchronize"; +const char *Video::Filter = "Filter"; +const char *Video::Shader = "Shader"; +const char *Video::FragmentShader = "FragmentShader"; +const char *Video::VertexShader = "VertexShader"; + +void VideoInterface::driver(const char *driver) { + if(p) term(); + + if(!driver || !*driver) driver = default_driver(); + + if(0); + + #ifdef VIDEO_DIRECT3D + else if(!strcmp(driver, "Direct3D")) p = new VideoD3D(); + #endif + + #ifdef VIDEO_DIRECTDRAW + else if(!strcmp(driver, "DirectDraw")) p = new VideoDD(); + #endif + + #ifdef VIDEO_GDI + else if(!strcmp(driver, "GDI")) p = new VideoGDI(); + #endif + + #ifdef VIDEO_GLX + else if(!strcmp(driver, "OpenGL")) p = new VideoGLX(); + #endif + + #ifdef VIDEO_QTOPENGL + else if(!strcmp(driver, "Qt-OpenGL")) p = new VideoQtOpenGL(); + #endif + + #ifdef VIDEO_QTRASTER + else if(!strcmp(driver, "Qt-Raster")) p = new VideoQtRaster(); + #endif + + #ifdef VIDEO_SDL + else if(!strcmp(driver, "SDL")) p = new VideoSDL(); + #endif + + #ifdef VIDEO_WGL + else if(!strcmp(driver, "OpenGL")) p = new VideoWGL(); + #endif + + #ifdef VIDEO_XV + else if(!strcmp(driver, "X-Video")) p = new VideoXv(); + #endif + + else p = new Video(); +} + +//select the *safest* available driver, not the fastest +const char* VideoInterface::default_driver() { + #if defined(VIDEO_DIRECT3D) + return "Direct3D"; + #elif defined(VIDEO_WGL) + return "OpenGL"; + #elif defined(VIDEO_DIRECTDRAW) + return "DirectDraw"; + #elif defined(VIDEO_GDI) + return "GDI"; + #elif defined(VIDEO_QTOPENGL) + return "Qt-OpenGL"; + #elif defined(VIDEO_QTRASTER) + return "Qt-Raster"; + #elif defined(VIDEO_SDL) + return "SDL"; + #elif defined(VIDEO_XV) + return "X-Video"; + #elif defined(VIDEO_GLX) + return "OpenGL"; + #else + return "None"; + #endif +} + +//returns list of available drivers, sorted from most to least optimal +const char* VideoInterface::driver_list() { + return + + //Windows + + #if defined(VIDEO_DIRECT3D) + "Direct3D;" + #endif + + #if defined(VIDEO_WGL) + "OpenGL;" + #endif + + #if defined(VIDEO_DIRECTDRAW) + "DirectDraw;" + #endif + + #if defined(VIDEO_GDI) + "GDI;" + #endif + + //Linux + + #if defined(VIDEO_GLX) + "OpenGL;" + #endif + + #if defined(VIDEO_QTOPENGL) + "Qt-OpenGL;" + #endif + + #if defined(VIDEO_XV) + "X-Video;" + #endif + + #if defined(VIDEO_QTRASTER) + "Qt-Raster;" + #endif + + #if defined(VIDEO_SDL) + "SDL;" + #endif + + "None"; +} + +bool VideoInterface::init() { + if(!p) driver(); + return p->init(); +} + +void VideoInterface::term() { + if(p) { + delete p; + p = 0; + } +} + +bool VideoInterface::cap(const string& name) { return p ? p->cap(name) : false; } +any VideoInterface::get(const string& name) { return p ? p->get(name) : false; } +bool VideoInterface::set(const string& name, const any& value) { return p ? p->set(name, value) : false; } +bool VideoInterface::lock(uint32_t *&data, unsigned &pitch, unsigned width, unsigned height) { return p ? p->lock(data, pitch, width, height) : false; } +void VideoInterface::unlock() { if(p) p->unlock(); } +void VideoInterface::clear() { if(p) p->clear(); } +void VideoInterface::refresh() { if(p) p->refresh(); } +VideoInterface::VideoInterface() : p(0) {} +VideoInterface::~VideoInterface() { term(); } + +/* AudioInterface */ + +void AudioInterface::driver(const char *driver) { + if(p) term(); + + if(!driver || !*driver) driver = default_driver(); + + if(0); + + #ifdef AUDIO_ALSA + else if(!strcmp(driver, "ALSA")) p = new AudioALSA(); + #endif + + #ifdef AUDIO_AO + else if(!strcmp(driver, "libao")) p = new AudioAO(); + #endif + + #ifdef AUDIO_DIRECTSOUND + else if(!strcmp(driver, "DirectSound")) p = new AudioDS(); + #endif + + #ifdef AUDIO_OPENAL + else if(!strcmp(driver, "OpenAL")) p = new AudioOpenAL(); + #endif + + #ifdef AUDIO_OSS + else if(!strcmp(driver, "OSS")) p = new AudioOSS(); + #endif + + #ifdef AUDIO_PULSEAUDIO + else if(!strcmp(driver, "PulseAudio")) p = new AudioPulseAudio(); + #endif + + #ifdef AUDIO_PULSEAUDIOSIMPLE + else if(!strcmp(driver, "PulseAudioSimple")) p = new AudioPulseAudioSimple(); + #endif + + #ifdef AUDIO_XAUDIO2 + else if(!strcmp(driver, "XAudio2")) p = new AudioXAudio2(); + #endif + + else p = new Audio(); +} + +//select the *safest* available driver, not the fastest +const char* AudioInterface::default_driver() { + #if defined(AUDIO_DIRECTSOUND) + return "DirectSound"; + #elif defined(AUDIO_XAUDIO2) + return "XAudio2"; + #elif defined(AUDIO_ALSA) + return "ALSA"; + #elif defined(AUDIO_OPENAL) + return "OpenAL"; + #elif defined(AUDIO_PULSEAUDIO) + return "PulseAudio"; + #elif defined(AUDIO_PULSEAUDIOSIMPLE) + return "PulseAudioSimple"; + #elif defined(AUDIO_AO) + return "libao"; + #elif defined(AUDIO_OSS) + return "OSS"; + #else + return "None"; + #endif +} + +//returns list of available drivers, sorted from most to least optimal +const char* AudioInterface::driver_list() { + return + + //Windows + + #if defined(AUDIO_DIRECTSOUND) + "DirectSound;" + #endif + + #if defined(AUDIO_XAUDIO2) + "XAudio2;" + #endif + + //Linux + + #if defined(AUDIO_ALSA) + "ALSA;" + #endif + + #if defined(AUDIO_OPENAL) + "OpenAL;" + #endif + + #if defined(AUDIO_OSS) + "OSS;" + #endif + + #if defined(AUDIO_PULSEAUDIO) + "PulseAudio;" + #endif + + #if defined(AUDIO_PULSEAUDIOSIMPLE) + "PulseAudioSimple;" + #endif + + #if defined(AUDIO_AO) + "libao;" + #endif + + "None"; +} + +#include "ruby_audio.cpp" + +/* InputInterface */ + +const char *Input::Handle = "Handle"; +const char *Input::KeyboardSupport = "KeyboardSupport"; +const char *Input::MouseSupport = "MouseSupport"; +const char *Input::JoypadSupport = "JoypadSupport"; + +void InputInterface::driver(const char *driver) { + if(p) term(); + + if(!driver || !*driver) driver = default_driver(); + + if(0); + + #ifdef INPUT_DIRECTINPUT + else if(!strcmp(driver, "DirectInput")) p = new InputDI(); + #endif + + #ifdef INPUT_RAWINPUT + else if(!strcmp(driver, "RawInput")) p = new InputRaw(); + #endif + + #ifdef INPUT_SDL + else if(!strcmp(driver, "SDL")) p = new InputSDL(); + #endif + + #ifdef INPUT_X + else if(!strcmp(driver, "X-Windows")) p = new InputX(); + #endif + + #ifdef INPUT_CARBON + else if(!strcmp(driver, "Carbon")) p = new InputCarbon(); + #endif + + else p = new Input(); +} + +//select the *safest* available driver, not the fastest +const char* InputInterface::default_driver() { + #if defined(INPUT_RAWINPUT) + return "RawInput"; + #elif defined(INPUT_DIRECTINPUT) + return "DirectInput"; + #elif defined(INPUT_SDL) + return "SDL"; + #elif defined(INPUT_X) + return "X-Windows"; + #elif defined(INPUT_CARBON) + return "Carbon"; + #else + return "none"; + #endif +} + +const char* InputInterface::driver_list() { + return + + //Windows + + #if defined(INPUT_RAWINPUT) + "RawInput;" + #endif + + #if defined(INPUT_DIRECTINPUT) + "DirectInput;" + #endif + + //Linux + + #if defined(INPUT_SDL) + "SDL;" + #endif + + #if defined(INPUT_X) + "X-Windows;" + #endif + + //OS X + + #if defined(INPUT_CARBON) + "Carbon;" + #endif + + "None"; +} + +bool InputInterface::init() { + if(!p) driver(); + return p->init(); +} + +void InputInterface::term() { + if(p) { + delete p; + p = 0; + } +} + +bool InputInterface::cap(const string& name) { return p ? p->cap(name) : false; } +any InputInterface::get(const string& name) { return p ? p->get(name) : false; } +bool InputInterface::set(const string& name, const any& value) { return p ? p->set(name, value) : false; } +bool InputInterface::acquire() { return p ? p->acquire() : false; } +bool InputInterface::unacquire() { return p ? p->unacquire() : false; } +bool InputInterface::acquired() { return p ? p->acquired() : false; } +bool InputInterface::poll(int16_t *table) { return p ? p->poll(table) : false; } +InputInterface::InputInterface() : p(0) {} +InputInterface::~InputInterface() { term(); } + +}; diff --git a/ruby/ruby.hpp b/ruby/ruby.hpp new file mode 100755 index 00000000..4d1b1a35 --- /dev/null +++ b/ruby/ruby.hpp @@ -0,0 +1,109 @@ +/* + ruby + version: 0.06 (2009-05-22) + license: public domain +*/ + +#ifndef RUBY_H +#define RUBY_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ruby { + +#include +#include +#include + +class VideoInterface { +public: + void driver(const char *driver = ""); + const char* default_driver(); + const char* driver_list(); + bool init(); + void term(); + + bool cap(const nall::string& name); + nall::any get(const nall::string& name); + bool set(const nall::string& name, const nall::any& value); + + bool lock(uint32_t *&data, unsigned &pitch, unsigned width, unsigned height); + void unlock(); + void clear(); + void refresh(); + VideoInterface(); + ~VideoInterface(); + +private: + Video *p; +}; + +class AudioInterface { +public: + void driver(const char *driver = ""); + const char* default_driver(); + const char* driver_list(); + bool init(); + void term(); + + bool cap(const nall::string& name); + nall::any get(const nall::string& name); + bool set(const nall::string& name, const nall::any& value); + + void sample(uint16_t left, uint16_t right); + void clear(); + AudioInterface(); + ~AudioInterface(); + +private: + Audio *p; + + unsigned volume; + + //resample unit + double hermite(double mu, double a, double b, double c, double d); + bool resample_enabled; + double r_step, r_frac; + int r_left[4], r_right[4]; +}; + +class InputInterface { +public: + void driver(const char *driver = ""); + const char* default_driver(); + const char* driver_list(); + bool init(); + void term(); + + bool cap(const nall::string& name); + nall::any get(const nall::string& name); + bool set(const nall::string& name, const nall::any& value); + + bool acquire(); + bool unacquire(); + bool acquired(); + + bool poll(int16_t *table); + InputInterface(); + ~InputInterface(); + +private: + Input *p; +}; + +extern VideoInterface video; +extern AudioInterface audio; +extern InputInterface input; + +}; + +#endif diff --git a/ruby/ruby_audio.cpp b/ruby/ruby_audio.cpp new file mode 100755 index 00000000..4afe1f56 --- /dev/null +++ b/ruby/ruby_audio.cpp @@ -0,0 +1,133 @@ +const char *Audio::Volume = "Volume"; +const char *Audio::Resample = "Resample"; +const char *Audio::ResampleRatio = "ResampleRatio"; + +const char *Audio::Handle = "Handle"; +const char *Audio::Synchronize = "Synchronize"; +const char *Audio::Frequency = "Frequency"; +const char *Audio::Latency = "Latency"; + +bool AudioInterface::init() { + if(!p) driver(); + return p->init(); +} + +void AudioInterface::term() { + if(p) { + delete p; + p = 0; + } +} + +bool AudioInterface::cap(const string& name) { + if(name == Audio::Volume) return true; + if(name == Audio::Resample) return true; + if(name == Audio::ResampleRatio) return true; + + return p ? p->cap(name) : false; +} + +any AudioInterface::get(const string& name) { + if(name == Audio::Volume) return volume; + if(name == Audio::Resample) return resample_enabled; + if(name == Audio::ResampleRatio); + + return p ? p->get(name) : false; +} + +bool AudioInterface::set(const string& name, const any& value) { + if(name == Audio::Volume) { + volume = any_cast(value); + return true; + } + + if(name == Audio::Resample) { + resample_enabled = any_cast(value); + return true; + } + + if(name == Audio::ResampleRatio) { + r_step = any_cast(value); + r_frac = 0; + return true; + } + + return p ? p->set(name, value) : false; +} + +//4-tap hermite interpolation +double AudioInterface::hermite(double mu1, double a, double b, double c, double d) { + const double tension = 0.0; //-1 = low, 0 = normal, 1 = high + const double bias = 0.0; //-1 = left, 0 = even, 1 = right + + double mu2, mu3, m0, m1, a0, a1, a2, a3; + + mu2 = mu1 * mu1; + mu3 = mu2 * mu1; + + m0 = (b - a) * (1 + bias) * (1 - tension) / 2; + m0 += (c - b) * (1 - bias) * (1 - tension) / 2; + m1 = (c - b) * (1 + bias) * (1 - tension) / 2; + m1 += (d - c) * (1 - bias) * (1 - tension) / 2; + + a0 = +2 * mu3 - 3 * mu2 + 1; + a1 = mu3 - 2 * mu2 + mu1; + a2 = mu3 - mu2; + a3 = -2 * mu3 + 3 * mu2; + + return (a0 * b) + (a1 * m0) + (a2 * m1) + (a3 * c); +} + +void AudioInterface::sample(uint16_t left, uint16_t right) { + int s_left = (int16_t)left; + int s_right = (int16_t)right; + + if(volume != 100) { + s_left = sclamp<16>((double)s_left * (double)volume / 100.0); + s_right = sclamp<16>((double)s_right * (double)volume / 100.0); + } + + r_left [0] = r_left [1]; + r_left [1] = r_left [2]; + r_left [2] = r_left [3]; + r_left [3] = s_left; + + r_right[0] = r_right[1]; + r_right[1] = r_right[2]; + r_right[2] = r_right[3]; + r_right[3] = s_right; + + if(resample_enabled == false) { + if(p) p->sample(left, right); + return; + } + + while(r_frac <= 1.0) { + int output_left = sclamp<16>(hermite(r_frac, r_left [0], r_left [1], r_left [2], r_left [3])); + int output_right = sclamp<16>(hermite(r_frac, r_right[0], r_right[1], r_right[2], r_right[3])); + r_frac += r_step; + if(p) p->sample(output_left, output_right); + } + + r_frac -= 1.0; +} + +void AudioInterface::clear() { + r_frac = 0; + r_left [0] = r_left [1] = r_left [2] = r_left [3] = 0; + r_right[0] = r_right[1] = r_right[2] = r_right[3] = 0; + if(p) p->clear(); +} + +AudioInterface::AudioInterface() { + p = 0; + volume = 100; + resample_enabled = false; + r_step = r_frac = 0; + r_left [0] = r_left [1] = r_left [2] = r_left [3] = 0; + r_right[0] = r_right[1] = r_right[2] = r_right[3] = 0; +} + +AudioInterface::~AudioInterface() { + term(); +} diff --git a/ruby/ruby_impl.cpp b/ruby/ruby_impl.cpp new file mode 100755 index 00000000..4b7049e2 --- /dev/null +++ b/ruby/ruby_impl.cpp @@ -0,0 +1,182 @@ +/* Global Headers */ + +#if defined(VIDEO_QTOPENGL) || defined(VIDEO_QTRASTER) + #include + #include +#endif + +#if defined(VIDEO_QTOPENGL) + #include + #if defined(PLATFORM_WIN) + #include + #endif +#endif + +#if defined(PLATFORM_X) + #include + #include + #include +#elif defined(PLATFORM_OSX) + #define __INTEL_COMPILER + #include +#elif defined(PLATFORM_WIN) + #define _WIN32_WINNT 0x0501 + #include +#endif + +/* Video */ + +#define DeclareVideo(Name) \ + class Video##Name : public Video { \ + public: \ + bool cap(const string& name) { return p.cap(name); } \ + any get(const string& name) { return p.get(name); } \ + bool set(const string& name, const any& value) { return p.set(name, value); } \ + \ + bool lock(uint32_t *&data, unsigned &pitch, unsigned width, unsigned height) { return p.lock(data, pitch, width, height); } \ + void unlock() { p.unlock(); } \ + \ + void clear() { p.clear(); } \ + void refresh() { p.refresh(); } \ + bool init() { return p.init(); } \ + void term() { p.term(); } \ + \ + Video##Name() : p(*new pVideo##Name) {} \ + ~Video##Name() { delete &p; } \ + \ + private: \ + pVideo##Name &p; \ + }; + +#ifdef VIDEO_DIRECT3D + #include +#endif + +#ifdef VIDEO_DIRECTDRAW + #include +#endif + +#ifdef VIDEO_GDI + #include +#endif + +#ifdef VIDEO_GLX + #include +#endif + +#ifdef VIDEO_QTOPENGL + #include +#endif + +#ifdef VIDEO_QTRASTER + #include +#endif + +#ifdef VIDEO_SDL + #include +#endif + +#ifdef VIDEO_WGL + #include +#endif + +#ifdef VIDEO_XV + #include +#endif + +/* Audio */ + +#define DeclareAudio(Name) \ + class Audio##Name : public Audio { \ + public: \ + bool cap(const string& name) { return p.cap(name); } \ + any get(const string& name) { return p.get(name); } \ + bool set(const string& name, const any& value) { return p.set(name, value); } \ + \ + void sample(uint16_t left, uint16_t right) { p.sample(left, right); } \ + void clear() { p.clear(); } \ + bool init() { return p.init(); } \ + void term() { p.term(); } \ + \ + Audio##Name() : p(*new pAudio##Name) {} \ + ~Audio##Name() { delete &p; } \ + \ + private: \ + pAudio##Name &p; \ + }; + +#ifdef AUDIO_ALSA + #include +#endif + +#ifdef AUDIO_AO + #include +#endif + +#ifdef AUDIO_DIRECTSOUND + #include +#endif + +#ifdef AUDIO_OPENAL + #include +#endif + +#ifdef AUDIO_OSS + #include +#endif + +#ifdef AUDIO_PULSEAUDIO + #include +#endif + +#ifdef AUDIO_PULSEAUDIOSIMPLE + #include +#endif + +#ifdef AUDIO_XAUDIO2 + #include +#endif + +/* Input */ + +#define DeclareInput(Name) \ + class Input##Name : public Input { \ + public: \ + bool cap(const string& name) { return p.cap(name); } \ + any get(const string& name) { return p.get(name); } \ + bool set(const string& name, const any& value) { return p.set(name, value); } \ + \ + bool acquire() { return p.acquire(); } \ + bool unacquire() { return p.unacquire(); } \ + bool acquired() { return p.acquired(); } \ + \ + bool poll(int16_t *table) { return p.poll(table); } \ + bool init() { return p.init(); } \ + void term() { p.term(); } \ + \ + Input##Name() : p(*new pInput##Name) {} \ + ~Input##Name() { delete &p; } \ + \ + private: \ + pInput##Name &p; \ + }; + +#ifdef INPUT_DIRECTINPUT + #include +#endif + +#ifdef INPUT_RAWINPUT + #include +#endif + +#ifdef INPUT_SDL + #include +#endif + +#ifdef INPUT_X + #include +#endif + +#ifdef INPUT_CARBON + #include +#endif diff --git a/ruby/video.hpp b/ruby/video.hpp new file mode 100755 index 00000000..c79c5531 --- /dev/null +++ b/ruby/video.hpp @@ -0,0 +1,29 @@ +class Video { +public: + static const char *Handle; + static const char *Synchronize; + static const char *Filter; + static const char *Shader; + static const char *FragmentShader; + static const char *VertexShader; + + enum Filter { + FilterPoint, + FilterLinear, + }; + + virtual bool cap(const nall::string& name) { return false; } + virtual nall::any get(const nall::string& name) { return false; } + virtual bool set(const nall::string& name, const nall::any& value) { return false; } + + virtual bool lock(uint32_t *&data, unsigned &pitch, unsigned width, unsigned height) { return false; } + virtual void unlock() {} + + virtual void clear() {} + virtual void refresh() {} + virtual bool init() { return true; } + virtual void term() {} + + Video() {} + virtual ~Video() {} +}; diff --git a/ruby/video/direct3d.cpp b/ruby/video/direct3d.cpp new file mode 100755 index 00000000..a9404d8e --- /dev/null +++ b/ruby/video/direct3d.cpp @@ -0,0 +1,469 @@ +#undef interface +#define interface struct +#include +#include +#undef interface + +#define D3DVERTEX (D3DFVF_XYZRHW | D3DFVF_TEX1) + +typedef HRESULT (__stdcall *EffectProc)(LPDIRECT3DDEVICE9, LPCVOID, UINT, D3DXMACRO const*, LPD3DXINCLUDE, DWORD, LPD3DXEFFECTPOOL, LPD3DXEFFECT*, LPD3DXBUFFER*); +typedef HRESULT (__stdcall *TextureProc)(LPDIRECT3DDEVICE9, LPCTSTR, LPDIRECT3DTEXTURE9*); + +namespace ruby { + +class pVideoD3D { +public: + LPDIRECT3D9 lpd3d; + LPDIRECT3DDEVICE9 device; + LPDIRECT3DVERTEXBUFFER9 vertex_buffer, *vertex_ptr; + D3DPRESENT_PARAMETERS presentation; + D3DSURFACE_DESC d3dsd; + D3DLOCKED_RECT d3dlr; + D3DRASTER_STATUS d3drs; + D3DCAPS9 d3dcaps; + LPDIRECT3DTEXTURE9 texture; + LPDIRECT3DSURFACE9 surface; + LPD3DXEFFECT effect; + string shader_source_xml; + + bool lost; + unsigned iwidth, iheight; + + struct d3dvertex { + float x, y, z, rhw; //screen coords + float u, v; //texture coords + }; + + struct { + uint32_t t_usage, v_usage; + uint32_t t_pool, v_pool; + uint32_t lock; + uint32_t filter; + } flags; + + struct { + bool dynamic; //device supports dynamic textures + bool shader; //device supports pixel shaders + } caps; + + struct { + HWND handle; + bool synchronize; + unsigned filter; + + unsigned width; + unsigned height; + } settings; + + struct { + unsigned width; + unsigned height; + } state; + + bool cap(const string& name) { + if(name == Video::Handle) return true; + if(name == Video::Synchronize) return true; + if(name == Video::Filter) return true; + if(name == Video::Shader) return true; + return false; + } + + any get(const string& name) { + if(name == Video::Handle) return (uintptr_t)settings.handle; + if(name == Video::Synchronize) return settings.synchronize; + if(name == Video::Filter) return settings.filter; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Video::Handle) { + settings.handle = (HWND)any_cast(value); + return true; + } + + if(name == Video::Synchronize) { + settings.synchronize = any_cast(value); + return true; + } + + if(name == Video::Filter) { + settings.filter = any_cast(value); + if(lpd3d) update_filter(); + return true; + } + + if(name == Video::Shader) { + set_shader(any_cast(value)); + return true; + } + + return false; + } + + bool recover() { + if(!device) return false; + + if(lost) { + release_resources(); + if(device->Reset(&presentation) != D3D_OK) return false; + } + + lost = false; + + device->SetDialogBoxMode(false); + + device->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1); + device->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE); + device->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE); + + device->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); + device->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + device->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); + + device->SetRenderState(D3DRS_LIGHTING, false); + device->SetRenderState(D3DRS_ZENABLE, false); + device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); + + device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); + device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); + device->SetRenderState(D3DRS_ALPHABLENDENABLE, false); + + device->SetVertexShader(NULL); + device->SetFVF(D3DVERTEX); + + device->CreateVertexBuffer(sizeof(d3dvertex) * 4, flags.v_usage, D3DVERTEX, (D3DPOOL)flags.v_pool, &vertex_buffer, NULL); + iwidth = 0; + iheight = 0; + resize(settings.width = 256, settings.height = 256); + update_filter(); + clear(); + return true; + } + + unsigned rounded_power_of_two(unsigned n) { + n--; + n |= n >> 1; + n |= n >> 2; + n |= n >> 4; + n |= n >> 8; + n |= n >> 16; + return n + 1; + } + + void resize(unsigned width, unsigned height) { + if(iwidth >= width && iheight >= height) return; + + iwidth = rounded_power_of_two(max(width, iwidth )); + iheight = rounded_power_of_two(max(height, iheight)); + + if(d3dcaps.MaxTextureWidth < iwidth || d3dcaps.MaxTextureWidth < iheight) { + //TODO: attempt to handle this more gracefully + return; + } + + if(texture) texture->Release(); + device->CreateTexture(iwidth, iheight, 1, flags.t_usage, D3DFMT_X8R8G8B8, (D3DPOOL)flags.t_pool, &texture, NULL); + } + + void update_filter() { + if(!device) return; + if(lost && !recover()) return; + + switch(settings.filter) { default: + case Video::FilterPoint: flags.filter = D3DTEXF_POINT; break; + case Video::FilterLinear: flags.filter = D3DTEXF_LINEAR; break; + } + + device->SetSamplerState(0, D3DSAMP_MINFILTER, flags.filter); + device->SetSamplerState(0, D3DSAMP_MAGFILTER, flags.filter); + } + + // Vertex format: + // + // 0----------1 + // | /| + // | / | + // | / | + // | / | + // | / | + // 2----------3 + // + // (x,y) screen coords, in pixels + // (u,v) texture coords, betweeen 0.0 (top, left) to 1.0 (bottom, right) + void set_vertex( + uint32_t px, uint32_t py, uint32_t pw, uint32_t ph, + uint32_t tw, uint32_t th, + uint32_t x, uint32_t y, uint32_t w, uint32_t h + ) { + d3dvertex vertex[4]; + vertex[0].x = vertex[2].x = (double)(x - 0.5); + vertex[1].x = vertex[3].x = (double)(x + w - 0.5); + vertex[0].y = vertex[1].y = (double)(y - 0.5); + vertex[2].y = vertex[3].y = (double)(y + h - 0.5); + + //Z-buffer and RHW are unused for 2D blit, set to normal values + vertex[0].z = vertex[1].z = vertex[2].z = vertex[3].z = 0.0; + vertex[0].rhw = vertex[1].rhw = vertex[2].rhw = vertex[3].rhw = 1.0; + + double rw = (double)w / (double)pw * (double)tw; + double rh = (double)h / (double)ph * (double)th; + vertex[0].u = vertex[2].u = (double)(px ) / rw; + vertex[1].u = vertex[3].u = (double)(px + w) / rw; + vertex[0].v = vertex[1].v = (double)(py ) / rh; + vertex[2].v = vertex[3].v = (double)(py + h) / rh; + + vertex_buffer->Lock(0, sizeof(d3dvertex) * 4, (void**)&vertex_ptr, 0); + memcpy(vertex_ptr, vertex, sizeof(d3dvertex) * 4); + vertex_buffer->Unlock(); + + device->SetStreamSource(0, vertex_buffer, 0, sizeof(d3dvertex)); + } + + void clear() { + if(lost && !recover()) return; + + texture->GetLevelDesc(0, &d3dsd); + texture->GetSurfaceLevel(0, &surface); + + if(surface) { + device->ColorFill(surface, 0, D3DCOLOR_XRGB(0x00, 0x00, 0x00)); + surface->Release(); + } + + //clear primary display and all backbuffers + for(unsigned i = 0; i < 3; i++) { + device->Clear(0, 0, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0x00, 0x00, 0x00), 1.0f, 0); + device->Present(0, 0, 0, 0); + } + } + + bool lock(uint32_t *&data, unsigned &pitch, unsigned width, unsigned height) { + if(lost && !recover()) return false; + + if(width != settings.width || height != settings.height) { + resize(settings.width = width, settings.height = height); + } + + texture->GetLevelDesc(0, &d3dsd); + texture->GetSurfaceLevel(0, &surface); + + surface->LockRect(&d3dlr, 0, flags.lock); + pitch = d3dlr.Pitch; + return data = (uint32_t*)d3dlr.pBits; + } + + void unlock() { + surface->UnlockRect(); + surface->Release(); + } + + void refresh() { + if(lost && !recover()) return; + + RECT rd, rs; //dest, source rectangles + GetClientRect(settings.handle, &rd); + SetRect(&rs, 0, 0, settings.width, settings.height); + + //if output size changed, driver must be re-initialized. + //failure to do so causes scaling issues on some video drivers. + if(state.width != rd.right || state.height != rd.bottom) { + init(); + set_shader(shader_source_xml); + return; + } + + if(caps.shader && effect) { + device->BeginScene(); + set_vertex(0, 0, settings.width, settings.height, iwidth, iheight, 0, 0, rd.right, rd.bottom); + + D3DXVECTOR4 rubyTextureSize; + rubyTextureSize.x = iwidth; + rubyTextureSize.y = iheight; + rubyTextureSize.z = 1.0 / iheight; + rubyTextureSize.w = 1.0 / iwidth; + effect->SetVector("rubyTextureSize", &rubyTextureSize); + + D3DXVECTOR4 rubyInputSize; + rubyInputSize.x = settings.width; + rubyInputSize.y = settings.height; + rubyInputSize.z = 1.0 / settings.height; + rubyInputSize.w = 1.0 / settings.width; + effect->SetVector("rubyInputSize", &rubyInputSize); + + D3DXVECTOR4 rubyOutputSize; + rubyOutputSize.x = rd.right; + rubyOutputSize.y = rd.bottom; + rubyOutputSize.z = 1.0 / rd.bottom; + rubyOutputSize.w = 1.0 / rd.right; + effect->SetVector("rubyOutputSize", &rubyOutputSize); + + UINT passes; + effect->Begin(&passes, 0); + effect->SetTexture("rubyTexture", texture); + device->SetTexture(0, texture); + for(unsigned pass = 0; pass < passes; pass++) { + effect->BeginPass(pass); + device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2); + effect->EndPass(); + } + effect->End(); + device->EndScene(); + } else { + device->BeginScene(); + set_vertex(0, 0, settings.width, settings.height, iwidth, iheight, 0, 0, rd.right, rd.bottom); + device->SetTexture(0, texture); + device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2); + device->EndScene(); + } + + if(settings.synchronize) { + while(true) { + D3DRASTER_STATUS status; + device->GetRasterStatus(0, &status); + if(status.InVBlank == true) break; + } + } + + if(device->Present(0, 0, 0, 0) == D3DERR_DEVICELOST) lost = true; + } + + void set_shader(const char *source) { + if(!caps.shader) return; + + if(effect) { + effect->Release(); + effect = NULL; + } + + if(!source || !*source) { + shader_source_xml = ""; + return; + } + shader_source_xml = source; + + bool is_hlsl = false; + string shader_source; + xml_element document = xml_parse(shader_source_xml); + foreach(head, document.element) { + if(head.name == "shader") { + foreach(attribute, head.attribute) { + if(attribute.name == "language" && attribute.content == "HLSL") is_hlsl = true; + } + foreach(element, head.element) { + if(element.name == "source") { + if(is_hlsl) shader_source = element.parse(); + } + } + } + } + if(shader_source == "") return; + + HMODULE d3dx; + for(unsigned i = 0; i < 256; i++) { + char t[256]; + sprintf(t, "d3dx9_%u.dll", i); + d3dx = LoadLibraryW(utf16_t(t)); + if(d3dx) break; + } + if(!d3dx) d3dx = LoadLibraryW(L"d3dx9.dll"); + if(!d3dx) return; + + EffectProc effectProc = (EffectProc)GetProcAddress(d3dx, "D3DXCreateEffect"); + TextureProc textureProc = (TextureProc)GetProcAddress(d3dx, "D3DXCreateTextureFromFileA"); + + LPD3DXBUFFER pBufferErrors = NULL; + effectProc(device, shader_source, lstrlenA(shader_source), NULL, NULL, 0, NULL, &effect, &pBufferErrors); + + D3DXHANDLE hTech; + effect->FindNextValidTechnique(NULL, &hTech); + effect->SetTechnique(hTech); + } + + bool init() { + term(); + + RECT rd; + GetClientRect(settings.handle, &rd); + state.width = rd.right; + state.height = rd.bottom; + + lpd3d = Direct3DCreate9(D3D_SDK_VERSION); + if(!lpd3d) return false; + + memset(&presentation, 0, sizeof(presentation)); + presentation.Flags = D3DPRESENTFLAG_VIDEO; + presentation.SwapEffect = D3DSWAPEFFECT_FLIP; + presentation.hDeviceWindow = settings.handle; + presentation.BackBufferCount = 1; + presentation.MultiSampleType = D3DMULTISAMPLE_NONE; + presentation.MultiSampleQuality = 0; + presentation.EnableAutoDepthStencil = false; + presentation.AutoDepthStencilFormat = D3DFMT_UNKNOWN; + presentation.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; + presentation.Windowed = true; + presentation.BackBufferFormat = D3DFMT_UNKNOWN; + presentation.BackBufferWidth = 0; + presentation.BackBufferHeight = 0; + + if(lpd3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, settings.handle, + D3DCREATE_SOFTWARE_VERTEXPROCESSING, &presentation, &device) != D3D_OK) { + return false; + } + + device->GetDeviceCaps(&d3dcaps); + + caps.dynamic = bool(d3dcaps.Caps2 & D3DCAPS2_DYNAMICTEXTURES); + caps.shader = d3dcaps.PixelShaderVersion > D3DPS_VERSION(1, 4); + + if(caps.dynamic == true) { + flags.t_usage = D3DUSAGE_DYNAMIC; + flags.v_usage = D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC; + flags.t_pool = D3DPOOL_DEFAULT; + flags.v_pool = D3DPOOL_DEFAULT; + flags.lock = D3DLOCK_NOSYSLOCK | D3DLOCK_DISCARD; + } else { + flags.t_usage = 0; + flags.v_usage = D3DUSAGE_WRITEONLY; + flags.t_pool = D3DPOOL_MANAGED; + flags.v_pool = D3DPOOL_MANAGED; + flags.lock = D3DLOCK_NOSYSLOCK | D3DLOCK_DISCARD; + } + + lost = false; + recover(); + return true; + } + + void release_resources() { + if(effect) { effect->Release(); effect = 0; } + if(vertex_buffer) { vertex_buffer->Release(); vertex_buffer = 0; } + if(surface) { surface->Release(); surface = 0; } + if(texture) { texture->Release(); texture = 0; } + } + + void term() { + release_resources(); + if(device) { device->Release(); device = 0; } + if(lpd3d) { lpd3d->Release(); lpd3d = 0; } + } + + pVideoD3D() { + effect = 0; + vertex_buffer = 0; + surface = 0; + texture = 0; + device = 0; + lpd3d = 0; + lost = true; + + settings.handle = 0; + settings.synchronize = false; + settings.filter = Video::FilterLinear; + } +}; + +DeclareVideo(D3D) + +}; + +#undef D3DVERTEX diff --git a/ruby/video/directdraw.cpp b/ruby/video/directdraw.cpp new file mode 100755 index 00000000..5a77ec44 --- /dev/null +++ b/ruby/video/directdraw.cpp @@ -0,0 +1,186 @@ +#include + +namespace ruby { + +class pVideoDD { +public: + LPDIRECTDRAW lpdd; + LPDIRECTDRAW7 lpdd7; + LPDIRECTDRAWSURFACE7 screen, raster; + LPDIRECTDRAWCLIPPER clipper; + DDSURFACEDESC2 ddsd; + DDSCAPS2 ddscaps; + unsigned iwidth, iheight; + + struct { + HWND handle; + bool synchronize; + + unsigned width; + unsigned height; + } settings; + + bool cap(const string& name) { + if(name == Video::Handle) return true; + if(name == Video::Synchronize) return true; + return false; + } + + any get(const string& name) { + if(name == Video::Handle) return (uintptr_t)settings.handle; + if(name == Video::Synchronize) return settings.synchronize; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Video::Handle) { + settings.handle = (HWND)any_cast(value); + return true; + } + + if(name == Video::Synchronize) { + settings.synchronize = any_cast(value); + return true; + } + + return false; + } + + void resize(unsigned width, unsigned height) { + if(iwidth >= width && iheight >= height) return; + + iwidth = max(width, iwidth); + iheight = max(height, iheight); + + if(raster) raster->Release(); + + screen->GetSurfaceDesc(&ddsd); + int depth = ddsd.ddpfPixelFormat.dwRGBBitCount; + if(depth == 32) goto try_native_surface; + + memset(&ddsd, 0, sizeof(DDSURFACEDESC2)); + ddsd.dwSize = sizeof(DDSURFACEDESC2); + ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT | DDSD_PIXELFORMAT; + ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_VIDEOMEMORY; //DDSCAPS_SYSTEMMEMORY + ddsd.dwWidth = iwidth; + ddsd.dwHeight = iheight; + + ddsd.ddpfPixelFormat.dwSize = sizeof(DDPIXELFORMAT); + ddsd.ddpfPixelFormat.dwFlags = DDPF_RGB; + ddsd.ddpfPixelFormat.dwRGBBitCount = 32; + ddsd.ddpfPixelFormat.dwRBitMask = 0xff0000; + ddsd.ddpfPixelFormat.dwGBitMask = 0x00ff00; + ddsd.ddpfPixelFormat.dwBBitMask = 0x0000ff; + + if(lpdd7->CreateSurface(&ddsd, &raster, 0) == DD_OK) return clear(); + + try_native_surface: + memset(&ddsd, 0, sizeof(DDSURFACEDESC2)); + ddsd.dwSize = sizeof(DDSURFACEDESC2); + ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT; + ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_VIDEOMEMORY; //DDSCAPS_SYSTEMMEMORY + ddsd.dwWidth = iwidth; + ddsd.dwHeight = iheight; + + if(lpdd7->CreateSurface(&ddsd, &raster, 0) == DD_OK) return clear(); + } + + void clear() { + DDBLTFX fx; + fx.dwSize = sizeof(DDBLTFX); + fx.dwFillColor = 0x00000000; + screen->Blt(0, 0, 0, DDBLT_WAIT | DDBLT_COLORFILL, &fx); + raster->Blt(0, 0, 0, DDBLT_WAIT | DDBLT_COLORFILL, &fx); + } + + bool lock(uint32_t *&data, unsigned &pitch, unsigned width, unsigned height) { + if(width != settings.width || height != settings.height) { + resize(settings.width = width, settings.height = height); + } + + if(raster->Lock(0, &ddsd, DDLOCK_WAIT, 0) != DD_OK) { + raster->Restore(); + if(raster->Lock(0, &ddsd, DDLOCK_WAIT, 0) != DD_OK) return false; + } + pitch = ddsd.lPitch; + return data = (uint32_t*)ddsd.lpSurface; + } + + void unlock() { + raster->Unlock(0); + } + + void refresh() { + if(settings.synchronize) { + while(true) { + BOOL in_vblank; + lpdd7->GetVerticalBlankStatus(&in_vblank); + if(in_vblank == true) break; + } + } + + HRESULT hr; + RECT rd, rs; + SetRect(&rs, 0, 0, settings.width, settings.height); + + POINT p = { 0, 0 }; + ClientToScreen(settings.handle, &p); + GetClientRect(settings.handle, &rd); + OffsetRect(&rd, p.x, p.y); + + if(screen->Blt(&rd, raster, &rs, DDBLT_WAIT, 0) == DDERR_SURFACELOST) { + screen->Restore(); + raster->Restore(); + } + } + + bool init() { + term(); + + DirectDrawCreate(0, &lpdd, 0); + lpdd->QueryInterface(IID_IDirectDraw7, (void**)&lpdd7); + if(lpdd) { lpdd->Release(); lpdd = 0; } + + lpdd7->SetCooperativeLevel(settings.handle, DDSCL_NORMAL); + + memset(&ddsd, 0, sizeof(DDSURFACEDESC2)); + ddsd.dwSize = sizeof(DDSURFACEDESC2); + + ddsd.dwFlags = DDSD_CAPS; + ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE; + lpdd7->CreateSurface(&ddsd, &screen, 0); + + lpdd7->CreateClipper(0, &clipper, 0); + clipper->SetHWnd(0, settings.handle); + screen->SetClipper(clipper); + + raster = 0; + iwidth = 0; + iheight = 0; + resize(settings.width = 256, settings.height = 256); + + return true; + } + + void term() { + if(clipper) { clipper->Release(); clipper = 0; } + if(raster) { raster->Release(); raster = 0; } + if(screen) { screen->Release(); screen = 0; } + if(lpdd7) { lpdd7->Release(); lpdd7 = 0; } + if(lpdd) { lpdd->Release(); lpdd = 0; } + } + + pVideoDD() { + lpdd = 0; + lpdd7 = 0; + screen = 0; + raster = 0; + clipper = 0; + + settings.handle = 0; + } +}; + +DeclareVideo(DD) + +}; diff --git a/ruby/video/gdi.cpp b/ruby/video/gdi.cpp new file mode 100755 index 00000000..c79f6d65 --- /dev/null +++ b/ruby/video/gdi.cpp @@ -0,0 +1,100 @@ +#include + +namespace ruby { + +class pVideoGDI { +public: + uint32_t *buffer; + HBITMAP bitmap; + HDC bitmapdc; + BITMAPINFO bmi; + + struct { + HWND handle; + + unsigned width; + unsigned height; + } settings; + + bool cap(const string& name) { + if(name == Video::Handle) return true; + return false; + } + + any get(const string& name) { + if(name == Video::Handle) return (uintptr_t)settings.handle; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Video::Handle) { + settings.handle = (HWND)any_cast(value); + return true; + } + + return false; + } + + bool lock(uint32_t *&data, unsigned &pitch, unsigned width, unsigned height) { + settings.width = width; + settings.height = height; + + pitch = 1024 * 4; + return data = buffer; + } + + void unlock() {} + + void clear() {} + + void refresh() { + RECT rc; + GetClientRect(settings.handle, &rc); + + SetDIBits(bitmapdc, bitmap, 0, settings.height, (void*)buffer, &bmi, DIB_RGB_COLORS); + HDC hdc = GetDC(settings.handle); + StretchBlt(hdc, rc.left, rc.top, rc.right, rc.bottom, bitmapdc, 0, 1024 - settings.height, settings.width, settings.height, SRCCOPY); + ReleaseDC(settings.handle, hdc); + } + + bool init() { + HDC hdc = GetDC(settings.handle); + bitmapdc = CreateCompatibleDC(hdc); + assert(bitmapdc); + bitmap = CreateCompatibleBitmap(hdc, 1024, 1024); + assert(bitmap); + SelectObject(bitmapdc, bitmap); + ReleaseDC(settings.handle, hdc); + + memset(&bmi, 0, sizeof(BITMAPINFO)); + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = 1024; + bmi.bmiHeader.biHeight = -1024; + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; //biBitCount of 15 is invalid, biBitCount of 16 is really RGB555 + bmi.bmiHeader.biCompression = BI_RGB; + bmi.bmiHeader.biSizeImage = 1024 * 1024 * sizeof(uint32_t); + + settings.width = 256; + settings.height = 256; + return true; + } + + void term() { + DeleteObject(bitmap); + DeleteDC(bitmapdc); + } + + pVideoGDI() { + buffer = (uint32_t*)malloc(1024 * 1024 * sizeof(uint32_t)); + settings.handle = 0; + } + + ~pVideoGDI() { + if(buffer) free(buffer); + } +}; + +DeclareVideo(GDI) + +}; diff --git a/ruby/video/glx.cpp b/ruby/video/glx.cpp new file mode 100755 index 00000000..1f6f9a3b --- /dev/null +++ b/ruby/video/glx.cpp @@ -0,0 +1,237 @@ +/* + video.glx + author: byuu + license: public domain + last updated: 2010-09-28 + + Design notes: + SGI's GLX is the X11/Xlib interface to OpenGL. + At the time of this writing, there are three relevant versions of the API: versions 1.2, 1.3 and 1.4. + + Version 1.2 was released on March 4th, 1997. + Version 1.3 was released on October 19th, 1998. + Version 1.4 was released on December 16th, 2005. + + Despite version 1.3 being roughly ten years old at this time, there are still many modern X11 GLX drivers + that lack full support for the specification. Most notable would be the official video drivers from ATI. + Given this, 1.4 support is pretty much hopeless to target. + + Luckily, each version has been designed to be backwards compatible with the previous version. As well, + version 1.2 is wholly sufficient, albeit less convenient, to implement this video module. + + Therefore, for the purpose of compatibility, this driver only uses GLX 1.2 or earlier API commands. + As well, it only uses raw Xlib API commands, so that it is compatible with any toolkit. +*/ + +#include "opengl.hpp" + +namespace ruby { + +//returns true once window is mapped (created and displayed onscreen) +static Bool glx_wait_for_map_notify(Display *d, XEvent *e, char *arg) { + return (e->type == MapNotify) && (e->xmap.window == (Window)arg); +} + +class pVideoGLX : public OpenGL { +public: + int (*glSwapInterval)(int); + + Display *display; + int screen; + Window xwindow; + Colormap colormap; + GLXContext glxcontext; + GLXWindow glxwindow; + + struct { + int version_major, version_minor; + bool double_buffer; + bool is_direct; + } glx; + + struct { + Window handle; + bool synchronize; + unsigned filter; + + unsigned width; + unsigned height; + } settings; + + bool cap(const string& name) { + if(name == Video::Handle) return true; + if(name == Video::Synchronize) return true; + if(name == Video::Filter) return true; + if(name == Video::Shader) return true; + if(name == Video::FragmentShader) return true; + if(name == Video::VertexShader) return true; + return false; + } + + any get(const string& name) { + if(name == Video::Handle) return (uintptr_t)settings.handle; + if(name == Video::Synchronize) return settings.synchronize; + if(name == Video::Filter) return settings.filter; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Video::Handle) { + settings.handle = any_cast(value); + return true; + } + + if(name == Video::Synchronize) { + if(settings.synchronize != any_cast(value)) { + settings.synchronize = any_cast(value); + if(glSwapInterval) glSwapInterval(settings.synchronize); + return true; + } + } + + if(name == Video::Filter) { + settings.filter = any_cast(value); + return true; + } + + if(name == Video::Shader) { + OpenGL::set_shader(any_cast(value)); + return true; + } + + if(name == Video::FragmentShader) { + OpenGL::set_fragment_shader(any_cast(value)); + return true; + } + + if(name == Video::VertexShader) { + OpenGL::set_vertex_shader(any_cast(value)); + return true; + } + + return false; + } + + bool lock(uint32_t *&data, unsigned &pitch, unsigned width, unsigned height) { + resize(width, height); + settings.width = width; + settings.height = height; + return OpenGL::lock(data, pitch); + } + + void unlock() { + } + + void clear() { + OpenGL::clear(); + if(glx.double_buffer) glXSwapBuffers(display, glxwindow); + } + + void refresh() { + //we must ensure that the child window is the same size as the parent window. + //unfortunately, we cannot hook the parent window resize event notification, + //as we did not create the parent window, nor have any knowledge of the toolkit used. + //therefore, inelegant as it may be, we query each window size and resize as needed. + XWindowAttributes parent, child; + XGetWindowAttributes(display, settings.handle, &parent); + XGetWindowAttributes(display, xwindow, &child); + if(child.width != parent.width || child.height != parent.height) { + XResizeWindow(display, xwindow, parent.width, parent.height); + } + + OpenGL::refresh(settings.filter == Video::FilterLinear, + settings.width, settings.height, parent.width, parent.height); + if(glx.double_buffer) glXSwapBuffers(display, glxwindow); + } + + bool init() { + term(); + + display = XOpenDisplay(0); + screen = DefaultScreen(display); + glXQueryVersion(display, &glx.version_major, &glx.version_minor); + //require GLX 1.2+ API + if(glx.version_major < 1 || (glx.version_major == 1 && glx.version_minor < 2)) return false; + + XWindowAttributes window_attributes; + XGetWindowAttributes(display, settings.handle, &window_attributes); + + //let GLX determine the best Visual to use for GL output; provide a few hints + //note: some video drivers will override double buffering attribute + int attributelist[] = { GLX_RGBA, GLX_DOUBLEBUFFER, None }; + XVisualInfo *vi = glXChooseVisual(display, screen, attributelist); + + //Window settings.handle has already been realized, most likely with DefaultVisual. + //GLX requires that the GL output window has the same Visual as the GLX context. + //it is not possible to change the Visual of an already realized (created) window. + //therefore a new child window, using the same GLX Visual, must be created and binded to settings.handle. + colormap = XCreateColormap(display, RootWindow(display, vi->screen), vi->visual, AllocNone); + XSetWindowAttributes attributes; + attributes.colormap = colormap; + attributes.border_pixel = 0; + attributes.event_mask = StructureNotifyMask; + xwindow = XCreateWindow(display, /* parent = */ settings.handle, + /* x = */ 0, /* y = */ 0, window_attributes.width, window_attributes.height, + /* border_width = */ 0, vi->depth, InputOutput, vi->visual, + CWColormap | CWBorderPixel | CWEventMask, &attributes); + XSetWindowBackground(display, xwindow, /* color = */ 0); + XMapWindow(display, xwindow); + XEvent event; + //window must be realized (appear onscreen) before we make the context current + XIfEvent(display, &event, glx_wait_for_map_notify, (char*)xwindow); + + glxcontext = glXCreateContext(display, vi, /* sharelist = */ 0, /* direct = */ GL_TRUE); + glXMakeCurrent(display, glxwindow = xwindow, glxcontext); + + //read attributes of frame buffer for later use, as requested attributes from above are not always granted + int value = 0; + glXGetConfig(display, vi, GLX_DOUBLEBUFFER, &value); + glx.double_buffer = value; + glx.is_direct = glXIsDirect(display, glxcontext); + + OpenGL::init(); + settings.width = 256; + settings.height = 256; + + //vertical synchronization + if(!glSwapInterval) glSwapInterval = (int (*)(int))glGetProcAddress("glXSwapIntervalSGI"); + if(!glSwapInterval) glSwapInterval = (int (*)(int))glGetProcAddress("glXSwapIntervalMESA"); + if( glSwapInterval) glSwapInterval(settings.synchronize); + + return true; + } + + void term() { + OpenGL::term(); + + if(glxcontext) { + glXDestroyContext(display, glxcontext); + glxcontext = 0; + } + + if(xwindow) { + XUnmapWindow(display, xwindow); + xwindow = 0; + } + + if(colormap) { + XFreeColormap(display, colormap); + colormap = 0; + } + } + + pVideoGLX() : glSwapInterval(0) { + settings.handle = 0; + settings.synchronize = false; + xwindow = 0; + colormap = 0; + glxcontext = 0; + glxwindow = 0; + } + + ~pVideoGLX() { term(); } +}; + +DeclareVideo(GLX) + +}; diff --git a/ruby/video/opengl.hpp b/ruby/video/opengl.hpp new file mode 100755 index 00000000..add60abb --- /dev/null +++ b/ruby/video/opengl.hpp @@ -0,0 +1,246 @@ +#include + +#if defined(PLATFORM_X) + #include + #define glGetProcAddress(name) (*glXGetProcAddress)((const GLubyte*)(name)) +#elif defined(PLATFORM_WIN) + #include + #define glGetProcAddress(name) wglGetProcAddress(name) +#else + #error "ruby::OpenGL: unsupported platform" +#endif + +PFNGLCREATEPROGRAMPROC glCreateProgram = 0; +PFNGLUSEPROGRAMPROC glUseProgram = 0; +PFNGLCREATESHADERPROC glCreateShader = 0; +PFNGLDELETESHADERPROC glDeleteShader = 0; +PFNGLSHADERSOURCEPROC glShaderSource = 0; +PFNGLCOMPILESHADERPROC glCompileShader = 0; +PFNGLATTACHSHADERPROC glAttachShader = 0; +PFNGLDETACHSHADERPROC glDetachShader = 0; +PFNGLLINKPROGRAMPROC glLinkProgram = 0; +PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation = 0; +PFNGLUNIFORM1IPROC glUniform1i = 0; +PFNGLUNIFORM2FVPROC glUniform2fv = 0; +PFNGLUNIFORM4FVPROC glUniform4fv = 0; + +class OpenGL { +public: + GLuint gltexture; + GLuint glprogram; + GLuint fragmentshader; + GLuint vertexshader; + bool shader_support; + + uint32_t *buffer; + unsigned iwidth, iheight; + + void resize(unsigned width, unsigned height) { + if(iwidth >= width && iheight >= height) return; + + if(gltexture) glDeleteTextures(1, &gltexture); + iwidth = max(width, iwidth ); + iheight = max(height, iheight); + if(buffer) delete[] buffer; + buffer = new uint32_t[iwidth * iheight]; + + glGenTextures(1, &gltexture); + glBindTexture(GL_TEXTURE_2D, gltexture); + glPixelStorei(GL_UNPACK_ROW_LENGTH, iwidth); + glTexImage2D(GL_TEXTURE_2D, + /* mip-map level = */ 0, /* internal format = */ GL_RGB, + iwidth, iheight, /* border = */ 0, /* format = */ GL_BGRA, + GL_UNSIGNED_INT_8_8_8_8_REV, buffer); + } + + bool lock(uint32_t *&data, unsigned &pitch) { + pitch = iwidth * sizeof(uint32_t); + return data = buffer; + } + + void clear() { + memset(buffer, 0, iwidth * iheight * sizeof(uint32_t)); + glClearColor(0.0, 0.0, 0.0, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + glFlush(); + } + + void refresh(bool smooth, unsigned inwidth, unsigned inheight, unsigned outwidth, unsigned outheight) { + if(shader_support) { + glUseProgram(glprogram); + GLint location; + + float inputSize[2] = { (float)inwidth, (float)inheight }; + location = glGetUniformLocation(glprogram, "rubyInputSize"); + glUniform2fv(location, 1, inputSize); + + float outputSize[2] = { (float)outwidth, (float)outheight }; + location = glGetUniformLocation(glprogram, "rubyOutputSize"); + glUniform2fv(location, 1, outputSize); + + float textureSize[2] = { (float)iwidth, (float)iheight }; + location = glGetUniformLocation(glprogram, "rubyTextureSize"); + glUniform2fv(location, 1, textureSize); + } + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, smooth ? GL_LINEAR : GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, smooth ? GL_LINEAR : GL_NEAREST); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0, outwidth, 0, outheight, -1.0, 1.0); + glViewport(0, 0, outwidth, outheight); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + glPixelStorei(GL_UNPACK_ROW_LENGTH, iwidth); + glTexSubImage2D(GL_TEXTURE_2D, + /* mip-map level = */ 0, /* x = */ 0, /* y = */ 0, + inwidth, inheight, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, buffer); + + //OpenGL projection sets 0,0 as *bottom-left* of screen. + //therefore, below vertices flip image to support top-left source. + //texture range = x1:0.0, y1:0.0, x2:1.0, y2:1.0 + //vertex range = x1:0, y1:0, x2:width, y2:height + double w = double(inwidth) / double(iwidth); + double h = double(inheight) / double(iheight); + int u = outwidth; + int v = outheight; + glBegin(GL_TRIANGLE_STRIP); + glTexCoord2f(0, 0); glVertex3i(0, v, 0); + glTexCoord2f(w, 0); glVertex3i(u, v, 0); + glTexCoord2f(0, h); glVertex3i(0, 0, 0); + glTexCoord2f(w, h); glVertex3i(u, 0, 0); + glEnd(); + + glFlush(); + + if(shader_support) { + glUseProgram(0); + } + } + + void set_shader(const char *source) { + if(!shader_support) return; + + if(fragmentshader) { + glDetachShader(glprogram, fragmentshader); + glDeleteShader(fragmentshader); + fragmentshader = 0; + } + + if(vertexshader) { + glDetachShader(glprogram, vertexshader); + glDeleteShader(vertexshader); + vertexshader = 0; + } + + if(source) { + bool is_glsl = false; + string fragment_source; + string vertex_source; + + xml_element document = xml_parse(source); + foreach(head, document.element) { + if(head.name == "shader") { + foreach(attribute, head.attribute) { + if(attribute.name == "language" && attribute.content == "GLSL") is_glsl = true; + } + foreach(element, head.element) { + if(element.name == "fragment") { + fragment_source = element.parse(); + } else if(element.name == "vertex") { + vertex_source = element.parse(); + } + } + } + } + + if(is_glsl) { + if(fragment_source != "") set_fragment_shader(fragment_source); + if(vertex_source != "") set_vertex_shader(vertex_source); + } + } + + glLinkProgram(glprogram); + } + + void set_fragment_shader(const char *source) { + fragmentshader = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(fragmentshader, 1, &source, 0); + glCompileShader(fragmentshader); + glAttachShader(glprogram, fragmentshader); + } + + void set_vertex_shader(const char *source) { + vertexshader = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(vertexshader, 1, &source, 0); + glCompileShader(vertexshader); + glAttachShader(glprogram, vertexshader); + } + + void init() { + //disable unused features + glDisable(GL_ALPHA_TEST); + glDisable(GL_BLEND); + glDisable(GL_DEPTH_TEST); + glDisable(GL_POLYGON_SMOOTH); + glDisable(GL_STENCIL_TEST); + + //enable useful and required features + glEnable(GL_DITHER); + glEnable(GL_TEXTURE_2D); + + //bind shader functions + glCreateProgram = (PFNGLCREATEPROGRAMPROC)glGetProcAddress("glCreateProgram"); + glUseProgram = (PFNGLUSEPROGRAMPROC)glGetProcAddress("glUseProgram"); + glCreateShader = (PFNGLCREATESHADERPROC)glGetProcAddress("glCreateShader"); + glDeleteShader = (PFNGLDELETESHADERPROC)glGetProcAddress("glDeleteShader"); + glShaderSource = (PFNGLSHADERSOURCEPROC)glGetProcAddress("glShaderSource"); + glCompileShader = (PFNGLCOMPILESHADERPROC)glGetProcAddress("glCompileShader"); + glAttachShader = (PFNGLATTACHSHADERPROC)glGetProcAddress("glAttachShader"); + glDetachShader = (PFNGLDETACHSHADERPROC)glGetProcAddress("glDetachShader"); + glLinkProgram = (PFNGLLINKPROGRAMPROC)glGetProcAddress("glLinkProgram"); + glGetUniformLocation = (PFNGLGETUNIFORMLOCATIONPROC)glGetProcAddress("glGetUniformLocation"); + glUniform1i = (PFNGLUNIFORM1IPROC)glGetProcAddress("glUniform1i"); + glUniform2fv = (PFNGLUNIFORM2FVPROC)glGetProcAddress("glUniform2fv"); + glUniform4fv = (PFNGLUNIFORM4FVPROC)glGetProcAddress("glUniform4fv"); + + shader_support = glCreateProgram && glUseProgram && glCreateShader + && glDeleteShader && glShaderSource && glCompileShader && glAttachShader + && glDetachShader && glLinkProgram && glGetUniformLocation + && glUniform1i && glUniform2fv && glUniform4fv; + + if(shader_support) glprogram = glCreateProgram(); + + //create surface texture + resize(256, 256); + } + + void term() { + if(gltexture) { + glDeleteTextures(1, &gltexture); + gltexture = 0; + } + + if(buffer) { + delete[] buffer; + buffer = 0; + iwidth = 0; + iheight = 0; + } + } + + OpenGL() { + gltexture = 0; + glprogram = 0; + fragmentshader = 0; + vertexshader = 0; + + buffer = 0; + iwidth = 0; + iheight = 0; + } +}; diff --git a/ruby/video/qtopengl.cpp b/ruby/video/qtopengl.cpp new file mode 100755 index 00000000..c357c403 --- /dev/null +++ b/ruby/video/qtopengl.cpp @@ -0,0 +1,174 @@ +#ifdef __APPLE__ + #include +#endif + +namespace ruby { + +class pVideoQtOpenGL { +public: + QWidget *parent; + QVBoxLayout *layout; + + class RubyGLWidget : public QGLWidget { + public: + GLuint texture; + unsigned textureWidth, textureHeight; + + uint32_t *buffer; + unsigned rasterWidth, rasterHeight; + + bool synchronize; + unsigned filter; + + void resize(unsigned width, unsigned height) { + if(width > textureWidth || height > textureHeight) { + textureWidth = max(width, textureWidth); + textureHeight = max(height, textureHeight); + + if(buffer) { + delete[] buffer; + glDeleteTextures(1, &texture); + } + + buffer = new uint32_t[textureWidth * textureHeight]; + glGenTextures(1, &texture); + glBindTexture(GL_TEXTURE_2D, texture); + glPixelStorei(GL_UNPACK_ROW_LENGTH, textureWidth); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, textureWidth, textureHeight, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, buffer); + } + } + + void updateSynchronization() { + #ifdef __APPLE__ + makeCurrent(); + CGLContextObj context = CGLGetCurrentContext(); + GLint value = synchronize; //0 = draw immediately (no vsync), 1 = draw once per frame (vsync) + CGLSetParameter(context, kCGLCPSwapInterval, &value); + #endif + } + + void paintGL() { + unsigned outputWidth = width(); + unsigned outputHeight = height(); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0, outputWidth, 0, outputHeight, -1.0, 1.0); + glViewport(0, 0, outputWidth, outputHeight); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (filter == Video::FilterPoint) ? GL_NEAREST : GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, (filter == Video::FilterPoint) ? GL_NEAREST : GL_LINEAR); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, rasterWidth, rasterHeight, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, buffer); + + double w = (double)rasterWidth / (double)textureWidth; + double h = (double)rasterHeight / (double)textureHeight; + unsigned u = outputWidth; + unsigned v = outputHeight; + + glBegin(GL_TRIANGLE_STRIP); + glTexCoord2f(0, 0); glVertex3i(0, v, 0); + glTexCoord2f(w, 0); glVertex3i(u, v, 0); + glTexCoord2f(0, h); glVertex3i(0, 0, 0); + glTexCoord2f(w, h); glVertex3i(u, 0, 0); + glEnd(); + } + + void initializeGL() { + format().setDoubleBuffer(true); + + texture = 0; + textureWidth = 0; + textureHeight = 0; + buffer = 0; + resize(rasterWidth = 256, rasterHeight = 256); + + glDisable(GL_ALPHA_TEST); + glDisable(GL_BLEND); + glDisable(GL_DEPTH_TEST); + glDisable(GL_POLYGON_SMOOTH); + glDisable(GL_STENCIL_TEST); + glEnable(GL_DITHER); + glEnable(GL_TEXTURE_2D); + glClearColor(0.0, 0.0, 0.0, 0.0); + } + } *widget; + + bool cap(const string& name) { + if(name == Video::Synchronize) return true; + if(name == Video::Filter) return true; + if(name == "QWidget") return true; + return false; + } + + any get(const string& name) { + if(name == Video::Synchronize) return widget->synchronize; + if(name == Video::Filter) return widget->filter; + if(name == "QWidget") return parent; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Video::Synchronize) { + widget->synchronize = any_cast(value); + widget->updateSynchronization(); + return true; + } + + if(name == Video::Filter) { + widget->filter = any_cast(value); + return true; + } + + if(name == "QWidget") { + parent = any_cast(value); + return true; + } + + return false; + } + + bool lock(uint32_t *&data, unsigned &pitch, unsigned width, unsigned height) { + widget->resize(width, height); + widget->rasterWidth = width; + widget->rasterHeight = height; + + pitch = widget->textureWidth * sizeof(uint32_t); + return data = widget->buffer; + } + + void unlock() { + } + + void clear() { + memset(widget->buffer, 0, widget->textureWidth * widget->textureHeight * sizeof(uint32_t)); + widget->updateGL(); + } + + void refresh() { + widget->updateGL(); + } + + bool init() { + layout = new QVBoxLayout; + layout->setMargin(0); + + widget = new RubyGLWidget; + widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + layout->addWidget(widget); + parent->setLayout(layout); + + return true; + } + + void term() { + } +}; + +DeclareVideo(QtOpenGL) + +}; diff --git a/ruby/video/qtraster.cpp b/ruby/video/qtraster.cpp new file mode 100755 index 00000000..414689b9 --- /dev/null +++ b/ruby/video/qtraster.cpp @@ -0,0 +1,126 @@ +namespace ruby { + +struct VideoQtRasterContext { + QImage *image; + unsigned width, height; + unsigned filter; +} context; + +class pVideoQtRaster { +public: + QWidget *parent; + QVBoxLayout *layout; + + struct QtImage : public QWidget { + VideoQtRasterContext &context; + + void paintEvent(QPaintEvent*) { + if(context.image == 0) return; + QPainter painter(this); + + if(size().width() == context.width && size().height() == context.height) { + painter.drawImage(0, 0, *context.image); + } else { + Qt::TransformationMode mode = Qt::FastTransformation; + if(context.filter == Video::FilterLinear) mode = Qt::SmoothTransformation; + painter.drawImage(0, 0, context.image->scaled(size(), Qt::IgnoreAspectRatio, mode)); + } + } + + QtImage(QWidget *parent, VideoQtRasterContext &context_) : QWidget(parent), context(context_) {} + } *widget; + + bool cap(const string& name) { + if(name == Video::Filter) return true; + if(name == "QWidget") return true; + return false; + } + + any get(const string& name) { + if(name == Video::Filter) return context.filter; + if(name == "QWidget") return parent; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Video::Filter) { + context.filter = any_cast(value); + return true; + } + + if(name == "QWidget") { + parent = any_cast(value); + return true; + } + + return false; + } + + void resize(unsigned width, unsigned height) { + if(context.width != width || context.height != height) { + if(context.image) delete context.image; + context.image = new QImage(context.width = width, context.height = height, QImage::Format_RGB32); + } + } + + bool lock(uint32_t *&data, unsigned &pitch, unsigned width, unsigned height) { + //if image size has changed since last lock(), re-allocate buffer to match new size + if(width != context.width || height != context.height) resize(width, height); + + pitch = width * sizeof(uint32_t); + return data = (uint32_t*)context.image->bits(); + } + + void unlock() { + } + + void clear() { + context.image->fill(0); + widget->update(); + } + + void refresh() { + widget->update(); + } + + bool init() { + term(); + + layout = new QVBoxLayout; + layout->setMargin(0); + + context.image = 0; + context.width = 0; + context.height = 0; + context.filter = Video::FilterPoint; + resize(256, 256); + + widget = new QtImage(parent, context); + widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + layout->addWidget(widget); + parent->setLayout(layout); + clear(); + + return true; + } + + void term() { + if(context.image) delete context.image; + if(widget) delete widget; + if(layout) delete layout; + + context.image = 0; + widget = 0; + layout = 0; + } + + pVideoQtRaster() { + context.image = 0; + widget = 0; + layout = 0; + } +}; + +DeclareVideo(QtRaster) + +}; diff --git a/ruby/video/sdl.cpp b/ruby/video/sdl.cpp new file mode 100755 index 00000000..8f70342e --- /dev/null +++ b/ruby/video/sdl.cpp @@ -0,0 +1,140 @@ +#include +#include +#include +#include +#include +#include + +namespace ruby { + +class pVideoSDL { +public: + Display *display; + SDL_Surface *screen, *buffer; + unsigned iwidth, iheight; + + struct { + uintptr_t handle; + + unsigned width; + unsigned height; + } settings; + + bool cap(const string& name) { + if(name == Video::Handle) return true; + return false; + } + + any get(const string& name) { + if(name == Video::Handle) return settings.handle; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Video::Handle) { + settings.handle = any_cast(value); + return true; + } + + return false; + } + + void resize(unsigned width, unsigned height) { + if(iwidth >= width && iheight >= height) return; + + iwidth = max(width, iwidth); + iheight = max(height, iheight); + + if(buffer) SDL_FreeSurface(buffer); + buffer = SDL_CreateRGBSurface( + SDL_SWSURFACE, iwidth, iheight, 32, + 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 + ); + } + + bool lock(uint32_t *&data, unsigned &pitch, unsigned width, unsigned height) { + if(width != settings.width || height != settings.height) { + resize(settings.width = width, settings.height = height); + } + + if(SDL_MUSTLOCK(buffer)) SDL_LockSurface(buffer); + pitch = buffer->pitch; + return data = (uint32_t*)buffer->pixels; + } + + void unlock() { + if(SDL_MUSTLOCK(buffer)) SDL_UnlockSurface(buffer); + } + + void clear() { + if(SDL_MUSTLOCK(buffer)) SDL_LockSurface(buffer); + for(unsigned y = 0; y < iheight; y++) { + uint32_t *data = (uint32_t*)buffer->pixels + y * (buffer->pitch >> 2); + for(unsigned x = 0; x < iwidth; x++) *data++ = 0xff000000; + } + if(SDL_MUSTLOCK(buffer)) SDL_UnlockSurface(buffer); + refresh(); + } + + void refresh() { + //ruby input is X8R8G8B8, top 8-bits are ignored. + //as SDL forces us to use a 32-bit buffer, we must set alpha to 255 (full opacity) + //to prevent blending against the window beneath when X window visual is 32-bits. + if(SDL_MUSTLOCK(buffer)) SDL_LockSurface(buffer); + for(unsigned y = 0; y < settings.height; y++) { + uint32_t *data = (uint32_t*)buffer->pixels + y * (buffer->pitch >> 2); + for(unsigned x = 0; x < settings.width; x++) *data++ |= 0xff000000; + } + if(SDL_MUSTLOCK(buffer)) SDL_UnlockSurface(buffer); + + XWindowAttributes attributes; + XGetWindowAttributes(display, settings.handle, &attributes); + + SDL_Rect src, dest; + + src.x = 0; + src.y = 0; + src.w = settings.width; + src.h = settings.height; + + dest.x = 0; + dest.y = 0; + dest.w = attributes.width; + dest.h = attributes.height; + + SDL_SoftStretch(buffer, &src, screen, &dest); + SDL_UpdateRect(screen, dest.x, dest.y, dest.w, dest.h); + } + + bool init() { + display = XOpenDisplay(0); + + char env[512]; + sprintf(env, "SDL_WINDOWID=%ld", (long int)settings.handle); + putenv(env); + + SDL_InitSubSystem(SDL_INIT_VIDEO); + screen = SDL_SetVideoMode(2560, 1600, 32, SDL_HWSURFACE); + + buffer = 0; + iwidth = 0; + iheight = 0; + resize(settings.width = 256, settings.height = 256); + + return true; + } + + void term() { + XCloseDisplay(display); + SDL_FreeSurface(buffer); + SDL_QuitSubSystem(SDL_INIT_VIDEO); + } + + pVideoSDL() { + settings.handle = 0; + } +}; + +DeclareVideo(SDL) + +}; diff --git a/ruby/video/wgl.cpp b/ruby/video/wgl.cpp new file mode 100755 index 00000000..9a2e82ac --- /dev/null +++ b/ruby/video/wgl.cpp @@ -0,0 +1,153 @@ +/* + video.wgl + authors: byuu, krom +*/ + +#include "opengl.hpp" + +namespace ruby { + +class pVideoWGL : public OpenGL { +public: + BOOL (APIENTRY *glSwapInterval)(int); + + HDC display; + HGLRC wglcontext; + HWND window; + HINSTANCE glwindow; + + struct { + HWND handle; + bool synchronize; + unsigned filter; + string shader; + + unsigned width; + unsigned height; + } settings; + + bool cap(const string& name) { + if(name == Video::Handle) return true; + if(name == Video::Synchronize) return true; + if(name == Video::Filter) return true; + if(name == Video::Shader) return true; + return false; + } + + any get(const string& name) { + if(name == Video::Handle) return (uintptr_t)settings.handle; + if(name == Video::Synchronize) return settings.synchronize; + if(name == Video::Filter) return settings.filter; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Video::Handle) { + settings.handle = (HWND)any_cast(value); + return true; + } + + if(name == Video::Synchronize) { + if(settings.synchronize != any_cast(value)) { + settings.synchronize = any_cast(value); + if(wglcontext) { + init(); + OpenGL::set_shader(settings.shader); + } + } + } + + if(name == Video::Filter) { + settings.filter = any_cast(value); + return true; + } + + if(name == Video::Shader) { + settings.shader = any_cast(value); + OpenGL::set_shader(settings.shader); + return true; + } + + return false; + } + + bool lock(uint32_t *&data, unsigned &pitch, unsigned width, unsigned height) { + resize(width, height); + settings.width = width; + settings.height = height; + return OpenGL::lock(data, pitch); + } + + void unlock() { + } + + void clear() { + OpenGL::clear(); + SwapBuffers(display); + } + + void refresh() { + RECT rc; + GetClientRect(settings.handle, &rc); + + OpenGL::refresh(settings.filter == Video::FilterLinear, + settings.width, settings.height, + rc.right - rc.left, rc.bottom - rc.top); + + SwapBuffers(display); + } + + bool init() { + term(); + + GLuint pixel_format; + PIXELFORMATDESCRIPTOR pfd; + memset(&pfd, 0, sizeof(PIXELFORMATDESCRIPTOR)); + pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR); + pfd.nVersion = 1; + pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; + pfd.iPixelType = PFD_TYPE_RGBA; + + display = GetDC(settings.handle); + pixel_format = ChoosePixelFormat(display, &pfd); + SetPixelFormat(display, pixel_format, &pfd); + + wglcontext = wglCreateContext(display); + wglMakeCurrent(display, wglcontext); + + OpenGL::init(); + settings.width = 256; + settings.height = 256; + + //vertical synchronization + if(!glSwapInterval) glSwapInterval = (BOOL (APIENTRY*)(int))glGetProcAddress("wglSwapIntervalEXT"); + if( glSwapInterval) glSwapInterval(settings.synchronize); + + return true; + } + + void term() { + OpenGL::term(); + + if(wglcontext) { + wglDeleteContext(wglcontext); + wglcontext = 0; + } + } + + pVideoWGL() : glSwapInterval(0) { + settings.handle = 0; + settings.synchronize = false; + settings.filter = 0; + + window = 0; + wglcontext = 0; + glwindow = 0; + } + + ~pVideoWGL() { term(); } +}; + +DeclareVideo(WGL) + +}; diff --git a/ruby/video/xv.cpp b/ruby/video/xv.cpp new file mode 100755 index 00000000..10d51647 --- /dev/null +++ b/ruby/video/xv.cpp @@ -0,0 +1,498 @@ +#include +#include +#include +#include +#include + +extern "C" XvImage* XvShmCreateImage(Display*, XvPortID, int, char*, int, int, XShmSegmentInfo*); + +namespace ruby { + +class pVideoXv { +public: + uint32_t *buffer; + uint8_t *ytable, *utable, *vtable; + + enum XvFormat { + XvFormatRGB32, + XvFormatRGB24, + XvFormatRGB16, + XvFormatRGB15, + XvFormatYUY2, + XvFormatUYVY, + XvFormatUnknown + }; + + struct { + Display *display; + GC gc; + Window window; + Colormap colormap; + XShmSegmentInfo shminfo; + + int port; + int depth; + int visualid; + + XvImage *image; + XvFormat format; + uint32_t fourcc; + + unsigned width; + unsigned height; + } device; + + struct { + Window handle; + bool synchronize; + + unsigned width; + unsigned height; + } settings; + + bool cap(const string& name) { + if(name == Video::Handle) return true; + if(name == Video::Synchronize) { + return XInternAtom(XOpenDisplay(0), "XV_SYNC_TO_VBLANK", true) != None; + } + return false; + } + + any get(const string& name) { + if(name == Video::Handle) return settings.handle; + if(name == Video::Synchronize) return settings.synchronize; + return false; + } + + bool set(const string& name, const any& value) { + if(name == Video::Handle) { + settings.handle = any_cast(value); + return true; + } + + if(name == Video::Synchronize) { + Display *display = XOpenDisplay(0); + Atom atom = XInternAtom(display, "XV_SYNC_TO_VBLANK", true); + if(atom != None && device.port >= 0) { + settings.synchronize = any_cast(value); + XvSetPortAttribute(display, device.port, atom, settings.synchronize); + return true; + } + return false; + } + + return false; + } + + void resize(unsigned width, unsigned height) { + if(device.width >= width && device.height >= height) return; + device.width = max(width, device.width); + device.height = max(height, device.height); + + XShmDetach(device.display, &device.shminfo); + shmdt(device.shminfo.shmaddr); + shmctl(device.shminfo.shmid, IPC_RMID, NULL); + XFree(device.image); + delete[] buffer; + + device.image = XvShmCreateImage(device.display, device.port, device.fourcc, 0, device.width, device.height, &device.shminfo); + + device.shminfo.shmid = shmget(IPC_PRIVATE, device.image->data_size, IPC_CREAT | 0777); + device.shminfo.shmaddr = device.image->data = (char*)shmat(device.shminfo.shmid, 0, 0); + device.shminfo.readOnly = false; + XShmAttach(device.display, &device.shminfo); + + buffer = new uint32_t[device.width * device.height]; + } + + bool lock(uint32_t *&data, unsigned &pitch, unsigned width, unsigned height) { + if(width != settings.width || height != settings.height) { + resize(settings.width = width, settings.height = height); + } + + pitch = device.width * 4; + return data = buffer; + } + + void unlock() { + } + + void clear() { + memset(buffer, 0, device.width * device.height * sizeof(uint32_t)); + //clear twice in case video is double buffered ... + refresh(); + refresh(); + } + + void refresh() { + unsigned width = settings.width; + unsigned height = settings.height; + + XWindowAttributes target; + XGetWindowAttributes(device.display, device.window, &target); + + //we must ensure that the child window is the same size as the parent window. + //unfortunately, we cannot hook the parent window resize event notification, + //as we did not create the parent window, nor have any knowledge of the toolkit used. + //therefore, query each window size and resize as needed. + XWindowAttributes parent; + XGetWindowAttributes(device.display, settings.handle, &parent); + if(target.width != parent.width || target.height != parent.height) { + XResizeWindow(device.display, device.window, parent.width, parent.height); + } + + //update target width and height attributes + XGetWindowAttributes(device.display, device.window, &target); + + switch(device.format) { + case XvFormatRGB32: render_rgb32(width, height); break; + case XvFormatRGB24: render_rgb24(width, height); break; + case XvFormatRGB16: render_rgb16(width, height); break; + case XvFormatRGB15: render_rgb15(width, height); break; + case XvFormatYUY2: render_yuy2 (width, height); break; + case XvFormatUYVY: render_uyvy (width, height); break; + } + + XvShmPutImage(device.display, device.port, device.window, device.gc, device.image, + 0, 0, width, height, + 0, 0, target.width, target.height, + true); + } + + bool init() { + device.display = XOpenDisplay(0); + + if(!XShmQueryExtension(device.display)) { + fprintf(stderr, "VideoXv: XShm extension not found.\n"); + return false; + } + + //find an appropriate Xv port + device.port = -1; + XvAdaptorInfo *adaptor_info; + unsigned adaptor_count; + XvQueryAdaptors(device.display, DefaultRootWindow(device.display), &adaptor_count, &adaptor_info); + for(unsigned i = 0; i < adaptor_count; i++) { + //find adaptor that supports both input (memory->drawable) and image (drawable->screen) masks + if(adaptor_info[i].num_formats < 1) continue; + if(!(adaptor_info[i].type & XvInputMask)) continue; + if(!(adaptor_info[i].type & XvImageMask)) continue; + + device.port = adaptor_info[i].base_id; + device.depth = adaptor_info[i].formats->depth; + device.visualid = adaptor_info[i].formats->visual_id; + break; + } + XvFreeAdaptorInfo(adaptor_info); + if(device.port < 0) { + fprintf(stderr, "VideoXv: failed to find valid XvPort.\n"); + return false; + } + + //create child window to attach to parent window. + //this is so that even if parent window visual depth doesn't match Xv visual + //(common with composited windows), Xv can still render to child window. + XWindowAttributes window_attributes; + XGetWindowAttributes(device.display, settings.handle, &window_attributes); + + XVisualInfo visualtemplate; + visualtemplate.visualid = device.visualid; + visualtemplate.screen = DefaultScreen(device.display); + visualtemplate.depth = device.depth; + visualtemplate.visual = 0; + int visualmatches = 0; + XVisualInfo *visualinfo = XGetVisualInfo(device.display, VisualIDMask | VisualScreenMask | VisualDepthMask, &visualtemplate, &visualmatches); + if(visualmatches < 1 || !visualinfo->visual) { + if(visualinfo) XFree(visualinfo); + fprintf(stderr, "VideoXv: unable to find Xv-compatible visual.\n"); + return false; + } + + device.colormap = XCreateColormap(device.display, settings.handle, visualinfo->visual, AllocNone); + XSetWindowAttributes attributes; + attributes.colormap = device.colormap; + attributes.border_pixel = 0; + attributes.event_mask = StructureNotifyMask; + device.window = XCreateWindow(device.display, /* parent = */ settings.handle, + /* x = */ 0, /* y = */ 0, window_attributes.width, window_attributes.height, + /* border_width = */ 0, device.depth, InputOutput, visualinfo->visual, + CWColormap | CWBorderPixel | CWEventMask, &attributes); + XFree(visualinfo); + XSetWindowBackground(device.display, device.window, /* color = */ 0); + XMapWindow(device.display, device.window); + + device.gc = XCreateGC(device.display, device.window, 0, 0); + + //set colorkey to auto paint, so that Xv video output is always visible + Atom atom = XInternAtom(device.display, "XV_AUTOPAINT_COLORKEY", true); + if(atom != None) XvSetPortAttribute(device.display, device.port, atom, 1); + + //find optimal rendering format + device.format = XvFormatUnknown; + signed format_count; + XvImageFormatValues *format = XvListImageFormats(device.display, device.port, &format_count); + + if(device.format == XvFormatUnknown) for(signed i = 0; i < format_count; i++) { + if(format[i].type == XvRGB && format[i].bits_per_pixel == 32) { + device.format = XvFormatRGB32; + device.fourcc = format[i].id; + break; + } + } + + if(device.format == XvFormatUnknown) for(signed i = 0; i < format_count; i++) { + if(format[i].type == XvRGB && format[i].bits_per_pixel == 24) { + device.format = XvFormatRGB24; + device.fourcc = format[i].id; + break; + } + } + + if(device.format == XvFormatUnknown) for(signed i = 0; i < format_count; i++) { + if(format[i].type == XvRGB && format[i].bits_per_pixel <= 16 && format[i].red_mask == 0xf800) { + device.format = XvFormatRGB16; + device.fourcc = format[i].id; + break; + } + } + + if(device.format == XvFormatUnknown) for(signed i = 0; i < format_count; i++) { + if(format[i].type == XvRGB && format[i].bits_per_pixel <= 16 && format[i].red_mask == 0x7c00) { + device.format = XvFormatRGB15; + device.fourcc = format[i].id; + break; + } + } + + if(device.format == XvFormatUnknown) for(signed i = 0; i < format_count; i++) { + if(format[i].type == XvYUV && format[i].bits_per_pixel == 16 && format[i].format == XvPacked) { + if(format[i].component_order[0] == 'Y' && format[i].component_order[1] == 'U' + && format[i].component_order[2] == 'Y' && format[i].component_order[3] == 'V' + ) { + device.format = XvFormatYUY2; + device.fourcc = format[i].id; + break; + } + } + } + + if(device.format == XvFormatUnknown) for(signed i = 0; i < format_count; i++) { + if(format[i].type == XvYUV && format[i].bits_per_pixel == 16 && format[i].format == XvPacked) { + if(format[i].component_order[0] == 'U' && format[i].component_order[1] == 'Y' + && format[i].component_order[2] == 'V' && format[i].component_order[3] == 'Y' + ) { + device.format = XvFormatUYVY; + device.fourcc = format[i].id; + break; + } + } + } + + free(format); + if(device.format == XvFormatUnknown) { + fprintf(stderr, "VideoXv: unable to find a supported image format.\n"); + return false; + } + + device.width = 256; + device.height = 256; + + device.image = XvShmCreateImage(device.display, device.port, device.fourcc, 0, device.width, device.height, &device.shminfo); + if(!device.image) { + fprintf(stderr, "VideoXv: XShmCreateImage failed.\n"); + return false; + } + + device.shminfo.shmid = shmget(IPC_PRIVATE, device.image->data_size, IPC_CREAT | 0777); + device.shminfo.shmaddr = device.image->data = (char*)shmat(device.shminfo.shmid, 0, 0); + device.shminfo.readOnly = false; + if(!XShmAttach(device.display, &device.shminfo)) { + fprintf(stderr, "VideoXv: XShmAttach failed.\n"); + return false; + } + + buffer = new uint32_t[device.width * device.height]; + settings.width = 256; + settings.height = 256; + init_yuv_tables(); + clear(); + return true; + } + + void term() { + XShmDetach(device.display, &device.shminfo); + shmdt(device.shminfo.shmaddr); + shmctl(device.shminfo.shmid, IPC_RMID, NULL); + XFree(device.image); + + if(device.window) { + XUnmapWindow(device.display, device.window); + device.window = 0; + } + + if(device.colormap) { + XFreeColormap(device.display, device.colormap); + device.colormap = 0; + } + + if(buffer) { delete[] buffer; buffer = 0; } + if(ytable) { delete[] ytable; ytable = 0; } + if(utable) { delete[] utable; utable = 0; } + if(vtable) { delete[] vtable; vtable = 0; } + } + + void render_rgb32(unsigned width, unsigned height) { + uint32_t *input = (uint32_t*)buffer; + uint32_t *output = (uint32_t*)device.image->data; + + for(unsigned y = 0; y < height; y++) { + memcpy(output, input, width * 4); + input += device.width; + output += device.width; + } + } + + void render_rgb24(unsigned width, unsigned height) { + uint32_t *input = (uint32_t*)buffer; + uint8_t *output = (uint8_t*)device.image->data; + + for(unsigned y = 0; y < height; y++) { + for(unsigned x = 0; x < width; x++) { + uint32_t p = *input++; + *output++ = p; + *output++ = p >> 8; + *output++ = p >> 16; + } + + input += (device.width - width); + output += (device.width - width) * 3; + } + } + + void render_rgb16(unsigned width, unsigned height) { + uint32_t *input = (uint32_t*)buffer; + uint16_t *output = (uint16_t*)device.image->data; + + for(unsigned y = 0; y < height; y++) { + for(unsigned x = 0; x < width; x++) { + uint32_t p = *input++; + *output++ = ((p >> 8) & 0xf800) | ((p >> 5) & 0x07e0) | ((p >> 3) & 0x001f); //RGB32->RGB16 + } + + input += device.width - width; + output += device.width - width; + } + } + + void render_rgb15(unsigned width, unsigned height) { + uint32_t *input = (uint32_t*)buffer; + uint16_t *output = (uint16_t*)device.image->data; + + for(unsigned y = 0; y < height; y++) { + for(unsigned x = 0; x < width; x++) { + uint32_t p = *input++; + *output++ = ((p >> 9) & 0x7c00) | ((p >> 6) & 0x03e0) | ((p >> 3) & 0x001f); //RGB32->RGB15 + } + + input += device.width - width; + output += device.width - width; + } + } + + void render_yuy2(unsigned width, unsigned height) { + uint32_t *input = (uint32_t*)buffer; + uint16_t *output = (uint16_t*)device.image->data; + + for(unsigned y = 0; y < height; y++) { + for(unsigned x = 0; x < width >> 1; x++) { + uint32_t p0 = *input++; + uint32_t p1 = *input++; + p0 = ((p0 >> 8) & 0xf800) + ((p0 >> 5) & 0x07e0) + ((p0 >> 3) & 0x001f); //RGB32->RGB16 + p1 = ((p1 >> 8) & 0xf800) + ((p1 >> 5) & 0x07e0) + ((p1 >> 3) & 0x001f); //RGB32->RGB16 + + uint8_t u = (utable[p0] + utable[p1]) >> 1; + uint8_t v = (vtable[p0] + vtable[p1]) >> 1; + + *output++ = (u << 8) | ytable[p0]; + *output++ = (v << 8) | ytable[p1]; + } + + input += device.width - width; + output += device.width - width; + } + } + + void render_uyvy(unsigned width, unsigned height) { + uint32_t *input = (uint32_t*)buffer; + uint16_t *output = (uint16_t*)device.image->data; + + for(unsigned y = 0; y < height; y++) { + for(unsigned x = 0; x < width >> 1; x++) { + uint32_t p0 = *input++; + uint32_t p1 = *input++; + p0 = ((p0 >> 8) & 0xf800) + ((p0 >> 5) & 0x07e0) + ((p0 >> 3) & 0x001f); + p1 = ((p1 >> 8) & 0xf800) + ((p1 >> 5) & 0x07e0) + ((p1 >> 3) & 0x001f); + + uint8_t u = (utable[p0] + utable[p1]) >> 1; + uint8_t v = (vtable[p0] + vtable[p1]) >> 1; + + *output++ = (ytable[p0] << 8) | u; + *output++ = (ytable[p1] << 8) | v; + } + + input += device.width - width; + output += device.width - width; + } + } + + void init_yuv_tables() { + ytable = new uint8_t[65536]; + utable = new uint8_t[65536]; + vtable = new uint8_t[65536]; + + for(unsigned i = 0; i < 65536; i++) { + //extract RGB565 color data from i + uint8_t r = (i >> 11) & 31, g = (i >> 5) & 63, b = (i) & 31; + r = (r << 3) | (r >> 2); //R5->R8 + g = (g << 2) | (g >> 4); //G6->G8 + b = (b << 3) | (b >> 2); //B5->B8 + + //ITU-R Recommendation BT.601 + //double lr = 0.299, lg = 0.587, lb = 0.114; + int y = int( +(double(r) * 0.257) + (double(g) * 0.504) + (double(b) * 0.098) + 16.0 ); + int u = int( -(double(r) * 0.148) - (double(g) * 0.291) + (double(b) * 0.439) + 128.0 ); + int v = int( +(double(r) * 0.439) - (double(g) * 0.368) - (double(b) * 0.071) + 128.0 ); + + //ITU-R Recommendation BT.709 + //double lr = 0.2126, lg = 0.7152, lb = 0.0722; + //int y = int( double(r) * lr + double(g) * lg + double(b) * lb ); + //int u = int( (double(b) - y) / (2.0 - 2.0 * lb) + 128.0 ); + //int v = int( (double(r) - y) / (2.0 - 2.0 * lr) + 128.0 ); + + ytable[i] = y < 0 ? 0 : y > 255 ? 255 : y; + utable[i] = u < 0 ? 0 : u > 255 ? 255 : u; + vtable[i] = v < 0 ? 0 : v > 255 ? 255 : v; + } + } + + pVideoXv() { + device.window = 0; + device.colormap = 0; + device.port = -1; + + ytable = 0; + utable = 0; + vtable = 0; + + settings.handle = 0; + settings.synchronize = false; + } + + ~pVideoXv() { + term(); + } +}; + +DeclareVideo(Xv) + +}; diff --git a/sync.sh b/sync.sh new file mode 100755 index 00000000..765c0b7a --- /dev/null +++ b/sync.sh @@ -0,0 +1,20 @@ +synchronize() { + if [ -d ../"$1" ]; then + test -d "$1" && rm -r "$1" + cp -r ../"$1" ./"$1" + fi +} + +synchronize "libco" +synchronize "nall" +synchronize "ruby" +synchronize "phoenix" + +test -d libco/doc && rm -r libco/doc +test -d libco/test && rm -r libco/test +test -d ruby/_test && rm -r ruby/_test +test -d phoenix/nall && rm -r phoenix/nall +rm -r phoenix/test* +rm -r phoenix/*.sh +rm -r phoenix/*.bat + diff --git a/ui/Makefile b/ui/Makefile new file mode 100755 index 00000000..c7c9db80 --- /dev/null +++ b/ui/Makefile @@ -0,0 +1,71 @@ +ui_objects := ui-main ui-utility +ui_objects += ruby phoenix + +# platform +ifeq ($(platform),x) + phoenix_compile = $(call compile,-DPHOENIX_GTK `pkg-config --cflags gtk+-2.0`) + link += `pkg-config --libs gtk+-2.0` +# phoenix_compile = $(call compile,-DPHOENIX_QT `pkg-config --cflags QtCore QtGui`) +# link += `pkg-config --libs QtCore QtGui` + + ruby := video.glx video.xv video.sdl + ruby += audio.alsa audio.openal audio.oss audio.pulseaudio audio.pulseaudiosimple audio.ao + ruby += input.sdl input.x + + link += $(if $(findstring audio.openal,$(ruby)),-lopenal) +else ifeq ($(platform),osx) + phoenix_compile = $(call compile,-DPHOENIX_QT) + link += + + ruby := + ruby += audio.openal + ruby += input.carbon + + link += $(if $(findstring audio.openal,$(ruby)),-framework OpenAL) +else ifeq ($(platform),win) + phoenix_compile = $(call compile,-DPHOENIX_WINDOWS) + link += + + ruby := video.direct3d video.wgl video.directdraw video.gdi + ruby += audio.directsound audio.xaudio2 + ruby += input.rawinput input.directinput + + link += $(if $(findstring audio.openal,$(ruby)),-lopenal32) +endif + +# ruby +rubyflags := $(if $(finstring .sdl,$(ruby)),`sdl-config --cflags`) + +link += $(if $(findstring .sdl,$(ruby)),`sdl-config --libs`) +link += $(if $(findstring video.direct3d,$(ruby)),-ld3d9) +link += $(if $(findstring video.directdraw,$(ruby)),-lddraw) +link += $(if $(findstring video.glx,$(ruby)),-lGL) +link += $(if $(findstring video.wgl,$(ruby)),-lopengl32) +link += $(if $(findstring video.xv,$(ruby)),-lXv) +link += $(if $(findstring audio.alsa,$(ruby)),-lasound) +link += $(if $(findstring audio.ao,$(ruby)),-lao) +link += $(if $(findstring audio.directsound,$(ruby)),-ldsound) +link += $(if $(findstring audio.pulseaudio,$(ruby)),-lpulse) +link += $(if $(findstring audio.pulseaudiosimple,$(ruby)),-lpulse-simple) +link += $(if $(findstring audio.xaudio2,$(ruby)),-lole32) +link += $(if $(findstring input.directinput,$(ruby)),-ldinput8 -ldxguid) +link += $(if $(findstring input.rawinput,$(ruby)),-ldinput8 -ldxguid) + +rubydef := $(foreach c,$(subst .,_,$(call strupper,$(ruby))),-D$c) + +# rules +objects := $(ui_objects) $(objects) + +obj/ui-main.o: $(ui)/main.cpp $(call rwildcard,$(ui)/*.hpp) $(call rwildcard,$(ui)/*); $(phoenix_compile) +obj/ui-utility.o: $(ui)/utility/utility.cpp $(call rwildcard,$(ui)/utility/*); $(phoenix_compile) + +obj/ruby.o: ruby/ruby.cpp $(call rwildcard,ruby/*) + $(call compile,$(rubydef) $(rubyflags)) + +obj/phoenix.o: phoenix/phoenix.cpp $(call rwildcard,phoenix/*) + $(phoenix_compile) + +# targets +ui_build:; + +ui_clean:; diff --git a/ui/base.hpp b/ui/base.hpp new file mode 100755 index 00000000..323331fb --- /dev/null +++ b/ui/base.hpp @@ -0,0 +1,24 @@ +#include +#include +#include +#include +using namespace nall; + +#include +using namespace phoenix; + +#include + +#include "interface.hpp" + +#include "general/main-window.hpp" + +#include "utility/utility.hpp" + +struct Application { + bool quit; + + void main(int argc, char **argv); +}; + +extern Application application; diff --git a/ui/general/main-window.cpp b/ui/general/main-window.cpp new file mode 100755 index 00000000..fb08eac2 --- /dev/null +++ b/ui/general/main-window.cpp @@ -0,0 +1,46 @@ +MainWindow mainWindow; + +void MainWindow::create() { + Window::create(128, 128, 160 * 3, 144 * 3, { GameBoy::Info::Name, " v", GameBoy::Info::Version }); + + system.create(*this, "System"); + systemLoadCartridge.create(system, "Load Cartridge ..."); + systemSeparator1.create(system); + systemPower.create(system, "Power Cycle"); + systemPower.setEnabled(false); + systemReset.create(system, "Reset"); + systemReset.setEnabled(false); + + settings.create(*this, "Settings"); + settings.setEnabled(false); + + tools.create(*this, "Tools"); + tools.setEnabled(false); + + help.create(*this, "Help"); + helpAbout.create(help, "About ..."); + + viewport.create(*this, 0, 0, 160 * 3, 144 * 3); + + setMenuVisible(true); + setStatusVisible(true); + + onClose = []() { + application.quit = true; + return false; + }; + + systemLoadCartridge.onTick = []() { + string filename = OS::fileOpen(mainWindow, "Game Boy cartridges\t*.gb", "/media/sdb1/root/gameboy_images/"); + if(filename != "") utility.loadCartridge(filename); + }; + + helpAbout.onTick = []() { + MessageWindow::information(mainWindow, { + "bgameboy\n\n", + "Version: ", GameBoy::Info::Version, "\n", + "Author: byuu\n", + "Homepage: http://byuu.org/" + }); + }; +} diff --git a/ui/general/main-window.hpp b/ui/general/main-window.hpp new file mode 100755 index 00000000..b9d965ad --- /dev/null +++ b/ui/general/main-window.hpp @@ -0,0 +1,20 @@ +struct MainWindow : Window { + Menu system; + MenuItem systemLoadCartridge; + MenuSeparator systemSeparator1; + MenuItem systemPower; + MenuItem systemReset; + + Menu settings; + + Menu tools; + + Menu help; + MenuItem helpAbout; + + Viewport viewport; + + void create(); +}; + +extern MainWindow mainWindow; diff --git a/ui/interface.cpp b/ui/interface.cpp new file mode 100755 index 00000000..dd28990d --- /dev/null +++ b/ui/interface.cpp @@ -0,0 +1,18 @@ +Interface interface; + +void Interface::video_refresh(const uint8_t *data) { +} + +void Interface::audio_sample(signed left, signed right) { +} + +void Interface::input_poll() { +} + +bool Interface::input_poll(unsigned id) { + return false; +} + +void Interface::message(const string &text) { + MessageWindow::information(mainWindow, text); +} diff --git a/ui/interface.hpp b/ui/interface.hpp new file mode 100755 index 00000000..e33902d5 --- /dev/null +++ b/ui/interface.hpp @@ -0,0 +1,10 @@ +struct Interface : public GameBoy::Interface { + void video_refresh(const uint8_t *data); + void audio_sample(signed left, signed right); + void input_poll(); + bool input_poll(unsigned id); + + void message(const string &text); +}; + +extern Interface interface; diff --git a/ui/main.cpp b/ui/main.cpp new file mode 100755 index 00000000..04a697c8 --- /dev/null +++ b/ui/main.cpp @@ -0,0 +1,26 @@ +#include "base.hpp" +Application application; + +#include "interface.cpp" + +#include "general/main-window.cpp" + +void Application::main(int argc, char **argv) { + quit = false; + + mainWindow.create(); + + mainWindow.setVisible(); + + GameBoy::system.init(&interface); + + while(quit == false) { + OS::run(); + } +} + +int main(int argc, char **argv) { + application.main(argc, argv); + + return 0; +} diff --git a/ui/utility/utility.cpp b/ui/utility/utility.cpp new file mode 100755 index 00000000..864a1393 --- /dev/null +++ b/ui/utility/utility.cpp @@ -0,0 +1,15 @@ +#include "../base.hpp" +Utility utility; + +void Utility::loadCartridge(const char *filename) { + file fp; + if(fp.open(filename, file::mode::read)) { + unsigned size = fp.size(); + uint8_t *data = new uint8_t[size]; + fp.read(data, size); + fp.close(); + GameBoy::cartridge.load(data, size); + delete[] data; + GameBoy::system.power(); + } +} diff --git a/ui/utility/utility.hpp b/ui/utility/utility.hpp new file mode 100755 index 00000000..d6e1afaa --- /dev/null +++ b/ui/utility/utility.hpp @@ -0,0 +1,5 @@ +struct Utility { + void loadCartridge(const char *filename); +}; + +extern Utility utility;