2017年11月4日土曜日

XCBでウィンドウ表示まで

XCBは, Xlibを置き換えるために開発されている, X Serverとお話するためのCライブラリです.
私の目的はVulkanのためなのですが, ノートを書いておきます.
インストールしたXubuntuにはライブラリがないようなので, 以下のようにインストールします.
sudo apt install libxcb-xkb-dev
sudo apt install libxcb-icccm4-dev

ウィンドウのサイズ変更をさせたくないため, xcb_icccm_set_wm_size_hintsで最小と最大を設定しています.
そのため, libxcb-icccmをリンクしています. ついでにXlibのコンパイルコマンドも書いておきました.
clang++ -std=c++11 -DLIME_USE_XCB -lxcb -lxcb-icccm Window.cpp main.cpp
clang++ -std=c++11 -DLIME_USE_XLIB -lX11 Window.cpp main.cpp
#ifndef INC_LGRAPHICS_WINDOW_H_
#define INC_LGRAPHICS_WINDOW_H_
/**
@file Window.h
@author t-sakai
@date 2017/11/03
*/
#ifndef LASSERT
#include <cassert>
#endif

#ifndef _MSC_VER
#include <cstdint>
#endif

#ifdef LIME_USE_WIN32
#include <Windows.h>
#endif

#ifdef LIME_USE_XCB
#include <xcb/xcb.h>
#endif

#ifdef LIME_USE_XLIB
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#endif

#ifndef NULL
#   ifdef __cplusplus
#       if 201103L<=__cplusplus || 1900<=_MSC_VER
#           define NULL nullptr
#       else
#           define NULL 0
#       endif
#   else
#       define NULL (void*)0
#   endif
#endif

#ifndef LASSERT
#ifdef _DEBUG
#define LASSERT(exp) assert(exp)
#else
#define LASSERT(exp)
#endif
#endif

namespace lgfx
{
#ifdef _MSC_VER
    typedef __int16 s16;
    typedef __int32 s32;

    typedef unsigned __int16 u16;
    typedef unsigned __int32 u32;
#else
    typedef int16_t s16;
    typedef int32_t s32;

    typedef uint16_t u16;
    typedef uint32_t u32;
#endif

    class Window
    {
    public:
#ifdef LIME_USE_WIN32
        typedef MSG Event;
        typedef HWND Handle;

        struct InitParam
        {
            u32 width_;
            u32 height_;
            u32 style_;
            u32 exStyle_;
            WNDPROC wndProc_;
            const char* title_;
            bool windowed_;
        };
#endif
#ifdef LIME_USE_XCB
        typedef xcb_generic_event_t* Event;
        typedef xcb_window_t Handle;
        struct InitParam
        {
            u16 width_;
            u16 height_;
            const char* title_;
        };
#endif
#ifdef LIME_USE_XLIB
        typedef XEvent Event;
        typedef ::Window Handle;
        struct InitParam
        {
            u32 width_; 
            u32 height_;
            const char* title_;
        };
#endif

#ifdef LIME_USE_WIN32
        bool peekEvent(HWND hDlg, Event& ev);
#endif
#ifdef LIME_USE_XCB
        xcb_connection_t* getConnection();
#endif
#ifdef LIME_USE_XLIB
        Display* getDisplay();
#endif

        struct Vector2
        {
            s32 x_;
            s32 y_;
        };

        Window();
        ~Window();

        bool create(InitParam& param);
        void destroy();
        const Handle getHandle() const;
        Handle getHandle();
        void setShow(bool show);
        Vector2 getViewSize();

        bool peekEvent(Event& ev);
    private:
        Window(const Window&) = delete;
        Window& operator=(const Window&) = delete;

#ifdef LIME_USE_WIN32
        Handle handle_;
#endif
#ifdef LIME_USE_XCB
        xcb_connection_t* connection_;
        Handle handle_;
        xcb_intern_atom_reply_t* delete_;
        u16 width_;
        u16 height_;
#endif
#ifdef LIME_USE_XLIB
        Display* display_;
        Handle handle_;
        Atom delete_;
        s32 width_;
        s32 height_;
#endif
    };
}
#endif //INC_LGRAPHICS_WINDOW_H_
/**
@file Window.cpp
@author t-sakai
@date 2017/11/03
*/
#include "Window.h"

#ifdef LIME_USE_XCB
#include <xcb/xcb_icccm.h>
#include <stdlib.h>
#include <string.h>
#endif

#ifdef LIME_USE_XLIB
#include <X11/Xatom.h>
#include <string.h>
#endif

namespace lgfx
{
namespace
{
#ifdef LIME_USE_WIN32
    const char* CLASSNAME_ = "LWINDOW";
    static LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wPara, LPARAM lParam);
#endif
}
    Window::Window()
#ifdef LIME_USE_WIN32
        :handle_(NULL)
#endif
#ifdef LIME_USE_XCB
        :connection_(NULL)
        ,handle_(0)
        ,delete_(NULL)
#endif
#ifdef LIME_USE_XLIB
        :display_(NULL)
        ,handle_(0)
#endif
    {
    }

    Window::~Window()
    {
        destroy();
    }

    bool Window::create(InitParam& param)
    {
        LASSERT(NULL == handle_);
        LASSERT(NULL != param.title_);

#ifdef LIME_USE_WIN32
        HINSTANCE hInstance = GetModuleHandle(0);
        //Create and register window class
        //-----------------------------------------------------------
        WNDCLASSEX wcex;
        wcex.cbSize = sizeof(WNDCLASSEX);
        wcex.style   = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
        wcex.lpfnWndProc = (NULL == param.wndProc_)? WndProc : param.wndProc_;
        wcex.cbClsExtra  = 0;
        wcex.cbWndExtra  = 0;
        wcex.hInstance  = hInstance;
        wcex.hIcon   = LoadIcon(NULL, IDI_WINLOGO);
        wcex.hCursor  = LoadCursor(NULL, IDC_ARROW);
        wcex.hbrBackground = NULL;//(HBRUSH)(COLOR_WINDOW+1);
        wcex.lpszMenuName = NULL;
        wcex.lpszClassName = CLASSNAME_;
        wcex.hIconSm  = LoadIcon(wcex.hInstance, NULL);

        if(!RegisterClassEx(&wcex)){
            return false;
        }

        RECT rect;
        rect.left = 0;
        rect.top = 0;
        rect.right = param.width_;
        rect.bottom = param.height_;

        u32 style = param.style_;
        u32 exStyle = param.exStyle_;
        if(param.windowed_){
            exStyle |= WS_EX_CLIENTEDGE;
            style |= WS_SYSMENU | WS_BORDER | WS_CAPTION | WS_VISIBLE;
        }else{
            style |= WS_POPUP | WS_VISIBLE;
            exStyle |= WS_EX_APPWINDOW;
            ShowCursor(FALSE);
        }

        AdjustWindowRectEx(&rect, style, FALSE, exStyle);

        handle_ = CreateWindowEx(
            exStyle,
            CLASSNAME_,
            param.title_,
            style,
            CW_USEDEFAULT, CW_USEDEFAULT,
            rect.right - rect.left,
            rect.bottom - rect.top,
            NULL,
            NULL,
            hInstance,
            NULL);

        if(NULL == handle_){
            UnregisterClass(CLASSNAME_, hInstance);
            return false;
        }

        UpdateWindow(handle_);
        SetFocus(handle_);
#endif

#ifdef LIME_USE_XCB
        int screenIndex = 0;
        connection_ = xcb_connect(NULL, &screenIndex);
        if(xcb_connection_has_error(connection_)){
            connection_ = NULL;
            return false;
        }
        const xcb_setup_t* setup = xcb_get_setup(connection_);
        xcb_screen_iterator_t screenIterator = xcb_setup_roots_iterator(setup);
        for(int i=0; i<screenIndex; ++i){
            xcb_screen_next(&screenIterator);
        }
        xcb_screen_t* screen = screenIterator.data;
        handle_ = xcb_generate_id(connection_);

        u32 valueList[] =
        {
            screen->white_pixel,
            XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY,
        };

        xcb_create_window(
            connection_,
            XCB_COPY_FROM_PARENT,
            handle_,
            screen->root,
            -1, -1, param.width_, param.height_,
            0,
            XCB_WINDOW_CLASS_INPUT_OUTPUT,
            screen->root_visual,
            XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK,
            valueList);
        xcb_flush(connection_);
        xcb_change_property(
            connection_,
            XCB_PROP_MODE_REPLACE,
            handle_,
            XCB_ATOM_WM_NAME,
            XCB_ATOM_STRING,
            8,
            strlen(param.title_),
            param.title_);

        xcb_intern_atom_cookie_t protocols_cookie = xcb_intern_atom(connection_, 1, 12, "WM_PROTOCOLS");
        xcb_intern_atom_reply_t* protocol_reply = xcb_intern_atom_reply(connection_, protocols_cookie, 0);
        xcb_intern_atom_cookie_t delete_cookie = xcb_intern_atom(connection_, 0, 16, "WM_DELETE_WINDOW");
        delete_ = xcb_intern_atom_reply(connection_, delete_cookie, 0);
        xcb_change_property(connection_, XCB_PROP_MODE_REPLACE, handle_, (*protocol_reply).atom, 4, 32, 1, &(*delete_).atom);
        free(protocol_reply);
        
        xcb_size_hints_t hints;
        xcb_icccm_size_hints_set_min_size(&hints, param.width_, param.height_);
        xcb_icccm_size_hints_set_max_size(&hints, param.width_, param.height_);
        xcb_icccm_set_wm_size_hints(connection_, handle_, XCB_ATOM_WM_NORMAL_HINTS, &hints);

        width_ = param.width_;
        height_ = param.height_;
        xcb_map_window(connection_, handle_);
        xcb_flush(connection_);
#endif
#ifdef LIME_USE_XLIB
        display_ = XOpenDisplay(NULL);
        if(NULL == display_){
            return false;
        }
        int default_screen = DefaultScreen(display_);
        handle_ = XCreateWindow(
            display_,
            DefaultRootWindow(display_),
            -1, -1, param.width_, param.height_,
            0, //border width
            CopyFromParent, //color depth
            InputOutput, //
            DefaultVisual(display_, DefaultScreen(display_)),
            0, NULL);
        if(handle_<=0){
            XCloseDisplay(display_);
            display_ = NULL;
            return false;
        }
        //------------------------
        XTextProperty name;
        name.value = reinterpret_cast<unsigned char*>(const_cast<char*>(param.title_));
        name.encoding = XA_STRING;
        name.format = 8;
        name.nitems = strlen(param.title_);
        XSetWMName(display_, handle_, &name);

        delete_ = XInternAtom(display_, "WM_DELETE_WINDOW", false);
        XSetWMProtocols(display_, handle_, &delete_, 1);
        XSelectInput(display_, handle_, ExposureMask | StructureNotifyMask);

        XSizeHints hints;
        hints.min_width = param.width_;
        hints.min_height = param.height_;
        hints.max_width = param.width_;
        hints.max_height = param.height_;
        hints.flags = PMinSize | PMaxSize;
        XSetWMNormalHints(display_, handle_, &hints);

        width_ = static_cast<s32>(param.width_);
        height_ = static_cast<s32>(param.height_);
        XClearWindow(display_, handle_);
        XMapWindow(display_, handle_);
#endif
        return true;
    }

    void Window::destroy()
    {
#ifdef LIME_USE_WIN32
        if(NULL != handle_){
            DestroyWindow(handle_);
            handle_ = NULL;
            UnregisterClass(CLASSNAME_, GetModuleHandle(0));
        }
#endif
#ifdef LIME_USE_XCB
        if(NULL != delete_){
            free(delete_);
            delete_ = NULL;
        }
        if(NULL != connection_){
            xcb_destroy_window(connection_, handle_);
            xcb_disconnect(connection_);
            handle_ = 0;
            connection_ = NULL;
        }
#endif
#ifdef LIME_USE_XLIB
        if(NULL != display_){
            XDestroyWindow(display_, handle_);
            XCloseDisplay(display_);
            handle_ = 0;
            display_ = NULL;
        }
#endif
    }

    const Window::Handle Window::getHandle() const
    {
        return handle_;
    }

    Window::Handle Window::getHandle()
    {
        return handle_;
    }

    void Window::setShow(bool show)
    {
#ifdef LIME_USE_WIN32
        ShowWindow(handle_, (show)?SW_SHOW : SW_HIDE);
#endif
#ifdef LIME_USE_XCB
        if(show){
            xcb_map_window(connection_, handle_);
        }else{
            xcb_unmap_window(connection_, handle_);
        }
#endif
#ifdef LIME_USE_XLIB
        if(show){
            XMapWindow(display_, handle_);
        }else{
            XUnmapWindow(display_, handle_);
        }
#endif
    }

    Window::Vector2 Window::getViewSize()
    {
#ifdef LIME_USE_WIN32
        RECT rect;
        s32 width, height;
        if(TRUE == GetClientRect(handle_, &rect)){
            width = rect.right - rect.left;
            height = rect.bottom - rect.top;
        }else{
            width = 1;
            height = 1;
        }
        return {width, height};
#endif
#ifdef LIME_USE_XCB
        xcb_get_geometry_cookie_t cookie = xcb_get_geometry(connection_, handle_);
        xcb_get_geometry_reply_t* geom = xcb_get_geometry_reply(connection_, cookie, NULL);
        s32 width = geom->width - geom->border_width*2;
        s32 height = geom->height - geom->border_width*2;
        free(geom);
        return {width, height};
#endif
#ifdef LIME_USE_XLIB
        s32 x;
        s32 y;
        s32 width=1;
        s32 height=1;
        ::Window root;
        u32 border;
        u32 depth;
        XGetGeometry(display_, handle_,
            &root,
            &x, &y, reinterpret_cast<u32*>(&width), reinterpret_cast<u32*>(&height),
            &border, //border
            &depth);//depth
        return {width, height};
#endif
    }

    bool Window::peekEvent(Event& ev)
    {
#ifdef LIME_USE_WIN32
        while(PeekMessage(&ev, NULL, 0, 0, PM_REMOVE)){
            if(ev.message==WM_QUIT)
                return false;
            TranslateMessage(&ev);
            DispatchMessage(&ev);
        }
        return true;
#endif
#ifdef LIME_USE_XCB
        for(;;){
            ev = xcb_poll_for_event(connection_);
            if(NULL == ev){
                return true;
            }
            switch(ev->response_type & 0x7F){
            case XCB_CONFIGURE_NOTIFY:
                break;
            case XCB_CLIENT_MESSAGE:
                if((*(xcb_client_message_event_t*)ev).data.data32[0] == (*delete_).atom){
                    return false;
                }
                break;
            }
        } //for(;;)
#endif
#ifdef LIME_USE_XLIB
        while(XPending(display_)){
            XNextEvent(display_, &ev);
            switch(ev.type){
            case ConfigureNotify:
                break;
            case DestroyNotify:
                return false;
            case ClientMessage:
                if(static_cast<u32>(ev.xclient.data.l[0]) == delete_){
                    return false;
                }
                break;
            }
        } //while(XPending
        return true;
#endif
    }


#ifdef LIME_USE_WIN32
    bool Window::peekEvent(HWND hDlg, Event& ev)
    {
        while(PeekMessage(&ev, NULL, 0, 0, PM_REMOVE)){
            if(ev.message==WM_QUIT)
                return false;

            if(NULL != hDlg && IsDialogMessage(hDlg, &ev)){
                continue;
            }

            TranslateMessage(&ev);
            DispatchMessage(&ev);
        }
        return true;
    }
#endif

#ifdef LIME_USE_XCB
    xcb_connection_t* Window::getConnection()
    {
        return connection_;
    }
#endif

#ifdef LIME_USE_XLIB
    Display* Window::getDisplay()
    {
        return display_;
    }
#endif

    namespace
    {
#ifdef LIME_USE_WIN32
        static LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
        {
            switch(msg)
            {
            case WM_SYSCOMMAND:
            {
                switch(wParam)
                {
                case SC_SCREENSAVE:
                case SC_MONITORPOWER:
                    return 0;
                }
                return DefWindowProc(hWnd, msg, wParam, lParam);
            }
            break;

            //case WM_CLOSE:
            //    DestroyWindow(hWnd);
            //    break;

            case WM_PAINT:
            {
                ValidateRect(hWnd, NULL);
            }

            break;

            case WM_DESTROY:
                PostQuitMessage(0);
                return 1;
                break;

            default:
                return DefWindowProc(hWnd, msg, wParam, lParam);
                break;
            }
            return 0;
        }
#endif
    }
}

0 件のコメント:

コメントを投稿