Graphics And Media/Wayland

Hello wayland - 간단 예제

kahuz 2021. 5. 29. 19:25

 

 

hello wayland

앞선 과정에서 웨이랜드가 무엇인지, 웨이랜드로 윈도우를 관리하기 위해 구성되는 요소들이 무엇인지, 그 구성요소들은 웨이랜드에서 어떻게 표현되는지에 간략히 소개하였습니다.

hello wayland 에서는 앞선 내용을 바탕으로 간단한 예제를 소개하도록 합니다.

 

Required library

리눅스에서 웨이랜드로 윈도우를 관리하기 위해서는 wayland library와 화면의 렌더링을 담당할 gles 라이브러리가 필요합니다.

wayland library는 디스플레이 장치로부터 윈도우 창을 생성하며, 그 외에 마우스나 키보드와 같은 윈도우에서 사용할 수 있는 입력 장치들과 연결하는 등에 필요한 라이브러리 입니다.

gles는 디스플레이 장치에 표현할 출력 화면의 렌더링에 필요한 라이브러리 입니다. 배경을 꾸미고, 이미지를 생성하여 화면에 보여주고 싶은 내용을 만드는데 필요한 라이브러리 입니다.

 

Example code

아래는 예제 코드는 wayland를 이용하여 디스플레이 장치로부터 윈도우를 생성, 키보드와 마우스 장치를 디스플레이 장치와 연결하여 입력받은 이벤트에 대해 간단한 처리를 행하는 예제입니다.

자세한 내용은 주석으로 대체하였습니다. 전체 소스 코드 프로젝트는 ( https://github.com/kahuz/wayland-manager ) 에서 받아보실 수 있습니다.

받은 프로젝트는 cmake를 통해 생성 및 make install로 바이너리 생성이 가능합니다.

 

가장 먼저 디스플레이 장치와 wayland를 연결하여 프로그램이 윈도우를 생성할 수 있도록 디스플레이 장치로부터 compositor와 shell을 얻어와 client를 구성하도록 합니다. seat의 경우 앞서 설명한 키보드, 마우스와 같은 입력 장치를 관리하기 위한 객체로 생성한 윈도우의 키보드 및 마우스 이벤트를 정의하기 위해 생성합니다.

 


void WLManager::InitWLClient()
{
	disp_info.wl_disp = (wl_display *)wl_display_connect(NULL);

	if(disp_info.wl_disp == NULL)
	{	
		Log("Can't create wayland display\n");
		exit(1);	
	}

	disp_info.registry = (wl_registry *)wl_display_get_registry(disp_info.wl_disp);

	//binding wayland compositor & wayland shell on system
	wl_registry_add_listener(disp_info.registry, &registry_listener, &wl_component /* is send to listner*/);

	// This call the attached listener global_registry_handle
	wl_display_dispatch(disp_info.wl_disp);
	// Wait until all events have been processed
	wl_display_roundtrip(disp_info.wl_disp);

	if(wl_component.compositor == NULL)
	{
		Log("Can't create wayland compositor\n");
		exit(1);
	}

	if(wl_component.shell == NULL)
	{
		Log("Can't create wayland shell\n");
		exit(1);
	}

	if(wl_component.seat == NULL)
	{
		Log("Can't create wayland seat\n");
		wl_component.exist_seat = false;
	}
	else
	{
		wl_component.exist_seat = true;
	}

	Log("Okay, we got a compositor and a shell... That's something !\n");
	disp_info.egl_info.native_display = disp_info.wl_disp;

}

 

wl_display_connect 함수를 통해 사용 가능한 웨이랜드 디스플레이 장치를 가져오고 wl_displaay_get_registry 함수로 해당 디스플레이에서 이벤트를 관리하는 레지스트리를 받아옵니다.

 

wl_registry_add_litener 함수는 가져온 레지스트리에 프로그램이 사용할 이벤트들을 정의한 글로벌 리스트너를 추가하는 함수입니다.

 

예제의 registry_litener 에서는 아래와 같이 등록된 리스트너 중 grobal_registry_handler 에 정의된 핸들러를 호출하여 필요한 작업 내용을 수행합니다.

레지스트리 코드 내용을 보면 알 수 있듯 이벤트 핸들러로부터 전달받은 interface의 내용을 확인하여 compositor와 shell, seat를 생성하고 있습니다.

이 부분은 wayland에서 참조할 컴포지터 객체들이 어떤 것인지에 따라 내용이 달라질 수 있다. 예를 들어 리눅스에서 기본적인 wayland는 x-wayland를 기반으로 동작하여 그에 맞는 프로토콜이 정의됩니다.

compositor는 프로토콜에서 정의한 wl_compositor 객체로, shell은 wl_shell로, seat는 wl_seat로 정의되고 있습니다. 하지만 목적에 따라 weston의 ivi-shell을 이용한다거나 하면 그 내용이 ivi_compositor, ivi_shell, ivi_seat와 같이 달라질 수 있습니다.

 

따라서 사용할 컴포지터의 참조 구현체에 따라 그 내용을 적절히 작성해주면 됩니다.

 

estatic void global_registry_handle(void *data, struct wl_registry *registry, uint32_t id, const char *interface, uint32_t version) 
{
	WLComponent *component = (WLComponent *)data;
	Log("Got a registry event for %s id %d\n", interface, id);
	if (strcmp(interface, "wl_compositor") == 0)
	{
		Log("Create wl compositor\n");
		component->compositor = (wl_compositor *)wl_registry_bind(registry, id, &wl_compositor_interface, 1);
	}
	else if (strcmp(interface, "wl_shell") == 0)
	{
		Log("Create wl shell\n");
		component->shell = (wl_shell *)wl_registry_bind(registry, id, &wl_shell_interface, 1);
	}
	else if( strcmp(interface, "wl_seat") == 0)
	{
		Log("Create wl seat\n");
		component->seat = (wl_seat *)wl_registry_bind(registry, id, &wl_seat_interface, 1);

		wl_seat_add_listener( component->seat, &seat_listener, &component->input_dev);
	}
}

const struct wl_registry_listener registry_listener = {
	global_registry_handle,
	global_registry_remover
};

위 과정을 통해 wayland의 윈도우 관리를 위한 컴포넌트들의 초기화가 끝났다면 렌더링을 위한 EGL (GLES) 의 초기화 과정을 수행해줍니다. 해당 내용은 wayland와 크게 중요하지 않으므로 부연 설명을 하지 않겠습니다.

 

EGL 초기화가 완료되었다면 wl_compositor로부터 화면에 렌더링할 영역인 wl_region을 생성해줍니다.

생성된 region은 compositor에 연결된 shell_surface에 등록합니다. 그 후 wl_egl_window_create 함수를 통해 wl_surface로부터 egl을 위한 디스플레이 장치 정보를 받아와 egl을 생성합니다.

 

EGLBoolean WLManager::CreateWindowWithEGLContext(const char* title, int width, int height) 
{
	EGLBoolean ret = EGL_FALSE;

	//created surface regions.
	disp_info.region = wl_compositor_create_region(wl_component.compositor);

	//added region infomation.
	wl_region_add(disp_info.region, 0, 0, width, height);
	//added region into surface.
	wl_surface_set_opaque_region(window.surface, disp_info.region);

	//is not doing
	wl_shell_surface_set_title(window.shell_surface, title);

	//created egl native window.
	disp_info.egl_window = wl_egl_window_create(window.surface, width, height);

	if (disp_info.egl_window == EGL_NO_SURFACE) 
	{
		Log("Err EGL No Surface\n");
		exit(1);
	}
	else
	{
		Log("Create EGL Surface\n");
	}

	disp_info.window_width = width;
	disp_info.window_height = height;
	disp_info.egl_info.native_window = disp_info.egl_window;

	ret =  CreateEGLContext( &disp_info.egl_info );

	wl_shell_surface_add_listener(window.shell_surface, &shell_surface_listener, &disp_info/* is send to listner*/);

	return ret;
}

 

그리고 마지막으로 wl_shell_surface_add_listener 함수를 통해 compositor에 대한 shell_surface 이벤트 핸들러를 등록해줍니다.

여기까지 완료되었다면 화면에 윈도우를 생성하기 위한 기초 과정은 모두 수행되었습니다. 다만 예제에서는 마우스와 키보드 제어 기능까지 구현되어 있으므로 해당 내용에 대해 설명하겠습니다.

 

우리는 아래와 같이 compositor와 shell을 생성할 때 seat를 같이 생성하였었습니다.

	else if( strcmp(interface, "wl_seat") == 0)
	{
		Log("Create wl seat\n");
		component->seat = (wl_seat *)wl_registry_bind(registry, id, &wl_seat_interface, 1);

		wl_seat_add_listener( component->seat, &seat_listener, &component->input_dev);
	}

 

seat는 wayland의 입력 장치들의 정보를 가진 객체이고, wl_seat_add_listener는 입력 장치에 대한 이벤트 핸들러를 등록하는 함수입니다. 아래 코드와 같이 seat_handle_capabilities라는 함수를 등록하면 프로그램은 wayland로 부터 caps라는 정보를 가져옵니다.

 

caps는 현재 시스템에서 사용 가능한 웨이랜드 입력 장치들에 대한 정보를 가진 변수입니다. caps로부터 WL_SEAT_CAPABILITY_POINTER 와 같은 상수 값을 통해 마우스나 키보드, 터치패드와 같은 입력 장치를 사용가능한지 확인할 수 있습니다.

 

만약 해당 장치를 사용할 수 있다면 위에서 해왔던 것과 같이 각 입력 장치에 대한 이벤트 핸들러를 등록해주도록 합니다. 해당 이벤트 핸들러의 내용은 전체 소스코드에서 확인해주시기 바랍니다.

static struct wl_seat_listener seat_listener = 
{
	seat_handle_capabilities,	
};

static void seat_handle_capabilities(void *data, struct wl_seat *in_seat, uint32_t caps)
{
	WLInputDev *dev = (WLInputDev *)data;
	if( caps & WL_SEAT_CAPABILITY_POINTER ) 
	{
		struct wl_pointer* in_pointer = wl_seat_get_pointer( in_seat );
		wl_pointer_add_listener( in_pointer, &pointer_listeners, &dev->wl_cursor);
		dev->mouse = in_pointer;
	}
	else if( !(caps & WL_SEAT_CAPABILITY_POINTER) ) 
	{
		wl_pointer_destroy( dev->mouse );
		dev->mouse = NULL;
	}

	if( caps & WL_SEAT_CAPABILITY_KEYBOARD ) 
	{
		struct wl_keyboard* kbd = wl_seat_get_keyboard( in_seat );
		wl_keyboard_add_listener( kbd, &keyboard_listeners, NULL );
		dev->keyboard = kbd;
	}
	else if( !(caps & WL_SEAT_CAPABILITY_KEYBOARD) ) 
	{
		wl_keyboard_destroy( dev->keyboard );
		dev->keyboard = NULL;
	}
}

 

위 내용들을 통해 우리는 wayland로부터 디스플레이 장치에 대한 정보를 얻어와 윈도우를 생성하고 윈도우에 입력 장치의 액션에 대한 처리를 등록하여 GUI 프로그램을 설계할 수 있게 되었습니다.

 

Conclusion

사실 웨이랜드는 상당히 사용하기 쉽게 만들어져서 복잡하지 않게 사용할 수 있습니다. 다만 시스템 환경에 맞게 프로토콜을 정의하고 사용하는 부분과 각 장치들을 어떻게 다뤄야할지 알고 있어야 제대로 사용할 수 있는 api입니다.

좀더 다양하고 상세한 기능을 구현해보기에 앞서 desktop-shell, ivi-shell, x-wayland와 같은 내용들에 대해 먼저 파악해보기를 추천드립니다.

'Graphics And Media > Wayland' 카테고리의 다른 글

Getting Started Wayland !  (0) 2021.05.08