diff --git a/.gitignore b/.gitignore index c1013451..f58bc49f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,7 @@ CMakeCache.txt cmake_install.cmake Makefile otclient + +otclient.kdev4 +CMakeLists.txt.user !.gitignore diff --git a/CMakeLists.txt b/CMakeLists.txt index d270c849..fd8b5d6e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,61 @@ CMAKE_MINIMUM_REQUIRED(VERSION 2.6) PROJECT(otclient) -ADD_EXECUTABLE(otclient srcs/main.cpp) + +# find needed packages +SET(Boost_USE_STATIC_LIBS ON) +FIND_PACKAGE(Boost COMPONENTS thread filesystem REQUIRED) +FIND_PACKAGE(OpenGL REQUIRED) +FIND_PACKAGE(Lua51 REQUIRED) + +# choose a default build type if not specified +IF(NOT CMAKE_BUILD_TYPE) + SET(CMAKE_BUILD_TYPE RelWithDebInfo) +ENDIF(NOT CMAKE_BUILD_TYPE) +MESSAGE(STATUS "BUILD TYPE: " ${CMAKE_BUILD_TYPE}) + +# setup compiler options +IF(CMAKE_COMPILER_IS_GNUCXX) + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wl,--as-needed") + SET(CMAKE_CXX_FLAGS_DEBUG "-O1 -g -ggdb -fno-inline") + SET(CMAKE_CXX_FLAGS_RELEASE "-O2 -Wl,-s") + SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O2 -g") +ENDIF(CMAKE_COMPILER_IS_GNUCXX) + +INCLUDE_DIRECTORIES( +${LUA_INCLUDE_DIRS} +${Boost_INCLUDE_DIRS}) + +LINK_DIRECTORIES( +${Boost_LIBRARY_DIRS} +${LUA_LIBRARY_DIRS}) + +# setup definitions +ADD_DEFINITIONS(-D_REENTRANT) + +IF(CMAKE_BUILD_TYPE STREQUAL "Debug") + ADD_DEFINITIONS(-D_DEBUG) +ENDIF(CMAKE_BUILD_TYPE STREQUAL "Debug") + +# find sources +SET(SOURCES +src/main.cpp +src/engine.cpp +src/graphics.cpp +src/logger.cpp +src/util.cpp) + +IF(WIN32) + SET(SOURCES ${SOURCES} src/win32platform.cpp) +ELSE(WIN32) + SET(SOURCES ${SOURCES} src/x11platform.cpp) +ENDIF(WIN32) + +# target executable +ADD_EXECUTABLE(otclient ${SOURCES}) + +# target link libraries +TARGET_LINK_LIBRARIES(otclient +${Boost_LIBRARIES} +${OPENGL_LIBRARY} +${LUA51_LIBRARY}) diff --git a/src/const.h b/src/const.h new file mode 100644 index 00000000..6ed1bcc0 --- /dev/null +++ b/src/const.h @@ -0,0 +1,10 @@ +#ifndef VERSION_H +#define VERSION_H + +#define APP_VERSION "0.1.0" +#define APP_NAME "OTClient" +#define APP_LONGNAME APP_NAME " " APP_VERSION +#define APP_ICON "data/otclient.bmp" + +#endif + diff --git a/src/engine.cpp b/src/engine.cpp new file mode 100644 index 00000000..9c564528 --- /dev/null +++ b/src/engine.cpp @@ -0,0 +1,105 @@ +#include "engine.h" +#include "platform.h" +#include "graphics.h" +#include "const.h" +#include "input.h" + +Engine g_engine; + +Engine::Engine() : + m_stopping(false), + m_running(false), + m_lastFrameTicks(0) +{ +} + +Engine::~Engine() +{ +} + +void Engine::init() +{ + Platform::init(); + + int width = 640; + int height = 480; + + // create the window + Platform::createWindow(width, height, 550, 450); + Platform::setWindowTitle(APP_NAME); + Platform::setVsync(); + + // initialize graphics stuff + g_graphics.init(); + + // finally show the window + onResize(width, height); + Platform::showWindow(); + Platform::hideMouseCursor(); +} + +void Engine::terminate() +{ + Platform::showMouseCursor(); + Platform::terminate(); + g_graphics.terminate(); +} + +void Engine::run() +{ + unsigned long ticks; + + m_running = true; + m_lastFrameTicks = Platform::getTicks(); + + while(!m_stopping) { + // fire platform events + Platform::poll(); + + // update + ticks = Platform::getTicks(); + update(ticks - m_lastFrameTicks); + m_lastFrameTicks = ticks; + + // render + render(); + + // swap buffers + Platform::swapBuffers(); + } + + m_lastFrameTicks = 0; + m_stopping = false; + m_running = false; +} + +void Engine::stop() +{ + m_stopping = true; +} + +void Engine::render() +{ + g_graphics.beginRender(); + g_graphics.endRender(); +} + +void Engine::update(int elapsedTicks) +{ + +} + +void Engine::onClose() +{ + stop(); +} + +void Engine::onResize(int width, int height) +{ + +} + +void Engine::onInputEvent(InputEvent *event) +{ + +} diff --git a/src/engine.h b/src/engine.h new file mode 100644 index 00000000..333b270a --- /dev/null +++ b/src/engine.h @@ -0,0 +1,40 @@ +#ifndef ENGINE_H +#define ENGINE_H + +struct InputEvent; + +class Engine +{ +public: + Engine(); + ~Engine(); + + void init(); + void terminate(); + + void run(); + void stop(); + + bool isRunning() const { return m_running; } + bool isStopping() const { return m_stopping; } + unsigned long getLastFrameTicks() const { return m_lastFrameTicks; } + + // events fired by platform + void onClose(); + void onResize(int width, int height); + void onInputEvent(InputEvent *event); + +private: + void render(); + void update(int elapsedTicks); + + bool m_stopping; + bool m_running; + + unsigned long m_lastFrameTicks; +}; + +extern Engine g_engine; + +#endif // ENGINE_H + diff --git a/src/graphics.cpp b/src/graphics.cpp new file mode 100644 index 00000000..b3570edd --- /dev/null +++ b/src/graphics.cpp @@ -0,0 +1,63 @@ +#include "graphics.h" + +#include +#include + +Graphics g_graphics; + +Graphics::Graphics() +{ + +} + +Graphics::~Graphics() +{ + +} + +void Graphics::init() +{ + // setup opengl + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // black background + glEnable(GL_ALPHA_TEST); // enable alpha + glAlphaFunc(GL_GREATER, 0.0f); // default alpha mode + glDisable(GL_DEPTH_TEST); // we are rendering 2D only, we don't need it +} + +void Graphics::terminate() +{ + +} + +void Graphics::resize(int width, int height) +{ + // resize gl viewport + glViewport(0, 0, width, height); + + /* + 0,0---------0,w + | | + | | + | | + h,0---------h,w + */ + // setup view region like above + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + gluOrtho2D(0.0f, width, height, 0.0f); + + // back to model view + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); +} + +void Graphics::beginRender() +{ + glClear(GL_COLOR_BUFFER_BIT); + glLoadIdentity(); +} + +void Graphics::endRender() +{ + +} diff --git a/src/graphics.h b/src/graphics.h new file mode 100644 index 00000000..a7c5cea5 --- /dev/null +++ b/src/graphics.h @@ -0,0 +1,21 @@ +#ifndef GRAPHICS_H +#define GRAPHICS_H + +class Graphics +{ +public: + Graphics(); + ~Graphics(); + + void init(); + void terminate(); + + void resize(int width, int height); + + void beginRender(); + void endRender(); +}; + +extern Graphics g_graphics; + +#endif // GRAPHICS_H diff --git a/src/input.h b/src/input.h new file mode 100644 index 00000000..726e5751 --- /dev/null +++ b/src/input.h @@ -0,0 +1,187 @@ +#ifndef INPUT_H +#define INPUT_H + +enum EKeyCode { + KC_UNKNOWN = 0x00, + KC_ESCAPE = 0x01, + KC_1 = 0x02, + KC_2 = 0x03, + KC_3 = 0x04, + KC_4 = 0x05, + KC_5 = 0x06, + KC_6 = 0x07, + KC_7 = 0x08, + KC_8 = 0x09, + KC_9 = 0x0A, + KC_0 = 0x0B, + KC_MINUS = 0x0C, // - on main keyboard + KC_EQUALS = 0x0D, + KC_BACK = 0x0E, // backspace + KC_TAB = 0x0F, + KC_Q = 0x10, + KC_W = 0x11, + KC_E = 0x12, + KC_R = 0x13, + KC_T = 0x14, + KC_Y = 0x15, + KC_U = 0x16, + KC_I = 0x17, + KC_O = 0x18, + KC_P = 0x19, + KC_LBRACKET = 0x1A, + KC_RBRACKET = 0x1B, + KC_RETURN = 0x1C, // Enter on main keyboard + KC_LCONTROL = 0x1D, + KC_A = 0x1E, + KC_S = 0x1F, + KC_D = 0x20, + KC_F = 0x21, + KC_G = 0x22, + KC_H = 0x23, + KC_J = 0x24, + KC_K = 0x25, + KC_L = 0x26, + KC_SEMICOLON = 0x27, + KC_APOSTROPHE = 0x28, + KC_GRAVE = 0x29, // accent + KC_LSHIFT = 0x2A, + KC_BACKSLASH = 0x2B, + KC_Z = 0x2C, + KC_X = 0x2D, + KC_C = 0x2E, + KC_V = 0x2F, + KC_B = 0x30, + KC_N = 0x31, + KC_M = 0x32, + KC_COMMA = 0x33, + KC_PERIOD = 0x34, // . on main keyboard + KC_SLASH = 0x35, // / on main keyboard + KC_RSHIFT = 0x36, + KC_MULTIPLY = 0x37, // * on numeric keypad + KC_LALT = 0x38, // left Alt + KC_SPACE = 0x39, + KC_CAPITAL = 0x3A, + KC_F1 = 0x3B, + KC_F2 = 0x3C, + KC_F3 = 0x3D, + KC_F4 = 0x3E, + KC_F5 = 0x3F, + KC_F6 = 0x40, + KC_F7 = 0x41, + KC_F8 = 0x42, + KC_F9 = 0x43, + KC_F10 = 0x44, + KC_NUMLOCK = 0x45, + KC_SCROLL = 0x46, // Scroll Lock + KC_NUMPAD7 = 0x47, + KC_NUMPAD8 = 0x48, + KC_NUMPAD9 = 0x49, + KC_SUBTRACT = 0x4A, // - on numeric keypad + KC_NUMPAD4 = 0x4B, + KC_NUMPAD5 = 0x4C, + KC_NUMPAD6 = 0x4D, + KC_ADD = 0x4E, // + on numeric keypad + KC_NUMPAD1 = 0x4F, + KC_NUMPAD2 = 0x50, + KC_NUMPAD3 = 0x51, + KC_NUMPAD0 = 0x52, + KC_DECIMAL = 0x53, // . on numeric keypad + KC_OEM_102 = 0x56, // < > | on UK/Germany keyboards + KC_F11 = 0x57, + KC_F12 = 0x58, + KC_F13 = 0x64, // (NEC PC98) + KC_F14 = 0x65, // (NEC PC98) + KC_F15 = 0x66, // (NEC PC98) + KC_KANA = 0x70, // (Japanese keyboard) + KC_ABNT_C1 = 0x73, // / ? on Portugese (Brazilian) keyboards + KC_CONVERT = 0x79, // (Japanese keyboard) + KC_NOCONVERT = 0x7B, // (Japanese keyboard) + KC_YEN = 0x7D, // (Japanese keyboard) + KC_ABNT_C2 = 0x7E, // Numpad . on Portugese (Brazilian) keyboards + KC_NUMPADEQUALS= 0x8D, // = on numeric keypad (NEC PC98) + KC_PREVTRACK = 0x90, // Previous Track (KC_CIRCUMFLEX on Japanese keyboard) + KC_AT = 0x91, // (NEC PC98) + KC_COLON = 0x92, // (NEC PC98) + KC_UNDERLINE = 0x93, // (NEC PC98) + KC_KANJI = 0x94, // (Japanese keyboard) + KC_STOP = 0x95, // (NEC PC98) + KC_AX = 0x96, // (Japan AX) + KC_UNLABELED = 0x97, // (J3100) + KC_NEXTTRACK = 0x99, // Next Track + KC_NUMPADENTER = 0x9C, // Enter on numeric keypad + KC_RCONTROL = 0x9D, + KC_MUTE = 0xA0, // Mute + KC_CALCULATOR = 0xA1, // Calculator + KC_PLAYPAUSE = 0xA2, // Play / Pause + KC_MEDIASTOP = 0xA4, // Media Stop + KC_VOLUMEDOWN = 0xAE, // Volume - + KC_VOLUMEUP = 0xB0, // Volume + + KC_WEBHOME = 0xB2, // Web home + KC_NUMPADCOMMA = 0xB3, // , on numeric keypad (NEC PC98) + KC_DIVIDE = 0xB5, // / on numeric keypad + KC_SYSRQ = 0xB7, + KC_RALT = 0xB8, // right Alt + KC_PAUSE = 0xC5, // Pause + KC_HOME = 0xC7, // Home on arrow keypad + KC_UP = 0xC8, // UpArrow on arrow keypad + KC_PGUP = 0xC9, // PgUp on arrow keypad + KC_LEFT = 0xCB, // LeftArrow on arrow keypad + KC_RIGHT = 0xCD, // RightArrow on arrow keypad + KC_END = 0xCF, // End on arrow keypad + KC_DOWN = 0xD0, // DownArrow on arrow keypad + KC_PGDOWN = 0xD1, // PgDn on arrow keypad + KC_INSERT = 0xD2, // Insert on arrow keypad + KC_DELETE = 0xD3, // Delete on arrow keypad + KC_LWIN = 0xDB, // Left Windows key + KC_RWIN = 0xDC, // Right Windows key + KC_APPS = 0xDD, // AppMenu key + KC_POWER = 0xDE, // System Power + KC_SLEEP = 0xDF, // System Sleep + KC_WAKE = 0xE3, // System Wake + KC_WEBSEARCH = 0xE5, // Web Search + KC_WEBFAVORITES= 0xE6, // Web Favorites + KC_WEBREFRESH = 0xE7, // Web Refresh + KC_WEBSTOP = 0xE8, // Web Stop + KC_WEBFORWARD = 0xE9, // Web Forward + KC_WEBBACK = 0xEA, // Web Back + KC_MYCOMPUTER = 0xEB, // My Computer + KC_MAIL = 0xEC, // Mail + KC_MEDIASELECT = 0xED // Media Select +}; + +enum EEvent { + EV_KEY_DOWN = 0, + EV_KEY_UP, + EV_TEXT_ENTER, + EV_MOUSE_LDOWN, + EV_MOUSE_LUP, + EV_MOUSE_MDOWN, + EV_MOUSE_MUP, + EV_MOUSE_RDOWN, + EV_MOUSE_RUP, + EV_MOUSE_WHEEL_UP, + EV_MOUSE_WHEEL_DOWN, + EV_MOUSE_MOVE +}; + +struct KeyEvent { + char keychar; + unsigned char keycode; + bool ctrl; + bool shift; + bool alt; +}; + +struct MouseEvent { + int x, y; +}; + +struct InputEvent { + EEvent type; + union { + KeyEvent key; + MouseEvent mouse; + }; +}; + +#endif // INPUT_H diff --git a/src/logger.cpp b/src/logger.cpp new file mode 100644 index 00000000..e52209b6 --- /dev/null +++ b/src/logger.cpp @@ -0,0 +1,53 @@ +#include "logger.h" +#include "util.h" + +#include +#include +#include +#include +#include + +void _log(int level, const char *trace, const char *format, ...) +{ + va_list args; + std::stringstream out; + std::string strace; + + va_start(args, format); + std::string text = vformat(format, args); + va_end(args); + + if(trace) { + strace = trace; + strace = strace.substr(0, strace.find_first_of('(')); + if(strace.find_last_of(' ') != std::string::npos) + strace = strace.substr(strace.find_last_of(' ') + 1); + } + +#ifdef linux + static char const *colors[] = { "\033[01;31m ", "\033[01;31m", "\033[01;33m", "\033[0;32m", "\033[01;34m" }; + static bool colored = getenv("COLORED_OUTPUT"); + if(colored) + out << colors[level]; +#endif + + if(!strace.empty()) + out << "[" << strace << "] "; + + static char const *prefixes[] = { "FATAL ERROR: ", "ERROR: ", "WARNING: ", "", "", "" }; + out << prefixes[level]; + out << text; + +#ifdef linux + if(colored) + out << "\033[0m"; +#endif + + if(level <= LWARNING) + std::cerr << out.str() << std::endl; + else + std::cout << out.str() << std::endl; + + if(level == LFATAL) + exit(-1); +} diff --git a/src/logger.h b/src/logger.h new file mode 100644 index 00000000..e7f9c6bf --- /dev/null +++ b/src/logger.h @@ -0,0 +1,22 @@ +#ifndef LOGGER_H +#define LOGGER_H + +#include + +enum ELogLevel { + LFATAL = 0, + LERROR, + LWARNING, + LNOTICE, + LDEBUG +}; + +void _log(int level, const char *trace, const char *format, ...); + +#define fatal(...) _log(LFATAL, NULL, ## __VA_ARGS__) +#define error(...) _log(LERROR, NULL, ## __VA_ARGS__) +#define warning(...) _log(LWARNING, NULL, ## __VA_ARGS__) +#define debug(...) _log(LDEBUG, NULL, ## __VA_ARGS__) +#define notice(...) _log(LNOTICE, NULL, ## __VA_ARGS__) + +#endif diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 00000000..a7f20a0f --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,39 @@ +#include "engine.h" +#include "const.h" +#include "logger.h" + +#include + +// catches terminate signals to exit nicely +void signal_handler(int sig) +{ + switch(sig) { + case SIGTERM: + case SIGINT: + case SIGQUIT: + { + static bool stopping = false; + if(!stopping) { + stopping = true; + g_engine.stop(); + } + break; + } + } +} + +int main(int argc, const char *argv[]) +{ + // install our signal handler + signal(SIGTERM, signal_handler); + signal(SIGINT, signal_handler); + signal(SIGQUIT, signal_handler); + + notice(APP_LONGNAME); + + // setup the engine and run + g_engine.init(); + g_engine.run(); + g_engine.terminate(); + return 0; +} diff --git a/src/platform.h b/src/platform.h new file mode 100644 index 00000000..6349234b --- /dev/null +++ b/src/platform.h @@ -0,0 +1,34 @@ +#ifndef PLATFORM_H +#define PLATFORM_H + +namespace Platform +{ + void init(); + void terminate(); + + void poll(); + + unsigned long getTicks(); + void sleep(unsigned long miliseconds); + + bool createWindow(int width, int height, int minWidth, int minHeight); + void destroyWindow(); + void showWindow(); + void setWindowTitle(const char *title); + bool isWindowFocused(); + bool isWindowVisible(); + + void *getExtensionProcAddress(const char *ext); + bool isExtensionSupported(const char *ext); + + const char *getTextFromClipboard(); + void copyToClipboard(const char *text); + + void hideMouseCursor(); + void showMouseCursor(); + + void setVsync(bool enable = true); + void swapBuffers(); +}; + +#endif // PLATFORM_H diff --git a/src/util.cpp b/src/util.cpp new file mode 100644 index 00000000..29f77d7d --- /dev/null +++ b/src/util.cpp @@ -0,0 +1,30 @@ +#include "util.h" + +#include + +std::string vformat(const char *format, va_list args) +{ + if(!format) + return ""; + int result = -1, length = 256; + char *buffer = 0; + while(result == -1) { + if(buffer) + delete [] buffer; + buffer = new char [length + 1]; + result = vsnprintf(buffer, length, format, args); + length *= 2; + } + std::string s(buffer); + delete [] buffer; + return s; +} + +std::string format(const char *format, ...) +{ + va_list args; + va_start(args, format); + std::string s = vformat(format, args); + va_end(args); + return s; +} diff --git a/src/util.h b/src/util.h new file mode 100644 index 00000000..88ee0e18 --- /dev/null +++ b/src/util.h @@ -0,0 +1,10 @@ +#ifndef UTIL_H +#define UTIL_H + +#include +#include + +std::string vformat(const char *format, va_list args); +std::string format(const char *format, ...); + +#endif \ No newline at end of file diff --git a/src/x11platform.cpp b/src/x11platform.cpp new file mode 100644 index 00000000..887d2ed8 --- /dev/null +++ b/src/x11platform.cpp @@ -0,0 +1,684 @@ +#include "platform.h" +#include "engine.h" +#include "input.h" +#include "logger.h" + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +struct X11PlatformPrivate { + Display *display; + XVisualInfo *visual; + GLXContext glxContext; + XIM xim; + XIC xic; + Colormap colormap; + Window window; + Cursor cursor; + Atom atomDeleteWindow; + Atom atomClipboard; + Atom atomTargets; + Atom atomText; + Atom atomCompoundText; + Atom atomUTF8String; + bool visible; + bool focused; + int width; + int height; + std::string clipboardText; + std::map keyMap; +} x11; + +void Platform::init() +{ + x11.display = NULL; + x11.visual = NULL; + x11.glxContext = NULL; + x11.xim = NULL; + x11.xic = NULL; + x11.colormap = None; + x11.window = None; + x11.cursor = None; + x11.visible = false; + x11.focused = false; + x11.width = 0; + x11.height = 0; + + // setup keymap + x11.keyMap[XK_1] = KC_1; + x11.keyMap[XK_2] = KC_2; + x11.keyMap[XK_3] = KC_3; + x11.keyMap[XK_4] = KC_4; + x11.keyMap[XK_5] = KC_5; + x11.keyMap[XK_6] = KC_6; + x11.keyMap[XK_7] = KC_7; + x11.keyMap[XK_8] = KC_8; + x11.keyMap[XK_9] = KC_9; + x11.keyMap[XK_0] = KC_0; + + x11.keyMap[XK_BackSpace] = KC_BACK; + + x11.keyMap[XK_minus] = KC_MINUS; + x11.keyMap[XK_equal] = KC_EQUALS; + x11.keyMap[XK_space] = KC_SPACE; + x11.keyMap[XK_comma] = KC_COMMA; + x11.keyMap[XK_period] = KC_PERIOD; + + x11.keyMap[XK_backslash] = KC_BACKSLASH; + x11.keyMap[XK_slash] = KC_SLASH; + x11.keyMap[XK_bracketleft] = KC_LBRACKET; + x11.keyMap[XK_bracketright] = KC_RBRACKET; + + x11.keyMap[XK_Escape] = KC_ESCAPE; + x11.keyMap[XK_Caps_Lock] = KC_CAPITAL; + + x11.keyMap[XK_Tab] = KC_TAB; + x11.keyMap[XK_Return] = KC_RETURN; + x11.keyMap[XK_Control_L] = KC_LCONTROL; + x11.keyMap[XK_Control_R] = KC_RCONTROL; + + x11.keyMap[XK_colon] = KC_COLON; + x11.keyMap[XK_semicolon] = KC_SEMICOLON; + x11.keyMap[XK_apostrophe] = KC_APOSTROPHE; + x11.keyMap[XK_grave] = KC_GRAVE; + + x11.keyMap[XK_b] = KC_B; + x11.keyMap[XK_a] = KC_A; + x11.keyMap[XK_c] = KC_C; + x11.keyMap[XK_d] = KC_D; + x11.keyMap[XK_e] = KC_E; + x11.keyMap[XK_f] = KC_F; + x11.keyMap[XK_g] = KC_G; + x11.keyMap[XK_h] = KC_H; + x11.keyMap[XK_i] = KC_I; + x11.keyMap[XK_j] = KC_J; + x11.keyMap[XK_k] = KC_K; + x11.keyMap[XK_l] = KC_L; + x11.keyMap[XK_m] = KC_M; + x11.keyMap[XK_n] = KC_N; + x11.keyMap[XK_o] = KC_O; + x11.keyMap[XK_p] = KC_P; + x11.keyMap[XK_q] = KC_Q; + x11.keyMap[XK_r] = KC_R; + x11.keyMap[XK_s] = KC_S; + x11.keyMap[XK_t] = KC_T; + x11.keyMap[XK_u] = KC_U; + x11.keyMap[XK_v] = KC_V; + x11.keyMap[XK_w] = KC_W; + x11.keyMap[XK_x] = KC_X; + x11.keyMap[XK_y] = KC_Y; + x11.keyMap[XK_z] = KC_Z; + + x11.keyMap[XK_F1] = KC_F1; + x11.keyMap[XK_F2] = KC_F2; + x11.keyMap[XK_F3] = KC_F3; + x11.keyMap[XK_F4] = KC_F4; + x11.keyMap[XK_F5] = KC_F5; + x11.keyMap[XK_F6] = KC_F6; + x11.keyMap[XK_F7] = KC_F7; + x11.keyMap[XK_F8] = KC_F8; + x11.keyMap[XK_F9] = KC_F9; + x11.keyMap[XK_F10] = KC_F10; + x11.keyMap[XK_F11] = KC_F11; + x11.keyMap[XK_F12] = KC_F12; + x11.keyMap[XK_F13] = KC_F13; + x11.keyMap[XK_F14] = KC_F14; + x11.keyMap[XK_F15] = KC_F15; + + // keypad + x11.keyMap[XK_KP_0] = KC_NUMPAD0; + x11.keyMap[XK_KP_1] = KC_NUMPAD1; + x11.keyMap[XK_KP_2] = KC_NUMPAD2; + x11.keyMap[XK_KP_3] = KC_NUMPAD3; + x11.keyMap[XK_KP_4] = KC_NUMPAD4; + x11.keyMap[XK_KP_5] = KC_NUMPAD5; + x11.keyMap[XK_KP_6] = KC_NUMPAD6; + x11.keyMap[XK_KP_7] = KC_NUMPAD7; + x11.keyMap[XK_KP_8] = KC_NUMPAD8; + x11.keyMap[XK_KP_9] = KC_NUMPAD9; + x11.keyMap[XK_KP_Add] = KC_ADD; + x11.keyMap[XK_KP_Subtract] = KC_SUBTRACT; + x11.keyMap[XK_KP_Decimal] = KC_DECIMAL; + x11.keyMap[XK_KP_Equal] = KC_NUMPADEQUALS; + x11.keyMap[XK_KP_Divide] = KC_DIVIDE; + x11.keyMap[XK_KP_Multiply] = KC_MULTIPLY; + x11.keyMap[XK_KP_Enter] = KC_NUMPADENTER; + + // keypad with numlock off + x11.keyMap[XK_KP_Home] = KC_NUMPAD7; + x11.keyMap[XK_KP_Up] = KC_NUMPAD8; + x11.keyMap[XK_KP_Page_Up] = KC_NUMPAD9; + x11.keyMap[XK_KP_Left] = KC_NUMPAD4; + x11.keyMap[XK_KP_Begin] = KC_NUMPAD5; + x11.keyMap[XK_KP_Right] = KC_NUMPAD6; + x11.keyMap[XK_KP_End] = KC_NUMPAD1; + x11.keyMap[XK_KP_Down] = KC_NUMPAD2; + x11.keyMap[XK_KP_Page_Down] = KC_NUMPAD3; + x11.keyMap[XK_KP_Insert] = KC_NUMPAD0; + x11.keyMap[XK_KP_Delete] = KC_DECIMAL; + + x11.keyMap[XK_Up] = KC_UP; + x11.keyMap[XK_Down] = KC_DOWN; + x11.keyMap[XK_Left] = KC_LEFT; + x11.keyMap[XK_Right] = KC_RIGHT; + + x11.keyMap[XK_Page_Up] = KC_PGUP; + x11.keyMap[XK_Page_Down] = KC_PGDOWN; + x11.keyMap[XK_Home] = KC_HOME; + x11.keyMap[XK_End] = KC_END; + + x11.keyMap[XK_Num_Lock] = KC_NUMLOCK; + x11.keyMap[XK_Print] = KC_SYSRQ; + x11.keyMap[XK_Scroll_Lock] = KC_SCROLL; + x11.keyMap[XK_Pause] = KC_PAUSE; + + x11.keyMap[XK_Shift_R] = KC_RSHIFT; + x11.keyMap[XK_Shift_L] = KC_LSHIFT; + x11.keyMap[XK_Alt_R] = KC_RALT; + x11.keyMap[XK_Alt_L] = KC_LALT; + + x11.keyMap[XK_Insert] = KC_INSERT; + x11.keyMap[XK_Delete] = KC_DELETE; + + x11.keyMap[XK_Super_L] = KC_LWIN; + x11.keyMap[XK_Super_R] = KC_RWIN; + x11.keyMap[XK_Menu] = KC_APPS; + + // open display + x11.display = XOpenDisplay(0); + if(!x11.display) + fatal("Failed to open X display"); + + // check if GLX is supported on this display + if(!glXQueryExtension(x11.display, 0, 0)) + fatal("GLX not supported"); + + // retrieve GLX version + int glxMajor; + int glxMinor; + if(!glXQueryVersion(x11.display, &glxMajor, &glxMinor)) + fatal("Unable to query GLX version"); + notice("GLX version %d.%d", glxMajor, glxMinor); + + // clipboard related atoms + x11.atomClipboard = XInternAtom(x11.display, "CLIPBOARD", False); + x11.atomTargets = XInternAtom(x11.display, "TARGETS", False); + x11.atomUTF8String = XInternAtom(x11.display, "UTF8_STRING", False); + x11.atomText = XInternAtom(x11.display, "TEXT", False); + x11.atomCompoundText = XInternAtom(x11.display, "COMPOUND_TEXT", False); +} + +void Platform::terminate() +{ + if(x11.window) { + destroyWindow(); + x11.window = None; + } + + // close display + if(x11.display) { + XCloseDisplay(x11.display); + x11.display = NULL; + } +} + +void Platform::poll() +{ + XEvent event, peekevent; + static InputEvent inputEvent; + while(XPending(x11.display) > 0) { + XNextEvent(x11.display, &event); + + // call filter because xim will discard KeyPress events when keys still composing + if(XFilterEvent(&event, x11.window)) + continue; + + // discard events of repeated key releases + if(event.type == KeyRelease && XPending(x11.display)) { + XPeekEvent(x11.display, &peekevent); + if((peekevent.type == KeyPress) && + (peekevent.xkey.keycode == event.xkey.keycode) && + ((peekevent.xkey.time-event.xkey.time) < 2)) + continue; + } + + switch(event.type) { + case ConfigureNotify: + // window resize + if(x11.width != event.xconfigure.width || x11.height != event.xconfigure.height) { + x11.width = event.xconfigure.width; + x11.height = event.xconfigure.height; + g_engine.onResize(x11.width, x11.height); + } + break; + + case KeyPress: + case KeyRelease: { + KeySym keysym; + char buf[32]; + int len; + + inputEvent.key.ctrl = (event.xkey.state & ControlMask); + inputEvent.key.shift = (event.xkey.state & ShiftMask); + inputEvent.key.alt = (event.xkey.state & Mod1Mask); + + // fire enter text event + if(event.type == KeyPress && !inputEvent.key.ctrl && !inputEvent.key.alt) { + if(x11.xic) { // with xim we can get latin1 input correctly + Status status; + len = XmbLookupString(x11.xic, &event.xkey, buf, sizeof(buf), &keysym, &status); + } else { // otherwise use XLookupString, but it doesn't work right with dead keys often + static XComposeStatus compose = {NULL, 0}; + len = XLookupString(&event.xkey, buf, sizeof(buf), &keysym, &compose); + } + if(len > 0 && + // for some reason these keys produces characters and we don't want that + keysym != XK_BackSpace && + keysym != XK_Return && + keysym != XK_Delete && + keysym != XK_Escape + ) { + inputEvent.type = EV_TEXT_ENTER; + inputEvent.key.keychar = buf[0]; + inputEvent.key.keycode = KC_UNKNOWN; + g_engine.onInputEvent(&inputEvent); + } + } + + // unmask Shift/Lock to get expected results + event.xkey.state &= ~(ShiftMask | LockMask); + len = XLookupString(&event.xkey, buf, sizeof(buf), &keysym, 0); + + // fire key up/down event + if(x11.keyMap.find(keysym) != x11.keyMap.end()) { + inputEvent.key.keycode = x11.keyMap[keysym]; + inputEvent.type = (event.type == KeyPress) ? EV_KEY_DOWN : EV_KEY_UP; + inputEvent.key.keychar = (len > 0) ? buf[0] : 0; + g_engine.onInputEvent(&inputEvent); + } + break; + } + case ButtonPress: + case ButtonRelease: + switch(event.xbutton.button) { + case Button1: + inputEvent.type = (event.type == ButtonPress) ? EV_MOUSE_LDOWN : EV_MOUSE_LUP; + break; + case Button3: + inputEvent.type = (event.type == ButtonPress) ? EV_MOUSE_RDOWN : EV_MOUSE_RUP; + break; + case Button2: + inputEvent.type = (event.type == ButtonPress) ? EV_MOUSE_MDOWN : EV_MOUSE_MUP; + break; + case Button4: + inputEvent.type = EV_MOUSE_WHEEL_UP; + break; + case Button5: + inputEvent.type = EV_MOUSE_WHEEL_DOWN; + break; + } + g_engine.onInputEvent(&inputEvent); + break; + + case MotionNotify: + inputEvent.type = EV_MOUSE_MOVE; + inputEvent.mouse.x = event.xbutton.x; + inputEvent.mouse.y = event.xbutton.y; + g_engine.onInputEvent(&inputEvent); + break; + + case MapNotify: + x11.visible = true; + break; + + case UnmapNotify: + x11.visible = false; + break; + + case FocusIn: + x11.focused = true; + break; + + case FocusOut: + x11.focused = false; + break; + + // clipboard data request + case SelectionRequest: + { + XEvent respond; + XSelectionRequestEvent *req = &(event.xselectionrequest); + + if(req->target == x11.atomTargets ) { + Atom typeList[] = {x11.atomText, x11.atomCompoundText, x11.atomUTF8String, XA_STRING}; + + XChangeProperty(x11.display, req->requestor, + req->property, req->target, + 8, PropModeReplace, + (unsigned char *) &typeList, + sizeof(typeList)); + respond.xselection.property = req->property; + } else { + XChangeProperty(x11.display, + req->requestor, + req->property, req->target, + 8, + PropModeReplace, + (unsigned char*) x11.clipboardText.c_str(), + x11.clipboardText.size()); + respond.xselection.property = req->property; + } + + respond.xselection.type = SelectionNotify; + respond.xselection.display = req->display; + respond.xselection.requestor = req->requestor; + respond.xselection.selection = req->selection; + respond.xselection.target = req->target; + respond.xselection.time = req->time; + XSendEvent(x11.display, req->requestor, 0, 0, &respond); + XFlush(x11.display); + break; + } + + case ClientMessage: + { + if((Atom)event.xclient.data.l[0] == x11.atomDeleteWindow) + g_engine.onClose(); + break; + } + } + } +} + +unsigned long Platform::getTicks() +{ + static timeval tv; + static unsigned long firstTick = 0; + + gettimeofday(&tv, 0); + if(!firstTick) + firstTick = tv.tv_sec; + + return ((tv.tv_sec - firstTick) * 1000) + (tv.tv_usec / 1000); +} + +void Platform::sleep(unsigned long miliseconds) +{ + timespec tv; + tv.tv_sec = miliseconds / 1000; + tv.tv_nsec = (miliseconds % 1000) * 1000000; + nanosleep(&tv, NULL); +} + +bool Platform::createWindow(int width, int height, int minWidth, int minHeight) +{ + static int attrList[] = { + GLX_USE_GL, + GLX_RGBA, + GLX_DOUBLEBUFFER, + None + }; + + // choose OpenGL, RGBA, double buffered, visual + x11.visual = glXChooseVisual(x11.display, DefaultScreen(x11.display), attrList); + if(!x11.visual) + fatal("RGBA/Double buffered visual not supported"); + + // create GLX context + x11.glxContext = glXCreateContext(x11.display, x11.visual, 0, GL_TRUE); + if(!x11.glxContext) + fatal("Unable to create GLX context"); + + // color map + x11.colormap = XCreateColormap(x11.display, + RootWindow(x11.display, x11.visual->screen), + x11.visual->visual, + AllocNone); + + // setup window attributes + XSetWindowAttributes wa; + wa.colormap = x11.colormap; + wa.border_pixel = 0; + wa.event_mask = KeyPressMask | KeyReleaseMask | + ButtonPressMask | ButtonReleaseMask | PointerMotionMask | + ExposureMask | VisibilityChangeMask | + StructureNotifyMask | FocusChangeMask; + + // calculate center position + int x = (XDisplayHeight(x11.display, DefaultScreen(x11.display)) - width) / 2; + int y = (XDisplayHeight(x11.display, DefaultScreen(x11.display)) - height) / 2; + + // create the window + x11.window = XCreateWindow(x11.display, + RootWindow(x11.display, x11.visual->screen), + x, y, + width, height, + 0, + x11.visual->depth, + InputOutput, + x11.visual->visual, + CWBorderPixel | CWColormap | CWEventMask, + &wa); + + if(!x11.window) + fatal("Unable to create X window"); + + // setup locale to en_US.ISO-8859-1 characters + // and create input context (to get special characters from input) + if(setlocale(LC_ALL, "en_US.ISO-8859-1")) { + if(XSupportsLocale()) { + XSetLocaleModifiers(""); + x11.xim = XOpenIM(x11.display, NULL, NULL, NULL); + if(x11.xim) { + x11.xic = XCreateIC(x11.xim, + XNInputStyle, + XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, x11.window, NULL); + if(!x11.xic) + error("Unable to create the input context"); + } else + error("Failed to open an input method"); + } else + error("X11 does not support the current locale"); + } + else + error("Failed setting locale to latin1"); + + if(!x11.xic) + warning("Input of special keys maybe messed up because we couldn't create an input context"); + + + // set window minimum size + XSizeHints xsizehints; + xsizehints.flags = PMinSize; + xsizehints.min_width = minWidth; + xsizehints.min_height= minHeight; + XSetWMSizeHints(x11.display, x11.window, &xsizehints, XA_WM_NORMAL_HINTS); + + // handle delete window event + x11.atomDeleteWindow = XInternAtom(x11.display, "WM_DELETE_WINDOW", True); + XSetWMProtocols(x11.display, x11.window, &x11.atomDeleteWindow , 1); + + // connect the GLX-context to the window + glXMakeCurrent(x11.display, x11.window, x11.glxContext); + + x11.width = width; + x11.height = height; + return true; +} + +void Platform::destroyWindow() +{ + if(x11.glxContext) { + glXMakeCurrent(x11.display, None, NULL); + glXDestroyContext(x11.display, x11.glxContext); + x11.glxContext = NULL; + } + + if(x11.visual) { + XFree(x11.visual); + x11.visual = NULL; + } + + if(x11.colormap != None) { + XFreeColormap(x11.display, x11.colormap); + x11.colormap = 0; + } + + if(x11.window != None) { + XUnmapWindow(x11.display, x11.window); + XDestroyWindow(x11.display, x11.window); + x11.window = None; + } + + if(x11.xic) { + XDestroyIC(x11.xic); + x11.xic = NULL; + } + + if(x11.xim) { + XCloseIM(x11.xim); + x11.xim = NULL; + } +} + +void Platform::showWindow() +{ + XMapWindow(x11.display, x11.window); +} + +void Platform::setWindowTitle(const char *title) +{ + XStoreName(x11.display, x11.window, title); + XSetIconName(x11.display, x11.window, title); +} + +void *Platform::getExtensionProcAddress(const char *ext) +{ + return (void*)glXGetProcAddressARB((const GLubyte*)ext); +} + +bool Platform::isExtensionSupported(const char *ext) +{ + const char *exts = glXQueryExtensionsString(x11.display, DefaultScreen(x11.display)); + if(strstr(exts, ext)) + return true; + return true; +} + +const char *Platform::getTextFromClipboard() +{ + Window ownerWindow = XGetSelectionOwner(x11.display, x11.atomClipboard); + if(ownerWindow == x11.window) + return x11.clipboardText.c_str(); + + std::string clipboard = ""; + if(ownerWindow != None) { + XConvertSelection(x11.display, x11.atomClipboard, XA_STRING, 0, ownerWindow, CurrentTime); + XFlush(x11.display); + + // hack to wait SelectioNotify event, otherwise we will get wrong clipboard pastes + sleep(100); + + // check for data + Atom type; + int format; + unsigned long numItems, bytesLeft, dummy; + unsigned char *data; + XGetWindowProperty(x11.display, ownerWindow, + XA_STRING, + 0, 0, 0, + AnyPropertyType, + &type, + &format, + &numItems, + &bytesLeft, + &data); + if(bytesLeft > 0) { + // get the data get + int result = XGetWindowProperty(x11.display, ownerWindow, + XA_STRING, + 0, + bytesLeft, + 0, + AnyPropertyType, + &type, + &format, + &numItems, + &dummy, + &data); + if(result == Success) + clipboard = (const char*)data; + XFree(data); + } + } + return clipboard.c_str(); +} + +void Platform::copyToClipboard(const char *text) +{ + x11.clipboardText = text; + XSetSelectionOwner(x11.display, x11.atomClipboard, x11.window, CurrentTime); + XFlush(x11.display); +} + +void Platform::hideMouseCursor() +{ + if(x11.cursor == None) { + char bm[] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + Pixmap pix = XCreateBitmapFromData(x11.display, x11.window, bm, 8, 8); + XColor black; + memset(&black, 0, sizeof(XColor)); + black.flags = DoRed | DoGreen | DoBlue; + x11.cursor = XCreatePixmapCursor(x11.display, pix, pix, &black, &black, 0, 0); + XFreePixmap(x11.display, pix); + } + XDefineCursor(x11.display, x11.window, x11.cursor); +} + +void Platform::showMouseCursor() +{ + XUndefineCursor(x11.display, x11.window); + if(x11.cursor != None) { + XFreeCursor(x11.display, x11.cursor); + x11.cursor = None; + } +} + +void Platform::setVsync(bool enable) +{ + typedef GLint (*glSwapIntervalProc)(GLint); + glSwapIntervalProc glSwapInterval = NULL; + + if(isExtensionSupported("GLX_MESA_swap_control")) + glSwapInterval = (glSwapIntervalProc)getExtensionProcAddress("glXSwapIntervalMESA"); + else if(isExtensionSupported("GLX_SGI_swap_control")) + glSwapInterval = (glSwapIntervalProc)getExtensionProcAddress("glXSwapIntervalSGI"); + + if(glSwapInterval) + glSwapInterval(enable ? 1 : 0); +} + +void Platform::swapBuffers() +{ + glXSwapBuffers(x11.display, x11.window); +} + +bool Platform::isWindowFocused() +{ + return x11.focused; +} + +bool Platform::isWindowVisible() +{ + return x11.visible; +} diff --git a/srcs/main.cpp b/srcs/main.cpp deleted file mode 100644 index 772b1d6e..00000000 --- a/srcs/main.cpp +++ /dev/null @@ -1,7 +0,0 @@ -#include - -int main(int argc, const char *argv[]) -{ - std::cout << "Hello World!" << std::endl; - return 0; -}