2012년 12월 23일 일요일

WebKitGtk+ Hackfest 2012 후기



지난 12월 9일 부터 12일까지 4일간 Spain  Coruña에서 열린 WebKitGtk+ Hackfest 2012 후기를 공유합니다. 이번이 4번째 행사이고, 저는 2회부터 이번이 세번째 참석이네요. 운좋게 매년 Spain을 같은 도시를 방문하고 있습니다. 계절이 겨울이라 도시의 아름다움을 만끽하기는 힘들지만, 대서양을 바라볼 수 있는 아름다운 해안 도시를 매년 방문하게 되어 행복합니다.

참고로, WebKitGtk+는 WebKit의 Gtk+ port입니다. Safari, Chrome 브라우저가 대표적으로 WebKit Engine을 사용하는 브라우저지만, WebKitGtk+는 eBooK reader, DTV등 다양한 Embedded Device에서 사용됩니다.  물론, GNOME Browser(Epiphany)도 WebKitGtk+를 사용하고 있습니다.  매년 참석자가 증가하고 있고 올해는 20여명이 참석한 것 같습니다. 

이번 행사에서 작업한 내용은 아래와 같습니다.

  • Many improvements in Web, the GNOME web browser: a new incognito mode to minimize the risk of being tracked both on the internet and in your own computer, a slick new UI for the search, revamping the creation of Web applications by selecting better icons and names, adding undo close tabs, preventing empty download-only windows and preliminary work for the integration of the Document Viewer in the browser.
  • Switching from Pango to Harfbuzz to render complex text.
  • Porting the WebAudio backend to GStreamer 1.0.
  • Better DOM bindings.
  • Many bugs fixed for accelerated compositing with Clutter, both in ELF and GTK+ ports.
  • Stabilization of  the libsoup API.
  • Improvements in the memory use of the V8 Javascript engine.
  • 2D-canvas accelerated support using Cairo OpenGL.
  • Better HTML5 media controls.
  • A new API to retrieve a screenshots from web pages.
  • Progress in accessibility support.
  • Documentation.
출처: http://www.gnome.org/news/2012/12/gnome-webkitgtk-hackfest/

저는 Accelerated Compositing Clutter backend을 드디어 반영했습니다. 이제 기본적인 CSS 3D Transforms가 동작합니다. 4일 동안  옆에 Reviewer를 두고 작업을 하니, 역시 Patch반영에 속도가 붙습니다. 



Hackfest의 이모저모는 제가 올린 사진을 확인하세요.  비록 Hacking하고 먹고 잔 기억밖에 없기 하지만요.

GNOME에서는 다양한 Hackfest가 열립니다. maintainer나 주요 Contributor는 GNOME Foundation에서 여행경비를 지원합니다.  여러분도 특정 프로젝트에 집중적으로 기여를 하면 초대를 받을 수 있습니다.

그럼, Happy Hacking!


2012년 12월 2일 일요일

제 11회 GNOME Korea Tech Talk 후기

11회 GNOME Korea Tech Talks 는 12/1 선릉 CNN The Biz 에서 진행했습니다.

이번 Talk 에서는 두 세미나가 진행이 되었는데요,

첫번째 세션으로 커널 개발자이신 김남형 님께서 perf  를 주제로 설명을 해주셨습니다.



리눅스 환경에서 개발을 하면서 내 어플리케이션의 어느부분이 병목인지 알고 싶은 분들이 많을텐데요, perf를 처음 사용해보는 초보자도 perf 를 통해 어플리케이션 계층의 정보 뿐만아니라 커널 계층의 정보까지 알아낼 수 있게 직접 데모를 보여주시며 쉽게 설명을 해주셔서 앞으로 개발을 하며 많은 도움이 될 것 같습니다. 다음에도 좋은 세미나 부탁 드립니다! :)

두번째 세션으로 홍영기 님께서 RDF, SPARQL and Tracker의 주제로 세미나를 진행하셨습니다.


지난 세미나 (Introduction to  Tracker)에 이어 이번 세미나에서는 Tracker 에서 사용하는 RDF, SPARQL, Ontology 등에 대한 내용을 소개하였습니다. Tracker 에서는 Nepomuk ontology 를 사용하여 Semantic Data Storage 를 구성하는데, Tracker 를 처음 접하게 되면 RDF 나 Ontology, SPARQL 등이 생소하여 처음에 쉽게 이해하기가 어려운 부분이 있습니다. 그래서 Tracker의 내부구조를 이해하기에 필요한 최소한의 정보등을 제공하는 것이 이번 세미나의 목적이었는데, 퀴즈도 쉽게 풀어주셔서 소기의 목적은 달성한것 같습니다 :)

세미나에 참석해주신 많은 분들께 감사드리며, 다음 모임에도 다시 뵈었으면 좋겠습니다!





2012년 11월 28일 수요일

GNOME.Asia 2013 Summit 준비 모임

내년에 서울에서 열릴 GNOME.Asia 2013 의 성공적인 개최를 위해 GNOME Korea 의
핵심 멤버들이 모였습니다.


드디어 약 6개월여 동안 Local Organizer 로 활동할 멤버가 구성이 되었네요.
관심이 없으시면 어쩌나 걱정을 했었는데, 제가 괜한 걱정을 했습니다.

앞으로 6개월 준비할일이 많겠지만, 즐거운 준비과정이 될것 같은 느낌이 들었습니다.

GNOME Korea 화이팅입니다 :)

2012년 11월 20일 화요일

제 11회 GNOME TechTalks 안내


그놈 한국 커뮤니티에서 다음과 같은 일정으로
제 11회 GNOME Techtalks 를 개최합니다.

일시: 12월 1일 토요일 오후  2시 ~ 4시 30분
장소: CNN The Biz 선릉점

세부일정
* perf 사용법 (김남형님)
* RDF, SPARQL and Tracker (홍영기님)
* GNOME.Asia 2013 관련 내용

장소 및 식사 후원: 구글 코리아(http://goo.gl/APdCM)
신청 (http://onoffmix.com/event/10779)

2012년 10월 28일 일요일

제 10회 GNOME Techtalk & Hackfest 안내

그놈 한국 커뮤니티에서 다음과 같은 일정으로
제 10회 GNOME Tech Talks (기술 세미나) & Hackfest 를 개최합니다.

일시: 11월 3일 토요일 오전 11시 ~ 오후 4시
세부일정
1부: 11시 ~ 2시
* Hackfest 를 진행합니다.
* 오픈소스와 관련된 개발이나 문서작업 등을 진행하는 시간입니다.
* 새로운 오픈소스 라이브러리를 공부하고 익히는 작업도 좋습니다.
* 개인적으로 진행하는 작업을 오셔서 계속하셔도 좋습니다.

2부: 2시 ~ 4시 
* RDF, SPARQL and Tracker (홍영기)
* Hackfest 회고(작업내용 발표)

장소 및 식사 후원: 구글 코리아 http://goo.gl/APdCM

2012년 9월 28일 금요일

제 9회 GNOME Hackfest 후기

9회 GNOME Hackfest 모임이 9월 22일 CNN THE  BIZ 선릉점에서  있었습니다.

이번달 모임은 이진규님, 홍영기님께서 참여하셨습니다.
이진규님께서는 Hackfest 시간동안 모질라 캘린더 프로젝트 번역작업을 진행하셨고,
홍영기님께서는 두건의 Chromium 버그 패치를 올리셨습니다.





















10월에는 다음에서 주최하는 DevOn 에 그놈 한국 커뮤니티에서도 부스를
개설할 예정입니다.

많은 참여 부탁드립니다.

2012년 9월 21일 금요일

제 9회 GNOME Hackfest 안내


그놈 한국 커뮤니티에서 다음과 같은 일정으로
제 9회 GNOME Hackfest 를 개최합니다.

일시: 9월 22일 토요일 오전 12시 ~ 오후 4시
세부일정
* Hackfest 를 진행합니다.
* 오픈소스와 관련된 개발이나 문서작업 등을 진행하는 시간입니다.
* 새로운 오픈소스 라이브러리를 공부하고 익히는 작업도 좋습니다.
* 개인적으로 진행하는 작업을 오셔서 계속하셔도 좋습니다.

장소 및 식사 후원: 구글 코리아 http://goo.gl/APdCM

2012년 9월 9일 일요일

제 8회 GNOME TechTalk & Hackfest 후기

8회 후기는 오창석님께서 작성해주셨습니다. 감사합니다.

8회 GNOME TechTalk 모음이 8월 25일 CNN THE  BIZ 선릉점에서  있었습니다.

기존처럼 1부는 자유롭게 오픈소스 관련 작업을 하면서 관심사를 공유하였습니다.
요즘 주요 관심사는 GNOME 관련 한글 튜토리얼 작성인데 문서 포맷등의 여러 시행착오가 있어서 진행이 좀 더딘감이 없지않아있네요.

2부에서는 먼저 서주영님께서 Enlightment에 대한 소개와 현재 개발 진행사항에 대해서 공유해주셨습니다.




조성호님께서 GNOME 한글화에 참여하는 방법, 그리고 남아있는 이슈에 대해서 공유해주셨습니다.




마지막으로 뜻하지 않게 반가운 손님이 찾아오셨습니다.
Enlightenment의 창시자이나 Maintainer인 Carsten Haitzler가 서주영님의 전화 한통에 이렇게 먼 발걸음 해주셨습니다.
과연 거대한 한 오픈소스의 창시자답게 Enlightment에 대한 여러가지 장점과 기능을 열정적으로 보여주셨습니다.
휴가때 간단히(?) 만들었다는 화려한 Terminal 환경이 기억에 남네요.





8월 모임에 참여해주신 분들께 감사의 말씀드리고, 9월에 다시 뵐 수 있으면 좋겠습니다.

2012년 8월 16일 목요일

제 8회 GNOME TechTalk & Hackfest 안내


그놈 한국 커뮤니티에서 다음과 같은 일정으로제 8회 GNOME Tech Talks (기술 세미나) & Hackfest 를 개최합니다.

일시: 8월 25일 토요일 오전 11시 ~ 오후 4시
장소: CNN THE BIZ 선릉점

세부일정
1부: 11시 ~ 2시
* Hackfest 를 진행합니다.
* 오픈소스와 관련된 개발이나 문서작업 등을 진행하는 시간입니다.
* 새로운 오픈소스 라이브러리를 공부하고 익히는 작업도 좋습니다.
* 개인적으로 진행하는 작업을 오셔서 계속하셔도 좋습니다.

2부: 2시 ~ 4시
* Enlightenment 소개 (서주영님)
* 그놈 한국 L10N (조성호님)
* 1부 Hackfest 회고 (작업내용 정리)

장소 및 식사 후원: 구글 코리아 http://goo.gl/APdCM

신청: http://onoffmix.com/event/8645




2012년 8월 3일 금요일

7회 GNOME TechTalk 후기

안녕하세요,

지난 7월 28일에 그놈 한국 모임이 진행되었습니다. 
이번 모임에서는 1부와 2부로 구분하여 진행하였습니다.

1부에서는 자유롭게 오픈소스 관련 작업을 하는 시간으로 구성하였고 다음과 같은 내용으로 작업을 진행하셨습니다.

조성호님께서는 그놈의 개발자 홈페이지의 한글화 작업을 진행하셨습니다. 조만간 한글로 된 developer.gnome.org 를 볼 수 있을 것 같습니다.
이진규님께서는 그놈 한국 커뮤니티에서 진행중인 개발 문서화 작업중 glade 관련 문서작업을 진행하셨습니다.
홍영기님께서는 크로미움 프로젝트의 디스크 캐시 저장공간 관련 작업을 진행하셨습니다.




2부에서는 홍영기님께서 2012년 OSCON 참관기와 Wayland Introduction 을 진행하셨습니다. 많은 분들께서 Wayland에 관심이 있으셔서 발표 진행 동안 질문들을 통해 내용을 잘 이해할 수 있었습니다.
류창우님께서는 RepRap 이라는 주제로 3D Printer 를 직접 만들었던 경험을 발표해 주셨습니다. Self Replicating 이라는 흥미로운 방법으로 3D printer machine 이 진화해가는 과정을 알수 있었고, 제작과정 및 부품 조달 방법까지 공유해주셔서 3D Printer 제작의 생생한 과정을 알 수 있었습니다.



더운 날씨에도 불구하고 7월 모임에 참여해주신 모든 분들께 감사드리고, 8월 모임에는 더 유익한 내용으로 만났으면 좋겠습니다.

2012년 5월 29일 화요일

5회 GNOME Hackfest 후기


안녕하세요.

지난주 토요일, GNOME Hackfest가 있었습니다. KLDP Codefest이후, 오픈소스 커뮤니티에서 이런 행사를 연 것은 처음이 아닐까 생각합니다. 물론, 최근은 헤카톤이나 다음, 네이버 내에 비슷한 행사가 있었습니다. 






다행이 빈자리가 없을 정도로 (아마 14분이 오신듯) 많은 분들이 오셨는데,  진행된 작업을 소개하면, 다음과 같습니다. 

이진규: ubuntu에 들어있는 ibus가 appindicator가 없을 때에도 패널에 나타나는 작업
문현진: GNOEM 기반 프로그래밍을 시작
홍영기: Chromium의 find in page 에 기능 추가
김남형: 리눅스 성능 분석 도구인 perf의 GUI 지원을 추가/향상
허준회: WebKitGkt+에 Clutter로 Accelerated Compositing구현
조성호: 한영전환이 안될 경우 혹은 입력기가 동작하지 않을 경우 영문 또는 특수 문자들을 입력하면 두벌식과
세벌식 키배치에 따라 한글문장으로 바꾸어주는 #gnome IRC 채널 봇 플러그인 모듈 (한글 입력 오토마타?) 을 자바로 구현

아마도 어떤 문제를 시간내에 해결하신 분은 없었던 것 같습니다. 원래는 뭔가 그 시간내에 어느 정도 결과를 내고 마지막에 서로 보여줄 수 있는 자리가 되어야 하고, 가능하면 함께 뭔가 문제를 해결하면 좋은데, 충분한 준비가 없어서 그런 자리를 만들지는 못했습니다.

다음달에는 Hackfest를 어떻게 진행할까 고민 중입니다. 우선, 창우님이 3D printing하는 Open Source Project를 소개해주시기로 했고, 남은 시간에는 뭔가 의미있고 함께 할 수 작업을 진행하려고 합니다.

고맙습니다.


2012년 5월 3일 목요일

4회 GNOME Tech talks 후기

이번 GNOME Tech Talks에서는 허준회님의 WebKit2Gtk+ 소개와 홍영기님의 Tracker소개가 있었습니다.

WebKit2Gtk+ 발표에서는 WebKit2에서 지원하는 Multi-process model의 특징과 어떻게 구현되었는지 WebKit2Gtk+ 포트 코드를 보면서 소개하였습니다. 그리고, 한글 입력을 위해 IME를 Web Process와 UI Process에서 어떻게 enable하고 signal을 처리하는지 설명하였습니다.

Tracker는 Desktop Search Engine으로 Mac OSX의 Spotlight, Google Desktop Search와 비슷한 기능을 가지고 있습니다. 사용자 하드디스크에 저장된 파일과 Twitter, RSS, Facebook과 같은 서비스에 저장된 사용자 컨텐츠를 인덱싱하여 빠르게 검색할 수 있도록 합니다.

자세한 내용은 발표 자료를 참고하세요.
https://gitorious.org/gnome-korea/gnome-tech-talks/trees/master/2012.04

그리고, 다음달 부터는 Hackfest를 시작합니다. 함께 모여서 즐겁게 hacking할 수 있는 좋은 시간이 될 것 같습니다.

고맙습니다.

2012년 4월 24일 화요일

제 4회 GNOME Tech Talks 안내


그놈 한국 커뮤니티에서 아래와 같이 제 3회 GNOME Tech Talks (기술 세미나)를 개최합니다.


  • WebKit2Gtk+ 소개 - 허준회(joone)
  • Tracker 소개 - 홍영기
  • GNOME 토론 - 모두

  • 일시: 4월 28일 토요일 오후 1시 30 ~ 4시 30 분
    장소: 교대역 토즈

  • 신청자가 많아지면, 다음 세미나에 발표 가능한 분을 우선으로 신청을 받겠습니다.
    대기자 명단에 계신 분 중 발표 가능한 분은 발표 주제와 함께 댓글을 남겨주세요

    장소 후원: 구글 코리아 http://goo.gl/P57iM

    신청: http://onoffmix.com/event/6604

    2012년 4월 3일 화요일

    3회 GNOME Tech talks 후기

    지난 토요일, 3회 GNOME Tech Talks가 열렸습니다.

    먼저 최환진님께서 오랫동안 개발해온 libhangul Project를 소개하였습니다. 환진님은 한글입력기 Nabi Project로 잘 알려져있고, 현재 libhangul Project과 ibus Project에서 ibus-hangul 모듈을 maintain하고 있습니다. 이번 발표에서 libhangul이 어떻게 동작하는지 코드를 보면서 쉽게 설명하였고, 현재 iBus에서 발생하는 한글 입력의 문제점을 소개하였습니다.  잠깐 소개하자면, iBus을 사용할 때, 조합중인 한글 문자가 여러번 입력되는 현상이 있습니다. 이것은 입력기를 사용하는 프로그램이 마우스 조작 등으로 조합(preedit) 상태를 reset해주어야 하는데, 그렇지 않아 다른 cursor위치에서 commit이 발생하는 문제입니다. 중국어, 일본어가 preedit상태에서 enter를 반드시 눌러야 입력이 완료되는데, 한글의 경우 그런 절차가 필요하지 않아 많은 리눅스 프로그램에서 비슷한 문제가 발생하고 있습니다. 이 부분은 각 애플리케이션 개발자에게 IME를 반드시 reset 하도록 알리는 것이 필요하다고 합니다. 참고로, 발표 자료와 코드는 여기에 올려놓았습니다.

    두번째 발표로 Glade maintainer인 Tristan Van Berkom님이 GTK+ UI를 쉽게 만들 수 있는 도구인,  Glade를 소개했습니다. 이번 발표에서 Glade Project의 다소 무시무시(?)한 역사와 어떻게 동작하고 custom widget을 추가하는 방법등을 소개하였습니다. 그리고, 대부분의 GNOME Application이 Glade를 사용해서 UI를 작성하고 있다는 사실도 알려주었습니다.


    마지막으로, 오창석님이 COGL를 소개하였습니다. COGL은 다양한 유틸리티와 함께  다양한 GPU에서 일관된 방식으로 OpenGL을 쉽게 사용하도록 API를 제공합니다. 이번 발표에서 COGL의 특징과 간단한 예제 코드를 설명하였습니다. 자세한 내용은 발표 자료를 참고하세요.

    발표하신 분들과 참석한 분들께 감사드리며, 4월 Tech Talks에서도 만나뵙기를 바랍니다.




    2012년 3월 27일 화요일

    제 3회 GNOME Tech Talks (기술 세미나) 안내

    그놈 한국 커뮤니티에서 아래와 같이 제 3회 GNOME Tech Talks (기술 세미나)를 개최합니다.

    libhangul & ibus-hangul 소개 - 최환진
    Cogl 소개 - 오창석(changseok)
    Glade 소개 - Tristan Van Berkom (tristan)
    WebKit2Gtk+ 소개 - 허준회(joone)

    일시: 3월 31일 토요일 오후 1시 30 ~ 4시 30 분
    장소: 교대역 토즈

    신청자가 많아지면, 다음 세미나에 발표 가능한 분을 우선으로 신청을 받겠습니다.
    대기자 명단에 계신 분 중 발표 가능한 분은 발표 주제와 함께 댓글을 남겨주세요

    장소 후원: 구글 코리아 http://goo.gl/P57iM

    신청: http://onoffmix.com/event/5919

    2012년 2월 26일 일요일

    2회 GNOME Tech talks 후기

    지난 토요일 2회 GNOME Tech Talks가 열렸습니다. 양선진님의 GObject 객체지향 프로그래밍과 차영호님이 Linux Multimedia Framework가 있었습니다. 두 분의 강의나 너무나 흥미 진진하고 많은 토론이 오고가서 3시간을 다 채우는 바람에 나머지 발표는 다음달로 미루어졌습니다.


    먼저 양선진님 지난 10년간 GNOME기술을 이용해서 보안 카메라 시스템을 개발해왔습니다. 그 동안 얻은 값진 경험를 여기 블로그에 소개해주었고, 이번에는 signal 활용 방법과  gobject property binding하는 예제를 설명해주었습니다.


    차영호님은 Linux Multimedia의  변화 발전을, Open Sound System을 시작으로 ALSA, ESD, PulseAudio, MPlayer, .., GStreamer 등을 소개하였습니다. 각각의 프로젝트가 왜 시작되었고 또 다른 프로젝트는 어떤 문제를 해결하려고 나타났는지 설명해주었습니다.

    두 분 발표 감사드립니다.

    2012년 2월 25일 토요일

    리눅스 멀티미디어 환경 소개

    어제 있었던 그놈 기술세미나에서 발표했던 자료입니다.

    언제나 부족한 자료지만 -,.-;; 도움되시길 빕니다.

    2012년 2월 24일 금요일

    GObject 객체 지향 프로그래밍 (5)

    거의 2년만에 GObject 객체 지향 프로그래밍 연재 글을 포스팅합니다. 사실 이 글의 일부는 예전에 작성해 둔 것인데, 이번 GNOME Tech Talks에서 발표 하나를 맡게 되면서, 슬라이드 자료를 따로 만들 시간은 없고 그렇다고 오래된 자료를 재탕하는 건 실례인 것 같아 조금 보완해서 작성했습니다. 참고로, GObject 개념을 잘 모르는 분이라면 이전 연재 글을 먼저 읽어 보시면 도움이 될 수 있습니다. :)

    1. GObject 객체 지향 프로그래밍 (1)

    2. GObject 객체 지향 프로그래밍 (2)

    3. GObject 객체 지향 프로그래밍 (3)

    4. GObject 객체 지향 프로그래밍 (4)

    5. 싱글턴(Singleton) GObject 객체 만들기

    6. GObject 속성 직렬화(Serialization)하기


    GObject 객체 지향 시스템을 구성하는 여러가지 개념 중 상속(inheritance), 참고 카운터(reference counting), 속성(properties) 등에 대해서는 지난 글에서 이미 소개했습니다. 아직 GObject 라이브러리에서 소개하지 않은 개념이 아직 많이 남아 있지만, 그 중에서 가장 중요한 것 중 하나는 바로 시그널(signals)이 아닐까 생각합니다. 속성이 변경되었을때 자동으로 호출되는 콜백 함수를 등록해서 사용하는 방법을 설명할 때 약간 소개했지만, 아무래도 그걸로는 부족하기 때문에 이번 글은 시그널의 개념과 사용 방법, 그리고 속성 바인딩을 정리해 보았습니다.

    간단한 클러터 기반 시계


    언제나 그렇듯이 재미없는 예제 소스를 먼저 보여드립니다. 이 소스를 컴파일해서 실행하면 위 그림과 같은 시계가 동작합니다.
    /* myclock1.c */

    /*****************************************************************************/

    #include <glib-object.h>

    #define MY_TYPE_CLOCK (my_clock_get_type ())
    #define MY_CLOCK(obj) \
    (G_TYPE_CHECK_INSTANCE_CAST ((obj), MY_TYPE_CLOCK, MyClock))
    #define MY_CLOCK_CLASS(klass) \
    (G_TYPE_CHECK_CLASS_CAST ((klass), MY_TYPE_CLOCK, MyClockClass))
    #define MY_IS_CLOCK(obj) \
    (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MY_TYPE_CLOCK))
    #define MY_IS_CLOCK_CLASS(klass) \
    (G_TYPE_CHECK_CLASS_TYPE ((klass), MY_TYPE_CLOCK))
    #define MY_CLOCK_GET_CLASS(obj) \
    (G_TYPE_INSTANCE_GET_CLASS ((obj), MY_TYPE_CLOCK, MyClockClass))

    typedef struct _MyClock MyClock;
    typedef struct _MyClockClass MyClockClass;
    typedef struct _MyClockPrivate MyClockPrivate;

    struct _MyClock
    {
    GObject parent;
    MyClockPrivate *priv;
    };

    struct _MyClockClass
    {
    GObjectClass parent_class;
    };

    enum
    {
    PROP_0,
    PROP_DATE_TIME,
    PROP_LAST
    };

    struct _MyClockPrivate
    {
    GDateTime *datetime;
    guint timeout;
    };

    G_DEFINE_TYPE (MyClock, my_clock, G_TYPE_OBJECT);

    static GParamSpec *props[PROP_LAST];

    GDateTime *
    my_clock_get_date_time (MyClock *clock_)
    {
    g_return_val_if_fail (MY_IS_CLOCK (clock_), NULL);

    return g_date_time_ref (clock_->priv->datetime);
    }

    static void
    my_clock_set_date_time (MyClock *clock_,
    GDateTime *datetime)
    {
    g_date_time_unref (clock_->priv->datetime);
    clock_->priv->datetime = g_date_time_ref (datetime);
    g_object_notify_by_pspec (G_OBJECT (clock_), props[PROP_DATE_TIME]);
    }

    static gboolean
    my_clock_update (gpointer data)
    {
    MyClock *clock_ = data;
    GTimeVal now;
    GDateTime *datetime;
    guint interval;

    g_get_current_time (&now);

    datetime = g_date_time_new_from_timeval_local (&now);
    my_clock_set_date_time (clock_, datetime);
    g_date_time_unref (datetime);

    interval = (1000000L - now.tv_usec) / 1000L;
    clock_->priv->timeout =
    g_timeout_add_full (G_PRIORITY_HIGH_IDLE,
    interval,
    my_clock_update,
    g_object_ref (clock_),
    g_object_unref);

    return FALSE;
    }

    static void
    my_clock_set_property (GObject *object,
    guint param_id,
    const GValue *value,
    GParamSpec *pspec)
    {
    switch (param_id)
    {
    default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
    break;
    }
    }

    static void
    my_clock_get_property (GObject *object,
    guint param_id,
    GValue *value,
    GParamSpec *pspec)
    {
    MyClock *clock_ = MY_CLOCK (object);

    switch (param_id)
    {
    case PROP_DATE_TIME:
    g_value_set_boxed (value, clock_->priv->datetime);
    break;
    default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
    break;
    }
    }

    static void
    my_clock_finalize (GObject *gobject)
    {
    MyClockPrivate *priv = MY_CLOCK (gobject)->priv;

    g_date_time_unref (priv->datetime);
    g_source_remove (priv->timeout);

    G_OBJECT_CLASS (my_clock_parent_class)->finalize (gobject);
    }

    static void
    my_clock_class_init (MyClockClass *klass)
    {
    GObjectClass *obj_class = G_OBJECT_CLASS (klass);
    GParamSpec *pspec;

    obj_class->set_property = my_clock_set_property;
    obj_class->get_property = my_clock_get_property;
    obj_class->finalize = my_clock_finalize;

    g_type_class_add_private (klass, sizeof (MyClockPrivate));

    pspec = g_param_spec_boxed ("datetime",
    "Date and Time",
    "The date and time to show in the clock",
    G_TYPE_DATE_TIME,
    G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
    props[PROP_DATE_TIME] = pspec;
    g_object_class_install_property (obj_class, PROP_DATE_TIME, pspec);
    }

    static void
    my_clock_init (MyClock *clock_)
    {
    MyClockPrivate *priv;

    priv = clock_->priv =
    G_TYPE_INSTANCE_GET_PRIVATE (clock_,
    MY_TYPE_CLOCK,
    MyClockPrivate);

    priv->datetime = g_date_time_new_now_local ();
    priv->timeout = 0;

    my_clock_update (clock_);
    }

    MyClock *
    my_clock_new (void)
    {
    return g_object_new (MY_TYPE_CLOCK, NULL);
    }

    /*****************************************************************************/

    #include <clutter/clutter.h>

    static void
    clock_datetime_changed (GObject *object,
    GParamSpec *pspec,
    gpointer data)
    {
    MyClock *clock_ = MY_CLOCK (object);
    ClutterActor *text = data;
    GDateTime *datetime;
    gchar *str;

    datetime = my_clock_get_date_time (clock_);
    str = g_date_time_format (datetime, "%x\n%H:%M:%S");

    clutter_text_set_text (CLUTTER_TEXT (text), str);

    g_free (str);
    g_date_time_unref (datetime);
    }

    int
    main (int argc,
    char **argv)
    {
    ClutterActor *stage;
    ClutterActor *text;
    ClutterConstraint *constraint;
    MyClock *clock_;

    if (clutter_init (&argc, &argv) != CLUTTER_INIT_SUCCESS)
    return -1;

    /* stage */
    stage = clutter_stage_get_default ();
    clutter_actor_set_size (stage, 320, 240);
    clutter_stage_set_color (CLUTTER_STAGE (stage), CLUTTER_COLOR_Black);
    clutter_stage_set_user_resizable (CLUTTER_STAGE (stage), TRUE);

    /* text */
    text = clutter_text_new_full ("Sans Bold 20",
    "NOW",
    CLUTTER_COLOR_LightButter);
    clutter_container_add_actor (CLUTTER_CONTAINER (stage), text);
    clutter_text_set_line_alignment (CLUTTER_TEXT (text), PANGO_ALIGN_CENTER);

    /* align text in center of stage */
    constraint =
    clutter_align_constraint_new (stage, CLUTTER_ALIGN_X_AXIS, 0.5);
    clutter_actor_add_constraint (text, constraint);

    constraint =
    clutter_align_constraint_new (stage, CLUTTER_ALIGN_Y_AXIS, 0.5);
    clutter_actor_add_constraint (text, constraint);

    /* clock */
    clock_ = my_clock_new ();
    g_signal_connect (clock_,
    "notify::datetime",
    G_CALLBACK (clock_datetime_changed),
    text);

    clutter_actor_show (stage);

    clutter_main ();

    return 0;
    }

    소스 코드를 간단하게 설명하면, MyClock 객체가 1초 간격으로 현재 시간을 얻어와 자신의 datetime 속성을 갱신하면[my_clock_update()], 속성이 변경되었을때(notify::datetime) 자동으로 호출되는 콜백 함수를[clock_datetime_changed()] 등록해 자동으로 클러터 텍스트(ClutterText)를 이용해 화면에 표시합니다.

    이제 이 소스 코드를 두 가지 방법으로 확장하려고 합니다. 첫번째 방법은 속성 바인딩(property binding)을 이용해 시그널을 사용하지 않는 방법이고, 두번째 방법은 시간이 변경되었을때 호출되는 진짜(!) 시그널을 추가하는 것입니다.

    속성 바인딩 (Property Binding)

    속성 바인딩(property binding)이란 두 GObject 객체간의 두 속성을 묶는 걸 말합니다. 여기서 묶는다는 의미는, 한 객체의 속성 값이 변하면 다른 객체의 속성 값도 자동으로 변한다는 의미입니다. 물론 묶으려는 두 속성은 같은 형(type)이어야 합니다. 그런데, 위 예제의 경우 MyClock:datetime 속성과 ClutterText:text 속성은 형(type)이 다릅니다. 그래서, 위 소스를 다음과 같이 수정합니다. (변경된 부분만 보여 드립니다)
    /* myclock2.c */

    /* ... */

    enum
    {
    PROP_0,
    PROP_DATE_TIME,
    PROP_TEXT,
    PROP_LAST
    };

    struct _MyClockPrivate
    {
    GDateTime *datetime;
    guint timeout;
    gchar *text;
    };

    /* ... */

    const gchar *
    my_clock_get_text (MyClock *clock_)
    {
    g_return_val_if_fail (MY_IS_CLOCK (clock_), NULL);

    return clock_->priv->text;
    }

    static void
    my_clock_set_date_time (MyClock *clock_,
    GDateTime *datetime)
    {
    g_date_time_unref (clock_->priv->datetime);
    clock_->priv->datetime = g_date_time_ref (datetime);
    g_object_notify_by_pspec (G_OBJECT (clock_), props[PROP_DATE_TIME]);

    g_free (clock_->priv->text);
    clock_->priv->text = g_date_time_format (datetime, "%x\n%H:%M:%S");
    g_object_notify_by_pspec (G_OBJECT (clock_), props[PROP_TEXT]);
    }

    /* ... */

    static void
    my_clock_finalize (GObject *gobject)
    {
    /* ... */
    g_free (priv->text);
    /* ... */
    }

    static void
    my_clock_class_init (MyClockClass *klass)
    {
    /* ... */

    pspec = g_param_spec_string ("text",
    "Text",
    "The text of the date and time",
    NULL,
    G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
    props[PROP_TEXT] = pspec;
    g_object_class_install_property (obj_class, PROP_TEXT, pspec);
    }

    static void
    my_clock_init (MyClock *clock_)
    {
    /* ... */
    priv->text = NULL;
    /* ... */
    }

    /* ... */

    int
    main (int argc,
    char **argv)
    {
    /* ... */

    /* clock */
    clock_ = my_clock_new ();
    g_object_bind_property (clock_, "text",
    text, "text",
    G_BINDING_SYNC_CREATE);

    /* ... */
    }

    위 코드에서 변경된 내용은, MyClock에 문자열 형식의 text 속성을 추가하고[my_clock_class_init()], datetime 속성을 갱신할때 text 속성도 함께 갱신하도록 한 다음[my_clock_set_date_time()], 기존 속성 변경(notify::datetime)에 대한 g_signal_connect() 함수 호출 대신 g_object_bind_property() 함수를 이용해 두 객체의 속성을 묶었다는 점입니다. 여기서 핵심은 물론 g_object_bind_property() 함수인데, 이 함수는 GLib 2.26 버전에 추가되었으며 예전에 소개한 ExoBinding과 사용법이 거의 유사합니다. 물론, 옵션을 통해 바인딩하는 시점부터 값을 동기화할 지(G_BINDING_SYNC_CREATE), 단방향이 아닌 양방향으로 동기화할 지(G_BINDING_BIDIRECTIONAL) 등을 지정할 수도 있습니다. 이처럼, 위의 코드에서 볼 수 있듯이, 속성 바인딩을 이용하면 매번 콜백함수를 만들지 않고도 간단하게 코드 몇 줄로 원하는 객체 속성간의 동기화(synchronization)를 처리할 수 있습니다.

    여담이지만, 처음 이 기능을 접했을때 맥, 아이폰 응용 프로그램을 개발하기 위해 XCode에서 마우스 드래그 만으로 객체 속성간 바인딩이 지원되는 것처럼, 코딩이 아닌, Glade 같은 GUI 도구에서 위젯 속성간 바인딩이 지원되면 참 편하지 않을까 하는 생각이 들었던 적도 있습니다.

    시그널 (Signals)

    GObject 공식 매뉴얼에 의하면 시그널(signals)은 메시지 전달 시스템을 구성하는 두가지 기술 중 하나입니다. 하나는 클로저(closures)이고 다른 하나가 시그널(signals)인데, 클로저가 콜백(callback) 함수를 자료구조로 정의한 거라면, 시그널은 이 콜백함수를 등록하고 호출하는 알고리즘을 정의한 것이라고 이해해도 무방합니다.

    클로저를 다시 정의하지 않고 함수 포인터를 직접 사용해도 될 것 같은데 이를 객체로 정의한 이유는 여러가지가 있지만, 무엇보다도 콜백함수에 전달되는 인자(parameters) 목록과 인자 형(type)에 대한 처리(marshalling) 때문입니다. C/C++ 언어에서 함수 호출시 스택에 쌓이는 인자를 가공하는 것 뿐 아니라, GObject가 지원하는 여러 언어에 대한 바인딩을 위해 더 일반화된 클로저(closure) 객체가 필요합니다.

    아무튼, 이론적인 설명은 그만하고 다시 본론으로 돌아와서, 위 예제에서 구현한 MyClock 객체가 생각보다 잘 설계되고 동작하는 바람에(...) 프로그램 전체에서 이 객체를 사용하기로 결정했다고 가정해 봅시다. 수많은 모듈과 수많은 객체에서 전역 시계 객체에 속성 알림(notify) 시그널을 연결합니다. 그리고 그때마다 my_clock_get_date_time()을 호출해 현재 시간을 가져와서 처리합니다. 물론 이 예제에서 전달되는 GDateTime 구조체는 참조 카운터 방식으로 관리되기 때문에 구조체 전달시 많은 오버헤드가 없지만, 문자열을 복사하거나 많은 데이터가 전달되는 경우라면 무시할 수 없는 상황이 발생합니다. 그래서, 위 첫번째 소스를 다음과 같이 조금 수정합니다.
    /* myclock3.c */

    /* ... */

    struct _MyClockClass
    {
    GObjectClass parent_class;

    /* signals */
    void (*changed) (MyClock *clock_,
    GDateTime *datetime);
    };

    enum
    {
    SIGNAL_CHANGED,
    SIGNAL_LAST
    };

    /* ... */

    static guint signals[SIGNAL_LAST];

    /* ... */

    static void
    my_clock_set_date_time (MyClock *clock_,
    GDateTime *datetime)
    {
    /* ... */
    }

    static void
    my_clock_real_changed (MyClock *clock_,
    GDateTime *datetime)
    {
    my_clock_set_date_time (clock_, datetime);
    }

    static gboolean
    my_clock_update (gpointer data)
    {
    /* ... */

    datetime = g_date_time_new_from_timeval_local (&now);
    g_signal_emit (clock_, signals[SIGNAL_CHANGED], 0, datetime);
    g_date_time_unref (datetime);

    /* ... */
    }

    static void
    my_clock_class_init (MyClockClass *klass)
    {
    /* ... */

    klass->changed = my_clock_real_changed;

    signals[SIGNAL_CHANGED] =
    g_signal_new ("changed",
    G_TYPE_FROM_CLASS (klass),
    G_SIGNAL_RUN_LAST,
    G_STRUCT_OFFSET (MyClockClass, changed),
    NULL,
    NULL,
    g_cclosure_marshal_VOID__POINTER,
    G_TYPE_NONE,
    1,
    G_TYPE_POINTER);
    }

    /* ... */

    static void
    clock_changed (MyClock *clock_,
    GDateTime *datetime,
    gpointer user_data)
    {
    ClutterActor *text = user_data;
    gchar *str;

    str = g_date_time_format (datetime, "%x\n%H:%M:%S");
    clutter_text_set_text (CLUTTER_TEXT (text), str);
    g_free (str);
    }

    int
    main (int argc,
    char **argv)
    {
    /* ... */

    /* clock */
    clock_ = my_clock_new ();
    g_signal_connect (clock_,
    "changed",
    G_CALLBACK (clock_changed),
    text);

    /* ... */
    }

    바로 위 코드에 보이는 것처럼 g_signal_connect() 호출시 연결하는 시그널 이름과 콜백 함수[clock_changed()]가 더 단순하고 효율적으로 변경된 걸 확인할 수 있습니다. 콜백 함수 호출시 전달되는 인수를 그냥 사용하면 되니까 오버헤드가 매우 많이 줄어들 수 밖에 없습니다. 하지만 시그널을 정의해서 사용하는게 단순히 성능과 효율 때문만은 아닙니다. 위 예제에서는 속성이 변경되었을 때 발생하는 시그널을 정의했지만, 일반적으로 시그널은 속성 만으로 표현할 수 없는 객체의 상태 변화를 알리기 위해서 많이 사용합니다.(예: ClutterActor::enter-event 시그널) 또한 속성의 변화를 통해 알 수 있더라도 더 쉽고 명확하게 이를 전파하기 위해서도 사용합니다.(예: ClutterActor::hide 시그널과 ClutterActor:visible 속성)

    더 나아가, 시그널은 상태 변화 뿐 아니라 객체의 동작 방식을 외부에서 제어할 수 있도록 유연성을 제공하는데도 사용합니다. 더 자세한 이해를 위해 시그널 함수 포인터부터 설명하자면, 클래스 구조체 안에 선언된 시그널 함수 포인터[MyClockClass::changed()]는 일종의 가상 함수(virtual function) 역할을 하면서, 시그널이 발생하면(emit) g_signal_connect()를 이용해 등록된 사용자 콜백함수가 모두 실행된 뒤 맨 나중에 실행되거나 혹은 사용자 콜백 함수보다 먼저 실행됩니다. 따라서 필요 없을 경우 그냥 NULL로 내버려두어도 상관없지만, 위 예제에서는 클래스 생성시 my_clock_real_changed() 함수를 등록시켰습니다. my_clock_real_changed()는 다시 실제로 datetime 속성을 갱신하는 작업을 처리하는 my_clock_set_date_time()을 호출합니다. 그리고, 기존 시간 갱신 함수[my_clock_update()]에서는 직접 my_clock_set_date_time()을 호출하지 않고, 시그널을 발생시켜[g_signal_emit()] 작업을 처리합니다.

    왜 이렇게 복잡하게 일을 나누어 처리할까요? 이렇게 구현하면 몇 가지 장점이 있기 때문입니다. 예를 들어 위 예제에서는 datetime 속성이 읽기 전용으로 선언되어 있기 때문에 외부에서 그 값을 변경할 수 없습니다. 하지만, 외부에서 직접 g_signal_emit_by_name() 등을 이용해 시그널을 발생시키면 시그널에 연결된 모든 콜백 함수 뿐 아니라 my_clock_real_changed() 함수까지도 간접적으로 호출되어 작업을 처리하도록 할 수 있습니다. 게다가 만일 시그널에 연결된 콜백 함수 중 하나가 어떤 이유로 g_signal_stop_emission_by_name() 등을 호출하면 이후 실행될 콜백 함수나 my_clock_real_changed() 함수가 호출되지 않게 할 수도 있고, 심지어 객체의 클래스에 등록된 함수 포인터에 직접 자신만의 콜백 함수를 등록해서 원래 작업이 아예 수행되지 않게 할 수도 있습니다.

    참고로, GTK+ / Clutter 등과 같은 GObject 기반 그래픽 툴킷 시스템은 대부분 이 시그널 콜백 함수 메커니즘을 이용해 커스텀 위젯을 만들거나 기존 액터를 상속받아 사용자가 마음껏 기능을 확장할 수 있는 길을 열어 두었습니다.(예: clutter_actor.c:clutter_actor_real_paint() 소스 참고)

    시그널 객체는 g_signal_new() 함수를 이용해 생성한 뒤 전역 signals[] 배열에 ID를 저장해 둡니다. 이렇게 저장한 시그널 ID는 g_signal_emit() 함수 호출시 사용합니다. 물론 이렇게 ID를 따로 저장하지 않고 g_signal_emit_by_name()을 사용해 시그널 이름으로 직접 시그널을 발생시켜도 되지만, 어차피 내부적으로 시그널 이름을 ID로 변환하는 과정을 거치기 때문에 효율을 위해 객체 구현시 관례적으로 이런식으로 작성합니다. 물론 객체 외부에서는 시그널 ID를 모르기 때문에 어쩔 수 없이 g_signal_emit_by_name()을 사용해야 합니다.

    g_signal_new() 함수의 인자 중에서 중요한 항목만 설명하면, 첫번째 항목은 시그널 이름을 정의하고, 세번째 항목은 시그널 함수 포인터가 맨 나중에 실행될 지(G_SIGNAL_RUN_LAST), 또는 가장 먼저 실행될 지(G_SIGNAL_RUN_FIRST) 등을 지정합니다. 네번째 항목은 클래스 구조체에 정의된 시그널 함수 포인터 위치를 지정하고, 여덟번째는 시그널 콜백 함수의 리턴 형(type), 아홉번째는 콜백 함수에게 전달할 인자의 갯수, 열번째부터는 전달될 인자의 형(types)을 차례대로 정의합니다.

    g_signal_new() 함수의 일곱번째 인자는 함수 호출시 인자를 처리하는 마샬링(marshalling) 함수를 지정하는데, 함수의 리턴 형(type)과 인자 목록, 인자의 각 형(type)이 정확히 일치되는 함수를 지정해야 합니다. 그런데 원하는 형태의 마샬링 함수를 GLib에서 기본으로 제공하지 않을 경우 glib-genmarshal 프로그램을 이용해 직접 C 소스 코드를 생성해서 사용해야 했는데, GLib 2.30 버전부터는 그냥 NULL을 지정하면 libffi 라이브러리를 이용해 구현한 g_cclosure_marshal_generic() 함수가 기본으로 호출되어, 알아서 자동으로 마샬링을 처리합니다.

    정리하자면, GObject 시그널은 모델-뷰(model-view) 구조나 관찰자 패턴(observer pattern)을 구현하는데 사용하기도 하지만, 더 복잡한 객체 지향 시스템을 설계할 때도 유용합니다. 하지만, 여기서는 시그널의 특징과 개념만 설명하느라 전체 기능의 반의 반도 소개되지 않은 셈입니다. 따라서 더 깊은 이해와 활용을 원하시면 반드시 참고 매뉴얼을 한 번 정독하시길 권합니다.

    그리고...

    이 글에 사용된 모든 예제 소스를 실행해보고 싶으신 분은 직접 타이핑하지 마시고, 소스 코드를 다운로드한 뒤, 클러터(clutter) 개발 패키지 설치 후 make 명령으로 컴파일하면 됩니다.

    그리고, 다른 프로그래머가 왜 C++, Java, Python 처럼 좋은 언어 놔두고 C 언어 기반에서 복잡한 GObject 같은 걸 가지고 객체 지향 프로그래밍을 할려고 애쓰냐고 물어본다면, 리눅스 커널 메일링 리스트 FAQ에 있는 유명한 다음 구절을 해석해서 미소지으며 알려주시기 바랍니다.
    What's important about object-oriented programming is the techniques, not the languages used.

    뭐, 모든 도구는 필요한 곳이 반드시 있으니까 계속 존재합니다. 다만 내가 아직 그 쓰임새를 알지 못할 뿐이죠... :)

    (이 글은 개인 블로그에 함께 게재되어 있습니다)

    2012년 2월 1일 수요일

    제2회 GNOME Tech Talks 안내

    그놈 한국 커뮤니티에서 아래와 같이 제 2회 GNOME Tech Talks (기술 세미나)를 개최합니다.

    • GObject 객체 지향 프로그래밍 - 양선진 
    • 리눅스 멀티미디어 소개 - 차영호 (gana코코아) 
    • Cogl 소개 - 오창석(changseok) 
    • WebKit2Gtk+ 소개 - 허준회(joone) 
    일시: 2월 25일 토요일 오후 1시 30 ~ 4시 30 분
    장소: 교대역 토즈

    신청자가 많아지면, 다음 세미나에 발표 가능한 분을 우선으로 신청을 받겠습니다.
    대기자 명단에 계신 분 중 발표 가능한 분은 발표 주제와 함께 댓글을 남겨주세요

    장소 후원: 구글 코리아 http://goo.gl/P57iM

    참가 신청: http://onoffmix.com/event/5307

    GTypeModule 을 이용한 플러그 인 작성하기

    1회 그놈 기술 세미나에서 발표한 내용입니다.

    http://goo.gl/JWPAX

    설명에 사용된 완전한 소스 코드는 아래 github 에서 보실 수 있습니다.

    https://github.com/eleheim/aniamltest


    GObject Introspection 소개

    1회 그놈 기술 세미나에서 발표한 자료를 공유합니다. (사골처럼 음청 우려먹네요;; )

    2012년 1월 29일 일요일

    WebKitGtk+ 소개 및 사용법

    GNOME3 Launch Party때 발표한 자료와 1회 그놈 기술 세미나에서 발표한 자료를 함께 올립니다. WebKitGtk+를 이해하는데 도움이 되었으면 좋겠습니다. 소스코드도 조만간 정리해서 올리겠습니다.

    1회 GNOME 기술 세미나 후기

    안녕하세요.
    지난 1월 28일 1회 GNOME 기술 세미나가 있었습니다. 추운 날씨에도 불구하고 많은 분들이 참석했습니다. 오랜만에 GNOME 기술에 대한 깊은 이야기가 오고간 자리였던 것 같습니다. 발표 자료는 그놈 한국 블로그를 통해 공개할 예정입니다. 우선, 세미나 현장 사진을 몇 장 올려봅니다.

    이재균님이 GTypeModule 로 플러그인 만드는 과정을 소개하였습니다.

     차영호님이 GObject Introspection의 동작과 사용예를 소개하였습니다.
     
    제가 WebKitGtk+의 구조와 API 사용방법을 간단하게 소개하였습니다. 

    지난번 GNOME3 출시 기념 모임 때, 단체 사진을 못찍어서 이번에 단체 사진을 찍어보았습니다.
    구글 코리아에서 세미나 장소와 기념품을 후원해주셨습니다.

    그럼, 다음달에 열릴 2회 세미나 때 뵙겠습니다.

    2012년 1월 4일 수요일

    GNOME 기술 세미나 안내


    안녕하세요. 

    그놈 한국 커뮤니티에서 아래와 같이 제 1회 GNOME 기술 세미나를 개최합니다.

    이재균:GTypeModule 로 플러그인 만드는 과정
    차영호: GObject Introspection
    허준회: WebKitGtk+ 내부와 사용 방법

    일시: 1월 28일 토요일 오후 1시 ~ 4시
    장소: 선릉역 토즈 

    장소는 구글 코리아에서 후원해주셨습니다. 고맙습니다~

    신청자가 많아지면, 다음 세미나에 발표 가능한 분을 우선으로 신청을 받겠습니다. 
    대기자 명단에 계신 분 중 발표 가능한 분은 발표 주제와 함께 댓글을 남겨주세요.